From 6fa24363ac4db37ee4485535d6f7b57bec6d0069 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 12 Oct 2024 01:59:03 +0000 Subject: [PATCH 001/391] chore(deps): weekly `cargo update` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v0.8.6 -> v0.8.7 Updating alloy-json-abi v0.8.6 -> v0.8.7 Updating alloy-primitives v0.8.6 -> v0.8.7 Updating alloy-sol-macro v0.8.6 -> v0.8.7 Updating alloy-sol-macro-expander v0.8.6 -> v0.8.7 Updating alloy-sol-macro-input v0.8.6 -> v0.8.7 Updating alloy-sol-type-parser v0.8.6 -> v0.8.7 Updating alloy-sol-types v0.8.6 -> v0.8.7 Updating async-compression v0.4.13 -> v0.4.14 Updating aws-sdk-kms v1.46.0 -> v1.47.0 Updating aws-sdk-sso v1.45.0 -> v1.46.0 Updating aws-sdk-ssooidc v1.46.0 -> v1.47.0 Updating aws-sdk-sts v1.45.0 -> v1.46.0 Updating aws-smithy-runtime v1.7.1 -> v1.7.2 Updating cc v1.1.28 -> v1.1.30 Updating clap v4.5.19 -> v4.5.20 Updating clap_builder v4.5.19 -> v4.5.20 Updating clap_complete v4.5.32 -> v4.5.33 Updating derive_builder v0.20.1 -> v0.20.2 Updating derive_builder_core v0.20.1 -> v0.20.2 Updating derive_builder_macro v0.20.1 -> v0.20.2 Updating js-sys v0.3.70 -> v0.3.72 Updating lru v0.12.4 -> v0.12.5 Updating newtype-uuid v1.1.0 -> v1.1.2 Updating proc-macro2 v1.0.86 -> v1.0.87 Updating scc v2.2.0 -> v2.2.1 Updating sdd v3.0.3 -> v3.0.4 Updating syn-solidity v0.8.6 -> v0.8.7 Updating wasm-bindgen v0.2.93 -> v0.2.95 Updating wasm-bindgen-backend v0.2.93 -> v0.2.95 Updating wasm-bindgen-futures v0.4.43 -> v0.4.45 Updating wasm-bindgen-macro v0.2.93 -> v0.2.95 Updating wasm-bindgen-macro-support v0.2.93 -> v0.2.95 Updating wasm-bindgen-shared v0.2.93 -> v0.2.95 Updating web-sys v0.3.70 -> v0.3.72 note: pass `--verbose` to see 10 unchanged dependencies behind latest --- Cargo.lock | 148 ++++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0aec92430c11f..e597a7a91fd0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1109c57718022ac84c194f775977a534e1b3969b405e55693a61c42187cc0612" +checksum = "f95d76a38cae906fd394a5afb0736aaceee5432efe76addfd71048e623e208af" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cc0e59c803dd44d14fc0cfa9fea1f74cfa8fd9fb60ca303ced390c58c28d4e" +checksum = "03c66eec1acdd96b39b995b8f5ee5239bc0c871d62c527ae1ac9fd1d7fecd455" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a289ffd7448036f2f436b377f981c79ce0b2090877bad938d43387dc09931877" +checksum = "8ecb848c43f6b06ae3de2e4a67496cbbabd78ae87db0f1248934f15d76192c6a" dependencies = [ "alloy-rlp", "arbitrary", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0409e3ba5d1de409997a7db8b8e9d679d52088c1dee042a85033affd3cadeab4" +checksum = "661c516eb1fa3294cc7f2fb8955b3b609d639c282ac81a4eedb14d3046db503a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -620,9 +620,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18372ef450d59f74c7a64a738f546ba82c92f816597fed1802ef559304c81f1" +checksum = "ecbabb8fc3d75a0c2cea5215be22e7a267e3efde835b0f2a8922f5e3f5d47683" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -639,9 +639,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7bad89dd0d5f109e8feeaf787a9ed7a05a91a9a0efc6687d147a70ebca8eff7" +checksum = "16517f2af03064485150d89746b8ffdcdbc9b6eeb3d536fb66efd7c2846fbc75" dependencies = [ "alloy-json-abi", "const-hex", @@ -656,9 +656,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd3548d5262867c2c4be6223fe4f2583e21ade0ca1c307fd23bc7f28fca479e" +checksum = "c07ebb0c1674ff8cbb08378d7c2e0e27919d2a2dae07ad3bca26174deda8d389" dependencies = [ "serde", "winnow", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa666f1036341b46625e72bd36878bf45ad0185f1b88601223e1ec6ed4b72b1" +checksum = "8e448d879903624863f608c552d10efb0e0905ddbee98b0049412799911eb062" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1129,9 +1129,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429" +checksum = "998282f8f49ccd6116b0ed8a4de0fbd3151697920e7c7533416d6e25e76434a7" dependencies = [ "flate2", "futures-core", @@ -1321,9 +1321,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33590e8d45206fdc4273ded8a1f292bcceaadd513037aa790fc67b237bc30ee" +checksum = "564a597a3c71a957d60a2e4c62c93d78ee5a0d636531e15b760acad983a5c18e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.45.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33ae899566f3d395cbf42858e433930682cc9c1889fa89318896082fef45efb" +checksum = "0dc2faec3205d496c7e57eff685dd944203df7ce16a4116d0281c44021788a7b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1365,9 +1365,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39c09e199ebd96b9f860b0fce4b6625f211e064ad7c8693b72ecf7ef03881e0" +checksum = "c93c241f52bc5e0476e259c953234dab7e2a35ee207ee202e86c0095ec4951dc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1387,9 +1387,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.45.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d95f93a98130389eb6233b9d615249e543f6c24a68ca1f109af9ca5164a8765" +checksum = "b259429be94a3459fa1b00c5684faee118d74f9577cc50aebadc36e507c63b5f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1483,9 +1483,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +checksum = "a065c0fe6fdbdf9f11817eb68582b2ab4aff9e9c39e986ae48f7ec576c6322db" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1991,9 +1991,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.28" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] @@ -2105,9 +2105,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -2115,9 +2115,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -2130,9 +2130,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.32" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74a01f4f9ee6c066d42a1c8dedf0dcddad16c72a8981a309d6398de3a75b0c39" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ "clap", ] @@ -2663,18 +2663,18 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", @@ -2684,9 +2684,9 @@ dependencies = [ [[package]] name = "derive_builder_macro" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn 2.0.79", @@ -4673,10 +4673,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -4684,6 +4680,8 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", "serde", ] @@ -5280,9 +5278,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -5493,11 +5491,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -5753,9 +5751,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "newtype-uuid" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526cb7c660872e401beaf3297f95f548ce3b4b4bdd8121b7c0713771d7c4a6e" +checksum = "4f4933943834e236c864a48aefdc2da43885dbd5eb77bff3ab20f31e0c3146f5" dependencies = [ "uuid 1.10.0", ] @@ -6661,9 +6659,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -7602,9 +7600,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50" +checksum = "553f8299af7450cda9a52d3a370199904e7a46b5ffd1bef187c4a6af3bb6db69" dependencies = [ "sdd", ] @@ -7678,9 +7676,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" [[package]] name = "sec1" @@ -8343,9 +8341,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a850d65181df41b83c6be01a7d91f5e9377c43d48faa5af7d95816f437f5a3" +checksum = "20e7b52ad118b2153644eea95c6fc740b6c1555b2344fdab763fc9de4075f665" dependencies = [ "paste", "proc-macro2", @@ -9252,9 +9250,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -9263,9 +9261,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -9278,9 +9276,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -9290,9 +9288,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9300,9 +9298,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -9313,9 +9311,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" @@ -9395,9 +9393,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", From c46fd9289e8a3d21a95db83163675fce7d7521d8 Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:09:52 -0700 Subject: [PATCH 002/391] Create codeql.yml Signed-off-by: Legion's <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/codeql.yml | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From f4c98067e8f66d0b4cc8634971412e7f065d79d5 Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:53:00 -0700 Subject: [PATCH 003/391] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/custom.md | 10 ++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..dd84ea7824f11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 4e543f759a28b7d944ca88d71982a23ace15c58d Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:54:56 -0700 Subject: [PATCH 004/391] Create apisec-scan.yml Signed-off-by: Legion's <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/apisec-scan.yml diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..d719efe66e0e2 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,71 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# APIsec addresses the critical need to secure APIs before they reach production. +# APIsec provides the industry’s only automated and continuous API testing platform that uncovers security vulnerabilities and logic flaws in APIs. +# Clients rely on APIsec to evaluate every update and release, ensuring that no APIs go to production with vulnerabilities. + +# How to Get Started with APIsec.ai +# 1. Schedule a demo at https://www.apisec.ai/request-a-demo . +# +# 2. Register your account at https://cloud.apisec.ai/#/signup . +# +# 3. Register your API . See the video (https://www.youtube.com/watch?v=MK3Xo9Dbvac) to get up and running with APIsec quickly. +# +# 4. Get GitHub Actions scan attributes from APIsec Project -> Configurations -> Integrations -> CI-CD -> GitHub Actions +# +# apisec-run-scan +# +# This action triggers the on-demand scans for projects registered in APIsec. +# If your GitHub account allows code scanning alerts, you can then upload the sarif file generated by this action to show the scan findings. +# Else you can view the scan results from the project home page in APIsec Platform. +# The link to view the scan results is also displayed on the console on successful completion of action. + +# This is a starter workflow to help you get started with APIsec-Scan Actions + +name: APIsec + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "master" branch + # Customize trigger events based on your DevSecOps processes. + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '42 12 * * 4' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +permissions: + contents: read + +jobs: + + Trigger_APIsec_scan: + permissions: + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + + steps: + - name: APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + # The APIsec username with which the scans will be executed + apisec-username: ${{ secrets.apisec_username }} + # The Password of the APIsec user with which the scans will be executed + apisec-password: ${{ secrets.apisec_password}} + # The name of the project for security scan + apisec-project: "VAmPI" + # The name of the sarif format result file The file is written only if this property is provided. + sarif-result-file: "apisec-results.sarif" + - name: Import results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ./apisec-results.sarif From 7843e74f82f95ebe60768b2bfa5a05adf9f27faa Mon Sep 17 00:00:00 2001 From: Dargon789 Date: Mon, 28 Oct 2024 03:57:33 +0700 Subject: [PATCH 005/391] add remix_tests project --- .deps/remix-tests/remix_accounts.sol | 39 +++++ .deps/remix-tests/remix_tests.sol | 225 +++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} From fb47f35432aabfcf203fa910709cc09597f16dab Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Tue, 19 Nov 2024 05:01:53 +0700 Subject: [PATCH 006/391] Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Legion's <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/compilation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs index b4f00b91b0d92..cf5195a4bbf32 100644 --- a/crates/config/src/compilation.rs +++ b/crates/config/src/compilation.rs @@ -54,7 +54,7 @@ impl SettingsOverrides { #[derive(Debug, thiserror::Error)] pub enum RestrictionsError { - #[error("specified both exact and relative restrictions for {0}")] + #[error("invalid configuration: cannot specify both exact and relative restrictions for '{0}' - please choose only one type of restriction")] BothExactAndRelative(&'static str), } From 682c4f0966365d1cc3405eb415e4bf7fa4a41528 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 4 Mar 2025 05:16:42 +0700 Subject: [PATCH 007/391] Delete .github/workflows/apisec-scan.yml (#13) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 71 ------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 .github/workflows/apisec-scan.yml diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml deleted file mode 100644 index d719efe66e0e2..0000000000000 --- a/.github/workflows/apisec-scan.yml +++ /dev/null @@ -1,71 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# APIsec addresses the critical need to secure APIs before they reach production. -# APIsec provides the industry’s only automated and continuous API testing platform that uncovers security vulnerabilities and logic flaws in APIs. -# Clients rely on APIsec to evaluate every update and release, ensuring that no APIs go to production with vulnerabilities. - -# How to Get Started with APIsec.ai -# 1. Schedule a demo at https://www.apisec.ai/request-a-demo . -# -# 2. Register your account at https://cloud.apisec.ai/#/signup . -# -# 3. Register your API . See the video (https://www.youtube.com/watch?v=MK3Xo9Dbvac) to get up and running with APIsec quickly. -# -# 4. Get GitHub Actions scan attributes from APIsec Project -> Configurations -> Integrations -> CI-CD -> GitHub Actions -# -# apisec-run-scan -# -# This action triggers the on-demand scans for projects registered in APIsec. -# If your GitHub account allows code scanning alerts, you can then upload the sarif file generated by this action to show the scan findings. -# Else you can view the scan results from the project home page in APIsec Platform. -# The link to view the scan results is also displayed on the console on successful completion of action. - -# This is a starter workflow to help you get started with APIsec-Scan Actions - -name: APIsec - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the "master" branch - # Customize trigger events based on your DevSecOps processes. - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - schedule: - - cron: '42 12 * * 4' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - -permissions: - contents: read - -jobs: - - Trigger_APIsec_scan: - permissions: - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - runs-on: ubuntu-latest - - steps: - - name: APIsec scan - uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea - with: - # The APIsec username with which the scans will be executed - apisec-username: ${{ secrets.apisec_username }} - # The Password of the APIsec user with which the scans will be executed - apisec-password: ${{ secrets.apisec_password}} - # The name of the project for security scan - apisec-project: "VAmPI" - # The name of the sarif format result file The file is written only if this property is provided. - sarif-result-file: "apisec-results.sarif" - - name: Import results - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ./apisec-results.sarif From 2a2b63d00c75aa243d19348de6515c475dd80b2c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:38:16 +0700 Subject: [PATCH 008/391] Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7824f11..43a59e29b9c29 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,7 +25,7 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] + - Browser [e.g. Chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** From cfc4503bce8cac7d5a22ee3f1a235895e1a927c8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 4 Mar 2025 21:13:23 +0700 Subject: [PATCH 009/391] Add .circleci/config.yml (#15) --- .circleci/config.yml | 31 ++++++++++++++ .github/workflows/apisec-scan.yml | 71 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .github/workflows/apisec-scan.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..62291703e26a7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello \ No newline at end of file diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..d719efe66e0e2 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,71 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# APIsec addresses the critical need to secure APIs before they reach production. +# APIsec provides the industry’s only automated and continuous API testing platform that uncovers security vulnerabilities and logic flaws in APIs. +# Clients rely on APIsec to evaluate every update and release, ensuring that no APIs go to production with vulnerabilities. + +# How to Get Started with APIsec.ai +# 1. Schedule a demo at https://www.apisec.ai/request-a-demo . +# +# 2. Register your account at https://cloud.apisec.ai/#/signup . +# +# 3. Register your API . See the video (https://www.youtube.com/watch?v=MK3Xo9Dbvac) to get up and running with APIsec quickly. +# +# 4. Get GitHub Actions scan attributes from APIsec Project -> Configurations -> Integrations -> CI-CD -> GitHub Actions +# +# apisec-run-scan +# +# This action triggers the on-demand scans for projects registered in APIsec. +# If your GitHub account allows code scanning alerts, you can then upload the sarif file generated by this action to show the scan findings. +# Else you can view the scan results from the project home page in APIsec Platform. +# The link to view the scan results is also displayed on the console on successful completion of action. + +# This is a starter workflow to help you get started with APIsec-Scan Actions + +name: APIsec + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "master" branch + # Customize trigger events based on your DevSecOps processes. + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '42 12 * * 4' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +permissions: + contents: read + +jobs: + + Trigger_APIsec_scan: + permissions: + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + + steps: + - name: APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + # The APIsec username with which the scans will be executed + apisec-username: ${{ secrets.apisec_username }} + # The Password of the APIsec user with which the scans will be executed + apisec-password: ${{ secrets.apisec_password}} + # The name of the project for security scan + apisec-project: "VAmPI" + # The name of the sarif format result file The file is written only if this property is provided. + sarif-result-file: "apisec-results.sarif" + - name: Import results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ./apisec-results.sarif From 0b98e332b6f3bb02671adc4ffa4afcb47f7b7966 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 4 Mar 2025 22:48:54 +0700 Subject: [PATCH 010/391] Update apisec-scan.yml (#16) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 79 +++++++------------------------ 1 file changed, 16 insertions(+), 63 deletions(-) diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml index d719efe66e0e2..728b4819e7213 100644 --- a/.github/workflows/apisec-scan.yml +++ b/.github/workflows/apisec-scan.yml @@ -1,71 +1,24 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# APIsec addresses the critical need to secure APIs before they reach production. -# APIsec provides the industry’s only automated and continuous API testing platform that uncovers security vulnerabilities and logic flaws in APIs. -# Clients rely on APIsec to evaluate every update and release, ensuring that no APIs go to production with vulnerabilities. - -# How to Get Started with APIsec.ai -# 1. Schedule a demo at https://www.apisec.ai/request-a-demo . -# -# 2. Register your account at https://cloud.apisec.ai/#/signup . -# -# 3. Register your API . See the video (https://www.youtube.com/watch?v=MK3Xo9Dbvac) to get up and running with APIsec quickly. -# -# 4. Get GitHub Actions scan attributes from APIsec Project -> Configurations -> Integrations -> CI-CD -> GitHub Actions -# -# apisec-run-scan -# -# This action triggers the on-demand scans for projects registered in APIsec. -# If your GitHub account allows code scanning alerts, you can then upload the sarif file generated by this action to show the scan findings. -# Else you can view the scan results from the project home page in APIsec Platform. -# The link to view the scan results is also displayed on the console on successful completion of action. - # This is a starter workflow to help you get started with APIsec-Scan Actions - name: APIsec -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the "master" branch - # Customize trigger events based on your DevSecOps processes. - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - schedule: - - cron: '42 12 * * 4' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - -permissions: - contents: read +on: [push, pull_request] jobs: - Trigger_APIsec_scan: - permissions: - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status runs-on: ubuntu-latest - steps: - - name: APIsec scan - uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea - with: - # The APIsec username with which the scans will be executed - apisec-username: ${{ secrets.apisec_username }} - # The Password of the APIsec user with which the scans will be executed - apisec-password: ${{ secrets.apisec_password}} - # The name of the project for security scan - apisec-project: "VAmPI" - # The name of the sarif format result file The file is written only if this property is provided. - sarif-result-file: "apisec-results.sarif" - - name: Import results - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ./apisec-results.sarif + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-project: VAmPI + sarif-result-file: apisec-results.sarif + apisec-profile: Master + apisec-oas: false + # Ensure you provide valid credentials and host details + username: ${{ secrets.APISEC_USERNAME }} + password: ${{ secrets.APISEC_PASSWORD }} + host: ${{ secrets.APISEC_HOST }} + scanner: "default-scanner" From 1e98af9cec50931c37d0e04379f4481b4cb45d1b Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:03:21 +0700 Subject: [PATCH 011/391] fix(forge): use float total cmp instead partial (#10005) (#17) fix(forge): use total cmp instead partial Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 3 +-- crates/common/src/contracts.rs | 2 +- crates/forge/bin/cmd/snapshot.rs | 6 ++---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..a675ccae963c9 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,5 +1,4 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. -use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -17,7 +16,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 1e78751fde964..f9da7638fd422 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -147,7 +147,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap()) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index 872a53138c8ee..aa0f0d94d95ee 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -373,9 +373,7 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> let mut overall_gas_change = 0i128; let mut overall_gas_used = 0i128; - diffs.sort_by(|a, b| { - a.gas_diff().abs().partial_cmp(&b.gas_diff().abs()).unwrap_or(Ordering::Equal) - }); + diffs.sort_by(|a, b| a.gas_diff().abs().total_cmp(&b.gas_diff().abs())); for diff in diffs { let gas_change = diff.gas_change(); @@ -401,7 +399,7 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> fn fmt_pct_change(change: f64) -> String { let change_pct = change * 100.0; - match change.partial_cmp(&0.0).unwrap_or(Ordering::Equal) { + match change.total_cmp(&0.0) { Ordering::Less => format!("{change_pct:.3}%").green().to_string(), Ordering::Equal => { format!("{change_pct:.3}%") From e45fdec11dd4bfea05b446395ca1a0ce90835160 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Mar 2025 05:15:58 +0700 Subject: [PATCH 012/391] Update apisec-scan.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml index 728b4819e7213..5875cc28ae724 100644 --- a/.github/workflows/apisec-scan.yml +++ b/.github/workflows/apisec-scan.yml @@ -1,24 +1,27 @@ -# This is a starter workflow to help you get started with APIsec-Scan Actions name: APIsec -on: [push, pull_request] +on: + pull_request: + branches: + - main jobs: - Trigger_APIsec_scan: + scan: runs-on: ubuntu-latest steps: - - name: Checkout repository + - name: Checkout code uses: actions/checkout@v2 - name: Run APIsec scan uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} apisec-project: VAmPI - sarif-result-file: apisec-results.sarif apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical apisec-oas: false - # Ensure you provide valid credentials and host details - username: ${{ secrets.APISEC_USERNAME }} - password: ${{ secrets.APISEC_PASSWORD }} - host: ${{ secrets.APISEC_HOST }} - scanner: "default-scanner" + apisec-openapi-spec-url: "https://example.com/openapi.json" From 02e28abdca0bf8c9acd833ab33b7a405198a7dea Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Mar 2025 06:20:17 +0700 Subject: [PATCH 013/391] Revert "fix(forge): use float total cmp instead partial (#10005) (#17)" (#18) This reverts commit 1e98af9cec50931c37d0e04379f4481b4cb45d1b. --- crates/cli/src/utils/suggestions.rs | 3 ++- crates/common/src/contracts.rs | 2 +- crates/forge/bin/cmd/snapshot.rs | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index f9da7638fd422..1e78751fde964 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -147,7 +147,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap()) .map(|(_, data)| data) } diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index aa0f0d94d95ee..872a53138c8ee 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -373,7 +373,9 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> let mut overall_gas_change = 0i128; let mut overall_gas_used = 0i128; - diffs.sort_by(|a, b| a.gas_diff().abs().total_cmp(&b.gas_diff().abs())); + diffs.sort_by(|a, b| { + a.gas_diff().abs().partial_cmp(&b.gas_diff().abs()).unwrap_or(Ordering::Equal) + }); for diff in diffs { let gas_change = diff.gas_change(); @@ -399,7 +401,7 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> fn fmt_pct_change(change: f64) -> String { let change_pct = change * 100.0; - match change.total_cmp(&0.0) { + match change.partial_cmp(&0.0).unwrap_or(Ordering::Equal) { Ordering::Less => format!("{change_pct:.3}%").green().to_string(), Ordering::Equal => { format!("{change_pct:.3}%") From 5931c87ca0200dcc7b7cd7dea7670086f9cb4d35 Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:10:23 +0000 Subject: [PATCH 014/391] forge test openz --- .codesandbox/tasks.json | 7 ++++ .gitmodules | 6 +++ counter/.github/workflows/test.yml | 43 +++++++++++++++++++ counter/.gitignore | 14 +++++++ counter/README.md | 66 ++++++++++++++++++++++++++++++ counter/foundry.toml | 6 +++ counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 +++++++++ counter/src/Counter.sol | 14 +++++++ counter/test/Counter.t.sol | 24 +++++++++++ 11 files changed, 201 insertions(+) create mode 100644 .codesandbox/tasks.json create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..9265b4558406a --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..acd4ff74de833 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From fdfd0e77852f21551fee8185091a2ba3be6dcb30 Mon Sep 17 00:00:00 2001 From: Legion's <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:11:02 +0000 Subject: [PATCH 015/391] diff sig --- counter/lib/openzeppelin-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts index acd4ff74de833..4458d32fb643a 160000 --- a/counter/lib/openzeppelin-contracts +++ b/counter/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 +Subproject commit 4458d32fb643a22ac81698c5ce3ca3ca9fed5a50 From a0a8bde78ec08ddd85eafe773cde166203b0cf83 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 6 Mar 2025 14:14:51 +0100 Subject: [PATCH 016/391] feat(forge): add params natspec for enums --- crates/doc/src/parser/comment.rs | 12 +++++-- crates/doc/src/writer/as_doc.rs | 11 ++++++- crates/doc/src/writer/buf_writer.rs | 51 +++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index bf2b0ad7b4f0d..cba7f32df2767 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -46,7 +46,7 @@ impl CommentTag { } _ => { warn!(target: "forge::doc", tag=trimmed, "unknown comment tag. custom tags must be preceded by `custom:`"); - return None + return None; } }; Some(tag) @@ -157,6 +157,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); @@ -184,8 +190,8 @@ impl<'a> CommentsRef<'a> { self.iter().any(|c| match (&c.tag, &target.tag) { (CommentTag::Inheritdoc, CommentTag::Inheritdoc) => c.value == target.value, (CommentTag::Param, CommentTag::Param) | (CommentTag::Return, CommentTag::Return) => { - c.split_first_word().map(|(name, _)| name) == - target.split_first_word().map(|(name, _)| name) + c.split_first_word().map(|(name, _)| name) + == target.split_first_word().map(|(name, _)| name) } (tag1, tag2) => tag1 == tag2, }) diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 56a0a4026c504..98fe8f668eca0 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -229,7 +229,16 @@ impl AsDoc for Document { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code) + + let filtered_comments: Comments = (*comments) + .iter() + .cloned() + .filter(|c| c.tag != CommentTag::Custom("variant".to_string())) + .collect::>() + .into(); + + writer.write_section(&filtered_comments, code)?; + writer.try_write_variant_table(&item, comments) })?; } } diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index e6109c338c03f..152f29dc61345 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,6 +1,8 @@ use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; -use solang_parser::pt::{ErrorParameter, EventParameter, Parameter, VariableDeclaration}; +use solang_parser::pt::{ + EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, +}; use std::{ fmt::{self, Display, Write}, sync::LazyLock, @@ -19,6 +21,11 @@ const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"]; static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock = LazyLock::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); +/// Headers and separator for rendering the variants table. +const VARIANTS_TABLE_HEADERS: &[&str] = &["Name", "Description"]; +const VARIANTS_TABLE_SEPARATOR: LazyLock = + LazyLock::new(|| VARIANTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); + /// The buffered writer. /// Writes various display items into the internal buffer. #[derive(Debug, Default)] @@ -132,7 +139,7 @@ impl BufWriter { // There is nothing to write. if params.is_empty() || comments.is_empty() { - return Ok(()) + return Ok(()); } self.write_bold(heading)?; @@ -177,6 +184,46 @@ impl BufWriter { self.try_write_table(CommentTag::Param, params, comments, "Properties") } + /// Tries to write the variant table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_variant_table( + &mut self, + params: &EnumDefinition, + comments: &Comments, + ) -> fmt::Result { + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); + + // There is nothing to write. + if comments.is_empty() { + return Ok(()); + } + + self.write_bold("Variants")?; + self.writeln()?; + + self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; + self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; + + for value in params.values.iter() { + let param_name = value.as_ref().map(|v| v.name.clone()); + + let comment = param_name.as_ref().and_then(|name| { + comments.iter().find_map(|comment| comment.match_first_word(name)) + }); + + let row = [ + Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + comment.unwrap_or_default().replace('\n', " "), + ]; + self.write_piped(&row.join("|"))?; + } + + self.writeln()?; + + Ok(()) + } + /// Tries to write the parameters table to the buffer. /// Doesn't write anything if either params or comments are empty. pub fn try_write_events_table( From c0b572530bad0202ee38f95f05988273b6ffda02 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 6 Mar 2025 14:15:45 +0100 Subject: [PATCH 017/391] chore: cargo fmt --- crates/doc/src/parser/comment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index cba7f32df2767..5dc6c17f98890 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -190,8 +190,8 @@ impl<'a> CommentsRef<'a> { self.iter().any(|c| match (&c.tag, &target.tag) { (CommentTag::Inheritdoc, CommentTag::Inheritdoc) => c.value == target.value, (CommentTag::Param, CommentTag::Param) | (CommentTag::Return, CommentTag::Return) => { - c.split_first_word().map(|(name, _)| name) - == target.split_first_word().map(|(name, _)| name) + c.split_first_word().map(|(name, _)| name) == + target.split_first_word().map(|(name, _)| name) } (tag1, tag2) => tag1 == tag2, }) From d6033484b6f75de82b51eec87e1d0f36745898ac Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 6 Mar 2025 14:23:16 +0100 Subject: [PATCH 018/391] fix: clippy errors --- crates/doc/src/writer/as_doc.rs | 4 ++-- crates/doc/src/writer/buf_writer.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 98fe8f668eca0..d38f880ed8693 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -232,13 +232,13 @@ impl AsDoc for Document { let filtered_comments: Comments = (*comments) .iter() - .cloned() .filter(|c| c.tag != CommentTag::Custom("variant".to_string())) + .cloned() .collect::>() .into(); writer.write_section(&filtered_comments, code)?; - writer.try_write_variant_table(&item, comments) + writer.try_write_variant_table(item, comments) })?; } } diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 152f29dc61345..7fb657ba1d7a4 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -23,7 +23,7 @@ static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock = /// Headers and separator for rendering the variants table. const VARIANTS_TABLE_HEADERS: &[&str] = &["Name", "Description"]; -const VARIANTS_TABLE_SEPARATOR: LazyLock = +static VARIANTS_TABLE_SEPARATOR: LazyLock = LazyLock::new(|| VARIANTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); /// The buffered writer. From 4cae0e34a3da13bbd8f6f4ce5236624b8f59d11d Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 7 Mar 2025 16:33:09 +0100 Subject: [PATCH 019/391] add @gregorsternat as co-author given their earlier work on https://github.com/foundry-rs/foundry/pull/9905 Co-authored-by: gregorsternat From ffefcd22688e53b429034b088442c51427c140c6 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 8 Mar 2025 20:05:26 +0700 Subject: [PATCH 020/391] Update openzeppelin-contracts --- counter/lib/openzeppelin-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts index 4458d32fb643a..ca7a4e39de086 160000 --- a/counter/lib/openzeppelin-contracts +++ b/counter/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 4458d32fb643a22ac81698c5ce3ca3ca9fed5a50 +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 From 30ccc2c75b877cfcbae31435d42bb8d0ff616be7 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Mar 2025 18:58:29 +0700 Subject: [PATCH 021/391] chore(deps): weekly `cargo update` (#10039) (#22) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 160 +++++++++++++++++++++++++---------------------------- 1 file changed, 76 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fc0e255c012d..1cea57209a9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc96bae4737f5a8c8fae0db8fe9f000ebf3dd9894db755ba178fedbbab244d6" +checksum = "1715ed2a977d3ca4b39ffe0fc69f9f5b0e81382b348bdb5172abaa77a10f0b6d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f02faf3f83ddd877925a46bc206eb70f718e0c438b3159cd153751cde7ade7a" +checksum = "660705969af143897d83937d73f53c741c1587f49c27c2cfce594e188fcbc1e4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47250b3cd43cf7a89aab508a81c11a1b92acd7f13b2200df21aed83cc3e1da7" +checksum = "5362637b25ba5282a921ca139a10f188fa34e1248a7c83c907a21de54d36dce1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b48f915ccad936effcfb6f3711225f3e00e1db0effd6faf7da57518da325c88" +checksum = "d13734f722326c846e7690ce732c9864f5ae82f52c7fb60c871f56654f348d4c" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f88ea3da00e9f890ea8c6913dba52025454c34a57df893dab1d7b0e3c53615" +checksum = "738b6d7da21955cfdebeb7bcf300040b79e51c58a22e5f029ae989a8d834a3f3" dependencies = [ "alloy-eips", "alloy-primitives", @@ -252,9 +252,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba0e8a8d1bf498da62affe8472e1ee2fd064144d5826a1088a49416f2f448a3" +checksum = "e6fbb61c4dfe5def9a065438162faf39503b3e8d90f36d01563418a75f0ef016" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -266,9 +266,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fc810a9967c3a6c69f9e9b2bb68bb13bd919aa741a533bdd5bf074f4a6709b" +checksum = "f10b0bc0657b018ee4f3758f889d066af6b1f20f57cd825b540182029090c151" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dddaef8fc70307e8775be36a853e6118a628842d013f77f2c73ee48497aaf6" +checksum = "6cac4aeeabbbc16623d0745ae3b5a515d727ce8ef4ec4b6a886c3634d8b298fe" dependencies = [ "alloy-consensus", "alloy-eips", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fc757d22ea387b404036301201e54f24e2f4f6a9632b6d8bd58d06d0abfa3b" +checksum = "d9151507742ac142201c3a56f74fd94c2ceda96115eed60a3dabaeef85e6b64a" dependencies = [ "alloy-genesis", "alloy-network", @@ -356,9 +356,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57dc6935c8b91fc742c6f2b6f100ea04060475b312c245b42a63729f4299951f" +checksum = "d06ffafc44e68c8244feb51919895c679c153a0b143c182e1ffe8cce998abf15" dependencies = [ "alloy-chains", "alloy-consensus", @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7538324684444a59fde89469f2dc097bca7dc2ee024e214f3a48c21917978f" +checksum = "6abe9f9e6be75dc8532bb2bf3f4c700c9e7bce8a3b05ec702a7324abdb118016" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -440,9 +440,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7604f6c3438bf48d5d183fa9dd02a0a0151a631eb15c1444ea4630c7f75b4271" +checksum = "c9ae316fdb92a4546f0dba4919ea4c1c0edb89a876536520c248fada0febac5d" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -466,9 +466,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0957527935118dfb52427d220f326a80f6b99e9d21b94fa16d884d150ddc814" +checksum = "61e50cc5a693dfbef452e3dbcea3cd3342840d10eb3ffa018b0a5676967d8b6b" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94cd833d43007286d1a3ff1246a8cd8f1d9a5854248e766e29f84b7d8a5bb86" +checksum = "2852d7350760c3fbfc60ee3396b95a66ea57afe3aeecee72bf1171ac6b1d5d18" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84ae770c31d4aae0ce056ff594dffc8bb51cc246d1854ddae5083fbd978ef58" +checksum = "f726ebb03d5918a946d0a6e17829cabd90ffe928664dc3f7fdbba1be511760de" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -505,9 +505,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed0352a0e8c55f6a53e82509342b5c3ff6a449601cb393569ce597d09ee816d" +checksum = "ab1fe2c636a14190fe3c6caf6a20d2fb8691a5824c1789ee495324a14295df82" dependencies = [ "alloy-primitives", "serde", @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d5c16b174beb31b50cbfc754b879dc9473bce12d0f2c4675d79419ed7381f5" +checksum = "b05bfe640e4708c5a83dfcc65b5e4a0deb6ddcb18897dd49862ddc3964e06ff8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171bf00513b8cbfb338a9028f9151cac787c2fca5fc923c2c2201b059444df44" +checksum = "c24a3b6c552b74c4abdbaa45fd467a230f5564f62c6adae16972dd90b6b4dca5" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658eb579d6499139da033c0b7e5a302dad90bb57c4e722250dd82636de035936" +checksum = "e25f16f6bfe65c23d873741aa343830de270db42c982822e23689d11f2f4d812" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.12.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51305494072ed029790cdfe322a1a30d7d8a0baf47ad8f61b5981354e298741" +checksum = "9415e7e3f32a93a38ecb83aa449f7326081b5b362964291463f8f2060b4b8a31" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -579,9 +579,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de933b08fd186491d7171b4075c54ccf37d3f4fe17178bc3a8ba7cd1e77fb387" +checksum = "3aebca035ca670bd7de8165a9494c0d502625e26129dd95d17fdfd70d5521c02" dependencies = [ "alloy-primitives", "serde", @@ -590,9 +590,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55818175303de00576c5eb27ffa22ca31efe481e81cac7b05cc87e5f0ba2153" +checksum = "7abfef2a155c7d6a9f54861159a3fdd29abe1f67f0865b081bce4c2fdc9e83cc" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -607,9 +607,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57980f25e09e42aaa0eb9267951f54de06a59f66b8b12c74d47b2128a4456a5b" +checksum = "f25d4994d79911d1fdf64945c8fc08df46e0ba828abd5c7505264b7012318443" dependencies = [ "alloy-consensus", "alloy-network", @@ -625,9 +625,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369ce741e4c0cbdb44db9761f5767befc03b8c63f469c57a821d101197d65fc" +checksum = "4316e51a4131884e42a5d8b3228a508a60d280add3742bc1196b81f758b7c204" dependencies = [ "alloy-consensus", "alloy-network", @@ -643,9 +643,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54fef49330880f268f3cab430f3d69665436b2e651dd9ba2cbfdd1cef3100d8e" +checksum = "4097dcfbbef9ca5bc3c02901af815c82db89a1a429520acad1c34a0b136acbba" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b1094d0e467f8bea128fc7a4d853a1300c53d20c9b2fddd76d59cc3b25d787" +checksum = "326033310c939b0d00b03fdbe2c243cb45add25c4e195d97b1792883c93a4c4c" dependencies = [ "alloy-consensus", "alloy-network", @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0586b1a20d7559d21f7e33318fa5881bf850f94c58b37e74a5ba245c34d8b80c" +checksum = "c6da910dc4386ab642a58a5a98cec80ecb47855b28b340a997ba7fa019f1cec0" dependencies = [ "alloy-consensus", "alloy-network", @@ -772,9 +772,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9f400ac2a4da066cd5c8beb4efc6b97111ac43e205928534768671b6cf2b7b" +checksum = "463f6cb5234c7420e7e77c248c0460a8e2dea933f2bb4e8f169d5f12510b38e0" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b582d46a163af722ce965e014933c19994c5161979929ce1e486067cf4503a0" +checksum = "1eacd1c195c2a706bfbc92113d4bd3481b0dbd1742923a232dbe8a7910ac0fe5" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -806,9 +806,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c7b142bd57d2416453c762c6216315562f1b9ed5db2019539d5b34032f655f" +checksum = "6c5d3531e65eed82f14f93bb668fb06797c3754d0141c5da042bb63c5c19f13c" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -827,9 +827,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2dec526ed36192bfb9f02e6b7e4575a5135c358a96d786bfb42d691a3ca898" +checksum = "218e64c375edd8fe8d00d9aa983fb2d85f7cfeebb91f707fe4c7ba52410803f5" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -2606,12 +2606,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam-channel" version = "0.5.14" @@ -5463,9 +5457,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" dependencies = [ "doctest-file", "futures-core", @@ -5884,9 +5878,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07d36d96ffe1b5b16ddf2bc80b3b26bb7a498b2a6591061250bf0af8e8095ad" +checksum = "85c54109598152e19add1933b223b1913bcd16e27cbd7c2783269fe2f9a6cbdc" dependencies = [ "ammonia", "anyhow", @@ -5896,6 +5890,7 @@ dependencies = [ "elasticlunr-rs", "env_logger", "handlebars", + "hex", "log", "memchr", "once_cell", @@ -5904,6 +5899,7 @@ dependencies = [ "regex", "serde", "serde_json", + "sha2", "shlex", "tempfile", "toml 0.5.11", @@ -6344,10 +6340,6 @@ name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" -dependencies = [ - "critical-section", - "portable-atomic", -] [[package]] name = "op-alloy-consensus" @@ -7681,9 +7673,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" dependencies = [ "bitflags 2.9.0", "errno", @@ -8840,7 +8832,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.0", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -8868,11 +8860,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -9053,9 +9045,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", From c235592917161f2eba6ec443739e84e524de9174 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 12 Mar 2025 02:59:45 +0700 Subject: [PATCH 022/391] python3-compile-vyper-contract (#27) * fix deny.toml, ignore RUSTSEC-2025-0014 (#10052) * fix deny.toml, ignore RUSTSEC-2025-0014 * roll back allow-git * update derive_more to 2.0 (#9987) * chore: fix ci, bump python setup version (#10054) Bump gh python setup version * feat(forge): match chain id with token symbol (#10043) * feat: match chain id with token symbol * fix: change symbol from matic to pol * fix: use NameChain instead of manual mapping * Fix tests, fmt and clippy --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: grandizzy * feat(cast): storage add --proxy to manually specify a proxy contract when Etherscan fails to find it (#10033) * Adding a proxy parameter to the cast storage command, allowing manual selection of a proxy address to retrieve the full storage layout. * Adding tests for the cast storage proxy optional argument. * Using if let some else pattern. * Improved documentation. --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(forge): use etherscan verifier if key provided (#10058) * fix(forge): use etherscan if key provided * Changes after review: expose is_sourcify and is_etherscan fns --------- Co-authored-by: gerald <3949379+getong@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: supamongkonR <73258014+supamongkonR@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Cizeon <110527347+Cizeon@users.noreply.github.com> --- .github/workflows/nextest.yml | 4 +- Cargo.lock | 347 +++++++++++++++++------------ Cargo.toml | 2 +- crates/cast/bin/cmd/storage.rs | 10 +- crates/cast/tests/cli/main.rs | 42 ++++ crates/forge/tests/cli/script.rs | 2 +- crates/script/src/simulate.rs | 10 +- crates/test-utils/src/util.rs | 5 +- crates/verify/src/etherscan/mod.rs | 9 +- crates/verify/src/provider.rs | 19 +- deny.toml | 3 +- 11 files changed, 295 insertions(+), 158 deletions(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index c8c0e0961eb0c..5f8a9561af7d4 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -68,11 +68,11 @@ jobs: with: bun-version: latest - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 - name: Install Vyper - run: pip install vyper==0.4.0 + run: pip --version && pip install vyper==0.4.0 - name: Forge RPC cache uses: actions/cache@v3 diff --git a/Cargo.lock b/Cargo.lock index aed90b9330cf4..f02aec99ed670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,7 @@ dependencies = [ "foldhash", "getrandom 0.2.15", "hashbrown 0.15.2", - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "k256", "keccak-asm", @@ -435,7 +435,7 @@ checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -708,7 +708,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -721,11 +721,11 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.7.1", + "indexmap 2.8.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "syn-solidity", "tiny-keccak", ] @@ -743,7 +743,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.99", + "syn 2.0.100", "syn-solidity", ] @@ -1289,7 +1289,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1311,7 +1311,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1322,7 +1322,7 @@ checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1375,7 +1375,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1565,9 +1565,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" dependencies = [ "futures-util", "pin-project-lite", @@ -1614,11 +1614,52 @@ dependencies = [ "tracing", ] +[[package]] +name = "aws-smithy-http" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.2.0", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0497ef5d53065b7cd6a35e9c1654bd1fefeae5c52900d91d1b188b0af0f29324" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.8", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "pin-project-lite", + "rustls 0.21.12", + "tokio", + "tracing", +] + [[package]] name = "aws-smithy-json" -version = "0.61.2" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" dependencies = [ "aws-smithy-types", ] @@ -1635,36 +1676,33 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.8" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" +checksum = "f6328865e36c6fd970094ead6b05efd047d3a80ec5fc3be5e743910da9f2ebf8" dependencies = [ "aws-smithy-async", - "aws-smithy-http 0.60.12", + "aws-smithy-http 0.62.0", + "aws-smithy-http-client", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "fastrand", - "h2 0.3.26", "http 0.2.12", + "http 1.2.0", "http-body 0.4.6", "http-body 1.0.1", - "httparse", - "hyper 0.14.32", - "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.12", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1679,9 +1717,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.13" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" +checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" dependencies = [ "base64-simd", "bytes", @@ -1826,9 +1864,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" [[package]] name = "bech32" @@ -1944,7 +1982,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2233,9 +2271,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -2243,9 +2281,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -2277,14 +2315,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2540,9 +2578,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" dependencies = [ "unicode-segmentation", ] @@ -2733,7 +2771,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2744,7 +2782,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2818,7 +2856,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2839,7 +2877,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2849,7 +2887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2876,10 +2914,9 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "unicode-xid", ] @@ -2889,9 +2926,10 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "unicode-xid", ] @@ -3000,7 +3038,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3025,7 +3063,7 @@ checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3157,7 +3195,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3178,14 +3216,14 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff 0.2.4", "log", ] @@ -3263,7 +3301,7 @@ dependencies = [ "ahash", "alloy-dyn-abi", "alloy-primitives", - "indexmap 2.7.1", + "indexmap 2.8.0", ] [[package]] @@ -3315,12 +3353,12 @@ dependencies = [ [[package]] name = "fd-lock" -version = "4.0.3" +version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44818c96aec5cadc9dacfb97bbcbcfc19a0de75b218412d56f57fbaab94e439" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 0.38.44", + "rustix 1.0.2", "windows-sys 0.59.0", ] @@ -3336,9 +3374,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", "subtle", @@ -3507,7 +3545,7 @@ name = "forge-doc" version = "1.0.0" dependencies = [ "alloy-primitives", - "derive_more 1.0.0", + "derive_more 2.0.1", "eyre", "forge-fmt", "foundry-common", @@ -3616,7 +3654,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4047,7 +4085,7 @@ version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "derive_more 1.0.0", + "derive_more 2.0.1", "foundry-common-fmt", "foundry-macros", "foundry-test-utils", @@ -4194,7 +4232,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4354,7 +4392,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4532,7 +4570,7 @@ checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f" dependencies = [ "bstr", "itoa", - "jiff", + "jiff 0.1.29", "thiserror 2.0.12", ] @@ -4754,7 +4792,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -4773,7 +4811,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.7.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -4911,7 +4949,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5254,7 +5292,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5336,7 +5374,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5364,9 +5402,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "arbitrary", "equivalent", @@ -5454,7 +5492,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5551,6 +5589,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "jiff-tzdb" version = "0.1.3" @@ -5880,9 +5942,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c54109598152e19add1933b223b1913bcd16e27cbd7c2783269fe2f9a6cbdc" +checksum = "7e1a8fe3a4a01f28dab245c474cb7b95ccb4d3d2f17a5419a3d949f474c45e84" dependencies = [ "ammonia", "anyhow", @@ -5963,7 +6025,7 @@ checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6053,7 +6115,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6297,7 +6359,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6339,9 +6401,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "op-alloy-consensus" @@ -6456,7 +6518,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6539,7 +6601,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6598,7 +6660,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6619,7 +6681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.8.0", ] [[package]] @@ -6672,7 +6734,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6721,7 +6783,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6775,11 +6837,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy 0.8.23", ] [[package]] @@ -6821,7 +6883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6896,7 +6958,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6916,7 +6978,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "version_check", "yansi", ] @@ -6928,7 +6990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d35f4dc9988d1326b065b4def5e950c3ed727aa03e3151b86cc9e2aec6b03f54" dependencies = [ "futures", - "indexmap 2.7.1", + "indexmap 2.8.0", "nix 0.29.0", "tokio", "tracing", @@ -6984,7 +7046,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -7007,7 +7069,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -7070,7 +7132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ "chrono", - "indexmap 2.7.1", + "indexmap 2.8.0", "newtype-uuid", "quick-xml 0.37.2", "strip-ansi-escapes", @@ -7423,9 +7485,9 @@ dependencies = [ [[package]] name = "revm" -version = "19.5.0" +version = "19.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc5bef3c95fadf3b6a24a253600348380c169ef285f9780a793bb7090c8990d" +checksum = "7b906766b7ba049b515848952b5ae74f363d456e98de2021048a513e442b4f42" dependencies = [ "auto_impl", "cfg-if", @@ -7675,9 +7737,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" dependencies = [ "bitflags 2.9.0", "errno", @@ -7898,7 +7960,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -7937,9 +7999,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" +checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" [[package]] name = "sec1" @@ -8059,22 +8121,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8085,7 +8147,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8094,7 +8156,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "memchr", "ryu", @@ -8129,7 +8191,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8163,7 +8225,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_derive", "serde_json", @@ -8180,7 +8242,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8215,7 +8277,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8481,7 +8543,7 @@ checksum = "71d07263243b313296eca18f18eda3a190902dc3284bf67ceff29b8b54dac3e6" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.7.1", + "indexmap 2.8.0", "parking_lot", "rayon", "rustc-hash", @@ -8524,7 +8586,7 @@ checksum = "970d7c774741f786d62cab78290e47d845b0b9c0c9d094a1642aced1d7946036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8696,7 +8758,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8709,7 +8771,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8777,9 +8839,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -8795,7 +8857,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8815,7 +8877,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8834,7 +8896,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.1", + "rustix 1.0.2", "windows-sys 0.59.0", ] @@ -8866,7 +8928,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.1", + "rustix 1.0.2", "windows-sys 0.59.0", ] @@ -8925,7 +8987,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -8936,7 +8998,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -9071,7 +9133,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -9174,7 +9236,7 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_spanned", "toml_datetime", @@ -9196,7 +9258,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_spanned", "toml_datetime", @@ -9347,7 +9409,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -9784,7 +9846,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -9819,7 +9881,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10064,7 +10126,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10075,7 +10137,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10086,7 +10148,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10097,7 +10159,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10474,7 +10536,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -10484,7 +10546,6 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] @@ -10505,7 +10566,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10516,7 +10577,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10536,7 +10597,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -10557,7 +10618,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10579,7 +10640,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -10594,7 +10655,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.7.1", + "indexmap 2.8.0", "memchr", "thiserror 2.0.12", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 0811a200fec91..eae5e6f6e266c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -255,7 +255,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = "2.0" async-trait = "0.1" -derive_more = { version = "1.0", features = ["full"] } +derive_more = { version = "2.0", features = ["full"] } thiserror = "2" # bench diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index c7a90ad2f06b5..c51511c423487 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -51,6 +51,10 @@ pub struct StorageArgs { #[arg(value_parser = parse_slot)] slot: Option, + /// The known proxy address. If provided, the storage layout is retrieved from this address. + #[arg(long,value_parser = NameOrAddress::from_str)] + proxy: Option, + /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. @@ -134,7 +138,11 @@ impl StorageArgs { let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; - let source = find_source(client, address).await?; + let source = if let Some(proxy) = self.proxy { + find_source(client, proxy.resolve(&provider).await?).await? + } else { + find_source(client, address).await? + }; let metadata = source.items.first().unwrap(); if metadata.is_vyper() { eyre::bail!("Contract at provided address is not a valid Solidity contract") diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index e88ade5f28489..509caf0efd468 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1295,6 +1295,48 @@ casttest!(storage_layout_complex, |_prj, cmd| { ╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯ +"#]]); +}); + +casttest!(storage_layout_complex_proxy, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Sepolia).as_str(), + "--block", + "7857852", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0xE2588A9CAb7Ea877206E35f615a39f84a64A7A3b", + "--proxy", + "0x29fcb43b46531bca003ddc8fcb67ffe91900c762" + ]) + .assert_success() + .stdout_eq(str![[r#" + +╭----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╮ +| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | ++============================================================================================================================================================================================================================================================+ +| singleton | address | 0 | 0 | 20 | 239704109775411986678417050956533140837380441954 | 0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| modules | mapping(address => address) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| owners | mapping(address => address) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| ownerCount | uint256 | 3 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| threshold | uint256 | 4 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| nonce | uint256 | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| _deprecatedDomainSeparator | bytes32 | 6 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| signedMessages | mapping(bytes32 => uint256) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| +| approvedHashes | mapping(address => mapping(bytes32 => uint256)) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | +╰----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╯ + + "#]]); }); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index aaf0658c65ac3..465d3db337590 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1953,7 +1953,7 @@ contract SimpleScript is Script { .assert_success() .stdout_eq(str![[r#" {"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940994,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940645,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} -{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}"} +{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index cf6aeb349436b..a58b1058ebc2e 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,6 +9,7 @@ use crate::{ sequence::get_commit_hash, ScriptArgs, ScriptConfig, ScriptResult, }; +use alloy_chains::NamedChain; use alloy_network::TransactionBuilder; use alloy_primitives::{map::HashMap, utils::format_units, Address, Bytes, TxKind, U256}; use dialoguer::Confirm; @@ -346,6 +347,12 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); + // Get the native token symbol for the chain using NamedChain + let token_symbol = NamedChain::try_from(provider_info.chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); + // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -369,7 +376,7 @@ impl FilledTransactionsState { sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; + sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; sh_println!("\n==========================")?; } else { sh_println!( @@ -379,6 +386,7 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, + "token_symbol": token_symbol, }) )?; } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index f2b71064f1f56..75f39bf1d2e2e 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1017,7 +1017,10 @@ fn test_redactions() -> snapbox::Redactions { ("[SAVED_SENSITIVE_VALUES]", r"Sensitive values saved to: .*\.json"), ("[ESTIMATED_GAS_PRICE]", r"Estimated gas price:\s*(\d+(\.\d+)?)\s*gwei"), ("[ESTIMATED_TOTAL_GAS_USED]", r"Estimated total gas used for script: \d+"), - ("[ESTIMATED_AMOUNT_REQUIRED]", r"Estimated amount required:\s*(\d+(\.\d+)?)\s*ETH"), + ( + "[ESTIMATED_AMOUNT_REQUIRED]", + r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", + ), ]; for (placeholder, re) in redactions { r.insert(placeholder, Regex::new(re).expect(re)).expect(re); diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index eece0c928064d..e61d5e1b07f5c 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -278,8 +278,13 @@ impl EtherscanVerificationProvider { builder = if let Some(api_url) = api_url { // we don't want any trailing slashes because this can cause cloudflare issues: let api_url = api_url.trim_end_matches('/'); - let base_url = if *verifier_type != VerificationProviderType::Etherscan { - // If verifier is not Etherscan then set base url as api url without trialing /api. + + // Verifier is etherscan if explicitly set or if no verifier set (default sourcify) but + // API key passed. + let is_etherscan = verifier_type.is_etherscan() || + (verifier_type.is_sourcify() && etherscan_key.is_some()); + let base_url = if !is_etherscan { + // If verifier is not Etherscan then set base url as api url without /api suffix. api_url.strip_prefix("/api").unwrap_or(api_url) } else { base_url.unwrap_or(api_url) diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 07f1e09ea6aa3..01a06332a36ce 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -170,8 +170,9 @@ pub enum VerificationProviderType { impl VerificationProviderType { /// Returns the corresponding `VerificationProvider` for the key pub fn client(&self, key: Option<&str>) -> Result> { - // 1. If `--verifier sourcify` is set, always use Sourcify. - if matches!(self, Self::Sourcify) { + let has_key = key.as_ref().is_some_and(|k| !k.is_empty()); + // 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify. + if !has_key && self.is_sourcify() { sh_println!( "Attempting to verify on Sourcify. Pass the --etherscan-api-key to verify on Etherscan, \ or use the --verifier flag to verify on another provider." @@ -180,8 +181,8 @@ impl VerificationProviderType { } // 2. If `--verifier etherscan` is explicitly set, enforce the API key requirement. - if matches!(self, Self::Etherscan) { - if key.as_ref().is_none_or(|key| key.is_empty()) { + if self.is_etherscan() { + if !has_key { eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier") } return Ok(Box::::default()); @@ -194,11 +195,19 @@ impl VerificationProviderType { } // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan. - if key.as_ref().is_some_and(|k| !k.is_empty()) { + if has_key { return Ok(Box::::default()); } // 5. If no valid provider is specified, bail. eyre::bail!("No valid verification provider specified. Pass the --verifier flag to specify a provider or set the ETHERSCAN_API_KEY environment variable to use Etherscan as a verifier.") } + + pub fn is_sourcify(&self) -> bool { + matches!(self, Self::Sourcify) + } + + pub fn is_etherscan(&self) -> bool { + matches!(self, Self::Etherscan) + } } diff --git a/deny.toml b/deny.toml index ac92742909a18..33c5a4233e55d 100644 --- a/deny.toml +++ b/deny.toml @@ -11,6 +11,8 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0437 protobuf! Crash due to uncontrolled recursion in protobuf crate. "RUSTSEC-2024-0437", + # humantime is unmaintained + "RUSTSEC-2025-0014", ] # This section is considered when running `cargo deny check bans`. @@ -47,7 +49,6 @@ allow = [ "BSD-3-Clause", "ISC", "Unicode-3.0", - "OpenSSL", "Unlicense", "WTFPL", "BSL-1.0", From 2802d43da3811c20c0fcec2454fd326921fb47d9 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 15 Mar 2025 12:59:39 +0700 Subject: [PATCH 023/391] const LATEST_SOLC: Version = Version::new(0, 8, 29); (#28) * feat(forge): allow path in forge selectors upload (#10073) * feat(forge): allow path in forge selectors upload * Changes after review: reuse PathOrContractInfo * feat: add `x86_64-musl` and `aarch64-musl` release targets (#9984) * feat: add `x86_64` and `aarch64` musl targets * feat: update nextest matrices * try with aarch64 targets * feat: define granular linux targets and restore `aarch64` one * revert `matrices.py` file * chore: reenable impersonate test (#10076) * feat: solc 0.8.29 (#10078) * feat: solc 0.8.29 * update match --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Giovanni Napoli Co-authored-by: Matthias Seitz --- .github/workflows/release.yml | 10 ++++ Cargo.lock | 59 ++++++------------- Cargo.toml | 2 +- crates/cast/tests/cli/main.rs | 69 ++++++++++------------ crates/config/src/utils.rs | 3 +- crates/forge/bin/cmd/selectors.rs | 27 +++++++-- crates/forge/tests/cli/odyssey.rs | 6 ++ crates/forge/tests/cli/svm.rs | 8 ++- crates/forge/tests/cli/test_cmd.rs | 95 ++++++++++++++++++++++++++++++ crates/test-utils/src/util.rs | 4 +- 10 files changed, 196 insertions(+), 87 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e0b5739e8ce7..b798ea6fd68fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,11 +94,21 @@ jobs: svm_target_platform: linux-amd64 platform: linux arch: amd64 + - runner: Linux-20.04 + target: x86_64-unknown-linux-musl + svm_target_platform: linux-amd64 + platform: linux + arch: amd64 - runner: Linux-20.04 target: aarch64-unknown-linux-gnu svm_target_platform: linux-aarch64 platform: linux arch: arm64 + - runner: Linux-20.04 + target: aarch64-unknown-linux-musl + svm_target_platform: linux-aarch64 + platform: linux + arch: arm64 # This is pinned to `macos-13-large` to support old SDK versions. # If the runner is deprecated it should be pinned to the oldest available version of the runner. - runner: macos-13-large diff --git a/Cargo.lock b/Cargo.lock index 48d28ddf548d1..5224c76930bac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2191,7 +2191,7 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "clap", - "dirs 6.0.0", + "dirs", "eyre", "forge-fmt", "foundry-cli", @@ -2958,22 +2958,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", + "dirs-sys", ] [[package]] @@ -2986,18 +2977,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", -] - [[package]] name = "dirs-sys" version = "0.5.0" @@ -3882,15 +3861,15 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8f0bab060fd7c1764c4be2563e6933d39ec8c2b8a8d6c08aaf45ab29d08310" +checksum = "21089dce1284b88283a6dc1684b22347debb517e86f7e0033e1cf277fda3ea7e" dependencies = [ "alloy-json-abi", "alloy-primitives", "auto_impl", "derive_more 1.0.0", - "dirs 6.0.0", + "dirs", "dyn-clone", "foundry-compilers-artifacts", "foundry-compilers-core", @@ -3919,9 +3898,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102dd131e939d80cc5c85214d2f0f6ba20ed75cf098019c4d995791b4ebae05" +checksum = "c6cc7a5aec89a3e0f271ec522aac586f97d9104c3b31563716f9ca20113e7f5c" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3929,9 +3908,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db9ef02de4fda04ae3ed098afb6e16edd742d1073f972197a4566836b453bdcd" +checksum = "c0c1058532f7a062f9bba3cd807f74e034b2f37f8a27d8bcccabdc104102f847" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3953,9 +3932,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd9d33cbeda448af917105920fefdc3ac42f9ffdaa2d840d58207db7807ea29" +checksum = "33f5f4621a0fad72868c5174c01bbfdfb16a6a650edd2f4a41cf5e5dc611a15e" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3968,9 +3947,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746d121f7b86b84b20e582a27a56c49435768ad3b8005e9afeaf68b53a77fb5c" +checksum = "3de4acbffff75510c230626f2c3071efa1d6c031afc821a8ed410b67ee6958dd" dependencies = [ "alloy-primitives", "cfg-if", @@ -3995,7 +3974,7 @@ dependencies = [ "Inflector", "alloy-chains", "alloy-primitives", - "dirs 6.0.0", + "dirs", "dunce", "eyre", "figment", @@ -8780,12 +8759,12 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4197826bb07b996788b9860a95a1fe2c1307b2404a8c66f5ba825c42532b7c3c" +checksum = "9a30a58b94c1ddc5f07a428d16e58e8f4c7cee9e67130cda12ed148f5ef98fff" dependencies = [ "const-hex", - "dirs 5.0.1", + "dirs", "fs4", "reqwest", "semver 1.0.26", @@ -8800,9 +8779,9 @@ dependencies = [ [[package]] name = "svm-rs-builds" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074faea21171905847a96135b3896e2e0b74373758ca07b96a41c646cc04a8e5" +checksum = "ebddc0ea9fd9fb61d0de9585a99c58f87e8c14b8811726a57cc5f5b1a029585e" dependencies = [ "build_const", "const-hex", diff --git a/Cargo.toml b/Cargo.toml index eae5e6f6e266c..b55f708db90cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,7 +188,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.11.0", default-features = false } -foundry-compilers = { version = "0.13.3", default-features = false } +foundry-compilers = { version = "0.13.5", default-features = false } foundry-fork-db = "0.12" solang-parser = "=0.3.3" solar-parse = { version = "=0.1.1", default-features = false } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 06c5e61167f16..2d1ad75854999 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2032,43 +2032,38 @@ forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| { }); // https://github.com/foundry-rs/foundry/issues/9541 -forgetest_async!( - // ignore for now, fails with "json: unsupported value: NaN" - #[ignore] - cast_run_impersonated_tx, - |_prj, cmd| { - let (_api, handle) = anvil::spawn( - NodeConfig::test() - .with_auto_impersonate(true) - .with_eth_rpc_url(Some("https://sepolia.base.org")), - ) - .await; - - let http_endpoint = handle.http_endpoint(); - - let provider = ProviderBuilder::new().on_http(http_endpoint.parse().unwrap()); - - // send impersonated tx - let tx = TransactionRequest::default() - .with_from(address!("041563c07028Fc89106788185763Fc73028e8511")) - .with_to(address!("F38aA5909D89F5d98fCeA857e708F6a6033f6CF8")) - .with_input( - Bytes::from_str( - "0x60fe47b1000000000000000000000000000000000000000000000000000000000000000c", - ) - .unwrap(), - ); - - let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - - assert!(receipt.status()); - - // run impersonated tx - cmd.cast_fuse() - .args(["run", &receipt.transaction_hash.to_string(), "--rpc-url", &http_endpoint]) - .assert_success(); - } -); +forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { + let (_api, handle) = anvil::spawn( + NodeConfig::test() + .with_auto_impersonate(true) + .with_eth_rpc_url(Some("https://sepolia.base.org")), + ) + .await; + + let http_endpoint = handle.http_endpoint(); + + let provider = ProviderBuilder::new().on_http(http_endpoint.parse().unwrap()); + + // send impersonated tx + let tx = TransactionRequest::default() + .with_from(address!("041563c07028Fc89106788185763Fc73028e8511")) + .with_to(address!("F38aA5909D89F5d98fCeA857e708F6a6033f6CF8")) + .with_input( + Bytes::from_str( + "0x60fe47b1000000000000000000000000000000000000000000000000000000000000000c", + ) + .unwrap(), + ); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + // run impersonated tx + cmd.cast_fuse() + .args(["run", &receipt.transaction_hash.to_string(), "--rpc-url", &http_endpoint]) + .assert_success(); +}); // casttest!(fetch_src_blockscout, |_prj, cmd| { diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 94f4823dc7cb5..86b25d1043797 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -263,6 +263,7 @@ pub fn evm_spec_id(evm_version: EvmVersion, odyssey: bool) -> SpecId { EvmVersion::Paris => SpecId::MERGE, EvmVersion::Shanghai => SpecId::SHANGHAI, EvmVersion::Cancun => SpecId::CANCUN, - EvmVersion::Prague => SpecId::OSAKA, // Osaka enables EOF + EvmVersion::Prague => SpecId::PRAGUE, + EvmVersion::Osaka => SpecId::OSAKA, } } diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index 56c25cc003cd6..9c2db2be8d8ec 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -7,7 +7,7 @@ use foundry_cli::{ utils::{cache_local_signatures, FoundryPathExt}, }; use foundry_common::{ - compile::{compile_target, ProjectCompiler}, + compile::{compile_target, PathOrContractInfo, ProjectCompiler}, selectors::{import_selectors, SelectorImportData}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; @@ -36,8 +36,9 @@ pub enum SelectorsSubcommands { #[command(visible_alias = "up")] Upload { /// The name of the contract to upload selectors for. + /// Can also be in form of `path:contract name`. #[arg(required_unless_present = "all")] - contract: Option, + contract: Option, /// Upload selectors for all contracts in the project. #[arg(long, required_unless_present = "contract")] @@ -107,8 +108,15 @@ impl SelectorsSubcommands { }; let project = build_args.project()?; - let output = if let Some(name) = &contract { - let target_path = project.find_contract_path(name)?; + let output = if let Some(contract_info) = &contract { + let Some(contract_name) = contract_info.name() else { + eyre::bail!("No contract name provided.") + }; + + let target_path = contract_info + .path() + .map(Ok) + .unwrap_or_else(|| project.find_contract_path(contract_name))?; compile_target(&target_path, &project, false)? } else { ProjectCompiler::new().compile(&project)? @@ -125,8 +133,15 @@ impl SelectorsSubcommands { .map(|(_, contract, artifact)| (contract, artifact)) .collect() } else { - let contract = contract.unwrap(); - let found_artifact = output.find_first(&contract); + let contract_info = contract.unwrap(); + let contract = contract_info.name().unwrap().to_string(); + + let found_artifact = if let Some(path) = contract_info.path() { + output.find(project.root().join(path).as_path(), &contract) + } else { + output.find_first(&contract) + }; + let artifact = found_artifact .ok_or_else(|| { eyre::eyre!( diff --git a/crates/forge/tests/cli/odyssey.rs b/crates/forge/tests/cli/odyssey.rs index 7d98e79fcf082..f465c032bd134 100644 --- a/crates/forge/tests/cli/odyssey.rs +++ b/crates/forge/tests/cli/odyssey.rs @@ -1,3 +1,5 @@ +use foundry_compilers::artifacts::EvmVersion; + // Ensure we can run basic counter tests with EOF support. forgetest_init!(test_eof_flag, |prj, cmd| { if !has_docker() { @@ -5,6 +7,10 @@ forgetest_init!(test_eof_flag, |prj, cmd| { return; } + prj.update_config(|config| { + config.evm_version = EvmVersion::Osaka; + }); + cmd.forge_fuse().args(["test", "--eof"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index e0c10a052e936..d585529da8e40 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 27); +const LATEST_SOLC: Version = Version::new(0, 8, 29); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( @@ -57,6 +57,12 @@ contract CounterTest is Test {{ "# ); prj.add_test("Counter", &src).unwrap(); + + // we need to remove the pinned solc version for this + prj.update_config(|c| { + c.solc.take(); + }); + cmd.arg("test").assert_success().stdout_eq(str![[r#" ... Ran 1 test for test/Counter.sol:CounterTest diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index ce0f4b13f89d1..a13f8c51ba81a 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -3316,3 +3316,98 @@ Traces: "#]]); }); + +// +forgetest_init!(can_upload_selectors_with_path, |prj, cmd| { + prj.add_source( + "CounterV1.sol", + r#" +contract Counter { + uint256 public number; + + function setNumberV1(uint256 newNumber) public { + number = newNumber; + } + + function incrementV1() public { + number++; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "CounterV2.sol", + r#" +contract CounterV2 { + uint256 public number; + + function setNumberV2(uint256 newNumber) public { + number = newNumber; + } + + function incrementV2() public { + number++; + } +} + "#, + ) + .unwrap(); + + // Upload Counter without path fails as there are multiple contracts with same name. + cmd.args(["selectors", "upload", "Counter"]).assert_failure().stderr_eq(str![[r#" +... +Error: Multiple contracts found with the name `Counter` +... + +"#]]); + + // Upload without contract name should fail. + cmd.forge_fuse().args(["selectors", "upload", "src/Counter.sol"]).assert_failure().stderr_eq( + str![[r#" +... +Error: No contract name provided. +... + +"#]], + ); + + // Upload single CounterV2. + cmd.forge_fuse().args(["selectors", "upload", "CounterV2"]).assert_success().stdout_eq(str![[ + r#" +... +Uploading selectors for CounterV2... +... +Selectors successfully uploaded to OpenChain +... + +"# + ]]); + + // Upload CounterV1 with path. + cmd.forge_fuse() + .args(["selectors", "upload", "src/CounterV1.sol:Counter"]) + .assert_success() + .stdout_eq(str![[r#" +... +Uploading selectors for Counter... +... +Selectors successfully uploaded to OpenChain +... + +"#]]); + + // Upload Counter with path. + cmd.forge_fuse() + .args(["selectors", "upload", "src/Counter.sol:Counter"]) + .assert_success() + .stdout_eq(str![[r#" +... +Uploading selectors for Counter... +... +Selectors successfully uploaded to OpenChain +... + +"#]]); +}); diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 75f39bf1d2e2e..3c2b0546f7061 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -213,7 +213,7 @@ impl ExtTester { } } -/// Initializes a project with `forge init` at the given path. +/// Initializes a project with `forge init` at the given path from a template directory. /// /// This should be called after an empty project is created like in /// [some of this crate's macros](crate::forgetest_init). @@ -226,6 +226,8 @@ impl ExtTester { /// This used to use a `static` `Lazy`, but this approach does not with `cargo-nextest` because it /// runs each test in a separate process. Instead, we use a global lock file to ensure that only one /// test can initialize the template at a time. +/// +/// This sets the project's solc version to the [`SOLC_VERSION`]. #[allow(clippy::disallowed_macros)] pub fn initialize(target: &Path) { println!("initializing {}", target.display()); From eca7fc8c53535001016249581985038e1de0f7a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:39:47 +0700 Subject: [PATCH 024/391] chore(deps): bump zip in the cargo group across 1 directory (#31) Bumps the cargo group with 1 update in the / directory: [zip](https://github.com/zip-rs/zip2). Updates `zip` from 2.2.3 to 2.4.1 - [Release notes](https://github.com/zip-rs/zip2/releases) - [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md) - [Commits](https://github.com/zip-rs/zip2/compare/v2.2.3...v2.4.1) --- updated-dependencies: - dependency-name: zip dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5224c76930bac..58a3082ed6709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8537,7 +8537,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] @@ -8772,7 +8772,7 @@ dependencies = [ "serde_json", "sha2", "tempfile", - "thiserror 2.0.12", + "thiserror 1.0.69", "url", "zip", ] @@ -10609,9 +10609,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b280484c454e74e5fff658bbf7df8fdbe7a07c6b2de4a53def232c15ef138f3a" +checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd" dependencies = [ "arbitrary", "bzip2", From e24df49cb7a20c65b66ae095613761b8cf9d60b6 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:12:34 +0700 Subject: [PATCH 025/391] Add .circleci/config.yml (#33) From f62dc844821b4f224f90969790ced81a87dab791 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:46:08 +0700 Subject: [PATCH 026/391] Revert "python3-compile-vyper-contract (#27)" (#34) This reverts commit c235592917161f2eba6ec443739e84e524de9174. --- .github/workflows/nextest.yml | 4 +- Cargo.lock | 347 ++++++++++++----------------- Cargo.toml | 2 +- crates/cast/bin/cmd/storage.rs | 10 +- crates/cast/tests/cli/main.rs | 42 ---- crates/forge/tests/cli/script.rs | 2 +- crates/script/src/simulate.rs | 10 +- crates/test-utils/src/util.rs | 5 +- crates/verify/src/etherscan/mod.rs | 9 +- crates/verify/src/provider.rs | 19 +- deny.toml | 3 +- 11 files changed, 158 insertions(+), 295 deletions(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index 5f8a9561af7d4..c8c0e0961eb0c 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -68,11 +68,11 @@ jobs: with: bun-version: latest - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: 3.11 - name: Install Vyper - run: pip --version && pip install vyper==0.4.0 + run: pip install vyper==0.4.0 - name: Forge RPC cache uses: actions/cache@v3 diff --git a/Cargo.lock b/Cargo.lock index 58a3082ed6709..b4532ce77227a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,7 @@ dependencies = [ "foldhash", "getrandom 0.2.15", "hashbrown 0.15.2", - "indexmap 2.8.0", + "indexmap 2.7.1", "itoa", "k256", "keccak-asm", @@ -435,7 +435,7 @@ checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -708,7 +708,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -721,11 +721,11 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.8.0", + "indexmap 2.7.1", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "syn-solidity", "tiny-keccak", ] @@ -743,7 +743,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.100", + "syn 2.0.99", "syn-solidity", ] @@ -1289,7 +1289,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -1311,7 +1311,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -1322,7 +1322,7 @@ checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -1375,7 +1375,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -1565,9 +1565,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" dependencies = [ "futures-util", "pin-project-lite", @@ -1614,52 +1614,11 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-smithy-http" -version = "0.62.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" -dependencies = [ - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http 1.2.0", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-http-client" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0497ef5d53065b7cd6a35e9c1654bd1fefeae5c52900d91d1b188b0af0f29324" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "h2 0.4.8", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "pin-project-lite", - "rustls 0.21.12", - "tokio", - "tracing", -] - [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" dependencies = [ "aws-smithy-types", ] @@ -1676,33 +1635,36 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.0" +version = "1.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6328865e36c6fd970094ead6b05efd047d3a80ec5fc3be5e743910da9f2ebf8" +checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" dependencies = [ "aws-smithy-async", - "aws-smithy-http 0.62.0", - "aws-smithy-http-client", + "aws-smithy-http 0.60.12", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "fastrand", + "h2 0.3.26", "http 0.2.12", - "http 1.2.0", "http-body 0.4.6", "http-body 1.0.1", + "httparse", + "hyper 0.14.32", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", + "rustls 0.21.12", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.4" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1717,9 +1679,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.0" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" +checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" dependencies = [ "base64-simd", "bytes", @@ -1864,9 +1826,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.7.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" @@ -1982,7 +1944,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2271,9 +2233,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.32" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -2281,9 +2243,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -2315,14 +2277,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2578,9 +2540,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" dependencies = [ "unicode-segmentation", ] @@ -2762,7 +2724,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2773,7 +2735,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2847,7 +2809,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2868,7 +2830,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2878,7 +2840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -2905,9 +2867,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "unicode-xid", ] @@ -2917,10 +2880,9 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "unicode-xid", ] @@ -3008,7 +2970,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -3033,7 +2995,7 @@ checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -3165,7 +3127,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -3186,14 +3148,14 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "env_logger" -version = "0.11.7" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", "env_filter", - "jiff 0.2.4", + "humantime", "log", ] @@ -3271,7 +3233,7 @@ dependencies = [ "ahash", "alloy-dyn-abi", "alloy-primitives", - "indexmap 2.8.0", + "indexmap 2.7.1", ] [[package]] @@ -3323,12 +3285,12 @@ dependencies = [ [[package]] name = "fd-lock" -version = "4.0.4" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +checksum = "c44818c96aec5cadc9dacfb97bbcbcfc19a0de75b218412d56f57fbaab94e439" dependencies = [ "cfg-if", - "rustix 1.0.2", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -3344,9 +3306,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", "subtle", @@ -3515,7 +3477,7 @@ name = "forge-doc" version = "1.0.0" dependencies = [ "alloy-primitives", - "derive_more 2.0.1", + "derive_more 1.0.0", "eyre", "forge-fmt", "foundry-common", @@ -3624,7 +3586,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -4055,7 +4017,7 @@ version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "derive_more 2.0.1", + "derive_more 1.0.0", "foundry-common-fmt", "foundry-macros", "foundry-test-utils", @@ -4202,7 +4164,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -4362,7 +4324,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -4540,7 +4502,7 @@ checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f" dependencies = [ "bstr", "itoa", - "jiff 0.1.29", + "jiff", "thiserror 2.0.12", ] @@ -4762,7 +4724,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.8.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -4781,7 +4743,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.8.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -4919,7 +4881,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -5262,7 +5224,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -5344,7 +5306,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -5372,9 +5334,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "arbitrary", "equivalent", @@ -5462,7 +5424,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -5559,30 +5521,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "jiff" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "jiff-tzdb" version = "0.1.3" @@ -5912,9 +5850,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.47" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1a8fe3a4a01f28dab245c474cb7b95ccb4d3d2f17a5419a3d949f474c45e84" +checksum = "85c54109598152e19add1933b223b1913bcd16e27cbd7c2783269fe2f9a6cbdc" dependencies = [ "ammonia", "anyhow", @@ -5995,7 +5933,7 @@ checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6073,7 +6011,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6323,7 +6261,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6365,9 +6303,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.0" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "op-alloy-consensus" @@ -6482,7 +6420,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6565,7 +6503,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6624,7 +6562,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6645,7 +6583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.8.0", + "indexmap 2.7.1", ] [[package]] @@ -6698,7 +6636,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6747,7 +6685,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6801,11 +6739,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.21" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.8.23", + "zerocopy 0.7.35", ] [[package]] @@ -6847,7 +6785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6922,7 +6860,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -6942,7 +6880,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "version_check", "yansi", ] @@ -6954,7 +6892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d35f4dc9988d1326b065b4def5e950c3ed727aa03e3151b86cc9e2aec6b03f54" dependencies = [ "futures", - "indexmap 2.8.0", + "indexmap 2.7.1", "nix 0.29.0", "tokio", "tracing", @@ -7010,7 +6948,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -7033,7 +6971,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -7096,7 +7034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ "chrono", - "indexmap 2.8.0", + "indexmap 2.7.1", "newtype-uuid", "quick-xml 0.37.2", "strip-ansi-escapes", @@ -7449,9 +7387,9 @@ dependencies = [ [[package]] name = "revm" -version = "19.6.0" +version = "19.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b906766b7ba049b515848952b5ae74f363d456e98de2021048a513e442b4f42" +checksum = "dfc5bef3c95fadf3b6a24a253600348380c169ef285f9780a793bb7090c8990d" dependencies = [ "auto_impl", "cfg-if", @@ -7701,9 +7639,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" dependencies = [ "bitflags 2.9.0", "errno", @@ -7924,7 +7862,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -7963,9 +7901,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.8" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" +checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" [[package]] name = "sec1" @@ -8085,22 +8023,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8111,7 +8049,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8120,7 +8058,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.7.1", "itoa", "memchr", "ryu", @@ -8155,7 +8093,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8189,7 +8127,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.8.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", @@ -8206,7 +8144,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8241,7 +8179,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8507,7 +8445,7 @@ checksum = "71d07263243b313296eca18f18eda3a190902dc3284bf67ceff29b8b54dac3e6" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.8.0", + "indexmap 2.7.1", "parking_lot", "rayon", "rustc-hash", @@ -8550,7 +8488,7 @@ checksum = "970d7c774741f786d62cab78290e47d845b0b9c0c9d094a1642aced1d7946036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8722,7 +8660,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8735,7 +8673,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8803,9 +8741,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -8821,7 +8759,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8841,7 +8779,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8860,7 +8798,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.2", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -8892,7 +8830,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.2", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -8951,7 +8889,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -8962,7 +8900,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -9097,7 +9035,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -9200,7 +9138,7 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -9222,7 +9160,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -9373,7 +9311,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -9810,7 +9748,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "wasm-bindgen-shared", ] @@ -9845,7 +9783,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10090,7 +10028,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10101,7 +10039,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10112,7 +10050,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10123,7 +10061,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10500,7 +10438,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "synstructure", ] @@ -10510,6 +10448,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive 0.7.35", ] @@ -10530,7 +10469,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10541,7 +10480,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10561,7 +10500,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", "synstructure", ] @@ -10582,7 +10521,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10604,7 +10543,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.99", ] [[package]] @@ -10619,7 +10558,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.8.0", + "indexmap 2.7.1", "memchr", "thiserror 2.0.12", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index b55f708db90cc..f6cb1e06f6c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -255,7 +255,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = "2.0" async-trait = "0.1" -derive_more = { version = "2.0", features = ["full"] } +derive_more = { version = "1.0", features = ["full"] } thiserror = "2" # bench diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index c51511c423487..c7a90ad2f06b5 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -51,10 +51,6 @@ pub struct StorageArgs { #[arg(value_parser = parse_slot)] slot: Option, - /// The known proxy address. If provided, the storage layout is retrieved from this address. - #[arg(long,value_parser = NameOrAddress::from_str)] - proxy: Option, - /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. @@ -138,11 +134,7 @@ impl StorageArgs { let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; - let source = if let Some(proxy) = self.proxy { - find_source(client, proxy.resolve(&provider).await?).await? - } else { - find_source(client, address).await? - }; + let source = find_source(client, address).await?; let metadata = source.items.first().unwrap(); if metadata.is_vyper() { eyre::bail!("Contract at provided address is not a valid Solidity contract") diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 2d1ad75854999..fedcb7d7d2a70 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1295,48 +1295,6 @@ casttest!(storage_layout_complex, |_prj, cmd| { ╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯ -"#]]); -}); - -casttest!(storage_layout_complex_proxy, |_prj, cmd| { - cmd.args([ - "storage", - "--rpc-url", - next_rpc_endpoint(NamedChain::Sepolia).as_str(), - "--block", - "7857852", - "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), - "0xE2588A9CAb7Ea877206E35f615a39f84a64A7A3b", - "--proxy", - "0x29fcb43b46531bca003ddc8fcb67ffe91900c762" - ]) - .assert_success() - .stdout_eq(str![[r#" - -╭----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╮ -| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | -+============================================================================================================================================================================================================================================================+ -| singleton | address | 0 | 0 | 20 | 239704109775411986678417050956533140837380441954 | 0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| modules | mapping(address => address) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| owners | mapping(address => address) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| ownerCount | uint256 | 3 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| threshold | uint256 | 4 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| nonce | uint256 | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| _deprecatedDomainSeparator | bytes32 | 6 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| signedMessages | mapping(bytes32 => uint256) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -|----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| -| approvedHashes | mapping(address => mapping(bytes32 => uint256)) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | -╰----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╯ - - "#]]); }); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 465d3db337590..aaf0658c65ac3 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1953,7 +1953,7 @@ contract SimpleScript is Script { .assert_success() .stdout_eq(str![[r#" {"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940994,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940645,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} -{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} +{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index a58b1058ebc2e..cf6aeb349436b 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,7 +9,6 @@ use crate::{ sequence::get_commit_hash, ScriptArgs, ScriptConfig, ScriptResult, }; -use alloy_chains::NamedChain; use alloy_network::TransactionBuilder; use alloy_primitives::{map::HashMap, utils::format_units, Address, Bytes, TxKind, U256}; use dialoguer::Confirm; @@ -347,12 +346,6 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); - // Get the native token symbol for the chain using NamedChain - let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() - .native_currency_symbol() - .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -376,7 +369,7 @@ impl FilledTransactionsState { sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; sh_println!("\n==========================")?; } else { sh_println!( @@ -386,7 +379,6 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, - "token_symbol": token_symbol, }) )?; } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 3c2b0546f7061..123bda33dbb9d 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1019,10 +1019,7 @@ fn test_redactions() -> snapbox::Redactions { ("[SAVED_SENSITIVE_VALUES]", r"Sensitive values saved to: .*\.json"), ("[ESTIMATED_GAS_PRICE]", r"Estimated gas price:\s*(\d+(\.\d+)?)\s*gwei"), ("[ESTIMATED_TOTAL_GAS_USED]", r"Estimated total gas used for script: \d+"), - ( - "[ESTIMATED_AMOUNT_REQUIRED]", - r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", - ), + ("[ESTIMATED_AMOUNT_REQUIRED]", r"Estimated amount required:\s*(\d+(\.\d+)?)\s*ETH"), ]; for (placeholder, re) in redactions { r.insert(placeholder, Regex::new(re).expect(re)).expect(re); diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index e61d5e1b07f5c..eece0c928064d 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -278,13 +278,8 @@ impl EtherscanVerificationProvider { builder = if let Some(api_url) = api_url { // we don't want any trailing slashes because this can cause cloudflare issues: let api_url = api_url.trim_end_matches('/'); - - // Verifier is etherscan if explicitly set or if no verifier set (default sourcify) but - // API key passed. - let is_etherscan = verifier_type.is_etherscan() || - (verifier_type.is_sourcify() && etherscan_key.is_some()); - let base_url = if !is_etherscan { - // If verifier is not Etherscan then set base url as api url without /api suffix. + let base_url = if *verifier_type != VerificationProviderType::Etherscan { + // If verifier is not Etherscan then set base url as api url without trialing /api. api_url.strip_prefix("/api").unwrap_or(api_url) } else { base_url.unwrap_or(api_url) diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 01a06332a36ce..07f1e09ea6aa3 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -170,9 +170,8 @@ pub enum VerificationProviderType { impl VerificationProviderType { /// Returns the corresponding `VerificationProvider` for the key pub fn client(&self, key: Option<&str>) -> Result> { - let has_key = key.as_ref().is_some_and(|k| !k.is_empty()); - // 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify. - if !has_key && self.is_sourcify() { + // 1. If `--verifier sourcify` is set, always use Sourcify. + if matches!(self, Self::Sourcify) { sh_println!( "Attempting to verify on Sourcify. Pass the --etherscan-api-key to verify on Etherscan, \ or use the --verifier flag to verify on another provider." @@ -181,8 +180,8 @@ impl VerificationProviderType { } // 2. If `--verifier etherscan` is explicitly set, enforce the API key requirement. - if self.is_etherscan() { - if !has_key { + if matches!(self, Self::Etherscan) { + if key.as_ref().is_none_or(|key| key.is_empty()) { eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier") } return Ok(Box::::default()); @@ -195,19 +194,11 @@ impl VerificationProviderType { } // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan. - if has_key { + if key.as_ref().is_some_and(|k| !k.is_empty()) { return Ok(Box::::default()); } // 5. If no valid provider is specified, bail. eyre::bail!("No valid verification provider specified. Pass the --verifier flag to specify a provider or set the ETHERSCAN_API_KEY environment variable to use Etherscan as a verifier.") } - - pub fn is_sourcify(&self) -> bool { - matches!(self, Self::Sourcify) - } - - pub fn is_etherscan(&self) -> bool { - matches!(self, Self::Etherscan) - } } diff --git a/deny.toml b/deny.toml index 33c5a4233e55d..ac92742909a18 100644 --- a/deny.toml +++ b/deny.toml @@ -11,8 +11,6 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0437 protobuf! Crash due to uncontrolled recursion in protobuf crate. "RUSTSEC-2024-0437", - # humantime is unmaintained - "RUSTSEC-2025-0014", ] # This section is considered when running `cargo deny check bans`. @@ -49,6 +47,7 @@ allow = [ "BSD-3-Clause", "ISC", "Unicode-3.0", + "OpenSSL", "Unlicense", "WTFPL", "BSL-1.0", From 90896e02a97e4846707f923d85f0e6d20e113792 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 27 Apr 2025 05:39:27 +0700 Subject: [PATCH 027/391] Add .circleci/config.yml --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..62291703e26a7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello \ No newline at end of file From 9b9f087ed2f71981b133d29e06fe3f8e95397ff0 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 27 Apr 2025 09:23:52 +0700 Subject: [PATCH 028/391] Updated config.yml --- .circleci/config.yml | 120 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 62291703e26a7..5121ad4351f4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,31 +1,109 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + build: docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current + - image: ubuntu:23.04 + + - image: mongo:6.0.14 + command: [mongod, --smallfiles] + + - image: postgres:14.12 + # some containers require setting environment variables + environment: + POSTGRES_USER: user + + - image: redis@sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673 + + - image: rabbitmq:3.12.12 + + environment: + TEST_REPORTS: /tmp/test-reports + + working_directory: ~/my-project - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps steps: - # Checkout the code as the first step. - checkout + + - run: + command: echo 127.0.0.1 devhost | sudo tee -a /etc/hosts + + # Create Postgres users and database + # Note the YAML heredoc '|' for nicer formatting + - run: | + sudo -u root createuser -h localhost --superuser ubuntu && + sudo createdb -h localhost test_db + + - restore_cache: + keys: + - v1-my-project-{{ checksum "project.clj" }} + - v1-my-project- + + - run: + environment: + SSH_TARGET: "localhost" + TEST_ENV: "linux" + command: | + set -xu + mkdir -p ${TEST_REPORTS} + run-tests.sh + cp out/tests/*.xml ${TEST_REPORTS} + + - run: | + set -xu + mkdir -p /tmp/artifacts + create_jars.sh << pipeline.number >> + cp *.jar /tmp/artifacts + + - save_cache: + key: v1-my-project-{{ checksum "project.clj" }} + paths: + - ~/.m2 + + # Save artifacts + - store_artifacts: + path: /tmp/artifacts + destination: build + + # Upload test results + - store_test_results: + path: /tmp/test-reports + + deploy-stage: + docker: + - image: ubuntu:23.04 + working_directory: /tmp/my-project + steps: + - run: + name: Deploy if tests pass and branch is Staging + command: ansible-playbook site.yml -i staging + + deploy-prod: + docker: + - image: ubuntu:23.04 + working_directory: /tmp/my-project + steps: - run: - name: "Say hello" - command: "echo Hello, World!" + name: Deploy if tests pass and branch is Main + command: ansible-playbook site.yml -i production -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. + build-deploy: jobs: - - say-hello \ No newline at end of file + - build: + filters: + branches: + ignore: + - develop + - /feature-.*/ + - deploy-stage: + requires: + - build + filters: + branches: + only: staging + - deploy-prod: + requires: + - build + filters: + branches: + only: main \ No newline at end of file From ad4415ad276513863df50b5e4e8bace30bfc05f0 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 27 Apr 2025 09:28:14 +0700 Subject: [PATCH 029/391] Updated config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5121ad4351f4b..29513b62d13e3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ jobs: - image: ubuntu:23.04 - image: mongo:6.0.14 - command: [mongod, --smallfiles] + command: [mongod --dbpath /path/to/data] - image: postgres:14.12 # some containers require setting environment variables From 37b29ad897addfcbd4f01c01bec55283a27bd947 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 27 Apr 2025 09:31:08 +0700 Subject: [PATCH 030/391] Updated config.yml --- .circleci/config.yml | 120 ++++++++----------------------------------- 1 file changed, 21 insertions(+), 99 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 29513b62d13e3..62291703e26a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,109 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs jobs: - build: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job docker: - - image: ubuntu:23.04 - - - image: mongo:6.0.14 - command: [mongod --dbpath /path/to/data] - - - image: postgres:14.12 - # some containers require setting environment variables - environment: - POSTGRES_USER: user - - - image: redis@sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673 - - - image: rabbitmq:3.12.12 - - environment: - TEST_REPORTS: /tmp/test-reports - - working_directory: ~/my-project + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps steps: + # Checkout the code as the first step. - checkout - - - run: - command: echo 127.0.0.1 devhost | sudo tee -a /etc/hosts - - # Create Postgres users and database - # Note the YAML heredoc '|' for nicer formatting - - run: | - sudo -u root createuser -h localhost --superuser ubuntu && - sudo createdb -h localhost test_db - - - restore_cache: - keys: - - v1-my-project-{{ checksum "project.clj" }} - - v1-my-project- - - - run: - environment: - SSH_TARGET: "localhost" - TEST_ENV: "linux" - command: | - set -xu - mkdir -p ${TEST_REPORTS} - run-tests.sh - cp out/tests/*.xml ${TEST_REPORTS} - - - run: | - set -xu - mkdir -p /tmp/artifacts - create_jars.sh << pipeline.number >> - cp *.jar /tmp/artifacts - - - save_cache: - key: v1-my-project-{{ checksum "project.clj" }} - paths: - - ~/.m2 - - # Save artifacts - - store_artifacts: - path: /tmp/artifacts - destination: build - - # Upload test results - - store_test_results: - path: /tmp/test-reports - - deploy-stage: - docker: - - image: ubuntu:23.04 - working_directory: /tmp/my-project - steps: - - run: - name: Deploy if tests pass and branch is Staging - command: ansible-playbook site.yml -i staging - - deploy-prod: - docker: - - image: ubuntu:23.04 - working_directory: /tmp/my-project - steps: - run: - name: Deploy if tests pass and branch is Main - command: ansible-playbook site.yml -i production + name: "Say hello" + command: "echo Hello, World!" +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows workflows: - build-deploy: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. jobs: - - build: - filters: - branches: - ignore: - - develop - - /feature-.*/ - - deploy-stage: - requires: - - build - filters: - branches: - only: staging - - deploy-prod: - requires: - - build - filters: - branches: - only: main \ No newline at end of file + - say-hello \ No newline at end of file From 975ca47b8c27518fe24014a3455a46ff950b1e94 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 9 May 2025 22:38:42 +0700 Subject: [PATCH 031/391] Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/jekyll.yml | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/jekyll.yml diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml new file mode 100644 index 0000000000000..501686bcc9563 --- /dev/null +++ b/.github/workflows/jekyll.yml @@ -0,0 +1,65 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy Jekyll site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby + # https://github.com/ruby/setup-ruby/releases/tag/v1.207.0 + uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 + with: + ruby-version: '3.1' # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + cache-version: 0 # Increment this number if you need to re-download cached gems + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + # Outputs to the './_site' directory by default + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + - name: Upload artifact + # Automatically uploads an artifact from the './_site' directory by default + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 3f90e930ddf16aa53b2fe971d7afbf55e982be7f Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 9 May 2025 23:02:24 +0700 Subject: [PATCH 032/391] Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000000000..793d8e0e39e39 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,18 @@ +name: Docker Image CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) From 79528db656d98ec2c59e33f37e2e3e5980e21189 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 9 May 2025 23:18:47 +0700 Subject: [PATCH 033/391] Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 793d8e0e39e39..fe65b8b969f4d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + jobs: build: From 4d200a767a10e925ceb4185fa4765d102984173e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 9 May 2025 23:19:20 +0700 Subject: [PATCH 034/391] Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index fe65b8b969f4d..b37794b4a7f85 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -17,5 +17,5 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) +- name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From a585df630d075684256e8901eff9af4ef93006d3 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 10 May 2025 04:02:45 +0700 Subject: [PATCH 035/391] Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index b37794b4a7f85..e0c9c518e8748 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -17,5 +17,5 @@ jobs: steps: - uses: actions/checkout@v4 -- name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From 3fb94af1686be281d6ea3e86fb6977bdd7e27bfd Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 27 Apr 2025 06:45:56 +0700 Subject: [PATCH 036/391] Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. --- crates/forge/tests/cli/test_optimizer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index 6c286c52793ec..944abed57ff30 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1347,7 +1347,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.update_config(|config| { config.dynamic_test_linking = true; From 6949f7f91c979f391192d9aa6877d31257b3d518 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 25 May 2025 06:07:02 +0700 Subject: [PATCH 037/391] Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/jekyll.yml | 65 ------------------------------------ 1 file changed, 65 deletions(-) delete mode 100644 .github/workflows/jekyll.yml diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml deleted file mode 100644 index 501686bcc9563..0000000000000 --- a/.github/workflows/jekyll.yml +++ /dev/null @@ -1,65 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll site to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ["master"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Ruby - # https://github.com/ruby/setup-ruby/releases/tag/v1.207.0 - uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 - with: - ruby-version: '3.1' # Not needed with a .ruby-version file - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - cache-version: 0 # Increment this number if you need to re-download cached gems - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - - name: Build with Jekyll - # Outputs to the './_site' directory by default - run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" - env: - JEKYLL_ENV: production - - name: Upload artifact - # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v3 - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From 868fd5c064c707621925567db21692a766861ec6 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 25 May 2025 23:08:28 +0700 Subject: [PATCH 038/391] Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bf0543fb7482..1b18d4ad1dd2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -295,6 +295,9 @@ jobs: runs-on: ubuntu-latest needs: [prepare, release-docker, release, cleanup] if: failure() + permissions: + issues: write + contents: read steps: - uses: actions/checkout@v4 - uses: JasonEtco/create-an-issue@v2 From 7aa139ecd096ab7ebd551e5196b497a3f354c682 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 25 May 2025 23:26:49 +0700 Subject: [PATCH 039/391] Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0486f2237f10b..d3fe0f910e5b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,9 +38,10 @@ jobs: uses: peaceiris/actions-gh-pages@v3 if: github.event_name == 'push' && github.ref == 'refs/heads/master' with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: target/doc - force_orphan: true + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc + force_orphan: true + commit_message: "Deploy documentation [skip ci]" doctest: runs-on: ubuntu-latest From f0897aa843fdcc48871abde95b9597cdffe263d1 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 25 May 2025 23:26:49 +0700 Subject: [PATCH 040/391] Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0486f2237f10b..d3fe0f910e5b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,9 +38,10 @@ jobs: uses: peaceiris/actions-gh-pages@v3 if: github.event_name == 'push' && github.ref == 'refs/heads/master' with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: target/doc - force_orphan: true + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc + force_orphan: true + commit_message: "Deploy documentation [skip ci]" doctest: runs-on: ubuntu-latest From 7bdfb1b6ad2c5221781bed53f4a470828c5570cc Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 25 May 2025 23:08:28 +0700 Subject: [PATCH 041/391] Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bf0543fb7482..1b18d4ad1dd2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -295,6 +295,9 @@ jobs: runs-on: ubuntu-latest needs: [prepare, release-docker, release, cleanup] if: failure() + permissions: + issues: write + contents: read steps: - uses: actions/checkout@v4 - uses: JasonEtco/create-an-issue@v2 From 3be9159c06ac637f76a65ef1e9e7299b7257aa8f Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 26 May 2025 01:43:49 +0700 Subject: [PATCH 042/391] Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3fe0f910e5b4..f4d5d2d361f5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,15 @@ jobs: publish_dir: target/doc force_orphan: true commit_message: "Deploy documentation [skip ci]" - + - name: Push changes + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + to pass before a push is allowed. + git commit -S -m "Auto-update gh-pages" + git push origin --force gh-pages + +### Solution **Review Repository Rules:** + doctest: runs-on: ubuntu-latest timeout-minutes: 30 From 428d049479cdd617336544f38a0646b60c5f8c68 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Mon, 26 May 2025 12:46:14 +0200 Subject: [PATCH 043/391] chore(deps): bump revm to 24.0.0 (#10601) --- Cargo.lock | 208 +++++++++---------- Cargo.toml | 68 +++--- crates/anvil/core/src/eth/transaction/mod.rs | 4 +- crates/anvil/src/eth/backend/mem/mod.rs | 4 +- crates/anvil/tests/it/optimism.rs | 2 +- crates/cheatcodes/src/inspector.rs | 9 +- crates/debugger/src/tui/draw.rs | 1 - 7 files changed, 139 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e972d3906b5dc..a8c4a15e7bc6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5848366a4f08dca1caca0a6151294a4799fe2e59ba25df100491d92e0b921b1c" +checksum = "517e5acbd38b6d4c59da380e8bbadc6d365bf001903ce46cf5521c53c647e07b" dependencies = [ "alloy-primitives", "num_enum", @@ -74,10 +74,10 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7329eb72d95576dfb8813175bcf671198fb24266b0b3e520052a513e30c284df" dependencies = [ - "alloy-eips 1.0.7", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-trie", "auto_impl", "c-kzg", @@ -99,10 +99,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31b286aeef04a32720c10defd21c3aa6c626154ac442b55f6d472caeb1c6741" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 1.0.7", + "alloy-serde", "serde", ] @@ -185,26 +185,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "alloy-eips" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609515c1955b33af3d78d26357540f68c5551a90ef58fd53def04f2aa074ec43" -dependencies = [ - "alloy-eip2124", - "alloy-eip2930", - "alloy-eip7702", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.14.0", - "auto_impl", - "c-kzg", - "derive_more 2.0.1", - "either", - "serde", - "sha2 0.10.9", -] - [[package]] name = "alloy-eips" version = "1.0.7" @@ -216,7 +196,7 @@ dependencies = [ "alloy-eip7702", "alloy-primitives", "alloy-rlp", - "alloy-serde 1.0.7", + "alloy-serde", "auto_impl", "c-kzg", "derive_more 2.0.1", @@ -241,12 +221,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c5b34c78c42525917b236e4135b1951ca183ede4004b594db0effee8bed169" +checksum = "394b09cf3a32773eedf11828987f9c72dfa74545040be0422e3f5f09a2a3fab9" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-hardforks", "alloy-primitives", "alloy-sol-types", @@ -264,9 +244,9 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b81b2dfd278d58af8bfde8753fa4685407ba8fbad8bc88a2bb0e065eed48478" dependencies = [ - "alloy-eips 1.0.7", + "alloy-eips", "alloy-primitives", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-trie", "serde", ] @@ -318,13 +298,13 @@ checksum = "f0ed07e76fbc72790a911ea100cdfbe85b1f12a097c91b948042e854959d140e" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-signer", "alloy-sol-types", "async-trait", @@ -343,20 +323,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05aa52713c376f797b3c7077708585f22a5c3053a7b1b2b355ea98edeb2052d" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-primitives", - "alloy-serde 1.0.7", + "alloy-serde", "serde", ] [[package]] name = "alloy-op-evm" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4fda8b1920a38a5adc607d6ff7be1e8991e16ffcf97bb12765644b87331c598" +checksum = "9f32538cc243ec5d4603da9845cc2f5254c6a3a78e82475beb1a2a1de6c0d36c" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -415,7 +395,7 @@ checksum = "05a3f7a59c276c6e410267e77a166f9297dbe74e4605f1abf625e29d85c53144" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -535,7 +515,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 1.0.7", + "alloy-serde", "serde", ] @@ -547,7 +527,7 @@ checksum = "51e15bd6456742d6dcadacf3cd238a90a8a7aa9f00bc7cc641ae272f5d3f5d4f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "serde", ] @@ -559,7 +539,7 @@ checksum = "67971a228100ac65bd86e90439028853435f21796330ef08f00a70a918a84126" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", ] [[package]] @@ -579,10 +559,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bcf49fe91b3d621440dcc5bb067afaeba5ca4b07f59e42fb7af42944146a8c0" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 1.0.7", + "alloy-serde", "derive_more 2.0.1", "jsonwebtoken", "rand 0.8.5", @@ -598,11 +578,11 @@ checksum = "89d9b4293dfd4721781d33ee40de060376932d4a55d421cf6618ad66ff97cc52" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -618,7 +598,7 @@ checksum = "7f68f020452c0d570b4eee22d4ffda9e4eda68ebcf67e1199d6dff48097f442b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "serde", "serde_json", "thiserror 2.0.12", @@ -632,21 +612,10 @@ checksum = "62a82f15f296c2c83c55519d21ca07801fb58b118878b0f4777250968e49f4fe" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "serde", ] -[[package]] -name = "alloy-serde" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4dba6ff08916bc0a9cbba121ce21f67c0b554c39cf174bc7b9df6c651bd3c3b" -dependencies = [ - "alloy-primitives", - "serde", - "serde_json", -] - [[package]] name = "alloy-serde" version = "1.0.7" @@ -1051,7 +1020,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1061,7 +1030,7 @@ dependencies = [ "alloy-pubsub", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1113,12 +1082,12 @@ version = "1.2.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "bytes", "foundry-common", "foundry-evm", @@ -2389,7 +2358,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -3814,7 +3783,7 @@ dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -3947,13 +3916,13 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-json-abi", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-signer", "clap", "dialoguer", @@ -4145,7 +4114,7 @@ version = "1.2.1" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-ens", "alloy-json-abi", "alloy-primitives", @@ -4191,7 +4160,7 @@ version = "1.2.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -4200,7 +4169,7 @@ dependencies = [ "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -4249,7 +4218,7 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 1.0.7", + "alloy-serde", "chrono", "foundry-macros", "revm", @@ -4570,9 +4539,9 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99831be91edd8a025fa60443b5404ad407708bc337d2e20440d498b5806fab8" +checksum = "b02fb598e4a8ae7b7af7c256081a419b071eacf5e03537806b339b3151409403" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6169,9 +6138,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "newtype-uuid" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056" +checksum = "a8ba303c7a8f8fdee1fe1513cfd918f50f1c69bf65c91b39217bfc2b2af5c081" dependencies = [ "uuid 1.17.0", ] @@ -6487,17 +6456,17 @@ dependencies = [ [[package]] name = "op-alloy-consensus" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f318b09e24148f07392c5e011bae047a0043851f9041145df5f3b01e4fedd1e" +checksum = "bb35d16e5420e43e400a235783e3d18b6ba564917139b668b48e9ac42cb3d35a" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "derive_more 2.0.1", "serde", "thiserror 2.0.12", @@ -6511,16 +6480,16 @@ checksum = "4ef71f23a8caf6f2a2d5cafbdc44956d44e6014dcb9aa58abf7e4e6481c6ec34" [[package]] name = "op-alloy-rpc-types" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15ede8322c10c21249de4fced204e2af4978972e715afee34b6fe684d73880cf" +checksum = "7534a0ec6b8409edc511acbe77abe7805aa63129b98e9a915bb4eb8555eaa6ff" dependencies = [ "alloy-consensus", - "alloy-eips 1.0.7", + "alloy-eips", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 1.0.7", + "alloy-serde", "derive_more 2.0.1", "op-alloy-consensus", "serde", @@ -6530,8 +6499,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "4.0.2" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47296d449fbe2d5cc74ab6e1213dee88cae3e2fd238343bec605c3c687bbcfab" dependencies = [ "auto_impl", "once_cell", @@ -7585,8 +7555,9 @@ dependencies = [ [[package]] name = "revm" -version = "23.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3ae9d1b08303eb5150dcf820a29e14235cf3f24f6c09024458a4dcbffe6695" dependencies = [ "revm-bytecode", "revm-context", @@ -7603,8 +7574,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91f9b90b3bab18942252de2d970ee8559794c49ca7452b2cc1774456040f8fb" dependencies = [ "bitvec", "once_cell", @@ -7615,8 +7587,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "4.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b181214eb2bbb76ee9d6195acba19857d991d2cdb9a65b7cb6939c30250a3966" dependencies = [ "cfg-if", "derive-where", @@ -7630,8 +7603,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "4.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b844f48a411e62c7dde0f757bf5cce49c85b86d6fc1d3b2722c07f2bec4c3ce" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -7645,10 +7619,11 @@ dependencies = [ [[package]] name = "revm-database" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad3fbe34f6bb00a9c3155723b3718b9cb9f17066ba38f9eb101b678cd3626775" dependencies = [ - "alloy-eips 0.14.0", + "alloy-eips", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -7658,8 +7633,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8acd36784a6d95d5b9e1b7be3ce014f1e759abb59df1fa08396b30f71adc2a" dependencies = [ "auto_impl", "revm-primitives", @@ -7669,8 +7645,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "4.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9204e3ac1a8edb850cc441a6a1d0f2251c0089e5fffdaba11566429e6c64e" dependencies = [ "auto_impl", "revm-bytecode", @@ -7686,8 +7663,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "4.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae4881eeae6ff35417c8569bc7cc03b6c0969869ee2c9b3945a39b4f9fa58bc5" dependencies = [ "auto_impl", "revm-context", @@ -7702,9 +7680,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.22.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f847f5e88a09ac84b36529fbe2fee80b3d8bbf91e9a7ae3ea856c4125d0d232" +checksum = "4b50ef375dbacefecfdacf8f02afc31df98acc5d8859a6f2b24d121ff2a740a8" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -7720,8 +7698,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "19.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ee65e57375c6639b0f50555e92a4f1b2434349dd32f52e2176f5c711171697" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -7731,8 +7710,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "20.1.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9311e735123d8d53a02af2aa81877bba185be7c141be7f931bb3d2f3af449c" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -7755,8 +7735,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "19.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "19.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ea2ea0134568ee1e14281ce52f60e2710d42be316888d464c53e37ff184fd8" dependencies = [ "alloy-primitives", "num_enum", @@ -7765,8 +7746,9 @@ dependencies = [ [[package]] name = "revm-state" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=b5808253#b580825320708f0c47d3734ceab03d90c0b11ba1" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" dependencies = [ "bitflags 2.9.1", "revm-bytecode", diff --git a/Cargo.toml b/Cargo.toml index 76f51d5337151..8686f046c2c14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,7 +200,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.17.0", default-features = false } foundry-compilers = { version = "0.16.1", default-features = false } -foundry-fork-db = "0.14" +foundry-fork-db = "0.15" solang-parser = { version = "=0.3.8", package = "foundry-solang-parser" } solar-ast = { version = "=0.1.3", default-features = false } solar-parse = { version = "=0.1.3", default-features = false } @@ -208,28 +208,28 @@ solar-interface = { version = "=0.1.3", default-features = false } solar-sema = { version = "=0.1.3", default-features = false } ## alloy -alloy-consensus = { version = "1.0.5", default-features = false } -alloy-contract = { version = "1.0.5", default-features = false } -alloy-eips = { version = "1.0.5", default-features = false } -alloy-ens = { version = "1.0.5", default-features = false } -alloy-genesis = { version = "1.0.5", default-features = false } -alloy-json-rpc = { version = "1.0.5", default-features = false } -alloy-network = { version = "1.0.5", default-features = false } -alloy-provider = { version = "1.0.5", default-features = false } -alloy-pubsub = { version = "1.0.5", default-features = false } -alloy-rpc-client = { version = "1.0.5", default-features = false } -alloy-rpc-types = { version = "1.0.5", default-features = true } -alloy-serde = { version = "1.0.5", default-features = false } -alloy-signer = { version = "1.0.5", default-features = false } -alloy-signer-aws = { version = "1.0.5", default-features = false } -alloy-signer-gcp = { version = "1.0.5", default-features = false } -alloy-signer-ledger = { version = "1.0.5", default-features = false } -alloy-signer-local = { version = "1.0.5", default-features = false } -alloy-signer-trezor = { version = "1.0.5", default-features = false } -alloy-transport = { version = "1.0.5", default-features = false } -alloy-transport-http = { version = "1.0.5", default-features = false } -alloy-transport-ipc = { version = "1.0.5", default-features = false } -alloy-transport-ws = { version = "1.0.5", default-features = false } +alloy-consensus = { version = "1.0.7", default-features = false } +alloy-contract = { version = "1.0.7", default-features = false } +alloy-eips = { version = "1.0.7", default-features = false } +alloy-ens = { version = "1.0.7", default-features = false } +alloy-genesis = { version = "1.0.7", default-features = false } +alloy-json-rpc = { version = "1.0.7", default-features = false } +alloy-network = { version = "1.0.7", default-features = false } +alloy-provider = { version = "1.0.7", default-features = false } +alloy-pubsub = { version = "1.0.7", default-features = false } +alloy-rpc-client = { version = "1.0.7", default-features = false } +alloy-rpc-types = { version = "1.0.7", default-features = true } +alloy-serde = { version = "1.0.7", default-features = false } +alloy-signer = { version = "1.0.7", default-features = false } +alloy-signer-aws = { version = "1.0.7", default-features = false } +alloy-signer-gcp = { version = "1.0.7", default-features = false } +alloy-signer-ledger = { version = "1.0.7", default-features = false } +alloy-signer-local = { version = "1.0.7", default-features = false } +alloy-signer-trezor = { version = "1.0.7", default-features = false } +alloy-transport = { version = "1.0.7", default-features = false } +alloy-transport-http = { version = "1.0.7", default-features = false } +alloy-transport-ipc = { version = "1.0.7", default-features = false } +alloy-transport-ws = { version = "1.0.7", default-features = false } ## alloy-core alloy-dyn-abi = "1.0" @@ -249,18 +249,18 @@ alloy-rlp = "0.3" alloy-trie = "0.8.1" ## op-alloy -op-alloy-consensus = "0.16.0" -op-alloy-rpc-types = "0.16.0" +op-alloy-consensus = "0.17.1" +op-alloy-rpc-types = "0.17.1" op-alloy-flz = "0.13.0" ## revm -revm = { version = "23.1.0", default-features = false } -revm-inspectors = { version = "0.22.3", features = ["serde"] } -op-revm = { version = "4.0.2", default-features = false } +revm = { version = "24.0.0", default-features = false } +revm-inspectors = { version = "0.23.0", features = ["serde"] } +op-revm = { version = "5.0.0", default-features = false } ## alloy-evm -alloy-evm = "0.9.1" -alloy-op-evm = "0.9.1" +alloy-evm = "0.10.0" +alloy-op-evm = "0.10.0" ## cli anstream = "0.6" @@ -390,12 +390,12 @@ zip-extract = "=0.2.1" # alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } ## alloy-evm -# alloy-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "95f6a8a" } -# alloy-op-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "95f6a8a" } +# alloy-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "8076e12" } +# alloy-op-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "8076e12" } ## revm -revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } -op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } +# revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } +# op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } ## foundry diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index f6122b2893207..ae6a7262565ca 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -60,7 +60,7 @@ pub fn transaction_request_to_typed( from: from.unwrap_or_default(), source_hash: other.get_deserialized::("sourceHash")?.ok()?, to: to.unwrap_or_default(), - mint: Some(mint), + mint, value: value.unwrap_or_default(), gas_limit: gas.unwrap_or_default(), is_system_transaction: other.get_deserialized::("isSystemTx")?.ok()?, @@ -574,7 +574,7 @@ impl PendingTransaction { let deposit = DepositTransactionParts { source_hash: *source_hash, - mint: *mint, + mint: Some(*mint), is_system_transaction: *is_system_transaction, }; diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index a7b02ff39c059..321ed41ea3925 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -3178,8 +3178,8 @@ impl TransactionValidator for Backend { // 1. no gas cost check required since already have prepaid gas from L1 // 2. increment account balance by deposited amount before checking for sufficient // funds `tx.value <= existing account value + deposited value` - if value > account.balance + U256::from(deposit_tx.mint.unwrap_or_default()) { - warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint.unwrap_or_default()), value, *pending.sender()); + if value > account.balance + U256::from(deposit_tx.mint) { + warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender()); return Err(InvalidTransactionError::InsufficientFunds); } } diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index e8bfe4d6560a5..f871cca724a97 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -200,7 +200,7 @@ async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value( source_hash: b256!("0x0000000000000000000000000000000000000000000000000000000000000000"), from: sender, to: TxKind::Call(recipient), - mint: Some(send_value), + mint: send_value, value: U256::from(send_value), gas_limit: 21_000, is_system_transaction: false, diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 9677806893d3f..4d9b5cd5ef5ed 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -54,7 +54,7 @@ use revm::{ context_interface::{transaction::SignedAuthorization, CreateScheme}, handler::FrameResult, interpreter::{ - interpreter_types::{Jumps, LoopControl, MemoryTr}, + interpreter_types::{Jumps, MemoryTr}, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, Host, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, @@ -1845,9 +1845,10 @@ impl Cheatcodes { if let Some(paused_gas) = self.gas_metering.paused_frames.last() { // Keep gas constant if paused. // Make sure we record the memory changes so that memory expansion is not paused. - let memory = interpreter.control.gas.memory; + let memory = *interpreter.control.gas.memory(); interpreter.control.gas = *paused_gas; - interpreter.control.gas.memory = memory; + interpreter.control.gas.memory_mut().words_num = memory.words_num; + interpreter.control.gas.memory_mut().expansion_cost = memory.expansion_cost; } else { // Record frame paused gas. self.gas_metering.paused_frames.push(interpreter.control.gas); @@ -1889,7 +1890,7 @@ impl Cheatcodes { #[cold] fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) { - interpreter.control.gas = Gas::new(interpreter.control.gas().limit()); + interpreter.control.gas = Gas::new(interpreter.control.gas.limit()); self.gas_metering.reset = false; } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index a918bc89ea733..580d6f958ac44 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -199,7 +199,6 @@ impl TUIContext<'_> { CallKind::CallCode => "Contract callcode", CallKind::DelegateCall => "Contract delegatecall", CallKind::AuthCall => "Contract authcall", - CallKind::EOFCreate => "EOF contract creation", }; let title = format!( "{} {} ", From 7ff1d3b2d4ae18d07495a85b1626be7cdf291a5e Mon Sep 17 00:00:00 2001 From: pistomat Date: Mon, 26 May 2025 16:26:49 +0200 Subject: [PATCH 044/391] feat: implement add_balance endpoint (#10636) --- crates/anvil/core/src/eth/mod.rs | 14 +++++++++++++- crates/anvil/src/eth/api.rs | 13 +++++++++++++ crates/anvil/tests/it/fork.rs | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index df07f62ee730c..ac7db74892003 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -361,9 +361,21 @@ pub enum EthRequest { SetRpcUrl(String), /// Modifies the balance of an account. - #[serde(rename = "anvil_setBalance", alias = "hardhat_setBalance")] + #[serde( + rename = "anvil_setBalance", + alias = "hardhat_setBalance", + alias = "tenderly_setBalance" + )] SetBalance(Address, #[serde(deserialize_with = "deserialize_number")] U256), + /// Increases the balance of an account. + #[serde( + rename = "anvil_addBalance", + alias = "hardhat_addBalance", + alias = "tenderly_addBalance" + )] + AddBalance(Address, #[serde(deserialize_with = "deserialize_number")] U256), + /// Modifies the ERC20 balance of an account. #[serde( rename = "anvil_dealERC20", diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 243ae88440e8d..ec6fbeb4227a1 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -356,6 +356,9 @@ impl EthApi { EthRequest::SetBalance(addr, val) => { self.anvil_set_balance(addr, val).await.to_rpc_result() } + EthRequest::AddBalance(addr, val) => { + self.anvil_add_balance(addr, val).await.to_rpc_result() + } EthRequest::DealERC20(addr, token_addr, val) => { self.anvil_deal_erc20(addr, token_addr, val).await.to_rpc_result() } @@ -1856,6 +1859,16 @@ impl EthApi { Ok(()) } + /// Increases the balance of an account. + /// + /// Handler for RPC call: `anvil_addBalance` + pub async fn anvil_add_balance(&self, address: Address, balance: U256) -> Result<()> { + node_info!("anvil_addBalance"); + let current_balance = self.backend.get_balance(address, None).await?; + self.backend.set_balance(address, current_balance + balance).await?; + Ok(()) + } + /// Deals ERC20 tokens to a address /// /// Handler for RPC call: `anvil_dealERC20` diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 54bae673a6bd0..e4e1cda1a31d3 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1490,6 +1490,22 @@ async fn test_set_erc20_balance() { assert_eq!(new_balance, value); } +#[tokio::test(flavor = "multi_thread")] +async fn test_add_balance() { + let config: NodeConfig = fork_config(); + let address = config.genesis_accounts[0].address(); + let (api, _handle) = spawn(config).await; + + let start_balance = U256::from(100_000_u64); + api.anvil_set_balance(address, start_balance).await.unwrap(); + + let balance_increase = U256::from(50_000_u64); + api.anvil_add_balance(address, balance_increase).await.unwrap(); + + let new_balance = api.balance(address, None).await.unwrap(); + assert_eq!(new_balance, start_balance + balance_increase); +} + #[tokio::test(flavor = "multi_thread")] async fn test_reset_updates_cache_path_when_rpc_url_not_provided() { let config: NodeConfig = fork_config(); From 15d07f0431bf217aa292e3ffe3dbffd4a48dae48 Mon Sep 17 00:00:00 2001 From: zark <77061323+zarkk01@users.noreply.github.com> Date: Mon, 26 May 2025 23:21:59 +0300 Subject: [PATCH 045/391] fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- Cargo.lock | 1 + crates/sol-macro-gen/Cargo.toml | 2 ++ crates/sol-macro-gen/src/sol_macro_gen.rs | 8 +++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8c4a15e7bc6d..c05a8b99f5d05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3978,6 +3978,7 @@ dependencies = [ "alloy-sol-macro-input", "eyre", "foundry-common", + "heck", "prettyplease", "proc-macro2", "quote", diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index 79b47880f33f9..b983dd6a762b8 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -26,3 +26,5 @@ prettyplease.workspace = true serde_json.workspace = true eyre.workspace = true + +heck.workspace = true diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 657dd4cbb8458..1e40d51169695 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -21,6 +21,8 @@ use std::{ str::FromStr, }; +use heck::ToSnakeCase; + pub struct SolMacroGen { pub path: PathBuf, pub name: String, @@ -209,7 +211,7 @@ edition = "2021" for instance in &self.instances { let contents = instance.expansion.as_ref().unwrap(); - let name = instance.name.to_lowercase(); + let name = instance.name.to_snake_case(); let path = src.join(format!("{name}.rs")); let file = syn::parse2(contents.clone()) .wrap_err_with(|| parse_error(&format!("{}:{}", path.display(), name)))?; @@ -266,7 +268,7 @@ edition = "2021" .to_string(); for instance in &self.instances { - let name = instance.name.to_lowercase(); + let name = instance.name.to_snake_case(); if !single_file { // Module write_mod_name(&mut mod_contents, &name)?; @@ -328,7 +330,7 @@ edition = "2021" )?; if !single_file { for instance in &self.instances { - let name = instance.name.to_lowercase(); + let name = instance.name.to_snake_case(); let path = if is_mod { crate_path.join(format!("{name}.rs")) } else { From 1e705a697ff01d4267a54b2052de1f7204baf7fb Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Mon, 26 May 2025 16:04:58 -0500 Subject: [PATCH 046/391] chore: standardize lint help + validate docs existance (#10639) --- crates/forge/tests/cli/lint.rs | 53 +++++++++++++++++++ crates/lint/src/linter.rs | 13 ++--- crates/lint/src/sol/gas/keccak.rs | 3 +- crates/lint/src/sol/high/incorrect_shift.rs | 3 +- crates/lint/src/sol/info/mixed_case.rs | 3 +- crates/lint/src/sol/info/pascal_case.rs | 3 +- .../lint/src/sol/info/screaming_snake_case.rs | 3 +- crates/lint/src/sol/macros.rs | 10 ++-- crates/lint/src/sol/med/div_mul.rs | 3 +- crates/lint/src/sol/mod.rs | 4 +- .../lint/testdata/DivideBeforeMultiply.stderr | 6 +++ crates/lint/testdata/IncorrectShift.stderr | 5 ++ crates/lint/testdata/Keccak256.stderr | 2 + crates/lint/testdata/MixedCase.stderr | 18 ++++--- .../lint/testdata/ScreamingSnakeCase.stderr | 12 +++-- crates/lint/testdata/StructPascalCase.stderr | 12 ++--- 16 files changed, 110 insertions(+), 43 deletions(-) diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index 94599eb059b17..2223d1cf11fce 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,3 +1,4 @@ +use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; use foundry_config::{LintSeverity, LinterConfig}; const CONTRACT: &str = r#" @@ -53,6 +54,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 | (1 / 2) * 3; | ----------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply "#]]); @@ -75,6 +77,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 6 | uint256 VARIABLE_MIXED_CASE_INFO; | ------------------------ | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable "#]]); @@ -109,6 +112,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 6 | uint256 VARIABLE_MIXED_CASE_INFO; | ------------------------ | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable "#]]); @@ -134,6 +138,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 | (1 / 2) * 3; | ----------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply "#]]); @@ -160,8 +165,56 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 13 | result = 8 >> localValue; | --------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift "# ]]); }); + +#[tokio::test] +async fn ensure_lint_rule_docs() { + const FOUNDRY_BOOK_LINT_PAGE_URL: &str = + "https://book.getfoundry.sh/reference/forge/forge-lint"; + + // Fetch the content of the lint reference + let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { + Ok(resp) => { + if !resp.status().is_success() { + panic!( + "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", + status = resp.status() + ); + } + match resp.text().await { + Ok(text) => text, + Err(e) => { + panic!("Failed to read response text: {e}"); + } + } + } + Err(e) => { + panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); + } + }; + + // Ensure no missing lints + let mut missing_lints = Vec::new(); + for lint in REGISTERED_LINTS { + let selector = format!("#{}", lint.id()); + if !content.contains(&selector) { + missing_lints.push(lint.id()); + } + } + + if !missing_lints.is_empty() { + let mut msg = String::from( + "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + ); + for lint in missing_lints { + msg.push_str(&format!(" - {lint}\n")); + } + msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); + panic!("{msg}"); + } +} diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 6c188ff86497f..2c11e0222a286 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -31,9 +31,7 @@ pub trait Lint { fn id(&self) -> &'static str; fn severity(&self) -> Severity; fn description(&self) -> &'static str; - fn help(&self) -> Option<&'static str> { - None - } + fn help(&self) -> &'static str; } pub struct LintContext<'s> { @@ -48,19 +46,14 @@ impl<'s> LintContext<'s> { // Helper method to emit diagnostics easily from passes pub fn emit(&self, lint: &'static L, span: Span) { - let (desc, help) = match (self.desc, lint.help()) { - (true, Some(help)) => (lint.description(), help), - (true, None) => (lint.description(), ""), - (false, _) => ("", ""), - }; - + let desc = if self.desc { lint.description() } else { "" }; let diag: DiagBuilder<'_, ()> = self .sess .dcx .diag(lint.severity().into(), desc) .code(DiagId::new_str(lint.id())) .span(MultiSpan::from_span(span)) - .help(help); + .help(lint.help()); diag.emit(); } diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index f4031554b90ec..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -11,8 +11,7 @@ declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "hash using inline assembly to save gas", - "" + "hash using inline assembly to save gas" ); impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { diff --git a/crates/lint/src/sol/high/incorrect_shift.rs b/crates/lint/src/sol/high/incorrect_shift.rs index 6d038d86382cd..8d2326c5cc9f9 100644 --- a/crates/lint/src/sol/high/incorrect_shift.rs +++ b/crates/lint/src/sol/high/incorrect_shift.rs @@ -10,8 +10,7 @@ declare_forge_lint!( INCORRECT_SHIFT, Severity::High, "incorrect-shift", - "the order of args in a shift operation is incorrect", - "" + "the order of args in a shift operation is incorrect" ); impl<'ast> EarlyLintPass<'ast> for IncorrectShift { diff --git a/crates/lint/src/sol/info/mixed_case.rs b/crates/lint/src/sol/info/mixed_case.rs index 035c21ef592c1..5e839e9f313cf 100644 --- a/crates/lint/src/sol/info/mixed_case.rs +++ b/crates/lint/src/sol/info/mixed_case.rs @@ -10,8 +10,7 @@ declare_forge_lint!( MIXED_CASE_FUNCTION, Severity::Info, "mixed-case-function", - "function names should use mixedCase", - "https://docs.soliditylang.org/en/latest/style-guide.html#function-names" + "function names should use mixedCase" ); impl<'ast> EarlyLintPass<'ast> for MixedCaseFunction { diff --git a/crates/lint/src/sol/info/pascal_case.rs b/crates/lint/src/sol/info/pascal_case.rs index 435debd66ac3c..60cafbc8365a3 100644 --- a/crates/lint/src/sol/info/pascal_case.rs +++ b/crates/lint/src/sol/info/pascal_case.rs @@ -10,8 +10,7 @@ declare_forge_lint!( PASCAL_CASE_STRUCT, Severity::Info, "pascal-case-struct", - "structs should use PascalCase", - "https://docs.soliditylang.org/en/latest/style-guide.html#struct-names" + "structs should use PascalCase" ); impl<'ast> EarlyLintPass<'ast> for PascalCaseStruct { diff --git a/crates/lint/src/sol/info/screaming_snake_case.rs b/crates/lint/src/sol/info/screaming_snake_case.rs index 528638181b126..ccc978029b8a4 100644 --- a/crates/lint/src/sol/info/screaming_snake_case.rs +++ b/crates/lint/src/sol/info/screaming_snake_case.rs @@ -10,8 +10,7 @@ declare_forge_lint!( SCREAMING_SNAKE_CASE_CONSTANT, Severity::Info, "screaming-snake-case-const", - "constants should use SCREAMING_SNAKE_CASE", - "https://docs.soliditylang.org/en/latest/style-guide.html#constants" + "constants should use SCREAMING_SNAKE_CASE" ); declare_forge_lint!( diff --git a/crates/lint/src/sol/macros.rs b/crates/lint/src/sol/macros.rs index 093b96a7e4a70..357f742aa297c 100644 --- a/crates/lint/src/sol/macros.rs +++ b/crates/lint/src/sol/macros.rs @@ -7,16 +7,20 @@ /// - `$severity`: The `Severity` of the lint (e.g. `High`, `Med`, `Low`, `Info`, `Gas`). /// - `$str_id`: A unique identifier used to reference a specific lint during configuration. /// - `$desc`: A short description of the lint. -/// - `$help` (optional): Link to additional information about the lint or best practices. +/// +/// # Note +/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by +/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the +/// book, the existence of the lint rule's help section is validated with a unit test. #[macro_export] macro_rules! declare_forge_lint { - ($id:ident, $severity:expr, $str_id:expr, $desc:expr, $help:expr) => { + ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { // Declare the static `Lint` metadata pub static $id: SolLint = SolLint { id: $str_id, severity: $severity, description: $desc, - help: if $help.is_empty() { None } else { Some($help) }, + help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id), }; }; diff --git a/crates/lint/src/sol/med/div_mul.rs b/crates/lint/src/sol/med/div_mul.rs index b513468be6b83..b154971e55434 100644 --- a/crates/lint/src/sol/med/div_mul.rs +++ b/crates/lint/src/sol/med/div_mul.rs @@ -10,8 +10,7 @@ declare_forge_lint!( DIVIDE_BEFORE_MULTIPLY, Severity::Med, "divide-before-multiply", - "multiplication should occur before division to avoid loss of precision", - "" + "multiplication should occur before division to avoid loss of precision" ); impl<'ast> EarlyLintPass<'ast> for DivideBeforeMultiply { diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index 6bb2ffabef39a..463777d5b194a 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -159,7 +159,7 @@ pub enum SolLintError { pub struct SolLint { id: &'static str, description: &'static str, - help: Option<&'static str>, + help: &'static str, severity: Severity, } @@ -173,7 +173,7 @@ impl Lint for SolLint { fn description(&self) -> &'static str { self.description } - fn help(&self) -> Option<&'static str> { + fn help(&self) -> &'static str { self.help } } diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index 08d8ebe25d1c9..1c98f4b13d194 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -4,6 +4,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 3 | (1 / 2) * 3; | ----------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -11,6 +12,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 5 | ((1 / 2) * 3) * 4; | ----------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -18,6 +20,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 6 | ((1 * 2) / 3) * 4; | ----------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -25,6 +28,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 7 | (1 / 2 / 3) * 4; | --------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -32,6 +36,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 8 | (1 / (2 + 3)) * 4; | ----------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -39,4 +44,5 @@ warning[divide-before-multiply]: multiplication should occur before division to 15 | 1 / ((2 / 3) * 3); | ----------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply diff --git a/crates/lint/testdata/IncorrectShift.stderr b/crates/lint/testdata/IncorrectShift.stderr index 16fbda627cd9b..46276262da4bd 100644 --- a/crates/lint/testdata/IncorrectShift.stderr +++ b/crates/lint/testdata/IncorrectShift.stderr @@ -4,6 +4,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 21 | result = 2 << stateValue; | --------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect --> ROOT/testdata/IncorrectShift.sol:LL:CC @@ -11,6 +12,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 22 | result = 8 >> localValue; | --------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect --> ROOT/testdata/IncorrectShift.sol:LL:CC @@ -18,6 +20,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 23 | result = 16 << (stateValue + 1); | ---------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect --> ROOT/testdata/IncorrectShift.sol:LL:CC @@ -25,6 +28,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 24 | result = 32 >> getAmount(); | ----------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect --> ROOT/testdata/IncorrectShift.sol:LL:CC @@ -32,4 +36,5 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 25 | ... result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); | ------------------------------------------------------------ | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift diff --git a/crates/lint/testdata/Keccak256.stderr b/crates/lint/testdata/Keccak256.stderr index e589bec00eb77..8011afa89a112 100644 --- a/crates/lint/testdata/Keccak256.stderr +++ b/crates/lint/testdata/Keccak256.stderr @@ -4,6 +4,7 @@ note[asm-keccak256]: hash using inline assembly to save gas 3 | keccak256(abi.encodePacked(a, b)); | --------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 note[asm-keccak256]: hash using inline assembly to save gas --> ROOT/testdata/Keccak256.sol:LL:CC @@ -11,4 +12,5 @@ note[asm-keccak256]: hash using inline assembly to save gas 7 | keccak256(abi.encodePacked(a, b)); | --------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 diff --git a/crates/lint/testdata/MixedCase.stderr b/crates/lint/testdata/MixedCase.stderr index 1245d53caf5cc..0976dced8716c 100644 --- a/crates/lint/testdata/MixedCase.stderr +++ b/crates/lint/testdata/MixedCase.stderr @@ -4,6 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 9 | uint256 Variablemixedcase; | ----------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -11,6 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 10 | uint256 VARIABLE_MIXED_CASE; | ------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -18,6 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 11 | uint256 VariableMixedCase; | ----------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -25,6 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 17 | uint256 testVAL; | ------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -32,6 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 18 | uint256 TestVal; | ------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -39,6 +44,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 19 | uint256 TESTVAL; | ------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -46,7 +52,7 @@ note[mixed-case-function]: function names should use mixedCase 26 | function Functionmixedcase() public {} | ----------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -54,7 +60,7 @@ note[mixed-case-function]: function names should use mixedCase 27 | function FUNCTION_MIXED_CASE() public {} | ------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -62,7 +68,7 @@ note[mixed-case-function]: function names should use mixedCase 28 | function FunctionMixedCase() public {} | ----------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -70,7 +76,7 @@ note[mixed-case-function]: function names should use mixedCase 29 | function function_mixed_case() public {} | ------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -78,7 +84,7 @@ note[mixed-case-function]: function names should use mixedCase 53 | function invariantBalance_MixedCase_Enabled() public {} | ---------------------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function note[mixed-case-function]: function names should use mixedCase --> ROOT/testdata/MixedCase.sol:LL:CC @@ -86,5 +92,5 @@ note[mixed-case-function]: function names should use mixedCase 54 | function invariantbalance_mixedcase_enabled() public {} | ---------------------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#function-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function diff --git a/crates/lint/testdata/ScreamingSnakeCase.stderr b/crates/lint/testdata/ScreamingSnakeCase.stderr index 96c2cb34405b3..b511f09d254f3 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.stderr +++ b/crates/lint/testdata/ScreamingSnakeCase.stderr @@ -4,7 +4,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE 9 | uint256 constant screamingSnakeCase = 0; | ------------------ | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#constants + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -12,7 +12,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE 10 | uint256 constant screaming_snake_case = 0; | -------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#constants + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -20,7 +20,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE 11 | uint256 constant ScreamingSnakeCase = 0; | ------------------ | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#constants + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -28,7 +28,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE 12 | uint256 constant SCREAMING_snake_case = 0; | -------------------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#constants + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -36,6 +36,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE 19 | uint256 immutable screamingSnakeCase0 = 0; | ------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -43,6 +44,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE 20 | uint256 immutable screaming_snake_case0 = 0; | --------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -50,6 +52,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE 21 | uint256 immutable ScreamingSnakeCase0 = 0; | ------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -57,4 +60,5 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE 22 | uint256 immutable SCREAMING_snake_case_0 = 0; | ---------------------- | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable diff --git a/crates/lint/testdata/StructPascalCase.stderr b/crates/lint/testdata/StructPascalCase.stderr index 2ef869c7ccc21..7858c18cbf33d 100644 --- a/crates/lint/testdata/StructPascalCase.stderr +++ b/crates/lint/testdata/StructPascalCase.stderr @@ -4,7 +4,7 @@ note[pascal-case-struct]: structs should use PascalCase 13 | struct _PascalCase { | ----------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct note[pascal-case-struct]: structs should use PascalCase --> ROOT/testdata/StructPascalCase.sol:LL:CC @@ -12,7 +12,7 @@ note[pascal-case-struct]: structs should use PascalCase 17 | struct pascalCase { | ---------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct note[pascal-case-struct]: structs should use PascalCase --> ROOT/testdata/StructPascalCase.sol:LL:CC @@ -20,7 +20,7 @@ note[pascal-case-struct]: structs should use PascalCase 21 | struct pascalcase { | ---------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct note[pascal-case-struct]: structs should use PascalCase --> ROOT/testdata/StructPascalCase.sol:LL:CC @@ -28,7 +28,7 @@ note[pascal-case-struct]: structs should use PascalCase 25 | struct pascal_case { | ----------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct note[pascal-case-struct]: structs should use PascalCase --> ROOT/testdata/StructPascalCase.sol:LL:CC @@ -36,7 +36,7 @@ note[pascal-case-struct]: structs should use PascalCase 29 | struct PASCAL_CASE { | ----------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct note[pascal-case-struct]: structs should use PascalCase --> ROOT/testdata/StructPascalCase.sol:LL:CC @@ -44,5 +44,5 @@ note[pascal-case-struct]: structs should use PascalCase 33 | struct PASCALCASE { | ---------- | - = help: https://docs.soliditylang.org/en/latest/style-guide.html#struct-names + = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct From ca03f8ffe74d56f559ad861e2eea911f19d5b906 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Tue, 27 May 2025 00:33:22 +0200 Subject: [PATCH 047/391] feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. --- crates/cast/src/cmd/mktx.rs | 22 +++++++++++++++++++--- crates/cast/tests/cli/main.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index af06d556c23ff..ebfb5afeadd47 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -2,6 +2,7 @@ use crate::tx::{self, CastTxBuilder}; use alloy_ens::NameOrAddress; use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; use alloy_primitives::hex; +use alloy_provider::Provider; use alloy_signer::Signer; use clap::Parser; use eyre::{OptionExt, Result}; @@ -50,6 +51,10 @@ pub struct MakeTxArgs { /// Relaxes the wallet requirement. #[arg(long, requires = "from")] raw_unsigned: bool, + + /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender + #[arg(long, requires = "from", conflicts_with = "raw_unsigned")] + ethsign: bool, } #[derive(Debug, Parser)] @@ -70,7 +75,7 @@ pub enum MakeTxSubcommands { impl MakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned } = self; + let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -91,7 +96,7 @@ impl MakeTxArgs { let provider = get_provider(&config)?; - let tx_builder = CastTxBuilder::new(provider, tx, &config) + let tx_builder = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? @@ -108,7 +113,18 @@ impl MakeTxArgs { return Ok(()); } - // Retrieve the signer, and bail if it can't be constructed. + if ethsign { + // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has + // unlocked accounts. + let (tx, _) = tx_builder.build(config.sender).await?; + let signed_tx = provider.sign_transaction(tx).await?; + + sh_println!("{signed_tx}")?; + return Ok(()); + } + + // Default to using the local signer. + // Get the signer from the wallet, and fail if it can't be constructed. let signer = eth.wallet.signer().await?; let from = signer.address(); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 4735a8ce1abbc..1a0c0bf958cf8 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1296,6 +1296,37 @@ casttest!(mktx_raw_unsigned, |_prj, cmd| { ]]); }); +casttest!(mktx_ethsign, async |_prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + cmd.args([ + "mktx", + "--from", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--chain", + "31337", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + "--ethsign", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[ + r#" +0x02f86d827a6980843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0b8eeb1ded87b085859c510c5692bed231e3ee8b068ccf71142bbf28da0e95987a07813b676a248ae8055f28495021d78dee6695479d339a6ad9d260d9eaf20674c + +"# + ]]); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); From fbb793c5383503b7aa0c037e510690ae1d17f5e2 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Tue, 27 May 2025 16:28:39 +0200 Subject: [PATCH 048/391] chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure --- crates/wallets/src/wallet.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 29ce54438586d..8e3a4dafb332a 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -128,12 +128,14 @@ impl WalletOpts { eyre::bail!( "\ Error accessing local wallet. Did you set a private key, mnemonic or keystore? -Run `cast send --help` or `forge create --help` and use the corresponding CLI +Run the command with --help flag for more information or use the corresponding CLI flag to set your key via: --private-key, --mnemonic-path, --aws, --gcp, --interactive, --trezor or --ledger. -Alternatively, if you're using a local node with unlocked accounts, -use the --unlocked flag and either set the `ETH_FROM` environment variable to the address -of the unlocked account you want to use, or provide the --from flag with the address directly." +Alternatively, when using the `cast send` or `cast mktx` commands with a local node +or RPC that has unlocked accounts, the --unlocked or --ethsign flags can be used, +respectively. The sender address can be specified by setting the `ETH_FROM` environment +variable to the desired unlocked account address, or by providing the address directly +using the --from flag." ) }; From fbdec00058201cf794b5e148de8d6807a033af21 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 27 May 2025 20:19:41 +0530 Subject: [PATCH 049/391] chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- Cargo.lock | 12 +- Cargo.toml | 2 + crates/anvil/Cargo.toml | 2 + crates/anvil/src/cmd.rs | 7 +- crates/anvil/src/config.rs | 15 +- crates/anvil/src/hardfork.rs | 274 ++++++++------------------- crates/anvil/src/lib.rs | 3 +- crates/anvil/tests/it/anvil.rs | 3 +- crates/anvil/tests/it/anvil_api.rs | 3 +- crates/anvil/tests/it/eip4844.rs | 3 +- crates/anvil/tests/it/eip7702.rs | 3 +- crates/anvil/tests/it/otterscan.rs | 3 +- crates/anvil/tests/it/traces.rs | 3 +- crates/anvil/tests/it/transaction.rs | 3 +- crates/cast/Cargo.toml | 1 + crates/cast/tests/cli/main.rs | 3 +- crates/forge/Cargo.toml | 1 + crates/forge/tests/cli/script.rs | 3 +- 18 files changed, 126 insertions(+), 218 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c05a8b99f5d05..1fa7e6a150282 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,9 +253,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6b8067561eb8f884b215ace4c962313c5467e47bde6b457c8c51e268fb5d99" +checksum = "fbff8445282ec080c2673692062bd4930d7a0d6bda257caf138cfc650c503000" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08043c9284e597f9b5cf741cc6d906fdb26c195a01d88423c84c00ffda835713" +checksum = "9ddfbb5cc9f614efa5d56e0d7226214bb67b29271d44b6ddfcbbe25eb0ff898b" dependencies = [ "alloy-hardforks", "auto_impl", @@ -1023,8 +1023,10 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-genesis", + "alloy-hardforks", "alloy-network", "alloy-op-evm", + "alloy-op-hardforks", "alloy-primitives", "alloy-provider", "alloy-pubsub", @@ -2351,6 +2353,7 @@ dependencies = [ "alloy-contract", "alloy-dyn-abi", "alloy-ens", + "alloy-hardforks", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -3778,6 +3781,7 @@ version = "1.2.1" dependencies = [ "alloy-chains", "alloy-dyn-abi", + "alloy-hardforks", "alloy-json-abi", "alloy-network", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 8686f046c2c14..3baefd5dca836 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,6 +230,8 @@ alloy-transport = { version = "1.0.7", default-features = false } alloy-transport-http = { version = "1.0.7", default-features = false } alloy-transport-ipc = { version = "1.0.7", default-features = false } alloy-transport-ws = { version = "1.0.7", default-features = false } +alloy-hardforks = { version = "0.2.6", default-features = false } +alloy-op-hardforks = { version = "0.2.6", default-features = false } ## alloy-core alloy-dyn-abi = "1.0" diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index e45c95e49f85e..23ff53b271895 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -55,6 +55,8 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true +alloy-hardforks.workspace = true +alloy-op-hardforks.workspace = true op-alloy-consensus = { workspace = true, features = ["serde"] } # revm diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index d4b91e14f20fd..c8223a76ed959 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,9 +1,10 @@ use crate::{ config::{ForkChoice, DEFAULT_MNEMONIC}, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - AccountGenerator, EthereumHardfork, NodeConfig, OptimismHardfork, CHAIN_ID, + AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID, }; use alloy_genesis::Genesis; +use alloy_op_hardforks::OpHardfork; use alloy_primitives::{utils::Unit, B256, U256}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; @@ -218,7 +219,7 @@ impl NodeArgs { let hardfork = match &self.hardfork { Some(hf) => { if self.evm.optimism { - Some(OptimismHardfork::from_str(hf)?.into()) + Some(OpHardfork::from_str(hf)?.into()) } else { Some(EthereumHardfork::from_str(hf)?.into()) } @@ -836,7 +837,7 @@ mod tests { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); let config = args.into_node_config().unwrap(); - assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into())); + assert_eq!(config.hardfork, Some(OpHardfork::Regolith.into())); } #[test] diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 722408c72a9c4..1cde743c3e952 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -11,13 +11,14 @@ use crate::{ fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::{PoolTransaction, TransactionOrder}, }, - hardfork::ChainHardfork, + hardfork::{ethereum_hardfork_from_block_tag, spec_id_from_ethereum_hardfork, ChainHardfork}, mem::{self, in_memory_db::MemDb}, - EthereumHardfork, FeeManager, OptimismHardfork, PrecompileFactory, + EthereumHardfork, FeeManager, PrecompileFactory, }; use alloy_consensus::BlockHeader; use alloy_genesis::Genesis; use alloy_network::{AnyNetwork, TransactionResponse}; +use alloy_op_hardforks::OpHardfork; use alloy_primitives::{hex, map::HashMap, utils::Unit, BlockNumber, TxHash, U256}; use alloy_provider::Provider; use alloy_rpc_types::{Block, BlockNumberOrTag}; @@ -530,9 +531,9 @@ impl NodeConfig { return hardfork; } if self.enable_optimism { - return OptimismHardfork::default().into(); + return OpHardfork::Isthmus.into(); } - EthereumHardfork::default().into() + EthereumHardfork::Cancun.into() } /// Sets a custom code size limit @@ -1186,8 +1187,10 @@ impl NodeConfig { let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; if alloy_chains::NamedChain::Mainnet == chain_id { - let hardfork: EthereumHardfork = fork_block_number.into(); - env.evm_env.cfg_env.spec = hardfork.into(); + let hardfork: EthereumHardfork = + ethereum_hardfork_from_block_tag(fork_block_number); + + env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); self.hardfork = Some(ChainHardfork::Ethereum(hardfork)); } Some(U256::from(chain_id)) diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index 34fe97fa1de41..f5e063aafc625 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -1,14 +1,14 @@ -use std::str::FromStr; - +use alloy_hardforks::EthereumHardfork; +use alloy_op_hardforks::OpHardfork::{self}; use alloy_rpc_types::BlockNumberOrTag; -use eyre::bail; + use op_revm::OpSpecId; use revm::primitives::hardfork::SpecId; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ChainHardfork { Ethereum(EthereumHardfork), - Optimism(OptimismHardfork), + Optimism(OpHardfork), } impl From for ChainHardfork { @@ -17,8 +17,8 @@ impl From for ChainHardfork { } } -impl From for ChainHardfork { - fn from(value: OptimismHardfork) -> Self { +impl From for ChainHardfork { + fn from(value: OpHardfork) -> Self { Self::Optimism(value) } } @@ -26,213 +26,99 @@ impl From for ChainHardfork { impl From for SpecId { fn from(fork: ChainHardfork) -> Self { match fork { - ChainHardfork::Ethereum(hardfork) => hardfork.into(), - ChainHardfork::Optimism(hardfork) => hardfork.into_eth_spec(), - } - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum EthereumHardfork { - Frontier, - Homestead, - Dao, - Tangerine, - SpuriousDragon, - Byzantium, - Constantinople, - Petersburg, - Istanbul, - Muirglacier, - Berlin, - London, - ArrowGlacier, - GrayGlacier, - Paris, - Shanghai, - Cancun, - Prague, - #[default] - Latest, -} - -impl EthereumHardfork { - /// Get the first block number of the hardfork. - pub fn fork_block(&self) -> u64 { - match *self { - Self::Frontier => 0, - Self::Homestead => 1150000, - Self::Dao => 1920000, - Self::Tangerine => 2463000, - Self::SpuriousDragon => 2675000, - Self::Byzantium => 4370000, - Self::Constantinople | Self::Petersburg => 7280000, - Self::Istanbul => 9069000, - Self::Muirglacier => 9200000, - Self::Berlin => 12244000, - Self::London => 12965000, - Self::ArrowGlacier => 13773000, - Self::GrayGlacier => 15050000, - Self::Paris => 15537394, - Self::Shanghai => 17034870, - Self::Cancun | Self::Latest => 19426587, - Self::Prague => 22431084, + ChainHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + ChainHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), } } } -impl FromStr for EthereumHardfork { - type Err = eyre::Report; - - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - let hardfork = match s.as_str() { - "frontier" | "1" => Self::Frontier, - "homestead" | "2" => Self::Homestead, - "dao" | "3" => Self::Dao, - "tangerine" | "4" => Self::Tangerine, - "spuriousdragon" | "5" => Self::SpuriousDragon, - "byzantium" | "6" => Self::Byzantium, - "constantinople" | "7" => Self::Constantinople, - "petersburg" | "8" => Self::Petersburg, - "istanbul" | "9" => Self::Istanbul, - "muirglacier" | "10" => Self::Muirglacier, - "berlin" | "11" => Self::Berlin, - "london" | "12" => Self::London, - "arrowglacier" | "13" => Self::ArrowGlacier, - "grayglacier" | "14" => Self::GrayGlacier, - "paris" | "merge" | "15" => Self::Paris, - "shanghai" | "16" => Self::Shanghai, - "cancun" | "17" => Self::Cancun, - "prague" | "18" => Self::Prague, - "latest" => Self::Latest, - _ => bail!("Unknown hardfork {s}"), - }; - Ok(hardfork) +/// Map an EthereumHardfork enum into its corresponding SpecId. +pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { + match hardfork { + EthereumHardfork::Frontier => SpecId::FRONTIER, + EthereumHardfork::Homestead => SpecId::HOMESTEAD, + EthereumHardfork::Dao => SpecId::DAO_FORK, + EthereumHardfork::Tangerine => SpecId::TANGERINE, + EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, + EthereumHardfork::Byzantium => SpecId::BYZANTIUM, + EthereumHardfork::Constantinople => SpecId::CONSTANTINOPLE, + EthereumHardfork::Petersburg => SpecId::PETERSBURG, + EthereumHardfork::Istanbul => SpecId::ISTANBUL, + EthereumHardfork::MuirGlacier => SpecId::MUIR_GLACIER, + EthereumHardfork::Berlin => SpecId::BERLIN, + EthereumHardfork::London => SpecId::LONDON, + EthereumHardfork::ArrowGlacier => SpecId::ARROW_GLACIER, + EthereumHardfork::GrayGlacier => SpecId::GRAY_GLACIER, + EthereumHardfork::Paris => SpecId::MERGE, + EthereumHardfork::Shanghai => SpecId::SHANGHAI, + EthereumHardfork::Cancun => SpecId::CANCUN, + EthereumHardfork::Prague => SpecId::PRAGUE, + EthereumHardfork::Osaka => SpecId::OSAKA, } } -impl From for SpecId { - fn from(fork: EthereumHardfork) -> Self { - match fork { - EthereumHardfork::Frontier => Self::FRONTIER, - EthereumHardfork::Homestead => Self::HOMESTEAD, - EthereumHardfork::Dao => Self::HOMESTEAD, - EthereumHardfork::Tangerine => Self::TANGERINE, - EthereumHardfork::SpuriousDragon => Self::SPURIOUS_DRAGON, - EthereumHardfork::Byzantium => Self::BYZANTIUM, - EthereumHardfork::Constantinople => Self::CONSTANTINOPLE, - EthereumHardfork::Petersburg => Self::PETERSBURG, - EthereumHardfork::Istanbul => Self::ISTANBUL, - EthereumHardfork::Muirglacier => Self::MUIR_GLACIER, - EthereumHardfork::Berlin => Self::BERLIN, - EthereumHardfork::London => Self::LONDON, - EthereumHardfork::ArrowGlacier => Self::LONDON, - EthereumHardfork::GrayGlacier => Self::GRAY_GLACIER, - EthereumHardfork::Paris => Self::MERGE, - EthereumHardfork::Shanghai => Self::SHANGHAI, - EthereumHardfork::Cancun | EthereumHardfork::Latest => Self::CANCUN, - EthereumHardfork::Prague => Self::PRAGUE, - } +/// Map an OptimismHardfork enum into its corresponding OpSpecId. +pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { + match hardfork { + OpHardfork::Bedrock => OpSpecId::BEDROCK, + OpHardfork::Regolith => OpSpecId::REGOLITH, + OpHardfork::Canyon => OpSpecId::CANYON, + OpHardfork::Ecotone => OpSpecId::ECOTONE, + OpHardfork::Fjord => OpSpecId::FJORD, + OpHardfork::Granite => OpSpecId::GRANITE, + OpHardfork::Holocene => OpSpecId::HOLOCENE, + OpHardfork::Isthmus => OpSpecId::ISTHMUS, + OpHardfork::Interop => OpSpecId::INTEROP, } } -impl> From for EthereumHardfork { - fn from(block: T) -> Self { - let num = match block.into() { - BlockNumberOrTag::Earliest => 0, - BlockNumberOrTag::Number(num) => num, - _ => u64::MAX, - }; - - match num { - _i if num < 1_150_000 => Self::Frontier, - _i if num < 1_920_000 => Self::Dao, - _i if num < 2_463_000 => Self::Homestead, - _i if num < 2_675_000 => Self::Tangerine, - _i if num < 4_370_000 => Self::SpuriousDragon, - _i if num < 7_280_000 => Self::Byzantium, - _i if num < 9_069_000 => Self::Constantinople, - _i if num < 9_200_000 => Self::Istanbul, - _i if num < 12_244_000 => Self::Muirglacier, - _i if num < 12_965_000 => Self::Berlin, - _i if num < 13_773_000 => Self::London, - _i if num < 15_050_000 => Self::ArrowGlacier, - _i if num < 17_034_870 => Self::Paris, - _i if num < 19_426_587 => Self::Shanghai, - _ => Self::Latest, - } - } -} +/// Convert a `BlockNumberOrTag` into an `EthereumHardfork`. +pub fn ethereum_hardfork_from_block_tag(block: impl Into) -> EthereumHardfork { + let num = match block.into() { + BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Number(num) => num, + _ => u64::MAX, + }; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum OptimismHardfork { - Bedrock, - Regolith, - Canyon, - Ecotone, - Fjord, - Granite, - Holocene, - #[default] - Isthmus, + EthereumHardfork::from_mainnet_block_number(num) } -impl OptimismHardfork { - pub fn into_eth_spec(self) -> SpecId { - let op_spec: OpSpecId = self.into(); - op_spec.into_eth_spec() - } -} +#[cfg(test)] +mod tests { + use super::*; + use alloy_hardforks::ethereum::mainnet::*; + #[allow(unused_imports)] + use alloy_rpc_types::BlockNumberOrTag; -impl FromStr for OptimismHardfork { - type Err = eyre::Report; + #[test] + fn test_ethereum_spec_id_mapping() { + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD); - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - let hardfork = match s.as_str() { - "bedrock" => Self::Bedrock, - "regolith" => Self::Regolith, - "canyon" => Self::Canyon, - "ecotone" => Self::Ecotone, - "fjord" => Self::Fjord, - "granite" => Self::Granite, - "holocene" => Self::Holocene, - "isthmus" => Self::Isthmus, - _ => bail!("Unknown hardfork {s}"), - }; - Ok(hardfork) + // Test latest hardforks + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE); } -} - -impl From for OpSpecId { - fn from(fork: OptimismHardfork) -> Self { - match fork { - OptimismHardfork::Bedrock => Self::BEDROCK, - OptimismHardfork::Regolith => Self::REGOLITH, - OptimismHardfork::Canyon => Self::CANYON, - OptimismHardfork::Ecotone => Self::ECOTONE, - OptimismHardfork::Fjord => Self::FJORD, - OptimismHardfork::Granite => Self::GRANITE, - OptimismHardfork::Holocene => Self::HOLOCENE, - OptimismHardfork::Isthmus => Self::ISTHMUS, - } - } -} - -#[cfg(test)] -mod tests { - use crate::EthereumHardfork; #[test] - fn test_hardfork_blocks() { - let hf: EthereumHardfork = 12_965_000u64.into(); - assert_eq!(hf, EthereumHardfork::London); + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - let hf: EthereumHardfork = 4370000u64.into(); - assert_eq!(hf, EthereumHardfork::Byzantium); + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } - let hf: EthereumHardfork = 12244000u64.into(); - assert_eq!(hf, EthereumHardfork::Berlin); + #[test] + fn test_hardfork_from_block_tag_numbers() { + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1), + EthereumHardfork::Frontier + ); + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1), + EthereumHardfork::London + ); } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 77e072e0aa1f2..5ee4fead559b2 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -47,8 +47,7 @@ pub use config::{ }; mod hardfork; -pub use hardfork::{EthereumHardfork, OptimismHardfork}; - +pub use alloy_hardforks::EthereumHardfork; /// ethereum related implementations pub mod eth; /// Evm related abstractions diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 329caea844b45..721aa404b36e1 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -2,9 +2,10 @@ use alloy_consensus::EMPTY_ROOT_HASH; use alloy_eips::BlockNumberOrTag; +use alloy_hardforks::EthereumHardfork; use alloy_primitives::Address; use alloy_provider::Provider; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index ce337714587b9..d4af986814ed6 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -6,6 +6,7 @@ use crate::{ utils::http_provider_with_signer, }; use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; use alloy_primitives::{address, fixed_bytes, utils::Unit, Address, Bytes, TxKind, U256}; use alloy_provider::{ext::TxPoolApi, Provider}; @@ -21,7 +22,7 @@ use anvil::{ api::CLIENT_VERSION, backend::mem::{EXECUTOR, P256_DELEGATION_CONTRACT, P256_DELEGATION_RUNTIME_CODE}, }, - spawn, EthereumHardfork, NodeConfig, + spawn, NodeConfig, }; use anvil_core::{ eth::{ diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 633c8a01bdef0..2d713c3d955e3 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -4,12 +4,13 @@ use alloy_eips::{ eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK_DENCUN}, Typed2718, }; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::{b256, Address, U256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction() { diff --git a/crates/anvil/tests/it/eip7702.rs b/crates/anvil/tests/it/eip7702.rs index c09412c85e306..9424360119e4e 100644 --- a/crates/anvil/tests/it/eip7702.rs +++ b/crates/anvil/tests/it/eip7702.rs @@ -1,12 +1,13 @@ use crate::utils::http_provider; use alloy_consensus::{transaction::TxEip7702, SignableTransaction}; +use alloy_hardforks::EthereumHardfork; use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; use alloy_primitives::{bytes, U256}; use alloy_provider::{PendingTransactionConfig, Provider}; use alloy_rpc_types::{Authorization, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_signer::SignerSync; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip7702_tx() { diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index 3809e3183e1a7..ffd70d1349ddb 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,6 +1,7 @@ //! Tests for otterscan endpoints. use crate::abi::Multicall; +use alloy_hardforks::EthereumHardfork; use alloy_network::TransactionResponse; use alloy_primitives::{address, Address, Bytes, U256}; use alloy_provider::Provider; @@ -10,7 +11,7 @@ use alloy_rpc_types::{ }; use alloy_serde::WithOtherFields; use alloy_sol_types::{sol, SolCall, SolError, SolValue}; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 849956cabe5dd..2e073ca9c8735 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -4,6 +4,7 @@ use crate::{ utils::http_provider_with_signer, }; use alloy_eips::BlockId; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{ hex::{self, FromHex}, @@ -27,7 +28,7 @@ use alloy_rpc_types::{ }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 953c30515d3ef..7cb681af85a14 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -2,6 +2,7 @@ use crate::{ abi::{Greeter, Multicall, SimpleStorage}, utils::{connect_pubsub, http_provider_with_signer}, }; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; use alloy_primitives::{address, hex, map::B256HashSet, Address, Bytes, FixedBytes, U256}; use alloy_provider::{Provider, WsConnect}; @@ -12,7 +13,7 @@ use alloy_rpc_types::{ }; use alloy_serde::WithOtherFields; use alloy_sol_types::SolValue; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; use eyre::Ok; use futures::{future::join_all, FutureExt, StreamExt}; use std::{str::FromStr, time::Duration}; diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 26f001132baff..2dda3ba341872 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -53,6 +53,7 @@ alloy-signer.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true alloy-ens = { workspace = true, features = ["provider"] } +alloy-hardforks.workspace = true op-alloy-flz.workspace = true op-alloy-consensus = { workspace = true, features = ["alloy-compat"] } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 1a0c0bf958cf8..eca950b2f89d4 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,11 +1,12 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{address, b256, Bytes, B256}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::{BlockNumberOrTag, Index, TransactionRequest}; -use anvil::{EthereumHardfork, NodeConfig}; +use anvil::NodeConfig; use foundry_test_utils::{ rpc::{ next_etherscan_api_key, next_http_archive_rpc_url, next_http_rpc_endpoint, diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c3251ed76c9eb..d823ca3285140 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -63,6 +63,7 @@ alloy-rpc-types.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true revm.workspace = true diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index d72c21fce60ff..1d6f415aef1d4 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,8 +1,9 @@ //! Contains various tests related to `forge script`. use crate::constants::TEMPLATE_CONTRACT; +use alloy_hardforks::EthereumHardfork; use alloy_primitives::{address, hex, Address, Bytes}; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ rpc::{self, next_http_archive_rpc_url}, From 07dee8164299a22f4826242b35e81387b4a55005 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Wed, 28 May 2025 13:03:08 +0100 Subject: [PATCH 050/391] fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit --- crates/anvil/src/cmd.rs | 2 +- crates/anvil/src/config.rs | 6 +++--- crates/anvil/tests/it/anvil_api.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index c8223a76ed959..4ca9624d57f6e 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -79,7 +79,7 @@ pub struct NodeArgs { /// The EVM hardfork to use. /// - /// Choose the hardfork by name, e.g. `cancun`, `shanghai`, `paris`, `london`, etc... + /// Choose the hardfork by name, e.g. `prague`, `cancun`, `shanghai`, `paris`, `london`, etc... /// [default: latest] #[arg(long)] pub hardfork: Option, diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 1cde743c3e952..d86515f9f8fba 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -525,15 +525,15 @@ impl NodeConfig { /// Returns the hardfork to use pub fn get_hardfork(&self) -> ChainHardfork { if self.odyssey { - return ChainHardfork::Ethereum(EthereumHardfork::Prague); + return ChainHardfork::Ethereum(EthereumHardfork::default()); } if let Some(hardfork) = self.hardfork { return hardfork; } if self.enable_optimism { - return OpHardfork::Isthmus.into(); + return OpHardfork::default().into(); } - EthereumHardfork::Cancun.into() + EthereumHardfork::default().into() } /// Sets a custom code size limit diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index d4af986814ed6..5b4bcd78d2059 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -449,7 +449,7 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); - let hard_fork: &str = SpecId::CANCUN.into(); + let hard_fork: &str = SpecId::PRAGUE.into(); let expected_node_info = NodeInfo { current_block_number: 0_u64, From 7837b82a765a843bdfb97e1e7aea939de6937f9b Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 6 Aug 2025 11:28:25 -0700 Subject: [PATCH 051/391] deprecate forge config --basic --- crates/config/src/lib.rs | 132 --------------------------------- crates/forge/src/cmd/config.rs | 13 +--- crates/forge/tests/cli/cmd.rs | 10 +-- 3 files changed, 6 insertions(+), 149 deletions(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 2aa4cc7b61a41..b69d3116f9dfc 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1700,24 +1700,6 @@ impl Config { } } - /// Extracts a basic subset of the config, used for initialisations. - /// - /// # Example - /// - /// ```rust - /// use foundry_config::Config; - /// let my_config = Config::with_root(".").into_basic(); - /// ``` - pub fn into_basic(self) -> BasicConfig { - BasicConfig { - profile: self.profile, - src: self.src, - out: self.out, - libs: self.libs, - remappings: self.remappings, - } - } - /// Updates the `foundry.toml` file for the given `root` based on the provided closure. /// /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See @@ -2523,49 +2505,6 @@ impl> From for SolcReq { } } -/// A subset of the foundry `Config` -/// used to initialize a `foundry.toml` file -/// -/// # Example -/// -/// ```rust -/// use foundry_config::{BasicConfig, Config}; -/// use serde::Deserialize; -/// -/// let my_config = Config::figment().extract::(); -/// ``` -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct BasicConfig { - /// the profile tag: `[profile.default]` - #[serde(skip)] - pub profile: Profile, - /// path of the source contracts dir, like `src` or `contracts` - pub src: PathBuf, - /// path to where artifacts shut be written to - pub out: PathBuf, - /// all library folders to include, `lib`, `node_modules` - pub libs: Vec, - /// `Remappings` to use for this repo - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub remappings: Vec, -} - -impl BasicConfig { - /// Serialize the config as a String of TOML. - /// - /// This serializes to a table with the name of the profile - pub fn to_string_pretty(&self) -> Result { - let s = toml::to_string_pretty(self)?; - Ok(format!( - "\ -[profile.{}] -{s} -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", - self.profile - )) - } -} - pub(crate) mod from_str_lowercase { use serde::{Deserialize, Deserializer, Serializer}; use std::str::FromStr; @@ -4021,52 +3960,6 @@ mod tests { }); } - #[test] - fn test_extract_basic() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [profile.default] - src = "mysrc" - out = "myout" - verbosity = 3 - evm_version = 'berlin' - - [profile.other] - src = "other-src" - "#, - )?; - let loaded = Config::load().unwrap(); - assert_eq!(loaded.evm_version, EvmVersion::Berlin); - let base = loaded.into_basic(); - let default = Config::default(); - assert_eq!( - base, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "mysrc".into(), - out: "myout".into(), - libs: default.libs.clone(), - remappings: default.remappings.clone(), - } - ); - jail.set_env("FOUNDRY_PROFILE", r"other"); - let base = Config::figment().extract::().unwrap(); - assert_eq!( - base, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "other-src".into(), - out: "myout".into(), - libs: default.libs.clone(), - remappings: default.remappings, - } - ); - Ok(()) - }); - } - #[test] #[should_panic] fn test_parse_invalid_fuzz_weight() { @@ -4622,31 +4515,6 @@ mod tests { }); } - #[test] - fn test_parse_with_profile() { - let foundry_str = r" - [profile.default] - src = 'src' - out = 'out' - libs = ['lib'] - - # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options - "; - assert_eq!( - parse_with_profile::(foundry_str).unwrap().unwrap(), - ( - Config::DEFAULT_PROFILE, - BasicConfig { - profile: Config::DEFAULT_PROFILE, - src: "src".into(), - out: "out".into(), - libs: vec!["lib".into()], - remappings: vec![] - } - ) - ); - } - #[test] fn test_implicit_profile_loads() { figment::Jail::expect_with(|jail| { diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index c337e1fd4facc..d006c43525b2d 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -10,10 +10,6 @@ foundry_config::impl_figment_convert!(ConfigArgs, build, evm); /// CLI arguments for `forge config`. #[derive(Clone, Debug, Parser)] pub struct ConfigArgs { - /// Print only a basic set of the currently set config values. - #[arg(long)] - basic: bool, - /// Attempt to fix any configuration warnings. #[arg(long)] fix: bool, @@ -41,14 +37,7 @@ impl ConfigArgs { // we explicitly normalize the version, so mimic the behavior when invoking solc .normalized_evm_version(); - let s = if self.basic { - let config = config.into_basic(); - if shell::is_json() { - serde_json::to_string_pretty(&config)? - } else { - config.to_string_pretty()? - } - } else if shell::is_json() { + if shell::is_json() { serde_json::to_string_pretty(&config)? } else { config.to_string_pretty()? diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 0be5e77956fc5..4147839902a33 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -3,7 +3,7 @@ use crate::constants::*; use foundry_compilers::artifacts::{ConfigurableContractArtifact, Metadata, remappings::Remapping}; use foundry_config::{ - BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, parse_with_profile, + Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, parse_with_profile, }; use foundry_test_utils::{ foundry_compilers::PathStyle, @@ -278,7 +278,7 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); let s = read_string(&foundry_toml); - let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; + let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; }); // Checks that a forge project fails to initialise if dir is already git repo and dirty @@ -638,7 +638,7 @@ Compiler run successful! "#]]); let s = read_string(&foundry_toml); - let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; + let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; }); // Checks that quiet mode does not print anything for clone @@ -686,7 +686,7 @@ Compiler run successful! "#]]); let s = read_string(&foundry_toml); - let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; + let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; }); // checks that clone works with --keep-directory-structure @@ -725,7 +725,7 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { } let s = read_string(&foundry_toml); - let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; + let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; }); // checks that clone works with raw src containing `node_modules` From 9e084ce8789a5f591ec8ce8168f405bfb8d9bc6f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 6 Aug 2025 11:42:03 -0700 Subject: [PATCH 052/391] fix --- crates/config/assets/config.schema.json | 0 crates/forge/src/cmd/config.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 crates/config/assets/config.schema.json diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index d006c43525b2d..a7b65fe949194 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -37,13 +37,13 @@ impl ConfigArgs { // we explicitly normalize the version, so mimic the behavior when invoking solc .normalized_evm_version(); - if shell::is_json() { + let s = if shell::is_json() { serde_json::to_string_pretty(&config)? } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } From 076ad02fd2aa8656769f5e90ac037344bd9de2a3 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 6 Aug 2025 12:45:03 -0700 Subject: [PATCH 053/391] start adding schema generation for config --- .cargo/config.toml | 6 +- Cargo.lock | 9 +++ Cargo.toml | 2 + .../cheatcodes/assets/cheatcodes.schema.json | 2 +- crates/cheatcodes/spec/src/lib.rs | 2 +- crates/config/assets/config.schema.json | 6 ++ crates/config/spec/Cargo.toml | 27 +++++++++ crates/config/spec/src/lib.rs | 60 +++++++++++++++++++ 8 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 crates/config/spec/Cargo.toml create mode 100644 crates/config/spec/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 76cf725f9e2e2..d5ea9679ea410 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,11 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" + # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] diff --git a/Cargo.lock b/Cargo.lock index 2392a73ff20ac..f9e96e513c5ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4616,6 +4616,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "foundry-config-spec" +version = "1.3.1" +dependencies = [ + "schemars 1.0.4", + "serde", + "serde_json", +] + [[package]] name = "foundry-debugger" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index ab299c998cec6..7364b02709bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/cli/", "crates/common/", "crates/config/", + "crates/config/spec/", "crates/debugger/", "crates/doc/", "crates/evm/core/", @@ -190,6 +191,7 @@ foundry-cli = { path = "crates/cli" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } +foundry-config-spec = { path = "crates/config/spec" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } foundry-evm-abi = { path = "crates/evm/abi" } diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index c98cfb69357bd..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index b6d4e9e75aa2e..e35d0b9ded12f 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json index e69de29bb2d1d..00a381eaf66ff 100644 --- a/crates/config/assets/config.schema.json +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..ef9fb39a3f9be --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..c04c20c79d428 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,60 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Config {} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(Config)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if let Ok(old_contents) = fs::read_to_string(file) + && normalize_newlines(&old_contents) == normalize_newlines(contents) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} From 602d68d6d1e7f1b56acc1d27eb6ddfb583656e55 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 6 Aug 2025 13:17:19 -0700 Subject: [PATCH 054/391] naive implementation, restore basic config - relevant for use in forge init --- Cargo.lock | 2 + crates/cheatcodes/spec/src/lib.rs | 2 +- crates/config/Cargo.toml | 4 + crates/config/spec/Cargo.toml | 3 +- crates/config/spec/src/lib.rs | 12 ++- crates/config/src/lib.rs | 152 ++++++++++++++++++++++++++++++ crates/forge/src/cmd/config.rs | 13 ++- crates/forge/tests/cli/cmd.rs | 10 +- 8 files changed, 186 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9e96e513c5ac..4eba64171dd1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4600,6 +4600,7 @@ dependencies = [ "regex", "reqwest", "revm", + "schemars 1.0.4", "semver 1.0.26", "serde", "serde_json", @@ -4620,6 +4621,7 @@ dependencies = [ name = "foundry-config-spec" version = "1.3.1" dependencies = [ + "foundry-config", "schemars 1.0.4", "serde", "serde_json", diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index e35d0b9ded12f..ad2c2619b53d0 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -179,7 +179,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 459dc0d9a75ec..892982a966960 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -49,6 +49,9 @@ walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" @@ -59,3 +62,4 @@ tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml index ef9fb39a3f9be..89d40cf5606ce 100644 --- a/crates/config/spec/Cargo.toml +++ b/crates/config/spec/Cargo.toml @@ -15,6 +15,7 @@ exclude.workspace = true workspace = true [dependencies] +foundry-config.workspace = true serde.workspace = true # schema @@ -24,4 +25,4 @@ schemars = { version = "1.0", optional = true } serde_json.workspace = true [features] -schema = ["dep:schemars"] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs index c04c20c79d428..5a362e963d956 100644 --- a/crates/config/spec/src/lib.rs +++ b/crates/config/spec/src/lib.rs @@ -3,14 +3,18 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use foundry_config::Config; use serde::{Deserialize, Serialize}; // The `config.json` schema. /// Foundry configuration. Learn more: -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] -pub struct Config {} +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} #[cfg(test)] #[expect(clippy::disallowed_macros)] @@ -24,7 +28,7 @@ mod tests { /// Generates the configuration JSON schema. #[cfg(feature = "schema")] fn json_schema() -> String { - serde_json::to_string_pretty(&schemars::schema_for!(Config)).unwrap() + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() } #[test] @@ -45,7 +49,7 @@ mod tests { eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b69d3116f9dfc..f36bd14bcda73 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -159,6 +159,7 @@ pub use compilation::{CompilationRestrictions, SettingsOverrides}; /// /// Note that these behaviors differ from those of [`Config::figment()`]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -1700,6 +1701,24 @@ impl Config { } } + /// Extracts a basic subset of the config, used for initialisations. + /// + /// # Example + /// + /// ```rust + /// use foundry_config::Config; + /// let my_config = Config::with_root(".").into_basic(); + /// ``` + pub fn into_basic(self) -> BasicConfig { + BasicConfig { + profile: self.profile, + src: self.src, + out: self.out, + libs: self.libs, + remappings: self.remappings, + } + } + /// Updates the `foundry.toml` file for the given `root` based on the provided closure. /// /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See @@ -2505,6 +2524,49 @@ impl> From for SolcReq { } } +/// A subset of the foundry `Config` +/// used to initialize a `foundry.toml` file +/// +/// # Example +/// +/// ```rust +/// use foundry_config::{BasicConfig, Config}; +/// use serde::Deserialize; +/// +/// let my_config = Config::figment().extract::(); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct BasicConfig { + /// the profile tag: `[profile.default]` + #[serde(skip)] + pub profile: Profile, + /// path of the source contracts dir, like `src` or `contracts` + pub src: PathBuf, + /// path to where artifacts shut be written to + pub out: PathBuf, + /// all library folders to include, `lib`, `node_modules` + pub libs: Vec, + /// `Remappings` to use for this repo + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub remappings: Vec, +} + +impl BasicConfig { + /// Serialize the config as a String of TOML. + /// + /// This serializes to a table with the name of the profile + pub fn to_string_pretty(&self) -> Result { + let s = toml::to_string_pretty(self)?; + Ok(format!( + "\ +[profile.{}] +{s} +# See more config options: https://getfoundry.sh/config/reference/default-config\n", + self.profile + )) + } +} + pub(crate) mod from_str_lowercase { use serde::{Deserialize, Deserializer, Serializer}; use std::str::FromStr; @@ -3960,6 +4022,51 @@ mod tests { }); } + #[test] + fn test_extract_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "mysrc" + out = "myout" + verbosity = 3 + evm_version = 'berlin' + [profile.other] + src = "other-src" + "#, + )?; + let loaded = Config::load().unwrap(); + assert_eq!(loaded.evm_version, EvmVersion::Berlin); + let base = loaded.into_basic(); + let default = Config::default(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "mysrc".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings.clone(), + } + ); + jail.set_env("FOUNDRY_PROFILE", r"other"); + let base = Config::figment().extract::().unwrap(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "other-src".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings, + } + ); + Ok(()) + }); + } + #[test] #[should_panic] fn test_parse_invalid_fuzz_weight() { @@ -4515,6 +4622,51 @@ mod tests { }); } + #[test] + fn test_extract_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "mysrc" + out = "myout" + verbosity = 3 + evm_version = 'berlin' + [profile.other] + src = "other-src" + "#, + )?; + let loaded = Config::load().unwrap(); + assert_eq!(loaded.evm_version, EvmVersion::Berlin); + let base = loaded.into_basic(); + let default = Config::default(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "mysrc".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings.clone(), + } + ); + jail.set_env("FOUNDRY_PROFILE", r"other"); + let base = Config::figment().extract::().unwrap(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "other-src".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings, + } + ); + Ok(()) + }); + } + #[test] fn test_implicit_profile_loads() { figment::Jail::expect_with(|jail| { diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index a7b65fe949194..89a08bff361a0 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -10,6 +10,10 @@ foundry_config::impl_figment_convert!(ConfigArgs, build, evm); /// CLI arguments for `forge config`. #[derive(Clone, Debug, Parser)] pub struct ConfigArgs { + /// Print only a basic set of the currently set config values. + #[arg(long)] + basic: bool, + /// Attempt to fix any configuration warnings. #[arg(long)] fix: bool, @@ -37,7 +41,14 @@ impl ConfigArgs { // we explicitly normalize the version, so mimic the behavior when invoking solc .normalized_evm_version(); - let s = if shell::is_json() { + let s = if self.basic { + let config = config.into_basic(); + if shell::is_json() { + serde_json::to_string_pretty(&config)? + } else { + config.to_string_pretty()? + } + } else if shell::is_json() { serde_json::to_string_pretty(&config)? } else { config.to_string_pretty()? diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 4147839902a33..0be5e77956fc5 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -3,7 +3,7 @@ use crate::constants::*; use foundry_compilers::artifacts::{ConfigurableContractArtifact, Metadata, remappings::Remapping}; use foundry_config::{ - Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, parse_with_profile, + BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, parse_with_profile, }; use foundry_test_utils::{ foundry_compilers::PathStyle, @@ -278,7 +278,7 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); let s = read_string(&foundry_toml); - let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // Checks that a forge project fails to initialise if dir is already git repo and dirty @@ -638,7 +638,7 @@ Compiler run successful! "#]]); let s = read_string(&foundry_toml); - let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // Checks that quiet mode does not print anything for clone @@ -686,7 +686,7 @@ Compiler run successful! "#]]); let s = read_string(&foundry_toml); - let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // checks that clone works with --keep-directory-structure @@ -725,7 +725,7 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { } let s = read_string(&foundry_toml); - let _config: Config = parse_with_profile(&s).unwrap().unwrap().1; + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // checks that clone works with raw src containing `node_modules` From 17b50b4242c36fa902d2c31f8d1b53b17bed0b31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:00:01 +0700 Subject: [PATCH 055/391] chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 76 +++++++++++++++++++----------------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fa7e6a150282..c6cae03ce7321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,7 +1074,7 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "yansi", ] @@ -2225,7 +2225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] @@ -2475,7 +2475,7 @@ dependencies = [ "time", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "walkdir", "yansi", ] @@ -3896,7 +3896,7 @@ dependencies = [ "thiserror 2.0.12", "toml 0.8.22", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -4153,7 +4153,7 @@ dependencies = [ "tikv-jemallocator", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "tracing-tracy", "tracy-client", "yansi", @@ -4619,7 +4619,7 @@ dependencies = [ "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "ui_test", "zip-extract", ] @@ -4898,8 +4898,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] @@ -5290,7 +5290,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata", "same-file", "walkdir", "winapi-util", @@ -5668,7 +5668,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.5", + "regex-syntax", "sha3", "string_cache", "term", @@ -5682,7 +5682,7 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ - "regex-automata 0.4.9", + "regex-automata", "rustversion", ] @@ -5866,7 +5866,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -5967,11 +5967,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -6254,12 +6254,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "overload", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -6561,12 +6560,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" version = "3.5.0" @@ -7105,7 +7098,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -7463,17 +7456,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -7484,7 +7468,7 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -7493,12 +7477,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -9598,7 +9576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -9635,14 +9613,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -9658,7 +9636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "tracy-client", ] From fcfa587cbfce70b8eb32ac87207cd8a2e2c4f726 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 7 Sep 2025 23:17:57 +0700 Subject: [PATCH 056/391] Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4d5d2d361f5a..716abb3648f13 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,8 +4,10 @@ on: push: branches: - master + - main + - dev + pull_request: - concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true From 5973becffce4febb02046b125adb408c5c4d5f3c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:12:20 +0700 Subject: [PATCH 057/391] Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index e0c9c518e8748..9c2e6a62fd947 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,21 +1,15 @@ name: Docker Image CI - on: push: branches: [ "master" ] pull_request: branches: [ "master" ] - permissions: - contents: read - + contents: read jobs: - build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From 70c73b4b5afaafeb5c587e690df25f6f93f2e94c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Sep 2025 04:33:10 +0700 Subject: [PATCH 058/391] Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .circleci/ci.yml diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..d5d401c51893c --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello From 913e6393fab483e6ddd9906fe97faab4939577fb Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:55:29 +0700 Subject: [PATCH 059/391] Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/ci_cargo.yml diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 8575916b7675f246b54daf70cfddccb3f5b97fb0 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:39:46 +0700 Subject: [PATCH 060/391] Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/web3_defi_gamefi.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- From a07134ed485f776c739b0809a93a8c3c81add390 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Sep 2025 23:51:21 +0700 Subject: [PATCH 061/391] Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index d5d401c51893c..b908ef7ada8fb 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -1,31 +1,14 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + build_and_test: docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + - image: cimg/rust:1.75.0 steps: - # Checkout the code as the first step. - checkout - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello + name: "Check formatting" + command: "cargo fmt --all -- --check" + - run: + name: "Run Clippy" + command: "cargo clippy --all-targets -- -D warnings" + - run: + name: "Run tests" + command: "cargo test --all-features" From bb76ae4cbcc85360612363a7934d07c1422331a8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 16 Sep 2025 03:39:32 +0700 Subject: [PATCH 062/391] Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..76b2889f1c4b2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + From 9c4a6353ac372fd499792e48fbfc01ec247f1a11 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 22 Sep 2025 01:41:52 +0700 Subject: [PATCH 063/391] Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/dependencies.yml | 50 +++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index e452a68c80967..53e2ca70b23b0 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -7,14 +7,48 @@ on: # Run weekly - cron: "0 0 * * SUN" workflow_dispatch: -# Needed so we can run it manually - -permissions: - contents: write - pull-requests: write + # Needed so we can run it manually +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: cargo-update + TITLE: "chore(deps): weekly `cargo update`" + BODY: | + Automation to keep dependencies in `Cargo.lock` current. +
cargo update log +

+ ```log + $cargo_update_log + ``` +

+
jobs: update: - uses: ithacaxyz/ci/.github/workflows/cargo-update-pr.yml@main - secrets: - token: ${{ secrets.GITHUB_TOKEN }} + name: Update + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + + - name: cargo update + # Remove first line that always just says "Updating crates.io index" + run: cargo update --color never 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log + + - name: craft commit message and PR body + id: msg + run: | + export cargo_update_log="$(cat cargo_update.log)" + echo "commit_message<> $GITHUB_OUTPUT + printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "body<> $GITHUB_OUTPUT + echo "$BODY" | envsubst >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + add-paths: ./Cargo.lock + commit-message: ${{ steps.msg.outputs.commit_message }} + title: ${{ env.TITLE }} + body: ${{ steps.msg.outputs.body }} + branch: ${{ env.BRANCH }} From a2eb55138f3a3eb17f33e363c608d99f67a6bffd Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 22 Sep 2025 01:43:43 +0700 Subject: [PATCH 064/391] Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/dependencies.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 53e2ca70b23b0..184e5d13f2c73 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -26,6 +26,9 @@ jobs: update: name: Update runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly From 58a14551db4bf60eaf2dfca242e6e9a062e5e3e8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 16 Sep 2025 05:14:25 +0700 Subject: [PATCH 065/391] Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/ci_cargo.yml diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 82923a7714a069007d6cc2770e5fa908276e258c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:46:59 +0700 Subject: [PATCH 066/391] Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..f967cfaa30db5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello From f0b1a99ac7c7d91ee2e6421f63a8666b2ab42ec5 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:00:39 +0700 Subject: [PATCH 067/391] Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml index 5875cc28ae724..166d2f8ee0235 100644 --- a/.github/workflows/apisec-scan.yml +++ b/.github/workflows/apisec-scan.yml @@ -1,4 +1,6 @@ name: APIsec +permissions: + contents: read on: pull_request: From ab7fffd8da41fed1a84a7173198f107490548ed8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:03:11 +0700 Subject: [PATCH 068/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index d66f340d8c6fc..7bd478a210b3e 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -163,7 +163,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap()) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } From 33594a198c8f0aeee0a28da299af89b275b745fd Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:46:12 +0700 Subject: [PATCH 069/391] Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/nix.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 5e9659e777e54..69c51935102c7 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -1,5 +1,8 @@ name: nix +permissions: + contents: read + on: schedule: # Run weekly @@ -14,6 +17,9 @@ concurrency: jobs: # Opens a PR with an updated flake.lock file update: + permissions: + contents: write + pull-requests: write runs-on: ubuntu-latest steps: - uses: DeterminateSystems/determinate-nix-action@v3 From 12807f145aad8c6e3821f99196b054865fbd9e59 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:46:33 +0700 Subject: [PATCH 070/391] Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 6d377de185359..bef8b7bedd0af 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -66,7 +66,7 @@ jobs: merge-multiple: true # Download all foundry artifacts from the triggering release run pattern: "foundry_*" - path: foundry_artifacts + path: ${{ runner.temp }}/foundry_artifacts github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} @@ -103,11 +103,11 @@ jobs: set -euo pipefail echo "Artifacts in foundry_artifacts:" - ls -la ../foundry_artifacts || true + ls -la ${{ runner.temp }}/foundry_artifacts || true # Derive RELEASE_VERSION from any foundry artifact we downloaded # Expected names: foundry___.{tar.gz,zip} - first_file=$(ls ../foundry_artifacts/foundry_* 2>/dev/null | head -n1 || true) + first_file=$(ls "${{ runner.temp }}/foundry_artifacts/foundry_"* 2>/dev/null | head -n1 || true) if [[ -z "${first_file}" ]]; then echo "No foundry artifacts found to publish" >&2 exit 1 From 6ff615c85092301c5e9478f372caa5cc54869fee Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:40:41 +0000 Subject: [PATCH 071/391] Create config.yml (#105) --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..d5d401c51893c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello From 251a2b4fce0c50e3426ffb2022d9abef5b948fa9 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:51:17 +0000 Subject: [PATCH 072/391] Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 6afa4a42b716ac74d0c107f15b9ff887413877b4 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:17:22 +0000 Subject: [PATCH 073/391] Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index b908ef7ada8fb..7293433a50f2d 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -1,14 +1,32 @@ - build_and_test: +version: 2.1 + +jobs: + build-and-test: docker: - - image: cimg/rust:1.75.0 + - image: cimg/rust:1.88.0 steps: - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- - run: name: "Check formatting" - command: "cargo fmt --all -- --check" + command: cargo fmt -- --check - run: - name: "Run Clippy" - command: "cargo clippy --all-targets -- -D warnings" + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check - run: name: "Run tests" - command: "cargo test --all-features" + command: cargo test From 85cbe70b54d9df09e27033011ad799e6a28c7310 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:30:55 +0700 Subject: [PATCH 074/391] Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index e0c9c518e8748..0000000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From bf979a354ebcd03740d081461860793c38d2244c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:28:17 +0000 Subject: [PATCH 075/391] Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..d5d401c51893c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello From b95fcbe71c234ad4f7fb16ea331f32e420fb0dbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:20:11 +0700 Subject: [PATCH 076/391] chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94526a89f54d4..25e86624d1c8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -189,12 +189,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" From 857e8adf9d575274aa269cfbc31c79490d35914c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:23:20 +0700 Subject: [PATCH 077/391] chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nix.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 09810ac02b208..1667f1096deed 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@dbda91f6efef3ee627f56175120aa9543687d830 # v3 + - uses: DeterminateSystems/determinate-nix-action@762d7fdba79d046449732c729c1d3aaad021baa2 # v3 - uses: actions/checkout@v5 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@dbda91f6efef3ee627f56175120aa9543687d830 # v3 + - uses: DeterminateSystems/determinate-nix-action@762d7fdba79d046449732c729c1d3aaad021baa2 # v3 - uses: actions/checkout@v5 with: persist-credentials: false From 3fa1ae4f94a2bc4067d3dc27e2c561079b59f97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:25:41 +0700 Subject: [PATCH 078/391] chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25e86624d1c8c..f7e8d00e8cda2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: crate-ci/typos@83157de2df0fa7c7ae20f73f9dbed44c41f2bb64 # v1 + - uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1 clippy: runs-on: depot-ubuntu-latest From 35729e26795c6161c162b1b0dd59db9b07e0f635 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:43:48 +0700 Subject: [PATCH 079/391] chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 589d38e8014c5..c4d553c80a428 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -267,7 +267,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -282,7 +282,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: name: "Nightly" tag_name: "nightly" From ee8bebac3a817ad0722e61ac79c727193ec5acd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:45:19 +0700 Subject: [PATCH 080/391] chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nextest.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index 5d986107519d9..a5513e9398fdc 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -67,7 +67,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2 + - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 with: tool: nextest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7e8d00e8cda2..e0424c485b067 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,7 +159,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2 + - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 From f96ff7748a4adf71e1082b02e716da5c4157ecd4 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:25:07 +0000 Subject: [PATCH 081/391] Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0424c485b067..94526a89f54d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1 + - uses: crate-ci/typos@83157de2df0fa7c7ae20f73f9dbed44c41f2bb64 # v1 clippy: runs-on: depot-ubuntu-latest @@ -159,7 +159,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 + - uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 @@ -189,12 +189,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" From 9c9343ad7b53f359ec32c65a31bf69c3f4b208e9 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:31:57 +0700 Subject: [PATCH 082/391] Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- crates/doc/src/writer/buf_writer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index d1e51dfeca6e0..e5dfbbaadce25 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -213,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; From fb852fce06ecb0cea856cb1d238697cc6dc74abf Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:25:54 +0700 Subject: [PATCH 083/391] Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d5d401c51893c..790c879119cd6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,31 +1,17 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference version: 2.1 -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs jobs: say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base + docker: - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: - # Checkout the code as the first step. - checkout - run: name: "Say hello" command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows + workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. + say-hello-workflow: jobs: - say-hello From 4a7067157f3813c366b07106e0bbe2534efab823 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 03:28:54 +0700 Subject: [PATCH 084/391] Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{ci_cargo.yml => cargo.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{ci_cargo.yml => cargo.yml} (100%) diff --git a/.circleci/ci_cargo.yml b/.circleci/cargo.yml similarity index 100% rename from .circleci/ci_cargo.yml rename to .circleci/cargo.yml From 2b20d9d4ccd0e79ba9d3660e172ee455084a76fb Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 03:31:48 +0700 Subject: [PATCH 085/391] Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{config.yml => ci.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{config.yml => ci.yml} (100%) diff --git a/.circleci/config.yml b/.circleci/ci.yml similarity index 100% rename from .circleci/config.yml rename to .circleci/ci.yml From e2b8aa9d6184931920d28f0cdfc7dfb6f4131871 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 03:36:47 +0700 Subject: [PATCH 086/391] Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{ci_cargo.yml => ci_v1.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{ci_cargo.yml => ci_v1.yml} (100%) diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_v1.yml similarity index 100% rename from .circleci/ci_cargo.yml rename to .circleci/ci_v1.yml From d4b621fc308e595e484eab989b403c38f36540d8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 03:55:28 +0700 Subject: [PATCH 087/391] Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_v1.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml index 46a18d45a5fca..94bf46b3bb04f 100644 --- a/.circleci/ci_v1.yml +++ b/.circleci/ci_v1.yml @@ -24,12 +24,6 @@ jobs: - "~/.cargo/registry/cache" - "~/.cargo/git/db" - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test workflows: ci: From b5585aebcd03672a0efd9054ce5502d750097c90 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:18:46 +0000 Subject: [PATCH 088/391] Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .circleci/ci.yml | 31 +++++++++++++++++++++++++++++++ .circleci/ci_v1.yml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_v1.yml diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..f967cfaa30db5 --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..94bf46b3bb04f --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test From b67d5891aa6dee2a15637794483188a656d1852c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:34:13 +0000 Subject: [PATCH 089/391] Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ++++++++++++++++++++++++++++++++++ .circleci/config.yml | 17 ----------------- 2 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 .circleci/ci_deploy.yml delete mode 100644 .circleci/config.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 790c879119cd6..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello From d39b7a5e51705fe2b696f6794e0adfc047309402 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:54:08 +0700 Subject: [PATCH 090/391] Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/snyk-container.yml | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/snyk-container.yml diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..0db31480beb38 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@14818c4695ecc4045f33c9cee9e795a788711ca4 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: snyk.sarif From a767f09584ca1598d02e8e93dedea07129f88523 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:14:19 +0700 Subject: [PATCH 091/391] Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{ci.yml => ci-say-hello.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{ci.yml => ci-say-hello.yml} (100%) diff --git a/.circleci/ci.yml b/.circleci/ci-say-hello.yml similarity index 100% rename from .circleci/ci.yml rename to .circleci/ci-say-hello.yml From 93f1ac838589770bd4a6826b5807c75cf0961806 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:18:58 +0700 Subject: [PATCH 092/391] Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94526a89f54d4..76b4367363adb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: docs: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -55,7 +55,7 @@ jobs: if: github.ref_name == 'master' && github.event_name == 'push' needs: [docs] runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: pages: write id-token: write @@ -69,7 +69,7 @@ jobs: doctest: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -85,7 +85,7 @@ jobs: typos: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -96,7 +96,7 @@ jobs: clippy: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -115,7 +115,7 @@ jobs: rustfmt: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -130,7 +130,7 @@ jobs: forge-fmt: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -148,7 +148,7 @@ jobs: crate-checks: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -213,7 +213,7 @@ jobs: - crate-checks - deny - codeql - timeout-minutes: 30 + timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 From cac7793c527951b86839c24d55156f60c27b695c Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 15 Oct 2025 22:14:25 +0000 Subject: [PATCH 093/391] Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.ym | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/config.ym diff --git a/.circleci/config.ym b/.circleci/config.ym new file mode 100644 index 0000000000000..709c9a7474d04 --- /dev/null +++ b/.circleci/config.ym @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/hardhat-project/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- From 95d4a0b5ac6da5fefadd287ef1e07f3a07e8104f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 06:11:14 +0700 Subject: [PATCH 094/391] chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fb563560bbbc..3a48463ff8597 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6c2905bafc2df7ccd32ca3af13f0b0d82f2e2ff9dfbeb12196c0d978d5c0deb" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2acb6637a9c0e1cdf8971e0ced8f3fa34c04c5e9dccf6bb184f6a64fe0e37d8" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -383,9 +383,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b77f7d5e60ad8ae6bd2200b8097919712a07a6db622a4b201e7ead6166f02e5" +checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" dependencies = [ "alloy-rlp", "arbitrary", @@ -784,9 +784,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c84c3637bee9b5c4a4d2b93360ee16553d299c3b932712353caf1cea76d0e6" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -798,9 +798,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a882aa4e1790063362434b9b40d358942b188477ac1c44cfb8a52816ffc0cc17" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5772107f9bb265d8d8c86e0733937bb20d0857ea5425b1b6ddf51a9804042" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "alloy-json-abi", "const-hex", @@ -835,9 +835,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e188b939aa4793edfaaa099cb1be4e620036a775b4bdf24fdc56f1cd6fd45890" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow", @@ -845,9 +845,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c8a9a909872097caffc05df134e5ef2253a1cdb56d3a9cf0052a042ac763f9" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9674,9 +9674,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2375c17f6067adc651d8c2c51658019cef32edfff4a982adaf1d7fd1c039f08b" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index a69d750b6eb2a..6c29441366881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,7 +259,7 @@ alloy-hardforks = { version = "0.3.2", default-features = false } alloy-op-hardforks = { version = "0.3.2", default-features = false } ## alloy-core -alloy-dyn-abi = "1.3.1" +alloy-dyn-abi = "1.4.1" alloy-json-abi = "1.3.0" alloy-primitives = { version = "1.3.1", features = [ "getrandom", From 0a0a62a2f9db40b779cbf77b8c074e464c916a06 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 00:25:27 +0000 Subject: [PATCH 095/391] Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..d7a82b5c93b6e --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 343afb6fad0635fff7f09f7f6580276f608bdff5 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:01:53 +0700 Subject: [PATCH 096/391] Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{config.ym => config.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{config.ym => config.yml} (100%) diff --git a/.circleci/config.ym b/.circleci/config.yml similarity index 100% rename from .circleci/config.ym rename to .circleci/config.yml From 9f64c477f81e02b10dc2bc39f62a86f20de8e72f Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:13:58 +0700 Subject: [PATCH 097/391] Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 709c9a7474d04..3b59d59e57c17 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ jobs: executor: my-custom-executor steps: - checkout + - run: echo "Build started" - run: | # echo Hello, World! From 1c5dcafbe1b4931c8a891067874d22a5c16caec9 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 07:49:33 +0000 Subject: [PATCH 098/391] fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .circleci/cargo.yml | 37 +++++++++++++++++++ .circleci/ci-say-hello.yml | 31 ++++++++++++++++ .circleci/ci_deploy.yml | 34 +++++++++++++++++ .circleci/ci_v1.yml | 31 ++++++++++++++++ .circleci/config.yml | 27 ++++++++++++++ .github/workflows/release.yml | 4 +- .github/workflows/snyk-container.yml | 55 ++++++++++++++++++++++++++++ .github/workflows/test.yml | 26 ++++++------- 8 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-say-hello.yml create mode 100644 .circleci/ci_deploy.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/config.yml create mode 100644 .github/workflows/snyk-container.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..d7a82b5c93b6e --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci-say-hello.yml b/.circleci/ci-say-hello.yml new file mode 100644 index 0000000000000..f967cfaa30db5 --- /dev/null +++ b/.circleci/ci-say-hello.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..94bf46b3bb04f --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..3b59d59e57c17 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,27 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/hardhat-project/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: echo "Build started" + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d03c82751e72b..06061ff6c7508 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -267,7 +267,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -282,7 +282,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..0db31480beb38 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@14818c4695ecc4045f33c9cee9e795a788711ca4 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0424c485b067..76b4367363adb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: docs: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -55,7 +55,7 @@ jobs: if: github.ref_name == 'master' && github.event_name == 'push' needs: [docs] runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: pages: write id-token: write @@ -69,7 +69,7 @@ jobs: doctest: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -85,18 +85,18 @@ jobs: typos: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1 + - uses: crate-ci/typos@83157de2df0fa7c7ae20f73f9dbed44c41f2bb64 # v1 clippy: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -115,7 +115,7 @@ jobs: rustfmt: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -130,7 +130,7 @@ jobs: forge-fmt: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -148,7 +148,7 @@ jobs: crate-checks: runs-on: depot-ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 5 permissions: contents: read steps: @@ -159,7 +159,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 + - uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 @@ -189,12 +189,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" @@ -213,7 +213,7 @@ jobs: - crate-checks - deny - codeql - timeout-minutes: 30 + timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 From 5b4a4997ef80f7f75dda19032c4f879f4905ead8 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:58:49 +0700 Subject: [PATCH 099/391] Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> From 8d1f66a47982e14d54d63b48e762ecd9b6f7cf8e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:00:26 +0700 Subject: [PATCH 100/391] Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> From 93a4079269bcc9e83abb5c4da5d2b3460e8a8075 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:00:48 +0700 Subject: [PATCH 101/391] Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> From b8b5fbb83fa2436063cebc34ddf900abc972b11d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:57:08 +0200 Subject: [PATCH 102/391] fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance --- crates/fmt/src/state/sol.rs | 34 +++++++++++++------ .../bracket-spacing.fmt.sol | 12 +++++++ .../contract-new-lines.fmt.sol | 12 +++++++ .../fmt/testdata/ContractDefinition/fmt.sol | 12 +++++++ .../testdata/ContractDefinition/original.sol | 14 +++++++- 5 files changed, 72 insertions(+), 12 deletions(-) diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index f5c01bb85b619..21664445ee1c0 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -288,6 +288,14 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.nbsp(); + if let Some(layout) = layout + && !self.handle_span(layout.span, false) + { + self.word("layout at "); + self.print_expr(layout.slot); + self.print_sep(Separator::Space); + } + if let Some(first) = bases.first().map(|base| base.span()) && let Some(last) = bases.last().map(|base| base.span()) && self.inline_config.is_disabled(first.to(last)) @@ -296,26 +304,30 @@ impl<'ast> State<'_, 'ast> { } else if !bases.is_empty() { self.word("is"); self.space(); - for (pos, base) in bases.iter().delimited() { + let last = bases.len() - 1; + for (i, base) in bases.iter().enumerate() { if !self.handle_span(base.span(), false) { self.print_modifier_call(base, false); - if !pos.is_last { + if i != last { self.word(","); - self.space(); + if self + .print_comments( + bases[i + 1].span().lo(), + CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(), + ) + .is_none() + { + self.space(); + } } } } - self.space(); + if !self.print_trailing_comment(bases.last().unwrap().span().hi(), None) { + self.space(); + } self.s.offset(-self.ind); } self.end(); - if let Some(layout) = layout - && !self.handle_span(layout.span, false) - { - self.word("layout at "); - self.print_expr(layout.slot); - self.print_sep(Separator::Space); - } self.print_word("{"); self.end(); diff --git a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol index 20894a47a3262..25074229db558 100644 --- a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol @@ -39,3 +39,15 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{ } + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{ } + +contract WithLayoutAndBase layout at 69 is Base { } diff --git a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol index 2e9661f956dcf..990845d3d1349 100644 --- a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol @@ -50,3 +50,15 @@ contract ERC20DecimalsMock is ERC20 { } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{} + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{} + +contract WithLayoutAndBase layout at 69 is Base {} diff --git a/crates/fmt/testdata/ContractDefinition/fmt.sol b/crates/fmt/testdata/ContractDefinition/fmt.sol index 551e84decfc5b..93cddcd2c6a20 100644 --- a/crates/fmt/testdata/ContractDefinition/fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/fmt.sol @@ -45,3 +45,15 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{} + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{} + +contract WithLayoutAndBase layout at 69 is Base {} diff --git a/crates/fmt/testdata/ContractDefinition/original.sol b/crates/fmt/testdata/ContractDefinition/original.sol index 4c671985bda7b..c0aa88a7d6d17 100644 --- a/crates/fmt/testdata/ContractDefinition/original.sol +++ b/crates/fmt/testdata/ContractDefinition/original.sol @@ -17,7 +17,7 @@ contract SampleContract { // comment 16 external /* comment 17 */ pure - returns(uint256) + returns(uint256) // comment 18 { // comment 19 return arg1 > arg2 ? arg1 : arg2; @@ -38,3 +38,15 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{ } + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{ } + +contract WithLayoutAndBase layout at 69 is Base {} From c35961b39146e60ad4f76f912cc383e5852842c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:07:12 +0700 Subject: [PATCH 103/391] chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nextest.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index a5513e9398fdc..d39b597f7613e 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -67,7 +67,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2 with: tool: nextest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76b4367363adb..aa505a05a8382 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,7 +159,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2 + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 From 79ead878042ea6f1cb9ba2cefb02d25d8ac8601e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:12:28 +0700 Subject: [PATCH 104/391] Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. --- crates/fmt/src/state/sol.rs | 34 ++++++------------- .../bracket-spacing.fmt.sol | 12 ------- .../contract-new-lines.fmt.sol | 12 ------- .../fmt/testdata/ContractDefinition/fmt.sol | 12 ------- .../testdata/ContractDefinition/original.sol | 14 +------- 5 files changed, 12 insertions(+), 72 deletions(-) diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index 21664445ee1c0..f5c01bb85b619 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -288,14 +288,6 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.nbsp(); - if let Some(layout) = layout - && !self.handle_span(layout.span, false) - { - self.word("layout at "); - self.print_expr(layout.slot); - self.print_sep(Separator::Space); - } - if let Some(first) = bases.first().map(|base| base.span()) && let Some(last) = bases.last().map(|base| base.span()) && self.inline_config.is_disabled(first.to(last)) @@ -304,30 +296,26 @@ impl<'ast> State<'_, 'ast> { } else if !bases.is_empty() { self.word("is"); self.space(); - let last = bases.len() - 1; - for (i, base) in bases.iter().enumerate() { + for (pos, base) in bases.iter().delimited() { if !self.handle_span(base.span(), false) { self.print_modifier_call(base, false); - if i != last { + if !pos.is_last { self.word(","); - if self - .print_comments( - bases[i + 1].span().lo(), - CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(), - ) - .is_none() - { - self.space(); - } + self.space(); } } } - if !self.print_trailing_comment(bases.last().unwrap().span().hi(), None) { - self.space(); - } + self.space(); self.s.offset(-self.ind); } self.end(); + if let Some(layout) = layout + && !self.handle_span(layout.span, false) + { + self.word("layout at "); + self.print_expr(layout.slot); + self.print_sep(Separator::Space); + } self.print_word("{"); self.end(); diff --git a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol index 25074229db558..20894a47a3262 100644 --- a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol @@ -39,15 +39,3 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } - -contract SomeContract is - ERC165Upgradeable, // 1 inherited component - ISomeContract // 4 inherited components -{ } - -contract AnotherContract is - Adminable, /* 1 inherited components */ - UUPSUpgradeable /* 1 inherited component */ -{ } - -contract WithLayoutAndBase layout at 69 is Base { } diff --git a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol index 990845d3d1349..2e9661f956dcf 100644 --- a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol @@ -50,15 +50,3 @@ contract ERC20DecimalsMock is ERC20 { } } - -contract SomeContract is - ERC165Upgradeable, // 1 inherited component - ISomeContract // 4 inherited components -{} - -contract AnotherContract is - Adminable, /* 1 inherited components */ - UUPSUpgradeable /* 1 inherited component */ -{} - -contract WithLayoutAndBase layout at 69 is Base {} diff --git a/crates/fmt/testdata/ContractDefinition/fmt.sol b/crates/fmt/testdata/ContractDefinition/fmt.sol index 93cddcd2c6a20..551e84decfc5b 100644 --- a/crates/fmt/testdata/ContractDefinition/fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/fmt.sol @@ -45,15 +45,3 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } - -contract SomeContract is - ERC165Upgradeable, // 1 inherited component - ISomeContract // 4 inherited components -{} - -contract AnotherContract is - Adminable, /* 1 inherited components */ - UUPSUpgradeable /* 1 inherited component */ -{} - -contract WithLayoutAndBase layout at 69 is Base {} diff --git a/crates/fmt/testdata/ContractDefinition/original.sol b/crates/fmt/testdata/ContractDefinition/original.sol index c0aa88a7d6d17..4c671985bda7b 100644 --- a/crates/fmt/testdata/ContractDefinition/original.sol +++ b/crates/fmt/testdata/ContractDefinition/original.sol @@ -17,7 +17,7 @@ contract SampleContract { // comment 16 external /* comment 17 */ pure - returns(uint256) + returns(uint256) // comment 18 { // comment 19 return arg1 > arg2 ? arg1 : arg2; @@ -38,15 +38,3 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } - -contract SomeContract is - ERC165Upgradeable, // 1 inherited component - ISomeContract // 4 inherited components -{ } - -contract AnotherContract is - Adminable, /* 1 inherited components */ - UUPSUpgradeable /* 1 inherited component */ -{ } - -contract WithLayoutAndBase layout at 69 is Base {} From 4a977cd7b1d2aebd2762d9670975d40e1c9e0fe1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:24:58 +0700 Subject: [PATCH 105/391] chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/snyk-container.yml | 2 +- .github/workflows/test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml index 0db31480beb38..171f2aabfbfb1 100644 --- a/.github/workflows/snyk-container.yml +++ b/.github/workflows/snyk-container.yml @@ -50,6 +50,6 @@ jobs: image: your/image-to-test args: --file=Dockerfile - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: snyk.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aa505a05a8382..1f16e1d9964b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -189,12 +189,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" From 1c660a0d0c1f75f90876e4d55f3f2228bb2a37aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:47:02 +0000 Subject: [PATCH 106/391] chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/snyk-container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml index 171f2aabfbfb1..3c3a0f30b84e3 100644 --- a/.github/workflows/snyk-container.yml +++ b/.github/workflows/snyk-container.yml @@ -40,7 +40,7 @@ jobs: # Snyk can be used to break the build when it detects vulnerabilities. # In this case we want to upload the issues to GitHub Code Scanning continue-on-error: true - uses: snyk/actions/docker@14818c4695ecc4045f33c9cee9e795a788711ca4 + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 env: # In order to use the Snyk Action you will need to have a Snyk API token. # More details in https://github.com/snyk/actions#getting-your-snyk-token From d287039817a741b53e724d4dacd0a0d4ba496eb7 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:02:39 +0700 Subject: [PATCH 107/391] Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 790c879119cd6..d5d401c51893c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference version: 2.1 +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs jobs: say-hello: - docker: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base - image: cimg/base:current - + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps steps: + # Checkout the code as the first step. - checkout - run: name: "Say hello" command: "echo Hello, World!" - + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows workflows: - say-hello-workflow: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. jobs: - say-hello From 4934b0283692720e702a258ee5300a62bd25005e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:17:59 +0700 Subject: [PATCH 108/391] Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b59d59e57c17..f967cfaa30db5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,27 +1,31 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - +# See: https://circleci.com/docs/reference/configuration-reference version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/hardhat-project/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs jobs: - web3-defi-game-project-: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current - executor: my-custom-executor + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps steps: + # Checkout the code as the first step. - checkout - - run: echo "Build started" - - run: | - # echo Hello, World! + - run: + name: "Say hello" + command: "echo Hello, World!" +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows workflows: - my-custom-workflow: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. jobs: - - web3-defi-game-project- + - say-hello From 0749dc831e2a63a259928cd773a389371457e671 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 06:32:48 +0700 Subject: [PATCH 109/391] Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b59d59e57c17..04f996a346ab7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,8 +18,6 @@ jobs: steps: - checkout - run: echo "Build started" - - run: | - # echo Hello, World! workflows: my-custom-workflow: From 880e28435b0ef38d85f57279df2d33839dfabfef Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:49:41 +0000 Subject: [PATCH 110/391] Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci-say-hello.yml | 31 ------------------------------- .circleci/ci-web3-defi-gamefi.yml | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 31 deletions(-) delete mode 100644 .circleci/ci-say-hello.yml create mode 100644 .circleci/ci-web3-defi-gamefi.yml diff --git a/.circleci/ci-say-hello.yml b/.circleci/ci-say-hello.yml deleted file mode 100644 index f967cfaa30db5..0000000000000 --- a/.circleci/ci-say-hello.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello diff --git a/.circleci/ci-web3-defi-gamefi.yml b/.circleci/ci-web3-defi-gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/ci-web3-defi-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- From 78de4afcd8c43817a445010eca9d771e3e87f07b Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 04:30:24 +0000 Subject: [PATCH 111/391] Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci-web3-defi-gamefi.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .circleci/ci-web3-defi-gamefi.yml diff --git a/.circleci/ci-web3-defi-gamefi.yml b/.circleci/ci-web3-defi-gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/ci-web3-defi-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From 23811a7b17a1358e75fa611d4af534e7cddeb603 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 07:19:43 +0000 Subject: [PATCH 112/391] Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail From cc3940779716d5a91105941c16faea922f1d0b0e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 07:22:09 +0000 Subject: [PATCH 113/391] Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index d7a82b5c93b6e..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test From aa9ac06c9c3303de0af9c42c3bd399f6714be098 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:43:53 +0700 Subject: [PATCH 114/391] chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nextest.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index d39b597f7613e..3d7ef42941deb 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -67,7 +67,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2 + - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 with: tool: nextest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f16e1d9964b1..e71c1d2f993e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,7 +159,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2 + - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 From 93e0c5525c67f81ee0ca78f773b5d22e24a9b117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:46:43 +0700 Subject: [PATCH 115/391] chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/snyk-container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml index 3c3a0f30b84e3..10ac023cf51fc 100644 --- a/.github/workflows/snyk-container.yml +++ b/.github/workflows/snyk-container.yml @@ -33,7 +33,7 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build a Docker image run: docker build -t your/image-to-test . - name: Run Snyk to check Docker image for vulnerabilities From 07b64156d78ae32bf392635e282bc71e2d3d347e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:50:52 +0000 Subject: [PATCH 116/391] Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .circleci/cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 9f4f6848a6f83c0e785573833b89471bcc08e048 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:56:21 +0000 Subject: [PATCH 117/391] fix(anvil): always disable nonce check (foundry-rs#12144) (#165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude --- .github/workflows/nextest.yml | 5 +- Cargo.lock | 150 +- Cargo.toml | 48 +- crates/anvil/src/eth/backend/mem/mod.rs | 13 +- crates/cheatcodes/spec/src/lib.rs | 3 +- crates/chisel/src/source.rs | 2 +- crates/common/src/fs.rs | 43 +- crates/forge/tests/cli/test_cmd/core.rs | 142 ++ crates/forge/tests/cli/test_cmd/fuzz.rs | 735 +++++++++ .../tests/cli/test_cmd/invariant/common.rs | 1424 +++++++++++++++++ .../test_cmd/invariant/mod.rs} | 708 +------- .../tests/cli/test_cmd/invariant/storage.rs | 19 +- .../tests/cli/test_cmd/invariant/target.rs | 766 +++++++++ crates/forge/tests/cli/test_cmd/logs.rs | 756 +++++++++ .../cli/{test_cmd.rs => test_cmd/mod.rs} | 62 +- .../forge/tests/cli/test_cmd/repros.rs | 447 +++++- .../forge/tests/{it => cli/test_cmd}/spec.rs | 13 +- .../forge/tests/{it => cli/test_cmd}/table.rs | 0 crates/forge/tests/cli/test_cmd/trace.rs | 397 +++++ crates/forge/tests/it/cheats.rs | 112 -- crates/forge/tests/it/config.rs | 170 -- crates/forge/tests/it/core.rs | 829 ---------- crates/forge/tests/it/fork.rs | 129 -- crates/forge/tests/it/fs.rs | 23 - crates/forge/tests/it/fuzz.rs | 496 ------ crates/forge/tests/it/inline.rs | 70 - crates/forge/tests/it/main.rs | 14 - crates/forge/tests/it/repros.rs | 411 ----- crates/forge/tests/it/test_helpers.rs | 283 ---- crates/forge/tests/it/vyper.rs | 10 - crates/linking/src/lib.rs | 15 +- crates/test-utils/src/rpc.rs | 20 +- crates/test-utils/src/script.rs | 25 +- crates/test-utils/src/util.rs | 71 +- testdata/default/cheats/AccessList.t.sol | 8 +- testdata/default/cheats/Addr.t.sol | 7 +- .../default/cheats/ArbitraryStorage.t.sol | 22 +- testdata/default/cheats/Assert.t.sol | 7 +- testdata/default/cheats/Assume.t.sol | 7 +- testdata/default/cheats/AssumeNoRevert.t.sol | 18 +- testdata/default/cheats/AttachBlob.t.sol | 6 +- .../default/cheats/AttachDelegation.t.sol | 6 +- testdata/default/cheats/Bank.t.sol | 7 +- testdata/default/cheats/Base64.t.sol | 8 +- testdata/default/cheats/BlobBaseFee.t.sol | 7 +- testdata/default/cheats/Blobhashes.t.sol | 7 +- testdata/default/cheats/Broadcast.t.sol | 56 +- .../cheats/BroadcastRawTransaction.t.sol | 11 +- testdata/default/cheats/ChainId.t.sol | 7 +- testdata/default/cheats/CloneAccount.t.sol | 7 +- testdata/default/cheats/Cool.t.sol | 6 +- testdata/default/cheats/CopyStorage.t.sol | 10 +- testdata/default/cheats/Deal.t.sol | 7 +- testdata/default/cheats/DeployCode.t.sol | 7 +- testdata/default/cheats/Derive.t.sol | 7 +- testdata/default/cheats/EnsNamehash.t.sol | 7 +- testdata/default/cheats/Env.t.sol | 7 +- testdata/default/cheats/Etch.t.sol | 7 +- testdata/default/cheats/ExpectCall.t.sol | 15 +- testdata/default/cheats/ExpectCreate.t.sol | 6 +- testdata/default/cheats/ExpectEmit.t.sol | 9 +- testdata/default/cheats/ExpectRevert.t.sol | 19 +- testdata/default/cheats/Fee.t.sol | 7 +- testdata/default/cheats/Ffi.t.sol | 7 +- testdata/default/cheats/Fork.t.sol | 6 +- testdata/default/cheats/Fork2.t.sol | 15 +- testdata/default/cheats/Fs.t.sol | 14 +- testdata/default/cheats/GasMetering.t.sol | 8 +- testdata/default/cheats/GetArtifactPath.t.sol | 12 +- .../default/cheats/GetBlockTimestamp.t.sol | 7 +- testdata/default/cheats/GetChain.t.sol | 7 +- testdata/default/cheats/GetCode.t.sol | 8 +- testdata/default/cheats/GetDeployedCode.t.sol | 7 +- .../default/cheats/GetFoundryVersion.t.sol | 7 +- testdata/default/cheats/GetLabel.t.sol | 7 +- testdata/default/cheats/GetNonce.t.sol | 7 +- .../default/cheats/GetRawBlockHeader.t.sol | 7 +- testdata/default/cheats/GetStorageSlots.t.sol | 6 +- testdata/default/cheats/Json.t.sol | 11 +- testdata/default/cheats/Label.t.sol | 7 +- testdata/default/cheats/Load.t.sol | 6 +- testdata/default/cheats/Mapping.t.sol | 7 +- testdata/default/cheats/MemSafety.t.sol | 7 +- testdata/default/cheats/MockCall.t.sol | 11 +- testdata/default/cheats/MockCalls.t.sol | 7 +- testdata/default/cheats/MockFunction.t.sol | 6 +- testdata/default/cheats/Nonce.t.sol | 8 +- testdata/default/cheats/Parse.t.sol | 7 +- testdata/default/cheats/Prank.t.sol | 15 +- testdata/default/cheats/Prevrandao.t.sol | 7 +- testdata/default/cheats/ProjectRoot.t.sol | 20 +- testdata/default/cheats/Prompt.t.sol | 23 +- testdata/default/cheats/RandomAddress.t.sol | 7 +- testdata/default/cheats/RandomBytes.t.sol | 7 +- .../default/cheats/RandomCheatcodes.t.sol | 11 +- testdata/default/cheats/RandomUint.t.sol | 7 +- testdata/default/cheats/ReadCallers.t.sol | 7 +- testdata/default/cheats/Record.t.sol | 7 +- .../cheats/RecordAccountAccesses.t.sol | 108 +- .../default/cheats/RecordDebugTrace.t.sol | 19 +- testdata/default/cheats/RecordLogs.t.sol | 6 +- testdata/default/cheats/Remember.t.sol | 7 +- testdata/default/cheats/ResetNonce.t.sol | 6 +- testdata/default/cheats/Rlp.t.sol | 7 +- testdata/default/cheats/Roll.t.sol | 7 +- testdata/default/cheats/RpcUrls.t.sol | 7 +- testdata/default/cheats/Seed.t.sol | 7 +- testdata/default/cheats/SetBlockhash.t.sol | 7 +- testdata/default/cheats/SetNonce.t.sol | 6 +- testdata/default/cheats/SetNonceUnsafe.t.sol | 6 +- testdata/default/cheats/Setup.t.sol | 6 +- testdata/default/cheats/Shuffle.t.sol | 7 +- testdata/default/cheats/Sign.t.sol | 7 +- testdata/default/cheats/SignP256.t.sol | 7 +- testdata/default/cheats/Skip.t.sol | 7 +- testdata/default/cheats/Sleep.t.sol | 7 +- testdata/default/cheats/Sort.t.sol | 7 +- .../default/cheats/StateDiffBytesString.t.sol | 6 +- .../default/cheats/StateDiffMappings.t.sol | 6 +- .../cheats/StateDiffStorageLayout.t.sol | 6 +- .../default/cheats/StateDiffStructTest.t.sol | 6 +- testdata/default/cheats/StateSnapshots.t.sol | 11 +- .../default/cheats/StorageSlotState.t.sol | 7 +- testdata/default/cheats/Store.t.sol | 6 +- testdata/default/cheats/StringUtils.t.sol | 7 +- testdata/default/cheats/ToString.t.sol | 7 +- testdata/default/cheats/Toml.t.sol | 11 +- testdata/default/cheats/Travel.t.sol | 7 +- testdata/default/cheats/TryFfi.sol | 7 +- testdata/default/cheats/UnixTime.t.sol | 7 +- testdata/default/cheats/Wallet.t.sol | 7 +- testdata/default/cheats/Warp.t.sol | 19 +- testdata/default/cheats/dumpState.t.sol | 7 +- testdata/default/cheats/getBlockNumber.t.sol | 7 +- testdata/default/cheats/loadAllocs.t.sol | 6 +- .../default/core/BadSigAfterInvariant.t.sol | 4 +- .../default/core/ContractEnvironment.t.sol | 4 +- .../core/FailingTestAfterFailedSetup.t.sol | 18 - testdata/default/core/LegacyAssertions.t.sol | 24 - testdata/default/core/PaymentFailure.t.sol | 19 - testdata/default/core/Reverting.t.sol | 7 +- testdata/default/core/SetupConsistency.t.sol | 4 +- testdata/default/fork/ForkSame_1.t.sol | 6 +- testdata/default/fork/ForkSame_2.t.sol | 6 +- testdata/default/fork/LaunchFork.t.sol | 21 +- testdata/default/fs/Disabled.t.sol | 23 +- .../fs/{Default.t.sol => ReadOnly.sol} | 14 +- testdata/default/fuzz/Fuzz.t.sol | 31 - testdata/default/fuzz/FuzzCollection.t.sol | 70 - .../default/fuzz/FuzzFailurePersist.t.sol | 29 - testdata/default/fuzz/FuzzInt.t.sol | 58 - testdata/default/fuzz/FuzzPositive.t.sol | 18 - testdata/default/fuzz/FuzzUint.t.sol | 46 - .../common/InvariantAfterInvariant.t.sol | 55 - .../invariant/common/InvariantAssume.t.sol | 23 - .../common/InvariantCalldataDictionary.t.sol | 95 -- .../common/InvariantCustomError.t.sol | 35 - .../common/InvariantExcludedSenders.t.sol | 22 - .../invariant/common/InvariantFixtures.t.sol | 77 - .../common/InvariantHandlerFailure.t.sol | 35 - .../common/InvariantInnerContract.t.sol | 50 - .../common/InvariantPreserveState.t.sol | 49 - .../common/InvariantReentrancy.t.sol | 55 - .../invariant/common/InvariantRollFork.t.sol | 50 - .../common/InvariantScrapeValues.t.sol | 69 - .../common/InvariantSequenceNoReverts.t.sol | 25 - .../common/InvariantShrinkBigSequence.t.sol | 31 - .../common/InvariantShrinkFailOnRevert.t.sol | 26 - .../common/InvariantShrinkWithAssert.t.sol | 32 - .../invariant/common/InvariantTest1.t.sol | 39 - .../invariant/target/ExcludeContracts.t.sol | 31 - .../invariant/target/ExcludeSelectors.t.sol | 41 - .../invariant/target/ExcludeSenders.t.sol | 45 - .../target/FuzzedTargetContracts.t.sol | 66 - .../invariant/target/TargetContracts.t.sol | 32 - .../invariant/target/TargetInterfaces.t.sol | 72 - .../invariant/target/TargetSelectors.t.sol | 41 - .../fuzz/invariant/target/TargetSenders.t.sol | 31 - .../targetAbi/ExcludeArtifacts.t.sol | 45 - .../targetAbi/TargetArtifactSelectors.t.sol | 42 - .../targetAbi/TargetArtifactSelectors2.t.sol | 72 - .../invariant/targetAbi/TargetArtifacts.t.sol | 44 - testdata/default/inline/FuzzInlineConf.t.sol | 6 +- .../default/inline/InvariantInlineConf.t.sol | 6 +- .../default/linking/duplicate/Duplicate.t.sol | 4 +- testdata/default/linking/nested/Nested.t.sol | 4 +- testdata/default/linking/simple/Simple.t.sol | 4 +- testdata/default/logs/DebugLogs.t.sol | 105 -- testdata/default/logs/HardhatLogs.t.sol | 238 --- testdata/default/repros/Issue10302.t.sol | 7 +- testdata/default/repros/Issue10477.t.sol | 7 +- testdata/default/repros/Issue10527.t.sol | 7 +- testdata/default/repros/Issue10552.t.sol | 7 +- testdata/default/repros/Issue10586.t.sol | 11 +- testdata/default/repros/Issue10957.t.sol | 7 +- testdata/default/repros/Issue11353.t.sol | 7 +- testdata/default/repros/Issue11616.t.sol | 6 +- testdata/default/repros/Issue2623.t.sol | 7 +- testdata/default/repros/Issue2629.t.sol | 7 +- testdata/default/repros/Issue2723.t.sol | 7 +- testdata/default/repros/Issue2851.t.sol | 29 - testdata/default/repros/Issue2898.t.sol | 7 +- testdata/default/repros/Issue2956.t.sol | 6 +- testdata/default/repros/Issue2984.t.sol | 6 +- testdata/default/repros/Issue3055.t.sol | 36 - testdata/default/repros/Issue3077.t.sol | 7 +- testdata/default/repros/Issue3110.t.sol | 7 +- testdata/default/repros/Issue3119.t.sol | 7 +- testdata/default/repros/Issue3189.t.sol | 32 - testdata/default/repros/Issue3190.t.sol | 8 +- testdata/default/repros/Issue3192.t.sol | 6 +- testdata/default/repros/Issue3220.t.sol | 6 +- testdata/default/repros/Issue3221.t.sol | 6 +- testdata/default/repros/Issue3223.t.sol | 7 +- testdata/default/repros/Issue3347.t.sol | 14 - testdata/default/repros/Issue3596.t.sol | 31 - testdata/default/repros/Issue3653.t.sol | 6 +- testdata/default/repros/Issue3661.t.sol | 4 +- testdata/default/repros/Issue3674.t.sol | 10 +- testdata/default/repros/Issue3685.t.sol | 7 +- testdata/default/repros/Issue3703.t.sol | 9 +- testdata/default/repros/Issue3708.t.sol | 6 +- testdata/default/repros/Issue3753.t.sol | 7 +- testdata/default/repros/Issue3792.t.sol | 4 +- testdata/default/repros/Issue4232.t.sol | 7 +- testdata/default/repros/Issue4402.t.sol | 7 +- testdata/default/repros/Issue4586.t.sol | 10 +- testdata/default/repros/Issue4630.t.sol | 7 +- testdata/default/repros/Issue4640.t.sol | 7 +- testdata/default/repros/Issue5038.t.sol | 7 +- testdata/default/repros/Issue5529.t.sol | 16 +- testdata/default/repros/Issue5739.t.sol | 6 +- testdata/default/repros/Issue5808.t.sol | 7 +- testdata/default/repros/Issue5929.t.sol | 7 +- testdata/default/repros/Issue5935.t.sol | 7 +- testdata/default/repros/Issue5948.t.sol | 7 +- testdata/default/repros/Issue6006.t.sol | 7 +- testdata/default/repros/Issue6032.t.sol | 7 +- testdata/default/repros/Issue6070.t.sol | 7 +- testdata/default/repros/Issue6115.t.sol | 4 +- testdata/default/repros/Issue6170.t.sol | 28 - testdata/default/repros/Issue6180.t.sol | 7 +- testdata/default/repros/Issue6293.t.sol | 7 +- testdata/default/repros/Issue6355.t.sol | 39 - testdata/default/repros/Issue6437.t.sol | 7 +- testdata/default/repros/Issue6501.t.sol | 14 - testdata/default/repros/Issue6538.t.sol | 7 +- testdata/default/repros/Issue6554.t.sol | 11 +- testdata/default/repros/Issue6616.t.sol | 7 +- testdata/default/repros/Issue6634.t.sol | 9 +- testdata/default/repros/Issue6643.t.sol | 6 +- testdata/default/repros/Issue6759.t.sol | 7 +- testdata/default/repros/Issue6966.t.sol | 4 +- testdata/default/repros/Issue7238.t.sol | 7 +- testdata/default/repros/Issue7457.t.sol | 7 +- testdata/default/repros/Issue7481.t.sol | 7 +- testdata/default/repros/Issue8004.t.sol | 12 +- testdata/default/repros/Issue8006.t.sol | 6 +- testdata/default/repros/Issue8168.t.sol | 7 +- testdata/default/repros/Issue8277.t.sol | 7 +- testdata/default/repros/Issue8287.t.sol | 7 +- testdata/default/repros/Issue8566.t.sol | 7 +- testdata/default/repros/Issue8639.t.sol | 10 +- testdata/default/repros/Issue8971.t.sol | 6 +- testdata/default/repros/Issue9643.t.sol | 9 +- testdata/default/spec/ShanghaiCompat.t.sol | 7 +- .../default/trace/ConflictingSignatures.t.sol | 41 - testdata/default/trace/Trace.t.sol | 98 -- testdata/default/vyper/CounterTest.vy | 4 +- testdata/fixtures/File/ignored/.gitignore | 1 + testdata/foundry.toml | 56 +- testdata/multi-version/Counter.sol | 2 + testdata/multi-version/cheats/GetCode.t.sol | 7 +- testdata/multi-version/cheats/GetCode17.t.sol | 7 +- testdata/paris/cheats/Fork.t.sol | 6 +- testdata/paris/cheats/GasSnapshots.t.sol | 23 +- testdata/paris/cheats/LastCallGas.t.sol | 7 +- testdata/paris/core/BeforeTest.t.sol | 22 +- testdata/paris/fork/Transact.t.sol | 8 +- testdata/paris/spec/ShanghaiCompat.t.sol | 7 +- testdata/{default/vyper => src}/Counter.vy | 0 testdata/{default/vyper => src}/ICounter.vyi | 0 .../ds-test/src/test.sol => utils/DSTest.sol} | 0 testdata/utils/Test.sol | 11 + testdata/{cheats => utils}/Vm.sol | 0 testdata/{default/logs => utils}/console.sol | 0 286 files changed, 5645 insertions(+), 6884 deletions(-) create mode 100644 crates/forge/tests/cli/test_cmd/core.rs create mode 100644 crates/forge/tests/cli/test_cmd/fuzz.rs create mode 100644 crates/forge/tests/cli/test_cmd/invariant/common.rs rename crates/forge/tests/{it/invariant.rs => cli/test_cmd/invariant/mod.rs} (54%) rename testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol => crates/forge/tests/cli/test_cmd/invariant/storage.rs (79%) create mode 100644 crates/forge/tests/cli/test_cmd/invariant/target.rs create mode 100644 crates/forge/tests/cli/test_cmd/logs.rs rename crates/forge/tests/cli/{test_cmd.rs => test_cmd/mod.rs} (98%) rename testdata/default/repros/Issue8383.t.sol => crates/forge/tests/cli/test_cmd/repros.rs (57%) rename crates/forge/tests/{it => cli/test_cmd}/spec.rs (93%) rename crates/forge/tests/{it => cli/test_cmd}/table.rs (100%) create mode 100644 crates/forge/tests/cli/test_cmd/trace.rs delete mode 100644 crates/forge/tests/it/cheats.rs delete mode 100644 crates/forge/tests/it/config.rs delete mode 100644 crates/forge/tests/it/core.rs delete mode 100644 crates/forge/tests/it/fork.rs delete mode 100644 crates/forge/tests/it/fs.rs delete mode 100644 crates/forge/tests/it/fuzz.rs delete mode 100644 crates/forge/tests/it/inline.rs delete mode 100644 crates/forge/tests/it/main.rs delete mode 100644 crates/forge/tests/it/repros.rs delete mode 100644 crates/forge/tests/it/test_helpers.rs delete mode 100644 crates/forge/tests/it/vyper.rs delete mode 100644 testdata/default/core/FailingTestAfterFailedSetup.t.sol delete mode 100644 testdata/default/core/LegacyAssertions.t.sol delete mode 100644 testdata/default/core/PaymentFailure.t.sol rename testdata/default/fs/{Default.t.sol => ReadOnly.sol} (68%) delete mode 100644 testdata/default/fuzz/Fuzz.t.sol delete mode 100644 testdata/default/fuzz/FuzzCollection.t.sol delete mode 100644 testdata/default/fuzz/FuzzFailurePersist.t.sol delete mode 100644 testdata/default/fuzz/FuzzInt.t.sol delete mode 100644 testdata/default/fuzz/FuzzPositive.t.sol delete mode 100644 testdata/default/fuzz/FuzzUint.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantAssume.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantTest1.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol delete mode 100644 testdata/default/logs/DebugLogs.t.sol delete mode 100644 testdata/default/logs/HardhatLogs.t.sol delete mode 100644 testdata/default/repros/Issue2851.t.sol delete mode 100644 testdata/default/repros/Issue3055.t.sol delete mode 100644 testdata/default/repros/Issue3189.t.sol delete mode 100644 testdata/default/repros/Issue3347.t.sol delete mode 100644 testdata/default/repros/Issue3596.t.sol delete mode 100644 testdata/default/repros/Issue6170.t.sol delete mode 100644 testdata/default/repros/Issue6355.t.sol delete mode 100644 testdata/default/repros/Issue6501.t.sol delete mode 100644 testdata/default/trace/ConflictingSignatures.t.sol delete mode 100644 testdata/default/trace/Trace.t.sol create mode 100644 testdata/fixtures/File/ignored/.gitignore rename testdata/{default/vyper => src}/Counter.vy (100%) rename testdata/{default/vyper => src}/ICounter.vyi (100%) rename testdata/{lib/ds-test/src/test.sol => utils/DSTest.sol} (100%) create mode 100644 testdata/utils/Test.sol rename testdata/{cheats => utils}/Vm.sol (100%) rename testdata/{default/logs => utils}/console.sol (100%) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index 3d7ef42941deb..272e1c7fcb8b4 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -98,7 +98,10 @@ jobs: ~/.config/.foundry/cache testdata/cache testdata/out - key: ${{ runner.os }}-foundry-${{ matrix.name }} + # Use a unique key for each run to always update the cache. + key: foundry-${{ matrix.name }}-${{ github.run_id }} + restore-keys: | + foundry-${{ matrix.name }}- - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - name: Setup Git config run: | diff --git a/Cargo.lock b/Cargo.lock index 61d92544e9846..b6f44ac1c26a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,9 +70,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0dd3ed764953a6b20458b2b7abbfdc93d20d14b38babe1a70fe631a443a9f1" +checksum = "b9b151e38e42f1586a01369ec52a6934702731d07e8509a7307331b09f6c46dc" dependencies = [ "alloy-eips", "alloy-primitives", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9556182afa73cddffa91e64a5aa9508d5e8c912b3a15f26998d2388a824d2c7b" +checksum = "6e2d5e8668ef6215efdb7dcca6f22277b4e483a5650e05f5de22b2350971f4b8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19d7092c96defc3d132ee0d8969ca1b79ef512b5eda5c66e3065266b253adf2" +checksum = "630288cf4f3a34a8c6bc75c03dce1dbd47833138f65f37d53a1661eafc96b83f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21b782483672d41c62e27973102276060f9526badf3a260adea8cdf4adc049e" +checksum = "15a5ec61206c5b2113bd79b0690395a456ef542d63b596c661b6aaf402f4a34d" dependencies = [ "alloy-primitives", "alloy-serde", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fa99b538ca7006b0c03cfed24ec6d82beda67aac857ef4714be24231d15e6" +checksum = "e5434834adaf64fa20a6fb90877bc1d33214c41b055cc49f82189c98614368cc" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a00efb95ebca2feb59b53930b295f853aef4e0642e865610f3d824d2727ca74" +checksum = "23e7b71e8963a7920dff8c1d4380ea275b3b37c5abde1fc8ea501cd2bffb159b" dependencies = [ "alloy-contract", "alloy-primitives", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a272533715aefc900f89d51db00c96e6fd4f517ea081a12fea482a352c8c815c" +checksum = "919a8471cfbed7bcd8cf1197a57dda583ce0e10c6385f6ff4e8b41304b223392" dependencies = [ "alloy-eips", "alloy-primitives", @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91676d242c0ced99c0dd6d0096d7337babe9457cc43407d26aa6367fcf90553" +checksum = "d7c69f6c9c68a1287c9d5ff903d0010726934de0dac10989be37b75a29190d55" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f82150116b30ba92f588b87f08fa97a46a1bd5ffc0d0597efdf0843d36bfda" +checksum = "8eaf2ae05219e73e0979cb2cf55612aafbab191d130f203079805eaf881cca58" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223612259a080160ce839a4e5df0125ca403a1d5e7206cc911cea54af5d769aa" +checksum = "e58f4f345cef483eab7374f2b6056973c7419ffe8ad35e994b7a7f5d8e0c7ba4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7283b81b6f136100b152e699171bc7ed8184a58802accbc91a7df4ebb944445" +checksum = "de2597751539b1cc8fe4204e5325f9a9ed83fcacfb212018dfcfa7877e76de21" dependencies = [ "alloy-chains", "alloy-consensus", @@ -458,9 +458,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee7e3d343814ec0dfea69bd1820042a133a9d0b9ac5faf1e6eb133b43366315" +checksum = "06e45a68423e732900a0c824b8e22237db461b79d2e472dd68b7547c16104427" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -502,9 +502,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1154b12d470bef59951c62676e106f4ce5de73b987d86b9faa935acebb138ded" +checksum = "edf8eb8be597cfa8c312934d2566ec4516f066d69164f9212d7a148979fdcfd8" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ab76bf97648a1c6ad8fb00f0d594618942b5a9e008afbfb5c8a8fca800d574" +checksum = "339af7336571dd39ae3a15bde08ae6a647e62f75350bd415832640268af92c06" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456cfc2c1677260edbd7ce3eddb7de419cb46de0e9826c43401f42b0286a779a" +checksum = "83d98fb386a462e143f5efa64350860af39950c49e7c0cbdba419c16793116ef" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -556,9 +556,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cc57ee0c1ac9fb14854195fc249494da7416591dc4a4d981ddfd5dd93b9bce" +checksum = "fbde0801a32d21c5f111f037bee7e22874836fba7add34ed4a6919932dd7cf23" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa4edd92c3124ec19b9d572dc7923d070fe5c2efb677519214affd6156a4463" +checksum = "55c8d51ebb7c5fa8be8ea739a3933c5bfea08777d2d662b30b2109ac5ca71e6b" dependencies = [ "alloy-eips", "alloy-primitives", @@ -583,9 +583,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ac29dd005c33e3f7e09087accc80843315303685c3f7a1b888002cd27785b" +checksum = "388cf910e66bd4f309a81ef746dcf8f9bca2226e3577890a8d56c5839225cf46" dependencies = [ "alloy-primitives", "derive_more", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9d173854879bcf26c7d71c1c3911972a3314df526f4349ffe488e676af577d" +checksum = "605ec375d91073851f566a3082548af69a28dca831b27a8be7c1b4c49f5c6ca2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7d47bca1a2a1541e4404aa38b7e262bb4dffd9ac23b4f178729a4ddc5a5caa" +checksum = "361cd87ead4ba7659bda8127902eda92d17fa7ceb18aba1676f7be10f7222487" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -636,9 +636,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c331c8e48665607682e8a9549a2347c13674d4fbcbdc342e7032834eba2424f4" +checksum = "de4e95fb0572b97b17751d0fdf5cdc42b0050f9dd9459eddd1bf2e2fbfed0a33" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -650,9 +650,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2f66afe1e76ca4485e593980056f061b2bdae2055486a062fca050ff111a52" +checksum = "cddde1bbd4feeb0d363ae7882af1e2e7955ef77c17f933f31402aad9343b57c5" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -662,9 +662,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8468f1a7f9ee3bae73c24eead0239abea720dbf7779384b9c7e20d51bfb6b0" +checksum = "64600fc6c312b7e0ba76f73a381059af044f4f21f43e07f51f1fa76c868fe302" dependencies = [ "alloy-primitives", "serde", @@ -673,9 +673,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33387c90b0a5021f45a5a77c2ce6c49b8f6980e66a318181468fb24cea771670" +checksum = "5772858492b26f780468ae693405f895d6a27dea6e3eab2c36b6217de47c2647" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -690,9 +690,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bf90f2355769ad93f790b930434b8d3d2948317f3e484de458010409024462" +checksum = "66acf5f8745dd935e94855aada39d83b555112872321d9293748424de144897e" dependencies = [ "alloy-consensus", "alloy-network", @@ -709,9 +709,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c768277bfc541a7aab3c3a079d838b3925b6c2f367e29be943f002ecde2712" +checksum = "0ccf849fbbdbd3657c0fd07e5654b8880f25bdcb325424edde5e1a4a77b48816" dependencies = [ "alloy-consensus", "alloy-network", @@ -727,9 +727,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccf703581d2c0b2dd2d5bd235de2b5ccfd6bdc43e750ac767327fe0fb0b4ea1" +checksum = "4599a95670313c028b1f69c425deee72c26f2c4911713eb49a4d5faf9eb67c29" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -747,9 +747,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55d9e795c85e36dcea08786d2e7ae9b73cb554b6bea6ac4c212def24e1b4d03" +checksum = "f4195b803d0a992d8dbaab2ca1986fc86533d4bc80967c0cce7668b26ad99ef9" dependencies = [ "alloy-consensus", "alloy-network", @@ -767,9 +767,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675184c6682378f32ce8fc8429f91e93f72098b8a73af6a9447549b6606241ea" +checksum = "f9985b3afacb904655814a47816cc3e1dc8819753b7896ee7bef7e8c66f8e697" dependencies = [ "alloy-consensus", "alloy-network", @@ -857,9 +857,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702002659778d89a94cd4ff2044f6b505460df6c162e2f47d1857573845b0ace" +checksum = "025a940182bddaeb594c26fe3728525ae262d0806fe6a4befdf5d7bc13d54bce" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -881,9 +881,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6bdc0830e5e8f08a4c70a4c791d400a86679c694a3b4b986caf26fad680438" +checksum = "e3b5064d1e1e1aabc918b5954e7fb8154c39e77ec6903a581b973198b26628fa" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ce41d99a32346f354725fe62eadd271cdbae45fe6b3cc40cb054e0bf763112" +checksum = "d47962f3f1d9276646485458dc842b4e35675f42111c9d814ae4711c664c8300" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686219dcef201655763bd3d4eabe42388d9368bfbf6f1c8016d14e739ec53aac" +checksum = "9476a36a34e2fb51b6746d009c53d309a186a825aa95435407f0e07149f4ad2d" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -950,9 +950,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bf39928a5e70c9755d6811a2928131b53ba785ad37c8bf85c90175b5d43b818" +checksum = "f8e52276fdb553d3c11563afad2898f4085165e4093604afe3d78b69afbf408f" dependencies = [ "alloy-primitives", "darling 0.21.3", @@ -3573,7 +3573,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3832,7 +3832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6731,7 +6731,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8505,7 +8505,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9742,7 +9742,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.1.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9762,7 +9762,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11035,7 +11035,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bda08cf270a9d..269c294f25b83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -236,30 +236,30 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## alloy -alloy-consensus = { version = "1.0.38", default-features = false } -alloy-contract = { version = "1.0.38", default-features = false } -alloy-eips = { version = "1.0.38", default-features = false } -alloy-eip5792 = { version = "1.0.38", default-features = false } -alloy-ens = { version = "1.0.38", default-features = false } -alloy-genesis = { version = "1.0.38", default-features = false } -alloy-json-rpc = { version = "1.0.38", default-features = false } -alloy-network = { version = "1.0.38", default-features = false } -alloy-provider = { version = "1.0.38", default-features = false } -alloy-pubsub = { version = "1.0.38", default-features = false } -alloy-rpc-client = { version = "1.0.38", default-features = false } -alloy-rpc-types = { version = "1.0.38", default-features = true } -alloy-rpc-types-beacon = { version = "1.0.38", default-features = true } -alloy-serde = { version = "1.0.38", default-features = false } -alloy-signer = { version = "1.0.38", default-features = false } -alloy-signer-aws = { version = "1.0.38", default-features = false } -alloy-signer-gcp = { version = "1.0.38", default-features = false } -alloy-signer-ledger = { version = "1.0.38", default-features = false } -alloy-signer-local = { version = "1.0.38", default-features = false } -alloy-signer-trezor = { version = "1.0.38", default-features = false } -alloy-transport = { version = "1.0.38", default-features = false } -alloy-transport-http = { version = "1.0.38", default-features = false } -alloy-transport-ipc = { version = "1.0.38", default-features = false } -alloy-transport-ws = { version = "1.0.38", default-features = false } +alloy-consensus = { version = "1.0.41", default-features = false } +alloy-contract = { version = "1.0.41", default-features = false } +alloy-eips = { version = "1.0.41", default-features = false } +alloy-eip5792 = { version = "1.0.41", default-features = false } +alloy-ens = { version = "1.0.41", default-features = false } +alloy-genesis = { version = "1.0.41", default-features = false } +alloy-json-rpc = { version = "1.0.41", default-features = false } +alloy-network = { version = "1.0.41", default-features = false } +alloy-provider = { version = "1.0.41", default-features = false } +alloy-pubsub = { version = "1.0.41", default-features = false } +alloy-rpc-client = { version = "1.0.41", default-features = false } +alloy-rpc-types = { version = "1.0.41", default-features = true } +alloy-rpc-types-beacon = { version = "1.0.41", default-features = true } +alloy-serde = { version = "1.0.41", default-features = false } +alloy-signer = { version = "1.0.41", default-features = false } +alloy-signer-aws = { version = "1.0.41", default-features = false } +alloy-signer-gcp = { version = "1.0.41", default-features = false } +alloy-signer-ledger = { version = "1.0.41", default-features = false } +alloy-signer-local = { version = "1.0.41", default-features = false } +alloy-signer-trezor = { version = "1.0.41", default-features = false } +alloy-transport = { version = "1.0.41", default-features = false } +alloy-transport-http = { version = "1.0.41", default-features = false } +alloy-transport-ipc = { version = "1.0.41", default-features = false } +alloy-transport-ws = { version = "1.0.41", default-features = false } alloy-hardforks = { version = "0.3.2", default-features = false } alloy-op-hardforks = { version = "0.3.2", default-features = false } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 64ca56809e0d5..72e3724ce1d5e 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1520,7 +1520,7 @@ impl Backend { /// - `disable_eip3607` is set to `true` /// - `disable_base_fee` is set to `true` /// - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap - /// - `nonce` check is skipped if `request.nonce` is None + /// - `nonce` check is skipped fn build_call_env( &self, request: WithOtherFields, @@ -1571,6 +1571,9 @@ impl Backend { // - tracing env.evm_env.cfg_env.disable_base_fee = true; + // Disable nonce check in revm + env.evm_env.cfg_env.disable_nonce_check = true; + let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| { self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE) }); @@ -1608,9 +1611,6 @@ impl Backend { if let Some(nonce) = nonce { env.tx.base.nonce = nonce; - } else { - // Disable nonce check in revm - env.evm_env.cfg_env.disable_nonce_check = true; } if env.evm_env.block_env.basefee == 0 { @@ -1909,8 +1909,9 @@ impl Backend { block_request: Option, opts: GethDebugTracingCallOptions, ) -> Result { - let GethDebugTracingCallOptions { tracing_options, block_overrides, state_overrides } = - opts; + let GethDebugTracingCallOptions { + tracing_options, block_overrides, state_overrides, .. + } = opts; let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; self.with_database_at(block_request, |state, mut block| { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 5ee5c03dea2b6..29e968aeb5bc6 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -117,8 +117,7 @@ mod tests { #[cfg(feature = "schema")] const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json"); - const IFACE_PATH: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/cheats/Vm.sol"); + const IFACE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/utils/Vm.sol"); /// Generates the `cheatcodes.json` file contents. fn json_cheatcodes() -> String { diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs index 8fce772a4ef69..576f36eab02f3 100644 --- a/crates/chisel/src/source.rs +++ b/crates/chisel/src/source.rs @@ -26,7 +26,7 @@ use walkdir::WalkDir; pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); /// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) -static VM_SOURCE: &str = include_str!("../../../testdata/cheats/Vm.sol"); +static VM_SOURCE: &str = include_str!("../../../testdata/utils/Vm.sol"); /// [`SessionSource`] build output. pub struct GeneratedOutput { diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 090f885e708a3..4cf3358f24400 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -5,7 +5,7 @@ use flate2::{Compression, read::GzDecoder, write::GzEncoder}; use serde::{Serialize, de::DeserializeOwned}; use std::{ fs::{self, File}, - io::{BufReader, BufWriter, Read, Write}, + io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, path::{Component, Path, PathBuf}, }; @@ -62,25 +62,29 @@ pub fn read_json_gzip_file(path: &Path) -> Result { /// Reads the entire contents of a locked shared file into a string. pub fn locked_read_to_string(path: impl AsRef) -> Result { let path = path.as_ref(); - let file = + let mut file = fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?; file.lock_shared().map_err(|err| FsPathError::lock(err, path))?; - let mut contents = String::new(); - (&file).read_to_string(&mut contents).map_err(|err| FsPathError::read(err, path))?; + let contents = read_inner(path, &mut file)?; file.unlock().map_err(|err| FsPathError::unlock(err, path))?; - Ok(contents) + String::from_utf8(contents).map_err(|err| FsPathError::read(std::io::Error::other(err), path)) } /// Reads the entire contents of a locked shared file into a bytes vector. pub fn locked_read(path: impl AsRef) -> Result> { let path = path.as_ref(); - let file = + let mut file = fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?; file.lock_shared().map_err(|err| FsPathError::lock(err, path))?; + let contents = read_inner(path, &mut file)?; + file.unlock().map_err(|err| FsPathError::unlock(err, path))?; + Ok(contents) +} + +fn read_inner(path: &Path, file: &mut File) -> Result> { let file_len = file.metadata().map_err(|err| FsPathError::open(err, path))?.len() as usize; let mut buffer = Vec::with_capacity(file_len); - (&file).read_to_end(&mut buffer).map_err(|err| FsPathError::read(err, path))?; - file.unlock().map_err(|err| FsPathError::unlock(err, path))?; + file.read_to_end(&mut buffer).map_err(|err| FsPathError::read(err, path))?; Ok(buffer) } @@ -136,18 +140,39 @@ pub fn locked_write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Resul } /// Writes a line in an exclusive locked file. -pub fn locked_write_line(path: impl AsRef, line: &String) -> Result<()> { +pub fn locked_write_line(path: impl AsRef, line: &str) -> Result<()> { let path = path.as_ref(); + if cfg!(windows) { + return locked_write_line_windows(path, line); + } + let mut file = std::fs::OpenOptions::new() .append(true) .create(true) .open(path) .map_err(|err| FsPathError::open(err, path))?; + file.lock().map_err(|err| FsPathError::lock(err, path))?; writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?; file.unlock().map_err(|err| FsPathError::unlock(err, path)) } +// Locking fails on Windows if the file is opened in append mode. +fn locked_write_line_windows(path: &Path, line: &str) -> Result<()> { + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(path) + .map_err(|err| FsPathError::open(err, path))?; + file.lock().map_err(|err| FsPathError::lock(err, path))?; + + file.seek(SeekFrom::End(0)).map_err(|err| FsPathError::write(err, path))?; + writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?; + + file.unlock().map_err(|err| FsPathError::unlock(err, path)) +} + /// Wrapper for `std::fs::copy` pub fn copy(from: impl AsRef, to: impl AsRef) -> Result { let from = from.as_ref(); diff --git a/crates/forge/tests/cli/test_cmd/core.rs b/crates/forge/tests/cli/test_cmd/core.rs new file mode 100644 index 0000000000000..fffb6394e4779 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/core.rs @@ -0,0 +1,142 @@ +//! Core test functionality tests + +use foundry_test_utils::str; + +forgetest_init!(failing_test_after_failed_setup, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "FailingTestAfterFailedSetup.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FailingTestAfterFailedSetupTest is Test { + function setUp() public { + assertTrue(false); + } + + function testAssertSuccess() public { + assertTrue(true); + } + + function testAssertFailure() public { + assertTrue(false); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest +[FAIL: assertion failed] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest +[FAIL: assertion failed] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(legacy_assertions, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "LegacyAssertions.t.sol", + r#" +import "forge-std/Test.sol"; + +contract NoAssertionsRevertTest is Test { + function testMultipleAssertFailures() public { + vm.assertEq(uint256(1), uint256(2)); + vm.assertLt(uint256(5), uint256(4)); + } +} + +/// forge-config: default.legacy_assertions = true +contract LegacyAssertionsTest { + bool public failed; + + function testFlagNotSetSuccess() public {} + + function testFlagSetFailure() public { + failed = true; + } +} +"#, + ); + + cmd.args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/LegacyAssertions.t.sol:LegacyAssertionsTest +[PASS] testFlagNotSetSuccess() ([GAS]) +[FAIL] testFlagSetFailure() ([GAS]) +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/LegacyAssertions.t.sol:NoAssertionsRevertTest +[FAIL: assertion failed: 1 != 2] testMultipleAssertFailures() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 1 failing test in test/LegacyAssertions.t.sol:LegacyAssertionsTest +[FAIL] testFlagSetFailure() ([GAS]) + +Encountered 1 failing test in test/LegacyAssertions.t.sol:NoAssertionsRevertTest +[FAIL: assertion failed: 1 != 2] testMultipleAssertFailures() ([GAS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +forgetest_init!(payment_failure, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "PaymentFailure.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Payable { + function pay() public payable {} +} + +contract PaymentFailureTest is Test { + function testCantPay() public { + Payable target = new Payable(); + vm.prank(address(1)); + target.pay{value: 1}(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/PaymentFailure.t.sol:PaymentFailureTest +[FAIL: EvmError: Revert] testCantPay() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/PaymentFailure.t.sol:PaymentFailureTest +[FAIL: EvmError: Revert] testCantPay() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs new file mode 100644 index 0000000000000..aaa8fe687d369 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -0,0 +1,735 @@ +use alloy_primitives::U256; +use foundry_test_utils::{TestCommand, forgetest_init, str}; +use regex::Regex; + +forgetest_init!(test_can_scrape_bytecode, |prj, cmd| { + prj.update_config(|config| config.optimizer = Some(true)); + prj.add_source( + "FuzzerDict.sol", + r#" +// https://github.com/foundry-rs/foundry/issues/1168 +contract FuzzerDict { + // Immutables should get added to the dictionary. + address public immutable immutableOwner; + // Regular storage variables should also get added to the dictionary. + address public storageOwner; + + constructor(address _immutableOwner, address _storageOwner) { + immutableOwner = _immutableOwner; + storageOwner = _storageOwner; + } +} + "#, + ); + + prj.add_test( + "FuzzerDictTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/FuzzerDict.sol"; + +contract FuzzerDictTest is Test { + FuzzerDict fuzzerDict; + + function setUp() public { + fuzzerDict = new FuzzerDict(address(100), address(200)); + } + + /// forge-config: default.fuzz.runs = 2000 + function testImmutableOwner(address who) public { + assertTrue(who != fuzzerDict.immutableOwner()); + } + + /// forge-config: default.fuzz.runs = 2000 + function testStorageOwner(address who) public { + assertTrue(who != fuzzerDict.storageOwner()); + } +} + "#, + ); + + // Test that immutable address is used as fuzzed input, causing test to fail. + cmd.args(["test", "--fuzz-seed", "119", "--mt", "testImmutableOwner"]).assert_failure(); + // Test that storage address is used as fuzzed input, causing test to fail. + cmd.forge_fuse() + .args(["test", "--fuzz-seed", "119", "--mt", "testStorageOwner"]) + .assert_failure(); +}); + +// tests that inline max-test-rejects config is properly applied +forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InlineMaxRejectsTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 1 + function test_fuzz_bound(uint256 a) public { + vm.assume(false); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] test_fuzz_bound(uint256) (runs: 0, [AVG_GAS]) +... +"#]]); +}); + +// Tests that test timeout config is properly applied. +// If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. +forgetest_init!(test_fuzz_timeout, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzTimeoutTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 50000 + /// forge-config: default.fuzz.timeout = 1 + function test_fuzz_bound(uint256 a) public pure { + vm.assume(a == 0); + } +} + "#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:FuzzTimeoutTest +[PASS] test_fuzz_bound(uint256) (runs: [..], [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(test_fuzz_fail_on_revert, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| config.fuzz.fail_on_revert = false); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + require(number > 10000000000, "low number"); + number = newNumber; + } +} + "#, + ); + + prj.add_test( + "CounterTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testFuzz_SetNumberRequire(uint256 x) public { + counter.setNumber(x); + require(counter.number() == 1); + } + + function testFuzz_SetNumberAssert(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), 1); + } +} + "#, + ); + + // Tests should not fail as revert happens in Counter contract. + cmd.args(["test", "--mc", "CounterTest"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/CounterTest.t.sol:CounterTest +[PASS] testFuzz_SetNumberAssert(uint256) (runs: 256, [AVG_GAS]) +[PASS] testFuzz_SetNumberRequire(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + + // Tested contract does not revert. + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + "#, + ); + + // Tests should fail as revert happens in cheatcode (assert) and test (require) contract. + cmd.assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/CounterTest.t.sol:CounterTest +[FAIL: assertion failed: [..]] testFuzz_SetNumberAssert(uint256) (runs: 0, [AVG_GAS]) +[FAIL: EvmError: Revert; [..]] testFuzz_SetNumberRequire(uint256) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] +... + +"#]]); +}); + +// Test 256 runs regardless number of test rejects. +// +forgetest_init!(test_fuzz_runs_with_rejects, |prj, cmd| { + prj.add_test( + "FuzzWithRejectsTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzWithRejectsTest is Test { + function testFuzzWithRejects(uint256 x) public pure { + vm.assume(x < 1_000_000); + } +} + "#, + ); + + // Tests should not fail as revert happens in Counter contract. + cmd.args(["test", "--mc", "FuzzWithRejectsTest"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/FuzzWithRejectsTest.t.sol:FuzzWithRejectsTest +[PASS] testFuzzWithRejects(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Test that counterexample is not replayed if test changes. +// +forgetest_init!(test_fuzz_replay_with_changed_test, |prj, cmd| { + prj.update_config(|config| config.fuzz.seed = Some(U256::from(100u32))); + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + require(x > 200); + } +} + "#, + ); + // Tests should fail and record counterexample with value 2. + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d70000000000000000000000000000000000000000000000000000000000000002 args=[2]] testFuzz_SetNumber(uint256) (runs: 19, [AVG_GAS]) +... + +"#]]); + + // Change test to assume counterexample 2 is discarded. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + vm.assume(x != 2); + } +} + "#, + ); + // Test should pass when replay failure with changed assume logic. + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Change test signature. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint8 x) public pure { + } +} + "#, + ); + // Test should pass when replay failure with changed function signature. + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint8) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Change test back to the original one that produced the counterexample. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + require(x > 200); + } +} + "#, + ); + // Test should fail with replayed counterexample 2 (0 runs). + cmd.forge_fuse().args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d70000000000000000000000000000000000000000000000000000000000000002 args=[2]] testFuzz_SetNumber(uint256) (runs: 0, [AVG_GAS]) +... + +"#]]); +}); + +forgetest_init!(fuzz_basic, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Fuzz.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzTest is Test { + constructor() { + emit log("constructor"); + } + + function setUp() public { + emit log("setUp"); + } + + function testShouldFailFuzz(uint8 x) public { + emit log("testFailFuzz"); + require(x > 128, "should revert"); + } + + function testSuccessfulFuzz(uint128 a, uint128 b) public { + emit log("testSuccessfulFuzz"); + assertEq(uint256(a) + uint256(b), uint256(a) + uint256(b)); + } + + function testToStringFuzz(bytes32 data) public { + vm.toString(data); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Fuzz.t.sol:FuzzTest +[FAIL: should revert; counterexample: calldata=[..] args=[..]] testShouldFailFuzz(uint8) (runs: [..], [AVG_GAS]) +[PASS] testSuccessfulFuzz(uint128,uint128) (runs: 256, [AVG_GAS]) +[PASS] testToStringFuzz(bytes32) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 2 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 1 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 1 failing test in test/Fuzz.t.sol:FuzzTest +[FAIL: should revert; counterexample: calldata=[..] args=[..]] testShouldFailFuzz(uint8) (runs: [..], [AVG_GAS]) + +Encountered a total of 1 failing tests, 2 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// Test that showcases PUSH collection on normal fuzzing. +// Ignored until we collect them in a smarter way. +forgetest_init!( + #[ignore] + fuzz_collection, + |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 100; + config.invariant.runs = 1000; + config.fuzz.runs = 1000; + config.fuzz.seed = Some(U256::from(6u32)); + }); + prj.add_test( + "FuzzCollection.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SampleContract { + uint256 public counter; + uint256 public counterX2; + address public owner = address(0xBEEF); + bool public found_needle; + + event Incremented(uint256 counter); + + modifier onlyOwner() { + require(msg.sender == owner, "ONLY_OWNER"); + _; + } + + function compare(uint256 val) public { + if (val == 0x4446) { + found_needle = true; + } + } + + function incrementBy(uint256 numToIncrement) public onlyOwner { + counter += numToIncrement; + counterX2 += numToIncrement * 2; + + emit Incremented(counter); + } + + function breakTheInvariant(uint256 x) public { + if (x == 0x5556) { + counterX2 = 0; + } + } +} + +contract SampleContractTest is Test { + event Incremented(uint256 counter); + + SampleContract public sample; + + function setUp() public { + sample = new SampleContract(); + } + + function testIncrement(address caller) public { + vm.startPrank(address(caller)); + + vm.expectRevert("ONLY_OWNER"); + sample.incrementBy(1); + } + + function testNeedle(uint256 needle) public { + sample.compare(needle); + require(!sample.found_needle(), "needle found."); + } + + function invariantCounter() public { + require(sample.counter() * 2 == sample.counterX2(), "broken counter."); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#""#]]); + } +); + +forgetest_init!(fuzz_failure_persist, |prj, cmd| { + prj.wipe_contracts(); + + let persist_dir = prj.cache().parent().unwrap().join("persist"); + assert!(!persist_dir.exists()); + prj.update_config(|config| { + config.fuzz.failure_persist_dir = Some(persist_dir.clone()); + }); + + prj.add_test( + "FuzzFailurePersist.t.sol", + r#" +import "forge-std/Test.sol"; + +struct TestTuple { + address user; + uint256 amount; +} + +contract FuzzFailurePersistTest is Test { + function test_persist_fuzzed_failure( + uint256 x, + int256 y, + address addr, + bool cond, + string calldata test, + TestTuple calldata tuple, + address[] calldata addresses + ) public { + // dummy assume to trigger runs + vm.assume(x > 1 && x < 1111111111111111111111111111); + vm.assume(y > 1 && y < 1111111111111111111111111111); + require(false); + } +} + "#, + ); + + let mut calldata = None; + let expected = str![[r#" +... +Ran 1 test for test/FuzzFailurePersist.t.sol:FuzzFailurePersistTest +[FAIL: EvmError: Revert; counterexample: calldata=[..] args=[..]] test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[]) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + let mut check = |cmd: &mut TestCommand, same: bool| { + let assert = cmd.assert_failure(); + let output = assert.get_output(); + let stdout = String::from_utf8_lossy(&output.stdout); + let calldata = calldata.get_or_insert_with(|| { + let re = Regex::new(r"calldata=(0x[0-9a-fA-F]+)").unwrap(); + re.captures(&stdout).unwrap().get(1).unwrap().as_str().to_string() + }); + assert_eq!(stdout.contains(calldata.as_str()), same, "\n{stdout}"); + assert.stdout_eq(expected.clone()); + }; + + cmd.arg("test"); + + // Run several times, asserting that the failure persists and is the same. + for _ in 0..3 { + check(&mut cmd, true); + assert!(persist_dir.exists()); + } + + // Change dir and run again, asserting that the failure persists. It should be a new failure. + let new_persist_dir = prj.cache().parent().unwrap().join("persist2"); + assert!(!new_persist_dir.exists()); + prj.update_config(|config| { + config.fuzz.failure_persist_dir = Some(new_persist_dir.clone()); + }); + check(&mut cmd, false); + assert!(new_persist_dir.exists()); +}); + +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined +forgetest_init!(fuzz_int, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "FuzzInt.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzNumbersTest is Test { + function testPositive(int256) public { + assertTrue(true); + } + + function testNegativeHalf(int256 val) public { + assertTrue(val < 2 ** 128 - 1); + } + + function testNegative0(int256 val) public { + assertTrue(val == 0); + } + + function testNegative1(int256 val) public { + assertTrue(val == -1); + } + + function testNegative2(int128 val) public { + assertTrue(val == 1); + } + + function testNegativeMax0(int256 val) public { + assertTrue(val == type(int256).max); + } + + function testNegativeMax1(int256 val) public { + assertTrue(val == type(int256).max - 2); + } + + function testNegativeMin0(int256 val) public { + assertTrue(val == type(int256).min); + } + + function testNegativeMin1(int256 val) public { + assertTrue(val == type(int256).min + 2); + } + + function testEquality(int256 x, int256 y) public { + int256 xy; + + unchecked { + xy = x * y; + } + + if ((x != 0 && xy / x != y)) { + return; + } + + assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 10 tests for test/FuzzInt.t.sol:FuzzNumbersTest +[FAIL: assertion failed[..]] testEquality(int256,int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative1(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2(int128) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeHalf(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax1(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMin0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMin1(int256) (runs: [..], [AVG_GAS]) +[PASS] testPositive(int256) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 1 passed; 9 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 9 failed, 0 skipped (10 total tests) +... +"#]]); +}); + +forgetest_init!(fuzz_positive, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "FuzzPositive.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzPositive is Test { + function testSuccessChecker(uint256 val) public { + assertTrue(true); + } + + function testSuccessChecker2(int256 val) public { + assert(val == val); + } + + function testSuccessChecker3(uint32 val) public { + assert(val + 0 == val); + } +} + "#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 tests for test/FuzzPositive.t.sol:FuzzPositive +[PASS] testSuccessChecker(uint256) (runs: 256, [AVG_GAS]) +[PASS] testSuccessChecker2(int256) (runs: 256, [AVG_GAS]) +[PASS] testSuccessChecker3(uint32) (runs: 256, [AVG_GAS]) +Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined +forgetest_init!(fuzz_uint, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "FuzzUint.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzNumbersTest is Test { + function testPositive(uint256) public { + assertTrue(true); + } + + function testNegativeHalf(uint256 val) public { + assertTrue(val < 2 ** 128 - 1); + } + + function testNegative0(uint256 val) public { + assertTrue(val == 0); + } + + function testNegative2(uint256 val) public { + assertTrue(val == 2); + } + + function testNegative2Max(uint256 val) public { + assertTrue(val == type(uint256).max - 2); + } + + function testNegativeMax(uint256 val) public { + assertTrue(val == type(uint256).max); + } + + function testEquality(uint256 x, uint256 y) public { + uint256 xy; + + unchecked { + xy = x * y; + } + + if ((x != 0 && xy / x != y)) { + return; + } + + assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 7 tests for test/FuzzUint.t.sol:FuzzNumbersTest +[FAIL: assertion failed[..]] testEquality(uint256,uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative0(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2Max(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeHalf(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax(uint256) (runs: [..], [AVG_GAS]) +[PASS] testPositive(uint256) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 1 passed; 6 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs new file mode 100644 index 0000000000000..4e32bf143fb58 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -0,0 +1,1424 @@ +use super::*; + +forgetest!(invariant_after_invariant, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantAfterInvariant.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract AfterInvariantHandler { + uint256 public count; + + function inc() external { + count += 1; + } +} + +contract InvariantAfterInvariantTest is Test { + AfterInvariantHandler handler; + + function setUp() public { + handler = new AfterInvariantHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.inc.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function afterInvariant() public { + require(handler.count() < 10, "afterInvariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_after_invariant_failure() public view { + require(handler.count() < 20, "invariant after invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_failure() public view { + require(handler.count() < 9, "invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + function invariant_success() public view { + require(handler.count() < 11, "invariant should not fail"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest +[FAIL: afterInvariant failure] + [SEQUENCE] + invariant_after_invariant_failure() ([RUNS]) + +[STATS] + +[FAIL: invariant failure] + [SEQUENCE] + invariant_failure() ([RUNS]) + +[STATS] + +[PASS] invariant_success() ([RUNS]) + +[STATS] + +Suite result: FAILED. 1 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 2 failing tests in test/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest +[FAIL: afterInvariant failure] + [SEQUENCE] + invariant_after_invariant_failure() ([RUNS]) +[FAIL: invariant failure] + [SEQUENCE] + invariant_failure() ([RUNS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +forgetest_init!(invariant_assume, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + // Should not treat vm.assume as revert. + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantAssume.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Handler is Test { + function doSomething(uint256 param) public { + vm.assume(param == 0); + } +} + +contract InvariantAssume is Test { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function invariant_dummy() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to pure + [FILE]:7:5: + | +7 | function doSomething(uint256 param) public { + | ^ (Relevant source part starts here and spans across multiple lines). + + +Ran 1 test for test/InvariantAssume.t.sol:InvariantAssume +[PASS] invariant_dummy() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test that max_assume_rejects is respected. + prj.update_config(|config| { + config.invariant.max_assume_rejects = 1; + }); + + assert_invariant(&mut cmd).failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantAssume.t.sol:InvariantAssume +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] invariant_dummy() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantAssume.t.sol:InvariantAssume +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] invariant_dummy() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/5868 +forgetest!(invariant_calldata_dictionary, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantCalldataDictionary.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Owned { + address public owner; + address private ownerCandidate; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier onlyOwnerCandidate() { + require(msg.sender == ownerCandidate); + _; + } + + function transferOwnership(address candidate) external onlyOwner { + ownerCandidate = candidate; + } + + function acceptOwnership() external onlyOwnerCandidate { + owner = ownerCandidate; + } +} + +contract Handler is Test { + Owned owned; + + constructor(Owned _owned) { + owned = _owned; + } + + function transferOwnership(address sender, address candidate) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.transferOwnership(candidate); + } + + function acceptOwnership(address sender) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.acceptOwnership(); + } +} + +contract InvariantCalldataDictionary is Test { + address owner; + Owned owned; + Handler handler; + address[] actors; + + function setUp() public { + owner = address(this); + owned = new Owned(); + handler = new Handler(owned); + actors.push(owner); + actors.push(address(777)); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.transferOwnership.selector; + selectors[1] = handler.acceptOwnership.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function fixtureSender() external returns (address[] memory) { + return actors; + } + + function fixtureCandidate() external returns (address[] memory) { + return actors; + } + + function invariant_owner_never_changes() public { + assertEq(owned.owner(), owner); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary +[FAIL: ] + [SEQUENCE] + invariant_owner_never_changes() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary +[FAIL: ] + [SEQUENCE] + invariant_owner_never_changes() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(invariant_custom_error, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantCustomError.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ContractWithCustomError { + error InvariantCustomError(uint256, string); + + function revertWithInvariantCustomError() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract Handler is Test { + ContractWithCustomError target; + + constructor() { + target = new ContractWithCustomError(); + } + + function revertTarget() external { + target.revertWithInvariantCustomError(); + } +} + +contract InvariantCustomError is Test { + Handler handler; + + function setUp() external { + handler = new Handler(); + } + + function invariant_decode_error() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantCustomError.t.sol:InvariantCustomError +[FAIL: InvariantCustomError(111, "custom")] + [SEQUENCE] + invariant_decode_error() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantCustomError.t.sol:InvariantCustomError +[FAIL: InvariantCustomError(111, "custom")] + [SEQUENCE] + invariant_decode_error() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(invariant_excluded_senders, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantExcludedSenders.t.sol", + r#" +import "forge-std/Test.sol"; + +contract InvariantSenders { + function checkSender() external { + require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); + require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); + require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); + } +} + +contract InvariantExcludedSendersTest is Test { + InvariantSenders target; + + function setUp() public { + target = new InvariantSenders(); + } + + function invariant_check_sender() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to view + [FILE]:7:5: + | +7 | function checkSender() external { + | ^ (Relevant source part starts here and spans across multiple lines). + + +Ran 1 test for test/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest +[PASS] invariant_check_sender() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(invariant_fixtures, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 100; + }); + + prj.add_test( + "InvariantFixtures.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values using fixtures. +contract InvariantFixtures is Test { + Target target; + address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; + uint256[] public fixture_amount = [1, 2, 1122334455]; + + function setUp() public { + target = new Target(); + } + + function fixtureMagic() external returns (int32[2] memory) { + int32[2] memory magic; + magic[0] = -777; + magic[1] = 777; + return magic; + } + + function fixtureKey() external pure returns (bytes32[] memory) { + bytes32[] memory keyFixture = new bytes32[](1); + keyFixture[0] = "abcd1234"; + return keyFixture; + } + + function fixtureBackup() external pure returns (bytes[] memory) { + bytes[] memory backupFixture = new bytes[](1); + backupFixture[0] = "qwerty1234"; + return backupFixture; + } + + function fixtureExtra() external pure returns (string[] memory) { + string[] memory extraFixture = new string[](1); + extraFixture[0] = "112233aabbccdd"; + return extraFixture; + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantFixtures.t.sol:InvariantFixtures +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantFixtures.t.sol:InvariantFixtures +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest!(invariant_handler_failure, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantHandlerFailure.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Handler is Test { + function doSomething() public { + require(false, "failed on revert"); + } +} + +contract InvariantHandlerFailure is Test { + bytes4[] internal selectors; + + Handler handler; + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.doSomething.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function setUp() public { + handler = new Handler(); + } + + function statefulFuzz_BrokenInvariant() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantHandlerFailure.t.sol:InvariantHandlerFailure +[FAIL: failed on revert] + [SEQUENCE] + statefulFuzz_BrokenInvariant() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantHandlerFailure.t.sol:InvariantHandlerFailure +[FAIL: failed on revert] + [SEQUENCE] + statefulFuzz_BrokenInvariant() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// Here we test that the fuzz engine can include a contract created during the fuzz +// in its fuzz dictionary and eventually break the invariant. +// Specifically, can Judas, a created contract from Jesus, break Jesus contract +// by revealing his identity. +forgetest_init!( + #[cfg_attr(windows, ignore = "for some reason there's different rng")] + invariant_inner_contract, + |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantInnerContract.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Jesus { + address fren; + bool public identity_revealed; + + function create_fren() public { + fren = address(new Judas()); + } + + function kiss() public { + require(msg.sender == fren); + identity_revealed = true; + } +} + +contract Judas { + Jesus jesus; + + constructor() { + jesus = Jesus(msg.sender); + } + + function betray() public { + jesus.kiss(); + } +} + +contract InvariantInnerContract is Test { + Jesus jesus; + + function setUp() public { + jesus = new Jesus(); + } + + function invariantHideJesus() public { + require(jesus.identity_revealed() == false, "jesus betrayed"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [SEQUENCE] + invariantHideJesus() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [SEQUENCE] + invariantHideJesus() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); + + // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + // Disable persisted failures for rerunning the test. + config.invariant.failure_persist_dir = Some( + config + .invariant + .failure_persist_dir + .as_ref() + .unwrap() + .parent() + .unwrap() + .join("persistence2"), + ); + }); + cmd.assert_failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [Sequence] (original: 2, shrunk: 2) + sender=[..] addr=[test/InvariantInnerContract.t.sol:Jesus][..] calldata=create_fren() args=[] + sender=[..] addr=[test/InvariantInnerContract.t.sol:Judas][..] calldata=betray() args=[] + invariantHideJesus() (runs: 0, calls: 0, reverts: 1) +... +"#]]); + } +); + +// https://github.com/foundry-rs/foundry/issues/7219 +forgetest!(invariant_preserve_state, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantPreserveState.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Handler is Test { + function thisFunctionReverts() external { + if (block.number < 10) {} else { + revert(); + } + } + + function advanceTime(uint256 blocks) external { + blocks = blocks % 10; + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 12); + } +} + +contract InvariantPreserveState is Test { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.thisFunctionReverts.selector; + selectors[1] = handler.advanceTime.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_preserve_state() public { + assertTrue(true); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantPreserveState.t.sol:InvariantPreserveState +[FAIL: EvmError: Revert] + [SEQUENCE] + invariant_preserve_state() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantPreserveState.t.sol:InvariantPreserveState +[FAIL: EvmError: Revert] + [SEQUENCE] + invariant_preserve_state() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// add code so contract is accounted as valid sender +// see https://github.com/foundry-rs/foundry/issues/4245 +forgetest!(invariant_reentrancy, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); + + prj.add_test( + "InvariantReentrancy.t.sol", + r#" +import "./utils/Test.sol"; + +contract Malicious { + function world() public { + payable(msg.sender).call(""); + } +} + +contract Vulnerable { + bool public open_door = false; + bool public stolen = false; + Malicious mal; + + constructor(address _mal) { + mal = Malicious(_mal); + } + + function hello() public { + open_door = true; + mal.world(); + open_door = false; + } + + function backdoor() public { + require(open_door, ""); + stolen = true; + } +} + +contract InvariantReentrancy is Test { + Vulnerable vuln; + Malicious mal; + + function setUp() public { + mal = new Malicious(); + vuln = new Vulnerable(address(mal)); + } + + // do not include `mal` in identified contracts + // see https://github.com/foundry-rs/foundry/issues/4245 + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(vuln); + return targets; + } + + function invariantNotStolen() public { + require(vuln.stolen() == false, "stolen"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantReentrancy.t.sol:InvariantReentrancy +[FAIL: stolen] + [SEQUENCE] + invariantNotStolen() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantReentrancy.t.sol:InvariantReentrancy +[FAIL: stolen] + [SEQUENCE] + invariantNotStolen() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(invariant_roll_fork, |prj, cmd| { + prj.wipe_contracts(); + prj.add_rpc_endpoints(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + }); + + prj.add_test( + "InvariantRollFork.t.sol", + r#" +import "forge-std/Test.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract RollForkHandler is Test { + uint256 public totalSupply; + + function work() external { + vm.rollFork(block.number + 1); + totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); + } +} + +contract InvariantRollForkBlockTest is Test { + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 2 + /// forge-config: default.invariant.depth = 4 + function invariant_fork_handler_block() public { + require(block.number < 19812634, "too many blocks mined"); + } +} + +contract InvariantRollForkStateTest is Test { + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 1 + function invariant_fork_handler_state() public { + require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkBlockTest +[FAIL: too many blocks mined] + [SEQUENCE] + invariant_fork_handler_block() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkStateTest +[FAIL: wrong supply] + [SEQUENCE] + invariant_fork_handler_state() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkBlockTest +[FAIL: too many blocks mined] + [SEQUENCE] + invariant_fork_handler_block() ([RUNS]) + +Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkStateTest +[FAIL: wrong supply] + [SEQUENCE] + invariant_fork_handler_state() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +forgetest_init!(invariant_scrape_values, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantScrapeValues.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FindFromReturnValue { + bool public found = false; + + function seed() public returns (int256) { + int256 mystery = 13337; + return (1337 + mystery); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromReturnValueTest is Test { + FindFromReturnValue target; + + function setUp() public { + target = new FindFromReturnValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from return found"); + } +} + +contract FindFromLogValue { + event FindFromLog(int256 indexed mystery, bytes32 rand); + + bool public found = false; + + function seed() public { + int256 mystery = 13337; + emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromLogValueTest is Test { + FindFromLogValue target; + + function setUp() public { + target = new FindFromLogValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from logs found"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantScrapeValues.t.sol:FindFromLogValueTest +[FAIL: value from logs found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/InvariantScrapeValues.t.sol:FindFromReturnValueTest +[FAIL: value from return found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantScrapeValues.t.sol:FindFromLogValueTest +[FAIL: value from logs found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +Encountered 1 failing test in test/InvariantScrapeValues.t.sol:FindFromReturnValueTest +[FAIL: value from return found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +forgetest_init!(invariant_sequence_no_reverts, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 15; + config.invariant.fail_on_revert = false; + // Use original counterexample to test sequence len. + config.invariant.shrink_run_limit = 0; + }); + + prj.add_test( + "InvariantSequenceNoReverts.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SequenceNoReverts { + uint256 public count; + + function work(uint256 x) public { + require(x % 2 != 0); + count++; + } +} + +contract SequenceNoRevertsTest is Test { + SequenceNoReverts target; + + function setUp() public { + target = new SequenceNoReverts(); + } + + function invariant_no_reverts() public view { + require(target.count() < 10, "condition met"); + } +} +"#, + ); + + // ensure original counterexample len is 10 (even without shrinking) + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantSequenceNoReverts.t.sol:SequenceNoRevertsTest +[FAIL: condition met] + [Sequence] (original: 10, shrunk: 10) +... + invariant_no_reverts() ([..]) +... +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +forgetest_init!( + #[cfg_attr(windows, ignore = "for some reason there's different rng")] + invariant_shrink_big_sequence, + |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 1000; + }); + + prj.add_test( + "InvariantShrinkBigSequence.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ShrinkBigSequence { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + } + + function checkCond() public view { + require(cond < 77, "condition met"); + } +} + +contract ShrinkBigSequenceTest is Test { + ShrinkBigSequence target; + + function setUp() public { + target = new ShrinkBigSequence(); + } + + function invariant_shrink_big_sequence() public view { + target.checkCond(); + } +} +"#, + ); + + // ensure shrinks to same sequence of 77 + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest +[FAIL: condition met] + [Sequence] (original: [..], shrunk: 77) +... +"#]]); + cmd.assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest +[FAIL: invariant_shrink_big_sequence replay failure] + [Sequence] (original: [..], shrunk: 77) +... +"#]]); + } +); + +forgetest_init!(invariant_shrink_fail_on_revert, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 200; + }); + + prj.add_test( + "InvariantShrinkFailOnRevert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ShrinkFailOnRevert { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + require(cond < 10, "condition met"); + } +} + +contract ShrinkFailOnRevertTest is Test { + ShrinkFailOnRevert target; + + function setUp() public { + target = new ShrinkFailOnRevert(); + } + + function invariant_shrink_fail_on_revert() public view {} +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkFailOnRevert.t.sol:ShrinkFailOnRevertTest +[FAIL: condition met] + [Sequence] (original: [..], shrunk: 10) +... +"#]]); +}); + +forgetest_init!(invariant_shrink_with_assert, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 1; + config.invariant.depth = 15; + }); + + prj.add_test( + "InvariantShrinkWithAssert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Counter { + uint256 public number; + + function increment() public { + number++; + } + + function decrement() public { + number--; + } +} + +contract InvariantShrinkWithAssert is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_with_assert() public { + assertTrue(counter.number() < 2, "wrong counter assert"); + } + + function invariant_with_require() public { + require(counter.number() < 2, "wrong counter require"); + } +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithAssert +[FAIL: wrong counter assert] + [Sequence] (original: 2, shrunk: 2) +... + invariant_with_assert() ([..]) +... +[FAIL: wrong counter require] + [Sequence] (original: 2, shrunk: 2) +... + invariant_with_require() ([..]) +... +"#]]); +}); + +forgetest_init!(invariant_test1, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantTest1.t.sol", + r#" +import "forge-std/Test.sol"; + +contract InvariantBreaker { + bool public flag0 = true; + bool public flag1 = true; + + function set0(int256 val) public returns (bool) { + if (val % 100 == 0) { + flag0 = false; + } + return flag0; + } + + function set1(int256 val) public returns (bool) { + if (val % 10 == 0 && !flag0) { + flag1 = false; + } + return flag1; + } +} + +contract InvariantTest is Test { + InvariantBreaker inv; + + function setUp() public { + inv = new InvariantBreaker(); + } + + function invariant_neverFalse() public { + require(inv.flag1(), "false"); + } + + function statefulFuzz_neverFalseWithInvariantAlias() public { + require(inv.flag1(), "false"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/InvariantTest1.t.sol:InvariantTest +[FAIL: false] + [SEQUENCE] + invariant_neverFalse() ([RUNS]) + +[STATS] + +[FAIL: false] + [SEQUENCE] + statefulFuzz_neverFalseWithInvariantAlias() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 2 failing tests in test/InvariantTest1.t.sol:InvariantTest +[FAIL: false] + [SEQUENCE] + invariant_neverFalse() ([RUNS]) +[FAIL: false] + [SEQUENCE] + statefulFuzz_neverFalseWithInvariantAlias() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs similarity index 54% rename from crates/forge/tests/it/invariant.rs rename to crates/forge/tests/cli/test_cmd/invariant/mod.rs index 83ba8660b4457..4e93f00b404b1 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -1,700 +1,16 @@ -//! Invariant tests. - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::U256; -use forge::fuzz::CounterExample; -use foundry_test_utils::{Filter, forgetest_init, str}; -use std::collections::BTreeMap; - -macro_rules! get_counterexample { - ($runner:ident, $filter:expr) => { - $runner - .test_collect($filter) - .unwrap() - .values() - .last() - .expect("Invariant contract should be testable.") - .test_results - .values() - .last() - .expect("Invariant contract should be testable.") - .counterexample - .as_ref() - .expect("Invariant contract should have failed with a counterexample.") - }; -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_with_alias() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantTest1.t.sol"); - let results = TEST_DATA_DEFAULT.runner().test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", - vec![ - ("invariant_neverFalse()", false, Some("false".into()), None, None), - ( - "statefulFuzz_neverFalseWithInvariantAlias()", - false, - Some("false".into()), - None, - None, - ), - ], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_filters() { - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.runs = 10; - }); - - // Contracts filter tests. - assert_multiple( - &runner - .test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/target/(ExcludeContracts|TargetContracts).t.sol", - )) - .unwrap(), - BTreeMap::from([ - ( - "default/fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", - vec![("invariantTrueWorld()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", - vec![("invariantTrueWorld()", true, None, None, None)], - ), - ]), - ); - - // Senders filter tests. - assert_multiple( - &runner - .test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/target/(ExcludeSenders|TargetSenders).t.sol", - )) - .unwrap(), - BTreeMap::from([ - ( - "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", - vec![("invariantTrueWorld()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", - vec![("invariantTrueWorld()", false, Some("false world".into()), None, None)], - ), - ]), - ); - - // Interfaces filter tests. - assert_multiple( - &runner - .test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/target/TargetInterfaces.t.sol", - )) - .unwrap(), - BTreeMap::from([( - "default/fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", - vec![("invariantTrueWorld()", false, Some("false world".into()), None, None)], - )]), - ); - - // Selectors filter tests. - assert_multiple( - &runner - .test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/target/(ExcludeSelectors|TargetSelectors).t.sol", - )) - .unwrap(), - BTreeMap::from([ - ( - "default/fuzz/invariant/target/ExcludeSelectors.t.sol:ExcludeSelectors", - vec![("invariantFalseWorld()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", - vec![("invariantTrueWorld()", true, None, None, None)], - ), - ]), - ); - - // Artifacts filter tests. - assert_multiple( - &runner.test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/targetAbi/(ExcludeArtifacts|TargetArtifacts|TargetArtifactSelectors|TargetArtifactSelectors2).t.sol", - )).unwrap(), - BTreeMap::from([ - ( - "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", - vec![("invariantShouldPass()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", - vec![ - ("invariantShouldPass()", true, None, None, None), - ( - "invariantShouldFail()", - false, - Some("false world".into()), - None, - None, - ), - ], - ), - ( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", - vec![("invariantShouldPass()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", - vec![( - "invariantShouldFail()", - false, - Some("it's false".into()), - None, - None, - )], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_override() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = false; - config.invariant.call_override = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", - vec![("invariantNotStolen()", false, Some("stolen".into()), None, None)], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_fail_on_revert() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = true; - config.invariant.runs = 1; - config.invariant.depth = 10; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", - vec![( - "statefulFuzz_BrokenInvariant()", - false, - Some("failed on revert".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_invariant_storage() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.depth = 100; - if cfg!(windows) { - config.invariant.depth += 50; - } - config.fuzz.seed = Some(U256::from(6u32)); - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", - vec![ - ("invariantChangeAddress()", false, Some("changedAddr".to_string()), None, None), - ("invariantChangeString()", false, Some("changedString".to_string()), None, None), - ("invariantChangeUint()", false, Some("changedUint".to_string()), None, None), - ("invariantPush()", false, Some("pushUint".to_string()), None, None), - ], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_inner_contract() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); - let results = TEST_DATA_DEFAULT.runner().test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", - vec![("invariantHideJesus()", false, Some("jesus betrayed".into()), None, None)], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "for some reason there's different rng")] -async fn test_invariant_shrink() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); - let mut runner = - TEST_DATA_DEFAULT.runner_with(|config| config.fuzz.seed = Some(U256::from(119u32))); - - match get_counterexample!(runner, &filter) { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. - CounterExample::Sequence(_, sequence) => { - assert!(sequence.len() <= 3); - - if sequence.len() == 2 { - // call order should always be preserved - let create_fren_sequence = sequence[0].clone(); - assert_eq!( - create_fren_sequence.contract_name.unwrap(), - "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Jesus" - ); - assert_eq!(create_fren_sequence.signature.unwrap(), "create_fren()"); - - let betray_sequence = sequence[1].clone(); - assert_eq!( - betray_sequence.contract_name.unwrap(), - "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Judas" - ); - assert_eq!(betray_sequence.signature.unwrap(), "betray()"); - } - } - }; -} - -#[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "for some reason there's different rng")] -async fn test_invariant_assert_shrink() { - // ensure assert shrinks to same sequence of 2 as require - check_shrink_sequence("invariant_with_assert", 2).await; -} - -#[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "for some reason there's different rng")] -async fn test_invariant_require_shrink() { - // ensure require shrinks to same sequence of 2 as assert - check_shrink_sequence("invariant_with_require", 2).await; -} - -async fn check_shrink_sequence(test_pattern: &str, expected_len: usize) { - let filter = - Filter::new(test_pattern, ".*", ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fuzz.seed = Some(U256::from(100u32)); - config.invariant.runs = 1; - config.invariant.depth = 15; - }); - - match get_counterexample!(runner, &filter) { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - CounterExample::Sequence(_, sequence) => { - assert_eq!(sequence.len(), expected_len); - } - }; -} - -#[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "for some reason there's different rng")] -async fn test_shrink_big_sequence() { - let filter = - Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fuzz.seed = Some(U256::from(119u32)); - config.invariant.runs = 1; - config.invariant.depth = 1000; - }); - - let initial_counterexample = runner - .test_collect(&filter) - .unwrap() - .values() - .last() - .expect("Invariant contract should be testable.") - .test_results - .values() - .last() - .expect("Invariant contract should be testable.") - .counterexample - .clone() - .unwrap(); - - let initial_sequence = match initial_counterexample { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - CounterExample::Sequence(_, sequence) => sequence, - }; - // ensure shrinks to same sequence of 77 - assert_eq!(initial_sequence.len(), 77); - - // test failure persistence - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest", - vec![( - "invariant_shrink_big_sequence()", - false, - Some("invariant_shrink_big_sequence replay failure".into()), - None, - None, - )], - )]), - ); - let new_sequence = match results - .values() - .last() - .expect("Invariant contract should be testable.") - .test_results - .values() - .last() - .expect("Invariant contract should be testable.") - .counterexample - .clone() - .unwrap() - { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - CounterExample::Sequence(_, sequence) => sequence, - }; - // ensure shrinks to same sequence of 77 - assert_eq!(new_sequence.len(), 77); - // ensure calls within failed sequence are the same as initial one - for index in 0..77 { - let new_call = new_sequence.get(index).unwrap(); - let initial_call = initial_sequence.get(index).unwrap(); - assert_eq!(new_call.sender, initial_call.sender); - assert_eq!(new_call.addr, initial_call.addr); - assert_eq!(new_call.calldata, initial_call.calldata); - } -} - -#[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "for some reason there's different rng")] -async fn test_shrink_fail_on_revert() { - let filter = - Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fuzz.seed = Some(U256::from(119u32)); - config.invariant.fail_on_revert = true; - config.invariant.runs = 1; - config.invariant.depth = 200; - }); - - match get_counterexample!(runner, &filter) { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - CounterExample::Sequence(_, sequence) => { - // ensure shrinks to sequence of 10 - assert_eq!(sequence.len(), 10); - } - }; -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_preserve_state() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantPreserveState.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", - vec![( - "invariant_preserve_state()", - false, - Some("EvmError: Revert".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_with_address_fixture() { - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner - .test_collect(&Filter::new( - ".*", - ".*", - ".*fuzz/invariant/common/InvariantCalldataDictionary.t.sol", - )) - .unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", - vec![( - "invariant_owner_never_changes()", - false, - Some("".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_assume_does_not_revert() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - // Should not treat vm.assume as revert. - config.invariant.fail_on_revert = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", - vec![("invariant_dummy()", true, None, None, None)], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_assume_respects_restrictions() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.runs = 1; - config.invariant.depth = 10; - config.invariant.max_assume_rejects = 1; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", - vec![( - "invariant_dummy()", - false, - Some("`vm.assume` rejected too many inputs (1 allowed)".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_decode_custom_error() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantCustomError.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", - vec![( - "invariant_decode_error()", - false, - Some("InvariantCustomError(111, \"custom\")".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_fuzzed_selected_targets() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/target/FuzzedTargetContracts.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", - vec![("invariant_explicit_target()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", - vec![( - "invariant_dynamic_targets()", - false, - Some("wrong target selector called".into()), - None, - None, - )], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_fixtures() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantFixtures.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.runs = 1; - config.invariant.depth = 100; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", - vec![( - "invariant_target_not_compromised()", - false, - Some("".into()), - None, - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_scrape_values() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantScrapeValues.t.sol"); - let results = TEST_DATA_DEFAULT.runner().test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromReturnValueTest", - vec![( - "invariant_value_not_found()", - false, - Some("value from return found".into()), - None, - None, - )], - ), - ( - "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromLogValueTest", - vec![( - "invariant_value_not_found()", - false, - Some("value from logs found".into()), - None, - None, - )], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_roll_fork_handler() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantRollFork.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fuzz.seed = Some(U256::from(119u32)); - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkBlockTest", - vec![( - "invariant_fork_handler_block()", - false, - Some("too many blocks mined".into()), - None, - None, - )], - ), - ( - "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkStateTest", - vec![( - "invariant_fork_handler_state()", - false, - Some("wrong supply".into()), - None, - None, - )], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_excluded_senders() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantExcludedSenders.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = true; - }); - let results = runner.test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest", - vec![("invariant_check_sender()", true, None, None, None)], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_after_invariant() { - // Check failure on passing invariant and failed `afterInvariant` condition - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAfterInvariant.t.sol"); - let results = TEST_DATA_DEFAULT.runner().test_collect(&filter).unwrap(); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest", - vec![ - ( - "invariant_after_invariant_failure()", - false, - Some("afterInvariant failure".into()), - None, - None, - ), - ("invariant_failure()", false, Some("invariant failure".into()), None, None), - ("invariant_success()", true, None, None, None), - ], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_no_reverts_in_counterexample() { - let filter = - Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSequenceNoReverts.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.fail_on_revert = false; - // Use original counterexample to test sequence len. - config.invariant.shrink_run_limit = 0; - }); - - match get_counterexample!(runner, &filter) { - CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - CounterExample::Sequence(_, sequence) => { - // ensure original counterexample len is 10 (even without shrinking) - assert_eq!(sequence.len(), 10); - } - }; +use foundry_test_utils::{TestCommand, forgetest_init, snapbox::cmd::OutputAssert, str}; + +mod common; +mod storage; +mod target; + +fn assert_invariant(cmd: &mut TestCommand) -> OutputAssert { + cmd.assert_with(&[ + ("[RUNS]", r"runs: \d+, calls: \d+, reverts: \d+"), + ("[SEQUENCE]", r"\[Sequence\].*(\n\t\t.*)*"), + ("[STATS]", r"╭[\s\S]*?╰.*"), + ]) } // Tests that a persisted failure doesn't fail due to assume revert if test driver is changed. diff --git a/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol b/crates/forge/tests/cli/test_cmd/invariant/storage.rs similarity index 79% rename from testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol rename to crates/forge/tests/cli/test_cmd/invariant/storage.rs index 890c495c310bb..83c4f72bca93d 100644 --- a/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol +++ b/crates/forge/tests/cli/test_cmd/invariant/storage.rs @@ -1,6 +1,13 @@ -pragma solidity >0.8.13; +use super::*; -import "ds-test/test.sol"; +forgetest_init!( + #[ignore = "slow"] + storage, + |prj, cmd| { + prj.add_test( + "name", + r#" +import "forge-std/Test.sol"; contract Contract { address public addr = address(0xbeef); @@ -33,7 +40,7 @@ contract Contract { } } -contract InvariantStorageTest is DSTest { +contract InvariantStorageTest is Test { Contract c; function setUp() public { @@ -56,3 +63,9 @@ contract InvariantStorageTest is DSTest { require(c.pushNum() == 0, "pushUint"); } } +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#""#]]); + } +); diff --git a/crates/forge/tests/cli/test_cmd/invariant/target.rs b/crates/forge/tests/cli/test_cmd/invariant/target.rs new file mode 100644 index 0000000000000..31e684d4f307d --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/invariant/target.rs @@ -0,0 +1,766 @@ +use super::*; + +forgetest!(filters, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + prj.update_config(|config| { + config.invariant.runs = 50; + config.invariant.depth = 10; + }); + + prj.add_test( + "ExcludeContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract ExcludeContracts is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + new Hello(); + } + + function excludeContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(hello); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = false; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract ExcludeSelectors is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSelectors() public view returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantFalseWorld() public { + require(hello.world() == false, "true world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeSenders.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + address seed_address = address(0xdeadbeef); + bool public world = true; + + function changeBeef() public { + require(msg.sender == address(0xdeadbeef)); + world = false; + } + + // address(0) should be automatically excluded + function change0() public { + require(msg.sender == address(0)); + world = false; + } +} + +contract ExcludeSenders is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + // Tests clashing. Exclusion takes priority. + function targetSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract TargetContracts is Test { + Hello hello1; + Hello hello2; + + function setUp() public { + hello1 = new Hello(); + hello2 = new Hello(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(hello1); + return addrs; + } + + function invariantTrueWorld() public { + require(hello2.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetInterfaces.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzInterface { + address target; + string[] artifacts; +} + +contract Hello { + bool public world; + + function changeWorld() external { + world = true; + } +} + +interface IHello { + function world() external view returns (bool); + function changeWorld() external; +} + +contract HelloProxy { + address internal immutable _implementation; + + constructor(address implementation_) { + _implementation = implementation_; + } + + function _delegate(address implementation) internal { + assembly { + calldatacopy(0, 0, calldatasize()) + + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + fallback() external payable { + _delegate(_implementation); + } +} + +contract TargetWorldInterfaces is Test { + IHello proxy; + + function setUp() public { + Hello hello = new Hello(); + proxy = IHello(address(new HelloProxy(address(hello)))); + } + + function targetInterfaces() public view returns (FuzzInterface[] memory) { + FuzzInterface[] memory targets = new FuzzInterface[](1); + + string[] memory artifacts = new string[](1); + artifacts[0] = "IHello"; + + targets[0] = FuzzInterface(address(proxy), artifacts); + + return targets; + } + + function invariantTrueWorld() public { + require(proxy.world() == false, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = true; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract TargetSelectors is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function targetSelectors() public view returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetSenders.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + require(msg.sender == address(0xdeadbeef)); + world = false; + } +} + +contract TargetSenders is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function targetSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeArtifacts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +// Will get automatically excluded. Otherwise it would throw error. +contract NoMutFunctions { + function no_change() public pure {} +} + +contract Excluded { + bool public world = true; + + function change() public { + world = false; + } +} + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract ExcludeArtifacts is Test { + Excluded excluded; + + function setUp() public { + excluded = new Excluded(); + new Hello(); + new NoMutFunctions(); + } + + function excludeArtifacts() public returns (string[] memory) { + string[] memory abis = new string[](1); + abis[0] = "test/ExcludeArtifacts.t.sol:Excluded"; + return abis; + } + + function invariantShouldPass() public { + require(excluded.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifactSelectors2.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; +} + +contract Parent { + bool public should_be_true = true; + address public child; + + function change() public { + child = msg.sender; + should_be_true = false; + } + + function create() public { + new Child(); + } +} + +contract Child { + Parent parent; + bool public changed = false; + + constructor() { + parent = Parent(msg.sender); + } + + function change_parent() public { + parent.change(); + } + + function tracked_change_parent() public { + parent.change(); + } +} + +contract TargetArtifactSelectors2 is Test { + Parent parent; + + function setUp() public { + parent = new Parent(); + } + + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); + bytes4[] memory selectors_child = new bytes4[](1); + + selectors_child[0] = Child.change_parent.selector; + targets[0] = FuzzArtifactSelector( + "test/TargetArtifactSelectors2.t.sol:Child", selectors_child + ); + + bytes4[] memory selectors_parent = new bytes4[](1); + selectors_parent[0] = Parent.create.selector; + targets[1] = FuzzArtifactSelector( + "test/TargetArtifactSelectors2.t.sol:Parent", selectors_parent + ); + return targets; + } + + function invariantShouldFail() public { + if (!parent.should_be_true()) { + require(!Child(address(parent.child())).changed(), "should have not happened"); + } + require(parent.should_be_true() == true, "it's false"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifactSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; +} + +contract Hi { + bool public world = true; + + function no_change() public { + world = true; + } + + function change() public { + world = false; + } +} + +contract TargetArtifactSelectors is Test { + Hi hello; + + function setUp() public { + hello = new Hi(); + } + + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hi.no_change.selector; + targets[0] = + FuzzArtifactSelector("test/TargetArtifactSelectors.t.sol:Hi", selectors); + return targets; + } + + function invariantShouldPass() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifacts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Targeted { + bool public world = true; + + function change() public { + world = false; + } +} + +contract Hello { + bool public world = true; + + function no_change() public {} +} + +contract TargetArtifacts is Test { + Targeted target1; + Targeted target2; + Hello hello; + + function setUp() public { + target1 = new Targeted(); + target2 = new Targeted(); + hello = new Hello(); + } + + function targetArtifacts() public returns (string[] memory) { + string[] memory abis = new string[](1); + abis[0] = "test/TargetArtifacts.t.sol:Targeted"; + return abis; + } + + function invariantShouldPass() public { + require(target2.world() == true || target1.world() == true || hello.world() == true, "false world"); + } + + function invariantShouldFail() public { + require(target2.world() == true || target1.world() == true, "false world"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/ExcludeArtifacts.t.sol:ExcludeArtifacts +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/ExcludeContracts.t.sol:ExcludeContracts +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/ExcludeSelectors.t.sol:ExcludeSelectors +[PASS] invariantFalseWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/ExcludeSenders.t.sol:ExcludeSenders +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetArtifactSelectors.t.sol:TargetArtifactSelectors +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2 +[FAIL: it's false] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 tests for test/TargetArtifacts.t.sol:TargetArtifacts +[FAIL: false world] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +[STATS] + +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetContracts.t.sol:TargetContracts +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetInterfaces.t.sol:TargetWorldInterfaces +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetSelectors.t.sol:TargetSelectors +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/TargetSenders.t.sol:TargetSenders +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 11 test suites [ELAPSED]: 8 tests passed, 4 failed, 0 skipped (12 total tests) + +Failing tests: +Encountered 1 failing test in test/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2 +[FAIL: it's false] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +Encountered 1 failing test in test/TargetArtifacts.t.sol:TargetArtifacts +[FAIL: false world] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +Encountered 1 failing test in test/TargetInterfaces.t.sol:TargetWorldInterfaces +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +Encountered 1 failing test in test/TargetSenders.t.sol:TargetSenders +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +Encountered a total of 4 failing tests, 8 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 4 failed tests + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/5625 +// https://github.com/foundry-rs/foundry/issues/6166 +// `Target.wrongSelector` is not called when handler added as `targetContract` +// `Target.wrongSelector` is called (and test fails) when no `targetContract` set +forgetest!(fuzzed_selected_targets, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "FuzzedTargetContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; +import "src/Vm.sol"; + +contract Target { + uint256 count; + + function wrongSelector() external { + revert("wrong target selector called"); + } + + function goodSelector() external { + count++; + } +} + +contract Handler is Test { + function increment() public { + Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); + } +} + +contract ExplicitTargetContract is Test { + Vm constant vm = Vm(HEVM_ADDRESS); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(handler); + return addrs; + } + + function invariant_explicit_target() public {} +} + +contract DynamicTargetContract is Test { + Vm constant vm = Vm(HEVM_ADDRESS); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function invariant_dynamic_targets() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/FuzzedTargetContracts.t.sol:DynamicTargetContract +[FAIL: wrong target selector called] + [SEQUENCE] + invariant_dynamic_targets() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/FuzzedTargetContracts.t.sol:ExplicitTargetContract +[PASS] invariant_explicit_target() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/FuzzedTargetContracts.t.sol:DynamicTargetContract +[FAIL: wrong target selector called] + [SEQUENCE] + invariant_dynamic_targets() ([RUNS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/logs.rs b/crates/forge/tests/cli/test_cmd/logs.rs new file mode 100644 index 0000000000000..7aeafc2065ac8 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/logs.rs @@ -0,0 +1,756 @@ +//! Tests for various logging functionality + +use foundry_test_utils::str; + +forgetest_init!(debug_logs, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "DebugLogs.t.sol", + r#" +import "forge-std/Test.sol"; + +contract DebugLogsTest is Test { + constructor() { + emit log_uint(0); + } + + function setUp() public { + emit log_uint(1); + } + + function test1() public { + emit log_uint(2); + } + + function test2() public { + emit log_uint(3); + } + + function testRevertIfWithRevert() public { + Fails fails = new Fails(); + emit log_uint(4); + vm.expectRevert(); + fails.failure(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfWithRequire() public { + emit log_uint(5); + vm.expectRevert(); + require(false); + } + + function testLog() public { + emit log("Error: Assertion Failed"); + } + + function testLogs() public { + emit logs(bytes("abcd")); + } + + function testLogAddress() public { + emit log_address(address(1)); + } + + function testLogBytes32() public { + emit log_bytes32(bytes32("abcd")); + } + + function testLogInt() public { + emit log_int(int256(-31337)); + } + + function testLogBytes() public { + emit log_bytes(bytes("abcd")); + } + + function testLogString() public { + emit log_string("here"); + } + + function testLogNamedAddress() public { + emit log_named_address("address", address(1)); + } + + function testLogNamedBytes32() public { + emit log_named_bytes32("abcd", bytes32("abcd")); + } + + function testLogNamedDecimalInt() public { + emit log_named_decimal_int("amount", int256(-31337), uint256(18)); + } + + function testLogNamedDecimalUint() public { + emit log_named_decimal_uint("amount", uint256(1 ether), uint256(18)); + } + + function testLogNamedInt() public { + emit log_named_int("amount", int256(-31337)); + } + + function testLogNamedUint() public { + emit log_named_uint("amount", uint256(1 ether)); + } + + function testLogNamedBytes() public { + emit log_named_bytes("abcd", bytes("abcd")); + } + + function testLogNamedString() public { + emit log_named_string("key", "val"); + } +} + +contract Fails is Test { + function failure() public { + emit log_uint(100); + revert(); + } +} +"#, + ); + + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 19 tests for test/DebugLogs.t.sol:DebugLogsTest +[PASS] test1() ([GAS]) +Logs: + 0 + 1 + 2 + +[PASS] test2() ([GAS]) +Logs: + 0 + 1 + 3 + +[PASS] testLog() ([GAS]) +Logs: + 0 + 1 + Error: Assertion Failed + +[PASS] testLogAddress() ([GAS]) +Logs: + 0 + 1 + 0x0000000000000000000000000000000000000001 + +[PASS] testLogBytes() ([GAS]) +Logs: + 0 + 1 + 0x61626364 + +[PASS] testLogBytes32() ([GAS]) +Logs: + 0 + 1 + 0x6162636400000000000000000000000000000000000000000000000000000000 + +[PASS] testLogInt() ([GAS]) +Logs: + 0 + 1 + -31337 + +[PASS] testLogNamedAddress() ([GAS]) +Logs: + 0 + 1 + address: 0x0000000000000000000000000000000000000001 + +[PASS] testLogNamedBytes() ([GAS]) +Logs: + 0 + 1 + abcd: 0x61626364 + +[PASS] testLogNamedBytes32() ([GAS]) +Logs: + 0 + 1 + abcd: 0x6162636400000000000000000000000000000000000000000000000000000000 + +[PASS] testLogNamedDecimalInt() ([GAS]) +Logs: + 0 + 1 + amount: -0.000000000000031337 + +[PASS] testLogNamedDecimalUint() ([GAS]) +Logs: + 0 + 1 + amount: 1.000000000000000000 + +[PASS] testLogNamedInt() ([GAS]) +Logs: + 0 + 1 + amount: -31337 + +[PASS] testLogNamedString() ([GAS]) +Logs: + 0 + 1 + key: val + +[PASS] testLogNamedUint() ([GAS]) +Logs: + 0 + 1 + amount: 1000000000000000000 + +[PASS] testLogString() ([GAS]) +Logs: + 0 + 1 + here + +[PASS] testLogs() ([GAS]) +Logs: + 0 + 1 + 0x61626364 + +[PASS] testRevertIfWithRequire() ([GAS]) +Logs: + 0 + 1 + 5 + +[PASS] testRevertIfWithRevert() ([GAS]) +Logs: + 0 + 1 + 4 + 100 + +Suite result: ok. 19 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 19 tests passed, 0 failed, 0 skipped (19 total tests) + +"#]]); +}); + +forgetest_init!(hardhat_logs, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "HardhatLogs.t.sol", + r#" +import "forge-std/console.sol"; + +contract HardhatLogsTest { + constructor() { + console.log("constructor"); + } + + string testStr; + int256 testInt; + uint256 testUint; + bool testBool; + address testAddr; + bytes testBytes; + + function setUp() public { + testStr = "test"; + testInt = -31337; + testUint = 1; + testBool = false; + testAddr = 0x0000000000000000000000000000000000000001; + testBytes = "a"; + } + + function testInts() public view { + console.log(uint256(0)); + console.log(uint256(1)); + console.log(uint256(2)); + console.log(uint256(3)); + } + + function testStrings() public view { + console.log("testStrings"); + } + + function testMisc() public view { + console.log("testMisc", address(1)); + console.log("testMisc", uint256(42)); + } + + function testConsoleLog() public view { + console.log(testStr); + } + + function testLogInt() public view { + console.logInt(testInt); + } + + function testLogUint() public view { + console.logUint(testUint); + } + + function testLogString() public view { + console.logString(testStr); + } + + function testLogBool() public view { + console.logBool(testBool); + } + + function testLogAddress() public view { + console.logAddress(testAddr); + } + + function testLogBytes() public view { + console.logBytes(testBytes); + } + + function testLogBytes1() public view { + console.logBytes1(bytes1(testBytes)); + } + + function testLogBytes2() public view { + console.logBytes2(bytes2(testBytes)); + } + + function testLogBytes3() public view { + console.logBytes3(bytes3(testBytes)); + } + + function testLogBytes4() public view { + console.logBytes4(bytes4(testBytes)); + } + + function testLogBytes5() public view { + console.logBytes5(bytes5(testBytes)); + } + + function testLogBytes6() public view { + console.logBytes6(bytes6(testBytes)); + } + + function testLogBytes7() public view { + console.logBytes7(bytes7(testBytes)); + } + + function testLogBytes8() public view { + console.logBytes8(bytes8(testBytes)); + } + + function testLogBytes9() public view { + console.logBytes9(bytes9(testBytes)); + } + + function testLogBytes10() public view { + console.logBytes10(bytes10(testBytes)); + } + + function testLogBytes11() public view { + console.logBytes11(bytes11(testBytes)); + } + + function testLogBytes12() public view { + console.logBytes12(bytes12(testBytes)); + } + + function testLogBytes13() public view { + console.logBytes13(bytes13(testBytes)); + } + + function testLogBytes14() public view { + console.logBytes14(bytes14(testBytes)); + } + + function testLogBytes15() public view { + console.logBytes15(bytes15(testBytes)); + } + + function testLogBytes16() public view { + console.logBytes16(bytes16(testBytes)); + } + + function testLogBytes17() public view { + console.logBytes17(bytes17(testBytes)); + } + + function testLogBytes18() public view { + console.logBytes18(bytes18(testBytes)); + } + + function testLogBytes19() public view { + console.logBytes19(bytes19(testBytes)); + } + + function testLogBytes20() public view { + console.logBytes20(bytes20(testBytes)); + } + + function testLogBytes21() public view { + console.logBytes21(bytes21(testBytes)); + } + + function testLogBytes22() public view { + console.logBytes22(bytes22(testBytes)); + } + + function testLogBytes23() public view { + console.logBytes23(bytes23(testBytes)); + } + + function testLogBytes24() public view { + console.logBytes24(bytes24(testBytes)); + } + + function testLogBytes25() public view { + console.logBytes25(bytes25(testBytes)); + } + + function testLogBytes26() public view { + console.logBytes26(bytes26(testBytes)); + } + + function testLogBytes27() public view { + console.logBytes27(bytes27(testBytes)); + } + + function testLogBytes28() public view { + console.logBytes28(bytes28(testBytes)); + } + + function testLogBytes29() public view { + console.logBytes29(bytes29(testBytes)); + } + + function testLogBytes30() public view { + console.logBytes30(bytes30(testBytes)); + } + + function testLogBytes31() public view { + console.logBytes31(bytes31(testBytes)); + } + + function testLogBytes32() public view { + console.logBytes32(bytes32(testBytes)); + } + + function testConsoleLogUint() public view { + console.log(testUint); + } + + function testConsoleLogString() public view { + console.log(testStr); + } + + function testConsoleLogBool() public view { + console.log(testBool); + } + + function testConsoleLogAddress() public view { + console.log(testAddr); + } + + function testConsoleLogFormatString() public view { + console.log("formatted log str=%s", testStr); + } + + function testConsoleLogFormatUint() public view { + console.log("formatted log uint=%s", testUint); + } + + function testConsoleLogFormatAddress() public view { + console.log("formatted log addr=%s", testAddr); + } + + function testConsoleLogFormatMulti() public view { + console.log("formatted log str=%s uint=%d", testStr, testUint); + } + + function testConsoleLogFormatEscape() public view { + console.log("formatted log %% %s", testStr); + } + + function testConsoleLogFormatSpill() public view { + console.log("formatted log %s", testStr, testUint); + } +} +"#, + ); + + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +... +Ran 52 tests for test/HardhatLogs.t.sol:HardhatLogsTest +[PASS] testConsoleLog() ([GAS]) +Logs: + constructor + test + +[PASS] testConsoleLogAddress() ([GAS]) +Logs: + constructor + 0x0000000000000000000000000000000000000001 + +[PASS] testConsoleLogBool() ([GAS]) +Logs: + constructor + false + +[PASS] testConsoleLogFormatAddress() ([GAS]) +Logs: + constructor + formatted log addr=0x0000000000000000000000000000000000000001 + +[PASS] testConsoleLogFormatEscape() ([GAS]) +Logs: + constructor + formatted log % test + +[PASS] testConsoleLogFormatMulti() ([GAS]) +Logs: + constructor + formatted log str=test uint=1 + +[PASS] testConsoleLogFormatSpill() ([GAS]) +Logs: + constructor + formatted log test 1 + +[PASS] testConsoleLogFormatString() ([GAS]) +Logs: + constructor + formatted log str=test + +[PASS] testConsoleLogFormatUint() ([GAS]) +Logs: + constructor + formatted log uint=1 + +[PASS] testConsoleLogString() ([GAS]) +Logs: + constructor + test + +[PASS] testConsoleLogUint() ([GAS]) +Logs: + constructor + 1 + +[PASS] testInts() ([GAS]) +Logs: + constructor + 0 + 1 + 2 + 3 + +[PASS] testLogAddress() ([GAS]) +Logs: + constructor + 0x0000000000000000000000000000000000000001 + +[PASS] testLogBool() ([GAS]) +Logs: + constructor + false + +[PASS] testLogBytes() ([GAS]) +Logs: + constructor + 0x61 + +[PASS] testLogBytes1() ([GAS]) +Logs: + constructor + 0x61 + +[PASS] testLogBytes10() ([GAS]) +Logs: + constructor + 0x61000000000000000000 + +[PASS] testLogBytes11() ([GAS]) +Logs: + constructor + 0x6100000000000000000000 + +[PASS] testLogBytes12() ([GAS]) +Logs: + constructor + 0x610000000000000000000000 + +[PASS] testLogBytes13() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000 + +[PASS] testLogBytes14() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000 + +[PASS] testLogBytes15() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000 + +[PASS] testLogBytes16() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000 + +[PASS] testLogBytes17() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000 + +[PASS] testLogBytes18() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000 + +[PASS] testLogBytes19() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000 + +[PASS] testLogBytes2() ([GAS]) +Logs: + constructor + 0x6100 + +[PASS] testLogBytes20() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000 + +[PASS] testLogBytes21() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000 + +[PASS] testLogBytes22() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000 + +[PASS] testLogBytes23() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000 + +[PASS] testLogBytes24() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000 + +[PASS] testLogBytes25() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes26() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes27() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes28() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes29() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes3() ([GAS]) +Logs: + constructor + 0x610000 + +[PASS] testLogBytes30() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes31() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes32() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes4() ([GAS]) +Logs: + constructor + 0x61000000 + +[PASS] testLogBytes5() ([GAS]) +Logs: + constructor + 0x6100000000 + +[PASS] testLogBytes6() ([GAS]) +Logs: + constructor + 0x610000000000 + +[PASS] testLogBytes7() ([GAS]) +Logs: + constructor + 0x61000000000000 + +[PASS] testLogBytes8() ([GAS]) +Logs: + constructor + 0x6100000000000000 + +[PASS] testLogBytes9() ([GAS]) +Logs: + constructor + 0x610000000000000000 + +[PASS] testLogInt() ([GAS]) +Logs: + constructor + -31337 + +[PASS] testLogString() ([GAS]) +Logs: + constructor + test + +[PASS] testLogUint() ([GAS]) +Logs: + constructor + 1 + +[PASS] testMisc() ([GAS]) +Logs: + constructor + testMisc 0x0000000000000000000000000000000000000001 + testMisc 42 + +[PASS] testStrings() ([GAS]) +Logs: + constructor + testStrings + +Suite result: ok. 52 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 52 tests passed, 0 failed, 0 skipped (52 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd/mod.rs similarity index 98% rename from crates/forge/tests/cli/test_cmd.rs rename to crates/forge/tests/cli/test_cmd/mod.rs index 11bbc782af2b6..d708cbf4123a7 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -3,11 +3,63 @@ use alloy_primitives::U256; use anvil::{NodeConfig, spawn}; use foundry_test_utils::{ - TestCommand, rpc, str, + TestCommand, + rpc::{self, rpc_endpoints}, + str, util::{OTHER_SOLC_VERSION, OutputExt, SOLC_VERSION}, }; use similar_asserts::assert_eq; -use std::{path::PathBuf, str::FromStr}; +use std::{io::Write, path::PathBuf, str::FromStr}; + +mod core; +mod fuzz; +mod invariant; +mod logs; +mod repros; +mod spec; +mod trace; + +// Run `forge test` on `/testdata`. +forgetest!(testdata, |_prj, cmd| { + let testdata = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap(); + cmd.current_dir(&testdata); + + let mut dotenv = std::fs::File::create(testdata.join(".env")).unwrap(); + for (name, endpoint) in rpc_endpoints().iter() { + if let Some(url) = endpoint.endpoint.as_url() { + let key = format!("RPC_{}", name.to_uppercase()); + // cmd.env(&key, url); + writeln!(dotenv, "{key}={url}").unwrap(); + } + } + drop(dotenv); + + let mut args = vec!["test"]; + if cfg!(feature = "isolate-by-default") { + args.push( + "--nmc=(LastCallGasDefaultTest|MockFunctionTest|WithSeed|StateDiff|GetStorageSlotsTest|RecordAccount)", + ); + } + + let orig_assert = cmd.args(args).assert(); + if orig_assert.get_output().status.success() { + return; + } + + // Retry failed tests. + cmd.args(["--rerun"]); + let n = 3; + for i in 1..=n { + test_debug!("retrying failed tests... ({i}/{n})"); + let assert = cmd.assert(); + if assert.get_output().status.success() { + return; + } + } + + orig_assert.success(); +}); // tests that test filters are handled correctly forgetest!(can_set_filter_values, |prj, cmd| { @@ -272,7 +324,7 @@ forgetest!(can_run_test_with_json_output_verbose, |prj, cmd| { // Assert that with verbose output the json output includes the traces cmd.args(["test", "-vvv", "--json"]) .assert_success() - .stdout_eq(file!["../fixtures/SimpleContractTestVerbose.json": Json]); + .stdout_eq(file!["../../fixtures/SimpleContractTestVerbose.json": Json]); }); forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| { @@ -284,7 +336,7 @@ forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| { // Assert that without verbose output the json output does not include the traces cmd.args(["test", "--json"]) .assert_success() - .stdout_eq(file!["../fixtures/SimpleContractTestNonVerbose.json": Json]); + .stdout_eq(file!["../../fixtures/SimpleContractTestNonVerbose.json": Json]); }); // tests that `forge test` will pick up tests that are stored in the `test = ` config value @@ -2931,7 +2983,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) forgetest_init!(colored_traces, |prj, cmd| { cmd.args(["test", "--mt", "test_Increment", "--color", "always", "-vvvvv"]) .assert_success() - .stdout_eq(file!["../fixtures/colored_traces.svg": TermSvg]); + .stdout_eq(file!["../../fixtures/colored_traces.svg": TermSvg]); }); // Tests that traces for successful tests can be suppressed by using `-s` flag. diff --git a/testdata/default/repros/Issue8383.t.sol b/crates/forge/tests/cli/test_cmd/repros.rs similarity index 57% rename from testdata/default/repros/Issue8383.t.sol rename to crates/forge/tests/cli/test_cmd/repros.rs index 3c40e44475fcc..bb663c051fa63 100644 --- a/testdata/default/repros/Issue8383.t.sol +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -1,13 +1,438 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; +//! Regression tests for specific GitHub issues -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +use foundry_test_utils::str; + +// https://github.com/foundry-rs/foundry/issues/3055 +forgetest_init!(issue_3055, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue3055.t.sol", + r#" +import "forge-std/Test.sol"; + +/// forge-config: default.assertions_revert = false +contract Issue3055Test is Test { + function test_snapshot() external { + uint256 snapshotId = vm.snapshotState(); + assertEq(uint256(0), uint256(1)); + vm.revertToState(snapshotId); + } + + function test_snapshot2() public { + uint256 snapshotId = vm.snapshotState(); + assertTrue(false); + vm.revertToState(snapshotId); + assertTrue(true); + } + + function test_snapshot3(uint256) public { + vm.expectRevert(); + // Call exposed_snapshot3() using this to perform an external call, + // so we can properly test for reverts. + this.exposed_snapshot3(); + } + + function exposed_snapshot3() public { + uint256 snapshotId = vm.snapshotState(); + assertTrue(false); + vm.revertToState(snapshotId); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 3 tests for test/Issue3055.t.sol:Issue3055Test +[FAIL] test_snapshot() ([GAS]) +[FAIL] test_snapshot2() ([GAS]) +[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 3 failing tests in test/Issue3055.t.sol:Issue3055Test +[FAIL] test_snapshot() ([GAS]) +[FAIL] test_snapshot2() ([GAS]) +[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS]) + +Encountered a total of 3 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 3 failed tests + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3189 +forgetest_init!(issue_3189, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue3189.t.sol", + r#" +import "forge-std/Test.sol"; + +contract MyContract { + function foo(uint256 arg) public returns (uint256) { + return arg + 2; + } +} + +contract MyContractUser is Test { + MyContract immutable myContract; + + constructor() { + myContract = new MyContract(); + } + + function foo(uint256 arg) public returns (uint256 ret) { + ret = myContract.foo(arg); + assertEq(ret, arg + 1, "Invariant failed"); + } +} + +contract Issue3189Test is Test { + function testFoo() public { + MyContractUser user = new MyContractUser(); + user.foo(123); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue3189.t.sol:Issue3189Test +[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue3189.t.sol:Issue3189Test +[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3596 +forgetest_init!(issue_3596, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue3596.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue3596Test is Test { + function testDealTransfer() public { + address addr = vm.addr(1337); + vm.startPrank(addr); + vm.deal(addr, 20000001 ether); + payable(address(this)).transfer(20000000 ether); + + Nested nested = new Nested(); + nested.doStuff(); + vm.stopPrank(); + } +} + +contract Nested { + function doStuff() public { + doRevert(); + } + + function doRevert() public { + revert("This fails"); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue3596.t.sol:Issue3596Test +[FAIL: EvmError: Revert] testDealTransfer() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue3596.t.sol:Issue3596Test +[FAIL: EvmError: Revert] testDealTransfer() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/2851 +forgetest_init!(issue_2851, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue2851.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Backdoor { + uint256 public number = 1; + + function backdoor(uint256 newNumber) public payable { + uint256 x = newNumber - 1; + if (x == 6912213124124531) { + number = 0; + } + } +} + +contract Issue2851Test is Test { + Backdoor back; + + function setUp() public { + back = new Backdoor(); + } + + /// forge-config: default.fuzz.seed = "111" + function invariantNotZero() public { + assertEq(back.number(), 1); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue2851.t.sol:Issue2851Test +[FAIL: assertion failed: 0 != 1] +... + invariantNotZero() ([..]) +... +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6170 +forgetest_init!(issue_6170, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue6170.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Emitter { + event Values(uint256 indexed a, uint256 indexed b); + + function plsEmit(uint256 a, uint256 b) external { + emit Values(a, b); + } +} + +contract Issue6170Test is Test { + event Values(uint256 indexed a, uint256 b); + + Emitter e = new Emitter(); + + function test() public { + vm.expectEmit(true, true, false, true); + emit Values(69, 420); + e.plsEmit(69, 420); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Issue6170.t.sol:Issue6170Test +[FAIL: log != expected log] test() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue6170.t.sol:Issue6170Test +[FAIL: log != expected log] test() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6355 +forgetest_init!(issue_6355, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue6355.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue6355Test is Test { + uint256 snapshotId; + Target targ; + + function setUp() public { + snapshotId = vm.snapshotState(); + targ = new Target(); + } + + // this non-deterministically fails sometimes and passes sometimes + function test_shouldPass() public { + assertEq(2, targ.num()); + } + + // always fails + function test_shouldFailWithRevertToState() public { + assertEq(3, targ.num()); + vm.revertToState(snapshotId); + } + + // always fails + function test_shouldFail() public { + assertEq(3, targ.num()); + } +} + +contract Target { + function num() public pure returns (uint256) { + return 2; + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue6355.t.sol:Issue6355Test +[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS]) +[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS]) +[PASS] test_shouldPass() ([GAS]) +Suite result: FAILED. 1 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 2 failing tests in test/Issue6355.t.sol:Issue6355Test +[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS]) +[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3347 +forgetest_init!(issue_3347, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue3347.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue3347Test is Test { + event log2(uint256, uint256); + + function test() public { + emit log2(1, 2); + } +} +"#, + ); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Issue3347.t.sol:Issue3347Test +[PASS] test() ([GAS]) +Traces: + [..] Issue3347Test::test() + ├─ emit log2(: 1, : 2) + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6501 +// Make sure we decode Hardhat-style `console.log`s correctly, in both logs and traces. +forgetest_init!(issue_6501, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Issue6501.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue6501Test is Test { + function test_hhLogs() public { + console.log("a"); + console.log(uint256(1)); + console.log("b", uint256(2)); + } +} +"#, + ); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue6501.t.sol:Issue6501Test +[PASS] test_hhLogs() ([GAS]) +Logs: + a + 1 + b 2 + +Traces: + [..] Issue6501Test::test_hhLogs() + ├─ [0] console::log("a") [staticcall] + │ └─ ← [Stop] + ├─ [0] console::log(1) [staticcall] + │ └─ ← [Stop] + ├─ [0] console::log("b", 2) [staticcall] + │ └─ ← [Stop] + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); // https://github.com/foundry-rs/foundry/issues/8383 -contract Issue8383Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +forgetest_init!(issue_8383, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.optimizer = Some(true); + config.optimizer_runs = Some(200); + }); + prj.add_test( + "Issue8383.t.sol", + r#" +import "forge-std/Test.sol"; +contract Issue8383Test is Test { address internal _verifier; mapping(bytes32 => bool) internal _vectorTested; @@ -326,3 +751,13 @@ contract P256Verifier { } } } +"#, + ); + + cmd.arg("test").with_no_redact().assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue8383.t.sol:Issue8383Test +[PASS] testP256VerifyOutOfBounds() (gas: 3139) +... +"#]]); +}); diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/cli/test_cmd/spec.rs similarity index 93% rename from crates/forge/tests/it/spec.rs rename to crates/forge/tests/cli/test_cmd/spec.rs index bcd16ce08028a..d8eabfad548e6 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/cli/test_cmd/spec.rs @@ -1,19 +1,10 @@ -//! Integration tests for EVM specifications. - -use crate::{config::*, test_helpers::TEST_DATA_PARIS}; -use foundry_test_utils::{Filter, forgetest_init, rpc, str}; -use revm::primitives::hardfork::SpecId; - -#[tokio::test(flavor = "multi_thread")] -async fn test_shanghai_compat() { - let filter = Filter::new("", "ShanghaiCompat", ".*spec"); - TestConfig::with_filter(TEST_DATA_PARIS.runner(), filter).spec_id(SpecId::SHANGHAI).run().await; -} +use foundry_test_utils::rpc; // Test evm version switch during tests / scripts. // // forgetest_init!(test_set_evm_version, |prj, cmd| { + prj.wipe_contracts(); let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( "TestEvmVersion.t.sol", diff --git a/crates/forge/tests/it/table.rs b/crates/forge/tests/cli/test_cmd/table.rs similarity index 100% rename from crates/forge/tests/it/table.rs rename to crates/forge/tests/cli/test_cmd/table.rs diff --git a/crates/forge/tests/cli/test_cmd/trace.rs b/crates/forge/tests/cli/test_cmd/trace.rs new file mode 100644 index 0000000000000..093afbbdb8fe5 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/trace.rs @@ -0,0 +1,397 @@ +//! Tests for tracing functionality + +use foundry_test_utils::str; + +forgetest_init!(conflicting_signatures, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "ConflictingSignatures.t.sol", + r#" +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; + +contract ReturnsNothing { + function func() public pure {} +} + +contract ReturnsString { + function func() public pure returns (string memory) { + return "string"; + } +} + +contract ReturnsUint { + function func() public pure returns (uint256) { + return 1; + } +} + +contract ConflictingSignaturesTest is Test { + ReturnsNothing retsNothing; + ReturnsString retsString; + ReturnsUint retsUint; + + function setUp() public { + retsNothing = new ReturnsNothing(); + retsString = new ReturnsString(); + retsUint = new ReturnsUint(); + } + + /// Tests that traces are decoded properly when multiple + /// functions have the same 4byte signature, but different + /// return values. + function testTraceWithConflictingSignatures() public { + retsNothing.func(); + retsString.func(); + retsUint.func(); + } +} +"#, + ); + + cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/ConflictingSignatures.t.sol:ConflictingSignaturesTest +[PASS] testTraceWithConflictingSignatures() ([GAS]) +Traces: + [..] ConflictingSignaturesTest::setUp() + ├─ [..] → new ReturnsNothing@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 106 bytes of code + ├─ [..] → new ReturnsString@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ └─ ← [Return] 334 bytes of code + ├─ [..] → new ReturnsUint@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a + │ └─ ← [Return] 175 bytes of code + └─ ← [Stop] + + [..] ConflictingSignaturesTest::testTraceWithConflictingSignatures() + ├─ [..] ReturnsNothing::func() [staticcall] + │ └─ ← [Stop] + ├─ [..] ReturnsString::func() [staticcall] + │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006737472696e670000000000000000000000000000000000000000000000000000 + ├─ [..] ReturnsUint::func() [staticcall] + │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(trace_test, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "Trace.t.sol", + r#" +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; + +contract RecursiveCall { + TraceTest factory; + + event Depth(uint256 depth); + event ChildDepth(uint256 childDepth); + event CreatedChild(uint256 childDepth); + + constructor(address _factory) { + factory = TraceTest(_factory); + } + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + this.negativeNum(); + return neededDepth; + } + + uint256 childDepth = this.recurseCall(neededDepth, depth + 1); + emit ChildDepth(childDepth); + + this.someCall(); + emit Depth(depth); + + return depth; + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + + RecursiveCall child = factory.create(); + emit CreatedChild(depth + 1); + + uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); + emit ChildDepth(childDepth); + emit Depth(depth); + + return depth; + } + + function someCall() public pure {} + + function negativeNum() public pure returns (int256) { + return -1000000000; + } +} + +contract TraceTest is Test { + uint256 nodeId = 0; + RecursiveCall first; + + function setUp() public { + first = this.create(); + } + + function create() public returns (RecursiveCall) { + RecursiveCall node = new RecursiveCall(address(this)); + vm.label(address(node), string(abi.encodePacked("Node ", uintToString(nodeId++)))); + + return node; + } + + function testRecurseCall() public { + first.recurseCall(8, 0); + } + + function testRecurseCreate() public { + first.recurseCreate(8, 0); + } +} + +function uintToString(uint256 value) pure returns (string memory) { + // Taken from OpenZeppelin + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); +} +"#, + ); + + cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/Trace.t.sol:TraceTest +[PASS] testRecurseCall() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCall() + ├─ [..] Node 0::recurseCall(8, 0) + │ ├─ [..] Node 0::recurseCall(8, 1) + │ │ ├─ [..] Node 0::recurseCall(8, 2) + │ │ │ ├─ [..] Node 0::recurseCall(8, 3) + │ │ │ │ ├─ [..] Node 0::recurseCall(8, 4) + │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 5) + │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 6) + │ │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 7) + │ │ │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 8) + │ │ │ │ │ │ │ │ │ ├─ [..] Node 0::negativeNum() [staticcall] + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] -1000000000 [-1e9] + │ │ │ │ │ │ │ │ │ └─ ← [Return] 8 + │ │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ │ │ ├─ emit Depth(depth: 7) + │ │ │ │ │ │ │ │ └─ ← [Return] 7 + │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 7) + │ │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ │ ├─ emit Depth(depth: 6) + │ │ │ │ │ │ │ └─ ← [Return] 6 + │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 6) + │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ ├─ emit Depth(depth: 5) + │ │ │ │ │ │ └─ ← [Return] 5 + │ │ │ │ │ ├─ emit ChildDepth(childDepth: 5) + │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ ├─ emit Depth(depth: 4) + │ │ │ │ │ └─ ← [Return] 4 + │ │ │ │ ├─ emit ChildDepth(childDepth: 4) + │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ └─ ← [Stop] + │ │ │ │ ├─ emit Depth(depth: 3) + │ │ │ │ └─ ← [Return] 3 + │ │ │ ├─ emit ChildDepth(childDepth: 3) + │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ └─ ← [Stop] + │ │ │ ├─ emit Depth(depth: 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ └─ ← [Stop] + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ [..] Node 0::someCall() [staticcall] + │ │ └─ ← [Stop] + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +[PASS] testRecurseCreate() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCreate() + ├─ [..] Node 0::recurseCreate(8, 0) + │ ├─ [..] TraceTest::create() + │ │ ├─ [..] → new Node 1@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ │ │ ├─ storage changes: + │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ └─ ← [Return] 1911 bytes of code + │ │ ├─ [0] VM::label(Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], "Node 1") + │ │ │ └─ ← [Return] + │ │ ├─ storage changes: + │ │ │ @ 32: 1 → 2 + │ │ └─ ← [Return] Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b] + │ ├─ emit CreatedChild(childDepth: 1) + │ ├─ [..] Node 1::recurseCreate(8, 1) + │ │ ├─ [..] TraceTest::create() + │ │ │ ├─ [..] → new Node 2@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a + │ │ │ │ ├─ storage changes: + │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ ├─ [0] VM::label(Node 2: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], "Node 2") + │ │ │ │ └─ ← [Return] + │ │ │ ├─ storage changes: + │ │ │ │ @ 32: 2 → 3 + │ │ │ └─ ← [Return] Node 2: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a] + │ │ ├─ emit CreatedChild(childDepth: 2) + │ │ ├─ [..] Node 2::recurseCreate(8, 2) + │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ ├─ [..] → new Node 3@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 + │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ ├─ [0] VM::label(Node 3: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], "Node 3") + │ │ │ │ │ └─ ← [Return] + │ │ │ │ ├─ storage changes: + │ │ │ │ │ @ 32: 3 → 4 + │ │ │ │ └─ ← [Return] Node 3: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9] + │ │ │ ├─ emit CreatedChild(childDepth: 3) + │ │ │ ├─ [..] Node 3::recurseCreate(8, 3) + │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ ├─ [..] → new Node 4@0xc7183455a4C133Ae270771860664b6B7ec320bB1 + │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ ├─ [0] VM::label(Node 4: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], "Node 4") + │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ @ 32: 4 → 5 + │ │ │ │ │ └─ ← [Return] Node 4: [0xc7183455a4C133Ae270771860664b6B7ec320bB1] + │ │ │ │ ├─ emit CreatedChild(childDepth: 4) + │ │ │ │ ├─ [..] Node 4::recurseCreate(8, 4) + │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ ├─ [..] → new Node 5@0xa0Cb889707d426A7A386870A03bc70d1b0697598 + │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ ├─ [0] VM::label(Node 5: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], "Node 5") + │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ @ 32: 5 → 6 + │ │ │ │ │ │ └─ ← [Return] Node 5: [0xa0Cb889707d426A7A386870A03bc70d1b0697598] + │ │ │ │ │ ├─ emit CreatedChild(childDepth: 5) + │ │ │ │ │ ├─ [..] Node 5::recurseCreate(8, 5) + │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ ├─ [..] → new Node 6@0x1d1499e622D69689cdf9004d05Ec547d650Ff211 + │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ ├─ [0] VM::label(Node 6: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], "Node 6") + │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ @ 32: 6 → 7 + │ │ │ │ │ │ │ └─ ← [Return] Node 6: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211] + │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 6) + │ │ │ │ │ │ ├─ [..] Node 6::recurseCreate(8, 6) + │ │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ │ ├─ [..] → new Node 7@0xA4AD4f68d0b91CFD19687c881e50f3A00242828c + │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ │ ├─ [0] VM::label(Node 7: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], "Node 7") + │ │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ @ 32: 7 → 8 + │ │ │ │ │ │ │ │ └─ ← [Return] Node 7: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c] + │ │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 7) + │ │ │ │ │ │ │ ├─ [..] Node 7::recurseCreate(8, 7) + │ │ │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ │ │ ├─ [..] → new Node 8@0x03A6a84cD762D9707A21605b548aaaB891562aAb + │ │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ │ │ ├─ [0] VM::label(Node 8: [0x03A6a84cD762D9707A21605b548aaaB891562aAb], "Node 8") + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ @ 32: 8 → 9 + │ │ │ │ │ │ │ │ │ └─ ← [Return] Node 8: [0x03A6a84cD762D9707A21605b548aaaB891562aAb] + │ │ │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ [..] Node 8::recurseCreate(8, 8) + │ │ │ │ │ │ │ │ │ └─ ← [Return] 8 + │ │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ emit Depth(depth: 7) + │ │ │ │ │ │ │ │ └─ ← [Return] 7 + │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 7) + │ │ │ │ │ │ │ ├─ emit Depth(depth: 6) + │ │ │ │ │ │ │ └─ ← [Return] 6 + │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 6) + │ │ │ │ │ │ ├─ emit Depth(depth: 5) + │ │ │ │ │ │ └─ ← [Return] 5 + │ │ │ │ │ ├─ emit ChildDepth(childDepth: 5) + │ │ │ │ │ ├─ emit Depth(depth: 4) + │ │ │ │ │ └─ ← [Return] 4 + │ │ │ │ ├─ emit ChildDepth(childDepth: 4) + │ │ │ │ ├─ emit Depth(depth: 3) + │ │ │ │ └─ ← [Return] 3 + │ │ │ ├─ emit ChildDepth(childDepth: 3) + │ │ │ ├─ emit Depth(depth: 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs deleted file mode 100644 index 1271f534cdc5d..0000000000000 --- a/crates/forge/tests/it/cheats.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Forge tests for cheatcodes. -use crate::{ - config::*, - test_helpers::{ - ForgeTestData, ForgeTestProfile, RE_PATH_SEPARATOR, TEST_DATA_DEFAULT, - TEST_DATA_MULTI_VERSION, TEST_DATA_PARIS, - }, -}; -use alloy_primitives::U256; -use foundry_cli::utils::install_crypto_provider; -use foundry_compilers::artifacts::output_selection::ContractOutputSelection; -use foundry_config::{FsPermissions, fs_permissions::PathPermission}; -use foundry_test_utils::{Filter, init_tracing, util::get_compiled}; - -/// Executes all cheat code tests but not fork cheat codes or tests that require isolation mode or -/// specific seed. -async fn test_cheats_local(test_data: &ForgeTestData) { - let mut filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")) - .exclude_paths("Fork") - .exclude_contracts("(Isolated|WithSeed|StateDiff|GetStorageSlotsTest)"); - - // Exclude FFI tests on Windows because no `echo`, and file tests that expect certain file paths - if cfg!(windows) { - filter = filter.exclude_tests("(Ffi|File|Line|Root)"); - } - - if cfg!(feature = "isolate-by-default") { - filter = filter.exclude_contracts( - "(LastCallGasDefaultTest|MockFunctionTest|WithSeed|StateDiff|GetStorageSlotsTest|RecordAccount)", - ); - } - - let runner = test_data.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - }); - - TestConfig::with_filter(runner, filter).run().await; -} - -/// Executes subset of all cheat code tests in isolation mode. -async fn test_cheats_local_isolated(test_data: &ForgeTestData) { - let filter = Filter::new(".*", ".*(Isolated)", &format!(".*cheats{RE_PATH_SEPARATOR}*")) - .exclude_contracts("(StateDiff|GetStorageSlotsTest)"); - - let runner = test_data.runner_with(|config| { - config.isolate = true; - }); - - TestConfig::with_filter(runner, filter).run().await; -} - -/// Executes subset of all cheat code tests using a specific seed. -async fn test_cheats_local_with_seed(test_data: &ForgeTestData) { - let filter = Filter::new(".*", ".*(WithSeed)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); - - let runner = test_data.runner_with(|config| { - config.fuzz.seed = Some(U256::from(100)); - }); - - TestConfig::with_filter(runner, filter).run().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_default() { - test_cheats_local(&TEST_DATA_DEFAULT).await -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_state_diff_storage_layout() { - let test_data = { - let profile = ForgeTestProfile::Default; - install_crypto_provider(); - init_tracing(); - let mut config = profile.config(); - config.extra_output = vec![ContractOutputSelection::StorageLayout]; - let mut project = config.project().unwrap(); - // Compile with StorageLayout - let output = get_compiled(&mut project); - ForgeTestData { project, output, config: config.into(), profile } - }; - let filter = Filter::new( - ".*", - "(StateDiff|GetStorageSlotsTest)", - &format!(".*cheats{RE_PATH_SEPARATOR}*"), - ); - - let runner = test_data.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - }); - - TestConfig::with_filter(runner, filter).run().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_default_isolated() { - test_cheats_local_isolated(&TEST_DATA_DEFAULT).await -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_default_with_seed() { - test_cheats_local_with_seed(&TEST_DATA_DEFAULT).await -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_multi_version() { - test_cheats_local(&TEST_DATA_MULTI_VERSION).await -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local_paris() { - test_cheats_local(&TEST_DATA_PARIS).await -} diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs deleted file mode 100644 index b028b45005b8d..0000000000000 --- a/crates/forge/tests/it/config.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Test config. - -use forge::{ - MultiContractRunner, - result::{SuiteResult, TestStatus}, -}; -use foundry_evm::{ - decode::decode_console_logs, - traces::{CallTraceDecoderBuilder, decode_trace_arena, render_trace_arena}, -}; -use foundry_test_utils::{Filter, init_tracing}; -use futures::future::join_all; -use itertools::Itertools; -use revm::primitives::hardfork::SpecId; -use std::collections::BTreeMap; - -/// How to execute a test run. -pub struct TestConfig { - pub runner: MultiContractRunner, - pub should_fail: bool, - pub filter: Filter, -} - -impl TestConfig { - pub fn new(runner: MultiContractRunner) -> Self { - Self::with_filter(runner, Filter::matches_all()) - } - - pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self { - init_tracing(); - Self { runner, should_fail: false, filter } - } - - pub fn spec_id(mut self, spec: SpecId) -> Self { - self.runner.spec_id = spec; - self - } - - pub fn should_fail(self) -> Self { - self.set_should_fail(true) - } - - pub fn set_should_fail(mut self, should_fail: bool) -> Self { - self.should_fail = should_fail; - self - } - - /// Executes the test runner - pub fn test(&mut self) -> eyre::Result> { - self.runner.test_collect(&self.filter) - } - - pub async fn run(&mut self) { - self.try_run().await.unwrap() - } - - /// Executes the test case - /// - /// Returns an error if - /// * filter matched 0 test cases - /// * a test results deviates from the configured `should_fail` setting - pub async fn try_run(&mut self) -> eyre::Result<()> { - let suite_result = self.test()?; - if suite_result.is_empty() { - eyre::bail!("empty test result"); - } - for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, mut result) in test_results { - if self.should_fail && (result.status == TestStatus::Success) - || !self.should_fail && (result.status == TestStatus::Failure) - { - let logs = decode_console_logs(&result.logs); - let outcome = if self.should_fail { "fail" } else { "pass" }; - let call_trace_decoder = CallTraceDecoderBuilder::default() - .with_known_contracts(&self.runner.known_contracts) - .build(); - let decoded_traces = join_all(result.traces.iter_mut().map(|(_, arena)| { - let decoder = &call_trace_decoder; - async move { - decode_trace_arena(arena, decoder).await; - render_trace_arena(arena) - } - })) - .await - .into_iter() - .collect::>(); - eyre::bail!( - "Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}\n\nTraces:\n{}", - test_name, - outcome, - result.reason, - logs.join("\n"), - decoded_traces.into_iter().format("\n"), - ) - } - } - } - - Ok(()) - } -} - -/// A helper to assert the outcome of multiple tests with helpful assert messages -#[track_caller] -#[expect(clippy::type_complexity)] -pub fn assert_multiple( - actuals: &BTreeMap, - expecteds: BTreeMap< - &str, - Vec<(&str, bool, Option, Option>, Option)>, - >, -) { - assert_eq!(actuals.len(), expecteds.len(), "We did not run as many contracts as we expected"); - for (contract_name, tests) in &expecteds { - assert!( - actuals.contains_key(*contract_name), - "We did not run the contract {contract_name}" - ); - - assert_eq!( - actuals[*contract_name].len(), - expecteds[contract_name].len(), - "We did not run as many test functions as we expected for {contract_name}" - ); - for (test_name, should_pass, reason, expected_logs, expected_warning_count) in tests { - let logs = &decode_console_logs(&actuals[*contract_name].test_results[*test_name].logs); - - let warnings_count = &actuals[*contract_name].warnings.len(); - - if *should_pass { - assert!( - actuals[*contract_name].test_results[*test_name].status == TestStatus::Success, - "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", - test_name, - actuals[*contract_name].test_results[*test_name].reason, - logs.join("\n") - ); - } else { - assert!( - actuals[*contract_name].test_results[*test_name].status == TestStatus::Failure, - "Test {} did not fail as expected.\nLogs:\n{}", - test_name, - logs.join("\n") - ); - assert_eq!( - actuals[*contract_name].test_results[*test_name].reason, *reason, - "Failure reason for test {test_name} did not match what we expected." - ); - } - - if let Some(expected_logs) = expected_logs { - assert_eq!( - logs, - expected_logs, - "Logs did not match for test {}.\nExpected:\n{}\n\nGot:\n{}", - test_name, - expected_logs.join("\n"), - logs.join("\n") - ); - } - - if let Some(expected_warning_count) = expected_warning_count { - assert_eq!( - warnings_count, expected_warning_count, - "Test {test_name} did not pass as expected. Expected:\n{expected_warning_count}Got:\n{warnings_count}" - ); - } - } - } -} diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs deleted file mode 100644 index 161f4db363c20..0000000000000 --- a/crates/forge/tests/it/core.rs +++ /dev/null @@ -1,829 +0,0 @@ -//! Forge tests for core functionality. - -use crate::{ - config::*, - test_helpers::{TEST_DATA_DEFAULT, TEST_DATA_PARIS}, -}; -use forge::result::SuiteResult; -use foundry_evm::traces::TraceKind; -use foundry_test_utils::Filter; -use std::{collections::BTreeMap, env}; - -#[tokio::test(flavor = "multi_thread")] -async fn test_core() { - let filter = Filter::new(".*", ".*", ".*core"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/core/Reverting.t.sol:RevertingTest", - vec![("testRevert()", true, None, None, None)], - ), - ( - "default/core/SetupConsistency.t.sol:SetupConsistencyCheck", - vec![ - ("testAdd()", true, None, None, None), - ("testMultiply()", true, None, None, None), - ], - ), - ( - "default/core/ContractEnvironment.t.sol:ContractEnvironmentTest", - vec![ - ("testAddresses()", true, None, None, None), - ("testEnvironment()", true, None, None, None), - ], - ), - ( - "default/core/PaymentFailure.t.sol:PaymentFailureTest", - vec![("testCantPay()", false, Some("EvmError: Revert".to_string()), None, None)], - ), - ( - "default/core/Abstract.t.sol:AbstractTest", - vec![("testSomething()", true, None, None, None)], - ), - ( - "default/core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", - vec![("setUp()", false, Some("execution error".to_string()), None, None)], - ), - ( - "default/core/BadSigAfterInvariant.t.sol:BadSigAfterInvariant", - vec![("testShouldPassWithWarning()", true, None, None, None)], - ), - ( - "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", - vec![( - "testMultipleAssertFailures()", - false, - Some("assertion failed: 1 != 2".to_string()), - None, - None, - )], - ), - ( - "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", - vec![ - ("testFlagNotSetSuccess()", true, None, None, None), - ("testFlagSetFailure()", true, None, None, None), - ], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_linking() { - let filter = Filter::new(".*", ".*", ".*linking"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", - vec![("testCall()", true, None, None, None)], - ), - ( - "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", - vec![ - ("testDirect()", true, None, None, None), - ("testNested()", true, None, None, None), - ], - ), - ( - "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", - vec![ - ("testA()", true, None, None, None), - ("testB()", true, None, None, None), - ("testC()", true, None, None, None), - ("testD()", true, None, None, None), - ("testE()", true, None, None, None), - ], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_logs() { - let filter = Filter::new(".*", ".*", ".*logs"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([ - ( - "default/logs/DebugLogs.t.sol:DebugLogsTest", - vec![ - ("test1()", true, None, Some(vec!["0".into(), "1".into(), "2".into()]), None), - ("test2()", true, None, Some(vec!["0".into(), "1".into(), "3".into()]), None), - ( - "testRevertIfWithRequire()", - true, - None, - Some(vec!["0".into(), "1".into(), "5".into()]), - None, - ), - ( - "testRevertIfWithRevert()", - true, - None, - Some(vec!["0".into(), "1".into(), "4".into(), "100".into()]), - None, - ), - ( - "testLog()", - true, - None, - Some(vec!["0".into(), "1".into(), "Error: Assertion Failed".into()]), - None, - ), - ( - "testLogs()", - true, - None, - Some(vec!["0".into(), "1".into(), "0x61626364".into()]), - None, - ), - ( - "testLogAddress()", - true, - None, - Some(vec![ - "0".into(), - "1".into(), - "0x0000000000000000000000000000000000000001".into(), - ]), - None, - ), - ( - "testLogBytes32()", - true, - None, - Some(vec![ - "0".into(), - "1".into(), - "0x6162636400000000000000000000000000000000000000000000000000000000" - .into(), - ]), - None, - ), - ( - "testLogInt()", - true, - None, - Some(vec!["0".into(), "1".into(), "-31337".into()]), - None, - ), - ( - "testLogBytes()", - true, - None, - Some(vec!["0".into(), "1".into(), "0x61626364".into()]), - None, - ), - ( - "testLogString()", - true, - None, - Some(vec!["0".into(), "1".into(), "here".into()]), - None, - ), - ( - "testLogNamedAddress()", - true, - None, - Some(vec![ - "0".into(), - "1".into(), - "address: 0x0000000000000000000000000000000000000001".into(), - ]), - None, - ), - ( - "testLogNamedBytes32()", - true, - None, - Some(vec![ - "0".into(), - "1".into(), - "abcd: 0x6162636400000000000000000000000000000000000000000000000000000000" - .into(), - ]), - None, - ), - ( - "testLogNamedDecimalInt()", - true, - None, - Some(vec!["0".into(), "1".into(), "amount: -0.000000000000031337".into()]), - None, - ), - ( - "testLogNamedDecimalUint()", - true, - None, - Some(vec!["0".into(), "1".into(), "amount: 1.000000000000000000".into()]), - None, - ), - ( - "testLogNamedInt()", - true, - None, - Some(vec!["0".into(), "1".into(), "amount: -31337".into()]), - None, - ), - ( - "testLogNamedUint()", - true, - None, - Some(vec!["0".into(), "1".into(), "amount: 1000000000000000000".into()]), - None, - ), - ( - "testLogNamedBytes()", - true, - None, - Some(vec!["0".into(), "1".into(), "abcd: 0x61626364".into()]), - None, - ), - ( - "testLogNamedString()", - true, - None, - Some(vec!["0".into(), "1".into(), "key: val".into()]), - None, - ), - ], - ), - ( - "default/logs/HardhatLogs.t.sol:HardhatLogsTest", - vec![ - ( - "testInts()", - true, - None, - Some(vec![ - "constructor".into(), - "0".into(), - "1".into(), - "2".into(), - "3".into(), - ]), - None, - ), - ( - "testMisc()", - true, - None, - Some(vec![ - "constructor".into(), - "testMisc 0x0000000000000000000000000000000000000001".into(), - "testMisc 42".into(), - ]), - None, - ), - ( - "testStrings()", - true, - None, - Some(vec!["constructor".into(), "testStrings".into()]), - None, - ), - ( - "testConsoleLog()", - true, - None, - Some(vec!["constructor".into(), "test".into()]), - None, - ), - ( - "testLogInt()", - true, - None, - Some(vec!["constructor".into(), "-31337".into()]), - None, - ), - ( - "testLogUint()", - true, - None, - Some(vec!["constructor".into(), "1".into()]), - None, - ), - ( - "testLogString()", - true, - None, - Some(vec!["constructor".into(), "test".into()]), - None, - ), - ( - "testLogBool()", - true, - None, - Some(vec!["constructor".into(), "false".into()]), - None, - ), - ( - "testLogAddress()", - true, - None, - Some(vec![ - "constructor".into(), - "0x0000000000000000000000000000000000000001".into(), - ]), - None, - ), - ( - "testLogBytes()", - true, - None, - Some(vec!["constructor".into(), "0x61".into()]), - None, - ), - ( - "testLogBytes1()", - true, - None, - Some(vec!["constructor".into(), "0x61".into()]), - None, - ), - ( - "testLogBytes2()", - true, - None, - Some(vec!["constructor".into(), "0x6100".into()]), - None, - ), - ( - "testLogBytes3()", - true, - None, - Some(vec!["constructor".into(), "0x610000".into()]), - None, - ), - ( - "testLogBytes4()", - true, - None, - Some(vec!["constructor".into(), "0x61000000".into()]), - None, - ), - ( - "testLogBytes5()", - true, - None, - Some(vec!["constructor".into(), "0x6100000000".into()]), - None, - ), - ( - "testLogBytes6()", - true, - None, - Some(vec!["constructor".into(), "0x610000000000".into()]), - None, - ), - ( - "testLogBytes7()", - true, - None, - Some(vec!["constructor".into(), "0x61000000000000".into()]), - None, - ), - ( - "testLogBytes8()", - true, - None, - Some(vec!["constructor".into(), "0x6100000000000000".into()]), - None, - ), - ( - "testLogBytes9()", - true, - None, - Some(vec!["constructor".into(), "0x610000000000000000".into()]), - None, - ), - ( - "testLogBytes10()", - true, - None, - Some(vec!["constructor".into(), "0x61000000000000000000".into()]), - None, - ), - ( - "testLogBytes11()", - true, - None, - Some(vec!["constructor".into(), "0x6100000000000000000000".into()]), - None, - ), - ( - "testLogBytes12()", - true, - None, - Some(vec!["constructor".into(), "0x610000000000000000000000".into()]), - None, - ), - ( - "testLogBytes13()", - true, - None, - Some(vec!["constructor".into(), "0x61000000000000000000000000".into()]), - None, - ), - ( - "testLogBytes14()", - true, - None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000".into()]), - None, - ), - ( - "testLogBytes15()", - true, - None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000".into()]), - None, - ), - ( - "testLogBytes16()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes17()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes18()", - true, - None, - Some(vec![ - "constructor".into(), - "0x610000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes19()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes20()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes21()", - true, - None, - Some(vec![ - "constructor".into(), - "0x610000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes22()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes23()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes24()", - true, - None, - Some(vec![ - "constructor".into(), - "0x610000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes25()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes26()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes27()", - true, - None, - Some(vec![ - "constructor".into(), - "0x610000000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes28()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes29()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes30()", - true, - None, - Some(vec![ - "constructor".into(), - "0x610000000000000000000000000000000000000000000000000000000000".into(), - ]), - None, - ), - ( - "testLogBytes31()", - true, - None, - Some(vec![ - "constructor".into(), - "0x61000000000000000000000000000000000000000000000000000000000000" - .into(), - ]), - None, - ), - ( - "testLogBytes32()", - true, - None, - Some(vec![ - "constructor".into(), - "0x6100000000000000000000000000000000000000000000000000000000000000" - .into(), - ]), - None, - ), - ( - "testConsoleLogUint()", - true, - None, - Some(vec!["constructor".into(), "1".into()]), - None, - ), - ( - "testConsoleLogString()", - true, - None, - Some(vec!["constructor".into(), "test".into()]), - None, - ), - ( - "testConsoleLogBool()", - true, - None, - Some(vec!["constructor".into(), "false".into()]), - None, - ), - ( - "testConsoleLogAddress()", - true, - None, - Some(vec![ - "constructor".into(), - "0x0000000000000000000000000000000000000001".into(), - ]), - None, - ), - ( - "testConsoleLogFormatString()", - true, - None, - Some(vec!["constructor".into(), "formatted log str=test".into()]), - None, - ), - ( - "testConsoleLogFormatUint()", - true, - None, - Some(vec!["constructor".into(), "formatted log uint=1".into()]), - None, - ), - ( - "testConsoleLogFormatAddress()", - true, - None, - Some(vec![ - "constructor".into(), - "formatted log addr=0x0000000000000000000000000000000000000001".into(), - ]), - None, - ), - ( - "testConsoleLogFormatMulti()", - true, - None, - Some(vec!["constructor".into(), "formatted log str=test uint=1".into()]), - None, - ), - ( - "testConsoleLogFormatEscape()", - true, - None, - Some(vec!["constructor".into(), "formatted log % test".into()]), - None, - ), - ( - "testConsoleLogFormatSpill()", - true, - None, - Some(vec!["constructor".into(), "formatted log test 1".into()]), - None, - ), - ], - ), - ]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_env_vars() { - let env_var_key = "_foundryCheatcodeSetEnvTestKey"; - let env_var_val = "_foundryCheatcodeSetEnvTestVal"; - unsafe { - env::remove_var(env_var_key); - } - - let filter = Filter::new("testSetEnv", ".*", ".*"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let _ = runner.test_collect(&filter); - - assert_eq!(env::var(env_var_key).unwrap(), env_var_val); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_doesnt_run_abstract_contract() { - let filter = Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()); - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner.test_collect(&filter).unwrap(); - assert!(!results.contains_key("default/core/Abstract.t.sol:AbstractTestBase")); - assert!(results.contains_key("default/core/Abstract.t.sol:AbstractTest")); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_trace() { - let filter = Filter::new(".*", ".*", ".*trace"); - let mut runner = TEST_DATA_DEFAULT.tracing_runner(); - let suite_result = runner.test_collect(&filter).unwrap(); - - // TODO: This trace test is very basic - it is probably a good candidate for snapshot - // testing. - for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, result) in test_results { - let deployment_traces = - result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Deployment); - let setup_traces = result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Setup); - let execution_traces = - result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Execution); - - assert_eq!( - deployment_traces.count(), - 14, - "Test {test_name} did not have exactly 14 deployment traces." - ); - assert!(setup_traces.count() <= 1, "Test {test_name} had more than 1 setup trace."); - assert_eq!( - execution_traces.count(), - 1, - "Test {test_name} did not have exactly 1 execution trace." - ); - } - } -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_assertions_revert_false() { - let filter = Filter::new(".*", ".*NoAssertionsRevertTest", ".*"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.assertions_revert = false; - }); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([( - "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", - vec![( - "testMultipleAssertFailures()", - false, - None, - Some(vec![ - "assertion failed: 1 != 2".to_string(), - "assertion failed: 5 >= 4".to_string(), - ]), - None, - )], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_legacy_assertions() { - let filter = Filter::new(".*", ".*LegacyAssertions", ".*"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.legacy_assertions = true; - }); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([( - "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", - vec![ - ("testFlagNotSetSuccess()", true, None, None, None), - ("testFlagSetFailure()", false, None, None, None), - ], - )]), - ); -} - -/// Test `beforeTest` functionality and `selfdestruct`. -/// See -#[tokio::test(flavor = "multi_thread")] -async fn test_before_setup_with_selfdestruct() { - let filter = Filter::new(".*", ".*BeforeTestSelfDestructTest", ".*"); - let results = TEST_DATA_PARIS.runner().test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([( - "paris/core/BeforeTest.t.sol:BeforeTestSelfDestructTest", - vec![ - ("testKill()", true, None, None, None), - ("testA()", true, None, None, None), - ("testSimpleA()", true, None, None, None), - ("testB()", true, None, None, None), - ("testC(uint256)", true, None, None, None), - ], - )]), - ); -} diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs deleted file mode 100644 index 92038ead6df9f..0000000000000 --- a/crates/forge/tests/it/fork.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Forge forking tests. - -use crate::{ - config::*, - test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT, TEST_DATA_PARIS}, -}; -use alloy_chains::Chain; -use forge::result::SuiteResult; -use foundry_config::{Config, FsPermissions, fs_permissions::PathPermission}; -use foundry_test_utils::Filter; -use std::fs; - -/// Executes reverting fork test -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_fork_revert() { - let filter = Filter::new( - "testNonExistingContractRevert", - ".*", - &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), - ); - let mut runner = TEST_DATA_DEFAULT.runner(); - let suite_result = runner.test_collect(&filter).unwrap(); - assert_eq!(suite_result.len(), 1); - - for (_, SuiteResult { test_results, .. }) in suite_result { - for (_, result) in test_results { - assert_eq!( - result.reason.unwrap(), - "Contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f does not exist on active fork with id `1`\n But exists on non active forks: `[0]`" - ); - } - } -} - -/// Executes all non-reverting fork cheatcodes -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_fork() { - let runner = TEST_DATA_PARIS.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - }); - let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) - .exclude_tests(".*Revert"); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Executes eth_getLogs cheatcode -#[tokio::test(flavor = "multi_thread")] -async fn test_get_logs_fork() { - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - }); - let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) - .exclude_tests(".*Revert"); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Executes rpc cheatcode -#[tokio::test(flavor = "multi_thread")] -async fn test_rpc_fork() { - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - }); - let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) - .exclude_tests(".*Revert"); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Tests that we can launch in forking mode -#[tokio::test(flavor = "multi_thread")] -async fn test_launch_fork() { - let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_url(); - let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; - let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Smoke test that forking workings with websockets -#[tokio::test(flavor = "multi_thread")] -async fn test_launch_fork_ws() { - let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_url(); - let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; - let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Tests that we can transact transactions in forking mode -#[tokio::test(flavor = "multi_thread")] -async fn test_transact_fork() { - let runner = TEST_DATA_PARIS.runner(); - let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Transact")); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Tests that we can create the same fork (provider,block) concurrently in different tests -#[tokio::test(flavor = "multi_thread")] -async fn test_create_same_fork() { - let runner = TEST_DATA_DEFAULT.runner(); - let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}ForkSame")); - TestConfig::with_filter(runner, filter).run().await; -} - -/// Test that `no_storage_caching` config is properly applied -#[tokio::test(flavor = "multi_thread")] -async fn test_storage_caching_config() { - let filter = - Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) - .exclude_tests(".*Revert"); - - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.no_storage_caching = true; - }); - - // no_storage_caching set to true: storage should not be cached - TestConfig::with_filter(runner, filter.clone()).run().await; - let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); - let _ = fs::remove_file(cache_dir); - - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.no_storage_caching = false; - }); - TestConfig::with_filter(runner, filter).run().await; - - // no_storage_caching set to false: storage should be cached - let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); - assert!(cache_dir.exists()); - - // cleanup cached storage so subsequent tests does not fail - let _ = fs::remove_file(cache_dir); -} diff --git a/crates/forge/tests/it/fs.rs b/crates/forge/tests/it/fs.rs deleted file mode 100644 index 3e9fb1f4dad82..0000000000000 --- a/crates/forge/tests/it/fs.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Filesystem tests. - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; -use foundry_config::{FsPermissions, fs_permissions::PathPermission}; -use foundry_test_utils::Filter; - -#[tokio::test(flavor = "multi_thread")] -async fn test_fs_disabled() { - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); - }); - let filter = Filter::new(".*", ".*", ".*fs/Disabled"); - TestConfig::with_filter(runner, filter).run().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_fs_default() { - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - }); - let filter = Filter::new(".*", ".*", ".*fs/Default"); - TestConfig::with_filter(runner, filter).run().await; -} diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs deleted file mode 100644 index 616b19a17f805..0000000000000 --- a/crates/forge/tests/it/fuzz.rs +++ /dev/null @@ -1,496 +0,0 @@ -//! Fuzz tests. - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; -use alloy_primitives::{Bytes, U256}; -use forge::{ - decode::decode_console_logs, - fuzz::CounterExample, - result::{SuiteResult, TestStatus}, -}; -use foundry_test_utils::{Filter, forgetest_init, str}; -use std::collections::BTreeMap; - -#[tokio::test(flavor = "multi_thread")] -async fn test_fuzz() { - let filter = Filter::new(".*", ".*", ".*fuzz/") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)|testStorageOwner\(address\)|testImmutableOwner\(address\)") - .exclude_paths("invariant"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let suite_result = runner.test_collect(&filter).unwrap(); - - assert!(!suite_result.is_empty()); - - for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, result) in test_results { - match test_name.as_str() { - "testPositive(uint256)" - | "testPositive(int256)" - | "testSuccessfulFuzz(uint128,uint128)" - | "testToStringFuzz(bytes32)" => assert_eq!( - result.status, - TestStatus::Success, - "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", - test_name, - result.reason, - decode_console_logs(&result.logs).join("\n") - ), - _ => assert_eq!( - result.status, - TestStatus::Failure, - "Test {} did not fail as expected.\nReason: {:?}\nLogs:\n{}", - test_name, - result.reason, - decode_console_logs(&result.logs).join("\n") - ), - } - } - } -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_successful_fuzz_cases() { - let filter = Filter::new(".*", ".*", ".*fuzz/FuzzPositive") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") - .exclude_paths("invariant"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let suite_result = runner.test_collect(&filter).unwrap(); - - assert!(!suite_result.is_empty()); - - for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, result) in test_results { - match test_name.as_str() { - "testSuccessChecker(uint256)" - | "testSuccessChecker2(int256)" - | "testSuccessChecker3(uint32)" => assert_eq!( - result.status, - TestStatus::Success, - "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", - test_name, - result.reason, - decode_console_logs(&result.logs).join("\n") - ), - _ => {} - } - } - } -} - -/// Test that showcases PUSH collection on normal fuzzing. Ignored until we collect them in a -/// smarter way. -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_fuzz_collection() { - let filter = Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.invariant.depth = 100; - config.invariant.runs = 1000; - config.fuzz.runs = 1000; - config.fuzz.seed = Some(U256::from(6u32)); - }); - let results = runner.test_collect(&filter).unwrap(); - - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/FuzzCollection.t.sol:SampleContractTest", - vec![ - ("invariantCounter", false, Some("broken counter.".into()), None, None), - ( - "testIncrement(address)", - false, - Some("Call did not revert as expected".into()), - None, - None, - ), - ("testNeedle(uint256)", false, Some("needle found.".into()), None, None), - ], - )]), - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_persist_fuzz_failure() { - let filter = Filter::new(".*", ".*", ".*fuzz/FuzzFailurePersist.t.sol"); - - macro_rules! run_fail { - () => { run_fail!(|config| {}) }; - (|$config:ident| $e:expr) => {{ - let mut runner = TEST_DATA_DEFAULT.runner_with(|$config| { - $config.fuzz.runs = 1000; - $e - }); - runner - .test_collect(&filter) - .unwrap() - .get("default/fuzz/FuzzFailurePersist.t.sol:FuzzFailurePersistTest") - .unwrap() - .test_results - .get("test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[])") - .unwrap() - .counterexample - .clone() - }}; - } - - // record initial counterexample calldata - let initial_counterexample = run_fail!(); - let initial_calldata = match initial_counterexample { - Some(CounterExample::Single(counterexample)) => counterexample.calldata, - _ => Bytes::new(), - }; - - // run several times and compare counterexamples calldata - for i in 0..10 { - let new_calldata = match run_fail!() { - Some(CounterExample::Single(counterexample)) => counterexample.calldata, - _ => Bytes::new(), - }; - // calldata should be the same with the initial one - assert_eq!(initial_calldata, new_calldata, "run {i}"); - } - - // write new failure in different dir. - let persist_dir = tempfile::tempdir().unwrap().keep(); - let new_calldata = match run_fail!(|config| config.fuzz.failure_persist_dir = Some(persist_dir)) - { - Some(CounterExample::Single(counterexample)) => counterexample.calldata, - _ => Bytes::new(), - }; - // empty file is used to load failure so new calldata is generated - assert_ne!(initial_calldata, new_calldata); -} - -forgetest_init!(test_can_scrape_bytecode, |prj, cmd| { - prj.update_config(|config| config.optimizer = Some(true)); - prj.add_source( - "FuzzerDict.sol", - r#" -// https://github.com/foundry-rs/foundry/issues/1168 -contract FuzzerDict { - // Immutables should get added to the dictionary. - address public immutable immutableOwner; - // Regular storage variables should also get added to the dictionary. - address public storageOwner; - - constructor(address _immutableOwner, address _storageOwner) { - immutableOwner = _immutableOwner; - storageOwner = _storageOwner; - } -} - "#, - ); - - prj.add_test( - "FuzzerDictTest.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; -import "src/FuzzerDict.sol"; - -contract FuzzerDictTest is Test { - FuzzerDict fuzzerDict; - - function setUp() public { - fuzzerDict = new FuzzerDict(address(100), address(200)); - } - - /// forge-config: default.fuzz.runs = 2000 - function testImmutableOwner(address who) public { - assertTrue(who != fuzzerDict.immutableOwner()); - } - - /// forge-config: default.fuzz.runs = 2000 - function testStorageOwner(address who) public { - assertTrue(who != fuzzerDict.storageOwner()); - } -} - "#, - ); - - // Test that immutable address is used as fuzzed input, causing test to fail. - cmd.args(["test", "--fuzz-seed", "119", "--mt", "testImmutableOwner"]).assert_failure(); - // Test that storage address is used as fuzzed input, causing test to fail. - cmd.forge_fuse() - .args(["test", "--fuzz-seed", "119", "--mt", "testStorageOwner"]) - .assert_failure(); -}); - -// tests that inline max-test-rejects config is properly applied -forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { - prj.wipe_contracts(); - - prj.add_test( - "Contract.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract InlineMaxRejectsTest is Test { - /// forge-config: default.fuzz.max-test-rejects = 1 - function test_fuzz_bound(uint256 a) public { - vm.assume(false); - } -} - "#, - ); - - cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" -... -[FAIL: `vm.assume` rejected too many inputs (1 allowed)] test_fuzz_bound(uint256) (runs: 0, [AVG_GAS]) -... -"#]]); -}); - -// Tests that test timeout config is properly applied. -// If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. -forgetest_init!(test_fuzz_timeout, |prj, cmd| { - prj.wipe_contracts(); - - prj.add_test( - "Contract.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract FuzzTimeoutTest is Test { - /// forge-config: default.fuzz.max-test-rejects = 50000 - /// forge-config: default.fuzz.timeout = 1 - function test_fuzz_bound(uint256 a) public pure { - vm.assume(a == 0); - } -} - "#, - ); - - cmd.args(["test"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for test/Contract.t.sol:FuzzTimeoutTest -[PASS] test_fuzz_bound(uint256) (runs: [..], [AVG_GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) - -"#]]); -}); - -forgetest_init!(test_fuzz_fail_on_revert, |prj, cmd| { - prj.wipe_contracts(); - prj.update_config(|config| config.fuzz.fail_on_revert = false); - prj.add_source( - "Counter.sol", - r#" -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - require(number > 10000000000, "low number"); - number = newNumber; - } -} - "#, - ); - - prj.add_test( - "CounterTest.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; -import "src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - } - - function testFuzz_SetNumberRequire(uint256 x) public { - counter.setNumber(x); - require(counter.number() == 1); - } - - function testFuzz_SetNumberAssert(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), 1); - } -} - "#, - ); - - // Tests should not fail as revert happens in Counter contract. - cmd.args(["test", "--mc", "CounterTest"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 2 tests for test/CounterTest.t.sol:CounterTest -[PASS] testFuzz_SetNumberAssert(uint256) (runs: 256, [AVG_GAS]) -[PASS] testFuzz_SetNumberRequire(uint256) (runs: 256, [AVG_GAS]) -Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) - -"#]]); - - // Tested contract does not revert. - prj.add_source( - "Counter.sol", - r#" -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } -} - "#, - ); - - // Tests should fail as revert happens in cheatcode (assert) and test (require) contract. - cmd.assert_failure().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 2 tests for test/CounterTest.t.sol:CounterTest -[FAIL: assertion failed: [..]] testFuzz_SetNumberAssert(uint256) (runs: 0, [AVG_GAS]) -[FAIL: EvmError: Revert; [..]] testFuzz_SetNumberRequire(uint256) (runs: 0, [AVG_GAS]) -Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] -... - -"#]]); -}); - -// Test 256 runs regardless number of test rejects. -// -forgetest_init!(test_fuzz_runs_with_rejects, |prj, cmd| { - prj.add_test( - "FuzzWithRejectsTest.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract FuzzWithRejectsTest is Test { - function testFuzzWithRejects(uint256 x) public pure { - vm.assume(x < 1_000_000); - } -} - "#, - ); - - // Tests should not fail as revert happens in Counter contract. - cmd.args(["test", "--mc", "FuzzWithRejectsTest"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for test/FuzzWithRejectsTest.t.sol:FuzzWithRejectsTest -[PASS] testFuzzWithRejects(uint256) (runs: 256, [AVG_GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) - -"#]]); -}); - -// Test that counterexample is not replayed if test changes. -// -forgetest_init!(test_fuzz_replay_with_changed_test, |prj, cmd| { - prj.update_config(|config| config.fuzz.seed = Some(U256::from(100u32))); - prj.add_test( - "Counter.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract CounterTest is Test { - function testFuzz_SetNumber(uint256 x) public pure { - require(x > 200); - } -} - "#, - ); - // Tests should fail and record counterexample with value 2. - cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" -... -Failing tests: -Encountered 1 failing test in test/Counter.t.sol:CounterTest -[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d70000000000000000000000000000000000000000000000000000000000000002 args=[2]] testFuzz_SetNumber(uint256) (runs: 19, [AVG_GAS]) -... - -"#]]); - - // Change test to assume counterexample 2 is discarded. - prj.add_test( - "Counter.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract CounterTest is Test { - function testFuzz_SetNumber(uint256 x) public pure { - vm.assume(x != 2); - } -} - "#, - ); - // Test should pass when replay failure with changed assume logic. - cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) - -"#]]); - - // Change test signature. - prj.add_test( - "Counter.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract CounterTest is Test { - function testFuzz_SetNumber(uint8 x) public pure { - } -} - "#, - ); - // Test should pass when replay failure with changed function signature. - cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint8) (runs: 256, [AVG_GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) - -"#]]); - - // Change test back to the original one that produced the counterexample. - prj.add_test( - "Counter.t.sol", - r#" -import {Test} from "forge-std/Test.sol"; - -contract CounterTest is Test { - function testFuzz_SetNumber(uint256 x) public pure { - require(x > 200); - } -} - "#, - ); - // Test should fail with replayed counterexample 2 (0 runs). - cmd.forge_fuse().args(["test"]).assert_failure().stdout_eq(str![[r#" -... -Failing tests: -Encountered 1 failing test in test/Counter.t.sol:CounterTest -[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d70000000000000000000000000000000000000000000000000000000000000002 args=[2]] testFuzz_SetNumber(uint256) (runs: 0, [AVG_GAS]) -... - -"#]]); -}); diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs deleted file mode 100644 index 475570a102d67..0000000000000 --- a/crates/forge/tests/it/inline.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Inline configuration tests. - -use crate::test_helpers::TEST_DATA_DEFAULT; -use forge::result::TestKind; -use foundry_test_utils::Filter; - -#[tokio::test(flavor = "multi_thread")] -async fn inline_config_run_fuzz() { - let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let result = runner.test_collect(&filter).unwrap(); - let results = result - .into_iter() - .flat_map(|(path, r)| { - r.test_results.into_iter().map(move |(name, t)| { - let runs = match t.kind { - TestKind::Fuzz { runs, .. } => runs, - _ => unreachable!(), - }; - (path.clone(), name, runs) - }) - }) - .collect::>(); - - assert_eq!( - results, - vec![ - ( - "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(), - "testInlineConfFuzz(uint8)".to_string(), - 1024 - ), - ( - "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), - "testInlineConfFuzz1(uint8)".to_string(), - 1 - ), - ( - "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), - "testInlineConfFuzz2(uint8)".to_string(), - 10 - ), - ] - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn inline_config_run_invariant() { - const ROOT: &str = "default/inline/InvariantInlineConf.t.sol"; - - let filter = Filter::new(".*", ".*", ".*inline/InvariantInlineConf.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let result = runner.test_collect(&filter).unwrap(); - - let suite_result_1 = result.get(&format!("{ROOT}:InvariantInlineConf")).expect("Result exists"); - let suite_result_2 = - result.get(&format!("{ROOT}:InvariantInlineConf2")).expect("Result exists"); - - let test_result_1 = suite_result_1.test_results.get("invariant_neverFalse()").unwrap(); - match test_result_1.kind { - TestKind::Invariant { runs, .. } => assert_eq!(runs, 333), - _ => unreachable!(), - } - - let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); - match test_result_2.kind { - TestKind::Invariant { runs, .. } => assert_eq!(runs, 42), - _ => unreachable!(), - } -} diff --git a/crates/forge/tests/it/main.rs b/crates/forge/tests/it/main.rs deleted file mode 100644 index c8890af3d4614..0000000000000 --- a/crates/forge/tests/it/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod config; -pub mod test_helpers; - -mod cheats; -mod core; -mod fork; -mod fs; -mod fuzz; -mod inline; -mod invariant; -mod repros; -mod spec; -mod table; -mod vyper; diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs deleted file mode 100644 index e8b1228ad2a5d..0000000000000 --- a/crates/forge/tests/it/repros.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Regression tests for previous issues. - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; -use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; -use alloy_json_abi::Event; -use alloy_primitives::{Address, U256, address, b256}; -use forge::{ - decode::decode_console_logs, - result::{TestKind, TestStatus}, -}; -use foundry_config::{Config, FsPermissions, fs_permissions::PathPermission}; -use foundry_evm::{ - constants::HARDHAT_CONSOLE_ADDRESS, - traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}, -}; -use foundry_test_utils::Filter; -use std::sync::Arc; - -/// Creates a test that runs `testdata/repros/Issue{issue}.t.sol`. -macro_rules! test_repro { - ($(#[$attr:meta])* $issue_number:literal $(,)?) => { - test_repro!($(#[$attr])* $issue_number, false, None); - }; - ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr $(,)?) => { - test_repro!($(#[$attr])* $issue_number, $should_fail, None); - }; - ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr, $sender:expr $(,)?) => { - paste::paste! { - #[tokio::test(flavor = "multi_thread")] - $(#[$attr])* - async fn [< issue_ $issue_number >]() { - repro_config($issue_number, $should_fail, $sender.into()).await.run().await; - } - } - }; - ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr, $sender:expr, |$res:ident| $e:expr $(,)?) => { - paste::paste! { - #[tokio::test(flavor = "multi_thread")] - $(#[$attr])* - async fn [< issue_ $issue_number >]() { - let mut $res = repro_config($issue_number, $should_fail, $sender.into()).await.test().unwrap(); - $e - } - } - }; - ($(#[$attr:meta])* $issue_number:literal; |$config:ident| $e:expr $(,)?) => { - paste::paste! { - #[tokio::test(flavor = "multi_thread")] - $(#[$attr])* - async fn [< issue_ $issue_number >]() { - let mut $config = repro_config($issue_number, false, None).await; - $e - $config.run().await; - } - } - }; -} - -async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { - foundry_test_utils::init_tracing(); - let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - - let runner = TEST_DATA_DEFAULT.runner_with(|config| { - config.fs_permissions = FsPermissions::new(vec![ - PathPermission::read("./fixtures"), - PathPermission::read("out"), - ]); - if let Some(sender) = sender { - config.sender = sender; - } - }); - TestConfig::with_filter(runner, filter).set_should_fail(should_fail) -} - -// https://github.com/foundry-rs/foundry/issues/2623 -test_repro!(2623); - -// https://github.com/foundry-rs/foundry/issues/2629 -test_repro!(2629); - -// https://github.com/foundry-rs/foundry/issues/2723 -test_repro!(2723); - -// https://github.com/foundry-rs/foundry/issues/2898 -test_repro!(2898); - -// https://github.com/foundry-rs/foundry/issues/2956 -test_repro!(2956); - -// https://github.com/foundry-rs/foundry/issues/2984 -test_repro!(2984); - -// https://github.com/foundry-rs/foundry/issues/3055 -test_repro!(3055, true); - -// https://github.com/foundry-rs/foundry/issues/3077 -test_repro!(3077); - -// https://github.com/foundry-rs/foundry/issues/3110 -test_repro!(3110); - -// https://github.com/foundry-rs/foundry/issues/3119 -test_repro!(3119); - -// https://github.com/foundry-rs/foundry/issues/3189 -test_repro!(3189, true); - -// https://github.com/foundry-rs/foundry/issues/3190 -test_repro!(3190); - -// https://github.com/foundry-rs/foundry/issues/3192 -test_repro!(3192); - -// https://github.com/foundry-rs/foundry/issues/3220 -test_repro!(3220); - -// https://github.com/foundry-rs/foundry/issues/3221 -test_repro!(3221); - -// https://github.com/foundry-rs/foundry/issues/3223 -test_repro!(3223, false, address!("0xF0959944122fb1ed4CfaBA645eA06EED30427BAA")); - -// https://github.com/foundry-rs/foundry/issues/3347 -test_repro!(3347, false, None, |res| { - let mut res = res.remove("default/repros/Issue3347.t.sol:Issue3347Test").unwrap(); - let test = res.test_results.remove("test()").unwrap(); - assert_eq!(test.logs.len(), 1); - let event = Event::parse("event log2(uint256, uint256)").unwrap(); - let decoded = event.decode_log(&test.logs[0].data).unwrap(); - assert_eq!( - decoded, - DecodedEvent { - selector: Some(b256!( - "0x78b9a1f3b55d6797ab2c4537e83ee04ff0c65a1ca1bb39d79a62e0a78d5a8a57" - )), - indexed: vec![], - body: vec![ - DynSolValue::Uint(U256::from(1), 256), - DynSolValue::Uint(U256::from(2), 256) - ] - } - ); -}); - -// https://github.com/foundry-rs/foundry/issues/3596 -test_repro!(3596, true, None); - -// https://github.com/foundry-rs/foundry/issues/3653 -test_repro!(3653); - -// https://github.com/foundry-rs/foundry/issues/3661 -test_repro!(3661); - -// https://github.com/foundry-rs/foundry/issues/3674 -test_repro!(3674, false, address!("0xF0959944122fb1ed4CfaBA645eA06EED30427BAA")); - -// https://github.com/foundry-rs/foundry/issues/3685 -test_repro!(3685); - -// https://github.com/foundry-rs/foundry/issues/3703 -test_repro!( - #[ignore = "flaky polygon RPCs"] - 3703 -); - -// https://github.com/foundry-rs/foundry/issues/3708 -test_repro!(3708); - -// https://github.com/foundry-rs/foundry/issues/3753 -test_repro!(3753); - -// https://github.com/foundry-rs/foundry/issues/3792 -test_repro!(3792); - -// https://github.com/foundry-rs/foundry/issues/4232 -test_repro!(4232); - -// https://github.com/foundry-rs/foundry/issues/4402 -test_repro!(4402); - -// https://github.com/foundry-rs/foundry/issues/4586 -test_repro!(4586); - -// https://github.com/foundry-rs/foundry/issues/4630 -test_repro!(4630); - -// https://github.com/foundry-rs/foundry/issues/4640 -test_repro!(4640); - -// https://github.com/foundry-rs/foundry/issues/5038 -test_repro!(5038); - -// https://github.com/foundry-rs/foundry/issues/5808 -test_repro!(5808); - -// -test_repro!(5929); - -// -test_repro!(5935); - -// -test_repro!(5948); - -// https://github.com/foundry-rs/foundry/issues/6006 -test_repro!(6006); - -// https://github.com/foundry-rs/foundry/issues/6032 -test_repro!(6032); - -// https://github.com/foundry-rs/foundry/issues/6070 -test_repro!(6070); - -// https://github.com/foundry-rs/foundry/issues/6115 -test_repro!(6115); - -// https://github.com/foundry-rs/foundry/issues/6170 -test_repro!(6170, false, None, |res| { - let mut res = res.remove("default/repros/Issue6170.t.sol:Issue6170Test").unwrap(); - let test = res.test_results.remove("test()").unwrap(); - assert_eq!(test.status, TestStatus::Failure); - assert_eq!(test.reason, Some("log != expected log".to_string())); -}); - -// -test_repro!(6293); - -// https://github.com/foundry-rs/foundry/issues/6180 -test_repro!(6180); - -// https://github.com/foundry-rs/foundry/issues/6355 -test_repro!(6355, false, None, |res| { - let mut res = res.remove("default/repros/Issue6355.t.sol:Issue6355Test").unwrap(); - let test = res.test_results.remove("test_shouldFail()").unwrap(); - assert_eq!(test.status, TestStatus::Failure); - - let test = res.test_results.remove("test_shouldFailWithRevertToState()").unwrap(); - assert_eq!(test.status, TestStatus::Failure); -}); - -// https://github.com/foundry-rs/foundry/issues/6437 -test_repro!(6437); - -// Test we decode Hardhat console logs AND traces correctly. -// https://github.com/foundry-rs/foundry/issues/6501 -test_repro!(6501, false, None, |res| { - let mut res = res.remove("default/repros/Issue6501.t.sol:Issue6501Test").unwrap(); - let test = res.test_results.remove("test_hhLogs()").unwrap(); - assert_eq!(test.status, TestStatus::Success); - assert_eq!( - decode_console_logs(&test.logs), - ["a".to_string(), "1".to_string(), "b 2".to_string()] - ); - - let (kind, traces) = test.traces.last().unwrap().clone(); - let nodes = traces.arena.into_nodes(); - assert_eq!(kind, TraceKind::Execution); - - let test_call = nodes.first().unwrap(); - assert_eq!(test_call.idx, 0); - assert_eq!(test_call.children, [1, 2, 3]); - assert_eq!(test_call.trace.depth, 0); - assert!(!test_call.trace.is_error()); - - let expected = [ - ("log(string)", vec!["\"a\""]), - ("log(uint256)", vec!["1"]), - ("log(string,uint256)", vec!["\"b\"", "2"]), - ]; - for (node, expected) in nodes[1..=3].iter().zip(expected) { - let trace = &node.trace; - let decoded = CallTraceDecoder::new().decode_function(trace).await; - assert_eq!(trace.kind, CallKind::StaticCall); - assert_eq!(trace.address, HARDHAT_CONSOLE_ADDRESS); - assert_eq!(decoded.label, Some("console".into())); - assert_eq!(trace.depth, 1); - assert!(!trace.is_error()); - assert_eq!( - decoded.call_data, - Some(DecodedCallData { - signature: expected.0.into(), - args: expected.1.into_iter().map(ToOwned::to_owned).collect(), - }) - ); - } -}); - -// https://github.com/foundry-rs/foundry/issues/6538 -test_repro!(6538); - -// https://github.com/foundry-rs/foundry/issues/6554 -test_repro!(6554; |config| { - let path = config.runner.config.root.join("out/default/Issue6554.t.sol"); - - let mut prj_config = Config::clone(&config.runner.config); - prj_config.fs_permissions.add(PathPermission::read_write(path)); - config.runner.config = Arc::new(prj_config); -}); - -// https://github.com/foundry-rs/foundry/issues/6759 -test_repro!(6759); - -// https://github.com/foundry-rs/foundry/issues/6966 -test_repro!(6966); - -// https://github.com/foundry-rs/foundry/issues/6616 -test_repro!(6616); - -// https://github.com/foundry-rs/foundry/issues/5529 -test_repro!(5529; |config| { - let mut prj_config = Config::clone(&config.runner.config); - prj_config.always_use_create_2_factory = true; - config.runner.evm_opts.always_use_create_2_factory = true; - config.runner.config = Arc::new(prj_config); -}); - -// https://github.com/foundry-rs/foundry/issues/6634 -test_repro!(6634; |config| { - let mut prj_config = Config::clone(&config.runner.config); - prj_config.always_use_create_2_factory = true; - config.runner.evm_opts.always_use_create_2_factory = true; - config.runner.config = Arc::new(prj_config); -}); - -// https://github.com/foundry-rs/foundry/issues/7457 -test_repro!(7457); - -// https://github.com/foundry-rs/foundry/issues/7481 -test_repro!(7481); - -// https://github.com/foundry-rs/foundry/issues/5739 -test_repro!(5739); - -// https://github.com/foundry-rs/foundry/issues/8004 -test_repro!(8004); - -// https://github.com/foundry-rs/foundry/issues/2851 -test_repro!(2851, false, None, |res| { - let mut suite_result = res.remove("default/repros/Issue2851.t.sol:Issue2851Test").unwrap(); - let tr = suite_result.test_results.remove("invariantNotZero()").unwrap(); - assert_eq!(tr.status, TestStatus::Failure, "{tr}"); -}); - -// https://github.com/foundry-rs/foundry/issues/8006 -test_repro!(8006); - -// https://github.com/foundry-rs/foundry/issues/8277 -test_repro!(8277); - -// https://github.com/foundry-rs/foundry/issues/8287 -test_repro!(8287); - -// https://github.com/foundry-rs/foundry/issues/8168 -test_repro!(8168); - -// https://github.com/foundry-rs/foundry/issues/8383 -test_repro!(8383, false, None, |res| { - let mut res = res.remove("default/repros/Issue8383.t.sol:Issue8383Test").unwrap(); - let test = res.test_results.remove("testP256VerifyOutOfBounds()").unwrap(); - assert_eq!(test.status, TestStatus::Success); - match test.kind { - TestKind::Unit { gas } => assert_eq!(gas, 3101), - _ => panic!("not a unit test kind"), - } -}); - -// https://github.com/foundry-rs/foundry/issues/6643 -test_repro!(6643); - -// https://github.com/foundry-rs/foundry/issues/8971 -test_repro!(8971; |config| { - let mut prj_config = Config::clone(&config.runner.config); - prj_config.isolate = true; - config.runner.config = Arc::new(prj_config); -}); - -// https://github.com/foundry-rs/foundry/issues/8639 -test_repro!(8639); - -// https://github.com/foundry-rs/foundry/issues/8566 -test_repro!(8566); - -// https://github.com/foundry-rs/foundry/issues/9643 -test_repro!(9643); - -// https://github.com/foundry-rs/foundry/issues/7238 -test_repro!(7238); - -// https://github.com/foundry-rs/foundry/issues/10302 -test_repro!(10302); - -// https://github.com/foundry-rs/foundry/issues/10477 -test_repro!(10477); - -// https://github.com/foundry-rs/foundry/issues/10527 -test_repro!(10527); - -// https://github.com/foundry-rs/foundry/issues/10552 -test_repro!(10552); - -// https://github.com/foundry-rs/foundry/issues/10586 -test_repro!(10586); - -// https://github.com/foundry-rs/foundry/issues/10957 -test_repro!(10957); - -// https://github.com/foundry-rs/foundry/issues/11353 -test_repro!(11353); - -// https://github.com/foundry-rs/foundry/issues/11616 -test_repro!(11616); diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs deleted file mode 100644 index 280e6f483abb5..0000000000000 --- a/crates/forge/tests/it/test_helpers.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Test helpers for Forge integration tests. - -use alloy_chains::NamedChain; -use alloy_primitives::U256; -use forge::{MultiContractRunner, MultiContractRunnerBuilder}; -use foundry_cli::utils::install_crypto_provider; -use foundry_compilers::{ - Project, ProjectCompileOutput, SolcConfig, - artifacts::{EvmVersion, Settings}, - compilers::multi::MultiCompiler, -}; -use foundry_config::{ - Config, FsPermissions, FuzzConfig, FuzzCorpusConfig, FuzzDictionaryConfig, InvariantConfig, - RpcEndpointUrl, RpcEndpoints, fs_permissions::PathPermission, -}; -use foundry_evm::{constants::CALLER, opts::EvmOpts}; -use foundry_test_utils::{ - init_tracing, - rpc::{next_http_archive_rpc_url, next_rpc_endpoint}, - util::get_compiled, -}; -use revm::primitives::hardfork::SpecId; -use std::{ - env, fmt, - path::{Path, PathBuf}, - sync::{Arc, LazyLock}, -}; - -pub const RE_PATH_SEPARATOR: &str = "/"; -const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); - -/// Profile for the tests group. Used to configure separate configurations for test runs. -pub enum ForgeTestProfile { - Default, - Paris, - MultiVersion, -} - -impl fmt::Display for ForgeTestProfile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => write!(f, "default"), - Self::Paris => write!(f, "paris"), - Self::MultiVersion => write!(f, "multi-version"), - } - } -} - -impl ForgeTestProfile { - /// Returns true if the profile is Paris. - pub fn is_paris(&self) -> bool { - matches!(self, Self::Paris) - } - - pub fn root(&self) -> PathBuf { - PathBuf::from(TESTDATA) - } - - /// Configures the solc settings for the test profile. - pub fn solc_config(&self) -> SolcConfig { - let mut settings = Settings::default(); - - if matches!(self, Self::Paris) { - settings.evm_version = Some(EvmVersion::Paris); - } - - let settings = SolcConfig::builder().settings(settings).build(); - SolcConfig { settings } - } - - /// Build [Config] for test profile. - /// - /// Project source files are read from testdata/{profile_name} - /// Project output files are written to testdata/out/{profile_name} - /// Cache is written to testdata/cache/{profile_name} - /// - /// AST output is enabled by default to support inline configs. - pub fn config(&self) -> Config { - let mut config = Config::with_root(self.root()); - - config.ast = true; - config.src = self.root().join(self.to_string()); - config.out = self.root().join("out").join(self.to_string()); - config.cache_path = self.root().join("cache").join(self.to_string()); - - config.prompt_timeout = 0; - - config.optimizer = Some(true); - config.optimizer_runs = Some(200); - - config.gas_limit = u64::MAX.into(); - config.chain = None; - config.tx_origin = CALLER; - config.block_number = U256::from(1); - config.block_timestamp = U256::from(1); - - config.sender = CALLER; - config.initial_balance = U256::MAX; - config.ffi = true; - config.verbosity = 3; - config.memory_limit = 1 << 26; - - if self.is_paris() { - config.evm_version = EvmVersion::Paris; - } - - config.fuzz = FuzzConfig { - runs: 256, - fail_on_revert: true, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig { - include_storage: true, - include_push_bytes: true, - dictionary_weight: 40, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - gas_report_samples: 256, - corpus: FuzzCorpusConfig::default(), - failure_persist_dir: Some(tempfile::tempdir().unwrap().keep()), - show_logs: false, - timeout: None, - }; - config.invariant = InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - include_storage: true, - include_push_bytes: true, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - shrink_run_limit: 5000, - max_assume_rejects: 65536, - gas_report_samples: 256, - corpus: FuzzCorpusConfig::default(), - failure_persist_dir: Some( - tempfile::Builder::new() - .prefix(&format!("foundry-{self}")) - .tempdir() - .unwrap() - .keep(), - ), - show_metrics: true, - timeout: None, - show_solidity: false, - }; - - config.sanitized() - } -} - -/// Container for test data for a specific test profile. -pub struct ForgeTestData { - pub project: Project, - pub output: ProjectCompileOutput, - pub config: Arc, - pub profile: ForgeTestProfile, -} - -impl ForgeTestData { - /// Builds [ForgeTestData] for the given [ForgeTestProfile]. - /// - /// Uses [get_compiled] to lazily compile the project. - pub fn new(profile: ForgeTestProfile) -> Self { - install_crypto_provider(); - init_tracing(); - let config = Arc::new(profile.config()); - let mut project = config.project().unwrap(); - let output = get_compiled(&mut project); - Self { project, output, config, profile } - } - - /// Builds a base runner - pub fn base_runner(&self) -> MultiContractRunnerBuilder { - init_tracing(); - let config = self.config.clone(); - let mut runner = MultiContractRunnerBuilder::new(config).sender(self.config.sender); - if self.profile.is_paris() { - runner = runner.evm_spec(SpecId::MERGE); - } - runner - } - - /// Builds a non-tracing runner - pub fn runner(&self) -> MultiContractRunner { - self.runner_with(|_| {}) - } - - /// Builds a non-tracing runner - pub fn runner_with(&self, modify: impl FnOnce(&mut Config)) -> MultiContractRunner { - let mut config = (*self.config).clone(); - modify(&mut config); - self.runner_with_config(config) - } - - fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root().to_path_buf()); - - if config.fs_permissions.is_empty() { - config.fs_permissions = - FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - } - - let opts = config_evm_opts(&config); - - let mut builder = self.base_runner(); - let config = Arc::new(config); - builder.config = config.clone(); - builder - .enable_isolation(opts.isolate) - .sender(config.sender) - .build::(&self.output, opts.local_evm_env(), opts) - .unwrap() - } - - /// Builds a tracing runner - pub fn tracing_runner(&self) -> MultiContractRunner { - let mut opts = config_evm_opts(&self.config); - opts.verbosity = 5; - self.base_runner().build::(&self.output, opts.local_evm_env(), opts).unwrap() - } - - /// Builds a runner that runs against forked state - pub async fn forked_runner(&self, rpc: &str) -> MultiContractRunner { - let mut opts = config_evm_opts(&self.config); - - opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC - opts.fork_url = Some(rpc.to_string()); - - let env = opts.evm_env().await.expect("Could not instantiate fork environment"); - let fork = opts.get_fork(&Default::default(), env.clone()); - - self.base_runner().with_fork(fork).build::(&self.output, env, opts).unwrap() - } -} - -/// Default data for the tests group. -pub static TEST_DATA_DEFAULT: LazyLock = - LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::Default)); - -/// Data for tests requiring Paris support on Solc and EVM level. -pub static TEST_DATA_PARIS: LazyLock = - LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::Paris)); - -/// Data for tests requiring no specific version on Solc and EVM level. -pub static TEST_DATA_MULTI_VERSION: LazyLock = - LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::MultiVersion)); - -pub fn manifest_root() -> &'static Path { - let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); - // need to check here where we're executing the test from, if in `forge` we need to also allow - // `testdata` - if root.ends_with("forge") { - root = root.parent().unwrap(); - } - root -} - -/// the RPC endpoints used during tests -pub fn rpc_endpoints() -> RpcEndpoints { - RpcEndpoints::new([ - ("mainnet", RpcEndpointUrl::Url(next_http_archive_rpc_url())), - ("mainnet2", RpcEndpointUrl::Url(next_http_archive_rpc_url())), - ("sepolia", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Sepolia))), - ("optimism", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Optimism))), - ("arbitrum", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Arbitrum))), - ("polygon", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Polygon))), - ("bsc", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::BinanceSmartChain))), - ("avaxTestnet", RpcEndpointUrl::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), - ("moonbeam", RpcEndpointUrl::Url("https://moonbeam-rpc.publicnode.com".into())), - ("rpcEnvAlias", RpcEndpointUrl::Env("${RPC_ENV_ALIAS}".into())), - ]) -} - -fn config_evm_opts(config: &Config) -> EvmOpts { - config.to_figment(foundry_config::FigmentProviders::None).extract().unwrap() -} diff --git a/crates/forge/tests/it/vyper.rs b/crates/forge/tests/it/vyper.rs deleted file mode 100644 index c40b87541bfb9..0000000000000 --- a/crates/forge/tests/it/vyper.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Integration tests for EVM specifications. - -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; -use foundry_test_utils::Filter; - -#[tokio::test(flavor = "multi_thread")] -async fn test_basic_vyper_test() { - let filter = Filter::new("", "CounterTest", ".*vyper"); - TestConfig::with_filter(TEST_DATA_DEFAULT.runner(), filter).run().await; -} diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 43acb20cd24bf..33fad7418ac2f 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -409,9 +409,8 @@ mod tests { .to_string_lossy(); let identifier = format!("{source}:{}", id.name); - // Skip ds-test as it always has no dependencies etc. (and the path is outside root - // so is not sanitized) - if identifier.contains("DSTest") { + // Skip test utils as they always have no dependencies. + if identifier.contains("utils/") { return None; } @@ -701,7 +700,7 @@ mod tests { "default/linking/nested/Nested.t.sol:NestedLib", &[( "default/linking/nested/Nested.t.sol:Lib", - address!("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74"), + address!("0x773253227cce756e50c3993ec6366b3ec27786f9"), )], ) .assert_dependencies( @@ -711,12 +710,12 @@ mod tests { // have the same address and nonce. ( "default/linking/nested/Nested.t.sol:Lib", - Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") + Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9") .unwrap(), ), ( "default/linking/nested/Nested.t.sol:NestedLib", - Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") + Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe") .unwrap(), ), ], @@ -726,12 +725,12 @@ mod tests { &[ ( "default/linking/nested/Nested.t.sol:Lib", - Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") + Address::from_str("0x773253227cce756e50c3993ec6366b3ec27786f9") .unwrap(), ), ( "default/linking/nested/Nested.t.sol:NestedLib", - Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") + Address::from_str("0xac231df03403867b05d092c26fc91b6b83f4bebe") .unwrap(), ), ], diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index cf0844429b0ee..52b5446ebdb73 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -1,10 +1,10 @@ //! RPC API keys utilities. use foundry_config::{ - NamedChain, NamedChain::{ - Arbitrum, Base, BinanceSmartChainTestnet, Celo, Mainnet, Optimism, Polygon, Sepolia, + self, Arbitrum, Base, BinanceSmartChainTestnet, Celo, Mainnet, Optimism, Polygon, Sepolia, }, + RpcEndpointUrl, RpcEndpoints, }; use rand::seq::SliceRandom; use std::sync::{ @@ -90,6 +90,22 @@ fn next(list: &[T]) -> &T { &list[next_idx() % list.len()] } +/// the RPC endpoints used during tests +pub fn rpc_endpoints() -> RpcEndpoints { + RpcEndpoints::new([ + ("mainnet", RpcEndpointUrl::Url(next_http_archive_rpc_url())), + ("mainnet2", RpcEndpointUrl::Url(next_http_archive_rpc_url())), + ("sepolia", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Sepolia))), + ("optimism", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Optimism))), + ("arbitrum", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Arbitrum))), + ("polygon", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Polygon))), + ("bsc", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::BinanceSmartChain))), + ("avaxTestnet", RpcEndpointUrl::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), + ("moonbeam", RpcEndpointUrl::Url("https://moonbeam-rpc.publicnode.com".into())), + ("rpcEnvAlias", RpcEndpointUrl::Env("${RPC_ENV_ALIAS}".into())), + ]) +} + /// Returns the next _mainnet_ rpc URL in inline /// /// This will rotate all available rpc endpoints diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 2dfd11986d13c..839a3bfe8b224 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -21,17 +21,7 @@ fn init_script_cmd( cmd.forge_fuse(); cmd.set_current_dir(project_root); - cmd.args([ - "script", - "-R", - "ds-test/=lib/", - "-R", - "cheats/=cheats/", - target_contract, - "--root", - project_root.to_str().unwrap(), - "-vvvvv", - ]); + cmd.args(["script", target_contract, "--root", project_root.to_str().unwrap(), "-vvvvv"]); if let Some(rpc_url) = endpoint { cmd.args(["--fork-url", rpc_url]); @@ -125,11 +115,16 @@ impl ScriptTester { } /// Initialises the test contracts by copying them into the workspace - fn copy_testdata(current_dir: &Path) -> Result<()> { + fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); - fs::create_dir_all(current_dir.join("cheats"))?; - fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("cheats/Vm.sol"))?; - fs::copy(testdata.join("lib/ds-test/src/test.sol"), current_dir.join("lib/test.sol"))?; + let from_dir = testdata.join("utils"); + let to_dir = root.join("utils"); + fs::create_dir_all(&to_dir)?; + for entry in fs::read_dir(&from_dir)? { + let file = &entry?.path(); + let name = file.file_name().unwrap(); + fs::copy(file, to_dir.join(name))?; + } Ok(()) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index a8d33e615fede..1f58821a574a6 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,4 +1,4 @@ -use crate::init_tracing; +use crate::{init_tracing, rpc::rpc_endpoints}; use eyre::{Result, WrapErr}; use foundry_compilers::{ ArtifactOutput, ConfigurableArtifacts, PathStyle, Project, ProjectCompileOutput, @@ -642,6 +642,13 @@ impl TestProject { pretty_err(&file, fs::write(&file, config.to_string_pretty().unwrap())); } + /// Writes [`rpc_endpoints`] to the project's config. + pub fn add_rpc_endpoints(&self) { + self.update_config(|config| { + config.rpc_endpoints = rpc_endpoints(); + }); + } + /// Adds a source file to the project. pub fn add_source(&self, name: &str, contents: &str) -> PathBuf { self.inner.add_source(name, Self::add_source_prelude(contents)).unwrap() @@ -740,19 +747,26 @@ impl TestProject { /// Adds DSTest as a source under "test.sol" pub fn insert_ds_test(&self) -> PathBuf { - let s = include_str!("../../../testdata/lib/ds-test/src/test.sol"); - self.add_source("test.sol", s) + self.add_source("test.sol", include_str!("../../../testdata/utils/DSTest.sol")) + } + + /// Adds custom test utils under the "test/utils" directory. + pub fn insert_utils(&self) { + self.add_test("utils/DSTest.sol", include_str!("../../../testdata/utils/DSTest.sol")); + self.add_test("utils/Test.sol", include_str!("../../../testdata/utils/Test.sol")); + self.add_test("utils/Vm.sol", include_str!("../../../testdata/utils/Vm.sol")); + self.add_test("utils/console.sol", include_str!("../../../testdata/utils/console.sol")); } /// Adds `console.sol` as a source under "console.sol" pub fn insert_console(&self) -> PathBuf { - let s = include_str!("../../../testdata/default/logs/console.sol"); + let s = include_str!("../../../testdata/utils/console.sol"); self.add_source("console.sol", s) } /// Adds `Vm.sol` as a source under "Vm.sol" pub fn insert_vm(&self) -> PathBuf { - let s = include_str!("../../../testdata/cheats/Vm.sol"); + let s = include_str!("../../../testdata/utils/Vm.sol"); self.add_source("Vm.sol", s) } @@ -1039,14 +1053,26 @@ impl TestCommand { /// Runs the command, returning a [`snapbox`] object to assert the command output. #[track_caller] - pub fn assert(&mut self) -> OutputAssert { + pub fn assert_with(&mut self, f: &[RegexRedaction]) -> OutputAssert { let assert = OutputAssert::new(self.execute()); if self.redact_output { - return assert.with_assert(test_assert()); + let mut redactions = test_redactions(); + insert_redactions(f, &mut redactions); + return assert.with_assert( + snapbox::Assert::new() + .action_env(snapbox::assert::DEFAULT_ACTION_ENV) + .redact_with(redactions), + ); } assert } + /// Runs the command, returning a [`snapbox`] object to assert the command output. + #[track_caller] + pub fn assert(&mut self) -> OutputAssert { + self.assert_with(&[]) + } + /// Runs the command and asserts that it resulted in success. #[track_caller] pub fn assert_success(&mut self) -> OutputAssert { @@ -1142,16 +1168,9 @@ impl TestCommand { } } -fn test_assert() -> snapbox::Assert { - snapbox::Assert::new() - .action_env(snapbox::assert::DEFAULT_ACTION_ENV) - .redact_with(test_redactions()) -} - fn test_redactions() -> snapbox::Redactions { static REDACTIONS: LazyLock = LazyLock::new(|| { - let mut r = snapbox::Redactions::new(); - let redactions = [ + make_redactions(&[ ("[SOLC_VERSION]", r"Solc( version)? \d+.\d+.\d+"), ("[ELAPSED]", r"(finished )?in \d+(\.\d+)?\w?s( \(.*?s CPU time\))?"), ("[GAS]", r"[Gg]as( used)?: \d+"), @@ -1174,15 +1193,27 @@ fn test_redactions() -> snapbox::Redactions { "[ESTIMATED_AMOUNT_REQUIRED]", r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", ), - ]; - for (placeholder, re) in redactions { - r.insert(placeholder, Regex::new(re).expect(re)).expect(re); - } - r + ]) }); REDACTIONS.clone() } +/// A tuple of a placeholder and a regex replacement string. +type RegexRedaction = (&'static str, &'static str); + +/// Creates a [`snapbox`] redactions object from a list of regex redactions. +fn make_redactions(redactions: &[RegexRedaction]) -> snapbox::Redactions { + let mut r = snapbox::Redactions::new(); + insert_redactions(redactions, &mut r); + r +} + +fn insert_redactions(redactions: &[RegexRedaction], r: &mut snapbox::Redactions) { + for &(placeholder, re) in redactions { + r.insert(placeholder, Regex::new(re).expect(re)).expect(re); + } +} + /// Extension trait for [`Output`]. pub trait OutputExt { /// Returns the stdout as lossy string diff --git a/testdata/default/cheats/AccessList.t.sol b/testdata/default/cheats/AccessList.t.sol index 4615ab588adba..f59e8cdb439c3 100644 --- a/testdata/default/cheats/AccessList.t.sol +++ b/testdata/default/cheats/AccessList.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AccessListIsolatedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +/// forge-config: default.isolate = true +contract AccessListIsolatedTest is Test { function test_access_list() public { Write anotherWrite = new Write(); Write write = new Write(); diff --git a/testdata/default/cheats/Addr.t.sol b/testdata/default/cheats/Addr.t.sol index b0b3fefbdba79..82b8cf7d277f0 100644 --- a/testdata/default/cheats/Addr.t.sol +++ b/testdata/default/cheats/Addr.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AddrTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract AddrTest is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevertIfPkZero() public { vm.expectRevert("vm.addr: private key cannot be 0"); diff --git a/testdata/default/cheats/ArbitraryStorage.t.sol b/testdata/default/cheats/ArbitraryStorage.t.sol index 1b5585b681b3d..407d14448a996 100644 --- a/testdata/default/cheats/ArbitraryStorage.t.sol +++ b/testdata/default/cheats/ArbitraryStorage.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public a; @@ -27,9 +26,8 @@ contract Counter { } } -contract CounterArbitraryStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract CounterArbitraryStorageWithSeedTest is Test { function test_fresh_storage() public { uint256 index = 55; Counter counter = new Counter(); @@ -77,9 +75,8 @@ contract AContract { bytes32[] public d; } -contract AContractArbitraryStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract AContractArbitraryStorageWithSeedTest is Test { function test_arbitrary_storage_with_seed() public { AContract target = new AContract(); vm.setArbitraryStorage(address(target)); @@ -96,9 +93,8 @@ contract SymbolicStore { constructor() {} } -contract SymbolicStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract SymbolicStorageWithSeedTest is Test { function test_SymbolicStorage() public { uint256 slot = vm.randomUint(0, 100); address addr = 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8; @@ -125,8 +121,8 @@ contract SymbolicStorageWithSeedTest is DSTest { } // -contract ArbitraryStorageOverwriteWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); +/// forge-config: default.fuzz.seed = "100" +contract ArbitraryStorageOverwriteWithSeedTest is Test { uint256 _value; function testArbitraryStorageFalse(uint256 value) public { diff --git a/testdata/default/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol index ea093efc4fcb2..d29a7dba3e604 100644 --- a/testdata/default/cheats/Assert.t.sol +++ b/testdata/default/cheats/Assert.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract AssertionsTest is DSTest { +contract AssertionsTest is Test { string constant errorMessage = "User provided message"; uint256 constant maxDecimals = 77; - Vm constant vm = Vm(HEVM_ADDRESS); - function _abs(int256 a) internal pure returns (uint256) { // Required or it will fail when `a = type(int256).min` if (a == type(int256).min) { diff --git a/testdata/default/cheats/Assume.t.sol b/testdata/default/cheats/Assume.t.sol index 14ed341c9970e..91de77f246e3b 100644 --- a/testdata/default/cheats/Assume.t.sol +++ b/testdata/default/cheats/Assume.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AssumeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract AssumeTest is Test { function testAssume(uint8 x) public { vm.assume(x < 2 ** 7); assertTrue(x < 2 ** 7, "did not discard inputs"); diff --git a/testdata/default/cheats/AssumeNoRevert.t.sol b/testdata/default/cheats/AssumeNoRevert.t.sol index ea6d2d9747bdd..f6a017efec212 100644 --- a/testdata/default/cheats/AssumeNoRevert.t.sol +++ b/testdata/default/cheats/AssumeNoRevert.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import {DSTest as Test} from "ds-test/test.sol"; -import {Vm} from "cheats/Vm.sol"; +import "utils/Test.sol"; contract ReverterB { /// @notice has same error selectors as contract below to test the `reverter` param @@ -62,7 +61,6 @@ contract Reverter { contract ReverterTest is Test { Reverter reverter; - Vm _vm = Vm(HEVM_ADDRESS); function setUp() public { reverter = new Reverter(); @@ -70,7 +68,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector function testAssumeSelector(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, @@ -82,7 +80,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector and data function testAssumeWithDataSingle(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 2), partialMatch: false, @@ -94,7 +92,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector with any extra data (ie providing selector allows for arbitrary extra data) function testAssumeWithDataPartial(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), partialMatch: true, @@ -106,14 +104,14 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` assumptions are not cleared after a cheatcode call function testAssumeNotClearedAfterCheatcodeCall(uint256 x) public { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(0) }) ); - _vm.warp(block.timestamp + 1000); + vm.warp(block.timestamp + 1000); reverter.revertIf2(x); } @@ -130,7 +128,7 @@ contract ReverterTest is Test { partialMatch: false, reverter: address(reverter) }); - _vm.assumeNoRevert(revertData); + vm.assumeNoRevert(revertData); reverter.twoPossibleReverts(x); } @@ -147,7 +145,7 @@ contract ReverterTest is Test { partialMatch: false, reverter: address(reverter) }); - _vm.assumeNoRevert(revertData); + vm.assumeNoRevert(revertData); reverter.twoPossibleReverts(x); } } diff --git a/testdata/default/cheats/AttachBlob.t.sol b/testdata/default/cheats/AttachBlob.t.sol index db17c1fae5f4c..1be49ceef2e5c 100644 --- a/testdata/default/cheats/AttachBlob.t.sol +++ b/testdata/default/cheats/AttachBlob.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public counter; @@ -12,8 +11,7 @@ contract Counter { } } -contract AttachBlobTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract AttachBlobTest is Test { uint256 bobPk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; address bob = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; diff --git a/testdata/default/cheats/AttachDelegation.t.sol b/testdata/default/cheats/AttachDelegation.t.sol index a4e870d4cee9e..63e00462d85ba 100644 --- a/testdata/default/cheats/AttachDelegation.t.sol +++ b/testdata/default/cheats/AttachDelegation.t.sol @@ -1,13 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract AttachDelegationTest is DSTest { +contract AttachDelegationTest is Test { event ExecutedBy(uint256 id); - Vm constant vm = Vm(HEVM_ADDRESS); uint256 alice_pk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; address payable alice = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); uint256 bob_pk = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; diff --git a/testdata/default/cheats/Bank.t.sol b/testdata/default/cheats/Bank.t.sol index 166fbb16ac8ea..bcda62aa6d676 100644 --- a/testdata/default/cheats/Bank.t.sol +++ b/testdata/default/cheats/Bank.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract CoinbaseTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract CoinbaseTest is Test { function testCoinbase() public { vm.coinbase(0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8); assertEq(block.coinbase, 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8, "coinbase failed"); diff --git a/testdata/default/cheats/Base64.t.sol b/testdata/default/cheats/Base64.t.sol index fad7bbf4f297c..c96ca6b4b3a79 100644 --- a/testdata/default/cheats/Base64.t.sol +++ b/testdata/default/cheats/Base64.t.sol @@ -1,13 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; - -contract Base64Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract Base64Test is Test { function test_toBase64() public { bytes memory input = hex"00112233445566778899aabbccddeeff"; string memory expected = "ABEiM0RVZneImaq7zN3u/w=="; diff --git a/testdata/default/cheats/BlobBaseFee.t.sol b/testdata/default/cheats/BlobBaseFee.t.sol index 54fbc8f7f0616..6ce979d046eda 100644 --- a/testdata/default/cheats/BlobBaseFee.t.sol +++ b/testdata/default/cheats/BlobBaseFee.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BlobBaseFeeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BlobBaseFeeTest is Test { function test_blob_base_fee() public { vm.blobBaseFee(6969); assertEq(vm.getBlobBaseFee(), 6969); diff --git a/testdata/default/cheats/Blobhashes.t.sol b/testdata/default/cheats/Blobhashes.t.sol index 4a589b45a38ff..6cf672c540d29 100644 --- a/testdata/default/cheats/Blobhashes.t.sol +++ b/testdata/default/cheats/Blobhashes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BlobhashesTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BlobhashesTest is Test { function testSetAndGetBlobhashes() public { bytes32[] memory blobhashes = new bytes32[](2); blobhashes[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000001); diff --git a/testdata/default/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol index 9916ca0efc799..3912ea1756d82 100644 --- a/testdata/default/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import {Test as ForgeTest} from "utils/Test.sol"; library F { function t2() public pure returns (uint256) { @@ -10,7 +9,7 @@ library F { } } -contract Test is DSTest { +contract Test is ForgeTest { uint256 public changed = 0; function t(uint256 a) public returns (uint256) { @@ -33,9 +32,7 @@ contract Test is DSTest { } } -contract BroadcastTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTest is ForgeTest { // 1st anvil account address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // 2nd anvil account @@ -160,7 +157,7 @@ contract BroadcastTest is DSTest { // } } -contract NoLink is DSTest { +contract NoLink is ForgeTest { function t(uint256 a) public returns (uint256) { uint256 b = 0; for (uint256 i; i < a; i++) { @@ -179,9 +176,7 @@ interface INoLink { function t(uint256 a) external returns (uint256); } -contract BroadcastTestNoLinking is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestNoLinking is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -254,9 +249,7 @@ contract BroadcastTestNoLinking is DSTest { } } -contract BroadcastMix is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastMix is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -312,9 +305,7 @@ contract BroadcastMix is DSTest { } } -contract BroadcastTestSetup is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestSetup is ForgeTest { function setUp() public { // It predeployed a library first assert(vm.getNonce(msg.sender) == 1); @@ -338,9 +329,7 @@ contract BroadcastTestSetup is DSTest { } } -contract BroadcastTestLog is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestLog is ForgeTest { function run() public { uint256[] memory arr = new uint256[](2); arr[0] = 3; @@ -361,14 +350,12 @@ contract BroadcastTestLog is DSTest { } } -contract TestInitialBalance is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract TestInitialBalance is ForgeTest { function runCustomSender() public { // Make sure we're testing a different caller than the default one. assert(msg.sender != address(0x00a329c0648769A73afAc7F9381E08FB43dBEA72)); - // NodeConfig::test() sets the balance of the address used in this test to 100 ether. + // NodeConfig::test() sets the balance of the address used in this ForgeTest to 100 ether. assert(msg.sender.balance == 100 ether); vm.broadcast(); @@ -386,9 +373,7 @@ contract TestInitialBalance is DSTest { } } -contract MultiChainBroadcastNoLink is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MultiChainBroadcastNoLink is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -431,9 +416,7 @@ contract MultiChainBroadcastNoLink is DSTest { } } -contract MultiChainBroadcastLink is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MultiChainBroadcastLink is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -454,9 +437,7 @@ contract MultiChainBroadcastLink is DSTest { } } -contract BroadcastEmptySetUp is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastEmptySetUp is ForgeTest { function setUp() public {} function run() public { @@ -495,9 +476,7 @@ contract ContractB { } } -contract CheckOverrides is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract CheckOverrides is ForgeTest { function run() external { // `script_caller` can be set by `--private-key ...` or `--sender ...` // Otherwise it will take the default value of 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 @@ -546,9 +525,7 @@ contract Parent { } } -contract ScriptAdditionalContracts is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ScriptAdditionalContracts is ForgeTest { function run() external { vm.startBroadcast(); new Parent(); @@ -567,8 +544,7 @@ contract SignatureTester { } } -contract ScriptSign is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ScriptSign is ForgeTest { bytes32 digest = keccak256("something"); function run() external { diff --git a/testdata/default/cheats/BroadcastRawTransaction.t.sol b/testdata/default/cheats/BroadcastRawTransaction.t.sol index 3806580281f73..9cf6c4950e37a 100644 --- a/testdata/default/cheats/BroadcastRawTransaction.t.sol +++ b/testdata/default/cheats/BroadcastRawTransaction.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BroadcastRawTransactionTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BroadcastRawTransactionTest is Test { function test_revert_not_a_tx() public { vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: unexpected string"); vm.broadcastRawTransaction(hex"0102"); @@ -277,9 +274,7 @@ contract MyERC20 { } } -contract ScriptBroadcastRawTransactionBroadcast is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ScriptBroadcastRawTransactionBroadcast is Test { function runSignedTxBroadcast() public { uint256 pk_to = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; vm.startBroadcast(pk_to); diff --git a/testdata/default/cheats/ChainId.t.sol b/testdata/default/cheats/ChainId.t.sol index ef0108e7e208e..bc2c69b798942 100644 --- a/testdata/default/cheats/ChainId.t.sol +++ b/testdata/default/cheats/ChainId.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ChainIdTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ChainIdTest is Test { function testChainId() public { uint256 newChainId = 99; vm.chainId(newChainId); diff --git a/testdata/default/cheats/CloneAccount.t.sol b/testdata/default/cheats/CloneAccount.t.sol index d584c747cb9b9..95eccd843ee09 100644 --- a/testdata/default/cheats/CloneAccount.t.sol +++ b/testdata/default/cheats/CloneAccount.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Source { uint256 public a; @@ -20,9 +19,7 @@ contract Source { } } -contract CloneAccountTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +contract CloneAccountTest is Test { address clone = address(777); function setUp() public { diff --git a/testdata/default/cheats/Cool.t.sol b/testdata/default/cheats/Cool.t.sol index d0750bebfa18e..71e19e4f8fd88 100644 --- a/testdata/default/cheats/Cool.t.sol +++ b/testdata/default/cheats/Cool.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "lib/ds-test/src/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract CoolTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract CoolTest is Test { uint256 public slot0 = 1; function testCool_SLOAD_normal() public { diff --git a/testdata/default/cheats/CopyStorage.t.sol b/testdata/default/cheats/CopyStorage.t.sol index e9195949e49c9..881a0c0aacf57 100644 --- a/testdata/default/cheats/CopyStorage.t.sol +++ b/testdata/default/cheats/CopyStorage.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public a; @@ -18,10 +17,10 @@ contract Counter { } } -contract CounterWithSeedTest is DSTest { +/// forge-config: default.fuzz.seed = "100" +contract CounterWithSeedTest is Test { Counter public counter; Counter public counter1; - Vm vm = Vm(HEVM_ADDRESS); function test_copy_storage() public { counter = new Counter(); @@ -67,11 +66,10 @@ contract CopyStorageContract { uint256 public x; } -contract CopyStorageTest is DSTest { +contract CopyStorageTest is Test { CopyStorageContract csc_1; CopyStorageContract csc_2; CopyStorageContract csc_3; - Vm vm = Vm(HEVM_ADDRESS); function _storeUInt256(address contractAddress, uint256 slot, uint256 value) internal { vm.store(contractAddress, bytes32(slot), bytes32(value)); diff --git a/testdata/default/cheats/Deal.t.sol b/testdata/default/cheats/Deal.t.sol index a46d9e7140e3e..4fcb37249aa67 100644 --- a/testdata/default/cheats/Deal.t.sol +++ b/testdata/default/cheats/Deal.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DealTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract DealTest is Test { function testDeal(uint256 amount) public { address target = address(10); assertEq(target.balance, 0, "initial balance incorrect"); diff --git a/testdata/default/cheats/DeployCode.t.sol b/testdata/default/cheats/DeployCode.t.sol index 3978cfc335f5f..9499a9ff7f6fc 100644 --- a/testdata/default/cheats/DeployCode.t.sol +++ b/testdata/default/cheats/DeployCode.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} @@ -36,9 +35,7 @@ contract TestPayableContractWithArgs { } } -contract DeployCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DeployCodeTest is Test { address public constant overrideAddress = 0x0000000000000000000000000000000000000064; event Payload(address sender, address target, bytes data); diff --git a/testdata/default/cheats/Derive.t.sol b/testdata/default/cheats/Derive.t.sol index c27456c6ec487..5ed5bd709c5e3 100644 --- a/testdata/default/cheats/Derive.t.sol +++ b/testdata/default/cheats/Derive.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DeriveTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract DeriveTest is Test { function testDerive() public { string memory mnemonic = "test test test test test test test test test test test junk"; diff --git a/testdata/default/cheats/EnsNamehash.t.sol b/testdata/default/cheats/EnsNamehash.t.sol index 965d505006060..94af39a8eb784 100644 --- a/testdata/default/cheats/EnsNamehash.t.sol +++ b/testdata/default/cheats/EnsNamehash.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EnsNamehashTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EnsNamehashTest is Test { function testEnsNamehash() public { assertEq(vm.ensNamehash(""), 0x0000000000000000000000000000000000000000000000000000000000000000); assertEq(vm.ensNamehash("eth"), 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae); diff --git a/testdata/default/cheats/Env.t.sol b/testdata/default/cheats/Env.t.sol index 7edb35dff13ff..641167f3dfb14 100644 --- a/testdata/default/cheats/Env.t.sol +++ b/testdata/default/cheats/Env.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EnvTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EnvTest is Test { function testSetEnv() public { string memory key = "_foundryCheatcodeSetEnvTestKey"; string memory val = "_foundryCheatcodeSetEnvTestVal"; diff --git a/testdata/default/cheats/Etch.t.sol b/testdata/default/cheats/Etch.t.sol index 5e93dfb81213b..6bd4c28914052 100644 --- a/testdata/default/cheats/Etch.t.sol +++ b/testdata/default/cheats/Etch.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EtchTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EtchTest is Test { function testEtch() public { address target = address(7070707); bytes memory code = hex"1010"; diff --git a/testdata/default/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol index 01a95b4277e01..d4f2a3fb593fa 100644 --- a/testdata/default/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Contract { function numberA() public pure returns (uint256) { @@ -60,9 +59,7 @@ contract ProxyWithDelegateCall { } } -contract ExpectCallTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallTest is Test { function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(a, b); @@ -206,9 +203,7 @@ contract ExpectCallTest is DSTest { } } -contract ExpectCallCountTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallCountTest is Test { function testExpectCallCountWithData() public { Contract target = new Contract(); vm.expectCall(address(target), abi.encodeWithSelector(Contract.add.selector, 1, 2), 3); @@ -332,9 +327,7 @@ contract ExpectCallCountTest is DSTest { } } -contract ExpectCallMixedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallMixedTest is Test { function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(1, 2); diff --git a/testdata/default/cheats/ExpectCreate.t.sol b/testdata/default/cheats/ExpectCreate.t.sol index a922d01b92bac..f416c645b3424 100644 --- a/testdata/default/cheats/ExpectCreate.t.sol +++ b/testdata/default/cheats/ExpectCreate.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Contract { function add(uint256 a, uint256 b) public pure returns (uint256) { @@ -20,8 +19,7 @@ contract ContractDeployer { } } -contract ExpectCreateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectCreateTest is Test { bytes bytecode = vm.getDeployedCode("cheats/ExpectCreate.t.sol:Contract"); function testExpectCreate() public { diff --git a/testdata/default/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol index ca91513443d21..516ac1079476c 100644 --- a/testdata/default/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Emitter { uint256 public thing; @@ -115,8 +114,7 @@ contract LowLevelCaller { function g() public {} } -contract ExpectEmitTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectEmitTest is Test { Emitter emitter; event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); @@ -416,8 +414,7 @@ contract ExpectEmitTest is DSTest { // } } -contract ExpectEmitCountTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectEmitCountTest is Test { Emitter emitter; event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 1b70af1188765..f8fcf3d621318 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Reverter { error CustomError(); @@ -71,9 +70,7 @@ contract Dummy { } } -contract ExpectRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertTest is Test { function shouldRevert() internal { revert(); } @@ -263,9 +260,7 @@ contract DContract { } } -contract ExpectRevertWithReverterTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertWithReverterTest is Test { error CContractError(string reason); AContract aContract; @@ -312,9 +307,7 @@ contract ExpectRevertWithReverterTest is DSTest { } } -contract ExpectRevertCount is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertCount is Test { function testRevertCountAny() public { uint64 count = 3; Reverter reverter = new Reverter(); @@ -395,9 +388,7 @@ contract ExpectRevertCount is DSTest { } } -contract ExpectRevertCountWithReverter is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertCountWithReverter is Test { function testRevertCountWithReverter() public { uint64 count = 2; Reverter reverter = new Reverter(); diff --git a/testdata/default/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol index 120627c0004e9..d96fa6f9b6af7 100644 --- a/testdata/default/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FeeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract FeeTest is Test { function testFee() public { vm.fee(10); assertEq(block.basefee, 10, "fee failed"); diff --git a/testdata/default/cheats/Ffi.t.sol b/testdata/default/cheats/Ffi.t.sol index 23ac54e6ace12..6c84bdf893c29 100644 --- a/testdata/default/cheats/Ffi.t.sol +++ b/testdata/default/cheats/Ffi.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FfiTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract FfiTest is Test { function testFfi() public { string[] memory inputs = new string[](3); inputs[0] = "bash"; diff --git a/testdata/default/cheats/Fork.t.sol b/testdata/default/cheats/Fork.t.sol index 2f2e627de131a..281a27a0b868c 100644 --- a/testdata/default/cheats/Fork.t.sol +++ b/testdata/default/cheats/Fork.t.sol @@ -1,19 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); } -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint256 constant mainblock = 14_608_400; - Vm constant vm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); uint256 forkA; diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index d0703ce7fa6ce..037d4e0fa9528 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "../logs/console.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct MyStruct { uint256 value; @@ -27,9 +25,7 @@ contract MyContract { } } -contract ForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ForkTest is Test { uint256 mainnetFork; uint256 optimismFork; @@ -150,7 +146,7 @@ contract ForkTest is DSTest { assertEq(dummy.val(), expectedValue); } - // checks diagnostic + /// forge-config: default.allow_internal_expect_revert = true function testNonExistingContractRevert() public { vm.selectFork(mainnetFork); DummyContract dummy = new DummyContract(); @@ -164,7 +160,8 @@ contract ForkTest is DSTest { assertEq(dummyAddress, address(dummy)); // this will revert since `dummy` does not exists on the currently active fork - string memory msg2 = dummy.hello(); + vm.expectRevert(); + dummy.noop(); } struct EthGetLogsJsonParseable { @@ -245,6 +242,8 @@ contract ForkTest is DSTest { contract DummyContract { uint256 public val; + function noop() external pure {} + function hello() external view returns (string memory) { return "hello"; } diff --git a/testdata/default/cheats/Fs.t.sol b/testdata/default/cheats/Fs.t.sol index b4882525944cf..80b89d7d69931 100644 --- a/testdata/default/cheats/Fs.t.sol +++ b/testdata/default/cheats/Fs.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract FsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract FsTest is Test { bytes constant FOUNDRY_TOML_ACCESS_ERR = "access to foundry.toml is not allowed"; bytes constant FOUNDRY_READ_ERR = "the path /etc/hosts is not allowed to be accessed for read operations"; bytes constant FOUNDRY_READ_DIR_ERR = "the path /etc is not allowed to be accessed for read operations"; @@ -42,7 +40,7 @@ contract FsTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm.writeFile(path, data); @@ -58,7 +56,7 @@ contract FsTest is DSTest { function testCopyFile() public { string memory from = "fixtures/File/read.txt"; - string memory to = "fixtures/File/copy.txt"; + string memory to = "fixtures/File/ignored/copy.txt"; uint64 copied = vm.copyFile(from, to); assertEq(vm.fsMetadata(to).length, uint256(copied)); assertEq(vm.readFile(from), vm.readFile(to)); @@ -66,7 +64,7 @@ contract FsTest is DSTest { } function testWriteLine() public { - string memory path = "fixtures/File/write_line.txt"; + string memory path = "fixtures/File/ignored/write_line.txt"; string memory line1 = "first line"; vm.writeLine(path, line1); @@ -91,7 +89,7 @@ contract FsTest is DSTest { } function testRemoveFile() public { - string memory path = "fixtures/File/remove_file.txt"; + string memory path = "fixtures/File/ignored/remove_file.txt"; string memory data = "hello writable world"; vm.writeFile(path, data); diff --git a/testdata/default/cheats/GasMetering.t.sol b/testdata/default/cheats/GasMetering.t.sol index 3cb105d236f02..fbde54741c624 100644 --- a/testdata/default/cheats/GasMetering.t.sol +++ b/testdata/default/cheats/GasMetering.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract B { function a() public returns (uint256) { @@ -10,9 +9,7 @@ contract B { } } -contract GasMeteringTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GasMeteringTest is Test { function testGasMetering() public { uint256 gas_start = gasleft(); @@ -40,6 +37,7 @@ contract GasMeteringTest is DSTest { function testGasMeteringExternal() public { B b = new B(); + uint256 gas_start = gasleft(); b.a(); diff --git a/testdata/default/cheats/GetArtifactPath.t.sol b/testdata/default/cheats/GetArtifactPath.t.sol index 4b0df4ba6e6ec..7a2e1335d0c04 100644 --- a/testdata/default/cheats/GetArtifactPath.t.sol +++ b/testdata/default/cheats/GetArtifactPath.t.sol @@ -1,20 +1,16 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract DummyForGetArtifactPath {} -contract GetArtifactPathTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetArtifactPathTest is Test { function testGetArtifactPathByCode() public { - DummyForGetArtifactPath dummy = new DummyForGetArtifactPath(); bytes memory dummyCreationCode = type(DummyForGetArtifactPath).creationCode; string memory path = vm.getArtifactPathByCode(dummyCreationCode); - assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + assertTrue(vm.contains(path, "/out/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); } function testGetArtifactPathByDeployedCode() public { @@ -22,6 +18,6 @@ contract GetArtifactPathTest is DSTest { bytes memory dummyRuntimeCode = address(dummy).code; string memory path = vm.getArtifactPathByDeployedCode(dummyRuntimeCode); - assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + assertTrue(vm.contains(path, "/out/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); } } diff --git a/testdata/default/cheats/GetBlockTimestamp.t.sol b/testdata/default/cheats/GetBlockTimestamp.t.sol index 816bc0d1ef89e..65bda02052764 100644 --- a/testdata/default/cheats/GetBlockTimestamp.t.sol +++ b/testdata/default/cheats/GetBlockTimestamp.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetBlockTimestampTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetBlockTimestampTest is Test { function testGetTimestamp() public { uint256 timestamp = vm.getBlockTimestamp(); assertEq(timestamp, 1, "timestamp should be 1"); diff --git a/testdata/default/cheats/GetChain.t.sol b/testdata/default/cheats/GetChain.t.sol index 15923612cfc60..ca6e1a5c284cf 100644 --- a/testdata/default/cheats/GetChain.t.sol +++ b/testdata/default/cheats/GetChain.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetChainTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetChainTest is Test { function testGetMainnet() public { // Test mainnet Vm.Chain memory mainnet = vm.getChain("mainnet"); diff --git a/testdata/default/cheats/GetCode.t.sol b/testdata/default/cheats/GetCode.t.sol index 6020e4f1fd5bb..514ac66b9d0b3 100644 --- a/testdata/default/cheats/GetCode.t.sol +++ b/testdata/default/cheats/GetCode.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} contract TestContractGetCode {} -contract GetCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest is Test { function testGetCode() public { bytes memory fullPath = vm.getCode("fixtures/GetCode/WorkingContract.json"); //bytes memory fileOnly = vm.getCode("WorkingContract.sol"); @@ -73,6 +70,7 @@ contract GetCodeTest is DSTest { /// forge-config: default.allow_internal_expect_revert = true function testRevertIfGetUnlinked() public { + vm.skip(true, "artifacts are always linked now"); vm.expectRevert("vm.getCode: no matching artifact found"); vm.getCode("UnlinkedContract.sol"); } diff --git a/testdata/default/cheats/GetDeployedCode.t.sol b/testdata/default/cheats/GetDeployedCode.t.sol index 295d2ae8f3f38..a33a13d47805c 100644 --- a/testdata/default/cheats/GetDeployedCode.t.sol +++ b/testdata/default/cheats/GetDeployedCode.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} -contract GetDeployedCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetDeployedCodeTest is Test { address public constant overrideAddress = 0x0000000000000000000000000000000000000064; event Payload(address sender, address target, bytes data); diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index ac30bb8491874..6139b8b6b6a5e 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetFoundryVersionTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetFoundryVersionTest is Test { function testGetFoundryVersion() public view { // (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug) string memory fullVersionString = vm.getFoundryVersion(); diff --git a/testdata/default/cheats/GetLabel.t.sol b/testdata/default/cheats/GetLabel.t.sol index c5a5638d36752..b5ccc64533862 100644 --- a/testdata/default/cheats/GetLabel.t.sol +++ b/testdata/default/cheats/GetLabel.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetLabelTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetLabelTest is Test { function testGetLabel() public { // Label an address. vm.label(address(1), "Sir Address the 1st"); diff --git a/testdata/default/cheats/GetNonce.t.sol b/testdata/default/cheats/GetNonce.t.sol index d4043a59992a8..a786fd496ff94 100644 --- a/testdata/default/cheats/GetNonce.t.sol +++ b/testdata/default/cheats/GetNonce.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo {} -contract GetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetNonceTest is Test { function testGetNonce() public { uint64 nonce1 = vm.getNonce(address(this)); new Foo(); diff --git a/testdata/default/cheats/GetRawBlockHeader.t.sol b/testdata/default/cheats/GetRawBlockHeader.t.sol index 9dcd2fc0d39c4..54600149863a0 100644 --- a/testdata/default/cheats/GetRawBlockHeader.t.sol +++ b/testdata/default/cheats/GetRawBlockHeader.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetRawBlockHeaderTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetRawBlockHeaderTest is Test { function testGetRawBlockHeaderWithFork() public { vm.createSelectFork("mainnet"); assertEq( diff --git a/testdata/default/cheats/GetStorageSlots.t.sol b/testdata/default/cheats/GetStorageSlots.t.sol index 4ac6f77d462fd..f61638ac51ea1 100644 --- a/testdata/default/cheats/GetStorageSlots.t.sol +++ b/testdata/default/cheats/GetStorageSlots.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract StorageContract { // Simple variables - 1 slot each @@ -44,8 +43,7 @@ contract StorageContract { } } -contract GetStorageSlotsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract GetStorageSlotsTest is Test { StorageContract storageContract; function setUp() public { diff --git a/testdata/default/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol index b7aefae736e9a..c187e07465bb1 100644 --- a/testdata/default/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; library JsonStructs { address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); @@ -66,7 +64,7 @@ library JsonStructs { } } -contract ParseJsonTest is DSTest { +contract ParseJsonTest is Test { using JsonStructs for *; struct FlatJson { @@ -88,7 +86,6 @@ contract ParseJsonTest is DSTest { string name; } - Vm constant vm = Vm(HEVM_ADDRESS); string json; function setUp() public { @@ -328,9 +325,7 @@ contract ParseJsonTest is DSTest { } } -contract WriteJsonTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract WriteJsonTest is Test { string json1; string json2; diff --git a/testdata/default/cheats/Label.t.sol b/testdata/default/cheats/Label.t.sol index 4ff5d3860bed0..7c1ebd02d2443 100644 --- a/testdata/default/cheats/Label.t.sol +++ b/testdata/default/cheats/Label.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract LabelTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract LabelTest is Test { function testLabel() public { vm.label(address(1), "Sir Address the 1st"); } diff --git a/testdata/default/cheats/Load.t.sol b/testdata/default/cheats/Load.t.sol index 06f4b5bd52718..dcdd145c7c8f7 100644 --- a/testdata/default/cheats/Load.t.sol +++ b/testdata/default/cheats/Load.t.sol @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Storage { uint256 slot0 = 10; } -contract LoadTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract LoadTest is Test { uint256 slot0 = 20; Storage store; diff --git a/testdata/default/cheats/Mapping.t.sol b/testdata/default/cheats/Mapping.t.sol index 82477150ae9ca..7b0eb11ccb32d 100644 --- a/testdata/default/cheats/Mapping.t.sol +++ b/testdata/default/cheats/Mapping.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract RecordMapping { int256 length; @@ -18,9 +17,7 @@ contract RecordMapping { } } -contract RecordMappingTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract RecordMappingTest is Test { function testRecordMapping() public { RecordMapping target = new RecordMapping(); diff --git a/testdata/default/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol index 9bb282538ddfe..953ca07e85e03 100644 --- a/testdata/default/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract MemSafetyTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract MemSafetyTest is Test { //////////////////////////////////////////////////////////////// // MSTORE // //////////////////////////////////////////////////////////////// diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index f11fd20984571..e2ac74d6f70fa 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Mock { uint256 state = 0; @@ -56,9 +55,7 @@ contract NestedMockDelegateCall { } } -contract MockCallTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MockCallTest is Test { function testMockGetters() public { Mock target = new Mock(); @@ -183,9 +180,7 @@ contract MockCallTest is DSTest { } } -contract MockCallRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MockCallRevertTest is Test { error TestError(bytes msg); bytes constant ERROR_MESSAGE = "ERROR_MESSAGE"; diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol index 2bd4d8bd9ea2e..e0f5eef151db6 100644 --- a/testdata/default/cheats/MockCalls.t.sol +++ b/testdata/default/cheats/MockCalls.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract MockCallsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract MockCallsTest is Test { function testMockCallsLastShouldPersist() public { address mockUser = vm.addr(vm.randomUint()); address mockErc20 = vm.addr(vm.randomUint()); diff --git a/testdata/default/cheats/MockFunction.t.sol b/testdata/default/cheats/MockFunction.t.sol index 6d670024b2053..3defa8cfd3580 100644 --- a/testdata/default/cheats/MockFunction.t.sol +++ b/testdata/default/cheats/MockFunction.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract MockFunctionContract { uint256 public a; @@ -28,10 +27,9 @@ contract ModelMockFunctionContract { } } -contract MockFunctionTest is DSTest { +contract MockFunctionTest is Test { MockFunctionContract my_contract; ModelMockFunctionContract model_contract; - Vm vm = Vm(HEVM_ADDRESS); function setUp() public { my_contract = new MockFunctionContract(); diff --git a/testdata/default/cheats/Nonce.t.sol b/testdata/default/cheats/Nonce.t.sol index 312c2b4d7edcc..7b7f7553e7854 100644 --- a/testdata/default/cheats/Nonce.t.sol +++ b/testdata/default/cheats/Nonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public count; @@ -12,9 +11,8 @@ contract Counter { } } -contract NonceIsolatedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.isolate = true +contract NonceIsolatedTest is Test { function testIncrementNonce() public { address bob = address(14); vm.startPrank(bob); diff --git a/testdata/default/cheats/Parse.t.sol b/testdata/default/cheats/Parse.t.sol index 65e7561d104de..2736aa8ade7b6 100644 --- a/testdata/default/cheats/Parse.t.sol +++ b/testdata/default/cheats/Parse.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ParseTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ParseTest is Test { function testParseBytes() public { bytes memory testBytes = hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D"; diff --git a/testdata/default/cheats/Prank.t.sol b/testdata/default/cheats/Prank.t.sol index 151f0d8306776..e8124b2475e16 100644 --- a/testdata/default/cheats/Prank.t.sol +++ b/testdata/default/cheats/Prank.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Victim { function assertCallerAndOrigin( @@ -107,9 +106,7 @@ contract ProxyTest { address public sender; } -contract PrankTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract PrankTest is Test { function testPrankDelegateCallPrank2() public { ProxyTest proxy = new ProxyTest(); ImplementationTest impl = new ImplementationTest(); @@ -551,9 +548,7 @@ contract PrankTest is DSTest { } } -contract Issue9990 is DSTest { - Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); - +contract Issue9990 is Test { function testDelegatePrank() external { A a = new A(); vm.etch(address(0x11111), hex"11"); @@ -600,9 +595,7 @@ contract Counter { } } -contract Issue10528 is DSTest { - Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); - +contract Issue10528 is Test { function testStartPrankOnContractCreation() external { vm.startPrank(address(0x22222)); Counter counter = new Counter(); diff --git a/testdata/default/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol index aab8e326c43ce..ff4d8b48b8c8d 100644 --- a/testdata/default/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract PrevrandaoTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract PrevrandaoTest is Test { function testPrevrandao() public { assertEq(block.prevrandao, 0); vm.prevrandao(uint256(10)); diff --git a/testdata/default/cheats/ProjectRoot.t.sol b/testdata/default/cheats/ProjectRoot.t.sol index cff3d83751d66..8835d2192cbfc 100644 --- a/testdata/default/cheats/ProjectRoot.t.sol +++ b/testdata/default/cheats/ProjectRoot.t.sol @@ -1,15 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ProjectRootTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ProjectRootTest is Test { bytes public manifestDirBytes; function testProjectRoot() public { - manifestDirBytes = bytes(vm.envString("CARGO_MANIFEST_DIR")); + // .../crates/forge + string memory manifestDir = vm.envOr("CARGO_MANIFEST_DIR", string("")); + if (bytes(manifestDir).length == 0) { + vm.skip(true, "CARGO_MANIFEST_DIR environment variable is not set"); + } + manifestDirBytes = bytes(manifestDir); + for (uint256 i = 0; i < 7; i++) { manifestDirBytes.pop(); } @@ -20,6 +24,10 @@ contract ProjectRootTest is DSTest { } bytes memory expectedRootDir = abi.encodePacked(manifestDirBytes, "ata"); - assertEq(vm.projectRoot(), string(expectedRootDir)); + assertEq(normalizePath(vm.projectRoot()), normalizePath(string(expectedRootDir))); + } + + function normalizePath(string memory path) internal pure returns (string memory) { + return vm.replace(path, "\\", "/"); } } diff --git a/testdata/default/cheats/Prompt.t.sol b/testdata/default/cheats/Prompt.t.sol index fa60f6740698e..5505629da15ba 100644 --- a/testdata/default/cheats/Prompt.t.sol +++ b/testdata/default/cheats/Prompt.t.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; - -contract PromptTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +// All `prompt` functions should revert in CI and testing environments either +// with a timeout or because no terminal is available. +contract PromptTest is Test { function testPrompt_revertNotATerminal() public { - // should revert in CI and testing environments either with timeout or because no terminal is available + checkTty(); + vm._expectCheatcodeRevert(); vm.prompt("test"); @@ -21,12 +20,22 @@ contract PromptTest is DSTest { } function testPrompt_Address() public { + checkTty(); + vm._expectCheatcodeRevert(); address test = vm.promptAddress("test"); } function testPrompt_Uint() public { + checkTty(); + vm._expectCheatcodeRevert(); uint256 test = vm.promptUint("test"); } + + function checkTty() internal { + if (!vm.envOr("CI", false)) { + vm.skip(true, "min timeout is 1s, don't test it"); + } + } } diff --git a/testdata/default/cheats/RandomAddress.t.sol b/testdata/default/cheats/RandomAddress.t.sol index 61510ed4eaaac..043884ed0a014 100644 --- a/testdata/default/cheats/RandomAddress.t.sol +++ b/testdata/default/cheats/RandomAddress.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomAddress is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomAddress is Test { function testRandomAddress() public { vm.randomAddress(); } diff --git a/testdata/default/cheats/RandomBytes.t.sol b/testdata/default/cheats/RandomBytes.t.sol index dbc03a6ccfb8f..4cdc08a61604c 100644 --- a/testdata/default/cheats/RandomBytes.t.sol +++ b/testdata/default/cheats/RandomBytes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomBytes is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomBytes is Test { function testRandomBytes4() public { vm.randomBytes4(); } diff --git a/testdata/default/cheats/RandomCheatcodes.t.sol b/testdata/default/cheats/RandomCheatcodes.t.sol index c42b4310012f1..bd1968000eab7 100644 --- a/testdata/default/cheats/RandomCheatcodes.t.sol +++ b/testdata/default/cheats/RandomCheatcodes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomCheatcodesTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomCheatcodesTest is Test { int128 constant min = -170141183460469231731687303715884105728; int128 constant max = 170141183460469231731687303715884105727; @@ -57,9 +54,7 @@ contract RandomCheatcodesTest is DSTest { } } -contract RandomBytesTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +contract RandomBytesTest is Test { bytes1 local_byte; bytes local_bytes; diff --git a/testdata/default/cheats/RandomUint.t.sol b/testdata/default/cheats/RandomUint.t.sol index c0021030d0d1e..8e0b402386133 100644 --- a/testdata/default/cheats/RandomUint.t.sol +++ b/testdata/default/cheats/RandomUint.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomUint is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomUint is Test { function testRandomUint() public { vm.randomUint(); } diff --git a/testdata/default/cheats/ReadCallers.t.sol b/testdata/default/cheats/ReadCallers.t.sol index dbd198a2d93e8..7bef8c502a091 100644 --- a/testdata/default/cheats/ReadCallers.t.sol +++ b/testdata/default/cheats/ReadCallers.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Target { function consumeNewCaller() external {} } -contract ReadCallersTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ReadCallersTest is Test { function testReadCallersWithNoActivePrankOrBroadcast() public { address expectedSender = msg.sender; address expectedTxOrigin = tx.origin; diff --git a/testdata/default/cheats/Record.t.sol b/testdata/default/cheats/Record.t.sol index c3029d5f54205..6525092803303 100644 --- a/testdata/default/cheats/Record.t.sol +++ b/testdata/default/cheats/Record.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract RecordAccess { function record() public returns (NestedRecordAccess) { @@ -25,9 +24,7 @@ contract NestedRecordAccess { } } -contract RecordTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract RecordTest is Test { function testRecordAccess() public { RecordAccess target = new RecordAccess(); diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index 88f795fafef99..fee8ebbc361c8 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; /// @notice Helper contract with a construction that makes a call to itself then /// optionally reverts if zero-length data is passed @@ -124,13 +122,13 @@ contract NestedRunner { /// Helper contract that uses all three EXT* opcodes on a given address contract ExtChecker { - function checkExts(address a) external { + function checkExts(address a) external returns (bytes memory out) { assembly { - let x := extcodesize(a) - let y := extcodehash(a) - extcodecopy(a, x, y, 0) - // sstore to check that storage accesses are correctly stored in a new access with a "resume" context - sstore(0, balance(a)) + mstore(out, mul(0x20, 4)) + mstore(add(out, 0x20), extcodesize(a)) + mstore(add(out, 0x40), extcodehash(a)) + extcodecopy(a, 0, 0x60, 0x20) + mstore(add(out, 0x80), balance(a)) } } } @@ -203,8 +201,7 @@ contract Proxy { } /// @notice Test that the cheatcode correctly records account accesses -contract RecordAccountAccessesTest is DSTest { - Vm constant cheats = Vm(HEVM_ADDRESS); +contract RecordAccountAccessesTest is Test { NestedRunner runner; NestedStorer nestedStorer; Create2or create2or; @@ -225,9 +222,9 @@ contract RecordAccountAccessesTest is DSTest { StorageAccessor one = test1; Proxy proxy = new Proxy(address(one)); - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 2, "incorrect length"); @@ -255,24 +252,24 @@ contract RecordAccountAccessesTest is DSTest { function testStorageAccesses() public { StorageAccessor one = test1; StorageAccessor two = test2; - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); one.read(bytes32(uint256(1234))); one.write(bytes32(uint256(1235)), bytes32(uint256(5678))); two.write(bytes32(uint256(5678)), bytes32(uint256(123469))); two.write(bytes32(uint256(5678)), bytes32(uint256(1234))); - string memory diffs = cheats.getStateDiff(); + string memory diffs = vm.getStateDiff(); assertEq( "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n", diffs ); - string memory diffsJson = cheats.getStateDiffJson(); + string memory diffsJson = vm.getStateDiffJson(); assertEq( '{"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9":{"label":null,"contract":"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x00000000000000000000000000000000000000000000000000000000000004d3":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000000000000000000000000000162e"}}},"0xc7183455a4c133ae270771860664b6b7ec320bb1":{"label":null,"contract":"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x000000000000000000000000000000000000000000000000000000000000162e":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x00000000000000000000000000000000000000000000000000000000000004d2"}}}}', diffsJson ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 4, "incorrect length"); assertEq(called[0].storageAccesses.length, 1, "incorrect storage length"); @@ -334,7 +331,7 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that basic account accesses are correctly recorded function testRecordAccountAccesses() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); (bool succ,) = address(1234).call(""); (succ,) = address(5678).call{value: 1 ether}(""); @@ -343,7 +340,7 @@ contract RecordAccountAccessesTest is DSTest { // contract calls to self in constructor SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); - string memory callerAddress = cheats.toString(address(caller)); + string memory callerAddress = vm.toString(address(caller)); string memory expectedStateDiff = "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n"; expectedStateDiff = string.concat(expectedStateDiff, callerAddress); @@ -353,9 +350,9 @@ contract RecordAccountAccessesTest is DSTest { expectedStateDiff, "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n- nonce diff: 0 \xE2\x86\x92 1\n\n" ); - assertEq(expectedStateDiff, cheats.getStateDiff()); + assertEq(expectedStateDiff, vm.getStateDiff()); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 6); assertEq( called[0], @@ -484,17 +481,17 @@ contract RecordAccountAccessesTest is DSTest { /// reverts function testRevertingCall() public { uint256 initBalance = address(this).balance; - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); try this.revertingCall{value: 1 ether}(address(1234), "") {} catch {} assertEq( "0x00000000000000000000000000000000000004d2\n- balance diff: 0 \xE2\x86\x92 100000000000000000\n\n", - cheats.getStateDiff() + vm.getStateDiff() ); assertEq( '{"0x00000000000000000000000000000000000004d2":{"label":null,"contract":null,"balanceDiff":{"previousValue":"0x0","newValue":"0x16345785d8a0000"},"nonceDiff":null,"stateDiff":{}}}', - cheats.getStateDiffJson() + vm.getStateDiffJson() ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 2); assertEq( called[0], @@ -540,14 +537,14 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that nested account accesses are correctly recorded function testNested() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); runNested(false, false); } /// @notice Test that nested account accesses are correctly recorded when /// the first call reverts function testNested_Revert() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); runNested(true, false); } @@ -555,7 +552,7 @@ contract RecordAccountAccessesTest is DSTest { /// @param shouldRevert Whether the first call should revert function runNested(bool shouldRevert, bool expectFirstCall) public { try runner.run{value: 1 ether}(shouldRevert) {} catch {} - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 7 + toUint(expectFirstCall), "incorrect length"); uint64 startingIndex = uint64(toUint(expectFirstCall)); @@ -829,18 +826,18 @@ contract RecordAccountAccessesTest is DSTest { } function testNestedStorage() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); nestedStorer.run(); - cheats.label(address(nestedStorer), "NestedStorer"); + vm.label(address(nestedStorer), "NestedStorer"); assertEq( "0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\ncontract: default/cheats/RecordAccountAccesses.t.sol:NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n", - cheats.getStateDiff() + vm.getStateDiff() ); assertEq( '{"0x2e234dae75c793f67a35089c9d99245e1c58470b":{"label":"NestedStorer","contract":"default/cheats/RecordAccountAccesses.t.sol:NestedStorer","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}}}}', - cheats.getStateDiffJson() + vm.getStateDiffJson() ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(called[0].storageAccesses.length, 2, "incorrect run storage length"); @@ -963,14 +960,14 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that constructor account and storage accesses are recorded, including reverts function testConstructorStorage() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); address storer = address(new ConstructorStorer(false)); try create2or.create2(bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) {} catch {} bytes memory creationCode = abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true)); address hypotheticalStorer = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create), "incorrect kind"); assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call), "incorrect kind"); @@ -1083,12 +1080,12 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that constructor calls and calls made within a constructor /// are correctly recorded, even if it reverts function testCreateRevert() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); bytes memory creationCode = abi.encodePacked(type(SelfCaller).creationCode, abi.encode("")); try create2or.create2(bytes32(0), creationCode) {} catch {} address hypotheticalAddress = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect length"); assertEq( called[1], @@ -1140,7 +1137,7 @@ contract RecordAccountAccessesTest is DSTest { this.startRecordingFromLowerDepth(); address a = address(new SelfDestructor{value: 1 ether}(address(this))); address b = address(new SelfDestructor{value: 1 ether}(address(bytes20("doesn't exist yet")))); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 5, "incorrect length"); assertEq( called[1], @@ -1228,13 +1225,13 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Asserts interaction between broadcast and recording cheatcodes function testIssue6514() public { - cheats.startStateDiffRecording(); - cheats.startBroadcast(); + vm.startStateDiffRecording(); + vm.startBroadcast(); StorageAccessor a = new StorageAccessor(); - cheats.stopBroadcast(); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + vm.stopBroadcast(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 1, "incorrect length"); assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create)); assertEq(called[0].account, address(a)); @@ -1242,22 +1239,17 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that EXT* opcodes are recorded as account accesses function testExtOpcodes() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); extChecker.checkExts(address(1234)); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); - assertEq(called.length, 7, "incorrect length"); - // initial solidity extcodesize check for calling extChecker - assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + assertEq(called.length, 5, "incorrect length"); // call to extChecker - assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call)); + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Call)); // extChecker checks - assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodesize)); - assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodehash)); - assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); - assertEq(toUint(called[5].kind), toUint(Vm.AccountAccessKind.Balance)); - // resume of extChecker to hold SSTORE access - assertEq(toUint(called[6].kind), toUint(Vm.AccountAccessKind.Resume)); - assertEq(called[6].storageAccesses.length, 1, "incorrect length"); + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodehash)); + assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); + assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Balance)); } /** @@ -1287,7 +1279,7 @@ contract RecordAccountAccessesTest is DSTest { } function startRecordingFromLowerDepth() external { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); assembly { pop(call(gas(), 1234, 0, 0, 0, 0, 0)) } @@ -1411,7 +1403,7 @@ contract RecordAccountAccessesTest is DSTest { StorageAccessor accessor = test1; // Start recording to enable storage access tracking - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); // Perform a read operation accessor.read(bytes32(uint256(789))); @@ -1423,7 +1415,7 @@ contract RecordAccountAccessesTest is DSTest { accessor.read(bytes32(uint256(123))); // Get all storage accesses - Vm.StorageAccess[] memory accesses = cheats.getStorageAccesses(); + Vm.StorageAccess[] memory accesses = vm.getStorageAccesses(); // Check we have 3 storage accesses (2 reads + 1 write) assertEq(accesses.length, 3, "should have 3 storage accesses"); diff --git a/testdata/default/cheats/RecordDebugTrace.t.sol b/testdata/default/cheats/RecordDebugTrace.t.sol index f66f701cc7ab4..f7c0b895761e4 100644 --- a/testdata/default/cheats/RecordDebugTrace.t.sol +++ b/testdata/default/cheats/RecordDebugTrace.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract MStoreAndMLoadCaller { uint256 public constant expectedValueInMemory = 999; @@ -64,23 +63,21 @@ contract OutOfGas { } } -contract RecordDebugTraceTest is DSTest { - Vm constant cheats = Vm(HEVM_ADDRESS); +contract RecordDebugTraceTest is Test { /** * The goal of this test is to ensure the debug steps provide the correct OPCODE with its stack * and memory input used. The test checke MSTORE and MLOAD and ensure it records the expected * stack and memory inputs. */ - function testDebugTraceCanRecordOpcodeWithStackAndMemoryData() public { MStoreAndMLoadCaller testContract = new MStoreAndMLoadCaller(); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); uint256 val = testContract.storeAndLoadValueFromMemory(); assertTrue(val == testContract.expectedValueInMemory()); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool mstoreCalled = false; bool mloadCalled = false; @@ -118,11 +115,11 @@ contract RecordDebugTraceTest is DSTest { SecondLayer second = new SecondLayer(); FirstLayer first = new FirstLayer(second); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); first.callSecondLayer(); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool goToDepthTwo = false; bool goToDepthThree = false; @@ -149,11 +146,11 @@ contract RecordDebugTraceTest is DSTest { function testDebugTraceCanRecordOutOfGas() public { OutOfGas testContract = new OutOfGas(); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); testContract.triggerOOG(); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool isOOG = false; for (uint256 i = 0; i < steps.length; i++) { diff --git a/testdata/default/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol index 14ca8dde35e99..5927d5af23866 100644 --- a/testdata/default/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Emitter { event LogAnonymous(bytes data) anonymous; @@ -48,8 +47,7 @@ contract Emitterv2 { } } -contract RecordLogsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract RecordLogsTest is Test { Emitter emitter; bytes32 internal seedTestData = keccak256(abi.encodePacked("Some data")); diff --git a/testdata/default/cheats/Remember.t.sol b/testdata/default/cheats/Remember.t.sol index b8dbe7e38007c..fdf00e3839f72 100644 --- a/testdata/default/cheats/Remember.t.sol +++ b/testdata/default/cheats/Remember.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RememberTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RememberTest is Test { function testRememberKey() public { string memory mnemonic = "test test test test test test test test test test test junk"; diff --git a/testdata/default/cheats/ResetNonce.t.sol b/testdata/default/cheats/ResetNonce.t.sol index d8c911587095c..53c8ec54b09c0 100644 --- a/testdata/default/cheats/ResetNonce.t.sol +++ b/testdata/default/cheats/ResetNonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract ResetNonce is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ResetNonce is Test { Foo public fooContract; address barEOA; diff --git a/testdata/default/cheats/Rlp.t.sol b/testdata/default/cheats/Rlp.t.sol index c0f10f362458d..24f2ec833de7a 100644 --- a/testdata/default/cheats/Rlp.t.sol +++ b/testdata/default/cheats/Rlp.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Rlp is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract Rlp is Test { function testToRlp() public { bytes[] memory data = new bytes[](2); data[0] = hex"01"; diff --git a/testdata/default/cheats/Roll.t.sol b/testdata/default/cheats/Roll.t.sol index 0f26e3a431d7a..38e94b2bea267 100644 --- a/testdata/default/cheats/Roll.t.sol +++ b/testdata/default/cheats/Roll.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RollTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RollTest is Test { function testRoll() public { vm.roll(10); assertEq(block.number, 10, "roll failed"); diff --git a/testdata/default/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol index 86f4d33b1d41c..321b5b3cbbeed 100644 --- a/testdata/default/cheats/RpcUrls.t.sol +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RpcUrlTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RpcUrlTest is Test { // returns the correct url function testCanGetRpcUrl() public { string memory url = vm.rpcUrl("mainnet"); diff --git a/testdata/default/cheats/Seed.t.sol b/testdata/default/cheats/Seed.t.sol index 6db1b41da0ef3..0909c38a26f72 100644 --- a/testdata/default/cheats/Seed.t.sol +++ b/testdata/default/cheats/Seed.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SeedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SeedTest is Test { function testSeedAffectsRandom() public { // Use a known seed uint256 seed = 123456789; diff --git a/testdata/default/cheats/SetBlockhash.t.sol b/testdata/default/cheats/SetBlockhash.t.sol index 1274620df41a6..2897976d11e5e 100644 --- a/testdata/default/cheats/SetBlockhash.t.sol +++ b/testdata/default/cheats/SetBlockhash.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SetBlockhash is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SetBlockhash is Test { function testSetBlockhash() public { bytes32 blockHash = 0x1234567890123456789012345678901234567890123456789012345678901234; vm.setBlockhash(block.number - 1, blockHash); diff --git a/testdata/default/cheats/SetNonce.t.sol b/testdata/default/cheats/SetNonce.t.sol index e0fda6aaec688..19e8a366b92ca 100644 --- a/testdata/default/cheats/SetNonce.t.sol +++ b/testdata/default/cheats/SetNonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract SetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract SetNonceTest is Test { Foo public foo; function setUp() public { diff --git a/testdata/default/cheats/SetNonceUnsafe.t.sol b/testdata/default/cheats/SetNonceUnsafe.t.sol index 0caf2b4ce7421..9f799f0829acc 100644 --- a/testdata/default/cheats/SetNonceUnsafe.t.sol +++ b/testdata/default/cheats/SetNonceUnsafe.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract SetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract SetNonceTest is Test { Foo public foo; function setUp() public { diff --git a/testdata/default/cheats/Setup.t.sol b/testdata/default/cheats/Setup.t.sol index 4d6e5954b5fe1..9ce1682ab7a3c 100644 --- a/testdata/default/cheats/Setup.t.sol +++ b/testdata/default/cheats/Setup.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Victim { function assertSender(address sender) external { @@ -10,8 +9,7 @@ contract Victim { } } -contract VmSetupTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract VmSetupTest is Test { Victim victim; function setUp() public { diff --git a/testdata/default/cheats/Shuffle.t.sol b/testdata/default/cheats/Shuffle.t.sol index 565f596a6dcb5..5701fa7ab72c2 100644 --- a/testdata/default/cheats/Shuffle.t.sol +++ b/testdata/default/cheats/Shuffle.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShuffleTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ShuffleTest is Test { function testDeterministicShuffle() public { // Use a known seed uint256 seed = 123456789; diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol index b21a6050b76c1..ed0a9aa2e93de 100644 --- a/testdata/default/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SignTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SignTest is Test { function testSignDigest(uint248 pk, bytes32 digest) public { vm.assume(pk != 0); diff --git a/testdata/default/cheats/SignP256.t.sol b/testdata/default/cheats/SignP256.t.sol index b92588ce9f823..d8d07acbf67b7 100644 --- a/testdata/default/cheats/SignP256.t.sol +++ b/testdata/default/cheats/SignP256.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SignTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SignTest is Test { function testSignP256() public { bytes32 pk = hex"A8568B74282DCC66FF70F10B4CE5CC7B391282F5381BBB4F4D8DD96974B16E6B"; bytes32 digest = hex"54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad"; diff --git a/testdata/default/cheats/Skip.t.sol b/testdata/default/cheats/Skip.t.sol index d7e75fa0f51af..0eff55b95db30 100644 --- a/testdata/default/cheats/Skip.t.sol +++ b/testdata/default/cheats/Skip.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SkipTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SkipTest is Test { function testSkip() public { vm.skip(true); revert("Should not reach this revert"); diff --git a/testdata/default/cheats/Sleep.t.sol b/testdata/default/cheats/Sleep.t.sol index 7af548e742573..b3268d41d3672 100644 --- a/testdata/default/cheats/Sleep.t.sol +++ b/testdata/default/cheats/Sleep.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SleepTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SleepTest is Test { function testSleep() public { uint256 milliseconds = 1234; diff --git a/testdata/default/cheats/Sort.t.sol b/testdata/default/cheats/Sort.t.sol index 2f557d54c2bbc..91991b572a081 100644 --- a/testdata/default/cheats/Sort.t.sol +++ b/testdata/default/cheats/Sort.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SortTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SortTest is Test { function testSortCheatcode() public { uint256[] memory numbers = new uint256[](3); numbers[0] = 3; diff --git a/testdata/default/cheats/StateDiffBytesString.t.sol b/testdata/default/cheats/StateDiffBytesString.t.sol index 31d37e9bd5b74..a61e3ee37f60d 100644 --- a/testdata/default/cheats/StateDiffBytesString.t.sol +++ b/testdata/default/cheats/StateDiffBytesString.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract BytesStringStorage { // Short string (less than 32 bytes) @@ -48,8 +47,7 @@ contract BytesStringStorage { } } -contract StateDiffBytesStringTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StateDiffBytesStringTest is Test { BytesStringStorage bytesStringStorage; function setUp() public { diff --git a/testdata/default/cheats/StateDiffMappings.t.sol b/testdata/default/cheats/StateDiffMappings.t.sol index b4ed985791cde..45b53e0dfc5f0 100644 --- a/testdata/default/cheats/StateDiffMappings.t.sol +++ b/testdata/default/cheats/StateDiffMappings.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract MappingStorage { // Simple mappings only @@ -29,8 +28,7 @@ contract MappingStorage { } } -contract StateDiffMappingsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StateDiffMappingsTest is Test { MappingStorage public mappingStorage; function setUp() public { diff --git a/testdata/default/cheats/StateDiffStorageLayout.t.sol b/testdata/default/cheats/StateDiffStorageLayout.t.sol index 027e3d8567098..298a8eb8bf236 100644 --- a/testdata/default/cheats/StateDiffStorageLayout.t.sol +++ b/testdata/default/cheats/StateDiffStorageLayout.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleStorage { uint256 public value; // Slot 0 @@ -97,8 +96,7 @@ contract TwoDArrayStorage { } } -contract StateDiffStorageLayoutTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StateDiffStorageLayoutTest is Test { SimpleStorage simpleStorage; VariousArrays variousArrays; TwoDArrayStorage twoDArrayStorage; diff --git a/testdata/default/cheats/StateDiffStructTest.t.sol b/testdata/default/cheats/StateDiffStructTest.t.sol index a0b29f156c673..54e5403411dd4 100644 --- a/testdata/default/cheats/StateDiffStructTest.t.sol +++ b/testdata/default/cheats/StateDiffStructTest.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract DiffTest { // slot 0 @@ -49,8 +48,7 @@ contract DiffTest { } } -contract StateDiffStructTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StateDiffStructTest is Test { DiffTest internal test; function setUp() public { diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 8751a04094129..4a72639ce8c8d 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct Storage { uint256 slot0; uint256 slot1; } -contract StateSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract StateSnapshotTest is Test { Storage store; function setUp() public { @@ -105,9 +102,7 @@ contract StateSnapshotTest is DSTest { } // TODO: remove this test suite once `snapshot*` has been deprecated in favor of `snapshotState*`. -contract DeprecatedStateSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DeprecatedStateSnapshotTest is Test { Storage store; function setUp() public { diff --git a/testdata/default/cheats/StorageSlotState.t.sol b/testdata/default/cheats/StorageSlotState.t.sol index 7c2971f22a14f..6c80900038558 100644 --- a/testdata/default/cheats/StorageSlotState.t.sol +++ b/testdata/default/cheats/StorageSlotState.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract StorageSlotStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract StorageSlotStateTest is Test { function test_gas_two_reads() public { Read read = new Read(); read.number(); diff --git a/testdata/default/cheats/Store.t.sol b/testdata/default/cheats/Store.t.sol index 9a1ce6101c1b0..e096dc3f50341 100644 --- a/testdata/default/cheats/Store.t.sol +++ b/testdata/default/cheats/Store.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Storage { uint256 public slot0 = 10; uint256 public slot1 = 20; } -contract StoreTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StoreTest is Test { Storage store; function setUp() public { diff --git a/testdata/default/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol index 256d65302a445..fbe8718d99cc6 100644 --- a/testdata/default/cheats/StringUtils.t.sol +++ b/testdata/default/cheats/StringUtils.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract StringManipulationTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract StringManipulationTest is Test { function testToLowercase() public { string memory original = "Hello World"; string memory lowercased = vm.toLowercase(original); diff --git a/testdata/default/cheats/ToString.t.sol b/testdata/default/cheats/ToString.t.sol index f19110e3e8655..3fda38c4e5f1b 100644 --- a/testdata/default/cheats/ToString.t.sol +++ b/testdata/default/cheats/ToString.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ToStringTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ToStringTest is Test { function testAddressToString() public { address testAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; string memory stringAddress = vm.toString(testAddress); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol index 9802389877ed2..613ec7c4046fb 100644 --- a/testdata/default/cheats/Toml.t.sol +++ b/testdata/default/cheats/Toml.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; library TomlStructs { address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); @@ -58,7 +56,7 @@ library TomlStructs { } } -contract ParseTomlTest is DSTest { +contract ParseTomlTest is Test { using TomlStructs for *; struct FlatToml { @@ -80,7 +78,6 @@ contract ParseTomlTest is DSTest { string name; } - Vm constant vm = Vm(HEVM_ADDRESS); string toml; function setUp() public { @@ -349,9 +346,7 @@ contract ParseTomlTest is DSTest { } } -contract WriteTomlTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract WriteTomlTest is Test { string json1; string json2; diff --git a/testdata/default/cheats/Travel.t.sol b/testdata/default/cheats/Travel.t.sol index b46d2e7ad7041..4937b679dc413 100644 --- a/testdata/default/cheats/Travel.t.sol +++ b/testdata/default/cheats/Travel.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ChainIdTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ChainIdTest is Test { function testChainId() public { vm.chainId(10); assertEq(block.chainid, 10, "chainId switch failed"); diff --git a/testdata/default/cheats/TryFfi.sol b/testdata/default/cheats/TryFfi.sol index 58d93a48b4fea..916377b6cc590 100644 --- a/testdata/default/cheats/TryFfi.sol +++ b/testdata/default/cheats/TryFfi.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract TryFfiTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract TryFfiTest is Test { function testTryFfi() public { string[] memory inputs = new string[](3); inputs[0] = "bash"; diff --git a/testdata/default/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol index 29d86699f64d1..6d11cdb3da02f 100644 --- a/testdata/default/cheats/UnixTime.t.sol +++ b/testdata/default/cheats/UnixTime.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract UnixTimeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract UnixTimeTest is Test { // This is really wide because CI sucks. uint256 constant errMargin = 1000; diff --git a/testdata/default/cheats/Wallet.t.sol b/testdata/default/cheats/Wallet.t.sol index d061b55ae45c5..e25ed4a9d071e 100644 --- a/testdata/default/cheats/Wallet.t.sol +++ b/testdata/default/cheats/Wallet.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo {} -contract WalletTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract WalletTest is Test { uint256 internal constant Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 private constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; diff --git a/testdata/default/cheats/Warp.t.sol b/testdata/default/cheats/Warp.t.sol index 7ba53f6e5eda4..f3512944d6797 100644 --- a/testdata/default/cheats/Warp.t.sol +++ b/testdata/default/cheats/Warp.t.sol @@ -1,26 +1,23 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract WarpTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract WarpTest is Test { function testWarp() public { vm.warp(10); - assertEq(block.timestamp, 10, "warp failed"); + assertEq(vm.getBlockTimestamp(), 10, "warp failed"); } function testWarpFuzzed(uint32 jump) public { - uint256 pre = block.timestamp; - vm.warp(block.timestamp + jump); - assertEq(block.timestamp, pre + jump, "warp failed"); + uint256 pre = vm.getBlockTimestamp(); + vm.warp(vm.getBlockTimestamp() + jump); + assertEq(vm.getBlockTimestamp(), pre + jump, "warp failed"); } function testWarp2() public { - assertEq(block.timestamp, 1); + assertEq(vm.getBlockTimestamp(), 1); vm.warp(100); - assertEq(block.timestamp, 100); + assertEq(vm.getBlockTimestamp(), 100); } } diff --git a/testdata/default/cheats/dumpState.t.sol b/testdata/default/cheats/dumpState.t.sol index 8a8675ca5eace..887de131e5888 100644 --- a/testdata/default/cheats/dumpState.t.sol +++ b/testdata/default/cheats/dumpState.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleContract { constructor() { @@ -12,9 +11,7 @@ contract SimpleContract { } } -contract DumpStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DumpStateTest is Test { function testDumpStateCheatAccount() public { // Path to temporary file that is deleted after the test string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_cheat.json"); diff --git a/testdata/default/cheats/getBlockNumber.t.sol b/testdata/default/cheats/getBlockNumber.t.sol index 18e2a163f3b86..491a726781b99 100644 --- a/testdata/default/cheats/getBlockNumber.t.sol +++ b/testdata/default/cheats/getBlockNumber.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetBlockNumberTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetBlockNumberTest is Test { function testGetBlockNumber() public { uint256 height = vm.getBlockNumber(); assertEq(height, uint256(block.number), "height should be equal to block.number"); diff --git a/testdata/default/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol index 94ce6804c1260..fa660e75fb775 100644 --- a/testdata/default/cheats/loadAllocs.t.sol +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract LoadAllocsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract LoadAllocsTest is Test { address constant ALLOCD = address(0x420); address constant ALLOCD_B = address(0x421); diff --git a/testdata/default/core/BadSigAfterInvariant.t.sol b/testdata/default/core/BadSigAfterInvariant.t.sol index 7b485e24f4a04..1e2ce2f9a9448 100644 --- a/testdata/default/core/BadSigAfterInvariant.t.sol +++ b/testdata/default/core/BadSigAfterInvariant.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract BadSigAfterInvariant is DSTest { +contract BadSigAfterInvariant is Test { function afterinvariant() public {} function testShouldPassWithWarning() public { diff --git a/testdata/default/core/ContractEnvironment.t.sol b/testdata/default/core/ContractEnvironment.t.sol index 452fa88022557..ddb03b41a2556 100644 --- a/testdata/default/core/ContractEnvironment.t.sol +++ b/testdata/default/core/ContractEnvironment.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract ContractEnvironmentTest is DSTest { +contract ContractEnvironmentTest is Test { function chainId() internal view returns (uint256 id) { assembly { id := chainid() diff --git a/testdata/default/core/FailingTestAfterFailedSetup.t.sol b/testdata/default/core/FailingTestAfterFailedSetup.t.sol deleted file mode 100644 index c56f4ba5de605..0000000000000 --- a/testdata/default/core/FailingTestAfterFailedSetup.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract FailingTestAfterFailedSetupTest is DSTest { - function setUp() public { - assertTrue(false); - } - - function testAssertSuccess() public { - assertTrue(true); - } - - function testAssertFailure() public { - assertTrue(false); - } -} diff --git a/testdata/default/core/LegacyAssertions.t.sol b/testdata/default/core/LegacyAssertions.t.sol deleted file mode 100644 index c35a63417efc3..0000000000000 --- a/testdata/default/core/LegacyAssertions.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract NoAssertionsRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testMultipleAssertFailures() public { - vm.assertEq(uint256(1), uint256(2)); - vm.assertLt(uint256(5), uint256(4)); - } -} - -contract LegacyAssertionsTest { - bool public failed; - - function testFlagNotSetSuccess() public {} - - function testFlagSetFailure() public { - failed = true; - } -} diff --git a/testdata/default/core/PaymentFailure.t.sol b/testdata/default/core/PaymentFailure.t.sol deleted file mode 100644 index 52c42fd376052..0000000000000 --- a/testdata/default/core/PaymentFailure.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Payable { - function pay() public payable {} -} - -contract PaymentFailureTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testCantPay() public { - Payable target = new Payable(); - vm.prank(address(1)); - target.pay{value: 1}(); - } -} diff --git a/testdata/default/core/Reverting.t.sol b/testdata/default/core/Reverting.t.sol index 73877cab0b542..699b2bf1123d3 100644 --- a/testdata/default/core/Reverting.t.sol +++ b/testdata/default/core/Reverting.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RevertingTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RevertingTest is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevert() public { vm.expectRevert("should revert here"); diff --git a/testdata/default/core/SetupConsistency.t.sol b/testdata/default/core/SetupConsistency.t.sol index 08d766f0f9242..bf5a2e31935c1 100644 --- a/testdata/default/core/SetupConsistency.t.sol +++ b/testdata/default/core/SetupConsistency.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract SetupConsistencyCheck is DSTest { +contract SetupConsistencyCheck is Test { uint256 two; uint256 four; uint256 result; diff --git a/testdata/default/fork/ForkSame_1.t.sol b/testdata/default/fork/ForkSame_1.t.sol index 949c7ea9ec17d..f8c0234f5baa6 100644 --- a/testdata/default/fork/ForkSame_1.t.sol +++ b/testdata/default/fork/ForkSame_1.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - Vm constant vm = Vm(HEVM_ADDRESS); uint256 forkA; // this will create two _different_ forks during setup diff --git a/testdata/default/fork/ForkSame_2.t.sol b/testdata/default/fork/ForkSame_2.t.sol index 949c7ea9ec17d..f8c0234f5baa6 100644 --- a/testdata/default/fork/ForkSame_2.t.sol +++ b/testdata/default/fork/ForkSame_2.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - Vm constant vm = Vm(HEVM_ADDRESS); uint256 forkA; // this will create two _different_ forks during setup diff --git a/testdata/default/fork/LaunchFork.t.sol b/testdata/default/fork/LaunchFork.t.sol index 710ac97f51d97..8784b84b8e7e1 100644 --- a/testdata/default/fork/LaunchFork.t.sol +++ b/testdata/default/fork/LaunchFork.t.sol @@ -1,14 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.6.12; -import "ds-test/test.sol"; +import "utils/Test.sol"; import "./DssExecLib.sol"; -interface Vm { - function store(address account, bytes32 slot, bytes32 value) external; - function activeFork() external returns (uint256); -} - interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); @@ -23,14 +18,13 @@ contract DummyContract { } } -contract ForkTest is DSTest { +abstract contract ForkTest is Test { address constant DAI_TOKEN_ADDR = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // checks that we can retrieve the fork we launched with function testActiveFork() public { - Vm cheatvm = Vm(HEVM_ADDRESS); - uint256 activeFork = cheatvm.activeFork(); + uint256 activeFork = vm.activeFork(); // launch fork has id `0` assertEq(activeFork, 0); } @@ -52,13 +46,12 @@ contract ForkTest is DSTest { } function testCheatcode() public { - Vm cheatvm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); bytes32 value = bytes32(uint256(1)); // "0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff" is the slot storing the balance of zero address for the weth contract // `cast index address uint 0x0000000000000000000000000000000000000000 3` bytes32 zero_address_balance_slot = 0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff; - cheatvm.store(WETH_TOKEN_ADDR, zero_address_balance_slot, value); + vm.store(WETH_TOKEN_ADDR, zero_address_balance_slot, value); assertEq( WETH.balanceOf(0x0000000000000000000000000000000000000000), 1, @@ -77,3 +70,9 @@ contract ForkTest is DSTest { assertEq(WETH.balanceOf(address(this)) - current, 1000, "WETH balance is not equal to deposited amount."); } } + +contract ForkTestHttp is ForkTest { + function setUp() public { + vm.createSelectFork("mainnet"); + } +} diff --git a/testdata/default/fs/Disabled.t.sol b/testdata/default/fs/Disabled.t.sol index 36f05c211fcad..19fb755c4f425 100644 --- a/testdata/default/fs/Disabled.t.sol +++ b/testdata/default/fs/Disabled.t.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract DisabledTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +// No permissions: all FS operations should revert. +/// forge-config: default.fs_permissions = [] +contract DisabledAccessTest is Test { function testReadFile() public { string memory path = "fixtures/File/read.txt"; vm._expectCheatcodeRevert(); vm.readFile(path); + + vm._expectCheatcodeRevert(); + vm.readFileBinary(path); } function testReadLine() public { @@ -20,21 +23,27 @@ contract DisabledTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); vm.writeFile(path, data); + + vm._expectCheatcodeRevert(); + vm.writeFileBinary(path, bytes(data)); } function testWriteLine() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); vm.writeLine(path, data); } function testRemoveFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; + vm._expectCheatcodeRevert(); vm.removeFile(path); } diff --git a/testdata/default/fs/Default.t.sol b/testdata/default/fs/ReadOnly.sol similarity index 68% rename from testdata/default/fs/Default.t.sol rename to testdata/default/fs/ReadOnly.sol index e1524963f6c3e..a738ae20e61c4 100644 --- a/testdata/default/fs/Default.t.sol +++ b/testdata/default/fs/ReadOnly.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract DefaultAccessTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +// Default permissions: only read FS operations should succeed. +/// forge-config: default.fs_permissions = [{ access = "read", path = "./fixtures"}] +contract ReadOnlyAccessTest is Test { function testReadFile() public { string memory path = "fixtures/File/read.txt"; vm.readFile(path); @@ -20,7 +20,7 @@ contract DefaultAccessTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm._expectCheatcodeRevert(); @@ -31,7 +31,7 @@ contract DefaultAccessTest is DSTest { } function testWriteLine() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm._expectCheatcodeRevert(); @@ -39,7 +39,7 @@ contract DefaultAccessTest is DSTest { } function testRemoveFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; vm._expectCheatcodeRevert(); vm.removeFile(path); diff --git a/testdata/default/fuzz/Fuzz.t.sol b/testdata/default/fuzz/Fuzz.t.sol deleted file mode 100644 index b1cf54716be93..0000000000000 --- a/testdata/default/fuzz/Fuzz.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FuzzTest is DSTest { - constructor() { - emit log("constructor"); - } - - Vm constant vm = Vm(HEVM_ADDRESS); - - function setUp() public { - emit log("setUp"); - } - - function testShouldFailFuzz(uint8 x) public { - emit log("testFailFuzz"); - require(x > 128, "should revert"); - } - - function testSuccessfulFuzz(uint128 a, uint128 b) public { - emit log("testSuccessfulFuzz"); - assertEq(uint256(a) + uint256(b), uint256(a) + uint256(b)); - } - - function testToStringFuzz(bytes32 data) public { - vm.toString(data); - } -} diff --git a/testdata/default/fuzz/FuzzCollection.t.sol b/testdata/default/fuzz/FuzzCollection.t.sol deleted file mode 100644 index 0c98ddc66b6b2..0000000000000 --- a/testdata/default/fuzz/FuzzCollection.t.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract SampleContract { - uint256 public counter; - uint256 public counterX2; - address public owner = address(0xBEEF); - bool public found_needle; - - event Incremented(uint256 counter); - - modifier onlyOwner() { - require(msg.sender == owner, "ONLY_OWNER"); - _; - } - - function compare(uint256 val) public { - if (val == 0x4446) { - found_needle = true; - } - } - - function incrementBy(uint256 numToIncrement) public onlyOwner { - counter += numToIncrement; - counterX2 += numToIncrement * 2; - - emit Incremented(counter); - } - - function breakTheInvariant(uint256 x) public { - if (x == 0x5556) { - counterX2 = 0; - } - } -} - -interface Vm { - function startPrank(address) external; - function expectRevert(bytes calldata msg) external; -} - -contract SampleContractTest is DSTest { - Vm hevm = Vm(HEVM_ADDRESS); - - event Incremented(uint256 counter); - - SampleContract public sample; - - function setUp() public { - sample = new SampleContract(); - } - - function testIncrement(address caller) public { - hevm.startPrank(address(caller)); - - hevm.expectRevert("ONLY_OWNER"); - sample.incrementBy(1); - } - - function testNeedle(uint256 needle) public { - sample.compare(needle); - require(!sample.found_needle(), "needle found."); - } - - function invariantCounter() public { - require(sample.counter() * 2 == sample.counterX2(), "broken counter."); - } -} diff --git a/testdata/default/fuzz/FuzzFailurePersist.t.sol b/testdata/default/fuzz/FuzzFailurePersist.t.sol deleted file mode 100644 index 3787060411e5d..0000000000000 --- a/testdata/default/fuzz/FuzzFailurePersist.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct TestTuple { - address user; - uint256 amount; -} - -contract FuzzFailurePersistTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - - function test_persist_fuzzed_failure( - uint256 x, - int256 y, - address addr, - bool cond, - string calldata test, - TestTuple calldata tuple, - address[] calldata addresses - ) public { - // dummy assume to trigger runs - vm.assume(x > 1 && x < 1111111111111111111111111111); - vm.assume(y > 1 && y < 1111111111111111111111111111); - require(false); - } -} diff --git a/testdata/default/fuzz/FuzzInt.t.sol b/testdata/default/fuzz/FuzzInt.t.sol deleted file mode 100644 index a47ff2953331f..0000000000000 --- a/testdata/default/fuzz/FuzzInt.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 -// random values (instead edge cases) are generated if no fixtures defined -contract FuzzNumbersTest is DSTest { - function testPositive(int256) public { - assertTrue(true); - } - - function testNegativeHalf(int256 val) public { - assertTrue(val < 2 ** 128 - 1); - } - - function testNegative0(int256 val) public { - assertTrue(val == 0); - } - - function testNegative1(int256 val) public { - assertTrue(val == -1); - } - - function testNegative2(int128 val) public { - assertTrue(val == 1); - } - - function testNegativeMax0(int256 val) public { - assertTrue(val == type(int256).max); - } - - function testNegativeMax1(int256 val) public { - assertTrue(val == type(int256).max - 2); - } - - function testNegativeMin0(int256 val) public { - assertTrue(val == type(int256).min); - } - - function testNegativeMin1(int256 val) public { - assertTrue(val == type(int256).min + 2); - } - - function testEquality(int256 x, int256 y) public { - int256 xy; - - unchecked { - xy = x * y; - } - - if ((x != 0 && xy / x != y)) { - return; - } - - assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); - } -} diff --git a/testdata/default/fuzz/FuzzPositive.t.sol b/testdata/default/fuzz/FuzzPositive.t.sol deleted file mode 100644 index 7d3639dfe5ec2..0000000000000 --- a/testdata/default/fuzz/FuzzPositive.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract FuzzPositive is DSTest { - function testSuccessChecker(uint256 val) public { - assertTrue(true); - } - - function testSuccessChecker2(int256 val) public { - assert(val == val); - } - - function testSuccessChecker3(uint32 val) public { - assert(val + 0 == val); - } -} diff --git a/testdata/default/fuzz/FuzzUint.t.sol b/testdata/default/fuzz/FuzzUint.t.sol deleted file mode 100644 index c0cbf6466c31b..0000000000000 --- a/testdata/default/fuzz/FuzzUint.t.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 -// random values (instead edge cases) are generated if no fixtures defined -contract FuzzNumbersTest is DSTest { - function testPositive(uint256) public { - assertTrue(true); - } - - function testNegativeHalf(uint256 val) public { - assertTrue(val < 2 ** 128 - 1); - } - - function testNegative0(uint256 val) public { - assertTrue(val == 0); - } - - function testNegative2(uint256 val) public { - assertTrue(val == 2); - } - - function testNegative2Max(uint256 val) public { - assertTrue(val == type(uint256).max - 2); - } - - function testNegativeMax(uint256 val) public { - assertTrue(val == type(uint256).max); - } - - function testEquality(uint256 x, uint256 y) public { - uint256 xy; - - unchecked { - xy = x * y; - } - - if ((x != 0 && xy / x != y)) { - return; - } - - assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol deleted file mode 100644 index 3030b43e077cc..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract AfterInvariantHandler { - uint256 public count; - - function inc() external { - count += 1; - } -} - -contract InvariantAfterInvariantTest is DSTest { - AfterInvariantHandler handler; - - function setUp() public { - handler = new AfterInvariantHandler(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = handler.inc.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function afterInvariant() public { - require(handler.count() < 10, "afterInvariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 11 - function invariant_after_invariant_failure() public view { - require(handler.count() < 20, "invariant after invariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 11 - function invariant_failure() public view { - require(handler.count() < 9, "invariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 5 - function invariant_success() public view { - require(handler.count() < 11, "invariant should not fail"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol deleted file mode 100644 index 9808a870f7228..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function doSomething(uint256 param) public { - vm.assume(param == 0); - } -} - -contract InvariantAssume is DSTest { - Handler handler; - - function setUp() public { - handler = new Handler(); - } - - function invariant_dummy() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol deleted file mode 100644 index 3d4c51eac41e5..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -// https://github.com/foundry-rs/foundry/issues/5868 -contract Owned { - address public owner; - address private ownerCandidate; - - constructor() { - owner = msg.sender; - } - - modifier onlyOwner() { - require(msg.sender == owner); - _; - } - - modifier onlyOwnerCandidate() { - require(msg.sender == ownerCandidate); - _; - } - - function transferOwnership(address candidate) external onlyOwner { - ownerCandidate = candidate; - } - - function acceptOwnership() external onlyOwnerCandidate { - owner = ownerCandidate; - } -} - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - Owned owned; - - constructor(Owned _owned) { - owned = _owned; - } - - function transferOwnership(address sender, address candidate) external { - vm.assume(sender != address(0)); - vm.prank(sender); - owned.transferOwnership(candidate); - } - - function acceptOwnership(address sender) external { - vm.assume(sender != address(0)); - vm.prank(sender); - owned.acceptOwnership(); - } -} - -contract InvariantCalldataDictionary is DSTest { - address owner; - Owned owned; - Handler handler; - address[] actors; - - function setUp() public { - owner = address(this); - owned = new Owned(); - handler = new Handler(owned); - actors.push(owner); - actors.push(address(777)); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = handler.transferOwnership.selector; - selectors[1] = handler.acceptOwnership.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function fixtureSender() external returns (address[] memory) { - return actors; - } - - function fixtureCandidate() external returns (address[] memory) { - return actors; - } - - function invariant_owner_never_changes() public { - assertEq(owned.owner(), owner); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol deleted file mode 100644 index 737cf5ba9dd05..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ContractWithCustomError { - error InvariantCustomError(uint256, string); - - function revertWithInvariantCustomError() external { - revert InvariantCustomError(111, "custom"); - } -} - -contract Handler is DSTest { - ContractWithCustomError target; - - constructor() { - target = new ContractWithCustomError(); - } - - function revertTarget() external { - target.revertWithInvariantCustomError(); - } -} - -contract InvariantCustomError is DSTest { - Handler handler; - - function setUp() external { - handler = new Handler(); - } - - function invariant_decode_error() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol deleted file mode 100644 index 8fe0bed2c6e7c..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract InvariantSenders { - function checkSender() external { - require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); - require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); - require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); - } -} - -contract InvariantExcludedSendersTest is DSTest { - InvariantSenders target; - - function setUp() public { - target = new InvariantSenders(); - } - - function invariant_check_sender() public view {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol deleted file mode 100644 index b3f1e17cb2497..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; - -contract Target { - bool ownerFound; - bool amountFound; - bool magicFound; - bool keyFound; - bool backupFound; - bool extraStringFound; - - function fuzzWithFixtures( - address owner_, - uint256 _amount, - int32 magic, - bytes32 key, - bytes memory backup, - string memory extra - ) external { - if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { - ownerFound = true; - } - if (_amount == 1122334455) amountFound = true; - if (magic == -777) magicFound = true; - if (key == "abcd1234") keyFound = true; - if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; - if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { - extraStringFound = true; - } - } - - function isCompromised() public view returns (bool) { - return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; - } -} - -/// Try to compromise target contract by finding all accepted values using fixtures. -contract InvariantFixtures is DSTest { - Target target; - address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; - uint256[] public fixture_amount = [1, 2, 1122334455]; - - function setUp() public { - target = new Target(); - } - - function fixtureMagic() external returns (int32[2] memory) { - int32[2] memory magic; - magic[0] = -777; - magic[1] = 777; - return magic; - } - - function fixtureKey() external pure returns (bytes32[] memory) { - bytes32[] memory keyFixture = new bytes32[](1); - keyFixture[0] = "abcd1234"; - return keyFixture; - } - - function fixtureBackup() external pure returns (bytes[] memory) { - bytes[] memory backupFixture = new bytes[](1); - backupFixture[0] = "qwerty1234"; - return backupFixture; - } - - function fixtureExtra() external pure returns (string[] memory) { - string[] memory extraFixture = new string[](1); - extraFixture[0] = "112233aabbccdd"; - return extraFixture; - } - - function invariant_target_not_compromised() public { - assertEq(target.isCompromised(), false); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol deleted file mode 100644 index 5ff50e782ac55..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Handler is DSTest { - function doSomething() public { - require(false, "failed on revert"); - } -} - -contract InvariantHandlerFailure is DSTest { - bytes4[] internal selectors; - - Handler handler; - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = handler.doSomething.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function setUp() public { - handler = new Handler(); - } - - function statefulFuzz_BrokenInvariant() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol deleted file mode 100644 index 817e65d179f81..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -/*////////////////////////////////////////////////////////////// - Here we test that the fuzz engine can include a contract created during the fuzz - in its fuzz dictionary and eventually break the invariant. - Specifically, can Judas, a created contract from Jesus, break Jesus contract - by revealing his identity. -/*/ -///////////////////////////////////////////////////////////// - -contract Jesus { - address fren; - bool public identity_revealed; - - function create_fren() public { - fren = address(new Judas()); - } - - function kiss() public { - require(msg.sender == fren); - identity_revealed = true; - } -} - -contract Judas { - Jesus jesus; - - constructor() { - jesus = Jesus(msg.sender); - } - - function betray() public { - jesus.kiss(); - } -} - -contract InvariantInnerContract is DSTest { - Jesus jesus; - - function setUp() public { - jesus = new Jesus(); - } - - function invariantHideJesus() public { - require(jesus.identity_revealed() == false, "jesus betrayed"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol deleted file mode 100644 index bd70dd3aeafba..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -// https://github.com/foundry-rs/foundry/issues/7219 - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function thisFunctionReverts() external { - if (block.number < 10) {} else { - revert(); - } - } - - function advanceTime(uint256 blocks) external { - blocks = blocks % 10; - vm.roll(block.number + blocks); - vm.warp(block.timestamp + blocks * 12); - } -} - -contract InvariantPreserveState is DSTest { - Handler handler; - - function setUp() public { - handler = new Handler(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = handler.thisFunctionReverts.selector; - selectors[1] = handler.advanceTime.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function invariant_preserve_state() public { - assertTrue(true); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol deleted file mode 100644 index 74a01f1805de6..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Malicious { - function world() public { - // add code so contract is accounted as valid sender - // see https://github.com/foundry-rs/foundry/issues/4245 - payable(msg.sender).call(""); - } -} - -contract Vulnerable { - bool public open_door = false; - bool public stolen = false; - Malicious mal; - - constructor(address _mal) { - mal = Malicious(_mal); - } - - function hello() public { - open_door = true; - mal.world(); - open_door = false; - } - - function backdoor() public { - require(open_door, ""); - stolen = true; - } -} - -contract InvariantReentrancy is DSTest { - Vulnerable vuln; - Malicious mal; - - function setUp() public { - mal = new Malicious(); - vuln = new Vulnerable(address(mal)); - } - - // do not include `mal` in identified contracts - // see https://github.com/foundry-rs/foundry/issues/4245 - function targetContracts() public view returns (address[] memory) { - address[] memory targets = new address[](1); - targets[0] = address(vuln); - return targets; - } - - function invariantNotStolen() public { - require(vuln.stolen() == false, "stolen"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol deleted file mode 100644 index d15619b635f18..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -interface IERC20 { - function totalSupply() external view returns (uint256 supply); -} - -contract RollForkHandler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - uint256 public totalSupply; - - function work() external { - vm.rollFork(block.number + 1); - totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); - } -} - -contract InvariantRollForkBlockTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - RollForkHandler forkHandler; - - function setUp() public { - vm.createSelectFork("mainnet", 19812632); - forkHandler = new RollForkHandler(); - } - - /// forge-config: default.invariant.runs = 2 - /// forge-config: default.invariant.depth = 4 - function invariant_fork_handler_block() public { - require(block.number < 19812634, "too many blocks mined"); - } -} - -contract InvariantRollForkStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - RollForkHandler forkHandler; - - function setUp() public { - vm.createSelectFork("mainnet", 19812632); - forkHandler = new RollForkHandler(); - } - - /// forge-config: default.invariant.runs = 1 - function invariant_fork_handler_state() public { - require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol deleted file mode 100644 index 40824a2602e77..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FindFromReturnValue { - bool public found = false; - - function seed() public returns (int256) { - int256 mystery = 13337; - return (1337 + mystery); - } - - function find(int256 i) public { - int256 mystery = 13337; - if (i == 1337 + mystery) { - found = true; - } - } -} - -contract FindFromReturnValueTest is DSTest { - FindFromReturnValue target; - - function setUp() public { - target = new FindFromReturnValue(); - } - - /// forge-config: default.invariant.runs = 50 - /// forge-config: default.invariant.depth = 300 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_value_not_found() public view { - require(!target.found(), "value from return found"); - } -} - -contract FindFromLogValue { - event FindFromLog(int256 indexed mystery, bytes32 rand); - - bool public found = false; - - function seed() public { - int256 mystery = 13337; - emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); - } - - function find(int256 i) public { - int256 mystery = 13337; - if (i == 1337 + mystery) { - found = true; - } - } -} - -contract FindFromLogValueTest is DSTest { - FindFromLogValue target; - - function setUp() public { - target = new FindFromLogValue(); - } - - /// forge-config: default.invariant.runs = 50 - /// forge-config: default.invariant.depth = 300 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_value_not_found() public view { - require(!target.found(), "value from logs found"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol b/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol deleted file mode 100644 index 993d806f81b38..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract SequenceNoReverts { - uint256 public count; - - function work(uint256 x) public { - require(x % 2 != 0); - count++; - } -} - -contract SequenceNoRevertsTest is DSTest { - SequenceNoReverts target; - - function setUp() public { - target = new SequenceNoReverts(); - } - - function invariant_no_reverts() public view { - require(target.count() < 10, "condition met"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol deleted file mode 100644 index 5699d9c455e89..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShrinkBigSequence { - uint256 cond; - - function work(uint256 x) public { - if (x % 2 != 0 && x < 9000) { - cond++; - } - } - - function checkCond() public view { - require(cond < 77, "condition met"); - } -} - -contract ShrinkBigSequenceTest is DSTest { - ShrinkBigSequence target; - - function setUp() public { - target = new ShrinkBigSequence(); - } - - function invariant_shrink_big_sequence() public view { - target.checkCond(); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol deleted file mode 100644 index d971367b69988..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShrinkFailOnRevert { - uint256 cond; - - function work(uint256 x) public { - if (x % 2 != 0 && x < 9000) { - cond++; - } - require(cond < 10, "condition met"); - } -} - -contract ShrinkFailOnRevertTest is DSTest { - ShrinkFailOnRevert target; - - function setUp() public { - target = new ShrinkFailOnRevert(); - } - - function invariant_shrink_fail_on_revert() public view {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol deleted file mode 100644 index fa4a6e945804e..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract Counter { - uint256 public number; - - function increment() public { - number++; - } - - function decrement() public { - number--; - } -} - -contract InvariantShrinkWithAssert is DSTest { - Counter public counter; - - function setUp() public { - counter = new Counter(); - } - - function invariant_with_assert() public { - assertTrue(counter.number() < 2, "wrong counter"); - } - - function invariant_with_require() public { - require(counter.number() < 2, "wrong counter"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol deleted file mode 100644 index bb62f34c6a965..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract InvariantBreaker { - bool public flag0 = true; - bool public flag1 = true; - - function set0(int256 val) public returns (bool) { - if (val % 100 == 0) { - flag0 = false; - } - return flag0; - } - - function set1(int256 val) public returns (bool) { - if (val % 10 == 0 && !flag0) { - flag1 = false; - } - return flag1; - } -} - -contract InvariantTest is DSTest { - InvariantBreaker inv; - - function setUp() public { - inv = new InvariantBreaker(); - } - - function invariant_neverFalse() public { - require(inv.flag1(), "false"); - } - - function statefulFuzz_neverFalseWithInvariantAlias() public { - require(inv.flag1(), "false"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol deleted file mode 100644 index e2e850e316d1e..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract ExcludeContracts is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - new Hello(); - } - - function excludeContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(hello); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol deleted file mode 100644 index e2251f42c8126..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Hello { - bool public world = false; - - function change() public { - world = true; - } - - function real_change() public { - world = false; - } -} - -contract ExcludeSelectors is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function excludeSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hello.change.selector; - targets[0] = FuzzSelector(address(hello), selectors); - return targets; - } - - function invariantFalseWorld() public { - require(hello.world() == false, "true world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol deleted file mode 100644 index dda07074d18c7..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - address seed_address = address(0xdeadbeef); - bool public world = true; - - function changeBeef() public { - require(msg.sender == address(0xdeadbeef)); - world = false; - } - - // address(0) should be automatically excluded - function change0() public { - require(msg.sender == address(0)); - world = false; - } -} - -contract ExcludeSenders is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function excludeSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - // Tests clashing. Exclusion takes priority. - function targetSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol deleted file mode 100644 index 759810611e95b..0000000000000 --- a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -interface Vm { - function etch(address target, bytes calldata newRuntimeBytecode) external; -} - -// https://github.com/foundry-rs/foundry/issues/5625 -// https://github.com/foundry-rs/foundry/issues/6166 -// `Target.wrongSelector` is not called when handler added as `targetContract` -// `Target.wrongSelector` is called (and test fails) when no `targetContract` set -contract Target { - uint256 count; - - function wrongSelector() external { - revert("wrong target selector called"); - } - - function goodSelector() external { - count++; - } -} - -contract Handler is DSTest { - function increment() public { - Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); - } -} - -contract ExplicitTargetContract is DSTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - Handler handler; - - function setUp() public { - Target target = new Target(); - bytes memory targetCode = address(target).code; - vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); - - handler = new Handler(); - } - - function targetContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(handler); - return addrs; - } - - function invariant_explicit_target() public {} -} - -contract DynamicTargetContract is DSTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - Handler handler; - - function setUp() public { - Target target = new Target(); - bytes memory targetCode = address(target).code; - vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); - - handler = new Handler(); - } - - function invariant_dynamic_targets() public {} -} diff --git a/testdata/default/fuzz/invariant/target/TargetContracts.t.sol b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol deleted file mode 100644 index d24c7eb5282a4..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetContracts.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract TargetContracts is DSTest { - Hello hello1; - Hello hello2; - - function setUp() public { - hello1 = new Hello(); - hello2 = new Hello(); - } - - function targetContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(hello1); - return addrs; - } - - function invariantTrueWorld() public { - require(hello2.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol deleted file mode 100644 index 30b4a05e3eaf9..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzInterface { - address target; - string[] artifacts; -} - -contract Hello { - bool public world; - - function changeWorld() external { - world = true; - } -} - -interface IHello { - function world() external view returns (bool); - function changeWorld() external; -} - -contract HelloProxy { - address internal immutable _implementation; - - constructor(address implementation_) { - _implementation = implementation_; - } - - function _delegate(address implementation) internal { - assembly { - calldatacopy(0, 0, calldatasize()) - - let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } - - fallback() external payable { - _delegate(_implementation); - } -} - -contract TargetWorldInterfaces is DSTest { - IHello proxy; - - function setUp() public { - Hello hello = new Hello(); - proxy = IHello(address(new HelloProxy(address(hello)))); - } - - function targetInterfaces() public returns (FuzzInterface[] memory) { - FuzzInterface[] memory targets = new FuzzInterface[](1); - - string[] memory artifacts = new string[](1); - artifacts[0] = "IHello"; - - targets[0] = FuzzInterface(address(proxy), artifacts); - - return targets; - } - - function invariantTrueWorld() public { - require(proxy.world() == false, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol deleted file mode 100644 index c74ac7fa18114..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Hello { - bool public world = true; - - function change() public { - world = true; - } - - function real_change() public { - world = false; - } -} - -contract TargetSelectors is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hello.change.selector; - targets[0] = FuzzSelector(address(hello), selectors); - return targets; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetSenders.t.sol b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol deleted file mode 100644 index 6fa4c9a6387d5..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetSenders.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - require(msg.sender == address(0xdeadbeef)); - world = false; - } -} - -contract TargetSenders is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function targetSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol deleted file mode 100644 index 86ca6d5439b7a..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// Will get automatically excluded. Otherwise it would throw error. -contract NoMutFunctions { - function no_change() public pure {} -} - -contract Excluded { - bool public world = true; - - function change() public { - world = false; - } -} - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract ExcludeArtifacts is DSTest { - Excluded excluded; - - function setUp() public { - excluded = new Excluded(); - new Hello(); - new NoMutFunctions(); - } - - function excludeArtifacts() public returns (string[] memory) { - string[] memory abis = new string[](1); - abis[0] = "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; - return abis; - } - - function invariantShouldPass() public { - require(excluded.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol deleted file mode 100644 index a8d9c2799486d..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzArtifactSelector { - string artifact; - bytes4[] selectors; -} - -contract Hi { - bool public world = true; - - function no_change() public { - world = true; - } - - function change() public { - world = false; - } -} - -contract TargetArtifactSelectors is DSTest { - Hi hello; - - function setUp() public { - hello = new Hi(); - } - - function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { - FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hi.no_change.selector; - targets[0] = - FuzzArtifactSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); - return targets; - } - - function invariantShouldPass() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol deleted file mode 100644 index 162d9cc2e1106..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzArtifactSelector { - string artifact; - bytes4[] selectors; -} - -contract Parent { - bool public should_be_true = true; - address public child; - - function change() public { - child = msg.sender; - should_be_true = false; - } - - function create() public { - new Child(); - } -} - -contract Child { - Parent parent; - bool public changed = false; - - constructor() { - parent = Parent(msg.sender); - } - - function change_parent() public { - parent.change(); - } - - function tracked_change_parent() public { - parent.change(); - } -} - -contract TargetArtifactSelectors2 is DSTest { - Parent parent; - - function setUp() public { - parent = new Parent(); - } - - function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { - FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); - bytes4[] memory selectors_child = new bytes4[](1); - - selectors_child[0] = Child.change_parent.selector; - targets[0] = FuzzArtifactSelector( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child - ); - - bytes4[] memory selectors_parent = new bytes4[](1); - selectors_parent[0] = Parent.create.selector; - targets[1] = FuzzArtifactSelector( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent - ); - return targets; - } - - function invariantShouldFail() public { - if (!parent.should_be_true()) { - require(!Child(address(parent.child())).changed(), "should have not happened"); - } - require(parent.should_be_true() == true, "it's false"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol deleted file mode 100644 index 28fa146059f92..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Targeted { - bool public world = true; - - function change() public { - world = false; - } -} - -contract Hello { - bool public world = true; - - function no_change() public {} -} - -contract TargetArtifacts is DSTest { - Targeted target1; - Targeted target2; - Hello hello; - - function setUp() public { - target1 = new Targeted(); - target2 = new Targeted(); - hello = new Hello(); - } - - function targetArtifacts() public returns (string[] memory) { - string[] memory abis = new string[](1); - abis[0] = "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; - return abis; - } - - function invariantShouldPass() public { - require(target2.world() == true || target1.world() == true || hello.world() == true, "false world"); - } - - function invariantShouldFail() public { - require(target2.world() == true || target1.world() == true, "false world"); - } -} diff --git a/testdata/default/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol index 73d2de2fc4ed1..24c059e935be7 100644 --- a/testdata/default/inline/FuzzInlineConf.t.sol +++ b/testdata/default/inline/FuzzInlineConf.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract FuzzInlineConf is DSTest { +contract FuzzInlineConf is Test { /** * forge-config: default.fuzz.runs = 1024 * forge-config: default.fuzz.max-test-rejects = 500 @@ -14,7 +14,7 @@ contract FuzzInlineConf is DSTest { } /// forge-config: default.fuzz.runs = 10 -contract FuzzInlineConf2 is DSTest { +contract FuzzInlineConf2 is Test { /// forge-config: default.fuzz.runs = 1 function testInlineConfFuzz1(uint8 x) public { require(true, "this is not going to revert"); diff --git a/testdata/default/inline/InvariantInlineConf.t.sol b/testdata/default/inline/InvariantInlineConf.t.sol index 5ac81755e998d..6699008a343ac 100644 --- a/testdata/default/inline/InvariantInlineConf.t.sol +++ b/testdata/default/inline/InvariantInlineConf.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract InvariantBreaker { bool public flag0 = true; @@ -22,7 +22,7 @@ contract InvariantBreaker { } } -contract InvariantInlineConf is DSTest { +contract InvariantInlineConf is Test { InvariantBreaker inv; function setUp() public { @@ -38,7 +38,7 @@ contract InvariantInlineConf is DSTest { } } -contract InvariantInlineConf2 is DSTest { +contract InvariantInlineConf2 is Test { InvariantBreaker inv; function setUp() public { diff --git a/testdata/default/linking/duplicate/Duplicate.t.sol b/testdata/default/linking/duplicate/Duplicate.t.sol index d1d0f32789238..2ad1d9f7fbe5b 100644 --- a/testdata/default/linking/duplicate/Duplicate.t.sol +++ b/testdata/default/linking/duplicate/Duplicate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract has many dependencies, some of which appear to the linker // more than once. @@ -93,7 +93,7 @@ contract LibraryConsumer { } } -contract DuplicateLibraryLinkingTest is DSTest { +contract DuplicateLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/linking/nested/Nested.t.sol b/testdata/default/linking/nested/Nested.t.sol index 136cb36479cbf..709bf0143857d 100644 --- a/testdata/default/linking/nested/Nested.t.sol +++ b/testdata/default/linking/nested/Nested.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract with a library that depends on a library @@ -27,7 +27,7 @@ contract LibraryConsumer { } } -contract NestedLibraryLinkingTest is DSTest { +contract NestedLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/linking/simple/Simple.t.sol b/testdata/default/linking/simple/Simple.t.sol index 85be791fd2489..ded3efb1694c8 100644 --- a/testdata/default/linking/simple/Simple.t.sol +++ b/testdata/default/linking/simple/Simple.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract with one library @@ -17,7 +17,7 @@ contract LibraryConsumer { } } -contract SimpleLibraryLinkingTest is DSTest { +contract SimpleLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/logs/DebugLogs.t.sol b/testdata/default/logs/DebugLogs.t.sol deleted file mode 100644 index b560fd2bfb9ca..0000000000000 --- a/testdata/default/logs/DebugLogs.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DebugLogsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - constructor() { - emit log_uint(0); - } - - function setUp() public { - emit log_uint(1); - } - - function test1() public { - emit log_uint(2); - } - - function test2() public { - emit log_uint(3); - } - - function testRevertIfWithRevert() public { - Fails fails = new Fails(); - emit log_uint(4); - vm.expectRevert(); - fails.failure(); - } - - /// forge-config: default.allow_internal_expect_revert = true - function testRevertIfWithRequire() public { - emit log_uint(5); - vm.expectRevert(); - require(false); - } - - function testLog() public { - emit log("Error: Assertion Failed"); - } - - function testLogs() public { - emit logs(bytes("abcd")); - } - - function testLogAddress() public { - emit log_address(address(1)); - } - - function testLogBytes32() public { - emit log_bytes32(bytes32("abcd")); - } - - function testLogInt() public { - emit log_int(int256(-31337)); - } - - function testLogBytes() public { - emit log_bytes(bytes("abcd")); - } - - function testLogString() public { - emit log_string("here"); - } - - function testLogNamedAddress() public { - emit log_named_address("address", address(1)); - } - - function testLogNamedBytes32() public { - emit log_named_bytes32("abcd", bytes32("abcd")); - } - - function testLogNamedDecimalInt() public { - emit log_named_decimal_int("amount", int256(-31337), uint256(18)); - } - - function testLogNamedDecimalUint() public { - emit log_named_decimal_uint("amount", uint256(1 ether), uint256(18)); - } - - function testLogNamedInt() public { - emit log_named_int("amount", int256(-31337)); - } - - function testLogNamedUint() public { - emit log_named_uint("amount", uint256(1 ether)); - } - - function testLogNamedBytes() public { - emit log_named_bytes("abcd", bytes("abcd")); - } - - function testLogNamedString() public { - emit log_named_string("key", "val"); - } -} - -contract Fails is DSTest { - function failure() public { - emit log_uint(100); - revert(); - } -} diff --git a/testdata/default/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol deleted file mode 100644 index a6226bbd665fe..0000000000000 --- a/testdata/default/logs/HardhatLogs.t.sol +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "./console.sol"; - -contract HardhatLogsTest { - constructor() { - console.log("constructor"); - } - - string testStr; - int256 testInt; - uint256 testUint; - bool testBool; - address testAddr; - bytes testBytes; - - function setUp() public { - testStr = "test"; - testInt = -31337; - testUint = 1; - testBool = false; - testAddr = 0x0000000000000000000000000000000000000001; - testBytes = "a"; - } - - function testInts() public view { - console.log(uint256(0)); - console.log(uint256(1)); - console.log(uint256(2)); - console.log(uint256(3)); - } - - function testStrings() public view { - console.log("testStrings"); - } - - function testMisc() public view { - console.log("testMisc", address(1)); - console.log("testMisc", uint256(42)); - } - - function testConsoleLog() public view { - console.log(testStr); - } - - function testLogInt() public view { - console.logInt(testInt); - } - - function testLogUint() public view { - console.logUint(testUint); - } - - function testLogString() public view { - console.logString(testStr); - } - - function testLogBool() public view { - console.logBool(testBool); - } - - function testLogAddress() public view { - console.logAddress(testAddr); - } - - function testLogBytes() public view { - console.logBytes(testBytes); - } - - function testLogBytes1() public view { - console.logBytes1(bytes1(testBytes)); - } - - function testLogBytes2() public view { - console.logBytes2(bytes2(testBytes)); - } - - function testLogBytes3() public view { - console.logBytes3(bytes3(testBytes)); - } - - function testLogBytes4() public view { - console.logBytes4(bytes4(testBytes)); - } - - function testLogBytes5() public view { - console.logBytes5(bytes5(testBytes)); - } - - function testLogBytes6() public view { - console.logBytes6(bytes6(testBytes)); - } - - function testLogBytes7() public view { - console.logBytes7(bytes7(testBytes)); - } - - function testLogBytes8() public view { - console.logBytes8(bytes8(testBytes)); - } - - function testLogBytes9() public view { - console.logBytes9(bytes9(testBytes)); - } - - function testLogBytes10() public view { - console.logBytes10(bytes10(testBytes)); - } - - function testLogBytes11() public view { - console.logBytes11(bytes11(testBytes)); - } - - function testLogBytes12() public view { - console.logBytes12(bytes12(testBytes)); - } - - function testLogBytes13() public view { - console.logBytes13(bytes13(testBytes)); - } - - function testLogBytes14() public view { - console.logBytes14(bytes14(testBytes)); - } - - function testLogBytes15() public view { - console.logBytes15(bytes15(testBytes)); - } - - function testLogBytes16() public view { - console.logBytes16(bytes16(testBytes)); - } - - function testLogBytes17() public view { - console.logBytes17(bytes17(testBytes)); - } - - function testLogBytes18() public view { - console.logBytes18(bytes18(testBytes)); - } - - function testLogBytes19() public view { - console.logBytes19(bytes19(testBytes)); - } - - function testLogBytes20() public view { - console.logBytes20(bytes20(testBytes)); - } - - function testLogBytes21() public view { - console.logBytes21(bytes21(testBytes)); - } - - function testLogBytes22() public view { - console.logBytes22(bytes22(testBytes)); - } - - function testLogBytes23() public view { - console.logBytes23(bytes23(testBytes)); - } - - function testLogBytes24() public view { - console.logBytes24(bytes24(testBytes)); - } - - function testLogBytes25() public view { - console.logBytes25(bytes25(testBytes)); - } - - function testLogBytes26() public view { - console.logBytes26(bytes26(testBytes)); - } - - function testLogBytes27() public view { - console.logBytes27(bytes27(testBytes)); - } - - function testLogBytes28() public view { - console.logBytes28(bytes28(testBytes)); - } - - function testLogBytes29() public view { - console.logBytes29(bytes29(testBytes)); - } - - function testLogBytes30() public view { - console.logBytes30(bytes30(testBytes)); - } - - function testLogBytes31() public view { - console.logBytes31(bytes31(testBytes)); - } - - function testLogBytes32() public view { - console.logBytes32(bytes32(testBytes)); - } - - function testConsoleLogUint() public view { - console.log(testUint); - } - - function testConsoleLogString() public view { - console.log(testStr); - } - - function testConsoleLogBool() public view { - console.log(testBool); - } - - function testConsoleLogAddress() public view { - console.log(testAddr); - } - - function testConsoleLogFormatString() public view { - console.log("formatted log str=%s", testStr); - } - - function testConsoleLogFormatUint() public view { - console.log("formatted log uint=%s", testUint); - } - - function testConsoleLogFormatAddress() public view { - console.log("formatted log addr=%s", testAddr); - } - - function testConsoleLogFormatMulti() public view { - console.log("formatted log str=%s uint=%d", testStr, testUint); - } - - function testConsoleLogFormatEscape() public view { - console.log("formatted log %% %s", testStr); - } - - function testConsoleLogFormatSpill() public view { - console.log("formatted log %s", testStr, testUint); - } -} diff --git a/testdata/default/repros/Issue10302.t.sol b/testdata/default/repros/Issue10302.t.sol index c47332cb2872d..c5516804ccd1c 100644 --- a/testdata/default/repros/Issue10302.t.sol +++ b/testdata/default/repros/Issue10302.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract A { function foo() public pure returns (bool) { @@ -10,9 +9,7 @@ contract A { } } -contract Issue10302Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10302Test is Test { function testDelegateFails() external { vm.createSelectFork("sepolia"); A a = new A(); diff --git a/testdata/default/repros/Issue10477.t.sol b/testdata/default/repros/Issue10477.t.sol index 86d36fc741ef4..29bfcb37ced29 100644 --- a/testdata/default/repros/Issue10477.t.sol +++ b/testdata/default/repros/Issue10477.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleDelegate { function call(address target, bytes memory data) external returns (bool callResult, bytes memory callData) { @@ -18,9 +17,7 @@ contract Counter { } } -contract Issue10477Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10477Test is Test { address payable ALICE_ADDRESS = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); uint256 constant ALICE_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; diff --git a/testdata/default/repros/Issue10527.t.sol b/testdata/default/repros/Issue10527.t.sol index fdf62b22db35c..ba92317672f3c 100644 --- a/testdata/default/repros/Issue10527.t.sol +++ b/testdata/default/repros/Issue10527.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract A { event Event1(); @@ -17,12 +16,10 @@ contract A { } } -contract Issue10527Test is DSTest { +contract Issue10527Test is Test { event Event1(); event Event2(); - Vm constant vm = Vm(HEVM_ADDRESS); - A a; function setUp() public { diff --git a/testdata/default/repros/Issue10552.t.sol b/testdata/default/repros/Issue10552.t.sol index e77168f150f4d..d0f6a7713a9b7 100644 --- a/testdata/default/repros/Issue10552.t.sol +++ b/testdata/default/repros/Issue10552.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -21,9 +20,7 @@ contract Counter { } } -contract Issue10552Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10552Test is Test { Counter public counter; uint256 mainnetId; uint256 opId; diff --git a/testdata/default/repros/Issue10586.t.sol b/testdata/default/repros/Issue10586.t.sol index 3d7eefbf3ac03..24c355abef7ee 100644 --- a/testdata/default/repros/Issue10586.t.sol +++ b/testdata/default/repros/Issue10586.t.sol @@ -1,20 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Target is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract Target is Test { function setChainId() public { vm.chainId(123); } } -contract Issue10586Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10586Test is Test { Target public target; function setUp() public { diff --git a/testdata/default/repros/Issue10957.t.sol b/testdata/default/repros/Issue10957.t.sol index b41e116da44fa..a8efd20f5cda6 100644 --- a/testdata/default/repros/Issue10957.t.sol +++ b/testdata/default/repros/Issue10957.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/10957 -contract Issue10957Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10957Test is Test { function testCreateSelectForkBlockNumber() public { // Transaction hash from mainnet bytes32 txHash = 0x2e175897d19307c664815129720c8ac3581da6cb92e4cce923996dd59fbb6ffc; diff --git a/testdata/default/repros/Issue11353.t.sol b/testdata/default/repros/Issue11353.t.sol index e5e863a762903..cb89de17a0ada 100644 --- a/testdata/default/repros/Issue11353.t.sol +++ b/testdata/default/repros/Issue11353.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.24; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Blobhash { function getIndices(uint256[] calldata blobIndices) public view returns (bytes32[] memory) { @@ -18,9 +17,7 @@ contract Blobhash { } // https://github.com/foundry-rs/foundry/issues/11353 -contract Issue11353Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue11353Test is Test { Blobhash public blobhashContract; function setUp() public { diff --git a/testdata/default/repros/Issue11616.t.sol b/testdata/default/repros/Issue11616.t.sol index 9d82dbbdc4ec3..cf2cbb1900faa 100644 --- a/testdata/default/repros/Issue11616.t.sol +++ b/testdata/default/repros/Issue11616.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.24; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Emit { event A(); @@ -13,8 +12,7 @@ contract Emit { } } -contract Issue11616Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue11616Test is Test { Emit public e; function setUp() public { diff --git a/testdata/default/repros/Issue2623.t.sol b/testdata/default/repros/Issue2623.t.sol index 31252cae36c3b..486db1963b190 100644 --- a/testdata/default/repros/Issue2623.t.sol +++ b/testdata/default/repros/Issue2623.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2623 -contract Issue2623Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2623Test is Test { function testRollFork() public { uint256 fork = vm.createFork("mainnet", 10); vm.selectFork(fork); diff --git a/testdata/default/repros/Issue2629.t.sol b/testdata/default/repros/Issue2629.t.sol index d46868903a60e..b7e1d9ebdb208 100644 --- a/testdata/default/repros/Issue2629.t.sol +++ b/testdata/default/repros/Issue2629.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2629 -contract Issue2629Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2629Test is Test { function testSelectFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; diff --git a/testdata/default/repros/Issue2723.t.sol b/testdata/default/repros/Issue2723.t.sol index b7678df450cb8..58e9ba43f64d8 100644 --- a/testdata/default/repros/Issue2723.t.sol +++ b/testdata/default/repros/Issue2723.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2723 -contract Issue2723Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2723Test is Test { function testRollFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; diff --git a/testdata/default/repros/Issue2851.t.sol b/testdata/default/repros/Issue2851.t.sol deleted file mode 100644 index 7742af4b708e0..0000000000000 --- a/testdata/default/repros/Issue2851.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.1; - -import "ds-test/test.sol"; - -contract Backdoor { - uint256 public number = 1; - - function backdoor(uint256 newNumber) public payable { - uint256 x = newNumber - 1; - if (x == 6912213124124531) { - number = 0; - } - } -} - -// https://github.com/foundry-rs/foundry/issues/2851 -contract Issue2851Test is DSTest { - Backdoor back; - - function setUp() public { - back = new Backdoor(); - } - - /// forge-config: default.fuzz.seed = '111' - function invariantNotZero() public { - assertEq(back.number(), 1); - } -} diff --git a/testdata/default/repros/Issue2898.t.sol b/testdata/default/repros/Issue2898.t.sol index a16adf5a350ff..564ef4fa2ef78 100644 --- a/testdata/default/repros/Issue2898.t.sol +++ b/testdata/default/repros/Issue2898.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2898 -contract Issue2898Test is DSTest { +contract Issue2898Test is Test { address private constant BRIDGE = address(10); address private constant BENEFICIARY = address(11); - Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { vm.deal(BRIDGE, 100); diff --git a/testdata/default/repros/Issue2956.t.sol b/testdata/default/repros/Issue2956.t.sol index c57b46cc1f4bc..3295df41dbf36 100644 --- a/testdata/default/repros/Issue2956.t.sol +++ b/testdata/default/repros/Issue2956.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2956 -contract Issue2956Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue2956Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol index fbcd1ab8c3c4a..52a3c52d06a84 100644 --- a/testdata/default/repros/Issue2984.t.sol +++ b/testdata/default/repros/Issue2984.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2984 -contract Issue2984Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue2984Test is Test { uint256 fork; uint256 snapshot; diff --git a/testdata/default/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol deleted file mode 100644 index 90ac8c3b08afd..0000000000000 --- a/testdata/default/repros/Issue3055.t.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3055 -contract Issue3055Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function test_snapshot() external { - uint256 snapshotId = vm.snapshotState(); - assertEq(uint256(0), uint256(1)); - vm.revertToState(snapshotId); - } - - function test_snapshot2() public { - uint256 snapshotId = vm.snapshotState(); - assertTrue(false); - vm.revertToState(snapshotId); - assertTrue(true); - } - - function test_snapshot3(uint256) public { - vm.expectRevert(); - // Call exposed_snapshot3() using this to perform an external call, - // so we can properly test for reverts. - this.exposed_snapshot3(); - } - - function exposed_snapshot3() public { - uint256 snapshotId = vm.snapshotState(); - assertTrue(false); - vm.revertToState(snapshotId); - } -} diff --git a/testdata/default/repros/Issue3077.t.sol b/testdata/default/repros/Issue3077.t.sol index 3b5e4257a3ad4..ec68bc3f4bf63 100644 --- a/testdata/default/repros/Issue3077.t.sol +++ b/testdata/default/repros/Issue3077.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3077 -abstract contract ZeroState is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +abstract contract ZeroState is Test { // deployer and users address public deployer = vm.addr(1); Token aaveToken; diff --git a/testdata/default/repros/Issue3110.t.sol b/testdata/default/repros/Issue3110.t.sol index 9f1da8d032ec3..4c4f3d257d5a5 100644 --- a/testdata/default/repros/Issue3110.t.sol +++ b/testdata/default/repros/Issue3110.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3110 -abstract contract ZeroState is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +abstract contract ZeroState is Test { // deployer and users address public deployer = vm.addr(1); Token aaveToken; diff --git a/testdata/default/repros/Issue3119.t.sol b/testdata/default/repros/Issue3119.t.sol index 6c0ceb429d6d6..99de3f7aab7fd 100644 --- a/testdata/default/repros/Issue3119.t.sol +++ b/testdata/default/repros/Issue3119.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3119 -contract Issue3119Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3119Test is Test { address public owner = vm.addr(1); address public alice = vm.addr(2); diff --git a/testdata/default/repros/Issue3189.t.sol b/testdata/default/repros/Issue3189.t.sol deleted file mode 100644 index 0bcf5ddce8d76..0000000000000 --- a/testdata/default/repros/Issue3189.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3189 -contract MyContract { - function foo(uint256 arg) public returns (uint256) { - return arg + 2; - } -} - -contract MyContractUser is DSTest { - MyContract immutable myContract; - - constructor() { - myContract = new MyContract(); - } - - function foo(uint256 arg) public returns (uint256 ret) { - ret = myContract.foo(arg); - assertEq(ret, arg + 1, "Invariant failed"); - } -} - -contract Issue3189Test is DSTest { - function testFoo() public { - MyContractUser user = new MyContractUser(); - uint256 fooRet = user.foo(123); - } -} diff --git a/testdata/default/repros/Issue3190.t.sol b/testdata/default/repros/Issue3190.t.sol index ede3e50e2e3ec..30a6bb623b8a1 100644 --- a/testdata/default/repros/Issue3190.t.sol +++ b/testdata/default/repros/Issue3190.t.sol @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3190 -contract Issue3190Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3190Test is Test { function setUp() public { vm.chainId(99); assertEq(99, block.chainid); diff --git a/testdata/default/repros/Issue3192.t.sol b/testdata/default/repros/Issue3192.t.sol index 9c5be8d89f3d6..6fa3e8dfd442c 100644 --- a/testdata/default/repros/Issue3192.t.sol +++ b/testdata/default/repros/Issue3192.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3192 -contract Issue3192Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3192Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3220.t.sol b/testdata/default/repros/Issue3220.t.sol index 5235e44c79c60..366c079619d84 100644 --- a/testdata/default/repros/Issue3220.t.sol +++ b/testdata/default/repros/Issue3220.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3220 -contract Issue3220Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3220Test is Test { IssueRepro repro; uint256 fork1; diff --git a/testdata/default/repros/Issue3221.t.sol b/testdata/default/repros/Issue3221.t.sol index 81398c41fc290..9526987ddd87b 100644 --- a/testdata/default/repros/Issue3221.t.sol +++ b/testdata/default/repros/Issue3221.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3221 -contract Issue3221Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3221Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3223.t.sol b/testdata/default/repros/Issue3223.t.sol index 6c21b7b3d60b1..ede3042ba732f 100644 --- a/testdata/default/repros/Issue3223.t.sol +++ b/testdata/default/repros/Issue3223.t.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3223 -contract Issue3223Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +/// forge-config: default.sender = "0xF0959944122fb1ed4CfaBA645eA06EED30427BAA" +contract Issue3223Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3347.t.sol b/testdata/default/repros/Issue3347.t.sol deleted file mode 100644 index e48c1305db426..0000000000000 --- a/testdata/default/repros/Issue3347.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3347 -contract Issue3347Test is DSTest { - event log2(uint256, uint256); - - function test() public { - emit log2(1, 2); - } -} diff --git a/testdata/default/repros/Issue3596.t.sol b/testdata/default/repros/Issue3596.t.sol deleted file mode 100644 index b0c6785874375..0000000000000 --- a/testdata/default/repros/Issue3596.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3596 -contract Issue3596Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testDealTransfer() public { - address addr = vm.addr(1337); - vm.startPrank(addr); - vm.deal(addr, 20000001 ether); - payable(address(this)).transfer(20000000 ether); - - Nested nested = new Nested(); - nested.doStuff(); - vm.stopPrank(); - } -} - -contract Nested { - function doStuff() public { - doRevert(); - } - - function doRevert() public { - revert("This fails"); - } -} diff --git a/testdata/default/repros/Issue3653.t.sol b/testdata/default/repros/Issue3653.t.sol index 26eb38e4a29f1..0657e9908d306 100644 --- a/testdata/default/repros/Issue3653.t.sol +++ b/testdata/default/repros/Issue3653.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3653 -contract Issue3653Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3653Test is Test { uint256 fork; Token token; diff --git a/testdata/default/repros/Issue3661.t.sol b/testdata/default/repros/Issue3661.t.sol index 76b55a222ca00..8491857074e07 100644 --- a/testdata/default/repros/Issue3661.t.sol +++ b/testdata/default/repros/Issue3661.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3661 -contract Issue3661Test is DSTest { +contract Issue3661Test is Test { address sender; function setUp() public { diff --git a/testdata/default/repros/Issue3674.t.sol b/testdata/default/repros/Issue3674.t.sol index de5a960059f52..39d8d9c24f582 100644 --- a/testdata/default/repros/Issue3674.t.sol +++ b/testdata/default/repros/Issue3674.t.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3674 -contract Issue3674Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.sender = "0xF0959944122fb1ed4CfaBA645eA06EED30427BAA" +contract Issue3674Test is Test { function testNonceCreateSelect() public { vm.createSelectFork("sepolia"); vm.createSelectFork("avaxTestnet"); - assert(vm.getNonce(msg.sender) > 0x17); + assertTrue(vm.getNonce(msg.sender) > 0x17); } } diff --git a/testdata/default/repros/Issue3685.t.sol b/testdata/default/repros/Issue3685.t.sol index f1da5bf6997bd..e7ff00264e52c 100644 --- a/testdata/default/repros/Issue3685.t.sol +++ b/testdata/default/repros/Issue3685.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3685 -contract Issue3685Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3685Test is Test { Actor a; Actor b; diff --git a/testdata/default/repros/Issue3703.t.sol b/testdata/default/repros/Issue3703.t.sol index 48651be24c669..3ac50765f1a93 100644 --- a/testdata/default/repros/Issue3703.t.sol +++ b/testdata/default/repros/Issue3703.t.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3703 -contract Issue3703Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3703Test is Test { function setUp() public { + vm.skip(true, "flaky polygon RPCs"); + uint256 fork = vm.createSelectFork("polygon", bytes32(0xbed0c8c1b9ff8bf0452979d170c52893bb8954f18a904aa5bcbd0f709be050b9)); } diff --git a/testdata/default/repros/Issue3708.t.sol b/testdata/default/repros/Issue3708.t.sol index 53a7c461f873f..c78304a1cbf02 100644 --- a/testdata/default/repros/Issue3708.t.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -1,14 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3708 -contract Issue3708Test is DSTest { +contract Issue3708Test is Test { // https://optimistic.etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { uint256 forkId = vm.createSelectFork("optimism"); diff --git a/testdata/default/repros/Issue3753.t.sol b/testdata/default/repros/Issue3753.t.sol index 2c927c823dbb6..1fe6040f2a44a 100644 --- a/testdata/default/repros/Issue3753.t.sol +++ b/testdata/default/repros/Issue3753.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3753 -contract Issue3753Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3753Test is Test { function test_repro() public { bool res; assembly { diff --git a/testdata/default/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol index 37f62bc61fabe..39d3f77bc28a4 100644 --- a/testdata/default/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/DSTest.sol"; +import "utils/Vm.sol"; contract Config { address public test = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; diff --git a/testdata/default/repros/Issue4232.t.sol b/testdata/default/repros/Issue4232.t.sol index 0ac6a77c77076..44f038bfbf28e 100644 --- a/testdata/default/repros/Issue4232.t.sol +++ b/testdata/default/repros/Issue4232.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4232 -contract Issue4232Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4232Test is Test { function testFork() public { // Smoke test, worked previously as well vm.createSelectFork("sepolia", 7215400); diff --git a/testdata/default/repros/Issue4402.t.sol b/testdata/default/repros/Issue4402.t.sol index 830b2926ef269..19072638e74da 100644 --- a/testdata/default/repros/Issue4402.t.sol +++ b/testdata/default/repros/Issue4402.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4402 -contract Issue4402Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4402Test is Test { function testReadNonEmptyArray() public { string memory path = "fixtures/Json/Issue4402.json"; string memory json = vm.readFile(path); diff --git a/testdata/default/repros/Issue4586.t.sol b/testdata/default/repros/Issue4586.t.sol index c904af1e46abb..57f1dd3052d04 100644 --- a/testdata/default/repros/Issue4586.t.sol +++ b/testdata/default/repros/Issue4586.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4586 -contract Issue4586Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4586Test is Test { uint256 constant initialBlock = 16730733; InvariantHandler handler; @@ -31,8 +28,7 @@ contract Issue4586Test is DSTest { } contract InvariantHandler { - address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); - Vm constant vm = Vm(HEVM_ADDRESS); + Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); uint256 public calledRollFork; diff --git a/testdata/default/repros/Issue4630.t.sol b/testdata/default/repros/Issue4630.t.sol index 01eb626505cd1..cfc0ebde43089 100644 --- a/testdata/default/repros/Issue4630.t.sol +++ b/testdata/default/repros/Issue4630.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4630 -contract Issue4630Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4630Test is Test { function testExistingValue() public { string memory path = "fixtures/Json/Issue4630.json"; string memory json = vm.readFile(path); diff --git a/testdata/default/repros/Issue4640.t.sol b/testdata/default/repros/Issue4640.t.sol index 1e7d887a9b57d..3a20eaa0f8d8c 100644 --- a/testdata/default/repros/Issue4640.t.sol +++ b/testdata/default/repros/Issue4640.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4640 -contract Issue4640Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4640Test is Test { function testArbitrumBlockNumber() public { // vm.createSelectFork("arbitrum", 75219831); diff --git a/testdata/default/repros/Issue5038.t.sol b/testdata/default/repros/Issue5038.t.sol index 51a90bca10d4c..2139639cc56ac 100644 --- a/testdata/default/repros/Issue5038.t.sol +++ b/testdata/default/repros/Issue5038.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct Value { uint256 value; } // https://github.com/foundry-rs/foundry/issues/5038 -contract Issue5038Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5038Test is Test { function testParseMaxUint64() public { string memory json = '{"value": 18446744073709551615}'; bytes memory parsed = vm.parseJson(json); diff --git a/testdata/default/repros/Issue5529.t.sol b/testdata/default/repros/Issue5529.t.sol index 14ec7cfdbce60..2f7afbbc2d524 100644 --- a/testdata/default/repros/Issue5529.t.sol +++ b/testdata/default/repros/Issue5529.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -16,20 +15,21 @@ contract Counter { } } -contract Issue5529Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.always_use_create_2_factory = true +contract Issue5529Test is Test { Counter public counter; address public constant default_create2_factory = 0x4e59b44847b379578588920cA78FbF26c0B4956C; function testCreate2FactoryUsedInTests() public { - address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); - address b = address(new Counter{salt: 0}()); - require(a == b, "create2 address mismatch"); + run(); } function testCreate2FactoryUsedWhenPranking() public { vm.startPrank(address(1234)); + run(); + } + + function run() private { address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); address b = address(new Counter{salt: 0}()); require(a == b, "create2 address mismatch"); diff --git a/testdata/default/repros/Issue5739.t.sol b/testdata/default/repros/Issue5739.t.sol index 6f3494b7e6e7d..bbde1b73e7c20 100644 --- a/testdata/default/repros/Issue5739.t.sol +++ b/testdata/default/repros/Issue5739.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IERC20 { function totalSupply() external view returns (uint256 supply); } // https://github.com/foundry-rs/foundry/issues/5739 -contract Issue5739Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue5739Test is Test { IERC20 dai; function setUp() public { diff --git a/testdata/default/repros/Issue5808.t.sol b/testdata/default/repros/Issue5808.t.sol index 40efe65a9ce69..fc76fe1e4a3cf 100644 --- a/testdata/default/repros/Issue5808.t.sol +++ b/testdata/default/repros/Issue5808.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5808 -contract Issue5808Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5808Test is Test { function testReadInt() public { string memory str1 = '["ffffffff","00000010"]'; vm._expectCheatcodeRevert(); diff --git a/testdata/default/repros/Issue5929.t.sol b/testdata/default/repros/Issue5929.t.sol index ced9d6d9b4a39..6dac42653e3ed 100644 --- a/testdata/default/repros/Issue5929.t.sol +++ b/testdata/default/repros/Issue5929.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5929 -contract Issue5929Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5929Test is Test { function test_transact_not_working() public { vm.createSelectFork("mainnet", 21134547); // https://etherscan.io/tx/0x96a129768ec66fd7d65114bf182f4e173bf0b73a44219adaf71f01381a3d0143 diff --git a/testdata/default/repros/Issue5935.t.sol b/testdata/default/repros/Issue5935.t.sol index 8ef724412ce31..220681cab55fb 100644 --- a/testdata/default/repros/Issue5935.t.sol +++ b/testdata/default/repros/Issue5935.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleStorage { uint256 public value; @@ -12,9 +11,7 @@ contract SimpleStorage { } } -contract Issue5935Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5935Test is Test { function testFork() public { uint256 forkId1 = vm.createFork("mainnet", 18234083); uint256 forkId2 = vm.createFork("mainnet", 18234083); diff --git a/testdata/default/repros/Issue5948.t.sol b/testdata/default/repros/Issue5948.t.sol index ae6ee9d50d8ba..b7a5080721baf 100644 --- a/testdata/default/repros/Issue5948.t.sol +++ b/testdata/default/repros/Issue5948.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5948 -contract Issue5948Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5948Test is Test { /// forge-config: default.fuzz.runs = 2 function testSleepFuzzed(uint256 _milliseconds) public { // Limit sleep time to 2 seconds to decrease test time diff --git a/testdata/default/repros/Issue6006.t.sol b/testdata/default/repros/Issue6006.t.sol index 54f0d11376d79..30dcbd00a74a2 100644 --- a/testdata/default/repros/Issue6006.t.sol +++ b/testdata/default/repros/Issue6006.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6006 -contract Issue6066Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6066Test is Test { function test_parse_11e20_sci() public { string memory json = '{"value": 1.1e20}'; bytes memory parsed = vm.parseJson(json); diff --git a/testdata/default/repros/Issue6032.t.sol b/testdata/default/repros/Issue6032.t.sol index 2fa05222d5a54..149097975ad30 100644 --- a/testdata/default/repros/Issue6032.t.sol +++ b/testdata/default/repros/Issue6032.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6032 -contract Issue6032Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6032Test is Test { function testEtchFork() public { // Deploy initial contract Counter counter = new Counter(); diff --git a/testdata/default/repros/Issue6070.t.sol b/testdata/default/repros/Issue6070.t.sol index ebf3c7ab15c7b..5188a877e8d90 100644 --- a/testdata/default/repros/Issue6070.t.sol +++ b/testdata/default/repros/Issue6070.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6070 -contract Issue6066Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6066Test is Test { function testNonPrefixed() public { vm.setEnv("__FOUNDRY_ISSUE_6066", "abcd"); vm._expectCheatcodeRevert( diff --git a/testdata/default/repros/Issue6115.t.sol b/testdata/default/repros/Issue6115.t.sol index ae65a7dae8647..878b584a31afb 100644 --- a/testdata/default/repros/Issue6115.t.sol +++ b/testdata/default/repros/Issue6115.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -16,7 +16,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/6115 -contract Issue6115Test is DSTest { +contract Issue6115Test is Test { Counter public counter; function setUp() public { diff --git a/testdata/default/repros/Issue6170.t.sol b/testdata/default/repros/Issue6170.t.sol deleted file mode 100644 index 78511f4a2dc91..0000000000000 --- a/testdata/default/repros/Issue6170.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Emitter { - event Values(uint256 indexed a, uint256 indexed b); - - function plsEmit(uint256 a, uint256 b) external { - emit Values(a, b); - } -} - -// https://github.com/foundry-rs/foundry/issues/6170 -contract Issue6170Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - event Values(uint256 indexed a, uint256 b); - - Emitter e = new Emitter(); - - function test() public { - vm.expectEmit(true, true, false, true); - emit Values(69, 420); - e.plsEmit(69, 420); - } -} diff --git a/testdata/default/repros/Issue6180.t.sol b/testdata/default/repros/Issue6180.t.sol index 3d08ccbebac5d..5d6b82988e139 100644 --- a/testdata/default/repros/Issue6180.t.sol +++ b/testdata/default/repros/Issue6180.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6180 -contract Issue6180Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6180Test is Test { function test_timebug() external { uint256 start = block.timestamp; uint256 count = 4; diff --git a/testdata/default/repros/Issue6293.t.sol b/testdata/default/repros/Issue6293.t.sol index 6d57d13850950..78fe026c3dbf3 100644 --- a/testdata/default/repros/Issue6293.t.sol +++ b/testdata/default/repros/Issue6293.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6293 -contract Issue6293Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6293Test is Test { constructor() { require(address(this).balance > 0); payable(address(1)).call{value: 1}(""); diff --git a/testdata/default/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol deleted file mode 100644 index bbc3a4a98d412..0000000000000 --- a/testdata/default/repros/Issue6355.t.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/6355 -contract Issue6355Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - uint256 snapshotId; - Target targ; - - function setUp() public { - snapshotId = vm.snapshotState(); - targ = new Target(); - } - - // this non-deterministically fails sometimes and passes sometimes - function test_shouldPass() public { - assertEq(2, targ.num()); - } - - // always fails - function test_shouldFailWithRevertToState() public { - assertEq(3, targ.num()); - vm.revertToState(snapshotId); - } - - // always fails - function test_shouldFail() public { - assertEq(3, targ.num()); - } -} - -contract Target { - function num() public pure returns (uint256) { - return 2; - } -} diff --git a/testdata/default/repros/Issue6437.t.sol b/testdata/default/repros/Issue6437.t.sol index 4cf27be7b233e..e2fceaa660f2a 100644 --- a/testdata/default/repros/Issue6437.t.sol +++ b/testdata/default/repros/Issue6437.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6437 -contract Issue6437Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6437Test is Test { function test0() public { string memory json = "[]"; address[] memory arr = vm.parseJsonAddressArray(json, "$"); diff --git a/testdata/default/repros/Issue6501.t.sol b/testdata/default/repros/Issue6501.t.sol deleted file mode 100644 index 5d631cbe3e0a8..0000000000000 --- a/testdata/default/repros/Issue6501.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "../logs/console.sol"; - -// https://github.com/foundry-rs/foundry/issues/6501 -contract Issue6501Test is DSTest { - function test_hhLogs() public { - console.log("a"); - console.log(uint256(1)); - console.log("b", uint256(2)); - } -} diff --git a/testdata/default/repros/Issue6538.t.sol b/testdata/default/repros/Issue6538.t.sol index 34c4e2253a68b..c870c668e6816 100644 --- a/testdata/default/repros/Issue6538.t.sol +++ b/testdata/default/repros/Issue6538.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6538 -contract Issue6538Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6538Test is Test { function test_transact() public { bytes32 lastHash = 0x4b70ca8c5a0990b43df3064372d424d46efa41dfaab961754b86c5afb2df4f61; vm.createSelectFork("mainnet", lastHash); diff --git a/testdata/default/repros/Issue6554.t.sol b/testdata/default/repros/Issue6554.t.sol index 7a5fe7879c655..a6b086653a96a 100644 --- a/testdata/default/repros/Issue6554.t.sol +++ b/testdata/default/repros/Issue6554.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6554 -contract Issue6554Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6554Test is Test { function testPermissions() public { - vm.writeFile("./out/default/Issue6554.t.sol/cachedFile.txt", "cached data"); - string memory content = vm.readFile("./out/default/Issue6554.t.sol/cachedFile.txt"); + vm.writeFile("./out/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/Issue6554.t.sol/cachedFile.txt"); assertEq(content, "cached data"); } } diff --git a/testdata/default/repros/Issue6616.t.sol b/testdata/default/repros/Issue6616.t.sol index 262721d86d118..587c4703e3d6c 100644 --- a/testdata/default/repros/Issue6616.t.sol +++ b/testdata/default/repros/Issue6616.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6616 -contract Issue6616Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6616Test is Test { function testCreateForkRollLatestBlock() public { vm.createSelectFork("mainnet"); uint256 startBlock = block.number; diff --git a/testdata/default/repros/Issue6634.t.sol b/testdata/default/repros/Issue6634.t.sol index aa94922dd1ccb..5426b3f2404ba 100644 --- a/testdata/default/repros/Issue6634.t.sol +++ b/testdata/default/repros/Issue6634.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; contract Box { uint256 public number; @@ -14,9 +12,8 @@ contract Box { } // https://github.com/foundry-rs/foundry/issues/6634 -contract Issue6634Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.always_use_create_2_factory = true +contract Issue6634Test is Test { function test_Create2FactoryCallRecordedInStandardTest() public { address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; diff --git a/testdata/default/repros/Issue6643.t.sol b/testdata/default/repros/Issue6643.t.sol index 5c7e1c483a03a..0f8b45fa2864d 100644 --- a/testdata/default/repros/Issue6643.t.sol +++ b/testdata/default/repros/Issue6643.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { event TestEvent(uint256 n); @@ -24,8 +23,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/6643 -contract Issue6643Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue6643Test is Test { Counter public counter; event TestEvent(uint256 n); diff --git a/testdata/default/repros/Issue6759.t.sol b/testdata/default/repros/Issue6759.t.sol index ffdcb88935a92..436d7922fe3d2 100644 --- a/testdata/default/repros/Issue6759.t.sol +++ b/testdata/default/repros/Issue6759.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6759 -contract Issue6759Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6759Test is Test { function testCreateMulti() public { uint256 fork1 = vm.createFork("mainnet", 10); uint256 fork2 = vm.createFork("mainnet", 10); diff --git a/testdata/default/repros/Issue6966.t.sol b/testdata/default/repros/Issue6966.t.sol index 7e35a869ed0fb..9032a51dd63f7 100644 --- a/testdata/default/repros/Issue6966.t.sol +++ b/testdata/default/repros/Issue6966.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6966 // See also https://github.com/RustCrypto/elliptic-curves/issues/988#issuecomment-1817681013 -contract Issue6966Test is DSTest { +contract Issue6966Test is Test { function testEcrecover() public { bytes32 h = 0x0000000000000000000000000000000000000000000000000000000000000000; uint8 v = 27; diff --git a/testdata/default/repros/Issue7238.t.sol b/testdata/default/repros/Issue7238.t.sol index 5291993525009..989b8d5799fb6 100644 --- a/testdata/default/repros/Issue7238.t.sol +++ b/testdata/default/repros/Issue7238.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Reverter { function doNotRevert() public {} @@ -13,9 +12,7 @@ contract Reverter { } // https://github.com/foundry-rs/foundry/issues/7238 -contract Issue7238Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7238Test is Test { function testExpectRevertString() public { Reverter reverter = new Reverter(); vm.expectRevert("revert"); diff --git a/testdata/default/repros/Issue7457.t.sol b/testdata/default/repros/Issue7457.t.sol index 13cd033afac9a..c6413ec8cbe4b 100644 --- a/testdata/default/repros/Issue7457.t.sol +++ b/testdata/default/repros/Issue7457.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface ITarget { event AnonymousEventEmpty() anonymous; @@ -46,9 +45,7 @@ contract Target is ITarget { } // https://github.com/foundry-rs/foundry/issues/7457 -contract Issue7457Test is DSTest, ITarget { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7457Test is Test, ITarget { Target public target; function setUp() external { diff --git a/testdata/default/repros/Issue7481.t.sol b/testdata/default/repros/Issue7481.t.sol index c8116b8095aeb..7fc01fde6523a 100644 --- a/testdata/default/repros/Issue7481.t.sol +++ b/testdata/default/repros/Issue7481.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/7481 // This test ensures that we don't panic -contract Issue7481Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7481Test is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevertTransact() public { vm.expectRevert("vm.createSelectFork: invalid rpc url: mainnet"); diff --git a/testdata/default/repros/Issue8004.t.sol b/testdata/default/repros/Issue8004.t.sol index 278aa12125ff3..b7dd82af36cec 100644 --- a/testdata/default/repros/Issue8004.t.sol +++ b/testdata/default/repros/Issue8004.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract NonPersistentHelper is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract NonPersistentHelper is Test { uint256 public curState; function createSelectFork() external { @@ -42,8 +40,7 @@ contract NonPersistentHelper is DSTest { } // https://github.com/foundry-rs/foundry/issues/8004 -contract Issue8004CreateSelectForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8004CreateSelectForkTest is Test { NonPersistentHelper helper; function setUp() public { @@ -72,8 +69,7 @@ contract Issue8004CreateSelectForkTest is DSTest { } } -contract Issue8004RollForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8004RollForkTest is Test { NonPersistentHelper helper; uint256 forkId; diff --git a/testdata/default/repros/Issue8006.t.sol b/testdata/default/repros/Issue8006.t.sol index efe339d9fef2b..706703eecf8ba 100644 --- a/testdata/default/repros/Issue8006.t.sol +++ b/testdata/default/repros/Issue8006.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IERC20 { function totalSupply() external view returns (uint256 supply); @@ -15,8 +14,7 @@ contract Mock { } // https://github.com/foundry-rs/foundry/issues/8006 -contract Issue8006Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8006Test is Test { IERC20 dai; bytes32 transaction = 0xb23f389b26eb6f95c08e275ec2c360ab3990169492ff0d3e7b7233a3f81d299f; diff --git a/testdata/default/repros/Issue8168.t.sol b/testdata/default/repros/Issue8168.t.sol index 9a072ce4bbf8f..f7871b4b76f71 100644 --- a/testdata/default/repros/Issue8168.t.sol +++ b/testdata/default/repros/Issue8168.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8168 -contract Issue8168Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8168Test is Test { function testForkWarpRollPreserved() public { uint256 fork1 = vm.createFork("mainnet"); uint256 fork2 = vm.createFork("mainnet"); diff --git a/testdata/default/repros/Issue8277.t.sol b/testdata/default/repros/Issue8277.t.sol index 48a089575b402..8dca9cd7ea832 100644 --- a/testdata/default/repros/Issue8277.t.sol +++ b/testdata/default/repros/Issue8277.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8277 -contract Issue8277Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8277Test is Test { struct MyJson { string s; } diff --git a/testdata/default/repros/Issue8287.t.sol b/testdata/default/repros/Issue8287.t.sol index d1e372bda91d8..e1a67f3538e46 100644 --- a/testdata/default/repros/Issue8287.t.sol +++ b/testdata/default/repros/Issue8287.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8287 -contract Issue8287Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8287Test is Test { function testRpcBalance() public { uint256 f2 = vm.createSelectFork("mainnet", 10); bytes memory data = vm.rpc("eth_getBalance", "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"); diff --git a/testdata/default/repros/Issue8566.t.sol b/testdata/default/repros/Issue8566.t.sol index f300d096f7a22..2dad2fc1f8371 100644 --- a/testdata/default/repros/Issue8566.t.sol +++ b/testdata/default/repros/Issue8566.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8566 -contract Issue8566Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8566Test is Test { function testParseJsonUint() public { string memory json = "{ \"1284\": { \"addRewardInfo\": { \"amount\": 74258.225772486694040708e18, \"rewardPerSec\": 0.03069536448928848133e20 } } }"; diff --git a/testdata/default/repros/Issue8639.t.sol b/testdata/default/repros/Issue8639.t.sol index 6f0a7b526336f..96b2fa3621522 100644 --- a/testdata/default/repros/Issue8639.t.sol +++ b/testdata/default/repros/Issue8639.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; library ExternalLibrary { function doWork(uint256 a) public returns (uint256) { @@ -20,7 +20,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/8639 -contract Issue8639Test is DSTest { +contract Issue8639Test is Test { Counter counter; function setUp() public { @@ -28,15 +28,15 @@ contract Issue8639Test is DSTest { } /// forge-config: default.fuzz.runs = 1000 - /// forge-config: default.fuzz.seed = '100' + /// forge-config: default.fuzz.seed = "100" function test_external_library_address(address test) public { require(test != address(ExternalLibrary)); } } -contract Issue8639AnotherTest is DSTest { +contract Issue8639AnotherTest is Test { /// forge-config: default.fuzz.runs = 1000 - /// forge-config: default.fuzz.seed = '100' + /// forge-config: default.fuzz.seed = "100" function test_another_external_library_address(address test) public { require(test != address(ExternalLibrary)); } diff --git a/testdata/default/repros/Issue8971.t.sol b/testdata/default/repros/Issue8971.t.sol index 37861b483ec5d..4d235be214679 100644 --- a/testdata/default/repros/Issue8971.t.sol +++ b/testdata/default/repros/Issue8971.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -31,7 +30,8 @@ contract Handler { } } -contract Invariant is DSTest { +/// forge-config: default.isolate = true +contract Invariant is Test { Handler h; function setUp() public { diff --git a/testdata/default/repros/Issue9643.t.sol b/testdata/default/repros/Issue9643.t.sol index 5429219e5863d..fead6ebcbb0c4 100644 --- a/testdata/default/repros/Issue9643.t.sol +++ b/testdata/default/repros/Issue9643.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Mock { uint256 private counter; @@ -33,16 +32,14 @@ contract DelegateProxy { } } -contract Issue9643Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue9643Test is Test { function test_storage_json_diff() public { vm.startStateDiffRecording(); Mock proxied = Mock(address(new DelegateProxy(address(new Mock())))); proxied.setCounter(42); string memory rawDiff = vm.getStateDiffJson(); assertEq( - "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"contract\":\"default/repros/Issue9643.t.sol:DelegateProxy\",\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}},\"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\":{\"label\":null,\"contract\":\"default/repros/Issue9643.t.sol:Mock\",\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{}}}", + '{"0x2e234dae75c793f67a35089c9d99245e1c58470b":{"label":null,"contract":"default/repros/Issue9643.t.sol:DelegateProxy","balanceDiff":null,"nonceDiff":{"previousValue":0,"newValue":1},"stateDiff":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000000000000000000000000000002a","label":"implementation","type":"address","offset":0,"slot":"0","decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000002A"}}}},"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f":{"label":null,"contract":"default/repros/Issue9643.t.sol:Mock","balanceDiff":null,"nonceDiff":{"previousValue":0,"newValue":1},"stateDiff":{}}}', rawDiff ); } diff --git a/testdata/default/spec/ShanghaiCompat.t.sol b/testdata/default/spec/ShanghaiCompat.t.sol index fd7213b3d0702..7ff6405d16ccf 100644 --- a/testdata/default/spec/ShanghaiCompat.t.sol +++ b/testdata/default/spec/ShanghaiCompat.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShanghaiCompat is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ShanghaiCompat is Test { function testPush0() public { address target = address(uint160(uint256(0xc4f3))); diff --git a/testdata/default/trace/ConflictingSignatures.t.sol b/testdata/default/trace/ConflictingSignatures.t.sol deleted file mode 100644 index c8b7066c7a2f1..0000000000000 --- a/testdata/default/trace/ConflictingSignatures.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ReturnsNothing { - function func() public pure {} -} - -contract ReturnsString { - function func() public pure returns (string memory) { - return "string"; - } -} - -contract ReturnsUint { - function func() public pure returns (uint256) { - return 1; - } -} - -contract ConflictingSignaturesTest is DSTest { - ReturnsNothing retsNothing; - ReturnsString retsString; - ReturnsUint retsUint; - - function setUp() public { - retsNothing = new ReturnsNothing(); - retsString = new ReturnsString(); - retsUint = new ReturnsUint(); - } - - /// Tests that traces are decoded properly when multiple - /// functions have the same 4byte signature, but different - /// return values. - function testTraceWithConflictingSignatures() public { - retsNothing.func(); - retsString.func(); - retsUint.func(); - } -} diff --git a/testdata/default/trace/Trace.t.sol b/testdata/default/trace/Trace.t.sol deleted file mode 100644 index 19af6dd7c9fe7..0000000000000 --- a/testdata/default/trace/Trace.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RecursiveCall { - TraceTest factory; - - event Depth(uint256 depth); - event ChildDepth(uint256 childDepth); - event CreatedChild(uint256 childDepth); - - constructor(address _factory) { - factory = TraceTest(_factory); - } - - function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { - if (depth == neededDepth) { - this.negativeNum(); - return neededDepth; - } - - uint256 childDepth = this.recurseCall(neededDepth, depth + 1); - emit ChildDepth(childDepth); - - this.someCall(); - emit Depth(depth); - - return depth; - } - - function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { - if (depth == neededDepth) { - return neededDepth; - } - - RecursiveCall child = factory.create(); - emit CreatedChild(depth + 1); - - uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); - emit ChildDepth(childDepth); - emit Depth(depth); - - return depth; - } - - function someCall() public pure {} - - function negativeNum() public pure returns (int256) { - return -1000000000; - } -} - -contract TraceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - uint256 nodeId = 0; - RecursiveCall first; - - function setUp() public { - first = this.create(); - } - - function create() public returns (RecursiveCall) { - RecursiveCall node = new RecursiveCall(address(this)); - vm.label(address(node), string(abi.encodePacked("Node ", uintToString(nodeId++)))); - - return node; - } - - function testRecurseCall() public { - first.recurseCall(8, 0); - } - - function testRecurseCreate() public { - first.recurseCreate(8, 0); - } -} - -function uintToString(uint256 value) pure returns (string memory) { - // Taken from OpenZeppelin - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); -} diff --git a/testdata/default/vyper/CounterTest.vy b/testdata/default/vyper/CounterTest.vy index b6cc517d25dd6..960ec5cbaa830 100644 --- a/testdata/default/vyper/CounterTest.vy +++ b/testdata/default/vyper/CounterTest.vy @@ -1,4 +1,4 @@ -from . import ICounter +from src import ICounter interface Vm: def deployCode(artifact_name: String[1024], args: Bytes[1024] = b"") -> address: nonpayable @@ -8,7 +8,7 @@ counter: ICounter @external def setUp(): - self.counter = ICounter(extcall vm.deployCode("vyper/Counter.vy")) + self.counter = ICounter(extcall vm.deployCode("src/Counter.vy")) @external def test_increment(): diff --git a/testdata/fixtures/File/ignored/.gitignore b/testdata/fixtures/File/ignored/.gitignore new file mode 100644 index 0000000000000..9c558e357c416 --- /dev/null +++ b/testdata/fixtures/File/ignored/.gitignore @@ -0,0 +1 @@ +. diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 2360d009c7a09..86700b2584c2f 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -1,14 +1,60 @@ [profile.default] -src = "./" +src = "src" test = "./" -solc = "0.8.18" -evm_version = "paris" -ffi = false -fs_permissions = [{ access = "read-write", path = "./" }] +optimizer = true +optimizer_runs = 200 +via_ir = false +ignored_error_codes = [ + 1878, # SPDX license identifier not provided + 2018, # Function state mutability can be restricted + 2072, # Unused local variable + 2319, # This declaration shadows a builtin symbol + 2519, # This declaration shadows an existing declaration + 3860, # Contract init code size exceeds limit + 5574, # Contract code size exceeds limit + 5667, # Unused function parameter +] +extra_output = ["storageLayout"] + +additional_compiler_profiles = [ + # paris + { name = "paris", evm_version = "paris" }, +] +compilation_restrictions = [ + # paris + { paths = "paris/**", evm_version = "paris" }, +] + +ffi = true +fs_permissions = [ + { access = "read-write", path = "out/" }, + { access = "read-write", path = "fixtures/" }, +] +verbosity = 3 +prompt_timeout = 0 [profile.default.rpc_storage_caching] chains = "all" endpoints = "all" +[profile.default.invariant] +depth = 15 + [fmt] ignore = ["cheats/Vm.sol"] + +[lint] +lint_on_build = false + +# These are set in .env, which is populated when running through `cargo (nex)test`. +[rpc_endpoints] +mainnet = "${RPC_MAINNET}" +mainnet2 = "${RPC_MAINNET2}" +sepolia = "${RPC_SEPOLIA}" +optimism = "${RPC_OPTIMISM}" +arbitrum = "${RPC_ARBITRUM}" +polygon = "${RPC_POLYGON}" +bsc = "${RPC_BSC}" +avaxTestnet = "https://api.avax-test.network/ext/bc/C/rpc" +moonbeam = "https://moonbeam-rpc.publicnode.com" +rpcEnvAlias = "${RPC_ENV_ALIAS}" diff --git a/testdata/multi-version/Counter.sol b/testdata/multi-version/Counter.sol index 4f0c350335f8e..b2d9774d4b9a8 100644 --- a/testdata/multi-version/Counter.sol +++ b/testdata/multi-version/Counter.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.4; + contract Counter { uint256 public number; diff --git a/testdata/multi-version/cheats/GetCode.t.sol b/testdata/multi-version/cheats/GetCode.t.sol index 72dae24e676af..ecb78876cb053 100644 --- a/testdata/multi-version/cheats/GetCode.t.sol +++ b/testdata/multi-version/cheats/GetCode.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; import "../Counter.sol"; -contract GetCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest is Test { function testGetCodeMultiVersion() public { assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); require( diff --git a/testdata/multi-version/cheats/GetCode17.t.sol b/testdata/multi-version/cheats/GetCode17.t.sol index f8bf4bb2aee28..3cdd42ef3c78a 100644 --- a/testdata/multi-version/cheats/GetCode17.t.sol +++ b/testdata/multi-version/cheats/GetCode17.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.17; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; import "../Counter.sol"; // Same as GetCode.t.sol but for 0.8.17 version -contract GetCodeTest17 is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest17 is Test { function testGetCodeMultiVersion() public { assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); require( diff --git a/testdata/paris/cheats/Fork.t.sol b/testdata/paris/cheats/Fork.t.sol index 2f2e627de131a..281a27a0b868c 100644 --- a/testdata/paris/cheats/Fork.t.sol +++ b/testdata/paris/cheats/Fork.t.sol @@ -1,19 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); } -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint256 constant mainblock = 14_608_400; - Vm constant vm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); uint256 forkA; diff --git a/testdata/paris/cheats/GasSnapshots.t.sol b/testdata/paris/cheats/GasSnapshots.t.sol index d4f471fdeaf4d..a3f1c9921a4b5 100644 --- a/testdata/paris/cheats/GasSnapshots.t.sol +++ b/testdata/paris/cheats/GasSnapshots.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GasSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GasSnapshotTest is Test { uint256 public slot0; Flare public flare; @@ -94,7 +91,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionDefaultGroupStop() public { vm.startSnapshotGas("testSnapshotGasSection"); - flare.run(256); + flare.run(8); // vm.stopSnapshotGas() will use the last snapshot name. uint256 gasUsed = vm.stopSnapshotGas(); @@ -105,7 +102,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionCustomGroupStop() public { vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); - flare.run(256); + flare.run(8); // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. uint256 gasUsed = vm.stopSnapshotGas(); @@ -116,7 +113,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionName() public { vm.startSnapshotGas("testSnapshotGasSectionName"); - flare.run(256); + flare.run(8); uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); assertGt(gasUsed, 0); @@ -126,7 +123,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionGroupName() public { vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); - flare.run(256); + flare.run(8); uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); @@ -149,9 +146,7 @@ contract GasSnapshotTest is DSTest { } } -contract GasComparisonTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GasComparisonTest is Test { uint256 public slot0; uint256 public slot1; @@ -263,12 +258,12 @@ contract GasComparisonTest is DSTest { // Start a cheatcode snapshot. vm.startSnapshotGas("ComparisonGroup", "testGasComparisonFlareA"); - flare.run(256); + flare.run(8); uint256 a = vm.stopSnapshotGas(); // Start a comparative Solidity snapshot. _snapStart(); - flare.run(256); + flare.run(8); uint256 b = _snapEnd(); vm.snapshotValue("ComparisonGroup", "testGasComparisonFlareB", b); diff --git a/testdata/paris/cheats/LastCallGas.t.sol b/testdata/paris/cheats/LastCallGas.t.sol index 23f6df224963f..8c2c20ee8c41e 100644 --- a/testdata/paris/cheats/LastCallGas.t.sol +++ b/testdata/paris/cheats/LastCallGas.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Target { uint256 public slot0; @@ -28,8 +27,7 @@ contract Target { fallback() external {} } -abstract contract LastCallGasFixture is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +abstract contract LastCallGasFixture is Test { Target public target; struct Gas { @@ -67,6 +65,7 @@ abstract contract LastCallGasFixture is DSTest { } } +/// forge-config: default.isolate = true contract LastCallGasIsolatedTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); diff --git a/testdata/paris/core/BeforeTest.t.sol b/testdata/paris/core/BeforeTest.t.sol index 2b14bcad1d2ef..10acfb53ee88f 100644 --- a/testdata/paris/core/BeforeTest.t.sol +++ b/testdata/paris/core/BeforeTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract SelfDestructor { function kill() external { @@ -10,7 +10,7 @@ contract SelfDestructor { } // https://github.com/foundry-rs/foundry/issues/1543 -contract BeforeTestSelfDestructTest is DSTest { +contract BeforeTestSelfDestructTest is Test { SelfDestructor killer; uint256 a; uint256 b; @@ -46,16 +46,18 @@ contract BeforeTestSelfDestructTest is DSTest { function kill_contract() external { uint256 killer_size = getSize(address(killer)); - require(killer_size == 106); + assertEq(killer_size, 106); killer.kill(); + assertEq(killer_size, 106); } - function testKill() public view { + /// forge-config: default.evm_version = "paris" + function testKill() public { uint256 killer_size = getSize(address(killer)); - require(killer_size == 0); + assertEq(killer_size, 0); } - function getSize(address c) public view returns (uint32) { + function getSize(address c) internal view returns (uint32) { uint32 size; assembly { size := extcodesize(c) @@ -64,12 +66,12 @@ contract BeforeTestSelfDestructTest is DSTest { } function testA() public { - require(a <= 3); + assertLe(a, 3); a += 1; } - function testSimpleA() public view { - require(a == 0); + function testSimpleA() public { + assertEq(a, 0); } function setB() public { @@ -77,7 +79,7 @@ contract BeforeTestSelfDestructTest is DSTest { } function testB() public { - require(b == 100); + assertEq(b, 100); } function setBWithValue(uint256 value) public { diff --git a/testdata/paris/fork/Transact.t.sol b/testdata/paris/fork/Transact.t.sol index 92d595f98c516..c543590d6b1be 100644 --- a/testdata/paris/fork/Transact.t.sol +++ b/testdata/paris/fork/Transact.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../../default/logs/console.sol"; +import "utils/Test.sol"; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); @@ -11,9 +9,7 @@ interface IERC20 { function balanceOf(address account) external view returns (uint256); } -contract TransactOnForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract TransactOnForkTest is Test { IERC20 constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/testdata/paris/spec/ShanghaiCompat.t.sol b/testdata/paris/spec/ShanghaiCompat.t.sol index fd7213b3d0702..7ff6405d16ccf 100644 --- a/testdata/paris/spec/ShanghaiCompat.t.sol +++ b/testdata/paris/spec/ShanghaiCompat.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShanghaiCompat is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ShanghaiCompat is Test { function testPush0() public { address target = address(uint160(uint256(0xc4f3))); diff --git a/testdata/default/vyper/Counter.vy b/testdata/src/Counter.vy similarity index 100% rename from testdata/default/vyper/Counter.vy rename to testdata/src/Counter.vy diff --git a/testdata/default/vyper/ICounter.vyi b/testdata/src/ICounter.vyi similarity index 100% rename from testdata/default/vyper/ICounter.vyi rename to testdata/src/ICounter.vyi diff --git a/testdata/lib/ds-test/src/test.sol b/testdata/utils/DSTest.sol similarity index 100% rename from testdata/lib/ds-test/src/test.sol rename to testdata/utils/DSTest.sol diff --git a/testdata/utils/Test.sol b/testdata/utils/Test.sol new file mode 100644 index 0000000000000..6c7930bc3088e --- /dev/null +++ b/testdata/utils/Test.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./DSTest.sol"; +import "./Vm.sol"; +import "./console.sol"; + +contract Test is DSTest { + Vm public constant vm = Vm(HEVM_ADDRESS); +} diff --git a/testdata/cheats/Vm.sol b/testdata/utils/Vm.sol similarity index 100% rename from testdata/cheats/Vm.sol rename to testdata/utils/Vm.sol diff --git a/testdata/default/logs/console.sol b/testdata/utils/console.sol similarity index 100% rename from testdata/default/logs/console.sol rename to testdata/utils/console.sol From 9a02aa8bab55a637472a9d2e1fe57a233f00d66e Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:21:36 +0700 Subject: [PATCH 118/391] Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 151 ++++++++++--------------------------- 1 file changed, 39 insertions(+), 112 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e71c1d2f993e3..fc858c4bd6f85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,5 @@ name: test -permissions: {} - on: push: branches: @@ -15,31 +13,23 @@ concurrency: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full - RUSTC_WRAPPER: "sccache" jobs: nextest: uses: ./.github/workflows/nextest.yml - permissions: - contents: read with: profile: default secrets: inherit docs: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: nightly - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + cache-on-failure: true - name: Build documentation run: cargo doc --workspace --all-features --no-deps --document-private-items env: @@ -47,161 +37,99 @@ jobs: - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v3 with: path: ./target/doc deploy-docs: if: github.ref_name == 'master' && github.event_name == 'push' + runs-on: ubuntu-latest needs: [docs] - runs-on: depot-ubuntu-latest - timeout-minutes: 5 + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} + timeout-minutes: 30 steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 doctest: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + cache-on-failure: true - run: cargo test --workspace --doc typos: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 - with: - persist-credentials: false - - uses: crate-ci/typos@83157de2df0fa7c7ae20f73f9dbed44c41f2bb64 # v1 + - uses: crate-ci/typos@v1 clippy: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@clippy + - uses: Swatinem/rust-cache@v2 with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: nightly - components: clippy - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + cache-on-failure: true - run: cargo clippy --workspace --all-targets --all-features env: RUSTFLAGS: -Dwarnings rustfmt: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: nightly components: rustfmt - run: cargo fmt --all --check forge-fmt: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + cache-on-failure: true - name: forge fmt shell: bash run: ./.github/scripts/format.sh --check crate-checks: - runs-on: depot-ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 - with: - tool: cargo-hack - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + cache-on-failure: true - run: cargo hack check deny: - uses: ithacaxyz/ci/.github/workflows/deny.yml@9c8d0dc20e7ad02455d3fdab2378a05f29907630 # main - permissions: - contents: read - - codeql: - name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - permissions: - security-events: write - actions: read - contents: read - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - persist-credentials: false - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" + uses: ithacaxyz/ci/.github/workflows/deny.yml@main ci-success: runs-on: ubuntu-latest if: always() - permissions: {} needs: - nextest - docs @@ -212,10 +140,9 @@ jobs: - forge-fmt - crate-checks - deny - - codeql - timeout-minutes: 5 + timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 + uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} From e787d818e5d56fdc15062c697fa57652d1b992f0 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:32:03 +0000 Subject: [PATCH 119/391] Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc858c4bd6f85..14265f1325db0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,3 +146,4 @@ jobs: uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} + From 99056c7fe5ad4e7eea5cc7fbf999c3353b1ca573 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:31:21 +0000 Subject: [PATCH 120/391] Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8af345fe28ff..6ce03503a8858 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: docs: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -56,7 +56,7 @@ jobs: if: github.ref_name == 'master' && github.event_name == 'push' needs: [docs] runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: pages: write id-token: write @@ -70,7 +70,7 @@ jobs: doctest: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -86,18 +86,18 @@ jobs: typos: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: crate-ci/typos@83157de2df0fa7c7ae20f73f9dbed44c41f2bb64 # v1 + - uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1 clippy: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -116,7 +116,7 @@ jobs: rustfmt: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -131,7 +131,7 @@ jobs: forge-fmt: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -149,7 +149,7 @@ jobs: crate-checks: runs-on: depot-ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 30 permissions: contents: read steps: @@ -160,7 +160,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 + - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 @@ -214,7 +214,7 @@ jobs: - crate-checks - deny - codeql - timeout-minutes: 5 + timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 From 243077e8a2bbc64b90416152d1ac221d05f19203 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:51:58 +0700 Subject: [PATCH 121/391] Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/ci.yml diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index f967cfaa30db5..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello From ac329187c3edb9299d681d9fd64e3df0a8e65580 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 18 Oct 2025 22:05:55 +0000 Subject: [PATCH 122/391] Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml index 46a18d45a5fca..d7a82b5c93b6e 100644 --- a/.circleci/cargo.yml +++ b/.circleci/cargo.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.89.0 steps: - checkout - restore_cache: From 081909d7ec8c854adf4a34a5d7d1d18b294a93c6 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:28:24 +0000 Subject: [PATCH 123/391] Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml index 46a18d45a5fca..d7a82b5c93b6e 100644 --- a/.circleci/cargo.yml +++ b/.circleci/cargo.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.89.0 steps: - checkout - restore_cache: From 2c1f1f9ee1e9e894dc2df4ab4d18b8fa7b810596 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:42:58 +0000 Subject: [PATCH 124/391] Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_v1.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/ci_v1.yml diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 94bf46b3bb04f..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test From b1d84b66b81a035c156dfd555df2e50cd4c4d8d6 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:06:59 +0000 Subject: [PATCH 125/391] Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> From 7de78fb9fc7cead569c81669e74d36941b6bbd2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:42:52 +0700 Subject: [PATCH 126/391] chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 447e61f3a0afa..8a1ab60296d24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -166,7 +166,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2 + - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 From a737afa7975a5942ac7da09ddabeb80247bc5038 Mon Sep 17 00:00:00 2001 From: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:05:18 +0700 Subject: [PATCH 127/391] Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. --- .circleci/cargo.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test From c56008ed7b73e17973d518036a2fb7117b0d9230 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:17:06 +0000 Subject: [PATCH 128/391] Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index d7a82b5c93b6e..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test From 75c3b25338c8c161703893b627d08fe90bb4b8ad Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:47:06 +0000 Subject: [PATCH 129/391] Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index d7a82b5c93b6e..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test From e3ae5bef386108acd6f65ec292468016c38213dc Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:27:46 +0000 Subject: [PATCH 130/391] Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_v1.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/ci_v1.yml diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 94bf46b3bb04f..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test From 956581c9997b1f362083c74c91cb5d5d5de8fe44 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:29:35 +0000 Subject: [PATCH 131/391] Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f967cfaa30db5..df6b1216062af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,6 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/reference/configuration-reference version: 2.1 - # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs jobs: From 0ba70d2cd676df26ef1bb1da45cdb0dbc22e05f0 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:24:31 +0000 Subject: [PATCH 132/391] Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index df6b1216062af..1b131002534c9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,6 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/reference/configuration-reference + version: 2.1 # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs From f6a1abeffd51ef2dd63204cfdba28c93c583cce4 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:49:42 +0000 Subject: [PATCH 133/391] Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index f967cfaa30db5..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello From 58865b9bc8090e4aa448653074372d36c36889ee Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:50:32 +0700 Subject: [PATCH 134/391] Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1b131002534c9..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference - -version: 2.1 -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello From e060526516fb805f9d3be6c38bb99811c0c80447 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:42:40 +0000 Subject: [PATCH 135/391] Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> From 6ff6e5954ec7ed348c745f2f4ab19d3bec7e2371 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:43:12 +0000 Subject: [PATCH 136/391] Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_v1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml index 94bf46b3bb04f..82c6de5b42b73 100644 --- a/.circleci/ci_v1.yml +++ b/.circleci/ci_v1.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.89.0 steps: - checkout - restore_cache: From 1b37bfc8cc836fb459cf8a4dfa30ee9f2ec25a84 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:04:59 +0700 Subject: [PATCH 137/391] Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 79f9c45604119..b410a80d7d11f 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -162,10 +162,11 @@ jobs: PLATFORM_NAME=${{ matrix.os }} ARCH=${{ matrix.arch }} FORGE_BIN_PATH="$BIN" bun ./scripts/prepublish.ts - name: Sanity Check Binary - working-directory: ./npm + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | set -euo pipefail - PKG_DIR="./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" + PKG_DIR="$ARTIFACT_DIR/@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" BIN="$PKG_DIR/bin/forge" if [[ "${{ matrix.os }}" == "win32" ]]; then BIN="$PKG_DIR/bin/forge.exe" @@ -185,7 +186,7 @@ jobs: fi - name: Publish ${{ matrix.os }}-${{ matrix.arch }} Binary - working-directory: ./npm + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} env: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} @@ -195,9 +196,9 @@ jobs: run: | set -euo pipefail - ls -la ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} + ls -la "$ARTIFACT_DIR/@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" - bun ./scripts/publish.ts ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} + bun ./scripts/publish.ts "$ARTIFACT_DIR/@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" echo "Published @foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" From 0b4d4dfb29a34884bd6ab7d651a3e5342293da0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:42:21 +0000 Subject: [PATCH 138/391] chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] --- Cargo.lock | 88 ++++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e972d3906b5dc..830ba11d18833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,16 +130,15 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cc14d832bc3331ca22a1c7819de1ede99f58f61a7d123952af7dde8de124a6" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "arbitrary", - "derive_arbitrary", "derive_more 2.0.1", "itoa", "proptest", @@ -286,9 +285,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -378,20 +377,19 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_arbitrary", "derive_more 2.0.1", - "foldhash", + "foldhash 0.2.0", "getrandom 0.3.3", - "hashbrown 0.15.3", + "hashbrown 0.16.0", "indexmap 2.9.0", "itoa", "k256", @@ -769,9 +767,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -783,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -802,9 +800,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "alloy-json-abi", "const-hex", @@ -820,9 +818,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow", @@ -830,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3803,6 +3801,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "forge" version = "1.2.1" @@ -5008,7 +5012,16 @@ checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", "serde", ] @@ -7138,9 +7151,9 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", @@ -7849,14 +7862,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -7870,7 +7884,7 @@ dependencies = [ "rand 0.9.1", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -8270,18 +8284,28 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -9068,9 +9092,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 76f51d5337151..68e6e2881ff7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,7 +232,7 @@ alloy-transport-ipc = { version = "1.0.5", default-features = false } alloy-transport-ws = { version = "1.0.5", default-features = false } ## alloy-core -alloy-dyn-abi = "1.0" +alloy-dyn-abi = "1.4" alloy-json-abi = "1.0" alloy-primitives = { version = "1.0", features = [ "getrandom", From 635d4360f35cecb84e4b3d72036fa63513c5b44a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:42:21 +0000 Subject: [PATCH 139/391] chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] --- Cargo.lock | 88 ++++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e972d3906b5dc..830ba11d18833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,16 +130,15 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cc14d832bc3331ca22a1c7819de1ede99f58f61a7d123952af7dde8de124a6" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "arbitrary", - "derive_arbitrary", "derive_more 2.0.1", "itoa", "proptest", @@ -286,9 +285,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -378,20 +377,19 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_arbitrary", "derive_more 2.0.1", - "foldhash", + "foldhash 0.2.0", "getrandom 0.3.3", - "hashbrown 0.15.3", + "hashbrown 0.16.0", "indexmap 2.9.0", "itoa", "k256", @@ -769,9 +767,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -783,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -802,9 +800,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "alloy-json-abi", "const-hex", @@ -820,9 +818,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow", @@ -830,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3803,6 +3801,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "forge" version = "1.2.1" @@ -5008,7 +5012,16 @@ checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", "serde", ] @@ -7138,9 +7151,9 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", @@ -7849,14 +7862,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -7870,7 +7884,7 @@ dependencies = [ "rand 0.9.1", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -8270,18 +8284,28 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -9068,9 +9092,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 76f51d5337151..68e6e2881ff7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,7 +232,7 @@ alloy-transport-ipc = { version = "1.0.5", default-features = false } alloy-transport-ws = { version = "1.0.5", default-features = false } ## alloy-core -alloy-dyn-abi = "1.0" +alloy-dyn-abi = "1.4" alloy-json-abi = "1.0" alloy-primitives = { version = "1.0", features = [ "getrandom", From 14f26f52dbe20e3ad1e33723e6a492a7adf2fc76 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:44:17 +0700 Subject: [PATCH 140/391] Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci-web3-gamefi.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/ci-web3-gamefi.yml diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- From 445d5cf19414cf6cf0ba026aba5a946ba5377ae1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 00:31:56 +0700 Subject: [PATCH 141/391] Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index 7293433a50f2d..9a32db6188818 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -1,5 +1,4 @@ version: 2.1 - jobs: build-and-test: docker: From b3bbd1ef5c91bbe27b1f3ba1c3f0886afd3316a8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:35:25 +0700 Subject: [PATCH 142/391] Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 79f9c45604119..60c1fe01c64e1 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -98,6 +98,17 @@ jobs: node-version: "24" registry-url: "https://registry.npmjs.org" + - name: Validate npm directory for artifact poisoning + run: | + set -euo pipefail + # Disallow dangerous files likely to be poisoned by artifacts + for f in "package.json" "bun.lockb" "package-lock.json" "yarn.lock" ".npmrc"; do + if [ -f "./npm/$f" ]; then + echo "ERROR: Untrusted '$f' present in ./npm – aborting to prevent artifact poisoning." + exit 1 + fi + done + - name: Install Dependencies working-directory: ./npm run: bun install --frozen-lockfile From e101ade3d5caa414dc80ef96f69740c17bf08790 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:35:55 +0700 Subject: [PATCH 143/391] Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index e123988861691..500eef320deef 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } From d96b67de4b62a28833f79ae0211420616c8ed44d Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:38:59 +0700 Subject: [PATCH 144/391] Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/evm/src/executors/corpus.rs | 33 ++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index 4577c9788d708..e4de7932f55f4 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -190,6 +190,15 @@ impl CorpusManager { foundry_common::fs::create_dir_all(corpus_dir)?; } + // Canonicalize the corpus_dir to a trusted absolute path + let canonical_corpus_dir = match corpus_dir.canonicalize() { + Ok(dir) => dir, + Err(e) => { + trace!(target: "corpus", "failed to canonicalize corpus_dir {}: {e}", corpus_dir.display()); + return Err(e.into()); + } + }; + let can_replay_tx = |tx: &BasicTxDetails| -> bool { fuzzed_contracts.is_some_and(|contracts| contracts.targets.lock().can_replay(tx)) || fuzzed_function.is_some_and(|function| { @@ -202,21 +211,31 @@ impl CorpusManager { 'corpus_replay: for entry in std::fs::read_dir(corpus_dir)? { let path = entry?.path(); - if path.is_file() - && let Some(name) = path.file_name().and_then(|s| s.to_str()) + // Canonicalize the candidate path, skip if it cannot be canonicalized (e.g. broken symlink) + let canonical_path = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, + }; + // Ensure file is inside the corpus directory (prevents path traversal/symlink escape) + if !canonical_path.starts_with(&canonical_corpus_dir) { + trace!(target: "corpus", "Skipping file outside corpus_dir: {}", path.display()); + continue; + } + if canonical_path.is_file() + && let Some(name) = canonical_path.file_name().and_then(|s| s.to_str()) && name.contains(METADATA_SUFFIX) { // Ignore metadata files continue; } - let read_corpus_result = match path.extension().and_then(|ext| ext.to_str()) { - Some("gz") => foundry_common::fs::read_json_gzip_file::>(&path), - _ => foundry_common::fs::read_json_file::>(&path), + let read_corpus_result = match canonical_path.extension().and_then(|ext| ext.to_str()) { + Some("gz") => foundry_common::fs::read_json_gzip_file::>(&canonical_path), + _ => foundry_common::fs::read_json_file::>(&canonical_path), }; let Ok(tx_seq) = read_corpus_result else { - trace!(target: "corpus", "failed to load corpus from {}", path.display()); + trace!(target: "corpus", "failed to load corpus from {}", canonical_path.display()); continue; }; @@ -265,7 +284,7 @@ impl CorpusManager { ); // Populate in memory corpus with the sequence from corpus file. - in_memory_corpus.push(CorpusEntry::new(tx_seq, path)?); + in_memory_corpus.push(CorpusEntry::new(tx_seq, canonical_path.clone())?); } } From 3011ba593af52c4ce3ae1090d04d44d67b9d5ff9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:40:44 +0700 Subject: [PATCH 145/391] Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 60c1fe01c64e1..d20eb24129462 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -87,6 +87,37 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + - name: Validate and Copy Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + # List expected artifacts and their paths + EXPECTED_PATHS=( + "@foundry-rs/forge-linux-amd64" + "@foundry-rs/forge-linux-arm64" + "@foundry-rs/forge-darwin-amd64" + "@foundry-rs/forge-darwin-arm64" + "@foundry-rs/forge-win32-amd64" + ) + DEST_DIR="./npm" + mkdir -p "$DEST_DIR" + for PKG in "${EXPECTED_PATHS[@]}"; do + SRC="$ARTIFACT_DIR/$PKG" + if [ -d "$SRC" ]; then + echo "Validating and copying $SRC to $DEST_DIR" + # Optionally: validate files inside $SRC (e.g., hashes, signatures, expected file names) + # Basic check: ensure correct file structure + find "$SRC" -type f | grep -E '/bin/(forge|forge.exe)$' || { + echo "ERROR: Expected binary not found in $SRC" >&2 + exit 1 + } + cp -a "$SRC" "$DEST_DIR/" + else + echo "WARNING: Expected package $SRC not found in artifacts." + fi + done + ls -l "$DEST_DIR" - name: Setup Bun uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2 with: @@ -196,6 +227,7 @@ jobs: fi - name: Publish ${{ matrix.os }}-${{ matrix.arch }} Binary + # Only use ./npm, which now contains validated/copies of artifact files working-directory: ./npm env: PROVENANCE: true From 321c2023ff954e2f0a99ad9d832ac2780dfaf872 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:41:19 +0700 Subject: [PATCH 146/391] Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 839a3bfe8b224..e1f5d83e46408 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -123,6 +123,18 @@ impl ScriptTester { for entry in fs::read_dir(&from_dir)? { let file = &entry?.path(); let name = file.file_name().unwrap(); + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Optionally verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } fs::copy(file, to_dir.join(name))?; } Ok(()) From 1bc0b5d5dd1ecfe05681f3b06e8faadc0c0a3ab5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:41:54 +0700 Subject: [PATCH 147/391] Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- npm/src/const.ts | 25 ++++++++++++++++++++++++- npm/src/install.ts | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/npm/src/const.ts b/npm/src/const.ts index 6762a149de196..1f0e32ab5d4fb 100644 --- a/npm/src/const.ts +++ b/npm/src/const.ts @@ -1,13 +1,36 @@ import type * as Process from 'node:process' + +// Allow-list of recognized/approved registry hostnames +export const ALLOWED_REGISTRY_HOSTS = [ + 'registry.npmjs.org', + 'localhost', + '127.0.0.1', + '::1', + // Add any additional trusted registry hosts below as needed +] + export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const rawUrl = ( process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' ) + let hostname + try { + hostname = new URL(rawUrl).hostname + } catch { + throw new Error(`Invalid registry URL: ${rawUrl}`) + } + if (!ALLOWED_REGISTRY_HOSTS.includes(hostname)) { + throw new Error( + `Registry URL host "${hostname}" is not in the allow-list (${ALLOWED_REGISTRY_HOSTS.join(', ')}). ` + + 'Set your registry to a trusted host.' + ) + } + return rawUrl } export type Architecture = Extract<(typeof Process)['arch'], 'arm64' | 'x64'> diff --git a/npm/src/install.ts b/npm/src/install.ts index a89fe5403571c..f637106a3e452 100644 --- a/npm/src/install.ts +++ b/npm/src/install.ts @@ -1,4 +1,4 @@ -import { BINARY_NAME, colors, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME } from '#const.ts' +import { BINARY_NAME, colors, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME, ALLOWED_REGISTRY_HOSTS } from '#const.ts' import * as NodeCrypto from 'node:crypto' import * as NodeFS from 'node:fs' import * as NodeHttp from 'node:http' From a970332c912f39c25246465f75ae4fb81a5e9bed Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:42:45 +0700 Subject: [PATCH 148/391] Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- npm/src/const.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/npm/src/const.ts b/npm/src/const.ts index 6762a149de196..42a352712c6ba 100644 --- a/npm/src/const.ts +++ b/npm/src/const.ts @@ -1,13 +1,34 @@ import type * as Process from 'node:process' +const ALLOWED_REGISTRY_HOSTS = [ + 'registry.npmjs.org', + 'registry.yarnpkg.com', + // Add any additional trusted registry domains here +] + +function isAllowedRegistryHostname(urlString: string): boolean { + try { + const url = new URL(urlString) + // Compare against allow-list. Consider only exact domain match for safety. + return ALLOWED_REGISTRY_HOSTS.includes(url.hostname) + } catch { + return false + } +} + export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const candidate = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + if (!isAllowedRegistryHostname(candidate)) { + throw new Error( + `Refusing to use registry URL '${candidate}' not in allowed list: ${ALLOWED_REGISTRY_HOSTS.join(', ')}` + ) + } + return candidate } export type Architecture = Extract<(typeof Process)['arch'], 'arm64' | 'x64'> From 1cf6991bb1b82fd10018efbe4ddbb31c001b9010 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:24:29 +0000 Subject: [PATCH 149/391] Create codeql.yml (#208) --- .github/workflows/codeql.yml | 105 +++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..63c774765c2b6 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,105 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '35 4 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + - language: python + build-mode: none + - language: rust + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From 51d92e156cc3e15a605ebc08307d607f1b122fbe Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:16:27 +0000 Subject: [PATCH 150/391] Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. --- .circleci/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index 7293433a50f2d..e5e4c033b1e82 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.89.0 steps: - checkout - restore_cache: From ce554bee23a64865b45c5321dd4afef99d10e1d6 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:58:37 +0000 Subject: [PATCH 151/391] Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml index 46a18d45a5fca..a8a81c7bbd986 100644 --- a/.circleci/cargo.yml +++ b/.circleci/cargo.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.87.0 steps: - checkout - restore_cache: From ae8b96f84fc30556907a5f9d210ec43845f0f599 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:37:24 +0000 Subject: [PATCH 152/391] Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index 9a32db6188818..1b5df6d6e668e 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.89.0 steps: - checkout - restore_cache: From f68ca66b074031df1f82aada59917b8b71349e9e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:39:16 +0000 Subject: [PATCH 153/391] Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04f996a346ab7..ce463545d7b10 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,11 +8,11 @@ executors: - image: cimg/base:stable auth: # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/hardhat-project/environment-variables + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables username: $DOCKER_HUB_USER password: $DOCKER_HUB_PASSWORD jobs: - web3-defi-game-project-: + web3-defi-game-project: executor: my-custom-executor steps: @@ -22,4 +22,4 @@ jobs: workflows: my-custom-workflow: jobs: - - web3-defi-game-project- + - web3-defi-game-project From 11e6a75587787c0d6d00539b209dc98c378146e1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Oct 2025 05:16:46 +0000 Subject: [PATCH 154/391] Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/cargo.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test From 647b198634605b36717e132d68c8c4ecdfc4fa9b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:55:38 +0000 Subject: [PATCH 155/391] Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 145 +++++++++++++--------------------- 1 file changed, 55 insertions(+), 90 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06061ff6c7508..1b18d4ad1dd2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,5 @@ name: release -permissions: {} - on: push: tags: @@ -17,35 +15,31 @@ env: CARGO_TERM_COLOR: always IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} PROFILE: maxperf - STABLE_VERSION: "v1.3.6" + STABLE_VERSION: "v1.1.0" jobs: prepare: name: Prepare release runs-on: ubuntu-latest timeout-minutes: 30 - permissions: - contents: write - pull-requests: read outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} release_name: ${{ steps.release_info.outputs.release_name }} changelog: ${{ steps.build_changelog.outputs.changelog }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 with: - persist-credentials: false fetch-depth: 0 - name: Compute release name and tag id: release_info run: | if [[ ${IS_NIGHTLY} == 'true' ]]; then - printf 'tag_name=%s\n' "nightly-${GITHUB_SHA}" >> "$GITHUB_OUTPUT" - printf 'release_name=%s\n' "Nightly ($(date '+%Y-%m-%d'))" >> "$GITHUB_OUTPUT" + echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT + echo "release_name=Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT else - printf 'tag_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" - printf 'release_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + echo "release_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT fi # Creates a `nightly-SHA` tag for this specific nightly @@ -54,7 +48,7 @@ jobs: # the changelog. - name: Create build-specific nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v8 + uses: actions/github-script@v7 env: TAG_NAME: ${{ steps.release_info.outputs.tag_name }} with: @@ -64,7 +58,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@d702b5bb7c23735c8afc130dac9c4c8b8eb669e8 # v6 + uses: mikepenz/release-changelog-builder-action@v4 with: configuration: "./.github/changelog.json" fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || env.STABLE_VERSION }} @@ -76,10 +70,6 @@ jobs: name: Release Docker needs: prepare uses: ./.github/workflows/docker-publish.yml - permissions: - contents: read - id-token: write - packages: write with: tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -100,27 +90,28 @@ jobs: # `target`: Rust build target triple # `platform` and `arch`: Used in tarball names # `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 - # These are pinned to the oldest runner versions to support old libc/SDK versions. - - runner: depot-ubuntu-22.04-16 + - runner: Linux-22.04 target: x86_64-unknown-linux-gnu svm_target_platform: linux-amd64 platform: linux arch: amd64 - - runner: depot-ubuntu-22.04-16 + - runner: Linux-22.04 target: x86_64-unknown-linux-musl svm_target_platform: linux-amd64 platform: alpine arch: amd64 - - runner: depot-ubuntu-22.04-arm-16 + - runner: Linux-22.04 target: aarch64-unknown-linux-gnu svm_target_platform: linux-aarch64 platform: linux arch: arm64 - - runner: depot-ubuntu-22.04-16 + - runner: Linux-22.04 target: aarch64-unknown-linux-musl svm_target_platform: linux-aarch64 platform: alpine arch: arm64 + # This is pinned to `macos-13-large` to support old SDK versions. + # If the runner is deprecated it should be pinned to the oldest available version of the runner. - runner: macos-13-large target: x86_64-apple-darwin svm_target_platform: macosx-amd64 @@ -131,31 +122,31 @@ jobs: svm_target_platform: macosx-aarch64 platform: darwin arch: arm64 - - runner: depot-windows-latest-16 + - runner: Windows target: x86_64-pc-windows-msvc svm_target_platform: windows-amd64 platform: win32 arch: amd64 steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable targets: ${{ matrix.target }} - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true - name: Apple M1 setup if: matrix.target == 'aarch64-apple-darwin' run: | - printf 'SDKROOT=%s\n' "$(xcrun -sdk macosx --show-sdk-path)" >> "$GITHUB_ENV" - printf 'MACOSX_DEPLOYMENT_TARGET=%s\n' "$(xcrun -sdk macosx --show-sdk-platform-version)" >> "$GITHUB_ENV" + echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV + echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - name: cross setup - if: contains(matrix.target, 'musl') + if: matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-gnu' run: | - cargo install cross --git https://github.com/cross-rs/cross --rev baf457efc2555225af47963475bd70e8d2f5993f + cargo install cross - name: Build binaries env: @@ -167,8 +158,8 @@ jobs: shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $PROFILE --bins - --no-default-features --features aws-kms,gcp-kms,cli,asm-keccak,js-tracer) + flags=(--target $TARGET --profile $PROFILE --bins + --no-default-features --features aws-kms,gcp-kms,cli,asm-keccak) # `jemalloc` is not fully supported on MSVC or aarch64 Linux. if [[ "$TARGET" != *msvc* && "$TARGET" != "aarch64-unknown-linux-gnu" ]]; then @@ -177,7 +168,7 @@ jobs: [[ "$TARGET" == *windows* ]] && ext=".exe" - if [[ "$TARGET" == *-musl ]]; then + if [[ "$TARGET" == *-musl || "$TARGET" == "aarch64-unknown-linux-gnu" ]]; then cross build "${flags[@]}" else cargo build "${flags[@]}" @@ -185,13 +176,13 @@ jobs: bins=(anvil cast chisel forge) for name in "${bins[@]}"; do - bin="$OUT_DIR/$name$ext" - printf '\n' + bin=$OUT_DIR/$name$ext + echo "" file "$bin" || true du -h "$bin" || true ldd "$bin" || true $bin --version || true - printf '%s_bin_path=%s\n' "$name" "$bin" >> "$GITHUB_ENV" + echo "${name}_bin_path=${bin}" >> $GITHUB_ENV done - name: Archive binaries @@ -204,27 +195,19 @@ jobs: shell: bash run: | if [[ "$PLATFORM_NAME" == "linux" || "$PLATFORM_NAME" == "alpine" ]]; then - tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel - printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" + tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT elif [ "$PLATFORM_NAME" == "darwin" ]; then # We need to use gtar here otherwise the archive is corrupt. # See: https://github.com/actions/virtual-environments/issues/2619 - gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel - printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" + gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT else - cd "$OUT_DIR" + cd $OUT_DIR 7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ - printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> $GITHUB_OUTPUT fi - printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - retention-days: 1 - name: ${{ steps.artifacts.outputs.file_name }} - path: ${{ steps.artifacts.outputs.file_name }} - name: Build man page id: man @@ -244,30 +227,11 @@ jobs: gzip anvil.1 gzip chisel.1 tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz - printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - - - name: Binaries attestation - id: attestation - uses: actions/attest-build-provenance@v3 - with: - subject-path: | - ${{ env.anvil_bin_path }} - ${{ env.cast_bin_path }} - ${{ env.chisel_bin_path }} - ${{ env.forge_bin_path }} - - - name: Record attestation URL - env: - ATTESTATION_URL: ${{ steps.attestation.outputs.attestation-url }} - FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} - shell: bash - run: | - set -euo pipefail - printf '%s\n' "$ATTESTATION_URL" > "$FOUNDRY_ATTESTATION" + echo "foundry_man=foundry_man_${VERSION_NAME}.tar.gz" >> $GITHUB_OUTPUT # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 + uses: softprops/action-gh-release@v2 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -275,14 +239,22 @@ jobs: body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} - ${{ steps.artifacts.outputs.foundry_attestation }} ${{ steps.man.outputs.foundry_man }} + - name: Binaries attestation + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + ${{ env.anvil_bin_path }} + ${{ env.cast_bin_path }} + ${{ env.chisel_bin_path }} + ${{ env.forge_bin_path }} + # If this is a nightly release, it also updates the release # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 + uses: softprops/action-gh-release@v2 with: name: "Nightly" tag_name: "nightly" @@ -290,33 +262,28 @@ jobs: body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} - ${{ steps.artifacts.outputs.foundry_attestation }} ${{ steps.man.outputs.foundry_man }} cleanup: name: Release cleanup runs-on: ubuntu-latest timeout-minutes: 30 - permissions: - contents: write needs: release if: always() steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false + - uses: actions/checkout@v4 # Moves the `nightly` tag to `HEAD` - name: Move nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v8 + uses: actions/github-script@v7 with: script: | const moveTag = require('./.github/scripts/move-tag.js') await moveTag({ github, context }, 'nightly') - name: Delete old nightlies - uses: actions/github-script@v8 + uses: actions/github-script@v7 with: script: | const prunePrereleases = require('./.github/scripts/prune-prereleases.js') @@ -329,13 +296,11 @@ jobs: needs: [prepare, release-docker, release, cleanup] if: failure() permissions: - contents: read issues: write + contents: read steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2 + - uses: actions/checkout@v4 + - uses: JasonEtco/create-an-issue@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | From 99d05916a91fc18c65bd20a60e585ace627d72c9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:23:58 +0000 Subject: [PATCH 156/391] Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 21 ------ .github/workflows/docker.yml | 100 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 21 deletions(-) delete mode 100644 .github/workflows/docker-image.yml create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index e0c9c518e8748..0000000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..e994f94e7085c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,100 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '21 12 * * *' + push: + branches: [ "master" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "master" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + - name: Build the Docker image + run: docker build . --file path/to/Dockerfile --tag my-image-name:$(date +%s) + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 + with: + cosign-release: 'v2.2.4' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v5.0.0 + with: + context: ./ + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From 130352fbd0745d9ce6fded0910407b079ee0a0a3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:08:28 +0000 Subject: [PATCH 157/391] Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci-web3-gamefi.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/ci-web3-gamefi.yml diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- From c89ea92d11c3401bdb2f40c2a06d7f1d749bcf94 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:59:11 +0700 Subject: [PATCH 158/391] Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker.yml | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..e994f94e7085c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,100 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '21 12 * * *' + push: + branches: [ "master" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "master" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + - name: Build the Docker image + run: docker build . --file path/to/Dockerfile --tag my-image-name:$(date +%s) + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 + with: + cosign-release: 'v2.2.4' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v5.0.0 + with: + context: ./ + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From ee6ba459e054c412b1aa90cf1975c33284b2476e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:29:14 +0700 Subject: [PATCH 159/391] Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/ci.yml b/.circleci/ci.yml index 7293433a50f2d..0d184dfcdec13 100644 --- a/.circleci/ci.yml +++ b/.circleci/ci.yml @@ -1,5 +1,5 @@ version: 2.1 - +# jobs: build-and-test: docker: From 4a51626d2317289955d6bbd82bed064074a87991 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 31 Oct 2025 07:50:14 +0700 Subject: [PATCH 160/391] Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/docker-image.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000000000..e0c9c518e8748 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,21 @@ +name: Docker Image CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From f4ae42a12d4f5119710877ea3c5830578e6d744b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:37:07 +0700 Subject: [PATCH 161/391] Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76b2889f1c4b2..a622410e8211b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/configuration-reference version: 2.1 - +# # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs jobs: @@ -12,7 +12,7 @@ jobs: # Specify the version you desire here # See: https://circleci.com/developer/images/image/cimg/base - image: cimg/base:current - + # # Add steps to the job # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps steps: @@ -21,7 +21,7 @@ jobs: - run: name: "Say hello" command: "echo Hello, World!" - +# # Orchestrate jobs using workflows # See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows workflows: From 64546a91d35ddaeac83810303e006cd18928d21a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:39:16 +0700 Subject: [PATCH 162/391] Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/script-sequence/src/sequence.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 5759e7cf15208..143f6b3e9e8a1 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -12,7 +12,7 @@ use std::{ path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; - +# pub const DRY_RUN_DIR: &str = "dry-run"; #[derive(Clone, Serialize, Deserialize)] @@ -20,7 +20,7 @@ pub struct NestedValue { pub internal_type: String, pub value: String, } - +# /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted #[derive(Clone, Default, Serialize, Deserialize)] @@ -148,7 +148,7 @@ impl ScriptSequence { pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { self.receipts.push(receipt); } - + # /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); @@ -164,7 +164,7 @@ impl ScriptSequence { pub fn remove_pending(&mut self, tx_hash: TxHash) { self.pending.retain(|element| element != &tx_hash); } - + # /// Gets paths in the formats /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. @@ -218,7 +218,7 @@ impl ScriptSequence { .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } - +# /// Converts the `sig` argument into the corresponding file path. /// /// This accepts either the signature of the function or the raw calldata. From ecfc99bf2353dfdcfb24d68a39631b1601ed69f9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:28:08 +0700 Subject: [PATCH 163/391] Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .github/workflows/dependencies.yml | 52 ++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index d3ec66f9d214d..0e51587b94623 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -2,18 +2,56 @@ name: dependencies -permissions: {} - on: schedule: - - cron: "0 0 * * SUN" # Run weekly on Sundays at midnight UTC - workflow_dispatch: # Needed so we can run it manually + # Run weekly + - cron: "0 0 * * SUN" + workflow_dispatch: + # Needed so we can run it manually +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: cargo-update + TITLE: "chore(deps): weekly `cargo update`" + BODY: | + Automation to keep dependencies in `Cargo.lock` current. +
cargo update log +

+ ```log + $cargo_update_log + ``` +

+
jobs: update: - uses: ithacaxyz/ci/.github/workflows/cargo-update-pr.yml@9c8d0dc20e7ad02455d3fdab2378a05f29907630 # main + name: Update + runs-on: ubuntu-latest permissions: contents: write pull-requests: write - secrets: - token: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + + - name: cargo update + # Remove first line that always just says "Updating crates.io index" + run: cargo update --color never 2>&1 | sed '/crates.io index/d' | tee cargo_update.log + + - name: craft commit message and PR body + id: msg + run: | + export cargo_update_log="$(cat cargo_update.log)" + echo "commit_message<> $GITHUB_OUTPUT + printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "body<> $GITHUB_OUTPUT + echo "$BODY" | envsubst >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + add-paths: ./Cargo.lock + commit-message: ${{ steps.msg.outputs.commit_message }} + title: ${{ env.TITLE }} + body: ${{ steps.msg.outputs.body }} + branch: ${{ env.BRANCH }} From 1b91245ea6ebda033002225a7abffeb6109280d3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:29:55 +0700 Subject: [PATCH 164/391] Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index b410a80d7d11f..1b2437288eb64 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -184,7 +184,8 @@ jobs: exit 1 fi fi - + + # Run automatically after a successful 'release' workflow, or manually if a run_id is provided - name: Publish ${{ matrix.os }}-${{ matrix.arch }} Binary ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} env: From f652ebab934253f0b1ca21a1131ad7f3e0523067 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 1 Nov 2025 00:25:46 +0700 Subject: [PATCH 165/391] Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/snyk-container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml index 10ac023cf51fc..f07df9c75c8d1 100644 --- a/.github/workflows/snyk-container.yml +++ b/.github/workflows/snyk-container.yml @@ -2,7 +2,7 @@ # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. - +# # A sample workflow which checks out the code, builds a container # image using Docker and scans that image for vulnerabilities using # Snyk. The results are then uploaded to GitHub Security Code Scanning From a63cc1a5e96d2ded9140ed74eaccf6f41da3dc47 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 1 Nov 2025 01:22:41 +0700 Subject: [PATCH 166/391] Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/nextest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index edd230a05a5d9..c1343dcc6d64c 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -1,5 +1,4 @@ # Reusable workflow for running tests via `cargo nextest` - name: nextest permissions: {} @@ -85,6 +84,7 @@ jobs: with: python-version: "3.14" - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev run: pip --version && pip install vyper==0.4.3 From 02e3a1b758bf51e3bff13150632731ad18ce3830 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 2 Nov 2025 02:34:56 +0700 Subject: [PATCH 167/391] Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- npm/src/const.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/npm/src/const.ts b/npm/src/const.ts index 42a352712c6ba..890dc5d384767 100644 --- a/npm/src/const.ts +++ b/npm/src/const.ts @@ -1,5 +1,4 @@ import type * as Process from 'node:process' - const ALLOWED_REGISTRY_HOSTS = [ 'registry.npmjs.org', 'registry.yarnpkg.com', From 297291cb60a6fb32893366d6bf500709b9519ca5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:26:12 +0700 Subject: [PATCH 168/391] Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. From 5f89e0e224f7c3cdeeb56dad7af71e156e7e037f Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:30:48 +0700 Subject: [PATCH 169/391] Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs From b2aaca372dc4880c4f4b32f0717d7e597f26c0a6 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Nov 2025 00:16:20 +0700 Subject: [PATCH 170/391] Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/dependencies.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 184e5d13f2c73..be3aab19c4eca 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -1,5 +1,4 @@ # Runs `cargo update` periodically. - name: dependencies on: From daa9b24b45532964b73a4140d494b239138dd626 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Nov 2025 05:07:08 +0700 Subject: [PATCH 171/391] Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/dependencies.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index be3aab19c4eca..2d7e331e5fffc 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -1,10 +1,12 @@ # Runs `cargo update` periodically. + name: dependencies on: schedule: # Run weekly - cron: "0 0 * * SUN" + workflow_dispatch: # Needed so we can run it manually From 732ee8b2223e8121bfc468c08fc6ffe294cc42a9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Nov 2025 05:30:19 +0700 Subject: [PATCH 172/391] Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/dependencies.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 2d7e331e5fffc..184e5d13f2c73 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -6,7 +6,6 @@ on: schedule: # Run weekly - cron: "0 0 * * SUN" - workflow_dispatch: # Needed so we can run it manually From fdba942106ad15725e4c9da90d1e5c34d6766b39 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Nov 2025 05:57:11 +0700 Subject: [PATCH 173/391] Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 716abb3648f13..38ddd73f42e6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: branches: - master - main - - dev pull_request: concurrency: From 3010559e30ce64cc93670d794b4af61b72013f77 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:19:33 +0700 Subject: [PATCH 174/391] Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3fe0f910e5b4..33846b945a7fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - main pull_request: concurrency: From d8c5c3cba74b2e69628e875c8f255d955a4964ff Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 11 Nov 2025 01:49:26 +0700 Subject: [PATCH 175/391] Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> From 9d2402a5506f5ec29cf8bf6b86272df8d3fd35f2 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 11 Nov 2025 06:19:32 +0700 Subject: [PATCH 176/391] Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 830ba11d18833..72f58c7188cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 - [[package]] name = "addr2line" version = "0.24.2" @@ -10847,3 +10846,5 @@ dependencies = [ "cc", "pkg-config", ] + + From dd9886e8d0adf479abf73df9cc7ab693a1bf877a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 11 Nov 2025 06:47:08 +0700 Subject: [PATCH 177/391] Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .circleci/config.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..e0476fddba507 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 +# +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:2024.06 + # + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" +# +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + From 724151fc36e84a1bbf337b4421d7ceb3d0a0cbe3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:13:25 +0700 Subject: [PATCH 178/391] Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76b2889f1c4b2..4168efef0971f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,20 +1,20 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference +# See: https://circleci.com/docs/reference/configuration-reference version: 2.1 # Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs jobs: say-hello: # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job docker: # Specify the version you desire here # See: https://circleci.com/developer/images/image/cimg/base - image: cimg/base:current # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps steps: # Checkout the code as the first step. - checkout @@ -23,7 +23,7 @@ jobs: command: "echo Hello, World!" # Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows workflows: say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. From 810d81ce1c61ca91a9d2f55a70053cb3b8e22457 Mon Sep 17 00:00:00 2001 From: "snyk-io[bot]" <141718529+snyk-io[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:21:45 +0000 Subject: [PATCH 179/391] fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr --- npm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/package.json b/npm/package.json index 23d091b1cec0c..8575d52837b43 100644 --- a/npm/package.json +++ b/npm/package.json @@ -10,7 +10,7 @@ "clean": "rm -rf bin dist && rm -rf ./@foundry-rs/forge*/bin ./@foundry-rs/forge*/dist ./@foundry-rs/forge*/*.tgz" }, "dependencies": { - "tsdown": "^0.15.1", + "tsdown": "^0.16.1", "typescript": "^5.9.2", "@types/bun": "^1.2.22", "@types/node": "^24.4.0" From bfd383fb0bbef4b366538ec4bce88a85e73339ef Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:49:46 +0700 Subject: [PATCH 180/391] Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/google.yml | 117 +++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/google.yml diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide From 76989a6f07049f9ca5eb89306f60d4d090ec521d Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Dec 2025 05:51:39 +0700 Subject: [PATCH 181/391] Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/{ci.yml => cargo.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .circleci/{ci.yml => cargo.yml} (95%) diff --git a/.circleci/ci.yml b/.circleci/cargo.yml similarity index 95% rename from .circleci/ci.yml rename to .circleci/cargo.yml index 7293433a50f2d..e89694e7d2a6e 100644 --- a/.circleci/ci.yml +++ b/.circleci/cargo.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build-and-test: docker: - - image: cimg/rust:1.88.0 + - image: cimg/rust:1.78.0 steps: - checkout - restore_cache: From 2c467d94ca1398ba1a6d2f3542a92d2d16f911af Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 13 Dec 2025 22:40:34 +0700 Subject: [PATCH 182/391] Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index e02008bf849af..af5e606fbc1de 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1760856120, - "narHash": "sha256-yH1K/WDJpwIIw7e3wKdRgwHAZ38LXgcGE2Ecvk3I6GU=", + "lastModified": 1757745213, + "narHash": "sha256-P9VX/P2mN96MkFN8hwCYUQ+LV1bfH57UJ/pGwjd0Olc=", "owner": "nix-community", "repo": "fenix", - "rev": "b435bfccee71c6591dbce2fcfabe3e17e98c09fa", + "rev": "1458349a1bd55105f917e962dca4b328ac0a55e8", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1760872779, - "narHash": "sha256-c5C907Raf9eY8f1NUXYeju9aUDlm227s/V0OptEbypA=", + "lastModified": 1757746433, + "narHash": "sha256-fEvTiU4s9lWgW7mYEU/1QUPirgkn+odUBTaindgiziY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "63bdb5d90fa2fa11c42f9716ad1e23565613b07c", + "rev": "6d7ec06d6868ac6d94c371458fc2391ded9ff13d", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1760714286, - "narHash": "sha256-WOt9KquZ1BXjMcVyHpMeliqNRL6BfRvBHFGfRDriDx4=", + "lastModified": 1757362324, + "narHash": "sha256-/PAhxheUq4WBrW5i/JHzcCqK5fGWwLKdH6/Lu1tyS18=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "1e20331e42449dfc0b44bce84147a06772d045d7", + "rev": "9edc9cbe5d8e832b5864e09854fa94861697d2fd", "type": "github" }, "original": { From ea542a2f2b35e931c9b4b20eb8a806125e2b1e1a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:23:06 +0700 Subject: [PATCH 183/391] Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- flake.nix | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index 5caaab9934aec..8cf41ed16d3a3 100644 --- a/flake.nix +++ b/flake.nix @@ -19,8 +19,7 @@ lib = pkgs.lib; toolchain = fenix.packages.${system}.stable.toolchain; - in - { + in { default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ pkg-config @@ -29,16 +28,13 @@ # test dependencies solc vyper - dprint nodejs ]; + buildInputs = lib.optionals pkgs.stdenv.isDarwin + [ pkgs.darwin.apple_sdk.frameworks.AppKit ]; packages = with pkgs; [ rust-analyzer-unwrapped ]; - # Remove the hardening added by nix to fix jmalloc compilation error. - # More info: https://github.com/tikv/jemallocator/issues/108 - hardeningDisable = [ "fortify" ]; - # Environment variables RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; From e45aa66f3dd2ad5d28bf4f9910cb6d99b09c1822 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 14 Dec 2025 20:18:30 +0700 Subject: [PATCH 184/391] Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 68e6e2881ff7d..9d2f94aad00cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ resolver = "2" version = "1.2.1" edition = "2021" # Remember to update clippy.toml as well -rust-version = "1.86" +rust-version = "1.89.0" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" From c0d08b1943203e4b3bc3fe31805f49ddca6173f0 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:11:32 +0700 Subject: [PATCH 185/391] Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .config/nextest.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 2a9a4acd42460..70616d4e45e6b 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,14 +1,14 @@ [test-groups] +chisel-serial = { max-threads = 1 } [profile.default] retries = { backoff = "exponential", count = 2, delay = "5s", jitter = true } -slow-timeout = { period = "30s", terminate-after = 3 } +slow-timeout = { period = "1m", terminate-after = 3 } [[profile.default.overrides]] -filter = "test(/ext_integration/)" +filter = "test(/ext_integration|can_test_forge_std/)" slow-timeout = { period = "5m", terminate-after = 4 } -# Do not re-run so that `cargo cheats` is ran locally. [[profile.default.overrides]] filter = "package(foundry-cheatcodes-spec)" retries = 0 From 562ffb33383724e4723b3ce71eb330fe06b41033 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:32:57 +0700 Subject: [PATCH 186/391] Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- dprint.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dprint.json b/dprint.json index f665622619dd4..bf3ea43b41a11 100644 --- a/dprint.json +++ b/dprint.json @@ -3,10 +3,10 @@ "indentWidth": 2, "useTabs": false, "excludes": [ + "!dprint.json", "!npm/**/*.{json,md,toml}", "!npm/**/*.{js,cjs,mjs,d.ts,d.cts,d.mts,ts,tsx,jsx}", "**/_", - "dprint.json", "**/abi", "**/build", "**/target", @@ -25,8 +25,8 @@ ], "plugins": [ "https://plugins.dprint.dev/toml-0.7.0.wasm", - "https://plugins.dprint.dev/json-0.20.0.wasm", - "https://plugins.dprint.dev/markdown-0.19.0.wasm", + "https://plugins.dprint.dev/json-0.21.0.wasm", + "https://plugins.dprint.dev/markdown-0.20.0.wasm", "https://plugins.dprint.dev/dockerfile-0.3.3.wasm", "https://plugins.dprint.dev/typescript-0.95.11.wasm", "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm" @@ -57,4 +57,4 @@ "exportDeclaration.sortTypeOnlyExports": "none", "importDeclaration.sortTypeOnlyImports": "none" } -} \ No newline at end of file +} From e8b5cb61e3110121163ea24933796caca5a98299 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:17:24 +0700 Subject: [PATCH 187/391] Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/apisec-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml index 166d2f8ee0235..e716760284792 100644 --- a/.github/workflows/apisec-scan.yml +++ b/.github/workflows/apisec-scan.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Run APIsec scan uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea From d72eca45cf6f58fd395089c9b3759b4364fc56c6 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:19:05 +0700 Subject: [PATCH 188/391] Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- counter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/counter/README.md b/counter/README.md index 9265b4558406a..679a7f4518035 100644 --- a/counter/README.md +++ b/counter/README.md @@ -7,7 +7,7 @@ Foundry consists of: - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. ## Documentation From f4ebaa7ee344d1bb6aeefade0e9b1512bc4869b9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:19:58 +0700 Subject: [PATCH 189/391] Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 43a59e29b9c29..53a505774ac88 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -31,6 +31,7 @@ If applicable, add screenshots to help explain your problem. **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] - Browser [e.g. stock browser, safari] - Version [e.g. 22] From d784ebfa40bbae32aaaf7e212c88209b3b83bfc9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:24:44 +0700 Subject: [PATCH 190/391] Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .circleci/{ci.yml => cargo.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .circleci/{ci.yml => cargo.yml} (100%) diff --git a/.circleci/ci.yml b/.circleci/cargo.yml similarity index 100% rename from .circleci/ci.yml rename to .circleci/cargo.yml From a531ef8fa32491e1b4ca0571cd7095db5fb40b30 Mon Sep 17 00:00:00 2001 From: Gengar Date: Wed, 24 Dec 2025 16:21:10 +0200 Subject: [PATCH 191/391] Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --- crates/verify/src/etherscan/standard_json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 8a38485e48922..0471e3e42888e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -56,7 +56,7 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { let sources = Source::read_all_from(path, &["vy", "vyi"])?; let input = VyperInput::new( sources, - context.clone().compiler_settings.vyper, + context.compiler_settings.vyper.clone(), &context.compiler_version, ); From d1d564ff1c9fbe1d5cb27c5ee0fcfd33b09650e5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 25 Dec 2025 00:52:27 +0700 Subject: [PATCH 192/391] Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e0476fddba507..ad53a8e498202 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,32 +1,26 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/configuration-reference + version: 2.1 -# -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job +executors: + my-custom-executor: docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:2024.06 - # - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor steps: - # Checkout the code as the first step. - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" -# -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows + - run: | + # echo Hello, World! + workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. + my-custom-workflow: jobs: - - say-hello - + - web3-defi-game-project- From cb8ddcd20e9ba21fcca10bc9458eab382437ad42 Mon Sep 17 00:00:00 2001 From: tskoyo Date: Thu, 25 Dec 2025 21:18:00 +0100 Subject: [PATCH 193/391] EIP-4788 implementation --- crates/cast/src/cmd/run.rs | 23 ++++++++++++++++++++-- crates/evm/evm/src/executors/trace.rs | 28 ++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 54894fc10b7ea..51a08274ee7cb 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -2,7 +2,7 @@ use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_chan use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ - Address, Bytes, U256, + Address, Bytes, FixedBytes, U256, map::{AddressSet, HashMap}, }; use alloy_provider::Provider; @@ -31,7 +31,7 @@ use foundry_evm::{ utils::configure_tx_env, }; use futures::TryFutureExt; -use revm::DatabaseRef; +use revm::{DatabaseRef, primitives::hardfork::SpecId}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -183,6 +183,8 @@ impl RunArgs { env.evm_env.cfg_env.limit_contract_code_size = None; env.evm_env.block_env.number = U256::from(tx_block_number); + let mut parent_beacon_block_root: FixedBytes<32> = FixedBytes::default(); + if let Some(block) = &block { env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); env.evm_env.block_env.beneficiary = block.header.beneficiary; @@ -191,6 +193,16 @@ impl RunArgs { env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); env.evm_env.block_env.gas_limit = block.header.gas_limit; + if env.evm_env.cfg_env.spec >= SpecId::CANCUN { + if let Some(beacon_root) = block.header.parent_beacon_block_root { + parent_beacon_block_root = beacon_root; + } else { + return Err(eyre::eyre!( + "ParentBeaconBlockRoot is missing for Cancun or later blocks" + )); + } + } + // TODO: we need a smarter way to map the block to the corresponding evm_version for // commonly used chains if evm_version.is_none() { @@ -214,6 +226,7 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); + let mut executor = TracingExecutor::new( env.clone(), fork, @@ -223,6 +236,12 @@ impl RunArgs { create2_deployer, None, )?; + + if parent_beacon_block_root != FixedBytes::default() { + let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().unwrap_or(0); + executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; + } + let mut env = Env::new_with_spec_id( env.evm_env.cfg_env.clone(), env.evm_env.block_env.clone(), diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index d951dde0d6663..319e2ab495ca6 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -2,7 +2,7 @@ use crate::{ Env, executors::{Executor, ExecutorBuilder}, }; -use alloy_primitives::{Address, U256, map::HashMap}; +use alloy_primitives::{Address, FixedBytes, U256, address, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; use foundry_compilers::artifacts::EvmVersion; @@ -92,6 +92,32 @@ impl TracingExecutor { let chain = env.tx.chain_id.unwrap().into(); Ok((env, fork, chain, networks)) } + + /// Processes the beacon block root by storing it in the appropriate storage slots. + pub fn process_beacon_block_root( + &mut self, + block_timestamp: u64, + beacon_root: FixedBytes<32>, + ) -> eyre::Result<()> { + const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); + const HISTORY_BUFFER_LENGTH: u64 = 8191; + + let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; + let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; + + let timestamp_slot = U256::from(timestamp_index); + let root_slot = U256::from(root_index); + + self.set_storage_slot(BEACON_ROOTS_ADDRESS, timestamp_slot, U256::from(block_timestamp))?; + + self.set_storage_slot( + BEACON_ROOTS_ADDRESS, + root_slot, + U256::from_be_bytes(beacon_root.into()), + )?; + + Ok(()) + } } impl Deref for TracingExecutor { From ee13505c8ca6dc6399c082c07cc075c3330c03a0 Mon Sep 17 00:00:00 2001 From: tskoyo Date: Sat, 27 Dec 2025 14:55:45 +0100 Subject: [PATCH 194/391] formatting --- crates/cast/src/cmd/run.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 51a08274ee7cb..6152f5b6978a6 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -226,7 +226,6 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::new( env.clone(), fork, From fdad166d8ca338d6a891d42c6c7dc153bf1d9c77 Mon Sep 17 00:00:00 2001 From: Aganis Date: Tue, 30 Dec 2025 05:33:33 +0800 Subject: [PATCH 195/391] Remove duplicate logic in TxSigner::address() implementations --- crates/wallets/src/signer.rs | 2 +- crates/wallets/src/wallet_browser/signer.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wallets/src/signer.rs b/crates/wallets/src/signer.rs index 5a5c77c676587..8d50c03d1a90b 100644 --- a/crates/wallets/src/signer.rs +++ b/crates/wallets/src/signer.rs @@ -322,7 +322,7 @@ impl Signer for WalletSigner { #[async_trait] impl TxSigner for WalletSigner { fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) + Signer::address(self) } async fn sign_transaction( diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 1e3df775e2598..78cf166a35ad0 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -177,7 +177,7 @@ impl Signer for BrowserSigner { #[async_trait] impl TxSigner for BrowserSigner { fn address(&self) -> Address { - self.address + Signer::address(self) } async fn sign_transaction( From 7214be116217e999bddb05a6163a0b255c279a68 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Sun, 28 Dec 2025 20:33:12 +0700 Subject: [PATCH 196/391] fix: use network-specific BaseFeeParams for Optimism in Anvil --- Cargo.lock | 2 + crates/anvil/src/config.rs | 6 +- crates/anvil/src/eth/backend/mem/mod.rs | 3 +- crates/anvil/src/eth/fees.rs | 25 +++++--- crates/anvil/tests/it/optimism.rs | 80 ++++++++++++++++++++++++- crates/evm/networks/Cargo.toml | 2 + crates/evm/networks/src/lib.rs | 19 ++++++ 7 files changed, 125 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f043ac4bd3b5..2a36076e9880b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4974,7 +4974,9 @@ name = "foundry-evm-networks" version = "1.5.1" dependencies = [ "alloy-chains", + "alloy-eips", "alloy-evm", + "alloy-op-hardforks", "alloy-primitives", "clap", "revm", diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index c0ef7c922ec9d..59e79bfe3a989 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -16,7 +16,7 @@ use crate::{ }; use alloy_chains::Chain; use alloy_consensus::BlockHeader; -use alloy_eips::eip7840::BlobParams; +use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_evm::EvmEnv; use alloy_genesis::Genesis; use alloy_network::{AnyNetwork, TransactionResponse}; @@ -1094,6 +1094,9 @@ impl NodeConfig { self.networks, ); + let base_fee_params: BaseFeeParams = + self.networks.base_fee_params(self.get_genesis_timestamp()); + let fees = FeeManager::new( spec_id, self.get_base_fee(), @@ -1101,6 +1104,7 @@ impl NodeConfig { self.get_gas_price(), self.get_blob_excess_gas_and_price(), self.get_blob_params(), + base_fee_params, ); let (db, fork): (Arc>>, Option) = diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index adc56fe33a711..c46669e439df7 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -41,7 +41,6 @@ use alloy_consensus::{ use alloy_eip5792::{Capabilities, DelegationCapability}; use alloy_eips::{ BlockNumHash, Encodable2718, - eip1559::BaseFeeParams, eip4844::{BlobTransactionSidecar, kzg_to_versioned_hash}, eip7840::BlobParams, eip7910::SystemContract, @@ -1857,7 +1856,7 @@ impl Backend { block_env.basefee = simulated_block .inner .header - .next_block_base_fee(BaseFeeParams::ethereum()) + .next_block_base_fee(self.fees.base_fee_params()) .unwrap_or_default(); block_res.push(simulated_block); diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index e34b7c6eb6a24..0e4c6dffef57f 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -36,10 +36,6 @@ pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; /// Minimum suggested priority fee pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128; -pub fn default_elasticity() -> f64 { - 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64 -} - /// Stores the fee related information #[derive(Clone, Debug)] pub struct FeeManager { @@ -62,6 +58,8 @@ pub struct FeeManager { /// This will be constant value unless changed manually gas_price: Arc>, elasticity: Arc>, + /// Network-specific base fee params for EIP-1559 calculations + base_fee_params: BaseFeeParams, } impl FeeManager { @@ -72,7 +70,9 @@ impl FeeManager { gas_price: u128, blob_excess_gas_and_price: BlobExcessGasAndPrice, blob_params: BlobParams, + base_fee_params: BaseFeeParams, ) -> Self { + let elasticity = 1f64 / base_fee_params.elasticity_multiplier as f64; Self { spec_id, blob_params: Arc::new(RwLock::new(blob_params)), @@ -80,10 +80,16 @@ impl FeeManager { is_min_priority_fee_enforced, gas_price: Arc::new(RwLock::new(gas_price)), blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), - elasticity: Arc::new(RwLock::new(default_elasticity())), + elasticity: Arc::new(RwLock::new(elasticity)), + base_fee_params, } } + /// Returns the base fee params used for EIP-1559 calculations + pub fn base_fee_params(&self) -> BaseFeeParams { + self.base_fee_params + } + pub fn elasticity(&self) -> f64 { *self.elasticity.read() } @@ -156,7 +162,7 @@ impl FeeManager { if self.base_fee() == 0 { return 0; } - calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas) + calc_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas, self.base_fee_params) } /// Calculates the next block blob base fee. @@ -185,7 +191,12 @@ impl FeeManager { } } -/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec +/// Calculate base fee for next block using Ethereum mainnet parameters. +/// +/// See [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec. +/// +/// Note: This uses Ethereum's base fee params. For network-specific calculations +/// (e.g., Optimism), use [`FeeManager::get_next_block_base_fee_per_gas`] instead. pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) } diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 1dbef5eb1e5fc..3eb3fa341a30f 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -5,9 +5,9 @@ use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, Bloom, TxHash, TxKind, U256, b256}; use alloy_provider::Provider; -use alloy_rpc_types::TransactionRequest; +use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{NodeConfig, spawn}; +use anvil::{NodeConfig, eth::fees::INITIAL_BASE_FEE, spawn}; use foundry_evm_networks::NetworkConfigs; use op_alloy_consensus::TxDeposit; use op_alloy_rpc_types::OpTransactionFields; @@ -270,3 +270,79 @@ fn preserves_op_fields_in_convert_to_anvil_receipt() { assert_eq!(got, Some(expected), "field `{key}` mismatch"); } } + +const GAS_TRANSFER: u64 = 21_000; + +/// Test that Optimism uses Canyon base fee params instead of Ethereum params. +/// +/// Optimism Canyon uses different EIP-1559 parameters: +/// - elasticity_multiplier: 6 (vs Ethereum's 2) +/// - base_fee_max_change_denominator: 250 (vs Ethereum's 8) +/// +/// This means with a full block: +/// - Ethereum: base_fee increases by base_fee * 1 / 8 = 12.5% +/// - Optimism: base_fee increases by base_fee * 5 / 250 = 2% +#[tokio::test(flavor = "multi_thread")] +async fn test_optimism_base_fee_params() { + // Spawn an Optimism node with a gas limit equal to one transfer (full block scenario) + let (_api, handle) = spawn( + NodeConfig::test() + .with_networks(NetworkConfigs::with_optimism()) + .with_base_fee(Some(INITIAL_BASE_FEE)) + .with_gas_limit(Some(GAS_TRANSFER)), + ) + .await; + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + // Send first transaction to fill the block + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + // Send second transaction to fill the next block + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + assert!(next_base_fee > base_fee, "base fee should increase with full block"); + + // Optimism Canyon formula: base_fee * (elasticity - 1) / denominator = base_fee * 5 / 250 + // = INITIAL_BASE_FEE * 5 / 250 = 1_000_000_000 * 5 / 250 = 20_000_000 + // + // Note: Ethereum would be INITIAL_BASE_FEE + 125_000_000 (12.5% increase) + let expected_op_increase = INITIAL_BASE_FEE * 5 / 250; // 2% increase = 20_000_000 + assert_eq!( + next_base_fee, + INITIAL_BASE_FEE + expected_op_increase, + "Optimism should use Canyon base fee params (2% max increase), not Ethereum's (12.5%)" + ); + + // Explicitly verify it's NOT using Ethereum params (which would give 12.5% increase) + let ethereum_increase = INITIAL_BASE_FEE / 8; // 125_000_000 + assert_ne!( + next_base_fee, + INITIAL_BASE_FEE + ethereum_increase, + "Should not be using Ethereum base fee params" + ); +} diff --git a/crates/evm/networks/Cargo.toml b/crates/evm/networks/Cargo.toml index b3bc2701ed3bb..934458a42165b 100644 --- a/crates/evm/networks/Cargo.toml +++ b/crates/evm/networks/Cargo.toml @@ -15,7 +15,9 @@ workspace = true [dependencies] alloy-chains.workspace = true +alloy-eips.workspace = true alloy-evm.workspace = true +alloy-op-hardforks.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs index 3f327e15617a5..8e64589bb71ad 100644 --- a/crates/evm/networks/src/lib.rs +++ b/crates/evm/networks/src/lib.rs @@ -9,7 +9,9 @@ use alloy_chains::{ NamedChain, NamedChain::{Chiado, Gnosis, Moonbase, Moonbeam, MoonbeamDev, Moonriver, Rsk, RskTestnet}, }; +use alloy_eips::eip1559::BaseFeeParams; use alloy_evm::precompiles::PrecompilesMap; +use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; use alloy_primitives::{Address, map::AddressHashMap}; use clap::Parser; use serde::{Deserialize, Serialize}; @@ -47,6 +49,23 @@ impl NetworkConfigs { self.optimism } + /// Returns the base fee parameters for the configured network. + /// + /// For Optimism networks, returns Canyon parameters if the Canyon hardfork is active + /// at the given timestamp, otherwise returns pre-Canyon parameters. + pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + if self.is_optimism() { + let op_hardforks = OpChainHardforks::op_mainnet(); + if op_hardforks.is_canyon_active_at_timestamp(timestamp) { + BaseFeeParams::optimism_canyon() + } else { + BaseFeeParams::optimism() + } + } else { + BaseFeeParams::ethereum() + } + } + pub fn bypass_prevrandao(&self, chain_id: u64) -> bool { if let Ok( Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet | Gnosis | Chiado, From 17250c474507d3a960742dcec147d8ff212a379b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:48:07 +0700 Subject: [PATCH 197/391] Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar --- .circleci/config.yml | 32 ------------------- benches/src/lib.rs | 16 ++++++++-- crates/evm/evm/src/executors/corpus.rs | 33 +++++++++++++++----- crates/test-utils/src/script.rs | 12 +++++++ crates/verify/src/etherscan/standard_json.rs | 2 +- 5 files changed, 52 insertions(+), 43 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d02c693397345..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 -# -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - # - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" -# -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello - diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 89629f6898628..eeb3cbeeafe11 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -132,10 +132,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index eb5431e3ba57a..92eb4452ce802 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -190,6 +190,15 @@ impl CorpusManager { foundry_common::fs::create_dir_all(corpus_dir)?; } + // Canonicalize the corpus_dir to a trusted absolute path + let canonical_corpus_dir = match corpus_dir.canonicalize() { + Ok(dir) => dir, + Err(e) => { + trace!(target: "corpus", "failed to canonicalize corpus_dir {}: {e}", corpus_dir.display()); + return Err(e.into()); + } + }; + let can_replay_tx = |tx: &BasicTxDetails| -> bool { fuzzed_contracts.is_some_and(|contracts| contracts.targets.lock().can_replay(tx)) || fuzzed_function.is_some_and(|function| { @@ -202,21 +211,31 @@ impl CorpusManager { 'corpus_replay: for entry in std::fs::read_dir(corpus_dir)? { let path = entry?.path(); - if path.is_file() - && let Some(name) = path.file_name().and_then(|s| s.to_str()) + // Canonicalize the candidate path, skip if it cannot be canonicalized (e.g. broken symlink) + let canonical_path = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, + }; + // Ensure file is inside the corpus directory (prevents path traversal/symlink escape) + if !canonical_path.starts_with(&canonical_corpus_dir) { + trace!(target: "corpus", "Skipping file outside corpus_dir: {}", path.display()); + continue; + } + if canonical_path.is_file() + && let Some(name) = canonical_path.file_name().and_then(|s| s.to_str()) && name.contains(METADATA_SUFFIX) { // Ignore metadata files continue; } - let read_corpus_result = match path.extension().and_then(|ext| ext.to_str()) { - Some("gz") => foundry_common::fs::read_json_gzip_file::>(&path), - _ => foundry_common::fs::read_json_file::>(&path), + let read_corpus_result = match canonical_path.extension().and_then(|ext| ext.to_str()) { + Some("gz") => foundry_common::fs::read_json_gzip_file::>(&canonical_path), + _ => foundry_common::fs::read_json_file::>(&canonical_path), }; let Ok(tx_seq) = read_corpus_result else { - trace!(target: "corpus", "failed to load corpus from {}", path.display()); + trace!(target: "corpus", "failed to load corpus from {}", canonical_path.display()); continue; }; @@ -257,7 +276,7 @@ impl CorpusManager { ); // Populate in memory corpus with the sequence from corpus file. - in_memory_corpus.push(CorpusEntry::new(tx_seq, path)?); + in_memory_corpus.push(CorpusEntry::new(tx_seq, canonical_path.clone())?); } } diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 45dbbbebdd73a..626ce94448a4a 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -123,6 +123,18 @@ impl ScriptTester { for entry in fs::read_dir(&from_dir)? { let file = &entry?.path(); let name = file.file_name().unwrap(); + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Optionally verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } fs::copy(file, to_dir.join(name))?; } Ok(()) diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 8a38485e48922..0471e3e42888e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -56,7 +56,7 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { let sources = Source::read_all_from(path, &["vy", "vyi"])?; let input = VyperInput::new( sources, - context.clone().compiler_settings.vyper, + context.compiler_settings.vyper.clone(), &context.compiler_version, ); From 8b29d48133545a0f5c32da2fdc48bb54ac2eb309 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:08:43 +0700 Subject: [PATCH 198/391] merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .circleci/config.yml | 31 +++++++++++ Cargo.lock | 8 +-- Cargo.toml | 4 +- crates/anvil/tests/it/anvil_api.rs | 2 +- crates/config/src/compilation.rs | 33 +++++++++++- crates/config/src/lib.rs | 75 +++++++++++++++++--------- crates/evm/core/src/backend/mod.rs | 4 +- crates/forge/tests/cli/test_cmd/mod.rs | 27 ++++++++++ 8 files changed, 148 insertions(+), 36 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..d5d401c51893c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello diff --git a/Cargo.lock b/Cargo.lock index 2f043ac4bd3b5..f19b33e2859ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9a33550fc21fd77a3f8b63e99969d17660eec8dcc50a95a80f7c9964f7680b" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96fb2fce4024ada5b2c11d4076acf778a0d3e4f011c6dfd2ffce6d0fcf84ee9" +checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", "alloy-hardforks", diff --git a/Cargo.toml b/Cargo.toml index dbeac987c0358..9f9fdc0498bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,8 +268,8 @@ alloy-transport = { version = "1.1.3", default-features = false } alloy-transport-http = { version = "1.1.3", default-features = false } alloy-transport-ipc = { version = "1.1.3", default-features = false } alloy-transport-ws = { version = "1.1.3", default-features = false } -alloy-hardforks = { version = "0.4.5", default-features = false } -alloy-op-hardforks = { version = "0.4.5", default-features = false } +alloy-hardforks = { version = "0.4.7", default-features = false } +alloy-op-hardforks = { version = "0.4.7", default-features = false } ## alloy-core alloy-dyn-abi = "1.5.1" diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index fc89206db81fd..e675001d7a480 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -440,7 +440,7 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); - let hard_fork: &str = SpecId::PRAGUE.into(); + let hard_fork: &str = SpecId::OSAKA.into(); let expected_node_info = NodeInfo { current_block_number: 0_u64, diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs index 94335dd26a8ef..46f0f2158a0f1 100644 --- a/crates/config/src/compilation.rs +++ b/crates/config/src/compilation.rs @@ -7,7 +7,7 @@ use foundry_compilers::{ solc::{Restriction, SolcRestrictions}, }; use semver::VersionReq; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; /// Keeps possible overrides for default settings which users may configure to construct additional /// settings profile. @@ -69,6 +69,7 @@ pub enum RestrictionsError { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CompilationRestrictions { pub paths: GlobMatcher, + #[serde(default, deserialize_with = "deserialize_version_req")] pub version: Option, pub via_ir: Option, pub bytecode_hash: Option, @@ -85,6 +86,36 @@ pub struct CompilationRestrictions { pub max_evm_version: Option, } +/// Custom deserializer for version field that rejects ambiguous bare version numbers. +fn deserialize_version_req<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt_string: Option = Option::deserialize(deserializer)?; + let Some(opt_string) = opt_string else { + return Ok(None); + }; + + let version = opt_string.trim(); + // Reject bare versions like "0.8.11" that lack an operator prefix + if version.chars().next().is_some_and(|c| c.is_ascii_digit()) { + return Err(serde::de::Error::custom(format!( + "Invalid version format '{version}' in compilation_restrictions. \ + Bare version numbers are ambiguous and default to caret requirements (e.g. '^{version}'). \ + Use an explicit constraint such as '={version}' for an exact version or '>={version}' for a minimum version." + ))); + } + + let req = VersionReq::parse(version).map_err(|e| { + serde::de::Error::custom(format!( + "Invalid version requirement '{version}': {e}. \ + Examples: '=0.8.11' (exact), '>=0.8.11' (minimum), '>=0.8.11 <0.9.0' (range)." + )) + })?; + + Ok(Some(req)) +} + impl TryFrom for RestrictionsWithVersion { type Error = RestrictionsError; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 50faf02cbfcb0..d54cb0aac63f5 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -774,40 +774,28 @@ impl Config { } fn from_figment(figment: Figment) -> Result { - // Helper to add a profile only if it's not already present - fn add_profile(profiles: &mut Vec, profile: &Profile) { - if !profiles.contains(profile) { - profiles.push(profile.clone()); - } - } - let mut config = figment.extract::().map_err(ExtractConfigError::new)?; - let active_profile = figment.profile().clone(); + config.profile = figment.profile().clone(); - // Collect profiles from the profile section. // The `"profile"` profile contains all the profiles as keys. + let mut add_profile = |profile: &Profile| { + if !config.profiles.contains(profile) { + config.profiles.push(profile.clone()); + } + }; let figment = figment.select(Self::PROFILE_SECTION); if let Ok(data) = figment.data() && let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) { for profile in profiles.keys() { - add_profile(&mut config.profiles, &Profile::new(profile)); + add_profile(&Profile::new(profile)); } } - - // Always include the default profile - add_profile(&mut config.profiles, &Self::DEFAULT_PROFILE); - - // Ensure the active profile exists. - if config.profiles.contains(&active_profile) { - config.profile = active_profile; - } else { - return Err(ExtractConfigError::new(Error::from(format!( - "Profile {active_profile} does not exist" - )))); - } + add_profile(&Self::DEFAULT_PROFILE); + add_profile(&config.profile); config.normalize_optimizer_settings(); + Ok(config) } @@ -6427,18 +6415,53 @@ mod tests { } #[test] - fn fails_on_unknown_profile() { + fn fails_on_ambiguous_version_in_compilation_restrictions() { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", r#" [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "0.8.11" "#, )?; - jail.set_env("FOUNDRY_PROFILE", "foo"); - let err = Config::load().expect_err("expected unknown profile to fail"); - assert!(err.to_string().contains("Profile foo does not exist")); + let err = Config::load().expect_err("expected bare version to fail"); + let err_msg = err.to_string(); + assert!( + err_msg.contains("Invalid version format '0.8.11'") + && err_msg.contains("Bare version numbers are ambiguous"), + "Expected error about ambiguous version, got: {err_msg}" + ); + + Ok(()) + }); + } + + #[test] + fn accepts_explicit_version_requirements() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "=0.8.11" + + [[profile.default.compilation_restrictions]] + paths = "test/*.sol" + version = ">=0.8.11" + "#, + )?; + + let config = Config::load().expect("should accept explicit version requirements"); + assert_eq!(config.compilation_restrictions.len(), 2); Ok(()) }); diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 26e56e7bf883f..6429ceb3db23f 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -6,7 +6,7 @@ use crate::{ evm::new_evm_with_inspector, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, - utils::{configure_tx_env, configure_tx_req_env, get_blob_base_fee_update_fraction_by_spec_id}, + utils::{configure_tx_env, configure_tx_req_env, get_blob_base_fee_update_fraction}, }; use alloy_consensus::Typed2718; use alloy_evm::Evm; @@ -1954,7 +1954,7 @@ fn update_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { if let Some(excess_blob_gas) = block.header.excess_blob_gas { env.block.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( excess_blob_gas, - get_blob_base_fee_update_fraction_by_spec_id(env.cfg.spec), + get_blob_base_fee_update_fraction(env.cfg.chain_id, block.header.timestamp), )); } } diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 9666cf4d7e69d..0abffff57c4a8 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -678,6 +678,33 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +// Validates BPO1 blob gas price calculation during fork transaction replay. +// Block 24127158 has a blob tx at index 0, target tx at index 1. +// Forking at the target tx replays the blob tx with correct BPO1 blob base fee calculation. +forgetest_init!(fork_tx_replay_bpo1_blob_base_fee, |prj, cmd| { + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "BlobFork.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +contract BlobForkTest is Test { + function test_fork_with_blob_replay() public { + // Fork at tx index 1 in block 24127158, which replays blob tx at index 0 + bytes32 txHash = 0xa0f349b16e0f338ee760a9954ff5dbf2a402cff3320f3fe2c3755aee8babc335; + vm.createSelectFork("", txHash); + // If we get here, blob tx replay succeeded + assertTrue(true); + } +} + "# + .replace("", &endpoint), + ); + + cmd.args(["test", "-vvvv"]).assert_success(); +}); + // https://github.com/foundry-rs/foundry/issues/6579 forgetest_init!(include_custom_types_in_traces, |prj, cmd| { prj.add_test( From 49754a8131b5ac24511cd478acb414ef279f085e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:22:25 +0700 Subject: [PATCH 199/391] Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/docker-image.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index e0c9c518e8748..0000000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From a859609cb8e0b2112adedec72aa758b1c3183fcf Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:42:42 +0700 Subject: [PATCH 200/391] Gamefi defi (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .github/workflows/bump-forge-std.yml | 2 +- .github/workflows/ci.yml | 12 +- .github/workflows/docs.yml | 2 +- .github/workflows/nix.yml | 4 +- .github/workflows/test.yml | 5 +- Cargo.lock | 312 ++++++++++++----------- Cargo.toml | 4 +- crates/anvil/core/src/eth/mod.rs | 4 +- crates/anvil/src/eth/api.rs | 4 +- crates/anvil/src/eth/backend/fork.rs | 4 +- crates/anvil/src/eth/backend/mem/mod.rs | 12 +- crates/anvil/src/tasks/mod.rs | 4 +- crates/anvil/tests/it/anvil_api.rs | 2 +- crates/anvil/tests/it/api.rs | 4 +- crates/cheatcodes/assets/cheatcodes.json | 8 +- crates/cheatcodes/spec/src/vm.rs | 4 +- crates/chisel/src/dispatcher.rs | 2 - crates/chisel/src/executor.rs | 6 +- crates/chisel/tests/it/repl/mod.rs | 62 +++-- crates/config/src/compilation.rs | 33 ++- crates/config/src/lib.rs | 53 ++++ crates/evm/core/src/backend/mod.rs | 4 +- crates/forge/tests/cli/test_cmd/mod.rs | 27 ++ crates/linking/src/lib.rs | 1 + crates/test-utils/src/rpc.rs | 7 + deny.toml | 2 + flake.lock | 18 +- testdata/default/repros/Issue2851.t.sol | 30 --- testdata/utils/Vm.sol | 4 +- 29 files changed, 372 insertions(+), 264 deletions(-) delete mode 100644 testdata/default/repros/Issue2851.t.sol diff --git a/.github/workflows/bump-forge-std.yml b/.github/workflows/bump-forge-std.yml index 2fb3e8a9a536f..00109defa89b1 100644 --- a/.github/workflows/bump-forge-std.yml +++ b/.github/workflows/bump-forge-std.yml @@ -23,7 +23,7 @@ jobs: - name: Fetch and update forge-std tag run: curl 'https://api.github.com/repos/foundry-rs/forge-std/tags' | jq '.[0].commit.sha' -jr > testdata/forge-std-rev - name: Create pull request - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7 with: commit-message: "chore: bump forge-std version used for tests" title: "chore(tests): bump forge-std version" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 578a95c4738cc..ac58f9560d0f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - run: cargo test --workspace --doc typos: @@ -58,7 +58,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1 + - uses: crate-ci/typos@1a319b54cc9e3b333fed6a5c88ba1a90324da514 # v1 shellcheck: runs-on: depot-ubuntu-latest @@ -88,7 +88,7 @@ jobs: components: clippy - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - run: cargo clippy --workspace --all-targets --all-features env: RUSTFLAGS: -Dwarnings @@ -122,7 +122,7 @@ jobs: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - name: forge fmt shell: bash run: ./.github/scripts/format.sh --check @@ -140,11 +140,11 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2 + - uses: taiki-e/install-action@4c6723ec9c638cccae824b8957c5085b695c8085 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - run: cargo hack check deny: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 04a99b63626ee..2ace7a6c6fcf1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: toolchain: nightly - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - name: Build documentation run: cargo doc --workspace --all-features --no-deps --document-private-items env: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 2c51ee2d99fbd..7082dc5dff403 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@95732e95d70db3ba1e0adc26a63c5e0375aba78c # v3 + - uses: DeterminateSystems/determinate-nix-action@1d699fc25db3f9e079cd2f168ca007a4183389be # v3 - uses: actions/checkout@v6 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@95732e95d70db3ba1e0adc26a63c5e0375aba78c # v3 + - uses: DeterminateSystems/determinate-nix-action@1d699fc25db3f9e079cd2f168ca007a4183389be # v3 - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d2e9ce09250e..79f4dfc4431db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2 + - uses: taiki-e/install-action@4c6723ec9c638cccae824b8957c5085b695c8085 # v2 with: tool: nextest @@ -102,7 +102,7 @@ jobs: restore-keys: | ${{ runner.os }}-foundry-${{ matrix.name }}- - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - name: Setup Git config run: | git config --global user.name "GitHub Actions Bot" @@ -115,4 +115,5 @@ jobs: WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} run: cargo nextest run ${{ matrix.flags }} diff --git a/Cargo.lock b/Cargo.lock index 0db46ec2c09e8..f19b33e2859ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471" +checksum = "b163ff4acf0eac29af05a911397cc418a76e153467b859398adc26cb9335a611" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" +checksum = "f3dcd2b4e208ce5477de90ccdcbd4bde2c8fb06af49a443974e92bb8f2c5e93f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" +checksum = "ee5655f234985f5ab1e31bef7e02ed11f0a899468cf3300e061e1b96e9e11de0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d39c80ffc806f27a76ed42f3351a455f3dc4f81d6ff92c8aad2cf36b7d3a34" +checksum = "7f01b6d8e5b4f3222aaf7f18613a7292e2fbc9163fe120649cd1b078ca534349" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d48a9101f4a67c22fae57489f1ddf3057b8ab4a368d8eac3be088b6e9d9c9d9" +checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb56700a502913fa9d40367e8f8c77720b043447c7763305f4ab582d713e36d" +checksum = "cdb31eba85ca1ac2f9df9170ffda56b470816b899c8be577a5b4c6a830368682" dependencies = [ "alloy-primitives", "alloy-serde", @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" +checksum = "6847d641141b92a1557094aa6c236cbe49c06fb24144d4a21fe6acb970c15888" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a171486ee60b6f98e12451b1eddd1458bc21571f3829f75fcf12bb16ae48e59" +checksum = "9ddbb0d31df814ea588fc61034c3f3d21dc3678e65e26a50fff21fd35b4d0d3b" dependencies = [ "alloy-contract", "alloy-primitives", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba4b1be0988c11f0095a2380aa596e35533276b8fa6c9e06961bbfe0aebcac5" +checksum = "fe3192fca2eb0b0c4b122b3c2d8254496b88a4e810558dddd3ea2f30ad9469df" dependencies = [ "alloy-eips", "alloy-primitives", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9a33550fc21fd77a3f8b63e99969d17660eec8dcc50a95a80f7c9964f7680b" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9914c147bb9b25f440eca68a31dc29f5c22298bfa7754aa802965695384122b0" +checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" +checksum = "d4ab3330e491053e9608b2a315f147357bb8acb9377a988c1203f2e8e2b296c9" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" +checksum = "c1e22ff194b1e34b4defd1e257e3fe4dce0eee37451c7757a1510d6b23e7379a" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -357,9 +357,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" +checksum = "b8a6cbb9f431bdad294eebb5af9b293d6979e633bfe5468d1e87c1421a858265" dependencies = [ "alloy-consensus", "alloy-eips", @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96fb2fce4024ada5b2c11d4076acf778a0d3e4f011c6dfd2ffce6d0fcf84ee9" +checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db950a29746be9e2f2c6288c8bd7a6202a81f999ce109a2933d2379970ec0fa" +checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" dependencies = [ "alloy-rlp", "arbitrary", @@ -431,9 +431,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b710636d7126e08003b8217e24c09f0cca0b46d62f650a841736891b1ed1fc1" +checksum = "3f5dde1abc3d582e53d139904fcdd8b2103f0bd03e8f2acb4292edbbaeaa7e6e" dependencies = [ "alloy-chains", "alloy-consensus", @@ -476,9 +476,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd4c64eb250a18101d22ae622357c6b505e158e9165d4c7974d59082a600c5e" +checksum = "acbfe0a3c553a027f722185fb574124d205147fffb309cae52d0a2094f076887" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -520,9 +520,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0882e72d2c1c0c79dcf4ab60a67472d3f009a949f774d4c17d0bdb669cfde05" +checksum = "5a94bdef2710322c6770be08689fee0878c2ad75615b8fc40e05d7f3c9618c0b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cf1398cb33aacb139a960fa3d8cf8b1202079f320e77e952a0b95967bf7a9f" +checksum = "811a573c8080e1b492d488e6a240ec5dd7677d7167e91ce9cb4d0ec1fcac8027" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -562,9 +562,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ce4c24e416bd0f17fceeb2f26cd8668df08fe19e1dc02f9d41c3b8ed1e93e0" +checksum = "838ca94be532a929f27961851000ec8bbbaeb06e2a2bcca44fac7855a2fe0f6f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -574,9 +574,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" +checksum = "12df0b34551ca2eab8ec83b56cb709ee5da991737282180d354a659b907f00dc" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -585,9 +585,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16633087e23d8d75161c3a59aa183203637b817a5a8d2f662f612ccb6d129af0" +checksum = "32598a2443750a2e884c1b48efccaeeaae75e7eb4e0f13df9146b78107b4c301" dependencies = [ "alloy-eips", "alloy-primitives", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4936f579d9d10eae01772b2ab3497f9d568684f05f26f8175e12f9a1a2babc33" +checksum = "6c49a3a168a5bf18f1cf7ed5723a650aebe714edf7665b53dacf5707716733d0" dependencies = [ "alloy-primitives", "derive_more", @@ -613,9 +613,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c60bdce3be295924122732b7ecd0b2495ce4790bedc5370ca7019c08ad3f26e" +checksum = "ffe16cd1dea6089902ec609e04261a9ae6d11ec66005ba24c1f97f0eefbc0fa9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" +checksum = "b7f9f130511b8632686dfe6f9909b38d7ae4c68de3ce17d28991400646a39b25" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -654,9 +654,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef206a4b8d436fbb7cf2e6a61c692d11df78f9382becc3c9a283bd58e64f0583" +checksum = "cafe859944638c5d57d1a3a0034cdb5d07c98c37de8adce5508f28834acf958f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb5a795264a02222f9534435b8f40dcbd88de8e9d586647884aae24f389ebf2" +checksum = "afaa06544e36f223b99b1415a12911230fd527994f020736c3c7950d5080208e" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -680,9 +680,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" +checksum = "067b718d2e6ac1bb889341fcc7a250cfa49bcd3ba4f23923f1c1eb1f2b10cb7c" dependencies = [ "alloy-primitives", "serde", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" +checksum = "acff6b251740ef473932386d3b71657d3825daebf2217fb41a7ef676229225d4" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8784b7567d5cfdad7450c5c71ddbdc12b288b82ea559be7a5363c28f774210" +checksum = "a4111255269e2d96b7e064ffa98f94ebc51c8e18d43501a10808c316e6d5a4d6" dependencies = [ "alloy-consensus", "alloy-network", @@ -727,9 +727,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70059cb6f08c225be3840f8104573fd9342ca29117a735ea09ed8ee1be09fb9f" +checksum = "b3fe5d26c2e1144aa89d65a0f1d1faec4dbf4c3ea1f7375888274c6609de2234" dependencies = [ "alloy-consensus", "alloy-network", @@ -745,9 +745,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a54e01fb2228c993c9ef7735043bb22c88a84502628fae820d6469c3bebddb84" +checksum = "018fe424516f3bad2aefca5ee556a9c75301d68ab5377e296f85d33d6dc446c0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -765,9 +765,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cfe0be3ec5a8c1a46b2e5a7047ed41121d360d97f4405bb7c1c784880c86cb" +checksum = "c9129ef31975d987114c27c9930ee817cf3952355834d47f2fdf4596404507e8" dependencies = [ "alloy-consensus", "alloy-network", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119c750dd3d2158c57ab14711dca06cd6dc5db657bd16e6ab46ed11112187958" +checksum = "af231a5e9b07e54cd3142ad51bd07cbb95b4ccb4db991897a0ab9e7ed052286f" dependencies = [ "alloy-consensus", "alloy-network", @@ -802,9 +802,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd65beb3a838bff2e528e2ac4f0cf14f55893c4f0bc67c8339f2752b3d144022" +checksum = "1dae31a1578b3dedbc7c07aa5376d399adcf883caca7fdd56151b7618df72634" dependencies = [ "alloy-consensus", "alloy-network", @@ -818,9 +818,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b96d5f5890605ba9907ce1e2158e2701587631dc005bfa582cf92dd6f21147" +checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -832,9 +832,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8247b7cca5cde556e93f8b3882b01dbd272f527836049083d240c57bf7b4c15" +checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -851,9 +851,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd54f38512ac7bae10bbc38480eefb1b9b398ca2ce25db9cc0c048c6411c4f1" +checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" dependencies = [ "alloy-json-abi", "const-hex", @@ -869,9 +869,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b09815b44899564566d4d56613d14fa9a274b1043a021f00468568752f449" +checksum = "af67a0b0dcebe14244fc92002cd8d96ecbf65db4639d479f5fcd5805755a4c27" dependencies = [ "serde", "winnow", @@ -879,9 +879,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1038284171df8bfd48befc0c7b78f667a7e2be162f45f07bd1c378078ebe58" +checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -891,9 +891,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be98b07210d24acf5b793c99b759e9a696e4a2e67593aec0487ae3b3e1a2478c" +checksum = "bec1fb08ee484e615f24867c0b154fff5722bb00176102a16868c6532b7c3623" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -914,9 +914,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4198a1ee82e562cab85e7f3d5921aab725d9bd154b6ad5017f82df1695877c97" +checksum = "64b722073c76f2de7e118d546ee1921c50710f97feb32aed50db94cfa5b663e1" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -929,9 +929,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8db249779ebc20dc265920c7e706ed0d31dbde8627818d1cbde60919b875bb0" +checksum = "bdedcf401aab4b96d8b5e6638b79d04a6afb96c0bfcb50a2324fbadfe65c47b3" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -949,15 +949,14 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad2344a12398d7105e3722c9b7a7044ea837128e11d453604dec6e3731a86e2" +checksum = "942210908f0c56941097f5653a5f334546940e6fd9073495b257e52216469feb" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http 1.4.0", - "rustls", "serde_json", "tokio", "tokio-tungstenite 0.26.2", @@ -967,9 +966,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +checksum = "2b77b56af09ead281337d06b1d036c88e2dc8a2e45da512a532476dbee94912b" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -983,9 +982,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" +checksum = "04950a13cc4209d8e9b78f306e87782466bad8538c94324702d061ff03e211c9" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -1957,9 +1956,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.6" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fda37911905ea4d3141a01364bc5509a0f32ae3f3b22d6e330c0abfb62d247" +checksum = "a392db6c583ea4a912538afb86b7be7c5d8887d91604f50eb55c262ee1b4a5f5" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -2080,9 +2079,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -2652,9 +2651,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.50" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -3292,7 +3291,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -3556,18 +3555,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -3688,9 +3687,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dtoa-short" @@ -4107,7 +4106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -4149,9 +4148,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fixed-hash" @@ -4554,7 +4553,7 @@ version = "1.5.1" dependencies = [ "alloy-sol-types", "foundry-macros", - "schemars 1.1.0", + "schemars 1.2.0", "serde", "serde_json", ] @@ -6189,15 +6188,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" dependencies = [ "jiff-static", "log", @@ -6208,9 +6207,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" dependencies = [ "proc-macro2", "quote", @@ -6418,9 +6417,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -6440,9 +6439,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -7670,9 +7669,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -7812,9 +7811,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -8350,9 +8349,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -9049,9 +9048,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" +checksum = "7f5befb5191be3584a4edaf63435e8ff92ffff622e711ca7e77f8f8f365a9df8" dependencies = [ "alloy-rlp", "arbitrary", @@ -9167,9 +9166,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -9270,9 +9269,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "ryu-js" @@ -9330,9 +9329,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -9343,9 +9342,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" dependencies = [ "proc-macro2", "quote", @@ -9570,16 +9569,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "indexmap 2.12.1", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -9635,7 +9634,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -9750,10 +9749,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -10349,9 +10349,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b1d2e2059056b66fec4a6bb2b79511d5e8d76196ef49c38996f4b48db7662f" +checksum = "5f92d01b5de07eaf324f7fca61cc6bd3d82bbc1de5b6c963e6fe79e86f36580d" dependencies = [ "paste", "proc-macro2", @@ -10414,14 +10414,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -10476,7 +10476,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.60.2", ] @@ -11747,7 +11747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.1.2", + "rustix 1.1.3", "winsafe", ] @@ -12411,9 +12411,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "e6d6085d62852e35540689d1f97ad663e3971fc19cf5eceab364d62c646ea167" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index dbeac987c0358..9f9fdc0498bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,8 +268,8 @@ alloy-transport = { version = "1.1.3", default-features = false } alloy-transport-http = { version = "1.1.3", default-features = false } alloy-transport-ipc = { version = "1.1.3", default-features = false } alloy-transport-ws = { version = "1.1.3", default-features = false } -alloy-hardforks = { version = "0.4.5", default-features = false } -alloy-op-hardforks = { version = "0.4.5", default-features = false } +alloy-hardforks = { version = "0.4.7", default-features = false } +alloy-op-hardforks = { version = "0.4.7", default-features = false } ## alloy-core alloy-dyn-abi = "1.5.1" diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 34f44453998d7..43e0e8ecc7745 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -210,7 +210,7 @@ pub enum EthRequest { GetGenesisTime(()), #[serde(rename = "eth_getTransactionByBlockHashAndIndex")] - EthGetTransactionByBlockHashAndIndex(TxHash, Index), + EthGetTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getTransactionByBlockNumberAndIndex")] EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), @@ -219,7 +219,7 @@ pub enum EthRequest { EthGetRawTransactionByHash(TxHash), #[serde(rename = "eth_getRawTransactionByBlockHashAndIndex")] - EthGetRawTransactionByBlockHashAndIndex(TxHash, Index), + EthGetRawTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getRawTransactionByBlockNumberAndIndex")] EthGetRawTransactionByBlockNumberAndIndex(BlockNumber, Index), diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 095d1337a3076..b8c193fca084e 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -29,7 +29,7 @@ use crate::{ filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, }; -use alloy_consensus::{Account, Blob, Transaction, TxEip4844Variant, transaction::Recovered}; +use alloy_consensus::{Blob, Transaction, TrieAccount, TxEip4844Variant, transaction::Recovered}; use alloy_dyn_abi::TypedData; use alloy_eips::{ eip2718::Encodable2718, @@ -751,7 +751,7 @@ impl EthApi { &self, address: Address, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getAccount"); let block_request = self.block_request(block_number).await?; diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index e251d8f11de81..995206fbfcc89 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,7 +1,7 @@ //! Support for forking off another client use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; -use alloy_consensus::Account; +use alloy_consensus::TrieAccount; use alloy_eips::eip2930::AccessListResult; use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse}; use alloy_primitives::{ @@ -297,7 +297,7 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_account={:?}", address); self.provider().get_account(address).block_id(blocknumber.into()).await } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 6cf5b085f4837..adc56fe33a711 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -33,8 +33,8 @@ use crate::{ }; use alloy_chains::NamedChain; use alloy_consensus::{ - Account, Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait, - TxEnvelope, Typed2718, + Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait, + TrieAccount, TxEnvelope, Typed2718, proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, }; @@ -1981,7 +1981,8 @@ impl Backend { GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), GethDebugBuiltInTracerType::FourByteTracer | GethDebugBuiltInTracerType::MuxTracer - | GethDebugBuiltInTracerType::FlatCallTracer => { + | GethDebugBuiltInTracerType::FlatCallTracer + | GethDebugBuiltInTracerType::Erc7562Tracer => { Err(RpcError::invalid_params("unsupported tracer type").into()) } }, @@ -2569,7 +2570,7 @@ impl Backend { &self, address: Address, block_request: Option, - ) -> Result { + ) -> Result { self.with_database_at(block_request, |block_db, _| { let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; let account = db.get(&address).cloned().unwrap_or_default(); @@ -2577,7 +2578,7 @@ impl Backend { let code_hash = account.info.code_hash; let balance = account.info.balance; let nonce = account.info.nonce; - Ok(Account { balance, nonce, code_hash, storage_root }) + Ok(TrieAccount { balance, nonce, code_hash, storage_root }) }) .await? } @@ -2951,6 +2952,7 @@ impl Backend { } GethDebugBuiltInTracerType::NoopTracer | GethDebugBuiltInTracerType::MuxTracer + | GethDebugBuiltInTracerType::Erc7562Tracer | GethDebugBuiltInTracerType::FlatCallTracer => {} }, GethDebugTracerType::JsTracer(_code) => {} diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index deab3b29e38f3..2d73fc99d8364 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -63,7 +63,7 @@ impl TaskManager { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// - /// let provider = RootProvider::connect_builtin(endpoint).await.unwrap(); + /// let provider = RootProvider::connect(endpoint).await.unwrap(); /// /// handle.task_manager().spawn_reset_on_new_polled_blocks(provider, api); /// # } @@ -120,7 +120,7 @@ impl TaskManager { /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// - /// let provider = RootProvider::connect_builtin("ws://...").await.unwrap(); + /// let provider = RootProvider::connect("ws://...").await.unwrap(); /// /// handle.task_manager().spawn_reset_on_subscribed_blocks(provider, api); /// diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index fc89206db81fd..e675001d7a480 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -440,7 +440,7 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); - let hard_fork: &str = SpecId::PRAGUE.into(); + let hard_fork: &str = SpecId::OSAKA.into(); let expected_node_info = NodeInfo { current_block_number: 0_u64, diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index 979b40c86f0f8..41df263fec070 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -571,7 +571,7 @@ async fn test_fill_transaction_eip4844_blob_fee() { // EIP-4844 blob transaction with sidecar but no blob fee let mut tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); - tx_req.sidecar = Some(sidecar); + tx_req.sidecar = Some(sidecar.into()); tx_req.transaction_type = Some(3); // EIP-4844 let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); @@ -602,7 +602,7 @@ async fn test_fill_transaction_eip4844_preserves_blob_fee() { .with_from(from) .with_to(Address::random()) .with_max_fee_per_blob_gas(provided_blob_fee); - tx_req.sidecar = Some(sidecar); + tx_req.sidecar = Some(sidecar.into()); tx_req.transaction_type = Some(3); // EIP-4844 let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 8b6eeb049c9fd..2134b95ac571d 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -9994,9 +9994,9 @@ "func": { "id": "signCompact_0", "description": "Signs data with a `Wallet`.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", - "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs);", + "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", - "mutability": "", + "mutability": "pure", "signature": "signCompact((address,uint256,uint256,uint256),bytes32)", "selector": "0x3d0e292f", "selectorBytes": [ @@ -10174,9 +10174,9 @@ "func": { "id": "sign_0", "description": "Signs data with a `Wallet`.", - "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);", + "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", - "mutability": "", + "mutability": "pure", "signature": "sign((address,uint256,uint256,uint256),bytes32)", "selector": "0xb25c5a25", "selectorBytes": [ diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 87d938db5691e..59b6d96eec2f1 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2704,7 +2704,7 @@ interface Vm { /// Signs data with a `Wallet`. #[cheatcode(group = Crypto)] - function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs data with a `Wallet`. /// @@ -2712,7 +2712,7 @@ interface Vm { /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. #[cheatcode(group = Crypto)] - function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with `privateKey` using the secp256k1 curve. #[cheatcode(group = Crypto)] diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 6b611e795f3ce..a1497677aee18 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -132,8 +132,6 @@ impl ChiselDispatcher { // Create new source with exact input appended and parse let (new_source, do_execute) = source.clone_with_new_line(input.to_string())?; - // TODO: Cloning / parsing the session source twice on non-inspected inputs kinda sucks. - // Should change up how this works. let (cf, res) = source.inspect(input).await?; if let Some(res) = &res { let _ = sh_println!("{res}"); diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index f9cd04af13f6a..abb6be8693585 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -36,9 +36,7 @@ impl SessionSource { .ok_or_else(|| eyre::eyre!("No bytecode found for `REPL` contract"))?; Ok((bytecode.into_owned(), output.final_pc(contract)?)) })?; - - let Some(final_pc) = final_pc else { return Ok(Default::default()) }; - + let final_pc = final_pc.unwrap_or_default(); let mut runner = self.build_runner(final_pc).await?; runner.run(bytecode) } @@ -59,7 +57,7 @@ impl SessionSource { let mut source = match self.clone_with_new_line(line) { Ok((source, _)) => source, Err(err) => { - debug!(%err, "failed to build new source"); + debug!(%err, "failed to build new source for inspection"); return Ok((ControlFlow::Continue(()), None)); } }; diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs index 9a24801752b4a..6fb38b6ca1805 100644 --- a/crates/chisel/tests/it/repl/mod.rs +++ b/crates/chisel/tests/it/repl/mod.rs @@ -19,14 +19,14 @@ macro_rules! repl_test { }; } -repl_test!(test_repl_help, |repl| { +repl_test!(repl_help, |repl| { repl.sendln_raw("!h"); repl.expect("Chisel help"); repl.expect_prompt(); }); // Test abi encode/decode. -repl_test!(test_abi_encode_decode, |repl| { +repl_test!(abi_encode_decode, |repl| { repl.sendln("bytes memory encoded = abi.encode(42, \"hello\")"); repl.sendln("(uint num, string memory str) = abi.decode(encoded, (uint, string))"); repl.sendln("num"); @@ -36,7 +36,7 @@ repl_test!(test_abi_encode_decode, |repl| { }); // Test 0x prefixed strings. -repl_test!(test_hex_string_interpretation, |repl| { +repl_test!(hex_string_interpretation, |repl| { repl.sendln("string memory s = \"0x1234\""); repl.sendln("s"); // Should be treated as string, not hex literal. @@ -44,7 +44,7 @@ repl_test!(test_hex_string_interpretation, |repl| { }); // Test cheatcodes availability. -repl_test!(test_cheatcodes_available, "", init = true, |repl| { +repl_test!(cheatcodes_available, "", init = true, |repl| { repl.sendln("address alice = address(0x1)"); repl.sendln("alice.balance"); @@ -60,12 +60,12 @@ repl_test!(test_cheatcodes_available, "", init = true, |repl| { }); // Test empty inputs. -repl_test!(test_empty_input, |repl| { +repl_test!(empty_input, |repl| { repl.sendln(" \n \n\n \t \t \n \n\t\t\t\t \n \n"); }); // Issue #4130: Test type(intN).min correctness. -repl_test!(test_int_min_values, |repl| { +repl_test!(int_min_values, |repl| { repl.sendln("type(int8).min"); repl.expect("-128"); repl.sendln("type(int256).min"); @@ -74,7 +74,7 @@ repl_test!(test_int_min_values, |repl| { // Issue #4393: Test edit command with traces. // TODO: test `!edit` -// repl_test!(test_edit_with_traces, |repl| { +// repl_test!(edit_with_traces, |repl| { // repl.sendln("!traces"); // repl.sendln("uint x = 42"); // repl.sendln("!edit"); @@ -83,7 +83,7 @@ repl_test!(test_int_min_values, |repl| { // }); // Test tuple support. -repl_test!(test_tuples, |repl| { +repl_test!(tuples, |repl| { repl.sendln("(uint a, uint b) = (1, 2)"); repl.sendln("a"); repl.expect("Decimal: 1"); @@ -92,7 +92,7 @@ repl_test!(test_tuples, |repl| { }); // Issue #4467: Test import. -repl_test!(test_import, "", init = true, |repl| { +repl_test!(import, "", init = true, |repl| { repl.sendln("import {Counter} from \"src/Counter.sol\""); repl.sendln("Counter c = new Counter()"); // TODO: pre-existing inspection failure. @@ -106,7 +106,7 @@ repl_test!(test_import, "", init = true, |repl| { }); // Issue #4617: Test code after assembly return. -repl_test!(test_assembly_return, |repl| { +repl_test!(assembly_return, |repl| { repl.sendln("uint x = 1;"); repl.sendln("assembly { mstore(0x0, 0x1337) return(0x0, 0x20) }"); repl.sendln("x = 2;"); @@ -116,25 +116,25 @@ repl_test!(test_assembly_return, |repl| { }); // Issue #4652: Test commands with trailing whitespace. -repl_test!(test_trailing_whitespace, |repl| { +repl_test!(trailing_whitespace, |repl| { repl.sendln("uint x = 42 "); repl.sendln("x"); repl.expect("Decimal: 42"); }); // Issue #4652: Test that solc flags are respected. -repl_test!(test_solc_flags, "--use 0.8.23", |repl| { +repl_test!(solc_flags, "--use 0.8.23", |repl| { repl.sendln("pragma solidity 0.8.24;"); repl.expect("invalid solc version"); }); // Issue #4915: `chisel eval` -repl_test!(test_eval_subcommand, "eval type(uint8).max", |repl| { +repl_test!(eval_subcommand, "eval type(uint8).max", |repl| { repl.expect("Decimal: 255"); }); // Issue #4938: Test memory/stack dumps with assembly. -repl_test!(test_assembly_memory_dump, |repl| { +repl_test!(assembly_memory_dump, |repl| { let input = r#" uint256 value = 12345; string memory str; @@ -154,34 +154,34 @@ assembly { }); // Issue #5051, #8978: Test EVM version normalization. -repl_test!(test_evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { +repl_test!(evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { repl.sendln("uint x;\nx"); repl.expect("Decimal: 0"); }); // Issue #5481: Test function return values are displayed. -repl_test!(test_function_return_display, |repl| { +repl_test!(function_return_display, |repl| { repl.sendln("function add(uint a, uint b) public pure returns (uint) { return a + b; }"); repl.sendln("add(2, 3)"); repl.expect("Decimal: 5"); }); // Issue #5737: Test bytesN return types. -repl_test!(test_bytes_length_type, |repl| { +repl_test!(bytes_length_type, |repl| { repl.sendln("bytes10 b = bytes10(0)"); repl.sendln("b.length"); repl.expect("Decimal: 10"); }); // Issue #5737: Test bytesN indexing return type. -repl_test!(test_bytes_index_type, |repl| { +repl_test!(bytes_index_type, |repl| { repl.sendln("bytes32 b = bytes32(uint256(0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20))"); repl.sendln("b[3]"); repl.expect("Data: 0x0400000000000000000000000000000000000000000000000000000000000000"); }); // Issue #6618: Test fetching interface with structs. -repl_test!(test_fetch_interface_with_structs, |repl| { +repl_test!(fetch_interface_with_structs, |repl| { repl.sendln_raw("!fe 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 IEntryPoint"); repl.expect( "Added 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789's interface to source as `IEntryPoint`", @@ -192,7 +192,7 @@ repl_test!(test_fetch_interface_with_structs, |repl| { }); // Issue #7035: Test that hex strings aren't checksummed as addresses. -repl_test!(test_hex_string_no_checksum, |repl| { +repl_test!(hex_string_no_checksum, |repl| { repl.sendln("function test(string memory s) public pure returns (string memory) { return s; }"); repl.sendln("test(\"0xe5f3af50fe5d0bf402a3c6f55ccc47d4307922d4\")"); // Should return the exact string, not checksummed. @@ -200,7 +200,7 @@ repl_test!(test_hex_string_no_checksum, |repl| { }); // Issue #7050: Test enum min/max operations. -repl_test!(test_enum_min_max, |repl| { +repl_test!(enum_min_max, |repl| { repl.sendln("enum Color { Red, Green, Blue }"); repl.sendln("type(Color).min"); repl.expect("Decimal: 0"); @@ -209,7 +209,7 @@ repl_test!(test_enum_min_max, |repl| { }); // Issue #9377: Test correct hex formatting for uint256. -repl_test!(test_uint256_hex_formatting, |repl| { +repl_test!(uint256_hex_formatting, |repl| { repl.sendln("uint256 x = 42"); // Full word hex should be 64 chars (256 bits). repl.sendln("x"); @@ -217,7 +217,7 @@ repl_test!(test_uint256_hex_formatting, |repl| { }); // Issue #9377: Test that full words are printed correctly. -repl_test!(test_full_word_hex_formatting, |repl| { +repl_test!(full_word_hex_formatting, |repl| { repl.sendln(r#"keccak256(abi.encode(uint256(keccak256("AgoraStableSwapStorage.OracleStorage")) - 1)) & ~bytes32(uint256(0xff))"#); repl.expect( "Hex (full word): 0x0a6b316b47a0cd26c1b582ae3dcffbd175283c221c3cb3d1c614e3e47f62a700", @@ -225,7 +225,7 @@ repl_test!(test_full_word_hex_formatting, |repl| { }); // Test that uint is printed properly with any size. -repl_test!(test_uint_formatting, |repl| { +repl_test!(uint_formatting, |repl| { for size in (8..=256).step_by(8) { repl.sendln(&format!("type(uint{size}).max")); repl.expect(&format!("Hex: 0x{}", "f".repeat(size / 4))); @@ -236,7 +236,7 @@ repl_test!(test_uint_formatting, |repl| { }); // Test that int is printed properly with any size. -repl_test!(test_int_formatting, |repl| { +repl_test!(int_formatting, |repl| { for size in (8..=256).step_by(8) { let size_minus_1: usize = size / 4 - 1; repl.sendln(&format!("type(int{size}).max")); @@ -252,3 +252,15 @@ repl_test!(test_int_formatting, |repl| { repl.expect(&format!("Hex: 0x{}e", "f".repeat(size_minus_1))); } }); + +repl_test!(uninitialized_variables, |repl| { + repl.sendln("uint256 x;"); + repl.sendln("address y;"); + repl.sendln("assembly { y := not(x) }"); + + repl.sendln("x"); + repl.expect("Hex: 0x0"); + + repl.sendln("y"); + repl.expect("Data: 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"); +}); diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs index bdd222348d055..492a18a0a96d6 100644 --- a/crates/config/src/compilation.rs +++ b/crates/config/src/compilation.rs @@ -7,7 +7,7 @@ use foundry_compilers::{ solc::{Restriction, SolcRestrictions}, }; use semver::VersionReq; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; /// Keeps possible overrides for default settings which users may configure to construct additional /// settings profile. @@ -69,6 +69,7 @@ pub enum RestrictionsError { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CompilationRestrictions { pub paths: GlobMatcher, + #[serde(default, deserialize_with = "deserialize_version_req")] pub version: Option, pub via_ir: Option, pub bytecode_hash: Option, @@ -85,6 +86,36 @@ pub struct CompilationRestrictions { pub max_evm_version: Option, } +/// Custom deserializer for version field that rejects ambiguous bare version numbers. +fn deserialize_version_req<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt_string: Option = Option::deserialize(deserializer)?; + let Some(opt_string) = opt_string else { + return Ok(None); + }; + + let version = opt_string.trim(); + // Reject bare versions like "0.8.11" that lack an operator prefix + if version.chars().next().is_some_and(|c| c.is_ascii_digit()) { + return Err(serde::de::Error::custom(format!( + "Invalid version format '{opt_string}' in compilation_restrictions. \ + Bare version numbers are ambiguous because Cargo interprets them as caret requirements (e.g. '^{version}'). \ + Use an explicit constraint such as '={version}' for an exact version or '>={version}' for a minimum version." + ))); + } + + let req = VersionReq::parse(&opt_string).map_err(|e| { + serde::de::Error::custom(format!( + "Invalid version requirement '{opt_string}': {e}. \ + Examples: '=0.8.11' (exact), '>=0.8.11' (minimum), '>=0.8.11 <0.9.0' (range)." + )) + })?; + + Ok(Some(req)) +} + impl TryFrom for RestrictionsWithVersion { type Error = RestrictionsError; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 17f13a3921fe5..d54cb0aac63f5 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -6413,4 +6413,57 @@ mod tests { Ok(()) }); } + + #[test] + fn fails_on_ambiguous_version_in_compilation_restrictions() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "0.8.11" + "#, + )?; + + let err = Config::load().expect_err("expected bare version to fail"); + let err_msg = err.to_string(); + assert!( + err_msg.contains("Invalid version format '0.8.11'") + && err_msg.contains("Bare version numbers are ambiguous"), + "Expected error about ambiguous version, got: {err_msg}" + ); + + Ok(()) + }); + } + + #[test] + fn accepts_explicit_version_requirements() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "=0.8.11" + + [[profile.default.compilation_restrictions]] + paths = "test/*.sol" + version = ">=0.8.11" + "#, + )?; + + let config = Config::load().expect("should accept explicit version requirements"); + assert_eq!(config.compilation_restrictions.len(), 2); + + Ok(()) + }); + } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 26e56e7bf883f..6429ceb3db23f 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -6,7 +6,7 @@ use crate::{ evm::new_evm_with_inspector, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, - utils::{configure_tx_env, configure_tx_req_env, get_blob_base_fee_update_fraction_by_spec_id}, + utils::{configure_tx_env, configure_tx_req_env, get_blob_base_fee_update_fraction}, }; use alloy_consensus::Typed2718; use alloy_evm::Evm; @@ -1954,7 +1954,7 @@ fn update_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { if let Some(excess_blob_gas) = block.header.excess_blob_gas { env.block.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( excess_blob_gas, - get_blob_base_fee_update_fraction_by_spec_id(env.cfg.spec), + get_blob_base_fee_update_fraction(env.cfg.chain_id, block.header.timestamp), )); } } diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 9666cf4d7e69d..0abffff57c4a8 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -678,6 +678,33 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +// Validates BPO1 blob gas price calculation during fork transaction replay. +// Block 24127158 has a blob tx at index 0, target tx at index 1. +// Forking at the target tx replays the blob tx with correct BPO1 blob base fee calculation. +forgetest_init!(fork_tx_replay_bpo1_blob_base_fee, |prj, cmd| { + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "BlobFork.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +contract BlobForkTest is Test { + function test_fork_with_blob_replay() public { + // Fork at tx index 1 in block 24127158, which replays blob tx at index 0 + bytes32 txHash = 0xa0f349b16e0f338ee760a9954ff5dbf2a402cff3320f3fe2c3755aee8babc335; + vm.createSelectFork("", txHash); + // If we get here, blob tx replay succeeded + assertTrue(true); + } +} + "# + .replace("", &endpoint), + ); + + cmd.args(["test", "-vvvv"]).assert_success(); +}); + // https://github.com/foundry-rs/foundry/issues/6579 forgetest_init!(include_custom_types_in_traces, |prj, cmd| { prj.add_test( diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 0066ab7c83843..ebf33383c9491 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -697,6 +697,7 @@ mod tests { } #[test] + #[ignore = "addresses depend on testdata utils internals for some reason"] fn link_create2_nested() { link_test(testdata().join("default/linking/nested"), |linker| { linker diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 336ea499fff41..cccde297ef967 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -187,6 +187,13 @@ fn next_url_inner(is_ws: bool, chain: NamedChain) -> String { return "https://celo.drpc.org".to_string(); } + if matches!(chain, Sepolia) { + let rpc_url = env::var("ETH_SEPOLIA_RPC").unwrap_or_default(); + if !rpc_url.is_empty() { + return rpc_url; + } + } + if matches!(chain, Arbitrum) { let rpc_url = env::var("ARBITRUM_RPC").unwrap_or_default(); if !rpc_url.is_empty() { diff --git a/deny.toml b/deny.toml index dfe4f0128f313..854f33033ed2c 100644 --- a/deny.toml +++ b/deny.toml @@ -9,6 +9,8 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0437 protobuf! Crash due to uncontrolled recursion in protobuf crate. "RUSTSEC-2024-0437", + # https://rustsec.org/advisories/RUSTSEC-2025-0137 `reciprocal_mg10` OOB, unused + "RUSTSEC-2025-0137", ] # This section is considered when running `cargo deny check bans`. diff --git a/flake.lock b/flake.lock index bd08c4fc369a7..48f8b3851e80f 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1766267710, - "narHash": "sha256-2icDwxnuPUl+vA5YwxbiFdRGlO/IrBH5Jllu8aswCkc=", + "lastModified": 1766818130, + "narHash": "sha256-FeVBDk9H5OPynbG+4wx0j04Mz/F+/LUfAPr4bnSeu/8=", "owner": "nix-community", "repo": "fenix", - "rev": "16642c5ae9941c36e4e3acb4ea02fd4959012010", + "rev": "3479aaf375090ede15fb623a84892f8597e00a25", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1766125104, - "narHash": "sha256-l/YGrEpLromL4viUo5GmFH3K5M1j0Mb9O+LiaeCPWEM=", + "lastModified": 1766840161, + "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7d853e518814cca2a657b72eeba67ae20ebf7059", + "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1766021917, - "narHash": "sha256-UfHpZWcVQouCRf64yAJGletSAuiAOmHnlmhHrsKTQtk=", + "lastModified": 1766755908, + "narHash": "sha256-1SBtDgvqG/tazoe3nyIfqhFgEI6RFSGBnh1Lnv0/Y6U=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "ea1d2998f2b3c6f6a494f72580550d521548e3d9", + "rev": "8c5a68e214578d4ccbb65fa8c48a4efd5c82697f", "type": "github" }, "original": { diff --git a/testdata/default/repros/Issue2851.t.sol b/testdata/default/repros/Issue2851.t.sol deleted file mode 100644 index bafa7c0ed0d07..0000000000000 --- a/testdata/default/repros/Issue2851.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.1; - -import "utils/Test.sol"; - -contract Backdoor { - uint256 public number = 1; - - function backdoor(uint256 newNumber) public payable { - uint256 x = newNumber - 1; - if (x == 6912213124124531) { - number = 0; - } - } -} - -// https://github.com/foundry-rs/foundry/issues/2851 -contract Issue2851Test is Test { - Backdoor back; - - function setUp() public { - back = new Backdoor(); - } - - /// forge-config: default.fuzz.dictionary.max_fuzz_dictionary_literals = 0 - /// forge-config: default.fuzz.seed = '111' - function invariantNotZero() public { - assertEq(back.number(), 1); - } -} diff --git a/testdata/utils/Vm.sol b/testdata/utils/Vm.sol index cf5930928cf8e..8cced251493aa 100644 --- a/testdata/utils/Vm.sol +++ b/testdata/utils/Vm.sol @@ -492,7 +492,7 @@ interface Vm { function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); function signAndAttachDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); - function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); @@ -501,7 +501,7 @@ interface Vm { function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); - function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); From 818f43b6a4f5e94ba5e9d6b1af4d03699a883065 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:58:07 +0700 Subject: [PATCH 201/391] Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> From 3478be83972d1822a47bec3a0980894acc71e534 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:13:06 +0700 Subject: [PATCH 202/391] Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .circleci/ci_cargo.yml | 37 +++++ .circleci/web3_defi_gamefi.yml | 26 ++++ crates/cast/src/cmd/mktx.rs | 26 +--- crates/forge/Cargo.toml | 1 + crates/lint/src/linter.rs | 129 ++++++++++++++++++ crates/lint/src/sol/gas/keccak.rs | 98 ++----------- .../lint/testdata/DivideBeforeMultiply.stderr | 64 ++++----- 7 files changed, 243 insertions(+), 138 deletions(-) create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 crates/lint/src/linter.rs diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index b4a9c0fb681bd..ebfb5afeadd47 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -1,14 +1,14 @@ use crate::tx::{self, CastTxBuilder}; use alloy_ens::NameOrAddress; -use alloy_network::{EthereumWallet, TransactionBuilder, eip2718::Encodable2718}; -use alloy_primitives::{Address, hex}; +use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; +use alloy_primitives::hex; use alloy_provider::Provider; use alloy_signer::Signer; use clap::Parser; -use eyre::Result; +use eyre::{OptionExt, Result}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{LoadConfig, get_provider}, + utils::{get_provider, LoadConfig}, }; use std::{path::PathBuf, str::FromStr}; @@ -25,7 +25,6 @@ pub struct MakeTxArgs { sig: Option, /// The arguments of the function to call. - #[arg(allow_negative_numbers = true)] args: Vec, #[command(subcommand)] @@ -50,7 +49,7 @@ pub struct MakeTxArgs { /// Generate a raw RLP-encoded unsigned transaction. /// /// Relaxes the wallet requirement. - #[arg(long)] + #[arg(long, requires = "from")] raw_unsigned: bool, /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender @@ -70,7 +69,6 @@ pub enum MakeTxSubcommands { sig: Option, /// The constructor arguments. - #[arg(allow_negative_numbers = true)] args: Vec, }, } @@ -98,7 +96,7 @@ impl MakeTxArgs { let provider = get_provider(&config)?; - let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) + let tx_builder = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? @@ -108,17 +106,7 @@ impl MakeTxArgs { if raw_unsigned { // Build unsigned raw tx - // Check if nonce is provided when --from is not specified - // See: - if eth.wallet.from.is_none() && tx.nonce.is_none() { - eyre::bail!( - "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" - ); - } - - // Use zero address as placeholder for unsigned transactions - let from = eth.wallet.from.unwrap_or(Address::ZERO); - + let from = eth.wallet.from.ok_or_eyre("missing `--from` address")?; let raw_tx = tx_builder.build_unsigned_raw(from).await?; sh_println!("{raw_tx}")?; diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 53b5243d54b38..515983876019c 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-rpc-types.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true revm.workspace = true diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index cb942510bbb49..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,103 +1,27 @@ use super::AsmKeccak256; use crate::{ - linter::{LateLintPass, LintContext}, + declare_forge_lint, + linter::EarlyLintPass, sol::{Severity, SolLint}, }; -use solar::{ - ast::{self as ast, Span}, - interface::kw, - sema::hir::{self}, -}; +use solar_ast::{Expr, ExprKind}; +use solar_interface::kw; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "use of inefficient hashing mechanism; consider using inline assembly" + "hash using inline assembly to save gas" ); -impl<'hir> LateLintPass<'hir> for AsmKeccak256 { - fn check_stmt( - &mut self, - ctx: &LintContext, - hir: &'hir hir::Hir<'hir>, - stmt: &'hir hir::Stmt<'hir>, - ) { - let check_expr_and_emit_lint = - |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { - if let Some(hash_arg) = extract_keccak256_arg(expr) { - self.emit_lint( - ctx, - hir, - stmt.span, - expr, - hash_arg, - AsmContext { _assign: assign, _is_return: is_return }, - ); - } - }; - - match stmt.kind { - hir::StmtKind::DeclSingle(var_id) => { - let var = hir.variable(var_id); - if let Some(init) = var.initializer { - // Constants should be optimized by the compiler, so no gas savings apply. - if !matches!(var.mutability, Some(hir::VarMut::Constant)) { - check_expr_and_emit_lint(init, var.name, false); - } +impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { + fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(expr, _) = &expr.kind { + if let ExprKind::Ident(ident) = &expr.kind { + if ident.name == kw::Keccak256 { + ctx.emit(&ASM_KECCAK256, expr.span); } } - // Expressions that don't (directly) assign to a variable - hir::StmtKind::Expr(expr) - | hir::StmtKind::Emit(expr) - | hir::StmtKind::Revert(expr) - | hir::StmtKind::DeclMulti(_, expr) - | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), - hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), - _ => (), } } } - -impl AsmKeccak256 { - /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. - fn emit_lint( - &self, - ctx: &LintContext, - _hir: &hir::Hir<'_>, - _stmt_span: Span, - call: &hir::Expr<'_>, - _hash: &hir::Expr<'_>, - _asm_ctx: AsmContext, - ) { - ctx.emit(&ASM_KECCAK256, call.span); - } -} - -/// If the expression is a call to `keccak256` with one argument, returns that argument. -fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { - let hir::ExprKind::Call( - callee, - hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, - .., - ) = &expr.kind - else { - return None; - }; - - let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { - matches!(builtin.name(), kw::Keccak256) - } else { - return None; - }; - - if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } -} - -// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- - -#[derive(Debug, Clone, Copy)] -struct AsmContext { - _assign: Option, - _is_return: bool, -} diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index 7677d97452527..1c98f4b13d194 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -1,48 +1,48 @@ warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -LL | (1 / 2) * 3; - | ^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + | +3 | (1 / 2) * 3; + | ----------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -LL | ((1 / 2) * 3) * 4; - | ^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + | +5 | ((1 / 2) * 3) * 4; + | ----------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -LL | ((1 * 2) / 3) * 4; - | ^^^^^^^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + | +6 | ((1 * 2) / 3) * 4; + | ----------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -LL | (1 / 2 / 3) * 4; - | ^^^^^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + | +7 | (1 / 2 / 3) * 4; + | --------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -LL | (1 / (2 + 3)) * 4; - | ^^^^^^^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + | +8 | (1 / (2 + 3)) * 4; + | ----------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC | -LL | 1 / ((2 / 3) * 3); - | ^^^^^^^^^^^ +15 | 1 / ((2 / 3) * 3); + | ----------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply From 4cd968ba7f3d847f42f4c243a550b7b22bd32885 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:32:30 +0700 Subject: [PATCH 203/391] Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/evm/src/executors/corpus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index 92eb4452ce802..f8333418caec3 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -209,7 +209,7 @@ impl CorpusManager { }) }; - 'corpus_replay: for entry in std::fs::read_dir(corpus_dir)? { + 'corpus_replay: for entry in std::fs::read_dir(&canonical_corpus_dir)? { let path = entry?.path(); // Canonicalize the candidate path, skip if it cannot be canonicalized (e.g. broken symlink) let canonical_path = match path.canonicalize() { From e614a6319ddbb974c94e478fc657b0f2fe9b4da9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 1 Jan 2026 00:41:48 +0700 Subject: [PATCH 204/391] Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci.yml | 31 +++++++++++++++++++++++++++++ .circleci/config.yml | 32 ++++++++++++++++++++++++++++++ .github/workflows/docker-image.yml | 21 ++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 .circleci/ci.yml create mode 100644 .circleci/config.yml create mode 100644 .github/workflows/docker-image.yml diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..76b2889f1c4b2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000000000..e0c9c518e8748 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,21 @@ +name: Docker Image CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From 3532bf0d43137fe7637f425747841bd71bd27407 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 1 Jan 2026 01:26:50 +0700 Subject: [PATCH 205/391] fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --- .circleci/config.yml | 10 +++++----- crates/wallets/src/signer.rs | 2 +- crates/wallets/src/wallet_browser/signer.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d5d401c51893c..f967cfaa30db5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,20 +1,20 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference +# See: https://circleci.com/docs/reference/configuration-reference version: 2.1 # Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs jobs: say-hello: # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job docker: # Specify the version you desire here # See: https://circleci.com/developer/images/image/cimg/base - image: cimg/base:current # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps steps: # Checkout the code as the first step. - checkout @@ -23,7 +23,7 @@ jobs: command: "echo Hello, World!" # Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows workflows: say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. diff --git a/crates/wallets/src/signer.rs b/crates/wallets/src/signer.rs index 5a5c77c676587..8d50c03d1a90b 100644 --- a/crates/wallets/src/signer.rs +++ b/crates/wallets/src/signer.rs @@ -322,7 +322,7 @@ impl Signer for WalletSigner { #[async_trait] impl TxSigner for WalletSigner { fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) + Signer::address(self) } async fn sign_transaction( diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 1e3df775e2598..78cf166a35ad0 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -177,7 +177,7 @@ impl Signer for BrowserSigner { #[async_trait] impl TxSigner for BrowserSigner { fn address(&self) -> Address { - self.address + Signer::address(self) } async fn sign_transaction( From 4f1dfb9fc6b58ab9b248af34e359f5600a743381 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 1 Jan 2026 01:40:10 +0700 Subject: [PATCH 206/391] fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --- .circleci/config.yml | 10 +++++----- crates/wallets/src/signer.rs | 2 +- crates/wallets/src/wallet_browser/signer.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76b2889f1c4b2..4168efef0971f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,20 +1,20 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference +# See: https://circleci.com/docs/reference/configuration-reference version: 2.1 # Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs jobs: say-hello: # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job docker: # Specify the version you desire here # See: https://circleci.com/developer/images/image/cimg/base - image: cimg/base:current # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps steps: # Checkout the code as the first step. - checkout @@ -23,7 +23,7 @@ jobs: command: "echo Hello, World!" # Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows workflows: say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. diff --git a/crates/wallets/src/signer.rs b/crates/wallets/src/signer.rs index 5a5c77c676587..8d50c03d1a90b 100644 --- a/crates/wallets/src/signer.rs +++ b/crates/wallets/src/signer.rs @@ -322,7 +322,7 @@ impl Signer for WalletSigner { #[async_trait] impl TxSigner for WalletSigner { fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) + Signer::address(self) } async fn sign_transaction( diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 1e3df775e2598..78cf166a35ad0 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -177,7 +177,7 @@ impl Signer for BrowserSigner { #[async_trait] impl TxSigner for BrowserSigner { fn address(&self) -> Address { - self.address + Signer::address(self) } async fn sign_transaction( From 20435ca66ec2f373542d4bed52ef33834a175198 Mon Sep 17 00:00:00 2001 From: tskoyo Date: Thu, 1 Jan 2026 07:29:18 +0100 Subject: [PATCH 207/391] add beacon block root tests --- crates/cast/src/cmd/run.rs | 14 ++++---------- crates/cast/tests/cli/main.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 6152f5b6978a6..8336714a66bb2 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -2,7 +2,7 @@ use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_chan use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ - Address, Bytes, FixedBytes, U256, + Address, Bytes, U256, map::{AddressSet, HashMap}, }; use alloy_provider::Provider; @@ -183,7 +183,7 @@ impl RunArgs { env.evm_env.cfg_env.limit_contract_code_size = None; env.evm_env.block_env.number = U256::from(tx_block_number); - let mut parent_beacon_block_root: FixedBytes<32> = FixedBytes::default(); + let mut parent_beacon_block_root = None; if let Some(block) = &block { env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); @@ -194,13 +194,7 @@ impl RunArgs { env.evm_env.block_env.gas_limit = block.header.gas_limit; if env.evm_env.cfg_env.spec >= SpecId::CANCUN { - if let Some(beacon_root) = block.header.parent_beacon_block_root { - parent_beacon_block_root = beacon_root; - } else { - return Err(eyre::eyre!( - "ParentBeaconBlockRoot is missing for Cancun or later blocks" - )); - } + parent_beacon_block_root = block.header.parent_beacon_block_root; } // TODO: we need a smarter way to map the block to the corresponding evm_version for @@ -236,7 +230,7 @@ impl RunArgs { None, )?; - if parent_beacon_block_root != FixedBytes::default() { + if let Some(parent_beacon_block_root) = parent_beacon_block_root { let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().unwrap_or(0); executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index b776370d2ac4d..c4a809f6c065c 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2954,6 +2954,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { From 77200b02e604ea5c67643d6cc3c5931b58777b74 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 2 Jan 2026 01:54:09 +0700 Subject: [PATCH 208/391] Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/dev_stage.yml | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .circleci/dev_stage.yml diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..03e77d78fcc3c --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,50 @@ +# ~/.circleci/config.yml +version: 2.1 + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ From a0c703a3761df9e10fbaa622c2b9d52239c630eb Mon Sep 17 00:00:00 2001 From: tskoyo Date: Thu, 25 Dec 2025 21:18:00 +0100 Subject: [PATCH 209/391] EIP-4788 implementation --- crates/cast/src/cmd/run.rs | 23 ++++++++++++++++++++-- crates/evm/evm/src/executors/trace.rs | 28 ++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 54894fc10b7ea..51a08274ee7cb 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -2,7 +2,7 @@ use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_chan use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ - Address, Bytes, U256, + Address, Bytes, FixedBytes, U256, map::{AddressSet, HashMap}, }; use alloy_provider::Provider; @@ -31,7 +31,7 @@ use foundry_evm::{ utils::configure_tx_env, }; use futures::TryFutureExt; -use revm::DatabaseRef; +use revm::{DatabaseRef, primitives::hardfork::SpecId}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -183,6 +183,8 @@ impl RunArgs { env.evm_env.cfg_env.limit_contract_code_size = None; env.evm_env.block_env.number = U256::from(tx_block_number); + let mut parent_beacon_block_root: FixedBytes<32> = FixedBytes::default(); + if let Some(block) = &block { env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); env.evm_env.block_env.beneficiary = block.header.beneficiary; @@ -191,6 +193,16 @@ impl RunArgs { env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); env.evm_env.block_env.gas_limit = block.header.gas_limit; + if env.evm_env.cfg_env.spec >= SpecId::CANCUN { + if let Some(beacon_root) = block.header.parent_beacon_block_root { + parent_beacon_block_root = beacon_root; + } else { + return Err(eyre::eyre!( + "ParentBeaconBlockRoot is missing for Cancun or later blocks" + )); + } + } + // TODO: we need a smarter way to map the block to the corresponding evm_version for // commonly used chains if evm_version.is_none() { @@ -214,6 +226,7 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); + let mut executor = TracingExecutor::new( env.clone(), fork, @@ -223,6 +236,12 @@ impl RunArgs { create2_deployer, None, )?; + + if parent_beacon_block_root != FixedBytes::default() { + let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().unwrap_or(0); + executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; + } + let mut env = Env::new_with_spec_id( env.evm_env.cfg_env.clone(), env.evm_env.block_env.clone(), diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index d951dde0d6663..319e2ab495ca6 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -2,7 +2,7 @@ use crate::{ Env, executors::{Executor, ExecutorBuilder}, }; -use alloy_primitives::{Address, U256, map::HashMap}; +use alloy_primitives::{Address, FixedBytes, U256, address, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; use foundry_compilers::artifacts::EvmVersion; @@ -92,6 +92,32 @@ impl TracingExecutor { let chain = env.tx.chain_id.unwrap().into(); Ok((env, fork, chain, networks)) } + + /// Processes the beacon block root by storing it in the appropriate storage slots. + pub fn process_beacon_block_root( + &mut self, + block_timestamp: u64, + beacon_root: FixedBytes<32>, + ) -> eyre::Result<()> { + const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); + const HISTORY_BUFFER_LENGTH: u64 = 8191; + + let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; + let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; + + let timestamp_slot = U256::from(timestamp_index); + let root_slot = U256::from(root_index); + + self.set_storage_slot(BEACON_ROOTS_ADDRESS, timestamp_slot, U256::from(block_timestamp))?; + + self.set_storage_slot( + BEACON_ROOTS_ADDRESS, + root_slot, + U256::from_be_bytes(beacon_root.into()), + )?; + + Ok(()) + } } impl Deref for TracingExecutor { From 61646b8242c176937abf134364180a44951ae4a2 Mon Sep 17 00:00:00 2001 From: tskoyo Date: Sat, 27 Dec 2025 14:55:45 +0100 Subject: [PATCH 210/391] formatting --- crates/cast/src/cmd/run.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 51a08274ee7cb..6152f5b6978a6 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -226,7 +226,6 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::new( env.clone(), fork, From 242e19c664797b1b9a0de672120b9eebe4287dbf Mon Sep 17 00:00:00 2001 From: tskoyo Date: Thu, 1 Jan 2026 07:29:18 +0100 Subject: [PATCH 211/391] add beacon block root tests --- crates/cast/src/cmd/run.rs | 14 ++++---------- crates/cast/tests/cli/main.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 6152f5b6978a6..8336714a66bb2 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -2,7 +2,7 @@ use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_chan use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ - Address, Bytes, FixedBytes, U256, + Address, Bytes, U256, map::{AddressSet, HashMap}, }; use alloy_provider::Provider; @@ -183,7 +183,7 @@ impl RunArgs { env.evm_env.cfg_env.limit_contract_code_size = None; env.evm_env.block_env.number = U256::from(tx_block_number); - let mut parent_beacon_block_root: FixedBytes<32> = FixedBytes::default(); + let mut parent_beacon_block_root = None; if let Some(block) = &block { env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); @@ -194,13 +194,7 @@ impl RunArgs { env.evm_env.block_env.gas_limit = block.header.gas_limit; if env.evm_env.cfg_env.spec >= SpecId::CANCUN { - if let Some(beacon_root) = block.header.parent_beacon_block_root { - parent_beacon_block_root = beacon_root; - } else { - return Err(eyre::eyre!( - "ParentBeaconBlockRoot is missing for Cancun or later blocks" - )); - } + parent_beacon_block_root = block.header.parent_beacon_block_root; } // TODO: we need a smarter way to map the block to the corresponding evm_version for @@ -236,7 +230,7 @@ impl RunArgs { None, )?; - if parent_beacon_block_root != FixedBytes::default() { + if let Some(parent_beacon_block_root) = parent_beacon_block_root { let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().unwrap_or(0); executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index b776370d2ac4d..c4a809f6c065c 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2954,6 +2954,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { From 4e7adcda7260affcadf032000bf3a9a05781c48e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:31:13 +0700 Subject: [PATCH 212/391] Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/evm/src/executors/trace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 319e2ab495ca6..7b63c019e848c 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -100,7 +100,7 @@ impl TracingExecutor { beacon_root: FixedBytes<32>, ) -> eyre::Result<()> { const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); - const HISTORY_BUFFER_LENGTH: u64 = 8191; + const HISTORY_BUFFER_LENGTH: u64 = 8192; let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; From 87051b31120d54da1bb40aba4ab5bf4f8a26b217 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:31:54 +0700 Subject: [PATCH 213/391] Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cast/src/cmd/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 8336714a66bb2..b65101f5db80d 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -231,7 +231,7 @@ impl RunArgs { )?; if let Some(parent_beacon_block_root) = parent_beacon_block_root { - let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().unwrap_or(0); + let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().wrap_err("failed to convert block timestamp to u64")?; executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; } From cda69962ef7eff908d5d6eb7c244c3e486aa02da Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 3 Jan 2026 07:03:13 +0700 Subject: [PATCH 214/391] Update crates/common/fmt/src/ui.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/fmt/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index a7e583b6696e1..002439fb93152 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -891,7 +891,7 @@ blobGasUsed {}", receipt.effective_gas_price.pretty(), receipt.from.pretty(), receipt.gas_used.pretty(), - serde_json::to_string(receipt.inner.logs()).unwrap(), + serde_json::to_string(receipt.inner.logs()).unwrap_or_else(|e| e.to_string()), receipt.inner.logs_bloom().pretty(), self.state_root().pretty(), receipt.inner.status().pretty(), From 0303c65d2e2c0735b4c24da45c2038f43372108f Mon Sep 17 00:00:00 2001 From: "snyk-io[bot]" <141718529+snyk-io[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:58:06 +0000 Subject: [PATCH 215/391] feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr --- npm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/package.json b/npm/package.json index 2362816053f8c..d596a7930b609 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.0.2", "bun": "^1.3.1", "typescript": "^5.9.3" }, From 43cd8245ddb3c6f14707ea5b80dfc71d6333e69b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:20:33 +0700 Subject: [PATCH 216/391] fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- Cargo.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f5f65ef4645d..7b4472f3e5388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3665,7 +3665,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3970,7 +3970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5409,8 +5409,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1", - "windows-result 0.4.1", + "windows-link 0.1.3", + "windows-result 0.3.4", ] [[package]] @@ -5801,7 +5801,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -6178,7 +6178,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6924,7 +6924,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8074,7 +8074,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.5.10", "thiserror 2.0.17", "tokio", "tracing", @@ -8111,9 +8111,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -8920,7 +8920,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9716,7 +9716,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.17", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] @@ -10042,9 +10042,9 @@ dependencies = [ [[package]] name = "svm-rs" -version = "0.5.22" +version = "0.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909e8ff825120cd2b34ceb236ab72e2a7f74b1d3a86c247936c8ff7a80c5d408" +checksum = "415b159b54c22d9810087f0991371fd6242a912673e982a7c4ca8ea122f7e00a" dependencies = [ "const-hex", "dirs", @@ -10054,7 +10054,7 @@ dependencies = [ "serde_json", "sha2", "tempfile", - "thiserror 2.0.17", + "thiserror 1.0.69", "url", "zip", ] @@ -10168,7 +10168,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.3", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -10208,7 +10208,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -11520,7 +11520,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] From 6067ae2b1f3cf645627443274bae23e1d0f5a935 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:22:30 +0700 Subject: [PATCH 217/391] Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar --- .github/workflows/docker-image.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index e0c9c518e8748..0000000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} From 784a3857a84dfa2a1bd8604dafc4a783d05e0c64 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:54:11 +0700 Subject: [PATCH 218/391] Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar From f61d8e8595b855634491413defc8508e84e0dede Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 8 Jan 2026 02:55:41 +0700 Subject: [PATCH 219/391] Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .circleci/dev_stage.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml index 03e77d78fcc3c..5ba351727d22b 100644 --- a/.circleci/dev_stage.yml +++ b/.circleci/dev_stage.yml @@ -1,5 +1,25 @@ -# ~/.circleci/config.yml +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + jobs: my-job: steps: From c97d4fa09efa5908766756c157ab34b6aee9d81f Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 8 Jan 2026 03:53:35 +0700 Subject: [PATCH 220/391] Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> From cc27594b6fbab226fdd556eb67fbdd6b19976577 Mon Sep 17 00:00:00 2001 From: Matt D Date: Thu, 8 Jan 2026 17:16:11 +0800 Subject: [PATCH 221/391] chore: ignore RUSTSEC (#13011) * update deny for CI * Update more --- deny.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deny.toml b/deny.toml index 854f33033ed2c..6b041e529e801 100644 --- a/deny.toml +++ b/deny.toml @@ -9,8 +9,10 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0437 protobuf! Crash due to uncontrolled recursion in protobuf crate. "RUSTSEC-2024-0437", - # https://rustsec.org/advisories/RUSTSEC-2025-0137 `reciprocal_mg10` OOB, unused - "RUSTSEC-2025-0137", + # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained, need to transition all deps to wincode first + "RUSTSEC-2025-0141", + # https://rustsec.org/advisories/RUSTSEC-2026-0002 lru unused directly: + "RUSTSEC-2026-0002", ] # This section is considered when running `cargo deny check bans`. From 39fb603d5a427c51393a91f36f447d4ff1413a8f Mon Sep 17 00:00:00 2001 From: onbjerg Date: Thu, 8 Jan 2026 10:50:39 +0100 Subject: [PATCH 222/391] chore(chisel): rm dead code (#13014) --- crates/chisel/src/source.rs | 47 ------------------------------------- 1 file changed, 47 deletions(-) diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs index 576f36eab02f3..7eedb31923439 100644 --- a/crates/chisel/src/source.rs +++ b/crates/chisel/src/source.rs @@ -567,20 +567,6 @@ impl SessionSource { self.clear_output(); } - /// Clear the global-level code . - pub fn clear_global(&mut self) -> &mut Self { - String::clear(&mut self.global_code); - self.clear_output(); - self - } - - /// Clear the contract-level code . - pub fn clear_contract(&mut self) -> &mut Self { - String::clear(&mut self.contract_code); - self.clear_output(); - self - } - /// Clear the `run()` function code. pub fn clear_run(&mut self) -> &mut Self { String::clear(&mut self.run_code); @@ -694,39 +680,6 @@ impl SessionSource { Ok(intermediate_output) } - /// Construct the source as a valid Forge script. - pub fn to_script_source(&self) -> String { - let Self { - contract_name, - global_code, - contract_code: top_level_code, - run_code, - config, - .. - } = self; - - let script_import = - if !config.no_vm { "import {Script} from \"forge-std/Script.sol\";\n" } else { "" }; - - format!( - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0; - -{script_import} -{global_code} - -contract {contract_name} is Script {{ - {top_level_code} - - /// @notice Script entry point - function run() public {{ - {run_code} - }} -}}"#, - ) - } - /// Construct the REPL source. pub fn to_repl_source(&self) -> String { let Self { From a1a18dfb7cfbd5201373093e16cd79386acabfaf Mon Sep 17 00:00:00 2001 From: onbjerg Date: Thu, 8 Jan 2026 10:50:54 +0100 Subject: [PATCH 223/391] chore(cli): rm dead code (#13015) --- crates/cli/src/opts/evm.rs | 8 ------- crates/cli/src/utils/cmd.rs | 46 +------------------------------------ 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index b1a55d5cbae03..6499d693ddf40 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -2,7 +2,6 @@ use alloy_primitives::{Address, B256, U256}; use clap::Parser; -use eyre::ContextCompat; use foundry_config::{ Chain, Config, figment::{ @@ -260,13 +259,6 @@ pub struct EnvArgs { pub enable_tx_gas_limit: bool, } -impl EvmArgs { - /// Ensures that fork url exists and returns its reference. - pub fn ensure_fork_url(&self) -> eyre::Result<&String> { - self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.") - } -} - /// We have to serialize chain IDs and not names because when extracting an EVM `Env`, it expects /// `chain_id` to be `u64`. fn id(chain: &Option, s: S) -> Result { diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index ec483bca88359..b0f3661881843 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -2,10 +2,7 @@ use alloy_json_abi::JsonAbi; use eyre::{Result, WrapErr}; use foundry_common::{TestFunctionExt, fs, fs::json_files, selectors::SelectorKind, shell}; use foundry_compilers::{ - Artifact, ArtifactId, ProjectCompileOutput, - artifacts::{CompactBytecode, Settings}, - cache::{CacheEntry, CompilerCache}, - utils::read_json_file, + Artifact, ArtifactId, ProjectCompileOutput, artifacts::CompactBytecode, utils::read_json_file, }; use foundry_config::{Chain, Config, NamedChain, error::ExtractConfigError, figment::Figment}; use foundry_evm::{ @@ -65,47 +62,6 @@ pub fn find_contract_artifacts( Ok((abi, bin, id)) } -/// Helper function for finding a contract by ContractName -// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a -// contract name? -pub fn get_cached_entry_by_name( - cache: &CompilerCache, - name: &str, -) -> Result<(PathBuf, CacheEntry)> { - let mut cached_entry = None; - let mut alternatives = Vec::new(); - - for (abs_path, entry) in &cache.files { - for artifact_name in entry.artifacts.keys() { - if artifact_name == name { - if cached_entry.is_some() { - eyre::bail!( - "contract with duplicate name `{}`. please pass the path instead", - name - ) - } - cached_entry = Some((abs_path.to_owned(), entry.to_owned())); - } else { - alternatives.push(artifact_name); - } - } - } - - if let Some(entry) = cached_entry { - return Ok(entry); - } - - let mut err = format!("could not find artifact: `{name}`"); - if let Some(suggestion) = super::did_you_mean(name, &alternatives).pop() { - err = format!( - r#"{err} - - Did you mean `{suggestion}`?"# - ); - } - eyre::bail!(err) -} - /// Returns error if constructor has arguments. pub fn ensure_clean_constructor(abi: &JsonAbi) -> Result<()> { if let Some(constructor) = &abi.constructor From 709daa895b3e5f9ba5778be572cbbe098ed3468b Mon Sep 17 00:00:00 2001 From: onbjerg Date: Thu, 8 Jan 2026 10:51:01 +0100 Subject: [PATCH 224/391] chore(cheatcodes): rm dead code (#13016) --- crates/cheatcodes/src/script.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index ca9e9e8ccef86..80bd907198a95 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -320,12 +320,6 @@ impl Wallets { .unwrap_or_else(|| panic!("not all instances were dropped")) } - /// Locks inner Mutex and adds a signer to the [MultiWallet]. - pub fn add_private_key(&self, private_key: &B256) -> Result<()> { - self.add_local_signer(PrivateKeySigner::from_bytes(private_key)?); - Ok(()) - } - /// Locks inner Mutex and adds a signer to the [MultiWallet]. pub fn add_local_signer(&self, wallet: PrivateKeySigner) { self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet)); From 397cc8b8e5a1a1a4b8813f8cb5deb38c0ebcd0da Mon Sep 17 00:00:00 2001 From: onbjerg Date: Thu, 8 Jan 2026 10:52:27 +0100 Subject: [PATCH 225/391] chore(common): rm dead code (#13018) --- crates/common/src/contracts.rs | 9 --------- crates/common/src/serde_helpers.rs | 12 ------------ 2 files changed, 21 deletions(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 5bed2124aee62..f079d0f4fbf3d 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -395,15 +395,6 @@ impl ContractsByArtifact { Ok(contracts.first().copied()) } - /// Finds abi for contract which has the same contract name or identifier as `id`. - pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option { - self.iter() - .find(|(artifact, _)| { - artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id - }) - .map(|(_, contract)| contract.abi.clone()) - } - /// Finds abi by name or source path /// /// Returns the abi and the contract name. diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs index fc04bfbbdb3d5..59c2b7063ca23 100644 --- a/crates/common/src/serde_helpers.rs +++ b/crates/common/src/serde_helpers.rs @@ -37,18 +37,6 @@ impl FromStr for Numeric { } } -/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the -/// inner value. -pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - match Option::::deserialize(deserializer)? { - Some(val) => val.try_into_u256().map(Some), - None => Ok(None), - } -} - /// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. #[derive(Debug, Deserialize)] #[serde(untagged)] From 703ffe40dd2a8f04c3aeaee9b9bb052e43f14117 Mon Sep 17 00:00:00 2001 From: onbjerg Date: Thu, 8 Jan 2026 10:52:36 +0100 Subject: [PATCH 226/391] chore(bench): rm dead code (#13017) --- benches/src/lib.rs | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index eeb3cbeeafe11..55bbd9762b1d9 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -6,9 +6,7 @@ use foundry_common::{sh_eprintln, sh_println}; use foundry_compilers::project_util::TempProject; use foundry_test_utils::util::clone_remote; use once_cell::sync::Lazy; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::{ - env, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -506,44 +504,3 @@ pub fn get_forge_version_details() -> Result { Ok(lines.first().unwrap_or(&"unknown").to_string()) } } - -/// Get Foundry versions to benchmark from environment variable or default -/// -/// Reads from FOUNDRY_BENCH_VERSIONS environment variable if set, -/// otherwise returns the default versions from FOUNDRY_VERSIONS constant. -/// -/// The environment variable should be a comma-separated list of versions, -/// e.g., "stable,nightly,v1.2.0" -pub fn get_benchmark_versions() -> Vec { - if let Ok(versions_env) = env::var("FOUNDRY_BENCH_VERSIONS") { - versions_env.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect() - } else { - FOUNDRY_VERSIONS.iter().map(|&s| s.to_string()).collect() - } -} - -/// Setup Repositories for benchmarking -pub fn setup_benchmark_repos() -> Vec<(RepoConfig, BenchmarkProject)> { - // Check for FOUNDRY_BENCH_REPOS environment variable - let repos = if let Ok(repos_env) = env::var("FOUNDRY_BENCH_REPOS") { - // Parse repo specs from the environment variable - // Format should be: "org1/repo1,org2/repo2" - repos_env - .split(',') - .map(|s| s.trim()) - .filter(|s| !s.is_empty()) - .map(|s| s.parse::()) - .collect::>>() - .expect("Failed to parse FOUNDRY_BENCH_REPOS") - } else { - BENCHMARK_REPOS.clone() - }; - - repos - .par_iter() - .map(|repo_config| { - let project = BenchmarkProject::setup(repo_config).expect("Failed to setup project"); - (repo_config.clone(), project) - }) - .collect() -} From 933901d916096150b84eac3b7240304bd8bc3478 Mon Sep 17 00:00:00 2001 From: Theodore Solis Date: Thu, 8 Jan 2026 10:07:36 +0000 Subject: [PATCH 227/391] fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol --- crates/cli/src/opts/build/utils.rs | 16 ++++++++++++++-- crates/forge/src/cmd/build.rs | 2 +- crates/forge/src/cmd/lint.rs | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/opts/build/utils.rs b/crates/cli/src/opts/build/utils.rs index e204130528946..1952886f36ee0 100644 --- a/crates/cli/src/opts/build/utils.rs +++ b/crates/cli/src/opts/build/utils.rs @@ -91,11 +91,20 @@ pub fn get_solar_sources_from_compile_output( config: &Config, output: &ProjectCompileOutput, target_paths: Option<&[PathBuf]>, + ignored_paths: Option<&[PathBuf]>, ) -> Result { let is_solidity_file = |path: &Path| -> bool { path.extension().and_then(|s| s.to_str()).is_some_and(|ext| SOLC_EXTENSIONS.contains(&ext)) }; + let is_ignored = |path: &Path| -> bool { + if let Some(ignored) = ignored_paths { + ignored.iter().any(|ignored_path| path == ignored_path) + } else { + false + } + }; + // Collect source path targets let mut source_paths: HashSet = if let Some(targets) = target_paths && !targets.is_empty() @@ -111,7 +120,10 @@ pub fn get_solar_sources_from_compile_output( while let Some(path) = queue.pop_front() { if source_paths.insert(path.clone()) { for import in output.graph().imports(path.as_path()) { - queue.push_back(import.to_path_buf()); + // Skip ignored imports to prevent solar from trying to compile them + if !is_ignored(import) { + queue.push_back(import.to_path_buf()); + } } } } @@ -166,7 +178,7 @@ pub fn configure_pcx_from_compile_output( output: &ProjectCompileOutput, target_paths: Option<&[PathBuf]>, ) -> Result<()> { - let solc = get_solar_sources_from_compile_output(config, output, target_paths)?; + let solc = get_solar_sources_from_compile_output(config, output, target_paths, None)?; configure_pcx_from_solc(pcx, &config.project_paths(), &solc, true); Ok(()) } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index d37c73fd68cbb..a98fdc8d7eeb0 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -175,7 +175,7 @@ impl BuildArgs { .collect::>(); let solar_sources = - get_solar_sources_from_compile_output(config, output, Some(&input_files))?; + get_solar_sources_from_compile_output(config, output, Some(&input_files), None)?; if solar_sources.input.sources.is_empty() { if !input_files.is_empty() { sh_warn!( diff --git a/crates/forge/src/cmd/lint.rs b/crates/forge/src/cmd/lint.rs index c62320b003780..1d5e5857ee594 100644 --- a/crates/forge/src/cmd/lint.rs +++ b/crates/forge/src/cmd/lint.rs @@ -108,7 +108,8 @@ impl LintArgs { .with_mixed_case_exceptions(&config.lint.mixed_case_exceptions); let output = ProjectCompiler::new().files(input.iter().cloned()).compile(&project)?; - let solar_sources = get_solar_sources_from_compile_output(&config, &output, Some(&input))?; + let solar_sources = + get_solar_sources_from_compile_output(&config, &output, Some(&input), Some(&ignored))?; if solar_sources.input.sources.is_empty() { return Err(eyre!( "unable to lint. Solar only supports Solidity versions prior to 0.8.0" From 74d697cb5ef17b5f6a8c68e1531e04065f3bbbeb Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:10:43 +0200 Subject: [PATCH 228/391] fix: deduplicate submodule status check logic (#13010) Update mod.rs --- crates/cli/src/utils/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 78e0f87d93e5d..924a717fed3af 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -699,10 +699,9 @@ ignore them in the `.gitignore` file." /// /// Ref: pub fn submodules_uninitialized(self) -> Result { - self.cmd() - .args(["submodule", "status"]) - .get_stdout_lossy() - .map(|stdout| stdout.lines().any(|line| line.starts_with('-'))) + // keep behavior consistent with `has_missing_dependencies`, but avoid duplicating the + // "submodule status has '-' prefix" logic. + self.has_missing_dependencies(std::iter::empty::<&OsStr>()) } /// Initializes the git submodules. From 04fae2756664825e6d416a94b93132597c08da5a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:45:22 +0700 Subject: [PATCH 229/391] Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar From a0042474d19f9aa331802e44b934704262d053ed Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:39:45 +0000 Subject: [PATCH 230/391] Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci-say-hello.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/ci-say-hello.yml diff --git a/.circleci/ci-say-hello.yml b/.circleci/ci-say-hello.yml deleted file mode 100644 index f967cfaa30db5..0000000000000 --- a/.circleci/ci-say-hello.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello From 8e4075aefdefe0d5a4d1e46e6c9c182d3ed0bf4c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:41:11 +0000 Subject: [PATCH 231/391] Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_v1.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .circleci/ci_v1.yml diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 94bf46b3bb04f..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test From 87dd517cf50fef686c8ef39431d06b16d4744d88 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:17:44 +0000 Subject: [PATCH 232/391] Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail From 99b70710b6ea82570ca82585b9c86deb0c0ac62b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:01:45 +0000 Subject: [PATCH 233/391] Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/snyk-container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml index 0db31480beb38..3809ed9cf966f 100644 --- a/.github/workflows/snyk-container.yml +++ b/.github/workflows/snyk-container.yml @@ -15,7 +15,7 @@ name: Snyk Container on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] From aac85e54f87bbf19254a199908d44ab6fd8038cf Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:05:22 +0700 Subject: [PATCH 234/391] Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 626ce94448a4a..07413fef5495c 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,21 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; // Validate file name to avoid path traversal and absolute paths let name_str = name.to_string_lossy(); if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { // Skip invalid (potentially dangerous) file names continue; } - // Optionally verify canonicalized file is in from_dir to avoid symlink traversal + // Verify canonicalized file is in from_dir to avoid symlink traversal if let Ok(canonical_file) = file.canonicalize() { if !canonical_file.starts_with(&from_dir) { continue; } + } else { + continue; } - fs::copy(file, to_dir.join(name))?; + fs::copy(&file, to_dir.join(name))?; } Ok(()) } From ecaa54b2f0987d02109f0600785705744bba78e5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:06:30 +0700 Subject: [PATCH 235/391] Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- npm/scripts/stage-from-artifact.mjs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix From e5f8f49f4a400837c6f888369d86ecc3daedd812 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:07:08 +0700 Subject: [PATCH 236/391] Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- npm/src/const.mjs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/npm/src/const.mjs b/npm/src/const.mjs index a37e8dfd7ae30..d07978918c4eb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** From 04b8cfd9640a40b1d8e921481c4bf969ad418ab3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:17:39 +0700 Subject: [PATCH 237/391] fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol --- .github/workflows/docker-publish.yml | 2 +- .github/workflows/release.yml | 2 +- Cargo.lock | 148 ++----- Cargo.toml | 72 ++- crates/anvil/src/eth/api.rs | 3 +- crates/anvil/tests/it/traces.rs | 2 +- crates/cast/tests/cli/main.rs | 2 +- crates/cheatcodes/assets/cheatcodes.json | 20 + crates/cheatcodes/spec/src/vm.rs | 4 + crates/cheatcodes/src/evm.rs | 28 ++ crates/config/src/error.rs | 4 +- crates/evm/core/src/precompiles.rs | 32 ++ crates/evm/evm/src/inspectors/stack.rs | 8 +- crates/evm/traces/src/decoder/mod.rs | 13 +- crates/evm/traces/src/decoder/precompiles.rs | 183 +++++++- crates/evm/traces/src/lib.rs | 2 +- crates/fmt/src/state/mod.rs | 9 - crates/fmt/src/state/sol.rs | 11 - crates/forge/Cargo.toml | 2 + crates/forge/src/cmd/clone.rs | 435 +++++++++++++++++-- crates/forge/tests/cli/cmd.rs | 27 ++ crates/forge/tests/cli/precompiles.rs | 172 ++++++++ crates/primitives/src/network/receipt.rs | 2 + crates/verify/src/lib.rs | 2 +- npm/env.d.ts | 4 +- npm/src/const.mjs | 2 +- testdata/default/cheats/RecordLogs.t.sol | 35 ++ testdata/utils/Vm.sol | 1 + 28 files changed, 1022 insertions(+), 205 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 369875f9373b3..614aa6a508f67 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -24,7 +24,7 @@ env: IMAGE_NAME: ${{ github.repository }} # Keep in sync with `release.yml`. - RUST_PROFILE: maxperf + RUST_PROFILE: dist RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5049cd617a0c1..85cc3e6f5859d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ env: IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} # Keep in sync with `docker-publish.yml`. - RUST_PROFILE: maxperf + RUST_PROFILE: dist RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer LAST_STABLE_VERSION: "v1.5.0" diff --git a/Cargo.lock b/Cargo.lock index ba6c0b99014d2..a27d890bc918a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1065,7 +1065,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1089,7 +1089,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3606,7 +3606,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3902,7 +3902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4096,17 +4096,16 @@ dependencies = [ ] [[package]] -name = "figment" -version = "0.10.19" +name = "figment2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +checksum = "4380ce44915a6227efbb61e3885bc1c8e99fb9820f5db612abfac2c5cfc46871" dependencies = [ "atomic", "parking_lot", - "pear", "serde", "tempfile", - "toml 0.8.23", + "toml_edit 0.23.10+spec-1.0.0", "uncased", "version_check", ] @@ -4247,6 +4246,7 @@ dependencies = [ "toml_edit 0.24.0+spec-1.1.0", "tower-http", "tracing", + "url", "watchexec", "watchexec-events", "watchexec-signals", @@ -4273,7 +4273,7 @@ dependencies = [ "serde_json", "solar-compiler", "thiserror 2.0.17", - "toml 0.9.11+spec-1.1.0", + "toml", "tracing", ] @@ -4288,7 +4288,7 @@ dependencies = [ "similar", "snapbox", "solar-compiler", - "toml 0.9.11+spec-1.1.0", + "toml", ] [[package]] @@ -4507,7 +4507,7 @@ dependencies = [ "serde_json", "solar-compiler", "thiserror 2.0.17", - "toml 0.9.11+spec-1.1.0", + "toml", "tracing", "walkdir", ] @@ -4759,7 +4759,7 @@ dependencies = [ "dirs", "dunce", "eyre", - "figment", + "figment2", "foundry-block-explorers", "foundry-compilers", "foundry-evm-networks", @@ -4782,7 +4782,7 @@ dependencies = [ "soldeer-core", "tempfile", "thiserror 2.0.17", - "toml 0.9.11+spec-1.1.0", + "toml", "toml_edit 0.24.0+spec-1.1.0", "tracing", "unit-prefix", @@ -5341,7 +5341,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result 0.4.1", ] @@ -5998,12 +5998,6 @@ dependencies = [ "str_stack", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inotify" version = "0.11.0" @@ -6107,7 +6101,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6537,7 +6531,7 @@ dependencies = [ "regex", "serde", "serde_json", - "toml 0.9.11+spec-1.1.0", + "toml", "tracing", ] @@ -6560,7 +6554,7 @@ dependencies = [ "serde_json", "shlex", "tempfile", - "toml 0.9.11+spec-1.1.0", + "toml", "topological-sort", "tracing", ] @@ -6924,7 +6918,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7406,29 +7400,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.114", -] - [[package]] name = "pem" version = "3.0.6" @@ -7841,19 +7812,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", - "version_check", - "yansi", -] - [[package]] name = "process-wrap" version = "8.2.1" @@ -8959,7 +8917,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9377,15 +9335,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.4" @@ -10196,7 +10145,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10236,7 +10185,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10522,18 +10471,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - [[package]] name = "toml" version = "0.9.11+spec-1.1.0" @@ -10542,22 +10479,13 @@ checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -10567,20 +10495,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.13.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow", -] - [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -10589,8 +10503,8 @@ checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", "winnow", @@ -10603,7 +10517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c740b185920170a6d9191122cafef7010bd6270a3824594bff6784c04d7f09e" dependencies = [ "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", "winnow", @@ -10618,12 +10532,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -11539,7 +11447,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0c43b235223fa..6da5c1359a06c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ strip = "none" [profile.bench] inherits = "profiling" -[profile.maxperf] +[profile.dist] inherits = "release" lto = "fat" codegen-units = 1 @@ -182,18 +182,62 @@ walkdir.opt-level = 3 # Override packages which aren't perf-sensitive for faster compilation speed and smaller binary size. [profile.release.package] -alloy-sol-macro-expander.opt-level = "z" -figment.opt-level = "z" -foundry-compilers-artifacts-solc.opt-level = "z" -foundry-config.opt-level = "z" -html5ever.opt-level = "z" -mdbook-driver.opt-level = "z" -prettyplease.opt-level = "z" -protobuf.opt-level = "z" -pulldown-cmark.opt-level = "z" -syn-solidity.opt-level = "z" -syn.opt-level = "z" -trezor-client.opt-level = "z" +alloy-sol-macro-expander.opt-level = "s" +figment2.opt-level = "s" +foundry-compilers-artifacts-solc.opt-level = "s" +foundry-config.opt-level = "s" +html5ever.opt-level = "s" +mdbook-driver.opt-level = "s" +prettyplease.opt-level = "s" +protobuf.opt-level = "s" +pulldown-cmark.opt-level = "s" +syn-solidity.opt-level = "s" +syn.opt-level = "s" +trezor-client.opt-level = "s" + +# Networking stack (not perf-critical for CLI tools). +reqwest.opt-level = "s" +hyper.opt-level = "s" +hyper-util.opt-level = "s" +h2.opt-level = "s" +rustls.opt-level = "s" +tower.opt-level = "s" +tower-http.opt-level = "s" + +# Doc generation / templating. +mdbook-core.opt-level = "s" +mdbook-html.opt-level = "s" +font-awesome-as-a-crate.opt-level = "s" +handlebars.opt-level = "s" + +# Watch mode. +watchexec.opt-level = "s" +watchexec-events.opt-level = "s" +watchexec-signals.opt-level = "s" +watchexec-supervisor.opt-level = "s" + +# Soldeer package manager. +soldeer-commands.opt-level = "s" +soldeer-core.opt-level = "s" + +# Parsing / editing (not perf-sensitive in CLI). +toml_edit.opt-level = "s" +toml.opt-level = "s" + +# Block explorers / verification. +foundry-block-explorers.opt-level = "s" + +# Misc CLI dependencies. +clap_builder.opt-level = "s" +clap_complete.opt-level = "s" +comfy-table.opt-level = "s" +indicatif.opt-level = "s" +dialoguer.opt-level = "s" +ratatui.opt-level = "s" +crossterm.opt-level = "s" + +# Alloy JSON (parsing, not hot path). +alloy-json-abi.opt-level = "s" [workspace.dependencies] anvil = { path = "crates/anvil" } @@ -342,7 +386,7 @@ dunce = "1" evm-disassembler = "0.6" evmole = "0.8" eyre = "0.6" -figment = "0.10" +figment = { package = "figment2", version = "0.11" } futures = { version = "0.3", default-features = false } hyper = "1.8" indicatif = "0.18" diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 53db1bc9670c4..3e783b7dbafaa 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -3354,11 +3354,10 @@ impl EthApi { .max_fee_per_gas() .is_none() .then(|| request.set_max_fee_per_gas(self.gas_price())); - // TODO: use suggested tip instead of 0 request .max_priority_fee_per_gas() .is_none() - .then(|| request.set_max_priority_fee_per_gas(Default::default())); + .then(|| request.set_max_priority_fee_per_gas(MIN_SUGGESTED_PRIORITY_FEE)); } if tx_type == FoundryTxType::Eip4844 { request.as_ref().max_fee_per_blob_gas().is_none().then(|| { diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index dd4a196831cea..5bf7e1afb5457 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1237,7 +1237,7 @@ async fn test_debug_trace_transaction_pre_state_tracer() { let expected = r#" { "0x0000000000000000000000000000000000000000": { - "balance": "0x0" + "balance": "1206031000000000" }, "0x5fbdb2315678afecb367f032d93f642f64180aa3": { "balance": "0x0", diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index c4a809f6c065c..5946d87986190 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3749,7 +3749,7 @@ Traces: │ │ │ │ └─ ← [Return] 0xc13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f │ │ │ ├─ [96] PRECOMPILES::sha256(0x424242424242424242424242424242424242424242424242424242424242424201000000c13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f) [staticcall] │ │ │ │ └─ ← [Return] 0xc544bd9a4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0 - │ │ │ ├─ [6900] 0x0000000000000000000000000000000000000100::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] + │ │ │ ├─ [6900] P256VERIFY::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 2134b95ac571d..67820b2030c23 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -6524,6 +6524,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getRecordedLogsJson", + "description": "Gets all the recorded logs, in JSON format.", + "declaration": "function getRecordedLogsJson() external view returns (string memory logsJson);", + "visibility": "external", + "mutability": "view", + "signature": "getRecordedLogsJson()", + "selector": "0x3b171111", + "selectorBytes": [ + 59, + 23, + 17, + 17 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getStateDiff", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 59b6d96eec2f1..640c97e3108fb 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -951,6 +951,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getRecordedLogs() external view returns (Log[] memory logs); + /// Gets all the recorded logs, in JSON format. + #[cheatcode(group = Evm, safety = Safe)] + function getRecordedLogsJson() external view returns (string memory logsJson); + // -------- Gas Metering -------- // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 70e5f66785999..b5e76e1148694 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -55,6 +55,18 @@ pub(crate) mod mapping; pub(crate) mod mock; pub(crate) mod prank; +/// JSON-serializable log entry for `getRecordedLogsJson`. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct LogJson { + /// The topics of the log, including the signature, if any. + topics: Vec, + /// The raw data of the log, hex-encoded with 0x prefix. + data: String, + /// The address of the log's emitter. + emitter: String, +} + /// Records storage slots reads and writes. #[derive(Clone, Debug, Default)] pub struct RecordAccess { @@ -403,6 +415,22 @@ impl Cheatcode for getRecordedLogsCall { } } +impl Cheatcode for getRecordedLogsJsonCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default(); + let json_logs: Vec<_> = logs + .into_iter() + .map(|log| LogJson { + topics: log.topics.iter().map(|t| format!("{t}")).collect(), + data: hex::encode_prefixed(&log.data), + emitter: format!("{}", log.emitter), + }) + .collect(); + Ok(serde_json::to_string(&json_logs)?.abi_encode()) + } +} + impl Cheatcode for pauseGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 61d954ef73956..67cc8d7e04762 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -12,7 +12,7 @@ pub struct ExtractConfigError { } impl ExtractConfigError { - /// Wraps the figment error + /// Wraps the figment error. pub fn new(error: figment::Error) -> Self { Self { error } } @@ -63,7 +63,7 @@ impl Error for ExtractConfigError { pub enum FoundryConfigError { /// An error thrown during toml parsing Toml(figment::Error), - /// Any other error thrown when constructing the config's figment + /// Any other error thrown when constructing the config's figment. Other(figment::Error), } diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs index 62f7397a12bf9..fd569dc30dc07 100644 --- a/crates/evm/core/src/precompiles.rs +++ b/crates/evm/core/src/precompiles.rs @@ -30,6 +30,30 @@ pub const BLAKE_2F: Address = address!("0x00000000000000000000000000000000000000 /// The PointEvaluation precompile address. pub const POINT_EVALUATION: Address = address!("0x000000000000000000000000000000000000000a"); +/// The BLS12-381 G1ADD precompile address. +pub const BLS12_G1ADD: Address = address!("0x000000000000000000000000000000000000000b"); + +/// The BLS12-381 G1MSM precompile address. +pub const BLS12_G1MSM: Address = address!("0x000000000000000000000000000000000000000c"); + +/// The BLS12-381 G2ADD precompile address. +pub const BLS12_G2ADD: Address = address!("0x000000000000000000000000000000000000000d"); + +/// The BLS12-381 G2MSM precompile address. +pub const BLS12_G2MSM: Address = address!("0x000000000000000000000000000000000000000e"); + +/// The BLS12-381 pairing check precompile address. +pub const BLS12_PAIRING_CHECK: Address = address!("0x000000000000000000000000000000000000000f"); + +/// The BLS12-381 map Fp to G1 precompile address. +pub const BLS12_MAP_FP_TO_G1: Address = address!("0x0000000000000000000000000000000000000010"); + +/// The BLS12-381 map Fp2 to G2 precompile address. +pub const BLS12_MAP_FP2_TO_G2: Address = address!("0x0000000000000000000000000000000000000011"); + +/// The P256VERIFY precompile address. +pub const P256_VERIFY: Address = address!("0x0000000000000000000000000000000000000100"); + /// Precompile addresses. pub const PRECOMPILES: &[Address] = &[ EC_RECOVER, @@ -42,4 +66,12 @@ pub const PRECOMPILES: &[Address] = &[ EC_PAIRING, BLAKE_2F, POINT_EVALUATION, + BLS12_G1ADD, + BLS12_G1MSM, + BLS12_G2ADD, + BLS12_G2MSM, + BLS12_PAIRING_CHECK, + BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, + P256_VERIFY, ]; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 8d889eca9a329..a76409308a0b3 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -964,11 +964,13 @@ impl Inspector> for InspectorStackRefMut<'_> if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { // Handle mocked functions, replace bytecode address with mock if matched. if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) { + let input_bytes = call.input.bytes(ecx); // Check if any mock function set for call data or if catch-all mock function set // for selector. - if let Some(target) = mocks.get(&call.input.bytes(ecx)).or_else(|| { - call.input.bytes(ecx).get(..4).and_then(|selector| mocks.get(selector)) - }) { + if let Some(target) = mocks + .get(&input_bytes) + .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector))) + { call.bytecode_address = *target; call.known_bytecode = None; } diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 4f377fe03c510..38f40b244c042 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -18,8 +18,9 @@ use foundry_evm_core::{ constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS}, decode::RevertDecoder, precompiles::{ - BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, - RIPEMD_160, SHA_256, + BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, + MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, }, }; use itertools::Itertools; @@ -183,6 +184,14 @@ impl CallTraceDecoder { (EC_PAIRING, "ECPairing".to_string()), (BLAKE_2F, "Blake2F".to_string()), (POINT_EVALUATION, "PointEvaluation".to_string()), + (BLS12_G1ADD, "BLS12_G1ADD".to_string()), + (BLS12_G1MSM, "BLS12_G1MSM".to_string()), + (BLS12_G2ADD, "BLS12_G2ADD".to_string()), + (BLS12_G2MSM, "BLS12_G2MSM".to_string()), + (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()), + (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()), + (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()), + (P256_VERIFY, "P256VERIFY".to_string()), ]), receive_contracts: Default::default(), fallback_contracts: Default::default(), diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index 7437c4eb1a3a1..fdbe744641cd7 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -2,8 +2,9 @@ use crate::{CallTrace, DecodedCallData}; use alloy_primitives::{Address, B256, U256, hex}; use alloy_sol_types::{SolCall, abi, sol}; use foundry_evm_core::precompiles::{ - BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, - RIPEMD_160, SHA_256, + BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, + MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, }; use itertools::Itertools; use revm_inspectors::tracing::types::DecodedCallTrace; @@ -33,6 +34,18 @@ interface Precompiles { /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success); /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h); /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value); + + // Prague BLS12-381 precompiles (EIP-2537) + /* 0x0b */ function bls12G1Add(bytes p1, bytes p2) returns (bytes result); + /* 0x0c */ function bls12G1Msm(bytes[] scalarsAndPoints) returns (bytes result); + /* 0x0d */ function bls12G2Add(bytes p1, bytes p2) returns (bytes result); + /* 0x0e */ function bls12G2Msm(bytes[] scalarsAndPoints) returns (bytes result); + /* 0x0f */ function bls12PairingCheck(bytes[] pairs) returns (bool success); + /* 0x10 */ function bls12MapFpToG1(bytes fp) returns (bytes result); + /* 0x11 */ function bls12MapFp2ToG2(bytes fp2) returns (bytes result); + + // Osaka precompiles (EIP-7212) + /* 0x100 */ function p256Verify(bytes32 hash, uint256 r, uint256 s, uint256 qx, uint256 qy) returns (bool success); } } use Precompiles::*; @@ -51,6 +64,14 @@ pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool { | EC_PAIRING | BLAKE_2F | POINT_EVALUATION + | BLS12_G1ADD + | BLS12_G1MSM + | BLS12_G2ADD + | BLS12_G2MSM + | BLS12_PAIRING_CHECK + | BLS12_MAP_FP_TO_G1 + | BLS12_MAP_FP2_TO_G2 + | P256_VERIFY ) } @@ -115,6 +136,14 @@ const PRECOMPILES: &[&dyn Precompile] = &[ &Ecpairing, &Blake2f, &PointEvaluation, + &Bls12G1Add, + &Bls12G1Msm, + &Bls12G2Add, + &Bls12G2Msm, + &Bls12PairingCheck, + &Bls12MapFpToG1, + &Bls12MapFp2ToG2, + &P256Verify, ]; struct Ecrecover; @@ -346,6 +375,156 @@ fn iter_to_string, T: std::fmt::Display>(iter: I) -> Strin format!("[{}]", iter.format(", ")) } +const G1_POINT_SIZE: usize = 128; +const G2_POINT_SIZE: usize = 256; +const SCALAR_SIZE: usize = 32; +const FP_SIZE: usize = 64; + +struct Bls12G1Add; +impl Precompile for Bls12G1Add { + fn address(&self) -> Address { + BLS12_G1ADD + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G1AddCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (p1, rest) = take_at_most(data, G1_POINT_SIZE); + let (p2, _) = take_at_most(rest, G1_POINT_SIZE); + Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)]) + } +} + +struct Bls12G1Msm; +impl Precompile for Bls12G1Msm { + fn address(&self) -> Address { + BLS12_G1MSM + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G1MsmCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G1_POINT_SIZE + SCALAR_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } +} + +struct Bls12G2Add; +impl Precompile for Bls12G2Add { + fn address(&self) -> Address { + BLS12_G2ADD + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G2AddCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (p1, rest) = take_at_most(data, G2_POINT_SIZE); + let (p2, _) = take_at_most(rest, G2_POINT_SIZE); + Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)]) + } +} + +struct Bls12G2Msm; +impl Precompile for Bls12G2Msm { + fn address(&self) -> Address { + BLS12_G2MSM + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G2MsmCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G2_POINT_SIZE + SCALAR_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } +} + +struct Bls12PairingCheck; +impl Precompile for Bls12PairingCheck { + fn address(&self) -> Address { + BLS12_PAIRING_CHECK + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12PairingCheckCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G1_POINT_SIZE + G2_POINT_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = bls12PairingCheckCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +struct Bls12MapFpToG1; +impl Precompile for Bls12MapFpToG1 { + fn address(&self) -> Address { + BLS12_MAP_FP_TO_G1 + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12MapFpToG1Call::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (fp, _) = take_at_most(data, FP_SIZE); + Ok(vec![hex::encode_prefixed(fp)]) + } +} + +struct Bls12MapFp2ToG2; +impl Precompile for Bls12MapFp2ToG2 { + fn address(&self) -> Address { + BLS12_MAP_FP2_TO_G2 + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12MapFp2ToG2Call::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (fp2, _) = take_at_most(data, G1_POINT_SIZE); + Ok(vec![hex::encode_prefixed(fp2)]) + } +} + +struct P256Verify; +impl Precompile for P256Verify { + fn address(&self) -> Address { + P256_VERIFY + } + + fn signature(&self, _: &[u8]) -> &'static str { + p256VerifyCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let p256VerifyCall { hash, r, s, qx, qy } = p256VerifyCall::abi_decode_raw(data)?; + Ok(vec![hash.to_string(), r.to_string(), s.to_string(), qx.to_string(), qy.to_string()]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = p256VerifyCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +fn take_at_most(data: &[u8], n: usize) -> (&[u8], &[u8]) { + let n = n.min(data.len()); + data.split_at(n) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 301454d43eeab..5e8c452c8695a 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -204,7 +204,7 @@ pub fn render_trace_arena_inner( with_storage_changes: bool, ) -> String { if shell::is_json() { - return serde_json::to_string(&arena.resolve_arena()).expect("Failed to write traces"); + return serde_json::to_string(&arena.resolve_arena()).expect("Failed to serialize traces"); } let mut w = TraceWriter::new(Vec::::new()) diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index ada5eadd55bf0..762ecdb4ce645 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -61,7 +61,6 @@ impl CallContext { #[derive(Debug, Default)] pub(super) struct CallStack { stack: Vec, - precall_size: usize, } impl Deref for CallStack { @@ -80,14 +79,6 @@ impl CallStack { self.stack.pop() } - pub(crate) fn add_precall(&mut self, size: usize) { - self.precall_size += size; - } - - pub(crate) fn reset_precall(&mut self) { - self.precall_size = 0; - } - pub(crate) fn is_nested(&self) -> bool { self.last().is_some_and(|call| call.is_nested()) } diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index 52fb7fe0172f1..a8c9bd6647de1 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -795,12 +795,8 @@ impl<'ast> State<'_, 'ast> { fits_alone && !self.has_comment_between(rhs.span.lo(), rhs.span.hi()); let force_break = overflows && fits_alone_no_cmnts; - // Set up precall size tracking if lhs_size <= space_left { self.neverbreak(); - self.call_stack.add_precall(lhs_size + 1); - } else { - self.call_stack.add_precall(space_left + self.config.tab_width); } // Handle comments before the RHS expression @@ -932,7 +928,6 @@ impl<'ast> State<'_, 'ast> { } self.var_init = cache; - self.call_stack.reset_precall(); } fn print_var(&mut self, var: &'ast ast::VariableDefinition<'ast>, is_var_def: bool) { @@ -1955,12 +1950,6 @@ impl<'ast> State<'_, 'ast> { self.end(); self.word(" ="); - // '(' + var + ', ' + var + ')' + ' =' - let vars_size = vars.iter().fold(0, |acc, var| { - acc + var.as_ref().unspan().map_or(0, |v| self.estimate_size(v.span)) + 2 - }) + 2; - self.call_stack.add_precall(vars_size); - if self.estimate_size(init_expr.span) + self.config.tab_width <= std::cmp::max(space_left, self.space_left()) { diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 515983876019c..8169750642c6c 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -88,6 +88,8 @@ watchexec-signals = "5.0" clearscreen = "4.0" evm-disassembler.workspace = true path-slash.workspace = true +reqwest = { workspace = true, features = ["json"] } +url.workspace = true # doc server axum = { workspace = true, features = ["ws"] } diff --git a/crates/forge/src/cmd/clone.rs b/crates/forge/src/cmd/clone.rs index 6cf3db9ff7ae9..97337907ac201 100644 --- a/crates/forge/src/cmd/clone.rs +++ b/crates/forge/src/cmd/clone.rs @@ -1,10 +1,13 @@ use super::{init::InitArgs, install::DependencyInstallOpts}; use alloy_primitives::{Address, Bytes, ChainId, TxHash}; -use clap::{Parser, ValueHint}; +use clap::{Parser, ValueEnum, ValueHint}; use eyre::Result; +use forge_verify::sourcify::SOURCIFY_URL; use foundry_block_explorers::{ Client, - contract::{ContractCreationData, ContractMetadata, Metadata}, + contract::{ + ContractCreationData, ContractMetadata, Metadata, SourceCodeEntry, SourceCodeMetadata, + }, errors::EtherscanError, }; use foundry_cli::{ @@ -22,11 +25,16 @@ use foundry_compilers::{ compilers::solc::Solc, }; use foundry_config::{Chain, Config}; +use reqwest::StatusCode; +use serde::Deserialize; use std::{ + collections::{BTreeMap, HashMap}, fs::read_dir, path::{Path, PathBuf}, time::Duration, }; +use tracing::trace; +use url::Url; /// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a /// cloned contract. The metadata can be serialized to a metadata file in the cloned project root. @@ -52,14 +60,25 @@ pub struct CloneMetadata { pub storage_layout: StorageLayout, } +/// Source explorer type for `forge clone`. +#[derive(Clone, Copy, Debug, ValueEnum, Default)] +pub enum SourceExplorer { + /// Use Etherscan API (default). + #[default] + Etherscan, + /// Use Sourcify API. + Sourcify, +} + /// CLI arguments for `forge clone`. /// -/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan) in the -/// following steps: +/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan, Sourcify) in +/// the following steps: /// 1. Fetch the contract source code from the block explorer. /// 2. Initialize a empty foundry project at the `root` directory specified in `CloneArgs`. /// 3. Dump the contract sources to the source directory. -/// 4. Update the `foundry.toml` configuration file with the compiler settings from Etherscan. +/// 4. Update the `foundry.toml` configuration file with the compiler settings from the block +/// explorer. /// 5. Try compile the cloned contract, so that we can get the original storage layout. This /// original storage layout is preserved in the `CloneMetadata` so that if the user later /// modifies the contract, it is possible to quickly check the storage layout compatibility with @@ -78,7 +97,7 @@ pub struct CloneArgs { #[arg(long)] pub no_remappings_txt: bool, - /// Keep the original directory structure collected from Etherscan. + /// Keep the original directory structure collected from the block explorer. /// /// If this flag is set, the directory structure of the cloned project will be kept as is. /// By default, the directory structure is re-orgnized to increase the readability, but may @@ -86,6 +105,18 @@ pub struct CloneArgs { #[arg(long)] pub keep_directory_structure: bool, + /// Source explorer to use for fetching contract data. + /// + /// Can be either "etherscan" (default) or "sourcify". + #[arg(long, default_value = "etherscan", value_name = "EXPLORER")] + pub source: SourceExplorer, + + /// Custom Sourcify API URL. + /// + /// Implies `--source sourcify`. + #[arg(long, value_name = "URL")] + pub sourcify_url: Option, + #[command(flatten)] pub etherscan: EtherscanOpts, @@ -95,19 +126,41 @@ pub struct CloneArgs { impl CloneArgs { pub async fn run(self) -> Result<()> { - let Self { address, root, install, etherscan, no_remappings_txt, keep_directory_structure } = - self; + let Self { + address, + root, + install, + etherscan, + no_remappings_txt, + keep_directory_structure, + source, + sourcify_url, + } = self; // step 0. get the chain and api key from the config let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); - let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new(chain, etherscan_api_key.clone())?; - - // step 1. get the metadata from client - sh_println!("Downloading the source code of {address} from Etherscan...")?; - let meta = Self::collect_metadata_from_client(address, &client).await?; + // If sourcify_url is specified, use Sourcify as the source + let source = if sourcify_url.is_some() { SourceExplorer::Sourcify } else { source }; + + // step 1. get the metadata from client based on source type + let (meta, explorer_name, sourcify_client) = match source { + SourceExplorer::Etherscan => { + let etherscan_api_key = + config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, etherscan_api_key.clone())?; + sh_println!("Downloading the source code of {address} from Etherscan...")?; + let meta = Self::collect_metadata_from_client(address, &client).await?; + (meta, "Etherscan", None) + } + SourceExplorer::Sourcify => { + let client = SourcifyClient::with_url(chain, sourcify_url.as_deref()); + sh_println!("Downloading the source code of {address} from Sourcify...")?; + let meta = Self::collect_metadata_from_client(address, &client).await?; + (meta, "Sourcify", Some(client)) + } + }; // step 2. initialize an empty project Self::init_an_empty_project(&root, install).await?; @@ -120,20 +173,31 @@ impl CloneArgs { .await?; // step 4. collect the compilation metadata - // if the etherscan api key is not set, we need to wait for 3 seconds between calls - sh_println!("Collecting the creation information of {address} from Etherscan...")?; - - if etherscan_api_key.is_empty() { - sh_warn!("Waiting for 5 seconds to avoid rate limit...")?; - tokio::time::sleep(Duration::from_secs(5)).await; + sh_println!("Collecting the creation information of {address} from {explorer_name}...")?; + + match source { + SourceExplorer::Etherscan => { + let etherscan_api_key = + config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, etherscan_api_key.clone())?; + if etherscan_api_key.is_empty() { + sh_warn!("Waiting for 5 seconds to avoid rate limit...")?; + tokio::time::sleep(Duration::from_secs(5)).await; + } + Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; + } + SourceExplorer::Sourcify => { + // Reuse the client from step 1 to benefit from cached creation data + let client = sourcify_client.expect("Sourcify client should exist"); + Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; + } } - Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; // step 5. git add and commit the changes if needed if install.commit { let git = Git::new(&root); git.add(Some("--all"))?; - let msg = format!("chore: forge clone {address}"); + let msg = format!("chore: forge clone {address} from {explorer_name}"); git.commit(&msg)?; } @@ -144,7 +208,7 @@ impl CloneArgs { /// /// * `address` - the address of the contract to be cloned. /// * `client` - the client of the block explorer. - pub(crate) async fn collect_metadata_from_client( + pub(crate) async fn collect_metadata_from_client( address: Address, client: &C, ) -> Result { @@ -175,12 +239,12 @@ impl CloneArgs { /// Collect the compilation metadata of the cloned contract. /// This function compiles the cloned contract and collects the compilation metadata. /// - /// * `meta` - the metadata of the contract (from Etherscan). + /// * `meta` - the metadata of the contract (from block explorer). /// * `chain` - the chain where the contract to be cloned locates. /// * `address` - the address of the contract to be cloned. /// * `root` - the root directory of the cloned project. /// * `client` - the client of the block explorer. - pub(crate) async fn collect_compilation_metadata( + pub(crate) async fn collect_compilation_metadata( meta: &Metadata, chain: Chain, address: Address, @@ -588,10 +652,10 @@ pub fn find_main_contract<'a>( rv.ok_or_else(|| eyre::eyre!("contract not found")) } -/// EtherscanClient is a trait that defines the methods to interact with Etherscan. +/// ExplorerClient is a trait that defines the methods to interact with block explorers. /// It is defined as a wrapper of the `foundry_block_explorers::Client` to allow mocking. #[cfg_attr(test, mockall::automock)] -pub(crate) trait EtherscanClient { +pub(crate) trait ExplorerClient { async fn contract_source_code( &self, address: Address, @@ -602,7 +666,7 @@ pub(crate) trait EtherscanClient { ) -> std::result::Result; } -impl EtherscanClient for Client { +impl ExplorerClient for Client { async fn contract_source_code( &self, address: Address, @@ -618,13 +682,322 @@ impl EtherscanClient for Client { } } +/// SourcifyClient is a client for interacting with Sourcify API. +pub(crate) struct SourcifyClient { + client: reqwest::Client, + chain: Chain, + base_url: String, + /// Whether the base_url already contains the full path (v2/contract/chain) + is_full_path: bool, + /// Cached creation data from the first API call + cached_creation_data: std::sync::Arc>>, +} + +impl SourcifyClient { + pub fn with_url(chain: Chain, verifier_url: Option<&str>) -> Self { + let (base_url, is_full_path) = match verifier_url { + Some(url) => ( + Url::parse(url).unwrap_or_else(|_| Url::parse(SOURCIFY_URL).unwrap()), + true, // Custom URL contains full path + ), + None => (Url::parse(SOURCIFY_URL).unwrap(), false), + }; + Self { + client: reqwest::Client::new(), + chain, + base_url: base_url.to_string().trim_end_matches('/').to_string(), + is_full_path, + cached_creation_data: std::sync::Arc::new(std::sync::Mutex::new(None)), + } + } + + fn get_contract_url(&self, address: Address, fields: &str) -> String { + if self.is_full_path { + // Custom URL already contains v2/contract/chain, just append address and fields + format!("{}/{}?fields={}", self.base_url, address, fields) + } else { + // Default URL, need to build full path + format!( + "{}/v2/contract/{}/{}?fields={}", + self.base_url, + self.chain.id(), + address, + fields + ) + } + } +} + +/// Sourcify API response for contract files. +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +#[allow(dead_code, clippy::large_enum_variant)] +enum SourcifyContractResponse { + Success(SourcifyContractData), + Error(SourcifyErrorResponse), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyContractData { + #[serde(default)] + sources: Option>, + #[serde(default)] + abi: Option, + #[serde(default)] + compilation: Option, + #[serde(default)] + #[allow(dead_code)] + creation_code: Option, + #[serde(default)] + #[allow(dead_code)] + deployed_bytecode: Option, + #[serde(default)] + #[allow(dead_code)] + runtime_bytecode: Option, + #[serde(default)] + deployment: Option, + // Additional fields that may be present in the response + #[serde(default)] + #[allow(dead_code)] + match_id: Option, + #[serde(default)] + #[allow(dead_code)] + creation_match: Option, + #[serde(default)] + #[allow(dead_code)] + runtime_match: Option, + #[serde(default)] + #[allow(dead_code)] + verified_at: Option, + #[serde(default)] + #[allow(dead_code)] + r#match: Option, + #[serde(default)] + #[allow(dead_code)] + chain_id: Option, + #[serde(default)] + #[allow(dead_code)] + address: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifySourceFile { + #[serde(default)] + content: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyCompilation { + #[serde(default)] + compiler_version: String, + #[serde(default)] + name: String, + #[serde(default)] + compiler_settings: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyDeployment { + #[serde(default)] + transaction_hash: Option, + #[serde(default)] + deployer: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyErrorResponse { + #[serde(default)] + custom_code: String, + #[serde(default)] + message: String, + #[serde(default)] + #[allow(dead_code)] + error_id: String, + // Error responses should not have sources field + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[allow(dead_code)] + sources: Option<()>, +} + +impl ExplorerClient for SourcifyClient { + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result { + // Request all fields including creation data to cache them + let url = self.get_contract_url(address, "sources,abi,compilation,deployment"); + let response = self.client.get(&url).send().await?; + + let status = response.status(); + trace!("Sourcify API response: status={:?}, url={}", status, url); + + match status { + StatusCode::NOT_FOUND => return Err(EtherscanError::ContractCodeNotVerified(address)), + StatusCode::TOO_MANY_REQUESTS => return Err(EtherscanError::RateLimitExceeded), + _ => {} + } + + // Read response body once + let response_text = response.text().await?; + trace!("Sourcify API response body: {}", response_text); + + if !status.is_success() { + return Err(EtherscanError::Unknown(format!( + "Sourcify API error (status {status}): {response_text}" + ))); + } + + // Use the untagged enum to properly handle both success and error responses + let response: SourcifyContractResponse = + serde_json::from_str(&response_text).map_err(|e| { + // Truncate response for error message to avoid huge output + let truncated = if response_text.len() > 500 { + format!("{}... (truncated)", &response_text[..500]) + } else { + response_text.clone() + }; + EtherscanError::Unknown(format!( + "Failed to parse Sourcify response: {e}. Response: {truncated}" + )) + })?; + + let data = match response { + SourcifyContractResponse::Success(data) => data, + SourcifyContractResponse::Error(error) => { + let error_msg = if error.custom_code.is_empty() && error.message.is_empty() { + "Unknown Sourcify API error".to_string() + } else { + format!("Sourcify API error: {} - {}", error.custom_code, error.message) + }; + return Err(EtherscanError::Unknown(error_msg)); + } + }; + + let sources_map = data.sources.ok_or_else(|| { + EtherscanError::Unknown("Sourcify response missing sources field".to_string()) + })?; + + // Convert sources map to SourceCodeMetadata::Sources format + let sources: HashMap = sources_map + .into_iter() + .map(|(path, source_file)| (path, SourceCodeEntry { content: source_file.content })) + .collect(); + + let source_code = SourceCodeMetadata::Sources(sources); + + let contract_name = data + .compilation + .as_ref() + .map(|c| c.name.clone()) + .unwrap_or_else(|| "Contract".to_string()); + + let compiler_version = + data.compilation.as_ref().map(|c| c.compiler_version.clone()).unwrap_or_default(); + + let abi = data.abi.map(|a| a.to_string()).unwrap_or_default(); + + // Cache creation data for later use in contract_creation_data + let tx_hash = data + .deployment + .as_ref() + .and_then(|d| d.transaction_hash.as_ref()) + .and_then(|h| h.parse().ok()) + .unwrap_or(TxHash::ZERO); + let creator = data + .deployment + .as_ref() + .and_then(|d| d.deployer.as_ref()) + .and_then(|a| a.parse().ok()) + .unwrap_or(Address::ZERO); + let creation_data = ContractCreationData { + contract_address: address, + contract_creator: creator, + transaction_hash: tx_hash, + }; + if let Ok(mut cache) = self.cached_creation_data.lock() { + *cache = Some(creation_data); + } + + // Extract compiler_settings from compilation if available + let constructor_arguments = Bytes::default(); + let optimization_used = data + .compilation + .as_ref() + .and_then(|c| c.compiler_settings.as_ref()) + .and_then(|s| s.get("optimizer")) + .and_then(|o| o.get("enabled")) + .and_then(|e| e.as_bool()) + .map(|b| if b { 1 } else { 0 }) + .unwrap_or(0); + + let runs = data + .compilation + .as_ref() + .and_then(|c| c.compiler_settings.as_ref()) + .and_then(|s| s.get("optimizer")) + .and_then(|o| o.get("runs")) + .and_then(|r| r.as_u64()) + .unwrap_or(0); + + Ok(ContractMetadata { + items: vec![Metadata { + source_code, + abi, + contract_name, + compiler_version, + optimization_used, + runs, + constructor_arguments, + evm_version: String::new(), + library: String::new(), + license_type: String::new(), + proxy: 0, + implementation: None, + swarm_source: String::new(), + }], + }) + } + + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result { + // Check cache first + if let Ok(cache) = self.cached_creation_data.lock() + && let Some(ref cached_data) = *cache + && cached_data.contract_address == address + { + return Ok(*cached_data); + } + + // If cache is empty or address doesn't match, use fallback values + // This should rarely happen since we cache in contract_source_code + trace!("Creation data not in cache for address {address}, using fallback values"); + let creation_data = ContractCreationData { + contract_address: address, + contract_creator: Address::ZERO, + transaction_hash: TxHash::ZERO, + }; + if let Ok(mut cache) = self.cached_creation_data.lock() { + *cache = Some(creation_data); + } + + Ok(creation_data) + } +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::hex; use foundry_compilers::CompilerContract; use foundry_test_utils::rpc::next_etherscan_api_key; - use std::collections::BTreeMap; #[expect(clippy::disallowed_macros)] fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { @@ -652,7 +1025,7 @@ mod tests { }); } - fn mock_etherscan(address: Address) -> impl super::EtherscanClient { + fn mock_etherscan(address: Address) -> impl super::ExplorerClient { // load mock data let mut mocked_data = BTreeMap::new(); let data_folder = @@ -679,7 +1052,7 @@ mod tests { let (metadata, creation_data) = mocked_data.get(&address).unwrap(); let metadata = metadata.clone(); let creation_data = *creation_data; - let mut mocked_client = super::MockEtherscanClient::new(); + let mut mocked_client = super::MockExplorerClient::new(); mocked_client .expect_contract_source_code() .times(1) diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 844caaa5f15a0..e0b2c43c499ac 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -742,6 +742,33 @@ forgetest!(can_clone_quiet, |prj, cmd| { .assert_empty_stdout(); }); +// checks that clone works with sourcify +forgetest!(can_clone_sourcify, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args(["clone", "--source", "sourcify", "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Downloading the source code of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify... +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project +Collecting the creation information of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + // checks that clone works with --no-remappings-txt forgetest!(can_clone_no_remappings_txt, |prj, cmd| { prj.wipe(); diff --git a/crates/forge/tests/cli/precompiles.rs b/crates/forge/tests/cli/precompiles.rs index 3ffda2d5ce414..505a6f7b04800 100644 --- a/crates/forge/tests/cli/precompiles.rs +++ b/crates/forge/tests/cli/precompiles.rs @@ -3,6 +3,178 @@ use foundry_evm_networks::NetworkConfigs; use foundry_test_utils::str; +forgetest_init!(precompile_trace_decoding, |prj, cmd| { + prj.add_test( + "PrecompileTrace.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +contract PrecompileCaller { + constructor() { + // 0x01 - ECRECOVER + { + bytes32 hash = keccak256("test message"); + uint8 v = 27; + bytes32 r = bytes32(uint256(1)); + bytes32 s = bytes32(uint256(2)); + address(0x01).staticcall(abi.encode(hash, v, r, s)); + } + + // 0x02 - SHA256 + address(0x02).staticcall(abi.encodePacked("hello")); + + // 0x03 - RIPEMD160 + address(0x03).staticcall(abi.encodePacked("hello")); + + // 0x04 - IDENTITY (datacopy) + address(0x04).staticcall(abi.encodePacked("hello")); + + // 0x05 - MODEXP: compute 2^3 mod 5 = 3 + { + bytes memory modexpInput = abi.encodePacked( + uint256(1), // base length + uint256(1), // exponent length + uint256(1), // modulus length + uint8(2), // base = 2 + uint8(3), // exponent = 3 + uint8(5) // modulus = 5 + ); + address(0x05).staticcall(modexpInput); + } + + // 0x06 - BN254 ADD (ecadd): P + O = P + { + uint256 g1x = 1; + uint256 g1y = 2; + uint256 zerox = 0; + uint256 zeroy = 0; + address(0x06).staticcall(abi.encode(g1x, g1y, zerox, zeroy)); + } + + // 0x07 - BN254 MUL (ecmul): 1 * G = G + { + uint256 g1x = 1; + uint256 g1y = 2; + uint256 scalar = 1; + address(0x07).staticcall(abi.encode(g1x, g1y, scalar)); + } + + // 0x08 - BN254 PAIRING: empty input returns success (1) + address(0x08).staticcall(""); + + // 0x09 - BLAKE2F + { + bytes memory blake2fInput = new bytes(213); + blake2fInput[3] = 0x0c; // 12 rounds + bytes8[8] memory iv = [ + bytes8(0x6a09e667f3bcc908), + bytes8(0xbb67ae8584caa73b), + bytes8(0x3c6ef372fe94f82b), + bytes8(0xa54ff53a5f1d36f1), + bytes8(0x510e527fade682d1), + bytes8(0x9b05688c2b3e6c1f), + bytes8(0x1f83d9abfb41bd6b), + bytes8(0x5be0cd19137e2179) + ]; + for (uint256 i = 0; i < 8; i++) { + for (uint256 j = 0; j < 8; j++) { + blake2fInput[4 + i * 8 + j] = iv[i][j]; + } + } + blake2fInput[212] = 0x01; + address(0x09).staticcall(blake2fInput); + } + + // 0x0B - BLS12-381 G1 ADD (two points at infinity) + address(0x0B).staticcall(new bytes(256)); + + // 0x0C - BLS12-381 G1 MSM + address(0x0C).staticcall(new bytes(160)); + + // 0x0D - BLS12-381 G2 ADD (two points at infinity) + address(0x0D).staticcall(new bytes(512)); + + // 0x0E - BLS12-381 G2 MSM + address(0x0E).staticcall(new bytes(288)); + + // 0x0F - BLS12-381 PAIRING (G1 + G2 infinity points) + address(0x0F).staticcall(new bytes(384)); + + // 0x10 - BLS12-381 MAP FP TO G1 + address(0x10).staticcall(new bytes(64)); + + // 0x11 - BLS12-381 MAP FP2 TO G2 + address(0x11).staticcall(new bytes(128)); + + // 0x100 - P256VERIFY (secp256r1) + address(0x100).staticcall(new bytes(160)); + } +} + +contract PrecompileTraceTest is Test { + function test_precompile_traces() public { + new PrecompileCaller(); + } +} + "#, + ); + + cmd.args(["test", "--mt", "test_precompile_traces", "-vvvv", "--evm-version", "osaka"]) + .assert_success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/PrecompileTrace.t.sol:PrecompileTraceTest +[PASS] test_precompile_traces() ([GAS]) +Traces: + [..] PrecompileTraceTest::test_precompile_traces() + ├─ [..] → new PrecompileCaller@[..] + │ ├─ [..] PRECOMPILES::ecrecover(0xea83cdcdd06bf61e414054115a551e23133711d0507dcbc07a4bab7dc4581935, 27, 1, 2) [staticcall] + │ │ └─ ← [Return] 0xBe038042508C42Df7b2A529cd4Cc0a9447c7D2b6 + │ ├─ [..] PRECOMPILES::sha256(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 + │ ├─ [..] PRECOMPILES::ripemd(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x000000000000000000000000108f07b838241261 + │ ├─ [..] PRECOMPILES::identity(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x68656c6c6f + │ ├─ [..] PRECOMPILES::modexp(1, 1, 1, 0x02, 0x03, 0x05) [staticcall] + │ │ └─ ← [Return] 0x03 + │ ├─ [..] PRECOMPILES::ecadd(1, 2, 0, 0) [staticcall] + │ │ └─ ← [Return] (1, 2) + │ ├─ [..] PRECOMPILES::ecmul(1, 2, 1) [staticcall] + │ │ └─ ← [Return] (1, 2) + │ ├─ [..] PRECOMPILES::ecpairing() [staticcall] + │ │ └─ ← [Return] true + │ ├─ [..] PRECOMPILES::blake2f(12, [633244976228469098, 4298627039875721147, 3168446158426304060, 17381112106731261861, 15096882533739138641, 2264253069420660123, 7763433881832358687, 8728396173323133019], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0], 1) [staticcall] + │ │ └─ ← [Return] 0x1a48bfec594a1b13bb024be345656b8af895d662ccbc3f39fb5ecf2ef05942b5acace594cb81cdff6044b5bfaabfea105168676ce5753f6bb559ce3f92ad4850 + │ ├─ [..] PRECOMPILES::bls12G1Add(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G1Msm(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G2Add(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G2Msm(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12PairingCheck(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] true + │ ├─ [..] PRECOMPILES::bls12MapFpToG1(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000011a9a0372b8f332d5c30de9ad14e50372a73fa4c45d5f2fa5097f2d6fb93bcac592f2e1711ac43db0519870c7d0ea41500000000000000000000000000000000092c0f994164a0719f51c24ba3788de240ff926b55f58c445116e8bc6a47cd63392fd4e8e22bdf9feaa96ee773222133 + │ ├─ [..] PRECOMPILES::bls12MapFp2ToG2(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000018320896ec9eef9d5e619848dc29ce266f413d02dd31d9b9d44ec0c79cd61f18b075ddba6d7bd20b7ff27a4b324bfce000000000000000000000000000000000a67d12118b5a35bb02d2e86b3ebfa7e23410db93de39fb06d7025fa95e96ffa428a7a27c3ae4dd4b40bd251ac658892000000000000000000000000000000000260e03644d1a2c321256b3246bad2b895cad13890cbe6f85df55106a0d334604fb143c7a042d878006271865bc359410000000000000000000000000000000004c69777a43f0bda07679d5805e63f18cf4e0e7c6112ac7f70266d199b4f76ae27c6269a3ceebdae30806e9a76aadf5c + │ ├─ [..] P256VERIFY::fulfillBasicOrder_efficient_6GL6yc() [staticcall] + │ │ └─ ← [Return] + │ └─ ← [Return] 62 bytes of code + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + // tests transfer using celo precompile. // forgetest_init!(celo_transfer, |prj, cmd| { diff --git a/crates/primitives/src/network/receipt.rs b/crates/primitives/src/network/receipt.rs index d671dbb3d5535..9facb404b8c87 100644 --- a/crates/primitives/src/network/receipt.rs +++ b/crates/primitives/src/network/receipt.rs @@ -6,6 +6,7 @@ use alloy_serde::WithOtherFields; use derive_more::AsRef; use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; use serde::{Deserialize, Serialize}; +use tempo_primitives::TEMPO_TX_TYPE_ID; use crate::FoundryReceiptEnvelope; @@ -118,6 +119,7 @@ impl TryFrom for FoundryTxReceipt { 0x02 => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom), 0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), 0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), + TEMPO_TX_TYPE_ID => FoundryReceiptEnvelope::Tempo(receipt_with_bloom), 0x7E => { // Construct the deposit receipt, extracting optional deposit fields // These fields may not be present in all receipts, so missing/invalid diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs index 85f8a7a1c830e..8e78c1bffde5f 100644 --- a/crates/verify/src/lib.rs +++ b/crates/verify/src/lib.rs @@ -19,7 +19,7 @@ pub use bytecode::VerifyBytecodeArgs; pub mod retry; pub use retry::RetryArgs; -mod sourcify; +pub mod sourcify; pub mod verify; pub use verify::{VerifierArgs, VerifyArgs, VerifyCheckArgs}; diff --git a/npm/env.d.ts b/npm/env.d.ts index 6df532a23210a..828acd8e5a17b 100644 --- a/npm/env.d.ts +++ b/npm/env.d.ts @@ -25,8 +25,8 @@ interface ImportMetaEnv { readonly VERSION_NAME: string // release.yml#jobs:release:strategy:matrix:include:-|platform readonly PLATFORM_NAME: 'linux' | 'darwin' | 'win32' - // `debug` / `release` / `maxperf` # <- always `maxperf` - readonly PROFILE: 'debug' | 'release' | 'maxperf' + // `debug` / `release` / `maxperf` / `dist` + readonly PROFILE: 'debug' | 'release' | 'maxperf' | 'dist' // Used for local testing/development only readonly REGISTRY_URL: string diff --git a/npm/src/const.mjs b/npm/src/const.mjs index d07978918c4eb..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -5,7 +5,7 @@ import { URL } from 'node:url' * @typedef {'amd64' | 'arm64'} Arch * @typedef {'linux' | 'darwin' | 'win32'} Platform * @typedef {'forge' | 'cast' | 'anvil' | 'chisel'} Tool - * @typedef {'debug' | 'release' | 'maxperf'} Profile + * @typedef {'debug' | 'release' | 'maxperf' | 'dist'} Profile */ /** @type {readonly Tool[]} */ diff --git a/testdata/default/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol index 5927d5af23866..a21e7f0a06304 100644 --- a/testdata/default/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -188,6 +188,41 @@ contract RecordLogsTest is Test { assertEq(entries[2].emitter, emitter2.getEmitterAddr()); } + function testRecordedLogsJson() public { + bytes memory testData = "Event Data in String"; + + vm.recordLogs(); + emitter.emitEvent(1, 2, 3, testData); + string memory logsJson = vm.getRecordedLogsJson(); + + // Verify JSON structure and values + assertGt(bytes(logsJson).length, 0); + + // Verify emitter address + string memory emitterAddr = vm.parseJsonString(logsJson, "[0].emitter"); + assertEq(vm.parseAddress(emitterAddr), address(emitter)); + + // Verify topics - first topic is event signature + string[] memory topics = vm.parseJsonStringArray(logsJson, "[0].topics"); + assertEq(topics.length, 4); + assertEq(vm.parseBytes32(topics[0]), keccak256("LogTopic123(uint256,uint256,uint256,bytes)")); + assertEq(vm.parseBytes32(topics[1]), bytes32(uint256(1))); + assertEq(vm.parseBytes32(topics[2]), bytes32(uint256(2))); + assertEq(vm.parseBytes32(topics[3]), bytes32(uint256(3))); + + // Verify data is hex-encoded + string memory data = vm.parseJsonString(logsJson, "[0].data"); + assertGt(bytes(data).length, 2); // At least "0x" + } + + function testRecordedLogsJsonEmpty() public { + vm.recordLogs(); + string memory logsJson = vm.getRecordedLogsJson(); + + // Empty array + assertEq(logsJson, "[]"); + } + function testRecordsConsumednAsRead() public { Vm.Log[] memory entries; diff --git a/testdata/utils/Vm.sol b/testdata/utils/Vm.sol index 8cced251493aa..e06ed8facda10 100644 --- a/testdata/utils/Vm.sol +++ b/testdata/utils/Vm.sol @@ -319,6 +319,7 @@ interface Vm { function getNonce(Wallet calldata wallet) external view returns (uint64 nonce); function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader); function getRecordedLogs() external view returns (Log[] memory logs); + function getRecordedLogsJson() external view returns (string memory logsJson); function getStateDiff() external view returns (string memory diff); function getStateDiffJson() external view returns (string memory diff); function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses); From 9c5e4e72529aa6115bad2be1ca80aaaaf4b0b024 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:09:24 +0700 Subject: [PATCH 238/391] Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/npm.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index ae5642a6e1baf..0ab853e0520d0 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -233,9 +233,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" From 2ad832aab599625b84a446e3c6b041a920086dd8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 23 Jan 2026 08:11:49 +0700 Subject: [PATCH 239/391] Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/Docker.yml | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/Docker.yml diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From e3d4029ef71342a1344f9b5b84cd112f4631927b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:23:49 +0700 Subject: [PATCH 240/391] Potential fix for code scanning alert no. 108: Artifact poisoning (#345) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/npm.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 0ab853e0520d0..6ef9898e62587 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -226,6 +226,7 @@ jobs: RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | set -euo pipefail @@ -245,7 +246,21 @@ jobs: esac PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" - echo "Preparing to publish package from: $PACKAGE_DIR" + EXPECTED_PKG_NAME="${TOOL}-${PLATFORM}-${ARCH}" + + # Ensure corresponding artifact directory exists in the isolated artifact dir + if [[ -z "${ARTIFACT_DIR:-}" ]]; then + echo "ERROR: ARTIFACT_DIR is not set; refusing to publish." >&2 + exit 1 + fi + + EXPECTED_ARTIFACT_PATH="${ARTIFACT_DIR}/${EXPECTED_PKG_NAME}" + if [[ ! -d "$EXPECTED_ARTIFACT_PATH" ]]; then + echo "ERROR: Expected artifact directory not found at: $EXPECTED_ARTIFACT_PATH" >&2 + exit 1 + fi + + echo "Preparing to publish package from: $PACKAGE_DIR (artifact: $EXPECTED_ARTIFACT_PATH)" if [[ ! -d "$PACKAGE_DIR" ]]; then echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 From b8eb87b1129ee13407177b2500dc93e00be182a4 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:46:11 +0700 Subject: [PATCH 241/391] Potential fix for code scanning alert no. 110: Uncontrolled data used in path expression (#347) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- crates/test-utils/src/script.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 07413fef5495c..f20dae571cef3 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -118,6 +118,7 @@ impl ScriptTester { fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); + let canonical_from_dir = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { @@ -138,9 +139,9 @@ impl ScriptTester { // Skip invalid (potentially dangerous) file names continue; } - // Verify canonicalized file is in from_dir to avoid symlink traversal + // Verify canonicalized file is in canonical_from_dir to avoid symlink traversal if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { + if !canonical_file.starts_with(&canonical_from_dir) { continue; } } else { From c97ebb32ec4b5f84897f9048488a94b2959e9cab Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 00:07:31 +0700 Subject: [PATCH 242/391] benches\LATEST.md --- benches/LATEST.md | 71 +--- sleep.json | 955 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 968 insertions(+), 58 deletions(-) create mode 100644 sleep.json diff --git a/benches/LATEST.md b/benches/LATEST.md index 7ea1049a2ac41..2ddf3b8c922f1 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,73 +1,28 @@ # Foundry Benchmark Results -**Date**: 2025-10-02 12:14:23 +**Date**: 2026-01-27 03:38:34 -## Repositories Tested +## Summary -1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) -4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) - -## Foundry Versions - -- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) -- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) - -## Forge Test - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 3.17 s | 2.94 s | -| solady | 2.28 s | 2.10 s | -| Uniswap-v4-core | 7.27 s | 6.13 s | -| sparkdotfi-spark-psm | 43.04 s | 44.08 s | +Benchmarked 2 Foundry versions across 1 repositories. -## Forge Fuzz Test +### Repositories Tested -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------ | ---------- | -| ithacaxyz-account | 3.18 s | 3.02 s | -| solady | 2.39 s | 2.24 s | -| Uniswap-v4-core | 6.84 s | 6.20 s | -| sparkdotfi-spark-psm | 3.07 s | 2.72 s | - -## Forge Test (Isolated) - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| solady | 2.26 s | 2.41 s | -| Uniswap-v4-core | 7.22 s | 7.71 s | -| sparkdotfi-spark-psm | 45.53 s | 50.49 s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -## Forge Build (No Cache) +### Foundry Versions -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 9.16 s | 9.08 s | -| solady | 14.62 s | 14.69 s | -| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | -| sparkdotfi-spark-psm | 13.17 s | 13.14 s | +- **stable**: forge Version: 1.5.0-dev (6e718be 2025-12-07) +- **nightly**: forge Version: 1.5.0-dev (6e718be 2025-12-07) ## Forge Build (With Cache) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 0.156 s | 0.113 s | -| solady | 0.089 s | 0.094 s | -| Uniswap-v4-core | 0.133 s | 0.127 s | -| sparkdotfi-spark-psm | 0.173 s | 0.131 s | - -## Forge Coverage - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | -------- | ---------- | -| ithacaxyz-account | 14.91 s | 13.34 s | -| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | -| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | +| Repository | stable | nightly | +|------------|----------|----------| +| ithacaxyz-account | 0.345 s | 0.279 s | ## System Information -- **OS**: macos +- **OS**: linux - **CPU**: 8 -- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) +- **Rustc**: rustc 1.93.0 (254b59607 2026-01-19) diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} From e0b64ca9f339c880a88c1c7e0a4b49597c67a77e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:16:04 +0700 Subject: [PATCH 243/391] Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/LATEST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/LATEST.md b/benches/LATEST.md index 2ddf3b8c922f1..00776abc94003 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -4,7 +4,7 @@ ## Summary -Benchmarked 2 Foundry versions across 1 repositories. +Benchmarked 2 Foundry versions across 1 repository. ### Repositories Tested From 4d4b5fc510a0631104062ca0efe18630a0851558 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 12:02:39 +0700 Subject: [PATCH 244/391] Potential fix for code scanning alert no. 102: Artifact poisoning (#351) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/npm.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 6ef9898e62587..3bfadac860785 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -164,15 +164,29 @@ jobs: # Derive RELEASE_VERSION from any foundry artifact we downloaded # Expected names: foundry___.{tar.gz,zip} - first_file=$(ls "$ARTIFACT_DIR"/foundry_* 2>/dev/null | head -n1 || true) - if [[ -z "${first_file}" ]]; then - echo "No foundry artifacts found to publish" >&2 + shopt -s nullglob + foundry_files=("$ARTIFACT_DIR"/foundry_*) + shopt -u nullglob + + if [[ ${#foundry_files[@]} -eq 0 ]]; then + echo "No foundry artifacts found to publish in $ARTIFACT_DIR" >&2 exit 1 fi + + first_file="${foundry_files[0]}" version_part=$(basename "$first_file") version_part=${version_part#foundry_} - export RELEASE_VERSION=${version_part%%_*} - echo "Detected RELEASE_VERSION=$RELEASE_VERSION" + RELEASE_VERSION=${version_part%%_*} + + # Validate derived RELEASE_VERSION to mitigate artifact poisoning + # Require a sane version format, e.g. 1.2.3 or 1.2.3-beta.1 + if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+(\.[0-9]+)*(-[0-9A-Za-z.-]+)?$ ]]; then + echo "ERROR: Derived RELEASE_VERSION '$RELEASE_VERSION' from artifact '$first_file' is not a valid version" >&2 + exit 1 + fi + + export RELEASE_VERSION + echo "Detected RELEASE_VERSION=$RELEASE_VERSION from artifact $first_file" printf 'RELEASE_VERSION=%s\n' "$RELEASE_VERSION" >>"$GITHUB_OUTPUT" From 71a26eea6cce80052b3a82da9aa7c9aa0d987b09 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 12:04:35 +0700 Subject: [PATCH 245/391] benches\LATEST.md (#350) * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- benches/LATEST.md | 71 +--- sleep.json | 955 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 968 insertions(+), 58 deletions(-) create mode 100644 sleep.json diff --git a/benches/LATEST.md b/benches/LATEST.md index 7ea1049a2ac41..00776abc94003 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,73 +1,28 @@ # Foundry Benchmark Results -**Date**: 2025-10-02 12:14:23 +**Date**: 2026-01-27 03:38:34 -## Repositories Tested +## Summary -1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) -4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) - -## Foundry Versions - -- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) -- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) - -## Forge Test - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 3.17 s | 2.94 s | -| solady | 2.28 s | 2.10 s | -| Uniswap-v4-core | 7.27 s | 6.13 s | -| sparkdotfi-spark-psm | 43.04 s | 44.08 s | +Benchmarked 2 Foundry versions across 1 repository. -## Forge Fuzz Test +### Repositories Tested -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------ | ---------- | -| ithacaxyz-account | 3.18 s | 3.02 s | -| solady | 2.39 s | 2.24 s | -| Uniswap-v4-core | 6.84 s | 6.20 s | -| sparkdotfi-spark-psm | 3.07 s | 2.72 s | - -## Forge Test (Isolated) - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| solady | 2.26 s | 2.41 s | -| Uniswap-v4-core | 7.22 s | 7.71 s | -| sparkdotfi-spark-psm | 45.53 s | 50.49 s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -## Forge Build (No Cache) +### Foundry Versions -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 9.16 s | 9.08 s | -| solady | 14.62 s | 14.69 s | -| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | -| sparkdotfi-spark-psm | 13.17 s | 13.14 s | +- **stable**: forge Version: 1.5.0-dev (6e718be 2025-12-07) +- **nightly**: forge Version: 1.5.0-dev (6e718be 2025-12-07) ## Forge Build (With Cache) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 0.156 s | 0.113 s | -| solady | 0.089 s | 0.094 s | -| Uniswap-v4-core | 0.133 s | 0.127 s | -| sparkdotfi-spark-psm | 0.173 s | 0.131 s | - -## Forge Coverage - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | -------- | ---------- | -| ithacaxyz-account | 14.91 s | 13.34 s | -| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | -| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | +| Repository | stable | nightly | +|------------|----------|----------| +| ithacaxyz-account | 0.345 s | 0.279 s | ## System Information -- **OS**: macos +- **OS**: linux - **CPU**: 8 -- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) +- **Rustc**: rustc 1.93.0 (254b59607 2026-01-19) diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} From cc693d32a187dc57a2d405601cdcf7c9586fe23f Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:35:41 +0700 Subject: [PATCH 246/391] Potential fix for code scanning alert no. 109: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index f20dae571cef3..f3adc9e7bb420 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -123,13 +123,21 @@ impl ScriptTester { fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); + // Canonicalize the file path and ensure it stays within canonical_from_dir + let canonical_file = match file.canonicalize() { + Ok(path) => path, + Err(_) => continue, + }; + if !canonical_file.starts_with(&canonical_from_dir) { + continue; + } // Only operate on regular files to avoid following symlinks or directories - let metadata = fs::symlink_metadata(&file)?; + let metadata = fs::symlink_metadata(&canonical_file)?; let ftype = metadata.file_type(); if !ftype.is_file() { continue; } - let name = match file.file_name() { + let name = match canonical_file.file_name() { Some(name) => name, None => continue, }; @@ -139,15 +147,7 @@ impl ScriptTester { // Skip invalid (potentially dangerous) file names continue; } - // Verify canonicalized file is in canonical_from_dir to avoid symlink traversal - if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&canonical_from_dir) { - continue; - } - } else { - continue; - } - fs::copy(&file, to_dir.join(name))?; + fs::copy(&canonical_file, to_dir.join(name))?; } Ok(()) } From 33588d6bdb3664a3ccea49f6ff4efe1a7f5bc6ce Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:38:35 +0700 Subject: [PATCH 247/391] Update docker.yml --- .github/workflows/docker.yml | 104 +++++++++++------------------------ 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e994f94e7085c..5a2330e7d5d62 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,100 +1,62 @@ name: Docker -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - on: - schedule: - - cron: '21 12 * * *' push: - branches: [ "master" ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] + tags: ["*"] + branches: + - "main" pull_request: - branches: [ "master" ] + branches: ["**"] env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag IMAGE_NAME: ${{ github.repository }} - + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} jobs: build: - - name: Build the Docker image - run: docker build . --file path/to/Dockerfile --tag my-image-name:$(date +%s) - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write + runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Install the cosign tool except on PR - # https://github.com/sigstore/cosign-installer - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 - with: - cosign-release: 'v2.2.4' - - # Set up BuildKit Docker container builder to be able to build - # multi-platform images and export cache - # https://github.com/docker/setup-buildx-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v6 with: - context: ./ + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - - # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. - # https://github.com/sigstore/cosign - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From 76bba522f0409013e1e868a7c13b7dd4a533156d Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:43:24 +0700 Subject: [PATCH 248/391] Wagmi (e604566) (#344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update docker.yml --------- Signed-off-by: dependabot[bot] Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Desant pivo Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory --- .circleci/cargo.yml | 32 +++ .circleci/ci-web3-gamefi.yml | 26 +++ .circleci/ci.yml | 31 +++ .circleci/ci_cargo.yml | 37 +++ .circleci/ci_v1.yml | 31 +++ .circleci/config.yml | 32 +++ .circleci/dev_stage.yml | 70 ++++++ .circleci/web3_defi_gamefi.yml | 26 +++ .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 ++++ .deps/remix-tests/remix_tests.sol | 225 +++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 39 ++++ .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 ++ .github/workflows/apisec-scan.yml | 29 +++ .github/workflows/codeql.yml | 92 ++++++++ .github/workflows/deploy.yml | 27 +++ .github/workflows/docker.yml | 62 +++++ .github/workflows/google.yml | 117 ++++++++++ .github/workflows/npm.yml | 37 +++ .github/workflows/snyk-container.yml | 55 +++++ .gitmodules | 6 + benches/src/lib.rs | 16 +- counter/.github/workflows/test.yml | 43 ++++ counter/.gitignore | 14 ++ counter/README.md | 66 ++++++ counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 ++ counter/src/Counter.sol | 14 ++ counter/test/Counter.t.sol | 24 ++ crates/cast/src/cmd/run.rs | 14 +- crates/cast/tests/cli/main.rs | 28 +++ crates/cli/src/utils/suggestions.rs | 3 +- crates/common/src/contracts.rs | 2 +- crates/doc/src/parser/comment.rs | 6 + crates/doc/src/writer/as_doc.rs | 35 ++- crates/doc/src/writer/buf_writer.rs | 20 +- crates/evm/evm/src/executors/trace.rs | 28 ++- crates/forge/Cargo.toml | 1 + crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 +++++++++++ crates/lint/src/sol/gas/keccak.rs | 98 +------- crates/script-sequence/src/sequence.rs | 10 +- crates/script/src/simulate.rs | 10 +- crates/test-utils/src/script.rs | 29 ++- crates/verify/src/etherscan/standard_json.rs | 2 +- flake.nix | 10 +- npm/package.json | 2 +- npm/scripts/stage-from-artifact.mjs | 28 ++- npm/src/const.mjs | 30 ++- 52 files changed, 1588 insertions(+), 152 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/config.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/lint/src/linter.rs diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..4168efef0971f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..53a505774ac88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 7bb1ad13a42db..6347fe0fd59d2 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -233,9 +233,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/benches/src/lib.rs b/benches/src/lib.rs index d50f88ae2bd63..55bbd9762b1d9 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 492ce360e7c2e..0999aa55ec495 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -31,7 +31,7 @@ use foundry_evm::{ utils::configure_tx_env, }; use futures::TryFutureExt; -use revm::DatabaseRef; +use revm::{DatabaseRef, primitives::hardfork::SpecId}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -183,6 +183,8 @@ impl RunArgs { env.evm_env.cfg_env.limit_contract_code_size = None; env.evm_env.block_env.number = U256::from(tx_block_number); + let mut parent_beacon_block_root = None; + if let Some(block) = &block { env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); env.evm_env.block_env.beneficiary = block.header.beneficiary; @@ -191,6 +193,10 @@ impl RunArgs { env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); env.evm_env.block_env.gas_limit = block.header.gas_limit; + if env.evm_env.cfg_env.spec >= SpecId::CANCUN { + parent_beacon_block_root = block.header.parent_beacon_block_root; + } + // TODO: we need a smarter way to map the block to the corresponding evm_version for // commonly used chains if evm_version.is_none() { @@ -223,6 +229,12 @@ impl RunArgs { create2_deployer, None, )?; + + if let Some(parent_beacon_block_root) = parent_beacon_block_root { + let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().wrap_err("failed to convert block timestamp to u64")?; + executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; + } + let mut env = Env::new_with_spec_id( env.evm_env.cfg_env.clone(), env.evm_env.block_env.clone(), diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index a9147e2e284ff..7aafdceb8a402 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2998,6 +2998,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 06b2c29945417..f079d0f4fbf3d 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 431bf70df88f5..69f529c3eb6a2 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -155,6 +155,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index a215a95b581c7..455cd4082bb61 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,12 +1,11 @@ use crate::{ - CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, - GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, - document::{DocumentContent, read_context}, - helpers::function_signature, + document::{read_context, DocumentContent}, parser::ParseSource, - solang_ext::SafeUnwrap, writer::BufWriter, + CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput, + CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID, }; +use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; use solang_parser::pt::{Base, FunctionDefinition}; use std::path::Path; @@ -63,7 +62,7 @@ impl AsDoc for CommentsRef<'_> { // Write dev tags let devs = self.include_tag(CommentTag::Dev); for d in devs.iter() { - writer.write_dev_content(&d.value)?; + writer.write_italic(&d.value)?; writer.writeln()?; } @@ -107,7 +106,16 @@ impl AsDoc for Document { for item in items { let func = item.as_function().unwrap(); - let heading = function_signature(func).replace(',', ", "); + let mut heading = item.source.ident(); + if !func.params.is_empty() { + heading.push_str(&format!( + "({})", + func.params + .iter() + .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default()) + .join(", ") + )); + } writer.write_heading(&heading)?; writer.write_section(&item.comments, &item.code)?; } @@ -226,7 +234,15 @@ impl AsDoc for Document { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code)?; + + let filtered_comments: Comments = (*comments) + .iter() + .filter(|c| c.tag != CommentTag::Custom("variant".to_string())) + .cloned() + .collect::>() + .into(); + + writer.write_section(&filtered_comments, code)?; writer.try_write_variant_table(item, comments) })?; } @@ -292,10 +308,9 @@ impl Document { comments: &Comments, code: &str, ) -> Result<(), std::fmt::Error> { - let func_sign = function_signature(func); let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); let comments = - comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc)); + comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); // Write function name writer.write_heading(&func_name)?; diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index a62dff3f21582..e5dfbbaadce25 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,4 +1,4 @@ -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike}; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; use solang_parser::pt::{ EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, @@ -89,17 +89,6 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes dev content to the buffer, handling markdown lists properly. - /// If the content contains markdown lists, it formats them correctly. - /// Otherwise, it writes the content in italics. - pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { - for line in text.lines() { - writeln!(self.buf, "{line}")?; - } - - Ok(()) - } - /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -202,7 +191,8 @@ impl BufWriter { params: &EnumDefinition, comments: &Comments, ) -> fmt::Result { - let comments = comments.include_tags(&[CommentTag::Param]); + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); // There is nothing to write. if comments.is_empty() { @@ -215,7 +205,7 @@ impl BufWriter { self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; - for value in ¶ms.values { + for value in params.values.iter() { let param_name = value.as_ref().map(|v| v.name.clone()); let comment = param_name.as_ref().and_then(|name| { @@ -223,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index d951dde0d6663..7b63c019e848c 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -2,7 +2,7 @@ use crate::{ Env, executors::{Executor, ExecutorBuilder}, }; -use alloy_primitives::{Address, U256, map::HashMap}; +use alloy_primitives::{Address, FixedBytes, U256, address, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; use foundry_compilers::artifacts::EvmVersion; @@ -92,6 +92,32 @@ impl TracingExecutor { let chain = env.tx.chain_id.unwrap().into(); Ok((env, fork, chain, networks)) } + + /// Processes the beacon block root by storing it in the appropriate storage slots. + pub fn process_beacon_block_root( + &mut self, + block_timestamp: u64, + beacon_root: FixedBytes<32>, + ) -> eyre::Result<()> { + const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); + const HISTORY_BUFFER_LENGTH: u64 = 8192; + + let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; + let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; + + let timestamp_slot = U256::from(timestamp_index); + let root_slot = U256::from(root_index); + + self.set_storage_slot(BEACON_ROOTS_ADDRESS, timestamp_slot, U256::from(block_timestamp))?; + + self.set_storage_slot( + BEACON_ROOTS_ADDRESS, + root_slot, + U256::from_be_bytes(beacon_root.into()), + )?; + + Ok(()) + } } impl Deref for TracingExecutor { diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index cd552853cfcff..2142a87c5cbd4 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-rpc-types.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true revm.workspace = true diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index cb942510bbb49..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,103 +1,27 @@ use super::AsmKeccak256; use crate::{ - linter::{LateLintPass, LintContext}, + declare_forge_lint, + linter::EarlyLintPass, sol::{Severity, SolLint}, }; -use solar::{ - ast::{self as ast, Span}, - interface::kw, - sema::hir::{self}, -}; +use solar_ast::{Expr, ExprKind}; +use solar_interface::kw; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "use of inefficient hashing mechanism; consider using inline assembly" + "hash using inline assembly to save gas" ); -impl<'hir> LateLintPass<'hir> for AsmKeccak256 { - fn check_stmt( - &mut self, - ctx: &LintContext, - hir: &'hir hir::Hir<'hir>, - stmt: &'hir hir::Stmt<'hir>, - ) { - let check_expr_and_emit_lint = - |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { - if let Some(hash_arg) = extract_keccak256_arg(expr) { - self.emit_lint( - ctx, - hir, - stmt.span, - expr, - hash_arg, - AsmContext { _assign: assign, _is_return: is_return }, - ); - } - }; - - match stmt.kind { - hir::StmtKind::DeclSingle(var_id) => { - let var = hir.variable(var_id); - if let Some(init) = var.initializer { - // Constants should be optimized by the compiler, so no gas savings apply. - if !matches!(var.mutability, Some(hir::VarMut::Constant)) { - check_expr_and_emit_lint(init, var.name, false); - } +impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { + fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(expr, _) = &expr.kind { + if let ExprKind::Ident(ident) = &expr.kind { + if ident.name == kw::Keccak256 { + ctx.emit(&ASM_KECCAK256, expr.span); } } - // Expressions that don't (directly) assign to a variable - hir::StmtKind::Expr(expr) - | hir::StmtKind::Emit(expr) - | hir::StmtKind::Revert(expr) - | hir::StmtKind::DeclMulti(_, expr) - | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), - hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), - _ => (), } } } - -impl AsmKeccak256 { - /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. - fn emit_lint( - &self, - ctx: &LintContext, - _hir: &hir::Hir<'_>, - _stmt_span: Span, - call: &hir::Expr<'_>, - _hash: &hir::Expr<'_>, - _asm_ctx: AsmContext, - ) { - ctx.emit(&ASM_KECCAK256, call.span); - } -} - -/// If the expression is a call to `keccak256` with one argument, returns that argument. -fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { - let hir::ExprKind::Call( - callee, - hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, - .., - ) = &expr.kind - else { - return None; - }; - - let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { - matches!(builtin.name(), kw::Keccak256) - } else { - return None; - }; - - if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } -} - -// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- - -#[derive(Debug, Clone, Copy)] -struct AsmContext { - _assign: Option, - _is_return: bool, -} diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 5da88735a70ec..56b7c6b36f79c 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -12,7 +12,7 @@ use std::{ path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; - +# pub const DRY_RUN_DIR: &str = "dry-run"; #[derive(Clone, Serialize, Deserialize)] @@ -20,7 +20,7 @@ pub struct NestedValue { pub internal_type: String, pub value: String, } - +# /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted #[derive(Clone, Default, Serialize, Deserialize)] @@ -148,7 +148,7 @@ impl ScriptSequence { pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { self.receipts.push(receipt); } - + # /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); @@ -164,7 +164,7 @@ impl ScriptSequence { pub fn remove_pending(&mut self, tx_hash: TxHash) { self.pending.retain(|element| element != &tx_hash); } - + # /// Gets paths in the formats /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. @@ -219,7 +219,7 @@ impl ScriptSequence { .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } - +# /// Converts the `sig` argument into the corresponding file path. /// /// This accepts either the signature of the function or the raw calldata. diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index ab57786398428..0b2be58ca30d2 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,7 +9,6 @@ use crate::{ execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, }; -use alloy_chains::NamedChain; use alloy_network::TransactionBuilder; use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; @@ -352,12 +351,6 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); - // Get the native token symbol for the chain using NamedChain - let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() - .native_currency_symbol() - .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -381,7 +374,7 @@ impl FilledTransactionsState { sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; sh_println!("\n==========================")?; } else { sh_println!( @@ -391,7 +384,6 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, - "token_symbol": token_symbol, }) )?; } diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 45dbbbebdd73a..07413fef5495c 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 8a38485e48922..0471e3e42888e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -56,7 +56,7 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { let sources = Source::read_all_from(path, &["vy", "vyi"])?; let input = VyperInput::new( sources, - context.clone().compiler_settings.vyper, + context.compiler_settings.vyper.clone(), &context.compiler_version, ); diff --git a/flake.nix b/flake.nix index 5caaab9934aec..8cf41ed16d3a3 100644 --- a/flake.nix +++ b/flake.nix @@ -19,8 +19,7 @@ lib = pkgs.lib; toolchain = fenix.packages.${system}.stable.toolchain; - in - { + in { default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ pkg-config @@ -29,16 +28,13 @@ # test dependencies solc vyper - dprint nodejs ]; + buildInputs = lib.optionals pkgs.stdenv.isDarwin + [ pkgs.darwin.apple_sdk.frameworks.AppKit ]; packages = with pkgs; [ rust-analyzer-unwrapped ]; - # Remove the hardening added by nix to fix jmalloc compilation error. - # More info: https://github.com/tikv/jemallocator/issues/108 - hardeningDisable = [ "fortify" ]; - # Environment variables RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; diff --git a/npm/package.json b/npm/package.json index 2362816053f8c..d596a7930b609 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.0.2", "bun": "^1.3.1", "typescript": "^5.9.3" }, diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** From eaf1abe653e58669070007dbd2a3a11f4c44c67e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:00:43 +0700 Subject: [PATCH 249/391] Potential fix for code scanning alert no. 102: Artifact poisoning (#354) * Potential fix for code scanning alert no. 102: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .github/workflows/npm.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index df45e41a50295..741590095fec5 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -136,6 +136,36 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + - name: Validate Downloaded Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Validating artifacts in: $ARTIFACT_DIR" + + if [[ ! -d "$ARTIFACT_DIR" ]]; then + echo "ERROR: Artifact directory does not exist: $ARTIFACT_DIR" >&2 + exit 1 + fi + + if ! find "$ARTIFACT_DIR" -mindepth 1 -print -quit | grep -q .; then + echo "ERROR: Artifact directory is empty: $ARTIFACT_DIR" >&2 + exit 1 + fi + + # Reject files with suspicious paths (absolute paths or parent directory traversals) + # Use null-delimited paths to safely handle filenames with newlines or whitespace + while IFS= read -r -d '' path; do + rel="${path#"$ARTIFACT_DIR"/}" + if [[ "$rel" == /* ]] || [[ "$rel" == *".."* ]]; then + echo "ERROR: Suspicious artifact path detected: $rel" >&2 + exit 1 + fi + done < <(find "$ARTIFACT_DIR" -type f -print0) + + echo "Artifact validation completed successfully." + - name: Setup Bun uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2 with: From 3ff465c909d0ac9f933f3f5abe6e6491cbcafce9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:32:33 +0700 Subject: [PATCH 250/391] fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#357) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> --- crates/config/src/etherscan.rs | 63 ++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 5999825e94681..e24f92b7d931e 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -319,10 +319,17 @@ impl ResolvedEtherscanConfig { .with_client(client) .with_api_key(api_key) .with_cache(cache, Duration::from_secs(24 * 60 * 60)); - if let Some(browser_url) = browser_url { + if let Some(ref browser_url) = browser_url { client_builder = client_builder.with_url(browser_url)?; } - client_builder.chain(chain)?.build() + + // Use the provided URL (either custom from foundry.toml or chain's default from resolve()) + client_builder = client_builder.with_api_url(api_url.clone())?; + // Fallback: Use api_url as browser URL if browser_url is not set + if browser_url.is_none() { + client_builder = client_builder.with_url(api_url)?; + } + client_builder.build() } } @@ -478,10 +485,8 @@ mod tests { let config = resolved.remove("mainnet").unwrap().unwrap(); assert_eq!(config.key, "ABCDEFG"); let client = config.into_client().unwrap(); - assert_eq!( - client.etherscan_api_url().as_str(), - "https://api.etherscan.io/v2/api?chainid=1" - ); + // Custom URL should be used even when chain has a default URL + assert_eq!(client.etherscan_api_url().as_str(), "https://api.etherscan.io/api"); unsafe { std::env::remove_var(env); @@ -518,4 +523,50 @@ mod tests { let resolved = config.resolve(Some("base-sepolia")).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); } + + #[test] + fn can_create_client_with_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should work if a custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: Some("https://custom.api.url/verify/etherscan".to_string()), + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap().unwrap(); + let result = config.into_client(); + assert!( + result.is_ok(), + "Should succeed with custom URL even for chains without default Etherscan URLs" + ); + } + + #[test] + fn fails_without_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should fail if no custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: None, + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap(); + + assert!( + config.is_err(), + "Should fail: chains without default Etherscan URLs require custom URL" + ); + } } From 1528ab2395a035c59cde2334575860763df6b802 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:25:47 +0700 Subject: [PATCH 251/391] Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. --- .circleci/ci_deploy.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail From a737f7f3a7a0ca7e92f06370e92f6155d7fcc2cc Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:02:20 +0000 Subject: [PATCH 252/391] refactor(common): make ProviderBuilder generic over Network #13250 (#361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg --------- Signed-off-by: dependabot[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Matt D Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Tim Beiko Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/docker-publish.yml | 2 +- .github/workflows/nix.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 2 +- Cargo.lock | 188 +++--- Cargo.toml | 4 +- crates/anvil/core/src/eth/mod.rs | 10 +- crates/anvil/src/args.rs | 2 + crates/anvil/src/cmd.rs | 15 + crates/anvil/src/config.rs | 40 +- crates/anvil/src/eth/api.rs | 17 +- crates/anvil/src/eth/backend/fork.rs | 14 +- crates/anvil/src/eth/backend/mem/mod.rs | 118 +++- crates/anvil/src/eth/otterscan/api.rs | 10 +- crates/anvil/test-data/state-dump.json | 315 +++++++++- crates/anvil/tests/it/beacon_api.rs | 3 +- crates/anvil/tests/it/eip4844.rs | 47 +- crates/anvil/tests/it/state.rs | 1 + crates/anvil/tests/it/traces.rs | 80 ++- crates/cast/src/args.rs | 2 + crates/cast/src/lib.rs | 2 +- crates/cast/src/tx.rs | 2 - crates/cheatcodes/src/evm/fork.rs | 7 +- crates/chisel/src/args.rs | 2 + crates/cli-markdown/Cargo.toml | 20 + crates/cli-markdown/src/lib.rs | 582 ++++++++++++++++++ crates/cli/Cargo.toml | 1 + crates/cli/src/handler.rs | 4 +- crates/cli/src/opts/global.rs | 11 + crates/cli/src/utils/mod.rs | 27 +- crates/common/Cargo.toml | 1 + crates/common/src/provider/mod.rs | 86 ++- crates/config/src/lint.rs | 17 +- crates/config/src/utils.rs | 7 +- crates/debugger/src/node.rs | 7 +- crates/debugger/src/tui/draw.rs | 5 +- crates/evm/evm/src/executors/fuzz/mod.rs | 2 +- crates/evm/evm/src/executors/invariant/mod.rs | 8 +- crates/evm/fuzz/src/lib.rs | 2 - crates/evm/traces/src/folded_stack_trace.rs | 32 +- crates/fmt/src/state/sol.rs | 4 +- crates/fmt/testdata/CommentEmptyLine/fmt.sol | 5 + .../testdata/CommentEmptyLine/original.sol | 6 + crates/fmt/tests/formatter.rs | 26 + crates/forge/Cargo.toml | 1 + crates/forge/src/args.rs | 2 + crates/forge/src/cmd/eip712.rs | 2 +- crates/forge/src/cmd/test/mod.rs | 15 +- crates/forge/src/gas_report.rs | 26 +- crates/forge/src/result.rs | 35 +- crates/forge/tests/cli/cmd.rs | 152 ++--- crates/forge/tests/cli/script.rs | 4 +- crates/forge/tests/cli/test_cmd/fuzz.rs | 2 + .../tests/cli/test_cmd/invariant/common.rs | 28 + .../forge/tests/cli/test_cmd/invariant/mod.rs | 6 + .../tests/cli/test_cmd/invariant/target.rs | 8 + crates/forge/tests/cli/test_cmd/mod.rs | 4 + crates/forge/tests/cli/test_cmd/repros.rs | 2 + .../fixtures/SimpleContractTestVerbose.json | 5 + .../forge/tests/fixtures/invariant_traces.svg | 8 +- crates/lint/src/linter/early.rs | 2 +- crates/lint/src/linter/late.rs | 2 +- crates/lint/testdata/MixedCase.sol | 13 + crates/primitives/src/network/receipt.rs | 18 + crates/primitives/src/transaction/request.rs | 9 +- crates/test-utils/src/prj.rs | 14 +- crates/test-utils/src/rpc.rs | 10 +- crates/test-utils/src/util.rs | 48 +- crates/verify/src/etherscan/mod.rs | 17 +- flake.lock | 18 +- testdata/default/cheats/RpcUrls.t.sol | 2 +- typos.toml | 1 + 76 files changed, 1825 insertions(+), 379 deletions(-) create mode 100644 crates/cli-markdown/Cargo.toml create mode 100644 crates/cli-markdown/src/lib.rs create mode 100644 crates/fmt/testdata/CommentEmptyLine/fmt.sol create mode 100644 crates/fmt/testdata/CommentEmptyLine/original.sol diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 34622ca7979a5..53e29ecd33691 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -29,7 +29,7 @@ env: jobs: run-benchmarks: name: Run All Benchmarks - runs-on: foundry-runner + runs-on: depot-ubuntu-24.04-32 permissions: contents: write steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0ca528491f95..bdb7848f55f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: crate-ci/typos@a1d64977b4aa1709d6328d518aa753f4899352d8 # v1 + - uses: crate-ci/typos@93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c # v1 shellcheck: runs-on: depot-ubuntu-latest @@ -140,7 +140,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2 + - uses: taiki-e/install-action@650c5ca14212efbbf3e580844b04bdccf68dac31 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 566844094730d..51e625d0d6d28 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -44,7 +44,7 @@ jobs: # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Login into registry ${{ env.REGISTRY }} - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 7c348a9726f90..8d39e71112494 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: DeterminateSystems/update-flake-lock@727cc5b0b19bc265bd5ef28fc66bccb284473b5d # main + - uses: DeterminateSystems/update-flake-lock@5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 # main with: pr-title: "Update flake.lock" pr-labels: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b18435be45d22..a267433f27ec2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@439f79b5b5428107c7688c1d2b0e8bacc9b8792c # v6 + uses: mikepenz/release-changelog-builder-action@6faf020194b7c8853f9e55c4fd92e40b02122a04 # v6 with: configuration: "./.github/changelog.json" fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || env.STABLE_VERSION }} diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index 84029e775c2b4..858d1c8c43f2c 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2 + - uses: taiki-e/install-action@650c5ca14212efbbf3e580844b04bdccf68dac31 # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index ee17ace188413..d24fbfca3d1a0 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,7 +37,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2 + - uses: taiki-e/install-action@650c5ca14212efbbf3e580844b04bdccf68dac31 # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25e32aac98b5e..abb8b8363eece 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2 + - uses: taiki-e/install-action@650c5ca14212efbbf3e580844b04bdccf68dac31 # v2 with: tool: nextest diff --git a/Cargo.lock b/Cargo.lock index d9b1edda73916..7b638f17ed1a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,9 +143,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" +checksum = "14ff5ee5f27aa305bda825c735f686ad71bb65508158f059f513895abe69b8c3" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" +checksum = "8708475665cc00e081c085886e68eada2f64cfa08fc668213a9231655093d4de" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -411,9 +411,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" +checksum = "3b88cf92ed20685979ed1d8472422f0c6c2d010cec77caf63aaa7669cc1a7bc2" dependencies = [ "alloy-rlp", "arbitrary", @@ -437,7 +437,6 @@ dependencies = [ "rustc-hash", "serde", "sha3", - "tiny-keccak", ] [[package]] @@ -829,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" +checksum = "f5fa1ca7e617c634d2bd9fa71f9ec8e47c07106e248b9fcbd3eaddc13cabd625" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -843,9 +842,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" +checksum = "27c00c0c3a75150a9dc7c8c679ca21853a137888b4e1c5569f92d7e2b15b5102" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -855,16 +854,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", + "sha3", "syn 2.0.114", "syn-solidity", - "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" +checksum = "297db260eb4d67c105f68d6ba11b8874eec681caec5505eab8fbebee97f790bc" dependencies = [ "alloy-json-abi", "const-hex", @@ -880,9 +879,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af67a0b0dcebe14244fc92002cd8d96ecbf65db4639d479f5fcd5805755a4c27" +checksum = "94b91b13181d3bcd23680fd29d7bc861d1f33fbe90fdd0af67162434aeba902d" dependencies = [ "serde", "winnow", @@ -890,9 +889,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" +checksum = "fc442cc2a75207b708d481314098a0f8b6f7b58e3148dd8d8cc7407b0d6f9385" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1024,9 +1023,9 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.12.10" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15580ece6ea97cbf832d60ba19c021113469480852c6a2a6beb0db28f097bf1f" +checksum = "16e4850548ff4a25a77ce3bda7241874e17fb702ea551f0cc62a2dbe052f1272" dependencies = [ "anstyle", "memchr", @@ -1874,9 +1873,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.7" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +checksum = "e39f47abe8641e434de98e047e85ced629862e7ab719b6914a846796ceb289e2" dependencies = [ "futures-util", "pin-project-lite", @@ -1906,9 +1905,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a" +checksum = "a395c914b1ff95db3cb7003ea4fd19432343af698a9b5028a7d35f8e712240a1" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -1939,18 +1938,18 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1fcbefc7ece1d70dcce29e490f269695dfca2d2bacdeaf9e5c3f799e4e6a42" +checksum = "7764bc1dfdb71157bc481528a649e617ed8c9c8aa93c0e8b01087133677cfc8e" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.9" +version = "0.60.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +checksum = "786bbf4434ed3e3413c5a1741abf53a0372dd48124ddb2091e6bff1b1b2582b5" dependencies = [ "aws-smithy-types", "urlencoding", @@ -1982,9 +1981,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.10.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efce7aaaf59ad53c5412f14fc19b2d5c6ab2c3ec688d272fd31f76ec12f44fb0" +checksum = "8b743e9aab0b8d50a9a40eebedf974fcfe3621032e07c6388d1c7821b155b7b0" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1999,9 +1998,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.6" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f172bcb02424eb94425db8aed1b6d583b5104d4d5ddddf22402c661a320048" +checksum = "0828575b70da70406b4cdb5d4afe0afe725f72245f04d34f02e0fb5ebd6fc872" dependencies = [ "base64-simd", "bytes", @@ -2478,9 +2477,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -2504,9 +2503,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -2641,9 +2640,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -2757,9 +2756,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", "clap_derive", @@ -2777,9 +2776,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -2811,9 +2810,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -2842,9 +2841,9 @@ dependencies = [ [[package]] name = "cliclack" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2381872509dfa50d8b92b92a5da8367ba68458ab9494be4134b57ad6ca26295f" +checksum = "aa510b739c618c679375ea9c5af44ce9f591289546e874ad5910e7ce7df79844" dependencies = [ "console 0.15.11", "indicatif", @@ -3562,6 +3561,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -4107,9 +4112,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed-hash" @@ -4222,6 +4227,7 @@ dependencies = [ "path-slash", "proptest", "quick-junit", + "rand 0.9.2", "rayon", "regex", "reqwest", @@ -4540,6 +4546,7 @@ dependencies = [ "dunce", "eyre", "foundry-block-explorers", + "foundry-cli-markdown", "foundry-common", "foundry-compilers", "foundry-config", @@ -4567,6 +4574,14 @@ dependencies = [ "yansi", ] +[[package]] +name = "foundry-cli-markdown" +version = "1.6.0" +dependencies = [ + "clap", + "pretty_assertions", +] + [[package]] name = "foundry-common" version = "1.6.0" @@ -4602,6 +4617,7 @@ dependencies = [ "foundry-block-explorers", "foundry-common-fmt", "foundry-compilers", + "foundry-config", "itertools 0.14.0", "jiff", "num-format", @@ -5728,9 +5744,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -6269,9 +6285,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -6875,9 +6891,12 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.10.0", +] [[package]] name = "nu-ansi-term" @@ -7630,15 +7649,15 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -7699,6 +7718,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettydiff" version = "0.9.0" @@ -8605,9 +8634,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.34.0" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1ce3f52a052d78cc251714d57bf05dc8bc75e269677de11805d3153300a2cd" +checksum = "6e435414e9de50a1b930da602067c76365fea2fea11e80ceb50783c94ddd127f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -9423,9 +9452,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" dependencies = [ "cc", "cfg-if", @@ -9533,15 +9562,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "small_btree" @@ -9678,7 +9707,7 @@ name = "solar-interface" version = "0.1.8" source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "annotate-snippets 0.12.10", + "annotate-snippets 0.12.11", "anstream", "anstyle", "derive_more", @@ -10058,9 +10087,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f92d01b5de07eaf324f7fca61cc6bd3d82bbc1de5b6c963e6fe79e86f36580d" +checksum = "2379beea9476b89d0237078be761cf8e012d92d5ae4ae0c9a329f974838870fc" dependencies = [ "paste", "proc-macro2", @@ -10332,15 +10361,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -11937,18 +11957,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -12063,9 +12083,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 870d36bc0787d..06a320a24b358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/primitives/", "crates/script-sequence/", "crates/test-utils/", + "crates/cli-markdown/", ] resolver = "2" @@ -255,6 +256,7 @@ forge-script-sequence = { path = "crates/script-sequence" } foundry-cheatcodes = { path = "crates/cheatcodes" } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cli = { path = "crates/cli" } +foundry-cli-markdown = { path = "crates/cli-markdown" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } @@ -343,7 +345,7 @@ alloy-op-evm = "0.26.3" # revm revm = { version = "34.0.0", default-features = false } -revm-inspectors = { version = "0.34.0", features = ["serde"] } +revm-inspectors = { version = "0.34.2", features = ["serde"] } op-revm = { version = "15.0.0", default-features = false } ## cli diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index fb4b774f775f9..ee28f59e67c9b 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,5 +1,5 @@ use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; -use alloy_primitives::{Address, B64, B256, Bytes, TxHash, U256}; +use alloy_primitives::{Address, B64, B256, Bytes, TxHash, U256, map::HashSet}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, anvil::{Forking, MineOptions}, @@ -10,6 +10,7 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, + parity::TraceType, }, }; use alloy_serde::WithOtherFields; @@ -325,6 +326,13 @@ pub enum EthRequest { #[serde(rename = "trace_filter", with = "sequence")] TraceFilter(TraceFilter), + /// Trace transaction endpoint for parity's `trace_replayBlockTransactions` + #[serde(rename = "trace_replayBlockTransactions")] + TraceReplayBlockTransactions( + #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, + HashSet, + ), + // Custom endpoints, they're not extracted to a separate type out of serde convenience /// send transactions impersonating specific account and contract addresses. #[serde( diff --git a/crates/anvil/src/args.rs b/crates/anvil/src/args.rs index 806c8bc992155..4dee7f830fa15 100644 --- a/crates/anvil/src/args.rs +++ b/crates/anvil/src/args.rs @@ -7,6 +7,8 @@ use foundry_cli::utils; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let mut args = Anvil::parse(); args.global.init()?; args.node.evm.resolve_rpc_alias(); diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 31759962111b1..017cc1ceff3c3 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -234,6 +234,7 @@ impl NodeArgs { Ok(NodeConfig::default() .with_gas_limit(self.evm.gas_limit) .disable_block_gas_limit(self.evm.disable_block_gas_limit) + .enable_tx_gas_limit(self.evm.enable_tx_gas_limit) .with_gas_price(self.evm.gas_price) .with_hardfork(hardfork) .with_blocktime(self.block_time) @@ -544,6 +545,10 @@ pub struct AnvilEvmArgs { )] pub disable_block_gas_limit: bool, + /// Enable the transaction gas limit check as imposed by EIP-7825 (Osaka hardfork). + #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")] + pub enable_tx_gas_limit: bool, + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] @@ -895,6 +900,16 @@ mod tests { assert!(args.is_err()); } + #[test] + fn can_parse_enable_tx_gas_limit() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--enable-tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + + // Also test the alias + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + } + #[test] fn can_parse_disable_code_size_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]); diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 648cdeed0256b..57e08dd54fc87 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -14,7 +14,6 @@ use crate::{ }, mem::{self, in_memory_db::MemDb}, }; -use alloy_chains::Chain; use alloy_consensus::BlockHeader; use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_evm::EvmEnv; @@ -1300,6 +1299,23 @@ latest block number: {latest_block}" ..Default::default() }; + // Determine chain_id early so we can use it consistently + let chain_id = if let Some(chain_id) = self.chain_id { + chain_id + } else { + let chain_id = if let Some(fork_chain_id) = fork_chain_id { + fork_chain_id.to() + } else { + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? + }; + + // need to update the dev signers and env with the chain id + self.set_chain_id(Some(chain_id)); + env.evm_env.cfg_env.chain_id = chain_id; + env.tx.base.chain_id = chain_id.into(); + chain_id + }; + // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() { if let Some(base_fee) = block.header.base_fee_per_gas { @@ -1320,12 +1336,7 @@ latest block number: {latest_block}" (block.header.excess_blob_gas, block.header.blob_gas_used) { // derive the blobparams that are active at this timestamp - let blob_params = get_blob_params( - fork_chain_id - .unwrap_or_else(|| U256::from(Chain::mainnet().id())) - .saturating_to(), - block.header.timestamp, - ); + let blob_params = get_blob_params(chain_id, block.header.timestamp); env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( blob_excess_gas, @@ -1353,21 +1364,6 @@ latest block number: {latest_block}" let block_hash = block.header.hash; - let chain_id = if let Some(chain_id) = self.chain_id { - chain_id - } else { - let chain_id = if let Some(fork_chain_id) = fork_chain_id { - fork_chain_id.to() - } else { - provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? - }; - - // need to update the dev signers and env with the chain id - self.set_chain_id(Some(chain_id)); - env.evm_env.cfg_env.chain_id = chain_id; - env.tx.base.chain_id = chain_id.into(); - chain_id - }; let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id apply_chain_and_block_specific_env_changes::( diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 7e623ea2bd986..a66f4f8d7d183 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -56,7 +56,7 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, }; @@ -344,6 +344,9 @@ impl EthApi { EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), + EthRequest::TraceReplayBlockTransactions(block, trace_types) => { + self.trace_replay_block_transactions(block, trace_types).await.to_rpc_result() + } EthRequest::ImpersonateAccount(addr) => { self.anvil_impersonate_account(addr).await.to_rpc_result() } @@ -1990,6 +1993,18 @@ impl EthApi { node_info!("trace_filter"); self.backend.trace_filter(filter).await } + + /// Replays all transactions in a block returning the requested traces for each transaction + /// + /// Handler for RPC call: `trace_replayBlockTransactions` + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result> { + node_info!("trace_replayBlockTransactions"); + self.backend.trace_replay_block_transactions(block, trace_types).await + } } // == impl EthApi anvil endpoints == diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 995206fbfcc89..b7732446c8358 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -6,7 +6,7 @@ use alloy_eips::eip2930::AccessListResult; use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse}; use alloy_primitives::{ Address, B256, Bytes, StorageValue, U256, - map::{FbHashMap, HashMap}, + map::{FbHashMap, HashMap, HashSet}, }; use alloy_provider::{ Provider, @@ -19,7 +19,7 @@ use alloy_rpc_types::{ simulate::{SimulatePayload, SimulatedBlock}, trace::{ geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace as Trace, + parity::{LocalizedTransactionTrace as Trace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_serde::WithOtherFields; @@ -419,6 +419,16 @@ impl ClientFork { Ok(traces) } + pub async fn trace_replay_block_transactions( + &self, + number: u64, + trace_types: HashSet, + ) -> Result, TransportError> { + // Forward to upstream provider for historical blocks + let params = (number, trace_types.iter().map(|t| format!("{t:?}")).collect::>()); + self.provider().raw_request("trace_replayBlockTransactions".into(), params).await + } + pub async fn transaction_receipt( &self, hash: B256, diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 68de83c20bb13..78718fda4f72c 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -54,7 +54,7 @@ use alloy_network::{ }; use alloy_primitives::{ Address, B256, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, - map::{AddressMap, HashMap}, + map::{AddressMap, HashMap, HashSet}, }; use alloy_rpc_types::{ AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, @@ -71,7 +71,7 @@ use alloy_rpc_types::{ FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, }, - parity::LocalizedTransactionTrace, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_serde::{OtherFields, WithOtherFields}; @@ -2141,8 +2141,7 @@ impl Backend { let from_block = self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { - // requested log range does not exist yet - return Ok(vec![]); + return Err(BlockchainError::BlockOutOfRange(best, from_block)); } self.logs_for_range(&filter, from_block, to_block).await @@ -2944,6 +2943,114 @@ impl Backend { Ok(vec![]) } + /// Replays all transactions in a block and returns the requested traces for each transaction + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result, BlockchainError> { + let block_number = self.convert_block_number(Some(block)); + + // Try mined blocks first + if let Some(results) = + self.mined_parity_trace_replay_block_transactions(block_number, &trace_types) + { + return Ok(results); + } + + // Fallback to fork if block predates fork + if let Some(fork) = self.get_fork() + && fork.predates_fork(block_number) + { + return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?); + } + + Ok(vec![]) + } + + /// Returns the trace results for all transactions in a mined block by replaying them + fn mined_parity_trace_replay_block_transactions( + &self, + block_number: u64, + trace_types: &HashSet, + ) -> Option> { + let block = self.get_block(block_number)?; + + // Execute this in the context of the parent state + let parent_hash = block.header.parent_hash; + let trace_config = TracingInspectorConfig::from_parity_config(trace_types); + + let read_guard = self.states.upgradable_read(); + if let Some(state) = read_guard.get_state(&parent_hash) { + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state = write_guard.get_on_disk_state(&parent_hash)?; + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) + } + } + + /// Replays all transactions in a block with the tracing inspector to generate TraceResults + fn replay_block_transactions_with_inspector( + &self, + block: &Block, + parent_state: &StateDb, + trace_config: TracingInspectorConfig, + trace_types: &HashSet, + ) -> Option> { + let mut cache_db = CacheDB::new(Box::new(parent_state)); + let mut results = Vec::new(); + + // Configure the block environment + let mut env = self.env.read().clone(); + env.evm_env.block_env = BlockEnv { + number: U256::from(block.header.number), + beneficiary: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: Some(block.header.mix_hash), + basefee: block.header.base_fee_per_gas.unwrap_or_default(), + gas_limit: block.header.gas_limit, + ..Default::default() + }; + + // Execute each transaction in the block with tracing + for tx_envelope in &block.body.transactions { + let tx_hash = tx_envelope.hash(); + + // Create a fresh inspector for this transaction + let mut inspector = TracingInspector::new(trace_config); + + // Prepare transaction environment + let pending_tx = + PendingTransaction::from_maybe_impersonated(tx_envelope.clone()).ok()?; + let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( + pending_tx.transaction.as_ref(), + *pending_tx.sender(), + ); + if env.networks.is_optimism() { + tx_env.enveloped_tx = Some(pending_tx.transaction.encoded_2718().into()); + } + + // Execute the transaction with the inspector + let mut evm = self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); + let result = evm.transact(tx_env.clone()).ok()?; + + // Build TraceResults from the inspector and execution result + let full_trace = inspector + .into_parity_builder() + .into_trace_results_with_state(&result, trace_types, &cache_db) + .ok()?; + + results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace }); + + // Commit the state changes for the next transaction + cache_db.commit(result.state); + } + + Some(results) + } + pub async fn transaction_receipt( &self, hash: B256, @@ -3083,7 +3190,8 @@ impl Backend { blob_gas_used, }; - let inner = FoundryTxReceipt::new(receipt); + // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API) + let inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp); Some(MinedTransactionReceipt { inner, out: info.out }) } diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 2e6fed286eb0f..3f4be8158f457 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -419,8 +419,14 @@ impl EthApi { let receipts = join_all(receipt_futs.map(|r| async { if let Ok(Some(r)) = r.await { - let block = self.block_by_number(r.block_number().unwrap().into()).await?; - let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + // Try to get timestamp from receipt's other fields first (set by mined receipts), + // fallback to block lookup for fork receipts that may not have it + let timestamp = if let Some(ts) = r.block_timestamp() { + ts + } else { + let block = self.block_by_number(r.block_number().unwrap().into()).await?; + block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp + }; let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { diff --git a/crates/anvil/test-data/state-dump.json b/crates/anvil/test-data/state-dump.json index a60e3c861c107..e7a1e37f62809 100644 --- a/crates/anvil/test-data/state-dump.json +++ b/crates/anvil/test-data/state-dump.json @@ -1 +1,314 @@ -{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724763179,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","type":"0x2"},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","type":"0x2"},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file +{ + "block": { + "number": 2, + "beneficiary": "0x0000000000000000000000000000000000000000", + "timestamp": 1724763179, + "gas_limit": 30000000, + "basefee": 875175000, + "difficulty": "0x0", + "prevrandao": "0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "nonce": 0, + "balance": "0xa410", + "code": "0x", + "storage": {} + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 0, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 1, + "balance": "0x21e19e0b90393da9b38", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 1, + "balance": "0x21e19e0b6a140b55df8", + "code": "0x", + "storage": {} + } + }, + "best_block_number": 2, + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x66cdcc25", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [], + "ommers": [] + }, + { + "header": { + "parentHash": "0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175", + "transactionsRoot": "0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc29", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853", + "s": "0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0", + "yParity": "0x0", + "hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + }, + { + "header": { + "parentHash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1", + "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x2", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc2b", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x342a1c58", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + } + ], + "transactions": [ + { + "info": { + "transaction_hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "transaction_index": 0, + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "block_number": 1 + }, + { + "info": { + "transaction_hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "transaction_index": 0, + "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0", + "block_number": 2 + } + ] +} diff --git a/crates/anvil/tests/it/beacon_api.rs b/crates/anvil/tests/it/beacon_api.rs index 5c6c9b2486fa8..ec0f13c02e7c0 100644 --- a/crates/anvil/tests/it/beacon_api.rs +++ b/crates/anvil/tests/it/beacon_api.rs @@ -66,8 +66,7 @@ async fn test_beacon_api_get_blobs() { .with_blob_sidecar(sidecar) .value(U256::from(100)); - let mut tx = WithOtherFields::new(tx); - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let pending = provider.send_transaction(tx).await.unwrap(); pending_txs.push(pending); diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 11200e5667206..ddc8c91eefda7 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -39,9 +39,7 @@ async fn can_send_eip4844_transaction() { .with_blob_sidecar(sidecar) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -103,6 +101,29 @@ async fn can_send_eip4844_transaction_eth_send_transaction() { let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); } +// +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction_with_eip7594_sidecar_format() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into())); + let (api, handle) = spawn(node_config).await; + let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build_7594().unwrap(); + + let mut tx = TransactionRequest::default().with_from(alice).with_to(bob); + alloy_network::TransactionBuilder7594::set_blob_sidecar_7594(&mut tx, sidecar); + + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let receipt = pending_tx.get_receipt().await.unwrap(); + let tx_hash = receipt.transaction_hash; + + let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); +} + #[tokio::test(flavor = "multi_thread")] async fn can_send_multiple_blobs_in_one_tx() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); @@ -131,9 +152,7 @@ async fn can_send_multiple_blobs_in_one_tx() { .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -169,9 +188,7 @@ async fn cannot_exceed_six_blobs() { .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let err = provider.send_transaction(tx).await.unwrap_err(); @@ -211,8 +228,6 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { .with_blob_sidecar(sidecar); let mut tx = WithOtherFields::new(tx); - tx.populate_blob_hashes(); - let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; @@ -224,7 +239,6 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { let sidecar = sidecar.build().unwrap(); tx.set_blob_sidecar(sidecar); tx.set_nonce(1); - tx.populate_blob_hashes(); let second_tx = provider.send_transaction(tx).await.unwrap(); api.mine_one().await; @@ -428,9 +442,7 @@ async fn can_get_blobs_by_versioned_hash() { .with_blob_sidecar(sidecar.clone()) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let _receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -466,10 +478,7 @@ async fn can_get_blobs_by_tx_hash() { .with_blob_sidecar(sidecar.clone()) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); - + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let hash = receipt.transaction_hash; api.anvil_set_auto_mine(true).await.unwrap(); diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs index 9d35644f24949..2c677231e07ad 100644 --- a/crates/anvil/tests/it/state.rs +++ b/crates/anvil/tests/it/state.rs @@ -725,6 +725,7 @@ async fn test_backward_compatibility_state_dump_deserialization_v1_2() { "output": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "gas_used": 96345, "gas_limit": 143385, + "gas_refund_counter": 0, "status": "Return", "steps": [], "decoded": null diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index ba6c7ba887246..fb1777aa4f9f0 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -25,7 +25,7 @@ use alloy_rpc_types::{ GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, PreStateConfig, PreStateFrame, }, - parity::{Action, LocalizedTransactionTrace}, + parity::{Action, ChangedType, LocalizedTransactionTrace, TraceType}, }, }; use alloy_serde::WithOtherFields; @@ -1273,3 +1273,81 @@ async fn test_debug_trace_transaction_pre_state_tracer() { _ => unreachable!(), } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_trace_replay_block_transactions_local() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let amount = U256::from(1000000u64); + + // Send first transaction + let tx1 = TransactionRequest::default().to(to).value(amount).from(from); + let tx1 = WithOtherFields::new(tx1); + let pending_tx1 = provider.send_transaction(tx1).await.unwrap(); + + // Send second transaction with different value + let tx2 = TransactionRequest::default().to(to).value(amount).from(from); + let tx2 = WithOtherFields::new(tx2); + let pending_tx2 = provider.send_transaction(tx2).await.unwrap(); + + api.mine_one().await; + let receipt1 = pending_tx1.get_receipt().await.unwrap(); + let receipt2 = pending_tx2.get_receipt().await.unwrap(); + + let block_number = receipt2.block_number.unwrap(); + + // Replay the block transactions with call trace type + // Pass block number as hex string as per Ethereum RPC spec + let results = api + .trace_replay_block_transactions( + block_number.into(), + vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(), + ) + .await + .unwrap(); + + // Verify we have traces for both transactions + assert_eq!(results.len(), 2, "Should have traces for 2 transactions"); + + // Verify first transaction hash matches + assert_eq!(results[0].transaction_hash, receipt1.transaction_hash); + + // Verify second transaction hash matches + assert_eq!(results[1].transaction_hash, receipt2.transaction_hash); + + // Verify trace types are present and accurate + for result in results { + let full_trace = &result.full_trace; + + // Verify Trace (call trace) is present and accurate + assert!(!full_trace.trace.is_empty(), "Trace should not be empty"); + let first_trace = &full_trace.trace[0]; + match &first_trace.action { + Action::Call(call) => { + assert_eq!(call.from, from, "Call from address should match"); + assert_eq!(call.to, to, "Call to address should match"); + } + _ => panic!("Expected Call action, got {:?}", first_trace.action), + } + + // Verify VmTrace is present + assert!(full_trace.vm_trace.is_some(), "VmTrace should be present when requested"); + + // Verify StateDiff is present + assert!(full_trace.state_diff.is_some(), "StateDiff should be present when requested"); + // Verify balance change is correct in state diff + let ChangedType:: { from, to } = + full_trace.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap(); + assert_eq!( + to.checked_sub(*from).unwrap(), + amount, + "Incorrect balance change in state diff" + ); + } +} diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index cf4966a86c15b..2f30b89e07216 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -33,6 +33,8 @@ use std::time::Instant; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = CastArgs::parse(); args.global.init()?; args.global.tokio_runtime().block_on(run_command(args)) diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index cf6c585bd1bdd..b24af1db60e84 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -2336,7 +2336,7 @@ fn explorer_client( api_url: Option, explorer_url: Option, ) -> Result { - let mut builder = Client::builder().chain(chain)?; + let mut builder = Client::builder(); let deduced = chain.etherscan_urls(); diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 64ff8da391200..d38d27fec63dc 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -682,8 +682,6 @@ where self.tx.set_blob_sidecar_7594(sidecar); } - self.tx.populate_blob_hashes(); - Ok(self) } } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index ea3a003a7ffc4..d32712219000e 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -3,6 +3,7 @@ use crate::{ json::json_value_to_token, }; use alloy_dyn_abi::DynSolValue; +use alloy_network::AnyNetwork; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::Filter; @@ -238,7 +239,7 @@ impl Cheatcode for eth_getLogsCall { .database .active_fork_url() .ok_or_else(|| fmt_err!("no active fork URL found"))?; - let provider = ProviderBuilder::new(&url).build()?; + let provider = ProviderBuilder::::new(&url).build()?; let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); for (i, &topic) in topics.iter().enumerate() { filter.topics[i] = topic.into(); @@ -275,7 +276,7 @@ impl Cheatcode for getRawBlockHeaderCall { .database .active_fork_url() .ok_or_else(|| fmt_err!("no active fork"))?; - let provider = ProviderBuilder::new(&url).build()?; + let provider = ProviderBuilder::::new(&url).build()?; let block_number = u64::try_from(blockNumber) .map_err(|_| fmt_err!("block number must be less than 2^64"))?; let block = @@ -397,7 +398,7 @@ fn persist_caller(ccx: &mut CheatsCtxt) { /// Performs an Ethereum JSON-RPC request to the given endpoint. fn rpc_call(url: &str, method: &str, params: &str) -> Result { - let provider = ProviderBuilder::new(url).build()?; + let provider = ProviderBuilder::::new(url).build()?; let params_json: serde_json::Value = serde_json::from_str(params)?; let result = foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) diff --git a/crates/chisel/src/args.rs b/crates/chisel/src/args.rs index e3af6d5c91203..8200f4fc44f63 100644 --- a/crates/chisel/src/args.rs +++ b/crates/chisel/src/args.rs @@ -14,6 +14,8 @@ use yansi::Paint; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Chisel::parse(); args.global.init()?; args.global.tokio_runtime().block_on(run_command(args)) diff --git a/crates/cli-markdown/Cargo.toml b/crates/cli-markdown/Cargo.toml new file mode 100644 index 0000000000000..7f039685d2cf1 --- /dev/null +++ b/crates/cli-markdown/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "foundry-cli-markdown" +description = "Generate Markdown documentation for clap CLIs" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +clap = { version = "4", features = ["env"] } + +[dev-dependencies] +clap = { version = "4", features = ["derive"] } +pretty_assertions = "1" diff --git a/crates/cli-markdown/src/lib.rs b/crates/cli-markdown/src/lib.rs new file mode 100644 index 0000000000000..732e22bfd193d --- /dev/null +++ b/crates/cli-markdown/src/lib.rs @@ -0,0 +1,582 @@ +//! Generate Markdown documentation for clap command-line tools. +//! +//! This is a fork of [`clap-markdown`](https://crates.io/crates/clap-markdown) with the following +//! enhancements: +//! - Support for grouped options by help heading ([PR #48](https://github.com/ConnorGray/clap-markdown/pull/48)) +//! - Show environment variable names for arguments ([PR #50](https://github.com/ConnorGray/clap-markdown/pull/50)) +//! - Add version information to generated Markdown ([PR #52](https://github.com/ConnorGray/clap-markdown/pull/52)) + +use std::{ + collections::BTreeMap, + fmt::{self, Write}, +}; + +use clap::builder::PossibleValue; + +/// Options to customize the structure of the output Markdown document. +#[non_exhaustive] +pub struct MarkdownOptions { + title: Option, + show_footer: bool, + show_table_of_contents: bool, + show_aliases: bool, +} + +impl MarkdownOptions { + /// Construct a default instance of `MarkdownOptions`. + pub fn new() -> Self { + Self { title: None, show_footer: true, show_table_of_contents: true, show_aliases: true } + } + + /// Set a custom title to use in the generated document. + pub fn title(mut self, title: String) -> Self { + self.title = Some(title); + self + } + + /// Whether to show the default footer advertising `clap-markdown`. + pub fn show_footer(mut self, show: bool) -> Self { + self.show_footer = show; + self + } + + /// Whether to show the default table of contents. + pub fn show_table_of_contents(mut self, show: bool) -> Self { + self.show_table_of_contents = show; + self + } + + /// Whether to show aliases for arguments and commands. + pub fn show_aliases(mut self, show: bool) -> Self { + self.show_aliases = show; + self + } +} + +impl Default for MarkdownOptions { + fn default() -> Self { + Self::new() + } +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown() -> String { + let command = C::command(); + help_markdown_command(&command) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_custom(options: &MarkdownOptions) -> String { + let command = C::command(); + help_markdown_command_custom(&command, options) +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown_command(command: &clap::Command) -> String { + help_markdown_command_custom(command, &Default::default()) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_command_custom(command: &clap::Command, options: &MarkdownOptions) -> String { + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, command, options); + buffer +} + +/// Format the help information for `command` as Markdown and print it. +/// +/// Output is printed to the standard output. +#[allow(clippy::disallowed_macros)] +pub fn print_help_markdown() { + let command = C::command(); + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, &command, &Default::default()); + println!("{buffer}"); +} + +fn write_help_markdown(buffer: &mut String, command: &clap::Command, options: &MarkdownOptions) { + let title_name = get_canonical_name(command); + + let title = match options.title { + Some(ref title) => title.to_owned(), + None => format!("Command-Line Help for `{title_name}`"), + }; + writeln!(buffer, "# {title}\n",).unwrap(); + + writeln!( + buffer, + "This document contains the help content for the `{title_name}` command-line program.\n", + ) + .unwrap(); + + // Write the version if available (PR #52) + if let Some(version) = command.get_version() { + let version_str = version.to_string(); + + if version_str.contains('\n') { + // Multi-line version: use a code block + writeln!(buffer, "**Version:**\n\n```\n{}\n```\n", version_str.trim()).unwrap(); + } else { + // Single-line version: use inline code + writeln!(buffer, "**Version:** `{version_str}`\n").unwrap(); + } + } + + // Write the table of contents + if options.show_table_of_contents { + writeln!(buffer, "**Command Overview:**\n").unwrap(); + build_table_of_contents_markdown(buffer, Vec::new(), command, 0).unwrap(); + writeln!(buffer).unwrap(); + } + + // Write the commands/subcommands sections + build_command_markdown(buffer, Vec::new(), command, 0, options).unwrap(); + + // Write the footer + if options.show_footer { + write!( + buffer, + r#"
+ + + This document was generated automatically by + clap-markdown. + +"# + ) + .unwrap(); + } +} + +fn build_table_of_contents_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path; + command_path.push(title_name); + command_path + }; + + writeln!(buffer, "* [`{}`↴](#{})", command_path.join(" "), command_path.join("-"),)?; + + for subcommand in command.get_subcommands() { + build_table_of_contents_markdown(buffer, command_path.clone(), subcommand, _depth + 1)?; + } + + Ok(()) +} + +fn build_command_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, + options: &MarkdownOptions, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path.clone(); + command_path.push(title_name); + command_path + }; + + // Write the markdown heading + writeln!(buffer, "## `{}`\n", command_path.join(" "))?; + + if let Some(long_about) = command.get_long_about() { + writeln!(buffer, "{long_about}\n")?; + } else if let Some(about) = command.get_about() { + writeln!(buffer, "{about}\n")?; + } + + if let Some(help) = command.get_before_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_before_help() { + writeln!(buffer, "{help}\n")?; + } + + writeln!( + buffer, + "**Usage:** `{}{}`\n", + if parent_command_path.is_empty() { + String::new() + } else { + let mut s = parent_command_path.join(" "); + s.push(' '); + s + }, + command.clone().render_usage().to_string().replace("Usage: ", "") + )?; + + if options.show_aliases { + let aliases = command.get_visible_aliases().collect::>(); + if let Some(aliases_str) = get_alias_string(&aliases) { + writeln!( + buffer, + "**{}:** {aliases_str}\n", + pluralize(aliases.len(), "Command Alias", "Command Aliases") + )?; + } + } + + if let Some(help) = command.get_after_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_after_help() { + writeln!(buffer, "{help}\n")?; + } + + // Subcommands + if command.get_subcommands().next().is_some() { + writeln!(buffer, "###### **Subcommands:**\n")?; + + for subcommand in command.get_subcommands() { + if subcommand.is_hide_set() { + continue; + } + + let title_name = get_canonical_name(subcommand); + let about = match subcommand.get_about() { + Some(about) => about.to_string(), + None => String::new(), + }; + + writeln!(buffer, "* `{title_name}` — {about}",)?; + } + + writeln!(buffer)?; + } + + // Arguments (positional) + if command.get_positionals().next().is_some() { + writeln!(buffer, "###### **Arguments:**\n")?; + + for pos_arg in command.get_positionals() { + write_arg_markdown(buffer, pos_arg)?; + } + + writeln!(buffer)?; + } + + // Options (grouped by help heading) - PR #48 + let non_pos: Vec<_> = + command.get_arguments().filter(|arg| !arg.is_positional() && !arg.is_hide_set()).collect(); + + if !non_pos.is_empty() { + // Group arguments by help heading + let mut grouped_args: BTreeMap<&str, Vec<&clap::Arg>> = BTreeMap::new(); + + for arg in non_pos { + let heading = arg.get_help_heading().unwrap_or("Options"); + grouped_args.entry(heading).or_default().push(arg); + } + + // Write each group with its heading + for (heading, args) in grouped_args { + writeln!(buffer, "###### **{heading}:**\n")?; + + for arg in args { + write_arg_markdown(buffer, arg)?; + } + + writeln!(buffer)?; + } + } + + // Include extra space between commands + write!(buffer, "\n\n")?; + + for subcommand in command.get_subcommands() { + build_command_markdown(buffer, command_path.clone(), subcommand, _depth + 1, options)?; + } + + Ok(()) +} + +fn write_arg_markdown(buffer: &mut String, arg: &clap::Arg) -> fmt::Result { + // Markdown list item + write!(buffer, "* ")?; + + let value_name: String = match arg.get_value_names() { + Some([name, ..]) => name.as_str().to_owned(), + Some([]) => unreachable!("clap Arg::get_value_names() returned Some(..) of empty list"), + None => arg.get_id().to_string().to_ascii_uppercase(), + }; + + match (arg.get_short(), arg.get_long()) { + (Some(short), Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short}`, `--{long} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`, `--{long}`")? + } + } + (Some(short), None) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`")? + } + } + (None, Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`--{long} <{value_name}>`")? + } else { + write!(buffer, "`--{long}`")? + } + } + (None, None) => { + debug_assert!( + arg.is_positional(), + "unexpected non-positional Arg with neither short nor long name: {arg:?}" + ); + write!(buffer, "`<{value_name}>`",)?; + } + } + + if let Some(aliases) = arg.get_visible_aliases().as_deref() + && let Some(aliases_str) = get_alias_string(aliases) + { + write!(buffer, " [{}: {aliases_str}]", pluralize(aliases.len(), "alias", "aliases"))?; + } + + if let Some(help) = arg.get_long_help() { + buffer.push_str(&indent(&help.to_string(), " — ", " ")) + } else if let Some(short_help) = arg.get_help() { + writeln!(buffer, " — {short_help}")?; + } else { + writeln!(buffer)?; + } + + // Arg default values + if !arg.get_default_values().is_empty() { + let default_values: String = arg + .get_default_values() + .iter() + .map(|value| format!("`{}`", value.to_string_lossy())) + .collect::>() + .join(", "); + + if arg.get_default_values().len() > 1 { + writeln!(buffer, "\n Default values: {default_values}")?; + } else { + writeln!(buffer, "\n Default value: {default_values}")?; + } + } + + // Arg possible values + let possible_values: Vec = + arg.get_possible_values().into_iter().filter(|pv| !pv.is_hide_set()).collect(); + + if !possible_values.is_empty() && !matches!(arg.get_action(), clap::ArgAction::SetTrue) { + let any_have_help: bool = possible_values.iter().any(|pv| pv.get_help().is_some()); + + if any_have_help { + let text: String = possible_values + .iter() + .map(|pv| match pv.get_help() { + Some(help) => { + format!(" - `{}`:\n {}\n", pv.get_name(), help) + } + None => format!(" - `{}`\n", pv.get_name()), + }) + .collect::>() + .join(""); + + writeln!(buffer, "\n Possible values:\n{text}")?; + } else { + let text: String = possible_values + .iter() + .map(|pv| format!("`{}`", pv.get_name())) + .collect::>() + .join(", "); + + writeln!(buffer, "\n Possible values: {text}\n")?; + } + } + + // Arg environment variable (PR #50) + if !arg.is_hide_env_set() + && let Some(env) = arg.get_env() + { + writeln!(buffer, "\n Environment variable: `{}`", env.to_string_lossy())?; + } + + Ok(()) +} + +/// Utility function to get the canonical name of a command. +fn get_canonical_name(command: &clap::Command) -> String { + command + .get_display_name() + .or_else(|| command.get_bin_name()) + .map(|name| name.to_owned()) + .unwrap_or_else(|| command.get_name().to_owned()) +} + +/// Indents non-empty lines. The output always ends with a newline. +fn indent(s: &str, first: &str, rest: &str) -> String { + if s.is_empty() { + return "\n".to_string(); + } + let mut result = String::new(); + let mut first_line = true; + + for line in s.lines() { + if !line.is_empty() { + result.push_str(if first_line { first } else { rest }); + result.push_str(line); + first_line = false; + } + result.push('\n'); + } + result +} + +fn get_alias_string(aliases: &[&str]) -> Option { + if aliases.is_empty() { + return None; + } + + Some(aliases.iter().map(|alias| format!("`{alias}`")).collect::>().join(", ")) +} + +fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { + if count == 1 { singular } else { plural } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Arg, Command}; + use pretty_assertions::assert_eq; + + #[test] + fn test_indent() { + assert_eq!(&indent("Header\n\nMore info", "___", "~~~~"), "___Header\n\n~~~~More info\n"); + assert_eq!( + &indent("Header\n\nMore info\n", "___", "~~~~"), + &indent("Header\n\nMore info", "___", "~~~~"), + ); + assert_eq!(&indent("", "___", "~~~~"), "\n"); + assert_eq!(&indent("\n", "___", "~~~~"), "\n"); + } + + #[test] + fn test_version_output() { + let app = Command::new("test-app").version("1.2.3").about("A test application"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:** `1.2.3`"), "Should contain version"); + } + + #[test] + fn test_multiline_version() { + let multi_line_version = "my-cli 1.2.3 (abc123)\nmy-lib 2.0.0 (789xyz)"; + + let app = Command::new("my-cli").version(multi_line_version).about("Multi-version CLI"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:**\n\n```"), "Should use code block for multi-line"); + } + + #[test] + fn test_env_var_output() { + let app = Command::new("env-test").about("Test env var output").arg( + Arg::new("config") + .short('c') + .long("config") + .env("CONFIG_PATH") + .help("Path to config file"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!( + markdown.contains("Environment variable: `CONFIG_PATH`"), + "Should show env var. Output: {markdown}" + ); + } + + #[test] + fn test_grouped_options() { + let app = Command::new("grouped-app") + .about("Test app with grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .help_heading("General Options") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("input") + .short('i') + .long("input") + .help("Input file") + .help_heading("File Options") + .value_name("FILE"), + ) + .arg( + Arg::new("format") + .short('f') + .long("format") + .help("Output format") + .value_name("FORMAT"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **File Options:**"), "Should have File Options heading"); + assert!( + markdown.contains("###### **General Options:**"), + "Should have General Options heading" + ); + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + } + + #[test] + fn test_no_grouped_options_backward_compatibility() { + let app = Command::new("simple-app") + .about("Test app without grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("output").short('o').long("output").help("Output file").value_name("FILE"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + assert!(markdown.contains("`-v`, `--verbose`"), "Should have verbose option"); + assert!(markdown.contains("`-o`, `--output `"), "Should have output option"); + } +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d9acf1de83d09..e1ec43c33df19 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] foundry-block-explorers.workspace = true +foundry-cli-markdown.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 020b7e953068f..763466d21185d 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,5 +1,4 @@ use eyre::EyreHandler; -use itertools::Itertools; use std::{error::Error, fmt}; /// A custom context type for Foundry specific error reporting via `eyre`. @@ -23,8 +22,7 @@ impl Handler { impl EyreHandler for Handler { fn display(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Display; - foundry_common::errors::dedup_chain(error).into_iter().format("; ").fmt(f) + f.write_str(&foundry_common::errors::display_chain(error)) } fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index 8bfc13c00001f..fee75a5d34e87 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -51,6 +51,17 @@ pub struct GlobalArgs { } impl GlobalArgs { + /// Check if `--markdown-help` was passed and print CLI reference as Markdown, then exit. + /// + /// This must be called **before** parsing arguments, since commands with required + /// subcommands would fail parsing before the flag is checked. + pub fn check_markdown_help() { + if std::env::args().any(|arg| arg == "--markdown-help") { + foundry_cli_markdown::print_help_markdown::(); + std::process::exit(0); + } + } + /// Initialize the global options. pub fn init(&self) -> eyre::Result<()> { // Set the global shell. diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f127010674aaf..dff85f9457fa7 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -115,33 +115,8 @@ pub fn get_provider_with_curl(config: &Config, curl_mode: bool) -> Result Result { - let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = ProviderBuilder::new(url.as_ref()); - - builder = builder.accept_invalid_certs(config.eth_rpc_accept_invalid_certs); - builder = builder.curl_mode(curl_mode); - - if let Ok(chain) = config.chain.unwrap_or_default().try_into() { - builder = builder.chain(chain); - } - - if let Some(jwt) = config.get_rpc_jwt_secret()? { - builder = builder.jwt(jwt.as_ref()); - } - - if let Some(rpc_timeout) = config.eth_rpc_timeout { - builder = builder.timeout(Duration::from_secs(rpc_timeout)); - } - - if let Some(rpc_headers) = config.eth_rpc_headers.clone() { - builder = builder.headers(rpc_headers); - } - - Ok(builder) + ProviderBuilder::from_config(config).map(|builder| builder.curl_mode(curl_mode)) } pub async fn get_chain

(chain: Option, provider: P) -> Result diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 154bbc5cc53ca..75776743d1132 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -16,6 +16,7 @@ workspace = true foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true +foundry-config.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 5d4c4657362e4..a098c33a34827 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -8,16 +8,19 @@ use crate::{ provider::{curl_transport::CurlTransport, runtime_transport::RuntimeTransportBuilder}, }; use alloy_chains::NamedChain; +use alloy_network::{Network, NetworkWallet}; use alloy_provider::{ Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, - fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + fillers::{FillProvider, JoinFill, RecommendedFillers, WalletFiller}, network::{AnyNetwork, EthereumWallet}, }; use alloy_rpc_client::ClientBuilder; use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; use eyre::{Result, WrapErr}; +use foundry_config::Config; use reqwest::Url; use std::{ + marker::PhantomData, net::SocketAddr, path::{Path, PathBuf}, str::FromStr, @@ -36,20 +39,8 @@ const POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR: f32 = 0.6; pub type RetryProvider = RootProvider; /// Helper type alias for a retry provider with a signer -pub type RetryProviderWithSigner = FillProvider< - JoinFill< - JoinFill< - Identity, - JoinFill< - GasFiller, - JoinFill< - alloy_provider::fillers::BlobGasFiller, - JoinFill, - >, - >, - >, - WalletFiller, - >, +pub type RetryProviderWithSigner = FillProvider< + JoinFill::RecommendedFillers>, WalletFiller>, RootProvider, N, >; @@ -84,8 +75,10 @@ pub fn try_get_http_provider(builder: impl AsRef) -> Result } /// Helper type to construct a `RetryProvider` +/// +/// This builder is generic over the network type `N`, defaulting to `AnyNetwork`. #[derive(Debug)] -pub struct ProviderBuilder { +pub struct ProviderBuilder { // Note: this is a result, so we can easily chain builder calls url: Result, chain: NamedChain, @@ -104,10 +97,12 @@ pub struct ProviderBuilder { no_proxy: bool, /// Whether to output curl commands instead of making requests. curl_mode: bool, + /// Phantom data for the network type. + _network: PhantomData, } -impl ProviderBuilder { - /// Creates a new builder instance +impl ProviderBuilder { + /// Creates a new ProviderBuilder helper instance. pub fn new(url_str: &str) -> Self { // a copy is needed for the next lines to work let mut url_str = url_str; @@ -156,9 +151,38 @@ impl ProviderBuilder { accept_invalid_certs: false, no_proxy: false, curl_mode: false, + _network: PhantomData, } } + /// Constructs a [ProviderBuilder] instantiated using [Config] values. + /// + /// Defaults to `http://localhost:8545` and `Mainnet`. + pub fn from_config(config: &Config) -> Result { + let url = config.get_rpc_url_or_localhost_http()?; + let mut builder = Self::new(url.as_ref()); + + builder = builder.accept_invalid_certs(config.eth_rpc_accept_invalid_certs); + + if let Ok(chain) = config.chain.unwrap_or_default().try_into() { + builder = builder.chain(chain); + } + + if let Some(jwt) = config.get_rpc_jwt_secret()? { + builder = builder.jwt(jwt.as_ref()); + } + + if let Some(rpc_timeout) = config.eth_rpc_timeout { + builder = builder.timeout(Duration::from_secs(rpc_timeout)); + } + + if let Some(rpc_headers) = config.eth_rpc_headers.clone() { + builder = builder.headers(rpc_headers); + } + + Ok(builder) + } + /// Enables a request timeout. /// /// The timeout is applied from when the request starts connecting until the @@ -278,7 +302,7 @@ impl ProviderBuilder { } /// Constructs the `RetryProvider` taking all configs into account. - pub fn build(self) -> Result { + pub fn build(self) -> Result> { let Self { url, chain, @@ -292,6 +316,7 @@ impl ProviderBuilder { accept_invalid_certs, no_proxy, curl_mode, + .. } = self; let url = url?; @@ -303,7 +328,7 @@ impl ProviderBuilder { let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .connect_provider(RootProvider::new(client)); return Ok(provider); @@ -330,14 +355,22 @@ impl ProviderBuilder { ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() - .connect_provider(RootProvider::new(client)); + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); Ok(provider) } +} +impl ProviderBuilder { /// Constructs the `RetryProvider` with a wallet. - pub fn build_with_wallet(self, wallet: EthereumWallet) -> Result { + pub fn build_with_wallet + Clone>( + self, + wallet: W, + ) -> Result> + where + N: RecommendedFillers, + { let Self { url, chain, @@ -351,6 +384,7 @@ impl ProviderBuilder { accept_invalid_certs, no_proxy, curl_mode, + .. } = self; let url = url?; @@ -362,7 +396,7 @@ impl ProviderBuilder { let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .with_recommended_fillers() .wallet(wallet) .connect_provider(RootProvider::new(client)); @@ -392,7 +426,7 @@ impl ProviderBuilder { ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .with_recommended_fillers() .wallet(wallet) .connect_provider(RootProvider::new(client)); @@ -426,7 +460,7 @@ mod tests { #[test] fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); + let builder = ProviderBuilder::::new("localhost:8545"); assert!(builder.url.is_ok()); let url = builder.url.unwrap(); diff --git a/crates/config/src/lint.rs b/crates/config/src/lint.rs index c726e3ecc8f5b..25094ead98cf8 100644 --- a/crates/config/src/lint.rs +++ b/crates/config/src/lint.rs @@ -28,8 +28,8 @@ pub struct LinterConfig { /// Configurable patterns that should be excluded when performing `mixedCase` lint checks. /// - /// Default's to ["ERC", "URI"] to allow common names like `rescueERC20`, `ERC721TokenReceiver` - /// or `tokenURI`. + /// Defaults to common abbreviations: `ERC`, `URI`, `ID`, `URL`, `API`, `JSON`, `XML`, `HTML`, + /// `HTTP`, `HTTPS`. This allows names like `marketID`, `tokenURI`, `apiURL`, `parseJSON`, etc. pub mixed_case_exceptions: Vec, } @@ -40,7 +40,18 @@ impl Default for LinterConfig { severity: vec![Severity::High, Severity::Med, Severity::Low], exclude_lints: Vec::new(), ignore: Vec::new(), - mixed_case_exceptions: vec!["ERC".to_string(), "URI".to_string()], + mixed_case_exceptions: vec![ + "ERC".to_string(), + "URI".to_string(), + "ID".to_string(), + "URL".to_string(), + "API".to_string(), + "JSON".to_string(), + "XML".to_string(), + "HTML".to_string(), + "HTTP".to_string(), + "HTTPS".to_string(), + ], } } } diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 52e751cd2ca03..624dd5a35f81b 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -213,7 +213,7 @@ where deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom) } -/// Deserialize into `U256` from either a `u64` or a `U256` hex string. +/// Deserialize into `U256` from either a `u64`, a `U256` hex string, or a decimal string. pub fn deserialize_u64_to_u256<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -223,11 +223,16 @@ where enum NumericValue { U256(U256), U64(u64), + String(String), } match NumericValue::deserialize(deserializer)? { NumericValue::U64(n) => Ok(U256::from(n)), NumericValue::U256(n) => Ok(n), + NumericValue::String(s) => { + // Handle decimal strings (e.g., "18446744073709551615") + U256::from_str(&s).map_err(D::Error::custom) + } } } diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs index f7b18a978a4f4..e74ae58d66323 100644 --- a/crates/debugger/src/node.rs +++ b/crates/debugger/src/node.rs @@ -14,6 +14,8 @@ pub struct DebugNode { pub kind: CallKind, /// Calldata of the call. pub calldata: Bytes, + /// The gas limit of the call. + pub gas_limit: u64, /// The debug steps. pub steps: Vec, } @@ -25,8 +27,9 @@ impl DebugNode { kind: CallKind, steps: Vec, calldata: Bytes, + gas_limit: u64, ) -> Self { - Self { address, kind, steps, calldata } + Self { address, kind, steps, calldata, gas_limit } } } @@ -78,7 +81,7 @@ pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec) { let call = &arena_nodes[pending.node_idx].trace; let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() }; - let node = DebugNode::new(call.address, call.kind, steps, calldata); + let node = DebugNode::new(call.address, call.kind, steps, calldata, call.gas_limit); out.push(node); } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 6badd441fdb94..daf2d01d1da84 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -372,10 +372,11 @@ impl TUIContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {}", + "Address: {} | PC: {} | Gas used: {} | Gas refund: {}", self.address(), self.current_step().pc, - self.current_step().gas_used, + self.debug_call().gas_limit - self.current_step().gas_remaining, + self.current_step().gas_refund_counter ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 1d6933ee5e981..5a6cc4e91bafb 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -280,7 +280,7 @@ impl FuzzedExecutor { if success { Ok(FuzzOutcome::Case(CaseOutcome { - case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, + case: FuzzCase { gas: call.gas_used, stipend: call.stipend }, traces: call.traces, coverage: call.line_coverage, breakpoints, diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index db992516850a1..0aa09f7fc406c 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -470,11 +470,9 @@ impl<'a> InvariantExecutor<'a> { { warn!(target: "forge::test", "{error}"); } - current_run.fuzz_runs.push(FuzzCase { - calldata: tx.call_details.calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); + current_run + .fuzz_runs + .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend }); // Determine if test can continue or should exit. // Check invariants based on check_interval to improve deep run performance. diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 1308be9b40241..44d71fb6deee3 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -317,8 +317,6 @@ impl FuzzTestResult { /// Data of a single fuzz test case #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct FuzzCase { - /// The calldata used for this fuzz test - pub calldata: Bytes, /// Consumed gas pub gas: u64, /// The initial gas stipend for the transaction diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs index 80ac8a8dcb620..2b4e58b8665a7 100644 --- a/crates/evm/traces/src/folded_stack_trace.rs +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -5,20 +5,26 @@ use revm_inspectors::tracing::{ }; /// Builds a folded stack trace from a call trace arena. -pub fn build(arena: &CallTraceArena) -> Vec { - let mut fst = EvmFoldedStackTraceBuilder::default(); +pub fn build(arena: &CallTraceArena, isolate: bool) -> Vec { + let mut fst = EvmFoldedStackTraceBuilder::new(isolate); fst.process_call_node(arena.nodes(), 0); fst.build() } /// Wrapper for building a folded stack trace using EVM call trace node. -#[derive(Default)] pub struct EvmFoldedStackTraceBuilder { + /// Trace produced in isolate mode, meaning refund needs to be reversed at the depth=1 + /// frame for consistent gas values. + isolate: bool, /// Raw folded stack trace builder. fst: FoldedStackTraceBuilder, } impl EvmFoldedStackTraceBuilder { + pub fn new(isolate: bool) -> Self { + Self { isolate, fst: FoldedStackTraceBuilder::default() } + } + /// Returns the folded stack trace. pub fn build(self) -> Vec { self.fst.build() @@ -57,7 +63,13 @@ impl EvmFoldedStackTraceBuilder { } }; - self.fst.enter(func_name, node.trace.gas_used as i64); + let mut gas_used = node.trace.gas_used; + let max_refund_adjust_depth = if self.isolate { 1 } else { 0 }; + if node.trace.depth <= max_refund_adjust_depth { + gas_used += node.trace.gas_refund_counter; + } + + self.fst.enter(func_name, gas_used); // Track internal function step exits to do in this call context. let mut step_exits = vec![]; @@ -99,8 +111,8 @@ impl EvmFoldedStackTraceBuilder { if let Some(decoded_step) = &step.decoded { match decoded_step.as_ref() { DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { - let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); - self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64); + let gas_used = step.gas_remaining - steps[*step_end_idx].gas_remaining; + self.fst.enter(decoded_internal_call.func_name.clone(), gas_used); step_exits.push(*step_end_idx); } DecodedTraceStep::Line(_) => {} @@ -158,13 +170,13 @@ pub struct FoldedStackTraceBuilder { struct TraceEntry { /// Names of all functions in the call stack of this trace. names: Vec, - /// Gas consumed by this function, allowed to be negative due to refunds. - gas: i64, + /// Gas consumed by this function, not including refunds. + gas: u64, } impl FoldedStackTraceBuilder { /// Enter execution of a function call that consumes `gas`. - pub fn enter(&mut self, label: String, gas: i64) { + pub fn enter(&mut self, label: String, gas: u64) { let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default(); while self.exits > 0 { @@ -189,7 +201,7 @@ impl FoldedStackTraceBuilder { /// Internal method to build the folded stack trace without subtracting gas consumed by /// the children function calls. - fn build_without_subtraction(&mut self) -> Vec { + pub fn build_without_subtraction(&mut self) -> Vec { let mut lines = Vec::new(); for TraceEntry { names, gas } in &self.traces { lines.push(format!("{} {}", names.join(";"), gas)); diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index fd0e5a9e54c0a..540949f51c071 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -407,7 +407,9 @@ impl<'ast> State<'_, 'ast> { self.block_depth -= 1; } else { if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() { - self.zerobreak(); + // Adjust the offset of the trailing break from comment printing + // so the closing brace is not indented + self.s.offset(-self.ind); } else if self.config.bracket_spacing { self.nbsp(); }; diff --git a/crates/fmt/testdata/CommentEmptyLine/fmt.sol b/crates/fmt/testdata/CommentEmptyLine/fmt.sol new file mode 100644 index 0000000000000..a2407017f4be9 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/fmt.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} diff --git a/crates/fmt/testdata/CommentEmptyLine/original.sol b/crates/fmt/testdata/CommentEmptyLine/original.sol new file mode 100644 index 0000000000000..9c7f35770e9f3 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/original.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index b0d4ed3bdcd7f..f337b5b3657fa 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -169,6 +169,7 @@ fmt_tests! { ArrayExpressions, BlockComments, BlockCommentsFunction, + CommentEmptyLine, ConditionalOperatorExpression, ConstructorDefinition, ConstructorModifierStyle, @@ -224,3 +225,28 @@ fmt_tests! { Yul, YulStrings, } + +#[test] +fn test_comment_empty_line_bug() { + init_tracing(); + let source = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} +"#; + + let expected = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} +"#; + + let fmt_config = Arc::new(FormatterConfig::default()); + let path = Path::new("test.sol"); + let formatted = format(source, path, fmt_config); + + assert_eq!(formatted, expected, "Formatting mismatch"); +} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 2142a87c5cbd4..c059fb00e8f0a 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -35,6 +35,7 @@ foundry-linking.workspace = true comfy-table.workspace = true eyre.workspace = true proptest.workspace = true +rand.workspace = true rayon.workspace = true serde.workspace = true tracing.workspace = true diff --git a/crates/forge/src/args.rs b/crates/forge/src/args.rs index aada974c91b9a..4f55f028a3690 100644 --- a/crates/forge/src/args.rs +++ b/crates/forge/src/args.rs @@ -13,6 +13,8 @@ use foundry_evm::inspectors::cheatcodes::{ForgeContext, set_execution_context}; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Forge::parse(); args.global.init()?; diff --git a/crates/forge/src/cmd/eip712.rs b/crates/forge/src/cmd/eip712.rs index 00a872a1df7a4..5d6b1bace69ca 100644 --- a/crates/forge/src/cmd/eip712.rs +++ b/crates/forge/src/cmd/eip712.rs @@ -84,7 +84,7 @@ impl Eip712Args { if compiler.sess().dcx.has_errors().is_err() { eyre::bail!("{diags}"); } else { - let _ = sh_print!("{diags}"); + let _ = sh_eprint!("{diags}"); } Ok(()) diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index a31f227d4d675..a026a80b5231b 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -43,6 +43,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, }; +use rand::Rng; use regex::Regex; use std::{ collections::{BTreeMap, BTreeSet}, @@ -304,6 +305,12 @@ impl TestArgs { config.invariant.gas_report_samples = 0; } + // Generate a random fuzz seed if none provided, for reproducibility. + config.fuzz.seed = config + .fuzz + .seed + .or_else(|| Some(U256::from_be_bytes(rand::rng().random::<[u8; 32]>()))); + // Create test options from general project settings and compiler output. let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; @@ -361,7 +368,7 @@ impl TestArgs { // Decode traces. let decoder = outcome.last_run_decoder.as_ref().unwrap(); decode_trace_arena(arena, decoder).await; - let mut fst = folded_stack_trace::build(arena); + let mut fst = folded_stack_trace::build(arena, self.evm.isolate); let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; let contract = suite_name.split(':').next_back().unwrap(); @@ -429,6 +436,7 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, output: &ProjectCompileOutput, ) -> eyre::Result { + let fuzz_seed = config.fuzz.seed; if self.list { return list(runner, filter); } @@ -504,13 +512,13 @@ impl TestArgs { } }); sh_println!("{}", serde_json::to_string(&results)?)?; - return Ok(TestOutcome::new(Some(runner), results, self.allow_failure)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } if self.junit { let results = runner.test_collect(filter)?; sh_println!("{}", junit_xml_report(&results, verbosity).to_string()?)?; - return Ok(TestOutcome::new(Some(runner), results, self.allow_failure)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } let remote_chain = @@ -567,6 +575,7 @@ impl TestArgs { let mut gas_snapshots = BTreeMap::>::new(); let mut outcome = TestOutcome::empty(None, self.allow_failure); + outcome.fuzz_seed = fuzz_seed; let mut any_test_failed = false; let mut backtrace_builder = None; diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 985febef2b964..a8a1b5bbc10cf 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -5,7 +5,9 @@ use crate::{ traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use alloy_primitives::map::HashSet; -use comfy_table::{Cell, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; +use comfy_table::{ + Cell, CellAlignment, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, +}; use foundry_common::{TestFunctionExt, calc, shell}; use foundry_evm::traces::CallKind; @@ -213,8 +215,8 @@ impl GasReport { Cell::new("Deployment Size").fg(Color::Cyan), ]); table.add_row(vec![ - Cell::new(contract.gas.to_string()), - Cell::new(contract.size.to_string()), + Cell::new(contract.gas.to_string()).set_alignment(CellAlignment::Right), + Cell::new(contract.size.to_string()).set_alignment(CellAlignment::Right), ]); // Add a blank row to separate deployment info from function info. @@ -237,11 +239,19 @@ impl GasReport { table.add_row(vec![ Cell::new(display_name), - Cell::new(gas_info.min.to_string()).fg(Color::Green), - Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), - Cell::new(gas_info.median.to_string()).fg(Color::Yellow), - Cell::new(gas_info.max.to_string()).fg(Color::Red), - Cell::new(gas_info.calls.to_string()), + Cell::new(gas_info.min.to_string()) + .fg(Color::Green) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.mean.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.median.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.max.to_string()) + .fg(Color::Red) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.calls.to_string()).set_alignment(CellAlignment::Right), ]); }) }); diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index c8ccdc5bc995d..693bea6f1eeb7 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -6,7 +6,7 @@ use crate::{ gas_report::GasReport, }; use alloy_primitives::{ - Address, I256, Log, + Address, I256, Log, U256, map::{AddressHashMap, HashMap}, }; use eyre::Report; @@ -46,6 +46,8 @@ pub struct TestOutcome { pub gas_report: Option, /// The runner used to execute the tests. pub runner: Option, + /// The fuzz seed used for the test run. + pub fuzz_seed: Option, } impl TestOutcome { @@ -54,13 +56,14 @@ impl TestOutcome { runner: Option, results: BTreeMap, allow_failure: bool, + fuzz_seed: Option, ) -> Self { - Self { results, allow_failure, last_run_decoder: None, gas_report: None, runner } + Self { results, allow_failure, last_run_decoder: None, gas_report: None, runner, fuzz_seed } } /// Creates a new empty test outcome. pub fn empty(runner: Option, allow_failure: bool) -> Self { - Self::new(runner, BTreeMap::new(), allow_failure) + Self::new(runner, BTreeMap::new(), allow_failure, None) } /// Returns an iterator over all individual succeeding tests and their names. @@ -130,6 +133,11 @@ impl TestOutcome { self.failures().count() } + /// Returns `true` if any fuzz or invariant test failed. + pub fn has_fuzz_failures(&self) -> bool { + self.failures().any(|(_, t)| t.kind.is_fuzz() || t.kind.is_invariant()) + } + /// Sums up all the durations of all individual test suites. /// /// Note that this is not necessarily the wall clock time of the entire test run. @@ -200,6 +208,17 @@ impl TestOutcome { test_word )?; + // Print seed for fuzz/invariant test failures to enable reproduction. + if let Some(seed) = self.fuzz_seed + && outcome.has_fuzz_failures() + { + sh_println!( + "\nFuzz seed: {} (use {} to reproduce)", + format!("{seed:#x}").cyan(), + "`--fuzz-seed`".cyan() + )?; + } + std::process::exit(1); } @@ -895,6 +914,16 @@ impl Default for TestKind { } impl TestKind { + /// Returns `true` if this is a fuzz test. + pub fn is_fuzz(&self) -> bool { + matches!(self, Self::Fuzz { .. }) + } + + /// Returns `true` if this is an invariant test. + pub fn is_invariant(&self) -> bool { + matches!(self, Self::Invariant { .. }) + } + /// The gas consumed by this test pub fn report(&self) -> TestKindReport { match self { diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 4594004e7a8d0..0ab26ee567deb 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1630,13 +1630,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1644,13 +1644,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1658,13 +1658,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1735,13 +1735,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1749,13 +1749,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1763,13 +1763,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1840,13 +1840,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1854,13 +1854,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1868,13 +1868,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1948,13 +1948,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1962,13 +1962,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1976,13 +1976,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2060,13 +2060,13 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2107,13 +2107,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2154,13 +2154,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2209,13 +2209,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2223,13 +2223,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2290,13 +2290,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2304,13 +2304,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2379,13 +2379,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2393,13 +2393,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2407,13 +2407,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2542,19 +2542,19 @@ contract CounterTest is DSTest { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 172107 | 578 | | | | | +| 172107 | 578 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| a | 2402 | 2402 | 2402 | 2402 | 1 | +| a | 2402 | 2402 | 2402 | 2402 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| b | 2447 | 2447 | 2447 | 2447 | 1 | +| b | 2447 | 2447 | 2447 | 2447 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | +| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | +| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2669,15 +2669,15 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| 117171 | 471 | | | | | +| 117171 | 471 | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| deposit | 21185 | 21185 | 21185 | 21185 | 1 | +| deposit | 21185 | 21185 | 21185 | 21185 | 1 | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 29758 | 29758 | 29758 | 29758 | 1 | +| fallback | 29758 | 29758 | 29758 | 29758 | 1 | ╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2685,13 +2685,13 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| 153531 | 494 | | | | | +| 153531 | 494 | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| deposit | 3661 | 3661 | 3661 | 3661 | 1 | +| deposit | 3661 | 3661 | 3661 | 3661 | 1 | ╰-----------------------------------------------------+-----------------+------+--------+------+---------╯ @@ -2807,13 +2807,13 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132471 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 43461 | 43461 | 43461 | 43461 | 1 | +| fallback | 43461 | 43461 | 43461 | 43461 | 1 | ╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2892,13 +2892,13 @@ contract NestedDeploy is Test { +======================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| 0 | 132 | | | | | +| 0 | 132 | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| w | 21185 | 21185 | 21185 | 21185 | 1 | +| w | 21185 | 21185 | 21185 | 21185 | 1 | ╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2906,13 +2906,13 @@ contract NestedDeploy is Test { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+------+--------+------+---------| -| 0 | 731 | | | | | +| 0 | 731 | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+------+--------+------+---------| -| child | 2681 | 2681 | 2681 | 2681 | 1 | +| child | 2681 | 2681 | 2681 | 2681 | 1 | ╰------------------------------------------+-----------------+------+--------+------+---------╯ ╭-------------------------------------------+-----------------+-----+--------+-----+---------╮ @@ -2920,13 +2920,13 @@ contract NestedDeploy is Test { +============================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| 328961 | 1163 | | | | | +| 328961 | 1163 | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | | | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| child | 525 | 525 | 525 | 525 | 1 | +| child | 525 | 525 | 525 | 525 | 1 | ╰-------------------------------------------+-----------------+-----+--------+-----+---------╯ @@ -3732,17 +3732,17 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 156813 | 509 | | | | | +| 156813 | 509 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| number | 2424 | 2424 | 2424 | 2424 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -3750,15 +3750,15 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| 1544498 | 7573 | | | | | +| 1544498 | 7573 | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| setUp | 218902 | 218902 | 218902 | 218902 | 1 | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | ╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -3774,21 +3774,21 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) | src/Counter.sol:Counter Contract | | | | | | |----------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 156813 | 509 | | | | | +| 156813 | 509 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | -| increment | 43482 | 43482 | 43482 | 43482 | 1 | -| number | 2424 | 2424 | 2424 | 2424 | 1 | -| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | | test/Counter.t.sol:CounterTest Contract | | | | | | |-----------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 1544498 | 7573 | | | | | +| 1544498 | 7573 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | -| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | +| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 14217dfaba4de..4a1243051dd88 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1945,7 +1945,7 @@ contract SimpleScript is Script { ]) .assert_success() .stdout_eq(str![[r#" -{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} {"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} @@ -3144,7 +3144,7 @@ contract CounterScript is Script { Compiler run successful! Traces: [..] → new CounterScript@[..] - └─ ← [Return] 2200 bytes of code + └─ ← [Return] 2162 bytes of code [..] CounterScript::setUp() └─ ← [Stop] diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index 7874712a7d21d..fefefb30d9b15 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -384,6 +384,8 @@ Encountered a total of 1 failing tests, 2 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs index 0c765515437a6..eb28ca8c15ca0 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/common.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -98,6 +98,8 @@ Encountered a total of 2 failing tests, 1 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -175,6 +177,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -304,6 +308,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -373,6 +379,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -538,6 +546,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -624,6 +634,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -696,6 +708,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -779,6 +793,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. @@ -888,6 +904,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -980,6 +998,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1092,6 +1112,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1191,6 +1213,8 @@ Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1290,6 +1314,8 @@ Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1589,6 +1615,8 @@ Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/invariant/mod.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs index db3c8247f175e..3cbeeb4a46db7 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/mod.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -431,6 +431,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -458,6 +460,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -481,6 +485,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); }); diff --git a/crates/forge/tests/cli/test_cmd/invariant/target.rs b/crates/forge/tests/cli/test_cmd/invariant/target.rs index 41fadd3cb59d3..a2b766926bb09 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/target.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/target.rs @@ -627,6 +627,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // Test TargetSelectors @@ -670,6 +672,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -715,6 +719,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // Test TargetArtifactSelectors @@ -763,6 +769,8 @@ Encountered a total of 1 failing tests, 1 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 1c02fd40e001f..8a2f0e2e7fafe 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -915,6 +915,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -966,6 +968,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 2ac4279a7c0e5..82ec6744a6172 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -63,6 +63,8 @@ Encountered a total of 3 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 3 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json index eff3ecf321bc8..244a2d94fc7eb 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -45,6 +45,7 @@ "output": "{...}", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Return", "steps": [], "decoded": null @@ -78,6 +79,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [ { @@ -10196,6 +10198,7 @@ "output": "{...}", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Return", "steps": [ { @@ -10564,6 +10567,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [ { @@ -13836,6 +13840,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [], "decoded": null diff --git a/crates/forge/tests/fixtures/invariant_traces.svg b/crates/forge/tests/fixtures/invariant_traces.svg index 37c7bc8ebcf84..8844062c2553c 100644 --- a/crates/forge/tests/fixtures/invariant_traces.svg +++ b/crates/forge/tests/fixtures/invariant_traces.svg @@ -1,4 +1,4 @@ - +

- - - - - - - - - - -  - -
- -**Takeaway: Forge compilation is consistently faster than Hardhat by a factor of `2.1x` to `5.2x`, depending on the amount of caching involved.** - -## Forge +## Getting Started -Forge helps you build, test, fuzz, debug and deploy Solidity contracts. - -The best way to understand Forge is to simply try it (in less than 30 seconds!). - -First, let's initialize a new `counter` example repository: - -```sh -forge init counter -``` - -Next `cd` into `counter` and build: +Initialize a new project, build and test: ```sh +forge init counter && cd counter forge build -``` - -```console -[⠊] Compiling... -[⠔] Compiling 27 files with Solc 0.8.28 -[⠒] Solc 0.8.28 finished in 452.13ms -Compiler run successful! -``` - -Let's [test](https://getfoundry.sh/forge/tests#tests) our contracts: - -```sh forge test ``` -```console -[⠊] Compiling... -No files changed, compilation skipped - -Ran 2 tests for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 31121, ~: 31277) -[PASS] test_Increment() (gas: 31293) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.35ms (4.86ms CPU time) - -Ran 1 test suite in 5.91ms (5.35ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) -``` - -Finally, let's run our deployment script: - -```sh -forge script script/Counter.s.sol -``` - -```console -[⠊] Compiling... -No files changed, compilation skipped -Script ran successfully. -Gas used: 109037 - -If you wish to simulate on-chain transactions pass a RPC URL. -``` - -Run `forge --help` to explore the full list of available subcommands and their usage. - -More documentation can be found in the [forge](https://getfoundry.sh/forge/overview) section of the Foundry Docs. - -## Cast - -Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. - -Here are a few examples of what you can do: - -**Check the latest block on Ethereum Mainnet**: +Interact with a live network: ```sh cast block-number --rpc-url https://eth.merkle.io -``` - -**Check the Ether balance of `vitalik.eth`** - -```sh cast balance vitalik.eth --ether --rpc-url https://eth.merkle.io ``` -**Replay and trace a transaction** - -```sh -cast run 0x9c32042f5e997e27e67f82583839548eb19dc78c4769ad6218657c17f2a5ed31 --rpc-url https://eth.merkle.io -``` - -Optionally, pass `--etherscan-api-key ` to decode transaction traces using verified source maps, providing more detailed and human-readable information. - ---- - -Run `cast --help` to explore the full list of available subcommands and their usage. - -More documentation can be found in the [cast](https://getfoundry.sh/cast/overview) section of the Foundry Docs. - -## Anvil - -Anvil is a fast local Ethereum development node. - -Let's fork Ethereum mainnet at the latest block: +Fork mainnet locally: ```sh anvil --fork-url https://eth.merkle.io ``` -You can use those same `cast` subcommands against your `anvil` instance: - -```sh -cast block-number -``` - ---- - -Run `anvil --help` to explore the full list of available features and their usage. - -More documentation can be found in the [anvil](https://getfoundry.sh/anvil/overview) section of the Foundry Docs. - -## Chisel - -Chisel is a fast, utilitarian, and verbose Solidity REPL. - -To use Chisel, simply type `chisel`. - -```sh -chisel -``` - -From here, start writing Solidity code! Chisel will offer verbose feedback on each input. - -Create a variable `a` and query it: - -```console -➜ uint256 a = 123; -➜ a -Type: uint256 -├ Hex: 0x7b -├ Hex (full word): 0x000000000000000000000000000000000000000000000000000000000000007b -└ Decimal: 123 -``` - -Finally, run `!source` to see `a` was applied: - -```solidity -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; - -import {Vm} from "forge-std/Vm.sol"; - -contract REPL { - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - - /// @notice REPL contract entry point - function run() public { - uint256 a = 123; - } -} -``` - ---- - -Run `chisel --help` to explore the full list of available features and their usage. - -More documentation can be found in the [chisel](https://getfoundry.sh/chisel/overview) section of the Foundry Docs. - -## Configuration - -Foundry is highly configurable, allowing you to tailor it to your needs. Configuration is managed via a file called [`foundry.toml`](./crates/config) located in the root of your project or any parent directory. For a full list of configuration options, refer to the [config package documentation](./crates/config/README.md#all-options). - -You can find additional [setup and configurations guides](https://getfoundry.sh/config/overview) in the [Foundry Docs][foundry-docs] and in the [config crate](./crates/config/README.md): - -- [Configuring with `foundry.toml`](https://getfoundry.sh/config/overview) -- [Setting up VSCode][vscode-setup] -- [Shell autocompletions][shell-setup] - -**Profiles and Namespaces** - -- Configuration can be organized into **profiles**, which are arbitrarily namespaced for flexibility. -- The default profile is named `default`. Learn more in the [Default Profile section](./crates/config/README.md#default-profile). -- To select a different profile, set the `FOUNDRY_PROFILE` environment variable. -- Override specific settings using environment variables prefixed with `FOUNDRY_` (e.g., `FOUNDRY_SRC`). - ---- +Read the [Foundry Docs][foundry-docs] to learn more. ## Contributing Contributions are welcome and highly appreciated. To get started, check out the [contributing guidelines](./CONTRIBUTING.md). -If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/foundry_rs) to chat with us about the development of Foundry! +Join our [Telegram][tg-url] to chat about the development of Foundry. ## Support -Having trouble? See if the answer to your question can be found in the [Foundry Docs][foundry-docs]. - -If the answer is not there: -- Join the [support Telegram][tg-support-url] to get help, or -- Open an issue with [the bug](https://github.com/foundry-rs/foundry/issues/new) +Having trouble? Check the [Foundry Docs][foundry-docs], join the [support Telegram][tg-support-url], or [open an issue](https://github.com/foundry-rs/foundry/issues/new). #### License @@ -338,30 +91,4 @@ for inclusion in these crates by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -## Acknowledgements - -- Foundry is a clean-room rewrite of the testing framework [DappTools][dapptools]. None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] (now [foundry-compilers]) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. -- [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. -- [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. -- Thank you to [Depot](https://depot.dev) for sponsoring us with their fast GitHub runners and sccache, which we use in CI to reduce build and test times significantly. -- All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs), [alloy][alloy] & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. - -[solidity]: https://soliditylang.org/ [foundry-docs]: https://getfoundry.sh -[foundry-gha]: https://github.com/foundry-rs/foundry-toolchain -[foundry-compilers]: https://github.com/foundry-rs/compilers -[ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ -[solady]: https://github.com/Vectorized/solady -[openzeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v5.1 -[morpho-blue]: https://github.com/morpho-org/morpho-blue -[solmate]: https://github.com/transmissions11/solmate/ -[geb]: https://github.com/reflexer-labs/geb -[benchmark-post]: https://www.paradigm.xyz/2022/03/foundry-02#blazing-fast-compilation--testing -[convex]: https://github.com/mds1/convex-shutdown-simulation -[vscode-setup]: https://getfoundry.sh/config/vscode.html -[shell-setup]: https://getfoundry.sh/config/shell-autocompletion.html -[foundry-0.2]: https://github.com/foundry-rs/foundry/releases/tag/nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a -[foundry-1.0]: https://github.com/foundry-rs/foundry/releases/tag/nightly-59f354c179f4e7f6d7292acb3d068815c79286d1 -[dapptools]: https://github.com/dapphub/dapptools -[alloy]: https://github.com/alloy-rs/alloy diff --git a/benches/Cargo.toml b/benches/Cargo.toml index a0012900facf2..0b7b6c4c19039 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -29,6 +29,3 @@ chrono = { version = "0.4", features = ["serde"] } rayon.workspace = true clap = { version = "4", features = ["derive"] } once_cell = "1.21" - -[dev-dependencies] -foundry-test-utils.workspace = true diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index adeb988cd4fca..c10e7c3cedb51 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -39,7 +39,6 @@ alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } alloy-network.workspace = true alloy-eips.workspace = true -alloy-eip5792.workspace = true alloy-rlp.workspace = true alloy-signer = { workspace = true, features = ["eip712"] } alloy-signer-local = { workspace = true, features = ["mnemonic"] } @@ -112,7 +111,6 @@ fdlimit = { version = "0.3", optional = true } [dev-dependencies] alloy-provider = { workspace = true, features = ["txpool-api"] } alloy-pubsub.workspace = true -alloy-eip5792.workspace = true rand.workspace = true reqwest.workspace = true foundry-test-utils.workspace = true diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 67354bcf50b34..17ca572e040c2 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,5 +1,5 @@ use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; -use alloy_primitives::{Address, B64, B256, Bytes, TxHash, U256}; +use alloy_primitives::{Address, B64, B256, Bytes, TxHash, U256, map::HashSet}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, anvil::{Forking, MineOptions}, @@ -10,6 +10,7 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, + parity::TraceType, }, }; use alloy_serde::WithOtherFields; @@ -325,6 +326,13 @@ pub enum EthRequest { #[serde(rename = "trace_filter", with = "sequence")] TraceFilter(TraceFilter), + /// Trace transaction endpoint for parity's `trace_replayBlockTransactions` + #[serde(rename = "trace_replayBlockTransactions")] + TraceReplayBlockTransactions( + #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, + HashSet, + ), + // Custom endpoints, they're not extracted to a separate type out of serde convenience /// send transactions impersonating specific account and contract addresses. #[serde( @@ -578,11 +586,6 @@ pub enum EthRequest { #[serde(rename = "eth_sendUnsignedTransaction", with = "sequence")] EthSendUnsignedTransaction(Box>), - /// Turn on call traces for transactions that are returned to the user when they execute a - /// transaction (instead of just txhash/receipt) - #[serde(rename = "anvil_enableTraces", with = "empty_params")] - EnableTraces(()), - /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. /// Ref: @@ -706,18 +709,6 @@ pub enum EthRequest { /// Rollback the chain #[serde(rename = "anvil_rollback", with = "sequence")] Rollback(Option), - - /// Wallet - #[serde(rename = "wallet_getCapabilities", with = "empty_params")] - WalletGetCapabilities(()), - - /// Add an address to the delegation capability of the wallet - #[serde(rename = "anvil_addCapability", with = "sequence")] - AnvilAddCapability(Address), - - /// Set the executor (sponsor) wallet - #[serde(rename = "anvil_setExecutor", with = "sequence")] - AnvilSetExecutor(String), } /// Represents ethereum JSON-RPC API diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 86cb813fe456a..b3146a7b14d58 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,6 +1,6 @@ //! Transaction related types use alloy_consensus::{ - Signed, Transaction, TxEnvelope, Typed2718, crypto::RecoveryError, transaction::Recovered, + Signed, Transaction, Typed2718, crypto::RecoveryError, transaction::Recovered, }; use alloy_eips::eip2718::Encodable2718; @@ -70,28 +70,8 @@ impl MaybeImpersonatedTransaction { // NOTE: we must update the hash because the tx can be impersonated, this requires forcing // the hash - let inner_envelope = match envelope { - TxEnvelope::Legacy(t) => { - let (tx, sig, _) = t.into_parts(); - TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)) - } - TxEnvelope::Eip2930(t) => { - let (tx, sig, _) = t.into_parts(); - TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)) - } - TxEnvelope::Eip1559(t) => { - let (tx, sig, _) = t.into_parts(); - TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)) - } - TxEnvelope::Eip4844(t) => { - let (tx, sig, _) = t.into_parts(); - TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)) - } - TxEnvelope::Eip7702(t) => { - let (tx, sig, _) = t.into_parts(); - TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)) - } - }; + let (typed_tx, signature, _) = Into::>::into(envelope).into_parts(); + let inner_envelope = Signed::new_unchecked(typed_tx, signature, hash).into(); RpcTransaction { block_hash: None, diff --git a/crates/anvil/server/src/pubsub.rs b/crates/anvil/server/src/pubsub.rs index 26d4126ea5389..ef02ceefb2d61 100644 --- a/crates/anvil/server/src/pubsub.rs +++ b/crates/anvil/server/src/pubsub.rs @@ -221,6 +221,7 @@ where let mut progress = false; for n in (0..pin.processing.len()).rev() { let mut req = pin.processing.swap_remove(n); + #[allow(clippy::collapsible_match)] match req.poll_unpin(cx) { Poll::Ready(resp) => { if let Ok(text) = serde_json::to_string(&resp) { @@ -238,6 +239,7 @@ where 'outer: for n in (0..subscriptions.len()).rev() { let (id, mut sub) = subscriptions.swap_remove(n); 'inner: loop { + #[allow(clippy::collapsible_match)] match sub.poll_next_unpin(cx) { Poll::Ready(Some(res)) => { if let Ok(text) = serde_json::to_string(&res) { diff --git a/crates/anvil/src/args.rs b/crates/anvil/src/args.rs index 806c8bc992155..4dee7f830fa15 100644 --- a/crates/anvil/src/args.rs +++ b/crates/anvil/src/args.rs @@ -7,6 +7,8 @@ use foundry_cli::utils; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let mut args = Anvil::parse(); args.global.init()?; args.node.evm.resolve_rpc_alias(); diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 9194580f8d7b4..9dac717e1f8a2 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -188,13 +188,21 @@ pub struct NodeArgs { #[arg(long)] pub transaction_block_keeper: Option, + /// Maximum number of transactions in a block. + #[arg(long)] + pub max_transactions: Option, + #[command(flatten)] pub evm: AnvilEvmArgs, #[command(flatten)] pub server_config: ServerConfig, - /// Path to the cache directory where states are stored. + /// Path to the cache directory where persisted states are stored (see + /// `--max-persisted-states`). + /// + /// Note: This does not affect the fork RPC cache location (`storage.json`), which is stored in + /// `~/.foundry/cache/rpc///`. #[arg(long, value_name = "PATH")] pub cache_path: Option, } @@ -230,6 +238,7 @@ impl NodeArgs { Ok(NodeConfig::default() .with_gas_limit(self.evm.gas_limit) .disable_block_gas_limit(self.evm.disable_block_gas_limit) + .enable_tx_gas_limit(self.evm.enable_tx_gas_limit) .with_gas_price(self.evm.gas_price) .with_hardfork(hardfork) .with_blocktime(self.block_time) @@ -259,7 +268,7 @@ impl NodeArgs { .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) .with_base_fee(self.evm.block_base_fee_per_gas) .disable_min_priority_fee(self.evm.disable_min_priority_fee) - .with_storage_caching(self.evm.no_storage_caching) + .with_no_storage_caching(self.evm.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) .set_silent(shell::is_quiet()) @@ -277,6 +286,7 @@ impl NodeArgs { .set_pruned_history(self.prune_history) .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) + .with_max_transactions(self.max_transactions) .with_max_persisted_states(self.max_persisted_states) .with_networks(self.evm.networks) .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer) @@ -540,6 +550,10 @@ pub struct AnvilEvmArgs { )] pub disable_block_gas_limit: bool, + /// Enable the transaction gas limit check as imposed by EIP-7825 (Osaka hardfork). + #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")] + pub enable_tx_gas_limit: bool, + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] @@ -891,6 +905,16 @@ mod tests { assert!(args.is_err()); } + #[test] + fn can_parse_enable_tx_gas_limit() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--enable-tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + + // Also test the alias + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + } + #[test] fn can_parse_disable_code_size_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]); diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 59e79bfe3a989..8e4be0a329387 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -14,7 +14,6 @@ use crate::{ }, mem::{self, in_memory_db::MemDb}, }; -use alloy_chains::Chain; use alloy_consensus::BlockHeader; use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_evm::EvmEnv; @@ -58,8 +57,6 @@ use revm::{ use serde_json::{Value, json}; use std::{ fmt::Write as FmtWrite, - fs::File, - io, net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::Arc, @@ -206,7 +203,8 @@ pub struct NodeConfig { pub networks: NetworkConfigs, /// Do not print log messages. pub silent: bool, - /// The path where states are cached. + /// The path where persisted states are cached (used with `max_persisted_states`). + /// This does not affect the fork RPC cache location. pub cache_path: Option, } @@ -533,7 +531,7 @@ impl NodeConfig { BlobExcessGasAndPrice::new( excess_blob_gas, get_blob_base_fee_update_fraction( - self.chain_id.unwrap_or(Chain::mainnet().id()), + self.get_chain_id(), self.get_genesis_timestamp(), ), ) @@ -542,10 +540,7 @@ impl NodeConfig { /// Returns the [`BlobParams`] that should be used. pub fn get_blob_params(&self) -> BlobParams { - get_blob_params( - self.chain_id.unwrap_or(Chain::mainnet().id()), - self.get_genesis_timestamp(), - ) + get_blob_params(self.get_chain_id(), self.get_genesis_timestamp()) } /// Returns the hardfork to use @@ -665,6 +660,15 @@ impl NodeConfig { self } + /// Sets the max number of transactions in a block + #[must_use] + pub fn with_max_transactions(mut self, max_transactions: Option) -> Self { + if let Some(max_transactions) = max_transactions { + self.max_transactions = max_transactions; + } + self + } + /// Sets max number of blocks with transactions to keep in memory #[must_use] pub fn with_transaction_block_keeper>( @@ -822,15 +826,9 @@ impl NodeConfig { self } - /// Disables storage caching #[must_use] - pub fn no_storage_caching(self) -> Self { - self.with_storage_caching(true) - } - - #[must_use] - pub fn with_storage_caching(mut self, storage_caching: bool) -> Self { - self.no_storage_caching = storage_caching; + pub fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { + self.no_storage_caching = no_storage_caching; self } @@ -980,11 +978,8 @@ impl NodeConfig { /// Prints the config info pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> { if let Some(path) = &self.config_out { - let file = io::BufWriter::new( - File::create(path).wrap_err("unable to create anvil config description file")?, - ); let value = self.as_json(fork); - serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?; + foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?; } if !self.silent { sh_println!("{}", self.as_string(fork))?; @@ -1044,7 +1039,10 @@ impl NodeConfig { self } - /// Sets the path where states are cached + /// Sets the path where persisted states are cached (used with `max_persisted_states`). + /// + /// Note: This does not control the fork RPC cache location (`storage.json`), which uses + /// `~/.foundry/cache/rpc///` via [`Config::foundry_block_cache_file`]. #[must_use] pub fn with_cache_path(mut self, cache_path: Option) -> Self { self.cache_path = cache_path; @@ -1212,7 +1210,7 @@ impl NodeConfig { eth_rpc_url: String, env: &mut Env, fees: &FeeManager, - ) -> Result<(ForkedDatabase, ClientForkConfig)> { + ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) @@ -1299,6 +1297,23 @@ latest block number: {latest_block}" ..Default::default() }; + // Determine chain_id early so we can use it consistently + let chain_id = if let Some(chain_id) = self.chain_id { + chain_id + } else { + let chain_id = if let Some(fork_chain_id) = fork_chain_id { + fork_chain_id.to() + } else { + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? + }; + + // need to update the dev signers and env with the chain id + self.set_chain_id(Some(chain_id)); + env.evm_env.cfg_env.chain_id = chain_id; + env.tx.base.chain_id = chain_id.into(); + chain_id + }; + // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() { if let Some(base_fee) = block.header.base_fee_per_gas { @@ -1319,12 +1334,7 @@ latest block number: {latest_block}" (block.header.excess_blob_gas, block.header.blob_gas_used) { // derive the blobparams that are active at this timestamp - let blob_params = get_blob_params( - fork_chain_id - .unwrap_or_else(|| U256::from(Chain::mainnet().id())) - .saturating_to(), - block.header.timestamp, - ); + let blob_params = get_blob_params(chain_id, block.header.timestamp); env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( blob_excess_gas, @@ -1352,21 +1362,6 @@ latest block number: {latest_block}" let block_hash = block.header.hash; - let chain_id = if let Some(chain_id) = self.chain_id { - chain_id - } else { - let chain_id = if let Some(fork_chain_id) = fork_chain_id { - fork_chain_id.to() - } else { - provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? - }; - - // need to update the dev signers and env with the chain id - self.set_chain_id(Some(chain_id)); - env.evm_env.cfg_env.chain_id = chain_id; - env.tx.base.chain_id = chain_id.into(); - chain_id - }; let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id apply_chain_and_block_specific_env_changes::( diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 3e783b7dbafaa..de572546de349 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,6 +1,6 @@ use super::{ backend::mem::{BlockRequest, DatabaseRef, State}, - sign::build_typed_transaction, + sign::build_impersonated, }; use crate::{ ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, @@ -41,7 +41,7 @@ use alloy_network::{ TransactionBuilder4844, TransactionResponse, eip2718::Decodable2718, }; use alloy_primitives::{ - Address, B64, B256, Bytes, Signature, TxHash, TxKind, U64, U256, + Address, B64, B256, Bytes, TxHash, TxKind, U64, U256, map::{HashMap, HashSet}, }; use alloy_rpc_types::{ @@ -56,7 +56,7 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, }; @@ -69,7 +69,6 @@ use anvil_core::{ EthRequest, block::BlockInfo, transaction::{MaybeImpersonatedTransaction, PendingTransaction}, - wallet::WalletCapabilities, }, types::{ReorgOptions, TransactionData}, }; @@ -77,7 +76,8 @@ use anvil_rpc::{error::RpcError, response::ResponseResult}; use foundry_common::provider::ProviderBuilder; use foundry_evm::decode::RevertDecoder; use foundry_primitives::{ - FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxReceipt, FoundryTxType, FoundryTypedTx, + FoundryNetwork, FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxReceipt, FoundryTxType, + FoundryTypedTx, }; use futures::{ StreamExt, TryFutureExt, @@ -113,7 +113,7 @@ pub struct EthApi { /// Whether this node is mining is_mining: bool, /// available signers - signers: Arc>>, + signers: Arc>>>, /// data required for `eth_feeHistory` fee_history_cache: FeeHistoryCache, /// max number of items kept in fee cache @@ -141,7 +141,7 @@ impl EthApi { pub fn new( pool: Arc, backend: Arc, - signers: Arc>>, + signers: Arc>>>, fee_history_cache: FeeHistoryCache, fee_history_limit: u64, miner: Miner, @@ -345,6 +345,9 @@ impl EthApi { EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), + EthRequest::TraceReplayBlockTransactions(block, trace_types) => { + self.trace_replay_block_transactions(block, trace_types).await.to_rpc_result() + } EthRequest::ImpersonateAccount(addr) => { self.anvil_impersonate_account(addr).await.to_rpc_result() } @@ -455,7 +458,6 @@ impl EthApi { EthRequest::EthSendUnsignedTransaction(tx) => { self.eth_send_unsigned_transaction(*tx).await.to_rpc_result() } - EthRequest::EnableTraces(_) => self.anvil_enable_traces().await.to_rpc_result(), EthRequest::EthNewFilter(filter) => self.new_filter(filter).await.to_rpc_result(), EthRequest::EthGetFilterChanges(id) => self.get_filter_changes(&id).await, EthRequest::EthNewBlockFilter(_) => self.new_block_filter().await.to_rpc_result(), @@ -512,11 +514,6 @@ impl EthApi { self.anvil_reorg(reorg_options).await.to_rpc_result() } EthRequest::Rollback(depth) => self.anvil_rollback(depth).await.to_rpc_result(), - EthRequest::WalletGetCapabilities(()) => self.get_capabilities().to_rpc_result(), - EthRequest::AnvilAddCapability(addr) => self.anvil_add_capability(addr).to_rpc_result(), - EthRequest::AnvilSetExecutor(executor_pk) => { - self.anvil_set_executor(executor_pk).to_rpc_result() - } }; if let ResponseResult::Error(err) = &response { @@ -528,21 +525,13 @@ impl EthApi { response } - fn sign_request(&self, from: &Address, request: FoundryTypedTx) -> Result { - match request { - FoundryTypedTx::Deposit(_) => { - let nil_signature = Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ); - return build_typed_transaction(request, nil_signature); - } + fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { + match typed_tx { + FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { if signer.accounts().contains(from) { - let signature = signer.sign_transaction(request.clone(), from)?; - return build_typed_transaction(request, signature); + return signer.sign_transaction_from(from, typed_tx); } } } @@ -568,7 +557,7 @@ impl EthApi { match self.pool.get_transaction(hash) { Some(tx) => Ok(Some(tx.transaction.encoded_2718().into())), None => match self.backend.transaction_by_hash(hash).await? { - Some(tx) => Ok(Some(tx.inner.inner.encoded_2718().into())), + Some(tx) => Ok(Some(tx.as_ref().encoded_2718().into())), None => Ok(None), }, } @@ -1070,17 +1059,16 @@ impl EthApi { })?; let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; - let request = self.build_tx_request(request, nonce).await?; + let typed_tx = self.build_tx_request(request, nonce).await?; // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { - let bypass_signature = self.impersonated_signature(&request); - let transaction = sign::build_typed_transaction(request, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; trace!(target : "node", ?from, "eth_sendTransaction: impersonating"); PendingTransaction::with_impersonated(transaction, from) } else { - let transaction = self.sign_request(&from, request)?; + let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? }; @@ -1371,10 +1359,7 @@ impl EthApi { } let typed_tx = self.build_tx_request(request, nonce).await?; - let tx = build_typed_transaction( - typed_tx, - Signature::new(Default::default(), Default::default(), false), - )?; + let tx = build_impersonated(typed_tx); let raw = tx.encoded_2718().to_vec().into(); @@ -1865,7 +1850,7 @@ impl EthApi { if let Some(filter) = self.filters.get_log_filter(id).await { self.backend.logs(filter).await } else { - Ok(Vec::new()) + Err(BlockchainError::FilterNotFound) } } @@ -1996,6 +1981,18 @@ impl EthApi { node_info!("trace_filter"); self.backend.trace_filter(filter).await } + + /// Replays all transactions in a block returning the requested traces for each transaction + /// + /// Handler for RPC call: `trace_replayBlockTransactions` + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result> { + node_info!("trace_replayBlockTransactions"); + self.backend.trace_replay_block_transactions(block, trace_types).await + } } // == impl EthApi anvil endpoints == @@ -2146,12 +2143,14 @@ impl EthApi { node_info!("anvil_reset"); if let Some(forking) = forking { // if we're resetting the fork we need to reset the instance id - self.backend.reset_fork(forking).await + self.backend.reset_fork(forking).await?; } else { // Reset to a fresh in-memory state - - self.backend.reset_to_in_mem().await + self.backend.reset_to_in_mem().await?; } + // Clear pending transactions since they reference the old chain state. + self.pool.clear(); + Ok(()) } pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { @@ -2175,7 +2174,7 @@ impl EthApi { pub async fn anvil_add_balance(&self, address: Address, balance: U256) -> Result<()> { node_info!("anvil_addBalance"); let current_balance = self.backend.get_balance(address, None).await?; - self.backend.set_balance(address, current_balance + balance).await?; + self.backend.set_balance(address, current_balance.saturating_add(balance)).await?; Ok(()) } @@ -2580,20 +2579,18 @@ impl EthApi { ); // Build typed transaction request - let typed = self.build_tx_request(request.into(), *curr_nonce).await?; + let typed_tx = self.build_tx_request(request.into(), *curr_nonce).await?; // Increment nonce *curr_nonce += 1; // Handle signer and convert to pending transaction if self.is_impersonated(from) { - let bypass_signature = self.impersonated_signature(&typed); - let transaction = - sign::build_typed_transaction(typed, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::with_impersonated(transaction, from) } else { - let transaction = self.sign_request(&from, typed)?; + let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? } @@ -2806,15 +2803,6 @@ impl EthApi { Ok(()) } - /// Turn on call traces for transactions that are returned to the user when they execute a - /// transaction (instead of just txhash/receipt) - /// - /// Handler for ETH RPC call: `anvil_enableTraces` - pub async fn anvil_enable_traces(&self) -> Result<()> { - node_info!("anvil_enableTraces"); - Err(BlockchainError::RpcUnimplemented) - } - /// Execute a transaction regardless of signature status /// /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` @@ -2828,10 +2816,9 @@ impl EthApi { let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; - let request = self.build_tx_request(request, nonce).await?; + let typed_tx = self.build_tx_request(request, nonce).await?; - let bypass_signature = self.impersonated_signature(&request); - let transaction = sign::build_typed_transaction(request, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; @@ -2939,33 +2926,6 @@ impl EthApi { } } -// ===== impl Wallet endpoints ===== -impl EthApi { - /// Get the capabilities of the wallet. - /// - /// See also [EIP-5792][eip-5792]. - /// - /// [eip-5792]: https://eips.ethereum.org/EIPS/eip-5792 - pub fn get_capabilities(&self) -> Result { - node_info!("wallet_getCapabilities"); - Ok(self.backend.get_capabilities()) - } - - /// Add an address to the delegation capability of wallet. - /// - /// This entails that the executor will now be able to sponsor transactions to this address. - pub fn anvil_add_capability(&self, address: Address) -> Result<()> { - node_info!("anvil_addCapability"); - self.backend.add_capability(address); - Ok(()) - } - - pub fn anvil_set_executor(&self, executor_pk: String) -> Result
{ - node_info!("anvil_setExecutor"); - self.backend.set_executor(executor_pk) - } -} - impl EthApi { /// Executes the future on a new blocking task. async fn on_blocking_task(&self, c: C) -> Result @@ -3223,7 +3183,7 @@ impl EthApi { /// Returns the first signer that can sign for the given address #[expect(clippy::borrowed_box)] - pub fn get_signer(&self, address: Address) -> Option<&Box> { + pub fn get_signer(&self, address: Address) -> Option<&Box>> { self.signers.iter().find(|signer| signer.is_signer_for(address)) } @@ -3328,10 +3288,14 @@ impl EthApi { request.kind().is_none().then(|| request.set_kind(TxKind::default())); if request.gas_limit().is_none() { request.set_gas_limit( - self.do_estimate_gas(request.as_ref().clone(), None, EvmOverrides::default()) - .await - .map(|v| v as u64) - .unwrap_or(self.backend.gas_limit()), + self.do_estimate_gas( + request.as_ref().clone().into(), + None, + EvmOverrides::default(), + ) + .await + .map(|v| v as u64) + .unwrap_or(self.backend.gas_limit()), ); } @@ -3387,30 +3351,6 @@ impl EthApi { self.backend.cheats().is_impersonated(addr) } - /// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC - fn impersonated_signature(&self, request: &FoundryTypedTx) -> Signature { - match request { - // Only the legacy transaction type requires v to be in {27, 28}, thus - // requiring the use of Parity::NonEip155 - FoundryTypedTx::Legacy(_) => Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ), - FoundryTypedTx::Eip2930(_) - | FoundryTypedTx::Eip1559(_) - | FoundryTypedTx::Eip7702(_) - | FoundryTypedTx::Eip4844(_) - | FoundryTypedTx::Deposit(_) => Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ), - // TODO(onbjerg): we should impl support for Tempo transactions - FoundryTypedTx::Tempo(_) => todo!(), - } - } - /// Returns the nonce of the `address` depending on the `block_number` async fn get_transaction_count( &self, diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 6ad560787adcc..e162fd89e67c2 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -7,7 +7,11 @@ use std::{ }; use alloy_consensus::{BlockBody, Header}; -use alloy_primitives::{Address, B256, Bytes, U256, keccak256, map::HashMap}; +use alloy_eips::eip4895::Withdrawals; +use alloy_primitives::{ + Address, B256, Bytes, U256, keccak256, + map::{AddressMap, HashMap}, +}; use alloy_rpc_types::BlockId; use anvil_core::eth::{ block::Block, @@ -37,7 +41,7 @@ use crate::mem::storage::MinedTransaction; /// Helper trait get access to the full state data of the database pub trait MaybeFullDatabase: DatabaseRef + Debug { - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { None } @@ -60,7 +64,7 @@ impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { T::maybe_as_full_db(self) } @@ -168,6 +172,7 @@ pub trait Db: Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0))) }, nonce, + account_id: None, }, ); @@ -237,7 +242,7 @@ impl + Send + Sync + Clone + fmt::Debug> D } impl + Debug> MaybeFullDatabase for CacheDB { - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.cache.accounts) } @@ -345,7 +350,7 @@ impl DatabaseRef for StateDb { } impl MaybeFullDatabase for StateDb { - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { self.0.maybe_as_full_db() } @@ -579,6 +584,8 @@ pub struct SerializableBlock { pub header: Header, pub transactions: Vec, pub ommers: Vec
, + #[serde(default)] + pub withdrawals: Option, } impl From for SerializableBlock { @@ -587,6 +594,7 @@ impl From for SerializableBlock { header: block.header, transactions: block.body.transactions.into_iter().map(Into::into).collect(), ommers: block.body.ommers.into_iter().collect(), + withdrawals: block.body.withdrawals, } } } @@ -595,7 +603,7 @@ impl From for Block { fn from(block: SerializableBlock) -> Self { let transactions = block.transactions.into_iter().map(Into::into).collect(); let ommers = block.ommers; - let body = BlockBody { transactions, ommers, withdrawals: None }; + let body = BlockBody { transactions, ommers, withdrawals: block.withdrawals }; Self::new(block.header, body) } } @@ -714,4 +722,36 @@ mod test { let _block: SerializableBlock = serde_json::from_str(block).unwrap(); } + + #[test] + fn test_block_withdrawals_preserved() { + use alloy_eips::eip4895::Withdrawal; + + // create a block with withdrawals (like post-Shanghai blocks) + let withdrawal = Withdrawal { + index: 42, + validator_index: 123, + address: Address::repeat_byte(1), + amount: 1000, + }; + + let header = Header::default(); + let body = BlockBody { + transactions: vec![], + ommers: vec![], + withdrawals: Some(vec![withdrawal].into()), + }; + let block = Block::new(header, body); + + // convert to SerializableBlock and back + let serializable = SerializableBlock::from(block); + let restored = Block::from(serializable); + + // withdrawals should be preserved + assert!(restored.body.withdrawals.is_some()); + let withdrawals = restored.body.withdrawals.unwrap(); + assert_eq!(withdrawals.len(), 1); + assert_eq!(withdrawals[0].index, 42); + assert_eq!(withdrawals[0].validator_index, 123); + } } diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index aac713c9a3ef4..f102df45f656c 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -18,6 +18,7 @@ use alloy_consensus::{ proofs::calculate_receipt_root, transaction::Either, }; use alloy_eips::{ + Encodable2718, eip2935, eip4788, eip7685::EMPTY_REQUESTS_HASH, eip7702::{RecoveredAuthority, RecoveredAuthorization}, eip7840::BlobParams, @@ -28,7 +29,7 @@ use alloy_evm::{ precompiles::{DynPrecompile, Precompile, PrecompilesMap}, }; use alloy_op_evm::OpEvmFactory; -use alloy_primitives::{B256, Bloom, BloomInput, Log}; +use alloy_primitives::{B256, Bloom, BloomInput, Bytes, Log}; use anvil_core::eth::{ block::{BlockInfo, create_block}, transaction::{PendingTransaction, TransactionInfo}, @@ -71,7 +72,7 @@ impl ExecutedTransaction { *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); // successful return see [Return] - let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); + let status_code = u8::from(self.exit_reason.is_ok()); let receipt_with_bloom: ReceiptWithBloom = Receipt { status: (status_code == 1).into(), cumulative_gas_used: *cumulative_gas_used, @@ -169,6 +170,26 @@ impl TransactionExecutor<'_, DB, V> { if is_cancun { self.evm_env.block_env().blob_excess_gas() } else { None }; let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; + // EIP-2935: store parent block hash in history storage contract. + if is_prague && !block_number.is_zero() { + let env = Env::new(self.evm_env.clone(), Default::default(), self.networks); + let mut inspector = AnvilInspector::default(); + let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); + // SYSTEM_ADDRESS is defined in EIP-4788 and reused by EIP-2935. + match evm.transact_system_call( + eip4788::SYSTEM_ADDRESS, + eip2935::HISTORY_STORAGE_ADDRESS, + Bytes::copy_from_slice(parent_hash.as_slice()), + ) { + Ok(result) => { + self.db.commit(result.state); + } + Err(err) => { + warn!(target: "backend", "EIP-2935 system call failed: {:?}", err); + } + } + } + for tx in self.into_iter() { let tx = match tx { TransactionExecutionOutcome::Executed(tx) => { @@ -210,14 +231,16 @@ impl TransactionExecutor<'_, DB, V> { let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; build_logs_bloom(&logs, &mut bloom); - let contract_address = out.as_ref().and_then(|out| { - if let Output::Create(_, contract_address) = out { - trace!(target: "backend", "New contract deployed: at {:?}", contract_address); - *contract_address - } else { - None - } - }); + // For contract creation transactions, compute the contract address from sender + nonce. + // This should be set even if the transaction reverted, matching geth's behavior. + let sender = *transaction.pending_transaction.sender(); + let contract_address = if transaction.pending_transaction.transaction.to().is_none() { + let addr = sender.create(tx.nonce); + trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); + Some(addr) + } else { + None + }; let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { @@ -303,7 +326,7 @@ impl TransactionExecutor<'_, DB, V> { } if self.networks.is_optimism() { - tx_env.enveloped_tx = Some(alloy_rlp::encode(tx.transaction.as_ref()).into()); + tx_env.enveloped_tx = Some(tx.transaction.encoded_2718().into()); } Env::new(self.evm_env.clone(), tx_env, self.networks) @@ -502,7 +525,10 @@ where { if env.networks.is_optimism() { let evm_env = EvmEnv::new( - env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS), + env.evm_env + .cfg_env + .clone() + .with_spec_and_mainnet_gas_params(op_revm::OpSpecId::ISTHMUS), env.evm_env.block_env.clone(), ); EitherEvm::Op(OpEvmFactory::default().create_evm_with_inspector(db, evm_env, inspector)) diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 995206fbfcc89..362a9906fd418 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -6,7 +6,7 @@ use alloy_eips::eip2930::AccessListResult; use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse}; use alloy_primitives::{ Address, B256, Bytes, StorageValue, U256, - map::{FbHashMap, HashMap}, + map::{FbHashMap, HashMap, HashSet}, }; use alloy_provider::{ Provider, @@ -19,7 +19,7 @@ use alloy_rpc_types::{ simulate::{SimulatePayload, SimulatedBlock}, trace::{ geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace as Trace, + parity::{LocalizedTransactionTrace as Trace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_serde::WithOtherFields; @@ -308,6 +308,7 @@ impl ClientFork { index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { + #[allow(clippy::collapsible_match)] match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { @@ -332,6 +333,7 @@ impl ClientFork { index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { + #[allow(clippy::collapsible_match)] match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { @@ -419,6 +421,16 @@ impl ClientFork { Ok(traces) } + pub async fn trace_replay_block_transactions( + &self, + number: u64, + trace_types: HashSet, + ) -> Result, TransportError> { + // Forward to upstream provider for historical blocks + let params = (number, trace_types.iter().map(|t| format!("{t:?}")).collect::>()); + self.provider().raw_request("trace_replayBlockTransactions".into(), params).await + } + pub async fn transaction_receipt( &self, hash: B256, @@ -675,7 +687,7 @@ impl ClientForkConfig { .initial_backoff(self.backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .build() - .map_err(|_| BlockchainError::InvalidUrl(url.clone()))?, // .interval(interval), + .map_err(|e| BlockchainError::InvalidUrl(format!("{url}: {e}")))?, /* .interval(interval), */ ); trace!(target: "fork", "Updated rpc url {}", url); self.eth_rpc_url = url; diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 597dd1ee2d179..a2db1c1d66e80 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -32,6 +32,7 @@ impl GenesisConfig { // we set this to empty so `Database::code_by_hash` doesn't get called code: Some(Default::default()), nonce: 0, + account_id: None, }; (address, info) }) @@ -65,6 +66,7 @@ impl GenesisConfig { nonce: nonce.unwrap_or_default(), code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), code, + account_id: None, } } } diff --git a/crates/anvil/src/eth/backend/mem/cache.rs b/crates/anvil/src/eth/backend/mem/cache.rs index b7c7d634a3d6c..524ef9cbd74bb 100644 --- a/crates/anvil/src/eth/backend/mem/cache.rs +++ b/crates/anvil/src/eth/backend/mem/cache.rs @@ -55,24 +55,21 @@ impl DiskStateCache { } } - /// Stores the snapshot for the given hash + /// Stores the snapshot for the given hash synchronously. /// - /// Note: this writes the state on a new spawned task - /// - /// Caution: this requires a running tokio Runtime. - pub fn write(&mut self, hash: B256, state: StateSnapshot) { - self.with_cache_file(hash, |file| { - tokio::task::spawn_blocking(move || { - match foundry_common::fs::write_json_file(&file, &state) { - Ok(_) => { - trace!(target: "backend", ?hash, "wrote state json file"); - } - Err(err) => { - error!(target: "backend", %err, ?hash, "Failed to write state snapshot"); - } - }; - }); - }); + /// Returns `true` if the write was successful, `false` otherwise. + pub fn write(&mut self, hash: B256, state: &StateSnapshot) -> bool { + self.with_cache_file(hash, |file| match foundry_common::fs::write_json_file(&file, state) { + Ok(_) => { + trace!(target: "backend", ?hash, "wrote state json file"); + true + } + Err(err) => { + error!(target: "backend", %err, ?hash, "Failed to write state snapshot"); + false + } + }) + .unwrap_or(false) } /// Loads the snapshot file for the given hash diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 306c60cfb62fa..308cdd2e4334c 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -2,7 +2,8 @@ use crate::eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }; -use alloy_primitives::{Address, B256, U256, map::HashMap}; +use alloy_network::Network; +use alloy_primitives::{Address, B256, U256, map::AddressMap}; use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{BlockchainDb, DatabaseResult, RevertStateSnapshotAction, StateSnapshot}, @@ -16,7 +17,7 @@ use revm::{ pub use foundry_evm::fork::database::ForkedDatabase; -impl Db for ForkedDatabase { +impl Db for ForkedDatabase { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.database_mut().insert_account(address, account) } @@ -86,8 +87,8 @@ impl Db for ForkedDatabase { } } -impl MaybeFullDatabase for ForkedDatabase { - fn maybe_as_full_db(&self) -> Option<&HashMap> { +impl MaybeFullDatabase for ForkedDatabase { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.database().cache.accounts) } @@ -121,8 +122,8 @@ impl MaybeFullDatabase for ForkedDatabase { } } -impl MaybeFullDatabase for ForkDbStateSnapshot { - fn maybe_as_full_db(&self) -> Option<&HashMap> { +impl MaybeFullDatabase for ForkDbStateSnapshot { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.local.cache.accounts) } @@ -154,7 +155,7 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { } } -impl MaybeForkedDatabase for ForkedDatabase { +impl MaybeForkedDatabase for ForkedDatabase { fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { self.reset(url, block_number) } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index d395259bad901..766805ee7ae9d 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -7,7 +7,7 @@ use crate::{ }, mem::state::state_root, }; -use alloy_primitives::{Address, B256, U256, map::HashMap}; +use alloy_primitives::{Address, B256, U256, map::AddressMap}; use alloy_rpc_types::BlockId; use foundry_evm::backend::{BlockchainDb, DatabaseResult, StateSnapshot}; use revm::{ @@ -106,7 +106,7 @@ impl Db for MemDb { } impl MaybeFullDatabase for MemDb { - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.inner.cache.accounts) } @@ -164,6 +164,7 @@ mod tests { code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, + account_id: None, }, ); dump_db @@ -206,6 +207,7 @@ mod tests { code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, + account_id: None, }, ); diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 3efaa1db98ec1..e50f2115f80c8 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -38,8 +38,8 @@ impl AnvilInspector { /// /// This will log all `console.sol` logs pub fn print_logs(&self) { - if let Some(collector) = &self.log_collector { - print_logs(&collector.logs); + if let Some(LogCollector::Capture { logs }) = &self.log_collector { + print_logs(logs); } } @@ -78,7 +78,7 @@ impl AnvilInspector { /// Configures the `Tracer` [`revm::Inspector`] with a log collector pub fn with_log_collector(mut self) -> Self { - self.log_collector = Some(Default::default()); + self.log_collector = Some(LogCollector::Capture { logs: Vec::new() }); self } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index b442843ccc4f7..cd1e2a03d5cd5 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -24,7 +24,7 @@ use crate::{ fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, pool::transactions::PoolTransaction, - sign::build_typed_transaction, + sign::build_impersonated, }, mem::{ inspector::AnvilInspector, @@ -38,9 +38,8 @@ use alloy_consensus::{ proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, }; -use alloy_eip5792::{Capabilities, DelegationCapability}; use alloy_eips::{ - BlockNumHash, Encodable2718, eip4844::kzg_to_versioned_hash, eip7840::BlobParams, + BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash, eip7840::BlobParams, eip7910::SystemContract, }; use alloy_evm::{ @@ -51,12 +50,11 @@ use alloy_evm::{ }; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, - EthereumWallet, ReceiptResponse, TransactionBuilder, UnknownTxEnvelope, - UnknownTypedTransaction, + ReceiptResponse, TransactionBuilder, UnknownTxEnvelope, UnknownTypedTransaction, }; use alloy_primitives::{ - Address, B256, Bytes, TxHash, TxKind, U64, U256, address, hex, keccak256, logs_bloom, - map::HashMap, + Address, B256, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, + map::{AddressMap, HashMap, HashSet}, }; use alloy_rpc_types::{ AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, @@ -73,17 +71,14 @@ use alloy_rpc_types::{ FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, }, - parity::LocalizedTransactionTrace, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_serde::{OtherFields, WithOtherFields}; -use alloy_signer::Signature; -use alloy_signer_local::PrivateKeySigner; use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer}; use anvil_core::eth::{ block::{Block, BlockInfo}, transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo}, - wallet::WalletCapabilities, }; use anvil_rpc::error::RpcError; use chrono::Datelike; @@ -152,15 +147,6 @@ impl DatabaseRef for dyn crate::eth::backend::db::Db {} pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. pub const MIN_CREATE_GAS: u128 = 53000; -// Executor -pub const EXECUTOR: Address = address!("0x6634F723546eCc92277e8a2F93d4f248bf1189ea"); -pub const EXECUTOR_PK: &str = "0x502d47e1421cb9abef497096728e69f07543232b93ef24de4998e18b5fd9ba0f"; -// Experimental ERC20 -pub const EXP_ERC20_CONTRACT: Address = address!("0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E"); -// Runtime code of the experimental ERC20 contract -pub const EXP_ERC20_RUNTIME_CODE: &[u8] = &hex!( - "60806040526004361015610010575b005b5f3560e01c806306fdde03146106f7578063095ea7b31461068c57806318160ddd1461066757806323b872dd146105a15780632bb7c5951461050e578063313ce567146104f35780633644e5151461045557806340c10f191461043057806370a08231146103fe5780637ecebe00146103cc57806395d89b4114610366578063a9059cbb146102ea578063ad0c8fdd146102ad578063d505accf146100fb5763dd62ed3e0361000e57346100f75760403660031901126100f7576100d261075c565b6100da610772565b602052637f5e9f20600c525f5260206034600c2054604051908152f35b5f80fd5b346100f75760e03660031901126100f75761011461075c565b61011c610772565b6084359160643560443560ff851685036100f757610138610788565b60208101906e04578706572696d656e74455243323608c1b8252519020908242116102a0576040519360018060a01b03169460018060a01b03169565383775081901600e52855f5260c06020600c20958654957f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252602082019586528660408301967fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc688528b6060850198468a528c608087019330855260a08820602e527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9885252528688525260a082015220604e526042602c205f5260ff1660205260a43560405260c43560605260208060805f60015afa93853d5103610293577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92594602094019055856303faf4f960a51b176040526034602c2055a3005b63ddafbaef5f526004601cfd5b631a15a3cc5f526004601cfd5b5f3660031901126100f7576103e834023481046103e814341517156102d65761000e90336107ac565b634e487b7160e01b5f52601160045260245ffd5b346100f75760403660031901126100f75761030361075c565b602435906387a211a2600c52335f526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c335f51602061080d5f395f51905f52602080a3602060405160018152f35b63f4d678b85f526004601cfd5b346100f7575f3660031901126100f757604051604081019080821067ffffffffffffffff8311176103b8576103b491604052600381526204558560ec1b602082015260405191829182610732565b0390f35b634e487b7160e01b5f52604160045260245ffd5b346100f75760203660031901126100f7576103e561075c565b6338377508600c525f52602080600c2054604051908152f35b346100f75760203660031901126100f75761041761075c565b6387a211a2600c525f52602080600c2054604051908152f35b346100f75760403660031901126100f75761000e61044c61075c565b602435906107ac565b346100f7575f3660031901126100f757602060a0610471610788565b828101906e04578706572696d656e74455243323608c1b8252519020604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252838201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604082015246606082015230608082015220604051908152f35b346100f7575f3660031901126100f757602060405160128152f35b346100f75760203660031901126100f7576004356387a211a2600c52335f526020600c2090815490818111610359575f80806103e88487839688039055806805345cdf77eb68f44c54036805345cdf77eb68f44c5580835282335f51602061080d5f395f51905f52602083a304818115610598575b3390f11561058d57005b6040513d5f823e3d90fd5b506108fc610583565b346100f75760603660031901126100f7576105ba61075c565b6105c2610772565b604435908260601b33602052637f5e9f208117600c526034600c20908154918219610643575b506387a211a2915017600c526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c9060018060a01b03165f51602061080d5f395f51905f52602080a3602060405160018152f35b82851161065a57846387a211a293039055856105e8565b6313be252b5f526004601cfd5b346100f7575f3660031901126100f75760206805345cdf77eb68f44c54604051908152f35b346100f75760403660031901126100f7576106a561075c565b60243590602052637f5e9f20600c52335f52806034600c20555f52602c5160601c337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3602060405160018152f35b346100f7575f3660031901126100f7576103b4610712610788565b6e04578706572696d656e74455243323608c1b6020820152604051918291825b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f757565b602435906001600160a01b03821682036100f757565b604051906040820182811067ffffffffffffffff8211176103b857604052600f8252565b6805345cdf77eb68f44c548281019081106107ff576805345cdf77eb68f44c556387a211a2600c525f526020600c20818154019055602052600c5160601c5f5f51602061080d5f395f51905f52602080a3565b63e5cfe9575f526004601cfdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fbe302881d9891005ba1448ba48547cc1cb17dea1a5c4011dfcb035de325bb1d64736f6c634300081b0033" -); pub type State = foundry_evm::utils::StateChangeset; @@ -239,9 +225,6 @@ pub struct Backend { precompile_factory: Option>, /// Prevent race conditions during mining mining: Arc>, - // === wallet === // - capabilities: Arc>, - executor_wallet: Arc>>, /// Disable pool balance checks disable_pool_balance_checks: bool, } @@ -340,8 +323,6 @@ impl Backend { slots_in_an_epoch, precompile_factory, mining: Arc::new(tokio::sync::Mutex::new(())), - capabilities: Arc::new(RwLock::new(WalletCapabilities(Default::default()))), - executor_wallet: Arc::new(RwLock::new(None)), disable_pool_balance_checks, }; @@ -361,44 +342,11 @@ impl Backend { Ok(()) } - /// Get the capabilities of the wallet. - /// - /// Currently the only capability is delegation. - /// - /// See `anvil_core::eth::wallet::Capabilities` for construction helpers. - pub(crate) fn get_capabilities(&self) -> WalletCapabilities { - self.capabilities.read().clone() - } - /// Updates memory limits that should be more strict when auto-mine is enabled pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { self.states.write().update_interval_mine_block_time(block_time) } - /// Adds an address to the wallet's delegation capability. - pub(crate) fn add_capability(&self, address: Address) { - let chain_id = self.env.read().evm_env.cfg_env.chain_id; - let mut capabilities = self.capabilities.write(); - let mut capability = capabilities - .get(chain_id) - .cloned() - .unwrap_or(Capabilities { delegation: DelegationCapability { addresses: vec![] } }); - capability.delegation.addresses.push(address); - capabilities.0.insert(chain_id, capability); - } - - pub(crate) fn set_executor(&self, executor_pk: String) -> Result { - let signer: PrivateKeySigner = - executor_pk.parse().map_err(|_| RpcError::invalid_params("Invalid private key"))?; - - let executor = signer.address(); - let wallet = EthereumWallet::new(signer); - - *self.executor_wallet.write() = Some(wallet); - - Ok(executor) - } - /// Applies the configured genesis settings /// /// This will fund, create the genesis accounts @@ -438,6 +386,14 @@ impl Backend { // insert the new genesis hash to the database so it's available for the next block in // the evm db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); + + // Deploy EIP-2935 blockhash history storage contract if Prague is active. + if self.spec_id() >= SpecId::PRAGUE { + db.set_code( + eip2935::HISTORY_STORAGE_ADDRESS, + eip2935::HISTORY_STORAGE_CODE.clone(), + )?; + } } let db = self.db.write().await; @@ -1218,8 +1174,7 @@ impl Backend { ); if env.networks.is_optimism() { - env.tx.enveloped_tx = - Some(alloy_rlp::encode(tx.pending_transaction.transaction.as_ref()).into()); + env.tx.enveloped_tx = Some(tx.pending_transaction.transaction.encoded_2718().into()); } let db = self.db.read().await; @@ -1747,10 +1702,7 @@ impl Backend { let typed_tx = request.build_unsigned().map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))?; - let tx = build_typed_transaction( - typed_tx, - Signature::new(Default::default(), Default::default(), false), - )?; + let tx = build_impersonated(typed_tx); let tx_hash = tx.hash(); let rpc_tx = transaction_build( None, @@ -1789,7 +1741,7 @@ impl Backend { }) .collect(), }; - logs.extend(sim_res.logs.clone().iter().map(|log| log.inner.clone())); + logs.extend(sim_res.logs.iter().map(|log| log.inner.clone())); log_index += sim_res.logs.len(); call_res.push(sim_res); } @@ -2032,7 +1984,7 @@ impl Backend { drop(evm); let tracing_inspector = inspector.tracer.expect("tracer disappeared"); - let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); + let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default(); trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); @@ -2192,8 +2144,7 @@ impl Backend { let from_block = self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { - // requested log range does not exist yet - return Ok(vec![]); + return Err(BlockchainError::BlockOutOfRange(best, from_block)); } self.logs_for_range(&filter, from_block, to_block).await @@ -2476,8 +2427,14 @@ impl Backend { None => None, }; let block_number = self.convert_block_number(block_number); + let current_number = self.best_number(); + + // Reject requests for future blocks that don't exist yet + if block_number > current_number { + return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); + } - if block_number < self.env.read().evm_env.block_env.number.saturating_to() { + if block_number < current_number { if let Some((block_hash, block)) = self .block_by_number(BlockNumber::Number(block_number)) .await? @@ -2495,10 +2452,7 @@ impl Backend { } warn!(target: "backend", "Not historic state found for block={}", block_number); - return Err(BlockchainError::BlockOutOfRange( - self.env.read().evm_env.block_env.number.saturating_to(), - block_number, - )); + return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); } let db = self.db.read().await; @@ -2995,6 +2949,114 @@ impl Backend { Ok(vec![]) } + /// Replays all transactions in a block and returns the requested traces for each transaction + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result, BlockchainError> { + let block_number = self.convert_block_number(Some(block)); + + // Try mined blocks first + if let Some(results) = + self.mined_parity_trace_replay_block_transactions(block_number, &trace_types) + { + return Ok(results); + } + + // Fallback to fork if block predates fork + if let Some(fork) = self.get_fork() + && fork.predates_fork(block_number) + { + return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?); + } + + Ok(vec![]) + } + + /// Returns the trace results for all transactions in a mined block by replaying them + fn mined_parity_trace_replay_block_transactions( + &self, + block_number: u64, + trace_types: &HashSet, + ) -> Option> { + let block = self.get_block(block_number)?; + + // Execute this in the context of the parent state + let parent_hash = block.header.parent_hash; + let trace_config = TracingInspectorConfig::from_parity_config(trace_types); + + let read_guard = self.states.upgradable_read(); + if let Some(state) = read_guard.get_state(&parent_hash) { + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state = write_guard.get_on_disk_state(&parent_hash)?; + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) + } + } + + /// Replays all transactions in a block with the tracing inspector to generate TraceResults + fn replay_block_transactions_with_inspector( + &self, + block: &Block, + parent_state: &StateDb, + trace_config: TracingInspectorConfig, + trace_types: &HashSet, + ) -> Option> { + let mut cache_db = CacheDB::new(Box::new(parent_state)); + let mut results = Vec::new(); + + // Configure the block environment + let mut env = self.env.read().clone(); + env.evm_env.block_env = BlockEnv { + number: U256::from(block.header.number), + beneficiary: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: Some(block.header.mix_hash), + basefee: block.header.base_fee_per_gas.unwrap_or_default(), + gas_limit: block.header.gas_limit, + ..Default::default() + }; + + // Execute each transaction in the block with tracing + for tx_envelope in &block.body.transactions { + let tx_hash = tx_envelope.hash(); + + // Create a fresh inspector for this transaction + let mut inspector = TracingInspector::new(trace_config); + + // Prepare transaction environment + let pending_tx = + PendingTransaction::from_maybe_impersonated(tx_envelope.clone()).ok()?; + let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( + pending_tx.transaction.as_ref(), + *pending_tx.sender(), + ); + if env.networks.is_optimism() { + tx_env.enveloped_tx = Some(pending_tx.transaction.encoded_2718().into()); + } + + // Execute the transaction with the inspector + let mut evm = self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); + let result = evm.transact(tx_env.clone()).ok()?; + + // Build TraceResults from the inspector and execution result + let full_trace = inspector + .into_parity_builder() + .into_trace_results_with_state(&result, trace_types, &cache_db) + .ok()?; + + results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace }); + + // Commit the state changes for the next transaction + cache_db.commit(result.state); + } + + Some(results) + } + pub async fn transaction_receipt( &self, hash: B256, @@ -3134,7 +3196,8 @@ impl Backend { blob_gas_used, }; - let inner = FoundryTxReceipt::new(receipt); + // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API) + let inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp); Some(MinedTransactionReceipt { inner, out: info.out }) } @@ -3361,14 +3424,14 @@ impl Backend { .into_iter() .map(|(_, v)| v) .collect(); - let storage_proofs = prove_storage(&account.storage, &keys); + let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys); let account_proof = AccountProof { address, balance: account.info.balance, nonce: account.info.nonce, code_hash: account.info.code_hash, - storage_hash: storage_root(&account.storage), + storage_hash, account_proof: proof, storage_proof: keys .into_iter() @@ -3443,7 +3506,7 @@ impl Backend { // Get the database at the common block let common_state = { let return_state_or_throw_err = - |db: Option<&StateDb>| -> Result, BlockchainError> { + |db: Option<&StateDb>| -> Result, BlockchainError> { let state_db = db.ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; @@ -3461,18 +3524,7 @@ impl Backend { }; { - // Set state to common state - self.db.write().await.clear(); - for (address, acc) in common_state { - for (key, value) in acc.storage { - self.db.write().await.set_storage_at(address, key.into(), value.into())?; - } - self.db.write().await.insert_account(address, acc.info); - } - } - - { - // Unwind the storage back to the common ancestor + // Unwind the storage back to the common ancestor first self.blockchain .storage .write() @@ -3488,6 +3540,41 @@ impl Backend { self.time.reset(env.evm_env.block_env.timestamp.saturating_to()); } + + { + // Collect block hashes before acquiring db lock to avoid holding blockchain storage + // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can + // access. + let block_hashes: Vec<_> = { + let storage = self.blockchain.storage.read(); + let min_block = common_block.header.number.saturating_sub(256); + storage + .hashes + .iter() + .filter(|(num, _)| **num >= min_block) + .map(|(&num, &hash)| (num, hash)) + .collect() + }; + + // Acquire db lock once for the entire restore operation to reduce lock churn. + let mut db = self.db.write().await; + db.clear(); + + // Insert account info before storage to prevent fork-mode RPC fetches after clear. + for (address, acc) in common_state { + db.insert_account(address, acc.info); + for (key, value) in acc.storage { + db.set_storage_at(address, key.into(), value.into())?; + } + } + + // Restore block hashes from blockchain storage (now unwound, contains only valid + // blocks). + for (block_num, hash) in block_hashes { + db.insert_block_hash(U256::from(block_num), hash); + } + } + Ok(()) } } @@ -3601,6 +3688,19 @@ impl TransactionValidator for Backend { } } + // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit + if env.evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create { + let max_initcode_size = env + .evm_env + .cfg_env + .limit_contract_code_size + .map(|limit| limit.saturating_mul(2)) + .unwrap_or(revm::primitives::eip3860::MAX_INITCODE_SIZE); + if tx.input().len() > max_initcode_size { + return Err(InvalidTransactionError::MaxInitCodeSizeExceeded); + } + } + // Balance and fee related checks if !self.disable_pool_balance_checks { // Gas limit validation @@ -3819,7 +3919,7 @@ pub fn transaction_build( /// `storage_key` is the hash of the desired storage key, meaning /// this will only work correctly under a secure trie. /// `storage_key` == keccak(key) -pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec> { +pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> (B256, Vec>) { let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect(); let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone())); @@ -3828,7 +3928,7 @@ pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec, keys: &[B256]) -> Vec bool { diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index e055cbfef5487..e577db9ff3b3a 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,6 +1,9 @@ //! Support for generating the state root for memdb storage -use alloy_primitives::{Address, B256, U256, keccak256, map::HashMap}; +use alloy_primitives::{ + B256, U256, keccak256, + map::{AddressMap, HashMap}, +}; use alloy_rlp::Encodable; use alloy_trie::{HashBuilder, Nibbles}; use revm::{database::DbAccount, state::AccountInfo}; @@ -14,7 +17,7 @@ pub fn build_root(values: impl IntoIterator)>) -> B256 } /// Builds state root from the given accounts -pub fn state_root(accounts: &HashMap) -> B256 { +pub fn state_root(accounts: &AddressMap) -> B256 { build_root(trie_accounts(accounts)) } @@ -32,21 +35,21 @@ pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { (Nibbles::unpack(keccak256(key.to_be_bytes::<32>())), data) }) .collect::>(); - storage.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + storage.sort_by_key(|(key, _)| *key); storage } /// Builds iterator over stored key-value pairs ready for account trie root calculation. -pub fn trie_accounts(accounts: &HashMap) -> Vec<(Nibbles, Vec)> { - let mut accounts = accounts +pub fn trie_accounts(accounts: &AddressMap) -> Vec<(Nibbles, Vec)> { + let mut accounts: Vec<(Nibbles, Vec)> = accounts .iter() .map(|(address, account)| { let data = trie_account_rlp(&account.info, &account.storage); (Nibbles::unpack(keccak256(*address)), data) }) - .collect::>(); - accounts.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + .collect(); + accounts.sort_by_key(|(key, _)| *key); accounts } diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index af99852a53032..4d73f19ebdaca 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -149,9 +149,19 @@ impl InMemoryBlockStates { // only write to disk if supported if !self.is_memory_only() { let state_snapshot = state.0.clear_into_state_snapshot(); - self.disk_cache.write(hash, state_snapshot); - self.on_disk_states.insert(hash, state); - self.oldest_on_disk.push_back(hash); + if self.disk_cache.write(hash, &state_snapshot) { + // Write succeeded, move state to on-disk tracking + self.on_disk_states.insert(hash, state); + self.oldest_on_disk.push_back(hash); + } else { + // Write failed, restore state to memory to avoid data loss + state.init_from_state_snapshot(state_snapshot); + self.states.insert(hash, state); + self.present.push_front(hash); + // Increase limit temporarily to prevent infinite retry loop + self.in_memory_limit = self.in_memory_limit.saturating_add(1); + break; + } } } } @@ -426,6 +436,13 @@ impl BlockchainStorage { let block_number = block.header.number; self.blocks.insert(block_hash, block); self.hashes.insert(block_number, block_hash); + + // Update genesis_hash if we are loading block 0, so that Finalized/Safe/Earliest + // block tag lookups return the correct hash. + // See: https://github.com/foundry-rs/foundry/issues/12645 + if block_number == 0 { + self.genesis_hash = block_hash; + } } } diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index 175f2792430b3..056941942c419 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -7,7 +7,7 @@ use std::{sync::Arc, time::Duration}; /// Returns the `Utc` datetime for the given seconds since unix epoch pub fn utc_from_secs(secs: u64) -> DateTime { - DateTime::from_timestamp(secs as i64, 0).unwrap() + DateTime::from_timestamp(secs as i64, 0).unwrap_or(DateTime::::MAX_UTC) } /// Manages block time diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 2db3afbae8060..d3844f67312f2 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -123,6 +123,8 @@ pub enum BlockchainError { }, #[error("Invalid transaction request: {0}")] InvalidTransactionRequest(String), + #[error("filter not found")] + FilterNotFound, } impl From for BlockchainError { @@ -577,6 +579,11 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::RecoveryError(_) => { RpcError::invalid_params(err.to_string()) } + BlockchainError::FilterNotFound => RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }, } .into(), } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 28ad0999e3eba..1d4c718a42f02 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -7,10 +7,7 @@ use std::{ }; use alloy_consensus::{Header, Transaction}; -use alloy_eips::{ - calc_next_block_base_fee, eip1559::BaseFeeParams, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA, - eip7840::BlobParams, -}; +use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_primitives::B256; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; @@ -276,15 +273,22 @@ impl FeeHistoryService { let gas_used = block.header.gas_used as f64; let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); item.gas_used_ratio = gas_used / block.header.gas_limit as f64; - item.blob_gas_used_ratio = - blob_gas_used.map(|g| g / MAX_BLOBS_PER_BLOCK_ELECTRA as f64).unwrap_or(0 as f64); + item.blob_gas_used_ratio = blob_gas_used + .map(|g| { + let max = self.blob_params.max_blob_gas_per_block() as f64; + if max == 0.0 { 0.0 } else { g / max } + }) + .unwrap_or(0.0); // extract useful tx info (gas_used, effective_reward) let mut transactions: Vec<(_, _)> = receipts .iter() .enumerate() .map(|(i, receipt)| { - let gas_used = receipt.cumulative_gas_used(); + let cumulative = receipt.cumulative_gas_used(); + let prev_cumulative = + if i > 0 { receipts[i - 1].cumulative_gas_used() } else { 0 }; + let gas_used = cumulative - prev_cumulative; let effective_reward = block .body .transactions @@ -297,7 +301,7 @@ impl FeeHistoryService { .collect(); // sort by effective reward asc - transactions.sort_by(|(_, a), (_, b)| a.cmp(b)); + transactions.sort_by_key(|(_, reward)| *reward); // calculate percentile rewards item.rewards = reward_percentiles diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 16151219786cb..3f4be8158f457 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -375,10 +375,11 @@ impl EthApi { let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash)); - let receipts = join_all(receipt_futs.map(|r| async { + // Reuse timestamp from the block we already have + let timestamp = block.header.timestamp; + + let receipts = join_all(receipt_futs.map(|r| async move { if let Ok(Some(r)) = r.await { - let block = self.block_by_number(r.block_number().unwrap().into()).await?; - let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { @@ -418,8 +419,14 @@ impl EthApi { let receipts = join_all(receipt_futs.map(|r| async { if let Ok(Some(r)) = r.await { - let block = self.block_by_number(r.block_number().unwrap().into()).await?; - let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + // Try to get timestamp from receipt's other fields first (set by mined receipts), + // fallback to block lookup for fork receipts that may not have it + let timestamp = if let Some(ts) = r.block_timestamp() { + ts + } else { + let block = self.block_by_number(r.block_number().unwrap().into()).await?; + block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp + }; let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 25b5e673db812..8231315ea7e31 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -106,19 +106,17 @@ impl Pool { markers: impl IntoIterator, ) -> PruneResult { debug!(target: "txpool", ?block_number, "pruning transactions"); - self.inner.write().prune_markers(markers) + let res = self.inner.write().prune_markers(markers); + for tx in &res.promoted { + self.notify_ready(tx); + } + res } /// Adds a new transaction to the pool pub fn add_transaction(&self, tx: PoolTransaction) -> Result { let added = self.inner.write().add_transaction(tx)?; - if let AddedTransaction::Ready(ref ready) = added { - self.notify_listener(ready.hash); - // also notify promoted transactions - for promoted in ready.promoted.iter().copied() { - self.notify_listener(promoted); - } - } + self.notify_ready(&added); Ok(added) } @@ -172,6 +170,16 @@ impl Pool { pool.clear(); } + /// Notifies listeners if the transaction was added to the ready queue. + fn notify_ready(&self, tx: &AddedTransaction) { + if let AddedTransaction::Ready(ready) = tx { + self.notify_listener(ready.hash); + for promoted in ready.promoted.iter().copied() { + self.notify_listener(promoted); + } + } + } + /// notifies all listeners about the transaction fn notify_listener(&self, hash: TxHash) { let mut listener = self.transaction_listener.lock(); diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index e549009d7a5c0..e044bf041132c 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -653,11 +653,11 @@ impl ReadyTransactions { // remove from unlocks for mark in &tx.transaction.transaction.requires { - if let Some(hash) = self.provided_markers.get(mark) - && let Some(tx) = ready.get_mut(hash) - && let Some(idx) = tx.unlocks.iter().position(|i| i == hash) + if let Some(provider_hash) = self.provided_markers.get(mark) + && let Some(provider_tx) = ready.get_mut(provider_hash) + && let Some(idx) = provider_tx.unlocks.iter().position(|i| i == &hash) { - tx.unlocks.swap_remove(idx); + provider_tx.unlocks.swap_remove(idx); } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index c29fbd02f2541..57af250a36869 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,16 +1,20 @@ use crate::eth::error::BlockchainError; use alloy_consensus::{Sealed, SignableTransaction}; use alloy_dyn_abi::TypedData; -use alloy_network::TxSignerSync; +use alloy_network::{Network, TxSignerSync}; use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; use alloy_signer::Signer as AlloySigner; use alloy_signer_local::PrivateKeySigner; use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx}; use tempo_primitives::TempoSignature; -/// A transaction signer +/// A transaction signer, generic over the network. +/// +/// Modelled after alloy's `NetworkWallet`: the +/// [`sign_transaction_from`](Signer::sign_transaction_from) method takes an +/// unsigned transaction and returns the fully-signed envelope in one step. #[async_trait::async_trait] -pub trait Signer: Send + Sync { +pub trait Signer: Send + Sync { /// returns the available accounts for this signer fn accounts(&self) -> Vec
; @@ -33,12 +37,14 @@ pub trait Signer: Send + Sync { /// Signs the given hash. async fn sign_hash(&self, address: Address, hash: B256) -> Result; - /// signs a transaction request using the given account in request - fn sign_transaction( + /// Signs an unsigned transaction and returns the signed envelope. + /// + /// Mirrors `NetworkWallet::sign_transaction_from`. + fn sign_transaction_from( &self, - request: FoundryTypedTx, - address: &Address, - ) -> Result; + sender: &Address, + tx: N::UnsignedTx, + ) -> Result; } /// Maintains developer keys @@ -56,7 +62,7 @@ impl DevSigner { } #[async_trait::async_trait] -impl Signer for DevSigner { +impl Signer for DevSigner { fn accounts(&self) -> Vec
{ self.addresses.clone() } @@ -92,36 +98,51 @@ impl Signer for DevSigner { Ok(signer.sign_hash(&hash).await?) } - fn sign_transaction( + fn sign_transaction_from( &self, - request: FoundryTypedTx, - address: &Address, - ) -> Result { - let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?; - match request { - FoundryTypedTx::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - FoundryTypedTx::Eip2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - FoundryTypedTx::Eip1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - FoundryTypedTx::Eip7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - FoundryTypedTx::Eip4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + sender: &Address, + tx: FoundryTypedTx, + ) -> Result { + let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?; + let envelope = match tx { + FoundryTypedTx::Legacy(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Legacy(t.into_signed(sig)) + } + FoundryTypedTx::Eip2930(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip2930(t.into_signed(sig)) + } + FoundryTypedTx::Eip1559(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip1559(t.into_signed(sig)) + } + FoundryTypedTx::Eip7702(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip7702(t.into_signed(sig)) + } + FoundryTypedTx::Eip4844(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip4844(t.into_signed(sig)) + } FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } - FoundryTypedTx::Tempo(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - } + FoundryTypedTx::Tempo(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Tempo(t.into_signed(sig.into())) + } + }; + Ok(envelope) } } -/// converts the `request` into a [`FoundryTypedTx`] with the given signature +/// Builds a TxEnvelope from UnsignedTx with a zeroed signature. /// -/// # Errors -/// -/// This will fail if the `signature` contains an erroneous recovery id. -pub fn build_typed_transaction( - request: FoundryTypedTx, - signature: Signature, -) -> Result { - let tx = match request { +/// Used for impersonated accounts, where transactions are accepted without a valid signature. +pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { + let signature = Signature::new(Default::default(), Default::default(), false); + match typed_tx { FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)), FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)), FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), @@ -132,7 +153,5 @@ pub fn build_typed_transaction( let tempo_sig: TempoSignature = signature.into(); FoundryTxEnvelope::Tempo(tx.into_signed(tempo_sig)) } - }; - - Ok(tx) + } } diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs index 295a3ef0d1fe2..65b718e4a767a 100644 --- a/crates/anvil/src/evm.rs +++ b/crates/anvil/src/evm.rs @@ -36,9 +36,12 @@ mod tests { primitives::hardfork::SpecId, }; - // A precompile activated in the `Prague` spec. + // A precompile activated in the `Prague` spec (BLS12-381 G2 map). const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); + // A precompile activated in the `Osaka` spec (EIP-7951). + const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + // A precompile activated in the `Isthmus` spec. const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); @@ -136,7 +139,7 @@ mod tests { chain.operator_fee_scalar = Some(U256::from(0)); } - let op_cfg = op_env.evm_env.cfg_env.clone().with_spec(op_spec); + let op_cfg: CfgEnv = CfgEnv::new_with_spec(op_spec); let op_evm_context = OpContext { journaled_state: { let mut journal = Journal::new(EmptyDB::default()); @@ -167,10 +170,13 @@ mod tests { } #[test] - fn build_eth_evm_with_extra_precompiles_default_spec() { - let (env, mut evm) = create_eth_evm(SpecId::default()); + fn build_eth_evm_with_extra_precompiles_osaka_spec() { + let (env, mut evm) = create_eth_evm(SpecId::OSAKA); + + // Check that the Osaka precompile IS present when using the Osaka spec. + assert!(evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); - // Check that the Prague precompile IS present when using the default spec. + // Check that the Prague precompile IS present when using the Osaka spec. assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); @@ -192,6 +198,9 @@ mod tests { fn build_eth_evm_with_extra_precompiles_london_spec() { let (env, mut evm) = create_eth_evm(SpecId::LONDON); + // Check that the Osaka precompile IS NOT present when using the London spec. + assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); + // Check that the Prague precompile IS NOT present when using the London spec. assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); @@ -211,13 +220,38 @@ mod tests { } #[test] - fn build_op_evm_with_extra_precompiles_default_spec() { - let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::default()); + fn build_eth_evm_with_extra_precompiles_prague_spec() { + let (env, mut evm) = create_eth_evm(SpecId::PRAGUE); + + // Check that the Osaka precompile IS NOT present when using the Prague spec. + assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); + + // Check that the Prague precompile IS present when using the Prague spec. + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = match &mut evm { + EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(), + _ => unreachable!(), + }; + + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_isthmus_spec() { + let (env, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); - // Check that the Isthmus precompile IS present when using the default spec. + // Check that the Isthmus precompile IS present when using the Isthmus spec. assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - // Check that the Prague precompile IS present when using the default spec. + // Check that the Prague precompile IS present when using the Isthmus spec. assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); @@ -237,7 +271,7 @@ mod tests { #[test] fn build_op_evm_with_extra_precompiles_bedrock_spec() { - let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::BEDROCK); + let (env, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); // Check that the Isthmus precompile IS NOT present when using the `OpSpecId::BEDROCK` spec. assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index 9d1e0958e99fe..a23be64cf89be 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -7,7 +7,10 @@ use crate::{ use alloy_primitives::{TxHash, map::HashMap}; use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; -use anvil_rpc::response::ResponseResult; +use anvil_rpc::{ + error::{ErrorCode, RpcError}, + response::ResponseResult, +}; use futures::{Stream, StreamExt, channel::mpsc::Receiver}; use std::{ pin::Pin, @@ -55,7 +58,11 @@ impl Filters { } } warn!(target: "node::filter", "No filter found for {}", id); - ResponseResult::success(Vec::<()>::new()) + ResponseResult::error(RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }) } /// Returns the original `Filter` of an `eth_newFilter` diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 82b1f6f356d25..af0d05657e476 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -184,7 +184,8 @@ pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { _ => Miner::new(mode), }; - let dev_signer: Box = Box::new(DevSigner::new(signer_accounts)); + let dev_signer: Box> = + Box::new(DevSigner::new(signer_accounts)); let mut signers = vec![dev_signer]; if let Some(genesis) = genesis { let genesis_signers = genesis diff --git a/crates/anvil/test-data/state-dump.json b/crates/anvil/test-data/state-dump.json index a60e3c861c107..e7a1e37f62809 100644 --- a/crates/anvil/test-data/state-dump.json +++ b/crates/anvil/test-data/state-dump.json @@ -1 +1,314 @@ -{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724763179,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","type":"0x2"},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","type":"0x2"},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file +{ + "block": { + "number": 2, + "beneficiary": "0x0000000000000000000000000000000000000000", + "timestamp": 1724763179, + "gas_limit": 30000000, + "basefee": 875175000, + "difficulty": "0x0", + "prevrandao": "0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "nonce": 0, + "balance": "0xa410", + "code": "0x", + "storage": {} + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 0, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 1, + "balance": "0x21e19e0b90393da9b38", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 1, + "balance": "0x21e19e0b6a140b55df8", + "code": "0x", + "storage": {} + } + }, + "best_block_number": 2, + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x66cdcc25", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [], + "ommers": [] + }, + { + "header": { + "parentHash": "0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175", + "transactionsRoot": "0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc29", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853", + "s": "0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0", + "yParity": "0x0", + "hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + }, + { + "header": { + "parentHash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1", + "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x2", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc2b", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x342a1c58", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + } + ], + "transactions": [ + { + "info": { + "transaction_hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "transaction_index": 0, + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "block_number": 1 + }, + { + "info": { + "transaction_hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "transaction_index": 0, + "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0", + "block_number": 2 + } + ] +} diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index e675001d7a480..c5fb4688bf1d5 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -658,8 +658,7 @@ async fn can_remove_pool_transactions() { } #[tokio::test(flavor = "multi_thread")] -#[ignore = "flaky"] -async fn test_reorg() { +async fn flaky_test_reorg() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); @@ -709,7 +708,8 @@ async fn test_reorg() { // Verify that historic blocks are still accessible for num in (0..14).rev() { - let _ = provider.get_block_by_number(num.into()).full().await.unwrap(); + let block = provider.get_block_by_number(num.into()).full().await.unwrap(); + assert!(block.is_some(), "Historic block {num} should be accessible after reorg"); } // Send a few more transaction to verify the chain can still progress @@ -787,6 +787,126 @@ async fn test_reorg() { assert!(res.is_err()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_reorg_blockhash_opcode_consistency() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let multicall = Multicall::deploy(&provider).await.unwrap(); + + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(200) })).await.unwrap(); + + let tip_before_reorg = api.block_number().unwrap().to::(); + + let mut cached_hashes: Vec<(u64, alloy_primitives::B256, alloy_primitives::B256)> = Vec::new(); + for i in 1..=10 { + let block_num = tip_before_reorg - i; + let rpc_hash = + provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; + let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); + assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); + cached_hashes.push((block_num, rpc_hash, opcode_hash)); + } + + let tx = TransactionRequest::default(); + api.anvil_reorg(ReorgOptions { + depth: 5, + tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], + }) + .await + .unwrap(); + + api.mine_one().await; + + for (block_num, rpc_before, opcode_before) in &cached_hashes { + let rpc_after = + provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; + let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); + if *block_num <= tip_before_reorg.saturating_sub(5) { + assert_eq!( + rpc_after, *rpc_before, + "Block {block_num}: hash should not change for non-reorged blocks" + ); + assert_eq!( + opcode_after, *opcode_before, + "Block {block_num}: BLOCKHASH should not change for non-reorged blocks" + ); + } + assert_eq!( + rpc_after, opcode_after, + "Block {block_num}: RPC ({rpc_after}) and BLOCKHASH opcode ({opcode_after}) should match after reorg" + ); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_reorg_deep_blockhash_consistency() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let multicall = Multicall::deploy(&provider).await.unwrap(); + + // Mine 300 blocks (more than BLOCKHASH limit of 256) + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(300) })).await.unwrap(); + + let tip_before_reorg = api.block_number().unwrap().to::(); + assert!(tip_before_reorg > 256, "Need more than 256 blocks for this test"); + + // Check blocks within the 256 window before reorg + let mut cached_hashes: Vec<(u64, alloy_primitives::B256)> = Vec::new(); + for i in [1, 10, 100, 200, 255] { + let block_num = tip_before_reorg - i; + let rpc_hash = + provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; + let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); + assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); + cached_hashes.push((block_num, rpc_hash)); + } + + // Perform a deep reorg (50 blocks) + let tx = TransactionRequest::default(); + api.anvil_reorg(ReorgOptions { + depth: 50, + tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], + }) + .await + .unwrap(); + + api.mine_one().await; + + let tip_after_reorg = api.block_number().unwrap().to::(); + + // Verify blocks still in the 256 window have consistent hashes + for (block_num, rpc_before) in &cached_hashes { + // Skip blocks that were reorged + if *block_num > tip_after_reorg - 50 { + continue; + } + let rpc_after = + provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; + let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); + assert_eq!( + rpc_after, *rpc_before, + "Block {block_num}: hash should not change for non-reorged blocks" + ); + assert_eq!( + rpc_after, opcode_after, + "Block {block_num}: RPC and BLOCKHASH should match after deep reorg" + ); + } + + // Verify BLOCKHASH returns 0 for blocks outside the 256 window + let old_block = tip_after_reorg.saturating_sub(257); + if old_block > 0 { + let opcode_hash = multicall.getBlockHash(U256::from(old_block)).call().await.unwrap(); + assert_eq!( + opcode_hash, + alloy_primitives::B256::ZERO, + "BLOCKHASH should return 0 for blocks outside 256 window" + ); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_rollback() { let (api, handle) = spawn(NodeConfig::test()).await; diff --git a/crates/anvil/tests/it/beacon_api.rs b/crates/anvil/tests/it/beacon_api.rs index 5c6c9b2486fa8..ec0f13c02e7c0 100644 --- a/crates/anvil/tests/it/beacon_api.rs +++ b/crates/anvil/tests/it/beacon_api.rs @@ -66,8 +66,7 @@ async fn test_beacon_api_get_blobs() { .with_blob_sidecar(sidecar) .value(U256::from(100)); - let mut tx = WithOtherFields::new(tx); - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let pending = provider.send_transaction(tx).await.unwrap(); pending_txs.push(pending); diff --git a/crates/anvil/tests/it/eip2935.rs b/crates/anvil/tests/it/eip2935.rs new file mode 100644 index 0000000000000..8bb2beef9605b --- /dev/null +++ b/crates/anvil/tests/it/eip2935.rs @@ -0,0 +1,70 @@ +use crate::utils::http_provider; +use alloy_eips::{BlockNumberOrTag, eip2935::HISTORY_STORAGE_ADDRESS}; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_contract_deployed_at_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); + assert!(!code.is_empty(), "EIP-2935 history storage contract should be deployed at genesis"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_stores_parent_block_hash() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + // Mine a few blocks so there are parent hashes to store + api.mine_one().await; + api.mine_one().await; + api.mine_one().await; + + // Block 1's hash should be stored when block 2 was mined + let block1 = provider + .get_block_by_number(BlockNumberOrTag::from(1)) + .await + .unwrap() + .expect("block 1 should exist"); + let block1_hash = block1.header.hash; + + // Query the history storage contract for block 1's hash. + // The EIP-2935 contract uses raw calldata (not ABI-encoded): pass the block number + // as a 32-byte big-endian word directly. + let call_data: [u8; 32] = U256::from(1).to_be_bytes(); + let tx = TransactionRequest::default().with_to(HISTORY_STORAGE_ADDRESS).with_input(call_data); + let result = provider.call(tx.into()).await.unwrap(); + + let stored_hash = alloy_primitives::B256::from_slice(&result); + assert_eq!(stored_hash, block1_hash, "EIP-2935 contract should store parent block hash"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_no_system_call_on_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + // At genesis (block 0), the contract should exist but no system call should have + // written any parent hash into its storage. Check raw storage slot 0 directly. + let slot = provider.get_storage_at(HISTORY_STORAGE_ADDRESS, U256::from(0)).await.unwrap(); + assert_eq!(slot, U256::ZERO, "No hash should be stored in the contract at genesis"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_not_deployed_before_prague() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); + assert!(code.is_empty(), "EIP-2935 contract should NOT be deployed before Prague"); +} diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 11200e5667206..ddc8c91eefda7 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -39,9 +39,7 @@ async fn can_send_eip4844_transaction() { .with_blob_sidecar(sidecar) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -103,6 +101,29 @@ async fn can_send_eip4844_transaction_eth_send_transaction() { let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); } +// +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction_with_eip7594_sidecar_format() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into())); + let (api, handle) = spawn(node_config).await; + let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build_7594().unwrap(); + + let mut tx = TransactionRequest::default().with_from(alice).with_to(bob); + alloy_network::TransactionBuilder7594::set_blob_sidecar_7594(&mut tx, sidecar); + + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let receipt = pending_tx.get_receipt().await.unwrap(); + let tx_hash = receipt.transaction_hash; + + let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); +} + #[tokio::test(flavor = "multi_thread")] async fn can_send_multiple_blobs_in_one_tx() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); @@ -131,9 +152,7 @@ async fn can_send_multiple_blobs_in_one_tx() { .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -169,9 +188,7 @@ async fn cannot_exceed_six_blobs() { .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let err = provider.send_transaction(tx).await.unwrap_err(); @@ -211,8 +228,6 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { .with_blob_sidecar(sidecar); let mut tx = WithOtherFields::new(tx); - tx.populate_blob_hashes(); - let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; @@ -224,7 +239,6 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { let sidecar = sidecar.build().unwrap(); tx.set_blob_sidecar(sidecar); tx.set_nonce(1); - tx.populate_blob_hashes(); let second_tx = provider.send_transaction(tx).await.unwrap(); api.mine_one().await; @@ -428,9 +442,7 @@ async fn can_get_blobs_by_versioned_hash() { .with_blob_sidecar(sidecar.clone()) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let _receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -466,10 +478,7 @@ async fn can_get_blobs_by_tx_hash() { .with_blob_sidecar(sidecar.clone()) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); - + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let hash = receipt.transaction_hash; api.anvil_set_auto_mine(true).await.unwrap(); diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index d5df1ca93382a..139e9e6b9bdbd 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -830,7 +830,7 @@ async fn test_fork_init_base_fee() { } #[tokio::test(flavor = "multi_thread")] -async fn test_reset_fork_on_new_blocks() { +async fn flaky_test_reset_fork_on_new_blocks() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; @@ -1202,7 +1202,7 @@ async fn test_fork_reset_basefee() { // #[tokio::test(flavor = "multi_thread")] -async fn test_arbitrum_fork_dev_balance() { +async fn flaky_test_arbitrum_fork_dev_balance() { let (api, handle) = spawn( fork_config() .with_fork_block_number(None::) @@ -1240,7 +1240,7 @@ async fn test_arb_fork_mining() { // #[tokio::test(flavor = "multi_thread")] -async fn test_arbitrum_fork_block_number() { +async fn flaky_test_arbitrum_fork_block_number() { // fork to get initial block for test let (_, handle) = spawn( fork_config() diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index 4a52391f776c2..e208093cfe62b 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -3,6 +3,7 @@ mod anvil; mod anvil_api; mod api; mod beacon_api; +mod eip2935; mod eip4844; mod eip7702; mod fork; diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs index abf0fc0d077f7..52c633b058790 100644 --- a/crates/anvil/tests/it/proof.rs +++ b/crates/anvil/tests/it/proof.rs @@ -55,14 +55,17 @@ async fn test_account_proof() { .await .unwrap(); + // Note: proof values account for EIP-2935 history storage contract deployed at genesis. verify_account_proof(&api, address!("0x2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", - "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", @@ -70,17 +73,19 @@ async fn test_account_proof() { ]).await; verify_account_proof(&api, address!("0x62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x1ed9b1dd266b607ee278726d324b855a093394a6"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", - "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; } diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs index ad542d73019db..2c677231e07ad 100644 --- a/crates/anvil/tests/it/state.rs +++ b/crates/anvil/tests/it/state.rs @@ -48,6 +48,65 @@ async fn can_load_state() { assert_eq!(num, U256::from(num_from_tag)); } +// +#[tokio::test(flavor = "multi_thread")] +async fn finalized_block_hash_consistent_after_load_state() { + use alloy_eips::BlockNumberOrTag; + + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, _handle) = spawn(NodeConfig::test()).await; + + api.mine_one().await; + + // Get the original genesis block hash + let original_genesis = api.block_by_number(BlockNumberOrTag::Number(0)).await.unwrap().unwrap(); + let original_genesis_hash = original_genesis.header.hash; + + let state = api.serialized_state(false).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &state).unwrap(); + + // Load state with a different genesis timestamp. + // The new instance will create its own genesis block with a different timestamp, + // but then load_state should overwrite it. The bug is that genesis_hash field isn't updated. + let (api, _handle) = spawn( + NodeConfig::test() + .with_genesis_timestamp(Some(original_genesis.header.timestamp + 1000)) + .with_init_state_path(state_file), + ) + .await; + + // Query finalized block - should return genesis (block 0) since best_number is small + let finalized_block = api.block_by_number(BlockNumberOrTag::Finalized).await.unwrap().unwrap(); + let finalized_hash = finalized_block.header.hash; + let finalized_number = finalized_block.header.number; + + // Query block by the finalized block's number directly + let block_by_number = + api.block_by_number(BlockNumberOrTag::Number(finalized_number)).await.unwrap().unwrap(); + let block_by_number_hash = block_by_number.header.hash; + + // Verify the loaded genesis matches the original + assert_eq!( + block_by_number_hash, original_genesis_hash, + "Loaded genesis should match original genesis hash" + ); + + // Both finalized and block 0 should return the same hash + assert_eq!( + finalized_hash, block_by_number_hash, + "Finalized block hash should match block queried by number" + ); + + // Also verify Earliest block tag returns consistent hash + let earliest_block = api.block_by_number(BlockNumberOrTag::Earliest).await.unwrap().unwrap(); + assert_eq!( + earliest_block.header.hash, original_genesis_hash, + "Earliest block hash should match original genesis hash" + ); +} + #[tokio::test(flavor = "multi_thread")] async fn can_load_existing_state_legacy() { let state_file = "test-data/state-dump-legacy.json"; @@ -666,6 +725,7 @@ async fn test_backward_compatibility_state_dump_deserialization_v1_2() { "output": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "gas_used": 96345, "gas_limit": 143385, + "gas_refund_counter": 0, "status": "Return", "steps": [], "decoded": null diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 5bf7e1afb5457..916dcbb728f63 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -25,7 +25,7 @@ use alloy_rpc_types::{ GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, PreStateConfig, PreStateFrame, }, - parity::{Action, LocalizedTransactionTrace}, + parity::{Action, ChangedType, LocalizedTransactionTrace, TraceType}, }, }; use alloy_serde::WithOtherFields; @@ -777,8 +777,7 @@ async fn test_trace_address_fork2() { } #[tokio::test(flavor = "multi_thread")] -#[ignore = "flaky"] -async fn test_trace_filter() { +async fn flaky_test_trace_filter() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); @@ -1244,7 +1243,7 @@ async fn test_debug_trace_transaction_pre_state_tracer() { "nonce": 1 }, "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { - "balance": "0x56bc75e2d630fffff" + "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" }, "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": { "balance": "0x0", @@ -1274,3 +1273,81 @@ async fn test_debug_trace_transaction_pre_state_tracer() { _ => unreachable!(), } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_trace_replay_block_transactions_local() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let amount = U256::from(1000000u64); + + // Send first transaction + let tx1 = TransactionRequest::default().to(to).value(amount).from(from); + let tx1 = WithOtherFields::new(tx1); + let pending_tx1 = provider.send_transaction(tx1).await.unwrap(); + + // Send second transaction with different value + let tx2 = TransactionRequest::default().to(to).value(amount).from(from); + let tx2 = WithOtherFields::new(tx2); + let pending_tx2 = provider.send_transaction(tx2).await.unwrap(); + + api.mine_one().await; + let receipt1 = pending_tx1.get_receipt().await.unwrap(); + let receipt2 = pending_tx2.get_receipt().await.unwrap(); + + let block_number = receipt2.block_number.unwrap(); + + // Replay the block transactions with call trace type + // Pass block number as hex string as per Ethereum RPC spec + let results = api + .trace_replay_block_transactions( + block_number.into(), + vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(), + ) + .await + .unwrap(); + + // Verify we have traces for both transactions + assert_eq!(results.len(), 2, "Should have traces for 2 transactions"); + + // Verify first transaction hash matches + assert_eq!(results[0].transaction_hash, receipt1.transaction_hash); + + // Verify second transaction hash matches + assert_eq!(results[1].transaction_hash, receipt2.transaction_hash); + + // Verify trace types are present and accurate + for result in results { + let full_trace = &result.full_trace; + + // Verify Trace (call trace) is present and accurate + assert!(!full_trace.trace.is_empty(), "Trace should not be empty"); + let first_trace = &full_trace.trace[0]; + match &first_trace.action { + Action::Call(call) => { + assert_eq!(call.from, from, "Call from address should match"); + assert_eq!(call.to, to, "Call to address should match"); + } + _ => panic!("Expected Call action, got {:?}", first_trace.action), + } + + // Verify VmTrace is present + assert!(full_trace.vm_trace.is_some(), "VmTrace should be present when requested"); + + // Verify StateDiff is present + assert!(full_trace.state_diff.is_some(), "StateDiff should be present when requested"); + // Verify balance change is correct in state diff + let ChangedType:: { from, to } = + full_trace.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap(); + assert_eq!( + to.checked_sub(*from).unwrap(), + amount, + "Incorrect balance change in state diff" + ); + } +} diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index fcbf2647e2cf0..86836de97cae6 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -3,7 +3,7 @@ use crate::{ utils::{connect_pubsub, http_provider_with_signer}, }; use alloy_consensus::Transaction; -use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; +use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; use alloy_primitives::{Address, Bytes, FixedBytes, U256, address, hex, map::B256HashSet}; use alloy_provider::{Provider, WsConnect}; use alloy_rpc_types::{ @@ -899,6 +899,42 @@ async fn test_tx_receipt() { assert!(tx.contract_address.is_some()); } +// +#[tokio::test(flavor = "multi_thread")] +async fn test_reverted_contract_creation_has_contract_address() { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + + // Init code that immediately reverts: PUSH1 0x00 PUSH1 0x00 REVERT (0x60006000fd) + let reverting_init_code = hex!("60006000fd"); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(reverting_init_code.to_vec()); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + // Transaction should have reverted + assert!(!receipt.status()); + + // `to` field should be none (contract creation) + assert!(receipt.to.is_none()); + + // `contractAddress` should still be set even though the transaction reverted + // This matches geth's behavior: https://github.com/ethereum/go-ethereum/issues/27937 + assert!( + receipt.contract_address.is_some(), + "contractAddress should be set for reverted contract creation" + ); + + // Verify the computed address is correct (sender.create(nonce)) + let expected_addr = wallet.address().create(0); + assert_eq!(receipt.contract_address, Some(expected_addr)); +} + #[tokio::test(flavor = "multi_thread")] async fn can_stream_pending_transactions() { let (_api, handle) = diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index cf4966a86c15b..3188ade007e3c 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -33,6 +33,8 @@ use std::time::Instant; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = CastArgs::parse(); args.global.init()?; args.global.tokio_runtime().block_on(run_command(args)) @@ -354,7 +356,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } CastSubcommand::BlockNumber { rpc, block } => { let config = rpc.load_config()?; - let provider = utils::get_provider_with_curl(&config, rpc.curl)?; + let provider = utils::get_provider(&config)?; let number = match block { Some(id) => { provider @@ -375,7 +377,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } CastSubcommand::ChainId { rpc } => { let config = rpc.load_config()?; - let provider = utils::get_provider_with_curl(&config, rpc.curl)?; + let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).chain_id().await?)? } CastSubcommand::Client { rpc } => { @@ -447,7 +449,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::FindBlock(cmd) => cmd.run().await?, CastSubcommand::GasPrice { rpc } => { let config = rpc.load_config()?; - let provider = utils::get_provider_with_curl(&config, rpc.curl)?; + let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).gas_price().await?)?; } CastSubcommand::Index { key_type, key, slot_number } => { diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 6359682ae717e..fef2288334b95 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -16,7 +16,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{ChainValueParser, RpcOpts, TransactionOpts}, - utils::{LoadConfig, TraceResult, get_provider_with_curl, parse_ether_value}, + utils::{LoadConfig, TraceResult, get_provider, parse_ether_value}, }; use foundry_common::{ abi::{encode_function_args, get_func}, @@ -247,7 +247,7 @@ impl CallArgs { sig = Some(data); } - let provider = get_provider_with_curl(&config, false)?; + let provider = get_provider(&config)?; let sender = SenderKind::from_wallet_opts(wallet).await?; let from = sender.address(); @@ -524,13 +524,12 @@ impl CallArgs { // Parse and apply state overrides for (addr, entries) in parse_state_overrides(&self.state_overrides)? { - state_overrides_builder = state_overrides_builder.with_state(addr, entries.into_iter()); + state_overrides_builder = state_overrides_builder.with_state(addr, entries); } // Parse and apply state diff overrides for (addr, entries) in parse_state_overrides(&self.state_diff_overrides)? { - state_overrides_builder = - state_overrides_builder.with_state_diff(addr, entries.into_iter()) + state_overrides_builder = state_overrides_builder.with_state_diff(addr, entries) } Ok(Some(state_overrides_builder.build())) diff --git a/crates/cast/src/cmd/create2.rs b/crates/cast/src/cmd/create2.rs index a07e3fd8e4378..a531c95856666 100644 --- a/crates/cast/src/cmd/create2.rs +++ b/crates/cast/src/cmd/create2.rs @@ -218,7 +218,10 @@ impl Create2Args { // Important: add the thread index to the salt to avoid duplicate results. *salt_word = salt_word.wrapping_add(i); - let mut checksum = [0; 42]; + // Use checksum format only when case_sensitive is enabled. + // This avoids an extra keccak256 call per iteration when not needed. + let mut checksum_buf = [0u8; 42]; + let mut hex_buf = [0u8; 40]; loop { // Stop if a result was found in another thread. if found.load(Ordering::Relaxed) { @@ -229,11 +232,19 @@ impl Create2Args { #[expect(clippy::needless_borrows_for_generic_args)] let addr = deployer.create2(&salt.0, &init_code_hash); - // Check if the regex matches the calculated address' checksum. - let _ = addr.to_checksum_raw(&mut checksum, None); - // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string - // is safe. - let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) }; + // Check if the regex matches the calculated address. + // When case_sensitive is true, use EIP-55 checksum format (requires keccak256). + // Otherwise, use lowercase hex to avoid the extra hash computation. + let s = if case_sensitive { + let _ = addr.to_checksum_raw(&mut checksum_buf, None); + // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 + // string is safe. + unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) } + } else { + // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits). + let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf); + unsafe { std::str::from_utf8_unchecked(&hex_buf) } + }; if regex.matches(s).into_iter().count() == regex_len { // Notify other threads that we found a result. found.store(true, Ordering::Relaxed); diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index 4806fc9489eb2..55ec3951ee6b0 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -3,11 +3,11 @@ use std::str::FromStr; use crate::{ cmd::send::cast_send, format_uint_exp, - tx::{CastTxSender, SendTxOpts, signing_provider_with_curl}, + tx::{CastTxSender, SendTxOpts, get_provider_with_wallet}, }; use alloy_eips::{BlockId, Encodable2718}; use alloy_ens::NameOrAddress; -use alloy_network::{AnyNetwork, NetworkWallet, TransactionBuilder}; +use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; use alloy_primitives::{U64, U256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; @@ -16,8 +16,9 @@ use alloy_sol_types::sol; use clap::{Args, Parser}; use foundry_cli::{ opts::{RpcOpts, TempoOpts}, - utils::{LoadConfig, get_chain, get_provider_with_curl}, + utils::{LoadConfig, get_chain, get_provider}, }; +use foundry_common::shell; #[doc(hidden)] pub use foundry_config::{Chain, utils::*}; use foundry_primitives::FoundryTransactionRequest; @@ -118,8 +119,7 @@ async fn send_erc20_tx>( ftx.set_chain_id(provider.get_chain_id().await?); } - // Sign using NetworkWallet - let signed_tx = signer.sign_request(ftx).await?; + let signed_tx = ftx.build(&EthereumWallet::new(signer)).await?; // Encode and send raw let mut raw_tx = Vec::with_capacity(signed_tx.encode_2718_len()); @@ -352,8 +352,8 @@ impl Erc20Subcommand { match self { // Read-only - Self::Allowance { token, owner, spender, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::Allowance { token, owner, spender, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let owner = owner.resolve(&provider).await?; let spender = spender.resolve(&provider).await?; @@ -364,10 +364,14 @@ impl Erc20Subcommand { .call() .await?; - sh_println!("{}", format_uint_exp(allowance))? + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&allowance.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(allowance))? + } } - Self::Balance { token, owner, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::Balance { token, owner, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let owner = owner.resolve(&provider).await?; @@ -376,10 +380,15 @@ impl Erc20Subcommand { .block(block.unwrap_or_default()) .call() .await?; - sh_println!("{}", format_uint_exp(balance))? + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&balance.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(balance))? + } } - Self::Name { token, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::Name { token, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let name = IERC20::new(token, &provider) @@ -387,10 +396,15 @@ impl Erc20Subcommand { .block(block.unwrap_or_default()) .call() .await?; - sh_println!("{}", name)? + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&name)?)? + } else { + sh_println!("{}", name)? + } } - Self::Symbol { token, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::Symbol { token, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let symbol = IERC20::new(token, &provider) @@ -398,10 +412,15 @@ impl Erc20Subcommand { .block(block.unwrap_or_default()) .call() .await?; - sh_println!("{}", symbol)? + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&symbol)?)? + } else { + sh_println!("{}", symbol)? + } } - Self::Decimals { token, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::Decimals { token, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let decimals = IERC20::new(token, &provider) @@ -409,10 +428,14 @@ impl Erc20Subcommand { .block(block.unwrap_or_default()) .call() .await?; - sh_println!("{}", decimals)? + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&decimals)?)? + } else { + sh_println!("{}", decimals)? + } } - Self::TotalSupply { token, block, rpc, .. } => { - let provider = get_provider_with_curl(&config, rpc.curl)?; + Self::TotalSupply { token, block, .. } => { + let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let total_supply = IERC20::new(token, &provider) @@ -420,11 +443,16 @@ impl Erc20Subcommand { .block(block.unwrap_or_default()) .call() .await?; - sh_println!("{}", format_uint_exp(total_supply))? + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&total_supply.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(total_supply))? + } } // State-changing Self::Transfer { token, to, amount, send_tx, tx: tx_opts, .. } => { - let provider = signing_provider_with_curl(&send_tx, send_tx.eth.rpc.curl).await?; + let provider = get_provider_with_wallet(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .transfer(to.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); @@ -445,7 +473,7 @@ impl Erc20Subcommand { .await? } Self::Approve { token, spender, amount, send_tx, tx: tx_opts, .. } => { - let provider = signing_provider_with_curl(&send_tx, send_tx.eth.rpc.curl).await?; + let provider = get_provider_with_wallet(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .approve(spender.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); @@ -466,7 +494,7 @@ impl Erc20Subcommand { .await? } Self::Mint { token, to, amount, send_tx, tx: tx_opts, .. } => { - let provider = signing_provider_with_curl(&send_tx, send_tx.eth.rpc.curl).await?; + let provider = get_provider_with_wallet(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .mint(to.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); @@ -487,7 +515,7 @@ impl Erc20Subcommand { .await? } Self::Burn { token, amount, send_tx, tx: tx_opts, .. } => { - let provider = signing_provider_with_curl(&send_tx, send_tx.eth.rpc.curl).await?; + let provider = get_provider_with_wallet(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .burn(U256::from_str(&amount)?) .into_transaction_request(); diff --git a/crates/cast/src/cmd/interface.rs b/crates/cast/src/cmd/interface.rs index caae11ae8892a..634fc96d83c31 100644 --- a/crates/cast/src/cmd/interface.rs +++ b/crates/cast/src/cmd/interface.rs @@ -1,4 +1,4 @@ -use alloy_json_abi::{ContractObject, JsonAbi}; +use alloy_json_abi::{ContractObject, JsonAbi, ToSolConfig}; use alloy_primitives::Address; use clap::Parser; use eyre::{Context, Result}; @@ -50,13 +50,19 @@ pub struct InterfaceArgs { )] output: Option, + /// If set, generate all types in a single interface, inlining any inherited or library types. + /// + /// This can fail if there are structs with the same name in different interfaces. + #[arg(long)] + flatten: bool, + #[command(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { - let Self { contract, name, pragma, output: output_location, etherscan } = self; + let Self { contract, name, pragma, output: output_location, flatten, etherscan } = self; // Determine if the target contract is an ABI file, a local contract or an Ethereum address. let abis = if Path::new(&contract).is_file() @@ -73,8 +79,11 @@ impl InterfaceArgs { } }; + // Build config for to_sol conversion. + let config = if flatten { Some(ToSolConfig::new().one_contract(true)) } else { None }; + // Retrieve interfaces from the array of ABIs. - let interfaces = get_interfaces(abis)?; + let interfaces = get_interfaces(abis, config)?; // Print result or write to file. let res = if shell::is_json() { @@ -142,11 +151,14 @@ fn load_abi_from_artifact(path_or_contract: &str) -> Result) -> Result> { +fn get_interfaces( + abis: Vec<(JsonAbi, String)>, + config: Option, +) -> Result> { abis.into_iter() .map(|(contract_abi, name)| { let source = match forge_fmt::format( - &contract_abi.to_sol(&name, None), + &contract_abi.to_sol(&name, config.clone()), FormatterConfig::default(), ) .into_result() @@ -154,7 +166,7 @@ fn get_interfaces(abis: Vec<(JsonAbi, String)>) -> Result> Ok(generated_source) => generated_source, Err(e) => { sh_warn!("Failed to format interface for {name}: {e}")?; - contract_abi.to_sol(&name, None) + contract_abi.to_sol(&name, config.clone()) } }; diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index 541789bbc94c6..da11ea3e47353 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -1,7 +1,7 @@ use crate::tx::{self, CastTxBuilder}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{EthereumWallet, NetworkWallet, TransactionBuilder}; +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, hex}; use alloy_provider::Provider; use alloy_signer::Signer; @@ -132,7 +132,7 @@ impl MakeTxArgs { // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has // unlocked accounts. let (tx, _) = tx_builder.build(config.sender).await?; - let signed_tx = provider.sign_transaction(tx.into_inner()).await?; + let signed_tx = provider.sign_transaction(tx.into_inner().into()).await?; sh_println!("{signed_tx}")?; return Ok(()); @@ -152,8 +152,7 @@ impl MakeTxArgs { if is_tempo { let (ftx, _) = tx_builder.build(&signer).await?; - // Sign using NetworkWallet - let signed_tx = signer.sign_request(ftx).await?; + let signed_tx = ftx.build(&EthereumWallet::new(signer)).await?; // Encode as 2718 let mut raw_tx = Vec::with_capacity(signed_tx.encode_2718_len()); diff --git a/crates/cast/src/cmd/rpc.rs b/crates/cast/src/cmd/rpc.rs index d31202eb136b5..8883c3fbb5be2 100644 --- a/crates/cast/src/cmd/rpc.rs +++ b/crates/cast/src/cmd/rpc.rs @@ -53,7 +53,7 @@ impl RpcArgs { serde_json::Value::Array(params.into_iter().map(value_or_string).collect()) }; - let provider = utils::get_provider_with_curl(&config, rpc.curl)?; + let provider = utils::get_provider(&config)?; let result = Cast::new(provider).rpc(&method, params).await?; if shell::is_json() { let result: serde_json::Value = serde_json::from_str(&result)?; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 0999aa55ec495..c6d316d571009 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -135,7 +135,7 @@ impl RunArgs { let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = foundry_cli::utils::get_provider_builder(&config, false)? + let provider = foundry_cli::utils::get_provider_builder(&config)? .compute_units_per_second_opt(compute_units_per_second) .build()?; @@ -271,7 +271,7 @@ impl RunArgs { break; } - configure_tx_env(&mut env.as_env_mut(), &tx.inner); + configure_tx_env(&mut env.as_env_mut(), tx); env.evm_env.cfg_env.disable_balance_check = true; @@ -312,8 +312,8 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env.as_env_mut(), &tx.inner); - if is_impersonated_tx(tx.inner.inner.inner()) { + configure_tx_env(&mut env.as_env_mut(), &tx); + if is_impersonated_tx(tx.as_ref()) { env.evm_env.cfg_env.disable_balance_check = true; } diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 3e50ed27ed799..d747c975ed63e 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, str::FromStr, time::Duration}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{AnyNetwork, EthereumWallet, NetworkWallet}; +use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; @@ -11,9 +11,8 @@ use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::TransactionOpts, - utils::{LoadConfig, get_provider_with_curl}, + utils::{LoadConfig, get_provider}, }; -use foundry_wallets::WalletSigner; use crate::tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}; @@ -120,7 +119,7 @@ impl SendTxArgs { }; let config = send_tx.eth.load_config()?; - let provider = get_provider_with_curl(&config, send_tx.eth.rpc.curl)?; + let provider = get_provider(&config)?; if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) @@ -139,8 +138,11 @@ impl SendTxArgs { // Check if this is a Tempo transaction - requires special handling for local signing let is_tempo = builder.is_tempo(); - // Tempo transactions with browser wallets are not supported - if is_tempo && send_tx.eth.wallet.browser { + // Launch browser signer if `--browser` flag is set + let browser = send_tx.browser.run().await?; + + // Tempo transactions with browser signer are not supported + if is_tempo && browser.is_some() { return Err(eyre!("Tempo transactions are not supported with browser wallets.")); } @@ -148,7 +150,7 @@ impl SendTxArgs { // Default to sending via eth_sendTransaction if the --unlocked flag is passed. // This should be the only way this RPC method is used as it requires a local node // or remote RPC with unlocked accounts. - if unlocked && !send_tx.eth.wallet.browser { + if unlocked && browser.is_none() { // only check current chain id if it was specified in the config if let Some(config_chain) = config.chain { let current_chain_id = provider.get_chain_id().await?; @@ -172,7 +174,7 @@ impl SendTxArgs { cast_send( provider, - tx.into_inner(), + tx.into_inner().into(), send_tx.cast_async, send_tx.sync, send_tx.confirmations, @@ -184,20 +186,10 @@ impl SendTxArgs { // If we cannot successfully instantiate a local signer, then we will assume we don't have // enough information to sign and we must bail. } else { - // Retrieve the signer, and bail if it can't be constructed. - let signer = send_tx.eth.wallet.signer().await?; - let from = signer.address(); - - tx::validate_from_address(send_tx.eth.wallet.from, from)?; - - // Browser wallets work differently as they sign and send the transaction in one step. - if send_tx.eth.wallet.browser - && let WalletSigner::Browser(ref browser_signer) = signer - { - let (tx_request, _) = builder.build(from).await?; - let tx_hash = browser_signer - .send_transaction_via_browser(tx_request.into_inner().inner) - .await?; + // Browser wallet work differently as it sign and send the transaction in one step. + if let Some(browser) = browser { + let (tx_request, _) = builder.build(browser.address()).await?; + let tx_hash = browser.send_transaction_via_browser(tx_request.into_inner()).await?; if send_tx.cast_async { sh_println!("{tx_hash:#x}")?; @@ -217,6 +209,11 @@ impl SendTxArgs { return Ok(()); } + // Retrieve the signer, and bail if it can't be constructed. + let signer = send_tx.eth.wallet.signer().await?; + let from = signer.address(); + + tx::validate_from_address(send_tx.eth.wallet.from, from)?; // Tempo transactions need to be signed locally and sent as raw transactions // because EthereumWallet doesn't understand type 0x76 // TODO(onbjerg): All of this is a side effect of a few things, most notably that we do @@ -225,8 +222,7 @@ impl SendTxArgs { if is_tempo { let (ftx, _) = builder.build(&signer).await?; - // Sign using NetworkWallet - let signed_tx = signer.sign_request(ftx).await?; + let signed_tx = ftx.build(&EthereumWallet::new(signer)).await?; // Encode and send raw let mut raw_tx = Vec::with_capacity(signed_tx.encode_2718_len()); @@ -238,18 +234,6 @@ impl SendTxArgs { if send_tx.cast_async { sh_println!("{tx_hash:#x}")?; - } else if send_tx.sync { - // For sync mode, we already have the hash, just wait for receipt - let receipt = cast - .receipt( - format!("{tx_hash:#x}"), - None, - send_tx.confirmations, - Some(timeout), - false, - ) - .await?; - sh_println!("{receipt}")?; } else { let receipt = cast .receipt( @@ -275,7 +259,7 @@ impl SendTxArgs { cast_send( provider, - tx_request.into_inner(), + tx_request.into_inner().into(), send_tx.cast_async, send_tx.sync, send_tx.confirmations, diff --git a/crates/cast/src/cmd/storage.rs b/crates/cast/src/cmd/storage.rs index d5a74c0954624..b2a2c32251f6f 100644 --- a/crates/cast/src/cmd/storage.rs +++ b/crates/cast/src/cmd/storage.rs @@ -156,6 +156,7 @@ impl StorageArgs { } // Create or reuse a persistent cache for Etherscan sources; fall back to a temp dir + let mut temp_dir = None; let root_path = if let Some(cache_root) = foundry_config::Config::foundry_etherscan_chain_cache_dir(chain) { @@ -163,12 +164,18 @@ impl StorageArgs { let contract_root = sources_root.join(format!("{address}")); if let Err(err) = std::fs::create_dir_all(&contract_root) { sh_warn!("Could not create etherscan cache dir, falling back to temp: {err}")?; - tempfile::tempdir()?.path().to_path_buf() + let tmp = tempfile::tempdir()?; + let path = tmp.path().to_path_buf(); + temp_dir = Some(tmp); + path } else { contract_root } } else { - tempfile::tempdir()?.keep() + let tmp = tempfile::tempdir()?; + let path = tmp.path().to_path_buf(); + temp_dir = Some(tmp); + path }; let mut project = etherscan_project(metadata, &root_path)?; add_storage_layout_output(&mut project); @@ -221,6 +228,7 @@ impl StorageArgs { artifact }; + drop(temp_dir); fetch_and_print_storage(provider, address, block, artifact).await } } diff --git a/crates/cast/src/cmd/trace.rs b/crates/cast/src/cmd/trace.rs index 6005dc952f086..bc7a4a78b96ad 100644 --- a/crates/cast/src/cmd/trace.rs +++ b/crates/cast/src/cmd/trace.rs @@ -54,7 +54,7 @@ impl TraceArgs { hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed))? } else if is_json { let tx: AnyRpcTransaction = serde_json::from_str(trimmed)?; - tx.inner.inner.encoded_2718().to_vec() + tx.as_ref().encoded_2718().to_vec() } else { hex::decode(trimmed)? }; diff --git a/crates/cast/src/cmd/wallet/list.rs b/crates/cast/src/cmd/wallet/list.rs index 6a4da6a0f18f2..9555dbe4a4f93 100644 --- a/crates/cast/src/cmd/wallet/list.rs +++ b/crates/cast/src/cmd/wallet/list.rs @@ -60,11 +60,10 @@ impl ListArgs { { match self.list_local_senders() { Ok(()) => {} - Err(e) => { - if !self.all { - sh_err!("{}", e)?; - } + Err(e) if !self.all => { + sh_err!("{}", e)?; } + _ => {} } } @@ -78,6 +77,7 @@ impl ListArgs { .turnkey(self.turnkey || self.all) .interactives(0) .interactive(false) + .browser(Default::default()) .build() .expect("build multi wallet"); @@ -96,11 +96,10 @@ impl ListArgs { }) } } - Err(e) => { - if !self.all { - sh_err!("{}", e)?; - } + Err(e) if !self.all => { + sh_err!("{}", e)?; } + _ => {} } }; } @@ -131,7 +130,10 @@ impl ListArgs { && let Some(file_name) = path.file_name() && let Some(name) = file_name.to_str() { - sh_println!("{name} (Local)")?; + // Extract address from keystore filename format: UTC--{timestamp}--{address} + if let Some(address) = name.split("--").last() { + sh_println!("0x{} (Local)", address)?; + } } } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index cf6c585bd1bdd..5f9b288017851 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -325,7 +325,7 @@ impl + Clone + Unpin> Cast

{ let mut result = String::new(); for field in fields { result.push_str( - &get_pretty_block_attr(&block, &field) + &get_pretty_block_attr::(&block, &field) .unwrap_or_else(|| format!("{field} is not a valid block field")), ); @@ -756,7 +756,7 @@ impl + Clone + Unpin> Cast

{ let encoded = foundry_tx.encoded_2718(); format!("0x{}", hex::encode(encoded)) } else if let Some(ref field) = field { - get_pretty_tx_attr(&tx.inner, field.as_str()) + get_pretty_tx_attr::(&tx, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? } else if shell::is_json() { // to_value first to sort json object keys @@ -1158,7 +1158,7 @@ impl SimpleCast { DynSolType::Uint(n) => { if MAX { let mut max = U256::MAX; - if n < 255 { + if n < 256 { max &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1)); } Ok(max.to_string()) @@ -1874,7 +1874,7 @@ impl SimpleCast { let mut topics = vec![event.selector()]; let mut data_tokens: Vec = Vec::new(); - for (input, token) in event.inputs.iter().zip(tokens.into_iter()) { + for (input, token) in event.inputs.iter().zip(tokens) { if input.indexed { let ty = DynSolType::parse(&input.ty)?; if matches!( @@ -2336,7 +2336,7 @@ fn explorer_client( api_url: Option, explorer_url: Option, ) -> Result { - let mut builder = Client::builder().chain(chain)?; + let mut builder = Client::builder(); let deduced = chain.etherscan_urls(); diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 64ff8da391200..1fceefaa66edb 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -19,12 +19,12 @@ use foundry_cli::{ utils::{self, LoadConfig, get_provider_builder, parse_function_args}, }; use foundry_common::{ - TransactionReceiptWithRevertReason, fmt::*, get_pretty_tx_receipt_attr, + TransactionReceiptWithRevertReason, fmt::*, get_pretty_receipt_w_reason_attr, provider::RetryProviderWithSigner, shell, }; use foundry_config::{Chain, Config}; use foundry_primitives::{FoundryTransactionRequest, FoundryTypedTx}; -use foundry_wallets::{WalletOpts, WalletSigner}; +use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; use std::{fmt::Write, str::FromStr, time::Duration}; @@ -55,6 +55,10 @@ pub struct SendTxOpts { /// Ethereum options #[command(flatten)] pub eth: EthereumOpts, + + /// Browser wallet options + #[command(flatten)] + pub browser: BrowserWalletOpts, } /// Different sender kinds used by [`CastTxBuilder`]. @@ -172,7 +176,7 @@ impl> CastTxSender

{ /// Sends a transaction and waits for receipt synchronously pub async fn send_sync(&self, tx: WithOtherFields) -> Result { - let mut receipt: TransactionReceiptWithRevertReason = + let mut receipt: TransactionReceiptWithRevertReason = self.provider.send_transaction_sync(tx).await?.into(); // Allow to fail silently @@ -259,7 +263,7 @@ impl> CastTxSender

{ ) -> Result { let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - let mut receipt: TransactionReceiptWithRevertReason = + let mut receipt: TransactionReceiptWithRevertReason = match self.provider.get_transaction_receipt(tx_hash).await? { Some(r) => r, None => { @@ -287,11 +291,11 @@ impl> CastTxSender

{ /// Helper method to format transaction receipts consistently fn format_receipt( &self, - receipt: TransactionReceiptWithRevertReason, + receipt: TransactionReceiptWithRevertReason, field: Option, ) -> Result { Ok(if let Some(ref field) = field { - get_pretty_tx_receipt_attr(&receipt, field) + get_pretty_receipt_w_reason_attr(&receipt, field) .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))? } else if shell::is_json() { // to_value first to sort json object keys @@ -682,8 +686,6 @@ where self.tx.set_blob_sidecar_7594(sidecar); } - self.tx.populate_blob_hashes(); - Ok(self) } } @@ -707,17 +709,13 @@ async fn decode_execution_revert(data: &RawValue) -> Result> { } /// Creates a provider with wallet for signing transactions locally. -/// -/// If `curl_mode` is true, the provider will print equivalent curl commands to stdout -/// instead of executing RPC requests. -pub(crate) async fn signing_provider_with_curl( +pub(crate) async fn get_provider_with_wallet( tx_opts: &SendTxOpts, - curl_mode: bool, ) -> eyre::Result { let config = tx_opts.eth.load_config()?; let signer = tx_opts.eth.wallet.signer().await?; let wallet = alloy_network::EthereumWallet::from(signer); - let provider = get_provider_builder(&config, curl_mode)?.build_with_wallet(wallet)?; + let provider = get_provider_builder(&config)?.build_with_wallet(wallet)?; if let Some(interval) = tx_opts.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } diff --git a/crates/cast/tests/cli/erc20.rs b/crates/cast/tests/cli/erc20.rs index 012844fb07730..3256e6f8bc167 100644 --- a/crates/cast/tests/cli/erc20.rs +++ b/crates/cast/tests/cli/erc20.rs @@ -501,3 +501,108 @@ casttest!(erc20_curl_total_supply, |_prj, cmd| { assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); + +// tests that `balance` command works correctly with --json flag +forgetest_async!(erc20_balance_json, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "balance", &token, anvil_const::ADDR1, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + + let balance_str: String = serde_json::from_str(&output).expect("valid json string"); + let balance: U256 = balance_str.parse().unwrap(); + assert_eq!(balance, U256::from(1_000_000_000_000_000_000_000u128)); +}); + +// tests that `allowance` command works correctly with --json flag +forgetest_async!(erc20_allowance_json, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + // First approve some tokens + let approve_amount = U256::from(50_000_000_000_000_000_000u128); + cmd.cast_fuse() + .args([ + "erc20", + "approve", + &token, + anvil_const::ADDR2, + &approve_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Check allowance with JSON flag + let output = cmd + .cast_fuse() + .args([ + "--json", + "erc20", + "allowance", + &token, + anvil_const::ADDR1, + anvil_const::ADDR2, + "--rpc-url", + &rpc, + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let allowance_str: String = serde_json::from_str(&output).expect("valid json string"); + let allowance: U256 = allowance_str.parse().unwrap(); + assert_eq!(allowance, approve_amount); +}); + +// tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly with --json +// flag +forgetest_async!(erc20_metadata_json, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + // Test name with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "name", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let name: String = serde_json::from_str(&output).expect("valid json string"); + assert_eq!(name, "Test Token"); + + // Test symbol with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "symbol", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let symbol: String = serde_json::from_str(&output).expect("valid json string"); + assert_eq!(symbol, "TEST"); + + // Test decimals with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "decimals", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let decimals: u8 = output.trim().parse().expect("valid number"); + assert_eq!(decimals, 18); + + // Test totalSupply with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply_str: String = serde_json::from_str(&output).expect("valid json string"); + let total_supply: U256 = total_supply_str.parse().unwrap(); + assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 5bfaee843d41c..27c156031e0df 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2025,19 +2025,19 @@ blockNumber 22287055 from 0x4648451b5F87FF8F0F7D622bD40574bb97E25980 transactionIndex 230 effectiveGasPrice 363392048 -accessList [] +hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 +type 2 chainId 1 +nonce 113642 gasLimit 350000 -hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 -input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 maxFeePerGas 675979146 maxPriorityFeePerGas 1337 -nonce 113642 -r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c -s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a to 0xdAC17F958D2ee523a2206206994597C13D831ec7 -type 2 value 0 +accessList [] +input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 +r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c +s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a yParity 1 ... "#]]); @@ -2154,7 +2154,7 @@ casttest!(storage, |_prj, cmd| { "#]]); }); -casttest!(storage_with_valid_solc_version_1, |_prj, cmd| { +casttest!(flaky_storage_with_valid_solc_version_1, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", @@ -2168,7 +2168,7 @@ casttest!(storage_with_valid_solc_version_1, |_prj, cmd| { .assert_success(); }); -casttest!(storage_with_valid_solc_version_2, |_prj, cmd| { +casttest!(flaky_storage_with_valid_solc_version_2, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", @@ -2182,7 +2182,7 @@ casttest!(storage_with_valid_solc_version_2, |_prj, cmd| { .assert_success(); }); -casttest!(storage_with_invalid_solc_version_1, |_prj, cmd| { +casttest!(flaky_storage_with_invalid_solc_version_1, |_prj, cmd| { let output = cmd .args([ "storage", @@ -2207,7 +2207,7 @@ casttest!(storage_with_invalid_solc_version_1, |_prj, cmd| { ); }); -casttest!(storage_with_invalid_solc_version_2, |_prj, cmd| { +casttest!(flaky_storage_with_invalid_solc_version_2, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", @@ -2226,7 +2226,7 @@ Error: Encountered invalid solc version in contracts/Create2Deployer.sol: No sol }); // -casttest!(storage_layout_simple, |_prj, cmd| { +casttest!(flaky_storage_layout_simple, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2253,7 +2253,7 @@ casttest!(storage_layout_simple, |_prj, cmd| { }); // -casttest!(storage_layout_simple_json, |_prj, cmd| { +casttest!(flaky_storage_layout_simple_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2270,7 +2270,7 @@ casttest!(storage_layout_simple_json, |_prj, cmd| { }); // -casttest!(storage_layout_complex, |_prj, cmd| { +casttest!(flaky_storage_layout_complex, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2318,7 +2318,7 @@ casttest!(storage_layout_complex, |_prj, cmd| { "#]]); }); -casttest!(storage_layout_complex_md, |_prj, cmd| { +casttest!(flaky_storage_layout_complex_md, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2353,7 +2353,7 @@ casttest!(storage_layout_complex_md, |_prj, cmd| { "#]]); }); -casttest!(storage_layout_complex_proxy, |_prj, cmd| { +casttest!(flaky_storage_layout_complex_proxy, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2395,7 +2395,7 @@ casttest!(storage_layout_complex_proxy, |_prj, cmd| { "#]]); }); -casttest!(storage_layout_complex_json, |_prj, cmd| { +casttest!(flaky_storage_layout_complex_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -2486,9 +2486,53 @@ interface Interface { ]]); }); +// tests that `cast interface --flatten` inlines inherited struct types into the interface +// +casttest!(interface_flatten, |prj, cmd| { + let interface = include_str!("../fixtures/interface_inherited_struct.json"); + + let path = prj.root().join("interface_inherited_struct.json"); + fs::write(&path, interface).unwrap(); + + // Without --flatten, a separate library is generated for the struct + cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ + r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +library IBase { + struct TestStruct { + address asset; + } +} + +interface Interface { + function test(IBase.TestStruct memory param) external; +} + +"# + ]]); + + // With --flatten, the struct is inlined into the interface + cmd.cast_fuse().arg("interface").arg("--flatten").arg(&path).assert_success().stdout_eq(str![ + [r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface Interface { + // Types from `IBase` + struct TestStruct { + address asset; + } + + function test(TestStruct memory param) external; +} + +"#] + ]); +}); + // tests that fetches WETH interface from etherscan // -casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_weth_interface_from_etherscan, |_prj, cmd| { cmd.args([ "interface", "--etherscan-api-key", @@ -2873,7 +2917,7 @@ casttest!(format_units, |_prj, cmd| { // tests that fetches a sample contract creation code // -casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_creation_code_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", @@ -2892,7 +2936,7 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { // tests that fetches a sample contract creation args bytes // -casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", @@ -2912,7 +2956,7 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { // tests that displays a sample contract creation args // -casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_constructor_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "constructor-args", @@ -2930,7 +2974,7 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { }); // -casttest!(test_non_mainnet_traces, |prj, cmd| { +casttest!(flaky_test_non_mainnet_traces, |prj, cmd| { prj.clear(); cmd.args([ "run", @@ -2984,7 +3028,7 @@ Transaction successfully executed. // tests that displays a sample contract artifact // -casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "artifact", @@ -3175,7 +3219,7 @@ Transaction successfully executed. }); // tests cast can decode external libraries traces with project cached selectors -forgetest_async!(decode_external_libraries_with_cached_selectors, |prj, cmd| { +forgetest_async!(flaky_decode_external_libraries_with_cached_selectors, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); @@ -3249,9 +3293,9 @@ contract CounterInExternalLibScript is Script { ... Traces: [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - ├─ [..] 0x6fD8bf6770F4bEe578348D24028000cE9c4D2bB9::updateCounterInExternalLib(0, 100) [delegatecall] + ├─ [..] [..]::updateCounterInExternalLib(0, 100) [delegatecall] │ └─ ← [Stop] - └─ ← [Return] 62 bytes of code + └─ ← [Return] [..] bytes of code Transaction successfully executed. @@ -3631,7 +3675,7 @@ Transaction successfully executed. }); // https://github.com/foundry-rs/foundry/issues/9541 -forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { +forgetest_async!(flaky_cast_run_impersonated_tx, |_prj, cmd| { let (_api, handle) = anvil::spawn( NodeConfig::test() .with_auto_impersonate(true) @@ -3665,48 +3709,35 @@ forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { }); // -casttest!( - #[cfg_attr(all(target_os = "linux", target_arch = "aarch64"), ignore = "no 0.4 solc")] - fetch_src_blockscout, - |_prj, cmd| { - let url = "https://eth.blockscout.com/api"; +casttest!(flaky_fetch_src_blockscout, |_prj, cmd| { + let url = "https://eth.blockscout.com/api"; - let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); - cmd.args([ - "source", - &weth.to_string(), - "--chain-id", - "1", - "--explorer-api-url", - url, - "--flatten", - ]) - .assert_success() - .stdout_eq(str![[r#" + cmd.args([ + "source", + &weth.to_string(), + "--chain-id", + "1", + "--explorer-api-url", + url, + "--flatten", + ]) + .assert_success() + .stdout_eq(str![[r#" ... contract WETH9 { string public name = "Wrapped Ether"; string public symbol = "WETH"; uint8 public decimals = 18; ..."#]]); - } -); +}); -casttest!( - #[cfg_attr(all(target_os = "linux", target_arch = "aarch64"), ignore = "no 0.4 solc")] - fetch_src_default, - |_prj, cmd| { - let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); - let etherscan_api_key = next_etherscan_api_key(); +casttest!(flaky_fetch_src_default, |_prj, cmd| { + let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + let etherscan_api_key = next_etherscan_api_key(); - cmd.args([ - "source", - &weth.to_string(), - "--flatten", - "--etherscan-api-key", - ðerscan_api_key, - ]) + cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", ðerscan_api_key]) .assert_success() .stdout_eq(str![[r#" ... @@ -3715,12 +3746,11 @@ contract WETH9 { string public symbol = "WETH"; uint8 public decimals = 18; ..."#]]); - } -); +}); // // -casttest!(osaka_can_run_p256_precompile, |_prj, cmd| { +casttest!(flaky_osaka_can_run_p256_precompile, |_prj, cmd| { cmd.args([ "run", "0x17b2de59ebd7dfd2452a3638a16737b6b65ae816c1c5571631dc0d80b63c41de", @@ -3862,7 +3892,7 @@ contract SimpleStorageScript is Script { &handle.http_endpoint(), ]) .assert_failure().stderr_eq(str![[r#" -Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8": AddressInsufficientBalance(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 1000) +Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8"[..] "#]]); }); @@ -4588,19 +4618,19 @@ casttest!(abi_encode_event_dynamic_indexed, |_prj, cmd| { // Test cast run Celo transfer with precompiles. casttest!( - #[ignore = "flaky celo rpc url"] - run_celo_with_precompiles, + #[ignore = "requires debug_traceTransaction, which most free Celo RPC endpoints no longer support"] + flaky_run_celo_with_precompiles, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Celo); cmd.args([ - "run", - "0xa652b9f41bb1a617ea6b2835b3316e79f0f21b8264e7bcd20e57c4092a70a0f6", - "--quick", - "--rpc-url", - rpc.as_str(), - ]) - .assert_success() - .stdout_eq(str![[r#" + "run", + "0xa652b9f41bb1a617ea6b2835b3316e79f0f21b8264e7bcd20e57c4092a70a0f6", + "--quick", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" Traces: [17776] 0x471EcE3750Da237f93B8E339c536989b8978a438::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) ├─ [12370] 0xFeA1B35f1D5f2A58532a70e7A32e6F2D3Bc4F7B1::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) [delegatecall] @@ -4781,3 +4811,56 @@ casttest!(curl_call, |_prj, cmd| { assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that invalid hex calldata (odd length) produces a clear error message +casttest!(cast_call_invalid_hex_calldata_error, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0x0", // Invalid: odd length hex + "--rpc-url", + rpc.as_str(), + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Invalid hex calldata '0x0': odd number of digits + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that valid hex calldata works correctly +casttest!(cast_call_valid_hex_calldata, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0x00", // Valid: even length hex + "--rpc-url", + rpc.as_str(), + ]) + .assert_success(); +}); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that invalid hex with uppercase 0X prefix also produces clear error +casttest!(cast_call_invalid_hex_uppercase_prefix, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0X1", // Invalid: odd length hex with uppercase prefix + "--rpc-url", + rpc.as_str(), + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Invalid hex calldata '0X1': odd number of digits + +"#]]); +}); diff --git a/crates/cast/tests/cli/selectors.rs b/crates/cast/tests/cli/selectors.rs index 7be4f4fa61898..09fb2c95384ce 100644 --- a/crates/cast/tests/cli/selectors.rs +++ b/crates/cast/tests/cli/selectors.rs @@ -1,7 +1,7 @@ use foundry_test_utils::util::OutputExt; use std::path::Path; -casttest!(error_decode_with_openchain, |prj, cmd| { +casttest!(flaky_error_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" ValueTooHigh(uint256,uint256) @@ -11,7 +11,7 @@ ValueTooHigh(uint256,uint256) "#]]); }); -casttest!(fourbyte, |_prj, cmd| { +casttest!(flaky_fourbyte, |_prj, cmd| { cmd.args(["4byte", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) @@ -27,7 +27,7 @@ For more information, try '--help'. "#]]); }); -casttest!(fourbyte_calldata, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 @@ -36,14 +36,14 @@ casttest!(fourbyte_calldata, |_prj, cmd| { "#]]); }); -casttest!(fourbyte_calldata_only_selector, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata_only_selector, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) "#]]); }); -casttest!(fourbyte_calldata_alias, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata_alias, |_prj, cmd| { cmd.args(["4byte-decode", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 @@ -52,7 +52,7 @@ casttest!(fourbyte_calldata_alias, |_prj, cmd| { "#]]); }); -casttest!(fourbyte_event, |_prj, cmd| { +casttest!(flaky_fourbyte_event, |_prj, cmd| { cmd.args(["4byte-event", "0x7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6"]) .assert_success() .stdout_eq(str![[r#" @@ -61,7 +61,7 @@ updateAuthority(address,uint8) "#]]); }); -casttest!(fourbyte_event_2, |_prj, cmd| { +casttest!(flaky_fourbyte_event_2, |_prj, cmd| { cmd.args(["4byte-event", "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd"]) .assert_success() .stdout_eq(str![[r#" @@ -70,7 +70,7 @@ canCall(address,address,bytes4) "#]]); }); -casttest!(upload_signatures, |_prj, cmd| { +casttest!(flaky_upload_signatures, |_prj, cmd| { // test no prefix is accepted as function let output = cmd .args(["upload-signature", "transfer(address,uint256)"]) @@ -148,7 +148,7 @@ casttest!(event_decode_with_sig, |_prj, cmd| { }); // tests cast can decode event with Openchain API -casttest!(event_decode_with_openchain, |prj, cmd| { +casttest!(flaky_event_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-event", "0xe27c4c1372396a3d15a9922f74f9dfc7c72b1ad6d63868470787249c356454c1000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]).assert_success().stdout_eq(str![[r#" BaseCurrencySet(address,uint256) @@ -176,7 +176,7 @@ casttest!(error_decode_with_sig, |_prj, cmd| { }); // tests cast can decode error and event when using local sig identifiers cache -forgetest_init!(error_event_decode_with_cache, |prj, cmd| { +forgetest_init!(flaky_error_event_decode_with_cache, |prj, cmd| { prj.add_source( "LocalProjectContract", r#" @@ -212,7 +212,7 @@ MyUniqueEventWithinLocalProject(uint256,address) "#]]); }); -forgetest!(cache_selectors_from_extra_abis, |prj, cmd| { +forgetest!(flaky_cache_selectors_from_extra_abis, |prj, cmd| { // Create folder with ABI JSON files containing a unique error let abis_dir = prj.root().join("external_abis"); std::fs::create_dir(&abis_dir).unwrap(); diff --git a/crates/cast/tests/fixtures/interface_inherited_struct.json b/crates/cast/tests/fixtures/interface_inherited_struct.json new file mode 100644 index 0000000000000..b82c0829b5ac7 --- /dev/null +++ b/crates/cast/tests/fixtures/interface_inherited_struct.json @@ -0,0 +1 @@ +[{"type":"function","name":"test","inputs":[{"name":"param","type":"tuple","internalType":"struct IBase.TestStruct","components":[{"name":"asset","type":"address","internalType":"address"}]}],"outputs":[],"stateMutability":"nonpayable"}] diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 799945a663161..5b8724e559257 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -20,6 +20,7 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true +foundry-primitives.workspace = true foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true foundry-wallets.workspace = true @@ -55,6 +56,7 @@ jsonpath_lib.workspace = true k256.workspace = true memchr.workspace = true p256 = "0.13" +ed25519-consensus = "2.1" ecdsa = "0.16" rand.workspace = true revm.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 67820b2030c23..7a865cc348386 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3780,6 +3780,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "createEd25519Key", + "description": "Generates an Ed25519 key pair from a deterministic salt.\nReturns (publicKey, privateKey) as 32-byte values.", + "declaration": "function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "createEd25519Key(bytes32)", + "selector": "0x1ef3f27a", + "selectorBytes": [ + 30, + 243, + 242, + 122 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "createFork_0", @@ -5084,6 +5104,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "executeTransaction", + "description": "Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode).\nThe transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP.\nReturns the execution output bytes.\nThis cheatcode is not allowed in `forge script` contexts.", + "declaration": "function executeTransaction(bytes calldata rawTx) external returns (bytes memory);", + "visibility": "external", + "mutability": "", + "signature": "executeTransaction(bytes)", + "selector": "0x943d7209", + "selectorBytes": [ + 148, + 61, + 114, + 9 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "exists", @@ -8426,6 +8466,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "publicKeyEd25519", + "description": "Derives the Ed25519 public key from a private key.", + "declaration": "function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey);", + "visibility": "external", + "mutability": "pure", + "signature": "publicKeyEd25519(bytes32)", + "selector": "0x27f44236", + "selectorBytes": [ + 39, + 244, + 66, + 54 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "publicKeyP256", @@ -10150,6 +10210,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signEd25519", + "description": "Signs a message with namespace using Ed25519.\nThe signature covers namespace || message for domain separation.\nReturns a 64-byte Ed25519 signature.", + "declaration": "function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature);", + "visibility": "external", + "mutability": "pure", + "signature": "signEd25519(bytes,bytes,bytes32)", + "selector": "0xef609c65", + "selectorBytes": [ + 239, + 96, + 156, + 101 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "signP256", @@ -11332,6 +11412,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "verifyEd25519", + "description": "Verifies an Ed25519 signature over namespace || message.\nReturns true if signature is valid, false otherwise.", + "declaration": "function verifyEd25519(bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey) external pure returns (bool valid);", + "visibility": "external", + "mutability": "pure", + "signature": "verifyEd25519(bytes,bytes,bytes,bytes32)", + "selector": "0xd08c2888", + "selectorBytes": [ + 208, + 140, + 40, + 136 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "warmSlot", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 640c97e3108fb..c703c2fc5fd9a 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -563,6 +563,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; + /// Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode). + /// The transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP. + /// Returns the execution output bytes. + /// + /// This cheatcode is not allowed in `forge script` contexts. + #[cheatcode(group = Evm, safety = Unsafe)] + function executeTransaction(bytes calldata rawTx) external returns (bytes memory); + // -------- Account State -------- /// Sets an address' balance. @@ -2783,6 +2791,34 @@ interface Vm { #[cheatcode(group = Crypto)] function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); + /// Generates an Ed25519 key pair from a deterministic salt. + /// Returns (publicKey, privateKey) as 32-byte values. + #[cheatcode(group = Crypto, safety = Safe)] + function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey); + + /// Derives the Ed25519 public key from a private key. + #[cheatcode(group = Crypto, safety = Safe)] + function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey); + + /// Signs a message with namespace using Ed25519. + /// The signature covers namespace || message for domain separation. + /// Returns a 64-byte Ed25519 signature. + #[cheatcode(group = Crypto, safety = Safe)] + function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) + external + pure + returns (bytes memory signature); + + /// Verifies an Ed25519 signature over namespace || message. + /// Returns true if signature is valid, false otherwise. + #[cheatcode(group = Crypto, safety = Safe)] + function verifyEd25519( + bytes calldata signature, + bytes calldata namespace, + bytes calldata message, + bytes32 publicKey + ) external pure returns (bool valid); + /// Derive a private key from a provided mnemonic string (or mnemonic file path) /// at the derivation path `m/44'/60'/0'/0/{index}`. #[cheatcode(group = Crypto)] diff --git a/crates/cheatcodes/src/base64.rs b/crates/cheatcodes/src/base64.rs index 4aa4ba74a0e4b..8c03a8b63ef6e 100644 --- a/crates/cheatcodes/src/base64.rs +++ b/crates/cheatcodes/src/base64.rs @@ -2,28 +2,28 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_sol_types::SolValue; use base64::prelude::*; -impl Cheatcode for toBase64_0Call { +impl Cheatcode for toBase64_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_STANDARD.encode(data).abi_encode()) } } -impl Cheatcode for toBase64_1Call { +impl Cheatcode for toBase64_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_STANDARD.encode(data).abi_encode()) } } -impl Cheatcode for toBase64URL_0Call { +impl Cheatcode for toBase64URL_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_URL_SAFE.encode(data).abi_encode()) } } -impl Cheatcode for toBase64URL_1Call { +impl Cheatcode for toBase64URL_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_URL_SAFE.encode(data).abi_encode()) diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 924a44ef7ce32..e897ee2779203 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -21,31 +21,36 @@ use p256::ecdsa::{ Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner, }; +use ed25519_consensus::{ + Signature as Ed25519Signature, SigningKey as Ed25519SigningKey, + VerificationKey as Ed25519VerificationKey, +}; + /// The BIP32 default derivation path prefix. const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; -impl Cheatcode for createWallet_0Call { +impl Cheatcode for createWallet_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { walletLabel } = self; create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state) } } -impl Cheatcode for createWallet_1Call { +impl Cheatcode for createWallet_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; create_wallet(privateKey, None, state) } } -impl Cheatcode for createWallet_2Call { +impl Cheatcode for createWallet_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey, walletLabel } = self; create_wallet(privateKey, Some(walletLabel), state) } } -impl Cheatcode for sign_0Call { +impl Cheatcode for sign_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; @@ -53,7 +58,7 @@ impl Cheatcode for sign_0Call { } } -impl Cheatcode for signWithNonceUnsafeCall { +impl Cheatcode for signWithNonceUnsafeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let pk: U256 = self.privateKey; let digest: B256 = self.digest; @@ -63,7 +68,7 @@ impl Cheatcode for signWithNonceUnsafeCall { } } -impl Cheatcode for signCompact_0Call { +impl Cheatcode for signCompact_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; @@ -71,35 +76,35 @@ impl Cheatcode for signCompact_0Call { } } -impl Cheatcode for deriveKey_0Call { +impl Cheatcode for deriveKey_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index } = self; derive_key::(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index) } } -impl Cheatcode for deriveKey_1Call { +impl Cheatcode for deriveKey_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index } = self; derive_key::(mnemonic, derivationPath, *index) } } -impl Cheatcode for deriveKey_2Call { +impl Cheatcode for deriveKey_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index, language } = self; derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language) } } -impl Cheatcode for deriveKey_3Call { +impl Cheatcode for deriveKey_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index, language } = self; derive_key_str(mnemonic, derivationPath, *index, language) } } -impl Cheatcode for rememberKeyCall { +impl Cheatcode for rememberKeyCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = parse_wallet(privateKey)?; @@ -108,7 +113,7 @@ impl Cheatcode for rememberKeyCall { } } -impl Cheatcode for rememberKeys_0Call { +impl Cheatcode for rememberKeys_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, count } = self; let wallets = derive_wallets::(mnemonic, derivationPath, *count)?; @@ -122,7 +127,7 @@ impl Cheatcode for rememberKeys_0Call { } } -impl Cheatcode for rememberKeys_1Call { +impl Cheatcode for rememberKeys_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, language, count } = self; let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?; @@ -142,7 +147,7 @@ fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner) -> Add address } -impl Cheatcode for sign_1Call { +impl Cheatcode for sign_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; @@ -150,7 +155,7 @@ impl Cheatcode for sign_1Call { } } -impl Cheatcode for signCompact_1Call { +impl Cheatcode for signCompact_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; @@ -158,7 +163,7 @@ impl Cheatcode for signCompact_1Call { } } -impl Cheatcode for sign_2Call { +impl Cheatcode for sign_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; @@ -166,7 +171,7 @@ impl Cheatcode for sign_2Call { } } -impl Cheatcode for signCompact_2Call { +impl Cheatcode for signCompact_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; @@ -174,7 +179,7 @@ impl Cheatcode for signCompact_2Call { } } -impl Cheatcode for sign_3Call { +impl Cheatcode for sign_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; @@ -182,7 +187,7 @@ impl Cheatcode for sign_3Call { } } -impl Cheatcode for signCompact_3Call { +impl Cheatcode for signCompact_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; @@ -190,14 +195,14 @@ impl Cheatcode for signCompact_3Call { } } -impl Cheatcode for signP256Call { +impl Cheatcode for signP256Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; sign_p256(privateKey, digest) } } -impl Cheatcode for publicKeyP256Call { +impl Cheatcode for publicKeyP256Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let pub_key = @@ -209,6 +214,34 @@ impl Cheatcode for publicKeyP256Call { } } +impl Cheatcode for createEd25519KeyCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { salt } = self; + create_ed25519_key(salt) + } +} + +impl Cheatcode for publicKeyEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + public_key_ed25519(privateKey) + } +} + +impl Cheatcode for signEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { namespace, message, privateKey } = self; + sign_ed25519(namespace, message, privateKey) + } +} + +impl Cheatcode for verifyEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { signature, namespace, message, publicKey } = self; + verify_ed25519(signature, namespace, message, publicKey) + } +} + /// Using a given private key, return its public ETH address, its public key affine x and y /// coordinates, and its private key (see the 'Wallet' struct) /// @@ -344,7 +377,7 @@ fn sign_with_wallet( let mut wallets = state.wallets().inner.lock(); let maybe_provided_sender = wallets.provided_sender; - let signers = wallets.multi_wallet.signers()?; + let (signers, _) = wallets.multi_wallet.signers()?; let signer = if let Some(signer) = signer { signer @@ -399,6 +432,47 @@ fn parse_private_key_p256(private_key: &U256) -> Result { Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) } +fn parse_signing_key_ed25519(private_key: &B256) -> Result { + Ed25519SigningKey::try_from(private_key.as_slice()) + .map_err(|e| fmt_err!("invalid Ed25519 private key: {e}")) +} + +fn create_ed25519_key(salt: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(salt)?; + let public_key = B256::from_slice(signing_key.verification_key().as_ref()); + Ok((public_key, *salt).abi_encode()) +} + +fn public_key_ed25519(private_key: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(private_key)?; + Ok(B256::from_slice(signing_key.verification_key().as_ref()).abi_encode()) +} + +fn sign_ed25519(namespace: &[u8], message: &[u8], private_key: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(private_key)?; + let combined = [namespace, message].concat(); + let signature: [u8; 64] = signing_key.sign(&combined).into(); + Ok(signature.to_vec().abi_encode()) +} + +fn verify_ed25519(signature: &[u8], namespace: &[u8], message: &[u8], public_key: &B256) -> Result { + if signature.len() != 64 { + return Ok(false.abi_encode()); + } + + let Ok(verification_key) = Ed25519VerificationKey::try_from(public_key.as_slice()) else { + return Ok(false.abi_encode()); + }; + + let Ok(sig_bytes): Result<[u8; 64], _> = signature.try_into() else { + return Ok(false.abi_encode()); + }; + + let combined = [namespace, message].concat(); + let valid = verification_key.verify(&Ed25519Signature::from(sig_bytes), &combined).is_ok(); + Ok(valid.abi_encode()) +} + pub(super) fn parse_wallet(private_key: &U256) -> Result { parse_private_key(private_key).map(PrivateKeySigner::from) } @@ -596,4 +670,105 @@ mod tests { let msg = err.to_string(); assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}"); } + + #[test] + fn test_create_ed25519_key_determinism() { + let salt = B256::from([1u8; 32]); + let result1 = create_ed25519_key(&salt).unwrap(); + let result2 = create_ed25519_key(&salt).unwrap(); + assert_eq!(result1, result2, "same salt should produce same keys"); + } + + #[test] + fn test_create_ed25519_key_different_salts() { + let salt1 = B256::from([1u8; 32]); + let salt2 = B256::from([2u8; 32]); + let result1 = create_ed25519_key(&salt1).unwrap(); + let result2 = create_ed25519_key(&salt2).unwrap(); + assert_ne!(result1, result2, "different salts should produce different keys"); + } + + #[test] + fn test_public_key_ed25519_consistency() { + let salt = B256::from([42u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (expected_public, private): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let derived_public_result = public_key_ed25519(&private).unwrap(); + let derived_public = B256::abi_decode(&derived_public_result).unwrap(); + + assert_eq!(expected_public, derived_public, "derived public key should match"); + } + + #[test] + fn test_sign_and_verify_ed25519_valid() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, private_key): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let namespace = b"test.namespace"; + let message = b"hello world"; + let sig_result = sign_ed25519(namespace, message, &private_key).unwrap(); + let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); + + let verify_result = verify_ed25519(&sig_bytes, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + + assert!(valid, "signature should be valid"); + } + + #[test] + fn test_verify_ed25519_invalid_signature() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let invalid_sig = [0u8; 64]; + let namespace = b"test.namespace"; + let message = b"hello world"; + + let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + + assert!(!valid, "invalid signature should not verify"); + } + + #[test] + fn test_verify_ed25519_namespace_separation() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, private_key): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let namespace_a = b"namespace.a"; + let message = b"message"; + let sig_result = sign_ed25519(namespace_a, message, &private_key).unwrap(); + let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); + + let namespace_b = b"namespace.b"; + let verify_result = verify_ed25519(&sig_bytes, namespace_b, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(!valid, "signature with namespace A should not verify with namespace B"); + + let verify_result = verify_ed25519(&sig_bytes, namespace_a, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(valid, "signature should verify with correct namespace"); + } + + #[test] + fn test_verify_ed25519_invalid_signature_length() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let invalid_sig = [0u8; 32]; + let namespace = b"test"; + let message = b"message"; + + let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(!valid, "signature with wrong length should not verify"); + } } diff --git a/crates/cheatcodes/src/env.rs b/crates/cheatcodes/src/env.rs index 63f9bd00ade3a..b4f623a741e2d 100644 --- a/crates/cheatcodes/src/env.rs +++ b/crates/cheatcodes/src/env.rs @@ -8,7 +8,7 @@ use std::{env, sync::OnceLock}; /// Stores the forge execution context for the duration of the program. pub static FORGE_CONTEXT: OnceLock = OnceLock::new(); -impl Cheatcode for setEnvCall { +impl Cheatcode for setEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name: key, value } = self; if key.is_empty() { @@ -28,7 +28,7 @@ impl Cheatcode for setEnvCall { } } -impl Cheatcode for resolveEnvCall { +impl Cheatcode for resolveEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; let resolved = foundry_config::resolve::interpolate(input) @@ -37,105 +37,105 @@ impl Cheatcode for resolveEnvCall { } } -impl Cheatcode for envExistsCall { +impl Cheatcode for envExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(env::var(name).is_ok().abi_encode()) } } -impl Cheatcode for envBool_0Call { +impl Cheatcode for envBool_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bool) } } -impl Cheatcode for envUint_0Call { +impl Cheatcode for envUint_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Uint(256)) } } -impl Cheatcode for envInt_0Call { +impl Cheatcode for envInt_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Int(256)) } } -impl Cheatcode for envAddress_0Call { +impl Cheatcode for envAddress_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Address) } } -impl Cheatcode for envBytes32_0Call { +impl Cheatcode for envBytes32_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::FixedBytes(32)) } } -impl Cheatcode for envString_0Call { +impl Cheatcode for envString_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::String) } } -impl Cheatcode for envBytes_0Call { +impl Cheatcode for envBytes_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bytes) } } -impl Cheatcode for envBool_1Call { +impl Cheatcode for envBool_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bool) } } -impl Cheatcode for envUint_1Call { +impl Cheatcode for envUint_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Uint(256)) } } -impl Cheatcode for envInt_1Call { +impl Cheatcode for envInt_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Int(256)) } } -impl Cheatcode for envAddress_1Call { +impl Cheatcode for envAddress_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Address) } } -impl Cheatcode for envBytes32_1Call { +impl Cheatcode for envBytes32_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::FixedBytes(32)) } } -impl Cheatcode for envString_1Call { +impl Cheatcode for envString_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::String) } } -impl Cheatcode for envBytes_1Call { +impl Cheatcode for envBytes_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bytes) @@ -143,7 +143,7 @@ impl Cheatcode for envBytes_1Call { } // bool -impl Cheatcode for envOr_0Call { +impl Cheatcode for envOr_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bool) @@ -151,7 +151,7 @@ impl Cheatcode for envOr_0Call { } // uint256 -impl Cheatcode for envOr_1Call { +impl Cheatcode for envOr_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Uint(256)) @@ -159,7 +159,7 @@ impl Cheatcode for envOr_1Call { } // int256 -impl Cheatcode for envOr_2Call { +impl Cheatcode for envOr_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Int(256)) @@ -167,7 +167,7 @@ impl Cheatcode for envOr_2Call { } // address -impl Cheatcode for envOr_3Call { +impl Cheatcode for envOr_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Address) @@ -175,7 +175,7 @@ impl Cheatcode for envOr_3Call { } // bytes32 -impl Cheatcode for envOr_4Call { +impl Cheatcode for envOr_4Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::FixedBytes(32)) @@ -183,7 +183,7 @@ impl Cheatcode for envOr_4Call { } // string -impl Cheatcode for envOr_5Call { +impl Cheatcode for envOr_5Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::String) @@ -191,7 +191,7 @@ impl Cheatcode for envOr_5Call { } // bytes -impl Cheatcode for envOr_6Call { +impl Cheatcode for envOr_6Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bytes) @@ -199,7 +199,7 @@ impl Cheatcode for envOr_6Call { } // bool[] -impl Cheatcode for envOr_7Call { +impl Cheatcode for envOr_7Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Bool) @@ -207,7 +207,7 @@ impl Cheatcode for envOr_7Call { } // uint256[] -impl Cheatcode for envOr_8Call { +impl Cheatcode for envOr_8Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Uint(256)) @@ -215,7 +215,7 @@ impl Cheatcode for envOr_8Call { } // int256[] -impl Cheatcode for envOr_9Call { +impl Cheatcode for envOr_9Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Int(256)) @@ -223,7 +223,7 @@ impl Cheatcode for envOr_9Call { } // address[] -impl Cheatcode for envOr_10Call { +impl Cheatcode for envOr_10Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Address) @@ -231,7 +231,7 @@ impl Cheatcode for envOr_10Call { } // bytes32[] -impl Cheatcode for envOr_11Call { +impl Cheatcode for envOr_11Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::FixedBytes(32)) @@ -239,7 +239,7 @@ impl Cheatcode for envOr_11Call { } // string[] -impl Cheatcode for envOr_12Call { +impl Cheatcode for envOr_12Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::String) @@ -247,7 +247,7 @@ impl Cheatcode for envOr_12Call { } // bytes[] -impl Cheatcode for envOr_13Call { +impl Cheatcode for envOr_13Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; let default = defaultValue.to_vec(); @@ -255,7 +255,7 @@ impl Cheatcode for envOr_13Call { } } -impl Cheatcode for isContextCall { +impl Cheatcode for isContextCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { context } = self; Ok((FORGE_CONTEXT.get() == Some(context)).abi_encode()) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index b5e76e1148694..d82998d58f543 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -2,10 +2,10 @@ use crate::{ BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result, - Vm::*, - inspector::{Ecx, RecordDebugStepInfo}, + Vm::*, inspector::RecordDebugStepInfo, }; use alloy_consensus::TxEnvelope; +use alloy_evm::FromRecoveredTx; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_network::eip2718::EIP4844_TX_TYPE_ID; use alloy_primitives::{ @@ -23,19 +23,22 @@ use foundry_common::{ }; use foundry_compilers::artifacts::EvmVersion; use foundry_evm_core::{ - ContextExt, - backend::{DatabaseExt, RevertStateSnapshotAction}, + backend::{DatabaseExt, FoundryJournalExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, + env::FoundryContextExt, + evm::NestedEvmExt, utils::get_blob_base_fee_update_fraction_by_spec_id, }; use foundry_evm_traces::TraceMode; +use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; use rand::Rng; use revm::{ bytecode::Bytecode, - context::{Block, JournalTr}, + context::{Block, Cfg, ContextTr, JournalTr, Transaction, TxEnv, result::ExecutionResult}, + inspector::JournalExt, primitives::{KECCAK_EMPTY, hardfork::SpecId}, - state::Account, + state::{Account, AccountStatus}, }; use std::{ collections::{BTreeMap, HashSet, btree_map::Entry}, @@ -241,7 +244,7 @@ impl Display for AccountStateDiffs { } } -impl Cheatcode for addrCall { +impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = super::crypto::parse_wallet(privateKey)?; @@ -249,29 +252,30 @@ impl Cheatcode for addrCall { } } -impl Cheatcode for getNonce_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getNonce_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; get_nonce(ccx, account) } } -impl Cheatcode for getNonce_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getNonce_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { wallet } = self; get_nonce(ccx, &wallet.addr) } } -impl Cheatcode for loadCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for loadCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, slot } = *self; ccx.ensure_not_precompile(&target)?; - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - journal.load_account(db, target)?; - let mut val = journal - .sload(db, target, slot.into(), false) + ccx.ecx.journal_mut().load_account(target)?; + let mut val = ccx + .ecx + .journal_mut() + .sload(target, slot.into()) .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?; if val.is_cold && val.data.is_zero() { @@ -304,8 +308,8 @@ impl Cheatcode for loadCall { } } -impl Cheatcode for loadAllocsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for loadAllocsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); @@ -322,29 +326,31 @@ impl Cheatcode for loadAllocsCall { }; // Then, load the allocs into the database. - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - db.load_allocs(&allocs, journal) + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.load_allocs(&allocs, inner) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } } -impl Cheatcode for cloneAccountCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for cloneAccountCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { source, target } = self; - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - let account = journal.load_account(db, *source)?; - let genesis = &genesis_account(account.data); - db.clone_account(genesis, target, journal)?; + let account = ccx.ecx.journal_mut().load_account(*source)?; + let genesis = genesis_account(account.data); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.clone_account(&genesis, target, inner)?; // Cloned account should persist in forked envs. - ccx.ecx.journaled_state.database.add_persistent_account(*target); + ccx.ecx.db_mut().add_persistent_account(*target); Ok(Default::default()) } } -impl Cheatcode for dumpStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for dumpStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); @@ -361,8 +367,8 @@ impl Cheatcode for dumpStateCall { let alloc = ccx .ecx - .journaled_state - .state() + .journal_mut() + .evm_state_mut() .iter_mut() .filter(|(key, val)| !skip(key, val)) .map(|(key, val)| (key, genesis_account(val))) @@ -373,7 +379,7 @@ impl Cheatcode for dumpStateCall { } } -impl Cheatcode for recordCall { +impl Cheatcode for recordCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recording_accesses = true; @@ -382,14 +388,14 @@ impl Cheatcode for recordCall { } } -impl Cheatcode for stopRecordCall { +impl Cheatcode for stopRecordCall { fn apply(&self, state: &mut Cheatcodes) -> Result { state.recording_accesses = false; Ok(Default::default()) } } -impl Cheatcode for accessesCall { +impl Cheatcode for accessesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target } = *self; let result = ( @@ -400,7 +406,7 @@ impl Cheatcode for accessesCall { } } -impl Cheatcode for recordLogsCall { +impl Cheatcode for recordLogsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_logs = Some(Default::default()); @@ -408,14 +414,14 @@ impl Cheatcode for recordLogsCall { } } -impl Cheatcode for getRecordedLogsCall { +impl Cheatcode for getRecordedLogsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode()) } } -impl Cheatcode for getRecordedLogsJsonCall { +impl Cheatcode for getRecordedLogsJsonCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default(); @@ -431,7 +437,7 @@ impl Cheatcode for getRecordedLogsJsonCall { } } -impl Cheatcode for pauseGasMeteringCall { +impl Cheatcode for pauseGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.paused = true; @@ -439,7 +445,7 @@ impl Cheatcode for pauseGasMeteringCall { } } -impl Cheatcode for resumeGasMeteringCall { +impl Cheatcode for resumeGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.resume(); @@ -447,7 +453,7 @@ impl Cheatcode for resumeGasMeteringCall { } } -impl Cheatcode for resetGasMeteringCall { +impl Cheatcode for resetGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.reset(); @@ -455,7 +461,7 @@ impl Cheatcode for resetGasMeteringCall { } } -impl Cheatcode for lastCallGasCall { +impl Cheatcode for lastCallGasCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let Some(last_call_gas) = &state.gas_metering.last_call_gas else { @@ -465,170 +471,171 @@ impl Cheatcode for lastCallGasCall { } } -impl Cheatcode for getChainIdCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getChainIdCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - Ok(U256::from(ccx.ecx.cfg.chain_id).abi_encode()) + Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode()) } } -impl Cheatcode for chainIdCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for chainIdCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64"); - ccx.ecx.cfg.chain_id = newChainId.to(); + ccx.ecx.cfg_mut().chain_id = newChainId.to(); Ok(Default::default()) } } -impl Cheatcode for coinbaseCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for coinbaseCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newCoinbase } = self; - ccx.ecx.block.beneficiary = *newCoinbase; + ccx.ecx.block_mut().beneficiary = *newCoinbase; Ok(Default::default()) } } -impl Cheatcode for difficultyCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for difficultyCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.ecx.cfg.spec < SpecId::MERGE, + ccx.ecx.cfg().spec().into() < SpecId::MERGE, "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.difficulty = *newDifficulty; + ccx.ecx.block_mut().difficulty = *newDifficulty; Ok(Default::default()) } } -impl Cheatcode for feeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for feeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newBasefee } = self; ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64"); - ccx.ecx.block.basefee = newBasefee.saturating_to(); + ccx.ecx.block_mut().basefee = newBasefee.saturating_to(); Ok(Default::default()) } } -impl Cheatcode for prevrandao_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for prevrandao_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::MERGE, + ccx.ecx.cfg().spec().into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.prevrandao = Some(*newPrevrandao); + ccx.ecx.block_mut().prevrandao = Some(*newPrevrandao); Ok(Default::default()) } } -impl Cheatcode for prevrandao_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for prevrandao_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::MERGE, + ccx.ecx.cfg().spec().into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.prevrandao = Some((*newPrevrandao).into()); + ccx.ecx.block_mut().prevrandao = Some((*newPrevrandao).into()); Ok(Default::default()) } } -impl Cheatcode for blobhashesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for blobhashesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { hashes } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`blobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - ccx.ecx.tx.blob_hashes.clone_from(hashes); + ccx.ecx.tx_mut().blob_hashes.clone_from(hashes); // force this as 4844 txtype - ccx.ecx.tx.tx_type = EIP4844_TX_TYPE_ID; + ccx.ecx.tx_mut().tx_type = EIP4844_TX_TYPE_ID; Ok(Default::default()) } } -impl Cheatcode for getBlobhashesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getBlobhashesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`getBlobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode()) + Ok(ccx.ecx.tx().blob_versioned_hashes().to_vec().abi_encode()) } } -impl Cheatcode for rollCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for rollCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newHeight } = self; - ccx.ecx.block.number = *newHeight; + ccx.ecx.block_mut().number = *newHeight; Ok(Default::default()) } } -impl Cheatcode for getBlockNumberCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getBlockNumberCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.number.abi_encode()) + Ok(ccx.ecx.block().number().abi_encode()) } } -impl Cheatcode for txGasPriceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for txGasPriceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newGasPrice } = self; ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64"); - ccx.ecx.tx.gas_price = newGasPrice.saturating_to(); + ccx.ecx.tx_mut().gas_price = newGasPrice.saturating_to(); Ok(Default::default()) } } -impl Cheatcode for warpCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for warpCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newTimestamp } = self; - ccx.ecx.block.timestamp = *newTimestamp; + ccx.ecx.block_mut().timestamp = *newTimestamp; Ok(Default::default()) } } -impl Cheatcode for getBlockTimestampCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getBlockTimestampCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.timestamp.abi_encode()) + Ok(ccx.ecx.block().timestamp().abi_encode()) } } -impl Cheatcode for blobBaseFeeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for blobBaseFeeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newBlobBaseFee } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`blobBaseFee` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - ccx.ecx.block.set_blob_excess_gas_and_price( + let spec: SpecId = ccx.ecx.cfg().spec().into(); + ccx.ecx.block_mut().set_blob_excess_gas_and_price( (*newBlobBaseFee).to(), - get_blob_base_fee_update_fraction_by_spec_id(ccx.ecx.cfg.spec), + get_blob_base_fee_update_fraction_by_spec_id(spec), ); Ok(Default::default()) } } -impl Cheatcode for getBlobBaseFeeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getBlobBaseFeeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode()) + Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode()) } } -impl Cheatcode for dealCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for dealCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account: address, newBalance: new_balance } = *self; let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); @@ -638,21 +645,20 @@ impl Cheatcode for dealCall { } } -impl Cheatcode for etchCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for etchCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, newRuntimeBytecode } = self; ccx.ensure_not_precompile(target)?; - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - journal.load_account(db, *target)?; + ccx.ecx.journal_mut().load_account(*target)?; let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone()) .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?; - journal.set_code(*target, bytecode); + ccx.ecx.journal_mut().set_code(*target, bytecode); Ok(Default::default()) } } -impl Cheatcode for resetNonceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for resetNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces @@ -666,8 +672,8 @@ impl Cheatcode for resetNonceCall { } } -impl Cheatcode for setNonceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for setNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; // nonce must increment only @@ -682,8 +688,8 @@ impl Cheatcode for setNonceCall { } } -impl Cheatcode for setNonceUnsafeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for setNonceUnsafeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; @@ -691,23 +697,23 @@ impl Cheatcode for setNonceUnsafeCall { } } -impl Cheatcode for storeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for storeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, slot, value } = *self; ccx.ensure_not_precompile(&target)?; ensure_loaded_account(ccx.ecx, target)?; - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - journal - .sstore(db, target, slot.into(), value.into(), false) + ccx.ecx + .journal_mut() + .sstore(target, slot.into(), value.into()) .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?; Ok(Default::default()) } } -impl Cheatcode for coolCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for coolCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target } = self; - if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { + if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) { account.unmark_touch(); account.storage.values_mut().for_each(|slot| slot.mark_cold()); } @@ -715,7 +721,7 @@ impl Cheatcode for coolCall { } } -impl Cheatcode for accessListCall { +impl Cheatcode for accessListCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { access } = self; let access_list = access @@ -730,7 +736,7 @@ impl Cheatcode for accessListCall { } } -impl Cheatcode for noAccessListCall { +impl Cheatcode for noAccessListCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; // Set to empty option in order to override previous applied access list. @@ -741,45 +747,45 @@ impl Cheatcode for noAccessListCall { } } -impl Cheatcode for warmSlotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for warmSlotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), false); Ok(Default::default()) } } -impl Cheatcode for coolSlotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for coolSlotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), true); Ok(Default::default()) } } -impl Cheatcode for readCallersCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for readCallersCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth()) + read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth()) } } -impl Cheatcode for snapshotValue_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for snapshotValue_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name, value } = self; inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) } } -impl Cheatcode for snapshotValue_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name, value } = self; inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } -impl Cheatcode for snapshotGasLastCall_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for snapshotGasLastCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); @@ -788,8 +794,8 @@ impl Cheatcode for snapshotGasLastCall_0Call { } } -impl Cheatcode for snapshotGasLastCall_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for snapshotGasLastCall_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name, group } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); @@ -803,117 +809,129 @@ impl Cheatcode for snapshotGasLastCall_1Call { } } -impl Cheatcode for startSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for startSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; inner_start_gas_snapshot(ccx, None, Some(name.clone())) } } -impl Cheatcode for startSnapshotGas_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name } = self; inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } -impl Cheatcode for stopSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_stop_gas_snapshot(ccx, None, None) } } -impl Cheatcode for stopSnapshotGas_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; inner_stop_gas_snapshot(ccx, None, Some(name.clone())) } } -impl Cheatcode for stopSnapshotGas_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopSnapshotGas_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name } = self; inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } // Deprecated in favor of `snapshotStateCall` -impl Cheatcode for snapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for snapshotCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } -impl Cheatcode for snapshotStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for snapshotStateCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } // Deprecated in favor of `revertToStateCall` -impl Cheatcode for revertToCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for revertToCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } -impl Cheatcode for revertToStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for revertToStateCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } // Deprecated in favor of `revertToStateAndDeleteCall` -impl Cheatcode for revertToAndDeleteCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for revertToAndDeleteCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } -impl Cheatcode for revertToStateAndDeleteCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for revertToStateAndDeleteCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } // Deprecated in favor of `deleteStateSnapshotCall` -impl Cheatcode for deleteSnapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for deleteSnapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } } -impl Cheatcode for deleteStateSnapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for deleteStateSnapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } } // Deprecated in favor of `deleteStateSnapshotsCall` -impl Cheatcode for deleteSnapshotsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for deleteSnapshotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } } -impl Cheatcode for deleteStateSnapshotsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for deleteStateSnapshotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } } -impl Cheatcode for startStateDiffRecordingCall { +impl Cheatcode for startStateDiffRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_account_diffs_stack = Some(Default::default()); @@ -923,15 +941,15 @@ impl Cheatcode for startStateDiffRecordingCall { } } -impl Cheatcode for stopAndReturnStateDiffCall { +impl Cheatcode for stopAndReturnStateDiffCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; get_state_diff(state) } } -impl Cheatcode for getStateDiffCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getStateDiffCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let mut diffs = String::new(); let state_diffs = get_recorded_state_diffs(ccx); for (address, state_diffs) in state_diffs { @@ -942,15 +960,15 @@ impl Cheatcode for getStateDiffCall { } } -impl Cheatcode for getStateDiffJsonCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getStateDiffJsonCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let state_diffs = get_recorded_state_diffs(ccx); Ok(serde_json::to_string(&state_diffs)?.abi_encode()) } } -impl Cheatcode for getStorageSlotsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getStorageSlotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, variableName } = self; let storage_layout = get_contract_data(ccx, *target) @@ -959,10 +977,11 @@ impl Cheatcode for getStorageSlotsCall { trace!(storage = ?storage_layout.storage, "fetched storage"); + let variable_name_lower = variableName.to_lowercase(); let storage = storage_layout .storage .iter() - .find(|s| s.label.to_lowercase() == *variableName.to_lowercase()) + .find(|s| s.label.to_lowercase() == variable_name_lower) .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?; let storage_type = storage_layout @@ -1005,8 +1024,7 @@ impl Cheatcode for getStorageSlotsCall { if storage_type.encoding == ENCODING_BYTES { // Try to check if it's a long bytes/string by reading the current storage // value - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - if let Ok(value) = journal.sload(db, *target, slot, false) { + if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) { let value_bytes = value.data.to_be_bytes::<32>(); let length_byte = value_bytes[31]; // Check if it's a long bytes/string (LSB is 1) @@ -1027,7 +1045,7 @@ impl Cheatcode for getStorageSlotsCall { } } -impl Cheatcode for getStorageAccessesCall { +impl Cheatcode for getStorageAccessesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let mut storage_accesses = Vec::new(); @@ -1041,22 +1059,26 @@ impl Cheatcode for getStorageAccessesCall { } } -impl Cheatcode for broadcastRawTransactionCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for broadcastRawTransactionCall +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let tx = TxEnvelope::decode(&mut self.data.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; - let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); - db.transact_from_tx( - &tx.clone().into(), - env.to_owned(), - journal, - &mut *executor.get_inspector(ccx.state), - )?; + let env = ccx.ecx.to_env(); + let mut inspector = executor.get_inspector(ccx.state); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.transact_from_tx(&tx.clone().into(), env, inner, &mut *inspector)?; + drop(inspector); if ccx.state.broadcast.is_some() { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ccx.ecx.journaled_state.database.active_fork_url(), + rpc: ccx.ecx.db().active_fork_url(), transaction: tx.try_into()?, }); } @@ -1065,23 +1087,174 @@ impl Cheatcode for broadcastRawTransactionCall { } } -impl Cheatcode for setBlockhashCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for setBlockhashCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber, blockHash } = *self; ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64"); ensure!( - blockNumber <= U256::from(ccx.ecx.block.number), + blockNumber <= U256::from(ccx.ecx.block().number()), "block number must be less than or equal to the current block number" ); - ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash); + ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash); Ok(Default::default()) } } -impl Cheatcode for startDebugTraceRecordingCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for executeTransactionCall +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + use crate::env::FORGE_CONTEXT; + + // Block in script contexts. + if let Some(ctx) = FORGE_CONTEXT.get() + && *ctx == ForgeContext::ScriptGroup + { + return Err(fmt_err!("executeTransaction is not allowed in forge script")); + } + + // Decode the RLP-encoded signed transaction. + let tx = FoundryTxEnvelope::decode(&mut self.rawTx.as_ref()) + .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; + + // Reject unsupported transaction types. + // TODO: add support for OP deposit transactions. + if matches!(tx, FoundryTxEnvelope::Deposit(_)) { + return Err(fmt_err!( + "OP deposit transactions are not yet supported by executeTransaction" + )); + } + // TODO: add support for Tempo AA transactions. + if matches!(tx, FoundryTxEnvelope::Tempo(_)) { + return Err(fmt_err!("Tempo transactions are not yet supported by executeTransaction")); + } + + // Recover signer from the transaction signature. + let sender = tx.recover().map_err(|err| fmt_err!("failed to recover signer: {err}"))?; + + // Build TxEnv from the recovered transaction. + let tx_env = >::from_recovered_tx(&tx, sender); + + // Save current env for restoration after execution. + let cached_env = ccx.ecx.to_env(); + + // Override env for isolated execution. + ccx.ecx.block_mut().basefee = 0; + *ccx.ecx.tx_mut() = tx_env; + ccx.ecx.tx_mut().gas_price = 0; + ccx.ecx.tx_mut().gas_priority_fee = None; + + // Enable nonce checks for realistic simulation. + ccx.ecx.cfg_mut().disable_nonce_check = false; + + // EIP-3860: enforce initcode size limit. + ccx.ecx.cfg_mut().limit_contract_initcode_size = + Some(revm::primitives::eip3860::MAX_INITCODE_SIZE); + + // Snapshot the modified env for EVM construction. + let modified_env = ccx.ecx.to_env(); + + // Mark as inner context so isolation mode doesn't trigger a nested transact_inner + // when the inner EVM executes calls at depth == 1. + executor.set_in_inner_context(true, Some(sender)); + + let res = { + let mut inspector = executor.get_inspector(ccx.state); + + let res = { + let (db, journal) = ccx.ecx.journal_mut().as_db_and_inner(); + + // Create a new EVM instance with the inspector. + let mut evm = CTX::new_nested_evm(db, modified_env.clone(), &mut *inspector); + + // Clone journaled state and mark all accounts/slots cold. + evm.journal_inner_mut().state = { + let mut state = journal.state.clone(); + for (addr, acc_mut) in &mut state { + if journal.warm_addresses.is_cold(addr) { + acc_mut.mark_cold(); + } + for slot_mut in acc_mut.storage.values_mut() { + slot_mut.is_cold = true; + slot_mut.original_value = slot_mut.present_value; + } + } + state + }; + + // Set depth to 1 for proper trace collection. + evm.journal_inner_mut().depth = 1; + + evm.transact(modified_env.tx) + }; + + // Inspector must be dropped before we can call set_in_inner_context again. + drop(inspector); + res + }; + + // Restore the original environment. + ccx.ecx.apply_env(cached_env); + + // Reset inner context flag. + executor.set_in_inner_context(false, None); + + let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?; + + // Merge state changes back into the parent journaled state. + for (addr, mut acc) in res.state { + let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else { + ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc); + continue; + }; + + // Preserve warm account status from parent context. + if acc.status.contains(AccountStatus::Cold) + && !acc_mut.status.contains(AccountStatus::Cold) + { + acc.status -= AccountStatus::Cold; + } + acc_mut.info = acc.info; + acc_mut.status |= acc.status; + + // Merge storage changes. + for (key, val) in acc.storage { + let Some(slot_mut) = acc_mut.storage.get_mut(&key) else { + acc_mut.storage.insert(key, val); + continue; + }; + slot_mut.present_value = val.present_value; + slot_mut.is_cold &= val.is_cold; + } + } + + // Return output bytes. + let output = match res.result { + ExecutionResult::Success { output, .. } => output.into_data(), + ExecutionResult::Halt { reason, .. } => { + return Err(fmt_err!("transaction halted: {reason:?}")); + } + ExecutionResult::Revert { output, .. } => { + return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output))); + } + }; + + Ok(output.abi_encode()) + } +} + +impl Cheatcode for startDebugTraceRecordingCall { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Some(tracer) = executor.tracing_inspector() else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; @@ -1106,8 +1279,12 @@ impl Cheatcode for startDebugTraceRecordingCall { } } -impl Cheatcode for stopAndReturnDebugTraceRecordingCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl Cheatcode for stopAndReturnDebugTraceRecordingCall { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Some(tracer) = executor.tracing_inspector() else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; @@ -1141,8 +1318,8 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { } } -impl Cheatcode for setEvmVersionCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for setEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { evm } = self; let spec_id = evm_spec_id( EvmVersion::from_str(evm) @@ -1153,64 +1330,80 @@ impl Cheatcode for setEvmVersionCall { } } -impl Cheatcode for getEvmVersionCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - Ok(ccx.ecx.cfg.spec.to_string().to_lowercase().abi_encode()) +impl Cheatcode for getEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + let spec: SpecId = ccx.ecx.cfg().spec().into(); + Ok(spec.to_string().to_lowercase().abi_encode()) } } -pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - let account = journal.load_account(db, *address)?; - Ok(account.info.nonce.abi_encode()) +pub(super) fn get_nonce>( + ccx: &mut CheatsCtxt<'_, CTX>, + address: &Address, +) -> Result { + let account = ccx.ecx.journal_mut().load_account(*address)?; + Ok(account.data.info.nonce.abi_encode()) } -fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - Ok(db.snapshot_state(journal, &mut env).abi_encode()) +fn inner_snapshot_state>( + ccx: &mut CheatsCtxt<'_, CTX>, +) -> Result { + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + Ok(db.snapshot_state(inner, &mut env).abi_encode()) } -fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let result = if let Some(journaled_state) = - db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep) +fn inner_revert_to_state>( + ccx: &mut CheatsCtxt<'_, CTX>, + snapshot_id: U256, +) -> Result { + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + if let Some(restored) = + db.revert_state(snapshot_id, inner, &mut env, RevertStateSnapshotAction::RevertKeep) { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state.inner = journaled_state; - true + *inner = restored; + Ok(true.abi_encode()) } else { - false - }; - Ok(result.abi_encode()) + Ok(false.abi_encode()) + } } -fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - - let result = if let Some(journaled_state) = - db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove) +fn inner_revert_to_state_and_delete< + CTX: FoundryContextExt + ContextTr, +>( + ccx: &mut CheatsCtxt<'_, CTX>, + snapshot_id: U256, +) -> Result { + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + if let Some(restored) = + db.revert_state(snapshot_id, inner, &mut env, RevertStateSnapshotAction::RevertRemove) { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state.inner = journaled_state; - true + *inner = restored; + Ok(true.abi_encode()) } else { - false - }; - Ok(result.abi_encode()) + Ok(false.abi_encode()) + } } -fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id); +fn inner_delete_state_snapshot>( + ccx: &mut CheatsCtxt<'_, CTX>, + snapshot_id: U256, +) -> Result { + let result = ccx.ecx.db_mut().delete_state_snapshot(snapshot_id); Ok(result.abi_encode()) } -fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { - ccx.ecx.journaled_state.database.delete_state_snapshots(); +fn inner_delete_state_snapshots>( + ccx: &mut CheatsCtxt<'_, CTX>, +) -> Result { + ccx.ecx.db_mut().delete_state_snapshots(); Ok(Default::default()) } -fn inner_value_snapshot( - ccx: &mut CheatsCtxt, +fn inner_value_snapshot( + ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, value: String, @@ -1222,8 +1415,8 @@ fn inner_value_snapshot( Ok(Default::default()) } -fn inner_last_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_last_gas_snapshot( + ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, value: u64, @@ -1235,8 +1428,8 @@ fn inner_last_gas_snapshot( Ok(value.abi_encode()) } -fn inner_start_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_start_gas_snapshot( + ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> Result { @@ -1251,7 +1444,7 @@ fn inner_start_gas_snapshot( group: group.clone(), name: name.clone(), gas_used: 0, - depth: ccx.ecx.journaled_state.depth(), + depth: ccx.ecx.journal().depth(), }); ccx.state.gas_metering.active_gas_snapshot = Some((group, name)); @@ -1261,8 +1454,8 @@ fn inner_start_gas_snapshot( Ok(Default::default()) } -fn inner_stop_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_stop_gas_snapshot( + ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> Result { @@ -1312,8 +1505,8 @@ fn inner_stop_gas_snapshot( } // Derives the snapshot group and name from the provided group and name or the running contract. -fn derive_snapshot_name( - ccx: &CheatsCtxt, +fn derive_snapshot_name( + ccx: &CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> (String, String) { @@ -1371,18 +1564,20 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<'a>( - ecx: Ecx<'a, '_, '_>, +pub(super) fn journaled_account>( + ecx: &mut CTX, addr: Address, -) -> Result<&'a mut Account> { +) -> Result<&mut Account> { ensure_loaded_account(ecx, addr)?; - Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) + Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded")) } -pub(super) fn ensure_loaded_account(ecx: Ecx, addr: Address) -> Result<()> { - let (db, journal, _) = ecx.as_db_env_and_journal(); - journal.load_account(db, addr)?; - journal.touch(addr); +pub(super) fn ensure_loaded_account>( + ecx: &mut CTX, + addr: Address, +) -> Result<()> { + ecx.journal_mut().load_account(addr)?; + ecx.journal_mut().touch_account(addr); Ok(()) } @@ -1422,7 +1617,9 @@ fn genesis_account(account: &Account) -> GenesisAccount { } /// Helper function to returns state diffs recorded for each changed account. -fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap { +fn get_recorded_state_diffs>( + ccx: &mut CheatsCtxt<'_, CTX>, +) -> BTreeMap { let mut state_diffs: BTreeMap = BTreeMap::default(); // First, collect all unique addresses we need to look up @@ -1599,17 +1796,16 @@ const EIP1822_PROXIABLE_SLOT: &str = "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"; /// Helper function to get the contract data from the deployed code at an address. -fn get_contract_data<'a>( - ccx: &'a mut CheatsCtxt, +fn get_contract_data<'a, CTX: ContextTr>( + ccx: &'a mut CheatsCtxt<'_, CTX>, address: Address, ) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> { // Check if we have available artifacts to match against let artifacts = ccx.state.config.available_artifacts.as_ref()?; // Try to load the account and get its code - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - let account = journal.load_account(db, address).ok()?; - let code = account.info.code.as_ref()?; + let account = ccx.ecx.journal_mut().load_account(address).ok()?; + let code = account.data.info.code.as_ref()?; // Skip if code is empty if code.is_empty() { @@ -1643,8 +1839,13 @@ fn get_contract_data<'a>( } /// Helper function to set / unset cold storage slot of the target address. -fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) { - if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target) +fn set_cold_slot>( + ccx: &mut CheatsCtxt<'_, CTX>, + target: Address, + slot: U256, + cold: bool, +) { + if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target) && let Some(storage_slot) = account.storage.get_mut(&slot) { storage_slot.is_cold = cold; diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index ea3a003a7ffc4..b631a9a2a6f3e 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -3,216 +3,246 @@ use crate::{ json::json_value_to_token, }; use alloy_dyn_abi::DynSolValue; +use alloy_network::AnyNetwork; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; use foundry_common::provider::ProviderBuilder; -use foundry_evm_core::{AsEnvMut, ContextExt, fork::CreateFork}; +use foundry_evm_core::{FoundryContextExt, backend::FoundryJournalExt, fork::CreateFork}; +use revm::context::ContextTr; -impl Cheatcode for activeForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for activeForkCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; ccx.ecx - .journaled_state - .database + .db() .active_fork_id() .map(|id| id.abi_encode()) .ok_or_else(|| fmt_err!("no active fork")) } } -impl Cheatcode for createFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for createFork_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } -impl Cheatcode for createFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for createFork_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } -impl Cheatcode for createFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for createFork_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } -impl Cheatcode for createSelectFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for createSelectFork_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } -impl Cheatcode for createSelectFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for createSelectFork_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } -impl Cheatcode for createSelectFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for createSelectFork_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } -impl Cheatcode for rollFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for rollFork_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork(None, (*blockNumber).to(), &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + db.roll_fork(None, (*blockNumber).to(), &mut env, inner)?; Ok(Default::default()) } } -impl Cheatcode for rollFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for rollFork_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { txHash } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork_to_transaction(None, *txHash, &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + db.roll_fork_to_transaction(None, *txHash, &mut env, inner)?; Ok(Default::default()) } } -impl Cheatcode for rollFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for rollFork_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut env, inner)?; Ok(Default::default()) } } -impl Cheatcode for rollFork_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for rollFork_3Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut env, inner)?; Ok(Default::default()) } } -impl Cheatcode for selectForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for selectForkCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.select_fork(*forkId, &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + db.select_fork(*forkId, &mut env, inner)?; Ok(Default::default()) } } -impl Cheatcode for transact_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for transact_0Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { txHash } = *self; transact(ccx, executor, txHash, None) } } -impl Cheatcode for transact_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for transact_1Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { forkId, txHash } = *self; transact(ccx, executor, txHash, Some(forkId)) } } -impl Cheatcode for allowCheatcodesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for allowCheatcodesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.allow_cheatcode_access(*account); + ccx.ecx.db_mut().allow_cheatcode_access(*account); Ok(Default::default()) } } -impl Cheatcode for makePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for makePersistent_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account); + ccx.ecx.db_mut().add_persistent_account(*account); Ok(Default::default()) } } -impl Cheatcode for makePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for makePersistent_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account0, account1 } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account0); - ccx.ecx.journaled_state.database.add_persistent_account(*account1); + ccx.ecx.db_mut().add_persistent_account(*account0); + ccx.ecx.db_mut().add_persistent_account(*account1); Ok(Default::default()) } } -impl Cheatcode for makePersistent_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for makePersistent_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account0, account1, account2 } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account0); - ccx.ecx.journaled_state.database.add_persistent_account(*account1); - ccx.ecx.journaled_state.database.add_persistent_account(*account2); + ccx.ecx.db_mut().add_persistent_account(*account0); + ccx.ecx.db_mut().add_persistent_account(*account1); + ccx.ecx.db_mut().add_persistent_account(*account2); Ok(Default::default()) } } -impl Cheatcode for makePersistent_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for makePersistent_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.journaled_state.database.add_persistent_account(*account); + ccx.ecx.db_mut().add_persistent_account(*account); } Ok(Default::default()) } } -impl Cheatcode for revokePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for revokePersistent_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.remove_persistent_account(account); + ccx.ecx.db_mut().remove_persistent_account(account); Ok(Default::default()) } } -impl Cheatcode for revokePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for revokePersistent_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.journaled_state.database.remove_persistent_account(account); + ccx.ecx.db_mut().remove_persistent_account(account); } Ok(Default::default()) } } -impl Cheatcode for isPersistentCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for isPersistentCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { account } = self; - Ok(ccx.ecx.journaled_state.database.is_persistent(account).abi_encode()) + Ok(ccx.ecx.db().is_persistent(account).abi_encode()) } } -impl Cheatcode for rpc_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for rpc_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { method, params } = self; - let url = ccx - .ecx - .journaled_state - .database - .active_fork_url() - .ok_or_else(|| fmt_err!("no active fork URL found"))?; + let url = + ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; rpc_call(&url, method, params) } } -impl Cheatcode for rpc_1Call { +impl Cheatcode for rpc_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { urlOrAlias, method, params } = self; let url = state.config.rpc_endpoint(urlOrAlias)?.url()?; @@ -220,8 +250,8 @@ impl Cheatcode for rpc_1Call { } } -impl Cheatcode for eth_getLogsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for eth_getLogsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { @@ -232,13 +262,9 @@ impl Cheatcode for eth_getLogsCall { bail!("topics array must contain at most 4 elements") } - let url = ccx - .ecx - .journaled_state - .database - .active_fork_url() - .ok_or_else(|| fmt_err!("no active fork URL found"))?; - let provider = ProviderBuilder::new(&url).build()?; + let url = + ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + let provider = ProviderBuilder::::new(&url).build()?; let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); for (i, &topic) in topics.iter().enumerate() { filter.topics[i] = topic.into(); @@ -266,16 +292,11 @@ impl Cheatcode for eth_getLogsCall { } } -impl Cheatcode for getRawBlockHeaderCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for getRawBlockHeaderCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber } = self; - let url = ccx - .ecx - .journaled_state - .database - .active_fork_url() - .ok_or_else(|| fmt_err!("no active fork"))?; - let provider = ProviderBuilder::new(&url).build()?; + let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?; + let provider = ProviderBuilder::::new(&url).build()?; let block_number = u64::try_from(blockNumber) .map_err(|_| fmt_err!("block number must be less than 2^64"))?; let block = @@ -294,50 +315,64 @@ impl Cheatcode for getRawBlockHeaderCall { } /// Creates and then also selects the new fork -fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { +fn create_select_fork< + CTX: FoundryContextExt + ContextTr, +>( + ccx: &mut CheatsCtxt<'_, CTX>, + url_or_alias: &str, + block: Option, +) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let id = db.create_select_fork(fork, &mut env, journal)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + let id = db.create_select_fork(fork, &mut env, inner)?; Ok(id.abi_encode()) } /// Creates a new fork -fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { +fn create_fork>( + ccx: &mut CheatsCtxt<'_, CTX>, + url_or_alias: &str, + block: Option, +) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.ecx.journaled_state.database.create_fork(fork)?; + let id = ccx.ecx.db_mut().create_fork(fork)?; Ok(id.abi_encode()) } /// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction( - ccx: &mut CheatsCtxt, +fn create_select_fork_at_transaction< + CTX: FoundryContextExt + ContextTr, +>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let id = db.create_select_fork_at_transaction(fork, &mut env, journal, *transaction)?; + let (journal, mut env) = ccx.ecx.journal_and_env_mut(); + let (db, inner) = journal.as_db_and_inner(); + let id = db.create_select_fork_at_transaction(fork, &mut env, inner, *transaction)?; Ok(id.abi_encode()) } /// Creates a new fork at the given transaction -fn create_fork_at_transaction( - ccx: &mut CheatsCtxt, +fn create_fork_at_transaction>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.ecx.journaled_state.database.create_fork_at_transaction(fork, *transaction)?; + let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } /// Creates the request object for a new fork request -fn create_fork_request( - ccx: &mut CheatsCtxt, +fn create_fork_request>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { @@ -356,7 +391,7 @@ fn create_fork_request( enable_caching: !ccx.state.config.no_storage_caching && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - env: ccx.ecx.as_env_mut().to_owned(), + env: ccx.ecx.to_env(), evm_opts, }; Ok(fork) @@ -370,20 +405,16 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { } } -fn transact( - ccx: &mut CheatsCtxt, +fn transact>( + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, transaction: B256, fork_id: Option, ) -> Result { - let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); - db.transact( - fork_id, - transaction, - env.to_owned(), - journal, - &mut *executor.get_inspector(ccx.state), - )?; + let env = ccx.ecx.to_env(); + let mut inspector = executor.get_inspector(ccx.state); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.transact(fork_id, transaction, env, inner, &mut *inspector)?; Ok(Default::default()) } @@ -391,13 +422,13 @@ fn transact( // state of caller contract is not lost when fork changes). // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 -fn persist_caller(ccx: &mut CheatsCtxt) { - ccx.ecx.journaled_state.database.add_persistent_account(ccx.caller); +fn persist_caller>(ccx: &mut CheatsCtxt<'_, CTX>) { + ccx.ecx.db_mut().add_persistent_account(ccx.caller); } /// Performs an Ethereum JSON-RPC request to the given endpoint. fn rpc_call(url: &str, method: &str, params: &str) -> Result { - let provider = ProviderBuilder::new(url).build()?; + let provider = ProviderBuilder::::new(url).build()?; let params_json: serde_json::Value = serde_json::from_str(params)?; let result = foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs index dab6e6aababe8..3fba5ce012de4 100644 --- a/crates/cheatcodes/src/evm/mapping.rs +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -3,7 +3,7 @@ use alloy_primitives::{Address, B256}; use alloy_sol_types::SolValue; use foundry_common::mapping_slots::MappingSlots; -impl Cheatcode for startMappingRecordingCall { +impl Cheatcode for startMappingRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mapping_slots.get_or_insert_default(); @@ -11,7 +11,7 @@ impl Cheatcode for startMappingRecordingCall { } } -impl Cheatcode for stopMappingRecordingCall { +impl Cheatcode for stopMappingRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mapping_slots = None; @@ -19,7 +19,7 @@ impl Cheatcode for stopMappingRecordingCall { } } -impl Cheatcode for getMappingLengthCall { +impl Cheatcode for getMappingLengthCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot } = self; let result = slot_child(state, target, mappingSlot).map(Vec::len).unwrap_or(0); @@ -27,7 +27,7 @@ impl Cheatcode for getMappingLengthCall { } } -impl Cheatcode for getMappingSlotAtCall { +impl Cheatcode for getMappingSlotAtCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot, idx } = self; let result = slot_child(state, target, mappingSlot) @@ -38,7 +38,7 @@ impl Cheatcode for getMappingSlotAtCall { } } -impl Cheatcode for getMappingKeyAndParentOfCall { +impl Cheatcode for getMappingKeyAndParentOfCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, elementSlot: slot } = self; let mut found = false; diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 3277ca338ec50..981782ea8a030 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -1,6 +1,11 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; -use revm::{bytecode::Bytecode, context::JournalTr, interpreter::InstructionResult}; +use foundry_evm_core::backend::DatabaseExt; +use revm::{ + bytecode::Bytecode, + context::{ContextTr, JournalTr}, + interpreter::InstructionResult, +}; use std::{cmp::Ordering, collections::VecDeque}; /// Mocked call data. @@ -38,7 +43,7 @@ impl Ord for MockCallDataContext { } } -impl Cheatcode for clearMockedCallsCall { +impl Cheatcode for clearMockedCallsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mocked_calls = Default::default(); @@ -46,8 +51,8 @@ impl Cheatcode for clearMockedCallsCall { } } -impl Cheatcode for mockCall_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -56,8 +61,8 @@ impl Cheatcode for mockCall_0Call { } } -impl Cheatcode for mockCall_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCall_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -66,8 +71,8 @@ impl Cheatcode for mockCall_1Call { } } -impl Cheatcode for mockCall_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCall_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -83,8 +88,8 @@ impl Cheatcode for mockCall_2Call { } } -impl Cheatcode for mockCall_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCall_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -100,8 +105,8 @@ impl Cheatcode for mockCall_3Call { } } -impl Cheatcode for mockCalls_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCalls_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -110,8 +115,8 @@ impl Cheatcode for mockCalls_0Call { } } -impl Cheatcode for mockCalls_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCalls_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -120,8 +125,8 @@ impl Cheatcode for mockCalls_1Call { } } -impl Cheatcode for mockCallRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCallRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -130,8 +135,8 @@ impl Cheatcode for mockCallRevert_0Call { } } -impl Cheatcode for mockCallRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCallRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -140,8 +145,8 @@ impl Cheatcode for mockCallRevert_1Call { } } -impl Cheatcode for mockCallRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCallRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -157,8 +162,8 @@ impl Cheatcode for mockCallRevert_2Call { } } -impl Cheatcode for mockCallRevert_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for mockCallRevert_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -174,7 +179,7 @@ impl Cheatcode for mockCallRevert_3Call { } } -impl Cheatcode for mockFunctionCall { +impl Cheatcode for mockFunctionCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, target, data } = self; state.mocked_functions.entry(*callee).or_default().insert(data.clone(), *target); @@ -213,13 +218,17 @@ fn mock_calls( // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. -fn make_acc_non_empty(callee: &Address, ecx: &mut CheatsCtxt) -> Result { - let acc = ecx.journaled_state.load_account(*callee)?; - - let empty_bytecode = acc.info.code.as_ref().is_none_or(Bytecode::is_empty); +fn make_acc_non_empty>( + callee: &Address, + ccx: &mut CheatsCtxt<'_, CTX>, +) -> Result { + let empty_bytecode = { + let acc = ccx.ecx.journal_mut().load_account(*callee)?; + acc.info.code.as_ref().is_none_or(Bytecode::is_empty) + }; if empty_bytecode { let code = Bytecode::new_raw(Bytes::from_static(&[0u8])); - ecx.journaled_state.set_code(*callee, code); + ccx.ecx.journal_mut().set_code(*callee, code); } Ok(Default::default()) diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index d4619d9368e36..70a489cdc508a 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -1,6 +1,10 @@ use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_primitives::Address; -use revm::context::JournalTr; +use foundry_evm_core::{backend::DatabaseExt, env::FoundryContextExt}; +use revm::{ + context::{ContextTr, JournalTr, Transaction}, + inspector::JournalExt, +}; /// Prank information. #[derive(Clone, Copy, Debug, Default)] @@ -53,72 +57,88 @@ impl Prank { } } -impl Cheatcode for prank_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for prank_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true, false) } } -impl Cheatcode for startPrank_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startPrank_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false, false) } } -impl Cheatcode for prank_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for prank_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true, false) } } -impl Cheatcode for startPrank_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startPrank_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false, false) } } -impl Cheatcode for prank_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for prank_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, true, *delegateCall) } } -impl Cheatcode for startPrank_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startPrank_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, false, *delegateCall) } } -impl Cheatcode for prank_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for prank_3Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), true, *delegateCall) } } -impl Cheatcode for startPrank_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startPrank_3Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), false, *delegateCall) } } -impl Cheatcode for stopPrankCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopPrankCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - ccx.state.pranks.remove(&ccx.ecx.journaled_state.depth()); + ccx.state.pranks.remove(&ccx.ecx.journal().depth()); Ok(Default::default()) } } -fn prank( - ccx: &mut CheatsCtxt, +fn prank>( + ccx: &mut CheatsCtxt<'_, CTX>, new_caller: &Address, new_origin: Option<&Address>, single_call: bool, @@ -137,7 +157,7 @@ fn prank( ); } - let depth = ccx.ecx.journaled_state.depth(); + let depth = ccx.ecx.journal().depth(); if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.get_prank(depth) { ensure!(used, "cannot overwrite a prank until it is applied at least once"); // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. @@ -151,7 +171,7 @@ fn prank( let prank = Prank::new( ccx.caller, - ccx.ecx.tx.caller, + ccx.ecx.tx().caller(), *new_caller, new_origin.copied(), depth, diff --git a/crates/cheatcodes/src/evm/record_debug_step.rs b/crates/cheatcodes/src/evm/record_debug_step.rs index d1cd2b52f111b..3691ceb52ad00 100644 --- a/crates/cheatcodes/src/evm/record_debug_step.rs +++ b/crates/cheatcodes/src/evm/record_debug_step.rs @@ -51,11 +51,9 @@ fn recursive_flatten_call_trace<'a>( for order in &node.ordering { match order { - TraceMemberOrder::Step(step_idx) => { - if *record_started { - let step = &node.trace.steps[*step_idx]; - flatten_steps.push(CallTraceCtx { node, step }); - } + TraceMemberOrder::Step(step_idx) if *record_started => { + let step = &node.trace.steps[*step_idx]; + flatten_steps.push(CallTraceCtx { node, step }); } TraceMemberOrder::Call(call_idx) => { let child_node_idx = node.children[*call_idx]; diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index c90c1937d0bb3..c63352e171377 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,7 +1,9 @@ //! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. use super::string::parse; -use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use crate::{ + Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*, inspector::exec_create, +}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; use alloy_network::AnyTransactionReceipt; @@ -12,8 +14,9 @@ use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::{backend::FoundryJournalExt, env::FoundryContextExt, evm::NestedEvmExt}; use revm::{ - context::{CreateScheme, JournalTr}, + context::{Cfg, ContextTr, CreateScheme, JournalTr}, interpreter::CreateInputs, }; use revm_inspectors::tracing::types::CallKind; @@ -28,7 +31,7 @@ use std::{ }; use walkdir::WalkDir; -impl Cheatcode for existsCall { +impl Cheatcode for existsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -36,7 +39,7 @@ impl Cheatcode for existsCall { } } -impl Cheatcode for fsMetadataCall { +impl Cheatcode for fsMetadataCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -62,7 +65,7 @@ impl Cheatcode for fsMetadataCall { } } -impl Cheatcode for isDirCall { +impl Cheatcode for isDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -70,7 +73,7 @@ impl Cheatcode for isDirCall { } } -impl Cheatcode for isFileCall { +impl Cheatcode for isFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -78,14 +81,14 @@ impl Cheatcode for isFileCall { } } -impl Cheatcode for projectRootCall { +impl Cheatcode for projectRootCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.config.root.display().to_string().abi_encode()) } } -impl Cheatcode for unixTimeCall { +impl Cheatcode for unixTimeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; let difference = SystemTime::now() @@ -95,7 +98,7 @@ impl Cheatcode for unixTimeCall { } } -impl Cheatcode for closeFileCall { +impl Cheatcode for closeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -106,7 +109,7 @@ impl Cheatcode for closeFileCall { } } -impl Cheatcode for copyFileCall { +impl Cheatcode for copyFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { from, to } = self; let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?; @@ -118,7 +121,7 @@ impl Cheatcode for copyFileCall { } } -impl Cheatcode for createDirCall { +impl Cheatcode for createDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; @@ -127,28 +130,28 @@ impl Cheatcode for createDirCall { } } -impl Cheatcode for readDir_0Call { +impl Cheatcode for readDir_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; read_dir(state, path.as_ref(), 1, false) } } -impl Cheatcode for readDir_1Call { +impl Cheatcode for readDir_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth } = self; read_dir(state, path.as_ref(), *maxDepth, false) } } -impl Cheatcode for readDir_2Call { +impl Cheatcode for readDir_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth, followLinks } = self; read_dir(state, path.as_ref(), *maxDepth, *followLinks) } } -impl Cheatcode for readFileCall { +impl Cheatcode for readFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -156,7 +159,7 @@ impl Cheatcode for readFileCall { } } -impl Cheatcode for readFileBinaryCall { +impl Cheatcode for readFileBinaryCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -164,7 +167,7 @@ impl Cheatcode for readFileBinaryCall { } } -impl Cheatcode for readLineCall { +impl Cheatcode for readLineCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -190,7 +193,7 @@ impl Cheatcode for readLineCall { } } -impl Cheatcode for readLinkCall { +impl Cheatcode for readLinkCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { linkPath: path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -199,7 +202,7 @@ impl Cheatcode for readLinkCall { } } -impl Cheatcode for removeDirCall { +impl Cheatcode for removeDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; @@ -208,7 +211,7 @@ impl Cheatcode for removeDirCall { } } -impl Cheatcode for removeFileCall { +impl Cheatcode for removeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; @@ -225,21 +228,21 @@ impl Cheatcode for removeFileCall { } } -impl Cheatcode for writeFileCall { +impl Cheatcode for writeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data.as_bytes()) } } -impl Cheatcode for writeFileBinaryCall { +impl Cheatcode for writeFileBinaryCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data) } } -impl Cheatcode for writeLineCall { +impl Cheatcode for writeLineCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data: line } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; @@ -253,7 +256,7 @@ impl Cheatcode for writeLineCall { } } -impl Cheatcode for getArtifactPathByCodeCall { +impl Cheatcode for getArtifactPathByCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { code } = self; let (artifact_id, _) = state @@ -267,7 +270,7 @@ impl Cheatcode for getArtifactPathByCodeCall { } } -impl Cheatcode for getArtifactPathByDeployedCodeCall { +impl Cheatcode for getArtifactPathByDeployedCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { deployedCode } = self; let (artifact_id, _) = state @@ -281,71 +284,119 @@ impl Cheatcode for getArtifactPathByDeployedCodeCall { } } -impl Cheatcode for getCodeCall { +impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, false)?.abi_encode()) } } -impl Cheatcode for getDeployedCodeCall { +impl Cheatcode for getDeployedCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, true)?.abi_encode()) } } -impl Cheatcode for deployCode_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_0Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path } = self; deploy_code(ccx, executor, path, None, None, None) } } -impl Cheatcode for deployCode_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_1Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args } = self; deploy_code(ccx, executor, path, Some(args), None, None) } } -impl Cheatcode for deployCode_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_2Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, value } = self; deploy_code(ccx, executor, path, None, Some(*value), None) } } -impl Cheatcode for deployCode_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_3Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, value } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), None) } } -impl Cheatcode for deployCode_4Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_4Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, salt } = self; deploy_code(ccx, executor, path, None, None, Some((*salt).into())) } } -impl Cheatcode for deployCode_5Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_5Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, salt } = self; deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into())) } } -impl Cheatcode for deployCode_6Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_6Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, value, salt } = self; deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into())) } } -impl Cheatcode for deployCode_7Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { +impl> Cheatcode + for deployCode_7Call +{ + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, value, salt } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into())) } @@ -353,8 +404,8 @@ impl Cheatcode for deployCode_7Call { /// Helper function to deploy contract from artifact code. /// Uses CREATE2 scheme if salt specified. -fn deploy_code( - ccx: &mut CheatsCtxt, +fn deploy_code>( + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, path: &str, constructor_args: Option<&Bytes>, @@ -376,19 +427,18 @@ fn deploy_code( if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create }; // If prank active at current depth, then use it as caller for create input. - let caller = ccx - .state - .get_prank(ccx.ecx.journaled_state.depth()) - .map_or(ccx.caller, |prank| prank.new_caller); + let caller = + ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller); - let outcome = executor.exec_create( - CreateInputs { + let outcome = exec_create( + executor, + CreateInputs::new( caller, scheme, - value: value.unwrap_or(U256::ZERO), - init_code: bytecode.into(), - gas_limit: ccx.gas_limit, - }, + value.unwrap_or(U256::ZERO), + bytecode.into(), + ccx.gas_limit, + ), ccx, )?; @@ -552,7 +602,7 @@ fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result Cheatcode for ffiCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; @@ -580,42 +630,42 @@ impl Cheatcode for ffiCall { } } -impl Cheatcode for tryFfiCall { +impl Cheatcode for tryFfiCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; ffi(state, input).map(|res| res.abi_encode()) } } -impl Cheatcode for promptCall { +impl Cheatcode for promptCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_input).map(|res| res.abi_encode()) } } -impl Cheatcode for promptSecretCall { +impl Cheatcode for promptSecretCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_password).map(|res| res.abi_encode()) } } -impl Cheatcode for promptSecretUintCall { +impl Cheatcode for promptSecretUintCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256)) } } -impl Cheatcode for promptAddressCall { +impl Cheatcode for promptAddressCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Address) } } -impl Cheatcode for promptUintCall { +impl Cheatcode for promptUintCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256)) @@ -729,7 +779,7 @@ fn prompt( } } -impl Cheatcode for getBroadcastCall { +impl Cheatcode for getBroadcastCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; @@ -744,7 +794,7 @@ impl Cheatcode for getBroadcastCall { } } -impl Cheatcode for getBroadcasts_0Call { +impl Cheatcode for getBroadcasts_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; @@ -765,7 +815,7 @@ impl Cheatcode for getBroadcasts_0Call { } } -impl Cheatcode for getBroadcasts_1Call { +impl Cheatcode for getBroadcasts_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; @@ -785,10 +835,10 @@ impl Cheatcode for getBroadcasts_1Call { } } -impl Cheatcode for getDeployment_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getDeployment_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { contractName } = self; - let chain_id = ccx.ecx.cfg.chain_id; + let chain_id = ccx.ecx.cfg().chain_id(); let latest_broadcast = latest_broadcast( contractName, @@ -801,7 +851,7 @@ impl Cheatcode for getDeployment_0Call { } } -impl Cheatcode for getDeployment_1Call { +impl Cheatcode for getDeployment_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; @@ -816,7 +866,7 @@ impl Cheatcode for getDeployment_1Call { } } -impl Cheatcode for getDeploymentsCall { +impl Cheatcode for getDeploymentsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; @@ -897,7 +947,7 @@ fn latest_broadcast( #[cfg(test)] mod tests { use super::*; - use crate::CheatsConfig; + use crate::{AnyCtx, CheatsConfig}; use std::sync::Arc; fn cheats() -> Cheatcodes { @@ -937,7 +987,7 @@ mod tests { #[cfg(windows)] let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()]; - let result = ffiCall { commandInput: args }.apply(&mut cheats); + let result = Cheatcode::::apply(&ffiCall { commandInput: args }, &mut cheats); // Assert that the cheatcode returned an error. assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded"); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 7dfad36322c6a..6047da22b260d 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1,7 +1,7 @@ //! Cheatcode EVM inspector. use crate::{ - CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, + Cheatcode, CheatsConfig, CheatsCtxt, Error, Result, Vm::{self, AccountAccess}, evm::{ DealRecord, GasRecord, RecordAccess, journaled_account, @@ -21,7 +21,6 @@ use crate::{ utils::IgnoredTraces, }; use alloy_consensus::BlobTransactionSidecarVariant; -use alloy_evm::eth::EthEvmContext; use alloy_network::{TransactionBuilder4844, TransactionBuilder7594}; use alloy_primitives::{ Address, B256, Bytes, Log, TxKind, U256, hex, @@ -37,11 +36,12 @@ use foundry_common::{ mapping_slots::{MappingSlots, step as mapping_step}, }; use foundry_evm_core::{ - Breakpoints, ContextExt, InspectorExt, + Breakpoints, FoundryInspectorExt, InspectorExt, abi::Vm::stopExpectSafeMemoryCall, - backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, + backend::{DatabaseError, DatabaseExt, FoundryJournalExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, - evm::{FoundryEvm, new_evm_with_existing_context}, + env::FoundryContextExt, + evm::NestedEvmExt, }; use foundry_evm_traces::{ TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier, @@ -51,13 +51,16 @@ use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; use rand::Rng; use revm::{ - Inspector, Journal, + Inspector, bytecode::opcode as op, - context::{BlockEnv, JournalTr, LocalContext, TransactionType, result::EVMError}, + context::{ + BlockEnv, Cfg, ContextTr, JournalTr, Transaction, TransactionType, result::EVMError, + }, context_interface::{CreateScheme, transaction::SignedAuthorization}, handler::FrameResult, + inspector::JournalExt, interpreter::{ - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, Host, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, interpreter_types::{Jumps, LoopControl, MemoryTr}, }, @@ -79,7 +82,19 @@ mod utils; pub mod analysis; pub use analysis::CheatcodeAnalysis; -pub type Ecx<'a, 'b, 'c> = &'a mut EthEvmContext<&'b mut (dyn DatabaseExt + 'c)>; +/// Bounds for the generic `Inspector` impl on `Cheatcodes`. +/// +/// Shorthand used internally to avoid repeating the full where-clause. +/// Any `EthEvmContext<&mut dyn DatabaseExt>` satisfies these bounds, so all +/// existing call-sites (e.g. `InspectorStackRefMut`) keep working unchanged. +pub trait CheatsCtxExt: + FoundryContextExt + NestedEvmExt +{ +} +impl CheatsCtxExt for CTX where + CTX: FoundryContextExt + NestedEvmExt +{ +} /// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to /// [Cheatcodes]. @@ -91,78 +106,45 @@ pub trait CheatcodesExecutor { /// [revm::Inspector]. fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box; - /// Obtains [FoundryEvm] instance and executes the given CREATE frame. - fn exec_create( - &mut self, - inputs: CreateInputs, - ccx: &mut CheatsCtxt, - ) -> Result> { - with_evm(self, ccx, |evm| { - evm.journaled_state.depth += 1; - - let frame = FrameInput::Create(Box::new(inputs)); - - let outcome = match evm.run_execution(frame)? { - FrameResult::Call(_) => unreachable!(), - FrameResult::Create(create) => create, - }; - - evm.journaled_state.depth -= 1; - - Ok(outcome) - }) - } - - fn console_log(&mut self, ccx: &mut CheatsCtxt, msg: &str) { - self.get_inspector(ccx.state).console_log(msg); + fn console_log(&mut self, cheats: &mut Cheatcodes, msg: &str) { + self.get_inspector(cheats).console_log(msg); } /// Returns a mutable reference to the tracing inspector if it is available. fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { None } + + /// Marks that the next EVM frame is an "inner context" so that isolation mode does not + /// trigger a nested `transact_inner`. `original_origin` is stored for the existing + /// inner-context adjustment logic that restores `tx.origin`. + fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option

) {} } -/// Constructs [FoundryEvm] and runs a given closure with it. -fn with_evm( - executor: &mut E, - ccx: &mut CheatsCtxt, - f: F, -) -> Result> +/// Builds a sub-EVM from the current context and executes the given CREATE frame. +pub(crate) fn exec_create( + executor: &mut dyn CheatcodesExecutor, + inputs: CreateInputs, + ccx: &mut CheatsCtxt<'_, CTX>, +) -> std::result::Result> where - E: CheatcodesExecutor + ?Sized, - F: for<'a, 'b> FnOnce( - &mut FoundryEvm<'a, &'b mut dyn InspectorExt>, - ) -> Result>, + CTX::Journal: FoundryJournalExt, { let mut inspector = executor.get_inspector(ccx.state); - let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); - - let ctx = EthEvmContext { - block: ccx.ecx.block.clone(), - cfg: ccx.ecx.cfg.clone(), - tx: ccx.ecx.tx.clone(), - journaled_state: Journal { - inner: ccx.ecx.journaled_state.inner.clone(), - database: &mut *ccx.ecx.journaled_state.database as &mut dyn DatabaseExt, - }, - local: LocalContext::default(), - chain: (), - error, - }; + ccx.ecx.with_nested_evm(&mut *inspector, |evm| { + evm.journal_inner_mut().depth += 1; - let mut evm = new_evm_with_existing_context(ctx, &mut *inspector); + let frame = FrameInput::Create(Box::new(inputs)); - let res = f(&mut evm)?; + let outcome = match evm.run_execution(frame)? { + FrameResult::Call(_) => unreachable!(), + FrameResult::Create(create) => create, + }; - let ctx = evm.into_context(); - ccx.ecx.journaled_state.inner = ctx.journaled_state.inner; - ccx.ecx.block = ctx.block; - ccx.ecx.tx = ctx.tx; - ccx.ecx.cfg = ctx.cfg; - ccx.ecx.error = ctx.error; + evm.journal_inner_mut().depth -= 1; - Ok(res) + Ok(outcome) + }) } /// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an @@ -314,12 +296,17 @@ impl ArbitraryStorage { /// Saves arbitrary storage value for a given address: /// - store value in changed values cache. /// - update account's storage with given value. - pub fn save(&mut self, ecx: Ecx, address: Address, slot: U256, data: U256) { + pub fn save>( + &mut self, + ecx: &mut CTX, + address: Address, + slot: U256, + data: U256, + ) { self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); - let (db, journal, _) = ecx.as_db_env_and_journal(); - if journal.load_account(db, address).is_ok() { - journal - .sstore(db, address, slot, data, false) + if ecx.journal_mut().load_account(address).is_ok() { + ecx.journal_mut() + .sstore(address, slot, data) .expect("could not set arbitrary storage value"); } } @@ -329,7 +316,13 @@ impl ArbitraryStorage { /// existing value. /// - if no value was yet generated for given slot, then save new value in cache and update both /// source and target storages. - pub fn copy(&mut self, ecx: Ecx, target: Address, slot: U256, new_value: U256) -> U256 { + pub fn copy>( + &mut self, + ecx: &mut CTX, + target: Address, + slot: U256, + new_value: U256, + ) -> U256 { let source = self.copies.get(&target).expect("missing arbitrary copy target entry"); let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage"); let value = match storage_cache.get(&slot) { @@ -337,19 +330,17 @@ impl ArbitraryStorage { None => { storage_cache.insert(slot, new_value); // Update source storage with new value. - let (db, journal, _) = ecx.as_db_env_and_journal(); - if journal.load_account(db, *source).is_ok() { - journal - .sstore(db, *source, slot, new_value, false) + if ecx.journal_mut().load_account(*source).is_ok() { + ecx.journal_mut() + .sstore(*source, slot, new_value) .expect("could not copy arbitrary storage value"); } new_value } }; // Update target storage with new value. - let (db, journal, _) = ecx.as_db_env_and_journal(); - if journal.load_account(db, target).is_ok() { - journal.sstore(db, target, slot, value, false).expect("could not set storage"); + if ecx.journal_mut().load_account(target).is_ok() { + ecx.journal_mut().sstore(target, slot, value).expect("could not set storage"); } value } @@ -610,9 +601,9 @@ impl Cheatcodes { } /// Decodes the input data and applies the cheatcode. - fn apply_cheatcode( + fn apply_cheatcode( &mut self, - ecx: Ecx, + ecx: &mut CTX, call: &CallInputs, executor: &mut dyn CheatcodesExecutor, ) -> Result { @@ -633,7 +624,7 @@ impl Cheatcodes { // ensure the caller is allowed to execute cheatcodes, // but only if the backend is in forking mode - ecx.journaled_state.database.ensure_cheatcode_access_forking_mode(&caller)?; + ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?; apply_dispatch( &decoded, @@ -647,11 +638,14 @@ impl Cheatcodes { /// /// There may be cheatcodes in the constructor of the new contract, in order to allow them /// automatically we need to determine the new address. - fn allow_cheatcodes_on_create(&self, ecx: Ecx, caller: Address, created_address: Address) { - if ecx.journaled_state.depth <= 1 - || ecx.journaled_state.database.has_cheatcode_access(&caller) - { - ecx.journaled_state.database.allow_cheatcode_access(created_address); + fn allow_cheatcodes_on_create>( + &self, + ecx: &mut CTX, + caller: Address, + created_address: Address, + ) { + if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) { + ecx.db_mut().allow_cheatcode_access(created_address); } } @@ -660,12 +654,12 @@ impl Cheatcodes { /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support /// access lists themselves. - fn apply_accesslist(&mut self, ecx: Ecx) { + fn apply_accesslist(&mut self, ecx: &mut CTX) { if let Some(access_list) = &self.access_list { - ecx.tx.access_list = access_list.clone(); + ecx.tx_mut().access_list = access_list.clone(); - if ecx.tx.tx_type == TransactionType::Legacy as u8 { - ecx.tx.tx_type = TransactionType::Eip2930 as u8; + if ecx.tx().tx_type() == TransactionType::Legacy as u8 { + ecx.tx_mut().tx_type = TransactionType::Eip2930 as u8; } } } @@ -674,7 +668,7 @@ impl Cheatcodes { /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. - pub fn on_revert(&mut self, ecx: Ecx) { + pub fn on_revert>(&mut self, ecx: &mut CTX) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. @@ -683,7 +677,7 @@ impl Cheatcodes { } // we only want to apply cleanup top level - if ecx.journaled_state.depth() > 0 { + if ecx.journal().depth() > 0 { return; } @@ -691,31 +685,31 @@ impl Cheatcodes { // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine // which rolls back any transfers. while let Some(record) = self.eth_deals.pop() { - if let Some(acc) = ecx.journaled_state.state.get_mut(&record.address) { + if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) { acc.info.balance = record.old_balance; } } } - pub fn call_with_executor( + pub fn call_with_executor( &mut self, - ecx: Ecx, + ecx: &mut CTX, call: &mut CallInputs, executor: &mut dyn CheatcodesExecutor, ) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { - ecx.cfg.spec = spec_id; + ecx.cfg_mut().spec = spec_id; } let gas = Gas::new(call.gas_limit); - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); // At the root call to test function or script `run()`/`setUp()` functions, we are // decreasing sender nonce to ensure that it matches on-chain nonce once we start // broadcasting. if curr_depth == 0 { - let sender = ecx.tx.caller; + let sender = ecx.tx().caller(); let account = match super::evm::journaled_account(ecx, sender) { Ok(account) => account, Err(err) => { @@ -766,6 +760,13 @@ impl Cheatcodes { return None; } + // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but + // precompile calls don't create an interpreter frame so we must also track it here. + // The callee executes at `curr_depth + 1`. + if let Some(expected) = &mut self.expected_revert { + expected.max_depth = max(curr_depth + 1, expected.max_depth); + } + // Handle expected calls // Grab the different calldatas expected. @@ -837,7 +838,7 @@ impl Cheatcodes { call.target_address = prank.new_caller; call.caller = prank.new_caller; if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; + ecx.tx_mut().caller = new_origin; } } @@ -854,7 +855,7 @@ impl Cheatcodes { // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; + ecx.tx_mut().caller = new_origin; prank_applied = true; } @@ -883,7 +884,7 @@ impl Cheatcodes { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the // broadcast.origin. - ecx.tx.caller = broadcast.new_origin; + ecx.tx_mut().caller = broadcast.new_origin; call.caller = broadcast.new_origin; // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here @@ -891,8 +892,7 @@ impl Cheatcodes { // into 1559, in the cli package, relatively easily once we // know the target chain supports EIP-1559. if !call.is_static { - let (db, journal, _) = ecx.as_db_env_and_journal(); - if let Err(err) = journal.load_account(db, broadcast.new_origin) { + if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -906,9 +906,10 @@ impl Cheatcodes { } let input = TransactionInput::new(call.input.bytes(ecx)); - + let chain_id = ecx.cfg().chain_id(); + let rpc = ecx.db().active_fork_url(); let account = - ecx.journaled_state.inner.state().get_mut(&broadcast.new_origin).unwrap(); + ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap(); let mut tx_req = TransactionRequest { from: Some(broadcast.new_origin), @@ -916,7 +917,7 @@ impl Cheatcodes { value: call.transfer_value(), input, nonce: Some(account.info.nonce), - chain_id: Some(ecx.cfg.chain_id), + chain_id: Some(chain_id), gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, ..Default::default() }; @@ -960,10 +961,8 @@ impl Cheatcodes { tx_req.authorization_list = Some(active_delegations); } - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.journaled_state.database.active_fork_url(), - transaction: tx_req.into(), - }); + self.broadcastable_transactions + .push_back(BroadcastableTransaction { rpc, transaction: tx_req.into() }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); // Explicitly increment nonce if calls are not isolated. @@ -996,11 +995,10 @@ impl Cheatcodes { let old_balance; let old_nonce; - let (db, journal, _) = ecx.as_db_env_and_journal(); - if let Ok(acc) = journal.load_account(db, call.target_address) { - initialized = acc.info.exists(); - old_balance = acc.info.balance; - old_nonce = acc.info.nonce; + if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { + initialized = acc.data.info.exists(); + old_balance = acc.data.info.balance; + old_nonce = acc.data.info.nonce; } else { initialized = false; old_balance = U256::ZERO; @@ -1021,8 +1019,8 @@ impl Cheatcodes { // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: call.caller, account: call.bytecode_address, @@ -1037,11 +1035,7 @@ impl Cheatcodes { reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step - depth: ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"), + depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"), }]); } @@ -1118,15 +1112,15 @@ impl Cheatcodes { } } -impl Inspector> for Cheatcodes { - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { +impl Inspector for Cheatcodes { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { - ecx.block = block; + *ecx.block_mut() = block; } if let Some(gas_price) = self.gas_price.take() { - ecx.tx.gas_price = gas_price; + ecx.tx_mut().gas_price = gas_price; } // Record gas for current frame. @@ -1136,11 +1130,11 @@ impl Inspector> for Cheatcodes { // `expectRevert`: track the max call depth during `expectRevert` if let Some(expected) = &mut self.expected_revert { - expected.max_depth = max(ecx.journaled_state.depth(), expected.max_depth); + expected.max_depth = max(ecx.journal().depth(), expected.max_depth); } } - fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { self.pc = interpreter.bytecode.pc(); if self.broadcast.is_some() { @@ -1171,7 +1165,7 @@ impl Inspector> for Cheatcodes { if !self.allowed_mem_writes.is_empty() { self.check_mem_opcodes( interpreter, - ecx.journaled_state.depth().try_into().expect("journaled state depth exceeds u64"), + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"), ); } @@ -1186,7 +1180,7 @@ impl Inspector> for Cheatcodes { } } - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -1201,7 +1195,7 @@ impl Inspector> for Cheatcodes { } } - fn log(&mut self, _ecx: Ecx, log: Log) { + fn log(&mut self, _ecx: &mut CTX, log: Log) { if !self.expected_emits.is_empty() && let Some(err) = expect::handle_expect_emit(self, &log, None) { @@ -1215,7 +1209,7 @@ impl Inspector> for Cheatcodes { record_logs(&mut self.recorded_logs, &log); } - fn log_full(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: Log) { + fn log_full(&mut self, interpreter: &mut Interpreter, _ecx: &mut CTX, log: Log) { if !self.expected_emits.is_empty() { expect::handle_expect_emit(self, &log, Some(interpreter)); } @@ -1224,11 +1218,11 @@ impl Inspector> for Cheatcodes { record_logs(&mut self.recorded_logs, &log); } - fn call(&mut self, ecx: Ecx, inputs: &mut CallInputs) -> Option { + fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) } - fn call_end(&mut self, ecx: Ecx, call: &CallInputs, outcome: &mut CallOutcome) { + fn call_end(&mut self, ecx: &mut CTX, call: &CallInputs, outcome: &mut CallOutcome) { let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || call.target_address == HARDHAT_CONSOLE_ADDRESS; @@ -1237,11 +1231,11 @@ impl Inspector> for Cheatcodes { // This should be placed before the revert handling, because we might exit early there if !cheatcode_call { // Clean up pranks - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if let Some(prank) = &self.get_prank(curr_depth) && curr_depth == prank.depth { - ecx.tx.caller = prank.prank_origin; + ecx.tx_mut().caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1253,7 +1247,7 @@ impl Inspector> for Cheatcodes { if let Some(broadcast) = &self.broadcast && curr_depth == broadcast.depth { - ecx.tx.caller = broadcast.original_origin; + ecx.tx_mut().caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1271,7 +1265,7 @@ impl Inspector> for Cheatcodes { } // allow multiple cheatcode calls at the same depth - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth <= assume_no_revert.depth && !cheatcode_call { // Discard run if we're at the same depth as cheatcode, call reverted, and no // specific reason was supplied @@ -1318,7 +1312,7 @@ impl Inspector> for Cheatcodes { } } - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth <= expected_revert.depth { let needs_processing = match expected_revert.kind { ExpectedRevertKind::Default => !cheatcode_call, @@ -1387,7 +1381,7 @@ impl Inspector> for Cheatcodes { // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // The root call cannot be recorded. - if ecx.journaled_state.depth() > 0 + if ecx.journal().depth() > 0 && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop() { // Update the reverted status of all deeper calls if this call reverted, in @@ -1407,14 +1401,13 @@ impl Inspector> for Cheatcodes { // changes. Depending on the depth the cheat was // called at, there may not be any pending // calls to update if execution has percolated up to a higher depth. - let (db, journal, _) = ecx.as_db_env_and_journal(); - let curr_depth = journal.depth; + let curr_depth = ecx.journal().depth(); if call_access.depth == curr_depth as u64 - && let Ok(acc) = journal.load_account(db, call.target_address) + && let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { debug_assert!(access_is_call(call_access.kind)); - call_access.newBalance = acc.info.balance; - call_access.newNonce = acc.info.nonce; + call_access.newBalance = acc.data.info.balance; + call_access.newNonce = acc.data.info.nonce; } // Merge the last depth's AccountAccesses into the AccountAccesses at the // current depth, or push them back onto the pending @@ -1444,7 +1437,7 @@ impl Inspector> for Cheatcodes { .expected_emits .iter() .any(|(expected, _)| { - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); expected.depth == curr_depth }) && // Ignore staticcalls @@ -1521,21 +1514,20 @@ impl Inspector> for Cheatcodes { // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TxKind::Call(test_contract) = ecx.tx.kind { + if let TxKind::Call(test_contract) = ecx.tx().kind() { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork - if ecx.journaled_state.db().is_forked_mode() + if ecx.db().is_forked_mode() && outcome.result.result == InstructionResult::Stop && call.target_address != test_contract { - let journaled_state = ecx.journaled_state.clone(); self.fork_revert_diagnostic = - ecx.journaled_state.db().diagnose_revert(call.target_address, &journaled_state); + ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state()); } } // If the depth is 0, then this is the root call terminating - if ecx.journaled_state.depth() == 0 { + if ecx.journal().depth() == 0 { // If we already have a revert, we shouldn't run the below logic as it can obfuscate an // earlier error that happened first with unrelated information about // another error when using cheatcodes. @@ -1632,10 +1624,10 @@ impl Inspector> for Cheatcodes { } } - fn create(&mut self, ecx: Ecx, mut input: &mut CreateInputs) -> Option { + fn create(&mut self, ecx: &mut CTX, mut input: &mut CreateInputs) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { - ecx.cfg.spec = spec_id; + ecx.cfg_mut().spec = spec_id; } let gas = Gas::new(input.gas_limit()); @@ -1654,7 +1646,7 @@ impl Inspector> for Cheatcodes { }); } - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) @@ -1673,7 +1665,7 @@ impl Inspector> for Cheatcodes { // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; + ecx.tx_mut().caller = new_origin; prank_applied = true; } @@ -1691,8 +1683,7 @@ impl Inspector> for Cheatcodes { && curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { - let (db, journal, _) = ecx.as_db_env_and_journal(); - if let Err(err) = journal.load_account(db, broadcast.new_origin) { + if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) { return Some(CreateOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1703,7 +1694,7 @@ impl Inspector> for Cheatcodes { }); } - ecx.tx.caller = broadcast.new_origin; + ecx.tx_mut().caller = broadcast.new_origin; if curr_depth == broadcast.depth || broadcast.deploy_from_code { // Reset deploy from code flag for upcoming calls; @@ -1711,9 +1702,10 @@ impl Inspector> for Cheatcodes { input.set_caller(broadcast.new_origin); - let account = &ecx.journaled_state.inner.state()[&broadcast.new_origin]; + let rpc = ecx.db().active_fork_url(); + let account = &ecx.journal().evm_state()[&broadcast.new_origin]; self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.journaled_state.database.active_fork_url(), + rpc, transaction: TransactionRequest { from: Some(broadcast.new_origin), to: None, @@ -1736,8 +1728,8 @@ impl Inspector> for Cheatcodes { if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: input.caller(), account: address, @@ -1759,15 +1751,15 @@ impl Inspector> for Cheatcodes { None } - fn create_end(&mut self, ecx: Ecx, call: &CreateInputs, outcome: &mut CreateOutcome) { + fn create_end(&mut self, ecx: &mut CTX, call: &CreateInputs, outcome: &mut CreateOutcome) { let call = Some(call); - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); // Clean up pranks if let Some(prank) = &self.get_prank(curr_depth) && curr_depth == prank.depth { - ecx.tx.caller = prank.prank_origin; + ecx.tx_mut().caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1779,7 +1771,7 @@ impl Inspector> for Cheatcodes { if let Some(broadcast) = &self.broadcast && curr_depth == broadcast.depth { - ecx.tx.caller = broadcast.original_origin; + ecx.tx_mut().caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1843,20 +1835,24 @@ impl Inspector> for Cheatcodes { // changes. Depending on what depth the cheat was called at, there // may not be any pending calls to update if execution has // percolated up to a higher depth. - let depth = ecx.journaled_state.depth(); + let depth = ecx.journal().depth(); if create_access.depth == depth as u64 { debug_assert_eq!( create_access.kind as u8, crate::Vm::AccountAccessKind::Create as u8 ); - let (db, journal, _) = ecx.as_db_env_and_journal(); if let Some(address) = outcome.address - && let Ok(created_acc) = journal.load_account(db, address) + && let Ok(created_acc) = ecx.journal_mut().load_account(address) { - create_access.newBalance = created_acc.info.balance; - create_access.newNonce = created_acc.info.nonce; - create_access.deployedCode = - created_acc.info.code.clone().unwrap_or_default().original_bytes(); + create_access.newBalance = created_acc.data.info.balance; + create_access.newNonce = created_acc.data.info.nonce; + create_access.deployedCode = created_acc + .data + .info + .code + .clone() + .unwrap_or_default() + .original_bytes(); } } // Merge the last depth's AccountAccesses into the AccountAccesses at the @@ -1873,16 +1869,15 @@ impl Inspector> for Cheatcodes { } // Match the create against expected_creates - let (db, journal, _) = ecx.as_db_env_and_journal(); if !self.expected_creates.is_empty() && let (Some(address), Some(call)) = (outcome.address, call) - && let Ok(created_acc) = journal.load_account(db, address) + && let Ok(created_acc) = ecx.journal_mut().load_account(address) { - let bytecode = created_acc.info.code.clone().unwrap_or_default().original_bytes(); + let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes(); if let Some((index, _)) = self.expected_creates.iter().find_position(|expected_create| { - expected_create.deployer == call.caller - && expected_create.create_scheme.eq(call.scheme.into()) + expected_create.deployer == call.caller() + && expected_create.create_scheme.eq(call.scheme().into()) && expected_create.bytecode == bytecode }) { @@ -1892,10 +1887,9 @@ impl Inspector> for Cheatcodes { } } -impl InspectorExt for Cheatcodes { - fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &CreateInputs) -> bool { - if let CreateScheme::Create2 { .. } = inputs.scheme { - let depth = ecx.journaled_state.depth(); +impl FoundryInspectorExt for Cheatcodes { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { + if let CreateScheme::Create2 { .. } = inputs.scheme() { let target_depth = if let Some(prank) = &self.get_prank(depth) { prank.depth } else if let Some(broadcast) = &self.broadcast { @@ -1933,10 +1927,14 @@ impl Cheatcodes { } #[cold] - fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn meter_gas_record( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut CTX, + ) { if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() { self.gas_metering.gas_records.iter_mut().for_each(|record| { - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth == record.depth { // Skip the first opcode of the first call frame as it includes the gas cost of // creating the snapshot. @@ -1997,7 +1995,11 @@ impl Cheatcodes { /// cache) from mapped source address to the target address. /// - generates arbitrary value and saves it in target address storage. #[cold] - fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn arbitrary_storage_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut CTX, + ) { let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD { (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address) } else { @@ -2049,7 +2051,11 @@ impl Cheatcodes { } #[cold] - fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn record_state_diffs( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut CTX, + ) { let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; match interpreter.bytecode.opcode() { op::SELFDESTRUCT => { @@ -2059,11 +2065,15 @@ impl Cheatcodes { // get previous balance, nonce and initialized status of the target account let target = try_or_return!(interpreter.stack.peek(0)); let target = Address::from_word(B256::from(target)); - let (db, journal, _) = ecx.as_db_env_and_journal(); - let (initialized, old_balance, old_nonce) = journal - .load_account(db, target) + let (initialized, old_balance, old_nonce) = ecx + .journal_mut() + .load_account(target) .map(|account| { - (account.info.exists(), account.info.balance, account.info.nonce) + ( + account.data.info.exists(), + account.data.info.balance, + account.data.info.nonce, + ) }) .unwrap_or_default(); @@ -2076,8 +2086,8 @@ impl Cheatcodes { // register access for the target account last.push(crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: target, @@ -2093,7 +2103,7 @@ impl Cheatcodes { deployedCode: Bytes::new(), storageAccesses: vec![], depth: ecx - .journaled_state + .journal() .depth() .try_into() .expect("journaled state depth exceeds u64"), @@ -2110,8 +2120,7 @@ impl Cheatcodes { // it's not set (zero value) let mut present_value = U256::ZERO; // Try to load the account and the slot's present value - let (db, journal, _) = ecx.as_db_env_and_journal(); - if journal.load_account(db, address).is_ok() + if ecx.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { present_value = previous.data; @@ -2124,11 +2133,8 @@ impl Cheatcodes { newValue: present_value.into(), reverted: false, }; - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); append_storage_access(last, access, curr_depth); } op::SSTORE => { @@ -2140,8 +2146,7 @@ impl Cheatcodes { // Try to load the account and the slot's previous value, otherwise, assume it's // not set (zero value) let mut previous_value = U256::ZERO; - let (db, journal, _) = ecx.as_db_env_and_journal(); - if journal.load_account(db, address).is_ok() + if ecx.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { previous_value = previous.data; @@ -2155,11 +2160,8 @@ impl Cheatcodes { newValue: value.into(), reverted: false, }; - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); append_storage_access(last, access, curr_depth); } @@ -2177,25 +2179,21 @@ impl Cheatcodes { let initialized; let balance; let nonce; - let (db, journal, _) = ecx.as_db_env_and_journal(); - if let Ok(acc) = journal.load_account(db, address) { - initialized = acc.info.exists(); - balance = acc.info.balance; - nonce = acc.info.nonce; + if let Ok(acc) = ecx.journal_mut().load_account(address) { + initialized = acc.data.info.exists(); + balance = acc.data.info.balance; + nonce = acc.data.info.nonce; } else { initialized = false; balance = U256::ZERO; nonce = 0; } - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); let account_access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: address, @@ -2507,29 +2505,61 @@ fn append_storage_access( } } +/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor. +fn cheatcode_of(_: &T) -> &'static spec::Cheatcode<'static> { + T::CHEATCODE +} + +fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.signature.split('(').next().unwrap() +} + +fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.id +} + +fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.signature +} + /// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch( +fn apply_dispatch( calls: &Vm::VmCalls, - ccx: &mut CheatsCtxt, + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { - let cheat = calls_as_dyn_cheatcode(calls); + // Extract metadata for logging/deprecation via CheatcodeDef. + macro_rules! get_cheatcode { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)* + } + }; + } + let cheat = vm_calls!(get_cheatcode); - let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()).entered(); - trace!(target: "cheatcodes", ?cheat, "applying"); + let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered(); + trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying"); - if let spec::Status::Deprecated(replacement) = *cheat.status() { - ccx.state.deprecated.insert(cheat.signature(), replacement); + if let spec::Status::Deprecated(replacement) = cheat.status { + ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement); } - // Apply the cheatcode. - let mut result = cheat.dyn_apply(ccx, executor); + // Monomorphized dispatch: calls apply_full directly, no trait objects. + macro_rules! dispatch { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)* + } + }; + } + let mut result = vm_calls!(dispatch); // Format the error message to include the cheatcode name. if let Err(e) = &mut result && e.is_str() { - let name = cheat.name(); + let name = cheatcode_name(cheat); // Skip showing the cheatcode name for: // - assertions: too verbose, and can already be inferred from the error message // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl` @@ -2549,17 +2579,6 @@ fn apply_dispatch( result } -fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { - macro_rules! as_dyn { - ($($variant:ident),*) => { - match calls { - $(Vm::VmCalls::$variant(cheat) => cheat,)* - } - }; - } - vm_calls!(as_dyn) -} - /// Helper function to check if frame execution will exit. fn will_exit(action: &InterpreterAction) -> bool { match action { diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index 58d1f2f90d7b7..b66eb31291933 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -1,7 +1,11 @@ -use super::Ecx; +use super::CheatsCtxExt; use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; -use revm::interpreter::{CreateInputs, CreateScheme}; +use revm::{ + context::ContextTr, + inspector::JournalExt, + interpreter::{CreateInputs, CreateScheme}, +}; /// Common behaviour of legacy and EOF create inputs. pub(crate) trait CommonCreateInput { @@ -12,27 +16,31 @@ pub(crate) trait CommonCreateInput { fn scheme(&self) -> Option; fn set_caller(&mut self, caller: Address); fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address; + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut CTX, + ) -> Address; } impl CommonCreateInput for &mut CreateInputs { fn caller(&self) -> Address { - self.caller + CreateInputs::caller(self) } fn gas_limit(&self) -> u64 { - self.gas_limit + CreateInputs::gas_limit(self) } fn value(&self) -> U256 { - self.value + CreateInputs::value(self) } fn init_code(&self) -> Bytes { - self.init_code.clone() + CreateInputs::init_code(self).clone() } fn scheme(&self) -> Option { - Some(self.scheme) + Some(CreateInputs::scheme(self)) } fn set_caller(&mut self, caller: Address) { - self.caller = caller; + CreateInputs::set_call(self, caller); } fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme) { let kind = match scheme { @@ -42,15 +50,16 @@ impl CommonCreateInput for &mut CreateInputs { }; debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address { - let old_nonce = ecx - .journaled_state - .state - .get(&self.caller) - .map(|acc| acc.info.nonce) - .unwrap_or_default(); + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut CTX, + ) -> Address { + let caller = CreateInputs::caller(self); + let old_nonce = + ecx.journal().evm_state().get(&caller).map(|acc| acc.info.nonce).unwrap_or_default(); let created_address = self.created_address(old_nonce); - cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); + cheatcodes.allow_cheatcodes_on_create(ecx, caller, created_address); created_address } } diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 02fa0777def82..7605359ace98a 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -12,133 +12,133 @@ use std::{ collections::{BTreeMap, BTreeSet}, }; -impl Cheatcode for keyExistsCall { +impl Cheatcode for keyExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } -impl Cheatcode for keyExistsJsonCall { +impl Cheatcode for keyExistsJsonCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } -impl Cheatcode for parseJson_0Call { +impl Cheatcode for parseJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json } = self; parse_json(json, "$", state.struct_defs()) } } -impl Cheatcode for parseJson_1Call { +impl Cheatcode for parseJson_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json(json, key, state.struct_defs()) } } -impl Cheatcode for parseJsonUintCall { +impl Cheatcode for parseJsonUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Uint(256)) } } -impl Cheatcode for parseJsonUintArrayCall { +impl Cheatcode for parseJsonUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } -impl Cheatcode for parseJsonIntCall { +impl Cheatcode for parseJsonIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Int(256)) } } -impl Cheatcode for parseJsonIntArrayCall { +impl Cheatcode for parseJsonIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } -impl Cheatcode for parseJsonBoolCall { +impl Cheatcode for parseJsonBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bool) } } -impl Cheatcode for parseJsonBoolArrayCall { +impl Cheatcode for parseJsonBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } -impl Cheatcode for parseJsonAddressCall { +impl Cheatcode for parseJsonAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Address) } } -impl Cheatcode for parseJsonAddressArrayCall { +impl Cheatcode for parseJsonAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } -impl Cheatcode for parseJsonStringCall { +impl Cheatcode for parseJsonStringCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::String) } } -impl Cheatcode for parseJsonStringArrayCall { +impl Cheatcode for parseJsonStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String))) } } -impl Cheatcode for parseJsonBytesCall { +impl Cheatcode for parseJsonBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bytes) } } -impl Cheatcode for parseJsonBytesArrayCall { +impl Cheatcode for parseJsonBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } -impl Cheatcode for parseJsonBytes32Call { +impl Cheatcode for parseJsonBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::FixedBytes(32)) } } -impl Cheatcode for parseJsonBytes32ArrayCall { +impl Cheatcode for parseJsonBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } -impl Cheatcode for parseJsonType_0Call { +impl Cheatcode for parseJsonType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, typeDescription } = self; parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?) @@ -146,7 +146,7 @@ impl Cheatcode for parseJsonType_0Call { } } -impl Cheatcode for parseJsonType_1Call { +impl Cheatcode for parseJsonType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?) @@ -154,7 +154,7 @@ impl Cheatcode for parseJsonType_1Call { } } -impl Cheatcode for parseJsonTypeArrayCall { +impl Cheatcode for parseJsonTypeArrayCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; @@ -162,14 +162,14 @@ impl Cheatcode for parseJsonTypeArrayCall { } } -impl Cheatcode for parseJsonKeysCall { +impl Cheatcode for parseJsonKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_keys(json, key) } } -impl Cheatcode for serializeJsonCall { +impl Cheatcode for serializeJsonCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, value } = self; *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?; @@ -177,56 +177,56 @@ impl Cheatcode for serializeJsonCall { } } -impl Cheatcode for serializeBool_0Call { +impl Cheatcode for serializeBool_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } -impl Cheatcode for serializeUint_0Call { +impl Cheatcode for serializeUint_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } -impl Cheatcode for serializeInt_0Call { +impl Cheatcode for serializeInt_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } -impl Cheatcode for serializeAddress_0Call { +impl Cheatcode for serializeAddress_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } -impl Cheatcode for serializeBytes32_0Call { +impl Cheatcode for serializeBytes32_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32)) } } -impl Cheatcode for serializeString_0Call { +impl Cheatcode for serializeString_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.clone().into()) } } -impl Cheatcode for serializeBytes_0Call { +impl Cheatcode for serializeBytes_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.to_vec().into()) } } -impl Cheatcode for serializeBool_1Call { +impl Cheatcode for serializeBool_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -238,7 +238,7 @@ impl Cheatcode for serializeBool_1Call { } } -impl Cheatcode for serializeUint_1Call { +impl Cheatcode for serializeUint_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -250,7 +250,7 @@ impl Cheatcode for serializeUint_1Call { } } -impl Cheatcode for serializeInt_1Call { +impl Cheatcode for serializeInt_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -262,7 +262,7 @@ impl Cheatcode for serializeInt_1Call { } } -impl Cheatcode for serializeAddress_1Call { +impl Cheatcode for serializeAddress_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -274,7 +274,7 @@ impl Cheatcode for serializeAddress_1Call { } } -impl Cheatcode for serializeBytes32_1Call { +impl Cheatcode for serializeBytes32_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -286,7 +286,7 @@ impl Cheatcode for serializeBytes32_1Call { } } -impl Cheatcode for serializeString_1Call { +impl Cheatcode for serializeString_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -298,7 +298,7 @@ impl Cheatcode for serializeString_1Call { } } -impl Cheatcode for serializeBytes_1Call { +impl Cheatcode for serializeBytes_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( @@ -312,7 +312,7 @@ impl Cheatcode for serializeBytes_1Call { } } -impl Cheatcode for serializeJsonType_0Call { +impl Cheatcode for serializeJsonType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeDescription, value } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; @@ -322,7 +322,7 @@ impl Cheatcode for serializeJsonType_0Call { } } -impl Cheatcode for serializeJsonType_1Call { +impl Cheatcode for serializeJsonType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, typeDescription, value } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; @@ -331,7 +331,7 @@ impl Cheatcode for serializeJsonType_1Call { } } -impl Cheatcode for serializeUintToHexCall { +impl Cheatcode for serializeUintToHexCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; let hex = format!("0x{value:x}"); @@ -339,7 +339,7 @@ impl Cheatcode for serializeUintToHexCall { } } -impl Cheatcode for writeJson_0Call { +impl Cheatcode for writeJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); @@ -348,7 +348,7 @@ impl Cheatcode for writeJson_0Call { } } -impl Cheatcode for writeJson_1Call { +impl Cheatcode for writeJson_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 1124f65dda9c4..18e2d0ba3b016 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -15,16 +15,16 @@ pub extern crate foundry_cheatcodes_spec as spec; #[macro_use] extern crate tracing; -use alloy_evm::eth::EthEvmContext; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; -use spec::Status; +use revm::context::{ContextTr, JournalTr}; pub use Vm::ForgeContext; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; pub use inspector::{ BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, + CheatsCtxExt, }; pub use spec::{CheatcodeDef, Vm}; @@ -64,7 +64,7 @@ mod toml; mod utils; /// Cheatcode implementation. -pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { +pub(crate) trait Cheatcode: CheatcodeDef { /// Applies this cheatcode to the given state. /// /// Implement this function if you don't need access to the EVM data. @@ -77,7 +77,7 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { self.apply(ccx.state) } @@ -85,60 +85,35 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the executor. #[inline(always)] - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let _ = executor; self.apply_stateful(ccx) } } -pub(crate) trait DynCheatcode: 'static + std::fmt::Debug { - fn cheatcode(&self) -> &'static spec::Cheatcode<'static>; - - fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result; -} - -impl DynCheatcode for T { - fn cheatcode(&self) -> &'static spec::Cheatcode<'static> { - Self::CHEATCODE - } - - fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { - self.apply_full(ccx, executor) - } -} - -impl dyn DynCheatcode { - pub(crate) fn name(&self) -> &'static str { - self.cheatcode().func.signature.split('(').next().unwrap() - } - - pub(crate) fn id(&self) -> &'static str { - self.cheatcode().func.id - } - - pub(crate) fn signature(&self) -> &'static str { - self.cheatcode().func.signature - } - - pub(crate) fn status(&self) -> &Status<'static> { - &self.cheatcode().status - } -} - -/// The cheatcode context, used in `Cheatcode`. -pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { +/// The cheatcode context. +pub struct CheatsCtxt<'a, CTX> { /// The cheatcodes inspector state. - pub(crate) state: &'cheats mut Cheatcodes, - /// The EVM data. - pub(crate) ecx: &'evm mut EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, + pub(crate) state: &'a mut Cheatcodes, + /// The EVM context. + pub(crate) ecx: &'a mut CTX, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. pub(crate) gas_limit: u64, } -impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { - type Target = EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; +/// Placeholder context type for cheatcodes that don't need EVM context access +/// (i.e., they only use `apply`, not `apply_stateful` or `apply_full`). +#[cfg(test)] +pub(crate) type AnyCtx = (); + +impl std::ops::Deref for CheatsCtxt<'_, CTX> { + type Target = CTX; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -146,20 +121,20 @@ impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { } } -impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> { +impl std::ops::DerefMut for CheatsCtxt<'_, CTX> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.ecx + self.ecx } } -impl CheatsCtxt<'_, '_, '_, '_> { +impl CheatsCtxt<'_, CTX> { pub(crate) fn ensure_not_precompile(&self, address: &Address) -> Result<()> { if self.is_precompile(address) { Err(precompile_error(address)) } else { Ok(()) } } pub(crate) fn is_precompile(&self, address: &Address) -> bool { - self.ecx.journaled_state.warm_addresses.precompiles().contains(address) + self.ecx.journal().precompile_addresses().contains(address) } } diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 5375630e996bb..4ce408760f7a7 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -4,106 +4,130 @@ use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_primitives::{Address, B256, U256, Uint}; use alloy_rpc_types::Authorization; -use alloy_signer::SignerSync; +use alloy_signer::{Signer, SignerSync}; use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; +use foundry_evm_core::{backend::DatabaseExt, env::FoundryContextExt}; use foundry_wallets::{WalletSigner, wallet_multi::MultiWallet}; use parking_lot::Mutex; use revm::{ bytecode::Bytecode, - context::JournalTr, + context::{Cfg, ContextTr, JournalTr, Transaction}, context_interface::transaction::SignedAuthorization, + inspector::JournalExt, primitives::{KECCAK_EMPTY, hardfork::SpecId}, }; use std::sync::Arc; -impl Cheatcode for broadcast_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for broadcast_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; broadcast(ccx, None, true) } } -impl Cheatcode for broadcast_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for broadcast_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } -impl Cheatcode for broadcast_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for broadcast_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } -impl Cheatcode for attachDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for attachDelegation_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { signedDelegation } = self; attach_delegation(ccx, signedDelegation, false) } } -impl Cheatcode for attachDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for attachDelegation_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { signedDelegation, crossChain } = self; attach_delegation(ccx, signedDelegation, *crossChain) } } -impl Cheatcode for signDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signDelegation_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, false) } } -impl Cheatcode for signDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signDelegation_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, false) } } -impl Cheatcode for signDelegation_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signDelegation_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey, crossChain } = *self; sign_delegation(ccx, privateKey, implementation, None, crossChain, false) } } -impl Cheatcode for signAndAttachDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signAndAttachDelegation_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, true) } } -impl Cheatcode for signAndAttachDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signAndAttachDelegation_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, true) } } -impl Cheatcode for signAndAttachDelegation_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for signAndAttachDelegation_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { implementation, privateKey, crossChain } = *self; sign_delegation(ccx, privateKey, implementation, None, crossChain, true) } } /// Helper function to attach an EIP-7702 delegation. -fn attach_delegation( - ccx: &mut CheatsCtxt, +fn attach_delegation>( + ccx: &mut CheatsCtxt<'_, CTX>, delegation: &SignedDelegation, cross_chain: bool, ) -> Result { let SignedDelegation { v, r, s, nonce, implementation } = delegation; // Set chain id to 0 if universal deployment is preferred. // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#protection-from-malleability-cross-chain - let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg().chain_id()) }; let auth = Authorization { address: *implementation, nonce: *nonce, chain_id }; let signed_auth = SignedAuthorization::new_unchecked( @@ -119,8 +143,8 @@ fn attach_delegation( /// Helper function to sign and attach (if needed) an EIP-7702 delegation. /// Uses the provided nonce, otherwise retrieves and increments the nonce of the EOA. -fn sign_delegation( - ccx: &mut CheatsCtxt, +fn sign_delegation>( + ccx: &mut CheatsCtxt<'_, CTX>, private_key: Uint<256, 4>, implementation: Address, nonce: Option, @@ -131,16 +155,19 @@ fn sign_delegation( let nonce = if let Some(nonce) = nonce { nonce } else { - let authority_acc = ccx.ecx.journaled_state.load_account(signer.address())?; + let account_nonce = { + let authority_acc = ccx.ecx.journal_mut().load_account(signer.address())?; + authority_acc.info.nonce + }; // Calculate next nonce considering existing active delegations next_delegation_nonce( &ccx.state.active_delegations, signer.address(), &ccx.state.broadcast, - authority_acc.data.info.nonce, + account_nonce, ) }; - let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg().chain_id()) }; let auth = Authorization { address: implementation, nonce, chain_id }; let sig = signer.sign_hash_sync(&auth.signature_hash())?; @@ -189,15 +216,21 @@ fn next_delegation_nonce( } } -fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> { +fn write_delegation>( + ccx: &mut CheatsCtxt<'_, CTX>, + auth: SignedAuthorization, +) -> Result<()> { let authority = auth.recover_authority().map_err(|e| format!("{e}"))?; - let authority_acc = ccx.ecx.journaled_state.load_account(authority)?; + let account_nonce = { + let authority_acc = ccx.ecx.journal_mut().load_account(authority)?; + authority_acc.info.nonce + }; let expected_nonce = next_delegation_nonce( &ccx.state.active_delegations, authority, &ccx.state.broadcast, - authority_acc.data.info.nonce, + account_nonce, ); if expected_nonce != auth.nonce { @@ -211,24 +244,26 @@ fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<( if auth.address.is_zero() { // Set empty code if the delegation address of authority is 0x. // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#behavior. - ccx.ecx.journaled_state.set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); + ccx.ecx.journal_mut().set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); } else { let bytecode = Bytecode::new_eip7702(*auth.address()); - ccx.ecx.journaled_state.set_code(authority, bytecode); + ccx.ecx.journal_mut().set_code(authority, bytecode); } Ok(()) } -impl Cheatcode for attachBlobCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for attachBlobCall +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blob } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`attachBlob` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(blob); - let sidecar_variant = if ccx.ecx.cfg.spec < SpecId::OSAKA { + let sidecar_variant = if ccx.ecx.cfg().spec().into() < SpecId::OSAKA { sidecar.build_4844().map_err(|e| format!("{e}"))?.into() } else { sidecar.build_7594().map_err(|e| format!("{e}"))?.into() @@ -238,29 +273,35 @@ impl Cheatcode for attachBlobCall { } } -impl Cheatcode for startBroadcast_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startBroadcast_0Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; broadcast(ccx, None, false) } } -impl Cheatcode for startBroadcast_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startBroadcast_1Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } -impl Cheatcode for startBroadcast_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode + for startBroadcast_2Call +{ + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } -impl Cheatcode for stopBroadcastCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopBroadcastCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); @@ -270,8 +311,8 @@ impl Cheatcode for stopBroadcastCall { } } -impl Cheatcode for getWalletsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for getWalletsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let wallets = ccx.state.wallets().signers().unwrap_or_default(); Ok(wallets.abi_encode()) } @@ -331,17 +372,18 @@ impl Wallets { /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. pub fn signers(&self) -> Result> { - Ok(self.inner.lock().multi_wallet.signers()?.keys().copied().collect()) + let mut wallets = self.inner.lock(); + let (signers, browser) = wallets.multi_wallet.signers()?; + Ok(signers.keys().copied().chain(browser.map(|b| b.address())).collect()) } /// Number of signers in the [MultiWallet]. pub fn len(&self) -> usize { let mut inner = self.inner.lock(); - let signers = inner.multi_wallet.signers(); - if signers.is_err() { - return 0; + match inner.multi_wallet.signers() { + Ok((signers, browser)) => signers.len() + usize::from(browser.is_some()), + Err(_) => 0, } - signers.unwrap().len() } /// Whether the [MultiWallet] is empty. @@ -351,8 +393,12 @@ impl Wallets { } /// Sets up broadcasting from a script using `new_origin` as the sender. -fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bool) -> Result { - let depth = ccx.ecx.journaled_state.depth(); +fn broadcast>( + ccx: &mut CheatsCtxt<'_, CTX>, + new_origin: Option<&Address>, + single_call: bool, +) -> Result { + let depth = ccx.ecx.journal().depth(); ensure!( ccx.state.get_prank(depth).is_none(), "you have an active prank; broadcasting and pranks are not compatible" @@ -366,21 +412,21 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo if let Some(provided_sender) = wallets.provided_sender { new_origin = Some(provided_sender); } else { - let signers = wallets.multi_wallet.signers()?; + let (signers, _) = wallets.multi_wallet.signers()?; if signers.len() == 1 { let address = signers.keys().next().unwrap(); new_origin = Some(*address); } } } - let new_origin = new_origin.unwrap_or(ccx.ecx.tx.caller); + let new_origin = new_origin.unwrap_or(ccx.ecx.tx().caller()); // Ensure new origin is loaded and touched. let _ = journaled_account(ccx.ecx, new_origin)?; let broadcast = Broadcast { new_origin, original_caller: ccx.caller, - original_origin: ccx.ecx.tx.caller, + original_origin: ccx.ecx.tx().caller(), depth, single_call, deploy_from_code: false, @@ -393,7 +439,11 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo /// Sets up broadcasting from a script with the sender derived from `private_key`. /// Adds this private key to `state`'s `wallets` vector to later be used for signing /// if broadcast is successful. -fn broadcast_key(ccx: &mut CheatsCtxt, private_key: &U256, single_call: bool) -> Result { +fn broadcast_key>( + ccx: &mut CheatsCtxt<'_, CTX>, + private_key: &U256, + single_call: bool, +) -> Result { let wallet = super::crypto::parse_wallet(private_key)?; let new_origin = wallet.address(); diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index ca3c1a88b7fee..e96cb6ea071b1 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -6,7 +6,7 @@ use alloy_primitives::{U256, hex}; use alloy_sol_types::SolValue; // address -impl Cheatcode for toString_0Call { +impl Cheatcode for toString_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) @@ -14,7 +14,7 @@ impl Cheatcode for toString_0Call { } // bytes -impl Cheatcode for toString_1Call { +impl Cheatcode for toString_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) @@ -22,7 +22,7 @@ impl Cheatcode for toString_1Call { } // bytes32 -impl Cheatcode for toString_2Call { +impl Cheatcode for toString_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) @@ -30,7 +30,7 @@ impl Cheatcode for toString_2Call { } // bool -impl Cheatcode for toString_3Call { +impl Cheatcode for toString_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) @@ -38,7 +38,7 @@ impl Cheatcode for toString_3Call { } // uint256 -impl Cheatcode for toString_4Call { +impl Cheatcode for toString_4Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) @@ -46,84 +46,84 @@ impl Cheatcode for toString_4Call { } // int256 -impl Cheatcode for toString_5Call { +impl Cheatcode for toString_5Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } -impl Cheatcode for parseBytesCall { +impl Cheatcode for parseBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bytes) } } -impl Cheatcode for parseAddressCall { +impl Cheatcode for parseAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Address) } } -impl Cheatcode for parseUintCall { +impl Cheatcode for parseUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Uint(256)) } } -impl Cheatcode for parseIntCall { +impl Cheatcode for parseIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Int(256)) } } -impl Cheatcode for parseBytes32Call { +impl Cheatcode for parseBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::FixedBytes(32)) } } -impl Cheatcode for parseBoolCall { +impl Cheatcode for parseBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bool) } } -impl Cheatcode for toLowercaseCall { +impl Cheatcode for toLowercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_lowercase().abi_encode()) } } -impl Cheatcode for toUppercaseCall { +impl Cheatcode for toUppercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_uppercase().abi_encode()) } } -impl Cheatcode for trimCall { +impl Cheatcode for trimCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.trim().abi_encode()) } } -impl Cheatcode for replaceCall { +impl Cheatcode for replaceCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, from, to } = self; Ok(input.replace(from, to).abi_encode()) } } -impl Cheatcode for splitCall { +impl Cheatcode for splitCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, delimiter } = self; let parts: Vec<&str> = input.split(delimiter).collect(); @@ -131,14 +131,14 @@ impl Cheatcode for splitCall { } } -impl Cheatcode for indexOfCall { +impl Cheatcode for indexOfCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, key } = self; Ok(input.find(key).map(U256::from).unwrap_or(U256::MAX).abi_encode()) } } -impl Cheatcode for containsCall { +impl Cheatcode for containsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { subject, search } = self; Ok(subject.contains(search).abi_encode()) @@ -193,10 +193,10 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option { - if !s.starts_with("0x") && hex::check_raw(s) { - return Some(Err("missing hex prefix (\"0x\") for hex string")); - } + | DynSolType::Bytes + if !s.starts_with("0x") && hex::check_raw(s) => + { + return Some(Err("missing hex prefix (\"0x\") for hex string")); } _ => {} } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 683c5131bcddf..cae52f829e446 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -6,6 +6,7 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; use foundry_evm_core::constants::MAGIC_SKIP; +use revm::context::{ContextTr, JournalTr}; use std::str::FromStr; pub(crate) mod assert; @@ -13,28 +14,28 @@ pub(crate) mod assume; pub(crate) mod expect; pub(crate) mod revert_handlers; -impl Cheatcode for breakpoint_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for breakpoint_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } -impl Cheatcode for breakpoint_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for breakpoint_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } } -impl Cheatcode for getFoundryVersionCall { +impl Cheatcode for getFoundryVersionCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(SEMVER_VERSION.abi_encode()) } } -impl Cheatcode for rpcUrlCall { +impl Cheatcode for rpcUrlCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { rpcAlias } = self; let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode(); @@ -42,21 +43,21 @@ impl Cheatcode for rpcUrlCall { } } -impl Cheatcode for rpcUrlsCall { +impl Cheatcode for rpcUrlsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } -impl Cheatcode for rpcUrlStructsCall { +impl Cheatcode for rpcUrlStructsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } -impl Cheatcode for sleepCall { +impl Cheatcode for sleepCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { duration } = self; let sleep_duration = std::time::Duration::from_millis(duration.saturating_to()); @@ -65,20 +66,20 @@ impl Cheatcode for sleepCall { } } -impl Cheatcode for skip_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for skip_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest } = *self; skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) } } -impl Cheatcode for skip_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for skip_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest, reason } = self; if *skipTest { // Skip should not work if called deeper than at test level. // Since we're not returning the magic skip bytes, this will cause a test failure. - ensure!(ccx.ecx.journaled_state.depth <= 1, "`skip` can only be used at test level"); + ensure!(ccx.ecx.journal().depth() <= 1, "`skip` can only be used at test level"); Err([MAGIC_SKIP, reason.as_bytes()].concat().into()) } else { Ok(Default::default()) @@ -86,14 +87,14 @@ impl Cheatcode for skip_1Call { } } -impl Cheatcode for getChain_0Call { +impl Cheatcode for getChain_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainAlias } = self; get_chain(state, chainAlias) } } -impl Cheatcode for getChain_1Call { +impl Cheatcode for getChain_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainId } = self; // Convert the chainId to a string and use the existing get_chain function diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index e1483644b3f40..f74737674832f 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -2,11 +2,11 @@ use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{I256, U256, U512}; use foundry_evm_core::{ abi::console::{format_units_int, format_units_uint}, - backend::GLOBAL_FAIL_SLOT, + backend::{DatabaseExt, GLOBAL_FAIL_SLOT}, constants::CHEATCODE_ADDRESS, }; use itertools::Itertools; -use revm::context::JournalTr; +use revm::context::{ContextTr, JournalTr}; use std::{borrow::Cow, fmt}; const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); @@ -187,8 +187,8 @@ impl EqRelAssertionError { type ComparisonResult<'a, T> = Result<(), ComparisonAssertionError<'a, T>>; #[cold] -fn handle_assertion_result( - ccx: &mut CheatsCtxt, +fn handle_assertion_result, E>( + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, err: E, error_formatter: Option<&dyn Fn(&E) -> String>, @@ -203,16 +203,16 @@ fn handle_assertion_result( handle_assertion_result_mono(ccx, executor, msg) } -fn handle_assertion_result_mono( - ccx: &mut CheatsCtxt, +fn handle_assertion_result_mono>( + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, msg: Cow<'_, str>, ) -> Result { if ccx.state.config.assertions_revert { Err(msg.into_owned().into()) } else { - executor.console_log(ccx, &msg); - ccx.ecx.journaled_state.sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; + executor.console_log(ccx.state, &msg); + ccx.ecx.journal_mut().sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; Ok(Default::default()) } } @@ -245,10 +245,10 @@ macro_rules! impl_assertions { }; (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr) => { - impl crate::Cheatcode for $no_error { + impl> crate::Cheatcode for $no_error { fn apply_full( &self, - ccx: &mut CheatsCtxt, + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg),* } = self; @@ -259,10 +259,10 @@ macro_rules! impl_assertions { } } - impl crate::Cheatcode for $with_error { + impl> crate::Cheatcode for $with_error { fn apply_full( &self, - ccx: &mut CheatsCtxt, + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg,)* error } = self; diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs index 98126bcee7621..1febc9a66fd03 100644 --- a/crates/cheatcodes/src/test/assume.rs +++ b/crates/cheatcodes/src/test/assume.rs @@ -1,6 +1,7 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; use alloy_primitives::Address; use foundry_evm_core::constants::MAGIC_ASSUME; +use revm::context::{ContextTr, JournalTr}; use spec::Vm::{ PotentialRevert, assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, }; @@ -43,36 +44,36 @@ impl AcceptableRevertParameters { } } -impl Cheatcode for assumeCall { +impl Cheatcode for assumeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { condition } = self; if *condition { Ok(Default::default()) } else { Err(Error::from(MAGIC_ASSUME)) } } } -impl Cheatcode for assumeNoRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth, vec![]) +impl Cheatcode for assumeNoRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + assume_no_revert(ccx.state, ccx.ecx.journal().depth(), vec![]) } } -impl Cheatcode for assumeNoRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for assumeNoRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { potentialRevert } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth, + ccx.ecx.journal().depth(), vec![AcceptableRevertParameters::from(potentialRevert)], ) } } -impl Cheatcode for assumeNoRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for assumeNoRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { potentialReverts } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth, + ccx.ecx.journal().depth(), potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), ) } diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index bdbfc583b5d04..a12979765d8f3 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -13,7 +13,7 @@ use alloy_primitives::{ use foundry_common::{abi::get_indexed_event, fmt::format_token}; use foundry_evm_traces::DecodedCallLog; use revm::{ - context::JournalTr, + context::{ContextTr, JournalTr}, interpreter::{ InstructionResult, Interpreter, InterpreterAction, interpreter_types::LoopControl, }, @@ -162,28 +162,28 @@ impl CreateScheme { } } -impl Cheatcode for expectCall_0Call { +impl Cheatcode for expectCall_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data } = self; expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount) } } -impl Cheatcode for expectCall_1Call { +impl Cheatcode for expectCall_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data, count } = self; expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count) } } -impl Cheatcode for expectCall_2Call { +impl Cheatcode for expectCall_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data } = self; expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount) } } -impl Cheatcode for expectCall_3Call { +impl Cheatcode for expectCall_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data, count } = self; expect_call( @@ -199,7 +199,7 @@ impl Cheatcode for expectCall_3Call { } } -impl Cheatcode for expectCall_4Call { +impl Cheatcode for expectCall_4Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data } = self; expect_call( @@ -215,7 +215,7 @@ impl Cheatcode for expectCall_4Call { } } -impl Cheatcode for expectCall_5Call { +impl Cheatcode for expectCall_5Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data, count } = self; expect_call( @@ -231,7 +231,7 @@ impl Cheatcode for expectCall_5Call { } } -impl Cheatcode for expectCallMinGas_0Call { +impl Cheatcode for expectCallMinGas_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data } = self; expect_call( @@ -247,7 +247,7 @@ impl Cheatcode for expectCallMinGas_0Call { } } -impl Cheatcode for expectCallMinGas_1Call { +impl Cheatcode for expectCallMinGas_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data, count } = self; expect_call( @@ -263,12 +263,12 @@ impl Cheatcode for expectCallMinGas_1Call { } } -impl Cheatcode for expectEmit_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, @@ -277,12 +277,12 @@ impl Cheatcode for expectEmit_0Call { } } -impl Cheatcode for expectEmit_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, @@ -291,26 +291,26 @@ impl Cheatcode for expectEmit_1Call { } } -impl Cheatcode for expectEmit_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, 1) } } -impl Cheatcode for expectEmit_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), false, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, 1) } } -impl Cheatcode for expectEmit_4Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_4Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, @@ -319,12 +319,12 @@ impl Cheatcode for expectEmit_4Call { } } -impl Cheatcode for expectEmit_5Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_5Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, @@ -333,33 +333,26 @@ impl Cheatcode for expectEmit_5Call { } } -impl Cheatcode for expectEmit_6Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_6Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { count } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, count) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, count) } } -impl Cheatcode for expectEmit_7Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmit_7Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter, count } = *self; - expect_emit( - ccx.state, - ccx.ecx.journaled_state.depth(), - [true; 5], - Some(emitter), - false, - count, - ) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, count) } } -impl Cheatcode for expectEmitAnonymous_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmitAnonymous_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], None, true, @@ -368,12 +361,12 @@ impl Cheatcode for expectEmitAnonymous_0Call { } } -impl Cheatcode for expectEmitAnonymous_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmitAnonymous_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), true, @@ -382,48 +375,48 @@ impl Cheatcode for expectEmitAnonymous_1Call { } } -impl Cheatcode for expectEmitAnonymous_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmitAnonymous_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, true, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, true, 1) } } -impl Cheatcode for expectEmitAnonymous_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectEmitAnonymous_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), true, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), true, 1) } } -impl Cheatcode for expectCreateCall { +impl Cheatcode for expectCreateCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create) } } -impl Cheatcode for expectCreate2Call { +impl Cheatcode for expectCreate2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create2) } } -impl Cheatcode for expectRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, 1) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, 1) } } -impl Cheatcode for expectRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -432,43 +425,27 @@ impl Cheatcode for expectRevert_1Call { } } -impl Cheatcode for expectRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; - expect_revert( - ccx.state, - Some(revertData), - ccx.ecx.journaled_state.depth(), - false, - false, - None, - 1, - ) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, 1) } } -impl Cheatcode for expectRevert_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { reverter } = self; - expect_revert( - ccx.state, - None, - ccx.ecx.journaled_state.depth(), - false, - false, - Some(*reverter), - 1, - ) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), 1) } } -impl Cheatcode for expectRevert_4Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_4Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -477,13 +454,13 @@ impl Cheatcode for expectRevert_4Call { } } -impl Cheatcode for expectRevert_5Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_5Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -492,20 +469,20 @@ impl Cheatcode for expectRevert_5Call { } } -impl Cheatcode for expectRevert_6Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_6Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { count } = self; - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, *count) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, *count) } } -impl Cheatcode for expectRevert_7Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_7Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -514,13 +491,13 @@ impl Cheatcode for expectRevert_7Call { } } -impl Cheatcode for expectRevert_8Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_8Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -529,13 +506,13 @@ impl Cheatcode for expectRevert_8Call { } } -impl Cheatcode for expectRevert_9Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_9Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { reverter, count } = self; expect_revert( ccx.state, None, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -544,13 +521,13 @@ impl Cheatcode for expectRevert_9Call { } } -impl Cheatcode for expectRevert_10Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_10Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -559,13 +536,13 @@ impl Cheatcode for expectRevert_10Call { } } -impl Cheatcode for expectRevert_11Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectRevert_11Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -574,13 +551,13 @@ impl Cheatcode for expectRevert_11Call { } } -impl Cheatcode for expectPartialRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectPartialRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, true, None, @@ -589,13 +566,13 @@ impl Cheatcode for expectPartialRevert_0Call { } } -impl Cheatcode for expectPartialRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectPartialRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, true, Some(*reverter), @@ -604,19 +581,19 @@ impl Cheatcode for expectPartialRevert_1Call { } } -impl Cheatcode for _expectCheatcodeRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None, 1) +impl Cheatcode for _expectCheatcodeRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), true, false, None, 1) } } -impl Cheatcode for _expectCheatcodeRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for _expectCheatcodeRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), true, false, None, @@ -625,40 +602,32 @@ impl Cheatcode for _expectCheatcodeRevert_1Call { } } -impl Cheatcode for _expectCheatcodeRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for _expectCheatcodeRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; - expect_revert( - ccx.state, - Some(revertData), - ccx.ecx.journaled_state.depth(), - true, - false, - None, - 1, - ) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), true, false, None, 1) } } -impl Cheatcode for expectSafeMemoryCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectSafeMemoryCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth().try_into()?) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journal().depth().try_into()?) } } -impl Cheatcode for stopExpectSafeMemoryCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for stopExpectSafeMemoryCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; - ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth().try_into()?); + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journal().depth().try_into()?); Ok(Default::default()) } } -impl Cheatcode for expectSafeMemoryCallCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for expectSafeMemoryCallCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, (ccx.ecx.journaled_state.depth() + 1).try_into()?) + expect_safe_memory(ccx.state, min, max, (ccx.ecx.journal().depth() + 1).try_into()?) } } @@ -932,10 +901,7 @@ pub(crate) fn handle_expect_emit( } // Maybe match source address. - if event_to_fill_or_check - .address - .is_some_and(|addr| addr.to_checksum(None) != log.address.to_checksum(None)) - { + if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) { event_to_fill_or_check.mismatch_error = Some(format!( "log emitter mismatch: expected={:#x}, got={:#x}", event_to_fill_or_check.address.unwrap(), diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs index 7fb8210c88d66..53c8911287185 100644 --- a/crates/cheatcodes/src/toml.rs +++ b/crates/cheatcodes/src/toml.rs @@ -15,14 +15,14 @@ use foundry_config::fs_permissions::FsAccessKind; use serde_json::Value as JsonValue; use toml::Value as TomlValue; -impl Cheatcode for keyExistsTomlCall { +impl Cheatcode for keyExistsTomlCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; check_json_key_exists(&toml_to_json_string(toml)?, key) } } -impl Cheatcode for parseToml_0Call { +impl Cheatcode for parseToml_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml } = self; parse_toml( @@ -33,7 +33,7 @@ impl Cheatcode for parseToml_0Call { } } -impl Cheatcode for parseToml_1Call { +impl Cheatcode for parseToml_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml( @@ -44,105 +44,105 @@ impl Cheatcode for parseToml_1Call { } } -impl Cheatcode for parseTomlUintCall { +impl Cheatcode for parseTomlUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Uint(256)) } } -impl Cheatcode for parseTomlUintArrayCall { +impl Cheatcode for parseTomlUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } -impl Cheatcode for parseTomlIntCall { +impl Cheatcode for parseTomlIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Int(256)) } } -impl Cheatcode for parseTomlIntArrayCall { +impl Cheatcode for parseTomlIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } -impl Cheatcode for parseTomlBoolCall { +impl Cheatcode for parseTomlBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bool) } } -impl Cheatcode for parseTomlBoolArrayCall { +impl Cheatcode for parseTomlBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } -impl Cheatcode for parseTomlAddressCall { +impl Cheatcode for parseTomlAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Address) } } -impl Cheatcode for parseTomlAddressArrayCall { +impl Cheatcode for parseTomlAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } -impl Cheatcode for parseTomlStringCall { +impl Cheatcode for parseTomlStringCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::String) } } -impl Cheatcode for parseTomlStringArrayCall { +impl Cheatcode for parseTomlStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String))) } } -impl Cheatcode for parseTomlBytesCall { +impl Cheatcode for parseTomlBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bytes) } } -impl Cheatcode for parseTomlBytesArrayCall { +impl Cheatcode for parseTomlBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } -impl Cheatcode for parseTomlBytes32Call { +impl Cheatcode for parseTomlBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) } } -impl Cheatcode for parseTomlBytes32ArrayCall { +impl Cheatcode for parseTomlBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } -impl Cheatcode for parseTomlType_0Call { +impl Cheatcode for parseTomlType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, typeDescription } = self; parse_toml_coerce( @@ -157,7 +157,7 @@ impl Cheatcode for parseTomlType_0Call { } } -impl Cheatcode for parseTomlType_1Call { +impl Cheatcode for parseTomlType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; parse_toml_coerce( @@ -172,7 +172,7 @@ impl Cheatcode for parseTomlType_1Call { } } -impl Cheatcode for parseTomlTypeArrayCall { +impl Cheatcode for parseTomlTypeArrayCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; let ty = resolve_type( @@ -183,14 +183,14 @@ impl Cheatcode for parseTomlTypeArrayCall { } } -impl Cheatcode for parseTomlKeysCall { +impl Cheatcode for parseTomlKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_keys(toml, key) } } -impl Cheatcode for writeToml_0Call { +impl Cheatcode for writeToml_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let value = @@ -201,7 +201,7 @@ impl Cheatcode for writeToml_0Call { } } -impl Cheatcode for writeToml_1Call { +impl Cheatcode for writeToml_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 2c59b58b1a083..1b19833102ed5 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -12,7 +12,10 @@ use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use foundry_evm_fuzz::strategies::BoundMutator; use proptest::prelude::Strategy; use rand::{Rng, RngCore, seq::SliceRandom}; -use revm::context::JournalTr; +use revm::{ + context::{ContextTr, JournalTr}, + inspector::JournalExt, +}; use std::path::PathBuf; /// Contains locations of traces ignored via cheatcodes. @@ -29,7 +32,7 @@ pub struct IgnoredTraces { pub last_pause_call: Option<(usize, usize)>, } -impl Cheatcode for labelCall { +impl Cheatcode for labelCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account, newLabel } = self; state.labels.insert(*account, newLabel.clone()); @@ -37,7 +40,7 @@ impl Cheatcode for labelCall { } } -impl Cheatcode for getLabelCall { +impl Cheatcode for getLabelCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account } = self; Ok(match state.labels.get(account) { @@ -47,7 +50,7 @@ impl Cheatcode for getLabelCall { } } -impl Cheatcode for computeCreateAddressCall { +impl Cheatcode for computeCreateAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { nonce, deployer } = self; ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64"); @@ -55,28 +58,28 @@ impl Cheatcode for computeCreateAddressCall { } } -impl Cheatcode for computeCreate2Address_0Call { +impl Cheatcode for computeCreate2Address_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash, deployer } = self; Ok(deployer.create2(salt, initCodeHash).abi_encode()) } } -impl Cheatcode for computeCreate2Address_1Call { +impl Cheatcode for computeCreate2Address_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash } = self; Ok(DEFAULT_CREATE2_DEPLOYER.create2(salt, initCodeHash).abi_encode()) } } -impl Cheatcode for ensNamehashCall { +impl Cheatcode for ensNamehashCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(namehash(name).abi_encode()) } } -impl Cheatcode for bound_0Call { +impl Cheatcode for bound_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = U256::bound(current, min, max, state.test_runner()) else { @@ -86,7 +89,7 @@ impl Cheatcode for bound_0Call { } } -impl Cheatcode for bound_1Call { +impl Cheatcode for bound_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = I256::bound(current, min, max, state.test_runner()) else { @@ -96,27 +99,27 @@ impl Cheatcode for bound_1Call { } } -impl Cheatcode for randomUint_0Call { +impl Cheatcode for randomUint_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { random_uint(state, None, None) } } -impl Cheatcode for randomUint_1Call { +impl Cheatcode for randomUint_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { min, max } = *self; random_uint(state, None, Some((min, max))) } } -impl Cheatcode for randomUint_2Call { +impl Cheatcode for randomUint_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_uint(state, Some(bits), None) } } -impl Cheatcode for randomAddressCall { +impl Cheatcode for randomAddressCall { fn apply(&self, state: &mut Cheatcodes) -> Result { Ok(DynSolValue::type_strategy(&DynSolType::Address) .new_tree(state.test_runner()) @@ -126,27 +129,27 @@ impl Cheatcode for randomAddressCall { } } -impl Cheatcode for randomInt_0Call { +impl Cheatcode for randomInt_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { random_int(state, None) } } -impl Cheatcode for randomInt_1Call { +impl Cheatcode for randomInt_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_int(state, Some(bits)) } } -impl Cheatcode for randomBoolCall { +impl Cheatcode for randomBoolCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_bool: bool = state.rng().random(); Ok(rand_bool.abi_encode()) } } -impl Cheatcode for randomBytesCall { +impl Cheatcode for randomBytesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { len } = *self; ensure!( @@ -159,24 +162,24 @@ impl Cheatcode for randomBytesCall { } } -impl Cheatcode for randomBytes4Call { +impl Cheatcode for randomBytes4Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u32 = state.rng().next_u32(); Ok(B32::from(rand_u32).abi_encode()) } } -impl Cheatcode for randomBytes8Call { +impl Cheatcode for randomBytes8Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u64 = state.rng().next_u64(); Ok(B64::from(rand_u64).abi_encode()) } } -impl Cheatcode for pauseTracingCall { +impl Cheatcode for pauseTracingCall { fn apply_full( &self, - ccx: &mut crate::CheatsCtxt, + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { @@ -196,10 +199,10 @@ impl Cheatcode for pauseTracingCall { } } -impl Cheatcode for resumeTracingCall { +impl Cheatcode for resumeTracingCall { fn apply_full( &self, - ccx: &mut crate::CheatsCtxt, + ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { @@ -219,7 +222,7 @@ impl Cheatcode for resumeTracingCall { } } -impl Cheatcode for interceptInitcodeCall { +impl Cheatcode for interceptInitcodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; if !state.intercept_next_create_call { @@ -231,8 +234,8 @@ impl Cheatcode for interceptInitcodeCall { } } -impl Cheatcode for setArbitraryStorage_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for setArbitraryStorage_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, false); @@ -240,8 +243,8 @@ impl Cheatcode for setArbitraryStorage_0Call { } } -impl Cheatcode for setArbitraryStorage_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for setArbitraryStorage_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, overwrite } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, *overwrite); @@ -249,8 +252,8 @@ impl Cheatcode for setArbitraryStorage_1Call { } } -impl Cheatcode for copyStorageCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl> Cheatcode for copyStorageCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { from, to } = self; ensure!( @@ -258,11 +261,11 @@ impl Cheatcode for copyStorageCall { "target address cannot have arbitrary storage" ); - if let Ok(from_account) = ccx.ecx.journaled_state.load_account(*from) { + if let Ok(from_account) = ccx.ecx.journal_mut().load_account(*from) { let from_storage = from_account.storage.clone(); - if ccx.ecx.journaled_state.load_account(*to).is_ok() { + if ccx.ecx.journal_mut().load_account(*to).is_ok() { // SAFETY: We ensured the account was already loaded. - ccx.ecx.journaled_state.state.get_mut(to).unwrap().storage = from_storage; + ccx.ecx.journal_mut().evm_state_mut().get_mut(to).unwrap().storage = from_storage; if let Some(arbitrary_storage) = &mut ccx.state.arbitrary_storage { arbitrary_storage.mark_copy(from, to); } @@ -273,7 +276,7 @@ impl Cheatcode for copyStorageCall { } } -impl Cheatcode for sortCall { +impl Cheatcode for sortCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { array } = self; @@ -284,7 +287,7 @@ impl Cheatcode for sortCall { } } -impl Cheatcode for shuffleCall { +impl Cheatcode for shuffleCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { array } = self; @@ -296,8 +299,8 @@ impl Cheatcode for shuffleCall { } } -impl Cheatcode for setSeedCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for setSeedCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { seed } = self; ccx.state.set_seed(*seed); Ok(Default::default()) @@ -349,7 +352,7 @@ fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { .abi_encode()) } -impl Cheatcode for eip712HashType_0Call { +impl Cheatcode for eip712HashType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeNameOrDefinition } = self; @@ -359,7 +362,7 @@ impl Cheatcode for eip712HashType_0Call { } } -impl Cheatcode for eip712HashType_1Call { +impl Cheatcode for eip712HashType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bindingsPath, typeName } = self; @@ -370,7 +373,7 @@ impl Cheatcode for eip712HashType_1Call { } } -impl Cheatcode for eip712HashStruct_0Call { +impl Cheatcode for eip712HashStruct_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeNameOrDefinition, abiEncodedData } = self; @@ -381,7 +384,7 @@ impl Cheatcode for eip712HashStruct_0Call { } } -impl Cheatcode for eip712HashStruct_1Call { +impl Cheatcode for eip712HashStruct_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bindingsPath, typeName, abiEncodedData } = self; @@ -392,7 +395,7 @@ impl Cheatcode for eip712HashStruct_1Call { } } -impl Cheatcode for eip712HashTypedDataCall { +impl Cheatcode for eip712HashTypedDataCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { jsonData } = self; let typed_data: TypedData = serde_json::from_str(jsonData)?; @@ -494,7 +497,7 @@ fn get_struct_hash(primary: &str, type_def: &String, abi_encoded_data: &Bytes) - Ok(keccak256(&bytes_to_hash).to_vec()) } -impl Cheatcode for toRlpCall { +impl Cheatcode for toRlpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; @@ -505,7 +508,7 @@ impl Cheatcode for toRlpCall { } } -impl Cheatcode for fromRlpCall { +impl Cheatcode for fromRlpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { rlp } = self; diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs index 84eace8f0017e..07652b3395f19 100644 --- a/crates/cheatcodes/src/version.rs +++ b/crates/cheatcodes/src/version.rs @@ -4,14 +4,14 @@ use foundry_common::version::SEMVER_VERSION; use semver::Version; use std::cmp::Ordering; -impl Cheatcode for foundryVersionCmpCall { +impl Cheatcode for foundryVersionCmpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| (cmp as i8).abi_encode()) } } -impl Cheatcode for foundryVersionAtLeastCall { +impl Cheatcode for foundryVersionAtLeastCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| cmp.is_ge().abi_encode()) diff --git a/crates/chisel/src/args.rs b/crates/chisel/src/args.rs index e3af6d5c91203..8200f4fc44f63 100644 --- a/crates/chisel/src/args.rs +++ b/crates/chisel/src/args.rs @@ -14,6 +14,8 @@ use yansi::Paint; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Chisel::parse(); args.global.init()?; args.global.tokio_runtime().block_on(run_command(args)) diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index abb6be8693585..6ea9859643ed4 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -213,15 +213,19 @@ impl SessionSource { let executor = ExecutorBuilder::new() .inspectors(|stack| { - stack.chisel_state(final_pc).trace_mode(TraceMode::Call).cheatcodes( - CheatsConfig::new( - &self.config.foundry_config, - self.config.evm_opts.clone(), - None, - None, + stack + .logs(self.config.foundry_config.live_logs) + .chisel_state(final_pc) + .trace_mode(TraceMode::Call) + .cheatcodes( + CheatsConfig::new( + &self.config.foundry_config, + self.config.evm_opts.clone(), + None, + None, + ) + .into(), ) - .into(), - ) }) .gas_limit(self.config.evm_opts.gas_limit()) .spec_id(self.config.foundry_config.evm_spec_id()) @@ -521,16 +525,10 @@ impl Type { pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), pt::Expression::HexNumberLiteral(_, s, _) => { match s.parse::
() { - Ok(addr) => { - if *s == addr.to_checksum(None) { - Some(Self::Builtin(DynSolType::Address)) - } else { - Some(Self::Builtin(DynSolType::Uint(256))) - } - }, - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) + Ok(addr) if *s == addr.to_checksum(None) => { + Some(Self::Builtin(DynSolType::Address)) } + _ => Some(Self::Builtin(DynSolType::Uint(256))), } } @@ -667,6 +665,7 @@ impl Type { // Type members, like array, bytes etc #[expect(clippy::single_match)] + #[allow(clippy::collapsible_match)] match &self { Self::Access(inner, access) => { if let Some(ty) = inner.as_ref().clone().try_as_ethabi(None) { diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index a23a927261172..1002b5533f116 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -169,16 +169,12 @@ impl SolidityHelper { } }, - Literal { kind: Str { terminated, .. } } => { - if !terminated { - return ValidationResult::Incomplete; - } + Literal { kind: Str { terminated, .. } } if !terminated => { + return ValidationResult::Incomplete; } - BlockComment { terminated, .. } => { - if !terminated { - return ValidationResult::Incomplete; - } + BlockComment { terminated, .. } if !terminated => { + return ValidationResult::Incomplete; } _ => {} diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs index 6fb38b6ca1805..aa9c00f471aa7 100644 --- a/crates/chisel/tests/it/repl/mod.rs +++ b/crates/chisel/tests/it/repl/mod.rs @@ -264,3 +264,13 @@ repl_test!(uninitialized_variables, |repl| { repl.sendln("y"); repl.expect("Data: 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"); }); + +repl_test!(chisel_can_run_with_live_logs_flag, "--live-logs", init = true, |repl| { + repl.sendln("import {console} from 'forge-std/Script.sol';"); + repl.sendln("console.log('Hello, World!');"); + repl.expect("Hello, World!"); + + repl.sendln("console.log('Goodbye, World!');"); + repl.expect("Hello, World!"); // old log is also printed + repl.expect("Goodbye, World!"); +}); diff --git a/crates/cli-markdown/Cargo.toml b/crates/cli-markdown/Cargo.toml new file mode 100644 index 0000000000000..7f039685d2cf1 --- /dev/null +++ b/crates/cli-markdown/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "foundry-cli-markdown" +description = "Generate Markdown documentation for clap CLIs" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +clap = { version = "4", features = ["env"] } + +[dev-dependencies] +clap = { version = "4", features = ["derive"] } +pretty_assertions = "1" diff --git a/crates/cli-markdown/src/lib.rs b/crates/cli-markdown/src/lib.rs new file mode 100644 index 0000000000000..732e22bfd193d --- /dev/null +++ b/crates/cli-markdown/src/lib.rs @@ -0,0 +1,582 @@ +//! Generate Markdown documentation for clap command-line tools. +//! +//! This is a fork of [`clap-markdown`](https://crates.io/crates/clap-markdown) with the following +//! enhancements: +//! - Support for grouped options by help heading ([PR #48](https://github.com/ConnorGray/clap-markdown/pull/48)) +//! - Show environment variable names for arguments ([PR #50](https://github.com/ConnorGray/clap-markdown/pull/50)) +//! - Add version information to generated Markdown ([PR #52](https://github.com/ConnorGray/clap-markdown/pull/52)) + +use std::{ + collections::BTreeMap, + fmt::{self, Write}, +}; + +use clap::builder::PossibleValue; + +/// Options to customize the structure of the output Markdown document. +#[non_exhaustive] +pub struct MarkdownOptions { + title: Option, + show_footer: bool, + show_table_of_contents: bool, + show_aliases: bool, +} + +impl MarkdownOptions { + /// Construct a default instance of `MarkdownOptions`. + pub fn new() -> Self { + Self { title: None, show_footer: true, show_table_of_contents: true, show_aliases: true } + } + + /// Set a custom title to use in the generated document. + pub fn title(mut self, title: String) -> Self { + self.title = Some(title); + self + } + + /// Whether to show the default footer advertising `clap-markdown`. + pub fn show_footer(mut self, show: bool) -> Self { + self.show_footer = show; + self + } + + /// Whether to show the default table of contents. + pub fn show_table_of_contents(mut self, show: bool) -> Self { + self.show_table_of_contents = show; + self + } + + /// Whether to show aliases for arguments and commands. + pub fn show_aliases(mut self, show: bool) -> Self { + self.show_aliases = show; + self + } +} + +impl Default for MarkdownOptions { + fn default() -> Self { + Self::new() + } +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown() -> String { + let command = C::command(); + help_markdown_command(&command) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_custom(options: &MarkdownOptions) -> String { + let command = C::command(); + help_markdown_command_custom(&command, options) +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown_command(command: &clap::Command) -> String { + help_markdown_command_custom(command, &Default::default()) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_command_custom(command: &clap::Command, options: &MarkdownOptions) -> String { + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, command, options); + buffer +} + +/// Format the help information for `command` as Markdown and print it. +/// +/// Output is printed to the standard output. +#[allow(clippy::disallowed_macros)] +pub fn print_help_markdown() { + let command = C::command(); + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, &command, &Default::default()); + println!("{buffer}"); +} + +fn write_help_markdown(buffer: &mut String, command: &clap::Command, options: &MarkdownOptions) { + let title_name = get_canonical_name(command); + + let title = match options.title { + Some(ref title) => title.to_owned(), + None => format!("Command-Line Help for `{title_name}`"), + }; + writeln!(buffer, "# {title}\n",).unwrap(); + + writeln!( + buffer, + "This document contains the help content for the `{title_name}` command-line program.\n", + ) + .unwrap(); + + // Write the version if available (PR #52) + if let Some(version) = command.get_version() { + let version_str = version.to_string(); + + if version_str.contains('\n') { + // Multi-line version: use a code block + writeln!(buffer, "**Version:**\n\n```\n{}\n```\n", version_str.trim()).unwrap(); + } else { + // Single-line version: use inline code + writeln!(buffer, "**Version:** `{version_str}`\n").unwrap(); + } + } + + // Write the table of contents + if options.show_table_of_contents { + writeln!(buffer, "**Command Overview:**\n").unwrap(); + build_table_of_contents_markdown(buffer, Vec::new(), command, 0).unwrap(); + writeln!(buffer).unwrap(); + } + + // Write the commands/subcommands sections + build_command_markdown(buffer, Vec::new(), command, 0, options).unwrap(); + + // Write the footer + if options.show_footer { + write!( + buffer, + r#"
+ + + This document was generated automatically by + clap-markdown. + +"# + ) + .unwrap(); + } +} + +fn build_table_of_contents_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path; + command_path.push(title_name); + command_path + }; + + writeln!(buffer, "* [`{}`↴](#{})", command_path.join(" "), command_path.join("-"),)?; + + for subcommand in command.get_subcommands() { + build_table_of_contents_markdown(buffer, command_path.clone(), subcommand, _depth + 1)?; + } + + Ok(()) +} + +fn build_command_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, + options: &MarkdownOptions, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path.clone(); + command_path.push(title_name); + command_path + }; + + // Write the markdown heading + writeln!(buffer, "## `{}`\n", command_path.join(" "))?; + + if let Some(long_about) = command.get_long_about() { + writeln!(buffer, "{long_about}\n")?; + } else if let Some(about) = command.get_about() { + writeln!(buffer, "{about}\n")?; + } + + if let Some(help) = command.get_before_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_before_help() { + writeln!(buffer, "{help}\n")?; + } + + writeln!( + buffer, + "**Usage:** `{}{}`\n", + if parent_command_path.is_empty() { + String::new() + } else { + let mut s = parent_command_path.join(" "); + s.push(' '); + s + }, + command.clone().render_usage().to_string().replace("Usage: ", "") + )?; + + if options.show_aliases { + let aliases = command.get_visible_aliases().collect::>(); + if let Some(aliases_str) = get_alias_string(&aliases) { + writeln!( + buffer, + "**{}:** {aliases_str}\n", + pluralize(aliases.len(), "Command Alias", "Command Aliases") + )?; + } + } + + if let Some(help) = command.get_after_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_after_help() { + writeln!(buffer, "{help}\n")?; + } + + // Subcommands + if command.get_subcommands().next().is_some() { + writeln!(buffer, "###### **Subcommands:**\n")?; + + for subcommand in command.get_subcommands() { + if subcommand.is_hide_set() { + continue; + } + + let title_name = get_canonical_name(subcommand); + let about = match subcommand.get_about() { + Some(about) => about.to_string(), + None => String::new(), + }; + + writeln!(buffer, "* `{title_name}` — {about}",)?; + } + + writeln!(buffer)?; + } + + // Arguments (positional) + if command.get_positionals().next().is_some() { + writeln!(buffer, "###### **Arguments:**\n")?; + + for pos_arg in command.get_positionals() { + write_arg_markdown(buffer, pos_arg)?; + } + + writeln!(buffer)?; + } + + // Options (grouped by help heading) - PR #48 + let non_pos: Vec<_> = + command.get_arguments().filter(|arg| !arg.is_positional() && !arg.is_hide_set()).collect(); + + if !non_pos.is_empty() { + // Group arguments by help heading + let mut grouped_args: BTreeMap<&str, Vec<&clap::Arg>> = BTreeMap::new(); + + for arg in non_pos { + let heading = arg.get_help_heading().unwrap_or("Options"); + grouped_args.entry(heading).or_default().push(arg); + } + + // Write each group with its heading + for (heading, args) in grouped_args { + writeln!(buffer, "###### **{heading}:**\n")?; + + for arg in args { + write_arg_markdown(buffer, arg)?; + } + + writeln!(buffer)?; + } + } + + // Include extra space between commands + write!(buffer, "\n\n")?; + + for subcommand in command.get_subcommands() { + build_command_markdown(buffer, command_path.clone(), subcommand, _depth + 1, options)?; + } + + Ok(()) +} + +fn write_arg_markdown(buffer: &mut String, arg: &clap::Arg) -> fmt::Result { + // Markdown list item + write!(buffer, "* ")?; + + let value_name: String = match arg.get_value_names() { + Some([name, ..]) => name.as_str().to_owned(), + Some([]) => unreachable!("clap Arg::get_value_names() returned Some(..) of empty list"), + None => arg.get_id().to_string().to_ascii_uppercase(), + }; + + match (arg.get_short(), arg.get_long()) { + (Some(short), Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short}`, `--{long} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`, `--{long}`")? + } + } + (Some(short), None) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`")? + } + } + (None, Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`--{long} <{value_name}>`")? + } else { + write!(buffer, "`--{long}`")? + } + } + (None, None) => { + debug_assert!( + arg.is_positional(), + "unexpected non-positional Arg with neither short nor long name: {arg:?}" + ); + write!(buffer, "`<{value_name}>`",)?; + } + } + + if let Some(aliases) = arg.get_visible_aliases().as_deref() + && let Some(aliases_str) = get_alias_string(aliases) + { + write!(buffer, " [{}: {aliases_str}]", pluralize(aliases.len(), "alias", "aliases"))?; + } + + if let Some(help) = arg.get_long_help() { + buffer.push_str(&indent(&help.to_string(), " — ", " ")) + } else if let Some(short_help) = arg.get_help() { + writeln!(buffer, " — {short_help}")?; + } else { + writeln!(buffer)?; + } + + // Arg default values + if !arg.get_default_values().is_empty() { + let default_values: String = arg + .get_default_values() + .iter() + .map(|value| format!("`{}`", value.to_string_lossy())) + .collect::>() + .join(", "); + + if arg.get_default_values().len() > 1 { + writeln!(buffer, "\n Default values: {default_values}")?; + } else { + writeln!(buffer, "\n Default value: {default_values}")?; + } + } + + // Arg possible values + let possible_values: Vec = + arg.get_possible_values().into_iter().filter(|pv| !pv.is_hide_set()).collect(); + + if !possible_values.is_empty() && !matches!(arg.get_action(), clap::ArgAction::SetTrue) { + let any_have_help: bool = possible_values.iter().any(|pv| pv.get_help().is_some()); + + if any_have_help { + let text: String = possible_values + .iter() + .map(|pv| match pv.get_help() { + Some(help) => { + format!(" - `{}`:\n {}\n", pv.get_name(), help) + } + None => format!(" - `{}`\n", pv.get_name()), + }) + .collect::>() + .join(""); + + writeln!(buffer, "\n Possible values:\n{text}")?; + } else { + let text: String = possible_values + .iter() + .map(|pv| format!("`{}`", pv.get_name())) + .collect::>() + .join(", "); + + writeln!(buffer, "\n Possible values: {text}\n")?; + } + } + + // Arg environment variable (PR #50) + if !arg.is_hide_env_set() + && let Some(env) = arg.get_env() + { + writeln!(buffer, "\n Environment variable: `{}`", env.to_string_lossy())?; + } + + Ok(()) +} + +/// Utility function to get the canonical name of a command. +fn get_canonical_name(command: &clap::Command) -> String { + command + .get_display_name() + .or_else(|| command.get_bin_name()) + .map(|name| name.to_owned()) + .unwrap_or_else(|| command.get_name().to_owned()) +} + +/// Indents non-empty lines. The output always ends with a newline. +fn indent(s: &str, first: &str, rest: &str) -> String { + if s.is_empty() { + return "\n".to_string(); + } + let mut result = String::new(); + let mut first_line = true; + + for line in s.lines() { + if !line.is_empty() { + result.push_str(if first_line { first } else { rest }); + result.push_str(line); + first_line = false; + } + result.push('\n'); + } + result +} + +fn get_alias_string(aliases: &[&str]) -> Option { + if aliases.is_empty() { + return None; + } + + Some(aliases.iter().map(|alias| format!("`{alias}`")).collect::>().join(", ")) +} + +fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { + if count == 1 { singular } else { plural } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Arg, Command}; + use pretty_assertions::assert_eq; + + #[test] + fn test_indent() { + assert_eq!(&indent("Header\n\nMore info", "___", "~~~~"), "___Header\n\n~~~~More info\n"); + assert_eq!( + &indent("Header\n\nMore info\n", "___", "~~~~"), + &indent("Header\n\nMore info", "___", "~~~~"), + ); + assert_eq!(&indent("", "___", "~~~~"), "\n"); + assert_eq!(&indent("\n", "___", "~~~~"), "\n"); + } + + #[test] + fn test_version_output() { + let app = Command::new("test-app").version("1.2.3").about("A test application"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:** `1.2.3`"), "Should contain version"); + } + + #[test] + fn test_multiline_version() { + let multi_line_version = "my-cli 1.2.3 (abc123)\nmy-lib 2.0.0 (789xyz)"; + + let app = Command::new("my-cli").version(multi_line_version).about("Multi-version CLI"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:**\n\n```"), "Should use code block for multi-line"); + } + + #[test] + fn test_env_var_output() { + let app = Command::new("env-test").about("Test env var output").arg( + Arg::new("config") + .short('c') + .long("config") + .env("CONFIG_PATH") + .help("Path to config file"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!( + markdown.contains("Environment variable: `CONFIG_PATH`"), + "Should show env var. Output: {markdown}" + ); + } + + #[test] + fn test_grouped_options() { + let app = Command::new("grouped-app") + .about("Test app with grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .help_heading("General Options") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("input") + .short('i') + .long("input") + .help("Input file") + .help_heading("File Options") + .value_name("FILE"), + ) + .arg( + Arg::new("format") + .short('f') + .long("format") + .help("Output format") + .value_name("FORMAT"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **File Options:**"), "Should have File Options heading"); + assert!( + markdown.contains("###### **General Options:**"), + "Should have General Options heading" + ); + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + } + + #[test] + fn test_no_grouped_options_backward_compatibility() { + let app = Command::new("simple-app") + .about("Test app without grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("output").short('o').long("output").help("Output file").value_name("FILE"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + assert!(markdown.contains("`-v`, `--verbose`"), "Should have verbose option"); + assert!(markdown.contains("`-o`, `--output `"), "Should have output option"); + } +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d9acf1de83d09..e1ec43c33df19 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] foundry-block-explorers.workspace = true +foundry-cli-markdown.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 020b7e953068f..763466d21185d 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,5 +1,4 @@ use eyre::EyreHandler; -use itertools::Itertools; use std::{error::Error, fmt}; /// A custom context type for Foundry specific error reporting via `eyre`. @@ -23,8 +22,7 @@ impl Handler { impl EyreHandler for Handler { fn display(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Display; - foundry_common::errors::dedup_chain(error).into_iter().format("; ").fmt(f) + f.write_str(&foundry_common::errors::display_chain(error)) } fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 6499d693ddf40..7af7c20fb021a 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -93,6 +93,11 @@ pub struct EvmArgs { #[serde(skip)] pub ffi: bool, + /// Whether to show `console.log` outputs in realtime during script/test execution + #[arg(long)] + #[serde(skip)] + pub live_logs: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. #[arg(long)] #[serde(skip)] @@ -157,6 +162,10 @@ impl Provider for EvmArgs { dict.insert("ffi".to_string(), self.ffi.into()); } + if self.live_logs { + dict.insert("live_logs".to_string(), self.live_logs.into()); + } + if self.isolate { dict.insert("isolate".to_string(), self.isolate.into()); } diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index 8bfc13c00001f..fee75a5d34e87 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -51,6 +51,17 @@ pub struct GlobalArgs { } impl GlobalArgs { + /// Check if `--markdown-help` was passed and print CLI reference as Markdown, then exit. + /// + /// This must be called **before** parsing arguments, since commands with required + /// subcommands would fail parsing before the flag is checked. + pub fn check_markdown_help() { + if std::env::args().any(|arg| arg == "--markdown-help") { + foundry_cli_markdown::print_help_markdown::(); + std::process::exit(0); + } + } + /// Initialize the global options. pub fn init(&self) -> eyre::Result<()> { // Set the global shell. diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 4016c86f2a427..15a5de678272a 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -30,6 +30,14 @@ pub struct RpcOpts { #[arg(short = 'k', long = "insecure", default_value = "false")] pub accept_invalid_certs: bool, + /// Disable automatic proxy detection. + /// + /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where + /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment + /// variables and system proxy settings will be ignored. + #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")] + pub no_proxy: bool, + /// Use the Flashbots RPC URL with fast mode (). /// /// This shares the transaction privately with all registered builders. @@ -118,6 +126,12 @@ impl RpcOpts { if self.accept_invalid_certs { dict.insert("eth_rpc_accept_invalid_certs".into(), true.into()); } + if self.no_proxy { + dict.insert("eth_rpc_no_proxy".into(), true.into()); + } + if self.curl { + dict.insert("eth_rpc_curl".into(), true.into()); + } dict } diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs index a33b2b5ee7e25..cbcd96a268166 100644 --- a/crates/cli/src/utils/abi.rs +++ b/crates/cli/src/utils/abi.rs @@ -38,8 +38,12 @@ pub async fn parse_function_args>( let args = resolve_name_args(&args, provider).await; + // Try to decode as hex calldata first, otherwise treat as function signature if let Ok(data) = hex::decode(sig) { return Ok((data, None)); + } else if sig.starts_with("0x") || sig.starts_with("0X") { + let e = hex::decode(sig).unwrap_err(); + eyre::bail!("Invalid hex calldata '{}': {e}", sig); } let func = if sig.contains('(') { diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index b0f3661881843..722a80fbe2d3c 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -125,13 +125,16 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool { | NamedChain::KaruraTestnet | NamedChain::Mantle | NamedChain::MantleSepolia + | NamedChain::Metis | NamedChain::Monad | NamedChain::MonadTestnet | NamedChain::Moonbase | NamedChain::Moonbeam | NamedChain::MoonbeamDev | NamedChain::Moonriver - | NamedChain::Metis + | NamedChain::PolkadotTestnet + | NamedChain::Kusama + | NamedChain::Polkadot ); } false diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 5bac965e11c92..92fa39343a761 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -87,7 +87,7 @@ pub fn subscriber() { let registry = tracing_subscriber::Registry::default().with(env_filter()); #[cfg(feature = "tracy")] let registry = registry.with(tracing_tracy::TracyLayer::default()); - registry.with(tracing_subscriber::fmt::layer()).init() + registry.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)).init() } fn env_filter() -> tracing_subscriber::EnvFilter { @@ -101,47 +101,14 @@ fn env_filter() -> tracing_subscriber::EnvFilter { /// Returns a [RetryProvider] instantiated using [Config]'s RPC settings. pub fn get_provider(config: &Config) -> Result { - get_provider_builder(config, false)?.build() -} - -/// Returns a [RetryProvider] with curl mode option. -/// -/// When `curl_mode` is true, the provider will print equivalent curl commands -/// to stdout instead of executing RPC requests. -pub fn get_provider_with_curl(config: &Config, curl_mode: bool) -> Result { - get_provider_builder(config, curl_mode)?.build() + get_provider_builder(config)?.build() } /// Returns a [ProviderBuilder] instantiated using [Config] values. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -/// -/// When `curl_mode` is true, the provider will print equivalent curl commands -/// to stdout instead of executing RPC requests. -pub fn get_provider_builder(config: &Config, curl_mode: bool) -> Result { - let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = ProviderBuilder::new(url.as_ref()); - - builder = builder.accept_invalid_certs(config.eth_rpc_accept_invalid_certs); - builder = builder.curl_mode(curl_mode); - - if let Ok(chain) = config.chain.unwrap_or_default().try_into() { - builder = builder.chain(chain); - } - - if let Some(jwt) = config.get_rpc_jwt_secret()? { - builder = builder.jwt(jwt.as_ref()); - } - - if let Some(rpc_timeout) = config.eth_rpc_timeout { - builder = builder.timeout(Duration::from_secs(rpc_timeout)); - } - - if let Some(rpc_headers) = config.eth_rpc_headers.clone() { - builder = builder.headers(rpc_headers); - } - - Ok(builder) +pub fn get_provider_builder(config: &Config) -> Result { + ProviderBuilder::from_config(config) } pub async fn get_chain

(chain: Option, provider: P) -> Result @@ -314,7 +281,7 @@ impl CommandUtils for Command { }; if !msg.is_empty() { err.push(':'); - err.push(if msg.lines().count() == 0 { ' ' } else { '\n' }); + err.push(if msg.lines().count() == 1 { ' ' } else { '\n' }); err.push_str(&msg); } Err(eyre::eyre!(err)) @@ -509,7 +476,7 @@ impl<'a> Git<'a> { } pub fn is_repo_root(self) -> Result { - self.cmd().args(["rev-parse", "--show-cdup"]).exec().map(|out| out.stdout.is_empty()) + self.cmd().args(["rev-parse", "--show-cdup"]).get_stdout_lossy().map(|s| s.is_empty()) } pub fn is_clean(self) -> Result { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 0152f353bc8b3..75776743d1132 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -16,6 +16,7 @@ workspace = true foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true +foundry-config.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } @@ -79,7 +80,6 @@ flate2.workspace = true [build-dependencies] chrono.workspace = true vergen = { workspace = true, features = ["build", "emit_and_set"] } -vergen-git2 = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/common/build.rs b/crates/common/build.rs index df57e9fe81888..7d1ba0f085309 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -2,16 +2,14 @@ use chrono::DateTime; use std::{error::Error, path::PathBuf}; -use vergen::{BuildBuilder, Emitter}; -use vergen_git2::Git2Builder; fn main() -> Result<(), Box> { println!("cargo:rerun-if-changed=build.rs"); - let build = BuildBuilder::default().build_date(true).build_timestamp(true).build()?; - let git2 = Git2Builder::default().describe(false, true, None).sha(false).build()?; + let build = vergen::Build::builder().build_date(true).build_timestamp(true).build(); + let git = vergen::Gitcl::builder().describe(false, true, None).sha(false).build(); - Emitter::default().add_instructions(&build)?.add_instructions(&git2)?.emit_and_set()?; + vergen::Emitter::new().add_instructions(&build)?.add_instructions(&git)?.emit_and_set()?; let sha = env_var("VERGEN_GIT_SHA"); let sha_short = &sha[..10]; diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 5adfea559f6fe..0c213d77de8e8 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -20,6 +20,7 @@ eyre.workspace = true # ui alloy-consensus.workspace = true +op-alloy-consensus.workspace = true alloy-network.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } alloy-serde.workspace = true @@ -32,6 +33,9 @@ yansi.workspace = true # foundry primitives foundry-primitives.workspace = true +# Tempo +tempo-alloy.workspace = true + [dev-dependencies] foundry-macros.workspace = true similar-asserts.workspace = true diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 6affaeec0438a..1d0336c7c7e78 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -15,4 +15,7 @@ mod exp; pub use exp::{format_int_exp, format_uint_exp, to_exp_notation}; mod ui; -pub use ui::{EthValue, UIfmt, get_pretty_block_attr, get_pretty_tx_attr}; +pub use ui::{ + EthValue, UIfmt, UIfmtReceiptExt, get_pretty_block_attr, get_pretty_receipt_attr, + get_pretty_tx_attr, +}; diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index a7e583b6696e1..2cd999adca536 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1,20 +1,32 @@ //! Helper trait and functions to format Ethereum types. use alloy_consensus::{ - Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, Typed2718, + BlockHeader, Eip658Value, Receipt, ReceiptWithBloom, Signed, Transaction as TxTrait, TxEip1559, + TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718, + transaction::TxHashRef, }; use alloy_network::{ - AnyHeader, AnyReceiptEnvelope, AnyRpcBlock, AnyRpcTransaction, AnyTransactionReceipt, - AnyTxEnvelope, ReceiptResponse, + AnyReceiptEnvelope, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, + AnyTxEnvelope, BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse, +}; +use alloy_primitives::{ + Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex, }; -use alloy_primitives::{Address, Bloom, Bytes, FixedBytes, I256, U8, U64, U256, Uint, hex}; use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; -use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxReceipt}; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxReceipt}; +use op_alloy_consensus::TxDeposit; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; +use tempo_alloy::{ + primitives::{ + AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, + transaction::{Call, PrimitiveSignature}, + }, + rpc::{TempoHeaderResponse, TempoTransactionReceipt}, +}; /// length of the name column for pretty formatting `{:>20}{value}` const NAME_COLUMN_LEN: usize = 20usize; @@ -114,12 +126,6 @@ impl UIfmt for Bloom { } } -impl UIfmt for TxType { - fn pretty(&self) -> String { - (*self as u8).to_string() - } -} - impl UIfmt for Vec { fn pretty(&self) -> String { self[..].pretty() @@ -159,6 +165,12 @@ impl UIfmt for Eip658Value { } } +impl UIfmt for Signature { + fn pretty(&self) -> String { + format!("[r: {}, s: {}, y_parity: {}]", self.r(), self.s(), self.v()) + } +} + impl UIfmt for AnyTransactionReceipt { fn pretty(&self) -> String { let Self { @@ -261,13 +273,13 @@ transactionIndex: {}", } } -impl UIfmt for Block> { +impl UIfmt for Block { fn pretty(&self) -> String { format!( " {} transactions: {}", - pretty_block_basics(self), + pretty_generic_header_response(&self.header), self.transactions.pretty() ) } @@ -311,184 +323,261 @@ impl UIfmt for AccessListItem { } } -impl UIfmt for TxEnvelope { +impl UIfmt for TxLegacy { fn pretty(&self) -> String { - match &self { - Self::Eip2930(tx) => format!( - " -accessList {} + format!( + " chainId {} -gasLimit {} +nonce {} gasPrice {} -hash {} -input {} +gasLimit {} +to {} +value {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_price.pretty(), + self.gas_limit.pretty(), + self.to().pretty(), + self.value.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip2930 { + fn pretty(&self) -> String { + format!( + " +chainId {} nonce {} -r {} -s {} +gasPrice {} +gasLimit {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip1559(tx) => format!( - " accessList {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_price.pretty(), + self.gas_limit.pretty(), + self.to().pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip1559 { + fn pretty(&self) -> String { + format!( + " chainId {} +nonce {} gasLimit {} -hash {} -input {} maxFeePerGas {} maxPriorityFeePerGas {} +to {} +value {} +accessList {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_limit.pretty(), + self.max_fee_per_gas.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.to().pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip4844Variant { + fn pretty(&self) -> String { + use alloy_consensus::TxEip4844; + let tx: &TxEip4844 = match self { + Self::TxEip4844(tx) => tx, + Self::TxEip4844WithSidecar(tx) => tx.tx(), + }; + format!( + " +chainId {} nonce {} -r {} -s {} +gasLimit {} +maxFeePerGas {} +maxPriorityFeePerGas {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip4844(tx) => format!( - " accessList {} blobVersionedHashes {} +maxFeePerBlobGas {} +input {}", + tx.chain_id.pretty(), + tx.nonce.pretty(), + tx.gas_limit.pretty(), + tx.max_fee_per_gas.pretty(), + tx.max_priority_fee_per_gas.pretty(), + tx.to.pretty(), + tx.value.pretty(), + tx.access_list.pretty(), + tx.blob_versioned_hashes.pretty(), + tx.max_fee_per_blob_gas.pretty(), + tx.input.pretty(), + ) + } +} + +impl UIfmt for TxEip7702 { + fn pretty(&self) -> String { + format!( + " chainId {} +nonce {} gasLimit {} -hash {} -input {} -maxFeePerBlobGas {} maxFeePerGas {} maxPriorityFeePerGas {} -nonce {} -r {} -s {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.blob_versioned_hashes().unwrap_or(&[]).pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_blob_gas().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip7702(tx) => format!( - " accessList {} authorizationList {} -chainId {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_limit.pretty(), + self.max_fee_per_gas.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.to.pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.authorization_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxDeposit { + fn pretty(&self) -> String { + format!( + " +sourceHash {} +from {} +to {} +mint {} +value {} gasLimit {} -hash {} -input {} -maxFeePerGas {} +isSystemTransaction {} +input {}", + self.source_hash.pretty(), + self.from.pretty(), + self.to().pretty(), + self.mint.pretty(), + self.value.pretty(), + self.gas_limit.pretty(), + self.is_system_transaction, + self.input.pretty(), + ) + } +} + +impl UIfmt for Call { + fn pretty(&self) -> String { + format!( + "to: {}, value: {}, input: {}", + self.to.into_to().pretty(), + self.value.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TempoTransaction { + fn pretty(&self) -> String { + format!( + " +chainId {} +feeToken {} maxPriorityFeePerGas {} +maxFeePerGas {} +gasLimit {} +calls {} +accessList {} +nonceKey {} nonce {} -r {} -s {} -to {} +feePayerSignature {} +validBefore {} +validAfter {}", + self.chain_id.pretty(), + self.fee_token.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.max_fee_per_gas.pretty(), + self.gas_limit.pretty(), + self.calls.pretty(), + self.access_list.pretty(), + self.nonce_key.pretty(), + self.nonce.pretty(), + self.fee_payer_signature.pretty(), + self.valid_after.pretty(), + self.valid_before.pretty(), + ) + } +} + +impl UIfmt for TempoSignature { + fn pretty(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } +} + +impl UIfmt for AASigned { + fn pretty(&self) -> String { + format!( + " +hash {} type {} -value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.authorization_list() - .as_ref() - .map(|l| l.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - _ => format!( - " -gas {} -gasPrice {} +{} +tempoSignature {}", + self.hash().pretty(), + self.tx().ty(), + self.tx().pretty().trim_start(), + self.signature().pretty(), + ) + } +} + +impl UIfmt for Signed +where + Self: TxHashRef, +{ + fn pretty(&self) -> String { + format!( + " hash {} -input {} -nonce {} +type {} +{} r {} s {} -to {} -type {} -v {} -value {}", - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - self.as_legacy() - .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) - .unwrap_or_default(), - self.as_legacy() - .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) - .unwrap_or_default(), - self.to().pretty(), - self.ty(), - self.as_legacy() - .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) - .unwrap_or_default(), - self.value().pretty(), - ), +yParity {}", + self.tx_hash().pretty(), + self.ty(), + self.tx().pretty().trim_start(), + FixedBytes::from(self.signature().r()).pretty(), + FixedBytes::from(self.signature().s()).pretty(), + (if self.signature().v() { 1u64 } else { 0 }).pretty(), + ) + } +} + +impl UIfmt for TxEnvelope { + fn pretty(&self) -> String { + match self { + Self::Legacy(tx) => tx.pretty(), + Self::Eip2930(tx) => tx.pretty(), + Self::Eip1559(tx) => tx.pretty(), + Self::Eip4844(tx) => tx.pretty(), + Self::Eip7702(tx) => tx.pretty(), } } } @@ -501,7 +590,7 @@ impl UIfmt for AnyTxEnvelope { format!( " hash {} -type {} +type {:#x} {} ", tx.hash.pretty(), @@ -512,234 +601,20 @@ type {} } } } -impl UIfmt for Transaction { + +impl UIfmt for TempoTxEnvelope { fn pretty(&self) -> String { - match &self.inner.inner() { - TxEnvelope::Eip2930(tx) => format!( - " -accessList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -gasPrice {} -hash {} -input {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.inner.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip1559(tx) => format!( - " -accessList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip4844(tx) => format!( - " -accessList {} -blobVersionedHashes {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerBlobGas {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.blob_versioned_hashes().unwrap_or(&[]).pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_blob_gas().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip7702(tx) => format!( - " -accessList {} -authorizationList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.authorization_list() - .as_ref() - .map(|l| l.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - _ => format!( - " -blockHash {} -blockNumber {} -from {} -gas {} -gasPrice {} -hash {} -input {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -v {} -value {}", - self.block_hash.pretty(), - self.block_number.pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.inner.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - self.inner - .as_legacy() - .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) - .unwrap_or_default(), - self.inner - .as_legacy() - .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) - .unwrap_or_default(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner - .as_legacy() - .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) - .unwrap_or_default(), - self.value().pretty(), - ), + match self { + Self::Legacy(tx) => tx.pretty(), + Self::Eip2930(tx) => tx.pretty(), + Self::Eip1559(tx) => tx.pretty(), + Self::Eip7702(tx) => tx.pretty(), + Self::AA(tx) => tx.pretty(), } } } -impl UIfmt for Transaction { +impl UIfmt for Transaction { fn pretty(&self) -> String { format!( " @@ -748,14 +623,13 @@ blockNumber {} from {} transactionIndex {} effectiveGasPrice {} -{} - ", +{}", self.block_hash.pretty(), self.block_number.pretty(), self.inner.signer().pretty(), self.transaction_index.pretty(), self.effective_gas_price.pretty(), - self.inner.pretty().trim_start(), + self.inner.inner().pretty().trim_start(), ) } } @@ -810,61 +684,220 @@ impl UIfmt for EthValue { } } -impl UIfmt for SignedAuthorization { - fn pretty(&self) -> String { - let signed_authorization = serde_json::to_string(self).unwrap_or("".to_string()); +impl UIfmt for SignedAuthorization { + fn pretty(&self) -> String { + let signed_authorization = serde_json::to_string(self).unwrap_or("".to_string()); + + match self.recover_authority() { + Ok(authority) => format!( + "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}", + ), + Err(e) => format!( + "{{recoveredAuthority: , signedAuthority: {signed_authorization}}}", + ), + } + } +} + +impl UIfmt for FoundryReceiptEnvelope +where + T: UIfmt + Clone + core::fmt::Debug + PartialEq + Eq, +{ + fn pretty(&self) -> String { + let receipt = self.as_receipt(); + let deposit_info = match self { + Self::Deposit(d) => { + format!( + " +depositNonce {} +depositReceiptVersion {}", + d.receipt.deposit_nonce.pretty(), + d.receipt.deposit_receipt_version.pretty() + ) + } + _ => String::new(), + }; + + format!( + " +status {} +cumulativeGasUsed {} +logs {} +logsBloom {} +type {}{}", + receipt.status.pretty(), + receipt.cumulative_gas_used.pretty(), + receipt.logs.pretty(), + self.logs_bloom().pretty(), + self.tx_type() as u8, + deposit_info + ) + } +} + +impl UIfmt for FoundryTxReceipt { + fn pretty(&self) -> String { + let receipt = &self.0.inner; + let other = &self.0.other; + + let mut pretty = format!( + " +blockHash {} +blockNumber {} +contractAddress {} +cumulativeGasUsed {} +effectiveGasPrice {} +from {} +gasUsed {} +logs {} +logsBloom {} +root {} +status {} +transactionHash {} +transactionIndex {} +type {} +blobGasPrice {} +blobGasUsed {}", + receipt.block_hash.pretty(), + receipt.block_number.pretty(), + receipt.contract_address.pretty(), + receipt.inner.cumulative_gas_used().pretty(), + receipt.effective_gas_price.pretty(), + receipt.from.pretty(), + receipt.gas_used.pretty(), + serde_json::to_string(receipt.inner.logs()).unwrap(), + receipt.inner.logs_bloom().pretty(), + self.state_root().pretty(), + receipt.inner.status().pretty(), + receipt.transaction_hash.pretty(), + receipt.transaction_index.pretty(), + receipt.inner.tx_type() as u8, + receipt.blob_gas_price.pretty(), + receipt.blob_gas_used.pretty() + ); + + if let Some(to) = receipt.to { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + // additional captured fields + pretty.push_str(&other.pretty()); + + pretty + } +} + +pub trait UIfmtHeaderExt { + fn size_pretty(&self) -> String; +} + +impl UIfmtHeaderExt for Header { + fn size_pretty(&self) -> String { + self.size.pretty() + } +} + +impl UIfmtHeaderExt for AnyRpcHeader { + fn size_pretty(&self) -> String { + self.size.pretty() + } +} + +impl UIfmtHeaderExt for TempoHeaderResponse { + fn size_pretty(&self) -> String { + self.inner.size.pretty() + } +} + +pub trait UIfmtSignatureExt { + fn signature_pretty(&self) -> Option<(String, String, String)>; +} + +impl UIfmtSignatureExt for TxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + let sig = self.signature(); + Some(( + FixedBytes::from(sig.r()).pretty(), + FixedBytes::from(sig.s()).pretty(), + U8::from_le_slice(&sig.as_bytes()[64..]).pretty(), + )) + } +} + +impl UIfmtSignatureExt for AnyTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + self.as_envelope().and_then(|envelope| envelope.signature_pretty()) + } +} + +impl UIfmtSignatureExt for FoundryTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + self.clone().try_into_eth().ok().and_then(|envelope| envelope.signature_pretty()) + } +} + +impl UIfmtSignatureExt for TempoTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + let sig = match self { + Self::Legacy(tx) => Some(tx.signature()), + Self::Eip2930(tx) => Some(tx.signature()), + Self::Eip1559(tx) => Some(tx.signature()), + Self::Eip7702(tx) => Some(tx.signature()), + Self::AA(tempo_tx) => { + if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig)) = + tempo_tx.signature() + { + Some(sig) + } else { + None + } + } + }?; + Some(( + FixedBytes::from(sig.r()).pretty(), + FixedBytes::from(sig.s()).pretty(), + U8::from_le_slice(&sig.as_bytes()[64..]).pretty(), + )) + } +} + +pub trait UIfmtReceiptExt { + fn logs_pretty(&self) -> String; + fn logs_bloom_pretty(&self) -> String; + fn tx_type_pretty(&self) -> String; +} - match self.recover_authority() { - Ok(authority) => format!( - "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}", - ), - Err(e) => format!( - "{{recoveredAuthority: , signedAuthority: {signed_authorization}}}", - ), - } +impl UIfmtReceiptExt for AnyTransactionReceipt { + fn logs_pretty(&self) -> String { + serde_json::to_string(&self.inner.inner.inner.receipt.logs).unwrap_or_default() + } + + fn logs_bloom_pretty(&self) -> String { + self.inner.inner.inner.logs_bloom.pretty() + } + + fn tx_type_pretty(&self) -> String { + self.inner.inner.r#type.to_string() } } -impl UIfmt for FoundryReceiptEnvelope -where - T: UIfmt + Clone + core::fmt::Debug + PartialEq + Eq, -{ - fn pretty(&self) -> String { - let receipt = self.as_receipt(); - let deposit_info = match self { - Self::Deposit(d) => { - format!( - " -depositNonce {} -depositReceiptVersion {}", - d.receipt.deposit_nonce.pretty(), - d.receipt.deposit_receipt_version.pretty() - ) - } - _ => String::new(), - }; +impl UIfmtReceiptExt for FoundryTxReceipt { + fn logs_pretty(&self) -> String { + serde_json::to_string(self.0.inner.inner.logs()).unwrap_or_default() + } - format!( - " -status {} -cumulativeGasUsed {} -logs {} -logsBloom {} -type {}{}", - receipt.status.pretty(), - receipt.cumulative_gas_used.pretty(), - receipt.logs.pretty(), - self.logs_bloom().pretty(), - self.tx_type() as u8, - deposit_info - ) + fn logs_bloom_pretty(&self) -> String { + self.0.inner.inner.logs_bloom().pretty() + } + + fn tx_type_pretty(&self) -> String { + (self.0.inner.inner.tx_type() as u8).to_string() } } -impl UIfmt for FoundryTxReceipt { +impl UIfmt for TempoTransactionReceipt { fn pretty(&self) -> String { - let receipt = &self.0.inner; - let other = &self.0.other; + let receipt = &self.inner; let mut pretty = format!( " @@ -882,8 +915,8 @@ status {} transactionHash {} transactionIndex {} type {} -blobGasPrice {} -blobGasUsed {}", +feePayer {} +feeToken {}", receipt.block_hash.pretty(), receipt.block_number.pretty(), receipt.contract_address.pretty(), @@ -892,85 +925,103 @@ blobGasUsed {}", receipt.from.pretty(), receipt.gas_used.pretty(), serde_json::to_string(receipt.inner.logs()).unwrap(), - receipt.inner.logs_bloom().pretty(), + receipt.inner.logs_bloom.pretty(), self.state_root().pretty(), receipt.inner.status().pretty(), receipt.transaction_hash.pretty(), receipt.transaction_index.pretty(), - receipt.inner.tx_type() as u8, - receipt.blob_gas_price.pretty(), - receipt.blob_gas_used.pretty() + receipt.inner.receipt.tx_type as u8, + self.fee_payer.pretty(), + self.fee_token.pretty(), ); if let Some(to) = receipt.to { pretty.push_str(&format!("\nto {}", to.pretty())); } - // additional captured fields - pretty.push_str(&other.pretty()); - pretty } } +impl UIfmtReceiptExt for TempoTransactionReceipt { + fn logs_pretty(&self) -> String { + serde_json::to_string(self.inner.inner.logs()).unwrap_or_default() + } + + fn logs_bloom_pretty(&self) -> String { + self.inner.inner.logs_bloom.pretty() + } + + fn tx_type_pretty(&self) -> String { + (self.inner.inner.receipt.tx_type as u8).to_string() + } +} + /// Returns the `UiFmt::pretty()` formatted attribute of the transactions -pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option { - let sig = match &transaction.inner.inner() { - AnyTxEnvelope::Ethereum(envelope) => match &envelope { - TxEnvelope::Eip2930(tx) => Some(tx.signature()), - TxEnvelope::Eip1559(tx) => Some(tx.signature()), - TxEnvelope::Eip4844(tx) => Some(tx.signature()), - TxEnvelope::Eip7702(tx) => Some(tx.signature()), - TxEnvelope::Legacy(tx) => Some(tx.signature()), - }, - _ => None, - }; +pub fn get_pretty_tx_attr(transaction: &N::TransactionResponse, attr: &str) -> Option +where + N: Network, + N::TxEnvelope: UIfmtSignatureExt, +{ + let (r, s, v) = transaction.as_ref().signature_pretty().unwrap_or_default(); match attr { - "blockHash" | "block_hash" => Some(transaction.block_hash.pretty()), - "blockNumber" | "block_number" => Some(transaction.block_number.pretty()), - "from" => Some(transaction.inner.signer().pretty()), - "gas" => Some(transaction.gas_limit().pretty()), - "gasPrice" | "gas_price" => Some(Transaction::gas_price(transaction).pretty()), + "blockHash" | "block_hash" => { + Some(alloy_network::TransactionResponse::block_hash(transaction).pretty()) + } + "blockNumber" | "block_number" => { + Some(alloy_network::TransactionResponse::block_number(transaction).pretty()) + } + "from" => Some(alloy_network::TransactionResponse::from(transaction).pretty()), + "gas" => Some(TxTrait::gas_limit(transaction).pretty()), + "gasPrice" | "gas_price" => Some(TxTrait::max_fee_per_gas(transaction).pretty()), "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()), - "input" => Some(transaction.input().pretty()), - "nonce" => Some(transaction.nonce().to_string()), - "s" => sig.map(|s| FixedBytes::from(s.s()).pretty()), - "r" => sig.map(|s| FixedBytes::from(s.r()).pretty()), - "to" => Some(transaction.to().pretty()), - "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), - "v" => sig.map(|s| U8::from_be_slice(&s.as_bytes()[64..]).pretty()), - "value" => Some(transaction.value().pretty()), + "input" => Some(TxTrait::input(transaction).pretty()), + "nonce" => Some(TxTrait::nonce(transaction).to_string()), + "s" => Some(s), + "r" => Some(r), + "to" => Some(TxTrait::to(transaction).pretty()), + "transactionIndex" | "transaction_index" => { + Some(alloy_network::TransactionResponse::transaction_index(transaction).pretty()) + } + "v" => Some(v), + "value" => Some(TxTrait::value(transaction).pretty()), _ => None, } } -/// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &AnyRpcBlock, attr: &str) -> Option { +pub fn get_pretty_block_attr(block: &N::BlockResponse, attr: &str) -> Option +where + N: Network, + N::BlockResponse: BlockResponse

, + N::HeaderResponse: UIfmtHeaderExt, +{ match attr { - "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), - "difficulty" => Some(block.header.difficulty.pretty()), - "extraData" | "extra_data" => Some(block.header.extra_data.pretty()), - "gasLimit" | "gas_limit" => Some(block.header.gas_limit.pretty()), - "gasUsed" | "gas_used" => Some(block.header.gas_used.pretty()), - "hash" => Some(block.header.hash.pretty()), - "logsBloom" | "logs_bloom" => Some(block.header.logs_bloom.pretty()), - "miner" | "author" => Some(block.header.inner.beneficiary.pretty()), - "mixHash" | "mix_hash" => Some(block.header.mix_hash.pretty()), - "nonce" => Some(block.header.nonce.pretty()), - "number" => Some(block.header.number.pretty()), - "parentHash" | "parent_hash" => Some(block.header.parent_hash.pretty()), - "transactionsRoot" | "transactions_root" => Some(block.header.transactions_root.pretty()), - "receiptsRoot" | "receipts_root" => Some(block.header.receipts_root.pretty()), - "sha3Uncles" | "sha_3_uncles" => Some(block.header.ommers_hash.pretty()), - "size" => Some(block.header.size.pretty()), - "stateRoot" | "state_root" => Some(block.header.state_root.pretty()), - "timestamp" => Some(block.header.timestamp.pretty()), - "totalDifficulty" | "total_difficult" => Some(block.header.total_difficulty.pretty()), - "blobGasUsed" | "blob_gas_used" => Some(block.header.blob_gas_used.pretty()), - "excessBlobGas" | "excess_blob_gas" => Some(block.header.excess_blob_gas.pretty()), - "requestsHash" | "requests_hash" => Some(block.header.requests_hash.pretty()), + "baseFeePerGas" | "base_fee_per_gas" => Some(block.header().base_fee_per_gas().pretty()), + "difficulty" => Some(block.header().difficulty().pretty()), + "extraData" | "extra_data" => Some(block.header().extra_data().pretty()), + "gasLimit" | "gas_limit" => Some(block.header().gas_limit().pretty()), + "gasUsed" | "gas_used" => Some(block.header().gas_used().pretty()), + "hash" => Some(block.header().hash().pretty()), + "logsBloom" | "logs_bloom" => Some(block.header().logs_bloom().pretty()), + "miner" | "author" => Some(block.header().beneficiary().pretty()), + "mixHash" | "mix_hash" => Some(block.header().mix_hash().pretty()), + "nonce" => Some(block.header().nonce().pretty()), + "number" => Some(block.header().number().pretty()), + "parentHash" | "parent_hash" => Some(block.header().parent_hash().pretty()), + "transactionsRoot" | "transactions_root" => { + Some(block.header().transactions_root().pretty()) + } + "receiptsRoot" | "receipts_root" => Some(block.header().receipts_root().pretty()), + "sha3Uncles" | "sha_3_uncles" => Some(block.header().ommers_hash().pretty()), + "size" => Some(block.header().size_pretty()), + "stateRoot" | "state_root" => Some(block.header().state_root().pretty()), + "timestamp" => Some(block.header().timestamp().pretty()), + "totalDifficulty" | "total_difficulty" => Some(block.header().difficulty().pretty()), + "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()), + "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()), + "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()), other => { - if let Some(value) = block.other.get(other) { + if let Some(value) = block.other_fields().and_then(|fields| fields.get(other)) { let val = EthValue::from(value.clone()); return Some(val.pretty()); } @@ -979,42 +1030,34 @@ pub fn get_pretty_block_attr(block: &AnyRpcBlock, attr: &str) -> Option } } -fn pretty_block_basics(block: &Block>) -> String { - let Block { - header: - Header { - hash, - size, - total_difficulty, - inner: - AnyHeader { - parent_hash, - ommers_hash, - beneficiary, - state_root, - transactions_root, - receipts_root, - logs_bloom, - difficulty, - number, - gas_limit, - gas_used, - timestamp, - extra_data, - mix_hash, - nonce, - base_fee_per_gas, - withdrawals_root, - blob_gas_used, - excess_blob_gas, - parent_beacon_block_root, - requests_hash, - }, - }, - uncles: _, - transactions: _, - withdrawals: _, - } = block; +pub fn get_pretty_receipt_attr(receipt: &N::ReceiptResponse, attr: &str) -> Option +where + N: Network, + N::ReceiptResponse: ReceiptResponse + UIfmtReceiptExt, +{ + match attr { + "blockHash" | "block_hash" => Some(receipt.block_hash().pretty()), + "blockNumber" | "block_number" => Some(receipt.block_number().pretty()), + "contractAddress" | "contract_address" => Some(receipt.contract_address().pretty()), + "cumulativeGasUsed" | "cumulative_gas_used" => Some(receipt.cumulative_gas_used().pretty()), + "effectiveGasPrice" | "effective_gas_price" => Some(receipt.effective_gas_price().pretty()), + "from" => Some(receipt.from().pretty()), + "gasUsed" | "gas_used" => Some(receipt.gas_used().pretty()), + "logs" => Some(receipt.logs_pretty()), + "logsBloom" | "logs_bloom" => Some(receipt.logs_bloom_pretty()), + "root" | "stateRoot" | "state_root" => Some(receipt.state_root().pretty()), + "status" | "statusCode" | "status_code" => Some(receipt.status().pretty()), + "transactionHash" | "transaction_hash" => Some(receipt.transaction_hash().pretty()), + "transactionIndex" | "transaction_index" => Some(receipt.transaction_index().pretty()), + "to" => Some(receipt.to().pretty()), + "type" | "transaction_type" => Some(receipt.tx_type_pretty()), + "blobGasPrice" | "blob_gas_price" => Some(receipt.blob_gas_price().pretty()), + "blobGasUsed" | "blob_gas_used" => Some(receipt.blob_gas_used().pretty()), + _ => None, + } +} + +fn pretty_generic_header_response(header: &H) -> String { format!( " baseFeePerGas {} @@ -1041,31 +1084,31 @@ totalDifficulty {} blobGasUsed {} excessBlobGas {} requestsHash {}", - base_fee_per_gas.pretty(), - difficulty.pretty(), - extra_data.pretty(), - gas_limit.pretty(), - gas_used.pretty(), - hash.pretty(), - logs_bloom.pretty(), - beneficiary.pretty(), - mix_hash.pretty(), - nonce.pretty(), - number.pretty(), - parent_hash.pretty(), - parent_beacon_block_root.pretty(), - transactions_root.pretty(), - receipts_root.pretty(), - ommers_hash.pretty(), - size.pretty(), - state_root.pretty(), - timestamp.pretty(), - fmt_timestamp(*timestamp), - withdrawals_root.pretty(), - total_difficulty.pretty(), - blob_gas_used.pretty(), - excess_blob_gas.pretty(), - requests_hash.pretty(), + header.base_fee_per_gas().pretty(), + header.difficulty().pretty(), + header.extra_data().pretty(), + header.gas_limit().pretty(), + header.gas_used().pretty(), + header.hash().pretty(), + header.logs_bloom().pretty(), + header.beneficiary().pretty(), + header.mix_hash().pretty(), + header.nonce().pretty(), + header.number().pretty(), + header.parent_hash().pretty(), + header.parent_beacon_block_root().pretty(), + header.transactions_root().pretty(), + header.receipts_root().pretty(), + header.ommers_hash().pretty(), + header.size_pretty(), + header.state_root().pretty(), + header.timestamp().pretty(), + fmt_timestamp(header.timestamp()), + header.withdrawals_root().pretty(), + header.difficulty().pretty(), + header.blob_gas_used().pretty(), + header.excess_blob_gas().pretty(), + header.requests_hash().pretty(), ) } @@ -1092,6 +1135,7 @@ mod tests { use super::*; use alloy_primitives::B256; use alloy_rpc_types::Authorization; + use foundry_primitives::FoundryNetwork; use similar_asserts::assert_eq; use std::str::FromStr; @@ -1150,23 +1194,26 @@ mod tests { } "#; - let tx: WithOtherFields = serde_json::from_str(s).unwrap(); + let tx: WithOtherFields> = serde_json::from_str(s).unwrap(); assert_eq!(tx.pretty().trim(), r" blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae blockNumber 436 from 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6 -gas 18660316 -gasPrice 0 +transactionIndex 0 +effectiveGasPrice 0 hash 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f -input 0xd294f093 +type 0 +chainId 10 nonce 162 -r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee -s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +gasPrice 0 +gasLimit 18660316 to 0x4a16A42407AA491564643E1dfc1fd50af29794eF -transactionIndex 0 -v 1 value 0 +input 0xd294f093 +r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee +s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +yParity 1 index 435 l1BlockNumber 12691036 l1Timestamp 1624460128 @@ -1197,17 +1244,29 @@ txType 0 "v": "0x1", "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1", "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc", - "chainId": "0x66a", - "accessList": [ - { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] }, - { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] } - ] + "chainId": "0x66a", + "accessList": [ + { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] }, + { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] } + ] } "#; - let tx: Transaction = serde_json::from_str(s).unwrap(); assert_eq!(tx.pretty().trim(), r" +blockHash 0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81 +blockNumber 76573 +from 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 +transactionIndex 2 +effectiveGasPrice 1000000000 +hash 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090 +type 1 +chainId 1642 +nonce 28 +gasPrice 1000000000 +gasLimit 27615 +to 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 +value 0 accessList [ 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [ 0x1122334455667788990011223344556677889900112233445566778899001122 @@ -1215,21 +1274,9 @@ accessList [ ] 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => [] ] -blockHash 0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81 -blockNumber 76573 -chainId 1642 -from 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 -gasLimit 27615 -gasPrice 1000000000 -hash 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090 input 0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a -nonce 28 r 0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1 s 0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc -to 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 -transactionIndex 2 -type 1 -value 0 yParity 1 ".trim() ); @@ -1271,27 +1318,28 @@ yParity 1 assert_eq!( tx.pretty().trim(), r" -accessList [ - 0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [ - 0x0000000000000000000000000000000000000000000000000000000000000000 - ] -] blockHash 0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125 blockNumber 30279 -chainId 1642 from 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00 -gasLimit 100000 +transactionIndex 65 +effectiveGasPrice 20000000000 hash 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244 -input 0x48600055323160015500 +type 2 +chainId 1642 +nonce 300 +gasLimit 100000 maxFeePerGas 20000000000 maxPriorityFeePerGas 20000000000 -nonce 300 -r 0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34 -s 0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158 to -transactionIndex 65 -type 2 value 0 +accessList [ + 0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [ + 0x0000000000000000000000000000000000000000000000000000000000000000 + ] +] +input 0x48600055323160015500 +r 0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34 +s 0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158 yParity 1 " .trim() @@ -1301,57 +1349,58 @@ yParity 1 #[test] fn can_pretty_print_eip4884() { let s = r#"{ - "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052", - "blockNumber": "0x2a1cb", - "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2", - "gas": "0x5208", - "gasPrice": "0x1d1a94a201c", - "maxFeePerGas": "0x1d1a94a201c", - "maxPriorityFeePerGas": "0x1d1a94a201c", - "maxFeePerBlobGas": "0x3e8", - "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00", - "input": "0x", - "nonce": "0x1b483", - "to": "0x000000000000000000000000000000000000f1c1", - "transactionIndex": "0x0", - "value": "0x0", - "type": "0x3", - "accessList": [], - "chainId": "0x1a1f0ff42", - "blobVersionedHashes": [ - "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76" - ], - "v": "0x0", - "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1", - "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b", - "yParity": "0x0" - } + "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052", + "blockNumber": "0x2a1cb", + "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2", + "gas": "0x5208", + "gasPrice": "0x1d1a94a201c", + "maxFeePerGas": "0x1d1a94a201c", + "maxPriorityFeePerGas": "0x1d1a94a201c", + "maxFeePerBlobGas": "0x3e8", + "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00", + "input": "0x", + "nonce": "0x1b483", + "to": "0x000000000000000000000000000000000000f1c1", + "transactionIndex": "0x0", + "value": "0x0", + "type": "0x3", + "accessList": [], + "chainId": "0x1a1f0ff42", + "blobVersionedHashes": [ + "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76" + ], + "v": "0x0", + "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1", + "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b", + "yParity": "0x0" + } "#; let tx: Transaction = serde_json::from_str(s).unwrap(); assert_eq!( tx.pretty().trim(), r" -accessList [] -blobVersionedHashes [ - 0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76 -] blockHash 0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052 blockNumber 172491 -chainId 7011893058 from 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2 -gasLimit 21000 +transactionIndex 0 +effectiveGasPrice 2000000000028 hash 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00 -input 0x -maxFeePerBlobGas 1000 +type 3 +chainId 7011893058 +nonce 111747 +gasLimit 21000 maxFeePerGas 2000000000028 maxPriorityFeePerGas 2000000000028 -nonce 111747 -r 0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1 -s 0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b to 0x000000000000000000000000000000000000f1C1 -transactionIndex 0 -type 3 value 0 +accessList [] +blobVersionedHashes [ + 0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76 +] +maxFeePerBlobGas 1000 +input 0x +r 0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1 +s 0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b yParity 0 " .trim() @@ -1362,21 +1411,26 @@ yParity 0 fn print_block_w_txs() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; let block: Block = serde_json::from_str(block).unwrap(); - let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 + let output = " +blockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 blockNumber 3 from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a -gas 90000 -gasPrice 20000000000 +transactionIndex 0 +effectiveGasPrice 20000000000 hash 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067 -input 0x +type 0 +chainId 1 nonce 2 +gasPrice 20000000000 +gasLimit 90000 +to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e +value 0 +input 0x r 0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88 s 0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e -to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e -transactionIndex 0 -v 0 -value 0".to_string(); - let txs = match block.transactions { +yParity 0" + .to_string(); + let txs = match block.transactions() { BlockTransactions::Full(txs) => txs, _ => panic!("not full transactions"), }; @@ -1428,41 +1482,51 @@ value 0".to_string(); #[test] fn test_pretty_tx_attr() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block> = serde_json::from_str(block).unwrap(); - let txs = match block.transactions { + let block: ::BlockResponse = + serde_json::from_str(block).unwrap(); + let txs = match block.transactions() { BlockTransactions::Full(txes) => txes, _ => panic!("not full transactions"), }; - assert_eq!(None, get_pretty_tx_attr(&txs[0], "")); - assert_eq!(Some("3".to_string()), get_pretty_tx_attr(&txs[0], "blockNumber")); + assert_eq!(None, get_pretty_tx_attr::(&txs[0], "")); + assert_eq!( + Some("3".to_string()), + get_pretty_tx_attr::(&txs[0], "blockNumber") + ); assert_eq!( Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr(&txs[0], "from") + get_pretty_tx_attr::(&txs[0], "from") + ); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::(&txs[0], "gas")); + assert_eq!( + Some("20000000000".to_string()), + get_pretty_tx_attr::(&txs[0], "gasPrice") ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&txs[0], "gas")); - assert_eq!(Some("20000000000".to_string()), get_pretty_tx_attr(&txs[0], "gasPrice")); assert_eq!( Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr(&txs[0], "hash") + get_pretty_tx_attr::(&txs[0], "hash") ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&txs[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&txs[0], "nonce")); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr::(&txs[0], "nonce")); assert_eq!( Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr(&txs[0], "r") + get_pretty_tx_attr::(&txs[0], "r") ); assert_eq!( Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr(&txs[0], "s") + get_pretty_tx_attr::(&txs[0], "s") ); assert_eq!( Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr(&txs[0], "to") + get_pretty_tx_attr::(&txs[0], "to") + ); + assert_eq!( + Some("0".to_string()), + get_pretty_tx_attr::(&txs[0], "transactionIndex") ); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "transactionIndex")); - assert_eq!(Some("27".to_string()), get_pretty_tx_attr(&txs[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "value")); + assert_eq!(Some("27".to_string()), get_pretty_tx_attr::(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr::(&txs[0], "value")); } #[test] @@ -1494,59 +1558,87 @@ value 0".to_string(); "gasUsed": "0x9f759", "timestamp": "0x54e34e8e", "transactions": [], - "uncles": [] + "uncles": [], } ); - let block: AnyRpcBlock = serde_json::from_value(json).unwrap(); + let block: ::BlockResponse = + serde_json::from_value(json).unwrap(); - assert_eq!(None, get_pretty_block_attr(&block, "")); - assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "difficulty")); + assert_eq!(None, get_pretty_block_attr::(&block, "")); + assert_eq!( + Some("7".to_string()), + get_pretty_block_attr::(&block, "baseFeePerGas") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "difficulty") + ); assert_eq!( Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()), - get_pretty_block_attr(&block, "extraData") + get_pretty_block_attr::(&block, "extraData") + ); + assert_eq!( + Some("653145".to_string()), + get_pretty_block_attr::(&block, "gasLimit") + ); + assert_eq!( + Some("653145".to_string()), + get_pretty_block_attr::(&block, "gasUsed") ); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasLimit")); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasUsed")); assert_eq!( Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), - get_pretty_block_attr(&block, "hash") + get_pretty_block_attr::(&block, "hash") ); - assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr(&block, "logsBloom")); + assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::(&block, "logsBloom")); assert_eq!( Some("0x0000000000000000000000000000000000000001".to_string()), - get_pretty_block_attr(&block, "miner") + get_pretty_block_attr::(&block, "miner") ); assert_eq!( Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()), - get_pretty_block_attr(&block, "mixHash") + get_pretty_block_attr::(&block, "mixHash") + ); + assert_eq!( + Some("0x0000000000000000".to_string()), + get_pretty_block_attr::(&block, "nonce") + ); + assert_eq!( + Some("436".to_string()), + get_pretty_block_attr::(&block, "number") ); - assert_eq!(Some("0x0000000000000000".to_string()), get_pretty_block_attr(&block, "nonce")); - assert_eq!(Some("436".to_string()), get_pretty_block_attr(&block, "number")); assert_eq!( Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), - get_pretty_block_attr(&block, "parentHash") + get_pretty_block_attr::(&block, "parentHash") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr(&block, "transactionsRoot") + get_pretty_block_attr::(&block, "transactionsRoot") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr(&block, "receiptsRoot") + get_pretty_block_attr::(&block, "receiptsRoot") ); assert_eq!( Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()), - get_pretty_block_attr(&block, "sha3Uncles") + get_pretty_block_attr::(&block, "sha3Uncles") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "size") ); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "size")); assert_eq!( Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()), - get_pretty_block_attr(&block, "stateRoot") + get_pretty_block_attr::(&block, "stateRoot") + ); + assert_eq!( + Some("1424182926".to_string()), + get_pretty_block_attr::(&block, "timestamp") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "totalDifficulty") ); - assert_eq!(Some("1424182926".to_string()), get_pretty_block_attr(&block, "timestamp")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "totalDifficulty")); } #[test] @@ -1670,7 +1762,7 @@ l1GasUsed 1600 "gasPrice":"0x2540be400" }"#; - let tx: AnyRpcTransaction = serde_json::from_str(s).unwrap(); + let tx: Transaction = serde_json::from_str(s).unwrap(); assert_eq!( tx.pretty().trim(), @@ -1682,22 +1774,53 @@ transactionIndex 0 effectiveGasPrice 10000000000 hash 0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd type 118 -aaAuthorizationList [] -accessList [] -calls [{"data":null,"input":"0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680","to":"0x20c0000000000000000000000000000000000000","value":"0x0"},{"data":null,"input":"0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330","to":"0xdec0000000000000000000000000000000000000","value":"0x0"}] chainId 42429 -feePayerSignature null feeToken 0x20C0000000000000000000000000000000000001 -gas 184696 -gasPrice 10000000000 -keyAuthorization null -maxFeePerGas 12000000000 maxPriorityFeePerGas 0 -nonce 0 +maxFeePerGas 12000000000 +gasLimit 184696 +calls [ + to: 0x20C0000000000000000000000000000000000000, value: 0, input: 0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680 + to: 0xDEc0000000000000000000000000000000000000, value: 0, input: 0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330 +] +accessList [] nonceKey 0 -signature {"pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","type":"webAuthn","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"} -validAfter null -validBefore null +nonce 0 +feePayerSignature +validBefore +validAfter +tempoSignature {"type":"webAuthn","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"} +"# + .trim() + ); + } + + #[test] + fn can_pretty_print_tempo_receipt() { + let s = r#"{"type":"0x76","status":"0x1","cumulativeGasUsed":"0x176d7f4","logs":[{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","gasUsed":"0xcc6a","effectiveGasPrice":"0x4a817c802","from":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd","to":"0x20c0000000000000000000000000000000000000","contractAddress":null,"feeToken":"0x20c0000000000000000000000000000000000003","feePayer":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"}"#; + + let tx: TempoTransactionReceipt = serde_json::from_str(s).unwrap(); + + assert_eq!( + tx.pretty().trim(), + r#" +blockHash 0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66 +blockNumber 6922711 +contractAddress +cumulativeGasUsed 24565748 +effectiveGasPrice 20000000002 +from 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd +gasUsed 52330 +logs [{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}] +logsBloom 0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000 +root +status true +transactionHash 0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f +transactionIndex 99 +type 118 +feePayer 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd +feeToken 0x20C0000000000000000000000000000000000003 +to 0x20C0000000000000000000000000000000000000 "# .trim() ); @@ -1732,4 +1855,58 @@ validBefore null // Verify status is pretty printed correctly (boolean true for successful transaction) assert!(pretty_output.contains("true")); } + + #[test] + fn test_get_pretty_receipt_attr() { + let receipt_json = serde_json::json!({ + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1234567890123456789012345678901234567890123456789012345678901234", + "transactionIndex": "0x0", + "blockHash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "blockNumber": "0x1", + "gasUsed": "0x5208", + "effectiveGasPrice": "0x3b9aca00", + "from": "0x1234567890123456789012345678901234567890", + "to": "0x0987654321098765432109876543210987654321", + "contractAddress": null + }); + + let receipt: ::ReceiptResponse = + serde_json::from_value(receipt_json).unwrap(); + + // Test basic receipt attributes + assert_eq!( + Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()), + get_pretty_receipt_attr::(&receipt, "blockHash") + ); + assert_eq!( + Some("1".to_string()), + get_pretty_receipt_attr::(&receipt, "blockNumber") + ); + assert_eq!( + Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()), + get_pretty_receipt_attr::(&receipt, "transactionHash") + ); + assert_eq!( + Some("21000".to_string()), + get_pretty_receipt_attr::(&receipt, "gasUsed") + ); + assert_eq!( + Some("true".to_string()), + get_pretty_receipt_attr::(&receipt, "status") + ); + assert_eq!( + Some("2".to_string()), + get_pretty_receipt_attr::(&receipt, "type") + ); + assert_eq!( + Some("[]".to_string()), + get_pretty_receipt_attr::(&receipt, "logs") + ); + assert!(get_pretty_receipt_attr::(&receipt, "logsBloom").is_some()); + } } diff --git a/crates/common/src/comments/inline_config.rs b/crates/common/src/comments/inline_config.rs index 7be6dd215b720..16ffd6993bfef 100644 --- a/crates/common/src/comments/inline_config.rs +++ b/crates/common/src/comments/inline_config.rs @@ -205,6 +205,7 @@ impl InlineConfig { let comment_range = result.data; let src = file.src.as_str(); + #[allow(clippy::collapsible_match)] match item { InlineConfigItem::DisableNextItem(ids) => { if let Some(next_item) = find_next_item(span.hi()) { diff --git a/crates/common/src/comments/mod.rs b/crates/common/src/comments/mod.rs index 033146da63ad2..a3e4a8f3ec743 100644 --- a/crates/common/src/comments/mod.rs +++ b/crates/common/src/comments/mod.rs @@ -195,6 +195,7 @@ impl<'ast> CommentGatherer<'ast> { self.disabled_block_depth -= 1; } + #[allow(clippy::collapsible_match)] match token.kind { TokenKind::Whitespace => { if let Some(mut idx) = token_text.find('\n') { @@ -436,12 +437,10 @@ pub fn line_with_tabs( (num_tabs, num_spaces) = (num_tabs + 1, 0); } } - Some(Consolidation::WithoutSpaces) => { - if num_spaces != 0 { - (num_tabs, num_spaces) = (num_tabs + 1, 0); - } + Some(Consolidation::WithoutSpaces) if num_spaces != 0 => { + (num_tabs, num_spaces) = (num_tabs + 1, 0); } - None => (), + _ => (), }; // Append the normalized indentation and the rest of the line to the output diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 4cf3358f24400..cb22287fb68b4 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -62,11 +62,7 @@ pub fn read_json_gzip_file(path: &Path) -> Result { /// Reads the entire contents of a locked shared file into a string. pub fn locked_read_to_string(path: impl AsRef) -> Result { let path = path.as_ref(); - let mut file = - fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?; - file.lock_shared().map_err(|err| FsPathError::lock(err, path))?; - let contents = read_inner(path, &mut file)?; - file.unlock().map_err(|err| FsPathError::unlock(err, path))?; + let contents = locked_read(path)?; String::from_utf8(contents).map_err(|err| FsPathError::read(std::io::Error::other(err), path)) } diff --git a/crates/common/src/mapping_slots.rs b/crates/common/src/mapping_slots.rs index 2c2a0c6e2894a..fccf2f1bff927 100644 --- a/crates/common/src/mapping_slots.rs +++ b/crates/common/src/mapping_slots.rs @@ -47,18 +47,17 @@ impl MappingSlots { /// Function to be used in Inspector::step to record mapping slots and keys #[cold] pub fn step(mapping_slots: &mut AddressHashMap, interpreter: &Interpreter) { + #[allow(clippy::collapsible_match)] match interpreter.bytecode.opcode() { - opcode::KECCAK256 => { - if interpreter.stack.peek(1) == Ok(U256::from(0x40)) { - let address = interpreter.input.target_address; - let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); - let data = interpreter.memory.slice_len(offset, 0x40); - let low = B256::from_slice(&data[..0x20]); - let high = B256::from_slice(&data[0x20..]); - let result = keccak256(&*data); + opcode::KECCAK256 if interpreter.stack.peek(1) == Ok(U256::from(0x40)) => { + let address = interpreter.input.target_address; + let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); + let data = interpreter.memory.slice_len(offset, 0x40); + let low = B256::from_slice(&data[..0x20]); + let high = B256::from_slice(&data[0x20..]); + let result = keccak256(&*data); - mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); - } + mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); } opcode::SSTORE => { if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address) diff --git a/crates/common/src/preprocessor/deps.rs b/crates/common/src/preprocessor/deps.rs index 6d60241a346e8..804801d98d8ba 100644 --- a/crates/common/src/preprocessor/deps.rs +++ b/crates/common/src/preprocessor/deps.rs @@ -182,6 +182,7 @@ impl<'gcx> Visit<'gcx> for BytecodeDependencyCollector<'gcx, '_> { } fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow { + #[allow(clippy::collapsible_match)] match &expr.kind { ExprKind::Call(call_expr, call_args, named_args) => { if let Some(dependency) = handle_call_expr( diff --git a/crates/common/src/preprocessor/mod.rs b/crates/common/src/preprocessor/mod.rs index bc27cca580eee..7499776cf2c72 100644 --- a/crates/common/src/preprocessor/mod.rs +++ b/crates/common/src/preprocessor/mod.rs @@ -46,7 +46,7 @@ impl Preprocessor for DynamicTestLinkingPreprocessor { ) -> Result<()> { // Skip if we are not preprocessing any tests or scripts. Avoids unnecessary AST parsing. if !input.input.sources.iter().any(|(path, _)| paths.is_test_or_script(path)) { - trace!("no tests or sources to preprocess"); + trace!("no tests or scripts to preprocess"); return Ok(()); } diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 66d8b5018bf2e..764a44db55baa 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -8,16 +8,19 @@ use crate::{ provider::{curl_transport::CurlTransport, runtime_transport::RuntimeTransportBuilder}, }; use alloy_chains::NamedChain; +use alloy_network::{Network, NetworkWallet}; use alloy_provider::{ Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, - fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + fillers::{FillProvider, JoinFill, RecommendedFillers, WalletFiller}, network::{AnyNetwork, EthereumWallet}, }; use alloy_rpc_client::ClientBuilder; use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; use eyre::{Result, WrapErr}; +use foundry_config::Config; use reqwest::Url; use std::{ + marker::PhantomData, net::SocketAddr, path::{Path, PathBuf}, str::FromStr, @@ -36,20 +39,8 @@ const POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR: f32 = 0.6; pub type RetryProvider = RootProvider; /// Helper type alias for a retry provider with a signer -pub type RetryProviderWithSigner = FillProvider< - JoinFill< - JoinFill< - Identity, - JoinFill< - GasFiller, - JoinFill< - alloy_provider::fillers::BlobGasFiller, - JoinFill, - >, - >, - >, - WalletFiller, - >, +pub type RetryProviderWithSigner = FillProvider< + JoinFill::RecommendedFillers>, WalletFiller>, RootProvider, N, >; @@ -84,8 +75,10 @@ pub fn try_get_http_provider(builder: impl AsRef) -> Result } /// Helper type to construct a `RetryProvider` +/// +/// This builder is generic over the network type `N`, defaulting to `AnyNetwork`. #[derive(Debug)] -pub struct ProviderBuilder { +pub struct ProviderBuilder { // Note: this is a result, so we can easily chain builder calls url: Result, chain: NamedChain, @@ -100,12 +93,16 @@ pub struct ProviderBuilder { is_local: bool, /// Whether to accept invalid certificates. accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection. + no_proxy: bool, /// Whether to output curl commands instead of making requests. curl_mode: bool, + /// Phantom data for the network type. + _network: PhantomData, } -impl ProviderBuilder { - /// Creates a new builder instance +impl ProviderBuilder { + /// Creates a new ProviderBuilder helper instance. pub fn new(url_str: &str) -> Self { // a copy is needed for the next lines to work let mut url_str = url_str; @@ -152,8 +149,39 @@ impl ProviderBuilder { headers: vec![], is_local, accept_invalid_certs: false, + no_proxy: false, curl_mode: false, + _network: PhantomData, + } + } + + /// Constructs a [ProviderBuilder] instantiated using [Config] values. + /// + /// Defaults to `http://localhost:8545` and `Mainnet`. + pub fn from_config(config: &Config) -> Result { + let url = config.get_rpc_url_or_localhost_http()?; + let mut builder = Self::new(url.as_ref()); + + builder = builder.accept_invalid_certs(config.eth_rpc_accept_invalid_certs); + builder = builder.curl_mode(config.eth_rpc_curl); + + if let Ok(chain) = config.chain.unwrap_or_default().try_into() { + builder = builder.chain(chain); + } + + if let Some(jwt) = config.get_rpc_jwt_secret()? { + builder = builder.jwt(jwt.as_ref()); + } + + if let Some(rpc_timeout) = config.eth_rpc_timeout { + builder = builder.timeout(Duration::from_secs(rpc_timeout)); + } + + if let Some(rpc_headers) = config.eth_rpc_headers.clone() { + builder = builder.headers(rpc_headers); } + + Ok(builder) } /// Enables a request timeout. @@ -256,6 +284,15 @@ impl ProviderBuilder { self } + /// Sets whether to disable automatic proxy detection. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub fn no_proxy(mut self, no_proxy: bool) -> Self { + self.no_proxy = no_proxy; + self + } + /// Sets whether to output curl commands instead of making requests. /// /// When enabled, the provider will print equivalent curl commands to stdout @@ -266,7 +303,7 @@ impl ProviderBuilder { } /// Constructs the `RetryProvider` taking all configs into account. - pub fn build(self) -> Result { + pub fn build(self) -> Result> { let Self { url, chain, @@ -278,7 +315,9 @@ impl ProviderBuilder { headers, is_local, accept_invalid_certs, + no_proxy, curl_mode, + .. } = self; let url = url?; @@ -290,7 +329,7 @@ impl ProviderBuilder { let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .connect_provider(RootProvider::new(client)); return Ok(provider); @@ -301,6 +340,7 @@ impl ProviderBuilder { .with_headers(headers) .with_jwt(jwt) .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) .build(); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); @@ -316,14 +356,22 @@ impl ProviderBuilder { ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() - .connect_provider(RootProvider::new(client)); + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); Ok(provider) } +} +impl ProviderBuilder { /// Constructs the `RetryProvider` with a wallet. - pub fn build_with_wallet(self, wallet: EthereumWallet) -> Result { + pub fn build_with_wallet + Clone>( + self, + wallet: W, + ) -> Result> + where + N: RecommendedFillers, + { let Self { url, chain, @@ -335,7 +383,9 @@ impl ProviderBuilder { headers, is_local, accept_invalid_certs, + no_proxy, curl_mode, + .. } = self; let url = url?; @@ -347,7 +397,7 @@ impl ProviderBuilder { let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .with_recommended_fillers() .wallet(wallet) .connect_provider(RootProvider::new(client)); @@ -360,6 +410,7 @@ impl ProviderBuilder { .with_headers(headers) .with_jwt(jwt) .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) .build(); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); @@ -376,7 +427,7 @@ impl ProviderBuilder { ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .with_recommended_fillers() .wallet(wallet) .connect_provider(RootProvider::new(client)); @@ -401,7 +452,11 @@ fn resolve_path(path: &Path) -> Result { { return Ok(path.to_path_buf()); } - Err(()) + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + std::env::current_dir().map(|d| d.join(path)).map_err(drop) + } } #[cfg(test)] @@ -410,7 +465,7 @@ mod tests { #[test] fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); + let builder = ProviderBuilder::::new("localhost:8545"); assert!(builder.url.is_ok()); let url = builder.url.unwrap(); diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 6c175411640d8..b5962c9a72f63 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -80,6 +80,8 @@ pub struct RuntimeTransport { timeout: std::time::Duration, /// Whether to accept invalid certificates. accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection. + no_proxy: bool, } /// A builder for [RuntimeTransport]. @@ -90,6 +92,7 @@ pub struct RuntimeTransportBuilder { jwt: Option, timeout: std::time::Duration, accept_invalid_certs: bool, + no_proxy: bool, } impl RuntimeTransportBuilder { @@ -101,6 +104,7 @@ impl RuntimeTransportBuilder { jwt: None, timeout: REQUEST_TIMEOUT, accept_invalid_certs: false, + no_proxy: false, } } @@ -128,6 +132,15 @@ impl RuntimeTransportBuilder { self } + /// Set whether to disable automatic proxy detection. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub fn no_proxy(mut self, no_proxy: bool) -> Self { + self.no_proxy = no_proxy; + self + } + /// Builds the [RuntimeTransport] and returns it in a disconnected state. /// The runtime transport will then connect when the first request happens. pub fn build(self) -> RuntimeTransport { @@ -138,6 +151,7 @@ impl RuntimeTransportBuilder { jwt: self.jwt, timeout: self.timeout, accept_invalid_certs: self.accept_invalid_certs, + no_proxy: self.no_proxy, } } } @@ -165,6 +179,14 @@ impl RuntimeTransport { .timeout(self.timeout) .tls_built_in_root_certs(self.url.scheme() == "https") .danger_accept_invalid_certs(self.accept_invalid_certs); + + // Disable automatic proxy detection if requested. This helps in sandboxed environments + // (e.g., Cursor IDE sandbox, macOS App Sandbox) where system proxy detection via + // SCDynamicStore causes crashes. See: https://github.com/foundry-rs/foundry/issues/12733 + if self.no_proxy { + client_builder = client_builder.no_proxy(); + } + let mut headers = reqwest::header::HeaderMap::new(); // If there's a JWT, add it to the headers if we can decode it. diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index 97ce7a9416386..27bb2d7ef4369 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -535,6 +535,7 @@ pub fn parse_signatures(tokens: Vec) -> ParsedSignatures { RawSelectorImportData::default(), |mut data, signature| { let mut split = signature.split(' '); + #[allow(clippy::collapsible_match)] match split.next() { Some("function") => { if let Some(sig) = split.next() { diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index 77d860a80038d..6fe1672d55000 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -1,7 +1,7 @@ //! terminal utils use foundry_compilers::{ artifacts::remappings::Remapping, - report::{self, BasicStdoutReporter, Reporter}, + report::{self, Reporter}, }; use itertools::Itertools; use semver::Version; @@ -209,19 +209,6 @@ impl Reporter for SpinnerReporter { } } -/// If the output medium is terminal, this calls `f` within the [`SpinnerReporter`] that displays a -/// spinning cursor to display solc progress. -/// -/// If no terminal is available this falls back to common `println!` in [`BasicStdoutReporter`]. -pub fn with_spinner_reporter(project_root: Option, f: impl FnOnce() -> T) -> T { - let reporter = if TERM_SETTINGS.indicate_progress { - report::Report::new(SpinnerReporter::spawn(project_root)) - } else { - report::Report::new(BasicStdoutReporter::default()) - }; - report::with_scoped(&reporter, f) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index 3da6ff9847837..2f3540c453595 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -175,7 +175,7 @@ impl TestFunctionKind { Self::InvariantTest } _ if name.starts_with("table") => Self::TableTest, - _ if name.eq_ignore_ascii_case("setup") => Self::Setup, + _ if name.eq_ignore_ascii_case("setup") && !has_inputs => Self::Setup, _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant, _ if name.starts_with("fixture") => Self::Fixture, _ => Self::Unknown, @@ -285,3 +285,18 @@ impl ErrorExt for T { alloy_sol_types::Revert::from(self.to_string()).abi_encode().into() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_setup_classification() { + // setUp() with no params should be classified as Setup + assert_eq!(TestFunctionKind::classify("setUp", false), TestFunctionKind::Setup); + + // setUp(bytes memory) with params should NOT be classified as Setup + // This is common in Gnosis Safe/Zodiac modules + assert_eq!(TestFunctionKind::classify("setUp", true), TestFunctionKind::Unknown); + } +} diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index b002a94e5fd41..555799326b798 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -2,7 +2,7 @@ use alloy_consensus::{Transaction, TxEnvelope, transaction::SignerRecoverable}; use alloy_eips::eip7702::SignedAuthorization; -use alloy_network::AnyTransactionReceipt; +use alloy_network::{AnyTransactionReceipt, Network, TransactionResponse}; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_provider::{ Provider, @@ -11,55 +11,48 @@ use alloy_provider::{ use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use eyre::Result; -use foundry_common_fmt::UIfmt; +use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TransactionReceiptWithRevertReason { +pub struct TransactionReceiptWithRevertReason { /// The underlying transaction receipt #[serde(flatten)] - pub receipt: AnyTransactionReceipt, + pub receipt: N::ReceiptResponse, /// The revert reason string if the transaction status is failed #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] pub revert_reason: Option, } -impl TransactionReceiptWithRevertReason { - /// Returns if the status of the transaction is 0 (failure) - pub fn is_failure(&self) -> bool { - !self.receipt.inner.inner.inner.receipt.status.coerce_status() - } - +impl TransactionReceiptWithRevertReason +where + N::TxEnvelope: Clone, + N::ReceiptResponse: UIfmtReceiptExt, +{ /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert /// reason was not successfully updated - pub async fn update_revert_reason>( - &mut self, - provider: &P, - ) -> Result<()> { + pub async fn update_revert_reason>(&mut self, provider: &P) -> Result<()> { self.revert_reason = self.fetch_revert_reason(provider).await?; Ok(()) } - async fn fetch_revert_reason>( - &self, - provider: &P, - ) -> Result> { - if !self.is_failure() { + async fn fetch_revert_reason>(&self, provider: &P) -> Result> { + // If the transaction succeeded, there is no revert reason to fetch + if self.receipt.status() { return Ok(None); } let transaction = provider - .get_transaction_by_hash(self.receipt.transaction_hash) + .get_transaction_by_hash(self.receipt.transaction_hash()) .await .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? .ok_or_else(|| eyre::eyre!("transaction not found"))?; - if let Some(block_hash) = self.receipt.block_hash { - let mut call_request: WithOtherFields = - transaction.inner.inner.clone_inner().into(); - call_request.set_from(transaction.inner.inner.signer()); + if let Some(block_hash) = self.receipt.block_hash() { + let mut call_request: N::TransactionRequest = transaction.as_ref().clone().into(); + call_request.set_from(transaction.from()); match provider.call(call_request).block(BlockId::Hash(block_hash.into())).await { Err(e) => return Ok(extract_revert_reason(e.to_string())), Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), @@ -69,19 +62,22 @@ impl TransactionReceiptWithRevertReason { } } -impl From for TransactionReceiptWithRevertReason { +impl From for TransactionReceiptWithRevertReason { fn from(receipt: AnyTransactionReceipt) -> Self { Self { receipt, revert_reason: None } } } -impl From for AnyTransactionReceipt { - fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { +impl From> for AnyTransactionReceipt { + fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { receipt_with_reason.receipt } } -impl UIfmt for TransactionReceiptWithRevertReason { +impl UIfmt for TransactionReceiptWithRevertReason +where + N::ReceiptResponse: UIfmt, +{ fn pretty(&self) -> String { if let Some(revert_reason) = &self.revert_reason { format!( @@ -143,36 +139,20 @@ fn extract_revert_reason>(error_string: S) -> Option { .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) } -/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt -pub fn get_pretty_tx_receipt_attr( - receipt: &TransactionReceiptWithRevertReason, +/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt with revert reason +pub fn get_pretty_receipt_w_reason_attr( + receipt: &TransactionReceiptWithRevertReason, attr: &str, -) -> Option { - match attr { - "blockHash" | "block_hash" => Some(receipt.receipt.block_hash.pretty()), - "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), - "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), - "cumulativeGasUsed" | "cumulative_gas_used" => { - Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) - } - "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.to_string()) - } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), - "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), - "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root().pretty()), - "status" | "statusCode" | "status_code" => { - Some(receipt.receipt.inner.inner.inner.receipt.status.pretty()) - } - "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), - "transactionIndex" | "transaction_index" => { - Some(receipt.receipt.transaction_index.pretty()) - } - "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), - "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), - _ => None, +) -> Option +where + N: Network, + N::ReceiptResponse: UIfmtReceiptExt, +{ + // Handle revert reason first, then delegate to the receipt formatting function + if matches!(attr, "revertReason" | "revert_reason") { + return Some(receipt.revert_reason.pretty()); } + get_pretty_receipt_attr::(&receipt.receipt, attr) } /// Used for broadcasting transactions diff --git a/crates/config/README.md b/crates/config/README.md index b0f210edea79b..ac742cc05db01 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -54,327 +54,15 @@ and merge, at the per-key level: The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, or if it is not set, "default". -### All Options +## Configuration Reference -The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](./src/lib.rs) and [/cli/tests/it/config.rs](../forge/tests/it/config.rs). +For a full list of all configuration options, see the [Foundry Book](https://getfoundry.sh/config/reference/overview): -```toml -## defaults for _all_ profiles -[profile.default] -src = 'src' -test = 'test' -script = 'script' -out = 'out' -libs = ['lib'] -auto_detect_remappings = true # recursive auto-detection of remappings -remappings = [] -# list of libraries to link in the form of `::
`: `"src/MyLib.sol:MyLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6"` -# the supports remappings -libraries = [] -cache = true -cache_path = 'cache' -broadcast = 'broadcast' -# additional solc allow paths -allow_paths = [] -# additional solc include paths -include_paths = [] -force = false -evm_version = 'prague' -gas_reports = ['*'] -gas_reports_ignore = [] -## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value -# solc = '0.8.10' -auto_detect_solc = true -offline = false -optimizer = false -optimizer_runs = 200 -model_checker = { contracts = { 'a.sol' = [ - 'A1', - 'A2', -], 'b.sol' = [ - 'B1', - 'B2', -] }, engine = 'chc', targets = [ - 'assert', - 'outOfBounds', -], timeout = 10000 } -verbosity = 0 -eth_rpc_url = "https://example.com/" -# Setting this option enables decoding of error traces from mainnet deployed / verified contracts via etherscan -etherscan_api_key = "YOURETHERSCANAPIKEY" -# ignore solc warnings for missing license and exceeded contract size -# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname", "too-many-warnings", "constructor-visibility", "init-code-size", "missing-receive-ether", "unnamed-return", "transient-storage"] -# additional warnings can be added using their numeric error code: ["license", 1337] -ignored_error_codes = ["license", "code-size"] -ignored_warnings_from = ["path_to_ignore"] -# Deny compiler warnings and/or notes with: -# - "never": default behavior, no denial -# - "warnings": warnings treated as errors -# - "notes": notes and warnings treated as notes -deny = "never" -match_test = "Foo" -no_match_test = "Bar" -match_contract = "Foo" -no_match_contract = "Bar" -match_path = "*/Foo*" -no_match_path = "*/Bar*" -no_match_coverage = "Baz" -# Number of threads to use. Specifying 0 defaults to the number of logical cores. -threads = 0 -# whether to show test execution progress -show_progress = true -ffi = false -always_use_create_2_factory = false -prompt_timeout = 120 -# These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` -sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -initial_balance = '0xffffffffffffffffffffffff' -block_number = 0 -fork_block_number = 0 -chain_id = 1 -# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds 2**63-1 (9223372036854775807). -# `gas_limit = "max"` is equivalent to `gas_limit = "18446744073709551615"`. This is not recommended -# as it will make infinite loops effectively hang during execution. -gas_limit = 1073741824 -gas_price = 0 -block_base_fee_per_gas = 0 -block_coinbase = '0x0000000000000000000000000000000000000000' -block_timestamp = 0 -block_difficulty = 0 -block_prevrandao = '0x0000000000000000000000000000000000000000' -block_gas_limit = 30000000 -memory_limit = 134217728 -extra_output = ["metadata"] -extra_output_files = [] -names = false -sizes = false -via_ir = false -ast = false -# caches storage retrieved locally for certain chains and endpoints -# can also be restricted to `chains = ["optimism", "mainnet"]` -# by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" -# to disable storage caching entirely set `no_storage_caching = true` -rpc_storage_caching = { chains = "all", endpoints = "all" } -# this overrides `rpc_storage_caching` entirely -no_storage_caching = false -# Whether to store the referenced sources in the metadata as literal data. -use_literal_content = false -# use ipfs method to generate the metadata hash, solc's default. -# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" -bytecode_hash = "ipfs" -# Whether to append the CBOR-encoded metadata file. -cbor_metadata = true -# How to treat revert (and require) reason strings. -# Possible values are: "default", "strip", "debug" and "verboseDebug". -# "default" does not inject compiler-generated revert strings and keeps user-supplied ones. -# "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects -# "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. -# "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) -revert_strings = "default" -# If this option is enabled, Solc is instructed to generate output (bytecode) only for the required contracts -# this can reduce compile time for `forge test` a bit but is considered experimental at this point. -sparse_mode = false -build_info = true -build_info_path = "build-info" -root = "root" -# Configures permissions for cheatcodes that touch the filesystem like `vm.writeFile` -# `access` restricts how the `path` can be accessed via cheatcodes -# `read-write` | `true` => `read` + `write` access allowed (`vm.readFile` + `vm.writeFile`) -# `none`| `false` => no access -# `read` => only read access (`vm.readFile`) -# `write` => only write access (`vm.writeFile`) -# The `allowed_paths` further lists the paths that are considered, e.g. `./` represents the project root directory -# By default, only read access is granted to the project's out dir, so generated artifacts can be read by default -# following example enables read-write access for the project dir : -# `fs_permissions = [{ access = "read-write", path = "./"}]` -fs_permissions = [{ access = "read", path = "./out"}] -# whether failed assertions should revert -# note that this only applies to native (cheatcode) assertions, invoked on Vm contract -assertions_revert = true -# whether `failed()` should be invoked to check if the test have failed -legacy_assertions = false - -[fuzz] -runs = 256 -max_test_rejects = 65536 -seed = '0x3e8' -# Fails the fuzz test if a revert occurs -fail_on_revert = true -# Number of runs to execute and include in the gas report -gas_report_samples = 256 -# Show `console.log` in fuzz test -show_logs = false -# Optional timeout (in seconds) for each property test -# timeout = 60 -# Path where fuzz failures are recorded and replayed -# failure_persist_dir = 'cache/fuzz' - -# Fuzz dictionary configuration -dictionary_weight = 40 -include_storage = true -include_push_bytes = true -# Maximum addresses to record for the fuzz dictionary -max_fuzz_dictionary_addresses = 15728640 -# Maximum values to record for the fuzz dictionary -max_fuzz_dictionary_values = 9830400 -# Maximum literals to seed from the AST for the fuzz dictionary -max_fuzz_dictionary_literals = 6553600 - -# Fuzz corpus configuration (for coverage-guided fuzzing) -# Path to corpus directory, enables coverage guided fuzzing mode -# corpus_dir = 'corpus' -# Whether corpus uses gzip compression -corpus_gzip = true -# Number of mutations until entry marked as eligible to be flushed from in-memory corpus -corpus_min_mutations = 5 -# Minimum corpus size that won't be evicted from memory -corpus_min_size = 0 -# Whether to collect and display edge coverage metrics -show_edge_coverage = false - -[invariant] -runs = 256 -depth = 500 -fail_on_revert = false -call_override = false -# The maximum number of attempts to shrink the sequence -shrink_run_limit = 5000 -# The maximum number of rejects via `vm.assume` which can be encountered during a single invariant run -max_assume_rejects = 65536 -# Number of runs to execute and include in the gas report -gas_report_samples = 256 -# Whether to collect and display fuzzed selectors metrics -show_metrics = true -# Optional timeout (in seconds) for each invariant test -# timeout = 60 -# Display counterexample as solidity calls -show_solidity = false -# Maximum time (in seconds) between generated txs -# max_time_delay = 86400 -# Maximum number of blocks elapsed between generated txs -# max_block_delay = 10000 -# Path where invariant failures are recorded and replayed -# failure_persist_dir = 'cache/invariant' - -# Invariant dictionary configuration -dictionary_weight = 80 -include_storage = true -include_push_bytes = true -# Maximum addresses to record for the fuzz dictionary -max_fuzz_dictionary_addresses = 15728640 -# Maximum values to record for the fuzz dictionary -max_fuzz_dictionary_values = 9830400 -# Maximum literals to seed from the AST for the fuzz dictionary -max_fuzz_dictionary_literals = 6553600 - -# Invariant corpus configuration (for coverage-guided fuzzing) -# Path to corpus directory, enables coverage guided fuzzing mode -# corpus_dir = 'corpus' -# Whether corpus uses gzip compression -corpus_gzip = true -# Number of mutations until entry marked as eligible to be flushed from in-memory corpus -corpus_min_mutations = 5 -# Minimum corpus size that won't be evicted from memory -corpus_min_size = 0 -# Whether to collect and display edge coverage metrics -show_edge_coverage = false - -[fmt] -line_length = 100 -tab_width = 2 -bracket_spacing = true -``` - -#### Additional Optimizer settings - -Optimizer components can be tweaked with the `OptimizerDetails` object: - -See [Compiler Input Description `settings.optimizer.details`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) - -The `optimizer_details` (`optimizerDetails` also works) settings must be prefixed with the profile they correspond -to: `[profile.default.optimizer_details]` -belongs to the `[profile.default]` profile - -```toml -[profile.default.optimizer_details] -constantOptimizer = true -yul = true -# this sets the `yulDetails` of the `optimizer_details` for the `default` profile -[profile.default.optimizer_details.yulDetails] -stackAllocation = true -optimizerSteps = 'dhfoDgvulfnTUtnIf' -``` - -#### RPC-Endpoints settings - -The `rpc_endpoints` value accepts a list of `alias = ""` pairs. - -The following example declares two pairs: -The alias `optimism` references the endpoint URL directly. -The alias `mainnet` references the environment variable `RPC_MAINNET` which holds the entire URL. -The alias `goerli` references an endpoint that will be interpolated with the value the `GOERLI_API_KEY` holds. - -Environment variables need to be wrapped in `${}` - -```toml -[rpc_endpoints] -optimism = "https://optimism.alchemyapi.io/v2/1234567" -mainnet = "${RPC_MAINNET}" -goerli = "https://eth-goerli.alchemyapi.io/v2/${GOERLI_API_KEY}" -``` - -#### Etherscan API Key settings - -The `etherscan` value accepts a list of `alias = "{key = "", url? ="", chain?= """""}"` items. - -the `key` attribute is always required and should contain the actual API key for that chain or an env var that holds the key in the form `${ENV_VAR}` -The `chain` attribute is optional if the `alias` is the already the `chain` name, such as in `mainnet = { key = "${ETHERSCAN_MAINNET_KEY}"}` -The optional `url` attribute can be used to explicitly set the Etherscan API url, this is the recommended setting for chains not natively supported by name. - -```toml -[etherscan] -mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } -mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576", chain = 42 } -unknownchain = { key = "ABCDEFG", url = "https://" } -``` - -##### Additional Model Checker settings - -[Solidity's built-in model checker](https://docs.soliditylang.org/en/latest/smtchecker.html#tutorial) -is an opt-in module that can be enabled via the `ModelChecker` object. - -See [Compiler Input Description `settings.modelChecker`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) -and [the model checker's options](https://docs.soliditylang.org/en/latest/smtchecker.html#smtchecker-options-and-tuning). - -The module is available in `solc` release binaries for OSX and Linux. -The latter requires the z3 library version [4.8.8, 4.8.14] to be installed -in the system (SO version 4.8). - -Similarly to the optimizer settings above, the `model_checker` settings must be -prefixed with the profile they correspond to: `[profile.default.model_checker]` belongs -to the `[profile.default]` profile. - -```toml -[profile.default.model_checker] -contracts = { 'src/Contract.sol' = [ 'Contract' ] } -engine = 'chc' -timeout = 10000 -targets = [ 'assert' ] -``` - -The fields above are recommended when using the model checker. -Setting which contract should be verified is extremely important, otherwise all -available contracts will be verified which can consume a lot of time. -The recommended engine is `chc`, but `bmc` and `all` (runs both) are also -accepted. -It is also important to set a proper timeout (given in milliseconds), since the -default time given to the underlying solvers may not be enough. -If no verification targets are given, only assertions will be checked. - -The model checker will run when `forge build` is invoked, and will show -findings as warnings if any. +- [Default Configuration](https://getfoundry.sh/config/reference/default-config) - All `[profile.default]` options +- [Testing Configuration](https://getfoundry.sh/config/reference/testing) - Fuzz and invariant testing options +- [Formatter Configuration](https://getfoundry.sh/config/reference/formatter) - Code formatting options +- [Linter Configuration](https://getfoundry.sh/config/reference/linter) - Linting options +- [Doc Generator Configuration](https://getfoundry.sh/config/reference/doc-generator) - Documentation generation options ## Environment Variables @@ -386,4 +74,3 @@ supported, this means that `FOUNDRY_SRC` and `DAPP_SRC` are equivalent. Some exceptions to the above are [explicitly ignored](https://github.com/foundry-rs/foundry/blob/10440422e63aae660104e079dfccd5b0ae5fd720/config/src/lib.rs#L1539-L15522) due to security concerns. Environment variables take precedence over values in `foundry.toml`. Values are parsed as a loose form of TOML syntax. -Consider the following examples: diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 0901842a00bff..af25127beb585 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -63,22 +63,6 @@ pub enum RpcEndpointType { } impl RpcEndpointType { - /// Returns the string variant - pub fn as_endpoint_string(&self) -> Option<&RpcEndpointUrl> { - match self { - Self::String(url) => Some(url), - Self::Config(_) => None, - } - } - - /// Returns the config variant - pub fn as_endpoint_config(&self) -> Option<&RpcEndpoint> { - match self { - Self::Config(config) => Some(config), - Self::String(_) => None, - } - } - /// Returns the url or config this type holds /// /// # Error @@ -137,14 +121,6 @@ impl RpcEndpointUrl { } } - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - Self::Env(val) => Some(val), - Self::Url(_) => None, - } - } - /// Returns the url this type holds /// /// # Error diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 67cc8d7e04762..4f9eddc5e3ad6 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -140,6 +140,10 @@ pub enum SolidityErrorCode { TransientStorageUsed, /// There are more than 256 warnings. Ignoring the rest. TooManyWarnings, + /// Warning: 'transfer' is deprecated and scheduled for removal. + TransferDeprecated, + /// Warning: Natspec memory-safe-assembly special comment for inline assembly is deprecated. + NatspecMemorySafeAssemblyDeprecated, /// All other error codes Other(u64), } @@ -167,6 +171,8 @@ impl SolidityErrorCode { Self::PragmaSolidity => "pragma-solidity", Self::TransientStorageUsed => "transient-storage", Self::TooManyWarnings => "too-many-warnings", + Self::TransferDeprecated => "transfer-deprecated", + Self::NatspecMemorySafeAssemblyDeprecated => "natspec-memory-safe-assembly-deprecated", Self::Other(code) => return Err(*code), }; Ok(s) @@ -193,6 +199,8 @@ impl From for u64 { SolidityErrorCode::PragmaSolidity => 3420, SolidityErrorCode::TransientStorageUsed => 2394, SolidityErrorCode::TooManyWarnings => 4591, + SolidityErrorCode::TransferDeprecated => 9207, + SolidityErrorCode::NatspecMemorySafeAssemblyDeprecated => 2424, SolidityErrorCode::Other(code) => code, } } @@ -229,6 +237,8 @@ impl FromStr for SolidityErrorCode { "pragma-solidity" => Self::PragmaSolidity, "transient-storage" => Self::TransientStorageUsed, "too-many-warnings" => Self::TooManyWarnings, + "transfer-deprecated" => Self::TransferDeprecated, + "natspec-memory-safe-assembly-deprecated" => Self::NatspecMemorySafeAssemblyDeprecated, _ => return Err(format!("Unknown variant {s}")), }; @@ -256,6 +266,8 @@ impl From for SolidityErrorCode { 3420 => Self::PragmaSolidity, 2394 => Self::TransientStorageUsed, 4591 => Self::TooManyWarnings, + 9207 => Self::TransferDeprecated, + 2424 => Self::NatspecMemorySafeAssemblyDeprecated, other => Self::Other(other), } } diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 5999825e94681..7fb68deb7accc 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -319,10 +319,17 @@ impl ResolvedEtherscanConfig { .with_client(client) .with_api_key(api_key) .with_cache(cache, Duration::from_secs(24 * 60 * 60)); - if let Some(browser_url) = browser_url { + if let Some(ref browser_url) = browser_url { client_builder = client_builder.with_url(browser_url)?; } - client_builder.chain(chain)?.build() + + // Use the provided URL (either custom from foundry.toml or chain's default from resolve()) + client_builder = client_builder.with_api_url(api_url.clone())?; + // Fallback: Use api_url as browser URL if browser_url is not set + if browser_url.is_none() { + client_builder = client_builder.with_url(api_url)?; + } + client_builder.build() } } @@ -343,22 +350,6 @@ pub enum EtherscanApiKey { } impl EtherscanApiKey { - /// Returns the key variant - pub fn as_key(&self) -> Option<&str> { - match self { - Self::Key(url) => Some(url), - Self::Env(_) => None, - } - } - - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - Self::Env(val) => Some(val), - Self::Key(_) => None, - } - } - /// Returns the key this type holds /// /// # Error @@ -478,10 +469,8 @@ mod tests { let config = resolved.remove("mainnet").unwrap().unwrap(); assert_eq!(config.key, "ABCDEFG"); let client = config.into_client().unwrap(); - assert_eq!( - client.etherscan_api_url().as_str(), - "https://api.etherscan.io/v2/api?chainid=1" - ); + // Custom URL should be used even when chain has a default URL + assert_eq!(client.etherscan_api_url().as_str(), "https://api.etherscan.io/api"); unsafe { std::env::remove_var(env); @@ -518,4 +507,50 @@ mod tests { let resolved = config.resolve(Some("base-sepolia")).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); } + + #[test] + fn can_create_client_with_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should work if a custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: Some("https://custom.api.url/verify/etherscan".to_string()), + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap().unwrap(); + let result = config.into_client(); + assert!( + result.is_ok(), + "Should succeed with custom URL even for chains without default Etherscan URLs" + ); + } + + #[test] + fn fails_without_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should fail if no custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: None, + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap(); + + assert!( + config.is_err(), + "Should fail: chains without default Etherscan URLs require custom URL" + ); + } } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 673b692fbe4fd..bea82b9a45449 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -37,6 +37,8 @@ pub struct FormatterConfig { pub contract_new_lines: bool, /// Sort import statements alphabetically in groups (a group is separated by a newline). pub sort_imports: bool, + /// Choose between `import "a" as name` and `import * as name from "a"` + pub namespace_import_style: NamespaceImportStyle, /// Whether to suppress spaces around the power operator (`**`). pub pow_no_space: bool, /// Style that determines if a broken list, should keep its elements together on their own @@ -189,6 +191,18 @@ impl MultilineFuncHeaderStyle { } } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NamespaceImportStyle { + /// prefer plain imports: `import "source" as name;` + #[default] + PreferPlain, + /// prefer glob imports: `import * as name from "source";` + PreferGlob, + /// preserve the original style + Preserve, +} + /// Style that determines if a broken list, should keep its elements together on their own line, /// before breaking individually. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -250,6 +264,7 @@ impl Default for FormatterConfig { ignore: vec![], contract_new_lines: false, sort_imports: false, + namespace_import_style: NamespaceImportStyle::default(), pow_no_space: false, prefer_compact: PreferCompact::default(), docs_style: DocCommentStyle::default(), diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 9f2ba141897a8..cb964f97f68be 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -12,11 +12,8 @@ pub struct FuzzConfig { pub runs: u32, /// Fails the fuzzed test if a revert occurs. pub fail_on_revert: bool, - /// The maximum number of test case rejections allowed by proptest, to be - /// encountered during usage of `vm.assume` cheatcode. This will be used - /// to set the `max_global_rejects` value in proptest test runner config. - /// `max_local_rejects` option isn't exposed here since we're not using - /// `prop_filter`. + /// The maximum number of test case rejections allowed, + /// encountered during usage of `vm.assume` cheatcode. pub max_test_rejects: u32, /// Optional seed for the fuzzing RNG algorithm pub seed: Option, diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 4a34a894b6d2e..591af88efdee4 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -41,6 +41,14 @@ pub struct InvariantConfig { pub max_time_delay: Option, /// Maximum number of blocks elapsed between generated txs. pub max_block_delay: Option, + /// Number of calls to execute between invariant assertions. + /// + /// - `0`: Only assert on the last call of each run (fastest, but may miss exact breaking call) + /// - `1` (default): Assert after every call (current behavior, most precise) + /// - `N`: Assert every N calls AND always on the last call + /// + /// Example: `check_interval = 10` means assert after calls 10, 20, 30, ... and the last call. + pub check_interval: u32, } impl Default for InvariantConfig { @@ -61,6 +69,7 @@ impl Default for InvariantConfig { show_solidity: false, max_time_delay: None, max_block_delay: None, + check_interval: 1, } } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d54cb0aac63f5..c133fa01291c4 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -286,6 +286,11 @@ pub struct Config { pub eth_rpc_url: Option, /// Whether to accept invalid certificates for the rpc server. pub eth_rpc_accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection for the rpc server. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub eth_rpc_no_proxy: bool, /// JWT secret that should be used for any rpc calls pub eth_rpc_jwt: Option, /// Timeout that should be used for any rpc calls @@ -299,6 +304,8 @@ pub struct Config { /// You can also the ETH_RPC_HEADERS env variable like so: /// `ETH_RPC_HEADERS="x-custom-header:value x-another-header:another-value"` pub eth_rpc_headers: Option>, + /// Print the equivalent curl command instead of making the RPC request. + pub eth_rpc_curl: bool, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, /// Multiple etherscan api configs and their aliases @@ -347,6 +354,8 @@ pub struct Config { pub invariant: InvariantConfig, /// Whether to allow ffi cheatcodes in test pub ffi: bool, + /// Whether to show `console.log` outputs in realtime during script/test execution + pub live_logs: bool, /// Whether to allow `expectRevert` for internal functions. pub allow_internal_expect_revert: bool, /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. @@ -747,6 +756,18 @@ impl Config { Self::from_provider(Self::figment_with_root(root.as_ref())) } + /// Loads the `Config` from the given root directory, allowing profile fallback. + /// + /// Unlike [`load_with_root`](Self::load_with_root), if the selected profile (via + /// `FOUNDRY_PROFILE`) does not exist in the config, this falls back to the default profile + /// instead of returning an error. This is useful for loading nested lib/dependency configs + /// that may not define all profiles the main project uses. + #[track_caller] + pub fn load_with_root_and_fallback(root: impl AsRef) -> Result { + let figment = Self::figment_with_root(root.as_ref()); + Self::from_figment_fallback(Figment::from(figment)) + } + /// Attempts to extract a `Config` from `provider`, returning the result. /// /// # Example @@ -774,25 +795,49 @@ impl Config { } fn from_figment(figment: Figment) -> Result { + Self::from_figment_inner(figment, true) + } + + /// Same as `from_figment` but allows unknown profiles, falling back to default profile. + /// Used when loading nested lib configs that may not define all profiles. + fn from_figment_fallback(figment: Figment) -> Result { + Self::from_figment_inner(figment, false) + } + + fn from_figment_inner( + figment: Figment, + strict_profile: bool, + ) -> Result { let mut config = figment.extract::().map_err(ExtractConfigError::new)?; - config.profile = figment.profile().clone(); + let selected_profile = figment.profile().clone(); // The `"profile"` profile contains all the profiles as keys. - let mut add_profile = |profile: &Profile| { - if !config.profiles.contains(profile) { - config.profiles.push(profile.clone()); + fn add_profile(profiles: &mut Vec, profile: &Profile) { + if !profiles.contains(profile) { + profiles.push(profile.clone()); } - }; + } let figment = figment.select(Self::PROFILE_SECTION); if let Ok(data) = figment.data() && let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) { for profile in profiles.keys() { - add_profile(&Profile::new(profile)); + add_profile(&mut config.profiles, &Profile::new(profile)); } } - add_profile(&Self::DEFAULT_PROFILE); - add_profile(&config.profile); + add_profile(&mut config.profiles, &Self::DEFAULT_PROFILE); + + // Check if the selected profile exists. + if config.profiles.contains(&selected_profile) { + config.profile = selected_profile; + } else if strict_profile { + return Err(ExtractConfigError::new(Error::from(format!( + "selected profile `{selected_profile}` does not exist" + )))); + } else { + // Fall back to default profile for nested lib configs. + config.profile = Self::DEFAULT_PROFILE; + } config.normalize_optimizer_settings(); @@ -1822,11 +1867,6 @@ impl Config { src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), libs: paths.libraries.into_iter().map(|lib| lib.file_name().unwrap().into()).collect(), - remappings: paths - .remappings - .into_iter() - .map(|r| RelativeRemapping::new(r, root)) - .collect(), fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), ..Self::default() } @@ -2287,8 +2327,8 @@ impl Config { figment = figment.merge(("evm_version", version)); } - // Normalize `deny` based on the provided `deny_warnings` version. - if self.deny_warnings + // Normalize `deny` based on the provided `deny_warnings` value. + if figment.extract_inner::("deny_warnings").unwrap_or(false) && let Ok(DenyLevel::Never) = figment.extract_inner("deny") { figment = figment.merge(("deny", DenyLevel::Warnings)); @@ -2499,7 +2539,7 @@ impl Default for Config { allow_paths: vec![], include_paths: vec![], force: false, - evm_version: EvmVersion::Prague, + evm_version: EvmVersion::Osaka, gas_reports: vec!["*".to_string()], gas_reports_ignore: vec![], gas_reports_include_tests: false, @@ -2529,6 +2569,7 @@ impl Default for Config { invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, ffi: false, + live_logs: false, allow_internal_expect_revert: false, prompt_timeout: 120, sender: Self::DEFAULT_SENDER, @@ -2551,9 +2592,11 @@ impl Default for Config { memory_limit: 1 << 27, // 2**27 = 128MiB = 134_217_728 bytes eth_rpc_url: None, eth_rpc_accept_invalid_certs: false, + eth_rpc_no_proxy: false, eth_rpc_jwt: None, eth_rpc_timeout: None, eth_rpc_headers: None, + eth_rpc_curl: false, etherscan_api_key: None, verbosity: 0, remappings: vec![], @@ -2564,6 +2607,8 @@ impl Default for Config { SolidityErrorCode::ContractExceeds24576Bytes, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, SolidityErrorCode::TransientStorageUsed, + SolidityErrorCode::TransferDeprecated, + SolidityErrorCode::NatspecMemorySafeAssemblyDeprecated, ], ignored_file_paths: vec![], deny: DenyLevel::Never, @@ -4625,6 +4670,48 @@ mod tests { }); } + #[test] + fn test_model_checker_settings_with_bool_flags() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [profile.default] + + [profile.default.model_checker] + engine = 'chc' + show_unproved = true + show_unsupported = true + show_proved_safe = false + div_mod_with_slacks = true + ", + )?; + let mut loaded = Config::load().unwrap(); + clear_warning(&mut loaded); + + let mc = loaded.model_checker.as_ref().unwrap(); + assert_eq!(mc.show_unproved, Some(true)); + assert_eq!(mc.show_unsupported, Some(true)); + assert_eq!(mc.show_proved_safe, Some(false)); + assert_eq!(mc.div_mod_with_slacks, Some(true)); + + // Test round-trip: serialize and reload + let s = loaded.to_string_pretty().unwrap(); + jail.create_file("foundry.toml", &s)?; + + let mut reloaded = Config::load().unwrap(); + clear_warning(&mut reloaded); + + let mc_reloaded = reloaded.model_checker.as_ref().unwrap(); + assert_eq!(mc_reloaded.show_unproved, Some(true)); + assert_eq!(mc_reloaded.show_unsupported, Some(true)); + assert_eq!(mc_reloaded.show_proved_safe, Some(false)); + assert_eq!(mc_reloaded.div_mod_with_slacks, Some(true)); + + Ok(()) + }); + } + #[test] fn test_model_checker_settings_relative_paths() { figment::Jail::expect_with(|jail| { @@ -6466,4 +6553,643 @@ mod tests { Ok(()) }); } + + #[test] + fn warns_on_unknown_keys_in_all_config_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + unknown_profile_key = "should_warn" + + # Standalone sections with unknown keys + [fmt] + line_length = 120 + unknown_fmt_key = "should_warn" + + [lint] + severity = ["high"] + unknown_lint_key = "should_warn" + + [doc] + out = "docs" + unknown_doc_key = "should_warn" + + [fuzz] + runs = 256 + unknown_fuzz_key = "should_warn" + + [invariant] + runs = 256 + unknown_invariant_key = "should_warn" + + [vyper] + unknown_vyper_key = "should_warn" + + [bind_json] + out = "bindings.sol" + unknown_bind_json_key = "should_warn" + + # Nested profile sections with unknown keys + [profile.default.fmt] + line_length = 100 + unknown_nested_fmt_key = "should_warn" + + [profile.default.lint] + severity = ["low"] + unknown_nested_lint_key = "should_warn" + + [profile.default.doc] + out = "documentation" + unknown_nested_doc_key = "should_warn" + + [profile.default.fuzz] + runs = 512 + unknown_nested_fuzz_key = "should_warn" + + [profile.default.invariant] + runs = 512 + unknown_nested_invariant_key = "should_warn" + + [profile.default.vyper] + unknown_nested_vyper_key = "should_warn" + + [profile.default.bind_json] + out = "nested_bindings.sol" + unknown_nested_bind_json_key = "should_warn" + + # Array sections with unknown keys + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + unknown_compilation_key = "should_warn" + + [[profile.default.additional_compiler_profiles]] + name = "via-ir" + via_ir = true + unknown_compiler_profile_key = "should_warn" + "#, + )?; + + let cfg = Config::load().unwrap(); + + // Expected warnings for profile-level unknown key + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownKey { key, .. } if key == "unknown_profile_key" + )), + "Expected warning for 'unknown_profile_key' in profile, got: {:?}", + cfg.warnings + ); + + // Expected warnings for standalone sections + let standalone_expected = [ + ("unknown_fmt_key", "fmt"), + ("unknown_lint_key", "lint"), + ("unknown_doc_key", "doc"), + ("unknown_fuzz_key", "fuzz"), + ("unknown_invariant_key", "invariant"), + ("unknown_vyper_key", "vyper"), + ("unknown_bind_json_key", "bind_json"), + ]; + + for (expected_key, expected_section) in standalone_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in standalone section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Expected warnings for nested profile sections + let nested_expected = [ + ("unknown_nested_fmt_key", "fmt"), + ("unknown_nested_lint_key", "lint"), + ("unknown_nested_doc_key", "doc"), + ("unknown_nested_fuzz_key", "fuzz"), + ("unknown_nested_invariant_key", "invariant"), + ("unknown_nested_vyper_key", "vyper"), + ("unknown_nested_bind_json_key", "bind_json"), + ]; + + for (expected_key, expected_section) in nested_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in nested section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Expected warnings for array item sections + let array_expected = [ + ("unknown_compilation_key", "compilation_restrictions"), + ("unknown_compiler_profile_key", "additional_compiler_profiles"), + ]; + + for (expected_key, expected_section) in array_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in array section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Verify total count of unknown key warnings + let unknown_key_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!(w, crate::Warning::UnknownKey { .. }) + || matches!(w, crate::Warning::UnknownSectionKey { .. }) + }) + .collect(); + + // 1 profile key + 7 standalone + 7 nested + 2 array = 17 total + assert_eq!( + unknown_key_warnings.len(), + 17, + "Expected 17 unknown key warnings (1 profile + 7 standalone + 7 nested + 2 array), got {}: {:?}", + unknown_key_warnings.len(), + unknown_key_warnings + ); + + Ok(()) + }); + } + + #[test] + fn warns_on_unknown_keys_in_extended_config() { + figment::Jail::expect_with(|jail| { + // Create base config with unknown keys + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer_runs = 800 + unknown_base_profile_key = "should_warn" + + [lint] + severity = ["high"] + unknown_base_lint_key = "should_warn" + + [fmt] + line_length = 100 + unknown_base_fmt_key = "should_warn" + "#, + )?; + + // Create local config that extends base with its own unknown keys + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + src = "src" + unknown_local_profile_key = "should_warn" + + [lint] + unknown_local_lint_key = "should_warn" + + [fuzz] + runs = 512 + unknown_local_fuzz_key = "should_warn" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + unknown_local_restriction_key = "should_warn" + "#, + )?; + + let cfg = Config::load().unwrap(); + + // Verify base config values are inherited + assert_eq!(cfg.optimizer_runs, Some(800)); + + // Unknown keys from both base and local configs should be detected. + // Note: Due to how figment merges configs before validation, the source + // will show the local config file for all warnings. This is a known + // limitation - proper source attribution for extended configs would + // require validating each file before the merge. + + // Verify all expected unknown keys are detected + let expected_unknown_keys = ["unknown_base_profile_key", "unknown_local_profile_key"]; + for expected_key in expected_unknown_keys { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownKey { key, .. } if key == expected_key + )), + "Expected warning for '{}', got: {:?}", + expected_key, + cfg.warnings + ); + } + + let expected_section_keys = [ + ("unknown_base_lint_key", "lint"), + ("unknown_base_fmt_key", "fmt"), + ("unknown_local_lint_key", "lint"), + ("unknown_local_fuzz_key", "fuzz"), + ("unknown_local_restriction_key", "compilation_restrictions"), + ]; + for (expected_key, expected_section) in expected_section_keys { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Verify total: 2 profile keys + 5 section keys = 7 warnings + let unknown_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!(w, crate::Warning::UnknownKey { .. }) + || matches!(w, crate::Warning::UnknownSectionKey { .. }) + }) + .collect(); + assert_eq!( + unknown_warnings.len(), + 7, + "Expected 7 unknown key warnings, got {}: {:?}", + unknown_warnings.len(), + unknown_warnings + ); + + Ok(()) + }); + } + + // Test for issue #12844: FOUNDRY_PROFILE=nonexistent should fail + #[test] + fn fails_on_unknown_profile() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "nonexistent"); + let err = Config::load().expect_err("expected unknown profile to fail"); + let err_msg = err.to_string(); + assert!( + err_msg.contains("selected profile `nonexistent` does not exist"), + "Expected error about nonexistent profile, got: {err_msg}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: vyper config keys should not trigger unknown key warnings + #[test] + fn no_false_warnings_for_vyper_config_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [vyper] + optimize = "gas" + path = "/usr/bin/vyper" + experimental_codegen = true + "#, + )?; + + let cfg = Config::load().unwrap(); + // None of the valid vyper keys should trigger warnings + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid vyper keys should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: vyper config in profile should not trigger false warnings + #[test] + fn no_false_warnings_for_nested_vyper_config_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.default.vyper] + optimize = "codesize" + path = "/opt/vyper/bin/vyper" + experimental_codegen = false + "#, + )?; + + let cfg = Config::load().unwrap(); + // None of the valid vyper keys should trigger warnings + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid nested vyper keys should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: inline vyper config format should not trigger false warnings + // This matches the exact format used in https://github.com/pcaversaccio/snekmate + #[test] + fn no_false_warnings_for_inline_vyper_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + vyper = { optimize = "gas" } + + [profile.default-venom] + vyper = { experimental_codegen = true } + + [profile.ci-venom] + vyper = { experimental_codegen = true } + "#, + )?; + + let cfg = Config::load().unwrap(); + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid inline vyper config should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: unknown vyper keys should still warn + #[test] + fn warns_on_unknown_vyper_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [vyper] + optimize = "gas" + unknown_vyper_option = true + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == "unknown_vyper_option" && section == "vyper" + )), + "Unknown vyper key should trigger warning, got: {:?}", + cfg.warnings + ); + + Ok(()) + }); + } + + // Test for issue #12844: known profile should work + #[test] + fn succeeds_on_known_profile() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci] + src = "src" + fuzz = { runs = 10000 } + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "ci"); + let config = Config::load().expect("known profile should work"); + assert_eq!(config.profile.as_str(), "ci"); + assert_eq!(config.fuzz.runs, 10000); + + Ok(()) + }); + } + + // Test for issue #12963: nested lib configs should fallback to default profile + // when they don't define the requested profile + #[test] + fn nested_lib_config_falls_back_to_default_profile() { + figment::Jail::expect_with(|jail| { + // Create a lib directory with only default profile + let lib_path = jail.directory().join("lib/mylib"); + std::fs::create_dir_all(&lib_path).unwrap(); + jail.create_file( + "lib/mylib/foundry.toml", + r#" + [profile.default] + src = "contracts" + "#, + )?; + + // Set a profile that doesn't exist in the lib + jail.set_env("FOUNDRY_PROFILE", "ci"); + + // load_with_root_and_fallback should succeed and fall back to default + let config = Config::load_with_root_and_fallback(&lib_path) + .expect("lib config should load with fallback"); + assert_eq!(config.profile, Config::DEFAULT_PROFILE); + assert_eq!(config.src.as_os_str(), "contracts"); + + Ok(()) + }); + } + + // Test for issue #12963: nested lib configs should use requested profile if it exists + #[test] + fn nested_lib_config_uses_profile_if_exists() { + figment::Jail::expect_with(|jail| { + // Create a lib directory with both default and ci profiles + let lib_path = jail.directory().join("lib/mylib"); + std::fs::create_dir_all(&lib_path).unwrap(); + jail.create_file( + "lib/mylib/foundry.toml", + r#" + [profile.default] + src = "contracts" + + [profile.ci] + src = "contracts" + fuzz = { runs = 5000 } + "#, + )?; + + // Set a profile that exists in the lib + jail.set_env("FOUNDRY_PROFILE", "ci"); + + // load_with_root_and_fallback should use the ci profile + let config = Config::load_with_root_and_fallback(&lib_path) + .expect("lib config should load with profile"); + assert_eq!(config.profile.as_str(), "ci"); + assert_eq!(config.fuzz.runs, 5000); + + Ok(()) + }); + } + + // Test for issue #13170: profile names with hyphens should work correctly + #[test] + fn succeeds_on_hyphenated_profile_name() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci-venom] + src = "src" + fuzz = { runs = 7500 } + + [profile.default-venom] + src = "src" + fuzz = { runs = 8000 } + "#, + )?; + + // Test ci-venom profile + jail.set_env("FOUNDRY_PROFILE", "ci-venom"); + let config = Config::load().expect("hyphenated profile should work"); + assert_eq!(config.profile.as_str(), "ci-venom"); + assert_eq!(config.fuzz.runs, 7500); + + // Test default-venom profile + jail.set_env("FOUNDRY_PROFILE", "default-venom"); + let config = Config::load().expect("hyphenated profile should work"); + assert_eq!(config.profile.as_str(), "default-venom"); + assert_eq!(config.fuzz.runs, 8000); + + // Verify the profiles list contains hyphenated names + assert!( + config.profiles.iter().any(|p| p.as_str() == "ci-venom"), + "profiles should contain 'ci-venom', got: {:?}", + config.profiles + ); + assert!( + config.profiles.iter().any(|p| p.as_str() == "default-venom"), + "profiles should contain 'default-venom', got: {:?}", + config.profiles + ); + + Ok(()) + }); + } + + // Test for issue #13170: hyphenated profile with nested config keys + #[test] + fn hyphenated_profile_with_nested_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci-venom] + src = "src" + optimizer_runs = 500 + + [profile.ci-venom.fuzz] + runs = 10000 + max_test_rejects = 350000 + + [profile.ci-venom.invariant] + runs = 375 + depth = 500 + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "ci-venom"); + let config = + Config::load().expect("hyphenated profile with nested sections should work"); + assert_eq!(config.profile.as_str(), "ci-venom"); + assert_eq!(config.optimizer_runs, Some(500)); + assert_eq!(config.fuzz.runs, 10000); + assert_eq!(config.fuzz.max_test_rejects, 350000); + assert_eq!(config.invariant.runs, 375); + assert_eq!(config.invariant.depth, 500); + + Ok(()) + }); + } } diff --git a/crates/config/src/lint.rs b/crates/config/src/lint.rs index c726e3ecc8f5b..25094ead98cf8 100644 --- a/crates/config/src/lint.rs +++ b/crates/config/src/lint.rs @@ -28,8 +28,8 @@ pub struct LinterConfig { /// Configurable patterns that should be excluded when performing `mixedCase` lint checks. /// - /// Default's to ["ERC", "URI"] to allow common names like `rescueERC20`, `ERC721TokenReceiver` - /// or `tokenURI`. + /// Defaults to common abbreviations: `ERC`, `URI`, `ID`, `URL`, `API`, `JSON`, `XML`, `HTML`, + /// `HTTP`, `HTTPS`. This allows names like `marketID`, `tokenURI`, `apiURL`, `parseJSON`, etc. pub mixed_case_exceptions: Vec, } @@ -40,7 +40,18 @@ impl Default for LinterConfig { severity: vec![Severity::High, Severity::Med, Severity::Low], exclude_lints: Vec::new(), ignore: Vec::new(), - mixed_case_exceptions: vec!["ERC".to_string(), "URI".to_string()], + mixed_case_exceptions: vec![ + "ERC".to_string(), + "URI".to_string(), + "ID".to_string(), + "URL".to_string(), + "API".to_string(), + "JSON".to_string(), + "XML".to_string(), + "HTML".to_string(), + "HTTP".to_string(), + "HTTPS".to_string(), + ], } } } diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs index e5d2d6e3ade81..a6404da946cd3 100644 --- a/crates/config/src/providers/ext.rs +++ b/crates/config/src/providers/ext.rs @@ -253,6 +253,9 @@ impl Provider for TomlFileProvider { /// A Provider that ensures all keys are snake case if they're not standalone sections, See /// `Config::STANDALONE_SECTIONS` +/// +/// For the `[profile]` section, profile names (like `ci-venom`) are preserved as-is, +/// but the config keys within each profile are still converted to snake_case. pub(crate) struct ForcedSnakeCaseData

(pub(crate) P); impl Provider for ForcedSnakeCaseData

{ @@ -267,6 +270,22 @@ impl Provider for ForcedSnakeCaseData

{ // don't force snake case for keys in standalone sections continue; } + + if profile.as_str().as_str() == Config::PROFILE_SECTION { + // For the `[profile]` section, we need to preserve profile names (the keys) + // but snake_case the config keys within each profile's dict. + let dict2 = std::mem::take(dict); + *dict = dict2 + .into_iter() + .map(|(profile_name, v)| { + // Keep the profile name exactly as-is (e.g., "ci-venom" stays "ci-venom") + let v = snake_case_value_keys(v); + (profile_name, v) + }) + .collect(); + continue; + } + let dict2 = std::mem::take(dict); *dict = dict2.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect(); } @@ -278,6 +297,24 @@ impl Provider for ForcedSnakeCaseData

{ } } +/// Recursively converts all keys in a Value (if it's a Dict) to snake_case. +fn snake_case_value_keys(value: Value) -> Value { + match value { + Value::Dict(tag, dict) => { + let new_dict = dict + .into_iter() + .map(|(k, v)| (k.to_snake_case(), snake_case_value_keys(v))) + .collect(); + Value::Dict(tag, new_dict) + } + Value::Array(tag, arr) => { + let new_arr = arr.into_iter().map(snake_case_value_keys).collect(); + Value::Array(tag, new_arr) + } + other => other, + } +} + /// A Provider that handles breaking changes in toml files pub(crate) struct BackwardsCompatTomlProvider

(pub(crate) P); diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 8890de642487c..0f308aa1ba523 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -166,14 +166,15 @@ impl RemappingsProvider<'_> { ) { let context_mappings = mappings.entry(context).or_default(); match context_mappings.entry(key) { - Entry::Occupied(mut e) => { - if e.get().components().count() > path.components().count() { - e.insert(path); - } + Entry::Occupied(mut e) + if e.get().components().count() > path.components().count() => + { + e.insert(path); } Entry::Vacant(e) => { e.insert(path); } + _ => {} } } @@ -258,8 +259,9 @@ impl RemappingsProvider<'_> { } fn nested_foundry_remappings(&self, lib: &Path) -> Vec { - // load config, of the nested lib if it exists - let Ok(config) = Config::load_with_root(lib) else { return vec![] }; + // load config of the nested lib if it exists, using fallback mode since libs may not + // define all profiles the main project uses + let Ok(config) = Config::load_with_root_and_fallback(lib) else { return vec![] }; let config = config.sanitized(); // if the configured _src_ directory is set to something that diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs index 881399bae0bbe..9113fcd76fffa 100644 --- a/crates/config/src/providers/warnings.rs +++ b/crates/config/src/providers/warnings.rs @@ -4,7 +4,41 @@ use figment::{ value::{Dict, Map, Value}, }; use heck::ToSnakeCase; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; + +/// Allowed keys for CompilationRestrictions. +const COMPILATION_RESTRICTIONS_KEYS: &[&str] = &[ + "paths", + "version", + "via_ir", + "bytecode_hash", + "min_optimizer_runs", + "optimizer_runs", + "max_optimizer_runs", + "min_evm_version", + "evm_version", + "max_evm_version", +]; + +/// Allowed keys for SettingsOverrides. +const SETTINGS_OVERRIDES_KEYS: &[&str] = + &["name", "via_ir", "evm_version", "optimizer", "optimizer_runs", "bytecode_hash"]; + +/// Allowed keys for VyperConfig. +/// Required because VyperConfig uses `skip_serializing_if = "Option::is_none"` on all fields, +/// causing the default serialization to produce an empty dict. +const VYPER_KEYS: &[&str] = &["optimize", "path", "experimental_codegen"]; + +/// Allowed keys for DocConfig. +/// Required because DocConfig uses `skip_serializing_if = "Option::is_none"` on some fields +/// (`repository`, `path`), whose defaults are `None` and thus excluded from serialization. +const DOC_KEYS: &[&str] = &["out", "title", "book", "homepage", "repository", "path", "ignore"]; + +/// Reserved keys that should not trigger unknown key warnings. +const RESERVED_KEYS: &[&str] = &["extends"]; + +/// Keys kept for backward compatibility that should not trigger unknown key warnings. +const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version"]; /// Generate warnings for unknown sections and deprecated keys pub struct WarningsProvider

{ @@ -84,9 +118,8 @@ impl WarningsProvider

{ if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data() && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE) { - let allowed_keys: std::collections::BTreeSet = - default_dict.keys().cloned().collect(); - for profile_map in profiles { + let allowed_keys: BTreeSet = default_dict.keys().cloned().collect(); + for profile_map in profiles.clone() { for (profile, value) in profile_map { let Some(profile_dict) = value.as_dict() else { continue; @@ -103,8 +136,10 @@ impl WarningsProvider

{ !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key); let is_not_allowed = !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case()); - let is_not_reserved = key != "extends" && key != Self::WARNINGS_KEY; - let is_not_backward_compatible = key != "solc_version"; + let is_not_reserved = + !RESERVED_KEYS.contains(&key.as_str()) && key != Self::WARNINGS_KEY; + let is_not_backward_compatible = + !BACKWARD_COMPATIBLE_KEYS.contains(&key.as_str()); if is_not_deprecated && is_not_allowed @@ -118,12 +153,160 @@ impl WarningsProvider

{ }); } } + + // Add warning for unknown keys in nested sections within profiles. + self.collect_nested_section_warnings( + profile_dict, + default_dict, + &source, + &mut out, + ); } } + + // Add warning for unknown keys in standalone sections. + self.collect_standalone_section_warnings(&data, default_dict, &mut out); } Ok(out) } + + /// Collects warnings for unknown keys in standalone sections like `[lint]`, `[fmt]`, etc. + fn collect_standalone_section_warnings( + &self, + data: &Map, + default_dict: &Dict, + out: &mut Vec, + ) { + let source = self + .provider + .metadata() + .source + .map(|s| s.to_string()) + .unwrap_or(Config::FILE_NAME.to_string()); + + for section_name in Config::STANDALONE_SECTIONS { + // Get the section from the parsed data + let section_profile = Profile::new(section_name); + let Some(section_dict) = data.get(§ion_profile) else { + continue; + }; + + // Get allowed keys for this section from the default config + // Special case for vyper: VyperConfig uses skip_serializing_if on all Option fields, + // so the default serialization produces an empty dict. Use explicit keys instead. + let allowed_keys: BTreeSet = if *section_name == "vyper" { + VYPER_KEYS.iter().map(|s| s.to_string()).collect() + } else if *section_name == "doc" { + DOC_KEYS.iter().map(|s| s.to_string()).collect() + } else { + let Some(default_section_value) = default_dict.get(*section_name) else { + continue; + }; + let Some(default_section_dict) = default_section_value.as_dict() else { + continue; + }; + default_section_dict.keys().cloned().collect() + }; + + for key in section_dict.keys() { + let is_not_allowed = + !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: key.clone(), + section: section_name.to_string(), + source: source.clone(), + }); + } + } + } + } + + /// Collects warnings for unknown keys in nested sections within profiles, + /// like `compilation_restrictions`. + fn collect_nested_section_warnings( + &self, + profile_dict: &Dict, + default_dict: &Dict, + source: &str, + out: &mut Vec, + ) { + // Check nested sections that are dicts (like `lint`, `fmt` when defined in profile) + for (key, value) in profile_dict { + let Some(nested_dict) = value.as_dict() else { + // Also check arrays of dicts (like `compilation_restrictions`) + if let Some(arr) = value.as_array() { + // Get allowed keys for known array item types + let allowed_keys = Self::get_array_item_allowed_keys(key); + + if allowed_keys.is_empty() { + continue; + } + + for item in arr { + let Some(item_dict) = item.as_dict() else { + continue; + }; + for item_key in item_dict.keys() { + let is_not_allowed = !allowed_keys.contains(item_key) + && !allowed_keys.contains(&item_key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: item_key.clone(), + section: key.clone(), + source: source.to_string(), + }); + } + } + } + } + continue; + }; + + // Get allowed keys from the default config for this nested section + // Special case for vyper: VyperConfig uses skip_serializing_if on all Option fields, + // so the default serialization produces an empty dict. Use explicit keys instead. + let allowed_keys: BTreeSet = if key == "vyper" { + VYPER_KEYS.iter().map(|s| s.to_string()).collect() + } else if key == "doc" { + DOC_KEYS.iter().map(|s| s.to_string()).collect() + } else { + let Some(default_value) = default_dict.get(key) else { + continue; + }; + let Some(default_nested_dict) = default_value.as_dict() else { + continue; + }; + default_nested_dict.keys().cloned().collect() + }; + + for nested_key in nested_dict.keys() { + let is_not_allowed = !allowed_keys.contains(nested_key) + && !allowed_keys.contains(&nested_key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: nested_key.clone(), + section: key.clone(), + source: source.to_string(), + }); + } + } + } + } + + /// Returns the allowed keys for array item types based on the section name. + fn get_array_item_allowed_keys(section_name: &str) -> BTreeSet { + match section_name { + "compilation_restrictions" => { + COMPILATION_RESTRICTIONS_KEYS.iter().map(|s| s.to_string()).collect() + } + "additional_compiler_profiles" => { + SETTINGS_OVERRIDES_KEYS.iter().map(|s| s.to_string()).collect() + } + _ => BTreeSet::new(), + } + } } impl Provider for WarningsProvider

{ diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 52e751cd2ca03..624dd5a35f81b 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -213,7 +213,7 @@ where deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom) } -/// Deserialize into `U256` from either a `u64` or a `U256` hex string. +/// Deserialize into `U256` from either a `u64`, a `U256` hex string, or a decimal string. pub fn deserialize_u64_to_u256<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -223,11 +223,16 @@ where enum NumericValue { U256(U256), U64(u64), + String(String), } match NumericValue::deserialize(deserializer)? { NumericValue::U64(n) => Ok(U256::from(n)), NumericValue::U256(n) => Ok(n), + NumericValue::String(s) => { + // Handle decimal strings (e.g., "18446744073709551615") + U256::from_str(&s).map_err(D::Error::custom) + } } } diff --git a/crates/config/src/warning.rs b/crates/config/src/warning.rs index 4476f1f5e9fcd..1c1518f5d7cc7 100644 --- a/crates/config/src/warning.rs +++ b/crates/config/src/warning.rs @@ -55,6 +55,15 @@ pub enum Warning { /// The config file where the key was found source: String, }, + /// An unknown key was encountered in a section in a TOML file + UnknownSectionKey { + /// The unknown key name + key: String, + /// The section where the key was found + section: String, + /// The config file where the key was found + source: String, + }, } impl fmt::Display for Warning { @@ -102,6 +111,12 @@ impl fmt::Display for Warning { "Found unknown `{key}` config for profile `{profile}` defined in {source}." ) } + Self::UnknownSectionKey { key, section, source } => { + write!( + f, + "Found unknown `{key}` config key in section `{section}` defined in {source}." + ) + } } } } diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs index f7b18a978a4f4..e74ae58d66323 100644 --- a/crates/debugger/src/node.rs +++ b/crates/debugger/src/node.rs @@ -14,6 +14,8 @@ pub struct DebugNode { pub kind: CallKind, /// Calldata of the call. pub calldata: Bytes, + /// The gas limit of the call. + pub gas_limit: u64, /// The debug steps. pub steps: Vec, } @@ -25,8 +27,9 @@ impl DebugNode { kind: CallKind, steps: Vec, calldata: Bytes, + gas_limit: u64, ) -> Self { - Self { address, kind, steps, calldata } + Self { address, kind, steps, calldata, gas_limit } } } @@ -78,7 +81,7 @@ pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec) { let call = &arena_nodes[pending.node_idx].trace; let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() }; - let node = DebugNode::new(call.address, call.kind, steps, calldata); + let node = DebugNode::new(call.address, call.kind, steps, calldata, call.gas_limit); out.push(node); } diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 5f1d023640160..c3f38b681aac1 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -196,11 +196,11 @@ impl TUIContext<'_> { } // Go to next call - KeyCode::Char('C') => { - if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 { - self.draw_memory.inner_call_index += 1; - self.current_step = 0; - } + KeyCode::Char('C') + if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 => + { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; } // Step forward diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 6badd441fdb94..daf2d01d1da84 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -372,10 +372,11 @@ impl TUIContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {}", + "Address: {} | PC: {} | Gas used: {} | Gas refund: {}", self.address(), self.current_step().pc, - self.current_step().gas_used, + self.debug_call().gas_limit - self.current_step().gas_remaining, + self.current_step().gas_refund_counter ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index f04f6eb340176..c007bb2ebfcb0 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -183,7 +183,7 @@ impl DocBuilder { HashMap>, HashMap>, ) = funcs.into_iter().partition(|(_, v)| v.len() == 1); - remaining.extend(items.into_iter().flat_map(|(_, v)| v)); + remaining.extend(items.into_values().flatten()); // Each regular item will be written into its own file. files = remaining diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 455cd4082bb61..e3358b2f437dd 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -7,7 +7,7 @@ use crate::{ }; use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; -use solang_parser::pt::{Base, FunctionDefinition}; +use solang_parser::pt::{Base, FunctionDefinition, VariableAttribute}; use std::path::Path; /// The result of [`AsDoc::as_doc`]. @@ -181,18 +181,45 @@ impl AsDoc for Document { writer.writeln_doc(&item.comments)?; - if let Some(state_vars) = item.variables() { - writer.write_subtitle("State Variables")?; - state_vars.into_iter().try_for_each(|(item, comments, code)| { - let comments = comments.merge_inheritdoc( - &item.name.safe_unwrap().name, - read_context!(self, INHERITDOC_ID, Inheritdoc), - ); + if let Some(all_vars) = item.variables() { + let (constants, state_vars): (Vec<_>, Vec<_>) = + all_vars.into_iter().partition(|(item, _, _)| { + item.attrs.iter().any(|attr| { + matches!( + attr, + VariableAttribute::Constant(_) + | VariableAttribute::Immutable(_) + ) + }) + }); + + if !constants.is_empty() { + writer.write_subtitle("Constants")?; + constants.into_iter().try_for_each(|(item, comments, code)| { + let comments = comments.merge_inheritdoc( + &item.name.safe_unwrap().name, + read_context!(self, INHERITDOC_ID, Inheritdoc), + ); + + writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_section(&comments, code)?; + writer.writeln() + })?; + } - writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(&comments, code)?; - writer.writeln() - })?; + if !state_vars.is_empty() { + writer.write_subtitle("State Variables")?; + state_vars.into_iter().try_for_each(|(item, comments, code)| { + let comments = comments.merge_inheritdoc( + &item.name.safe_unwrap().name, + read_context!(self, INHERITDOC_ID, Inheritdoc), + ); + + writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_section(&comments, code)?; + writer.writeln() + })?; + } } if let Some(funcs) = item.functions() { diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index a2118233465a3..8b6ccb7004bd5 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -21,7 +21,7 @@ use revm::{ context_interface::result::ResultAndState, database::DatabaseRef, primitives::{HashMap as Map, hardfork::SpecId}, - state::{Account, AccountInfo}, + state::{Account, AccountInfo, EvmState}, }; use std::{borrow::Cow, collections::BTreeMap}; @@ -47,16 +47,15 @@ pub struct CowBackend<'a> { /// /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. pub backend: Cow<'a, Backend>, - /// Keeps track of whether the backed is already initialized - is_initialized: bool, - /// The [SpecId] of the current backend. - spec_id: SpecId, + /// The [SpecId] to initialize the backend with on first mutable access. + /// `None` means the backend has already been initialized for the current call. + spec_id: Option, } impl<'a> CowBackend<'a> { /// Creates a new `CowBackend` with the given `Backend`. pub fn new_borrowed(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::default() } + Self { backend: Cow::Borrowed(backend), spec_id: Some(SpecId::default()) } } /// Executes the configured transaction of the `env` without committing state changes @@ -71,8 +70,7 @@ impl<'a> CowBackend<'a> { ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state - self.is_initialized = false; - self.spec_id = env.evm_env.cfg_env.spec; + self.spec_id = Some(env.evm_env.cfg_env.spec); let mut evm = crate::evm::new_evm_with_inspector(self, env.to_owned(), inspector); @@ -94,12 +92,11 @@ impl<'a> CowBackend<'a> { /// /// If this is the first time this is called, the backed is cloned and initialized. fn backend_mut(&mut self, env: &EnvMut<'_>) -> &mut Backend { - if !self.is_initialized { + if let Some(spec_id) = self.spec_id.take() { let backend = self.backend.to_mut(); let mut env = env.to_owned(); - env.evm_env.cfg_env.spec = self.spec_id; + env.evm_env.cfg_env.spec = spec_id; backend.initialize(&env); - self.is_initialized = true; return backend; } self.backend.to_mut() @@ -107,7 +104,7 @@ impl<'a> CowBackend<'a> { /// Returns a mutable instance of the Backend if it is initialized. fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { - if self.is_initialized { + if self.spec_id.is_none() { return Some(self.backend.to_mut()); } None @@ -232,12 +229,8 @@ impl DatabaseExt for CowBackend<'_> { self.backend.ensure_fork_id(id) } - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option { - self.backend.diagnose_revert(callee, journaled_state) + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option { + self.backend.diagnose_revert(callee, evm_state) } fn load_allocs( @@ -245,7 +238,7 @@ impl DatabaseExt for CowBackend<'_> { allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default().as_env_mut()).load_allocs(allocs, journaled_state) + self.backend.to_mut().load_allocs(allocs, journaled_state) } fn clone_account( @@ -254,11 +247,7 @@ impl DatabaseExt for CowBackend<'_> { target: &Address, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default().as_env_mut()).clone_account( - source, - target, - journaled_state, - ) + self.backend.to_mut().clone_account(source, target, journaled_state) } fn is_persistent(&self, acc: &Address) -> bool { diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index 42456cceadd16..d6a08fa19e6b4 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -24,8 +24,6 @@ pub enum BackendError { For a test environment, you can use `etch` to place the required bytecode at that address." )] MissingCreate2Deployer, - #[error("{0}")] - Other(String), } impl BackendError { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 6429ceb3db23f..b1b937736b5f7 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -11,19 +11,24 @@ use crate::{ use alloy_consensus::Typed2718; use alloy_evm::Evm; use alloy_genesis::GenesisAccount; -use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; +use alloy_network::{ + AnyNetwork, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope, TransactionResponse, +}; use alloy_primitives::{Address, B256, TxKind, U256, keccak256, uint}; use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; use eyre::Context; use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender}; pub use foundry_fork_db::{BlockchainDb, SharedBackend, cache::BlockchainDbMeta}; use revm::{ - Database, DatabaseCommit, JournalEntry, + Database, DatabaseCommit, Journal, JournalEntry, bytecode::Bytecode, context::JournalInner, - context_interface::{block::BlobExcessGasAndPrice, result::ResultAndState}, + context_interface::{ + block::BlobExcessGasAndPrice, journaled_state::account::JournaledAccountTr, + result::ResultAndState, + }, database::{CacheDB, DatabaseRef}, - inspector::NoOpInspector, + inspector::{JournalExt, NoOpInspector}, precompile::{PrecompileSpecId, Precompiles}, primitives::{HashMap as Map, KECCAK_EMPTY, Log, hardfork::SpecId}, state::{Account, AccountInfo, EvmState, EvmStorageSlot}, @@ -271,11 +276,7 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug /// the contract is deployed there. /// /// Returns a more useful error message if that's the case - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option; + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option; /// Loads the account allocs from the given `allocs` map into the passed [JournaledState]. /// @@ -379,6 +380,38 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug struct _ObjectSafe(dyn DatabaseExt); +/// Extension trait for [`Journal`] providing borrow splitting and state replacement. +/// +/// Generic code accesses the journal via `ctx.journal_mut()` which returns `&mut impl JournalTr`. +/// This trait adds the ability to split the journal into its database and inner state components, +/// enabling direct [`DatabaseExt`] method calls with zero-copy borrow splitting. +pub trait FoundryJournalExt: JournalExt { + /// Returns mutable references to the database and journal inner state. + /// + /// Enables calling [`DatabaseExt`] methods directly, e.g.: + /// ```ignore + /// let (journal, env) = ctx.journal_and_env_mut(); // FoundryContextExt + /// let (db, inner) = journal.as_db_and_inner(); // FoundryJournalExt + /// db.select_fork(id, &env, inner)?; // DatabaseExt + /// ``` + fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState); + + /// Replaces the journal inner state. + /// + /// Used by sub-EVM execution to write back modified state after running a closure. + fn set_inner(&mut self, inner: JournaledState); +} + +impl FoundryJournalExt for Journal { + fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState) { + (&mut self.database, &mut self.inner) + } + + fn set_inner(&mut self, inner: JournaledState) { + self.inner = inner; + } +} + /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -435,7 +468,7 @@ struct _ObjectSafe(dyn DatabaseExt); #[must_use] pub struct Backend { /// The access point for managing forks - forks: MultiFork, + forks: MultiFork, // The default in memory db mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with @@ -478,7 +511,7 @@ impl Backend { /// database. /// /// Prefer using [`spawn`](Self::spawn) instead. - pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { + pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` let inner = BackendInner { @@ -899,7 +932,7 @@ impl Backend { trace!(tx=?tx.tx_hash(), "committing transaction"); commit_transaction( - &tx.inner, + tx, &mut env.as_env_mut(), journaled_state, fork, @@ -1293,7 +1326,7 @@ impl DatabaseExt for Backend { let fork = self.inner.get_fork_by_id_mut(id)?; commit_transaction( - &tx.inner, + &tx, &mut env.as_env_mut(), journaled_state, fork, @@ -1315,7 +1348,7 @@ impl DatabaseExt for Backend { self.commit(journaled_state.state.clone()); let res = { - configure_tx_req_env(&mut env.as_env_mut(), tx, None)?; + configure_tx_req_env(&mut env.as_env_mut(), tx)?; let mut db = self.clone(); let mut evm = new_evm_with_inspector(&mut db, env.to_owned(), inspector); @@ -1352,11 +1385,7 @@ impl DatabaseExt for Backend { self.inner.ensure_fork_id(id) } - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option { + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option { let active_id = self.active_fork_id()?; let active_fork = self.active_fork()?; @@ -1366,7 +1395,7 @@ impl DatabaseExt for Backend { return None; } - if !active_fork.is_contract(callee) && !is_contract_in_state(journaled_state, callee) { + if !active_fork.is_contract(callee) && !is_contract_in_state(evm_state, callee) { // no contract for `callee` available on current fork, check if available on other forks let mut available_on = Vec::new(); for (id, fork) in self.inner.forks_iter().filter(|(id, _)| *id != active_id) { @@ -1603,7 +1632,7 @@ impl Fork { { return true; } - is_contract_in_state(&self.journaled_state, acc) + is_contract_in_state(&self.journaled_state.state, acc) } } @@ -1933,12 +1962,8 @@ fn merge_db_account_data( } /// Returns true of the address is a contract -fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool { - journaled_state - .state - .get(&acc) - .map(|acc| acc.info.code_hash != KECCAK_EMPTY) - .unwrap_or_default() +fn is_contract_in_state(evm_state: &EvmState, acc: Address) -> bool { + evm_state.get(&acc).map(|acc| acc.info.code_hash != KECCAK_EMPTY).unwrap_or_default() } /// Updates the env's block with the block's data @@ -1962,7 +1987,7 @@ fn update_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an inspector. fn commit_transaction( - tx: &Transaction, + tx: &AnyRpcTransaction, env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, fork: &mut Fork, diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 9fe6a7b9b5c3e..0e28d92d91cae 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -54,7 +54,7 @@ pub fn decode_console_logs(logs: &[Log]) -> Vec { /// /// This function returns [None] if it is not a DSTest log or the result of a Hardhat /// `console.log`. -fn decode_console_log(log: &Log) -> Option { +pub fn decode_console_log(log: &Log) -> Option { console::ds::ConsoleEvents::decode_log(log).ok().map(|decoded| decoded.to_string()) } diff --git a/crates/evm/core/src/either_evm.rs b/crates/evm/core/src/either_evm.rs index 58f6d3063ffd1..40a9953e68fe3 100644 --- a/crates/evm/core/src/either_evm.rs +++ b/crates/evm/core/src/either_evm.rs @@ -286,6 +286,6 @@ where /// Maps [`EvmEnv`] to [`EvmEnv`]. fn map_env(env: EvmEnv) -> EvmEnv { let eth_spec_id = env.spec_id().into_eth_spec(); - let cfg = env.cfg_env.with_spec(eth_spec_id); + let cfg = env.cfg_env.with_spec_and_mainnet_gas_params(eth_spec_id); EvmEnv { cfg_env: cfg, block_env: env.block_env } } diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 000f3ad06b0ed..d21bc4bc57b8c 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -1,7 +1,8 @@ pub use alloy_evm::EvmEnv; use revm::{ - Context, Database, Journal, JournalEntry, - context::{BlockEnv, CfgEnv, JournalInner, JournalTr, TxEnv}, + Context, Database, + context::{BlockEnv, CfgEnv, JournalTr, TxEnv}, + context_interface::ContextTr, primitives::hardfork::SpecId, }; @@ -48,6 +49,17 @@ impl EnvMut<'_> { tx: self.tx.to_owned(), } } + + /// Writes an owned [`Env`] back into the context. + /// + /// Counterpart to [`to_owned`](Self::to_owned): completes the read/write pair so callers + /// that receive an updated [`Env`] by value (e.g. after a fork switch or snapshot revert) + /// can apply it without manually assigning each field. + pub fn set_env(&mut self, env: Env) { + *self.block = env.evm_env.block_env; + *self.cfg = env.evm_env.cfg_env; + *self.tx = env.tx; + } } pub trait AsEnvMut { @@ -78,25 +90,62 @@ impl, C> AsEnvMut } } -pub trait ContextExt { - type DB: Database; - - fn as_db_env_and_journal( - &mut self, - ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>); +/// Extension trait providing mutable field access to block, tx, and cfg environments. +/// +/// [`ContextTr`] only exposes immutable references for block, tx, and cfg. +/// Cheatcodes like `vm.warp()`, `vm.roll()`, `vm.chainId()` need to mutate these fields. +/// +/// Also provides [`journal_and_env_mut`](FoundryContextExt::journal_and_env_mut) for +/// simultaneous mutable access to journal and env — needed because calling `journal_mut()` +/// and `block_mut()` separately would create conflicting borrows on `&mut self`. +pub trait FoundryContextExt: ContextTr { + /// Mutable reference to the block environment. + fn block_mut(&mut self) -> &mut BlockEnv; + /// Mutable reference to the transaction environment. + fn tx_mut(&mut self) -> &mut TxEnv; + /// Mutable reference to the configuration environment. + fn cfg_mut(&mut self) -> &mut CfgEnv; + + /// Returns a cloned snapshot of the current environment. + fn to_env(&self) -> Env; + + /// Applies an owned [`Env`] to this context, replacing block, cfg, and tx. + fn apply_env(&mut self, env: Env); + + /// Returns mutable references to the journal and environment simultaneously. + /// + /// This solves the borrow-splitting problem: calling `self.journal_mut()` and + /// `self.block_mut()` separately would both borrow `&mut self`. This method + /// splits the borrows at the field level in one call. + fn journal_and_env_mut(&mut self) -> (&mut Self::Journal, EnvMut<'_>); } -impl ContextExt - for Context, C> +impl, C> FoundryContextExt + for Context { - type DB = DB; - - fn as_db_env_and_journal( - &mut self, - ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>) { + fn block_mut(&mut self) -> &mut BlockEnv { + &mut self.block + } + fn tx_mut(&mut self) -> &mut TxEnv { + &mut self.tx + } + fn cfg_mut(&mut self) -> &mut CfgEnv { + &mut self.cfg + } + fn to_env(&self) -> Env { + Env { + evm_env: EvmEnv { cfg_env: self.cfg.clone(), block_env: self.block.clone() }, + tx: self.tx.clone(), + } + } + fn apply_env(&mut self, env: Env) { + self.block = env.evm_env.block_env; + self.cfg = env.evm_env.cfg_env; + self.tx = env.tx; + } + fn journal_and_env_mut(&mut self) -> (&mut J, EnvMut<'_>) { ( - &mut self.journaled_state.database, - &mut self.journaled_state.inner, + &mut self.journaled_state, EnvMut { block: &mut self.block, cfg: &mut self.cfg, tx: &mut self.tx }, ) } diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs index 379b1ba1f8414..24f24e18bd104 100644 --- a/crates/evm/core/src/evm.rs +++ b/crates/evm/core/src/evm.rs @@ -4,7 +4,9 @@ use std::{ }; use crate::{ - Env, InspectorExt, backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, + Env, FoundryContextExt, InspectorExt, + backend::{DatabaseExt, FoundryJournalExt, JournaledState}, + constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, }; use alloy_consensus::constants::KECCAK_EMPTY; use alloy_evm::{Evm, EvmEnv, eth::EthEvmContext, precompiles::PrecompilesMap}; @@ -101,16 +103,16 @@ fn get_create2_factory_call_inputs( inputs: &CreateInputs, deployer: Address, ) -> CallInputs { - let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat(); CallInputs { - caller: inputs.caller, + caller: inputs.caller(), bytecode_address: deployer, known_bytecode: None, target_address: deployer, scheme: CallScheme::Call, - value: CallValue::Transfer(inputs.value), + value: CallValue::Transfer(inputs.value()), input: CallInput::Bytes(calldata.into()), - gas_limit: inputs.gas_limit, + gas_limit: inputs.gas_limit(), is_static: false, return_memory_offset: 0..0, } @@ -252,6 +254,131 @@ impl DerefMut for FoundryEvm<'_, I> { } } +/// Object-safe trait exposing the operations that cheatcode nested EVM closures need. +/// +/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.) +/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type. +pub trait NestedEvm { + /// Returns a mutable reference to the journal inner state (`JournaledState`). + fn journal_inner_mut(&mut self) -> &mut JournaledState; + + /// Runs a single execution frame (create or call) through the EVM handler loop. + fn run_execution(&mut self, frame: FrameInput) -> Result>; + + /// Executes a full transaction with the given `TxEnv`. + fn transact( + &mut self, + tx: TxEnv, + ) -> Result, EVMError>; +} + +impl NestedEvm for FoundryEvm<'_, I> { + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.inner.ctx.journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + FoundryEvm::run_execution(self, frame) + } + + fn transact( + &mut self, + tx: TxEnv, + ) -> Result, EVMError> { + Evm::transact_raw(self, tx) + } +} + +/// Extension trait for building nested EVMs from a generic context. +/// +/// Each network provides its own impl that constructs the right EVM type +/// (instructions, precompiles) for that network. +pub trait NestedEvmExt: FoundryContextExt { + /// Clones the current context (env + journal), builds a nested EVM, runs the closure, + /// and writes modified state back. Used by `exec_create` (`deployCode`). + fn with_nested_evm( + &mut self, + inspector: &mut dyn InspectorExt, + f: impl FnOnce(&mut dyn NestedEvm) -> Result>, + ) -> Result> + where + Self::Journal: FoundryJournalExt; + + /// Creates a fresh nested EVM from a database, environment, and inspector. + /// Used by `executeTransactionCall`. + fn new_nested_evm<'a>( + db: &'a mut dyn DatabaseExt, + env: Env, + inspector: &'a mut dyn InspectorExt, + ) -> Box + where + Self: Sized; +} + +impl, J: JournalTr, C> NestedEvmExt + for Context +{ + fn with_nested_evm( + &mut self, + inspector: &mut dyn InspectorExt, + f: impl FnOnce(&mut dyn NestedEvm) -> Result>, + ) -> Result> + where + Self::Journal: FoundryJournalExt, + { + // Clone env fields via field-level borrows on Context. + let block_clone = self.block.clone(); + let cfg_clone = self.cfg.clone(); + let tx_clone = self.tx.clone(); + let error = std::mem::replace(&mut self.error, Ok(())); + + // Split journal into (db, inner) and clone the inner state. + let (db, journal_inner) = self.journaled_state.as_db_and_inner(); + let journal_inner_clone = journal_inner.clone(); + + // Build a nested EVM context. The db reference borrows self.journaled_state. + let ctx = EthEvmContext { + block: block_clone, + cfg: cfg_clone, + tx: tx_clone, + journaled_state: Journal { inner: journal_inner_clone, database: db }, + local: LocalContext::default(), + chain: (), + error, + }; + + let mut evm = new_evm_with_existing_context(ctx, inspector); + let res = f(&mut evm); + + // Destructure the nested EVM context to release the db borrow. + let sub_ctx = evm.into_context(); + let Context { block, cfg, tx, journaled_state: sub_journal, error: sub_error, .. } = + sub_ctx; + // Dropping `database` releases the mutable borrow on self.journaled_state. + let Journal { inner: sub_inner, database: _ } = sub_journal; + + // Write back modified state. + self.block = block; + self.cfg = cfg; + self.tx = tx; + self.error = sub_error; + self.journaled_state.set_inner(sub_inner); + + res + } + + fn new_nested_evm<'a>( + db: &'a mut dyn DatabaseExt, + env: Env, + inspector: &'a mut dyn InspectorExt, + ) -> Box + where + Self: Sized, + { + Box::new(new_evm_with_inspector(db, env, inspector)) + } +} + pub struct FoundryHandler<'db, I: InspectorExt> { create2_overrides: Vec<(usize, CallInputs)>, _phantom: PhantomData<(&'db mut dyn DatabaseExt, I)>, @@ -286,12 +413,12 @@ impl<'db, I: InspectorExt> FoundryHandler<'db, I> { init: &mut FrameInit, ) -> Result, ::Error> { if let FrameInput::Create(inputs) = &init.frame_input - && let CreateScheme::Create2 { salt } = inputs.scheme + && let CreateScheme::Create2 { salt } = inputs.scheme() { let (ctx, inspector) = evm.ctx_inspector(); - if inspector.should_use_create2_factory(ctx, inputs) { - let gas_limit = inputs.gas_limit; + if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { + let gas_limit = inputs.gas_limit(); // Get CREATE2 deployer. let create2_deployer = evm.inspector().create2_deployer(); diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 6a53a19843b1b..65686ade1aada 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -4,6 +4,7 @@ use crate::{ backend::{RevertStateSnapshotAction, StateSnapshot}, state_snapshot::StateSnapshots, }; +use alloy_network::Network; use alloy_primitives::{Address, B256, U256, map::HashMap}; use alloy_rpc_types::BlockId; use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; @@ -23,28 +24,28 @@ use std::sync::Arc; /// This database uses the `backend` for read and the `db` for write operations. But note the /// `backend` will also write (missing) data to the `db` in the background #[derive(Clone, Debug)] -pub struct ForkedDatabase { +pub struct ForkedDatabase { /// Responsible for fetching missing data. /// /// This is responsible for getting data. - backend: SharedBackend, + backend: SharedBackend, /// Cached Database layer, ensures that changes are not written to the database that /// exclusively stores the state of the remote client. /// /// This separates Read/Write operations /// - reads from the `SharedBackend as DatabaseRef` writes to the internal cache storage. - cache_db: CacheDB, + cache_db: CacheDB>, /// Contains all the data already fetched. /// /// This exclusively stores the _unchanged_ remote client state. db: BlockchainDb, /// Holds the state snapshots of a blockchain. - state_snapshots: Arc>>, + state_snapshots: Arc>>>, } -impl ForkedDatabase { +impl ForkedDatabase { /// Creates a new instance of this DB - pub fn new(backend: SharedBackend, db: BlockchainDb) -> Self { + pub fn new(backend: SharedBackend, db: BlockchainDb) -> Self { Self { cache_db: CacheDB::new(backend.clone()), backend, @@ -53,15 +54,15 @@ impl ForkedDatabase { } } - pub fn database(&self) -> &CacheDB { + pub fn database(&self) -> &CacheDB> { &self.cache_db } - pub fn database_mut(&mut self) -> &mut CacheDB { + pub fn database_mut(&mut self) -> &mut CacheDB> { &mut self.cache_db } - pub fn state_snapshots(&self) -> &Arc>> { + pub fn state_snapshots(&self) -> &Arc>>> { &self.state_snapshots } @@ -93,7 +94,7 @@ impl ForkedDatabase { &self.db } - pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot { + pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot { let db = self.db.db(); let state_snapshot = StateSnapshot { accounts: db.accounts.read().clone(), @@ -150,7 +151,7 @@ impl ForkedDatabase { } } -impl Database for ForkedDatabase { +impl Database for ForkedDatabase { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -173,7 +174,7 @@ impl Database for ForkedDatabase { } } -impl DatabaseRef for ForkedDatabase { +impl DatabaseRef for ForkedDatabase { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -193,7 +194,7 @@ impl DatabaseRef for ForkedDatabase { } } -impl DatabaseCommit for ForkedDatabase { +impl DatabaseCommit for ForkedDatabase { fn commit(&mut self, changes: HashMap) { self.database_mut().commit(changes) } @@ -203,12 +204,12 @@ impl DatabaseCommit for ForkedDatabase { /// /// This mimics `revm::CacheDB` #[derive(Clone, Debug)] -pub struct ForkDbStateSnapshot { - pub local: CacheDB, +pub struct ForkDbStateSnapshot { + pub local: CacheDB>, pub state_snapshot: StateSnapshot, } -impl ForkDbStateSnapshot { +impl ForkDbStateSnapshot { fn get_storage(&self, address: Address, index: U256) -> Option { self.local .cache @@ -222,7 +223,7 @@ impl ForkDbStateSnapshot { // This `DatabaseRef` implementation works similar to `CacheDB` which prioritizes modified elements, // and uses another db as fallback // We prioritize stored changed accounts/storage -impl DatabaseRef for ForkDbStateSnapshot { +impl DatabaseRef for ForkDbStateSnapshot { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 186183b9a8d2a..728d380153257 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -6,6 +6,7 @@ use super::CreateFork; use crate::Env; use alloy_consensus::BlockHeader; +use alloy_network::Network; use alloy_primitives::{U256, map::HashMap}; use alloy_provider::network::BlockResponse; use foundry_config::Config; @@ -67,14 +68,14 @@ impl> From for ForkId { /// Can send requests to the `MultiForkHandler` to create forks. #[derive(Clone, Debug)] #[must_use] -pub struct MultiFork { +pub struct MultiFork { /// Channel to send `Request`s to the handler. - handler: Sender, + handler: Sender>, /// Ensures that all rpc resources get flushed properly. - _shutdown: Arc, + _shutdown: Arc>, } -impl MultiFork { +impl MultiFork { /// Creates a new pair and spawns the `MultiForkHandler` on a background thread. pub fn spawn() -> Self { trace!(target: "fork::multi", "spawning multifork"); @@ -116,7 +117,7 @@ impl MultiFork { /// /// Use [`spawn`](Self::spawn) instead. #[doc(hidden)] - pub fn new() -> (Self, MultiForkHandler) { + pub fn new() -> (Self, MultiForkHandler) { let (handler, handler_rx) = channel(1); let _shutdown = Arc::new(ShutDownMultiFork { handler: Some(handler.clone()) }); (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx)) @@ -125,7 +126,7 @@ impl MultiFork { /// Returns a fork backend. /// /// If no matching fork backend exists it will be created. - pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<(ForkId, SharedBackend, Env)> { + pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<(ForkId, SharedBackend, Env)> { trace!("Creating new fork, url={}, block={:?}", fork.url, fork.evm_opts.fork_block_number); let (sender, rx) = oneshot_channel(); let req = Request::CreateFork(Box::new(fork), sender); @@ -140,7 +141,7 @@ impl MultiFork { &self, fork: ForkId, block: u64, - ) -> eyre::Result<(ForkId, SharedBackend, Env)> { + ) -> eyre::Result<(ForkId, SharedBackend, Env)> { trace!(?fork, ?block, "rolling fork"); let (sender, rx) = oneshot_channel(); let req = Request::RollFork(fork, block, sender); @@ -181,7 +182,7 @@ impl MultiFork { /// Returns the corresponding fork if it exists. /// /// Returns `None` if no matching fork backend is available. - pub fn get_fork(&self, id: impl Into) -> eyre::Result> { + pub fn get_fork(&self, id: impl Into) -> eyre::Result>> { let id = id.into(); trace!(?id, "get fork backend"); let (sender, rx) = oneshot_channel(); @@ -201,21 +202,20 @@ impl MultiFork { } } -type Handler = BackendHandler; -type CreateFuture = - Pin> + Send>>; -type CreateSender = OneshotSender>; +type CreateFuture = + Pin, BackendHandler)>> + Send>>; +type CreateSender = OneshotSender, Env)>>; type GetEnvSender = OneshotSender>; /// Request that's send to the handler. #[derive(Debug)] -enum Request { +enum Request { /// Creates a new ForkBackend. - CreateFork(Box, CreateSender), + CreateFork(Box, CreateSender), /// Returns the Fork backend for the `ForkId` if it exists. - GetFork(ForkId, OneshotSender>), + GetFork(ForkId, OneshotSender>>), /// Adjusts the block that's being forked, by creating a new fork at the new block. - RollFork(ForkId, u64, CreateSender), + RollFork(ForkId, u64, CreateSender), /// Returns the environment of the fork. GetEnv(ForkId, GetEnvSender), /// Updates the block number and timestamp of the fork. @@ -228,37 +228,37 @@ enum Request { GetForkUrl(ForkId, OneshotSender>), } -enum ForkTask { +enum ForkTask { /// Contains the future that will establish a new fork. - Create(CreateFuture, ForkId, CreateSender, Vec), + Create(CreateFuture, ForkId, CreateSender, Vec>), } /// The type that manages connections in the background. #[must_use = "futures do nothing unless polled"] -pub struct MultiForkHandler { +pub struct MultiForkHandler { /// Incoming requests from the `MultiFork`. - incoming: Fuse>, + incoming: Fuse>>, /// All active handlers. /// /// It's expected that this list will be rather small (<10). - handlers: Vec<(ForkId, Handler)>, + handlers: Vec<(ForkId, BackendHandler)>, // tasks currently in progress - pending_tasks: Vec, + pending_tasks: Vec>, /// All _unique_ forkids mapped to their corresponding backend. /// /// Note: The backend can be shared by multiple ForkIds if the target the same provider and /// block number. - forks: HashMap, + forks: HashMap>, /// Optional periodic interval to flush rpc cache. flush_cache_interval: Option, } -impl MultiForkHandler { - fn new(incoming: Receiver) -> Self { +impl MultiForkHandler { + fn new(incoming: Receiver>) -> Self { Self { incoming: incoming.fuse(), handlers: Default::default(), @@ -276,19 +276,16 @@ impl MultiForkHandler { } /// Returns the list of additional senders of a matching task for the given id, if any. - #[expect(irrefutable_let_patterns)] - fn find_in_progress_task(&mut self, id: &ForkId) -> Option<&mut Vec> { - for task in &mut self.pending_tasks { - if let ForkTask::Create(_, in_progress, _, additional) = task - && in_progress == id - { + fn find_in_progress_task(&mut self, id: &ForkId) -> Option<&mut Vec>> { + for ForkTask::Create(_, in_progress, _, additional) in &mut self.pending_tasks { + if in_progress == id { return Some(additional); } } None } - fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { + fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { let fork_id = ForkId::new(&fork.url, fork.evm_opts.fork_block_number); trace!(?fork_id, "created new forkId"); @@ -306,9 +303,9 @@ impl MultiForkHandler { fn insert_new_fork( &mut self, fork_id: ForkId, - fork: CreatedFork, - sender: CreateSender, - additional_senders: Vec, + fork: CreatedFork, + sender: CreateSender, + additional_senders: Vec>, ) { self.forks.insert(fork_id.clone(), fork.clone()); let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.opts.env.clone()))); @@ -336,7 +333,7 @@ impl MultiForkHandler { } } - fn on_request(&mut self, req: Request) { + fn on_request(&mut self, req: Request) { match req { Request::CreateFork(fork, sender) => self.create_fork(*fork, sender), Request::GetFork(fork_id, sender) => { @@ -380,7 +377,7 @@ impl MultiForkHandler { // Drives all handler to completion. // This future will finish once all underlying BackendHandler are completed. -impl Future for MultiForkHandler { +impl Future for MultiForkHandler { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -481,18 +478,18 @@ impl Future for MultiForkHandler { /// Tracks the created Fork #[derive(Debug, Clone)] -struct CreatedFork { +struct CreatedFork { /// How the fork was initially created. opts: CreateFork, /// Copy of the sender. - backend: SharedBackend, + backend: SharedBackend, /// How many consumers there are, since a `SharedBacked` can be used by multiple /// consumers. num_senders: Arc, } -impl CreatedFork { - pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { +impl CreatedFork { + pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } } @@ -514,11 +511,11 @@ impl CreatedFork { /// This type intentionally does not implement `Clone` since it's intended that there's only once /// instance. #[derive(Debug)] -struct ShutDownMultiFork { - handler: Option>, +struct ShutDownMultiFork { + handler: Option>>, } -impl Drop for ShutDownMultiFork { +impl Drop for ShutDownMultiFork { fn drop(&mut self) { trace!(target: "fork::multi", "initiating shutdown"); let (sender, rx) = oneshot_channel(); @@ -535,11 +532,14 @@ impl Drop for ShutDownMultiFork { /// Creates a new fork. /// /// This will establish a new `Provider` to the endpoint and return the Fork Backend. -async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { +async fn create_fork( + mut fork: CreateFork, +) -> eyre::Result<(ForkId, CreatedFork, BackendHandler)> { let provider = fork.evm_opts.fork_provider_with_url(&fork.url)?; // Initialise the fork environment. - let (env, block) = fork.evm_opts.fork_evm_env_with_provider(&fork.url, &provider).await?; + let (env, block) = + fork.evm_opts.fork_evm_env_with_provider::<_, N>(&fork.url, &provider).await?; fork.env = env; let meta = BlockchainDbMeta::new(fork.env.evm_env.block_env.clone(), fork.url.clone()); diff --git a/crates/evm/core/src/hardfork.rs b/crates/evm/core/src/hardfork.rs index fb97db475584d..0176f562d55af 100644 --- a/crates/evm/core/src/hardfork.rs +++ b/crates/evm/core/src/hardfork.rs @@ -113,6 +113,7 @@ mod tests { // Test latest hardforks assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN); assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA); } #[test] diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 153748c7b8662..6982ff20dc163 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -43,19 +43,18 @@ pub mod precompiles; pub mod state_snapshot; pub mod utils; -/// An extension trait that allows us to add additional hooks to Inspector for later use in -/// handlers. +/// Foundry-specific inspector methods, decoupled from any particular EVM context type. +/// +/// This trait holds Foundry-specific extensions (create2 factory, console logging, +/// network config, deployer address). It has no `Inspector` supertrait so it can +/// be used in generic code with `I: FoundryInspectorExt + Inspector`. #[auto_impl(&mut, Box)] -pub trait InspectorExt: for<'a> Inspector> { +pub trait FoundryInspectorExt { /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. /// /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 /// factory. - fn should_use_create2_factory( - &mut self, - _context: &mut EthEvmContext<&mut dyn DatabaseExt>, - _inputs: &CreateInputs, - ) -> bool { + fn should_use_create2_factory(&mut self, _depth: usize, _inputs: &CreateInputs) -> bool { false } @@ -75,6 +74,20 @@ pub trait InspectorExt: for<'a> Inspector } } -impl InspectorExt for NoOpInspector {} +/// Combined trait: `Inspector>` + [`FoundryInspectorExt`]. +/// +/// Used as a trait object (`dyn InspectorExt`) in backend code that is Eth-specific. +/// For generic multi-network code, use `I: FoundryInspectorExt + Inspector` instead. +pub trait InspectorExt: + for<'a> Inspector> + FoundryInspectorExt +{ +} + +impl InspectorExt for T where + T: for<'a> Inspector> + FoundryInspectorExt +{ +} + +impl FoundryInspectorExt for NoOpInspector {} -impl InspectorExt for AccessListInspector {} +impl FoundryInspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index a5fa6f079b301..f2868ea84cd8e 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -4,14 +4,11 @@ use crate::{ constants::DEFAULT_CREATE2_DEPLOYER, fork::{CreateFork, configure_env}, }; -use alloy_network::Network; +use alloy_network::{AnyNetwork, Network}; use alloy_primitives::{Address, B256, U256}; -use alloy_provider::{Provider, network::AnyRpcBlock}; +use alloy_provider::{Provider, RootProvider, network::AnyRpcBlock}; use eyre::WrapErr; -use foundry_common::{ - ALCHEMY_FREE_TIER_CUPS, - provider::{ProviderBuilder, RetryProvider}, -}; +use foundry_common::{ALCHEMY_FREE_TIER_CUPS, provider::ProviderBuilder}; use foundry_config::{Chain, Config, GasLimit}; use foundry_evm_networks::NetworkConfigs; use revm::context::{BlockEnv, TxEnv}; @@ -116,8 +113,12 @@ impl Default for EvmOpts { } impl EvmOpts { - /// Returns a `RetryProvider` for the given fork URL configured with options in `self`. - pub fn fork_provider_with_url(&self, fork_url: &str) -> eyre::Result { + /// Returns a `RootProvider` for the given fork URL configured with options in `self` and + /// annotated `Network` type. + pub fn fork_provider_with_url( + &self, + fork_url: &str, + ) -> eyre::Result> { ProviderBuilder::new(fork_url) .maybe_max_retry(self.fork_retries) .maybe_initial_backoff(self.fork_retry_backoff) @@ -141,7 +142,7 @@ impl EvmOpts { /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint, /// and the block that was used to configure the environment. pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> { - let provider = self.fork_provider_with_url(fork_url)?; + let provider = self.fork_provider_with_url::(fork_url)?; self.fork_evm_env_with_provider(fork_url, &provider).await } @@ -257,7 +258,7 @@ impl EvmOpts { /// Returns the chain ID from the RPC, if any. pub async fn get_remote_chain_id(&self) -> Option { if let Some(url) = &self.fork_url - && let Ok(provider) = self.fork_provider_with_url(url) + && let Ok(provider) = self.fork_provider_with_url::(url) { trace!(?url, "retrieving chain via eth_chainId"); diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index efcda2f08e15c..b25a202e0514c 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -3,10 +3,10 @@ use alloy_chains::Chain; use alloy_consensus::{BlockHeader, private::alloy_eips::eip7840::BlobParams}; use alloy_hardforks::EthereumHardfork; use alloy_json_abi::{Function, JsonAbi}; -use alloy_network::{AnyTxEnvelope, TransactionResponse}; -use alloy_primitives::{Address, B256, ChainId, Selector, TxKind, U256}; +use alloy_network::{AnyRpcTransaction, TransactionResponse}; +use alloy_primitives::{B256, ChainId, Selector, TxKind, U256}; use alloy_provider::{Network, network::BlockResponse}; -use alloy_rpc_types::{Transaction, TransactionRequest}; +use alloy_rpc_types::TransactionRequest; use foundry_config::NamedChain; use foundry_evm_networks::NetworkConfigs; use revm::primitives::{ @@ -138,26 +138,19 @@ pub fn get_function<'a>( /// Configures the env for the given RPC transaction. /// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`. -pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction) { +pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &AnyRpcTransaction) { let from = tx.from(); - if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() { + if let Some(tx) = tx.as_envelope() { configure_tx_req_env( env, &TransactionRequest::from_transaction_with_sender(tx.clone(), from), - Some(from), ) .expect("cannot fail"); } } /// Configures the env for the given RPC transaction request. -/// `impersonated_from` is the address of the impersonated account. This helps account for an -/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`. -pub fn configure_tx_req_env( - env: &mut EnvMut<'_>, - tx: &TransactionRequest, - impersonated_from: Option

, -) -> eyre::Result<()> { +pub fn configure_tx_req_env(env: &mut EnvMut<'_>, tx: &TransactionRequest) -> eyre::Result<()> { // If no transaction type is provided, we need to infer it from the other fields. let tx_type = tx.transaction_type.unwrap_or_else(|| tx.minimal_tx_type() as u8); env.tx.tx_type = tx_type; @@ -183,13 +176,7 @@ pub fn configure_tx_req_env( // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction env.tx.kind = to.unwrap_or(TxKind::Create); - // If the transaction is impersonated, we need to set the caller to the from - // address Ref: https://github.com/foundry-rs/foundry/issues/9541 - env.tx.caller = if let Some(caller) = impersonated_from { - caller - } else { - from.ok_or_else(|| eyre::eyre!("missing `from` field"))? - }; + env.tx.caller = from.ok_or_else(|| eyre::eyre!("missing `from` field"))?; env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?; env.tx.nonce = nonce.unwrap_or_default(); env.tx.value = value.unwrap_or_default(); diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index b3e9553f40304..a69c19cbdb2bd 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -56,3 +56,5 @@ indicatif.workspace = true serde_json.workspace = true serde.workspace = true uuid.workspace = true +rayon.workspace = true +tokio.workspace = true diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index a62a9b9abcf1e..5a6cc4e91bafb 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,9 +1,10 @@ use crate::executors::{ DURATION_BETWEEN_METRICS_REPORT, EarlyExit, Executor, FuzzTestTimer, RawCallResult, + corpus::{GlobalCorpusMetrics, WorkerCorpus}, }; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; -use alloy_primitives::{Address, Bytes, Log, U256, map::HashMap}; +use alloy_primitives::{Address, Bytes, Log, U256, keccak256, map::HashMap}; use eyre::Result; use foundry_common::sh_println; use foundry_config::FuzzConfig; @@ -22,41 +23,134 @@ use foundry_evm_traces::SparsedTraceArena; use indicatif::ProgressBar; use proptest::{ strategy::Strategy, - test_runner::{TestCaseError, TestRunner}, + test_runner::{RngAlgorithm, TestCaseError, TestRng, TestRunner}, }; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde_json::json; -use std::time::{Instant, SystemTime, UNIX_EPOCH}; +use std::{ + sync::{ + Arc, OnceLock, + atomic::{AtomicU32, Ordering}, + }, + time::{Instant, SystemTime, UNIX_EPOCH}, +}; mod types; -use crate::executors::corpus::CorpusManager; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; -/// Contains data collected during fuzz test runs. +/// Corpus syncs across workers every `SYNC_INTERVAL` runs. +const SYNC_INTERVAL: u32 = 1000; + +/// Minimum number of runs per worker. +/// This is mainly to reduce the overall number of rayon jobs. +const MIN_RUNS_PER_WORKER: u32 = 64; + #[derive(Default)] -struct FuzzTestData { - // Stores the first fuzz case. - first_case: Option, - // Stored gas usage per fuzz case. +struct WorkerState { + /// Worker identifier + id: usize, + /// First fuzz case this worker encountered (with global run number) + first_case: Option<(u32, FuzzCase)>, + /// Gas usage for all cases this worker ran gas_by_case: Vec<(u64, u64)>, - // Stores the result and calldata of the last failed call, if any. + /// Counterexample if this worker found one counterexample: (Bytes, RawCallResult), - // Stores up to `max_traces_to_collect` traces. + /// Traces collected by this worker + /// + /// Stores up to `max_traces_to_collect` which is `config.gas_report_samples / num_workers` traces: Vec, - // Stores breakpoints for the last fuzz case. + /// Last breakpoints from this worker breakpoints: Option, - // Stores coverage information for all fuzz cases. + /// Coverage collected by this worker coverage: Option, - // Stores logs for all fuzz cases (when show_logs is true) or just the last run (when show_logs - // is false) + /// Logs from all cases this worker ran logs: Vec, - // Deprecated cheatcodes mapped to their replacements. + /// Deprecated cheatcodes seen by this worker deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, - // Runs performed in fuzz test. + /// Number of runs this worker completed runs: u32, - // Current assume rejects of the fuzz run. - rejects: u32, - // Test failure. + /// Failure reason if this worker failed failure: Option, + /// Last run timestamp in milliseconds + /// + /// Used to identify which worker ran last and collect its traces and call breakpoints + last_run_timestamp: u128, + /// Failed corpus replays + failed_corpus_replays: usize, +} + +impl WorkerState { + fn new(worker_id: usize) -> Self { + Self { id: worker_id, ..Default::default() } + } +} + +/// Shared state for coordinating parallel fuzz workers +struct SharedFuzzState { + state: EvmFuzzState, + /// Total runs across workers + total_runs: Arc, + /// Found failure + /// + /// The worker that found the failure sets it's ID. + /// + /// This ID is then used to correctly extract the failure reason and counterexample. + failed_worker_id: OnceLock, + /// Total rejects across workers + total_rejects: Arc, + /// Fuzz timer + timer: FuzzTestTimer, + /// Global corpus metrics + global_corpus_metrics: GlobalCorpusMetrics, + + /// Global test suite early exit. + global_early_exit: EarlyExit, + /// Local fuzz early exit. + local_early_exit: EarlyExit, +} + +impl SharedFuzzState { + fn new(state: EvmFuzzState, timeout: Option, early_exit: EarlyExit) -> Self { + Self { + state, + total_runs: Arc::new(AtomicU32::new(0)), + failed_worker_id: OnceLock::new(), + total_rejects: Arc::new(AtomicU32::new(0)), + timer: FuzzTestTimer::new(timeout), + global_corpus_metrics: GlobalCorpusMetrics::default(), + global_early_exit: early_exit, + local_early_exit: EarlyExit::new(true), + } + } + + /// Increments the number of runs and returns the new value. + fn increment_runs(&self) -> u32 { + self.total_runs.fetch_add(1, Ordering::Relaxed) + 1 + } + + /// Increments and returns the new value of the number of rejected tests. + fn increment_rejects(&self) -> u32 { + self.total_rejects.fetch_add(1, Ordering::Relaxed) + 1 + } + + /// Returns `true` if the worker should continue running. + fn should_continue(&self) -> bool { + !(self.global_early_exit.should_stop() + || self.local_early_exit.should_stop() + || self.timer.is_timed_out()) + } + + /// Returns true if the worker was able to claim the failure, false if failure was set by + /// another worker + fn try_claim_failure(&self, worker_id: usize) -> bool { + let mut claimed = false; + let _ = self.failed_worker_id.get_or_init(|| { + claimed = true; + self.local_early_exit.record_failure(); + worker_id + }); + claimed + } } /// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`]. @@ -66,7 +160,7 @@ struct FuzzTestData { /// configuration which can be overridden via [environment variables](proptest::test_runner::Config) pub struct FuzzedExecutor { /// The EVM executor. - executor: Executor, + executor_f: Executor, /// The fuzzer runner: TestRunner, /// The account that calls tests. @@ -75,6 +169,8 @@ pub struct FuzzedExecutor { config: FuzzConfig, /// The persisted counterexample to be replayed, if any. persisted_failure: Option, + /// The number of parallel workers. + num_workers: usize, } impl FuzzedExecutor { @@ -86,7 +182,12 @@ impl FuzzedExecutor { config: FuzzConfig, persisted_failure: Option, ) -> Self { - Self { executor, runner, sender, config, persisted_failure } + let mut max_workers = Ord::max(1, config.runs / MIN_RUNS_PER_WORKER); + if config.runs == 0 { + max_workers = 0; + } + let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); + Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } /// Fuzzes the provided function, assuming it is available at the contract at `address` @@ -104,14 +205,210 @@ impl FuzzedExecutor { rd: &RevertDecoder, progress: Option<&ProgressBar>, early_exit: &EarlyExit, + tokio_handle: &tokio::runtime::Handle, ) -> Result { - let state = &state; - // Stores the fuzz test execution data. - let mut test_data = FuzzTestData::default(); + let shared_state = SharedFuzzState::new(state, self.config.timeout, early_exit.clone()); + + debug!(n = self.num_workers, "spawning workers"); + let workers = (0..self.num_workers) + .into_par_iter() + .map(|worker_id| { + let _guard = tokio_handle.enter(); + let _guard = info_span!("fuzz_worker", id = worker_id).entered(); + let timer = Instant::now(); + let r = self.run_worker( + worker_id, + func, + fuzz_fixtures, + address, + rd, + &shared_state, + progress, + ); + debug!("finished in {:?}", timer.elapsed()); + r + }) + .collect::>>()?; + + Ok(self.aggregate_results(workers, func, &shared_state)) + } + + /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` + /// or a `CounterExampleOutcome` + fn single_fuzz( + &self, + executor: &Executor, + address: Address, + calldata: Bytes, + coverage_metrics: &mut WorkerCorpus, + ) -> Result { + let mut call = executor + .call_raw(self.sender, address, calldata.clone(), U256::ZERO) + .map_err(|e| TestCaseError::fail(e.to_string()))?; + let new_coverage = coverage_metrics.merge_edge_coverage(&mut call); + coverage_metrics.process_inputs( + &[BasicTxDetails { + warp: None, + roll: None, + sender: self.sender, + call_details: CallDetails { target: address, calldata: calldata.clone() }, + }], + new_coverage, + ); + + // Handle `vm.assume`. + if call.result.as_ref() == MAGIC_ASSUME { + return Err(TestCaseError::reject(FuzzError::AssumeReject)); + } + + let (breakpoints, deprecated_cheatcodes) = + call.cheatcodes.as_ref().map_or_else(Default::default, |cheats| { + (cheats.breakpoints.clone(), cheats.deprecated.clone()) + }); + + // Consider call success if test should not fail on reverts and reverter is not the + // cheatcode or test address. + let success = if !self.config.fail_on_revert + && call + .reverter + .is_some_and(|reverter| reverter != address && reverter != CHEATCODE_ADDRESS) + { + true + } else { + executor.is_raw_call_mut_success(address, &mut call, false) + }; + + if success { + Ok(FuzzOutcome::Case(CaseOutcome { + case: FuzzCase { gas: call.gas_used, stipend: call.stipend }, + traces: call.traces, + coverage: call.line_coverage, + breakpoints, + logs: call.logs, + deprecated_cheatcodes, + })) + } else { + Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: call.exit_reason, + counterexample: (calldata, call), + breakpoints, + })) + } + } + + /// Aggregates the results from all workers + fn aggregate_results( + &self, + mut workers: Vec, + func: &Function, + shared_state: &SharedFuzzState, + ) -> FuzzTestResult { + let mut result = FuzzTestResult::default(); + if workers.is_empty() { + result.success = true; + return result; + } + + // Find first case and last run worker. Set `failed_corpus_replays`. + let mut first_case_candidate = None; + let mut last_run_worker = None; + for (i, worker) in workers.iter().enumerate() { + if let Some((run, ref case)) = worker.first_case + && first_case_candidate.as_ref().is_none_or(|&(r, _)| run < r) + { + first_case_candidate = Some((run, case.clone())); + } + + if last_run_worker.is_none_or(|(t, _)| worker.last_run_timestamp > t) { + last_run_worker = Some((worker.last_run_timestamp, i)); + } + + // Only set replays from master which is responsible for replaying persisted corpus. + if worker.id == 0 { + result.failed_corpus_replays = worker.failed_corpus_replays; + } + } + result.first_case = first_case_candidate.map(|(_, case)| case).unwrap_or_default(); + let (_, last_run_worker_idx) = last_run_worker.expect("at least one worker"); + + if let Some(&failed_worker_id) = shared_state.failed_worker_id.get() { + result.success = false; + + let failed_worker_idx = workers.iter().position(|w| w.id == failed_worker_id).unwrap(); + let failed_worker = &mut workers[failed_worker_idx]; + + let (calldata, call) = std::mem::take(&mut failed_worker.counterexample); + result.labels = call.labels; + result.traces = call.traces.clone(); + result.breakpoints = call.cheatcodes.map(|c| c.breakpoints); + + match &failed_worker.failure { + Some(TestCaseError::Fail(reason)) => { + let reason = reason.to_string(); + result.reason = (!reason.is_empty()).then_some(reason); + let args = if let Some(data) = calldata.get(4..) { + func.abi_decode_input(data).unwrap_or_default() + } else { + vec![] + }; + result.counterexample = Some(CounterExample::Single( + BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + )); + } + Some(TestCaseError::Reject(reason)) => { + let reason = reason.to_string(); + result.reason = (!reason.is_empty()).then_some(reason); + } + None => {} + } + } else { + let last_run_worker = &workers[last_run_worker_idx]; + result.success = true; + result.traces = last_run_worker.traces.last().cloned(); + result.breakpoints = last_run_worker.breakpoints.clone(); + } + + if !self.config.show_logs { + result.logs = workers[last_run_worker_idx].logs.clone(); + } + + for mut worker in workers { + result.gas_by_case.append(&mut worker.gas_by_case); + if self.config.show_logs { + result.logs.append(&mut worker.logs); + } + result.gas_report_traces.extend(worker.traces.into_iter().map(|t| t.arena)); + HitMaps::merge_opt(&mut result.line_coverage, worker.coverage); + result.deprecated_cheatcodes.extend(worker.deprecated_cheatcodes); + } + + if let Some(reason) = &result.reason + && let Some(reason) = SkipReason::decode_self(reason) + { + result.skipped = true; + result.reason = reason.0; + } + + result + } + + /// Runs a single fuzz worker + #[allow(clippy::too_many_arguments)] + fn run_worker( + &self, + worker_id: usize, + func: &Function, + fuzz_fixtures: &FuzzFixtures, + address: Address, + rd: &RevertDecoder, + shared_state: &SharedFuzzState, + progress: Option<&ProgressBar>, + ) -> Result { + // Prepare let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); let strategy = proptest::prop_oneof![ 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), - dictionary_weight => fuzz_calldata_from_state(func.clone(), state), + dictionary_weight => fuzz_calldata_from_state(func.clone(), &shared_state.state), ] .prop_map(move |calldata| BasicTxDetails { warp: None, @@ -119,272 +416,231 @@ impl FuzzedExecutor { sender: Default::default(), call_details: CallDetails { target: Default::default(), calldata }, }); - // We want to collect at least one trace which will be displayed to user. - let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; - let mut corpus_manager = CorpusManager::new( + let mut corpus = WorkerCorpus::new( + worker_id, self.config.corpus.clone(), strategy.boxed(), - &self.executor, + // Master worker replays the persisted corpus using the executor + if worker_id == 0 { Some(&self.executor_f) } else { None }, Some(func), - None, + None, // fuzzed_contracts for invariant tests )?; + let mut executor = self.executor_f.clone(); - // Start timer for this fuzz test. - let timer = FuzzTestTimer::new(self.config.timeout); - let mut last_metrics_report = Instant::now(); - let max_runs = self.config.runs; - let continue_campaign = |runs: u32| { - if early_exit.should_stop() { - return false; - } + let mut worker = WorkerState::new(worker_id); + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = + std::cmp::max(1, self.config.gas_report_samples / self.num_workers as u32); - if timer.is_enabled() { !timer.is_timed_out() } else { runs < max_runs } + let worker_runs = self.runs_per_worker(worker_id); + debug!(worker_runs); + + let mut runner_config = self.runner.config().clone(); + runner_config.cases = worker_runs; + + let mut runner = if let Some(seed) = self.config.seed { + // For deterministic parallel fuzzing, derive a unique seed for each worker + let worker_seed = if worker_id == 0 { + // Master worker uses the provided seed as is. + seed + } else { + // Derive a worker-specific seed using keccak256(seed || worker_id) + let seed_data = + [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); + U256::from_be_bytes(keccak256(seed_data).0) + }; + trace!(target: "forge::test", ?worker_seed, "deterministic seed for worker {worker_id}"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &worker_seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(runner_config, rng) + } else { + TestRunner::new(runner_config) }; - 'stop: while continue_campaign(test_data.runs) { + let mut persisted_failure = self.persisted_failure.as_ref().filter(|_| worker_id == 0); + + // Offset to stagger corpus syncs across workers; so that workers don't sync at the same + // time. + let sync_offset = worker_id as u32 * 100; + let sync_threshold = SYNC_INTERVAL + sync_offset; + let mut runs_since_sync = sync_threshold; // Always sync at the start. + let mut last_metrics_report = Instant::now(); + // Continue while: + // 1. Global state allows (not timed out, not at global limit, no failure found) + // 2. Worker hasn't reached its specific run limit + 'stop: while shared_state.should_continue() && worker.runs < worker_runs { // If counterexample recorded, replay it first, without incrementing runs. - let input = if let Some(failure) = self.persisted_failure.take() + let input = if worker_id == 0 + && let Some(failure) = persisted_failure.take() && failure.calldata.get(..4).is_some_and(|selector| func.selector() == selector) { failure.calldata.clone() } else { - // If running with progress, then increment current run. - if let Some(progress) = progress { - progress.inc(1); - // Display metrics in progress bar. - if self.config.corpus.collect_edge_coverage() { - progress.set_message(format!("{}", &corpus_manager.metrics)); - } - } else if self.config.corpus.collect_edge_coverage() - && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT - { - // Display metrics inline. - let metrics = json!({ - "timestamp": SystemTime::now() - .duration_since(UNIX_EPOCH)? - .as_secs(), - "test": func.name, - "metrics": &corpus_manager.metrics, - }); - let _ = sh_println!("{}", serde_json::to_string(&metrics)?); - last_metrics_report = Instant::now(); - }; - - if let Some(cheats) = self.executor.inspector_mut().cheatcodes.as_mut() + runs_since_sync += 1; + if runs_since_sync >= sync_threshold { + let timer = Instant::now(); + corpus.sync( + self.num_workers, + &executor, + Some(func), + None, + &shared_state.global_corpus_metrics, + )?; + trace!("finished corpus sync in {:?}", timer.elapsed()); + runs_since_sync = 0; + } + + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() && let Some(seed) = self.config.seed { - cheats.set_seed(seed.wrapping_add(U256::from(test_data.runs))); + cheats.set_seed(seed.wrapping_add(U256::from(worker.runs))); } - test_data.runs += 1; - match corpus_manager.new_input(&mut self.runner, state, func) { + match corpus.new_input(&mut runner, &shared_state.state, func) { Ok(input) => input, Err(err) => { - test_data.failure = Some(TestCaseError::fail(format!( - "failed to generate fuzzed input: {err}" + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id ))); + shared_state.try_claim_failure(worker_id); break 'stop; } } }; - match self.single_fuzz(address, input, &mut corpus_manager) { + let mut inc_runs = || { + let total_runs = shared_state.increment_runs(); + debug_assert!( + shared_state.timer.is_enabled() || total_runs <= self.config.runs, + "worker runs were not distributed correctly" + ); + worker.runs += 1; + if let Some(progress) = progress { + progress.inc(1); + } + total_runs + }; + + worker.last_run_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); + match self.single_fuzz(&executor, address, input, &mut corpus) { Ok(fuzz_outcome) => match fuzz_outcome { FuzzOutcome::Case(case) => { - test_data.gas_by_case.push((case.case.gas, case.case.stipend)); + let total_runs = inc_runs(); - if test_data.first_case.is_none() { - test_data.first_case.replace(case.case); + if worker_id == 0 && self.config.corpus.collect_edge_coverage() { + if let Some(progress) = progress { + corpus.sync_metrics(&shared_state.global_corpus_metrics); + progress + .set_message(format!("{}", shared_state.global_corpus_metrics)); + } else if last_metrics_report.elapsed() + > DURATION_BETWEEN_METRICS_REPORT + { + corpus.sync_metrics(&shared_state.global_corpus_metrics); + // Display metrics inline. + let metrics = json!({ + "timestamp": SystemTime::now() + .duration_since(UNIX_EPOCH)? + .as_secs(), + "test": func.name, + "metrics": shared_state.global_corpus_metrics.load(), + }); + let _ = sh_println!("{metrics}"); + last_metrics_report = Instant::now(); + } + } + + worker.gas_by_case.push((case.case.gas, case.case.stipend)); + + if worker.first_case.is_none() { + worker.first_case = Some((total_runs, case.case)); } if let Some(call_traces) = case.traces { - if test_data.traces.len() == max_traces_to_collect { - test_data.traces.pop(); + if worker.traces.len() == max_traces_to_collect as usize { + worker.traces.pop(); } - test_data.traces.push(call_traces); - test_data.breakpoints.replace(case.breakpoints); + worker.traces.push(call_traces); + worker.breakpoints = Some(case.breakpoints); } // Always store logs from the last run in test_data.logs for display at // verbosity >= 2. When show_logs is true, // accumulate all logs. When false, only keep the last run's logs. if self.config.show_logs { - test_data.logs.extend(case.logs); + worker.logs.extend(case.logs); } else { - test_data.logs = case.logs; + worker.logs = case.logs; } - HitMaps::merge_opt(&mut test_data.coverage, case.coverage); - test_data.deprecated_cheatcodes = case.deprecated_cheatcodes; + HitMaps::merge_opt(&mut worker.coverage, case.coverage); + worker.deprecated_cheatcodes = case.deprecated_cheatcodes; } FuzzOutcome::CounterExample(CounterExampleOutcome { exit_reason: status, counterexample: outcome, .. }) => { + inc_runs(); + let reason = rd.maybe_decode(&outcome.1.result, status); - test_data.logs.extend(outcome.1.logs.clone()); - test_data.counterexample = outcome; - test_data.failure = Some(TestCaseError::fail(reason.unwrap_or_default())); + worker.logs.extend(outcome.1.logs.clone()); + worker.counterexample = outcome; + worker.failure = Some(TestCaseError::fail(reason.unwrap_or_default())); + shared_state.try_claim_failure(worker_id); break 'stop; } }, - Err(err) => { - match err { - TestCaseError::Fail(_) => { - test_data.failure = Some(err); - break 'stop; - } - TestCaseError::Reject(_) => { - // Discard run and apply max rejects if configured. Saturate to handle - // the case of replayed failure, which doesn't count as a run. - test_data.runs = test_data.runs.saturating_sub(1); - test_data.rejects += 1; - - // Update progress bar to reflect rejected runs. - if let Some(progress) = progress { - progress.set_message(format!("([{}] rejected)", test_data.rejects)); - progress.dec(1); - } - - if self.config.max_test_rejects > 0 - && test_data.rejects >= self.config.max_test_rejects - { - test_data.failure = Some(TestCaseError::reject( - FuzzError::TooManyRejects(self.config.max_test_rejects), - )); - break 'stop; - } - } + Err(err) => match err { + TestCaseError::Fail(_) => { + worker.failure = Some(err); + shared_state.try_claim_failure(worker_id); + break 'stop; } - } - } - } + TestCaseError::Reject(_) => { + let max = self.config.max_test_rejects; - let (calldata, call) = test_data.counterexample; - let mut traces = test_data.traces; - let (last_run_traces, last_run_breakpoints) = if test_data.failure.is_none() { - (traces.pop(), test_data.breakpoints) - } else { - (call.traces.clone(), call.cheatcodes.map(|c| c.breakpoints)) - }; + let total = shared_state.increment_rejects(); - // test_data.logs already contains the appropriate logs: - // - For failed tests: logs from the counterexample - // - For successful tests with show_logs=true: all logs from all runs - // - For successful tests with show_logs=false: logs from the last run only - let result_logs = test_data.logs; - - let mut result = FuzzTestResult { - first_case: test_data.first_case.unwrap_or_default(), - gas_by_case: test_data.gas_by_case, - success: test_data.failure.is_none(), - skipped: false, - reason: None, - counterexample: None, - logs: result_logs, - labels: call.labels, - traces: last_run_traces, - breakpoints: last_run_breakpoints, - gas_report_traces: traces.into_iter().map(|a| a.arena).collect(), - line_coverage: test_data.coverage, - deprecated_cheatcodes: test_data.deprecated_cheatcodes, - failed_corpus_replays: corpus_manager.failed_replays(), - }; + // Update progress bar to reflect rejected runs. + // TODO(dani): (pre-existing) conflicts with corpus metrics `set_message` + if !self.config.corpus.collect_edge_coverage() + && let Some(progress) = progress + { + progress.set_message(format!("([{total}] rejected)")); + } - match test_data.failure { - Some(TestCaseError::Fail(reason)) => { - let reason = reason.to_string(); - result.reason = (!reason.is_empty()).then_some(reason); - let args = if let Some(data) = calldata.get(4..) { - func.abi_decode_input(data).unwrap_or_default() - } else { - vec![] - }; - result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), - )); - } - Some(TestCaseError::Reject(reason)) => { - let reason = reason.to_string(); - result.reason = (!reason.is_empty()).then_some(reason); + if max > 0 && total > max { + worker.failure = + Some(TestCaseError::reject(FuzzError::TooManyRejects(max))); + shared_state.try_claim_failure(worker_id); + break 'stop; + } + } + }, } - None => {} } - if let Some(reason) = &result.reason - && let Some(reason) = SkipReason::decode_self(reason) - { - result.skipped = true; - result.reason = reason.0; + if worker_id == 0 { + worker.failed_corpus_replays = corpus.failed_replays; } - state.log_stats(); + // Logs stats + trace!("worker {worker_id} fuzz stats"); + shared_state.state.log_stats(); - Ok(result) + Ok(worker) } - /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` - /// or a `CounterExampleOutcome` - fn single_fuzz( - &mut self, - address: Address, - calldata: Bytes, - coverage_metrics: &mut CorpusManager, - ) -> Result { - let mut call = self - .executor - .call_raw(self.sender, address, calldata.clone(), U256::ZERO) - .map_err(|e| TestCaseError::fail(e.to_string()))?; - let new_coverage = coverage_metrics.merge_edge_coverage(&mut call); - coverage_metrics.process_inputs( - &[BasicTxDetails { - warp: None, - roll: None, - sender: self.sender, - call_details: CallDetails { target: address, calldata: calldata.clone() }, - }], - new_coverage, - ); - - // Handle `vm.assume`. - if call.result.as_ref() == MAGIC_ASSUME { - return Err(TestCaseError::reject(FuzzError::AssumeReject)); - } - - let (breakpoints, deprecated_cheatcodes) = - call.cheatcodes.as_ref().map_or_else(Default::default, |cheats| { - (cheats.breakpoints.clone(), cheats.deprecated.clone()) - }); - - // Consider call success if test should not fail on reverts and reverter is not the - // cheatcode or test address. - let success = if !self.config.fail_on_revert - && call - .reverter - .is_some_and(|reverter| reverter != address && reverter != CHEATCODE_ADDRESS) - { - true - } else { - self.executor.is_raw_call_mut_success(address, &mut call, false) - }; - - if success { - Ok(FuzzOutcome::Case(CaseOutcome { - case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, - traces: call.traces, - coverage: call.line_coverage, - breakpoints, - logs: call.logs, - deprecated_cheatcodes, - })) - } else { - Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { - exit_reason: call.exit_reason, - counterexample: (calldata, call), - breakpoints, - })) - } + /// Determines the number of runs per worker. + fn runs_per_worker(&self, worker_id: usize) -> u32 { + let worker_id = worker_id as u32; + let total_runs = self.config.runs; + let n = self.num_workers as u32; + let runs = total_runs / n; + let remainder = total_runs % n; + // Distribute the remainder evenly among the first `remainder` workers, + // assuming `worker_id` is in `0..n`. + if worker_id < remainder { runs + 1 } else { runs } } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 4ea542ae94a39..86e8e18581764 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -1,14 +1,21 @@ use crate::{ - executors::{Executor, RawCallResult}, + executors::{ + DURATION_BETWEEN_METRICS_REPORT, EarlyExit, EvmError, Executor, FuzzTestTimer, + RawCallResult, corpus::WorkerCorpus, + }, inspectors::Fuzzer, }; use alloy_primitives::{ - Address, Bytes, FixedBytes, Selector, U256, + Address, Bytes, FixedBytes, I256, Selector, U256, map::{AddressMap, HashMap}, }; use alloy_sol_types::{SolCall, sol}; use eyre::{ContextCompat, Result, eyre}; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; +use foundry_common::{ + TestFunctionExt, + contracts::{ContractsByAddress, ContractsByArtifact}, + sh_println, +}; use foundry_config::InvariantConfig; use foundry_evm_core::{ constants::{ @@ -30,6 +37,8 @@ use parking_lot::RwLock; use proptest::{strategy::Strategy, test_runner::TestRunner}; use result::{assert_after_invariant, assert_invariants, can_continue}; use revm::state::Account; +use serde::{Deserialize, Serialize}; +use serde_json::json; use std::{ collections::{HashMap as Map, btree_map::Entry}, sync::Arc, @@ -44,16 +53,10 @@ mod replay; pub use replay::{replay_error, replay_run}; mod result; -use foundry_common::{TestFunctionExt, sh_println}; pub use result::InvariantFuzzTestResult; -use serde::{Deserialize, Serialize}; -use serde_json::json; mod shrink; -use crate::executors::{ - DURATION_BETWEEN_METRICS_REPORT, EarlyExit, EvmError, FuzzTestTimer, corpus::CorpusManager, -}; -pub use shrink::check_sequence; +pub use shrink::{check_sequence, check_sequence_value}; sol! { interface IInvariantTest { @@ -142,6 +145,11 @@ struct InvariantTestData { // until the desired `depth` so we can use the evolving fuzz dictionary // during the run. branch_runner: TestRunner, + + // Optimization mode state: tracks the best (maximum) value and the sequence that produced it. + // Only used when invariant function returns int256. + optimization_best_value: Option, + optimization_best_sequence: Vec, } /// Contains invariant test data. @@ -176,6 +184,8 @@ impl InvariantTest { line_coverage: None, metrics: Map::default(), branch_runner, + optimization_best_value: None, + optimization_best_sequence: vec![], }; Self { fuzz_state, targeted_contracts, test_data } } @@ -244,6 +254,14 @@ impl InvariantTest { // Revert state to not persist values between runs. self.fuzz_state.revert(); } + + /// Updates the optimization state if the new value is better (higher) than the current best. + fn update_optimization_value(&mut self, value: I256, sequence: &[BasicTxDetails]) { + if self.test_data.optimization_best_value.is_none_or(|best| value > best) { + self.test_data.optimization_best_value = Some(value); + self.test_data.optimization_best_sequence = sequence.to_vec(); + } + } } /// Contains data for an invariant test run. @@ -428,7 +446,7 @@ impl<'a> InvariantExecutor<'a> { // inconsistencies whenever proptest tries to use the input case after test // execution. // See . - let mut state_changeset = call_result.state_changeset.clone(); + let mut state_changeset = std::mem::take(&mut call_result.state_changeset); if !call_result.reverted { collect_data( &invariant_test, @@ -452,22 +470,65 @@ impl<'a> InvariantExecutor<'a> { { warn!(target: "forge::test", "{error}"); } - current_run.fuzz_runs.push(FuzzCase { - calldata: tx.call_details.calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); + current_run + .fuzz_runs + .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend }); // Determine if test can continue or should exit. - let result = can_continue( - &invariant_contract, - &mut invariant_test, - &mut current_run, - &self.config, - call_result, - &state_changeset, - ) - .map_err(|e| eyre!(e.to_string()))?; + // Check invariants based on check_interval to improve deep run performance. + // - check_interval=0: only assert on the last call + // - check_interval=1 (default): assert after every call + // - check_interval=N: assert every N calls AND always on the last call + let is_last_call = current_run.depth == self.config.depth - 1; + let should_check_invariant = if self.config.check_interval == 0 { + is_last_call + } else { + self.config.check_interval == 1 + || (current_run.depth + 1).is_multiple_of(self.config.check_interval) + || is_last_call + }; + + let result = if should_check_invariant { + can_continue( + &invariant_contract, + &mut invariant_test, + &mut current_run, + &self.config, + call_result, + &state_changeset, + ) + .map_err(|e| eyre!(e.to_string()))? + } else { + // Skip invariant check but still track reverts + if call_result.reverted { + invariant_test.test_data.failures.reverts += 1; + if self.config.fail_on_revert { + let case_data = error::FailedInvariantCaseData::new( + &invariant_contract, + &self.config, + &invariant_test.targeted_contracts, + ¤t_run.inputs, + call_result, + &[], + ); + invariant_test.test_data.failures.revert_reason = + Some(case_data.revert_reason.clone()); + invariant_test.test_data.failures.error = + Some(InvariantFuzzError::Revert(case_data)); + result::RichInvariantResults::new(false, None) + } else if !invariant_contract.is_optimization() { + // In optimization mode, keep reverted calls to preserve + // warp/roll values for correct replay during shrinking. + current_run.inputs.pop(); + result::RichInvariantResults::new(true, None) + } else { + result::RichInvariantResults::new(true, None) + } + } else { + result::RichInvariantResults::new(true, None) + } + }; + if !result.can_continue || current_run.depth == self.config.depth - 1 { invariant_test.set_last_run_inputs(¤t_run.inputs); } @@ -541,7 +602,9 @@ impl<'a> InvariantExecutor<'a> { gas_report_traces: result.gas_report_traces, line_coverage: result.line_coverage, metrics: result.metrics, - failed_corpus_replays: corpus_manager.failed_replays(), + failed_corpus_replays: corpus_manager.failed_replays, + optimization_best_value: result.optimization_best_value, + optimization_best_sequence: result.optimization_best_sequence, }) } @@ -553,7 +616,7 @@ impl<'a> InvariantExecutor<'a> { invariant_contract: &InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, fuzz_state: EvmFuzzState, - ) -> Result<(InvariantTest, CorpusManager)> { + ) -> Result<(InvariantTest, WorkerCorpus)> { // Finds out the chosen deployed contracts and/or senders. self.select_contract_artifacts(invariant_contract.address)?; let (targeted_senders, targeted_contracts) = @@ -569,25 +632,6 @@ impl<'a> InvariantExecutor<'a> { ) .no_shrink(); - // Allows `override_call_strat` to use the address given by the Fuzzer inspector during - // EVM execution. - let mut call_generator = None; - if self.config.call_override { - let target_contract_ref = Arc::new(RwLock::new(Address::ZERO)); - - call_generator = Some(RandomCallGenerator::new( - invariant_contract.address, - self.runner.clone(), - override_call_strat( - fuzz_state.clone(), - targeted_contracts.clone(), - target_contract_ref.clone(), - fuzz_fixtures.clone(), - ), - target_contract_ref, - )); - } - // If any of the targeted contracts have the storage layout enabled then we can sample // mapping values. To accomplish, we need to record the mapping storage slots and keys. let fuzz_state = @@ -597,8 +641,11 @@ impl<'a> InvariantExecutor<'a> { fuzz_state }; + // Set up fuzzer WITHOUT call_generator initially. + // We defer call_override until after the initial invariant check to avoid + // injecting random calls during setup which would break the invariant assertion. self.executor.inspector_mut().set_fuzzer(Fuzzer { - call_generator, + call_generator: None, fuzz_state: fuzz_state.clone(), collect: true, }); @@ -620,13 +667,44 @@ impl<'a> InvariantExecutor<'a> { return Err(eyre!(error.revert_reason().unwrap_or_default())); } - let corpus_manager = CorpusManager::new( + // NOW enable call_override after the initial invariant check has passed. + // This allows `override_call_strat` to inject calls during actual fuzz runs + // for reentrancy vulnerability detection. + if self.config.call_override { + let target_contract_ref = Arc::new(RwLock::new(Address::ZERO)); + + // Collect handler addresses - these are the contracts we want to inject + // reentrancy into (simulating malicious receive() functions). + let handler_addresses: std::collections::HashSet
= + targeted_contracts.targets.lock().keys().copied().collect(); + + let call_generator = RandomCallGenerator::new( + invariant_contract.address, + handler_addresses, + self.runner.clone(), + override_call_strat( + fuzz_state.clone(), + targeted_contracts.clone(), + target_contract_ref.clone(), + fuzz_fixtures.clone(), + ), + target_contract_ref, + ); + + if let Some(fuzzer) = self.executor.inspector_mut().fuzzer.as_mut() { + fuzzer.call_generator = Some(call_generator); + } + } + + let worker = WorkerCorpus::new( + 0, self.config.corpus.clone(), strategy.boxed(), - &self.executor, + Some(&self.executor), None, Some(&targeted_contracts), )?; + let invariant_test = InvariantTest::new( fuzz_state, targeted_contracts, @@ -635,7 +713,7 @@ impl<'a> InvariantExecutor<'a> { self.runner.clone(), ); - Ok((invariant_test, corpus_manager)) + Ok((invariant_test, worker)) } /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string @@ -1019,28 +1097,32 @@ pub(crate) fn call_invariant_function( Ok((call_result, success)) } -/// Calls the invariant selector and returns call result and if succeeded. -/// Updates the block number and block timestamp if configured. +/// Executes a fuzz call and returns the result. +/// Applies any block timestamp (warp) and block number (roll) adjustments before the call. pub(crate) fn execute_tx(executor: &mut Executor, tx: &BasicTxDetails) -> Result { - // Apply pre-call block adjustments. - if let Some(warp) = tx.warp { + let warp = tx.warp.unwrap_or_default(); + let roll = tx.roll.unwrap_or_default(); + + if warp > 0 || roll > 0 { + // Apply pre-call block adjustments to the executor's env. executor.env_mut().evm_env.block_env.timestamp += warp; - } - if let Some(roll) = tx.roll { executor.env_mut().evm_env.block_env.number += roll; + + // Also update the inspector's cheatcodes.block if set. + // The inspector's block may override the env during interpreter initialization, + // so we need to add our warp/roll on top of any existing cheatcode-set values. + let block_env = executor.env().evm_env.block_env.clone(); + if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { + if let Some(block) = cheatcodes.block.as_mut() { + block.timestamp += warp; + block.number += roll; + } else { + cheatcodes.block = Some(block_env); + } + } } - // Perform the raw call. - let mut call_result = executor + executor .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), U256::ZERO) - .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))?; - - // Propagate block adjustments to call result which will be committed. - if let Some(warp) = tx.warp { - call_result.env.evm_env.block_env.timestamp += warp; - } - if let Some(roll) = tx.roll { - call_result.env.evm_env.block_env.number += roll; - } - Ok(call_result) + .map_err(|e| eyre!(format!("Could not make raw evm call: {e}"))) } diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index e720feba9dd77..fe507123f9fd7 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -1,7 +1,10 @@ use super::{call_after_invariant_function, call_invariant_function, execute_tx}; -use crate::executors::{EarlyExit, Executor, invariant::shrink::shrink_sequence}; +use crate::executors::{ + EarlyExit, Executor, + invariant::shrink::{shrink_sequence, shrink_sequence_value}, +}; use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{Log, map::HashMap}; +use alloy_primitives::{I256, Log, map::HashMap}; use eyre::Result; use foundry_common::{ContractsByAddress, ContractsByArtifact}; use foundry_config::InvariantConfig; @@ -87,13 +90,17 @@ pub fn replay_run( Ok(counterexample_sequence) } -/// Replays the error case, shrinks the failing sequence and collects all necessary traces. +/// Replays and shrinks a call sequence, collecting logs and traces. +/// +/// For check mode (target_value=None): shrinks to find shortest failing sequence. +/// For optimization mode (target_value=Some): shrinks to find shortest sequence producing target. #[expect(clippy::too_many_arguments)] pub fn replay_error( config: InvariantConfig, mut executor: Executor, calls: &[BasicTxDetails], inner_sequence: Option>>, + target_value: Option, invariant_contract: &InvariantContract<'_>, known_contracts: &ContractsByArtifact, ided_contracts: ContractsByAddress, @@ -104,15 +111,24 @@ pub fn replay_error( progress: Option<&ProgressBar>, early_exit: &EarlyExit, ) -> Result> { - // Shrink sequence of failed calls. - let calls = - shrink_sequence(&config, invariant_contract, calls, &executor, progress, early_exit)?; + let calls = if let Some(target) = target_value { + shrink_sequence_value( + &config, + invariant_contract, + calls, + &executor, + target, + progress, + early_exit, + )? + } else { + shrink_sequence(&config, invariant_contract, calls, &executor, progress, early_exit)? + }; if let Some(sequence) = inner_sequence { set_up_inner_replay(&mut executor, &sequence); } - // Replay calls to get the counterexample and to collect logs, traces and coverage. replay_run( invariant_contract, executor, diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs index 611f9fb6bfbc8..b6cd1879d29d0 100644 --- a/crates/evm/evm/src/executors/invariant/result.rs +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -4,6 +4,7 @@ use super::{ }; use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::I256; use eyre::Result; use foundry_config::InvariantConfig; use foundry_evm_core::utils::StateChangeset; @@ -32,8 +33,13 @@ pub struct InvariantFuzzTestResult { pub line_coverage: Option, /// Fuzzed selectors metrics collected during the invariant test runs. pub metrics: HashMap, - /// NUmber of failed replays from persisted corpus. + /// Number of failed replays from persisted corpus. pub failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best (maximum) value achieved. + /// None means standard invariant check mode. + pub optimization_best_value: Option, + /// For optimization mode: the call sequence that produced the best value. + pub optimization_best_sequence: Vec, } /// Enriched results of an invariant run check. @@ -45,7 +51,7 @@ pub(crate) struct RichInvariantResults { } impl RichInvariantResults { - fn new(can_continue: bool, call_result: Option) -> Self { + pub(crate) fn new(can_continue: bool, call_result: Option) -> Self { Self { can_continue, call_result } } } @@ -95,6 +101,9 @@ pub(crate) fn assert_invariants( /// Returns if invariant test can continue and last successful call result of the invariant test /// function (if it can continue). +/// +/// For optimization mode (int256 return), tracks the max value but never fails on invariant. +/// For check mode, asserts the invariant and fails if broken. pub(crate) fn can_continue( invariant_contract: &InvariantContract<'_>, invariant_test: &mut InvariantTest, @@ -104,6 +113,7 @@ pub(crate) fn can_continue( state_changeset: &StateChangeset, ) -> Result { let mut call_results = None; + let is_optimization = invariant_contract.is_optimization(); let handlers_succeeded = || { invariant_test.targeted_contracts.targets.lock().keys().all(|address| { @@ -116,22 +126,38 @@ pub(crate) fn can_continue( }) }; - // Assert invariants if the call did not revert and the handlers did not fail. if !call_result.reverted && handlers_succeeded() { if let Some(traces) = call_result.traces { invariant_run.run_traces.push(traces); } - call_results = assert_invariants( - invariant_contract, - invariant_config, - &invariant_test.targeted_contracts, - &invariant_run.executor, - &invariant_run.inputs, - &mut invariant_test.test_data.failures, - )?; - if call_results.is_none() { - return Ok(RichInvariantResults::new(false, None)); + if is_optimization { + // Optimization mode: call invariant and track max value, never fail. + let (inv_result, success) = call_invariant_function( + &invariant_run.executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + if success + && inv_result.result.len() >= 32 + && let Some(value) = I256::try_from_be_slice(&inv_result.result[..32]) + { + invariant_test.update_optimization_value(value, &invariant_run.inputs); + } + call_results = Some(inv_result); + } else { + // Check mode: assert invariants and fail if broken. + call_results = assert_invariants( + invariant_contract, + invariant_config, + &invariant_test.targeted_contracts, + &invariant_run.executor, + &invariant_run.inputs, + &mut invariant_test.test_data.failures, + )?; + if call_results.is_none() { + return Ok(RichInvariantResults::new(false, None)); + } } } else { // Increase the amount of reverts. @@ -151,9 +177,10 @@ pub(crate) fn can_continue( invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data)); return Ok(RichInvariantResults::new(false, None)); - } else if call_result.reverted { + } else if call_result.reverted && !is_optimization { // If we don't fail test on revert then remove last reverted call from inputs. - // This improves shrinking performance as irrelevant calls won't be checked again. + // In optimization mode, we keep reverted calls to preserve warp/roll values + // for correct replay during shrinking. invariant_run.inputs.pop(); } } diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs index 315cdfa261db8..1e56a019cc576 100644 --- a/crates/evm/evm/src/executors/invariant/shrink.rs +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -2,7 +2,7 @@ use crate::executors::{ EarlyExit, Executor, invariant::{call_after_invariant_function, call_invariant_function, execute_tx}, }; -use alloy_primitives::{Address, Bytes}; +use alloy_primitives::{Address, Bytes, I256, U256}; use foundry_config::InvariantConfig; use foundry_evm_core::constants::MAGIC_ASSUME; use foundry_evm_fuzz::{BasicTxDetails, invariant::InvariantContract}; @@ -30,6 +30,50 @@ impl CallSequenceShrinker { fn current(&self) -> impl Iterator + '_ { (0..self.call_sequence_len).filter(|&call_id| self.included_calls.test(call_id)) } + + /// Advance to the next call index, wrapping around to 0 at the end. + fn next_index(&self, call_idx: usize) -> usize { + if call_idx + 1 == self.call_sequence_len { 0 } else { call_idx + 1 } + } +} + +/// Resets the progress bar for shrinking. +fn reset_shrink_progress(config: &InvariantConfig, progress: Option<&ProgressBar>) { + if let Some(progress) = progress { + progress.set_length(config.shrink_run_limit as u64); + progress.reset(); + progress.set_message(" Shrink"); + } +} + +/// Applies accumulated warp/roll to a call, returning a modified copy. +fn apply_warp_roll(call: &BasicTxDetails, warp: U256, roll: U256) -> BasicTxDetails { + let mut result = call.clone(); + if warp > U256::ZERO { + result.warp = Some(warp); + } + if roll > U256::ZERO { + result.roll = Some(roll); + } + result +} + +/// Applies warp/roll adjustments directly to the executor's environment. +fn apply_warp_roll_to_env(executor: &mut Executor, warp: U256, roll: U256) { + if warp > U256::ZERO || roll > U256::ZERO { + executor.env_mut().evm_env.block_env.timestamp += warp; + executor.env_mut().evm_env.block_env.number += roll; + + let block_env = executor.env().evm_env.block_env.clone(); + if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { + if let Some(block) = cheatcodes.block.as_mut() { + block.timestamp += warp; + block.number += roll; + } else { + cheatcodes.block = Some(block_env); + } + } + } } pub(crate) fn shrink_sequence( @@ -42,12 +86,7 @@ pub(crate) fn shrink_sequence( ) -> eyre::Result> { trace!(target: "forge::test", "Shrinking sequence of {} calls.", calls.len()); - // Reset run count and display shrinking message. - if let Some(progress) = progress { - progress.set_length(config.shrink_run_limit as u64); - progress.reset(); - progress.set_message(" Shrink"); - } + reset_shrink_progress(config, progress); let target_address = invariant_contract.address; let calldata: Bytes = invariant_contract.invariant_function.selector().to_vec().into(); @@ -59,14 +98,13 @@ pub(crate) fn shrink_sequence( } let mut call_idx = 0; - let mut shrinker = CallSequenceShrinker::new(calls.len()); + for _ in 0..config.shrink_run_limit { if early_exit.should_stop() { break; } - // Remove call at current index. shrinker.included_calls.clear(call_idx); match check_sequence( @@ -89,12 +127,7 @@ pub(crate) fn shrink_sequence( progress.inc(1); } - // Restart from first call once we reach the end of sequence. - if call_idx + 1 == shrinker.call_sequence_len { - call_idx = 0; - } else { - call_idx += 1; - }; + call_idx = shrinker.next_index(call_idx); } Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) @@ -140,3 +173,135 @@ pub fn check_sequence( Ok((success, true)) } + +/// Shrinks a call sequence to the shortest sequence that still produces the target optimization +/// value. This is specifically for optimization mode where we want to find the minimal sequence +/// that achieves the maximum value. +/// +/// Unlike `shrink_sequence` (for check mode), this function: +/// - Accumulates warp/roll values from removed calls into the next kept call +/// - Checks for target value equality rather than invariant failure +pub(crate) fn shrink_sequence_value( + config: &InvariantConfig, + invariant_contract: &InvariantContract<'_>, + calls: &[BasicTxDetails], + executor: &Executor, + target_value: I256, + progress: Option<&ProgressBar>, + early_exit: &EarlyExit, +) -> eyre::Result> { + trace!(target: "forge::test", "Shrinking optimization sequence of {} calls for target value {}.", calls.len(), target_value); + + reset_shrink_progress(config, progress); + + let target_address = invariant_contract.address; + let calldata: Bytes = invariant_contract.invariant_function.selector().to_vec().into(); + + // Special case: check if target value is achieved with 0 calls. + if check_sequence_value(executor.clone(), calls, vec![], target_address, calldata.clone())? + == Some(target_value) + { + return Ok(vec![]); + } + + let mut call_idx = 0; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + + for _ in 0..config.shrink_run_limit { + if early_exit.should_stop() { + break; + } + + shrinker.included_calls.clear(call_idx); + + let keeps_target = check_sequence_value( + executor.clone(), + calls, + shrinker.current().collect(), + target_address, + calldata.clone(), + )? == Some(target_value); + + if keeps_target { + if shrinker.included_calls.count() == 1 { + break; + } + } else { + shrinker.included_calls.set(call_idx); + } + + if let Some(progress) = progress { + progress.inc(1); + } + + call_idx = shrinker.next_index(call_idx); + } + + // Build the final shrunk sequence, accumulating warp/roll from removed calls. + let mut result = Vec::new(); + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + + for (idx, call) in calls.iter().enumerate() { + accumulated_warp += call.warp.unwrap_or(U256::ZERO); + accumulated_roll += call.roll.unwrap_or(U256::ZERO); + + if shrinker.included_calls.test(idx) { + result.push(apply_warp_roll(call, accumulated_warp, accumulated_roll)); + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + } + + Ok(result) +} + +/// Executes a call sequence and returns the optimization value (int256) from the invariant +/// function. Used during shrinking for optimization mode. +/// +/// Returns `None` if the invariant call fails or doesn't return a valid int256. +/// Unlike `check_sequence`, this applies warp/roll from ALL calls (including removed ones). +pub fn check_sequence_value( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, +) -> eyre::Result> { + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + let mut seq_iter = sequence.iter().peekable(); + + for (idx, tx) in calls.iter().enumerate() { + accumulated_warp += tx.warp.unwrap_or(U256::ZERO); + accumulated_roll += tx.roll.unwrap_or(U256::ZERO); + + if seq_iter.peek() == Some(&&idx) { + seq_iter.next(); + + let tx_with_accumulated = apply_warp_roll(tx, accumulated_warp, accumulated_roll); + let mut call_result = execute_tx(&mut executor, &tx_with_accumulated)?; + + if !call_result.reverted { + executor.commit(&mut call_result); + } + + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + } + + // Apply any remaining accumulated warp/roll before calling invariant. + apply_warp_roll_to_env(&mut executor, accumulated_warp, accumulated_roll); + + let (inv_result, success) = call_invariant_function(&executor, test_address, calldata)?; + + if success + && inv_result.result.len() >= 32 + && let Some(value) = I256::try_from_be_slice(&inv_result.result[..32]) + { + return Ok(Some(value)); + } + + Ok(None) +} diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index fa9d15b0e4d12..23bd0cd97ce67 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -91,11 +91,14 @@ sol! { #[derive(Clone, Debug)] pub struct Executor { /// The underlying `revm::Database` that contains the EVM storage. + /// + /// Wrapped in `Arc` for efficient cloning during parallel fuzzing. Use [`Arc::make_mut`] + /// for copy-on-write semantics when mutation is needed. // Note: We do not store an EVM here, since we are really // only interested in the database. REVM's `EVM` is a thin // wrapper around spawning a new EVM on every call anyway, // so the performance difference should be negligible. - backend: Backend, + backend: Arc, /// The EVM environment. env: Env, /// The Revm inspector stack. @@ -107,12 +110,6 @@ pub struct Executor { } impl Executor { - /// Creates a new `ExecutorBuilder`. - #[inline] - pub fn builder() -> ExecutorBuilder { - ExecutorBuilder::new() - } - /// Creates a new `Executor` with the given arguments. #[inline] pub fn new( @@ -135,7 +132,7 @@ impl Executor { }, ); - Self { backend, env, inspector, gas_limit, legacy_assertions } + Self { backend: Arc::new(backend), env, inspector, gas_limit, legacy_assertions } } fn clone_with_backend(&self, backend: Backend) -> Self { @@ -145,7 +142,13 @@ impl Executor { self.env.tx.clone(), self.spec_id(), ); - Self::new(backend, env, self.inspector().clone(), self.gas_limit, self.legacy_assertions) + Self { + backend: Arc::new(backend), + env, + inspector: self.inspector().clone(), + gas_limit: self.gas_limit, + legacy_assertions: self.legacy_assertions, + } } /// Returns a reference to the EVM backend. @@ -154,8 +157,11 @@ impl Executor { } /// Returns a mutable reference to the EVM backend. + /// + /// Uses copy-on-write semantics: if other clones of this executor share the backend, + /// this will clone the backend first. pub fn backend_mut(&mut self) -> &mut Backend { - &mut self.backend + Arc::make_mut(&mut self.backend) } /// Returns a reference to the EVM environment. diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 7b63c019e848c..ac506855355dc 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -29,8 +29,8 @@ impl TracingExecutor { state_overrides: Option, ) -> eyre::Result { let db = Backend::spawn(Some(fork))?; - // configures a bare version of the evm executor: no cheatcode inspector is enabled, - // tracing will be enabled only for the targeted transaction + // configures a bare version of the evm executor: no cheatcode and log_collector inspector + // is enabled, tracing will be enabled only for the targeted transaction let mut executor = ExecutorBuilder::new() .inspectors(|stack| { stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) diff --git a/crates/evm/evm/src/inspectors/custom_printer.rs b/crates/evm/evm/src/inspectors/custom_printer.rs index 206d4fe3a65a9..b6b1a2aa756c5 100644 --- a/crates/evm/evm/src/inspectors/custom_printer.rs +++ b/crates/evm/evm/src/inspectors/custom_printer.rs @@ -94,11 +94,11 @@ where fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option { let _ = sh_println!( "CREATE CALL: caller:{:?}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", - inputs.caller, - inputs.scheme, - inputs.value, - inputs.init_code, - inputs.gas_limit + inputs.caller(), + inputs.scheme(), + inputs.value(), + inputs.init_code(), + inputs.gas_limit() ); None } diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 4dd550caaaf0f..bc09521fa771d 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,7 +1,10 @@ use alloy_primitives::Log; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; -use foundry_common::{ErrorExt, fmt::ConsoleFmt}; -use foundry_evm_core::{InspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS}; +use foundry_common::{ErrorExt, fmt::ConsoleFmt, sh_println}; +use foundry_evm_core::{ + FoundryInspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS, + decode::decode_console_log, +}; use revm::{ Inspector, context::ContextTr, @@ -14,13 +17,22 @@ use revm::{ /// An inspector that collects logs during execution. /// /// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs. -#[derive(Clone, Debug, Default)] -pub struct LogCollector { +#[derive(Clone, Debug)] +pub enum LogCollector { /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs. - pub logs: Vec, + Capture { logs: Vec }, + /// Print logs directly to stdout. + LiveLogs, } impl LogCollector { + pub fn into_captured_logs(self) -> Option> { + match self { + Self::Capture { logs } => Some(logs), + Self::LiveLogs => None, + } + } + #[cold] fn do_hardhat_log(&mut self, context: &mut CTX, inputs: &CallInputs) -> Option where @@ -41,9 +53,32 @@ impl LogCollector { fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.logs.push(hh_to_ds(&decoded)); + self.push_msg(&decoded.fmt(Default::default())); Ok(()) } + + fn push_raw_log(&mut self, log: Log) { + match self { + Self::Capture { logs } => logs.push(log), + Self::LiveLogs => { + if let Some(msg) = decode_console_log(&log) { + sh_println!("{msg}").expect("fail printing to stdout"); + } else { + // This case should not happen if the users call through forge-std. + // We print the log data for the user nonetheless. + sh_println!("console.log({:?}, {})", log.data.topics(), log.data.data) + .expect("fail printing to stdout"); + } + } + } + } + + fn push_msg(&mut self, msg: &str) { + match self { + Self::Capture { logs } => logs.push(new_console_log(msg)), + Self::LiveLogs => sh_println!("{msg}").expect("fail printing to stdout"), + } + } } impl Inspector for LogCollector @@ -51,7 +86,7 @@ where CTX: ContextTr, { fn log(&mut self, _context: &mut CTX, log: Log) { - self.logs.push(log); + self.push_raw_log(log); } fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option { @@ -62,19 +97,12 @@ where } } -impl InspectorExt for LogCollector { +impl FoundryInspectorExt for LogCollector { fn console_log(&mut self, msg: &str) { - self.logs.push(new_console_log(msg)); + self.push_msg(msg); } } -/// Converts a Hardhat `console.log` call to a DSTest `log(string)` event. -fn hh_to_ds(call: &console::hh::ConsoleCalls) -> Log { - // Convert the parameters of the call to their string representation using `ConsoleFmt`. - let msg = call.fmt(Default::default()); - new_console_log(&msg) -} - /// Creates a `console.log(string)` event. fn new_console_log(msg: &str) -> Log { Log::new_unchecked( diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index a76409308a0b3..7f3f99e6b6a0e 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -11,8 +11,9 @@ use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, Wallets}; use foundry_common::compile::Analysis; use foundry_compilers::ProjectPathsConfig; use foundry_evm_core::{ - ContextExt, Env, InspectorExt, - backend::{DatabaseExt, JournaledState}, + Env, FoundryInspectorExt, InspectorExt, + backend::{DatabaseExt, FoundryJournalExt, JournaledState}, + env::FoundryContextExt, evm::new_evm_with_inspector, }; use foundry_evm_coverage::HitMaps; @@ -21,10 +22,11 @@ use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ Inspector, context::{ - BlockEnv, + BlockEnv, Cfg, ContextTr, JournalTr, result::{ExecutionResult, Output}, }, context_interface::CreateScheme, + inspector::JournalExt, interpreter::{ CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, @@ -59,6 +61,9 @@ pub struct InspectorStackBuilder { /// Whether to enable tracing and revert diagnostics. pub trace_mode: TraceMode, /// Whether logs should be collected. + /// - None for no log collection. + /// - Some(true) for realtime console.log-ing. + /// - Some(false) for log collection. pub logs: Option, /// Whether line coverage info should be collected. pub line_coverage: Option, @@ -134,10 +139,10 @@ impl InspectorStackBuilder { self } - /// Set whether to collect logs. + /// Set the log collector, and whether to print the logs directly to stdout. #[inline] - pub fn logs(mut self, yes: bool) -> Self { - self.logs = Some(yes); + pub fn logs(mut self, live_logs: bool) -> Self { + self.logs = Some(live_logs); self } @@ -228,7 +233,7 @@ impl InspectorStackBuilder { stack.set_chisel(chisel_state); } stack.collect_line_coverage(line_coverage.unwrap_or(false)); - stack.collect_logs(logs.unwrap_or(true)); + stack.collect_logs(logs); stack.print(print.unwrap_or(false)); stack.tracing(trace_mode); @@ -366,6 +371,17 @@ impl CheatcodesExecutor for InspectorStackInner { fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { self.tracer.as_deref_mut() } + + fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option
) { + self.in_inner_context = enabled; + self.inner_context_data = if enabled { + Some(InnerContextData { + original_origin: original_origin.expect("origin required when enabling inner ctx"), + }) + } else { + None + }; + } } impl InspectorStack { @@ -479,9 +495,18 @@ impl InspectorStack { } /// Set whether to enable the log collector. + /// - None for no log collection. + /// - Some(true) for realtime console.log-ing. + /// - Some(false) for log collection. #[inline] - pub fn collect_logs(&mut self, yes: bool) { - self.log_collector = yes.then(Default::default); + pub fn collect_logs(&mut self, live_logs: Option) { + self.log_collector = live_logs.map(|live_logs| { + Box::new(if live_logs { + LogCollector::LiveLogs + } else { + LogCollector::Capture { logs: Vec::new() } + }) + }); } /// Set whether to enable the trace printer. @@ -556,7 +581,7 @@ impl InspectorStack { }); InspectorData { - logs: log_collector.map(|logs| logs.logs).unwrap_or_default(), + logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(), labels: cheatcodes .as_ref() .map(|cheatcodes| cheatcodes.labels.clone()) @@ -579,7 +604,7 @@ impl InspectorStackRefMut<'_> { fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>) { let inner_context_data = self.inner_context_data.as_ref().expect("should be called in inner context"); - ecx.tx.caller = inner_context_data.original_origin; + ecx.tx_mut().caller = inner_context_data.original_origin; } fn do_call_end( @@ -654,29 +679,30 @@ impl InspectorStackRefMut<'_> { gas_limit: u64, value: U256, ) -> (InterpreterResult, Option
) { - let cached_env = Env::from(ecx.cfg.clone(), ecx.block.clone(), ecx.tx.clone()); - - ecx.block.basefee = 0; - ecx.tx.chain_id = Some(ecx.cfg.chain_id); - ecx.tx.caller = caller; - ecx.tx.kind = kind; - ecx.tx.data = input; - ecx.tx.value = value; + let cached_env = Env::from(ecx.cfg().clone(), ecx.block().clone(), ecx.tx().clone()); + + ecx.block_mut().basefee = 0; + ecx.tx_mut().chain_id = Some(ecx.cfg().chain_id()); + ecx.tx_mut().caller = caller; + ecx.tx_mut().kind = kind; + ecx.tx_mut().data = input; + ecx.tx_mut().value = value; // Add 21000 to the gas limit to account for the base cost of transaction. - ecx.tx.gas_limit = gas_limit + 21000; + ecx.tx_mut().gas_limit = gas_limit + 21000; // If we haven't disabled gas limit checks, ensure that transaction gas limit will not // exceed block gas limit. - if !ecx.cfg.disable_block_gas_limit { - ecx.tx.gas_limit = std::cmp::min(ecx.tx.gas_limit, ecx.block.gas_limit); + if !ecx.cfg().disable_block_gas_limit { + ecx.tx_mut().gas_limit = std::cmp::min(ecx.tx().gas_limit, ecx.block().gas_limit); } - ecx.tx.gas_price = 0; + ecx.tx_mut().gas_price = 0; self.inner_context_data = Some(InnerContextData { original_origin: cached_env.tx.caller }); self.in_inner_context = true; let res = self.with_inspector(|inspector| { - let (db, journal, env) = ecx.as_db_env_and_journal(); + let (journal, env) = ecx.journal_and_env_mut(); + let (db, journal) = journal.as_db_and_inner(); let mut evm = new_evm_with_inspector(db, env.to_owned(), inspector); evm.journaled_state.state = { @@ -726,8 +752,8 @@ impl InspectorStackRefMut<'_> { }; for (addr, mut acc) in res.state { - let Some(acc_mut) = ecx.journaled_state.state.get_mut(&addr) else { - ecx.journaled_state.state.insert(addr, acc); + let Some(acc_mut) = ecx.journal_mut().evm_state_mut().get_mut(&addr) else { + ecx.journal_mut().evm_state_mut().insert(addr, acc); continue; }; @@ -797,7 +823,7 @@ impl InspectorStackRefMut<'_> { if self.enable_isolation { // If we're in isolation mode, we need to keep track of the state at the beginning of // the frame to be able to roll back on revert - self.top_frame_journal.clone_from(&ecx.journaled_state.state); + self.top_frame_journal.clone_from(ecx.journal().evm_state()); } } @@ -821,7 +847,7 @@ impl InspectorStackRefMut<'_> { // created We can't rely on revm's journal because it doesn't account for changes // made by isolated calls if self.enable_isolation { - ecx.journaled_state.state = std::mem::take(&mut self.top_frame_journal); + *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal); } } @@ -934,12 +960,12 @@ impl Inspector> for InspectorStackRefMut<'_> ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, call: &mut CallInputs, ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { self.adjust_evm_data_for_inner_context(ecx); return None; } - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_start(ecx); } @@ -963,7 +989,7 @@ impl Inspector> for InspectorStackRefMut<'_> if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { // Handle mocked functions, replace bytecode address with mock if matched. - if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) { + if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) { let input_bytes = call.input.bytes(ecx); // Check if any mock function set for call data or if catch-all mock function set // for selector. @@ -981,7 +1007,7 @@ impl Inspector> for InspectorStackRefMut<'_> } } - if self.enable_isolation && !self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1 { match call.scheme { // Isolate CALLs CallScheme::Call => { @@ -1003,8 +1029,8 @@ impl Inspector> for InspectorStackRefMut<'_> } // Mark accounts and storage cold before STATICCALLs CallScheme::StaticCall => { - let JournaledState { state, warm_addresses, .. } = - &mut ecx.journaled_state.inner; + let (_, journal_inner) = ecx.journal_mut().as_db_and_inner(); + let JournaledState { state, warm_addresses, .. } = journal_inner; for (addr, acc_mut) in state { // Do not mark accounts and storage cold accounts with arbitrary storage. if let Some(cheatcodes) = &self.cheatcodes @@ -1038,13 +1064,13 @@ impl Inspector> for InspectorStackRefMut<'_> ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { return; } self.do_call_end(ecx, inputs, outcome); - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_end(ecx, outcome.result.result); } } @@ -1054,12 +1080,12 @@ impl Inspector> for InspectorStackRefMut<'_> ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, create: &mut CreateInputs, ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { self.adjust_evm_data_for_inner_context(ecx); return None; } - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_start(ecx); } @@ -1069,18 +1095,18 @@ impl Inspector> for InspectorStackRefMut<'_> |inspector| inspector.create(ecx, create).map(Some), ); - if !matches!(create.scheme, CreateScheme::Create2 { .. }) + if !matches!(create.scheme(), CreateScheme::Create2 { .. }) && self.enable_isolation && !self.in_inner_context - && ecx.journaled_state.depth == 1 + && ecx.journal().depth() == 1 { let (result, address) = self.transact_inner( ecx, TxKind::Create, - create.caller, - create.init_code.clone(), - create.gas_limit, - create.value, + create.caller(), + create.init_code().clone(), + create.gas_limit(), + create.value(), ); return Some(CreateOutcome { result, address }); } @@ -1096,13 +1122,13 @@ impl Inspector> for InspectorStackRefMut<'_> ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { return; } self.do_create_end(ecx, call, outcome); - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_end(ecx, outcome.result.result); } } @@ -1116,23 +1142,19 @@ impl Inspector> for InspectorStackRefMut<'_> } } -impl InspectorExt for InspectorStackRefMut<'_> { - fn should_use_create2_factory( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - inputs: &CreateInputs, - ) -> bool { +impl FoundryInspectorExt for InspectorStackRefMut<'_> { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { call_inspectors!( #[ret] [&mut self.cheatcodes], - |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, + |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) }, ); false } fn console_log(&mut self, msg: &str) { - call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log( + call_inspectors!([&mut self.log_collector], |inspector| FoundryInspectorExt::console_log( inspector, msg )); } @@ -1223,13 +1245,9 @@ impl Inspector> for InspectorStack { } } -impl InspectorExt for InspectorStack { - fn should_use_create2_factory( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - inputs: &CreateInputs, - ) -> bool { - self.as_mut().should_use_create2_factory(ecx, inputs) +impl FoundryInspectorExt for InspectorStack { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { + self.as_mut().should_use_create2_factory(depth, inputs) } fn get_networks(&self) -> NetworkConfigs { diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 2cbc3675c6453..5c40b2c264c7b 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -13,7 +13,8 @@ pub mod inspectors; pub use foundry_evm_core as core; pub use foundry_evm_core::{ - Env, EnvMut, EvmEnv, InspectorExt, backend, constants, decode, fork, hardfork, opts, utils, + Env, EnvMut, EvmEnv, FoundryInspectorExt, InspectorExt, backend, constants, decode, fork, + hardfork, opts, utils, }; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs index 1dfd29ead8278..3776df17001aa 100644 --- a/crates/evm/fuzz/src/inspector.rs +++ b/crates/evm/fuzz/src/inspector.rs @@ -1,10 +1,11 @@ use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; use foundry_common::mapping_slots::step as mapping_step; +use foundry_evm_core::constants::CHEATCODE_ADDRESS; use revm::{ Inspector, - context::{ContextTr, Transaction}, + context::{ContextTr, JournalTr, Transaction}, inspector::JournalExt, - interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, Interpreter}, + interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, CallValue, Interpreter}, }; /// An inspector that can fuzz and collect data for that effect. @@ -36,7 +37,7 @@ where fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { // We don't want to override the very first call made to the test contract. if self.call_generator.is_some() && ecx.tx().caller() != inputs.caller { - self.override_call(inputs); + self.override_call(ecx, inputs); } // We only collect `stack` and `memory` data before and after calls. @@ -48,7 +49,10 @@ where fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) { if let Some(ref mut call_generator) = self.call_generator { - call_generator.used = false; + // Decrement depth when any call ends while inside an override + if call_generator.override_depth > 0 { + call_generator.override_depth -= 1; + } } // We only collect `stack` and `memory` data before and after calls. @@ -74,25 +78,75 @@ impl Fuzzer { self.collect = false; } - /// Overrides an external call and tries to call any method of msg.sender. - fn override_call(&mut self, call: &mut CallInputs) { - if let Some(ref mut call_generator) = self.call_generator { - // We only override external calls which are not coming from the test contract. - if call.caller != call_generator.test_address - && call.scheme == CallScheme::Call - && !call_generator.used - { - // There's only a 30% chance that an override happens. - if let Some(tx) = call_generator.next(call.caller, call.target_address) { - call.input = CallInput::Bytes(tx.call_details.calldata.0.into()); - call.caller = tx.sender; - call.target_address = tx.call_details.target; - - // TODO: in what scenarios can the following be problematic - call.bytecode_address = tx.call_details.target; - call_generator.used = true; - } - } + /// Overrides an external call to simulate reentrancy attacks. + /// + /// This function detects reentrancy vulnerabilities by replacing external calls + /// with callbacks that reenter the caller contract. + /// + /// For calls with value (ETH transfers): + /// 1. Performs the ETH transfer via the journal first + /// 2. Replaces the call with a reentrant callback (value = 0) + /// + /// For calls without value: + /// - Replaces the call entirely with a reentrant callback + /// + /// This simulates malicious contracts that immediately reenter when called. + fn override_call(&mut self, ecx: &mut CTX, call: &mut CallInputs) + where + CTX: ContextTr, + { + let Some(ref mut call_generator) = self.call_generator else { + return; + }; + + // Skip if: + // - Caller is test contract (don't override the initial calls from the test) + // - Not a CALL scheme (only override CALLs, not STATICCALLs, DELEGATECALLs, etc.) + // - Inside an override (prevent recursive overrides) + // - Target is cheatcode address + // - Neither caller nor target is a handler contract + // + // We override calls when either the caller OR target is a handler. This covers: + // 1. EtherStore pattern: handler sends ETH out, attacker reenters handler + // 2. Rari pattern: external protocol sends ETH to handler, handler reenters protocol + let caller_is_handler = call_generator.is_handler(call.caller); + let target_is_handler = call_generator.is_handler(call.target_address); + if call.caller == call_generator.test_address + || call.scheme != CallScheme::Call + || call_generator.override_depth > 0 + || call.target_address == CHEATCODE_ADDRESS + || (!caller_is_handler && !target_is_handler) + { + return; } + + // There's only a ~27% chance that an override happens (90% * 30% from strategy). + let Some(tx) = call_generator.next(call.caller, call.target_address) else { + return; + }; + + // For value transfers, perform the ETH transfer before injecting the callback. + // This simulates a malicious receive() that gets the ETH and then reenters. + let value = call.transfer_value().unwrap_or_default(); + let has_value = !value.is_zero() && call.gas_limit > 2300; + if has_value && ecx.journal_mut().transfer(call.caller, call.target_address, value).is_err() + { + return; + } + + // Replace the call with a reentrant callback + call.input = CallInput::Bytes(tx.call_details.calldata.0.into()); + call.caller = tx.sender; + call.target_address = tx.call_details.target; + call.bytecode_address = tx.call_details.target; + // Clear known_bytecode to force REVM to load bytecode from the new target. + // Without this, REVM uses cached bytecode from the original target (e.g., empty + // bytecode for EOA), causing the call to short-circuit before executing any code. + call.known_bytecode = None; + // Clear value since ETH was already transferred above + call.value = CallValue::Transfer(alloy_primitives::U256::ZERO); + + // Track that we're inside an overridden call to avoid recursive overrides + call_generator.override_depth = 1; } } diff --git a/crates/evm/fuzz/src/invariant/call_override.rs b/crates/evm/fuzz/src/invariant/call_override.rs index 0c33c54d4e48d..a9fdb1fc36bfc 100644 --- a/crates/evm/fuzz/src/invariant/call_override.rs +++ b/crates/evm/fuzz/src/invariant/call_override.rs @@ -6,22 +6,30 @@ use proptest::{ strategy::{SBoxedStrategy, Strategy, ValueTree}, test_runner::TestRunner, }; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; /// Given a TestRunner and a strategy, it generates calls. Used inside the Fuzzer inspector to -/// override external calls to test for potential reentrancy vulnerabilities.. +/// override external calls to test for potential reentrancy vulnerabilities. +/// +/// The key insight is that we only override calls TO handler contracts (targeted contracts). +/// This simulates a malicious contract that reenters when receiving ETH via its receive() function. #[derive(Clone, Debug)] pub struct RandomCallGenerator { /// Address of the test contract. pub test_address: Address, + /// Addresses of handler contracts that can be reentered. + /// We only inject callbacks when the call target is one of these. + pub handler_addresses: Arc>>, /// Runner that will generate the call from the strategy. pub runner: Arc>, /// Strategy to be used to generate calls from `target_reference`. pub strategy: SBoxedStrategy>, /// Reference to which contract we want a fuzzed calldata from. pub target_reference: Arc>, - /// Flag to know if a call has been overridden. Don't allow nesting for now. - pub used: bool, + /// Tracks the call depth when an override is active. When > 0, we're inside an overridden + /// call and should not override nested calls. Incremented when we override a call, + /// decremented when any call ends while inside an override. + pub override_depth: usize, /// If set to `true`, consumes the next call from `last_sequence`, otherwise queries it from /// the strategy. pub replay: bool, @@ -32,21 +40,28 @@ pub struct RandomCallGenerator { impl RandomCallGenerator { pub fn new( test_address: Address, + handler_addresses: HashSet
, runner: TestRunner, strategy: impl Strategy + Send + Sync + 'static, target_reference: Arc>, ) -> Self { Self { test_address, + handler_addresses: Arc::new(RwLock::new(handler_addresses)), runner: Arc::new(Mutex::new(runner)), strategy: weighted(0.9, strategy).sboxed(), target_reference, last_sequence: Arc::default(), replay: false, - used: false, + override_depth: 0, } } + /// Check if the given address is a handler that can be reentered. + pub fn is_handler(&self, address: Address) -> bool { + self.handler_addresses.read().contains(&address) + } + /// All `self.next()` calls will now pop `self.last_sequence`. Used to replay an invariant /// failure. pub fn set_replay(&mut self, status: bool) { diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index a773e8f04bb27..cb99d71771954 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -14,6 +14,12 @@ pub use filters::{ArtifactFilters, SenderFilters}; use foundry_common::{ContractsByAddress, ContractsByArtifact}; use foundry_evm_core::utils::{StateChangeset, get_function}; +/// Returns true if the function returns `int256`, indicating optimization mode. +/// In optimization mode, the fuzzer maximizes the return value instead of checking invariants. +pub fn is_optimization_invariant(func: &Function) -> bool { + func.outputs.len() == 1 && func.outputs[0].ty == "int256" +} + /// Contracts identified as targets during a fuzz run. /// /// During execution, any newly created contract is added as target and used through the rest of @@ -264,3 +270,20 @@ pub struct InvariantContract<'a> { /// ABI of the test contract. pub abi: &'a JsonAbi, } + +impl<'a> InvariantContract<'a> { + /// Creates a new invariant contract. + pub fn new( + address: Address, + invariant_function: &'a Function, + call_after_invariant: bool, + abi: &'a JsonAbi, + ) -> Self { + Self { address, invariant_function, call_after_invariant, abi } + } + + /// Returns true if this is an optimization mode invariant (returns int256). + pub fn is_optimization(&self) -> bool { + is_optimization_invariant(self.invariant_function) + } +} diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index ae1f011413606..44d71fb6deee3 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -36,25 +36,35 @@ pub use inspector::Fuzzer; /// Details of a transaction generated by fuzz strategy for fuzzing a target. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BasicTxDetails { - // Time (in seconds) to increase block timestamp before executing the tx. + /// Time (in seconds) to increase block timestamp before executing the tx. + #[serde(default, skip_serializing_if = "Option::is_none")] pub warp: Option, - // Number to increase block number before executing the tx. + /// Number to increase block number before executing the tx. + #[serde(default, skip_serializing_if = "Option::is_none")] pub roll: Option, - // Transaction sender address. + /// Transaction sender address. pub sender: Address, - // Transaction call details. + /// Transaction call details. + #[serde(flatten)] pub call_details: CallDetails, } /// Call details of a transaction generated to fuzz. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CallDetails { - // Address of target contract. + /// Address of target contract. pub target: Address, - // The data of the transaction. + /// The data of the transaction. pub calldata: Bytes, } +impl BasicTxDetails { + /// Returns an estimate of the serialized (JSON) size in bytes. + pub fn estimate_serialized_size(&self) -> usize { + size_of::() + self.call_details.calldata.len() * 2 + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[expect(clippy::large_enum_variant)] pub enum CounterExample { @@ -277,7 +287,7 @@ pub struct FuzzTestResult { // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, - /// NUmber of failed replays from persisted corpus. + /// Number of failed replays from persisted corpus. pub failed_corpus_replays: usize, } @@ -307,8 +317,6 @@ impl FuzzTestResult { /// Data of a single fuzz test case #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct FuzzCase { - /// The calldata used for this fuzz test - pub calldata: Bytes, /// Consumed gas pub gas: u64, /// The initial gas stipend for the transaction diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 07495a9cbeba4..0f7ba181be7ac 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -29,20 +29,30 @@ pub fn override_call_strat( let fuzz_state = fuzz_state.clone(); let fuzz_fixtures = fuzz_fixtures.clone(); - let func = { + let (actual_target, func) = { let contracts = contracts.targets.lock(); - let contract = contracts.get(&target_address).unwrap_or_else(|| { - // Choose a random contract if target selected by lazy strategy is not in fuzz run - // identified contracts. This can happen when contract is created in `setUp` call - // but is not included in targetContracts. - contracts.values().choose(&mut rand::rng()).unwrap() - }); + // If the target address is in the contracts map, use it directly. + // Otherwise, fall back to a random contract from the targeted contracts. + // This can happen when call_override sets target_reference to a contract + // that is not in targetContracts (e.g., the protocol contract during reentrancy). + let (actual_target, contract) = + contracts.get(&target_address).map(|c| (target_address, c)).unwrap_or_else(|| { + let entry = contracts + .iter() + .choose(&mut rand::rng()) + .expect("at least one target contract"); + (*entry.0, entry.1) + }); let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect(); - any::().prop_map(move |index| index.get(&fuzzed_functions).clone()) + ( + actual_target, + any::() + .prop_map(move |index| index.get(&fuzzed_functions).clone()), + ) }; func.prop_flat_map(move |func| { - fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func) + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, actual_target, func) }) }) } diff --git a/crates/evm/fuzz/src/strategies/mod.rs b/crates/evm/fuzz/src/strategies/mod.rs index 115ba8c1e8ffc..ceed0df29f701 100644 --- a/crates/evm/fuzz/src/strategies/mod.rs +++ b/crates/evm/fuzz/src/strategies/mod.rs @@ -5,7 +5,10 @@ mod uint; pub use uint::UintStrategy; mod param; -pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures, mutate_param_value}; +pub use param::{ + fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures, mutate_param_value, + mutate_param_value_with_senders, +}; mod calldata; pub use calldata::{fuzz_calldata, fuzz_calldata_from_state}; diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index aeef938bbc1a2..54bc341d767e7 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -1,6 +1,9 @@ use super::state::EvmFuzzState; -use crate::strategies::mutators::{ - BitMutator, GaussianNoiseMutator, IncrementDecrementMutator, InterestingWordMutator, +use crate::{ + invariant::SenderFilters, + strategies::mutators::{ + BitMutator, GaussianNoiseMutator, IncrementDecrementMutator, InterestingWordMutator, + }, }; use alloy_dyn_abi::{DynSolType, DynSolValue, Word}; use alloy_primitives::{Address, B256, I256, U256}; @@ -280,12 +283,86 @@ pub fn fuzz_param_from_state( } } +/// Selects a random address for mutation, respecting sender filters if provided. +/// +/// Priority: +/// 1. If `senders` has targeted addresses, pick randomly from those +/// 2. Otherwise, pick from the dictionary state values (excluding any in `senders.excluded`) +/// 3. Returns `None` if no suitable address is found or if the selected address equals `current` +fn select_random_address( + current: Address, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, +) -> Option
{ + if let Some(senders) = senders { + if !senders.targeted.is_empty() { + // Pick from targeted senders + let index = test_runner.rng().random_range(0..senders.targeted.len()); + let addr = senders.targeted[index]; + return (addr != current).then_some(addr); + } + + // Pick from dictionary state values, excluding addresses in the exclusion list + let dict = state.dictionary_read(); + let values = dict.values(); + if values.is_empty() { + return None; + } + + // Try a few times to find a non-excluded address + for _ in 0..10 { + let index = test_runner.rng().random_range(0..values.len()); + let addr = Address::from_word(values[index]); + if addr != current && !senders.excluded.contains(&addr) { + return Some(addr); + } + } + None + } else { + // No sender filters, just pick from dictionary state values + let dict = state.dictionary_read(); + let values = dict.values(); + if values.is_empty() { + None + } else { + let index = test_runner.rng().random_range(0..values.len()); + let addr = Address::from_word(values[index]); + (addr != current).then_some(addr) + } + } +} + /// Mutates the current value of the given parameter type and value. pub fn mutate_param_value( param: &DynSolType, value: DynSolValue, test_runner: &mut TestRunner, state: &EvmFuzzState, +) -> DynSolValue { + mutate_param_value_inner(param, value, test_runner, state, None) +} + +/// Mutates the current value of the given parameter type and value, with optional sender filters. +/// +/// When `senders` is provided and has targeted addresses, address mutations will prefer +/// selecting from those targeted addresses (similar to `select_random_sender` behavior). +pub fn mutate_param_value_with_senders( + param: &DynSolType, + value: DynSolValue, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: &SenderFilters, +) -> DynSolValue { + mutate_param_value_inner(param, value, test_runner, state, Some(senders)) +} + +fn mutate_param_value_inner( + param: &DynSolType, + value: DynSolValue, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, ) -> DynSolValue { let new_value = |param: &DynSolType, test_runner: &mut TestRunner| { fuzz_param_from_state(param, state) @@ -322,12 +399,14 @@ pub fn mutate_param_value( _ => unreachable!(), } .map(|v| DynSolValue::Int(v, size)), - DynSolValue::Address(val) => match test_runner.rng().random_range(0..=4) { + DynSolValue::Address(val) => match test_runner.rng().random_range(0..=5) { 0 => Address::flip_random_bit(val, 20, test_runner), 1 => Address::mutate_interesting_byte(val, 20, test_runner), 2 => Address::mutate_interesting_word(val, 20, test_runner), 3 => Address::mutate_interesting_dword(val, 20, test_runner), - 4 => None, + // Replace with a random address from targeted senders or dictionary. + 4 => select_random_address(val, test_runner, state, senders), + 5 => None, _ => unreachable!(), } .map(DynSolValue::Address), @@ -343,7 +422,13 @@ pub fn mutate_param_value( // Increase array size. 1 => values.push(new_value(param_type, test_runner)), // Mutate random array element. - 2 => mutate_random_array_value(&mut values, param_type, test_runner, state), + 2 => mutate_random_array_value( + &mut values, + param_type, + test_runner, + state, + senders, + ), _ => unreachable!(), } Some(DynSolValue::Array(values)) @@ -355,7 +440,7 @@ pub fn mutate_param_value( if let DynSolType::FixedArray(param_type, _size) = param && !values.is_empty() { - mutate_random_array_value(&mut values, param_type, test_runner, state); + mutate_random_array_value(&mut values, param_type, test_runner, state, senders); Some(DynSolValue::FixedArray(values)) } else { None @@ -376,7 +461,7 @@ pub fn mutate_param_value( && !values.is_empty() { // Mutate random struct element. - mutate_random_tuple_value(&mut values, tuple_types, test_runner, state); + mutate_random_tuple_value(&mut values, tuple_types, test_runner, state, senders); Some(DynSolValue::CustomStruct { name, prop_names, tuple: values }) } else { None @@ -387,7 +472,7 @@ pub fn mutate_param_value( && !values.is_empty() { // Mutate random tuple element. - mutate_random_tuple_value(&mut values, tuple_types, test_runner, state); + mutate_random_tuple_value(&mut values, tuple_types, test_runner, state, senders); Some(DynSolValue::Tuple(values)) } else { None @@ -404,11 +489,12 @@ fn mutate_random_tuple_value( tuple_types: &[DynSolType], test_runner: &mut TestRunner, state: &EvmFuzzState, + senders: Option<&SenderFilters>, ) { let id = test_runner.rng().random_range(0..tuple_values.len()); let param_type = &tuple_types[id]; let old_val = replace(&mut tuple_values[id], DynSolValue::Bool(false)); - let new_val = mutate_param_value(param_type, old_val, test_runner, state); + let new_val = mutate_param_value_inner(param_type, old_val, test_runner, state, senders); tuple_values[id] = new_val; } @@ -418,10 +504,11 @@ fn mutate_random_array_value( element_type: &DynSolType, test_runner: &mut TestRunner, state: &EvmFuzzState, + senders: Option<&SenderFilters>, ) { let elem = array_values.choose_mut(&mut test_runner.rng()).unwrap(); let old_val = replace(elem, DynSolValue::Bool(false)); - let new_val = mutate_param_value(element_type, old_val, test_runner, state); + let new_val = mutate_param_value_inner(element_type, old_val, test_runner, state, senders); *elem = new_val; } @@ -506,4 +593,151 @@ mod tests { assert!(generated_hashes.contains(&keccak256("hello"))); assert!(generated_hashes.contains(&keccak256("world"))); } + + #[test] + fn mutate_address_can_select_from_dictionary() { + use super::mutate_param_value; + use alloy_dyn_abi::{DynSolType, DynSolValue}; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary via state values. + let addr1 = Address::repeat_byte(0x11); + let addr2 = Address::repeat_byte(0x22); + let addr3 = Address::repeat_byte(0x33); + state.collect_values([addr1.into_word(), addr2.into_word(), addr3.into_word()]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Mutate an address many times and verify we can get addresses from the dictionary. + let original = Address::repeat_byte(0xff); + let mut got_addr1 = false; + let mut got_addr2 = false; + let mut got_addr3 = false; + + for _ in 0..1000 { + let mutated = mutate_param_value( + &DynSolType::Address, + DynSolValue::Address(original), + &mut runner, + &state, + ); + if let DynSolValue::Address(addr) = mutated { + if addr == addr1 { + got_addr1 = true; + } + if addr == addr2 { + got_addr2 = true; + } + if addr == addr3 { + got_addr3 = true; + } + } + if got_addr1 && got_addr2 && got_addr3 { + break; + } + } + + // We should have seen at least one dictionary address in 1000 iterations. + assert!( + got_addr1 || got_addr2 || got_addr3, + "Address mutation should select addresses from dictionary" + ); + } + + #[test] + fn mutate_address_prefers_targeted_senders() { + use super::select_random_address; + use crate::invariant::SenderFilters; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary (these should NOT be selected when targeted is set). + let dict_addr = Address::repeat_byte(0xdd); + state.collect_values([dict_addr.into_word()]); + + // Set up targeted senders. + let targeted1 = Address::repeat_byte(0x11); + let targeted2 = Address::repeat_byte(0x22); + let senders = SenderFilters::new(vec![targeted1, targeted2], vec![]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Call select_random_address directly to verify it uses targeted senders. + let original = Address::repeat_byte(0xff); + let mut got_targeted1 = false; + let mut got_targeted2 = false; + let mut got_dict = false; + + for _ in 0..100 { + if let Some(addr) = select_random_address(original, &mut runner, &state, Some(&senders)) + { + if addr == targeted1 { + got_targeted1 = true; + } + if addr == targeted2 { + got_targeted2 = true; + } + if addr == dict_addr { + got_dict = true; + } + } + } + + // Should see targeted addresses, never dictionary address. + assert!( + got_targeted1 || got_targeted2, + "select_random_address should select from targeted senders" + ); + assert!( + !got_dict, + "select_random_address should not select from dictionary when targeted senders are set" + ); + } + + #[test] + fn mutate_address_respects_excluded_senders() { + use super::select_random_address; + use crate::invariant::SenderFilters; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary. + let addr1 = Address::repeat_byte(0x11); + let addr2 = Address::repeat_byte(0x22); + let excluded_addr = Address::repeat_byte(0xee); + state.collect_values([addr1.into_word(), addr2.into_word(), excluded_addr.into_word()]); + + // Exclude one address. + let senders = SenderFilters::new(vec![], vec![excluded_addr]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Call select_random_address directly to verify it respects excluded senders. + let original = Address::repeat_byte(0xff); + let mut got_excluded = false; + let mut got_valid = false; + + for _ in 0..100 { + if let Some(addr) = select_random_address(original, &mut runner, &state, Some(&senders)) + { + if addr == excluded_addr { + got_excluded = true; + break; + } + if addr == addr1 || addr == addr2 { + got_valid = true; + } + } + } + + assert!(!got_excluded, "select_random_address should not select excluded addresses"); + assert!(got_valid, "select_random_address should select valid (non-excluded) addresses"); + } } diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 4eed2fcf4f10b..0d31cfca6a7ae 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -47,6 +47,3 @@ tempfile.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true yansi.workspace = true - -[dev-dependencies] -tempfile.workspace = true diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 38f40b244c042..f312f2a4def1b 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -323,7 +323,7 @@ impl CallTraceDecoder { self.contracts.entry(address).or_insert(contract); } - if let Some(label) = label { + if let Some(label) = label.filter(|s| !s.is_empty()) { self.labels.entry(address).or_insert(label); } diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs index 80ac8a8dcb620..2b4e58b8665a7 100644 --- a/crates/evm/traces/src/folded_stack_trace.rs +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -5,20 +5,26 @@ use revm_inspectors::tracing::{ }; /// Builds a folded stack trace from a call trace arena. -pub fn build(arena: &CallTraceArena) -> Vec { - let mut fst = EvmFoldedStackTraceBuilder::default(); +pub fn build(arena: &CallTraceArena, isolate: bool) -> Vec { + let mut fst = EvmFoldedStackTraceBuilder::new(isolate); fst.process_call_node(arena.nodes(), 0); fst.build() } /// Wrapper for building a folded stack trace using EVM call trace node. -#[derive(Default)] pub struct EvmFoldedStackTraceBuilder { + /// Trace produced in isolate mode, meaning refund needs to be reversed at the depth=1 + /// frame for consistent gas values. + isolate: bool, /// Raw folded stack trace builder. fst: FoldedStackTraceBuilder, } impl EvmFoldedStackTraceBuilder { + pub fn new(isolate: bool) -> Self { + Self { isolate, fst: FoldedStackTraceBuilder::default() } + } + /// Returns the folded stack trace. pub fn build(self) -> Vec { self.fst.build() @@ -57,7 +63,13 @@ impl EvmFoldedStackTraceBuilder { } }; - self.fst.enter(func_name, node.trace.gas_used as i64); + let mut gas_used = node.trace.gas_used; + let max_refund_adjust_depth = if self.isolate { 1 } else { 0 }; + if node.trace.depth <= max_refund_adjust_depth { + gas_used += node.trace.gas_refund_counter; + } + + self.fst.enter(func_name, gas_used); // Track internal function step exits to do in this call context. let mut step_exits = vec![]; @@ -99,8 +111,8 @@ impl EvmFoldedStackTraceBuilder { if let Some(decoded_step) = &step.decoded { match decoded_step.as_ref() { DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { - let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); - self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64); + let gas_used = step.gas_remaining - steps[*step_end_idx].gas_remaining; + self.fst.enter(decoded_internal_call.func_name.clone(), gas_used); step_exits.push(*step_end_idx); } DecodedTraceStep::Line(_) => {} @@ -158,13 +170,13 @@ pub struct FoldedStackTraceBuilder { struct TraceEntry { /// Names of all functions in the call stack of this trace. names: Vec, - /// Gas consumed by this function, allowed to be negative due to refunds. - gas: i64, + /// Gas consumed by this function, not including refunds. + gas: u64, } impl FoldedStackTraceBuilder { /// Enter execution of a function call that consumes `gas`. - pub fn enter(&mut self, label: String, gas: i64) { + pub fn enter(&mut self, label: String, gas: u64) { let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default(); while self.exits > 0 { @@ -189,7 +201,7 @@ impl FoldedStackTraceBuilder { /// Internal method to build the folded stack trace without subtracting gas consumed by /// the children function calls. - fn build_without_subtraction(&mut self) -> Vec { + pub fn build_without_subtraction(&mut self) -> Vec { let mut lines = Vec::new(); for TraceEntry { names, gas } in &self.traces { lines.push(format!("{} {}", names.join(";"), gas)); diff --git a/crates/evm/traces/src/identifier/external.rs b/crates/evm/traces/src/identifier/external.rs index b489882a13d39..6702287200680 100644 --- a/crates/evm/traces/src/identifier/external.rs +++ b/crates/evm/traces/src/identifier/external.rs @@ -2,7 +2,7 @@ use super::{IdentifiedAddress, TraceIdentifier}; use crate::debug::ContractSources; use alloy_primitives::{ Address, - map::{Entry, HashMap}, + map::{Entry, HashMap, HashSet}, }; use eyre::WrapErr; use foundry_block_explorers::{contract::Metadata, errors::EtherscanError}; @@ -61,7 +61,14 @@ impl ExternalIdentifier { } if let Some(config) = config { debug!(target: "evm::traces::external", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); - fetchers.push(Arc::new(EtherscanFetcher::new(config.into_client()?))); + match config.into_client() { + Ok(client) => { + fetchers.push(Arc::new(EtherscanFetcher::new(client))); + } + Err(err) => { + warn!(target: "evm::traces::external", ?err, "failed to create etherscan client"); + } + } } if fetchers.is_empty() { debug!(target: "evm::traces::external", "no fetchers enabled"); @@ -151,7 +158,7 @@ impl TraceIdentifier for ExternalIdentifier { trace!(target: "evm::traces::external", "identify {} addresses", nodes.len()); let mut identities = Vec::new(); - let mut to_fetch = Vec::new(); + let mut to_fetch = HashSet::new(); // Check cache first. for &node in nodes { @@ -163,7 +170,7 @@ impl TraceIdentifier for ExternalIdentifier { // Do nothing. We know that this contract was not verified. } } else { - to_fetch.push(address); + to_fetch.insert(address); } } @@ -172,6 +179,7 @@ impl TraceIdentifier for ExternalIdentifier { } trace!(target: "evm::traces::external", "fetching {} addresses", to_fetch.len()); + let to_fetch = to_fetch.into_iter().collect::>(); let fetchers = self.fetchers.iter().map(|fetcher| ExternalFetcher::new(fetcher.clone(), &to_fetch)); let fetched_identities = foundry_common::block_on( @@ -408,8 +416,6 @@ impl ExternalFetcherT for SourcifyFetcher { let url = format!("{url}/{address}?fields=abi,compilation", url = self.url); let response = self.client.get(url).send().await?; let code = response.status(); - let response: SourcifyResponse = response.json().await?; - trace!(target: "evm::traces::external", "Sourcify response for {address}: {response:#?}"); match code.as_u16() { // Not verified. 404 => return Err(EtherscanError::ContractCodeNotVerified(address)), @@ -417,6 +423,8 @@ impl ExternalFetcherT for SourcifyFetcher { 429 => return Err(EtherscanError::RateLimitExceeded), _ => {} } + let response: SourcifyResponse = response.json().await?; + trace!(target: "evm::traces::external", "Sourcify response for {address}: {response:#?}"); match response { SourcifyResponse::Success(metadata) => Ok(Some(metadata.into())), SourcifyResponse::Error(error) => Err(EtherscanError::Unknown(format!("{error:#?}"))), diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index 57884af234e6b..bad5c577bc69e 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -24,6 +24,5 @@ similar = { version = "2", features = ["inline"] } [dev-dependencies] foundry-test-utils.workspace = true -itertools.workspace = true toml.workspace = true snapbox.workspace = true diff --git a/crates/fmt/README.md b/crates/fmt/README.md index 65f05d6c82847..2a151009b1019 100644 --- a/crates/fmt/README.md +++ b/crates/fmt/README.md @@ -127,6 +127,7 @@ The formatter supports multiple configuration options defined in `foundry.toml`. | `ignore` | `[]` | Globs to ignore. | | `contract_new_lines` | `false` | Add a new line at the start and end of contract declarations. | | `sort_imports` | `false` | Sort import statements alphabetically in groups. A group is a set of imports separated by a newline. | +| `namespace_import_style` | `prefer_plain` | Style for namespace imports. Options: `prefer_plain` (`import "foo" as foo;`), `prefer_glob` (`import * as foo from "foo";`), `preserve`. | | `pow_no_space` | `false` | Suppress spaces around the power operator (`**`). | | `single_line_imports` | `false` | Keep single imports on a single line, even if they exceed the line length limit. | diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 762ecdb4ce645..e98eac6f4e5c1 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -38,15 +38,18 @@ pub(super) struct CallContext { /// The size of the callee's head, excluding its arguments. pub(super) size: usize, + + /// Whether this chain context added its own indentation box. + pub(super) has_indent: bool, } impl CallContext { pub(super) fn nested(size: usize) -> Self { - Self { kind: CallContextKind::Nested, size } + Self { kind: CallContextKind::Nested, size, has_indent: false } } - pub(super) fn chained(size: usize) -> Self { - Self { kind: CallContextKind::Chained, size } + pub(super) fn chained(size: usize, has_indent: bool) -> Self { + Self { kind: CallContextKind::Chained, size, has_indent } } pub(super) fn is_nested(&self) -> bool { @@ -83,8 +86,10 @@ impl CallStack { self.last().is_some_and(|call| call.is_nested()) } - pub(crate) fn is_chain(&self) -> bool { - self.last().is_some_and(|call| call.is_chained()) + /// Returns true if any chained call in the stack has its own indentation. + /// Used to determine if commasep should skip its own indentation (to avoid double indent). + pub(crate) fn has_chain_with_indent(&self) -> bool { + self.stack.iter().any(|call| call.is_chained() && call.has_indent) } } diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index a8c9bd6647de1..f46455b2a6b5d 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -205,16 +205,32 @@ impl<'ast> State<'_, 'ast> { fn print_import(&mut self, import: &'ast ast::ImportDirective<'ast>) { let ast::ImportDirective { path, items } = import; self.word("import "); - match items { - ast::ImportItems::Plain(_) | ast::ImportItems::Glob(_) => { + + use ast::ImportItems; + use config::NamespaceImportStyle as NIStyle; + + match (items, self.config.namespace_import_style) { + (ImportItems::Plain(None), _) => { self.print_ast_str_lit(path); - if let Some(ident) = items.source_alias() { - self.word(" as "); - self.print_ident(&ident); - } } - ast::ImportItems::Aliases(aliases) => { + (ImportItems::Plain(Some(source_alias)), NIStyle::Preserve | NIStyle::PreferPlain) + | (ImportItems::Glob(source_alias), NIStyle::PreferPlain) => { + self.print_ast_str_lit(path); + self.word(" as "); + self.print_ident(source_alias); + } + + (ImportItems::Glob(source_alias), NIStyle::Preserve | NIStyle::PreferGlob) + | (ImportItems::Plain(Some(source_alias)), NIStyle::PreferGlob) => { + self.word("*"); + self.word(" as "); + self.print_ident(source_alias); + self.word(" from "); + self.print_ast_str_lit(path); + } + + (ImportItems::Aliases(aliases), _) => { // Check if we should keep single imports on one line let use_single_line = self.config.single_line_imports && aliases.len() == 1; @@ -391,7 +407,9 @@ impl<'ast> State<'_, 'ast> { self.block_depth -= 1; } else { if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() { - self.zerobreak(); + // Adjust the offset of the trailing break from comment printing + // so the closing brace is not indented + self.s.offset(-self.ind); } else if self.config.bracket_spacing { self.nbsp(); }; @@ -1335,6 +1353,11 @@ impl<'ast> State<'_, 'ast> { match member_expr.kind { ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) => (), ast::ExprKind::Index(..) if s.skip_index_break => (), + // Don't add break when accessing a field after a call with named args. + // e.g., `_lzSend({_dstEid: x, ...}).guid` should keep `.guid` + // on the same line as the closing `})`. + // See: https://github.com/foundry-rs/foundry/issues/12399 + _ if is_call_with_named_args(&member_expr.kind) => (), _ => s.zerobreak(), } s.word("."); @@ -1667,11 +1690,6 @@ impl<'ast> State<'_, 'ast> { let callee_size = get_callee_head_size(child_expr) + member_or_args.member_size(); let expr_size = self.estimate_size(child_expr.span); - // Start a new chain if needed - if is_call_chain(&child_expr.kind, false) { - self.call_stack.push(CallContext::chained(callee_size)); - } - let callee_fits_line = self.space_left() > callee_size + 1; let total_fits_line = self.space_left() > expr_size + member_or_args.size() + 2; let no_cmnt_or_mixed = @@ -1683,13 +1701,20 @@ impl<'ast> State<'_, 'ast> { extra_box = true; } - if !is_call_chain(&child_expr.kind, true) - && (no_cmnt_or_mixed || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..))) - && callee_fits_line - && (member_depth(0, child_expr) < 2 - // calls with cmnts between the args always break - || (total_fits_line && !member_or_args.has_comments())) - { + // Determine if this chain will add its own indentation + let chain_has_indent = is_call_chain(&child_expr.kind, true) + || !(no_cmnt_or_mixed + || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..))) + || !callee_fits_line + || (member_depth(0, child_expr) >= 2 + && (!total_fits_line || member_or_args.has_comments())); + + // Start a new chain if needed + if is_call_chain(&child_expr.kind, false) { + self.call_stack.push(CallContext::chained(callee_size, chain_has_indent)); + } + + if !chain_has_indent { self.skip_index_break = true; self.cbox(0); } else { @@ -1796,7 +1821,7 @@ impl<'ast> State<'_, 'ast> { list_format .break_cmnts() .break_single(true) - .without_ind(self.call_stack.is_chain()) + .without_ind(self.call_stack.has_chain_with_indent()) .with_delimiters(!self.call_with_opts_and_args), ); } else if self.config.bracket_spacing { @@ -2408,20 +2433,19 @@ impl<'ast> State<'_, 'ast> { // If possible, take an early decision based on the block style configuration. match self.config.single_line_statement_blocks { - config::SingleLineBlockStyle::Preserve => { - if self.is_stmt_in_new_line(cond, then) || self.is_multiline_block_stmt(then, true) - { - return Decision { outcome: false, is_cached: false }; - } + config::SingleLineBlockStyle::Preserve + if self.is_stmt_in_new_line(cond, then) + || self.is_multiline_block_stmt(then, true) => + { + return Decision { outcome: false, is_cached: false }; } - config::SingleLineBlockStyle::Single => { - if self.is_multiline_block_stmt(then, true) { - return Decision { outcome: false, is_cached: false }; - } + config::SingleLineBlockStyle::Single if self.is_multiline_block_stmt(then, true) => { + return Decision { outcome: false, is_cached: false }; } config::SingleLineBlockStyle::Multi => { return Decision { outcome: false, is_cached: false }; } + _ => {} }; // If no decision was made, estimate the length to be formatted. @@ -2505,11 +2529,17 @@ impl<'ast> State<'_, 'ast> { false } - /// Checks if a block statement `{ ... }` contains more than one line of actual code. + /// Checks if a block statement `{ ... }` should be treated as multiline, + /// either because it spans multiple lines or contains multiple statements. fn is_multiline_block(&self, block: &'ast ast::Block<'ast>, empty_as_multiline: bool) -> bool { if block.stmts.is_empty() { return empty_as_multiline; } + // A block with multiple statements should never be inlined, regardless of + // whether it was written on a single line in the source. + if block.stmts.len() > 1 { + return true; + } if self.sm.is_multiline(block.span) && let Ok(snip) = self.sm.span_to_snippet(block.span) { @@ -2873,6 +2903,18 @@ fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool { matches!(expr_kind, ast::ExprKind::Call(..)) } +/// Returns true if this is a call with named arguments (struct-style syntax). +/// Used to determine if `.field` after such a call should avoid breaking. +/// E.g., `_lzSend({_dstEid: x, ...}).guid` → true (named args call) +/// E.g., `someFunc(a, b).field` → false (positional args) +fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool { + if let ast::ExprKind::Call(_, args) = expr_kind { + matches!(args.kind, ast::CallArgsKind::Named(_)) + } else { + false + } +} + fn is_call_chain(expr_kind: &ast::ExprKind<'_>, must_have_child: bool) -> bool { if let ast::ExprKind::Member(child, ..) = expr_kind { is_call_chain(&child.kind, false) diff --git a/crates/fmt/testdata/CommentEmptyLine/fmt.sol b/crates/fmt/testdata/CommentEmptyLine/fmt.sol new file mode 100644 index 0000000000000..a2407017f4be9 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/fmt.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} diff --git a/crates/fmt/testdata/CommentEmptyLine/original.sol b/crates/fmt/testdata/CommentEmptyLine/original.sol new file mode 100644 index 0000000000000..9c7f35770e9f3 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/original.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol new file mode 100644 index 0000000000000..e930ceee0754d --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "prefer_glob" +import "SomeFile.sol"; +import "SomeFile.sol"; +import * as SomeOtherFile from "SomeFile.sol"; +import * as SomeOtherFile from "SomeFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol new file mode 100644 index 0000000000000..6ea4cabeeb1ec --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "prefer_plain" +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol new file mode 100644 index 0000000000000..607817b9eadfb --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "preserve" +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/Repros/fmt.sol b/crates/fmt/testdata/Repros/fmt.sol index 3ccbaab706ea1..771c86e6a641e 100644 --- a/crates/fmt/testdata/Repros/fmt.sol +++ b/crates/fmt/testdata/Repros/fmt.sol @@ -393,3 +393,37 @@ contract WETHHook is BaseTokenWrapperHook { weth = WETH(payable(_weth)); } } + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/original.sol b/crates/fmt/testdata/Repros/original.sol index f9ae36eb3f2fe..0a37ed6f9807d 100644 --- a/crates/fmt/testdata/Repros/original.sol +++ b/crates/fmt/testdata/Repros/original.sol @@ -405,3 +405,22 @@ contract WETHHook is BaseTokenWrapperHook { weth = WETH(payable(_weth)); } } + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { bytes32 sender; address receiver; address token; uint256 amount; } + function pack(TransferMessage memory m) internal pure returns (bytes memory) { return ""; } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, receiver: someAddressValue, token: someAddressValue, amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/sorted.fmt.sol b/crates/fmt/testdata/Repros/sorted.fmt.sol index 4a155b28c8b42..0d56601d6c098 100644 --- a/crates/fmt/testdata/Repros/sorted.fmt.sol +++ b/crates/fmt/testdata/Repros/sorted.fmt.sol @@ -394,3 +394,37 @@ contract WETHHook is BaseTokenWrapperHook { weth = WETH(payable(_weth)); } } + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/tab.fmt.sol b/crates/fmt/testdata/Repros/tab.fmt.sol index e398157b75ae6..b423e45507513 100644 --- a/crates/fmt/testdata/Repros/tab.fmt.sol +++ b/crates/fmt/testdata/Repros/tab.fmt.sol @@ -394,3 +394,37 @@ contract WETHHook is BaseTokenWrapperHook { weth = WETH(payable(_weth)); } } + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/StructFieldAccess/fmt.sol b/crates/fmt/testdata/StructFieldAccess/fmt.sol new file mode 100644 index 0000000000000..22b16bde3f77c --- /dev/null +++ b/crates/fmt/testdata/StructFieldAccess/fmt.sol @@ -0,0 +1,46 @@ +// config: line_length = 120 +// https://github.com/foundry-rs/foundry/issues/12399 +contract StructFieldAccess { + function a() external { + bytes32 guid = + _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).guid; + } + + function b() external view returns (uint256) { + return _quote({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _payInLzToken: false + }).nativeFee; + } + + // Simple cases + function c() external { + uint256 val = getData().value; + bool flag = getStruct({param: 1}).isActive; + } + + // Nested struct field access + function d() external { + uint256 nested = getOuter().inner.value; + } + + // Chained calls with named args + function e() external { + bytes32 guid = + _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).wrap({wrapper: wrapperAddress, extraData: bytes("")}).guid; + } +} diff --git a/crates/fmt/testdata/StructFieldAccess/original.sol b/crates/fmt/testdata/StructFieldAccess/original.sol new file mode 100644 index 0000000000000..7671273c028be --- /dev/null +++ b/crates/fmt/testdata/StructFieldAccess/original.sol @@ -0,0 +1,43 @@ +// https://github.com/foundry-rs/foundry/issues/12399 +contract StructFieldAccess { + function a() external { + bytes32 guid = _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).guid; + } + + function b() external view returns (uint256) { + return _quote({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _payInLzToken: false + }).nativeFee; + } + + // Simple cases + function c() external { + uint256 val = getData().value; + bool flag = getStruct({param: 1}).isActive; + } + + // Nested struct field access + function d() external { + uint256 nested = getOuter().inner.value; + } + + // Chained calls with named args + function e() external { + bytes32 guid = _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).wrap({wrapper: wrapperAddress, extraData: bytes("")}).guid; + } +} diff --git a/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol b/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol index 1f3b3b7e8302e..5fddf215b8980 100644 --- a/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol +++ b/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol @@ -53,6 +53,11 @@ contract WhileStatement { doIt(); } + while (condition) { + doIt(); + doIt(); + } + while (condition) { doIt(); } diff --git a/crates/fmt/testdata/WhileStatement/block-single.fmt.sol b/crates/fmt/testdata/WhileStatement/block-single.fmt.sol index f4f695a398868..58f2c08463e3e 100644 --- a/crates/fmt/testdata/WhileStatement/block-single.fmt.sol +++ b/crates/fmt/testdata/WhileStatement/block-single.fmt.sol @@ -33,6 +33,11 @@ contract WhileStatement { while (condition) doIt(); + while (condition) { + doIt(); + doIt(); + } + while (condition) doIt(); while ( // comment1 diff --git a/crates/fmt/testdata/WhileStatement/fmt.sol b/crates/fmt/testdata/WhileStatement/fmt.sol index 57a882aee102e..7e366786f0816 100644 --- a/crates/fmt/testdata/WhileStatement/fmt.sol +++ b/crates/fmt/testdata/WhileStatement/fmt.sol @@ -40,6 +40,11 @@ contract WhileStatement { while (condition) doIt(); + while (condition) { + doIt(); + doIt(); + } + while (condition) doIt(); while ( // comment1 diff --git a/crates/fmt/testdata/WhileStatement/original.sol b/crates/fmt/testdata/WhileStatement/original.sol index 8b245b0cfa0f8..12fe15fb600a4 100644 --- a/crates/fmt/testdata/WhileStatement/original.sol +++ b/crates/fmt/testdata/WhileStatement/original.sol @@ -36,6 +36,8 @@ contract WhileStatement { while(condition) { doIt(); } + while(condition) { doIt(); doIt(); } + while (condition) doIt(); diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 953dc6b48dbba..f337b5b3657fa 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -169,6 +169,7 @@ fmt_tests! { ArrayExpressions, BlockComments, BlockCommentsFunction, + CommentEmptyLine, ConditionalOperatorExpression, ConstructorDefinition, ConstructorModifierStyle, @@ -210,6 +211,7 @@ fmt_tests! { SortedImports, StatementBlock, StructDefinition, + StructFieldAccess, ThisExpression, #[ignore = "Solar errors when parsing inputs with trailing commas"] TrailingComma, @@ -223,3 +225,28 @@ fmt_tests! { Yul, YulStrings, } + +#[test] +fn test_comment_empty_line_bug() { + init_tracing(); + let source = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} +"#; + + let expected = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} +"#; + + let fmt_config = Arc::new(FormatterConfig::default()); + let path = Path::new("test.sol"); + let formatted = format(source, path, fmt_config); + + assert_eq!(formatted, expected, "Formatting mismatch"); +} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 8169750642c6c..3be249dd24b1a 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -35,6 +35,7 @@ foundry-linking.workspace = true comfy-table.workspace = true eyre.workspace = true proptest.workspace = true +rand.workspace = true rayon.workspace = true serde.workspace = true tracing.workspace = true @@ -98,6 +99,7 @@ opener = "0.8" # soldeer soldeer-commands.workspace = true +soldeer-core.workspace = true quick-junit = "0.5.2" [dev-dependencies] @@ -105,7 +107,6 @@ alloy-hardforks.workspace = true anvil.workspace = true forge-script-sequence.workspace = true foundry-test-utils.workspace = true -reqwest = { workspace = true, features = ["json"] } mockall = "0.14" globset = "0.4" diff --git a/crates/forge/assets/solidity/workflowTemplate.yml b/crates/forge/assets/solidity/workflowTemplate.yml index 03824c4abd03c..8aeae90e03e63 100644 --- a/crates/forge/assets/solidity/workflowTemplate.yml +++ b/crates/forge/assets/solidity/workflowTemplate.yml @@ -7,9 +7,6 @@ on: pull_request: workflow_dispatch: -env: - FOUNDRY_PROFILE: ci - jobs: check: name: Foundry project diff --git a/crates/forge/assets/tempo/workflowTemplate.yml b/crates/forge/assets/tempo/workflowTemplate.yml index e1e368ed6406a..f2294304b402a 100644 --- a/crates/forge/assets/tempo/workflowTemplate.yml +++ b/crates/forge/assets/tempo/workflowTemplate.yml @@ -7,9 +7,6 @@ on: pull_request: workflow_dispatch: -env: - FOUNDRY_PROFILE: ci - jobs: check: name: Tempo Foundry project diff --git a/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml index a189658705d85..7a5fc54a1b084 100644 --- a/crates/forge/assets/vyper/workflowTemplate.yml +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -7,9 +7,6 @@ on: pull_request: workflow_dispatch: -env: - FOUNDRY_PROFILE: ci - jobs: check: name: Foundry project diff --git a/crates/forge/src/args.rs b/crates/forge/src/args.rs index aada974c91b9a..4f55f028a3690 100644 --- a/crates/forge/src/args.rs +++ b/crates/forge/src/args.rs @@ -13,6 +13,8 @@ use foundry_evm::inspectors::cheatcodes::{ForgeContext, set_execution_context}; pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Forge::parse(); args.global.init()?; diff --git a/crates/forge/src/cmd/bind_json.rs b/crates/forge/src/cmd/bind_json.rs index fec5133ac8718..f03fc64f467f7 100644 --- a/crates/forge/src/cmd/bind_json.rs +++ b/crates/forge/src/cmd/bind_json.rs @@ -285,7 +285,7 @@ impl BindJsonArgs { } } - for (s, fn_name) in structs_to_write.iter_mut().zip(fn_names.into_iter()) { + for (s, fn_name) in structs_to_write.iter_mut().zip(fn_names) { s.name_in_fns = fn_name.unwrap_or(s.name.clone()); } } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index a98fdc8d7eeb0..51c257a52965e 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -4,7 +4,7 @@ use eyre::{Context, Result}; use forge_lint::{linter::Linter, sol::SolidityLinter}; use foundry_cli::{ opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, - utils::{LoadConfig, cache_local_signatures}, + utils::{Git, LoadConfig, cache_local_signatures}, }; use foundry_common::{compile::ProjectCompiler, shell}; use foundry_compilers::{ @@ -80,6 +80,9 @@ impl BuildArgs { config = self.load_config()?; } + self.check_soldeer_lock_consistency(&config).await; + self.check_foundry_lock_consistency(&config); + let project = config.project()?; // Collect sources to compile if build subdirectories specified. @@ -178,9 +181,7 @@ impl BuildArgs { get_solar_sources_from_compile_output(config, output, Some(&input_files), None)?; if solar_sources.input.sources.is_empty() { if !input_files.is_empty() { - sh_warn!( - "unable to lint. Solar only supports Solidity versions prior to 0.8.0" - )?; + sh_warn!("unable to lint. Solar only supports Solidity versions >=0.8.0")?; } return Ok(()); } @@ -228,6 +229,97 @@ impl BuildArgs { Ok([config.src, config.test, config.script, foundry_toml]) }) } + + /// Check soldeer.lock file consistency using soldeer_core APIs + async fn check_soldeer_lock_consistency(&self, config: &Config) { + let soldeer_lock_path = config.root.join("soldeer.lock"); + if !soldeer_lock_path.exists() { + return; + } + + // Note: read_lockfile returns Ok with empty entries for malformed files + let Ok(lockfile) = soldeer_core::lock::read_lockfile(&soldeer_lock_path) else { + return; + }; + + let deps_dir = config.root.join("dependencies"); + for entry in &lockfile.entries { + let dep_name = entry.name(); + + // Use soldeer_core's integrity check + match soldeer_core::install::check_dependency_integrity(entry, &deps_dir).await { + Ok(status) => { + use soldeer_core::install::DependencyStatus; + // Check if status indicates a problem + if matches!( + status, + DependencyStatus::Missing | DependencyStatus::FailedIntegrity + ) { + sh_warn!("Dependency '{}' integrity check failed: {:?}", dep_name, status) + .ok(); + } + } + Err(e) => { + sh_warn!("Dependency '{}' integrity check error: {}", dep_name, e).ok(); + } + } + } + } + + /// Check foundry.lock file consistency with git submodules + fn check_foundry_lock_consistency(&self, config: &Config) { + use crate::lockfile::{DepIdentifier, FOUNDRY_LOCK, Lockfile}; + + let foundry_lock_path = config.root.join(FOUNDRY_LOCK); + if !foundry_lock_path.exists() { + return; + } + + let git = Git::new(&config.root); + + let mut lockfile = Lockfile::new(&config.root).with_git(&git); + if let Err(e) = lockfile.read() { + if !e.to_string().contains("Lockfile not found") { + sh_warn!("Failed to parse foundry.lock: {}", e).ok(); + } + return; + } + + for (dep_path, dep_identifier) in lockfile.iter() { + let full_path = config.root.join(dep_path); + + if !full_path.exists() { + sh_warn!("Dependency '{}' not found at expected path", dep_path.display()).ok(); + continue; + } + + let actual_rev = match git.get_rev("HEAD", &full_path) { + Ok(rev) => rev, + Err(_) => { + sh_warn!("Failed to get git revision for dependency '{}'", dep_path.display()) + .ok(); + continue; + } + }; + + // Compare with the expected revision from lockfile + let expected_rev = match dep_identifier { + DepIdentifier::Branch { rev, .. } + | DepIdentifier::Tag { rev, .. } + | DepIdentifier::Rev { rev, .. } => rev.clone(), + }; + + if actual_rev != expected_rev { + sh_warn!( + "Dependency '{}' revision mismatch: expected '{}', found '{}'", + dep_path.display(), + expected_rev, + actual_rev + ) + .ok(); + } + } + } } // Make this args a `figment::Provider` so that it can be merged into the `Config` diff --git a/crates/forge/src/cmd/clone.rs b/crates/forge/src/cmd/clone.rs index 97337907ac201..c8f5680f16793 100644 --- a/crates/forge/src/cmd/clone.rs +++ b/crates/forge/src/cmd/clone.rs @@ -1090,7 +1090,8 @@ mod tests { /// Run the clone command with the specified contract address and assert the compilation. async fn one_test_case(address: Address, check_compilation_result: bool) { - let mut project_root = tempfile::tempdir().unwrap().path().to_path_buf(); + let temp_dir = tempfile::tempdir().unwrap(); + let mut project_root = temp_dir.path().to_path_buf(); let client = mock_etherscan(address); let meta = CloneArgs::collect_metadata_from_client(address, &client).await.unwrap(); CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()) @@ -1115,7 +1116,6 @@ mod tests { pick_creation_info(&address.to_string()).expect("creation code not found"); assert_compilation_result(rv, contract_name, stripped_creation_code); } - std::fs::remove_dir_all(project_root).unwrap(); } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/forge/src/cmd/compiler.rs b/crates/forge/src/cmd/compiler.rs index a939d8823308b..39d38b8b249ba 100644 --- a/crates/forge/src/cmd/compiler.rs +++ b/crates/forge/src/cmd/compiler.rs @@ -72,8 +72,8 @@ impl ResolveArgs { .iter() .map(|(version, sources, _)| { let paths: Vec = sources - .iter() - .filter_map(|(path_file, _)| { + .keys() + .filter_map(|path_file| { let path_str = path_file .strip_prefix(&project.paths.root) .unwrap_or(path_file) diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index 9ab9174a630ba..2e3ac14f70de7 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -137,13 +137,14 @@ impl CoverageArgs { which can result in inaccurate source mappings.\n\ Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n\ Note that `viaIR` is production ready since Solidity 0.8.13 and above.\n\ - See more: https://github.com/foundry-rs/foundry/issues/3357" + See more: https://book.getfoundry.sh/guides/best-practices/stack-too-deep" )?; } else { sh_warn!( "optimizer settings and `viaIR` have been disabled for accurate coverage reports.\n\ If you encounter \"stack too deep\" errors, consider using `--ir-minimum` which \ - enables `viaIR` with minimum optimization resolving most of the errors" + enables `viaIR` with minimum optimization resolving most of the errors.\n\ + See more: https://book.getfoundry.sh/guides/best-practices/stack-too-deep" )?; } @@ -254,7 +255,7 @@ impl CoverageArgs { let known_contracts = outcome.runner.as_ref().unwrap().known_contracts.clone(); // Add hit data to the coverage report - let data = outcome.results.iter().flat_map(|(_, suite)| { + let data = outcome.results.values().flat_map(|suite| { let mut hits = Vec::new(); for result in suite.test_results.values() { let Some(hit_maps) = result.line_coverage.as_ref() else { continue }; diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 0187020721af1..625d93974f248 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -316,6 +316,15 @@ impl CreateArgs { deployer.tx.set_value(value); } + // set access list if specified + if let Some(access_list) = match self.tx.access_list { + None => None, + Some(None) => Some(provider.create_access_list(&deployer.tx).await?.access_list), + Some(Some(ref access_list)) => Some(access_list.clone()), + } { + deployer.tx.set_access_list(access_list); + } + deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { Ok(gas_limit.to()) } else { @@ -455,6 +464,14 @@ impl CreateArgs { constructor: &Constructor, constructor_args: &[String], ) -> Result> { + if constructor.inputs.len() != constructor_args.len() { + eyre::bail!( + "Constructor argument count mismatch: expected {} but got {}", + constructor.inputs.len(), + constructor_args.len() + ); + } + let mut params = Vec::with_capacity(constructor.inputs.len()); for (input, arg) in constructor.inputs.iter().zip(constructor_args) { // resolve the input type directly diff --git a/crates/forge/src/cmd/eip712.rs b/crates/forge/src/cmd/eip712.rs index 00a872a1df7a4..5d6b1bace69ca 100644 --- a/crates/forge/src/cmd/eip712.rs +++ b/crates/forge/src/cmd/eip712.rs @@ -84,7 +84,7 @@ impl Eip712Args { if compiler.sess().dcx.has_errors().is_err() { eyre::bail!("{diags}"); } else { - let _ = sh_print!("{diags}"); + let _ = sh_eprint!("{diags}"); } Ok(()) diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index de8a1a66548b4..9c1fb32775019 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -177,7 +177,7 @@ impl InspectArgs { fn parse_errors(abi: &JsonAbi) -> Map { let mut out = serde_json::Map::new(); - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { + for er in abi.errors.values().flatten() { let types = get_ty_sig(&er.inputs); let sig = format!("{:x}", er.selector()); let sig_trimmed = &sig[0..8]; @@ -188,7 +188,7 @@ fn parse_errors(abi: &JsonAbi) -> Map { fn parse_events(abi: &JsonAbi) -> Map { let mut out = serde_json::Map::new(); - for ev in abi.events.iter().flat_map(|(_, events)| events) { + for ev in abi.events.values().flatten() { let types = parse_event_params(&ev.inputs); let topic = hex::encode(keccak256(ev.signature())); out.insert(format!("{}({})", ev.name, types), format!("0x{topic}").into()); @@ -219,14 +219,14 @@ fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> { headers, |table| { // Print events - for ev in abi.events.iter().flat_map(|(_, events)| events) { + for ev in abi.events.values().flatten() { let types = parse_event_params(&ev.inputs); let selector = ev.selector().to_string(); table.add_row(["event", &format!("{}({})", ev.name, types), &selector]); } // Print errors - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { + for er in abi.errors.values().flatten() { let selector = er.selector().to_string(); table.add_row([ "error", @@ -236,7 +236,7 @@ fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> { } // Print functions - for func in abi.functions.iter().flat_map(|(_, f)| f) { + for func in abi.functions.values().flatten() { let selector = func.selector().to_string(); let state_mut = func.state_mutability.as_json_str(); let func_sig = if !func.outputs.is_empty() { diff --git a/crates/forge/src/cmd/lint.rs b/crates/forge/src/cmd/lint.rs index 1d5e5857ee594..5176903cbb50d 100644 --- a/crates/forge/src/cmd/lint.rs +++ b/crates/forge/src/cmd/lint.rs @@ -111,9 +111,7 @@ impl LintArgs { let solar_sources = get_solar_sources_from_compile_output(&config, &output, Some(&input), Some(&ignored))?; if solar_sources.input.sources.is_empty() { - return Err(eyre!( - "unable to lint. Solar only supports Solidity versions prior to 0.8.0" - )); + return Err(eyre!("unable to lint. Solar only supports Solidity versions >=0.8.0")); } // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new diff --git a/crates/forge/src/cmd/selectors.rs b/crates/forge/src/cmd/selectors.rs index dc2fe6de50ca4..78c80c19ec6da 100644 --- a/crates/forge/src/cmd/selectors.rs +++ b/crates/forge/src/cmd/selectors.rs @@ -416,12 +416,12 @@ impl SelectorsSubcommands { table.set_header(["Type", "Signature", "Selector", "Contract"]); + let selector_str = selector.strip_prefix("0x").unwrap_or(selector.as_str()); + let selector_bytes = hex::decode(selector_str)?; + for (_file, contract, artifact) in artifacts { let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - let selector_bytes = - hex::decode(selector.strip_prefix("0x").unwrap_or(&selector))?; - for func in abi.functions() { if func.selector().as_slice().starts_with(selector_bytes.as_slice()) { table.add_row([ diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index 4d6e4cae4b54b..1a37bd02aa4eb 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -270,6 +270,7 @@ impl FromStr for GasSnapshotEntry { reverts: reverts.as_str().parse().unwrap(), metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, }, }) } @@ -625,6 +626,7 @@ mod tests { reverts: 200, metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, } } ); @@ -645,6 +647,7 @@ mod tests { reverts: 2388, metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, } } ); diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index a37a422e6688b..158d0f07e2938 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -43,6 +43,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, }; +use rand::Rng; use regex::Regex; use std::{ collections::{BTreeMap, BTreeSet}, @@ -304,6 +305,12 @@ impl TestArgs { config.invariant.gas_report_samples = 0; } + // Generate a random fuzz seed if none provided, for reproducibility. + config.fuzz.seed = config + .fuzz + .seed + .or_else(|| Some(U256::from_be_bytes(rand::rng().random::<[u8; 32]>()))); + // Create test options from general project settings and compiler output. let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; @@ -361,7 +368,7 @@ impl TestArgs { // Decode traces. let decoder = outcome.last_run_decoder.as_ref().unwrap(); decode_trace_arena(arena, decoder).await; - let mut fst = folded_stack_trace::build(arena); + let mut fst = folded_stack_trace::build(arena, self.evm.isolate); let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; let contract = suite_name.split(':').next_back().unwrap(); @@ -429,6 +436,7 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, output: &ProjectCompileOutput, ) -> eyre::Result { + let fuzz_seed = config.fuzz.seed; if self.list { return list(runner, filter); } @@ -504,13 +512,13 @@ impl TestArgs { } }); sh_println!("{}", serde_json::to_string(&results)?)?; - return Ok(TestOutcome::new(Some(runner), results, self.allow_failure)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } if self.junit { let results = runner.test_collect(filter)?; sh_println!("{}", junit_xml_report(&results, verbosity).to_string()?)?; - return Ok(TestOutcome::new(Some(runner), results, self.allow_failure)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } let remote_chain = @@ -532,8 +540,9 @@ impl TestArgs { let mut identifier = TraceIdentifiers::new().with_local(&known_contracts); // Avoid using external identifiers for gas report as we decode more traces and this will be - // expensive. - if !self.gas_report { + // expensive. Also skip external identifiers for local tests (no remote chain) to avoid + // unnecessary Etherscan API calls that significantly slow down test execution. + if !self.gas_report && remote_chain.is_some() { identifier = identifier.with_external(&config, remote_chain)?; } @@ -566,6 +575,7 @@ impl TestArgs { let mut gas_snapshots = BTreeMap::>::new(); let mut outcome = TestOutcome::empty(None, self.allow_failure); + outcome.fuzz_seed = fuzz_seed; let mut any_test_failed = false; let mut backtrace_builder = None; @@ -745,7 +755,7 @@ impl TestArgs { |mut found, (group, snapshots)| { // If the snapshot file doesn't exist, we can't compare so we skip. if !&config.snapshots.join(format!("{group}.json")).exists() { - return false; + return found; } let previous_snapshots: BTreeMap = diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index bb9e749cbcff2..040f51d587bf3 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -135,6 +135,19 @@ impl CoverageReporter for LcovReporter { writeln!(out, "TN:")?; writeln!(out, "SF:{}", path.display())?; + // First pass: collect line hits for DA records. + // Track both which lines have been recorded and the max hits per line. + let mut line_hits: HashMap = HashMap::default(); + for item in &items { + if matches!(item.kind, CoverageItemKind::Line | CoverageItemKind::Statement) { + let line = item.loc.lines.start; + line_hits + .entry(line) + .and_modify(|h| *h = (*h).max(item.hits)) + .or_insert(item.hits); + } + } + let mut recorded_lines = HashSet::new(); for item in items { @@ -160,15 +173,26 @@ impl CoverageReporter for LcovReporter { } } // Add lines / statement hits only once. - CoverageItemKind::Line | CoverageItemKind::Statement => { - if recorded_lines.insert(line) { - writeln!(out, "DA:{line},{hits}")?; - } + CoverageItemKind::Line | CoverageItemKind::Statement + if recorded_lines.insert(line) => + { + writeln!(out, "DA:{line},{hits}")?; } CoverageItemKind::Branch { branch_id, path_id, .. } => { - let hits_str = if hits == 0 { "-" } else { &hits.to_string() }; + // Per LCOV spec: "-" means the expression was never evaluated (line not + // executed), "0" means branch exists but was never taken. + // Check if the line containing this branch was hit. + let line_was_hit = line_hits.get(&line).is_some_and(|&h| h > 0); + let hits_str = if hits > 0 { + hits.to_string() + } else if line_was_hit { + "0".to_string() + } else { + "-".to_string() + }; writeln!(out, "BRDA:{line},{branch_id},{path_id},{hits_str}")?; } + _ => {} } } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 985febef2b964..95b16c93a146c 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -5,7 +5,9 @@ use crate::{ traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use alloy_primitives::map::HashSet; -use comfy_table::{Cell, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; +use comfy_table::{ + Cell, CellAlignment, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, +}; use foundry_common::{TestFunctionExt, calc, shell}; use foundry_evm::traces::CallKind; @@ -175,8 +177,8 @@ impl GasReport { let functions = contract .functions - .iter() - .flat_map(|(_, sigs)| { + .values() + .flat_map(|sigs| { sigs.iter().map(|(sig, gas_info)| { let display_name = sig.replace(':', ""); (display_name, gas_info) @@ -213,8 +215,8 @@ impl GasReport { Cell::new("Deployment Size").fg(Color::Cyan), ]); table.add_row(vec![ - Cell::new(contract.gas.to_string()), - Cell::new(contract.size.to_string()), + Cell::new(contract.gas.to_string()).set_alignment(CellAlignment::Right), + Cell::new(contract.size.to_string()).set_alignment(CellAlignment::Right), ]); // Add a blank row to separate deployment info from function info. @@ -237,11 +239,19 @@ impl GasReport { table.add_row(vec![ Cell::new(display_name), - Cell::new(gas_info.min.to_string()).fg(Color::Green), - Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), - Cell::new(gas_info.median.to_string()).fg(Color::Yellow), - Cell::new(gas_info.max.to_string()).fg(Color::Red), - Cell::new(gas_info.calls.to_string()), + Cell::new(gas_info.min.to_string()) + .fg(Color::Green) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.mean.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.median.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.max.to_string()) + .fg(Color::Red) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.calls.to_string()).set_alignment(CellAlignment::Right), ]); }) }); diff --git a/crates/forge/src/lockfile.rs b/crates/forge/src/lockfile.rs index bbcbc5dedc4e8..68330db657317 100644 --- a/crates/forge/src/lockfile.rs +++ b/crates/forge/src/lockfile.rs @@ -55,11 +55,10 @@ impl<'a> Lockfile<'a> { pub fn sync(&mut self, lib: &Path) -> Result> { match self.read() { Ok(_) => {} - Err(e) => { - if !e.to_string().contains("Lockfile not found") { - return Err(e); - } + Err(e) if !e.to_string().contains("Lockfile not found") => { + return Err(e); } + _ => {} } if let Some(git) = &self.git { @@ -81,10 +80,8 @@ impl<'a> Lockfile<'a> { let entry = self.deps.entry(rel_path.to_path_buf()); match entry { - Entry::Occupied(e) => { - if e.get().rev() != rev { - out_of_sync.insert(rel_path.to_path_buf(), e.get().clone()); - } + Entry::Occupied(e) if e.get().rev() != rev => { + out_of_sync.insert(rel_path.to_path_buf(), e.get().clone()); } Entry::Vacant(e) => { // Check if there is branch specified for the submodule at rel_path in @@ -108,6 +105,7 @@ impl<'a> Lockfile<'a> { e.insert(dep_id.clone()); out_of_sync.insert(rel_path.to_path_buf(), dep_id); } + _ => {} } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index f7e36dc323427..988191b7d7c0f 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -377,6 +377,7 @@ impl TestRunnerConfig { ExecutorBuilder::new() .inspectors(|stack| { stack + .logs(self.config.live_logs) .cheatcodes(cheats_config) .trace_mode(self.trace_mode()) .line_coverage(self.line_coverage) @@ -514,8 +515,8 @@ impl MultiContractRunnerBuilder { // Build revert decoder from ABIs of all artifacts. let abis = linker .contracts - .iter() - .filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow())); + .values() + .filter_map(|contract| contract.abi.as_ref().map(|abi| abi.borrow())); let revert_decoder = RevertDecoder::new().with_abis(abis); let LinkOutput { libraries, libs_to_deploy } = linker.link_with_nonce_or_address( diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 7a0f42846f4c1..693bea6f1eeb7 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -6,7 +6,7 @@ use crate::{ gas_report::GasReport, }; use alloy_primitives::{ - Address, Log, + Address, I256, Log, U256, map::{AddressHashMap, HashMap}, }; use eyre::Report; @@ -46,6 +46,8 @@ pub struct TestOutcome { pub gas_report: Option, /// The runner used to execute the tests. pub runner: Option, + /// The fuzz seed used for the test run. + pub fuzz_seed: Option, } impl TestOutcome { @@ -54,13 +56,14 @@ impl TestOutcome { runner: Option, results: BTreeMap, allow_failure: bool, + fuzz_seed: Option, ) -> Self { - Self { results, allow_failure, last_run_decoder: None, gas_report: None, runner } + Self { results, allow_failure, last_run_decoder: None, gas_report: None, runner, fuzz_seed } } /// Creates a new empty test outcome. pub fn empty(runner: Option, allow_failure: bool) -> Self { - Self::new(runner, BTreeMap::new(), allow_failure) + Self::new(runner, BTreeMap::new(), allow_failure, None) } /// Returns an iterator over all individual succeeding tests and their names. @@ -130,6 +133,11 @@ impl TestOutcome { self.failures().count() } + /// Returns `true` if any fuzz or invariant test failed. + pub fn has_fuzz_failures(&self) -> bool { + self.failures().any(|(_, t)| t.kind.is_fuzz() || t.kind.is_invariant()) + } + /// Sums up all the durations of all individual test suites. /// /// Note that this is not necessarily the wall clock time of the entire test run. @@ -200,6 +208,17 @@ impl TestOutcome { test_word )?; + // Print seed for fuzz/invariant test failures to enable reproduction. + if let Some(seed) = self.fuzz_seed + && outcome.has_fuzz_failures() + { + sh_println!( + "\nFuzz seed: {} (use {} to reproduce)", + format!("{seed:#x}").cyan(), + "`--fuzz-seed`".cyan() + )?; + } + std::process::exit(1); } @@ -444,7 +463,25 @@ pub struct TestResult { impl fmt::Display for TestResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.status { - TestStatus::Success => "[PASS]".green().fmt(f), + TestStatus::Success => { + // For optimization mode, show the best example sequence in green. + if let Some(CounterExample::Sequence(original, sequence)) = &self.counterexample { + let mut s = String::from("[PASS]"); + s.push_str( + format!( + "\n\t[Best sequence] (original: {original}, shrunk: {})\n", + sequence.len() + ) + .as_str(), + ); + for ex in sequence { + writeln!(s, "{ex}").unwrap(); + } + s.green().wrap().fmt(f) + } else { + "[PASS]".green().fmt(f) + } + } TestStatus::Skipped => { let mut s = String::from("[SKIP"); if let Some(reason) = &self.reason { @@ -561,8 +598,9 @@ impl TestResult { reason: Option, raw_call_result: RawCallResult, ) { - self.kind = - TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) }; + self.kind = TestKind::Unit { + gas: raw_call_result.gas_used.saturating_sub(raw_call_result.stipend), + }; extend!(self, raw_call_result, TraceKind::Execution); @@ -620,6 +658,7 @@ impl TestResult { failed_corpus_replays: 0, }; self.status = TestStatus::Failure; + debug!(?e, "failed to set up fuzz testing environment"); self.reason = Some(format!("failed to set up fuzz testing environment: {e}")); } @@ -631,6 +670,7 @@ impl TestResult { reverts: 1, metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, }; self.status = TestStatus::Skipped; self.reason = reason.0; @@ -649,6 +689,7 @@ impl TestResult { reverts: 1, metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, }; self.status = TestStatus::Failure; self.reason = if replayed_entirely { @@ -667,6 +708,7 @@ impl TestResult { reverts: 0, metrics: HashMap::default(), failed_corpus_replays: 0, + optimization_best_value: None, }; self.status = TestStatus::Failure; self.reason = Some(format!("failed to set up invariant testing environment: {e}")); @@ -684,6 +726,7 @@ impl TestResult { reverts: usize, metrics: Map, failed_corpus_replays: usize, + optimization_best_value: Option, ) { self.kind = TestKind::Invariant { runs: cases.len(), @@ -691,10 +734,13 @@ impl TestResult { reverts, metrics, failed_corpus_replays, + optimization_best_value, }; - self.status = match success { - true => TestStatus::Success, - false => TestStatus::Failure, + // For optimization mode (Some value), always succeed. For check mode (None), use success. + self.status = if optimization_best_value.is_some() || success { + TestStatus::Success + } else { + TestStatus::Failure }; self.reason = reason; self.counterexample = counterexample; @@ -767,6 +813,8 @@ pub enum TestKindReport { reverts: usize, metrics: Map, failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best value achieved. None = check mode. + optimization_best_value: Option, }, Table { runs: usize, @@ -791,8 +839,18 @@ impl fmt::Display for TestKindReport { write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") } } - Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => { - if *failed_corpus_replays != 0 { + Self::Invariant { + runs, + calls, + reverts, + metrics: _, + failed_corpus_replays, + optimization_best_value, + } => { + // If optimization_best_value is Some, this is optimization mode. + if let Some(best_value) = optimization_best_value { + write!(f, "(best: {best_value}, runs: {runs}, calls: {calls})") + } else if *failed_corpus_replays != 0 { write!( f, "(runs: {runs}, calls: {calls}, reverts: {reverts}, failed corpus replays: {failed_corpus_replays})" @@ -842,6 +900,8 @@ pub enum TestKind { reverts: usize, metrics: Map, failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best value achieved. None = check mode. + optimization_best_value: Option, }, /// A table test. Table { runs: usize, mean_gas: u64, median_gas: u64 }, @@ -854,6 +914,16 @@ impl Default for TestKind { } impl TestKind { + /// Returns `true` if this is a fuzz test. + pub fn is_fuzz(&self) -> bool { + matches!(self, Self::Fuzz { .. }) + } + + /// Returns `true` if this is an invariant test. + pub fn is_invariant(&self) -> bool { + matches!(self, Self::Invariant { .. }) + } + /// The gas consumed by this test pub fn report(&self) -> TestKindReport { match self { @@ -866,15 +936,21 @@ impl TestKind { failed_corpus_replays: *failed_corpus_replays, } } - Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => { - TestKindReport::Invariant { - runs: *runs, - calls: *calls, - reverts: *reverts, - metrics: HashMap::default(), - failed_corpus_replays: *failed_corpus_replays, - } - } + Self::Invariant { + runs, + calls, + reverts, + metrics: _, + failed_corpus_replays, + optimization_best_value, + } => TestKindReport::Invariant { + runs: *runs, + calls: *calls, + reverts: *reverts, + metrics: HashMap::default(), + failed_corpus_replays: *failed_corpus_replays, + optimization_best_value: *optimization_best_value, + }, Self::Table { runs, mean_gas, median_gas } => { TestKindReport::Table { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index abf4805cf1343..8fb9a29c7512a 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -754,12 +754,8 @@ impl<'a> FunctionRunner<'a> { identified_contracts, &self.cr.mcr.known_contracts, ); - let invariant_contract = InvariantContract { - address: self.address, - invariant_function: func, - call_after_invariant, - abi: &self.cr.contract.abi, - }; + let invariant_contract = + InvariantContract::new(self.address, func, call_after_invariant, &self.cr.contract.abi); let show_solidity = invariant_config.show_solidity; let progress = start_fuzz_progress( @@ -818,6 +814,7 @@ impl<'a> FunctionRunner<'a> { self.clone_executor(), &txes, None, + None, // check mode &invariant_contract, &self.cr.mcr.known_contracts, identified_contracts.clone(), @@ -828,21 +825,20 @@ impl<'a> FunctionRunner<'a> { progress.as_ref(), &self.tcfg.early_exit, ) { - Ok(replayed_call_sequence) => { - if !replayed_call_sequence.is_empty() { - call_sequence = replayed_call_sequence; - // Persist error in invariant failure dir. - record_invariant_failure( - failure_dir.as_path(), - failure_file.as_path(), - &call_sequence, - test_bytecode, - ); - } + Ok(replayed_call_sequence) if !replayed_call_sequence.is_empty() => { + call_sequence = replayed_call_sequence; + // Persist error in invariant failure dir. + record_invariant_failure( + failure_dir.as_path(), + failure_file.as_path(), + &call_sequence, + test_bytecode, + ); } Err(err) => { error!(%err, "Failed to replay invariant error"); } + _ => {} } self.result.invariant_replay_fail( @@ -889,6 +885,7 @@ impl<'a> FunctionRunner<'a> { self.clone_executor(), calls, Some(case_data.inner_sequence), + None, // check mode &invariant_contract, &self.cr.mcr.known_contracts, identified_contracts.clone(), @@ -899,33 +896,31 @@ impl<'a> FunctionRunner<'a> { progress.as_ref(), &self.tcfg.early_exit, ) { - Ok(call_sequence) => { - if !call_sequence.is_empty() { - // Persist error in invariant failure dir. - record_invariant_failure( - failure_dir.as_path(), - failure_file.as_path(), - &call_sequence, - test_bytecode, - ); - - let original_seq_len = if let TestError::Fail(_, calls) = - &case_data.test_error - { + Ok(call_sequence) if !call_sequence.is_empty() => { + // Persist error in invariant failure dir. + record_invariant_failure( + failure_dir.as_path(), + failure_file.as_path(), + &call_sequence, + test_bytecode, + ); + + let original_seq_len = + if let TestError::Fail(_, calls) = &case_data.test_error { calls.len() } else { call_sequence.len() }; - counterexample = Some(CounterExample::Sequence( - original_seq_len, - call_sequence, - )) - } + counterexample = Some(CounterExample::Sequence( + original_seq_len, + call_sequence, + )) } Err(err) => { error!(%err, "Failed to replay invariant error"); } + _ => {} } } }; @@ -933,22 +928,53 @@ impl<'a> FunctionRunner<'a> { InvariantFuzzError::MaxAssumeRejects(_) => {} }, - // If invariants ran successfully, replay the last run to collect logs and - // traces. + // If invariants ran successfully, replay the last run to collect logs and traces. _ => { - if let Err(err) = replay_run( - &invariant_contract, - self.clone_executor(), - &self.cr.mcr.known_contracts, - identified_contracts.clone(), - &mut self.result.logs, - &mut self.result.traces, - &mut self.result.line_coverage, - &mut self.result.deprecated_cheatcodes, - &invariant_result.last_run_inputs, - show_solidity, - ) { - error!(%err, "Failed to replay last invariant run"); + if let Some(best_value) = invariant_result.optimization_best_value { + // Optimization mode: replay and shrink to find shortest best sequence. + match replay_error( + evm.config(), + self.clone_executor(), + &invariant_result.optimization_best_sequence, + None, + Some(best_value), + &invariant_contract, + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.line_coverage, + &mut self.result.deprecated_cheatcodes, + progress.as_ref(), + &self.tcfg.early_exit, + ) { + Ok(best_sequence) if !best_sequence.is_empty() => { + counterexample = Some(CounterExample::Sequence( + invariant_result.optimization_best_sequence.len(), + best_sequence, + )); + } + Err(err) => { + error!(%err, "Failed to replay optimization best sequence"); + } + _ => {} + } + } else { + // Standard check mode: replay last run for traces. + if let Err(err) = replay_run( + &invariant_contract, + self.clone_executor(), + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.line_coverage, + &mut self.result.deprecated_cheatcodes, + &invariant_result.last_run_inputs, + show_solidity, + ) { + error!(%err, "Failed to replay last invariant run"); + } } } } @@ -962,6 +988,7 @@ impl<'a> FunctionRunner<'a> { invariant_result.reverts, invariant_result.metrics, invariant_result.failed_corpus_replays, + invariant_result.optimization_best_value, ); self.result } @@ -1017,6 +1044,7 @@ impl<'a> FunctionRunner<'a> { &self.cr.mcr.revert_decoder, progress.as_ref(), &self.tcfg.early_exit, + self.cr.tokio_handle, ) { Ok(x) => x, Err(e) => { diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 979521c33e489..01be19cb6298f 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,6 +1,7 @@ use crate::utils::generate_large_init_contract; -use foundry_test_utils::{forgetest, snapbox::IntoData, str}; +use foundry_test_utils::{forgetest, forgetest_init, snapbox::IntoData, str}; use globset::Glob; +use std::fs; forgetest_init!(can_parse_build_filters, |prj, cmd| { prj.initialize_default_contracts(); @@ -439,22 +440,74 @@ contract ContractB { cmd.args(["build", "src/ContractWithInvalidNatspec.sol"]).assert_success().stderr_eq(str![[ r#" warning: invalid natspec tag '@deprecated', custom tags must use format '@custom:name' - [FILE]:5:5 - | -5 | /// @deprecated quoteExactOutputSingle and exactOutput. Use QuoterV2 instead. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | + [FILE]:5:5 + │ +5 │ /// @deprecated quoteExactOutputSingle and exactOutput. Use QuoterV2 instead. + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ ... warning: invalid natspec tag '@note', custom tags must use format '@custom:name' - [FILE]:9:1 - | -9 | /// @note foo bar - | ^^^^^^^^^^^^^^^^^ - | + [FILE]:9:1 + │ +9 │ /// @note foo bar + │ ━━━━━━━━━━━━━━━━━ + │ ... "# ]]); }); + +// tests that build succeeds without warning when no soldeer.lock exists +forgetest_init!(build_no_warning_without_soldeer_lock, |prj, cmd| { + let soldeer_lock = prj.root().join("soldeer.lock"); + // soldeer.lock should not exist in a fresh project + assert!(!soldeer_lock.exists()); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +"#]]); +}); + +// tests that malformed foundry.lock triggers a warning during build +forgetest_init!(build_warns_on_malformed_foundry_lock, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + fs::write(&foundry_lock, "this is not valid toml { [ }").unwrap(); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +Warning: Failed to parse foundry.lock: [..] +... +"#]]); +}); + +// tests that build succeeds without warning when no foundry.lock exists +forgetest_init!(build_no_warning_without_foundry_lock, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + // Remove foundry.lock if it exists from template + let _ = fs::remove_file(&foundry_lock); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +"#]]); +}); + +// tests that build warns when foundry.lock revision differs from actual submodule revision +forgetest_init!(build_warns_on_foundry_lock_revision_mismatch, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + + // Write a foundry.lock with a fake/old revision for forge-std that differs from the actual + let lockfile_content = r#"{ + "lib/forge-std": { + "tag": { + "name": "v1.9.7", + "rev": "0000000000000000000000000000000000000000" + } + } +}"#; + fs::write(&foundry_lock, lockfile_content).unwrap(); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +Warning: Dependency 'lib/forge-std' revision mismatch: expected '0000000000000000000000000000000000000000', found '[..]' + +"#]]); +}); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index e0b2c43c499ac..30f0f43001b60 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -696,7 +696,7 @@ Initializing [..] from https://github.com/foundry-rs/forge-template... }); // checks that clone works -forgetest!(can_clone, |prj, cmd| { +forgetest!(flaky_can_clone, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -728,7 +728,7 @@ Compiler run successful! }); // Checks that quiet mode does not print anything for clone -forgetest!(can_clone_quiet, |prj, cmd| { +forgetest!(flaky_can_clone_quiet, |prj, cmd| { prj.wipe(); cmd.args([ @@ -743,7 +743,7 @@ forgetest!(can_clone_quiet, |prj, cmd| { }); // checks that clone works with sourcify -forgetest!(can_clone_sourcify, |prj, cmd| { +forgetest!(flaky_can_clone_sourcify, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -770,7 +770,7 @@ Compiler run successful! }); // checks that clone works with --no-remappings-txt -forgetest!(can_clone_no_remappings_txt, |prj, cmd| { +forgetest!(flaky_can_clone_no_remappings_txt, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -803,7 +803,7 @@ Compiler run successful! }); // checks that clone works with --keep-directory-structure -forgetest!(can_clone_keep_directory_structure, |prj, cmd| { +forgetest!(flaky_can_clone_keep_directory_structure, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -935,7 +935,7 @@ Installing tempo-std in [..] (url: https://github.com/tempoxyz/tempo-std, tag: N // checks that clone works with raw src containing `node_modules` // -forgetest!(can_clone_with_node_modules, |prj, cmd| { +forgetest!(flaky_can_clone_with_node_modules, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -1630,13 +1630,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1644,13 +1644,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1658,13 +1658,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1735,13 +1735,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1749,13 +1749,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1763,13 +1763,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1840,13 +1840,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1854,13 +1854,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1868,13 +1868,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1948,13 +1948,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1962,13 +1962,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1976,13 +1976,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2060,13 +2060,13 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2107,13 +2107,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2154,13 +2154,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2209,13 +2209,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2223,13 +2223,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2290,13 +2290,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2304,13 +2304,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2379,13 +2379,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2393,13 +2393,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133243 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2407,13 +2407,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133027 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2542,19 +2542,19 @@ contract CounterTest is DSTest { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 172107 | 578 | | | | | +| 172107 | 578 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| a | 2402 | 2402 | 2402 | 2402 | 1 | +| a | 2402 | 2402 | 2402 | 2402 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| b | 2447 | 2447 | 2447 | 2447 | 1 | +| b | 2447 | 2447 | 2447 | 2447 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | +| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | +| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2669,15 +2669,15 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| 117171 | 471 | | | | | +| 117171 | 471 | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| deposit | 21185 | 21185 | 21185 | 21185 | 1 | +| deposit | 21185 | 21185 | 21185 | 21185 | 1 | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 29758 | 29758 | 29758 | 29758 | 1 | +| fallback | 29758 | 29758 | 29758 | 29758 | 1 | ╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2685,13 +2685,13 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| 153531 | 494 | | | | | +| 153531 | 494 | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| deposit | 3661 | 3661 | 3661 | 3661 | 1 | +| deposit | 3661 | 3661 | 3661 | 3661 | 1 | ╰-----------------------------------------------------+-----------------+------+--------+------+---------╯ @@ -2751,7 +2751,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); // -forgetest_init!(gas_report_fallback_with_calldata, |prj, cmd| { +forgetest_init!(flaky_gas_report_fallback_with_calldata, |prj, cmd| { prj.initialize_default_contracts(); prj.add_test( "FallbackWithCalldataTest.sol", @@ -2807,13 +2807,13 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132459 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 43461 | 43461 | 43461 | 43461 | 1 | +| fallback | 43461 | 43461 | 43461 | 43461 | 1 | ╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2830,7 +2830,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", "deployment": { - "gas": 132459, + "gas": 132471, "size": 396 }, "functions": { @@ -2892,13 +2892,13 @@ contract NestedDeploy is Test { +======================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| 0 | 132 | | | | | +| 0 | 132 | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| w | 21185 | 21185 | 21185 | 21185 | 1 | +| w | 21185 | 21185 | 21185 | 21185 | 1 | ╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2906,13 +2906,13 @@ contract NestedDeploy is Test { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+------+--------+------+---------| -| 0 | 731 | | | | | +| 0 | 731 | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+------+--------+------+---------| -| child | 2681 | 2681 | 2681 | 2681 | 1 | +| child | 2681 | 2681 | 2681 | 2681 | 1 | ╰------------------------------------------+-----------------+------+--------+------+---------╯ ╭-------------------------------------------+-----------------+-----+--------+-----+---------╮ @@ -2920,13 +2920,13 @@ contract NestedDeploy is Test { +============================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| 328961 | 1163 | | | | | +| 328961 | 1163 | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | | | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| child | 525 | 525 | 525 | 525 | 1 | +| child | 525 | 525 | 525 | 525 | 1 | ╰-------------------------------------------+-----------------+-----+--------+-----+---------╯ @@ -3670,7 +3670,7 @@ forgetest_init!(can_inspect_standard_json, |prj, cmd| { ] } }, - "evmVersion": "prague", + "evmVersion": "osaka", "viaIR": false, "libraries": {} } @@ -3732,17 +3732,17 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 156813 | 509 | | | | | +| 156813 | 509 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| number | 2424 | 2424 | 2424 | 2424 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -3750,15 +3750,15 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| 1544498 | 7573 | | | | | +| 1544498 | 7573 | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| setUp | 218902 | 218902 | 218902 | 218902 | 1 | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | ╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -3774,21 +3774,21 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) | src/Counter.sol:Counter Contract | | | | | | |----------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 156813 | 509 | | | | | +| 156813 | 509 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | -| increment | 43482 | 43482 | 43482 | 43482 | 1 | -| number | 2424 | 2424 | 2424 | 2424 | 1 | -| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | | test/Counter.t.sol:CounterTest Contract | | | | | | |-----------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 1544498 | 7573 | | | | | +| 1544498 | 7573 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | -| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | +| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/cli/compiler.rs b/crates/forge/tests/cli/compiler.rs index 7c549e1ce3490..eec4873b0ae32 100644 --- a/crates/forge/tests/cli/compiler.rs +++ b/crates/forge/tests/cli/compiler.rs @@ -18,14 +18,14 @@ contract ContractB {} const CONTRACT_C: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.30; +pragma solidity 0.8.33; contract ContractC {} "#; const CONTRACT_D: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.30; +pragma solidity 0.8.33; contract ContractD {} "#; @@ -111,7 +111,7 @@ forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| { cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#" Solidity: -0.8.30: +0.8.33: ├── src/ContractC.sol └── src/ContractD.sol @@ -128,7 +128,7 @@ forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| { { "Solidity": [ { - "version": "0.8.30", + "version": "0.8.33", "paths": [ "src/ContractC.sol", "src/ContractD.sol" @@ -153,7 +153,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions, |prj, cmd| { Solidity: - 0.8.4 - 0.8.11 -- 0.8.30 +- 0.8.33 Vyper: - 0.4.3 @@ -198,7 +198,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| { "Solidity": [ { - "version": "0.8.30", + "version": "0.8.33", "paths": [ "src/ContractD.sol" ] @@ -236,7 +236,7 @@ Solidity: 0.8.11 (<= london): └── src/ContractB.sol -0.8.30 (<= prague): +0.8.33 (<= prague): ├── src/ContractC.sol └── src/ContractD.sol @@ -277,7 +277,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| ] }, { - "version": "0.8.30", + "version": "0.8.33", "evm_version": "[..]", "paths": [ "src/ContractC.sol", diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 468d79e0730b9..b172ea70c24e0 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -49,7 +49,7 @@ allow_paths = [] include_paths = [] skip = [] force = false -evm_version = "prague" +evm_version = "osaka" gas_reports = ["*"] gas_reports_ignore = [] gas_reports_include_tests = false @@ -59,17 +59,22 @@ optimizer = false optimizer_runs = 200 verbosity = 0 eth_rpc_accept_invalid_certs = false +eth_rpc_no_proxy = false +eth_rpc_curl = false ignored_error_codes = [ "license", "code-size", "init-code-size", "transient-storage", + "transfer-deprecated", + "natspec-memory-safe-assembly-deprecated", ] ignored_warnings_from = [] deny = "never" test_failures_file = "cache/test-failures" show_progress = false ffi = false +live_logs = false allow_internal_expect_revert = false always_use_create_2_factory = false prompt_timeout = 120 @@ -137,6 +142,7 @@ docs_style = "preserve" ignore = [] contract_new_lines = false sort_imports = false +namespace_import_style = "prefer_plain" pow_no_space = false prefer_compact = "all" single_line_imports = false @@ -153,6 +159,14 @@ lint_on_build = true mixed_case_exceptions = [ "ERC", "URI", + "ID", + "URL", + "API", + "JSON", + "XML", + "HTML", + "HTTP", + "HTTPS", ] [doc] @@ -201,6 +215,7 @@ show_edge_coverage = false failure_persist_dir = "cache/invariant" show_metrics = true show_solidity = false +check_interval = 1 [labels] @@ -283,6 +298,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { ..Default::default() }, ffi: true, + live_logs: true, allow_internal_expect_revert: false, always_use_create_2_factory: false, prompt_timeout: 0, @@ -306,9 +322,11 @@ forgetest!(can_extract_config_values, |prj, cmd| { memory_limit: 1 << 27, eth_rpc_url: Some("localhost".to_string()), eth_rpc_accept_invalid_certs: false, + eth_rpc_no_proxy: false, eth_rpc_jwt: None, eth_rpc_timeout: None, eth_rpc_headers: None, + eth_rpc_curl: false, etherscan_api_key: None, etherscan: Default::default(), verbosity: 4, @@ -1195,7 +1213,7 @@ forgetest_init!(test_default_config, |prj, cmd| { "include_paths": [], "skip": [], "force": false, - "evm_version": "prague", + "evm_version": "osaka", "gas_reports": [ "*" ], @@ -1211,15 +1229,19 @@ forgetest_init!(test_default_config, |prj, cmd| { "verbosity": 0, "eth_rpc_url": null, "eth_rpc_accept_invalid_certs": false, + "eth_rpc_no_proxy": false, "eth_rpc_jwt": null, "eth_rpc_timeout": null, "eth_rpc_headers": null, + "eth_rpc_curl": false, "etherscan_api_key": null, "ignored_error_codes": [ "license", "code-size", "init-code-size", - "transient-storage" + "transient-storage", + "transfer-deprecated", + "natspec-memory-safe-assembly-deprecated" ], "ignored_warnings_from": [], "deny": "never", @@ -1278,9 +1300,11 @@ forgetest_init!(test_default_config, |prj, cmd| { "timeout": null, "show_solidity": false, "max_time_delay": null, - "max_block_delay": null + "max_block_delay": null, + "check_interval": 1 }, "ffi": false, + "live_logs": false, "allow_internal_expect_revert": false, "always_use_create_2_factory": false, "prompt_timeout": 120, @@ -1336,6 +1360,7 @@ forgetest_init!(test_default_config, |prj, cmd| { "ignore": [], "contract_new_lines": false, "sort_imports": false, + "namespace_import_style": "prefer_plain", "pow_no_space": false, "prefer_compact": "all", "single_line_imports": false @@ -1351,7 +1376,15 @@ forgetest_init!(test_default_config, |prj, cmd| { "lint_on_build": true, "mixed_case_exceptions": [ "ERC", - "URI" + "URI", + "ID", + "URL", + "API", + "JSON", + "XML", + "HTML", + "HTTP", + "HTTPS" ] }, "doc": { @@ -1790,7 +1823,7 @@ contract Counter { let v1_profile = SettingsOverrides { name: "v1".to_string(), via_ir: Some(true), - evm_version: Some(EvmVersion::Prague), + evm_version: Some(EvmVersion::Osaka), optimizer: None, optimizer_runs: Some(44444444), bytecode_hash: None, @@ -1876,19 +1909,19 @@ contract Counter { let (via_ir, evm_version, enabled, runs) = artifact_settings("Counter.sol/Counter.json"); assert_eq!(None, via_ir); - assert_eq!("\"prague\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("false", enabled.unwrap().to_string()); assert_eq!("200", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v1/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"prague\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("44444444", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v2/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"prague\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("111", runs.unwrap().to_string()); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 96c40723b3a8a..a8805ff9c4c34 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -2204,6 +2204,90 @@ contract CounterTest is DSTest { "#]]); }); +// +// Test BRDA hit values follow LCOV spec: "-" when line never executed, "0" when line hit but +// branch not taken. This ensures `genhtml` consistency. +forgetest!(brda_lcov_consistency, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setPositive(uint256 newNumber) public { + if (newNumber > 0) { + number = newNumber; + } else { + number = 1; + } + } + + function neverCalled(uint256 x) public { + if (x > 100) { + number = x; + } else { + number = 100; + } + } +} + "#, + ); + + prj.add_source( + "Counter.t.sol", + r#" +import "./test.sol"; +import "./Counter.sol"; + +contract CounterTest is DSTest { + function test_only_positive_branch() public { + Counter counter = new Counter(); + counter.setPositive(42); + counter.setPositive(100); + } +} + "#, + ); + + // Verify BRDA values: + // - BRDA:8,0,0,2 - if branch taken 2 times + // - BRDA:8,0,1,0 - else branch NOT taken but line was hit (outputs "0", not "-") + // - BRDA:16,1,0,- - if branch NOT taken AND line never executed (outputs "-") + // - BRDA:16,1,1,- - else branch NOT taken AND line never executed (outputs "-") + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/Counter.sol +DA:7,2 +FN:7,Counter.setPositive +FNDA:2,Counter.setPositive +DA:8,2 +BRDA:8,0,0,2 +BRDA:8,0,1,0 +DA:9,2 +DA:11,0 +DA:15,0 +FN:15,Counter.neverCalled +FNDA:0,Counter.neverCalled +DA:16,0 +BRDA:16,1,0,- +BRDA:16,1,1,- +DA:17,0 +DA:19,0 +FNF:2 +FNH:1 +LF:8 +LH:3 +BRF:4 +BRH:1 +end_of_record + +"#]], + ); +}); + // Test that coverage files are written even when tests fail. forgetest!(coverage_with_failing_tests, |prj, cmd| { prj.insert_ds_test(); diff --git a/crates/forge/tests/cli/doc.rs b/crates/forge/tests/cli/doc.rs index f1f6a8b2dec59..947c7a6a9acec 100644 --- a/crates/forge/tests/cli/doc.rs +++ b/crates/forge/tests/cli/doc.rs @@ -112,3 +112,178 @@ contract Derived is IBase { content.lines().find(|line| line.contains("[IBase]")).unwrap_or("not found") ); }); + +// Test that constants and immutables are documented under "Constants" section when only constants +// are present fixes +forgetest_init!(constants_and_immutables_are_documented_under_constants_section, |prj, cmd| { + prj.add_source( + "CounterConstants.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterConstants { + uint256 public constant FOO = 1; + uint256 public immutable BAR; + + constructor() { + BAR = 2; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = + prj.root().join("docs/src/src/CounterConstants.sol/contract.CounterConstants.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section exists + assert!(content.contains("## Constants"), "Should have Constants section"); + // Check that State Variables section does not exist + assert!(!content.contains("## State Variables"), "Should not have State Variables section"); + + // Get the position of the Constants section and of the Functions section + let constants_section_pos = content.find("## Constants").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Check that Constants section contains the constant + assert!(content.contains("### FOO"), "Should have FOO constant"); + let foo_constant_pos = content.find("## FOO").unwrap(); + assert!( + foo_constant_pos > constants_section_pos && foo_constant_pos < functions_section_pos, + "FOO constant should be after Constants section and before Functions section" + ); + + // Check that Constants section contains the immutable + let bar_immutable_pos = content.find("## BAR").unwrap(); + assert!(content.contains("### BAR"), "Should have BAR immutable"); + assert!( + bar_immutable_pos > constants_section_pos && bar_immutable_pos < functions_section_pos, + "BAR immutable should be after Constants section and before Functions section" + ); +}); + +// Test that state variables are documented under "State Variables" section when only state +// variables are present fixes +forgetest_init!(state_variables_are_documented_under_state_variables_section, |prj, cmd| { + prj.add_source( + "CounterStateVariables.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterStateVariables { + uint256 public baz; + + function increment() public { + baz++; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = + prj.root().join("docs/src/src/CounterStateVariables.sol/contract.CounterStateVariables.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section does not exist + assert!(!content.contains("## Constants"), "Should not have Constants section"); + // Check that State Variables section exists + assert!(content.contains("## State Variables"), "Should have State Variables section"); + + // Get the position of the State Variables section and of the Functions section + let state_variables_section_pos = content.find("## State Variables").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Check that State Variables section contains the state variable + assert!(content.contains("### baz"), "Should have baz state variable"); + let baz_state_variable_pos = content.find("## baz").unwrap(); + assert!( + baz_state_variable_pos > state_variables_section_pos + && baz_state_variable_pos < functions_section_pos, + "baz state variable should be after State Variables section and before Functions section" + ); +}); + +// Test that constants/immutables and state-variables are documented under separate sections when +// both are present fixes +forgetest_init!( + constants_and_immutables_and_state_variables_are_documented_under_separate_sections, + |prj, cmd| { + prj.add_source( + "CounterMixedVariables.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterMixedVariables { + uint256 public constant FOO = 1; + uint256 public immutable BAR; + uint256 public baz; + + constructor() { + BAR = 2; + } + + function increment() public { + baz++; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = prj + .root() + .join("docs/src/src/CounterMixedVariables.sol/contract.CounterMixedVariables.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section and the State Variables section exist + assert!(content.contains("## Constants"), "Should have Constants section"); + assert!(content.contains("## State Variables"), "Should have State Variables section"); + + // Get the position of the Constants, State Variables, and Functions sections + let constants_section_pos = content.find("## Constants").unwrap(); + let state_variables_section_pos = content.find("## State Variables").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Validate that the sections are in the correct order + assert!( + constants_section_pos < state_variables_section_pos + && state_variables_section_pos < functions_section_pos, + "Constants section should be before State Variables section and before Functions section" + ); + + // Check that Constants section contains the constant + assert!(content.contains("### FOO"), "Should have FOO constant"); + let foo_constant_pos = content.find("## FOO").unwrap(); + assert!( + foo_constant_pos > constants_section_pos + && foo_constant_pos < state_variables_section_pos, + "FOO constant should be after Constants section and before State Variables section" + ); + + // Check that Constants section contains the immutable + assert!(content.contains("### BAR"), "Should have BAR immutable"); + let bar_immutable_pos = content.find("## BAR").unwrap(); + assert!( + bar_immutable_pos > constants_section_pos + && bar_immutable_pos < state_variables_section_pos, + "BAR immutable should be after Constants section and before State Variables section" + ); + + // Check that State Variables section contains the state variable + assert!(content.contains("### baz"), "Should have baz state variable"); + let baz_state_variable_pos = content.find("## baz").unwrap(); + assert!( + baz_state_variable_pos > state_variables_section_pos + && baz_state_variable_pos < functions_section_pos, + "baz state variable should be after State Variables section and before Functions section" + ); + } +); diff --git a/crates/forge/tests/cli/eip712.rs b/crates/forge/tests/cli/eip712.rs index 1340c86565389..919f93366b23b 100644 --- a/crates/forge/tests/cli/eip712.rs +++ b/crates/forge/tests/cli/eip712.rs @@ -284,8 +284,7 @@ contract Eip712Test is DSTest { [r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] -Compiler run successful! - +... Ran 1 test for src/Eip712Cheat.sol:Eip712Test [PASS] testEip712HashType() ([GAS]) Logs: diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index b4fd3d908133d..fbd84739635c5 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -64,7 +64,17 @@ fn sablier_v2_core() { // #[test] fn solady() { - ExtTester::new("Vectorized", "solady", "cbcfe0009477aa329574f17e8db0a05703bb8bdd").run(); + let mut tester = + ExtTester::new("Vectorized", "solady", "cbcfe0009477aa329574f17e8db0a05703bb8bdd"); + + // This test expects the mover contract created via CREATE2 to be selfdestructed within the + // same transaction. In isolation mode, each top-level call runs as a separate transaction + // context, so the selfdestruct doesn't clear the code as expected by the test. + if cfg!(feature = "isolate-by-default") { + tester = tester.args(["--nmt", "testSafeMoveETHViaMover"]); + } + + tester.run(); } // diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index f6b3f15f3fd71..087b1fc9491ac 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -190,7 +190,7 @@ Suite result: FAILED. 0 passed; 8 failed; 0 skipped; [ELAPSED] "#]]); }); -forgetest!(expect_emit_tests_should_fail, |prj, cmd| { +forgetest!(flaky_expect_emit_tests_should_fail, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); @@ -239,7 +239,7 @@ Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] ); }); -forgetest!(expect_emit_params_tests_should_fail, |prj, cmd| { +forgetest!(flaky_expect_emit_params_tests_should_fail, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); prj.update_config(|config| { diff --git a/crates/forge/tests/cli/fmt.rs b/crates/forge/tests/cli/fmt.rs index c2deb78b00d61..0865f381854de 100644 --- a/crates/forge/tests/cli/fmt.rs +++ b/crates/forge/tests/cli/fmt.rs @@ -3,7 +3,7 @@ use foundry_test_utils::{forgetest, forgetest_init}; const UNFORMATTED: &str = r#"// SPDX-License-Identifier: MIT -pragma solidity =0.8.30 ; +pragma solidity =0.8.33 ; contract Test { uint256 public value ; @@ -13,7 +13,7 @@ contract Test { }"#; const FORMATTED: &str = r#"// SPDX-License-Identifier: MIT -pragma solidity =0.8.30; +pragma solidity =0.8.33; contract Test { uint256 public value; @@ -88,8 +88,8 @@ forgetest!(fmt_check_mode_stdin, |_prj, cmd| { cmd.assert_failure().stderr_eq("").stdout_eq(str![[r#" Diff in stdin: 1 1 | // SPDX-License-Identifier: MIT -2 |-pragma solidity =0.8.30 ; - 2 |+pragma solidity =0.8.30; +2 |-pragma solidity =0.8.33 ; + 2 |+pragma solidity =0.8.33; ... 4 |-contract Test { 5 |- uint256 public value ; diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index f707de279d0e8..5b91c1bf9bcf6 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,5 +1,5 @@ use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; -use foundry_config::{DenyLevel, LintSeverity, LinterConfig}; +use foundry_config::{DenyLevel, LintSeverity, LinterConfig, SolidityErrorCode}; mod geiger; @@ -128,12 +128,12 @@ forgetest!(can_use_config, |prj, cmd| { }); cmd.arg("lint").assert_success().stderr_eq(str![[r#" warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - [FILE]:16:9 - | -16 | (1 / 2) * 3; - | ^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply "#]]); @@ -155,12 +155,12 @@ forgetest!(can_use_config_ignore, |prj, cmd| { }); cmd.arg("lint").assert_success().stderr_eq(str![[r#" note[mixed-case-function]: function names should use mixedCase - [FILE]:9:14 - | -9 | function functionMIXEDCaseInfo() public {} - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `functionMixedCaseInfo` - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + [FILE]:9:14 + │ +9 │ function functionMIXEDCaseInfo() public {} + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function "#]]); @@ -211,12 +211,12 @@ forgetest!(can_override_config_severity, |prj, cmd| { }); cmd.arg("lint").args(["--severity", "info"]).assert_success().stderr_eq(str![[r#" note[mixed-case-function]: function names should use mixedCase - [FILE]:9:14 - | -9 | function functionMIXEDCaseInfo() public {} - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `functionMixedCaseInfo` - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + [FILE]:9:14 + │ +9 │ function functionMIXEDCaseInfo() public {} + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function "#]]); @@ -238,12 +238,12 @@ forgetest!(can_override_config_path, |prj, cmd| { }); cmd.arg("lint").arg("src/ContractWithLints.sol").assert_success().stderr_eq(str![[r#" warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - [FILE]:16:9 - | -16 | (1 / 2) * 3; - | ^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply "#]]); @@ -266,12 +266,12 @@ forgetest!(can_override_config_lint, |prj, cmd| { cmd.arg("lint").args(["--only-lint", "incorrect-shift"]).assert_success().stderr_eq(str![[ r#" warning[incorrect-shift]: the order of args in a shift operation is incorrect - [FILE]:13:26 - | -13 | uint256 result = 8 >> localValue; - | ^^^^^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + [FILE]:13:26 + │ +13 │ uint256 result = 8 >> localValue; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift "# @@ -295,12 +295,12 @@ forgetest!(build_runs_linter_by_default, |prj, cmd| { // Run forge build and expect linting output before compilation cmd.arg("build").assert_success().stderr_eq(str![[r#" warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - [FILE]:16:9 - | -16 | (1 / 2) * 3; - | ^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply "#]]).stdout_eq(str![[r#" @@ -456,12 +456,12 @@ forgetest!(can_use_only_lint_with_multilint_passes, |prj, cmd| { prj.add_source("OnlyImports", ONLY_IMPORTS); cmd.arg("lint").args(["--only-lint", "unused-import"]).assert_success().stderr_eq(str![[r#" note[unused-import]: unused imports should be removed - [FILE]:8:10 - | -8 | import { _PascalCaseInfo } from "./ContractWithLints.sol"; - | ^^^^^^^^^^^^^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + [FILE]:8:10 + │ +8 │ import { _PascalCaseInfo } from "./ContractWithLints.sol"; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import "#]]); @@ -488,12 +488,12 @@ note[mixed-case-variable]: mutable variables should use mixedCase let args = ["build", "src/CounterBWithLints.sol"]; cmd.forge_fuse().args(args).assert_success().stderr_eq(str![[r#" note[mixed-case-variable]: mutable variables should use mixedCase - [FILE]:6:20 - | -6 | uint256 public CounterB_Fail_Lint; - | ^^^^^^^^^^^^^^^^^^ help: consider using: `counterBFailLint` - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + [FILE]:6:20 + │ +6 │ uint256 public CounterB_Fail_Lint; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterBFailLint` + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable "#]]); @@ -654,7 +654,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { "rendered": null } ], - "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\n --> src/UnwrappedModifierTest.sol:8:13\n |\n 8 | / modifier onlyOwner() {\n 9 | | require(isOwner[msg.sender], \"Not owner\");\n10 | | require(msg.sender != address(0), \"Zero address\");\n11 | | _;\n12 | | }\n | |_____________^\n |\nhelp: wrap modifier logic to reduce code size\n |\n 8 ~ modifier onlyOwner() {\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n |\n = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic\n" + "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" } "#]], ); @@ -735,8 +735,7 @@ Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. #[tokio::test] async fn ensure_lint_rule_docs() { - const FOUNDRY_BOOK_LINT_PAGE_URL: &str = - "https://book.getfoundry.sh/reference/forge/forge-lint"; + const FOUNDRY_BOOK_LINT_PAGE_URL: &str = "https://book.getfoundry.sh/forge/linting"; // Fetch the content of the lint reference let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { @@ -762,8 +761,11 @@ async fn ensure_lint_rule_docs() { // Ensure no missing lints let mut missing_lints = Vec::new(); for lint in REGISTERED_LINTS { - let selector = format!("#{}", lint.id()); - if !content.contains(&selector) { + let selector = lint.id().to_lowercase(); + let selector_with_space = selector.replace("-", " "); + if !content.to_lowercase().contains(&selector) + && !content.to_lowercase().contains(&selector_with_space) + { missing_lints.push(lint.id()); } } @@ -787,6 +789,51 @@ fn ensure_no_privileged_lint_id() { } } +// +forgetest!(dependency_warnings_do_not_affect_lint_exit_code, |prj, cmd| { + // Library with code that triggers a solc warning (unused local variable) + const LIB_WITH_WARNING: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library LibWithWarning { + function foo() internal pure returns (uint256) { + uint256 unusedVar = 42; + return 1; + } +} +"#; + + // Clean contract that imports the library but has no lint issues + const CLEAN_CONTRACT: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibWithWarning } from "../lib/LibWithWarning.sol"; + +contract CleanContract { + function bar() public pure returns (uint256) { + return LibWithWarning.foo(); + } +} +"#; + + prj.add_lib("LibWithWarning", LIB_WITH_WARNING); + prj.add_source("CleanContract", CLEAN_CONTRACT); + + // Ignore the solc warning so compilation succeeds, but it still gets counted in diagnostics + prj.update_config(|config| { + config.ignored_error_codes = vec![SolidityErrorCode::UnusedLocalVariable]; + }); + + // Clear cache to force recompilation during lint + prj.clear_cache(); + + // Lint with deny = notes via CLI flag. + // Should succeed because the linter only counts lint diagnostics, not build-phase warnings. + cmd.args(["lint", "-D", "notes"]).assert_success(); +}); + forgetest!(skips_linting_for_old_solidity_versions, |prj, cmd| { const OLD_CONTRACT: &str = r#" // SPDX-License-Identifier: MIT @@ -813,14 +860,14 @@ contract OldContract { // Run forge build - should SUCCEED without linting cmd.arg("build").assert_success().stderr_eq(str![[ - r#"Warning: unable to lint. Solar only supports Solidity versions prior to 0.8.0 + r#"Warning: unable to lint. Solar only supports Solidity versions >=0.8.0 "# ]]); // Run forge lint - should FAIL cmd.forge_fuse().arg("lint").assert_failure().stderr_eq(str![[ - r#"Error: unable to lint. Solar only supports Solidity versions prior to 0.8.0 + r#"Error: unable to lint. Solar only supports Solidity versions >=0.8.0 "# ]]); diff --git a/crates/forge/tests/cli/lint/geiger.rs b/crates/forge/tests/cli/lint/geiger.rs index 9b768f21ec239..faecfb212fb90 100644 --- a/crates/forge/tests/cli/lint/geiger.rs +++ b/crates/forge/tests/cli/lint/geiger.rs @@ -16,12 +16,12 @@ forgetest_init!(call, |prj, cmd| { cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" ... note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations - [FILE]:9:20 - | -9 | vm.ffi(inputs); - | ^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + [FILE]:9:20 + │ +9 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -47,12 +47,12 @@ forgetest_init!(assignment, |prj, cmd| { cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" ... note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations - [FILE]:9:41 - | -9 | bytes memory stuff = vm.ffi(inputs); - | ^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + [FILE]:9:41 + │ +9 │ bytes memory stuff = vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -79,28 +79,28 @@ forgetest_init!(exit_code, |prj, cmd| { cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" ... note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations - [FILE]:9:20 - | -9 | vm.ffi(inputs); - | ^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + [FILE]:9:20 + │ +9 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations - [FILE]:10:20 - | -10 | vm.ffi(inputs); - | ^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + [FILE]:10:20 + │ +10 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations - [FILE]:11:20 - | -11 | vm.ffi(inputs); - | ^^^ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + [FILE]:11:20 + │ +11 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode Error: aborting due to 3 linter note(s) ... diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index d0aa25f918f76..7f3dc7b9cd7a5 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1945,7 +1945,7 @@ contract SimpleScript is Script { ]) .assert_success() .stdout_eq(str![[r#" -{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} {"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} @@ -3144,7 +3144,7 @@ contract CounterScript is Script { Compiler run successful! Traces: [..] → new CounterScript@[..] - └─ ← [Return] 2200 bytes of code + └─ ← [Return] 2162 bytes of code [..] CounterScript::setUp() └─ ← [Stop] @@ -3298,7 +3298,7 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. "#]]); }); -forgetest_async!(can_deploy_with_broadcast_in_setup, |prj, cmd| { +forgetest_async!(flaky_can_deploy_with_broadcast_in_setup, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); prj.add_script( "Deploy.s.sol", @@ -3422,3 +3422,72 @@ forgetest_async!(can_execute_script_with_createx_and_via_ir, |prj, cmd| { ]) .assert_success(); }); + +forgetest_async!(script_can_run_with_live_logs_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; + +contract Foo is Script { + function setUp() pure public { + console.log("Setup"); + } + + function run() pure public { + console.log("Run %d", uint256(1)); + } +} + "#, + ); + + cmd.forge_fuse() + .args(["script", "script/Foo.s.sol", "--live-logs"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Run 1 +Script ran successfully. +[GAS] + +"#]]); +}); + +forgetest_async!(script_can_run_with_live_logs_config, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.update_config(|config| { + config.live_logs = true; + }); + + prj.add_script( + "Foo.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; + +contract Foo is Script { + function setUp() pure public { + console.log("Setup"); + } + + function run() pure public { + console.log("Run %d", uint256(1)); + } +} + "#, + ); + + cmd.forge_fuse().args(["script", "script/Foo.s.sol"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Run 1 +Script ran successfully. +[GAS] + +"#]]); +}); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index 8d9fd6811b5ec..b80805cbecfc4 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 30); +const LATEST_SOLC: Version = Version::new(0, 8, 34); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index d908c2d5542ff..fefefb30d9b15 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -88,7 +88,7 @@ forgetest_init!(test_fuzz_timeout, |prj, cmd| { import {Test} from "forge-std/Test.sol"; contract FuzzTimeoutTest is Test { - /// forge-config: default.fuzz.max-test-rejects = 50000 + /// forge-config: default.fuzz.max-test-rejects = 0 /// forge-config: default.fuzz.timeout = 1 function test_fuzz_bound(uint256 a) public pure { vm.assume(a == 0); @@ -97,7 +97,7 @@ contract FuzzTimeoutTest is Test { "#, ); - cmd.args(["test"]).assert_success().stdout_eq(str![[r#" + cmd.args(["test", "-j2"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! @@ -186,7 +186,7 @@ contract Counter { ); // Tests should fail as revert happens in cheatcode (assert) and test (require) contract. - cmd.assert_failure().stdout_eq(str![[r#" + cmd.args(["-j1"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! @@ -248,7 +248,7 @@ contract CounterTest is Test { "#, ); // Tests should fail and record counterexample with value 200. - cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" + cmd.args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" ... Failing tests: Encountered 1 failing test in test/Counter.t.sol:CounterTest @@ -324,7 +324,7 @@ contract CounterTest is Test { "#, ); // Test should fail with replayed counterexample 200 (0 runs). - cmd.forge_fuse().args(["test"]).assert_failure().stdout_eq(str![[r#" + cmd.forge_fuse().args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" ... Failing tests: Encountered 1 failing test in test/Counter.t.sol:CounterTest @@ -384,6 +384,8 @@ Encountered a total of 1 failing tests, 2 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -525,7 +527,7 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] assert.stdout_eq(expected.clone()); }; - cmd.arg("test"); + cmd.args(["test", "-j1"]); // Run several times, asserting that the failure persists and is the same. for _ in 0..3 { @@ -775,12 +777,12 @@ forgetest_init!(should_fuzz_literals, |prj, cmd| { ); // Helper to create expected output for a test failure - let expected_fail = |test_name: &str, type_sig: &str, value: &str, runs: u32| -> String { + let expected_fail = |test_name: &str, type_sig: &str, value: &str| -> String { format!( r#"No files changed, compilation skipped Ran 1 test for test/MagicFuzz.t.sol:MagicTest -[FAIL: panic: assertion failed (0x01); counterexample: calldata=[..] args=[{value}]] {test_name}({type_sig}) (runs: {runs}, [AVG_GAS]) +[FAIL: panic: assertion failed (0x01); counterexample: calldata=[..] args=[{value}]] {test_name}({type_sig}) (runs: [..], [AVG_GAS]) [..] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) @@ -797,41 +799,39 @@ Encountered a total of 1 failing tests, 0 tests succeeded let mut test_literal = |seed: u32, test_name: &'static str, type_sig: &'static str, - expected_value: &'static str, - expected_runs: u32| { + expected_value: &'static str| { // the fuzzer is UNABLE to find a breaking input (fast) when NOT seeding from the AST prj.update_config(|config| { config.fuzz.runs = 100; config.fuzz.dictionary.max_fuzz_dictionary_literals = 0; config.fuzz.seed = Some(U256::from(seed)); }); - cmd.forge_fuse().args(["test", "--match-test", test_name]).assert_success(); + cmd.forge_fuse().args(["test", "--match-test", test_name, "-j1"]).assert_success(); // the fuzzer is ABLE to find a breaking input when seeding from the AST prj.update_config(|config| { config.fuzz.dictionary.max_fuzz_dictionary_literals = 10_000; }); - let expected_output = expected_fail(test_name, type_sig, expected_value, expected_runs); + let expected_output = expected_fail(test_name, type_sig, expected_value); cmd.forge_fuse() - .args(["test", "--match-test", test_name]) + .args(["test", "--match-test", test_name, "-j1"]) .assert_failure() .stdout_eq(expected_output); }; - test_literal(100, "testFuzz_Addr", "address", "0x6B175474E89094C44Da98b954EedeAC495271d0F", 28); - test_literal(200, "testFuzz_Number", "uint64", "1122334455 [1.122e9]", 5); - test_literal(300, "testFuzz_Integer", "int32", "-777", 0); + test_literal(100, "testFuzz_Addr", "address", "0x6B175474E89094C44Da98b954EedeAC495271d0F"); + test_literal(200, "testFuzz_Number", "uint64", "1122334455 [1.122e9]"); + test_literal(300, "testFuzz_Integer", "int32", "-777"); test_literal( 400, "testFuzz_Word", "bytes32", "0x6162636431323334000000000000000000000000000000000000000000000000", /* bytes32("abcd1234") */ - 7, ); - test_literal(500, "testFuzz_BytesFromHex", "bytes", "0xdeadbeef", 5); - test_literal(600, "testFuzz_String", "string", "\"xyzzy\"", 35); - test_literal(999, "testFuzz_BytesFromString", "bytes", "0x78797a7a79", 19); // abi.encodePacked("xyzzy") + test_literal(500, "testFuzz_BytesFromHex", "bytes", "0xdeadbeef"); + test_literal(600, "testFuzz_String", "string", "\"xyzzy\""); + test_literal(999, "testFuzz_BytesFromString", "bytes", "0x78797a7a79"); // abi.encodePacked("xyzzy") }); // Tests that `vm.randomUint()` produces different values across fuzz runs. diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs index 09cebfd41b9ea..eb28ca8c15ca0 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/common.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -98,6 +98,8 @@ Encountered a total of 2 failing tests, 1 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -115,8 +117,10 @@ forgetest_init!(invariant_assume, |prj, cmd| { import "forge-std/Test.sol"; contract Handler is Test { + uint256 public count; function doSomething(uint256 param) public { vm.assume(param == 0); + count++; } } @@ -135,13 +139,7 @@ contract InvariantAssume is Test { assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] -Compiler run successful with warnings: -Warning (2018): Function state mutability can be restricted to pure - [FILE]:7:5: - | -7 | function doSomething(uint256 param) public { - | ^ (Relevant source part starts here and spans across multiple lines). - +Compiler run successful! Ran 1 test for test/InvariantAssume.t.sol:InvariantAssume [PASS] invariant_dummy() ([RUNS]) @@ -179,6 +177,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -308,6 +308,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -377,6 +379,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -392,10 +396,12 @@ forgetest_init!(invariant_excluded_senders, |prj, cmd| { import "forge-std/Test.sol"; contract InvariantSenders { + uint256 public count; function checkSender() external { require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); + count++; } } @@ -414,13 +420,7 @@ contract InvariantExcludedSendersTest is Test { assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] -Compiler run successful with warnings: -Warning (2018): Function state mutability can be restricted to view - [FILE]:7:5: - | -7 | function checkSender() external { - | ^ (Relevant source part starts here and spans across multiple lines). - +Compiler run successful! Ran 1 test for test/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest [PASS] invariant_check_sender() ([RUNS]) @@ -546,6 +546,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -632,6 +634,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -704,6 +708,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -787,6 +793,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. @@ -896,6 +904,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -988,6 +998,122 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// Tests that call_override detects the classic DAO-style reentrancy vulnerability +// in EtherStore where balances are updated AFTER the external call. +forgetest!(invariant_reentrancy_ether_store, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 15; + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); + + prj.add_test( + "InvariantReentrancyEtherStore.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// Classic reentrancy-vulnerable contract +contract EtherStore { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw() public { + uint256 bal = balances[msg.sender]; + require(bal > 0); + // BUG: External call before state update + (bool sent,) = msg.sender.call{value: bal}(""); + require(sent, "Failed to send Ether"); + balances[msg.sender] = 0; + } +} + +contract InvariantReentrancyEtherStore is Test { + EtherStore store; + address attacker; + + function setUp() public { + store = new EtherStore(); + attacker = address(0x1337); + + vm.deal(address(this), 10 ether); + store.deposit{value: 5 ether}(); + + // Attacker gets 2 ether, deposits 1 ether, keeps 1 ether in wallet. + // After withdrawing their deposit, attacker wallet balance cannot exceed 2 ether. + vm.deal(attacker, 2 ether); + vm.prank(attacker); + store.deposit{value: 1 ether}(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(store); + return targets; + } + + function targetSenders() public view returns (address[] memory) { + address[] memory senders = new address[](1); + senders[0] = attacker; + return senders; + } + + function targetSelectors() public view returns (FuzzSelector[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = EtherStore.withdraw.selector; + FuzzSelector[] memory targets = new FuzzSelector[](1); + targets[0] = FuzzSelector(address(store), selectors); + return targets; + } + + // Attacker should never have more than 2 ether (1 kept + 1 withdrawn) + function invariantSolvency() public view { + require(attacker.balance <= 2 ether, "reentrancy: attacker wallet > 2 ether"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 1 test for test/InvariantReentrancyEtherStore.t.sol:InvariantReentrancyEtherStore +[FAIL: reentrancy: attacker wallet > 2 ether] + [SEQUENCE] + invariantSolvency() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantReentrancyEtherStore.t.sol:InvariantReentrancyEtherStore +[FAIL: reentrancy: attacker wallet > 2 ether] + [SEQUENCE] + invariantSolvency() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1026,7 +1152,7 @@ contract InvariantRollForkBlockTest is Test { /// forge-config: default.invariant.runs = 2 /// forge-config: default.invariant.depth = 4 - function invariant_fork_handler_block() public { + function invariant_fork_handler_block() public view { require(block.number < 19812634, "too many blocks mined"); } } @@ -1040,7 +1166,7 @@ contract InvariantRollForkStateTest is Test { } /// forge-config: default.invariant.runs = 1 - function invariant_fork_handler_state() public { + function invariant_fork_handler_state() public view { require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); } } @@ -1048,24 +1174,47 @@ contract InvariantRollForkStateTest is Test { ); assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" -... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkBlockTest +[FAIL: too many blocks mined] + [SEQUENCE] + invariant_fork_handler_block() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkStateTest +[FAIL: wrong supply] + [SEQUENCE] + invariant_fork_handler_state() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) Failing tests: Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkBlockTest [FAIL: too many blocks mined] -... + [SEQUENCE] invariant_fork_handler_block() ([RUNS]) Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkStateTest [FAIL: wrong supply] -... + [SEQUENCE] invariant_fork_handler_state() ([RUNS]) Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1165,6 +1314,8 @@ Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1464,6 +1615,8 @@ Encountered a total of 2 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 2 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1603,14 +1756,14 @@ Compiler run successful! Ran 1 test for test/HandlerWarpAndRoll.t.sol:HandlerWarpAndRoll [FAIL: max timestamp] - [Sequence] (original: 7, shrunk: 7) + [Sequence] (original: 5, shrunk: 5) sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=6280 roll=21461 calldata=setNumber(uint256) args=[200000 [2e5]] sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=92060 roll=51816 calldata=setNumber(uint256) args=[0] sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=198040 roll=60259 calldata=increment() args=[] sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=20609 roll=27086 calldata=setNumber(uint256) args=[26717227324157985679793128079000084308648530834088529513797156275625002 [2.671e70]] sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=409368 roll=24864 calldata=increment() args=[] - sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=218105 roll=17834 calldata=setNumber(uint256) args=[24752675372815722001736610830 [2.475e28]] - sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=579093 roll=23244 calldata=increment() args=[] + invariant_handler() (runs: 0, calls: 0, reverts: 1) + ... "#]]); @@ -1670,3 +1823,182 @@ Logs: ... "#]]); }); + +// Test optimization mode for invariant testing. +// When an invariant function returns int256, it becomes an optimization target. +// The fuzzer maximizes the return value instead of checking for failures. +forgetest!(invariant_optimization_mode, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantOptimize.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract OptimizationHandler { + int256 public value; + + // Each call adds exactly 10 to value + function increment() external { + value += 10; + } +} + +contract InvariantOptimizeTest is Test { + OptimizationHandler handler; + + function setUp() public { + handler = new OptimizationHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.increment.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + /// @notice Optimization mode: returns int256 to maximize. + /// With depth=5 and only increment(), max value should be 50. + function invariant_optimize_value() public view returns (int256) { + return handler.value(); + } +} +"#, + ); + + // Optimization mode: best value shown first, should reach 50 (5 calls * 10 each) + // Shows [Best sequence] with calls in output + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] [..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] + invariant_optimize_value() (best: 50, runs: 1, calls: 5) +... +"#]]); +}); + +// Test that optimization mode works with negative values (finding max of negative range). +forgetest!(invariant_optimization_negative_values, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantOptimizeNegative.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract NegativeHandler { + int256 public value = -100; + + // Each call adds exactly 25 to value + function increase() external { + value += 25; + } +} + +contract InvariantOptimizeNegativeTest is Test { + NegativeHandler handler; + + function setUp() public { + handler = new NegativeHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.increase.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 4 + /// Starting at -100, 4 calls of +25 each = -100 + 100 = 0 + function invariant_optimize_negative() public view returns (int256) { + return handler.value(); + } +} +"#, + ); + + // Optimization should reach 0: starting at -100, 4 calls * +25 = 0 + // Shows [Best sequence] with calls in output + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] [..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] + invariant_optimize_negative() (best: 0, runs: 1, calls: 4) +... +"#]]); +}); + +// Test optimization mode with time-dependent logic using warp and fixed seed for reproducibility. +// This test ensures warp values are correctly accumulated during shrinking. +forgetest_init!(invariant_optimization_with_warp, |prj, cmd| { + prj.add_test( + "InvariantOptimizeWarp.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantOptimizeWarpTest is Test { + int256 public maxValue; + + function setUp() public { + targetContract(address(this)); + } + + // Simulates time-dependent pricing with warp. Higher timestamp = higher value. + function updateValue(uint256 multiplier) public { + if (multiplier == 0 || multiplier > 100) revert(); + // Value depends on block.timestamp which can be warped + int256 newValue = int256(block.timestamp * multiplier / 1000); + if (newValue > maxValue) { + maxValue = newValue; + } + } + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.max_time_delay = 604800 + function invariant_optimize_max_value() public view returns (int256) { + return maxValue; + } +} +"#, + ); + + // Use fixed seed for deterministic output. The optimizer finds sequences that + // maximize value through time manipulation (warp). Shrinking reduces to 1 call. + cmd.args(["test", "-vvv", "--fuzz-seed", "12345"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] (original: 9, shrunk: 1) + sender=0x0000000000000000000000000000000000000637 addr=[test/InvariantOptimizeWarp.t.sol:InvariantOptimizeWarpTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 warp=3249628 calldata=updateValue(uint256) args=[100] + invariant_optimize_max_value() (best: 324962, runs: 10, calls: 150) +... +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/invariant/mod.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs index 6f2f93363e293..3cbeeb4a46db7 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/mod.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -431,6 +431,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -458,6 +460,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -481,6 +485,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); }); @@ -991,3 +997,183 @@ Ran 3 test suites [ELAPSED]: 6 tests passed, 0 failed, 0 skipped (6 total tests) prj.root().join("fuzz_corpus").join("Counter2Test").join("testFuzz_SetNumber").exists() ); }); + +// Tests that check_interval=0 only asserts on the last call of each run. +forgetest_init!(check_interval_zero_only_checks_last_call, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 5; + config.invariant.depth = 10; + config.invariant.check_interval = 0; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // This invariant would fail on intermediate calls (counter 1-9) but passes on call 10 + // With check_interval=0, only the last call is checked, so if depth=10 and counter=10 + // at the end, this should pass even though intermediate states violated the invariant. + function invariant_counter_multiple_of_depth() public view { + // Only passes when counter is 0 or 10 (depth). Fails for 1-9. + require(handler.counter() == 0 || handler.counter() == 10, "not multiple of depth"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_counter"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter_multiple_of_depth() (runs: 5, calls: 50, reverts: 0) +... +"#]]); +}); + +// Tests that check_interval=1 (default) asserts after every call. +forgetest_init!(check_interval_one_checks_every_call, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.check_interval = 1; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // This invariant fails as soon as counter > 5. + // With check_interval=1, it should fail on call 6. + function invariant_counter_le_five() public view { + require(handler.counter() <= 5, "counter > 5"); + } +} + "#, + ); + + assert_invariant(cmd.args(["test", "--mt", "invariant_counter"])).failure().stdout_eq(str![[ + r#" +... +[FAIL: counter > 5] + [SEQUENCE] +... +"# + ]]); +}); + +// Tests that check_interval=N checks every N calls AND always on the last call. +forgetest_init!(check_interval_n_checks_every_n_calls, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 20; + config.invariant.check_interval = 5; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // With check_interval=5 and depth=20, invariant is checked at calls 5,10,15,20. + // This passes because 5,10,15,20 are all multiples of 5. + function invariant_counter_multiple_of_five() public view { + require(handler.counter() % 5 == 0, "not multiple of 5"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_counter"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter_multiple_of_five() (runs: 1, calls: 20, reverts: 0) +... +"#]]); +}); + +// Tests check_interval via inline config annotation. +forgetest_init!(check_interval_inline_config, |prj, cmd| { + prj.add_test( + "CheckIntervalInlineTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalInlineTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.check_interval = 0 + function invariant_only_last_checked() public view { + // Only passes when counter is 0 or 10. With check_interval=0, only last call is checked. + require(handler.counter() == 0 || handler.counter() == 10, "not at boundary"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_only_last_checked"]).assert_success().stdout_eq(str![[ + r#" +... +[PASS] invariant_only_last_checked() (runs: 1, calls: 10, reverts: 0) +... +"# + ]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/invariant/target.rs b/crates/forge/tests/cli/test_cmd/invariant/target.rs index 41fadd3cb59d3..a2b766926bb09 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/target.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/target.rs @@ -627,6 +627,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // Test TargetSelectors @@ -670,6 +672,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]], ); @@ -715,6 +719,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); // Test TargetArtifactSelectors @@ -763,6 +769,8 @@ Encountered a total of 1 failing tests, 1 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/logs.rs b/crates/forge/tests/cli/test_cmd/logs.rs index c9d1e39dc6155..0dba7fa8baa7c 100644 --- a/crates/forge/tests/cli/test_cmd/logs.rs +++ b/crates/forge/tests/cli/test_cmd/logs.rs @@ -752,3 +752,145 @@ Ran 1 test suite [ELAPSED]: 52 tests passed, 0 failed, 0 skipped (52 total tests "#]]); }); + +forgetest_init!(test_can_run_with_live_logs_flag, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } +} + "#, + ); + + cmd.forge_fuse() + .args(["test", "--live-logs", "--match-test", "test1"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test 1 + +Ran 1 test for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(test_can_run_with_live_logs_config, |prj, cmd| { + prj.update_config(|config| { + config.live_logs = true; + }); + + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } +} + "#, + ); + + cmd.forge_fuse().args(["test", "--match-test", "test1"]).assert_success().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test 1 + +Ran 1 test for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"# + ]]); +}); + +forgetest_init!(test_can_run_with_live_logs_flag_race_condition, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } + + function test2() pure public { + console.log("Test 2"); + } +} + "#, + ); + + // Two threads. Inconsistent printing order. + cmd.forge_fuse().args(["test", "--live-logs", "--threads", "2"]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test [..] +Test [..] + +Ran 2 tests for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +[PASS] test2() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]], + ); + + // Single thread. Deterministic printing order. + + for _ in 0..10 { + cmd.forge_fuse() + .args(["test", "--live-logs", "--threads", "1"]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped +Setup +Test 1 +Test 2 + +Ran 2 tests for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +[PASS] test2() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + } +}); diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 0abffff57c4a8..8a2f0e2e7fafe 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -902,19 +902,21 @@ contract CounterTest is Test { Compiler run successful! Ran 1 test for test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]] testAddOne(uint256) (runs: 27, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]] testAddOne(uint256) (runs: 27, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS]) Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -966,6 +968,8 @@ Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -1420,10 +1424,10 @@ contract SimpleContractTest is Test { ... Traces: [..] SimpleContractTest::test() - ├─ [370554] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f - │ └─ ← [Return] 1737 bytes of code - ├─ [2511] SimpleContract::setStr("new value") - │ ├─ [1588] SimpleContract::_setStr("new value") + ├─ [..] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] bytes of code + ├─ [..] SimpleContract::setStr("new value") + │ ├─ [..] SimpleContract::_setStr("new value") │ │ └─ ← "initial value" │ └─ ← [Stop] └─ ← [Stop] @@ -2719,7 +2723,7 @@ Ran 8 tests for src/AssumeNoRevertTest.t.sol:ReverterTest "#]]); }); -forgetest_async!(can_get_broadcast_txs, |prj, cmd| { +forgetest_async!(flaky_can_get_broadcast_txs, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); let (_api, handle) = spawn(NodeConfig::test().silent()).await; @@ -2860,7 +2864,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { 31337 ); - assertEq(deployedAddress, address(0xD32c10E38A626Db0b0978B1A5828eb2957665668)); + assertGt(uint160(deployedAddress), 0); } function test_getDeployments() public { @@ -2870,8 +2874,10 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { ); assertEq(deployments.length, 2); - assertEq(deployments[0], address(0xD32c10E38A626Db0b0978B1A5828eb2957665668)); // Create2 address - latest deployment - assertEq(deployments[1], address(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Create address - oldest deployment + // Verify valid addresses returned and they're different (CREATE vs CREATE2) + assertGt(uint160(deployments[0]), 0); + assertGt(uint160(deployments[1]), 0); + assertTrue(deployments[0] != deployments[1]); } } "#; @@ -3400,7 +3406,7 @@ Traces: }); // -forgetest_init!(can_upload_selectors_with_path, |prj, cmd| { +forgetest_init!(flaky_can_upload_selectors_with_path, |prj, cmd| { prj.initialize_default_contracts(); prj.add_source( "CounterV1.sol", @@ -3865,7 +3871,7 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) }); // -forgetest_init!(should_not_panic_on_cool, |prj, cmd| { +forgetest_init!(flaky_should_not_panic_on_cool, |prj, cmd| { prj.initialize_default_contracts(); prj.add_test( "Counter.t.sol", @@ -4130,7 +4136,7 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // This test is a copy of `error_event_decode_with_cache` in cast/tests/cli/selectors.rs // but it uses `forge build` to check that the project selectors are cached by default. -forgetest_init!(build_with_selectors_cache, |prj, cmd| { +forgetest_init!(flaky_build_with_selectors_cache, |prj, cmd| { prj.initialize_default_contracts(); prj.add_source( "LocalProjectContract", @@ -4411,3 +4417,46 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test "#]]); }); + +forgetest_init!(zero_runs, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "ZeroRuns.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Handler is Test { + function doSomething(uint256 param) public { + revert("unreachable"); + } +} + +contract ZeroRuns is Test { + Handler handler = new Handler(); + + /// forge-config: default.fuzz.runs = 0 + function test_fuzzZeroRuns(uint256 x) public { + revert("unreachable"); + } + + /// forge-config: default.invariant.runs = 0 + function invariant_zeroRuns() public {} + + /// forge-config: default.invariant.depth = 0 + function invariant_zeroDepth() public {} +} +"#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 tests for test/ZeroRuns.t.sol:ZeroRuns +[PASS] invariant_zeroDepth() (runs: 256, calls: 0, reverts: 0) +[PASS] invariant_zeroRuns() (runs: 0, calls: 0, reverts: 0) +[PASS] test_fuzzZeroRuns(uint256) (runs: 0, [AVG_GAS]) +Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 32c042f32d050..82ec6744a6172 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -63,6 +63,8 @@ Encountered a total of 3 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 3 failed tests +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); @@ -780,3 +782,99 @@ ParserError: Source "Missing.sol" not found: File not found. Searched the follow "#]]); }); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test gas underflow prevention on Cancun (no EIP-7702 gas floor) +forgetest_init!(issue_12803_cancun, |prj, cmd| { + prj.add_test( + "Issue12803.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803Test is Test { + uint a; + function test_negativeGas() public { + vm.pauseGasMetering(); + a = 100; + vm.resumeGasMetering(); + delete a; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[ + r#" +... +Ran 1 test for test/Issue12803.t.sol:Issue12803Test +[PASS] test_negativeGas() (gas: 0) +... +"# + ]]); +}); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test gas underflow prevention on Shanghai (also no EIP-7702 gas floor) +forgetest_init!(issue_12803_shanghai, |prj, cmd| { + prj.add_test( + "Issue12803.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803Test is Test { + uint a; + function test_negativeGas() public { + vm.pauseGasMetering(); + a = 100; + vm.resumeGasMetering(); + delete a; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=shanghai"]).with_no_redact().assert_success().stdout_eq(str![ + [r#" +... +Ran 1 test for test/Issue12803.t.sol:Issue12803Test +[PASS] test_negativeGas() (gas: 0) +... +"#] + ]); +}); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test multiple storage deletions (higher refund) don't cause underflow +forgetest_init!(issue_12803_multiple_deletes, |prj, cmd| { + prj.add_test( + "Issue12803Multi.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803MultiTest is Test { + uint a; + uint b; + uint c; + function test_multipleDeletes() public { + vm.pauseGasMetering(); + a = 100; + b = 200; + c = 300; + vm.resumeGasMetering(); + delete a; + delete b; + delete c; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[ + r#" +... +Ran 1 test for test/Issue12803Multi.t.sol:Issue12803MultiTest +[PASS] test_multipleDeletes() (gas: 0) +... +"# + ]]); +}); diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index b5dbc0919d5c1..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1057,7 +1057,7 @@ Compiling 1 files with [..] }); // Test preprocessing contracts with payable constructor, value and salt named args. -forgetest_init!(preprocess_contracts_with_payable_constructor_and_salt, |prj, cmd| { +forgetest_init!(flaky_preprocess_contracts_with_payable_constructor_and_salt, |prj, cmd| { prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1119,7 +1119,9 @@ contract CounterTest is Test { function test_Increment_In_Counter_With_Salt() public { CounterWithSalt counter = new CounterWithSalt{value: 111, salt: bytes32("preprocess_counter_with_salt")}(1); - assertEq(address(counter), 0x223e63BE3BF01DD04f852d70f1bE217017055f49); + assertGt(uint160(address(counter)), 0); + counter.increment(); + assertEq(counter.number(), 112); } } "#, @@ -1194,7 +1196,7 @@ contract CounterWithSalt { Compiling 1 files with [..] ... [FAIL: assertion failed: 113 != 112] test_Increment_In_Counter() (gas: [..]) -[FAIL: assertion failed: 0x11acEfcD29A1BA964A05C0E7F3901054BEfb17c0 != 0x223e63BE3BF01DD04f852d70f1bE217017055f49] test_Increment_In_Counter_With_Salt() (gas: [..]) +[FAIL: assertion failed: 113 != 112] test_Increment_In_Counter_With_Salt() (gas: [..]) ... "#]]); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 4e0f3c1862715..c8368f324f2c8 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -1,13 +1,16 @@ +use alloy_chains::Chain; use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; use foundry_config::Config; use foundry_test_utils::{ - TestCommand, TestProject, forgetest_async, + TestCommand, TestProject, + etherscan::fetch_etherscan_source_flattened, + forgetest_async, rpc::{next_etherscan_api_key, next_http_archive_rpc_url}, util::OutputExt, }; #[expect(clippy::too_many_arguments)] -fn test_verify_bytecode( +async fn test_verify_bytecode( prj: TestProject, mut cmd: TestCommand, addr: &str, @@ -17,17 +20,15 @@ fn test_verify_bytecode( verifier: &str, verifier_url: &str, expected_matches: (&str, &str), + chain: Chain, ) { let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); - // fetch and flatten source code - let source_code = cmd - .cast_fuse() - .args(["source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) - .assert_success() - .get_output() - .stdout_lossy(); + // fetch and flatten source code using the library directly + let source_code = fetch_etherscan_source_flattened(addr, ðerscan_key, chain) + .await + .expect("failed to fetch source code from etherscan"); prj.add_source(contract_name, &source_code); prj.write_config(config); @@ -65,7 +66,7 @@ fn test_verify_bytecode( } #[expect(clippy::too_many_arguments)] -fn test_verify_bytecode_with_ignore( +async fn test_verify_bytecode_with_ignore( prj: TestProject, mut cmd: TestCommand, addr: &str, @@ -75,26 +76,15 @@ fn test_verify_bytecode_with_ignore( verifier_url: &str, expected_matches: (&str, &str), ignore: &str, - chain: &str, + chain: Chain, ) { let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); - // fetch and flatten source code - let source_code = cmd - .cast_fuse() - .args([ - "source", - addr, - "--flatten", - "--etherscan-api-key", - ðerscan_key, - "--chain", - chain, - ]) - .assert_success() - .get_output() - .stdout_lossy(); + // fetch and flatten source code using the library directly + let source_code = fetch_etherscan_source_flattened(addr, ðerscan_key, chain) + .await + .expect("failed to fetch source code from etherscan"); prj.add_source(contract_name, &source_code); prj.write_config(config); @@ -145,221 +135,197 @@ fn test_verify_bytecode_with_ignore( } } -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_verify_bytecode_no_metadata, - |prj, cmd| { - test_verify_bytecode( - prj, - cmd, - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "SystemConfig", - None, - Config { - evm_version: EvmVersion::London, - optimizer_runs: Some(999999), - optimizer: Some(true), - cbor_metadata: false, - bytecode_hash: BytecodeHash::None, - ..Default::default() - }, - "etherscan", - "https://api.etherscan.io/v2/api?chainid=1", - ("partial", "partial"), - ); - } -); +forgetest_async!(flaky_verify_bytecode_no_metadata, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + None, + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ("partial", "partial"), + Chain::mainnet(), + ) + .await; +}); -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_verify_bytecode_with_metadata, - |prj, cmd| { - test_verify_bytecode( - prj, - cmd, - "0xb8901acb165ed027e32754e0ffe830802919727f", - "L1_ETH_Bridge", - None, - Config { - evm_version: EvmVersion::Paris, - optimizer_runs: Some(50000), - optimizer: Some(true), - ..Default::default() - }, - "etherscan", - "https://api.etherscan.io/v2/api?chainid=1", - ("partial", "partial"), - ); - } -); +forgetest_async!(flaky_verify_bytecode_with_metadata, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xb8901acb165ed027e32754e0ffe830802919727f", + "L1_ETH_Bridge", + None, + Config { + evm_version: EvmVersion::Paris, + optimizer_runs: Some(50000), + optimizer: Some(true), + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ("partial", "partial"), + Chain::mainnet(), + ) + .await; +}); // Test non-CREATE2 deployed contract with blockscout -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_verify_bytecode_with_blockscout, - |prj, cmd| { - test_verify_bytecode( - prj, - cmd, - "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", - "StrategyManager", - None, - Config { - evm_version: EvmVersion::London, - optimizer: Some(true), - optimizer_runs: Some(200), - ..Default::default() - }, - "blockscout", - "https://eth.blockscout.com/api", - ("partial", "partial"), - ); - } -); +forgetest_async!(flaky_verify_bytecode_with_blockscout, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", + "StrategyManager", + None, + Config { + evm_version: EvmVersion::London, + optimizer: Some(true), + optimizer_runs: Some(200), + ..Default::default() + }, + "blockscout", + "https://eth.blockscout.com/api", + ("partial", "partial"), + Chain::mainnet(), + ) + .await; +}); // Test CREATE2 deployed contract with blockscout -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_vb_create2_with_blockscout, - |prj, cmd| { - test_verify_bytecode( - prj, - cmd, - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "SystemConfig", - None, - Config { - evm_version: EvmVersion::London, - optimizer_runs: Some(999999), - optimizer: Some(true), - cbor_metadata: false, - bytecode_hash: BytecodeHash::None, - ..Default::default() - }, - "blockscout", - "https://eth.blockscout.com/api", - ("partial", "partial"), - ); - } -); +forgetest_async!(flaky_verify_bytecode_create2_with_blockscout, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + None, + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "blockscout", + "https://eth.blockscout.com/api", + ("partial", "partial"), + Chain::mainnet(), + ) + .await; +}); // Test `--constructor-args` -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_verify_bytecode_with_constructor_args, - |prj, cmd| { - let constructor_args = vec![ - "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", - "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338", - "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd", - ]; - test_verify_bytecode( - prj, - cmd, - "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", - "StrategyManager", - Some(constructor_args), - Config { - evm_version: EvmVersion::London, - optimizer: Some(true), - optimizer_runs: Some(200), - ..Default::default() - }, - "etherscan", - "https://api.etherscan.io/v2/api?chainid=1", - ("partial", "partial"), - ); - } -); +forgetest_async!(flaky_verify_bytecode_with_constructor_args, |prj, cmd| { + let constructor_args = vec![ + "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338", + "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd", + ]; + test_verify_bytecode( + prj, + cmd, + "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", + "StrategyManager", + Some(constructor_args), + Config { + evm_version: EvmVersion::London, + optimizer: Some(true), + optimizer_runs: Some(200), + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ("partial", "partial"), + Chain::mainnet(), + ) + .await; +}); // `--ignore` tests -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_ignore_creation, - |prj, cmd| { - test_verify_bytecode_with_ignore( - prj, - cmd, - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "SystemConfig", - Config { - evm_version: EvmVersion::London, - optimizer_runs: Some(999999), - optimizer: Some(true), - cbor_metadata: false, - bytecode_hash: BytecodeHash::None, - ..Default::default() - }, - "etherscan", - "https://api.etherscan.io/v2/api?chainid=1", - ("ignored", "partial"), - "creation", - "1", - ); - } -); - -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_ignore_runtime, - |prj, cmd| { - test_verify_bytecode_with_ignore( - prj, - cmd, - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "SystemConfig", - Config { - evm_version: EvmVersion::London, - optimizer_runs: Some(999999), - optimizer: Some(true), - cbor_metadata: false, - bytecode_hash: BytecodeHash::None, - ..Default::default() - }, - "etherscan", - "https://api.etherscan.io/v2/api?chainid=1", - ("partial", "ignored"), - "runtime", - "1", - ); - } -); - -// Test that verification fails when source code doesn't match deployed bytecode -forgetest_async!( - #[ignore = "flaky due to rate limits"] - can_verify_bytecode_fails_on_source_mismatch, - |prj, cmd| { - let etherscan_key = next_etherscan_api_key(); - let rpc_url = next_http_archive_rpc_url(); - - // Fetch real source code - let real_source = cmd - .cast_fuse() - .args([ - "source", - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "--flatten", - "--etherscan-api-key", - ðerscan_key, - ]) - .assert_success() - .get_output() - .stdout_lossy(); +forgetest_async!(flaky_verify_bytecode_can_ignore_creation, |prj, cmd| { + test_verify_bytecode_with_ignore( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ("ignored", "partial"), + "creation", + Chain::mainnet(), + ) + .await; +}); - prj.add_source("SystemConfig", &real_source); - prj.write_config(Config { +forgetest_async!(flaky_verify_bytecode_can_ignore_runtime, |prj, cmd| { + test_verify_bytecode_with_ignore( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + Config { evm_version: EvmVersion::London, optimizer_runs: Some(999999), optimizer: Some(true), cbor_metadata: false, bytecode_hash: BytecodeHash::None, ..Default::default() - }); - // Build once with correct source (creates cache) - cmd.forge_fuse().arg("build").assert_success(); + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ("partial", "ignored"), + "runtime", + Chain::mainnet(), + ) + .await; +}); + +// Test that verification fails when source code doesn't match deployed bytecode +forgetest_async!(flaky_can_verify_bytecode_fails_on_source_mismatch, |prj, cmd| { + let etherscan_key = next_etherscan_api_key(); + let rpc_url = next_http_archive_rpc_url(); - let source_code = r#" + // Fetch real source code using the library directly + let real_source = fetch_etherscan_source_flattened( + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + ðerscan_key, + Chain::mainnet(), + ) + .await + .expect("failed to fetch source code from etherscan"); + + prj.add_source("SystemConfig", &real_source); + prj.write_config(Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }); + // Build once with correct source (creates cache) + cmd.forge_fuse().arg("build").assert_success(); + + let source_code = r#" contract SystemConfig { uint256 public constant MODIFIED_VALUE = 999; @@ -369,28 +335,28 @@ forgetest_async!( } "#; - // Now replace with different incorrect source code - prj.add_source("SystemConfig", source_code); - let args = vec![ - "verify-bytecode", - "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", - "SystemConfig", - "--etherscan-api-key", - ðerscan_key, - "--verifier", - "etherscan", - "--verifier-url", - "https://api.etherscan.io/v2/api?chainid=1", - "--rpc-url", - &rpc_url, - ]; - let output = cmd.forge_fuse().args(args).assert_success().get_output().stderr_lossy(); + // Now replace with different incorrect source code + prj.add_source("SystemConfig", source_code); + let etherscan_key = next_etherscan_api_key(); + let args = vec![ + "verify-bytecode", + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + "--etherscan-api-key", + ðerscan_key, + "--verifier", + "etherscan", + "--verifier-url", + "https://api.etherscan.io/v2/api?chainid=1", + "--rpc-url", + &rpc_url, + ]; + let output = cmd.forge_fuse().args(args).assert_success().get_output().stderr_lossy(); - // Verify that bytecode does NOT match (recompiled with incorrect source) - assert!(output.contains("Error: Creation code did not match".to_string().as_str())); - assert!(output.contains("Error: Runtime code did not match".to_string().as_str())); - } -); + // Verify that bytecode does NOT match (recompiled with incorrect source) + assert!(output.contains("Error: Creation code did not match".to_string().as_str())); + assert!(output.contains("Error: Runtime code did not match".to_string().as_str())); +}); // Test predeploy contracts // TODO: Add test utils for base such as basescan keys and alchemy keys. @@ -413,6 +379,6 @@ forgetest_async!( // "https://api.basescan.org/api", // ("ignored", "partial"), // "creation", -// "base", -// ); +// Chain::base_mainnet(), +// ).await; // }); diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json index 96fb5dffe1fa4..244a2d94fc7eb 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -45,6 +45,7 @@ "output": "{...}", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Return", "steps": [], "decoded": null @@ -78,6 +79,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [ { @@ -970,7 +972,7 @@ "0x142" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -992,7 +994,7 @@ "0x1c2" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1014,7 +1016,7 @@ "0x267" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1030,7 +1032,7 @@ "op": 91, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1046,7 +1048,7 @@ "op": 96, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1062,7 +1064,7 @@ "op": 81, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2", "0x40"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1078,7 +1080,7 @@ "op": 128, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2", "0x80"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1101,7 +1103,7 @@ "0x80" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1124,7 +1126,7 @@ "0x1c2" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1140,7 +1142,7 @@ "op": 144, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x80", "0x142"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1156,7 +1158,7 @@ "op": 95, "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x142", "0x80"], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1179,7 +1181,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1200,7 +1202,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1222,7 +1224,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629525, "gas_refund_counter": 0, @@ -1244,7 +1246,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629522, "gas_refund_counter": 0, @@ -1267,7 +1269,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629519, "gas_refund_counter": 0, @@ -1290,7 +1292,7 @@ "0x1" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629516, "gas_refund_counter": 0, @@ -1314,7 +1316,7 @@ "0x280" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629513, "gas_refund_counter": 0, @@ -1336,7 +1338,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629503, "gas_refund_counter": 0, @@ -1358,7 +1360,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629502, "gas_refund_counter": 0, @@ -1379,7 +1381,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629500, "gas_refund_counter": 0, @@ -1400,7 +1402,7 @@ "0x0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629497, "gas_refund_counter": 0, @@ -1420,7 +1422,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629495, "gas_refund_counter": 0, @@ -1441,7 +1443,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629492, "gas_refund_counter": 0, @@ -1463,7 +1465,7 @@ "0xffffffffffffffffffffffffffffffffffffffff" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629489, "gas_refund_counter": 0, @@ -1484,7 +1486,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629486, "gas_refund_counter": 0, @@ -1506,7 +1508,7 @@ "0xe26d1474" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629483, "gas_refund_counter": 0, @@ -1529,7 +1531,7 @@ "0x64" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629480, "gas_refund_counter": 0, @@ -1553,7 +1555,7 @@ "0x40" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629477, "gas_refund_counter": 0, @@ -1577,7 +1579,7 @@ "0x80" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629474, "gas_refund_counter": 0, @@ -1602,7 +1604,7 @@ "0xe26d1474" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629471, "gas_refund_counter": 0, @@ -1628,7 +1630,7 @@ "0xffffffff" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629468, "gas_refund_counter": 0, @@ -1653,7 +1655,7 @@ "0xe26d1474" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629465, "gas_refund_counter": 0, @@ -1679,7 +1681,7 @@ "0xe0" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629462, "gas_refund_counter": 0, @@ -1704,7 +1706,7 @@ "0xe26d147400000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629459, "gas_refund_counter": 0, @@ -1730,7 +1732,7 @@ "0x80" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629456, "gas_refund_counter": 0, @@ -1754,7 +1756,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629453, "gas_refund_counter": 0, @@ -1779,7 +1781,7 @@ "0x4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629450, "gas_refund_counter": 0, @@ -1803,7 +1805,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629447, "gas_refund_counter": 0, @@ -1828,7 +1830,7 @@ "0x2bd" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629444, "gas_refund_counter": 0, @@ -1853,7 +1855,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629441, "gas_refund_counter": 0, @@ -1878,7 +1880,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629438, "gas_refund_counter": 0, @@ -1904,7 +1906,7 @@ "0x651" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629435, "gas_refund_counter": 0, @@ -1929,7 +1931,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629427, "gas_refund_counter": 0, @@ -1954,7 +1956,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629426, "gas_refund_counter": 0, @@ -1980,7 +1982,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629424, "gas_refund_counter": 0, @@ -2007,7 +2009,7 @@ "0x20" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629421, "gas_refund_counter": 0, @@ -2035,7 +2037,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629418, "gas_refund_counter": 0, @@ -2062,7 +2064,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629415, "gas_refund_counter": 0, @@ -2089,7 +2091,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629412, "gas_refund_counter": 0, @@ -2115,7 +2117,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629410, "gas_refund_counter": 0, @@ -2142,7 +2144,7 @@ "0x664" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629407, "gas_refund_counter": 0, @@ -2170,7 +2172,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629405, "gas_refund_counter": 0, @@ -2199,7 +2201,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629402, "gas_refund_counter": 0, @@ -2227,7 +2229,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629399, "gas_refund_counter": 0, @@ -2256,7 +2258,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629396, "gas_refund_counter": 0, @@ -2286,7 +2288,7 @@ "0x642" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629393, "gas_refund_counter": 0, @@ -2315,7 +2317,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629385, "gas_refund_counter": 0, @@ -2344,7 +2346,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629384, "gas_refund_counter": 0, @@ -2374,7 +2376,7 @@ "0x64b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629381, "gas_refund_counter": 0, @@ -2405,7 +2407,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629378, "gas_refund_counter": 0, @@ -2437,7 +2439,7 @@ "0x621" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629375, "gas_refund_counter": 0, @@ -2468,7 +2470,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629367, "gas_refund_counter": 0, @@ -2499,7 +2501,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629366, "gas_refund_counter": 0, @@ -2531,7 +2533,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629364, "gas_refund_counter": 0, @@ -2564,7 +2566,7 @@ "0x63b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629361, "gas_refund_counter": 0, @@ -2598,7 +2600,7 @@ "0x636" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629358, "gas_refund_counter": 0, @@ -2633,7 +2635,7 @@ "0x631" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629355, "gas_refund_counter": 0, @@ -2669,7 +2671,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629352, "gas_refund_counter": 0, @@ -2706,7 +2708,7 @@ "0x606" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629349, "gas_refund_counter": 0, @@ -2742,7 +2744,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629341, "gas_refund_counter": 0, @@ -2778,7 +2780,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629340, "gas_refund_counter": 0, @@ -2815,7 +2817,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629338, "gas_refund_counter": 0, @@ -2853,7 +2855,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629335, "gas_refund_counter": 0, @@ -2891,7 +2893,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629332, "gas_refund_counter": 0, @@ -2928,7 +2930,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629330, "gas_refund_counter": 0, @@ -2965,7 +2967,7 @@ "0x631" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629327, "gas_refund_counter": 0, @@ -3002,7 +3004,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629324, "gas_refund_counter": 0, @@ -3038,7 +3040,7 @@ "0x631" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629322, "gas_refund_counter": 0, @@ -3073,7 +3075,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629314, "gas_refund_counter": 0, @@ -3108,7 +3110,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629313, "gas_refund_counter": 0, @@ -3144,7 +3146,7 @@ "0x618" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629310, "gas_refund_counter": 0, @@ -3179,7 +3181,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629302, "gas_refund_counter": 0, @@ -3214,7 +3216,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629301, "gas_refund_counter": 0, @@ -3250,7 +3252,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629299, "gas_refund_counter": 0, @@ -3287,7 +3289,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629296, "gas_refund_counter": 0, @@ -3324,7 +3326,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629293, "gas_refund_counter": 0, @@ -3360,7 +3362,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629291, "gas_refund_counter": 0, @@ -3396,7 +3398,7 @@ "0x636" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629288, "gas_refund_counter": 0, @@ -3432,7 +3434,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629285, "gas_refund_counter": 0, @@ -3467,7 +3469,7 @@ "0x636" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629283, "gas_refund_counter": 0, @@ -3501,7 +3503,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629275, "gas_refund_counter": 0, @@ -3535,7 +3537,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629274, "gas_refund_counter": 0, @@ -3570,7 +3572,7 @@ "0x60f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629271, "gas_refund_counter": 0, @@ -3604,7 +3606,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629263, "gas_refund_counter": 0, @@ -3638,7 +3640,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629262, "gas_refund_counter": 0, @@ -3673,7 +3675,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629260, "gas_refund_counter": 0, @@ -3709,7 +3711,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629257, "gas_refund_counter": 0, @@ -3745,7 +3747,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629254, "gas_refund_counter": 0, @@ -3780,7 +3782,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629252, "gas_refund_counter": 0, @@ -3815,7 +3817,7 @@ "0x63b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629249, "gas_refund_counter": 0, @@ -3850,7 +3852,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629246, "gas_refund_counter": 0, @@ -3884,7 +3886,7 @@ "0x63b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629244, "gas_refund_counter": 0, @@ -3917,7 +3919,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629236, "gas_refund_counter": 0, @@ -3950,7 +3952,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629235, "gas_refund_counter": 0, @@ -3983,7 +3985,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629232, "gas_refund_counter": 0, @@ -4015,7 +4017,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629230, "gas_refund_counter": 0, @@ -4047,7 +4049,7 @@ "0x64b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629227, "gas_refund_counter": 0, @@ -4079,7 +4081,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629224, "gas_refund_counter": 0, @@ -4110,7 +4112,7 @@ "0x64b" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629222, "gas_refund_counter": 0, @@ -4140,7 +4142,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629214, "gas_refund_counter": 0, @@ -4170,7 +4172,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629213, "gas_refund_counter": 0, @@ -4201,7 +4203,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629210, "gas_refund_counter": 0, @@ -4230,7 +4232,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629207, "gas_refund_counter": 0, @@ -4258,7 +4260,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629205, "gas_refund_counter": 0, @@ -4285,7 +4287,7 @@ "0x664" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629203, "gas_refund_counter": 0, @@ -4311,7 +4313,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629195, "gas_refund_counter": 0, @@ -4337,7 +4339,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629194, "gas_refund_counter": 0, @@ -4363,7 +4365,7 @@ "0x2bd" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629191, "gas_refund_counter": 0, @@ -4389,7 +4391,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629188, "gas_refund_counter": 0, @@ -4414,7 +4416,7 @@ "0x84" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629186, "gas_refund_counter": 0, @@ -4438,7 +4440,7 @@ "0x2bd" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629184, "gas_refund_counter": 0, @@ -4461,7 +4463,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629176, "gas_refund_counter": 0, @@ -4484,7 +4486,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629175, "gas_refund_counter": 0, @@ -4508,7 +4510,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629173, "gas_refund_counter": 0, @@ -4533,7 +4535,7 @@ "0x40" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629170, "gas_refund_counter": 0, @@ -4558,7 +4560,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629167, "gas_refund_counter": 0, @@ -4584,7 +4586,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629164, "gas_refund_counter": 0, @@ -4611,7 +4613,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629161, "gas_refund_counter": 0, @@ -4637,7 +4639,7 @@ "0x24" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629158, "gas_refund_counter": 0, @@ -4664,7 +4666,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629155, "gas_refund_counter": 0, @@ -4692,7 +4694,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629153, "gas_refund_counter": 0, @@ -4721,7 +4723,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629150, "gas_refund_counter": 0, @@ -4751,7 +4753,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629147, "gas_refund_counter": 0, @@ -4781,7 +4783,7 @@ "0x126" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629047, "gas_refund_counter": 0, @@ -4811,7 +4813,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629044, "gas_refund_counter": 0, @@ -4842,7 +4844,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629041, "gas_refund_counter": 0, @@ -4873,7 +4875,7 @@ "0x1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629038, "gas_refund_counter": 0, @@ -4905,7 +4907,7 @@ "0x2d4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629035, "gas_refund_counter": 0, @@ -4935,7 +4937,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629025, "gas_refund_counter": 0, @@ -4965,7 +4967,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629024, "gas_refund_counter": 0, @@ -4994,7 +4996,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629022, "gas_refund_counter": 0, @@ -5024,7 +5026,7 @@ "0x3ffe475c" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073629020, "gas_refund_counter": 0, @@ -5048,7 +5050,7 @@ "0x1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606406, "gas_refund_counter": 0, @@ -5072,7 +5074,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606403, "gas_refund_counter": 0, @@ -5097,7 +5099,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606400, "gas_refund_counter": 0, @@ -5122,7 +5124,7 @@ "0x1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606397, "gas_refund_counter": 0, @@ -5148,7 +5150,7 @@ "0x2e6" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606394, "gas_refund_counter": 0, @@ -5172,7 +5174,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606384, "gas_refund_counter": 0, @@ -5196,7 +5198,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606383, "gas_refund_counter": 0, @@ -5219,7 +5221,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606381, "gas_refund_counter": 0, @@ -5241,7 +5243,7 @@ "0xe26d1474" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606379, "gas_refund_counter": 0, @@ -5262,7 +5264,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606377, "gas_refund_counter": 0, @@ -5282,7 +5284,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606375, "gas_refund_counter": 0, @@ -5303,7 +5305,7 @@ "0x2f4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606372, "gas_refund_counter": 0, @@ -5325,7 +5327,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606369, "gas_refund_counter": 0, @@ -5348,7 +5350,7 @@ "0x32e" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606366, "gas_refund_counter": 0, @@ -5370,7 +5372,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606358, "gas_refund_counter": 0, @@ -5392,7 +5394,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606357, "gas_refund_counter": 0, @@ -5415,7 +5417,7 @@ "0x3c4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606354, "gas_refund_counter": 0, @@ -5439,7 +5441,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606351, "gas_refund_counter": 0, @@ -5464,7 +5466,7 @@ "0x40" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606348, "gas_refund_counter": 0, @@ -5489,7 +5491,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606345, "gas_refund_counter": 0, @@ -5515,7 +5517,7 @@ "0x24" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606342, "gas_refund_counter": 0, @@ -5540,7 +5542,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606339, "gas_refund_counter": 0, @@ -5566,7 +5568,7 @@ "0x342" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606336, "gas_refund_counter": 0, @@ -5592,7 +5594,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606333, "gas_refund_counter": 0, @@ -5618,7 +5620,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606330, "gas_refund_counter": 0, @@ -5645,7 +5647,7 @@ "0x679" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606327, "gas_refund_counter": 0, @@ -5671,7 +5673,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606319, "gas_refund_counter": 0, @@ -5697,7 +5699,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606318, "gas_refund_counter": 0, @@ -5724,7 +5726,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606316, "gas_refund_counter": 0, @@ -5752,7 +5754,7 @@ "0x20" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606313, "gas_refund_counter": 0, @@ -5781,7 +5783,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606310, "gas_refund_counter": 0, @@ -5809,7 +5811,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606307, "gas_refund_counter": 0, @@ -5837,7 +5839,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606304, "gas_refund_counter": 0, @@ -5864,7 +5866,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606302, "gas_refund_counter": 0, @@ -5892,7 +5894,7 @@ "0x68c" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606299, "gas_refund_counter": 0, @@ -5921,7 +5923,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606297, "gas_refund_counter": 0, @@ -5951,7 +5953,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606294, "gas_refund_counter": 0, @@ -5980,7 +5982,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606291, "gas_refund_counter": 0, @@ -6010,7 +6012,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606288, "gas_refund_counter": 0, @@ -6041,7 +6043,7 @@ "0x66a" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606285, "gas_refund_counter": 0, @@ -6071,7 +6073,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606277, "gas_refund_counter": 0, @@ -6101,7 +6103,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606276, "gas_refund_counter": 0, @@ -6132,7 +6134,7 @@ "0x673" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606273, "gas_refund_counter": 0, @@ -6164,7 +6166,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606270, "gas_refund_counter": 0, @@ -6197,7 +6199,7 @@ "0x60f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606267, "gas_refund_counter": 0, @@ -6229,7 +6231,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606259, "gas_refund_counter": 0, @@ -6261,7 +6263,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606258, "gas_refund_counter": 0, @@ -6294,7 +6296,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606256, "gas_refund_counter": 0, @@ -6328,7 +6330,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606253, "gas_refund_counter": 0, @@ -6362,7 +6364,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606250, "gas_refund_counter": 0, @@ -6395,7 +6397,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606248, "gas_refund_counter": 0, @@ -6428,7 +6430,7 @@ "0x673" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606245, "gas_refund_counter": 0, @@ -6461,7 +6463,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606242, "gas_refund_counter": 0, @@ -6493,7 +6495,7 @@ "0x673" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606240, "gas_refund_counter": 0, @@ -6524,7 +6526,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606232, "gas_refund_counter": 0, @@ -6555,7 +6557,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606231, "gas_refund_counter": 0, @@ -6587,7 +6589,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606228, "gas_refund_counter": 0, @@ -6617,7 +6619,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606225, "gas_refund_counter": 0, @@ -6646,7 +6648,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606223, "gas_refund_counter": 0, @@ -6674,7 +6676,7 @@ "0x68c" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606221, "gas_refund_counter": 0, @@ -6701,7 +6703,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606213, "gas_refund_counter": 0, @@ -6728,7 +6730,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606212, "gas_refund_counter": 0, @@ -6755,7 +6757,7 @@ "0x342" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606209, "gas_refund_counter": 0, @@ -6782,7 +6784,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606206, "gas_refund_counter": 0, @@ -6808,7 +6810,7 @@ "0xa4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606204, "gas_refund_counter": 0, @@ -6833,7 +6835,7 @@ "0x342" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606202, "gas_refund_counter": 0, @@ -6857,7 +6859,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606194, "gas_refund_counter": 0, @@ -6881,7 +6883,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606193, "gas_refund_counter": 0, @@ -6906,7 +6908,7 @@ "0x40" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606190, "gas_refund_counter": 0, @@ -6931,7 +6933,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606187, "gas_refund_counter": 0, @@ -6957,7 +6959,7 @@ "0x20" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606184, "gas_refund_counter": 0, @@ -6984,7 +6986,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606181, "gas_refund_counter": 0, @@ -7012,7 +7014,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606178, "gas_refund_counter": 0, @@ -7039,7 +7041,7 @@ "0x44" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606175, "gas_refund_counter": 0, @@ -7065,7 +7067,7 @@ "0x24" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606172, "gas_refund_counter": 0, @@ -7092,7 +7094,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606169, "gas_refund_counter": 0, @@ -7117,7 +7119,7 @@ "0x80" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606166, "gas_refund_counter": 0, @@ -7142,7 +7144,7 @@ "0xc4" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606163, "gas_refund_counter": 0, @@ -7168,7 +7170,7 @@ "0x40" ], "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606160, "gas_refund_counter": 0, @@ -7192,7 +7194,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606157, "gas_refund_counter": 0, @@ -7217,7 +7219,7 @@ "0xf82c50f100000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606154, "gas_refund_counter": 0, @@ -7243,7 +7245,7 @@ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606151, "gas_refund_counter": 0, @@ -7269,7 +7271,7 @@ "0xffffffff00000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606148, "gas_refund_counter": 0, @@ -7294,7 +7296,7 @@ "0xf82c50f100000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606145, "gas_refund_counter": 0, @@ -7320,7 +7322,7 @@ "0x20" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606142, "gas_refund_counter": 0, @@ -7347,7 +7349,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606139, "gas_refund_counter": 0, @@ -7373,7 +7375,7 @@ "0xa0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606136, "gas_refund_counter": 0, @@ -7400,7 +7402,7 @@ "0xa0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606133, "gas_refund_counter": 0, @@ -7427,7 +7429,7 @@ "0x6400000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606130, "gas_refund_counter": 0, @@ -7455,7 +7457,7 @@ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606127, "gas_refund_counter": 0, @@ -7484,7 +7486,7 @@ "0xf82c50f100000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606124, "gas_refund_counter": 0, @@ -7514,7 +7516,7 @@ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606121, "gas_refund_counter": 0, @@ -7545,7 +7547,7 @@ "0x6400000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606118, "gas_refund_counter": 0, @@ -7575,7 +7577,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606115, "gas_refund_counter": 0, @@ -7604,7 +7606,7 @@ "0xf82c50f100000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606112, "gas_refund_counter": 0, @@ -7634,7 +7636,7 @@ "0xa0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606109, "gas_refund_counter": 0, @@ -7662,7 +7664,7 @@ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606106, "gas_refund_counter": 0, @@ -7689,7 +7691,7 @@ "0x6400000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606104, "gas_refund_counter": 0, @@ -7715,7 +7717,7 @@ "0xa0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606102, "gas_refund_counter": 0, @@ -7740,7 +7742,7 @@ "0xf82c50f100000000000000000000000000000000000000000000000000000000" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606100, "gas_refund_counter": 0, @@ -7764,7 +7766,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606098, "gas_refund_counter": 0, @@ -7789,7 +7791,7 @@ "0x3c7" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606095, "gas_refund_counter": 0, @@ -7813,7 +7815,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606087, "gas_refund_counter": 0, @@ -7837,7 +7839,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606086, "gas_refund_counter": 0, @@ -7862,7 +7864,7 @@ "0x3de" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606083, "gas_refund_counter": 0, @@ -7888,7 +7890,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606080, "gas_refund_counter": 0, @@ -7915,7 +7917,7 @@ "0x3d6" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606077, "gas_refund_counter": 0, @@ -7943,7 +7945,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606074, "gas_refund_counter": 0, @@ -7972,7 +7974,7 @@ "0x400" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606071, "gas_refund_counter": 0, @@ -8000,7 +8002,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606063, "gas_refund_counter": 0, @@ -8028,7 +8030,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606062, "gas_refund_counter": 0, @@ -8057,7 +8059,7 @@ "0x418" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606059, "gas_refund_counter": 0, @@ -8087,7 +8089,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606056, "gas_refund_counter": 0, @@ -8117,7 +8119,7 @@ "0x418" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606053, "gas_refund_counter": 0, @@ -8146,7 +8148,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606051, "gas_refund_counter": 0, @@ -8175,7 +8177,7 @@ "0x3d6" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606048, "gas_refund_counter": 0, @@ -8204,7 +8206,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606045, "gas_refund_counter": 0, @@ -8232,7 +8234,7 @@ "0x3d6" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606043, "gas_refund_counter": 0, @@ -8259,7 +8261,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606035, "gas_refund_counter": 0, @@ -8286,7 +8288,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606034, "gas_refund_counter": 0, @@ -8314,7 +8316,7 @@ "0xffffffff" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606031, "gas_refund_counter": 0, @@ -8341,7 +8343,7 @@ "0x3e1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606028, "gas_refund_counter": 0, @@ -8367,7 +8369,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606020, "gas_refund_counter": 0, @@ -8393,7 +8395,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606019, "gas_refund_counter": 0, @@ -8420,7 +8422,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606017, "gas_refund_counter": 0, @@ -8448,7 +8450,7 @@ "0x636f6e736f6c652e6c6f67" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606014, "gas_refund_counter": 0, @@ -8476,7 +8478,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606011, "gas_refund_counter": 0, @@ -8503,7 +8505,7 @@ "0x636f6e736f6c652e6c6f67" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606009, "gas_refund_counter": 0, @@ -8531,7 +8533,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606007, "gas_refund_counter": 0, @@ -8560,7 +8562,7 @@ "0x0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606005, "gas_refund_counter": 0, @@ -8590,7 +8592,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073606002, "gas_refund_counter": 0, @@ -8620,7 +8622,7 @@ "0x24" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605999, "gas_refund_counter": 0, @@ -8651,7 +8653,7 @@ "0x20" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605996, "gas_refund_counter": 0, @@ -8683,7 +8685,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605993, "gas_refund_counter": 0, @@ -8714,7 +8716,7 @@ "0xa0" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605990, "gas_refund_counter": 0, @@ -8746,7 +8748,7 @@ "0x636f6e736f6c652e6c6f67" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605987, "gas_refund_counter": 0, @@ -8779,7 +8781,7 @@ "0x3ffded61" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073605985, "gas_refund_counter": 0, @@ -8807,7 +8809,7 @@ "0x1" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603385, "gas_refund_counter": 0, @@ -8834,7 +8836,7 @@ "0x636f6e736f6c652e6c6f67" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603383, "gas_refund_counter": 0, @@ -8860,7 +8862,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603381, "gas_refund_counter": 0, @@ -8885,7 +8887,7 @@ "0x3de" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603379, "gas_refund_counter": 0, @@ -8909,7 +8911,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603371, "gas_refund_counter": 0, @@ -8933,7 +8935,7 @@ "0x80" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603370, "gas_refund_counter": 0, @@ -8956,7 +8958,7 @@ "0x3c4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603368, "gas_refund_counter": 0, @@ -8978,7 +8980,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603360, "gas_refund_counter": 0, @@ -9000,7 +9002,7 @@ "0x64" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603359, "gas_refund_counter": 0, @@ -9021,7 +9023,7 @@ "0x2f4" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603357, "gas_refund_counter": 0, @@ -9041,7 +9043,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603349, "gas_refund_counter": 0, @@ -9061,7 +9063,7 @@ "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" ], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603348, "gas_refund_counter": 0, @@ -9077,7 +9079,7 @@ "op": 86, "stack": ["0xf8a8fd6d", "0x92"], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603346, "gas_refund_counter": 0, @@ -9093,7 +9095,7 @@ "op": 91, "stack": ["0xf8a8fd6d"], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603338, "gas_refund_counter": 0, @@ -9109,7 +9111,7 @@ "op": 0, "stack": ["0xf8a8fd6d"], "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e0033000000000000000000000000000000000000000000000000000000000000", + "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1073603337, "gas_refund_counter": 0, @@ -10196,6 +10198,7 @@ "output": "{...}", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Return", "steps": [ { @@ -10459,7 +10462,7 @@ "stack": ["0x126"], "push_stack": null, - "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e00330000000000000000000000000000000000000000000000000000", + "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c634300082100330000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1056911949, "gas_refund_counter": 0, @@ -10476,7 +10479,7 @@ "stack": ["0x126", "0x0"], "push_stack": null, - "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212204fa676a81446bb9567311b8206428a7b97acb6b735a9b7fec033df5f3ffbbf0c64736f6c634300081e00330000000000000000000000000000000000000000000000000000", + "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c634300082100330000000000000000000000000000000000000000000000000000", "returndata": "0x", "gas_remaining": 1056911947, "gas_refund_counter": 0, @@ -10564,6 +10567,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [ { @@ -13836,6 +13840,7 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [], "decoded": null diff --git a/crates/forge/tests/fixtures/invariant_traces.svg b/crates/forge/tests/fixtures/invariant_traces.svg index 37c7bc8ebcf84..8844062c2553c 100644 --- a/crates/forge/tests/fixtures/invariant_traces.svg +++ b/crates/forge/tests/fixtures/invariant_traces.svg @@ -1,4 +1,4 @@ - + (&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.as_ref().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for FoundryTransactionRequest { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - WithOtherFields::::deserialize(deserializer).map(Self) - } +/// This is a union of different transaction request types, instantiated from a +/// [`WithOtherFields`]. The specific variant is determined by the transaction +/// type field and/or the presence of certain fields: +/// - **Ethereum**: Default variant when no special fields are present +/// - **Op**: When `sourceHash`, `mint`, and `isSystemTx` fields are present, or transaction type is +/// `DEPOSIT_TX_TYPE_ID` +/// - **Tempo**: When `feeToken` or `nonceKey` fields are present, or transaction type is +/// `TEMPO_TX_TYPE_ID` +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FoundryTransactionRequest { + Ethereum(TransactionRequest), + Op(WithOtherFields), + Tempo(Box), } impl FoundryTransactionRequest { @@ -44,79 +36,40 @@ impl FoundryTransactionRequest { /// [`WithOtherFields`]. #[inline] pub fn new(inner: WithOtherFields) -> Self { - Self(inner) + inner.into() } - /// Consume self and return the inner [`WithOtherFields`]. - #[inline] - pub fn into_inner(self) -> WithOtherFields { - self.0 - } - - /// Check if this is a deposit transaction. - #[inline] - pub fn is_deposit(&self) -> bool { - self.as_ref().transaction_type == Some(DEPOSIT_TX_TYPE_ID) + /// Consume the [`FoundryTransactionRequest`] and return the inner transaction request. + pub fn into_inner(self) -> TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + Self::Op(tx) => tx.inner, + Self::Tempo(tx) => tx.inner, + } } - /// Check if this is a Tempo transaction. + /// Get the deposit transaction parts from the request, calling [`get_deposit_tx_parts`] helper + /// with OtherFields. /// - /// Returns true if the transaction type is explicitly set to Tempo (0x76) or if - /// a `feeToken` is set in OtherFields. - #[inline] - pub fn is_tempo(&self) -> bool { - self.as_ref().transaction_type == Some(TEMPO_TX_TYPE_ID) - || self.as_ref().other.contains_key("feeToken") - || self.as_ref().other.contains_key("nonceKey") - } - - /// Get the Tempo fee token from OtherFields if present. - fn get_tempo_fee_token(&self) -> Option
{ - self.as_ref().other.get_deserialized::
("feeToken").transpose().ok().flatten() - } - - /// Get the Tempo nonce sequence key from OtherFields if present. - fn get_tempo_nonce_key(&self) -> U256 { - self.as_ref() - .other - .get_deserialized::("nonceKey") - .transpose() - .ok() - .flatten() - .unwrap_or_default() - } - - /// Check if all necessary keys are present to build a Tempo transaction, returning a list of - /// keys that are missing. - pub fn complete_tempo(&self) -> Result<(), Vec<&'static str>> { - let mut missing = Vec::new(); - if self.chain_id().is_none() { - missing.push("chain_id"); - } - if self.gas_limit().is_none() { - missing.push("gas_limit"); - } - if self.max_fee_per_gas().is_none() { - missing.push("max_fee_per_gas"); - } - if self.max_priority_fee_per_gas().is_none() { - missing.push("max_priority_fee_per_gas"); - } - if self.nonce().is_none() { - missing.push("nonce"); + /// # Returns + /// - Ok(deposit_tx_parts) if all necessary keys are present to build a deposit transaction. + /// - Err(missing) if some keys are missing to build a deposit transaction. + pub fn get_deposit_tx_parts(&self) -> Result> { + match self { + Self::Op(tx) => get_deposit_tx_parts(&tx.other), + // Not a deposit transaction request, so missing at least sourceHash, mint, and + // isSystemTx + _ => Err(vec!["sourceHash", "mint", "isSystemTx"]), } - if missing.is_empty() { Ok(()) } else { Err(missing) } } /// Returns the minimal transaction type this request can be converted into based on the fields /// that are set. See [`TransactionRequest::preferred_type`]. pub fn preferred_type(&self) -> FoundryTxType { - if self.is_deposit() { - FoundryTxType::Deposit - } else if self.is_tempo() { - FoundryTxType::Tempo - } else { - self.as_ref().preferred_type().into() + match self { + Self::Ethereum(tx) => tx.preferred_type().into(), + Self::Op(_) => FoundryTxType::Deposit, + Self::Tempo(_) => FoundryTxType::Tempo, } } @@ -139,23 +92,17 @@ impl FoundryTransactionRequest { /// Check if all necessary keys are present to build a Deposit transaction, returning a list of /// keys that are missing. pub fn complete_deposit(&self) -> Result<(), Vec<&'static str>> { - get_deposit_tx_parts(&self.as_ref().other).map(|_| ()) + self.get_deposit_tx_parts().map(|_| ()) } - /// Return the tx type this request can be built as. Computed by checking - /// the preferred type, and then checking for completeness. - pub fn buildable_type(&self) -> Option { - let pref = self.preferred_type(); - match pref { - FoundryTxType::Legacy => self.as_ref().complete_legacy().ok(), - FoundryTxType::Eip2930 => self.as_ref().complete_2930().ok(), - FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), - FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), - FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), - FoundryTxType::Deposit => self.complete_deposit().ok(), - FoundryTxType::Tempo => self.complete_tempo().ok(), - }?; - Some(pref) + /// Check if all necessary keys are present to build a Tempo transaction, returning a list of + /// keys that are missing. + pub fn complete_tempo(&self) -> Result<(), Vec<&'static str>> { + match self { + Self::Tempo(tx) => tx.complete_type(TempoTxType::AA).map(|_| ()), + // Not a Tempo transaction request, so missing at least feeToken and nonceKey + _ => Err(vec!["feeToken", "nonceKey"]), + } } /// Check if all necessary keys are present to build a transaction. @@ -186,8 +133,8 @@ impl FoundryTransactionRequest { /// Converts the request into a `FoundryTypedTx`, handling all Ethereum and OP-stack transaction /// types. pub fn build_typed_tx(self) -> Result { - // Handle deposit transactions - if let Ok(deposit_tx_parts) = get_deposit_tx_parts(&self.as_ref().other) { + if let Ok(deposit_tx_parts) = self.get_deposit_tx_parts() { + // Build deposit transaction Ok(FoundryTypedTx::Deposit(TxDeposit { from: self.from().unwrap_or_default(), source_hash: deposit_tx_parts.source_hash, @@ -198,35 +145,26 @@ impl FoundryTransactionRequest { is_system_transaction: deposit_tx_parts.is_system_transaction, input: self.input().cloned().unwrap_or_default(), })) - } else if self.is_tempo() { - // Build Tempo transaction from request fields - Ok(FoundryTypedTx::Tempo(TempoTransaction { - chain_id: self.chain_id().unwrap_or_default(), - fee_token: self.get_tempo_fee_token(), - max_fee_per_gas: self.max_fee_per_gas().unwrap_or_default(), - max_priority_fee_per_gas: self.max_priority_fee_per_gas().unwrap_or_default(), - gas_limit: self.gas_limit().unwrap_or_default(), - nonce_key: self.get_tempo_nonce_key(), - nonce: self.nonce().unwrap_or_default(), - calls: vec![Call { - to: self.kind().unwrap_or_default(), - value: self.value().unwrap_or_default(), - input: self.input().cloned().unwrap_or_default(), - }], - access_list: self.access_list().cloned().unwrap_or_default(), - ..Default::default() - })) - } else if self.as_ref().has_eip4844_fields() && self.as_ref().blob_sidecar().is_none() { - // if request has eip4844 fields but no blob sidecar, try to build to eip4844 without - // sidecar - self.0 - .into_inner() + } else if self.complete_tempo().is_ok() + && let Self::Tempo(tx_req) = self + { + // Build Tempo transaction + Ok(FoundryTypedTx::Tempo( + tx_req.build_aa().map_err(|e| Self::Tempo(Box::new(e.into_value())))?, + )) + } else if self.as_ref().has_eip4844_fields() + && self.blob_sidecar().is_none() + && alloy_network::TransactionBuilder7594::blob_sidecar_7594(self.as_ref()).is_none() + { + // if request has eip4844 fields but no blob sidecar (neither eip4844 nor eip7594 + // format), try to build to eip4844 without sidecar + self.into_inner() .build_4844_without_sidecar() - .map_err(|e| Self(e.into_value().into())) + .map_err(|e| Self::Ethereum(e.into_value())) .map(|tx| FoundryTypedTx::Eip4844(tx.into())) } else { // Use the inner transaction request to build EthereumTypedTransaction - let typed_tx = self.0.into_inner().build_typed_tx().map_err(|tx| Self(tx.into()))?; + let typed_tx = self.into_inner().build_typed_tx().map_err(Self::Ethereum)?; // Convert EthereumTypedTransaction to FoundryTypedTx Ok(match typed_tx { EthereumTypedTransaction::Legacy(tx) => FoundryTypedTx::Legacy(tx), @@ -239,14 +177,90 @@ impl FoundryTransactionRequest { } } +impl Default for FoundryTransactionRequest { + fn default() -> Self { + Self::Ethereum(TransactionRequest::default()) + } +} + +impl Serialize for FoundryTransactionRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Ethereum(tx) => tx.serialize(serializer), + Self::Op(tx) => tx.serialize(serializer), + Self::Tempo(tx) => tx.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for FoundryTransactionRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + WithOtherFields::::deserialize(deserializer).map(Into::::into) + } +} + +impl AsRef for FoundryTransactionRequest { + fn as_ref(&self) -> &TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + Self::Op(tx) => tx, + Self::Tempo(tx) => tx, + } + } +} + +impl AsMut for FoundryTransactionRequest { + fn as_mut(&mut self) -> &mut TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + Self::Op(tx) => tx, + Self::Tempo(tx) => tx, + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: WithOtherFields) -> Self { + if tx.transaction_type == Some(TEMPO_TX_TYPE_ID) + || tx.other.contains_key("feeToken") + || tx.other.contains_key("nonceKey") + { + let mut tempo_tx_req: TempoTransactionRequest = tx.inner.into(); + if let Some(fee_token) = + tx.other.get_deserialized::
("feeToken").transpose().ok().flatten() + { + tempo_tx_req.fee_token = Some(fee_token); + } + if let Some(nonce_key) = + tx.other.get_deserialized::("nonceKey").transpose().ok().flatten() + { + tempo_tx_req.set_nonce_key(nonce_key); + } + Self::Tempo(Box::new(tempo_tx_req)) + } else if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) + || get_deposit_tx_parts(&tx.other).is_ok() + { + Self::Op(tx) + } else { + Self::Ethereum(tx.into_inner()) + } + } +} + impl From for FoundryTransactionRequest { fn from(tx: FoundryTypedTx) -> Self { match tx { - FoundryTypedTx::Legacy(tx) => Self(Into::::into(tx).into()), - FoundryTypedTx::Eip2930(tx) => Self(Into::::into(tx).into()), - FoundryTypedTx::Eip1559(tx) => Self(Into::::into(tx).into()), - FoundryTypedTx::Eip4844(tx) => Self(Into::::into(tx).into()), - FoundryTypedTx::Eip7702(tx) => Self(Into::::into(tx).into()), + FoundryTypedTx::Legacy(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip2930(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip1559(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip4844(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip7702(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Deposit(tx) => { let other = OtherFields::from_iter([ ("sourceHash", tx.source_hash.to_string().into()), @@ -418,8 +432,8 @@ impl TransactionBuilder for FoundryTransactionRequest { fn can_build(&self) -> bool { self.as_ref().can_build() - || get_deposit_tx_parts(&self.as_ref().other).is_ok() - || self.is_tempo() + || self.complete_deposit().is_ok() + || self.complete_tempo().is_ok() } fn output_tx_type(&self) -> FoundryTxType { @@ -427,7 +441,17 @@ impl TransactionBuilder for FoundryTransactionRequest { } fn output_tx_type_checked(&self) -> Option { - self.buildable_type() + let pref = self.preferred_type(); + match pref { + FoundryTxType::Legacy => self.as_ref().complete_legacy().ok(), + FoundryTxType::Eip2930 => self.as_ref().complete_2930().ok(), + FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), + FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), + FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), + FoundryTxType::Deposit => self.complete_deposit().ok(), + FoundryTxType::Tempo => self.complete_tempo().ok(), + }?; + Some(pref) } /// Prepares [`FoundryTransactionRequest`] by trimming conflicting fields, and filling with @@ -436,19 +460,19 @@ impl TransactionBuilder for FoundryTransactionRequest { let preferred_type = self.preferred_type(); let inner = self.as_mut(); inner.transaction_type = Some(preferred_type as u8); - inner.gas_limit().is_none().then(|| inner.set_gas_limit(Default::default())); + inner.gas.is_none().then(|| inner.set_gas_limit(Default::default())); if !matches!(preferred_type, FoundryTxType::Deposit | FoundryTxType::Tempo) { inner.trim_conflicting_keys(); inner.populate_blob_hashes(); } if preferred_type != FoundryTxType::Deposit { - inner.nonce().is_none().then(|| inner.set_nonce(Default::default())); + inner.nonce.is_none().then(|| inner.set_nonce(Default::default())); } if matches!(preferred_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { - inner.gas_price().is_none().then(|| inner.set_gas_price(Default::default())); + inner.gas_price.is_none().then(|| inner.set_gas_price(Default::default())); } if preferred_type == FoundryTxType::Eip2930 { - inner.access_list().is_none().then(|| inner.set_access_list(Default::default())); + inner.access_list.is_none().then(|| inner.set_access_list(Default::default())); } if matches!( preferred_type, @@ -458,13 +482,10 @@ impl TransactionBuilder for FoundryTransactionRequest { | FoundryTxType::Tempo ) { inner - .max_priority_fee_per_gas() + .max_priority_fee_per_gas .is_none() .then(|| inner.set_max_priority_fee_per_gas(Default::default())); - inner - .max_fee_per_gas() - .is_none() - .then(|| inner.set_max_fee_per_gas(Default::default())); + inner.max_fee_per_gas.is_none().then(|| inner.set_max_fee_per_gas(Default::default())); } if preferred_type == FoundryTxType::Eip4844 { inner @@ -491,6 +512,24 @@ impl TransactionBuilder for FoundryTransactionRequest { } } +impl TransactionBuilder4844 for FoundryTransactionRequest { + fn max_fee_per_blob_gas(&self) -> Option { + self.as_ref().max_fee_per_blob_gas() + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.as_mut().set_max_fee_per_blob_gas(max_fee_per_blob_gas); + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + self.as_ref().blob_sidecar() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { + self.as_mut().set_blob_sidecar(sidecar); + } +} + /// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields pub fn get_deposit_tx_parts( other: &OtherFields, @@ -524,3 +563,121 @@ pub fn get_deposit_tx_parts( Err(missing) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn default_tx_req() -> TransactionRequest { + TransactionRequest::default() + .with_to(Address::random()) + .with_nonce(1) + .with_value(U256::from(1000000)) + .with_gas_limit(1000000) + .with_max_fee_per_gas(1000000) + .with_max_priority_fee_per_gas(1000000) + } + + #[test] + fn test_routing_ethereum_default() { + let tx = default_tx_req(); + let req: FoundryTransactionRequest = WithOtherFields::new(tx).into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_routing_tempo_by_fee_token() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("feeToken".to_string(), serde_json::to_value(Address::random()).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Tempo(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Tempo(_)))); + } + + #[test] + fn test_routing_op_by_deposit_fields() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + other.insert("isSystemTx".to_string(), serde_json::to_value(false).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Op(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Deposit(_)))); + } + + #[test] + fn test_op_incomplete_routes_to_ethereum() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + // Only provide 2 of 3 required Op fields + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_ethereum_with_unrelated_other_fields() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("anotherField".to_string(), serde_json::to_value(123).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_serialization_ethereum() { + let tx = default_tx_req(); + let original: FoundryTransactionRequest = WithOtherFields::new(tx).into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Ethereum(_))); + } + + #[test] + fn test_serialization_op() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + other.insert("isSystemTx".to_string(), serde_json::to_value(false).unwrap()); + + let original: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Op(_))); + } + + #[test] + fn test_serialization_tempo() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("feeToken".to_string(), serde_json::to_value(Address::ZERO).unwrap()); + other.insert("nonceKey".to_string(), serde_json::to_value(U256::from(42)).unwrap()); + + let original: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Tempo(_))); + } +} diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs index 4867f4f50e767..bfe14e14f3fd2 100644 --- a/crates/script-sequence/src/reader.rs +++ b/crates/script-sequence/src/reader.rs @@ -43,6 +43,12 @@ impl BroadcastReader { self } + fn matches_filters(&self, tx: &TransactionWithMetadata) -> bool { + let name_filter = tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); + let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); + name_filter && type_filter + } + /// Read all broadcast files in the broadcast directory. /// /// Example structure: @@ -113,19 +119,12 @@ impl BroadcastReader { return false; } - broadcast.transactions.iter().any(move |tx| { - let name_filter = - tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); - - let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); - - name_filter && type_filter - }) + broadcast.transactions.iter().any(|tx| self.matches_filters(tx)) }) .collect::>(); // Sort by descending timestamp - seqs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + seqs.sort_by_key(|s| std::cmp::Reverse(s.timestamp)); seqs } @@ -146,13 +145,7 @@ impl BroadcastReader { let ScriptSequence { transactions, receipts, .. } = broadcast; let mut targets = Vec::new(); - for tx in transactions.into_iter().filter(|tx| { - let name_filter = tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); - - let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); - - name_filter && type_filter - }) { + for tx in transactions.into_iter().filter(|tx| self.matches_filters(tx)) { let maybe_receipt = receipts .iter() .find(|receipt| tx.hash.is_some_and(|hash| hash == receipt.transaction_hash)); @@ -163,7 +156,7 @@ impl BroadcastReader { } // Sort by descending block number - targets.sort_by(|a, b| b.1.block_number.cmp(&a.1.block_number)); + targets.sort_by_key(|t| std::cmp::Reverse(t.1.block_number)); targets } diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 56b7c6b36f79c..4a7d69093d23b 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -8,7 +8,6 @@ use foundry_config::Config; use serde::{Deserialize, Serialize}; use std::{ collections::VecDeque, - io::{BufWriter, Write}, path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -108,9 +107,7 @@ impl ScriptSequence { // broadcast folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; + fs::write_pretty_json_file(path, &self)?; if save_ts { //../run-[timestamp].json fs::copy(path, path.with_file_name(&ts_name))?; @@ -118,9 +115,7 @@ impl ScriptSequence { // cache folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; - writer.flush()?; + fs::write_pretty_json_file(sensitive_path, &sensitive_script_sequence)?; if save_ts { //../run-[timestamp].json fs::copy(sensitive_path, sensitive_path.with_file_name(&ts_name))?; @@ -166,8 +161,8 @@ impl ScriptSequence { } # /// Gets paths in the formats - /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and - /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. + /// `./broadcast/[contract_filename]/[chain_id]/[sig]-latest.json` and + /// `./cache/[contract_filename]/[chain_id]/[sig]-latest.json`. pub fn get_paths( config: &Config, sig: &str, diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 76b8302fd3c92..aa2bb83d3600c 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -12,6 +12,7 @@ use alloy_primitives::{ use alloy_provider::{Provider, utils::Eip1559Estimation}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; +use alloy_signer::Signer; use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; @@ -22,6 +23,7 @@ use foundry_common::{ shell, }; use foundry_config::Config; +use foundry_wallets::{WalletSigner, wallet_browser::signer::BrowserSigner}; use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; use itertools::Itertools; @@ -64,6 +66,7 @@ pub async fn next_nonce( pub enum SendTransactionKind<'a> { Unlocked(WithOtherFields), Raw(WithOtherFields, &'a EthereumWallet), + Browser(WithOtherFields, &'a BrowserSigner), Signed(TxEnvelope), } @@ -82,7 +85,7 @@ impl<'a> SendTransactionKind<'a> { estimate_via_rpc: bool, estimate_multiplier: u64, ) -> Result<()> { - if let Self::Raw(tx, _) | Self::Unlocked(tx) = self { + if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) = self { if sequential_broadcast { let from = tx.from.expect("no sender"); @@ -131,27 +134,34 @@ impl<'a> SendTransactionKind<'a> { /// - Sign and submit via `eth_sendRawTransaction` for raw transactions /// - Submit pre-signed transaction via `eth_sendRawTransaction` pub async fn send(self, provider: Arc) -> Result { - let pending = match self { + match self { Self::Unlocked(tx) => { debug!("sending transaction from unlocked account {:?}", tx); // Submit the transaction - provider.send_transaction(tx).await? + let pending = provider.send_transaction(tx).await?; + Ok(*pending.tx_hash()) } Self::Raw(tx, signer) => { debug!("sending transaction: {:?}", tx); let signed = tx.build(signer).await?; // Submit the raw transaction - provider.send_raw_transaction(signed.encoded_2718().as_ref()).await? + let pending = provider.send_raw_transaction(signed.encoded_2718().as_ref()).await?; + Ok(*pending.tx_hash()) } Self::Signed(tx) => { debug!("sending transaction: {:?}", tx); - provider.send_raw_transaction(tx.encoded_2718().as_ref()).await? + let pending = provider.send_raw_transaction(tx.encoded_2718().as_ref()).await?; + Ok(*pending.tx_hash()) } - }; + Self::Browser(tx, signer) => { + debug!("sending transaction: {:?}", tx); - Ok(*pending.tx_hash()) + // Sign and send the transaction via the browser wallet + Ok(signer.send_transaction_via_browser(tx.into_inner()).await?) + } + } } /// Prepares and sends the transaction in one operation. @@ -179,12 +189,36 @@ impl<'a> SendTransactionKind<'a> { } } +/// Convenience enum to represent either an Ethereum wallet or a browser signer +pub enum EitherSigner { + Ethereum(EthereumWallet), + Browser(BrowserSigner), +} + +impl From for EitherSigner { + fn from(wallet: EthereumWallet) -> Self { + Self::Ethereum(wallet) + } +} + +impl From for EitherSigner { + fn from(wallet: WalletSigner) -> Self { + EthereumWallet::new(wallet).into() + } +} + +impl From for EitherSigner { + fn from(wallet: BrowserSigner) -> Self { + Self::Browser(wallet) + } +} + /// Represents how to send _all_ transactions pub enum SendTransactionsKind { /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. Unlocked(AddressHashSet), /// Send a signed transaction via `eth_sendRawTransaction` - Raw(AddressHashMap), + Raw(AddressHashMap), } impl SendTransactionsKind { @@ -205,7 +239,12 @@ impl SendTransactionsKind { } Self::Raw(wallets) => { if let Some(wallet) = wallets.get(addr) { - Ok(SendTransactionKind::Raw(tx, wallet)) + match wallet { + EitherSigner::Ethereum(wallet) => Ok(SendTransactionKind::Raw(tx, wallet)), + EitherSigner::Browser(signer) => { + Ok(SendTransactionKind::Browser(tx, signer)) + } + } } else { bail!("No matching signer for {:?} found", addr) } @@ -282,11 +321,12 @@ impl BundledState { let send_kind = if self.args.unlocked { SendTransactionsKind::Unlocked(required_addresses.clone()) } else { - let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let signers: Vec
= + self.script_wallets.signers().map_err(|e| eyre::eyre!("{e}"))?; let mut missing_addresses = Vec::new(); for addr in &required_addresses { - if !signers.contains_key(addr) { + if !signers.contains(addr) { missing_addresses.push(addr); } } @@ -295,14 +335,16 @@ impl BundledState { eyre::bail!( "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", missing_addresses, - signers.keys().collect::>() + signers ); } - let signers = signers - .into_iter() - .map(|(addr, signer)| (addr, EthereumWallet::new(signer))) - .collect(); + let (signers, browser) = self.script_wallets.into_multi_wallet().into_signers()?; + let mut signers: AddressHashMap = + signers.into_iter().map(|(addr, signer)| (addr, signer.into())).collect(); + if let Some(browser) = browser { + signers.insert(browser.address(), browser.into()); + } SendTransactionsKind::Raw(signers) }; @@ -504,8 +546,10 @@ impl BundledState { (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price) }); let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); - let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u64, 9) - .unwrap_or_else(|_| "N/A".to_string()); + let avg_gas_price = total_gas_price + .checked_div(sequence.receipts.len() as u64) + .and_then(|avg| format_units(avg, 9).ok()) + .unwrap_or_else(|| "N/A".to_string()); let token_symbol = NamedChain::try_from(sequence.chain) .unwrap_or_default() diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 0e6d1fd302656..9044d635666e0 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -134,7 +134,7 @@ pub struct ScriptArgs { /// Send via `eth_sendTransaction` using the `--sender` argument as sender. #[arg( long, - conflicts_with_all = &["private_key", "private_keys", "ledger", "trezor", "aws"], + conflicts_with_all = &["private_key", "private_keys", "ledger", "trezor", "aws", "browser"], )] pub unlocked: bool, @@ -233,6 +233,15 @@ impl ScriptArgs { if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; + } else if self.evm.sender.is_none() { + // If no sender was explicitly set via --sender and there's exactly one signer + // (e.g. from --account or --keystore), use that signer's address as the sender. + // This makes --account behave consistently with --private-key. + if let Ok(signers) = script_wallets.signers() + && signers.len() == 1 + { + evm_opts.sender = signers[0]; + } } let script_config = ScriptConfig::new(config, evm_opts).await?; @@ -340,9 +349,13 @@ impl ScriptArgs { Ok(()) } - /// In case the user has loaded *only* one private-key, we can assume that he's using it as the - /// `--sender` + /// In case the user has loaded *only* one private-key or a single remote signer (e.g., + /// Turnkey), we can assume that they're using it as the `--sender`. fn maybe_load_private_key(&self) -> Result> { + if let Some(turnkey_address) = self.wallets.turnkey_address() { + return Ok(Some(turnkey_address)); + } + let maybe_sender = self .wallets .private_keys()? @@ -646,6 +659,7 @@ impl ScriptConfig { let mut builder = ExecutorBuilder::new() .inspectors(|stack| { stack + .logs(self.config.live_logs) .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) .networks(self.evm_opts.networks) .create2_deployer(self.evm_opts.create2_deployer) diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs index fb8f07d0d425b..128df9460e95b 100644 --- a/crates/script/src/multi_sequence.rs +++ b/crates/script/src/multi_sequence.rs @@ -6,10 +6,7 @@ use foundry_common::{fs, shell}; use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; -use std::{ - io::{BufWriter, Write}, - path::PathBuf, -}; +use std::path::PathBuf; /// Holds the sequences of multiple chain deployments. #[derive(Clone, Default, Serialize, Deserialize)] @@ -119,9 +116,7 @@ impl MultiChainSequence { // broadcast writes //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; + fs::write_pretty_json_file(&self.path, self)?; if save_ts { //../Contract-[timestamp]/run.json @@ -133,9 +128,7 @@ impl MultiChainSequence { // cache writes //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; - writer.flush()?; + fs::write_pretty_json_file(&self.sensitive_path, &sensitive_sequence)?; if save_ts { //../Contract-[timestamp]/run.json diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs index 314c4d4983f3a..df2d2ba2dae02 100644 --- a/crates/script/src/progress.rs +++ b/crates/script/src/progress.rs @@ -242,7 +242,11 @@ impl ScriptProgress { Ok(TxStatus::Success(receipt)) => { trace!(tx_hash=?tx_hash, "received tx receipt"); - let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + let msg = format_receipt( + deployment_sequence.chain.into(), + &receipt, + Some(deployment_sequence), + ); seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; deployment_sequence.remove_pending(receipt.transaction_hash); @@ -255,7 +259,11 @@ impl ScriptProgress { warn!(tx_hash=?tx_hash, "Transaction Failure"); deployment_sequence.remove_pending(receipt.transaction_hash); - let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + let msg = format_receipt( + deployment_sequence.chain.into(), + &receipt, + Some(deployment_sequence), + ); seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index 169153f996a28..478ccbf8ab7f9 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,8 +1,9 @@ use alloy_chains::{Chain, NamedChain}; -use alloy_network::AnyTransactionReceipt; +use alloy_network::{AnyTransactionReceipt, ReceiptResponse}; use alloy_primitives::{TxHash, U256, utils::format_units}; use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; use eyre::{Result, eyre}; +use forge_script_sequence::ScriptSequence; use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell}; use std::time::Duration; @@ -24,11 +25,7 @@ pub enum TxStatus { impl From for TxStatus { fn from(receipt: AnyTransactionReceipt) -> Self { - if !receipt.inner.inner.inner.receipt.status.coerce_status() { - Self::Revert(receipt) - } else { - Self::Success(receipt) - } + if !receipt.status() { Self::Revert(receipt) } else { Self::Success(receipt) } } } @@ -89,34 +86,67 @@ pub async fn check_tx_status( } /// Prints parts of the receipt to stdout -pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { +pub fn format_receipt( + chain: Chain, + receipt: &AnyTransactionReceipt, + sequence: Option<&ScriptSequence>, +) -> String { let gas_used = receipt.gas_used; let gas_price = receipt.effective_gas_price; let block_number = receipt.block_number.unwrap_or_default(); let success = receipt.inner.inner.inner.receipt.status.coerce_status(); + let (contract_name, function) = sequence + .and_then(|seq| { + seq.transactions + .iter() + .find(|tx| tx.hash == Some(receipt.transaction_hash)) + .map(|tx| (tx.contract_name.clone(), tx.function.clone())) + }) + .unwrap_or((None, None)); + if shell::is_json() { - let _ = sh_println!( - "{}", - serde_json::json!({ - "chain": chain, - "status": if success { - "success" - } else { - "failed" - }, - "tx_hash": receipt.transaction_hash, - "contract_address": receipt.contract_address.map(|addr| addr.to_string()), - "block_number": block_number, - "gas_used": gas_used, - "gas_price": gas_price, - }) - ); + let mut json = serde_json::json!({ + "chain": chain, + "status": if success { + "success" + } else { + "failed" + }, + "tx_hash": receipt.transaction_hash, + "contract_address": receipt.contract_address.map(|addr| addr.to_string()), + "block_number": block_number, + "gas_used": gas_used, + "gas_price": gas_price, + }); + + if let Some(name) = &contract_name + && !name.is_empty() + { + json["contract_name"] = serde_json::Value::String(name.clone()); + } + if let Some(func) = &function + && !func.is_empty() + { + json["function"] = serde_json::Value::String(func.clone()); + } + + let _ = sh_println!("{}", json); String::new() } else { + let contract_info = match &contract_name { + Some(name) if !name.is_empty() => format!("\nContract: {name}"), + _ => String::new(), + }; + + let function_info = match &function { + Some(func) if !func.is_empty() => format!("\nFunction: {func}"), + _ => String::new(), + }; + format!( - "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n", + "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_info}{function_info}{contract_address}\nBlock: {block_number}\n{gas}\n\n", status = if success { "✅ [Success]" } else { "❌ [Failed]" }, tx_hash = receipt.transaction_hash, contract_address = if let Some(addr) = &receipt.contract_address { @@ -145,3 +175,92 @@ pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + use std::collections::VecDeque; + + fn mock_receipt(tx_hash: B256, success: bool) -> AnyTransactionReceipt { + serde_json::from_value(serde_json::json!({ + "type": "0x02", "status": if success { "0x1" } else { "0x0" }, + "cumulativeGasUsed": "0x5208", "logs": [], "transactionHash": tx_hash, + "logsBloom": format!("0x{}", "0".repeat(512)), + "transactionIndex": "0x0", "blockHash": B256::ZERO, "blockNumber": "0x3039", + "gasUsed": "0x5208", "effectiveGasPrice": "0x4a817c800", + "from": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000000", "contractAddress": null + })) + .unwrap() + } + + fn mock_sequence(tx_hash: B256, contract: Option<&str>, func: Option<&str>) -> ScriptSequence { + let tx = serde_json::from_value(serde_json::json!({ + "hash": tx_hash, "transactionType": "CALL", + "contractName": contract, "contractAddress": null, "function": func, + "arguments": null, "additionalContracts": [], "isFixedGasLimit": false, + "transaction": { + "type": "0x02", "chainId": "0x1", "nonce": "0x0", "gas": "0x5208", + "maxFeePerGas": "0x4a817c800", "maxPriorityFeePerGas": "0x3b9aca00", + "to": "0x0000000000000000000000000000000000000000", + "value": "0x0", "input": "0x", "accessList": [] + }, + })) + .unwrap(); + ScriptSequence { transactions: VecDeque::from([tx]), chain: 1, ..Default::default() } + } + + #[test] + fn format_receipt_displays_contract_and_function() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some("MyContract"), Some("init(address)")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), Some(&seq)); + + assert!(out.contains("Contract: MyContract")); + assert!(out.contains("Function: init(address)")); + assert!(out.contains("✅ [Success]")); + } + + #[test] + fn format_receipt_without_sequence_omits_metadata() { + let hash = B256::repeat_byte(0x42); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), None); + + assert!(!out.contains("Contract:")); + assert!(!out.contains("Function:")); + } + + #[test] + fn format_receipt_skips_empty_contract_name() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some(""), Some("transfer(address)")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), Some(&seq)); + + assert!(!out.contains("Contract:")); + assert!(out.contains("Function: transfer(address)")); + } + + #[test] + fn format_receipt_handles_missing_tx_in_sequence() { + let seq = mock_sequence(B256::repeat_byte(0x99), Some("Other"), Some("other()")); + let out = format_receipt( + Chain::mainnet(), + &mock_receipt(B256::repeat_byte(0x42), true), + Some(&seq), + ); + + assert!(!out.contains("Contract:")); + assert!(!out.contains("Function:")); + } + + #[test] + fn format_receipt_shows_contract_on_failure() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some("FailContract"), Some("fail()")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, false), Some(&seq)); + + assert!(out.contains("❌ [Failed]")); + assert!(out.contains("Contract: FailContract")); + } +} diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 0b2be58ca30d2..05834ed8bb036 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -59,7 +59,7 @@ impl PreSimulationState { .map(|tx| { let rpc = tx.rpc.expect("missing broadcastable tx rpc url"); let sender = tx.transaction.from().expect("all transactions should have a sender"); - let nonce = tx.transaction.nonce().expect("all transactions should have a sender"); + let nonce = tx.transaction.nonce().expect("all transactions should have a nonce"); let to = tx.transaction.to(); let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index ceec637e66661..cc460f732b4d7 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -119,6 +119,7 @@ impl VerifyBundle { if artifact.source.extension().is_some_and(|e| e.to_str() == Some("vy")) { warn!("Skipping verification of Vyper contract: {}", artifact.name); + return None; } // Strip artifact profile from contract name when creating contract info. diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 088f8f921fde8..6d98a26d79cb3 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -391,7 +391,7 @@ edition = "2021" } fn write_mod_name(contents: &mut String, name: &str) -> Result<()> { - if syn::parse_str::(&format!("pub mod {name};")).is_ok() { + if syn::parse_str::(name).is_ok() { write!(contents, "pub mod {name};")?; } else { write!(contents, "pub mod r#{name};")?; diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 82c8c8e1b9e05..d29f6358e93d2 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -15,10 +15,12 @@ repository.workspace = true workspace = true [dependencies] +foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util"] } foundry-config.workspace = true +alloy-chains.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true @@ -42,4 +44,3 @@ idna_adapter.workspace = true [dev-dependencies] tokio.workspace = true -foundry-block-explorers.workspace = true diff --git a/crates/test-utils/src/etherscan.rs b/crates/test-utils/src/etherscan.rs new file mode 100644 index 0000000000000..7ba33e0bbfbf5 --- /dev/null +++ b/crates/test-utils/src/etherscan.rs @@ -0,0 +1,32 @@ +//! Etherscan utilities for tests. + +use alloy_chains::Chain; +use alloy_primitives::Address; +use eyre::Result; +use foundry_block_explorers::Client; +use foundry_common::{compile::etherscan_project, flatten}; +use std::str::FromStr; + +/// Fetches the source code of a verified contract from Etherscan, flattens it, and returns it. +/// +/// This provides the same functionality as `cast source --flatten` but using the library directly, +/// avoiding the need to shell out to the `cast` binary. +pub async fn fetch_etherscan_source_flattened( + address: &str, + etherscan_api_key: &str, + chain: Chain, +) -> Result { + let client = Client::builder().chain(chain)?.with_api_key(etherscan_api_key).build()?; + + let address = Address::from_str(address)?; + let metadata = client.contract_source_code(address).await?; + let Some(metadata) = metadata.items.first() else { + eyre::bail!("Empty contract source code for {address}") + }; + + let tmp = tempfile::tempdir()?; + let project = etherscan_project(metadata, tmp.path())?; + let target_path = project.find_contract_path(&metadata.contract_name)?; + + flatten(project, &target_path) +} diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index ce195026db52b..64c68d69a5415 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -14,6 +14,7 @@ extern crate tracing; #[macro_use] mod macros; +pub mod etherscan; pub mod rpc; pub mod fd_lock; diff --git a/crates/test-utils/src/prj.rs b/crates/test-utils/src/prj.rs index 63fb09e637328..098eab059c075 100644 --- a/crates/test-utils/src/prj.rs +++ b/crates/test-utils/src/prj.rs @@ -1,11 +1,8 @@ use crate::{init_tracing, rpc::rpc_endpoints}; use eyre::{Result, WrapErr}; use foundry_compilers::{ - ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, - artifacts::Contract, - cache::CompilerCache, - compilers::multi::MultiCompiler, - project_util::{TempProject, copy_dir}, + ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, artifacts::Contract, + cache::CompilerCache, compilers::multi::MultiCompiler, project_util::TempProject, solc::SolcSettings, }; use foundry_config::Config; @@ -25,7 +22,7 @@ use std::{ }, }; -use crate::util::{SOLC_VERSION, pretty_err}; +use crate::util::{SOLC_VERSION, copy_dir_filtered, pretty_err}; static CURRENT_DIR_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); @@ -356,12 +353,12 @@ impl TestProject { config_paths_exist(&paths, self.inner.project().cached); } - /// Copies the project's root directory to the given target + /// Copies the project's root directory to the given target, excluding build artifacts. #[track_caller] pub fn copy_to(&self, target: impl AsRef) { let target = target.as_ref(); pretty_err(target, fs::create_dir_all(target)); - pretty_err(target, copy_dir(self.root(), target)); + pretty_err(target, copy_dir_filtered(self.root(), target)); } /// Creates a file with contents `contents` in the test project's directory. The @@ -820,7 +817,7 @@ fn test_redactions() -> snapbox::Redactions { ("[GAS_COST]", r"[Gg]as cost\s*\(\d+\)"), ("[GAS_LIMIT]", r"[Gg]as limit\s*\(\d+\)"), ("[AVG_GAS]", r"μ: \d+, ~: \d+"), - ("[FILE]", r"-->.*\.sol"), + ("[FILE]", r"(-->|╭▸).*\.sol"), ("[FILE]", r"Location(.|\n)*\.rs(.|\n)*Backtrace"), ("[COMPILING_FILES]", r"Compiling \d+ files?"), ("[TX_HASH]", r"Transaction hash: 0x[0-9A-Fa-f]{64}"), @@ -836,6 +833,7 @@ fn test_redactions() -> snapbox::Redactions { "[ESTIMATED_AMOUNT_REQUIRED]", r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", ), + ("[SEED]", r"Fuzz seed: 0x[0-9A-Fa-f]+"), ]) }); REDACTIONS.clone() diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index cccde297ef967..8a31098e824b8 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -44,30 +44,28 @@ shuffled_list!( HTTP_ARCHIVE_DOMAINS, vec![ // - "reth-ethereum.ithaca.xyz/rpc", + "ethereum.reth.rs/rpc", ], ); shuffled_list!( HTTP_DOMAINS, vec![ // - "reth-ethereum.ithaca.xyz/rpc", - // "reth-ethereum-full.ithaca.xyz/rpc", + "ethereum.reth.rs/rpc", ], ); shuffled_list!( WS_ARCHIVE_DOMAINS, vec![ // - "reth-ethereum.ithaca.xyz/ws", + "ethereum.reth.rs/ws", ], ); shuffled_list!( WS_DOMAINS, vec![ // - "reth-ethereum.ithaca.xyz/ws", - // "reth-ethereum-full.ithaca.xyz/ws", + "ethereum.reth.rs/ws", ], ); @@ -108,6 +106,9 @@ pub fn rpc_endpoints() -> RpcEndpoints { ("bsc", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::BinanceSmartChain))), ("avaxTestnet", RpcEndpointUrl::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), ("moonbeam", RpcEndpointUrl::Url("https://moonbeam-rpc.publicnode.com".into())), + ("polkadotTestnet", RpcEndpointUrl::Url("https://eth-rpc-testnet.polkadot.io".into())), + ("kusama", RpcEndpointUrl::Url("https://eth-rpc-kusama.polkadot.io".into())), + ("polkadot", RpcEndpointUrl::Url("https://eth-rpc.polkadot.io".into())), ("rpcEnvAlias", RpcEndpointUrl::Env("${RPC_ENV_ALIAS}".into())), ]) } @@ -136,12 +137,12 @@ pub fn next_ws_endpoint(chain: NamedChain) -> String { next_url(true, chain) } -/// Returns a websocket URL that has access to archive state +/// Returns an HTTP URL that has access to archive state pub fn next_http_archive_rpc_url() -> String { next_archive_url(false) } -/// Returns an HTTP URL that has access to archive state +/// Returns a websocket URL that has access to archive state pub fn next_ws_archive_rpc_url() -> String { next_archive_url(true) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index e265581d35a59..6fdcbafb94404 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,24 +1,24 @@ -use foundry_compilers::{ - Project, ProjectCompileOutput, Vyper, project_util::copy_dir, utils::RuntimeOrHandle, -}; +use foundry_compilers::{Project, ProjectCompileOutput, Vyper, utils::RuntimeOrHandle}; use foundry_config::Config; use std::{ env, fs::{self, File}, - io::{IsTerminal, Read, Seek, Write}, + io::{Read, Seek, Write}, path::{Path, PathBuf}, process::Command, sync::LazyLock, }; +/// Directories to skip when copying project directories. +/// These are build artifacts and runtime-generated files that should not be copied to temp +/// workspaces. +const SKIP_DIRS: &[&str] = &["out", "cache", "broadcast"]; + pub use crate::{ext::*, prj::*}; /// The commit of forge-std to use. pub const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); -/// Stores whether `stdout` is a tty / terminal. -pub static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); - /// Global default template path. Contains the global template project from which all other /// temp projects are initialized. See [`initialize()`] for more info. static TEMPLATE_PATH: LazyLock = @@ -30,7 +30,7 @@ static TEMPLATE_LOCK: LazyLock = LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template.lock")); /// The default Solc version used when compiling tests. -pub const SOLC_VERSION: &str = "0.8.30"; +pub const SOLC_VERSION: &str = "0.8.33"; /// Another Solc version used when compiling tests. /// @@ -101,8 +101,8 @@ pub fn initialize(target: &Path) { // Remove the existing template, if any. let _ = fs::remove_dir_all(tpath); - // Copy the template to the global template path. - pretty_err(tpath, copy_dir(prj.root(), tpath)); + // Copy the template to the global template path, excluding build artifacts. + pretty_err(tpath, copy_dir_filtered(prj.root(), tpath)); // Update lockfile to mark that template is initialized. write.set_len(0).unwrap(); @@ -117,7 +117,7 @@ pub fn initialize(target: &Path) { test_debug!("- copying template dir from {}", tpath.display()); pretty_err(target, fs::create_dir_all(target)); - pretty_err(target, copy_dir(tpath, target)); + pretty_err(target, copy_dir_filtered(tpath, target)); } /// Compile the project with a lock for the cache. @@ -225,3 +225,52 @@ pub fn read_string(path: impl AsRef) -> String { let path = path.as_ref(); pretty_err(path, std::fs::read_to_string(path)) } + +/// Copies the directory at `src` to `dst`, skipping build artifact directories. +/// +/// This is similar to `foundry_compilers::project_util::copy_dir`, but skips directories +/// like `out/`, `cache/`, and `broadcast/` which are build artifacts that should not be +/// copied to temporary test workspaces. +pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { + fs::create_dir_all(dst)?; + // Canonicalize the source once and treat it as the base directory for all traversal. + let base = src.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, true) +} + +fn copy_dir_filtered_inner( + src: &Path, + dst: &Path, + base: &Path, + is_root: bool, +) -> std::io::Result<()> { + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + let src_path = entry.path(); + // Ensure that any path we operate on stays within the original base directory. + let canonical_src_path = src_path.canonicalize()?; + if !canonical_src_path.starts_with(base) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed base directory", + )); + } + let dst_path = dst.join(entry.file_name()); + + if ty.is_dir() { + // Skip build artifact directories at the root level + if is_root + && let Some(name) = entry.file_name().to_str() + && SKIP_DIRS.contains(&name) + { + continue; + } + fs::create_dir_all(&dst_path)?; + copy_dir_filtered_inner(&src_path, &dst_path, base, false)?; + } else { + fs::copy(&canonical_src_path, &dst_path)?; + } + } + Ok(()) +} diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 67301a6c83dcf..efabcab549006 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -259,7 +259,7 @@ impl VerifyBytecodeArgs { gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128); } - configure_tx_req_env(&mut env.as_env_mut(), &gen_tx_req, None) + configure_tx_req_env(&mut env.as_env_mut(), &gen_tx_req) .wrap_err("Failed to configure tx request env")?; // Seed deployer account with funds @@ -335,6 +335,7 @@ impl VerifyBytecodeArgs { ); }; + let creation_block = transaction.block_number; let mut transaction: TransactionRequest = match transaction.inner.inner.inner() { AnyTxEnvelope::Ethereum(tx) => tx.clone().into(), AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"), @@ -440,13 +441,7 @@ impl VerifyBytecodeArgs { Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, Some(_) => eyre::bail!("Invalid block number"), None => { - let provider = utils::get_provider(&config)?; - provider - .get_transaction_by_hash(creation_data.transaction_hash) - .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| { - eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) - })? - .block_number.ok_or_else(|| { + creation_block.ok_or_else(|| { eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag") })? } @@ -495,7 +490,7 @@ impl VerifyBytecodeArgs { break; } - configure_tx_env(&mut env.as_env_mut(), &tx.inner); + configure_tx_env(&mut env.as_env_mut(), tx); if let TxKind::Call(_) = tx.inner.kind() { executor.transact_with_env(env.clone()).wrap_err_with(|| { @@ -538,7 +533,7 @@ impl VerifyBytecodeArgs { } // configure_req__env(&mut env, &transaction.inner); - configure_tx_req_env(&mut env.as_env_mut(), &transaction, None) + configure_tx_req_env(&mut env.as_env_mut(), &transaction) .wrap_err("Failed to configure tx request env")?; let fork_address = crate::utils::deploy_contract( diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 2d9b465c00b85..b55f8f7669ce1 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -267,9 +267,8 @@ impl EtherscanVerificationProvider { || (verifier_type.is_sourcify() && etherscan_key.is_some()); let etherscan_config = config.get_etherscan_config_with_chain(Some(chain))?; - let etherscan_api_url = verifier_url.or(None).map(str::to_owned); - - let api_url = etherscan_api_url.as_deref(); + let api_url = + verifier_url.or_else(|| etherscan_config.as_ref().map(|c| c.api_url.as_str())); let base_url = etherscan_config .as_ref() .and_then(|c| c.browser_url.as_deref()) @@ -419,9 +418,9 @@ impl EtherscanVerificationProvider { .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; let maybe_creation_code = if receipt.contract_address == Some(args.address) { - transaction.inner.inner.input() + transaction.input() } else if transaction.to() == Some(DEFAULT_CREATE2_DEPLOYER) { - &transaction.inner.inner.input()[32..] + &transaction.input()[32..] } else { eyre::bail!( "Fetching of constructor arguments is not supported for contracts created by contracts" @@ -492,10 +491,8 @@ mod tests { let etherscan = EtherscanVerificationProvider::default(); let client = etherscan.client(&args.etherscan, &args.verifier, &config).unwrap(); - assert_eq!( - client.etherscan_api_url().as_str(), - "https://api.etherscan.io/v2/api?chainid=80002" - ); + // Custom URL from foundry.toml should be used + assert_eq!(client.etherscan_api_url().as_str(), "https://amoy.polygonscan.com/"); assert!(format!("{client:?}").contains("dummykey")); @@ -552,10 +549,8 @@ mod tests { let client = etherscan.client(&args.etherscan, &args.verifier, &config).unwrap(); - assert_eq!( - client.etherscan_api_url().as_str(), - "https://api.etherscan.io/v2/api?chainid=80002" - ); + // Custom URL from foundry.toml should be used + assert_eq!(client.etherscan_api_url().as_str(), "https://amoy.polygonscan.com/"); assert!(format!("{client:?}").contains("dummykey")); let args: VerifyArgs = VerifyArgs::parse_from([ diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 03315ae024176..62124f6613382 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -8,11 +8,8 @@ use async_trait::async_trait; use eyre::{OptionExt, Result}; use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ - Graph, Project, - artifacts::{Metadata, Source, output_selection::OutputSelection}, - compilers::solc::SolcCompiler, - multi::{MultiCompilerParser, MultiCompilerSettings}, - solc::Solc, + Project, artifacts::output_selection::OutputSelection, compilers::solc::SolcCompiler, + multi::MultiCompilerSettings, solc::Solc, }; use foundry_config::{Chain, Config, EtherscanConfigError}; use semver::Version; @@ -50,7 +47,7 @@ impl VerificationContext { pub fn get_target_abi(&self) -> Result { let mut project = self.project.clone(); project.update_output_selection(|selection| { - *selection = OutputSelection::common_output_selection(["abi".to_string()]) + *selection = OutputSelection::common_output_selection(["abi".to_string()]); }); let output = ProjectCompiler::new() @@ -64,34 +61,6 @@ impl VerificationContext { artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") } - - /// Compiles target file requesting only metadata and returns it. - pub fn get_target_metadata(&self) -> Result { - let mut project = self.project.clone(); - project.update_output_selection(|selection| { - *selection = OutputSelection::common_output_selection(["metadata".to_string()]); - }); - - let output = ProjectCompiler::new() - .quiet(true) - .files([self.target_path.clone()]) - .compile(&project)?; - - let artifact = output - .find(&self.target_path, &self.target_name) - .ok_or_eyre("failed to find target artifact when compiling for metadata")?; - - artifact.metadata.clone().ok_or_eyre("target artifact does not have metadata") - } - - /// Returns [Vec] containing imports of the target file. - pub fn get_target_imports(&self) -> Result> { - let mut sources = self.project.paths.read_input_files()?; - sources.insert(self.target_path.clone(), Source::read(&self.target_path)?); - let graph = Graph::::resolve_sources(&self.project.paths, sources)?; - - Ok(graph.imports(&self.target_path).into_iter().map(Into::into).collect()) - } } /// An abstraction for various verification providers such as etherscan, sourcify, blockscout @@ -189,6 +158,7 @@ impl VerificationProviderType { if self.is_etherscan() { if let Some(chain) = chain && chain.etherscan_urls().is_none() + && !has_url { eyre::bail!(EtherscanConfigError::UnknownChain( "when using Etherscan verifier".to_string(), @@ -229,3 +199,27 @@ impl VerificationProviderType { matches!(self, Self::Etherscan) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn etherscan_allows_unknown_chain_with_verifier_url() { + let chain = Chain::from(3658348u64); + let res = VerificationProviderType::Etherscan.client(Some("key"), Some(chain), true); + assert!(res.is_ok()); + } + + #[test] + fn etherscan_rejects_unknown_chain_without_verifier_url() { + let chain = Chain::from(3658348u64); + let res = VerificationProviderType::Etherscan.client(Some("key"), Some(chain), false); + match res { + Ok(_) => panic!("expected unknown-chain error"), + Err(err) => { + assert!(err.to_string().contains("No known Etherscan API URL")); + } + } + } +} diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 603fd8d99b775..3aacfc1926996 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -20,8 +20,13 @@ use foundry_common::{ use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; use foundry_evm::{ - Env, EnvMut, constants::DEFAULT_CREATE2_DEPLOYER, core::AsEnvMut, executors::TracingExecutor, - opts::EvmOpts, traces::TraceMode, utils::apply_chain_and_block_specific_env_changes, + Env, EnvMut, + constants::DEFAULT_CREATE2_DEPLOYER, + core::{AsEnvMut, decode::RevertDecoder}, + executors::TracingExecutor, + opts::EvmOpts, + traces::TraceMode, + utils::apply_chain_and_block_specific_env_changes, }; use foundry_evm_networks::NetworkConfigs; use reqwest::Url; @@ -320,9 +325,28 @@ pub fn deploy_contract( let result = executor.transact_with_env(env)?; trace!(transact_result = ?result.exit_reason); + + if result.reverted { + let decoded_reason = if result.result.is_empty() { + String::new() + } else { + format!(": {}", RevertDecoder::default().decode(&result.result, result.exit_reason)) + }; + eyre::bail!( + "Failed to deploy contract via CREATE2 on fork at block{decoded_reason}.\n\ + This typically happens when your local bytecode differs from what was actually deployed.\n\ + Common causes:\n\ + - Your contract source is not at the same commit used during deployment\n\ + - Cached build artifacts are stale (try `forge clean && forge build`)\n\ + - Compiler settings (optimizer, evm_version, via_ir) don't match the deployment" + ); + } + if result.result.len() != 20 { eyre::bail!( - "Failed to deploy contract on fork at block: call result is not exactly 20 bytes" + "Failed to deploy contract via CREATE2 on fork at block: deployer returned {} bytes instead of 20.\n\ + This may indicate a bytecode mismatch - ensure your source code matches the deployed contract.", + result.result.len() ); } @@ -358,11 +382,8 @@ pub async fn get_runtime_codes( ) })?; - let onchain_runtime_code = if let Some(block) = block { - provider.get_code_at(address).block_id(BlockId::number(block)).await? - } else { - provider.get_code_at(address).await? - }; + let block_id = block.map_or_else(BlockId::latest, BlockId::number); + let onchain_runtime_code = provider.get_code_at(address).block_id(block_id).await?; Ok((fork_runtime_code, onchain_runtime_code)) } diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index f9ce32a415633..cab8462ebb25e 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -16,7 +16,9 @@ use foundry_cli::{ }; use foundry_common::{ContractsByArtifact, compile::ProjectCompiler}; use foundry_compilers::{artifacts::EvmVersion, compilers::solc::Solc, info::ContractInfo}; -use foundry_config::{Config, SolcReq, figment, impl_figment_convert, impl_figment_convert_cast}; +use foundry_config::{ + Chain, Config, SolcReq, figment, impl_figment_convert, impl_figment_convert_cast, +}; use itertools::Itertools; use reqwest::Url; use semver::BuildMetadata; @@ -248,6 +250,14 @@ impl VerifyArgs { self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + // For chains with Sourcify-compatible APIs, use the chain's URL from etherscan_urls + if self.verifier.verifier.is_sourcify() + && self.verifier.verifier_url.is_none() + && let Some(url) = sourcify_api_url(chain) + { + self.verifier.verifier_url = Some(url); + } + if self.show_standard_json_input { let args = EtherscanVerificationProvider::default() .create_verify_request(&self, &context) @@ -275,18 +285,17 @@ impl VerifyArgs { self.verifier.verifier.client(self.etherscan.key().as_deref(), self.etherscan.chain, self.verifier.verifier_url.is_some())?.verify(self, context).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { - Ok(url) => { - if is_host_only(&url) { - return err.wrap_err(format!( - "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" - )) - } + Ok(url) if is_host_only(&url) => { + return err.wrap_err(format!( + "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" + )) } Err(url_err) => { return err.wrap_err(format!( "Invalid URL {verifier_url} provided: {url_err}" )) } + _ => {} } } @@ -412,7 +421,7 @@ impl VerifyArgs { &project.settings } else { eyre::bail!( - "If cache is disabled, compilation profile must be provided with `--compiler-version` option or set in foundry.toml" + "If cache is disabled, compilation profile must be provided with `--compilation-profile` option or set in foundry.toml" ) }; @@ -528,6 +537,21 @@ impl figment::Provider for VerifyCheckArgs { } } +/// Returns the Sourcify-compatible API URL for chains that have one registered in `etherscan_urls`. +/// +/// Some chains register their Sourcify-compatible verification API under `etherscan_urls` in +/// alloy-chains. This function returns the properly formatted URL for such chains. +fn sourcify_api_url(chain: Chain) -> Option { + if chain.is_custom_sourcify() { + chain.etherscan_urls().map(|(api_url, _)| { + let api_url = api_url.trim_end_matches('/'); + format!("{api_url}/") + }) + } else { + None + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index 24013c74f925a..e68de25e9b06e 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -14,7 +14,6 @@ workspace = true [dependencies] foundry-config.workspace = true -foundry-primitives.workspace = true alloy-primitives.workspace = true alloy-signer = { workspace = true, features = ["eip712"] } @@ -26,10 +25,7 @@ alloy-consensus.workspace = true alloy-sol-types.workspace = true alloy-dyn-abi.workspace = true -tempo-primitives.workspace = true - # browser wallet -alloy-rpc-types.workspace = true axum.workspace = true foundry-common.workspace = true serde_json.workspace = true @@ -60,7 +56,6 @@ tracing.workspace = true eth-keystore = "0.5.0" [dev-dependencies] -tokio = { workspace = true, features = ["macros"] } reqwest = { workspace = true, features = ["json"] } [features] diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index e29e6484e2c15..f27e908b63a40 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -21,6 +21,7 @@ pub mod wallet_raw; pub use opts::WalletOpts; pub use signer::{PendingSigner, WalletSigner}; +pub use wallet_browser::opts::BrowserWalletOpts; pub use wallet_multi::MultiWalletOpts; pub use wallet_raw::RawWalletOpts; diff --git a/crates/wallets/src/opts.rs b/crates/wallets/src/opts.rs index 9265a7c79ebbb..d1c4a97690411 100644 --- a/crates/wallets/src/opts.rs +++ b/crates/wallets/src/opts.rs @@ -102,36 +102,6 @@ pub struct WalletOpts { /// See: #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))] pub turnkey: bool, - - /// Use a browser wallet. - #[arg(long, help_heading = "Wallet options - browser")] - pub browser: bool, - - /// Port for the browser wallet server. - #[arg( - long, - help_heading = "Wallet options - browser", - value_name = "PORT", - default_value = "9545", - requires = "browser" - )] - pub browser_port: u16, - - /// Whether to open the browser for wallet connection. - #[arg( - long, - help_heading = "Wallet options - browser", - default_value_t = false, - requires = "browser" - )] - pub browser_disable_open: bool, - - /// Enable development mode for the browser wallet. - /// This relaxes certain security features for local development. - /// - /// **WARNING**: This should only be used in a development environment. - #[arg(long, help_heading = "Wallet options - browser", hide = true)] - pub browser_development: bool, } impl WalletOpts { @@ -169,13 +139,6 @@ impl WalletOpts { eyre::eyre!("TURNKEY_ADDRESS could not be parsed as an Ethereum address") })?; WalletSigner::from_turnkey(api_private_key, organization_id, address)? - } else if self.browser { - WalletSigner::from_browser( - self.browser_port, - !self.browser_disable_open, - self.browser_development, - ) - .await? } else if let Some(raw_wallet) = self.raw.signer()? { raw_wallet } else if let Some(path) = utils::maybe_get_keystore_path( @@ -281,10 +244,6 @@ mod tests { aws: false, gcp: false, turnkey: false, - browser: false, - browser_port: 9545, - browser_development: false, - browser_disable_open: false, }; match wallet.signer().await { Ok(_) => { diff --git a/crates/wallets/src/signer.rs b/crates/wallets/src/signer.rs index 21ab2c8530162..ddbd5a47fe978 100644 --- a/crates/wallets/src/signer.rs +++ b/crates/wallets/src/signer.rs @@ -1,7 +1,7 @@ -use crate::{error::WalletSignerError, wallet_browser::signer::BrowserSigner}; -use alloy_consensus::{Sealed, SignableTransaction}; +use crate::error::WalletSignerError; +use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; -use alloy_network::{NetworkWallet, TransactionBuilder, TxSigner}; +use alloy_network::TxSigner; use alloy_primitives::{Address, B256, ChainId, Signature, hex}; use alloy_signer::Signer; use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; @@ -9,11 +9,7 @@ use alloy_signer_local::{MnemonicBuilder, PrivateKeySigner, coins_bip39::English use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; use alloy_sol_types::{Eip712Domain, SolStruct}; use async_trait::async_trait; -use foundry_primitives::{ - FoundryNetwork, FoundryTransactionRequest, FoundryTxEnvelope, FoundryTypedTx, -}; -use std::{collections::HashSet, path::PathBuf, time::Duration}; -use tempo_primitives::TempoSignature; +use std::{collections::HashSet, path::PathBuf}; use tracing::warn; #[cfg(feature = "aws-kms")] @@ -42,8 +38,6 @@ pub enum WalletSigner { Ledger(LedgerSigner), /// Wrapper around Trezor signer. Trezor(TrezorSigner), - /// Wrapper around browser wallet. - Browser(BrowserSigner), /// Wrapper around AWS KMS signer. #[cfg(feature = "aws-kms")] Aws(AwsSigner), @@ -66,18 +60,6 @@ impl WalletSigner { Ok(Self::Trezor(trezor)) } - pub async fn from_browser( - port: u16, - open_browser: bool, - browser_development: bool, - ) -> Result { - let browser_signer = - BrowserSigner::new(port, open_browser, Duration::from_secs(300), browser_development) - .await - .map_err(|e| WalletSignerError::Browser(e.into()))?; - Ok(Self::Browser(browser_signer)) - } - pub async fn from_aws(key_id: String) -> Result { #[cfg(feature = "aws-kms")] { @@ -164,7 +146,7 @@ impl WalletSigner { let _ = api_private_key; let _ = organization_id; let _ = address; - Err(WalletSignerError::UnsupportedSigner("Turnkey")) + Err(WalletSignerError::turnkey_unsupported()) } } @@ -223,9 +205,6 @@ impl WalletSigner { } } } - Self::Browser(browser) => { - senders.insert(alloy_signer::Signer::address(browser)); - } #[cfg(feature = "aws-kms")] Self::Aws(aws) => { senders.insert(alloy_signer::Signer::address(aws)); @@ -270,7 +249,6 @@ macro_rules! delegate { Self::Local($inner) => $e, Self::Ledger($inner) => $e, Self::Trezor($inner) => $e, - Self::Browser($inner) => $e, #[cfg(feature = "aws-kms")] Self::Aws($inner) => $e, #[cfg(feature = "gcp-kms")] @@ -337,74 +315,6 @@ impl TxSigner for WalletSigner { } } -impl NetworkWallet for WalletSigner { - fn default_signer_address(&self) -> Address { - alloy_signer::Signer::address(self) - } - - fn has_signer_for(&self, address: &Address) -> bool { - self.default_signer_address() == *address - } - - fn signer_addresses(&self) -> impl Iterator { - std::iter::once(self.default_signer_address()) - } - - async fn sign_transaction_from( - &self, - sender: Address, - tx: FoundryTypedTx, - ) -> alloy_signer::Result { - if sender != self.default_signer_address() { - return Err(alloy_signer::Error::other("Signer address mismatch")); - } - - match tx { - FoundryTypedTx::Legacy(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - Ok(FoundryTxEnvelope::Legacy(inner.into_signed(sig))) - } - FoundryTypedTx::Eip2930(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - Ok(FoundryTxEnvelope::Eip2930(inner.into_signed(sig))) - } - FoundryTypedTx::Eip1559(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - Ok(FoundryTxEnvelope::Eip1559(inner.into_signed(sig))) - } - FoundryTypedTx::Eip4844(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - Ok(FoundryTxEnvelope::Eip4844(inner.into_signed(sig))) - } - FoundryTypedTx::Eip7702(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - Ok(FoundryTxEnvelope::Eip7702(inner.into_signed(sig))) - } - FoundryTypedTx::Deposit(inner) => { - // Deposit transactions don't require signing - Ok(FoundryTxEnvelope::Deposit(Sealed::new(inner))) - } - FoundryTypedTx::Tempo(mut inner) => { - let sig = TxSigner::sign_transaction(self, &mut inner).await?; - let tempo_sig: TempoSignature = sig.into(); - Ok(FoundryTxEnvelope::Tempo(inner.into_signed(tempo_sig))) - } - } - } - - #[doc(hidden)] - async fn sign_request( - &self, - request: FoundryTransactionRequest, - ) -> alloy_signer::Result { - let sender = request.from().unwrap_or_else(|| self.default_signer_address()); - let tx = request.build_typed_tx().map_err(|_| { - alloy_signer::Error::other("Failed to build typed transaction from request") - })?; - self.sign_transaction_from(sender, tx).await - } -} - /// Signers that require user action to be obtained. #[derive(Debug, Clone)] pub enum PendingSigner { diff --git a/crates/wallets/src/wallet_browser/app/assets/main.js b/crates/wallets/src/wallet_browser/app/assets/main.js index 88265ee32ed48..060f0198f6c2c 100644 --- a/crates/wallets/src/wallet_browser/app/assets/main.js +++ b/crates/wallets/src/wallet_browser/app/assets/main.js @@ -63,7 +63,7 @@ Resources:`;for(let t of c){if(!RB(t))throw new VB({field:`resources`,metaMessag [zustand devtools middleware] Unsupported __setState action format. When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(), and value of this only key should be a state object. Example: { "type": "__setState", "state": { "abc123Store": { "foo": "bar" } } } - `);let t=e.state[s];if(t==null)return;JSON.stringify(i.getState())!==JSON.stringify(t)&&p(t);return}i.dispatchFromDevtools&&typeof i.dispatch==`function`&&i.dispatch(e)});case`DISPATCH`:switch(e.payload.type){case`RESET`:return p(m),s===void 0?u?.init(i.getState()):u?.init(pV(c.name));case`COMMIT`:if(s===void 0){u?.init(i.getState());return}return u?.init(pV(c.name));case`ROLLBACK`:return vV(e.state,e=>{if(s===void 0){p(e),u?.init(i.getState());return}p(e[s]),u?.init(pV(c.name))});case`JUMP_TO_STATE`:case`JUMP_TO_ACTION`:return vV(e.state,e=>{if(s===void 0){p(e);return}JSON.stringify(i.getState())!==JSON.stringify(e[s])&&p(e[s])});case`IMPORT_STATE`:{let{nextLiftedState:t}=e.payload,n=t.computedStates.slice(-1)[0]?.state;if(!n)return;p(s===void 0?n:n[s]),u?.send(null,t);return}case`PAUSE_RECORDING`:return f=!f}return}}),m},vV=(e,t)=>{let n;try{n=JSON.parse(e)}catch(e){console.error(`[zustand devtools middleware] Could not parse the received json`,e)}n!==void 0&&t(n)},yV=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function bV(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var xV=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return xV(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return xV(t)(e)}}}},SV=(e,t)=>(n,r,i)=>{let a={storage:bV(()=>localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=new Set,c=new Set,l=a.storage;if(!l)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let u=()=>{let e=a.partialize({...r()});return l.setItem(a.name,{state:e,version:a.version})},d=i.setState;i.setState=(e,t)=>(d(e,t),u());let f=e((...e)=>(n(...e),u()),r,i);i.getInitialState=()=>f;let p,m=()=>{if(!l)return;o=!1,s.forEach(e=>e(r()??f));let e=a.onRehydrateStorage?.call(a,r()??f)||void 0;return xV(l.getItem.bind(l))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(e=>{let[t,i]=e;if(p=a.merge(i,r()??f),n(p,!0),t)return u()}).then(()=>{e?.(p,void 0),p=r(),o=!0,c.forEach(e=>e(p))}).catch(t=>{e?.(void 0,t)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(l=e.storage)},clearStorage:()=>{l?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>m(),hasHydrated:()=>o,onHydrate:e=>(s.add(e),()=>{s.delete(e)}),onFinishHydration:e=>(c.add(e),()=>{c.delete(e)})},a.skipHydration||m(),p||f},CV=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},wV=(e=>e?CV(e):CV);function TV(e){return new Promise((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)})}function EV(e,t){let n,r=()=>{if(n)return n;let r=indexedDB.open(e);return r.onupgradeneeded=()=>r.result.createObjectStore(t),n=TV(r),n.then(e=>{e.onclose=()=>n=void 0},()=>{}),n};return(e,n)=>r().then(r=>n(r.transaction(t,e).objectStore(t)))}var DV;function OV(){return DV||=EV(`keyval-store`,`keyval`),DV}function kV(e,t=OV()){return t(`readonly`,t=>TV(t.get(e)))}function AV(e,t,n=OV()){return n(`readwrite`,n=>(n.put(t,e),TV(n.transaction)))}function jV(e,t=OV()){return t(`readwrite`,t=>(t.delete(e),TV(t.transaction)))}function MV(e){return e}function NV(){let e=typeof indexedDB<`u`?EV(`porto`,`store`):void 0;return MV({async getItem(t){let n=await kV(t,e);return n===null?null:n},async removeItem(t){await jV(t,e)},async setItem(t,n){await AV(t,pN(n),e)},sizeLimit:1024*1024*50})}function PV(){let e=new Map;return MV({getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)},sizeLimit:1/0})}var FV=typeof window<`u`&&typeof document<`u`;const IV={announceProvider:!0,chains:CM,mode:FV?lV({host:bN.prod}):cV(),relay:zv(rz.prod.http),storage:FV&&typeof indexedDB<`u`?NV():PV(),storageKey:`porto.store`};function LV(e={}){let t=e.chains??IV.chains,n=Object.fromEntries(t.map(t=>[t.id,e.transports?.[t.id]??zv()])),r={announceProvider:e.announceProvider??IV.announceProvider,authUrl:e.authUrl,chains:t,feeToken:e.feeToken,merchantUrl:e.merchantUrl,mode:e.mode??IV.mode,relay:e.relay??IV.relay,storage:e.storage??IV.storage,storageKey:e.storageKey??IV.storageKey,transports:n},i=wV(_V(yV(SV(e=>({accounts:[],chainIds:r.chains.map(e=>e.id),feeToken:r.feeToken,requestQueue:[]}),{merge(e,t){let n=e,i=r.chains.find(e=>e.id===n.chainIds[0])?.id??r.chains[0].id,a=[i,...r.chains.map(e=>e.id).filter(e=>e!==i)];return{...t,...n,chainIds:a}},name:r.storageKey,partialize:e=>({accounts:e.accounts.map(e=>pN(e)),chainIds:e.chainIds}),storage:r.storage,version:5})))),a=r.mode,o={config:r,getMode(){return a},id:hN(),setMode(e){return c?.(),a=e,c=e.setup({internal:o}),c},store:i},s=gB(o),c=a===null?()=>{}:a.setup({internal:o});return{_internal:o,config:r,destroy(){c(),s._internal.destroy()},provider:s}}const RV=Object.freeze(Object.values(xM)),zV=e=>RV.find(t=>t.id===e);var BV=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`)return;let t=e.trim();if(/^0x[0-9a-fA-F]+$/.test(t)){let e=Number.parseInt(t,16);return Number.isNaN(e)?void 0:e}if(/^\d+$/.test(t)){let e=Number.parseInt(t,10);return Number.isNaN(e)?void 0:e}};const VV=(e,t,n)=>{let r=BV(e);t(r),n(r==null?void 0:zV(r))},HV=async(e,t=`GET`,n)=>{let r={"Content-Type":`application/json`},i=typeof window<`u`?window.__SESSION_TOKEN__:void 0;i&&(r[`X-Session-Token`]=i);let a=await fetch(`http://127.0.0.1:9545${e}`,{method:t,headers:r,body:n===void 0?void 0:JSON.stringify(n)});if(!a.ok)throw Error(`API request failed: ${a.status} ${a.statusText}`);try{return await a.json()}catch{throw Error(`Invalid JSON response`)}},UV=e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t,2),WV=e=>{if(e==null)return UV(e);if(typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`){let t=e;try{let n=JSON.parse(t.message);return UV({...e,message:n})}catch{return UV(e)}}return UV(e)},GV=e=>!!e&&e.status===`ok`;var KV=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=u(s(((e,t)=>{t.exports=KV()}))());function qV(){(0,y.useEffect)(()=>{window.__PORTO__||(window.__PORTO__=LV())},[]);let[e,t]=(0,y.useState)([]),[n,r]=(0,y.useState)(!1),[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(null),[c,l]=(0,y.useState)(null),u=e.find(e=>e.info.uuid===c)??null,[d,f]=(0,y.useState)(),[p,m]=(0,y.useState)(),[h,g]=(0,y.useState)(),[_,v]=(0,y.useState)(null),[b,x]=(0,y.useState)(null),[S,C]=(0,y.useState)(null),w=(0,y.useRef)(null),ee=(0,y.useRef)(null),te=(0,y.useRef)(null),ne=async()=>{if(!u||n)return;let e=await u.provider.request({method:`eth_requestAccounts`});f(e?.[0]??void 0);try{let e=await u.provider.request({method:`eth_chainId`});VV(e,m,g)}catch{m(void 0),g(void 0)}},re=async()=>{if(!(!d||p==null)){try{await HV(`/api/connection`,`POST`,[d,p])}catch{return}r(!0)}},ie=async()=>{if(!u||!o)return;let{id:e,signType:t,request:n}=o,r=n.address,i=n.message;try{let n;switch(t){case`PersonalSign`:n=await u.provider.request({method:`personal_sign`,params:[i,r]});break;case`SignTypedDataV4`:n=await u.provider.request({method:`eth_signTypedData_v4`,params:[r,i]});break;default:throw Error(`Unsupported signType: ${t}`)}await HV(`/api/signing/response`,`POST`,{id:e,signature:n,error:null}),C(n),s(null)}catch(t){let n=typeof t==`object`&&t&&`message`in t&&typeof t.message==`string`?t.message:String(t);try{await HV(`/api/signing/response`,`POST`,{id:e,signature:null,error:n})}catch{}C(null),s(null)}},ae=async()=>{if(!u||!i?.request)return;let e=Mv({transport:Pv(u.provider),chain:h??void 0});try{let t=await u.provider.request({method:`eth_sendTransaction`,params:[i.request]});x(t),await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:t,error:null});let n=await uv(e,{hash:t});v(n)}catch(e){let t=typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?e.message:String(e);console.error(`send failed:`,t);try{await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:null,error:t})}catch{}}},oe=(0,y.useCallback)(()=>{w.current&&=(window.clearInterval(w.current),null),ee.current&&=(window.clearInterval(ee.current),null),a(null),s(null),x(null),v(null),f(void 0),m(void 0),g(void 0),r(!1),HV(`/api/connection`,`POST`,null)},[]);return(0,y.useEffect)(()=>{te.current&&c&&te.current!==c&&oe(),te.current=c},[c,oe]),(0,y.useEffect)(()=>{e.length===1&&!u&&l(e[0].info.uuid)},[e,u]),(0,y.useEffect)(()=>{let e=e=>{let{info:n,provider:r}=e.detail;t(e=>e.some(e=>e.info.uuid===n.uuid)?e:[...e,{info:n,provider:r}])};return window.addEventListener(`eip6963:announceProvider`,e),window.dispatchEvent(new Event(`eip6963:requestProvider`)),()=>window.removeEventListener(`eip6963:announceProvider`,e)},[]),(0,y.useEffect)(()=>{if(!u)return;let e=e=>{n||f(e[0]??void 0)},t=e=>{n||VV(e,m,g)};return u.provider.on?.(`accountsChanged`,e),u.provider.on?.(`chainChanged`,t),()=>{u.provider.removeListener?.(`accountsChanged`,e),u.provider.removeListener?.(`chainChanged`,t)}},[u,n]),(0,y.useEffect)(()=>{if(!n||i||o)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/transaction/request`);GV(n)&&(window.clearInterval(t),e&&a(n.data))}catch{}},1e3);return w.current=t,()=>{e=!1,window.clearInterval(t),w.current===t&&(w.current=null)}},[n,i,o]),(0,y.useEffect)(()=>{if(!n||o||i)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/signing/request`);GV(n)&&(window.clearInterval(t),e&&s(n.data))}catch{}},1e3);return ee.current=t,()=>{e=!1,window.clearInterval(t),ee.current===t&&(ee.current=null)}},[n,o,i]),(0,$.jsx)(`div`,{className:`wrapper`,children:(0,$.jsxs)(`div`,{className:`container`,children:[(0,$.jsx)(`div`,{className:`notice`,children:`Browser wallet is still in early development. Use with caution!`}),(0,$.jsx)(`img`,{className:`banner`,src:`banner.png`,alt:`Foundry Browser Wallet`}),e.length>1&&(0,$.jsx)(`div`,{className:`wallet-selector`,children:(0,$.jsx)(`label`,{children:(0,$.jsxs)(`select`,{value:c??``,onChange:e=>l(e.target.value||null),disabled:n,children:[(0,$.jsx)(`option`,{value:``,disabled:!0,children:`Select wallet…`}),e.map(({info:e})=>(0,$.jsxs)(`option`,{value:e.uuid,children:[e.name,` (`,e.rdns,`)`]},e.uuid))]})})}),e.length===0&&(0,$.jsx)(`p`,{children:`No wallets found.`}),u&&!d&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-connect`,onClick:ne,disabled:n,children:`Connect Wallet`}),u&&d&&!n&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-confirm`,onClick:re,disabled:!d||p==null,children:`Confirm Connection`}),u&&d&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Connected`}),(0,$.jsx)(`pre`,{className:`box`,children:`\ + `);let t=e.state[s];if(t==null)return;JSON.stringify(i.getState())!==JSON.stringify(t)&&p(t);return}i.dispatchFromDevtools&&typeof i.dispatch==`function`&&i.dispatch(e)});case`DISPATCH`:switch(e.payload.type){case`RESET`:return p(m),s===void 0?u?.init(i.getState()):u?.init(pV(c.name));case`COMMIT`:if(s===void 0){u?.init(i.getState());return}return u?.init(pV(c.name));case`ROLLBACK`:return vV(e.state,e=>{if(s===void 0){p(e),u?.init(i.getState());return}p(e[s]),u?.init(pV(c.name))});case`JUMP_TO_STATE`:case`JUMP_TO_ACTION`:return vV(e.state,e=>{if(s===void 0){p(e);return}JSON.stringify(i.getState())!==JSON.stringify(e[s])&&p(e[s])});case`IMPORT_STATE`:{let{nextLiftedState:t}=e.payload,n=t.computedStates.slice(-1)[0]?.state;if(!n)return;p(s===void 0?n:n[s]),u?.send(null,t);return}case`PAUSE_RECORDING`:return f=!f}return}}),m},vV=(e,t)=>{let n;try{n=JSON.parse(e)}catch(e){console.error(`[zustand devtools middleware] Could not parse the received json`,e)}n!==void 0&&t(n)},yV=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function bV(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var xV=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return xV(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return xV(t)(e)}}}},SV=(e,t)=>(n,r,i)=>{let a={storage:bV(()=>localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=new Set,c=new Set,l=a.storage;if(!l)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let u=()=>{let e=a.partialize({...r()});return l.setItem(a.name,{state:e,version:a.version})},d=i.setState;i.setState=(e,t)=>(d(e,t),u());let f=e((...e)=>(n(...e),u()),r,i);i.getInitialState=()=>f;let p,m=()=>{if(!l)return;o=!1,s.forEach(e=>e(r()??f));let e=a.onRehydrateStorage?.call(a,r()??f)||void 0;return xV(l.getItem.bind(l))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(e=>{let[t,i]=e;if(p=a.merge(i,r()??f),n(p,!0),t)return u()}).then(()=>{e?.(p,void 0),p=r(),o=!0,c.forEach(e=>e(p))}).catch(t=>{e?.(void 0,t)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(l=e.storage)},clearStorage:()=>{l?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>m(),hasHydrated:()=>o,onHydrate:e=>(s.add(e),()=>{s.delete(e)}),onFinishHydration:e=>(c.add(e),()=>{c.delete(e)})},a.skipHydration||m(),p||f},CV=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},wV=(e=>e?CV(e):CV);function TV(e){return new Promise((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)})}function EV(e,t){let n,r=()=>{if(n)return n;let r=indexedDB.open(e);return r.onupgradeneeded=()=>r.result.createObjectStore(t),n=TV(r),n.then(e=>{e.onclose=()=>n=void 0},()=>{}),n};return(e,n)=>r().then(r=>n(r.transaction(t,e).objectStore(t)))}var DV;function OV(){return DV||=EV(`keyval-store`,`keyval`),DV}function kV(e,t=OV()){return t(`readonly`,t=>TV(t.get(e)))}function AV(e,t,n=OV()){return n(`readwrite`,n=>(n.put(t,e),TV(n.transaction)))}function jV(e,t=OV()){return t(`readwrite`,t=>(t.delete(e),TV(t.transaction)))}function MV(e){return e}function NV(){let e=typeof indexedDB<`u`?EV(`porto`,`store`):void 0;return MV({async getItem(t){let n=await kV(t,e);return n===null?null:n},async removeItem(t){await jV(t,e)},async setItem(t,n){await AV(t,pN(n),e)},sizeLimit:1024*1024*50})}function PV(){let e=new Map;return MV({getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)},sizeLimit:1/0})}var FV=typeof window<`u`&&typeof document<`u`;const IV={announceProvider:!0,chains:CM,mode:FV?lV({host:bN.prod}):cV(),relay:zv(rz.prod.http),storage:FV&&typeof indexedDB<`u`?NV():PV(),storageKey:`porto.store`};function LV(e={}){let t=e.chains??IV.chains,n=Object.fromEntries(t.map(t=>[t.id,e.transports?.[t.id]??zv()])),r={announceProvider:e.announceProvider??IV.announceProvider,authUrl:e.authUrl,chains:t,feeToken:e.feeToken,merchantUrl:e.merchantUrl,mode:e.mode??IV.mode,relay:e.relay??IV.relay,storage:e.storage??IV.storage,storageKey:e.storageKey??IV.storageKey,transports:n},i=wV(_V(yV(SV(e=>({accounts:[],chainIds:r.chains.map(e=>e.id),feeToken:r.feeToken,requestQueue:[]}),{merge(e,t){let n=e,i=r.chains.find(e=>e.id===n.chainIds[0])?.id??r.chains[0].id,a=[i,...r.chains.map(e=>e.id).filter(e=>e!==i)];return{...t,...n,chainIds:a}},name:r.storageKey,partialize:e=>({accounts:e.accounts.map(e=>pN(e)),chainIds:e.chainIds}),storage:r.storage,version:5})))),a=r.mode,o={config:r,getMode(){return a},id:hN(),setMode(e){return c?.(),a=e,c=e.setup({internal:o}),c},store:i},s=gB(o),c=a===null?()=>{}:a.setup({internal:o});return{_internal:o,config:r,destroy(){c(),s._internal.destroy()},provider:s}}const RV=Object.freeze(Object.values(xM)),zV=e=>RV.find(t=>t.id===e);var BV=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`)return;let t=e.trim();if(/^0x[0-9a-fA-F]+$/.test(t)){let e=Number.parseInt(t,16);return Number.isNaN(e)?void 0:e}if(/^\d+$/.test(t)){let e=Number.parseInt(t,10);return Number.isNaN(e)?void 0:e}};const VV=(e,t,n)=>{let r=BV(e);t(r),n(r==null?void 0:zV(r))},HV=async(e,t=`GET`,n)=>{let r={"Content-Type":`application/json`},i=typeof window<`u`?window.__SESSION_TOKEN__:void 0;i&&(r[`X-Session-Token`]=i);let a=await fetch(`http://127.0.0.1:9545${e}`,{method:t,headers:r,body:n===void 0?void 0:JSON.stringify(n)});if(!a.ok)throw Error(`API request failed: ${a.status} ${a.statusText}`);try{return await a.json()}catch{throw Error(`Invalid JSON response`)}},UV=e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t,2),WV=e=>{if(e==null)return UV(e);if(typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`){let t=e;try{let n=JSON.parse(t.message);return UV({...e,message:n})}catch{return UV(e)}}return UV(e)},GV=e=>!!e&&e.status===`ok`;var KV=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=u(s(((e,t)=>{t.exports=KV()}))());function qV(){(0,y.useEffect)(()=>{window.__PORTO__||(window.__PORTO__=LV())},[]);let[e,t]=(0,y.useState)([]),[n,r]=(0,y.useState)(!1),[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(null),[c,l]=(0,y.useState)(null),u=e.find(e=>e.info.uuid===c)??null,[d,f]=(0,y.useState)(),[p,m]=(0,y.useState)(),[h,g]=(0,y.useState)(),[_,v]=(0,y.useState)(null),[b,x]=(0,y.useState)(null),[S,C]=(0,y.useState)(null),w=(0,y.useRef)(null),ee=(0,y.useRef)(null),te=(0,y.useRef)(null),ne=async()=>{if(!u||n)return;let e=await u.provider.request({method:`eth_requestAccounts`});f(e?.[0]??void 0);try{let e=await u.provider.request({method:`eth_chainId`});VV(e,m,g)}catch{m(void 0),g(void 0)}},re=async()=>{if(!(!d||p==null)){try{await HV(`/api/connection`,`POST`,[d,p])}catch{return}r(!0)}},ie=async()=>{if(!u||!o)return;let{id:e,signType:t,request:n}=o,r=n.address,i=n.message;try{let n;switch(t){case`PersonalSign`:n=await u.provider.request({method:`personal_sign`,params:[i,r]});break;case`SignTypedDataV4`:n=await u.provider.request({method:`eth_signTypedData_v4`,params:[r,i]});break;default:throw Error(`Unsupported signType: ${t}`)}await HV(`/api/signing/response`,`POST`,{id:e,signature:n,error:null}),C(n),s(null)}catch(t){let n=typeof t==`object`&&t&&`message`in t&&typeof t.message==`string`?t.message:String(t);try{await HV(`/api/signing/response`,`POST`,{id:e,signature:null,error:n})}catch{}C(null),s(null)}},ae=async()=>{if(!u||!i?.request)return;let e=Mv({transport:Pv(u.provider),chain:h});try{let{from:t,input:n,to:r,...a}=i.request,o=await e.sendTransaction({...a,account:t||(await e.getAddresses())[0],...n?{data:n}:{},...r?{to:r}:{},chain:h});x(o),await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:o,error:null});let s=await uv(e,{hash:o});v(s)}catch(e){let t=typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?e.message:String(e);console.error(`send failed:`,t);try{await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:null,error:t})}catch{}}},oe=(0,y.useCallback)(()=>{w.current&&=(window.clearInterval(w.current),null),ee.current&&=(window.clearInterval(ee.current),null),a(null),s(null),x(null),v(null),f(void 0),m(void 0),g(void 0),r(!1),HV(`/api/connection`,`POST`,null)},[]);return(0,y.useEffect)(()=>{te.current&&c&&te.current!==c&&oe(),te.current=c},[c,oe]),(0,y.useEffect)(()=>{e.length===1&&!u&&l(e[0].info.uuid)},[e,u]),(0,y.useEffect)(()=>{let e=e=>{let{info:n,provider:r}=e.detail;t(e=>e.some(e=>e.info.uuid===n.uuid)?e:[...e,{info:n,provider:r}])};return window.addEventListener(`eip6963:announceProvider`,e),window.dispatchEvent(new Event(`eip6963:requestProvider`)),()=>window.removeEventListener(`eip6963:announceProvider`,e)},[]),(0,y.useEffect)(()=>{if(!u)return;let e=e=>{n||f(e[0]??void 0)},t=e=>{n||VV(e,m,g)};return u.provider.on?.(`accountsChanged`,e),u.provider.on?.(`chainChanged`,t),()=>{u.provider.removeListener?.(`accountsChanged`,e),u.provider.removeListener?.(`chainChanged`,t)}},[u,n]),(0,y.useEffect)(()=>{if(!n||i||o)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/transaction/request`);GV(n)&&(window.clearInterval(t),e&&a(n.data))}catch{}},1e3);return w.current=t,()=>{e=!1,window.clearInterval(t),w.current===t&&(w.current=null)}},[n,i,o]),(0,y.useEffect)(()=>{if(!n||o||i)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/signing/request`);GV(n)&&(window.clearInterval(t),e&&s(n.data))}catch{}},1e3);return ee.current=t,()=>{e=!1,window.clearInterval(t),ee.current===t&&(ee.current=null)}},[n,o,i]),(0,$.jsx)(`div`,{className:`wrapper`,children:(0,$.jsxs)(`div`,{className:`container`,children:[(0,$.jsx)(`div`,{className:`notice`,children:`Browser wallet is still in early development. Use with caution!`}),(0,$.jsx)(`img`,{className:`banner`,src:`banner.png`,alt:`Foundry Browser Wallet`}),e.length>1&&(0,$.jsx)(`div`,{className:`wallet-selector`,children:(0,$.jsx)(`label`,{children:(0,$.jsxs)(`select`,{value:c??``,onChange:e=>l(e.target.value||null),disabled:n,children:[(0,$.jsx)(`option`,{value:``,disabled:!0,children:`Select wallet…`}),e.map(({info:e})=>(0,$.jsxs)(`option`,{value:e.uuid,children:[e.name,` (`,e.rdns,`)`]},e.uuid))]})})}),e.length===0&&(0,$.jsx)(`p`,{children:`No wallets found.`}),u&&!d&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-connect`,onClick:ne,disabled:n,children:`Connect Wallet`}),u&&d&&!n&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-confirm`,onClick:re,disabled:!d||p==null,children:`Confirm Connection`}),u&&d&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Connected`}),(0,$.jsx)(`pre`,{className:`box`,children:`\ account: ${d} chain: ${h?`${h.name} (${p})`:p??`unknown`} rpc: ${h?.rpcUrls?.default?.http?.[0]??h?.rpcUrls?.public?.http?.[0]??`unknown`}`})]}),u&&d&&n&&!i&&!o&&!b&&!S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction To Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:`No pending transaction or signing request`})})]}),u&&d&&n&&!b&&i&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction to Sign & Send`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:UV(i.request)})}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ae,children:`Sign & Send`})]}),u&&d&&n&&!i&&o&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Message / Data to Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:WV(o.request)})}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ie,children:`Sign`})]}),u&&d&&b&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction Hash`}),(0,$.jsx)(`pre`,{className:`box`,children:b}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Receipt`}),(0,$.jsx)(`pre`,{className:`box`,children:_?UV(_):`Waiting for receipt...`})]})]}),u&&d&&n&&S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Signature Result`}),(0,$.jsx)(`pre`,{className:`box`,children:S})]})]})})}var JV=document.getElementById(`root`);if(JV)(0,v.createRoot)(JV).render((0,$.jsx)(y.StrictMode,{children:(0,$.jsx)(qV,{})}));else throw Error(`Root element with id "root" not found`); \ No newline at end of file diff --git a/crates/wallets/src/wallet_browser/handlers.rs b/crates/wallets/src/wallet_browser/handlers.rs index 918bdbb161131..4b210dca60d7f 100644 --- a/crates/wallets/src/wallet_browser/handlers.rs +++ b/crates/wallets/src/wallet_browser/handlers.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use alloy_network::Network; use axum::{ Json, extract::State, @@ -46,8 +47,8 @@ pub(crate) async fn serve_css() -> impl axum::response::IntoResponse { } /// Serve main.js with injected session token. -pub(crate) async fn serve_js( - State(state): State>, +pub(crate) async fn serve_js( + State(state): State>>, ) -> impl axum::response::IntoResponse { let token = state.session_token(); let js = format!("window.__SESSION_TOKEN__ = \"{}\";\n{}", token, contents::MAIN_JS); @@ -81,9 +82,9 @@ pub(crate) async fn serve_logo_png() -> impl axum::response::IntoResponse { /// Get the next pending transaction request. /// Route: GET /api/transaction/request -pub(crate) async fn get_next_transaction_request( - State(state): State>, -) -> Json> { +pub(crate) async fn get_next_transaction_request( + State(state): State>>, +) -> Json>> { match state.read_next_transaction_request().await { Some(tx) => Json(BrowserApiResponse::with_data(tx)), None => Json(BrowserApiResponse::error("No pending transaction request")), @@ -92,8 +93,8 @@ pub(crate) async fn get_next_transaction_request( /// Post a transaction response (signed or error). /// Route: POST /api/transaction/response -pub(crate) async fn post_transaction_response( - State(state): State>, +pub(crate) async fn post_transaction_response( + State(state): State>>, Json(body): Json, ) -> Json { // Ensure that the transaction request exists. @@ -134,8 +135,8 @@ pub(crate) async fn post_transaction_response( /// Get the next pending signing request. /// Route: GET /api/signing/request -pub(crate) async fn get_next_signing_request( - State(state): State>, +pub(crate) async fn get_next_signing_request( + State(state): State>>, ) -> Json> { match state.read_next_signing_request().await { Some(req) => Json(BrowserApiResponse::with_data(req)), @@ -145,8 +146,8 @@ pub(crate) async fn get_next_signing_request( /// Post a signing response (signature or error). /// Route: POST /api/signing/response -pub(crate) async fn post_signing_response( - State(state): State>, +pub(crate) async fn post_signing_response( + State(state): State>>, Json(body): Json, ) -> Json { // Ensure that the signing request exists. @@ -174,8 +175,8 @@ pub(crate) async fn post_signing_response( /// Get current connection information. /// Route: GET /api/connection -pub(crate) async fn get_connection_info( - State(state): State>, +pub(crate) async fn get_connection_info( + State(state): State>>, ) -> Json>> { let connection = state.get_connection().await; @@ -184,8 +185,8 @@ pub(crate) async fn get_connection_info( /// Post connection update (connect or disconnect). /// Route: POST /api/connection -pub(crate) async fn post_connection_update( - State(state): State>, +pub(crate) async fn post_connection_update( + State(state): State>>, Json(body): Json>, ) -> Json { state.set_connection(body).await; diff --git a/crates/wallets/src/wallet_browser/mod.rs b/crates/wallets/src/wallet_browser/mod.rs index 00cf2b46b1c44..dc7df0a01b107 100644 --- a/crates/wallets/src/wallet_browser/mod.rs +++ b/crates/wallets/src/wallet_browser/mod.rs @@ -1,4 +1,5 @@ pub mod error; +pub mod opts; pub mod server; pub mod signer; pub mod state; @@ -13,8 +14,8 @@ mod types; mod tests { use std::time::Duration; + use alloy_network::{Ethereum, Network, TransactionBuilder}; use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256, address}; - use alloy_rpc_types::TransactionRequest; use axum::http::{HeaderMap, HeaderValue}; use tokio::task::JoinHandle; use uuid::Uuid; @@ -36,7 +37,7 @@ mod tests { #[tokio::test] async fn test_setup_server() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); // Check initial state @@ -56,7 +57,7 @@ mod tests { #[tokio::test] async fn test_connect_disconnect_wallet() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); @@ -93,7 +94,7 @@ mod tests { #[tokio::test] async fn test_switch_wallet() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); @@ -116,7 +117,7 @@ mod tests { #[tokio::test] async fn test_transaction_response_both_hash_and_error_rejected() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -151,7 +152,7 @@ mod tests { #[tokio::test] async fn test_transaction_response_neither_hash_nor_error_rejected() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -181,7 +182,7 @@ mod tests { #[tokio::test] async fn test_transaction_response_zero_hash_rejected() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -216,7 +217,7 @@ mod tests { #[tokio::test] async fn test_send_transaction_client_accept() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -252,7 +253,7 @@ mod tests { #[tokio::test] async fn test_send_transaction_client_not_requested() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -288,7 +289,7 @@ mod tests { #[tokio::test] async fn test_send_transaction_invalid_response_format() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -314,7 +315,7 @@ mod tests { #[tokio::test] async fn test_send_transaction_client_reject() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -356,7 +357,7 @@ mod tests { #[tokio::test] async fn test_send_multiple_transaction_requests() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -377,8 +378,10 @@ mod tests { .await .unwrap(); - let BrowserApiResponse::Ok(pending_tx) = - resp.json::>().await.unwrap() + let BrowserApiResponse::Ok(pending_tx) = resp + .json::>>() + .await + .unwrap() else { panic!("expected BrowserApiResponse::Ok with a pending transaction"); }; @@ -421,8 +424,10 @@ mod tests { .await .unwrap(); - let BrowserApiResponse::Ok(pending_tx) = - resp.json::>().await.unwrap() + let BrowserApiResponse::Ok(pending_tx) = resp + .json::>>() + .await + .unwrap() else { panic!("expected BrowserApiResponse::Ok with a pending transaction"); }; @@ -467,7 +472,7 @@ mod tests { #[tokio::test] async fn test_send_sign_response_both_signature_and_error_rejected() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -501,7 +506,7 @@ mod tests { #[tokio::test] async fn test_send_sign_response_neither_hash_nor_error_rejected() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -531,7 +536,7 @@ mod tests { #[tokio::test] async fn test_send_sign_client_accept() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -567,7 +572,7 @@ mod tests { #[tokio::test] async fn test_send_sign_client_not_requested() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -603,7 +608,7 @@ mod tests { #[tokio::test] async fn test_send_sign_invalid_response_format() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -629,7 +634,7 @@ mod tests { #[tokio::test] async fn test_send_sign_client_reject() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -666,7 +671,7 @@ mod tests { #[tokio::test] async fn test_send_multiple_sign_requests() { - let mut server = create_server(); + let mut server = create_server::(); let client = client_with_token(&server); server.start().await.unwrap(); connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; @@ -770,21 +775,21 @@ mod tests { } /// Helper to create a default browser wallet server. - fn create_server() -> BrowserWalletServer { + fn create_server() -> BrowserWalletServer { BrowserWalletServer::new(0, false, DEFAULT_TIMEOUT, DEFAULT_DEVELOPMENT) } /// Helper to create a reqwest client with the session token header. - fn client_with_token(server: &BrowserWalletServer) -> reqwest::Client { + fn client_with_token(server: &BrowserWalletServer) -> reqwest::Client { let mut headers = HeaderMap::new(); headers.insert("X-Session-Token", HeaderValue::from_str(server.session_token()).unwrap()); reqwest::Client::builder().default_headers(headers).build().unwrap() } /// Helper to connect a wallet to the server. - async fn connect_wallet( + async fn connect_wallet( client: &reqwest::Client, - server: &BrowserWalletServer, + server: &BrowserWalletServer, connection: Connection, ) { let resp = client @@ -795,7 +800,10 @@ mod tests { } /// Helper to disconnect a wallet from the server. - async fn disconnect_wallet(client: &reqwest::Client, server: &BrowserWalletServer) { + async fn disconnect_wallet( + client: &reqwest::Client, + server: &BrowserWalletServer, + ) { let resp = client .post(format!("http://localhost:{}/api/connection", server.port())) .json(&Option::::None) @@ -804,9 +812,9 @@ mod tests { } /// Spawn the transaction signing flow in the background and return the join handle. - async fn wait_for_transaction_signing( - server: &BrowserWalletServer, - tx_request: BrowserTransactionRequest, + async fn wait_for_transaction_signing( + server: &BrowserWalletServer, + tx_request: BrowserTransactionRequest, ) -> JoinHandle> { // Spawn the signing flow in the background let browser_server = server.clone(); @@ -819,8 +827,8 @@ mod tests { } /// Spawn the message signing flow in the background and return the join handle. - async fn wait_for_message_signing( - server: &BrowserWalletServer, + async fn wait_for_message_signing( + server: &BrowserWalletServer, sign_request: BrowserSignRequest, ) -> JoinHandle> { // Spawn the signing flow in the background @@ -834,32 +842,25 @@ mod tests { } /// Create a simple browser transaction request. - fn create_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) { + fn create_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) { let id = Uuid::new_v4(); - let tx = BrowserTransactionRequest { - id, - request: TransactionRequest { - from: Some(ALICE), - to: Some(TxKind::Call(BOB)), - value: Some(U256::from(1000)), - ..Default::default() - }, - }; + let request = N::TransactionRequest::default() + .with_from(ALICE) + .with_to(BOB) + .with_value(U256::from(1000)); + let tx = BrowserTransactionRequest { id, request }; (id, tx) } /// Create a different browser transaction request (from the first one). - fn create_different_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) { + fn create_different_browser_transaction_request() + -> (Uuid, BrowserTransactionRequest) { let id = Uuid::new_v4(); - let tx = BrowserTransactionRequest { - id, - request: TransactionRequest { - from: Some(BOB), - to: Some(TxKind::Call(ALICE)), - value: Some(U256::from(2000)), - ..Default::default() - }, - }; + let request = N::TransactionRequest::default() + .with_from(BOB) + .with_to(ALICE) + .with_value(U256::from(2000)); + let tx = BrowserTransactionRequest { id, request }; (id, tx) } @@ -886,9 +887,9 @@ mod tests { } /// Check that the transaction request queue is empty, if not panic. - async fn check_transaction_request_queue_empty( + async fn check_transaction_request_queue_empty( client: &reqwest::Client, - server: &BrowserWalletServer, + server: &BrowserWalletServer, ) { let resp = client .get(format!("http://localhost:{}/api/transaction/request", server.port())) @@ -897,7 +898,7 @@ mod tests { .unwrap(); let BrowserApiResponse::Error { message } = - resp.json::>().await.unwrap() + resp.json::>>().await.unwrap() else { panic!("expected BrowserApiResponse::Error (no pending transaction), but got Ok"); }; @@ -906,9 +907,9 @@ mod tests { } /// Check that the transaction request matches the expected request ID and fields. - async fn check_transaction_request_content( + async fn check_transaction_request_content( client: &reqwest::Client, - server: &BrowserWalletServer, + server: &BrowserWalletServer, tx_request_id: Uuid, ) { let resp = client @@ -918,21 +919,21 @@ mod tests { .unwrap(); let BrowserApiResponse::Ok(pending_tx) = - resp.json::>().await.unwrap() + resp.json::>>().await.unwrap() else { panic!("expected BrowserApiResponse::Ok with a pending transaction"); }; assert_eq!(pending_tx.id, tx_request_id); - assert_eq!(pending_tx.request.from, Some(ALICE)); - assert_eq!(pending_tx.request.to, Some(TxKind::Call(BOB))); - assert_eq!(pending_tx.request.value, Some(U256::from(1000))); + assert_eq!(pending_tx.request.from(), Some(ALICE)); + assert_eq!(pending_tx.request.kind(), Some(TxKind::Call(BOB))); + assert_eq!(pending_tx.request.value(), Some(U256::from(1000))); } /// Check that the sign request queue is empty, if not panic. - async fn check_sign_request_queue_empty( + async fn check_sign_request_queue_empty( client: &reqwest::Client, - server: &BrowserWalletServer, + server: &BrowserWalletServer, ) { let resp = client .get(format!("http://localhost:{}/api/signing/request", server.port())) @@ -950,9 +951,9 @@ mod tests { } /// Check that the sign request matches the expected request ID and fields. - async fn check_sign_request_content( + async fn check_sign_request_content( client: &reqwest::Client, - server: &BrowserWalletServer, + server: &BrowserWalletServer, sign_request_id: Uuid, ) { let resp = client diff --git a/crates/wallets/src/wallet_browser/opts.rs b/crates/wallets/src/wallet_browser/opts.rs new file mode 100644 index 0000000000000..15e766ce1a1b0 --- /dev/null +++ b/crates/wallets/src/wallet_browser/opts.rs @@ -0,0 +1,49 @@ +use std::time::Duration; + +use clap::Parser; +use eyre::Result; +use serde::Serialize; + +use crate::wallet_browser::signer::BrowserSigner; + +/// Browser wallet options +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Browser wallet options")] +pub struct BrowserWalletOpts { + /// Use a browser wallet. + #[arg(long, help_heading = "")] + pub browser: bool, + + /// Port for the browser wallet server. + #[arg(long, value_name = "PORT", default_value = "9545", requires = "browser")] + pub browser_port: u16, + + /// Whether to open the browser for wallet connection. + #[arg(long, default_value_t = false, requires = "browser")] + pub browser_disable_open: bool, + + /// Enable development mode for the browser wallet. + /// This relaxes certain security features for local development. + /// + /// **WARNING**: This should only be used in a development environment. + #[arg(long, hide = true)] + pub browser_development: bool, +} + +impl BrowserWalletOpts { + pub async fn run(&self) -> Result> { + Ok(if self.browser { + Some( + BrowserSigner::new( + self.browser_port, + !self.browser_disable_open, + Duration::from_secs(300), + self.browser_development, + ) + .await?, + ) + } else { + None + }) + } +} diff --git a/crates/wallets/src/wallet_browser/queue.rs b/crates/wallets/src/wallet_browser/queue.rs index 608c7e18dbbde..5df44a09e9fde 100644 --- a/crates/wallets/src/wallet_browser/queue.rs +++ b/crates/wallets/src/wallet_browser/queue.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, VecDeque}; +use alloy_network::Network; use uuid::Uuid; use crate::wallet_browser::types::{BrowserSignRequest, BrowserTransactionRequest}; @@ -69,7 +70,7 @@ pub(crate) trait HasId { fn id(&self) -> &Uuid; } -impl HasId for BrowserTransactionRequest { +impl HasId for BrowserTransactionRequest { fn id(&self) -> &Uuid { &self.id } diff --git a/crates/wallets/src/wallet_browser/router.rs b/crates/wallets/src/wallet_browser/router.rs index f62a5d3977054..cb29e9122a5d8 100644 --- a/crates/wallets/src/wallet_browser/router.rs +++ b/crates/wallets/src/wallet_browser/router.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use alloy_network::Network; use axum::{ Router, extract::{Request, State}, @@ -13,7 +14,7 @@ use tower_http::{cors::CorsLayer, set_header::SetResponseHeaderLayer}; use crate::wallet_browser::{handlers, state::BrowserWalletState}; -pub async fn build_router(state: Arc, port: u16) -> Router { +pub async fn build_router(state: Arc>, port: u16) -> Router { let api = Router::new() .route("/transaction/request", get(handlers::get_next_transaction_request)) .route("/transaction/response", post(handlers::post_transaction_response)) @@ -76,8 +77,8 @@ pub async fn build_router(state: Arc, port: u16) -> Router { .with_state(state) } -async fn require_session_token( - State(state): State>, +async fn require_session_token( + State(state): State>>, req: Request, next: Next, ) -> Result { diff --git a/crates/wallets/src/wallet_browser/server.rs b/crates/wallets/src/wallet_browser/server.rs index 4f067c3bbcbab..507338a3bd291 100644 --- a/crates/wallets/src/wallet_browser/server.rs +++ b/crates/wallets/src/wallet_browser/server.rs @@ -5,6 +5,7 @@ use std::{ }; use alloy_dyn_abi::TypedData; +use alloy_network::Network; use alloy_primitives::{Address, Bytes, TxHash}; use tokio::{ net::TcpListener, @@ -24,15 +25,15 @@ use crate::wallet_browser::{ /// Browser wallet server. #[derive(Debug, Clone)] -pub struct BrowserWalletServer { +pub struct BrowserWalletServer { port: u16, - state: Arc, + state: Arc>, shutdown_tx: Option>>>>, open_browser: bool, timeout: Duration, } -impl BrowserWalletServer { +impl BrowserWalletServer { /// Create a new browser wallet server. pub fn new(port: u16, open_browser: bool, timeout: Duration, development: bool) -> Self { Self { @@ -118,7 +119,7 @@ impl BrowserWalletServer { /// Request a transaction to be signed and sent via the browser wallet. pub async fn request_transaction( &self, - request: BrowserTransactionRequest, + request: BrowserTransactionRequest, ) -> Result { if !self.is_connected().await { return Err(BrowserWalletError::NotConnected); diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 78cf166a35ad0..c698017d7ee61 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -5,9 +5,8 @@ use std::{ use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; -use alloy_network::TxSigner; +use alloy_network::{Ethereum, Network, TransactionBuilder, TxSigner}; use alloy_primitives::{Address, B256, ChainId, hex}; -use alloy_rpc_types::TransactionRequest; use alloy_signer::{Result, Signature, Signer, SignerSync}; use alloy_sol_types::{Eip712Domain, SolStruct}; use async_trait::async_trait; @@ -20,13 +19,13 @@ use crate::wallet_browser::{ }; #[derive(Clone, Debug)] -pub struct BrowserSigner { - server: Arc>, +pub struct BrowserSigner { + server: Arc>>, address: Address, chain_id: ChainId, } -impl BrowserSigner { +impl BrowserSigner { pub async fn new( port: u16, open_browser: bool, @@ -62,9 +61,9 @@ impl BrowserSigner { /// Send a transaction through the browser wallet. pub async fn send_transaction_via_browser( &self, - tx_request: TransactionRequest, + tx_request: N::TransactionRequest, ) -> Result { - if let Some(from) = tx_request.from + if let Some(from) = tx_request.from() && from != self.address { return Err(alloy_signer::Error::other( @@ -72,7 +71,7 @@ impl BrowserSigner { )); } - if let Some(chain_id) = tx_request.chain_id + if let Some(chain_id) = tx_request.chain_id() && chain_id != self.chain_id { return Err(alloy_signer::Error::other( @@ -92,7 +91,7 @@ impl BrowserSigner { } } -impl SignerSync for BrowserSigner { +impl SignerSync for BrowserSigner { fn sign_hash_sync(&self, _hash: &B256) -> Result { Err(alloy_signer::Error::other( "Browser wallets cannot sign raw hashes. Use sign_message or send_transaction instead.", @@ -111,7 +110,7 @@ impl SignerSync for BrowserSigner { } #[async_trait] -impl Signer for BrowserSigner { +impl Signer for BrowserSigner { async fn sign_hash(&self, _hash: &B256) -> Result { Err(alloy_signer::Error::other( "Browser wallets sign and send transactions in one step. Use eth_sendTransaction instead.", @@ -175,7 +174,7 @@ impl Signer for BrowserSigner { } #[async_trait] -impl TxSigner for BrowserSigner { +impl TxSigner for BrowserSigner { fn address(&self) -> Address { Signer::address(self) } @@ -188,7 +187,7 @@ impl TxSigner for BrowserSigner { } } -impl Drop for BrowserSigner { +impl Drop for BrowserSigner { fn drop(&mut self) { let server = self.server.clone(); diff --git a/crates/wallets/src/wallet_browser/state.rs b/crates/wallets/src/wallet_browser/state.rs index 07954654a7185..0fa38da9cc110 100644 --- a/crates/wallets/src/wallet_browser/state.rs +++ b/crates/wallets/src/wallet_browser/state.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use alloy_network::Network; use tokio::sync::{Mutex, RwLock}; use uuid::Uuid; @@ -12,11 +13,12 @@ use crate::wallet_browser::{ }; #[derive(Debug, Clone)] -pub(crate) struct BrowserWalletState { +pub(crate) struct BrowserWalletState { /// Current information about the wallet connection. connection: Arc>>, /// Request/response queue for transactions. - transactions: Arc>>, + transactions: + Arc, BrowserTransactionResponse>>>, /// Request/response queue for signings. signings: Arc>>, /// Unique session token for the wallet browser instance. @@ -29,7 +31,7 @@ pub(crate) struct BrowserWalletState { development: bool, } -impl BrowserWalletState { +impl BrowserWalletState { /// Create a new browser wallet state. pub fn new(session_token: String, development: bool) -> Self { Self { @@ -70,7 +72,7 @@ impl BrowserWalletState { } /// Add a transaction request. - pub async fn add_transaction_request(&self, request: BrowserTransactionRequest) { + pub async fn add_transaction_request(&self, request: BrowserTransactionRequest) { self.transactions.lock().await.add_request(request); } @@ -80,7 +82,7 @@ impl BrowserWalletState { } /// Read the next transaction request. - pub async fn read_next_transaction_request(&self) -> Option { + pub async fn read_next_transaction_request(&self) -> Option> { self.transactions.lock().await.read_request().cloned() } diff --git a/crates/wallets/src/wallet_browser/types.rs b/crates/wallets/src/wallet_browser/types.rs index 61f3edf3d85ca..da4ac924a2b41 100644 --- a/crates/wallets/src/wallet_browser/types.rs +++ b/crates/wallets/src/wallet_browser/types.rs @@ -1,6 +1,6 @@ use alloy_dyn_abi::TypedData; +use alloy_network::Network; use alloy_primitives::{Address, Bytes, ChainId, TxHash}; -use alloy_rpc_types::TransactionRequest; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -37,11 +37,11 @@ impl BrowserApiResponse { /// Represents a transaction request sent to the browser wallet. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct BrowserTransactionRequest { +pub struct BrowserTransactionRequest { /// The unique identifier for the transaction. pub id: Uuid, /// The transaction request details. - pub request: TransactionRequest, + pub request: N::TransactionRequest, } /// Represents a transaction response sent from the browser wallet. diff --git a/crates/wallets/src/wallet_multi/mod.rs b/crates/wallets/src/wallet_multi/mod.rs index 7e098002a26e0..fe9556c60af77 100644 --- a/crates/wallets/src/wallet_multi/mod.rs +++ b/crates/wallets/src/wallet_multi/mod.rs @@ -1,6 +1,8 @@ use crate::{ + BrowserWalletOpts, signer::{PendingSigner, WalletSigner}, utils, + wallet_browser::signer::BrowserSigner, }; use alloy_primitives::map::AddressHashMap; use alloy_signer::Signer; @@ -19,12 +21,18 @@ pub struct MultiWallet { pending_signers: Vec, /// Contains unlocked signers. signers: AddressHashMap, + /// Browser signer + browser: Option, } impl MultiWallet { - pub fn new(pending_signers: Vec, signers: Vec) -> Self { + pub fn new( + pending_signers: Vec, + signers: Vec, + browser: Option, + ) -> Self { let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); - Self { pending_signers, signers } + Self { pending_signers, signers, browser } } fn maybe_unlock_pending(&mut self) -> Result<()> { @@ -35,14 +43,14 @@ impl MultiWallet { Ok(()) } - pub fn signers(&mut self) -> Result<&AddressHashMap> { + pub fn signers(&mut self) -> Result<(&AddressHashMap, Option<&BrowserSigner>)> { self.maybe_unlock_pending()?; - Ok(&self.signers) + Ok((&self.signers, self.browser.as_ref())) } - pub fn into_signers(mut self) -> Result> { + pub fn into_signers(mut self) -> Result<(AddressHashMap, Option)> { self.maybe_unlock_pending()?; - Ok(self.signers) + Ok((self.signers, self.browser)) } pub fn add_signer(&mut self, signer: WalletSigner) { @@ -229,6 +237,10 @@ pub struct MultiWalletOpts { /// See: #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))] pub turnkey: bool, + + /// Browser wallet options + #[command(flatten)] + pub browser: BrowserWalletOpts, } impl MultiWalletOpts { @@ -236,6 +248,7 @@ impl MultiWalletOpts { pub async fn get_multi_wallet(&self) -> Result { let mut pending = Vec::new(); let mut signers: Vec = Vec::new(); + let browser = self.browser_signer().await?; if let Some(ledgers) = self.ledgers().await? { signers.extend(ledgers); @@ -272,7 +285,7 @@ impl MultiWalletOpts { )); } - Ok(MultiWallet::new(pending, signers)) + Ok(MultiWallet::new(pending, signers, browser)) } pub fn private_keys(&self) -> Result>> { @@ -477,6 +490,21 @@ impl MultiWalletOpts { Ok(None) } + + /// Returns the Turnkey address if `--turnkey` flag is set and `TURNKEY_ADDRESS` is available. + pub fn turnkey_address(&self) -> Option { + #[cfg(feature = "turnkey")] + if self.turnkey { + return std::env::var("TURNKEY_ADDRESS").ok().and_then(|addr| addr.parse().ok()); + } + + None + } + + /// Launches and returns the Browser signer if `--browser` flag is set + pub async fn browser_signer(&self) -> Result> { + self.browser.run().await + } } #[cfg(test)] @@ -528,6 +556,42 @@ mod tests { assert_eq!(unlocked[0].address(), address!("0xec554aeafe75601aaab43bd4621a22284db566c2")); } + // https://github.com/foundry-rs/foundry/issues/12916 + #[test] + #[cfg(feature = "turnkey")] + fn turnkey_address_returns_address_when_flag_set() { + let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli", "--turnkey"]); + assert!(args.turnkey); + + unsafe { + std::env::set_var("TURNKEY_ADDRESS", "0x1234567890123456789012345678901234567890"); + } + + let addr = args.turnkey_address(); + assert_eq!(addr, Some(address!("0x1234567890123456789012345678901234567890"))); + + unsafe { + std::env::remove_var("TURNKEY_ADDRESS"); + } + } + + #[test] + fn turnkey_address_returns_none_when_flag_not_set() { + let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); + assert!(!args.turnkey); + + unsafe { + std::env::set_var("TURNKEY_ADDRESS", "0x1234567890123456789012345678901234567890"); + } + + let addr = args.turnkey_address(); + assert_eq!(addr, None); + + unsafe { + std::env::remove_var("TURNKEY_ADDRESS"); + } + } + // https://github.com/foundry-rs/foundry/issues/5179 #[test] fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { diff --git a/deny.toml b/deny.toml index 8e5996c2bacdc..2b9258cd3991a 100644 --- a/deny.toml +++ b/deny.toml @@ -7,6 +7,8 @@ yanked = "warn" ignore = [ # https://rustsec.org/advisories/RUSTSEC-2024-0436 paste! is unmaintained "RUSTSEC-2024-0436", + # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained + "RUSTSEC-2025-0141", ] # This section is considered when running `cargo deny check bans`. @@ -61,7 +63,6 @@ exceptions = [ # CC0 is a permissive license but somewhat unclear status for source code # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal - { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, { allow = ["CC0-1.0"], name = "dunce" }, diff --git a/flake.lock b/flake.lock index 9dafaa33fa15b..a11c598e7a872 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1768027312, - "narHash": "sha256-MW85tjeLZHPB1y0bQaBat/4vtDVaSot2nSWre2rj9nU=", + "lastModified": 1772261909, + "narHash": "sha256-8XbJXrhMFhLgoBrjFIJx5XJi+SD+7/gbvaIXCuqy9Z0=", "owner": "nix-community", "repo": "fenix", - "rev": "334c4b4a8a2554b8dd19df8932d43345232f7f84", + "rev": "e4c413b9546d6c9e6426b33b4d6de1a49a375024", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1768032153, - "narHash": "sha256-6kD1MdY9fsE6FgSwdnx29hdH2UcBKs3/+JJleMShuJg=", + "lastModified": 1772173633, + "narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3146c6aa9995e7351a398e17470e15305e6e18ff", + "rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1767961401, - "narHash": "sha256-7PZ/pXpRrd3JfgCSEuBeYW6nCm3UEZ1TLDztFbnjPgU=", + "lastModified": 1772178959, + "narHash": "sha256-DkjUvrEnnhHjOcjMx6aXfYGIZ0PWmcYzvVayhRj1r4M=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "714d0476ae96352f09933e1d8d51e027be25e5c3", + "rev": "8494a8b3b769c17e8594d811012cc1b0fab090c7", "type": "github" }, "original": { diff --git a/foundryup/foundryup b/foundryup/foundryup index 2e867184de9d2..2dcbb5c2f6d0b 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -55,11 +55,6 @@ main() { esac; shift done - # Tempo only distributes a subset of the binaries - if [[ "$FOUNDRYUP_NETWORK" == "tempo" ]]; then - BINS=(forge cast) - fi - CARGO_BUILD_ARGS=(--release) if [ -n "$FOUNDRYUP_JOBS" ]; then @@ -333,8 +328,8 @@ main() { # Compute the URL of the release tarball in the Tempo Foundry repository. RELEASE_URL="https://github.com/${FOUNDRYUP_REPO}/releases/download/${FOUNDRYUP_TAG}/" - BIN_ARCHIVE_URL="${RELEASE_URL}foundry_nightly_${PLATFORM}_${ARCHITECTURE}.$EXT" - MAN_TARBALL_URL="${RELEASE_URL}foundry_man_nightly.tar.gz" + BIN_ARCHIVE_URL="${RELEASE_URL}foundry_${FOUNDRYUP_VERSION}_${PLATFORM}_${ARCHITECTURE}.$EXT" + MAN_TARBALL_URL="${RELEASE_URL}foundry_man_${FOUNDRYUP_VERSION}.tar.gz" ensure mkdir -p "$FOUNDRY_VERSIONS_DIR" diff --git a/testdata/default/cheats/Ed25519.t.sol b/testdata/default/cheats/Ed25519.t.sol new file mode 100644 index 0000000000000..a7cdeae4ce3fc --- /dev/null +++ b/testdata/default/cheats/Ed25519.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract Ed25519Test is Test { + function testCreateEd25519Key() public { + bytes32 salt = bytes32(uint256(1)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + assertTrue(publicKey != bytes32(0), "public key should not be zero"); + assertEq(privateKey, salt, "private key should equal salt"); + } + + function testCreateEd25519KeyDeterministic() public { + bytes32 salt = bytes32(uint256(42)); + (bytes32 pub1, bytes32 priv1) = vm.createEd25519Key(salt); + (bytes32 pub2, bytes32 priv2) = vm.createEd25519Key(salt); + assertEq(pub1, pub2, "same salt should produce same public key"); + assertEq(priv1, priv2, "same salt should produce same private key"); + } + + function testCreateEd25519KeyDifferentSalts() public { + bytes32 salt1 = bytes32(uint256(1)); + bytes32 salt2 = bytes32(uint256(2)); + (bytes32 pub1,) = vm.createEd25519Key(salt1); + (bytes32 pub2,) = vm.createEd25519Key(salt2); + assertTrue(pub1 != pub2, "different salts should produce different public keys"); + } + + function testPublicKeyEd25519() public { + bytes32 salt = bytes32(uint256(123)); + (bytes32 expectedPub, bytes32 privateKey) = vm.createEd25519Key(salt); + bytes32 derivedPub = vm.publicKeyEd25519(privateKey); + assertEq(derivedPub, expectedPub, "derived public key should match created one"); + } + + function testSignAndVerifyEd25519() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "test.namespace"; + bytes memory message = "hello world"; + + bytes memory signature = vm.signEd25519(namespace, message, privateKey); + assertEq(signature.length, 64, "signature should be 64 bytes"); + + bool valid = vm.verifyEd25519(signature, namespace, message, publicKey); + assertTrue(valid, "signature should be valid"); + } + + function testVerifyEd25519WrongMessage() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "ns"; + bytes memory signature = vm.signEd25519(namespace, "correct message", privateKey); + + bool valid = vm.verifyEd25519(signature, namespace, "wrong message", publicKey); + vm.assertFalse(valid, "signature should not verify with wrong message"); + } + + function testVerifyEd25519NamespaceSeparation() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory message = "message"; + bytes memory signature = vm.signEd25519("namespace.a", message, privateKey); + + bool valid = vm.verifyEd25519(signature, "namespace.b", message, publicKey); + vm.assertFalse(valid, "signature should not verify with different namespace"); + + valid = vm.verifyEd25519(signature, "namespace.a", message, publicKey); + assertTrue(valid, "signature should verify with correct namespace"); + } + + function testVerifyEd25519InvalidSignature() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey,) = vm.createEd25519Key(salt); + + bytes memory invalidSig = new bytes(64); + bool valid = vm.verifyEd25519(invalidSig, "ns", "msg", publicKey); + vm.assertFalse(valid, "zero signature should not verify"); + } + + function testVerifyEd25519WrongSignatureLength() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey,) = vm.createEd25519Key(salt); + + bytes memory shortSig = new bytes(32); + bool valid = vm.verifyEd25519(shortSig, "ns", "msg", publicKey); + vm.assertFalse(valid, "short signature should not verify"); + } + + function testSignEd25519Deterministic() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "ns"; + bytes memory message = "msg"; + + bytes memory sig1 = vm.signEd25519(namespace, message, privateKey); + bytes memory sig2 = vm.signEd25519(namespace, message, privateKey); + assertEq(sig1, sig2, "same inputs should produce same signature"); + } +} diff --git a/testdata/default/cheats/ExecuteTransaction.t.sol b/testdata/default/cheats/ExecuteTransaction.t.sol new file mode 100644 index 0000000000000..b86a3b96c42ff --- /dev/null +++ b/testdata/default/cheats/ExecuteTransaction.t.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract ExecuteTransactionTest is Test { + function test_revert_not_a_tx() public { + vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: unexpected string"); + vm.executeTransaction(hex"0102"); + } + + function test_execute_legacy_transfer() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 17; + + vm.deal(address(from), balance); + assertEq(address(from).balance, balance); + assertEq(address(to).balance, 0); + + /* + Legacy signed transaction (type 0): + { from: 0x5316812db67073c4d4af8bb3000c5b86c2877e94, to: 0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6, gas: 200000, gasPrice: 100, value: 17, nonce: 0, chainId: 1 } + */ + vm.executeTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + // Gas price is set to 0 in isolated execution, so no gas cost deducted. + assertEq(address(from).balance, balance - amountSent); + assertEq(address(to).balance, amountSent); + } + + function test_execute_eip1559_transfer() public { + vm.chainId(1); + + address from = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 42; + + vm.deal(from, balance); + assertEq(from.balance, balance); + assertEq(to.balance, 0); + + /* + EIP-1559 signed transaction (type 2): + { from: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, to: 0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6, gas: 21000, maxFeePerGas: 100, maxPriorityFeePerGas: 10, value: 42, nonce: 0, chainId: 1 } + */ + vm.executeTransaction( + hex"02f86201800a64825208946fd0a0cff9a87adf51695b40b4fa267855a8f4c62a80c080a03447a5bb5068bea134c052824759b5dd973aefcf745d0d67a6e2ee6543571f2ca05f3ee9f04a4d3cbc883f5a8b68cb6149fbc47083bb7f4abf644df780f2f11638" + ); + + // Gas price is set to 0 in isolated execution, so no gas cost deducted. + assertEq(from.balance, balance - amountSent); + assertEq(to.balance, amountSent); + } + + function test_execute_erc20_transfer() public { + vm.fee(1); + vm.chainId(1); + + address alice = 0x7ED31830602f9F7419307235c0610Fb262AA0375; + address bob = 0x70CF146aB98ffD5dE24e75dd7423F16181Da8E13; + address charlie = 0xae0900Cf97f8C233c64F7089cEC7d5457215BB8d; + + bytes memory code = + hex"608060405234801561001057600080fd5b50600436106100625760003560e01c8063095ea7b31461006757806323b872dd1461008f57806370a08231146100a257806394bf804d146100d9578063a9059cbb146100ee578063dd62ed3e14610101575b600080fd5b61007a61007536600461051d565b61013a565b60405190151581526020015b60405180910390f35b61007a61009d366004610547565b610152565b6100cb6100b0366004610583565b6001600160a01b031660009081526020819052604090205490565b604051908152602001610086565b6100ec6100e73660046105a5565b610176565b005b61007a6100fc36600461051d565b610184565b6100cb61010f3660046105d1565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600033610148818585610192565b5060019392505050565b600033610160858285610286565b61016b858585610318565b506001949350505050565b6101808183610489565b5050565b600033610148818585610318565b6001600160a01b0383166101f95760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084015b60405180910390fd5b6001600160a01b03821661025a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016101f0565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461031257818110156103055760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016101f0565b6103128484848403610192565b50505050565b6001600160a01b03831661037c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016101f0565b6001600160a01b0382166103de5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016101f0565b6001600160a01b038316600090815260208190526040902054818110156104565760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016101f0565b6001600160a01b039384166000908152602081905260408082209284900390925592909316825291902080549091019055565b6001600160a01b0382166104df5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016101f0565b6001600160a01b03909116600090815260208190526040902080549091019055565b80356001600160a01b038116811461051857600080fd5b919050565b6000806040838503121561053057600080fd5b61053983610501565b946020939093013593505050565b60008060006060848603121561055c57600080fd5b61056584610501565b925061057360208501610501565b9150604084013590509250925092565b60006020828403121561059557600080fd5b61059e82610501565b9392505050565b600080604083850312156105b857600080fd5b823591506105c860208401610501565b90509250929050565b600080604083850312156105e457600080fd5b6105ed83610501565b91506105c86020840161050156fea2646970667358221220e1fee5cd1c5bbf066a9ce9228e1baf7e7fcb77b5050506c7d614aaf8608b42e364736f6c63430008110033"; + + MyERC20 token = MyERC20(address(uint160(uint256(keccak256(abi.encodePacked("mytoken")))))); + vm.etch(address(token), code); + + token.mint(100, alice); + + assertEq(token.balanceOf(alice), 100); + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 0); + + vm.deal(alice, 10 ether); + + /* + Signed transaction: + { + from: '0x7ED31830602f9F7419307235c0610Fb262AA0375', + to: '0x5bF11839F61EF5ccEEaf1F4153e44df5D02825f7', + value: 0, + data: '0x095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e130000000000000000000000000000000000000000000000000000000000000032', + nonce: 0, + gasPrice: 100, + gasLimit: 200000, + chainId: 1 + } + */ + // alice approves bob for 50 tokens + vm.executeTransaction( + hex"f8a5806483030d40945bf11839f61ef5cceeaf1f4153e44df5d02825f780b844095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e13000000000000000000000000000000000000000000000000000000000000003225a0e25b9ef561d9a413b21755cc0e4bb6e80f2a88a8a52305690956130d612074dfa07bfd418bc2ad3c3f435fa531cdcdc64887f64ed3fb0d347d6b0086e320ad4eb1" + ); + + assertEq(token.allowance(alice, bob), 50); + + // Use the allowance via a normal prank call. + vm.deal(bob, 1 ether); + vm.prank(bob); + token.transferFrom(alice, charlie, 20); + + assertEq(token.balanceOf(alice), 80); + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 20); + } + + // Verify state isolation: operations after executeTransaction should work correctly. + function test_execute_then_interact() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + address random = address(uint160(uint256(keccak256(abi.encodePacked("random"))))); + + uint256 balance = 1 ether; + + vm.deal(address(from), balance); + + vm.executeTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + assertEq(address(to).balance, 17); + + // Interact with the state after executeTransaction. + uint256 value = 5; + vm.prank(to); + (bool success,) = random.call{value: value}(""); + require(success); + assertEq(address(to).balance, 17 - value); + assertEq(address(random).balance, value); + } +} + +contract MyERC20 { + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + function mint(uint256 amount, address to) public { + _mint(to, amount); + } + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) public returns (bool) { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public returns (bool) { + address owner = msg.sender; + _approve(owner, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function _transfer(address from, address to, uint256 amount) internal { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + _balances[to] += amount; + } + } + + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + unchecked { + _balances[account] += amount; + } + } + + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + _allowances[owner][spender] = amount; + } + + function _spendAllowance(address owner, address spender, uint256 amount) internal { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } +} diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 6f3ddf305b302..839d97962aa94 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -413,6 +413,18 @@ contract ExpectRevertCountWithReverter is Test { } } +contract ExpectRevertPrecompileTest is Test { + /// Test that vm.expectRevert works when the next external call targets a + /// precompile address directly. Precompile calls don't create an interpreter + /// frame (no `initialize_interp`), so depth tracking must account for them. + function testExpectRevertDirectPrecompileCall() public { + // BLAKE2F precompile (0x09) expects exactly 213 bytes of input. + // Calling it with invalid input reverts. + vm.expectRevert(); + address(0x09).call(hex"00"); + } +} + contract ExpectRevertWithErrorTest is Test { /// Ref: function test_f() external { diff --git a/testdata/default/cheats/MockFunction.t.sol b/testdata/default/cheats/MockFunction.t.sol index 3defa8cfd3580..6cd93253c3953 100644 --- a/testdata/default/cheats/MockFunction.t.sol +++ b/testdata/default/cheats/MockFunction.t.sol @@ -3,7 +3,13 @@ pragma solidity ^0.8.18; import "utils/Test.sol"; -contract MockFunctionContract { +interface IMockFunctionContract { + function a() external view returns (uint256); + function mocked_function() external; + function mocked_args_function(uint256 x) external; +} + +contract MockFunctionContract is IMockFunctionContract { uint256 public a; function mocked_function() public { @@ -15,7 +21,7 @@ contract MockFunctionContract { } } -contract ModelMockFunctionContract { +contract ModelMockFunctionContract is IMockFunctionContract { uint256 public a; function mocked_function() public { @@ -27,13 +33,53 @@ contract ModelMockFunctionContract { } } +contract Proxy { + address immutable impl; + + constructor(address impl_) { + impl = impl_; + } + + fallback() external { + _delegate(impl); + } + + // code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/239795bea728c8dca4deb6c66856dd58a6991112/contracts/proxy/Proxy.sol#L22-L45 + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0x00, 0x00, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0x00, calldatasize(), 0x00, 0x00) + + // Copy the returned data. + returndatacopy(0x00, 0x00, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0x00, returndatasize()) + } + default { + return(0x00, returndatasize()) + } + } + } +} + contract MockFunctionTest is Test { MockFunctionContract my_contract; ModelMockFunctionContract model_contract; + IMockFunctionContract my_proxy; function setUp() public { my_contract = new MockFunctionContract(); model_contract = new ModelMockFunctionContract(); + my_proxy = IMockFunctionContract(address(new Proxy(address(my_contract)))); } function test_mock_function() public { @@ -69,4 +115,132 @@ contract MockFunctionTest is Test { my_contract.mocked_args_function(789); assertEq(my_contract.a(), 123 + 789); } + + function test_mock_function_via_proxy() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 123, "mocked function should be called via proxy"); + + // reset mock + vm.mockFunction( + address(my_proxy), address(my_proxy), abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 321, "after reset, original function should be called"); + } + + function test_mock_function_via_proxy_concrete_args() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 100) + ); + my_proxy.mocked_args_function(100); + assertEq(my_proxy.a(), 123 + 100, "mocked args function should be called via proxy"); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "original args function should be called for different args"); + + // reset mock + vm.mockFunction( + address(my_proxy), + address(my_proxy), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 100) + ); + my_proxy.mocked_args_function(100); + assertEq(my_proxy.a(), 321 + 100, "after reset, original args function should be called"); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "original args function should be called for different args"); + } + + function test_mock_function_via_proxy_all_args() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 123 + 300, "mocked args function should be called via proxy"); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 123 + 400, "mocked args function should be called via proxy"); + + // reset mock + vm.mockFunction( + address(my_proxy), + address(my_proxy), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "after reset, original args function should be called"); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 321 + 400, "after reset, original args function should be called"); + } + + function test_mock_function_via_impl() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 123, "mocked function should be called via impl address"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 321, "after reset, original function should be called"); + } + + function test_mock_function_via_impl_concrete_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 200) + ); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 123 + 200, "mocked args function should be called via impl address"); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "original args function should be called for different args"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 200) + ); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "after reset, original args function should be called"); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "original args function should be called for different args"); + } + + function test_mock_function_via_impl_all_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 123 + 400, "mocked args function should be called via impl address"); + my_proxy.mocked_args_function(500); + assertEq(my_proxy.a(), 123 + 500, "mocked args function should be called via impl address"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 321 + 400, "after reset, original args function should be called"); + my_proxy.mocked_args_function(500); + assertEq(my_proxy.a(), 321 + 500, "after reset, original args function should be called"); + } } diff --git a/testdata/default/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol index 321b5b3cbbeed..97ee9ad3a9293 100644 --- a/testdata/default/cheats/RpcUrls.t.sol +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -7,7 +7,7 @@ contract RpcUrlTest is Test { // returns the correct url function testCanGetRpcUrl() public { string memory url = vm.rpcUrl("mainnet"); - assertTrue(bytes(url).length >= 36); + assertTrue(bytes(url).length != 0); } // returns an error if env alias does not exist diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev index 213471e492846..8bc543e5cf68b 100644 --- a/testdata/forge-std-rev +++ b/testdata/forge-std-rev @@ -1 +1 @@ -1801b0541f4fda118a10798fd3486bb7051c5dd6 \ No newline at end of file +0844d7e1fc5e60d77b68e469bff60265f236c398 \ No newline at end of file diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 0fd8135bbcd22..8c60909ff66c9 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -8,11 +8,13 @@ ignored_error_codes = [ 1878, # SPDX license identifier not provided 2018, # Function state mutability can be restricted 2072, # Unused local variable + 2424, # Natspec memory-safe-assembly special comment deprecated 2519, # This declaration shadows an existing declaration 3860, # Contract init code size exceeds limit 5159, # Selfdestruct has been deprecated 5574, # Contract code size exceeds limit 5667, # Unused function parameter + 9207, # 'transfer' is deprecated ] extra_output = ["storageLayout"] @@ -57,4 +59,7 @@ polygon = "${RPC_POLYGON}" bsc = "${RPC_BSC}" avaxTestnet = "https://api.avax-test.network/ext/bc/C/rpc" moonbeam = "https://moonbeam-rpc.publicnode.com" +polkadotTestnet = "https://eth-rpc-testnet.polkadot.io" +kusama = "https://eth-rpc-kusama.polkadot.io" +polkadot = "https://eth-rpc.polkadot.io" rpcEnvAlias = "${RPC_ENV_ALIAS}" diff --git a/testdata/utils/Vm.sol b/testdata/utils/Vm.sol index e06ed8facda10..e4b11e8d42e64 100644 --- a/testdata/utils/Vm.sol +++ b/testdata/utils/Vm.sol @@ -182,6 +182,7 @@ interface Vm { function copyFile(string calldata from, string calldata to) external returns (uint64 copied); function copyStorage(address from, address to) external; function createDir(string calldata path, bool recursive) external; + function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey); function createFork(string calldata urlOrAlias) external returns (uint256 forkId); function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); @@ -247,6 +248,7 @@ interface Vm { function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); function etch(address target, bytes calldata newRuntimeBytecode) external; function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external view returns (EthGetLogs[] memory logs); + function executeTransaction(bytes calldata rawTx) external returns (bytes memory); function exists(string calldata path) external view returns (bool result); function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; @@ -414,6 +416,7 @@ interface Vm { function promptSecret(string calldata promptText) external returns (string memory input); function promptSecretUint(string calldata promptText) external returns (uint256); function promptUint(string calldata promptText) external returns (uint256); + function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey); function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); function randomAddress() external view returns (address); function randomBool() external view returns (bool); @@ -500,6 +503,7 @@ interface Vm { function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); function signDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); + function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature); function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); @@ -559,6 +563,7 @@ interface Vm { function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); function txGasPrice(uint256 newGasPrice) external; function unixTime() external view returns (uint256 milliseconds); + function verifyEd25519(bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey) external pure returns (bool valid); function warmSlot(address target, bytes32 slot) external; function warp(uint256 newTimestamp) external; function writeFile(string calldata path, string calldata data) external; diff --git a/typos.toml b/typos.toml index 2e86c7c3aaffe..136333b3b57e3 100644 --- a/typos.toml +++ b/typos.toml @@ -22,3 +22,4 @@ Caf = "Caf" froms = "froms" strat = "strat" ba = "ba" +consts = "consts" From 3d621aaefcf4b233fccc1f969c13b90f18039af2 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:10:46 +0000 Subject: [PATCH 260/391] Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/util.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 8e0d7912871bb..d899a1e764629 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -298,17 +298,25 @@ fn resolve_and_validate_under_base(path: &Path) -> io::Result { } fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { - for entry in fs::read_dir(src)? { + // Ensure that each recursion step operates on paths that are constrained to the + // configured base directory. This guarantees that any `src_path` passed to + // filesystem operations cannot escape the allowed workspace even if the initial + // input was influenced by the user. + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + for entry in fs::read_dir(&src)? { let entry = entry?; let ty = entry.file_type()?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + let name = entry.file_name(); + let src_path = src.join(&name); + let dst_path = dst.join(&name); if ty.is_dir() { // Skip build artifact directories at the root level if is_root - && let Some(name) = entry.file_name().to_str() - && SKIP_DIRS.contains(&name) + && let Some(name_str) = name.to_str() + && SKIP_DIRS.contains(&name_str) { continue; } From 65e57c91505cc1895026e6b7836c5077f34b28c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:12:53 +0000 Subject: [PATCH 261/391] chore(deps): bump the cargo group across 1 directory with 9 updates (#377) Bumps the cargo group with 3 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing), [keccak](https://github.com/RustCrypto/sponges) and [slab](https://github.com/tokio-rs/slab). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `time` from 0.3.41 to 0.3.47 - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.41...v0.3.47) Updates `alloy-dyn-abi` from 1.3.0 to 1.5.7 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.3.0...v1.5.7) Updates `bytes` from 1.10.1 to 1.11.1 - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.1...v1.11.1) Updates `keccak` from 0.1.5 to 0.1.6 - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) Updates `lru` from 0.12.5 to 0.16.3 - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.3) Updates `protobuf` from 3.3.0 to 3.7.2 Updates `ruint` from 1.15.0 to 1.17.2 - [Release notes](https://github.com/recmo/uint/releases) - [Changelog](https://github.com/recmo/uint/blob/main/CHANGELOG.md) - [Commits](https://github.com/recmo/uint/compare/v1.15.0...v1.17.2) Updates `slab` from 0.4.10 to 0.4.12 - [Release notes](https://github.com/tokio-rs/slab/releases) - [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.12) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: time dependency-version: 0.3.47 dependency-type: direct:production dependency-group: cargo - dependency-name: alloy-dyn-abi dependency-version: 1.5.7 dependency-type: direct:production dependency-group: cargo - dependency-name: bytes dependency-version: 1.11.1 dependency-type: direct:production dependency-group: cargo - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect dependency-group: cargo - dependency-name: lru dependency-version: 0.16.3 dependency-type: indirect dependency-group: cargo - dependency-name: protobuf dependency-version: 3.7.2 dependency-type: indirect dependency-group: cargo - dependency-name: ruint dependency-version: 1.17.2 dependency-type: indirect dependency-group: cargo - dependency-name: slab dependency-version: 0.4.12 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- Cargo.lock | 3757 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 2423 insertions(+), 1334 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4eba64171dd1a..a977601d2c7be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -65,14 +74,14 @@ dependencies = [ "alloy-primitives", "num_enum", "serde", - "strum 0.27.2", + "strum", ] [[package]] name = "alloy-consensus" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6093bc69509849435a2d68237a2e9fea79d27390c8e62f1e4012c460aabad8" +checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" dependencies = [ "alloy-eips", "alloy-primitives", @@ -81,23 +90,25 @@ dependencies = [ "alloy-trie", "alloy-tx-macros", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", "k256", "once_cell", "rand 0.8.5", "secp256k1 0.30.0", "serde", + "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-consensus-any" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d1cfed4fefd13b5620cb81cdb6ba397866ff0de514c1b24806e6e79cdff5570" +checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" dependencies = [ "alloy-consensus", "alloy-eips", @@ -109,9 +120,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28074a21cd4f7c3a7ab218c4f38fae6be73944e1feae3b670c68b60bf85ca40" +checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -127,22 +138,21 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-dyn-abi" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e8a436f0aad7df8bb47f144095fba61202265d9f5f09a70b0e3227881a668e" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "arbitrary", - "derive_arbitrary", - "derive_more 2.0.1", + "derive_more", "itoa", "proptest", "serde", @@ -160,105 +170,141 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-eip2930" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip5792" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ec415c2141bef46ea7d1b80877d7dc4639c6ccf6a774b75f05e3deadb27469" +dependencies = [ + "alloy-primitives", + "alloy-serde", "serde", + "serde_json", ] [[package]] name = "alloy-eip7702" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "k256", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", ] [[package]] name = "alloy-eips" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5937e2d544e9b71000942d875cbc57965b32859a666ea543cc57aae5a06d602d" +checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive", "serde", + "serde_with", "sha2 0.10.9", + "thiserror 2.0.18", ] [[package]] name = "alloy-ens" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7b88162405231467e75e4230785f88672780bbaf19710fe4aaae597f793126" +checksum = "79460500df1a98836253ab24871ecd54d12809ac9045b0d10eed727c3555efa9" dependencies = [ "alloy-contract", "alloy-primitives", "alloy-provider", "alloy-sol-types", "async-trait", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-evm" -version = "0.16.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4042e855163839443cba91147fb7737c4aba02df4767cb322b0e8cea5a77642c" +checksum = "7b99ba7b74a87176f31ee1cd26768f7155b0eeff61ed925f59b13085ffe5f891" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-hardforks", + "alloy-op-hardforks", "alloy-primitives", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", - "derive_more 2.0.1", - "op-alloy-consensus", + "derive_more", + "op-alloy", "op-revm", "revm", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-genesis" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51b4c13e02a8104170a4de02ccf006d7c233e6c10ab290ee16e7041e6ac221d" +checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-serde", "alloy-trie", + "borsh", "serde", + "serde_with", ] [[package]] name = "alloy-hardforks" -version = "0.2.13" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165210652f71dfc094b051602bafd691f506c54050a174b1cba18fb5ef706a3" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -269,9 +315,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459f98c6843f208856f338bfb25e65325467f7aff35dfeb0484d0a76e059134b" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -281,24 +327,24 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b590caa6b6d8bc10e6e7a7696c59b1e550e89f27f50d1ee13071150d3a3e3f66" +checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" dependencies = [ "alloy-primitives", "alloy-sol-types", "http 1.3.1", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-network" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36fe5af1fca03277daa56ad4ce5f6d623d3f4c2273ea30b9ee8674d18cefc1fa" +checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -313,18 +359,18 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.0.1", + "derive_more", "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-network-primitives" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793df1e3457573877fbde8872e4906638fde565ee2d3bd16d04aad17d43dbf0e" +checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" dependencies = [ "alloy-consensus", "alloy-eips", @@ -335,9 +381,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.16.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c0bc6a883d3198c43c4018aa952448a303dec265439fa1c2e7c4397beeb289" +checksum = "646a01ebc9778ee08bcc33cfa6714efc548f94e53de1aff6b34d9da55a2e5d41" dependencies = [ "alloy-consensus", "alloy-eips", @@ -345,39 +391,40 @@ dependencies = [ "alloy-op-hardforks", "alloy-primitives", "auto_impl", - "op-alloy-consensus", + "op-alloy", "op-revm", "revm", + "thiserror 2.0.18", ] [[package]] name = "alloy-op-hardforks" -version = "0.2.13" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3417f4187eaf7f7fb0d7556f0197bca26f0b23c4bb3aca0c9d566dc1c5d727a2" +checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", "alloy-hardforks", + "alloy-primitives", "auto_impl", ] [[package]] name = "alloy-primitives" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cfebde8c581a5d37b678d0a48a32decb51efd7a63a08ce2517ddec26db705c8" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_arbitrary", - "derive_more 2.0.1", - "foldhash", - "getrandom 0.3.3", - "hashbrown 0.15.4", - "indexmap 2.10.0", + "derive_more", + "foldhash 0.2.0", + "getrandom 0.4.2", + "hashbrown 0.16.1", + "indexmap 2.13.0", "itoa", "k256", "keccak-asm", @@ -385,18 +432,18 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash 2.1.1", "serde", "sha3", - "tiny-keccak", ] [[package]] name = "alloy-provider" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59879a772ebdcde9dc4eb38b2535d32e8503d3175687cc09e763a625c5fcf32" +checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" dependencies = [ "alloy-chains", "alloy-consensus", @@ -424,14 +471,13 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "http 1.3.1", - "lru 0.13.0", + "lru", "parking_lot", "pin-project 1.1.10", "reqwest", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -440,13 +486,14 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdfb2899b54b7cb0063fa8e61938320f9be6b81b681be69c203abf130a87baa" +checksum = "e8bd82953194dec221aa4cbbbb0b1e2df46066fe9d0333ac25b43a311e122d13" dependencies = [ "alloy-json-rpc", "alloy-primitives", "alloy-transport", + "auto_impl", "bimap", "futures", "parking_lot", @@ -478,14 +525,14 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "alloy-rpc-client" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f060e3bb9f319eb01867a2d6d1ff9e0114e8877f5ca8f5db447724136106cae" +checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -509,9 +556,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d47b637369245d2dafef84b223b1ff5ea59e6cd3a98d2d3516e32788a0b216df" +checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -525,9 +572,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b1f499acb3fc729615147bc113b8b798b17379f19d43058a687edc5792c102" +checksum = "e0a3100b76987c1b1dc81f3abe592b7edc29e92b1242067a69d65e0030b35cf9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -537,48 +584,68 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e26b4dd90b33bd158975307fb9cf5fafa737a0e33cbb772a8648bf8be13c104" +checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-beacon" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a22e13215866f5dfd5d3278f4c41f1fad9410dc68ce39022f58593c873c26f8" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "derive_more", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + [[package]] name = "alloy-rpc-types-debug" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71841e6fc8e221892035a74f7d5b279c0a2bf27a7e1c93e7476c64ce9056624e" +checksum = "e1b21e1ad18ff1b31ff1030e046462ab8168cf8894e6778cd805c8bdfe2bd649" dependencies = [ "alloy-primitives", + "derive_more", "serde", + "serde_with", ] [[package]] name = "alloy-rpc-types-engine" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f9cbf5f781b9ee39cfdddea078fdef6015424f4c8282ef0e5416d15ca352c4" +checksum = "e4ac61f03f1edabccde1c687b5b25fff28f183afee64eaa2e767def3929e4457" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more 2.0.1", + "derive_more", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive", "jsonwebtoken", "rand 0.8.5", "serde", - "strum 0.27.2", + "strum", ] [[package]] name = "alloy-rpc-types-eth" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46586ec3c278639fc0e129f0eb73dbfa3d57f683c44b2ff5e066fab7ba63fa1f" +checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -592,28 +659,28 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-trace" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9a2184493c374ca1dbba9569d37215c23e489970f8c3994f731cb3ed6b0b7d" +checksum = "1ad79f1e27e161943b5a4f99fe5534ef0849876214be411e0032c12f38e94daa" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aaf142f4f6c0bdd06839c422179bae135024407d731e6f365380f88cd4730e" +checksum = "d459f902a2313737bc66d18ed094c25d2aeb268b74d98c26bbbda2aa44182ab0" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -623,9 +690,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1722bc30feef87cc0fa824e43c9013f9639cc6c037be7be28a31361c788be2" +checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" dependencies = [ "alloy-primitives", "serde", @@ -634,9 +701,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3674beb29e68fbbc7be302b611cf35fe07b736e308012a280861df5a2361395" +checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -646,32 +713,33 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-signer-aws" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605b1659b320b16708bb84b41038b2f0e2a60d90972c28319c4f5a4866f0efd4" +checksum = "e38b411077d7b17e464de7dfa599f5b94161cdffc25c2f28a90a3a345b6d6490" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", + "aws-config", "aws-sdk-kms", "k256", "spki", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-gcp" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a207671ef0bf6f61e9c80c9ccb6d203439071252fb35886d6a89aae5431cd9c" +checksum = "485d0c73c53a36580be4d882a5c6c9a069759088de88ff759e59342a793adb16" dependencies = [ "alloy-consensus", "alloy-network", @@ -681,15 +749,15 @@ dependencies = [ "gcloud-sdk", "k256", "spki", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-ledger" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce5e8c97a526d39052035a99652b9cfacf0d646d4a3625fac9c919d20a46fb0" +checksum = "ff7a41e469bce9a836a9fbba7c09f8eba25703062accf6a64bd90b5ed61c1b01" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -701,15 +769,15 @@ dependencies = [ "coins-ledger", "futures-util", "semver 1.0.26", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-local" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7094c39cd41b03ed642145b0bd37251e31a9cf2ed19e1ce761f089867356a6" +checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" dependencies = [ "alloy-consensus", "alloy-network", @@ -721,14 +789,15 @@ dependencies = [ "eth-keystore", "k256", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.18", + "zeroize", ] [[package]] name = "alloy-signer-trezor" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fc3a082bf4dcf2e330b040378c73a113d31fe03068607a4d80368b47e60c44" +checksum = "fb778ec3bef46eb5c0edab6e25e0c6f40d98a5e81e423940f819824b879680a8" dependencies = [ "alloy-consensus", "alloy-network", @@ -736,49 +805,65 @@ dependencies = [ "alloy-signer", "async-trait", "semver 1.0.26", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "trezor-client", ] +[[package]] +name = "alloy-signer-turnkey" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589b334f4cf9de0d80568e8eaf11479b492e74dc2c08991306361065bde2321a" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "thiserror 2.0.18", + "tracing", + "turnkey_client", +] + [[package]] name = "alloy-sol-macro" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedac07a10d4c2027817a43cc1f038313fc53c7ac866f7363239971fd01f9f18" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f9a598f010f048d8b8226492b6401104f5a5c1273c2869b72af29b48bb4ba9" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.10.0", + "indexmap 2.13.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "sha3", + "syn 2.0.117", "syn-solidity", - "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f494adf9d60e49aa6ce26dfd42c7417aa6d4343cf2ae621f20e4d92a5ad07d85" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" dependencies = [ "alloy-json-abi", "const-hex", @@ -788,15 +873,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.104", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52db32fbd35a9c0c0e538b58b81ebbae08a51be029e7ad60e08b60481c2ec6c3" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", "winnow", @@ -804,9 +889,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a285b46e3e0c177887028278f04cc8262b76fd3b8e0e20e93cea0a58c35f5ac5" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -816,20 +901,20 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89bec2f59a41c0e259b6fe92f78dfc49862c17d10f938db9c33150d5a7f42b6" +checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "auto_impl", "base64 0.22.1", - "derive_more 2.0.1", + "derive_more", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -839,12 +924,13 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3615ec64d775fec840f4e9d5c8e1f739eb1854d8d28db093fb3d4805e0cb53" +checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" dependencies = [ "alloy-json-rpc", "alloy-transport", + "itertools 0.14.0", "reqwest", "serde_json", "tower", @@ -854,9 +940,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374db72669d8ee09063b9aa1a316e812d5cdfce7fc9a99a3eceaa0e5512300d2" +checksum = "c2ef85688e5ac2da72afc804e0a1f153a1f309f05a864b1998bbbed7804dbaab" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -874,15 +960,14 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5dbaa6851875d59c8803088f4b6ec72eaeddf7667547ae8995c1a19fbca6303" +checksum = "b9f00445db69d63298e2b00a0ea1d859f00e6424a3144ffc5eba9c31da995e16" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http 1.3.1", - "rustls", "serde_json", "tokio", "tokio-tungstenite", @@ -892,52 +977,33 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" +checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 2.0.1", + "derive_more", "nybbles", "serde", "smallvec", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-tx-macros" -version = "1.0.23" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f916ff6d52f219c44a9684aea764ce2c7e1d53bd4a724c9b127863aeacc30bb" +checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" dependencies = [ - "alloy-primitives", - "darling 0.20.11", + "darling 0.21.0", "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "ammonia" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" -dependencies = [ - "cssparser", - "html5ever", - "maplit", - "tendril", - "url", + "syn 2.0.117", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -952,6 +1018,16 @@ name = "annotate-snippets" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.0", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96401ca08501972288ecbcde33902fce858bf73fbcbdf91dab8c3a9544e106bb" dependencies = [ "anstyle", "memchr", @@ -1032,24 +1108,25 @@ dependencies = [ [[package]] name = "anvil" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", + "alloy-eip5792", "alloy-eips", "alloy-evm", "alloy-genesis", - "alloy-hardforks", "alloy-network", "alloy-op-evm", - "alloy-op-hardforks", "alloy-primitives", "alloy-provider", "alloy-pubsub", "alloy-rlp", "alloy-rpc-types", + "alloy-rpc-types-beacon", + "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", "alloy-signer-local", @@ -1064,8 +1141,8 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_fig", "ctrlc", + "ethereum_ssz 0.10.1", "eyre", "fdlimit", "flate2", @@ -1073,7 +1150,8 @@ dependencies = [ "foundry-common", "foundry-config", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", + "foundry-primitives", "foundry-test-utils", "futures", "hyper", @@ -1084,26 +1162,28 @@ dependencies = [ "parking_lot", "rand 0.8.5", "rand 0.9.2", + "reqwest", "revm", "revm-inspectors", "serde", "serde_json", "tempfile", - "thiserror 2.0.12", + "tempo-primitives", + "thiserror 2.0.18", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", "yansi", ] [[package]] name = "anvil-core" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", + "alloy-eip5792", "alloy-eips", - "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", @@ -1111,18 +1191,17 @@ dependencies = [ "bytes", "foundry-common", "foundry-evm", - "op-alloy-consensus", - "op-revm", + "foundry-primitives", "rand 0.9.2", "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "anvil-rpc" -version = "1.3.1" +version = "1.6.0" dependencies = [ "serde", "serde_json", @@ -1130,7 +1209,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.3.1" +version = "1.6.0" dependencies = [ "anvil-rpc", "async-trait", @@ -1143,7 +1222,7 @@ dependencies = [ "pin-project 1.1.10", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio-util", "tower-http", "tracing", @@ -1151,9 +1230,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -1164,16 +1243,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "ariadne" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f5e3dca4e09a6f340a61a0e9c7b61e030c69fc27bf29d73218f7e5e3b7638f" -dependencies = [ - "unicode-width 0.1.14", - "yansi", -] - [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1304,7 +1373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1342,7 +1411,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1431,7 +1500,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1529,7 +1598,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1540,7 +1609,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1593,7 +1662,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -1688,7 +1757,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.17.0", + "uuid 1.21.0", ] [[package]] @@ -2028,12 +2097,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - [[package]] name = "backtrace" version = "0.3.75" @@ -2101,7 +2164,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -2114,7 +2177,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.104", + "syn 2.0.117", "which 4.4.2", ] @@ -2157,11 +2220,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2197,9 +2260,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", @@ -2209,116 +2272,123 @@ dependencies = [ [[package]] name = "boa_ast" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c340fe0f0b267787095cbe35240c6786ff19da63ec7b69367ba338eace8169b" +checksum = "bc119a5ad34c3f459062a96907f53358989b173d104258891bb74f95d93747e8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "boa_interner", "boa_macros", "boa_string", - "indexmap 2.10.0", + "indexmap 2.13.0", "num-bigint", "rustc-hash 2.1.1", ] [[package]] name = "boa_engine" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f620c3f06f51e65c0504ddf04978be1b814ac6586f0b45f6019801ab5efd37f9" +checksum = "e637ec52ea66d76b0ca86180c259d6c7bb6e6a6e14b2f36b85099306d8b00cc3" dependencies = [ + "aligned-vec", "arrayvec", - "bitflags 2.9.1", + "bitflags 2.11.0", "boa_ast", "boa_gc", "boa_interner", "boa_macros", "boa_parser", - "boa_profiler", "boa_string", "bytemuck", "cfg-if", + "cow-utils", "dashmap", + "dynify", "fast-float2", - "hashbrown 0.15.4", + "float16", + "futures-channel", + "futures-concurrency", + "futures-lite", + "hashbrown 0.16.1", "icu_normalizer", - "indexmap 2.10.0", + "indexmap 2.13.0", "intrusive-collections", - "itertools 0.13.0", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", "num_enum", - "once_cell", - "pollster", + "paste", "portable-atomic", - "rand 0.8.5", + "rand 0.9.2", "regress", "rustc-hash 2.1.1", "ryu-js", "serde", "serde_json", - "sptr", + "small_btree", "static_assertions", + "tag_ptr", "tap", "thin-vec", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", + "xsum", ] [[package]] name = "boa_gc" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2425c0b7720d42d73eaa6a883fbb77a5c920da8694964a3d79a67597ac55cce2" +checksum = "f1179f690cbfcbe5364cceee5f1cb577265bb6f07b0be6f210aabe270adcf9da" dependencies = [ "boa_macros", - "boa_profiler", "boa_string", - "hashbrown 0.15.4", + "hashbrown 0.16.1", "thin-vec", ] [[package]] name = "boa_interner" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42407a3b724cfaecde8f7d4af566df4b56af32a2f11f0956f5570bb974e7f749" +checksum = "9626505d33dc63d349662437297df1d3afd9d5fc4a2b3ad34e5e1ce879a78848" dependencies = [ "boa_gc", "boa_macros", - "hashbrown 0.15.4", - "indexmap 2.10.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", "once_cell", - "phf", + "phf 0.13.1", "rustc-hash 2.1.1", "static_assertions", ] [[package]] name = "boa_macros" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" +checksum = "7f36418a46544b152632c141b0a0b7a453cd69ca150caeef83aee9e2f4b48b7d" dependencies = [ + "cfg-if", + "cow-utils", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", "synstructure", ] [[package]] name = "boa_parser" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc142dac798cdc6e2dbccfddeb50f36d2523bb977a976e19bdb3ae19b740804" +checksum = "02f99bf5b684f0de946378fcfe5f38c3a0fbd51cbf83a0f39ff773a0e218541f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "boa_ast", "boa_interner", "boa_macros", - "boa_profiler", "fast-float2", "icu_properties", "num-bigint", @@ -2327,30 +2397,25 @@ dependencies = [ "rustc-hash 2.1.1", ] -[[package]] -name = "boa_profiler" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4064908e7cdf9b6317179e9b04dcb27f1510c1c144aeab4d0394014f37a0f922" - [[package]] name = "boa_string" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7debc13fbf7997bf38bf8e9b20f1ad5e2a7d27a900e1f6039fe244ce30f589b5" +checksum = "45ce9d7aa5563a2e14eab111e2ae1a06a69a812f6c0c3d843196c9d03fbef440" dependencies = [ "fast-float2", + "itoa", "paste", "rustc-hash 2.1.1", - "sptr", + "ryu-js", "static_assertions", ] [[package]] name = "bon" -version = "3.6.5" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d9ef19ae5263a138da9a86871eca537478ab0332a7770bac7e3f08b801f89f" +checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83" dependencies = [ "bon-macros", "rustversion", @@ -2358,9 +2423,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.5" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577ae008f2ca11ca7641bd44601002ee5ab49ef0af64846ce1ab6057218a5cc1" +checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0" dependencies = [ "darling 0.21.0", "ident_case", @@ -2368,7 +2433,30 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2394,7 +2482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] @@ -2412,22 +2500,22 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -2438,9 +2526,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -2457,9 +2545,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" dependencies = [ "blst", "cc", @@ -2502,15 +2590,9 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cast" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -2526,6 +2608,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", + "alloy-rpc-types-beacon", "alloy-serde", "alloy-signer", "alloy-signer-local", @@ -2535,23 +2618,24 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_fig", "comfy-table", + "dirs", "dunce", "evmole", "eyre", + "forge-fmt", "foundry-block-explorers", "foundry-cli", "foundry-common", "foundry-compilers", "foundry-config", + "foundry-debugger", "foundry-evm", - "foundry-evm-core", + "foundry-primitives", "foundry-test-utils", "foundry-wallets", "futures", "itertools 0.14.0", - "op-alloy-consensus", "op-alloy-flz", "rand 0.8.5", "rand 0.9.2", @@ -2588,6 +2672,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -2611,7 +2701,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chisel" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2619,6 +2709,7 @@ dependencies = [ "clap", "dirs", "eyre", + "forge-doc", "forge-fmt", "foundry-cli", "foundry-common", @@ -2626,35 +2717,35 @@ dependencies = [ "foundry-config", "foundry-evm", "foundry-solang-parser", - "regex", + "foundry-test-utils", + "itertools 0.14.0", "reqwest", - "revm", + "rexpect", "rustyline", "semver 1.0.26", "serde", "serde_json", - "solar-parse", - "strum 0.27.2", + "solar-compiler", + "tempfile", "time", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", "walkdir", "yansi", ] [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2750,10 +2841,10 @@ dependencies = [ ] [[package]] -name = "clap_complete_fig" -version = "4.5.2" +name = "clap_complete_nushell" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b" +checksum = "685bc86fd34b7467e0532a4f8435ab107960d69a243785ef0275e571b35b641a" dependencies = [ "clap", "clap_complete", @@ -2768,7 +2859,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -2785,7 +2876,7 @@ checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae" dependencies = [ "nix 0.29.0", "terminfo", - "thiserror 2.0.12", + "thiserror 2.0.18", "which 8.0.0", "windows-sys 0.59.0", ] @@ -2907,7 +2998,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors 4.2.2", + "owo-colors", "tracing-error", ] @@ -2918,7 +3009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", - "owo-colors 4.2.2", + "owo-colors", "tracing-core", "tracing-error", ] @@ -2931,12 +3022,21 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "2.2.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.60.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", ] [[package]] @@ -2958,9 +3058,9 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -3056,9 +3156,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -3089,6 +3189,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -3122,6 +3228,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -3162,13 +3274,10 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "crossterm_winapi", - "mio", "parking_lot", "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", "winapi", ] @@ -3178,13 +3287,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "crossterm_winapi", - "derive_more 2.0.1", + "derive_more", "document-features", "mio", "parking_lot", - "rustix 1.0.8", + "rustix 1.1.4", "signal-hook", "signal-hook-mio", "winapi", @@ -3227,29 +3336,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.104", -] - [[package]] name = "ctr" version = "0.9.2" @@ -3270,12 +3356,25 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.20.11" +name = "curve25519-dalek-ng" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ - "darling_core 0.20.11", + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", "darling_macro 0.20.11", ] @@ -3300,7 +3399,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3313,8 +3412,9 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3325,7 +3425,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3336,7 +3436,7 @@ checksum = "e79f8e61677d5df9167cd85265f8e5f64b215cdea3fb55eebc3e622e44c7a146" dependencies = [ "darling_core 0.21.0", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3372,12 +3472,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -3393,13 +3493,13 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3410,7 +3510,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3431,7 +3531,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3441,65 +3541,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.104", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", + "syn 2.0.117", ] [[package]] name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "unicode-xid", + "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.104", + "rustc_version 0.4.1", + "syn 2.0.117", "unicode-xid", ] [[package]] name = "dialoguer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" dependencies = [ - "console 0.15.11", + "console 0.16.0", "shell-words", - "tempfile", - "thiserror 1.0.69", "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -3550,7 +3634,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3580,21 +3664,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - [[package]] name = "dunce" version = "1.0.5" @@ -3607,6 +3676,26 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dynify" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81acb15628a3e22358bf73de5e7e62360b8a777dbcb5fc9ac7dfa9ae73723747" +dependencies = [ + "dynify-macros", +] + +[[package]] +name = "dynify-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -3622,6 +3711,21 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -3631,9 +3735,15 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "either" version = "1.15.0" @@ -3736,7 +3846,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -3768,6 +3878,26 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -3822,6 +3952,61 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -3835,9 +4020,9 @@ dependencies = [ [[package]] name = "evm-disassembler" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded685d9f07315ff689ba56e7d84e6f1e782db19b531a46c34061a733bba7258" +checksum = "ace07bd9e99c61333fa7b95b264f0f814bdfc4ec6714f67ffd8e703b25edb2be" dependencies = [ "eyre", "hex", @@ -3851,7 +4036,7 @@ checksum = "c29ecc930ee2ed03083436c2ddd7e5292c3c3bcda65f6a37369502d578a853f1" dependencies = [ "alloy-dyn-abi", "alloy-primitives", - "indexmap 2.10.0", + "indexmap 2.13.0", ] [[package]] @@ -3905,7 +4090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.0.8", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -3930,17 +4115,16 @@ dependencies = [ ] [[package]] -name = "figment" -version = "0.10.19" +name = "figment2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +checksum = "4380ce44915a6227efbb61e3885bc1c8e99fb9820f5db612abfac2c5cfc46871" dependencies = [ "atomic", "parking_lot", - "pear", "serde", "tempfile", - "toml 0.8.23", + "toml_edit 0.23.10+spec-1.0.0", "uncased", "version_check", ] @@ -3974,6 +4158,16 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float16" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bffafbd079d520191c7c2779ae9cf757601266cf4167d3f659ff09617ff8483" +dependencies = [ + "cfg-if", + "rustc_version 0.2.3", +] + [[package]] name = "fnv" version = "1.0.7" @@ -3986,9 +4180,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-awesome-as-a-crate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "932dcfbd51320af5f27f1ba02d2e567dec332cac7d2c221ba45d8e767264c4dc" + [[package]] name = "forge" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-dyn-abi", @@ -4007,7 +4213,6 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_fig", "clearscreen", "comfy-table", "dunce", @@ -4027,23 +4232,21 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", "foundry-linking", - "foundry-solang-parser", "foundry-test-utils", "foundry-wallets", - "futures", "globset", - "indicatif 0.18.0", + "indicatif 0.18.4", "inferno", "itertools 0.14.0", "mockall", "opener", "parking_lot", - "paste", "path-slash", "proptest", "quick-junit", + "rand 0.9.2", "rayon", "regex", "reqwest", @@ -4053,17 +4256,18 @@ dependencies = [ "serde_json", "similar", "similar-asserts", - "solar-parse", - "solar-sema", + "solar-compiler", "soldeer-commands", - "strum 0.27.2", + "soldeer-core", + "strum", "svm-rs", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", - "toml_edit 0.23.2", + "toml_edit 0.24.1+spec-1.1.0", "tower-http", "tracing", + "url", "watchexec", "watchexec-events", "watchexec-signals", @@ -4072,10 +4276,10 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-primitives", - "derive_more 2.0.1", + "derive_more", "eyre", "forge-fmt", "foundry-common", @@ -4083,52 +4287,48 @@ dependencies = [ "foundry-config", "foundry-solang-parser", "itertools 0.14.0", - "mdbook", + "mdbook-driver", "rayon", "regex", "serde", "serde_json", - "thiserror 2.0.12", - "toml 0.9.4", + "solar-compiler", + "thiserror 2.0.18", + "toml", "tracing", ] [[package]] name = "forge-fmt" -version = "1.3.1" +version = "1.6.0" dependencies = [ - "alloy-primitives", - "ariadne", + "foundry-common", "foundry-config", - "foundry-solang-parser", + "foundry-test-utils", "itertools 0.14.0", - "similar-asserts", - "thiserror 2.0.12", - "toml 0.9.4", - "tracing", - "tracing-subscriber 0.3.19", + "similar", + "snapbox", + "solar-compiler", + "toml", ] [[package]] name = "forge-lint" -version = "1.3.1" +version = "1.6.0" dependencies = [ + "eyre", "foundry-common", "foundry-compilers", "foundry-config", "heck", "rayon", - "solar-ast", - "solar-data-structures", - "solar-interface", - "solar-parse", - "solar-sema", - "thiserror 2.0.12", + "solar-compiler", + "thiserror 2.0.18", ] [[package]] name = "forge-script" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4147,7 +4347,6 @@ dependencies = [ "eyre", "forge-script-sequence", "forge-verify", - "foundry-block-explorers", "foundry-cheatcodes", "foundry-cli", "foundry-common", @@ -4158,7 +4357,7 @@ dependencies = [ "foundry-linking", "foundry-wallets", "futures", - "indicatif 0.18.0", + "indicatif 0.18.4", "itertools 0.14.0", "parking_lot", "revm-inspectors", @@ -4166,6 +4365,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "thiserror 2.0.18", "tokio", "tracing", "yansi", @@ -4173,7 +4373,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-network", "alloy-primitives", @@ -4189,7 +4389,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -4199,13 +4399,12 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "serde_json", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "forge-verify" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4221,7 +4420,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", "foundry-test-utils", "futures", "itertools 0.14.0", @@ -4234,6 +4433,7 @@ dependencies = [ "tempfile", "tokio", "tracing", + "url", "yansi", ] @@ -4248,7 +4448,7 @@ dependencies = [ [[package]] name = "foundry-bench" -version = "0.1.0" +version = "1.6.0" dependencies = [ "chrono", "clap", @@ -4256,22 +4456,18 @@ dependencies = [ "eyre", "foundry-common", "foundry-compilers", - "foundry-config", "foundry-test-utils", - "num_cpus", "once_cell", "rayon", "serde", "serde_json", - "tempfile", - "tokio", ] [[package]] name = "foundry-block-explorers" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc107bbc3b4480995fdf337ca0ddedc631728175f418d3136ead9df8f4dc465e" +checksum = "ff814624bb21bfe43b70fb736ab39527b405d04cdc94d90b7e182fba28b25ec7" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -4281,13 +4477,13 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "foundry-cheatcodes" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4307,6 +4503,7 @@ dependencies = [ "base64 0.22.1", "dialoguer", "ecdsa", + "ed25519-consensus", "eyre", "forge-script-sequence", "foundry-cheatcodes-spec", @@ -4314,7 +4511,9 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm-core", + "foundry-evm-fuzz", "foundry-evm-traces", + "foundry-primitives", "foundry-wallets", "itertools 0.14.0", "jsonpath_lib", @@ -4329,26 +4528,27 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "thiserror 2.0.12", - "toml 0.9.4", + "solar-compiler", + "thiserror 2.0.18", + "toml", "tracing", "walkdir", ] [[package]] name = "foundry-cheatcodes-spec" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-sol-types", "foundry-macros", - "schemars 1.0.4", + "schemars 1.2.1", "serde", "serde_json", ] [[package]] name = "foundry-cli" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-dyn-abi", @@ -4360,43 +4560,54 @@ dependencies = [ "alloy-rlp", "cfg-if", "clap", + "clap_complete", + "clap_complete_nushell", "color-eyre", "dotenvy", "dunce", "eyre", - "forge-fmt", "foundry-block-explorers", + "foundry-cli-markdown", "foundry-common", "foundry-compilers", "foundry-config", - "foundry-debugger", "foundry-evm", "foundry-wallets", "futures", - "indicatif 0.18.0", + "indicatif 0.18.4", "itertools 0.14.0", "mimalloc", + "path-slash", "rayon", "regex", "rustls", "serde", "serde_json", - "solar-sema", + "solar-compiler", "strsim", - "strum 0.27.2", + "strum", "tempfile", "tikv-jemallocator", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", "tracing-tracy", "yansi", ] +[[package]] +name = "foundry-cli-markdown" +version = "1.6.0" +dependencies = [ + "clap", + "pretty_assertions", +] + [[package]] name = "foundry-common" -version = "1.3.1" +version = "1.6.0" dependencies = [ + "alloy-chains", "alloy-consensus", "alloy-dyn-abi", "alloy-eips", @@ -4434,25 +4645,24 @@ dependencies = [ "path-slash", "regex", "reqwest", + "revm", "semver 1.0.26", "serde", "serde_json", - "solar-parse", - "solar-sema", - "terminal_size", - "thiserror 2.0.12", + "solar-compiler", + "thiserror 2.0.18", "tokio", "tower", "tracing", "url", - "vergen", + "vergen-gitcl", "walkdir", "yansi", ] [[package]] name = "foundry-common-fmt" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4461,46 +4671,44 @@ dependencies = [ "alloy-rpc-types", "alloy-serde", "chrono", + "eyre", "foundry-macros", + "foundry-primitives", "revm", "serde", "serde_json", "similar-asserts", + "tempo-alloy", "yansi", ] [[package]] name = "foundry-compilers" -version = "0.18.2" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e953daf389ec3f8d82566cfcdadae58ca01b1bf0a7c467b2b03d447971589df" +checksum = "e639f98fe54d1cc0011a4bdb2eb1d838b379c9f004991ae7555a4cc09e8da32a" dependencies = [ "alloy-json-abi", "alloy-primitives", "auto_impl", - "derive_more 1.0.0", - "dirs", + "derive_more", "dyn-clone", "foundry-compilers-artifacts", "foundry-compilers-core", "fs_extra", - "futures-util", - "home", "itertools 0.14.0", "path-slash", - "rand 0.8.5", + "rand 0.9.2", "rayon", "semver 1.0.26", "serde", "serde_json", "sha2 0.10.9", - "solar-parse", - "solar-sema", + "solar-compiler", "svm-rs", "svm-rs-builds", "tempfile", - "thiserror 2.0.12", - "tokio", + "thiserror 2.0.18", "tracing", "winnow", "yansi", @@ -4508,9 +4716,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.18.2" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88824bfa6a5c2d7ecd076ba232cf37532af611c764fc27bb74371a91f662c36" +checksum = "93ec96df20055211f4e46b5a61fa479b2ea7d1ce0659818e0359afadfcded8d2" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4518,32 +4726,30 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.18.2" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55273eea0135b2d6e80d8475f660c87c855100999ce6f3e8c065e2fc55b95d7c" +checksum = "f8a206e475b5dd1a77dc33cd917cde4846148f5136729a24edb3a16ab431b90a" dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers-core", - "futures-util", + "memchr", "path-slash", "rayon", "regex", "semver 1.0.26", "serde", "serde_json", - "thiserror 2.0.12", - "tokio", + "thiserror 2.0.18", "tracing", - "walkdir", "yansi", ] [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.18.2" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d0254354ec10a7fc3b5a38ea0e6d9c841c35159f9b052e0bd87769e28c00da" +checksum = "f74883db8036522fa21d0853c21ac318e165ec88e141f1ef1d6f7b4dfa841ff7" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4556,9 +4762,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.18.2" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0773827e62d4eba29b04dff112211c1cadb614352da1cc2d951c33ca446415be" +checksum = "2ab384daeaea5c33cad8c3c094a1eb6f98e70922e18380c660980c74c19e362b" dependencies = [ "alloy-primitives", "cfg-if", @@ -4571,7 +4777,7 @@ dependencies = [ "serde_json", "svm-rs", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "walkdir", "xxhash-rust", @@ -4579,7 +4785,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-primitives", @@ -4587,49 +4793,51 @@ dependencies = [ "dirs", "dunce", "eyre", - "figment", + "figment2", "foundry-block-explorers", "foundry-compilers", + "foundry-evm-networks", "glob", "globset", "heck", "itertools 0.14.0", "mesc", - "number_prefix", "path-slash", + "rayon", "regex", "reqwest", "revm", - "schemars 1.0.4", + "schemars 1.2.1", "semver 1.0.26", "serde", "serde_json", "similar-asserts", - "solar-interface", - "solar-parse", + "snapbox", + "solar-compiler", "soldeer-core", "tempfile", - "thiserror 2.0.12", - "toml 0.9.4", - "toml_edit 0.23.2", + "thiserror 2.0.18", + "toml", + "toml_edit 0.24.1+spec-1.1.0", "tracing", + "unit-prefix", "walkdir", "yansi", ] [[package]] name = "foundry-config-spec" -version = "1.3.1" +version = "1.6.0" dependencies = [ "foundry-config", - "schemars 1.0.4", + "schemars 1.2.1", "serde", "serde_json", ] [[package]] name = "foundry-debugger" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-primitives", "crossterm 0.29.0", @@ -4647,7 +4855,7 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-dyn-abi", "alloy-evm", @@ -4663,26 +4871,29 @@ dependencies = [ "foundry-evm-core", "foundry-evm-coverage", "foundry-evm-fuzz", + "foundry-evm-networks", "foundry-evm-traces", - "indicatif 0.18.0", + "indicatif 0.18.4", "parking_lot", "proptest", + "rayon", "revm", "revm-inspectors", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", + "tokio", "tracing", - "uuid 1.17.0", + "uuid 1.21.0", ] [[package]] name = "foundry-evm-abi" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "derive_more 2.0.1", + "derive_more", "foundry-common-fmt", "foundry-macros", "itertools 0.14.0", @@ -4690,7 +4901,7 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4701,6 +4912,7 @@ dependencies = [ "alloy-json-abi", "alloy-network", "alloy-op-evm", + "alloy-op-hardforks", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -4711,6 +4923,7 @@ dependencies = [ "foundry-common", "foundry-config", "foundry-evm-abi", + "foundry-evm-networks", "foundry-fork-db", "foundry-test-utils", "futures", @@ -4721,7 +4934,7 @@ dependencies = [ "revm-inspectors", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -4729,7 +4942,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-primitives", "eyre", @@ -4739,12 +4952,13 @@ dependencies = [ "rayon", "revm", "semver 1.0.26", + "solar-compiler", "tracing", ] [[package]] name = "foundry-evm-fuzz" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4762,18 +4976,34 @@ dependencies = [ "rand 0.9.2", "revm", "serde", - "thiserror 2.0.12", + "solar-compiler", + "thiserror 2.0.18", "tracing", ] +[[package]] +name = "foundry-evm-networks" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-eips", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives", + "clap", + "revm", + "serde", +] + [[package]] name = "foundry-evm-traces" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", "alloy-sol-types", + "async-trait", "eyre", "foundry-block-explorers", "foundry-common", @@ -4783,22 +5013,25 @@ dependencies = [ "foundry-linking", "futures", "itertools 0.14.0", + "memchr", "rayon", + "reqwest", "revm", "revm-inspectors", "serde", "serde_json", - "solar-parse", + "solar-compiler", "tempfile", "tokio", "tracing", + "yansi", ] [[package]] name = "foundry-fork-db" -version = "0.16.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdf390c3633b0eb14c6bb26a0aeb63ea0200f1350ccbe07493f23148f58c4a5" +checksum = "ad537243394b8cda523e63749f2c9833be4c612500d35518efad34a3d1086710" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4812,7 +5045,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -4820,22 +5053,48 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-primitives", "foundry-compilers", + "rayon", "semver 1.0.26", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "foundry-macros" -version = "1.3.1" +version = "1.6.0" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "foundry-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "derive_more", + "op-alloy-consensus", + "op-alloy-rpc-types", + "op-revm", + "revm", + "serde", + "serde_json", + "tempo-alloy", + "tempo-primitives", ] [[package]] @@ -4847,15 +5106,16 @@ dependencies = [ "itertools 0.14.0", "lalrpop", "lalrpop-util", - "phf", - "thiserror 2.0.12", + "phf 0.11.3", + "thiserror 2.0.18", "unicode-xid", ] [[package]] name = "foundry-test-utils" -version = "1.3.1" +version = "1.6.0" dependencies = [ + "alloy-chains", "alloy-primitives", "alloy-provider", "eyre", @@ -4868,18 +5128,20 @@ dependencies = [ "parking_lot", "rand 0.9.2", "regex", + "reqwest", "serde_json", "snapbox", + "svm-rs", "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", "ui_test", ] [[package]] name = "foundry-wallets" -version = "1.3.1" +version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4891,21 +5153,28 @@ dependencies = [ "alloy-signer-ledger", "alloy-signer-local", "alloy-signer-trezor", + "alloy-signer-turnkey", "alloy-sol-types", "async-trait", "aws-config", - "aws-sdk-kms", + "axum", "clap", "derive_builder", "eth-keystore", "eyre", + "foundry-common", "foundry-config", - "gcloud-sdk", + "reqwest", "rpassword", "serde", - "thiserror 2.0.12", + "serde_json", + "thiserror 2.0.18", "tokio", + "tower", + "tower-http", "tracing", + "uuid 1.21.0", + "webbrowser", ] [[package]] @@ -4920,7 +5189,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -4980,6 +5249,19 @@ dependencies = [ "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project 1.1.10", + "smallvec", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -5003,6 +5285,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "futures-macro" version = "0.3.31" @@ -5011,7 +5306,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -5052,9 +5347,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gcloud-sdk" -version = "0.27.3" +version = "0.27.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac903b34cd86b6e3479924e8a9517edba8d5deebee0c1013353b05108ea9bd3" +checksum = "c8458d2ad7741b6a16981b84e66b7e4d8026423096da721894769c6980d06ecc" dependencies = [ "async-trait", "bytes", @@ -5063,8 +5358,8 @@ dependencies = [ "hyper", "jsonwebtoken", "once_cell", - "prost", - "prost-types", + "prost 0.13.5", + "prost-types 0.13.5", "reqwest", "secret-vault-value", "serde", @@ -5125,11 +5420,36 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "gimli" version = "0.31.1" @@ -5144,25 +5464,15 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "gmp-mpfr-sys" -version = "1.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" -dependencies = [ - "libc", - "windows-sys 0.59.0", + "regex-automata", + "regex-syntax", ] [[package]] @@ -5188,7 +5498,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.10.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -5218,7 +5528,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -5241,8 +5551,20 @@ checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] @@ -5316,13 +5638,12 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" dependencies = [ "log", "markup5ever", - "match_token", ] [[package]] @@ -5401,13 +5722,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http 1.3.1", "http-body 1.0.1", @@ -5415,6 +5737,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -5503,54 +5826,36 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "8b24a59706036ba941c9476a55cd57b82b77f38a3c667d637ee7cabbc85eaedc" dependencies = [ "displaydoc", "icu_collections", @@ -5559,65 +5864,60 @@ dependencies = [ "icu_provider", "smallvec", "utf16_iter", - "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "f5a97b8ac6235e69506e8dacfb2adf38461d2ce6d3e9bd9c94c4cbc3cd4400a4" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", + "serde", "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "ident_case" @@ -5658,15 +5958,15 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata", "same-file", "walkdir", "winapi-util", @@ -5689,7 +5989,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -5717,14 +6017,15 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] @@ -5742,9 +6043,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ "console 0.16.0", "portable-atomic", @@ -5775,19 +6076,13 @@ dependencies = [ "str_stack", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -5820,7 +6115,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -5866,7 +6161,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "libc", ] @@ -5953,12 +6248,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", - "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", ] [[package]] @@ -5969,23 +6262,30 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] -name = "jiff-tzdb" -version = "0.1.4" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] [[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" @@ -6048,20 +6348,31 @@ dependencies = [ "signature", ] +[[package]] +name = "kasuari" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -6100,9 +6411,9 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.5", + "regex-syntax", "sha3", - "string_cache", + "string_cache 0.8.9", "term", "unicode-xid", "walkdir", @@ -6114,7 +6425,7 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ - "regex-automata 0.4.9", + "regex-automata", "rustversion", ] @@ -6130,6 +6441,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "levenshtein" version = "1.0.5" @@ -6138,9 +6455,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -6174,56 +6491,10 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "libc", ] -[[package]] -name = "libsecp256k1" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" -dependencies = [ - "arrayref", - "base64 0.22.1", - "digest 0.9.0", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.8.5", - "serde", - "sha2 0.9.9", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core", -] - [[package]] name = "libusb1-sys" version = "0.7.0" @@ -6246,22 +6517,31 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "line-clipping" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "litrs" @@ -6298,25 +6578,16 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", ] [[package]] name = "lru" -version = "0.12.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.4", -] - -[[package]] -name = "lru" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" -dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.16.1", ] [[package]] @@ -6339,20 +6610,14 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "markup5ever" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" dependencies = [ "log", "tendril", @@ -6360,71 +6625,137 @@ dependencies = [ ] [[package]] -name = "match_cfg" -version = "0.1.0" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] [[package]] -name = "match_token" -version = "0.35.0" +name = "matchit" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "matchers" -version = "0.1.0" +name = "mdbook-core" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "39a3873d4afac65583f1acb56ff058df989d5b4a2464bb02c785549727d307ee" dependencies = [ - "regex-automata 0.1.10", + "anyhow", + "regex", + "serde", + "serde_json", + "toml", + "tracing", ] [[package]] -name = "matchit" -version = "0.8.4" +name = "mdbook-driver" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "a229930b29a9908560883e1f386eae25d8a971d259a80f49916a50627f04a42d" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "mdbook-core", + "mdbook-html", + "mdbook-markdown", + "mdbook-preprocessor", + "mdbook-renderer", + "mdbook-summary", + "regex", + "serde", + "serde_json", + "shlex", + "tempfile", + "toml", + "topological-sort", + "tracing", +] [[package]] -name = "mdbook" -version = "0.4.52" +name = "mdbook-html" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c284d2855916af7c5919cf9ad897cfc77d3c2db6f55429c7cfb769182030ec" +checksum = "9dee80c03c65e3212fb528b8c9be5568a6a85cf795d03cf9fd6ba39ad52069ca" dependencies = [ - "ammonia", "anyhow", - "chrono", - "clap", - "clap_complete", + "ego-tree", "elasticlunr-rs", - "env_logger", + "font-awesome-as-a-crate", "handlebars", "hex", - "log", - "memchr", - "opener", + "html5ever", + "indexmap 2.13.0", + "mdbook-core", + "mdbook-markdown", + "mdbook-renderer", "pulldown-cmark", "regex", "serde", "serde_json", "sha2 0.10.9", - "shlex", - "tempfile", - "toml 0.5.11", - "topological-sort", + "tracing", +] + +[[package]] +name = "mdbook-markdown" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c41bf35212f5d8b83e543aa6a4887dc5709c8489c5fb9ed00f1b51ce1a2cc6" +dependencies = [ + "pulldown-cmark", + "regex", + "tracing", +] + +[[package]] +name = "mdbook-preprocessor" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d87bf40be0597f26f0822f939a64f02bf92c4655ba04490aadbf83601a013bb" +dependencies = [ + "anyhow", + "mdbook-core", + "serde", + "serde_json", +] + +[[package]] +name = "mdbook-renderer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed59f225b3ae4283c56bea633db83184627a090d892908bd66990c68e10b43" +dependencies = [ + "anyhow", + "mdbook-core", + "serde", + "serde_json", +] + +[[package]] +name = "mdbook-summary" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00d85b291d67a69c92e939450390fe34d6ea418a868c8f7b42f0b300af35a7b" +dependencies = [ + "anyhow", + "mdbook-core", + "memchr", + "pulldown-cmark", + "serde", + "tracing", ] [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -6474,7 +6805,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -6531,9 +6862,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" dependencies = [ "cfg-if", "downcast", @@ -6545,16 +6876,43 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -6567,7 +6925,7 @@ version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a17d82edb1c8a6c20c238747ae7aae9181133e766bc92cd2556fdd764407d0d1" dependencies = [ - "uuid 1.17.0", + "uuid 1.21.0", ] [[package]] @@ -6598,7 +6956,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6610,7 +6968,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6653,7 +7011,7 @@ version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "fsevent-sys", "inotify", "kqueue", @@ -6673,12 +7031,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -6717,9 +7074,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-format" @@ -6816,7 +7173,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -6848,6 +7205,31 @@ dependencies = [ "smallvec", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + [[package]] name = "object" version = "0.36.7" @@ -6862,6 +7244,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -6881,11 +7267,24 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "op-alloy" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848" +dependencies = [ + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-provider", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", +] + [[package]] name = "op-alloy-consensus" -version = "0.18.14" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c88d2940558fd69f8f07b3cbd7bb3c02fc7d31159c1a7ba9deede50e7881024" +checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6894,9 +7293,9 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", - "derive_more 2.0.1", + "derive_more", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -6905,11 +7304,42 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" +[[package]] +name = "op-alloy-network" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034183dca6bff6632e7c24c92e75ff5f0eabb58144edb4d8241814851334d47" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-signer", + "op-alloy-consensus", + "op-alloy-rpc-types", +] + +[[package]] +name = "op-alloy-provider" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "async-trait", + "op-alloy-rpc-types-engine", +] + [[package]] name = "op-alloy-rpc-types" -version = "0.18.14" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22201e53e8cbb67a053e88b534b4e7f02265c5406994bf35978482a9ad0ae26" +checksum = "ddd87c6b9e5b6eee8d6b76f41b04368dca0e9f38d83338e5b00e730c282098a4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6917,21 +7347,42 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", - "derive_more 2.0.1", + "derive_more", "op-alloy-consensus", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-rpc-types-engine" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727699310a18cdeed32da3928c709e2704043b6584ed416397d5da65694efc" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-serde", + "derive_more", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive", + "op-alloy-consensus", + "serde", + "sha2 0.10.9", + "snap", + "thiserror 2.0.18", ] [[package]] name = "op-revm" -version = "8.1.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce1dc7533f4e5716c55cd3d62488c6200cb4dfda96e0c75a7e484652464343b" +checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" dependencies = [ "auto_impl", - "once_cell", "revm", "serde", ] @@ -6971,18 +7422,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "owo-colors" version = "4.2.2" @@ -6998,18 +7437,10 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", + "serdect", "sha2 0.10.9", ] -[[package]] -name = "pad" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -7035,7 +7466,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7098,29 +7529,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.104", -] - [[package]] name = "pem" version = "3.0.5" @@ -7153,7 +7561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.18", "ucd-trie", ] @@ -7177,7 +7585,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7197,7 +7605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.13.0", ] [[package]] @@ -7216,8 +7624,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", "serde", ] @@ -7227,8 +7645,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -7237,21 +7665,44 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7263,6 +7714,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "0.4.30" @@ -7300,7 +7760,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7331,17 +7791,11 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "pollster" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" - [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" @@ -7352,6 +7806,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -7399,14 +7862,23 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettydiff" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abec3fb083c10660b3854367697da94c674e9e82aa7511014dc958beeb7215e9" +checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" dependencies = [ - "owo-colors 3.5.0", - "pad", + "owo-colors", ] [[package]] @@ -7416,7 +7888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7426,6 +7898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", + "serdect", ] [[package]] @@ -7467,7 +7940,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -7479,19 +7952,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "version_check", - "yansi", -] - [[package]] name = "process-wrap" version = "8.2.1" @@ -7499,7 +7959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" dependencies = [ "futures", - "indexmap 2.10.0", + "indexmap 2.13.0", "nix 0.30.1", "tokio", "tracing", @@ -7508,19 +7968,18 @@ dependencies = [ [[package]] name = "proptest" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.1", - "lazy_static", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -7528,13 +7987,23 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", ] [[package]] @@ -7544,7 +8013,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -7557,7 +8039,16 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", ] [[package]] @@ -7566,14 +8057,14 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ - "prost", + "prost 0.13.5", ] [[package]] name = "protobuf" -version = "3.3.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" dependencies = [ "once_cell", "protobuf-support", @@ -7582,20 +8073,20 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.3.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" dependencies = [ "thiserror 1.0.69", ] [[package]] name = "pulldown-cmark" -version = "0.10.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" +checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "memchr", "pulldown-cmark-escape", "unicase", @@ -7603,9 +8094,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-escape" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] name = "quick-error" @@ -7615,17 +8106,17 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-junit" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" +checksum = "6ee9342d671fae8d66b3ae9fd7a9714dfd089c04d2a8b1ec0436ef77aee15e5f" dependencies = [ "chrono", - "indexmap 2.10.0", + "indexmap 2.13.0", "newtype-uuid", - "quick-xml 0.37.5", + "quick-xml 0.38.4", "strip-ansi-escapes", - "thiserror 2.0.12", - "uuid 1.17.0", + "thiserror 2.0.18", + "uuid 1.21.0", ] [[package]] @@ -7646,6 +8137,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -7660,7 +8160,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -7681,7 +8181,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -7703,9 +8203,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -7716,6 +8216,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -7803,32 +8309,84 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rand 0.9.2", + "rustversion", +] + [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ - "bitflags 2.9.1", - "cassowary", + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.11.0", "compact_str", - "crossterm 0.28.1", + "hashbrown 0.16.1", "indoc", - "instability", - "itertools 0.13.0", - "lru 0.12.5", - "paste", - "strum 0.26.3", + "itertools 0.14.0", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", ] +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm 0.29.0", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width 0.2.0", +] + [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -7836,9 +8394,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -7856,7 +8414,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] @@ -7867,7 +8425,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -7887,39 +8445,30 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -7928,12 +8477,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -7998,13 +8541,91 @@ dependencies = [ "webpki-roots 1.0.2", ] +[[package]] +name = "reth-codecs" +version = "1.10.0" +source = "git+https://github.com/paradigmxyz/reth?rev=b25f32a977b489f9b84254c7811a2a5a25a81369#b25f32a977b489f9b84254c7811a2a5a25a81369" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "bytes", + "modular-bitfield", + "op-alloy-consensus", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.10.0" +source = "git+https://github.com/paradigmxyz/reth?rev=b25f32a977b489f9b84254c7811a2a5a25a81369#b25f32a977b489f9b84254c7811a2a5a25a81369" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.10.0" +source = "git+https://github.com/paradigmxyz/reth?rev=b25f32a977b489f9b84254c7811a2a5a25a81369#b25f32a977b489f9b84254c7811a2a5a25a81369" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "reth-codecs", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.10.0" +source = "git+https://github.com/paradigmxyz/reth?rev=b25f32a977b489f9b84254c7811a2a5a25a81369#b25f32a977b489f9b84254c7811a2a5a25a81369" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie", + "auto_impl", + "bytes", + "derive_more", + "once_cell", + "op-alloy-consensus", + "reth-codecs", + "revm-bytecode 7.1.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "secp256k1 0.30.0", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.10.0" +source = "git+https://github.com/paradigmxyz/reth?rev=b25f32a977b489f9b84254c7811a2a5a25a81369#b25f32a977b489f9b84254c7811a2a5a25a81369" +dependencies = [ + "zstd", +] + [[package]] name = "revm" -version = "27.1.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" +checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" dependencies = [ - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-context", "revm-context-interface", "revm-database", @@ -8013,106 +8634,131 @@ dependencies = [ "revm-inspector", "revm-interpreter", "revm-precompile", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", ] [[package]] name = "revm-bytecode" -version = "6.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6922f7f4fbc15ca61ea459711ff75281cc875648c797088c34e4e064de8b8a7c" +checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" dependencies = [ "bitvec", - "once_cell", - "phf", - "revm-primitives", + "phf 0.13.1", + "revm-primitives 21.0.2", + "serde", +] + +[[package]] +name = "revm-bytecode" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +dependencies = [ + "bitvec", + "phf 0.13.1", + "revm-primitives 22.1.0", + "serde", +] + +[[package]] +name = "revm-bytecode" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86e468df3cf5cf59fa7ef71a3e9ccabb76bb336401ea2c0674f563104cf3c5e" +dependencies = [ + "bitvec", + "phf 0.13.1", + "revm-primitives 22.1.0", "serde", ] [[package]] name = "revm-context" -version = "8.0.4" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" +checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" dependencies = [ + "bitvec", "cfg-if", "derive-where", - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-context-interface", "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", ] [[package]] name = "revm-context-interface" -version = "9.0.0" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" +checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" dependencies = [ "alloy-eip2930", "alloy-eip7702", "auto_impl", "either", "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", ] [[package]] name = "revm-database" -version = "7.0.2" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61495e01f01c343dd90e5cb41f406c7081a360e3506acf1be0fc7880bfb04eb" +checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" dependencies = [ "alloy-eips", - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", ] [[package]] name = "revm-database-interface" -version = "7.0.2" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20628d6cd62961a05f981230746c16854f903762d01937f13244716530bf98f" +checksum = "8bc1da862c71ba380b0e2ee0b19b186e282f931ba4f479dfbc83903294a5e596" dependencies = [ "auto_impl", "either", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 10.0.0", "serde", + "thiserror 2.0.18", ] [[package]] name = "revm-handler" -version = "8.1.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" +checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" dependencies = [ "auto_impl", "derive-where", - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-context", "revm-context-interface", "revm-database-interface", "revm-interpreter", "revm-precompile", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", ] [[package]] name = "revm-inspector" -version = "8.1.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" +checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" dependencies = [ "auto_impl", "either", @@ -8120,17 +8766,17 @@ dependencies = [ "revm-database-interface", "revm-handler", "revm-interpreter", - "revm-primitives", - "revm-state", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", "serde_json", ] [[package]] name = "revm-inspectors" -version = "0.27.1" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad27cab355b0aa905d0744f3222e716b40ad48b32276ac4b0a615f2c3364c97" +checksum = "6e435414e9de50a1b930da602067c76365fea2fea11e80ceb50783c94ddd127f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -8143,26 +8789,27 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "revm-interpreter" -version = "24.0.0" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" +checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" dependencies = [ - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-context-interface", - "revm-primitives", + "revm-primitives 22.1.0", + "revm-state 9.0.0", "serde", ] [[package]] name = "revm-precompile" -version = "25.0.0" +version = "32.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" +checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -8175,39 +8822,87 @@ dependencies = [ "c-kzg", "cfg-if", "k256", - "libsecp256k1", + "p256", + "revm-primitives 22.1.0", + "ripemd", + "secp256k1 0.31.1", + "sha2 0.10.9", +] + +[[package]] +name = "revm-primitives" +version = "21.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" +dependencies = [ + "alloy-primitives", + "num_enum", "once_cell", - "p256", - "revm-primitives", - "ripemd", - "rug", - "secp256k1 0.31.1", - "sha2 0.10.9", + "serde", ] [[package]] name = "revm-primitives" -version = "20.1.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66145d3dc61c0d6403f27fc0d18e0363bb3b7787e67970a05c71070092896599" +checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" dependencies = [ "alloy-primitives", "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" +dependencies = [ + "bitflags 2.11.0", + "revm-bytecode 7.1.1", + "revm-primitives 21.0.2", + "serde", +] + +[[package]] +name = "revm-state" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +dependencies = [ + "alloy-eip7928", + "bitflags 2.11.0", + "revm-bytecode 8.0.0", + "revm-primitives 22.1.0", "serde", ] [[package]] name = "revm-state" -version = "7.0.2" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc830a0fd2600b91e371598e3d123480cd7bb473dd6def425a51213aa6c6d57" +checksum = "d29404707763da607e5d6e4771cb203998c28159279c2f64cc32de08d2814651" dependencies = [ - "bitflags 2.9.1", - "revm-bytecode", - "revm-primitives", + "alloy-eip7928", + "bitflags 2.11.0", + "revm-bytecode 9.0.0", + "revm-primitives 22.1.0", "serde", ] +[[package]] +name = "rexpect" +version = "0.6.2" +source = "git+https://github.com/rust-cli/rexpect?rev=2ed0b1898d7edaf6a85bedbae71a01cc578958fc#2ed0b1898d7edaf6a85bedbae71a01cc578958fc" +dependencies = [ + "comma", + "nix 0.30.1", + "regex", + "tempfile", + "thiserror 2.0.18", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -8281,28 +8976,17 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rug" -version = "1.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" -dependencies = [ - "az", - "gmp-mpfr-sys", - "libc", - "libm", -] - [[package]] name = "ruint" -version = "1.15.0" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -8316,7 +9000,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -8364,6 +9048,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -8400,7 +9093,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -8409,14 +9102,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys 0.12.1", "windows-sys 0.60.2", ] @@ -8472,9 +9165,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" @@ -8490,11 +9183,11 @@ dependencies = [ [[package]] name = "rustyline" -version = "16.0.0" +version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" +checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "clipboard-win", "fd-lock", @@ -8507,7 +9200,7 @@ dependencies = [ "unicode-segmentation", "unicode-width 0.2.0", "utf8parse", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -8572,9 +9265,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -8585,14 +9278,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -8681,8 +9374,8 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc32a777b53b3433b974c9c26b6d502a50037f8da94e46cb8ce2ced2cfdfaea0" dependencies = [ - "prost", - "prost-types", + "prost 0.13.5", + "prost-types 0.13.5", "serde", "serde_json", "zeroize", @@ -8694,7 +9387,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -8711,13 +9404,22 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser", + "semver-parser 0.10.3", ] [[package]] @@ -8729,6 +9431,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "semver-parser" version = "0.10.3" @@ -8746,22 +9454,32 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -8772,7 +9490,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -8786,15 +9504,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -8809,20 +9528,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -8847,9 +9557,9 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.2.1", "serde", "serde_derive", "serde_json", @@ -8866,7 +9576,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -8926,9 +9636,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" dependencies = [ "cc", "cfg-if", @@ -9029,7 +9739,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", ] @@ -9041,9 +9751,18 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "small_btree" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "0ba60d2df92ba73864714808ca68c059734853e6ab722b40e1cf543ebb3a057a" +dependencies = [ + "arrayvec", +] [[package]] name = "smallvec" @@ -9060,6 +9779,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "snapbox" version = "0.6.21" @@ -9108,41 +9833,52 @@ dependencies = [ [[package]] name = "solar-ast" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7f30449c304fd09db4637209dc73bde7ec203e6e5c691fc9eed26b68cd105a" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-primitives", "bumpalo", "either", - "num-bigint", "num-rational", "semver 1.0.26", "solar-data-structures", "solar-interface", "solar-macros", - "strum 0.27.2", - "typed-arena", + "strum", +] + +[[package]] +name = "solar-compiler" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" +dependencies = [ + "alloy-primitives", + "solar-ast", + "solar-config", + "solar-data-structures", + "solar-interface", + "solar-macros", + "solar-parse", + "solar-sema", ] [[package]] name = "solar-config" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643ddf85ab917f5643ec47eb79ee6db6c6158cfaf7415e39840e154eecf7176f" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "strum 0.27.2", + "colorchoice", + "strum", ] [[package]] name = "solar-data-structures" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc21b4df6061e1c825c16faf8e1f16c2341f4c46a2b2a60e03069c4453fc5ac" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.10.0", + "indexmap 2.13.0", "parking_lot", "rayon", "rustc-hash 2.1.1", @@ -9151,58 +9887,56 @@ dependencies = [ [[package]] name = "solar-interface" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2571bfb2f54c5a24688afe6876682117e96f6fd35393398dbac43e5f6f7144" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "annotate-snippets", + "annotate-snippets 0.12.5", "anstream", "anstyle", - "const-hex", - "derive_more 2.0.1", + "derive_more", "dunce", "inturn", "itertools 0.14.0", "itoa", - "match_cfg", "normalize-path", + "once_map", "rayon", "scoped-tls", + "semver 1.0.26", "serde", "serde_json", "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] [[package]] name = "solar-macros" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca57257a3b6ef16bd7995d23da3e661e89cebdae6fb9e42e4331ba0f033bd1d" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "solar-parse" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b4d9e2ddd3c7bb90079b3eb253b957ef9eb5c860ed6d6a4068884ec3a8b85" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-primitives", - "bitflags 2.9.1", + "bitflags 2.11.0", "bumpalo", "itertools 0.14.0", "memchr", "num-bigint", "num-rational", "num-traits", + "ruint", "smallvec", "solar-ast", "solar-data-structures", @@ -9212,15 +9946,14 @@ dependencies = [ [[package]] name = "solar-sema" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b017d4017ee8324e669c6e5e6fe9a282ed07eb6171e5384ddd17b8a854766f" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-json-abi", "alloy-primitives", - "bitflags 2.9.1", + "bitflags 2.11.0", "bumpalo", - "derive_more 2.0.1", + "derive_more", "either", "once_map", "paste", @@ -9232,23 +9965,22 @@ dependencies = [ "solar-interface", "solar-macros", "solar-parse", - "strum 0.27.2", + "strum", "thread_local", "tracing", - "typed-arena", ] [[package]] name = "soldeer-commands" -version = "0.6.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8ff0e7ac2832b40dafe5b80811be1be41e6cab457c53aec3adcc80d8e03d02" +checksum = "dfd18a52d398ab7defa85ea5a3f94ca335e8ae0b40c76e8f7d5cc1ea0924e356" dependencies = [ "bon", "clap", "clap-verbosity-flag", "cliclack", - "derive_more 2.0.1", + "derive_more", "email-address-parser", "env_logger", "path-slash", @@ -9259,14 +9991,14 @@ dependencies = [ [[package]] name = "soldeer-core" -version = "0.6.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92fd37a392b41211f12efbe0d5475bc7effde301dddc088998be8ada02e39941" +checksum = "295509f269318c6e3f11ea3bd09b29c974d00403bdf5291577c0bc6adaa9c2fd" dependencies = [ "bon", "chrono", "const-hex", - "derive_more 2.0.1", + "derive_more", "dunce", "home", "ignore", @@ -9280,10 +10012,10 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", - "toml_edit 0.22.27", - "uuid 1.17.0", + "toml_edit 0.23.10+spec-1.0.0", + "uuid 1.21.0", "zip", "zip-extract", ] @@ -9309,12 +10041,6 @@ dependencies = [ "der", ] -[[package]] -name = "sptr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -9341,19 +10067,30 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", + "precomputed-hash", +] + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -9373,35 +10110,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.104", + "strum_macros", ] [[package]] @@ -9413,7 +10128,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -9422,6 +10137,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "sval" version = "2.14.1" @@ -9515,7 +10236,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.12", + "thiserror 1.0.69", "url", "zip", ] @@ -9545,9 +10266,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -9556,14 +10277,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a985ff4ffd7373e10e0fb048110fb11a162e5a4c47f92ddb8787a6f766b769" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -9583,7 +10304,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -9592,7 +10313,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9607,6 +10328,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tag_ptr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05" + [[package]] name = "tap" version = "1.0.1" @@ -9615,15 +10342,68 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix 1.1.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "tempo-alloy" +version = "1.0.0" +source = "git+https://github.com/tempoxyz/tempo?tag=v1.0.0#9ad04e2058993dfba4984cc7d413474b0278d427" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "derive_more", + "serde", + "tempo-contracts", + "tempo-primitives", +] + +[[package]] +name = "tempo-contracts" +version = "1.0.0" +source = "git+https://github.com/tempoxyz/tempo?tag=v1.0.0#9ad04e2058993dfba4984cc7d413474b0278d427" +dependencies = [ + "alloy-contract", + "alloy-primitives", + "alloy-sol-types", +] + +[[package]] +name = "tempo-primitives" +version = "1.0.0" +source = "git+https://github.com/tempoxyz/tempo?tag=v1.0.0#9ad04e2058993dfba4984cc7d413474b0278d427" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "base64 0.22.1", + "derive_more", + "p256", + "reth-codecs", + "reth-ethereum-primitives", + "reth-primitives-traits", + "serde", + "serde_json", + "sha2 0.10.9", ] [[package]] @@ -9652,7 +10432,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -9664,8 +10444,8 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom", - "phf", - "phf_codegen", + "phf 0.11.3", + "phf_codegen 0.11.3", ] [[package]] @@ -9702,11 +10482,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -9717,18 +10497,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -9771,9 +10551,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -9782,43 +10562,35 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -9865,7 +10637,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -9921,35 +10693,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.8.23" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" -dependencies = [ - "indexmap 2.10.0", - "serde", - "serde_spanned 1.0.0", - "toml_datetime 0.7.0", + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -9958,19 +10709,16 @@ dependencies = [ [[package]] name = "toml_datetime" version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -9979,47 +10727,53 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", - "serde", - "serde_spanned 0.6.9", + "indexmap 2.13.0", "toml_datetime 0.6.11", - "toml_write", "winnow", ] [[package]] name = "toml_edit" -version = "0.23.2" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dee9dc43ac2aaf7d3b774e2fba5148212bf2bd9374f4e50152ebe9afd03d42" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.10.0", - "toml_datetime 0.7.0", + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", ] [[package]] -name = "toml_parser" -version = "1.0.1" +name = "toml_edit" +version = "0.24.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_parser" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow", +] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -10040,7 +10794,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project 1.1.10", - "prost", + "prost 0.13.5", "rustls-native-certs", "socket2 0.5.10", "tokio", @@ -10066,7 +10820,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.10.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -10083,7 +10837,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -10131,9 +10885,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -10143,20 +10897,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -10169,7 +10923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", ] [[package]] @@ -10194,14 +10948,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -10217,7 +10971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.22", "tracy-client", ] @@ -10245,15 +10999,15 @@ dependencies = [ [[package]] name = "trezor-client" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10636211ab89c96ed2824adc5ec0d081e1080aeacc24c37abb318dcb31dcc779" +checksum = "87873db279766278a7e56b01139943e00a45afc079fc8fa6651e949f2234c3f6" dependencies = [ "byteorder", "hex", "protobuf", "rusb", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] @@ -10278,15 +11032,43 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.12", + "thiserror 2.0.18", "utf-8", ] [[package]] -name = "typed-arena" -version = "2.0.2" +name = "turnkey_api_key_stamper" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b72664037582371dfa96bfaa2e272446ea2551e269455e9fe3166445c76736" +dependencies = [ + "base64 0.22.1", + "hex", + "k256", + "p256", + "rand_core 0.6.4", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "turnkey_client" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +checksum = "4cbf8cea094b6536ecc5cfad42cd45d2f9abf523cc7dacf7de23f132412d0ec3" +dependencies = [ + "mime", + "prost 0.12.6", + "prost-types 0.12.6", + "reqwest", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "turnkey_api_key_stamper", +] [[package]] name = "typeid" @@ -10308,11 +11090,11 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ui_test" -version = "0.30.2" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56a6897cc4bb6f8daf1939b0b39cd9645856997f46f4d0b3e3cb7122dfe9251" +checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8" dependencies = [ - "annotate-snippets", + "annotate-snippets 0.11.5", "anyhow", "bstr", "cargo-platform", @@ -10321,7 +11103,7 @@ dependencies = [ "colored", "comma", "crossbeam-channel", - "indicatif 0.17.11", + "indicatif 0.18.4", "levenshtein", "prettydiff", "regex", @@ -10406,13 +11188,13 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] @@ -10435,9 +11217,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "untrusted" @@ -10454,6 +11236,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -10504,13 +11287,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.17.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -10564,14 +11347,41 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.2" +version = "10.0.0-beta.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +checksum = "4065d92137ae477a3701e18a7afccdf584229fe504eaa4d290bc722df4076141" dependencies = [ "anyhow", - "cfg-if", + "bon", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-gitcl" +version = "10.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3911418c678932ff5b8f8162bfb73753b6f3d1e22bf37d07d53aff44a6f224" +dependencies = [ + "anyhow", + "bon", "rustversion", "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "10.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbabf186269781b2b81f38937d07c37bbd980853dea6edc89b90820c09aab4c3" +dependencies = [ + "anyhow", + "bon", + "getset", + "rustversion", ] [[package]] @@ -10638,6 +11448,24 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -10660,7 +11488,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -10695,7 +11523,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10709,6 +11537,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -10722,6 +11572,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.4", + "indexmap 2.13.0", + "semver 1.0.26", +] + [[package]] name = "wasmtimer" version = "0.4.2" @@ -10748,7 +11610,7 @@ dependencies = [ "miette", "normalize-path", "notify", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "watchexec-events", @@ -10774,7 +11636,7 @@ checksum = "377729679262964c27e6a28f360a84b7aedb172b59841301c1c77922305dfd83" dependencies = [ "miette", "nix 0.30.1", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -10813,16 +11675,32 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.3" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" dependencies = [ - "phf", - "phf_codegen", - "string_cache", + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", "string_cache_codegen", ] +[[package]] +name = "webbrowser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -10860,7 +11738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.0.8", + "rustix 1.1.4", "winsafe", ] @@ -10910,7 +11788,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -10931,7 +11809,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -10943,7 +11821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -10955,7 +11833,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -10966,7 +11844,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -10975,6 +11853,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -10982,7 +11866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -10991,7 +11875,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -11002,7 +11886,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -11011,7 +11895,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -11041,6 +11934,21 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -11063,7 +11971,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -11080,9 +11988,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -11095,6 +12009,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -11107,6 +12027,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -11131,6 +12057,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -11143,6 +12075,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -11155,6 +12093,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -11167,6 +12111,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -11181,9 +12131,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -11194,13 +12144,101 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] @@ -11211,9 +12249,9 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "ws_stream_wasm" @@ -11228,7 +12266,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror 2.0.12", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -11249,6 +12287,12 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xsum" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -11266,11 +12310,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -11278,13 +12321,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", "synstructure", ] @@ -11305,7 +12348,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -11325,7 +12368,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", "synstructure", ] @@ -11346,15 +12389,27 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -11362,13 +12417,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.117", ] [[package]] @@ -11380,7 +12435,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.10.0", + "indexmap 2.13.0", "memchr", "zopfli", ] @@ -11392,7 +12447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fa5b9958fd0b5b685af54f2c3fa21fca05fe295ebaf3e77b6d24d96c4174037" dependencies = [ "log", - "thiserror 2.0.12", + "thiserror 2.0.18", "zip", ] @@ -11402,6 +12457,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zopfli" version = "0.8.2" @@ -11413,3 +12474,31 @@ dependencies = [ "log", "simd-adler32", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] From 3053d38bced0735050459294d6ae503b55ff63a8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:54:12 +0700 Subject: [PATCH 262/391] Create static.yml (#381) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/static.yml | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/static.yml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..0ba82305f82b2 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 417dca8fcec1714baad22df0a47eba7525a04ac8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:27:13 +0000 Subject: [PATCH 263/391] Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/util.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index d899a1e764629..0a3fad720ef57 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -13,7 +13,12 @@ use std::{ /// Using a fixed directory under the system temp dir avoids trusting the current /// working directory (which may be user-controlled) as a security boundary. static TEST_UTIL_BASE: LazyLock = LazyLock::new(|| { - let mut base = env::temp_dir(); + // Resolve the system temp directory to an absolute, canonical path where possible. + // If canonicalization fails for any reason, fall back to the raw temp_dir value. + let tmp = env::temp_dir(); + let mut base = tmp + .canonicalize() + .unwrap_or(tmp); base.push("foundry_test_utils"); // Ignore errors here; they will surface when the path is actually used. let _ = fs::create_dir_all(&base); From 1c2294924ae0c1e4f408f6c488d189d8d4ce30d4 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 13 Mar 2026 03:44:29 +0700 Subject: [PATCH 264/391] Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/util.rs | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 6fdcbafb94404..ef76a4628819f 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -235,25 +235,28 @@ pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { fs::create_dir_all(dst)?; // Canonicalize the source once and treat it as the base directory for all traversal. let base = src.canonicalize()?; - copy_dir_filtered_inner(src, dst, &base, true) + // Canonicalize the destination once and treat it as the base directory for all writes. + let dst_base = dst.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, &dst_base, true) } fn copy_dir_filtered_inner( src: &Path, dst: &Path, - base: &Path, + base_src: &Path, + base_dst: &Path, is_root: bool, ) -> std::io::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); - // Ensure that any path we operate on stays within the original base directory. + // Ensure that any path we operate on stays within the original source base directory. let canonical_src_path = src_path.canonicalize()?; - if !canonical_src_path.starts_with(base) { + if !canonical_src_path.starts_with(base_src) { return Err(std::io::Error::new( std::io::ErrorKind::PermissionDenied, - "attempted to access path outside of allowed base directory", + "attempted to access path outside of allowed source base directory", )); } let dst_path = dst.join(entry.file_name()); @@ -266,10 +269,58 @@ fn copy_dir_filtered_inner( { continue; } + // Ensure that the destination directory stays within the allowed destination base. + let canonical_dst_dir = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // Directory does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_dir.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } fs::create_dir_all(&dst_path)?; - copy_dir_filtered_inner(&src_path, &dst_path, base, false)?; + copy_dir_filtered_inner(&src_path, &dst_path, base_src, base_dst, false)?; } else { - fs::copy(&canonical_src_path, &dst_path)?; + // Ensure that the destination file path stays within the allowed destination base. + let canonical_dst_path = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // File does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_path.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + fs::copy(&canonical_src_path, &canonical_dst_path)?; } } Ok(()) From 6ced0a646f6041dfb1f23bf580911bccd519a6aa Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:00:51 +0000 Subject: [PATCH 265/391] Hardhat project (#389) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot --- .circleci/ci_deploy.yml | 34 ++++++++++++++++ crates/test-utils/src/util.rs | 74 +++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 86c611c12a95a..ef76a4628819f 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -233,14 +233,32 @@ pub fn read_string(path: impl AsRef) -> String { /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + // Canonicalize the source once and treat it as the base directory for all traversal. + let base = src.canonicalize()?; + // Canonicalize the destination once and treat it as the base directory for all writes. + let dst_base = dst.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, &dst_base, true) } -fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { +fn copy_dir_filtered_inner( + src: &Path, + dst: &Path, + base_src: &Path, + base_dst: &Path, + is_root: bool, +) -> std::io::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); + // Ensure that any path we operate on stays within the original source base directory. + let canonical_src_path = src_path.canonicalize()?; + if !canonical_src_path.starts_with(base_src) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } let dst_path = dst.join(entry.file_name()); if ty.is_dir() { @@ -251,10 +269,58 @@ fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Re { continue; } + // Ensure that the destination directory stays within the allowed destination base. + let canonical_dst_dir = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // Directory does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_dir.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } fs::create_dir_all(&dst_path)?; - copy_dir_filtered_inner(&src_path, &dst_path, false)?; + copy_dir_filtered_inner(&src_path, &dst_path, base_src, base_dst, false)?; } else { - fs::copy(&src_path, &dst_path)?; + // Ensure that the destination file path stays within the allowed destination base. + let canonical_dst_path = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // File does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_path.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + fs::copy(&canonical_src_path, &canonical_dst_path)?; } } Ok(()) From da97bc160f9b6193d3acd5a38d9ccb3f74addad1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:18:16 +0000 Subject: [PATCH 266/391] foundry-rs#13763 (#398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: update EtherlinkTestnet -> EtherlinkShadownet for alloy-chains v0.2.31 (#13763) Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> * chore(evm): make `FoundryCfg` generic over `Spec` (#13757) * chore(deps): weekly `cargo update` (#13760) Updating git repository `https://github.com/alloy-rs/alloy` Updating git repository `https://github.com/alloy-rs/evm.git` Updating git repository `https://github.com/foundry-rs/optimism` Updating git submodule `https://github.com/flashbots/op-rbuilder` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/runtimeverification/kontrol-cheatcodes` Updating git submodule `https://github.com/ethereum-optimism/lib-keccak` Updating git submodule `https://github.com/dapphub/ds-test` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/a16z/erc4626-tests.git` Updating git submodule `https://github.com/safe-global/safe-contracts` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/transmissions11/solmate` Updating git submodule `https://github.com/ethereum-optimism/superchain-registry` Updating git submodule `https://github.com/flashbots/rollup-boost` Updating git repository `https://github.com/foundry-rs/foundry-fork-db` Updating git repository `https://github.com/paradigmxyz/revm-inspectors.git` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/tempoxyz/tempo-std` Updating git repository `https://github.com/stevencartavia/reth` Locking 31 packages to latest compatible versions Updating alloy-chains v0.2.30 -> v0.2.31 Updating alloy-trie v0.9.4 -> v0.9.5 Adding anstream v1.0.0 Unchanged anstream v0.6.21 (available: v1.0.0) Updating anstyle v1.0.13 -> v1.0.14 Updating anstyle-lossy v1.1.4 -> v1.1.5 Adding anstyle-parse v1.0.0 Updating bon v3.9.0 -> v3.9.1 Updating bon-macros v3.9.0 -> v3.9.1 Updating c-kzg v2.1.6 -> v2.1.7 Updating cc v1.2.56 -> v1.2.57 Updating clap v4.5.60 -> v4.6.0 Updating clap_builder v4.5.60 -> v4.6.0 Updating clap_complete v4.5.66 -> v4.6.0 Updating clap_complete_nushell v4.5.10 -> v4.6.0 Updating clap_derive v4.5.55 -> v4.6.0 Updating clap_lex v1.0.0 -> v1.1.0 Updating colorchoice v1.0.4 -> v1.0.5 Updating console v0.16.2 -> v0.16.3 Removing darling v0.21.3 Removing darling_core v0.21.3 Removing darling_macro v0.21.3 Updating derive-where v1.6.0 -> v1.6.1 Updating evmole v0.8.2 -> v0.8.4 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating kasuari v0.4.11 -> v0.4.12 Unchanged matchit v0.8.4 (available: v0.8.6) Updating once_cell v1.21.3 -> v1.21.4 Unchanged op-revm v15.0.0 (available: v17.0.0) Updating portable-atomic-util v0.2.5 -> v0.2.6 Unchanged quick-junit v0.5.2 (available: v0.6.0) Unchanged rand v0.8.5 (available: v0.10.0) Unchanged rand v0.9.2 (available: v0.10.0) Unchanged revm v34.0.0 (available: v36.0.0) Updating schannel v0.1.28 -> v0.1.29 Updating serde_with v3.17.0 -> v3.18.0 Updating serde_with_macros v3.17.0 -> v3.18.0 Unchanged snapbox v0.6.24 (available: v1.1.0) Unchanged soldeer-commands v0.10.0 (available: v0.10.1) Unchanged soldeer-core v0.10.0 (available: v0.10.1) Unchanged strum v0.27.2 (available: v0.28.0) Updating tempfile v3.26.0 -> v3.27.0 Updating tinyvec v1.10.0 -> v1.11.0 Unchanged toml v0.9.12+spec-1.1.0 (available: v1.0.6+spec-1.1.0) Unchanged toml_edit v0.24.1+spec-1.1.0 (available: v0.25.4+spec-1.1.0) Updating tracing-subscriber v0.3.22 -> v0.3.23 Updating zerocopy v0.8.41 -> v0.8.42 Updating zerocopy-derive v0.8.41 -> v0.8.42 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * feat(cheatcodes): bubble-up `Network` generic to `Wallets` (#13768) * feat(script): generic `TxStatus` receipt type (#13770) feat(script): generic `TxStatus` * chore(script): idiomatic `BroadcastReader::into_tx_receipts` (#13771) * refactor(evm): simplify `Backend::initialize` and `CowBackend::backend_mut` (#13755) `initialize` now takes `(SpecId, Address, TxKind)` instead of `&Env`, since those are the only fields it reads. This removes the need for `backend_mut` to clone `EvmEnv` — it only needs `&TxEnv` now, because the `SpecId` already comes from `self.spec_id`. Moves towards aligning with alloy-evm's BlockExecutor ownership model where `EvmEnv` is block-scoped and `TxEnv` is tx-scoped. * feat(cheatcodes): make `Cheatcodes` context-generic (#13767) * feat(cheatcodes): make `Cheatcodes` context-generic * fix: merge conflict --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): add `--network` flag to `cast tx` for network-specific raw encoding (#13745) * feat(cast): add --network flag to `cast tx` for multi-network raw encoding Replace the `FoundryNetwork`-based `transaction_raw` workaround with proper network selection via a new `--network` / `-n` CLI flag. This moves raw tx encoding back into `Cast::transaction` dispatching to the correct provider (Ethereum, Optimism, or Tempo) based on the flag. - Add `NetworkVariant` enum (Ethereum/Optimism/Tempo) in foundry-cli opts - use it w/ `--network` through CastSubcommand::Tx and provider construction - Add test for Tempo raw tx encoding (`tx_raw_tempo`) * fix: network num args * feat(cast): `block --raw` network selection (#13754) * rebase PR 13745 * feat(cast): `block` for multi-network raw encoding Co-authored-by: figtracer <1gusredo@gmail.com> * fix: doctest --------- Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make mined_receipts generic (#13761) * chore(evm): split `Executor::env` into `evm_env` and `tx_env` fields (#13773) Split the getters/setters accordingly, and update all call sites. * refactor(cheatcodes): `CheatcodesExecutor` generic (#13774) refactor(cheatcodes): make `CheatcodesExecutor` use generic env types from `ContextTr` Replace concrete `EvmEnv`/`TxEnv` in `with_fresh_nested_evm` with `EvmEnv<::Spec, CTX::Block>` and `CTX::Tx` to support non-Eth networks. Add `FoundryContextTr` trait as fully generic counterpart to the concrete `FoundryContextExt`. Remove unused `clone_to_cfg_env`/`apply_cfg_env` from `FoundryCfg`. * fix(anvil): flaky `test_trace_filter()` (#13764) fix * chore(cast): granular bounds on `Cast` (#13776) * refactor(evm): `FoundryContextExt` generic types (#13778) * fix(cheatcodes): create file in writeJson/writeToml 3-arg overload (#13777) Closes foundry-rs/foundry#13775 Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make EthApi generic over `N: Network` (#13751) * refactor(anvil): make EthApi generic over N: Network * rm todo comments --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): move `CheatsCtxExt` trait to `foundry-evm-core` (#13781) Previously `CheatsCtxExt` was defined in `foundry-cheatcodes`. Move it to `foundry-evm-core`, and rename it to `EthCheatCtx` to make clear it pins the context to Ethereum-specific env types (`BlockEnv`/`TxEnv`/`CfgEnv`) as a temporary alias during the transition to fully generic EVM and cheatcodes. * refactor(evm): make `NestedEvm` trait generic with associated types (#13782) Add `Tx`, `Block`, and `Spec` associated types to `NestedEvm` so each network can specify its own environment types. The trait remains object-safe when used as `dyn NestedEvm`. Update `NestedEvmClosure` to pin the associated types to Eth types, and set the concrete types in the `FoundryEvm` implementation. * refactor(anvil): propagate `EthApi` to all holders (#13783) refactor(anvil): propagate EthApi to all holders * refactor(evm): rename `NestedEvmClosure` and move to `foundry-evm-core` (#13785) refactor(evm): rename `NestedEvmClosure` to `EthNestedEvmClosure` and move to `foundry-evm-core` * refactor(evm): remove `Env` abstraction from `Executor` impl (#13790) * refactor(anvil): remove redundant param (#13792) * refactor(cheatcodes): tighten verbose bounds to `EthCheatCtx` (#13791) * refactor(evm): remove `eth_*_mut()` from `FoundryContextExt` (#13789) * feat(script): generic `TransactionWithMetadata` + generic pprinting `TransactionMaybeSigned` (#13795) * refactor(evm): `DatabaseExt` generic over env types (#13797) refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * test(cast): mark flaky revert_reason_from and wildcard RPC-dependent tail (#13796) * fix(anvil): reject invalid versioned_hashes in beacon blobs endpoint (#13787) * fix(cheatcodes): prevent panic in expectRevert with empty bytes (#13769) * fix(cheatcodes): prevent panic in expectRevert with empty bytes When vm.expectRevert(bytes('')) catches a revert with non-empty data, decode_error in alloy-dyn-abi panics on the empty expected_reason (slice index out of range). Guard the decode_error call with a length check. Closes #13766 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: add regression test for expectRevert empty bytes panic Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: fix snapshot for expectRevert empty bytes regression test Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): add `DB` associated type to `FoundryJournalExt` (#13799) * refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * refactor(evm): replace dyn DatabaseExt in FoundryJournalExt with associated type Replace `&mut dyn DatabaseExt` return type in `FoundryJournalExt` with an associated type `type DB: DatabaseExt`. This removes 3 uses of `dyn DatabaseExt` while remaining backwards-compatible — `&mut DB` auto-coerces to `&mut dyn DatabaseExt` at call sites that still need it. `FoundryJournalExt` is never used as a trait object itself, so adding the associated type has no object-safety impact. --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(anvil): add `AnvilBlockExecutor` and `FoundryReceiptBuilder` (#13788) feat(anvil): add AnvilBlockExecutor and FoundryReceiptBuilder * fix(anvil): swap param order in get_next_block_blob_excess_gas to match callers (#13740) * feat(script): `Network`-generic `ScriptSequence` (#13803) * fix(config): add symmetric serialization for FuzzDictionaryConfig usize fields (#13723) * chore(evm): remove `Env::new_with_spec_id()` method (#13806) * fix(install): clean up nested submodules when using --no-git (#13779) * fix(install): clean up nested submodules when using --no-git Fixes #13688 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b * test: mark regression test as flaky_ instead of ignored Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * style: fix fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): use associated types in `with_cloned_context` (#13802) refactor(evm): use associated types in `with_cloned_context` closure signature * refactor(evm): propagate env types through `FoundryJournalExt` (#13808) * refactor(evm): simplify `FoundryCfg` to marker trait (#13810) * feat(anvil): add `AnvilBlockExecutorFactory` (#13811) * feat(script): `Network`-generic `ScriptSequenceKind` (#13809) * feature(evm): owned `Tx`/`Evm` getters and `Evm` setter for `FoundryContextExt` (#13812) * chore(evm): remove `Env::{clone_evm_and_tx,apply_evm_and_tx}` methods (#13813) * chore(evm): rename `InspectorExt` to `EthInspectorExt` (#13815) * refactor(evm): add `Fork::backend()` accessor (#13817) * refactor(evm): remove `Env` from `commit_transaction` and `replay_until` (#13816) * feat(script): generic `BundledState` impl (#13825) * chore: bump alloy chains (#13827) * chore(evm): remove `Env` abstraction (#13826) * chore(evm): remove `Env` abstraction completely * fix: nit Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(macros): use correct index for tuple struct fields in ConsoleFmt (#13829) * chore(evm): fix stale `Env` references in doc comments (#13828) The combined `Env` wrapper struct was removed in #13826. Update remaining doc comments that still reference it to use the current `EvmEnv`/`TxEnv` names instead. * refactor(anvil): wire `AnvilBlockExecutorFactory` into `Backend::mine_block` (#13814) refactor(anvil): wire AnvilBlockExecutorFactory into Backend::mine_block * feat(script): generic `ScriptTransactionBuilder` (#13830) Remove hardcoded `Ethereum` default from `TransactionWithMetadata`, making `ScriptTransactionBuilder`, `simulate_and_fill`, and broadcast reader calls `Network`-generic. Cheatcode broadcast helpers now explicitly use Ethereum. * fix(`foundryup`): bump foundryup version (#13832) bump foundryup version * fix(foundryup): tempo-foundry now ships all binaries (#13834) nit * chore(deps): bump taiki-e/install-action from 2.68.17 to 2.68.35 (#13821) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump oven-sh/setup-bun from 2.1.2 to 2.2.0 (#13819) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump Swatinem/rust-cache from 2.8.2 to 2.9.1 (#13818) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.5 to 1.44.0 (#13820) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): backend env helpers generic (#13836) Update `update_env_block` and `update_current_env_with_fork_env` to use generic `FoundryBlock`/`FoundryTransaction` bounds and `BlockHeader` trait methods via setters, replacing direct field access on `AnyRpcBlock`. * refactor(anvil): rm `TransactionExecutor`, mv revm callers to `AnvilBlockExecutorFactory` (#13835) refactor(anvil): rm , mv remai callers AnvilBlockExecutorFactory * refactor(script): extract `BrowserSigner` from `MultiWallet` (#13839) * refactor(anvil): mv `Backend` methods to generic impl, thread N through NodeConfig/spawn (#13840) * refactor(anvil): extract `block_env_from_header` utility (#13838) * chore(evm): clean-up `FoundryEvm` impl (#13844) * feat(cheatcodes): add `currentFilePath` cheatcode (#13735) * feat(cheatcodes): add `currentFilePath` cheatcode Add `vm.currentFilePath()` that returns the source file path of the currently running test or script contract, relative to the project root. This enables contracts to locate sibling files (calldata JSONs, markdown descriptions, configs) without hardcoding paths. A common pattern in proposal/script repos is overriding a `dirPath()` function with a hardcoded string — this cheatcode eliminates that boilerplate. Implementation leverages `CheatsConfig::running_artifact` which already tracks the entry-point `ArtifactId`. The `source` field is stripped of the project root prefix to return a consistent relative path. * fix: rustfmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfb9f-8fce-717d-b9de-fedd8ee7d555 Co-authored-by: Amp * fix: remove view from test functions, fix forge-fmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfba7-4986-77c6-9630-574261e9d580 Co-authored-by: Amp --------- Co-authored-by: Alex Netto Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify nested Evm handling (#13846) refactor(evm): simplify nested EVM handling - Replace `to_env()` (returning cfg+block+tx tuple) with `to_evm_env()` returning only `EvmEnv` (cfg+block), since tx is not needed by callers - Remove unused `with_cloned_context` generic type parameter `R` - Drop `set_tx` call in `with_cloned_context` since nested tx modifications should not propagate back to the outer context - Remove `Evm` useless `db_mut`, `precompiles`, `precompiles_mut`, `inspector`, `inspector_mut` provideds methods reimplementations * refactor(cheatcodes): extract fork env helper to reduce duplication (#13848) Extract `fork_env_op` helper that handles the common pattern shared by all fork-switching cheatcodes: clone EVM/tx env → run db operation → write env back. Deduplicates 7 call sites (rollFork × 4, selectFork, createSelectFork × 2). * refactor(cheatcodes): `BroadcastableTransaction` network-agnostic (#13849) refactor(cheatcodes): make BroadcastableTransaction network-agnostic Remove the `Network` type parameter from `BroadcastableTransaction` by storing raw EVM data (from, to, value, input, nonce, gas) as primitive fields instead of wrapping `TransactionMaybeSigned`. This prevents the `Network` generic from leaking through `Cheatcodes`, `RawCallResult`, and `ScriptResult`. The conversion to network-specific types (`TransactionMaybeSigned`) now happens in the script layer (`simulate.rs`) at the point where transactions are handed off to `ScriptTransactionBuilder`. * feat(evm): generic `NestedEvmClosure` (#13850) refactor(evm): generalize NestedEvmClosure with generic type params Replace `EthNestedEvmClosure` (pinned to Eth types) with generic `NestedEvmClosure` to support non-Eth networks. * feat(evm): `FoundryContextExt` generic impl (#13857) * feat(evm): wire Inspector and DatabaseExt Context generics (#13856) * refactor(anvil): make `PendingTransaction` generic over tx type (#13854) * chore(cheatcodes): remove `Cheatcodes` context generic (#13861) * refactor(evm): delegate to alloy's `EthEvmFactory` in `new_evm_with_inspector` (#13860) * chore: add `id` attributes to issue templates (#13864) * chore: add `id` attributes to issue templates Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 * chore: add `id` attributes to required issue template fields only Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` (#13863) * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` * fix: failed merge * fix(traces): fix verbosity trace mode and unify verbosity handling (#13859) * fix(traces): use max instead of min for verbosity trace mode Add regression tests for TraceMode::with_verbosity to ensure verbosity 5 raises the trace mode to at least Steps rather than lowering it. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: James Niken <155266991+dizer-ti@users.noreply.github.com> * fix(traces): unify verbosity handling in TraceMode with_verbosity now raises to RecordStateDiff at verbosity 5, matching the documented behavior of -vvvvv (storage changes + backtraces). This removes the separate with_state_changes(verbosity() > 4) call in trace_mode() which used the global shell verbosity instead of the local evm_opts.verbosity — these two values can diverge when --gas-report or --flamegraph bumps evm_opts.verbosity to 3. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(forge): only show backtraces at verbosity 5 Backtraces require opcode-level step recording which is expensive. Gate backtrace display at verbosity >= 5 (-vvvvv) instead of >= 3, matching the documented behavior and the step recording threshold. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): comprehensive unit tests for TraceMode verbosity levels Tests every verbosity level (0-5) × every TraceMode variant: - verbosity 0-2: noop across all modes - verbosity 3-4: raises to Call, never downgrades - verbosity 5: raises to RecordStateDiff, never downgrades - into_config correctness at each level (record_steps, record_state_diff) - monotonicity invariant: with_verbosity never lowers any mode Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): add integration test for backtrace verbosity levels Runs the same failing test at verbosity 1, 3, 4, and 5 with snapshot assertions to verify backtraces only appear at -vvvvv. Removes the monotonicity unit test in favor of concrete integration coverage. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): keep backtraces at verbosity >= 3, add source locations at 5 Backtraces are useful even without source locations — they show contract/function names at -vvv/-vvvv. Source file locations are only added at -vvvvv when step recording is enabled. Updates integration test to assert all three behaviors: - -vvv: backtrace with function names only - -vvvv: same, plus setup traces - -vvvvv: backtrace with source file:line:col locations Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: fix rustfmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): handle compiler warnings in snapshot Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): add [staticcall] to snapshot for pure function Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * perf(traces): RecordStateDiff should not enable debug-level recording RecordStateDiff now behaves as Steps + state diff, not Debug + state diff. This avoids recording full stack snapshots (memcpy per opcode), memory snapshots, and unfiltered opcode recording at -vvvvv. Before: 50k opcodes → 50k step records with full stack copies (~16MB) After: 50k opcodes → ~500 step records (JUMP/JUMPDEST only), no copies Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): assert Debug mode config is unchanged Locks in that Debug mode still enables full memory/stack snapshots, returndata, immediate bytes, and unfiltered opcode recording — ensuring the RecordStateDiff optimization doesn't affect the debugger or cheatcode recording paths. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): state diff needs unfiltered opcodes for SLOAD/SSTORE record_state_diff captures storage changes in the step() callback, which only fires for recorded opcodes. With a JUMP/JUMPDEST filter, SSTORE steps are skipped and state diffs are lost. RecordStateDiff now disables the opcode filter (like before) but still skips memory/stack snapshots — saving the expensive per-opcode memcpy. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): update JSON fixture for RecordStateDiff config Stack and memory snapshots are no longer recorded at -vvvvv since RecordStateDiff now uses Steps-level config. These fields are null in the JSON output instead of populated. Full debugger-level data is still available via --debug. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> * refactor(anvil): make Block generic over tx type (#13865) * refactor(evm): `FoundryContextExt` bound, use generic `Spec` in `EthCheatCtx` (#13866) * refactor(evm): use `TxEnv` directly in `DatabaseExt` instead of `TransactionRequest` (#13867) * chore(deps): weekly `cargo update` (#13878) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * Update flake.lock (#13877) Co-authored-by: github-actions[bot] * fix(deps): bump to Foundry browser wallet version 0.2.0 (#13890) bump to https://github.com/foundry-rs/foundry-browser-wallet/releases/tag/v0.2.0 * revert: "BroadcastableTransaction network-agnostic" (#13849) (#13891) * perf(anvil): remove redundant clone in create_access_list (#13887) * perf(evm): make `NestedEvm::to_evm_env` consuming to avoid useless clone (#13893) Change `to_evm_env(&self)` to `to_evm_env(self)` so the implementation can use `Evm::finish()` to destructure the EVM without an extra clone of `cfg_env` and `block_env`. Update `with_fresh_nested_evm` to return `EvmEnv` after consuming the EVM post-closure, so callers no longer need to capture the env inside a `NestedEvmClosure` (which only has `&mut dyn NestedEvm` and cannot call a consuming method). Fix the `sub_inner` / `sub_evm_env` ordering in `with_nested_evm` impls so the journal is cloned before `to_evm_env` consumes the EVM. * chore(common): consistency fix on `TransactionMaybeSigned::to()` (#13895) * feat(cheatcodes): Network/Evm generic `Cheatcodes` (#13894) * feat(cheatcodes): Network/Evm generic `Cheatcodes` * fix: tx create detection * fix(ci): adapt to `TransactionMaybeSigned::to()` return type change (#13896) * chore(cheatcodes): remove unused `cheats` param from `CheatcodesExecutor::console_log` (#13897) * refactor(evm): remove `tx_env` param from EVM constructor helper (#13903) Remove the `tx_env` parameter from `new_eth_evm_with_inspector`, `with_cloned_context`, and `with_fresh_nested_evm`. Instead of setting the transaction environment at EVM construction time (where it may become stale or be set redundantly), callers now pass the tx env directly to `evm.transact()` at execution time. This avoids some clones. * chore: remove dead code `paths_config` and `log_status` from inspector stack (#13905) chore: remove dead code in InspectorStackInner Remove two unused methods: - `InspectorStack::paths_config()`: zero callers across the codebase - `InspectorStackInner::log_status()`: zero callers across the codebase Also removes the now-unused `foundry_compilers::ProjectPathsConfig` import. * chore: remove no-op `spec_id` reassignment in `Executor::clone_with_backend` (#13906) chore: remove no-op spec_id reassignment in clone_with_backend After cloning `self.evm_env`, `evm_env.cfg_env.spec` already equals `self.spec_id()` (which simply returns `self.evm_env.cfg_env.spec`). The reassignment is a no-op. * feat(evm): `Backend` generic network (#13579) * feat(evm): `Backend` generic over `Network` * fix(evm): use `AnyNetwork` for fork env init to support non-standard chains When forking non-standard EVM chains (e.g. Moonbeam, Arbitrum), their block headers may lack standard Ethereum fields like `mixHash`. Using the generic `N`-typed provider (defaulting to `Ethereum`) caused deserialization failures for these chains. * feat(evm): add `TryAnyIntoTxEnv` trait for `AnyTxEnvelope` to `TxEnv` conversion Introduces TryAnyIntoTxEnv trait in foundry-evm-core to replace the TxEnv: FromRecoveredTx bound on Backend. This allows Backend to default to AnyNetwork: - TxEnvelope (Ethereum): delegates to FromRecoveredTx directly - AnyTxEnvelope: extracts inner TxEnvelope via as_envelope(), then delegates to FromRecoveredTx; returns error for unknown tx types Co-authored-by: Amp * chore: fix `cargo deny` failure --------- Co-authored-by: Amp * refactor(evm): `NestedEvm` based on revm's `Evm` instead of alloy-evm (#13908) * Add feature: `forge inspect linearization` to see a Solidity contract's linearized inheritance (#13704) * feat: Tempo wallet access key support for cast (#13909) * refactor(script-sequence): rename misleading `opcode` field to `call_kind` (#13907) * fix(fmt): swap valid_before/valid_after in TempoTransaction pretty print (#13910) * fix(fmt): prefer header total_difficulty for totalDifficulty (#13919) * feat(evm): `FoundryContextExt` impls for OP (#13925) * refactor(evm): simplify `NestedEvm` trait by removing `Block` and `Spec` (#13933) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits (#13922) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits Restructure the inspector extension traits for clarity and genericity: - Rename FoundryInspectorExt -> InspectorExt: the context-free base trait providing Foundry-specific inspector methods (console logging, network config) without any Inspector supertrait. - Rename EthInspectorExt -> FoundryInspectorExt: the combined trait that unifies Inspector + InspectorExt. Generic over any CTX that implements FoundryContextExt, rather than being hardcoded to specific BLOCK/TX/SPEC type parameters with for<'a> HRTB baked in. - Introduce EthEvmCtx<'db> type alias for the concrete Eth context (Context), keeping usage sites concise. This makes the trait hierarchy composable for multi-network support while keeping the Eth-specific default path ergonomic via the type alias during refactor. Co-authored-by: Amp * fix: use `EthEvmContext` instead of EthEvmCtx as default --------- Co-authored-by: Amp * fix(evm): restore `code_size_limit` config in `CfgEnv` (#13912) `cfg_env()` unconditionally set `limit_contract_code_size = Some(usize::MAX)`, ignoring the user's `code_size_limit` setting from foundry.toml. Use the configured value when present, falling back to `usize::MAX` (disabled). * perf(evm): remove unnecessary clones in do_call_end/do_create_end (#13913) perf(evm): remove unnecessary clones in `do_call_end`/`do_create_end` Both methods returned `CallOutcome`/`CreateOutcome` but all callers discarded the return value — the outcome is already mutated in-place via `&mut`. This removes the final `outcome.clone()` and changes the early-return in the `#[ret]` macro from `then_some(outcome.clone())` to `then(|| ())`, eliminating up to 2 clones per call/create end. * fix(wallets): browser wallet CLI help heading formatting (#13876) fix: browser wallet CLI help heading formatting - Set proper help_heading on BrowserWalletOpts to use 'Wallet options - browser wallet' consistently - Add next_help_heading to Erc20TxOpts to prevent transaction options from leaking into the browser wallet section Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(forge-script): Tempo access key support for forge script (#13917) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` (#13932) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` Extract the repeated ~20-line drain-and-reset block from 3 locations in the block mining loop into `AnvilInspector::finish_transaction()`. The method prints traces/logs, drains the tracer into `Vec`, and reinstalls fresh tracer + log collector for the next transaction. Introduces `InspectorTxConfig` to carry the print/tracing settings, replacing direct field access into the inspector internals. * style: fix rustfmt * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification (#13924) * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification The old trait converted `TxEnvelope` (the consensus-layer type) into `TxEnv`, requiring callers to separately pass the sender address. This was a leaky abstraction: the sender is already carried by the RPC transaction wrapper (TransactionResponse::from()), so callers had to extract it themselves before calling `try_into_tx_env`. The new trait `TryAnyToTxEnv` is generic over the output TxEnv type and takes the full RPC transaction as receiver, which means: - Implementations for `alloy_rpc_types::Transaction` and `AnyRpcTransaction` can call ToTxEnv / FromRecoveredTx internally without leaking address handling to callers. - A new impl for `op_alloy_rpc_types::Transaction` produces `OpTransaction`, enabling OP-stack fork replay without a separate code path. - `Backend` now constrains `N::TransactionResponse: TryAnyToTxEnv` instead of `N::TxEnvelope: TryAnyIntoTxEnv`, which is both more accurate and unlocks multi-network support. * fix: docs --------- Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify `replay_until` to use single `ForkDB` clone (#13931) * refactor: simplify replay_until to use single ForkDB clone Replace the per-transaction Backend + EVM creation in replay_until with a single cloned ForkDB and one persistent EthEvm instance. Before: for each tx in the block, commit_transaction would clone Fork + JournaledState, spawn a new Backend (including MultiFork), create a new FoundryEvm, transact, then apply_state_changeset. In a block with N preceding transactions, this meant N full clones. After: clone the fork's CacheDB once (SharedBackend is Arc-backed, so only the local cache layer is duplicated), create one EthEvm via EthEvmFactory, call transact_commit for each tx, then replace the fork's DB and refresh journaled states once at the end. Also: - Remove unused tx_env parameter from replay_until - Extract Fork::refresh_journaled_states helper to deduplicate the paired update_state calls in both replay_until and apply_state_changeset - Remove unused NoOpInspector import Amp-Thread-ID: https://ampcode.com/threads/T-019d2512-8074-72ac-92d8-e8f887911219 Co-authored-by: Amp * style: fix rustfmt * refactor: simplify tx collection loop and remove stale comments --------- Co-authored-by: Amp Co-authored-by: zerosnacks * fix(evm): use try_any_to_tx_env in replay_until (#13938) The merge of #13931 introduced a call to the old `try_into_tx_env` method which was renamed to `try_any_to_tx_env` in #13924. This fixes the docs CI build. Amp-Thread-ID: https://ampcode.com/threads/T-019d2952-9ec2-76f9-8f90-b7b3735d4ce3 Co-authored-by: Amp * chore(deps): bump taiki-e/install-action from 2.68.35 to 2.69.8 (#13915) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.35 to 2.69.8. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d...7bc99eee1f1b8902a125006cf790a1f4c8461e63) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.69.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.17.0 to 3.17.1 (#13914) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.17.0 to 3.17.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/131015bad844610e5e6300f8a143bf625d3e74f4...a18f73c54ca8525de051e73c31512a67f44df919) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.17.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): simplify `FoundryContextExt` spec handling (#13935) - Introduced `FoundryContextExt::Spec` type for convenience - Removed complex bounds on `ContextTr::Cfg` using instead the new `Spec` type - Simplified accordingly the rest of the code Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(anvil): remove some intermediate `Env` wrappers, pass `EvmEnv` directly (#13941) refactor(anvil): remove some intermediate Env wrapper, pass EvmEnv directly Replace usage of the `Env` struct wrapper with direct `EvmEnv` references in `new_eth_evm_with_inspector`, `validate_pool_transaction_for`, and `validate_for`. The optimism flag is now passed explicitly as `is_optimism: bool` instead of being extracted from `env.networks`. This eliminates unnecessary `Env::new(...)` construction in several hot paths (block mining, pending block simulation, tx replay) and simplifies the function signatures. * refactor(anvil): remove Env from storage, call env, and executor APIs (#13942) Continue the cleanup of the Env wrapper by passing EvmEnv directly in remaining call sites: - BlockchainStorage::new / Blockchain::new: drop the redundant spec_id parameter, derive it from evm_env.spec_id() instead - build_call_env: return (EvmEnv, OpTransaction) instead of Env, consumers destructure and use directly - new_eth_evm_with_inspector_ref: accept &EvmEnv instead of &Env, derive is_optimism from self.is_optimism() - build_tx_env_for_pending: take is_optimism: bool instead of NetworkConfigs + &EvmEnv - inspect_tx / replay_block_transactions_with_inspector: extract only evm_env from next_env(), avoid cloning the whole Env - api.rs: use spec_id() / chain_id() helpers instead of digging into env.evm_env.cfg_env fields directly - Various minor cleanups: inline single-use env write guards, use self.chain_id() / self.spec_id() helpers consistently --------- Signed-off-by: dependabot[bot] Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Nikki Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Edgar Richards Co-authored-by: Red Swan Co-authored-by: onbjerg Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/BUG-FORM.yml | 2 + .github/ISSUE_TEMPLATE/FEATURE-FORM.yml | 2 + .github/workflows/ci.yml | 12 +- .github/workflows/docs.yml | 2 +- .github/workflows/nix.yml | 4 +- .github/workflows/npm.yml | 4 +- .github/workflows/test-flaky.yml | 4 +- .github/workflows/test-isolate.yml | 4 +- .github/workflows/test.yml | 6 +- Cargo.lock | 697 +- Cargo.toml | 2 + crates/anvil/core/src/eth/block.rs | 20 +- crates/anvil/core/src/eth/transaction/mod.rs | 10 +- crates/anvil/src/cmd.rs | 11 +- crates/anvil/src/config.rs | 28 +- crates/anvil/src/eth/api.rs | 172 +- crates/anvil/src/eth/backend/db.rs | 99 +- crates/anvil/src/eth/backend/executor.rs | 793 +- crates/anvil/src/eth/backend/fork.rs | 4 +- crates/anvil/src/eth/backend/info.rs | 13 +- crates/anvil/src/eth/backend/mem/inspector.rs | 44 +- crates/anvil/src/eth/backend/mem/mod.rs | 2592 +++--- crates/anvil/src/eth/backend/mem/storage.rs | 34 +- crates/anvil/src/eth/backend/validate.rs | 13 +- crates/anvil/src/eth/fees.rs | 6 +- crates/anvil/src/eth/miner.rs | 3 +- crates/anvil/src/eth/otterscan/api.rs | 3 +- crates/anvil/src/eth/pool/mod.rs | 11 +- crates/anvil/src/eth/pool/transactions.rs | 16 +- crates/anvil/src/filter.rs | 111 +- crates/anvil/src/lib.rs | 15 +- crates/anvil/src/pubsub.rs | 50 +- crates/anvil/src/server/beacon/handlers.rs | 38 +- crates/anvil/src/server/beacon/mod.rs | 3 +- crates/anvil/src/server/mod.rs | 11 +- crates/anvil/src/server/rpc_handlers.rs | 11 +- crates/anvil/src/service.rs | 60 +- crates/anvil/src/tasks/mod.rs | 5 +- crates/anvil/tests/it/fork.rs | 7 +- crates/anvil/tests/it/proof.rs | 5 +- crates/anvil/tests/it/traces.rs | 6 +- crates/cast/Cargo.toml | 1 + crates/cast/src/args.rs | 84 +- crates/cast/src/cmd/access_list.rs | 12 +- crates/cast/src/cmd/call.rs | 24 +- crates/cast/src/cmd/erc20.rs | 225 +- crates/cast/src/cmd/run.rs | 78 +- crates/cast/src/cmd/send.rs | 97 +- crates/cast/src/lib.rs | 533 +- crates/cast/src/opts.rs | 10 +- crates/cast/src/rlp_converter.rs | 31 +- crates/cast/src/tempo.rs | 21 + crates/cast/src/tx.rs | 6 +- crates/cast/tests/cli/main.rs | 50 +- crates/cheatcodes/assets/cheatcodes.json | 20 + crates/cheatcodes/spec/src/vm.rs | 5 + crates/cheatcodes/src/crypto.rs | 2 +- crates/cheatcodes/src/evm.rs | 172 +- crates/cheatcodes/src/evm/fork.rs | 136 +- crates/cheatcodes/src/fs.rs | 52 +- crates/cheatcodes/src/inspector.rs | 183 +- crates/cheatcodes/src/json.rs | 12 +- crates/cheatcodes/src/lib.rs | 6 +- crates/cheatcodes/src/script.rs | 11 +- crates/cheatcodes/src/test.rs | 18 +- crates/cheatcodes/src/test/assert.rs | 8 +- crates/cheatcodes/src/test/revert_handlers.rs | 3 +- crates/cheatcodes/src/toml.rs | 14 +- crates/cheatcodes/src/utils.rs | 4 +- crates/chisel/src/executor.rs | 6 +- crates/cli/src/opts/mod.rs | 2 + crates/cli/src/opts/network.rs | 11 + crates/cli/src/utils/cmd.rs | 2 +- crates/common/Cargo.toml | 1 + crates/common/fmt/src/ui.rs | 27 +- crates/common/src/transactions.rs | 67 +- crates/config/src/fuzz.rs | 15 +- crates/config/src/utils.rs | 15 + crates/evm/core/Cargo.toml | 3 + crates/evm/core/src/backend/cow.rs | 82 +- crates/evm/core/src/backend/mod.rs | 475 +- crates/evm/core/src/env.rs | 445 +- crates/evm/core/src/evm.rs | 256 +- crates/evm/core/src/lib.rs | 26 +- crates/evm/core/src/opts.rs | 37 +- crates/evm/core/src/utils.rs | 25 +- crates/evm/evm/Cargo.toml | 1 + crates/evm/evm/src/executors/builder.rs | 21 +- crates/evm/evm/src/executors/invariant/mod.rs | 6 +- .../evm/evm/src/executors/invariant/shrink.rs | 6 +- crates/evm/evm/src/executors/mod.rs | 235 +- crates/evm/evm/src/executors/trace.rs | 24 +- crates/evm/evm/src/inspectors/logs.rs | 5 +- crates/evm/evm/src/inspectors/stack.rs | 162 +- crates/evm/evm/src/lib.rs | 4 +- crates/evm/traces/src/lib.rs | 128 +- crates/forge/src/cmd/inspect.rs | 151 +- crates/forge/src/cmd/install.rs | 45 +- crates/forge/src/cmd/test/mod.rs | 12 +- crates/forge/src/multi_runner.rs | 18 +- crates/forge/tests/cli/backtrace.rs | 138 + crates/forge/tests/cli/cmd.rs | 93 + crates/forge/tests/cli/install.rs | 43 + crates/forge/tests/cli/script.rs | 10 +- crates/forge/tests/cli/test_cmd/repros.rs | 30 + .../fixtures/SimpleContractTestVerbose.json | 6995 +++-------------- crates/macros/src/console_fmt.rs | 5 +- crates/primitives/src/network/transaction.rs | 11 + crates/primitives/src/transaction/envelope.rs | 56 +- crates/primitives/src/transaction/request.rs | 4 +- crates/script-sequence/Cargo.toml | 1 - crates/script-sequence/src/reader.rs | 54 +- crates/script-sequence/src/sequence.rs | 71 +- crates/script-sequence/src/transaction.rs | 29 +- crates/script/Cargo.toml | 1 + crates/script/src/broadcast.rs | 148 +- crates/script/src/build.rs | 89 +- crates/script/src/execute.rs | 19 +- crates/script/src/lib.rs | 28 +- crates/script/src/multi_sequence.rs | 25 +- crates/script/src/progress.rs | 24 +- crates/script/src/receipts.rs | 47 +- crates/script/src/runner.rs | 17 +- crates/script/src/sequence.rs | 57 +- crates/script/src/simulate.rs | 35 +- crates/script/src/transaction.rs | 26 +- crates/script/src/verify.rs | 33 +- crates/verify/src/bytecode.rs | 60 +- crates/verify/src/utils.rs | 51 +- crates/wallets/Cargo.toml | 7 + crates/wallets/src/lib.rs | 2 + crates/wallets/src/opts.rs | 41 +- crates/wallets/src/tempo.rs | 196 + .../src/wallet_browser/app/assets/main.js | 114 +- .../src/wallet_browser/app/assets/styles.css | 2 +- crates/wallets/src/wallet_browser/opts.rs | 4 +- crates/wallets/src/wallet_browser/signer.rs | 4 +- crates/wallets/src/wallet_multi/mod.rs | 45 +- deny.toml | 1 + flake.lock | 18 +- foundryup/foundryup | 4 +- testdata/default/cheats/CurrentFilePath.t.sol | 21 + testdata/default/cheats/Json.t.sol | 15 + testdata/utils/Vm.sol | 1 + 144 files changed, 7882 insertions(+), 9857 deletions(-) create mode 100644 crates/cast/src/tempo.rs create mode 100644 crates/cli/src/opts/network.rs create mode 100644 crates/wallets/src/tempo.rs create mode 100644 testdata/default/cheats/CurrentFilePath.t.sol diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml index bebfe0343ee69..281cf2fd254ff 100644 --- a/.github/ISSUE_TEMPLATE/BUG-FORM.yml +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -10,6 +10,7 @@ body: Thanks for taking the time to report this bug! - type: dropdown + id: component attributes: label: Component description: What component is the bug in? @@ -52,6 +53,7 @@ body: - macOS (Apple Silicon) - Linux - type: textarea + id: description attributes: label: Describe the bug description: Please include relevant Solidity snippets as well if relevant. diff --git a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml index a07529633cc94..4268a73baa8b6 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml @@ -10,6 +10,7 @@ body: Thanks for helping us improve Foundry! - type: dropdown + id: component attributes: label: Component description: What component is the feature for? @@ -24,6 +25,7 @@ body: validations: required: true - type: textarea + id: description attributes: label: Describe the feature you would like description: Please also describe what the feature is aiming to solve, if relevant. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b12bb1b3242d4..a2ef7e83d79db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo test --workspace --doc typos: @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: crate-ci/typos@57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1 + - uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1 shellcheck: runs-on: depot-ubuntu-latest @@ -89,7 +89,7 @@ jobs: components: clippy - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo clippy --workspace --all-targets --all-features env: RUSTFLAGS: -Dwarnings @@ -123,7 +123,7 @@ jobs: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: forge fmt shell: bash run: ./.github/scripts/format.sh --check @@ -141,11 +141,11 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@edba51d32f66935a112c0516fec65a13d420cbc6 # v2 + - uses: taiki-e/install-action@7bc99eee1f1b8902a125006cf790a1f4c8461e63 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo hack check deny: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2ace7a6c6fcf1..baa407fd5573e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: toolchain: nightly - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Build documentation run: cargo doc --workspace --all-features --no-deps --document-private-items env: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index ed4bbd1246799..9b71107ca4c68 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@131015bad844610e5e6300f8a143bf625d3e74f4 # v3 + - uses: DeterminateSystems/determinate-nix-action@a18f73c54ca8525de051e73c31512a67f44df919 # v3 - uses: actions/checkout@v6 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@131015bad844610e5e6300f8a143bf625d3e74f4 # v3 + - uses: DeterminateSystems/determinate-nix-action@a18f73c54ca8525de051e73c31512a67f44df919 # v3 - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index fad46ce27bf27..766beae8cc8d7 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -137,7 +137,7 @@ jobs: run-id: ${{ github.event.workflow_run.id || inputs.run_id }} - name: Setup Bun - uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest @@ -259,7 +259,7 @@ jobs: persist-credentials: false - name: Setup Bun - uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index c64b3ef1d963e..6f992dc80c4ea 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,11 +33,11 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@edba51d32f66935a112c0516fec65a13d420cbc6 # v2 + - uses: taiki-e/install-action@7bc99eee1f1b8902a125006cf790a1f4c8461e63 # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests env: SVM_TARGET_PLATFORM: linux-amd64 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index a4aaefdbfe7df..d2526945e929a 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,11 +37,11 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@edba51d32f66935a112c0516fec65a13d420cbc6 # v2 + - uses: taiki-e/install-action@7bc99eee1f1b8902a125006cf790a1f4c8461e63 # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests with isolation env: SVM_TARGET_PLATFORM: linux-amd64 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f9c5793ab138..b99ff97bda688 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@edba51d32f66935a112c0516fec65a13d420cbc6 # v2 + - uses: taiki-e/install-action@7bc99eee1f1b8902a125006cf790a1f4c8461e63 # v2 with: tool: nextest @@ -78,7 +78,7 @@ jobs: node-version: 24 - name: Install Bun if: contains(matrix.name, 'external') - uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest - name: Setup Python @@ -102,7 +102,7 @@ jobs: restore-keys: | ${{ runner.os }}-foundry-${{ matrix.name }}- - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Setup Git config run: | git config --global user.name "GitHub Actions Bot" diff --git a/Cargo.lock b/Cargo.lock index 7e41f8ccbadbc..0cac3a43e8812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.30" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" +checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" dependencies = [ "alloy-primitives", "num_enum", @@ -155,7 +155,7 @@ dependencies = [ "proptest", "serde", "serde_json", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -298,7 +298,7 @@ dependencies = [ "derive_more", "op-alloy", "op-revm", - "revm", + "revm 34.0.0", "thiserror 2.0.18", "tracing", ] @@ -406,7 +406,7 @@ dependencies = [ "auto_impl", "op-alloy", "op-revm", - "revm", + "revm 34.0.0", "thiserror 2.0.18", ] @@ -887,7 +887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -978,13 +978,12 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", "derive_more", "nybbles", "serde", @@ -1041,7 +1040,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -1051,15 +1065,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-lossy" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d3a5dc826f84d0ea11882bb8054ff7f3d482602e11bb181101303a279ea01f" +checksum = "d9ca7d0f520afcd6d817970d0b2d5fd7c630c75e7783cae046b8b8a783c5befa" dependencies = [ "anstyle", ] @@ -1073,13 +1087,22 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1090,7 +1113,7 @@ checksum = "e22d9f3dea8bbda97c75bd0f0203e23f1e190d6d6f27a40e10063946dc4d4362" dependencies = [ "anstyle", "anstyle-lossy", - "anstyle-parse", + "anstyle-parse 0.2.7", "html-escape", "unicode-width 0.2.2", ] @@ -1103,7 +1126,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1162,7 +1185,7 @@ dependencies = [ "rand 0.8.5", "rand 0.9.2", "reqwest 0.13.2", - "revm", + "revm 34.0.0", "revm-inspectors", "serde", "serde_json", @@ -1171,7 +1194,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", "yansi", ] @@ -1193,7 +1216,7 @@ dependencies = [ "foundry-evm", "foundry-primitives", "rand 0.9.2", - "revm", + "revm 34.0.0", "serde", "serde_json", "thiserror 2.0.18", @@ -1544,9 +1567,6 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] [[package]] name = "ascii-canvas" @@ -1714,9 +1734,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "zeroize", @@ -1724,9 +1744,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" dependencies = [ "cc", "cmake", @@ -1761,9 +1781,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.103.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bfd8dfb5a562f9a605bbd5c3aef09db9231c826a1e0488ce3f4338c70dbbb" +checksum = "c41ae6a33da941457e89075ef8ca5b4870c8009fe4dceeba82fce2f30f313ac6" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1785,9 +1805,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.96.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64a6eded248c6b453966e915d32aeddb48ea63ad17932682774eb026fbef5b1" +checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1809,9 +1829,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.98.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db96d720d3c622fcbe08bae1c4b04a72ce6257d8b0584cb5418da00ae20a344f" +checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1833,9 +1853,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.100.0" +version = "1.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafbdda43b93f57f699c5dfe8328db590b967b8a820a13ccdd6687355dfcc7ca" +checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" dependencies = [ "aws-credential-types", "aws-runtime", @@ -2006,9 +2026,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b1117b3b2bbe166d11199b540ceed0d0f7676e36e7b962b5a437a9971eac75" +checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" dependencies = [ "base64-simd", "bytes", @@ -2407,9 +2427,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" dependencies = [ "bon-macros", "rustversion", @@ -2417,11 +2437,11 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.23.0", + "darling 0.20.11", "ident_case", "prettyplease", "proc-macro2", @@ -2432,19 +2452,20 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", @@ -2539,9 +2560,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.6" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -2632,11 +2653,12 @@ dependencies = [ "futures", "itertools 0.14.0", "op-alloy-flz", + "op-alloy-network", "rand 0.8.5", "rand 0.9.2", "rayon", "regex", - "revm", + "revm 34.0.0", "rpassword", "semver 1.0.27", "serde", @@ -2659,9 +2681,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -2717,7 +2739,7 @@ dependencies = [ "tempfile", "time", "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", "walkdir", "yansi", ] @@ -2775,9 +2797,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -2795,11 +2817,11 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -2810,18 +2832,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.66" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" dependencies = [ "clap", ] [[package]] name = "clap_complete_nushell" -version = "4.5.10" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685bc86fd34b7467e0532a4f8435ab107960d69a243785ef0275e571b35b641a" +checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" dependencies = [ "clap", "clap_complete", @@ -2829,9 +2851,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -2841,21 +2863,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clearscreen" -version = "4.0.5" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5def4343d62f01f67ff1a49147e4a15112e936c6a6a3f8ff7a29394e76468244" +checksum = "d669bb552908e336ad5681789752033b45566b7e591aeaac7a614e58e5d6d8f2" dependencies = [ "nix 0.31.2", "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2992,9 +3014,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" @@ -3002,7 +3024,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3087,13 +3109,12 @@ dependencies = [ [[package]] name = "console" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", "unicode-width 0.2.2", "windows-sys 0.61.2", ] @@ -3358,16 +3379,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -3392,20 +3403,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -3431,17 +3428,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" @@ -3508,9 +3494,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", @@ -3588,7 +3574,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" dependencies = [ - "console 0.16.2", + "console 0.16.3", "shell-words", "zeroize", ] @@ -3638,7 +3624,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3666,9 +3652,9 @@ dependencies = [ [[package]] name = "doctest-file" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" [[package]] name = "document-features" @@ -3892,7 +3878,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "env_filter", "jiff", @@ -3943,7 +3929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4037,9 +4023,9 @@ dependencies = [ [[package]] name = "evmole" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc83a05bd5d83b886d1aa5b725649d42172532c6f6243da9cd4a3c25fbda20f6" +checksum = "61e332670cb19161dee3f15ae86bfb5e4361bb1716d7c1995bd309b5360cb630" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -4098,7 +4084,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4263,7 +4249,7 @@ dependencies = [ "rayon", "regex", "reqwest 0.13.2", - "revm", + "revm 34.0.0", "semver 1.0.27", "serde", "serde_json", @@ -4379,6 +4365,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "tempo-alloy", "thiserror 2.0.18", "tokio", "tracing", @@ -4391,7 +4378,6 @@ version = "1.6.0" dependencies = [ "alloy-network", "alloy-primitives", - "alloy-rpc-types-eth", "eyre", "foundry-common", "foundry-compilers", @@ -4443,7 +4429,7 @@ dependencies = [ "itertools 0.14.0", "regex", "reqwest 0.13.2", - "revm", + "revm 34.0.0", "semver 1.0.27", "serde", "serde_json", @@ -4540,7 +4526,7 @@ dependencies = [ "parking_lot", "proptest", "rand 0.9.2", - "revm", + "revm 34.0.0", "revm-inspectors", "semver 1.0.27", "serde", @@ -4610,7 +4596,7 @@ dependencies = [ "tikv-jemallocator", "tokio", "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", "tracing-tracy", "yansi", ] @@ -4644,7 +4630,7 @@ dependencies = [ "alloy-transport-http", "alloy-transport-ipc", "alloy-transport-ws", - "anstream", + "anstream 0.6.21", "anstyle", "axum", "chrono", @@ -4658,13 +4644,14 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-primitives", "itertools 0.14.0", "jiff", "num-format", "path-slash", "regex", "reqwest 0.13.2", - "revm", + "revm 34.0.0", "semver 1.0.27", "serde", "serde_json", @@ -4695,7 +4682,7 @@ dependencies = [ "foundry-primitives", "op-alloy-consensus", "op-alloy-rpc-types", - "revm", + "revm 34.0.0", "serde", "serde_json", "similar-asserts", @@ -4731,7 +4718,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "tracing", - "winnow", + "winnow 0.7.15", "yansi", ] @@ -4826,7 +4813,7 @@ dependencies = [ "path-slash", "rayon", "regex", - "revm", + "revm 34.0.0", "semver 1.0.27", "serde", "serde_json", @@ -4856,7 +4843,7 @@ dependencies = [ "foundry-evm-core", "foundry-evm-traces", "ratatui", - "revm", + "revm 34.0.0", "revm-inspectors", "serde", "tracing", @@ -4869,6 +4856,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-evm", "alloy-json-abi", + "alloy-network", "alloy-primitives", "alloy-rpc-types", "alloy-sol-types", @@ -4886,7 +4874,7 @@ dependencies = [ "parking_lot", "proptest", "rayon", - "revm", + "revm 34.0.0", "revm-inspectors", "serde", "serde_json", @@ -4925,6 +4913,7 @@ dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types", + "alloy-serde 2.0.0-rc.0", "alloy-sol-types", "auto_impl", "eyre", @@ -4937,9 +4926,11 @@ dependencies = [ "foundry-test-utils", "futures", "itertools 0.14.0", + "op-alloy-consensus", + "op-alloy-rpc-types", "op-revm", "parking_lot", - "revm", + "revm 34.0.0", "revm-inspectors", "serde", "serde_json", @@ -4959,7 +4950,7 @@ dependencies = [ "foundry-compilers", "foundry-evm-core", "rayon", - "revm", + "revm 34.0.0", "semver 1.0.27", "solar-compiler", "tracing", @@ -4983,7 +4974,7 @@ dependencies = [ "parking_lot", "proptest", "rand 0.9.2", - "revm", + "revm 34.0.0", "serde", "solar-compiler", "thiserror 2.0.18", @@ -5000,7 +4991,7 @@ dependencies = [ "alloy-op-hardforks", "alloy-primitives", "clap", - "revm", + "revm 34.0.0", "serde", ] @@ -5025,7 +5016,7 @@ dependencies = [ "memchr", "rayon", "reqwest 0.13.2", - "revm", + "revm 34.0.0", "revm-inspectors", "serde", "serde_json", @@ -5050,7 +5041,7 @@ dependencies = [ "eyre", "futures", "parking_lot", - "revm", + "revm 34.0.0", "serde", "serde_json", "thiserror 2.0.18", @@ -5098,7 +5089,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-rpc-types", "op-revm", - "revm", + "revm 34.0.0", "serde", "serde_json", "tempo-alloy", @@ -5143,7 +5134,7 @@ dependencies = [ "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", "ui_test", ] @@ -5153,8 +5144,10 @@ version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", + "alloy-eips 2.0.0-rc.0", "alloy-network", "alloy-primitives", + "alloy-rlp", "alloy-signer", "alloy-signer-aws", "alloy-signer-gcp", @@ -5168,6 +5161,7 @@ dependencies = [ "axum", "clap", "derive_builder", + "dirs", "eth-keystore", "eyre", "foundry-common", @@ -5176,8 +5170,11 @@ dependencies = [ "rpassword", "serde", "serde_json", + "tempo-alloy", + "tempo-primitives", "thiserror 2.0.18", "tokio", + "toml", "tower", "tower-http", "tracing", @@ -5785,7 +5782,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -6027,7 +6024,7 @@ version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ - "console 0.16.2", + "console 0.16.3", "portable-atomic", "unicode-width 0.2.2", "unit-prefix", @@ -6090,9 +6087,9 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling 0.23.0", "indoc", @@ -6162,7 +6159,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6209,9 +6206,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" @@ -6255,9 +6252,9 @@ dependencies = [ [[package]] name = "jni" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295dc9997acda1562fdf8d299f56063c936443b60f078e63a5d8d3c34ef2642b" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ "cfg-if", "combine", @@ -6272,9 +6269,9 @@ dependencies = [ [[package]] name = "jni-macros" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3d1da60c95c98847b26b9d45f4360fee718b31de746df016d9cd6de916a7ef" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ "proc-macro2", "quote", @@ -6371,9 +6368,9 @@ dependencies = [ [[package]] name = "kasuari" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", @@ -6566,7 +6563,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", ] [[package]] @@ -7027,7 +7024,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7148,9 +7145,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -7158,9 +7155,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -7227,9 +7224,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -7363,7 +7360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" dependencies = [ "auto_impl", - "revm", + "revm 34.0.0", "serde", ] @@ -7778,9 +7775,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -7897,7 +7894,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.4+spec-1.1.0", + "toml_edit 0.25.5+spec-1.1.0", ] [[package]] @@ -8025,7 +8022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -8038,7 +8035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -8093,9 +8090,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" +checksum = "14104c5a24d9bcf7eb2c24753e0f49fe14555d8bd565ea3d38e4b4303267259d" dependencies = [ "bitflags 2.11.0", "memchr", @@ -8161,7 +8158,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -8199,9 +8196,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -8644,9 +8641,9 @@ dependencies = [ "once_cell", "op-alloy-consensus", "reth-codecs", - "revm-bytecode", + "revm-bytecode 8.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", "secp256k1 0.30.0", "serde", "thiserror 2.0.18", @@ -8666,17 +8663,36 @@ version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" dependencies = [ - "revm-bytecode", - "revm-context", - "revm-context-interface", - "revm-database", - "revm-database-interface", - "revm-handler", - "revm-inspector", - "revm-interpreter", + "revm-bytecode 8.0.0", + "revm-context 13.0.0", + "revm-context-interface 14.0.0", + "revm-database 10.0.0", + "revm-database-interface 9.0.0", + "revm-handler 15.0.0", + "revm-inspector 15.0.0", + "revm-interpreter 32.0.0", "revm-precompile", "revm-primitives", - "revm-state", + "revm-state 9.0.0", +] + +[[package]] +name = "revm" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0abc15d09cd211e9e73410ada10134069c794d4bcdb787dfc16a1bf0939849c" +dependencies = [ + "revm-bytecode 9.0.0", + "revm-context 15.0.0", + "revm-context-interface 16.0.0", + "revm-database 12.0.0", + "revm-database-interface 10.0.0", + "revm-handler 17.0.0", + "revm-inspector 17.0.0", + "revm-interpreter 34.0.0", + "revm-precompile", + "revm-primitives", + "revm-state 10.0.0", ] [[package]] @@ -8691,6 +8707,18 @@ dependencies = [ "serde", ] +[[package]] +name = "revm-bytecode" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86e468df3cf5cf59fa7ef71a3e9ccabb76bb336401ea2c0674f563104cf3c5e" +dependencies = [ + "bitvec", + "phf 0.13.1", + "revm-primitives", + "serde", +] + [[package]] name = "revm-context" version = "13.0.0" @@ -8700,11 +8728,28 @@ dependencies = [ "bitvec", "cfg-if", "derive-where", - "revm-bytecode", - "revm-context-interface", - "revm-database-interface", + "revm-bytecode 8.0.0", + "revm-context-interface 14.0.0", + "revm-database-interface 9.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", +] + +[[package]] +name = "revm-context" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1f0a76b14d684a444fc52f7bf6b7564bf882599d91ee62e76d602e7a187c7" +dependencies = [ + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode 9.0.0", + "revm-context-interface 16.0.0", + "revm-database-interface 10.0.0", + "revm-primitives", + "revm-state 10.0.0", "serde", ] @@ -8718,9 +8763,25 @@ dependencies = [ "alloy-eip7702", "auto_impl", "either", - "revm-database-interface", + "revm-database-interface 9.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc256b27743e2912ca16899568e6652a372eb5d1d573e6edb16c7836b16cf487" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface 10.0.0", + "revm-primitives", + "revm-state 10.0.0", "serde", ] @@ -8731,10 +8792,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" dependencies = [ "alloy-eips 1.7.3", - "revm-bytecode", - "revm-database-interface", + "revm-bytecode 8.0.0", + "revm-database-interface 9.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", +] + +[[package]] +name = "revm-database" +version = "12.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a7d6da41061f2c50f99a2632571026b23684b5449ff319914151f4449b6c8" +dependencies = [ + "alloy-eips 1.7.3", + "revm-bytecode 9.0.0", + "revm-database-interface 10.0.0", + "revm-primitives", + "revm-state 10.0.0", "serde", ] @@ -8747,7 +8822,21 @@ dependencies = [ "auto_impl", "either", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "revm-database-interface" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd497a38a79057b94a049552cb1f925ad15078bc1a479c132aeeebd1d2ccc768" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state 10.0.0", "serde", "thiserror 2.0.18", ] @@ -8760,14 +8849,33 @@ checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" dependencies = [ "auto_impl", "derive-where", - "revm-bytecode", - "revm-context", - "revm-context-interface", - "revm-database-interface", - "revm-interpreter", + "revm-bytecode 8.0.0", + "revm-context 13.0.0", + "revm-context-interface 14.0.0", + "revm-database-interface 9.0.0", + "revm-interpreter 32.0.0", "revm-precompile", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", +] + +[[package]] +name = "revm-handler" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1eed729ca9b228ae98688f352235871e9b8be3d568d488e4070f64c56e9d3d" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode 9.0.0", + "revm-context 15.0.0", + "revm-context-interface 16.0.0", + "revm-database-interface 10.0.0", + "revm-interpreter 34.0.0", + "revm-precompile", + "revm-primitives", + "revm-state 10.0.0", "serde", ] @@ -8779,16 +8887,33 @@ checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" dependencies = [ "auto_impl", "either", - "revm-context", - "revm-database-interface", - "revm-handler", - "revm-interpreter", + "revm-context 13.0.0", + "revm-database-interface 9.0.0", + "revm-handler 15.0.0", + "revm-interpreter 32.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", "serde", "serde_json", ] +[[package]] +name = "revm-inspector" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf5102391706513689f91cb3cb3d97b5f13a02e8647e6e9cb7620877ef84847" +dependencies = [ + "auto_impl", + "either", + "revm-context 15.0.0", + "revm-database-interface 10.0.0", + "revm-handler 17.0.0", + "revm-interpreter 34.0.0", + "revm-primitives", + "revm-state 10.0.0", + "serde", +] + [[package]] name = "revm-inspectors" version = "0.36.0" @@ -8802,7 +8927,7 @@ dependencies = [ "boa_engine", "boa_gc", "colorchoice", - "revm", + "revm 34.0.0", "serde", "serde_json", "thiserror 2.0.18", @@ -8814,10 +8939,23 @@ version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" dependencies = [ - "revm-bytecode", - "revm-context-interface", + "revm-bytecode 8.0.0", + "revm-context-interface 14.0.0", "revm-primitives", - "revm-state", + "revm-state 9.0.0", + "serde", +] + +[[package]] +name = "revm-interpreter" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf22f80612bb8f58fd1f578750281f2afadb6c93835b14ae6a4d6b75ca26f445" +dependencies = [ + "revm-bytecode 9.0.0", + "revm-context-interface 16.0.0", + "revm-primitives", + "revm-state 10.0.0", "serde", ] @@ -8865,7 +9003,20 @@ checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" dependencies = [ "alloy-eip7928", "bitflags 2.11.0", - "revm-bytecode", + "revm-bytecode 8.0.0", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-state" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29404707763da607e5d6e4771cb203998c28159279c2f64cc32de08d2814651" +dependencies = [ + "alloy-eip7928", + "bitflags 2.11.0", + "revm-bytecode 9.0.0", "revm-primitives", "serde", ] @@ -9070,7 +9221,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -9129,7 +9280,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -9140,9 +9291,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -9231,9 +9382,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -9538,9 +9689,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", @@ -9557,11 +9708,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -9796,7 +9947,7 @@ version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c1abc378119f77310836665f8523018532cf7e3faeb3b10b01da5a7321bf8e1" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "anstyle-svg", "normalize-line-endings", @@ -9813,7 +9964,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74" dependencies = [ - "anstream", + "anstream 0.6.21", ] [[package]] @@ -9833,7 +9984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9896,12 +10047,12 @@ version = "0.1.8" source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "annotate-snippets 0.12.13", - "anstream", + "anstream 0.6.21", "anstyle", "derive_more", "dunce", "inturn", - "itertools 0.14.0", + "itertools 0.12.1", "itoa", "normalize-path", "once_map", @@ -9913,7 +10064,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.18", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.2", ] @@ -9936,7 +10087,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.0", "bumpalo", - "itertools 0.14.0", + "itertools 0.12.1", "memchr", "num-bigint", "num-rational", @@ -10240,7 +10391,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.18", + "thiserror 1.0.69", "url", "zip", ] @@ -10346,21 +10497,21 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] name = "tempo-alloy" -version = "1.3.0" -source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#f609d775e8e1fe6a1c0282d97b567ac60b93b7cb" +version = "1.4.2" +source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#b364dab518df5e0ac7859faa24d52e591e5b48fa" dependencies = [ "alloy-consensus", "alloy-contract", @@ -10372,7 +10523,10 @@ dependencies = [ "alloy-serde 2.0.0-rc.0", "alloy-signer-local", "alloy-transport", + "async-trait", + "dashmap", "derive_more", + "futures", "serde", "tempo-contracts", "tempo-primitives", @@ -10381,18 +10535,19 @@ dependencies = [ [[package]] name = "tempo-contracts" -version = "1.3.0" -source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#f609d775e8e1fe6a1c0282d97b567ac60b93b7cb" +version = "1.4.2" +source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#b364dab518df5e0ac7859faa24d52e591e5b48fa" dependencies = [ "alloy-contract", "alloy-primitives", "alloy-sol-types", + "serde", ] [[package]] name = "tempo-primitives" -version = "1.3.0" -source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#f609d775e8e1fe6a1c0282d97b567ac60b93b7cb" +version = "1.4.2" +source = "git+https://github.com/tempoxyz/tempo?branch=alloy-2.0#b364dab518df5e0ac7859faa24d52e591e5b48fa" dependencies = [ "alloy-consensus", "alloy-eips 2.0.0-rc.0", @@ -10406,10 +10561,11 @@ dependencies = [ "reth-codecs", "reth-ethereum-primitives", "reth-primitives-traits", - "revm", + "revm 36.0.0", "serde", "serde_json", "sha2 0.10.9", + "tempo-contracts", ] [[package]] @@ -10429,7 +10585,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10602,9 +10758,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -10706,7 +10862,7 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -10720,9 +10876,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] @@ -10739,7 +10895,7 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -10752,35 +10908,35 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime 1.0.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tonic" @@ -10931,7 +11087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", ] [[package]] @@ -10956,9 +11112,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -10979,7 +11135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", - "tracing-subscriber 0.3.22", + "tracing-subscriber 0.3.23", "tracy-client", ] @@ -11605,7 +11761,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -11682,7 +11838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ "core-foundation 0.10.1", - "jni 0.22.3", + "jni 0.22.4", "log", "ndk-context", "objc2", @@ -11755,7 +11911,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -12125,6 +12281,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -12305,18 +12470,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.41" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e13bc581734df6250836c59a5f44f3c57db9f9acb9dc8e3eaabdaf6170254d" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.41" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3545ea9e86d12ab9bba9fcd99b54c1556fd3199007def5a03c375623d05fac1c" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a515ea6807c20..8ac9de957302a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,6 +335,7 @@ alloy-trie = "0.9" ## op-alloy op-alloy-consensus = "0.24.0" +op-alloy-network = "0.24.0" op-alloy-rpc-types = "0.24.0" op-alloy-flz = "0.13.1" @@ -501,6 +502,7 @@ alloy-evm = { git = "https://github.com/alloy-rs/evm.git", branch = "alloy-2.0" ## op-alloy / alloy-op-evm op-alloy-consensus = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } +op-alloy-network = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } op-alloy-rpc-types = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } op-alloy = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } alloy-op-evm = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 23c21b1b647d6..12f35ef94eaad 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -4,13 +4,14 @@ use alloy_consensus::{ }; use alloy_eips::eip2718::Encodable2718; use alloy_network::Network; -use foundry_primitives::FoundryNetwork; +use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; use std::fmt::Debug; use crate::eth::transaction::MaybeImpersonatedTransaction; -/// Type alias for Ethereum Block with Anvil's transaction type -pub type Block = alloy_consensus::Block; +/// Type alias for Ethereum Block with Anvil's transaction type, generic over the transaction +/// envelope with a default of [`FoundryTxEnvelope`]. +pub type Block = alloy_consensus::Block>; /// Anvil's concrete block info type. pub type BlockInfo = TypedBlockInfo; @@ -23,13 +24,18 @@ pub struct TypedBlockInfo { pub receipts: Vec, } -/// Helper function to create a new block with Header and Anvil transactions +/// Helper function to create a new block with Header and Anvil transactions, generic over the +/// transaction envelope with a default of [`FoundryTxEnvelope`]. /// /// Note: if the `impersonate-tx` feature is enabled this will also accept /// `MaybeImpersonatedTransaction`. -pub fn create_block(mut header: Header, transactions: impl IntoIterator) -> Block +pub fn create_block( + mut header: Header, + transactions: impl IntoIterator, +) -> Block where - T: Into, + Tx: Encodable2718, + T: Into>, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); let transactions_root = calculate_transaction_root(&transactions); @@ -211,7 +217,7 @@ mod tests { let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - let block = Block::decode(&mut data.as_slice()).unwrap(); + let block = ::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 02bcc749bcb82..5a355358f1146 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -20,7 +20,7 @@ use std::ops::Deref; /// This is a helper that carries the `impersonated` sender so that the right hash /// can be created. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct MaybeImpersonatedTransaction { +pub struct MaybeImpersonatedTransaction { transaction: T, impersonated_sender: Option
, } @@ -93,13 +93,13 @@ impl Encodable for MaybeImpersonatedTransaction { } } -impl From for FoundryTxEnvelope { - fn from(value: MaybeImpersonatedTransaction) -> Self { +impl From> for FoundryTxEnvelope { + fn from(value: MaybeImpersonatedTransaction) -> Self { value.transaction } } -impl From for MaybeImpersonatedTransaction { +impl From for MaybeImpersonatedTransaction { fn from(value: FoundryTxEnvelope) -> Self { Self::new(value) } @@ -127,7 +127,7 @@ impl Deref for MaybeImpersonatedTransaction { /// Queued transaction #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingTransaction { +pub struct PendingTransaction { /// The actual transaction pub transaction: MaybeImpersonatedTransaction, /// the recovered sender of this transaction diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 9dac717e1f8a2..636d6f7c0e945 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -13,6 +13,7 @@ use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; use foundry_evm_networks::NetworkConfigs; +use foundry_primitives::FoundryNetwork; use futures::FutureExt; use rand_08::{SeedableRng, rngs::StdRng}; use std::{ @@ -639,7 +640,7 @@ impl AnvilEvmArgs { /// Helper type to periodically dump the state of the chain to disk struct PeriodicStateDumper { in_progress_dump: Option + Send + Sync + 'static>>>, - api: EthApi, + api: EthApi, dump_state: Option, preserve_historical_states: bool, interval: Interval, @@ -647,7 +648,7 @@ struct PeriodicStateDumper { impl PeriodicStateDumper { fn new( - api: EthApi, + api: EthApi, dump_state: Option, interval: Duration, preserve_historical_states: bool, @@ -671,7 +672,11 @@ impl PeriodicStateDumper { } /// Infallible state dump - async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) { + async fn dump_state( + api: EthApi, + dump_state: PathBuf, + preserve_historical_states: bool, + ) { trace!(path=?dump_state, "Dumping state on shutdown"); match api.serialized_state(preserve_historical_states).await { Ok(state) => { diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 56a5e4ddcfa81..b5d6745495f9b 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -42,9 +42,12 @@ use foundry_evm::{ FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag, spec_id_from_ethereum_hardfork, }, - utils::{apply_chain_and_block_specific_env_changes, get_blob_base_fee_update_fraction}, + utils::{ + apply_chain_and_block_specific_env_changes, block_env_from_header, + get_blob_base_fee_update_fraction, + }, }; -use foundry_primitives::FoundryNetwork; +use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; use op_revm::OpTransaction; use parking_lot::RwLock; @@ -1053,7 +1056,13 @@ impl NodeConfig { /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> Result> { + pub(crate) async fn setup(&mut self) -> Result> + where + N: alloy_network::Network< + TxEnvelope = foundry_primitives::FoundryTxEnvelope, + ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope, + >, + { // configure the revm environment let mut cfg = CfgEnv::default(); @@ -1175,10 +1184,6 @@ impl NodeConfig { .wrap_err("failed to create default create2 deployer")?; } - if let Some(state) = self.init_state.clone() { - backend.load_state(state).await.wrap_err("failed to load init state")?; - } - Ok(backend) } @@ -1285,16 +1290,11 @@ latest block number: {latest_block}" self.gas_limit = Some(gas_limit); env.evm_env.block_env = BlockEnv { - number: U256::from(fork_block_number), - timestamp: U256::from(block.header.timestamp()), - difficulty: block.header.difficulty(), - // ensures prevrandao is set - prevrandao: Some(block.header.mix_hash().unwrap_or_default()), gas_limit, // Keep previous `coinbase` and `basefee` value beneficiary: env.evm_env.block_env.beneficiary, basefee: env.evm_env.block_env.basefee, - ..Default::default() + ..block_env_from_header(&block.header) }; // Determine chain_id early so we can use it consistently @@ -1451,7 +1451,7 @@ latest block number: {latest_block}" async fn derive_block_and_transactions( fork_choice: &ForkChoice, provider: &Arc, -) -> eyre::Result<(BlockNumber, Option>)> { +) -> eyre::Result<(BlockNumber, Option>>)> { match fork_choice { ForkChoice::Block(block_number) => { let block_number = *block_number; diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 1b298befb35a7..9f12c66e5c7b5 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -39,7 +39,7 @@ use alloy_eips::{ }; use alloy_evm::overrides::{OverrideBlockHashes, apply_state_overrides}; use alloy_network::{ - AnyRpcBlock, AnyRpcTransaction, BlockResponse, ReceiptResponse, TransactionBuilder, + AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, ReceiptResponse, TransactionBuilder, TransactionBuilder4844, TransactionResponse, eip2718::Decodable2718, }; use alloy_primitives::{ @@ -105,16 +105,16 @@ pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); /// The entry point for executing eth api RPC call - The Eth RPC interface. /// /// This type is cheap to clone and can be used concurrently -pub struct EthApi { +pub struct EthApi { /// The transaction pool - pool: Arc>, + pool: Arc>, /// Holds all blockchain related data /// In-Memory only for now - pub backend: Arc>, + pub backend: Arc>, /// Whether this node is mining is_mining: bool, /// available signers - signers: Arc>>>, + signers: Arc>>>, /// data required for `eth_feeHistory` fee_history_cache: FeeHistoryCache, /// max number of items kept in fee cache @@ -123,11 +123,11 @@ pub struct EthApi { /// /// This access is required in order to adjust miner settings based on requests received from /// custom RPC endpoints - miner: Miner, + miner: Miner, /// allows to enabled/disable logging logger: LoggingManager, /// Tracks all active filters - filters: Filters, + filters: Filters, /// How transactions are ordered in the pool transaction_order: Arc>, /// Whether we're listening for RPC calls @@ -136,7 +136,7 @@ pub struct EthApi { instance_id: Arc>, } -impl Clone for EthApi { +impl Clone for EthApi { fn clone(&self) -> Self { Self { pool: self.pool.clone(), @@ -155,20 +155,20 @@ impl Clone for EthApi { } } -// == impl EthApi generic methods == +// == impl EthApi generic methods == -impl EthApi { +impl EthApi { /// Creates a new instance #[expect(clippy::too_many_arguments)] pub fn new( - pool: Arc>, - backend: Arc>, - signers: Arc>>>, + pool: Arc>, + backend: Arc>, + signers: Arc>>>, fee_history_cache: FeeHistoryCache, fee_history_limit: u64, - miner: Miner, + miner: Miner, logger: LoggingManager, - filters: Filters, + filters: Filters, transactions_order: TransactionOrder, ) -> Self { Self { @@ -286,26 +286,6 @@ impl EthApi { Ok(()) } - /// Reset the fork to a fresh forked state, and optionally update the fork config. - /// - /// If `forking` is `None` then this will disable forking entirely. - /// - /// Handler for RPC call: `anvil_reset` - pub async fn anvil_reset(&self, forking: Option) -> Result<()> { - self.reset_instance_id(); - node_info!("anvil_reset"); - if let Some(forking) = forking { - // if we're resetting the fork we need to reset the instance id - self.backend.reset_fork(forking).await?; - } else { - // Reset to a fresh in-memory state - self.backend.reset_to_in_mem().await?; - } - // Clear pending transactions since they reference the old chain state. - self.pool.clear(); - Ok(()) - } - pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { node_info!("anvil_setChainId"); self.backend.set_chain_id(chain_id); @@ -401,35 +381,6 @@ impl EthApi { Ok(()) } - /// Create a buffer that represents all state on the chain, which can be loaded to separate - /// process by calling `anvil_loadState` - /// - /// Handler for RPC call: `anvil_dumpState` - pub async fn anvil_dump_state( - &self, - preserve_historical_states: Option, - ) -> Result { - node_info!("anvil_dumpState"); - self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await - } - - /// Returns the current state - pub async fn serialized_state( - &self, - preserve_historical_states: bool, - ) -> Result { - self.backend.serialized_state(preserve_historical_states).await - } - - /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or - /// storage. - /// - /// Handler for RPC call: `anvil_loadState` - pub async fn anvil_load_state(&self, buf: Bytes) -> Result { - node_info!("anvil_loadState"); - self.backend.load_state_bytes(buf).await - } - /// Retrieves the Anvil node configuration params. /// /// Handler for RPC call: `anvil_nodeInfo` @@ -439,7 +390,7 @@ impl EthApi { let env = self.backend.env().read(); let fork_config = self.backend.get_fork(); let tx_order = self.transaction_order.read(); - let hard_fork: &str = env.evm_env.cfg_env.spec.into(); + let hard_fork: &str = (*env.evm_env.spec_id()).into(); Ok(NodeInfo { current_block_number: self.backend.best_number(), @@ -643,7 +594,7 @@ impl EthApi { /// Returns the first signer that can sign for the given address #[expect(clippy::borrowed_box)] - pub fn get_signer(&self, address: Address) -> Option<&Box>> { + pub fn get_signer(&self, address: Address) -> Option<&Box>> { self.signers.iter().find(|signer| signer.is_signer_for(address)) } @@ -666,12 +617,64 @@ impl EthApi { pub fn is_impersonated(&self, addr: Address) -> bool { self.backend.cheats().is_impersonated(addr) } + + /// Returns a new accessor for certain storage elements + pub fn storage_info(&self) -> StorageInfo { + StorageInfo::new(Arc::clone(&self.backend)) + } } // == impl EthApi anvil endpoints == -impl EthApi { - // TODO: move to `impl EthApi` once `Backend::block_by_hash` is network-generic. +impl EthApi { + /// Reset the fork to a fresh forked state, and optionally update the fork config. + /// + /// If `forking` is `None` then this will disable forking entirely. + /// + /// Handler for RPC call: `anvil_reset` + pub async fn anvil_reset(&self, forking: Option) -> Result<()> { + self.reset_instance_id(); + node_info!("anvil_reset"); + if let Some(forking) = forking { + // if we're resetting the fork we need to reset the instance id + self.backend.reset_fork(forking).await?; + } else { + // Reset to a fresh in-memory state + self.backend.reset_to_in_mem().await?; + } + // Clear pending transactions since they reference the old chain state. + self.pool.clear(); + Ok(()) + } + + /// Create a buffer that represents all state on the chain, which can be loaded to separate + /// process by calling `anvil_loadState` + /// + /// Handler for RPC call: `anvil_dumpState` + pub async fn anvil_dump_state( + &self, + preserve_historical_states: Option, + ) -> Result { + node_info!("anvil_dumpState"); + self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await + } + + /// Returns the current state + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + self.backend.serialized_state(preserve_historical_states).await + } + + /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or + /// storage. + /// + /// Handler for RPC call: `anvil_loadState` + pub async fn anvil_load_state(&self, buf: Bytes) -> Result { + node_info!("anvil_loadState"); + self.backend.load_state_bytes(buf).await + } /// Revert the state of the blockchain to a previous snapshot. /// Takes a single parameter, which is the snapshot id to revert to. @@ -682,7 +685,10 @@ impl EthApi { self.backend.revert_state_snapshot(id).await } - async fn block_request(&self, block_number: Option) -> Result { + async fn block_request( + &self, + block_number: Option, + ) -> Result> { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { let pending_txs = self.pool.ready_transactions().collect(); @@ -870,11 +876,6 @@ impl EthApi { self.backend.new_block_notifications() } - /// Returns a new accessor for certain storage elements - pub fn storage_info(&self) -> StorageInfo { - StorageInfo::new(Arc::clone(&self.backend)) - } - /// Executes the [EthRequest] and returns an RPC [ResponseResult]. pub async fn execute(&self, request: EthRequest) -> ResponseResult { trace!(target: "rpc::api", "executing eth request"); @@ -1972,12 +1973,8 @@ impl EthApi { // execute again but with access list set request.access_list = Some(access_list.clone()); - let (exit, out, gas_used, _) = self.backend.call_with_state( - &state, - request.clone(), - FeeDetails::zero(), - block_env, - )?; + let (exit, out, gas_used, _) = + self.backend.call_with_state(&state, request, FeeDetails::zero(), block_env)?; ensure_return_ok(exit, &out)?; Ok(AccessListResult { @@ -2322,7 +2319,7 @@ impl EthApi { current: EthForkConfig { activation_time: 0, blob_schedule: self.backend.blob_params(), - chain_id: self.backend.env().read().evm_env.cfg_env.chain_id, + chain_id: self.backend.chain_id().to::(), fork_id: Bytes::from_static(&[0; 4]), precompiles: self.backend.precompiles(), system_contracts: self.backend.system_contracts(), @@ -2662,7 +2659,7 @@ impl EthApi { // == impl EthApi anvil endpoints == -impl EthApi { +impl EthApi { /// Send transactions impersonating specific account and contract addresses. /// /// Handler for ETH RPC call: `anvil_impersonateAccount` @@ -2921,7 +2918,8 @@ impl EthApi { // address -> cumulative nonce let mut nonces: HashMap = HashMap::default(); - let mut txs: HashMap>> = HashMap::default(); + let mut txs: HashMap>>> = + HashMap::default(); for pair in pairs { let (tx_data, block_index) = pair; @@ -3087,7 +3085,7 @@ impl EthApi { node_info!("txpool_inspect"); let mut inspect = TxpoolInspect::default(); - fn convert(tx: Arc) -> TxpoolInspectSummary { + fn convert(tx: Arc>) -> TxpoolInspectSummary { let tx = &tx.pending_transaction.transaction; let to = tx.to(); let gas_price = tx.max_fee_per_gas(); @@ -3124,7 +3122,7 @@ impl EthApi { pub async fn txpool_content(&self) -> Result> { node_info!("txpool_content"); let mut content = TxpoolContent::::default(); - fn convert(tx: Arc) -> Result { + fn convert(tx: Arc>) -> Result { let from = *tx.pending_transaction.sender(); let tx = transaction_build( Some(tx.hash()), @@ -3160,7 +3158,7 @@ impl EthApi { } } -impl EthApi { +impl EthApi { /// Executes the `evm_mine` and returns the number of blocks mined async fn do_evm_mine(&self, opts: Option) -> Result { let mut blocks_to_mine = 1u64; @@ -3421,7 +3419,7 @@ impl EthApi { /// Adds the given transaction to the pool fn add_pending_transaction( &self, - pending_transaction: PendingTransaction, + pending_transaction: PendingTransaction, requires: Vec, provides: Vec, ) -> Result { diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 14691862a225e..fdbac6c54a5eb 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -8,6 +8,7 @@ use std::{ use alloy_consensus::{BlockBody, Header}; use alloy_eips::eip4895::Withdrawals; +use alloy_evm::block::StateDB; use alloy_primitives::{ Address, B256, Bytes, U256, keccak256, map::{AddressMap, HashMap}, @@ -90,6 +91,96 @@ pub trait MaybeForkedDatabase { fn maybe_inner(&self) -> Result<&BlockchainDb, String>; } +/// `dyn Db` satisfies all `alloy_evm::Database` requirements via its supertraits, but the +/// blanket impl has an implicit `Sized` bound. Provide an explicit impl. +impl alloy_evm::Database for dyn Db {} + +impl StateDB for dyn Db { + fn set_state_clear_flag(&mut self, _has_state_clear: bool) { + // Anvil does not use the revm State wrapper, so this is a no-op. + } +} + +/// A wrapper around [`CacheDB`] that implements [`StateDB`]. +/// +/// `StateDB` is a foreign trait with an orphan-rule constraint, so we cannot +/// implement it directly for `CacheDB`. This newtype sidesteps the orphan +/// rule while delegating all [`Database`], [`DatabaseCommit`] and +/// [`DatabaseRef`] calls to the inner `CacheDB`. +#[derive(Debug)] +pub struct AnvilCacheDB(pub CacheDB); + +impl> AnvilCacheDB { + pub fn new(inner: T) -> Self { + Self(CacheDB::new(inner)) + } +} + +impl> std::ops::Deref for AnvilCacheDB { + type Target = CacheDB; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> std::ops::DerefMut for AnvilCacheDB { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl + fmt::Debug> Database for AnvilCacheDB { + type Error = DatabaseError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + self.0.basic(address) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.0.code_by_hash(code_hash) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + self.0.storage(address, index) + } + + fn block_hash(&mut self, number: u64) -> Result { + self.0.block_hash(number) + } +} + +impl> DatabaseRef for AnvilCacheDB { + type Error = DatabaseError; + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + self.0.basic_ref(address) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.0.code_by_hash_ref(code_hash) + } + + fn storage_ref(&self, address: Address, index: U256) -> Result { + self.0.storage_ref(address, index) + } + + fn block_hash_ref(&self, number: u64) -> Result { + self.0.block_hash_ref(number) + } +} + +impl + fmt::Debug> DatabaseCommit for AnvilCacheDB { + fn commit(&mut self, changes: revm::state::EvmState) { + self.0.commit(changes) + } +} + +impl + fmt::Debug> StateDB for AnvilCacheDB { + fn set_state_clear_flag(&mut self, _has_state_clear: bool) { + // Anvil does not use the revm State wrapper, so this is a no-op. + } +} + /// This bundles all required revm traits pub trait Db: DatabaseRef @@ -576,7 +667,7 @@ where #[serde(untagged)] pub enum SerializableTransactionType { TypedTransaction(FoundryTxEnvelope), - MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), + MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -608,13 +699,13 @@ impl From for Block { } } -impl From for SerializableTransactionType { - fn from(transaction: MaybeImpersonatedTransaction) -> Self { +impl From> for SerializableTransactionType { + fn from(transaction: MaybeImpersonatedTransaction) -> Self { Self::MaybeImpersonatedTransaction(transaction) } } -impl From for MaybeImpersonatedTransaction { +impl From for MaybeImpersonatedTransaction { fn from(transaction: SerializableTransactionType) -> Self { match transaction { SerializableTransactionType::TypedTransaction(tx) => Self::new(tx), diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 06dc14796cc54..d583933717567 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -1,571 +1,376 @@ -use crate::{ - PrecompileFactory, - eth::{ - backend::{ - cheats::{CheatEcrecover, CheatsManager}, - db::Db, - env::Env, - mem::op_haltreason_to_instruction_result, - validate::TransactionValidator, - }, - error::InvalidTransactionError, - pool::transactions::PoolTransaction, - }, - mem::inspector::AnvilInspector, -}; -use alloy_consensus::{ - Header, Receipt, ReceiptWithBloom, Transaction, constants::EMPTY_WITHDRAWALS, - proofs::calculate_receipt_root, transaction::Either, -}; +use crate::{eth::backend::cheats::CheatsManager, mem::inspector::AnvilInspector}; +use alloy_consensus::{Eip658Value, Transaction, TransactionEnvelope, transaction::Either}; use alloy_eips::{ Encodable2718, eip2935, eip4788, - eip7685::EMPTY_REQUESTS_HASH, eip7702::{RecoveredAuthority, RecoveredAuthorization}, - eip7840::BlobParams, }; use alloy_evm::{ - EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, - eth::EthEvmContext, - precompiles::{DynPrecompile, Precompile, PrecompilesMap}, + EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, FromTxWithEncoded, RecoveredTx, + block::{ + BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError, + ExecutableTx, OnStateHook, StateChangeSource, StateDB, TxResult, + }, + eth::{ + EthEvmContext, EthTxResult, + receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}, + }, + precompiles::PrecompilesMap, }; -use alloy_network::Network; use alloy_op_evm::OpEvmFactory; -use alloy_primitives::{B256, Bloom, BloomInput, Bytes, Log}; -use anvil_core::eth::{ - block::{TypedBlockInfo, create_block}, - transaction::{PendingTransaction, TransactionInfo}, -}; -use foundry_evm::{ - backend::DatabaseError, - core::{either_evm::EitherEvm, precompiles::EC_RECOVER}, - traces::{CallTraceDecoder, CallTraceNode}, -}; -use foundry_evm_networks::NetworkConfigs; -use foundry_primitives::{FoundryNetwork, FoundryReceiptEnvelope, FoundryTxEnvelope}; +use alloy_primitives::{Address, B256, Bytes}; +use anvil_core::eth::transaction::PendingTransaction; +use foundry_evm::{backend::DatabaseError, core::either_evm::EitherEvm}; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType}; use op_revm::{OpContext, OpTransaction}; use revm::{ - Database, Inspector, - context::{Block as RevmBlock, Cfg, TxEnv}, - context_interface::result::{EVMError, ExecutionResult, Output}, - interpreter::InstructionResult, - primitives::hardfork::SpecId, + Database, DatabaseCommit, Inspector, + context::{Block as RevmBlock, TxEnv}, + context_interface::result::ResultAndState, }; -use std::{fmt, fmt::Debug, sync::Arc}; - -/// Represents an executed transaction (transacted on the DB) -pub struct ExecutedTransaction { - transaction: Arc>, - exit_reason: InstructionResult, - out: Option, - gas_used: u64, - logs: Vec, - traces: Vec, - nonce: u64, -} - -impl fmt::Debug for ExecutedTransaction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ExecutedTransaction") - .field("exit_reason", &self.exit_reason) - .field("gas_used", &self.gas_used) - .field("nonce", &self.nonce) - .finish_non_exhaustive() - } -} - -// == impl ExecutedTransaction == - -impl ExecutedTransaction { - /// Creates the receipt for the transaction - fn create_receipt(&self, cumulative_gas_used: &mut u64) -> FoundryReceiptEnvelope { - let logs = self.logs.clone(); - *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); - - // successful return see [Return] - let status_code = u8::from(self.exit_reason.is_ok()); - let receipt_with_bloom: ReceiptWithBloom = Receipt { - status: (status_code == 1).into(), - cumulative_gas_used: *cumulative_gas_used, - logs, +use std::{fmt, fmt::Debug}; + +/// Receipt builder for Foundry/Anvil that handles all transaction types +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct FoundryReceiptBuilder; + +impl ReceiptBuilder for FoundryReceiptBuilder { + type Transaction = FoundryTxEnvelope; + type Receipt = FoundryReceiptEnvelope; + + fn build_receipt( + &self, + ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>, + ) -> FoundryReceiptEnvelope { + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), } - .into(); - - match self.transaction.pending_transaction.transaction.as_ref() { - FoundryTxEnvelope::Legacy(_) => FoundryReceiptEnvelope::Legacy(receipt_with_bloom), - FoundryTxEnvelope::Eip2930(_) => FoundryReceiptEnvelope::Eip2930(receipt_with_bloom), - FoundryTxEnvelope::Eip1559(_) => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom), - FoundryTxEnvelope::Eip4844(_) => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), - FoundryTxEnvelope::Eip7702(_) => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), - FoundryTxEnvelope::Deposit(_tx) => { - FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom { - receipt: op_alloy_consensus::OpDepositReceipt { - inner: receipt_with_bloom.receipt, - deposit_nonce: Some(0), - deposit_receipt_version: Some(1), - }, - logs_bloom: receipt_with_bloom.logs_bloom, - }) + .with_bloom(); + + match ctx.tx_type { + FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt), + FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt), + FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), + FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), + FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), + FoundryTxType::Deposit => { + unreachable!("deposit receipts are built in commit_transaction") } - // TODO(onbjerg): we should impl support for Tempo transactions - FoundryTxEnvelope::Tempo(_) => todo!(), + FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), } } } -/// Represents the outcome of mining a new block -pub struct ExecutedTransactions { - /// The block created after executing the `included` transactions - pub block: TypedBlockInfo, - /// All transactions included in the block - pub included: Vec>>, - /// All transactions that were invalid at the point of their execution and were not included in - /// the block - pub invalid: Vec>>, +/// Result of executing a transaction in [`AnvilBlockExecutor`]. +/// +/// Wraps [`EthTxResult`] with the sender address, needed for deposit nonce resolution. +#[derive(Debug)] +pub struct AnvilTxResult { + pub inner: EthTxResult, + pub sender: Address, } -impl fmt::Debug for ExecutedTransactions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ExecutedTransactions") - .field("included", &self.included.len()) - .field("invalid", &self.invalid.len()) - .finish_non_exhaustive() +impl TxResult for AnvilTxResult { + type HaltReason = H; + + fn result(&self) -> &ResultAndState { + self.inner.result() } } -/// An executor for a series of transactions -pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator, T = FoundryTxEnvelope> { - /// where to insert the transactions - pub db: &'a mut Db, - /// type used to validate before inclusion - pub validator: &'a V, - /// all pending transactions - pub pending: std::vec::IntoIter>>, - pub evm_env: EvmEnv, +/// Execution context for [`AnvilBlockExecutor`], providing block-level data +/// needed for pre/post execution system calls. +#[derive(Debug, Clone)] +pub struct AnvilExecutionCtx { + /// Parent block hash — needed for EIP-2935 system call. pub parent_hash: B256, - /// Cumulative gas used by all executed transactions - pub gas_used: u64, - /// Cumulative blob gas used by all executed transactions - pub blob_gas_used: u64, - pub enable_steps_tracing: bool, - pub networks: NetworkConfigs, - pub print_logs: bool, - pub print_traces: bool, - /// Recorder used for decoding traces, used together with print_traces - pub call_trace_decoder: Arc, - /// Precompiles to inject to the EVM. - pub precompile_factory: Option>, - pub blob_params: BlobParams, - pub cheats: CheatsManager, + /// Whether Prague hardfork is active. + pub is_prague: bool, + /// Whether Cancun hardfork is active. + pub is_cancun: bool, } -impl TransactionExecutor<'_, DB, V> { - /// Executes all transactions and puts them in a new block with the provided `timestamp` - pub fn execute(mut self) -> ExecutedTransactions { - let mut transactions = Vec::new(); - let mut transaction_infos = Vec::new(); - let mut receipts = Vec::new(); - let mut bloom = Bloom::default(); - let mut cumulative_gas_used = 0u64; - let mut invalid = Vec::new(); - let mut included = Vec::new(); - let gas_limit = self.evm_env.block_env().gas_limit; - let parent_hash = self.parent_hash; - let block_number = self.evm_env.block_env().number; - let difficulty = self.evm_env.block_env().difficulty; - let mix_hash = self.evm_env.block_env().prevrandao; - let beneficiary = self.evm_env.block_env().beneficiary; - let timestamp = self.evm_env.block_env().timestamp; - let base_fee = if self.evm_env.cfg_env().spec.is_enabled_in(SpecId::LONDON) { - Some(self.evm_env.block_env().basefee) - } else { - None - }; - - let is_shanghai = self.evm_env.cfg_env().spec >= SpecId::SHANGHAI; - let is_cancun = self.evm_env.cfg_env().spec >= SpecId::CANCUN; - let is_prague = self.evm_env.cfg_env().spec >= SpecId::PRAGUE; - let excess_blob_gas = - if is_cancun { self.evm_env.block_env().blob_excess_gas() } else { None }; - let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; - - // EIP-2935: store parent block hash in history storage contract. - if is_prague && !block_number.is_zero() { - let env = Env::new(self.evm_env.clone(), Default::default(), self.networks); - let mut inspector = AnvilInspector::default(); - let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); - // SYSTEM_ADDRESS is defined in EIP-4788 and reused by EIP-2935. - match evm.transact_system_call( - eip4788::SYSTEM_ADDRESS, - eip2935::HISTORY_STORAGE_ADDRESS, - Bytes::copy_from_slice(parent_hash.as_slice()), - ) { - Ok(result) => { - self.db.commit(result.state); - } - Err(err) => { - warn!(target: "backend", "EIP-2935 system call failed: {:?}", err); - } - } - } - - for tx in self.into_iter() { - let tx = match tx { - TransactionExecutionOutcome::Executed(tx) => { - included.push(tx.transaction.clone()); - tx - } - TransactionExecutionOutcome::BlockGasExhausted(tx) => { - trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction"); - continue; - } - TransactionExecutionOutcome::BlobGasExhausted(tx) => { - trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas_used().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction"); - continue; - } - TransactionExecutionOutcome::TransactionGasExhausted(tx) => { - trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "transaction gas limit exhausting, skipping transaction"); - continue; - } - TransactionExecutionOutcome::Invalid(tx, _) => { - trace!(target: "backend", ?tx, "skipping invalid transaction"); - invalid.push(tx); - continue; - } - TransactionExecutionOutcome::DatabaseError(_, err) => { - // Note: this is only possible in forking mode, if for example a rpc request - // failed - trace!(target: "backend", ?err, "Failed to execute transaction due to database error"); - continue; - } - }; - if is_cancun { - let tx_blob_gas = - tx.transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0); - cumulative_blob_gas_used = - Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas)); - } - let receipt = tx.create_receipt(&mut cumulative_gas_used); - - let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - build_logs_bloom(&logs, &mut bloom); - - // For contract creation transactions, compute the contract address from sender + nonce. - // This should be set even if the transaction reverted, matching geth's behavior. - let sender = *transaction.pending_transaction.sender(); - let contract_address = if transaction.pending_transaction.transaction.to().is_none() { - let addr = sender.create(tx.nonce); - trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); - Some(addr) - } else { - None - }; - - let transaction_index = transaction_infos.len() as u64; - let info = TransactionInfo { - transaction_hash: transaction.hash(), - transaction_index, - from: *transaction.pending_transaction.sender(), - to: transaction.pending_transaction.transaction.to(), - contract_address, - traces, - exit, - out: out.map(Output::into_data), - nonce: tx.nonce, - gas_used: tx.gas_used, - }; - - transaction_infos.push(info); - receipts.push(receipt); - transactions.push(transaction.pending_transaction.transaction.clone()); - } - - let receipts_root = calculate_receipt_root(&receipts); - - let header = Header { - parent_hash, - ommers_hash: Default::default(), - beneficiary, - state_root: self.db.maybe_state_root().unwrap_or_default(), - transactions_root: Default::default(), // Will be computed by create_block - receipts_root, - logs_bloom: bloom, - difficulty, - number: block_number.saturating_to(), - gas_limit, - gas_used: cumulative_gas_used, - timestamp: timestamp.saturating_to(), - extra_data: Default::default(), - mix_hash: mix_hash.unwrap_or_default(), - nonce: Default::default(), - base_fee_per_gas: base_fee, - parent_beacon_block_root: is_cancun.then_some(Default::default()), - blob_gas_used: cumulative_blob_gas_used, - excess_blob_gas, - withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), - }; +/// Block executor for Anvil that implements [`BlockExecutor`]. +/// +/// Wraps an EVM instance and produces [`FoundryReceiptEnvelope`] receipts. +/// Validation (gas limits, blob gas, transaction validity) is handled by the +/// caller before transactions are fed to this executor. +pub struct AnvilBlockExecutor { + /// The EVM instance used for execution. + evm: E, + /// Execution context. + ctx: AnvilExecutionCtx, + /// Receipt builder. + receipt_builder: FoundryReceiptBuilder, + /// Receipts of executed transactions. + receipts: Vec, + /// Total gas used by transactions in this block. + gas_used: u64, + /// Blob gas used by the block. + blob_gas_used: u64, + /// Optional state change hook. + state_hook: Option>, +} - let block = create_block(header, transactions); - let block = TypedBlockInfo { block, transactions: transaction_infos, receipts }; - ExecutedTransactions { block, included, invalid } +impl fmt::Debug for AnvilBlockExecutor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AnvilBlockExecutor") + .field("evm", &self.evm) + .field("ctx", &self.ctx) + .field("gas_used", &self.gas_used) + .field("blob_gas_used", &self.blob_gas_used) + .field("receipts", &self.receipts.len()) + .finish_non_exhaustive() } +} - fn env_for(&self, tx: &PendingTransaction) -> Env { - let mut tx_env: OpTransaction = - FromRecoveredTx::from_recovered_tx(tx.transaction.as_ref(), *tx.sender()); - - if let FoundryTxEnvelope::Eip7702(tx_7702) = tx.transaction.as_ref() - && self.cheats.has_recover_overrides() - { - // Override invalid recovered authorizations with signature overrides from cheat manager - let cheated_auths = tx_7702 - .tx() - .authorization_list - .iter() - .zip(tx_env.base.authorization_list) - .map(|(signed_auth, either_auth)| { - either_auth.right_and_then(|recovered_auth| { - if recovered_auth.authority().is_none() - && let Ok(signature) = signed_auth.signature() - && let Some(override_addr) = - self.cheats.get_recover_override(&signature.as_bytes().into()) - { - Either::Right(RecoveredAuthorization::new_unchecked( - recovered_auth.into_parts().0, - RecoveredAuthority::Valid(override_addr), - )) - } else { - Either::Right(recovered_auth) - } - }) - }) - .collect(); - tx_env.base.authorization_list = cheated_auths; - } - - if self.networks.is_optimism() { - tx_env.enveloped_tx = Some(tx.transaction.encoded_2718().into()); +impl AnvilBlockExecutor { + /// Creates a new [`AnvilBlockExecutor`]. + pub fn new(evm: E, ctx: AnvilExecutionCtx) -> Self { + Self { + evm, + ctx, + receipt_builder: FoundryReceiptBuilder, + receipts: Vec::new(), + gas_used: 0, + blob_gas_used: 0, + state_hook: None, } - - Env::new(self.evm_env.clone(), tx_env, self.networks) } } -/// Represents the result of a single transaction execution attempt -pub enum TransactionExecutionOutcome { - /// Transaction successfully executed - Executed(ExecutedTransaction), - /// Invalid transaction not executed - Invalid(Arc>, InvalidTransactionError), - /// Execution skipped because could exceed block gas limit - BlockGasExhausted(Arc>), - /// Execution skipped because it exceeded the blob gas limit - BlobGasExhausted(Arc>), - /// Execution skipped because it exceeded the transaction gas limit - TransactionGasExhausted(Arc>), - /// When an error occurred during execution - DatabaseError(Arc>, DatabaseError), -} +impl BlockExecutor for AnvilBlockExecutor +where + E: Evm< + DB: StateDB, + Tx: FromRecoveredTx + FromTxWithEncoded, + >, +{ + type Transaction = FoundryTxEnvelope; + type Receipt = FoundryReceiptEnvelope; + type Evm = E; + type Result = AnvilTxResult; -impl fmt::Debug for TransactionExecutionOutcome { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Executed(_) => write!(f, "Executed(..)"), - Self::Invalid(_, err) => write!(f, "Invalid({err:?})"), - Self::BlockGasExhausted(_) => write!(f, "BlockGasExhausted(..)"), - Self::BlobGasExhausted(_) => write!(f, "BlobGasExhausted(..)"), - Self::TransactionGasExhausted(_) => write!(f, "TransactionGasExhausted(..)"), - Self::DatabaseError(_, err) => write!(f, "DatabaseError({err:?})"), + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + // EIP-2935: store parent block hash in history storage contract. + if self.ctx.is_prague { + let result = self + .evm + .transact_system_call( + eip4788::SYSTEM_ADDRESS, + eip2935::HISTORY_STORAGE_ADDRESS, + Bytes::copy_from_slice(self.ctx.parent_hash.as_slice()), + ) + .map_err(BlockExecutionError::other)?; + + if let Some(hook) = &mut self.state_hook { + hook.on_state( + StateChangeSource::PreBlock( + alloy_evm::block::StateChangePreBlockSource::BlockHashesContract, + ), + &result.state, + ); + } + self.evm.db_mut().commit(result.state); } + Ok(()) } -} - -impl Iterator for &mut TransactionExecutor<'_, DB, V> { - type Item = TransactionExecutionOutcome; - fn next(&mut self) -> Option { - let transaction = self.pending.next()?; - let sender = *transaction.pending_transaction.sender(); - let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) { - Ok(account) => account, - Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)), - }; - let env = self.env_for(&transaction.pending_transaction); - - // check that we comply with the block's gas limit, if not disabled - let max_block_gas = self.gas_used.saturating_add(env.tx.base.gas_limit); - if !env.evm_env.cfg_env.disable_block_gas_limit - && max_block_gas > env.evm_env.block_env.gas_limit - { - return Some(TransactionExecutionOutcome::BlockGasExhausted(transaction)); + fn execute_transaction_without_commit( + &mut self, + tx: impl ExecutableTx, + ) -> Result { + let (tx_env, tx) = tx.into_parts(); + + let block_available_gas = self.evm.block().gas_limit() - self.gas_used; + if tx.tx().gas_limit() > block_available_gas { + return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: tx.tx().gas_limit(), + block_available_gas, + } + .into()); } - // check that we comply with the transaction's gas limit as imposed by Osaka (EIP-7825) - if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() - && transaction.pending_transaction.transaction.gas_limit() - > env.evm_env.cfg_env().tx_gas_limit_cap() - { - return Some(TransactionExecutionOutcome::TransactionGasExhausted(transaction)); - } + let sender = *tx.signer(); + + let result = self.evm.transact(tx_env).map_err(|err| { + let hash = tx.tx().trie_hash(); + BlockExecutionError::evm(err, hash) + })?; + + Ok(AnvilTxResult { + inner: EthTxResult { + result, + blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(), + tx_type: tx.tx().tx_type(), + }, + sender, + }) + } - // check that we comply with the block's blob gas limit - let max_blob_gas = self.blob_gas_used.saturating_add( - transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0), - ); - if max_blob_gas > self.blob_params.max_blob_gas_per_block() { - return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)); - } + fn commit_transaction(&mut self, output: Self::Result) -> Result { + let AnvilTxResult { + inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, + sender, + } = output; - // validate before executing - if let Err(err) = self.validator.validate_pool_transaction_for( - &transaction.pending_transaction, - &account, - &env, - ) { - warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err); - return Some(TransactionExecutionOutcome::Invalid(transaction, err)); + if let Some(hook) = &mut self.state_hook { + hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); } - let nonce = account.nonce; + let gas_used = result.gas_used(); + self.gas_used += gas_used; - let mut inspector = AnvilInspector::default().with_tracing(); - if self.enable_steps_tracing { - inspector = inspector.with_steps_tracing(); - } - if self.print_logs { - inspector = inspector.with_log_collector(); + if self.ctx.is_cancun { + self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); } - if self.print_traces { - inspector = inspector.with_trace_printer(); - } - - let exec_result = { - let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); - self.networks.inject_precompiles(evm.precompiles_mut()); - if let Some(factory) = &self.precompile_factory { - evm.precompiles_mut().extend_precompiles(factory.precompiles()); - } - - let cheats = Arc::new(self.cheats.clone()); - if cheats.has_recover_overrides() { - let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats)); - evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { - Some(DynPrecompile::new_stateful( - cheat_ecrecover.precompile_id().clone(), - move |input| cheat_ecrecover.call(input), - )) - }); - } - - trace!(target: "backend", "[{:?}] executing", transaction.hash()); - // transact and commit the transaction - match evm.transact_commit(env.tx) { - Ok(exec_result) => exec_result, - Err(err) => { - warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); - match err { - EVMError::Database(err) => { - return Some(TransactionExecutionOutcome::DatabaseError( - transaction, - err, - )); - } - EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid( - transaction, - err.into(), - )); - } - // This will correspond to prevrandao not set, and it should never happen. - // If it does, it's a bug. - e => panic!("failed to execute transaction: {e}"), - } - } + let receipt = if tx_type == FoundryTxType::Deposit { + let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), + cumulative_gas_used: self.gas_used, + logs: result.into_logs(), } + .with_bloom(); + FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom { + receipt: op_alloy_consensus::OpDepositReceipt { + inner: receipt.receipt, + deposit_nonce, + deposit_receipt_version: deposit_nonce.map(|_| 1), + }, + logs_bloom: receipt.logs_bloom, + }) + } else { + self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }) }; - if self.print_traces { - inspector.print_traces(self.call_trace_decoder.clone()); - } - inspector.print_logs(); - - let (exit_reason, gas_used, out, logs) = match exec_result { - ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (reason.into(), gas_used, Some(output), Some(logs)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) - } - ExecutionResult::Halt { reason, gas_used } => { - (op_haltreason_to_instruction_result(reason), gas_used, None, None) - } - }; + self.receipts.push(receipt); + self.evm.db_mut().commit(state); - if exit_reason == InstructionResult::OutOfGas { - // this currently useful for debugging estimations - warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash()) - } + Ok(gas_used) + } - trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); + fn finish( + self, + ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> + { + Ok(( + self.evm, + BlockExecutionResult { + receipts: self.receipts, + requests: Default::default(), + gas_used: self.gas_used, + blob_gas_used: self.blob_gas_used, + }, + )) + } - // Track the total gas used for total gas per block checks - self.gas_used = self.gas_used.saturating_add(gas_used); + fn set_state_hook(&mut self, hook: Option>) { + self.state_hook = hook; + } - // Track the total blob gas used for total blob gas per blob checks - if let Some(blob_gas) = transaction.pending_transaction.transaction.blob_gas_used() { - self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas); - } + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } - trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used); + fn evm(&self) -> &Self::Evm { + &self.evm + } - let tx = ExecutedTransaction { - transaction, - exit_reason, - out, - gas_used, - logs: logs.unwrap_or_default(), - traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(), - nonce, - }; + fn receipts(&self) -> &[FoundryReceiptEnvelope] { + &self.receipts + } +} - Some(TransactionExecutionOutcome::Executed(tx)) +pub struct AnvilBlockExecutorFactory; + +impl AnvilBlockExecutorFactory { + pub fn create_executor( + evm: EitherEvm, + ctx: AnvilExecutionCtx, + ) -> AnvilBlockExecutor> + where + DB: StateDB, + { + AnvilBlockExecutor::new(evm, ctx) } } -/// Inserts all logs into the bloom -fn build_logs_bloom(logs: &[Log], bloom: &mut Bloom) { - for log in logs { - bloom.accrue(BloomInput::Raw(&log.address[..])); - for topic in log.topics() { - bloom.accrue(BloomInput::Raw(&topic[..])); - } +/// Builds the per-tx `OpTransaction` from a pending transaction, replicating the logic +/// from `TransactionExecutor::env_for`. +pub fn build_tx_env_for_pending( + tx: &PendingTransaction, + cheats: &CheatsManager, + is_optimism: bool, +) -> OpTransaction { + let mut tx_env: OpTransaction = + FromRecoveredTx::from_recovered_tx(tx.transaction.as_ref(), *tx.sender()); + + if let FoundryTxEnvelope::Eip7702(tx_7702) = tx.transaction.as_ref() + && cheats.has_recover_overrides() + { + let cheated_auths = tx_7702 + .tx() + .authorization_list + .iter() + .zip(tx_env.base.authorization_list) + .map(|(signed_auth, either_auth)| { + either_auth.right_and_then(|recovered_auth| { + if recovered_auth.authority().is_none() + && let Ok(signature) = signed_auth.signature() + && let Some(override_addr) = + cheats.get_recover_override(&signature.as_bytes().into()) + { + Either::Right(RecoveredAuthorization::new_unchecked( + recovered_auth.into_parts().0, + RecoveredAuthority::Valid(override_addr), + )) + } else { + Either::Right(recovered_auth) + } + }) + }) + .collect(); + tx_env.base.authorization_list = cheated_auths; } + + if is_optimism { + tx_env.enveloped_tx = Some(tx.transaction.encoded_2718().into()); + } + + tx_env } /// Creates a database with given database and inspector. -pub fn new_evm_with_inspector( +pub fn new_eth_evm_with_inspector( db: DB, - env: &Env, + evm_env: &EvmEnv, inspector: I, + is_optimism: bool, ) -> EitherEvm where DB: Database + Debug, I: Inspector> + Inspector>, { - if env.networks.is_optimism() { + if is_optimism { let evm_env = EvmEnv::new( - env.evm_env - .cfg_env - .clone() - .with_spec_and_mainnet_gas_params(op_revm::OpSpecId::ISTHMUS), - env.evm_env.block_env.clone(), + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(op_revm::OpSpecId::ISTHMUS), + evm_env.block_env.clone(), ); EitherEvm::Op(OpEvmFactory::default().create_evm_with_inspector(db, evm_env, inspector)) } else { EitherEvm::Eth(EthEvmFactory::default().create_evm_with_inspector( db, - env.evm_env.clone(), + evm_env.clone(), inspector, )) } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index b6f5345aa850d..423173471db65 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -26,7 +26,7 @@ use alloy_rpc_types::{ }; use alloy_transport::TransportError; use foundry_common::provider::{ProviderBuilder, RetryProvider}; -use foundry_primitives::FoundryTxReceipt; +use foundry_primitives::{FoundryTxEnvelope, FoundryTxReceipt}; use parking_lot::{ RawRwLock, RwLock, lock_api::{RwLockReadGuard, RwLockWriteGuard}, @@ -664,7 +664,7 @@ pub struct ClientForkConfig { /// total difficulty of the chain until this block pub total_difficulty: U256, /// Transactions to force include in the forked chain - pub force_transactions: Option>, + pub force_transactions: Option>>, } impl ClientForkConfig { diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index 72acc5e62a39e..220cfe93dddea 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -1,10 +1,10 @@ //! Handler that can get current storage related data use crate::mem::Backend; +use alloy_consensus::TxReceipt; use alloy_network::{AnyRpcBlock, Network}; use alloy_primitives::B256; use anvil_core::eth::block::Block; -use foundry_primitives::{FoundryNetwork, FoundryReceiptEnvelope}; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. @@ -39,16 +39,17 @@ impl StorageInfo { } } -impl StorageInfo { - // TODO: receipts methods require N::ReceiptEnvelope generalization - +impl StorageInfo +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ /// Returns the receipts of the current block - pub fn current_receipts(&self) -> Option> { + pub fn current_receipts(&self) -> Option> { self.backend.mined_receipts(self.backend.best_hash()) } /// Returns the receipts of the block with the given hash - pub fn receipts(&self, hash: B256) -> Option> { + pub fn receipts(&self, hash: B256) -> Option> { self.backend.mined_receipts(hash) } } diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index e50f2115f80c8..bb63e74e912e0 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -7,7 +7,8 @@ use foundry_evm::{ decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, traces::{ - CallTraceDecoder, SparsedTraceArena, TracingInspectorConfig, render_trace_arena_inner, + CallTraceDecoder, CallTraceNode, SparsedTraceArena, TracingInspectorConfig, + render_trace_arena_inner, }, }; use revm::{ @@ -33,7 +34,48 @@ pub struct AnvilInspector { pub transfer: Option, } +/// Configuration for per-transaction inspector lifecycle. +#[derive(Clone, Debug)] +pub struct InspectorTxConfig { + /// Whether to print traces to stdout. + pub print_traces: bool, + /// Whether to print logs to stdout. + pub print_logs: bool, + /// Whether to enable step-level tracing (with state diffs). + pub enable_steps_tracing: bool, + /// Decoder for populating trace labels. + pub call_trace_decoder: Arc, +} + impl AnvilInspector { + /// Finish a transaction: print traces/logs, drain the tracer, and reset for the next tx. + /// + /// Returns the collected call trace nodes from the finished transaction. + pub fn finish_transaction(&mut self, config: &InspectorTxConfig) -> Vec { + // Print before draining so the tracer is still populated. + if config.print_traces { + self.print_traces(config.call_trace_decoder.clone()); + } + self.print_logs(); + + let traces = self.tracer.take().map(|t| t.into_traces().into_nodes()).unwrap_or_default(); + + // Reinstall tracer for next tx. + let tracing_config = if config.enable_steps_tracing { + TracingInspectorConfig::all().with_state_diffs() + } else { + TracingInspectorConfig::all().set_steps(false) + }; + self.tracer = Some(TracingInspector::new(tracing_config)); + + // Reset log collector for next tx. + if config.print_logs { + self.log_collector = Some(LogCollector::Capture { logs: Vec::new() }); + } + + traces + } + /// Called after the inspecting the evm /// /// This will log all `console.sol` logs diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index d0cbce6fe3ecd..d542bcf17db19 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,15 +1,15 @@ //! In-memory blockchain backend. use self::state::trie_storage; -use super::executor::new_evm_with_inspector; +use super::executor::new_eth_evm_with_inspector; use crate::{ ForkChoice, NodeConfig, PrecompileFactory, config::PruneStateHistoryConfig, eth::{ backend::{ cheats::{CheatEcrecover, CheatsManager}, - db::{Db, MaybeFullDatabase, SerializableState, StateDb}, + db::{AnvilCacheDB, Db, MaybeFullDatabase, SerializableState, StateDb}, env::Env, - executor::{ExecutedTransactions, TransactionExecutor}, + executor::{AnvilBlockExecutorFactory, AnvilExecutionCtx, build_tx_env_for_pending}, fork::ClientFork, genesis::GenesisConfig, mem::{ @@ -27,7 +27,7 @@ use crate::{ sign::build_impersonated, }, mem::{ - inspector::AnvilInspector, + inspector::{AnvilInspector, InspectorTxConfig}, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, }; @@ -35,15 +35,17 @@ use alloy_chains::NamedChain; use alloy_consensus::{ Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait, TrieAccount, TxEnvelope, TxReceipt, Typed2718, + constants::EMPTY_WITHDRAWALS, proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, }; use alloy_eips::{ - BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash, eip7840::BlobParams, - eip7910::SystemContract, + BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash, + eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, eip7910::SystemContract, }; use alloy_evm::{ - Database, Evm, FromRecoveredTx, + Database, Evm, EvmEnv, FromRecoveredTx, + block::BlockExecutor, eth::EthEvmContext, overrides::{OverrideBlockHashes, apply_state_overrides}, precompiles::{DynPrecompile, Precompile, PrecompilesMap}, @@ -53,7 +55,7 @@ use alloy_network::{ ReceiptResponse, TransactionBuilder, UnknownTxEnvelope, UnknownTypedTransaction, }; use alloy_primitives::{ - Address, B256, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, + Address, B256, Bloom, BloomInput, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, map::{AddressMap, HashMap, HashSet}, }; use alloy_rpc_types::{ @@ -77,7 +79,7 @@ use alloy_rpc_types::{ use alloy_serde::{OtherFields, WithOtherFields}; use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer}; use anvil_core::eth::{ - block::{Block, BlockInfo}, + block::{Block, BlockInfo, TypedBlockInfo, create_block}, transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo}, }; use anvil_rpc::error::RpcError; @@ -94,7 +96,10 @@ use foundry_evm::{ CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector, TracingInspectorConfig, }, - utils::{get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id}, + utils::{ + block_env_from_header, get_blob_base_fee_update_fraction, + get_blob_base_fee_update_fraction_by_spec_id, + }, }; use foundry_primitives::{ FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, @@ -105,7 +110,7 @@ use op_alloy_consensus::DEPOSIT_TX_TYPE_ID; use op_revm::{OpContext, OpHaltReason, OpTransaction}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ - DatabaseCommit, Inspector, + Database as RevmDatabase, DatabaseCommit, Inspector, context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, @@ -151,7 +156,7 @@ pub const MIN_CREATE_GAS: u128 = 53000; pub type State = foundry_evm::utils::StateChangeset; /// A block request, which includes the Pool Transactions if it's Pending -pub enum BlockRequest { +pub enum BlockRequest { Pending(Vec>>), Number(u64), } @@ -281,8 +286,7 @@ impl Backend { return true; } // Ensure EIP-3607 is disabled - let mut env = self.env.write(); - env.evm_env.cfg_env.disable_eip3607 = true; + self.env.write().evm_env.cfg_env.disable_eip3607 = true; self.cheats.impersonate(addr) } @@ -368,8 +372,7 @@ impl Backend { /// Sets the block number pub fn set_block_number(&self, number: u64) { - let mut env = self.env.write(); - env.evm_env.block_env.number = U256::from(number); + self.env.write().evm_env.block_env.number = U256::from(number); } /// Returns the client coinbase address. @@ -433,7 +436,7 @@ impl Backend { /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().evm_env.cfg_env.spec + *self.env.read().evm_env.spec_id() } /// Returns true for post London @@ -468,7 +471,7 @@ impl Backend { /// Returns the precompiles for the current spec. pub fn precompiles(&self) -> BTreeMap { - let spec_id = self.env.read().evm_env.cfg_env.spec; + let spec_id = self.spec_id(); let precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)); let mut precompiles_map = BTreeMap::::default(); @@ -492,7 +495,7 @@ impl Backend { pub fn system_contracts(&self) -> BTreeMap { let mut system_contracts = BTreeMap::::default(); - let spec_id = self.env.read().evm_env.cfg_env.spec; + let spec_id = self.spec_id(); if spec_id >= SpecId::CANCUN { system_contracts.extend(SystemContract::cancun()); @@ -507,7 +510,7 @@ impl Backend { /// Returns [`BlobParams`] corresponding to the current spec. pub fn blob_params(&self) -> BlobParams { - let spec_id = self.env.read().evm_env.cfg_env.spec; + let spec_id = self.spec_id(); if spec_id >= SpecId::OSAKA { return BlobParams::osaka(); @@ -889,7 +892,7 @@ impl Backend { let mut block = WithOtherFields::new(block); // If Arbitrum, apply chain specifics to converted block. - if is_arbitrum(self.env.read().evm_env.cfg_env.chain_id) { + if is_arbitrum(self.chain_id().to::()) { // Set `l1BlockNumber` field. block.other.insert("l1BlockNumber".to_string(), number.into()); } @@ -1000,10 +1003,10 @@ impl Backend { } /// Creates an EVM instance with optionally injected precompiles. - fn new_evm_with_inspector_ref<'db, I, DB>( + fn new_eth_evm_with_inspector_ref<'db, I, DB>( &self, db: &'db DB, - env: &Env, + evm_env: &EvmEnv, inspector: &'db mut I, ) -> EitherEvm, &'db mut I, PrecompilesMap> where @@ -1012,8 +1015,9 @@ impl Backend { + Inspector>>, WrapDatabaseRef<&'db DB>: Database, { - let mut evm = new_evm_with_inspector(WrapDatabaseRef(db), env, inspector); - self.env.read().networks.inject_precompiles(evm.precompiles_mut()); + let mut evm = + new_eth_evm_with_inspector(WrapDatabaseRef(db), evm_env, inspector, self.is_optimism()); + self.env.write().networks.inject_precompiles(evm.precompiles_mut()); if let Some(factory) = &self.precompile_factory { evm.precompiles_mut().extend_precompiles(factory.precompiles()); @@ -1046,7 +1050,7 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Env { + ) -> (EvmEnv, OpTransaction) { let tx_type = request.minimal_tx_type() as u8; let WithOtherFields:: { @@ -1076,22 +1080,22 @@ impl Backend { } = fee_details; let gas_limit = gas.unwrap_or(block_env.gas_limit); - let mut env = self.env.read().clone(); - env.evm_env.block_env = block_env; + let mut evm_env = self.env.read().evm_env.clone(); + evm_env.block_env = block_env; // we want to disable this in eth_call, since this is common practice used by other node // impls and providers - env.evm_env.cfg_env.disable_block_gas_limit = true; - env.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + evm_env.cfg_env.disable_block_gas_limit = true; + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); // The basefee should be ignored for calls against state for // - eth_call // - eth_estimateGas // - eth_createAccessList // - tracing - env.evm_env.cfg_env.disable_base_fee = true; + evm_env.cfg_env.disable_base_fee = true; // Disable nonce check in revm - env.evm_env.cfg_env.disable_nonce_check = true; + evm_env.cfg_env.disable_nonce_check = true; let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| { self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE) @@ -1107,7 +1111,7 @@ impl Backend { max_fee_per_blob_gas: max_fee_per_blob_gas .or_else(|| { if !blob_hashes.is_empty() { - env.evm_env.block_env.blob_gasprice() + evm_env.block_env.blob_gasprice() } else { Some(0) } @@ -1120,30 +1124,30 @@ impl Backend { tx_type, value: value.unwrap_or_default(), data: input.into_input().unwrap_or_default(), - chain_id: Some(chain_id.unwrap_or(self.env.read().evm_env.cfg_env.chain_id)), + chain_id: Some(chain_id.unwrap_or(self.chain_id().to::())), access_list: access_list.unwrap_or_default(), blob_hashes, ..Default::default() }; base.set_signed_authorization(authorization_list.unwrap_or_default()); - env.tx = OpTransaction { base, ..Default::default() }; + let mut tx_env = OpTransaction { base, ..Default::default() }; if let Some(nonce) = nonce { - env.tx.base.nonce = nonce; + tx_env.base.nonce = nonce; } - if env.evm_env.block_env.basefee == 0 { + if evm_env.block_env.basefee == 0 { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set - env.evm_env.cfg_env.disable_base_fee = true; + evm_env.cfg_env.disable_base_fee = true; } // Deposit transaction? if let Ok(deposit) = get_deposit_tx_parts(&other) { - env.tx.deposit = deposit; + tx_env.deposit = deposit; } - env + (evm_env, tx_env) } pub fn call_with_state( @@ -1155,9 +1159,9 @@ impl Backend { ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { let mut inspector = self.build_inspector(); - let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state } = evm.transact(env.tx)?; + let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_eth_evm_with_inspector_ref(state, &evm_env, &mut inspector); + let ResultAndState { result, state } = evm.transact(tx_env)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1189,9 +1193,9 @@ impl Backend { let mut inspector = AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; + let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_eth_evm_with_inspector_ref(state, &evm_env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact(tx_env)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1431,8 +1435,8 @@ impl Backend { let mut results = Vec::new(); // Configure the block environment - let mut env = self.env.read().clone(); - env.evm_env.block_env = block_env_from_header(&block.header); + let mut evm_env = self.env.read().evm_env.clone(); + evm_env.block_env = block_env_from_header(&block.header); // Execute each transaction in the block with tracing for tx_envelope in &block.body.transactions { @@ -1448,12 +1452,12 @@ impl Backend { pending_tx.transaction.as_ref(), *pending_tx.sender(), ); - if env.networks.is_optimism() { + if self.is_optimism() { tx_env.enveloped_tx = Some(pending_tx.transaction.encoded_2718().into()); } // Execute the transaction with the inspector - let mut evm = self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); + let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &evm_env, &mut inspector); let result = evm.transact(tx_env.clone()).ok()?; // Build TraceResults from the inspector and execution result @@ -1566,121 +1570,7 @@ impl Backend { } Ok(None) } -} - -impl Backend -where - N::ReceiptEnvelope: alloy_consensus::TxReceipt, -{ - /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter` - fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec { - let mut all_logs = Vec::new(); - let mut block_log_index = 0u32; - - let storage = self.blockchain.storage.read(); - - for tx in block.body.transactions { - let Some(tx) = storage.transactions.get(&tx.hash()) else { - continue; - }; - - let logs = tx.receipt.logs(); - let transaction_hash = tx.info.transaction_hash; - - for log in logs { - if filter.matches(log) { - all_logs.push(Log { - inner: log.clone(), - block_hash: Some(block_hash), - block_number: Some(block.header.number()), - block_timestamp: Some(block.header.timestamp()), - transaction_hash: Some(transaction_hash), - transaction_index: Some(tx.info.transaction_index), - log_index: Some(block_log_index as u64), - removed: false, - }); - } - block_log_index += 1; - } - } - all_logs - } - - /// Returns the logs of the block that match the filter - async fn logs_for_block( - &self, - filter: Filter, - hash: B256, - ) -> Result, BlockchainError> { - if let Some(block) = self.blockchain.get_block_by_hash(&hash) { - return Ok(self.mined_logs_for_block(filter, block, hash)); - } - - if let Some(fork) = self.get_fork() { - return Ok(fork.logs(&filter).await?); - } - - Ok(Vec::new()) - } - /// Returns the logs that match the filter in the given range of blocks - async fn logs_for_range( - &self, - filter: &Filter, - mut from: u64, - to: u64, - ) -> Result, BlockchainError> { - let mut all_logs = Vec::new(); - - // get the range that predates the fork if any - if let Some(fork) = self.get_fork() { - let mut to_on_fork = to; - - if !fork.predates_fork(to) { - // adjust the ranges - to_on_fork = fork.block_number(); - } - - if fork.predates_fork_inclusive(from) { - // this data is only available on the forked client - let filter = filter.clone().from_block(from).to_block(to_on_fork); - all_logs = fork.logs(&filter).await?; - - // update the range - from = fork.block_number() + 1; - } - } - - for number in from..=to { - if let Some((block, hash)) = self.get_block_with_hash(number) { - all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash)); - } - } - - Ok(all_logs) - } - - /// Returns the logs according to the filter - pub async fn logs(&self, filter: Filter) -> Result, BlockchainError> { - trace!(target: "backend", "get logs [{:?}]", filter); - if let Some(hash) = filter.get_block_hash() { - self.logs_for_block(filter, hash).await - } else { - let best = self.best_number(); - let to_block = - self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); - let from_block = - self.convert_block_number(filter.block_option.get_from_block().copied()); - if from_block > best { - return Err(BlockchainError::BlockOutOfRange(best, from_block)); - } - - self.logs_for_range(&filter, from_block, to_block).await - } - } -} - -impl Backend { /// Initialises the balance of the given accounts #[expect(clippy::too_many_arguments)] pub async fn with_genesis( @@ -1707,8 +1597,7 @@ impl Backend { } else { let env = env.read(); Blockchain::new( - &env, - env.evm_env.cfg_env.spec, + &env.evm_env, fees.is_eip1559().then(|| fees.base_fee()), genesis.timestamp, genesis.number, @@ -1959,7 +1848,6 @@ impl Backend { let env = self.env.read().clone(); let genesis_timestamp = self.genesis.timestamp; let genesis_number = self.genesis.number; - let spec_id = self.spec_id(); // Reset environment to genesis state { @@ -1974,7 +1862,7 @@ impl Backend { // Clear all storage and reinitialize with genesis let base_fee = if self.fees.is_eip1559() { Some(self.fees.base_fee()) } else { None }; *self.blockchain.storage.write() = - BlockchainStorage::new(&env, spec_id, base_fee, genesis_timestamp, genesis_number); + BlockchainStorage::new(&env.evm_env, base_fee, genesis_timestamp, genesis_number); self.states.write().clear(); // Clear the database @@ -2065,263 +1953,197 @@ impl Backend { Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove)) } - /// Get the current state. - pub async fn serialized_state( + /// executes the transactions without writing to the underlying database + pub async fn inspect_tx( &self, - preserve_historical_states: bool, - ) -> Result { - let at = self.env.read().evm_env.block_env.clone(); - let best_number = self.blockchain.storage.read().best_number; - let blocks = self.blockchain.storage.read().serialized_blocks(); - let transactions = self.blockchain.storage.read().serialized_transactions(); - let historical_states = if preserve_historical_states { - Some(self.states.write().serialized_states()) - } else { - None + tx: Arc>, + ) -> Result< + (InstructionResult, Option, u64, State, Vec), + BlockchainError, + > { + let evm_env = self.next_env().evm_env; + let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( + tx.pending_transaction.transaction.as_ref(), + *tx.pending_transaction.sender(), + ); + + if self.is_optimism() { + tx_env.enveloped_tx = Some(tx.pending_transaction.transaction.encoded_2718().into()); + } + + let db = self.db.read().await; + let mut inspector = self.build_inspector(); + let mut evm = self.new_eth_evm_with_inspector_ref(&**db, &evm_env, &mut inspector); + let ResultAndState { result, state } = evm.transact(tx_env)?; + let (exit_reason, gas_used, out, logs) = match result { + ExecutionResult::Success { reason, gas_used, logs, output, .. } => { + (reason.into(), gas_used, Some(output), Some(logs)) + } + ExecutionResult::Revert { gas_used, output } => { + (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) + } + ExecutionResult::Halt { reason, gas_used } => { + let eth_reason = op_haltreason_to_instruction_result(reason); + (eth_reason, gas_used, None, None) + } }; - let state = self.db.read().await.dump_state( - at, - best_number, - blocks, - transactions, - historical_states, - )?; - state.ok_or_else(|| { - RpcError::invalid_params("Dumping state not supported with the current configuration") - .into() - }) - } + drop(evm); + inspector.print_logs(); - /// Write all chain data to serialized bytes buffer - pub async fn dump_state( - &self, - preserve_historical_states: bool, - ) -> Result { - let state = self.serialized_state(preserve_historical_states).await?; - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder - .write_all(&serde_json::to_vec(&state).unwrap_or_default()) - .map_err(|_| BlockchainError::DataUnavailable)?; - Ok(encoder.finish().unwrap_or_default().into()) - } + if self.print_traces { + inspector.print_traces(self.call_trace_decoder.clone()); + } - /// Apply [SerializableState] data to the backend storage. - pub async fn load_state(&self, state: SerializableState) -> Result { - // load the blocks and transactions into the storage - self.blockchain.storage.write().load_blocks(state.blocks.clone()); - self.blockchain.storage.write().load_transactions(state.transactions.clone()); - // reset the block env - if let Some(block) = state.block.clone() { - self.env.write().evm_env.block_env = block.clone(); + Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) + } +} - // Set the current best block number. - // Defaults to block number for compatibility with existing state files. - let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); +impl Backend +where + N::ReceiptEnvelope: alloy_consensus::TxReceipt + Clone, +{ + /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter` + fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec { + let mut all_logs = Vec::new(); + let mut block_log_index = 0u32; - let best_number = state.best_block_number.unwrap_or(block.number.saturating_to()); - if let Some((number, hash)) = fork_num_and_hash { - trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); - // If the state.block_number is greater than the fork block number, set best number - // to the state block number. - // Ref: https://github.com/foundry-rs/foundry/issues/9539 - if best_number > number { - self.blockchain.storage.write().best_number = best_number; - let best_hash = - self.blockchain.storage.read().hash(best_number.into()).ok_or_else( - || { - BlockchainError::RpcError(RpcError::internal_error_with(format!( - "Best hash not found for best number {best_number}", - ))) - }, - )?; - self.blockchain.storage.write().best_hash = best_hash; - } else { - // If loading state file on a fork, set best number to the fork block number. - // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 - self.blockchain.storage.write().best_number = number; - self.blockchain.storage.write().best_hash = hash; - } - } else { - self.blockchain.storage.write().best_number = best_number; + let storage = self.blockchain.storage.read(); - // Set the current best block hash; - let best_hash = - self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { - BlockchainError::RpcError(RpcError::internal_error_with(format!( - "Best hash not found for best number {best_number}", - ))) - })?; + for tx in block.body.transactions { + let Some(tx) = storage.transactions.get(&tx.hash()) else { + continue; + }; - self.blockchain.storage.write().best_hash = best_hash; - } - } + let logs = tx.receipt.logs(); + let transaction_hash = tx.info.transaction_hash; - if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number()) { - let header = &latest.header; - let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used(), - header.gas_limit(), - header.base_fee_per_gas().unwrap_or_default(), - ); - let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( - header.excess_blob_gas().unwrap_or_default(), - header.blob_gas_used().unwrap_or_default(), - ); - - // update next base fee - self.fees.set_base_fee(next_block_base_fee); - - self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( - next_block_excess_blob_gas, - get_blob_base_fee_update_fraction( - self.env.read().evm_env.cfg_env.chain_id, - header.timestamp, - ), - )); + for log in logs { + if filter.matches(log) { + all_logs.push(Log { + inner: log.clone(), + block_hash: Some(block_hash), + block_number: Some(block.header.number()), + block_timestamp: Some(block.header.timestamp()), + transaction_hash: Some(transaction_hash), + transaction_index: Some(tx.info.transaction_index), + log_index: Some(block_log_index as u64), + removed: false, + }); + } + block_log_index += 1; + } } + all_logs + } - if !self.db.write().await.load_state(state.clone())? { - return Err(RpcError::invalid_params( - "Loading state not supported with the current configuration", - ) - .into()); + /// Returns the logs of the block that match the filter + async fn logs_for_block( + &self, + filter: Filter, + hash: B256, + ) -> Result, BlockchainError> { + if let Some(block) = self.blockchain.get_block_by_hash(&hash) { + return Ok(self.mined_logs_for_block(filter, block, hash)); } - if let Some(historical_states) = state.historical_states { - self.states.write().load_states(historical_states); + if let Some(fork) = self.get_fork() { + return Ok(fork.logs(&filter).await?); } - Ok(true) - } - - /// Deserialize and add all chain data to the backend storage - pub async fn load_state_bytes(&self, buf: Bytes) -> Result { - let orig_buf = &buf.0[..]; - let mut decoder = GzDecoder::new(orig_buf); - let mut decoded_data = Vec::new(); - - let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() { - decoder - .read_to_end(decoded_data.as_mut()) - .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - &decoded_data - } else { - &buf.0 - }) - .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - - self.load_state(state).await + Ok(Vec::new()) } - /// executes the transactions without writing to the underlying database - pub async fn inspect_tx( + /// Returns the logs that match the filter in the given range of blocks + async fn logs_for_range( &self, - tx: Arc, - ) -> Result< - (InstructionResult, Option, u64, State, Vec), - BlockchainError, - > { - let mut env = self.next_env(); - env.tx = FromRecoveredTx::from_recovered_tx( - tx.pending_transaction.transaction.as_ref(), - *tx.pending_transaction.sender(), - ); + filter: &Filter, + mut from: u64, + to: u64, + ) -> Result, BlockchainError> { + let mut all_logs = Vec::new(); - if env.networks.is_optimism() { - env.tx.enveloped_tx = Some(tx.pending_transaction.transaction.encoded_2718().into()); - } + // get the range that predates the fork if any + if let Some(fork) = self.get_fork() { + let mut to_on_fork = to; - let db = self.db.read().await; - let mut inspector = self.build_inspector(); - let mut evm = self.new_evm_with_inspector_ref(&**db, &env, &mut inspector); - let ResultAndState { result, state } = evm.transact(env.tx)?; - let (exit_reason, gas_used, out, logs) = match result { - ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (reason.into(), gas_used, Some(output), Some(logs)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) - } - ExecutionResult::Halt { reason, gas_used } => { - let eth_reason = op_haltreason_to_instruction_result(reason); - (eth_reason, gas_used, None, None) + if !fork.predates_fork(to) { + // adjust the ranges + to_on_fork = fork.block_number(); } - }; - drop(evm); - inspector.print_logs(); + if fork.predates_fork_inclusive(from) { + // this data is only available on the forked client + let filter = filter.clone().from_block(from).to_block(to_on_fork); + all_logs = fork.logs(&filter).await?; - if self.print_traces { - inspector.print_traces(self.call_trace_decoder.clone()); + // update the range + from = fork.block_number() + 1; + } } - Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) - } + for number in from..=to { + if let Some((block, hash)) = self.get_block_with_hash(number) { + all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash)); + } + } - /// Creates the pending block - /// - /// This will execute all transaction in the order they come but will not mine the block - pub async fn pending_block(&self, pool_transactions: Vec>) -> BlockInfo { - self.with_pending_block(pool_transactions, |_, block| block).await + Ok(all_logs) } - /// Creates the pending block - /// - /// This will execute all transaction in the order they come but will not mine the block - pub async fn with_pending_block( - &self, - pool_transactions: Vec>, - f: F, - ) -> T - where - F: FnOnce(Box, BlockInfo) -> T, - { - let db = self.db.read().await; - let env = self.next_env(); + /// Returns the logs according to the filter + pub async fn logs(&self, filter: Filter) -> Result, BlockchainError> { + trace!(target: "backend", "get logs [{:?}]", filter); + if let Some(hash) = filter.get_block_hash() { + self.logs_for_block(filter, hash).await + } else { + let best = self.best_number(); + let to_block = + self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); + let from_block = + self.convert_block_number(filter.block_option.get_from_block().copied()); + if from_block > best { + return Err(BlockchainError::BlockOutOfRange(best, from_block)); + } - let mut cache_db = CacheDB::new(&*db); + self.logs_for_range(&filter, from_block, to_block).await + } + } + /// Returns all receipts of the block + pub fn mined_receipts(&self, hash: B256) -> Option> { + let block = self.mined_block_by_hash(hash)?; + let mut receipts = Vec::new(); let storage = self.blockchain.storage.read(); - - let executor = TransactionExecutor { - db: &mut cache_db, - validator: self, - pending: pool_transactions.into_iter(), - evm_env: env.evm_env, - parent_hash: storage.best_hash, - gas_used: 0, - blob_gas_used: 0, - enable_steps_tracing: self.enable_steps_tracing, - print_logs: self.print_logs, - print_traces: self.print_traces, - call_trace_decoder: self.call_trace_decoder.clone(), - precompile_factory: self.precompile_factory.clone(), - networks: self.env.read().networks, - blob_params: self.blob_params(), - cheats: self.cheats().clone(), - }; - - // create a new pending block - let executed = executor.execute(); - f(Box::new(cache_db), executed.block) + for tx in block.transactions.hashes() { + let receipt = storage.transactions.get(&tx)?.receipt.clone(); + receipts.push(receipt); + } + Some(receipts) } +} +// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now. +impl Backend +where + Self: TransactionValidator, + N: Network, +{ /// Mines a new block and stores it. /// /// this will execute all transaction in the order they come in and return all the markers they /// provide. pub async fn mine_block( &self, - pool_transactions: Vec>, - ) -> MinedBlockOutcome { + pool_transactions: Vec>>, + ) -> MinedBlockOutcome { self.do_mine_block(pool_transactions).await } async fn do_mine_block( &self, - pool_transactions: Vec>, - ) -> MinedBlockOutcome { + pool_transactions: Vec>>, + ) -> MinedBlockOutcome { let _mining_guard = self.mining.lock().await; trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len()); @@ -2364,7 +2186,7 @@ impl Backend { self.states.write().insert(best_hash, db); } - let (executed_tx, block_hash) = { + let (block_info, included, invalid, block_hash) = { let mut db = self.db.write().await; // finally set the next block timestamp, this is done just before execution, because @@ -2372,38 +2194,283 @@ impl Backend { // to ensure the timestamp is as close as possible to the actual execution. env.evm_env.block_env.timestamp = U256::from(self.time.next_timestamp()); - let executor = TransactionExecutor { - db: &mut **db, - validator: self, - pending: pool_transactions.into_iter(), - evm_env: env.evm_env.clone(), - parent_hash: best_hash, - gas_used: 0, - blob_gas_used: 0, - enable_steps_tracing: self.enable_steps_tracing, - print_logs: self.print_logs, + let spec_id = *env.evm_env.spec_id(); + let is_shanghai = spec_id >= SpecId::SHANGHAI; + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + let gas_limit = env.evm_env.block_env.gas_limit; + let difficulty = env.evm_env.block_env.difficulty; + let mix_hash = env.evm_env.block_env.prevrandao; + let beneficiary = env.evm_env.block_env.beneficiary; + let timestamp = env.evm_env.block_env.timestamp; + let base_fee = if spec_id >= SpecId::LONDON { + Some(env.evm_env.block_env.basefee) + } else { + None + }; + let excess_blob_gas = + if is_cancun { env.evm_env.block_env.blob_excess_gas() } else { None }; + + // 1. Build inspector (per-block, NOT per-tx) + let mut inspector = AnvilInspector::default().with_tracing(); + if self.enable_steps_tracing { + inspector = inspector.with_steps_tracing(); + } + if self.print_logs { + inspector = inspector.with_log_collector(); + } + if self.print_traces { + inspector = inspector.with_trace_printer(); + } + + // 2. Create EVM + let mut evm = new_eth_evm_with_inspector( + &mut **db, + &env.evm_env, + inspector, + env.networks.is_optimism(), + ); + + // 3. Inject precompiles (once, before the tx loop) + env.networks.inject_precompiles(evm.precompiles_mut()); + if let Some(factory) = &self.precompile_factory { + evm.precompiles_mut().extend_precompiles(factory.precompiles()); + } + let cheats = self.cheats().clone(); + if cheats.has_recover_overrides() { + let cheats_arc = Arc::new(cheats.clone()); + let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); + evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { + Some(DynPrecompile::new_stateful( + cheat_ecrecover.precompile_id().clone(), + move |input| cheat_ecrecover.call(input), + )) + }); + } + + // 4. Create executor via AnvilBlockExecutorFactory + let exec_ctx = AnvilExecutionCtx { parent_hash: best_hash, is_prague, is_cancun }; + let mut executor = AnvilBlockExecutorFactory::create_executor(evm, exec_ctx); + executor.apply_pre_execution_changes().expect("pre-execution changes failed"); + + // 5. Per-tx loop + let mut included: Vec>> = Vec::new(); + let mut invalid: Vec>> = Vec::new(); + let mut transaction_infos: Vec = Vec::new(); + let mut transactions = Vec::new(); + let mut bloom = Bloom::default(); + + let blob_params = self.blob_params(); + let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; + + let inspector_tx_config = InspectorTxConfig { print_traces: self.print_traces, + print_logs: self.print_logs, + enable_steps_tracing: self.enable_steps_tracing, call_trace_decoder: self.call_trace_decoder.clone(), - networks: self.env.read().networks, - precompile_factory: self.precompile_factory.clone(), - blob_params: self.blob_params(), - cheats: self.cheats().clone(), }; - let executed_tx = executor.execute(); - // we also need to update the new blockhash in the db itself - let block_hash = executed_tx.block.block.header.hash_slow(); - db.insert_block_hash( - U256::from(executed_tx.block.block.header.number()), - block_hash, - ); + for pool_tx in pool_transactions { + let pending = &pool_tx.pending_transaction; + let sender = *pending.sender(); + + let account = match executor + .evm_mut() + .db_mut() + .basic(sender) + .map(|a| a.unwrap_or_default()) + { + Ok(acc) => acc, + Err(err) => { + trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash()); + continue; + } + }; + + // Build the per-tx env + let tx_env = build_tx_env_for_pending(pending, &cheats, self.is_optimism()); + + // Gas limit checks (same logic as TransactionExecutor::next) + let cumulative_gas = + executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); + let max_block_gas = + cumulative_gas.saturating_add(pending.transaction.gas_limit()); + if !env.evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction"); + continue; + } + + // Osaka EIP-7825 tx gas limit cap check + if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() + && pending.transaction.gas_limit() > env.evm_env.cfg_env.tx_gas_limit_cap() + { + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction"); + continue; + } + + // Blob gas check + let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); + let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); + if current_blob_gas.saturating_add(tx_blob_gas) + > blob_params.max_blob_gas_per_block() + { + trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction"); + continue; + } + + // Validate + if let Err(err) = + self.validate_pool_transaction_for(pending, &account, &env.evm_env) + { + warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err); + invalid.push(pool_tx.clone()); + continue; + } + + let nonce = account.nonce; + + // Execute via block executor + let recovered = + Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); + trace!(target: "backend", "[{:?}] executing", pool_tx.hash()); + match executor.execute_transaction_without_commit((tx_env, recovered)) { + Ok(result) => { + let exec_result = result.inner.result.result.clone(); + let gas_used = result.inner.result.result.gas_used(); + + executor.commit_transaction(result).expect("commit failed"); + + let traces = executor + .evm_mut() + .inspector_mut() + .finish_transaction(&inspector_tx_config); + + // Track blob gas + if is_cancun { + cumulative_blob_gas_used = Some( + cumulative_blob_gas_used + .unwrap_or(0) + .saturating_add(tx_blob_gas), + ); + } + + let (exit_reason, out, logs) = match exec_result { + ExecutionResult::Success { + reason, + gas_used: _, + logs, + output, + .. + } => (reason.into(), Some(output), logs), + ExecutionResult::Revert { gas_used: _, output } => ( + InstructionResult::Revert, + Some(Output::Call(output)), + Vec::new(), + ), + ExecutionResult::Halt { reason, gas_used: _ } => { + (op_haltreason_to_instruction_result(reason), None, Vec::new()) + } + }; + + if exit_reason == InstructionResult::OutOfGas { + warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash()); + } + + trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out); + trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used); + + // Build bloom from logs + for log in &logs { + bloom.accrue(BloomInput::Raw(&log.address[..])); + for topic in log.topics() { + bloom.accrue(BloomInput::Raw(&topic[..])); + } + } + + // Contract address for creation txs + let contract_address = if pending.transaction.to().is_none() { + let addr = sender.create(nonce); + trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); + Some(addr) + } else { + None + }; + + let transaction_index = transaction_infos.len() as u64; + let info = TransactionInfo { + transaction_hash: pool_tx.hash(), + transaction_index, + from: sender, + to: pending.transaction.to(), + contract_address, + traces, + exit: exit_reason, + out: out.map(Output::into_data), + nonce, + gas_used, + }; + + included.push(pool_tx.clone()); + transaction_infos.push(info); + transactions.push(pending.transaction.clone()); + } + Err(err) => { + trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash()); + } + } + } + + // 6. Finish — drop EVM BEFORE accessing db again + let (evm, block_result) = executor.finish().expect("executor finish failed"); + drop(evm); + + let state_root = db.maybe_state_root().unwrap_or_default(); + + // 7. Build block header + let receipts_root = calculate_receipt_root(&block_result.receipts); + + let cumulative_gas_used = block_result.gas_used; + + let header = Header { + parent_hash: best_hash, + ommers_hash: Default::default(), + beneficiary, + state_root, + transactions_root: Default::default(), + receipts_root, + logs_bloom: bloom, + difficulty, + number: block_number, + gas_limit, + gas_used: cumulative_gas_used, + timestamp: timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: mix_hash.unwrap_or_default(), + nonce: Default::default(), + base_fee_per_gas: base_fee, + parent_beacon_block_root: is_cancun.then_some(Default::default()), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas, + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), + }; + + let block = create_block(header, transactions); + let block_info = TypedBlockInfo { + block, + transactions: transaction_infos, + receipts: block_result.receipts, + }; + + // update the new blockhash in the db itself + let block_hash = block_info.block.header.hash_slow(); + db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash); - (executed_tx, block_hash) + (block_info, included, invalid, block_hash) }; // create the new block with the current timestamp - let ExecutedTransactions { block, included, invalid } = executed_tx; - let BlockInfo { block, transactions, receipts } = block; + let BlockInfo { block, transactions, receipts } = block_info; let header = block.header.clone(); @@ -2504,16 +2571,291 @@ impl Backend { outcome } - /// Executes the [TransactionRequest] without writing to the DB + /// Reorg the chain to a common height and execute blocks to build new chain. /// - /// # Errors + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. /// - /// Returns an error if the `block_number` is greater than the current height - pub async fn call( + /// Finally, `do_mine_block` is called to create the new chain. + pub async fn reorg( &self, - request: WithOtherFields, + depth: u64, + tx_pairs: HashMap>>>, + common_block: Block, + ) -> Result<(), BlockchainError> { + self.rollback(common_block).await?; + // Create the new reorged chain, filling the blocks with transactions if supplied + for i in 0..depth { + let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); + let outcome = self.do_mine_block(to_be_mined).await; + node_info!( + " Mined reorg block number {}. With {} valid txs and with invalid {} txs", + outcome.block_number, + outcome.included.len(), + outcome.invalid.len() + ); + } + + Ok(()) + } + + /// Creates the pending block + /// + /// This will execute all transaction in the order they come but will not mine the block + pub async fn pending_block( + &self, + pool_transactions: Vec>>, + ) -> BlockInfo { + self.with_pending_block(pool_transactions, |_, block| block).await + } + + /// Creates the pending block + /// + /// This will execute all transaction in the order they come but will not mine the block + pub async fn with_pending_block( + &self, + pool_transactions: Vec>>, + f: F, + ) -> T + where + F: FnOnce(Box, BlockInfo) -> T, + { + let db = self.db.read().await; + let evm_env = self.next_env().evm_env; + + let mut cache_db = AnvilCacheDB::new(&*db); + + let parent_hash = self.blockchain.storage.read().best_hash; + + let spec_id = *evm_env.spec_id(); + let is_shanghai = spec_id >= SpecId::SHANGHAI; + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + let gas_limit = evm_env.block_env.gas_limit; + let difficulty = evm_env.block_env.difficulty; + let mix_hash = evm_env.block_env.prevrandao; + let beneficiary = evm_env.block_env.beneficiary; + let timestamp = evm_env.block_env.timestamp; + let base_fee = + if spec_id >= SpecId::LONDON { Some(evm_env.block_env.basefee) } else { None }; + let excess_blob_gas = if is_cancun { evm_env.block_env.blob_excess_gas() } else { None }; + + let mut inspector = AnvilInspector::default().with_tracing(); + if self.enable_steps_tracing { + inspector = inspector.with_steps_tracing(); + } + if self.print_logs { + inspector = inspector.with_log_collector(); + } + if self.print_traces { + inspector = inspector.with_trace_printer(); + } + + let mut evm = + new_eth_evm_with_inspector(&mut cache_db, &evm_env, inspector, self.is_optimism()); + + self.env.read().networks.inject_precompiles(evm.precompiles_mut()); + if let Some(factory) = &self.precompile_factory { + evm.precompiles_mut().extend_precompiles(factory.precompiles()); + } + let cheats = self.cheats().clone(); + if cheats.has_recover_overrides() { + let cheats_arc = Arc::new(cheats.clone()); + let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); + evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { + Some(DynPrecompile::new_stateful( + cheat_ecrecover.precompile_id().clone(), + move |input| cheat_ecrecover.call(input), + )) + }); + } + + let exec_ctx = AnvilExecutionCtx { parent_hash, is_prague, is_cancun }; + let mut executor = AnvilBlockExecutorFactory::create_executor(evm, exec_ctx); + executor.apply_pre_execution_changes().expect("pre-execution changes failed"); + + let mut transaction_infos: Vec = Vec::new(); + let mut transactions = Vec::new(); + let mut bloom = Bloom::default(); + + let blob_params = self.blob_params(); + let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; + + let inspector_tx_config = InspectorTxConfig { + print_traces: self.print_traces, + print_logs: self.print_logs, + enable_steps_tracing: self.enable_steps_tracing, + call_trace_decoder: self.call_trace_decoder.clone(), + }; + + for pool_tx in pool_transactions { + let pending = &pool_tx.pending_transaction; + let sender = *pending.sender(); + + let account = match executor + .evm_mut() + .db_mut() + .basic(sender) + .map(|a| a.unwrap_or_default()) + { + Ok(acc) => acc, + Err(err) => { + trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash()); + continue; + } + }; + + let tx_env = build_tx_env_for_pending(pending, &cheats, self.is_optimism()); + + let cumulative_gas = + executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); + let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); + if !evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction"); + continue; + } + + if evm_env.cfg_env.tx_gas_limit_cap.is_none() + && pending.transaction.gas_limit() > evm_env.cfg_env.tx_gas_limit_cap() + { + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction"); + continue; + } + + let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); + let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); + if current_blob_gas.saturating_add(tx_blob_gas) > blob_params.max_blob_gas_per_block() { + trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction"); + continue; + } + + if let Err(err) = self.validate_pool_transaction_for(pending, &account, &evm_env) { + warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err); + continue; + } + + let nonce = account.nonce; + + let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); + trace!(target: "backend", "[{:?}] executing", pool_tx.hash()); + match executor.execute_transaction_without_commit((tx_env, recovered)) { + Ok(result) => { + let exec_result = result.inner.result.result.clone(); + let gas_used = result.inner.result.result.gas_used(); + + executor.commit_transaction(result).expect("commit failed"); + + let traces = + executor.evm_mut().inspector_mut().finish_transaction(&inspector_tx_config); + + if is_cancun { + cumulative_blob_gas_used = + Some(cumulative_blob_gas_used.unwrap_or(0).saturating_add(tx_blob_gas)); + } + + let (exit_reason, out, logs) = match exec_result { + ExecutionResult::Success { reason, gas_used: _, logs, output, .. } => { + (reason.into(), Some(output), logs) + } + ExecutionResult::Revert { gas_used: _, output } => { + (InstructionResult::Revert, Some(Output::Call(output)), Vec::new()) + } + ExecutionResult::Halt { reason, gas_used: _ } => { + (op_haltreason_to_instruction_result(reason), None, Vec::new()) + } + }; + + for log in &logs { + bloom.accrue(BloomInput::Raw(&log.address[..])); + for topic in log.topics() { + bloom.accrue(BloomInput::Raw(&topic[..])); + } + } + + let contract_address = if pending.transaction.to().is_none() { + let addr = sender.create(nonce); + Some(addr) + } else { + None + }; + + let transaction_index = transaction_infos.len() as u64; + let info = TransactionInfo { + transaction_hash: pool_tx.hash(), + transaction_index, + from: sender, + to: pending.transaction.to(), + contract_address, + traces, + exit: exit_reason, + out: out.map(Output::into_data), + nonce, + gas_used, + }; + + transaction_infos.push(info); + transactions.push(pending.transaction.clone()); + } + Err(err) => { + trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash()); + } + } + } + + let (evm, block_result) = executor.finish().expect("executor finish failed"); + drop(evm); + + // Extract inner CacheDB (which implements MaybeFullDatabase) + let cache_db = cache_db.0; + + let state_root = cache_db.maybe_state_root().unwrap_or_default(); + let receipts_root = calculate_receipt_root(&block_result.receipts); + let cumulative_gas_used = block_result.gas_used; + + let header = Header { + parent_hash, + ommers_hash: Default::default(), + beneficiary, + state_root, + transactions_root: Default::default(), + receipts_root, + logs_bloom: bloom, + difficulty, + number: evm_env.block_env.number.saturating_to(), + gas_limit, + gas_used: cumulative_gas_used, + timestamp: timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: mix_hash.unwrap_or_default(), + nonce: Default::default(), + base_fee_per_gas: base_fee, + parent_beacon_block_root: is_cancun.then_some(Default::default()), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas, + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), + }; + + let block = create_block(header, transactions); + let block_info = TypedBlockInfo { + block, + transactions: transaction_infos, + receipts: block_result.receipts, + }; + + f(Box::new(cache_db), block_info) + } + + /// Executes the [TransactionRequest] without writing to the DB + /// + /// # Errors + /// + /// Returns an error if the `block_number` is greater than the current height + pub async fn call( + &self, + request: WithOtherFields, fee_details: FeeDetails, - block_request: Option, + block_request: Option>, overrides: EvmOverrides, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { self.with_database_at(block_request, |state, mut block| { @@ -2533,293 +2875,85 @@ impl Backend { }).await? } - /// Simulates the payload by executing the calls in request. - pub async fn simulate( + pub async fn call_with_tracing( &self, - request: SimulatePayload, - block_request: Option, - ) -> Result>, BlockchainError> { - self.with_database_at(block_request, |state, mut block_env| { - let SimulatePayload { - block_state_calls, - trace_transfers, - validation, - return_full_transactions, - } = request; - let mut cache_db = CacheDB::new(state); - let mut block_res = Vec::with_capacity(block_state_calls.len()); - - // execute the blocks - for block in block_state_calls { - let SimBlock { block_overrides, state_overrides, calls } = block; - let mut call_res = Vec::with_capacity(calls.len()); - let mut log_index = 0; - let mut gas_used = 0; - let mut transactions = Vec::with_capacity(calls.len()); - let mut logs= Vec::new(); - - // apply state overrides before executing the transactions - if let Some(state_overrides) = state_overrides { - apply_state_overrides(state_overrides, &mut cache_db)?; - } - if let Some(block_overrides) = block_overrides { - cache_db.apply_block_overrides(block_overrides, &mut block_env); - } + request: WithOtherFields, + fee_details: FeeDetails, + block_request: Option>, + opts: GethDebugTracingCallOptions, + ) -> Result { + let GethDebugTracingCallOptions { + tracing_options, block_overrides, state_overrides, .. + } = opts; + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; - // execute all calls in that block - for (req_idx, request) in calls.into_iter().enumerate() { - let fee_details = FeeDetails::new( - request.gas_price, - request.max_fee_per_gas, - request.max_priority_fee_per_gas, - request.max_fee_per_blob_gas, - )? - .or_zero_fees(); + self.with_database_at(block_request, |state, mut block| { + let block_number = block.number; - let mut env = self.build_call_env( - WithOtherFields::new(request.clone()), - fee_details, - block_env.clone(), - ); + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = state_overrides { + apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + cache_db.apply_block_overrides(block_overrides, &mut block); + } - // Always disable EIP-3607 - env.evm_env.cfg_env.disable_eip3607 = true; + if let Some(tracer) = tracer { + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::CallTracer => { + let call_config = tracer_config + .into_call_config() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; - if !validation { - env.evm_env.cfg_env.disable_base_fee = !validation; - env.evm_env.block_env.basefee = 0; - } + let mut inspector = self.build_inspector().with_tracing_config( + TracingInspectorConfig::from_geth_call_config(&call_config), + ); - let mut inspector = self.build_inspector(); + let (evm_env, tx_env) = + self.build_call_env(request, fee_details, block); + let mut evm = self.new_eth_evm_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + ); + let ResultAndState { result, state: _ } = evm.transact(tx_env)?; - // transact - let ResultAndState { result, state } = if trace_transfers { - // prepare inspector to capture transfer inside the evm so they are - // recorded and included in logs - inspector = inspector.with_transfers(); - let mut evm= self.new_evm_with_inspector_ref( - &cache_db, - &env, - &mut inspector, - ); + drop(evm); - trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.transact(env.tx)? - } else { - let mut evm = self.new_evm_with_inspector_ref( - &cache_db, - &env, - &mut inspector, - ); - trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.transact(env.tx)? - }; - trace!(target: "backend", ?result, ?request, "simulate call"); + inspector.print_logs(); + if self.print_traces { + inspector.print_traces(self.call_trace_decoder.clone()); + } - inspector.print_logs(); - if self.print_traces { - inspector.into_print_traces(self.call_trace_decoder.clone()); - } + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); - // commit the transaction - cache_db.commit(state); - gas_used += result.gas_used(); + Ok(tracing_inspector + .into_geth_builder() + .geth_call_traces(call_config, result.gas_used()) + .into()) + } + GethDebugBuiltInTracerType::PreStateTracer => { + let pre_state_config = tracer_config + .into_pre_state_config() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; - // create the transaction from a request - let from = request.from.unwrap_or_default(); + let mut inspector = TracingInspector::new( + TracingInspectorConfig::from_geth_prestate_config( + &pre_state_config, + ), + ); - let mut request = Into::::into(WithOtherFields::new(request)); - request.prep_for_submission(); + let (evm_env, tx_env) = + self.build_call_env(request, fee_details, block); + let mut evm = self.new_eth_evm_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + ); + let result = evm.transact(tx_env)?; - let typed_tx = request.build_unsigned().map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))?; - - let tx = build_impersonated(typed_tx); - let tx_hash = tx.hash(); - let rpc_tx = transaction_build( - None, - MaybeImpersonatedTransaction::impersonated(tx, from), - None, - None, - Some(block_env.basefee), - ); - transactions.push(rpc_tx); - - let return_data = result.output().cloned().unwrap_or_default(); - let sim_res = SimCallResult { - return_data, - gas_used: result.gas_used(), - status: result.is_success(), - error: result.is_success().not().then(|| { - alloy_rpc_types::simulate::SimulateError { - code: -3200, - message: "execution failed".to_string(), - data: None, - } - }), - logs: result.clone() - .into_logs() - .into_iter() - .enumerate() - .map(|(idx, log)| Log { - inner: log, - block_number: Some(block_env.number.saturating_to()), - block_timestamp: Some(block_env.timestamp.saturating_to()), - transaction_index: Some(req_idx as u64), - log_index: Some((idx + log_index) as u64), - removed: false, - - block_hash: None, - transaction_hash: Some(tx_hash), - }) - .collect(), - }; - logs.extend(sim_res.logs.iter().map(|log| log.inner.clone())); - log_index += sim_res.logs.len(); - call_res.push(sim_res); - } - - let transactions_envelopes: Vec = transactions - .iter() - .map(|tx| AnyTxEnvelope::from(tx.clone())) - .collect(); - let header = Header { - logs_bloom: logs_bloom(logs.iter()), - transactions_root: calculate_transaction_root(&transactions_envelopes), - receipts_root: calculate_receipt_root(&transactions_envelopes), - parent_hash: Default::default(), - beneficiary: block_env.beneficiary, - state_root: Default::default(), - difficulty: Default::default(), - number: block_env.number.saturating_to(), - gas_limit: block_env.gas_limit, - gas_used, - timestamp: block_env.timestamp.saturating_to(), - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - base_fee_per_gas: Some(block_env.basefee), - withdrawals_root: None, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - requests_hash: None, - ..Default::default() - }; - let mut block = alloy_rpc_types::Block { - header: AnyRpcHeader { - hash: header.hash_slow(), - inner: header.into(), - total_difficulty: None, - size: None, - }, - uncles: vec![], - transactions: BlockTransactions::Full(transactions), - withdrawals: None, - }; - - if !return_full_transactions { - block.transactions.convert_to_hashes(); - } - - for res in &mut call_res { - res.logs.iter_mut().for_each(|log| { - log.block_hash = Some(block.header.hash); - }); - } - - let simulated_block = SimulatedBlock { - inner: AnyRpcBlock::new(WithOtherFields::new(block)), - calls: call_res, - }; - - // update block env - block_env.number += U256::from(1); - block_env.timestamp += U256::from(12); - block_env.basefee = simulated_block - .inner - .header - .next_block_base_fee(self.fees.base_fee_params()) - .unwrap_or_default(); - - block_res.push(simulated_block); - } - - Ok(block_res) - }) - .await? - } - - pub async fn call_with_tracing( - &self, - request: WithOtherFields, - fee_details: FeeDetails, - block_request: Option, - opts: GethDebugTracingCallOptions, - ) -> Result { - let GethDebugTracingCallOptions { - tracing_options, block_overrides, state_overrides, .. - } = opts; - let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; - - self.with_database_at(block_request, |state, mut block| { - let block_number = block.number; - - let mut cache_db = CacheDB::new(state); - if let Some(state_overrides) = state_overrides { - apply_state_overrides(state_overrides, &mut cache_db)?; - } - if let Some(block_overrides) = block_overrides { - cache_db.apply_block_overrides(block_overrides, &mut block); - } - - if let Some(tracer) = tracer { - return match tracer { - GethDebugTracerType::BuiltInTracer(tracer) => match tracer { - GethDebugBuiltInTracerType::CallTracer => { - let call_config = tracer_config - .into_call_config() - .map_err(|e| RpcError::invalid_params(e.to_string()))?; - - let mut inspector = self.build_inspector().with_tracing_config( - TracingInspectorConfig::from_geth_call_config(&call_config), - ); - - let env = self.build_call_env(request, fee_details, block); - let mut evm = - self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; - - drop(evm); - - inspector.print_logs(); - if self.print_traces { - inspector.print_traces(self.call_trace_decoder.clone()); - } - - let tracing_inspector = inspector.tracer.expect("tracer disappeared"); - - Ok(tracing_inspector - .into_geth_builder() - .geth_call_traces(call_config, result.gas_used()) - .into()) - } - GethDebugBuiltInTracerType::PreStateTracer => { - let pre_state_config = tracer_config - .into_pre_state_config() - .map_err(|e| RpcError::invalid_params(e.to_string()))?; - - let mut inspector = TracingInspector::new( - TracingInspectorConfig::from_geth_prestate_config( - &pre_state_config, - ), - ); - - let env = self.build_call_env(request, fee_details, block); - let mut evm = - self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); - let result = evm.transact(env.tx)?; - - drop(evm); + drop(evm); Ok(inspector .into_geth_builder() @@ -2846,13 +2980,17 @@ impl Backend { revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; - let env = self.build_call_env(request, fee_details, block.clone()); - let mut evm = - self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); - let result = evm.transact(env.tx.clone())?; + let (evm_env, tx_env) = + self.build_call_env(request, fee_details, block.clone()); + let mut evm = self.new_eth_evm_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + ); + let result = evm.transact(tx_env.clone())?; let res = evm .inspector_mut() - .json_result(result, &env.tx.into_tx_env(), &block, &cache_db) + .json_result(result, &tx_env.into_tx_env(), &block, &cache_db) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(GethTrace::JS(res)) @@ -2865,9 +3003,9 @@ impl Backend { .build_inspector() .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); - let env = self.build_call_env(request, fee_details, block); - let mut evm = self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; + let (evm_env, tx_env) = self.build_call_env(request, fee_details, block); + let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &evm_env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact(tx_env)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { @@ -2897,27 +3035,10 @@ impl Backend { .await? } - /// returns all receipts for the given transactions - fn get_receipts( - &self, - tx_hashes: impl IntoIterator, - ) -> Vec { - let storage = self.blockchain.storage.read(); - let mut receipts = vec![]; - - for hash in tx_hashes { - if let Some(tx) = storage.transactions.get(&hash) { - receipts.push(tx.receipt.clone()); - } - } - - receipts - } - /// Helper function to execute a closure with the database at a specific block pub async fn with_database_at( &self, - block_request: Option, + block_request: Option>, f: F, ) -> Result where @@ -2974,7 +3095,7 @@ impl Backend { &self, address: Address, index: U256, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| { trace!(target: "backend", "get storage for {:?} at {:?}", address, index); @@ -2991,7 +3112,7 @@ impl Backend { pub async fn get_code( &self, address: Address, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await? } @@ -3002,7 +3123,7 @@ impl Backend { pub async fn get_balance( &self, address: Address, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address)) .await? @@ -3011,7 +3132,7 @@ impl Backend { pub async fn get_account_at_block( &self, address: Address, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |block_db, _| { let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; @@ -3031,7 +3152,7 @@ impl Backend { pub async fn get_nonce( &self, address: Address, - block_request: BlockRequest, + block_request: BlockRequest, ) -> Result { if let BlockRequest::Pending(pool_transactions) = &block_request && let Some(value) = get_pool_transactions_nonce(pool_transactions, address) @@ -3050,32 +3171,6 @@ impl Backend { .await? } - /// Returns the traces for the given transaction - pub async fn debug_trace_transaction( - &self, - hash: B256, - opts: GethDebugTracingOptions, - ) -> Result { - #[cfg(feature = "js-tracer")] - if let Some(tracer_type) = opts.tracer.as_ref() - && tracer_type.is_js() - { - return self - .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone()) - .await; - } - - if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await { - return trace; - } - - if let Some(fork) = self.get_fork() { - return Ok(fork.debug_trace_transaction(hash, opts).await?); - } - - Ok(GethTrace::Default(Default::default())) - } - fn replay_tx_with_inspector( &self, hash: B256, @@ -3107,7 +3202,8 @@ impl Backend { .position(|tx| tx.hash() == hash) .expect("transaction not found in block"); - let pool_txs: Vec> = block.body.transactions[..index] + let pool_txs: Vec>> = block.body.transactions + [..index] .iter() .map(|tx| { let pending_tx = @@ -3122,91 +3218,737 @@ impl Backend { .collect(); let trace = |parent_state: &StateDb| -> Result { - let mut cache_db = CacheDB::new(Box::new(parent_state)); + let mut cache_db = AnvilCacheDB::new(Box::new(parent_state)); // configure the blockenv for the block of the transaction let mut env = self.env.read().clone(); env.evm_env.block_env = block_env_from_header(&block.header); - let executor = TransactionExecutor { - db: &mut cache_db, - validator: self, - pending: pool_txs.into_iter(), - evm_env: env.evm_env.clone(), - parent_hash: block.header.parent_hash, - gas_used: 0, - blob_gas_used: 0, - enable_steps_tracing: self.enable_steps_tracing, - print_logs: self.print_logs, + let spec_id = *env.evm_env.spec_id(); + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + let gas_limit = env.evm_env.block_env.gas_limit; + + let mut inspector_replay = AnvilInspector::default().with_tracing(); + if self.enable_steps_tracing { + inspector_replay = inspector_replay.with_steps_tracing(); + } + if self.print_logs { + inspector_replay = inspector_replay.with_log_collector(); + } + if self.print_traces { + inspector_replay = inspector_replay.with_trace_printer(); + } + + let mut evm_replay = new_eth_evm_with_inspector( + &mut cache_db, + &env.evm_env, + inspector_replay, + env.networks.is_optimism(), + ); + + env.networks.inject_precompiles(evm_replay.precompiles_mut()); + if let Some(factory) = &self.precompile_factory { + evm_replay.precompiles_mut().extend_precompiles(factory.precompiles()); + } + let cheats = self.cheats().clone(); + if cheats.has_recover_overrides() { + let cheats_arc = Arc::new(cheats.clone()); + let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); + evm_replay.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { + Some(DynPrecompile::new_stateful( + cheat_ecrecover.precompile_id().clone(), + move |input| cheat_ecrecover.call(input), + )) + }); + } + + let exec_ctx = + AnvilExecutionCtx { parent_hash: block.header.parent_hash, is_prague, is_cancun }; + let mut replay_executor = + AnvilBlockExecutorFactory::create_executor(evm_replay, exec_ctx); + replay_executor.apply_pre_execution_changes().expect("pre-execution changes failed"); + + let blob_params = self.blob_params(); + let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; + + let inspector_tx_config = InspectorTxConfig { print_traces: self.print_traces, + print_logs: self.print_logs, + enable_steps_tracing: self.enable_steps_tracing, call_trace_decoder: self.call_trace_decoder.clone(), - precompile_factory: self.precompile_factory.clone(), - networks: self.env.read().networks, - blob_params: self.blob_params(), - cheats: self.cheats().clone(), }; - let _ = executor.execute(); + for pool_tx in pool_txs { + let pending = &pool_tx.pending_transaction; + let sender = *pending.sender(); - let target_tx = block.body.transactions[index].clone(); - let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?; - let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( - target_tx.transaction.as_ref(), - *target_tx.sender(), - ); - if env.networks.is_optimism() { - tx_env.enveloped_tx = Some(target_tx.transaction.encoded_2718().into()); - } + let account = match replay_executor + .evm_mut() + .db_mut() + .basic(sender) + .map(|a| a.unwrap_or_default()) + { + Ok(acc) => acc, + Err(_) => continue, + }; + + let tx_env = build_tx_env_for_pending(pending, &cheats, self.is_optimism()); + + let cumulative_gas = + replay_executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); + let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); + if !env.evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { + continue; + } + + if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() + && pending.transaction.gas_limit() > env.evm_env.cfg_env.tx_gas_limit_cap() + { + continue; + } + + let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); + let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); + if current_blob_gas.saturating_add(tx_blob_gas) + > blob_params.max_blob_gas_per_block() + { + continue; + } + + if self.validate_pool_transaction_for(pending, &account, &env.evm_env).is_err() { + continue; + } + + let recovered = + Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); + if let Ok(result) = + replay_executor.execute_transaction_without_commit((tx_env, recovered)) + { + replay_executor.commit_transaction(result).expect("commit failed"); + + replay_executor + .evm_mut() + .inspector_mut() + .finish_transaction(&inspector_tx_config); + + if is_cancun { + cumulative_blob_gas_used = + Some(cumulative_blob_gas_used.unwrap_or(0).saturating_add(tx_blob_gas)); + } + } + } + + let (evm_done, _) = replay_executor.finish().expect("executor finish failed"); + drop(evm_done); + + // Extract inner CacheDB to match the expected types for the target tx execution + let cache_db = cache_db.0; + + let target_tx = block.body.transactions[index].clone(); + let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?; + let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( + target_tx.transaction.as_ref(), + *target_tx.sender(), + ); + if env.networks.is_optimism() { + tx_env.enveloped_tx = Some(target_tx.transaction.encoded_2718().into()); + } + + let mut evm = + self.new_eth_evm_with_inspector_ref(&cache_db, &env.evm_env, &mut inspector); + + let result = evm + .transact(tx_env.clone()) + .map_err(|err| BlockchainError::Message(err.to_string()))?; + + Ok(f(result, cache_db, inspector, tx_env.base, env)) + }; + + let read_guard = self.states.upgradable_read(); + if let Some(state) = read_guard.get_state(&block.header.parent_hash) { + trace(state) + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state = write_guard + .get_on_disk_state(&block.header.parent_hash) + .ok_or(BlockchainError::BlockNotFound)?; + trace(state) + } + } + + /// Traces the transaction with the js tracer + #[cfg(feature = "js-tracer")] + pub async fn trace_tx_with_js_tracer( + &self, + hash: B256, + code: String, + opts: GethDebugTracingOptions, + ) -> Result { + let GethDebugTracingOptions { tracer_config, .. } = opts; + let config = tracer_config.into_json(); + let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) + .map_err(|err| BlockchainError::Message(err.to_string()))?; + let trace = self.replay_tx_with_inspector( + hash, + inspector, + |result, cache_db, mut inspector, tx_env, env| { + inspector + .json_result( + result, + &alloy_evm::IntoTxEnv::into_tx_env(tx_env), + &env.evm_env.block_env, + &cache_db, + ) + .map_err(|e| BlockchainError::Message(e.to_string())) + }, + )??; + Ok(GethTrace::JS(trace)) + } + + /// Prove an account's existence or nonexistence in the state trie. + /// + /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address) + pub async fn prove_account_at( + &self, + address: Address, + keys: Vec, + block_request: Option>, + ) -> Result { + let block_number = block_request.as_ref().map(|r| r.block_number()); + + self.with_database_at(block_request, |block_db, _| { + trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); + + let mut builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); + + for (key, account) in trie_accounts(db) { + builder.add_leaf(key, &account); + } + + let _ = builder.root(); + + let proof = builder + .take_proof_nodes() + .into_nodes_sorted() + .into_iter() + .map(|(_, v)| v) + .collect(); + let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys); + + let account_proof = AccountProof { + address, + balance: account.info.balance, + nonce: account.info.nonce, + code_hash: account.info.code_hash, + storage_hash, + account_proof: proof, + storage_proof: keys + .into_iter() + .zip(storage_proofs) + .map(|(key, proof)| { + let storage_key: U256 = key.into(); + let value = account.storage.get(&storage_key).copied().unwrap_or_default(); + StorageProof { key: JsonStorageKey::Hash(key), value, proof } + }) + .collect(), + }; + + Ok(account_proof) + }) + .await? + } + + /// Rollback the chain to a common height. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { + let hash = common_block.header.hash_slow(); + + // Get the database at the common block + let common_state = { + let return_state_or_throw_err = + |db: Option<&StateDb>| -> Result, BlockchainError> { + let state_db = db.ok_or(BlockchainError::DataUnavailable)?; + let db_full = + state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + Ok(db_full.clone()) + }; + + let read_guard = self.states.upgradable_read(); + if let Some(db) = read_guard.get_state(&hash) { + return_state_or_throw_err(Some(db))? + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + return_state_or_throw_err(write_guard.get_on_disk_state(&hash))? + } + }; + + { + // Unwind the storage back to the common ancestor first + self.blockchain.storage.write().unwind_to(common_block.header.number(), hash); + + // Set environment back to common block + let mut env = self.env.write(); + env.evm_env.block_env.number = U256::from(common_block.header.number()); + env.evm_env.block_env.timestamp = U256::from(common_block.header.timestamp()); + env.evm_env.block_env.gas_limit = common_block.header.gas_limit(); + env.evm_env.block_env.difficulty = common_block.header.difficulty(); + env.evm_env.block_env.prevrandao = common_block.header.mix_hash(); + + self.time.reset(env.evm_env.block_env.timestamp.saturating_to()); + } + + { + // Collect block hashes before acquiring db lock to avoid holding blockchain storage + // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can + // access. + let block_hashes: Vec<_> = { + let storage = self.blockchain.storage.read(); + let min_block = common_block.header.number().saturating_sub(256); + storage + .hashes + .iter() + .filter(|(num, _)| **num >= min_block) + .map(|(&num, &hash)| (num, hash)) + .collect() + }; + + // Acquire db lock once for the entire restore operation to reduce lock churn. + let mut db = self.db.write().await; + db.clear(); + + // Insert account info before storage to prevent fork-mode RPC fetches after clear. + for (address, acc) in common_state { + db.insert_account(address, acc.info); + for (key, value) in acc.storage { + db.set_storage_at(address, key.into(), value.into())?; + } + } + + // Restore block hashes from blockchain storage (now unwound, contains only valid + // blocks). + for (block_num, hash) in block_hashes { + db.insert_block_hash(U256::from(block_num), hash); + } + } + + Ok(()) + } +} + +impl Backend { + /// Get the current state. + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + let at = self.env.read().evm_env.block_env.clone(); + let best_number = self.blockchain.storage.read().best_number; + let blocks = self.blockchain.storage.read().serialized_blocks(); + let transactions = self.blockchain.storage.read().serialized_transactions(); + let historical_states = if preserve_historical_states { + Some(self.states.write().serialized_states()) + } else { + None + }; + + let state = self.db.read().await.dump_state( + at, + best_number, + blocks, + transactions, + historical_states, + )?; + state.ok_or_else(|| { + RpcError::invalid_params("Dumping state not supported with the current configuration") + .into() + }) + } + + /// Write all chain data to serialized bytes buffer + pub async fn dump_state( + &self, + preserve_historical_states: bool, + ) -> Result { + let state = self.serialized_state(preserve_historical_states).await?; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(&serde_json::to_vec(&state).unwrap_or_default()) + .map_err(|_| BlockchainError::DataUnavailable)?; + Ok(encoder.finish().unwrap_or_default().into()) + } + + /// Apply [SerializableState] data to the backend storage. + pub async fn load_state(&self, state: SerializableState) -> Result { + // load the blocks and transactions into the storage + self.blockchain.storage.write().load_blocks(state.blocks.clone()); + self.blockchain.storage.write().load_transactions(state.transactions.clone()); + // reset the block env + if let Some(block) = state.block.clone() { + self.env.write().evm_env.block_env = block.clone(); + + // Set the current best block number. + // Defaults to block number for compatibility with existing state files. + let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); + + let best_number = state.best_block_number.unwrap_or(block.number.saturating_to()); + if let Some((number, hash)) = fork_num_and_hash { + trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); + // If the state.block_number is greater than the fork block number, set best number + // to the state block number. + // Ref: https://github.com/foundry-rs/foundry/issues/9539 + if best_number > number { + self.blockchain.storage.write().best_number = best_number; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else( + || { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + }, + )?; + self.blockchain.storage.write().best_hash = best_hash; + } else { + // If loading state file on a fork, set best number to the fork block number. + // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 + self.blockchain.storage.write().best_number = number; + self.blockchain.storage.write().best_hash = hash; + } + } else { + self.blockchain.storage.write().best_number = best_number; + + // Set the current best block hash; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + })?; + + self.blockchain.storage.write().best_hash = best_hash; + } + } + + if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number()) { + let header = &latest.header; + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + header.gas_used(), + header.gas_limit(), + header.base_fee_per_gas().unwrap_or_default(), + ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas().unwrap_or_default(), + header.blob_gas_used().unwrap_or_default(), + ); + + // update next base fee + self.fees.set_base_fee(next_block_base_fee); + + self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_excess_blob_gas, + get_blob_base_fee_update_fraction( + self.env.read().evm_env.cfg_env.chain_id, + header.timestamp, + ), + )); + } + + if !self.db.write().await.load_state(state.clone())? { + return Err(RpcError::invalid_params( + "Loading state not supported with the current configuration", + ) + .into()); + } + + if let Some(historical_states) = state.historical_states { + self.states.write().load_states(historical_states); + } + + Ok(true) + } + + /// Deserialize and add all chain data to the backend storage + pub async fn load_state_bytes(&self, buf: Bytes) -> Result { + let orig_buf = &buf.0[..]; + let mut decoder = GzDecoder::new(orig_buf); + let mut decoded_data = Vec::new(); + + let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() { + decoder + .read_to_end(decoded_data.as_mut()) + .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; + &decoded_data + } else { + &buf.0 + }) + .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; + + self.load_state(state).await + } + + /// Simulates the payload by executing the calls in request. + pub async fn simulate( + &self, + request: SimulatePayload, + block_request: Option>, + ) -> Result>, BlockchainError> { + self.with_database_at(block_request, |state, mut block_env| { + let SimulatePayload { + block_state_calls, + trace_transfers, + validation, + return_full_transactions, + } = request; + let mut cache_db = CacheDB::new(state); + let mut block_res = Vec::with_capacity(block_state_calls.len()); + + // execute the blocks + for block in block_state_calls { + let SimBlock { block_overrides, state_overrides, calls } = block; + let mut call_res = Vec::with_capacity(calls.len()); + let mut log_index = 0; + let mut gas_used = 0; + let mut transactions = Vec::with_capacity(calls.len()); + let mut logs= Vec::new(); + + // apply state overrides before executing the transactions + if let Some(state_overrides) = state_overrides { + apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + cache_db.apply_block_overrides(block_overrides, &mut block_env); + } + + // execute all calls in that block + for (req_idx, request) in calls.into_iter().enumerate() { + let fee_details = FeeDetails::new( + request.gas_price, + request.max_fee_per_gas, + request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, + )? + .or_zero_fees(); + + let (mut evm_env, tx_env) = self.build_call_env( + WithOtherFields::new(request.clone()), + fee_details, + block_env.clone(), + ); + + // Always disable EIP-3607 + evm_env.cfg_env.disable_eip3607 = true; + + if !validation { + evm_env.cfg_env.disable_base_fee = !validation; + evm_env.block_env.basefee = 0; + } + + let mut inspector = self.build_inspector(); + + // transact + let ResultAndState { result, state } = if trace_transfers { + // prepare inspector to capture transfer inside the evm so they are + // recorded and included in logs + inspector = inspector.with_transfers(); + let mut evm= self.new_eth_evm_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + ); + + trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); + evm.transact(tx_env)? + } else { + let mut evm = self.new_eth_evm_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + ); + trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); + evm.transact(tx_env)? + }; + trace!(target: "backend", ?result, ?request, "simulate call"); + + inspector.print_logs(); + if self.print_traces { + inspector.into_print_traces(self.call_trace_decoder.clone()); + } + + // commit the transaction + cache_db.commit(state); + gas_used += result.gas_used(); + + // create the transaction from a request + let from = request.from.unwrap_or_default(); + + let mut request = Into::::into(WithOtherFields::new(request)); + request.prep_for_submission(); + + let typed_tx = request.build_unsigned().map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))?; + + let tx = build_impersonated(typed_tx); + let tx_hash = tx.hash(); + let rpc_tx = transaction_build( + None, + MaybeImpersonatedTransaction::impersonated(tx, from), + None, + None, + Some(block_env.basefee), + ); + transactions.push(rpc_tx); + + let return_data = result.output().cloned().unwrap_or_default(); + let sim_res = SimCallResult { + return_data, + gas_used: result.gas_used(), + status: result.is_success(), + error: result.is_success().not().then(|| { + alloy_rpc_types::simulate::SimulateError { + code: -3200, + message: "execution failed".to_string(), + data: None, + } + }), + logs: result.clone() + .into_logs() + .into_iter() + .enumerate() + .map(|(idx, log)| Log { + inner: log, + block_number: Some(block_env.number.saturating_to()), + block_timestamp: Some(block_env.timestamp.saturating_to()), + transaction_index: Some(req_idx as u64), + log_index: Some((idx + log_index) as u64), + removed: false, + + block_hash: None, + transaction_hash: Some(tx_hash), + }) + .collect(), + }; + logs.extend(sim_res.logs.iter().map(|log| log.inner.clone())); + log_index += sim_res.logs.len(); + call_res.push(sim_res); + } + + let transactions_envelopes: Vec = transactions + .iter() + .map(|tx| AnyTxEnvelope::from(tx.clone())) + .collect(); + let header = Header { + logs_bloom: logs_bloom(logs.iter()), + transactions_root: calculate_transaction_root(&transactions_envelopes), + receipts_root: calculate_receipt_root(&transactions_envelopes), + parent_hash: Default::default(), + beneficiary: block_env.beneficiary, + state_root: Default::default(), + difficulty: Default::default(), + number: block_env.number.saturating_to(), + gas_limit: block_env.gas_limit, + gas_used, + timestamp: block_env.timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + base_fee_per_gas: Some(block_env.basefee), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + ..Default::default() + }; + let mut block = alloy_rpc_types::Block { + header: AnyRpcHeader { + hash: header.hash_slow(), + inner: header.into(), + total_difficulty: None, + size: None, + }, + uncles: vec![], + transactions: BlockTransactions::Full(transactions), + withdrawals: None, + }; + + if !return_full_transactions { + block.transactions.convert_to_hashes(); + } + + for res in &mut call_res { + res.logs.iter_mut().for_each(|log| { + log.block_hash = Some(block.header.hash); + }); + } + + let simulated_block = SimulatedBlock { + inner: AnyRpcBlock::new(WithOtherFields::new(block)), + calls: call_res, + }; + + // update block env + block_env.number += U256::from(1); + block_env.timestamp += U256::from(12); + block_env.basefee = simulated_block + .inner + .header + .next_block_base_fee(self.fees.base_fee_params()) + .unwrap_or_default(); - let mut evm = self.new_evm_with_inspector_ref(&cache_db, &env, &mut inspector); + block_res.push(simulated_block); + } - let result = evm - .transact(tx_env.clone()) - .map_err(|err| BlockchainError::Message(err.to_string()))?; + Ok(block_res) + }) + .await? + } - Ok(f(result, cache_db, inspector, tx_env.base, env)) - }; + /// returns all receipts for the given transactions + fn get_receipts( + &self, + tx_hashes: impl IntoIterator, + ) -> Vec { + let storage = self.blockchain.storage.read(); + let mut receipts = vec![]; - let read_guard = self.states.upgradable_read(); - if let Some(state) = read_guard.get_state(&block.header.parent_hash) { - trace(state) - } else { - let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); - let state = write_guard - .get_on_disk_state(&block.header.parent_hash) - .ok_or(BlockchainError::BlockNotFound)?; - trace(state) + for hash in tx_hashes { + if let Some(tx) = storage.transactions.get(&hash) { + receipts.push(tx.receipt.clone()); + } } + + receipts } - /// Traces the transaction with the js tracer - #[cfg(feature = "js-tracer")] - pub async fn trace_tx_with_js_tracer( + /// Returns the traces for the given transaction + pub async fn debug_trace_transaction( &self, hash: B256, - code: String, opts: GethDebugTracingOptions, ) -> Result { - let GethDebugTracingOptions { tracer_config, .. } = opts; - let config = tracer_config.into_json(); - let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) - .map_err(|err| BlockchainError::Message(err.to_string()))?; - let trace = self.replay_tx_with_inspector( - hash, - inspector, - |result, cache_db, mut inspector, tx_env, env| { - inspector - .json_result( - result, - &alloy_evm::IntoTxEnv::into_tx_env(tx_env), - &env.evm_env.block_env, - &cache_db, - ) - .map_err(|e| BlockchainError::Message(e.to_string())) - }, - )??; - Ok(GethTrace::JS(trace)) + #[cfg(feature = "js-tracer")] + if let Some(tracer_type) = opts.tracer.as_ref() + && tracer_type.is_js() + { + return self + .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone()) + .await; + } + + if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await { + return trace; + } + + if let Some(fork) = self.get_fork() { + return Ok(fork.debug_trace_transaction(hash, opts).await?); + } + + Ok(GethTrace::Default(Default::default())) } fn geth_trace( @@ -3327,18 +4069,6 @@ impl Backend { Ok(None) } - /// Returns all receipts of the block - pub fn mined_receipts(&self, hash: B256) -> Option> { - let block = self.mined_block_by_hash(hash)?; - let mut receipts = Vec::new(); - let storage = self.blockchain.storage.read(); - for tx in block.transactions.hashes() { - let receipt = storage.transactions.get(&tx)?.receipt.clone(); - receipts.push(receipt); - } - Some(receipts) - } - /// Returns all transaction receipts of the block pub fn mined_block_receipts(&self, id: impl Into) -> Option> { let mut receipts = Vec::new(); @@ -3439,187 +4169,11 @@ impl Backend { Ok(None) } - - /// Prove an account's existence or nonexistence in the state trie. - /// - /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address) - pub async fn prove_account_at( - &self, - address: Address, - keys: Vec, - block_request: Option, - ) -> Result { - let block_number = block_request.as_ref().map(|r| r.block_number()); - - self.with_database_at(block_request, |block_db, _| { - trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); - let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; - let account = db.get(&address).cloned().unwrap_or_default(); - - let mut builder = HashBuilder::default() - .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); - - for (key, account) in trie_accounts(db) { - builder.add_leaf(key, &account); - } - - let _ = builder.root(); - - let proof = builder - .take_proof_nodes() - .into_nodes_sorted() - .into_iter() - .map(|(_, v)| v) - .collect(); - let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys); - - let account_proof = AccountProof { - address, - balance: account.info.balance, - nonce: account.info.nonce, - code_hash: account.info.code_hash, - storage_hash, - account_proof: proof, - storage_proof: keys - .into_iter() - .zip(storage_proofs) - .map(|(key, proof)| { - let storage_key: U256 = key.into(); - let value = account.storage.get(&storage_key).copied().unwrap_or_default(); - StorageProof { key: JsonStorageKey::Hash(key), value, proof } - }) - .collect(), - }; - - Ok(account_proof) - }) - .await? - } - - /// Reorg the chain to a common height and execute blocks to build new chain. - /// - /// The state of the chain is rewound using `rewind` to the common block, including the db, - /// storage, and env. - /// - /// Finally, `do_mine_block` is called to create the new chain. - pub async fn reorg( - &self, - depth: u64, - tx_pairs: HashMap>>, - common_block: Block, - ) -> Result<(), BlockchainError> { - self.rollback(common_block).await?; - // Create the new reorged chain, filling the blocks with transactions if supplied - for i in 0..depth { - let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); - let outcome = self.do_mine_block(to_be_mined).await; - node_info!( - " Mined reorg block number {}. With {} valid txs and with invalid {} txs", - outcome.block_number, - outcome.included.len(), - outcome.invalid.len() - ); - } - - Ok(()) - } - - /// Rollback the chain to a common height. - /// - /// The state of the chain is rewound using `rewind` to the common block, including the db, - /// storage, and env. - pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { - let hash = common_block.header.hash_slow(); - - // Get the database at the common block - let common_state = { - let return_state_or_throw_err = - |db: Option<&StateDb>| -> Result, BlockchainError> { - let state_db = db.ok_or(BlockchainError::DataUnavailable)?; - let db_full = - state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; - Ok(db_full.clone()) - }; - - let read_guard = self.states.upgradable_read(); - if let Some(db) = read_guard.get_state(&hash) { - return_state_or_throw_err(Some(db))? - } else { - let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); - return_state_or_throw_err(write_guard.get_on_disk_state(&hash))? - } - }; - - { - // Unwind the storage back to the common ancestor first - self.blockchain.storage.write().unwind_to(common_block.header.number(), hash); - - // Set environment back to common block - let mut env = self.env.write(); - env.evm_env.block_env.number = U256::from(common_block.header.number()); - env.evm_env.block_env.timestamp = U256::from(common_block.header.timestamp()); - env.evm_env.block_env.gas_limit = common_block.header.gas_limit(); - env.evm_env.block_env.difficulty = common_block.header.difficulty(); - env.evm_env.block_env.prevrandao = common_block.header.mix_hash(); - - self.time.reset(env.evm_env.block_env.timestamp.saturating_to()); - } - - { - // Collect block hashes before acquiring db lock to avoid holding blockchain storage - // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can - // access. - let block_hashes: Vec<_> = { - let storage = self.blockchain.storage.read(); - let min_block = common_block.header.number().saturating_sub(256); - storage - .hashes - .iter() - .filter(|(num, _)| **num >= min_block) - .map(|(&num, &hash)| (num, hash)) - .collect() - }; - - // Acquire db lock once for the entire restore operation to reduce lock churn. - let mut db = self.db.write().await; - db.clear(); - - // Insert account info before storage to prevent fork-mode RPC fetches after clear. - for (address, acc) in common_state { - db.insert_account(address, acc.info); - for (key, value) in acc.storage { - db.set_storage_at(address, key.into(), value.into())?; - } - } - - // Restore block hashes from blockchain storage (now unwound, contains only valid - // blocks). - for (block_num, hash) in block_hashes { - db.insert_block_hash(U256::from(block_num), hash); - } - } - - Ok(()) - } -} - -/// Constructs a `BlockEnv` from a block header. -fn block_env_from_header(header: &impl BlockHeader) -> BlockEnv { - BlockEnv { - number: U256::from(header.number()), - beneficiary: header.beneficiary(), - timestamp: U256::from(header.timestamp()), - difficulty: header.difficulty(), - prevrandao: header.mix_hash(), - basefee: header.base_fee_per_gas().unwrap_or_default(), - gas_limit: header.gas_limit(), - ..Default::default() - } } /// Get max nonce from transaction pool by address. fn get_pool_transactions_nonce( - pool_transactions: &[Arc], + pool_transactions: &[Arc>], address: Address, ) -> Option { if let Some(highest_nonce) = pool_transactions @@ -3635,22 +4189,21 @@ fn get_pool_transactions_nonce( } #[async_trait::async_trait] -impl TransactionValidator for Backend { +impl TransactionValidator for Backend { async fn validate_pool_transaction( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, ) -> Result<(), BlockchainError> { let address = *tx.sender(); let account = self.get_account(address).await?; - let env = self.next_env(); - Ok(self.validate_pool_transaction_for(tx, &account, &env)?) + Ok(self.validate_pool_transaction_for(tx, &account, &self.next_env().evm_env)?) } fn validate_pool_transaction_for( &self, - pending: &PendingTransaction, + pending: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; @@ -3659,9 +4212,7 @@ impl TransactionValidator for Backend { if chain_id.to::() != tx_chain_id { if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() { // - if env.evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON - && tx.chain_id().is_none() - { + if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); return Err(InvalidTransactionError::IncompatibleEIP155); } @@ -3681,7 +4232,7 @@ impl TransactionValidator for Backend { } // EIP-4844 structural validation - if env.evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() { + if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() { // Heavy (blob validation) checks let blob_tx = match tx.as_ref() { FoundryTxEnvelope::Eip4844(tx) => tx.tx(), @@ -3710,9 +4261,8 @@ impl TransactionValidator for Backend { } // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit - if env.evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create { - let max_initcode_size = env - .evm_env + if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create { + let max_initcode_size = evm_env .cfg_env .limit_contract_code_size .map(|limit| limit.saturating_mul(2)) @@ -3731,8 +4281,8 @@ impl TransactionValidator for Backend { } // Check tx gas limit against block gas limit, if block gas limit is set. - if !env.evm_env.cfg_env.disable_block_gas_limit - && tx.gas_limit() > env.evm_env.block_env.gas_limit + if !evm_env.cfg_env.disable_block_gas_limit + && tx.gas_limit() > evm_env.block_env.gas_limit { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { @@ -3741,8 +4291,8 @@ impl TransactionValidator for Backend { } // Check tx gas limit against tx gas limit cap (Osaka hard fork and later). - if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() - && tx.gas_limit() > env.evm_env.cfg_env().tx_gas_limit_cap() + if evm_env.cfg_env.tx_gas_limit_cap.is_none() + && tx.gas_limit() > evm_env.cfg_env().tx_gas_limit_cap() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { @@ -3751,9 +4301,9 @@ impl TransactionValidator for Backend { } // EIP-1559 fee validation (London hard fork and later). - if env.evm_env.cfg_env.spec >= SpecId::LONDON { - if tx.max_fee_per_gas() < env.evm_env.block_env.basefee.into() && !is_deposit_tx { - warn!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), env.evm_env.block_env.basefee); + if evm_env.cfg_env.spec >= SpecId::LONDON { + if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx { + warn!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee); return Err(InvalidTransactionError::FeeCapTooLow); } @@ -3767,10 +4317,10 @@ impl TransactionValidator for Backend { } // EIP-4844 blob fee validation - if env.evm_env.cfg_env.spec >= SpecId::CANCUN + if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() - && let Some(blob_gas_and_price) = &env.evm_env.block_env.blob_excess_gas_and_price + && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice { warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); @@ -3819,11 +4369,11 @@ impl TransactionValidator for Backend { fn validate_for( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError> { - self.validate_pool_transaction_for(tx, account, env)?; + self.validate_pool_transaction_for(tx, account, evm_env)?; if tx.nonce() > account.nonce { return Err(InvalidTransactionError::NonceTooHigh); } @@ -3834,7 +4384,7 @@ impl TransactionValidator for Backend { /// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data pub fn transaction_build( tx_hash: Option, - eth_transaction: MaybeImpersonatedTransaction, + eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, base_fee: Option, diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 1efa73501d11d..51f8000bd44bf 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -5,13 +5,13 @@ use crate::eth::{ MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates, SerializableTransaction, StateDb, }, - env::Env, mem::cache::DiskStateCache, }, pool::transactions::PoolTransaction, }; use alloy_consensus::{BlockHeader, Header, constants::EMPTY_WITHDRAWALS}; use alloy_eips::eip7685::EMPTY_REQUESTS_HASH; +use alloy_evm::EvmEnv; use alloy_network::Network; use alloy_primitives::{ B256, Bytes, U256, @@ -280,32 +280,32 @@ pub struct BlockchainStorage { impl BlockchainStorage { /// Creates a new storage with a genesis block pub fn new( - env: &Env, - spec_id: SpecId, + evm_env: &EvmEnv, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { - let is_shanghai = spec_id >= SpecId::SHANGHAI; - let is_cancun = spec_id >= SpecId::CANCUN; - let is_prague = spec_id >= SpecId::PRAGUE; + let is_shanghai = *evm_env.spec_id() >= SpecId::SHANGHAI; + let is_cancun = *evm_env.spec_id() >= SpecId::CANCUN; + let is_prague = *evm_env.spec_id() >= SpecId::PRAGUE; // create a dummy genesis block let header = Header { timestamp, base_fee_per_gas: base_fee, - gas_limit: env.evm_env.block_env.gas_limit, - beneficiary: env.evm_env.block_env.beneficiary, - difficulty: env.evm_env.block_env.difficulty, - blob_gas_used: env.evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0), - excess_blob_gas: env.evm_env.block_env.blob_excess_gas(), + gas_limit: evm_env.block_env.gas_limit, + beneficiary: evm_env.block_env.beneficiary, + difficulty: evm_env.block_env.difficulty, + blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas: evm_env.block_env.blob_excess_gas(), number: genesis_number, parent_beacon_block_root: is_cancun.then_some(Default::default()), withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), ..Default::default() }; - let block = create_block(header, Vec::::new()); + let block = + create_block(header, Vec::>::new()); let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; let best_number = genesis_number; @@ -471,16 +471,14 @@ pub struct Blockchain { impl Blockchain { /// Creates a new storage with a genesis block pub fn new( - env: &Env, - spec_id: SpecId, + evm_env: &EvmEnv, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new( - env, - spec_id, + evm_env, base_fee, timestamp, genesis_number, @@ -521,7 +519,7 @@ impl Blockchain { } /// Represents the outcome of mining a new block -pub struct MinedBlockOutcome { +pub struct MinedBlockOutcome { /// The block that was mined pub block_number: u64, /// All transactions included in the block @@ -724,7 +722,7 @@ mod tests { let header = Header { gas_limit: 123456, ..Default::default() }; let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; - let tx: MaybeImpersonatedTransaction = + let tx: MaybeImpersonatedTransaction = FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into(); let block = create_block(header.clone(), vec![tx.clone()]); let block_hash = block.header.hash_slow(); diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index 78a8579094158..858602787ca60 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -1,16 +1,13 @@ //! Support for validating transactions at certain stages -use crate::eth::{ - backend::env::Env, - error::{BlockchainError, InvalidTransactionError}, -}; +use crate::eth::error::{BlockchainError, InvalidTransactionError}; +use alloy_evm::EvmEnv; use anvil_core::eth::transaction::PendingTransaction; -use foundry_primitives::FoundryTxEnvelope; use revm::state::AccountInfo; /// A trait for validating transactions #[async_trait::async_trait] -pub trait TransactionValidator { +pub trait TransactionValidator { /// Validates the transaction's validity when it comes to nonce, payment /// /// This is intended to be checked before the transaction makes it into the pool and whether it @@ -25,7 +22,7 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -35,6 +32,6 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index ab3904409776b..f6dad33b2d336 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -168,9 +168,9 @@ impl FeeManager { self.blob_params().calc_blob_fee(self.blob_excess_gas_and_price.read().excess_blob_gas) } - /// Calculates the next block blob excess gas, using the provided parent blob gas used and - /// parent blob excess gas - pub fn get_next_block_blob_excess_gas(&self, blob_gas_used: u64, blob_excess_gas: u64) -> u64 { + /// Calculates the next block blob excess gas, using the provided parent blob excess gas and + /// parent blob gas used + pub fn get_next_block_blob_excess_gas(&self, blob_excess_gas: u64, blob_gas_used: u64) -> u64 { self.blob_params().next_block_excess_blob_gas_osaka( blob_excess_gas, blob_gas_used, diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index 74c960c795b1f..723adbbab76c4 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -2,7 +2,6 @@ use crate::eth::pool::{Pool, transactions::PoolTransaction}; use alloy_primitives::TxHash; -use foundry_primitives::FoundryTxEnvelope; use futures::{ channel::mpsc::Receiver, stream::{Fuse, StreamExt}, @@ -17,7 +16,7 @@ use std::{ }; use tokio::time::{Interval, MissedTickBehavior}; -pub struct Miner { +pub struct Miner { /// The mode this miner currently operates in mode: Arc>, /// used for task wake up when the mining mode was forcefully changed diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 8f2040f37a240..4da86933020ae 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -19,10 +19,11 @@ use alloy_rpc_types::{ parity::{Action, CreateAction, CreateOutput, TraceOutput}, }, }; +use foundry_primitives::FoundryNetwork; use futures::future::join_all; use itertools::Itertools; -impl EthApi { +impl EthApi { /// Otterscan currently requires this endpoint, even though it's not part of the `ots_*`. /// Ref: /// diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 6a692d1d5a360..b2f2e09fbe864 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -40,7 +40,6 @@ use alloy_consensus::Transaction; use alloy_primitives::{Address, TxHash}; use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; -use foundry_primitives::FoundryTxEnvelope; use futures::channel::mpsc::{Receiver, Sender, channel}; use parking_lot::{Mutex, RwLock}; use std::{collections::VecDeque, fmt, sync::Arc}; @@ -48,7 +47,7 @@ use std::{collections::VecDeque, fmt, sync::Arc}; pub mod transactions; /// Transaction pool that performs validation. -pub struct Pool { +pub struct Pool { /// processes all pending transactions inner: RwLock>, /// listeners for new ready transactions @@ -226,7 +225,7 @@ impl Pool { /// /// Contains all transactions that are ready to be executed #[derive(Debug)] -struct PoolInner { +struct PoolInner { ready_transactions: ReadyTransactions, pending_transactions: PendingTransactions, } @@ -434,7 +433,7 @@ impl PoolInner { } /// Represents the outcome of a prune -pub struct PruneResult { +pub struct PruneResult { /// a list of added transactions that a pruned marker satisfied pub promoted: Vec>, /// all transactions that failed to be promoted and now are discarded @@ -463,7 +462,7 @@ impl fmt::Debug for PruneResult { } #[derive(Clone, Debug)] -pub struct ReadyTransaction { +pub struct ReadyTransaction { /// the hash of the submitted transaction hash: TxHash, /// transactions promoted to the ready queue @@ -486,7 +485,7 @@ impl ReadyTransaction { } #[derive(Clone, Debug)] -pub enum AddedTransaction { +pub enum AddedTransaction { /// transaction was successfully added and being processed Ready(ReadyTransaction), /// Transaction was successfully added but not yet queued for processing diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 81059c7b48d20..d6acb34f5e002 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -73,7 +73,7 @@ pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] -pub struct PoolTransaction { +pub struct PoolTransaction { /// the pending eth transaction pub pending_transaction: PendingTransaction, /// Markers required by the transaction @@ -128,7 +128,7 @@ impl fmt::Debug for PoolTransaction { } } -impl TryFrom for PoolTransaction { +impl TryFrom for PoolTransaction { type Error = eyre::Error; fn try_from(value: AnyRpcTransaction) -> Result { let typed_transaction = FoundryTxEnvelope::try_from(value)?; @@ -146,7 +146,7 @@ impl TryFrom for PoolTransaction { /// /// Keeps a set of transactions that are waiting for other transactions #[derive(Clone, Debug)] -pub struct PendingTransactions { +pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, /// mapping of the markers of a transaction to the hash of the transaction @@ -289,7 +289,7 @@ impl PendingTransactions { /// A transaction in the pool #[derive(Clone)] -pub struct PendingPoolTransaction { +pub struct PendingPoolTransaction { pub transaction: Arc>, /// markers required and have not been satisfied yet by other transactions in the pool pub missing_markers: HashSet, @@ -337,7 +337,7 @@ impl fmt::Debug for PendingPoolTransaction { } } -pub struct TransactionsIterator { +pub struct TransactionsIterator { all: HashMap>, awaiting: HashMap)>, independent: BTreeSet>, @@ -394,7 +394,7 @@ impl Iterator for TransactionsIterator { /// transactions that are ready to be included in a block. #[derive(Clone, Debug)] -pub struct ReadyTransactions { +pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// /// this way we can determine when transactions where submitted to the pool @@ -704,7 +704,7 @@ impl ReadyTransactions { /// A reference to a transaction in the pool #[derive(Debug)] -pub struct PoolTransactionRef { +pub struct PoolTransactionRef { /// actual transaction pub transaction: Arc>, /// identifier used to internally compare the transaction in the pool @@ -741,7 +741,7 @@ impl Ord for PoolTransactionRef { } #[derive(Debug)] -pub struct ReadyTransaction { +pub struct ReadyTransaction { /// ref to the actual transaction pub transaction: PoolTransactionRef, /// tracks the transactions that get unlocked by this transaction diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index 9c4de9ae7b519..c576cb2050e40 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -4,6 +4,8 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::ToRpcResponseResult}, pubsub::filter_logs, }; +use alloy_consensus::TxReceipt; +use alloy_network::Network; use alloy_primitives::{TxHash, map::HashMap}; use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; @@ -11,7 +13,6 @@ use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; -use foundry_primitives::FoundryNetwork; use futures::{Stream, StreamExt, channel::mpsc::Receiver}; use std::{ pin::Pin, @@ -22,23 +23,34 @@ use std::{ use tokio::sync::Mutex; /// Type alias for filters identified by their id and their expiration timestamp -type FilterMap = Arc>>; +type FilterMap = Arc, Instant)>>>; /// timeout after which to remove an active filter if it wasn't polled since then pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters -#[derive(Clone, Debug)] -pub struct Filters { +pub struct Filters { /// all currently active filters - active_filters: FilterMap, + active_filters: FilterMap, /// How long we keep a live the filter after the last poll keepalive: Duration, } -impl Filters { +impl Clone for Filters { + fn clone(&self) -> Self { + Self { active_filters: self.active_filters.clone(), keepalive: self.keepalive } + } +} + +impl std::fmt::Debug for Filters { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Filters").field("keepalive", &self.keepalive).finish_non_exhaustive() + } +} + +impl Filters { /// Adds a new `EthFilter` to the set - pub async fn add_filter(&self, filter: EthFilter) -> String { + pub async fn add_filter(&self, filter: EthFilter) -> String { let id = new_id(); trace!(target: "node::filter", "Adding new filter id {}", id); let mut filters = self.active_filters.lock().await; @@ -46,26 +58,6 @@ impl Filters { id } - pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { - { - let mut filters = self.active_filters.lock().await; - if let Some((filter, deadline)) = filters.get_mut(id) { - let resp = filter - .next() - .await - .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); - *deadline = self.next_deadline(); - return resp; - } - } - warn!(target: "node::filter", "No filter found for {}", id); - ResponseResult::error(RpcError { - code: ErrorCode::ServerError(-32000), - message: "filter not found".into(), - data: None, - }) - } - /// Returns the original `Filter` of an `eth_newFilter` pub async fn get_log_filter(&self, id: &str) -> Option { let filters = self.active_filters.lock().await; @@ -76,7 +68,7 @@ impl Filters { } /// Removes the filter identified with the `id` - pub async fn uninstall_filter(&self, id: &str) -> Option { + pub async fn uninstall_filter(&self, id: &str) -> Option> { trace!(target: "node::filter", "Uninstalling filter id {}", id); self.active_filters.lock().await.remove(id).map(|(f, _)| f) } @@ -106,7 +98,32 @@ impl Filters { } } -impl Default for Filters { +impl Filters +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ + pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { + { + let mut filters = self.active_filters.lock().await; + if let Some((filter, deadline)) = filters.get_mut(id) { + let resp = filter + .next() + .await + .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); + *deadline = self.next_deadline(); + return resp; + } + } + warn!(target: "node::filter", "No filter found for {}", id); + ResponseResult::error(RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }) + } +} + +impl Default for Filters { fn default() -> Self { Self { active_filters: Arc::new(Default::default()), @@ -121,14 +138,26 @@ fn new_id() -> String { } /// Represents a poll based filter -#[derive(Debug)] -pub enum EthFilter { - Logs(Box), +pub enum EthFilter { + Logs(Box>), Blocks(NewBlockNotifications), PendingTransactions(Receiver), } -impl Stream for EthFilter { +impl std::fmt::Debug for EthFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Logs(_) => f.debug_tuple("Logs").finish(), + Self::Blocks(_) => f.debug_tuple("Blocks").finish(), + Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(), + } + } +} + +impl Stream for EthFilter +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -154,12 +183,11 @@ impl Stream for EthFilter { } /// Listens for new blocks and matching logs emitted in that block -#[derive(Debug)] -pub struct LogsFilter { +pub struct LogsFilter { /// listener for new blocks pub blocks: NewBlockNotifications, /// accessor for block storage - pub storage: StorageInfo, + pub storage: StorageInfo, /// matcher with all provided filter params pub filter: FilteredParams, /// existing logs that matched the filter when the listener was installed @@ -168,7 +196,16 @@ pub struct LogsFilter { pub historic: Option>, } -impl LogsFilter { +impl std::fmt::Debug for LogsFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive() + } +} + +impl LogsFilter +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ /// Returns all the logs since the last time this filter was polled pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index af0d05657e476..db97548c12006 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -23,9 +23,10 @@ use alloy_eips::eip7840::BlobParams; use alloy_primitives::{Address, U256}; use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; -use eyre::Result; +use eyre::{Result, WrapErr}; use foundry_common::provider::{ProviderBuilder, RetryProvider}; pub use foundry_evm::hardfork::EthereumHardfork; +use foundry_primitives::FoundryNetwork; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; use revm::primitives::hardfork::SpecId; @@ -111,7 +112,7 @@ extern crate tracing; /// # Ok(()) /// # } /// ``` -pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { +pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { try_spawn(config).await.expect("failed to spawn node") } @@ -135,11 +136,17 @@ pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { /// # Ok(()) /// # } /// ``` -pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { +pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); - let backend = Arc::new(config.setup().await?); + let backend = config.setup::().await?; + + if let Some(state) = config.init_state.clone() { + backend.load_state(state).await.wrap_err("failed to load init state")?; + } + + let backend = Arc::new(backend); if config.enable_auto_impersonate { backend.auto_impersonate_account(true); diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index c62427eb9a4c8..8a72447333543 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -3,12 +3,11 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, }; use alloy_consensus::{BlockHeader, TxReceipt}; -use alloy_network::AnyRpcTransaction; +use alloy_network::{AnyRpcTransaction, Network}; use alloy_primitives::{B256, TxHash}; use alloy_rpc_types::{FilteredParams, Log, Transaction, pubsub::SubscriptionResult}; use anvil_core::eth::{block::Block, subscription::SubscriptionId}; use anvil_rpc::{request::Version, response::ResponseResult}; -use foundry_primitives::FoundryNetwork; use futures::{Stream, StreamExt, channel::mpsc::Receiver, ready}; use serde::Serialize; use std::{ @@ -19,16 +18,27 @@ use std::{ use tokio::sync::mpsc::UnboundedReceiver; /// Listens for new blocks and matching logs emitted in that block -#[derive(Debug)] -pub struct LogsSubscription { +pub struct LogsSubscription { pub blocks: NewBlockNotifications, - pub storage: StorageInfo, + pub storage: StorageInfo, pub filter: FilteredParams, pub queued: VecDeque, pub id: SubscriptionId, } -impl LogsSubscription { +impl std::fmt::Debug for LogsSubscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LogsSubscription") + .field("filter", &self.filter) + .field("id", &self.id) + .finish_non_exhaustive() + } +} + +impl LogsSubscription +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if let Some(log) = self.queued.pop_front() { @@ -85,15 +95,28 @@ pub struct EthSubscriptionParams { } /// Represents an ethereum Websocket subscription -#[derive(Debug)] -pub enum EthSubscription { - Logs(Box), - Header(NewBlockNotifications, StorageInfo, SubscriptionId), +pub enum EthSubscription { + Logs(Box>), + Header(NewBlockNotifications, StorageInfo, SubscriptionId), PendingTransactions(Receiver, SubscriptionId), FullPendingTransactions(UnboundedReceiver, SubscriptionId), } -impl EthSubscription { +impl std::fmt::Debug for EthSubscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Logs(_) => f.debug_tuple("Logs").finish(), + Self::Header(..) => f.debug_tuple("Header").finish(), + Self::PendingTransactions(..) => f.debug_tuple("PendingTransactions").finish(), + Self::FullPendingTransactions(..) => f.debug_tuple("FullPendingTransactions").finish(), + } + } +} + +impl EthSubscription +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { Self::Logs(listener) => listener.poll(cx), @@ -136,7 +159,10 @@ impl EthSubscription { } } -impl Stream for EthSubscription { +impl Stream for EthSubscription +where + N::ReceiptEnvelope: TxReceipt + Clone, +{ type Item = serde_json::Value; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { diff --git a/crates/anvil/src/server/beacon/handlers.rs b/crates/anvil/src/server/beacon/handlers.rs index 9310a7960293c..7f6bf2838b9fc 100644 --- a/crates/anvil/src/server/beacon/handlers.rs +++ b/crates/anvil/src/server/beacon/handlers.rs @@ -12,6 +12,7 @@ use axum::{ http::HeaderMap, response::{IntoResponse, Response}, }; +use foundry_primitives::FoundryNetwork; use ssz::Encode; use std::{collections::HashMap, str::FromStr as _}; @@ -21,7 +22,7 @@ use std::{collections::HashMap, str::FromStr as _}; /// /// GET /eth/v1/beacon/blob_sidecars/{block_id} pub async fn handle_get_blob_sidecars( - State(_api): State, + State(_api): State>, Path(_block_id): Path, Query(_params): Query>, ) -> Response { @@ -34,7 +35,7 @@ pub async fn handle_get_blob_sidecars( /// GET /eth/v1/beacon/blobs/{block_id} pub async fn handle_get_blobs( headers: HeaderMap, - State(api): State, + State(api): State>, Path(block_id): Path, Query(versioned_hashes): Query>, ) -> Response { @@ -43,12 +44,31 @@ pub async fn handle_get_blobs( return BeaconError::invalid_block_id(block_id).into_response(); }; - // Parse indices from query parameters - // Supports both comma-separated (?indices=1,2,3) and repeated parameters (?indices=1&indices=2) - let versioned_hashes: Vec = versioned_hashes - .get("versioned_hashes") - .map(|s| s.split(',').filter_map(|hash| B256::from_str(hash.trim()).ok()).collect()) - .unwrap_or_default(); + // Parse versioned hashes from query parameters + // Supports comma-separated format: ?versioned_hashes=0x...,0x... + let versioned_hashes: Vec = match versioned_hashes.get("versioned_hashes") { + Some(s) => { + let mut hashes = Vec::new(); + for hash in s.split(',') { + let hash = hash.trim(); + if hash.is_empty() { + continue; + } + match B256::from_str(hash) { + Ok(h) => hashes.push(h), + Err(_) => { + return BeaconError::new( + super::error::BeaconErrorCode::BadRequest, + format!("Invalid versioned hash: {hash}"), + ) + .into_response(); + } + } + } + hashes + } + None => Vec::new(), + }; // Get the blob sidecars using existing EthApi logic match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) { @@ -74,7 +94,7 @@ pub async fn handle_get_blobs( /// Only returns the `genesis_time`, other fields are set to zero. /// /// GET /eth/v1/beacon/genesis -pub async fn handle_get_genesis(State(api): State) -> Response { +pub async fn handle_get_genesis(State(api): State>) -> Response { match api.anvil_get_genesis_time() { Ok(genesis_time) => Json(GenesisResponse { data: GenesisData { diff --git a/crates/anvil/src/server/beacon/mod.rs b/crates/anvil/src/server/beacon/mod.rs index 9100186d436d5..331062316a217 100644 --- a/crates/anvil/src/server/beacon/mod.rs +++ b/crates/anvil/src/server/beacon/mod.rs @@ -3,13 +3,14 @@ use axum::{Router, routing::get}; use crate::eth::EthApi; +use foundry_primitives::FoundryNetwork; mod error; mod handlers; mod utils; /// Configures an [`axum::Router`] that handles Beacon REST API calls. -pub fn router(api: EthApi) -> Router { +pub fn router(api: EthApi) -> Router { Router::new() .route("/eth/v1/beacon/blob_sidecars/{block_id}", get(handlers::handle_get_blob_sidecars)) .route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs)) diff --git a/crates/anvil/src/server/mod.rs b/crates/anvil/src/server/mod.rs index 9d8238a56b8b5..1f6d78d2a9466 100644 --- a/crates/anvil/src/server/mod.rs +++ b/crates/anvil/src/server/mod.rs @@ -4,6 +4,7 @@ use crate::{EthApi, IpcTask}; use anvil_server::{ServerConfig, ipc::IpcEndpoint}; use axum::Router; +use foundry_primitives::FoundryNetwork; use futures::StreamExt; use rpc_handlers::{HttpEthRpcHandler, PubSubEthRpcHandler}; use std::{io, net::SocketAddr, pin::pin}; @@ -18,7 +19,7 @@ mod rpc_handlers; /// future that runs it. pub async fn serve( addr: SocketAddr, - api: EthApi, + api: EthApi, config: ServerConfig, ) -> io::Result>> { let tcp_listener = TcpListener::bind(addr).await?; @@ -28,7 +29,7 @@ pub async fn serve( /// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. pub async fn serve_on( tcp_listener: TcpListener, - api: EthApi, + api: EthApi, config: ServerConfig, ) -> io::Result<()> { axum::serve(tcp_listener, router(api, config).into_make_service()).await @@ -36,7 +37,7 @@ pub async fn serve_on( /// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS, /// and Beacon REST API calls. -pub fn router(api: EthApi, config: ServerConfig) -> Router { +pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); let ws = PubSubEthRpcHandler::new(api.clone()); @@ -56,12 +57,12 @@ pub fn router(api: EthApi, config: ServerConfig) -> Router { /// /// Panics if setting up the IPC connection was unsuccessful. #[track_caller] -pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { +pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { try_spawn_ipc(api, path).expect("failed to establish ipc connection") } /// Launches an ipc server at the given path in a new task. -pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { +pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { let handler = PubSubEthRpcHandler::new(api); let ipc = IpcEndpoint::new(handler, path); let incoming = ipc.incoming()?; diff --git a/crates/anvil/src/server/rpc_handlers.rs b/crates/anvil/src/server/rpc_handlers.rs index e880e5e5628a9..321970bd03bcf 100644 --- a/crates/anvil/src/server/rpc_handlers.rs +++ b/crates/anvil/src/server/rpc_handlers.rs @@ -11,17 +11,18 @@ use alloy_rpc_types::{ use anvil_core::eth::{EthPubSub, EthRequest, EthRpcCall, subscription::SubscriptionId}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; +use foundry_primitives::FoundryNetwork; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] pub struct HttpEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub fn new(api: EthApi) -> Self { Self { api } } } @@ -39,12 +40,12 @@ impl RpcHandler for HttpEthRpcHandler { #[derive(Clone)] pub struct PubSubEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl PubSubEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub fn new(api: EthApi) -> Self { Self { api } } @@ -129,7 +130,7 @@ impl PubSubEthRpcHandler { impl PubSubRpcHandler for PubSubEthRpcHandler { type Request = EthRpcCall; type SubscriptionId = SubscriptionId; - type Subscription = EthSubscription; + type Subscription = EthSubscription; async fn on_request(&self, request: Self::Request, cx: PubSubContext) -> ResponseResult { trace!(target: "rpc", "received pubsub request {:?}", request); diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index c0071505157ae..83edd774951c0 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -3,6 +3,7 @@ use crate::{ NodeResult, eth::{ + backend::validate::TransactionValidator, fees::FeeHistoryService, miner::Miner, pool::{Pool, transactions::PoolTransaction}, @@ -10,7 +11,8 @@ use crate::{ filter::Filters, mem::{Backend, storage::MinedBlockOutcome}, }; -use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; +use alloy_network::Network; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; use futures::{FutureExt, Stream, StreamExt}; use std::{ collections::VecDeque, @@ -26,28 +28,32 @@ use tokio::{task::JoinHandle, time::Interval}; /// transactions for the next block, then those transactions are handed off to the backend to /// construct a new block, if all transactions were successfully included in a new block they get /// purged from the `Pool`. -pub struct NodeService { +pub struct NodeService { /// The pool that holds all transactions. - pool: Arc>, + pool: Arc>, /// Creates new blocks. - block_producer: BlockProducer, + block_producer: BlockProducer, /// The miner responsible to select transactions from the `pool`. - miner: Miner, + miner: Miner, /// Maintenance task for fee history related tasks. fee_history: FeeHistoryService, /// Tracks all active filters - filters: Filters, + filters: Filters, /// The interval at which to check for filters that need to be evicted filter_eviction_interval: Interval, } -impl NodeService { +impl NodeService +where + Backend: TransactionValidator, + N: Network, +{ pub fn new( - pool: Arc, - backend: Arc>, - miner: Miner, + pool: Arc>, + backend: Arc>, + miner: Miner, fee_history: FeeHistoryService, - filters: Filters, + filters: Filters, ) -> Self { let start = tokio::time::Instant::now() + filters.keep_alive(); let filter_eviction_interval = tokio::time::interval_at(start, filters.keep_alive()); @@ -62,7 +68,11 @@ impl NodeService { } } -impl Future for NodeService { +impl Future for NodeService +where + Backend: TransactionValidator, + N: Network, +{ type Output = NodeResult<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -101,27 +111,35 @@ impl Future for NodeService { } } -type MiningResult = (MinedBlockOutcome, Arc>); +type MiningResult = (MinedBlockOutcome<::TxEnvelope>, Arc>); /// A type that exclusively mines one block at a time #[must_use = "streams do nothing unless polled"] -struct BlockProducer { +struct BlockProducer { /// Holds the backend if no block is being mined - idle_backend: Option>>, + idle_backend: Option>>, /// Single active future that mines a new block - block_mining: Option>>, + block_mining: Option>>, /// backlog of sets of transactions ready to be mined - queued: VecDeque>>>, + queued: VecDeque>>>, } -impl BlockProducer { - fn new(backend: Arc>) -> Self { +impl BlockProducer +where + Backend: TransactionValidator, + N: Network, +{ + fn new(backend: Arc>) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } } } -impl Stream for BlockProducer { - type Item = MinedBlockOutcome; +impl Stream for BlockProducer +where + Backend: TransactionValidator + Send + Sync + 'static, + N: Network + 'static, +{ + type Item = MinedBlockOutcome; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index ee0b7f7e610f8..3d0b38bbb64cd 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -8,6 +8,7 @@ use alloy_network::{BlockResponse, Network}; use alloy_primitives::B256; use alloy_provider::Provider; use alloy_rpc_types::anvil::Forking; +use foundry_primitives::FoundryNetwork; use futures::StreamExt; use std::fmt; use tokio::{runtime::Handle, task::JoinHandle}; @@ -69,7 +70,7 @@ impl TaskManager { /// handle.task_manager().spawn_reset_on_new_polled_blocks::(provider, api); /// # } /// ``` - pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) where N: Network, P: Provider + Clone + Unpin + 'static, @@ -129,7 +130,7 @@ impl TaskManager { /// /// # } /// ``` - pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) where N: Network, P: Provider + 'static, diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 2430901049de2..19ebdc314f7ca 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -24,6 +24,7 @@ use anvil::{EthereumHardfork, NodeConfig, NodeHandle, PrecompileFactory, eth::Et use foundry_common::provider::get_http_provider; use foundry_config::Config; use foundry_evm_networks::NetworkConfigs; +use foundry_primitives::FoundryNetwork; use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint}; use futures::StreamExt; use std::{ @@ -41,9 +42,9 @@ const BLOCK_TIMESTAMP: u64 = 1_650_274_250u64; /// Represents an anvil fork of an anvil node #[expect(unused)] pub struct LocalFork { - origin_api: EthApi, + origin_api: EthApi, origin_handle: NodeHandle, - fork_api: EthApi, + fork_api: EthApi, fork_handle: NodeHandle, } @@ -1585,7 +1586,7 @@ async fn test_reset_updates_cache_path_when_rpc_url_not_provided() { let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); - async fn get_block_from_cache_path(api: &mut EthApi) -> u64 { + async fn get_block_from_cache_path(api: &mut EthApi) -> u64 { let db = api.backend.get_db().read().await; let cache_path = db.maybe_inner().unwrap().cache().cache_path().unwrap(); cache_path diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs index 52c633b058790..a03ac47646405 100644 --- a/crates/anvil/tests/it/proof.rs +++ b/crates/anvil/tests/it/proof.rs @@ -2,10 +2,11 @@ use alloy_primitives::{Address, B256, Bytes, U256, address, fixed_bytes}; use anvil::{NodeConfig, eth::EthApi, spawn}; +use foundry_primitives::FoundryNetwork; use std::{collections::BTreeMap, str::FromStr}; async fn verify_account_proof( - api: &EthApi, + api: &EthApi, address: Address, proof: impl IntoIterator, ) { @@ -17,7 +18,7 @@ async fn verify_account_proof( } async fn verify_storage_proof( - api: &EthApi, + api: &EthApi, address: Address, slot: B256, proof: impl IntoIterator, diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 916dcbb728f63..caaf6ca6466c7 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -777,7 +777,7 @@ async fn test_trace_address_fork2() { } #[tokio::test(flavor = "multi_thread")] -async fn flaky_test_trace_filter() { +async fn test_trace_filter() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); @@ -802,11 +802,11 @@ async fn flaky_test_trace_filter() { for i in 0..=5 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); - assert_eq!(traces.len(), 5); + assert_eq!(traces.len(), 6); // Test filtering by address let tracer = TraceFilter { diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 77772a44223fa..420f79479ab68 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -60,6 +60,7 @@ tempo-alloy.workspace = true alloy-evm.workspace = true op-alloy-flz.workspace = true +op-alloy-network.workspace = true chrono.workspace = true eyre.workspace = true diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index f99dfdada1703..d181735902be9 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -9,17 +9,22 @@ use alloy_consensus::transaction::Recovered; use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; use alloy_eips::eip7702::SignedAuthorization; use alloy_ens::{ProviderEnsExt, namehash}; +use alloy_network::Ethereum; use alloy_primitives::{Address, B256, eip191_hash_message, hex, keccak256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; -use foundry_cli::{utils, utils::LoadConfig}; +use foundry_cli::{ + opts::NetworkVariant, + utils::{self, LoadConfig}, +}; use foundry_common::{ abi::{get_error, get_event}, fmt::{format_tokens, format_uint_exp, serialize_value_as_json}, fs, + provider::ProviderBuilder, selectors::{ ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, import_selectors, parse_signatures, @@ -27,7 +32,9 @@ use foundry_common::{ }, shell, stdin, }; +use op_alloy_network::Optimism; use std::time::Instant; +use tempo_alloy::TempoNetwork; /// Run the `cast` command-line interface. pub fn run() -> Result<()> { @@ -341,18 +348,42 @@ pub async fn run_command(args: CastArgs) -> Result<()> { Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? )? } - CastSubcommand::Block { block, full, fields, raw, rpc } => { + CastSubcommand::Block { block, full, fields, raw, rpc, network } => { let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; // Can use either --raw or specify raw as a field - let raw = raw || fields.contains(&"raw".into()); + let output = if raw || fields.contains(&"raw".into()) { + match network { + Some(NetworkVariant::Optimism) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; - sh_println!( - "{}", + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + Some(NetworkVariant::Tempo) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + // Ethereum (default) or no --raw flag + _ => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + } + } else { + let provider = utils::get_provider(&config)?; Cast::new(provider) - .block(block.unwrap_or(BlockId::Number(Latest)), full, fields, raw) + .block(block.unwrap_or(BlockId::Number(Latest)), full, fields) .await? - )? + }; + sh_println!("{}", output)? } CastSubcommand::BlockNumber { rpc, block } => { let config = rpc.load_config()?; @@ -533,23 +564,34 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } CastSubcommand::Run(cmd) => cmd.run().await?, CastSubcommand::SendTx(cmd) => cmd.run().await?, - CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request } => { + CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request, network } => { let config = rpc.load_config()?; // Can use either --raw or specify raw as a field - if raw || field.as_ref().is_some_and(|f| f == "raw") { - // Temporary workaround to handle tx raw encoding through FoundryNetwork - // TODO: Once the Network selection UI will be finalized, bring back --raw - // handling to `Cast::transaction` - sh_println!("{}", crate::transaction_raw(&config, tx_hash, from, nonce).await?)? - } else { - let provider = utils::get_provider(&config)?; - sh_println!( - "{}", + let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); + let output = match network { + Some(NetworkVariant::Optimism) => { + let provider = ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) - .transaction(tx_hash, from, nonce, field, to_request) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) .await? - )? - } + } + Some(NetworkVariant::Tempo) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) + .await? + } + // Ethereum (default) or no --raw flag + _ => { + let provider = utils::get_provider(&config)?; + Cast::new(&provider) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) + .await? + } + }; + sh_println!("{}", output)? } // 4Byte diff --git a/crates/cast/src/cmd/access_list.rs b/crates/cast/src/cmd/access_list.rs index ae533483fb820..3c3427f5f0747 100644 --- a/crates/cast/src/cmd/access_list.rs +++ b/crates/cast/src/cmd/access_list.rs @@ -1,6 +1,5 @@ use crate::{ Cast, - rlp_converter::TryIntoRlpEncodable, tx::{CastTxBuilder, SenderKind}, }; use alloy_ens::NameOrAddress; @@ -12,13 +11,9 @@ use foundry_cli::{ opts::{RpcOpts, TransactionOpts}, utils::LoadConfig, }; -use foundry_common::{ - fmt::{UIfmt, UIfmtHeaderExt, UIfmtSignatureExt}, - provider::ProviderBuilder, -}; +use foundry_common::provider::ProviderBuilder; use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::WalletOpts; -use serde::Serialize; use std::str::FromStr; use tempo_alloy::TempoNetwork; @@ -74,12 +69,7 @@ impl AccessListArgs { pub async fn run_with_network(self) -> Result<()> where - N::TxEnvelope: Serialize + UIfmtSignatureExt, - N::Header: TryIntoRlpEncodable, N::TransactionRequest: FoundryTransactionBuilder, - N::TransactionResponse: UIfmt, - N::HeaderResponse: UIfmtHeaderExt, - N::BlockResponse: UIfmt, { let Self { to, mut sig, args, data, tx, rpc, wallet, block } = self; diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index d410861c943cd..f3b436438829c 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -2,7 +2,6 @@ use super::run::fetch_contracts_bytecode_from_trace; use crate::{ Cast, debug::handle_traces, - rlp_converter::TryIntoRlpEncodable, traces::TraceKind, tx::{CastTxBuilder, SenderKind}, }; @@ -22,7 +21,6 @@ use foundry_cli::{ }; use foundry_common::{ abi::{encode_function_args, get_func}, - fmt::{UIfmt, UIfmtHeaderExt, UIfmtSignatureExt}, provider::{ProviderBuilder, curl_transport::generate_curl_command}, sh_println, shell, }; @@ -44,7 +42,6 @@ use foundry_wallets::WalletOpts; use itertools::Either; use regex::Regex; use revm::context::TransactionType; -use serde::Serialize; use std::{str::FromStr, sync::LazyLock}; use tempo_alloy::TempoNetwork; @@ -232,12 +229,7 @@ impl CallArgs { pub async fn run_with_network(self) -> Result<()> where - N::TxEnvelope: Serialize + UIfmtSignatureExt, - N::Header: TryIntoRlpEncodable, N::TransactionRequest: FoundryTransactionBuilder, - N::TransactionResponse: UIfmt, - N::HeaderResponse: UIfmtHeaderExt, - N::BlockResponse: UIfmt, { let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); let evm_opts = figment.extract::()?; @@ -306,21 +298,21 @@ impl CallArgs { } let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, networks) = + let (mut evm_env, tx_env, fork, chain, networks) = TracingExecutor::get_fork_material(&mut config, evm_opts).await?; // modify settings that usually set in eth_call - env.evm_env.cfg_env.disable_block_gas_limit = true; - env.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); - env.evm_env.block_env.gas_limit = u64::MAX; + evm_env.cfg_env.disable_block_gas_limit = true; + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + evm_env.block_env.gas_limit = u64::MAX; // Apply the block overrides. if let Some(block_overrides) = block_overrides { if let Some(number) = block_overrides.number { - env.evm_env.block_env.number = number.to(); + evm_env.block_env.number = number.to(); } if let Some(time) = block_overrides.time { - env.evm_env.block_env.timestamp = U256::from(time); + evm_env.block_env.timestamp = U256::from(time); } } @@ -333,7 +325,7 @@ impl CallArgs { }) .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( - env, + (evm_env, tx_env), fork, evm_version, trace_mode, @@ -345,7 +337,7 @@ impl CallArgs { let value = tx.value().unwrap_or_default(); let input = tx.input().cloned().unwrap_or_default(); let tx_kind = tx.kind().expect("set by builder"); - let env_tx = &mut executor.env_mut().tx; + let env_tx = executor.tx_env_mut(); // Set transaction options with --trace if let Some(gas_limit) = tx.gas_limit() { diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index 0cdaa05dff39a..b3c39936fe759 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -22,6 +22,7 @@ use foundry_common::{ #[doc(hidden)] pub use foundry_config::{Chain, utils::*}; use foundry_primitives::FoundryTransactionBuilder; +use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; sol! { @@ -45,6 +46,7 @@ sol! { /// /// This struct contains only the transaction options relevant to ERC20 token interactions #[derive(Debug, Clone, Args)] +#[command(next_help_heading = "Transaction options")] pub struct Erc20TxOpts { /// Gas limit for the transaction. #[arg(long, env = "ETH_GAS_LIMIT")] @@ -66,16 +68,16 @@ pub struct Erc20TxOpts { pub tempo: TempoOpts, } -/// Creates a provider with wallet for signing transactions locally. -pub(crate) async fn get_provider_with_wallet( +/// Creates a provider with a pre-resolved signer. +pub(crate) fn build_provider_with_signer( tx_opts: &SendTxOpts, + signer: WalletSigner, ) -> eyre::Result> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, { let config = tx_opts.eth.load_config()?; - let signer = tx_opts.eth.wallet.signer().await?; let wallet = EthereumWallet::from(signer); let provider = ProviderBuilder::::from_config(&config)?.build_with_wallet(wallet)?; if let Some(interval) = tx_opts.poll_interval { @@ -328,16 +330,38 @@ impl Erc20Subcommand { } pub async fn run(self) -> eyre::Result<()> { - if let Some(erc20) = self.erc20_opts() - && erc20.tempo.is_tempo() - { - self.run_generic::().await + // Resolve the signer once for state-changing variants. + let (signer, tempo_access_key) = match &self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => { + // Only attempt Tempo lookup if --from is set (avoids unnecessary I/O). + if send_tx.eth.wallet.from.is_some() { + let (s, ak) = send_tx.eth.wallet.maybe_signer().await?; + (s, ak) + } else { + (None, None) + } + } + _ => (None, None), + }; + + let is_tempo = self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some(); + + if is_tempo { + self.run_generic::(signer, tempo_access_key).await } else { - self.run_generic::().await + self.run_generic::(signer, None).await } } - pub async fn run_generic(self) -> eyre::Result<()> + pub async fn run_generic( + self, + pre_resolved_signer: Option, + tempo_keychain: Option, + ) -> eyre::Result<()> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, @@ -346,6 +370,62 @@ impl Erc20Subcommand { { let config = self.rpc_opts().load_config()?; + // Macro to DRY the keychain-vs-normal send pattern for state-changing ops. + // The only thing that varies per variant is the IERC20 call expression. + macro_rules! erc20_send { + ( + $token:expr, + $send_tx:expr, + $tx_opts:expr, | + $erc20:ident, + $provider:ident | + $build_tx:expr + ) => {{ + let timeout = $send_tx.timeout.unwrap_or(config.transaction_timeout); + if let Some(ref access_key) = tempo_keychain { + let signer = + pre_resolved_signer.as_ref().expect("signer required for access key"); + let $provider = + ProviderBuilder::::from_config(&config)?.build()?; + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + $tx_opts.apply::( + &mut tx, + get_chain(config.chain, &$provider).await?.is_legacy(), + ); + apply_tempo_access_key::(&mut tx, Some(access_key)); + send_tempo_keychain( + &$provider, + tx, + signer, + access_key, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? + } else { + let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); + let $provider = build_provider_with_signer::(&$send_tx, signer)?; + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + $tx_opts.apply::( + &mut tx, + get_chain(config.chain, &$provider).await?.is_legacy(), + ); + cast_send( + $provider, + tx, + $send_tx.cast_async, + $send_tx.sync, + $send_tx.confirmations, + timeout, + ) + .await? + } + }}; + } + match self { // Read-only Self::Allowance { token, owner, spender, block, .. } => { @@ -448,78 +528,77 @@ impl Erc20Subcommand { } // State-changing Self::Transfer { token, to, amount, send_tx, tx: tx_opts, .. } => { - let provider = get_provider_with_wallet::(&send_tx).await?; - let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) - .transfer(to.resolve(&provider).await?, U256::from_str(&amount)?) - .into_transaction_request(); - - tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); - - cast_send( - provider, - tx, - send_tx.cast_async, - send_tx.sync, - send_tx.confirmations, - send_tx.timeout.unwrap_or(config.transaction_timeout), - ) - .await? + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.transfer(to.resolve(&provider).await?, U256::from_str(&amount)?) + }) } Self::Approve { token, spender, amount, send_tx, tx: tx_opts, .. } => { - let provider = get_provider_with_wallet::(&send_tx).await?; - let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) - .approve(spender.resolve(&provider).await?, U256::from_str(&amount)?) - .into_transaction_request(); - - tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); - - cast_send( - provider, - tx, - send_tx.cast_async, - send_tx.sync, - send_tx.confirmations, - send_tx.timeout.unwrap_or(config.transaction_timeout), - ) - .await? + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.approve(spender.resolve(&provider).await?, U256::from_str(&amount)?) + }) } Self::Mint { token, to, amount, send_tx, tx: tx_opts, .. } => { - let provider = get_provider_with_wallet::(&send_tx).await?; - let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) - .mint(to.resolve(&provider).await?, U256::from_str(&amount)?) - .into_transaction_request(); - - tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); - - cast_send( - provider, - tx, - send_tx.cast_async, - send_tx.sync, - send_tx.confirmations, - send_tx.timeout.unwrap_or(config.transaction_timeout), - ) - .await? + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.mint(to.resolve(&provider).await?, U256::from_str(&amount)?) + }) } Self::Burn { token, amount, send_tx, tx: tx_opts, .. } => { - let provider = get_provider_with_wallet::(&send_tx).await?; - let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) - .burn(U256::from_str(&amount)?) - .into_transaction_request(); - - tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); - - cast_send( - provider, - tx, - send_tx.cast_async, - send_tx.sync, - send_tx.confirmations, - send_tx.timeout.unwrap_or(config.transaction_timeout), - ) - .await? + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.burn(U256::from_str(&amount)?) + }) } }; Ok(()) } } + +/// Applies Tempo access key fields (from, key_id) to a transaction request. +/// +/// Note: `key_authorization` is intentionally not set here. It is only included +/// if the key is not yet provisioned on-chain (checked in [`send_tempo_keychain`]). +fn apply_tempo_access_key( + tx: &mut N::TransactionRequest, + config: Option<&TempoAccessKeyConfig>, +) where + N::TransactionRequest: FoundryTransactionBuilder, +{ + if let Some(config) = config { + tx.set_from(config.wallet_address); + tx.set_key_id(config.key_address); + } +} + +/// Sends a Tempo transaction using access key (keychain V2 mode). +/// +/// Signs the transaction with the access key and sends it via `send_raw_transaction`, +/// bypassing `EthereumWallet`. Only includes `key_authorization` if the key is not yet +/// provisioned on-chain. +async fn send_tempo_keychain>( + provider: &P, + mut tx: ::TransactionRequest, + signer: &WalletSigner, + access_key: &TempoAccessKeyConfig, + cast_async: bool, + confirmations: u64, + timeout: u64, +) -> eyre::Result<()> { + // Only include key_authorization if the key is not yet provisioned on-chain. + if let Some(ref auth) = access_key.key_authorization + && !crate::tempo::is_key_provisioned( + provider, + access_key.wallet_address, + access_key.key_address, + ) + .await + { + tx.set_key_authorization(auth.clone()); + } + + let raw_tx = + foundry_wallets::tempo::sign_with_access_key(tx, signer, access_key.wallet_address).await?; + + let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); + + let cast = crate::tx::CastTxSender::new(provider); + cast.print_tx_result(tx_hash, cast_async, confirmations, timeout).await +} diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 41a59972c79f0..9b9b153b2f66c 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -1,4 +1,7 @@ -use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_changes}; +use crate::{ + debug::handle_traces, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, +}; use alloy_consensus::{BlockHeader, Transaction}; use alloy_evm::FromRecoveredTx; use alloy_network::{AnyNetwork, TransactionResponse}; @@ -24,7 +27,6 @@ use foundry_config::{ }, }; use foundry_evm::{ - Env, executors::{EvmError, Executor, TracingExecutor}, opts::EvmOpts, traces::{InternalTraceMode, TraceMode, Traces}, @@ -163,7 +165,7 @@ impl RunArgs { config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; - let (block, (mut env, fork, chain, networks)) = tokio::try_join!( + let (block, (mut evm_env, tx_env, fork, chain, networks)) = tokio::try_join!( // fetch the block the transaction was mined in provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into), TracingExecutor::get_fork_material(&mut config, evm_opts) @@ -171,24 +173,19 @@ impl RunArgs { let mut evm_version = self.evm_version; - env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; + evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825). // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true. if !self.enable_tx_gas_limit { - env.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); } - env.evm_env.cfg_env.limit_contract_code_size = None; - env.evm_env.block_env.number = U256::from(tx_block_number); + evm_env.cfg_env.limit_contract_code_size = None; + evm_env.block_env.number = U256::from(tx_block_number); if let Some(block) = &block { - env.evm_env.block_env.timestamp = U256::from(block.header.timestamp()); - env.evm_env.block_env.beneficiary = block.header.beneficiary(); - env.evm_env.block_env.difficulty = block.header.difficulty(); - env.evm_env.block_env.prevrandao = Some(block.header.mix_hash().unwrap_or_default()); - env.evm_env.block_env.basefee = block.header.base_fee_per_gas().unwrap_or_default(); - env.evm_env.block_env.gas_limit = block.header.gas_limit(); + evm_env.block_env = block_env_from_header(&block.header); // TODO: we need a smarter way to map the block to the corresponding evm_version for // commonly used chains @@ -199,7 +196,7 @@ impl RunArgs { } } apply_chain_and_block_specific_env_changes::( - &mut env.evm_env, + &mut evm_env, block, config.networks, ); @@ -214,7 +211,7 @@ impl RunArgs { }) .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( - env.clone(), + (evm_env.clone(), tx_env), fork, evm_version, trace_mode, @@ -222,12 +219,8 @@ impl RunArgs { create2_deployer, None, )?; - let mut env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx.clone(), - executor.spec_id(), - ); + + evm_env.cfg_env.set_spec(executor.spec_id()); // Set the state to the moment right before the transaction if !self.quick { @@ -258,24 +251,28 @@ impl RunArgs { break; } - if let Some(tx_envelope) = tx.as_envelope() { - env.tx = TxEnv::from_recovered_tx(tx_envelope, tx.from()); - } + let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| { + TxEnv::from_recovered_tx(tx_envelope, tx.from()) + }); - env.evm_env.cfg_env.disable_balance_check = true; + evm_env.cfg_env.disable_balance_check = true; if let Some(to) = Transaction::to(tx) { trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); - executor.transact_with_env(env.clone()).wrap_err_with(|| { - format!( - "Failed to execute transaction: {:?} in block {}", - tx.tx_hash(), - env.evm_env.block_env.number - ) - })?; + executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( + || { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + evm_env.block_env.number + ) + }, + )?; } else { trace!(tx=?tx.tx_hash(), "executing previous create transaction"); - if let Err(error) = executor.deploy_with_env(env.clone(), None) { + if let Err(error) = + executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) + { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), @@ -284,7 +281,7 @@ impl RunArgs { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), - env.evm_env.block_env.number + evm_env.block_env.number ) }); } @@ -301,19 +298,20 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - if let Some(tx_envelope) = tx.as_envelope() { - env.tx = TxEnv::from_recovered_tx(tx_envelope, tx.from()); - } + let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| { + TxEnv::from_recovered_tx(tx_envelope, tx.from()) + }); + if is_impersonated_tx(tx.as_ref()) { - env.evm_env.cfg_env.disable_balance_check = true; + evm_env.cfg_env.disable_balance_check = true; } if let Some(to) = Transaction::to(&tx) { trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); - TraceResult::try_from(executor.transact_with_env(env))? + TraceResult::try_from(executor.transact_with_env(evm_env, tx_env))? } else { trace!(tx=?tx.tx_hash(), "executing create transaction"); - TraceResult::try_from(executor.deploy_with_env(env, None))? + TraceResult::try_from(executor.deploy_with_env(evm_env, tx_env, None))? } }; diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 1b590fb889f99..6b07d7966a467 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -13,6 +13,7 @@ use foundry_common::{ provider::ProviderBuilder, }; use foundry_primitives::FoundryTransactionBuilder; +use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; use crate::tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}; @@ -83,14 +84,97 @@ pub enum SendTxSubcommands { impl SendTxArgs { pub async fn run(self) -> Result<()> { - if self.tx.tempo.is_tempo() { - self.run_generic::().await + // Resolve the signer early so we know if it's a Tempo access key. + let (signer, tempo_access_key) = self.send_tx.eth.wallet.maybe_signer().await?; + + if let Some(tempo_access_key) = tempo_access_key { + // Tempo keychain mode: always uses TempoNetwork. + self.run_tempo_keychain( + signer.expect("signer required for access key"), + tempo_access_key, + ) + .await + } else if self.tx.tempo.is_tempo() { + self.run_generic::(signer).await + } else { + self.run_generic::(signer).await + } + } + + /// Handles Tempo access key (keychain mode) transactions. + /// + /// Bypasses `EthereumWallet` and manually constructs a `KeychainSignature`, + /// then sends the raw transaction. + async fn run_tempo_keychain( + self, + signer: WalletSigner, + access_key: TempoAccessKeyConfig, + ) -> Result<()> { + let Self { to, mut sig, mut args, data, send_tx, mut tx, command, unlocked: _, path } = + self; + + let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; + + if let Some(data) = data { + sig = Some(data); + } + + let code = if let Some(SendTxSubcommands::Create { + code, + sig: constructor_sig, + args: constructor_args, + }) = command + { + sig = constructor_sig; + args = constructor_args; + Some(code) } else { - self.run_generic::().await + None + }; + + // Inject access key ID into TempoOpts so it's set before gas estimation. + tx.tempo.key_id = Some(access_key.key_address); + + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)) + } + + let builder = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(code, sig, args) + .await? + .with_blob_data(blob_data)?; + + let from = access_key.wallet_address; + + // Build using wallet address for correct nonce/gas estimation. + let (mut tx_request, _) = builder.build(from).await?; + + // Only include key_authorization if the key is not yet provisioned on-chain. + if let Some(auth) = access_key.key_authorization + && !crate::tempo::is_key_provisioned(&provider, from, access_key.key_address).await + { + tx_request.set_key_authorization(auth); } + + let raw_tx = crate::tempo::sign_with_access_key(tx_request, &signer, from).await?; + + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); + + let cast = CastTxSender::new(&provider); + cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await } - pub async fn run_generic(self) -> Result<()> + pub async fn run_generic( + self, + pre_resolved_signer: Option, + ) -> Result<()> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, @@ -214,7 +298,10 @@ impl SendTxArgs { // If we cannot successfully instantiate a local signer, then we will assume we don't have // enough information to sign and we must bail. } else { - let signer = send_tx.eth.wallet.signer().await?; + let signer = match pre_resolved_signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; let from = signer.address(); tx::validate_from_address(send_tx.eth.wallet.from, from)?; diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index dedd15e460728..c1d4e26209657 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -18,7 +18,7 @@ use alloy_primitives::{ utils::{ParseUnits, Unit, keccak256}, }; use alloy_provider::{PendingTransactionBuilder, Provider, network::eip2718::Decodable2718}; -use alloy_rlp::Decodable; +use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockOverrides, Filter, FilterBlockOption, Log, state::StateOverride, }; @@ -31,13 +31,11 @@ use foundry_common::{ compile::etherscan_project, flatten, fmt::*, - fs, - provider::ProviderBuilder, - shell, + fs, shell, }; -use foundry_config::{Chain, Config}; +use foundry_config::Chain; use foundry_evm::core::bytecode::InstIter; -use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; +use foundry_primitives::FoundryTxEnvelope; use futures::{FutureExt, StreamExt, future::Either}; use rayon::prelude::*; @@ -63,12 +61,11 @@ pub mod base; pub(crate) mod debug; pub mod errors; mod rlp_converter; +pub mod tempo; pub mod tx; use rlp_converter::Item; -use crate::rlp_converter::TryIntoRlpEncodable; - // TODO: CastContract with common contract initializers? Same for CastProviders? pub struct Cast { @@ -76,14 +73,7 @@ pub struct Cast { _phantom: PhantomData, } -impl + Clone + Unpin, N: Network> Cast -where - N::TxEnvelope: Serialize + UIfmtSignatureExt, - N::Header: TryIntoRlpEncodable, - N::TransactionResponse: UIfmt, - N::HeaderResponse: UIfmtHeaderExt, - N::BlockResponse: UIfmt, -{ +impl + Clone + Unpin, N: Network> Cast { /// Creates a new Cast instance from the provided client /// /// # Example @@ -294,160 +284,6 @@ where Ok(res) } - /// # Example - /// - /// ``` - /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let block = cast.block(5, true, vec![], false).await?; - /// println!("{}", block); - /// # Ok(()) - /// # } - /// ``` - pub async fn block>( - &self, - block: B, - full: bool, - fields: Vec, - raw: bool, - ) -> Result { - let block = block.into(); - if fields.contains(&"transactions".into()) && !full { - eyre::bail!("use --full to view transactions") - } - - let block = self - .provider - .get_block(block) - .kind(full.into()) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - Ok(if raw { - let encoded = block.header().as_ref().try_rlp_encode()?; - format!("0x{}", hex::encode(encoded)) - } else if !fields.is_empty() { - let mut result = String::new(); - for field in fields { - result.push_str( - &get_pretty_block_attr::(&block, &field) - .unwrap_or_else(|| format!("{field} is not a valid block field")), - ); - - result.push('\n'); - } - result.trim_end().to_string() - } else if shell::is_json() { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - }) - } - - async fn block_field_as_num>(&self, block: B, field: String) -> Result { - Self::block( - self, - block.into(), - false, - // Select only select field - vec![field], - false, - ) - .await? - .parse() - .map_err(Into::into) - } - - pub async fn base_fee>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await - } - - pub async fn age>(&self, block: B) -> Result { - let timestamp_str = - Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); - Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) - } - - pub async fn timestamp>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, "timestamp".to_string()).await - } - - pub async fn chain(&self) -> Result<&str> { - let genesis_hash = Self::block( - self, - 0, - false, - // Select only block hash - vec![String::from("hash")], - false, - ) - .await?; - - Ok(match &genesis_hash[..] { - "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Self::block(self, 1920000, false, vec![String::from("hash")], false) - .await?)[..] - { - "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { - "etclive" - } - _ => "ethlive", - } - } - "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", - "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", - "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { - "optimism-mainnet" - } - "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { - "optimism-goerli" - } - "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { - "optimism-kovan" - } - "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", - "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { - "fraxtal-testnet" - } - "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { - "arbitrum-mainnet" - } - "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", - "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", - "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", - "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", - "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", - "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { - "polygon-pos-amoy-testnet" - } - "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", - "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { - "polygon-zkevm-cardona-testnet" - } - "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", - "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", - "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", - "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", - "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Self::block(self, 1, false, vec![String::from("hash")], false).await?)[..] { - "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { - "avalanche-fuji" - } - _ => "avalanche", - } - } - "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", - "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", - _ => "unknown", - }) - } - pub async fn chain_id(&self) -> Result { Ok(self.provider.get_chain_id().await?) } @@ -710,67 +546,6 @@ where Ok(code.len().to_string()) } - /// # Example - /// - /// ``` - /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?; - /// println!("{}", tx); - /// # Ok(()) - /// # } - /// ``` - pub async fn transaction( - &self, - tx_hash: Option, - from: Option, - nonce: Option, - field: Option, - to_request: bool, - ) -> Result { - let tx = if let Some(tx_hash) = tx_hash { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - self.provider - .get_transaction_by_hash(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? - } else if let Some(from) = from { - // If nonce is not provided, uses 0. - let nonce = U64::from(nonce.unwrap_or_default()); - let from = from.resolve(self.provider.root()).await?; - - self.provider - .raw_request::<_, Option>( - "eth_getTransactionBySenderAndNonce".into(), - (from, nonce), - ) - .await? - .ok_or_else(|| { - eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) - })? - } else { - eyre::bail!("tx hash or from address is required") - }; - - Ok(if let Some(ref field) = field { - get_pretty_tx_attr::(&tx, field.as_str()) - .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? - } else if shell::is_json() { - // to_value first to sort json object keys - serde_json::to_value(&tx)?.to_string() - } else if to_request { - serde_json::to_string_pretty(&Into::::into(tx))? - } else { - tx.pretty() - }) - } - /// Perform a raw JSON-RPC request /// /// # Example @@ -1106,6 +881,264 @@ where } } +impl, N: Network> Cast +where + N::HeaderResponse: UIfmtHeaderExt, + N::BlockResponse: UIfmt, +{ + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use cast::Cast; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let block = cast.block(5, true, vec![]).await?; + /// println!("{}", block); + /// # Ok(()) + /// # } + /// ``` + pub async fn block>( + &self, + block: B, + full: bool, + fields: Vec, + ) -> Result { + let block = block.into(); + if fields.contains(&"transactions".into()) && !full { + eyre::bail!("use --full to view transactions") + } + + let block = self + .provider + .get_block(block) + .kind(full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; + + Ok(if !fields.is_empty() { + let mut result = String::new(); + for field in fields { + result.push_str( + &get_pretty_block_attr::(&block, &field) + .unwrap_or_else(|| format!("{field} is not a valid block field")), + ); + + result.push('\n'); + } + result.trim_end().to_string() + } else if shell::is_json() { + serde_json::to_value(&block).unwrap().to_string() + } else { + block.pretty() + }) + } + + async fn block_field_as_num>(&self, block: B, field: String) -> Result { + Self::block( + self, + block.into(), + false, + // Select only select field + vec![field], + ) + .await? + .parse() + .map_err(Into::into) + } + + pub async fn base_fee>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await + } + + pub async fn age>(&self, block: B) -> Result { + let timestamp_str = + Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); + let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); + Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) + } + + pub async fn timestamp>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, "timestamp".to_string()).await + } + + pub async fn chain(&self) -> Result<&str> { + let genesis_hash = Self::block( + self, + 0, + false, + // Select only block hash + vec![String::from("hash")], + ) + .await?; + + Ok(match &genesis_hash[..] { + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { + match &(Self::block(self, 1920000, false, vec![String::from("hash")]).await?)[..] { + "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { + "etclive" + } + _ => "ethlive", + } + } + "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", + "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", + "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { + "optimism-mainnet" + } + "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { + "optimism-goerli" + } + "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { + "optimism-kovan" + } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } + "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { + "arbitrum-mainnet" + } + "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", + "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", + "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", + "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", + "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", + "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { + "polygon-pos-amoy-testnet" + } + "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", + "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { + "polygon-zkevm-cardona-testnet" + } + "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", + "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", + "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", + "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", + "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { + match &(Self::block(self, 1, false, vec![String::from("hash")]).await?)[..] { + "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { + "avalanche-fuji" + } + _ => "avalanche", + } + } + "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", + "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", + _ => "unknown", + }) + } +} + +impl, N: Network> Cast +where + N::Header: Encodable, +{ + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::Ethereum}; + /// use cast::Cast; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, Ethereum>::default().connect("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let block = cast.block_raw(5, true).await?; + /// println!("{}", block); + /// # Ok(()) + /// # } + /// ``` + pub async fn block_raw>(&self, block: B, full: bool) -> Result { + let block_id = block.into(); + + let block = self + .provider + .get_block(block_id) + .kind(full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?; + + let encoded = alloy_rlp::encode(block.header().as_ref()); + + Ok(format!("0x{}", hex::encode(encoded))) + } +} + +impl, N: Network> Cast +where + N::TxEnvelope: Serialize + UIfmtSignatureExt, + N::TransactionResponse: UIfmt, +{ + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use cast::Cast; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; + /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false, false).await?; + /// println!("{}", tx); + /// # Ok(()) + /// # } + /// ``` + pub async fn transaction( + &self, + tx_hash: Option, + from: Option, + nonce: Option, + field: Option, + raw: bool, + to_request: bool, + ) -> Result { + let tx = if let Some(tx_hash) = tx_hash { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + self.provider + .get_transaction_by_hash(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? + } else if let Some(from) = from { + // If nonce is not provided, uses 0. + let nonce = U64::from(nonce.unwrap_or_default()); + let from = from.resolve(self.provider.root()).await?; + + self.provider + .raw_request::<_, Option>( + "eth_getTransactionBySenderAndNonce".into(), + (from, nonce), + ) + .await? + .ok_or_else(|| { + eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) + })? + } else { + eyre::bail!("tx hash or from address is required") + }; + + Ok(if raw { + let encoded = tx.as_ref().encoded_2718(); + format!("0x{}", hex::encode(encoded)) + } else if let Some(ref field) = field { + get_pretty_tx_attr::(&tx, field.as_str()) + .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? + } else if shell::is_json() { + // to_value first to sort json object keys + serde_json::to_value(&tx)?.to_string() + } else if to_request { + serde_json::to_string_pretty(&Into::::into(tx))? + } else { + tx.pretty() + }) + } +} + pub struct SimpleCast; impl SimpleCast { @@ -2358,44 +2391,6 @@ fn explorer_client( builder.build().map_err(Into::into) } -// Temporary workaround to handle tx raw encoding through FoundryNetwork -// TODO: Once the Network selection UI will be finalized, bring back --raw -// handling to `Cast::transaction` -pub async fn transaction_raw( - config: &Config, - tx_hash: Option, - from: Option, - nonce: Option, -) -> Result { - let provider = ProviderBuilder::::from_config(config)?.build()?; - let tx = if let Some(tx_hash) = tx_hash { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - provider - .get_transaction_by_hash(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? - } else if let Some(from) = from { - // If nonce is not provided, uses 0. - let nonce = U64::from(nonce.unwrap_or_default()); - let from = from.resolve(provider.root()).await?; - - provider - .raw_request::<_, Option<::TransactionResponse>>( - "eth_getTransactionBySenderAndNonce".into(), - (from, nonce), - ) - .await? - .ok_or_else(|| { - eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) - })? - } else { - eyre::bail!("tx hash or from address is required") - }; - - let encoded = tx.as_ref().encoded_2718(); - Ok(format!("0x{}", hex::encode(encoded))) -} - #[cfg(test)] mod tests { use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json}; diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 3e160c725039b..4eacf4a9c53d5 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -11,7 +11,7 @@ use alloy_primitives::{Address, B256, Selector, U256}; use alloy_rpc_types::BlockId; use clap::{ArgAction, Parser, Subcommand, ValueHint}; use eyre::Result; -use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; +use foundry_cli::opts::{EtherscanOpts, GlobalArgs, NetworkVariant, RpcOpts}; use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::{path::PathBuf, str::FromStr}; /// A Swiss Army knife for interacting with Ethereum applications from the command line. @@ -390,6 +390,10 @@ pub enum CastSubcommand { #[command(flatten)] rpc: RpcOpts, + + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, }, /// Get the latest block number. @@ -519,6 +523,10 @@ pub enum CastSubcommand { /// If specified, the transaction will be converted to a TransactionRequest JSON format. #[arg(long)] to_request: bool, + + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, }, /// Get the transaction receipt for a transaction. diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index 3ebc6e6d70892..f386edddc541e 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -1,10 +1,8 @@ -use alloy_network::AnyHeader; use alloy_primitives::{U256, hex}; use alloy_rlp::{Buf, Decodable, Encodable, Header}; -use eyre::{Context, Result}; +use eyre::Context; use serde_json::Value; use std::fmt; -use tempo_alloy::primitives::TempoHeader; /// Arbitrary nested data. /// @@ -94,33 +92,6 @@ impl fmt::Display for Item { } } -/// Trait for types that can be RLP-encoded, possibly after a fallible conversion. -/// -/// Types that directly implement [`Encodable`] can opt in via [`RlpEncodable`] -/// to get a blanket implementation. Types like [`AnyHeader`] that require -/// conversion provide their own implementation. -pub trait TryIntoRlpEncodable { - fn try_rlp_encode(&self) -> Result>; -} - -/// Marker trait for [`Encodable`] types to opt into [`TryIntoRlpEncodable`]. -trait RlpEncodable: Encodable {} - -impl RlpEncodable for alloy_consensus::Header {} -impl RlpEncodable for TempoHeader {} - -impl TryIntoRlpEncodable for T { - fn try_rlp_encode(&self) -> Result> { - Ok(alloy_rlp::encode(self)) - } -} - -impl TryIntoRlpEncodable for AnyHeader { - fn try_rlp_encode(&self) -> Result> { - Ok(alloy_rlp::encode(self.clone().try_into_header()?)) - } -} - #[cfg(test)] mod test { use crate::rlp_converter::Item; diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs new file mode 100644 index 0000000000000..3c64491aaea63 --- /dev/null +++ b/crates/cast/src/tempo.rs @@ -0,0 +1,21 @@ +use alloy_primitives::Address; +use alloy_provider::Provider; +use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; + +pub use foundry_wallets::tempo::sign_with_access_key; + +/// Checks whether an access key is already provisioned on-chain. +/// +/// Queries the AccountKeychain precompile's `getKey` function. A key is considered +/// provisioned if the returned `keyId` is non-zero (i.e. the key exists and has not +/// been revoked). +pub async fn is_key_provisioned>( + provider: &P, + wallet_address: Address, + key_address: Address, +) -> bool { + match provider.get_keychain_key(wallet_address, key_address).await { + Ok(info) => info.keyId != Address::ZERO, + Err(_) => false, + } +} diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index ebbfd5c64fc06..80c52e565c6cb 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -86,10 +86,10 @@ impl SenderKind<'_> { /// If from is not specified, but there is a signer configured, returns the signer's address /// If from is not specified and there is no signer configured, returns zero address pub async fn from_wallet_opts(opts: WalletOpts) -> Result { - if let Some(from) = opts.from { - Ok(from.into()) - } else if let Ok(signer) = opts.signer().await { + if let (Some(signer), _) = opts.maybe_signer().await? { Ok(Self::OwnedSigner(Box::new(signer))) + } else if let Some(from) = opts.from { + Ok(from.into()) } else { Ok(Address::ZERO.into()) } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 778033145424e..a33252e17dc7d 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -181,6 +181,32 @@ casttest!(block_raw, |_prj, cmd| { ); }); +casttest!(block_raw_tempo, |_prj, cmd| { + // https://explore.tempo.xyz/block/8386710 + let output = cmd + .args([ + "block", + "8386710", + "--rpc-url", + "https://rpc.moderato.tempo.xyz", + "--raw", + "-n", + "tempo", + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + let hash = alloy_primitives::keccak256(hex::decode(output).unwrap()); + + assert_eq!( + hash.to_string(), + "0xcd6170dc28b888bcb93ed1ad76a6bea4ad9977b678db5d462df83d35ec9b8d15" + ); +}); + // tests that the `cast find-block` command works correctly casttest!(finds_block, |_prj, cmd| { // Construct args @@ -1636,7 +1662,9 @@ revertReason [..]Transaction too old, data: "0x08c379a000000000000000000 "#,"","","","")); }); // tests that the revert reason is loaded using the correct `from` address. -casttest!(revert_reason_from, |_prj, cmd| { +// Flaky: Sepolia RPC may not return the revertReason field depending on provider +// support for debug/trace APIs. +casttest!(flaky_revert_reason_from, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); // https://sepolia.etherscan.io/tx/0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 cmd.args([ @@ -1664,7 +1692,7 @@ type 0 blobGasPrice {} blobGasUsed {} to 0x91b5d4111a4C038153b24e31F75ccdC47123595d -revertReason Counter is too large, data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014436f756e74657220697320746f6f206c61726765000000000000000000000000" +... "#, "", "", "", "")); }); @@ -3978,6 +4006,8 @@ casttest!(tx_raw_opstack_deposit, |_prj, cmd| { "tx", "0xf403cba612d1c01c027455c0d97427ccd5f7f99aac30017e065f81d1e30244ea", "--raw", + "-n", + "optimism", "--rpc-url", "https://sepolia.base.org", ]).assert_success() @@ -3987,6 +4017,22 @@ casttest!(tx_raw_opstack_deposit, |_prj, cmd| { "#]]); }); +casttest!(tx_raw_tempo, |_prj, cmd| { + cmd.args([ + "tx", + "0xa24c6bbeea629a80be79e970a9749d0cbc6ee31625a0b75f585c173ab15a18ec", + "--raw", + "-n", + "tempo", + "--rpc-url", + "https://rpc.moderato.tempo.xyz", + ]).assert_success() + .stdout_eq(str![[r#" +0x76f8cf82a5bf1485059682f018830494e5f85ef85c9420c0000000000000000000007d9cc57068833ea780b84440c10f190000000000000000000000008a871f4189067637cfc4cc1500abd6244bf1df740000000000000000000000000000000000000000000000000000000005f5e100c08082057e80809420c000000000000000000000000000000000000080c0b841eb100c4cbd96903bf9e97968c0982670bb90fc191ee4544c7ff32d44e901dbea3f6fbdd58255051135c2fe1aa81583a270d96009cbe375f4605ef15971273a4f1b + +"#]]); +}); + // Test that cast send --create works correctly with constructor arguments // forgetest_async!(cast_send_create_with_constructor_args, |prj, cmd| { diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 7a865cc348386..ec91dec4a5e44 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3980,6 +3980,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "currentFilePath", + "description": "Get the source file path of the currently running test or script contract,\nrelative to the project root.", + "declaration": "function currentFilePath() external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "currentFilePath()", + "selector": "0x9b45555c", + "selectorBytes": [ + 155, + 69, + 85, + 92 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "deal", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index c703c2fc5fd9a..31b579848f3db 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1828,6 +1828,11 @@ interface Vm { #[cheatcode(group = Filesystem)] function projectRoot() external view returns (string memory path); + /// Get the source file path of the currently running test or script contract, + /// relative to the project root. + #[cheatcode(group = Filesystem)] + function currentFilePath() external view returns (string memory path); + /// Returns the time since unix epoch in milliseconds. #[cheatcode(group = Filesystem)] function unixTime() external view returns (uint256 milliseconds); diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 95b925a157b66..fad6f945aadf5 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -377,7 +377,7 @@ fn sign_with_wallet( let mut wallets = state.wallets().inner.lock(); let maybe_provided_sender = wallets.provided_sender; - let (signers, _) = wallets.multi_wallet.signers()?; + let signers = wallets.multi_wallet.signers()?; let signer = if let Some(signer) = signer { signer diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 8fa56c256e5aa..c709550fb7ad4 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,11 +1,11 @@ //! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{ - BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxExt, CheatsCtxt, - Error, Result, Vm::*, inspector::RecordDebugStepInfo, + BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, + EthCheatCtx, Result, Vm::*, inspector::RecordDebugStepInfo, }; -use alloy_consensus::TxEnvelope; -use alloy_evm::{EvmEnv, FromRecoveredTx}; +use alloy_consensus::{TxEnvelope, transaction::SignerRecoverable}; +use alloy_evm::FromRecoveredTx; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_network::eip2718::EIP4844_TX_TYPE_ID; use alloy_primitives::{ @@ -15,6 +15,7 @@ use alloy_primitives::{ use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::{ + TransactionMaybeSigned, fs::{read_json_file, write_json_file}, slot_identifier::{ ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier, @@ -23,8 +24,8 @@ use foundry_common::{ }; use foundry_compilers::artifacts::EvmVersion; use foundry_evm_core::{ - Env, FoundryBlock, FoundryCfg, FoundryTransaction, - backend::{DatabaseExt, FoundryJournalExt, RevertStateSnapshotAction}, + FoundryBlock, FoundryTransaction, + backend::{DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, env::FoundryContextExt, utils::get_blob_base_fee_update_fraction_by_spec_id, @@ -318,7 +319,7 @@ impl Cheatcode for loadCall { } impl Cheatcode for loadAllocsCall { - fn apply_stateful>( + fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { @@ -338,7 +339,7 @@ impl Cheatcode for loadAllocsCall { }; // Then, load the allocs into the database. - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); db.load_allocs(&allocs, inner) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) @@ -346,7 +347,7 @@ impl Cheatcode for loadAllocsCall { } impl Cheatcode for cloneAccountCall { - fn apply_stateful>( + fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { @@ -354,7 +355,7 @@ impl Cheatcode for cloneAccountCall { let account = ccx.ecx.journal_mut().load_account(*source)?; let genesis = genesis_account(account.data); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); db.clone_account(&genesis, target, inner)?; // Cloned account should persist in forked envs. ccx.ecx.db_mut().add_persistent_account(*target); @@ -495,10 +496,10 @@ impl Cheatcode for getChainIdCall { } impl Cheatcode for chainIdCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64"); - ccx.ecx.cfg_mut().set_chain_id(newChainId.to()); + ccx.ecx.cfg_mut().chain_id = newChainId.to(); Ok(Default::default()) } } @@ -515,7 +516,7 @@ impl Cheatcode for difficultyCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.ecx.cfg().spec().into() < SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() < SpecId::MERGE, "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); @@ -537,7 +538,7 @@ impl Cheatcode for prevrandao_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg().spec().into() >= SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); @@ -550,7 +551,7 @@ impl Cheatcode for prevrandao_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg().spec().into() >= SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); @@ -563,7 +564,7 @@ impl Cheatcode for blobhashesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { hashes } = self; ensure!( - ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`blobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); @@ -575,10 +576,10 @@ impl Cheatcode for blobhashesCall { } impl Cheatcode for getBlobhashesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; ensure!( - ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`getBlobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); @@ -629,12 +630,12 @@ impl Cheatcode for blobBaseFeeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newBlobBaseFee } = self; ensure!( - ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`blobBaseFee` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - let spec: SpecId = ccx.ecx.cfg().spec().into(); + let spec: SpecId = (*ccx.ecx.cfg().spec()).into(); ccx.ecx.block_mut().set_blob_excess_gas_and_price( (*newBlobBaseFee).to(), get_blob_base_fee_update_fraction_by_spec_id(spec), @@ -889,20 +890,14 @@ impl Cheatcode for stopSnapshotGas_2Call { // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } impl Cheatcode for snapshotStateCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } @@ -910,20 +905,14 @@ impl Cheatcode for snapshotStateCall { // Deprecated in favor of `revertToStateCall` impl Cheatcode for revertToCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } impl Cheatcode for revertToStateCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } @@ -931,20 +920,14 @@ impl Cheatcode for revertToStateCall { // Deprecated in favor of `revertToStateAndDeleteCall` impl Cheatcode for revertToAndDeleteCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } impl Cheatcode for revertToStateAndDeleteCall { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } @@ -1130,7 +1113,7 @@ impl Cheatcode for getStorageAccessesCall { } impl Cheatcode for broadcastRawTransactionCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -1138,12 +1121,15 @@ impl Cheatcode for broadcastRawTransactionCall { let tx = TxEnvelope::decode(&mut self.data.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; - executor.transact_from_tx_on_db(ccx.state, ccx.ecx, &tx.clone().into())?; + let from = tx.recover_signer()?; + let tx_env = FromRecoveredTx::from_recovered_tx(&tx, from); + + executor.transact_from_tx_on_db(ccx.state, ccx.ecx, &tx_env)?; if ccx.state.broadcast.is_some() { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { rpc: ccx.ecx.db().active_fork_url(), - transaction: tx.try_into()?, + transaction: TransactionMaybeSigned::new_signed(tx)?, }); } @@ -1170,7 +1156,7 @@ impl Cheatcode for setBlockhashCall { } impl Cheatcode for executeTransactionCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -1207,24 +1193,25 @@ impl Cheatcode for executeTransactionCall { let tx_env = >::from_recovered_tx(&tx, sender); // Save current env for restoration after execution. - let (cached_evm_env, cached_tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let cached_evm_env = ccx.ecx.evm_clone(); + let cached_tx_env = ccx.ecx.tx_clone(); // Override env for isolated execution. ccx.ecx.block_mut().set_basefee(0); - *ccx.ecx.tx_mut() = tx_env; + ccx.ecx.set_tx(tx_env); ccx.ecx.tx_mut().set_gas_price(0); ccx.ecx.tx_mut().set_gas_priority_fee(None); // Enable nonce checks for realistic simulation. - ccx.ecx.cfg_mut().set_disable_nonce_check(false); + ccx.ecx.cfg_mut().disable_nonce_check = false; // EIP-3860: enforce initcode size limit. - ccx.ecx - .cfg_mut() - .set_limit_contract_initcode_size(Some(revm::primitives::eip3860::MAX_INITCODE_SIZE)); + ccx.ecx.cfg_mut().limit_contract_initcode_size = + Some(revm::primitives::eip3860::MAX_INITCODE_SIZE); // Snapshot the modified env for EVM construction. - let (modified_evm_env, modified_tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let modified_evm_env = ccx.ecx.evm_clone(); + let modified_tx_env = ccx.ecx.tx_clone(); // Mark as inner context so isolation mode doesn't trigger a nested transact_inner // when the inner EVM executes calls at depth == 1. @@ -1232,7 +1219,7 @@ impl Cheatcode for executeTransactionCall { // Clone journaled state and mark all accounts/slots cold. let cold_state = { - let (_, journal) = ccx.ecx.journal_mut().as_db_and_inner(); + let (_, journal) = ccx.ecx.db_journal_inner_mut(); let mut state = journal.state.clone(); for (addr, acc_mut) in &mut state { if journal.warm_addresses.is_cold(addr) { @@ -1247,28 +1234,19 @@ impl Cheatcode for executeTransactionCall { }; let mut res = None; - let mut nested_env = None; let mut cold_state = Some(cold_state); - let modified_tx = modified_tx_env.clone(); - { - let (db, _) = ccx.ecx.journal_mut().as_db_and_inner(); - executor.with_fresh_nested_evm( - ccx.state, - db, - modified_evm_env, - modified_tx_env, - &mut |evm| { - // SAFETY: closure is called exactly once by the executor. - evm.journal_inner_mut().state = cold_state.take().expect("called once"); - // Set depth to 1 for proper trace collection. - evm.journal_inner_mut().depth = 1; - res = Some(evm.transact(modified_tx.clone())); - nested_env = Some(evm.to_env()); - Ok(()) - }, - )?; - } - let (res, (mut nested_evm_env, _)) = (res.unwrap(), nested_env.unwrap()); + let mut nested_evm_env = { + let (db, _) = ccx.ecx.db_journal_inner_mut(); + executor.with_fresh_nested_evm(ccx.state, db, modified_evm_env, &mut |evm| { + // SAFETY: closure is called exactly once by the executor. + evm.journal_inner_mut().state = cold_state.take().expect("called once"); + // Set depth to 1 for proper trace collection. + evm.journal_inner_mut().depth = 1; + res = Some(evm.transact_raw(modified_tx_env.clone())); + Ok(()) + })? + }; + let res = res.unwrap(); // Restore env, preserving cheatcode cfg/block changes from the nested EVM // but restoring the original tx and basefee (which we zeroed for the nested call) @@ -1277,7 +1255,8 @@ impl Cheatcode for executeTransactionCall { nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check; nested_evm_env.cfg_env.limit_contract_initcode_size = cached_evm_env.cfg_env.limit_contract_initcode_size; - Env::apply_evm_and_tx(ccx.ecx, nested_evm_env, cached_tx_env); + ccx.ecx.set_evm(nested_evm_env); + ccx.ecx.set_tx(cached_tx_env); // Reset inner context flag. executor.set_in_inner_context(false, None); @@ -1327,7 +1306,7 @@ impl Cheatcode for executeTransactionCall { } impl Cheatcode for startDebugTraceRecordingCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -1357,7 +1336,7 @@ impl Cheatcode for startDebugTraceRecordingCall { } impl Cheatcode for stopAndReturnDebugTraceRecordingCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -1408,8 +1387,8 @@ impl Cheatcode for setEvmVersionCall { } impl Cheatcode for getEvmVersionCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { - let spec: SpecId = ccx.ecx.cfg().spec().into(); + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + let spec = (*ccx.ecx.cfg().spec()).into(); Ok(spec.to_string().to_lowercase().abi_encode()) } } @@ -1422,22 +1401,20 @@ pub(super) fn get_nonce>( Ok(account.data.info.nonce.abi_encode()) } -fn inner_snapshot_state>( - ccx: &mut CheatsCtxt<'_, CTX>, -) -> Result { - let evm_env = - EvmEnv { cfg_env: ccx.ecx.cfg_mut().clone(), block_env: ccx.ecx.block_mut().clone() }; - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); +fn inner_snapshot_state(ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + let evm_env = ccx.ecx.evm_clone(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); let id = db.snapshot_state(inner, &evm_env); Ok(id.abi_encode()) } -fn inner_revert_to_state>( +fn inner_revert_to_state( ccx: &mut CheatsCtxt<'_, CTX>, snapshot_id: U256, ) -> Result { - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let mut evm_env = ccx.ecx.evm_clone(); + let mut tx_env = ccx.ecx.tx_clone(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); if let Some(restored) = db.revert_state( snapshot_id, inner, @@ -1446,19 +1423,21 @@ fn inner_revert_to_state>( RevertStateSnapshotAction::RevertKeep, ) { *inner = restored; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + ccx.ecx.set_evm(evm_env); + ccx.ecx.set_tx(tx_env); Ok(true.abi_encode()) } else { Ok(false.abi_encode()) } } -fn inner_revert_to_state_and_delete>( +fn inner_revert_to_state_and_delete( ccx: &mut CheatsCtxt<'_, CTX>, snapshot_id: U256, ) -> Result { - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let mut evm_env = ccx.ecx.evm_clone(); + let mut tx_env = ccx.ecx.tx_clone(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); if let Some(restored) = db.revert_state( snapshot_id, inner, @@ -1467,7 +1446,8 @@ fn inner_revert_to_state_and_delete>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber } = self; persist_caller(ccx); - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - db.roll_fork(None, (*blockNumber).to(), &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(Default::default()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.roll_fork(None, (*blockNumber).to(), evm_env, tx_env, inner) + }) } } impl Cheatcode for rollFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { txHash } = self; persist_caller(ccx); - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - db.roll_fork_to_transaction(None, *txHash, &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(Default::default()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.roll_fork_to_transaction(None, *txHash, evm_env, tx_env, inner) + }) } } impl Cheatcode for rollFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(Default::default()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, tx_env, inner) + }) } } impl Cheatcode for rollFork_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(Default::default()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, tx_env, inner) + }) } } impl Cheatcode for selectForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - db.select_fork(*forkId, &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(Default::default()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.select_fork(*forkId, evm_env, tx_env, inner) + }) } } impl Cheatcode for transact_0Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -151,7 +132,7 @@ impl Cheatcode for transact_0Call { } impl Cheatcode for transact_1Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -346,7 +327,7 @@ impl Cheatcode for getRawBlockHeaderCall { } /// Creates and then also selects the new fork -fn create_select_fork( +fn create_select_fork( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, @@ -354,15 +335,13 @@ fn create_select_fork( check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - let id = db.create_select_fork(fork, &mut evm_env, &mut tx_env, inner)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(id.abi_encode()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.create_select_fork(fork, evm_env, tx_env, inner) + }) } /// Creates a new fork -fn create_fork>( +fn create_fork( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, @@ -373,7 +352,7 @@ fn create_fork>( } /// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction( +fn create_select_fork_at_transaction( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, @@ -381,16 +360,13 @@ fn create_select_fork_at_transaction( check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; - let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); - let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); - let id = - db.create_select_fork_at_transaction(fork, &mut evm_env, &mut tx_env, inner, *transaction)?; - Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); - Ok(id.abi_encode()) + fork_env_op(ccx, |db, evm_env, tx_env, inner| { + db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction) + }) } /// Creates a new fork at the given transaction -fn create_fork_at_transaction>( +fn create_fork_at_transaction( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, @@ -401,7 +377,7 @@ fn create_fork_at_transaction>( } /// Creates the request object for a new fork request -fn create_fork_request>( +fn create_fork_request( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, @@ -421,15 +397,33 @@ fn create_fork_request>( enable_caching: !ccx.state.config.no_storage_caching && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - evm_env: EvmEnv { - cfg_env: ccx.ecx.cfg_mut().clone(), - block_env: ccx.ecx.block_mut().clone(), - }, + evm_env: ccx.ecx.evm_clone(), evm_opts, }; Ok(fork) } +/// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes +/// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork, +/// createSelectFork). +fn fork_env_op( + ccx: &mut CheatsCtxt<'_, CTX>, + f: impl FnOnce( + &mut dyn DatabaseExt, + &mut EvmEnv, + &mut CTX::Tx, + &mut JournaledState, + ) -> eyre::Result, +) -> Result { + let mut evm_env = ccx.ecx.evm_clone(); + let mut tx_env = ccx.ecx.tx_clone(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + let result = f(db, &mut evm_env, &mut tx_env, inner)?; + ccx.ecx.set_evm(evm_env); + ccx.ecx.set_tx(tx_env); + Ok(result.abi_encode()) +} + fn check_broadcast(state: &Cheatcodes) -> Result<()> { if state.broadcast.is_none() { Ok(()) @@ -438,7 +432,7 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { } } -fn transact>( +fn transact( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, transaction: B256, diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index f599b3f09a325..1bfc95b789ea3 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -2,19 +2,18 @@ use super::string::parse; use crate::{ - Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*, inspector::exec_create, + Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, EthCheatCtx, Result, Vm::*, + inspector::exec_create, }; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_network::ReceiptResponse; +use alloy_network::{Ethereum, Network, ReceiptResponse}; use alloy_primitives::{Bytes, U256, hex, map::Entry}; -use alloy_rpc_types::TransactionReceipt; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use foundry_evm_core::{backend::FoundryJournalExt, env::FoundryContextExt}; use revm::{ context::{Cfg, ContextTr, CreateScheme, JournalTr}, interpreter::CreateInputs, @@ -88,6 +87,19 @@ impl Cheatcode for projectRootCall { } } +impl Cheatcode for currentFilePathCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let artifact = state + .config + .running_artifact + .as_ref() + .ok_or_else(|| fmt_err!("no running contract found"))?; + let relative = artifact.source.strip_prefix(&state.config.root).unwrap_or(&artifact.source); + Ok(relative.display().to_string().abi_encode()) + } +} + impl Cheatcode for unixTimeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; @@ -299,7 +311,7 @@ impl Cheatcode for getDeployedCodeCall { } impl Cheatcode for deployCode_0Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -310,7 +322,7 @@ impl Cheatcode for deployCode_0Call { } impl Cheatcode for deployCode_1Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -321,7 +333,7 @@ impl Cheatcode for deployCode_1Call { } impl Cheatcode for deployCode_2Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -332,7 +344,7 @@ impl Cheatcode for deployCode_2Call { } impl Cheatcode for deployCode_3Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -343,7 +355,7 @@ impl Cheatcode for deployCode_3Call { } impl Cheatcode for deployCode_4Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -354,7 +366,7 @@ impl Cheatcode for deployCode_4Call { } impl Cheatcode for deployCode_5Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -365,7 +377,7 @@ impl Cheatcode for deployCode_5Call { } impl Cheatcode for deployCode_6Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -376,7 +388,7 @@ impl Cheatcode for deployCode_6Call { } impl Cheatcode for deployCode_7Call { - fn apply_full>( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -388,7 +400,7 @@ impl Cheatcode for deployCode_7Call { /// Helper function to deploy contract from artifact code. /// Uses CREATE2 scheme if salt specified. -fn deploy_code>( +fn deploy_code( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, path: &str, @@ -785,7 +797,7 @@ impl Cheatcode for getBroadcasts_0Call { let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(map_broadcast_tx_type(*txType)); - let broadcasts = reader.read()?; + let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() @@ -805,7 +817,7 @@ impl Cheatcode for getBroadcasts_1Call { let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?; - let broadcasts = reader.read()?; + let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() @@ -858,7 +870,7 @@ impl Cheatcode for getDeploymentsCall { .with_tx_type(CallKind::Create) .with_tx_type(CallKind::Create2); - let broadcasts = reader.read()?; + let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() @@ -884,15 +896,15 @@ fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind { } } -fn parse_broadcast_results( - results: Vec<(TransactionWithMetadata, TransactionReceipt)>, +fn parse_broadcast_results( + results: Vec<(TransactionWithMetadata, N::ReceiptResponse)>, ) -> Vec { results .into_iter() .map(|(tx, receipt)| BroadcastTxSummary { txHash: receipt.transaction_hash(), blockNumber: receipt.block_number().unwrap_or_default(), - txType: match tx.opcode { + txType: match tx.call_kind { CallKind::Call => BroadcastTxType::Call, CallKind::Create => BroadcastTxType::Create, CallKind::Create2 => BroadcastTxType::Create2, @@ -916,7 +928,7 @@ fn latest_broadcast( reader = reader.with_tx_type(filter); } - let broadcast = reader.read_latest()?; + let broadcast = reader.read_latest::()?; let results = reader.into_tx_receipts(broadcast); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 0e5310c9e0c62..2a7307f6ee584 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -21,55 +21,54 @@ use crate::{ utils::IgnoredTraces, }; use alloy_consensus::BlobTransactionSidecarVariant; -use alloy_network::TransactionBuilder4844; +use alloy_network::{Ethereum, Network, TransactionBuilder}; use alloy_primitives::{ Address, B256, Bytes, Log, TxKind, U256, hex, map::{AddressHashMap, HashMap, HashSet}, }; -use alloy_rpc_types::{ - AccessList, - request::{TransactionInput, TransactionRequest}, -}; +use alloy_rpc_types::{AccessList, TransactionRequest}; use alloy_sol_types::{SolCall, SolInterface, SolValue}; use foundry_common::{ SELECTOR_LEN, TransactionMaybeSigned, mapping_slots::{MappingSlots, step as mapping_step}, }; use foundry_evm_core::{ - Breakpoints, Env, EvmEnv, FoundryInspectorExt, FoundryTransaction, + Breakpoints, EthCheatCtx, EvmEnv, FoundryTransaction, InspectorExt, abi::Vm::stopExpectSafeMemoryCall, - backend::{DatabaseError, DatabaseExt, FoundryJournalExt, RevertDiagnostic}, + backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, env::FoundryContextExt, - evm::{NestedEvm, new_evm_with_inspector, with_cloned_context}, + evm::{NestedEvm, NestedEvmClosure, new_revm_with_inspector, with_cloned_context}, }; use foundry_evm_traces::{ TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier, }; +use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::wallet_multi::MultiWallet; use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; use rand::Rng; use revm::{ - Inspector, + Context, Inspector, bytecode::opcode as op, context::{ - BlockEnv, Cfg, ContextTr, JournalTr, Transaction, TransactionType, TxEnv, result::EVMError, + BlockEnv, Cfg, CfgEnv, ContextTr, JournalTr, Transaction, TransactionType, TxEnv, + result::EVMError, }, context_interface::{CreateScheme, transaction::SignedAuthorization}, - handler::FrameResult, + handler::{EvmTr, FrameResult}, inspector::JournalExt, interpreter::{ CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, interpreter_types::{Jumps, LoopControl, MemoryTr}, }, - primitives::hardfork::SpecId, }; use serde_json::Value; use std::{ cmp::max, collections::{BTreeMap, VecDeque}, + fmt::Debug, fs::File, io::BufReader, ops::Range, @@ -82,33 +81,15 @@ mod utils; pub mod analysis; pub use analysis::CheatcodeAnalysis; -/// Bounds for the generic `Inspector` impl on `Cheatcodes`. -/// -/// Shorthand used internally to avoid repeating the full where-clause. -/// Any `EthEvmContext<&mut dyn DatabaseExt>` satisfies these bounds, so all -/// existing call-sites (e.g. `InspectorStackRefMut`) keep working unchanged. -pub trait CheatsCtxExt: FoundryContextExt {} -impl CheatsCtxExt for CTX where - CTX: FoundryContextExt -{ -} - -/// Closure type used by [`CheatcodesExecutor`] methods that run nested EVM operations. -pub type NestedEvmClosure<'a> = - &'a mut dyn FnMut(&mut dyn NestedEvm) -> Result<(), EVMError>; - /// Helper trait for running nested EVM operations from inside cheatcode implementations. -/// -/// The executor assembles the full inspector stack internally and never exposes it across the -/// trait boundary. This keeps the trait free of `InspectorExt` (which is Eth-specific). -pub trait CheatcodesExecutor { +pub trait CheatcodesExecutor { /// Runs a closure with a nested EVM built from the current context. /// The inspector is assembled internally — never exposed to the caller. fn with_nested_evm( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - f: NestedEvmClosure<'_>, + f: NestedEvmClosure<'_, CTX::Tx>, ) -> Result<(), EVMError>; /// Replays a historical transaction on the database. Inspector is assembled internally. @@ -125,23 +106,24 @@ pub trait CheatcodesExecutor { &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - tx: &TransactionRequest, + tx: &CTX::Tx, ) -> eyre::Result<()>; /// Runs a closure with a fresh nested EVM built from a raw database and environment. /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back. /// The caller is responsible for state merging. Used by `executeTransactionCall`. + /// Returns the final EVM environment after the closure runs (consumed without cloning). + #[allow(clippy::type_complexity)] fn with_fresh_nested_evm( &mut self, cheats: &mut Cheatcodes, - db: &mut dyn DatabaseExt, - evm_env: EvmEnv, - tx_env: TxEnv, - f: NestedEvmClosure<'_>, - ) -> Result<(), EVMError>; + db: &mut dyn DatabaseExt::Spec>, + evm_env: EvmEnv<::Spec, CTX::Block>, + f: NestedEvmClosure<'_, CTX::Tx>, + ) -> Result::Spec, CTX::Block>, EVMError>; /// Simulates `console.log` invocation. - fn console_log(&mut self, cheats: &mut Cheatcodes, msg: &str); + fn console_log(&mut self, msg: &str); /// Returns a mutable reference to the tracing inspector if it is available. fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { @@ -155,7 +137,7 @@ pub trait CheatcodesExecutor { } /// Builds a sub-EVM from the current context and executes the given CREATE frame. -pub(crate) fn exec_create>( +pub(crate) fn exec_create( executor: &mut dyn CheatcodesExecutor, inputs: CreateInputs, ccx: &mut CheatsCtxt<'_, CTX>, @@ -186,35 +168,33 @@ pub(crate) fn exec_create>( #[derive(Debug, Default, Clone, Copy)] struct TransparentCheatcodesExecutor; -impl> CheatcodesExecutor - for TransparentCheatcodesExecutor -{ +impl CheatcodesExecutor for TransparentCheatcodesExecutor { fn with_nested_evm( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - f: NestedEvmClosure<'_>, + f: NestedEvmClosure<'_, CTX::Tx>, ) -> Result<(), EVMError> { - with_cloned_context(ecx, |db, evm_env, tx_env, journal_inner| { - let mut evm = new_evm_with_inspector(db, evm_env, tx_env, cheats); + with_cloned_context(ecx, |db, evm_env, journal_inner| { + let mut evm = new_revm_with_inspector(db, evm_env, cheats); *evm.journal_inner_mut() = journal_inner; f(&mut evm)?; - let (sub_evm_env, sub_tx) = evm.to_env(); - let sub_inner = evm.into_context().journaled_state.inner; - Ok(((), sub_evm_env, sub_tx, sub_inner)) + let sub_inner = evm.journaled_state.inner.clone(); + let sub_evm_env = evm.ctx_ref().evm_clone(); + Ok((sub_evm_env, sub_inner)) }) } fn with_fresh_nested_evm( &mut self, cheats: &mut Cheatcodes, - db: &mut dyn DatabaseExt, - evm_env: EvmEnv, - tx_env: TxEnv, - f: NestedEvmClosure<'_>, - ) -> Result<(), EVMError> { - let mut evm = new_evm_with_inspector(db, evm_env, tx_env, cheats); - f(&mut evm) + db: &mut dyn DatabaseExt, + evm_env: EvmEnv, + f: NestedEvmClosure<'_, CTX::Tx>, + ) -> Result, EVMError> { + let mut evm = new_revm_with_inspector(db, evm_env, cheats); + f(&mut evm)?; + Ok(evm.ctx_ref().evm_clone()) } fn transact_on_db( @@ -224,8 +204,9 @@ impl> CheatcodesExecutor fork_id: Option, transaction: B256, ) -> eyre::Result<()> { - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); - let (db, inner) = ecx.journal_mut().as_db_and_inner(); + let evm_env = ecx.evm_clone(); + let tx_env = ecx.tx_clone(); + let (db, inner) = ecx.db_journal_inner_mut(); db.transact(fork_id, transaction, evm_env, tx_env, inner, cheats) } @@ -233,14 +214,14 @@ impl> CheatcodesExecutor &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - tx: &TransactionRequest, + tx: &CTX::Tx, ) -> eyre::Result<()> { - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); - let (db, inner) = ecx.journal_mut().as_db_and_inner(); - db.transact_from_tx(tx, evm_env, tx_env, inner, cheats) + let evm_env = ecx.evm_clone(); + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact_from_tx(tx, evm_env, inner, cheats) } - fn console_log(&mut self, _cheats: &mut Cheatcodes, _msg: &str) {} + fn console_log(&mut self, _msg: &str) {} } macro_rules! try_or_return { @@ -275,11 +256,11 @@ impl TestContext { /// Helps collecting transactions from different forks. #[derive(Clone, Debug)] -pub struct BroadcastableTransaction { +pub struct BroadcastableTransaction { /// The optional RPC URL. pub rpc: Option, /// The transaction to broadcast. - pub transaction: TransactionMaybeSigned, + pub transaction: TransactionMaybeSigned, } #[derive(Clone, Debug, Copy)] @@ -432,7 +413,7 @@ impl ArbitraryStorage { } /// List of transactions that can be broadcasted. -pub type BroadcastableTransactions = VecDeque; +pub type BroadcastableTransactions = VecDeque>; /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// @@ -452,7 +433,10 @@ pub type BroadcastableTransactions = VecDeque; /// cheatcode address: by default, the caller, test contract and newly deployed contracts are /// allowed to execute cheatcodes #[derive(Clone, Debug)] -pub struct Cheatcodes { +pub struct Cheatcodes< + CTX: FoundryContextExt = Context, + N: Network = Ethereum, +> { /// Solar compiler instance, to grant syntactic and semantic analysis capabilities pub analysis: Option, @@ -460,7 +444,7 @@ pub struct Cheatcodes { /// /// Used in the cheatcode handler to overwrite the block environment separately from the /// execution block environment. - pub block: Option, + pub block: Option, /// Currently active EIP-7702 delegations that will be consumed when building the next /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during @@ -531,7 +515,7 @@ pub struct Cheatcodes { pub broadcast: Option, /// Scripting based transactions - pub broadcastable_transactions: BroadcastableTransactions, + pub broadcastable_transactions: BroadcastableTransactions, /// Current EIP-2930 access lists. pub access_list: Option, @@ -591,7 +575,7 @@ pub struct Cheatcodes { /// Used to determine whether the broadcasted call has dynamic gas limit. pub dynamic_gas_limit: bool, // Custom execution evm version. - pub execution_evm_version: Option, + pub execution_evm_version: Option, } // This is not derived because calling this in `fn new` with `..Default::default()` creates a second @@ -686,7 +670,7 @@ impl Cheatcodes { } /// Decodes the input data and applies the cheatcode. - fn apply_cheatcode( + fn apply_cheatcode( &mut self, ecx: &mut CTX, call: &CallInputs, @@ -776,7 +760,7 @@ impl Cheatcodes { } } - pub fn call_with_executor( + pub fn call_with_executor( &mut self, ecx: &mut CTX, call: &mut CallInputs, @@ -990,22 +974,22 @@ impl Cheatcodes { }); } - let input = TransactionInput::new(call.input.bytes(ecx)); + let input = call.input.bytes(ecx); let chain_id = ecx.cfg().chain_id(); let rpc = ecx.db().active_fork_url(); let account = ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap(); - let mut tx_req = TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(TxKind::from(Some(call.target_address))), - value: call.transfer_value(), - input, - nonce: Some(account.info.nonce), - chain_id: Some(chain_id), - gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, - ..Default::default() - }; + let mut tx_req = TransactionRequest::default() + .with_from(broadcast.new_origin) + .with_to(call.target_address) + .with_value(call.transfer_value().unwrap_or_default()) + .with_input(input) + .with_nonce(account.info.nonce) + .with_chain_id(chain_id); + if is_fixed_gas_limit { + tx_req.set_gas_limit(call.gas_limit) + } let active_delegations = std::mem::take(&mut self.active_delegations); // Set active blob sidecar, if any. @@ -1039,11 +1023,13 @@ impl Cheatcodes { account.info.nonce += 1; } } - tx_req.authorization_list = Some(active_delegations); + tx_req.set_authorization_list(active_delegations); } - self.broadcastable_transactions - .push_back(BroadcastableTransaction { rpc, transaction: tx_req.into() }); + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc, + transaction: TransactionMaybeSigned::new(tx_req), + }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); // Explicitly increment nonce if calls are not isolated. @@ -1193,12 +1179,12 @@ impl Cheatcodes { } } -impl Inspector for Cheatcodes { +impl Inspector for Cheatcodes { fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { - *ecx.block_mut() = block; + ecx.set_block(block); } if let Some(gas_price) = self.gas_price.take() { ecx.tx_mut().set_gas_price(gas_price); @@ -1787,15 +1773,14 @@ impl Inspector for Cheatcodes { let account = &ecx.journal().evm_state()[&broadcast.new_origin]; self.broadcastable_transactions.push_back(BroadcastableTransaction { rpc, - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: None, - value: Some(input.value()), - input: TransactionInput::new(input.init_code()), - nonce: Some(account.info.nonce), - ..Default::default() - } - .into(), + transaction: TransactionMaybeSigned::new( + TransactionRequest::default() + .with_from(broadcast.new_origin) + .with_kind(TxKind::Create) + .with_value(input.value()) + .with_input(input.init_code()) + .with_nonce(account.info.nonce), + ), }); input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); @@ -1968,7 +1953,7 @@ impl Inspector for Cheatcodes { } } -impl FoundryInspectorExt for Cheatcodes { +impl InspectorExt for Cheatcodes { fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { if let CreateScheme::Create2 { .. } = inputs.scheme() { let target_depth = if let Some(prank) = &self.get_prank(depth) { @@ -2600,7 +2585,7 @@ fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { } /// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch( +fn apply_dispatch( calls: &Vm::VmCalls, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 02fa0777def82..bae97dab5b7a2 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -352,11 +352,15 @@ impl Cheatcode for writeJson_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; - // Read, parse, and update the JSON object + // Read, parse, and update the JSON object. + // If the file doesn't exist, start with an empty JSON object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data_string = fs::locked_read_to_string(&data_path)?; - let mut data = - serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string)); + let mut data = if data_path.exists() { + let data_string = fs::locked_read_to_string(&data_path)?; + serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string)) + } else { + Value::Object(Default::default()) + }; upsert_json_value(&mut data, value, valueKey)?; // Write the updated content back to the file diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 7775d61841b2c..3c609cc2dbf19 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -22,9 +22,9 @@ use revm::context::{ContextTr, JournalTr}; pub use Vm::ForgeContext; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; +pub use foundry_evm_core::{EthCheatCtx, evm::NestedEvmClosure}; pub use inspector::{ BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, - CheatsCtxExt, NestedEvmClosure, }; pub use spec::{CheatcodeDef, Vm}; @@ -77,7 +77,7 @@ pub(crate) trait Cheatcode: CheatcodeDef { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { self.apply(ccx.state) } @@ -85,7 +85,7 @@ pub(crate) trait Cheatcode: CheatcodeDef { /// /// Implement this function if you need access to the executor. #[inline(always)] - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 1284f6d51a230..9f5c88d57405d 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -384,18 +384,13 @@ impl Wallets { /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. pub fn signers(&self) -> Result> { - let mut wallets = self.inner.lock(); - let (signers, browser) = wallets.multi_wallet.signers()?; - Ok(signers.keys().copied().chain(browser.map(|b| b.address())).collect()) + Ok(self.inner.lock().multi_wallet.signers()?.keys().copied().collect()) } /// Number of signers in the [MultiWallet]. pub fn len(&self) -> usize { let mut inner = self.inner.lock(); - match inner.multi_wallet.signers() { - Ok((signers, browser)) => signers.len() + usize::from(browser.is_some()), - Err(_) => 0, - } + inner.multi_wallet.signers().map_or(0, |signers| signers.len()) } /// Whether the [MultiWallet] is empty. @@ -424,7 +419,7 @@ fn broadcast>( if let Some(provided_sender) = wallets.provided_sender { new_origin = Some(provided_sender); } else { - let (signers, _) = wallets.multi_wallet.signers()?; + let signers = wallets.multi_wallet.signers()?; if signers.len() == 1 { let address = signers.keys().next().unwrap(); new_origin = Some(*address); diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index c6441007ab0ce..5b5491afb58a8 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,15 +1,11 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, EthCheatCtx, Result, Vm::*}; use alloy_chains::Chain as AlloyChain; use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; -use foundry_evm_core::{ - backend::{DatabaseExt, FoundryJournalExt}, - constants::MAGIC_SKIP, - env::FoundryContextExt, -}; +use foundry_evm_core::constants::MAGIC_SKIP; use revm::context::{ContextTr, JournalTr}; use std::str::FromStr; @@ -71,20 +67,14 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skip_0Call { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest } = *self; skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) } } impl Cheatcode for skip_1Call { - fn apply_stateful>( - &self, - ccx: &mut CheatsCtxt<'_, CTX>, - ) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest, reason } = self; if *skipTest { // Skip should not work if called deeper than at test level. diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index dd8c5ae94b853..88f38b700e993 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -1,4 +1,4 @@ -use crate::{CheatcodesExecutor, CheatsCtxExt, CheatsCtxt, Result, Vm::*}; +use crate::{CheatcodesExecutor, CheatsCtxt, EthCheatCtx, Result, Vm::*}; use alloy_primitives::{I256, U256, U512}; use foundry_evm_core::{ abi::console::{format_units_int, format_units_uint}, @@ -211,7 +211,7 @@ fn handle_assertion_result_mono>( if ccx.state.config.assertions_revert { Err(msg.into_owned().into()) } else { - executor.console_log(ccx.state, &msg); + executor.console_log(&msg); ccx.ecx.journal_mut().sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; Ok(Default::default()) } @@ -246,7 +246,7 @@ macro_rules! impl_assertions { (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr) => { impl crate::Cheatcode for $no_error { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -260,7 +260,7 @@ macro_rules! impl_assertions { } impl crate::Cheatcode for $with_error { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs index 6b1b4f6d9ceab..3b0610ebf07a4 100644 --- a/crates/cheatcodes/src/test/revert_handlers.rs +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -102,7 +102,8 @@ fn handle_revert( // If expected reason is `Error(string)` then decode and compare with actual revert. // See - if let Ok(e) = get_error("Error(string)") + if expected_reason.len() >= 4 + && let Ok(e) = get_error("Error(string)") && let Ok(dec) = e.decode_error(expected_reason) && let Some(DynSolValue::String(revert_str)) = dec.body.first() && revert_str.as_str() == String::from_utf8_lossy(&actual_revert) diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs index 7fb8210c88d66..98b46a33f9f1d 100644 --- a/crates/cheatcodes/src/toml.rs +++ b/crates/cheatcodes/src/toml.rs @@ -205,13 +205,15 @@ impl Cheatcode for writeToml_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; - // Read and parse the TOML file + // Read and parse the TOML file. + // If the file doesn't exist, start with an empty object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let toml_data = fs::locked_read_to_string(&data_path)?; - - // Convert to JSON and update the object - let mut json_data: JsonValue = - toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?; + let mut json_data: JsonValue = if data_path.exists() { + let toml_data = fs::locked_read_to_string(&data_path)?; + toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))? + } else { + JsonValue::Object(Default::default()) + }; upsert_json_value(&mut json_data, value, valueKey)?; // Serialize back to TOML and write the updated content back to the file diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 491a321525d9e..f025410e3e458 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -177,7 +177,7 @@ impl Cheatcode for randomBytes8Call { } impl Cheatcode for pauseTracingCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, @@ -200,7 +200,7 @@ impl Cheatcode for pauseTracingCall { } impl Cheatcode for resumeTracingCall { - fn apply_full( + fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 8ebeca5ee2549..9fa67804352e7 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -199,13 +199,13 @@ impl SessionSource { } async fn build_runner(&mut self, final_pc: usize) -> Result { - let env = self.config.evm_opts.env().await?; + let (evm_env, tx_env) = self.config.evm_opts.env().await?; let backend = match self.config.backend.clone() { Some(backend) => backend, None => { let fork = - self.config.evm_opts.get_fork(&self.config.foundry_config, env.evm_env.clone()); + self.config.evm_opts.get_fork(&self.config.foundry_config, evm_env.clone()); let backend = Backend::spawn(fork)?; self.config.backend = Some(backend.clone()); backend @@ -231,7 +231,7 @@ impl SessionSource { .gas_limit(self.config.evm_opts.gas_limit()) .spec_id(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) - .build(env, backend); + .build(evm_env, tx_env, backend); Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone())) } diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index c173ebe3fd5f8..ce1234528a94b 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -3,6 +3,7 @@ mod chain; mod dependency; mod evm; mod global; +mod network; mod rpc; mod tempo; mod transaction; @@ -12,6 +13,7 @@ pub use chain::*; pub use dependency::*; pub use evm::*; pub use global::*; +pub use network::*; pub use rpc::*; pub use tempo::*; pub use transaction::*; diff --git a/crates/cli/src/opts/network.rs b/crates/cli/src/opts/network.rs new file mode 100644 index 0000000000000..f78af85842c95 --- /dev/null +++ b/crates/cli/src/opts/network.rs @@ -0,0 +1,11 @@ +/// Network selection, defaulting to Ethereum +#[derive(Clone, Debug, Default, clap::ValueEnum)] +pub enum NetworkVariant { + /// Ethereum (default) + #[default] + Ethereum, + /// Optimism / OP-stack + Optimism, + /// Tempo + Tempo, +} diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 722a80fbe2d3c..2dc021f12b4b1 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -120,7 +120,7 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool { | NamedChain::AcalaMandalaTestnet | NamedChain::AcalaTestnet | NamedChain::Etherlink - | NamedChain::EtherlinkTestnet + | NamedChain::EtherlinkShadownet | NamedChain::Karura | NamedChain::KaruraTestnet | NamedChain::Mantle diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 99156568b8c37..852b1ab2141ea 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,6 +17,7 @@ foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-primitives.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index b62dd39ed8ceb..36407b3f0c9f2 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -519,8 +519,8 @@ validAfter {}", self.nonce_key.pretty(), self.nonce.pretty(), self.fee_payer_signature.pretty(), - self.valid_after.pretty(), self.valid_before.pretty(), + self.valid_after.pretty(), ) } } @@ -815,24 +815,37 @@ blobGasUsed {}", pub trait UIfmtHeaderExt { fn size_pretty(&self) -> String; + fn total_difficulty_pretty(&self) -> String; } impl UIfmtHeaderExt for Header { fn size_pretty(&self) -> String { self.size.pretty() } + + fn total_difficulty_pretty(&self) -> String { + self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() + } } impl UIfmtHeaderExt for AnyRpcHeader { fn size_pretty(&self) -> String { self.size.pretty() } + + fn total_difficulty_pretty(&self) -> String { + self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() + } } impl UIfmtHeaderExt for TempoHeaderResponse { fn size_pretty(&self) -> String { self.inner.size.pretty() } + + fn total_difficulty_pretty(&self) -> String { + self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty() + } } pub trait UIfmtSignatureExt { @@ -1054,7 +1067,7 @@ where "size" => Some(block.header().size_pretty()), "stateRoot" | "state_root" => Some(block.header().state_root().pretty()), "timestamp" => Some(block.header().timestamp().pretty()), - "totalDifficulty" | "total_difficulty" => Some(block.header().difficulty().pretty()), + "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()), "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()), "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()), "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()), @@ -1143,7 +1156,7 @@ requestsHash {}", header.timestamp().pretty(), fmt_timestamp(header.timestamp()), header.withdrawals_root().pretty(), - header.difficulty().pretty(), + header.total_difficulty_pretty(), header.blob_gas_used().pretty(), header.excess_blob_gas().pretty(), header.requests_hash().pretty(), @@ -1638,7 +1651,7 @@ yParity 0" "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", - "difficulty": "0x27f07", + "difficulty": "0x1", "totalDifficulty": "0x27f07", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "size": "0x27f07", @@ -1660,7 +1673,7 @@ yParity 0" get_pretty_block_attr::(&block, "baseFeePerGas") ); assert_eq!( - Some("163591".to_string()), + Some("1".to_string()), get_pretty_block_attr::(&block, "difficulty") ); assert_eq!( @@ -1728,6 +1741,10 @@ yParity 0" Some("163591".to_string()), get_pretty_block_attr::(&block, "totalDifficulty") ); + + let pretty = pretty_generic_header_response(block.header()); + assert!(pretty.contains("difficulty 1"), "{pretty}"); + assert!(pretty.contains("totalDifficulty 163591"), "{pretty}"); } #[test] diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 893e7806d1dbc..c0bdb636796d4 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -1,18 +1,17 @@ //! Wrappers for transactions. -use alloy_consensus::{Transaction, TxEnvelope, transaction::SignerRecoverable}; +use alloy_consensus::{Transaction, transaction::SignerRecoverable}; use alloy_eips::eip7702::SignedAuthorization; -use alloy_network::{ - AnyTransactionReceipt, Ethereum, Network, TransactionBuilder7702, TransactionResponse, -}; -use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_network::{AnyTransactionReceipt, Network, TransactionResponse}; +use alloy_primitives::{Address, Bytes, U256}; use alloy_provider::{ Provider, network::{AnyNetwork, ReceiptResponse, TransactionBuilder}, }; -use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_rpc_types::BlockId; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +use foundry_primitives::FoundryTransactionBuilder; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason @@ -93,7 +92,11 @@ revertReason {}", } } -impl UIfmt for TransactionMaybeSigned { +impl UIfmt for TransactionMaybeSigned +where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, +{ fn pretty(&self) -> String { match self { Self::Signed { tx, .. } => tx.pretty(), @@ -111,22 +114,22 @@ nonce {} to {} type {} value {}", - tx.access_list + tx.access_list() .as_ref() .map(|a| a.iter().collect::>()) .unwrap_or_default() .pretty(), - tx.chain_id.pretty(), + tx.chain_id().pretty(), tx.gas_limit().unwrap_or_default(), - tx.gas_price.pretty(), - tx.input.input.pretty(), - tx.max_fee_per_blob_gas.pretty(), - tx.max_fee_per_gas.pretty(), - tx.max_priority_fee_per_gas.pretty(), - tx.nonce.pretty(), - tx.to.as_ref().map(|a| a.to()).unwrap_or_default().pretty(), - tx.transaction_type.unwrap_or_default(), - tx.value.pretty(), + tx.gas_price().pretty(), + tx.input().pretty(), + tx.max_fee_per_blob_gas().pretty(), + tx.max_fee_per_gas().pretty(), + tx.max_priority_fee_per_gas().pretty(), + tx.nonce().pretty(), + tx.to().pretty(), + tx.output_tx_type(), + tx.value().pretty(), ), } } @@ -157,11 +160,11 @@ where } /// Used for broadcasting transactions -/// A transaction can either be a [`TransactionRequest`] waiting to be signed -/// or a [`TxEnvelope`], already signed +/// A transaction can either be a `TransactionRequest` waiting to be signed +/// or a `TxEnvelope`, already signed #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] -pub enum TransactionMaybeSigned { +pub enum TransactionMaybeSigned { Signed { #[serde(flatten)] tx: N::TxEnvelope, @@ -212,10 +215,10 @@ impl TransactionMaybeSigned { } } - pub fn to(&self) -> Option { + pub fn to(&self) -> Option
{ match self { - Self::Signed { tx, .. } => Some(tx.kind()), - Self::Unsigned(tx) => tx.kind(), + Self::Signed { tx, .. } => tx.to(), + Self::Unsigned(tx) => tx.to(), } } @@ -242,7 +245,7 @@ impl TransactionMaybeSigned { pub fn authorization_list(&self) -> Option> where - N::TransactionRequest: TransactionBuilder7702, + N::TransactionRequest: FoundryTransactionBuilder, { match self { Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()), @@ -252,20 +255,6 @@ impl TransactionMaybeSigned { } } -impl From for TransactionMaybeSigned { - fn from(tx: TransactionRequest) -> Self { - Self::new(tx) - } -} - -impl TryFrom for TransactionMaybeSigned { - type Error = alloy_consensus::crypto::RecoveryError; - - fn try_from(tx: TxEnvelope) -> core::result::Result { - Self::new_signed(tx) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index cb964f97f68be..d5bb4baa980ab 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -71,16 +71,25 @@ pub struct FuzzDictionaryConfig { /// Once the fuzzer exceeds this limit, it will start evicting random entries /// /// This limit is put in place to prevent memory blowup. - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] pub max_fuzz_dictionary_addresses: usize, /// How many values to record at most. /// Once the fuzzer exceeds this limit, it will start evicting random entries - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] pub max_fuzz_dictionary_values: usize, /// How many literal values to seed from the AST, at most. /// /// This value is independent from the max amount of addresses and values. - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] pub max_fuzz_dictionary_literals: usize, } diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 624dd5a35f81b..8570397a37d20 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -213,6 +213,21 @@ where deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom) } +/// Serialize a `usize` as `"max"` if it equals `usize::MAX`, as a string if it exceeds +/// `i64::MAX` (TOML integer limit), or as a plain number otherwise. +pub(crate) fn serialize_usize_or_max(value: &usize, serializer: S) -> Result +where + S: Serializer, +{ + if *value == usize::MAX { + serializer.serialize_str("max") + } else if *value > i64::MAX as usize { + serializer.serialize_str(&value.to_string()) + } else { + serializer.serialize_u64(*value as u64) + } +} + /// Deserialize into `U256` from either a `u64`, a `U256` hex string, or a decimal string. pub fn deserialize_u64_to_u256<'de, D>(deserializer: D) -> Result where diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 18d9458d46c1b..c3db588640cdc 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -39,6 +39,7 @@ alloy-consensus.workspace = true alloy-rpc-types.workspace = true alloy-sol-types.workspace = true foundry-fork-db.workspace = true +op-alloy-rpc-types.workspace = true revm = { workspace = true, features = [ "std", @@ -68,4 +69,6 @@ tracing.workspace = true url.workspace = true [dev-dependencies] +alloy-serde.workspace = true +op-alloy-consensus.workspace = true foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 8c80b21121c37..b4a484304c9a0 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -2,17 +2,16 @@ use super::BackendError; use crate::{ - Env, InspectorExt, + FoundryInspectorExt, backend::{ Backend, DatabaseExt, JournaledState, LocalForkId, RevertStateSnapshotAction, diagnostic::RevertDiagnostic, }, fork::{CreateFork, ForkId}, }; -use alloy_evm::{Evm, EvmEnv}; +use alloy_evm::{Evm, EvmEnv, eth::EthEvmContext}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; -use alloy_rpc_types::TransactionRequest; +use alloy_primitives::{Address, B256, TxKind, U256}; use eyre::WrapErr; use foundry_fork_db::DatabaseError; use revm::{ @@ -48,15 +47,15 @@ pub struct CowBackend<'a> { /// /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. pub backend: Cow<'a, Backend>, - /// The [SpecId] to initialize the backend with on first mutable access. + /// Pending initialization params for the backend on first mutable access. /// `None` means the backend has already been initialized for the current call. - spec_id: Option, + pending_init: Option<(SpecId, Address, TxKind)>, } impl<'a> CowBackend<'a> { /// Creates a new `CowBackend` with the given `Backend`. pub fn new_borrowed(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), spec_id: Some(SpecId::default()) } + Self { backend: Cow::Borrowed(backend), pending_init: None } } /// Executes the configured transaction of the `env` without committing state changes @@ -64,25 +63,22 @@ impl<'a> CowBackend<'a> { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( + pub fn inspect FoundryInspectorExt>>( &mut self, - env: &mut Env, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, inspector: I, ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state - self.spec_id = Some(env.evm_env.cfg_env.spec); + self.pending_init = Some((evm_env.cfg_env.spec, tx_env.caller, tx_env.kind)); - let mut evm = crate::evm::new_evm_with_inspector( - self, - env.evm_env.clone(), - env.tx.clone(), - inspector, - ); + let mut evm = crate::evm::new_eth_evm_with_inspector(self, evm_env.clone(), inspector); - let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; + let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; - *env = Env::from(evm.cfg.clone(), evm.block.clone(), evm.tx.clone()); + *evm_env = EvmEnv::new(evm.cfg.clone(), evm.block.clone()); + *tx_env = evm.tx.clone(); Ok(res) } @@ -97,12 +93,10 @@ impl<'a> CowBackend<'a> { /// Returns a mutable instance of the Backend. /// /// If this is the first time this is called, the backed is cloned and initialized. - fn backend_mut(&mut self, evm_env: &EvmEnv, tx_env: &TxEnv) -> &mut Backend { - if let Some(spec_id) = self.spec_id.take() { + fn backend_mut(&mut self) -> &mut Backend { + if let Some((spec_id, caller, tx_kind)) = self.pending_init.take() { let backend = self.backend.to_mut(); - let mut env = Env { evm_env: evm_env.clone(), tx: tx_env.clone() }; - env.evm_env.cfg_env.spec = spec_id; - backend.initialize(&env); + backend.initialize(spec_id, caller, tx_kind); return backend; } self.backend.to_mut() @@ -110,7 +104,7 @@ impl<'a> CowBackend<'a> { /// Returns a mutable instance of the Backend if it is initialized. fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { - if self.spec_id.is_none() { + if self.pending_init.is_none() { return Some(self.backend.to_mut()); } None @@ -119,7 +113,7 @@ impl<'a> CowBackend<'a> { impl DatabaseExt for CowBackend<'_> { fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256 { - self.backend_mut(evm_env, &TxEnv::default()).snapshot_state(journaled_state, evm_env) + self.backend_mut().snapshot_state(journaled_state, evm_env) } fn revert_state( @@ -130,7 +124,7 @@ impl DatabaseExt for CowBackend<'_> { tx_env: &mut TxEnv, action: RevertStateSnapshotAction, ) -> Option { - self.backend_mut(evm_env, tx_env).revert_state(id, journaled_state, evm_env, tx_env, action) + self.backend_mut().revert_state(id, journaled_state, evm_env, tx_env, action) } fn delete_state_snapshot(&mut self, id: U256) -> bool { @@ -166,7 +160,7 @@ impl DatabaseExt for CowBackend<'_> { tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(evm_env, tx_env).select_fork(id, evm_env, tx_env, journaled_state) + self.backend_mut().select_fork(id, evm_env, tx_env, journaled_state) } fn roll_fork( @@ -177,13 +171,7 @@ impl DatabaseExt for CowBackend<'_> { tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(evm_env, tx_env).roll_fork( - id, - block_number, - evm_env, - tx_env, - journaled_state, - ) + self.backend_mut().roll_fork(id, block_number, evm_env, tx_env, journaled_state) } fn roll_fork_to_transaction( @@ -194,7 +182,7 @@ impl DatabaseExt for CowBackend<'_> { tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(evm_env, tx_env).roll_fork_to_transaction( + self.backend_mut().roll_fork_to_transaction( id, transaction, evm_env, @@ -210,33 +198,19 @@ impl DatabaseExt for CowBackend<'_> { evm_env: EvmEnv, tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()> { - self.backend_mut(&evm_env, &tx_env).transact( - id, - transaction, - evm_env, - tx_env, - journaled_state, - inspector, - ) + self.backend_mut().transact(id, transaction, evm_env, tx_env, journaled_state, inspector) } fn transact_from_tx( &mut self, - transaction: &TransactionRequest, + tx_env: &TxEnv, evm_env: EvmEnv, - tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()> { - self.backend_mut(&evm_env, &tx_env).transact_from_tx( - transaction, - evm_env, - tx_env, - journaled_state, - inspector, - ) + self.backend_mut().transact_from_tx(tx_env, evm_env, journaled_state, inspector) } fn active_fork_id(&self) -> Option { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 61f03d8608412..5719121fd4eb0 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1,34 +1,28 @@ //! Foundry's main executor backend abstraction and implementation. use crate::{ - Env, InspectorExt, + FoundryBlock, FoundryInspectorExt, FoundryTransaction, TryAnyToTxEnv, constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, - evm::new_evm_with_inspector, + evm::new_eth_evm_with_inspector, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, utils::get_blob_base_fee_update_fraction, }; use alloy_consensus::{BlockHeader, Typed2718}; -use alloy_evm::{Evm, EvmEnv, FromRecoveredTx, rpc::TryIntoTxEnv}; +use alloy_evm::{Evm, EvmEnv, EvmFactory, eth::EthEvmContext}; use alloy_genesis::GenesisAccount; -use alloy_network::{ - AnyNetwork, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope, TransactionResponse, -}; +use alloy_network::{AnyNetwork, BlockResponse, Network, TransactionResponse}; use alloy_primitives::{Address, B256, TxKind, U256, keccak256, uint}; -use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; +use alloy_rpc_types::BlockNumberOrTag; use eyre::Context; use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender}; pub use foundry_fork_db::{BlockchainDb, SharedBackend, cache::BlockchainDbMeta}; use revm::{ - Database, DatabaseCommit, Journal, JournalEntry, + Database, DatabaseCommit, JournalEntry, bytecode::Bytecode, - context::{JournalInner, TxEnv}, - context_interface::{ - block::BlobExcessGasAndPrice, journaled_state::account::JournaledAccountTr, - result::ResultAndState, - }, + context::{BlockEnv, JournalInner, TxEnv}, + context_interface::{journaled_state::account::JournaledAccountTr, result::ResultAndState}, database::{CacheDB, DatabaseRef}, - inspector::{JournalExt, NoOpInspector}, precompile::{PrecompileSpecId, Precompiles}, primitives::{HashMap as Map, KECCAK_EMPTY, Log, hardfork::SpecId}, state::{Account, AccountInfo, EvmState, EvmStorageSlot}, @@ -55,7 +49,7 @@ mod snapshot; pub use snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapshot}; // A `revm::Database` that is used in forking mode -type ForkDB = CacheDB; +type ForkDB = CacheDB>; /// Represents a numeric `ForkId` valid only for the existence of the `Backend`. /// @@ -82,13 +76,19 @@ pub type JournaledState = JournalInner; /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] -pub trait DatabaseExt: Database + DatabaseCommit + Debug { +pub trait DatabaseExt: + Database + DatabaseCommit + Debug +{ /// Creates a new state snapshot at the current point of execution. /// /// A state snapshot is associated with a new unique id that's created for the snapshot. /// State snapshots can be reverted: [DatabaseExt::revert_state], however, depending on the /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. - fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256; + fn snapshot_state( + &mut self, + journaled_state: &JournaledState, + evm_env: &EvmEnv, + ) -> U256; /// Reverts the snapshot if it exists /// @@ -98,16 +98,16 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug /// **N.B.** While this reverts the state of the evm to the snapshot, it keeps new logs made /// since the snapshots was created. This way we can show logs that were emitted between /// snapshot and its revert. - /// This will also revert any changes in the `Env` and replace it with the captured `Env` of - /// `Self::snapshot_state`. + /// This will also revert any changes in the `EvmEnv` and `TxEnv` and replace them with the + /// captured values from `Self::snapshot_state`. /// /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, action: RevertStateSnapshotAction, ) -> Option; @@ -126,8 +126,8 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug fn create_select_fork( &mut self, fork: CreateFork, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, journaled_state: &mut JournaledState, ) -> eyre::Result { let id = self.create_fork(fork)?; @@ -141,8 +141,8 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug fn create_select_fork_at_transaction( &mut self, fork: CreateFork, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, journaled_state: &mut JournaledState, transaction: B256, ) -> eyre::Result { @@ -163,7 +163,7 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug /// Selects the fork's state /// - /// This will also modify the current `Env`. + /// This will also modify the current `EvmEnv` and `TxEnv`. /// /// **Note**: this does not change the local state, but swaps the remote state /// @@ -173,8 +173,8 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug fn select_fork( &mut self, id: LocalForkId, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -189,8 +189,8 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug &mut self, id: Option, block_number: u64, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -206,8 +206,8 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug &mut self, id: Option, transaction: B256, - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TX, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -216,20 +216,19 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug &mut self, id: Option, transaction: B256, - evm_env: EvmEnv, - tx_env: TxEnv, + evm_env: EvmEnv, + tx_env: TX, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()>; /// Executes a given TransactionRequest, commits the new state to the DB fn transact_from_tx( &mut self, - transaction: &TransactionRequest, - evm_env: EvmEnv, - tx_env: TxEnv, + tx_env: &TX, + evm_env: EvmEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on @@ -388,38 +387,6 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug struct _ObjectSafe(dyn DatabaseExt); -/// Extension trait for [`Journal`] providing borrow splitting and state replacement. -/// -/// Generic code accesses the journal via `ctx.journal_mut()` which returns `&mut impl JournalTr`. -/// This trait adds the ability to split the journal into its database and inner state components, -/// enabling direct [`DatabaseExt`] method calls with zero-copy borrow splitting. -pub trait FoundryJournalExt: JournalExt { - /// Returns mutable references to the database and journal inner state. - /// - /// Enables calling [`DatabaseExt`] methods directly, e.g.: - /// ```ignore - /// let (journal, env) = ctx.journal_and_env_mut(); // FoundryContextExt - /// let (db, inner) = journal.as_db_and_inner(); // FoundryJournalExt - /// db.select_fork(id, &env, inner)?; // DatabaseExt - /// ``` - fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState); - - /// Replaces the journal inner state. - /// - /// Used by sub-EVM execution to write back modified state after running a closure. - fn set_inner(&mut self, inner: JournaledState); -} - -impl FoundryJournalExt for Journal { - fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState) { - (&mut self.database, &mut self.inner) - } - - fn set_inner(&mut self, inner: JournaledState) { - self.inner = inner; - } -} - /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -452,7 +419,7 @@ impl FoundryJournalExt for Journal { /// Multiple "forks" can be created `Backend::create_fork()`, however only 1 can be used by the /// `db`. However, their state can be hot-swapped by swapping the read half of `db` from one fork to /// another. -/// When swapping forks (`Backend::select_fork()`) we also update the current `Env` of the `EVM` +/// When swapping forks (`Backend::select_fork()`) we also update the current `EvmEnv` of the `EVM` /// accordingly, so that all `block.*` config values match /// /// When another for is selected [`DatabaseExt::select_fork()`] the entire storage, including @@ -474,9 +441,9 @@ impl FoundryJournalExt for Journal { /// after reverting the snapshot. #[derive(Clone, Debug)] #[must_use] -pub struct Backend { +pub struct Backend { /// The access point for managing forks - forks: MultiFork, + forks: MultiFork, // The default in memory db mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with @@ -501,10 +468,13 @@ pub struct Backend { /// If this is set, then the Backend is currently in forking mode active_fork_ids: Option<(LocalForkId, ForkLookupIndex)>, /// holds additional Backend data - inner: BackendInner, + inner: BackendInner, } -impl Backend { +impl Backend +where + N::TransactionResponse: TryAnyToTxEnv, +{ /// Creates a new Backend with a spawned multi fork thread. /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory @@ -519,7 +489,7 @@ impl Backend { /// database. /// /// Prefer using [`spawn`](Self::spawn) instead. - pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { + pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` let inner = BackendInner { @@ -556,7 +526,7 @@ impl Backend { /// as active pub(crate) fn new_with_fork( id: &ForkId, - fork: Fork, + fork: Fork, journaled_state: JournaledState, ) -> eyre::Result { let mut backend = Self::spawn(None)?; @@ -618,7 +588,7 @@ impl Backend { /// Returns all snapshots created in this backend pub fn state_snapshots( &self, - ) -> &StateSnapshots> { + ) -> &StateSnapshots>> { &self.inner.state_snapshots } @@ -673,7 +643,7 @@ impl Backend { pub(crate) fn update_fork_db( &self, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork, ) { self.update_fork_db_contracts( self.inner.persistent_accounts.iter().copied(), @@ -687,7 +657,7 @@ impl Backend { &self, accounts: impl IntoIterator, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork, ) { if let Some(db) = self.active_fork_db() { merge_account_data(accounts, db, active_journaled_state, target_fork) @@ -712,22 +682,22 @@ impl Backend { } /// Returns the currently active `Fork`, if any - pub fn active_fork(&self) -> Option<&Fork> { + pub fn active_fork(&self) -> Option<&Fork> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork(idx)) } /// Returns the currently active `Fork`, if any - pub fn active_fork_mut(&mut self) -> Option<&mut Fork> { + pub fn active_fork_mut(&mut self) -> Option<&mut Fork> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork_mut(idx)) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db(&self) -> Option<&ForkDB> { + pub fn active_fork_db(&self) -> Option<&ForkDB> { self.active_fork().map(|f| &f.db) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB> { + pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB> { self.active_fork_mut().map(|f| &mut f.db) } @@ -748,7 +718,7 @@ impl Backend { } /// Creates a snapshot of the currently active database - pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { + pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { if let Some((id, idx)) = self.active_fork_ids { let fork = self.inner.get_fork(idx).clone(); let fork_id = self.inner.ensure_fork_id(id).cloned().expect("Exists; qed"); @@ -784,18 +754,16 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &Env) { - self.set_caller(env.tx.caller); - self.set_spec_id(env.evm_env.cfg_env.spec); + pub(crate) fn initialize(&mut self, spec_id: SpecId, caller: Address, tx_kind: TxKind) { + self.set_caller(caller); + self.set_spec_id(spec_id); - let test_contract = match env.tx.kind { + let test_contract = match tx_kind { TxKind::Call(to) => to, TxKind::Create => { - let nonce = self - .basic_ref(env.tx.caller) - .map(|b| b.unwrap_or_default().nonce) - .unwrap_or_default(); - env.tx.caller.create(nonce) + let nonce = + self.basic_ref(caller).map(|b| b.unwrap_or_default().nonce).unwrap_or_default(); + caller.create(nonce) } }; self.set_test_contract(test_contract); @@ -806,22 +774,19 @@ impl Backend { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( + pub fn inspect FoundryInspectorExt>>( &mut self, - env: &mut Env, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, inspector: I, ) -> eyre::Result { - self.initialize(env); - let mut evm = crate::evm::new_evm_with_inspector( - self, - env.evm_env.to_owned(), - env.tx.to_owned(), - inspector, - ); + self.initialize(evm_env.cfg_env.spec, tx_env.caller, tx_env.kind); + let mut evm = crate::evm::new_eth_evm_with_inspector(self, evm_env.to_owned(), inspector); - let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; + let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; - *env = Env::from(evm.cfg.clone(), evm.block.clone(), evm.tx.clone()); + *evm_env = EvmEnv::new(evm.cfg.clone(), evm.block.clone()); + *tx_env = evm.tx.clone(); Ok(res) } @@ -888,22 +853,22 @@ impl Backend { &self, id: LocalForkId, transaction: B256, - ) -> eyre::Result<(u64, AnyRpcBlock)> { + ) -> eyre::Result<(u64, N::BlockResponse)> { let fork = self.inner.get_fork_by_id(id)?; - let tx = fork.db.db.get_transaction(transaction)?; + let tx = fork.backend().get_transaction(transaction)?; // get the block number we need to fork - if let Some(tx_block) = tx.block_number { - let block = fork.db.db.get_full_block(tx_block)?; + if let Some(tx_block) = tx.block_number() { + let block = fork.backend().get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; Ok((fork_block, block)) } else { - let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; + let block = fork.backend().get_full_block(BlockNumberOrTag::Latest)?; - let number = block.header.number(); + let number = block.header().number(); Ok((number, block)) } @@ -915,51 +880,67 @@ impl Backend { pub fn replay_until( &mut self, id: LocalForkId, - mut env: Env, + evm_env: EvmEnv, tx_hash: B256, journaled_state: &mut JournaledState, - ) -> eyre::Result>> { + ) -> eyre::Result> { trace!(?id, ?tx_hash, "replay until transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); - let fork_id = self.ensure_fork_id(id)?.clone(); let fork = self.inner.get_fork_by_id_mut(id)?; let full_block = - fork.db.db.get_full_block(env.evm_env.block_env.number.saturating_to::())?; + fork.backend().get_full_block(evm_env.block_env.number.saturating_to::())?; - for tx in full_block.inner.transactions.txns() { - // System transactions such as on L2s don't contain any pricing info so we skip them - // otherwise this would cause reverts - if is_known_system_sender(tx.inner().inner.signer()) - || tx.ty() == SYSTEM_TRANSACTION_TYPE - { - trace!(tx=?tx.tx_hash(), "skipping system transaction"); - continue; - } + // Collect non-system transactions up to and including the target. + let txs = full_block + .transactions() + .txns() + .filter(|tx| !is_known_system_sender(tx.from()) && tx.ty() != SYSTEM_TRANSACTION_TYPE); + let mut txs_to_replay = Vec::new(); + let mut target_tx = None; + for tx in txs { if tx.tx_hash() == tx_hash { - // found the target transaction - return Ok(Some(tx.inner.clone())); + target_tx = Some(tx.clone()); + break; } - trace!(tx=?tx.tx_hash(), "committing transaction"); - - commit_transaction( - tx, - &mut env, - journaled_state, - fork, - &fork_id, - &persistent_accounts, - &mut NoOpInspector, - )?; + txs_to_replay.push(tx.clone()); + } + + // Replay all preceding transactions using a single EVM + cloned ForkDB. + if !txs_to_replay.is_empty() { + let now = Instant::now(); + + // Clone the fork's CacheDB once. The underlying SharedBackend is Arc-backed, + // so only the local cache layer is actually duplicated. + let replay_db = fork.db.clone(); + let mut evm = alloy_evm::EthEvmFactory::default().create_evm(replay_db, evm_env); + + for tx in &txs_to_replay { + let tx_env: TxEnv = tx.try_any_to_tx_env()?; + trace!(tx=?tx.tx_hash(), "committing transaction"); + evm.transact_commit(tx_env).wrap_err("backend: failed committing transaction")?; + } + + // Extract the DB back and replace the fork's database with the replayed state. + fork.db = evm.into_db(); + + // Refresh journaled states from the updated database, preserving persistent + // accounts (cheatcode address, CREATE2 deployer, test contract, etc.). + fork.refresh_journaled_states(journaled_state, &persistent_accounts)?; + + trace!(elapsed=?now.elapsed(), count=txs_to_replay.len(), "replayed transactions"); } - Ok(None) + Ok(target_tx) } } -impl DatabaseExt for Backend { +impl DatabaseExt for Backend +where + N::TransactionResponse: TryAnyToTxEnv, +{ fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256 { trace!("create snapshot"); let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( @@ -1299,7 +1280,7 @@ impl DatabaseExt for Backend { self.roll_fork(Some(id), fork_block, evm_env, tx_env, journaled_state)?; // we need to update the env to the block - update_env_block(evm_env, &block); + update_env_block(evm_env, block.header()); // after we forked at the fork block we need to properly update the block env to the block // env of the tx's block @@ -1307,10 +1288,8 @@ impl DatabaseExt for Backend { .forks .update_block_env(self.inner.ensure_fork_id(id).cloned()?, evm_env.block_env.clone()); - let env = Env { evm_env: evm_env.clone(), tx: tx_env.clone() }; - // replay all transactions that came before - self.replay_until(id, env, transaction, journaled_state)?; + self.replay_until(id, evm_env.clone(), transaction, journaled_state)?; Ok(()) } @@ -1320,9 +1299,9 @@ impl DatabaseExt for Backend { maybe_id: Option, transaction: B256, mut evm_env: EvmEnv, - tx_env: TxEnv, + mut tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1331,7 +1310,7 @@ impl DatabaseExt for Backend { let tx = { let fork = self.inner.get_fork_by_id_mut(id)?; - fork.db.db.get_transaction(transaction)? + fork.backend().get_transaction(transaction)? }; // This is a bit ambiguous because the user wants to transact an arbitrary transaction in @@ -1342,13 +1321,13 @@ impl DatabaseExt for Backend { // So we modify the env to match the transaction's block. let (_fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; - update_env_block(&mut evm_env, &block); + update_env_block(&mut evm_env, block.header()); - let mut env = Env { evm_env, tx: tx_env }; let fork = self.inner.get_fork_by_id_mut(id)?; commit_transaction( &tx, - &mut env, + &mut evm_env, + &mut tx_env, journaled_state, fork, &fork_id, @@ -1359,29 +1338,20 @@ impl DatabaseExt for Backend { fn transact_from_tx( &mut self, - tx: &TransactionRequest, + tx_env: &TxEnv, evm_env: EvmEnv, - tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()> { - trace!(?tx, "execute signed transaction"); + trace!(?tx_env, "execute signed transaction"); self.commit(journaled_state.state.clone()); - let mut env = Env { evm_env, tx: tx_env }; let res = { - env.tx = tx.clone().try_into_tx_env(&env.evm_env)?; - let mut db = self.clone(); - let mut evm = new_evm_with_inspector( - &mut db, - env.evm_env.to_owned(), - env.tx.to_owned(), - inspector, - ); + let mut evm = new_eth_evm_with_inspector(&mut db, evm_env, inspector); evm.journaled_state.depth = journaled_state.depth + 1; - evm.transact(env.tx)? + evm.transact_raw(tx_env.to_owned())? }; self.commit(res.state); @@ -1555,7 +1525,10 @@ impl DatabaseExt for Backend { } } -impl DatabaseRef for Backend { +impl DatabaseRef for Backend +where + N::TransactionResponse: TryAnyToTxEnv, +{ type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -1591,7 +1564,10 @@ impl DatabaseRef for Backend { } } -impl DatabaseCommit for Backend { +impl DatabaseCommit for Backend +where + N::TransactionResponse: TryAnyToTxEnv, +{ fn commit(&mut self, changes: Map) { if let Some(db) = self.active_fork_db_mut() { db.commit(changes) @@ -1601,7 +1577,10 @@ impl DatabaseCommit for Backend { } } -impl Database for Backend { +impl Database for Backend +where + N::TransactionResponse: TryAnyToTxEnv, +{ type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { if let Some(db) = self.active_fork_db_mut() { @@ -1638,21 +1617,26 @@ impl Database for Backend { /// Variants of a [revm::Database] #[derive(Clone, Debug)] -pub enum BackendDatabaseSnapshot { +pub enum BackendDatabaseSnapshot { /// Simple in-memory [revm::Database] InMemory(FoundryEvmInMemoryDB), /// Contains the entire forking mode database - Forked(LocalForkId, ForkId, ForkLookupIndex, Box), + Forked(LocalForkId, ForkId, ForkLookupIndex, Box>), } /// Represents a fork #[derive(Clone, Debug)] -pub struct Fork { - db: ForkDB, +pub struct Fork { + db: ForkDB, journaled_state: JournaledState, } -impl Fork { +impl Fork { + /// Returns a reference to the underlying [`SharedBackend`]. + pub fn backend(&self) -> &SharedBackend { + &self.db.db + } + /// Returns true if the account is a contract pub fn is_contract(&self, acc: Address) -> bool { if let Ok(Some(acc)) = self.db.basic_ref(acc) @@ -1662,11 +1646,23 @@ impl Fork { } is_contract_in_state(&self.journaled_state.state, acc) } + + /// Refreshes the given journaled state and the fork's own journaled state from the + /// database, preserving persistent accounts. + fn refresh_journaled_states( + &mut self, + journaled_state: &mut JournaledState, + persistent_accounts: &HashSet
, + ) -> Result<(), BackendError> { + update_state(&mut journaled_state.state, &mut self.db, Some(persistent_accounts))?; + update_state(&mut self.journaled_state.state, &mut self.db, Some(persistent_accounts))?; + Ok(()) + } } /// Container type for various Backend related data #[derive(Clone, Debug)] -pub struct BackendInner { +pub struct BackendInner { /// Stores the `ForkId` of the fork the `Backend` launched with from the start. /// /// In other words if [`Backend::spawn()`] was called with a `CreateFork` command, to launch @@ -1689,9 +1685,9 @@ pub struct BackendInner { pub created_forks: HashMap, /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling - pub forks: Vec>, + pub forks: Vec>>, /// Contains state snapshots made at a certain point - pub state_snapshots: StateSnapshots>, + pub state_snapshots: StateSnapshots>>, /// Tracks whether there was a failure in a snapshot that was reverted /// /// The Test contract contains a bool variable that is set to true when an `assert` function @@ -1716,7 +1712,7 @@ pub struct BackendInner { pub cheatcode_access_accounts: HashSet
, } -impl BackendInner { +impl BackendInner { pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids .get(&id) @@ -1736,51 +1732,51 @@ impl BackendInner { /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork(&self, idx: ForkLookupIndex) -> &Fork { + fn get_fork(&self, idx: ForkLookupIndex) -> &Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_ref().unwrap() } /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork { + fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_mut().unwrap() } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id_mut(&mut self, id: LocalForkId) -> eyre::Result<&mut Fork> { + fn get_fork_by_id_mut(&mut self, id: LocalForkId) -> eyre::Result<&mut Fork> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork_mut(idx)) } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork> { + fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork(idx)) } /// Removes the fork - fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork { + fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].take().unwrap() } - fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork) { + fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork) { self.forks[idx] = Some(fork) } /// Returns an iterator over Forks - pub fn forks_iter(&self) -> impl Iterator + '_ { + pub fn forks_iter(&self) -> impl Iterator)> + '_ { self.issued_local_fork_ids .iter() .map(|(id, fork_id)| (*id, self.get_fork(self.created_forks[fork_id]))) } /// Returns a mutable iterator over all Forks - pub fn forks_iter_mut(&mut self) -> impl Iterator + '_ { + pub fn forks_iter_mut(&mut self) -> impl Iterator> + '_ { self.forks.iter_mut().filter_map(|f| f.as_mut()) } @@ -1790,7 +1786,7 @@ impl BackendInner { id: LocalForkId, fork_id: ForkId, idx: ForkLookupIndex, - fork: Fork, + fork: Fork, ) { self.created_forks.insert(fork_id.clone(), idx); self.issued_local_fork_ids.insert(id, fork_id); @@ -1802,7 +1798,7 @@ impl BackendInner { &mut self, id: LocalForkId, fork_id: ForkId, - db: ForkDB, + db: ForkDB, journaled_state: JournaledState, ) -> ForkLookupIndex { let idx = self.forks.len(); @@ -1818,7 +1814,7 @@ impl BackendInner { &mut self, id: LocalForkId, new_fork_id: ForkId, - backend: SharedBackend, + backend: SharedBackend, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1843,7 +1839,7 @@ impl BackendInner { pub fn insert_new_fork( &mut self, fork_id: ForkId, - db: ForkDB, + db: ForkDB, journaled_state: JournaledState, ) -> (LocalForkId, ForkLookupIndex) { let idx = self.forks.len(); @@ -1889,7 +1885,7 @@ impl BackendInner { } } -impl Default for BackendInner { +impl Default for BackendInner { fn default() -> Self { Self { launched_with_fork: None, @@ -1914,22 +1910,26 @@ impl Default for BackendInner { } /// This updates the currently used env with the fork's environment -pub(crate) fn update_current_env_with_fork_env( - evm_env: &mut EvmEnv, - tx_env: &mut TxEnv, - fork_evm_env: EvmEnv, +pub(crate) fn update_current_env_with_fork_env< + SPEC, + BLOCK: FoundryBlock, + TX: FoundryTransaction, +>( + evm_env: &mut EvmEnv, + tx_env: &mut TX, + fork_evm_env: EvmEnv, ) { - tx_env.chain_id = Some(fork_evm_env.cfg_env.chain_id); + tx_env.set_chain_id(Some(fork_evm_env.cfg_env.chain_id)); *evm_env = fork_evm_env; } /// Clones the data of the given `accounts` from the `active` database into the `fork_db` /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. -pub(crate) fn merge_account_data( +pub(crate) fn merge_account_data( accounts: impl IntoIterator, active: &CacheDB, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork, ) { for addr in accounts.into_iter() { merge_db_account_data(addr, active, &mut target_fork.db); @@ -1958,10 +1958,10 @@ fn merge_journaled_state_data( } /// Clones the account data from the `active` db into the `ForkDB` -fn merge_db_account_data( +fn merge_db_account_data( addr: Address, active: &CacheDB, - fork_db: &mut ForkDB, + fork_db: &mut ForkDB, ) { trace!(?addr, "merging database data"); @@ -1998,38 +1998,44 @@ fn is_contract_in_state(evm_state: &EvmState, acc: Address) -> bool { } /// Updates the evm env's block with the block's data -fn update_env_block(evm_env: &mut EvmEnv, block: &AnyRpcBlock) { +fn update_env_block( + evm_env: &mut EvmEnv, + header: &impl BlockHeader, +) { let block_env = &mut evm_env.block_env; - block_env.timestamp = U256::from(block.header.timestamp()); - block_env.beneficiary = block.header.beneficiary(); - block_env.difficulty = block.header.difficulty(); - block_env.prevrandao = Some(block.header.mix_hash().unwrap_or_default()); - block_env.basefee = block.header.base_fee_per_gas().unwrap_or_default(); - block_env.gas_limit = block.header.gas_limit(); - block_env.number = U256::from(block.header.number()); - - if let Some(excess_blob_gas) = block.header.excess_blob_gas() { - evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( + block_env.set_timestamp(U256::from(header.timestamp())); + block_env.set_beneficiary(header.beneficiary()); + block_env.set_difficulty(header.difficulty()); + block_env.set_prevrandao(header.mix_hash()); + block_env.set_basefee(header.base_fee_per_gas().unwrap_or_default()); + block_env.set_gas_limit(header.gas_limit()); + block_env.set_number(U256::from(header.number())); + + if let Some(excess_blob_gas) = header.excess_blob_gas() { + evm_env.block_env.set_blob_excess_gas_and_price( excess_blob_gas, - get_blob_base_fee_update_fraction(evm_env.cfg_env.chain_id, block.header.timestamp()), - )); + get_blob_base_fee_update_fraction(evm_env.cfg_env.chain_id, header.timestamp()), + ); } } /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an inspector. -fn commit_transaction( - tx: &AnyRpcTransaction, - env: &mut Env, +#[allow(clippy::too_many_arguments)] +fn commit_transaction( + tx: &N::TransactionResponse, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, - fork: &mut Fork, + fork: &mut Fork, fork_id: &ForkId, persistent_accounts: &HashSet
, - inspector: &mut dyn InspectorExt, -) -> eyre::Result<()> { - if let Some(tx_envelope) = tx.as_envelope() { - env.tx = TxEnv::from_recovered_tx(tx_envelope, tx.from()); - } + inspector: &mut dyn for<'db> FoundryInspectorExt>, +) -> eyre::Result<()> +where + N::TransactionResponse: TryAnyToTxEnv, +{ + *tx_env = tx.try_any_to_tx_env()?; let now = Instant::now(); let res = { @@ -2038,15 +2044,11 @@ fn commit_transaction( let depth = journaled_state.depth; let mut db = Backend::new_with_fork(fork_id, fork, journaled_state)?; - let mut evm = crate::evm::new_evm_with_inspector( - &mut db as _, - env.evm_env.to_owned(), - env.tx.to_owned(), - inspector, - ); + let mut evm = + crate::evm::new_eth_evm_with_inspector(&mut db as _, evm_env.to_owned(), inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. evm.journaled_state.depth = depth + 1; - evm.transact(env.tx.clone()).wrap_err("backend: failed committing transaction")? + evm.transact(tx_env.clone()).wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); @@ -2075,24 +2077,21 @@ pub fn update_state( /// Applies the changeset of a transaction to the active journaled state and also commits it in the /// forked db -fn apply_state_changeset( +fn apply_state_changeset( state: Map, journaled_state: &mut JournaledState, - fork: &mut Fork, + fork: &mut Fork, persistent_accounts: &HashSet
, ) -> Result<(), BackendError> { // commit the state and update the loaded accounts fork.db.commit(state); - - update_state(&mut journaled_state.state, &mut fork.db, Some(persistent_accounts))?; - update_state(&mut fork.journaled_state.state, &mut fork.db, Some(persistent_accounts))?; - - Ok(()) + fork.refresh_journaled_states(journaled_state, persistent_accounts) } #[cfg(test)] mod tests { use crate::{backend::Backend, opts::EvmOpts}; + use alloy_network::Ethereum; use alloy_primitives::{U256, address}; use alloy_provider::Provider; use foundry_common::provider::get_http_provider; @@ -2111,11 +2110,11 @@ mod tests { evm_opts.fork_url = Some(endpoint.to_string()); evm_opts.fork_block_number = Some(block_num); - let env = evm_opts.env().await.unwrap(); + let (evm_env, _) = evm_opts.env().await.unwrap(); - let fork = evm_opts.get_fork(&Config::default(), env.evm_env.clone()).unwrap(); + let fork = evm_opts.get_fork(&Config::default(), evm_env.clone()).unwrap(); - let backend = Backend::spawn(Some(fork)).unwrap(); + let backend = Backend::::spawn(Some(fork)).unwrap(); // some rng contract from etherscan let address = address!("0x63091244180ae240c87d1f528f5f269134cb07b3"); @@ -2129,7 +2128,7 @@ mod tests { let meta = BlockchainDbMeta { chain: None, - block_env: env.evm_env.block_env, + block_env: evm_env.block_env, hosts: Default::default(), }; diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 4f707ac295705..dd31cf100f908 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -1,47 +1,22 @@ +use std::fmt::Debug; + pub use alloy_evm::EvmEnv; +use alloy_evm::{FromRecoveredTx, ToTxEnv}; +use alloy_network::{AnyRpcTransaction, TransactionResponse}; use alloy_primitives::{Address, B256, Bytes, U256}; +use op_revm::{ + OpTransaction, + transaction::{OpTxTr, deposit::DEPOSIT_TRANSACTION_TYPE}, +}; use revm::{ - Context, Database, - context::{Block, BlockEnv, Cfg, CfgEnv, JournalTr, Transaction, TxEnv}, + Context, Database, Journal, + context::{Block, BlockEnv, Cfg, CfgEnv, Transaction, TxEnv}, context_interface::{ContextTr, transaction::AccessList}, + inspector::JournalExt, primitives::{TxKind, hardfork::SpecId}, }; -/// Helper container type for [`EvmEnv`] and [`TxEnv`]. -#[derive(Clone, Debug, Default)] -pub struct Env { - pub evm_env: EvmEnv, - pub tx: TxEnv, -} - -/// Helper container type for [`EvmEnv`] and [`TxEnv`]. -impl Env { - pub fn from(cfg: CfgEnv, block: BlockEnv, tx: TxEnv) -> Self { - Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx } - } - - pub fn new_with_spec_id(cfg: CfgEnv, block: BlockEnv, tx: TxEnv, spec_id: SpecId) -> Self { - let mut cfg = cfg; - cfg.spec = spec_id; - - Self::from(cfg, block, tx) - } - - /// Clones the evm env and tx env separately from a [`FoundryContextExt`] context. - pub fn clone_evm_and_tx(ecx: &mut impl FoundryContextExt) -> (EvmEnv, TxEnv) { - ( - EvmEnv { cfg_env: ecx.cfg_mut().clone(), block_env: ecx.block_mut().clone() }, - ecx.tx_mut().clone(), - ) - } - - /// Writes the split evm env and tx env back into a [`FoundryContextExt`] context. - pub fn apply_evm_and_tx(ecx: &mut impl FoundryContextExt, evm_env: EvmEnv, tx_env: TxEnv) { - *ecx.block_mut() = evm_env.block_env; - *ecx.cfg_mut() = evm_env.cfg_env; - *ecx.tx_mut() = tx_env; - } -} +use crate::backend::{DatabaseExt, JournaledState}; /// Extension of [`Block`] with mutable setters, allowing EVM-agnostic mutation of block fields. pub trait FoundryBlock: Block { @@ -153,6 +128,45 @@ pub trait FoundryTransaction: Transaction { /// Sets the max fee per blob gas. fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128); + + // `OpTransaction` methods + + /// Enveloped transaction bytes. + fn enveloped_tx(&self) -> Option<&Bytes> { + None + } + + /// Set Enveloped transaction bytes. + fn set_enveloped_tx(&mut self, _bytes: Bytes) {} + + /// Source hash of the deposit transaction. + fn source_hash(&self) -> Option { + None + } + + /// Sets source hash of the deposit transaction. + fn set_source_hash(&mut self, _source_hash: B256) {} + + /// Mint of the deposit transaction + fn mint(&self) -> Option { + None + } + + /// Sets mint of the deposit transaction. + fn set_mint(&mut self, _mint: u128) {} + + /// Whether the transaction is a system transaction + fn is_system_transaction(&self) -> bool { + false + } + + /// Sets whether the transaction is a system transaction + fn set_system_transaction(&mut self, _is_system_transaction: bool) {} + + /// Returns `true` if transaction is of type [`DEPOSIT_TRANSACTION_TYPE`]. + fn is_deposit(&self) -> bool { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE + } } impl FoundryTransaction for TxEnv { @@ -209,65 +223,91 @@ impl FoundryTransaction for TxEnv { } } -/// Extension of [`Cfg`] with mutable setters, allowing EVM-agnostic mutation of EVM configuration -/// fields. -pub trait FoundryCfg: Cfg { - /// Sets the EVM spec (hardfork). - fn set_spec(&mut self, spec: SpecId); +impl FoundryTransaction for OpTransaction { + fn set_tx_type(&mut self, tx_type: u8) { + self.base.set_tx_type(tx_type); + } - /// Sets the chain ID. - fn set_chain_id(&mut self, chain_id: u64); + fn set_caller(&mut self, caller: Address) { + self.base.set_caller(caller); + } - /// Sets the contract code size limit. - fn set_limit_contract_code_size(&mut self, limit: Option); + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } - /// Sets the contract initcode size limit. - fn set_limit_contract_initcode_size(&mut self, limit: Option); + fn set_gas_price(&mut self, gas_price: u128) { + self.base.set_gas_price(gas_price); + } - /// Sets whether nonce checks are disabled. - fn set_disable_nonce_check(&mut self, disabled: bool); + fn set_kind(&mut self, kind: TxKind) { + self.base.set_kind(kind); + } - /// Sets the max blobs per transaction. - fn set_max_blobs_per_tx(&mut self, max: Option); + fn set_value(&mut self, value: U256) { + self.base.set_value(value); + } - /// Sets the blob base fee update fraction. - fn set_blob_base_fee_update_fraction(&mut self, fraction: Option); + fn set_data(&mut self, data: Bytes) { + self.base.set_data(data); + } - /// Sets the transaction gas limit cap. - fn set_tx_gas_limit_cap(&mut self, cap: Option); -} + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } -impl FoundryCfg for CfgEnv { - fn set_spec(&mut self, spec: SpecId) { - self.spec = spec; + fn set_chain_id(&mut self, chain_id: Option) { + self.base.set_chain_id(chain_id); } - fn set_chain_id(&mut self, chain_id: u64) { - self.chain_id = chain_id; + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); } - fn set_limit_contract_code_size(&mut self, limit: Option) { - self.limit_contract_code_size = limit; + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.base.set_gas_priority_fee(gas_priority_fee); } - fn set_limit_contract_initcode_size(&mut self, limit: Option) { - self.limit_contract_initcode_size = limit; + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + OpTxTr::enveloped_tx(self) } - fn set_disable_nonce_check(&mut self, disabled: bool) { - self.disable_nonce_check = disabled; + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.enveloped_tx = Some(bytes); } - fn set_max_blobs_per_tx(&mut self, max: Option) { - self.max_blobs_per_tx = max; + fn source_hash(&self) -> Option { + OpTxTr::source_hash(self) } - fn set_blob_base_fee_update_fraction(&mut self, fraction: Option) { - self.blob_base_fee_update_fraction = fraction; + fn set_source_hash(&mut self, source_hash: B256) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.source_hash = source_hash; + } } - fn set_tx_gas_limit_cap(&mut self, cap: Option) { - self.tx_gas_limit_cap = cap; + fn mint(&self) -> Option { + OpTxTr::mint(self) + } + + fn set_mint(&mut self, mint: u128) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.mint = Some(mint); + } + } + + fn is_system_transaction(&self) -> bool { + OpTxTr::is_system_transaction(self) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.is_system_transaction = is_system_transaction; + } } } @@ -276,26 +316,265 @@ impl FoundryCfg for CfgEnv { /// [`ContextTr`] only exposes immutable references for block, tx, and cfg. /// Cheatcodes like `vm.warp()`, `vm.roll()`, `vm.chainId()` need to mutate these fields. pub trait FoundryContextExt: - ContextTr + ContextTr< + Block: FoundryBlock + Clone, + Tx: FoundryTransaction + Clone, + Cfg = CfgEnv, + Journal: JournalExt, + > { + /// Specification id type + /// + /// Bubbled-up from `ContextTr::Cfg` for convenience and simplified bounds. + type Spec: Into + Copy + Debug; + /// Mutable reference to the block environment. - fn block_mut(&mut self) -> &mut BlockEnv; + fn block_mut(&mut self) -> &mut Self::Block; + /// Mutable reference to the transaction environment. - fn tx_mut(&mut self) -> &mut TxEnv; + fn tx_mut(&mut self) -> &mut Self::Tx; + /// Mutable reference to the configuration environment. - fn cfg_mut(&mut self) -> &mut CfgEnv; + fn cfg_mut(&mut self) -> &mut Self::Cfg; + + /// Mutable reference to the db and the journal inner. + fn db_journal_inner_mut(&mut self) -> (&mut Self::Db, &mut JournaledState); + + /// Sets block environment. + fn set_block(&mut self, block: Self::Block) { + *self.block_mut() = block; + } + + /// Sets transaction environment. + fn set_tx(&mut self, tx: Self::Tx) { + *self.tx_mut() = tx; + } + + /// Sets configuration environment. + fn set_cfg(&mut self, cfg: Self::Cfg) { + *self.cfg_mut() = cfg; + } + + /// Sets journal inner. + fn set_journal_inner(&mut self, journal_inner: JournaledState) { + *self.db_journal_inner_mut().1 = journal_inner; + } + + /// Sets EVM environment. + fn set_evm(&mut self, evm_env: EvmEnv) { + *self.cfg_mut() = evm_env.cfg_env; + *self.block_mut() = evm_env.block_env; + } + + /// Cloned transaction environment. + fn tx_clone(&self) -> Self::Tx { + self.tx().clone() + } + + /// Cloned EVM environment (Cfg + Block). + fn evm_clone(&self) -> EvmEnv { + EvmEnv::new(self.cfg().clone(), self.block().clone()) + } } -impl, C> FoundryContextExt - for Context +impl< + BLOCK: FoundryBlock + Clone, + TX: FoundryTransaction + Clone, + SPEC: Into + Copy + Debug, + DB: Database, + C, +> FoundryContextExt for Context, DB, Journal, C> { - fn block_mut(&mut self) -> &mut BlockEnv { + type Spec = ::Spec; + + fn block_mut(&mut self) -> &mut Self::Block { &mut self.block } - fn tx_mut(&mut self) -> &mut TxEnv { + + fn tx_mut(&mut self) -> &mut Self::Tx { &mut self.tx } - fn cfg_mut(&mut self) -> &mut CfgEnv { + + fn cfg_mut(&mut self) -> &mut Self::Cfg { &mut self.cfg } + + fn db_journal_inner_mut(&mut self) -> (&mut Self::Db, &mut JournaledState) { + (&mut self.journaled_state.database, &mut self.journaled_state.inner) + } +} + +/// Temporary bound alias used during the transition to a fully generic foundry-evm and +/// foundry-cheatcodes. +/// +/// Pins the EVM context to Ethereum-specific environment types (`BlockEnv`, `TxEnv`, `CfgEnv`) +/// so that cheatcode implementations don't need to repeat the full where-clause everywhere. +/// Once cheatcodes are fully generic over network/environment types this alias will be removed. +pub trait EthCheatCtx: + FoundryContextExt< + Spec = SpecId, + Block = BlockEnv, + Tx = TxEnv, + Cfg = CfgEnv, + Db: DatabaseExt, + > +{ +} +impl EthCheatCtx for CTX where + CTX: FoundryContextExt< + Spec = SpecId, + Block = BlockEnv, + Tx = TxEnv, + Cfg = CfgEnv, + Db: DatabaseExt, + > +{ +} + +/// Abstraction trait for converting any RPC transaction into corresponding `TxEnv`. +/// +/// This trait bridges the gap between different network RPC transaction types and the EVM's +/// `TxEnv`: +/// - For [`alloy_rpc_types::Transaction`] (Ethereum): delegates to [`ToTxEnv`]. +/// - For [`AnyRpcTransaction`] (AnyNetwork): extracts the inner [`alloy_consensus::TxEnvelope`] via +/// [`as_envelope()`](alloy_network::AnyTxEnvelope::as_envelope) then delegates to +/// [`FromRecoveredTx`]. +/// - For [`op_alloy_rpc_types::Transaction`] (Optimism): delegates to [`ToTxEnv`]. +pub trait TryAnyToTxEnv { + /// Tries to convert this RPC transaction into a [`TxEnv`]. + fn try_any_to_tx_env(&self) -> eyre::Result; +} + +impl TryAnyToTxEnv for alloy_rpc_types::Transaction { + fn try_any_to_tx_env(&self) -> eyre::Result { + Ok(self.as_recovered().to_tx_env()) + } +} + +impl TryAnyToTxEnv for AnyRpcTransaction { + fn try_any_to_tx_env(&self) -> eyre::Result { + if let Some(envelope) = self.as_envelope() { + Ok(TxEnv::from_recovered_tx(envelope, self.from())) + } else { + eyre::bail!("cannot convert unknown transaction type to TxEnv") + } + } +} + +impl TryAnyToTxEnv> for op_alloy_rpc_types::Transaction { + fn try_any_to_tx_env(&self) -> eyre::Result> { + Ok(self.as_recovered().to_tx_env()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::{Sealed, Signed, TxEip1559, transaction::Recovered}; + use alloy_network::{AnyTxEnvelope, AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; + use alloy_primitives::Signature; + use alloy_rpc_types::{Transaction, TransactionInfo}; + use alloy_serde::WithOtherFields; + use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo}; + + fn make_signed_eip1559() -> Signed { + Signed::new_unchecked( + TxEip1559 { + chain_id: 1, + nonce: 42, + gas_limit: 21001, + to: TxKind::Call(Address::with_last_byte(0xBB)), + value: U256::from(101), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + B256::ZERO, + ) + } + + #[test] + fn try_any_to_tx_env_for_eth_and_any_transactions() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + let tx = Transaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let tx_env: TxEnv = tx.try_any_to_tx_env().unwrap(); + + assert_eq!(tx_env.caller, from); + assert_eq!(tx_env.nonce, 42); + assert_eq!(tx_env.gas_limit, 21001); + assert_eq!(tx_env.value, U256::from(101)); + assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); + + // Wrap as AnyRpcTransaction (Ethereum variant) via From>. + let any_tx = >::from(tx); + let any_tx_env: TxEnv = any_tx.try_any_to_tx_env().unwrap(); + + // TxEnv from AnyRpcTransaction must be the same + assert_eq!(tx_env, any_tx_env); + } + + #[test] + fn try_any_to_tx_env_for_op_transactions() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let eth_tx = Transaction::from_transaction( + Recovered::new_unchecked(signed_tx.clone().into(), from), + TransactionInfo::default(), + ); + let expected_base: TxEnv = eth_tx.try_any_to_tx_env().unwrap(); + + let op_tx = op_alloy_rpc_types::Transaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + OpTransactionInfo::default(), + ); + let op_tx_env: OpTransaction = op_tx.try_any_to_tx_env().unwrap(); + + assert_eq!(op_tx_env.base, expected_base); + + // Test with Deposit tx + let op_deposit_tx = op_alloy_rpc_types::Transaction::from_transaction( + Recovered::new_unchecked( + OpTxEnvelope::Deposit(Sealed::new(TxDeposit { + from, + mint: 1111, + ..Default::default() + })), + from, + ), + OpTransactionInfo::default(), + ); + let op_deposit_tx_env: OpTransaction = op_deposit_tx.try_any_to_tx_env().unwrap(); + + assert_eq!(op_deposit_tx_env.deposit.mint, Some(1111)); + assert_eq!(op_deposit_tx_env.base.caller, from); + } + + #[test] + fn try_any_to_tx_env_unknown_envelope_errors() { + let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { + hash: B256::ZERO, + inner: UnknownTypedTransaction { + ty: AnyTxType(0xFF), + fields: Default::default(), + memo: Default::default(), + }, + }); + let from = Address::random(); + let any_tx = AnyRpcTransaction::new(WithOtherFields::new(Transaction { + inner: Recovered::new_unchecked(unknown, from), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + block_timestamp: None, + })); + + let result = any_tx.try_any_to_tx_env().unwrap_err(); + assert!(result.to_string().contains("unknown transaction type")); + } } diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs index 7e2cc3da7393f..6ba7d4798b92d 100644 --- a/crates/evm/core/src/evm.rs +++ b/crates/evm/core/src/evm.rs @@ -4,24 +4,23 @@ use std::{ }; use crate::{ - Env, FoundryContextExt, InspectorExt, - backend::{DatabaseExt, FoundryJournalExt, JournaledState}, + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, }; use alloy_consensus::constants::KECCAK_EMPTY; -use alloy_evm::{Evm, EvmEnv, eth::EthEvmContext, precompiles::PrecompilesMap}; +use alloy_evm::{Evm, EvmEnv, EvmFactory, eth::EthEvmContext, precompiles::PrecompilesMap}; use alloy_primitives::{Address, Bytes, U256}; use foundry_fork_db::DatabaseError; use revm::{ - Context, Journal, + Context, context::{ - BlockEnv, CfgEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContext, - LocalContextTr, TxEnv, + BlockEnv, CfgEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContextTr, + TxEnv, result::{EVMError, ExecResultAndState, ExecutionResult, HaltReason, ResultAndState}, }, handler::{ - EthFrame, EthPrecompiles, EvmTr, FrameResult, FrameTr, Handler, ItemOrResult, - instructions::EthInstructions, + EthFrame, EvmTr, FrameResult, FrameTr, Handler, ItemOrResult, instructions::EthInstructions, }, inspector::{InspectorEvmTr, InspectorHandler}, interpreter::{ @@ -29,56 +28,43 @@ use revm::{ FrameInput, Gas, InstructionResult, InterpreterResult, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, return_ok, }, - precompile::{PrecompileSpecId, Precompiles}, primitives::hardfork::SpecId, }; -pub fn new_evm_with_inspector<'db, I: InspectorExt>( +pub fn new_revm_with_inspector< + 'db, + I: FoundryInspectorExt>, +>( + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, +) -> EthRevmEvm<'db, I> { + let mut revm = alloy_evm::EthEvmFactory::default() + .create_evm_with_inspector(db, evm_env, inspector) + .into_inner(); + revm.ctx.cfg.tx_chain_id_check = true; + revm.inspector.get_networks().inject_precompiles(&mut revm.precompiles); + revm +} + +pub fn new_eth_evm_with_inspector< + 'db, + I: FoundryInspectorExt>, +>( db: &'db mut dyn DatabaseExt, evm_env: EvmEnv, - tx_env: TxEnv, inspector: I, ) -> FoundryEvm<'db, I> { - let mut ctx = EthEvmContext { - journaled_state: { - let mut journal = Journal::new(db); - journal.set_spec_id(evm_env.cfg_env.spec); - journal - }, - block: evm_env.block_env, - cfg: evm_env.cfg_env, - tx: tx_env, - chain: (), - local: LocalContext::default(), - error: Ok(()), - }; - ctx.cfg.tx_chain_id_check = true; - let spec = ctx.cfg.spec; - - let mut evm = FoundryEvm { - inner: RevmEvm::new_with_inspector( - ctx, - inspector, - EthInstructions::default(), - get_precompiles(spec), - ), - }; + let eth_evm = + alloy_evm::EthEvmFactory::default().create_evm_with_inspector(db, evm_env, inspector); + let mut inner = eth_evm.into_inner(); + inner.ctx.cfg.tx_chain_id_check = true; + let mut evm = FoundryEvm { inner }; evm.inspector().get_networks().inject_precompiles(evm.precompiles_mut()); evm } -/// Get the precompiles for the given spec. -fn get_precompiles(spec: SpecId) -> PrecompilesMap { - PrecompilesMap::from_static( - EthPrecompiles { - precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), - spec, - } - .precompiles, - ) -} - /// Get the call inputs for the CREATE2 factory. fn get_create2_factory_call_inputs( salt: U256, @@ -100,44 +86,21 @@ fn get_create2_factory_call_inputs( } } -pub struct FoundryEvm<'db, I: InspectorExt> { - #[allow(clippy::type_complexity)] - inner: RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - EthFrame, - >, -} -impl<'db, I: InspectorExt> FoundryEvm<'db, I> { - /// Consumes the EVM and returns the inner context. - pub fn into_context(self) -> EthEvmContext<&'db mut dyn DatabaseExt> { - self.inner.ctx - } - - pub fn run_execution( - &mut self, - frame: FrameInput, - ) -> Result> { - let mut handler = FoundryHandler::::default(); - - // Create first frame - let memory = - SharedMemory::new_with_buffer(self.inner.ctx().local().shared_memory_buffer().clone()); - let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; - - // Run execution loop - let mut frame_result = handler.inspect_run_exec_loop(&mut self.inner, first_frame_input)?; +type EthRevmEvm<'db, I> = RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + EthFrame, +>; - // Handle last frame result - handler.last_frame_result(&mut self.inner, &mut frame_result)?; - - Ok(frame_result) - } +pub struct FoundryEvm<'db, I: FoundryInspectorExt>> { + inner: EthRevmEvm<'db, I>, } -impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { +impl<'db, I: FoundryInspectorExt>> Evm + for FoundryEvm<'db, I> +{ type Precompiles = PrecompilesMap; type Inspector = I; type DB = &'db mut dyn DatabaseExt; @@ -167,26 +130,6 @@ impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { ) } - fn db_mut(&mut self) -> &mut Self::DB { - &mut self.inner.ctx.journaled_state.database - } - - fn precompiles(&self) -> &Self::Precompiles { - &self.inner.precompiles - } - - fn precompiles_mut(&mut self) -> &mut Self::Precompiles { - &mut self.inner.precompiles - } - - fn inspector(&self) -> &Self::Inspector { - &self.inner.inspector - } - - fn inspector_mut(&mut self) -> &mut Self::Inspector { - &mut self.inner.inspector - } - fn set_inspector_enabled(&mut self, _enabled: bool) { unimplemented!("FoundryEvm is always inspecting") } @@ -195,7 +138,7 @@ impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { &mut self, tx: Self::Tx, ) -> Result, Self::Error> { - self.inner.ctx.tx = tx; + self.inner.set_tx(tx); let mut handler = FoundryHandler::::default(); let result = handler.inspect_run(&mut self.inner)?; @@ -222,7 +165,9 @@ impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { } } -impl<'db, I: InspectorExt> Deref for FoundryEvm<'db, I> { +impl<'db, I: FoundryInspectorExt>> Deref + for FoundryEvm<'db, I> +{ type Target = Context; fn deref(&self) -> &Self::Target { @@ -230,7 +175,9 @@ impl<'db, I: InspectorExt> Deref for FoundryEvm<'db, I> { } } -impl DerefMut for FoundryEvm<'_, I> { +impl<'db, I: FoundryInspectorExt>> DerefMut + for FoundryEvm<'db, I> +{ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner.ctx } @@ -241,83 +188,102 @@ impl DerefMut for FoundryEvm<'_, I> { /// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.) /// so that cheatcode impls can build and run nested EVMs without knowing the concrete type. pub trait NestedEvm { + /// The transaction environment type. + type Tx; + /// Returns a mutable reference to the journal inner state (`JournaledState`). fn journal_inner_mut(&mut self) -> &mut JournaledState; /// Runs a single execution frame (create or call) through the EVM handler loop. fn run_execution(&mut self, frame: FrameInput) -> Result>; - /// Executes a full transaction with the given `TxEnv`. - fn transact( + /// Executes a full transaction with the given tx env. + fn transact_raw( &mut self, - tx: TxEnv, + tx: Self::Tx, ) -> Result, EVMError>; - - /// Returns a snapshot of the current environment (cfg + block, tx). - fn to_env(&self) -> (EvmEnv, TxEnv); } -impl NestedEvm for FoundryEvm<'_, I> { +impl<'db, I: FoundryInspectorExt>> NestedEvm + for EthRevmEvm<'db, I> +{ + type Tx = TxEnv; + fn journal_inner_mut(&mut self) -> &mut JournaledState { - &mut self.inner.ctx.journaled_state.inner + &mut self.ctx_mut().journaled_state.inner } fn run_execution(&mut self, frame: FrameInput) -> Result> { - FoundryEvm::run_execution(self, frame) + let mut handler = FoundryHandler::::default(); + + // Create first frame + let memory = + SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + // Run execution loop + let mut frame_result = handler.inspect_run_exec_loop(self, first_frame_input)?; + + // Handle last frame result + handler.last_frame_result(self, &mut frame_result)?; + + Ok(frame_result) } - fn transact( + fn transact_raw( &mut self, - tx: TxEnv, + tx: Self::Tx, ) -> Result, EVMError> { - Evm::transact_raw(self, tx) - } + self.set_tx(tx); - fn to_env(&self) -> (EvmEnv, TxEnv) { - ( - EvmEnv { cfg_env: self.inner.ctx.cfg.clone(), block_env: self.inner.ctx.block.clone() }, - self.inner.ctx.tx.clone(), - ) + let mut handler = FoundryHandler::::default(); + let result = handler.inspect_run(self)?; + + Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) } } +/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations. +pub type NestedEvmClosure<'a, Tx> = + &'a mut dyn FnMut(&mut dyn NestedEvm) -> Result<(), EVMError>; + /// Clones the current context (env + journal), passes the database, cloned env, /// and cloned journal inner to the callback. The callback builds whatever EVM it /// needs, runs its operations, and returns `(result, modified_env, modified_journal)`. /// Modified state is written back after the callback returns. -pub fn with_cloned_context( +pub fn with_cloned_context< + CTX: FoundryContextExt>, +>( ecx: &mut CTX, f: impl FnOnce( - &mut dyn DatabaseExt, - EvmEnv, - TxEnv, + &mut dyn DatabaseExt, + EvmEnv, JournaledState, - ) -> Result<(R, EvmEnv, TxEnv, JournaledState), EVMError>, -) -> Result> -where - CTX::Journal: FoundryJournalExt, -{ - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); + ) + -> Result<(EvmEnv, JournaledState), EVMError>, +) -> Result<(), EVMError> { + let evm_env = ecx.evm_clone(); - let journal = ecx.journal_mut(); - let (db, journal_inner) = journal.as_db_and_inner(); + let (db, journal_inner) = ecx.db_journal_inner_mut(); let journal_inner_clone = journal_inner.clone(); - let (result, sub_evm_env, sub_tx, sub_inner) = f(db, evm_env, tx_env, journal_inner_clone)?; + let (sub_evm_env, sub_inner) = f(db, evm_env, journal_inner_clone)?; // Write back modified state. The db borrow was released when f returned. - ecx.journal_mut().set_inner(sub_inner); - Env::apply_evm_and_tx(ecx, sub_evm_env, sub_tx); + ecx.set_journal_inner(sub_inner); + ecx.set_evm(sub_evm_env); - Ok(result) + Ok(()) } -pub struct FoundryHandler<'db, I: InspectorExt> { +pub struct FoundryHandler<'db, I: FoundryInspectorExt>> { create2_overrides: Vec<(usize, CallInputs)>, _phantom: PhantomData<(&'db mut dyn DatabaseExt, I)>, } -impl Default for FoundryHandler<'_, I> { +impl<'db, I: FoundryInspectorExt>> Default + for FoundryHandler<'db, I> +{ fn default() -> Self { Self { create2_overrides: Vec::new(), _phantom: PhantomData } } @@ -325,7 +291,9 @@ impl Default for FoundryHandler<'_, I> { // Blanket Handler implementation for FoundryHandler, needed for implementing the InspectorHandler // trait. -impl<'db, I: InspectorExt> Handler for FoundryHandler<'db, I> { +impl<'db, I: FoundryInspectorExt>> Handler + for FoundryHandler<'db, I> +{ type Evm = RevmEvm< EthEvmContext<&'db mut dyn DatabaseExt>, I, @@ -337,7 +305,7 @@ impl<'db, I: InspectorExt> Handler for FoundryHandler<'db, I> { type HaltReason = HaltReason; } -impl<'db, I: InspectorExt> FoundryHandler<'db, I> { +impl<'db, I: FoundryInspectorExt>> FoundryHandler<'db, I> { /// Handles CREATE2 frame initialization, potentially transforming it to use the CREATE2 /// factory. fn handle_create_frame( @@ -431,7 +399,9 @@ impl<'db, I: InspectorExt> FoundryHandler<'db, I> { } } -impl InspectorHandler for FoundryHandler<'_, I> { +impl<'db, I: FoundryInspectorExt>> InspectorHandler + for FoundryHandler<'db, I> +{ type IT = EthInterpreter; fn inspect_run_exec_loop( diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 6982ff20dc163..32c699ee49e3e 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -6,10 +6,8 @@ #![cfg_attr(docsrs, feature(doc_cfg))] use crate::constants::DEFAULT_CREATE2_DEPLOYER; -use alloy_evm::eth::EthEvmContext; use alloy_primitives::{Address, map::HashMap}; use auto_impl::auto_impl; -use backend::DatabaseExt; use revm::{Inspector, inspector::NoOpInspector, interpreter::CreateInputs}; use revm_inspectors::access_list::AccessListInspector; @@ -49,7 +47,7 @@ pub mod utils; /// network config, deployer address). It has no `Inspector` supertrait so it can /// be used in generic code with `I: FoundryInspectorExt + Inspector`. #[auto_impl(&mut, Box)] -pub trait FoundryInspectorExt { +pub trait InspectorExt { /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. /// /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 @@ -74,20 +72,14 @@ pub trait FoundryInspectorExt { } } -/// Combined trait: `Inspector>` + [`FoundryInspectorExt`]. -/// -/// Used as a trait object (`dyn InspectorExt`) in backend code that is Eth-specific. -/// For generic multi-network code, use `I: FoundryInspectorExt + Inspector` instead. -pub trait InspectorExt: - for<'a> Inspector> + FoundryInspectorExt -{ -} +/// A combined inspector trait that integrates revm's [`Inspector`] with Foundry-specific +/// extensions. Automatically implemented for any type that implements both [`Inspector`] +/// and [`InspectorExt`]. +pub trait FoundryInspectorExt: Inspector + InspectorExt {} -impl InspectorExt for T where - T: for<'a> Inspector> + FoundryInspectorExt -{ -} +impl FoundryInspectorExt for T where T: Inspector + InspectorExt +{} -impl FoundryInspectorExt for NoOpInspector {} +impl InspectorExt for NoOpInspector {} -impl FoundryInspectorExt for AccessListInspector {} +impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index b27727bb3bd5e..8b00016dff853 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,6 +1,8 @@ use crate::{ - EvmEnv, constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork, - utils::apply_chain_and_block_specific_env_changes, + EvmEnv, + constants::DEFAULT_CREATE2_DEPLOYER, + fork::CreateFork, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; use alloy_consensus::BlockHeader; use alloy_network::{AnyNetwork, BlockResponse, Network}; @@ -127,18 +129,18 @@ impl EvmOpts { .build() } - /// Assembles a complete [`Env`] + /// Returns a tuple with [`EvmEnv`] and [`TxEnv`] /// /// If a `fork_url` is set, creates a provider and passes it to both `EvmOpts::fork_evm_env` /// and `EvmOpts::fork_tx_env`. Falls back to local settings when no fork URL is configured. - pub async fn env(&self) -> eyre::Result { + pub async fn env(&self) -> eyre::Result<(EvmEnv, TxEnv)> { if let Some(ref fork_url) = self.fork_url { let provider = self.fork_provider_with_url::(fork_url)?; let ((evm_env, _block), tx) = tokio::try_join!(self.fork_evm_env(&provider), self.fork_tx_env(&provider))?; - Ok(crate::Env { evm_env, tx }) + Ok((evm_env, tx)) } else { - Ok(crate::Env { evm_env: self.local_evm_env(), tx: self.local_tx_env() }) + Ok((self.local_evm_env(), self.local_tx_env())) } } @@ -200,16 +202,7 @@ impl EvmOpts { let block_number = block.header().number(); let mut evm_env = EvmEnv { cfg_env: self.cfg_env(chain_id), - block_env: BlockEnv { - number: U256::from(block_number), - timestamp: U256::from(block.header().timestamp()), - beneficiary: block.header().beneficiary(), - difficulty: block.header().difficulty(), - prevrandao: block.header().mix_hash(), - basefee: block.header().base_fee_per_gas().unwrap_or_default(), - gas_limit: block.header().gas_limit(), - ..Default::default() - }, + block_env: block_env_from_header(block.header()), }; apply_chain_and_block_specific_env_changes::(&mut evm_env, &block, self.networks); @@ -267,7 +260,7 @@ impl EvmOpts { let mut cfg = CfgEnv::default(); cfg.chain_id = chain_id; cfg.memory_limit = self.memory_limit; - cfg.limit_contract_code_size = Some(usize::MAX); + cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); // EIP-3607 rejects transactions from senders with deployed code. // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller // is a contract. So we disable the check by default. @@ -428,12 +421,12 @@ mod tests { assert!(evm_opts.fork_block_number.is_none()); // Fetch the environment (this resolves "latest" to an actual block number) - let env = evm_opts.env().await.unwrap(); - let resolved_block = env.evm_env.block_env.number; + let (evm_env, _) = evm_opts.env().await.unwrap(); + let resolved_block = evm_env.block_env.number; assert!(resolved_block > U256::ZERO, "should have resolved to a real block number"); // Create the fork - this should pin the block number - let fork = evm_opts.get_fork(&Config::default(), env.evm_env).unwrap(); + let fork = evm_opts.get_fork(&Config::default(), evm_env).unwrap(); // The fork's evm_opts should now have fork_block_number set to the resolved block assert_eq!( @@ -453,9 +446,9 @@ mod tests { // Set an explicit block number evm_opts.fork_block_number = Some(12345678); - let env = evm_opts.env().await.unwrap(); + let (evm_env, _) = evm_opts.env().await.unwrap(); - let fork = evm_opts.get_fork(&Config::default(), env.evm_env).unwrap(); + let fork = evm_opts.get_fork(&Config::default(), evm_env).unwrap(); // Should preserve the explicit block number, not override it assert_eq!( diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 826384b5ea259..3d5e432d3d45b 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -7,11 +7,14 @@ use alloy_primitives::{B256, ChainId, Selector, U256}; use alloy_provider::{Network, network::BlockResponse}; use foundry_config::NamedChain; use foundry_evm_networks::NetworkConfigs; -use revm::primitives::{ - eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, - hardfork::SpecId, -}; pub use revm::state::EvmState as StateChangeset; +use revm::{ + context::BlockEnv, + primitives::{ + eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, + hardfork::SpecId, + }, +}; /// Hints to the compiler that this is a cold path, i.e. unlikely to be taken. #[cold] @@ -20,6 +23,20 @@ pub fn cold_path() { // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable. } +/// Constructs a [`BlockEnv`] from a block header. +pub fn block_env_from_header(header: &impl BlockHeader) -> BlockEnv { + BlockEnv { + number: U256::from(header.number()), + beneficiary: header.beneficiary(), + timestamp: U256::from(header.timestamp()), + difficulty: header.difficulty(), + prevrandao: header.mix_hash(), + basefee: header.base_fee_per_gas().unwrap_or_default(), + gas_limit: header.gas_limit(), + ..Default::default() + } +} + /// Depending on the configured chain id and block number this should apply any specific changes /// /// - checks for prevrandao mixhash after merge diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index a69c19cbdb2bd..c2f1436c1a575 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -27,6 +27,7 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-evm.workspace = true alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index d3336fcc9013c..a42a6ead54849 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,6 +1,6 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; -use foundry_evm_core::{Env, backend::Backend}; -use revm::primitives::hardfork::SpecId; +use foundry_evm_core::{EvmEnv, backend::Backend}; +use revm::{context::TxEnv, primitives::hardfork::SpecId}; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -73,21 +73,16 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, env: Env, db: Backend) -> Executor { + pub fn build(self, mut evm_env: EvmEnv, tx_env: TxEnv, db: Backend) -> Executor { let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { - stack.block = Some(env.evm_env.block_env.clone()); + stack.block = Some(evm_env.block_env.clone()); } if stack.gas_price.is_none() { - stack.gas_price = Some(env.tx.gas_price); + stack.gas_price = Some(tx_env.gas_price); } - let gas_limit = gas_limit.unwrap_or(env.evm_env.block_env.gas_limit); - let env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx, - spec_id, - ); - Executor::new(db, env, stack.build(), gas_limit, legacy_assertions) + let gas_limit = gas_limit.unwrap_or(evm_env.block_env.gas_limit); + evm_env.cfg_env.set_spec(spec_id); + Executor::new(db, evm_env, tx_env, stack.build(), gas_limit, legacy_assertions) } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 86e8e18581764..e47a94e3fa99d 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -1105,13 +1105,13 @@ pub(crate) fn execute_tx(executor: &mut Executor, tx: &BasicTxDetails) -> Result if warp > 0 || roll > 0 { // Apply pre-call block adjustments to the executor's env. - executor.env_mut().evm_env.block_env.timestamp += warp; - executor.env_mut().evm_env.block_env.number += roll; + executor.evm_env_mut().block_env.timestamp += warp; + executor.evm_env_mut().block_env.number += roll; // Also update the inspector's cheatcodes.block if set. // The inspector's block may override the env during interpreter initialization, // so we need to add our warp/roll on top of any existing cheatcode-set values. - let block_env = executor.env().evm_env.block_env.clone(); + let block_env = executor.evm_env().block_env.clone(); if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { if let Some(block) = cheatcodes.block.as_mut() { block.timestamp += warp; diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs index 1e56a019cc576..8296417dea405 100644 --- a/crates/evm/evm/src/executors/invariant/shrink.rs +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -61,10 +61,10 @@ fn apply_warp_roll(call: &BasicTxDetails, warp: U256, roll: U256) -> BasicTxDeta /// Applies warp/roll adjustments directly to the executor's environment. fn apply_warp_roll_to_env(executor: &mut Executor, warp: U256, roll: U256) { if warp > U256::ZERO || roll > U256::ZERO { - executor.env_mut().evm_env.block_env.timestamp += warp; - executor.env_mut().evm_env.block_env.number += roll; + executor.evm_env_mut().block_env.timestamp += warp; + executor.evm_env_mut().block_env.number += roll; - let block_env = executor.env().evm_env.block_env.clone(); + let block_env = executor.evm_env().block_env.clone(); if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { if let Some(block) = cheatcodes.block.as_mut() { block.timestamp += warp; diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 23bd0cd97ce67..331a79d0ec04c 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -6,14 +6,12 @@ // `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of // the concrete `Executor` type. -use crate::{ - Env, - inspectors::{ - Cheatcodes, InspectorData, InspectorStack, cheatcodes::BroadcastableTransactions, - }, +use crate::inspectors::{ + Cheatcodes, InspectorData, InspectorStack, cheatcodes::BroadcastableTransactions, }; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; +use alloy_network::Ethereum; use alloy_primitives::{ Address, Bytes, Log, TxKind, U256, keccak256, map::{AddressHashMap, HashMap}, @@ -99,8 +97,10 @@ pub struct Executor { // wrapper around spawning a new EVM on every call anyway, // so the performance difference should be negligible. backend: Arc, - /// The EVM environment. - env: Env, + /// The EVM environment (block and cfg). + evm_env: EvmEnv, + /// The transaction environment. + tx_env: TxEnv, /// The Revm inspector stack. inspector: InspectorStack, /// The gas limit for calls and deployments. @@ -114,7 +114,8 @@ impl Executor { #[inline] pub fn new( mut backend: Backend, - env: Env, + evm_env: EvmEnv, + tx_env: TxEnv, inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, @@ -132,19 +133,22 @@ impl Executor { }, ); - Self { backend: Arc::new(backend), env, inspector, gas_limit, legacy_assertions } + Self { + backend: Arc::new(backend), + evm_env, + tx_env, + inspector, + gas_limit, + legacy_assertions, + } } fn clone_with_backend(&self, backend: Backend) -> Self { - let env = Env::new_with_spec_id( - self.env.evm_env.cfg_env.clone(), - self.env.evm_env.block_env.clone(), - self.env.tx.clone(), - self.spec_id(), - ); + let evm_env = self.evm_env.clone(); Self { backend: Arc::new(backend), - env, + evm_env, + tx_env: self.tx_env.clone(), inspector: self.inspector().clone(), gas_limit: self.gas_limit, legacy_assertions: self.legacy_assertions, @@ -164,14 +168,24 @@ impl Executor { Arc::make_mut(&mut self.backend) } - /// Returns a reference to the EVM environment. - pub fn env(&self) -> &Env { - &self.env + /// Returns a reference to the EVM environment (block and cfg). + pub fn evm_env(&self) -> &EvmEnv { + &self.evm_env } - /// Returns a mutable reference to the EVM environment. - pub fn env_mut(&mut self) -> &mut Env { - &mut self.env + /// Returns a mutable reference to the EVM environment (block and cfg). + pub fn evm_env_mut(&mut self) -> &mut EvmEnv { + &mut self.evm_env + } + + /// Returns a reference to the transaction environment. + pub fn tx_env(&self) -> &TxEnv { + &self.tx_env + } + + /// Returns a mutable reference to the transaction environment. + pub fn tx_env_mut(&mut self) -> &mut TxEnv { + &mut self.tx_env } /// Returns a reference to the EVM inspector. @@ -186,12 +200,12 @@ impl Executor { /// Returns the EVM spec ID. pub fn spec_id(&self) -> SpecId { - self.env.evm_env.cfg_env.spec + self.evm_env.cfg_env.spec } /// Sets the EVM spec ID. pub fn set_spec_id(&mut self, spec_id: SpecId) { - self.env.evm_env.cfg_env.spec = spec_id; + self.evm_env.cfg_env.spec = spec_id; } /// Returns the gas limit for calls and deployments. @@ -263,7 +277,7 @@ impl Executor { let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; self.backend_mut().insert_account_info(address, account); - self.env_mut().tx.nonce = nonce; + self.tx_env_mut().nonce = nonce; Ok(()) } @@ -340,8 +354,8 @@ impl Executor { value: U256, rd: Option<&RevertDecoder>, ) -> Result { - let env = self.build_test_env(from, TxKind::Create, code, value); - self.deploy_with_env(env, rd) + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value); + self.deploy_with_env(evm_env, tx_env, rd) } /// Deploys a contract using the given `env` and commits the new state to the underlying @@ -349,21 +363,22 @@ impl Executor { /// /// # Panics /// - /// Panics if `env.tx.kind` is not `TxKind::Create(_)`. + /// Panics if `tx_env.kind` is not `TxKind::Create(_)`. #[instrument(name = "deploy", level = "debug", skip_all)] pub fn deploy_with_env( &mut self, - env: Env, + evm_env: EvmEnv, + tx_env: TxEnv, rd: Option<&RevertDecoder>, ) -> Result { assert!( - matches!(env.tx.kind, TxKind::Create), + matches!(tx_env.kind, TxKind::Create), "Expected create transaction, got {:?}", - env.tx.kind + tx_env.kind ); - trace!(sender=%env.tx.caller, "deploying contract"); + trace!(sender=%tx_env.caller, "deploying contract"); - let mut result = self.transact_with_env(env)?; + let mut result = self.transact_with_env(evm_env, tx_env)?; result = result.into_result(rd)?; let Some(Output::Create(_, Some(address))) = result.out else { panic!("Deployment succeeded, but no address was returned: {result:#?}"); @@ -400,9 +415,9 @@ impl Executor { res = res.into_result(rd)?; // record any changes made to the block's environment during setup - self.env_mut().evm_env.block_env = res.env.evm_env.block_env.clone(); + self.evm_env_mut().block_env = res.evm_env.block_env.clone(); // and also the chainid, which can be set manually - self.env_mut().evm_env.cfg_env.chain_id = res.env.evm_env.cfg_env.chain_id; + self.evm_env_mut().cfg_env.chain_id = res.evm_env.cfg_env.chain_id; let success = self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false); @@ -466,8 +481,8 @@ impl Executor { calldata: Bytes, value: U256, ) -> eyre::Result { - let env = self.build_test_env(from, TxKind::Call(to), calldata, value); - self.call_with_env(env) + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.call_with_env(evm_env, tx_env) } /// Performs a raw call to an account on the current state of the VM with an EIP-7702 @@ -480,10 +495,10 @@ impl Executor { value: U256, authorization_list: Vec, ) -> eyre::Result { - let mut env = self.build_test_env(from, to.into(), calldata, value); - env.tx.set_signed_authorization(authorization_list); - env.tx.tx_type = 4; - self.call_with_env(env) + let (evm_env, mut tx_env) = self.build_test_env(from, to.into(), calldata, value); + tx_env.set_signed_authorization(authorization_list); + tx_env.tx_type = 4; + self.call_with_env(evm_env, tx_env) } /// Performs a raw call to an account on the current state of the VM. @@ -494,8 +509,8 @@ impl Executor { calldata: Bytes, value: U256, ) -> eyre::Result { - let env = self.build_test_env(from, TxKind::Call(to), calldata, value); - self.transact_with_env(env) + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.transact_with_env(evm_env, tx_env) } /// Performs a raw call to an account on the current state of the VM with an EIP-7702 @@ -508,31 +523,51 @@ impl Executor { value: U256, authorization_list: Vec, ) -> eyre::Result { - let mut env = self.build_test_env(from, TxKind::Call(to), calldata, value); - env.tx.set_signed_authorization(authorization_list); - env.tx.tx_type = 4; - self.transact_with_env(env) + let (evm_env, mut tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + tx_env.set_signed_authorization(authorization_list); + tx_env.tx_type = 4; + self.transact_with_env(evm_env, tx_env) } - /// Execute the transaction configured in `env.tx`. + /// Execute the transaction configured in `tx_env`. /// /// The state after the call is **not** persisted. #[instrument(name = "call", level = "debug", skip_all)] - pub fn call_with_env(&self, mut env: Env) -> eyre::Result { + pub fn call_with_env( + &self, + mut evm_env: EvmEnv, + mut tx_env: TxEnv, + ) -> eyre::Result { let mut stack = self.inspector().clone(); let mut backend = CowBackend::new_borrowed(self.backend()); - let result = backend.inspect(&mut env, stack.as_inspector())?; - convert_executed_result(env, stack, result, backend.has_state_snapshot_failure()) + let result = backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?; + convert_executed_result( + evm_env, + tx_env, + stack, + result, + backend.has_state_snapshot_failure(), + ) } - /// Execute the transaction configured in `env.tx`. + /// Execute the transaction configured in `tx_env`. #[instrument(name = "transact", level = "debug", skip_all)] - pub fn transact_with_env(&mut self, mut env: Env) -> eyre::Result { + pub fn transact_with_env( + &mut self, + mut evm_env: EvmEnv, + mut tx_env: TxEnv, + ) -> eyre::Result { let mut stack = self.inspector().clone(); let backend = self.backend_mut(); - let result = backend.inspect(&mut env, stack.as_inspector())?; - let mut result = - convert_executed_result(env, stack, result, backend.has_state_snapshot_failure())?; + let result: revm::context::result::ExecResultAndState = + backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?; + let mut result = convert_executed_result( + evm_env, + tx_env, + stack, + result, + backend.has_state_snapshot_failure(), + )?; self.commit(&mut result); Ok(result) } @@ -561,7 +596,7 @@ impl Executor { } // Persist the changed environment. - self.inspector_mut().set_env(&result.env); + self.inspector_mut().set_env(&result.evm_env, &result.tx_env); } /// Returns `true` if a test can be considered successful. @@ -703,36 +738,41 @@ impl Executor { /// /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by /// the cheatcode state in between calls. - fn build_test_env(&self, caller: Address, kind: TxKind, data: Bytes, value: U256) -> Env { - Env { - evm_env: EvmEnv { - cfg_env: { - let mut cfg = self.env().evm_env.cfg_env.clone(); - cfg.spec = self.spec_id(); - cfg - }, - // We always set the gas price to 0 so we can execute the transaction regardless of - // network conditions - the actual gas price is kept in `self.block` and is applied - // by the cheatcode handler if it is enabled - block_env: BlockEnv { - basefee: 0, - gas_limit: self.gas_limit, - ..self.env().evm_env.block_env.clone() - }, + fn build_test_env( + &self, + caller: Address, + kind: TxKind, + data: Bytes, + value: U256, + ) -> (EvmEnv, TxEnv) { + let evm_env = EvmEnv { + cfg_env: { + let mut cfg = self.evm_env.cfg_env.clone(); + cfg.spec = self.spec_id(); + cfg }, - tx: TxEnv { - caller, - kind, - data, - value, - // As above, we set the gas price to 0. - gas_price: 0, - gas_priority_fee: None, + // We always set the gas price to 0 so we can execute the transaction regardless of + // network conditions - the actual gas price is kept in `self.block` and is applied + // by the cheatcode handler if it is enabled + block_env: BlockEnv { + basefee: 0, gas_limit: self.gas_limit, - chain_id: Some(self.env().evm_env.cfg_env.chain_id), - ..self.env().tx.clone() + ..self.evm_env.block_env.clone() }, - } + }; + let tx_env = TxEnv { + caller, + kind, + data, + value, + // As above, we set the gas price to 0. + gas_price: 0, + gas_priority_fee: None, + gas_limit: self.gas_limit, + chain_id: Some(self.evm_env.cfg_env.chain_id), + ..self.tx_env.clone() + }; + (evm_env, tx_env) } pub fn call_sol_default(&self, to: Address, args: &C) -> C::Return @@ -866,11 +906,13 @@ pub struct RawCallResult { /// The edge coverage info collected during the call pub edge_coverage: Option>, /// Scripted transactions generated from this call - pub transactions: Option, + pub transactions: Option>, /// The changeset of the state. pub state_changeset: StateChangeset, - /// The `revm::Env` after the call - pub env: Env, + /// The `EvmEnv` after the call + pub evm_env: EvmEnv, + /// The `TxEnv` after the call + pub tx_env: TxEnv, /// The cheatcode states after execution pub cheatcodes: Option>, /// The raw output of the execution @@ -897,7 +939,8 @@ impl Default for RawCallResult { edge_coverage: None, transactions: None, state_changeset: HashMap::default(), - env: Env::default(), + evm_env: EvmEnv::default(), + tx_env: TxEnv::default(), cheatcodes: Default::default(), out: None, chisel_state: None, @@ -959,7 +1002,7 @@ impl RawCallResult { } /// Returns the transactions generated from this call. - pub fn transactions(&self) -> Option<&BroadcastableTransactions> { + pub fn transactions(&self) -> Option<&BroadcastableTransactions> { self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions) } @@ -1032,7 +1075,8 @@ impl std::ops::DerefMut for CallResult { /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` fn convert_executed_result( - env: Env, + evm_env: EvmEnv, + tx_env: TxEnv, inspector: InspectorStack, ResultAndState { result, state: state_changeset }: ResultAndState, has_state_snapshot_failure: bool, @@ -1050,10 +1094,10 @@ fn convert_executed_result( } }; let gas = revm::interpreter::gas::calculate_initial_tx_gas( - env.evm_env.cfg_env.spec, - &env.tx.data, - env.tx.kind.is_create(), - env.tx.access_list.len().try_into()?, + evm_env.cfg_env.spec, + &tx_env.data, + tx_env.kind.is_create(), + tx_env.access_list.len().try_into()?, 0, 0, ); @@ -1098,7 +1142,8 @@ fn convert_executed_result( edge_coverage, transactions, state_changeset, - env, + evm_env, + tx_env, cheatcodes, out, chisel_state, diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index a1721276f0fb0..a08239c77f91a 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,7 +1,5 @@ -use crate::{ - Env, - executors::{Executor, ExecutorBuilder}, -}; +use crate::executors::{Executor, ExecutorBuilder}; +use alloy_evm::EvmEnv; use alloy_primitives::{Address, U256, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; @@ -10,7 +8,7 @@ use foundry_config::{Chain, Config, utils::evm_spec_id}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_networks::NetworkConfigs; use foundry_evm_traces::TraceMode; -use revm::{primitives::hardfork::SpecId, state::Bytecode}; +use revm::{context::TxEnv, primitives::hardfork::SpecId, state::Bytecode}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled @@ -20,7 +18,7 @@ pub struct TracingExecutor { impl TracingExecutor { pub fn new( - env: Env, + env: (EvmEnv, TxEnv), fork: CreateFork, version: Option, trace_mode: TraceMode, @@ -36,7 +34,7 @@ impl TracingExecutor { stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) }) .spec_id(evm_spec_id(version.unwrap_or_default())) - .build(env, db); + .build(env.0, env.1, db); // Apply the state overrides. if let Some(state_overrides) = state_overrides { @@ -79,18 +77,18 @@ impl TracingExecutor { pub async fn get_fork_material( config: &mut Config, mut evm_opts: EvmOpts, - ) -> eyre::Result<(Env, CreateFork, Chain, NetworkConfigs)> { + ) -> eyre::Result<(EvmEnv, TxEnv, CreateFork, Chain, NetworkConfigs)> { evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); evm_opts.fork_block_number = config.fork_block_number; - let env = evm_opts.env().await?; + let (evm_env, tx_env) = evm_opts.env().await?; - let fork = evm_opts.get_fork(config, env.evm_env.clone()).unwrap(); - let networks = evm_opts.networks.with_chain_id(env.evm_env.cfg_env.chain_id); + let fork = evm_opts.get_fork(config, evm_env.clone()).unwrap(); + let networks = evm_opts.networks.with_chain_id(evm_env.cfg_env.chain_id); config.labels.extend(networks.precompiles_label()); - let chain = env.tx.chain_id.unwrap().into(); - Ok((env, fork, chain, networks)) + let chain = tx_env.chain_id.unwrap().into(); + Ok((evm_env, tx_env, fork, chain, networks)) } } diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index bc09521fa771d..84c05f69db1c2 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -2,8 +2,7 @@ use alloy_primitives::Log; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; use foundry_common::{ErrorExt, fmt::ConsoleFmt, sh_println}; use foundry_evm_core::{ - FoundryInspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS, - decode::decode_console_log, + InspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS, decode::decode_console_log, }; use revm::{ Inspector, @@ -97,7 +96,7 @@ where } } -impl FoundryInspectorExt for LogCollector { +impl InspectorExt for LogCollector { fn console_log(&mut self, msg: &str) { self.push_msg(msg); } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 59abc04347893..d3a124381d2a0 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -2,21 +2,20 @@ use super::{ Cheatcodes, CheatsConfig, ChiselState, CustomPrintTracer, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic, ScriptExecutionInspector, TracingInspector, }; +use alloy_evm::EvmEnv; use alloy_primitives::{ Address, B256, Bytes, Log, TxKind, U256, map::{AddressHashMap, HashMap}, }; -use alloy_rpc_types::request::TransactionRequest; use foundry_cheatcodes::{ - CheatcodeAnalysis, CheatcodesExecutor, CheatsCtxExt, NestedEvmClosure, Wallets, + CheatcodeAnalysis, CheatcodesExecutor, EthCheatCtx, NestedEvmClosure, Wallets, }; use foundry_common::compile::Analysis; -use foundry_compilers::ProjectPathsConfig; use foundry_evm_core::{ - Env, FoundryBlock, FoundryInspectorExt, FoundryTransaction, InspectorExt, - backend::{DatabaseError, DatabaseExt, FoundryJournalExt, JournaledState}, + FoundryBlock, FoundryTransaction, InspectorExt, + backend::{DatabaseError, DatabaseExt, JournaledState}, env::FoundryContextExt, - evm::{NestedEvm, new_evm_with_inspector, with_cloned_context}, + evm::{NestedEvm, new_revm_with_inspector, with_cloned_context}, }; use foundry_evm_coverage::HitMaps; use foundry_evm_networks::NetworkConfigs; @@ -24,10 +23,11 @@ use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ Inspector, context::{ - Block, BlockEnv, Cfg, ContextTr, JournalTr, Transaction, + Block, BlockEnv, Cfg, ContextTr, JournalTr, Transaction, TxEnv, result::{EVMError, ExecutionResult, Output}, }, context_interface::CreateScheme, + handler::EvmTr, inspector::JournalExt, interpreter::{ CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, @@ -318,12 +318,6 @@ pub struct InspectorStack { pub inner: InspectorStackInner, } -impl InspectorStack { - pub fn paths_config(&self) -> Option<&ProjectPathsConfig> { - self.cheatcodes.as_ref().map(|c| &c.config.paths) - } -} - /// All used inpectors besides [Cheatcodes]. /// /// See [`InspectorStack`]. @@ -345,7 +339,7 @@ pub struct InspectorStackInner { pub script_execution_inspector: Option>, pub tracer: Option>, - // InspectorExt and other internal data. + // FoundryInspectorExt and other internal data. pub enable_isolation: bool, pub networks: NetworkConfigs, pub create2_deployer: Address, @@ -364,37 +358,35 @@ pub struct InspectorStackRefMut<'a> { pub inner: &'a mut InspectorStackInner, } -impl> CheatcodesExecutor - for InspectorStackInner -{ +impl CheatcodesExecutor for InspectorStackInner { fn with_nested_evm( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - f: NestedEvmClosure<'_>, + f: NestedEvmClosure<'_, CTX::Tx>, ) -> Result<(), EVMError> { let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; - with_cloned_context(ecx, |db, evm_env, tx_env, journal_inner| { - let mut evm = new_evm_with_inspector(db, evm_env, tx_env, &mut inspector); + with_cloned_context(ecx, |db, evm_env, journal_inner| { + let mut evm = new_revm_with_inspector(db, evm_env, &mut inspector); *evm.journal_inner_mut() = journal_inner; f(&mut evm)?; - let (sub_evm_env, sub_tx) = evm.to_env(); - let sub_inner = evm.into_context().journaled_state.inner; - Ok(((), sub_evm_env, sub_tx, sub_inner)) + let sub_inner = evm.journaled_state.inner.clone(); + let sub_evm_env = evm.ctx_ref().evm_clone(); + Ok((sub_evm_env, sub_inner)) }) } fn with_fresh_nested_evm( &mut self, cheats: &mut Cheatcodes, - db: &mut dyn DatabaseExt, - evm_env: foundry_evm_core::EvmEnv, - tx_env: revm::context::TxEnv, - f: NestedEvmClosure<'_>, - ) -> Result<(), EVMError> { + db: &mut dyn DatabaseExt, + evm_env: EvmEnv, + f: NestedEvmClosure<'_, CTX::Tx>, + ) -> Result, EVMError> { let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; - let mut evm = new_evm_with_inspector(db, evm_env, tx_env, &mut inspector); - f(&mut evm) + let mut evm = new_revm_with_inspector(db, evm_env, &mut inspector); + f(&mut evm)?; + Ok(evm.ctx_ref().evm_clone()) } fn transact_on_db( @@ -404,9 +396,10 @@ impl> CheatcodesExecutor fork_id: Option, transaction: B256, ) -> eyre::Result<()> { - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); + let evm_env = ecx.evm_clone(); + let tx_env = ecx.tx_clone(); let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; - let (db, inner) = ecx.journal_mut().as_db_and_inner(); + let (db, inner) = ecx.db_journal_inner_mut(); db.transact(fork_id, transaction, evm_env, tx_env, inner, &mut inspector) } @@ -414,17 +407,17 @@ impl> CheatcodesExecutor &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, - tx: &TransactionRequest, + tx_env: &CTX::Tx, ) -> eyre::Result<()> { - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); + let evm_env = ecx.evm_clone(); let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; - let (db, inner) = ecx.journal_mut().as_db_and_inner(); - db.transact_from_tx(tx, evm_env, tx_env, inner, &mut inspector) + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact_from_tx(tx_env, evm_env, inner, &mut inspector) } - fn console_log(&mut self, _cheats: &mut Cheatcodes, msg: &str) { + fn console_log(&mut self, msg: &str) { if let Some(ref mut collector) = self.log_collector { - FoundryInspectorExt::console_log(&mut **collector, msg); + InspectorExt::console_log(&mut **collector, msg); } } @@ -455,27 +448,6 @@ impl InspectorStack { Self::default() } - /// Logs the status of the inspectors. - pub fn log_status(&self) { - trace!(enabled=%{ - let mut enabled = Vec::with_capacity(16); - macro_rules! push { - ($($id:ident),* $(,)?) => { - $( - if self.$id.is_some() { - enabled.push(stringify!($id)); - } - )* - }; - } - push!(cheatcodes, chisel_state, line_coverage, fuzzer, log_collector, printer, tracer); - if self.enable_isolation { - enabled.push("isolation"); - } - format!("[{}]", enabled.join(", ")) - }); - } - /// Set the solar compiler instance. #[inline] pub fn set_analysis(&mut self, analysis: Analysis) { @@ -484,9 +456,9 @@ impl InspectorStack { /// Set variables from an environment for the relevant inspectors. #[inline] - pub fn set_env(&mut self, env: &Env) { - self.set_block(&env.evm_env.block_env); - self.set_gas_price(env.tx.gas_price); + pub fn set_env(&mut self, evm_env: &EvmEnv, tx_env: &TxEnv) { + self.set_block(&evm_env.block_env); + self.set_gas_price(tx_env.gas_price); } /// Sets the block for the relevant inspectors. @@ -600,12 +572,6 @@ impl InspectorStack { InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner } } - /// Returns an [`InspectorExt`] using this stack's inspectors. - #[inline] - pub fn as_inspector(&mut self) -> impl InspectorExt + '_ { - self - } - /// Collects all the data gathered during inspection into a single struct. pub fn collect(self) -> InspectorData { let Self { @@ -667,12 +633,12 @@ impl InspectorStackRefMut<'_> { ecx.tx_mut().set_caller(inner_context_data.original_origin); } - fn do_call_end( + fn do_call_end( &mut self, ecx: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome, - ) -> CallOutcome { + ) { let result = outcome.result.result; call_inspectors!( #[ret] @@ -692,7 +658,7 @@ impl InspectorStackRefMut<'_> { let different = outcome.result.result != result || (outcome.result.result == InstructionResult::Revert && outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) + different.then_some(()) }, ); @@ -700,16 +666,14 @@ impl InspectorStackRefMut<'_> { if result.is_revert() && self.reverter.is_none() { self.reverter = Some(inputs.target_address); } - - outcome.clone() } - fn do_create_end( + fn do_create_end( &mut self, ecx: &mut CTX, call: &CreateInputs, outcome: &mut CreateOutcome, - ) -> CreateOutcome { + ) { let result = outcome.result.result; call_inspectors!( #[ret] @@ -723,14 +687,12 @@ impl InspectorStackRefMut<'_> { let different = outcome.result.result != result || (outcome.result.result == InstructionResult::Revert && outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) + different.then_some(()) }, ); - - outcome.clone() } - fn transact_inner( + fn transact_inner( &mut self, ecx: &mut CTX, kind: TxKind, @@ -738,11 +700,9 @@ impl InspectorStackRefMut<'_> { input: Bytes, gas_limit: u64, value: U256, - ) -> (InterpreterResult, Option
) - where - CTX::Journal: FoundryJournalExt, - { - let (cached_evm_env, cached_tx_env) = Env::clone_evm_and_tx(ecx); + ) -> (InterpreterResult, Option
) { + let cached_evm_env = ecx.evm_clone(); + let cached_tx_env = ecx.tx_clone(); ecx.block_mut().set_basefee(0); @@ -766,12 +726,13 @@ impl InspectorStackRefMut<'_> { self.inner_context_data = Some(InnerContextData { original_origin: cached_tx_env.caller }); self.in_inner_context = true; - let (evm_env, tx_env) = Env::clone_evm_and_tx(ecx); + let evm_env = ecx.evm_clone(); + let tx_env = ecx.tx_clone(); let res = self.with_inspector(|mut inspector| { let (res, nested_env) = { - let (db, journal) = ecx.journal_mut().as_db_and_inner(); - let mut evm = new_evm_with_inspector(db, evm_env, tx_env.clone(), &mut inspector); + let (db, journal) = ecx.db_journal_inner_mut(); + let mut evm = new_revm_with_inspector(db, evm_env, &mut inspector); evm.journal_inner_mut().state = { let mut state = journal.state.clone(); @@ -795,8 +756,8 @@ impl InspectorStackRefMut<'_> { // set depth to 1 to make sure traces are collected correctly evm.journal_inner_mut().depth = 1; - let res = evm.transact(tx_env); - let (nested_evm_env, _) = evm.to_env(); + let res = evm.transact_raw(tx_env); + let nested_evm_env = evm.ctx_ref().evm_clone(); (res, nested_evm_env) }; @@ -804,7 +765,8 @@ impl InspectorStackRefMut<'_> { // but restoring the original tx and basefee (which we zeroed for the nested call). let mut restored_evm_env = nested_env; restored_evm_env.block_env.basefee = cached_evm_env.block_env.basefee; - Env::apply_evm_and_tx(ecx, restored_evm_env, cached_tx_env); + ecx.set_evm(restored_evm_env); + ecx.set_tx(cached_tx_env); res }); @@ -927,7 +889,7 @@ impl InspectorStackRefMut<'_> { // delegate to `InspectorStackRefMut` in this case. #[inline(always)] - fn step_inlined(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { + fn step_inlined(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { call_inspectors!( [ // These are sorted in definition order. @@ -946,11 +908,7 @@ impl InspectorStackRefMut<'_> { } #[inline(always)] - fn step_end_inlined( - &mut self, - interpreter: &mut Interpreter, - ecx: &mut CTX, - ) { + fn step_end_inlined(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { call_inspectors!( [ // These are sorted in definition order. @@ -966,7 +924,7 @@ impl InspectorStackRefMut<'_> { } } -impl Inspector for InspectorStackRefMut<'_> { +impl Inspector for InspectorStackRefMut<'_> { fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { call_inspectors!( [ @@ -1074,7 +1032,7 @@ impl Inspector for InspectorStackRefMut<'_> { } // Mark accounts and storage cold before STATICCALLs CallScheme::StaticCall => { - let (_, journal_inner) = ecx.journal_mut().as_db_and_inner(); + let (_, journal_inner) = ecx.db_journal_inner_mut(); let JournaledState { state, warm_addresses, .. } = journal_inner; for (addr, acc_mut) in state { // Do not mark accounts and storage cold accounts with arbitrary storage. @@ -1171,7 +1129,7 @@ impl Inspector for InspectorStackRefMut<'_> { } } -impl FoundryInspectorExt for InspectorStackRefMut<'_> { +impl InspectorExt for InspectorStackRefMut<'_> { fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { call_inspectors!( #[ret] @@ -1183,7 +1141,7 @@ impl FoundryInspectorExt for InspectorStackRefMut<'_> { } fn console_log(&mut self, msg: &str) { - call_inspectors!([&mut self.log_collector], |inspector| FoundryInspectorExt::console_log( + call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log( inspector, msg )); } @@ -1197,7 +1155,7 @@ impl FoundryInspectorExt for InspectorStackRefMut<'_> { } } -impl Inspector for InspectorStack { +impl Inspector for InspectorStack { fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { self.as_mut().step_inlined(interpreter, ecx) } @@ -1241,7 +1199,7 @@ impl Inspector for InspectorStack { } } -impl FoundryInspectorExt for InspectorStack { +impl InspectorExt for InspectorStack { fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { self.as_mut().should_use_create2_factory(depth, inputs) } diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index b93f07dee9327..a301ddfd08786 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -13,8 +13,8 @@ pub mod inspectors; pub use foundry_evm_core as core; pub use foundry_evm_core::{ - Env, EvmEnv, FoundryInspectorExt, InspectorExt, backend, constants, decode, fork, hardfork, - opts, utils, + EvmEnv, FoundryInspectorExt, InspectorExt, backend, constants, decode, fork, hardfork, opts, + utils, }; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 5e8c452c8695a..2c5965b1141f7 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -321,7 +321,10 @@ pub enum TraceMode { /// /// Used by debugger. Debug, - /// Debug trace with storage changes. + /// Step trace with storage change recording. + /// + /// Records JUMP/JUMPDEST steps (like `Steps`) plus storage diffs on SLOAD/SSTORE. + /// Does not enable memory/stack snapshots or unfiltered opcode recording. RecordStateDiff, } @@ -370,9 +373,9 @@ impl TraceMode { match verbosity { 0..3 => self, 3..=4 => std::cmp::max(self, Self::Call), - // Enable step recording for backtraces when verbosity is 5 or higher. - // We need to ensure we're recording JUMP AND JUMPDEST steps. - _ => std::cmp::min(self, Self::Steps), + // Enable step recording and state diff recording when verbosity is 5 or higher. + // This includes backtraces (JUMP/JUMPDEST steps) and storage changes. + _ => std::cmp::max(self, Self::RecordStateDiff), } } @@ -380,23 +383,128 @@ impl TraceMode { if self.is_none() { None } else { + // RecordStateDiff is Steps + state diff recording, not Debug + state diff. + // It should not enable memory/stack snapshots. + // State diff recording requires all opcodes (no filter) since it needs + // SLOAD/SSTORE steps, not just JUMP/JUMPDEST. + let effective = if self.record_state_diff() { Self::Steps } else { self }; TracingInspectorConfig { record_steps: self >= Self::Steps, - record_memory_snapshots: self >= Self::Jump, - record_stack_snapshots: if self > Self::Steps { + record_memory_snapshots: effective >= Self::Jump, + record_stack_snapshots: if effective > Self::Steps { StackSnapshotType::Full } else { StackSnapshotType::None }, record_logs: true, record_state_diff: self.record_state_diff(), - record_returndata_snapshots: self.is_debug(), - record_opcodes_filter: (self.is_steps() || self.is_jump() || self.is_jump_simple()) - .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), + record_returndata_snapshots: effective.is_debug(), + // State diff needs all opcodes recorded to capture SLOAD/SSTORE. + record_opcodes_filter: if self.record_state_diff() { + None + } else { + (effective.is_steps() || effective.is_jump() || effective.is_jump_simple()) + .then(|| { + OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST) + }) + }, exclude_precompile_calls: false, - record_immediate_bytes: self.is_debug(), + record_immediate_bytes: effective.is_debug(), } .into() } } } + +#[cfg(test)] +mod tests { + use super::*; + + // -- TraceMode::with_verbosity level tests -- + + #[test] + fn verbosity_0_through_2_is_noop() { + for v in 0..=2 { + assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::None, "v={v}"); + assert_eq!(TraceMode::Call.with_verbosity(v), TraceMode::Call, "v={v}"); + assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); + } + } + + #[test] + fn verbosity_3_and_4_raises_to_call() { + for v in 3..=4 { + assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::Call, "v={v}"); + // Already above Call — must not downgrade. + assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); + assert_eq!( + TraceMode::RecordStateDiff.with_verbosity(v), + TraceMode::RecordStateDiff, + "v={v}" + ); + } + } + + #[test] + fn verbosity_5_raises_to_record_state_diff() { + assert_eq!(TraceMode::None.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Call.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Steps.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Debug.with_verbosity(5), TraceMode::RecordStateDiff); + // Already at the top — stays the same. + assert_eq!(TraceMode::RecordStateDiff.with_verbosity(5), TraceMode::RecordStateDiff); + } + + // -- into_config at each verbosity level -- + + #[test] + fn config_at_verbosity_0_is_none() { + let mode = TraceMode::None.with_verbosity(0); + assert!(mode.into_config().is_none()); + } + + #[test] + fn config_at_verbosity_3_records_calls_only() { + let cfg = TraceMode::None.with_verbosity(3).into_config().unwrap(); + assert!(!cfg.record_steps, "verbosity 3 should not record steps"); + assert!(!cfg.record_state_diff, "verbosity 3 should not record state diff"); + assert!(cfg.record_logs, "verbosity 3 should record logs"); + } + + #[test] + fn config_at_verbosity_5_records_steps_and_state_diff() { + let cfg = TraceMode::None.with_verbosity(5).into_config().unwrap(); + assert!(cfg.record_steps, "verbosity 5 must record steps for backtraces"); + assert!(cfg.record_state_diff, "verbosity 5 must record state diff"); + assert!(cfg.record_logs, "verbosity 5 must record logs"); + // RecordStateDiff should NOT enable expensive debug-level features. + assert!(!cfg.record_memory_snapshots, "verbosity 5 should not record memory snapshots"); + assert_eq!( + cfg.record_stack_snapshots, + StackSnapshotType::None, + "verbosity 5 should not record stack snapshots" + ); + // State diff requires all opcodes to capture SLOAD/SSTORE, so no filter. + assert!( + cfg.record_opcodes_filter.is_none(), + "verbosity 5 needs unfiltered opcodes for state diff" + ); + } + + #[test] + fn config_debug_mode_unchanged() { + // Debug mode must still enable full recording for the debugger. + let cfg = TraceMode::Debug.into_config().unwrap(); + assert!(cfg.record_steps); + assert!(cfg.record_memory_snapshots, "Debug must record memory snapshots"); + assert_eq!( + cfg.record_stack_snapshots, + StackSnapshotType::Full, + "Debug must record full stack snapshots" + ); + assert!(cfg.record_returndata_snapshots, "Debug must record returndata"); + assert!(cfg.record_immediate_bytes, "Debug must record immediate bytes"); + assert!(cfg.record_opcodes_filter.is_none(), "Debug must record all opcodes (no filter)"); + assert!(!cfg.record_state_diff, "Debug alone should not record state diff"); + } +} diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index 9c1fb32775019..ac4c697b5e5ac 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -9,6 +9,7 @@ use foundry_common::{ find_matching_contract_artifact, find_target_path, shell, }; use foundry_compilers::{ + ProjectCompileOutput, artifacts::{ StorageLayout, output_selection::{ @@ -18,9 +19,11 @@ use foundry_compilers::{ }, solc::SolcLanguage, }; +use path_slash::PathExt; use regex::Regex; use serde_json::{Map, Value}; -use std::{collections::BTreeMap, fmt, str::FromStr, sync::LazyLock}; +use solar::sema::interface::source_map::FileName; +use std::{collections::BTreeMap, fmt, ops::ControlFlow, path::Path, str::FromStr, sync::LazyLock}; /// CLI arguments for `forge inspect`. #[derive(Clone, Debug, Parser)] @@ -76,8 +79,13 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let compiler = ProjectCompiler::new().quiet(true); let target_path = find_target_path(&project, &contract)?; + if field == ContractArtifactField::Linearization && !is_solidity_source(&target_path) { + eyre::bail!( + "linearization inspection is only supported for Solidity contracts (.sol targets)" + ); + } + let compiler = ProjectCompiler::new().quiet(true); let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact @@ -169,6 +177,15 @@ impl InspectArgs { )?; } } + ContractArtifactField::Linearization => { + print_linearization( + &mut output, + project.root(), + &target_path, + contract.name(), + wrap, + )?; + } }; Ok(()) @@ -406,6 +423,111 @@ fn print_table( Ok(()) } +fn print_linearization( + output: &mut ProjectCompileOutput, + root: &Path, + target_path: &Path, + target_name: Option<&str>, + should_wrap: bool, +) -> Result<()> { + let mut chain = Vec::new(); + let mut lowered = false; + let compiler = output.parser_mut().solc_mut().compiler_mut(); + compiler.enter_mut(|compiler| -> Result<()> { + let Ok(ControlFlow::Continue(())) = compiler.lower_asts() else { return Ok(()) }; + lowered = true; + + let hir = &compiler.gcx().hir; + let matching_contracts = hir + .contract_ids() + .filter(|id| { + let contract = hir.contract(*id); + if let Some(target_name) = target_name + && contract.name.as_str() != target_name + { + return false; + } + + matches!( + &hir.source(contract.source).file.name, + FileName::Real(path) if path == target_path + ) + }) + .collect::>(); + + let target_contract = match matching_contracts.as_slice() { + [id] => *id, + [] => { + if let Some(target_name) = target_name { + eyre::bail!( + "Could not find contract `{target_name}` in `{}`", + target_path.display() + ); + } + eyre::bail!("Could not find contract in `{}`", target_path.display()); + } + _ => { + eyre::bail!( + "Multiple contracts found in the same file, please specify the target : or " + ); + } + }; + + for (order, base_id) in hir.contract(target_contract).linearized_bases.iter().enumerate() { + let contract = hir.contract(*base_id); + let source = hir.source(contract.source); + let FileName::Real(path) = &source.file.name else { continue }; + let path = path.strip_prefix(root).unwrap_or(path); + chain.push(( + order, + path.to_slash_lossy().into_owned(), + contract.name.as_str().to_string(), + )); + } + + Ok(()) + })?; + + // `compiler.sess()` inside of `ProjectCompileOutput` is built with `with_buffer_emitter`. + let diags = compiler.sess().dcx.emitted_diagnostics().unwrap(); + if compiler.sess().dcx.has_errors().is_err() { + eyre::bail!("{diags}"); + } else { + let _ = sh_eprint!("{diags}"); + } + if !lowered { + eyre::bail!( + "unable to inspect linearization: failed to lower Solidity ASTs for `{}`", + target_path.display() + ); + } + + if shell::is_json() { + let contracts = chain + .into_iter() + .map(|(order, source, contract)| { + serde_json::json!({ + "order": order, + "source": source, + "contract": contract, + }) + }) + .collect::>(); + return print_json(&contracts); + } + + let headers = vec![Cell::new("Order"), Cell::new("Source"), Cell::new("Contract")]; + print_table( + headers, + |table| { + for (order, source, contract) in &chain { + table.add_row([order.to_string(), source.to_string(), contract.to_string()]); + } + }, + should_wrap, + ) +} + /// Contract level output selection #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ContractArtifactField { @@ -428,6 +550,7 @@ pub enum ContractArtifactField { Events, StandardJson, Libraries, + Linearization, } macro_rules! impl_value_enum { @@ -512,6 +635,9 @@ impl_value_enum! { Events => "events" | "ev", StandardJson => "standardJson" | "standard-json" | "standard_json", Libraries => "libraries" | "lib" | "libs", + Linearization => "linearization" | "linearizedInheritance" + | "linearized-inheritance" | "linearized_inheritance" + | "linearizedBases" | "linearized-bases" | "linearized_bases", } } @@ -545,6 +671,9 @@ impl TryFrom for ContractOutputSelection { Err(eyre!("StandardJson is not supported for ContractOutputSelection")) } Caf::Libraries => Err(eyre!("Libraries is not supported for ContractOutputSelection")), + Caf::Linearization => { + Err(eyre!("Linearization is not supported for ContractOutputSelection")) + } } } } @@ -585,7 +714,11 @@ impl ContractArtifactField { pub const fn can_skip_field(&self) -> bool { matches!( self, - Self::Bytecode | Self::DeployedBytecode | Self::StandardJson | Self::Libraries + Self::Bytecode + | Self::DeployedBytecode + | Self::StandardJson + | Self::Libraries + | Self::Linearization ) } } @@ -632,6 +765,10 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result bool { + path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| ext.eq_ignore_ascii_case("sol")) +} + fn missing_error(field: &str) -> eyre::Error { eyre!( "{field} missing from artifact; \ @@ -662,6 +799,14 @@ mod tests { .to_string() .eq("Libraries is not supported for ContractOutputSelection") ); + } else if field == ContractArtifactField::Linearization { + let selection: Result = field.try_into(); + assert!( + selection + .unwrap_err() + .to_string() + .eq("Linearization is not supported for ContractOutputSelection") + ); } else { let selection: ContractOutputSelection = field.try_into().unwrap(); assert_eq!(field, selection); diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index b7aa4ca294098..b56d774a2b2ca 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -327,8 +327,10 @@ impl Installer<'_> { dep.tag = self.last_tag(path); } - // checkout the tag if necessary - self.git_checkout(&dep, path, false)?; + // checkout the tag if necessary, using recursive checkout to properly clean up + // nested submodules that may exist on the default branch but not on the target tag. + // See: https://github.com/foundry-rs/foundry/issues/13688 + self.git_checkout(&dep, path, true)?; trace!("updating dependency submodules recursively"); self.git.root(path).submodule_update( @@ -339,12 +341,51 @@ impl Installer<'_> { std::iter::empty::(), )?; + // remove nested .git directories from submodules before removing the top-level .git + Self::remove_nested_git_dirs(path)?; + // remove git artifacts fs::remove_dir_all(path.join(".git"))?; Ok(dep.tag) } + /// Recursively removes `.git` files/directories from nested submodules within `root`. + /// + /// Submodules typically have a `.git` file (not a directory) pointing to the parent's + /// `.git/modules/` directory. This cleans those up so the result is a plain folder tree. + fn remove_nested_git_dirs(root: &Path) -> Result<()> { + Self::remove_nested_git_dirs_inner(root, root) + } + + fn remove_nested_git_dirs_inner(root: &Path, dir: &Path) -> Result<()> { + let entries = match std::fs::read_dir(dir) { + Ok(entries) => entries, + Err(_) => return Ok(()), + }; + for entry in entries { + let entry = entry?; + let ft = entry.file_type()?; + + // never follow symlinks + if ft.is_symlink() { + continue; + } + + let path = entry.path(); + if path.file_name() == Some(".git".as_ref()) && path.parent() != Some(root) { + if ft.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } else if ft.is_dir() { + Self::remove_nested_git_dirs_inner(root, &path)?; + } + } + Ok(()) + } + /// Installs the dependency as new submodule. /// /// This will add the git submodule to the given dir, initialize it and checkout the tag if diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index d8cea4a9e2b77..85d69e7c2488a 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -321,7 +321,7 @@ impl TestArgs { evm_opts.verbosity = 3; } - let env = evm_opts.env().await?; + let (evm_env, tx_env) = evm_opts.env().await?; // Enable internal tracing for more informative flamegraph. if should_draw && !self.decode_internal { @@ -345,12 +345,12 @@ impl TestArgs { .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) - .with_fork(evm_opts.get_fork(&config, env.evm_env.clone())) + .with_fork(evm_opts.get_fork(&config, evm_env.clone())) .enable_isolation(evm_opts.isolate) .networks(evm_opts.networks) .fail_fast(self.fail_fast) .set_coverage(coverage) - .build::(output, env, evm_opts)?; + .build::(output, evm_env, tx_env, evm_opts)?; let libraries = runner.libraries.clone(); let mut outcome = self.run_tests_inner(runner, config, verbosity, filter, output).await?; @@ -522,7 +522,7 @@ impl TestArgs { } let remote_chain = - if runner.fork.is_some() { runner.env.tx.chain_id.map(Into::into) } else { None }; + if runner.fork.is_some() { runner.tx_env.chain_id.map(Into::into) } else { None }; let known_contracts = runner.known_contracts.clone(); let libraries = runner.libraries.clone(); @@ -682,7 +682,9 @@ impl TestArgs { } } - // Extract and display backtrace for failed tests when verbosity >= 3 + // Extract and display backtrace for failed tests when verbosity >= 3. + // At verbosity 3-4 backtraces show contract/function names only. + // At verbosity 5 backtraces include source file locations. if !silent && result.status.is_failure() && verbosity >= 3 diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 988191b7d7c0f..8d1e6970e9bbc 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -10,7 +10,6 @@ use eyre::Result; use foundry_cli::opts::configure_pcx_from_compile_output; use foundry_common::{ ContractsByArtifact, ContractsByArtifactBuilder, TestFunctionExt, get_contract_name, - shell::verbosity, }; use foundry_compilers::{ Artifact, ArtifactId, Compiler, ProjectCompileOutput, @@ -18,7 +17,7 @@ use foundry_compilers::{ }; use foundry_config::{Config, InlineConfig}; use foundry_evm::{ - Env, + EvmEnv, backend::Backend, decode::RevertDecoder, executors::{EarlyExit, Executor, ExecutorBuilder}, @@ -31,7 +30,7 @@ use foundry_evm::{ use foundry_evm_networks::NetworkConfigs; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; -use revm::primitives::hardfork::SpecId; +use revm::{context::TxEnv, primitives::hardfork::SpecId}; use std::{ borrow::Borrow, collections::BTreeMap, @@ -294,7 +293,9 @@ pub struct TestRunnerConfig { /// EVM configuration. pub evm_opts: EvmOpts, /// EVM environment. - pub env: Env, + pub evm_env: EvmEnv, + /// Transaction environment. + pub tx_env: TxEnv, /// EVM version. pub spec_id: SpecId, /// The address which will be used to deploy the initial contracts and send all transactions. @@ -389,7 +390,7 @@ impl TestRunnerConfig { .spec_id(self.spec_id) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db) + .build(self.evm_env.clone(), self.tx_env.clone(), db) } fn trace_mode(&self) -> TraceMode { @@ -397,7 +398,6 @@ impl TestRunnerConfig { .with_debug(self.debug) .with_decode_internal(self.decode_internal) .with_verbosity(self.evm_opts.verbosity) - .with_state_changes(verbosity() > 4) } } @@ -502,7 +502,8 @@ impl MultiContractRunnerBuilder { pub fn build>( self, output: &ProjectCompileOutput, - env: Env, + evm_env: EvmEnv, + tx_env: TxEnv, evm_opts: EvmOpts, ) -> Result { let root = &self.config.root; @@ -601,7 +602,8 @@ impl MultiContractRunnerBuilder { tcfg: TestRunnerConfig { evm_opts, - env, + evm_env, + tx_env, spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), sender: self.sender.unwrap_or(self.config.sender), line_coverage: self.line_coverage, diff --git a/crates/forge/tests/cli/backtrace.rs b/crates/forge/tests/cli/backtrace.rs index a38c60de6bc30..434fa4cb289ba 100644 --- a/crates/forge/tests/cli/backtrace.rs +++ b/crates/forge/tests/cli/backtrace.rs @@ -548,3 +548,141 @@ Backtrace: ... "#]]); }); + +// Test that backtraces only appear at verbosity 5 (-vvvvv). +// Runs the same failing test at every verbosity level to assert correct output. +#[cfg(not(feature = "isolate-by-default"))] +forgetest!(test_backtrace_verbosity_levels, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + prj.add_source( + "SimpleRevert.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SimpleRevert { + function doRevert() public pure { + revert("Simple revert message"); + } +} +"#, + ); + + prj.add_test( + "BacktraceVerbosity.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/SimpleRevert.sol"; + +contract BacktraceVerbosityTest is DSTest { + SimpleRevert simpleRevert; + + function setUp() public { + simpleRevert = new SimpleRevert(); + } + + function testRevert() public { + simpleRevert.doRevert(); + } +} +"#, + ); + + // -v (verbosity 1): no traces, no backtrace. + cmd.args(["test", "--mc", "BacktraceVerbosityTest", "-v"]).assert_failure().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"# + ]]); + + // -vvv (verbosity 3): traces and backtrace WITHOUT source locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert + at BacktraceVerbosityTest.testRevert + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // -vvvv (verbosity 4): traces with setup and backtrace WITHOUT source locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::setUp() + ├─ [..] → new SimpleRevert@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] + └─ ← [Stop] + + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert + at BacktraceVerbosityTest.testRevert + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // -vvvvv (verbosity 5): traces with setup, storage changes, and backtrace WITH source + // locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::setUp() + ├─ [..] → new SimpleRevert@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] + └─ ← [Stop] + + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert (src/SimpleRevert.sol:[..]:[..]) + at BacktraceVerbosityTest.testRevert (test/BacktraceVerbosity.t.sol:[..]:[..]) + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 30f0f43001b60..9f352a0109a66 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1227,6 +1227,99 @@ contract Foo { ]]); }); +forgetest!(can_inspect_linearization_markdown, |prj, cmd| { + prj.add_source("A.sol", "contract A {}"); + prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#); + prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#); + + cmd.forge_fuse().args(["inspect", "C", "linearization", "--md"]).assert_success().stdout_eq( + str![[r#" + +| Order | Source | Contract | +|-------|-----------|----------| +| 0 | src/C.sol | C | +| 1 | src/B.sol | B | +| 2 | src/A.sol | A | + + +"#]], + ); +}); + +forgetest!(can_inspect_linearization_json, |prj, cmd| { + prj.add_source("A.sol", "contract A {}"); + prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#); + prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#); + + cmd.forge_fuse().args(["inspect", "C", "linearization", "--json"]).assert_success().stdout_eq( + str![[r#" +[ + { + "order": 0, + "source": "src/C.sol", + "contract": "C" + }, + { + "order": 1, + "source": "src/B.sol", + "contract": "B" + }, + { + "order": 2, + "source": "src/A.sol", + "contract": "A" + } +] + +"#]], + ); +}); + +forgetest!(can_inspect_linearization_path_qualified_contract, |prj, cmd| { + prj.add_source("one/Base.sol", "contract Base {}"); + prj.add_source( + "one/Target.sol", + r#"import {Base} from "./Base.sol"; contract Target is Base {}"#, + ); + + prj.add_source("two/Base.sol", "contract Base {}"); + prj.add_source( + "two/Target.sol", + r#"import {Base} from "./Base.sol"; contract Target is Base {}"#, + ); + + cmd.forge_fuse() + .args(["inspect", "src/two/Target.sol:Target", "linearization", "--json"]) + .assert_success() + .stdout_eq(str![[r#" +[ + { + "order": 0, + "source": "src/two/Target.sol", + "contract": "Target" + }, + { + "order": 1, + "source": "src/two/Base.sol", + "contract": "Base" + } +] + +"#]]); +}); + +forgetest!(cannot_inspect_linearization_non_solidity_target, |prj, cmd| { + prj.create_file("src/NotSol.vy", "x: uint256"); + + cmd.forge_fuse() + .args(["inspect", "src/NotSol.vy", "linearization"]) + .assert_failure() + .stderr_eq(str![[r#" +Error: linearization inspection is only supported for Solidity contracts (.sol targets) + +"#]]); +}); + // test that `forge snapshot` commands work forgetest!(can_check_snapshot, |prj, cmd| { prj.insert_ds_test(); diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index f95f865f92bfb..838d791c5d0e7 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -591,6 +591,49 @@ async fn correctly_sync_dep_with_multiple_version() { assert_eq!(solday_v_245.rev(), submod_solday_v_245.rev()); } +// Regression test: `forge install --no-git` should clean up nested submodule contents +// when installing a tag that does not use submodules for its dependencies. +// https://github.com/foundry-rs/foundry/issues/13688 +forgetest!(flaky_install_no_git_cleans_nested_submodules, |prj, cmd| { + cmd.git_init(); + + // Install openzeppelin-contracts-upgradeable at v4.7.3 with --no-git. + // The default branch has submodules in lib/ (e.g. openzeppelin-contracts, erc4626-tests), + // but v4.7.3 does not use submodules for dependencies. + cmd.forge_fuse() + .args(["install", "--no-git", "OpenZeppelin/openzeppelin-contracts-upgradeable@v4.7.3"]) + .assert_success(); + + let dep_dir = prj.root().join("lib").join("openzeppelin-contracts-upgradeable"); + assert!(dep_dir.exists(), "dependency should be installed"); + + // The nested lib/ directory should either not exist or be empty — v4.7.3 does not use + // submodules so there should be no leftover submodule contents from the default branch. + let nested_lib = dep_dir.join("lib"); + if nested_lib.exists() { + let entries: Vec<_> = fs::read_dir(&nested_lib).unwrap().collect(); + assert!( + entries.is_empty(), + "nested lib/ should be empty after --no-git install at v4.7.3, found: {entries:?}" + ); + } + + // There should be no .git file or directory anywhere under the installed dependency. + fn assert_no_git(dir: &Path) { + for entry in fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.file_name() == Some(".git".as_ref()) { + panic!("found leftover .git at {}", path.display()); + } + if path.is_dir() { + assert_no_git(&path); + } + } + } + assert_no_git(&dep_dir); +}); + forgetest_init!(sync_on_forge_update, |prj, cmd| { let git = Git::new(prj.root()); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 7f3dc7b9cd7a5..2714011ec8d3d 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2,6 +2,7 @@ use crate::constants::TEMPLATE_CONTRACT; use alloy_hardforks::EthereumHardfork; +use alloy_network::Ethereum; use alloy_primitives::{Address, Bytes, address, hex}; use anvil::{NodeConfig, spawn}; use forge_script_sequence::ScriptSequence; @@ -2419,7 +2420,8 @@ contract ContractScript is Script { .find(|file| file.ends_with("run-latest.json")) .expect("No broadcast artifacts"); - let sequence: ScriptSequence = foundry_common::fs::read_json_file(&run_latest).unwrap(); + let sequence: ScriptSequence = + foundry_common::fs::read_json_file(&run_latest).unwrap(); assert_eq!(sequence.transactions.len(), 2); assert_eq!(sequence.transactions[1].additional_contracts.len(), 1); @@ -2566,7 +2568,7 @@ maxFeePerGas maxPriorityFeePerGas nonce 0 to -type 0 +type EIP-1559 value 0 ### Transaction 2 ### @@ -2581,7 +2583,7 @@ maxFeePerGas maxPriorityFeePerGas nonce 1 to 0x5FbDB2315678afecb367f032d93F642f64180aa3 -type 0 +type EIP-1559 value 0 contract: Called(0x5FbDB2315678afecb367f032d93F642f64180aa3) data (decoded): run(uint256,uint256)( @@ -3061,7 +3063,7 @@ contract FactoryScript is Script { .assert_success(); let broadcast_log = prj.root().join("broadcast/Factory.s.sol/31337/run-latest.json"); - let script_sequence: ScriptSequence = serde_json::from_reader( + let script_sequence: ScriptSequence = serde_json::from_reader( fs::File::open(prj.artifacts().join(broadcast_log)).expect("no broadcast log"), ) .expect("no script sequence"); diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 82ec6744a6172..32bfe6a98a9fd 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -843,6 +843,36 @@ Ran 1 test for test/Issue12803.t.sol:Issue12803Test ]); }); +// https://github.com/foundry-rs/foundry/issues/13766 +// vm.expectRevert(bytes("")) should not panic when actual revert has data +forgetest_init!(issue_13766, |prj, cmd| { + prj.add_test( + "Issue13766.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Reverter { + error CustomError(); + function revertWithData() public pure { revert CustomError(); } +} + +contract Issue13766Test is Test { + function test_expectRevertEmptyBytes() public { + Reverter r = new Reverter(); + vm.expectRevert(bytes("")); + r.revertWithData(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +[FAIL: Error != expected error: CustomError() != EvmError: Revert] test_expectRevertEmptyBytes() ([GAS]) +... +"#]]); +}); + // https://github.com/foundry-rs/foundry/issues/12803 // Test multiple storage deletions (higher refund) don't cause underflow forgetest_init!(issue_12803_multiple_deletes, |prj, cmd| { diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json index 244a2d94fc7eb..39544b6ccbbe9 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -15,7 +15,9 @@ "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000033130300000000000000000000000000000000000000000000000000000000000" } ], - "decoded_logs": ["100"], + "decoded_logs": [ + "100" + ], "kind": { "Unit": { "gas": "{...}" @@ -62,7 +64,11 @@ "arena": [ { "parent": null, - "children": [1, 2, 3], + "children": [ + 1, + 2, + 3 + ], "idx": 0, "trace": { "depth": 0, @@ -85,9 +91,9 @@ { "pc": 0, "op": 96, - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -101,9 +107,9 @@ { "pc": 2, "op": 96, - "stack": ["0x80"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -117,9 +123,9 @@ { "pc": 4, "op": 82, - "stack": ["0x80", "0x40"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -133,9 +139,9 @@ { "pc": 5, "op": 52, - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -149,9 +155,9 @@ { "pc": 6, "op": 128, - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -165,9 +171,9 @@ { "pc": 7, "op": 21, - "stack": ["0x0", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -181,9 +187,9 @@ { "pc": 8, "op": 97, - "stack": ["0x0", "0x1"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -197,9 +203,9 @@ { "pc": 11, "op": 87, - "stack": ["0x0", "0x1", "0xf"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -213,9 +219,9 @@ { "pc": 15, "op": 91, - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -229,9 +235,9 @@ { "pc": 16, "op": 80, - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -245,9 +251,9 @@ { "pc": 17, "op": 96, - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -261,9 +267,9 @@ { "pc": 19, "op": 54, - "stack": ["0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -277,9 +283,9 @@ { "pc": 20, "op": 16, - "stack": ["0x4", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -293,9 +299,9 @@ { "pc": 21, "op": 97, - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -309,9 +315,9 @@ { "pc": 24, "op": 87, - "stack": ["0x0", "0x4a"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -325,9 +331,9 @@ { "pc": 25, "op": 95, - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -341,9 +347,9 @@ { "pc": 26, "op": 53, - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -357,11 +363,9 @@ { "pc": 27, "op": 96, - "stack": [ - "0xf8a8fd6d00000000000000000000000000000000000000000000000000000000" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -375,12 +379,9 @@ { "pc": 29, "op": 28, - "stack": [ - "0xf8a8fd6d00000000000000000000000000000000000000000000000000000000", - "0xe0" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -394,9 +395,9 @@ { "pc": 30, "op": 128, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -410,9 +411,9 @@ { "pc": 31, "op": 99, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -426,9 +427,9 @@ { "pc": 36, "op": 20, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d", "0xba414fa6"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -442,9 +443,9 @@ { "pc": 37, "op": 97, - "stack": ["0xf8a8fd6d", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -458,9 +459,9 @@ { "pc": 40, "op": 87, - "stack": ["0xf8a8fd6d", "0x0", "0x4e"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -474,9 +475,9 @@ { "pc": 41, "op": 128, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -490,9 +491,9 @@ { "pc": 42, "op": 99, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -506,9 +507,9 @@ { "pc": 47, "op": 20, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d", "0xf6e62afc"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -522,9 +523,9 @@ { "pc": 48, "op": 97, - "stack": ["0xf8a8fd6d", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -538,9 +539,9 @@ { "pc": 51, "op": 87, - "stack": ["0xf8a8fd6d", "0x0", "0x6c"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -554,9 +555,9 @@ { "pc": 52, "op": 128, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -570,9 +571,9 @@ { "pc": 53, "op": 99, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -586,9 +587,9 @@ { "pc": 58, "op": 20, - "stack": ["0xf8a8fd6d", "0xf8a8fd6d", "0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -602,9 +603,9 @@ { "pc": 59, "op": 97, - "stack": ["0xf8a8fd6d", "0x1"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -618,9 +619,9 @@ { "pc": 62, "op": 87, - "stack": ["0xf8a8fd6d", "0x1", "0x8a"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -634,9 +635,9 @@ { "pc": 138, "op": 91, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -650,9 +651,9 @@ { "pc": 139, "op": 97, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -666,9 +667,9 @@ { "pc": 142, "op": 97, - "stack": ["0xf8a8fd6d", "0x92"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -682,9 +683,9 @@ { "pc": 145, "op": 86, - "stack": ["0xf8a8fd6d", "0x92", "0x25a"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -698,9 +699,9 @@ { "pc": 602, "op": 91, - "stack": ["0xf8a8fd6d", "0x92"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -714,9 +715,9 @@ { "pc": 603, "op": 95, - "stack": ["0xf8a8fd6d", "0x92"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -730,9 +731,9 @@ { "pc": 604, "op": 96, - "stack": ["0xf8a8fd6d", "0x92", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -746,9 +747,9 @@ { "pc": 606, "op": 81, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x40"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -762,9 +763,9 @@ { "pc": 607, "op": 97, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -778,9 +779,9 @@ { "pc": 610, "op": 144, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x80", "0x267"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -794,9 +795,9 @@ { "pc": 611, "op": 97, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x267", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -810,16 +811,9 @@ { "pc": 614, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x40b" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -833,9 +827,9 @@ { "pc": 1035, "op": 91, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x267", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -849,9 +843,9 @@ { "pc": 1036, "op": 97, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x267", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -865,16 +859,9 @@ { "pc": 1039, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x142" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -888,17 +875,9 @@ { "pc": 1040, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x142", - "0x142" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -912,18 +891,9 @@ { "pc": 1043, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x142", - "0x142", - "0x6c0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -937,19 +907,9 @@ { "pc": 1044, "op": 57, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x142", - "0x142", - "0x6c0", - "0x80" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -963,16 +923,9 @@ { "pc": 1045, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x80", - "0x142" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -986,15 +939,9 @@ { "pc": 1046, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x267", - "0x1c2" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1008,15 +955,9 @@ { "pc": 1047, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x1c2", - "0x267" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1030,9 +971,9 @@ { "pc": 615, "op": 91, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1046,9 +987,9 @@ { "pc": 616, "op": 96, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1062,9 +1003,9 @@ { "pc": 618, "op": 81, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2", "0x40"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1078,9 +1019,9 @@ { "pc": 619, "op": 128, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x1c2", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1094,16 +1035,9 @@ { "pc": 620, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x1c2", - "0x80", - "0x80" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1117,16 +1051,9 @@ { "pc": 621, "op": 3, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x80", - "0x80", - "0x1c2" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1140,9 +1067,9 @@ { "pc": 622, "op": 144, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x80", "0x142"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1156,9 +1083,9 @@ { "pc": 623, "op": 95, - "stack": ["0xf8a8fd6d", "0x92", "0x0", "0x142", "0x80"], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1172,16 +1099,9 @@ { "pc": 624, "op": 240, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x142", - "0x80", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1195,14 +1115,9 @@ { "pc": 625, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -1216,15 +1131,9 @@ { "pc": 626, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629525, "gas_refund_counter": 0, @@ -1238,15 +1147,9 @@ { "pc": 627, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629522, "gas_refund_counter": 0, @@ -1260,16 +1163,9 @@ { "pc": 628, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629519, "gas_refund_counter": 0, @@ -1283,16 +1179,9 @@ { "pc": 629, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x1" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629516, "gas_refund_counter": 0, @@ -1306,17 +1195,9 @@ { "pc": 632, "op": 87, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x1", - "0x280" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629513, "gas_refund_counter": 0, @@ -1330,15 +1211,9 @@ { "pc": 640, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629503, "gas_refund_counter": 0, @@ -1352,15 +1227,9 @@ { "pc": 641, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629502, "gas_refund_counter": 0, @@ -1374,14 +1243,9 @@ { "pc": 642, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629500, "gas_refund_counter": 0, @@ -1395,14 +1259,9 @@ { "pc": 643, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629497, "gas_refund_counter": 0, @@ -1416,13 +1275,9 @@ { "pc": 644, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629495, "gas_refund_counter": 0, @@ -1436,14 +1291,9 @@ { "pc": 645, "op": 115, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629492, "gas_refund_counter": 0, @@ -1457,15 +1307,9 @@ { "pc": 666, "op": 22, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xffffffffffffffffffffffffffffffffffffffff" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629489, "gas_refund_counter": 0, @@ -1479,14 +1323,9 @@ { "pc": 667, "op": 99, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629486, "gas_refund_counter": 0, @@ -1500,15 +1339,9 @@ { "pc": 672, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629483, "gas_refund_counter": 0, @@ -1522,16 +1355,9 @@ { "pc": 674, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629480, "gas_refund_counter": 0, @@ -1545,17 +1371,9 @@ { "pc": 676, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x40" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629477, "gas_refund_counter": 0, @@ -1569,17 +1387,9 @@ { "pc": 677, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629474, "gas_refund_counter": 0, @@ -1593,18 +1403,9 @@ { "pc": 678, "op": 99, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d1474" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629471, "gas_refund_counter": 0, @@ -1618,19 +1419,9 @@ { "pc": 683, "op": 22, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d1474", - "0xffffffff" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629468, "gas_refund_counter": 0, @@ -1644,18 +1435,9 @@ { "pc": 684, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d1474" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629465, "gas_refund_counter": 0, @@ -1669,19 +1451,9 @@ { "pc": 686, "op": 27, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d1474", - "0xe0" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629462, "gas_refund_counter": 0, @@ -1695,18 +1467,9 @@ { "pc": 687, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d147400000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629459, "gas_refund_counter": 0, @@ -1720,19 +1483,9 @@ { "pc": 688, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0xe26d147400000000000000000000000000000000000000000000000000000000", - "0x80" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f5ffd5b506101268061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629456, "gas_refund_counter": 0, @@ -1746,17 +1499,9 @@ { "pc": 689, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629453, "gas_refund_counter": 0, @@ -1770,18 +1515,9 @@ { "pc": 691, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x80", - "0x4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629450, "gas_refund_counter": 0, @@ -1795,17 +1531,9 @@ { "pc": 692, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629447, "gas_refund_counter": 0, @@ -1819,18 +1547,9 @@ { "pc": 695, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x64", - "0x84", - "0x2bd" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629444, "gas_refund_counter": 0, @@ -1844,18 +1563,9 @@ { "pc": 696, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629441, "gas_refund_counter": 0, @@ -1869,18 +1579,9 @@ { "pc": 697, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629438, "gas_refund_counter": 0, @@ -1894,19 +1595,9 @@ { "pc": 700, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0x651" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629435, "gas_refund_counter": 0, @@ -1920,18 +1611,9 @@ { "pc": 1617, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629427, "gas_refund_counter": 0, @@ -1945,18 +1627,9 @@ { "pc": 1618, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629426, "gas_refund_counter": 0, @@ -1970,19 +1643,9 @@ { "pc": 1619, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629424, "gas_refund_counter": 0, @@ -1996,20 +1659,9 @@ { "pc": 1621, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0x0", - "0x20" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629421, "gas_refund_counter": 0, @@ -2023,21 +1675,9 @@ { "pc": 1622, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0x0", - "0x20", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629418, "gas_refund_counter": 0, @@ -2051,20 +1691,9 @@ { "pc": 1623, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0x0", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629415, "gas_refund_counter": 0, @@ -2078,20 +1707,9 @@ { "pc": 1624, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629412, "gas_refund_counter": 0, @@ -2105,19 +1723,9 @@ { "pc": 1625, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629410, "gas_refund_counter": 0, @@ -2131,20 +1739,9 @@ { "pc": 1628, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629407, "gas_refund_counter": 0, @@ -2158,21 +1755,9 @@ { "pc": 1629, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629405, "gas_refund_counter": 0, @@ -2186,22 +1771,9 @@ { "pc": 1630, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x0", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629402, "gas_refund_counter": 0, @@ -2215,21 +1787,9 @@ { "pc": 1631, "op": 132, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629399, "gas_refund_counter": 0, @@ -2243,22 +1803,9 @@ { "pc": 1632, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629396, "gas_refund_counter": 0, @@ -2272,23 +1819,9 @@ { "pc": 1635, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x642" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629393, "gas_refund_counter": 0, @@ -2302,22 +1835,9 @@ { "pc": 1602, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629385, "gas_refund_counter": 0, @@ -2331,22 +1851,9 @@ { "pc": 1603, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629384, "gas_refund_counter": 0, @@ -2360,23 +1867,9 @@ { "pc": 1606, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629381, "gas_refund_counter": 0, @@ -2390,24 +1883,9 @@ { "pc": 1607, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629378, "gas_refund_counter": 0, @@ -2421,25 +1899,9 @@ { "pc": 1610, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x621" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629375, "gas_refund_counter": 0, @@ -2453,24 +1915,9 @@ { "pc": 1569, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629367, "gas_refund_counter": 0, @@ -2484,24 +1931,9 @@ { "pc": 1570, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629366, "gas_refund_counter": 0, @@ -2515,25 +1947,9 @@ { "pc": 1571, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629364, "gas_refund_counter": 0, @@ -2547,26 +1963,9 @@ { "pc": 1574, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629361, "gas_refund_counter": 0, @@ -2580,27 +1979,9 @@ { "pc": 1577, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629358, "gas_refund_counter": 0, @@ -2614,28 +1995,9 @@ { "pc": 1580, "op": 132, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629355, "gas_refund_counter": 0, @@ -2649,29 +2011,9 @@ { "pc": 1581, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629352, "gas_refund_counter": 0, @@ -2685,30 +2027,9 @@ { "pc": 1584, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64", - "0x606" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629349, "gas_refund_counter": 0, @@ -2722,29 +2043,9 @@ { "pc": 1542, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629341, "gas_refund_counter": 0, @@ -2758,29 +2059,9 @@ { "pc": 1543, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629340, "gas_refund_counter": 0, @@ -2794,30 +2075,9 @@ { "pc": 1544, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629338, "gas_refund_counter": 0, @@ -2831,31 +2091,9 @@ { "pc": 1545, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629335, "gas_refund_counter": 0, @@ -2869,31 +2107,9 @@ { "pc": 1546, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629332, "gas_refund_counter": 0, @@ -2907,30 +2123,9 @@ { "pc": 1547, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x631", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629330, "gas_refund_counter": 0, @@ -2944,30 +2139,9 @@ { "pc": 1548, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x64", - "0x631" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629327, "gas_refund_counter": 0, @@ -2981,30 +2155,9 @@ { "pc": 1549, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x631", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629324, "gas_refund_counter": 0, @@ -3018,29 +2171,9 @@ { "pc": 1550, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x631" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629322, "gas_refund_counter": 0, @@ -3054,28 +2187,9 @@ { "pc": 1585, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629314, "gas_refund_counter": 0, @@ -3089,28 +2203,9 @@ { "pc": 1586, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629313, "gas_refund_counter": 0, @@ -3124,29 +2219,9 @@ { "pc": 1589, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x618" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629310, "gas_refund_counter": 0, @@ -3160,28 +2235,9 @@ { "pc": 1560, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629302, "gas_refund_counter": 0, @@ -3195,28 +2251,9 @@ { "pc": 1561, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629301, "gas_refund_counter": 0, @@ -3230,29 +2267,9 @@ { "pc": 1562, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629299, "gas_refund_counter": 0, @@ -3266,30 +2283,9 @@ { "pc": 1563, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629296, "gas_refund_counter": 0, @@ -3303,30 +2299,9 @@ { "pc": 1564, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629293, "gas_refund_counter": 0, @@ -3340,29 +2315,9 @@ { "pc": 1565, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x636", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629291, "gas_refund_counter": 0, @@ -3376,29 +2331,9 @@ { "pc": 1566, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x64", - "0x636" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629288, "gas_refund_counter": 0, @@ -3412,29 +2347,9 @@ { "pc": 1567, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x636", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629285, "gas_refund_counter": 0, @@ -3448,28 +2363,9 @@ { "pc": 1568, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x636" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629283, "gas_refund_counter": 0, @@ -3483,27 +2379,9 @@ { "pc": 1590, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629275, "gas_refund_counter": 0, @@ -3517,27 +2395,9 @@ { "pc": 1591, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629274, "gas_refund_counter": 0, @@ -3551,28 +2411,9 @@ { "pc": 1594, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x60f" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629271, "gas_refund_counter": 0, @@ -3586,27 +2427,9 @@ { "pc": 1551, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629263, "gas_refund_counter": 0, @@ -3620,27 +2443,9 @@ { "pc": 1552, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629262, "gas_refund_counter": 0, @@ -3654,28 +2459,9 @@ { "pc": 1553, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629260, "gas_refund_counter": 0, @@ -3689,29 +2475,9 @@ { "pc": 1554, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629257, "gas_refund_counter": 0, @@ -3725,29 +2491,9 @@ { "pc": 1555, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629254, "gas_refund_counter": 0, @@ -3761,28 +2507,9 @@ { "pc": 1556, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x63b", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629252, "gas_refund_counter": 0, @@ -3796,28 +2523,9 @@ { "pc": 1557, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x64", - "0x64", - "0x63b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629249, "gas_refund_counter": 0, @@ -3831,28 +2539,9 @@ { "pc": 1558, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x64", - "0x63b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629246, "gas_refund_counter": 0, @@ -3866,27 +2555,9 @@ { "pc": 1559, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x64", - "0x63b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629244, "gas_refund_counter": 0, @@ -3900,26 +2571,9 @@ { "pc": 1595, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629236, "gas_refund_counter": 0, @@ -3933,26 +2587,9 @@ { "pc": 1596, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629235, "gas_refund_counter": 0, @@ -3966,26 +2603,9 @@ { "pc": 1597, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629232, "gas_refund_counter": 0, @@ -3999,25 +2619,9 @@ { "pc": 1598, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64b", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629230, "gas_refund_counter": 0, @@ -4031,25 +2635,9 @@ { "pc": 1599, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64", - "0x64", - "0x64b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629227, "gas_refund_counter": 0, @@ -4063,25 +2651,9 @@ { "pc": 1600, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64", - "0x64b", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629224, "gas_refund_counter": 0, @@ -4095,24 +2667,9 @@ { "pc": 1601, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64", - "0x64b" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629222, "gas_refund_counter": 0, @@ -4126,23 +2683,9 @@ { "pc": 1611, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629214, "gas_refund_counter": 0, @@ -4156,23 +2699,9 @@ { "pc": 1612, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629213, "gas_refund_counter": 0, @@ -4186,24 +2715,9 @@ { "pc": 1613, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64", - "0x64", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629210, "gas_refund_counter": 0, @@ -4217,22 +2731,9 @@ { "pc": 1614, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629207, "gas_refund_counter": 0, @@ -4246,21 +2747,9 @@ { "pc": 1615, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629205, "gas_refund_counter": 0, @@ -4274,20 +2763,9 @@ { "pc": 1616, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4", - "0x664" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629203, "gas_refund_counter": 0, @@ -4301,19 +2779,9 @@ { "pc": 1636, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629195, "gas_refund_counter": 0, @@ -4327,19 +2795,9 @@ { "pc": 1637, "op": 146, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0x2bd", - "0x64", - "0x84", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629194, "gas_refund_counter": 0, @@ -4353,19 +2811,9 @@ { "pc": 1638, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x64", - "0x84", - "0x2bd" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629191, "gas_refund_counter": 0, @@ -4379,19 +2827,9 @@ { "pc": 1639, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x2bd", - "0x84", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629188, "gas_refund_counter": 0, @@ -4405,18 +2843,9 @@ { "pc": 1640, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x2bd", - "0x84" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629186, "gas_refund_counter": 0, @@ -4430,17 +2859,9 @@ { "pc": 1641, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x2bd" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629184, "gas_refund_counter": 0, @@ -4454,16 +2875,9 @@ { "pc": 701, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629176, "gas_refund_counter": 0, @@ -4477,16 +2891,9 @@ { "pc": 702, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073629175, "gas_refund_counter": 0, @@ -4500,17 +2907,9 @@ { "pc": 703, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629173, "gas_refund_counter": 0, @@ -4524,18 +2923,9 @@ { "pc": 705, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x40" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629170, "gas_refund_counter": 0, @@ -4549,18 +2939,9 @@ { "pc": 706, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629167, "gas_refund_counter": 0, @@ -4574,19 +2955,9 @@ { "pc": 707, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629164, "gas_refund_counter": 0, @@ -4600,20 +2971,9 @@ { "pc": 708, "op": 3, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x80", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629161, "gas_refund_counter": 0, @@ -4627,19 +2987,9 @@ { "pc": 709, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629158, "gas_refund_counter": 0, @@ -4653,20 +3003,9 @@ { "pc": 710, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629155, "gas_refund_counter": 0, @@ -4680,21 +3019,9 @@ { "pc": 711, "op": 135, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629153, "gas_refund_counter": 0, @@ -4708,22 +3035,9 @@ { "pc": 712, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629150, "gas_refund_counter": 0, @@ -4737,23 +3051,9 @@ { "pc": 713, "op": 59, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629147, "gas_refund_counter": 0, @@ -4767,23 +3067,9 @@ { "pc": 714, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x126" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629047, "gas_refund_counter": 0, @@ -4797,23 +3083,9 @@ { "pc": 715, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629044, "gas_refund_counter": 0, @@ -4827,24 +3099,9 @@ { "pc": 716, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629041, "gas_refund_counter": 0, @@ -4858,24 +3115,9 @@ { "pc": 717, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629038, "gas_refund_counter": 0, @@ -4889,25 +3131,9 @@ { "pc": 720, "op": 87, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0", - "0x1", - "0x2d4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629035, "gas_refund_counter": 0, @@ -4921,23 +3147,9 @@ { "pc": 724, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629025, "gas_refund_counter": 0, @@ -4951,23 +3163,9 @@ { "pc": 725, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629024, "gas_refund_counter": 0, @@ -4981,22 +3179,9 @@ { "pc": 726, "op": 90, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629022, "gas_refund_counter": 0, @@ -5010,23 +3195,9 @@ { "pc": 727, "op": 241, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x80", - "0x24", - "0x80", - "0x0", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x3ffe475c" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073629020, "gas_refund_counter": 0, @@ -5040,17 +3211,9 @@ { "pc": 728, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606406, "gas_refund_counter": 0, @@ -5064,17 +3227,9 @@ { "pc": 729, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606403, "gas_refund_counter": 0, @@ -5088,18 +3243,9 @@ { "pc": 730, "op": 21, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606400, "gas_refund_counter": 0, @@ -5113,18 +3259,9 @@ { "pc": 731, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606397, "gas_refund_counter": 0, @@ -5138,19 +3275,9 @@ { "pc": 734, "op": 87, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0", - "0x1", - "0x2e6" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606394, "gas_refund_counter": 0, @@ -5164,17 +3291,9 @@ { "pc": 742, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606384, "gas_refund_counter": 0, @@ -5188,17 +3307,9 @@ { "pc": 743, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606383, "gas_refund_counter": 0, @@ -5212,16 +3323,9 @@ { "pc": 744, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474", - "0xa4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606381, "gas_refund_counter": 0, @@ -5235,15 +3339,9 @@ { "pc": 745, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0xe26d1474" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606379, "gas_refund_counter": 0, @@ -5257,14 +3355,9 @@ { "pc": 746, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606377, "gas_refund_counter": 0, @@ -5278,13 +3371,9 @@ { "pc": 747, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606375, "gas_refund_counter": 0, @@ -5298,14 +3387,9 @@ { "pc": 750, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606372, "gas_refund_counter": 0, @@ -5319,15 +3403,9 @@ { "pc": 752, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606369, "gas_refund_counter": 0, @@ -5341,16 +3419,9 @@ { "pc": 755, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x32e" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606366, "gas_refund_counter": 0, @@ -5364,15 +3435,9 @@ { "pc": 814, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606358, "gas_refund_counter": 0, @@ -5386,15 +3451,9 @@ { "pc": 815, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606357, "gas_refund_counter": 0, @@ -5408,16 +3467,9 @@ { "pc": 818, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073606354, "gas_refund_counter": 0, @@ -5431,17 +3483,9 @@ { "pc": 819, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606351, "gas_refund_counter": 0, @@ -5455,18 +3499,9 @@ { "pc": 821, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64", - "0x40" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606348, "gas_refund_counter": 0, @@ -5480,18 +3515,9 @@ { "pc": 822, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606345, "gas_refund_counter": 0, @@ -5505,19 +3531,9 @@ { "pc": 824, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64", - "0x80", - "0x24" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606342, "gas_refund_counter": 0, @@ -5531,18 +3547,9 @@ { "pc": 825, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606339, "gas_refund_counter": 0, @@ -5556,19 +3563,9 @@ { "pc": 828, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x64", - "0xa4", - "0x342" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606336, "gas_refund_counter": 0, @@ -5582,19 +3579,9 @@ { "pc": 829, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606333, "gas_refund_counter": 0, @@ -5608,19 +3595,9 @@ { "pc": 830, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606330, "gas_refund_counter": 0, @@ -5634,20 +3611,9 @@ { "pc": 833, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0x679" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606327, "gas_refund_counter": 0, @@ -5661,19 +3627,9 @@ { "pc": 1657, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606319, "gas_refund_counter": 0, @@ -5687,19 +3643,9 @@ { "pc": 1658, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606318, "gas_refund_counter": 0, @@ -5713,20 +3659,9 @@ { "pc": 1659, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606316, "gas_refund_counter": 0, @@ -5740,21 +3675,9 @@ { "pc": 1661, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0x0", - "0x20" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606313, "gas_refund_counter": 0, @@ -5768,22 +3691,9 @@ { "pc": 1662, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0x0", - "0x20", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606310, "gas_refund_counter": 0, @@ -5797,21 +3707,9 @@ { "pc": 1663, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0x0", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606307, "gas_refund_counter": 0, @@ -5825,21 +3723,9 @@ { "pc": 1664, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606304, "gas_refund_counter": 0, @@ -5853,20 +3739,9 @@ { "pc": 1665, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606302, "gas_refund_counter": 0, @@ -5880,21 +3755,9 @@ { "pc": 1668, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606299, "gas_refund_counter": 0, @@ -5908,22 +3771,9 @@ { "pc": 1669, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606297, "gas_refund_counter": 0, @@ -5937,23 +3787,9 @@ { "pc": 1670, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0x0", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606294, "gas_refund_counter": 0, @@ -5967,22 +3803,9 @@ { "pc": 1671, "op": 132, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606291, "gas_refund_counter": 0, @@ -5996,23 +3819,9 @@ { "pc": 1672, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606288, "gas_refund_counter": 0, @@ -6026,24 +3835,9 @@ { "pc": 1675, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x66a" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606285, "gas_refund_counter": 0, @@ -6057,23 +3851,9 @@ { "pc": 1642, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606277, "gas_refund_counter": 0, @@ -6087,23 +3867,9 @@ { "pc": 1643, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606276, "gas_refund_counter": 0, @@ -6117,24 +3883,9 @@ { "pc": 1646, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606273, "gas_refund_counter": 0, @@ -6148,25 +3899,9 @@ { "pc": 1647, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606270, "gas_refund_counter": 0, @@ -6180,26 +3915,9 @@ { "pc": 1650, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64", - "0x60f" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606267, "gas_refund_counter": 0, @@ -6213,25 +3931,9 @@ { "pc": 1551, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606259, "gas_refund_counter": 0, @@ -6245,25 +3947,9 @@ { "pc": 1552, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606258, "gas_refund_counter": 0, @@ -6277,26 +3963,9 @@ { "pc": 1553, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606256, "gas_refund_counter": 0, @@ -6310,27 +3979,9 @@ { "pc": 1554, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606253, "gas_refund_counter": 0, @@ -6344,27 +3995,9 @@ { "pc": 1555, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606250, "gas_refund_counter": 0, @@ -6378,26 +4011,9 @@ { "pc": 1556, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x673", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606248, "gas_refund_counter": 0, @@ -6411,26 +4027,9 @@ { "pc": 1557, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64", - "0x64", - "0x673" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606245, "gas_refund_counter": 0, @@ -6444,26 +4043,9 @@ { "pc": 1558, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64", - "0x673", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606242, "gas_refund_counter": 0, @@ -6477,25 +4059,9 @@ { "pc": 1559, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64", - "0x673" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606240, "gas_refund_counter": 0, @@ -6509,24 +4075,9 @@ { "pc": 1651, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606232, "gas_refund_counter": 0, @@ -6540,24 +4091,9 @@ { "pc": 1652, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606231, "gas_refund_counter": 0, @@ -6571,25 +4107,9 @@ { "pc": 1653, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64", - "0x64", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d14740000000000000000000000000000000000000000000000000000000000000064600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606228, "gas_refund_counter": 0, @@ -6603,23 +4123,9 @@ { "pc": 1654, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606225, "gas_refund_counter": 0, @@ -6633,22 +4139,9 @@ { "pc": 1655, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606223, "gas_refund_counter": 0, @@ -6662,21 +4155,9 @@ { "pc": 1656, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4", - "0x68c" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606221, "gas_refund_counter": 0, @@ -6690,20 +4171,9 @@ { "pc": 1676, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606213, "gas_refund_counter": 0, @@ -6717,20 +4187,9 @@ { "pc": 1677, "op": 146, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x342", - "0x64", - "0xa4", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606212, "gas_refund_counter": 0, @@ -6744,20 +4203,9 @@ { "pc": 1678, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x64", - "0xa4", - "0x342" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606209, "gas_refund_counter": 0, @@ -6771,20 +4219,9 @@ { "pc": 1679, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x342", - "0xa4", - "0x64" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606206, "gas_refund_counter": 0, @@ -6798,19 +4235,9 @@ { "pc": 1680, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x342", - "0xa4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606204, "gas_refund_counter": 0, @@ -6824,18 +4251,9 @@ { "pc": 1681, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x342" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606202, "gas_refund_counter": 0, @@ -6849,17 +4267,9 @@ { "pc": 834, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606194, "gas_refund_counter": 0, @@ -6873,17 +4283,9 @@ { "pc": 835, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606193, "gas_refund_counter": 0, @@ -6897,18 +4299,9 @@ { "pc": 837, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x40" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606190, "gas_refund_counter": 0, @@ -6922,18 +4315,9 @@ { "pc": 838, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606187, "gas_refund_counter": 0, @@ -6947,19 +4331,9 @@ { "pc": 840, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x20" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606184, "gas_refund_counter": 0, @@ -6973,20 +4347,9 @@ { "pc": 841, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x20", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606181, "gas_refund_counter": 0, @@ -7000,21 +4363,9 @@ { "pc": 842, "op": 3, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x20", - "0x80", - "0xc4" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606178, "gas_refund_counter": 0, @@ -7028,20 +4379,9 @@ { "pc": 843, "op": 3, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x20", - "0x44" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606175, "gas_refund_counter": 0, @@ -7055,19 +4395,9 @@ { "pc": 844, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x24" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606172, "gas_refund_counter": 0, @@ -7081,20 +4411,9 @@ { "pc": 845, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80", - "0x24", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000e26d147400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606169, "gas_refund_counter": 0, @@ -7108,18 +4427,9 @@ { "pc": 846, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0xc4", - "0x80" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606166, "gas_refund_counter": 0, @@ -7133,18 +4443,9 @@ { "pc": 847, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xc4" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606163, "gas_refund_counter": 0, @@ -7158,19 +4459,9 @@ { "pc": 849, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xc4", - "0x40" - ], - "push_stack": null, - "memory": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606160, "gas_refund_counter": 0, @@ -7184,17 +4475,9 @@ { "pc": 850, "op": 127, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606157, "gas_refund_counter": 0, @@ -7208,18 +4491,9 @@ { "pc": 883, "op": 123, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606154, "gas_refund_counter": 0, @@ -7233,19 +4507,9 @@ { "pc": 912, "op": 25, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606151, "gas_refund_counter": 0, @@ -7259,19 +4523,9 @@ { "pc": 913, "op": 22, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xffffffff00000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606148, "gas_refund_counter": 0, @@ -7285,18 +4539,9 @@ { "pc": 914, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606145, "gas_refund_counter": 0, @@ -7310,19 +4555,9 @@ { "pc": 916, "op": 130, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0x20" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606142, "gas_refund_counter": 0, @@ -7336,20 +4571,9 @@ { "pc": 917, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0x20", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606139, "gas_refund_counter": 0, @@ -7363,19 +4587,9 @@ { "pc": 918, "op": 128, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606136, "gas_refund_counter": 0, @@ -7389,20 +4603,9 @@ { "pc": 919, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0xa0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606133, "gas_refund_counter": 0, @@ -7416,20 +4619,9 @@ { "pc": 920, "op": 123, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606130, "gas_refund_counter": 0, @@ -7443,21 +4635,9 @@ { "pc": 949, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606127, "gas_refund_counter": 0, @@ -7471,22 +4651,9 @@ { "pc": 950, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606124, "gas_refund_counter": 0, @@ -7500,23 +4667,9 @@ { "pc": 951, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606121, "gas_refund_counter": 0, @@ -7530,24 +4683,9 @@ { "pc": 952, "op": 22, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0x6400000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606118, "gas_refund_counter": 0, @@ -7561,23 +4699,9 @@ { "pc": 953, "op": 23, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606115, "gas_refund_counter": 0, @@ -7591,22 +4715,9 @@ { "pc": 954, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606112, "gas_refund_counter": 0, @@ -7620,23 +4731,9 @@ { "pc": 955, "op": 82, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000640000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606109, "gas_refund_counter": 0, @@ -7650,21 +4747,9 @@ { "pc": 956, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606106, "gas_refund_counter": 0, @@ -7678,20 +4763,9 @@ { "pc": 957, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0", - "0x6400000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606104, "gas_refund_counter": 0, @@ -7705,19 +4779,9 @@ { "pc": 958, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000", - "0xa0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606102, "gas_refund_counter": 0, @@ -7731,18 +4795,9 @@ { "pc": 959, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0xf82c50f100000000000000000000000000000000000000000000000000000000" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606100, "gas_refund_counter": 0, @@ -7756,17 +4811,9 @@ { "pc": 960, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606098, "gas_refund_counter": 0, @@ -7780,18 +4827,9 @@ { "pc": 963, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3c7" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606095, "gas_refund_counter": 0, @@ -7805,17 +4843,9 @@ { "pc": 967, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606087, "gas_refund_counter": 0, @@ -7829,17 +4859,9 @@ { "pc": 968, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606086, "gas_refund_counter": 0, @@ -7853,18 +4875,9 @@ { "pc": 971, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606083, "gas_refund_counter": 0, @@ -7878,19 +4891,9 @@ { "pc": 972, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606080, "gas_refund_counter": 0, @@ -7904,20 +4907,9 @@ { "pc": 975, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606077, "gas_refund_counter": 0, @@ -7931,21 +4923,9 @@ { "pc": 978, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606074, "gas_refund_counter": 0, @@ -7959,22 +4939,9 @@ { "pc": 981, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1", - "0x400" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606071, "gas_refund_counter": 0, @@ -7988,21 +4955,9 @@ { "pc": 1024, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606063, "gas_refund_counter": 0, @@ -8016,21 +4971,9 @@ { "pc": 1025, "op": 97, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606062, "gas_refund_counter": 0, @@ -8044,22 +4987,9 @@ { "pc": 1028, "op": 129, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1", - "0x418" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606059, "gas_refund_counter": 0, @@ -8073,23 +5003,9 @@ { "pc": 1029, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1", - "0x418", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606056, "gas_refund_counter": 0, @@ -8103,23 +5019,9 @@ { "pc": 1030, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1", - "0x3e1", - "0x418" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606053, "gas_refund_counter": 0, @@ -8133,22 +5035,9 @@ { "pc": 1031, "op": 145, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3d6", - "0x3e1", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606051, "gas_refund_counter": 0, @@ -8162,22 +5051,9 @@ { "pc": 1032, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1", - "0x3e1", - "0x3d6" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606048, "gas_refund_counter": 0, @@ -8191,22 +5067,9 @@ { "pc": 1033, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1", - "0x3d6", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606045, "gas_refund_counter": 0, @@ -8220,21 +5083,9 @@ { "pc": 1034, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1", - "0x3d6" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606043, "gas_refund_counter": 0, @@ -8248,20 +5099,9 @@ { "pc": 982, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606035, "gas_refund_counter": 0, @@ -8275,20 +5115,9 @@ { "pc": 983, "op": 99, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606034, "gas_refund_counter": 0, @@ -8302,21 +5131,9 @@ { "pc": 988, "op": 22, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1", - "0xffffffff" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606031, "gas_refund_counter": 0, @@ -8330,20 +5147,9 @@ { "pc": 989, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x3e1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606028, "gas_refund_counter": 0, @@ -8357,19 +5163,9 @@ { "pc": 993, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606020, "gas_refund_counter": 0, @@ -8383,19 +5179,9 @@ { "pc": 994, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606019, "gas_refund_counter": 0, @@ -8409,20 +5195,9 @@ { "pc": 995, "op": 106, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606017, "gas_refund_counter": 0, @@ -8436,21 +5211,9 @@ { "pc": 1007, "op": 144, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x0", - "0x636f6e736f6c652e6c6f67" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606014, "gas_refund_counter": 0, @@ -8464,21 +5227,9 @@ { "pc": 1008, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606011, "gas_refund_counter": 0, @@ -8492,20 +5243,9 @@ { "pc": 1009, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606009, "gas_refund_counter": 0, @@ -8519,21 +5259,9 @@ { "pc": 1010, "op": 95, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606007, "gas_refund_counter": 0, @@ -8547,22 +5275,9 @@ { "pc": 1011, "op": 131, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606005, "gas_refund_counter": 0, @@ -8576,23 +5291,9 @@ { "pc": 1012, "op": 81, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073606002, "gas_refund_counter": 0, @@ -8606,23 +5307,9 @@ { "pc": 1013, "op": 96, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605999, "gas_refund_counter": 0, @@ -8636,24 +5323,9 @@ { "pc": 1015, "op": 133, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24", - "0x20" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605996, "gas_refund_counter": 0, @@ -8667,25 +5339,9 @@ { "pc": 1016, "op": 1, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24", - "0x20", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605993, "gas_refund_counter": 0, @@ -8699,24 +5355,9 @@ { "pc": 1017, "op": 132, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24", - "0xa0" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605990, "gas_refund_counter": 0, @@ -8730,25 +5371,9 @@ { "pc": 1018, "op": 90, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24", - "0xa0", - "0x636f6e736f6c652e6c6f67" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605987, "gas_refund_counter": 0, @@ -8762,26 +5387,9 @@ { "pc": 1019, "op": 250, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x0", - "0x0", - "0x24", - "0xa0", - "0x636f6e736f6c652e6c6f67", - "0x3ffded61" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073605985, "gas_refund_counter": 0, @@ -8795,21 +5403,9 @@ { "pc": 1020, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67", - "0x1" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603385, "gas_refund_counter": 0, @@ -8823,20 +5419,9 @@ { "pc": 1021, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80", - "0x636f6e736f6c652e6c6f67" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603383, "gas_refund_counter": 0, @@ -8850,19 +5435,9 @@ { "pc": 1022, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603381, "gas_refund_counter": 0, @@ -8876,18 +5451,9 @@ { "pc": 1023, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80", - "0x3de" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603379, "gas_refund_counter": 0, @@ -8901,17 +5467,9 @@ { "pc": 990, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603371, "gas_refund_counter": 0, @@ -8925,17 +5483,9 @@ { "pc": 991, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4", - "0x80" - ], - "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1073603370, "gas_refund_counter": 0, @@ -8949,16 +5499,9 @@ { "pc": 992, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64", - "0x3c4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603368, "gas_refund_counter": 0, @@ -8972,15 +5515,9 @@ { "pc": 964, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603360, "gas_refund_counter": 0, @@ -8994,15 +5531,9 @@ { "pc": 965, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4", - "0x64" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603359, "gas_refund_counter": 0, @@ -9016,14 +5547,9 @@ { "pc": 966, "op": 86, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "0x2f4" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603357, "gas_refund_counter": 0, @@ -9037,13 +5563,9 @@ { "pc": 756, "op": 91, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603349, "gas_refund_counter": 0, @@ -9057,13 +5579,9 @@ { "pc": 757, "op": 80, - "stack": [ - "0xf8a8fd6d", - "0x92", - "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f" - ], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603348, "gas_refund_counter": 0, @@ -9077,9 +5595,9 @@ { "pc": 758, "op": 86, - "stack": ["0xf8a8fd6d", "0x92"], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603346, "gas_refund_counter": 0, @@ -9093,9 +5611,9 @@ { "pc": 146, "op": 91, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603338, "gas_refund_counter": 0, @@ -9109,9 +5627,9 @@ { "pc": 147, "op": 0, - "stack": ["0xf8a8fd6d"], + "stack": null, "push_stack": null, - "memory": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f82c50f10000000000000000000000000000000000000000000000000000000000000064e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c63430008210033000000000000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1073603337, "gas_refund_counter": 0, @@ -10204,10 +6722,9 @@ { "pc": 0, "op": 96, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10221,10 +6738,9 @@ { "pc": 2, "op": 96, - - "stack": ["0x80"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": 1056912053, "gas_refund_counter": 0, @@ -10238,10 +6754,9 @@ { "pc": 4, "op": 82, - - "stack": ["0x80", "0x40"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": 1056912050, "gas_refund_counter": 0, @@ -10255,10 +6770,9 @@ { "pc": 5, "op": 52, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912038, "gas_refund_counter": 0, @@ -10272,10 +6786,9 @@ { "pc": 6, "op": 128, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912036, "gas_refund_counter": 0, @@ -10289,10 +6802,9 @@ { "pc": 7, "op": 21, - - "stack": ["0x0", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912033, "gas_refund_counter": 0, @@ -10306,10 +6818,9 @@ { "pc": 8, "op": 96, - - "stack": ["0x0", "0x1"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912030, "gas_refund_counter": 0, @@ -10323,10 +6834,9 @@ { "pc": 10, "op": 87, - - "stack": ["0x0", "0x1", "0xe"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912027, "gas_refund_counter": 0, @@ -10340,10 +6850,9 @@ { "pc": 14, "op": 91, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912017, "gas_refund_counter": 0, @@ -10357,10 +6866,9 @@ { "pc": 15, "op": 80, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912016, "gas_refund_counter": 0, @@ -10374,10 +6882,9 @@ { "pc": 16, "op": 97, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912014, "gas_refund_counter": 0, @@ -10391,10 +6898,9 @@ { "pc": 19, "op": 128, - - "stack": ["0x126"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912011, "gas_refund_counter": 0, @@ -10408,10 +6914,9 @@ { "pc": 20, "op": 97, - - "stack": ["0x126", "0x126"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912008, "gas_refund_counter": 0, @@ -10425,10 +6930,9 @@ { "pc": 23, "op": 95, - - "stack": ["0x126", "0x126", "0x1c"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912005, "gas_refund_counter": 0, @@ -10442,10 +6946,9 @@ { "pc": 24, "op": 57, - - "stack": ["0x126", "0x126", "0x1c", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056912003, "gas_refund_counter": 0, @@ -10459,10 +6962,9 @@ { "pc": 25, "op": 95, - - "stack": ["0x126"], + "stack": null, "push_stack": null, - "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c634300082100330000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1056911949, "gas_refund_counter": 0, @@ -10476,10 +6978,9 @@ { "pc": 26, "op": 243, - - "stack": ["0x126", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c80634e70b1dc146034578063e26d147414604e575b5f5ffd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f5ffd5b60ac816074565b811460b5575f5ffd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea2646970667358221220ea1195a62b411681af2ab1c55fe6e4f679055b53f56d5984b82acb0a50450db664736f6c634300082100330000000000000000000000000000000000000000000000000000", + "memory": null, "returndata": "0x", "gas_remaining": 1056911947, "gas_refund_counter": 0, @@ -10573,10 +7074,9 @@ { "pc": 0, "op": 96, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10590,10 +7090,9 @@ { "pc": 2, "op": 96, - - "stack": ["0x80"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10607,10 +7106,9 @@ { "pc": 4, "op": 82, - - "stack": ["0x80", "0x40"], + "stack": null, "push_stack": null, - "memory": "0x", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10624,10 +7122,9 @@ { "pc": 5, "op": 52, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10641,10 +7138,9 @@ { "pc": 6, "op": 128, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10658,10 +7154,9 @@ { "pc": 7, "op": 21, - - "stack": ["0x0", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10675,10 +7170,9 @@ { "pc": 8, "op": 96, - - "stack": ["0x0", "0x1"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10692,10 +7186,9 @@ { "pc": 10, "op": 87, - - "stack": ["0x0", "0x1", "0xe"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10709,10 +7202,9 @@ { "pc": 14, "op": 91, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10726,10 +7218,9 @@ { "pc": 15, "op": 80, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10743,10 +7234,9 @@ { "pc": 16, "op": 96, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10760,10 +7250,9 @@ { "pc": 18, "op": 54, - - "stack": ["0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10777,10 +7266,9 @@ { "pc": 19, "op": 16, - - "stack": ["0x4", "0x24"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10794,10 +7282,9 @@ { "pc": 20, "op": 96, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10811,10 +7298,9 @@ { "pc": 22, "op": 87, - - "stack": ["0x0", "0x30"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10828,10 +7314,9 @@ { "pc": 23, "op": 95, - - "stack": [], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10845,10 +7330,9 @@ { "pc": 24, "op": 53, - - "stack": ["0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10862,12 +7346,9 @@ { "pc": 25, "op": 96, - - "stack": [ - "0xe26d147400000000000000000000000000000000000000000000000000000000" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10881,13 +7362,9 @@ { "pc": 27, "op": 28, - - "stack": [ - "0xe26d147400000000000000000000000000000000000000000000000000000000", - "0xe0" - ], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10901,10 +7378,9 @@ { "pc": 28, "op": 128, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10918,10 +7394,9 @@ { "pc": 29, "op": 99, - - "stack": ["0xe26d1474", "0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10935,10 +7410,9 @@ { "pc": 34, "op": 20, - - "stack": ["0xe26d1474", "0xe26d1474", "0x4e70b1dc"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10952,10 +7426,9 @@ { "pc": 35, "op": 96, - - "stack": ["0xe26d1474", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10969,10 +7442,9 @@ { "pc": 37, "op": 87, - - "stack": ["0xe26d1474", "0x0", "0x34"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": "{...}", "gas_refund_counter": 0, @@ -10986,10 +7458,9 @@ { "pc": 38, "op": 128, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853373, "gas_refund_counter": 0, @@ -11003,10 +7474,9 @@ { "pc": 39, "op": 99, - - "stack": ["0xe26d1474", "0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853370, "gas_refund_counter": 0, @@ -11020,10 +7490,9 @@ { "pc": 44, "op": 20, - - "stack": ["0xe26d1474", "0xe26d1474", "0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853367, "gas_refund_counter": 0, @@ -11037,10 +7506,9 @@ { "pc": 45, "op": 96, - - "stack": ["0xe26d1474", "0x1"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853364, "gas_refund_counter": 0, @@ -11054,10 +7522,9 @@ { "pc": 47, "op": 87, - - "stack": ["0xe26d1474", "0x1", "0x4e"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853361, "gas_refund_counter": 0, @@ -11071,10 +7538,9 @@ { "pc": 78, "op": 91, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853351, "gas_refund_counter": 0, @@ -11088,10 +7554,9 @@ { "pc": 79, "op": 96, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853350, "gas_refund_counter": 0, @@ -11105,10 +7570,9 @@ { "pc": 81, "op": 96, - - "stack": ["0xe26d1474", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853347, "gas_refund_counter": 0, @@ -11122,10 +7586,9 @@ { "pc": 83, "op": 128, - - "stack": ["0xe26d1474", "0x64", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853344, "gas_refund_counter": 0, @@ -11139,10 +7602,9 @@ { "pc": 84, "op": 54, - - "stack": ["0xe26d1474", "0x64", "0x4", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853341, "gas_refund_counter": 0, @@ -11156,10 +7618,9 @@ { "pc": 85, "op": 3, - - "stack": ["0xe26d1474", "0x64", "0x4", "0x4", "0x24"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853339, "gas_refund_counter": 0, @@ -11173,10 +7634,9 @@ { "pc": 86, "op": 129, - - "stack": ["0xe26d1474", "0x64", "0x4", "0x20"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853336, "gas_refund_counter": 0, @@ -11190,10 +7650,9 @@ { "pc": 87, "op": 1, - - "stack": ["0xe26d1474", "0x64", "0x4", "0x20", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853333, "gas_refund_counter": 0, @@ -11207,10 +7666,9 @@ { "pc": 88, "op": 144, - - "stack": ["0xe26d1474", "0x64", "0x4", "0x24"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853330, "gas_refund_counter": 0, @@ -11224,10 +7682,9 @@ { "pc": 89, "op": 96, - - "stack": ["0xe26d1474", "0x64", "0x24", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853327, "gas_refund_counter": 0, @@ -11241,10 +7698,9 @@ { "pc": 91, "op": 145, - - "stack": ["0xe26d1474", "0x64", "0x24", "0x4", "0x60"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853324, "gas_refund_counter": 0, @@ -11258,10 +7714,9 @@ { "pc": 92, "op": 144, - - "stack": ["0xe26d1474", "0x64", "0x60", "0x4", "0x24"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853321, "gas_refund_counter": 0, @@ -11275,10 +7730,9 @@ { "pc": 93, "op": 96, - - "stack": ["0xe26d1474", "0x64", "0x60", "0x24", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853318, "gas_refund_counter": 0, @@ -11292,17 +7746,9 @@ { "pc": 95, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0xca" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853315, "gas_refund_counter": 0, @@ -11316,10 +7762,9 @@ { "pc": 202, "op": 91, - - "stack": ["0xe26d1474", "0x64", "0x60", "0x24", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853307, "gas_refund_counter": 0, @@ -11333,10 +7778,9 @@ { "pc": 203, "op": 95, - - "stack": ["0xe26d1474", "0x64", "0x60", "0x24", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853306, "gas_refund_counter": 0, @@ -11350,17 +7794,9 @@ { "pc": 204, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853304, "gas_refund_counter": 0, @@ -11374,18 +7810,9 @@ { "pc": 206, "op": 130, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x20" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853301, "gas_refund_counter": 0, @@ -11399,19 +7826,9 @@ { "pc": 207, "op": 132, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x20", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853298, "gas_refund_counter": 0, @@ -11425,20 +7842,9 @@ { "pc": 208, "op": 3, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x20", - "0x4", - "0x24" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853295, "gas_refund_counter": 0, @@ -11452,19 +7858,9 @@ { "pc": 209, "op": 18, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x20", - "0x20" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853292, "gas_refund_counter": 0, @@ -11478,18 +7874,9 @@ { "pc": 210, "op": 21, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853289, "gas_refund_counter": 0, @@ -11503,18 +7890,9 @@ { "pc": 211, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x1" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853286, "gas_refund_counter": 0, @@ -11528,19 +7906,9 @@ { "pc": 213, "op": 87, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x1", - "0xdc" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853283, "gas_refund_counter": 0, @@ -11554,17 +7922,9 @@ { "pc": 220, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853273, "gas_refund_counter": 0, @@ -11578,17 +7938,9 @@ { "pc": 221, "op": 95, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853272, "gas_refund_counter": 0, @@ -11602,18 +7954,9 @@ { "pc": 222, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853270, "gas_refund_counter": 0, @@ -11627,19 +7970,9 @@ { "pc": 224, "op": 132, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853267, "gas_refund_counter": 0, @@ -11653,20 +7986,9 @@ { "pc": 225, "op": 130, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853264, "gas_refund_counter": 0, @@ -11680,21 +8002,9 @@ { "pc": 226, "op": 133, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853261, "gas_refund_counter": 0, @@ -11708,22 +8018,9 @@ { "pc": 227, "op": 1, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x0", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853258, "gas_refund_counter": 0, @@ -11737,21 +8034,9 @@ { "pc": 228, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853255, "gas_refund_counter": 0, @@ -11765,22 +8050,9 @@ { "pc": 230, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0xb8" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853252, "gas_refund_counter": 0, @@ -11794,21 +8066,9 @@ { "pc": 184, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853244, "gas_refund_counter": 0, @@ -11822,21 +8082,9 @@ { "pc": 185, "op": 95, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853243, "gas_refund_counter": 0, @@ -11850,22 +8098,9 @@ { "pc": 186, "op": 129, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853241, "gas_refund_counter": 0, @@ -11879,23 +8114,9 @@ { "pc": 187, "op": 53, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x0", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853238, "gas_refund_counter": 0, @@ -11909,23 +8130,9 @@ { "pc": 188, "op": 144, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853235, "gas_refund_counter": 0, @@ -11939,23 +8146,9 @@ { "pc": 189, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853232, "gas_refund_counter": 0, @@ -11969,22 +8162,9 @@ { "pc": 190, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853230, "gas_refund_counter": 0, @@ -11998,23 +8178,9 @@ { "pc": 192, "op": 129, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853227, "gas_refund_counter": 0, @@ -12028,24 +8194,9 @@ { "pc": 193, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853224, "gas_refund_counter": 0, @@ -12059,25 +8210,9 @@ { "pc": 195, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xa5" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853221, "gas_refund_counter": 0, @@ -12091,24 +8226,9 @@ { "pc": 165, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853213, "gas_refund_counter": 0, @@ -12122,24 +8242,9 @@ { "pc": 166, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853212, "gas_refund_counter": 0, @@ -12153,25 +8258,9 @@ { "pc": 168, "op": 129, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853209, "gas_refund_counter": 0, @@ -12185,26 +8274,9 @@ { "pc": 169, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853206, "gas_refund_counter": 0, @@ -12218,27 +8290,9 @@ { "pc": 171, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64", - "0x74" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853203, "gas_refund_counter": 0, @@ -12252,26 +8306,9 @@ { "pc": 116, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853195, "gas_refund_counter": 0, @@ -12285,26 +8322,9 @@ { "pc": 117, "op": 95, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853194, "gas_refund_counter": 0, @@ -12318,27 +8338,9 @@ { "pc": 118, "op": 129, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853192, "gas_refund_counter": 0, @@ -12352,28 +8354,9 @@ { "pc": 119, "op": 144, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853189, "gas_refund_counter": 0, @@ -12387,28 +8370,9 @@ { "pc": 120, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853186, "gas_refund_counter": 0, @@ -12422,27 +8386,9 @@ { "pc": 121, "op": 145, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0xac", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853184, "gas_refund_counter": 0, @@ -12456,27 +8402,9 @@ { "pc": 122, "op": 144, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64", - "0x64", - "0xac" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853181, "gas_refund_counter": 0, @@ -12490,27 +8418,9 @@ { "pc": 123, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64", - "0xac", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853178, "gas_refund_counter": 0, @@ -12524,26 +8434,9 @@ { "pc": 124, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64", - "0xac" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853176, "gas_refund_counter": 0, @@ -12557,25 +8450,9 @@ { "pc": 172, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853168, "gas_refund_counter": 0, @@ -12589,25 +8466,9 @@ { "pc": 173, "op": 129, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853167, "gas_refund_counter": 0, @@ -12621,26 +8482,9 @@ { "pc": 174, "op": 20, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x64", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853164, "gas_refund_counter": 0, @@ -12654,25 +8498,9 @@ { "pc": 175, "op": 96, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x1" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853161, "gas_refund_counter": 0, @@ -12686,26 +8514,9 @@ { "pc": 177, "op": 87, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64", - "0x1", - "0xb5" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853158, "gas_refund_counter": 0, @@ -12719,24 +8530,9 @@ { "pc": 181, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853148, "gas_refund_counter": 0, @@ -12750,24 +8546,9 @@ { "pc": 182, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853147, "gas_refund_counter": 0, @@ -12781,23 +8562,9 @@ { "pc": 183, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64", - "0xc4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853145, "gas_refund_counter": 0, @@ -12811,22 +8578,9 @@ { "pc": 196, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853137, "gas_refund_counter": 0, @@ -12840,22 +8594,9 @@ { "pc": 197, "op": 146, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0xe7", - "0x24", - "0x4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853136, "gas_refund_counter": 0, @@ -12869,22 +8610,9 @@ { "pc": 198, "op": 145, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64", - "0x24", - "0x4", - "0xe7" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853133, "gas_refund_counter": 0, @@ -12898,22 +8626,9 @@ { "pc": 199, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64", - "0xe7", - "0x4", - "0x24" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853130, "gas_refund_counter": 0, @@ -12927,21 +8642,9 @@ { "pc": 200, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64", - "0xe7", - "0x4" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853128, "gas_refund_counter": 0, @@ -12955,20 +8658,9 @@ { "pc": 201, "op": 86, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64", - "0xe7" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853126, "gas_refund_counter": 0, @@ -12982,19 +8674,9 @@ { "pc": 231, "op": 91, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853118, "gas_refund_counter": 0, @@ -13008,19 +8690,9 @@ { "pc": 232, "op": 145, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x0", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853117, "gas_refund_counter": 0, @@ -13034,19 +8706,9 @@ { "pc": 233, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x64", - "0x0", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853114, "gas_refund_counter": 0, @@ -13060,18 +8722,9 @@ { "pc": 234, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853112, "gas_refund_counter": 0, @@ -13085,17 +8738,9 @@ { "pc": 235, "op": 146, - - "stack": [ - "0xe26d1474", - "0x64", - "0x60", - "0x24", - "0x4", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853110, "gas_refund_counter": 0, @@ -13109,17 +8754,9 @@ { "pc": 236, "op": 145, - - "stack": [ - "0xe26d1474", - "0x64", - "0x64", - "0x24", - "0x4", - "0x60" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853107, "gas_refund_counter": 0, @@ -13133,17 +8770,9 @@ { "pc": 237, "op": 80, - - "stack": [ - "0xe26d1474", - "0x64", - "0x64", - "0x60", - "0x4", - "0x24" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853104, "gas_refund_counter": 0, @@ -13157,10 +8786,9 @@ { "pc": 238, "op": 80, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x60", "0x4"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853102, "gas_refund_counter": 0, @@ -13174,10 +8802,9 @@ { "pc": 239, "op": 86, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x60"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853100, "gas_refund_counter": 0, @@ -13191,10 +8818,9 @@ { "pc": 96, "op": 91, - - "stack": ["0xe26d1474", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853092, "gas_refund_counter": 0, @@ -13208,10 +8834,9 @@ { "pc": 97, "op": 96, - - "stack": ["0xe26d1474", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853091, "gas_refund_counter": 0, @@ -13225,10 +8850,9 @@ { "pc": 99, "op": 86, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x6b"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853088, "gas_refund_counter": 0, @@ -13242,10 +8866,9 @@ { "pc": 107, "op": 91, - - "stack": ["0xe26d1474", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853080, "gas_refund_counter": 0, @@ -13259,10 +8882,9 @@ { "pc": 108, "op": 128, - - "stack": ["0xe26d1474", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853079, "gas_refund_counter": 0, @@ -13276,10 +8898,9 @@ { "pc": 109, "op": 95, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853076, "gas_refund_counter": 0, @@ -13293,10 +8914,9 @@ { "pc": 110, "op": 129, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x64", "0x0"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056853074, "gas_refund_counter": 0, @@ -13310,17 +8930,9 @@ { "pc": 111, "op": 144, - - "stack": [ - "0xe26d1474", - "0x64", - "0x64", - "0x64", - "0x0", - "0x64" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853071, "gas_refund_counter": 0, @@ -13334,17 +8946,9 @@ { "pc": 112, "op": 85, - - "stack": [ - "0xe26d1474", - "0x64", - "0x64", - "0x64", - "0x64", - "0x0" - ], - "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "stack": null, + "push_stack": null, + "memory": null, "returndata": "0x", "gas_remaining": 1056853068, "gas_refund_counter": 0, @@ -13363,10 +8967,9 @@ { "pc": 113, "op": 80, - - "stack": ["0xe26d1474", "0x64", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056830968, "gas_refund_counter": 0, @@ -13380,10 +8983,9 @@ { "pc": 114, "op": 80, - - "stack": ["0xe26d1474", "0x64", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056830966, "gas_refund_counter": 0, @@ -13397,10 +8999,9 @@ { "pc": 115, "op": 86, - - "stack": ["0xe26d1474", "0x64"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056830964, "gas_refund_counter": 0, @@ -13414,10 +9015,9 @@ { "pc": 100, "op": 91, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056830956, "gas_refund_counter": 0, @@ -13431,10 +9031,9 @@ { "pc": 101, "op": 0, - - "stack": ["0xe26d1474"], + "stack": null, "push_stack": null, - "memory": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "memory": null, "returndata": "0x", "gas_remaining": 1056830955, "gas_refund_counter": 0, diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 2372756c2efc7..e8a8e6319bd09 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -47,10 +47,7 @@ fn impl_struct(s: &DataStruct) -> Option { .into_iter() .map(|member| match member { Member::Named(ident) => quote!(&self.#ident), - // For Tuple structs generated by the sol!. - // These are generated only in case of a single unnamed field, hence it is safe to - // hardcode the index to `.0`. - Member::Unnamed(_) => quote!(&self.0), + Member::Unnamed(idx) => quote!(&self.#idx), }) .collect(); diff --git a/crates/primitives/src/network/transaction.rs b/crates/primitives/src/network/transaction.rs index 4b9c7517718fd..683352466f83e 100644 --- a/crates/primitives/src/network/transaction.rs +++ b/crates/primitives/src/network/transaction.rs @@ -5,6 +5,7 @@ use alloy_network::{AnyNetwork, Ethereum, Network, TransactionBuilder}; use alloy_primitives::{Address, B256, Signature, U256}; use alloy_rpc_types::SignedAuthorization; use tempo_alloy::TempoNetwork; +use tempo_primitives::transaction::SignedKeyAuthorization; /// Composite transaction builder trait for Foundry transactions. /// @@ -226,6 +227,12 @@ pub trait FoundryTransactionBuilder: TransactionBuilder { fn compute_sponsor_hash(&self, _from: Address) -> Option { None } + + /// Set the key authorization for a Tempo transaction. + /// + /// Embeds a [`SignedKeyAuthorization`] in the transaction body, provisioning the access key + /// on-chain as part of this transaction. + fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} } impl FoundryTransactionBuilder for ::TransactionRequest { @@ -371,4 +378,8 @@ impl FoundryTransactionBuilder for ::Tran let tx = self.clone().build_aa().ok()?; Some(tx.fee_payer_signature_hash(from)) } + + fn set_key_authorization(&mut self, key_authorization: SignedKeyAuthorization) { + self.key_authorization = Some(key_authorization); + } } diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index f99865a8d4da5..240ad65ec3a31 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -7,12 +7,12 @@ use alloy_consensus::{ eip4844::{TxEip4844Variant, TxEip4844WithSidecar}, }, }; -use alloy_evm::FromRecoveredTx; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_primitives::{Address, B256, TxHash}; +use alloy_primitives::{Address, B256, Bytes, TxHash}; use alloy_rpc_types::ConversionError; use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait, TxDeposit}; -use op_revm::OpTransaction; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; use revm::context::TxEnv; use tempo_primitives::{AASigned, TempoTransaction}; @@ -210,7 +210,7 @@ impl FromRecoveredTx for TxEnv { FoundryTxEnvelope::Deposit(sealed_tx) => { Self::from_recovered_tx(sealed_tx.inner(), caller) } - FoundryTxEnvelope::Tempo(_) => panic!("unsupported tx type on ethereum"), + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Ethereum context"), } } } @@ -226,7 +226,51 @@ impl FromRecoveredTx for OpTransaction { FoundryTxEnvelope::Deposit(sealed_tx) => { Self::from_recovered_tx(sealed_tx.inner(), caller) } - FoundryTxEnvelope::Tempo(_) => panic!("unsupported tx type on optimism"), + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl FromTxWithEncoded for TxEnv { + fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { + Self::from_recovered_tx(tx, sender) + } +} + +impl FromTxWithEncoded for OpTransaction { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv::from_recovered_tx(deposit_tx, caller); + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: Some(encoded), deposit } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), } } } @@ -275,7 +319,7 @@ impl From for FoundryTypedTx { mod tests { use std::str::FromStr; - use alloy_primitives::{Bytes, TxKind, U256, b256, hex}; + use alloy_primitives::{TxKind, U256, b256, hex}; use alloy_rlp::Decodable; use alloy_signer::Signature; diff --git a/crates/primitives/src/transaction/request.rs b/crates/primitives/src/transaction/request.rs index b80fd10d8b096..c0451a6d377de 100644 --- a/crates/primitives/src/transaction/request.rs +++ b/crates/primitives/src/transaction/request.rs @@ -207,7 +207,7 @@ impl AsRef for FoundryTransactionRequest { match self { Self::Ethereum(tx) => tx, Self::Op(tx) => tx, - Self::Tempo(tx) => tx, + Self::Tempo(tx) => tx.as_ref(), } } } @@ -217,7 +217,7 @@ impl AsMut for FoundryTransactionRequest { match self { Self::Ethereum(tx) => tx, Self::Op(tx) => tx, - Self::Tempo(tx) => tx, + Self::Tempo(tx) => tx.as_mut(), } } } diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index d6cd35bec0836..7f112ce1bbda8 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -27,4 +27,3 @@ revm-inspectors.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true -alloy-rpc-types-eth.workspace = true diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs index 8350b842dbb66..839ab77664202 100644 --- a/crates/script-sequence/src/reader.rs +++ b/crates/script-sequence/src/reader.rs @@ -1,9 +1,9 @@ use crate::{ScriptSequence, TransactionWithMetadata}; -use alloy_network::ReceiptResponse; -use alloy_rpc_types_eth::TransactionReceipt; +use alloy_network::{Network, ReceiptResponse}; use eyre::{Result, bail}; use foundry_common::fs; use revm_inspectors::tracing::types::CallKind; +use serde::Deserialize; use std::path::{Component, Path, PathBuf}; /// This type reads broadcast files in the @@ -44,9 +44,9 @@ impl BroadcastReader { self } - fn matches_filters(&self, tx: &TransactionWithMetadata) -> bool { + fn matches_filters(&self, tx: &TransactionWithMetadata) -> bool { let name_filter = tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); - let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); + let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.call_kind); name_filter && type_filter } @@ -56,7 +56,10 @@ impl BroadcastReader { /// /// project-root/broadcast/{script_name}.s.sol/{chain_id}/*.json /// project-root/broadcast/multi/{multichain_script_name}.s.sol-{timestamp}/deploy.json - pub fn read(&self) -> eyre::Result> { + pub fn read(&self) -> eyre::Result>> + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { // 1. Recursively read all .json files in the broadcast directory let mut broadcasts = vec![]; for entry in walkdir::WalkDir::new(&self.broadcast_path).into_iter() { @@ -77,7 +80,8 @@ impl BroadcastReader { let multichain_deployments = broadcast .get("deployments") .and_then(|deployments| { - serde_json::from_value::>(deployments.clone()).ok() + serde_json::from_value::>>(deployments.clone()) + .ok() }) .unwrap_or_default(); @@ -85,7 +89,7 @@ impl BroadcastReader { continue; } - let broadcast = fs::read_json_file::(path)?; + let broadcast = fs::read_json_file::>(path)?; broadcasts.push(broadcast); } } @@ -98,7 +102,10 @@ impl BroadcastReader { /// Attempts read the latest broadcast file in the broadcast directory. /// /// This may be the `run-latest.json` file or the broadcast file with the latest timestamp. - pub fn read_latest(&self) -> eyre::Result { + pub fn read_latest(&self) -> eyre::Result> + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let broadcasts = self.read()?; // Find the broadcast with the latest timestamp @@ -111,7 +118,10 @@ impl BroadcastReader { } /// Applies the filters and sorts the broadcasts by descending timestamp. - pub fn filter_and_sort(&self, broadcasts: Vec) -> Vec { + pub fn filter_and_sort( + &self, + broadcasts: Vec>, + ) -> Vec> { // Apply the filters let mut seqs = broadcasts .into_iter() @@ -139,22 +149,22 @@ impl BroadcastReader { /// Transactions that don't have a corresponding receipt are ignored. /// /// Sorts the transactions by descending block number. - pub fn into_tx_receipts( + pub fn into_tx_receipts( &self, - broadcast: ScriptSequence, - ) -> Vec<(TransactionWithMetadata, TransactionReceipt)> { + broadcast: ScriptSequence, + ) -> Vec<(TransactionWithMetadata, N::ReceiptResponse)> { let ScriptSequence { transactions, receipts, .. } = broadcast; - let mut targets = Vec::new(); - for tx in transactions.into_iter().filter(|tx| self.matches_filters(tx)) { - let maybe_receipt = receipts - .iter() - .find(|receipt| tx.hash.is_some_and(|hash| hash == receipt.transaction_hash())); - - if let Some(receipt) = maybe_receipt { - targets.push((tx, receipt.clone())); - } - } + let mut targets: Vec<_> = transactions + .into_iter() + .filter(|tx| self.matches_filters(tx)) + .filter_map(|tx| { + let receipt = receipts + .iter() + .find(|r| tx.hash.is_some_and(|hash| hash == r.transaction_hash()))?; + Some((tx, receipt.clone())) + }) + .collect(); // Sort by descending block number targets.sort_by_key(|t| std::cmp::Reverse(t.1.block_number())); diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 18805f49a2d20..85a7a955f2c5f 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -1,7 +1,6 @@ use crate::transaction::TransactionWithMetadata; -use alloy_network::ReceiptResponse; +use alloy_network::{Network, ReceiptResponse}; use alloy_primitives::{TxHash, hex, map::HashMap}; -use alloy_rpc_types_eth::TransactionReceipt; use eyre::{ContextCompat, Result, WrapErr}; use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, fs, shell}; use foundry_compilers::ArtifactId; @@ -21,12 +20,28 @@ pub struct NestedValue { pub value: String, } +/// Sensitive values from the transactions in a script sequence +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveTransactionMetadata { + pub rpc: String, +} + +/// Sensitive info from the script sequence which is saved into the cache folder +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveScriptSequence { + pub transactions: VecDeque, +} + /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct ScriptSequence { - pub transactions: VecDeque, - pub receipts: Vec, +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" +))] +pub struct ScriptSequence { + pub transactions: VecDeque>, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] @@ -39,20 +54,24 @@ pub struct ScriptSequence { pub commit: Option, } -/// Sensitive values from the transactions in a script sequence -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveTransactionMetadata { - pub rpc: String, -} - -/// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveScriptSequence { - pub transactions: VecDeque, +impl Default for ScriptSequence { + fn default() -> Self { + Self { + transactions: Default::default(), + receipts: Default::default(), + libraries: Default::default(), + pending: Default::default(), + paths: Default::default(), + returns: Default::default(), + timestamp: Default::default(), + chain: Default::default(), + commit: Default::default(), + } + } } -impl From<&ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &ScriptSequence) -> Self { +impl From<&ScriptSequence> for SensitiveScriptSequence { + fn from(sequence: &ScriptSequence) -> Self { Self { transactions: sequence .transactions @@ -63,7 +82,7 @@ impl From<&ScriptSequence> for SensitiveScriptSequence { } } -impl ScriptSequence { +impl ScriptSequence { /// Loads The sequence for the corresponding json file pub fn load( config: &Config, @@ -71,7 +90,10 @@ impl ScriptSequence { target: &ArtifactId, chain_id: u64, dry_run: bool, - ) -> Result { + ) -> Result + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = fs::read_json_file(&path) @@ -92,7 +114,10 @@ impl ScriptSequence { /// Saves the transactions as file if it's a standalone deployment. /// `save_ts` should be set to true for checkpoint updates, which might happen many times and /// could result in us saving many identical files. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> + where + N::TxEnvelope: Serialize, + { self.sort_receipts(); if self.transactions.is_empty() { @@ -141,7 +166,7 @@ impl ScriptSequence { Ok(()) } - pub fn add_receipt(&mut self, receipt: TransactionReceipt) { + pub fn add_receipt(&mut self, receipt: N::ReceiptResponse) { self.receipts.push(receipt); } @@ -204,7 +229,7 @@ impl ScriptSequence { } /// Returns the list of the transactions without the metadata. - pub fn transactions(&self) -> impl Iterator { + pub fn transactions(&self) -> impl Iterator> { self.transactions.iter().map(|tx| tx.tx()) } diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs index cf3c92d7eb5e2..3e4b46d3fa327 100644 --- a/crates/script-sequence/src/transaction.rs +++ b/crates/script-sequence/src/transaction.rs @@ -1,3 +1,4 @@ +use alloy_network::Network; use alloy_primitives::{Address, B256, Bytes}; use foundry_common::TransactionMaybeSigned; use revm_inspectors::tracing::types::CallKind; @@ -7,18 +8,24 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct AdditionalContract { #[serde(rename = "transactionType")] - pub opcode: CallKind, + pub call_kind: CallKind, pub contract_name: Option, pub address: Address, pub init_code: Bytes, } #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionWithMetadata { +#[serde( + rename_all = "camelCase", + bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" + ) +)] +pub struct TransactionWithMetadata { pub hash: Option, #[serde(rename = "transactionType")] - pub opcode: CallKind, + pub call_kind: CallKind, #[serde(default = "default_string")] pub contract_name: Option, #[serde(default = "default_address")] @@ -29,7 +36,7 @@ pub struct TransactionWithMetadata { pub arguments: Option>, #[serde(skip)] pub rpc: String, - pub transaction: TransactionMaybeSigned, + pub transaction: TransactionMaybeSigned, #[serde(default)] pub additional_contracts: Vec, #[serde(default)] @@ -48,12 +55,12 @@ fn default_vec_of_strings() -> Option> { Some(vec![]) } -impl TransactionWithMetadata { - pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { Self { transaction, hash: Default::default(), - opcode: Default::default(), + call_kind: Default::default(), contract_name: Default::default(), contract_address: Default::default(), function: Default::default(), @@ -64,15 +71,15 @@ impl TransactionWithMetadata { } } - pub fn tx(&self) -> &TransactionMaybeSigned { + pub fn tx(&self) -> &TransactionMaybeSigned { &self.transaction } - pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { + pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { &mut self.transaction } pub fn is_create2(&self) -> bool { - self.opcode == CallKind::Create2 + self.call_kind == CallKind::Create2 } } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 90cfc39e6ed7b..c445045623e9d 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -55,6 +55,7 @@ alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true thiserror.workspace = true +tempo-alloy.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 265e89adbcc48..05b3033212f62 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,16 +1,16 @@ use std::{cmp::Ordering, sync::Arc, time::Duration}; use alloy_chains::{Chain, NamedChain}; -use alloy_consensus::TxEnvelope; +use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::{BlockId, eip2718::Encodable2718}; -use alloy_network::{Ethereum, EthereumWallet, Network, ReceiptResponse, TransactionBuilder}; +use alloy_network::{EthereumWallet, Network, ReceiptResponse, TransactionBuilder}; use alloy_primitives::{ Address, TxHash, map::{AddressHashMap, AddressHashSet}, utils::format_units, }; use alloy_provider::{Provider, RootProvider, utils::Eip1559Estimation}; -use alloy_rpc_types::TransactionRequest; +use alloy_signer::Signature; use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; @@ -22,9 +22,11 @@ use foundry_common::{ }; use foundry_config::Config; use foundry_primitives::FoundryTransactionBuilder; -use foundry_wallets::wallet_browser::signer::BrowserSigner; +use foundry_wallets::{TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner}; use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use tempo_alloy::provider::TempoProviderExt; use crate::{ ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, @@ -65,14 +67,21 @@ pub async fn next_nonce( /// Represents how to send a single transaction. #[derive(Clone)] -pub enum SendTransactionKind<'a> { - Unlocked(TransactionRequest), - Raw(TransactionRequest, &'a EthereumWallet), - Browser(TransactionRequest, &'a BrowserSigner), - Signed(TxEnvelope), +pub enum SendTransactionKind<'a, N: Network> { + Unlocked(N::TransactionRequest), + Raw(N::TransactionRequest, &'a EthereumWallet), + Browser(N::TransactionRequest, &'a BrowserSigner), + Signed(N::TxEnvelope), + AccessKey(N::TransactionRequest, &'a WalletSigner, &'a TempoAccessKeyConfig, String), } -impl<'a> SendTransactionKind<'a> { +impl<'a, N: Network> SendTransactionKind<'a, N> +where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: + FoundryTransactionBuilder + Into, +{ /// Prepares the transaction for broadcasting by synchronizing nonce and estimating gas. /// /// This method performs two key operations: @@ -81,17 +90,21 @@ impl<'a> SendTransactionKind<'a> { /// 2. Gas estimation: Re-estimates gas right before broadcasting for chains that require it pub async fn prepare( &mut self, - provider: &RootProvider, + provider: &RootProvider, sequential_broadcast: bool, is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, ) -> Result<()> { - if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) = self { + if let Self::Raw(tx, _) + | Self::Unlocked(tx) + | Self::Browser(tx, _) + | Self::AccessKey(tx, _, _, _) = self + { if sequential_broadcast { - let from = tx.from.expect("no sender"); + let from = tx.from().expect("no sender"); - let tx_nonce = tx.nonce.expect("no nonce"); + let tx_nonce = tx.nonce().expect("no nonce"); for attempt in 0..5 { let nonce = provider.get_transaction_count(from).await?; match nonce.cmp(&tx_nonce) { @@ -135,7 +148,7 @@ impl<'a> SendTransactionKind<'a> { /// - Submit via `eth_sendTransaction` for unlocked accounts /// - Sign and submit via `eth_sendRawTransaction` for raw transactions /// - Submit pre-signed transaction via `eth_sendRawTransaction` - pub async fn send(self, provider: Arc>) -> Result { + pub async fn send(self, provider: Arc>) -> Result { match self { Self::Unlocked(tx) => { debug!("sending transaction from unlocked account {:?}", tx); @@ -163,6 +176,35 @@ impl<'a> SendTransactionKind<'a> { // Sign and send the transaction via the browser wallet Ok(signer.send_transaction_via_browser(tx).await?) } + Self::AccessKey(mut tx, signer, access_key, rpc_url) => { + debug!("sending transaction via tempo access key: {:?}", tx); + + // Check if the key needs on-chain provisioning. + if let Some(auth) = &access_key.key_authorization { + let tempo_provider = foundry_common::provider::ProviderBuilder::< + tempo_alloy::TempoNetwork, + >::new(&rpc_url) + .build()?; + if !tempo_provider + .get_keychain_key(access_key.wallet_address, access_key.key_address) + .await + .map(|info| info.keyId != Address::ZERO) + .unwrap_or(false) + { + tx.set_key_authorization(auth.clone()); + } + } + + let raw_tx = foundry_wallets::tempo::sign_with_access_key( + tx, + signer, + access_key.wallet_address, + ) + .await?; + + let pending = provider.send_raw_transaction(&raw_tx).await?; + Ok(*pending.tx_hash()) + } } } @@ -172,7 +214,7 @@ impl<'a> SendTransactionKind<'a> { /// [`send`](Self::send) into a single call. pub async fn prepare_and_send( mut self, - provider: Arc>, + provider: Arc>, sequential_broadcast: bool, is_fixed_gas_limit: bool, estimate_via_rpc: bool, @@ -192,22 +234,27 @@ impl<'a> SendTransactionKind<'a> { } /// Represents how to send _all_ transactions -pub enum SendTransactionsKind { +pub enum SendTransactionsKind { /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. Unlocked(AddressHashSet), /// Send a signed transaction via `eth_sendRawTransaction`, or via browser - Raw { eth_wallets: AddressHashMap, browser: Option }, + Raw { + eth_wallets: AddressHashMap, + browser: Option>, + access_keys: AddressHashMap<(WalletSigner, TempoAccessKeyConfig)>, + }, } -impl SendTransactionsKind { +impl SendTransactionsKind { /// Returns the [`SendTransactionKind`] for the given address /// /// Returns an error if no matching signer is found or the address is not unlocked pub fn for_sender( &self, addr: &Address, - tx: TransactionRequest, - ) -> Result> { + tx: N::TransactionRequest, + rpc_url: &str, + ) -> Result> { match self { Self::Unlocked(unlocked) => { if !unlocked.contains(addr) { @@ -215,8 +262,10 @@ impl SendTransactionsKind { } Ok(SendTransactionKind::Unlocked(tx)) } - Self::Raw { eth_wallets, browser } => { - if let Some(wallet) = eth_wallets.get(addr) { + Self::Raw { eth_wallets, browser, access_keys } => { + if let Some((signer, config)) = access_keys.get(addr) { + Ok(SendTransactionKind::AccessKey(tx, signer, config, rpc_url.to_string())) + } else if let Some(wallet) = eth_wallets.get(addr) { Ok(SendTransactionKind::Raw(tx, wallet)) } else if let Some(b) = browser && b.address() == *addr @@ -233,15 +282,24 @@ impl SendTransactionsKind { /// State after we have bundled all /// [`TransactionWithMetadata`](forge_script_sequence::TransactionWithMetadata) objects into a /// single [`ScriptSequenceKind`] object containing one or more script sequences. -pub struct BundledState { +pub struct BundledState +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BundledState { +impl BundledState +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ pub async fn wait_for_pending(mut self) -> Result { let progress = ScriptProgress::default(); let progress_ref = &progress; @@ -276,7 +334,13 @@ impl BundledState { } /// Broadcasts transactions from all sequences. - pub async fn broadcast(mut self) -> Result { + pub async fn broadcast(mut self) -> Result> + where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: + FoundryTransactionBuilder + Into, + { let required_addresses = self .sequence .sequences() @@ -298,13 +362,29 @@ impl BundledState { let send_kind = if self.args.unlocked { SendTransactionsKind::Unlocked(required_addresses.clone()) } else { - let signers: Vec
= - self.script_wallets.signers().map_err(|e| eyre::eyre!("{e}"))?; + let signers: Vec
= self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("{e}"))? + .into_iter() + .chain(self.browser_wallet.as_ref().map(|b| b.address())) + .collect(); + + // For addresses without an explicit signer, try Tempo access key lookup. + let mut access_keys: AddressHashMap<(WalletSigner, TempoAccessKeyConfig)> = + AddressHashMap::default(); let mut missing_addresses = Vec::new(); for addr in &required_addresses { if !signers.contains(addr) { - missing_addresses.push(addr); + match foundry_wallets::tempo::lookup_signer(*addr) { + Ok(foundry_wallets::tempo::TempoLookup::Keychain(signer, config)) => { + access_keys.insert(*addr, (signer, *config)); + } + _ => { + missing_addresses.push(addr); + } + } } } @@ -316,11 +396,11 @@ impl BundledState { ); } - let (signers, browser) = self.script_wallets.into_multi_wallet().into_signers()?; + let signers = self.script_wallets.into_multi_wallet().into_signers()?; let eth_wallets = signers.into_iter().map(|(addr, signer)| (addr, signer.into())).collect(); - SendTransactionsKind::Raw { eth_wallets, browser } + SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; let progress = ScriptProgress::default(); @@ -379,7 +459,7 @@ impl BundledState { SendTransactionKind::Signed(tx) } TransactionMaybeSigned::Unsigned(mut tx) => { - let from = tx.from.expect("No sender for onchain transaction!"); + let from = tx.from().expect("No sender for onchain transaction!"); tx.set_chain_id(sequence.chain); @@ -399,7 +479,7 @@ impl BundledState { tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } - send_kind.for_sender(&from, tx)? + send_kind.for_sender(&from, tx, sequence.rpc_url())? } }; diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 7c5ea9e14d54a..3388989b35457 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -2,7 +2,7 @@ use crate::{ ScriptArgs, ScriptConfig, broadcast::BundledState, execute::LinkedState, multi_sequence::MultiChainSequence, sequence::ScriptSequenceKind, }; -use alloy_network::AnyNetwork; +use alloy_network::{AnyNetwork, Ethereum}; use alloy_primitives::{B256, Bytes}; use alloy_provider::Provider; use eyre::{OptionExt, Result}; @@ -20,6 +20,7 @@ use foundry_compilers::{ }; use foundry_evm::traces::debug::ContractSources; use foundry_linking::Linker; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use std::{path::PathBuf, str::FromStr, sync::Arc}; /// Container for the compiled contracts. @@ -157,13 +158,14 @@ pub struct PreprocessedState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, } impl PreprocessedState { /// Parses user input and compiles the contracts depending on script target. /// After compilation, finds exact [ArtifactId] of the target contract. pub fn compile(self) -> Result { - let Self { args, script_config, script_wallets } = self; + let Self { args, script_config, script_wallets, browser_wallet } = self; let project = script_config.config.project()?; let mut target_name = args.target_contract.clone(); @@ -232,6 +234,7 @@ impl PreprocessedState { args, script_config, script_wallets, + browser_wallet, build_data: BuildData { output, target, project_root: project.root().to_path_buf() }, }) } @@ -242,21 +245,22 @@ pub struct CompiledState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: BuildData, } impl CompiledState { /// Uses provided sender address to compute library addresses and link contracts with them. pub async fn link(self) -> Result { - let Self { args, script_config, script_wallets, build_data } = self; + let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let build_data = build_data.link(&script_config).await?; - Ok(LinkedState { args, script_config, script_wallets, build_data }) + Ok(LinkedState { args, script_config, script_wallets, browser_wallet, build_data }) } /// Tries loading the resumed state from the cache files, skipping simulation stage. - pub async fn resume(self) -> Result { + pub async fn resume(self) -> Result> { let chain = if self.args.multi { None } else { @@ -285,35 +289,49 @@ impl CompiledState { } }; - let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { - let mut froms = sequence.sequences().iter().flat_map(|s| { - s.transactions - .iter() - .skip(s.receipts.len()) - .map(|t| t.transaction.from().expect("from is missing in script artifact")) - }); - - let available_signers = self - .script_wallets - .signers() - .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; - - if !froms.all(|from| available_signers.contains(&from)) { - // IF we are missing required signers, execute script as we might need to collect - // private keys from the execution. - let executed = self.link().await?.prepare_execution().await?.execute().await?; + let (args, build_data, script_wallets, browser_wallet, script_config) = + if !self.args.unlocked { + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from().expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if !froms.all(|from| available_signers.contains(&from)) { + // IF we are missing required signers, execute script as we might need to + // collect private keys from the execution. + let executed = self.link().await?.prepare_execution().await?.execute().await?; + ( + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.browser_wallet, + executed.script_config, + ) + } else { + ( + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, + ) + } + } else { ( - executed.args, - executed.build_data.build_data, - executed.script_wallets, - executed.script_config, + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, ) - } else { - (self.args, self.build_data, self.script_wallets, self.script_config) - } - } else { - (self.args, self.build_data, self.script_wallets, self.script_config) - }; + }; // Collect libraries from sequence and link contracts with them. let libraries = match sequence { @@ -328,12 +346,17 @@ impl CompiledState { args, script_config, script_wallets, + browser_wallet, build_data: linked_build_data, sequence, }) } - fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { + fn try_load_sequence( + &self, + chain: Option, + dry_run: bool, + ) -> Result> { if let Some(chain) = chain { let sequence = ScriptSequence::load( &self.script_config.config, diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 326960c23ac66..06b54403e1480 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy_dyn_abi::FunctionExt; use alloy_json_abi::{Function, InternalType, JsonAbi}; -use alloy_network::AnyNetwork; +use alloy_network::{AnyNetwork, Ethereum}; use alloy_primitives::{ Address, Bytes, map::{HashMap, HashSet}, @@ -32,6 +32,7 @@ use foundry_evm::{ render_trace_arena, }, }; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::join_all; use itertools::Itertools; use std::path::Path; @@ -43,6 +44,7 @@ pub struct LinkedState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, } @@ -63,7 +65,7 @@ impl LinkedState { /// Given linked and compiled artifacts, prepares data we need for execution. /// This includes the function to call and the calldata to pass to it. pub async fn prepare_execution(self) -> Result { - let Self { args, script_config, script_wallets, build_data } = self; + let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let target_contract = build_data.get_target_contract()?; @@ -77,6 +79,7 @@ impl LinkedState { args, script_config, script_wallets, + browser_wallet, execution_data: ExecutionData { func, calldata, @@ -94,6 +97,7 @@ pub struct PreExecutionState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, } @@ -123,6 +127,7 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data.build_data, }; @@ -133,6 +138,7 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: result, @@ -180,7 +186,7 @@ impl PreExecutionState { /// them instead. fn maybe_new_sender( &self, - transactions: Option<&BroadcastableTransactions>, + transactions: Option<&BroadcastableTransactions>, ) -> Result> { let mut new_sender = None; @@ -220,7 +226,7 @@ pub struct RpcData { impl RpcData { /// Iterates over script transactions and collects RPC urls. - fn from_transactions(txs: &BroadcastableTransactions) -> Self { + fn from_transactions(txs: &BroadcastableTransactions) -> Self { let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); @@ -276,6 +282,7 @@ pub struct ExecutedState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, pub execution_result: ScriptResult, @@ -288,7 +295,8 @@ impl ExecutedState { let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; - let mut txs = self.execution_result.transactions.clone().unwrap_or_default(); + let mut txs: BroadcastableTransactions = + self.execution_result.transactions.clone().unwrap_or_default(); // Ensure that unsigned transactions have both `data` and `input` populated to avoid // issues with eth_estimateGas and eth_sendTransaction requests. @@ -314,6 +322,7 @@ impl ExecutedState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: self.execution_result, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 684bd42baae89..6d7e65598c68f 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -13,8 +13,9 @@ extern crate tracing; use crate::runner::ScriptRunner; use alloy_json_abi::{Function, JsonAbi}; +use alloy_network::Ethereum; use alloy_primitives::{ - Address, Bytes, Log, TxKind, U256, hex, + Address, Bytes, Log, U256, hex, map::{AddressHashMap, HashMap}, }; use alloy_signer::Signer; @@ -228,25 +229,28 @@ pub struct ScriptArgs { impl ScriptArgs { pub async fn preprocess(self) -> Result { let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); + let browser_wallet = self.wallets.browser_signer::().await?; let (config, mut evm_opts) = self.load_config_and_evm_opts()?; if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; } else if self.evm.sender.is_none() { - // If no sender was explicitly set via --sender and there's exactly one signer - // (e.g. from --account or --keystore), use that signer's address as the sender. - // This makes --account behave consistently with --private-key. + // If no sender was explicitly set via --sender, auto-detect it from available signers: + // use the sole signer's address if there's exactly one, or fall back to the browser + // wallet address if present. if let Ok(signers) = script_wallets.signers() && signers.len() == 1 { evm_opts.sender = signers[0]; + } else if let Some(signer) = browser_wallet.as_ref().map(|b| b.address()) { + evm_opts.sender = signer } } let script_config = ScriptConfig::new(config, evm_opts).await?; - Ok(PreprocessedState { args: self, script_config, script_wallets }) + Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } /// Executes the script @@ -464,15 +468,13 @@ impl ScriptArgs { let mut offset = 0; // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(TxKind::Call(to)) = to { + if let Some(to) = to { if to == create2_deployer { // Size of the salt prefix. offset = 32; } else { continue; } - } else if let Some(TxKind::Create) = to { - // Pass } // Find artifact with a deployment code same as the data. @@ -541,7 +543,7 @@ pub struct ScriptResult { pub gas_used: u64, pub labeled_addresses: AddressHashMap, #[serde(skip)] - pub transactions: Option, + pub transactions: Option>, pub returned: Bytes, pub address: Option
, #[serde(skip)] @@ -563,7 +565,7 @@ impl ScriptResult { .find_by_creation_code(init_code.as_ref()) .map(|artifact| artifact.0.name.clone()); return Some(AdditionalContract { - opcode: node.trace.kind, + call_kind: node.trace.kind, address: node.trace.address, contract_name, init_code, @@ -636,13 +638,13 @@ impl ScriptConfig { debug: bool, ) -> Result { trace!("preparing script runner"); - let env = self.evm_opts.env().await?; + let (evm_env, tx_env) = self.evm_opts.env().await?; let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { Some(db) => db.clone(), None => { - let fork = self.evm_opts.get_fork(&self.config, env.evm_env.clone()); + let fork = self.evm_opts.get_fork(&self.config, evm_env.clone()); let backend = Backend::spawn(fork)?; self.backends.insert(fork_url.clone(), backend.clone()); backend @@ -685,7 +687,7 @@ impl ScriptConfig { }); } - Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone())) + Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } } diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs index 128df9460e95b..3c1e7e448ae05 100644 --- a/crates/script/src/multi_sequence.rs +++ b/crates/script/src/multi_sequence.rs @@ -1,3 +1,4 @@ +use alloy_network::Network; use eyre::{ContextCompat, Result, WrapErr}; use forge_script_sequence::{ DRY_RUN_DIR, ScriptSequence, SensitiveScriptSequence, now, sig_to_file_name, @@ -10,8 +11,12 @@ use std::path::PathBuf; /// Holds the sequences of multiple chain deployments. #[derive(Clone, Default, Serialize, Deserialize)] -pub struct MultiChainSequence { - pub deployments: Vec, +#[serde(bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" +))] +pub struct MultiChainSequence { + pub deployments: Vec>, #[serde(skip)] pub path: PathBuf, #[serde(skip)] @@ -26,16 +31,16 @@ pub struct SensitiveMultiChainSequence { } impl SensitiveMultiChainSequence { - fn from_multi_sequence(sequence: &MultiChainSequence) -> Self { + fn from_multi_sequence(sequence: &MultiChainSequence) -> Self { Self { deployments: sequence.deployments.iter().map(SensitiveScriptSequence::from).collect(), } } } -impl MultiChainSequence { +impl MultiChainSequence { pub fn new( - deployments: Vec, + deployments: Vec>, sig: &str, target: &ArtifactId, config: &Config, @@ -88,7 +93,10 @@ impl MultiChainSequence { } /// Loads the sequences for the multi chain deployment. - pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result { + pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; let mut sequence: Self = foundry_compilers::utils::read_json_file(&path) .wrap_err("Multi-chain deployment not found.")?; @@ -107,7 +115,10 @@ impl MultiChainSequence { } /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> + where + N::TxEnvelope: Serialize, + { self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); self.timestamp = now().as_millis(); diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs index 4038d800a784b..303d5125c8521 100644 --- a/crates/script/src/progress.rs +++ b/crates/script/src/progress.rs @@ -1,6 +1,6 @@ use crate::receipts::{PendingReceiptError, TxStatus, check_tx_status, format_receipt}; use alloy_chains::Chain; -use alloy_network::{Ethereum, ReceiptResponse}; +use alloy_network::{Network, ReceiptResponse}; use alloy_primitives::{ B256, map::{B256HashMap, HashMap}, @@ -32,7 +32,11 @@ pub struct SequenceProgressState { } impl SequenceProgressState { - pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + pub fn new( + sequence_idx: usize, + sequence: &ScriptSequence, + multi: MultiProgress, + ) -> Self { let mut state = if shell::is_quiet() || shell::is_json() { let top_spinner = ProgressBar::hidden(); let txs = ProgressBar::hidden(); @@ -143,7 +147,11 @@ pub struct SequenceProgress { } impl SequenceProgress { - pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + pub fn new( + sequence_idx: usize, + sequence: &ScriptSequence, + multi: MultiProgress, + ) -> Self { Self { inner: Arc::new(RwLock::new(SequenceProgressState::new(sequence_idx, sequence, multi))), } @@ -160,10 +168,10 @@ pub struct ScriptProgress { impl ScriptProgress { /// Returns a [SequenceProgress] instance for the given sequence index. If it doesn't exist, /// creates one. - pub fn get_sequence_progress( + pub fn get_sequence_progress( &self, sequence_idx: usize, - sequence: &ScriptSequence, + sequence: &ScriptSequence, ) -> SequenceProgress { if let Some(progress) = self.state.read().get(&sequence_idx) { return progress.clone(); @@ -183,11 +191,11 @@ impl ScriptProgress { /// has not confirmed, and cannot be found in the mempool, we remove it from /// the `deploy_sequence.pending` vector so that it will be rebroadcast in /// later steps. - pub async fn wait_for_pending( + pub async fn wait_for_pending( &self, sequence_idx: usize, - deployment_sequence: &mut ScriptSequence, - provider: &RootProvider, + deployment_sequence: &mut ScriptSequence, + provider: &RootProvider, timeout: u64, ) -> Result<()> { if deployment_sequence.pending.is_empty() { diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index afa4e8ab03e4d..217cfc36c088a 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,6 +1,6 @@ use alloy_chains::{Chain, NamedChain}; -use alloy_network::{Ethereum, ReceiptResponse}; -use alloy_primitives::{TxHash, U256, utils::format_units}; +use alloy_network::{Network, ReceiptResponse}; +use alloy_primitives::{Address, TxHash, U256, utils::format_units}; use alloy_provider::{ PendingTransactionBuilder, PendingTransactionError, Provider, RootProvider, WatchTxError, }; @@ -10,6 +10,18 @@ use forge_script_sequence::ScriptSequence; use foundry_common::{retry, retry::RetryError, shell}; use std::time::Duration; +/// Helper trait providing `contract_address` setter for generic `ReceiptResponse` +pub trait FoundryReceiptResponse { + /// Sets address of the created contract, or `None` if the transaction was not a deployment. + fn set_contract_address(&mut self, contract_address: Address); +} + +impl FoundryReceiptResponse for TransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.contract_address = Some(contract_address); + } +} + /// Marker error type for pending receipts #[derive(Debug, thiserror::Error)] #[error( @@ -20,25 +32,25 @@ pub struct PendingReceiptError { } /// Convenience enum for internal signalling of transaction status -pub enum TxStatus { +pub enum TxStatus { Dropped, - Success(TransactionReceipt), - Revert(TransactionReceipt), + Success(R), + Revert(R), } -impl From for TxStatus { - fn from(receipt: TransactionReceipt) -> Self { +impl From for TxStatus { + fn from(receipt: R) -> Self { if !receipt.status() { Self::Revert(receipt) } else { Self::Success(receipt) } } } /// Checks the status of a txhash by first polling for a receipt, then for /// mempool inclusion. Returns the tx hash, and a status -pub async fn check_tx_status( - provider: &RootProvider, +pub async fn check_tx_status( + provider: &RootProvider, hash: TxHash, timeout: u64, -) -> (TxHash, Result) { +) -> (TxHash, Result, eyre::Report>) { let result = retry::Retry::new_no_delay(3) .run_async_until_break(|| async { match PendingTransactionBuilder::new(provider.clone(), hash) @@ -89,10 +101,10 @@ pub async fn check_tx_status( } /// Prints parts of the receipt to stdout -pub fn format_receipt( +pub fn format_receipt( chain: Chain, - receipt: &TransactionReceipt, - sequence: Option<&ScriptSequence>, + receipt: &N::ReceiptResponse, + sequence: Option<&ScriptSequence>, ) -> String { let gas_used = receipt.gas_used(); let gas_price = receipt.effective_gas_price(); @@ -182,6 +194,7 @@ pub fn format_receipt( #[cfg(test)] mod tests { use super::*; + use alloy_network::Ethereum; use alloy_primitives::B256; use std::collections::VecDeque; @@ -198,7 +211,11 @@ mod tests { .unwrap() } - fn mock_sequence(tx_hash: B256, contract: Option<&str>, func: Option<&str>) -> ScriptSequence { + fn mock_sequence( + tx_hash: B256, + contract: Option<&str>, + func: Option<&str>, + ) -> ScriptSequence { let tx = serde_json::from_value(serde_json::json!({ "hash": tx_hash, "transactionType": "CALL", "contractName": contract, "contractAddress": null, "function": func, @@ -228,7 +245,7 @@ mod tests { #[test] fn format_receipt_without_sequence_omits_metadata() { let hash = B256::repeat_byte(0x42); - let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), None); + let out = format_receipt::(Chain::mainnet(), &mock_receipt(hash, true), None); assert!(!out.contains("Contract:")); assert!(!out.contains("Function:")); diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index d3d0b83319bc6..510a5afaf2833 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -5,6 +5,7 @@ use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types::TransactionRequest; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; +use foundry_common::TransactionMaybeSigned; use foundry_config::Config; use foundry_evm::{ constants::CALLER, @@ -73,13 +74,12 @@ impl ScriptRunner { library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionRequest { + transaction: TransactionMaybeSigned::new(TransactionRequest { from: Some(self.evm_opts.sender), input: code.clone().into(), nonce: Some(sender_nonce + library_transactions.len() as u64), ..Default::default() - } - .into(), + }), }) }), ScriptPredeployLibraries::Create2(libraries, salt) => { @@ -107,14 +107,13 @@ impl ScriptRunner { library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionRequest { + transaction: TransactionMaybeSigned::new(TransactionRequest { from: Some(self.evm_opts.sender), input: calldata.into(), nonce: Some(sender_nonce + library_transactions.len() as u64), to: Some(TxKind::Call(create2_deployer)), ..Default::default() - } - .into(), + }), }); } @@ -369,14 +368,14 @@ impl ScriptRunner { let mut gas_used = res.gas_used; if matches!(res.exit_reason, Some(return_ok!())) { // Store the current gas limit and reset it later. - let init_gas_limit = self.executor.env().tx.gas_limit; + let init_gas_limit = self.executor.tx_env().gas_limit; let mut highest_gas_limit = gas_used * 3; let mut lowest_gas_limit = gas_used; let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env_mut().tx.gas_limit = mid_gas_limit; + self.executor.tx_env_mut().gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?; match res.exit_reason { Some(InstructionResult::Revert) @@ -402,7 +401,7 @@ impl ScriptRunner { } } // Reset gas limit in the executor. - self.executor.env_mut().tx.gas_limit = init_gas_limit; + self.executor.tx_env_mut().gas_limit = init_gas_limit; } Ok(gas_used) } diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index c0a7e9deddca7..b2a74cc182e19 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,23 +1,33 @@ use crate::multi_sequence::MultiChainSequence; +use alloy_network::Network; use eyre::Result; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cli::utils::Git; use foundry_common::fmt::UIfmt; use foundry_compilers::ArtifactId; use foundry_config::Config; +use foundry_primitives::FoundryTransactionBuilder; +use serde::{Deserialize, Serialize}; use std::{ fmt::{Error, Write}, path::Path, }; /// Format transaction details for display -fn format_transaction(index: usize, tx: &TransactionWithMetadata) -> Result { +fn format_transaction( + index: usize, + tx: &TransactionWithMetadata, +) -> Result +where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, +{ let mut output = String::new(); writeln!(output, "### Transaction {index} ###")?; writeln!(output, "{}", tx.tx().pretty())?; // Show contract name and address if available - if !tx.opcode.is_any_create() + if !tx.call_kind.is_any_create() && let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) { writeln!(output, "contract: {name}({addr})")?; @@ -45,12 +55,20 @@ pub fn get_commit_hash(root: &Path) -> Option { Git::new(root).commit_hash(true, "HEAD").ok() } -pub enum ScriptSequenceKind { - Single(ScriptSequence), - Multi(MultiChainSequence), +pub enum ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ + Single(ScriptSequence), + Multi(MultiChainSequence), } -impl ScriptSequenceKind { +impl ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { match self { Self::Single(sequence) => sequence.save(silent, save_ts), @@ -58,14 +76,14 @@ impl ScriptSequenceKind { } } - pub fn sequences(&self) -> &[ScriptSequence] { + pub fn sequences(&self) -> &[ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_ref(sequence), Self::Multi(sequence) => &sequence.deployments, } } - pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_mut(sequence), Self::Multi(sequence) => &mut sequence.deployments, @@ -80,19 +98,28 @@ impl ScriptSequenceKind { ) -> Result<()> { match self { Self::Single(sequence) => { - sequence.paths = - Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); + sequence.paths = Some(ScriptSequence::::get_paths( + config, + sig, + target, + sequence.chain, + false, + )?); } Self::Multi(sequence) => { (sequence.path, sequence.sensitive_path) = - MultiChainSequence::get_paths(config, sig, target, false)?; + MultiChainSequence::::get_paths(config, sig, target, false)?; } }; Ok(()) } - pub fn show_transactions(&self) -> Result<()> { + pub fn show_transactions(&self) -> Result<()> + where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, + { for sequence in self.sequences() { if !sequence.transactions.is_empty() { sh_println!("\nChain {}\n", sequence.chain)?; @@ -107,7 +134,11 @@ impl ScriptSequenceKind { } } -impl Drop for ScriptSequenceKind { +impl Drop for ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ fn drop(&mut self) { if let Err(err) = self.save(false, true) { error!(?err, "could not save deployment sequence"); diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 00869a34dd3cf..e32b8e870c7e3 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -10,8 +10,8 @@ use crate::{ sequence::get_commit_hash, }; use alloy_chains::NamedChain; -use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; +use alloy_network::{Ethereum, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; @@ -19,6 +19,8 @@ use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{ContractData, shell}; use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; +use foundry_primitives::FoundryTransactionBuilder; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -36,6 +38,7 @@ pub struct PreSimulationState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, pub execution_result: ScriptResult, @@ -65,7 +68,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if let Some(TxKind::Call(_)) = to { + if to.is_some() { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -89,6 +92,7 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -99,10 +103,13 @@ impl PreSimulationState { /// transactions in those environments. /// /// Collects gas usage and metadata for each transaction. - pub async fn simulate_and_fill( + pub async fn simulate_and_fill( &self, - transactions: VecDeque, - ) -> Result> { + transactions: VecDeque>, + ) -> Result>> + where + N::TransactionRequest: FoundryTransactionBuilder, + { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -122,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; + let to = tx.to(); let result = runner .simulate( tx.from() @@ -140,7 +147,7 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.env_mut().evm_env.block_env.number += U256::from(1); + runner.executor.evm_env_mut().block_env.number += U256::from(1); } let is_noop_tx = if let Some(to) = to { @@ -253,9 +260,10 @@ pub struct FilledTransactionsState { pub args: ScriptArgs, pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque, + pub transactions: VecDeque>, } impl FilledTransactionsState { @@ -264,7 +272,7 @@ impl FilledTransactionsState { /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result { + pub async fn bundle(mut self) -> Result> { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -414,6 +422,7 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -424,14 +433,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque, - ) -> Result { + transactions: VecDeque>, + ) -> Result> { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::get_paths( + Some(ScriptSequence::::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index 9efbe363b23ef..4d742eebe6dc2 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -1,8 +1,8 @@ use super::ScriptResult; use crate::build::LinkedBuildData; use alloy_dyn_abi::JsonAbiExt; -use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, B256, TxKind, hex}; +use alloy_network::{Network, TransactionBuilder}; +use alloy_primitives::{Address, B256, hex}; use eyre::Result; use forge_script_sequence::TransactionWithMetadata; use foundry_common::{ContractData, SELECTOR_LEN, TransactionMaybeSigned, fmt::format_token_raw}; @@ -12,12 +12,12 @@ use revm_inspectors::tracing::types::CallKind; use std::collections::BTreeMap; #[derive(Debug)] -pub struct ScriptTransactionBuilder { - transaction: TransactionWithMetadata, +pub struct ScriptTransactionBuilder { + transaction: TransactionWithMetadata, } -impl ScriptTransactionBuilder { - pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { +impl ScriptTransactionBuilder { + pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { let mut transaction = TransactionWithMetadata::from_tx_request(transaction); transaction.rpc = rpc; // If tx.gas is already set that means it was specified in script @@ -33,7 +33,7 @@ impl ScriptTransactionBuilder { decoder: &CallTraceDecoder, create2_deployer: Address, ) -> Result<()> { - if let Some(TxKind::Call(to)) = self.transaction.transaction.to() { + if let Some(to) = self.transaction.transaction.to() { if to == create2_deployer { if let Some(input) = self.transaction.transaction.input() { let (salt, init_code) = input.split_at(32); @@ -45,7 +45,7 @@ impl ScriptTransactionBuilder { )?; } } else { - self.transaction.opcode = CallKind::Call; + self.transaction.call_kind = CallKind::Call; self.transaction.contract_address = Some(to); let Some(data) = self.transaction.transaction.input() else { return Ok(()) }; @@ -97,9 +97,9 @@ impl ScriptTransactionBuilder { contracts: &BTreeMap, ) -> Result<()> { if is_create2 { - self.transaction.opcode = CallKind::Create2; + self.transaction.call_kind = CallKind::Create2; } else { - self.transaction.opcode = CallKind::Create; + self.transaction.call_kind = CallKind::Create; } let info = contracts.get(&address); @@ -171,13 +171,13 @@ impl ScriptTransactionBuilder { self } - pub fn build(self) -> TransactionWithMetadata { + pub fn build(self) -> TransactionWithMetadata { self.transaction } } -impl From for ScriptTransactionBuilder { - fn from(transaction: TransactionWithMetadata) -> Self { +impl From> for ScriptTransactionBuilder { + fn from(transaction: TransactionWithMetadata) -> Self { Self { transaction } } } diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 1bfac6d9564bc..156e32b7aea06 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -1,9 +1,10 @@ use crate::{ ScriptArgs, ScriptConfig, build::LinkedBuildData, + receipts::FoundryReceiptResponse, sequence::{ScriptSequenceKind, get_commit_hash}, }; -use alloy_network::ReceiptResponse; +use alloy_network::{Network, ReceiptResponse}; use alloy_primitives::{Address, hex}; use eyre::{Result, eyre}; use forge_script_sequence::{AdditionalContract, ScriptSequence}; @@ -13,18 +14,28 @@ use foundry_common::ContractsByArtifact; use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo}; use foundry_config::{Chain, Config}; use semver::Version; +use serde::{Deserialize, Serialize}; /// State after we have broadcasted the script. /// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all /// broadcasted transactions. -pub struct BroadcastedState { +pub struct BroadcastedState +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ pub args: ScriptArgs, pub script_config: ScriptConfig, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BroadcastedState { +impl BroadcastedState +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, + N::ReceiptResponse: FoundryReceiptResponse, +{ pub async fn verify(self) -> Result<()> { let Self { args, script_config, build_data, mut sequence, .. } = self; @@ -179,8 +190,8 @@ impl VerifyBundle { /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. -async fn verify_contracts( - sequence: &mut ScriptSequence, +async fn verify_contracts>( + sequence: &mut ScriptSequence, config: &Config, mut verify: VerifyBundle, ) -> Result<()> { @@ -202,8 +213,10 @@ async fn verify_contracts( // create2 hash offset let mut offset = 0; - if tx.is_create2() { - receipt.contract_address = tx.contract_address; + if tx.is_create2() + && let Some(contract_address) = tx.contract_address + { + receipt.set_contract_address(contract_address); offset = 32; } @@ -266,8 +279,8 @@ async fn verify_contracts( Ok(()) } -fn check_unverified( - sequence: &ScriptSequence, +fn check_unverified( + sequence: &ScriptSequence, unverifiable_contracts: Vec
, verify: VerifyBundle, ) { diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index c22b9c857abed..4f442839ccc8b 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -231,7 +231,7 @@ impl VerifyBytecodeArgs { // Deploy at genesis let gen_blk_num = 0_u64; let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; - let (mut env, mut executor) = crate::utils::get_tracing_executor( + let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor( &mut fork_config, gen_blk_num, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), @@ -239,10 +239,10 @@ impl VerifyBytecodeArgs { ) .await?; - env.evm_env.block_env.number = U256::ZERO; + evm_env.block_env.number = U256::ZERO; let genesis_block = provider.get_block(gen_blk_num.into()).full().await?; - // Setup genesis tx and env. + // Setup genesis tx_env and evm_evm. let deployer = Address::with_last_byte(0x1); let mut gen_tx_req = TransactionRequest::default() .with_from(deployer) @@ -250,14 +250,14 @@ impl VerifyBytecodeArgs { .into_create(); if let Some(ref block) = genesis_block { - configure_env_block(&mut env, block, config.networks); + configure_env_block(&mut evm_env, block, config.networks); gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas().map(|g| g as u128); gen_tx_req.gas = Some(block.header.gas_limit()); gen_tx_req.gas_price = block.header.base_fee_per_gas().map(|g| g as u128); } let kind = gen_tx_req.kind(); - env.tx = gen_tx_req.try_into_tx_env(&env.evm_env)?; + let tx_env = gen_tx_req.try_into_tx_env(&evm_env)?; // Seed deployer account with funds let account_info = AccountInfo { @@ -267,8 +267,13 @@ impl VerifyBytecodeArgs { }; executor.backend_mut().insert_account_info(deployer, account_info); - let fork_address = - crate::utils::deploy_contract(&mut executor, &env, config.evm_spec_id(), kind)?; + let fork_address = crate::utils::deploy_contract( + &mut executor, + &evm_env, + &tx_env, + config.evm_spec_id(), + kind, + )?; // Compare runtime bytecode let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes( @@ -442,14 +447,14 @@ impl VerifyBytecodeArgs { // Fork the chain at `simulation_block`. let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; - let (mut env, mut executor) = crate::utils::get_tracing_executor( + let (mut evm_env, mut tx_env, mut executor) = crate::utils::get_tracing_executor( &mut fork_config, simulation_block - 1, // env.fork_block_number etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, ) .await?; - env.evm_env.block_env.number = U256::from(simulation_block); + evm_env.block_env.number = U256::from(simulation_block); let block = provider.get_block(simulation_block.into()).full().await?; // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same @@ -465,7 +470,7 @@ impl VerifyBytecodeArgs { transaction.set_nonce(prev_block_nonce); if let Some(ref block) = block { - configure_env_block(&mut env, block, config.networks); + configure_env_block(&mut evm_env, block, config.networks); let BlockTransactions::Full(ref txs) = block.transactions else { return Err(eyre::eyre!("Could not get block txs")); @@ -484,18 +489,22 @@ impl VerifyBytecodeArgs { } if let Some(tx_envelope) = tx.as_envelope() { - env.tx = TxEnv::from_recovered_tx(tx_envelope, tx.from()); + tx_env = TxEnv::from_recovered_tx(tx_envelope, tx.from()); } if let TxKind::Call(_) = tx.inner.kind() { - executor.transact_with_env(env.clone()).wrap_err_with(|| { - format!( - "Failed to execute transaction: {:?} in block {}", - tx.tx_hash(), - env.evm_env.block_env.number - ) - })?; - } else if let Err(error) = executor.deploy_with_env(env.clone(), None) { + executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( + || { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + evm_env.block_env.number + ) + }, + )?; + } else if let Err(error) = + executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) + { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), @@ -504,7 +513,7 @@ impl VerifyBytecodeArgs { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), - env.evm_env.block_env.number + evm_env.block_env.number ) }); } @@ -528,10 +537,15 @@ impl VerifyBytecodeArgs { } let kind = transaction.kind(); - env.tx = transaction.try_into_tx_env(&env.evm_env)?; + tx_env = transaction.try_into_tx_env(&evm_env)?; - let fork_address = - crate::utils::deploy_contract(&mut executor, &env, config.evm_spec_id(), kind)?; + let fork_address = crate::utils::deploy_contract( + &mut executor, + &evm_env, + &tx_env, + config.evm_spec_id(), + kind, + )?; // State committed using deploy_with_env, now get the runtime bytecode from the db. let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes( diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index b0b2a7cc1ad74..27d9bdf8e37a5 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -1,7 +1,7 @@ use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; -use alloy_consensus::BlockHeader; use alloy_dyn_abi::DynSolValue; -use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_evm::EvmEnv; +use alloy_primitives::{Address, Bytes, TxKind}; use alloy_provider::{ Provider, network::{AnyNetwork, AnyRpcBlock}, @@ -18,13 +18,16 @@ use foundry_common::{abi::encode_args, compile::ProjectCompiler, ignore_metadata use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; use foundry_evm::{ - Env, constants::DEFAULT_CREATE2_DEPLOYER, core::decode::RevertDecoder, - executors::TracingExecutor, opts::EvmOpts, traces::TraceMode, - utils::apply_chain_and_block_specific_env_changes, + constants::DEFAULT_CREATE2_DEPLOYER, + core::decode::RevertDecoder, + executors::TracingExecutor, + opts::EvmOpts, + traces::TraceMode, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; use foundry_evm_networks::NetworkConfigs; use reqwest::Url; -use revm::{bytecode::Bytecode, database::Database, primitives::hardfork::SpecId}; +use revm::{bytecode::Bytecode, context::TxEnv, database::Database, primitives::hardfork::SpecId}; use semver::{BuildMetadata, Version}; use serde::{Deserialize, Serialize}; use yansi::Paint; @@ -265,16 +268,16 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, -) -> Result<(Env, TracingExecutor)> { +) -> Result<(EvmEnv, TxEnv, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; let create2_deployer = evm_opts.create2_deployer; - let (env, fork, _chain, networks) = + let (evm_env, tx_env, fork, _chain, networks) = TracingExecutor::get_fork_material(fork_config, evm_opts).await?; let executor = TracingExecutor::new( - env.clone(), + (evm_env.clone(), tx_env.clone()), fork, Some(fork_config.evm_version), TraceMode::Call, @@ -283,31 +286,25 @@ pub async fn get_tracing_executor( None, )?; - Ok((env, executor)) + Ok((evm_env, tx_env, executor)) } -pub fn configure_env_block(env: &mut Env, block: &AnyRpcBlock, config: NetworkConfigs) { - env.evm_env.block_env.timestamp = U256::from(block.header.timestamp()); - env.evm_env.block_env.beneficiary = block.header.beneficiary(); - env.evm_env.block_env.difficulty = block.header.difficulty(); - env.evm_env.block_env.prevrandao = Some(block.header.mix_hash().unwrap_or_default()); - env.evm_env.block_env.basefee = block.header.base_fee_per_gas().unwrap_or_default(); - env.evm_env.block_env.gas_limit = block.header.gas_limit(); - apply_chain_and_block_specific_env_changes::(&mut env.evm_env, block, config); +pub fn configure_env_block(evm_env: &mut EvmEnv, block: &AnyRpcBlock, config: NetworkConfigs) { + let number = evm_env.block_env.number; + evm_env.block_env = block_env_from_header(&block.header); + evm_env.block_env.number = number; + apply_chain_and_block_specific_env_changes::(evm_env, block, config); } pub fn deploy_contract( executor: &mut TracingExecutor, - env: &Env, + evm_env: &EvmEnv, + tx_env: &TxEnv, spec_id: SpecId, to: Option, ) -> Result { - let env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx.clone(), - spec_id, - ); + let mut evm_env = evm_env.clone(); + evm_env.cfg_env.set_spec(spec_id); if to.is_some_and(|to| to.is_call()) { let TxKind::Call(to) = to.unwrap() else { unreachable!() }; @@ -316,7 +313,7 @@ pub fn deploy_contract( "Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx." ); } - let result = executor.transact_with_env(env)?; + let result = executor.transact_with_env(evm_env, tx_env.clone())?; trace!(transact_result = ?result.exit_reason); @@ -346,7 +343,7 @@ pub fn deploy_contract( Ok(Address::from_slice(&result.result)) } else { - let deploy_result = executor.deploy_with_env(env, None)?; + let deploy_result = executor.deploy_with_env(evm_env, tx_env.clone(), None)?; trace!(deploy_result = ?deploy_result.raw.exit_reason); Ok(deploy_result.address) } diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index e68de25e9b06e..381d8b2c600f7 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -43,13 +43,20 @@ alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } # turnkey alloy-signer-turnkey = { workspace = true, features = ["eip712"], optional = true } +tempo-primitives.workspace = true +tempo-alloy.workspace = true +alloy-eips.workspace = true +alloy-rlp.workspace = true + async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } derive_builder = "0.20" +dirs.workspace = true eyre.workspace = true rpassword = "7" serde.workspace = true thiserror.workspace = true +toml.workspace = true tower.workspace = true tower-http = { workspace = true, features = ["cors", "set-header"] } tracing.workspace = true diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index f27e908b63a40..435da79ace3d8 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -14,6 +14,7 @@ extern crate tracing; pub mod error; pub mod opts; pub mod signer; +pub mod tempo; pub mod utils; pub mod wallet_browser; pub mod wallet_multi; @@ -21,6 +22,7 @@ pub mod wallet_raw; pub use opts::WalletOpts; pub use signer::{PendingSigner, WalletSigner}; +pub use tempo::TempoAccessKeyConfig; pub use wallet_browser::opts::BrowserWalletOpts; pub use wallet_multi::MultiWalletOpts; pub use wallet_raw::RawWalletOpts; diff --git a/crates/wallets/src/opts.rs b/crates/wallets/src/opts.rs index d1c4a97690411..5ca010929a7f0 100644 --- a/crates/wallets/src/opts.rs +++ b/crates/wallets/src/opts.rs @@ -1,4 +1,4 @@ -use crate::{signer::WalletSigner, utils, wallet_raw::RawWalletOpts}; +use crate::{signer::WalletSigner, tempo::TempoAccessKeyConfig, utils, wallet_raw::RawWalletOpts}; use alloy_primitives::Address; use clap::Parser; use eyre::Result; @@ -105,7 +105,16 @@ pub struct WalletOpts { } impl WalletOpts { - pub async fn signer(&self) -> Result { + /// Attempts to resolve a signer from the configured wallet options. + /// + /// Returns the signer and, for Tempo keychain mode, an [`TempoAccessKeyConfig`] describing the + /// root wallet and provisioning data. + /// + /// Returns `Ok((None, None))` if no wallet option was configured and no Tempo fallback + /// matched. + pub async fn maybe_signer( + &self, + ) -> Result<(Option, Option)> { trace!("start finding signer"); let get_env = |key: &str| { @@ -158,7 +167,29 @@ impl WalletOpts { unreachable!() } } else { - eyre::bail!( + // No explicit wallet option was provided. Try Tempo wallet as a fallback + // if `--from` is set. + if let Some(from) = self.from { + match crate::tempo::lookup_signer(from)? { + crate::tempo::TempoLookup::Direct(signer) => { + return Ok((Some(signer), None)); + } + crate::tempo::TempoLookup::Keychain(signer, config) => { + return Ok((Some(signer), Some(*config))); + } + crate::tempo::TempoLookup::NotFound => {} + } + } + + return Ok((None, None)); + }; + + Ok((Some(signer), None)) + } + + pub async fn signer(&self) -> Result { + self.maybe_signer().await?.0.ok_or_else(|| { + eyre::eyre!( "\ Error accessing local wallet. Did you pass a keystore, hardware wallet, private key or mnemonic? @@ -182,9 +213,7 @@ respectively. The sender address can be specified by setting the `ETH_FROM` envi variable to the desired unlocked account address, or by providing the address directly using the --from flag." ) - }; - - Ok(signer) + }) } } diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs new file mode 100644 index 0000000000000..a86b568fdea2b --- /dev/null +++ b/crates/wallets/src/tempo.rs @@ -0,0 +1,196 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use alloy_signer::Signer; +use eyre::Result; +use std::path::PathBuf; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::transaction::{ + KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, +}; + +use crate::{WalletSigner, utils}; + +/// Wallet type: how this wallet was created. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// A single entry from Tempo's `keys.toml`. +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct KeyEntry { + #[serde(default)] + wallet_type: WalletType, + #[serde(default)] + wallet_address: Address, + #[serde(default)] + chain_id: u64, + #[serde(default)] + key_type: KeyType, + #[serde(default)] + key_address: Option
, + #[serde(default)] + key: Option, + #[serde(default)] + key_authorization: Option, + #[serde(default)] + expiry: Option, + #[serde(default)] + limits: Vec, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(serde::Deserialize)] +struct StoredTokenLimit { + #[allow(dead_code)] + currency: Address, + #[allow(dead_code)] + limit: String, +} + +/// The top-level structure of `~/.tempo/wallet/keys.toml`. +#[derive(serde::Deserialize)] +struct KeysFile { + #[serde(default)] + keys: Vec, +} + +/// Configuration for a Tempo access key (keychain mode). +/// +/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer +/// is an access key that signs on behalf of the root wallet. This struct carries the metadata +/// needed to construct the correct transaction. +#[derive(Debug, Clone)] +pub struct TempoAccessKeyConfig { + /// The root wallet address (the `from` address for transactions). + pub wallet_address: Address, + /// The access key's address (derived from the private key that actually signs). + pub key_address: Address, + /// Decoded key authorization for on-chain provisioning. + /// + /// When present, callers should check whether the key is already provisioned on-chain + /// (via the AccountKeychain precompile) before including this in a transaction. + pub key_authorization: Option, +} + +/// Result of looking up an address in Tempo's key store. +pub enum TempoLookup { + /// A direct (EOA) signer was found — `wallet_address == key_address`. + Direct(WalletSigner), + /// A keychain (access key) signer was found — `wallet_address != key_address`. + Keychain(WalletSigner, Box), + /// No matching entry was found. + NotFound, +} + +/// Returns the path to Tempo's keys file. +/// +/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. +fn keys_path() -> Option { + let base = std::env::var_os("TEMPO_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; + Some(base.join("wallet").join("keys.toml")) +} + +/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. +fn decode_key_authorization(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str)?; + let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; + Ok(auth) +} + +/// Looks up a signer for the given address in Tempo's `keys.toml`. +/// +/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, +/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, +/// or [`TempoLookup::NotFound`] if no entry matches. +pub fn lookup_signer(from: Address) -> Result { + let path = match keys_path() { + Some(p) if p.is_file() => p, + _ => return Ok(TempoLookup::NotFound), + }; + + let contents = std::fs::read_to_string(&path)?; + let file: KeysFile = toml::from_str(&contents)?; + + for entry in &file.keys { + if entry.wallet_address != from { + continue; + } + + let Some(key) = &entry.key else { + continue; + }; + + // Direct mode: wallet_address == key_address (or key_address is absent). + let is_direct = + entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let signer = utils::create_private_key_signer(key)?; + + if is_direct { + return Ok(TempoLookup::Direct(signer)); + } + + // Keychain mode: the key is an access key signing on behalf of wallet_address. + let key_authorization = + entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; + + let config = TempoAccessKeyConfig { + wallet_address: entry.wallet_address, + // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address + key_address: entry.key_address.unwrap(), + key_authorization, + }; + return Ok(TempoLookup::Keychain(signer, Box::new(config))); + } + + Ok(TempoLookup::NotFound) +} + +/// Signs a Tempo transaction request using an access key (keychain V2 mode). +/// +/// Bypasses the standard `EthereumWallet` signing path and instead: +/// 1. Builds the `TempoTransaction` from the request +/// 2. Computes the V2 keychain signing hash +/// 3. Signs with the access key +/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format +pub async fn sign_with_access_key( + tx_request: impl Into, + signer: &impl Signer, + wallet_address: Address, +) -> Result> { + let tx_request: TempoTransactionRequest = tx_request.into(); + let tempo_tx = tx_request + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + + Ok(buf) +} diff --git a/crates/wallets/src/wallet_browser/app/assets/main.js b/crates/wallets/src/wallet_browser/app/assets/main.js index 060f0198f6c2c..6d89aa9e303f1 100644 --- a/crates/wallets/src/wallet_browser/app/assets/main.js +++ b/crates/wallets/src/wallet_browser/app/assets/main.js @@ -1,69 +1,71 @@ -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),c=e=>{let n={};for(var r in e)t(n,r,{get:e[r],enumerable:!0});return n},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},ee=Object.prototype.hasOwnProperty;function te(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ne(e,t){return te(e.type,t,e.props)}function re(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ie(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ae=/\/+/g;function oe(e,t){return typeof e==`object`&&e&&e.key!=null?ie(``+e.key):t.toString(36)}function se(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ce(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ce(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+oe(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(ae,`$&/`)+`/`),ce(o,r,i,``,function(e){return e})):o!=null&&(re(o)&&(o=ne(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ae,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=d()})),p=s((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,re());else{var t=n(l);t!==null&&oe(x,t.startTime-e)}}var S=!1,C=-1,w=5,ee=-1;function te(){return g?!0:!(e.unstable_now()-eet&&te());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&oe(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?re():S=!1}}}var re;if(typeof y==`function`)re=function(){y(ne)};else if(typeof MessageChannel<`u`){var ie=new MessageChannel,ae=ie.port2;ie.port1.onmessage=ne,re=function(){ae.postMessage(null)}}else re=function(){_(ne,0)};function oe(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,oe(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,re()))),r},e.unstable_shouldYield=te,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),m=s(((e,t)=>{t.exports=p()})),h=s((e=>{var t=f();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=s((e=>{var t=m(),n=f(),r=g();function i(e){var t=`https://react.dev/errors/`+e;if(1me||(e.current=pe[me],pe[me]=null,me--)}function _e(e,t){me++,pe[me]=e.current,e.current=t}var ve=he(null),ye=he(null),be=he(null),xe=he(null);function Se(e,t){switch(_e(be,t),_e(ye,e),_e(ve,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Sf(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Sf(t),e=Cf(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}ge(ve),_e(ve,e)}function Ce(){ge(ve),ge(ye),ge(be)}function we(e){e.memoizedState!==null&&_e(xe,e);var t=ve.current,n=Cf(t,e.type);t!==n&&(_e(ye,e),_e(ve,n))}function Te(e){ye.current===e&&(ge(ve),ge(ye)),xe.current===e&&(ge(xe),jp._currentValue=fe)}var Ee,De;function Oe(e){if(Ee===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);Ee=t&&t[1]||``,De=-1()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function ee(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var oe=/\/+/g;function se(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function ce(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(ee,ee):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function le(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,le(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+se(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(oe,`$&/`)+`/`),le(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(oe,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=d()})),p=s((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,ie());else{var t=n(l);t!==null&&se(x,t.startTime-e)}}var S=!1,ee=-1,C=5,te=-1;function ne(){return g?!0:!(e.unstable_now()-tet&&ne());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&se(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?ie():S=!1}}}var ie;if(typeof y==`function`)ie=function(){y(re)};else if(typeof MessageChannel<`u`){var ae=new MessageChannel,oe=ae.port2;ae.port1.onmessage=re,ie=function(){oe.postMessage(null)}}else ie=function(){_(re,0)};function se(t,n){ee=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(ee),ee=-1):h=!0,se(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,ie()))),r},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),m=s(((e,t)=>{t.exports=p()})),h=s((e=>{var t=f();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=s((e=>{var t=m(),n=f(),r=g();function i(e){var t=`https://react.dev/errors/`+e;if(1he||(e.current=me[he],me[he]=null,he--)}function ve(e,t){he++,me[he]=e.current,e.current=t}var ye=ge(null),be=ge(null),xe=ge(null),Se=ge(null);function Ce(e,t){switch(ve(xe,t),ve(be,e),ve(ye,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?sf(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=sf(t),e=cf(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}_e(ye),ve(ye,e)}function we(){_e(ye),_e(be),_e(xe)}function Te(e){e.memoizedState!==null&&ve(Se,e);var t=ye.current,n=cf(t,e.type);t!==n&&(ve(be,e),ve(ye,n))}function Ee(e){be.current===e&&(_e(ye),_e(be)),Se.current===e&&(_e(Se),hp._currentValue=pe)}var De,Oe;function ke(e){if(De===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);De=t&&t[1]||``,Oe=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ke=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Oe(n):``}function je(e,t){switch(e.tag){case 26:case 27:case 5:return Oe(e.type);case 16:return Oe(`Lazy`);case 13:return e.child!==t&&t!==null?Oe(`Suspense Fallback`):Oe(`Suspense`);case 19:return Oe(`SuspenseList`);case 0:case 15:return Ae(e.type,!1);case 11:return Ae(e.type.render,!1);case 1:return Ae(e.type,!0);case 31:return Oe(`Activity`);default:return``}}function Me(e){try{var t=``,n=null;do t+=je(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ae=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ke(n):``}function Me(e,t){switch(e.tag){case 26:case 27:case 5:return ke(e.type);case 16:return ke(`Lazy`);case 13:return e.child!==t&&t!==null?ke(`Suspense Fallback`):ke(`Suspense`);case 19:return ke(`SuspenseList`);case 0:case 15:return je(e.type,!1);case 11:return je(e.type.render,!1);case 1:return je(e.type,!0);case 31:return ke(`Activity`);default:return``}}function Ne(e){try{var t=``,n=null;do t+=Me(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` -`+e.stack}}var Ne=Object.prototype.hasOwnProperty,Pe=t.unstable_scheduleCallback,Fe=t.unstable_cancelCallback,Ie=t.unstable_shouldYield,Le=t.unstable_requestPaint,Re=t.unstable_now,ze=t.unstable_getCurrentPriorityLevel,Be=t.unstable_ImmediatePriority,Ve=t.unstable_UserBlockingPriority,He=t.unstable_NormalPriority,Ue=t.unstable_LowPriority,We=t.unstable_IdlePriority,Ge=t.log,Ke=t.unstable_setDisableYieldValue,qe=null,Je=null;function Ye(e){if(typeof Ge==`function`&&Ke(e),Je&&typeof Je.setStrictMode==`function`)try{Je.setStrictMode(qe,e)}catch{}}var Xe=Math.clz32?Math.clz32:$e,Ze=Math.log,Qe=Math.LN2;function $e(e){return e>>>=0,e===0?32:31-(Ze(e)/Qe|0)|0}var et=256,tt=262144,nt=4194304;function rt(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function it(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=rt(n))):i=rt(o):i=rt(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=rt(n))):i=rt(o)):i=rt(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function at(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function ot(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function st(){var e=nt;return nt<<=1,!(nt&62914560)&&(nt=4194304),e}function ct(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function lt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ut(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),bn=!1;if(yn)try{var xn={};Object.defineProperty(xn,`passive`,{get:function(){bn=!0}}),window.addEventListener(`test`,xn,xn),window.removeEventListener(`test`,xn,xn)}catch{bn=!1}var Sn=null,Cn=null,wn=null;function Tn(){if(wn)return wn;var e,t=Cn,n=t.length,r,i=`value`in Sn?Sn.value:Sn.textContent,a=i.length;for(e=0;e=fr),hr=` `,gr=!1;function _r(e,t){switch(e){case`keyup`:return ur.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function vr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var yr=!1;function br(e,t){switch(e){case`compositionend`:return vr(t);case`keypress`:return t.which===32?(gr=!0,hr):null;case`textInput`:return e=t.data,e===hr&&gr?null:e;default:return null}}function xr(e,t){if(yr)return e===`compositionend`||!dr&&_r(e,t)?(e=Tn(),wn=Cn=Sn=null,yr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Ur(n)}}function Gr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Gr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Kr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Kt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Kt(e.document)}return t}function qr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Jr=yn&&`documentMode`in document&&11>=document.documentMode,Yr=null,Xr=null,Zr=null,Qr=!1;function $r(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Qr||Yr==null||Yr!==Kt(r)||(r=Yr,`selectionStart`in r&&qr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Zr&&Hr(Zr,r)||(Zr=r,r=af(Xr,`onSelect`),0>=o,i-=o,Gi=1<<32-Xe(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),ea&&qi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),ea&&qi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return ea&&qi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),ea&&qi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===re&&Ja(l)===r.type){n(e,r.sibling),c=a(r,o.props),to(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=Mi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ji(o.type,o.key,o.props,null,e.mode,c),to(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Fi(o,e.mode,c),c.return=e,e=c}return s(e);case re:return o=Ja(o),b(e,r,o,c)}if(ue(o))return h(e,r,o,c);if(se(o)){if(l=se(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,eo(o),c);if(o.$$typeof===C)return b(e,r,Sa(e,o),c);no(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=Ni(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{$a=0;var i=b(e,t,n,r);return Qa=null,i}catch(t){if(t===Ha||t===Wa)throw t;var a=Di(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var io=ro(!0),ao=ro(!1),oo=!1;function so(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function co(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function lo(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function uo(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,fu&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=wi(e),Ci(e,null,n),t}return bi(e,r,t,n),wi(e)}function fo(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ft(e,n)}}function po(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var mo=!1;function ho(){if(mo){var e=Na;if(e!==null)throw e}}function go(e,t,n,r){mo=!1;var i=e.updateQueue;oo=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(N&f)===f:(r&f)===f){f!==0&&f===Ma&&(mo=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:oo=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),xu|=o,e.lanes=o,e.memoizedState=d}}function _o(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function vo(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=T.T,s={};T.T=s,ac(e,!1,t,n);try{var c=i(),l=T.S;if(l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`){var u=Ia(c,r);ic(e,t,u,Uu(e))}else ic(e,t,r,Uu(e))}catch(n){ic(e,t,{then:function(){},status:`rejected`,reason:n},Uu())}finally{de.p=a,o!==null&&s.types!==null&&(o.types=s.types),T.T=o}}function Js(){}function Ys(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Xs(e).queue;qs(e,a,t,fe,n===null?Js:function(){return Zs(e),n(r)})}function Xs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:fe,baseState:fe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:os,lastRenderedState:fe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:os,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Zs(e){var t=Xs(e);t.next===null&&(t=e.alternate.memoizedState),ic(e,t.next.queue,{},Uu())}function Qs(){return xa(jp)}function $s(){return $o().memoizedState}function ec(){return $o().memoizedState}function tc(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=Uu();e=lo(n);var r=uo(t,e,n);r!==null&&(Gu(r,t,n),fo(r,t,n)),t={cache:Oa()},e.payload=t;return}t=t.return}}function nc(e,t,n){var r=Uu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},oc(e)?sc(t,n):(n=xi(e,t,n,r),n!==null&&(Gu(n,e,r),cc(n,t,r)))}function rc(e,t,n){var r=Uu();ic(e,t,n,r)}function ic(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(oc(e))sc(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Vr(s,o))return bi(e,t,i,0),pu===null&&yi(),!1}catch{}if(n=xi(e,t,i,r),n!==null)return Gu(n,e,r),cc(n,t,r),!0}return!1}function ac(e,t,n,r){if(r={lane:2,revertLane:Hd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},oc(e)){if(t)throw Error(i(479))}else t=xi(e,n,r,2),t!==null&&Gu(t,e,2)}function oc(e){var t=e.alternate;return e===j||t!==null&&t===j}function sc(e,t){Lo=Io=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function cc(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ft(e,n)}}var lc={readContext:xa,use:ns,useCallback:Uo,useContext:Uo,useEffect:Uo,useImperativeHandle:Uo,useLayoutEffect:Uo,useInsertionEffect:Uo,useMemo:Uo,useReducer:Uo,useRef:Uo,useState:Uo,useDebugValue:Uo,useDeferredValue:Uo,useTransition:Uo,useSyncExternalStore:Uo,useId:Uo,useHostTransitionStatus:Uo,useFormState:Uo,useActionState:Uo,useOptimistic:Uo,useMemoCache:Uo,useCacheRefresh:Uo};lc.useEffectEvent=Uo;var uc={readContext:xa,use:ns,useCallback:function(e,t){return Qo().memoizedState=[e,t===void 0?null:t],e},useContext:xa,useEffect:Ps,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),Ms(4194308,4,Bs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ms(4194308,4,e,t)},useInsertionEffect:function(e,t){Ms(4,2,e,t)},useMemo:function(e,t){var n=Qo();t=t===void 0?null:t;var r=e();if(Ro){Ye(!0);try{e()}finally{Ye(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Qo();if(n!==void 0){var i=n(t);if(Ro){Ye(!0);try{n(t)}finally{Ye(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=nc.bind(null,j,e),[r.memoizedState,e]},useRef:function(e){var t=Qo();return e={current:e},t.memoizedState=e},useState:function(e){e=gs(e);var t=e.queue,n=rc.bind(null,j,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:Hs,useDeferredValue:function(e,t){var n=Qo();return Gs(n,e,t)},useTransition:function(){var e=gs(!1);return e=qs.bind(null,j,e.queue,!0,!1),Qo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=j,a=Qo();if(ea){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),pu===null)throw Error(i(349));N&127||ds(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,Ps(ps.bind(null,r,o,e),[e]),r.flags|=2048,As(9,{destroy:void 0},fs.bind(null,r,o,n,t),null),n},useId:function(){var e=Qo(),t=pu.identifierPrefix;if(ea){var n=Ki,r=Gi;n=(r&~(1<<32-Xe(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=zo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[yt]=t,o[bt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(hf(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&il(t)}}return ll(t),al(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&il(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=be.current,sa(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Qi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[yt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||ff(e.nodeValue,n)),e||ia(t,!0)}else e=xf(e).createTextNode(r),e[yt]=t,t.stateNode=e}return ll(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=sa(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[yt]=t}else ca(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;ll(t),e=!1}else n=la(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(Ao(t),t):(Ao(t),null);if(t.flags&128)throw Error(i(558))}return ll(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=sa(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[yt]=t}else ca(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;ll(t),a=!1}else a=la(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(Ao(t),t):(Ao(t),null)}return Ao(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),sl(t,t.updateQueue),ll(t),null);case 4:return Ce(),e===null&&ef(t.stateNode.containerInfo),ll(t),null;case 10:return ha(t.type),ll(t),null;case 19:if(ge(jo),r=t.memoizedState,r===null)return ll(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)cl(r,!1);else{if(bu!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=Mo(e),o!==null){for(t.flags|=128,cl(r,!1),e=o.updateQueue,t.updateQueue=e,sl(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)Ai(n,e),n=n.sibling;return _e(jo,jo.current&1|2),ea&&qi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Re()>ju&&(t.flags|=128,a=!0,cl(r,!1),t.lanes=4194304)}else{if(!a)if(e=Mo(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,sl(t,e),cl(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!ea)return ll(t),null}else 2*Re()-r.renderingStartTime>ju&&n!==536870912&&(t.flags|=128,a=!0,cl(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(ll(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Re(),e.sibling=null,n=jo.current,_e(jo,a?n&1|2:n&1),ea&&qi(t,r.treeForkCount),e);case 22:case 23:return Ao(t),Co(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(ll(t),t.subtreeFlags&6&&(t.flags|=8192)):ll(t),n=t.updateQueue,n!==null&&sl(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&ge(Ra),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ha(Da),ll(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function dl(e,t){switch(Xi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ha(Da),Ce(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Te(t),null;case 31:if(t.memoizedState!==null){if(Ao(t),t.alternate===null)throw Error(i(340));ca()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(Ao(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));ca()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ge(jo),null;case 4:return Ce(),null;case 10:return ha(t.type),null;case 22:case 23:return Ao(t),Co(),e!==null&&ge(Ra),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ha(Da),null;case 25:return null;default:return null}}function fl(e,t){switch(Xi(t),t.tag){case 3:ha(Da),Ce();break;case 26:case 27:case 5:Te(t);break;case 4:Ce();break;case 31:t.memoizedState!==null&&Ao(t);break;case 13:Ao(t);break;case 19:ge(jo);break;case 10:ha(t.type);break;case 22:case 23:Ao(t),Co(),e!==null&&ge(Ra);break;case 24:ha(Da)}}function pl(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){xd(t,t.return,e)}}function ml(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){xd(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){xd(t,t.return,e)}}function hl(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{vo(t,n)}catch(t){xd(e,e.return,t)}}}function gl(e,t,n){n.props=_c(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){xd(e,t,n)}}function _l(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){xd(e,t,n)}}function vl(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){xd(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){xd(e,t,n)}else n.current=null}function yl(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){xd(e,e.return,t)}}function bl(e,t,n){try{var r=e.stateNode;gf(r,e.type,n,t),r[bt]=t}catch(t){xd(e,e.return,t)}}function xl(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Mf(e.type)||e.tag===4}function Sl(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||xl(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Mf(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Cl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=un));else if(r!==4&&(r===27&&Mf(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Cl(e,t,n),e=e.sibling;e!==null;)Cl(e,t,n),e=e.sibling}function wl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Mf(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(wl(e,t,n),e=e.sibling;e!==null;)wl(e,t,n),e=e.sibling}function Tl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);hf(t,r,n),t[yt]=e,t[bt]=n}catch(t){xd(e,e.return,t)}}var El=!1,Dl=!1,Ol=!1,kl=typeof WeakSet==`function`?WeakSet:Set,Al=null;function jl(e,t){if(e=e.containerInfo,yf=Bp,e=Kr(e),qr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(bf={focusedElem:e,selectionRange:n},Bp=!1,Al=t;Al!==null;)if(t=Al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,Al=e;else for(;Al!==null;){switch(t=Al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),hf(o,r,n),o[yt]=e,jt(o),r=o;break a;case`link`:var s=bp(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Wr(s,h),v=Wr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,T.T=null,n=zu,zu=null;var o=Fu,s=Lu;if(Pu=0,Iu=Fu=null,Lu=0,fu&6)throw Error(i(331));var c=fu;if(fu|=4,su(o.current),$l(o,o.current,s,n),fu=c,Fd(0,!1),Je&&typeof Je.onPostCommitFiberRoot==`function`)try{Je.onPostCommitFiberRoot(qe,o)}catch{}return!0}finally{de.p=a,T.T=r,_d(e,t)}}function bd(e,t,n){t=Li(n,t),t=Cc(e.stateNode,t,2),e=uo(e,t,2),e!==null&&(lt(e,2),Pd(e))}function xd(e,t,n){if(e.tag===3)bd(e,e,n);else for(;t!==null;){if(t.tag===3){bd(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(Nu===null||!Nu.has(r))){e=Li(n,e),n=wc(2),r=uo(t,n,2),r!==null&&(Tc(n,r,t,e),lt(r,2),Pd(r));break}}t=t.return}}function Sd(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new du;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(vu=!0,i.add(n),e=Cd.bind(null,e,t,n),t.then(e,e))}function Cd(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,pu===e&&(N&n)===n&&(bu===4||bu===3&&(N&62914560)===N&&300>Re()-ku?!(fu&2)&&Qu(e,0):Cu|=n,Tu===N&&(Tu=0)),Pd(e)}function wd(e,t){t===0&&(t=st()),e=Si(e,t),e!==null&&(lt(e,t),Pd(e))}function Td(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),wd(e,n)}function Ed(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),wd(e,n)}function Dd(e,t){return Pe(e,t)}var Od=null,kd=null,Ad=!1,jd=!1,Md=!1,Nd=0;function Pd(e){e!==kd&&e.next===null&&(kd===null?Od=kd=e:kd=kd.next=e),jd=!0,Ad||(Ad=!0,Vd())}function Fd(e,t){if(!Md&&jd){Md=!0;do for(var n=!1,r=Od;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Xe(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,Bd(r,a))}else a=N,a=it(r,r===pu?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||at(r,a)||(n=!0,Bd(r,a));r=r.next}while(n);Md=!1}}function Id(){Ld()}function Ld(){jd=Ad=!1;var e=0;Nd!==0&&Ef()&&(e=Nd);for(var t=Re(),n=null,r=Od;r!==null;){var i=r.next,a=Rd(r,t);a===0?(r.next=null,n===null?Od=i:n.next=i,i===null&&(kd=n)):(n=r,(e!==0||a&3)&&(jd=!0)),r=i}Pu!==0&&Pu!==5||Fd(e,!1),Nd!==0&&(Nd=0)}function Rd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&_f(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function ep(e,t,n){var r=$f;if(r&&typeof t==`string`&&t){var i=Jt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),Jf.has(i)||(Jf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),hf(t,`link`,e),jt(t),r.head.appendChild(t)))}}function tp(e){Xf.D(e),ep(`dns-prefetch`,e,null)}function np(e,t){Xf.C(e,t),ep(`preconnect`,e,t)}function rp(e,t,n){Xf.L(e,t,n);var r=$f;if(r&&e&&t){var i=`link[rel="preload"][as="`+Jt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Jt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Jt(n.imageSizes)+`"]`)):i+=`[href="`+Jt(e)+`"]`;var a=i;switch(t){case`style`:a=lp(e);break;case`script`:a=pp(e)}qf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),qf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(up(a))||t===`script`&&r.querySelector(mp(a))||(t=r.createElement(`link`),hf(t,`link`,e),jt(t),r.head.appendChild(t)))}}function ip(e,t){Xf.m(e,t);var n=$f;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Jt(r)+`"][href="`+Jt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=pp(e)}if(!qf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),qf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(mp(a)))return}r=n.createElement(`link`),hf(r,`link`,e),jt(r),n.head.appendChild(r)}}}function ap(e,t,n){Xf.S(e,t,n);var r=$f;if(r&&e){var i=At(r).hoistableStyles,a=lp(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(up(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=qf.get(a))&&_p(e,n);var c=o=r.createElement(`link`);jt(c),hf(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,gp(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function op(e,t){Xf.X(e,t);var n=$f;if(n&&e){var r=At(n).hoistableScripts,i=pp(e),a=r.get(i);a||(a=n.querySelector(mp(i)),a||(e=p({src:e,async:!0},t),(t=qf.get(i))&&vp(e,t),a=n.createElement(`script`),jt(a),hf(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function sp(e,t){Xf.M(e,t);var n=$f;if(n&&e){var r=At(n).hoistableScripts,i=pp(e),a=r.get(i);a||(a=n.querySelector(mp(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=qf.get(i))&&vp(e,t),a=n.createElement(`script`),jt(a),hf(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function cp(e,t,n,r){var a=(a=be.current)?Yf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=lp(n.href),n=At(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=lp(n.href);var o=At(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(up(e)))&&!o._p&&(s.instance=o,s.state.loading=5),qf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},qf.set(e,n),o||fp(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=pp(n),n=At(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function lp(e){return`href="`+Jt(e)+`"`}function up(e){return`link[rel="stylesheet"][`+e+`]`}function dp(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function fp(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),hf(t,`link`,n),jt(t),e.head.appendChild(t))}function pp(e){return`[src="`+Jt(e)+`"]`}function mp(e){return`script[async]`+e}function hp(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Jt(n.href)+`"]`);if(r)return t.instance=r,jt(r),r;var a=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),jt(r),hf(r,`style`,a),gp(r,n.precedence,e),t.instance=r;case`stylesheet`:a=lp(n.href);var o=e.querySelector(up(a));if(o)return t.state.loading|=4,t.instance=o,jt(o),o;r=dp(n),(a=qf.get(a))&&_p(r,a),o=(e.ownerDocument||e).createElement(`link`),jt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),hf(o,`link`,r),t.state.loading|=4,gp(o,n.precedence,e),t.instance=o;case`script`:return o=pp(n.src),(a=e.querySelector(mp(o)))?(t.instance=a,jt(a),a):(r=n,(a=qf.get(o))&&(r=p({},n),vp(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),jt(a),hf(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,gp(r,n.precedence,e));return t.instance}function gp(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Sp(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Cp(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function wp(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=lp(r.href),a=t.querySelector(up(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Dp.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,jt(a);return}a=t.ownerDocument||t,r=dp(r),(i=qf.get(i))&&_p(r,i),a=a.createElement(`link`),jt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),hf(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Dp.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Tp=0;function Ep(e,t){return e.stylesheets&&e.count===0&&kp(e,e.stylesheets),0Tp?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Dp(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)kp(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Op=null;function kp(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Op=new Map,t.forEach(Ap,e),Op=null,Dp.call(e))}function Ap(e,t){if(!(t.state.loading&4)){var n=Op.get(e);if(n)var r=n.get(null);else{n=new Map,Op.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=_()}))()),y=u(f()),b,x=o((()=>{b=`1.1.0`})),S,C=o((()=>{x(),S=class e extends Error{constructor(t,n={}){let r=n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details,i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...i?[`Docs: https://abitype.dev${i}`]:[],...r?[`Details: ${r}`]:[],`Version: abitype@${b}`].join(` -`);super(a),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiTypeError`}),n.cause&&(this.cause=n.cause),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.shortMessage=t}}}));function w(e,t){return e.exec(t)?.groups}var ee,te,ne,re=o((()=>{ee=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,te=/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/,ne=/^\(.+?\).*?$/}));function ie(e){let t=e.type;if(ae.test(e.type)&&`components`in e){t=`(`;let n=e.components.length;for(let r=0;r{re(),ae=/^tuple(?(\[(\d*)\])*)$/}));function se(e){let t=``,n=e.length;for(let r=0;r{oe()}));function le(e){return e.type===`function`?`function ${e.name}(${se(e.inputs)})${e.stateMutability&&e.stateMutability!==`nonpayable`?` ${e.stateMutability}`:``}${e.outputs?.length?` returns (${se(e.outputs)})`:``}`:e.type===`event`?`event ${e.name}(${se(e.inputs)})`:e.type===`error`?`error ${e.name}(${se(e.inputs)})`:e.type===`constructor`?`constructor(${se(e.inputs)})${e.stateMutability===`payable`?` payable`:``}`:e.type===`fallback`?`fallback() external${e.stateMutability===`payable`?` payable`:``}`:`receive() external payable`}var ue=o((()=>{ce()}));function T(e){return Ce.test(e)}function de(e){return w(Ce,e)}function fe(e){return we.test(e)}function pe(e){return w(we,e)}function me(e){return Te.test(e)}function he(e){return w(Te,e)}function ge(e){return Ee.test(e)}function _e(e){return w(Ee,e)}function ve(e){return De.test(e)}function ye(e){return w(De,e)}function be(e){return Oe.test(e)}function xe(e){return w(Oe,e)}function Se(e){return ke.test(e)}var Ce,we,Te,Ee,De,Oe,ke,Ae,je,Me,Ne=o((()=>{re(),Ce=/^error (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,we=/^event (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,Te=/^function (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)(?: (?external|public{1}))?(?: (?pure|view|nonpayable|payable{1}))?(?: returns\s?\((?.*?)\))?$/,Ee=/^struct (?[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?.*?)\}$/,De=/^constructor\((?.*?)\)(?:\s(?payable{1}))?$/,Oe=/^fallback\(\) external(?:\s(?payable{1}))?$/,ke=/^receive\(\) external payable$/,Ae=new Set([`memory`,`indexed`,`storage`,`calldata`]),je=new Set([`indexed`]),Me=new Set([`calldata`,`memory`,`storage`])})),Pe,Fe,Ie,Le=o((()=>{C(),Pe=class extends S{constructor({signature:e}){super(`Failed to parse ABI item.`,{details:`parseAbiItem(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiitem-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiItemError`})}},Fe=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownTypeError`})}},Ie=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSolidityTypeError`})}}})),Re,ze,Be,Ve,He,Ue,We=o((()=>{C(),Re=class extends S{constructor({params:e}){super(`Failed to parse ABI parameters.`,{details:`parseAbiParameters(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiparameters-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiParametersError`})}},ze=class extends S{constructor({param:e}){super(`Invalid ABI parameter.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParameterError`})}},Be=class extends S{constructor({param:e,name:t}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`"${t}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SolidityProtectedKeywordError`})}},Ve=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidModifierError`})}},He=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`,`Data location can only be specified for array, struct, or mapping types, but "${n}" was given.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidFunctionModifierError`})}},Ue=class extends S{constructor({abiParameter:e}){super(`Invalid ABI parameter.`,{details:JSON.stringify(e,null,2),metaMessages:[`ABI parameter type is invalid.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiTypeParameterError`})}}})),Ge,Ke,qe,Je=o((()=>{C(),Ge=class extends S{constructor({signature:e,type:t}){super(`Invalid ${t} signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidSignatureError`})}},Ke=class extends S{constructor({signature:e}){super(`Unknown signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSignatureError`})}},qe=class extends S{constructor({signature:e}){super(`Invalid struct signature.`,{details:e,metaMessages:[`No properties exist.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidStructSignatureError`})}}})),Ye,Xe=o((()=>{C(),Ye=class extends S{constructor({type:e}){super(`Circular reference detected.`,{metaMessages:[`Struct "${e}" is a circular reference.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`CircularReferenceError`})}}})),Ze,Qe=o((()=>{C(),Ze=class extends S{constructor({current:e,depth:t}){super(`Unbalanced parentheses.`,{metaMessages:[`"${e.trim()}" has too many ${t>0?`opening`:`closing`} parentheses.`],details:`Depth "${t}"`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParenthesisError`})}}}));function $e(e,t,n){let r=``;if(n)for(let e of Object.entries(n)){if(!e)continue;let t=``;for(let n of e[1])t+=`[${n.type}${n.name?`:${n.name}`:``}]`;r+=`(${e[0]}{${t}})`}return t?`${t}:${e}${r}`:e}var et,tt=o((()=>{et=new Map([[`address`,{type:`address`}],[`bool`,{type:`bool`}],[`bytes`,{type:`bytes`}],[`bytes32`,{type:`bytes32`}],[`int`,{type:`int256`}],[`int256`,{type:`int256`}],[`string`,{type:`string`}],[`uint`,{type:`uint256`}],[`uint8`,{type:`uint8`}],[`uint16`,{type:`uint16`}],[`uint24`,{type:`uint24`}],[`uint32`,{type:`uint32`}],[`uint64`,{type:`uint64`}],[`uint96`,{type:`uint96`}],[`uint112`,{type:`uint112`}],[`uint160`,{type:`uint160`}],[`uint192`,{type:`uint192`}],[`uint256`,{type:`uint256`}],[`address owner`,{type:`address`,name:`owner`}],[`address to`,{type:`address`,name:`to`}],[`bool approved`,{type:`bool`,name:`approved`}],[`bytes _data`,{type:`bytes`,name:`_data`}],[`bytes data`,{type:`bytes`,name:`data`}],[`bytes signature`,{type:`bytes`,name:`signature`}],[`bytes32 hash`,{type:`bytes32`,name:`hash`}],[`bytes32 r`,{type:`bytes32`,name:`r`}],[`bytes32 root`,{type:`bytes32`,name:`root`}],[`bytes32 s`,{type:`bytes32`,name:`s`}],[`string name`,{type:`string`,name:`name`}],[`string symbol`,{type:`string`,name:`symbol`}],[`string tokenURI`,{type:`string`,name:`tokenURI`}],[`uint tokenId`,{type:`uint256`,name:`tokenId`}],[`uint8 v`,{type:`uint8`,name:`v`}],[`uint256 balance`,{type:`uint256`,name:`balance`}],[`uint256 tokenId`,{type:`uint256`,name:`tokenId`}],[`uint256 value`,{type:`uint256`,name:`value`}],[`event:address indexed from`,{type:`address`,name:`from`,indexed:!0}],[`event:address indexed to`,{type:`address`,name:`to`,indexed:!0}],[`event:uint indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}],[`event:uint256 indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}]])}));function nt(e,t={}){if(me(e))return rt(e,t);if(fe(e))return it(e,t);if(T(e))return at(e,t);if(ve(e))return ot(e,t);if(be(e))return st(e);if(Se(e))return{type:`receive`,stateMutability:`payable`};throw new Ke({signature:e})}function rt(e,t={}){let n=he(e);if(!n)throw new Ge({signature:e,type:`function`});let r=lt(n.parameters),i=[],a=r.length;for(let e=0;e{re(),Le(),We(),Je(),Qe(),tt(),Ne(),pt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*(?:\spayable)?)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,mt=/^\((?.+?)\)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,ht=/^u?int$/,gt=/^(?:after|alias|anonymous|apply|auto|byte|calldata|case|catch|constant|copyof|default|defined|error|event|external|false|final|function|immutable|implements|in|indexed|inline|internal|let|mapping|match|memory|mutable|null|of|override|partial|private|promise|public|pure|reference|relocatable|return|returns|sizeof|static|storage|struct|super|supports|switch|this|true|try|typedef|typeof|var|view|virtual)$/}));function vt(e){let t={},n=e.length;for(let r=0;r{re(),Le(),We(),Je(),Xe(),Ne(),_t(),bt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*)(?(?:\[\d*?\])+?)?$/}));function St(e){let t=vt(e),n=[],r=e.length;for(let i=0;i{Ne(),xt(),_t()}));function wt(e){let t;if(typeof e==`string`)t=nt(e);else{let n=vt(e),r=e.length;for(let i=0;i{Le(),Ne(),xt(),_t()}));function Et(e){let t=[];if(typeof e==`string`){let n=lt(e),r=n.length;for(let e=0;e{We(),Ne(),xt(),_t()})),Ot=o((()=>{ue(),ce(),Ct(),Tt(),Dt()}));function E(e,t,n){let r=e[t.name];if(typeof r==`function`)return r;let i=e[n];return typeof i==`function`?i:n=>t(e,n)}function kt(e,{includeName:t=!1}={}){if(e.type!==`function`&&e.type!==`event`&&e.type!==`error`)throw new on(e.type);return`${e.name}(${At(e.inputs,{includeName:t})})`}function At(e,{includeName:t=!1}={}){return e?e.map(e=>jt(e,{includeName:t})).join(t?`, `:`,`):``}function jt(e,{includeName:t}){return e.type.startsWith(`tuple`)?`(${At(e.components,{includeName:t})})${e.type.slice(5)}`:e.type+(t&&e.name?` ${e.name}`:``)}var Mt=o((()=>{sn()}));function Nt(e,{strict:t=!0}={}){return!e||typeof e!=`string`?!1:t?/^0x[0-9a-fA-F]*$/.test(e):e.startsWith(`0x`)}var Pt=o((()=>{}));function Ft(e){return Nt(e,{strict:!1})?Math.ceil((e.length-2)/2):e.length}var It=o((()=>{Pt()})),Lt,Rt=o((()=>{Lt=`2.38.3`}));function zt(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause!==void 0?zt(e.cause,t):t?null:e}var Bt,D,O=o((()=>{Rt(),Bt={getDocsUrl:({docsBaseUrl:e,docsPath:t=``,docsSlug:n})=>t?`${e??`https://viem.sh`}${t}${n?`#${n}`:``}`:void 0,version:`viem@${Lt}`},D=class e extends Error{constructor(t,n={}){let r=(()=>n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details)(),i=(()=>n.cause instanceof e&&n.cause.docsPath||n.docsPath)(),a=Bt.getDocsUrl?.({...n,docsPath:i}),o=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...a?[`Docs: ${a}`]:[],...r?[`Details: ${r}`]:[],...Bt.version?[`Version: ${Bt.version}`]:[]].join(` -`);super(o,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.name=n.name??this.name,this.shortMessage=t,this.version=Lt}walk(e){return zt(this,e)}}})),Vt,Ht,Ut,Wt,Gt,Kt,qt,Jt,Yt,Xt,Zt,Qt,$t,en,tn,nn,rn,an,on,sn=o((()=>{Mt(),It(),O(),Vt=class extends D{constructor({docsPath:e}){super([`A constructor was not found on the ABI.`,`Make sure you are using the correct ABI and that the constructor exists on it.`].join(` -`),{docsPath:e,name:`AbiConstructorNotFoundError`})}},Ht=class extends D{constructor({docsPath:e}){super(["Constructor arguments were provided (`args`), but a constructor parameters (`inputs`) were not found on the ABI.","Make sure you are using the correct ABI, and that the `inputs` attribute on the constructor exists."].join(` -`),{docsPath:e,name:`AbiConstructorParamsNotFoundError`})}},Ut=class extends D{constructor({data:e,params:t,size:n}){super([`Data size of ${n} bytes is too small for given parameters.`].join(` -`),{metaMessages:[`Params: (${At(t,{includeName:!0})})`,`Data: ${e} (${n} bytes)`],name:`AbiDecodingDataSizeTooSmallError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`params`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`size`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e,this.params=t,this.size=n}},Wt=class extends D{constructor(){super(`Cannot decode zero data ("0x") with ABI parameters.`,{name:`AbiDecodingZeroDataError`})}},Gt=class extends D{constructor({expectedLength:e,givenLength:t,type:n}){super([`ABI encoding array length mismatch for type ${n}.`,`Expected length: ${e}`,`Given length: ${t}`].join(` -`),{name:`AbiEncodingArrayLengthMismatchError`})}},Kt=class extends D{constructor({expectedSize:e,value:t}){super(`Size of bytes "${t}" (bytes${Ft(t)}) does not match expected size (bytes${e}).`,{name:`AbiEncodingBytesSizeMismatchError`})}},qt=class extends D{constructor({expectedLength:e,givenLength:t}){super([`ABI encoding params/values length mismatch.`,`Expected length (params): ${e}`,`Given length (values): ${t}`].join(` -`),{name:`AbiEncodingLengthMismatchError`})}},Jt=class extends D{constructor(e,{docsPath:t}){super([`Arguments (\`args\`) were provided to "${e}", but "${e}" on the ABI does not contain any parameters (\`inputs\`).`,`Cannot encode error result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the inputs exist on it.`].join(` -`),{docsPath:t,name:`AbiErrorInputsNotFoundError`})}},Yt=class extends D{constructor(e,{docsPath:t}={}){super([`Error ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`].join(` -`),{docsPath:t,name:`AbiErrorNotFoundError`})}},Xt=class extends D{constructor(e,{docsPath:t}){super([`Encoded error signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://openchain.xyz/signatures?query=${e}.`].join(` -`),{docsPath:t,name:`AbiErrorSignatureNotFoundError`}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.signature=e}},Zt=class extends D{constructor(e,{docsPath:t}={}){super([`Function ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` -`),{docsPath:t,name:`AbiFunctionNotFoundError`})}},Qt=class extends D{constructor(e,{docsPath:t}){super([`Function "${e}" does not contain any \`outputs\` on ABI.`,`Cannot decode function result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` -`),{docsPath:t,name:`AbiFunctionOutputsNotFoundError`})}},$t=class extends D{constructor(e,{docsPath:t}){super([`Encoded function signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`,`You can look up the signature here: https://openchain.xyz/signatures?query=${e}.`].join(` -`),{docsPath:t,name:`AbiFunctionSignatureNotFoundError`})}},en=class extends D{constructor(e,t){super(`Found ambiguous types in overloaded ABI items.`,{metaMessages:[`\`${e.type}\` in \`${kt(e.abiItem)}\`, and`,`\`${t.type}\` in \`${kt(t.abiItem)}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`],name:`AbiItemAmbiguityError`})}},tn=class extends D{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`,{name:`BytesSizeMismatchError`})}},nn=class extends D{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid encoding type.`,`Please provide a valid ABI type.`].join(` -`),{docsPath:t,name:`InvalidAbiEncodingType`})}},rn=class extends D{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid decoding type.`,`Please provide a valid ABI type.`].join(` -`),{docsPath:t,name:`InvalidAbiDecodingType`})}},an=class extends D{constructor(e){super([`Value "${e}" is not a valid array.`].join(` -`),{name:`InvalidArrayError`})}},on=class extends D{constructor(e){super([`"${e}" is not a valid definition type.`,`Valid types: "function", "event", "error"`].join(` -`),{name:`InvalidDefinitionTypeError`})}}})),cn,ln,un,dn=o((()=>{O(),cn=class extends D{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset "${e}" is out-of-bounds (size: ${n}).`,{name:`SliceOffsetOutOfBoundsError`})}},ln=class extends D{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (${e}) exceeds padding size (${t}).`,{name:`SizeExceedsPaddingSizeError`})}},un=class extends D{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} is expected to be ${t} ${n} long, but is ${e} ${n} long.`,{name:`InvalidBytesLengthError`})}}}));function fn(e,{dir:t,size:n=32}={}){return typeof e==`string`?pn(e,{dir:t,size:n}):mn(e,{dir:t,size:n})}function pn(e,{dir:t,size:n=32}={}){if(n===null)return e;let r=e.replace(`0x`,``);if(r.length>n*2)throw new ln({size:Math.ceil(r.length/2),targetSize:n,type:`hex`});return`0x${r[t===`right`?`padEnd`:`padStart`](n*2,`0`)}`}function mn(e,{dir:t,size:n=32}={}){if(n===null)return e;if(e.length>n)throw new ln({size:e.length,targetSize:n,type:`bytes`});let r=new Uint8Array(n);for(let i=0;i{dn()})),gn,_n,vn,yn,bn=o((()=>{O(),gn=class extends D{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number "${i}" is not in safe ${r?`${r*8}-bit ${n?`signed`:`unsigned`} `:``}integer range ${e?`(${t} to ${e})`:`(above ${t})`}`,{name:`IntegerOutOfRangeError`})}},_n=class extends D{constructor(e){super(`Bytes value "${e}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`,{name:`InvalidBytesBooleanError`})}},vn=class extends D{constructor(e){super(`Hex value "${e}" is not a valid boolean. The hex value must be "0x0" (false) or "0x1" (true).`,{name:`InvalidHexBooleanError`})}},yn=class extends D{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed ${t} bytes. Given size: ${e} bytes.`,{name:`SizeOverflowError`})}}}));function xn(e,{dir:t=`left`}={}){let n=typeof e==`string`?e.replace(`0x`,``):e,r=0;for(let e=0;e{}));function Cn(e,{size:t}){if(Ft(e)>t)throw new yn({givenSize:Ft(e),maxSize:t})}function wn(e,t={}){let{signed:n}=t;t.size&&Cn(e,{size:t.size});let r=BigInt(e);if(!n)return r;let i=(e.length-2)/2,a=(1n<{bn(),It(),Sn()}));function On(e,t={}){return typeof e==`number`||typeof e==`bigint`?k(e,t):typeof e==`string`?jn(e,t):typeof e==`boolean`?kn(e,t):An(e,t)}function kn(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(Cn(n,{size:t.size}),fn(n,{size:t.size})):n}function An(e,t={}){let n=``;for(let t=0;ta||i{bn(),hn(),Dn(),Mn=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),Nn=new TextEncoder}));function Pn(e,t={}){return typeof e==`number`||typeof e==`bigint`?Rn(e,t):typeof e==`boolean`?Fn(e,t):Nt(e)?Ln(e,t):zn(e,t)}function Fn(e,t={}){let n=new Uint8Array(1);return n[0]=Number(e),typeof t.size==`number`?(Cn(n,{size:t.size}),fn(n,{size:t.size})):n}function In(e){if(e>=Vn.zero&&e<=Vn.nine)return e-Vn.zero;if(e>=Vn.A&&e<=Vn.F)return e-(Vn.A-10);if(e>=Vn.a&&e<=Vn.f)return e-(Vn.a-10)}function Ln(e,t={}){let n=e;t.size&&(Cn(n,{size:t.size}),n=fn(n,{dir:`right`,size:t.size}));let r=n.slice(2);r.length%2&&(r=`0${r}`);let i=r.length/2,a=new Uint8Array(i);for(let e=0,t=0;e{O(),Pt(),hn(),Dn(),A(),Bn=new TextEncoder,Vn={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function Un(e,t=!1){return t?{h:Number(e&Kn),l:Number(e>>qn&Kn)}:{h:Number(e>>qn&Kn)|0,l:Number(e&Kn)|0}}function Wn(e,t=!1){let n=e.length,r=new Uint32Array(n),i=new Uint32Array(n);for(let a=0;a>>0)+(r>>>0);return{h:e+n+(i/2**32|0)|0,l:i|0}}var Kn,qn,Jn,Yn,Xn,Zn,Qn,$n,er,tr,nr,rr,ir,ar,or,sr,cr,lr,ur=o((()=>{Kn=BigInt(2**32-1),qn=BigInt(32),Jn=(e,t,n)=>e>>>n,Yn=(e,t,n)=>e<<32-n|t>>>n,Xn=(e,t,n)=>e>>>n|t<<32-n,Zn=(e,t,n)=>e<<32-n|t>>>n,Qn=(e,t,n)=>e<<64-n|t>>>n-32,$n=(e,t,n)=>e>>>n-32|t<<64-n,er=(e,t,n)=>e<>>32-n,tr=(e,t,n)=>t<>>32-n,nr=(e,t,n)=>t<>>64-n,rr=(e,t,n)=>e<>>64-n,ir=(e,t,n)=>(e>>>0)+(t>>>0)+(n>>>0),ar=(e,t,n,r)=>t+n+r+(e/2**32|0)|0,or=(e,t,n,r)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0),sr=(e,t,n,r,i)=>t+n+r+i+(e/2**32|0)|0,cr=(e,t,n,r,i)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0)+(i>>>0),lr=(e,t,n,r,i,a)=>t+n+r+i+a+(e/2**32|0)|0})),dr,fr=o((()=>{dr=typeof globalThis==`object`&&`crypto`in globalThis?globalThis.crypto:void 0}));function pr(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function mr(e){if(!Number.isSafeInteger(e)||e<0)throw Error(`positive integer expected, got `+e)}function hr(e,...t){if(!pr(e))throw Error(`Uint8Array expected`);if(t.length>0&&!t.includes(e.length))throw Error(`Uint8Array expected of length `+t+`, got length=`+e.length)}function gr(e){if(typeof e!=`function`||typeof e.create!=`function`)throw Error(`Hash should be wrapped by utils.createHasher`);mr(e.outputLen),mr(e.blockLen)}function _r(e,t=!0){if(e.destroyed)throw Error(`Hash instance has been destroyed`);if(t&&e.finished)throw Error(`Hash#digest() has already been called`)}function vr(e,t){hr(e);let n=t.outputLen;if(e.length>>t}function Cr(e){return e<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255}function wr(e){for(let t=0;te().update(Er(t)).digest(),n=e();return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=()=>e(),t}function kr(e=32){if(dr&&typeof dr.getRandomValues==`function`)return dr.getRandomValues(new Uint8Array(e));if(dr&&typeof dr.randomBytes==`function`)return Uint8Array.from(dr.randomBytes(e));throw Error(`crypto.getRandomValues must be defined`)}var Ar,jr,Mr,Nr=o((()=>{fr(),Ar=(()=>new Uint8Array(new Uint32Array([287454020]).buffer)[0]===68)(),jr=Ar?e=>e:wr,Mr=class{}}));function Pr(e,t=24){let n=new Uint32Array(10);for(let r=24-t;r<24;r++){for(let t=0;t<10;t++)n[t]=e[t]^e[t+10]^e[t+20]^e[t+30]^e[t+40];for(let t=0;t<10;t+=2){let r=(t+8)%10,i=(t+2)%10,a=n[i],o=n[i+1],s=qr(a,o,1)^n[r],c=Jr(a,o,1)^n[r+1];for(let n=0;n<50;n+=10)e[t+n]^=s,e[t+n+1]^=c}let t=e[2],i=e[3];for(let n=0;n<24;n++){let r=Hr[n],a=qr(t,i,r),o=Jr(t,i,r),s=Vr[n];t=e[s],i=e[s+1],e[s]=a,e[s+1]=o}for(let t=0;t<50;t+=10){for(let r=0;r<10;r++)n[r]=e[t+r];for(let r=0;r<10;r++)e[t+r]^=~n[(r+2)%10]&n[(r+4)%10]}e[0]^=Gr[r],e[1]^=Kr[r]}br(n)}var Fr,Ir,Lr,Rr,zr,Br,Vr,Hr,Ur,Wr,Gr,Kr,qr,Jr,Yr,Xr,Zr,Qr=o((()=>{ur(),Nr(),Fr=BigInt(0),Ir=BigInt(1),Lr=BigInt(2),Rr=BigInt(7),zr=BigInt(256),Br=BigInt(113),Vr=[],Hr=[],Ur=[];for(let e=0,t=Ir,n=1,r=0;e<24;e++){[n,r]=[r,(2*n+3*r)%5],Vr.push(2*(5*r+n)),Hr.push((e+1)*(e+2)/2%64);let i=Fr;for(let e=0;e<7;e++)t=(t<>Rr)*Br)%zr,t&Lr&&(i^=Ir<<(Ir<n>32?nr(e,t,n):er(e,t,n),Jr=(e,t,n)=>n>32?rr(e,t,n):tr(e,t,n),Yr=class e extends Mr{constructor(e,t,n,r=!1,i=24){if(super(),this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,this.enableXOF=!1,this.blockLen=e,this.suffix=t,this.outputLen=n,this.enableXOF=r,this.rounds=i,mr(n),!(0=n&&this.keccak();let a=Math.min(n-this.posOut,i-r);e.set(t.subarray(this.posOut,this.posOut+a),r),this.posOut+=a,r+=a}return e}xofInto(e){if(!this.enableXOF)throw Error(`XOF is not possible for this instance`);return this.writeInto(e)}xof(e){return mr(e),this.xofInto(new Uint8Array(e))}digestInto(e){if(vr(e,this),this.finished)throw Error(`digest() was already called`);return this.writeInto(e),this.destroy(),e}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,br(this.state)}_cloneInto(t){let{blockLen:n,suffix:r,outputLen:i,rounds:a,enableXOF:o}=this;return t||=new e(n,r,i,o,a),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=a,t.suffix=r,t.outputLen=i,t.enableXOF=o,t.destroyed=this.destroyed,t}},Xr=(e,t,n)=>Or(()=>new Yr(t,e,n)),Zr=(()=>Xr(1,136,256/8))()}));function $r(e,t){let n=t||`hex`,r=Zr(Nt(e,{strict:!1})?Pn(e):e);return n===`bytes`?r:On(r)}var ei=o((()=>{Qr(),Pt(),Hn(),A()}));function ti(e){return ni(e)}var ni,ri=o((()=>{Hn(),ei(),ni=e=>$r(Pn(e))}));function ii(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;o{O()})),oi,si=o((()=>{Ot(),ai(),oi=e=>{let t=(()=>typeof e==`string`?e:le(e))();return ii(t)}}));function ci(e){return ti(oi(e))}var li=o((()=>{ri(),si()})),ui,di=o((()=>{li(),ui=ci})),fi,pi=o((()=>{O(),fi=class extends D{constructor({address:e}){super(`Address "${e}" is invalid.`,{metaMessages:[`- Address must be a hex value of 20 bytes (40 hex characters).`,`- Address must match its checksum counterpart.`],name:`InvalidAddressError`})}}})),mi,hi=o((()=>{mi=class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&t!==void 0&&(this.delete(e),super.set(e,t)),t}set(e,t){if(super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}}));function gi(e,t){if(vi.has(`${e}.${t}`))return vi.get(`${e}.${t}`);let n=t?`${t}${e.toLowerCase()}`:e.substring(2).toLowerCase(),r=$r(zn(n),`bytes`),i=(t?n.substring(`${t}0x`.length):n).split(``);for(let e=0;e<40;e+=2)r[e>>1]>>4>=8&&i[e]&&(i[e]=i[e].toUpperCase()),(r[e>>1]&15)>=8&&i[e+1]&&(i[e+1]=i[e+1].toUpperCase());let a=`0x${i.join(``)}`;return vi.set(`${e}.${t}`,a),a}function _i(e,t){if(!bi(e,{strict:!1}))throw new fi({address:e});return gi(e,t)}var vi,yi=o((()=>{pi(),Hn(),ei(),hi(),Ci(),vi=new mi(8192)}));function bi(e,t){let{strict:n=!0}=t??{},r=`${e}.${n}`;if(Si.has(r))return Si.get(r);let i=(()=>xi.test(e)?e.toLowerCase()===e?!0:n?gi(e)===e:!0:!1)();return Si.set(r,i),i}var xi,Si,Ci=o((()=>{hi(),yi(),xi=/^0x[a-fA-F0-9]{40}$/,Si=new mi(8192)}));function wi(e){return typeof e[0]==`string`?Ei(e):Ti(e)}function Ti(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}function Ei(e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}var Di=o((()=>{}));function Oi(e,t,n,{strict:r}={}){return Nt(e,{strict:!1})?Mi(e,t,n,{strict:r}):ji(e,t,n,{strict:r})}function ki(e,t){if(typeof t==`number`&&t>0&&t>Ft(e)-1)throw new cn({offset:t,position:`start`,size:Ft(e)})}function Ai(e,t,n){if(typeof t==`number`&&typeof n==`number`&&Ft(e)!==n-t)throw new cn({offset:n,position:`end`,size:Ft(e)})}function ji(e,t,n,{strict:r}={}){ki(e,t);let i=e.slice(t,n);return r&&Ai(i,t,n),i}function Mi(e,t,n,{strict:r}={}){ki(e,t);let i=`0x${e.replace(`0x`,``).slice((t??0)*2,(n??e.length)*2)}`;return r&&Ai(i,t,n),i}var Ni=o((()=>{dn(),Pt(),It()})),Pi,Fi,Ii=o((()=>{Pi=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,Fi=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/}));function Li(e,t){if(e.length!==t.length)throw new qt({expectedLength:e.length,givenLength:t.length});let n=Ri({params:e,values:t}),r=Bi(n);return r.length===0?`0x`:r}function Ri({params:e,values:t}){let n=[];for(let r=0;r0?wi([t,e]):t}}if(i)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:wi(a.map(({encoded:e})=>e))}}function Ui(e,{param:t}){let[,n]=t.type.split(`bytes`),r=Ft(e);if(!n){let t=e;return r%32!=0&&(t=pn(t,{dir:`right`,size:Math.ceil((e.length-2)/2/32)*32})),{dynamic:!0,encoded:wi([pn(k(r,{size:32})),t])}}if(r!==Number.parseInt(n,10))throw new Kt({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:pn(e,{dir:`right`})}}function Wi(e){if(typeof e!=`boolean`)throw new D(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:pn(kn(e))}}function Gi(e,{signed:t,size:n=256}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Ji(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}var Yi=o((()=>{sn(),pi(),O(),bn(),Ci(),Di(),hn(),It(),Ni(),A(),Ii()})),Xi,Zi=o((()=>{Ni(),li(),Xi=e=>Oi(ci(e),0,4)}));function Qi(e){let{abi:t,args:n=[],name:r}=e,i=Nt(r,{strict:!1}),a=t.filter(e=>i?e.type===`function`?Xi(e)===r:e.type===`event`?ui(e)===r:!1:`name`in e&&e.name===r);if(a.length===0)return;if(a.length===1)return a[0];let o;for(let e of a)if(`inputs`in e){if(!n||n.length===0){if(!e.inputs||e.inputs.length===0)return e;continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===n.length&&n.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?$i(t,r):!1})){if(o&&`inputs`in o&&o.inputs){let t=ea(e.inputs,o.inputs,n);if(t)throw new en({abiItem:e,type:t[0]},{abiItem:o,type:t[1]})}o=e}}return o||a[0]}function $i(e,t){let n=typeof e,r=t.type;switch(r){case`address`:return bi(e,{strict:!1});case`bool`:return n===`boolean`;case`function`:return n===`string`;case`string`:return n===`string`;default:return r===`tuple`&&`components`in t?Object.values(t.components).every((t,n)=>$i(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>$i(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function ea(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return ea(i.components,a.components,n[r]);let o=[i.type,a.type];if((()=>o.includes(`address`)&&o.includes(`bytes20`)?!0:o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`)?bi(n[r],{strict:!1}):!1)())return o}}var ta=o((()=>{sn(),Pt(),Ci(),di(),Zi()}));function na(e){return typeof e==`string`?{address:e,type:`json-rpc`}:e}var ra=o((()=>{}));function ia(e){let{abi:t,args:n,functionName:r}=e,i=t[0];if(r){let e=Qi({abi:t,args:n,name:r});if(!e)throw new Zt(r,{docsPath:aa});i=e}if(i.type!==`function`)throw new Zt(void 0,{docsPath:aa});return{abi:[i],functionName:Xi(kt(i))}}var aa,oa=o((()=>{sn(),Zi(),Mt(),ta(),aa=`/docs/contract/encodeFunctionData`}));function sa(e){let{args:t}=e,{abi:n,functionName:r}=(()=>e.abi.length===1&&e.functionName?.startsWith(`0x`)?e:ia(e))(),i=n[0],a=r,o=`inputs`in i&&i.inputs?Li(i.inputs,t??[]):void 0;return Ei([a,o??`0x`])}var ca=o((()=>{Di(),Yi(),oa()})),la,ua,da,fa=o((()=>{la={1:"An `assert` condition failed.",17:`Arithmetic operation resulted in underflow or overflow.`,18:"Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).",33:`Attempted to convert to an invalid type.`,34:`Attempted to access a storage byte array that is incorrectly encoded.`,49:"Performed `.pop()` on an empty array",50:`Array index is out of bounds.`,65:`Allocated too much memory or created an array which is too large.`,81:`Attempted to call a zero-initialized variable of internal function type.`},ua={inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`},da={inputs:[{name:`reason`,type:`uint256`}],name:`Panic`,type:`error`}})),pa,ma,ha,ga=o((()=>{O(),pa=class extends D{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`,{name:`NegativeOffsetError`})}},ma=class extends D{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`,{name:`PositionOutOfBoundsError`})}},ha=class extends D{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`,{name:`RecursiveReadLimitExceededError`})}}}));function _a(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(va);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var va,ya=o((()=>{ga(),va={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new ha({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new ma({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new pa({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new pa({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}}}));function ba(e,t={}){t.size!==void 0&&Cn(e,{size:t.size});let n=An(e,t);return wn(n,t)}function xa(e,t={}){let n=e;if(t.size!==void 0&&(Cn(n,{size:t.size}),n=xn(n)),n.length>1||n[0]>1)throw new _n(n);return!!n[0]}function Sa(e,t={}){t.size!==void 0&&Cn(e,{size:t.size});let n=An(e,t);return En(n,t)}function Ca(e,t={}){let n=e;return t.size!==void 0&&(Cn(n,{size:t.size}),n=xn(n,{dir:`right`})),new TextDecoder().decode(n)}var wa=o((()=>{bn(),Sn(),Dn(),A()}));function Ta(e,t){let n=typeof t==`string`?Ln(t):t,r=_a(n);if(Ft(n)===0&&e.length>0)throw new Wt;if(Ft(t)&&Ft(t)<32)throw new Ut({data:typeof t==`string`?t:An(t),params:e,size:Ft(t)});let i=0,a=[];for(let t=0;t48?ba(i,{signed:n}):Sa(i,{signed:n}),32]}function Ma(e,t,{staticPosition:n}){let r=t.components.length===0||t.components.some(({name:e})=>!e),i=r?[]:{},a=0;if(Pa(t)){let o=Sa(e.readBytes(Ia)),s=n+o;for(let n=0;n{sn(),yi(),ya(),It(),Ni(),Sn(),wa(),Hn(),A(),Yi(),Fa=32,Ia=32}));function Ra(e){let{abi:t,data:n}=e,r=Oi(n,0,4);if(r===`0x`)throw new Wt;let i=[...t||[],ua,da].find(e=>e.type===`error`&&r===Xi(kt(e)));if(!i)throw new Xt(r,{docsPath:`/docs/contract/decodeErrorResult`});return{abiItem:i,args:`inputs`in i&&i.inputs&&i.inputs.length>0?Ta(i.inputs,Oi(n,4)):void 0,errorName:i.name}}var za=o((()=>{fa(),sn(),Ni(),Zi(),La(),Mt()})),Ba,Va=o((()=>{Ba=(e,t,n)=>JSON.stringify(e,(e,n)=>{let r=typeof n==`bigint`?n.toString():n;return typeof t==`function`?t(e,r):r},n)}));function Ha({abiItem:e,args:t,includeFunctionName:n=!0,includeName:r=!1}){if(`name`in e&&`inputs`in e&&e.inputs)return`${n?e.name:``}(${e.inputs.map((e,n)=>`${r&&e.name?`${e.name}: `:``}${typeof t[n]==`object`?Ba(t[n]):t[n]}`).join(`, `)})`}var Ua=o((()=>{Va()})),Wa,Ga,Ka=o((()=>{Wa={gwei:9,wei:18},Ga={ether:-9,wei:9}}));function qa(e,t){let n=e.toString(),r=n.startsWith(`-`);r&&(n=n.slice(1)),n=n.padStart(t,`0`);let[i,a]=[n.slice(0,n.length-t),n.slice(n.length-t)];return a=a.replace(/(0+)$/,``),`${r?`-`:``}${i||`0`}${a?`.${a}`:``}`}var Ja=o((()=>{}));function Ya(e,t=`wei`){return qa(e,Wa[t])}var Xa=o((()=>{Ka(),Ja()}));function Za(e,t=`wei`){return qa(e,Ga[t])}var Qa=o((()=>{Ka(),Ja()}));function $a(e){return e.reduce((e,{slot:t,value:n})=>`${e} ${t}: ${n}\n`,``)}function eo(e){return e.reduce((e,{address:t,...n})=>{let r=`${e} ${t}:\n`;return n.nonce&&(r+=` nonce: ${n.nonce}\n`),n.balance&&(r+=` balance: ${n.balance}\n`),n.code&&(r+=` code: ${n.code}\n`),n.state&&(r+=` state: -`,r+=$a(n.state)),n.stateDiff&&(r+=` stateDiff: -`,r+=$a(n.stateDiff)),r},` State Override: -`).slice(0,-1)}var to,no,ro=o((()=>{O(),to=class extends D{constructor({address:e}){super(`State for account "${e}" is set multiple times.`,{name:`AccountStateConflictError`})}},no=class extends D{constructor(){super(`state and stateDiff are set on the same account.`,{name:`StateAssignmentConflictError`})}}}));function io(e){let t=Object.entries(e).map(([e,t])=>t===void 0||t===!1?null:[e,t]).filter(Boolean),n=t.reduce((e,[t])=>Math.max(e,t.length),0);return t.map(([e,t])=>` ${`${e}:`.padEnd(n+1)} ${t}`).join(` -`)}var ao,oo,so,co,lo,uo,fo,po,mo,ho=o((()=>{Xa(),Qa(),O(),ao=class extends D{constructor(){super(["Cannot specify both a `gasPrice` and a `maxFeePerGas`/`maxPriorityFeePerGas`.","Use `maxFeePerGas`/`maxPriorityFeePerGas` for EIP-1559 compatible networks, and `gasPrice` for others."].join(` -`),{name:`FeeConflictError`})}},oo=class extends D{constructor({v:e}){super(`Invalid \`v\` value "${e}". Expected 27 or 28.`,{name:`InvalidLegacyVError`})}},so=class extends D{constructor({transaction:e}){super(`Cannot infer a transaction type from provided transaction.`,{metaMessages:[`Provided Transaction:`,`{`,io(e),`}`,``,`To infer the type, either provide:`,"- a `type` to the Transaction, or","- an EIP-1559 Transaction with `maxFeePerGas`, or","- an EIP-2930 Transaction with `gasPrice` & `accessList`, or","- an EIP-4844 Transaction with `blobs`, `blobVersionedHashes`, `sidecars`, or","- an EIP-7702 Transaction with `authorizationList`, or","- a Legacy Transaction with `gasPrice`"],name:`InvalidSerializableTransactionError`})}},co=class extends D{constructor({storageKey:e}){super(`Size for storage key "${e}" is invalid. Expected 32 bytes. Got ${Math.floor((e.length-2)/2)} bytes.`,{name:`InvalidStorageKeySizeError`})}},lo=class extends D{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=io({chain:r&&`${r?.name} (id: ${r?.id})`,from:t?.address,to:u,value:d!==void 0&&`${Ya(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${Za(o)} gwei`,maxFeePerGas:s!==void 0&&`${Za(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${Za(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Request Arguments:`,f].filter(Boolean),name:`TransactionExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},uo=class extends D{constructor({blockHash:e,blockNumber:t,blockTag:n,hash:r,index:i}){let a=`Transaction`;n&&i!==void 0&&(a=`Transaction at block time "${n}" at index "${i}"`),e&&i!==void 0&&(a=`Transaction at block hash "${e}" at index "${i}"`),t&&i!==void 0&&(a=`Transaction at block number "${t}" at index "${i}"`),r&&(a=`Transaction with hash "${r}"`),super(`${a} could not be found.`,{name:`TransactionNotFoundError`})}},fo=class extends D{constructor({hash:e}){super(`Transaction receipt with hash "${e}" could not be found. The Transaction may not be processed on a block yet.`,{name:`TransactionReceiptNotFoundError`})}},po=class extends D{constructor({receipt:e}){super(`Transaction with hash "${e.transactionHash}" reverted.`,{metaMessages:[`The receipt marked the transaction as "reverted". This could mean that the function on the contract you are trying to call threw an error.`,` `,`You can attempt to extract the revert reason by:`,"- calling the `simulateContract` or `simulateCalls` Action with the `abi` and `functionName` of the contract","- using the `call` Action with raw `data`"],name:`TransactionReceiptRevertedError`}),Object.defineProperty(this,`receipt`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.receipt=e}},mo=class extends D{constructor({hash:e}){super(`Timed out while waiting for transaction with hash "${e}" to be confirmed.`,{name:`WaitForTransactionReceiptTimeoutError`})}}})),go,_o,vo=o((()=>{go=e=>e,_o=e=>e})),yo,bo,xo,So,Co,wo,To=o((()=>{ra(),fa(),za(),Mt(),Ua(),ta(),Xa(),Qa(),sn(),O(),ro(),ho(),vo(),yo=class extends D{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d,stateOverride:f}){let p=t?na(t):void 0,m=io({from:p?.address,to:u,value:d!==void 0&&`${Ya(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${Za(o)} gwei`,maxFeePerGas:s!==void 0&&`${Za(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${Za(c)} gwei`,nonce:l});f&&(m+=`\n${eo(f)}`),super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Raw Call Arguments:`,m].filter(Boolean),name:`CallExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},bo=class extends D{constructor(e,{abi:t,args:n,contractAddress:r,docsPath:i,functionName:a,sender:o}){let s=Qi({abi:t,args:n,name:a}),c=s?Ha({abiItem:s,args:n,includeFunctionName:!1,includeName:!1}):void 0,l=s?kt(s,{includeName:!0}):void 0,u=io({address:r&&go(r),function:l,args:c&&c!==`()`&&`${[...Array(a?.length??0).keys()].map(()=>` `).join(``)}${c}`,sender:o});super(e.shortMessage||`An unknown error occurred while executing the contract function "${a}".`,{cause:e,docsPath:i,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],u&&`Contract Call:`,u].filter(Boolean),name:`ContractFunctionExecutionError`}),Object.defineProperty(this,`abi`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`args`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`contractAddress`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`formattedArgs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`functionName`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`sender`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abi=t,this.args=n,this.cause=e,this.contractAddress=r,this.functionName=a,this.sender=o}},xo=class extends D{constructor({abi:e,data:t,functionName:n,message:r}){let i,a,o,s;if(t&&t!==`0x`)try{a=Ra({abi:e,data:t});let{abiItem:n,errorName:r,args:i}=a;if(r===`Error`)s=i[0];else if(r===`Panic`){let[e]=i;s=la[e]}else{let e=n?kt(n,{includeName:!0}):void 0,t=n&&i?Ha({abiItem:n,args:i,includeFunctionName:!1,includeName:!1}):void 0;o=[e?`Error: ${e}`:``,t&&t!==`()`?` ${[...Array(r?.length??0).keys()].map(()=>` `).join(``)}${t}`:``]}}catch(e){i=e}else r&&(s=r);let c;i instanceof Xt&&(c=i.signature,o=[`Unable to decode signature "${c}" as it was not found on the provided ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://openchain.xyz/signatures?query=${c}.`]),super(s&&s!==`execution reverted`||c?[`The contract function "${n}" reverted with the following ${c?`signature`:`reason`}:`,s||c].join(` -`):`The contract function "${n}" reverted.`,{cause:i,metaMessages:o,name:`ContractFunctionRevertedError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`raw`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`reason`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=a,this.raw=t,this.reason=s,this.signature=c}},So=class extends D{constructor({functionName:e}){super(`The contract function "${e}" returned no data ("0x").`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function "${e}",`,` - The parameters passed to the contract function may be invalid, or`,` - The address is not a contract.`],name:`ContractFunctionZeroDataError`})}},Co=class extends D{constructor({factory:e}){super(`Deployment for counterfactual contract call failed${e?` for factory "${e}".`:``}`,{metaMessages:[`Please ensure:`,"- The `factory` is a valid contract deployment factory (ie. Create2 Factory, ERC-4337 Factory, etc).","- The `factoryData` is a valid encoded function call for contract deployment function on the factory."],name:`CounterfactualDeploymentFailedError`})}},wo=class extends D{constructor({data:e,message:t}){super(t||``,{name:`RawContractError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e}}})),Eo,Do,Oo,ko=o((()=>{Va(),O(),vo(),Eo=class extends D{constructor({body:e,cause:t,details:n,headers:r,status:i,url:a}){super(`HTTP request failed.`,{cause:t,details:n,metaMessages:[i&&`Status: ${i}`,`URL: ${_o(a)}`,e&&`Request body: ${Ba(e)}`].filter(Boolean),name:`HttpRequestError`}),Object.defineProperty(this,`body`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`headers`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`status`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.body=e,this.headers=r,this.status=i,this.url=a}},Do=class extends D{constructor({body:e,error:t,url:n}){super(`RPC Request failed.`,{cause:t,details:t.message,metaMessages:[`URL: ${_o(n)}`,`Request body: ${Ba(e)}`],name:`RpcRequestError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=t.code,this.data=t.data}},Oo=class extends D{constructor({body:e,url:t}){super(`The request took too long to respond.`,{details:`The request timed out.`,metaMessages:[`URL: ${_o(t)}`,`Request body: ${Ba(e)}`],name:`TimeoutError`})}}})),Ao,jo,Mo,No,j,Po,Fo,Io,Lo,Ro,zo,Bo,Vo,Ho,Uo,Wo,Go,Ko,qo,Jo,Yo,Xo,Zo,Qo,$o,es,ts,ns,rs,os=o((()=>{O(),ko(),Ao=-1,jo=class extends D{constructor(e,{code:t,docsPath:n,metaMessages:r,name:i,shortMessage:a}){super(a,{cause:e,docsPath:n,metaMessages:r||e?.metaMessages,name:i||`RpcError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name=i||e.name,this.code=e instanceof Do?e.code:t??Ao}},Mo=class extends jo{constructor(e,t){super(e,t),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=t.data}},No=class e extends jo{constructor(t){super(t,{code:e.code,name:`ParseRpcError`,shortMessage:`Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.`})}},Object.defineProperty(No,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700}),j=class e extends jo{constructor(t){super(t,{code:e.code,name:`InvalidRequestRpcError`,shortMessage:`JSON is not a valid request object.`})}},Object.defineProperty(j,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600}),Po=class e extends jo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotFoundRpcError`,shortMessage:`The method${n?` "${n}"`:``} does not exist / is not available.`})}},Object.defineProperty(Po,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601}),Fo=class e extends jo{constructor(t){super(t,{code:e.code,name:`InvalidParamsRpcError`,shortMessage:[`Invalid parameters were provided to the RPC method.`,`Double check you have provided the correct parameters.`].join(` -`)})}},Object.defineProperty(Fo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602}),Io=class e extends jo{constructor(t){super(t,{code:e.code,name:`InternalRpcError`,shortMessage:`An internal error was received.`})}},Object.defineProperty(Io,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603}),Lo=class e extends jo{constructor(t){super(t,{code:e.code,name:`InvalidInputRpcError`,shortMessage:[`Missing or invalid parameters.`,`Double check you have provided the correct parameters.`].join(` -`)})}},Object.defineProperty(Lo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3}),Ro=class e extends jo{constructor(t){super(t,{code:e.code,name:`ResourceNotFoundRpcError`,shortMessage:`Requested resource not found.`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`ResourceNotFoundRpcError`})}},Object.defineProperty(Ro,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001}),zo=class e extends jo{constructor(t){super(t,{code:e.code,name:`ResourceUnavailableRpcError`,shortMessage:`Requested resource not available.`})}},Object.defineProperty(zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002}),Bo=class e extends jo{constructor(t){super(t,{code:e.code,name:`TransactionRejectedRpcError`,shortMessage:`Transaction creation failed.`})}},Object.defineProperty(Bo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003}),Vo=class e extends jo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotSupportedRpcError`,shortMessage:`Method${n?` "${n}"`:``} is not supported.`})}},Object.defineProperty(Vo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004}),Ho=class e extends jo{constructor(t){super(t,{code:e.code,name:`LimitExceededRpcError`,shortMessage:`Request exceeds defined limit.`})}},Object.defineProperty(Ho,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005}),Uo=class e extends jo{constructor(t){super(t,{code:e.code,name:`JsonRpcVersionUnsupportedError`,shortMessage:`Version of JSON-RPC protocol is not supported.`})}},Object.defineProperty(Uo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006}),Wo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`UserRejectedRequestError`,shortMessage:`User rejected the request.`})}},Object.defineProperty(Wo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001}),Go=class e extends Mo{constructor(t){super(t,{code:e.code,name:`UnauthorizedProviderError`,shortMessage:`The requested method and/or account has not been authorized by the user.`})}},Object.defineProperty(Go,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100}),Ko=class e extends Mo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`UnsupportedProviderMethodError`,shortMessage:`The Provider does not support the requested method${n?` " ${n}"`:``}.`})}},Object.defineProperty(Ko,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200}),qo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ProviderDisconnectedError`,shortMessage:`The Provider is disconnected from all chains.`})}},Object.defineProperty(qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900}),Jo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ChainDisconnectedError`,shortMessage:`The Provider is not connected to the requested chain.`})}},Object.defineProperty(Jo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901}),Yo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`SwitchChainError`,shortMessage:`An error occurred when attempting to switch chain.`})}},Object.defineProperty(Yo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902}),Xo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`UnsupportedNonOptionalCapabilityError`,shortMessage:`This Wallet does not support a capability that was not marked as optional.`})}},Object.defineProperty(Xo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700}),Zo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`UnsupportedChainIdError`,shortMessage:`This Wallet does not support the requested chain ID.`})}},Object.defineProperty(Zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710}),Qo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`DuplicateIdError`,shortMessage:`There is already a bundle submitted with this ID.`})}},Object.defineProperty(Qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720}),$o=class e extends Mo{constructor(t){super(t,{code:e.code,name:`UnknownBundleIdError`,shortMessage:`This bundle id is unknown / has not been submitted`})}},Object.defineProperty($o,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730}),es=class e extends Mo{constructor(t){super(t,{code:e.code,name:`BundleTooLargeError`,shortMessage:`The call bundle is too large for the Wallet to process.`})}},Object.defineProperty(es,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740}),ts=class e extends Mo{constructor(t){super(t,{code:e.code,name:`AtomicReadyWalletRejectedUpgradeError`,shortMessage:`The Wallet can support atomicity after an upgrade, but the user rejected the upgrade.`})}},Object.defineProperty(ts,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750}),ns=class e extends Mo{constructor(t){super(t,{code:e.code,name:`AtomicityNotSupportedError`,shortMessage:`The wallet does not support atomic execution but the request requires it.`})}},Object.defineProperty(ns,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760}),rs=class extends jo{constructor(e){super(e,{name:`UnknownRpcError`,shortMessage:`An unknown RPC error occurred.`})}}}));sn(),O(),To(),ko(),os();var ss=3;function cs(e,{abi:t,address:n,args:r,docsPath:i,functionName:a,sender:o}){let s=e instanceof wo?e:e instanceof D?e.walk(e=>`data`in e)||e.walk():{},{code:c,data:l,details:u,message:d,shortMessage:f}=s,p=(()=>e instanceof Wt?new So({functionName:a}):[ss,Io.code].includes(c)&&(l||u||d||f)?new xo({abi:t,data:typeof l==`object`?l.data:l,functionName:a,message:s instanceof Do?u:f??d}):e)();return new bo(p,{abi:t,args:r,contractAddress:n,docsPath:i,functionName:a,sender:o})}yi(),ei();function ls(e){let t=$r(`0x${e.substring(4)}`).substring(26);return gi(`0x${t}`)}var us,ds,fs,ps,ms=o((()=>{us=(function(){let e=typeof document<`u`&&document.createElement(`link`).relList;return e&&e.supports&&e.supports(`modulepreload`)?`modulepreload`:`preload`})(),ds=function(e){return`/`+e},fs={},ps=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=ds(t,n),t in fs)return;fs[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:us,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})}}));function hs(e,t,n,r){if(typeof e.setBigUint64==`function`)return e.setBigUint64(t,n,r);let i=BigInt(32),a=BigInt(4294967295),o=Number(n>>i&a),s=Number(n&a),c=r?4:0,l=r?0:4;e.setUint32(t+c,o,r),e.setUint32(t+l,s,r)}function gs(e,t,n){return e&t^~e&n}function _s(e,t,n){return e&t^e&n^t&n}var vs,ys,bs,xs,Ss=o((()=>{Nr(),vs=class extends Mr{constructor(e,t,n,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=t,this.padOffset=n,this.isLE=r,this.buffer=new Uint8Array(e),this.view=xr(this.buffer)}update(e){_r(this),e=Er(e),hr(e);let{view:t,buffer:n,blockLen:r}=this,i=e.length;for(let a=0;ar-a&&(this.process(n,0),a=0);for(let e=a;el.length)throw Error(`_sha2: outputLen bigger than state`);for(let e=0;e{Ss(),ur(),Nr(),Cs=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),ws=new Uint32Array(64),Ts=class extends vs{constructor(e=32){super(64,e,8,!1),this.A=ys[0]|0,this.B=ys[1]|0,this.C=ys[2]|0,this.D=ys[3]|0,this.E=ys[4]|0,this.F=ys[5]|0,this.G=ys[6]|0,this.H=ys[7]|0}get(){let{A:e,B:t,C:n,D:r,E:i,F:a,G:o,H:s}=this;return[e,t,n,r,i,a,o,s]}set(e,t,n,r,i,a,o,s){this.A=e|0,this.B=t|0,this.C=n|0,this.D=r|0,this.E=i|0,this.F=a|0,this.G=o|0,this.H=s|0}process(e,t){for(let n=0;n<16;n++,t+=4)ws[n]=e.getUint32(t,!1);for(let e=16;e<64;e++){let t=ws[e-15],n=ws[e-2],r=Sr(t,7)^Sr(t,18)^t>>>3;ws[e]=(Sr(n,17)^Sr(n,19)^n>>>10)+ws[e-7]+r+ws[e-16]|0}let{A:n,B:r,C:i,D:a,E:o,F:s,G:c,H:l}=this;for(let e=0;e<64;e++){let t=Sr(o,6)^Sr(o,11)^Sr(o,25),u=l+t+gs(o,s,c)+Cs[e]+ws[e]|0,d=(Sr(n,2)^Sr(n,13)^Sr(n,22))+_s(n,r,i)|0;l=c,c=s,s=o,o=a+u|0,a=i,i=r,r=n,n=u+d|0}n=n+this.A|0,r=r+this.B|0,i=i+this.C|0,a=a+this.D|0,o=o+this.E|0,s=s+this.F|0,c=c+this.G|0,l=l+this.H|0,this.set(n,r,i,a,o,s,c,l)}roundClean(){br(ws)}destroy(){this.set(0,0,0,0,0,0,0,0),br(this.buffer)}},Es=(()=>Wn(`0x428a2f98d728ae22.0x7137449123ef65cd.0xb5c0fbcfec4d3b2f.0xe9b5dba58189dbbc.0x3956c25bf348b538.0x59f111f1b605d019.0x923f82a4af194f9b.0xab1c5ed5da6d8118.0xd807aa98a3030242.0x12835b0145706fbe.0x243185be4ee4b28c.0x550c7dc3d5ffb4e2.0x72be5d74f27b896f.0x80deb1fe3b1696b1.0x9bdc06a725c71235.0xc19bf174cf692694.0xe49b69c19ef14ad2.0xefbe4786384f25e3.0x0fc19dc68b8cd5b5.0x240ca1cc77ac9c65.0x2de92c6f592b0275.0x4a7484aa6ea6e483.0x5cb0a9dcbd41fbd4.0x76f988da831153b5.0x983e5152ee66dfab.0xa831c66d2db43210.0xb00327c898fb213f.0xbf597fc7beef0ee4.0xc6e00bf33da88fc2.0xd5a79147930aa725.0x06ca6351e003826f.0x142929670a0e6e70.0x27b70a8546d22ffc.0x2e1b21385c26c926.0x4d2c6dfc5ac42aed.0x53380d139d95b3df.0x650a73548baf63de.0x766a0abb3c77b2a8.0x81c2c92e47edaee6.0x92722c851482353b.0xa2bfe8a14cf10364.0xa81a664bbc423001.0xc24b8b70d0f89791.0xc76c51a30654be30.0xd192e819d6ef5218.0xd69906245565a910.0xf40e35855771202a.0x106aa07032bbd1b8.0x19a4c116b8d2d0c8.0x1e376c085141ab53.0x2748774cdf8eeb99.0x34b0bcb5e19b48a8.0x391c0cb3c5c95a63.0x4ed8aa4ae3418acb.0x5b9cca4f7763e373.0x682e6ff3d6b2b8a3.0x748f82ee5defb2fc.0x78a5636f43172f60.0x84c87814a1f0ab72.0x8cc702081a6439ec.0x90befffa23631e28.0xa4506cebde82bde9.0xbef9a3f7b2c67915.0xc67178f2e372532b.0xca273eceea26619c.0xd186b8c721c0c207.0xeada7dd6cde0eb1e.0xf57d4f7fee6ed178.0x06f067aa72176fba.0x0a637dc5a2c898a6.0x113f9804bef90dae.0x1b710b35131c471b.0x28db77f523047d84.0x32caab7b40c72493.0x3c9ebe0a15c9bebc.0x431d67c49c100d4c.0x4cc5d4becb3e42b6.0x597f299cfc657e2a.0x5fcb6fab3ad6faec.0x6c44198c4a475817`.split(`.`).map(e=>BigInt(e))))(),Ds=(()=>Es[0])(),Os=(()=>Es[1])(),ks=new Uint32Array(80),As=new Uint32Array(80),js=class extends vs{constructor(e=64){super(128,e,16,!1),this.Ah=xs[0]|0,this.Al=xs[1]|0,this.Bh=xs[2]|0,this.Bl=xs[3]|0,this.Ch=xs[4]|0,this.Cl=xs[5]|0,this.Dh=xs[6]|0,this.Dl=xs[7]|0,this.Eh=xs[8]|0,this.El=xs[9]|0,this.Fh=xs[10]|0,this.Fl=xs[11]|0,this.Gh=xs[12]|0,this.Gl=xs[13]|0,this.Hh=xs[14]|0,this.Hl=xs[15]|0}get(){let{Ah:e,Al:t,Bh:n,Bl:r,Ch:i,Cl:a,Dh:o,Dl:s,Eh:c,El:l,Fh:u,Fl:d,Gh:f,Gl:p,Hh:m,Hl:h}=this;return[e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h]}set(e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h){this.Ah=e|0,this.Al=t|0,this.Bh=n|0,this.Bl=r|0,this.Ch=i|0,this.Cl=a|0,this.Dh=o|0,this.Dl=s|0,this.Eh=c|0,this.El=l|0,this.Fh=u|0,this.Fl=d|0,this.Gh=f|0,this.Gl=p|0,this.Hh=m|0,this.Hl=h|0}process(e,t){for(let n=0;n<16;n++,t+=4)ks[n]=e.getUint32(t),As[n]=e.getUint32(t+=4);for(let e=16;e<80;e++){let t=ks[e-15]|0,n=As[e-15]|0,r=Xn(t,n,1)^Xn(t,n,8)^Jn(t,n,7),i=Zn(t,n,1)^Zn(t,n,8)^Yn(t,n,7),a=ks[e-2]|0,o=As[e-2]|0,s=Xn(a,o,19)^Qn(a,o,61)^Jn(a,o,6),c=Zn(a,o,19)^$n(a,o,61)^Yn(a,o,6),l=or(i,c,As[e-7],As[e-16]);ks[e]=sr(l,r,s,ks[e-7],ks[e-16])|0,As[e]=l|0}let{Ah:n,Al:r,Bh:i,Bl:a,Ch:o,Cl:s,Dh:c,Dl:l,Eh:u,El:d,Fh:f,Fl:p,Gh:m,Gl:h,Hh:g,Hl:_}=this;for(let e=0;e<80;e++){let t=Xn(u,d,14)^Xn(u,d,18)^Qn(u,d,41),v=Zn(u,d,14)^Zn(u,d,18)^$n(u,d,41),y=u&f^~u&m,b=d&p^~d&h,x=cr(_,v,b,Os[e],As[e]),S=lr(x,g,t,y,Ds[e],ks[e]),C=x|0,w=Xn(n,r,28)^Qn(n,r,34)^Qn(n,r,39),ee=Zn(n,r,28)^$n(n,r,34)^$n(n,r,39),te=n&i^n&o^i&o,ne=r&a^r&s^a&s;g=m|0,_=h|0,m=f|0,h=p|0,f=u|0,p=d|0,{h:u,l:d}=Gn(c|0,l|0,S|0,C|0),c=o|0,l=s|0,o=i|0,s=a|0,i=n|0,a=r|0;let re=ir(C,ee,ne);n=ar(re,S,w,te),r=re|0}({h:n,l:r}=Gn(this.Ah|0,this.Al|0,n|0,r|0)),{h:i,l:a}=Gn(this.Bh|0,this.Bl|0,i|0,a|0),{h:o,l:s}=Gn(this.Ch|0,this.Cl|0,o|0,s|0),{h:c,l}=Gn(this.Dh|0,this.Dl|0,c|0,l|0),{h:u,l:d}=Gn(this.Eh|0,this.El|0,u|0,d|0),{h:f,l:p}=Gn(this.Fh|0,this.Fl|0,f|0,p|0),{h:m,l:h}=Gn(this.Gh|0,this.Gl|0,m|0,h|0),{h:g,l:_}=Gn(this.Hh|0,this.Hl|0,g|0,_|0),this.set(n,r,i,a,o,s,c,l,u,d,f,p,m,h,g,_)}roundClean(){br(ks,As)}destroy(){br(this.buffer),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}},Ms=class extends js{constructor(){super(48),this.Ah=bs[0]|0,this.Al=bs[1]|0,this.Bh=bs[2]|0,this.Bl=bs[3]|0,this.Ch=bs[4]|0,this.Cl=bs[5]|0,this.Dh=bs[6]|0,this.Dl=bs[7]|0,this.Eh=bs[8]|0,this.El=bs[9]|0,this.Fh=bs[10]|0,this.Fl=bs[11]|0,this.Gh=bs[12]|0,this.Gl=bs[13]|0,this.Hh=bs[14]|0,this.Hl=bs[15]|0}},Ns=Or(()=>new Ts),Ps=Or(()=>new js),Fs=Or(()=>new Ms)})),Ls,Rs,zs=o((()=>{Nr(),Ls=class extends Mr{constructor(e,t){super(),this.finished=!1,this.destroyed=!1,gr(e);let n=Er(t);if(this.iHash=e.create(),typeof this.iHash.update!=`function`)throw Error(`Expected instance of class which extends utils.Hash`);this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,i=new Uint8Array(r);i.set(n.length>r?e.create().update(n).digest():n);for(let e=0;enew Ls(e,t).update(n).digest(),Rs.create=(e,t)=>new Ls(e,t)}));function Bs(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function Vs(e){if(!Bs(e))throw Error(`Uint8Array expected`)}function Hs(e,t){if(typeof t!=`boolean`)throw Error(e+` boolean expected, got `+t)}function Us(e){let t=e.toString(16);return t.length&1?`0`+t:t}function Ws(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);return e===``?sc:BigInt(`0x`+e)}function Gs(e){if(Vs(e),lc)return e.toHex();let t=``;for(let n=0;n=dc._0&&e<=dc._9)return e-dc._0;if(e>=dc.A&&e<=dc.F)return e-(dc.A-10);if(e>=dc.a&&e<=dc.f)return e-(dc.a-10)}function qs(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);if(lc)return Uint8Array.fromHex(e);let t=e.length,n=t/2;if(t%2)throw Error(`hex string expected, got unpadded hex of length `+t);let r=new Uint8Array(n);for(let t=0,i=0;tsc;e>>=cc,t+=1);return t}function ic(e,t,n){if(typeof e!=`number`||e<2)throw Error(`hashLen must be a number`);if(typeof t!=`number`||t<2)throw Error(`qByteLen must be a number`);if(typeof n!=`function`)throw Error(`hmacFn must be a function`);let r=mc(e),i=mc(e),a=0,o=()=>{r.fill(1),i.fill(0),a=0},s=(...e)=>n(i,r,...e),c=(e=mc(0))=>{i=s(hc([0]),e),r=s(),e.length!==0&&(i=s(hc([1]),e),r=s())},l=()=>{if(a++>=1e3)throw Error(`drbg: tried 1000 values`);let e=0,n=[];for(;e{o(),c(e);let n;for(;!(n=t(l()));)c();return o(),n}}function ac(e,t,n={}){let r=(t,n,r)=>{let i=gc[n];if(typeof i!=`function`)throw Error(`invalid validator function`);let a=e[t];if(!(r&&a===void 0)&&!i(a,e))throw Error(`param `+String(t)+` is invalid. Expected `+n+`, got `+a)};for(let[e,n]of Object.entries(t))r(e,n,!1);for(let[e,t]of Object.entries(n))r(e,t,!0);return e}function oc(e){let t=new WeakMap;return(n,...r)=>{let i=t.get(n);if(i!==void 0)return i;let a=e(n,...r);return t.set(n,a),a}}var sc,cc,lc,uc,dc,fc,pc,mc,hc,gc,_c=o((()=>{sc=BigInt(0),cc=BigInt(1),lc=typeof Uint8Array.from([]).toHex==`function`&&typeof Uint8Array.fromHex==`function`,uc=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),dc={_0:48,_9:57,A:65,F:70,a:97,f:102},fc=e=>typeof e==`bigint`&&sc<=e,pc=e=>(cc<new Uint8Array(e),hc=e=>Uint8Array.from(e),gc={bigint:e=>typeof e==`bigint`,function:e=>typeof e==`function`,boolean:e=>typeof e==`boolean`,string:e=>typeof e==`string`,stringOrUint8Array:e=>typeof e==`string`||Bs(e),isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>typeof e==`function`&&Number.isSafeInteger(e.outputLen)}}));function vc(e,t){let n=e%t;return n>=Pc?n:t+n}function yc(e,t,n){let r=e;for(;t-- >Pc;)r*=r,r%=n;return r}function bc(e,t){if(e===Pc)throw Error(`invert: expected non-zero number`);if(t<=Pc)throw Error(`invert: expected positive modulus, got `+t);let n=vc(e,t),r=t,i=Pc,a=Fc,o=Fc,s=Pc;for(;n!==Pc;){let e=r/n,t=r%n,c=i-o*e,l=a-s*e;r=n,n=t,i=o,a=s,o=c,s=l}if(r!==Fc)throw Error(`invert: does not exist`);return vc(i,t)}function xc(e,t){let n=(e.ORDER+Fc)/Rc,r=e.pow(t,n);if(!e.eql(e.sqr(r),t))throw Error(`Cannot find square root`);return r}function Sc(e,t){let n=(e.ORDER-zc)/Bc,r=e.mul(t,Ic),i=e.pow(r,n),a=e.mul(t,i),o=e.mul(e.mul(a,Ic),i),s=e.mul(a,e.sub(o,e.ONE));if(!e.eql(e.sqr(s),t))throw Error(`Cannot find square root`);return s}function Cc(e){if(e1e3)throw Error(`Cannot find square root: probably non-prime P`);if(n===1)return xc;let a=i.pow(r,t),o=(t+Fc)/Ic;return function(e,r){if(e.is0(r))return r;if(Oc(e,r)!==1)throw Error(`Cannot find square root`);let i=n,s=e.mul(e.ONE,a),c=e.pow(r,t),l=e.pow(r,o);for(;!e.eql(c,e.ONE);){if(e.is0(c))return e.ZERO;let t=1,n=e.sqr(c);for(;!e.eql(n,e.ONE);)if(t++,n=e.sqr(n),t===i)throw Error(`Cannot find square root`);let r=Fc<(e[t]=`function`,e),{ORDER:`bigint`,MASK:`bigint`,BYTES:`isSafeInteger`,BITS:`isSafeInteger`});return ac(e,t)}function Ec(e,t,n){if(nPc;)n&Fc&&(r=e.mul(r,i)),i=e.sqr(i),n>>=Fc;return r}function Dc(e,t,n=!1){let r=Array(t.length).fill(n?e.ZERO:void 0),i=t.reduce((t,n,i)=>e.is0(n)?t:(r[i]=t,e.mul(t,n)),e.ONE),a=e.inv(i);return t.reduceRight((t,n,i)=>e.is0(n)?t:(r[i]=e.mul(t,r[i]),e.mul(t,n)),a),r}function Oc(e,t){let n=(e.ORDER-Fc)/Ic,r=e.pow(t,n),i=e.eql(r,e.ONE),a=e.eql(r,e.ZERO),o=e.eql(r,e.neg(e.ONE));if(!i&&!a&&!o)throw Error(`invalid Legendre symbol result`);return i?1:a?0:-1}function kc(e,t){t!==void 0&&mr(t);let n=t===void 0?e.toString(2).length:t,r=Math.ceil(n/8);return{nBitLength:n,nByteLength:r}}function Ac(e,t,n=!1,r={}){if(e<=Pc)throw Error(`invalid field: expected ORDER > 0, got `+e);let{nBitLength:i,nByteLength:a}=kc(e,t);if(a>2048)throw Error(`invalid field: expected ORDER of <= 2048 bytes`);let o,s=Object.freeze({ORDER:e,isLE:n,BITS:i,BYTES:a,MASK:pc(i),ZERO:Pc,ONE:Fc,create:t=>vc(t,e),isValid:t=>{if(typeof t!=`bigint`)throw Error(`invalid field element: expected bigint, got `+typeof t);return Pc<=t&&te===Pc,isOdd:e=>(e&Fc)===Fc,neg:t=>vc(-t,e),eql:(e,t)=>e===t,sqr:t=>vc(t*t,e),add:(t,n)=>vc(t+n,e),sub:(t,n)=>vc(t-n,e),mul:(t,n)=>vc(t*n,e),pow:(e,t)=>Ec(s,e,t),div:(t,n)=>vc(t*bc(n,e),e),sqrN:e=>e*e,addN:(e,t)=>e+t,subN:(e,t)=>e-t,mulN:(e,t)=>e*t,inv:t=>bc(t,e),sqrt:r.sqrt||(t=>(o||=wc(e),o(s,t))),toBytes:e=>n?Zs(e,a):Xs(e,a),fromBytes:e=>{if(e.length!==a)throw Error(`Field.fromBytes: expected `+a+` bytes, got `+e.length);return n?Ys(e):Js(e)},invertBatch:e=>Dc(s,e),cmov:(e,t,n)=>n?t:e});return Object.freeze(s)}function jc(e){if(typeof e!=`bigint`)throw Error(`field order must be bigint`);let t=e.toString(2).length;return Math.ceil(t/8)}function Mc(e){let t=jc(e);return t+Math.ceil(t/2)}function Nc(e,t,n=!1){let r=e.length,i=jc(t),a=Mc(t);if(r<16||r1024)throw Error(`expected `+a+`-1024 bytes of input, got `+r);let o=n?Ys(e):Js(e),s=vc(o,t-Fc)+Fc;return n?Zs(s,i):Xs(s,i)}var Pc,Fc,Ic,Lc,Rc,zc,Bc,Vc,Hc=o((()=>{Nr(),_c(),Pc=BigInt(0),Fc=BigInt(1),Ic=BigInt(2),Lc=BigInt(3),Rc=BigInt(4),zc=BigInt(5),Bc=BigInt(8),Vc=[`create`,`isValid`,`is0`,`neg`,`inv`,`sqrt`,`sqr`,`eql`,`add`,`sub`,`mul`,`pow`,`div`,`addN`,`subN`,`mulN`,`sqrN`]}));function Uc(e,t){let n=t.negate();return e?n:t}function Wc(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw Error(`invalid window size, expected [1..`+t+`], got W=`+e)}function Gc(e,t){Wc(e,t);let n=Math.ceil(t/e)+1,r=2**(e-1),i=2**e,a=pc(e),o=BigInt(e);return{windows:n,windowSize:r,mask:a,maxNumber:i,shiftBy:o}}function Kc(e,t,n){let{windowSize:r,mask:i,maxNumber:a,shiftBy:o}=n,s=Number(e&i),c=e>>o;s>r&&(s-=a,c+=el);let l=t*r,u=l+Math.abs(s)-1,d=s===0,f=s<0,p=t%2!=0;return{nextN:c,offset:u,isZero:d,isNeg:f,isNegF:p,offsetF:l}}function qc(e,t){if(!Array.isArray(e))throw Error(`array expected`);e.forEach((e,n)=>{if(!(e instanceof t))throw Error(`invalid point at index `+n)})}function Jc(e,t){if(!Array.isArray(e))throw Error(`array of scalars expected`);e.forEach((e,n)=>{if(!t.isValid(e))throw Error(`invalid scalar at index `+n)})}function Yc(e){return nl.get(e)||1}function Xc(e,t){return{constTimeNegate:Uc,hasPrecomputes(e){return Yc(e)!==1},unsafeLadder(t,n,r=e.ZERO){let i=t;for(;n>$c;)n&el&&(r=r.add(i)),i=i.double(),n>>=el;return r},precomputeWindow(e,n){let{windows:r,windowSize:i}=Gc(n,t),a=[],o=e,s=o;for(let e=0;e12?c=s-3:s>4?c=s-2:s>0&&(c=2);let l=pc(c),u=Array(Number(l)+1).fill(o),d=Math.floor((t.BITS-1)/c)*c,f=o;for(let e=d;e>=0;e-=c){u.fill(o);for(let t=0;t>BigInt(e)&l);u[a]=u[a].add(n[t])}let t=o;for(let e=u.length-1,n=o;e>0;e--)n=n.add(u[e]),t=t.add(n);if(f=f.add(t),e!==0)for(let e=0;e{Hc(),_c(),$c=BigInt(0),el=BigInt(1),tl=new WeakMap,nl=new WeakMap}));function il(e){e.lowS!==void 0&&Hs(`lowS`,e.lowS),e.prehash!==void 0&&Hs(`prehash`,e.prehash)}function al(e){let t=Qc(e);ac(t,{a:`field`,b:`field`},{allowInfinityPoint:`boolean`,allowedPrivateKeyLengths:`array`,clearCofactor:`function`,fromBytes:`function`,isTorsionFree:`function`,toBytes:`function`,wrapPrivateKey:`boolean`});let{endo:n,Fp:r,a:i}=t;if(n){if(!r.eql(i,r.ZERO))throw Error(`invalid endo: CURVE.a must be 0`);if(typeof n!=`object`||typeof n.beta!=`bigint`||typeof n.splitScalar!=`function`)throw Error(`invalid endo: expected "beta": bigint and "splitScalar": function`)}return Object.freeze({...t})}function ol(e,t){return Gs(Xs(e,t))}function sl(e){let t=al(e),{Fp:n}=t,r=Ac(t.n,t.nBitLength),i=t.toBytes||((e,t,r)=>{let i=t.toAffine();return $s(Uint8Array.from([4]),n.toBytes(i.x),n.toBytes(i.y))}),a=t.fromBytes||(e=>{let t=e.subarray(1),r=n.fromBytes(t.subarray(0,n.BYTES)),i=n.fromBytes(t.subarray(n.BYTES,2*n.BYTES));return{x:r,y:i}});function o(e){let{a:r,b:i}=t,a=n.sqr(e),o=n.mul(a,e);return n.add(n.add(o,n.mul(e,r)),i)}function s(e,t){let r=n.sqr(t),i=o(e);return n.eql(r,i)}if(!s(t.Gx,t.Gy))throw Error(`bad curve params: generator point`);let c=n.mul(n.pow(t.a,_l),vl),l=n.mul(n.sqr(t.b),BigInt(27));if(n.is0(n.add(c,l)))throw Error(`bad curve params: a or b`);function u(e){return tc(e,hl,t.n)}function d(e){let{allowedPrivateKeyLengths:n,nByteLength:r,wrapPrivateKey:i,n:a}=t;if(n&&typeof e!=`bigint`){if(Bs(e)&&(e=Gs(e)),typeof e!=`string`||!n.includes(e.length))throw Error(`invalid private key`);e=e.padStart(r*2,`0`)}let o;try{o=typeof e==`bigint`?e:Js(Qs(`private key`,e,r))}catch{throw Error(`invalid private key, expected hex or `+r+` bytes, got `+typeof e)}return i&&(o=vc(o,a)),nc(`private key`,o,hl,a),o}function f(e){if(!(e instanceof h))throw Error(`ProjectivePoint expected`)}let p=oc((e,t)=>{let{px:r,py:i,pz:a}=e;if(n.eql(a,n.ONE))return{x:r,y:i};let o=e.is0();t??=o?n.ONE:n.inv(a);let s=n.mul(r,t),c=n.mul(i,t),l=n.mul(a,t);if(o)return{x:n.ZERO,y:n.ZERO};if(!n.eql(l,n.ONE))throw Error(`invZ was invalid`);return{x:s,y:c}}),m=oc(e=>{if(e.is0()){if(t.allowInfinityPoint&&!n.is0(e.py))return;throw Error(`bad point: ZERO`)}let{x:r,y:i}=e.toAffine();if(!n.isValid(r)||!n.isValid(i))throw Error(`bad point: x or y not FE`);if(!s(r,i))throw Error(`bad point: equation left != right`);if(!e.isTorsionFree())throw Error(`bad point: not in prime-order subgroup`);return!0});class h{constructor(e,t,r){if(e==null||!n.isValid(e))throw Error(`x required`);if(t==null||!n.isValid(t)||n.is0(t))throw Error(`y required`);if(r==null||!n.isValid(r))throw Error(`z required`);this.px=e,this.py=t,this.pz=r,Object.freeze(this)}static fromAffine(e){let{x:t,y:r}=e||{};if(!e||!n.isValid(t)||!n.isValid(r))throw Error(`invalid affine point`);if(e instanceof h)throw Error(`projective point not allowed`);let i=e=>n.eql(e,n.ZERO);return i(t)&&i(r)?h.ZERO:new h(t,r,n.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(e){let t=Dc(n,e.map(e=>e.pz));return e.map((e,n)=>e.toAffine(t[n])).map(h.fromAffine)}static fromHex(e){let t=h.fromAffine(a(Qs(`pointHex`,e)));return t.assertValidity(),t}static fromPrivateKey(e){return h.BASE.multiply(d(e))}static msm(e,t){return Zc(h,r,e,t)}_setWindowSize(e){v.setWindowSize(this,e)}assertValidity(){m(this)}hasEvenY(){let{y:e}=this.toAffine();if(n.isOdd)return!n.isOdd(e);throw Error(`Field doesn't support isOdd`)}equals(e){f(e);let{px:t,py:r,pz:i}=this,{px:a,py:o,pz:s}=e,c=n.eql(n.mul(t,s),n.mul(a,i)),l=n.eql(n.mul(r,s),n.mul(o,i));return c&&l}negate(){return new h(this.px,n.neg(this.py),this.pz)}double(){let{a:e,b:r}=t,i=n.mul(r,_l),{px:a,py:o,pz:s}=this,c=n.ZERO,l=n.ZERO,u=n.ZERO,d=n.mul(a,a),f=n.mul(o,o),p=n.mul(s,s),m=n.mul(a,o);return m=n.add(m,m),u=n.mul(a,s),u=n.add(u,u),c=n.mul(e,u),l=n.mul(i,p),l=n.add(c,l),c=n.sub(f,l),l=n.add(f,l),l=n.mul(c,l),c=n.mul(m,c),u=n.mul(i,u),p=n.mul(e,p),m=n.sub(d,p),m=n.mul(e,m),m=n.add(m,u),u=n.add(d,d),d=n.add(u,d),d=n.add(d,p),d=n.mul(d,m),l=n.add(l,d),p=n.mul(o,s),p=n.add(p,p),d=n.mul(p,m),c=n.sub(c,d),u=n.mul(p,f),u=n.add(u,u),u=n.add(u,u),new h(c,l,u)}add(e){f(e);let{px:r,py:i,pz:a}=this,{px:o,py:s,pz:c}=e,l=n.ZERO,u=n.ZERO,d=n.ZERO,p=t.a,m=n.mul(t.b,_l),g=n.mul(r,o),_=n.mul(i,s),v=n.mul(a,c),y=n.add(r,i),b=n.add(o,s);y=n.mul(y,b),b=n.add(g,_),y=n.sub(y,b),b=n.add(r,a);let x=n.add(o,c);return b=n.mul(b,x),x=n.add(g,v),b=n.sub(b,x),x=n.add(i,a),l=n.add(s,c),x=n.mul(x,l),l=n.add(_,v),x=n.sub(x,l),d=n.mul(p,b),l=n.mul(m,v),d=n.add(l,d),l=n.sub(_,d),d=n.add(_,d),u=n.mul(l,d),_=n.add(g,g),_=n.add(_,g),v=n.mul(p,v),b=n.mul(m,b),_=n.add(_,v),v=n.sub(g,v),v=n.mul(p,v),b=n.add(b,v),g=n.mul(_,b),u=n.add(u,g),g=n.mul(x,b),l=n.mul(y,l),l=n.sub(l,g),g=n.mul(y,_),d=n.mul(x,d),d=n.add(d,g),new h(l,u,d)}subtract(e){return this.add(e.negate())}is0(){return this.equals(h.ZERO)}wNAF(e){return v.wNAFCached(this,e,h.normalizeZ)}multiplyUnsafe(e){let{endo:r,n:i}=t;nc(`scalar`,e,ml,i);let a=h.ZERO;if(e===ml)return a;if(this.is0()||e===hl)return this;if(!r||v.hasPrecomputes(this))return v.wNAFCachedUnsafe(this,e,h.normalizeZ);let{k1neg:o,k1:s,k2neg:c,k2:l}=r.splitScalar(e),u=a,d=a,f=this;for(;s>ml||l>ml;)s&hl&&(u=u.add(f)),l&hl&&(d=d.add(f)),f=f.double(),s>>=hl,l>>=hl;return o&&(u=u.negate()),c&&(d=d.negate()),d=new h(n.mul(d.px,r.beta),d.py,d.pz),u.add(d)}multiply(e){let{endo:r,n:i}=t;nc(`scalar`,e,hl,i);let a,o;if(r){let{k1neg:t,k1:i,k2neg:s,k2:c}=r.splitScalar(e),{p:l,f:u}=this.wNAF(i),{p:d,f}=this.wNAF(c);l=v.constTimeNegate(t,l),d=v.constTimeNegate(s,d),d=new h(n.mul(d.px,r.beta),d.py,d.pz),a=l.add(d),o=u.add(f)}else{let{p:t,f:n}=this.wNAF(e);a=t,o=n}return h.normalizeZ([a,o])[0]}multiplyAndAddUnsafe(e,t,n){let r=h.BASE,i=(e,t)=>t===ml||t===hl||!e.equals(r)?e.multiplyUnsafe(t):e.multiply(t),a=i(this,t).add(i(e,n));return a.is0()?void 0:a}toAffine(e){return p(this,e)}isTorsionFree(){let{h:e,isTorsionFree:n}=t;if(e===hl)return!0;if(n)return n(h,this);throw Error(`isTorsionFree() has not been declared for the elliptic curve`)}clearCofactor(){let{h:e,clearCofactor:n}=t;return e===hl?this:n?n(h,this):this.multiplyUnsafe(t.h)}toRawBytes(e=!0){return Hs(`isCompressed`,e),this.assertValidity(),i(h,this,e)}toHex(e=!0){return Hs(`isCompressed`,e),Gs(this.toRawBytes(e))}}h.BASE=new h(t.Gx,t.Gy,n.ONE),h.ZERO=new h(n.ZERO,n.ONE,n.ZERO);let{endo:g,nBitLength:_}=t,v=Xc(h,g?Math.ceil(_/2):_);return{CURVE:t,ProjectivePoint:h,normPrivateKeyToScalar:d,weierstrassEquation:o,isWithinCurveOrder:u}}function cl(e){let t=Qc(e);return ac(t,{hash:`hash`,hmac:`function`,randomBytes:`function`},{bits2int:`function`,bits2int_modN:`function`,lowS:`boolean`}),Object.freeze({lowS:!0,...t})}function ll(e){let t=cl(e),{Fp:n,n:r,nByteLength:i,nBitLength:a}=t,o=n.BYTES+1,s=2*n.BYTES+1;function c(e){return vc(e,r)}function l(e){return bc(e,r)}let{ProjectivePoint:u,normPrivateKeyToScalar:d,weierstrassEquation:f,isWithinCurveOrder:p}=sl({...t,toBytes(e,t,r){let i=t.toAffine(),a=n.toBytes(i.x),o=$s;return Hs(`isCompressed`,r),r?o(Uint8Array.from([t.hasEvenY()?2:3]),a):o(Uint8Array.from([4]),a,n.toBytes(i.y))},fromBytes(e){let t=e.length,r=e[0],i=e.subarray(1);if(t===o&&(r===2||r===3)){let e=Js(i);if(!tc(e,hl,n.ORDER))throw Error(`Point is not on curve`);let t=f(e),a;try{a=n.sqrt(t)}catch(e){let t=e instanceof Error?`: `+e.message:``;throw Error(`Point is not on curve`+t)}let o=(a&hl)===hl;return(r&1)==1!==o&&(a=n.neg(a)),{x:e,y:a}}else if(t===s&&r===4){let e=n.fromBytes(i.subarray(0,n.BYTES)),t=n.fromBytes(i.subarray(n.BYTES,2*n.BYTES));return{x:e,y:t}}else{let e=o,n=s;throw Error(`invalid Point, expected length of `+e+`, or uncompressed `+n+`, got `+t)}}});function m(e){let t=r>>hl;return e>t}function h(e){return m(e)?c(-e):e}let g=(e,t,n)=>Js(e.slice(t,n));class _{constructor(e,t,n){nc(`r`,e,hl,r),nc(`s`,t,hl,r),this.r=e,this.s=t,n!=null&&(this.recovery=n),Object.freeze(this)}static fromCompact(e){let t=i;return e=Qs(`compactSignature`,e,t*2),new _(g(e,0,t),g(e,t,2*t))}static fromDER(e){let{r:t,s:n}=pl.toSig(Qs(`DER`,e));return new _(t,n)}assertValidity(){}addRecoveryBit(e){return new _(this.r,this.s,e)}recoverPublicKey(e){let{r,s:i,recovery:a}=this,o=C(Qs(`msgHash`,e));if(a==null||![0,1,2,3].includes(a))throw Error(`recovery id invalid`);let s=a===2||a===3?r+t.n:r;if(s>=n.ORDER)throw Error(`recovery id 2 or 3 invalid`);let d=a&1?`03`:`02`,f=u.fromHex(d+ol(s,n.BYTES)),p=l(s),m=c(-o*p),h=c(i*p),g=u.BASE.multiplyAndAddUnsafe(f,m,h);if(!g)throw Error(`point at infinify`);return g.assertValidity(),g}hasHighS(){return m(this.s)}normalizeS(){return this.hasHighS()?new _(this.r,c(-this.s),this.recovery):this}toDERRawBytes(){return qs(this.toDERHex())}toDERHex(){return pl.hexFromSig(this)}toCompactRawBytes(){return qs(this.toCompactHex())}toCompactHex(){let e=i;return ol(this.r,e)+ol(this.s,e)}}let v={isValidPrivateKey(e){try{return d(e),!0}catch{return!1}},normPrivateKeyToScalar:d,randomPrivateKey:()=>{let e=Mc(t.n);return Nc(t.randomBytes(e),t.n)},precompute(e=8,t=u.BASE){return t._setWindowSize(e),t.multiply(BigInt(3)),t}};function y(e,t=!0){return u.fromPrivateKey(e).toRawBytes(t)}function b(e){if(typeof e==`bigint`)return!1;if(e instanceof u)return!0;let r=Qs(`key`,e).length,a=n.BYTES,o=a+1,s=2*a+1;if(!(t.allowedPrivateKeyLengths||i===o))return r===o||r===s}function x(e,t,n=!0){if(b(e)===!0)throw Error(`first arg must be private key`);if(b(t)===!1)throw Error(`second arg must be public key`);return u.fromHex(t).multiply(d(e)).toRawBytes(n)}let S=t.bits2int||function(e){if(e.length>8192)throw Error(`input is too large`);let t=Js(e),n=e.length*8-a;return n>0?t>>BigInt(n):t},C=t.bits2int_modN||function(e){return c(S(e))},w=pc(a);function ee(e){return nc(`num < 2^`+a,e,ml,w),Xs(e,i)}function te(e,r,i=ne){if([`recovered`,`canonical`].some(e=>e in i))throw Error(`sign() legacy options not supported`);let{hash:a,randomBytes:o}=t,{lowS:s,prehash:f,extraEntropy:g}=i;s??=!0,e=Qs(`msgHash`,e),il(i),f&&(e=Qs(`prehashed msgHash`,a(e)));let v=C(e),y=d(r),b=[ee(y),ee(v)];if(g!=null&&g!==!1){let e=g===!0?o(n.BYTES):g;b.push(Qs(`extraEntropy`,e))}let x=$s(...b),w=v;function te(e){let t=S(e);if(!p(t))return;let n=l(t),r=u.BASE.multiply(t).toAffine(),i=c(r.x);if(i===ml)return;let a=c(n*c(w+i*y));if(a===ml)return;let o=(r.x===i?0:2)|Number(r.y&hl),d=a;return s&&m(a)&&(d=h(a),o^=1),new _(i,d,o)}return{seed:x,k2sig:te}}let ne={lowS:t.lowS,prehash:!1},re={lowS:t.lowS,prehash:!1};function ie(e,n,r=ne){let{seed:i,k2sig:a}=te(e,n,r),o=t;return ic(o.hash.outputLen,o.nByteLength,o.hmac)(i,a)}u.BASE._setWindowSize(8);function ae(e,n,r,i=re){let a=e;n=Qs(`msgHash`,n),r=Qs(`publicKey`,r);let{lowS:o,prehash:s,format:d}=i;if(il(i),`strict`in i)throw Error(`options.strict was renamed to lowS`);if(d!==void 0&&d!==`compact`&&d!==`der`)throw Error(`format must be compact or der`);let f=typeof a==`string`||Bs(a),p=!f&&!d&&typeof a==`object`&&!!a&&typeof a.r==`bigint`&&typeof a.s==`bigint`;if(!f&&!p)throw Error(`invalid signature, expected Uint8Array, hex string or Signature instance`);let m,h;try{if(p&&(m=new _(a.r,a.s)),f){try{d!==`compact`&&(m=_.fromDER(a))}catch(e){if(!(e instanceof pl.Err))throw e}!m&&d!==`der`&&(m=_.fromCompact(a))}h=u.fromHex(r)}catch{return!1}if(!m||o&&m.hasHighS())return!1;s&&(n=t.hash(n));let{r:g,s:v}=m,y=C(n),b=l(v),x=c(y*b),S=c(g*b),w=u.BASE.multiplyAndAddUnsafe(h,x,S)?.toAffine();return w?c(w.x)===g:!1}return{CURVE:t,getPublicKey:y,getSharedSecret:x,sign:ie,verify:ae,ProjectivePoint:u,Signature:_,utils:v}}function ul(e,t){let n=e.ORDER,r=ml;for(let e=n-hl;e%gl===ml;e/=gl)r+=hl;let i=r,a=gl<{let r=d,a=e.pow(n,l),o=e.sqr(a);o=e.mul(o,n);let s=e.mul(t,o);s=e.pow(s,c),s=e.mul(s,a),a=e.mul(s,n),o=e.mul(s,t);let p=e.mul(o,a);s=e.pow(p,u);let m=e.eql(s,e.ONE);a=e.mul(o,f),s=e.mul(p,r),o=e.cmov(a,o,m),p=e.cmov(s,p,m);for(let t=i;t>hl;t--){let n=t-gl;n=gl<{let a=e.sqr(i),o=e.mul(t,i);a=e.mul(a,o);let s=e.pow(a,n);s=e.mul(s,o);let c=e.mul(s,r),l=e.mul(e.sqr(s),i),u=e.eql(l,t),d=e.cmov(c,s,u);return{isValid:u,value:d}}}return p}function dl(e,t){if(Tc(e),!e.isValid(t.A)||!e.isValid(t.B)||!e.isValid(t.Z))throw Error(`mapToCurveSimpleSWU: invalid opts`);let n=ul(e,t.Z);if(!e.isOdd)throw Error(`Fp.isOdd is not implemented!`);return r=>{let i,a,o,s,c,l,u,d;i=e.sqr(r),i=e.mul(i,t.Z),a=e.sqr(i),a=e.add(a,i),o=e.add(a,e.ONE),o=e.mul(o,t.B),s=e.cmov(t.Z,e.neg(a),!e.eql(a,e.ZERO)),s=e.mul(s,t.A),a=e.sqr(o),l=e.sqr(s),c=e.mul(l,t.A),a=e.add(a,c),a=e.mul(a,o),l=e.mul(l,s),c=e.mul(l,t.B),a=e.add(a,c),u=e.mul(i,o);let{isValid:f,value:p}=n(a,l);d=e.mul(i,r),d=e.mul(d,p),u=e.cmov(u,o,f),d=e.cmov(d,p,f);let m=e.isOdd(r)===e.isOdd(d);d=e.cmov(e.neg(d),d,m);let h=Dc(e,[s],!0)[0];return u=e.mul(u,h),{x:u,y:d}}}var fl,pl,ml,hl,gl,_l,vl,yl=o((()=>{rl(),Hc(),_c(),fl=class extends Error{constructor(e=``){super(e)}},pl={Err:fl,_tlv:{encode:(e,t)=>{let{Err:n}=pl;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length&1)throw new n(`tlv.encode: unpadded data`);let r=t.length/2,i=Us(r);if(i.length/2&128)throw new n(`tlv.encode: long form length too big`);let a=r>127?Us(i.length/2|128):``;return Us(e)+a+i+t},decode(e,t){let{Err:n}=pl,r=0;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length<2||t[r++]!==e)throw new n(`tlv.decode: wrong tlv`);let i=t[r++],a=!!(i&128),o=0;if(!a)o=i;else{let e=i&127;if(!e)throw new n(`tlv.decode(long): indefinite length not supported`);if(e>4)throw new n(`tlv.decode(long): byte length is too big`);let a=t.subarray(r,r+e);if(a.length!==e)throw new n(`tlv.decode: length bytes not complete`);if(a[0]===0)throw new n(`tlv.decode(long): zero leftmost byte`);for(let e of a)o=o<<8|e;if(r+=e,o<128)throw new n(`tlv.decode(long): not minimal encoding`)}let s=t.subarray(r,r+o);if(s.length!==o)throw new n(`tlv.decode: wrong value length`);return{v:s,l:t.subarray(r+o)}}},_int:{encode(e){let{Err:t}=pl;if(eRs(e,t,Dr(...n)),randomBytes:kr}}function xl(e,t){let n=t=>ll({...e,...bl(t)});return{...n(t),create:n}}var Sl=o((()=>{zs(),Nr(),yl()}));function Cl(e,t){if(Tl(e),Tl(t),e<0||e>=1<<8*t)throw Error(`invalid I2OSP input: `+e);let n=Array.from({length:t}).fill(0);for(let r=t-1;r>=0;r--)n[r]=e&255,e>>>=8;return new Uint8Array(n)}function wl(e,t){let n=new Uint8Array(e.length);for(let r=0;r255&&(t=r($s(ec(`H2C-OVERSIZE-DST-`),t)));let{outputLen:i,blockLen:a}=r,o=Math.ceil(n/i);if(n>65535||o>255)throw Error(`expand_message_xmd: invalid lenInBytes`);let s=$s(t,Cl(t.length,1)),c=Cl(0,a),l=Cl(n,2),u=Array(o),d=r($s(c,e,l,Cl(0,1),s));u[0]=r($s(d,Cl(1,1),s));for(let e=1;e<=o;e++){let t=[wl(d,u[e-1]),Cl(e+1,1),s];u[e]=r($s(...t))}return $s(...u).slice(0,n)}function Dl(e,t,n,r,i){if(Vs(e),Vs(t),Tl(n),t.length>255){let e=Math.ceil(2*r/8);t=i.create({dkLen:e}).update(ec(`H2C-OVERSIZE-DST-`)).update(t).digest()}if(n>65535||t.length>255)throw Error(`expand_message_xof: invalid lenInBytes`);return i.create({dkLen:n}).update(e).update(Cl(n,2)).update(t).update(Cl(t.length,1)).digest()}function Ol(e,t,n){ac(n,{DST:`stringOrUint8Array`,p:`bigint`,m:`isSafeInteger`,k:`isSafeInteger`,hash:`hash`});let{p:r,k:i,m:a,hash:o,expand:s,DST:c}=n;Vs(e),Tl(t);let l=typeof c==`string`?ec(c):c,u=r.toString(2).length,d=Math.ceil((u+i)/8),f=t*a*d,p;if(s===`xmd`)p=El(e,l,f,o);else if(s===`xof`)p=Dl(e,l,f,i,o);else if(s===`_internal_pass`)p=e;else throw Error(`expand must be "xmd" or "xof"`);let m=Array(t);for(let e=0;eArray.from(e).reverse());return(t,r)=>{let[i,a,o,s]=n.map(n=>n.reduce((n,r)=>e.add(e.mul(n,t),r))),[c,l]=Dc(e,[a,s],!0);return t=e.mul(i,c),r=e.mul(r,e.mul(o,l)),{x:t,y:r}}}function Al(e,t,n){if(typeof t!=`function`)throw Error(`mapToCurve() must be defined`);function r(n){return e.fromAffine(t(n))}function i(t){let n=t.clearCofactor();return n.equals(e.ZERO)?e.ZERO:(n.assertValidity(),n)}return{defaults:n,hashToCurve(e,t){let a=Ol(e,2,{...n,DST:n.DST,...t}),o=r(a[0]),s=r(a[1]);return i(o.add(s))},encodeToCurve(e,t){let a=Ol(e,1,{...n,DST:n.encodeDST,...t});return i(r(a[0]))},mapToCurve(e){if(!Array.isArray(e))throw Error(`expected array of bigints`);for(let t of e)if(typeof t!=`bigint`)throw Error(`expected array of bigints`);return i(r(e))}}}var jl,Ml=o((()=>{Hc(),_c(),jl=Js})),Nl=c({encodeToCurve:()=>lu,hashToCurve:()=>cu,schnorr:()=>iu,secp256k1:()=>Yl,secp256k1_hasher:()=>su});function Pl(e){let t=Hl,n=BigInt(3),r=BigInt(6),i=BigInt(11),a=BigInt(22),o=BigInt(23),s=BigInt(44),c=BigInt(88),l=e*e*e%t,u=l*l*e%t,d=yc(u,n,t)*u%t,f=yc(d,n,t)*u%t,p=yc(f,Kl,t)*l%t,m=yc(p,i,t)*p%t,h=yc(m,a,t)*m%t,g=yc(h,s,t)*h%t,_=yc(g,c,t)*g%t,v=yc(_,s,t)*h%t,y=yc(v,n,t)*u%t,b=yc(y,o,t)*m%t,x=yc(b,r,t)*l%t,S=yc(x,Kl,t);if(!Jl.eql(Jl.sqr(S),e))throw Error(`Cannot find square root`);return S}function Fl(e,...t){let n=Xl[e];if(n===void 0){let t=Ns(Uint8Array.from(e,e=>e.charCodeAt(0)));n=$s(t,t),Xl[e]=n}return Ns($s(n,...t))}function Il(e){let t=Yl.utils.normPrivateKeyToScalar(e),n=tu.fromPrivateKey(t);return{scalar:n.hasEvenY()?t:eu(-t),bytes:Zl(n)}}function Ll(e){nc(`x`,e,Gl,Hl);let t=$l(e*e),n=$l(t*e+BigInt(7)),r=Pl(n);r%Kl!==Wl&&(r=$l(-r));let i=new tu(e,r,Gl);return i.assertValidity(),i}function Rl(...e){return eu(ru(Fl(`BIP0340/challenge`,...e)))}function zl(e){return Il(e).bytes}function Bl(e,t,n=kr(32)){let r=Qs(`message`,e),{bytes:i,scalar:a}=Il(t),o=Qs(`auxRand`,n,32),s=Ql(a^ru(Fl(`BIP0340/aux`,o))),c=Fl(`BIP0340/nonce`,s,i,r),l=eu(ru(c));if(l===Wl)throw Error(`sign failed: k is zero`);let{bytes:u,scalar:d}=Il(l),f=Rl(u,i,r),p=new Uint8Array(64);if(p.set(u,0),p.set(Ql(eu(d+f*a)),32),!Vl(p,r,i))throw Error(`sign: Invalid signature produced`);return p}function Vl(e,t,n){let r=Qs(`signature`,e,64),i=Qs(`message`,t),a=Qs(`publicKey`,n,32);try{let e=Ll(ru(a)),t=ru(r.subarray(0,32));if(!tc(t,Gl,Hl))return!1;let n=ru(r.subarray(32,64));if(!tc(n,Gl,Ul))return!1;let o=Rl(Ql(t),Zl(e),i),s=nu(e,n,eu(-o));return!(!s||!s.hasEvenY()||s.toAffine().x!==t)}catch{return!1}}var Hl,Ul,Wl,Gl,Kl,ql,Jl,Yl,Xl,Zl,Ql,$l,eu,tu,nu,ru,iu,au,ou,su,cu,lu,uu=o((()=>{Is(),Nr(),Sl(),Ml(),Hc(),_c(),yl(),Hl=BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f`),Ul=BigInt(`0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141`),Wl=BigInt(0),Gl=BigInt(1),Kl=BigInt(2),ql=(e,t)=>(e+t/Kl)/t,Jl=Ac(Hl,void 0,void 0,{sqrt:Pl}),Yl=xl({a:Wl,b:BigInt(7),Fp:Jl,n:Ul,Gx:BigInt(`55066263022277343669578718895168534326250603453777594175500187360389116729240`),Gy:BigInt(`32670510020758816978083085130507043184471273380659243275938904335757337482424`),h:BigInt(1),lowS:!0,endo:{beta:BigInt(`0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee`),splitScalar:e=>{let t=Ul,n=BigInt(`0x3086d221a7d46bcde86c90e49284eb15`),r=-Gl*BigInt(`0xe4437ed6010e88286f547fa90abfe4c3`),i=BigInt(`0x114ca50f7a8e2f3f657c1108d9d44cfd8`),a=n,o=BigInt(`0x100000000000000000000000000000000`),s=ql(a*e,t),c=ql(-r*e,t),l=vc(e-s*n-c*i,t),u=vc(-s*r-c*a,t),d=l>o,f=u>o;if(d&&(l=t-l),f&&(u=t-u),l>o||u>o)throw Error(`splitScalar: Endomorphism failed, k=`+e);return{k1neg:d,k1:l,k2neg:f,k2:u}}}},Ns),Xl={},Zl=e=>e.toRawBytes(!0).slice(1),Ql=e=>Xs(e,32),$l=e=>vc(e,Hl),eu=e=>vc(e,Ul),tu=(()=>Yl.ProjectivePoint)(),nu=(e,t,n)=>tu.BASE.multiplyAndAddUnsafe(e,t,n),ru=Js,iu=(()=>({getPublicKey:zl,sign:Bl,verify:Vl,utils:{randomPrivateKey:Yl.utils.randomPrivateKey,lift_x:Ll,pointToBytes:Zl,numberToBytesBE:Xs,bytesToNumberBE:Js,taggedHash:Fl,mod:vc}}))(),au=(()=>kl(Jl,[[`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7`,`0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581`,`0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262`,`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c`],[`0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b`,`0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14`,`0x0000000000000000000000000000000000000000000000000000000000000001`],[`0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c`,`0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3`,`0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931`,`0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84`],[`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b`,`0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573`,`0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f`,`0x0000000000000000000000000000000000000000000000000000000000000001`]].map(e=>e.map(e=>BigInt(e)))))(),ou=(()=>dl(Jl,{A:BigInt(`0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533`),B:BigInt(`1771`),Z:Jl.create(BigInt(`-11`))}))(),su=(()=>Al(Yl.ProjectivePoint,e=>{let{x:t,y:n}=ou(Jl.create(e[0]));return au(t,n)},{DST:`secp256k1_XMD:SHA-256_SSWU_RO_`,encodeDST:`secp256k1_XMD:SHA-256_SSWU_NU_`,p:Jl.ORDER,m:1,k:128,expand:`xmd`,hash:Ns}))(),cu=(()=>su.hashToCurve)(),lu=(()=>su.encodeToCurve)()}));Pt(),It(),Dn(),A(),ms();async function du({hash:e,signature:t}){let n=Nt(e)?e:On(e),{secp256k1:r}=await ps(async()=>{let{secp256k1:e}=await Promise.resolve().then(()=>(uu(),Nl));return{secp256k1:e}},void 0);return`0x${(()=>{if(typeof t==`object`&&`r`in t&&`s`in t){let{r:e,s:n,v:i,yParity:a}=t,o=fu(Number(a??i));return new r.Signature(wn(e),wn(n)).addRecoveryBit(o)}let e=Nt(t)?t:On(t);if(Ft(e)!==65)throw Error(`invalid signature length`);let n=En(`0x${e.slice(130)}`),i=fu(n);return r.Signature.fromCompact(e.substring(2,130)).addRecoveryBit(i)})().recoverPublicKey(n.substring(2)).toHex(!1)}`}function fu(e){if(e===0||e===1)return e;if(e===27)return 0;if(e===28)return 1;throw Error(`Invalid yParityOrV value`)}async function pu({hash:e,signature:t}){return ls(await du({hash:e,signature:t}))}O(),ya(),Hn(),A();function M(e,t=`hex`){let n=N(e),r=_a(new Uint8Array(n.length));return n.encode(r),t===`hex`?An(r.bytes):r.bytes}function N(e){return Array.isArray(e)?mu(e.map(e=>N(e))):hu(e)}function mu(e){let t=e.reduce((e,t)=>e+t.length,0),n=gu(t);return{length:(()=>t<=55?1+t:1+n+t)(),encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function hu(e){let t=typeof e==`string`?Ln(e):e,n=gu(t.length);return{length:(()=>t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length)(),encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function gu(e){if(e<2**8)return 1;if(e<2**16)return 2;if(e<2**24)return 3;if(e<2**32)return 4;throw new D(`Length is too large.`)}Di(),Hn(),A(),ei();function _u(e){let{chainId:t,nonce:n,to:r}=e,i=e.contractAddress??e.address,a=$r(Ei([`0x05`,M([t?k(t):`0x`,i,n?k(n):`0x`])]));return r===`bytes`?Ln(a):a}async function vu(e){let{authorization:t,signature:n}=e;return pu({hash:_u(t),signature:n??t})}Xa(),Qa(),O(),ho();var yu=class extends D{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=io({from:t?.address,to:u,value:d!==void 0&&`${Ya(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${Za(o)} gwei`,maxFeePerGas:s!==void 0&&`${Za(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${Za(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Estimate Gas Arguments:`,f].filter(Boolean),name:`EstimateGasExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},bu,xu,Su,Cu,wu,Tu,Eu,Du,Ou,ku,Au,ju,Mu=o((()=>{Qa(),O(),bu=class extends D{constructor({cause:e,message:t}={}){let n=t?.replace(`execution reverted: `,``)?.replace(`execution reverted`,``);super(`Execution reverted ${n?`with reason: ${n}`:`for an unknown reason`}.`,{cause:e,name:`ExecutionRevertedError`})}},Object.defineProperty(bu,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(bu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/execution reverted/}),xu=class extends D{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${Za(t)} gwei`:``}) cannot be higher than the maximum allowed value (2^256-1).`,{cause:e,name:`FeeCapTooHighError`})}},Object.defineProperty(xu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas higher than 2\^256-1|fee cap higher than 2\^256-1/}),Su=class extends D{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${Za(t)}`:``} gwei) cannot be lower than the block base fee.`,{cause:e,name:`FeeCapTooLowError`})}},Object.defineProperty(Su,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas less than block base fee|fee cap less than block base fee|transaction is outdated/}),Cu=class extends D{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}is higher than the next one expected.`,{cause:e,name:`NonceTooHighError`})}},Object.defineProperty(Cu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too high/}),wu=class extends D{constructor({cause:e,nonce:t}={}){super([`Nonce provided for the transaction ${t?`(${t}) `:``}is lower than the current nonce of the account.`,"Try increasing the nonce or find the latest nonce with `getTransactionCount`."].join(` -`),{cause:e,name:`NonceTooLowError`})}},Object.defineProperty(wu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too low|transaction already imported|already known/}),Tu=class extends D{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}exceeds the maximum allowed nonce.`,{cause:e,name:`NonceMaxValueError`})}},Object.defineProperty(Tu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce has max value/}),Eu=class extends D{constructor({cause:e}={}){super([`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`].join(` -`),{cause:e,metaMessages:[`This error could arise when the account does not have enough funds to:`,` - pay for the total gas fee,`,` - pay for the value to send.`,` `,"The cost of the transaction is calculated as `gas * gas fee + value`, where:"," - `gas` is the amount of gas needed for transaction to execute,"," - `gas fee` is the gas fee,"," - `value` is the amount of ether to send to the recipient."],name:`InsufficientFundsError`})}},Object.defineProperty(Eu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/insufficient funds|exceeds transaction sender account balance/}),Du=class extends D{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction exceeds the limit allowed for the block.`,{cause:e,name:`IntrinsicGasTooHighError`})}},Object.defineProperty(Du,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too high|gas limit reached/}),Ou=class extends D{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction is too low.`,{cause:e,name:`IntrinsicGasTooLowError`})}},Object.defineProperty(Ou,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too low/}),ku=class extends D{constructor({cause:e}){super(`The transaction type is not supported for this chain.`,{cause:e,name:`TransactionTypeNotSupportedError`})}},Object.defineProperty(ku,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/transaction type not valid/}),Au=class extends D{constructor({cause:e,maxPriorityFeePerGas:t,maxFeePerGas:n}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${t?` = ${Za(t)} gwei`:``}) cannot be higher than the fee cap (\`maxFeePerGas\`${n?` = ${Za(n)} gwei`:``}).`].join(` -`),{cause:e,name:`TipAboveFeeCapError`})}},Object.defineProperty(Au,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max priority fee per gas higher than max fee per gas|tip higher than fee cap/}),ju=class extends D{constructor({cause:e}){super(`An error occurred while executing: ${e?.shortMessage}`,{cause:e,name:`UnknownNodeError`})}}}));function Nu(e,t){let n=(e.details||``).toLowerCase(),r=e instanceof D?e.walk(e=>e?.code===bu.code):e;return r instanceof D?new bu({cause:e,message:r.details}):bu.nodeMessage.test(n)?new bu({cause:e,message:e.details}):xu.nodeMessage.test(n)?new xu({cause:e,maxFeePerGas:t?.maxFeePerGas}):Su.nodeMessage.test(n)?new Su({cause:e,maxFeePerGas:t?.maxFeePerGas}):Cu.nodeMessage.test(n)?new Cu({cause:e,nonce:t?.nonce}):wu.nodeMessage.test(n)?new wu({cause:e,nonce:t?.nonce}):Tu.nodeMessage.test(n)?new Tu({cause:e,nonce:t?.nonce}):Eu.nodeMessage.test(n)?new Eu({cause:e}):Du.nodeMessage.test(n)?new Du({cause:e,gas:t?.gas}):Ou.nodeMessage.test(n)?new Ou({cause:e,gas:t?.gas}):ku.nodeMessage.test(n)?new ku({cause:e}):Au.nodeMessage.test(n)?new Au({cause:e,maxFeePerGas:t?.maxFeePerGas,maxPriorityFeePerGas:t?.maxPriorityFeePerGas}):new ju({cause:e})}var Pu=o((()=>{O(),Mu()}));Mu(),Pu();function Fu(e,{docsPath:t,...n}){let r=(()=>{let t=Nu(e,n);return t instanceof ju?e:t})();return new yu(r,{docsPath:t,...n})}function Iu(e,{format:t}){if(!t)return{};let n={};function r(t){let i=Object.keys(t);for(let a of i)a in e&&(n[a]=e[a]),t[a]&&typeof t[a]==`object`&&!Array.isArray(t[a])&&r(t[a])}let i=t(e||{});return r(i),n}var Lu=o((()=>{}));function Ru(e,t){return({exclude:n,format:r})=>({exclude:n,format:(e,i)=>{let a=t(e,i);if(n)for(let e of n)delete a[e];return{...a,...r(e,i)}},type:e})}var zu=o((()=>{}));function Bu(e,t){let n={};return e.authorizationList!==void 0&&(n.authorizationList=Vu(e.authorizationList)),e.accessList!==void 0&&(n.accessList=e.accessList),e.blobVersionedHashes!==void 0&&(n.blobVersionedHashes=e.blobVersionedHashes),e.blobs!==void 0&&(typeof e.blobs[0]==`string`?n.blobs=e.blobs:n.blobs=e.blobs.map(e=>An(e))),e.data!==void 0&&(n.data=e.data),e.from!==void 0&&(n.from=e.from),e.gas!==void 0&&(n.gas=k(e.gas)),e.gasPrice!==void 0&&(n.gasPrice=k(e.gasPrice)),e.maxFeePerBlobGas!==void 0&&(n.maxFeePerBlobGas=k(e.maxFeePerBlobGas)),e.maxFeePerGas!==void 0&&(n.maxFeePerGas=k(e.maxFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(n.maxPriorityFeePerGas=k(e.maxPriorityFeePerGas)),e.nonce!==void 0&&(n.nonce=k(e.nonce)),e.to!==void 0&&(n.to=e.to),e.type!==void 0&&(n.type=Hu[e.type]),e.value!==void 0&&(n.value=k(e.value)),n}function Vu(e){return e.map(e=>({address:e.address,r:e.r?k(BigInt(e.r)):e.r,s:e.s?k(BigInt(e.s)):e.s,chainId:k(e.chainId),nonce:k(e.nonce),...e.yParity===void 0?{}:{yParity:k(e.yParity)},...e.v!==void 0&&e.yParity===void 0?{v:k(e.v)}:{}}))}var Hu,Uu,Wu=o((()=>{A(),zu(),Hu={legacy:`0x0`,eip2930:`0x1`,eip1559:`0x2`,eip4844:`0x3`,eip7702:`0x4`},Uu=Ru(`transactionRequest`,Bu)}));function Gu(e){if(!(!e||e.length===0))return e.reduce((e,{slot:t,value:n})=>{if(t.length!==66)throw new un({size:t.length,targetSize:66,type:`hex`});if(n.length!==66)throw new un({size:n.length,targetSize:66,type:`hex`});return e[t]=n,e},{})}function Ku(e){let{balance:t,nonce:n,state:r,stateDiff:i,code:a}=e,o={};if(a!==void 0&&(o.code=a),t!==void 0&&(o.balance=k(t)),n!==void 0&&(o.nonce=k(n)),r!==void 0&&(o.state=Gu(r)),i!==void 0){if(o.state)throw new no;o.stateDiff=Gu(i)}return o}function qu(e){if(!e)return;let t={};for(let{address:n,...r}of e){if(!bi(n,{strict:!1}))throw new fi({address:n});if(t[n])throw new to({address:n});t[n]=Ku(r)}return t}var Ju=o((()=>{pi(),dn(),ro(),Ci(),A()})),Yu,Xu,Zu=o((()=>{2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n)),Yu=2n**16n-1n,Xu=2n**256n-1n}));function Qu(e){let{account:t,gasPrice:n,maxFeePerGas:r,maxPriorityFeePerGas:i,to:a}=e,o=t?na(t):void 0;if(o&&!bi(o.address))throw new fi({address:o.address});if(a&&!bi(a))throw new fi({address:a});if(n!==void 0&&(r!==void 0||i!==void 0))throw new ao;if(r&&r>Xu)throw new xu({maxFeePerGas:r});if(i&&r&&i>r)throw new Au({maxFeePerGas:r,maxPriorityFeePerGas:i})}var $u=o((()=>{ra(),Zu(),pi(),Mu(),ho(),Ci()}));Qa(),O();var ed=class extends D{constructor(){super("`baseFeeMultiplier` must be greater than 1.",{name:`BaseFeeScalarError`})}},td=class extends D{constructor(){super(`Chain does not support EIP-1559 fees.`,{name:`Eip1559FeesNotSupportedError`})}},nd=class extends D{constructor({maxPriorityFeePerGas:e}){super(`\`maxFeePerGas\` cannot be less than the \`maxPriorityFeePerGas\` (${Za(e)} gwei).`,{name:`MaxFeePerGasTooLowError`})}};O();var rd=class extends D{constructor({blockHash:e,blockNumber:t}){let n=`Block`;e&&(n=`Block at hash "${e}"`),t&&(n=`Block at number "${t}"`),super(`${n} could not be found.`,{name:`BlockNotFoundError`})}};Dn(),zu();const id={"0x0":`legacy`,"0x1":`eip2930`,"0x2":`eip1559`,"0x3":`eip4844`,"0x4":`eip7702`};function ad(e,t){let n={...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,chainId:e.chainId?En(e.chainId):void 0,gas:e.gas?BigInt(e.gas):void 0,gasPrice:e.gasPrice?BigInt(e.gasPrice):void 0,maxFeePerBlobGas:e.maxFeePerBlobGas?BigInt(e.maxFeePerBlobGas):void 0,maxFeePerGas:e.maxFeePerGas?BigInt(e.maxFeePerGas):void 0,maxPriorityFeePerGas:e.maxPriorityFeePerGas?BigInt(e.maxPriorityFeePerGas):void 0,nonce:e.nonce?En(e.nonce):void 0,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,type:e.type?id[e.type]:void 0,typeHex:e.type?e.type:void 0,value:e.value?BigInt(e.value):void 0,v:e.v?BigInt(e.v):void 0};return e.authorizationList&&(n.authorizationList=sd(e.authorizationList)),n.yParity=(()=>{if(e.yParity)return Number(e.yParity);if(typeof n.v==`bigint`){if(n.v===0n||n.v===27n)return 0;if(n.v===1n||n.v===28n)return 1;if(n.v>=35n)return n.v%2n==0n?1:0}})(),n.type===`legacy`&&(delete n.accessList,delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas,delete n.yParity),n.type===`eip2930`&&(delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas),n.type===`eip1559`&&delete n.maxFeePerBlobGas,n}const od=Ru(`transaction`,ad);function sd(e){return e.map(e=>({address:e.address,chainId:Number(e.chainId),nonce:Number(e.nonce),r:e.r,s:e.s,yParity:Number(e.yParity)}))}zu();function cd(e,t){let n=(e.transactions??[]).map(e=>typeof e==`string`?e:ad(e));return{...e,baseFeePerGas:e.baseFeePerGas?BigInt(e.baseFeePerGas):null,blobGasUsed:e.blobGasUsed?BigInt(e.blobGasUsed):void 0,difficulty:e.difficulty?BigInt(e.difficulty):void 0,excessBlobGas:e.excessBlobGas?BigInt(e.excessBlobGas):void 0,gasLimit:e.gasLimit?BigInt(e.gasLimit):void 0,gasUsed:e.gasUsed?BigInt(e.gasUsed):void 0,hash:e.hash?e.hash:null,logsBloom:e.logsBloom?e.logsBloom:null,nonce:e.nonce?e.nonce:null,number:e.number?BigInt(e.number):null,size:e.size?BigInt(e.size):void 0,timestamp:e.timestamp?BigInt(e.timestamp):void 0,transactions:n,totalDifficulty:e.totalDifficulty?BigInt(e.totalDifficulty):null}}const ld=Ru(`block`,cd);A();async function ud(e,{blockHash:t,blockNumber:n,blockTag:r=e.experimental_blockTag??`latest`,includeTransactions:i}={}){let a=i??!1,o=n===void 0?void 0:k(n),s=null;if(s=t?await e.request({method:`eth_getBlockByHash`,params:[t,a]},{dedupe:!0}):await e.request({method:`eth_getBlockByNumber`,params:[o||r,a]},{dedupe:!!o}),!s)throw new rd({blockHash:t,blockNumber:n});return(e.chain?.formatters?.block?.format||cd)(s,`getBlock`)}async function dd(e){let t=await e.request({method:`eth_gasPrice`});return BigInt(t)}Dn();async function fd(e,t){let{block:n,chain:r=e.chain,request:i}=t||{};try{let t=r?.fees?.maxPriorityFeePerGas??r?.fees?.defaultPriorityFee;if(typeof t==`function`){let r=n||await E(e,ud,`getBlock`)({}),a=await t({block:r,client:e,request:i});if(a===null)throw Error();return a}if(t!==void 0)return t;let a=await e.request({method:`eth_maxPriorityFeePerGas`});return wn(a)}catch{let[t,r]=await Promise.all([n?Promise.resolve(n):E(e,ud,`getBlock`)({}),E(e,dd,`getGasPrice`)({})]);if(typeof t.baseFeePerGas!=`bigint`)throw new td;let i=r-t.baseFeePerGas;return i<0n?0n:i}}async function pd(e,t){let{block:n,chain:r=e.chain,request:i,type:a=`eip1559`}=t||{},o=await(async()=>typeof r?.fees?.baseFeeMultiplier==`function`?r.fees.baseFeeMultiplier({block:n,client:e,request:i}):r?.fees?.baseFeeMultiplier??1.2)();if(o<1)throw new ed;let s=10**(o.toString().split(`.`)[1]?.length??0),c=e=>e*BigInt(Math.ceil(o*s))/BigInt(s),l=n||await E(e,ud,`getBlock`)({});if(typeof r?.fees?.estimateFeesPerGas==`function`){let t=await r.fees.estimateFeesPerGas({block:n,client:e,multiply:c,request:i,type:a});if(t!==null)return t}if(a===`eip1559`){if(typeof l.baseFeePerGas!=`bigint`)throw new td;let t=typeof i?.maxPriorityFeePerGas==`bigint`?i.maxPriorityFeePerGas:await fd(e,{block:l,chain:r,request:i}),n=c(l.baseFeePerGas);return{maxFeePerGas:i?.maxFeePerGas??n+t,maxPriorityFeePerGas:t}}return{gasPrice:i?.gasPrice??c(await E(e,dd,`getGasPrice`)({}))}}Dn(),A();async function md(e,{address:t,blockTag:n=`latest`,blockNumber:r}){let i=await e.request({method:`eth_getTransactionCount`,params:[t,typeof r==`bigint`?k(r):n]},{dedupe:!!r});return En(i)}Wu(),Hn(),A();function hd(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Ln(e)):e.blobs,i=[];for(let e of r)i.push(Uint8Array.from(t.blobToKzgCommitment(e)));return n===`bytes`?i:i.map(e=>An(e))}Hn(),A();function gd(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Ln(e)):e.blobs,i=typeof e.commitments[0]==`string`?e.commitments.map(e=>Ln(e)):e.commitments,a=[];for(let e=0;eAn(e))}Is();const _d=Ns;Pt(),Hn(),A();function vd(e,t){let n=t||`hex`,r=_d(Nt(e,{strict:!1})?Pn(e):e);return n===`bytes`?r:On(r)}A();function yd(e){let{commitment:t,version:n=1}=e,r=e.to??(typeof t==`string`?`hex`:`bytes`),i=vd(t,`bytes`);return i.set([n],0),r===`bytes`?i:An(i)}function bd(e){let{commitments:t,version:n}=e,r=e.to??(typeof t[0]==`string`?`hex`:`bytes`),i=[];for(let e of t)i.push(yd({commitment:e,to:r,version:n}));return i}var xd=6;const Sd=4096,Cd=32*Sd,wd=Cd*xd-1-1*Sd*xd;O();var Td=class extends D{constructor({maxSize:e,size:t}){super(`Blob size is too large.`,{metaMessages:[`Max: ${e} bytes`,`Given: ${t} bytes`],name:`BlobSizeTooLargeError`})}},Ed=class extends D{constructor(){super(`Blob data must not be empty.`,{name:`EmptyBlobError`})}},Dd=class extends D{constructor({hash:e,size:t}){super(`Versioned hash "${e}" size is invalid.`,{metaMessages:[`Expected: 32`,`Received: ${t}`],name:`InvalidVersionedHashSizeError`})}},Od=class extends D{constructor({hash:e,version:t}){super(`Versioned hash "${e}" version is invalid.`,{metaMessages:[`Expected: 1`,`Received: ${t}`],name:`InvalidVersionedHashVersionError`})}};ya(),It(),Hn(),A();function kd(e){let t=e.to??(typeof e.data==`string`?`hex`:`bytes`),n=typeof e.data==`string`?Ln(e.data):e.data,r=Ft(n);if(!r)throw new Ed;if(r>761855)throw new Td({maxSize:wd,size:r});let i=[],a=!0,o=0;for(;a;){let e=_a(new Uint8Array(Cd)),t=0;for(;te.bytes):i.map(e=>An(e.bytes))}function Ad(e){let{data:t,kzg:n,to:r}=e,i=e.blobs??kd({data:t,to:r}),a=e.commitments??hd({blobs:i,kzg:n,to:r}),o=e.proofs??gd({blobs:i,commitments:a,kzg:n,to:r}),s=[];for(let e=0;e{if(v.to)return v.to;if(i&&i.length>0)return await vu({authorization:i[0]}).catch(()=>{throw new D("`to` is required. Could not infer from `authorizationList`")})})();Qu(t);let S=e.chain?.formatters?.transactionRequest?.format,C=(S||Bu)({...Iu(v,{format:S}),from:r?.address,accessList:n,authorizationList:i,blobs:a,blobVersionedHashes:o,data:l,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,to:x,value:g},`estimateGas`);return BigInt(await e.request({method:`eth_estimateGas`,params:b?[C,y??e.experimental_blockTag??`latest`,b]:y?[C,y]:[C]}))}catch(n){throw Fu(n,{...t,account:r,chain:e.chain})}}function Ld(e,t){if(!bi(e,{strict:!1}))throw new fi({address:e});if(!bi(t,{strict:!1}))throw new fi({address:t});return e.toLowerCase()===t.toLowerCase()}var Rd=o((()=>{pi(),Ci()}));function zd(e,{args:t,eventName:n}={}){return{...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,logIndex:e.logIndex?Number(e.logIndex):null,transactionHash:e.transactionHash?e.transactionHash:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,...n?{args:t,eventName:n}:{}}}function Bd(e){let{abi:t,args:n,functionName:r,data:i}=e,a=t[0];if(r){let e=Qi({abi:t,args:n,name:r});if(!e)throw new Zt(r,{docsPath:Vd});a=e}if(a.type!==`function`)throw new Zt(void 0,{docsPath:Vd});if(!a.outputs)throw new Qt(a.name,{docsPath:Vd});let o=Ta(a.outputs,i);if(o&&o.length>1)return o;if(o&&o.length===1)return o[0]}var Vd,Hd=o((()=>{sn(),La(),ta(),Vd=`/docs/contract/decodeFunctionResult`})),Ud,Wd=o((()=>{Ud=`0.1.1`}));function Gd(){return Ud}var Kd=o((()=>{Wd()}));function qd(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause?qd(e.cause,t):t?null:e}var P,Jd=o((()=>{Kd(),P=class e extends Error{constructor(t,n={}){let r=(()=>{if(n.cause instanceof e){if(n.cause.details)return n.cause.details;if(n.cause.shortMessage)return n.cause.shortMessage}return n.cause&&`details`in n.cause&&typeof n.cause.details==`string`?n.cause.details:n.cause?.message?n.cause.message:n.details})(),i=(()=>n.cause instanceof e&&n.cause.docsPath||n.docsPath)(),a=`https://oxlib.sh${i??``}`,o=[t||`An error occurred.`,...n.metaMessages?[``,...n.metaMessages]:[],...r||i?[``,r?`Details: ${r}`:void 0,i?`See: ${a}`:void 0]:[]].filter(e=>typeof e==`string`).join(` -`);super(o,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:`ox@${Gd()}`}),this.cause=n.cause,this.details=r,this.docs=a,this.docsPath=i,this.shortMessage=t}walk(e){return qd(this,e)}}}));function Yd(e,t){if(bf(e)>t)throw new Pf({givenSize:bf(e),maxSize:t})}function Xd(e,t){if(typeof t==`number`&&t>0&&t>bf(e)-1)throw new Ff({offset:t,position:`start`,size:bf(e)})}function Zd(e,t,n){if(typeof t==`number`&&typeof n==`number`&&bf(e)!==n-t)throw new Ff({offset:n,position:`end`,size:bf(e)})}function F(e){if(e>=ef.zero&&e<=ef.nine)return e-ef.zero;if(e>=ef.A&&e<=ef.F)return e-(ef.A-10);if(e>=ef.a&&e<=ef.f)return e-(ef.a-10)}function Qd(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;if(e.length>r)throw new If({size:e.length,targetSize:r,type:`Bytes`});let i=new Uint8Array(r);for(let t=0;t{Lf(),ef={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function nf(e,t){if(qf(e)>t)throw new rp({givenSize:qf(e),maxSize:t})}function rf(e,t){if(typeof t==`number`&&t>0&&t>qf(e)-1)throw new ip({offset:t,position:`start`,size:qf(e)})}function af(e,t,n){if(typeof t==`number`&&typeof n==`number`&&qf(e)!==n-t)throw new ip({offset:n,position:`end`,size:qf(e)})}function sf(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;let i=e.replace(`0x`,``);if(i.length>r*2)throw new ap({size:Math.ceil(i.length/2),targetSize:r,type:`Hex`});return`0x${i[n===`right`?`padEnd`:`padStart`](r*2,`0`)}`}function cf(e,t={}){let{dir:n=`left`}=t,r=e.replace(`0x`,``),i=0;for(let e=0;e{op()}));function uf(e,t){return JSON.parse(e,(e,n)=>{let r=n;return typeof r==`string`&&r.endsWith(ff)?BigInt(r.slice(0,-9)):typeof t==`function`?t(e,r):r})}function df(e,t,n){return JSON.stringify(e,(e,n)=>typeof t==`function`?t(e,n):typeof n==`bigint`?n.toString()+ff:n,n)}var ff,pf=o((()=>{ff=`#__bigint`}));function mf(e){if(!(e instanceof Uint8Array)&&(!e||typeof e!=`object`||!(`BYTES_PER_ELEMENT`in e)||e.BYTES_PER_ELEMENT!==1||e.constructor.name!==`Uint8Array`))throw new Nf(e)}function hf(e){return e instanceof Uint8Array?e:typeof e==`string`?_f(e):gf(e)}function gf(e){return e instanceof Uint8Array?e:new Uint8Array(e)}function _f(e,t={}){let{size:n}=t,r=e;n&&(nf(e,n),r=Gf(e,n));let i=r.slice(2);i.length%2&&(i=`0${i}`);let a=i.length/2,o=new Uint8Array(a);for(let e=0,t=0;e1||r[0]>1)throw new Mf(r);return!!r[0]}function wf(e,t={}){return Hf(e,t)}function Tf(e,t={}){let{size:n}=t;n!==void 0&&Yd(e,n);let r=Hf(e,t);return Xf(r,t)}function Ef(e,t={}){let{size:n}=t,r=e;return n!==void 0&&(Yd(r,n),r=Of(r)),Af.decode(r)}function Df(e){return $d(e,{dir:`left`})}function Of(e){return $d(e,{dir:`right`})}function kf(e){try{return mf(e),!0}catch{return!1}}var Af,jf,Mf,Nf,Pf,Ff,If,Lf=o((()=>{Jd(),op(),tf(),lf(),pf(),Af=new TextDecoder,jf=new TextEncoder,Mf=class extends P{constructor(e){super(`Bytes value \`${e}\` is not a valid boolean.`,{metaMessages:["The bytes array must contain a single byte of either a `0` or `1` value."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesBooleanError`})}},Nf=class extends P{constructor(e){super(`Value \`${typeof e==`object`?df(e):e}\` of type \`${typeof e}\` is an invalid Bytes value.`,{metaMessages:["Bytes values must be of type `Bytes`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesTypeError`})}},Pf=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeOverflowError`})}},Ff=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SliceOffsetOutOfBoundsError`})}},If=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeExceedsPaddingSizeError`})}}}));function Rf(e,t={}){let{strict:n=!1}=t;if(!e||typeof e!=`string`)throw new tp(e);if(n&&!/^0x[0-9a-fA-F]*$/.test(e)||!e.startsWith(`0x`))throw new np(e)}function zf(...e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}function Bf(e){return e instanceof Uint8Array?Hf(e):Array.isArray(e)?Hf(new Uint8Array(e)):e}function Vf(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(nf(n,t.size),Wf(n,t.size)):n}function Hf(e,t={}){let n=``;for(let t=0;ta||i>1n;return r<=o?r:r-a-1n}function Xf(e,t={}){let{signed:n,size:r}=t;return!n&&!r?Number(e):Number(Yf(e,t))}function Zf(e,t={}){let{strict:n=!1}=t;try{return Rf(e,{strict:n}),!0}catch{return!1}}var Qf,$f,ep,tp,np,rp,ip,ap,op=o((()=>{Jd(),lf(),pf(),Qf=new TextEncoder,$f=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),ep=class extends P{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number \`${i}\` is not in safe${r?` ${r*8}-bit`:``}${n?` signed`:` unsigned`} integer range ${e?`(\`${t}\` to \`${e}\`)`:`(above \`${t}\`)`}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.IntegerOutOfRangeError`})}},tp=class extends P{constructor(e){super(`Value \`${typeof e==`object`?df(e):e}\` of type \`${typeof e}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexTypeError`})}},np=class extends P{constructor(e){super(`Value \`${e}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexValueError`})}},rp=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeOverflowError`})}},ip=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SliceOffsetOutOfBoundsError`})}},ap=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeExceedsPaddingSizeError`})}}}));function sp(e){return{address:e.address,amount:I(e.amount),index:I(e.index),validatorIndex:I(e.validatorIndex)}}var cp=o((()=>{op()}));function lp(e){return{...typeof e.baseFeePerGas==`bigint`&&{baseFeePerGas:I(e.baseFeePerGas)},...typeof e.blobBaseFee==`bigint`&&{blobBaseFee:I(e.blobBaseFee)},...typeof e.feeRecipient==`string`&&{feeRecipient:e.feeRecipient},...typeof e.gasLimit==`bigint`&&{gasLimit:I(e.gasLimit)},...typeof e.number==`bigint`&&{number:I(e.number)},...typeof e.prevRandao==`bigint`&&{prevRandao:I(e.prevRandao)},...typeof e.time==`bigint`&&{time:I(e.time)},...e.withdrawals&&{withdrawals:e.withdrawals.map(sp)}}}var up=o((()=>{op(),cp()})),dp,fp,pp,mp,hp,gp=o((()=>{dp=[{inputs:[{components:[{name:`target`,type:`address`},{name:`allowFailure`,type:`bool`},{name:`callData`,type:`bytes`}],name:`calls`,type:`tuple[]`}],name:`aggregate3`,outputs:[{components:[{name:`success`,type:`bool`},{name:`returnData`,type:`bytes`}],name:`returnData`,type:`tuple[]`}],stateMutability:`view`,type:`function`},{inputs:[],name:`getCurrentBlockTimestamp`,outputs:[{internalType:`uint256`,name:`timestamp`,type:`uint256`}],stateMutability:`view`,type:`function`}],fp=[{name:`query`,type:`function`,stateMutability:`view`,inputs:[{type:`tuple[]`,name:`queries`,components:[{type:`address`,name:`sender`},{type:`string[]`,name:`urls`},{type:`bytes`,name:`data`}]}],outputs:[{type:`bool[]`,name:`failures`},{type:`bytes[]`,name:`responses`}]},{name:`HttpError`,type:`error`,inputs:[{type:`uint16`,name:`status`},{type:`string`,name:`message`}]}],pp=[{inputs:[{name:`dns`,type:`bytes`}],name:`DNSDecodingFailed`,type:`error`},{inputs:[{name:`ens`,type:`string`}],name:`DNSEncodingFailed`,type:`error`},{inputs:[],name:`EmptyAddress`,type:`error`},{inputs:[{name:`status`,type:`uint16`},{name:`message`,type:`string`}],name:`HttpError`,type:`error`},{inputs:[],name:`InvalidBatchGatewayResponse`,type:`error`},{inputs:[{name:`errorData`,type:`bytes`}],name:`ResolverError`,type:`error`},{inputs:[{name:`name`,type:`bytes`},{name:`resolver`,type:`address`}],name:`ResolverNotContract`,type:`error`},{inputs:[{name:`name`,type:`bytes`}],name:`ResolverNotFound`,type:`error`},{inputs:[{name:`primary`,type:`string`},{name:`primaryAddress`,type:`bytes`}],name:`ReverseAddressMismatch`,type:`error`},{inputs:[{internalType:`bytes4`,name:`selector`,type:`bytes4`}],name:`UnsupportedResolverProfile`,type:`error`}],[...pp],[...pp],mp=[{name:`isValidSignature`,type:`function`,stateMutability:`view`,inputs:[{name:`hash`,type:`bytes32`},{name:`signature`,type:`bytes`}],outputs:[{name:``,type:`bytes4`}]}],hp=[{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],stateMutability:`nonpayable`,type:`constructor`},{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],outputs:[{type:`bool`}],stateMutability:`nonpayable`,type:`function`,name:`isValidSig`}]})),_p=o((()=>{})),vp,yp,bp,xp,Sp=o((()=>{vp=`0x608060405234801561001057600080fd5b5060405161018e38038061018e83398101604081905261002f91610124565b6000808351602085016000f59050803b61004857600080fd5b6000808351602085016000855af16040513d6000823e81610067573d81fd5b3d81f35b634e487b7160e01b600052604160045260246000fd5b600082601f83011261009257600080fd5b81516001600160401b038111156100ab576100ab61006b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156100d9576100d961006b565b6040528181528382016020018510156100f157600080fd5b60005b82811015610110576020818601810151838301820152016100f4565b506000918101602001919091529392505050565b6000806040838503121561013757600080fd5b82516001600160401b0381111561014d57600080fd5b61015985828601610081565b602085015190935090506001600160401b0381111561017757600080fd5b61018385828601610081565b915050925092905056fe`,yp=`0x608060405234801561001057600080fd5b506040516102c03803806102c083398101604081905261002f916101e6565b836001600160a01b03163b6000036100e457600080836001600160a01b03168360405161005c9190610270565b6000604051808303816000865af19150503d8060008114610099576040519150601f19603f3d011682016040523d82523d6000602084013e61009e565b606091505b50915091508115806100b857506001600160a01b0386163b155b156100e1578060405163101bb98d60e01b81526004016100d8919061028c565b60405180910390fd5b50505b6000808451602086016000885af16040513d6000823e81610103573d81fd5b3d81f35b80516001600160a01b038116811461011e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015457818101518382015260200161013c565b50506000910152565b600082601f83011261016e57600080fd5b81516001600160401b0381111561018757610187610123565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101b5576101b5610123565b6040528181528382016020018510156101cd57600080fd5b6101de826020830160208701610139565b949350505050565b600080600080608085870312156101fc57600080fd5b61020585610107565b60208601519094506001600160401b0381111561022157600080fd5b61022d8782880161015d565b93505061023c60408601610107565b60608601519092506001600160401b0381111561025857600080fd5b6102648782880161015d565b91505092959194509250565b60008251610282818460208701610139565b9190910192915050565b60208152600082518060208401526102ab816040850160208701610139565b601f01601f1916919091016040019291505056fe`,bp=`0x608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572`,xp=`0x608060405234801561001057600080fd5b506115b9806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e14610325578063bce38bd714610350578063c3077fa914610380578063ee82ac5e146103b2576100f3565b80634d2301cc1461026257806372425d9d1461029f57806382ad56cb146102ca57806386d516e8146102fa576100f3565b80633408e470116100c65780633408e470146101af578063399542e9146101da5780633e64a6961461020c57806342cbb15c14610237576100f3565b80630f28c97d146100f8578063174dea7114610123578063252dba421461015357806327e86d6e14610184575b600080fd5b34801561010457600080fd5b5061010d6103ef565b60405161011a9190610c0a565b60405180910390f35b61013d60048036038101906101389190610c94565b6103f7565b60405161014a9190610e94565b60405180910390f35b61016d60048036038101906101689190610f0c565b610615565b60405161017b92919061101b565b60405180910390f35b34801561019057600080fd5b506101996107ab565b6040516101a69190611064565b60405180910390f35b3480156101bb57600080fd5b506101c46107b7565b6040516101d19190610c0a565b60405180910390f35b6101f460048036038101906101ef91906110ab565b6107bf565b6040516102039392919061110b565b60405180910390f35b34801561021857600080fd5b506102216107e1565b60405161022e9190610c0a565b60405180910390f35b34801561024357600080fd5b5061024c6107e9565b6040516102599190610c0a565b60405180910390f35b34801561026e57600080fd5b50610289600480360381019061028491906111a7565b6107f1565b6040516102969190610c0a565b60405180910390f35b3480156102ab57600080fd5b506102b4610812565b6040516102c19190610c0a565b60405180910390f35b6102e460048036038101906102df919061122a565b61081a565b6040516102f19190610e94565b60405180910390f35b34801561030657600080fd5b5061030f6109e4565b60405161031c9190610c0a565b60405180910390f35b34801561033157600080fd5b5061033a6109ec565b6040516103479190611286565b60405180910390f35b61036a600480360381019061036591906110ab565b6109f4565b6040516103779190610e94565b60405180910390f35b61039a60048036038101906103959190610f0c565b610ba6565b6040516103a99392919061110b565b60405180910390f35b3480156103be57600080fd5b506103d960048036038101906103d491906112cd565b610bca565b6040516103e69190611064565b60405180910390f35b600042905090565b60606000808484905090508067ffffffffffffffff81111561041c5761041b6112fa565b5b60405190808252806020026020018201604052801561045557816020015b610442610bd5565b81526020019060019003908161043a5790505b5092503660005b828110156105c957600085828151811061047957610478611329565b5b6020026020010151905087878381811061049657610495611329565b5b90506020028101906104a89190611367565b925060008360400135905080860195508360000160208101906104cb91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16818580606001906104f2919061138f565b604051610500929190611431565b60006040518083038185875af1925050503d806000811461053d576040519150601f19603f3d011682016040523d82523d6000602084013e610542565b606091505b5083600001846020018290528215151515815250505081516020850135176105bc577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b826001019250505061045c565b5082341461060c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610603906114a7565b60405180910390fd5b50505092915050565b6000606043915060008484905090508067ffffffffffffffff81111561063e5761063d6112fa565b5b60405190808252806020026020018201604052801561067157816020015b606081526020019060019003908161065c5790505b5091503660005b828110156107a157600087878381811061069557610694611329565b5b90506020028101906106a791906114c7565b92508260000160208101906106bc91906111a7565b73ffffffffffffffffffffffffffffffffffffffff168380602001906106e2919061138f565b6040516106f0929190611431565b6000604051808303816000865af19150503d806000811461072d576040519150601f19603f3d011682016040523d82523d6000602084013e610732565b606091505b5086848151811061074657610745611329565b5b60200260200101819052819250505080610795576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161078c9061153b565b60405180910390fd5b81600101915050610678565b5050509250929050565b60006001430340905090565b600046905090565b6000806060439250434091506107d68686866109f4565b905093509350939050565b600048905090565b600043905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b606060008383905090508067ffffffffffffffff81111561083e5761083d6112fa565b5b60405190808252806020026020018201604052801561087757816020015b610864610bd5565b81526020019060019003908161085c5790505b5091503660005b828110156109db57600084828151811061089b5761089a611329565b5b602002602001015190508686838181106108b8576108b7611329565b5b90506020028101906108ca919061155b565b92508260000160208101906108df91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060400190610905919061138f565b604051610913929190611431565b6000604051808303816000865af19150503d8060008114610950576040519150601f19603f3d011682016040523d82523d6000602084013e610955565b606091505b5082600001836020018290528215151515815250505080516020840135176109cf577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b8160010191505061087e565b50505092915050565b600045905090565b600041905090565b606060008383905090508067ffffffffffffffff811115610a1857610a176112fa565b5b604051908082528060200260200182016040528015610a5157816020015b610a3e610bd5565b815260200190600190039081610a365790505b5091503660005b82811015610b9c576000848281518110610a7557610a74611329565b5b60200260200101519050868683818110610a9257610a91611329565b5b9050602002810190610aa491906114c7565b9250826000016020810190610ab991906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060200190610adf919061138f565b604051610aed929190611431565b6000604051808303816000865af19150503d8060008114610b2a576040519150601f19603f3d011682016040523d82523d6000602084013e610b2f565b606091505b508260000183602001829052821515151581525050508715610b90578060000151610b8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b869061153b565b60405180910390fd5b5b81600101915050610a58565b5050509392505050565b6000806060610bb7600186866107bf565b8093508194508295505050509250925092565b600081409050919050565b6040518060400160405280600015158152602001606081525090565b6000819050919050565b610c0481610bf1565b82525050565b6000602082019050610c1f6000830184610bfb565b92915050565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f840112610c5457610c53610c2f565b5b8235905067ffffffffffffffff811115610c7157610c70610c34565b5b602083019150836020820283011115610c8d57610c8c610c39565b5b9250929050565b60008060208385031215610cab57610caa610c25565b5b600083013567ffffffffffffffff811115610cc957610cc8610c2a565b5b610cd585828601610c3e565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b610d2281610d0d565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610d62578082015181840152602081019050610d47565b83811115610d71576000848401525b50505050565b6000601f19601f8301169050919050565b6000610d9382610d28565b610d9d8185610d33565b9350610dad818560208601610d44565b610db681610d77565b840191505092915050565b6000604083016000830151610dd96000860182610d19565b5060208301518482036020860152610df18282610d88565b9150508091505092915050565b6000610e0a8383610dc1565b905092915050565b6000602082019050919050565b6000610e2a82610ce1565b610e348185610cec565b935083602082028501610e4685610cfd565b8060005b85811015610e825784840389528151610e638582610dfe565b9450610e6e83610e12565b925060208a01995050600181019050610e4a565b50829750879550505050505092915050565b60006020820190508181036000830152610eae8184610e1f565b905092915050565b60008083601f840112610ecc57610ecb610c2f565b5b8235905067ffffffffffffffff811115610ee957610ee8610c34565b5b602083019150836020820283011115610f0557610f04610c39565b5b9250929050565b60008060208385031215610f2357610f22610c25565b5b600083013567ffffffffffffffff811115610f4157610f40610c2a565b5b610f4d85828601610eb6565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000610f918383610d88565b905092915050565b6000602082019050919050565b6000610fb182610f59565b610fbb8185610f64565b935083602082028501610fcd85610f75565b8060005b858110156110095784840389528151610fea8582610f85565b9450610ff583610f99565b925060208a01995050600181019050610fd1565b50829750879550505050505092915050565b60006040820190506110306000830185610bfb565b81810360208301526110428184610fa6565b90509392505050565b6000819050919050565b61105e8161104b565b82525050565b60006020820190506110796000830184611055565b92915050565b61108881610d0d565b811461109357600080fd5b50565b6000813590506110a58161107f565b92915050565b6000806000604084860312156110c4576110c3610c25565b5b60006110d286828701611096565b935050602084013567ffffffffffffffff8111156110f3576110f2610c2a565b5b6110ff86828701610eb6565b92509250509250925092565b60006060820190506111206000830186610bfb565b61112d6020830185611055565b818103604083015261113f8184610e1f565b9050949350505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061117482611149565b9050919050565b61118481611169565b811461118f57600080fd5b50565b6000813590506111a18161117b565b92915050565b6000602082840312156111bd576111bc610c25565b5b60006111cb84828501611192565b91505092915050565b60008083601f8401126111ea576111e9610c2f565b5b8235905067ffffffffffffffff81111561120757611206610c34565b5b60208301915083602082028301111561122357611222610c39565b5b9250929050565b6000806020838503121561124157611240610c25565b5b600083013567ffffffffffffffff81111561125f5761125e610c2a565b5b61126b858286016111d4565b92509250509250929050565b61128081611169565b82525050565b600060208201905061129b6000830184611277565b92915050565b6112aa81610bf1565b81146112b557600080fd5b50565b6000813590506112c7816112a1565b92915050565b6000602082840312156112e3576112e2610c25565b5b60006112f1848285016112b8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600080fd5b600080fd5b600080fd5b60008235600160800383360303811261138357611382611358565b5b80830191505092915050565b600080833560016020038436030381126113ac576113ab611358565b5b80840192508235915067ffffffffffffffff8211156113ce576113cd61135d565b5b6020830192506001820236038313156113ea576113e9611362565b5b509250929050565b600081905092915050565b82818337600083830152505050565b600061141883856113f2565b93506114258385846113fd565b82840190509392505050565b600061143e82848661140c565b91508190509392505050565b600082825260208201905092915050565b7f4d756c746963616c6c333a2076616c7565206d69736d61746368000000000000600082015250565b6000611491601a8361144a565b915061149c8261145b565b602082019050919050565b600060208201905081810360008301526114c081611484565b9050919050565b6000823560016040038336030381126114e3576114e2611358565b5b80830191505092915050565b7f4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000600082015250565b600061152560178361144a565b9150611530826114ef565b602082019050919050565b6000602082019050818103600083015261155481611518565b9050919050565b60008235600160600383360303811261157757611576611358565b5b8083019150509291505056fea264697066735822122020c1bc9aacf8e4a6507193432a895a8e77094f45a1395583f07b24e860ef06cd64736f6c634300080c0033`})),Cp,wp,Tp,Ep,Dp,Op=o((()=>{O(),Cp=class extends D{constructor({blockNumber:e,chain:t,contract:n}){super(`Chain "${t.name}" does not support contract "${n.name}".`,{metaMessages:[`This could be due to any of the following:`,...e&&n.blockCreated&&n.blockCreated>e?[`- The contract "${n.name}" was not deployed until block ${n.blockCreated} (current block ${e}).`]:[`- The chain does not have the contract "${n.name}" configured.`]],name:`ChainDoesNotSupportContract`})}},wp=class extends D{constructor({chain:e,currentChainId:t}){super(`The current chain of the wallet (id: ${t}) does not match the target chain for the transaction (id: ${e.id} – ${e.name}).`,{metaMessages:[`Current Chain ID: ${t}`,`Expected Chain ID: ${e.id} – ${e.name}`],name:`ChainMismatchError`})}},Tp=class extends D{constructor(){super([`No chain was provided to the request.`,"Please provide a chain with the `chain` argument on the Action, or by supplying a `chain` to WalletClient."].join(` -`),{name:`ChainNotFoundError`})}},Ep=class extends D{constructor(){super(`No chain was provided to the Client.`,{name:`ClientChainNotConfiguredError`})}},Dp=class extends D{constructor({chainId:e}){super(typeof e==`number`?`Chain ID "${e}" is invalid.`:`Chain ID is invalid.`,{name:`InvalidChainIdError`})}}}));function kp(e){let{abi:t,args:n,bytecode:r}=e;if(!n||n.length===0)return r;let i=t.find(e=>`type`in e&&e.type===`constructor`);if(!i)throw new Vt({docsPath:Ap});if(!(`inputs`in i)||!i.inputs||i.inputs.length===0)throw new Ht({docsPath:Ap});let a=Li(i.inputs,n);return Ei([r,a])}var Ap,jp=o((()=>{sn(),Di(),Yi(),Ap=`/docs/contract/encodeDeployData`}));function Mp({blockNumber:e,chain:t,contract:n}){let r=t?.contracts?.[n];if(!r)throw new Cp({chain:t,contract:{name:n}});if(e&&r.blockCreated&&r.blockCreated>e)throw new Cp({blockNumber:e,chain:t,contract:{name:n,blockCreated:r.blockCreated}});return r.address}var Np=o((()=>{Op()}));function Pp(e,{docsPath:t,...n}){let r=(()=>{let t=Nu(e,n);return t instanceof ju?e:t})();return new yo(r,{docsPath:t,...n})}var Fp=o((()=>{To(),Mu(),Pu()}));function Ip(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}var Lp=o((()=>{}));function Rp({fn:e,id:t,shouldSplitBatch:n,wait:r=0,sort:i}){let a=async()=>{let t=c();o();let n=t.map(({args:e})=>e);n.length!==0&&e(n).then(e=>{i&&Array.isArray(e)&&e.sort(i);for(let n=0;n{for(let n=0;nzp.delete(t),s=()=>c().map(({args:e})=>e),c=()=>zp.get(t)||[],l=e=>zp.set(t,[...c(),e]);return{flush:o,async schedule(e){let{promise:t,resolve:i,reject:o}=Ip();return n?.([...s(),e])&&a(),c().length>0?(l({args:e,resolve:i,reject:o}),t):(l({args:e,resolve:i,reject:o}),setTimeout(a,r),t)}}}var zp,Bp=o((()=>{Lp(),zp=new Map})),Vp,Hp,Up,Wp=o((()=>{Va(),O(),vo(),Vp=class extends D{constructor({callbackSelector:e,cause:t,data:n,extraData:r,sender:i,urls:a}){super(t.shortMessage||`An error occurred while fetching for an offchain result.`,{cause:t,metaMessages:[...t.metaMessages||[],t.metaMessages?.length?``:[],`Offchain Gateway Call:`,a&&[` Gateway URL(s):`,...a.map(e=>` ${_o(e)}`)],` Sender: ${i}`,` Data: ${n}`,` Callback selector: ${e}`,` Extra data: ${r}`].flat(),name:`OffchainLookupError`})}},Hp=class extends D{constructor({result:e,url:t}){super(`Offchain gateway response is malformed. Response data must be a hex value.`,{metaMessages:[`Gateway URL: ${_o(t)}`,`Response: ${Ba(e)}`],name:`OffchainLookupResponseMalformedError`})}},Up=class extends D{constructor({sender:e,to:t}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${t}`,`OffchainLookup sender address: ${e}`],name:`OffchainLookupSenderMismatchError`})}}}));function Gp(e){let{abi:t,data:n}=e,r=Oi(n,0,4),i=t.find(e=>e.type===`function`&&r===Xi(kt(e)));if(!i)throw new $t(r,{docsPath:`/docs/contract/decodeFunctionData`});return{functionName:i.name,args:`inputs`in i&&i.inputs&&i.inputs.length>0?Ta(i.inputs,Oi(n,4)):void 0}}var Kp=o((()=>{sn(),Ni(),Zi(),La(),Mt()}));function qp(e){let{abi:t,errorName:n,args:r}=e,i=t[0];if(n){let e=Qi({abi:t,args:r,name:n});if(!e)throw new Yt(n,{docsPath:Jp});i=e}if(i.type!==`error`)throw new Yt(void 0,{docsPath:Jp});let a=kt(i),o=Xi(a),s=`0x`;if(r&&r.length>0){if(!i.inputs)throw new Jt(i.name,{docsPath:Jp});s=Li(i.inputs,r)}return Ei([o,s])}var Jp,Yp=o((()=>{sn(),Di(),Zi(),Yi(),Mt(),ta(),Jp=`/docs/contract/encodeErrorResult`}));function Xp(e){let{abi:t,functionName:n,result:r}=e,i=t[0];if(n){let e=Qi({abi:t,name:n});if(!e)throw new Zt(n,{docsPath:Zp});i=e}if(i.type!==`function`)throw new Zt(void 0,{docsPath:Zp});if(!i.outputs)throw new Qt(i.name,{docsPath:Zp});let a=(()=>{if(i.outputs.length===0)return[];if(i.outputs.length===1)return[r];if(Array.isArray(r))return r;throw new an(r)})();return Li(i.outputs,a)}var Zp,Qp=o((()=>{sn(),Yi(),ta(),Zp=`/docs/contract/encodeFunctionResult`}));async function $p(e){let{data:t,ccipRequest:n}=e,{args:[r]}=Gp({abi:fp,data:t}),i=[],a=[];return await Promise.all(r.map(async(e,t)=>{try{a[t]=e.urls.includes(`x-batch-gateway:true`)?await $p({data:e.data,ccipRequest:n}):await n(e),i[t]=!1}catch(e){i[t]=!0,a[t]=em(e)}})),Xp({abi:fp,functionName:`query`,result:[i,a]})}function em(e){return e.name===`HttpRequestError`&&e.status?qp({abi:fp,errorName:`HttpError`,args:[e.status,e.shortMessage]}):qp({abi:[ua],errorName:`Error`,args:[`shortMessage`in e?e.shortMessage:e.message]})}var tm=o((()=>{gp(),fa(),Kp(),Yp(),Qp()})),nm=c({ccipRequest:()=>im,offchainLookup:()=>rm,offchainLookupAbiItem:()=>om,offchainLookupSignature:()=>am});async function rm(e,{blockNumber:t,blockTag:n,data:r,to:i}){let{args:a}=Ra({data:r,abi:[om]}),[o,s,c,l,u]=a,{ccipRead:d}=e,f=d&&typeof d?.request==`function`?d.request:im;try{if(!Ld(i,o))throw new Up({sender:o,to:i});let r=s.includes(`x-batch-gateway:true`)?await $p({data:c,ccipRequest:f}):await f({data:c,sender:o,urls:s}),{data:a}=await cm(e,{blockNumber:t,blockTag:n,data:wi([l,Li([{type:`bytes`},{type:`bytes`}],[r,u])]),to:i});return a}catch(e){throw new Vp({callbackSelector:l,cause:e,data:r,extraData:u,sender:o,urls:s})}}async function im({data:e,sender:t,urls:n}){let r=Error(`An unknown error occurred.`);for(let i=0;i{mm(),Wp(),ko(),za(),Yi(),Rd(),Di(),Pt(),tm(),Va(),am=`0x556f1830`,om={name:`OffchainLookup`,type:`error`,inputs:[{name:`sender`,type:`address`},{name:`urls`,type:`string[]`},{name:`callData`,type:`bytes`},{name:`callbackFunction`,type:`bytes4`},{name:`extraData`,type:`bytes`}]}}));async function cm(e,t){let{account:n=e.account,authorizationList:r,batch:i=!!e.batch?.multicall,blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,accessList:s,blobs:c,blockOverrides:l,code:u,data:d,factory:f,factoryData:p,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:b,value:x,stateOverride:S,...C}=t,w=n?na(n):void 0;if(u&&(f||p))throw new D("Cannot provide both `code` & `factory`/`factoryData` as parameters.");if(u&&b)throw new D("Cannot provide both `code` & `to` as parameters.");let ee=u&&d,te=f&&p&&b&&d,ne=ee||te,re=(()=>ee?dm({code:u,data:d}):te?fm({data:d,factory:f,factoryData:p,to:b}):d)();try{Qu(t);let n=(typeof a==`bigint`?k(a):void 0)||o,u=l?lp(l):void 0,d=qu(S),f=e.chain?.formatters?.transactionRequest?.format,p=(f||Bu)({...Iu(C,{format:f}),from:w?.address,accessList:s,authorizationList:r,blobs:c,data:re,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:ne?void 0:b,value:x},`call`);if(i&&lm({request:p})&&!d&&!u)try{return await um(e,{...p,blockNumber:a,blockTag:o})}catch(e){if(!(e instanceof Ep)&&!(e instanceof Cp))throw e}let ee=(()=>{let e=[p,n];return d&&u?[...e,d,u]:d?[...e,d]:u?[...e,{},u]:e})(),te=await e.request({method:`eth_call`,params:ee});return te===`0x`?{data:void 0}:{data:te}}catch(n){let r=pm(n),{offchainLookup:i,offchainLookupSignature:a}=await ps(async()=>{let{offchainLookup:e,offchainLookupSignature:t}=await Promise.resolve().then(()=>(sm(),nm));return{offchainLookup:e,offchainLookupSignature:t}},void 0);if(e.ccipRead!==!1&&r?.slice(0,10)===a&&b)return{data:await i(e,{data:r,to:b})};throw ne&&r?.slice(0,10)===`0x101bb98d`?new Co({factory:f}):Pp(n,{...t,account:w,chain:e.chain})}}function lm({request:e}){let{data:t,to:n,...r}=e;return!(!t||t.startsWith(`0x82ad56cb`)||!n||Object.values(r).filter(e=>e!==void 0).length>0)}async function um(e,t){let{batchSize:n=1024,deployless:r=!1,wait:i=0}=typeof e.batch?.multicall==`object`?e.batch.multicall:{},{blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,data:s,to:c}=t,l=(()=>{if(r)return null;if(t.multicallAddress)return t.multicallAddress;if(e.chain)return Mp({blockNumber:a,chain:e.chain,contract:`multicall3`});throw new Ep})(),u=(typeof a==`bigint`?k(a):void 0)||o,{schedule:d}=Rp({id:`${e.uid}.${u}`,wait:i,shouldSplitBatch(e){return e.reduce((e,{data:t})=>e+(t.length-2),0)>n*2},fn:async t=>{let n=t.map(e=>({allowFailure:!0,callData:e.data,target:e.to})),r=sa({abi:dp,args:[n],functionName:`aggregate3`}),i=await e.request({method:`eth_call`,params:[{...l===null?{data:dm({code:xp,data:r})}:{to:l,data:r}},u]});return Bd({abi:dp,args:[n],functionName:`aggregate3`,data:i||`0x`})}}),[{returnData:f,success:p}]=await d({data:s,to:c});if(!p)throw new wo({data:f});return f===`0x`?{data:void 0}:{data:f}}function dm(e){let{code:t,data:n}=e;return kp({abi:St([`constructor(bytes, bytes)`]),bytecode:vp,args:[t,n]})}function fm(e){let{data:t,factory:n,factoryData:r,to:i}=e;return kp({abi:St([`constructor(address, bytes, address, bytes)`]),bytecode:yp,args:[i,t,n,r]})}function pm(e){if(!(e instanceof D))return;let t=e.walk();return typeof t?.data==`object`?t.data?.data:t.data}var mm=o((()=>{Ot(),up(),ra(),gp(),_p(),Sp(),O(),Op(),To(),Hd(),jp(),ca(),Np(),A(),Fp(),Lu(),Wu(),Bp(),Ju(),$u(),ms()}));Hd(),ca(),mm();async function hm(e,t){let{abi:n,address:r,args:i,functionName:a,...o}=t,s=sa({abi:n,args:i,functionName:a});try{let{data:t}=await E(e,cm,`call`)({...o,data:s,to:r});return Bd({abi:n,args:i,functionName:a,data:t||`0x`})}catch(e){throw cs(e,{abi:n,address:r,args:i,docsPath:`/docs/contract/readContract`,functionName:a})}}const gm=new Map,_m=new Map;var vm=0;function ym(e,t,n){let r=++vm,i=()=>gm.get(e)||[],a=()=>{let t=i();gm.set(e,t.filter(e=>e.id!==r))},o=()=>{let t=i();if(!t.some(e=>e.id===r))return;let n=_m.get(e);if(t.length===1&&n){let e=n();e instanceof Promise&&e.catch(()=>{})}a()},s=i();if(gm.set(e,[...s,{id:r,fns:t}]),s&&s.length>0)return o;let c={};for(let e in t)c[e]=((...t)=>{let n=i();if(n.length!==0)for(let r of n)r.fns[e]?.(...t)});let l=n(c);return typeof l==`function`&&_m.set(e,l),o}async function bm(e){return new Promise(t=>setTimeout(t,e))}function xm(e,{emitOnBegin:t,initialWaitTime:n,interval:r}){let i=!0,a=()=>i=!1;return(async()=>{let o;t&&(o=await e({unpoll:a}));let s=await n?.(o)??r;await bm(s);let c=async()=>{i&&(await e({unpoll:a}),await bm(r),c())};c()})(),a}const Sm=new Map,Cm=new Map;function wm(e){let t=(e,t)=>({clear:()=>t.delete(e),get:()=>t.get(e),set:n=>t.set(e,n)}),n=t(e,Sm),r=t(e,Cm);return{clear:()=>{n.clear(),r.clear()},promise:n,response:r}}async function Tm(e,{cacheKey:t,cacheTime:n=1/0}){let r=wm(t),i=r.response.get();if(i&&n>0&&Date.now()-i.created.getTime()`blockNumber.${e}`;async function Dm(e,{cacheTime:t=e.cacheTime}={}){let n=await Tm(()=>e.request({method:`eth_blockNumber`}),{cacheKey:Em(e.uid),cacheTime:t});return BigInt(n)}O();var Om=class extends D{constructor({docsPath:e}={}){super([`Could not find an Account to execute with this Action.`,"Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client."].join(` -`),{docsPath:e,docsSlug:`account`,name:`AccountNotFoundError`})}},km=class extends D{constructor({docsPath:e,metaMessages:t,type:n}){super(`Account type "${n}" is not supported.`,{docsPath:e,metaMessages:t,name:`AccountTypeNotSupportedError`})}};Op();function Am({chain:e,currentChainId:t}){if(!e)throw new Tp;if(t!==e.id)throw new wp({chain:e,currentChainId:t})}Mu(),ho(),Pu();function jm(e,{docsPath:t,...n}){let r=(()=>{let t=Nu(e,n);return t instanceof ju?e:t})();return new lo(r,{docsPath:t,...n})}async function Mm(e,{serializedTransaction:t}){return e.request({method:`eth_sendRawTransaction`,params:[t]},{retryCount:0})}ra(),O(),Lu(),Wu(),hi(),$u();var Nm=new mi(128);async function Pm(e,t){let{account:n=e.account,chain:r=e.chain,accessList:i,authorizationList:a,blobs:o,data:s,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,type:m,value:h,...g}=t;if(n===void 0)throw new Om({docsPath:`/docs/actions/wallet/sendTransaction`});let _=n?na(n):null;try{Qu(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&a&&a.length>0)return await vu({authorization:a[0]}).catch(()=>{throw new D("`to` is required. Could not infer from `authorizationList`.")})})();if(_?.type===`json-rpc`||_===null){let t;r!==null&&(t=await E(e,Md,`getChainId`)({}),Am({currentChainId:t,chain:r}));let v=e.chain?.formatters?.transactionRequest?.format,y=(v||Bu)({...Iu(g,{format:v}),accessList:i,authorizationList:a,blobs:o,chainId:t,data:s,from:_?.address,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,to:n,type:m,value:h},`sendTransaction`),b=Nm.get(e.uid),x=b?`wallet_sendTransaction`:`eth_sendTransaction`;try{return await e.request({method:x,params:[y]},{retryCount:0})}catch(t){if(b===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[y]},{retryCount:0}).then(t=>(Nm.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(Nm.set(e.uid,!1),n):r});throw n}}if(_?.type===`local`){let t=await E(e,Fd,`prepareTransactionRequest`)({account:_,accessList:i,authorizationList:a,blobs:o,chain:r,data:s,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,nonceManager:_.nonceManager,parameters:[...Nd,`sidecars`],type:m,value:h,...g,to:n}),v=r?.serializers?.transaction,y=await _.signTransaction(t,{serializer:v});return await E(e,Mm,`sendRawTransaction`)({serializedTransaction:y})}throw _?.type===`smart`?new km({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new km({docsPath:`/docs/actions/wallet/sendTransaction`,type:_?.type})}catch(e){throw e instanceof km?e:jm(e,{...t,account:_,chain:t.chain||void 0})}}ra(),ca();async function Fm(e,t){return Fm.internal(e,Pm,`sendTransaction`,t)}(function(e){async function t(e,t,n,r){let{abi:i,account:a=e.account,address:o,args:s,dataSuffix:c,functionName:l,...u}=r;if(a===void 0)throw new Om({docsPath:`/docs/contract/writeContract`});let d=a?na(a):null,f=sa({abi:i,args:s,functionName:l});try{return await E(e,t,n)({data:`${f}${c?c.replace(`0x`,``):``}`,to:o,account:d,...u})}catch(e){throw cs(e,{abi:i,address:o,args:s,docsPath:`/docs/contract/writeContract`,functionName:l,sender:d?.address})}}e.internal=t})(Fm||={}),O();var Im=class extends D{constructor(e){super(`Call bundle failed with status: ${e.statusCode}`,{name:`BundleFailedError`}),Object.defineProperty(this,`result`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.result=e}};function Lm(e,{delay:t=100,retryCount:n=2,shouldRetry:r=()=>!0}={}){return new Promise((i,a)=>{let o=async({count:s=0}={})=>{let c=async({error:e})=>{let n=typeof t==`function`?t({count:s,error:e}):t;n&&await bm(n),o({count:s+1})};try{let t=await e();i(t)}catch(e){if(szd(e)):null,to:e.to?e.to:null,transactionIndex:e.transactionIndex?En(e.transactionIndex):null,status:e.status?Rm[e.status]:null,type:e.type?id[e.type]||e.type:null};return e.blobGasPrice&&(n.blobGasPrice=BigInt(e.blobGasPrice)),e.blobGasUsed&&(n.blobGasUsed=BigInt(e.blobGasUsed)),n}const Bm=Ru(`transactionReceipt`,zm);ra(),O(),os(),ca(),Di(),Dn(),A();const Vm=k(0,{size:32});async function Hm(e,t){let{account:n=e.account,capabilities:r,chain:i=e.chain,experimental_fallback:a,experimental_fallbackDelay:o=32,forceAtomic:s=!1,id:c,version:l=`2.0.0`}=t,u=n?na(n):null,d=t.calls.map(e=>{let t=e,n=t.abi?sa({abi:t.abi,functionName:t.functionName,args:t.args}):t.data;return{data:t.dataSuffix&&n?wi([n,t.dataSuffix]):n,to:t.to,value:t.value?k(t.value):void 0}});try{let t=await e.request({method:`wallet_sendCalls`,params:[{atomicRequired:s,calls:d,capabilities:r,chainId:k(i.id),from:u?.address,id:c,version:l}]},{retryCount:0});return typeof t==`string`?{id:t}:t}catch(n){let c=n;if(a&&(c.name===`MethodNotFoundRpcError`||c.name===`MethodNotSupportedRpcError`||c.name===`UnknownRpcError`||c.details.toLowerCase().includes(`does not exist / is not available`)||c.details.toLowerCase().includes(`missing or invalid. request()`)||c.details.toLowerCase().includes(`did not match any variant of untagged enum`)||c.details.toLowerCase().includes(`account upgraded to unsupported contract`)||c.details.toLowerCase().includes(`eip-7702 not supported`)||c.details.toLowerCase().includes(`unsupported wc_ method`)||c.details.toLowerCase().includes(`feature toggled misconfigured`)||c.details.toLowerCase().includes(`jsonrpcengine: response has no error or result for request`))){if(r&&Object.values(r).some(e=>!e.optional)){let e="non-optional `capabilities` are not supported on fallback to `eth_sendTransaction`.";throw new Xo(new D(e,{details:e}))}if(s&&d.length>1){let e="`forceAtomic` is not supported on fallback to `eth_sendTransaction`.";throw new ns(new D(e,{details:e}))}let t=[];for(let n of d){let r=Pm(e,{account:u,chain:i,data:n.data,to:n.to,value:n.value?wn(n.value):void 0});t.push(r),o>0&&await new Promise(e=>setTimeout(e,o))}let n=await Promise.allSettled(t);if(n.every(e=>e.status===`rejected`))throw n[0].reason;let a=n.map(e=>e.status===`fulfilled`?e.value:Vm);return{id:wi([...a,k(i.id,{size:32}),`0x5792579257925792579257925792579257925792579257925792579257925792`])}}throw jm(n,{...t,account:u,chain:t.chain})}}Ni(),Sn(),Dn();async function Um(e,t){async function n(t){if(t.endsWith(`5792579257925792579257925792579257925792579257925792579257925792`)){let n=xn(Mi(t,-64,-32)),r=Mi(t,0,-64).slice(2).match(/.{1,64}/g),i=await Promise.all(r.map(t=>Vm.slice(2)===t?void 0:e.request({method:`eth_getTransactionReceipt`,params:[`0x${t}`]},{dedupe:!0}))),a=(()=>i.some(e=>e===null)?100:i.every(e=>e?.status===`0x1`)?200:i.every(e=>e?.status===`0x0`)?500:600)();return{atomic:!1,chainId:En(n),receipts:i.filter(Boolean),status:a,version:`2.0.0`}}return e.request({method:`wallet_getCallsStatus`,params:[t]})}let{atomic:r=!1,chainId:i,receipts:a,version:o=`2.0.0`,...s}=await n(t.id),[c,l]=(()=>{let e=s.status;return e>=100&&e<200?[`pending`,e]:e>=200&&e<300?[`success`,e]:e>=300&&e<700?[`failure`,e]:e===`CONFIRMED`?[`success`,200]:e===`PENDING`?[`pending`,100]:[void 0,e]})();return{...s,atomic:r,chainId:i?En(i):void 0,receipts:a?.map(e=>({...e,blockNumber:wn(e.blockNumber),gasUsed:wn(e.gasUsed),status:Rm[e.status]}))??[],statusCode:l,status:c,version:o}}O(),Lp(),Va();async function Wm(e,t){let{id:n,pollingInterval:r=e.pollingInterval,status:i=({statusCode:e})=>e===200||e>=300,retryCount:a=4,retryDelay:o=({count:e})=>~~(1<{let s=xm(async()=>{let r=e=>{clearTimeout(p),s(),e(),m()};try{let s=await Lm(async()=>{let t=await E(e,Um,`getCallsStatus`)({id:n});if(c&&t.status===`failure`)throw new Im(t);return t},{retryCount:a,delay:o});if(!i(s))return;r(()=>t.resolve(s))}catch(e){r(()=>t.reject(e))}},{interval:r,emitOnBegin:!0});return s});return p=s?setTimeout(()=>{m(),clearTimeout(p),f(new Gm({id:n}))},s):void 0,await u}var Gm=class extends D{constructor({id:e}){super(`Timed out while waiting for call bundle with id "${e}" to be confirmed.`,{name:`WaitForCallsStatusTimeoutError`})}},Km=256,qm=Km,Jm;function Ym(e=11){if(!Jm||qm+e>Km*2){Jm=``,qm=0;for(let e=0;e{let n=t(e);for(let e in _)delete n[e];let r={...e,...n};return Object.assign(r,{extend:v(r)})}}return Object.assign(_,{extend:v(_)})}A();async function Zm(e,{address:t,blockNumber:n,blockTag:r=`latest`}){let i=n===void 0?void 0:k(n),a=await e.request({method:`eth_getCode`,params:[t,i||r]},{dedupe:!!i});if(a!==`0x`)return a}O();var Qm=class extends D{constructor({address:e}){super(`No EIP-712 domain found on contract "${e}".`,{metaMessages:[`Ensure that:`,`- The contract is deployed at the address "${e}".`,"- `eip712Domain()` function exists on the contract.","- `eip712Domain()` function matches signature to ERC-5267 specification."],name:`Eip712DomainNotFoundError`})}};async function $m(e,t){let{address:n,factory:r,factoryData:i}=t;try{let[t,a,o,s,c,l,u]=await E(e,hm,`readContract`)({abi:eh,address:n,functionName:`eip712Domain`,factory:r,factoryData:i});return{domain:{name:a,version:o,chainId:Number(s),verifyingContract:c,salt:l},extensions:u,fields:t}}catch(e){let t=e;throw t.name===`ContractFunctionExecutionError`&&t.cause.name===`ContractFunctionZeroDataError`?new Qm({address:n}):t}}var eh=[{inputs:[],name:`eip712Domain`,outputs:[{name:`fields`,type:`bytes1`},{name:`name`,type:`string`},{name:`version`,type:`string`},{name:`chainId`,type:`uint256`},{name:`verifyingContract`,type:`address`},{name:`salt`,type:`bytes32`},{name:`extensions`,type:`uint256[]`}],stateMutability:`view`,type:`function`}];Zu(),pi(),O(),Op(),Mu(),Ci(),It(),Ni(),Dn();function th(e){let{authorizationList:t}=e;if(t)for(let e of t){let{chainId:t}=e,n=e.address;if(!bi(n))throw new fi({address:n});if(t<0)throw new Dp({chainId:t})}rh(e)}function nh(e){let{blobVersionedHashes:t}=e;if(t){if(t.length===0)throw new Ed;for(let e of t){let t=Ft(e),n=En(Oi(e,0,1));if(t!==32)throw new Dd({hash:e,size:t});if(n!==1)throw new Od({hash:e,version:n})}}rh(e)}function rh(e){let{chainId:t,maxPriorityFeePerGas:n,maxFeePerGas:r,to:i}=e;if(t<=0)throw new Dp({chainId:t});if(i&&!bi(i))throw new fi({address:i});if(r&&r>Xu)throw new xu({maxFeePerGas:r});if(n&&r&&n>r)throw new Au({maxFeePerGas:r,maxPriorityFeePerGas:n})}function ih(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(t<=0)throw new Dp({chainId:t});if(a&&!bi(a))throw new fi({address:a});if(n||i)throw new D("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid EIP-2930 Transaction attribute.");if(r&&r>Xu)throw new xu({maxFeePerGas:r})}function ah(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(a&&!bi(a))throw new fi({address:a});if(t!==void 0&&t<=0)throw new Dp({chainId:t});if(n||i)throw new D("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid Legacy Transaction attribute.");if(r&&r>Xu)throw new xu({maxFeePerGas:r})}pi(),ho(),Ci();function oh(e){if(!e||e.length===0)return[];let t=[];for(let n=0;nAn(e)),n=e.kzg,r=hd({blobs:t,kzg:n});if(f===void 0&&(f=bd({commitments:r})),p===void 0){let e=gd({blobs:t,commitments:r,kzg:n});p=Ad({blobs:t,commitments:r,proofs:e})}}let m=oh(u),h=[k(n),i?k(i):`0x`,l?k(l):`0x`,c?k(c):`0x`,r?k(r):`0x`,a??`0x`,o?k(o):`0x`,d??`0x`,m,s?k(s):`0x`,f??[],...ph(e,t)],g=[],_=[],v=[];if(p)for(let e=0;e{if(t.v>=35n)return(t.v-35n)/2n>0?t.v:27n+(t.v===35n?0n:1n);if(n>0)return BigInt(n*2)+BigInt(35n+t.v-27n);let e=27n+(t.v===27n?0n:1n);if(t.v!==e)throw new oo({v:t.v});return e})(),r=xn(t.r),i=xn(t.s);l=[...l,k(e),r===`0x00`?`0x`:r,i===`0x00`?`0x`:i]}else n>0&&(l=[...l,k(n),`0x`,`0x`]);return M(l)}function ph(e,t){let n=t??e,{v:r,yParity:i}=n;if(n.r===void 0||n.s===void 0||r===void 0&&i===void 0)return[];let a=xn(n.r),o=xn(n.s);return[(()=>typeof i==`number`?i?k(1):`0x`:r===0n?`0x`:r===1n?k(1):r===27n?`0x`:k(1))(),a===`0x00`?`0x`:a,o===`0x00`?`0x`:o]}A();function mh(e){if(!e||e.length===0)return[];let t=[];for(let n of e){let{chainId:e,nonce:r,...i}=n,a=n.address;t.push([e?On(e):`0x`,a,r?On(r):`0x`,...ph({},i)])}return t}yi(),Rd();async function hh({address:e,authorization:t,signature:n}){return Ld(_i(e),await vu({authorization:t,signature:n}))}hi();const gh=new mi(8192);function _h(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(gh.get(n))return gh.get(n);let r=e().finally(()=>gh.delete(n));return gh.set(n,r),r}O(),ko(),os(),A(),Va();function vh(e,t={}){return async(n,r={})=>{let{dedupe:i=!1,methods:a,retryDelay:o=150,retryCount:s=3,uid:c}={...t,...r},{method:l}=n;if(a?.exclude?.includes(l)||a?.include&&!a.include.includes(l))throw new Vo(Error(`method not supported`),{method:l});let u=i?jn(`${c}.${Ba(n)}`):void 0;return _h(()=>Lm(async()=>{try{return await e(n)}catch(e){let t=e;switch(t.code){case No.code:throw new No(t);case j.code:throw new j(t);case Po.code:throw new Po(t,{method:n.method});case Fo.code:throw new Fo(t);case Io.code:throw new Io(t);case Lo.code:throw new Lo(t);case Ro.code:throw new Ro(t);case zo.code:throw new zo(t);case Bo.code:throw new Bo(t);case Vo.code:throw new Vo(t,{method:n.method});case Ho.code:throw new Ho(t);case Uo.code:throw new Uo(t);case Wo.code:throw new Wo(t);case Go.code:throw new Go(t);case Ko.code:throw new Ko(t);case qo.code:throw new qo(t);case Jo.code:throw new Jo(t);case Yo.code:throw new Yo(t);case Xo.code:throw new Xo(t);case Zo.code:throw new Zo(t);case Qo.code:throw new Qo(t);case $o.code:throw new $o(t);case es.code:throw new es(t);case ts.code:throw new ts(t);case ns.code:throw new ns(t);case 5e3:throw new Wo(t);default:throw e instanceof D?e:new rs(t)}}},{delay:({count:e,error:t})=>{if(t&&t instanceof Eo){let e=t?.headers?.get(`Retry-After`);if(e?.match(/\d/))return Number.parseInt(e,10)*1e3}return~~(1<yh(e)}),{enabled:i,id:u})}}function yh(e){return`code`in e&&typeof e.code==`number`?e.code===-1||e.code===Ho.code||e.code===Io.code:e instanceof Eo&&e.status?e.status===403||e.status===408||e.status===413||e.status===429||e.status===500||e.status===502||e.status===503||e.status===504:!0}function L(e){return{formatters:void 0,fees:void 0,serializers:void 0,...e}}function bh(e,{errorInstance:t=Error(`timed out`),timeout:n,signal:r}){return new Promise((i,a)=>{(async()=>{let o;try{let s=new AbortController;n>0&&(o=setTimeout(()=>{r?s.abort():a(t)},n)),i(await e({signal:s?.signal||null}))}catch(e){e?.name===`AbortError`&&a(t),a(e)}finally{clearTimeout(o)}})()})}function xh(){return{current:0,take(){return this.current++},reset(){this.current=0}}}const Sh=xh();ko(),Va();function Ch(e,t={}){return{async request(n){let{body:r,fetchFn:i=t.fetchFn??fetch,onRequest:a=t.onRequest,onResponse:o=t.onResponse,timeout:s=t.timeout??1e4}=n,c={...t.fetchOptions??{},...n.fetchOptions??{}},{headers:l,method:u,signal:d}=c;try{let t=await bh(async({signal:t})=>{let n={...c,body:Ba(Array.isArray(r)?r.map(e=>({jsonrpc:`2.0`,id:e.id??Sh.take(),...e})):{jsonrpc:`2.0`,id:r.id??Sh.take(),...r}),headers:{"Content-Type":`application/json`,...l},method:u||`POST`,signal:d||(s>0?t:null)},o=new Request(e,n),f=await a?.(o,n)??{...n,url:e};return await i(f.url??e,f)},{errorInstance:new Oo({body:r,url:e}),timeout:s,signal:!0});o&&await o(t);let n;if(t.headers.get(`Content-Type`)?.startsWith(`application/json`))n=await t.json();else{n=await t.text();try{n=JSON.parse(n||`{}`)}catch(e){if(t.ok)throw e;n={error:n}}}if(!t.ok)throw new Eo({body:r,details:Ba(n.error)||t.statusText,headers:t.headers,status:t.status,url:e});return n}catch(t){throw t instanceof Eo||t instanceof Oo?t:new Eo({body:r,cause:t,url:e})}}}}Di(),It(),A();function wh(e){let t=(()=>typeof e==`string`?jn(e):typeof e.raw==`string`?e.raw:An(e.raw))(),n=jn(`Ethereum Signed Message: -${Ft(t)}`);return wi([n,t])}ei();function Th(e,t){return $r(wh(e),t)}Va(),O();var Eh=class extends D{constructor({domain:e}){super(`Invalid domain "${Ba(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]})}},Dh=class extends D{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{docsPath:`/api/glossary/Errors#typeddatainvalidprimarytypeerror`,metaMessages:["Check that the primary type is a key in `types`."]})}},Oh=class extends D{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`],name:`InvalidStructTypeError`})}};sn(),pi(),Ci(),It(),A(),Ii(),Va();function kh(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{let n={...t};for(let t of e){let{name:e,type:r}=t;r===`address`&&(n[e]=n[e].toLowerCase())}return n},o=(()=>!i.EIP712Domain||!t?{}:a(i.EIP712Domain,t))(),s=(()=>{if(r!==`EIP712Domain`)return a(i[r],n)})();return Ba({domain:o,message:s,primaryType:r,types:i})}function Ah(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(Fi);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[e,t,n]=s;k(o,{signed:t===`int`,size:Number.parseInt(n,10)/8})}if(r===`address`&&typeof o==`string`&&!bi(o))throw new fi({address:o});let c=r.match(Pi);if(c){let[e,t]=c;if(t&&Ft(o)!==Number.parseInt(t,10))throw new tn({expectedSize:Number.parseInt(t,10),givenSize:Ft(o)})}let l=i[r];l&&(Mh(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new Eh({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new Dh({primaryType:r,types:i})}function jh({domain:e}){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function Mh(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new Oh({type:e})}Yi(),Di(),A(),ei();function Nh(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:jh({domain:t}),...e.types};Ah({domain:t,message:n,primaryType:r,types:i});let a=[`0x1901`];return t&&a.push(Ph({domain:t,types:i})),r!==`EIP712Domain`&&a.push(Fh({data:n,primaryType:r,types:i})),$r(wi(a))}function Ph({domain:e,types:t}){return Fh({data:e,primaryType:`EIP712Domain`,types:t})}function Fh({data:e,primaryType:t,types:n}){let r=Ih({data:e,primaryType:t,types:n});return $r(r)}function Ih({data:e,primaryType:t,types:n}){let r=[{type:`bytes32`}],i=[Lh({primaryType:t,types:n})];for(let a of n[t]){let[t,o]=Bh({types:n,name:a.name,type:a.type,value:e[a.name]});r.push(t),i.push(o)}return Li(r,i)}function Lh({primaryType:e,types:t}){let n=On(Rh({primaryType:e,types:t}));return $r(n)}function Rh({primaryType:e,types:t}){let n=``,r=zh({primaryType:e,types:t});r.delete(e);let i=[e,...Array.from(r).sort()];for(let e of i)n+=`${e}(${t[e].map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return n}function zh({primaryType:e,types:t},n=new Set){let r=e.match(/^\w*/u)?.[0];if(n.has(r)||t[r]===void 0)return n;n.add(r);for(let e of t[r])zh({primaryType:e.type,types:t},n);return n}function Bh({types:e,name:t,type:n,value:r}){if(e[n]!==void 0)return[{type:`bytes32`},$r(Ih({data:r,primaryType:n,types:e}))];if(n===`bytes`)return r=`0x${(r.length%2?`0`:``)+r.slice(2)}`,[{type:`bytes32`},$r(r)];if(n===`string`)return[{type:`bytes32`},$r(On(r))];if(n.lastIndexOf(`]`)===n.length-1){let i=n.slice(0,n.lastIndexOf(`[`)),a=r.map(n=>Bh({name:t,type:i,types:e,value:n}));return[{type:`bytes32`},$r(Li(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:n},r]}const Vh={checksum:new class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&t!==void 0&&(this.delete(e),super.set(e,t)),t}set(e,t){if(super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}(8192)}.checksum;Qr(),Lf(),op();function Hh(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=Zr(hf(e));return n===`Bytes`?r:Hf(r)}function Uh(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=_d(hf(e));return n===`Bytes`?r:Hf(r)}Lf(),Jd(),op(),pf();function Wh(e,t={}){let{compressed:n}=t,{prefix:r,x:i,y:a}=e;if(n===!1||typeof i==`bigint`&&typeof a==`bigint`){if(r!==4)throw new Xh({prefix:r,cause:new Qh});return}if(n===!0||typeof i==`bigint`&&a===void 0){if(r!==3&&r!==2)throw new Xh({prefix:r,cause:new Zh});return}throw new Yh({publicKey:e})}function Gh(e){let t=(()=>{if(Zf(e))return qh(e);if(kf(e))return Kh(e);let{prefix:t,x:n,y:r}=e;return typeof n==`bigint`&&typeof r==`bigint`?{prefix:t??4,x:n,y:r}:{prefix:t,x:n}})();return Wh(t),t}function Kh(e){return qh(Hf(e))}function qh(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new $h({publicKey:e});if(e.length===130){let t=BigInt(Kf(e,0,32)),n=BigInt(Kf(e,32,64));return{prefix:4,x:t,y:n}}if(e.length===132){let t=Number(Kf(e,0,1)),n=BigInt(Kf(e,1,33)),r=BigInt(Kf(e,33,65));return{prefix:t,x:n,y:r}}let t=Number(Kf(e,0,1)),n=BigInt(Kf(e,1,33));return{prefix:t,x:n}}function Jh(e,t={}){Wh(e);let{prefix:n,x:r,y:i}=e,{includePrefix:a=!0}=t;return zf(a?I(n,{size:1}):`0x`,I(r,{size:32}),typeof i==`bigint`?I(i,{size:32}):`0x`)}var Yh=class extends P{constructor({publicKey:e}){super(`Value \`${df(e)}\` is not a valid public key.`,{metaMessages:[`Public key must contain:`,"- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidError`})}},Xh=class extends P{constructor({prefix:e,cause:t}){super(`Prefix "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidPrefixError`})}},Zh=class extends P{constructor(){super(`Prefix must be 2 or 3 for compressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidCompressedPrefixError`})}},Qh=class extends P{constructor(){super(`Prefix must be 4 for uncompressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidUncompressedPrefixError`})}},$h=class extends P{constructor({publicKey:e}){super(`Value \`${e}\` is an invalid public key size.`,{metaMessages:[`Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).`,`Received ${qf(Bf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidSerializedSizeError`})}};Lf(),Jd();var eg=/^0x[a-fA-F0-9]{40}$/;function tg(e,t={}){let{strict:n=!0}=t;if(!eg.test(e))throw new sg({address:e,cause:new cg});if(n){if(e.toLowerCase()===e)return;if(ng(e)!==e)throw new sg({address:e,cause:new lg})}}function ng(e){if(Vh.has(e))return Vh.get(e);tg(e,{strict:!1});let t=e.substring(2).toLowerCase(),n=Hh(vf(t),{as:`Bytes`}),r=t.split(``);for(let e=0;e<40;e+=2)n[e>>1]>>4>=8&&r[e]&&(r[e]=r[e].toUpperCase()),(n[e>>1]&15)>=8&&r[e+1]&&(r[e+1]=r[e+1].toUpperCase());let i=`0x${r.join(``)}`;return Vh.set(e,i),i}function rg(e,t={}){let{checksum:n=!1}=t;return tg(e),n?ng(e):e}function ig(e,t={}){let n=Hh(`0x${Jh(e).slice(4)}`).substring(26);return rg(`0x${n}`,t)}function ag(e,t){return tg(e,{strict:!1}),tg(t,{strict:!1}),e.toLowerCase()===t.toLowerCase()}function og(e,t={}){let{strict:n=!0}=t??{};try{return tg(e,{strict:n}),!0}catch{return!1}}var sg=class extends P{constructor({address:e,cause:t}){super(`Address "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidAddressError`})}},cg=class extends P{constructor(){super(`Address is not a 20 byte (40 hexadecimal character) value.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidInputError`})}},lg=class extends P{constructor(){super(`Address does not match its checksum counterpart.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidChecksumError`})}};const ug=/^(.*)\[([0-9]*)\]$/,dg=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,fg=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n));const pg=2n**256n-1n;Lf(),Jd(),op();function mg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=Pg(t.type);if(a){let[n,o]=a;return vg(e,{...t,type:o},{checksumAddress:r,length:n,staticPosition:i})}if(t.type===`tuple`)return Sg(e,t,{checksumAddress:r,staticPosition:i});if(t.type===`address`)return _g(e,{checksum:r});if(t.type===`bool`)return yg(e);if(t.type.startsWith(`bytes`))return bg(e,t,{staticPosition:i});if(t.type.startsWith(`uint`)||t.type.startsWith(`int`))return xg(e,t);if(t.type===`string`)return Cg(e,{staticPosition:i});throw new Zg(t.type)}var hg=32,gg=32;function _g(e,t={}){let{checksum:n=!1}=t,r=e.readBytes(32);return[(e=>n?ng(e):e)(Hf(xf(r,-20))),32]}function vg(e,t,n){let{checksumAddress:r,length:i,staticPosition:a}=n;if(!i){let n=Tf(e.readBytes(gg)),i=a+n,o=i+hg;e.setPosition(i);let s=Tf(e.readBytes(hg)),c=Fg(t),l=0,u=[];for(let n=0;n48?Sf(i,{signed:n}):Tf(i,{signed:n}),32]}function Sg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=t.components.length===0||t.components.some(({name:e})=>!e),o=a?[]:{},s=0;if(Fg(t)){let n=Tf(e.readBytes(gg)),c=i+n;for(let n=0;n0?zf(t,e):t}}if(o)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:zf(...s.map(({encoded:e})=>e))}}function kg(e,{type:t}){let[,n]=t.split(`bytes`),r=qf(e);if(!n){let t=e;return r%32!=0&&(t=Gf(t,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:zf(Wf(I(r,{size:32})),t)}}if(r!==Number.parseInt(n,10))throw new Jg({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:Gf(e)}}function Ag(e){if(typeof e!=`boolean`)throw new P(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:Wf(Vf(e))}}function jg(e,{signed:t,size:n}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Pg(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}function Fg(e){let{type:t}=e;if(t===`string`||t===`bytes`||t.endsWith(`[]`))return!0;if(t===`tuple`)return e.components?.some(Fg);let n=Pg(e.type);return!!(n&&Fg({...e,type:n[1]}))}Jd();var Ig={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new Bg({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new zg({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new Rg({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new Rg({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function Lg(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(Ig);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var Rg=class extends P{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.NegativeOffsetError`})}},zg=class extends P{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.PositionOutOfBoundsError`})}},Bg=class extends P{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.RecursiveReadLimitExceededError`})}};Ot(),Lf(),Jd(),op();function Vg(e,t,n={}){let{as:r=`Array`,checksumAddress:i=!1}=n,a=typeof t==`string`?_f(t):t,o=Lg(a);if(bf(a)===0&&e.length>0)throw new Kg;if(bf(a)&&bf(a)<32)throw new Gg({data:typeof t==`string`?t:Hf(t),parameters:e,size:bf(a)});let s=0,c=r===`Array`?[]:{};for(let t=0;te_(e))):n_(e)}function t_(e){let t=e.reduce((e,t)=>e+t.length,0),n=r_(t);return{length:(()=>t<=55?1+t:1+n+t)(),encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function n_(e){let t=typeof e==`string`?_f(e):e,n=r_(t.length);return{length:(()=>t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length)(),encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function r_(e){if(e<2**8)return 1;if(e<2**16)return 2;if(e<2**24)return 3;if(e<2**32)return 4;throw new P(`Length is too large.`)}Jd(),op(),pf();function i_(e,t={}){let{recovered:n}=t;if(e.r===void 0||e.s===void 0||n&&e.yParity===void 0)throw new g_({signature:e});if(e.r<0n||e.r>pg)throw new __({value:e.r});if(e.s<0n||e.s>pg)throw new v_({value:e.s});if(typeof e.yParity==`number`&&e.yParity!==0&&e.yParity!==1)throw new y_({value:e.yParity})}function a_(e){return o_(Hf(e))}function o_(e){if(e.length!==130&&e.length!==132)throw new h_({signature:e});let t=BigInt(Kf(e,0,32)),n=BigInt(Kf(e,32,64)),r=(()=>{let t=Number(`0x${e.slice(130)}`);if(!Number.isNaN(t))try{return p_(t)}catch{throw new y_({value:t})}})();return r===void 0?{r:t,s:n}:{r:t,s:n,yParity:r}}function s_(e){if(e.r!==void 0&&e.s!==void 0)return c_(e)}function c_(e){let t=(()=>typeof e==`string`?o_(e):e instanceof Uint8Array?a_(e):typeof e.r==`string`?u_(e):e.v?l_(e):{r:e.r,s:e.s,...e.yParity===void 0?{}:{yParity:e.yParity}})();return i_(t),t}function l_(e){return{r:e.r,s:e.s,yParity:p_(e.v)}}function u_(e){let t=(()=>{let t=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof t==`number`&&typeof n!=`number`&&(n=p_(t)),typeof n!=`number`)throw new y_({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function d_(e){i_(e);let t=e.r,n=e.s;return zf(I(t,{size:32}),I(n,{size:32}),typeof e.yParity==`number`?I(m_(e.yParity),{size:1}):`0x`)}function f_(e){let{r:t,s:n,yParity:r}=e;return[r?`0x01`:`0x`,t===0n?`0x`:Jf(I(t)),n===0n?`0x`:Jf(I(n))]}function p_(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2==0?1:0;throw new b_({value:e})}function m_(e){if(e===0)return 27;if(e===1)return 28;throw new y_({value:e})}var h_=class extends P{constructor({signature:e}){super(`Value \`${e}\` is an invalid signature size.`,{metaMessages:[`Expected: 64 bytes or 65 bytes.`,`Received ${qf(Bf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSerializedSizeError`})}},g_=class extends P{constructor({signature:e}){super(`Signature \`${df(e)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.MissingPropertiesError`})}},__=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidRError`})}},v_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSError`})}},y_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidYParityError`})}},b_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidVError`})}};op();function x_(e,t={}){return typeof e.chainId==`string`?S_(e):{...e,...t.signature}}function S_(e){let{address:t,chainId:n,nonce:r}=e,i=s_(e);return{address:t,chainId:Number(n),nonce:BigInt(r),...i}}function C_(e){return w_(e,{presign:!0})}function w_(e,t={}){let{presign:n}=t;return Hh(zf(`0x05`,$g(T_(n?{address:e.address,chainId:e.chainId,nonce:e.nonce}:e))))}function T_(e){let{address:t,chainId:n,nonce:r}=e,i=s_(e);return[n?I(n):`0x`,t,r?I(r):`0x`,...i?f_(i):[]]}uu(),Lf(),op();function E_(e){let{privateKey:t}=e,n=Yl.ProjectivePoint.fromPrivateKey(Bf(t).slice(2));return Gh(n)}function D_(e={}){let{as:t=`Hex`}=e,n=Yl.utils.randomPrivateKey();return t===`Hex`?Hf(n):n}function O_(e){return ig(k_(e))}function k_(e){let{payload:t,signature:n}=e,{r,s:i,yParity:a}=n,o=new Yl.Signature(BigInt(r),BigInt(i)).addRecoveryBit(a).recoverPublicKey(Bf(t).substring(2));return Gh(o)}function A_(e){let{extraEntropy:t=!1,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=Yl.sign(hf(r),hf(i),{extraEntropy:typeof t==`boolean`?t:Bf(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}Jd(),op();const j_=Wg(`(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data`);function M_(e){if(typeof e==`string`){if(Kf(e,-32)!==`0x8010801080108010801080108010801080108010801080108010801080108010`)throw new I_(e)}else i_(e.authorization)}function N_(e){M_(e);let t=Xf(Kf(e,-64,-32)),n=Kf(e,-t-64,-64),r=Kf(e,0,-t-64),[i,a,o]=Vg(j_,n);return{authorization:x_({address:i.delegation,chainId:Number(i.chainId),nonce:i.nonce,yParity:i.yParity,r:i.r,s:i.s}),signature:r,...o&&o!==`0x`?{data:o,to:a}:{}}}function P_(e){let{data:t,signature:n}=e;M_(e);let r=O_({payload:C_(e.authorization),signature:c_(e.authorization)}),i=Hg(j_,[{...e.authorization,delegation:e.authorization.address,chainId:BigInt(e.authorization.chainId)},e.to??r,t??`0x`]),a=I(qf(i),{size:32});return zf(n,i,a,`0x8010801080108010801080108010801080108010801080108010801080108010`)}function F_(e){try{return M_(e),!0}catch{return!1}}var I_=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc8010.InvalidWrappedSignatureError`})}};ho(),A();async function L_(e,{blockHash:t,blockNumber:n,blockTag:r,hash:i,index:a}){let o=r||`latest`,s=n===void 0?void 0:k(n),c=null;if(i?c=await e.request({method:`eth_getTransactionByHash`,params:[i]},{dedupe:!0}):t?c=await e.request({method:`eth_getTransactionByBlockHashAndIndex`,params:[t,k(a)]},{dedupe:!0}):(s||o)&&(c=await e.request({method:`eth_getTransactionByBlockNumberAndIndex`,params:[s||o,k(a)]},{dedupe:!!s})),!c)throw new uo({blockHash:t,blockNumber:n,blockTag:o,hash:i,index:a});return(e.chain?.formatters?.transaction?.format||ad)(c,`getTransaction`)}ho();async function R_(e,{hash:t}){let n=await e.request({method:`eth_getTransactionReceipt`,params:[t]},{dedupe:!0});if(!n)throw new fo({hash:t});return(e.chain?.formatters?.transactionReceipt?.format||zm)(n,`getTransactionReceipt`)}Jd();function z_(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;oB_(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>B_(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function V_(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return V_(i.components,a.components,n[r]);let o=[i.type,a.type];if((()=>o.includes(`address`)&&o.includes(`bytes20`)?!0:o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`)?og(n[r],{strict:!1}):!1)())return o}}Ot(),Jd(),op();function H_(e,t={}){let{prepare:n=!0}=t,r=(()=>Array.isArray(e)||typeof e==`string`?wt(e):e)();return{...r,...n?{hash:K_(r)}:{}}}function U_(e,t,n){let{args:r=[],prepare:i=!0}=n??{},a=Zf(t,{strict:!1}),o=e.filter(e=>a?e.type===`function`||e.type===`error`?W_(e)===Kf(t,0,4):e.type===`event`?K_(e)===t:!1:`name`in e&&e.name===t);if(o.length===0)throw new J_({name:t});if(o.length===1)return{...o[0],...i?{hash:K_(o[0])}:{}};let s;for(let e of o)if(`inputs`in e){if(!r||r.length===0){if(!e.inputs||e.inputs.length===0)return{...e,...i?{hash:K_(e)}:{}};continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===r.length&&r.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?B_(t,r):!1})){if(s&&`inputs`in s&&s.inputs){let t=V_(e.inputs,s.inputs,r);if(t)throw new q_({abiItem:e,type:t[0]},{abiItem:s,type:t[1]})}s=e}}let c=(()=>{if(s)return s;let[e,...t]=o;return{...e,overloads:t}})();if(!c)throw new J_({name:t});return{...c,...i?{hash:K_(c)}:{}}}function W_(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return U_(t,n)}return e[0]})();return Kf(K_(t),0,4)}function G_(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return U_(t,n)}return e[0]})(),n=(()=>typeof t==`string`?t:le(t))();return z_(n)}function K_(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return U_(t,n)}return e[0]})();return typeof t!=`string`&&`hash`in t&&t.hash?t.hash:Hh(Uf(G_(t)))}var q_=class extends P{constructor(e,t){super(`Found ambiguous types in overloaded ABI Items.`,{metaMessages:[`\`${e.type}\` in \`${z_(le(e.abiItem))}\`, and`,`\`${t.type}\` in \`${z_(le(t.abiItem))}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.AmbiguityError`})}},J_=class extends P{constructor({name:e,data:t,type:n=`item`}){let r=(()=>e?` with name "${e}"`:t?` with data "${t}"`:``)();super(`ABI ${n}${r} not found.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.NotFoundError`})}};op();function Y_(...e){let[t,n=[]]=(()=>{if(Array.isArray(e[0])){let[t,n,r]=e;return[X_(t,n,{args:r}),r]}let[t,n]=e;return[t,n]})(),{overloads:r}=t,i=r?X_([t,...r],t.name,{args:n}):t,a=Z_(i),o=n.length>0?Hg(i.inputs,n):void 0;return o?zf(a,o):a}function X_(e,t,n){let r=U_(e,t,n);if(r.type!==`function`)throw new J_({name:t,type:`function`});return r}function Z_(e){return W_(e)}const Q_=`0x0000000000000000000000000000000000000000`;Jd(),op();function $_(e){if(Kf(e,-32)!==`0x6492649264926492649264926492649264926492649264926492649264926492`)throw new nv(e)}function ev(e){let{data:t,signature:n,to:r}=e;return zf(Hg(Wg(`address, bytes, bytes`),[r,t,n]),`0x6492649264926492649264926492649264926492649264926492649264926492`)}function tv(e){try{return $_(e),!0}catch{return!1}}var nv=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-6492 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc6492.InvalidWrappedSignatureError`})}};uu(),Dn(),Hn();function rv({r:e,s:t,to:n=`hex`,v:r,yParity:i}){let a=(()=>{if(i===0||i===1)return i;if(r&&(r===27n||r===28n||r>=35n))return r%2n==0n?1:0;throw Error("Invalid `v` or `yParity` value")})(),o=`0x${new Yl.Signature(wn(e),wn(t)).toCompactHex()}${a===0?`1b`:`1c`}`;return n===`hex`?o:Ln(o)}gp(),Sp(),To(),jp(),ca(),yi(),Rd(),Di(),Pt(),Dn(),A(),mm();async function iv(e,t){let{address:n,hash:r,erc6492VerifierAddress:i=t.universalSignatureVerifierAddress??e.chain?.contracts?.erc6492Verifier?.address,multicallAddress:a=t.multicallAddress??e.chain?.contracts?.multicall3?.address}=t,o=(()=>{let e=t.signature;return Nt(e)?e:typeof e==`object`&&`r`in e&&`s`in e?rv(e):An(e)})();try{return F_(o)?await av(e,{...t,multicallAddress:a,signature:o}):await ov(e,{...t,verifierAddress:i,signature:o})}catch(e){try{if(Ld(_i(n),await pu({hash:r,signature:o})))return!0}catch{}if(e instanceof cv)return!1;throw e}}async function av(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,multicallAddress:o}=t,{authorization:s,data:c,signature:l,to:u}=N_(t.signature);if(await Zm(e,{address:n,blockNumber:r,blockTag:i})===Ei([`0xef0100`,s.address]))return await sv(e,{address:n,blockNumber:r,blockTag:i,hash:a,signature:l});let d={address:s.address,chainId:Number(s.chainId),nonce:Number(s.nonce),r:k(s.r,{size:32}),s:k(s.s,{size:32}),yParity:s.yParity};if(!await hh({address:n,authorization:d}))throw new cv;let f=await E(e,hm,`readContract`)({...o?{address:o}:{code:xp},authorizationList:[d],abi:dp,blockNumber:r,blockTag:`pending`,functionName:`aggregate3`,args:[[...c?[{allowFailure:!0,target:u??n,callData:c}]:[],{allowFailure:!0,target:n,callData:sa({abi:mp,functionName:`isValidSignature`,args:[a,l]})}]]});if((f[f.length-1]?.returnData)?.startsWith(`0x1626ba7e`))return!0;throw new cv}async function ov(e,t){let{address:n,factory:r,factoryData:i,hash:a,signature:o,verifierAddress:s,...c}=t,l=await(async()=>!r&&!i||tv(o)?o:ev({data:i,signature:o,to:r}))(),u=s?{to:s,data:sa({abi:hp,functionName:`isValidSig`,args:[n,a,l]}),...c}:{data:kp({abi:hp,args:[n,a,l],bytecode:bp}),...c},{data:d}=await E(e,cm,`call`)(u).catch(e=>{throw e instanceof yo?new cv:e});if(Tn(d??`0x0`))return!0;throw new cv}async function sv(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,signature:o}=t;if((await E(e,hm,`readContract`)({address:n,abi:mp,args:[a,o],blockNumber:r,blockTag:i,functionName:`isValidSignature`}).catch(e=>{throw e instanceof bo?new cv:e})).startsWith(`0x1626ba7e`))return!0;throw new cv}var cv=class extends Error{};Dn(),Va();function lv(e,{emitOnBegin:t=!1,emitMissed:n=!1,onBlockNumber:r,onError:i,poll:a,pollingInterval:o=e.pollingInterval}){let s=(()=>a===void 0?!(e.transport.type===`webSocket`||e.transport.type===`ipc`||e.transport.type===`fallback`&&(e.transport.transports[0].config.type===`webSocket`||e.transport.transports[0].config.type===`ipc`)):a)(),c;return s?(()=>{let a=Ba([`watchBlockNumber`,e.uid,t,n,o]);return ym(a,{onBlockNumber:r,onError:i},r=>xm(async()=>{try{let t=await E(e,Dm,`getBlockNumber`)({cacheTime:0});if(c!==void 0){if(t===c)return;if(t-c>1&&n)for(let e=c+1n;ec)&&(r.onBlockNumber(t,c),c=t)}catch(e){r.onError?.(e)}},{emitOnBegin:t,interval:o}))})():(()=>{let a=Ba([`watchBlockNumber`,e.uid,t,n]);return ym(a,{onBlockNumber:r,onError:i},t=>{let n=!0,r=()=>n=!1;return(async()=>{try{let{unsubscribe:i}=await(()=>{if(e.transport.type===`fallback`){let t=e.transport.transports.find(e=>e.config.type===`webSocket`||e.config.type===`ipc`);return t?t.value:e.transport}return e.transport})().subscribe({params:[`newHeads`],onData(e){if(!n)return;let r=wn(e.result?.number);t.onBlockNumber(r,c),c=r},onError(e){t.onError?.(e)}});r=i,n||r()}catch(e){i?.(e)}})(),()=>r()})})()}ho(),Lp(),Va();async function uv(e,t){let{checkReplacement:n=!0,confirmations:r=1,hash:i,onReplaced:a,retryCount:o=6,retryDelay:s=({count:e})=>~~(1<t.pollingInterval?t.pollingInterval:e.chain?.experimental_preconfirmationTime?e.chain.experimental_preconfirmationTime:e.pollingInterval)(),d,f,p,m=!1,h,g,{promise:_,resolve:v,reject:y}=Ip(),b=c?setTimeout(()=>{g?.(),h?.(),y(new mo({hash:i}))},c):void 0;return h=ym(l,{onReplaced:a,resolve:v,reject:y},async t=>{if(p=await E(e,R_,`getTransactionReceipt`)({hash:i}).catch(()=>void 0),p&&r<=1){clearTimeout(b),t.resolve(p),h?.();return}g=E(e,lv,`watchBlockNumber`)({emitMissed:!0,emitOnBegin:!0,poll:!0,pollingInterval:u,async onBlockNumber(a){let c=e=>{clearTimeout(b),g?.(),e(),h?.()},l=a;if(!m)try{if(p){if(r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p));return}if(n&&!d&&(m=!0,await Lm(async()=>{d=await E(e,L_,`getTransaction`)({hash:i}),d.blockNumber&&(l=d.blockNumber)},{delay:s,retryCount:o}),m=!1),p=await E(e,R_,`getTransactionReceipt`)({hash:i}),r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p))}catch(n){if(n instanceof uo||n instanceof fo){if(!d){m=!1;return}try{f=d,m=!0;let n=await Lm(()=>E(e,ud,`getBlock`)({blockNumber:l,includeTransactions:!0}),{delay:s,retryCount:o,shouldRetry:({error:e})=>e instanceof rd});m=!1;let i=n.transactions.find(({from:e,nonce:t})=>e===f.from&&t===f.nonce);if(!i||(p=await E(e,R_,`getTransactionReceipt`)({hash:i.hash}),r>1&&(!p.blockNumber||l-p.blockNumber+1n{t.onReplaced?.({reason:a,replacedTransaction:f,transaction:i,transactionReceipt:p}),t.resolve(p)})}catch(e){c(()=>t.reject(e))}}else c(()=>t.reject(n))}}})}),_}ho();async function dv(e,{serializedTransaction:t,throwOnReceiptRevert:n,timeout:r}){let i=await e.request({method:`eth_sendRawTransactionSync`,params:r?[t,k(r)]:[t]},{retryCount:0}),a=(e.chain?.formatters?.transactionReceipt?.format||zm)(i);if(a.status===`reverted`&&n)throw new po({receipt:a});return a}A();async function fv(e,{chain:t}){let{id:n,name:r,nativeCurrency:i,rpcUrls:a,blockExplorers:o}=t;await e.request({method:`wallet_addEthereumChain`,params:[{chainId:k(n),chainName:r,nativeCurrency:i,rpcUrls:a.default.http,blockExplorerUrls:o?Object.values(o).map(({url:e})=>e):void 0}]},{dedupe:!0,retryCount:0})}jp();function pv(e,t){let{abi:n,args:r,bytecode:i,...a}=t,o=kp({abi:n,args:r,bytecode:i});return Pm(e,{...a,...a.authorizationList?{to:null}:{},data:o})}yi();async function mv(e){return e.account?.type===`local`?[e.account.address]:(await e.request({method:`eth_accounts`},{dedupe:!0})).map(e=>gi(e))}ra(),A();async function hv(e,t={}){let{account:n=e.account,chainId:r}=t,i=n?na(n):void 0,a=r?[i?.address,[k(r)]]:[i?.address],o=await e.request({method:`wallet_getCapabilities`,params:a}),s={};for(let[e,t]of Object.entries(o)){s[Number(e)]={};for(let[n,r]of Object.entries(t))n===`addSubAccount`&&(n=`unstable_addSubAccount`),s[Number(e)][n]=r}return typeof r==`number`?s[r]:s}async function gv(e){return await e.request({method:`wallet_getPermissions`},{dedupe:!0})}ra(),Rd();async function _v(e,t){let{account:n=e.account,chainId:r,nonce:i}=t;if(!n)throw new Om({docsPath:`/docs/eip7702/prepareAuthorization`});let a=na(n),o=(()=>{if(t.executor)return t.executor===`self`?t.executor:na(t.executor)})(),s={address:t.contractAddress??t.address,chainId:r,nonce:i};return s.chainId===void 0&&(s.chainId=e.chain?.id??await E(e,Md,`getChainId`)({})),s.nonce===void 0&&(s.nonce=await E(e,md,`getTransactionCount`)({address:a.address,blockTag:`pending`}),(o===`self`||o?.address&&Ld(o.address,a.address))&&(s.nonce+=1)),s}yi();async function vv(e){return(await e.request({method:`eth_requestAccounts`},{dedupe:!0,retryCount:0})).map(e=>_i(e))}async function yv(e,t){return e.request({method:`wallet_requestPermissions`,params:[t]},{retryCount:0})}async function bv(e,t){let{chain:n=e.chain}=t,r=t.timeout??Math.max((n?.blockTime??0)*3,5e3),i=await Hm(e,t);return await Wm(e,{...t,id:i.id,timeout:r})}ra(),O(),ho(),Lu(),Wu(),hi(),$u();var xv=new mi(128);async function Sv(e,t){let{account:n=e.account,chain:r=e.chain,accessList:i,authorizationList:a,blobs:o,data:s,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,pollingInterval:m,throwOnReceiptRevert:h,type:g,value:_,...v}=t,y=t.timeout??Math.max((r?.blockTime??0)*3,5e3);if(n===void 0)throw new Om({docsPath:`/docs/actions/wallet/sendTransactionSync`});let b=n?na(n):null;try{Qu(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&a&&a.length>0)return await vu({authorization:a[0]}).catch(()=>{throw new D("`to` is required. Could not infer from `authorizationList`.")})})();if(b?.type===`json-rpc`||b===null){let t;r!==null&&(t=await E(e,Md,`getChainId`)({}),Am({currentChainId:t,chain:r}));let x=e.chain?.formatters?.transactionRequest?.format,S=(x||Bu)({...Iu(v,{format:x}),accessList:i,authorizationList:a,blobs:o,chainId:t,data:s,from:b?.address,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,to:n,type:g,value:_},`sendTransaction`),C=xv.get(e.uid),w=C?`wallet_sendTransaction`:`eth_sendTransaction`,ee=await(async()=>{try{return await e.request({method:w,params:[S]},{retryCount:0})}catch(t){if(C===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[S]},{retryCount:0}).then(t=>(xv.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(xv.set(e.uid,!1),n):r});throw n}})(),te=await E(e,uv,`waitForTransactionReceipt`)({checkReplacement:!1,hash:ee,pollingInterval:m,timeout:y});if(h&&te.status===`reverted`)throw new po({receipt:te});return te}if(b?.type===`local`){let t=await E(e,Fd,`prepareTransactionRequest`)({account:b,accessList:i,authorizationList:a,blobs:o,chain:r,data:s,gas:c,gasPrice:l,maxFeePerBlobGas:u,maxFeePerGas:d,maxPriorityFeePerGas:f,nonce:p,nonceManager:b.nonceManager,parameters:[...Nd,`sidecars`],type:g,value:_,...v,to:n}),m=r?.serializers?.transaction,y=await b.signTransaction(t,{serializer:m});return await E(e,dv,`sendRawTransactionSync`)({serializedTransaction:y,throwOnReceiptRevert:h})}throw b?.type===`smart`?new km({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new km({docsPath:`/docs/actions/wallet/sendTransactionSync`,type:b?.type})}catch(e){throw e instanceof km?e:jm(e,{...t,account:b,chain:t.chain||void 0})}}async function Cv(e,t){let{id:n}=t;await e.request({method:`wallet_showCallsStatus`,params:[n]})}ra();async function wv(e,t){let{account:n=e.account}=t;if(!n)throw new Om({docsPath:`/docs/eip7702/signAuthorization`});let r=na(n);if(!r.signAuthorization)throw new km({docsPath:`/docs/eip7702/signAuthorization`,metaMessages:["The `signAuthorization` Action does not support JSON-RPC Accounts."],type:r.type});let i=await _v(e,t);return r.signAuthorization(i)}ra(),A();async function Tv(e,{account:t=e.account,message:n}){if(!t)throw new Om({docsPath:`/docs/actions/wallet/signMessage`});let r=na(t);if(r.signMessage)return r.signMessage({message:n});let i=(()=>typeof n==`string`?jn(n):n.raw instanceof Uint8Array?On(n.raw):n.raw)();return e.request({method:`personal_sign`,params:[i,r.address]},{retryCount:0})}ra(),A(),Wu(),$u();async function Ev(e,t){let{account:n=e.account,chain:r=e.chain,...i}=t;if(!n)throw new Om({docsPath:`/docs/actions/wallet/signTransaction`});let a=na(n);Qu({account:a,...t});let o=await E(e,Md,`getChainId`)({});r!==null&&Am({currentChainId:o,chain:r});let s=(r?.formatters||e.chain?.formatters)?.transactionRequest?.format||Bu;return a.signTransaction?a.signTransaction({...i,chainId:o},{serializer:e.chain?.serializers?.transaction}):await e.request({method:`eth_signTransaction`,params:[{...s(i,`signTransaction`),chainId:k(o),from:a.address}]},{retryCount:0})}ra();async function Dv(e,t){let{account:n=e.account,domain:r,message:i,primaryType:a}=t;if(!n)throw new Om({docsPath:`/docs/actions/wallet/signTypedData`});let o=na(n),s={EIP712Domain:jh({domain:r}),...t.types};if(Ah({domain:r,message:i,primaryType:a,types:s}),o.signTypedData)return o.signTypedData({domain:r,message:i,primaryType:a,types:s});let c=kh({domain:r,message:i,primaryType:a,types:s});return e.request({method:`eth_signTypedData_v4`,params:[o.address,c]},{retryCount:0})}A();async function Ov(e,{id:t}){await e.request({method:`wallet_switchEthereumChain`,params:[{chainId:k(t)}]},{retryCount:0})}async function kv(e,t){return await e.request({method:`wallet_watchAsset`,params:t},{retryCount:0})}async function Av(e,t){return Fm.internal(e,Sv,`sendTransactionSync`,t)}function jv(e){return{addChain:t=>fv(e,t),deployContract:t=>pv(e,t),getAddresses:()=>mv(e),getCallsStatus:t=>Um(e,t),getCapabilities:t=>hv(e,t),getChainId:()=>Md(e),getPermissions:()=>gv(e),prepareAuthorization:t=>_v(e,t),prepareTransactionRequest:t=>Fd(e,t),requestAddresses:()=>vv(e),requestPermissions:t=>yv(e,t),sendCalls:t=>Hm(e,t),sendCallsSync:t=>bv(e,t),sendRawTransaction:t=>Mm(e,t),sendRawTransactionSync:t=>dv(e,t),sendTransaction:t=>Pm(e,t),sendTransactionSync:t=>Sv(e,t),showCallsStatus:t=>Cv(e,t),signAuthorization:t=>wv(e,t),signMessage:t=>Tv(e,t),signTransaction:t=>Ev(e,t),signTypedData:t=>Dv(e,t),switchChain:t=>Ov(e,t),waitForCallsStatus:t=>Wm(e,t),watchAsset:t=>kv(e,t),writeContract:t=>Fm(e,t),writeContractSync:t=>Av(e,t)}}function Mv(e){let{key:t=`wallet`,name:n=`Wallet Client`,transport:r}=e;return Xm({...e,key:t,name:n,transport:r,type:`walletClient`}).extend(jv)}function Nv({key:e,methods:t,name:n,request:r,retryCount:i=3,retryDelay:a=150,timeout:o,type:s},c){let l=Ym();return{config:{key:e,methods:t,name:n,request:r,retryCount:i,retryDelay:a,timeout:o,type:s},request:vh(r,{methods:t,retryCount:i,retryDelay:a,uid:l}),value:c}}function Pv(e,t={}){let{key:n=`custom`,methods:r,name:i=`Custom Provider`,retryDelay:a}=t;return({retryCount:o})=>Nv({key:n,methods:r,name:i,request:e.request.bind(e),retryCount:t.retryCount??o,retryDelay:a,type:`custom`})}Mu(),os();function Fv(e,t={}){let{key:n=`fallback`,name:r=`Fallback`,rank:i=!1,shouldThrow:a=Iv,retryCount:o,retryDelay:s}=t;return(({chain:t,pollingInterval:c=4e3,timeout:l,...u})=>{let d=e,f=()=>{},p=Nv({key:n,name:r,async request({method:e,params:n}){let r,i=async(o=0)=>{let s=d[o]({...u,chain:t,retryCount:0,timeout:l});try{let t=await s.request({method:e,params:n});return f({method:e,params:n,response:t,transport:s,status:`success`}),t}catch(c){if(f({error:c,method:e,params:n,transport:s,status:`error`}),a(c)||o===d.length-1||(r??=d.slice(o+1).some(n=>{let{include:r,exclude:i}=n({chain:t}).config.methods||{};return r?r.includes(e):i?!i.includes(e):!0}),!r))throw c;return i(o+1)}};return i()},retryCount:o,retryDelay:s,type:`fallback`},{onResponse:e=>f=e,transports:d.map(e=>e({chain:t,retryCount:0}))});if(i){let e=typeof i==`object`?i:{};Lv({chain:t,interval:e.interval??c,onTransports:e=>d=e,ping:e.ping,sampleCount:e.sampleCount,timeout:e.timeout,transports:d,weights:e.weights})}return p})}function Iv(e){return!!(`code`in e&&typeof e.code==`number`&&(e.code===Bo.code||e.code===Wo.code||bu.nodeMessage.test(e.message)||e.code===5e3))}function Lv({chain:e,interval:t=4e3,onTransports:n,ping:r,sampleCount:i=10,timeout:a=1e3,transports:o,weights:s={}}){let{stability:c=.7,latency:l=.3}=s,u=[],d=async()=>{let s=await Promise.all(o.map(async t=>{let n=t({chain:e,retryCount:0,timeout:a}),i=Date.now(),o,s;try{await(r?r({transport:n}):n.request({method:`net_listening`})),s=1}catch{s=0}finally{o=Date.now()}return{latency:o-i,success:s}}));u.push(s),u.length>i&&u.shift();let f=Math.max(...u.map(e=>Math.max(...e.map(({latency:e})=>e)))),p=o.map((e,t)=>{let n=u.map(e=>e[t].latency),r=1-n.reduce((e,t)=>e+t,0)/n.length/f,i=u.map(e=>e[t].success),a=i.reduce((e,t)=>e+t,0)/i.length;return a===0?[0,t]:[l*r+c*a,t]}).sort((e,t)=>t[0]-e[0]);n(p.map(([,e])=>o[e])),await bm(t),d()};d()}O();var Rv=class extends D{constructor(){super(`No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.`,{docsPath:`/docs/clients/intro`,name:`UrlRequiredError`})}};ko(),Bp();function zv(e,t={}){let{batch:n,fetchFn:r,fetchOptions:i,key:a=`http`,methods:o,name:s=`HTTP JSON-RPC`,onFetchRequest:c,onFetchResponse:l,retryDelay:u,raw:d}=t;return({chain:f,retryCount:p,timeout:m})=>{let{batchSize:h=1e3,wait:g=0}=typeof n==`object`?n:{},_=t.retryCount??p,v=m??t.timeout??1e4,y=e||f?.rpcUrls.default.http[0];if(!y)throw new Rv;let b=Ch(y,{fetchFn:r,fetchOptions:i,onRequest:c,onResponse:l,timeout:v});return Nv({key:a,methods:o,name:s,async request({method:e,params:t}){let r={method:e,params:t},{schedule:i}=Rp({id:y,wait:g,shouldSplitBatch(e){return e.length>h},fn:e=>b.request({body:e}),sort:(e,t)=>e.id-t.id}),[{error:a,result:o}]=await(async e=>n?i(e):[await b.request({body:e})])(r);if(d)return{error:a,result:o};if(a)throw new Do({body:r,error:a,url:y});return o},retryCount:_,retryDelay:u,timeout:v,type:`http`},{fetchOptions:i,url:y})}}const Bv=L({id:16600,name:`0G Newton Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-newton.0g.ai`}},testnet:!0}),Vv=L({id:16601,name:`0G Galileo Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-galileo.0g.ai`}},testnet:!0}),Hv=L({id:16661,name:`0G Mainnet`,nativeCurrency:{name:`0G`,symbol:`0G`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan.0g.ai`}},testnet:!1}),Uv=L({id:995,name:`5ireChain`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Mainnet Explorer`,url:`https://5irescan.io/`}},testnet:!1}),Wv=L({id:179,name:`ABEY Mainnet`,nativeCurrency:{name:`ABEY`,symbol:`ABEY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.abeychain.com`]}},blockExplorers:{default:{name:`Abey Scan`,url:`https://abeyscan.com`}},testnet:!1});Zu();const Gv=50000n,Kv=Yu*32n;Dn(),Hn(),A(),Wu();const qv={block:ld({format(e){let t=e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=qv.transaction?.format(e);return t.typeHex===`0x71`?t.type=`eip712`:t.typeHex===`0xff`&&(t.type=`priority`),t});return{l1BatchNumber:e.l1BatchNumber?wn(e.l1BatchNumber):null,l1BatchTimestamp:e.l1BatchTimestamp?wn(e.l1BatchTimestamp):null,transactions:t}}}),transaction:od({format(e){let t={};return e.type===`0x71`?t.type=`eip712`:e.type===`0xff`&&(t.type=`priority`),{...t,l1BatchNumber:e.l1BatchNumber?wn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?wn(e.l1BatchTxIndex):null}}}),transactionReceipt:Bm({format(e){return{l1BatchNumber:e.l1BatchNumber?wn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?wn(e.l1BatchTxIndex):null,logs:e.logs.map(e=>({...zd(e),l1BatchNumber:e.l1BatchNumber?wn(e.l1BatchNumber):null,transactionLogIndex:En(e.transactionLogIndex),logType:e.logType})),l2ToL1Logs:e.l2ToL1Logs.map(e=>({blockNumber:wn(e.blockHash),blockHash:e.blockHash,l1BatchNumber:e.l1BatchNumber?wn(e.l1BatchNumber):null,transactionIndex:wn(e.transactionIndex),shardId:wn(e.shardId),isService:e.isService,sender:e.sender,key:e.key,value:e.value,transactionHash:e.transactionHash,logIndex:wn(e.logIndex)}))}}}),transactionRequest:Uu({exclude:[`customSignature`,`factoryDeps`,`gasPerPubdata`,`paymaster`,`paymasterInput`],format(e){return e.gasPerPubdata||e.paymaster&&e.paymasterInput||e.factoryDeps||e.customSignature?{eip712Meta:{...e.gasPerPubdata?{gasPerPubdata:On(e.gasPerPubdata)}:{gasPerPubdata:On(Gv)},...e.paymaster&&e.paymasterInput?{paymasterParams:{paymaster:e.paymaster,paymasterInput:Array.from(Ln(e.paymasterInput))}}:{},...e.factoryDeps?{factoryDeps:e.factoryDeps.map(e=>Array.from(Ln(e)))}:{},...e.customSignature?{customSignature:Array.from(Ln(e.customSignature))}:{}},type:`0x71`}:{}}})};O();var Jv=class extends D{constructor(){super([`Transaction is not an EIP712 transaction.`,``,`Transaction must:`,' - include `type: "eip712"`'," - include one of the following: `customSignature`, `paymaster`, `paymasterInput`, `gasPerPubdata`, `factoryDeps`"].join(` -`),{name:`InvalidEip712TransactionError`})}};function Yv(e){return!!(e.type===`eip712`||`customSignature`in e&&e.customSignature||`paymaster`in e&&e.paymaster||`paymasterInput`in e&&e.paymasterInput||`gasPerPubdata`in e&&typeof e.gasPerPubdata==`bigint`||`factoryDeps`in e&&e.factoryDeps)}pi(),O(),Op(),Ci();function Xv(e){let{chainId:t,to:n,from:r,paymaster:i,paymasterInput:a}=e;if(!Yv(e))throw new Jv;if(!t||t<=0)throw new Dp({chainId:t});if(n&&!bi(n))throw new fi({address:n});if(r&&!bi(r))throw new fi({address:r});if(i&&!bi(i))throw new fi({address:i});if(i&&!a)throw new D("`paymasterInput` must be provided when `paymaster` is defined");if(!i&&a)throw new D("`paymaster` must be provided when `paymasterInput` is defined")}Di(),A();function Zv(e,t){return Yv(e)?$v(e):sh(e,t)}const Qv={transaction:Zv};function $v(e){let{chainId:t,gas:n,nonce:r,to:i,from:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,customSignature:l,factoryDeps:u,paymaster:d,paymasterInput:f,gasPerPubdata:p,data:m}=e;Xv(e);let h=[r?On(r):`0x`,c?On(c):`0x`,s?On(s):`0x`,n?On(n):`0x`,i??`0x`,o?On(o):`0x`,m??`0x`,On(t),On(``),On(``),On(t),a??`0x`,On(p||Gv),u??[],l??`0x`,d&&f?[d,f]:[]];return Ei([`0x71`,M(h)])}O();var ey=class extends D{constructor({givenLength:e,maxBytecodeSize:t}){super(`Bytecode cannot be longer than ${t} bytes. Given length: ${e}`,{name:`BytecodeLengthExceedsMaxSizeError`})}},ty=class extends D{constructor({givenLengthInWords:e}){super(`Bytecode length in 32-byte words must be odd. Given length in words: ${e}`,{name:`BytecodeLengthInWordsMustBeOddError`})}},ny=class extends D{constructor({givenLength:e}){super(`The bytecode length in bytes must be divisible by 32. Given length: ${e}`,{name:`BytecodeLengthMustBeDivisibleBy32Error`})}};hn(),Hn();function ry(e){let t=Pn(e);if(t.length%32!=0)throw new ny({givenLength:t.length});if(t.length>Kv)throw new ey({givenLength:t.length,maxBytecodeSize:Kv});let n=vd(t),r=Pn(n),i=t.length/32;if(i%2==0)throw new ty({givenLengthInWords:i});let a=Pn(i),o=fn(a,{size:2}),s=new Uint8Array([1,0]);return r.set(s,0),r.set(o,2),r}A();const iy=e=>{Xv(e);let t=ay(e);return{domain:{name:`zkSync`,version:`2`,chainId:e.chainId},types:{Transaction:[{name:`txType`,type:`uint256`},{name:`from`,type:`uint256`},{name:`to`,type:`uint256`},{name:`gasLimit`,type:`uint256`},{name:`gasPerPubdataByteLimit`,type:`uint256`},{name:`maxFeePerGas`,type:`uint256`},{name:`maxPriorityFeePerGas`,type:`uint256`},{name:`paymaster`,type:`uint256`},{name:`nonce`,type:`uint256`},{name:`value`,type:`uint256`},{name:`data`,type:`bytes`},{name:`factoryDeps`,type:`bytes32[]`},{name:`paymasterInput`,type:`bytes`}]},primaryType:`Transaction`,message:t}};function ay(e){let{gas:t,nonce:n,to:r,from:i,value:a,maxFeePerGas:o,maxPriorityFeePerGas:s,factoryDeps:c,paymaster:l,paymasterInput:u,gasPerPubdata:d,data:f}=e;return{txType:113n,from:BigInt(i),to:r?BigInt(r):0n,gasLimit:t??0n,gasPerPubdataByteLimit:d??50000n,maxFeePerGas:o??0n,maxPriorityFeePerGas:s??0n,paymaster:l?BigInt(l):0n,nonce:n?BigInt(n):0n,value:a??0n,data:f??`0x`,factoryDeps:c?.map(e=>On(ry(e)))??[],paymasterInput:u||`0x`}}const oy={blockTime:1e3,formatters:qv,serializers:Qv,custom:{getEip712Domain:iy}},sy=L({...oy,id:2741,name:`Abstract`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.mainnet.abs.xyz`],webSocket:[`wss://api.mainnet.abs.xyz/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.mainnet.abs.xyz`}},contracts:{multicall3:{address:`0xAa4De41dba0Ca5dCBb288b7cC6b708F3aaC759E7`,blockCreated:5288},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:5263}}}),cy=L({...oy,id:11124,name:`Abstract Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.testnet.abs.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.testnet.abs.xyz`}},testnet:!0,contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:358349},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:431682}}}),ly=L({id:787,name:`Acala`,network:`acala`,nativeCurrency:{name:`Acala`,symbol:`ACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-acala.aca-api.network`],webSocket:[`wss://eth-rpc-acala.aca-api.network`]}},blockExplorers:{default:{name:`Acala Blockscout`,url:`https://blockscout.acala.network`,apiUrl:`https://blockscout.acala.network/api`}},testnet:!1}),uy=L({id:47,name:`Acria IntelliChain`,nativeCurrency:{decimals:18,name:`ACRIA`,symbol:`ACRIA`},rpcUrls:{default:{http:[`https://aic.acria.ai`]}},blockExplorers:{default:{name:`Acria Explorer`,url:`https://explorer.acria.ai`}},testnet:!1}),dy=L({id:1215,name:`ADF Chain`,nativeCurrency:{name:`ADDFILL`,symbol:`ADF`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.adftechnology.com`]}},blockExplorers:{default:{name:`ADF Mainnet Explorer`,url:`https://explorer.adftechnology.com`}},testnet:!1}),fy=L({id:9990,name:`Agung Network`,nativeCurrency:{decimals:18,name:`Agung`,symbol:`AGNG`},rpcUrls:{default:{http:[`https://wss-async.agung.peaq.network`],webSocket:[`wss://wss-async.agung.peaq.network`]}},blockExplorers:{default:{name:`Subscan`,url:`https://agung-testnet.subscan.io`}},testnet:!0}),py=L({id:168,name:`AIOZ Network`,nativeCurrency:{decimals:18,name:`AIOZ`,symbol:`AIOZ`},rpcUrls:{default:{http:[`https://eth-dataseed.aioz.network`]}},blockExplorers:{default:{name:`AIOZ Explorer`,url:`https://explorer.aioz.network`}},testnet:!1}),my=L({id:41455,name:`Aleph Zero`,nativeCurrency:{name:`Aleph Zero`,symbol:`AZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero.raas.gelato.cloud`]}},blockExplorers:{default:{name:`Aleph Zero EVM Explorer`,url:`https://evm-explorer.alephzero.org`,apiUrl:`https://evm-explorer.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4603377}}}),hy=L({id:2039,name:`Aleph Zero Testnet`,nativeCurrency:{name:`TZERO`,symbol:`TZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero-testnet.gelato.digital`],webSocket:[`wss://ws.alephzero-testnet.gelato.digital`]}},blockExplorers:{default:{name:`Aleph Zero EVM Testnet explorer`,url:`https://evm-explorer-testnet.alephzero.org`,apiUrl:`https://evm-explorer-testnet.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2861745}},testnet:!0}),gy=L({id:10241024,name:`AlienX Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://explorer.alienxchain.io`}},testnet:!1}),_y=L({id:10241025,name:`ALIENX Hal Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://hal-rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://hal-explorer.alienxchain.io`}},testnet:!0}),vy={gasPriceOracle:{address:`0x420000000000000000000000000000000000000F`},l1Block:{address:`0x4200000000000000000000000000000000000015`},l2CrossDomainMessenger:{address:`0x4200000000000000000000000000000000000007`},l2Erc721Bridge:{address:`0x4200000000000000000000000000000000000014`},l2StandardBridge:{address:`0x4200000000000000000000000000000000000010`},l2ToL1MessagePasser:{address:`0x4200000000000000000000000000000000000016`}};Dn();const yy={block:ld({format(e){return{transactions:e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=ad(e);return t.typeHex===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?wn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}),stateRoot:e.stateRoot}}}),transaction:od({format(e){let t={};return e.type===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?wn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}}),transactionReceipt:Bm({format(e){return{l1GasPrice:e.l1GasPrice?wn(e.l1GasPrice):null,l1GasUsed:e.l1GasUsed?wn(e.l1GasUsed):null,l1Fee:e.l1Fee?wn(e.l1Fee):null,l1FeeScalar:e.l1FeeScalar?Number(e.l1FeeScalar):null}}})};pi(),Ci(),Di(),A();function by(e,t){return Cy(e)?Sy(e):sh(e,t)}const xy={transaction:by};function Sy(e){wy(e);let{sourceHash:t,data:n,from:r,gas:i,isSystemTx:a,mint:o,to:s,value:c}=e,l=[t,r,s??`0x`,o?On(o):`0x`,c?On(c):`0x`,i?On(i):`0x`,a?`0x1`:`0x`,n??`0x`];return Ei([`0x7e`,M(l)])}function Cy(e){return e.type===`deposit`||e.sourceHash!==void 0}function wy(e){let{from:t,to:n}=e;if(t&&!bi(t))throw new fi({address:t});if(n&&!bi(n))throw new fi({address:n})}const R={blockTime:2e3,contracts:vy,formatters:yy,serializers:xy};var Ty=1;const Ey=L({...R,id:888888888,name:`Ancient8`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 explorer`,url:`https://scan.ancient8.gg`,apiUrl:`https://scan.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Ty]:{address:`0xB09DC08428C8b4EFB4ff9C0827386CDF34277996`}},portal:{[Ty]:{address:`0x639F2AECE398Aa76b07e59eF6abe2cFe32bacb68`,blockCreated:19070571}},l1StandardBridge:{[Ty]:{address:`0xd5e3eDf5b68135D559D572E26bF863FBC1950033`,blockCreated:19070571}}},sourceId:Ty});var Dy=11155111;const Oy=L({...R,id:28122024,name:`Ancient8 Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpcv2-testnet.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 Celestia Testnet explorer`,url:`https://scanv2-testnet.ancient8.gg`,apiUrl:`https://scanv2-testnet.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Dy]:{address:`0x942fD5017c0F60575930D8574Eaca13BEcD6e1bB`}},portal:{[Dy]:{address:`0xfa1d9E26A6aCD7b22115D27572c1221B9803c960`,blockCreated:4972908}},l1StandardBridge:{[Dy]:{address:`0xF6Bc0146d3c74D48306e79Ae134A260E418C9335`,blockCreated:4972908}}},sourceId:Dy}),ky=L({id:31337,name:`Anvil`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}}),Ay=L({id:33139,name:`Ape Chain`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.apechain.com/http`],webSocket:[`wss://rpc.apechain.com/ws`]}},blockExplorers:{default:{name:`Apescan`,url:`https://apescan.io`,apiUrl:`https://api.apescan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:20889}},sourceId:42161}),jy=L({id:3993,name:`APEX Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.apexlayer.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-testnet.apexlayer.xyz`,apiUrl:`https://exp-testnet.apexlayer.xyz/api`}},contracts:{multicall3:{address:`0xf7642be33a6b18D16a995657adb5a68CD0438aE2`,blockCreated:283775}},testnet:!0}),My=L({id:42161,name:`Arbitrum One`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:250,rpcUrls:{default:{http:[`https://arb1.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://arbiscan.io`,apiUrl:`https://api.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7654707}}}),Ny=L({id:421613,name:`Arbitrum Goerli`,nativeCurrency:{name:`Arbitrum Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://goerli.arbiscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:88114}},testnet:!0}),Py=L({id:42170,name:`Arbitrum Nova`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://nova.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://nova.arbiscan.io`,apiUrl:`https://api-nova.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1746963}}}),Fy=L({id:421614,name:`Arbitrum Sepolia`,blockTime:250,nativeCurrency:{name:`Arbitrum Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://sepolia.arbiscan.io`,apiUrl:`https://api-sepolia.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:81930}},testnet:!0}),Iy=L({id:7897,name:`Arena-Z`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arena-z.gg`]}},blockExplorers:{default:{name:`Arena-Z Explorer`,url:`https://explorer.arena-z.gg`,apiUrl:`https://explorer.arena-z.gg`}}}),Ly=L({id:463,name:`Areon Network`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areon.network`],webSocket:[`wss://mainnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),Ry=L({id:462,name:`Areon Network Testnet`,nativeCurrency:{decimals:18,name:`TAREA`,symbol:`TAREA`},rpcUrls:{default:{http:[`https://testnet-rpc.areon.network`],webSocket:[`wss://testnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},testnet:!0}),zy=L({id:463,name:`Areum`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areum.network`],webSocket:[`wss://mainnet-ws.areum.network`]}},blockExplorers:{default:{name:`Areum Explorer`,url:`https://explorer.areum.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),By=L({id:11822,name:`Artela Testnet`,nativeCurrency:{name:`ART`,symbol:`ART`,decimals:18},rpcUrls:{default:{http:[`https://betanet-rpc1.artela.network`]}},blockExplorers:{default:{name:`Artela`,url:`https://betanet-scan.artela.network`,apiUrl:`https://betanet-scan.artela.network/api`}},contracts:{multicall3:{address:`0xd07c8635f76e8745Ee7092fbb6e8fbc5FeF09DD7`,blockCreated:7001871}},testnet:!0}),Vy=L({id:10242,name:`Arthera`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer.arthera.net`,apiUrl:`https://explorer.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4502791}}}),Hy=L({id:10243,name:`Arthera Testnet`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer-test.arthera.net`,apiUrl:`https://explorer-test.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:22051}}}),Uy=L({id:42420,name:`AssetChain Mainnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://mainnet-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Explorer`,url:`https://scan.assetchain.org`,apiUrl:`https://scan.assetchain.org/api`}},testnet:!1,contracts:{}}),Wy=L({id:42421,name:`AssetChain Testnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://enugu-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Testnet Explorer`,url:`https://scan-testnet.assetchain.org`,apiUrl:`https://scan-testnet.assetchain.org/api`}},testnet:!0,contracts:{multicall3:{address:`0x989F832D35988cb5e3eB001Fa2Fe789469EC31Ea`,blockCreated:17177}}}),Gy=L({id:592,name:`Astar`,network:`astar-mainnet`,nativeCurrency:{name:`Astar`,symbol:`ASTR`,decimals:18},rpcUrls:{default:{http:[`https://astar.api.onfinality.io/public`]}},blockExplorers:{default:{name:`Astar Subscan`,url:`https://astar.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:761794}},testnet:!1}),Ky=L({id:3776,name:`Astar zkEVM`,network:`AstarZkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-zkevm.astar.network`]}},blockExplorers:{default:{name:`Astar zkEVM Explorer`,url:`https://astar-zkevm.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:93528}},testnet:!1}),qy=L({id:6038361,name:`Astar zkEVM Testnet zKyoto`,network:`zKyoto`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.startale.com/zkyoto`]}},blockExplorers:{default:{name:`zKyoto Explorer`,url:`https://zkyoto.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:196153}},testnet:!0}),Jy=L({id:2340,name:`Atleta Olympia`,nativeCurrency:{decimals:18,name:`Atla`,symbol:`ATLA`},rpcUrls:{default:{http:[`https://testnet-rpc.atleta.network:9944`,`https://testnet-rpc.atleta.network`],ws:[`wss://testnet-rpc.atleta.network:9944`]}},blockExplorers:{default:{name:`Atleta Olympia Explorer`,url:`https://blockscout.atleta.network`,apiUrl:`https://blockscout.atleta.network/api`}},contracts:{multicall3:{address:`0x1472ec6392180fb84F345d2455bCC75B26577115`,blockCreated:1076473}},testnet:!0}),Yy=L({id:1313161554,name:`Aurora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://aurorascan.dev`,apiUrl:`https://aurorascan.dev/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:62907816}}}),Xy=L({id:1313161555,name:`Aurora Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://testnet.aurorascan.dev`,apiUrl:`https://testnet.aurorascan.dev/api`}},testnet:!0}),Zy=L({id:205205,name:`Auroria Testnet`,network:`auroria`,nativeCurrency:{name:`Auroria Stratis`,symbol:`tSTRAX`,decimals:18},rpcUrls:{default:{http:[`https://auroria.rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Auroria Testnet Explorer`,url:`https://auroria.explorer.stratisevm.com`}},testnet:!0}),Qy=L({id:785,name:`Autheo Testnet`,nativeCurrency:{decimals:18,name:`Autheo`,symbol:`THEO`},rpcUrls:{default:{http:[`https://testnet-rpc1.autheo.com`,`https://testnet-rpc2.autheo.com`]}},blockExplorers:{default:{name:`Autheo Testnet Block Explorer`,url:`https://testnet-explorer.autheo.com/`}}}),$y=L({id:43114,name:`Avalanche`,blockTime:1700,nativeCurrency:{decimals:18,name:`Avalanche`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://snowtrace.io`,apiUrl:`https://api.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11907934}}}),eb=L({id:43113,name:`Avalanche Fuji`,nativeCurrency:{decimals:18,name:`Avalanche Fuji`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax-test.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://testnet.snowtrace.io`,apiUrl:`https://api-testnet.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7096959}},testnet:!0}),tb=L({id:8333,name:`B3`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},sourceId:8453}),nb=L({id:1993,name:`B3 Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},testnet:!0,sourceId:168587773}),rb=L({id:5165,network:`bahamut`,name:`Bahamut`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.bahamut.io`,`https://bahamut-rpc.publicnode.com`,`https://rpc2.bahamut.io`],webSocket:[`wss://ws1.sahara.bahamutchain.com`,`wss://bahamut-rpc.publicnode.com`,`wss://ws2.sahara.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://www.ftnscan.com`,apiUrl:`https://www.ftnscan.com/api`}}});var ib=1;const ab=L({...R,id:8453,name:`Base`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://basescan.org`,apiUrl:`https://api.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[ib]:{address:`0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e`}},l2OutputOracle:{[ib]:{address:`0x56315b90c40730925ec5485cf004d835058518A0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:5022},portal:{[ib]:{address:`0x49048044D57e1C92A77f79988d21Fa8fAF74E97e`,blockCreated:17482143}},l1StandardBridge:{[ib]:{address:`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`,blockCreated:17482143}}},sourceId:ib}),ob=L({...ab,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://mainnet-preconf.base.org`]}}}),sb=L({id:123420001114,name:`Basecamp Testnet`,nativeCurrency:{decimals:18,name:`Camp`,symbol:`CAMP`},rpcUrls:{default:{http:[`https://rpc.basecamp.t.raas.gelato.cloud`]}},blockExplorers:{default:{name:`basecamp`,url:`https://basecamp.cloud.blockscout.com`}},testnet:!0});var cb=5;const lb=L({...R,id:84531,name:`Base Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://goerli.basescan.org`,apiUrl:`https://goerli.basescan.org/api`}},contracts:{...R.contracts,l2OutputOracle:{[cb]:{address:`0x2A35891ff30313CcFa6CE88dcf3858bb075A2298`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1376988},portal:{[cb]:{address:`0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA`}},l1StandardBridge:{[cb]:{address:`0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a`}}},testnet:!0,sourceId:cb});var ub=11155111;const db=L({...R,id:84532,network:`base-sepolia`,name:`Base Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://sepolia.basescan.org`,apiUrl:`https://api-sepolia.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[ub]:{address:`0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1`}},l2OutputOracle:{[ub]:{address:`0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254`}},portal:{[ub]:{address:`0x49f53e41452c74589e85ca1677426ba426459e85`,blockCreated:4446677}},l1StandardBridge:{[ub]:{address:`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`,blockCreated:4446677}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1059647}},testnet:!0,sourceId:ub}),fb=L({...db,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://sepolia-preconf.base.org`]}}}),pb=L({id:4337,name:`Beam`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc`],webSocket:[`wss://build.onbeam.com/ws`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets.avax.network/beam`}},contracts:{multicall3:{address:`0x4956f15efdc3dc16645e90cc356eafa65ffc65ec`,blockCreated:1}}}),mb=L({id:13337,name:`Beam Testnet`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc/testnet`],webSocket:[`wss://build.onbeam.com/ws/testnet`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets-test.avax.network/beam`}},contracts:{multicall3:{address:`0x9bf49b704ee2a095b95c1f2d4eb9010510c41c9e`,blockCreated:3}},testnet:!0}),hb=L({id:641230,name:`Bear Network Chain Mainnet`,nativeCurrency:{decimals:18,name:`BearNetworkChain`,symbol:`BRNKC`},rpcUrls:{default:{http:[`https://brnkc-mainnet.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkScan`,url:`https://brnkscan.bearnetwork.net`,apiUrl:`https://brnkscan.bearnetwork.net/api`}}}),gb=L({id:751230,name:`Bear Network Chain Testnet`,nativeCurrency:{decimals:18,name:`tBRNKC`,symbol:`tBRNKC`},rpcUrls:{default:{http:[`https://brnkc-test.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkTestScan`,url:`https://brnktest-scan.bearnetwork.net`,apiUrl:`https://brnktest-scan.bearnetwork.net/api`}},testnet:!0}),_b=L({id:80094,name:`Berachain`,blockTime:2e3,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},ensRegistry:{address:`0x5b22280886a2f5e09a49bea7e320eab0e5320e28`,blockCreated:877007},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:9310021}},rpcUrls:{default:{http:[`https://rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://berascan.com`}},ensTlds:[`.bera`],testnet:!1}),vb=L({id:80069,blockTime:2e3,name:`Berachain Bepolia`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},rpcUrls:{default:{http:[`https://bepolia.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://bepolia.beratrail.io`}},testnet:!0}),yb=L({id:80085,name:`Berachain Artio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},rpcUrls:{default:{http:[`https://artio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain`,url:`https://artio.beratrail.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:866924}},testnet:!0}),bb=L({id:80084,name:`Berachain bArtio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:109269},ensRegistry:{address:`0xB0eef18971290b333450586D33dcA6cE122651D2`,blockCreated:7736794},ensUniversalResolver:{address:`0x41692Ef1EA0C79E6b73077E4A67572D2BDbD7057`,blockCreated:7736795}},ensTlds:[`.bera`],rpcUrls:{default:{http:[`https://bartio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain bArtio Beratrail`,url:`https://bartio.beratrail.io`}},testnet:!0}),xb=L({id:11501,name:`BEVM Mainnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet-1.bevm.io`]}},blockExplorers:{default:{name:`Bevmscan`,url:`https://scan-mainnet.bevm.io`,apiUrl:`https://scan-mainnet-api.bevm.io/api`}}}),Sb=L({id:3068,name:`Bifrost Mainnet`,nativeCurrency:{name:`BFC`,symbol:`BFC`,decimals:18},rpcUrls:{default:{http:[`https://public-01.mainnet.bifrostnetwork.com/rpc`]}},blockExplorers:{default:{name:`Bifrost Blockscout`,url:`https://explorer.mainnet.bifrostnetwork.com`}},testnet:!1}),Cb=L({id:53456,name:`BirdLayer`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.birdlayer.xyz`,`https://rpc1.birdlayer.xyz`],webSocket:[`wss://rpc.birdlayer.xyz/ws`]}},blockExplorers:{default:{name:`BirdLayer Explorer`,url:`https://scan.birdlayer.xyz`}}}),wb=L({id:32520,name:`Bitgert Mainnet`,nativeCurrency:{decimals:18,name:`Brise`,symbol:`Brise`},rpcUrls:{default:{http:[`https://rpc-bitgert.icecreamswap.com`]}},blockExplorers:{default:{name:`Bitgert Scan`,url:`https://brisescan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2118034}},testnet:!1}),Tb=L({id:96,name:`KUB Mainnet`,nativeCurrency:{name:`KUB Coin`,symbol:`KUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitkubchain.io`]}},blockExplorers:{default:{name:`KUB Chain Mainnet Explorer`,url:`https://www.bkcscan.com`,apiUrl:`https://www.bkcscan.com/api`}}}),Eb=L({id:25925,name:`Bitkub Testnet`,network:`Bitkub Testnet`,nativeCurrency:{name:`Bitkub Test`,symbol:`tKUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.bitkubchain.io`]}},blockExplorers:{default:{name:`Bitkub Chain Testnet Explorer`,url:`https://testnet.bkcscan.com`,apiUrl:`https://testnet.bkcscan.com/api`}},testnet:!0}),Db=L({id:200901,name:`Bitlayer Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`],webSocket:[`wss://ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer mainnet scan`,url:`https://www.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:2421963}}}),Ob=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer testnet scan`,url:`https://testnet.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:4135671}},testnet:!0}),kb=L({id:7171,name:`Bitrock Mainnet`,nativeCurrency:{name:`BROCK`,symbol:`BROCK`,decimals:18},rpcUrls:{default:{http:[`https://brockrpc.io`]}},blockExplorers:{default:{name:`Bitrock Explorer`,url:`https://explorer.bit-rock.io`}},testnet:!1}),Ab=L({id:199,name:`BitTorrent`,network:`bittorrent-chain-mainnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://bttcscan.com`,apiUrl:`https://api.bttcscan.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:31078552}}}),jb=L({id:1028,name:`BitTorrent Chain Testnet`,network:`bittorrent-chain-testnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://testrpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://testnet.bttcscan.com`,apiUrl:`https://testnet.bttcscan.com/api`}},testnet:!0});var Mb=1;const Nb=L({...R,id:81457,name:`Blast`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://blastscan.io`,apiUrl:`https://api.blastscan.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:212929},l2OutputOracle:{[Mb]:{address:`0x826D1B0D4111Ad9146Eb8941D7Ca2B6a44215c76`,blockCreated:19300358}},portal:{[Mb]:{address:`0x0Ec68c5B10F21EFFb74f2A5C61DFe6b08C0Db6Cb`,blockCreated:19300357}},l1StandardBridge:{[Mb]:{address:`0x697402166Fbf2F22E970df8a6486Ef171dbfc524`,blockCreated:19300360}}},sourceId:Mb}),Pb=L({id:168587773,name:`Blast Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://sepolia.blastscan.io`,apiUrl:`https://api-sepolia.blastscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:756690}},testnet:!0,sourceId:11155111});var Fb=1;const Ib=L({...R,id:60808,name:`BOB`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.gobob.xyz`],webSocket:[`wss://rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Explorer`,url:`https://explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:23131},l2OutputOracle:{[Fb]:{address:`0xdDa53E23f8a32640b04D7256e651C1db98dB11C1`,blockCreated:4462615}},portal:{[Fb]:{address:`0x8AdeE124447435fE03e3CD24dF3f4cAE32E65a3E`,blockCreated:4462615}}},sourceId:Fb}),Lb=L({id:288,name:`Boba Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://bobascan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:446859}}}),Rb=L({id:28882,name:`Boba Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://testnet.bobascan.com`}},testnet:!0});var zb=11155111;const Bb=L({...R,id:808813,name:`BOB Sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://bob-sepolia.rpc.gobob.xyz`],webSocket:[`wss://bob-sepolia.rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Sepolia Explorer`,url:`https://bob-sepolia.explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:35677},l2OutputOracle:{[zb]:{address:`0x14D0069452b4AE2b250B395b8adAb771E4267d2f`,blockCreated:4462615}},portal:{[zb]:{address:`0x867B1Aa872b9C8cB5E9F7755feDC45BB24Ad0ae4`,blockCreated:4462615}}},testnet:!0,sourceId:zb}),Vb=L({id:11100,name:`Bool Beta Mainnet`,nativeCurrency:{decimals:18,name:`BOL`,symbol:`BOL`},rpcUrls:{default:{http:[`https://beta-rpc-node-http.bool.network`]}},blockExplorers:{default:{name:`BoolScan`,url:`https://beta-mainnet.boolscan.com/`}},testnet:!1}),Hb=L({id:3637,name:`Botanix`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.botanixlabs.com`],webSocket:[`wss://rpc.botanixlabs.com/ws`]}},blockExplorers:{default:{name:`Botanixscan`,url:`https://botanixscan.io`}}}),Ub=L({id:3636,name:`Botanix Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://node.botanixlabs.dev`]}},blockExplorers:{default:{name:`Botanix Testnet Explorer`,url:`https://testnet.botanixscan.io`}},testnet:!0}),Wb=L({id:6001,name:`BounceBit Mainnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-mainnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://bbscan.io`}},testnet:!1}),Gb=L({id:6e3,name:`BounceBit Testnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-testnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://testnet.bbscan.io`}},testnet:!0}),Kb=L({id:1039,name:`Bronos`,nativeCurrency:{decimals:18,name:`BRO`,symbol:`BRO`},rpcUrls:{default:{http:[`https://evm.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://broscan.bronos.org`}}}),qb=L({id:1038,name:`Bronos Testnet`,nativeCurrency:{decimals:18,name:`Bronos Coin`,symbol:`tBRO`},rpcUrls:{default:{http:[`https://evm-testnet.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://tbroscan.bronos.org`}},testnet:!0}),Jb=L({id:56,name:`BNB Smart Chain`,blockTime:750,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://56.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`BscScan`,url:`https://bscscan.com`,apiUrl:`https://api.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15921452}}}),Yb=L({id:1017,name:`BNB Greenfield Chain`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://greenfield-chain.bnbchain.org`]}},blockExplorers:{default:{name:`BNB Greenfield Mainnet Scan`,url:`https://greenfieldscan.com`}},testnet:!1}),Xb=L({id:97,name:`BNB Smart Chain Testnet`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://data-seed-prebsc-1-s1.bnbchain.org:8545`]}},blockExplorers:{default:{name:`BscScan`,url:`https://testnet.bscscan.com`,apiUrl:`https://api-testnet.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:17422483}},testnet:!0}),Zb=L({id:223,name:`B2`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.bsquared.network`}}}),Qb=L({id:1123,name:`B2 Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-explorer.bsquared.network`}},testnet:!0}),$b=L({id:200901,name:`Bitlayer`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`,`https://rpc.bitlayer-rpc.com`],webSocket:[`wss://ws.bitlayer.org`,`wss://ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://www.btrscan.com`}}}),ex=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`,`wss://testnet-ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://testnet.btrscan.com`}},testnet:!0}),tx=L({id:4999,name:`BlackFort Exchange Network`,nativeCurrency:{name:`BlackFort Token`,symbol:`BXN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.blackfort.network`,apiUrl:`https://explorer.blackfort.network/api`}}}),nx=L({id:4777,name:`BlackFort Exchange Network Testnet`,nativeCurrency:{name:`BlackFort Testnet Token`,symbol:`TBXN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.blackfort.network`,apiUrl:`https://testnet-explorer.blackfort.network/api`}},testnet:!0}),rx=L({id:13370,name:`Cannon`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),ix=L({id:7700,name:`Canto`,nativeCurrency:{decimals:18,name:`Canto`,symbol:`CANTO`},rpcUrls:{default:{http:[`https://canto.gravitychain.io`]}},blockExplorers:{default:{name:`Tuber.Build (Blockscout)`,url:`https://tuber.build`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2905789}}}),ax={estimateFeesPerGas:async e=>{if(!e.request?.feeCurrency)return null;let[t,n]=await Promise.all([ox(e.client,e.request.feeCurrency),sx(e.client,e.request.feeCurrency)]);return{maxFeePerGas:e.multiply(t-n)+n,maxPriorityFeePerGas:n}}};async function ox(e,t){let n=await e.request({method:`eth_gasPrice`,params:[t]});return BigInt(n)}async function sx(e,t){let n=await e.request({method:`eth_maxPriorityFeePerGas`,params:[t]});return BigInt(n)}Sn();function cx(e){return e===0||e===0n||e==null||e===`0`||e===``||typeof e==`string`&&(xn(e).toLowerCase()===`0x`||xn(e).toLowerCase()===`0x00`)}function lx(e){return!cx(e)}function ux(e){return e.maxFeePerGas!==void 0&&e.maxPriorityFeePerGas!==void 0}function dx(e){return e.type===`cip64`?!0:ux(e)&&lx(e.feeCurrency)}Dn(),Wu();const fx={block:ld({format(e){return{transactions:e.transactions?.map(e=>typeof e==`string`?e:{...ad(e),...e.gatewayFee?{gatewayFee:wn(e.gatewayFee),gatewayFeeRecipient:e.gatewayFeeRecipient}:{},feeCurrency:e.feeCurrency})}}}),transaction:od({format(e){if(e.type===`0x7e`)return{isSystemTx:e.isSystemTx,mint:e.mint?wn(e.mint):void 0,sourceHash:e.sourceHash,type:`deposit`};let t={feeCurrency:e.feeCurrency};return e.type===`0x7b`?t.type=`cip64`:(e.type===`0x7c`&&(t.type=`cip42`),t.gatewayFee=e.gatewayFee?wn(e.gatewayFee):null,t.gatewayFeeRecipient=e.gatewayFeeRecipient),t}}),transactionRequest:Uu({format(e){let t={};return e.feeCurrency&&(t.feeCurrency=e.feeCurrency),dx(e)&&(t.type=`0x7b`),t}})};Zu(),pi(),O(),Op(),Mu(),Ci(),Di(),A();function px(e,t){return dx(e)?hx(e,t):by(e,t)}const mx={transaction:px};function hx(e,t){_x(e);let{chainId:n,gas:r,nonce:i,to:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,accessList:l,feeCurrency:u,data:d}=e,f=[On(n),i?On(i):`0x`,c?On(c):`0x`,s?On(s):`0x`,r?On(r):`0x`,a??`0x`,o?On(o):`0x`,d??`0x`,oh(l),u,...ph(e,t)];return Ei([`0x7b`,M(f)])}var gx=Xu;function _x(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a,feeCurrency:o}=e;if(t<=0)throw new Dp({chainId:t});if(a&&!bi(a))throw new fi({address:a});if(r)throw new D("`gasPrice` is not a valid CIP-64 Transaction attribute.");if(lx(i)&&i>gx)throw new xu({maxFeePerGas:i});if(lx(n)&&lx(i)&&n>i)throw new Au({maxFeePerGas:i,maxPriorityFeePerGas:n});if(lx(o)&&!bi(o))throw new D("`feeCurrency` MUST be a token address for CIP-64 transactions.");if(cx(o))throw new D("`feeCurrency` must be provided for CIP-64 transactions.")}const vx={blockTime:1e3,contracts:vy,formatters:fx,serializers:mx,fees:ax},yx=L({...vx,id:42220,name:`Celo`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`CELO`},rpcUrls:{default:{http:[`https://forno.celo.org`]}},blockExplorers:{default:{name:`Celo Explorer`,url:`https://celoscan.io`,apiUrl:`https://api.celoscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13112599}},testnet:!1});var bx=17e3;const xx=L({...vx,id:44787,name:`Alfajores`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`A-CELO`},rpcUrls:{default:{http:[`https://alfajores-forno.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Alfajores Explorer`,url:`https://celo-alfajores.blockscout.com`,apiUrl:`https://celo-alfajores.blockscout.com/api`}},contracts:{...vx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:14569001},portal:{[bx]:{address:`0x82527353927d8D069b3B452904c942dA149BA381`,blockCreated:2411324}},disputeGameFactory:{[bx]:{address:`0xE28AAdcd9883746c0e5068F58f9ea06027b214cb`,blockCreated:2411324}},l2OutputOracle:{[bx]:{address:`0x4a2635e9e4f6e45817b1D402ac4904c1d1752438`,blockCreated:2411324}},l1StandardBridge:{[bx]:{address:`0xD1B0E0581973c9eB7f886967A606b9441A897037`,blockCreated:2411324}}},testnet:!0});var Sx=11155111;const Cx=L({...vx,id:11142220,name:`Celo Sepolia Testnet`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`S-CELO`},rpcUrls:{default:{http:[`https://forno.celo-sepolia.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Sepolia Explorer`,url:`https://celo-sepolia.blockscout.com/`,apiUrl:`https://celo-sepolia.blockscout.com/api`}},contracts:{...vx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1},portal:{[Sx]:{address:`0x44ae3d41a335a7d05eb533029917aad35662dcc2`,blockCreated:8825790}},disputeGameFactory:{[Sx]:{address:`0x57c45d82d1a995f1e135b8d7edc0a6bb5211cfaa`,blockCreated:8825790}},l1StandardBridge:{[Sx]:{address:`0xec18a3c30131a0db4246e785355fbc16e2eaf408`,blockCreated:8825790}}},testnet:!0}),wx=L({id:5858,name:`Chang Chain Foundation Mainnet`,nativeCurrency:{decimals:18,name:`CTH`,symbol:`CTH`},rpcUrls:{default:{http:[`https://rpc.cthscan.com`]}},blockExplorers:{default:{name:`Chang Chain explorer`,url:`https://cthscan.com`}}}),Tx=L({id:88888,name:`Chiliz Chain`,network:`chiliz-chain`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://rpc.chiliz.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`https://scan.chiliz.com`,apiUrl:`https://scan.chiliz.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8080847}}}),Ex=L({id:2882,name:`Chips Network`,network:`CHIPS`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://node.chips.ooo/wasp/api/v1/chains/iota1pp3d3mnap3ufmgqnjsnw344sqmf5svjh26y2khnmc89sv6788y3r207a8fn/evm`]}}}),Dx=L({id:5115,name:`Citrea Testnet`,nativeCurrency:{name:`cBTC`,symbol:`cBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.citrea.xyz`]}},blockExplorers:{default:{name:`Citrea Explorer`,url:`https://explorer.testnet.citrea.xyz`,apiUrl:`https://explorer.testnet.citrea.xyz/api`}},testnet:!0}),Ox=L({id:61,name:`Ethereum Classic`,nativeCurrency:{decimals:18,name:`ETC`,symbol:`ETC`},rpcUrls:{default:{http:[`https://etc.rivet.link`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.com/etc/mainnet`}}}),kx=L({id:112,name:`Coinbit Mainnet`,nativeCurrency:{name:`GIDR`,symbol:`GIDR`,decimals:18},rpcUrls:{default:{http:[`https://coinbit-rpc-mainnet.chain.sbcrypto.app`]}},blockExplorers:{default:{name:`Coinbit Explorer`,url:`https://coinbit-explorer.chain.sbcrypto.app`}},testnet:!1}),Ax=L({id:52,name:`CoinEx Mainnet`,nativeCurrency:{name:`cet`,symbol:`cet`,decimals:18},rpcUrls:{default:{http:[`https://rpc.coinex.net`]}},blockExplorers:{default:{name:`CoinEx Explorer`,url:`https://www.coinex.net`}},testnet:!1}),jx=L({id:1030,name:`Conflux eSpace`,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evm.confluxrpc.com`],webSocket:[`wss://evm.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evm.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:68602935}}}),Mx=L({id:71,name:`Conflux eSpace Testnet`,network:`cfx-espace-testnet`,testnet:!0,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evmtestnet.confluxrpc.com`],webSocket:[`wss://evmtestnet.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evmtestnet.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:117499050}}}),Nx=L({id:1116,name:`Core Dao`,nativeCurrency:{decimals:18,name:`Core`,symbol:`CORE`},rpcUrls:{default:{http:[`https://rpc.coredao.org`]}},blockExplorers:{default:{name:`CoreDao`,url:`https://scan.coredao.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:11907934}},testnet:!1}),Px=L({id:1115,name:`Core Testnet`,nativeCurrency:{decimals:18,name:`tCore`,symbol:`TCORE`},rpcUrls:{default:{http:[`https://rpc.test.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet`,url:`https://scan.test.btcs.network`,apiUrl:`https://api.test.btcs.network/api`}},contracts:{multicall3:{address:`0xCcddF20A1932537123C2E48Bd8e00b108B8f7569`,blockCreated:29350509}},testnet:!0}),Fx=L({id:1114,name:`Core Testnet2`,nativeCurrency:{decimals:18,name:`tCore2`,symbol:`TCORE2`},rpcUrls:{default:{http:[`https://rpc.test2.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet2`,url:`https://scan.test2.btcs.network`,apiUrl:`https://api.test2.btcs.network/api`}},contracts:{multicall3:{address:`0x3CB285ff3Cd5C7C7e570b1E7DE3De17A0f985e56`,blockCreated:3838600}},testnet:!0}),Ix=L({id:21e6,name:`Corn`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Explorer`,url:`https://cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/21000000/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3228}},sourceId:1}),Lx=L({id:21000001,name:`Corn Testnet`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Testnet Explorer`,url:`https://testnet.cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/21000001/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4886}},testnet:!0,sourceId:11155111}),Rx=L({id:44,name:`Crab Network`,nativeCurrency:{decimals:18,name:`Crab Network Native Token`,symbol:`CRAB`},rpcUrls:{default:{http:[`https://crab-rpc.darwinia.network`],webSocket:[`wss://crab-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://crab-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3032593}}}),zx=L({id:66665,name:`Creator`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.creatorchain.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.creatorchain.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0}),Bx=L({id:102032,name:`Creditcoin Devnet`,nativeCurrency:{name:`Devnet CTC`,symbol:`devCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-devnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-devnet.creditcoin.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-devnet.blockscout.com`,apiUrl:`https://creditcoin3-dev.subscan.io`}},testnet:!0}),Vx=L({id:102030,name:`Creditcoin`,nativeCurrency:{name:`Creditcoin`,symbol:`CTC`,decimals:18},rpcUrls:{default:{http:[`https://mainnet3.creditcoin.network`],webSocket:[`wss://mainnet3.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin.blockscout.com`,apiUrl:`https://creditcoin.blockscout.com/api`}},testnet:!1}),Hx=L({id:102031,name:`Creditcoin Testnet`,nativeCurrency:{name:`Creditcoin Testnet`,symbol:`tCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-testnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-testnet.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-testnet.blockscout.com`,apiUrl:`https://creditcoin-testnet.blockscout.com/api`}},testnet:!0}),Ux=L({id:25,name:`Cronos Mainnet`,nativeCurrency:{decimals:18,name:`Cronos`,symbol:`CRO`},rpcUrls:{default:{http:[`https://evm.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer`,url:`https://explorer.cronos.org`,apiUrl:`https://explorer-api.cronos.org/mainnet/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1963112}}}),Wx=L({id:338,name:`Cronos Testnet`,nativeCurrency:{decimals:18,name:`CRO`,symbol:`tCRO`},rpcUrls:{default:{http:[`https://evm-t3.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer (Testnet)`,url:`https://explorer.cronos.org/testnet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:10191251}},testnet:!0}),Gx=L({id:388,name:`Cronos zkEVM Mainnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM CRO`,symbol:`zkCRO`},rpcUrls:{default:{http:[`https://mainnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM (Mainnet) Chain Explorer`,url:`https://explorer.zkevm.cronos.org`}},contracts:{multicall3:{address:`0x06f4487d7c4a5983d2660db965cc6d2565e4cfaa`,blockCreated:72}}}),Kx=L({id:282,name:`Cronos zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM Test Coin`,symbol:`zkTCRO`},rpcUrls:{default:{http:[`https://testnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM Testnet Explorer`,url:`https://explorer.zkevm.cronos.org/testnet`}},testnet:!0}),qx=L({id:3737,name:`Crossbell`,nativeCurrency:{decimals:18,name:`CSB`,symbol:`CSB`},rpcUrls:{default:{http:[`https://rpc.crossbell.io`]}},blockExplorers:{default:{name:`CrossScan`,url:`https://scan.crossbell.io`,apiUrl:`https://scan.crossbell.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:38246031}}}),Jx=L({id:4158,name:`CrossFi Mainnet`,nativeCurrency:{decimals:18,name:`CrossFi`,symbol:`XFI`},rpcUrls:{default:{http:[`https://rpc.mainnet.ms`]}},blockExplorers:{default:{name:`CrossFi Blockchain Explorer`,url:`https://xfiscan.com`}},testnet:!1}),Yx=L({id:33111,name:`Curtis`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.curtis.apechain.com`]}},blockExplorers:{default:{name:`Curtis Explorer`,url:`https://explorer.curtis.apechain.com`}},testnet:!0}),Xx=L({id:7560,name:`Cyber`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://cyberscan.co`,apiUrl:`https://cyberscan.co/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),Zx=L({id:111557560,name:`Cyber Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber-testnet.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet.cyberscan.co`,apiUrl:`https://testnet.cyberscan.co/api`}},contracts:{multicall3:{address:`0xffc391F0018269d4758AEA1a144772E8FB99545E`,blockCreated:304545}},testnet:!0}),Qx=L({id:824,name:`Daily Network Mainnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.mainnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Mainnet Explorer`,url:`https://explorer.mainnet.dailycrypto.net`}},testnet:!1}),$x=L({id:825,name:`Daily Network Testnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.testnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Testnet Explorer`,url:`https://explorer.testnet.dailycrypto.net`}},testnet:!0}),eS=L({id:46,name:`Darwinia Network`,nativeCurrency:{decimals:18,name:`RING`,symbol:`RING`},rpcUrls:{default:{http:[`https://rpc.darwinia.network`],webSocket:[`wss://rpc.darwinia.network`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69420}}}),tS=L({id:20240603,name:`DBK chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.dbkchain.io`]}},blockExplorers:{default:{name:`DBK Chain Explorer`,url:`https://scan.dbkchain.io`}},testnet:!1}),nS=L({...R,id:0x9a697f88076c8,name:`Dchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchain-2716446429837000-1.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchain-2716446429837000-1.sagaexplorer.io`,apiUrl:`https://api-dchain-2716446429837000-1.sagaexplorer.io/api`}},contracts:{...R.contracts}}),rS=L({...R,id:0x9a379ba03cf10,name:`Dchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchaintestnet-2713017997578000-1.jsonrpc.testnet.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io`,apiUrl:`https://api-dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io/api`}},contracts:{...R.contracts}}),iS=L({id:1130,network:`defichain-evm`,name:`DeFiChain EVM Mainnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.mainnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:137852}}}),aS=L({id:1131,network:`defichain-evm-testnet`,name:`DeFiChain EVM Testnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.testnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live/?network=TestNet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:156462}},testnet:!0}),oS=L({id:666666666,name:`Degen`,nativeCurrency:{decimals:18,name:`Degen`,symbol:`DEGEN`},rpcUrls:{default:{http:[`https://rpc.degen.tips`],webSocket:[`wss://rpc.degen.tips`]}},blockExplorers:{default:{name:`Degen Chain Explorer`,url:`https://explorer.degen.tips`,apiUrl:`https://explorer.degen.tips/api/v2`}}}),sS=L({id:53935,name:`DFK Chain`,nativeCurrency:{decimals:18,name:`Jewel`,symbol:`JEWEL`},rpcUrls:{default:{http:[`https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc`]}},blockExplorers:{default:{name:`DFKSubnetScan`,url:`https://subnets.avax.network/defi-kingdoms`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14790551}}}),cS=L({id:15,name:`Diode Prenet`,nativeCurrency:{decimals:18,name:`DIODE`,symbol:`DIODE`},rpcUrls:{default:{http:[`https://prenet.diode.io:8443`],webSocket:[`wss://prenet.diode.io:8443/ws`]}},blockExplorers:{default:{name:`Diode Explorer`,url:`https://diode.io/prenet`}},testnet:!1}),lS=L({id:513100,name:`DisChain`,nativeCurrency:{decimals:18,name:`DIS`,symbol:`DIS`},rpcUrls:{default:{http:[`https://rpc.dischain.xyz`]}},blockExplorers:{default:{name:`DisChain Explorer`,url:`https://www.oklink.com/dis`}}}),uS=L({id:53457,name:`DODOchain Testnet`,nativeCurrency:{decimals:18,name:`DODO`,symbol:`DODO`},rpcUrls:{default:{http:[`https://dodochain-testnet.alt.technology`],webSocket:[`wss://dodochain-testnet.alt.technology/ws`]}},blockExplorers:{default:{name:`DODOchain Testnet (Sepolia) Explorer`,url:`https://testnet-scan.dodochain.com`}},testnet:!0}),dS=L({id:2e3,name:`Dogechain`,nativeCurrency:{decimals:18,name:`Wrapped Dogecoin`,symbol:`WDOGE`},rpcUrls:{default:{http:[`https://rpc.dogechain.dog`]}},blockExplorers:{default:{name:`DogeChainExplorer`,url:`https://explorer.dogechain.dog`,apiUrl:`https://explorer.dogechain.dog/api`}},contracts:{multicall3:{address:`0x68a8609a60a008EFA633dfdec592c03B030cC508`,blockCreated:25384031}}}),fS=L({id:97476,name:`Doma Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc-testnet.doma.xyz`]}},blockExplorers:{default:{name:`Doma Testnet Explorer`,url:`https://explorer-testnet.doma.xyz`}},testnet:!0}),pS=L({id:42026,name:`Donatuz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.donatuz.com`]}},blockExplorers:{default:{name:`Donatuz Explorer`,url:`https://explorer.donatuz.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),mS=L({id:7979,name:`DOS Chain`,nativeCurrency:{decimals:18,name:`DOS Chain`,symbol:`DOS`},rpcUrls:{default:{http:[`https://main.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Explorer`,url:`https://doscan.io`,apiUrl:`https://api.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:161908}}}),hS=L({id:3939,name:`DOS Chain Testnet`,nativeCurrency:{decimals:18,name:`DOS Chain Testnet`,symbol:`DOS`},rpcUrls:{default:{http:[`https://test.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Testnet Explorer`,url:`https://test.doscan.io`,apiUrl:`https://api-test.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69623}},testnet:!0}),gS=L({id:23451,name:`DreyerX Mainnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Scan`,url:`https://scan.dreyerx.com`}}}),_S=L({id:23452,name:`DreyerX Testnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`http://testnet-rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Testnet Scan`,url:`https://testnet-scan.dreyerx.com`}},testnet:!0}),vS=L({id:555888,name:`DustBoy IoT`,nativeCurrency:{name:`Ether`,symbol:`DST`,decimals:18},rpcUrls:{default:{http:[`https://dustboy-rpc.jibl2.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://dustboy.jibl2.com`,apiUrl:`https://dustboy.jibl2.com/api`}},contracts:{multicall3:{address:`0xFFD34aa2C62B2D52E00A361e466C229788f4eD6a`,blockCreated:526569}},testnet:!1}),yS=L({id:1100,name:`Dymension`,nativeCurrency:{name:`DYM`,symbol:`DYM`,decimals:18},rpcUrls:{default:{http:[`https://dymension-evm-rpc.publicnode.com`],webSocket:[`wss://dymension-evm-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Dym FYI`,url:`https://dym.fyi`}},testnet:!1}),bS=L({id:5424,name:`edeXa`,nativeCurrency:{name:`edeXa`,symbol:`EDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edexa.network`]}},blockExplorers:{default:{name:`edeXa Explorer`,url:`https://explorer.edexa.network`,apiUrl:`https://explorer.edexa.network/api/v2`}}}),xS=L({id:1995,name:`edeXa Testnet`,nativeCurrency:{name:`edeXa`,symbol:`tEDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.edexa.network`]}},blockExplorers:{default:{name:`edeXa Testnet Explorer`,url:`https://explorer.testnet.edexa.network`,apiUrl:`https://explorer.testnet.edexa.network/api/v2`}},testnet:!0}),SS=L({id:2026,name:`Edgeless Network`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edgeless.network/http`],webSocket:[`wss://rpc.edgeless.network/ws`]}},blockExplorers:{default:{name:`Edgeless Explorer`,url:`https://explorer.edgeless.network`}}}),CS=L({id:202,name:`Edgeless Testnet`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://edgeless-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://edgeless-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Edgeless Testnet Explorer`,url:`https://testnet.explorer.edgeless.network`}}}),wS=L({id:2021,name:`Edgeware EdgeEVM Mainnet`,nativeCurrency:{decimals:18,name:`Edgeware`,symbol:`EDG`},rpcUrls:{default:{http:[`https://edgeware-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://edgscan.live`,apiUrl:`https://edgscan.live/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18117872}}}),TS=L({id:2022,name:`Beresheet BereEVM Testnet`,nativeCurrency:{decimals:18,name:`Testnet EDG`,symbol:`tEDG`},rpcUrls:{default:{http:[`https://beresheet-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://testnet.edgscan.live`,apiUrl:`https://testnet.edgscan.live/api`}}}),ES=L({id:41923,name:`EDU Chain`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.edu-chain.raas.gelato.cloud`]}},blockExplorers:{default:{name:`EDU Chain Explorer`,url:`https://educhain.blockscout.com/`}},testnet:!1}),DS=L({id:656476,name:`EDU Chain Testnet`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.open-campus-codex.gelato.digital/`],webSocket:[`wss://ws.open-campus-codex.gelato.digital`]}},blockExplorers:{default:{name:`EDU Chain Testnet Explorer`,url:`https://opencampus-codex.blockscout.com`,apiUrl:`https://opencampus-codex.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15514133}},testnet:!0}),OS=L({id:1994,name:`Ekta`,nativeCurrency:{decimals:18,name:`EKTA`,symbol:`EKTA`},rpcUrls:{default:{http:[`https://main.ekta.io`]}},blockExplorers:{default:{name:`Ektascan`,url:`https://ektascan.io`,apiUrl:`https://ektascan.io/api`}}}),kS=L({id:1004,name:`Ekta Testnet`,nativeCurrency:{decimals:18,name:`EKTA`,symbol:`EKTA`},rpcUrls:{default:{http:[`https://test.ekta.io:8545`]}},blockExplorers:{default:{name:`Test Ektascan`,url:`https://test.ektascan.io`,apiUrl:`https://test.ektascan.io/api`}},testnet:!0}),AS=L({id:20,name:`Elastos Smart Chain`,nativeCurrency:{name:`ELA`,symbol:`ELA`,decimals:18},rpcUrls:{default:{http:[`https://api2.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc.elastos.io`}},testnet:!1}),jS=L({id:21,name:`Elastos Smart Chain Testnet`,nativeCurrency:{name:`tELA`,symbol:`tELA`,decimals:18},rpcUrls:{default:{http:[`https://api-testnet.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc-testnet.elastos.io`}},testnet:!0}),MS=L({id:52014,name:`Electroneum Mainnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.electroneum.com`}},testnet:!1}),NS=L({id:5201420,name:`Electroneum Testnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.thesecurityteam.rocks`}},testnet:!0}),PS=L({...R,id:1338,name:`Elysium Testnet`,nativeCurrency:{decimals:18,name:`LAVA`,symbol:`LAVA`},rpcUrls:{default:{http:[`https://elysium-test-rpc.vulcanforged.com`]}},blockExplorers:{default:{name:`Elysium testnet explorer`,url:`https://elysium-explorer.vulcanforged.com`}},testnet:!0}),FS=L({id:246,name:`Energy Mainnet`,nativeCurrency:{name:`EWT`,symbol:`EWT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.energyweb.org`]}},blockExplorers:{default:{name:`EnergyWeb Explorer`,url:`https://explorer.energyweb.org`}},testnet:!1}),IS=L({id:173,name:`ENI Mainnet`,nativeCurrency:{decimals:18,name:`ENI`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc.eniac.network`]}},blockExplorers:{default:{name:`ENI Explorer`,url:`https://scan.eniac.network`}},testnet:!1}),LS=L({id:6912115,name:`ENI Testnet`,nativeCurrency:{decimals:18,name:`ENI Testnet Token`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc-testnet.eniac.network`]}},blockExplorers:{default:{name:`ENI Testnet Explorer`,url:`https://scan-testnet.eniac.network`}},testnet:!0}),RS=L({id:119,name:`ENULS Mainnet`,nativeCurrency:{decimals:18,name:`NULS`,symbol:`NULS`},rpcUrls:{default:{http:[`https://evmapi2.nuls.io`]}},blockExplorers:{default:{name:`ENULS Explorer`,url:`https://evmscan.nuls.io`}},testnet:!1}),zS=L({id:7332,name:`Horizen EON`,nativeCurrency:{decimals:18,name:`ZEN`,symbol:`ZEN`},rpcUrls:{default:{http:[`https://eon-rpc.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`EON Explorer`,url:`https://eon-explorer.horizenlabs.io`}},contracts:{}}),BS=L({id:17777,name:`EOS EVM`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Explorer`,url:`https://explorer.evm.eosnetwork.com`,apiUrl:`https://explorer.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7943933}}}),VS=L({id:15557,name:`EOS EVM Testnet`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.testnet.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Testnet Explorer`,url:`https://explorer.testnet.evm.eosnetwork.com`,apiUrl:`https://explorer.testnet.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9067940}},testnet:!0}),HS=L({id:140,name:`Eteria`,nativeCurrency:{name:`Eteria`,symbol:`ERA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.eteria.io/v1`]}},blockExplorers:{default:{name:`Eteria Explorer`,url:`https://explorer.eteria.io`,apiUrl:`https://explorer.eteria.io/api`}}}),US=L({id:42793,name:`Etherlink`,blockTime:4830,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.mainnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink`,url:`https://explorer.etherlink.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:33899}}}),WS=L({id:128123,name:`Etherlink Testnet`,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.ghostnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink Testnet`,url:`https://testnet.explorer.etherlink.com`}},testnet:!0}),GS=L({id:183,name:`Ethernity`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.ethernitychain.io`]}},blockExplorers:{default:{name:`Ethernity Explorer`,url:`https://ernscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),KS=L({id:20256789,name:`ETP Mainnet`,nativeCurrency:{decimals:18,name:`ETP Chain Native Token`,symbol:`ETP`},rpcUrls:{default:{http:[`https://rpc.etpscan.xyz`]}},blockExplorers:{default:{name:`ETP Scan`,url:`https://etpscan.xyz`}}}),qS=L({id:9001,name:`Evmos`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.org:8545`]}},blockExplorers:{default:{name:`Evmos Block Explorer`,url:`https://escan.live`}}}),JS=L({id:9e3,name:`Evmos Testnet`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.dev:8545`]}},blockExplorers:{default:{name:`Evmos Testnet Block Explorer`,url:`https://evm.evmos.dev/`}}}),YS=L({id:22052002,name:`Excelon Mainnet`,network:`XLON`,nativeCurrency:{decimals:18,name:`Excelon`,symbol:`xlon`},rpcUrls:{default:{http:[`https://edgewallet1.xlon.org`]}},blockExplorers:{default:{name:`Excelon explorer`,url:`https://explorer.excelon.io`}}}),XS=L({id:2,name:`Expanse Network`,nativeCurrency:{decimals:18,name:`EXP`,symbol:`EXP`},rpcUrls:{default:{http:[`https://node.expanse.tech`]}},blockExplorers:{default:{name:`Expanse Explorer`,url:`https://explorer.expanse.tech`}},testnet:!1}),ZS=L({id:7200,name:`exSat Network`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan.exsat.network`,apiUrl:`https://scan.exsat.network/api`}}}),QS=L({id:839999,name:`exSat Testnet`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm-tst3.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan-testnet.exsat.network`,apiUrl:`https://scan-testnet.exsat.network/api`}}}),$S=L({id:250,name:`Fantom`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://250.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://ftmscan.com`,apiUrl:`https://api.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:33001987}}}),eC=L({id:64240,name:`Fantom Sonic Open Testnet`,network:`fantom-sonic-testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpcapi.sonic.fantom.network`]}},blockExplorers:{default:{name:`Fantom Sonic Open Testnet Explorer`,url:`https://public-sonic.fantom.network`}},testnet:!0}),tC=L({id:4002,name:`Fantom Testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpc.testnet.fantom.network`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://testnet.ftmscan.com`,apiUrl:`https://testnet.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8328688}},testnet:!0}),nC=L({id:12306,name:`Fibo Chain`,nativeCurrency:{decimals:18,name:`fibo`,symbol:`FIBO`},rpcUrls:{default:{http:[`https://network.hzroc.art`]}},blockExplorers:{default:{name:`FiboScan`,url:`https://scan.fibochain.org`}}}),rC=L({id:314,name:`Filecoin Mainnet`,nativeCurrency:{decimals:18,name:`filecoin`,symbol:`FIL`},rpcUrls:{default:{http:[`https://api.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://filfox.info/en`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3328594}}}),iC=L({id:314159,name:`Filecoin Calibration`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.calibration.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filscan`,url:`https://calibration.filscan.io`}},testnet:!0}),aC=L({id:3141,name:`Filecoin Hyperspace`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.hyperspace.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://hyperspace.filfox.info/en`}},testnet:!0}),oC=L({id:253368190,name:`Flame`,network:`flame`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.flame.astria.org`],webSocket:[`wss://ws.flame.astria.org`]}},blockExplorers:{default:{name:`Flame Explorer`,url:`https://explorer.flame.astria.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6829148}}}),sC=L({id:14,name:`Flare Mainnet`,nativeCurrency:{decimals:18,name:`Flare`,symbol:`FLR`},rpcUrls:{default:{http:[`https://flare-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Flare Explorer`,url:`https://flare-explorer.flare.network`,apiUrl:`https://flare-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3002461}}}),cC=L({id:114,name:`Flare Testnet Coston2`,nativeCurrency:{decimals:18,name:`Coston2 Flare`,symbol:`C2FLR`},rpcUrls:{default:{http:[`https://coston2-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston2 Explorer`,url:`https://coston2-explorer.flare.network`,apiUrl:`https://coston2-explorer.flare.network/api`}},testnet:!0}),lC=L({id:747,name:`Flow EVM Mainnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://mainnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Mainnet Explorer`,url:`https://evm.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}},blockTime:800}),uC=L({id:646,name:`Flow EVM Previewnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://previewnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Previewnet Explorer`,url:`https://previewnet.flowdiver.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}}}),dC=L({id:545,name:`Flow EVM Testnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://testnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Flow Diver`,url:`https://evm-testnet.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:137518}},testnet:!0,blockTime:800}),fC=L({id:9999999,name:`Fluence`,nativeCurrency:{name:`FLT`,symbol:`FLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.fluence.dev`],webSocket:[`wss://ws.mainnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.mainnet.fluence.dev`,apiUrl:`https://blockscout.mainnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:207583}}}),pC=L({id:123420000220,name:`Fluence Stage`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stage.fluence.dev`],webSocket:[`wss://ws.stage.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.stage.fluence.dev`,apiUrl:`https://blockscout.stage.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83227}},testnet:!0}),mC=L({id:52164803,name:`Fluence Testnet`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.fluence.dev`],webSocket:[`wss://ws.testnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.testnet.fluence.dev`,apiUrl:`https://blockscout.testnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96424}},testnet:!0}),hC=L({id:20993,name:`Fluent Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.dev.gblend.xyz`]}},blockExplorers:{default:{name:`Fluent Explorer`,url:`https://blockscout.dev.gblend.xyz`}},testnet:!0});var gC=1;const _C=L({id:478,name:`Form Network`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.form.network/http`],webSocket:[`wss://rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Explorer`,url:`https://explorer.form.network`}},contracts:{...R.contracts,addressManager:{[gC]:{address:`0x15c249E46A2F924C2dB3A1560CF86729bAD1f07B`}},l1CrossDomainMessenger:{[gC]:{address:`0xF333158DCCad1dF6C3F0a3aEe8BC31fA94d9eD5c`}},l2OutputOracle:{[gC]:{address:`0x4ccAAF69F41c5810cA875183648B577CaCf1F67E`}},portal:{[gC]:{address:`0x4E259Ee5F4136408908160dD32295A5031Fa426F`}},l1StandardBridge:{[gC]:{address:`0xdc20aA63D3DE59574E065957190D8f24e0F7B8Ba`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:gC}),vC=L({id:984122,name:`Forma`,network:`forma`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.forma.art`],webSocket:[`wss://ws.forma.art`]}},blockExplorers:{default:{name:`Forma Explorer`,url:`https://explorer.forma.art`}},contracts:{multicall3:{address:`0xd53C6FFB123F7349A32980F87faeD8FfDc9ef079`,blockCreated:252705}}});var yC=11155111;const bC=L({id:132902,name:`Form Testnet`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia-rpc.form.network/http`],webSocket:[`wss://sepolia-rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Testnet Explorer`,url:`https://sepolia-explorer.form.network`}},contracts:{...R.contracts,addressManager:{[yC]:{address:`0xd5C38fa934f7fd7477D4800F4f38a1c5BFdF1373`}},l1CrossDomainMessenger:{[yC]:{address:`0x37A68565c4BE9700b3E3Ec60cC4416cAC3052FAa`}},l2OutputOracle:{[yC]:{address:`0x9eA2239E65a59EC9C7F1ED4C116dD58Da71Fc1e2`}},portal:{[yC]:{address:`0x60377e3cE15dF4CCA24c4beF076b60314240b032`}},l1StandardBridge:{[yC]:{address:`0xD4531f633942b2725896F47cD2aFd260b44Ab1F7`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0,sourceId:yC}),xC=L({id:80931,name:`Forta Chain`,nativeCurrency:{symbol:`FORT`,name:`FORT`,decimals:18},rpcUrls:{default:{http:[`https://rpc-forta-chain-8gj1qndmfc.t.conduit.xyz`]}},blockExplorers:{default:{name:`Forta Explorer`,url:`https://explorer.forta.org`}}}),SC=L({id:31337,name:`Foundry`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}});var CC=1;const wC=L({...R,id:252,name:`Fraxtal`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.frax.com`]}},blockExplorers:{default:{name:`fraxscan`,url:`https://fraxscan.com`,apiUrl:`https://api.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[CC]:{address:`0x66CC916Ed5C6C2FA97014f7D1cD141528Ae171e4`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[CC]:{address:`0x36cb65c1967A0Fb0EEE11569C51C2f2aA1Ca6f6D`,blockCreated:19135323}},l1StandardBridge:{[CC]:{address:`0x34C0bD5877A5Ee7099D0f5688D65F4bB9158BDE2`,blockCreated:19135323}}},sourceId:CC});var TC=17e3;const EC=L({...R,id:2522,name:`Fraxtal Testnet`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.frax.com`]}},blockExplorers:{default:{name:`fraxscan testnet`,url:`https://holesky.fraxscan.com`,apiUrl:`https://api-holesky.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[TC]:{address:`0x715EA64DA13F4d0831ece4Ad3E8c1aa013167F32`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[TC]:{address:`0xB9c64BfA498d5b9a8398Ed6f46eb76d90dE5505d`,blockCreated:318416}},l1StandardBridge:{[TC]:{address:`0x0BaafC217162f64930909aD9f2B27125121d6332`,blockCreated:318416}}},sourceId:TC});var DC=1;const OC=L({...R,id:33979,name:`Funki`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.funkichain.com`]}},blockExplorers:{default:{name:`Funki Mainnet Explorer`,url:`https://funkiscan.io`}},contracts:{...R.contracts},sourceId:DC});var kC=11155111;const AC=L({...R,id:3397901,network:`funkiSepolia`,name:`Funki Sepolia Sandbox`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://funki-testnet.alt.technology`]}},blockExplorers:{default:{name:`Funki Sepolia Sandbox Explorer`,url:`https://sepolia-sandbox.funkichain.com/`}},testnet:!0,contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204}},sourceId:kC}),jC=L({id:122,name:`Fuse`,nativeCurrency:{name:`Fuse`,symbol:`FUSE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fuse.io`]}},blockExplorers:{default:{name:`Fuse Explorer`,url:`https://explorer.fuse.io`,apiUrl:`https://explorer.fuse.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:16146628}}}),MC=L({id:123,name:`Fuse Sparknet`,nativeCurrency:{name:`Spark`,symbol:`SPARK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fusespark.io`]}},blockExplorers:{default:{name:`Sparkent Explorer`,url:`https://explorer.fusespark.io`,apiUrl:`https://explorer.fusespark.io/api`}}}),NC=L({id:32659,name:`Fusion Mainnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.fusionnetwork.io`],webSocket:[`wss://mainnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10441605}},testnet:!1}),PC=L({id:46688,name:`Fusion Testnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.fusionnetwork.io`],webSocket:[`wss://testnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://testnet.fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10428309}},testnet:!0});var FC=17e3;const IC=L({...R,name:`Garnet Testnet`,testnet:!0,id:17069,sourceId:FC,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.garnetchain.com`],webSocket:[`wss://rpc.garnetchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.garnetchain.com`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[FC]:{address:`0x57ee40586fbE286AfC75E67cb69511A6D9aF5909`,blockCreated:1274684}},l2OutputOracle:{[FC]:{address:`0xCb8E7AC561b8EF04F2a15865e9fbc0766FEF569B`,blockCreated:1274684}},l1StandardBridge:{[FC]:{address:`0x09bcDd311FE398F80a78BE37E489f5D440DB95DE`,blockCreated:1274684}}}}),LC=L({id:63157,name:`Geist Mainnet`,nativeCurrency:{decimals:18,name:`Aavegotchi GHST Token`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://geist-mainnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:660735}}}),RC=L({id:16507,name:`Genesys Mainnet`,nativeCurrency:{decimals:18,name:`GSYS`,symbol:`GSYS`},rpcUrls:{default:{http:[`https://rpc.genesys.network`]}},blockExplorers:{default:{name:`Genesys Explorer`,url:`https://gchainexplorer.genesys.network`}},testnet:!1});var zC=11155111;const BC=L({...R,id:91342,network:`giwa-sepolia`,name:`GIWA Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia-rpc.giwa.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-explorer.giwa.io`,apiUrl:`https://sepolia-explorer.giwa.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[zC]:{address:`0x37347caB2afaa49B776372279143D71ad1f354F6`}},portal:{[zC]:{address:`0x956962C34687A954e611A83619ABaA37Ce6bC78A`}},l1StandardBridge:{[zC]:{address:`0x77b2ffc0F57598cAe1DB76cb398059cF5d10A7E7`}}},testnet:!0,sourceId:zC}),VC=L({id:251,name:`Glide L1 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l1-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l1-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),HC=L({id:253,name:`Glide L2 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l2-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l2-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),UC=L({id:100,name:`Gnosis`,nativeCurrency:{decimals:18,name:`xDAI`,symbol:`XDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.gnosischain.com`],webSocket:[`wss://rpc.gnosischain.com/wss`]}},blockExplorers:{default:{name:`Gnosisscan`,url:`https://gnosisscan.io`,apiUrl:`https://api.gnosisscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:21022491}}}),WC=L({id:10200,name:`Gnosis Chiado`,nativeCurrency:{decimals:18,name:`Gnosis`,symbol:`xDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.chiadochain.net`],webSocket:[`wss://rpc.chiadochain.net/wss`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.chiadochain.net`,apiUrl:`https://blockscout.chiadochain.net/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4967313}},testnet:!0}),GC=L({id:2345,name:`GOAT`,nativeCurrency:{decimals:18,name:`Bitcoin`,symbol:`BTC`},rpcUrls:{default:{http:[`https://rpc.goat.network`]}},blockExplorers:{default:{name:`Goat Explorer`,url:`https://explorer.goat.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),KC=L({id:1663,name:`Horizen Gobi Testnet`,nativeCurrency:{decimals:18,name:`Test ZEN`,symbol:`tZEN`},rpcUrls:{default:{http:[`https://gobi-testnet.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`Gobi Explorer`,url:`https://gobi-explorer.horizen.io`}},contracts:{},testnet:!0}),qC=L({id:60,name:`GoChain`,nativeCurrency:{decimals:18,name:`GO`,symbol:`GO`},rpcUrls:{default:{http:[`https://rpc.gochain.io`]}},blockExplorers:{default:{name:`GoChain Explorer`,url:`https://explorer.gochain.io`}},testnet:!1}),JC=L({id:71402,name:`Godwoken Mainnet`,nativeCurrency:{decimals:18,name:`pCKB`,symbol:`pCKB`},rpcUrls:{default:{http:[`https://v1.mainnet.godwoken.io/rpc`]}},blockExplorers:{default:{name:`GW Scan`,url:`https://v1.gwscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15034}},testnet:!1}),YC=L({id:5,name:`Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://5.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.etherscan.io`,apiUrl:`https://api-goerli.etherscan.io/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},ensUniversalResolver:{address:`0xfc4AC75C46C914aF5892d6d3eFFcebD7917293F1`,blockCreated:10339206},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6507670}},testnet:!0}),XC=L({id:440017,name:`Graphite Network`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Spectre`,url:`https://main.atgraphite.com`,apiUrl:`https://api.main.atgraphite.com/api`}},testnet:!1}),ZC=L({id:54170,name:`Graphite Network Testnet`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-test-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-test-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Testnet Spectre`,url:`https://test.atgraphite.com`,apiUrl:`https://api.test.atgraphite.com/api`}},testnet:!0}),QC=L({id:1625,name:`Gravity Alpha Mainnet`,nativeCurrency:{name:`G`,symbol:`G`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gravity.xyz`]}},blockExplorers:{default:{name:`Gravity Explorer`,url:`https://explorer.gravity.xyz`,apiUrl:`https://explorer.gravity.xyz/api`}},contracts:{multicall3:{address:`0xf8ac4BEB2F75d2cFFb588c63251347fdD629B92c`,blockCreated:16851}}}),$C=L({id:43419,name:`Gunz Mainnet`,nativeCurrency:{name:`GUN`,symbol:`GUN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gunzchain.io/ext/bc/2M47TxWHGnhNtq6pM5zPXdATBtuqubxn5EPFgFmEawCQr9WFML/rpc`]}},blockExplorers:{default:{name:`Gunz Explorer`,url:`https://gunzscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:70502}}}),ew=L({id:260,name:`Guru Network Mainnet`,nativeCurrency:{name:`GURU Token`,symbol:`GURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-main.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/260`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://scan.gurunetwork.ai`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:271691}},testnet:!1}),tw=L({id:261,name:`Guru Network Testnet`,nativeCurrency:{name:`tGURU Token`,symbol:`tGURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/261`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://sepolia.gurunetwork.ai`}},testnet:!0}),nw=L({id:5112,name:`Ham`,nativeCurrency:{decimals:18,name:`Ham`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.ham.fun`],webSocket:[`wss://rpc.ham.fun`]}},blockExplorers:{default:{name:`Ham Chain Explorer`,url:`https://explorer.ham.fun`,apiUrl:`https://explorer.ham.fun/api/v2`}}}),rw=L({id:216,name:`Happychain Testnet`,nativeCurrency:{symbol:`HAPPY`,name:`HAPPY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.happy.tech/http`],webSocket:[`wss://rpc.testnet.happy.tech/ws`]}},blockExplorers:{default:{name:`Happy Chain Testnet Explorer`,url:`https://explorer.testnet.happy.tech`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0}),iw=L({id:11235,name:`HAQQ Mainnet`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLM`},rpcUrls:{default:{http:[`https://rpc.eth.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.haqq.network`,apiUrl:`https://explorer.haqq.network/api`}}}),aw=L({id:54211,name:`HAQQ Testedge 2`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLMT`},rpcUrls:{default:{http:[`https://rpc.eth.testedge2.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.testedge2.haqq.network`,apiUrl:`https://explorer.testedge2.haqq.network/api`}}}),ow=L({id:31337,name:`Hardhat`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),sw=L({id:16666e5,name:`Harmony One`,nativeCurrency:{name:`Harmony`,symbol:`ONE`,decimals:18},rpcUrls:{default:{http:[`https://1666600000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Harmony Explorer`,url:`https://explorer.harmony.one`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:24185753}}}),cw=L({id:177,name:`HashKey Chain`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://mainnet.hsk.xyz`]}},blockExplorers:{default:{name:`HashKey Chain Explorer`,url:`https://hashkey.blockscout.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),lw=L({id:133,name:`HashKey Chain Testnet`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://hashkeychain-testnet.alt.technology`]}},blockExplorers:{default:{name:`HashKey Chain Explorer`,url:`https://hashkeychain-testnet-explorer.alt.technology`}},testnet:!0}),uw=L({id:1523903251,name:`Haust Network Testnet`,nativeCurrency:{decimals:18,name:`HAUST`,symbol:`HAUST`},rpcUrls:{default:{http:[`https://rpc-testnet.haust.app`]}},blockExplorers:{default:{name:`Haust Network Testnet Explorer`,url:`https://explorer-testnet.haust.app`}},testnet:!0}),dw=L({id:295,name:`Hedera Mainnet`,network:`hedera-mainnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/mainnet`}},testnet:!1}),fw=L({id:297,name:`Hedera Previewnet`,network:`hedera-previewnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://previewnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/previewnet`}},testnet:!0}),pw=L({id:296,name:`Hedera Testnet`,network:`hedera-testnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://testnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/testnet`}},testnet:!0}),mw=L({id:8668,name:`Hela Mainnet`,nativeCurrency:{name:`HLUSD`,symbol:`HLUSD`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.helachain.com`]}},blockExplorers:{default:{name:`Hela explorer`,url:`https://mainnet-blockexplorer.helachain.com`}},testnet:!1}),hw=L({id:43111,name:`Hemi`,network:`Hemi`,blockTime:12e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.hemi.xyz`}},testnet:!1}),gw=L({id:743111,name:`Hemi Sepolia`,network:`Hemi Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`Hemi Sepolia explorer`,url:`https://testnet.explorer.hemi.xyz`}},testnet:!0}),_w=L({id:17e3,name:`Holesky`,nativeCurrency:{name:`Holesky Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://ethereum-holesky-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://holesky.etherscan.io`,apiUrl:`https://api-holesky.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:77},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:4295055}},testnet:!0}),vw=L({id:560048,name:`Hoodi`,nativeCurrency:{name:`Hoodi Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hoodi.ethpandaops.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://hoodi.etherscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2589}},testnet:!0}),yw=L({id:269,name:`High Performance Blockchain`,nativeCurrency:{name:`HPB`,symbol:`HPB`,decimals:18},rpcUrls:{default:{http:[`https://hpbnode.com`]}},blockExplorers:{default:{name:`hpbScan`,url:`https://hscan.org`}},testnet:!1}),bw=L({id:12323,name:`Huddle01 dRTC Chain`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle01.calderachain.xyz/http`],webSocket:[`wss://huddle01.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle01.calderaexplorer.xyz`,apiUrl:`https://huddle01.calderaexplorer.xyz/api`}},sourceId:42161}),xw=L({id:2524852,name:`Huddle01 dRTC Chain Testnet`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://huddle-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle-testnet.explorer.caldera.xyz`,apiUrl:`https://huddle-testnet.explorer.caldera.xyz/api`}},sourceId:421614}),Sw=L({id:6985385,name:`Humanity`,nativeCurrency:{name:`H`,symbol:`H`,decimals:18},rpcUrls:{default:{http:[`https://humanity-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Humanity Mainnet Explorer`,url:`https://humanity-mainnet.explorer.alchemy.com`,apiUrl:`https://humanity-mainnet.explorer.alchemy.com/api`}},testnet:!1}),Cw=L({id:7080969,name:`Humanity Testnet`,nativeCurrency:{name:`tHP`,symbol:`tHP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.humanity.org`]}},blockExplorers:{default:{name:`Humanity Testnet Explorer`,url:`https://humanity-testnet.explorer.alchemy.com`,apiUrl:`https://humanity-testnet.explorer.alchemy.com/api`}},testnet:!0}),ww=L({id:5234,name:`Humanode`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.mainnet.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.mainnet.stages.humanode.io`]}},blockExplorers:{default:{name:`Subscan`,url:`https://humanode.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4413097}}}),Tw=L({id:14853,name:`Humanode Testnet 5`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.testnet5.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.testnet5.stages.humanode.io`]}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),Ew=L({id:2911,name:`HYCHAIN`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://explorer.hychain.com`}},testnet:!1}),Dw=L({id:29112,name:`HYCHAIN Testnet`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://testnet-rpc.hychain.com/http`}},testnet:!0}),Ow=L({id:998,name:`Hyperliquid EVM Testnet`,nativeCurrency:{name:`HYPE`,symbol:`HYPE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hyperliquid-testnet.xyz/evm`]}},testnet:!0}),kw=L({id:73115,name:`ICB Network`,nativeCurrency:{decimals:18,name:`ICB Native Token`,symbol:`ICBX`},rpcUrls:{default:{http:[`https://rpc1-mainnet.icbnetwork.info`]}},blockExplorers:{default:{name:`ICB Explorer`,url:`https://icbscan.io`,apiUrl:`https://icbscan.io/api`}},testnet:!1}),Aw=L({id:74,name:`IDChain Mainnet`,nativeCurrency:{decimals:18,name:`EIDI`,symbol:`EIDI`},rpcUrls:{default:{http:[`https://idchain.one/rpc`],webSocket:[`wss://idchain.one/ws`]}},blockExplorers:{default:{name:`IDChain Explorer`,url:`https://explorer.idchain.one`}},testnet:!1}),jw=L({id:13371,name:`Immutable zkEVM`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.immutable.com`]}},blockExplorers:{default:{name:`Immutable Explorer`,url:`https://explorer.immutable.com`,apiUrl:`https://explorer.immutable.com/api`}},contracts:{multicall3:{address:`0x236bdA4589e44e6850f5aC6a74BfCa398a86c6c0`,blockCreated:4335972}}}),Mw=L({id:13473,name:`Immutable zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.testnet.immutable.com`]}},blockExplorers:{default:{name:`Immutable Testnet Explorer`,url:`https://explorer.testnet.immutable.com/`}},contracts:{multicall3:{address:`0x2CC787Ed364600B0222361C4188308Fa8E68bA60`,blockCreated:5977391}},testnet:!0}),Nw=L({id:2525,name:`inEVM Mainnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://mainnet.rpc.inevm.com/http`]}},blockExplorers:{default:{name:`inEVM Explorer`,url:`https://inevm.calderaexplorer.xyz`,apiUrl:`https://inevm.calderaexplorer.xyz/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:118606}}}),Pw=L({id:7233,name:`InitVerse Mainnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-mainnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseScan`,url:`https://www.iniscan.com`,apiUrl:`https://explorer-api.inichain.com/api`}},contracts:{multicall3:{address:`0x83466BE48A067115FFF91f7b892Ed1726d032e47`,blockCreated:2318}}}),Fw=L({id:7234,name:`InitVerse Genesis Testnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-testnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseGenesisScan`,url:`https://genesis-testnet.iniscan.com`,apiUrl:`https://explorer-testnet-api.inichain.com/api`}},contracts:{multicall3:{address:`0x0cF32CBDd6c437331EA4f85ed2d881A5379B5a6F`,blockCreated:16361}},testnet:!0}),Iw=L({id:1776,name:`Injective`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://sentry.evm-rpc.injective.network`],webSocket:[`wss://sentry.evm-ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://blockscout.injective.network`,apiUrl:`https://blockscout.injective.network/api`}},testnet:!1}),Lw=L({id:1439,name:`Injective Testnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://k8s.testnet.json-rpc.injective.network`],webSocket:[`wss://k8s.testnet.ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://testnet.blockscout.injective.network`,apiUrl:`https://testnet.blockscout.injective.network/api`}},testnet:!0});var Rw=1;const zw=L({...R,id:57073,name:`Ink`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel.inkonchain.com`,`https://rpc-qnd.inkonchain.com`],webSocket:[`wss://rpc-gel.inkonchain.com`,`wss://rpc-qnd.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.inkonchain.com`,apiUrl:`https://explorer.inkonchain.com/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},...R.contracts,disputeGameFactory:{[Rw]:{address:`0x10d7b35078d3baabb96dd45a9143b94be65b12cd`}},portal:{[Rw]:{address:`0x5d66c1782664115999c47c9fa5cd031f495d3e4f`}},l1StandardBridge:{[Rw]:{address:`0x88ff1e5b602916615391f55854588efcbb7663f0`}}},testnet:!1,sourceId:Rw});var Bw=11155111;const Vw=L({...R,id:763373,name:`Ink Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel-sepolia.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer-sepolia.inkonchain.com/`,apiUrl:`https://explorer-sepolia.inkonchain.com/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[Bw]:{address:`0x860e626c700af381133d9f4af31412a2d1db3d5d`}},portal:{[Bw]:{address:`0x5c1d29c6c9c8b0800692acc95d700bcb4966a1d7`}},l1StandardBridge:{[Bw]:{address:`0x33f60714bbd74d62b66d79213c348614de51901c`}}},testnet:!0,sourceId:Bw}),Hw=L({id:8822,name:`IOTA EVM`,network:`iotaevm`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.iota.org`,apiUrl:`https://explorer.evm.iota.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25022}}}),Uw=L({id:1075,name:`IOTA EVM Testnet`,network:`iotaevm-testnet`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.testnet.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.testnet.iotaledger.net`,apiUrl:`https://explorer.evm.testnet.iotaledger.net/api`}},testnet:!0}),Ww=L({id:4689,name:`IoTeX`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.mainnet.iotex.io`],webSocket:[`wss://babel-api.mainnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://iotexscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:22163670}}}),Gw=L({id:4690,name:`IoTeX Testnet`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.testnet.iotex.io`],webSocket:[`wss://babel-api.testnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://testnet.iotexscan.io`}},contracts:{multicall3:{address:`0xb5cecD6894c6f473Ec726A176f1512399A2e355d`,blockCreated:24347592}},testnet:!0}),Kw=L({id:8017,name:`iSunCoin Mainnet`,nativeCurrency:{decimals:18,name:`ISC`,symbol:`ISC`},rpcUrls:{default:{http:[`https://mainnet.isuncoin.com`]}},blockExplorers:{default:{name:`iSunCoin Explorer`,url:`https://baifa.io/app/chains/8017`}}}),qw=L({id:8899,name:`JB Chain`,network:`jbc`,nativeCurrency:{name:`JBC`,symbol:`JBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-l1.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-l1.jibchain.net`,apiUrl:`https://exp-l1.jibchain.net/api`}},contracts:{multicall3:{address:`0xc0C8C486D1466C57Efe13C2bf000d4c56F47CBdC`,blockCreated:2299048}},testnet:!1}),Jw=L({id:88991,name:`Jibchain Testnet`,nativeCurrency:{name:`tJBC`,symbol:`tJBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.testnet.jibchain.net`,apiUrl:`https://exp.testnet.jibchain.net/api`}},contracts:{multicall3:{address:`0xa1a858ad9041B4741e620355a3F96B3c78e70ecE`,blockCreated:32848}},testnet:!0}),Yw=L({id:81,name:`Japan Open Chain Mainnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Token`,symbol:`JOC`},rpcUrls:{default:{http:[`https://rpc-1.japanopenchain.org:8545`,`https://rpc-2.japanopenchain.org:8545`,`https://rpc-3.japanopenchain.org`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.japanopenchain.org`}},testnet:!1}),Xw=L({id:10081,name:`Japan Open Chain Testnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Testnet Token`,symbol:`JOCT`},rpcUrls:{default:{http:[`https://rpc-1.testnet.japanopenchain.org:8545`,`https://rpc-2.testnet.japanopenchain.org:8545`,`https://rpc-3.testnet.japanopenchain.org`]}},blockExplorers:{default:{name:`Testnet Block Explorer`,url:`https://explorer.testnet.japanopenchain.org`}},testnet:!0}),Zw=L({id:5734951,name:`Jovay Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.zan.top/public/jovay-mainnet`]}},blockExplorers:{default:{name:`Jovay Explorer`,url:`https://explorer.jovay.io`}},testnet:!1}),Qw=L({id:2019775,name:`Jovay Sepolia Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.zan.top/public/jovay-testnet`]}},blockExplorers:{default:{name:`Jovay Testnet Explorer`,url:`https://sepolia-explorer.jovay.io/l2`}},testnet:!0}),$w=L({id:45003,name:`Juneo JUNE-Chain`,nativeCurrency:{decimals:18,name:`JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/2`,apiUrl:`https://juneoscan.io/chain/2/api`}}}),eT=L({id:45013,name:`Juneo BCH1-Chain`,nativeCurrency:{decimals:18,name:`Juneo BCH1-Chain`,symbol:`BCH1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/BCH1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/12`,apiUrl:`https://juneoscan.io/chain/12/api`}}}),tT=L({id:45004,name:`Juneo DAI1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DAI1-Chain`,symbol:`DAI1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DAI1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/5`,apiUrl:`https://juneoscan.io/chain/5/api`}}}),nT=L({id:45010,name:`Juneo DOGE1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DOGE1-Chain`,symbol:`DOGE1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DOGE1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/10`,apiUrl:`https://juneoscan.io/chain/10/api`}}}),rT=L({id:45011,name:`Juneo EUR1-Chain`,nativeCurrency:{decimals:18,name:`Juneo EUR1-Chain`,symbol:`EUR1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/EUR1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/6`,apiUrl:`https://juneoscan.io/chain/6/api`}}}),iT=L({id:45008,name:`Juneo GLD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo GLD1-Chain`,symbol:`GLD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/GLD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/8`,apiUrl:`https://juneoscan.io/chain/8/api`}}}),aT=L({id:45014,name:`Juneo LINK1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LINK1-Chain`,symbol:`LINK1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LINK1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/13`,apiUrl:`https://juneoscan.io/chain/13/api`}}}),oT=L({id:45009,name:`Juneo LTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LTC1-Chain`,symbol:`LTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/11`,apiUrl:`https://juneoscan.io/chain/11/api`}}}),sT=L({id:45007,name:`Juneo mBTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo mBTC1-Chain`,symbol:`mBTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/mBTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/9`,apiUrl:`https://juneoscan.io/chain/9/api`}}}),cT=L({id:45012,name:`Juneo SGD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo SGD1-Chain`,symbol:`SGD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/SGD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/7`,apiUrl:`https://juneoscan.io/chain/7/api`}}}),lT=L({id:101003,name:`Socotra JUNE-Chain`,nativeCurrency:{decimals:18,name:`Socotra JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.socotra-testnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://socotra.juneoscan.io/chain/2`,apiUrl:`https://socotra.juneoscan.io/chain/2/api`}},testnet:!0}),uT=L({id:45006,name:`Juneo USD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USD1-Chain`,symbol:`USD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/4`,apiUrl:`https://juneoscan.io/chain/4/api`}}}),dT=L({id:45005,name:`Juneo USDT1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USDT1-Chain`,symbol:`USDT1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USDT1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/3`,apiUrl:`https://juneoscan.io/chain/3/api`}}}),fT=L({id:8217,name:`Kaia`,nativeCurrency:{decimals:18,name:`Kaia`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kaiascan.io`,apiUrl:`https://api-cypress.klaytnscope.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),pT=L({id:1001,name:`Kairos Testnet`,network:`kairos`,nativeCurrency:{decimals:18,name:`Kairos KAIA`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en-kairos.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kairos.kaiascan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),mT=L({id:1802203764,name:`Kakarot Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),hT=L({id:920637907288165,name:`Kakarot Starknet Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),gT=L({id:24,name:`KardiaChain Mainnet`,nativeCurrency:{name:`KAI`,symbol:`KAI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kardiachain.io`]}},blockExplorers:{default:{name:`KardiaChain Explorer`,url:`https://explorer.kardiachain.io`}},testnet:!1}),_T=L({id:686,name:`Karura`,network:`karura`,nativeCurrency:{name:`Karura`,symbol:`KAR`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-karura.aca-api.network`],webSocket:[`wss://eth-rpc-karura.aca-api.network`]}},blockExplorers:{default:{name:`Karura Blockscout`,url:`https://blockscout.karura.network`,apiUrl:`https://blockscout.karura.network/api`}},testnet:!1}),vT=L({id:747474,name:`Katana`,network:`katana`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katana.network`]}},blockExplorers:{default:{name:`katana explorer`,url:`https://explorer.katanarpc.com`}},testnet:!1}),yT=L({id:2222,name:`Kava EVM`,network:`kava-mainnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Explorer`,url:`https://kavascan.com`,apiUrl:`https://kavascan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3661165}},testnet:!1}),bT=L({id:2221,name:`Kava EVM Testnet`,network:`kava-testnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.testnet.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Testnet Explorer`,url:`https://testnet.kavascan.com/`,apiUrl:`https://testnet.kavascan.com/api`}},contracts:{multicall3:{address:`0xDf1D724A7166261eEB015418fe8c7679BBEa7fd6`,blockCreated:7242179}},testnet:!0}),xT=L({id:321,name:`KCC Mainnet`,network:`KCC Mainnet`,nativeCurrency:{decimals:18,name:`KCS`,symbol:`KCS`},rpcUrls:{default:{http:[`https://kcc-rpc.com`]}},blockExplorers:{default:{name:`KCC Explorer`,url:`https://explorer.kcc.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11760430}},testnet:!1}),ST=L({id:1336,name:`Kii Testnet Oro`,network:`kii-testnet-oro`,nativeCurrency:{name:`Kii`,symbol:`KII`,decimals:18},rpcUrls:{default:{http:[`https://json-rpc.uno.sentry.testnet.v3.kiivalidator.com`]}},blockExplorers:{default:{name:`KiiExplorer`,url:`https://explorer.kiichain.io/testnet`}},testnet:!0}),CT=L({id:7887,name:`Kinto Mainnet`,network:`Kinto Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kinto.xyz/http`]}},blockExplorers:{default:{name:`Kinto Explorer`,url:`https://explorer.kinto.xyz`}},testnet:!1}),wT=L({id:8217,name:`Klaytn`,nativeCurrency:{decimals:18,name:`Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-cypress.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://scope.klaytn.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),TT=L({id:1001,name:`Klaytn Baobab Testnet`,network:`klaytn-baobab`,nativeCurrency:{decimals:18,name:`Baobab Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-baobab.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://baobab.klaytnscope.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),ET=L({id:701,name:`Koi Network`,nativeCurrency:{decimals:18,name:`Koi Network Native Token`,symbol:`KRING`},rpcUrls:{default:{http:[`https://koi-rpc.darwinia.network`],webSocket:[`wss://koi-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://koi-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:180001}},testnet:!0}),DT=L({id:255,name:`Kroma`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.kroma.network`]}},blockExplorers:{default:{name:`Kroma Explorer`,url:`https://blockscout.kroma.network`,apiUrl:`https://blockscout.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:16054868}},testnet:!1}),OT=L({id:2358,name:`Kroma Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.sepolia.kroma.network`]}},blockExplorers:{default:{name:`Kroma Sepolia Explorer`,url:`https://blockscout.sepolia.kroma.network`,apiUrl:`https://blockscout.sepolia.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8900914}},testnet:!0}),kT=L({id:12324,name:`L3X Protocol`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.l3x.com`],webSocket:[`wss://rpc-mainnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Mainnet Explorer`,url:`https://explorer.l3x.com`,apiUrl:`https://explorer.l3x.com/api/v2`}},testnet:!1}),AT=L({id:12325,name:`L3X Protocol Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.l3x.com`],webSocket:[`wss://rpc-testnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Testnet Explorer`,url:`https://explorer-testnet.l3x.com`,apiUrl:`https://explorer-testnet.l3x.com/api/v2`}},testnet:!0}),jT=L({id:360890,name:`LAVITA Mainnet`,nativeCurrency:{name:`vTFUEL`,symbol:`vTFUEL`,decimals:18},rpcUrls:{default:{http:[`https://tsub360890-eth-rpc.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`LAVITA Explorer`,url:`https://tsub360890-explorer.thetatoken.org`}},testnet:!1}),MT=L({id:232,name:`Lens`,nativeCurrency:{name:`GHO`,symbol:`GHO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lens.xyz`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://explorer.lens.xyz`,apiUrl:`https://explorer.lens.xyz/api`}}}),NT=L({id:37111,name:`Lens Testnet`,nativeCurrency:{name:`GRASS`,symbol:`GRASS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.lens.dev`],webSocket:[`wss://rpc.testnet.lens.dev/ws`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://block-explorer.testnet.lens.dev`,apiUrl:`https://block-explorer-api.staging.lens.dev/api`}},testnet:!0}),PT=L({id:21363,name:`Lestnet`,nativeCurrency:{name:`Lestnet Ether`,symbol:`LETH`,decimals:18},rpcUrls:{default:{http:[`https://service.lestnet.org`]}},blockExplorers:{default:{name:`Lestnet Explorer`,url:`https://explore.lestnet.org`}},testnet:!0}),FT=L({id:1891,name:`LightLink Pegasus Testnet`,network:`lightlink-pegasus`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.pegasus.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Pegasus Explorer`,url:`https://pegasus.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:127188532}},testnet:!0}),IT=L({id:1890,name:`LightLink Phoenix Mainnet`,network:`lightlink-phoenix`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.phoenix.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Phoenix Explorer`,url:`https://phoenix.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:125499184}},testnet:!1});ra(),A(),Fp(),Lu(),Wu(),$u();async function LT(e,t){let{account:n=e.account}=t;if(!n)throw new Om;let r=na(n);try{let{accessList:n,blockNumber:i,blockTag:a,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p,...m}=t,h=(typeof i==`bigint`?k(i):void 0)||a;Qu(t);let g=e.chain?.formatters?.transactionRequest?.format,_=(g||Bu)({...Iu(m,{format:g}),from:r?.address,accessList:n,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p},`estimateGas`),{baseFeePerGas:v,gasLimit:y,priorityFeePerGas:b}=await e.request({method:`linea_estimateGas`,params:h?[_,h]:[_]});return{baseFeePerGas:BigInt(v),gasLimit:BigInt(y),priorityFeePerGas:BigInt(b)}}catch(n){throw Pp(n,{...t,account:r,chain:e.chain})}}const RT={fees:{estimateFeesPerGas:zT,async maxPriorityFeePerGas({block:e,client:t,request:n}){let r=await zT({block:e,client:t,multiply:e=>e,request:n,type:`eip1559`});return r?.maxPriorityFeePerGas?r.maxPriorityFeePerGas:null}}};async function zT({client:e,multiply:t,request:n,type:r}){try{let i=await LT(e,{...n,account:n?.account}),{priorityFeePerGas:a}=i,o=t(BigInt(i.baseFeePerGas))+a;return r===`legacy`?{gasPrice:o}:{maxFeePerGas:o,maxPriorityFeePerGas:a}}catch{return null}}const BT=L({...RT,id:59144,name:`Linea Mainnet`,blockTime:2e3,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.linea.build`],webSocket:[`wss://rpc.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://lineascan.build`,apiUrl:`https://api.lineascan.build/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:42},ensRegistry:{address:`0x50130b669B28C339991d8676FA73CF122a121267`,blockCreated:6682888},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:22222151}},ensTlds:[`.linea.eth`],testnet:!1}),VT=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://api-goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0}),HT=L({...RT,id:59141,name:`Linea Sepolia Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia.linea.build`],webSocket:[`wss://rpc.sepolia.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.lineascan.build`,apiUrl:`https://api-sepolia.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:227427},ensRegistry:{address:`0x5B2636F0f2137B4aE722C01dd5122D7d3e9541f7`,blockCreated:2395094},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:17168484}},ensTlds:[`.linea.eth`],testnet:!0}),UT=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0});var WT=1;const GT=L({...R,id:1135,name:`Lisk`,network:`lisk`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.lisk.com`,apiUrl:`https://blockscout.lisk.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xA9d71E1dd7ca26F26e656E66d6AA81ed7f745bf0`},l2OutputOracle:{[WT]:{address:`0x113cB99283AF242Da0A0C54347667edF531Aa7d6`}},portal:{[WT]:{address:`0x26dB93F8b8b4f7016240af62F7730979d353f9A7`}},l1StandardBridge:{[WT]:{address:`0x2658723Bf70c7667De6B25F99fcce13A16D25d08`}}},sourceId:WT});var KT=11155111;const qT=L({...R,id:4202,network:`lisk-sepolia`,name:`Lisk Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia-api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-blockscout.lisk.com`,apiUrl:`https://sepolia-blockscout.lisk.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[KT]:{address:`0xA0E35F56C318DE1bD5D9ca6A94Fe7e37C5663348`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[KT]:{address:`0xe3d90F21490686Ec7eF37BE788E02dfC12787264`}},l1StandardBridge:{[KT]:{address:`0x1Fb30e446eA791cd1f011675E5F3f5311b70faF5`}}},testnet:!0,sourceId:KT}),JT=L({id:9496,name:`Load Alphanet`,nativeCurrency:{name:`Testnet LOAD`,symbol:`tLOAD`,decimals:18},rpcUrls:{default:{http:[`https://alphanet.load.network`]}},blockExplorers:{default:{name:`Load Alphanet Explorer`,url:`https://explorer.load.network`}},testnet:!0}),YT=L({id:1337,name:`Localhost`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),XT=L({id:15551,name:`LoopNetwork Mainnet`,nativeCurrency:{name:`LOOP`,symbol:`LOOP`,decimals:18},rpcUrls:{default:{http:[`https://api.mainnetloop.com`]}},blockExplorers:{default:{name:`LoopNetwork Blockchain Explorer`,url:`https://explorer.mainnetloop.com/`}},testnet:!1}),ZT=L({id:42,network:`lukso`,name:`LUKSO`,nativeCurrency:{name:`LUKSO`,symbol:`LYX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.lukso.network`],webSocket:[`wss://ws-rpc.mainnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Mainnet Explorer`,url:`https://explorer.execution.mainnet.lukso.network`,apiUrl:`https://api.explorer.execution.mainnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:468183}}}),QT=L({id:4201,name:`LUKSO Testnet`,nativeCurrency:{decimals:18,name:`LUKSO Testnet`,symbol:`LYXt`},rpcUrls:{default:{http:[`https://rpc.testnet.lukso.network`],webSocket:[`wss://ws-rpc.testnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Testnet Explorer`,url:`https://explorer.execution.testnet.lukso.network`,apiUrl:`https://api.explorer.execution.testnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:605348}},testnet:!0}),$T=L({id:994873017,name:`Lumia Mainnet`,network:`LumiaMainnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Explorer`,url:`https://explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3975939}},testnet:!1}),eE=L({id:1952959480,name:`Lumia Testnet`,network:`LumiaTestnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Testnet Explorer`,url:`https://testnet-explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2235063}},testnet:!0}),tE=L({id:96370,name:`Lumoz`,nativeCurrency:{decimals:18,name:`Lumoz Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://rpc.lumoz.org`]}},blockExplorers:{default:{name:`Lumoz Scan`,url:`https://scan.lumoz.info`}},testnet:!1}),nE=L({id:105363,name:`Lumoz Testnet`,nativeCurrency:{decimals:18,name:`Lumoz Testnet Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://testnet-rpc.lumoz.org`]}},testnet:!0}),rE=L({id:721,name:`Lycan`,nativeCurrency:{decimals:18,name:`Lycan`,symbol:`LYC`},rpcUrls:{default:{http:[`https://rpc.lycanchain.com`,`https://us-east.lycanchain.com`,`https://us-west.lycanchain.com`,`https://eu-north.lycanchain.com`,`https://eu-west.lycanchain.com`,`https://asia-southeast.lycanchain.com`],webSocket:[`wss://rpc.lycanchain.com`,`wss://us-east.lycanchain.com`,`wss://us-west.lycanchain.com`,`wss://eu-north.lycanchain.com`,`wss://eu-west.lycanchain.com`,`wss://asia-southeast.lycanchain.com`]}},blockExplorers:{default:{name:`Lycan Explorer`,url:`https://explorer.lycanchain.com`}}}),iE=L({id:957,name:`Lyra Chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lyra.finance`]}},blockExplorers:{default:{name:`Lyra Explorer`,url:`https://explorer.lyra.finance`,apiUrl:`https://explorer.lyra.finance/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1935198}}}),aE=L({id:1,name:`Ethereum`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:12e3,rpcUrls:{default:{http:[`https://eth.merkle.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://etherscan.io`,apiUrl:`https://api.etherscan.io/api`}},contracts:{ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:23085558},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),oE=L({id:595,name:`Mandala TC9`,network:`mandala`,nativeCurrency:{name:`Mandala`,symbol:`mACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-tc9.aca-staging.network`],webSocket:[`wss://eth-rpc-tc9.aca-staging.network`]}},blockExplorers:{default:{name:`Mandala Blockscout`,url:`https://blockscout.mandala.aca-staging.network`,apiUrl:`https://blockscout.mandala.aca-staging.network/api`}},testnet:!0}),sE=L({id:169,name:`Manta Pacific Mainnet`,network:`manta`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.manta.network/http`]}},blockExplorers:{default:{name:`Manta Explorer`,url:`https://pacific-explorer.manta.network`,apiUrl:`https://pacific-explorer.manta.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:332890}}}),cE=L({id:3441006,name:`Manta Pacific Sepolia Testnet`,network:`manta-sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.sepolia-testnet.manta.network/http`]}},blockExplorers:{default:{name:`Manta Sepolia Testnet Explorer`,url:`https://pacific-explorer.sepolia-testnet.manta.network`,apiUrl:`https://pacific-explorer.sepolia-testnet.manta.network/api`}},contracts:{multicall3:{address:`0xca54918f7B525C8df894668846506767412b53E3`,blockCreated:479584}},testnet:!0}),lE=L({id:3441005,name:`Manta Pacific Testnet`,network:`manta-testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://manta-testnet.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Manta Testnet Explorer`,url:`https://pacific-explorer.testnet.manta.network`,apiUrl:`https://pacific-explorer.testnet.manta.network/api`}},contracts:{multicall3:{address:`0x211B1643b95Fe76f11eD8880EE810ABD9A4cf56C`,blockCreated:419915}},testnet:!0}),uE=L({id:5e3,name:`Mantle`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Explorer`,url:`https://mantlescan.xyz/`,apiUrl:`https://api.mantlescan.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:304717}}}),dE=L({id:5003,name:`Mantle Sepolia Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.sepolia.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.sepolia.mantle.xyz/`,apiUrl:`https://explorer.sepolia.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4584012}},testnet:!0}),fE=L({id:5001,name:`Mantle Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.testnet.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.testnet.mantle.xyz`,apiUrl:`https://explorer.testnet.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:561333}},testnet:!0}),pE=L({id:5887,name:`MANTRA DuKong EVM Testnet`,nativeCurrency:{decimals:18,name:`OM`,symbol:`OM`},rpcUrls:{default:{http:[`https://evm.dukong.mantrachain.io`]}},blockExplorers:{default:{name:`MANTRAScan`,url:`https://mantrascan.io/dukong`}},testnet:!0}),mE=L({id:5888,name:`MANTRA EVM`,nativeCurrency:{decimals:18,name:`OM`,symbol:`OM`},rpcUrls:{default:{http:[`https://evm.mantrachain.io`],webSocket:[`https://evm.mantrachain.io/ws`]}},blockExplorers:{default:{name:`MANTRA Scan`,url:`https://mantrascan.io/mainnet`}}}),hE=L({id:22776,name:`MAP Protocol`,nativeCurrency:{decimals:18,name:`MAPO`,symbol:`MAPO`},rpcUrls:{default:{http:[`https://rpc.maplabs.io`]}},blockExplorers:{default:{name:`MAPO Scan`,url:`https://maposcan.io`}},testnet:!1}),gE=L({id:698,name:`Matchain`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://matchscan.io`}}}),_E=L({id:699,name:`Matchain Testnet`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://testnet.matchscan.io`}},testnet:!0}),vE=L({id:29548,name:`MCH Verse`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.oasys.mycryptoheroes.net`]}},blockExplorers:{default:{name:`MCH Verse Explorer`,url:`https://explorer.oasys.mycryptoheroes.net`,apiUrl:`https://explorer.oasys.mycryptoheroes.net/api`}},testnet:!1}),yE=L({id:6342,blockTime:1e3,name:`MegaETH Testnet`,nativeCurrency:{name:`MegaETH Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://carrot.megaeth.com/rpc`],webSocket:[`wss://carrot.megaeth.com/ws`]}},blockExplorers:{default:{name:`MegaETH Testnet Explorer`,url:`https://www.megaexplorer.xyz/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0}),bE=L({id:7078815900,name:`Mekong Pectra Devnet`,nativeCurrency:{name:`eth`,symbol:`eth`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mekong.ethpandaops.io`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.mekong.ethpandaops.io`}},testnet:!0}),xE=L({id:333000333,name:`Meld`,nativeCurrency:{decimals:18,name:`Meld`,symbol:`MELD`},rpcUrls:{default:{http:[`https://rpc-1.meld.com`]}},blockExplorers:{default:{name:`MELDscan`,url:`https://meldscan.io`}},contracts:{multicall3:{address:`0x769ee5a8e82c15c1b6e358f62ac8eb6e3abe8dc5`,blockCreated:360069}}}),SE=L({id:4352,name:`MemeCore`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.memecore.net`],webSocket:[`wss://ws.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Explorer`,url:`https://memecorescan.io`,apiUrl:`https://api.memecorescan.io/api`},okx:{name:`MemeCore Explorer`,url:`https://web3.okx.com/explorer/memecore`},memecore:{name:`MemeCore Explorer`,url:`https://blockscout.memecore.com`,apiUrl:`https://blockscout.memecore.com/api`}}}),CE=L({id:43521,name:`Formicarium`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.formicarium.memecore.net`],webSocket:[`wss://ws.formicarium.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.memecorescan.io`},okx:{name:`MemeCore Testnet Explorer`,url:`https://web3.okx.com/explorer/formicarium-testnet`},memecore:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.blockscout.memecore.com`,apiUrl:`https://formicarium.blockscout.memecore.com/api`}},testnet:!0}),wE=L({id:4200,name:`Merlin`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://scan.merlinchain.io`,apiUrl:`https://scan.merlinchain.io/api`}}}),TE=L({id:4203,name:`Merlin Erigon Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-erigon-rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-erigon-scan.merlinchain.io`,apiUrl:`https://testnet-erigon-scan.merlinchain.io/api`}},testnet:!0}),EE=L({id:571,name:`MetaChain Mainnet`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.metatime.com`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://explorer.metatime.com`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}}}),DE=L({id:1453,name:`MetaChain Istanbul`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://istanbul-rpc.metachain.dev`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://istanbul-explorer.metachain.dev`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}},testnet:!0}),OE=L({id:11,name:`Metadium Network`,nativeCurrency:{decimals:18,name:`META`,symbol:`META`},rpcUrls:{default:{http:[`https://api.metadium.com/prod`]}},blockExplorers:{default:{name:`Metadium Explorer`,url:`https://explorer.metadium.com`}},testnet:!1});var kE=1;const AE=L({...R,id:1750,name:`Metal L2`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.metall2.com`],webSocket:[`wss://rpc.metall2.com`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.metall2.com`,apiUrl:`https://explorer.metall2.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[kE]:{address:`0x3B1F7aDa0Fcc26B13515af752Dd07fB1CAc11426`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},portal:{[kE]:{address:`0x3F37aBdE2C6b5B2ed6F8045787Df1ED1E3753956`}},l1StandardBridge:{[kE]:{address:`0x6d0f65D59b55B0FEC5d2d15365154DcADC140BF3`}}},sourceId:kE}),jE=L({id:82,name:`Meter`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpc.meter.io`]}},blockExplorers:{default:{name:`MeterScan`,url:`https://scan.meter.io`}}}),ME=L({id:83,name:`Meter Testnet`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpctest.meter.io`]}},blockExplorers:{default:{name:`MeterTestnetScan`,url:`https://scan-warringstakes.meter.io`}}}),NE=L({id:1088,name:`Metis`,nativeCurrency:{decimals:18,name:`Metis`,symbol:`METIS`},rpcUrls:{default:{http:[`https://metis.rpc.hypersync.xyz`,`https://metis-pokt.nodies.app`,`https://api.blockeden.xyz/metis/67nCBdZQSH9z3YqDDjdm`,`https://metis-andromeda.rpc.thirdweb.com`,`https://metis-andromeda.gateway.tenderly.co`,`https://metis.api.onfinality.io/public`,`https://andromeda.metis.io/?owner=1088`,`https://metis-mainnet.public.blastapi.io`],webSocket:[`wss://metis-rpc.publicnode.com`,`wss://metis.drpc.org`]}},blockExplorers:{default:{name:`Metis Explorer`,url:`https://explorer.metis.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2338552}}}),PE=L({id:599,name:`Metis Goerli`,nativeCurrency:{decimals:18,name:`Metis Goerli`,symbol:`METIS`},rpcUrls:{default:{http:[`https://goerli.gateway.metisdevops.link`]}},blockExplorers:{default:{name:`Metis Goerli Explorer`,url:`https://goerli.explorer.metisdevops.link`,apiUrl:`https://goerli.explorer.metisdevops.link/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1006207}}}),FE=L({id:59902,name:`Metis Sepolia`,nativeCurrency:{decimals:18,name:`Test Metis`,symbol:`tMETIS`},rpcUrls:{default:{http:[`https://sepolia.metisdevops.link`,`https://metis-sepolia-rpc.publicnode.com`,`https://metis-sepolia.gateway.tenderly.co`],webSocket:[`wss://metis-sepolia-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Metis Sepolia Explorer`,url:`https://sepolia-explorer.metisdevops.link`,apiUrl:`https://sepolia-explorer.metisdevops.link/api-docs`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:224185}}}),IE=L({id:7518,name:`MEVerse Chain Mainnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversemainnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://www.meversescan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:86881340}}}),LE=L({id:4759,name:`MEVerse Chain Testnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversetestnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.meversescan.io/`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:64371115}},testnet:!0}),RE=L({id:185,name:`Mint Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain explorer`,url:`https://explorer.mintchain.io`}},testnet:!1}),zE=L({id:1686,name:`Mint Sepolia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain Testnet explorer`,url:`https://testnet-explorer.mintchain.io`}},testnet:!0}),BE=L({id:124832,name:`Mitosis Testnet`,nativeCurrency:{name:`MITO`,symbol:`MITO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.mitosis.org`]}},blockExplorers:{default:{name:`Mitosis testnet explorer`,url:`https://testnet.mitosiscan.xyz`}},testnet:!0});var VE=1;const HE=L({...R,id:34443,name:`Mode Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.mode.network`]}},blockExplorers:{default:{name:`Modescan`,url:`https://modescan.io`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2465882},l2OutputOracle:{[VE]:{address:`0x4317ba146D4933D889518a3e5E11Fe7a53199b04`}},portal:{[VE]:{address:`0x8B34b14c7c7123459Cf3076b8Cb929BE097d0C07`}},l1StandardBridge:{[VE]:{address:`0x735aDBbE72226BD52e818E7181953f42E3b0FF21`}}},sourceId:VE});var UE=11155111;const WE=L({...R,id:919,name:`Mode Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.mode.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.mode.network`,apiUrl:`https://sepolia.explorer.mode.network/api`}},contracts:{...R.contracts,l2OutputOracle:{[UE]:{address:`0x2634BD65ba27AB63811c74A63118ACb312701Bfa`,blockCreated:3778393}},portal:{[UE]:{address:`0x320e1580effF37E008F1C92700d1eBa47c1B23fD`,blockCreated:3778395}},l1StandardBridge:{[UE]:{address:`0xbC5C679879B2965296756CD959C3C739769995E2`,blockCreated:3778392}},multicall3:{address:`0xBAba8373113Fb7a68f195deF18732e01aF8eDfCF`,blockCreated:3019007}},testnet:!0,sourceId:UE}),GE=L({id:10143,name:`Monad Testnet`,blockTime:400,nativeCurrency:{name:`Testnet MON Token`,symbol:`MON`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.monad.xyz`]}},blockExplorers:{default:{name:`Monad Testnet explorer`,url:`https://testnet.monadexplorer.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:251449}},testnet:!0}),KE=L({id:1287,name:`Moonbase Alpha`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`https://rpc.api.moonbase.moonbeam.network`],webSocket:[`wss://wss.api.moonbase.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonbase.moonscan.io`,apiUrl:`https://moonbase.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1850686}},testnet:!0}),qE=L({id:1284,name:`Moonbeam`,nativeCurrency:{decimals:18,name:`GLMR`,symbol:`GLMR`},rpcUrls:{default:{http:[`https://moonbeam.public.blastapi.io`],webSocket:[`wss://moonbeam.public.blastapi.io`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonscan.io`,apiUrl:`https://api-moonbeam.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:609002}},testnet:!1}),JE=L({id:1281,name:`Moonbeam Development Node`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`http://127.0.0.1:9944`],webSocket:[`wss://127.0.0.1:9944`]}}}),YE=L({id:1285,name:`Moonriver`,nativeCurrency:{decimals:18,name:`MOVR`,symbol:`MOVR`},rpcUrls:{default:{http:[`https://moonriver.public.blastapi.io`],webSocket:[`wss://moonriver.public.blastapi.io`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonriver.moonscan.io`,apiUrl:`https://api-moonriver.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1597904}},testnet:!1}),XE=L({id:2818,name:`Morph`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.morphl2.io`],webSocket:[`wss://rpc.morphl2.io:8443`]}},blockExplorers:{default:{name:`Morph Explorer`,url:`https://explorer.morphl2.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3654913}},testnet:!1}),ZE=L({id:2810,name:`Morph Holesky`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-quicknode-holesky.morphl2.io`],webSocket:[`wss://rpc-quicknode-holesky.morphl2.io`]}},blockExplorers:{default:{name:`Morph Holesky Explorer`,url:`https://explorer-holesky.morphl2.io`,apiUrl:`https://explorer-api-holesky.morphl2.io/api?`}},testnet:!0}),QE=L({id:2710,name:`Morph Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.morphl2.io`]}},blockExplorers:{default:{name:`Morph Testnet Explorer`,url:`https://explorer-testnet.morphl2.io`,apiUrl:`https://explorer-api-testnet.morphl2.io/api`}},testnet:!0}),$E=L({id:5551,name:`Nahmii 2 Mainnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://l2.nahmii.io`]}},blockExplorers:{default:{name:`Nahmii 2 Explorer`,url:`https://explorer.n2.nahmii.io`}},testnet:!1}),eD=L({id:22222,name:`Nautilus Mainnet`,nativeCurrency:{name:`ZBC`,symbol:`ZBC`,decimals:9},rpcUrls:{default:{http:[`https://api.nautilus.nautchain.xyz`]}},blockExplorers:{default:{name:`NautScan`,url:`https://nautscan.com`}}}),tD=L({id:397,name:`NEAR Protocol`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.mainnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer.near.org`}},testnet:!1}),nD=L({id:398,name:`NEAR Protocol Testnet`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.testnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer-testnet.near.org`}},testnet:!0}),rD=L({id:245022926,name:`Neon EVM DevNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://devnet.neonevm.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://devnet.neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:205206112}},testnet:!0}),iD=L({id:245022934,network:`neonMainnet`,name:`Neon EVM MainNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://neon-proxy-mainnet.solana.p2p.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:206545524}},testnet:!1}),aD=L({id:47763,name:`Neo X Mainnet`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-1.rpc.banelabs.org`,`https://mainnet-2.rpc.banelabs.org`]}},blockExplorers:{default:{name:`Neo X - Explorer`,url:`https://xexplorer.neo.org`}},testnet:!1}),oD=L({id:12227332,name:`Neo X Testnet T4`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.banelabs.org/`]}},blockExplorers:{default:{name:`neox-scan`,url:`https://xt4scan.ngd.network`}},testnet:!0}),sD=L({id:1012,name:`Newton`,nativeCurrency:{name:`Newton`,symbol:`NEW`,decimals:18},rpcUrls:{default:{http:[`https://global.rpc.mainnet.newtonproject.org`]}},blockExplorers:{default:{name:`NewFi explorer`,url:`https://explorer.newtonproject.org/`}},testnet:!1}),cD=L({id:4242,name:`Nexi`,nativeCurrency:{name:`Nexi`,symbol:`NEXI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.chain.nexi.technology`]}},blockExplorers:{default:{name:`NexiScan`,url:`https://www.nexiscan.com`,apiUrl:`https://www.nexiscan.com/api`}},contracts:{multicall3:{address:`0x0277A46Cc69A57eE3A6C8c158bA874832F718B8E`,blockCreated:25770160}}}),lD=L({id:240,name:`Nexilix Smart Chain`,nativeCurrency:{decimals:18,name:`Nexilix`,symbol:`NEXILIX`},rpcUrls:{default:{http:[`https://rpcurl.pos.nexilix.com`]}},blockExplorers:{default:{name:`NexilixScan`,url:`https://scan.nexilix.com`}},contracts:{multicall3:{address:`0x58381c8e2BF9d0C2C4259cA14BdA9Afe02831244`,blockCreated:74448}}}),uD=L({id:6900,name:`Nibiru`,nativeCurrency:{decimals:18,name:`NIBI`,symbol:`NIBI`},rpcUrls:{default:{http:[`https://evm-rpc.nibiru.fi`]}},blockExplorers:{default:{name:`NibiScan`,url:`https://nibiscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:19587573}}}),dD=L({id:200024,name:`Nitrograph Testnet`,testnet:!0,rpcUrls:{default:{http:[`https://rpc-testnet.nitrograph.foundation`]}},nativeCurrency:{name:`Nitro`,symbol:`NOS`,decimals:18},blockExplorers:{default:{url:`https://explorer-testnet.nitrograph.foundation`,name:`Nitrograph Explorer`}}}),fD=L({id:4090,network:`oasis-testnet`,name:`Oasis Testnet`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.oasis.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://oasis.ftnscan.com`,apiUrl:`https://oasis.ftnscan.com/api`}},testnet:!0}),pD=L({id:248,name:`Oasys`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.oasys.games`]}},blockExplorers:{default:{name:`OasysScan`,url:`https://scan.oasys.games`,apiUrl:`https://scan.oasys.games/api`}}}),mD=L({id:911867,name:`Odyssey Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://odyssey.ithaca.xyz`]}},blockExplorers:{default:{name:`Odyssey Explorer`,url:`https://odyssey-explorer.ithaca.xyz`,apiUrl:`https://odyssey-explorer.ithaca.xyz/api`}},testnet:!0}),hD=L({id:66,name:`OKC`,nativeCurrency:{decimals:18,name:`OKT`,symbol:`OKT`},rpcUrls:{default:{http:[`https://exchainrpc.okex.org`]}},blockExplorers:{default:{name:`oklink`,url:`https://www.oklink.com/okc`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10364792}}}),gD=L({id:311,name:`Omax Mainnet`,nativeCurrency:{decimals:18,name:`OMAX`,symbol:`OMAX`},rpcUrls:{default:{http:[`https://mainapi.omaxray.com`]}},blockExplorers:{default:{name:`Omax Explorer`,url:`https://omaxscan.com`}},testnet:!1}),_D=L({id:166,name:`Omni`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.omni.network`],webSocket:[`wss://mainnet.omni.network`]}},blockExplorers:{default:{name:`OmniScan`,url:`https://omniscan.network`}},testnet:!1}),vD=L({id:164,name:`Omni Omega`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://omega.omni.network`],webSocket:[`wss://omega.omni.network`]}},blockExplorers:{default:{name:`Omega OmniScan`,url:`https://omega.omniscan.network/`}},testnet:!0}),yD=L({id:309075,name:`One World Chain Mainnet`,nativeCurrency:{decimals:18,name:`OWCT`,symbol:`OWCT`},rpcUrls:{default:{http:[`https://mainnet-rpc.oneworldchain.org`]}},blockExplorers:{default:{name:`One World Explorer`,url:`https://mainnet.oneworldchain.org`}},testnet:!1}),bD=L({id:9700,name:`OORT MainnetDev`,nativeCurrency:{decimals:18,name:`OORT`,symbol:`OORT`},rpcUrls:{default:{http:[`https://dev-rpc.oortech.com`]}},blockExplorers:{default:{name:`OORT MainnetDev Explorer`,url:`https://dev-scan.oortech.com`}}});var xD=56;const SD=L({id:204,name:`opBNB`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://opbnb-mainnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opBNB (BSCScan)`,url:`https://opbnb.bscscan.com`,apiUrl:`https://api-opbnb.bscscan.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:512881},l2OutputOracle:{[xD]:{address:`0x153CAB79f4767E2ff862C94aa49573294B13D169`}},portal:{[xD]:{address:`0x1876EA7702C0ad0C6A2ae6036DE7733edfBca519`}},l1StandardBridge:{[xD]:{address:`0xF05F0e4362859c3331Cb9395CBC201E3Fa6757Ea`}}},sourceId:xD});var CD=97;const wD=L({id:5611,name:`opBNB Testnet`,nativeCurrency:{decimals:18,name:`tBNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://opbnb-testnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opbnbscan`,url:`https://testnet.opbnbscan.com`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3705108},l2OutputOracle:{[CD]:{address:`0xFf2394Bb843012562f4349C6632a0EcB92fC8810`}},portal:{[CD]:{address:`0x4386C8ABf2009aC0c263462Da568DD9d46e52a31`}},l1StandardBridge:{[CD]:{address:`0x677311Fd2cCc511Bbc0f581E8d9a07B033D5E840`}}},testnet:!0,sourceId:CD}),TD=L({id:1612,name:`OpenLedger`,nativeCurrency:{name:`Open`,symbol:`OPEN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.openledger.xyz`]}},blockExplorers:{default:{name:`OpenLedger Explorer`,url:`https://scan.openledger.xyz`}},testnet:!1});var ED=1;const DD=L({...R,id:10,name:`OP Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.optimism.io`]}},blockExplorers:{default:{name:`Optimism Explorer`,url:`https://optimistic.etherscan.io`,apiUrl:`https://api-optimistic.etherscan.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[ED]:{address:`0xe5965Ab5962eDc7477C8520243A95517CD252fA9`}},l2OutputOracle:{[ED]:{address:`0xdfe97868233d1aa22e815a266982f2cf17685a27`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4286263},portal:{[ED]:{address:`0xbEb5Fc579115071764c7423A4f12eDde41f106Ed`}},l1StandardBridge:{[ED]:{address:`0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1`}}},sourceId:ED});var OD=5;const kD=L({...R,id:420,name:`Optimism Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.optimism.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli-optimism.etherscan.io`,apiUrl:`https://goerli-optimism.etherscan.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[OD]:{address:`0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:49461},portal:{[OD]:{address:`0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383`}},l1StandardBridge:{[OD]:{address:`0x636Af16bf2f682dD3109e60102b8E1A089FedAa8`}}},testnet:!0,sourceId:OD});var AD=11155111;const jD=L({...R,id:11155420,name:`OP Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.optimism.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://optimism-sepolia.blockscout.com`,apiUrl:`https://optimism-sepolia.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[AD]:{address:`0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1`}},l2OutputOracle:{[AD]:{address:`0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204},portal:{[AD]:{address:`0x16Fc5058F25648194471939df75CF27A2fdC48BC`}},l1StandardBridge:{[AD]:{address:`0xFBb0621E0B23b5478B630BD55a5f21f67730B0F1`}}},testnet:!0,sourceId:AD}),MD=L({id:62050,name:`Optopia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan.optopia.ai`}},testnet:!1}),ND=L({id:62049,name:`Optopia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan-testnet.optopia.ai`}},testnet:!0}),PD=L({id:291,name:`Orderly`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.orderly.network`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorer.orderly.network`}},testnet:!1}),FD=L({id:4460,name:`Orderly Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorerl2new-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`}},testnet:!0}),ID=L({id:41144114,name:`Otim Devnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`http://devnet.otim.xyz`]}},contracts:{batchInvoker:{address:`0x5FbDB2315678afecb367f032d93F642f64180aa3`}}}),LD=L({id:11297108109,name:`Palm`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}}}),RD=L({id:11297108099,name:`Palm Testnet`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}},testnet:!0}),zD=L({id:3338,name:`Peaq`,nativeCurrency:{decimals:18,name:`peaq`,symbol:`PEAQ`},rpcUrls:{default:{http:[`https://quicknode1.peaq.xyz`,`https://quicknode2.peaq.xyz`,`https://quicknode3.peaq.xyz`],webSocket:[`wss://quicknode1.peaq.xyz`,`wss://quicknode2.peaq.xyz`,`wss://quicknode3.peaq.xyz`]}},blockExplorers:{default:{name:`Subscan`,url:`https://peaq.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3566354}}});var BD=1;const VD=L({id:424,network:`pgn`,name:`PGN`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Explorer`,url:`https://explorer.publicgoods.network`,apiUrl:`https://explorer.publicgoods.network/api`}},contracts:{l2OutputOracle:{[BD]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3380209},portal:{[BD]:{address:`0xb26Fd985c5959bBB382BAFdD0b879E149e48116c`}},l1StandardBridge:{[BD]:{address:`0xD0204B9527C1bA7bD765Fa5CCD9355d38338272b`}}},formatters:yy,sourceId:BD});var HD=11155111;const UD=L({id:58008,network:`pgn-testnet`,name:`PGN `,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Testnet Explorer`,url:`https://explorer.sepolia.publicgoods.network`,apiUrl:`https://explorer.sepolia.publicgoods.network/api`}},contracts:{l2OutputOracle:{[HD]:{address:`0xD5bAc3152ffC25318F848B3DD5dA6C85171BaEEe`}},portal:{[HD]:{address:`0xF04BdD5353Bb0EFF6CA60CfcC78594278eBfE179`}},l1StandardBridge:{[HD]:{address:`0xFaE6abCAF30D23e233AC7faF747F2fC3a5a6Bfa3`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3754925}},formatters:yy,sourceId:HD,testnet:!0}),WD=L({id:13381,name:`Phoenix Blockchain`,nativeCurrency:{name:`Phoenix`,symbol:`PHX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.phoenixplorer.com`]}},blockExplorers:{default:{name:`Phoenixplorer`,url:`https://phoenixplorer.com`,apiUrl:`https://phoenixplorer.com/api`}},contracts:{multicall3:{address:`0x498cF757a575cFF2c2Ed9f532f56Efa797f86442`,blockCreated:5620192}}}),GD=L({id:7070,name:`Planq Mainnet`,nativeCurrency:{decimals:18,name:`PLQ`,symbol:`PLQ`},rpcUrls:{default:{http:[`https://planq-rpc.nodies.app`,`https://evm-rpc.planq.network`,`https://jsonrpc.planq.nodestake.top`]}},blockExplorers:{default:{name:`Planq Explorer`,url:`https://evm.planq.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8470015}},testnet:!1}),KD=L({id:9745,name:`Plasma`,blockTime:1e3,nativeCurrency:{name:`Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plasma.to`]}},blockExplorers:{default:{name:`PlasmaScan`,url:`https://plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),qD=L({id:9747,name:`Plasma Devnet`,nativeCurrency:{name:`Devnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://devnet-rpc.plasma.to`]}},testnet:!0,contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),JD=L({id:9746,name:`Plasma Testnet`,nativeCurrency:{name:`Testnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plasma.to`]}},blockExplorers:{default:{name:`RouteScan`,url:`https://testnet.plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),YD=L({...oy,id:1612127,name:`PlayFi Albireo Testnet`,network:`albireo`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://albireo-rpc.playfi.ai`],webSocket:[`wss://albireo-rpc-ws.playfi.ai/ws`]}},blockExplorers:{default:{name:`PlayFi Albireo Explorer`,url:`https://albireo-explorer.playfi.ai`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`}},testnet:!0}),XD=L({id:242,name:`Plinga`,nativeCurrency:{name:`Plinga`,symbol:`PLINGA`,decimals:18},rpcUrls:{default:{http:[`https://rpcurl.mainnet.plgchain.com`]}},blockExplorers:{default:{name:`Plgscan`,url:`https://www.plgscan.com`}},contracts:{multicall3:{address:`0x0989576160f2e7092908BB9479631b901060b6e4`,blockCreated:204489}}}),ZD=L({id:98865,name:`Plume (Legacy)`,nativeCurrency:{name:`Plume Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plumenetwork.xyz`],webSocket:[`wss://rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plumenetwork.xyz`,apiUrl:`https://explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:48577}},sourceId:1}),QD=L({id:98864,name:`Plume Devnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc.plumenetwork.xyz`],webSocket:[`wss://test-rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://test-explorer.plumenetwork.xyz`,apiUrl:`https://test-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:481948}},testnet:!0,sourceId:11155111}),$D=L({id:98866,name:`Plume`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plume.org`],webSocket:[`wss://rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plume.org`,apiUrl:`https://explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39679}},sourceId:1}),eO=L({id:98867,name:`Plume Testnet`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plume.org`],webSocket:[`wss://testnet-rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plume.org`,apiUrl:`https://testnet-explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:199712}},testnet:!0,sourceId:11155111}),tO=L({id:161221135,name:`Plume Testnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plumenetwork.xyz/http`],webSocket:[`wss://testnet-rpc.plumenetwork.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plumenetwork.xyz`,apiUrl:`https://testnet-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6022332}},testnet:!0,sourceId:11155111}),nO=L({id:631571,name:`Polter Testnet`,nativeCurrency:{decimals:18,name:`Polter GHST`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-polter.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://polter-testnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11245}},testnet:!0}),rO=L({id:137,name:`Polygon`,blockTime:2e3,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://polygon-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://polygonscan.com`,apiUrl:`https://api.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}}}),iO=L({id:80002,name:`Polygon Amoy`,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://rpc-amoy.polygon.technology`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://amoy.polygonscan.com`,apiUrl:`https://api-amoy.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3127388}},testnet:!0}),aO=L({id:80001,name:`Polygon Mumbai`,nativeCurrency:{name:`MATIC`,symbol:`MATIC`,decimals:18},rpcUrls:{default:{http:[`https://80001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://mumbai.polygonscan.com`,apiUrl:`https://api-testnet.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}},testnet:!0}),oO=L({id:1101,name:`Polygon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://zkevm.polygonscan.com`,apiUrl:`https://api-zkevm.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:57746}}}),sO=L({id:2442,name:`Polygon zkEVM Cardona`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cardona.zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://cardona-zkevm.polygonscan.com`,apiUrl:`https://cardona-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:114091}}}),cO=L({id:1442,name:`Polygon zkEVM Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.public.zkevm-test.net`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://testnet-zkevm.polygonscan.com`,apiUrl:`https://testnet-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:525686}}}),lO=L({id:8008,name:`Polynomial`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://polynomialscan.io`}},testnet:!1,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),uO=L({id:80008,name:`Polynomia Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sepolia.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://sepolia.polynomialscan.io`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),dO=L({id:23023,name:`PremiumBlock Testnet`,nativeCurrency:{name:`Premium Block`,symbol:`PBLK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.premiumblock.org`]}},blockExplorers:{default:{name:`PremiumBlocks Explorer`,url:`https://scan.premiumblock.org`}},testnet:!0}),fO=L({id:369,name:`PulseChain`,nativeCurrency:{name:`Pulse`,symbol:`PLS`,decimals:18},testnet:!1,blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.pulsechain.com`],webSocket:[`wss://ws.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://ipfs.scan.pulsechain.com`,apiUrl:`https://api.scan.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),pO=L({id:943,name:`PulseChain V4`,testnet:!0,nativeCurrency:{name:`V4 Pulse`,symbol:`v4PLS`,decimals:18},blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.v4.testnet.pulsechain.com`],webSocket:[`wss://ws.v4.testnet.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://scan.v4.testnet.pulsechain.com`,apiUrl:`https://scan.v4.testnet.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),mO=L({id:490092,name:`Pumpfi Testnet`,nativeCurrency:{decimals:18,name:`PMPT`,symbol:`PMPT`},rpcUrls:{default:{http:[`https://rpc1testnet.pumpfi.me`]}},blockExplorers:{default:{name:`Pumpfi Testnet Scan`,url:`https://testnetscan.pumpfi.me`}},testnet:!0});var hO=11155111;const gO=L({...R,name:`Pyrope Testnet`,testnet:!0,id:695569,sourceId:hO,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.pyropechain.com`],webSocket:[`wss://rpc.pyropechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://pyrope.blockscout.com`}},contracts:{...R.contracts,l1StandardBridge:{[hO]:{address:`0xC24932c31D9621aE9e792576152B7ef010cFC2F8`}}}}),_O=L({id:766,name:`QL1`,nativeCurrency:{decimals:18,name:`QOM`,symbol:`QOM`},rpcUrls:{default:{http:[`https://rpc.qom.one`]}},blockExplorers:{default:{name:`Ql1 Explorer`,url:`https://scan.qom.one`}},contracts:{multicall3:{address:`0x7A52370716ea730585884F5BDB0f6E60C39b8C64`}},testnet:!1}),vO=L({id:35441,name:`Q Mainnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.q.org`]}},blockExplorers:{default:{name:`Q Mainnet Explorer`,url:`https://explorer.q.org`,apiUrl:`https://explorer.q.org/api`}}}),yO=L({id:35443,name:`Q Testnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.qtestnet.org`]}},blockExplorers:{default:{name:`Q Testnet Explorer`,url:`https://explorer.qtestnet.org`,apiUrl:`https://explorer.qtestnet.org/api`}},testnet:!0}),bO=L({id:111188,name:`re.al`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.realforreal.gelato.digital`]}},blockExplorers:{default:{name:`re.al Explorer`,url:`https://explorer.re.al`,apiUrl:`https://explorer.re.al/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:695}}}),xO=L({id:151,name:`Redbelly Network Mainnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.mainnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/151/etherscan/api`}},testnet:!1}),SO=L({id:153,name:`Redbelly Network Testnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.testnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.testnet.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/153_2/etherscan/api`}},testnet:!0}),CO=L({id:50342,name:`Reddio`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.reddio.com/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://reddio.cloud.blockscout.com`,apiUrl:`https://reddio.cloud.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:848849}},testnet:!1}),wO=L({id:50341,name:`Reddio Sepolia`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://reddio-dev.reddio.com`]}},blockExplorers:{default:{name:`Reddioscan`,url:`https://reddio-devnet.l2scan.co`,apiUrl:`https://reddio-devnet.l2scan.co/api`}},testnet:!0});var TO=1;const EO=L({...R,name:`Redstone`,id:690,sourceId:TO,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.redstonechain.com`],webSocket:[`wss://rpc.redstonechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.redstone.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[TO]:{address:`0xC7bCb0e8839a28A1cFadd1CF716de9016CdA51ae`,blockCreated:19578329}},l2OutputOracle:{[TO]:{address:`0xa426A052f657AEEefc298b3B5c35a470e4739d69`,blockCreated:19578337}},l1StandardBridge:{[TO]:{address:`0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69`,blockCreated:19578331}}}}),DO=L({id:47805,name:`REI Mainnet`,nativeCurrency:{decimals:18,name:`REI`,symbol:`REI`},rpcUrls:{default:{http:[`https://rpc.rei.network`],webSocket:[`wss://rpc.rei.network`]}},blockExplorers:{default:{name:`REI Scan`,url:`https://scan.rei.network`}},testnet:!1}),OO=L({id:1729,name:`Reya Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.reya.network`],webSocket:[`wss://ws.reya.network`]}},blockExplorers:{default:{name:`Reya Network Explorer`,url:`https://explorer.reya.network`}},testnet:!1}),kO=L({id:11155931,name:`RISE Testnet`,nativeCurrency:{name:`RISE Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.riselabs.xyz`],webSocket:[`wss://testnet.riselabs.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.testnet.riselabs.xyz/`,apiUrl:`https://explorer.testnet.riselabs.xyz/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},testnet:!0}),AO=L({id:753,name:`Rivalz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rivalz.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Rivalz Caldera Explorer`,url:`https://rivalz.calderaexplorer.xyz`}},testnet:!1}),jO=L({id:570,name:`Rollux Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.rollux.com`],webSocket:[`wss://rpc.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxExplorer`,url:`https://explorer.rollux.com`,apiUrl:`https://explorer.rollux.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:119222}}}),MO=L({id:57e3,name:`Rollux Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc-tanenbaum.rollux.com/`],webSocket:[`wss://rpc-tanenbaum.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxTestnetExplorer`,url:`https://rollux.tanenbaum.io`,apiUrl:`https://rollux.tanenbaum.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1813675}}}),NO=L({id:2020,name:`Ronin`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://api.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Ronin Explorer`,url:`https://app.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:26023535}}}),PO=L({id:7668,name:`The Root Network`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://root.rootnet.live/archive`],webSocket:[`wss://root.rootnet.live/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:9218338}}}),FO=L({id:7672,name:`The Root Network - Porcini`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://porcini.rootnet.app/archive`],webSocket:[`wss://porcini.rootnet.app/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://porcini.rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:10555692}},testnet:!0}),IO=L({id:30,name:`Rootstock Mainnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`RBTC`},rpcUrls:{default:{http:[`https://public-node.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.rsk.co`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4249540}}}),LO=L({id:31,name:`Rootstock Testnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`tRBTC`},rpcUrls:{default:{http:[`https://public-node.testnet.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.testnet.rootstock.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2771150}},testnet:!0});var RO=1;const zO=L({...R,id:12553,name:`RSS3 VSL Mainnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Mainnet Scan`,url:`https://scan.rss3.io`,apiUrl:`https://scan.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[RO]:{address:`0xE6f24d2C32B3109B18ed33cF08eFb490b1e09C10`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14193},portal:{[RO]:{address:`0x6A12432491bbbE8d3babf75F759766774C778Db4`,blockCreated:19387057}},l1StandardBridge:{[RO]:{address:`0x4cbab69108Aa72151EDa5A3c164eA86845f18438`}}},sourceId:RO});var BO=11155111;const VO=L({...R,id:2331,name:`RSS3 VSL Sepolia Testnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Sepolia Testnet Scan`,url:`https://scan.testnet.rss3.io`,apiUrl:`https://scan.testnet.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[BO]:{address:`0xDb5c46C3Eaa6Ed6aE8b2379785DF7dd029C0dC81`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:55697},portal:{[BO]:{address:`0xcBD77E8E1E7F06B25baDe67142cdE82652Da7b57`,blockCreated:5345035}},l1StandardBridge:{[BO]:{address:`0xdDD29bb63B0839FB1cE0eE439Ff027738595D07B`}}},testnet:!0,sourceId:BO}),HO=L({id:7225878,name:`Saakuru Mainnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.saakuru.network`]}},blockExplorers:{default:{name:`Saakuru Explorer`,url:`https://explorer.saakuru.network`}},testnet:!1}),UO=L({id:5464,name:`Saga`,network:`saga`,nativeCurrency:{decimals:18,name:`gas`,symbol:`GAS`},rpcUrls:{default:{http:[`https://sagaevm.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Saga Explorer`,url:`https://sagaevm.sagaexplorer.io`}},contracts:{multicall3:{address:`0x864DDc9B50B9A0dF676d826c9B9EDe9F8913a160`,blockCreated:467530}}}),WO=L({id:2021,name:`Saigon Testnet`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://saigon-testnet.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Saigon Explorer`,url:`https://saigon-app.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18736871}},testnet:!0}),GO=L({id:1996,name:`Sanko`,nativeCurrency:{name:`DMT`,symbol:`DMT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.sanko.xyz`]}},blockExplorers:{default:{name:`Sanko Explorer`,url:`https://explorer.sanko.xyz`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:37}},testnet:!1}),KO=L({id:23294,name:`Oasis Sapphire`,network:`sapphire`,nativeCurrency:{name:`Sapphire Rose`,symbol:`ROSE`,decimals:18},rpcUrls:{default:{http:[`https://sapphire.oasis.io`],webSocket:[`wss://sapphire.oasis.io/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/mainnet/sapphire`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:734531}}}),qO=L({id:23295,name:`Oasis Sapphire Testnet`,network:`sapphire-testnet`,nativeCurrency:{name:`Sapphire Test Rose`,symbol:`TEST`,decimals:18},rpcUrls:{default:{http:[`https://testnet.sapphire.oasis.dev`],webSocket:[`wss://testnet.sapphire.oasis.dev/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/testnet/sapphire`}},testnet:!0}),JO=L({id:3109,name:`SatoshiVM Alpha Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://alpha-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://svmscan.io`,apiUrl:`https://svmscan.io/api`}}}),YO=L({id:3110,name:`SatoshiVM Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet.svmscan.io`,apiUrl:`https://testnet.svmscan.io/api`}},testnet:!0}),XO=L({id:534352,name:`Scroll`,blockTime:3e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.scroll.io`],webSocket:[`wss://wss-rpc.scroll.io/ws`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://scrollscan.com`,apiUrl:`https://api.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14}},testnet:!1}),ZO=L({id:534351,name:`Scroll Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.scroll.io`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://sepolia.scrollscan.com`,apiUrl:`https://api-sepolia.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9473}},testnet:!0}),QO=L({id:1329,name:`Sei Network`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc.sei-apis.com/`],webSocket:[`wss://evm-ws.sei-apis.com/`]}},blockExplorers:{default:{name:`Seitrace`,url:`https://seitrace.com`,apiUrl:`https://seitrace.com/pacific-1/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}}}),$O=L({id:713715,name:`Sei Devnet`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc-arctic-1.sei-apis.com`]}},blockExplorers:{default:{name:`Seitrace`,url:`https://seitrace.com`}},testnet:!0}),ek=L({id:5124,name:`Seismic Devnet`,nativeCurrency:{name:`Seismic Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://node-2.seismicdev.net/rpc`]}},blockExplorers:{default:{name:`Seismic Devnet Explorer`,url:`https://explorer-2.seismicdev.net`}},testnet:!0}),tk=L({id:1328,name:`Sei Testnet`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc-testnet.sei-apis.com`],webSocket:[`wss://evm-ws-testnet.sei-apis.com`]}},blockExplorers:{default:{name:`Seitrace`,url:`https://seitrace.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:98697651}},testnet:!0}),nk=L({id:11155111,name:`Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.drpc.org`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.etherscan.io`,apiUrl:`https://api-sepolia.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:751532},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:8928790}},testnet:!0});var rk=1;const ik=L({...R,id:360,name:`Shape`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.shape.network`]}},blockExplorers:{default:{name:`shapescan`,url:`https://shapescan.xyz`,apiUrl:`https://shapescan.xyz/api`}},contracts:{...R.contracts,l2OutputOracle:{[rk]:{address:`0x6Ef8c69CfE4635d866e3E02732068022c06e724D`,blockCreated:20369940}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1},portal:{[rk]:{address:`0xEB06fFa16011B5628BaB98E29776361c83741dd3`,blockCreated:20369933}},l1StandardBridge:{[rk]:{address:`0x62Edd5f4930Ea92dCa3fB81689bDD9b9d076b57B`,blockCreated:20369935}}},sourceId:rk});var ak=11155111;const ok=L({...R,id:11011,name:`Shape Sepolia Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.shape.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer-sepolia.shape.network/`,apiUrl:`https://explorer-sepolia.shape.network/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0,sourceId:ak}),sk=L({id:8118,name:`Shardeum`,nativeCurrency:{name:`Shardeum`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://api.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer.shardeum.org`}},testnet:!1}),ck=L({id:8082,name:`Shardeum Sphinx`,nativeCurrency:{name:`SHARDEUM`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://sphinx.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer-sphinx.shardeum.org`}},testnet:!0}),lk=L({id:109,name:`Shibarium`,network:`shibarium`,nativeCurrency:{name:`Bone`,symbol:`BONE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://shibariumscan.io`}},contracts:{multicall3:{address:`0x864Bf681ADD6052395188A89101A1B37d3B4C961`,blockCreated:265900}}}),uk=L({id:157,name:`Puppynet Shibarium`,nativeCurrency:{decimals:18,name:`Bone`,symbol:`BONE`},rpcUrls:{default:{http:[`https://puppynet.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://puppyscan.shib.io`,apiUrl:`https://puppyscan.shib.io/api`}},contracts:{multicall3:{address:`0xA4029b74FBA366c926eDFA7Dd10B21C621170a4c`,blockCreated:3035769}},testnet:!0}),dk=L({id:336,name:`Shiden`,nativeCurrency:{decimals:18,name:`SDN`,symbol:`SDN`},rpcUrls:{default:{http:[`https://shiden.public.blastapi.io`],webSocket:[`wss://shiden-rpc.dwellir.com`]}},blockExplorers:{default:{name:`Shiden Scan`,url:`https://shiden.subscan.io`}},testnet:!1}),fk=L({id:148,name:`Shimmer`,network:`shimmer`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.shimmer.network`,apiUrl:`https://explorer.evm.shimmer.network/api`}}}),pk=L({id:1073,name:`Shimmer Testnet`,network:`shimmer-testnet`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.testnet.shimmer.network`,apiUrl:`https://explorer.evm.testnet.shimmer.network/api`}},testnet:!0}),mk=L({id:97453,name:`Sidra Chain`,nativeCurrency:{decimals:18,name:`Sidra Digital Asset`,symbol:`SDA`},rpcUrls:{default:{http:[`https://node.sidrachain.com`]}},blockExplorers:{default:{name:`Sidra Chain Explorer`,url:`https://ledger.sidrachain.com`}}}),hk=L({id:2355,name:`Silicon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.silicon.network`,`https://silicon-mainnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconScope`,url:`https://scope.silicon.network`}}}),gk=L({id:1722641160,name:`Silicon Sepolia zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-sepolia.silicon.network`,`https://silicon-testnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconSepoliaScope`,url:`https://scope-sepolia.silicon.network`}},testnet:!0}),_k=L({id:98,name:`Six Protocol`,nativeCurrency:{decimals:18,name:`SIX`,symbol:`SIX`},rpcUrls:{default:{http:[`https://sixnet-rpc-evm.sixprotocol.net`]}},blockExplorers:{default:{name:`Six Protocol Scan`,url:`https://sixscan.io/sixnet`}},testnet:!1}),vk=L({id:391845894,name:`SKALE | Block Brawlers`,nativeCurrency:{name:`BRAWL`,symbol:`BRAWL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/frayed-decent-antares`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/frayed-decent-antares`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://frayed-decent-antares.explorer.mainnet.skalenodes.com`}},contracts:{}}),yk=L({id:1564830818,name:`SKALE Calypso Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/honorable-steel-rasalhague`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/honorable-steel-rasalhague`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3107626}}}),bk=L({id:974399131,name:`SKALE Calypso Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/giant-half-dual-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/giant-half-dual-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://giant-half-dual-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:103220}},testnet:!0}),xk=L({id:1026062157,name:`SKALE | CryptoBlades`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/affectionate-immediate-pollux`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/affectionate-immediate-pollux`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://affectionate-immediate-pollux.explorer.mainnet.skalenodes.com`}},contracts:{}}),Sk=L({id:1032942172,name:`SKALE | Crypto Colosseum`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/haunting-devoted-deneb`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/haunting-devoted-deneb`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://haunting-devoted-deneb.explorer.mainnet.skalenodes.com`}},contracts:{}}),Ck=L({id:2046399126,name:`SKALE Europa Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/elated-tan-skat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/elated-tan-skat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://elated-tan-skat.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3113495}}}),wk=L({id:1444673419,name:`SKALE Europa Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/juicy-low-small-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/juicy-low-small-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://juicy-low-small-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:110858}},testnet:!0}),Tk=L({id:2139927552,name:`Exorde Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/light-vast-diphda`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/light-vast-diphda`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://light-vast-diphda.explorer.mainnet.skalenodes.com`}},contracts:{}}),Ek=L({id:1273227453,name:`SKALE | Human Protocol`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/wan-red-ain`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/wan-red-ain`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://wan-red-ain.explorer.mainnet.skalenodes.com`}},contracts:{}}),Dk=L({id:1482601649,name:`SKALE Nebula Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/green-giddy-denebola`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/green-giddy-denebola`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://green-giddy-denebola.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2372986}}}),Ok=L({id:37084624,name:`SKALE Nebula Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/lanky-ill-funny-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/lanky-ill-funny-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:105141}},testnet:!0}),kk=L({id:278611351,name:`SKALE | Razor Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/turbulent-unique-scheat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/turbulent-unique-scheat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://turbulent-unique-scheat.explorer.mainnet.skalenodes.com`}},contracts:{}}),Ak=L({id:1350216234,name:`SKALE Titan Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/parallel-stormy-spica`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/parallel-stormy-spica`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://parallel-stormy-spica.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2076458}}}),jk=L({id:1020352220,name:`SKALE Titan Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/aware-fake-trim-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/aware-fake-trim-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://aware-fake-trim-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:104072}},testnet:!0}),Mk=L({id:984123,name:`Forma Sketchpad`,network:`sketchpad`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sketchpad-1.forma.art`],webSocket:[`wss://ws.sketchpad-1.forma.art`]}},blockExplorers:{default:{name:`Sketchpad Explorer`,url:`https://explorer.sketchpad-1.forma.art`}},testnet:!0});var Nk=1;const Pk=L({...R,id:2192,network:`snaxchain-mainnet`,name:`SnaxChain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://explorer.snaxchain.io`,apiUrl:`https://explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[Nk]:{address:`0x472562Fcf26D6b2793f8E0b0fB660ba0E5e08A46`}},l2OutputOracle:{[Nk]:{address:`0x2172e492Fc807F5d5645D0E3543f139ECF539294`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[Nk]:{address:`0x79f446D024d74D0Bb6E699C131c703463c5D65E9`}},l1StandardBridge:{[Nk]:{address:`0x6534Bdb6b5c060d3e6aa833433333135eFE8E0aA`}}},sourceId:Nk});var Fk=11155111;const Ik=L({...R,id:13001,network:`snaxchain-testnet`,name:`SnaxChain Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://testnet-explorer.snaxchain.io`,apiUrl:`https://testnet-explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[Fk]:{address:`0x206a75d89d45F146C54020F132FF93bEDD09f55E`}},l2OutputOracle:{[Fk]:{address:`0x60e3A368a4cdCEf85ffB964e372726F56A46221e`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[Fk]:{address:`0xb5afdd0E8dDF081Ef90e8A3e0c7b5798e66E954E`}},l1StandardBridge:{[Fk]:{address:`0xbd37E1a59D4C00C9A46F75018dffd84061bC5f74`}}},testnet:!0,sourceId:Fk}),Lk=L({id:50312,name:`Somnia Testnet`,nativeCurrency:{name:`STT`,symbol:`STT`,decimals:18},rpcUrls:{default:{http:[`https://dream-rpc.somnia.network`]}},blockExplorers:{default:{name:`Somnia Testnet Explorer`,url:`https://shannon-explorer.somnia.network/`,apiUrl:`https://shannon-explorer.somnia.network/api`}},contracts:{multicall3:{address:`0x841b8199E6d3Db3C6f264f6C2bd8848b3cA64223`,blockCreated:71314235}},testnet:!0});var Rk=1;const zk=L({...R,id:1868,name:`Soneium Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium.blockscout.com`,apiUrl:`https://soneium.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[Rk]:{address:`0x512a3d2c7a43bd9261d2b8e8c9c70d4bd4d503c0`}},l2OutputOracle:{[Rk]:{address:`0x0000000000000000000000000000000000000000`}},portal:{[Rk]:{address:`0x88e529a6ccd302c948689cd5156c83d4614fae92`,blockCreated:7061266}},l1StandardBridge:{[Rk]:{address:`0xeb9bf100225c214efc3e7c651ebbadcf85177607`,blockCreated:7061266}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},sourceId:Rk});var Bk=11155111;const Vk=L({...R,id:1946,name:`Soneium Minato Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.minato.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium-minato.blockscout.com`,apiUrl:`https://soneium-minato.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[Bk]:{address:`0xB3Ad2c38E6e0640d7ce6aA952AB3A60E81bf7a01`}},l2OutputOracle:{[Bk]:{address:`0x710e5286C746eC38beeB7538d0146f60D27be343`}},portal:{[Bk]:{address:`0x65ea1489741A5D72fFdD8e6485B216bBdcC15Af3`,blockCreated:6466136}},l1StandardBridge:{[Bk]:{address:`0x5f5a404A5edabcDD80DB05E8e54A78c9EBF000C2`,blockCreated:6466136}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},testnet:!0,sourceId:Bk}),Hk=L({id:19,name:`Songbird Canary-Network`,nativeCurrency:{decimals:18,name:`Songbird`,symbol:`SGB`},rpcUrls:{default:{http:[`https://songbird-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Songbird Explorer`,url:`https://songbird-explorer.flare.network`,apiUrl:`https://songbird-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13382504}}}),Uk=L({id:16,name:`Songbird Testnet Coston`,nativeCurrency:{decimals:18,name:`Coston Flare`,symbol:`CFLR`},rpcUrls:{default:{http:[`https://coston-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston Explorer`,url:`https://coston-explorer.flare.network`,apiUrl:`https://coston-explorer.flare.network/api`}},testnet:!0}),Wk=L({id:146,name:`Sonic`,blockTime:630,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Explorer`,url:`https://sonicscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:60}},testnet:!1}),Gk=L({id:57054,name:`Sonic Blaze Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.blaze.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Blaze Testnet Explorer`,url:`https://testnet.sonicscan.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1100}},testnet:!0}),Kk=L({id:64165,name:`Sonic Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.testnet.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Testnet Explorer`,url:`https://testnet.soniclabs.com/`}},testnet:!0}),qk=L({...oy,id:50104,name:`Sophon`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.sophon.xyz`],webSocket:[`wss://rpc.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.sophon.xyz`}},contracts:{multicall3:{address:`0x5f4867441d2416cA88B1b3fd38f21811680CD2C8`,blockCreated:116}},testnet:!1}),Jk=L({...oy,id:531050104,name:`Sophon Testnet`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.testnet.sophon.xyz`],webSocket:[`wss://rpc.testnet.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.testnet.sophon.xyz`}},contracts:{multicall3:{address:`0x83c04d112adedA2C6D9037bb6ecb42E7f0b108Af`,blockCreated:15642}},testnet:!0}),Yk=L({id:100021,name:`Sova`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sova.io`]}},blockExplorers:{default:{name:`Sova Block Explorer`,url:`hhttps://explorer.sova.io`}},testnet:!1}),Xk=L({id:120893,name:`Sova Network Sepolia`,nativeCurrency:{decimals:18,name:`Sepolia Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.testnet.sova.io`]}},blockExplorers:{default:{name:`Sova Sepolia Explorer`,url:`https://explorer.testnet.sova.io`}},testnet:!0}),Zk=L({id:88882,name:`Chiliz Spicy Testnet`,network:`chiliz-spicy-Testnet`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://spicy-rpc.chiliz.com`,`https://chiliz-spicy-rpc.publicnode.com`],webSocket:[`wss://spicy-rpc-ws.chiliz.com`,`wss://chiliz-spicy-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`http://spicy-explorer.chiliz.com`,apiUrl:`http://spicy-explorer.chiliz.com/api`}},testnet:!0}),Qk=L({...RT,id:1660990954,name:`Status Network Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://public.sepolia.rpc.status.network`],webSocket:[`wss://public.sepolia.rpc.status.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepoliascan.status.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1578364}},testnet:!0}),$k=L({id:1234,name:`Step Network`,nativeCurrency:{name:`FITFI`,symbol:`FITFI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.step.network`]}},blockExplorers:{default:{name:`Step Scan`,url:`https://stepscan.io`}},testnet:!1}),eA=L({id:1514,name:`Story`,nativeCurrency:{decimals:18,name:`IP Token`,symbol:`IP`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:340998},ensRegistry:{address:`0x5dc881dda4e4a8d312be3544ad13118d1a04cb17`,blockCreated:648924},ensUniversalResolver:{address:`0xddfb18888a9466688235887dec2a10c4f5effee9`,blockCreated:649114}},rpcUrls:{default:{http:[`https://mainnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story explorer`,url:`https://storyscan.io`,apiUrl:`https://storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!1}),tA=L({id:1315,name:`Story Aeneid`,network:`story-aeneid`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1792},ensRegistry:{address:`0x5dC881dDA4e4a8d312be3544AD13118D1a04Cb17`,blockCreated:1322033},ensUniversalResolver:{address:`0x6D3B3F99177FB2A5de7F9E928a9BD807bF7b5BAD`,blockCreated:1322097}},rpcUrls:{default:{http:[`https://aeneid.storyrpc.io`]}},blockExplorers:{default:{name:`Story Aeneid Explorer`,url:`https://aeneid.storyscan.io`,apiUrl:`https://aeneid.storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!0}),nA=L({id:1516,name:`Story Odyssey`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://rpc.odyssey.storyrpc.io`]}},blockExplorers:{default:{name:`Story Odyssey Explorer`,url:`https://odyssey.storyscan.xyz`}},testnet:!0}),rA=L({id:1513,name:`Story Testnet`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://testnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story Testnet Explorer`,url:`https://testnet.storyscan.xyz`}},testnet:!0}),iA=L({id:105105,name:`Stratis Mainnet`,network:`stratis`,nativeCurrency:{name:`Stratis`,symbol:`STRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Stratis Explorer`,url:`https://explorer.stratisevm.com`}}}),aA=L({id:8866,name:`SuperLumio`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.lumio.io`]}},blockExplorers:{default:{name:`Lumio explorer`,url:`https://explorer.lumio.io`}},testnet:!1}),oA=L({id:55244,name:`Superposition`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.superposition.so`]}},blockExplorers:{default:{name:`Superposition Explorer`,url:`https://explorer.superposition.so`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39}},testnet:!1});var sA=1;const cA=L({...R,id:5330,name:`Superseed`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Explorer`,url:`https://explorer.superseed.xyz`,apiUrl:`https://explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,disputeGameFactory:{[sA]:{address:`0x8b097CF1f9BbD9cbFD0DD561858a1FCbC8857Be0`,blockCreated:20737481}},l2OutputOracle:{[sA]:{address:`0x693A0F8854F458D282DE3C5b69E8eE5EEE8aA949`,blockCreated:20737481}},portal:{[sA]:{address:`0x2c2150aa5c75A24fB93d4fD2F2a895D618054f07`,blockCreated:20737481}},l1StandardBridge:{[sA]:{address:`0x8b0576E39F1233679109F9b40cFcC2a7E0901Ede`,blockCreated:20737481}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:sA});var lA=11155111;const uA=L({...R,id:53302,name:`Superseed Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Sepolia Explorer`,url:`https://sepolia-explorer.superseed.xyz`,apiUrl:`https://sepolia-explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},portal:{[lA]:{address:`0x7A0db8C51432d2C3eb4e8f360a2EeB26FF2809fB`,blockCreated:5523438}},l1StandardBridge:{[lA]:{address:`0x2B227A603fAAdB3De0ED050b63ADD232B5f2c28C`,blockCreated:5523442}}},testnet:!0,sourceId:lA}),dA=L({id:763375,name:`Surge Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-rpc.hoodi.surge.wtf`],webSocket:[`wss://l2-ws.hoodi.surge.wtf`]}},blockExplorers:{default:{name:`Surge Testnet Blockscout`,url:`https://explorer.hoodi.surge.wtf`}},testnet:!0}),fA=L({id:254,name:`Swan Chain Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.swanchain.org`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://swanscan.io`}},testnet:!1}),pA=L({id:20241133,name:`Swan Proxima Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-proxima.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://proxima-explorer.swanchain.io`}},testnet:!0}),mA=L({id:2024,name:`Swan Saturn Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://saturn-rpc.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://saturn-explorer.swanchain.io`}},testnet:!0}),hA=L({...R,id:1923,name:`Swellchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-mainnet.alt.technology`]}},blockExplorers:{default:{name:`Swell Explorer`,url:`https://explorer.swellnetwork.io`,apiUrl:`https://explorer.swellnetwork.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),gA=L({...R,id:1924,name:`Swellchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-testnet.alt.technology`]}},blockExplorers:{default:{name:`Swellchain Testnet Explorer`,url:`https://swell-testnet-explorer.alt.technology`,apiUrl:`https://swell-testnet-explorer.alt.technology/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),_A=L({id:94,name:`SwissDLT Mainnet`,nativeCurrency:{decimals:18,name:`BCTS`,symbol:`BCTS`},rpcUrls:{default:{http:[`https://rpc.swissdlt.ch`]}},blockExplorers:{default:{name:`SwissDLT Explorer`,url:`https://explorer.swissdlt.ch`}},testnet:!1}),vA=L({id:57,name:`Syscoin Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.syscoin.org`],webSocket:[`wss://rpc.syscoin.org/wss`]}},blockExplorers:{default:{name:`SyscoinExplorer`,url:`https://explorer.syscoin.org`,apiUrl:`https://explorer.syscoin.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:287139}}}),yA=L({id:5700,name:`Syscoin Tanenbaum Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.tanenbaum.io`],webSocket:[`wss://rpc.tanenbaum.io/wss`]}},blockExplorers:{default:{name:`SyscoinTestnetExplorer`,url:`https://tanenbaum.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:271288}}}),bA=L({id:239,name:`TAC`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ankr.com/tac`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://tac.blockscout.com`,apiUrl:`https://tac.blockscout.com/api`},native:{name:`TAC Explorer`,url:`https://explorer.tac.build`,apiUrl:`https://explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),xA=L({id:2391,name:`TAC SPB Testnet`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://spb.rpc.tac.build`]}},blockExplorers:{default:{name:`TAC`,url:`https://spb.explorer.tac.build`,apiUrl:`https://spb.explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:471429}},testnet:!0}),SA=L({id:167e3,name:`Taiko Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.mainnet.taiko.xyz`],webSocket:[`wss://ws.mainnet.taiko.xyz`]}},blockExplorers:{default:{name:`Taikoscan`,url:`https://taikoscan.io`,apiUrl:`https://api.taikoscan.io/api`}},contracts:{multicall3:{address:`0xcb2436774C3e191c85056d248EF4260ce5f27A9D`}}}),CA=L({id:167009,name:`Taiko Hekla L2`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hekla.taiko.xyz`]}},blockExplorers:{default:{name:`Taikoscan`,url:`https://hekla.taikoscan.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59757}},testnet:!0}),wA=L({id:167007,name:`Taiko Jolnir (Alpha-5 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.jolnir.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.jolnir.taiko.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:732706}},testnet:!0}),TA=L({id:167008,name:`Taiko Katla (Alpha-6 Testnet)`,network:`tko-katla`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katla.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.katla.taiko.xyz`}}}),EA=L({id:167005,name:`Taiko (Alpha-3 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.test.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.test.taiko.xyz`}}}),DA=L({id:841,name:`Taraxa Mainnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.mainnet.taraxa.io`}}}),OA=L({id:842,name:`Taraxa Testnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.testnet.taraxa.io`}},testnet:!0}),kA=L({id:10218,name:`Tea Sepolia`,nativeCurrency:{name:`Sepolia Tea`,symbol:`TEA`,decimals:18},rpcUrls:{default:{http:[`https://tea-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Tea Sepolia Explorer`,url:`https://sepolia.tea.xyz`}},testnet:!0}),AA=L({id:2017,name:`Telcoin Adiri Testnet`,nativeCurrency:{name:`Telcoin`,symbol:`TEL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.telcoin.network`]}},blockExplorers:{default:{name:`telscan`,url:`https://telscan.io`}},testnet:!0}),jA=L({id:40,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.telos.net`]}},blockExplorers:{default:{name:`Teloscan`,url:`https://www.teloscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:246530709}}}),MA=L({id:41,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.testnet.telos.net`]}},blockExplorers:{default:{name:`Teloscan (testnet)`,url:`https://testnet.teloscan.io/`}},testnet:!0}),NA=L({id:1559,name:`Tenet`,network:`tenet-mainnet`,nativeCurrency:{name:`TENET`,symbol:`TENET`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tenet.org`]}},blockExplorers:{default:{name:`TenetScan Mainnet`,url:`https://tenetscan.io`,apiUrl:`https://tenetscan.io/api`}},testnet:!1}),PA=L({id:752025,name:`Ternoa`,nativeCurrency:{name:`Capsule Coin`,symbol:`CAPS`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.zkevm.ternoa.network`]}},blockExplorers:{default:{name:`Ternoa Explorer`,url:`https://explorer-mainnet.zkevm.ternoa.network`}},testnet:!1}),FA=L({id:7,name:`ThaiChain`,nativeCurrency:{name:`TCH`,symbol:`TCH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.thaichain.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.thaichain.org`,apiUrl:`https://exp.thaichain.org/api`}},contracts:{multicall3:{address:`0x0DaD6130e832c21719C5CE3bae93454E16A84826`,blockCreated:4806386}},testnet:!1}),IA=L({id:8428,name:`THAT Mainnet`,nativeCurrency:{name:`THAT`,symbol:`THAT`,decimals:18},rpcUrls:{default:{http:[`https://api.thatchain.io/mainnet`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://that.blockscout.com`}},testnet:!1}),LA=L({id:361,name:`Theta Mainnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://explorer.thetatoken.org`}},testnet:!1}),RA=L({id:365,name:`Theta Testnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api-testnet.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://testnet-explorer.thetatoken.org`}},testnet:!0}),zA=L({id:108,name:`ThunderCore Mainnet`,nativeCurrency:{name:`TT`,symbol:`TT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.thundercore.com`]}},blockExplorers:{default:{name:`ThunderCore Explorer`,url:`https://explorer-mainnet.thundercore.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),BA=L({id:997,name:`5ireChain Thunder Testnet`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Thunder Explorer`,url:`https://testnet.5irescan.io/`}},testnet:!0}),VA=L({id:62092,name:`TikTrix Testnet`,nativeCurrency:{name:`tTTX`,symbol:`tTTX`,decimals:18},rpcUrls:{default:{http:[`https://tiktrix-rpc.xyz`]}},blockExplorers:{default:{name:`TikTrix Testnet Explorer`,url:`https://tiktrix.xyz`}},testnet:!0}),HA=L({id:6969,name:`Tomb Mainnet`,nativeCurrency:{name:`TOMB`,symbol:`TOMB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tombchain.com`]}},blockExplorers:{default:{name:`Tomb Explorer`,url:`https://tombscout.com`}},testnet:!1}),UA=L({...oy,id:61166,name:`Treasure`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.treasure.lol`],webSocket:[`wss://rpc.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Block Explorer`,url:`https://treasurescan.io`}},contracts:{multicall3:{address:`0x2e29fe39496a56856D8698bD43e1dF4D0CE6266a`,blockCreated:101}},testnet:!1}),WA=L({...oy,id:978658,name:`Treasure Topaz Testnet`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.topaz.treasure.lol`],webSocket:[`wss://rpc.topaz.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Topaz Block Explorer`,url:`https://topaz.treasurescan.io`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:108112}},testnet:!0}),GA=L({id:728126428,name:`Tron`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://tronscan.org`,apiUrl:`https://apilist.tronscanapi.com/api`}}}),KA=L({id:3448148188,name:`Tron Nile`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://nile.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://nile.tronscan.org`}},testnet:!0}),qA=L({id:2494104990,name:`Tron Shasta`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.shasta.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://shasta.tronscan.org`}},testnet:!0}),JA=L({id:8,name:`Ubiq Mainnet`,nativeCurrency:{name:`UBQ`,symbol:`UBQ`,decimals:18},rpcUrls:{default:{http:[`https://pyrus2.ubiqscan.io`]}},blockExplorers:{default:{name:`Ubiq Scan`,url:`https://ubiqscan.io`}},testnet:!1}),YA=L({id:19991,name:`Ultra EVM`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Explorer`,url:`https://evmexplorer.ultra.io`}}}),XA=L({id:18881,name:`Ultra EVM Testnet`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.test.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Testnet Explorer`,url:`https://evmexplorer.testnet.ultra.io`}},testnet:!0}),ZA=L({id:1231,name:`Ultron Mainnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-rpc.net`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://ulxscan.com`}},testnet:!1}),QA=L({id:1230,name:`Ultron Testnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-dev.io`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://explorer.ultron-dev.io`}},testnet:!0});var $A=1;const ej=L({...R,id:130,name:`Unichain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://mainnet.unichain.org/`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://uniscan.xyz`,apiUrl:`https://api.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[$A]:{address:`0x2F12d621a16e2d3285929C9996f478508951dFe4`}},portal:{[$A]:{address:`0x0bd48f6B86a26D3a217d0Fa6FfE2B491B956A7a2`}},l1StandardBridge:{[$A]:{address:`0x81014F44b0a345033bB2b3B21C7a1A308B35fEeA`}}},sourceId:$A});var tj=11155111;const nj=L({...R,id:1301,name:`Unichain Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia.unichain.org`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://sepolia.uniscan.xyz`,apiUrl:`https://api-sepolia.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},portal:{[tj]:{address:`0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD`}},l1StandardBridge:{[tj]:{address:`0xea58fcA6849d79EAd1f26608855c2D6407d54Ce2`}},disputeGameFactory:{[tj]:{address:`0xeff73e5aa3B9AEC32c659Aa3E00444d20a84394b`}}},testnet:!0,sourceId:tj}),rj=L({id:8880,name:`Unique Mainnet`,nativeCurrency:{decimals:18,name:`UNQ`,symbol:`UNQ`},rpcUrls:{default:{http:[`https://rpc.unique.network`]}},blockExplorers:{default:{name:`Unique Subscan`,url:`https://unique.subscan.io/`}}}),ij=L({id:8882,name:`Opal Testnet`,nativeCurrency:{decimals:18,name:`OPL`,symbol:`OPL`},rpcUrls:{default:{http:[`https://rpc-opal.unique.network`]}},blockExplorers:{default:{name:`Opal Subscan`,url:`https://opal.subscan.io/`}},testnet:!0}),aj=L({id:8881,name:`Quartz Mainnet`,nativeCurrency:{decimals:18,name:`QTZ`,symbol:`QTZ`},rpcUrls:{default:{http:[`https://rpc-quartz.unique.network`]}},blockExplorers:{default:{name:`Quartz Subscan`,url:`https://quartz.subscan.io/`}}}),oj=L({id:18233,name:`Unreal`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.unreal-orbit.gelato.digital`]}},blockExplorers:{default:{name:`Unreal Explorer`,url:`https://unreal.blockscout.com`,apiUrl:`https://unreal.blockscout.com/api/v2`}},testnet:!0,contracts:{multicall3:{address:`0x8b6B0e60D8CD84898Ea8b981065A12F876eA5677`,blockCreated:1745}}}),sj=L({id:1480,name:`Vana`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.vana.org/`]}},blockExplorers:{default:{name:`Vana Block Explorer`,url:`https://vanascan.io`,apiUrl:`https://vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:716763}}}),cj=L({id:14800,name:`Vana Moksha Testnet`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.moksha.vana.org`]}},blockExplorers:{default:{name:`Vana Moksha Testnet`,url:`https://moksha.vanascan.io`,apiUrl:`https://moksha.vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:732283}},testnet:!0}),lj=L({id:2040,name:`Vanar Mainnet`,nativeCurrency:{name:`VANRY`,symbol:`VANRY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.vanarchain.com`]}},blockExplorers:{default:{name:`Vanar Mainnet Explorer`,url:`https://explorer.vanarchain.com/`}},testnet:!1}),uj=L({id:100009,name:`Vechain`,nativeCurrency:{name:`VeChain`,symbol:`VET`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.vechain.org`]}},blockExplorers:{default:{name:`Vechain Explorer`,url:`https://explore.vechain.org`},vechainStats:{name:`Vechain Stats`,url:`https://vechainstats.com`}}}),dj=L({id:106,name:`Velas EVM Mainnet`,nativeCurrency:{name:`VLX`,symbol:`VLX`,decimals:18},rpcUrls:{default:{http:[`https://evmexplorer.velas.com/rpc`]}},blockExplorers:{default:{name:`Velas Explorer`,url:`https://evmexplorer.velas.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:55883577}},testnet:!1}),fj=L({id:88,name:`Viction`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://vicscan.xyz`}},testnet:!1}),pj=L({id:89,name:`Viction Testnet`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://testnet.vicscan.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:12170179}},testnet:!0}),mj=L({id:888888,name:`Vision`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org`}},testnet:!1}),hj=L({id:666666,name:`Vision Testnet`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://vpioneer.infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org/?chain=vpioneer`}},testnet:!0}),gj=L({id:888,name:`Wanchain`,nativeCurrency:{name:`WANCHAIN`,symbol:`WAN`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:56891`,`https://gwan2-ssl.wandevs.org`]}},blockExplorers:{default:{name:`WanScan`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0xcDF6A1566e78EB4594c86Fe73Fcdc82429e97fbB`,blockCreated:25312390}}}),_j=L({id:999,name:`Wanchain Testnet`,nativeCurrency:{name:`WANCHAIN`,symbol:`WANt`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:46891`]}},blockExplorers:{default:{name:`WanScanTest`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0x11c89bF4496c39FB80535Ffb4c92715839CC5324`,blockCreated:24743448}},testnet:!0}),vj=L({id:9496,name:`WeaveVM Alphanet`,nativeCurrency:{name:`Testnet WeaveVM`,symbol:`tWVM`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.wvm.dev`]}},blockExplorers:{default:{name:`WeaveVM Alphanet Explorer`,url:`https://explorer.wvm.dev`}},testnet:!0}),yj=L({id:1111,name:`WEMIX`,network:`wemix-mainnet`,nativeCurrency:{name:`WEMIX`,symbol:`WEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://explorer.wemix.com`}}}),bj=L({id:1112,name:`WEMIX Testnet`,network:`wemix-testnet`,nativeCurrency:{name:`WEMIX`,symbol:`tWEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.test.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://testnet.wemixscan.com`,apiUrl:`https://testnet.wemixscan.com/api`}},testnet:!0}),xj=L({id:420420421,name:`Westend Asset Hub`,nativeCurrency:{decimals:18,name:`Westies`,symbol:`WND`},rpcUrls:{default:{http:[`https://westend-asset-hub-eth-rpc.polkadot.io`]}},blockExplorers:{default:{name:`subscan`,url:`https://westend-asset-hub-eth-explorer.parity.io`}},testnet:!0}),Sj=L({testnet:!1,name:`Whitechain`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://explorer.whitechain.io`}},id:1875,rpcUrls:{default:{http:[`https://rpc.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25212237}}}),Cj=L({testnet:!0,name:`Whitechain Testnet`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://testnet.whitechain.io`}},id:2625,rpcUrls:{default:{http:[`https://rpc-testnet.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`}}),wj=L({id:42070,name:`WMC Testnet`,nativeCurrency:{name:`WMTx`,symbol:`WMTx`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet-base.worldmobile.net`]}},blockExplorers:{default:{name:`WMC Explorer`,url:`https://explorer2-base-testnet.worldmobile.net`}},testnet:!0});var Tj=1;const Ej=L({...R,id:480,name:`World Chain`,network:`worldchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan`,url:`https://worldscan.org`,apiUrl:`https://api.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-mainnet.explorer.alchemy.com`,apiUrl:`https://worldchain-mainnet.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[Tj]:{address:`0x069c4c579671f8c120b1327a73217D01Ea2EC5ea`}},l2OutputOracle:{[Tj]:{address:`0x19A6d1E9034596196295CF148509796978343c5D`}},portal:{[Tj]:{address:`0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C`}},l1StandardBridge:{[Tj]:{address:`0x470458C91978D2d929704489Ad730DC3E3001113`}}},testnet:!1,sourceId:Tj});var Dj=11155111;const Oj=L({...R,id:4801,name:`World Chain Sepolia`,network:`worldchain-sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan Sepolia`,url:`https://sepolia.worldscan.org`,apiUrl:`https://api-sepolia.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-sepolia.explorer.alchemy.com`,apiUrl:`https://worldchain-sepolia.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[Dj]:{address:`0x8Ec1111f67Dad6b6A93B3F42DfBC92D81c98449A`}},l2OutputOracle:{[Dj]:{address:`0xc8886f8BAb6Eaeb215aDB5f1c686BF699248300e`}},portal:{[Dj]:{address:`0xFf6EBa109271fe6d4237EeeD4bAb1dD9A77dD1A4`}},l1StandardBridge:{[Dj]:{address:`0xd7DF54b3989855eb66497301a4aAEc33Dbb3F8DE`}}},testnet:!0,sourceId:Dj}),kj=L({id:103,name:`WorldLand Mainnet`,nativeCurrency:{decimals:18,name:`WLC`,symbol:`WLC`},rpcUrls:{default:{http:[`https://seoul.worldland.foundation`]}},blockExplorers:{default:{name:`WorldLand Scan`,url:`https://scan.worldland.foundation`}},testnet:!1}),Aj=L({id:660279,name:`Xai Mainnet`,nativeCurrency:{name:`Xai`,symbol:`XAI`,decimals:18},rpcUrls:{default:{http:[`https://xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.xai-chain.net`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:222549}},testnet:!1}),jj=L({id:37714555429,name:`Xai Testnet`,nativeCurrency:{name:`sXai`,symbol:`sXAI`,decimals:18},rpcUrls:{default:{http:[`https://testnet-v2.xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer-v2.xai-chain.net`}},testnet:!0}),Mj=L({id:50,name:`XDC Network`,nativeCurrency:{decimals:18,name:`XDC`,symbol:`XDC`},rpcUrls:{default:{http:[`https://rpc.xdcrpc.com`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://xdcscan.com`}},contracts:{multicall3:{address:`0x0B1795ccA8E4eC4df02346a082df54D437F8D9aF`,blockCreated:75884020}}}),Nj=L({id:51,name:`Apothem Network`,nativeCurrency:{decimals:18,name:`TXDC`,symbol:`TXDC`},rpcUrls:{default:{http:[`https://erpc.apothem.network`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://testnet.xdcscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59765389}}}),Pj=L({id:196,name:`X Layer Mainnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://rpc.xlayer.tech`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer`,apiUrl:`https://www.oklink.com/api/v5/explorer/xlayer/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:47416}}}),Fj=L({id:195,name:`X1 Testnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://xlayertestrpc.okx.com`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer-test`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:624344}},testnet:!0}),Ij=L({id:20250217,name:`Xphere Mainnet`,nativeCurrency:{decimals:18,name:`XP`,symbol:`XP`},rpcUrls:{default:{http:[`https://en-bkk.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xp.tamsa.io`}},testnet:!1}),Lj=L({id:1998991,name:`Xphere Testnet`,nativeCurrency:{decimals:18,name:`XPT`,symbol:`XPT`},rpcUrls:{default:{http:[`http://testnet.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xpt.tamsa.io`}},testnet:!0}),Rj=L({id:273,name:`XR One`,nativeCurrency:{decimals:18,name:`XR1`,symbol:`XR1`},rpcUrls:{default:{http:[`https://xr1.calderachain.xyz/http`],webSocket:[`wss://xr1.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr1.calderaexplorer.xyz`}},testnet:!1}),zj=L({id:1440002,name:`XRPL EVM Devnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xrplevm.org/`]},public:{http:[`https://rpc.xrplevm.org/`]}},blockExplorers:{default:{name:`XRPLEVM Devnet Explorer`,url:`https://explorer.xrplevm.org/`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:15237286}},testnet:!0}),Bj=L({id:1449e3,name:`XRPL EVM Testnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.xrplevm.org`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.testnet.xrplevm.org`,apiUrl:`https://explorer.testnet.xrplevm.org/api/v2`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:492302}},testnet:!0}),Vj=L({id:2730,name:`XR Sepolia`,nativeCurrency:{decimals:18,name:`tXR`,symbol:`tXR`},rpcUrls:{default:{http:[`https://xr-sepolia-testnet.rpc.caldera.xyz/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr-sepolia-testnet.explorer.caldera.xyz`}},testnet:!0}),Hj=L({id:50005,name:`Yooldo Verse`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Explorer`,url:`https://explorer.yooldo-verse.xyz`}}}),Uj=L({id:50006,name:`Yooldo Verse Testnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Testnet Explorer`,url:`https://explorer.testnet.yooldo-verse.xyz`}},testnet:!0}),Wj=L({id:8408,name:`ZenChain Testnet`,nativeCurrency:{decimals:18,name:`ZTC`,symbol:`ZTC`},rpcUrls:{default:{http:[`https://zenchain-testnet.api.onfinality.io/public`],webSocket:[`wss://zenchain-testnet.api.onfinality.io/public-ws`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:230019}},blockExplorers:{default:{name:`Zentrace`,url:`https://zentrace.io`}},testnet:!0}),Gj=L({id:383414847825,name:`Zeniq Mainnet`,nativeCurrency:{name:`ZENIQ`,symbol:`ZENIQ`,decimals:18},rpcUrls:{default:{http:[`https://api.zeniq.network`]}},blockExplorers:{default:{name:`Zeniq Explorer`,url:`https://zeniqscan.com`}},testnet:!1}),Kj=L({id:543210,name:`Zero Network`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zerion.io/v1/zero`]}},blockExplorers:{default:{name:`Zero Network Explorer`,url:`https://explorer.zero.network`}},testnet:!1}),qj=L({id:7e3,name:`ZetaChain`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`ZETA`},rpcUrls:{default:{http:[`https://zetachain-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1632781}},blockExplorers:{default:{name:`ZetaScan`,url:`https://zetascan.com`}},testnet:!1}),Jj=L({id:7001,name:`ZetaChain Athens Testnet`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`aZETA`},rpcUrls:{default:{http:[`https://zetachain-athens-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2715217}},blockExplorers:{default:{name:`ZetaScan`,url:`https://testnet.zetascan.com`}},testnet:!0}),Yj=L({id:1337803,name:`Zhejiang`,nativeCurrency:{name:`Zhejiang Ether`,symbol:`ZhejETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zhejiang.ethpandaops.io`]}},blockExplorers:{default:{name:`Beaconchain`,url:`https://zhejiang.beaconcha.in`}},testnet:!0}),Xj=L({id:32769,name:`Zilliqa`,network:`zilliqa`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.zilliqa.com`}},testnet:!1}),Zj=L({id:33101,name:`Zilliqa Testnet`,network:`zilliqa-testnet`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://dev-api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.testnet.zilliqa.com`}},testnet:!0});var Qj=1;const $j=L({...R,id:48900,name:`Zircuit Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.zircuit.com`,`https://zircuit1-mainnet.liquify.com`,`https://zircuit1-mainnet.p2pify.com`,`https://zircuit-mainnet.drpc.org`]}},blockExplorers:{default:{name:`Zircuit Explorer`,url:`https://explorer.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[Qj]:{address:`0x92Ef6Af472b39F1b363da45E35530c24619245A4`}},portal:{[Qj]:{address:`0x17bfAfA932d2e23Bd9B909Fd5B4D2e2a27043fb1`}},l1StandardBridge:{[Qj]:{address:`0x386B76D9cA5F5Fb150B6BFB35CF5379B22B26dd8`}}},testnet:!1});var eM=11155111;const tM=L({...R,id:48898,name:`Zircuit Garfield Testnet`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://garfield-testnet.zircuit.com/`]}},blockExplorers:{default:{name:`Zircuit Garfield Testnet Explorer`,url:`https://explorer.garfield-testnet.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[eM]:{address:`0xd69D3AC5CA686cCF94b258291772bc520FEAf211`}},portal:{[eM]:{address:`0x4E21A71Ac3F7607Da5c06153A17B1DD20E702c21`}},l1StandardBridge:{[eM]:{address:`0x87a7E2bCA9E35BA49282E832a28A6023904460D8`}}},testnet:!0});var nM=11155111;const rM=L({...R,id:48899,name:`Zircuit Testnet`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.zircuit.com`,`https://zircuit1-testnet.p2pify.com`,`https://zircuit1-testnet.liquify.com`]}},blockExplorers:{default:{name:`Zircuit Testnet Explorer`,url:`https://explorer.testnet.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6040287},l2OutputOracle:{[nM]:{address:`0x740C2dac453aEf7140809F80b72bf0e647af8148`}},portal:{[nM]:{address:`0x787f1C8c5924178689E0560a43D848bF8E54b23e`}},l1StandardBridge:{[nM]:{address:`0x0545c5fe980098C16fcD0eCB5E79753afa6d9af9`}}},testnet:!0}),iM=L({id:42766,name:`ZKFair Mainnet`,network:`zkfair-mainnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://scan.zkfair.io`,apiUrl:`https://scan.zkfair.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6090959}},testnet:!1}),aM=L({id:43851,name:`ZKFair Testnet`,network:`zkfair-testnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://testnet-rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://testnet-scan.zkfair.io`}},testnet:!0}),oM=L({id:810180,name:`zkLink Nova`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://explorer.zklink.io`}}}),sM=L({id:810181,name:`zkLink Nova Sepolia Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://sepolia.explorer.zklink.io`}}}),cM=L({...oy,id:324,name:`ZKsync Era`,network:`zksync-era`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.era.zksync.io`],webSocket:[`wss://mainnet.era.zksync.io/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://era.zksync.network/`,apiUrl:`https://api-era.zksync.network/api`},native:{name:`ZKsync Explorer`,url:`https://explorer.zksync.io/`,apiUrl:`https://block-explorer-api.mainnet.zksync.io/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:45659388}}}),lM=L({...oy,id:260,name:`ZKsync InMemory Node`,network:`zksync-in-memory-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:8011`]}},testnet:!0}),uM=L({...oy,id:272,name:`ZKsync CLI Local Custom Hyperchain`,nativeCurrency:{name:`BAT`,symbol:`BAT`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15200`],webSocket:[`ws://localhost:15201`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),dM=L({...oy,id:270,name:`ZKsync CLI Local Hyperchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15100`],webSocket:[`ws://localhost:15101`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),fM=L({id:9,name:`ZKsync CLI Local Hyperchain L1`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15045`]}},blockExplorers:{default:{name:`Blockscout`,url:`http://localhost:15001/`,apiUrl:`http://localhost:15001/api/v2`}},testnet:!0}),pM=L({...oy,id:270,name:`ZKsync CLI Local Node`,network:`zksync-cli-local-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:3050`]}},testnet:!0}),mM=L({...oy,id:300,name:`ZKsync Sepolia Testnet`,network:`zksync-sepolia-testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.era.zksync.dev`],webSocket:[`wss://sepolia.era.zksync.dev/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia-era.zksync.network/`,apiUrl:`https://api-sepolia-era.zksync.network/api`},native:{name:`ZKsync Explorer`,url:`https://sepolia.explorer.zksync.io/`,blockExplorerApi:`https://block-explorer-api.sepolia.zksync.dev/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:3855712}},testnet:!0});var hM=1;const gM=L({...R,id:7777777,name:`Zora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zora.energy`],webSocket:[`wss://rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.zora.energy`,apiUrl:`https://explorer.zora.energy/api`}},contracts:{...R.contracts,l2OutputOracle:{[hM]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:5882},portal:{[hM]:{address:`0x1a0ad011913A150f69f6A19DF447A0CfD9551054`}},l1StandardBridge:{[hM]:{address:`0x3e2Ea9B92B7E48A52296fD261dc26fd995284631`}}},sourceId:hM});var _M=11155111;const vM=L({...R,id:999999999,name:`Zora Sepolia`,network:`zora-sepolia`,nativeCurrency:{decimals:18,name:`Zora Sepolia`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zora.energy`],webSocket:[`wss://sepolia.rpc.zora.energy`]}},blockExplorers:{default:{name:`Zora Sepolia Explorer`,url:`https://sepolia.explorer.zora.energy/`,apiUrl:`https://sepolia.explorer.zora.energy/api`}},contracts:{...R.contracts,l2OutputOracle:{[_M]:{address:`0x2615B481Bd3E5A1C0C7Ca3Da1bdc663E8615Ade9`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83160},portal:{[_M]:{address:`0xeffE2C6cA9Ab797D418f0D91eA60807713f3536f`}},l1StandardBridge:{[_M]:{address:`0x5376f1D543dcbB5BD416c56C189e4cB7399fCcCB`}}},sourceId:_M,testnet:!0});var yM=5;const bM=L({...R,id:999,name:`Zora Goerli Testnet`,nativeCurrency:{decimals:18,name:`Zora Goerli`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.rpc.zora.energy`],webSocket:[`wss://testnet.rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.explorer.zora.energy`,apiUrl:`https://testnet.explorer.zora.energy/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:189123},portal:{[yM]:{address:`0xDb9F51790365e7dc196e7D072728df39Be958ACe`}}},sourceId:yM,testnet:!0});var xM=c({abey:()=>Wv,abstract:()=>sy,abstractTestnet:()=>cy,acala:()=>ly,acria:()=>uy,adf:()=>dy,agungTestnet:()=>fy,aioz:()=>py,alephZero:()=>my,alephZeroTestnet:()=>hy,alienx:()=>gy,alienxHalTestnet:()=>_y,ancient8:()=>Ey,ancient8Sepolia:()=>Oy,anvil:()=>ky,apeChain:()=>Ay,apexTestnet:()=>jy,arbitrum:()=>My,arbitrumGoerli:()=>Ny,arbitrumNova:()=>Py,arbitrumSepolia:()=>Fy,arenaz:()=>Iy,areonNetwork:()=>Ly,areonNetworkTestnet:()=>Ry,areum:()=>zy,artelaTestnet:()=>By,arthera:()=>Vy,artheraTestnet:()=>Hy,assetChain:()=>Uy,assetChainTestnet:()=>Wy,astar:()=>Gy,astarZkEVM:()=>Ky,astarZkyoto:()=>qy,atletaOlympia:()=>Jy,aurora:()=>Yy,auroraTestnet:()=>Xy,auroria:()=>Zy,autheoTestnet:()=>Qy,avalanche:()=>$y,avalancheFuji:()=>eb,b3:()=>tb,b3Sepolia:()=>nb,bahamut:()=>rb,base:()=>ab,baseGoerli:()=>lb,basePreconf:()=>ob,baseSepolia:()=>db,baseSepoliaPreconf:()=>fb,basecampTestnet:()=>sb,beam:()=>pb,beamTestnet:()=>mb,bearNetworkChainMainnet:()=>hb,bearNetworkChainTestnet:()=>gb,berachain:()=>_b,berachainBepolia:()=>vb,berachainTestnet:()=>yb,berachainTestnetbArtio:()=>bb,bevmMainnet:()=>xb,bifrost:()=>Sb,birdlayer:()=>Cb,bitTorrent:()=>Ab,bitTorrentTestnet:()=>jb,bitgert:()=>wb,bitkub:()=>Tb,bitkubTestnet:()=>Eb,bitlayer:()=>Db,bitlayerTestnet:()=>Ob,bitrock:()=>kb,blast:()=>Nb,blastSepolia:()=>Pb,bob:()=>Ib,bobSepolia:()=>Bb,boba:()=>Lb,bobaSepolia:()=>Rb,boolBetaMainnet:()=>Vb,botanix:()=>Hb,botanixTestnet:()=>Ub,bounceBit:()=>Wb,bounceBitTestnet:()=>Gb,bronos:()=>Kb,bronosTestnet:()=>qb,bsc:()=>Jb,bscGreenfield:()=>Yb,bscTestnet:()=>Xb,bsquared:()=>Zb,bsquaredTestnet:()=>Qb,btr:()=>$b,btrTestnet:()=>ex,bxn:()=>tx,bxnTestnet:()=>nx,cannon:()=>rx,canto:()=>ix,celo:()=>yx,celoAlfajores:()=>xx,celoSepolia:()=>Cx,chang:()=>wx,chiliz:()=>Tx,chips:()=>Ex,citreaTestnet:()=>Dx,classic:()=>Ox,coinbit:()=>kx,coinex:()=>Ax,confluxESpace:()=>jx,confluxESpaceTestnet:()=>Mx,coreDao:()=>Nx,coreTestnet1:()=>Px,coreTestnet2:()=>Fx,corn:()=>Ix,cornTestnet:()=>Lx,crab:()=>Rx,creatorTestnet:()=>zx,creditCoin3Devnet:()=>Bx,creditCoin3Mainnet:()=>Vx,creditCoin3Testnet:()=>Hx,cronos:()=>Ux,cronosTestnet:()=>Wx,cronoszkEVM:()=>Gx,cronoszkEVMTestnet:()=>Kx,crossbell:()=>qx,crossfi:()=>Jx,curtis:()=>Yx,cyber:()=>Xx,cyberTestnet:()=>Zx,dailyNetwork:()=>Qx,dailyNetworkTestnet:()=>$x,darwinia:()=>eS,dbkchain:()=>tS,dchain:()=>nS,dchainTestnet:()=>rS,defichainEvm:()=>iS,defichainEvmTestnet:()=>aS,degen:()=>oS,dfk:()=>sS,diode:()=>cS,disChain:()=>lS,dodochainTestnet:()=>uS,dogechain:()=>dS,domaTestnet:()=>fS,donatuz:()=>pS,dosChain:()=>mS,dosChainTestnet:()=>hS,dreyerxMainnet:()=>gS,dreyerxTestnet:()=>_S,dustboyIoT:()=>vS,dymension:()=>yS,edexa:()=>bS,edexaTestnet:()=>xS,edgeless:()=>SS,edgelessTestnet:()=>CS,edgeware:()=>wS,edgewareTestnet:()=>TS,eduChain:()=>ES,eduChainTestnet:()=>DS,ekta:()=>OS,ektaTestnet:()=>kS,elastos:()=>AS,elastosTestnet:()=>jS,electroneum:()=>MS,electroneumTestnet:()=>NS,elysiumTestnet:()=>PS,energy:()=>FS,eni:()=>IS,eniTestnet:()=>LS,enuls:()=>RS,eon:()=>zS,eos:()=>BS,eosTestnet:()=>VS,eteria:()=>HS,etherlink:()=>US,etherlinkTestnet:()=>WS,ethernity:()=>GS,etp:()=>KS,evmos:()=>qS,evmosTestnet:()=>JS,excelonMainnet:()=>YS,expanse:()=>XS,exsat:()=>ZS,exsatTestnet:()=>QS,fantom:()=>$S,fantomSonicTestnet:()=>eC,fantomTestnet:()=>tC,fibo:()=>nC,filecoin:()=>rC,filecoinCalibration:()=>iC,filecoinHyperspace:()=>aC,fireChain:()=>Uv,flame:()=>oC,flare:()=>sC,flareTestnet:()=>cC,flowMainnet:()=>lC,flowPreviewnet:()=>uC,flowTestnet:()=>dC,fluence:()=>fC,fluenceStage:()=>pC,fluenceTestnet:()=>mC,fluentTestnet:()=>hC,form:()=>_C,formTestnet:()=>bC,forma:()=>vC,formicarium:()=>CE,forta:()=>xC,foundry:()=>SC,fraxtal:()=>wC,fraxtalTestnet:()=>EC,funkiMainnet:()=>OC,funkiSepolia:()=>AC,fuse:()=>jC,fuseSparknet:()=>MC,fusion:()=>NC,fusionTestnet:()=>PC,garnet:()=>IC,geist:()=>LC,genesys:()=>RC,giwaSepolia:()=>BC,glideL1Protocol:()=>VC,glideL2Protocol:()=>HC,gnosis:()=>UC,gnosisChiado:()=>WC,goChain:()=>qC,goat:()=>GC,gobi:()=>KC,godwoken:()=>JC,goerli:()=>YC,graphite:()=>XC,graphiteTestnet:()=>ZC,gravity:()=>QC,gunz:()=>$C,guruNetwork:()=>ew,guruTestnet:()=>tw,ham:()=>nw,happychainTestnet:()=>rw,haqqMainnet:()=>iw,haqqTestedge2:()=>aw,hardhat:()=>ow,harmonyOne:()=>sw,hashkey:()=>cw,hashkeyTestnet:()=>lw,haustTestnet:()=>uw,hedera:()=>dw,hederaPreviewnet:()=>fw,hederaTestnet:()=>pw,hela:()=>mw,hemi:()=>hw,hemiSepolia:()=>gw,holesky:()=>_w,hoodi:()=>vw,hpb:()=>yw,huddle01Mainnet:()=>bw,huddle01Testnet:()=>xw,humanity:()=>Sw,humanityTestnet:()=>Cw,humanode:()=>ww,humanodeTestnet5:()=>Tw,hychain:()=>Ew,hychainTestnet:()=>Dw,hyperliquidEvmTestnet:()=>Ow,iSunCoin:()=>Kw,icbNetwork:()=>kw,idchain:()=>Aw,immutableZkEvm:()=>jw,immutableZkEvmTestnet:()=>Mw,inEVM:()=>Nw,initVerse:()=>Pw,initVerseGenesis:()=>Fw,injective:()=>Iw,injectiveTestnet:()=>Lw,ink:()=>zw,inkSepolia:()=>Vw,iota:()=>Hw,iotaTestnet:()=>Uw,iotex:()=>Ww,iotexTestnet:()=>Gw,jbc:()=>qw,jbcTestnet:()=>Jw,jocMainnet:()=>Yw,jocTestnet:()=>Xw,jovay:()=>Zw,jovaySepolia:()=>Qw,juneo:()=>$w,juneoBCH1Chain:()=>eT,juneoDAI1Chain:()=>tT,juneoDOGE1Chain:()=>nT,juneoEUR1Chain:()=>rT,juneoGLD1Chain:()=>iT,juneoLINK1Chain:()=>aT,juneoLTC1Chain:()=>oT,juneoSGD1Chain:()=>cT,juneoSocotraTestnet:()=>lT,juneoUSD1Chain:()=>uT,juneoUSDT1Chain:()=>dT,juneomBTC1Chain:()=>sT,kaia:()=>fT,kairos:()=>pT,kakarotSepolia:()=>mT,kakarotStarknetSepolia:()=>hT,kardiaChain:()=>gT,karura:()=>_T,katana:()=>vT,kava:()=>yT,kavaTestnet:()=>bT,kcc:()=>xT,kiiTestnetOro:()=>ST,kinto:()=>CT,klaytn:()=>wT,klaytnBaobab:()=>TT,koi:()=>ET,kroma:()=>DT,kromaSepolia:()=>OT,l3x:()=>kT,l3xTestnet:()=>AT,lavita:()=>jT,lens:()=>MT,lensTestnet:()=>NT,lestnet:()=>PT,lightlinkPegasus:()=>FT,lightlinkPhoenix:()=>IT,linea:()=>BT,lineaGoerli:()=>VT,lineaSepolia:()=>HT,lineaTestnet:()=>UT,lisk:()=>GT,liskSepolia:()=>qT,loadAlphanet:()=>JT,localhost:()=>YT,loop:()=>XT,lukso:()=>ZT,luksoTestnet:()=>QT,lumiaMainnet:()=>$T,lumiaTestnet:()=>eE,lumoz:()=>tE,lumozTestnet:()=>nE,lycan:()=>rE,lyra:()=>iE,mainnet:()=>aE,mandala:()=>oE,manta:()=>sE,mantaSepoliaTestnet:()=>cE,mantaTestnet:()=>lE,mantle:()=>uE,mantleSepoliaTestnet:()=>dE,mantleTestnet:()=>fE,mantraDuKongEVMTestnet:()=>pE,mantraEVM:()=>mE,mapProtocol:()=>hE,matchain:()=>gE,matchainTestnet:()=>_E,mchVerse:()=>vE,megaethTestnet:()=>yE,mekong:()=>bE,meld:()=>xE,memecore:()=>SE,merlin:()=>wE,merlinErigonTestnet:()=>TE,metachain:()=>EE,metachainIstanbul:()=>DE,metadium:()=>OE,metalL2:()=>AE,meter:()=>jE,meterTestnet:()=>ME,metis:()=>NE,metisGoerli:()=>PE,metisSepolia:()=>FE,mev:()=>IE,mevTestnet:()=>LE,mint:()=>RE,mintSepoliaTestnet:()=>zE,mitosisTestnet:()=>BE,mode:()=>HE,modeTestnet:()=>WE,monadTestnet:()=>GE,moonbaseAlpha:()=>KE,moonbeam:()=>qE,moonbeamDev:()=>JE,moonriver:()=>YE,morph:()=>XE,morphHolesky:()=>ZE,morphSepolia:()=>QE,nahmii:()=>$E,nautilus:()=>eD,near:()=>tD,nearTestnet:()=>nD,neonDevnet:()=>rD,neonMainnet:()=>iD,neoxMainnet:()=>aD,neoxT4:()=>oD,newton:()=>sD,nexi:()=>cD,nexilix:()=>lD,nibiru:()=>uD,nitrographTestnet:()=>dD,oasisTestnet:()=>fD,oasys:()=>pD,odysseyTestnet:()=>mD,okc:()=>hD,omax:()=>gD,omni:()=>_D,omniOmega:()=>vD,oneWorld:()=>yD,oortMainnetDev:()=>bD,opBNB:()=>SD,opBNBTestnet:()=>wD,openledger:()=>TD,optimism:()=>DD,optimismGoerli:()=>kD,optimismSepolia:()=>jD,optopia:()=>MD,optopiaTestnet:()=>ND,orderly:()=>PD,orderlySepolia:()=>FD,otimDevnet:()=>ID,palm:()=>LD,palmTestnet:()=>RD,peaq:()=>zD,pgn:()=>VD,pgnTestnet:()=>UD,phoenix:()=>WD,planq:()=>GD,plasma:()=>KD,plasmaDevnet:()=>qD,plasmaTestnet:()=>JD,playfiAlbireo:()=>YD,plinga:()=>XD,plume:()=>ZD,plumeDevnet:()=>QD,plumeMainnet:()=>$D,plumeSepolia:()=>eO,plumeTestnet:()=>tO,polterTestnet:()=>nO,polygon:()=>rO,polygonAmoy:()=>iO,polygonMumbai:()=>aO,polygonZkEvm:()=>oO,polygonZkEvmCardona:()=>sO,polygonZkEvmTestnet:()=>cO,polynomial:()=>lO,polynomialSepolia:()=>uO,premiumBlockTestnet:()=>dO,pulsechain:()=>fO,pulsechainV4:()=>pO,pumpfiTestnet:()=>mO,pyrope:()=>gO,qMainnet:()=>vO,qTestnet:()=>yO,ql1:()=>_O,real:()=>bO,redbellyMainnet:()=>xO,redbellyTestnet:()=>SO,reddio:()=>CO,reddioSepolia:()=>wO,redstone:()=>EO,rei:()=>DO,reyaNetwork:()=>OO,riseTestnet:()=>kO,rivalz:()=>AO,rollux:()=>jO,rolluxTestnet:()=>MO,ronin:()=>NO,root:()=>PO,rootPorcini:()=>FO,rootstock:()=>IO,rootstockTestnet:()=>LO,rss3:()=>zO,rss3Sepolia:()=>VO,saakuru:()=>HO,saga:()=>UO,saigon:()=>WO,sanko:()=>GO,sapphire:()=>KO,sapphireTestnet:()=>qO,satoshiVM:()=>JO,satoshiVMTestnet:()=>YO,scroll:()=>XO,scrollSepolia:()=>ZO,sei:()=>QO,seiDevnet:()=>$O,seiTestnet:()=>tk,seismicDevnet:()=>ek,sepolia:()=>nk,shape:()=>ik,shapeSepolia:()=>ok,shardeum:()=>sk,shardeumSphinx:()=>ck,shibarium:()=>lk,shibariumTestnet:()=>uk,shiden:()=>dk,shimmer:()=>fk,shimmerTestnet:()=>pk,sidraChain:()=>mk,silicon:()=>hk,siliconSepolia:()=>gk,sixProtocol:()=>_k,skaleBlockBrawlers:()=>vk,skaleCalypso:()=>yk,skaleCalypsoTestnet:()=>bk,skaleCryptoBlades:()=>xk,skaleCryptoColosseum:()=>Sk,skaleEuropa:()=>Ck,skaleEuropaTestnet:()=>wk,skaleExorde:()=>Tk,skaleHumanProtocol:()=>Ek,skaleNebula:()=>Dk,skaleNebulaTestnet:()=>Ok,skaleRazor:()=>kk,skaleTitan:()=>Ak,skaleTitanTestnet:()=>jk,sketchpad:()=>Mk,snax:()=>Pk,snaxTestnet:()=>Ik,somniaTestnet:()=>Lk,soneium:()=>zk,soneiumMinato:()=>Vk,songbird:()=>Hk,songbirdTestnet:()=>Uk,sonic:()=>Wk,sonicBlazeTestnet:()=>Gk,sonicTestnet:()=>Kk,sophon:()=>qk,sophonTestnet:()=>Jk,sova:()=>Yk,sovaSepolia:()=>Xk,spicy:()=>Zk,statusNetworkSepolia:()=>Qk,statusSepolia:()=>Qk,step:()=>$k,story:()=>eA,storyAeneid:()=>tA,storyOdyssey:()=>nA,storyTestnet:()=>rA,stratis:()=>iA,superlumio:()=>aA,superposition:()=>oA,superseed:()=>cA,superseedSepolia:()=>uA,surgeTestnet:()=>dA,swan:()=>fA,swanProximaTestnet:()=>pA,swanSaturnTestnet:()=>mA,swellchain:()=>hA,swellchainTestnet:()=>gA,swissdlt:()=>_A,syscoin:()=>vA,syscoinTestnet:()=>yA,tac:()=>bA,tacSPB:()=>xA,taiko:()=>SA,taikoHekla:()=>CA,taikoJolnir:()=>wA,taikoKatla:()=>TA,taikoTestnetSepolia:()=>EA,taraxa:()=>DA,taraxaTestnet:()=>OA,teaSepolia:()=>kA,telcoinTestnet:()=>AA,telos:()=>jA,telosTestnet:()=>MA,tenet:()=>NA,ternoa:()=>PA,thaiChain:()=>FA,that:()=>IA,theta:()=>LA,thetaTestnet:()=>RA,thunderCore:()=>zA,thunderTestnet:()=>BA,tiktrixTestnet:()=>VA,tomb:()=>HA,treasure:()=>UA,treasureTopaz:()=>WA,tron:()=>GA,tronNile:()=>KA,tronShasta:()=>qA,ubiq:()=>JA,ultra:()=>YA,ultraTestnet:()=>XA,ultron:()=>ZA,ultronTestnet:()=>QA,unichain:()=>ej,unichainSepolia:()=>nj,unique:()=>rj,uniqueOpal:()=>ij,uniqueQuartz:()=>aj,unreal:()=>oj,vana:()=>sj,vanaMoksha:()=>cj,vanar:()=>lj,vechain:()=>uj,velas:()=>dj,viction:()=>fj,victionTestnet:()=>pj,vision:()=>mj,visionTestnet:()=>hj,wanchain:()=>gj,wanchainTestnet:()=>_j,weaveVMAlphanet:()=>vj,wemix:()=>yj,wemixTestnet:()=>bj,westendAssetHub:()=>xj,whitechain:()=>Sj,whitechainTestnet:()=>Cj,wmcTestnet:()=>wj,worldLand:()=>kj,worldchain:()=>Ej,worldchainSepolia:()=>Oj,x1Testnet:()=>Fj,xLayer:()=>Pj,xLayerTestnet:()=>Fj,xai:()=>Aj,xaiTestnet:()=>jj,xdc:()=>Mj,xdcTestnet:()=>Nj,xphereMainnet:()=>Ij,xphereTestnet:()=>Lj,xrOne:()=>Rj,xrSepolia:()=>Vj,xrplevmDevnet:()=>zj,xrplevmTestnet:()=>Bj,yooldoVerse:()=>Hj,yooldoVerseTestnet:()=>Uj,zenchainTestnet:()=>Wj,zeniq:()=>Gj,zeroG:()=>Bv,zeroGGalileoTestnet:()=>Vv,zeroGMainnet:()=>Hv,zeroNetwork:()=>Kj,zetachain:()=>qj,zetachainAthensTestnet:()=>Jj,zhejiang:()=>Yj,zilliqa:()=>Xj,zilliqaTestnet:()=>Zj,zircuit:()=>$j,zircuitGarfieldTestnet:()=>tM,zircuitTestnet:()=>rM,zkFair:()=>iM,zkFairTestnet:()=>aM,zkLinkNova:()=>oM,zkLinkNovaSepoliaTestnet:()=>sM,zkSync:()=>cM,zkSyncInMemoryNode:()=>lM,zkSyncLocalNode:()=>pM,zkSyncSepoliaTestnet:()=>mM,zksync:()=>cM,zksyncInMemoryNode:()=>lM,zksyncLocalCustomHyperchain:()=>uM,zksyncLocalHyperchain:()=>dM,zksyncLocalHyperchainL1:()=>fM,zksyncLocalNode:()=>pM,zksyncSepoliaTestnet:()=>mM,zora:()=>gM,zoraSepolia:()=>vM,zoraTestnet:()=>bM}),SM=c({arbitrum:()=>My,arbitrumSepolia:()=>Fy,base:()=>ab,baseSepolia:()=>db,berachain:()=>_b,berachainBepolia:()=>vb,bsc:()=>Jb,celo:()=>yx,gnosis:()=>UC,hoodi:()=>vw,katana:()=>vT,mainnet:()=>aE,optimism:()=>DD,optimismSepolia:()=>jD,polygon:()=>rO,sepolia:()=>nk});const CM=[ab,...Object.values(SM).filter(e=>e&&e.id!==ab.id)],wM=ky;({...wM}),{...wM};var TM=u(s(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=`~`;function i(){}Object.create&&(i.prototype=Object.create(null),new i().__proto__||(r=!1));function a(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function o(e,t,n,i,o){if(typeof n!=`function`)throw TypeError(`The listener must be a function`);var s=new a(n,i||e,o),c=r?r+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],s]:e._events[c].push(s):(e._events[c]=s,e._eventsCount++),e}function s(e,t){--e._eventsCount===0?e._events=new i:delete e._events[t]}function c(){this._events=new i,this._eventsCount=0}c.prototype.eventNames=function(){var e=[],t,i;if(this._eventsCount===0)return e;for(i in t=this._events)n.call(t,i)&&e.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e},c.prototype.listeners=function(e){var t=r?r+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,a=n.length,o=Array(a);i`IntersectionObserver`in window&&`IntersectionObserverEntry`in window&&`intersectionRatio`in IntersectionObserverEntry.prototype&&`isVisible`in IntersectionObserverEntry.prototype;function oN(e={}){let{prefix:t=`[Porto]`}=e,n=new Set;return{error:cN(console.error,{prefix:t}),errorOnce:cN(console.error,{memo:n,prefix:t}),log:cN(console.log,{prefix:t}),logOnce:cN(console.log,{memo:n,prefix:t}),warn:cN(console.warn,{prefix:t}),warnOnce:cN(console.warn,{memo:n,prefix:t})}}const sN=oN();function cN(e,t={}){let{memo:n,prefix:r}=t;return(...t)=>{let i=t.join(` `);n?.has(i)||(n?.add(i),e(`${r} ${i}`))}}function lN(){let e=navigator.userAgent.toLowerCase();return e.includes(`safari`)&&!e.includes(`chrome`)}function uN(){let e=navigator.userAgent.toLowerCase();return(e.includes(`firefox`)||e.includes(`fxios`))&&!e.includes(`seamonkey`)}function dN(){return window.navigator?.userAgentData?.mobile?!0:navigator.maxTouchPoints>1||/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(navigator.userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(navigator.userAgent.slice(0,4))}function fN(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),reject:t,resolve:e}}function pN(e){if(Array.isArray(e))return e.map(pN);if(typeof e==`function`)return;if(typeof e!=`object`||!e)return e;if(Object.getPrototypeOf(e)!==Object.prototype)try{return structuredClone(e)}catch{return}let t={};for(let[n,r]of Object.entries(e))t[n]=pN(r);return t}function mN(e,t){let n=[],r=new Set;for(let i of e){let e=t(i);r.has(e)||(r.add(e),n.push(i))}return n}function hN(){return typeof globalThis<`u`&&`crypto`in globalThis?globalThis.crypto.randomUUID():crypto.randomUUID()}function gN(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(gN.cache.get(n))return gN.cache.get(n);let r=e().finally(()=>gN.cache.delete(n));return gN.cache.set(n,r),r}(function(e){e.cache=new Map})(gN||={});function _N(e){return e}function vN(e,t={}){let{targetOrigin:n}=t,r=new Map;return _N({destroy(){for(let t of r.values())e.removeEventListener(`message`,t)},on(t,i,a){function o(e){e.data.topic===t&&(a&&e.data.id!==a||n&&e.origin!==n||i(e.data.payload,e))}return e.addEventListener(`message`,o),r.set(t,o),()=>e.removeEventListener(`message`,o)},async send(t,r,i){let a=hN();return e.postMessage(pN({id:a,payload:r,topic:t}),i??n??`*`),{id:a,payload:r,topic:t}},async sendAsync(e,t,n){let{id:r}=await this.send(e,t,n);return new Promise(t=>this.on(e,t,r))}})}function yN(e){let{from:t,to:n,waitForReady:r=!1}=e,i=!1,a=fN();t.on(`ready`,a.resolve);let o=_N({destroy(){t.destroy(),n.destroy(),i&&a.reject()},on(e,n,r){return t.on(e,n,r)},async send(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.send(e,t)},async sendAsync(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.sendAsync(e,t)}});return{...o,ready(e){o.send(`ready`,e)},waitForReady(){return a.promise}}}const bN={local:`http://localhost:5175/dialog/`,prod:`https://id.porto.sh/dialog`,stg:`https://stg.id.porto.sh/dialog`};function xN(e){return e}function SN(e={}){let{skipProtocolCheck:t,skipUnsupported:n}=e,r=e=>!n&&lN()&&e?.some(e=>[`wallet_connect`,`eth_requestAccounts`].includes(e.method));return typeof window>`u`?wN():xN({name:`iframe`,setup(e){let{host:n,internal:i,theme:a,themeController:o}=e,{store:s}=i,c=CN().setup(e),l=!1,u=new URL(n),d=document.createElement(`dialog`);d.dataset.porto=``,d.setAttribute(`role`,`dialog`),d.setAttribute(`aria-closed`,`true`),d.setAttribute(`aria-label`,`Porto Wallet`),d.setAttribute(`hidden`,`until-found`),Object.assign(d.style,{background:`transparent`,border:`0`,outline:`0`,padding:`0`,position:`fixed`}),document.body.appendChild(d);let f=document.createElement(`iframe`);f.setAttribute(`data-testid`,`porto`);let p=[`payment`,`publickey-credentials-get ${u.origin}`,`publickey-credentials-create ${u.origin}`];uN()||p.push(`clipboard-write`),f.setAttribute(`allow`,p.join(`; `)),f.setAttribute(`tabindex`,`0`),f.setAttribute(`sandbox`,`allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox`),f.setAttribute(`src`,AN(n)),f.setAttribute(`title`,`Porto`),Object.assign(f.style,{backgroundColor:`transparent`,border:`0`,colorScheme:`light dark`,height:`100%`,left:`0`,position:`fixed`,top:`0`,width:`100%`});let m=document.createElement(`style`);m.innerHTML=` +`+e.stack}}var Pe=Object.prototype.hasOwnProperty,Fe=t.unstable_scheduleCallback,Ie=t.unstable_cancelCallback,Le=t.unstable_shouldYield,Re=t.unstable_requestPaint,ze=t.unstable_now,Be=t.unstable_getCurrentPriorityLevel,Ve=t.unstable_ImmediatePriority,He=t.unstable_UserBlockingPriority,Ue=t.unstable_NormalPriority,We=t.unstable_LowPriority,Ge=t.unstable_IdlePriority,Ke=t.log,qe=t.unstable_setDisableYieldValue,Je=null,Ye=null;function Xe(e){if(typeof Ke==`function`&&qe(e),Ye&&typeof Ye.setStrictMode==`function`)try{Ye.setStrictMode(Je,e)}catch{}}var Ze=Math.clz32?Math.clz32:et,Qe=Math.log,$e=Math.LN2;function et(e){return e>>>=0,e===0?32:31-(Qe(e)/$e|0)|0}var tt=256,nt=262144,rt=4194304;function it(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function at(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=it(n))):i=it(o):i=it(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=it(n))):i=it(o)):i=it(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function ot(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function st(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ct(){var e=rt;return rt<<=1,!(rt&62914560)&&(rt=4194304),e}function lt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function ut(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function dt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),xn=!1;if(bn)try{var Sn={};Object.defineProperty(Sn,`passive`,{get:function(){xn=!0}}),window.addEventListener(`test`,Sn,Sn),window.removeEventListener(`test`,Sn,Sn)}catch{xn=!1}var Cn=null,wn=null,Tn=null;function En(){if(Tn)return Tn;var e,t=wn,n=t.length,r,i=`value`in Cn?Cn.value:Cn.textContent,a=i.length;for(e=0;e=nr),ar=` `,or=!1;function sr(e,t){switch(e){case`keyup`:return er.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function cr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var lr=!1;function ur(e,t){switch(e){case`compositionend`:return cr(t);case`keypress`:return t.which===32?(or=!0,ar):null;case`textInput`:return e=t.data,e===ar&&or?null:e;default:return null}}function dr(e,t){if(lr)return e===`compositionend`||!tr&&sr(e,t)?(e=En(),Tn=wn=Cn=null,lr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Nr(n)}}function Fr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Fr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ir(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=qt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=qt(e.document)}return t}function Lr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Rr=bn&&`documentMode`in document&&11>=document.documentMode,zr=null,Br=null,Vr=null,Hr=!1;function Ur(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Hr||zr==null||zr!==qt(r)||(r=zr,`selectionStart`in r&&Lr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Vr&&Mr(Vr,r)||(Vr=r,r=Gd(Br,`onSelect`),0>=o,i-=o,Fi=1<<32-Ze(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),Wi&&Li(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),Wi&&Li(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return Wi&&Li(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),Wi&&Li(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===ie&&Ra(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ga(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=Si(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=xi(o.type,o.key,o.props,null,e.mode,c),Ga(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Ti(o,e.mode,c),c.return=e,e=c}return s(e);case ie:return o=Ra(o),b(e,r,o,c)}if(de(o))return h(e,r,o,c);if(ce(o)){if(l=ce(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Wa(o),c);if(o.$$typeof===ee)return b(e,r,fa(e,o),c);Ka(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=Ci(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ua=0;var i=b(e,t,n,r);return Ha=null,i}catch(t){if(t===Ma||t===Pa)throw t;var a=_i(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ja=qa(!0),Ya=qa(!1),Xa=!1;function Za(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Qa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function $a(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function eo(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,eu&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=mi(e),pi(e,null,n),t}return ui(e,r,t,n),mi(e)}function to(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,pt(e,n)}}function no(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var ro=!1;function io(){if(ro){var e=Ca;if(e!==null)throw e}}function ao(e,t,n,r){ro=!1;var i=e.updateQueue;Xa=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(M&f)===f:(r&f)===f){f!==0&&f===Sa&&(ro=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:Xa=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),lu|=o,e.lanes=o,e.memoizedState=d}}function oo(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function so(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=w.T,s={};w.T=s,qs(e,!1,t,n);try{var c=i(),l=w.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ks(e,t,Ea(c,r),ju(e)):Ks(e,t,r,ju(e))}catch(n){Ks(e,t,{then:function(){},status:`rejected`,reason:n},ju())}finally{fe.p=a,o!==null&&s.types!==null&&(o.types=s.types),w.T=o}}function Is(){}function Ls(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Rs(e).queue;Fs(e,a,t,pe,n===null?Is:function(){return zs(e),n(r)})}function Rs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:pe,baseState:pe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jo,lastRenderedState:pe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function zs(e){var t=Rs(e);t.next===null&&(t=e.alternate.memoizedState),Ks(e,t.next.queue,{},ju())}function Bs(){return da(hp)}function Vs(){return Uo().memoizedState}function Hs(){return Uo().memoizedState}function Us(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=ju();e=$a(n);var r=eo(t,e,n);r!==null&&(Nu(r,t,n),to(r,t,n)),t={cache:va()},e.payload=t;return}t=t.return}}function Ws(e,t,n){var r=ju();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Js(e)?Ys(t,n):(n=di(e,t,n,r),n!==null&&(Nu(n,e,r),Xs(n,t,r)))}function Gs(e,t,n){Ks(e,t,n,ju())}function Ks(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Js(e))Ys(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,jr(s,o))return ui(e,t,i,0),tu===null&&li(),!1}catch{}if(n=di(e,t,i,r),n!==null)return Nu(n,e,r),Xs(n,t,r),!0}return!1}function qs(e,t,n,r){if(r={lane:2,revertLane:Ad(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Js(e)){if(t)throw Error(i(479))}else t=di(e,n,r,2),t!==null&&Nu(t,e,2)}function Js(e){var t=e.alternate;return e===A||t!==null&&t===A}function Ys(e,t){Do=Eo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Xs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,pt(e,n)}}var Zs={readContext:da,use:Ko,useCallback:No,useContext:No,useEffect:No,useImperativeHandle:No,useLayoutEffect:No,useInsertionEffect:No,useMemo:No,useReducer:No,useRef:No,useState:No,useDebugValue:No,useDeferredValue:No,useTransition:No,useSyncExternalStore:No,useId:No,useHostTransitionStatus:No,useFormState:No,useActionState:No,useOptimistic:No,useMemoCache:No,useCacheRefresh:No};Zs.useEffectEvent=No;var Qs={readContext:da,use:Ko,useCallback:function(e,t){return Ho().memoizedState=[e,t===void 0?null:t],e},useContext:da,useEffect:Ss,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),bs(4194308,4,Os.bind(null,t,e),n)},useLayoutEffect:function(e,t){return bs(4194308,4,e,t)},useInsertionEffect:function(e,t){bs(4,2,e,t)},useMemo:function(e,t){var n=Ho();t=t===void 0?null:t;var r=e();if(Oo){Xe(!0);try{e()}finally{Xe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Ho();if(n!==void 0){var i=n(t);if(Oo){Xe(!0);try{n(t)}finally{Xe(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ws.bind(null,A,e),[r.memoizedState,e]},useRef:function(e){var t=Ho();return e={current:e},t.memoizedState=e},useState:function(e){e=is(e);var t=e.queue,n=Gs.bind(null,A,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:As,useDeferredValue:function(e,t){return Ns(Ho(),e,t)},useTransition:function(){var e=is(!1);return e=Fs.bind(null,A,e.queue,!0,!1),Ho().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=A,a=Ho();if(Wi){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),tu===null)throw Error(i(349));M&127||$o(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,Ss(ts.bind(null,r,o,e),[e]),r.flags|=2048,vs(9,{destroy:void 0},es.bind(null,r,o,n,t),null),n},useId:function(){var e=Ho(),t=tu.identifierPrefix;if(Wi){var n=Ii,r=Fi;n=(r&~(1<<32-Ze(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=ko++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[bt]=t,o[xt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch($d(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Kc(t)}}return Zc(t),qc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Kc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=xe.current,Zi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Hi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[bt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Zd(e.nodeValue,n)),e||Ji(t,!0)}else e=of(e).createTextNode(r),e[bt]=t,t.stateNode=e}return Zc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Zi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[bt]=t}else Qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Zc(t),e=!1}else n=$i(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(bo(t),t):(bo(t),null);if(t.flags&128)throw Error(i(558))}return Zc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Zi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[bt]=t}else Qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Zc(t),a=!1}else a=$i(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(bo(t),t):(bo(t),null)}return bo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Yc(t,t.updateQueue),Zc(t),null);case 4:return we(),e===null&&Vd(t.stateNode.containerInfo),Zc(t),null;case 10:return aa(t.type),Zc(t),null;case 19:if(_e(xo),r=t.memoizedState,r===null)return Zc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Xc(r,!1);else{if(cu!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=So(e),o!==null){for(t.flags|=128,Xc(r,!1),e=o.updateQueue,t.updateQueue=e,Yc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)bi(n,e),n=n.sibling;return ve(xo,xo.current&1|2),Wi&&Li(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&ze()>yu&&(t.flags|=128,a=!0,Xc(r,!1),t.lanes=4194304)}else{if(!a)if(e=So(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Yc(t,e),Xc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!Wi)return Zc(t),null}else 2*ze()-r.renderingStartTime>yu&&n!==536870912&&(t.flags|=128,a=!0,Xc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Zc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ze(),e.sibling=null,n=xo.current,ve(xo,a?n&1|2:n&1),Wi&&Li(t,r.treeForkCount),e);case 22:case 23:return bo(t),po(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Zc(t),t.subtreeFlags&6&&(t.flags|=8192)):Zc(t),n=t.updateQueue,n!==null&&Yc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&_e(Oa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),aa(_a),Zc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function $c(e,t){switch(Bi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return aa(_a),we(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Ee(t),null;case 31:if(t.memoizedState!==null){if(bo(t),t.alternate===null)throw Error(i(340));Qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(bo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return _e(xo),null;case 4:return we(),null;case 10:return aa(t.type),null;case 22:case 23:return bo(t),po(),e!==null&&_e(Oa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return aa(_a),null;case 25:return null;default:return null}}function el(e,t){switch(Bi(t),t.tag){case 3:aa(_a),we();break;case 26:case 27:case 5:Ee(t);break;case 4:we();break;case 31:t.memoizedState!==null&&bo(t);break;case 13:bo(t);break;case 19:_e(xo);break;case 10:aa(t.type);break;case 22:case 23:bo(t),po(),e!==null&&_e(Oa);break;case 24:aa(_a)}}function tl(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){ld(t,t.return,e)}}function nl(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){ld(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){ld(t,t.return,e)}}function rl(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{so(t,n)}catch(t){ld(e,e.return,t)}}}function il(e,t,n){n.props=ac(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){ld(e,t,n)}}function al(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){ld(e,t,n)}}function ol(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){ld(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){ld(e,t,n)}else n.current=null}function sl(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){ld(e,e.return,t)}}function cl(e,t,n){try{var r=e.stateNode;ef(r,e.type,n,t),r[xt]=t}catch(t){ld(e,e.return,t)}}function ll(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&_f(e.type)||e.tag===4}function ul(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||ll(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&_f(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function dl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=dn));else if(r!==4&&(r===27&&_f(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(dl(e,t,n),e=e.sibling;e!==null;)dl(e,t,n),e=e.sibling}function fl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&_f(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(fl(e,t,n),e=e.sibling;e!==null;)fl(e,t,n),e=e.sibling}function pl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);$d(t,r,n),t[bt]=e,t[xt]=n}catch(t){ld(e,e.return,t)}}var ml=!1,hl=!1,gl=!1,_l=typeof WeakSet==`function`?WeakSet:Set,vl=null;function yl(e,t){if(e=e.containerInfo,rf=wp,e=Ir(e),Lr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(af={focusedElem:e,selectionRange:n},wp=!1,vl=t;vl!==null;)if(t=vl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,vl=e;else for(;vl!==null;){switch(t=vl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),$d(o,r,n),o[bt]=e,Mt(o),r=o;break a;case`link`:var s=ip(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Pr(s,h),v=Pr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,w.T=null,n=Du,Du=null;var o=Cu,s=Tu;if(Su=0,wu=Cu=null,Tu=0,eu&6)throw Error(i(331));var c=eu;if(eu|=4,Yl(o.current),Vl(o,o.current,s,n),eu=c,Cd(0,!1),Ye&&typeof Ye.onPostCommitFiberRoot==`function`)try{Ye.onPostCommitFiberRoot(Je,o)}catch{}return!0}finally{fe.p=a,w.T=r,ad(e,t)}}function cd(e,t,n){t=Di(n,t),t=dc(e.stateNode,t,2),e=eo(e,t,2),e!==null&&(ut(e,2),Sd(e))}function ld(e,t,n){if(e.tag===3)cd(e,e,n);else for(;t!==null;){if(t.tag===3){cd(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(xu===null||!xu.has(r))){e=Di(n,e),n=fc(2),r=eo(t,n,2),r!==null&&(pc(n,r,t,e),ut(r,2),Sd(r));break}}t=t.return}}function ud(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new $l;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(ou=!0,i.add(n),e=dd.bind(null,e,t,n),t.then(e,e))}function dd(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,tu===e&&(M&n)===n&&(cu===4||cu===3&&(M&62914560)===M&&300>ze()-_u?!(eu&2)&&Bu(e,0):du|=n,pu===M&&(pu=0)),Sd(e)}function fd(e,t){t===0&&(t=ct()),e=fi(e,t),e!==null&&(ut(e,t),Sd(e))}function pd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),fd(e,n)}function md(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),fd(e,n)}function hd(e,t){return Fe(e,t)}var gd=null,_d=null,vd=!1,yd=!1,bd=!1,xd=0;function Sd(e){e!==_d&&e.next===null&&(_d===null?gd=_d=e:_d=_d.next=e),yd=!0,vd||(vd=!0,kd())}function Cd(e,t){if(!bd&&yd){bd=!0;do for(var n=!1,r=gd;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ze(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,Od(r,a))}else a=M,a=at(r,r===tu?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||ot(r,a)||(n=!0,Od(r,a));r=r.next}while(n);bd=!1}}function wd(){Td()}function Td(){yd=vd=!1;var e=0;xd!==0&&df()&&(e=xd);for(var t=ze(),n=null,r=gd;r!==null;){var i=r.next,a=Ed(r,t);a===0?(r.next=null,n===null?gd=i:n.next=i,i===null&&(_d=n)):(n=r,(e!==0||a&3)&&(yd=!0)),r=i}Su!==0&&Su!==5||Cd(e,!1),xd!==0&&(xd=0)}function Ed(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&tf(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Bf(e,t,n){var r=zf;if(r&&typeof t==`string`&&t){var i=Yt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),Pf.has(i)||(Pf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),$d(t,`link`,e),Mt(t),r.head.appendChild(t)))}}function Vf(e){If.D(e),Bf(`dns-prefetch`,e,null)}function Hf(e,t){If.C(e,t),Bf(`preconnect`,e,t)}function Uf(e,t,n){If.L(e,t,n);var r=zf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Yt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Yt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Yt(n.imageSizes)+`"]`)):i+=`[href="`+Yt(e)+`"]`;var a=i;switch(t){case`style`:a=Jf(e);break;case`script`:a=I(e)}Nf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),Nf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Yf(a))||t===`script`&&r.querySelector(Qf(a))||(t=r.createElement(`link`),$d(t,`link`,e),Mt(t),r.head.appendChild(t)))}}function Wf(e,t){If.m(e,t);var n=zf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Yt(r)+`"][href="`+Yt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=I(e)}if(!Nf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),Nf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Qf(a)))return}r=n.createElement(`link`),$d(r,`link`,e),Mt(r),n.head.appendChild(r)}}}function Gf(e,t,n){If.S(e,t,n);var r=zf;if(r&&e){var i=jt(r).hoistableStyles,a=Jf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Yf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=Nf.get(a))&&tp(e,n);var c=o=r.createElement(`link`);Mt(c),$d(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,ep(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Kf(e,t){If.X(e,t);var n=zf;if(n&&e){var r=jt(n).hoistableScripts,i=I(e),a=r.get(i);a||(a=n.querySelector(Qf(i)),a||(e=p({src:e,async:!0},t),(t=Nf.get(i))&&np(e,t),a=n.createElement(`script`),Mt(a),$d(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function qf(e,t){If.M(e,t);var n=zf;if(n&&e){var r=jt(n).hoistableScripts,i=I(e),a=r.get(i);a||(a=n.querySelector(Qf(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=Nf.get(i))&&np(e,t),a=n.createElement(`script`),Mt(a),$d(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function F(e,t,n,r){var a=(a=xe.current)?Ff(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Jf(n.href),n=jt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Jf(n.href);var o=jt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Yf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),Nf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Nf.set(e,n),o||Zf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=I(n),n=jt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Jf(e){return`href="`+Yt(e)+`"`}function Yf(e){return`link[rel="stylesheet"][`+e+`]`}function Xf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Zf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),$d(t,`link`,n),Mt(t),e.head.appendChild(t))}function I(e){return`[src="`+Yt(e)+`"]`}function Qf(e){return`script[async]`+e}function $f(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Yt(n.href)+`"]`);if(r)return t.instance=r,Mt(r),r;var a=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Mt(r),$d(r,`style`,a),ep(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Jf(n.href);var o=e.querySelector(Yf(a));if(o)return t.state.loading|=4,t.instance=o,Mt(o),o;r=Xf(n),(a=Nf.get(a))&&tp(r,a),o=(e.ownerDocument||e).createElement(`link`),Mt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),$d(o,`link`,r),t.state.loading|=4,ep(o,n.precedence,e),t.instance=o;case`script`:return o=I(n.src),(a=e.querySelector(Qf(o)))?(t.instance=a,Mt(a),a):(r=n,(a=Nf.get(o))&&(r=p({},n),np(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Mt(a),$d(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,ep(r,n.precedence,e));return t.instance}function ep(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function op(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function sp(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function cp(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Jf(r.href),a=t.querySelector(Yf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=dp.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Mt(a);return}a=t.ownerDocument||t,r=Xf(r),(i=Nf.get(i))&&tp(r,i),a=a.createElement(`link`),Mt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),$d(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=dp.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var lp=0;function up(e,t){return e.stylesheets&&e.count===0&&pp(e,e.stylesheets),0lp?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function dp(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)pp(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var fp=null;function pp(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,fp=new Map,t.forEach(mp,e),fp=null,dp.call(e))}function mp(e,t){if(!(t.state.loading&4)){var n=fp.get(e);if(n)var r=n.get(null);else{n=new Map,fp.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=_()}))(),y=f(),b,x=o((()=>{b=`1.2.3`})),S,ee=o((()=>{x(),S=class e extends Error{constructor(t,n={}){let r=n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details,i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...i?[`Docs: https://abitype.dev${i}`]:[],...r?[`Details: ${r}`]:[],`Version: abitype@${b}`].join(` +`);super(a),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiTypeError`}),n.cause&&(this.cause=n.cause),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.shortMessage=t}}}));function C(e,t){return e.exec(t)?.groups}var te,ne,re,ie=o((()=>{te=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,ne=/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/,re=/^\(.+?\).*?$/}));function ae(e){let t=e.type;if(oe.test(e.type)&&`components`in e){t=`(`;let n=e.components.length;for(let r=0;r{ie(),oe=/^tuple(?(\[(\d*)\])*)$/}));function ce(e){let t=``,n=e.length;for(let r=0;r{se()}));function ue(e){return e.type===`function`?`function ${e.name}(${ce(e.inputs)})${e.stateMutability&&e.stateMutability!==`nonpayable`?` ${e.stateMutability}`:``}${e.outputs?.length?` returns (${ce(e.outputs)})`:``}`:e.type===`event`?`event ${e.name}(${ce(e.inputs)})`:e.type===`error`?`error ${e.name}(${ce(e.inputs)})`:e.type===`constructor`?`constructor(${ce(e.inputs)})${e.stateMutability===`payable`?` payable`:``}`:e.type===`fallback`?`fallback() external${e.stateMutability===`payable`?` payable`:``}`:`receive() external payable`}var de=o((()=>{le()}));function w(e){return we.test(e)}function fe(e){return C(we,e)}function pe(e){return Te.test(e)}function me(e){return C(Te,e)}function he(e){return Ee.test(e)}function ge(e){return C(Ee,e)}function _e(e){return De.test(e)}function ve(e){return C(De,e)}function ye(e){return Oe.test(e)}function be(e){return C(Oe,e)}function xe(e){return ke.test(e)}function Se(e){return C(ke,e)}function Ce(e){return Ae.test(e)}var we,Te,Ee,De,Oe,ke,Ae,je,Me,Ne,Pe=o((()=>{ie(),we=/^error (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,Te=/^event (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,Ee=/^function (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)(?: (?external|public{1}))?(?: (?pure|view|nonpayable|payable{1}))?(?: returns\s?\((?.*?)\))?$/,De=/^struct (?[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?.*?)\}$/,Oe=/^constructor\((?.*?)\)(?:\s(?payable{1}))?$/,ke=/^fallback\(\) external(?:\s(?payable{1}))?$/,Ae=/^receive\(\) external payable$/,je=new Set([`memory`,`indexed`,`storage`,`calldata`]),Me=new Set([`indexed`]),Ne=new Set([`calldata`,`memory`,`storage`])})),Fe,Ie,Le,Re=o((()=>{ee(),Fe=class extends S{constructor({signature:e}){super(`Failed to parse ABI item.`,{details:`parseAbiItem(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiitem-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiItemError`})}},Ie=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownTypeError`})}},Le=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSolidityTypeError`})}}})),ze,Be,Ve,He,Ue,We,Ge=o((()=>{ee(),ze=class extends S{constructor({params:e}){super(`Failed to parse ABI parameters.`,{details:`parseAbiParameters(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiparameters-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiParametersError`})}},Be=class extends S{constructor({param:e}){super(`Invalid ABI parameter.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParameterError`})}},Ve=class extends S{constructor({param:e,name:t}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`"${t}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SolidityProtectedKeywordError`})}},He=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidModifierError`})}},Ue=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`,`Data location can only be specified for array, struct, or mapping types, but "${n}" was given.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidFunctionModifierError`})}},We=class extends S{constructor({abiParameter:e}){super(`Invalid ABI parameter.`,{details:JSON.stringify(e,null,2),metaMessages:[`ABI parameter type is invalid.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiTypeParameterError`})}}})),Ke,qe,Je,Ye=o((()=>{ee(),Ke=class extends S{constructor({signature:e,type:t}){super(`Invalid ${t} signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidSignatureError`})}},qe=class extends S{constructor({signature:e}){super(`Unknown signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSignatureError`})}},Je=class extends S{constructor({signature:e}){super(`Invalid struct signature.`,{details:e,metaMessages:[`No properties exist.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidStructSignatureError`})}}})),Xe,Ze=o((()=>{ee(),Xe=class extends S{constructor({type:e}){super(`Circular reference detected.`,{metaMessages:[`Struct "${e}" is a circular reference.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`CircularReferenceError`})}}})),Qe,$e=o((()=>{ee(),Qe=class extends S{constructor({current:e,depth:t}){super(`Unbalanced parentheses.`,{metaMessages:[`"${e.trim()}" has too many ${t>0?`opening`:`closing`} parentheses.`],details:`Depth "${t}"`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParenthesisError`})}}}));function et(e,t,n){let r=``;if(n)for(let e of Object.entries(n)){if(!e)continue;let t=``;for(let n of e[1])t+=`[${n.type}${n.name?`:${n.name}`:``}]`;r+=`(${e[0]}{${t}})`}return t?`${t}:${e}${r}`:`${e}${r}`}var tt,nt=o((()=>{tt=new Map([[`address`,{type:`address`}],[`bool`,{type:`bool`}],[`bytes`,{type:`bytes`}],[`bytes32`,{type:`bytes32`}],[`int`,{type:`int256`}],[`int256`,{type:`int256`}],[`string`,{type:`string`}],[`uint`,{type:`uint256`}],[`uint8`,{type:`uint8`}],[`uint16`,{type:`uint16`}],[`uint24`,{type:`uint24`}],[`uint32`,{type:`uint32`}],[`uint64`,{type:`uint64`}],[`uint96`,{type:`uint96`}],[`uint112`,{type:`uint112`}],[`uint160`,{type:`uint160`}],[`uint192`,{type:`uint192`}],[`uint256`,{type:`uint256`}],[`address owner`,{type:`address`,name:`owner`}],[`address to`,{type:`address`,name:`to`}],[`bool approved`,{type:`bool`,name:`approved`}],[`bytes _data`,{type:`bytes`,name:`_data`}],[`bytes data`,{type:`bytes`,name:`data`}],[`bytes signature`,{type:`bytes`,name:`signature`}],[`bytes32 hash`,{type:`bytes32`,name:`hash`}],[`bytes32 r`,{type:`bytes32`,name:`r`}],[`bytes32 root`,{type:`bytes32`,name:`root`}],[`bytes32 s`,{type:`bytes32`,name:`s`}],[`string name`,{type:`string`,name:`name`}],[`string symbol`,{type:`string`,name:`symbol`}],[`string tokenURI`,{type:`string`,name:`tokenURI`}],[`uint tokenId`,{type:`uint256`,name:`tokenId`}],[`uint8 v`,{type:`uint8`,name:`v`}],[`uint256 balance`,{type:`uint256`,name:`balance`}],[`uint256 tokenId`,{type:`uint256`,name:`tokenId`}],[`uint256 value`,{type:`uint256`,name:`value`}],[`event:address indexed from`,{type:`address`,name:`from`,indexed:!0}],[`event:address indexed to`,{type:`address`,name:`to`,indexed:!0}],[`event:uint indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}],[`event:uint256 indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}]])}));function rt(e,t={}){if(he(e))return it(e,t);if(pe(e))return at(e,t);if(w(e))return ot(e,t);if(ye(e))return st(e,t);if(xe(e))return ct(e);if(Ce(e))return{type:`receive`,stateMutability:`payable`};throw new qe({signature:e})}function it(e,t={}){let n=ge(e);if(!n)throw new Ke({signature:e,type:`function`});let r=ut(n.parameters),i=[],a=r.length;for(let e=0;e{ie(),Re(),Ge(),Ye(),$e(),nt(),Pe(),mt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*(?:\spayable)?)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,ht=/^\((?.+?)\)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,gt=/^u?int$/,_t=/^(?:after|alias|anonymous|apply|auto|byte|calldata|case|catch|constant|copyof|default|defined|error|event|external|false|final|function|immutable|implements|in|indexed|inline|internal|let|mapping|match|memory|mutable|null|of|override|partial|private|promise|public|pure|reference|relocatable|return|returns|sizeof|static|storage|struct|super|supports|switch|this|true|try|typedef|typeof|var|view|virtual)$/}));function yt(e){let t={},n=e.length;for(let r=0;r{ie(),Re(),Ge(),Ye(),Ze(),Pe(),vt(),xt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*)(?(?:\[\d*?\])+?)?$/}));function Ct(e){let t=yt(e),n=[],r=e.length;for(let i=0;i{Pe(),St(),vt()}));function Tt(e){let t;if(typeof e==`string`)t=rt(e);else{let n=yt(e),r=e.length;for(let i=0;i{Re(),Pe(),St(),vt()}));function Dt(e){let t=[];if(typeof e==`string`){let n=ut(e),r=n.length;for(let e=0;e{Ge(),Pe(),St(),vt()})),kt=o((()=>{de(),le(),wt(),Et(),Ot()}));kt();function T(e,t,n){let r=e[t.name];if(typeof r==`function`)return r;let i=e[n];return typeof i==`function`?i:n=>t(e,n)}function At(e,{includeName:t=!1}={}){if(e.type!==`function`&&e.type!==`event`&&e.type!==`error`)throw new sn(e.type);return`${e.name}(${jt(e.inputs,{includeName:t})})`}function jt(e,{includeName:t=!1}={}){return e?e.map(e=>Mt(e,{includeName:t})).join(t?`, `:`,`):``}function Mt(e,{includeName:t}){return e.type.startsWith(`tuple`)?`(${jt(e.components,{includeName:t})})${e.type.slice(5)}`:e.type+(t&&e.name?` ${e.name}`:``)}var Nt=o((()=>{cn()}));function Pt(e,{strict:t=!0}={}){return!e||typeof e!=`string`?!1:t?/^0x[0-9a-fA-F]*$/.test(e):e.startsWith(`0x`)}var Ft=o((()=>{}));function It(e){return Pt(e,{strict:!1})?Math.ceil((e.length-2)/2):e.length}var Lt=o((()=>{Ft()})),Rt,zt=o((()=>{Rt=`2.47.6`}));function Bt(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause!==void 0?Bt(e.cause,t):t?null:e}var Vt,E,D=o((()=>{zt(),Vt={getDocsUrl:({docsBaseUrl:e,docsPath:t=``,docsSlug:n})=>t?`${e??`https://viem.sh`}${t}${n?`#${n}`:``}`:void 0,version:`viem@${Rt}`},E=class e extends Error{constructor(t,n={}){let r=n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details,i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=Vt.getDocsUrl?.({...n,docsPath:i}),o=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...a?[`Docs: ${a}`]:[],...r?[`Details: ${r}`]:[],...Vt.version?[`Version: ${Vt.version}`]:[]].join(` +`);super(o,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.name=n.name??this.name,this.shortMessage=t,this.version=Rt}walk(e){return Bt(this,e)}}})),Ht,Ut,Wt,Gt,Kt,qt,Jt,Yt,Xt,Zt,Qt,$t,en,tn,nn,rn,an,on,sn,cn=o((()=>{Nt(),Lt(),D(),Ht=class extends E{constructor({docsPath:e}){super([`A constructor was not found on the ABI.`,`Make sure you are using the correct ABI and that the constructor exists on it.`].join(` +`),{docsPath:e,name:`AbiConstructorNotFoundError`})}},Ut=class extends E{constructor({docsPath:e}){super(["Constructor arguments were provided (`args`), but a constructor parameters (`inputs`) were not found on the ABI.","Make sure you are using the correct ABI, and that the `inputs` attribute on the constructor exists."].join(` +`),{docsPath:e,name:`AbiConstructorParamsNotFoundError`})}},Wt=class extends E{constructor({data:e,params:t,size:n}){super([`Data size of ${n} bytes is too small for given parameters.`].join(` +`),{metaMessages:[`Params: (${jt(t,{includeName:!0})})`,`Data: ${e} (${n} bytes)`],name:`AbiDecodingDataSizeTooSmallError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`params`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`size`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e,this.params=t,this.size=n}},Gt=class extends E{constructor({cause:e}={}){super(`Cannot decode zero data ("0x") with ABI parameters.`,{name:`AbiDecodingZeroDataError`,cause:e})}},Kt=class extends E{constructor({expectedLength:e,givenLength:t,type:n}){super([`ABI encoding array length mismatch for type ${n}.`,`Expected length: ${e}`,`Given length: ${t}`].join(` +`),{name:`AbiEncodingArrayLengthMismatchError`})}},qt=class extends E{constructor({expectedSize:e,value:t}){super(`Size of bytes "${t}" (bytes${It(t)}) does not match expected size (bytes${e}).`,{name:`AbiEncodingBytesSizeMismatchError`})}},Jt=class extends E{constructor({expectedLength:e,givenLength:t}){super([`ABI encoding params/values length mismatch.`,`Expected length (params): ${e}`,`Given length (values): ${t}`].join(` +`),{name:`AbiEncodingLengthMismatchError`})}},Yt=class extends E{constructor(e,{docsPath:t}){super([`Arguments (\`args\`) were provided to "${e}", but "${e}" on the ABI does not contain any parameters (\`inputs\`).`,`Cannot encode error result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the inputs exist on it.`].join(` +`),{docsPath:t,name:`AbiErrorInputsNotFoundError`})}},Xt=class extends E{constructor(e,{docsPath:t}={}){super([`Error ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`].join(` +`),{docsPath:t,name:`AbiErrorNotFoundError`})}},Zt=class extends E{constructor(e,{docsPath:t,cause:n}){super([`Encoded error signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${e}.`].join(` +`),{docsPath:t,name:`AbiErrorSignatureNotFoundError`,cause:n}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.signature=e}},Qt=class extends E{constructor(e,{docsPath:t}={}){super([`Function ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` +`),{docsPath:t,name:`AbiFunctionNotFoundError`})}},$t=class extends E{constructor(e,{docsPath:t}){super([`Function "${e}" does not contain any \`outputs\` on ABI.`,`Cannot decode function result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` +`),{docsPath:t,name:`AbiFunctionOutputsNotFoundError`})}},en=class extends E{constructor(e,{docsPath:t}){super([`Encoded function signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`,`You can look up the signature here: https://4byte.sourcify.dev/?q=${e}.`].join(` +`),{docsPath:t,name:`AbiFunctionSignatureNotFoundError`})}},tn=class extends E{constructor(e,t){super(`Found ambiguous types in overloaded ABI items.`,{metaMessages:[`\`${e.type}\` in \`${At(e.abiItem)}\`, and`,`\`${t.type}\` in \`${At(t.abiItem)}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`],name:`AbiItemAmbiguityError`})}},nn=class extends E{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`,{name:`BytesSizeMismatchError`})}},rn=class extends E{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid encoding type.`,`Please provide a valid ABI type.`].join(` +`),{docsPath:t,name:`InvalidAbiEncodingType`})}},an=class extends E{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid decoding type.`,`Please provide a valid ABI type.`].join(` +`),{docsPath:t,name:`InvalidAbiDecodingType`})}},on=class extends E{constructor(e){super([`Value "${e}" is not a valid array.`].join(` +`),{name:`InvalidArrayError`})}},sn=class extends E{constructor(e){super([`"${e}" is not a valid definition type.`,`Valid types: "function", "event", "error"`].join(` +`),{name:`InvalidDefinitionTypeError`})}}})),ln,un,dn,fn=o((()=>{D(),ln=class extends E{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset "${e}" is out-of-bounds (size: ${n}).`,{name:`SliceOffsetOutOfBoundsError`})}},un=class extends E{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (${e}) exceeds padding size (${t}).`,{name:`SizeExceedsPaddingSizeError`})}},dn=class extends E{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} is expected to be ${t} ${n} long, but is ${e} ${n} long.`,{name:`InvalidBytesLengthError`})}}}));function pn(e,{dir:t,size:n=32}={}){return typeof e==`string`?mn(e,{dir:t,size:n}):hn(e,{dir:t,size:n})}function mn(e,{dir:t,size:n=32}={}){if(n===null)return e;let r=e.replace(`0x`,``);if(r.length>n*2)throw new un({size:Math.ceil(r.length/2),targetSize:n,type:`hex`});return`0x${r[t===`right`?`padEnd`:`padStart`](n*2,`0`)}`}function hn(e,{dir:t,size:n=32}={}){if(n===null)return e;if(e.length>n)throw new un({size:e.length,targetSize:n,type:`bytes`});let r=new Uint8Array(n);for(let i=0;i{fn()})),_n,vn,yn,bn,xn=o((()=>{D(),_n=class extends E{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number "${i}" is not in safe ${r?`${r*8}-bit ${n?`signed`:`unsigned`} `:``}integer range ${e?`(${t} to ${e})`:`(above ${t})`}`,{name:`IntegerOutOfRangeError`})}},vn=class extends E{constructor(e){super(`Bytes value "${e}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`,{name:`InvalidBytesBooleanError`})}},yn=class extends E{constructor(e){super(`Hex value "${e}" is not a valid boolean. The hex value must be "0x0" (false) or "0x1" (true).`,{name:`InvalidHexBooleanError`})}},bn=class extends E{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed ${t} bytes. Given size: ${e} bytes.`,{name:`SizeOverflowError`})}}}));function Sn(e,{dir:t=`left`}={}){let n=typeof e==`string`?e.replace(`0x`,``):e,r=0;for(let e=0;e{}));function wn(e,{size:t}){if(It(e)>t)throw new bn({givenSize:It(e),maxSize:t})}function Tn(e,t={}){let{signed:n}=t;t.size&&wn(e,{size:t.size});let r=BigInt(e);if(!n)return r;let i=(e.length-2)/2;return r<=(1n<{xn(),Lt(),Cn()}));function kn(e,t={}){return typeof e==`number`||typeof e==`bigint`?O(e,t):typeof e==`string`?Mn(e,t):typeof e==`boolean`?An(e,t):jn(e,t)}function An(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(wn(n,{size:t.size}),pn(n,{size:t.size})):n}function jn(e,t={}){let n=``;for(let t=0;ta||i{xn(),gn(),On(),Nn=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),Pn=new TextEncoder}));function Fn(e,t={}){return typeof e==`number`||typeof e==`bigint`?zn(e,t):typeof e==`boolean`?In(e,t):Pt(e)?Rn(e,t):Bn(e,t)}function In(e,t={}){let n=new Uint8Array(1);return n[0]=Number(e),typeof t.size==`number`?(wn(n,{size:t.size}),pn(n,{size:t.size})):n}function Ln(e){if(e>=Hn.zero&&e<=Hn.nine)return e-Hn.zero;if(e>=Hn.A&&e<=Hn.F)return e-(Hn.A-10);if(e>=Hn.a&&e<=Hn.f)return e-(Hn.a-10)}function Rn(e,t={}){let n=e;t.size&&(wn(n,{size:t.size}),n=pn(n,{dir:`right`,size:t.size}));let r=n.slice(2);r.length%2&&(r=`0${r}`);let i=r.length/2,a=new Uint8Array(i);for(let e=0,t=0;e{D(),Ft(),gn(),On(),k(),Vn=new TextEncoder,Hn={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function Wn(e,t=!1){return t?{h:Number(e&qn),l:Number(e>>Jn&qn)}:{h:Number(e>>Jn&qn)|0,l:Number(e&qn)|0}}function Gn(e,t=!1){let n=e.length,r=new Uint32Array(n),i=new Uint32Array(n);for(let a=0;a>>0)+(r>>>0);return{h:e+n+(i/2**32|0)|0,l:i|0}}var qn,Jn,Yn,Xn,Zn,Qn,$n,er,tr,nr,rr,ir,ar,or,sr,cr,lr,ur,dr=o((()=>{qn=BigInt(2**32-1),Jn=BigInt(32),Yn=(e,t,n)=>e>>>n,Xn=(e,t,n)=>e<<32-n|t>>>n,Zn=(e,t,n)=>e>>>n|t<<32-n,Qn=(e,t,n)=>e<<32-n|t>>>n,$n=(e,t,n)=>e<<64-n|t>>>n-32,er=(e,t,n)=>e>>>n-32|t<<64-n,tr=(e,t,n)=>e<>>32-n,nr=(e,t,n)=>t<>>32-n,rr=(e,t,n)=>t<>>64-n,ir=(e,t,n)=>e<>>64-n,ar=(e,t,n)=>(e>>>0)+(t>>>0)+(n>>>0),or=(e,t,n,r)=>t+n+r+(e/2**32|0)|0,sr=(e,t,n,r)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0),cr=(e,t,n,r,i)=>t+n+r+i+(e/2**32|0)|0,lr=(e,t,n,r,i)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0)+(i>>>0),ur=(e,t,n,r,i,a)=>t+n+r+i+a+(e/2**32|0)|0})),fr,pr=o((()=>{fr=typeof globalThis==`object`&&`crypto`in globalThis?globalThis.crypto:void 0}));function mr(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function hr(e){if(!Number.isSafeInteger(e)||e<0)throw Error(`positive integer expected, got `+e)}function gr(e,...t){if(!mr(e))throw Error(`Uint8Array expected`);if(t.length>0&&!t.includes(e.length))throw Error(`Uint8Array expected of length `+t+`, got length=`+e.length)}function _r(e){if(typeof e!=`function`||typeof e.create!=`function`)throw Error(`Hash should be wrapped by utils.createHasher`);hr(e.outputLen),hr(e.blockLen)}function vr(e,t=!0){if(e.destroyed)throw Error(`Hash instance has been destroyed`);if(t&&e.finished)throw Error(`Hash#digest() has already been called`)}function yr(e,t){gr(e);let n=t.outputLen;if(e.length>>t}function wr(e){return e<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255}function Tr(e){for(let t=0;te().update(Dr(t)).digest(),n=e();return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=()=>e(),t}function Ar(e){let t=(t,n)=>e(n).update(Dr(t)).digest(),n=e({});return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=t=>e(t),t}function jr(e=32){if(fr&&typeof fr.getRandomValues==`function`)return fr.getRandomValues(new Uint8Array(e));if(fr&&typeof fr.randomBytes==`function`)return Uint8Array.from(fr.randomBytes(e));throw Error(`crypto.getRandomValues must be defined`)}var Mr,Nr,Pr,Fr=o((()=>{pr(),Mr=new Uint8Array(new Uint32Array([287454020]).buffer)[0]===68,Nr=Mr?e=>e:Tr,typeof Uint8Array.from([]).toHex==`function`&&Uint8Array.fromHex,Pr=class{}}));function Ir(e,t=24){let n=new Uint32Array(10);for(let r=24-t;r<24;r++){for(let t=0;t<10;t++)n[t]=e[t]^e[t+10]^e[t+20]^e[t+30]^e[t+40];for(let t=0;t<10;t+=2){let r=(t+8)%10,i=(t+2)%10,a=n[i],o=n[i+1],s=Yr(a,o,1)^n[r],c=Xr(a,o,1)^n[r+1];for(let n=0;n<50;n+=10)e[t+n]^=s,e[t+n+1]^=c}let t=e[2],i=e[3];for(let n=0;n<24;n++){let r=Wr[n],a=Yr(t,i,r),o=Xr(t,i,r),s=Ur[n];t=e[s],i=e[s+1],e[s]=a,e[s+1]=o}for(let t=0;t<50;t+=10){for(let r=0;r<10;r++)n[r]=e[t+r];for(let r=0;r<10;r++)e[t+r]^=~n[(r+2)%10]&n[(r+4)%10]}e[0]^=qr[r],e[1]^=Jr[r]}xr(n)}var Lr,Rr,zr,Br,Vr,Hr,Ur,Wr,Gr,Kr,qr,Jr,Yr,Xr,Zr,Qr,$r,ei,ti=o((()=>{dr(),Fr(),Lr=BigInt(0),Rr=BigInt(1),zr=BigInt(2),Br=BigInt(7),Vr=BigInt(256),Hr=BigInt(113),Ur=[],Wr=[],Gr=[];for(let e=0,t=Rr,n=1,r=0;e<24;e++){[n,r]=[r,(2*n+3*r)%5],Ur.push(2*(5*r+n)),Wr.push((e+1)*(e+2)/2%64);let i=Lr;for(let e=0;e<7;e++)t=(t<>Br)*Hr)%Vr,t&zr&&(i^=Rr<<(Rr<n>32?rr(e,t,n):tr(e,t,n),Xr=(e,t,n)=>n>32?ir(e,t,n):nr(e,t,n),Zr=class e extends Pr{constructor(e,t,n,r=!1,i=24){if(super(),this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,this.enableXOF=!1,this.blockLen=e,this.suffix=t,this.outputLen=n,this.enableXOF=r,this.rounds=i,hr(n),!(0=n&&this.keccak();let a=Math.min(n-this.posOut,i-r);e.set(t.subarray(this.posOut,this.posOut+a),r),this.posOut+=a,r+=a}return e}xofInto(e){if(!this.enableXOF)throw Error(`XOF is not possible for this instance`);return this.writeInto(e)}xof(e){return hr(e),this.xofInto(new Uint8Array(e))}digestInto(e){if(yr(e,this),this.finished)throw Error(`digest() was already called`);return this.writeInto(e),this.destroy(),e}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,xr(this.state)}_cloneInto(t){let{blockLen:n,suffix:r,outputLen:i,rounds:a,enableXOF:o}=this;return t||=new e(n,r,i,o,a),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=a,t.suffix=r,t.outputLen=i,t.enableXOF=o,t.destroyed=this.destroyed,t}},Qr=(e,t,n)=>kr(()=>new Zr(t,e,n)),Qr(6,144,224/8),Qr(6,136,256/8),Qr(6,104,384/8),Qr(6,72,512/8),Qr(1,144,224/8),$r=Qr(1,136,256/8),Qr(1,104,384/8),Qr(1,72,512/8),ei=(e,t,n)=>Ar((r={})=>new Zr(t,e,r.dkLen===void 0?n:r.dkLen,!0)),ei(31,168,128/8),ei(31,136,256/8)}));function ni(e,t){let n=t||`hex`,r=$r(Pt(e,{strict:!1})?Fn(e):e);return n===`bytes`?r:kn(r)}var ri=o((()=>{ti(),Ft(),Un(),k()}));function ii(e){return ai(e)}var ai,oi=o((()=>{Un(),ri(),ai=e=>ni(Fn(e))}));function si(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;o{D()})),li,ui=o((()=>{ci(),li=e=>si(typeof e==`string`?e:ue(e))}));function di(e){return ii(li(e))}var fi=o((()=>{oi(),ui()})),pi,mi=o((()=>{fi(),pi=di})),hi,gi=o((()=>{D(),hi=class extends E{constructor({address:e}){super(`Address "${e}" is invalid.`,{metaMessages:[`- Address must be a hex value of 20 bytes (40 hex characters).`,`- Address must match its checksum counterpart.`],name:`InvalidAddressError`})}}})),_i,vi=o((()=>{_i=class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&(super.delete(e),super.set(e,t)),t}set(e,t){if(super.has(e)&&super.delete(e),super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=super.keys().next().value;e!==void 0&&super.delete(e)}return this}}}));function yi(e,t){if(xi.has(`${e}.${t}`))return xi.get(`${e}.${t}`);let n=t?`${t}${e.toLowerCase()}`:e.substring(2).toLowerCase(),r=ni(Bn(n),`bytes`),i=(t?n.substring(`${t}0x`.length):n).split(``);for(let e=0;e<40;e+=2)r[e>>1]>>4>=8&&i[e]&&(i[e]=i[e].toUpperCase()),(r[e>>1]&15)>=8&&i[e+1]&&(i[e+1]=i[e+1].toUpperCase());let a=`0x${i.join(``)}`;return xi.set(`${e}.${t}`,a),a}function bi(e,t){if(!Ci(e,{strict:!1}))throw new hi({address:e});return yi(e,t)}var xi,Si=o((()=>{gi(),Un(),ri(),vi(),Ei(),xi=new _i(8192)}));function Ci(e,t){let{strict:n=!0}=t??{},r=`${e}.${n}`;if(Ti.has(r))return Ti.get(r);let i=wi.test(e)?e.toLowerCase()===e?!0:n?yi(e)===e:!0:!1;return Ti.set(r,i),i}var wi,Ti,Ei=o((()=>{vi(),Si(),wi=/^0x[a-fA-F0-9]{40}$/,Ti=new _i(8192)}));function Di(e){return typeof e[0]==`string`?ki(e):Oi(e)}function Oi(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}function ki(e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}var Ai=o((()=>{}));function ji(e,t,n,{strict:r}={}){return Pt(e,{strict:!1})?Fi(e,t,n,{strict:r}):Pi(e,t,n,{strict:r})}function Mi(e,t){if(typeof t==`number`&&t>0&&t>It(e)-1)throw new ln({offset:t,position:`start`,size:It(e)})}function Ni(e,t,n){if(typeof t==`number`&&typeof n==`number`&&It(e)!==n-t)throw new ln({offset:n,position:`end`,size:It(e)})}function Pi(e,t,n,{strict:r}={}){Mi(e,t);let i=e.slice(t,n);return r&&Ni(i,t,n),i}function Fi(e,t,n,{strict:r}={}){Mi(e,t);let i=`0x${e.replace(`0x`,``).slice((t??0)*2,(n??e.length)*2)}`;return r&&Ni(i,t,n),i}var Ii=o((()=>{fn(),Ft(),Lt()})),Li,Ri,zi=o((()=>{Li=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,Ri=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/}));function Bi(e,t){if(e.length!==t.length)throw new Jt({expectedLength:e.length,givenLength:t.length});let n=Ui(Vi({params:e,values:t}));return n.length===0?`0x`:n}function Vi({params:e,values:t}){let n=[];for(let r=0;r0?Di([t,e]):t}}if(i)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:Di(a.map(({encoded:e})=>e))}}function Ki(e,{param:t}){let[,n]=t.type.split(`bytes`),r=It(e);if(!n){let t=e;return r%32!=0&&(t=mn(t,{dir:`right`,size:Math.ceil((e.length-2)/2/32)*32})),{dynamic:!0,encoded:Di([mn(O(r,{size:32})),t])}}if(r!==Number.parseInt(n,10))throw new qt({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:mn(e,{dir:`right`})}}function qi(e){if(typeof e!=`boolean`)throw new E(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:mn(An(e))}}function Ji(e,{signed:t,size:n=256}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Zi(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}var Qi=o((()=>{cn(),gi(),D(),xn(),Ei(),Ai(),gn(),Lt(),Ii(),k(),zi()})),$i,ea=o((()=>{Ii(),fi(),$i=e=>ji(di(e),0,4)}));function ta(e){let{abi:t,args:n=[],name:r}=e,i=Pt(r,{strict:!1}),a=t.filter(e=>i?e.type===`function`?$i(e)===r:e.type===`event`?pi(e)===r:!1:`name`in e&&e.name===r);if(a.length===0)return;if(a.length===1)return a[0];let o;for(let e of a)if(`inputs`in e){if(!n||n.length===0){if(!e.inputs||e.inputs.length===0)return e;continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===n.length&&n.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?na(t,r):!1})){if(o&&`inputs`in o&&o.inputs){let t=ra(e.inputs,o.inputs,n);if(t)throw new tn({abiItem:e,type:t[0]},{abiItem:o,type:t[1]})}o=e}}return o||a[0]}function na(e,t){let n=typeof e,r=t.type;switch(r){case`address`:return Ci(e,{strict:!1});case`bool`:return n===`boolean`;case`function`:return n===`string`;case`string`:return n===`string`;default:return r===`tuple`&&`components`in t?Object.values(t.components).every((t,r)=>n===`object`&&na(Object.values(e)[r],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>na(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function ra(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return ra(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&Ci(n[r],{strict:!1}))return o}}var ia=o((()=>{cn(),Ft(),Ei(),mi(),ea()}));function aa(e){return typeof e==`string`?{address:e,type:`json-rpc`}:e}var oa=o((()=>{}));function sa(e){let{abi:t,args:n,functionName:r}=e,i=t[0];if(r){let e=ta({abi:t,args:n,name:r});if(!e)throw new Qt(r,{docsPath:ca});i=e}if(i.type!==`function`)throw new Qt(void 0,{docsPath:ca});return{abi:[i],functionName:$i(At(i))}}var ca,la=o((()=>{cn(),ea(),Nt(),ia(),ca=`/docs/contract/encodeFunctionData`}));function ua(e){let{args:t}=e,{abi:n,functionName:r}=e.abi.length===1&&e.functionName?.startsWith(`0x`)?e:sa(e),i=n[0];return ki([r,(`inputs`in i&&i.inputs?Bi(i.inputs,t??[]):void 0)??`0x`])}var da=o((()=>{Ai(),Qi(),la()})),fa,pa,ma,ha=o((()=>{fa={1:"An `assert` condition failed.",17:`Arithmetic operation resulted in underflow or overflow.`,18:"Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).",33:`Attempted to convert to an invalid type.`,34:`Attempted to access a storage byte array that is incorrectly encoded.`,49:"Performed `.pop()` on an empty array",50:`Array index is out of bounds.`,65:`Allocated too much memory or created an array which is too large.`,81:`Attempted to call a zero-initialized variable of internal function type.`},pa={inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`},ma={inputs:[{name:`reason`,type:`uint256`}],name:`Panic`,type:`error`}})),ga,_a,va,ya=o((()=>{D(),ga=class extends E{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`,{name:`NegativeOffsetError`})}},_a=class extends E{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`,{name:`PositionOutOfBoundsError`})}},va=class extends E{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`,{name:`RecursiveReadLimitExceededError`})}}}));function ba(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(xa);return n.bytes=e,n.dataView=new DataView(e.buffer??e,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var xa,Sa=o((()=>{ya(),xa={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new va({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new _a({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new ga({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new ga({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}}}));function Ca(e,t={}){return t.size!==void 0&&wn(e,{size:t.size}),Tn(jn(e,t),t)}function wa(e,t={}){let n=e;if(t.size!==void 0&&(wn(n,{size:t.size}),n=Sn(n)),n.length>1||n[0]>1)throw new vn(n);return!!n[0]}function Ta(e,t={}){return t.size!==void 0&&wn(e,{size:t.size}),Dn(jn(e,t),t)}function Ea(e,t={}){let n=e;return t.size!==void 0&&(wn(n,{size:t.size}),n=Sn(n,{dir:`right`})),new TextDecoder().decode(n)}var Da=o((()=>{xn(),Cn(),On(),k()}));function Oa(e,t){let n=typeof t==`string`?Rn(t):t,r=ba(n);if(It(n)===0&&e.length>0)throw new Gt;if(It(t)&&It(t)<32)throw new Wt({data:typeof t==`string`?t:jn(t),params:e,size:It(t)});let i=0,a=[];for(let t=0;t48?Ca(i,{signed:n}):Ta(i,{signed:n}),32]}function Fa(e,t,{staticPosition:n}){let r=t.components.length===0||t.components.some(({name:e})=>!e),i=r?[]:{},a=0;if(La(t)){let o=n+Ta(e.readBytes(za));for(let n=0;n{cn(),Si(),Sa(),Lt(),Ii(),Cn(),Da(),Un(),k(),Qi(),Ra=32,za=32}));function Va(e){let{abi:t,data:n,cause:r}=e,i=ji(n,0,4);if(i===`0x`)throw new Gt({cause:r});let a=[...t||[],pa,ma].find(e=>e.type===`error`&&i===$i(At(e)));if(!a)throw new Zt(i,{docsPath:`/docs/contract/decodeErrorResult`,cause:r});return{abiItem:a,args:`inputs`in a&&a.inputs&&a.inputs.length>0?Oa(a.inputs,ji(n,4)):void 0,errorName:a.name}}var Ha=o((()=>{ha(),cn(),Ii(),ea(),Ba(),Nt()})),Ua,Wa=o((()=>{Ua=(e,t,n)=>JSON.stringify(e,(e,n)=>{let r=typeof n==`bigint`?n.toString():n;return typeof t==`function`?t(e,r):r},n)}));function Ga({abiItem:e,args:t,includeFunctionName:n=!0,includeName:r=!1}){if(`name`in e&&`inputs`in e&&e.inputs)return`${n?e.name:``}(${e.inputs.map((e,n)=>`${r&&e.name?`${e.name}: `:``}${typeof t[n]==`object`?Ua(t[n]):t[n]}`).join(`, `)})`}var Ka=o((()=>{Wa()})),qa,Ja,Ya=o((()=>{qa={gwei:9,wei:18},Ja={ether:-9,wei:9}}));function Xa(e,t){let n=e.toString(),r=n.startsWith(`-`);r&&(n=n.slice(1)),n=n.padStart(t,`0`);let[i,a]=[n.slice(0,n.length-t),n.slice(n.length-t)];return a=a.replace(/(0+)$/,``),`${r?`-`:``}${i||`0`}${a?`.${a}`:``}`}var Za=o((()=>{}));function Qa(e,t=`wei`){return Xa(e,qa[t])}var $a=o((()=>{Ya(),Za()}));function eo(e,t=`wei`){return Xa(e,Ja[t])}var to=o((()=>{Ya(),Za()}));function no(e){return e.reduce((e,{slot:t,value:n})=>`${e} ${t}: ${n}\n`,``)}function ro(e){return e.reduce((e,{address:t,...n})=>{let r=`${e} ${t}:\n`;return n.nonce&&(r+=` nonce: ${n.nonce}\n`),n.balance&&(r+=` balance: ${n.balance}\n`),n.code&&(r+=` code: ${n.code}\n`),n.state&&(r+=` state: +`,r+=no(n.state)),n.stateDiff&&(r+=` stateDiff: +`,r+=no(n.stateDiff)),r},` State Override: +`).slice(0,-1)}var io,ao,oo=o((()=>{D(),io=class extends E{constructor({address:e}){super(`State for account "${e}" is set multiple times.`,{name:`AccountStateConflictError`})}},ao=class extends E{constructor(){super(`state and stateDiff are set on the same account.`,{name:`StateAssignmentConflictError`})}}}));function so(e){let t=Object.entries(e).map(([e,t])=>t===void 0||t===!1?null:[e,t]).filter(Boolean),n=t.reduce((e,[t])=>Math.max(e,t.length),0);return t.map(([e,t])=>` ${`${e}:`.padEnd(n+1)} ${t}`).join(` +`)}var co,lo,uo,fo,po,mo,ho,go,_o=o((()=>{$a(),to(),D(),co=class extends E{constructor({v:e}){super(`Invalid \`v\` value "${e}". Expected 27 or 28.`,{name:`InvalidLegacyVError`})}},lo=class extends E{constructor({transaction:e}){super(`Cannot infer a transaction type from provided transaction.`,{metaMessages:[`Provided Transaction:`,`{`,so(e),`}`,``,`To infer the type, either provide:`,"- a `type` to the Transaction, or","- an EIP-1559 Transaction with `maxFeePerGas`, or","- an EIP-2930 Transaction with `gasPrice` & `accessList`, or","- an EIP-4844 Transaction with `blobs`, `blobVersionedHashes`, `sidecars`, or","- an EIP-7702 Transaction with `authorizationList`, or","- a Legacy Transaction with `gasPrice`"],name:`InvalidSerializableTransactionError`})}},uo=class extends E{constructor({storageKey:e}){super(`Size for storage key "${e}" is invalid. Expected 32 bytes. Got ${Math.floor((e.length-2)/2)} bytes.`,{name:`InvalidStorageKeySizeError`})}},fo=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=so({chain:r&&`${r?.name} (id: ${r?.id})`,from:t?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Request Arguments:`,f].filter(Boolean),name:`TransactionExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},po=class extends E{constructor({blockHash:e,blockNumber:t,blockTag:n,hash:r,index:i}){let a=`Transaction`;n&&i!==void 0&&(a=`Transaction at block time "${n}" at index "${i}"`),e&&i!==void 0&&(a=`Transaction at block hash "${e}" at index "${i}"`),t&&i!==void 0&&(a=`Transaction at block number "${t}" at index "${i}"`),r&&(a=`Transaction with hash "${r}"`),super(`${a} could not be found.`,{name:`TransactionNotFoundError`})}},mo=class extends E{constructor({hash:e}){super(`Transaction receipt with hash "${e}" could not be found. The Transaction may not be processed on a block yet.`,{name:`TransactionReceiptNotFoundError`})}},ho=class extends E{constructor({receipt:e}){super(`Transaction with hash "${e.transactionHash}" reverted.`,{metaMessages:[`The receipt marked the transaction as "reverted". This could mean that the function on the contract you are trying to call threw an error.`,` `,`You can attempt to extract the revert reason by:`,"- calling the `simulateContract` or `simulateCalls` Action with the `abi` and `functionName` of the contract","- using the `call` Action with raw `data`"],name:`TransactionReceiptRevertedError`}),Object.defineProperty(this,`receipt`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.receipt=e}},go=class extends E{constructor({hash:e}){super(`Timed out while waiting for transaction with hash "${e}" to be confirmed.`,{name:`WaitForTransactionReceiptTimeoutError`})}}})),vo,yo,bo=o((()=>{vo=e=>e,yo=e=>e})),xo,So,Co,A,wo,To,Eo=o((()=>{oa(),ha(),Ha(),Nt(),Ka(),ia(),$a(),to(),cn(),D(),oo(),_o(),bo(),xo=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d,stateOverride:f}){let p=so({from:(t?aa(t):void 0)?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});f&&(p+=`\n${ro(f)}`),super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Raw Call Arguments:`,p].filter(Boolean),name:`CallExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},So=class extends E{constructor(e,{abi:t,args:n,contractAddress:r,docsPath:i,functionName:a,sender:o}){let s=ta({abi:t,args:n,name:a}),c=s?Ga({abiItem:s,args:n,includeFunctionName:!1,includeName:!1}):void 0,l=s?At(s,{includeName:!0}):void 0,u=so({address:r&&vo(r),function:l,args:c&&c!==`()`&&`${[...Array(a?.length??0).keys()].map(()=>` `).join(``)}${c}`,sender:o});super(e.shortMessage||`An unknown error occurred while executing the contract function "${a}".`,{cause:e,docsPath:i,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],u&&`Contract Call:`,u].filter(Boolean),name:`ContractFunctionExecutionError`}),Object.defineProperty(this,`abi`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`args`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`contractAddress`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`formattedArgs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`functionName`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`sender`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abi=t,this.args=n,this.cause=e,this.contractAddress=r,this.functionName=a,this.sender=o}},Co=class extends E{constructor({abi:e,data:t,functionName:n,message:r,cause:i}){let a,o,s,c;if(t&&t!==`0x`)try{o=Va({abi:e,data:t,cause:i});let{abiItem:n,errorName:r,args:a}=o;if(r===`Error`)c=a[0];else if(r===`Panic`){let[e]=a;c=fa[e]}else{let e=n?At(n,{includeName:!0}):void 0,t=n&&a?Ga({abiItem:n,args:a,includeFunctionName:!1,includeName:!1}):void 0;s=[e?`Error: ${e}`:``,t&&t!==`()`?` ${[...Array(r?.length??0).keys()].map(()=>` `).join(``)}${t}`:``]}}catch(e){a=e}else r&&(c=r);let l;a instanceof Zt&&(l=a.signature,s=[`Unable to decode signature "${l}" as it was not found on the provided ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${l}.`]),super(c&&c!==`execution reverted`||l?[`The contract function "${n}" reverted with the following ${l?`signature`:`reason`}:`,c||l].join(` +`):`The contract function "${n}" reverted.`,{cause:a??i,metaMessages:s,name:`ContractFunctionRevertedError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`raw`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`reason`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=o,this.raw=t,this.reason=c,this.signature=l}},A=class extends E{constructor({functionName:e,cause:t}){super(`The contract function "${e}" returned no data ("0x").`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function "${e}",`,` - The parameters passed to the contract function may be invalid, or`,` - The address is not a contract.`],name:`ContractFunctionZeroDataError`,cause:t})}},wo=class extends E{constructor({factory:e}){super(`Deployment for counterfactual contract call failed${e?` for factory "${e}".`:``}`,{metaMessages:[`Please ensure:`,"- The `factory` is a valid contract deployment factory (ie. Create2 Factory, ERC-4337 Factory, etc).","- The `factoryData` is a valid encoded function call for contract deployment function on the factory."],name:`CounterfactualDeploymentFailedError`})}},To=class extends E{constructor({data:e,message:t}){super(t||``,{name:`RawContractError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e}}})),Do,Oo,ko,Ao=o((()=>{Wa(),D(),bo(),Do=class extends E{constructor({body:e,cause:t,details:n,headers:r,status:i,url:a}){super(`HTTP request failed.`,{cause:t,details:n,metaMessages:[i&&`Status: ${i}`,`URL: ${yo(a)}`,e&&`Request body: ${Ua(e)}`].filter(Boolean),name:`HttpRequestError`}),Object.defineProperty(this,`body`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`headers`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`status`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.body=e,this.headers=r,this.status=i,this.url=a}},Oo=class extends E{constructor({body:e,error:t,url:n}){super(`RPC Request failed.`,{cause:t,details:t.message,metaMessages:[`URL: ${yo(n)}`,`Request body: ${Ua(e)}`],name:`RpcRequestError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=t.code,this.data=t.data,this.url=n}},ko=class extends E{constructor({body:e,url:t}){super(`The request took too long to respond.`,{details:`The request timed out.`,metaMessages:[`URL: ${yo(t)}`,`Request body: ${Ua(e)}`],name:`TimeoutError`}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.url=t}}})),jo,Mo,No,Po,Fo,Io,Lo,Ro,zo,Bo,Vo,Ho,Uo,Wo,Go,Ko,qo,Jo,Yo,Xo,Zo,Qo,$o,es,ts,ns,rs,is,as,os,ss=o((()=>{D(),Ao(),jo=-1,Mo=class extends E{constructor(e,{code:t,docsPath:n,metaMessages:r,name:i,shortMessage:a}){super(a,{cause:e,docsPath:n,metaMessages:r||e?.metaMessages,name:i||`RpcError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name=i||e.name,this.code=e instanceof Oo?e.code:t??jo}},No=class extends Mo{constructor(e,t){super(e,t),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=t.data}},Po=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ParseRpcError`,shortMessage:`Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.`})}},Object.defineProperty(Po,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700}),Fo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidRequestRpcError`,shortMessage:`JSON is not a valid request object.`})}},Object.defineProperty(Fo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600}),Io=class e extends Mo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotFoundRpcError`,shortMessage:`The method${n?` "${n}"`:``} does not exist / is not available.`})}},Object.defineProperty(Io,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601}),Lo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidParamsRpcError`,shortMessage:[`Invalid parameters were provided to the RPC method.`,`Double check you have provided the correct parameters.`].join(` +`)})}},Object.defineProperty(Lo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602}),Ro=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InternalRpcError`,shortMessage:`An internal error was received.`})}},Object.defineProperty(Ro,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603}),zo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidInputRpcError`,shortMessage:[`Missing or invalid parameters.`,`Double check you have provided the correct parameters.`].join(` +`)})}},Object.defineProperty(zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3}),Bo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ResourceNotFoundRpcError`,shortMessage:`Requested resource not found.`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`ResourceNotFoundRpcError`})}},Object.defineProperty(Bo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001}),Vo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ResourceUnavailableRpcError`,shortMessage:`Requested resource not available.`})}},Object.defineProperty(Vo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002}),Ho=class e extends Mo{constructor(t){super(t,{code:e.code,name:`TransactionRejectedRpcError`,shortMessage:`Transaction creation failed.`})}},Object.defineProperty(Ho,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003}),Uo=class e extends Mo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotSupportedRpcError`,shortMessage:`Method${n?` "${n}"`:``} is not supported.`})}},Object.defineProperty(Uo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004}),Wo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`LimitExceededRpcError`,shortMessage:`Request exceeds defined limit.`})}},Object.defineProperty(Wo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005}),Go=class e extends Mo{constructor(t){super(t,{code:e.code,name:`JsonRpcVersionUnsupportedError`,shortMessage:`Version of JSON-RPC protocol is not supported.`})}},Object.defineProperty(Go,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006}),Ko=class e extends No{constructor(t){super(t,{code:e.code,name:`UserRejectedRequestError`,shortMessage:`User rejected the request.`})}},Object.defineProperty(Ko,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001}),qo=class e extends No{constructor(t){super(t,{code:e.code,name:`UnauthorizedProviderError`,shortMessage:`The requested method and/or account has not been authorized by the user.`})}},Object.defineProperty(qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100}),Jo=class e extends No{constructor(t,{method:n}={}){super(t,{code:e.code,name:`UnsupportedProviderMethodError`,shortMessage:`The Provider does not support the requested method${n?` " ${n}"`:``}.`})}},Object.defineProperty(Jo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200}),Yo=class e extends No{constructor(t){super(t,{code:e.code,name:`ProviderDisconnectedError`,shortMessage:`The Provider is disconnected from all chains.`})}},Object.defineProperty(Yo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900}),Xo=class e extends No{constructor(t){super(t,{code:e.code,name:`ChainDisconnectedError`,shortMessage:`The Provider is not connected to the requested chain.`})}},Object.defineProperty(Xo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901}),Zo=class e extends No{constructor(t){super(t,{code:e.code,name:`SwitchChainError`,shortMessage:`An error occurred when attempting to switch chain.`})}},Object.defineProperty(Zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902}),Qo=class e extends No{constructor(t){super(t,{code:e.code,name:`UnsupportedNonOptionalCapabilityError`,shortMessage:`This Wallet does not support a capability that was not marked as optional.`})}},Object.defineProperty(Qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700}),$o=class e extends No{constructor(t){super(t,{code:e.code,name:`UnsupportedChainIdError`,shortMessage:`This Wallet does not support the requested chain ID.`})}},Object.defineProperty($o,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710}),es=class e extends No{constructor(t){super(t,{code:e.code,name:`DuplicateIdError`,shortMessage:`There is already a bundle submitted with this ID.`})}},Object.defineProperty(es,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720}),ts=class e extends No{constructor(t){super(t,{code:e.code,name:`UnknownBundleIdError`,shortMessage:`This bundle id is unknown / has not been submitted`})}},Object.defineProperty(ts,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730}),ns=class e extends No{constructor(t){super(t,{code:e.code,name:`BundleTooLargeError`,shortMessage:`The call bundle is too large for the Wallet to process.`})}},Object.defineProperty(ns,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740}),rs=class e extends No{constructor(t){super(t,{code:e.code,name:`AtomicReadyWalletRejectedUpgradeError`,shortMessage:`The Wallet can support atomicity after an upgrade, but the user rejected the upgrade.`})}},Object.defineProperty(rs,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750}),is=class e extends No{constructor(t){super(t,{code:e.code,name:`AtomicityNotSupportedError`,shortMessage:`The wallet does not support atomic execution but the request requires it.`})}},Object.defineProperty(is,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760}),as=class e extends No{constructor(t){super(t,{code:e.code,name:`WalletConnectSessionSettlementError`,shortMessage:`WalletConnect session settlement failed.`})}},Object.defineProperty(as,`code`,{enumerable:!0,configurable:!0,writable:!0,value:7e3}),os=class extends Mo{constructor(e){super(e,{name:`UnknownRpcError`,shortMessage:`An unknown RPC error occurred.`})}}}));da(),cn(),D(),Eo(),Ao(),ss();var cs=3;function ls(e,{abi:t,address:n,args:r,docsPath:i,functionName:a,sender:o}){let s=e instanceof To?e:e instanceof E?e.walk(e=>`data`in e)||e.walk():{},{code:c,data:l,details:u,message:d,shortMessage:f}=s;return new So(e instanceof Gt?new A({functionName:a,cause:e}):[cs,Ro.code].includes(c)&&(l||u||d||f)||c===zo.code&&u===`execution reverted`&&l?new Co({abi:t,data:typeof l==`object`?l.data:l,functionName:a,message:s instanceof Oo?u:f??d,cause:e}):e,{abi:t,args:r,contractAddress:n,docsPath:i,functionName:a,sender:o})}Si(),ri();function us(e){return yi(`0x${ni(`0x${e.substring(4)}`).substring(26)}`)}var ds,fs,ps,ms,hs=o((()=>{ds=(function(){let e=typeof document<`u`&&document.createElement(`link`).relList;return e&&e.supports&&e.supports(`modulepreload`)?`modulepreload`:`preload`})(),fs=function(e){return`/`+e},ps={},ms=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=fs(t,n),t in ps)return;ps[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:ds,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})}}));function gs(e,t,n,r){if(typeof e.setBigUint64==`function`)return e.setBigUint64(t,n,r);let i=BigInt(32),a=BigInt(4294967295),o=Number(n>>i&a),s=Number(n&a),c=r?4:0,l=r?0:4;e.setUint32(t+c,o,r),e.setUint32(t+l,s,r)}function _s(e,t,n){return e&t^~e&n}function vs(e,t,n){return e&t^e&n^t&n}var ys,bs,xs,Ss,Cs=o((()=>{Fr(),ys=class extends Pr{constructor(e,t,n,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=t,this.padOffset=n,this.isLE=r,this.buffer=new Uint8Array(e),this.view=Sr(this.buffer)}update(e){vr(this),e=Dr(e),gr(e);let{view:t,buffer:n,blockLen:r}=this,i=e.length;for(let a=0;ar-a&&(this.process(n,0),a=0);for(let e=a;el.length)throw Error(`_sha2: outputLen bigger than state`);for(let e=0;e{Cs(),dr(),Fr(),ws=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),Ts=new Uint32Array(64),Es=class extends ys{constructor(e=32){super(64,e,8,!1),this.A=bs[0]|0,this.B=bs[1]|0,this.C=bs[2]|0,this.D=bs[3]|0,this.E=bs[4]|0,this.F=bs[5]|0,this.G=bs[6]|0,this.H=bs[7]|0}get(){let{A:e,B:t,C:n,D:r,E:i,F:a,G:o,H:s}=this;return[e,t,n,r,i,a,o,s]}set(e,t,n,r,i,a,o,s){this.A=e|0,this.B=t|0,this.C=n|0,this.D=r|0,this.E=i|0,this.F=a|0,this.G=o|0,this.H=s|0}process(e,t){for(let n=0;n<16;n++,t+=4)Ts[n]=e.getUint32(t,!1);for(let e=16;e<64;e++){let t=Ts[e-15],n=Ts[e-2],r=Cr(t,7)^Cr(t,18)^t>>>3;Ts[e]=(Cr(n,17)^Cr(n,19)^n>>>10)+Ts[e-7]+r+Ts[e-16]|0}let{A:n,B:r,C:i,D:a,E:o,F:s,G:c,H:l}=this;for(let e=0;e<64;e++){let t=Cr(o,6)^Cr(o,11)^Cr(o,25),u=l+t+_s(o,s,c)+ws[e]+Ts[e]|0,d=(Cr(n,2)^Cr(n,13)^Cr(n,22))+vs(n,r,i)|0;l=c,c=s,s=o,o=a+u|0,a=i,i=r,r=n,n=u+d|0}n=n+this.A|0,r=r+this.B|0,i=i+this.C|0,a=a+this.D|0,o=o+this.E|0,s=s+this.F|0,c=c+this.G|0,l=l+this.H|0,this.set(n,r,i,a,o,s,c,l)}roundClean(){xr(Ts)}destroy(){this.set(0,0,0,0,0,0,0,0),xr(this.buffer)}},Ds=Gn(`0x428a2f98d728ae22.0x7137449123ef65cd.0xb5c0fbcfec4d3b2f.0xe9b5dba58189dbbc.0x3956c25bf348b538.0x59f111f1b605d019.0x923f82a4af194f9b.0xab1c5ed5da6d8118.0xd807aa98a3030242.0x12835b0145706fbe.0x243185be4ee4b28c.0x550c7dc3d5ffb4e2.0x72be5d74f27b896f.0x80deb1fe3b1696b1.0x9bdc06a725c71235.0xc19bf174cf692694.0xe49b69c19ef14ad2.0xefbe4786384f25e3.0x0fc19dc68b8cd5b5.0x240ca1cc77ac9c65.0x2de92c6f592b0275.0x4a7484aa6ea6e483.0x5cb0a9dcbd41fbd4.0x76f988da831153b5.0x983e5152ee66dfab.0xa831c66d2db43210.0xb00327c898fb213f.0xbf597fc7beef0ee4.0xc6e00bf33da88fc2.0xd5a79147930aa725.0x06ca6351e003826f.0x142929670a0e6e70.0x27b70a8546d22ffc.0x2e1b21385c26c926.0x4d2c6dfc5ac42aed.0x53380d139d95b3df.0x650a73548baf63de.0x766a0abb3c77b2a8.0x81c2c92e47edaee6.0x92722c851482353b.0xa2bfe8a14cf10364.0xa81a664bbc423001.0xc24b8b70d0f89791.0xc76c51a30654be30.0xd192e819d6ef5218.0xd69906245565a910.0xf40e35855771202a.0x106aa07032bbd1b8.0x19a4c116b8d2d0c8.0x1e376c085141ab53.0x2748774cdf8eeb99.0x34b0bcb5e19b48a8.0x391c0cb3c5c95a63.0x4ed8aa4ae3418acb.0x5b9cca4f7763e373.0x682e6ff3d6b2b8a3.0x748f82ee5defb2fc.0x78a5636f43172f60.0x84c87814a1f0ab72.0x8cc702081a6439ec.0x90befffa23631e28.0xa4506cebde82bde9.0xbef9a3f7b2c67915.0xc67178f2e372532b.0xca273eceea26619c.0xd186b8c721c0c207.0xeada7dd6cde0eb1e.0xf57d4f7fee6ed178.0x06f067aa72176fba.0x0a637dc5a2c898a6.0x113f9804bef90dae.0x1b710b35131c471b.0x28db77f523047d84.0x32caab7b40c72493.0x3c9ebe0a15c9bebc.0x431d67c49c100d4c.0x4cc5d4becb3e42b6.0x597f299cfc657e2a.0x5fcb6fab3ad6faec.0x6c44198c4a475817`.split(`.`).map(e=>BigInt(e))),Os=Ds[0],ks=Ds[1],As=new Uint32Array(80),js=new Uint32Array(80),Ms=class extends ys{constructor(e=64){super(128,e,16,!1),this.Ah=Ss[0]|0,this.Al=Ss[1]|0,this.Bh=Ss[2]|0,this.Bl=Ss[3]|0,this.Ch=Ss[4]|0,this.Cl=Ss[5]|0,this.Dh=Ss[6]|0,this.Dl=Ss[7]|0,this.Eh=Ss[8]|0,this.El=Ss[9]|0,this.Fh=Ss[10]|0,this.Fl=Ss[11]|0,this.Gh=Ss[12]|0,this.Gl=Ss[13]|0,this.Hh=Ss[14]|0,this.Hl=Ss[15]|0}get(){let{Ah:e,Al:t,Bh:n,Bl:r,Ch:i,Cl:a,Dh:o,Dl:s,Eh:c,El:l,Fh:u,Fl:d,Gh:f,Gl:p,Hh:m,Hl:h}=this;return[e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h]}set(e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h){this.Ah=e|0,this.Al=t|0,this.Bh=n|0,this.Bl=r|0,this.Ch=i|0,this.Cl=a|0,this.Dh=o|0,this.Dl=s|0,this.Eh=c|0,this.El=l|0,this.Fh=u|0,this.Fl=d|0,this.Gh=f|0,this.Gl=p|0,this.Hh=m|0,this.Hl=h|0}process(e,t){for(let n=0;n<16;n++,t+=4)As[n]=e.getUint32(t),js[n]=e.getUint32(t+=4);for(let e=16;e<80;e++){let t=As[e-15]|0,n=js[e-15]|0,r=Zn(t,n,1)^Zn(t,n,8)^Yn(t,n,7),i=Qn(t,n,1)^Qn(t,n,8)^Xn(t,n,7),a=As[e-2]|0,o=js[e-2]|0,s=Zn(a,o,19)^$n(a,o,61)^Yn(a,o,6),c=Qn(a,o,19)^er(a,o,61)^Xn(a,o,6),l=sr(i,c,js[e-7],js[e-16]);As[e]=cr(l,r,s,As[e-7],As[e-16])|0,js[e]=l|0}let{Ah:n,Al:r,Bh:i,Bl:a,Ch:o,Cl:s,Dh:c,Dl:l,Eh:u,El:d,Fh:f,Fl:p,Gh:m,Gl:h,Hh:g,Hl:_}=this;for(let e=0;e<80;e++){let t=Zn(u,d,14)^Zn(u,d,18)^$n(u,d,41),v=Qn(u,d,14)^Qn(u,d,18)^er(u,d,41),y=u&f^~u&m,b=d&p^~d&h,x=lr(_,v,b,ks[e],js[e]),S=ur(x,g,t,y,Os[e],As[e]),ee=x|0,C=Zn(n,r,28)^$n(n,r,34)^$n(n,r,39),te=Qn(n,r,28)^er(n,r,34)^er(n,r,39),ne=n&i^n&o^i&o,re=r&a^r&s^a&s;g=m|0,_=h|0,m=f|0,h=p|0,f=u|0,p=d|0,{h:u,l:d}=Kn(c|0,l|0,S|0,ee|0),c=o|0,l=s|0,o=i|0,s=a|0,i=n|0,a=r|0;let ie=ar(ee,te,re);n=or(ie,S,C,ne),r=ie|0}({h:n,l:r}=Kn(this.Ah|0,this.Al|0,n|0,r|0)),{h:i,l:a}=Kn(this.Bh|0,this.Bl|0,i|0,a|0),{h:o,l:s}=Kn(this.Ch|0,this.Cl|0,o|0,s|0),{h:c,l}=Kn(this.Dh|0,this.Dl|0,c|0,l|0),{h:u,l:d}=Kn(this.Eh|0,this.El|0,u|0,d|0),{h:f,l:p}=Kn(this.Fh|0,this.Fl|0,f|0,p|0),{h:m,l:h}=Kn(this.Gh|0,this.Gl|0,m|0,h|0),{h:g,l:_}=Kn(this.Hh|0,this.Hl|0,g|0,_|0),this.set(n,r,i,a,o,s,c,l,u,d,f,p,m,h,g,_)}roundClean(){xr(As,js)}destroy(){xr(this.buffer),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}},Ns=class extends Ms{constructor(){super(48),this.Ah=xs[0]|0,this.Al=xs[1]|0,this.Bh=xs[2]|0,this.Bl=xs[3]|0,this.Ch=xs[4]|0,this.Cl=xs[5]|0,this.Dh=xs[6]|0,this.Dl=xs[7]|0,this.Eh=xs[8]|0,this.El=xs[9]|0,this.Fh=xs[10]|0,this.Fl=xs[11]|0,this.Gh=xs[12]|0,this.Gl=xs[13]|0,this.Hh=xs[14]|0,this.Hl=xs[15]|0}},Ps=kr(()=>new Es),Fs=kr(()=>new Ms),Is=kr(()=>new Ns)})),Rs,zs,Bs=o((()=>{Fr(),Rs=class extends Pr{constructor(e,t){super(),this.finished=!1,this.destroyed=!1,_r(e);let n=Dr(t);if(this.iHash=e.create(),typeof this.iHash.update!=`function`)throw Error(`Expected instance of class which extends utils.Hash`);this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,i=new Uint8Array(r);i.set(n.length>r?e.create().update(n).digest():n);for(let e=0;enew Rs(e,t).update(n).digest(),zs.create=(e,t)=>new Rs(e,t)}));function Vs(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function Hs(e){if(!Vs(e))throw Error(`Uint8Array expected`)}function Us(e,t){if(typeof t!=`boolean`)throw Error(e+` boolean expected, got `+t)}function Ws(e){let t=e.toString(16);return t.length&1?`0`+t:t}function Gs(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);return e===``?lc:BigInt(`0x`+e)}function Ks(e){if(Hs(e),dc)return e.toHex();let t=``;for(let n=0;n=pc._0&&e<=pc._9)return e-pc._0;if(e>=pc.A&&e<=pc.F)return e-(pc.A-10);if(e>=pc.a&&e<=pc.f)return e-(pc.a-10)}function Js(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);if(dc)return Uint8Array.fromHex(e);let t=e.length,n=t/2;if(t%2)throw Error(`hex string expected, got unpadded hex of length `+t);let r=new Uint8Array(n);for(let t=0,i=0;tlc;e>>=uc,t+=1);return t}function oc(e,t,n){if(typeof e!=`number`||e<2)throw Error(`hashLen must be a number`);if(typeof t!=`number`||t<2)throw Error(`qByteLen must be a number`);if(typeof n!=`function`)throw Error(`hmacFn must be a function`);let r=gc(e),i=gc(e),a=0,o=()=>{r.fill(1),i.fill(0),a=0},s=(...e)=>n(i,r,...e),c=(e=gc(0))=>{i=s(_c([0]),e),r=s(),e.length!==0&&(i=s(_c([1]),e),r=s())},l=()=>{if(a++>=1e3)throw Error(`drbg: tried 1000 values`);let e=0,n=[];for(;e{o(),c(e);let n;for(;!(n=t(l()));)c();return o(),n}}function sc(e,t,n={}){let r=(t,n,r)=>{let i=vc[n];if(typeof i!=`function`)throw Error(`invalid validator function`);let a=e[t];if(!(r&&a===void 0)&&!i(a,e))throw Error(`param `+String(t)+` is invalid. Expected `+n+`, got `+a)};for(let[e,n]of Object.entries(t))r(e,n,!1);for(let[e,t]of Object.entries(n))r(e,t,!0);return e}function cc(e){let t=new WeakMap;return(n,...r)=>{let i=t.get(n);if(i!==void 0)return i;let a=e(n,...r);return t.set(n,a),a}}var lc,uc,dc,fc,pc,mc,hc,gc,_c,vc,yc=o((()=>{lc=BigInt(0),uc=BigInt(1),dc=typeof Uint8Array.from([]).toHex==`function`&&typeof Uint8Array.fromHex==`function`,fc=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),pc={_0:48,_9:57,A:65,F:70,a:97,f:102},mc=e=>typeof e==`bigint`&&lc<=e,hc=e=>(uc<new Uint8Array(e),_c=e=>Uint8Array.from(e),vc={bigint:e=>typeof e==`bigint`,function:e=>typeof e==`function`,boolean:e=>typeof e==`boolean`,string:e=>typeof e==`string`,stringOrUint8Array:e=>typeof e==`string`||Vs(e),isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>typeof e==`function`&&Number.isSafeInteger(e.outputLen)}}));function bc(e,t){let n=e%t;return n>=Ic?n:t+n}function xc(e,t,n){let r=e;for(;t-- >Ic;)r*=r,r%=n;return r}function Sc(e,t){if(e===Ic)throw Error(`invert: expected non-zero number`);if(t<=Ic)throw Error(`invert: expected positive modulus, got `+t);let n=bc(e,t),r=t,i=Ic,a=Lc,o=Lc,s=Ic;for(;n!==Ic;){let e=r/n,t=r%n,c=i-o*e,l=a-s*e;r=n,n=t,i=o,a=s,o=c,s=l}if(r!==Lc)throw Error(`invert: does not exist`);return bc(i,t)}function Cc(e,t){let n=(e.ORDER+Lc)/Bc,r=e.pow(t,n);if(!e.eql(e.sqr(r),t))throw Error(`Cannot find square root`);return r}function wc(e,t){let n=(e.ORDER-Vc)/Hc,r=e.mul(t,Rc),i=e.pow(r,n),a=e.mul(t,i),o=e.mul(e.mul(a,Rc),i),s=e.mul(a,e.sub(o,e.ONE));if(!e.eql(e.sqr(s),t))throw Error(`Cannot find square root`);return s}function Tc(e){if(e1e3)throw Error(`Cannot find square root: probably non-prime P`);if(n===1)return Cc;let a=i.pow(r,t),o=(t+Lc)/Rc;return function(e,r){if(e.is0(r))return r;if(Ac(e,r)!==1)throw Error(`Cannot find square root`);let i=n,s=e.mul(e.ONE,a),c=e.pow(r,t),l=e.pow(r,o);for(;!e.eql(c,e.ONE);){if(e.is0(c))return e.ZERO;let t=1,n=e.sqr(c);for(;!e.eql(n,e.ONE);)if(t++,n=e.sqr(n),t===i)throw Error(`Cannot find square root`);let r=Lc<(e[t]=`function`,e),{ORDER:`bigint`,MASK:`bigint`,BYTES:`isSafeInteger`,BITS:`isSafeInteger`}))}function Oc(e,t,n){if(nIc;)n&Lc&&(r=e.mul(r,i)),i=e.sqr(i),n>>=Lc;return r}function kc(e,t,n=!1){let r=Array(t.length).fill(n?e.ZERO:void 0),i=t.reduce((t,n,i)=>e.is0(n)?t:(r[i]=t,e.mul(t,n)),e.ONE),a=e.inv(i);return t.reduceRight((t,n,i)=>e.is0(n)?t:(r[i]=e.mul(t,r[i]),e.mul(t,n)),a),r}function Ac(e,t){let n=(e.ORDER-Lc)/Rc,r=e.pow(t,n),i=e.eql(r,e.ONE),a=e.eql(r,e.ZERO),o=e.eql(r,e.neg(e.ONE));if(!i&&!a&&!o)throw Error(`invalid Legendre symbol result`);return i?1:a?0:-1}function jc(e,t){t!==void 0&&hr(t);let n=t===void 0?e.toString(2).length:t;return{nBitLength:n,nByteLength:Math.ceil(n/8)}}function Mc(e,t,n=!1,r={}){if(e<=Ic)throw Error(`invalid field: expected ORDER > 0, got `+e);let{nBitLength:i,nByteLength:a}=jc(e,t);if(a>2048)throw Error(`invalid field: expected ORDER of <= 2048 bytes`);let o,s=Object.freeze({ORDER:e,isLE:n,BITS:i,BYTES:a,MASK:hc(i),ZERO:Ic,ONE:Lc,create:t=>bc(t,e),isValid:t=>{if(typeof t!=`bigint`)throw Error(`invalid field element: expected bigint, got `+typeof t);return Ic<=t&&te===Ic,isOdd:e=>(e&Lc)===Lc,neg:t=>bc(-t,e),eql:(e,t)=>e===t,sqr:t=>bc(t*t,e),add:(t,n)=>bc(t+n,e),sub:(t,n)=>bc(t-n,e),mul:(t,n)=>bc(t*n,e),pow:(e,t)=>Oc(s,e,t),div:(t,n)=>bc(t*Sc(n,e),e),sqrN:e=>e*e,addN:(e,t)=>e+t,subN:(e,t)=>e-t,mulN:(e,t)=>e*t,inv:t=>Sc(t,e),sqrt:r.sqrt||(t=>(o||=Ec(e),o(s,t))),toBytes:e=>n?Qs(e,a):Zs(e,a),fromBytes:e=>{if(e.length!==a)throw Error(`Field.fromBytes: expected `+a+` bytes, got `+e.length);return n?Xs(e):Ys(e)},invertBatch:e=>kc(s,e),cmov:(e,t,n)=>n?t:e});return Object.freeze(s)}function Nc(e){if(typeof e!=`bigint`)throw Error(`field order must be bigint`);let t=e.toString(2).length;return Math.ceil(t/8)}function Pc(e){let t=Nc(e);return t+Math.ceil(t/2)}function Fc(e,t,n=!1){let r=e.length,i=Nc(t),a=Pc(t);if(r<16||r1024)throw Error(`expected `+a+`-1024 bytes of input, got `+r);let o=bc(n?Xs(e):Ys(e),t-Lc)+Lc;return n?Qs(o,i):Zs(o,i)}var Ic,Lc,Rc,zc,Bc,Vc,Hc,Uc,Wc=o((()=>{Fr(),yc(),Ic=BigInt(0),Lc=BigInt(1),Rc=BigInt(2),zc=BigInt(3),Bc=BigInt(4),Vc=BigInt(5),Hc=BigInt(8),Uc=[`create`,`isValid`,`is0`,`neg`,`inv`,`sqrt`,`sqr`,`eql`,`add`,`sub`,`mul`,`pow`,`div`,`addN`,`subN`,`mulN`,`sqrN`]}));function Gc(e,t){let n=t.negate();return e?n:t}function Kc(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw Error(`invalid window size, expected [1..`+t+`], got W=`+e)}function qc(e,t){Kc(e,t);let n=Math.ceil(t/e)+1,r=2**(e-1),i=2**e;return{windows:n,windowSize:r,mask:hc(e),maxNumber:i,shiftBy:BigInt(e)}}function Jc(e,t,n){let{windowSize:r,mask:i,maxNumber:a,shiftBy:o}=n,s=Number(e&i),c=e>>o;s>r&&(s-=a,c+=nl);let l=t*r,u=l+Math.abs(s)-1,d=s===0,f=s<0,p=t%2!=0;return{nextN:c,offset:u,isZero:d,isNeg:f,isNegF:p,offsetF:l}}function Yc(e,t){if(!Array.isArray(e))throw Error(`array expected`);e.forEach((e,n)=>{if(!(e instanceof t))throw Error(`invalid point at index `+n)})}function Xc(e,t){if(!Array.isArray(e))throw Error(`array of scalars expected`);e.forEach((e,n)=>{if(!t.isValid(e))throw Error(`invalid scalar at index `+n)})}function Zc(e){return il.get(e)||1}function Qc(e,t){return{constTimeNegate:Gc,hasPrecomputes(e){return Zc(e)!==1},unsafeLadder(t,n,r=e.ZERO){let i=t;for(;n>tl;)n&nl&&(r=r.add(i)),i=i.double(),n>>=nl;return r},precomputeWindow(e,n){let{windows:r,windowSize:i}=qc(n,t),a=[],o=e,s=o;for(let e=0;e12?c=s-3:s>4?c=s-2:s>0&&(c=2);let l=hc(c),u=Array(Number(l)+1).fill(o),d=Math.floor((t.BITS-1)/c)*c,f=o;for(let e=d;e>=0;e-=c){u.fill(o);for(let t=0;t>BigInt(e)&l);u[a]=u[a].add(n[t])}let t=o;for(let e=u.length-1,n=o;e>0;e--)n=n.add(u[e]),t=t.add(n);if(f=f.add(t),e!==0)for(let e=0;e{Wc(),yc(),tl=BigInt(0),nl=BigInt(1),rl=new WeakMap,il=new WeakMap}));function ol(e){e.lowS!==void 0&&Us(`lowS`,e.lowS),e.prehash!==void 0&&Us(`prehash`,e.prehash)}function sl(e){let t=el(e);sc(t,{a:`field`,b:`field`},{allowInfinityPoint:`boolean`,allowedPrivateKeyLengths:`array`,clearCofactor:`function`,fromBytes:`function`,isTorsionFree:`function`,toBytes:`function`,wrapPrivateKey:`boolean`});let{endo:n,Fp:r,a:i}=t;if(n){if(!r.eql(i,r.ZERO))throw Error(`invalid endo: CURVE.a must be 0`);if(typeof n!=`object`||typeof n.beta!=`bigint`||typeof n.splitScalar!=`function`)throw Error(`invalid endo: expected "beta": bigint and "splitScalar": function`)}return Object.freeze({...t})}function cl(e,t){return Ks(Zs(e,t))}function ll(e){let t=sl(e),{Fp:n}=t,r=Mc(t.n,t.nBitLength),i=t.toBytes||((e,t,r)=>{let i=t.toAffine();return ec(Uint8Array.from([4]),n.toBytes(i.x),n.toBytes(i.y))}),a=t.fromBytes||(e=>{let t=e.subarray(1);return{x:n.fromBytes(t.subarray(0,n.BYTES)),y:n.fromBytes(t.subarray(n.BYTES,2*n.BYTES))}});function o(e){let{a:r,b:i}=t,a=n.sqr(e),o=n.mul(a,e);return n.add(n.add(o,n.mul(e,r)),i)}function s(e,t){let r=n.sqr(t),i=o(e);return n.eql(r,i)}if(!s(t.Gx,t.Gy))throw Error(`bad curve params: generator point`);let c=n.mul(n.pow(t.a,yl),bl),l=n.mul(n.sqr(t.b),BigInt(27));if(n.is0(n.add(c,l)))throw Error(`bad curve params: a or b`);function u(e){return rc(e,_l,t.n)}function d(e){let{allowedPrivateKeyLengths:n,nByteLength:r,wrapPrivateKey:i,n:a}=t;if(n&&typeof e!=`bigint`){if(Vs(e)&&(e=Ks(e)),typeof e!=`string`||!n.includes(e.length))throw Error(`invalid private key`);e=e.padStart(r*2,`0`)}let o;try{o=typeof e==`bigint`?e:Ys($s(`private key`,e,r))}catch{throw Error(`invalid private key, expected hex or `+r+` bytes, got `+typeof e)}return i&&(o=bc(o,a)),ic(`private key`,o,_l,a),o}function f(e){if(!(e instanceof h))throw Error(`ProjectivePoint expected`)}let p=cc((e,t)=>{let{px:r,py:i,pz:a}=e;if(n.eql(a,n.ONE))return{x:r,y:i};let o=e.is0();t??=o?n.ONE:n.inv(a);let s=n.mul(r,t),c=n.mul(i,t),l=n.mul(a,t);if(o)return{x:n.ZERO,y:n.ZERO};if(!n.eql(l,n.ONE))throw Error(`invZ was invalid`);return{x:s,y:c}}),m=cc(e=>{if(e.is0()){if(t.allowInfinityPoint&&!n.is0(e.py))return;throw Error(`bad point: ZERO`)}let{x:r,y:i}=e.toAffine();if(!n.isValid(r)||!n.isValid(i))throw Error(`bad point: x or y not FE`);if(!s(r,i))throw Error(`bad point: equation left != right`);if(!e.isTorsionFree())throw Error(`bad point: not in prime-order subgroup`);return!0});class h{constructor(e,t,r){if(e==null||!n.isValid(e))throw Error(`x required`);if(t==null||!n.isValid(t)||n.is0(t))throw Error(`y required`);if(r==null||!n.isValid(r))throw Error(`z required`);this.px=e,this.py=t,this.pz=r,Object.freeze(this)}static fromAffine(e){let{x:t,y:r}=e||{};if(!e||!n.isValid(t)||!n.isValid(r))throw Error(`invalid affine point`);if(e instanceof h)throw Error(`projective point not allowed`);let i=e=>n.eql(e,n.ZERO);return i(t)&&i(r)?h.ZERO:new h(t,r,n.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(e){let t=kc(n,e.map(e=>e.pz));return e.map((e,n)=>e.toAffine(t[n])).map(h.fromAffine)}static fromHex(e){let t=h.fromAffine(a($s(`pointHex`,e)));return t.assertValidity(),t}static fromPrivateKey(e){return h.BASE.multiply(d(e))}static msm(e,t){return $c(h,r,e,t)}_setWindowSize(e){v.setWindowSize(this,e)}assertValidity(){m(this)}hasEvenY(){let{y:e}=this.toAffine();if(n.isOdd)return!n.isOdd(e);throw Error(`Field doesn't support isOdd`)}equals(e){f(e);let{px:t,py:r,pz:i}=this,{px:a,py:o,pz:s}=e,c=n.eql(n.mul(t,s),n.mul(a,i)),l=n.eql(n.mul(r,s),n.mul(o,i));return c&&l}negate(){return new h(this.px,n.neg(this.py),this.pz)}double(){let{a:e,b:r}=t,i=n.mul(r,yl),{px:a,py:o,pz:s}=this,c=n.ZERO,l=n.ZERO,u=n.ZERO,d=n.mul(a,a),f=n.mul(o,o),p=n.mul(s,s),m=n.mul(a,o);return m=n.add(m,m),u=n.mul(a,s),u=n.add(u,u),c=n.mul(e,u),l=n.mul(i,p),l=n.add(c,l),c=n.sub(f,l),l=n.add(f,l),l=n.mul(c,l),c=n.mul(m,c),u=n.mul(i,u),p=n.mul(e,p),m=n.sub(d,p),m=n.mul(e,m),m=n.add(m,u),u=n.add(d,d),d=n.add(u,d),d=n.add(d,p),d=n.mul(d,m),l=n.add(l,d),p=n.mul(o,s),p=n.add(p,p),d=n.mul(p,m),c=n.sub(c,d),u=n.mul(p,f),u=n.add(u,u),u=n.add(u,u),new h(c,l,u)}add(e){f(e);let{px:r,py:i,pz:a}=this,{px:o,py:s,pz:c}=e,l=n.ZERO,u=n.ZERO,d=n.ZERO,p=t.a,m=n.mul(t.b,yl),g=n.mul(r,o),_=n.mul(i,s),v=n.mul(a,c),y=n.add(r,i),b=n.add(o,s);y=n.mul(y,b),b=n.add(g,_),y=n.sub(y,b),b=n.add(r,a);let x=n.add(o,c);return b=n.mul(b,x),x=n.add(g,v),b=n.sub(b,x),x=n.add(i,a),l=n.add(s,c),x=n.mul(x,l),l=n.add(_,v),x=n.sub(x,l),d=n.mul(p,b),l=n.mul(m,v),d=n.add(l,d),l=n.sub(_,d),d=n.add(_,d),u=n.mul(l,d),_=n.add(g,g),_=n.add(_,g),v=n.mul(p,v),b=n.mul(m,b),_=n.add(_,v),v=n.sub(g,v),v=n.mul(p,v),b=n.add(b,v),g=n.mul(_,b),u=n.add(u,g),g=n.mul(x,b),l=n.mul(y,l),l=n.sub(l,g),g=n.mul(y,_),d=n.mul(x,d),d=n.add(d,g),new h(l,u,d)}subtract(e){return this.add(e.negate())}is0(){return this.equals(h.ZERO)}wNAF(e){return v.wNAFCached(this,e,h.normalizeZ)}multiplyUnsafe(e){let{endo:r,n:i}=t;ic(`scalar`,e,gl,i);let a=h.ZERO;if(e===gl)return a;if(this.is0()||e===_l)return this;if(!r||v.hasPrecomputes(this))return v.wNAFCachedUnsafe(this,e,h.normalizeZ);let{k1neg:o,k1:s,k2neg:c,k2:l}=r.splitScalar(e),u=a,d=a,f=this;for(;s>gl||l>gl;)s&_l&&(u=u.add(f)),l&_l&&(d=d.add(f)),f=f.double(),s>>=_l,l>>=_l;return o&&(u=u.negate()),c&&(d=d.negate()),d=new h(n.mul(d.px,r.beta),d.py,d.pz),u.add(d)}multiply(e){let{endo:r,n:i}=t;ic(`scalar`,e,_l,i);let a,o;if(r){let{k1neg:t,k1:i,k2neg:s,k2:c}=r.splitScalar(e),{p:l,f:u}=this.wNAF(i),{p:d,f}=this.wNAF(c);l=v.constTimeNegate(t,l),d=v.constTimeNegate(s,d),d=new h(n.mul(d.px,r.beta),d.py,d.pz),a=l.add(d),o=u.add(f)}else{let{p:t,f:n}=this.wNAF(e);a=t,o=n}return h.normalizeZ([a,o])[0]}multiplyAndAddUnsafe(e,t,n){let r=h.BASE,i=(e,t)=>t===gl||t===_l||!e.equals(r)?e.multiplyUnsafe(t):e.multiply(t),a=i(this,t).add(i(e,n));return a.is0()?void 0:a}toAffine(e){return p(this,e)}isTorsionFree(){let{h:e,isTorsionFree:n}=t;if(e===_l)return!0;if(n)return n(h,this);throw Error(`isTorsionFree() has not been declared for the elliptic curve`)}clearCofactor(){let{h:e,clearCofactor:n}=t;return e===_l?this:n?n(h,this):this.multiplyUnsafe(t.h)}toRawBytes(e=!0){return Us(`isCompressed`,e),this.assertValidity(),i(h,this,e)}toHex(e=!0){return Us(`isCompressed`,e),Ks(this.toRawBytes(e))}}h.BASE=new h(t.Gx,t.Gy,n.ONE),h.ZERO=new h(n.ZERO,n.ONE,n.ZERO);let{endo:g,nBitLength:_}=t,v=Qc(h,g?Math.ceil(_/2):_);return{CURVE:t,ProjectivePoint:h,normPrivateKeyToScalar:d,weierstrassEquation:o,isWithinCurveOrder:u}}function ul(e){let t=el(e);return sc(t,{hash:`hash`,hmac:`function`,randomBytes:`function`},{bits2int:`function`,bits2int_modN:`function`,lowS:`boolean`}),Object.freeze({lowS:!0,...t})}function dl(e){let t=ul(e),{Fp:n,n:r,nByteLength:i,nBitLength:a}=t,o=n.BYTES+1,s=2*n.BYTES+1;function c(e){return bc(e,r)}function l(e){return Sc(e,r)}let{ProjectivePoint:u,normPrivateKeyToScalar:d,weierstrassEquation:f,isWithinCurveOrder:p}=ll({...t,toBytes(e,t,r){let i=t.toAffine(),a=n.toBytes(i.x),o=ec;return Us(`isCompressed`,r),r?o(Uint8Array.from([t.hasEvenY()?2:3]),a):o(Uint8Array.from([4]),a,n.toBytes(i.y))},fromBytes(e){let t=e.length,r=e[0],i=e.subarray(1);if(t===o&&(r===2||r===3)){let e=Ys(i);if(!rc(e,_l,n.ORDER))throw Error(`Point is not on curve`);let t=f(e),a;try{a=n.sqrt(t)}catch(e){let t=e instanceof Error?`: `+e.message:``;throw Error(`Point is not on curve`+t)}let o=(a&_l)===_l;return(r&1)==1!==o&&(a=n.neg(a)),{x:e,y:a}}else if(t===s&&r===4)return{x:n.fromBytes(i.subarray(0,n.BYTES)),y:n.fromBytes(i.subarray(n.BYTES,2*n.BYTES))};else{let e=o,n=s;throw Error(`invalid Point, expected length of `+e+`, or uncompressed `+n+`, got `+t)}}});function m(e){return e>r>>_l}function h(e){return m(e)?c(-e):e}let g=(e,t,n)=>Ys(e.slice(t,n));class _{constructor(e,t,n){ic(`r`,e,_l,r),ic(`s`,t,_l,r),this.r=e,this.s=t,n!=null&&(this.recovery=n),Object.freeze(this)}static fromCompact(e){let t=i;return e=$s(`compactSignature`,e,t*2),new _(g(e,0,t),g(e,t,2*t))}static fromDER(e){let{r:t,s:n}=hl.toSig($s(`DER`,e));return new _(t,n)}assertValidity(){}addRecoveryBit(e){return new _(this.r,this.s,e)}recoverPublicKey(e){let{r,s:i,recovery:a}=this,o=ee($s(`msgHash`,e));if(a==null||![0,1,2,3].includes(a))throw Error(`recovery id invalid`);let s=a===2||a===3?r+t.n:r;if(s>=n.ORDER)throw Error(`recovery id 2 or 3 invalid`);let d=a&1?`03`:`02`,f=u.fromHex(d+cl(s,n.BYTES)),p=l(s),m=c(-o*p),h=c(i*p),g=u.BASE.multiplyAndAddUnsafe(f,m,h);if(!g)throw Error(`point at infinify`);return g.assertValidity(),g}hasHighS(){return m(this.s)}normalizeS(){return this.hasHighS()?new _(this.r,c(-this.s),this.recovery):this}toDERRawBytes(){return Js(this.toDERHex())}toDERHex(){return hl.hexFromSig(this)}toCompactRawBytes(){return Js(this.toCompactHex())}toCompactHex(){let e=i;return cl(this.r,e)+cl(this.s,e)}}let v={isValidPrivateKey(e){try{return d(e),!0}catch{return!1}},normPrivateKeyToScalar:d,randomPrivateKey:()=>{let e=Pc(t.n);return Fc(t.randomBytes(e),t.n)},precompute(e=8,t=u.BASE){return t._setWindowSize(e),t.multiply(BigInt(3)),t}};function y(e,t=!0){return u.fromPrivateKey(e).toRawBytes(t)}function b(e){if(typeof e==`bigint`)return!1;if(e instanceof u)return!0;let r=$s(`key`,e).length,a=n.BYTES,o=a+1,s=2*a+1;if(!(t.allowedPrivateKeyLengths||i===o))return r===o||r===s}function x(e,t,n=!0){if(b(e)===!0)throw Error(`first arg must be private key`);if(b(t)===!1)throw Error(`second arg must be public key`);return u.fromHex(t).multiply(d(e)).toRawBytes(n)}let S=t.bits2int||function(e){if(e.length>8192)throw Error(`input is too large`);let t=Ys(e),n=e.length*8-a;return n>0?t>>BigInt(n):t},ee=t.bits2int_modN||function(e){return c(S(e))},C=hc(a);function te(e){return ic(`num < 2^`+a,e,gl,C),Zs(e,i)}function ne(e,r,i=re){if([`recovered`,`canonical`].some(e=>e in i))throw Error(`sign() legacy options not supported`);let{hash:a,randomBytes:o}=t,{lowS:s,prehash:f,extraEntropy:g}=i;s??=!0,e=$s(`msgHash`,e),ol(i),f&&(e=$s(`prehashed msgHash`,a(e)));let v=ee(e),y=d(r),b=[te(y),te(v)];if(g!=null&&g!==!1){let e=g===!0?o(n.BYTES):g;b.push($s(`extraEntropy`,e))}let x=ec(...b),C=v;function ne(e){let t=S(e);if(!p(t))return;let n=l(t),r=u.BASE.multiply(t).toAffine(),i=c(r.x);if(i===gl)return;let a=c(n*c(C+i*y));if(a===gl)return;let o=(r.x===i?0:2)|Number(r.y&_l),d=a;return s&&m(a)&&(d=h(a),o^=1),new _(i,d,o)}return{seed:x,k2sig:ne}}let re={lowS:t.lowS,prehash:!1},ie={lowS:t.lowS,prehash:!1};function ae(e,n,r=re){let{seed:i,k2sig:a}=ne(e,n,r),o=t;return oc(o.hash.outputLen,o.nByteLength,o.hmac)(i,a)}u.BASE._setWindowSize(8);function oe(e,n,r,i=ie){let a=e;n=$s(`msgHash`,n),r=$s(`publicKey`,r);let{lowS:o,prehash:s,format:d}=i;if(ol(i),`strict`in i)throw Error(`options.strict was renamed to lowS`);if(d!==void 0&&d!==`compact`&&d!==`der`)throw Error(`format must be compact or der`);let f=typeof a==`string`||Vs(a),p=!f&&!d&&typeof a==`object`&&!!a&&typeof a.r==`bigint`&&typeof a.s==`bigint`;if(!f&&!p)throw Error(`invalid signature, expected Uint8Array, hex string or Signature instance`);let m,h;try{if(p&&(m=new _(a.r,a.s)),f){try{d!==`compact`&&(m=_.fromDER(a))}catch(e){if(!(e instanceof hl.Err))throw e}!m&&d!==`der`&&(m=_.fromCompact(a))}h=u.fromHex(r)}catch{return!1}if(!m||o&&m.hasHighS())return!1;s&&(n=t.hash(n));let{r:g,s:v}=m,y=ee(n),b=l(v),x=c(y*b),S=c(g*b),C=u.BASE.multiplyAndAddUnsafe(h,x,S)?.toAffine();return C?c(C.x)===g:!1}return{CURVE:t,getPublicKey:y,getSharedSecret:x,sign:ae,verify:oe,ProjectivePoint:u,Signature:_,utils:v}}function fl(e,t){let n=e.ORDER,r=gl;for(let e=n-_l;e%vl===gl;e/=vl)r+=_l;let i=r,a=vl<{let r=d,a=e.pow(n,l),o=e.sqr(a);o=e.mul(o,n);let s=e.mul(t,o);s=e.pow(s,c),s=e.mul(s,a),a=e.mul(s,n),o=e.mul(s,t);let p=e.mul(o,a);s=e.pow(p,u);let m=e.eql(s,e.ONE);a=e.mul(o,f),s=e.mul(p,r),o=e.cmov(a,o,m),p=e.cmov(s,p,m);for(let t=i;t>_l;t--){let n=t-vl;n=vl<{let a=e.sqr(i),o=e.mul(t,i);a=e.mul(a,o);let s=e.pow(a,n);s=e.mul(s,o);let c=e.mul(s,r),l=e.mul(e.sqr(s),i),u=e.eql(l,t);return{isValid:u,value:e.cmov(c,s,u)}}}return p}function pl(e,t){if(Dc(e),!e.isValid(t.A)||!e.isValid(t.B)||!e.isValid(t.Z))throw Error(`mapToCurveSimpleSWU: invalid opts`);let n=fl(e,t.Z);if(!e.isOdd)throw Error(`Fp.isOdd is not implemented!`);return r=>{let i,a,o,s,c,l,u,d;i=e.sqr(r),i=e.mul(i,t.Z),a=e.sqr(i),a=e.add(a,i),o=e.add(a,e.ONE),o=e.mul(o,t.B),s=e.cmov(t.Z,e.neg(a),!e.eql(a,e.ZERO)),s=e.mul(s,t.A),a=e.sqr(o),l=e.sqr(s),c=e.mul(l,t.A),a=e.add(a,c),a=e.mul(a,o),l=e.mul(l,s),c=e.mul(l,t.B),a=e.add(a,c),u=e.mul(i,o);let{isValid:f,value:p}=n(a,l);d=e.mul(i,r),d=e.mul(d,p),u=e.cmov(u,o,f),d=e.cmov(d,p,f);let m=e.isOdd(r)===e.isOdd(d);d=e.cmov(e.neg(d),d,m);let h=kc(e,[s],!0)[0];return u=e.mul(u,h),{x:u,y:d}}}var ml,hl,gl,_l,vl,yl,bl,xl=o((()=>{al(),Wc(),yc(),ml=class extends Error{constructor(e=``){super(e)}},hl={Err:ml,_tlv:{encode:(e,t)=>{let{Err:n}=hl;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length&1)throw new n(`tlv.encode: unpadded data`);let r=t.length/2,i=Ws(r);if(i.length/2&128)throw new n(`tlv.encode: long form length too big`);let a=r>127?Ws(i.length/2|128):``;return Ws(e)+a+i+t},decode(e,t){let{Err:n}=hl,r=0;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length<2||t[r++]!==e)throw new n(`tlv.decode: wrong tlv`);let i=t[r++],a=!!(i&128),o=0;if(!a)o=i;else{let e=i&127;if(!e)throw new n(`tlv.decode(long): indefinite length not supported`);if(e>4)throw new n(`tlv.decode(long): byte length is too big`);let a=t.subarray(r,r+e);if(a.length!==e)throw new n(`tlv.decode: length bytes not complete`);if(a[0]===0)throw new n(`tlv.decode(long): zero leftmost byte`);for(let e of a)o=o<<8|e;if(r+=e,o<128)throw new n(`tlv.decode(long): not minimal encoding`)}let s=t.subarray(r,r+o);if(s.length!==o)throw new n(`tlv.decode: wrong value length`);return{v:s,l:t.subarray(r+o)}}},_int:{encode(e){let{Err:t}=hl;if(ezs(e,t,Or(...n)),randomBytes:jr}}function Cl(e,t){let n=t=>dl({...e,...Sl(t)});return{...n(t),create:n}}var wl=o((()=>{Bs(),Fr(),xl()}));function Tl(e,t){if(Dl(e),Dl(t),e<0||e>=1<<8*t)throw Error(`invalid I2OSP input: `+e);let n=Array.from({length:t}).fill(0);for(let r=t-1;r>=0;r--)n[r]=e&255,e>>>=8;return new Uint8Array(n)}function El(e,t){let n=new Uint8Array(e.length);for(let r=0;r255&&(t=r(ec(nc(`H2C-OVERSIZE-DST-`),t)));let{outputLen:i,blockLen:a}=r,o=Math.ceil(n/i);if(n>65535||o>255)throw Error(`expand_message_xmd: invalid lenInBytes`);let s=ec(t,Tl(t.length,1)),c=Tl(0,a),l=Tl(n,2),u=Array(o),d=r(ec(c,e,l,Tl(0,1),s));u[0]=r(ec(d,Tl(1,1),s));for(let e=1;e<=o;e++)u[e]=r(ec(El(d,u[e-1]),Tl(e+1,1),s));return ec(...u).slice(0,n)}function kl(e,t,n,r,i){if(Hs(e),Hs(t),Dl(n),t.length>255){let e=Math.ceil(2*r/8);t=i.create({dkLen:e}).update(nc(`H2C-OVERSIZE-DST-`)).update(t).digest()}if(n>65535||t.length>255)throw Error(`expand_message_xof: invalid lenInBytes`);return i.create({dkLen:n}).update(e).update(Tl(n,2)).update(t).update(Tl(t.length,1)).digest()}function Al(e,t,n){sc(n,{DST:`stringOrUint8Array`,p:`bigint`,m:`isSafeInteger`,k:`isSafeInteger`,hash:`hash`});let{p:r,k:i,m:a,hash:o,expand:s,DST:c}=n;Hs(e),Dl(t);let l=typeof c==`string`?nc(c):c,u=r.toString(2).length,d=Math.ceil((u+i)/8),f=t*a*d,p;if(s===`xmd`)p=Ol(e,l,f,o);else if(s===`xof`)p=kl(e,l,f,i,o);else if(s===`_internal_pass`)p=e;else throw Error(`expand must be "xmd" or "xof"`);let m=Array(t);for(let e=0;eArray.from(e).reverse());return(t,r)=>{let[i,a,o,s]=n.map(n=>n.reduce((n,r)=>e.add(e.mul(n,t),r))),[c,l]=kc(e,[a,s],!0);return t=e.mul(i,c),r=e.mul(r,e.mul(o,l)),{x:t,y:r}}}function Ml(e,t,n){if(typeof t!=`function`)throw Error(`mapToCurve() must be defined`);function r(n){return e.fromAffine(t(n))}function i(t){let n=t.clearCofactor();return n.equals(e.ZERO)?e.ZERO:(n.assertValidity(),n)}return{defaults:n,hashToCurve(e,t){let a=Al(e,2,{...n,DST:n.DST,...t}),o=r(a[0]),s=r(a[1]);return i(o.add(s))},encodeToCurve(e,t){return i(r(Al(e,1,{...n,DST:n.encodeDST,...t})[0]))},mapToCurve(e){if(!Array.isArray(e))throw Error(`expected array of bigints`);for(let t of e)if(typeof t!=`bigint`)throw Error(`expected array of bigints`);return i(r(e))}}}var Nl,Pl=o((()=>{Wc(),yc(),Nl=Ys})),Fl=c({encodeToCurve:()=>lu,hashToCurve:()=>cu,schnorr:()=>iu,secp256k1:()=>Zl,secp256k1_hasher:()=>su});function Il(e){let t=Wl,n=BigInt(3),r=BigInt(6),i=BigInt(11),a=BigInt(22),o=BigInt(23),s=BigInt(44),c=BigInt(88),l=e*e*e%t,u=l*l*e%t,d=xc(xc(xc(u,n,t)*u%t,n,t)*u%t,Jl,t)*l%t,f=xc(d,i,t)*d%t,p=xc(f,a,t)*f%t,m=xc(p,s,t)*p%t,h=xc(xc(xc(xc(xc(xc(m,c,t)*m%t,s,t)*p%t,n,t)*u%t,o,t)*f%t,r,t)*l%t,Jl,t);if(!Xl.eql(Xl.sqr(h),e))throw Error(`Cannot find square root`);return h}function Ll(e,...t){let n=Ql[e];if(n===void 0){let t=Ps(Uint8Array.from(e,e=>e.charCodeAt(0)));n=ec(t,t),Ql[e]=n}return Ps(ec(n,...t))}function Rl(e){let t=Zl.utils.normPrivateKeyToScalar(e),n=M.fromPrivateKey(t);return{scalar:n.hasEvenY()?t:j(-t),bytes:$l(n)}}function zl(e){ic(`x`,e,ql,Wl);let t=Il(tu(tu(e*e)*e+BigInt(7)));t%Jl!==Kl&&(t=tu(-t));let n=new M(e,t,ql);return n.assertValidity(),n}function Bl(...e){return j(ru(Ll(`BIP0340/challenge`,...e)))}function Vl(e){return Rl(e).bytes}function Hl(e,t,n=jr(32)){let r=$s(`message`,e),{bytes:i,scalar:a}=Rl(t),o=j(ru(Ll(`BIP0340/nonce`,eu(a^ru(Ll(`BIP0340/aux`,$s(`auxRand`,n,32)))),i,r)));if(o===Kl)throw Error(`sign failed: k is zero`);let{bytes:s,scalar:c}=Rl(o),l=Bl(s,i,r),u=new Uint8Array(64);if(u.set(s,0),u.set(eu(j(c+l*a)),32),!Ul(u,r,i))throw Error(`sign: Invalid signature produced`);return u}function Ul(e,t,n){let r=$s(`signature`,e,64),i=$s(`message`,t),a=$s(`publicKey`,n,32);try{let e=zl(ru(a)),t=ru(r.subarray(0,32));if(!rc(t,ql,Wl))return!1;let n=ru(r.subarray(32,64));if(!rc(n,ql,Gl))return!1;let o=nu(e,n,j(-Bl(eu(t),$l(e),i)));return!(!o||!o.hasEvenY()||o.toAffine().x!==t)}catch{return!1}}var Wl,Gl,Kl,ql,Jl,Yl,Xl,Zl,Ql,$l,eu,tu,j,M,nu,ru,iu,au,ou,su,cu,lu,uu=o((()=>{Ls(),Fr(),wl(),Pl(),Wc(),yc(),xl(),Wl=BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f`),Gl=BigInt(`0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141`),Kl=BigInt(0),ql=BigInt(1),Jl=BigInt(2),Yl=(e,t)=>(e+t/Jl)/t,Xl=Mc(Wl,void 0,void 0,{sqrt:Il}),Zl=Cl({a:Kl,b:BigInt(7),Fp:Xl,n:Gl,Gx:BigInt(`55066263022277343669578718895168534326250603453777594175500187360389116729240`),Gy:BigInt(`32670510020758816978083085130507043184471273380659243275938904335757337482424`),h:BigInt(1),lowS:!0,endo:{beta:BigInt(`0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee`),splitScalar:e=>{let t=Gl,n=BigInt(`0x3086d221a7d46bcde86c90e49284eb15`),r=-ql*BigInt(`0xe4437ed6010e88286f547fa90abfe4c3`),i=BigInt(`0x114ca50f7a8e2f3f657c1108d9d44cfd8`),a=n,o=BigInt(`0x100000000000000000000000000000000`),s=Yl(a*e,t),c=Yl(-r*e,t),l=bc(e-s*n-c*i,t),u=bc(-s*r-c*a,t),d=l>o,f=u>o;if(d&&(l=t-l),f&&(u=t-u),l>o||u>o)throw Error(`splitScalar: Endomorphism failed, k=`+e);return{k1neg:d,k1:l,k2neg:f,k2:u}}}},Ps),Ql={},$l=e=>e.toRawBytes(!0).slice(1),eu=e=>Zs(e,32),tu=e=>bc(e,Wl),j=e=>bc(e,Gl),M=Zl.ProjectivePoint,nu=(e,t,n)=>M.BASE.multiplyAndAddUnsafe(e,t,n),ru=Ys,iu={getPublicKey:Vl,sign:Hl,verify:Ul,utils:{randomPrivateKey:Zl.utils.randomPrivateKey,lift_x:zl,pointToBytes:$l,numberToBytesBE:Zs,bytesToNumberBE:Ys,taggedHash:Ll,mod:bc}},au=jl(Xl,[[`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7`,`0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581`,`0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262`,`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c`],[`0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b`,`0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14`,`0x0000000000000000000000000000000000000000000000000000000000000001`],[`0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c`,`0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3`,`0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931`,`0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84`],[`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b`,`0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573`,`0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f`,`0x0000000000000000000000000000000000000000000000000000000000000001`]].map(e=>e.map(e=>BigInt(e)))),ou=pl(Xl,{A:BigInt(`0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533`),B:BigInt(`1771`),Z:Xl.create(BigInt(`-11`))}),su=Ml(Zl.ProjectivePoint,e=>{let{x:t,y:n}=ou(Xl.create(e[0]));return au(t,n)},{DST:`secp256k1_XMD:SHA-256_SSWU_RO_`,encodeDST:`secp256k1_XMD:SHA-256_SSWU_NU_`,p:Xl.ORDER,m:1,k:128,expand:`xmd`,hash:Ps}),cu=su.hashToCurve,lu=su.encodeToCurve}));Ft(),Lt(),On(),k(),hs();async function du({hash:e,signature:t}){let n=Pt(e)?e:kn(e),{secp256k1:r}=await ms(async()=>{let{secp256k1:e}=await Promise.resolve().then(()=>(uu(),Fl));return{secp256k1:e}},void 0);return`0x${(()=>{if(typeof t==`object`&&`r`in t&&`s`in t){let{r:e,s:n,v:i,yParity:a}=t,o=fu(Number(a??i));return new r.Signature(Tn(e),Tn(n)).addRecoveryBit(o)}let e=Pt(t)?t:kn(t);if(It(e)!==65)throw Error(`invalid signature length`);let n=fu(Dn(`0x${e.slice(130)}`));return r.Signature.fromCompact(e.substring(2,130)).addRecoveryBit(n)})().recoverPublicKey(n.substring(2)).toHex(!1)}`}function fu(e){if(e===0||e===1)return e;if(e===27)return 0;if(e===28)return 1;throw Error(`Invalid yParityOrV value`)}async function pu({hash:e,signature:t}){return us(await du({hash:e,signature:t}))}D(),Sa(),Un(),k();function mu(e,t=`hex`){let n=hu(e),r=ba(new Uint8Array(n.length));return n.encode(r),t===`hex`?jn(r.bytes):r.bytes}function hu(e){return Array.isArray(e)?gu(e.map(e=>hu(e))):_u(e)}function gu(e){let t=e.reduce((e,t)=>e+t.length,0),n=vu(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function _u(e){let t=typeof e==`string`?Rn(e):e,n=vu(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function vu(e){if(e<2**8)return 1;if(e<2**16)return 2;if(e<2**24)return 3;if(e<2**32)return 4;throw new E(`Length is too large.`)}Ai(),Un(),k(),ri();function yu(e){let{chainId:t,nonce:n,to:r}=e,i=e.contractAddress??e.address,a=ni(ki([`0x05`,mu([t?O(t):`0x`,i,n?O(n):`0x`])]));return r===`bytes`?Rn(a):a}async function bu(e){let{authorization:t,signature:n}=e;return pu({hash:yu(t),signature:n??t})}$a(),to(),D(),_o();var xu=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=so({from:t?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Estimate Gas Arguments:`,f].filter(Boolean),name:`EstimateGasExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},Su,Cu,wu,Tu,Eu,Du,Ou,ku,Au,ju,Mu,Nu,Pu=o((()=>{to(),D(),Su=class extends E{constructor({cause:e,message:t}={}){let n=t?.replace(`execution reverted: `,``)?.replace(`execution reverted`,``);super(`Execution reverted ${n?`with reason: ${n}`:`for an unknown reason`}.`,{cause:e,name:`ExecutionRevertedError`})}},Object.defineProperty(Su,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(Su,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/execution reverted|gas required exceeds allowance/}),Cu=class extends E{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${eo(t)} gwei`:``}) cannot be higher than the maximum allowed value (2^256-1).`,{cause:e,name:`FeeCapTooHighError`})}},Object.defineProperty(Cu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas higher than 2\^256-1|fee cap higher than 2\^256-1/}),wu=class extends E{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${eo(t)}`:``} gwei) cannot be lower than the block base fee.`,{cause:e,name:`FeeCapTooLowError`})}},Object.defineProperty(wu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas less than block base fee|fee cap less than block base fee|transaction is outdated/}),Tu=class extends E{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}is higher than the next one expected.`,{cause:e,name:`NonceTooHighError`})}},Object.defineProperty(Tu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too high/}),Eu=class extends E{constructor({cause:e,nonce:t}={}){super([`Nonce provided for the transaction ${t?`(${t}) `:``}is lower than the current nonce of the account.`,"Try increasing the nonce or find the latest nonce with `getTransactionCount`."].join(` +`),{cause:e,name:`NonceTooLowError`})}},Object.defineProperty(Eu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too low|transaction already imported|already known/}),Du=class extends E{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}exceeds the maximum allowed nonce.`,{cause:e,name:`NonceMaxValueError`})}},Object.defineProperty(Du,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce has max value/}),Ou=class extends E{constructor({cause:e}={}){super([`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`].join(` +`),{cause:e,metaMessages:[`This error could arise when the account does not have enough funds to:`,` - pay for the total gas fee,`,` - pay for the value to send.`,` `,"The cost of the transaction is calculated as `gas * gas fee + value`, where:"," - `gas` is the amount of gas needed for transaction to execute,"," - `gas fee` is the gas fee,"," - `value` is the amount of ether to send to the recipient."],name:`InsufficientFundsError`})}},Object.defineProperty(Ou,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/insufficient funds|exceeds transaction sender account balance/}),ku=class extends E{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction exceeds the limit allowed for the block.`,{cause:e,name:`IntrinsicGasTooHighError`})}},Object.defineProperty(ku,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too high|gas limit reached/}),Au=class extends E{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction is too low.`,{cause:e,name:`IntrinsicGasTooLowError`})}},Object.defineProperty(Au,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too low/}),ju=class extends E{constructor({cause:e}){super(`The transaction type is not supported for this chain.`,{cause:e,name:`TransactionTypeNotSupportedError`})}},Object.defineProperty(ju,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/transaction type not valid/}),Mu=class extends E{constructor({cause:e,maxPriorityFeePerGas:t,maxFeePerGas:n}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${t?` = ${eo(t)} gwei`:``}) cannot be higher than the fee cap (\`maxFeePerGas\`${n?` = ${eo(n)} gwei`:``}).`].join(` +`),{cause:e,name:`TipAboveFeeCapError`})}},Object.defineProperty(Mu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max priority fee per gas higher than max fee per gas|tip higher than fee cap/}),Nu=class extends E{constructor({cause:e}){super(`An error occurred while executing: ${e?.shortMessage}`,{cause:e,name:`UnknownNodeError`})}}}));function Fu(e,t){let n=(e.details||``).toLowerCase(),r=e instanceof E?e.walk(e=>e?.code===Su.code):e;return r instanceof E?new Su({cause:e,message:r.details}):Su.nodeMessage.test(n)?new Su({cause:e,message:e.details}):Cu.nodeMessage.test(n)?new Cu({cause:e,maxFeePerGas:t?.maxFeePerGas}):wu.nodeMessage.test(n)?new wu({cause:e,maxFeePerGas:t?.maxFeePerGas}):Tu.nodeMessage.test(n)?new Tu({cause:e,nonce:t?.nonce}):Eu.nodeMessage.test(n)?new Eu({cause:e,nonce:t?.nonce}):Du.nodeMessage.test(n)?new Du({cause:e,nonce:t?.nonce}):Ou.nodeMessage.test(n)?new Ou({cause:e}):ku.nodeMessage.test(n)?new ku({cause:e,gas:t?.gas}):Au.nodeMessage.test(n)?new Au({cause:e,gas:t?.gas}):ju.nodeMessage.test(n)?new ju({cause:e}):Mu.nodeMessage.test(n)?new Mu({cause:e,maxFeePerGas:t?.maxFeePerGas,maxPriorityFeePerGas:t?.maxPriorityFeePerGas}):new Nu({cause:e})}var Iu=o((()=>{D(),Pu()}));Pu(),Iu();function Lu(e,{docsPath:t,...n}){return new xu((()=>{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}function Ru(e,{format:t}){if(!t)return{};let n={};function r(t){let i=Object.keys(t);for(let a of i)a in e&&(n[a]=e[a]),t[a]&&typeof t[a]==`object`&&!Array.isArray(t[a])&&r(t[a])}return r(t(e||{})),n}var zu=o((()=>{}));function Bu(e,t){return({exclude:n,format:r})=>({exclude:n,format:(e,i)=>{let a=t(e,i);if(n)for(let e of n)delete a[e];return{...a,...r(e,i)}},type:e})}var Vu=o((()=>{}));function Hu(e,t){let n={};return e.authorizationList!==void 0&&(n.authorizationList=Uu(e.authorizationList)),e.accessList!==void 0&&(n.accessList=e.accessList),e.blobVersionedHashes!==void 0&&(n.blobVersionedHashes=e.blobVersionedHashes),e.blobs!==void 0&&(typeof e.blobs[0]==`string`?n.blobs=e.blobs:n.blobs=e.blobs.map(e=>jn(e))),e.data!==void 0&&(n.data=e.data),e.account&&(n.from=e.account.address),e.from!==void 0&&(n.from=e.from),e.gas!==void 0&&(n.gas=O(e.gas)),e.gasPrice!==void 0&&(n.gasPrice=O(e.gasPrice)),e.maxFeePerBlobGas!==void 0&&(n.maxFeePerBlobGas=O(e.maxFeePerBlobGas)),e.maxFeePerGas!==void 0&&(n.maxFeePerGas=O(e.maxFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(n.maxPriorityFeePerGas=O(e.maxPriorityFeePerGas)),e.nonce!==void 0&&(n.nonce=O(e.nonce)),e.to!==void 0&&(n.to=e.to),e.type!==void 0&&(n.type=Wu[e.type]),e.value!==void 0&&(n.value=O(e.value)),n}function Uu(e){return e.map(e=>({address:e.address,r:e.r?O(BigInt(e.r)):e.r,s:e.s?O(BigInt(e.s)):e.s,chainId:O(e.chainId),nonce:O(e.nonce),...e.yParity===void 0?{}:{yParity:O(e.yParity)},...e.v!==void 0&&e.yParity===void 0?{v:O(e.v)}:{}}))}var Wu,Gu,Ku=o((()=>{k(),Vu(),Wu={legacy:`0x0`,eip2930:`0x1`,eip1559:`0x2`,eip4844:`0x3`,eip7702:`0x4`},Gu=Bu(`transactionRequest`,Hu)}));function qu(e){if(!(!e||e.length===0))return e.reduce((e,{slot:t,value:n})=>{if(t.length!==66)throw new dn({size:t.length,targetSize:66,type:`hex`});if(n.length!==66)throw new dn({size:n.length,targetSize:66,type:`hex`});return e[t]=n,e},{})}function Ju(e){let{balance:t,nonce:n,state:r,stateDiff:i,code:a}=e,o={};if(a!==void 0&&(o.code=a),t!==void 0&&(o.balance=O(t)),n!==void 0&&(o.nonce=O(n)),r!==void 0&&(o.state=qu(r)),i!==void 0){if(o.state)throw new ao;o.stateDiff=qu(i)}return o}function Yu(e){if(!e)return;let t={};for(let{address:n,...r}of e){if(!Ci(n,{strict:!1}))throw new hi({address:n});if(t[n])throw new io({address:n});t[n]=Ju(r)}return t}var Xu=o((()=>{gi(),fn(),oo(),Ei(),k()})),Zu,Qu,$u=o((()=>{2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n)),Zu=2n**16n-1n,Qu=2n**256n-1n}));function ed(e){let{account:t,maxFeePerGas:n,maxPriorityFeePerGas:r,to:i}=e,a=t?aa(t):void 0;if(a&&!Ci(a.address))throw new hi({address:a.address});if(i&&!Ci(i))throw new hi({address:i});if(n&&n>Qu)throw new Cu({maxFeePerGas:n});if(r&&n&&r>n)throw new Mu({maxFeePerGas:n,maxPriorityFeePerGas:r})}var td=o((()=>{oa(),$u(),gi(),Pu(),Ei()}));to(),D();var nd=class extends E{constructor(){super("`baseFeeMultiplier` must be greater than 1.",{name:`BaseFeeScalarError`})}},rd=class extends E{constructor(){super(`Chain does not support EIP-1559 fees.`,{name:`Eip1559FeesNotSupportedError`})}},id=class extends E{constructor({maxPriorityFeePerGas:e}){super(`\`maxFeePerGas\` cannot be less than the \`maxPriorityFeePerGas\` (${eo(e)} gwei).`,{name:`MaxFeePerGasTooLowError`})}};D();var ad=class extends E{constructor({blockHash:e,blockNumber:t}){let n=`Block`;e&&(n=`Block at hash "${e}"`),t&&(n=`Block at number "${t}"`),super(`${n} could not be found.`,{name:`BlockNotFoundError`})}};On(),Vu();var od={"0x0":`legacy`,"0x1":`eip2930`,"0x2":`eip1559`,"0x3":`eip4844`,"0x4":`eip7702`};function sd(e,t){let n={...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,chainId:e.chainId?Dn(e.chainId):void 0,gas:e.gas?BigInt(e.gas):void 0,gasPrice:e.gasPrice?BigInt(e.gasPrice):void 0,maxFeePerBlobGas:e.maxFeePerBlobGas?BigInt(e.maxFeePerBlobGas):void 0,maxFeePerGas:e.maxFeePerGas?BigInt(e.maxFeePerGas):void 0,maxPriorityFeePerGas:e.maxPriorityFeePerGas?BigInt(e.maxPriorityFeePerGas):void 0,nonce:e.nonce?Dn(e.nonce):void 0,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,type:e.type?od[e.type]:void 0,typeHex:e.type?e.type:void 0,value:e.value?BigInt(e.value):void 0,v:e.v?BigInt(e.v):void 0};return e.authorizationList&&(n.authorizationList=ld(e.authorizationList)),n.yParity=(()=>{if(e.yParity)return Number(e.yParity);if(typeof n.v==`bigint`){if(n.v===0n||n.v===27n)return 0;if(n.v===1n||n.v===28n)return 1;if(n.v>=35n)return n.v%2n==0n?1:0}})(),n.type===`legacy`&&(delete n.accessList,delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas,delete n.yParity),n.type===`eip2930`&&(delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas),n.type===`eip1559`&&delete n.maxFeePerBlobGas,n}var cd=Bu(`transaction`,sd);function ld(e){return e.map(e=>({address:e.address,chainId:Number(e.chainId),nonce:Number(e.nonce),r:e.r,s:e.s,yParity:Number(e.yParity)}))}Vu();function ud(e,t){let n=(e.transactions??[]).map(e=>typeof e==`string`?e:sd(e));return{...e,baseFeePerGas:e.baseFeePerGas?BigInt(e.baseFeePerGas):null,blobGasUsed:e.blobGasUsed?BigInt(e.blobGasUsed):void 0,difficulty:e.difficulty?BigInt(e.difficulty):void 0,excessBlobGas:e.excessBlobGas?BigInt(e.excessBlobGas):void 0,gasLimit:e.gasLimit?BigInt(e.gasLimit):void 0,gasUsed:e.gasUsed?BigInt(e.gasUsed):void 0,hash:e.hash?e.hash:null,logsBloom:e.logsBloom?e.logsBloom:null,nonce:e.nonce?e.nonce:null,number:e.number?BigInt(e.number):null,size:e.size?BigInt(e.size):void 0,timestamp:e.timestamp?BigInt(e.timestamp):void 0,transactions:n,totalDifficulty:e.totalDifficulty?BigInt(e.totalDifficulty):null}}var dd=Bu(`block`,ud);k();async function fd(e,{blockHash:t,blockNumber:n,blockTag:r=e.experimental_blockTag??`latest`,includeTransactions:i}={}){let a=i??!1,o=n===void 0?void 0:O(n),s=null;if(s=t?await e.request({method:`eth_getBlockByHash`,params:[t,a]},{dedupe:!0}):await e.request({method:`eth_getBlockByNumber`,params:[o||r,a]},{dedupe:!!o}),!s)throw new ad({blockHash:t,blockNumber:n});return(e.chain?.formatters?.block?.format||ud)(s,`getBlock`)}async function pd(e){let t=await e.request({method:`eth_gasPrice`});return BigInt(t)}On();async function md(e,t){let{block:n,chain:r=e.chain,request:i}=t||{};try{let t=r?.fees?.maxPriorityFeePerGas??r?.fees?.defaultPriorityFee;if(typeof t==`function`){let r=await t({block:n||await T(e,fd,`getBlock`)({}),client:e,request:i});if(r===null)throw Error();return r}return t===void 0?Tn(await e.request({method:`eth_maxPriorityFeePerGas`})):t}catch{let[t,r]=await Promise.all([n?Promise.resolve(n):T(e,fd,`getBlock`)({}),T(e,pd,`getGasPrice`)({})]);if(typeof t.baseFeePerGas!=`bigint`)throw new rd;let i=r-t.baseFeePerGas;return i<0n?0n:i}}async function hd(e,t){let{block:n,chain:r=e.chain,request:i,type:a=`eip1559`}=t||{},o=await(async()=>typeof r?.fees?.baseFeeMultiplier==`function`?r.fees.baseFeeMultiplier({block:n,client:e,request:i}):r?.fees?.baseFeeMultiplier??1.2)();if(o<1)throw new nd;let s=10**(o.toString().split(`.`)[1]?.length??0),c=e=>e*BigInt(Math.ceil(o*s))/BigInt(s),l=n||await T(e,fd,`getBlock`)({});if(typeof r?.fees?.estimateFeesPerGas==`function`){let t=await r.fees.estimateFeesPerGas({block:n,client:e,multiply:c,request:i,type:a});if(t!==null)return t}if(a===`eip1559`){if(typeof l.baseFeePerGas!=`bigint`)throw new rd;let t=typeof i?.maxPriorityFeePerGas==`bigint`?i.maxPriorityFeePerGas:await md(e,{block:l,chain:r,request:i}),n=c(l.baseFeePerGas);return{maxFeePerGas:i?.maxFeePerGas??n+t,maxPriorityFeePerGas:t}}return{gasPrice:i?.gasPrice??c(await T(e,pd,`getGasPrice`)({}))}}On(),k();async function gd(e,{address:t,blockTag:n=`latest`,blockNumber:r}){return Dn(await e.request({method:`eth_getTransactionCount`,params:[t,typeof r==`bigint`?O(r):n]},{dedupe:!!r}))}Ku(),Un(),k();function _d(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Rn(e)):e.blobs,i=[];for(let e of r)i.push(Uint8Array.from(t.blobToKzgCommitment(e)));return n===`bytes`?i:i.map(e=>jn(e))}Un(),k();function vd(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Rn(e)):e.blobs,i=typeof e.commitments[0]==`string`?e.commitments.map(e=>Rn(e)):e.commitments,a=[];for(let e=0;ejn(e))}Ls();var yd=Ps;Ft(),Un(),k();function bd(e,t){let n=t||`hex`,r=yd(Pt(e,{strict:!1})?Fn(e):e);return n===`bytes`?r:kn(r)}k();function xd(e){let{commitment:t,version:n=1}=e,r=e.to??(typeof t==`string`?`hex`:`bytes`),i=bd(t,`bytes`);return i.set([n],0),r===`bytes`?i:jn(i)}function Sd(e){let{commitments:t,version:n}=e,r=e.to??(typeof t[0]==`string`?`hex`:`bytes`),i=[];for(let e of t)i.push(xd({commitment:e,to:r,version:n}));return i}var Cd=6,wd=4096,Td=32*wd,Ed=Td*Cd-1-1*wd*Cd;D();var Dd=class extends E{constructor({maxSize:e,size:t}){super(`Blob size is too large.`,{metaMessages:[`Max: ${e} bytes`,`Given: ${t} bytes`],name:`BlobSizeTooLargeError`})}},Od=class extends E{constructor(){super(`Blob data must not be empty.`,{name:`EmptyBlobError`})}},kd=class extends E{constructor({hash:e,size:t}){super(`Versioned hash "${e}" size is invalid.`,{metaMessages:[`Expected: 32`,`Received: ${t}`],name:`InvalidVersionedHashSizeError`})}},Ad=class extends E{constructor({hash:e,version:t}){super(`Versioned hash "${e}" version is invalid.`,{metaMessages:[`Expected: 1`,`Received: ${t}`],name:`InvalidVersionedHashVersionError`})}};Sa(),Lt(),Un(),k();function jd(e){let t=e.to??(typeof e.data==`string`?`hex`:`bytes`),n=typeof e.data==`string`?Rn(e.data):e.data,r=It(n);if(!r)throw new Od;if(r>761855)throw new Dd({maxSize:Ed,size:r});let i=[],a=!0,o=0;for(;a;){let e=ba(new Uint8Array(Td)),t=0;for(;te.bytes):i.map(e=>jn(e.bytes))}function Md(e){let{data:t,kzg:n,to:r}=e,i=e.blobs??jd({data:t,to:r}),a=e.commitments??_d({blobs:i,kzg:n,to:r}),o=e.proofs??vd({blobs:i,commitments:a,kzg:n,to:r}),s=[];for(let e=0;e{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}On();async function Fd(e){return Dn(await e.request({method:`eth_chainId`},{dedupe:!0}))}oa(),zu(),td();async function Id(e,t){let{account:n=e.account,accessList:r,authorizationList:i,chain:a=e.chain,blobVersionedHashes:o,blobs:s,data:c,gas:l,gasPrice:u,maxFeePerBlobGas:d,maxFeePerGas:f,maxPriorityFeePerGas:p,nonce:m,nonceManager:h,to:g,type:_,value:v,...y}=t,b=await(async()=>{if(!n||!h||m!==void 0)return m;let t=aa(n),r=a?a.id:await T(e,Fd,`getChainId`)({});return await h.consume({address:t.address,chainId:r,client:e})})();ed(t);let x=a?.formatters?.transactionRequest?.format,S=(x||Hu)({...Ru(y,{format:x}),account:n?aa(n):void 0,accessList:r,authorizationList:i,blobs:s,blobVersionedHashes:o,data:c,gas:l,gasPrice:u,maxFeePerBlobGas:d,maxFeePerGas:f,maxPriorityFeePerGas:p,nonce:b,to:g,type:_,value:v},`fillTransaction`);try{let n=await e.request({method:`eth_fillTransaction`,params:[S]}),r=(a?.formatters?.transaction?.format||sd)(n.tx);delete r.blockHash,delete r.blockNumber,delete r.r,delete r.s,delete r.transactionIndex,delete r.v,delete r.yParity,r.data=r.input,r.gas&&=t.gas??r.gas,r.gasPrice&&=t.gasPrice??r.gasPrice,r.maxFeePerBlobGas&&=t.maxFeePerBlobGas??r.maxFeePerBlobGas,r.maxFeePerGas&&=t.maxFeePerGas??r.maxFeePerGas,r.maxPriorityFeePerGas&&=t.maxPriorityFeePerGas??r.maxPriorityFeePerGas,r.nonce&&=t.nonce??r.nonce;let i=await(async()=>{if(typeof a?.fees?.baseFeeMultiplier==`function`){let n=await T(e,fd,`getBlock`)({});return a.fees.baseFeeMultiplier({block:n,client:e,request:t})}return a?.fees?.baseFeeMultiplier??1.2})();if(i<1)throw new nd;let o=10**(i.toString().split(`.`)[1]?.length??0),s=e=>e*BigInt(Math.ceil(i*o))/BigInt(o);return r.maxFeePerGas&&!t.maxFeePerGas&&(r.maxFeePerGas=s(r.maxFeePerGas)),r.gasPrice&&!t.gasPrice&&(r.gasPrice=s(r.gasPrice)),{raw:n.raw,transaction:{from:S.from,...r}}}catch(n){throw Pd(n,{...t,chain:e.chain})}}oa(),vi(),td();var Ld=[`blobVersionedHashes`,`chainId`,`fees`,`gas`,`nonce`,`type`],Rd=new Map,N=new _i(128);async function zd(e,t){let n=t;n.account??=e.account,n.parameters??=Ld;let{account:r,chain:i=e.chain,nonceManager:a,parameters:o}=n,s=(()=>{if(typeof i?.prepareTransactionRequest==`function`)return{fn:i.prepareTransactionRequest,runAt:[`beforeFillTransaction`]};if(Array.isArray(i?.prepareTransactionRequest))return{fn:i.prepareTransactionRequest[0],runAt:i.prepareTransactionRequest[1].runAt}})(),c;async function l(){return c||(n.chainId===void 0?i?i.id:(c=await T(e,Fd,`getChainId`)({}),c):n.chainId)}let u=r&&aa(r),d=n.nonce;if(o.includes(`nonce`)&&d===void 0&&u&&a){let t=await l();d=await a.consume({address:u.address,chainId:t,client:e})}s?.fn&&s.runAt?.includes(`beforeFillTransaction`)&&(n=await s.fn({...n,chain:i},{phase:`beforeFillTransaction`}),d??=n.nonce);let f=!((o.includes(`blobVersionedHashes`)||o.includes(`sidecars`))&&n.kzg&&n.blobs||N.get(e.uid)===!1||![`fees`,`gas`].some(e=>o.includes(e)))&&(o.includes(`chainId`)&&typeof n.chainId!=`number`||o.includes(`nonce`)&&typeof d!=`number`||o.includes(`fees`)&&typeof n.gasPrice!=`bigint`&&(typeof n.maxFeePerGas!=`bigint`||typeof n.maxPriorityFeePerGas!=`bigint`)||o.includes(`gas`)&&typeof n.gas!=`bigint`)?await T(e,Id,`fillTransaction`)({...n,nonce:d}).then(t=>{let{chainId:r,from:i,gas:a,gasPrice:o,nonce:s,maxFeePerBlobGas:c,maxFeePerGas:l,maxPriorityFeePerGas:u,type:d,...f}=t.transaction;return N.set(e.uid,!0),{...n,...i?{from:i}:{},...d&&!n.type?{type:d}:{},...r===void 0?{}:{chainId:r},...a===void 0?{}:{gas:a},...o===void 0?{}:{gasPrice:o},...s===void 0?{}:{nonce:s},...c!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxFeePerBlobGas:c}:{},...l!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxFeePerGas:l}:{},...u!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxPriorityFeePerGas:u}:{},...`nonceKey`in f&&f.nonceKey!==void 0?{nonceKey:f.nonceKey}:{}}}).catch(t=>{let r=t;return r.name===`TransactionExecutionError`&&r.walk?.(e=>{let t=e;return t.name===`MethodNotFoundRpcError`||t.name===`MethodNotSupportedRpcError`||t.message?.includes(`eth_fillTransaction is not available`)})&&N.set(e.uid,!1),n}):n;d??=f.nonce,n={...f,...u?{from:u?.address}:{},...d?{nonce:d}:{}};let{blobs:p,gas:m,kzg:h,type:g}=n;s?.fn&&s.runAt?.includes(`beforeFillParameters`)&&(n=await s.fn({...n,chain:i},{phase:`beforeFillParameters`}));let _;async function v(){return _||(_=await T(e,fd,`getBlock`)({blockTag:`latest`}),_)}if(o.includes(`nonce`)&&d===void 0&&u&&!a&&(n.nonce=await T(e,gd,`getTransactionCount`)({address:u.address,blockTag:`pending`})),(o.includes(`blobVersionedHashes`)||o.includes(`sidecars`))&&p&&h){let e=_d({blobs:p,kzg:h});if(o.includes(`blobVersionedHashes`)){let t=Sd({commitments:e,to:`hex`});n.blobVersionedHashes=t}if(o.includes(`sidecars`)){let t=Md({blobs:p,commitments:e,proofs:vd({blobs:p,commitments:e,kzg:h}),to:`hex`});n.sidecars=t}}if(o.includes(`chainId`)&&(n.chainId=await l()),(o.includes(`fees`)||o.includes(`type`))&&g===void 0)try{n.type=Nd(n)}catch{let t=Rd.get(e.uid);t===void 0&&(t=typeof(await v())?.baseFeePerGas==`bigint`,Rd.set(e.uid,t)),n.type=t?`eip1559`:`legacy`}if(o.includes(`fees`))if(n.type!==`legacy`&&n.type!==`eip2930`){if(n.maxFeePerGas===void 0||n.maxPriorityFeePerGas===void 0){let{maxFeePerGas:t,maxPriorityFeePerGas:r}=await hd(e,{block:await v(),chain:i,request:n});if(n.maxPriorityFeePerGas===void 0&&n.maxFeePerGas&&n.maxFeePerGas{if(Array.isArray(r))return r;if(i?.type!==`local`)return[`blobVersionedHashes`]})();try{let n=await(async()=>{if(t.to)return t.to;if(t.authorizationList&&t.authorizationList.length>0)return await bu({authorization:t.authorizationList[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`")})})(),{accessList:o,authorizationList:s,blobs:c,blobVersionedHashes:l,blockNumber:u,blockTag:d,data:f,gas:p,gasPrice:m,maxFeePerBlobGas:h,maxFeePerGas:g,maxPriorityFeePerGas:_,nonce:v,value:y,stateOverride:b,...x}=r?await zd(e,{...t,parameters:a,to:n}):t;if(p&&t.gas!==p)return p;let S=(typeof u==`bigint`?O(u):void 0)||d,ee=Yu(b);ed(t);let C=e.chain?.formatters?.transactionRequest?.format,te=(C||Hu)({...Ru(x,{format:C}),account:i,accessList:o,authorizationList:s,blobs:c,blobVersionedHashes:l,data:f,gasPrice:m,maxFeePerBlobGas:h,maxFeePerGas:g,maxPriorityFeePerGas:_,nonce:v,to:n,value:y},`estimateGas`);return BigInt(await e.request({method:`eth_estimateGas`,params:ee?[te,S??e.experimental_blockTag??`latest`,ee]:S?[te,S]:[te]}))}catch(n){throw Lu(n,{...t,account:i,chain:e.chain})}}function Vd(e,t){if(!Ci(e,{strict:!1}))throw new hi({address:e});if(!Ci(t,{strict:!1}))throw new hi({address:t});return e.toLowerCase()===t.toLowerCase()}var Hd=o((()=>{gi(),Ei()}));function Ud(e,{args:t,eventName:n}={}){return{...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,blockTimestamp:e.blockTimestamp?BigInt(e.blockTimestamp):e.blockTimestamp===null?null:void 0,logIndex:e.logIndex?Number(e.logIndex):null,transactionHash:e.transactionHash?e.transactionHash:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,...n?{args:t,eventName:n}:{}}}function Wd(e){let{abi:t,args:n,functionName:r,data:i}=e,a=t[0];if(r){let e=ta({abi:t,args:n,name:r});if(!e)throw new Qt(r,{docsPath:Gd});a=e}if(a.type!==`function`)throw new Qt(void 0,{docsPath:Gd});if(!a.outputs)throw new $t(a.name,{docsPath:Gd});let o=Oa(a.outputs,i);if(o&&o.length>1)return o;if(o&&o.length===1)return o[0]}var Gd,Kd=o((()=>{cn(),Ba(),ia(),Gd=`/docs/contract/decodeFunctionResult`})),qd,Jd=o((()=>{qd=`0.1.1`}));function Yd(){return qd}var Xd=o((()=>{Jd()}));function Zd(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause?Zd(e.cause,t):t?null:e}var P,Qd=o((()=>{Xd(),P=class e extends Error{static setStaticOptions(t){e.prototype.docsOrigin=t.docsOrigin,e.prototype.showVersion=t.showVersion,e.prototype.version=t.version}constructor(t,n={}){let r=(()=>{if(n.cause instanceof e){if(n.cause.details)return n.cause.details;if(n.cause.shortMessage)return n.cause.shortMessage}return n.cause&&`details`in n.cause&&typeof n.cause.details==`string`?n.cause.details:n.cause?.message?n.cause.message:n.details})(),i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=n.docsOrigin??e.prototype.docsOrigin,o=`${a}${i??``}`,s=!!(n.version??e.prototype.showVersion),c=n.version??e.prototype.version,l=[t||`An error occurred.`,...n.metaMessages?[``,...n.metaMessages]:[],...r||i||s?[``,r?`Details: ${r}`:void 0,i?`See: ${o}`:void 0,s?`Version: ${c}`:void 0]:[]].filter(e=>typeof e==`string`).join(` +`);super(l,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsOrigin`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`showVersion`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.cause=n.cause,this.details=r,this.docs=o,this.docsOrigin=a,this.docsPath=i,this.shortMessage=t,this.showVersion=s,this.version=c}walk(e){return Zd(this,e)}},Object.defineProperty(P,`defaultStaticOptions`,{enumerable:!0,configurable:!0,writable:!0,value:{docsOrigin:`https://oxlib.sh`,showVersion:!1,version:`ox@${Yd()}`}}),P.setStaticOptions(P.defaultStaticOptions)}));function $d(e,t){if(Ef(e)>t)throw new zf({givenSize:Ef(e),maxSize:t})}function ef(e,t){if(typeof t==`number`&&t>0&&t>Ef(e)-1)throw new Bf({offset:t,position:`start`,size:Ef(e)})}function tf(e,t,n){if(typeof t==`number`&&typeof n==`number`&&Ef(e)!==n-t)throw new Bf({offset:n,position:`end`,size:Ef(e)})}function nf(e){if(e>=of.zero&&e<=of.nine)return e-of.zero;if(e>=of.A&&e<=of.F)return e-(of.A-10);if(e>=of.a&&e<=of.f)return e-(of.a-10)}function rf(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;if(e.length>r)throw new Vf({size:e.length,targetSize:r,type:`Bytes`});let i=new Uint8Array(r);for(let t=0;t{Hf(),of={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function cf(e,t){if(Qf(e)>t)throw new up({givenSize:Qf(e),maxSize:t})}function lf(e,t){if(typeof t==`number`&&t>0&&t>Qf(e)-1)throw new dp({offset:t,position:`start`,size:Qf(e)})}function uf(e,t,n){if(typeof t==`number`&&typeof n==`number`&&Qf(e)!==n-t)throw new dp({offset:n,position:`end`,size:Qf(e)})}function df(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;let i=e.replace(`0x`,``);if(i.length>r*2)throw new fp({size:Math.ceil(i.length/2),targetSize:r,type:`Hex`});return`0x${i[n===`right`?`padEnd`:`padStart`](r*2,`0`)}`}function ff(e,t={}){let{dir:n=`left`}=t,r=e.replace(`0x`,``),i=0;for(let e=0;e{pp()}));function mf(e,t,n){return JSON.stringify(e,(e,n)=>typeof t==`function`?t(e,n):typeof n==`bigint`?n.toString()+hf:n,n)}var hf,gf=o((()=>{hf=`#__bigint`}));function _f(e){if(!(e instanceof Uint8Array)&&(!e||typeof e!=`object`||!(`BYTES_PER_ELEMENT`in e)||e.BYTES_PER_ELEMENT!==1||e.constructor.name!==`Uint8Array`))throw new Rf(e)}function vf(...e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t);for(let t=0,r=0;t1||r[0]>1)throw new Lf(r);return!!r[0]}function Af(e,t={}){let{size:n}=t;return n!==void 0&&$d(e,n),tp(qf(e,t),t)}function jf(e,t={}){let{size:n}=t,r=e;return n!==void 0&&($d(r,n),r=Nf(r)),Ff.decode(r)}function Mf(e){return af(e,{dir:`left`})}function Nf(e){return af(e,{dir:`right`})}function Pf(e){try{return _f(e),!0}catch{return!1}}var Ff,If,Lf,Rf,zf,Bf,Vf,Hf=o((()=>{yc(),Qd(),pp(),sf(),pf(),gf(),Ff=new TextDecoder,If=new TextEncoder,Lf=class extends P{constructor(e){super(`Bytes value \`${e}\` is not a valid boolean.`,{metaMessages:["The bytes array must contain a single byte of either a `0` or `1` value."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesBooleanError`})}},Rf=class extends P{constructor(e){super(`Value \`${typeof e==`object`?mf(e):e}\` of type \`${typeof e}\` is an invalid Bytes value.`,{metaMessages:["Bytes values must be of type `Bytes`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesTypeError`})}},zf=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeOverflowError`})}},Bf=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SliceOffsetOutOfBoundsError`})}},Vf=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeExceedsPaddingSizeError`})}}}));function Uf(e,t={}){let{strict:n=!1}=t;if(!e||typeof e!=`string`)throw new sp(e);if(n&&!/^0x[0-9a-fA-F]*$/.test(e)||!e.startsWith(`0x`))throw new cp(e)}function Wf(...e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}function Gf(e){return e instanceof Uint8Array?qf(e):Array.isArray(e)?qf(new Uint8Array(e)):e}function Kf(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(cf(n,t.size),Yf(n,t.size)):n}function qf(e,t={}){let n=``;for(let t=0;ta||i>1n?r:r-a-1n}function tp(e,t={}){let{signed:n,size:r}=t;return Number(!n&&!r?e:ep(e,t))}function np(e,t={}){let{size:n}=t,r=xf(e);return n&&($d(r,n),r=Nf(r)),new TextDecoder().decode(r)}function rp(e,t={}){let{strict:n=!1}=t;try{return Uf(e,{strict:n}),!0}catch{return!1}}var ip,ap,op,sp,cp,lp,up,dp,fp,pp=o((()=>{Hf(),Qd(),sf(),pf(),gf(),ip=new TextEncoder,ap=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),op=class extends P{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number \`${i}\` is not in safe${r?` ${r*8}-bit`:``}${n?` signed`:` unsigned`} integer range ${e?`(\`${t}\` to \`${e}\`)`:`(above \`${t}\`)`}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.IntegerOutOfRangeError`})}},sp=class extends P{constructor(e){super(`Value \`${typeof e==`object`?mf(e):e}\` of type \`${typeof e}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexTypeError`})}},cp=class extends P{constructor(e){super(`Value \`${e}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexValueError`})}},lp=class extends P{constructor(e){super(`Hex value \`"${e}"\` is an odd length (${e.length-2} nibbles).`,{metaMessages:[`It must be an even length.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidLengthError`})}},up=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeOverflowError`})}},dp=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SliceOffsetOutOfBoundsError`})}},fp=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeExceedsPaddingSizeError`})}}}));function mp(e){return{address:e.address,amount:F(e.amount),index:F(e.index),validatorIndex:F(e.validatorIndex)}}var hp=o((()=>{pp()}));function gp(e){return{...typeof e.baseFeePerGas==`bigint`&&{baseFeePerGas:F(e.baseFeePerGas)},...typeof e.blobBaseFee==`bigint`&&{blobBaseFee:F(e.blobBaseFee)},...typeof e.feeRecipient==`string`&&{feeRecipient:e.feeRecipient},...typeof e.gasLimit==`bigint`&&{gasLimit:F(e.gasLimit)},...typeof e.number==`bigint`&&{number:F(e.number)},...typeof e.prevRandao==`bigint`&&{prevRandao:F(e.prevRandao)},...typeof e.time==`bigint`&&{time:F(e.time)},...e.withdrawals&&{withdrawals:e.withdrawals.map(mp)}}}var _p=o((()=>{pp(),hp()})),vp,yp,bp,xp,Sp,Cp=o((()=>{vp=[{inputs:[{components:[{name:`target`,type:`address`},{name:`allowFailure`,type:`bool`},{name:`callData`,type:`bytes`}],name:`calls`,type:`tuple[]`}],name:`aggregate3`,outputs:[{components:[{name:`success`,type:`bool`},{name:`returnData`,type:`bytes`}],name:`returnData`,type:`tuple[]`}],stateMutability:`view`,type:`function`},{inputs:[{name:`addr`,type:`address`}],name:`getEthBalance`,outputs:[{name:`balance`,type:`uint256`}],stateMutability:`view`,type:`function`},{inputs:[],name:`getCurrentBlockTimestamp`,outputs:[{internalType:`uint256`,name:`timestamp`,type:`uint256`}],stateMutability:`view`,type:`function`}],yp=[{name:`query`,type:`function`,stateMutability:`view`,inputs:[{type:`tuple[]`,name:`queries`,components:[{type:`address`,name:`sender`},{type:`string[]`,name:`urls`},{type:`bytes`,name:`data`}]}],outputs:[{type:`bool[]`,name:`failures`},{type:`bytes[]`,name:`responses`}]},{name:`HttpError`,type:`error`,inputs:[{type:`uint16`,name:`status`},{type:`string`,name:`message`}]}],bp=[{inputs:[{name:`dns`,type:`bytes`}],name:`DNSDecodingFailed`,type:`error`},{inputs:[{name:`ens`,type:`string`}],name:`DNSEncodingFailed`,type:`error`},{inputs:[],name:`EmptyAddress`,type:`error`},{inputs:[{name:`status`,type:`uint16`},{name:`message`,type:`string`}],name:`HttpError`,type:`error`},{inputs:[],name:`InvalidBatchGatewayResponse`,type:`error`},{inputs:[{name:`errorData`,type:`bytes`}],name:`ResolverError`,type:`error`},{inputs:[{name:`name`,type:`bytes`},{name:`resolver`,type:`address`}],name:`ResolverNotContract`,type:`error`},{inputs:[{name:`name`,type:`bytes`}],name:`ResolverNotFound`,type:`error`},{inputs:[{name:`primary`,type:`string`},{name:`primaryAddress`,type:`bytes`}],name:`ReverseAddressMismatch`,type:`error`},{inputs:[{internalType:`bytes4`,name:`selector`,type:`bytes4`}],name:`UnsupportedResolverProfile`,type:`error`}],[...bp],[...bp],xp=[{name:`isValidSignature`,type:`function`,stateMutability:`view`,inputs:[{name:`hash`,type:`bytes32`},{name:`signature`,type:`bytes`}],outputs:[{name:``,type:`bytes4`}]}],Sp=[{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],stateMutability:`nonpayable`,type:`constructor`},{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],outputs:[{type:`bool`}],stateMutability:`nonpayable`,type:`function`,name:`isValidSig`}]})),wp=o((()=>{})),Tp,Ep,Dp,Op,kp=o((()=>{Tp=`0x608060405234801561001057600080fd5b5060405161018e38038061018e83398101604081905261002f91610124565b6000808351602085016000f59050803b61004857600080fd5b6000808351602085016000855af16040513d6000823e81610067573d81fd5b3d81f35b634e487b7160e01b600052604160045260246000fd5b600082601f83011261009257600080fd5b81516001600160401b038111156100ab576100ab61006b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156100d9576100d961006b565b6040528181528382016020018510156100f157600080fd5b60005b82811015610110576020818601810151838301820152016100f4565b506000918101602001919091529392505050565b6000806040838503121561013757600080fd5b82516001600160401b0381111561014d57600080fd5b61015985828601610081565b602085015190935090506001600160401b0381111561017757600080fd5b61018385828601610081565b915050925092905056fe`,Ep=`0x608060405234801561001057600080fd5b506040516102c03803806102c083398101604081905261002f916101e6565b836001600160a01b03163b6000036100e457600080836001600160a01b03168360405161005c9190610270565b6000604051808303816000865af19150503d8060008114610099576040519150601f19603f3d011682016040523d82523d6000602084013e61009e565b606091505b50915091508115806100b857506001600160a01b0386163b155b156100e1578060405163101bb98d60e01b81526004016100d8919061028c565b60405180910390fd5b50505b6000808451602086016000885af16040513d6000823e81610103573d81fd5b3d81f35b80516001600160a01b038116811461011e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015457818101518382015260200161013c565b50506000910152565b600082601f83011261016e57600080fd5b81516001600160401b0381111561018757610187610123565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101b5576101b5610123565b6040528181528382016020018510156101cd57600080fd5b6101de826020830160208701610139565b949350505050565b600080600080608085870312156101fc57600080fd5b61020585610107565b60208601519094506001600160401b0381111561022157600080fd5b61022d8782880161015d565b93505061023c60408601610107565b60608601519092506001600160401b0381111561025857600080fd5b6102648782880161015d565b91505092959194509250565b60008251610282818460208701610139565b9190910192915050565b60208152600082518060208401526102ab816040850160208701610139565b601f01601f1916919091016040019291505056fe`,Dp=`0x608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572`,Op=`0x608060405234801561001057600080fd5b506115b9806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e14610325578063bce38bd714610350578063c3077fa914610380578063ee82ac5e146103b2576100f3565b80634d2301cc1461026257806372425d9d1461029f57806382ad56cb146102ca57806386d516e8146102fa576100f3565b80633408e470116100c65780633408e470146101af578063399542e9146101da5780633e64a6961461020c57806342cbb15c14610237576100f3565b80630f28c97d146100f8578063174dea7114610123578063252dba421461015357806327e86d6e14610184575b600080fd5b34801561010457600080fd5b5061010d6103ef565b60405161011a9190610c0a565b60405180910390f35b61013d60048036038101906101389190610c94565b6103f7565b60405161014a9190610e94565b60405180910390f35b61016d60048036038101906101689190610f0c565b610615565b60405161017b92919061101b565b60405180910390f35b34801561019057600080fd5b506101996107ab565b6040516101a69190611064565b60405180910390f35b3480156101bb57600080fd5b506101c46107b7565b6040516101d19190610c0a565b60405180910390f35b6101f460048036038101906101ef91906110ab565b6107bf565b6040516102039392919061110b565b60405180910390f35b34801561021857600080fd5b506102216107e1565b60405161022e9190610c0a565b60405180910390f35b34801561024357600080fd5b5061024c6107e9565b6040516102599190610c0a565b60405180910390f35b34801561026e57600080fd5b50610289600480360381019061028491906111a7565b6107f1565b6040516102969190610c0a565b60405180910390f35b3480156102ab57600080fd5b506102b4610812565b6040516102c19190610c0a565b60405180910390f35b6102e460048036038101906102df919061122a565b61081a565b6040516102f19190610e94565b60405180910390f35b34801561030657600080fd5b5061030f6109e4565b60405161031c9190610c0a565b60405180910390f35b34801561033157600080fd5b5061033a6109ec565b6040516103479190611286565b60405180910390f35b61036a600480360381019061036591906110ab565b6109f4565b6040516103779190610e94565b60405180910390f35b61039a60048036038101906103959190610f0c565b610ba6565b6040516103a99392919061110b565b60405180910390f35b3480156103be57600080fd5b506103d960048036038101906103d491906112cd565b610bca565b6040516103e69190611064565b60405180910390f35b600042905090565b60606000808484905090508067ffffffffffffffff81111561041c5761041b6112fa565b5b60405190808252806020026020018201604052801561045557816020015b610442610bd5565b81526020019060019003908161043a5790505b5092503660005b828110156105c957600085828151811061047957610478611329565b5b6020026020010151905087878381811061049657610495611329565b5b90506020028101906104a89190611367565b925060008360400135905080860195508360000160208101906104cb91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16818580606001906104f2919061138f565b604051610500929190611431565b60006040518083038185875af1925050503d806000811461053d576040519150601f19603f3d011682016040523d82523d6000602084013e610542565b606091505b5083600001846020018290528215151515815250505081516020850135176105bc577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b826001019250505061045c565b5082341461060c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610603906114a7565b60405180910390fd5b50505092915050565b6000606043915060008484905090508067ffffffffffffffff81111561063e5761063d6112fa565b5b60405190808252806020026020018201604052801561067157816020015b606081526020019060019003908161065c5790505b5091503660005b828110156107a157600087878381811061069557610694611329565b5b90506020028101906106a791906114c7565b92508260000160208101906106bc91906111a7565b73ffffffffffffffffffffffffffffffffffffffff168380602001906106e2919061138f565b6040516106f0929190611431565b6000604051808303816000865af19150503d806000811461072d576040519150601f19603f3d011682016040523d82523d6000602084013e610732565b606091505b5086848151811061074657610745611329565b5b60200260200101819052819250505080610795576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161078c9061153b565b60405180910390fd5b81600101915050610678565b5050509250929050565b60006001430340905090565b600046905090565b6000806060439250434091506107d68686866109f4565b905093509350939050565b600048905090565b600043905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b606060008383905090508067ffffffffffffffff81111561083e5761083d6112fa565b5b60405190808252806020026020018201604052801561087757816020015b610864610bd5565b81526020019060019003908161085c5790505b5091503660005b828110156109db57600084828151811061089b5761089a611329565b5b602002602001015190508686838181106108b8576108b7611329565b5b90506020028101906108ca919061155b565b92508260000160208101906108df91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060400190610905919061138f565b604051610913929190611431565b6000604051808303816000865af19150503d8060008114610950576040519150601f19603f3d011682016040523d82523d6000602084013e610955565b606091505b5082600001836020018290528215151515815250505080516020840135176109cf577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b8160010191505061087e565b50505092915050565b600045905090565b600041905090565b606060008383905090508067ffffffffffffffff811115610a1857610a176112fa565b5b604051908082528060200260200182016040528015610a5157816020015b610a3e610bd5565b815260200190600190039081610a365790505b5091503660005b82811015610b9c576000848281518110610a7557610a74611329565b5b60200260200101519050868683818110610a9257610a91611329565b5b9050602002810190610aa491906114c7565b9250826000016020810190610ab991906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060200190610adf919061138f565b604051610aed929190611431565b6000604051808303816000865af19150503d8060008114610b2a576040519150601f19603f3d011682016040523d82523d6000602084013e610b2f565b606091505b508260000183602001829052821515151581525050508715610b90578060000151610b8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b869061153b565b60405180910390fd5b5b81600101915050610a58565b5050509392505050565b6000806060610bb7600186866107bf565b8093508194508295505050509250925092565b600081409050919050565b6040518060400160405280600015158152602001606081525090565b6000819050919050565b610c0481610bf1565b82525050565b6000602082019050610c1f6000830184610bfb565b92915050565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f840112610c5457610c53610c2f565b5b8235905067ffffffffffffffff811115610c7157610c70610c34565b5b602083019150836020820283011115610c8d57610c8c610c39565b5b9250929050565b60008060208385031215610cab57610caa610c25565b5b600083013567ffffffffffffffff811115610cc957610cc8610c2a565b5b610cd585828601610c3e565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b610d2281610d0d565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610d62578082015181840152602081019050610d47565b83811115610d71576000848401525b50505050565b6000601f19601f8301169050919050565b6000610d9382610d28565b610d9d8185610d33565b9350610dad818560208601610d44565b610db681610d77565b840191505092915050565b6000604083016000830151610dd96000860182610d19565b5060208301518482036020860152610df18282610d88565b9150508091505092915050565b6000610e0a8383610dc1565b905092915050565b6000602082019050919050565b6000610e2a82610ce1565b610e348185610cec565b935083602082028501610e4685610cfd565b8060005b85811015610e825784840389528151610e638582610dfe565b9450610e6e83610e12565b925060208a01995050600181019050610e4a565b50829750879550505050505092915050565b60006020820190508181036000830152610eae8184610e1f565b905092915050565b60008083601f840112610ecc57610ecb610c2f565b5b8235905067ffffffffffffffff811115610ee957610ee8610c34565b5b602083019150836020820283011115610f0557610f04610c39565b5b9250929050565b60008060208385031215610f2357610f22610c25565b5b600083013567ffffffffffffffff811115610f4157610f40610c2a565b5b610f4d85828601610eb6565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000610f918383610d88565b905092915050565b6000602082019050919050565b6000610fb182610f59565b610fbb8185610f64565b935083602082028501610fcd85610f75565b8060005b858110156110095784840389528151610fea8582610f85565b9450610ff583610f99565b925060208a01995050600181019050610fd1565b50829750879550505050505092915050565b60006040820190506110306000830185610bfb565b81810360208301526110428184610fa6565b90509392505050565b6000819050919050565b61105e8161104b565b82525050565b60006020820190506110796000830184611055565b92915050565b61108881610d0d565b811461109357600080fd5b50565b6000813590506110a58161107f565b92915050565b6000806000604084860312156110c4576110c3610c25565b5b60006110d286828701611096565b935050602084013567ffffffffffffffff8111156110f3576110f2610c2a565b5b6110ff86828701610eb6565b92509250509250925092565b60006060820190506111206000830186610bfb565b61112d6020830185611055565b818103604083015261113f8184610e1f565b9050949350505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061117482611149565b9050919050565b61118481611169565b811461118f57600080fd5b50565b6000813590506111a18161117b565b92915050565b6000602082840312156111bd576111bc610c25565b5b60006111cb84828501611192565b91505092915050565b60008083601f8401126111ea576111e9610c2f565b5b8235905067ffffffffffffffff81111561120757611206610c34565b5b60208301915083602082028301111561122357611222610c39565b5b9250929050565b6000806020838503121561124157611240610c25565b5b600083013567ffffffffffffffff81111561125f5761125e610c2a565b5b61126b858286016111d4565b92509250509250929050565b61128081611169565b82525050565b600060208201905061129b6000830184611277565b92915050565b6112aa81610bf1565b81146112b557600080fd5b50565b6000813590506112c7816112a1565b92915050565b6000602082840312156112e3576112e2610c25565b5b60006112f1848285016112b8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600080fd5b600080fd5b600080fd5b60008235600160800383360303811261138357611382611358565b5b80830191505092915050565b600080833560016020038436030381126113ac576113ab611358565b5b80840192508235915067ffffffffffffffff8211156113ce576113cd61135d565b5b6020830192506001820236038313156113ea576113e9611362565b5b509250929050565b600081905092915050565b82818337600083830152505050565b600061141883856113f2565b93506114258385846113fd565b82840190509392505050565b600061143e82848661140c565b91508190509392505050565b600082825260208201905092915050565b7f4d756c746963616c6c333a2076616c7565206d69736d61746368000000000000600082015250565b6000611491601a8361144a565b915061149c8261145b565b602082019050919050565b600060208201905081810360008301526114c081611484565b9050919050565b6000823560016040038336030381126114e3576114e2611358565b5b80830191505092915050565b7f4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000600082015250565b600061152560178361144a565b9150611530826114ef565b602082019050919050565b6000602082019050818103600083015261155481611518565b9050919050565b60008235600160600383360303811261157757611576611358565b5b8083019150509291505056fea264697066735822122020c1bc9aacf8e4a6507193432a895a8e77094f45a1395583f07b24e860ef06cd64736f6c634300080c0033`})),Ap,jp,Mp,Np,Pp,Fp=o((()=>{D(),Ap=class extends E{constructor({blockNumber:e,chain:t,contract:n}){super(`Chain "${t.name}" does not support contract "${n.name}".`,{metaMessages:[`This could be due to any of the following:`,...e&&n.blockCreated&&n.blockCreated>e?[`- The contract "${n.name}" was not deployed until block ${n.blockCreated} (current block ${e}).`]:[`- The chain does not have the contract "${n.name}" configured.`]],name:`ChainDoesNotSupportContract`})}},jp=class extends E{constructor({chain:e,currentChainId:t}){super(`The current chain of the wallet (id: ${t}) does not match the target chain for the transaction (id: ${e.id} – ${e.name}).`,{metaMessages:[`Current Chain ID: ${t}`,`Expected Chain ID: ${e.id} – ${e.name}`],name:`ChainMismatchError`})}},Mp=class extends E{constructor(){super([`No chain was provided to the request.`,"Please provide a chain with the `chain` argument on the Action, or by supplying a `chain` to WalletClient."].join(` +`),{name:`ChainNotFoundError`})}},Np=class extends E{constructor(){super(`No chain was provided to the Client.`,{name:`ClientChainNotConfiguredError`})}},Pp=class extends E{constructor({chainId:e}){super(typeof e==`number`?`Chain ID "${e}" is invalid.`:`Chain ID is invalid.`,{name:`InvalidChainIdError`})}}}));function Ip(e){let{abi:t,args:n,bytecode:r}=e;if(!n||n.length===0)return r;let i=t.find(e=>`type`in e&&e.type===`constructor`);if(!i)throw new Ht({docsPath:Lp});if(!(`inputs`in i)||!i.inputs||i.inputs.length===0)throw new Ut({docsPath:Lp});return ki([r,Bi(i.inputs,n)])}var Lp,Rp=o((()=>{cn(),Ai(),Qi(),Lp=`/docs/contract/encodeDeployData`}));function zp({blockNumber:e,chain:t,contract:n}){let r=t?.contracts?.[n];if(!r)throw new Ap({chain:t,contract:{name:n}});if(e&&r.blockCreated&&r.blockCreated>e)throw new Ap({blockNumber:e,chain:t,contract:{name:n,blockCreated:r.blockCreated}});return r.address}var Bp=o((()=>{Fp()}));function Vp(e,{docsPath:t,...n}){return new xo((()=>{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}var Hp=o((()=>{Eo(),Pu(),Iu()}));function Up(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}var Wp=o((()=>{}));function Gp({fn:e,id:t,shouldSplitBatch:n,wait:r=0,sort:i}){let a=async()=>{let t=c();o();let n=t.map(({args:e})=>e);n.length!==0&&e(n).then(e=>{i&&Array.isArray(e)&&e.sort(i);for(let n=0;n{for(let n=0;nKp.delete(t),s=()=>c().map(({args:e})=>e),c=()=>Kp.get(t)||[],l=e=>Kp.set(t,[...c(),e]);return{flush:o,async schedule(e){let{promise:t,resolve:i,reject:o}=Up();return n?.([...s(),e])&&a(),c().length>0?(l({args:e,resolve:i,reject:o}),t):(l({args:e,resolve:i,reject:o}),setTimeout(a,r),t)}}}var Kp,qp=o((()=>{Wp(),Kp=new Map})),Jp,Yp,Xp,Zp=o((()=>{Wa(),D(),bo(),Jp=class extends E{constructor({callbackSelector:e,cause:t,data:n,extraData:r,sender:i,urls:a}){super(t.shortMessage||`An error occurred while fetching for an offchain result.`,{cause:t,metaMessages:[...t.metaMessages||[],t.metaMessages?.length?``:[],`Offchain Gateway Call:`,a&&[` Gateway URL(s):`,...a.map(e=>` ${yo(e)}`)],` Sender: ${i}`,` Data: ${n}`,` Callback selector: ${e}`,` Extra data: ${r}`].flat(),name:`OffchainLookupError`})}},Yp=class extends E{constructor({result:e,url:t}){super(`Offchain gateway response is malformed. Response data must be a hex value.`,{metaMessages:[`Gateway URL: ${yo(t)}`,`Response: ${Ua(e)}`],name:`OffchainLookupResponseMalformedError`})}},Xp=class extends E{constructor({sender:e,to:t}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${t}`,`OffchainLookup sender address: ${e}`],name:`OffchainLookupSenderMismatchError`})}}}));function Qp(e){let{abi:t,data:n}=e,r=ji(n,0,4),i=t.find(e=>e.type===`function`&&r===$i(At(e)));if(!i)throw new en(r,{docsPath:`/docs/contract/decodeFunctionData`});return{functionName:i.name,args:`inputs`in i&&i.inputs&&i.inputs.length>0?Oa(i.inputs,ji(n,4)):void 0}}var $p=o((()=>{cn(),Ii(),ea(),Ba(),Nt()}));function em(e){let{abi:t,errorName:n,args:r}=e,i=t[0];if(n){let e=ta({abi:t,args:r,name:n});if(!e)throw new Xt(n,{docsPath:tm});i=e}if(i.type!==`error`)throw new Xt(void 0,{docsPath:tm});let a=$i(At(i)),o=`0x`;if(r&&r.length>0){if(!i.inputs)throw new Yt(i.name,{docsPath:tm});o=Bi(i.inputs,r)}return ki([a,o])}var tm,nm=o((()=>{cn(),Ai(),ea(),Qi(),Nt(),ia(),tm=`/docs/contract/encodeErrorResult`}));function rm(e){let{abi:t,functionName:n,result:r}=e,i=t[0];if(n){let e=ta({abi:t,name:n});if(!e)throw new Qt(n,{docsPath:im});i=e}if(i.type!==`function`)throw new Qt(void 0,{docsPath:im});if(!i.outputs)throw new $t(i.name,{docsPath:im});let a=(()=>{if(i.outputs.length===0)return[];if(i.outputs.length===1)return[r];if(Array.isArray(r))return r;throw new on(r)})();return Bi(i.outputs,a)}var im,am=o((()=>{cn(),Qi(),ia(),im=`/docs/contract/encodeFunctionResult`}));async function om(e){let{data:t,ccipRequest:n}=e,{args:[r]}=Qp({abi:yp,data:t}),i=[],a=[];return await Promise.all(r.map(async(e,t)=>{try{a[t]=e.urls.includes(`x-batch-gateway:true`)?await om({data:e.data,ccipRequest:n}):await n(e),i[t]=!1}catch(e){i[t]=!0,a[t]=sm(e)}})),rm({abi:yp,functionName:`query`,result:[i,a]})}function sm(e){return e.name===`HttpRequestError`&&e.status?em({abi:yp,errorName:`HttpError`,args:[e.status,e.shortMessage]}):em({abi:[pa],errorName:`Error`,args:[`shortMessage`in e?e.shortMessage:e.message]})}var cm=o((()=>{Cp(),ha(),$p(),nm(),am()})),lm=c({ccipRequest:()=>dm,offchainLookup:()=>um,offchainLookupAbiItem:()=>pm,offchainLookupSignature:()=>fm});async function um(e,{blockNumber:t,blockTag:n,data:r,to:i}){let{args:a}=Va({data:r,abi:[pm]}),[o,s,c,l,u]=a,{ccipRead:d}=e,f=d&&typeof d?.request==`function`?d.request:dm;try{if(!Vd(i,o))throw new Xp({sender:o,to:i});let{data:r}=await hm(e,{blockNumber:t,blockTag:n,data:Di([l,Bi([{type:`bytes`},{type:`bytes`}],[s.includes(`x-batch-gateway:true`)?await om({data:c,ccipRequest:f}):await f({data:c,sender:o,urls:s}),u])]),to:i});return r}catch(e){throw new Jp({callbackSelector:l,cause:e,data:r,extraData:u,sender:o,urls:s})}}async function dm({data:e,sender:t,urls:n}){let r=Error(`An unknown error occurred.`);for(let i=0;i{xm(),Zp(),Ao(),Ha(),Qi(),Hd(),Ai(),Ft(),cm(),Wa(),fm=`0x556f1830`,pm={name:`OffchainLookup`,type:`error`,inputs:[{name:`sender`,type:`address`},{name:`urls`,type:`string[]`},{name:`callData`,type:`bytes`},{name:`callbackFunction`,type:`bytes4`},{name:`extraData`,type:`bytes`}]}}));async function hm(e,t){let{account:n=e.account,authorizationList:r,batch:i=!!e.batch?.multicall,blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,accessList:s,blobs:c,blockOverrides:l,code:u,data:d,factory:f,factoryData:p,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:b,value:x,stateOverride:S,...ee}=t,C=n?aa(n):void 0;if(u&&(f||p))throw new E("Cannot provide both `code` & `factory`/`factoryData` as parameters.");if(u&&b)throw new E("Cannot provide both `code` & `to` as parameters.");let te=u&&d,ne=f&&p&&b&&d,re=te||ne,ie=te?vm({code:u,data:d}):ne?ym({data:d,factory:f,factoryData:p,to:b}):d;try{ed(t);let n=(typeof a==`bigint`?O(a):void 0)||o,u=l?gp(l):void 0,d=Yu(S),f=e.chain?.formatters?.transactionRequest?.format,p=(f||Hu)({...Ru(ee,{format:f}),accessList:s,account:C,authorizationList:r,blobs:c,data:ie,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:re?void 0:b,value:x},`call`);if(i&&gm({request:p})&&!d&&!u)try{return await _m(e,{...p,blockNumber:a,blockTag:o})}catch(e){if(!(e instanceof Np)&&!(e instanceof Ap))throw e}let te=(()=>{let e=[p,n];return d&&u?[...e,d,u]:d?[...e,d]:u?[...e,{},u]:e})(),ne=await e.request({method:`eth_call`,params:te});return ne===`0x`?{data:void 0}:{data:ne}}catch(n){let r=bm(n),{offchainLookup:i,offchainLookupSignature:a}=await ms(async()=>{let{offchainLookup:e,offchainLookupSignature:t}=await Promise.resolve().then(()=>(mm(),lm));return{offchainLookup:e,offchainLookupSignature:t}},void 0);if(e.ccipRead!==!1&&r?.slice(0,10)===a&&b)return{data:await i(e,{data:r,to:b})};throw re&&r?.slice(0,10)===`0x101bb98d`?new wo({factory:f}):Vp(n,{...t,account:C,chain:e.chain})}}function gm({request:e}){let{data:t,to:n,...r}=e;return!(!t||t.startsWith(`0x82ad56cb`)||!n||Object.values(r).filter(e=>e!==void 0).length>0)}async function _m(e,t){let{batchSize:n=1024,deployless:r=!1,wait:i=0}=typeof e.batch?.multicall==`object`?e.batch.multicall:{},{blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,data:s,to:c}=t,l=(()=>{if(r)return null;if(t.multicallAddress)return t.multicallAddress;if(e.chain)return zp({blockNumber:a,chain:e.chain,contract:`multicall3`});throw new Np})(),u=(typeof a==`bigint`?O(a):void 0)||o,{schedule:d}=Gp({id:`${e.uid}.${u}`,wait:i,shouldSplitBatch(e){return e.reduce((e,{data:t})=>e+(t.length-2),0)>n*2},fn:async t=>{let n=t.map(e=>({allowFailure:!0,callData:e.data,target:e.to})),r=ua({abi:vp,args:[n],functionName:`aggregate3`}),i=await e.request({method:`eth_call`,params:[{...l===null?{data:vm({code:Op,data:r})}:{to:l,data:r}},u]});return Wd({abi:vp,args:[n],functionName:`aggregate3`,data:i||`0x`})}}),[{returnData:f,success:p}]=await d({data:s,to:c});if(!p)throw new To({data:f});return f===`0x`?{data:void 0}:{data:f}}function vm(e){let{code:t,data:n}=e;return Ip({abi:Ct([`constructor(bytes, bytes)`]),bytecode:Tp,args:[t,n]})}function ym(e){let{data:t,factory:n,factoryData:r,to:i}=e;return Ip({abi:Ct([`constructor(address, bytes, address, bytes)`]),bytecode:Ep,args:[i,t,n,r]})}function bm(e){if(!(e instanceof E))return;let t=e.walk();return typeof t?.data==`object`?t.data?.data:t.data}var xm=o((()=>{kt(),_p(),oa(),Cp(),wp(),kp(),D(),Fp(),Eo(),Kd(),Rp(),Bp(),k(),Hp(),zu(),Ku(),qp(),Xu(),td(),hs()}));Kd(),da(),xm();async function Sm(e,t){let{abi:n,address:r,args:i,functionName:a,...o}=t,s=ua({abi:n,args:i,functionName:a});try{let{data:t}=await T(e,hm,`call`)({...o,data:s,to:r});return Wd({abi:n,args:i,functionName:a,data:t||`0x`})}catch(e){throw ls(e,{abi:n,address:r,args:i,docsPath:`/docs/contract/readContract`,functionName:a})}}var Cm=new Map,wm=new Map,Tm=0;function Em(e,t,n){let r=++Tm,i=()=>Cm.get(e)||[],a=()=>{let t=i();Cm.set(e,t.filter(e=>e.id!==r))},o=()=>{let t=i();if(!t.some(e=>e.id===r))return;let n=wm.get(e);if(t.length===1&&n){let e=n();e instanceof Promise&&e.catch(()=>{})}a()},s=i();if(Cm.set(e,[...s,{id:r,fns:t}]),s&&s.length>0)return o;let c={};for(let e in t)c[e]=((...t)=>{let n=i();if(n.length!==0)for(let r of n)r.fns[e]?.(...t)});let l=n(c);return typeof l==`function`&&wm.set(e,l),o}async function Dm(e){return new Promise(t=>setTimeout(t,e))}function Om(e,{emitOnBegin:t,initialWaitTime:n,interval:r}){let i=!0,a=()=>i=!1;return(async()=>{let o;t&&(o=await e({unpoll:a})),await Dm(await n?.(o)??r);let s=async()=>{i&&(await e({unpoll:a}),await Dm(r),s())};s()})(),a}var km=new Map,Am=new Map;function jm(e){let t=(e,t)=>({clear:()=>t.delete(e),get:()=>t.get(e),set:n=>t.set(e,n)}),n=t(e,km),r=t(e,Am);return{clear:()=>{n.clear(),r.clear()},promise:n,response:r}}async function Mm(e,{cacheKey:t,cacheTime:n=1/0}){let r=jm(t),i=r.response.get();if(i&&n>0&&Date.now()-i.created.getTime()`blockNumber.${e}`;async function Pm(e,{cacheTime:t=e.cacheTime}={}){let n=await Mm(()=>e.request({method:`eth_blockNumber`}),{cacheKey:Nm(e.uid),cacheTime:t});return BigInt(n)}D();var Fm=class extends E{constructor({docsPath:e}={}){super([`Could not find an Account to execute with this Action.`,"Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client."].join(` +`),{docsPath:e,docsSlug:`account`,name:`AccountNotFoundError`})}},Im=class extends E{constructor({docsPath:e,metaMessages:t,type:n}){super(`Account type "${n}" is not supported.`,{docsPath:e,metaMessages:t,name:`AccountTypeNotSupportedError`})}};Fp();function Lm({chain:e,currentChainId:t}){if(!e)throw new Mp;if(t!==e.id)throw new jp({chain:e,currentChainId:t})}async function Rm(e,{serializedTransaction:t}){return e.request({method:`eth_sendRawTransaction`,params:[t]},{retryCount:0})}oa(),D(),Ai(),zu(),Ku(),vi(),td();var zm=new _i(128);async function Bm(e,t){let{account:n=e.account,assertChainId:r=!0,chain:i=e.chain,accessList:a,authorizationList:o,blobs:s,data:c,dataSuffix:l=typeof e.dataSuffix==`string`?e.dataSuffix:e.dataSuffix?.value,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,type:g,value:_,...v}=t;if(n===void 0)throw new Fm({docsPath:`/docs/actions/wallet/sendTransaction`});let y=n?aa(n):null;try{ed(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&o&&o.length>0)return await bu({authorization:o[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`.")})})();if(y?.type===`json-rpc`||y===null){let t;i!==null&&(t=await T(e,Fd,`getChainId`)({}),r&&Lm({currentChainId:t,chain:i}));let b=e.chain?.formatters?.transactionRequest?.format,x=(b||Hu)({...Ru(v,{format:b}),accessList:a,account:y,authorizationList:o,blobs:s,chainId:t,data:l?Di([c??`0x`,l]):c,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,to:n,type:g,value:_},`sendTransaction`),S=zm.get(e.uid),ee=S?`wallet_sendTransaction`:`eth_sendTransaction`;try{return await e.request({method:ee,params:[x]},{retryCount:0})}catch(t){if(S===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[x]},{retryCount:0}).then(t=>(zm.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(zm.set(e.uid,!1),n):r});throw n}}if(y?.type===`local`){let t=await T(e,zd,`prepareTransactionRequest`)({account:y,accessList:a,authorizationList:o,blobs:s,chain:i,data:l?Di([c??`0x`,l]):c,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,nonceManager:y.nonceManager,parameters:[...Ld,`sidecars`],type:g,value:_,...v,to:n}),r=i?.serializers?.transaction,b=await y.signTransaction(t,{serializer:r});return await T(e,Rm,`sendRawTransaction`)({serializedTransaction:b})}throw y?.type===`smart`?new Im({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new Im({docsPath:`/docs/actions/wallet/sendTransaction`,type:y?.type})}catch(e){throw e instanceof Im?e:Pd(e,{...t,account:y,chain:t.chain||void 0})}}oa(),da();async function Vm(e,t){return Vm.internal(e,Bm,`sendTransaction`,t)}(function(e){async function t(e,t,n,r){let{abi:i,account:a=e.account,address:o,args:s,functionName:c,...l}=r;if(a===void 0)throw new Fm({docsPath:`/docs/contract/writeContract`});let u=a?aa(a):null,d=ua({abi:i,args:s,functionName:c});try{return await T(e,t,n)({data:d,to:o,account:u,...l})}catch(e){throw ls(e,{abi:i,address:o,args:s,docsPath:`/docs/contract/writeContract`,functionName:c,sender:u?.address})}}e.internal=t})(Vm||={}),D();var Hm=class extends E{constructor(e){super(`Call bundle failed with status: ${e.statusCode}`,{name:`BundleFailedError`}),Object.defineProperty(this,`result`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.result=e}};function Um(e,{delay:t=100,retryCount:n=2,shouldRetry:r=()=>!0}={}){return new Promise((i,a)=>{let o=async({count:s=0}={})=>{let c=async({error:e})=>{let n=typeof t==`function`?t({count:s,error:e}):t;n&&await Dm(n),o({count:s+1})};try{i(await e())}catch(e){if(sUd(e)):null,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Dn(e.transactionIndex):null,status:e.status?Wm[e.status]:null,type:e.type?od[e.type]||e.type:null};return e.blobGasPrice&&(n.blobGasPrice=BigInt(e.blobGasPrice)),e.blobGasUsed&&(n.blobGasUsed=BigInt(e.blobGasUsed)),n}var Km=Bu(`transactionReceipt`,Gm);oa(),D(),ss(),da(),Ai(),On(),k();var qm=`0x5792579257925792579257925792579257925792579257925792579257925792`,Jm=O(0,{size:32});async function Ym(e,t){let{account:n=e.account,chain:r=e.chain,experimental_fallback:i,experimental_fallbackDelay:a=32,forceAtomic:o=!1,id:s,version:c=`2.0.0`}=t,l=n?aa(n):null,u=t.capabilities;e.dataSuffix&&!t.capabilities?.dataSuffix&&(u=typeof e.dataSuffix==`string`?{...t.capabilities,dataSuffix:{value:e.dataSuffix,optional:!0}}:{...t.capabilities,dataSuffix:{value:e.dataSuffix.value,...e.dataSuffix.required?{}:{optional:!0}}});let d=t.calls.map(e=>{let t=e,n=t.abi?ua({abi:t.abi,functionName:t.functionName,args:t.args}):t.data;return{data:t.dataSuffix&&n?Di([n,t.dataSuffix]):n,to:t.to,value:t.value?O(t.value):void 0}});try{let t=await e.request({method:`wallet_sendCalls`,params:[{atomicRequired:o,calls:d,capabilities:u,chainId:O(r.id),from:l?.address,id:s,version:c}]},{retryCount:0});return typeof t==`string`?{id:t}:t}catch(n){let s=n;if(i&&(s.name===`MethodNotFoundRpcError`||s.name===`MethodNotSupportedRpcError`||s.name===`UnknownRpcError`||s.details.toLowerCase().includes(`does not exist / is not available`)||s.details.toLowerCase().includes(`missing or invalid. request()`)||s.details.toLowerCase().includes(`did not match any variant of untagged enum`)||s.details.toLowerCase().includes(`account upgraded to unsupported contract`)||s.details.toLowerCase().includes(`eip-7702 not supported`)||s.details.toLowerCase().includes(`unsupported wc_ method`)||s.details.toLowerCase().includes(`feature toggled misconfigured`)||s.details.toLowerCase().includes(`jsonrpcengine: response has no error or result for request`))){if(u&&Object.values(u).some(e=>!e.optional)){let e="non-optional `capabilities` are not supported on fallback to `eth_sendTransaction`.";throw new Qo(new E(e,{details:e}))}if(o&&d.length>1){let e="`forceAtomic` is not supported on fallback to `eth_sendTransaction`.";throw new is(new E(e,{details:e}))}let t=[];for(let n of d){let i=Bm(e,{account:l,chain:r,data:n.data,to:n.to,value:n.value?Tn(n.value):void 0});t.push(i),a>0&&await new Promise(e=>setTimeout(e,a))}let n=await Promise.allSettled(t);if(n.every(e=>e.status===`rejected`))throw n[0].reason;return{id:Di([...n.map(e=>e.status===`fulfilled`?e.value:Jm),O(r.id,{size:32}),qm])}}throw Pd(n,{...t,account:l,chain:t.chain})}}Ii(),Cn(),On();async function Xm(e,t){async function n(t){if(t.endsWith(`5792579257925792579257925792579257925792579257925792579257925792`)){let n=Sn(Fi(t,-64,-32)),r=Fi(t,0,-64).slice(2).match(/.{1,64}/g),i=await Promise.all(r.map(t=>Jm.slice(2)===t?void 0:e.request({method:`eth_getTransactionReceipt`,params:[`0x${t}`]},{dedupe:!0}))),a=i.some(e=>e===null)?100:i.every(e=>e?.status===`0x1`)?200:i.every(e=>e?.status===`0x0`)?500:600;return{atomic:!1,chainId:Dn(n),receipts:i.filter(Boolean),status:a,version:`2.0.0`}}return e.request({method:`wallet_getCallsStatus`,params:[t]})}let{atomic:r=!1,chainId:i,receipts:a,version:o=`2.0.0`,...s}=await n(t.id),[c,l]=(()=>{let e=s.status;return e>=100&&e<200?[`pending`,e]:e>=200&&e<300?[`success`,e]:e>=300&&e<700?[`failure`,e]:e===`CONFIRMED`?[`success`,200]:e===`PENDING`?[`pending`,100]:[void 0,e]})();return{...s,atomic:r,chainId:i?Dn(i):void 0,receipts:a?.map(e=>({...e,blockNumber:Tn(e.blockNumber),gasUsed:Tn(e.gasUsed),status:Wm[e.status]}))??[],statusCode:l,status:c,version:o}}D(),Wp(),Wa();async function Zm(e,t){let{id:n,pollingInterval:r=e.pollingInterval,status:i=({statusCode:e})=>e===200||e>=300,retryCount:a=4,retryDelay:o=({count:e})=>~~(1<{let s=Om(async()=>{let r=e=>{clearTimeout(p),s(),e(),m()};try{let s=await Um(async()=>{let t=await T(e,Xm,`getCallsStatus`)({id:n});if(c&&t.status===`failure`)throw new Hm(t);return t},{retryCount:a,delay:o});if(!i(s))return;r(()=>t.resolve(s))}catch(e){r(()=>t.reject(e))}},{interval:r,emitOnBegin:!0});return s});return p=s?setTimeout(()=>{m(),clearTimeout(p),f(new Qm({id:n}))},s):void 0,await u}var Qm=class extends E{constructor({id:e}){super(`Timed out while waiting for call bundle with id "${e}" to be confirmed.`,{name:`WaitForCallsStatusTimeoutError`})}},$m=256,eh=$m,th;function nh(e=11){if(!th||eh+e>$m*2){th=``,eh=0;for(let e=0;e<$m;e++)th+=(256+Math.random()*256|0).toString(16).substring(1)}return th.substring(eh,eh+++e)}oa();function rh(e){let{batch:t,chain:n,ccipRead:r,dataSuffix:i,key:a=`base`,name:o=`Base Client`,type:s=`base`}=e,c=e.experimental_blockTag??(typeof n?.experimental_preconfirmationTime==`number`?`pending`:void 0),l=n?.blockTime??12e3,u=Math.min(Math.max(Math.floor(l/2),500),4e3),d=e.pollingInterval??u,f=e.cacheTime??d,p=e.account?aa(e.account):void 0,{config:m,request:h,value:g}=e.transport({account:p,chain:n,pollingInterval:d}),_={account:p,batch:t,cacheTime:f,ccipRead:r,chain:n,dataSuffix:i,key:a,name:o,pollingInterval:d,request:h,transport:{...m,...g},type:s,uid:nh(),...c?{experimental_blockTag:c}:{}};function v(e){return t=>{let n=t(e);for(let e in _)delete n[e];let r={...e,...n};return Object.assign(r,{extend:v(r)})}}return Object.assign(_,{extend:v(_)})}k();async function ih(e,{address:t,blockNumber:n,blockTag:r=`latest`}){let i=n===void 0?void 0:O(n),a=await e.request({method:`eth_getCode`,params:[t,i||r]},{dedupe:!!i});if(a!==`0x`)return a}D();var ah=class extends E{constructor({address:e}){super(`No EIP-712 domain found on contract "${e}".`,{metaMessages:[`Ensure that:`,`- The contract is deployed at the address "${e}".`,"- `eip712Domain()` function exists on the contract.","- `eip712Domain()` function matches signature to ERC-5267 specification."],name:`Eip712DomainNotFoundError`})}};async function oh(e,t){let{address:n,factory:r,factoryData:i}=t;try{let[t,a,o,s,c,l,u]=await T(e,Sm,`readContract`)({abi:sh,address:n,functionName:`eip712Domain`,factory:r,factoryData:i});return{domain:{name:a,version:o,chainId:Number(s),verifyingContract:c,salt:l},extensions:u,fields:t}}catch(e){let t=e;throw t.name===`ContractFunctionExecutionError`&&t.cause.name===`ContractFunctionZeroDataError`?new ah({address:n}):t}}var sh=[{inputs:[],name:`eip712Domain`,outputs:[{name:`fields`,type:`bytes1`},{name:`name`,type:`string`},{name:`version`,type:`string`},{name:`chainId`,type:`uint256`},{name:`verifyingContract`,type:`address`},{name:`salt`,type:`bytes32`},{name:`extensions`,type:`uint256[]`}],stateMutability:`view`,type:`function`}];$u(),gi(),D(),Fp(),Pu(),Ei(),Lt(),Ii(),On();function ch(e){let{authorizationList:t}=e;if(t)for(let e of t){let{chainId:t}=e,n=e.address;if(!Ci(n))throw new hi({address:n});if(t<0)throw new Pp({chainId:t})}uh(e)}function lh(e){let{blobVersionedHashes:t}=e;if(t){if(t.length===0)throw new Od;for(let e of t){let t=It(e),n=Dn(ji(e,0,1));if(t!==32)throw new kd({hash:e,size:t});if(n!==1)throw new Ad({hash:e,version:n})}}uh(e)}function uh(e){let{chainId:t,maxPriorityFeePerGas:n,maxFeePerGas:r,to:i}=e;if(t<=0)throw new Pp({chainId:t});if(i&&!Ci(i))throw new hi({address:i});if(r&&r>Qu)throw new Cu({maxFeePerGas:r});if(n&&r&&n>r)throw new Mu({maxFeePerGas:r,maxPriorityFeePerGas:n})}function dh(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(t<=0)throw new Pp({chainId:t});if(a&&!Ci(a))throw new hi({address:a});if(n||i)throw new E("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid EIP-2930 Transaction attribute.");if(r&&r>Qu)throw new Cu({maxFeePerGas:r})}function fh(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(a&&!Ci(a))throw new hi({address:a});if(t!==void 0&&t<=0)throw new Pp({chainId:t});if(n||i)throw new E("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid Legacy Transaction attribute.");if(r&&r>Qu)throw new Cu({maxFeePerGas:r})}gi(),_o(),Ei();function ph(e){if(!e||e.length===0)return[];let t=[];for(let n=0;njn(e)),n=e.kzg,r=_d({blobs:t,kzg:n});f===void 0&&(f=Sd({commitments:r})),p===void 0&&(p=Md({blobs:t,commitments:r,proofs:vd({blobs:t,commitments:r,kzg:n})}))}let m=ph(u),h=[O(n),i?O(i):`0x`,l?O(l):`0x`,c?O(c):`0x`,r?O(r):`0x`,a??`0x`,o?O(o):`0x`,d??`0x`,m,s?O(s):`0x`,f??[],...bh(e,t)],g=[],_=[],v=[];if(p)for(let e=0;e{if(t.v>=35n)return(t.v-35n)/2n>0?t.v:27n+(t.v===35n?0n:1n);if(n>0)return BigInt(n*2)+BigInt(35n+t.v-27n);let e=27n+(t.v===27n?0n:1n);if(t.v!==e)throw new co({v:t.v});return e})(),r=Sn(t.r),i=Sn(t.s);l=[...l,O(e),r===`0x00`?`0x`:r,i===`0x00`?`0x`:i]}else n>0&&(l=[...l,O(n),`0x`,`0x`]);return mu(l)}function bh(e,t){let n=t??e,{v:r,yParity:i}=n;if(n.r===void 0||n.s===void 0||r===void 0&&i===void 0)return[];let a=Sn(n.r),o=Sn(n.s);return[typeof i==`number`?i?O(1):`0x`:r===0n?`0x`:r===1n?O(1):r===27n?`0x`:O(1),a===`0x00`?`0x`:a,o===`0x00`?`0x`:o]}k();function xh(e){if(!e||e.length===0)return[];let t=[];for(let n of e){let{chainId:e,nonce:r,...i}=n,a=n.address;t.push([e?kn(e):`0x`,a,r?kn(r):`0x`,...bh({},i)])}return t}Si(),Hd();async function Sh({address:e,authorization:t,signature:n}){return Vd(bi(e),await bu({authorization:t,signature:n}))}vi();var Ch=new _i(8192);function wh(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(Ch.get(n))return Ch.get(n);let r=e().finally(()=>Ch.delete(n));return Ch.set(n,r),r}D(),Ao(),ss(),k(),Wa();function Th(e,t={}){return async(n,r={})=>{let{dedupe:i=!1,methods:a,retryDelay:o=150,retryCount:s=3,uid:c}={...t,...r},{method:l}=n;if(a?.exclude?.includes(l)||a?.include&&!a.include.includes(l))throw new Uo(Error(`method not supported`),{method:l});return wh(()=>Um(async()=>{try{return await e(n)}catch(e){let t=e;switch(t.code){case Po.code:throw new Po(t);case Fo.code:throw new Fo(t);case Io.code:throw new Io(t,{method:n.method});case Lo.code:throw new Lo(t);case Ro.code:throw new Ro(t);case zo.code:throw new zo(t);case Bo.code:throw new Bo(t);case Vo.code:throw new Vo(t);case Ho.code:throw new Ho(t);case Uo.code:throw new Uo(t,{method:n.method});case Wo.code:throw new Wo(t);case Go.code:throw new Go(t);case Ko.code:throw new Ko(t);case qo.code:throw new qo(t);case Jo.code:throw new Jo(t);case Yo.code:throw new Yo(t);case Xo.code:throw new Xo(t);case Zo.code:throw new Zo(t);case Qo.code:throw new Qo(t);case $o.code:throw new $o(t);case es.code:throw new es(t);case ts.code:throw new ts(t);case ns.code:throw new ns(t);case rs.code:throw new rs(t);case is.code:throw new is(t);case 5e3:throw new Ko(t);case as.code:throw new as(t);default:throw e instanceof E?e:new os(t)}}},{delay:({count:e,error:t})=>{if(t&&t instanceof Do){let e=t?.headers?.get(`Retry-After`);if(e?.match(/\d/))return Number.parseInt(e,10)*1e3}return~~(1<Eh(e)}),{enabled:i,id:i?Mn(`${c}.${Ua(n)}`):void 0})}}function Eh(e){return`code`in e&&typeof e.code==`number`?e.code===-1||e.code===Wo.code||e.code===Ro.code:e instanceof Do&&e.status?e.status===403||e.status===408||e.status===413||e.status===429||e.status===500||e.status===502||e.status===503||e.status===504:!0}function L(e){let t={formatters:void 0,fees:void 0,serializers:void 0,...e};function n(e){return t=>{let r=typeof t==`function`?t(e):t,i={...e,...r};return Object.assign(i,{extend:n(i)})}}return Object.assign(t,{extend:n(t)})}function Dh(){return{}}function Oh(e,{errorInstance:t=Error(`timed out`),timeout:n,signal:r}){return new Promise((i,a)=>{(async()=>{let o;try{let s=new AbortController;n>0&&(o=setTimeout(()=>{r?s.abort():a(t)},n)),i(await e({signal:s?.signal||null}))}catch(e){e?.name===`AbortError`&&a(t),a(e)}finally{clearTimeout(o)}})()})}function kh(){return{current:0,take(){return this.current++},reset(){this.current=0}}}var Ah=kh();Ao(),Wa();function jh(e,t={}){let{url:n,headers:r}=Mh(e);return{async request(e){let{body:i,fetchFn:a=t.fetchFn??fetch,onRequest:o=t.onRequest,onResponse:s=t.onResponse,timeout:c=t.timeout??1e4}=e,l={...t.fetchOptions??{},...e.fetchOptions??{}},{headers:u,method:d,signal:f}=l;try{let e=await Oh(async({signal:e})=>{let t={...l,body:Ua(Array.isArray(i)?i.map(e=>({jsonrpc:`2.0`,id:e.id??Ah.take(),...e})):{jsonrpc:`2.0`,id:i.id??Ah.take(),...i}),headers:{...r,"Content-Type":`application/json`,...u},method:d||`POST`,signal:f||(c>0?e:null)},s=new Request(n,t),p=await o?.(s,t)??{...t,url:n};return await a(p.url??n,p)},{errorInstance:new ko({body:i,url:n}),timeout:c,signal:!0});s&&await s(e);let t;if(e.headers.get(`Content-Type`)?.startsWith(`application/json`))t=await e.json();else{t=await e.text();try{t=JSON.parse(t||`{}`)}catch(n){if(e.ok)throw n;t={error:t}}}if(!e.ok){if(typeof t.error?.code==`number`&&typeof t.error?.message==`string`)return t;throw new Do({body:i,details:Ua(t.error)||e.statusText,headers:e.headers,status:e.status,url:n})}return t}catch(e){throw e instanceof Do||e instanceof ko?e:new Do({body:i,cause:e,url:n})}}}}function Mh(e){try{let t=new URL(e),n=(()=>{if(t.username){let e=`${decodeURIComponent(t.username)}:${decodeURIComponent(t.password)}`;return t.username=``,t.password=``,{url:t.toString(),headers:{Authorization:`Basic ${btoa(e)}`}}}})();return{url:t.toString(),...n}}catch{return{url:e}}}var Nh=`Ethereum Signed Message: +`;Ai(),Lt(),k();function Ph(e){let t=typeof e==`string`?Mn(e):typeof e.raw==`string`?e.raw:jn(e.raw);return Di([Mn(`${Nh}${It(t)}`),t])}ri();function Fh(e,t){return ni(Ph(e),t)}Wa(),D();var Ih=class extends E{constructor({domain:e}){super(`Invalid domain "${Ua(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]})}},Lh=class extends E{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{docsPath:`/api/glossary/Errors#typeddatainvalidprimarytypeerror`,metaMessages:["Check that the primary type is a key in `types`."]})}},Rh=class extends E{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`],name:`InvalidStructTypeError`})}};cn(),gi(),Ei(),Lt(),k(),zi(),Wa();function zh(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{let n={...t};for(let t of e){let{name:e,type:r}=t;r===`address`&&(n[e]=n[e].toLowerCase())}return n};return Ua({domain:!i.EIP712Domain||!t?{}:a(i.EIP712Domain,t),message:(()=>{if(r!==`EIP712Domain`)return a(i[r],n)})(),primaryType:r,types:i})}function Bh(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(Ri);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[e,t,n]=s;O(o,{signed:t===`int`,size:Number.parseInt(n,10)/8})}if(r===`address`&&typeof o==`string`&&!Ci(o))throw new hi({address:o});let c=r.match(Li);if(c){let[e,t]=c;if(t&&It(o)!==Number.parseInt(t,10))throw new nn({expectedSize:Number.parseInt(t,10),givenSize:It(o)})}let l=i[r];l&&(Hh(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new Ih({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new Lh({primaryType:r,types:i})}function Vh({domain:e}){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function Hh(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new Rh({type:e})}Qi(),Ai(),k(),ri();function Uh(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:Vh({domain:t}),...e.types};Bh({domain:t,message:n,primaryType:r,types:i});let a=[`0x1901`];return t&&a.push(Wh({domain:t,types:i})),r!==`EIP712Domain`&&a.push(Gh({data:n,primaryType:r,types:i})),ni(Di(a))}function Wh({domain:e,types:t}){return Gh({data:e,primaryType:`EIP712Domain`,types:t})}function Gh({data:e,primaryType:t,types:n}){return ni(Kh({data:e,primaryType:t,types:n}))}function Kh({data:e,primaryType:t,types:n}){let r=[{type:`bytes32`}],i=[qh({primaryType:t,types:n})];for(let a of n[t]){let[t,o]=Xh({types:n,name:a.name,type:a.type,value:e[a.name]});r.push(t),i.push(o)}return Bi(r,i)}function qh({primaryType:e,types:t}){return ni(kn(Jh({primaryType:e,types:t})))}function Jh({primaryType:e,types:t}){let n=``,r=Yh({primaryType:e,types:t});r.delete(e);let i=[e,...Array.from(r).sort()];for(let e of i)n+=`${e}(${t[e].map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return n}function Yh({primaryType:e,types:t},n=new Set){let r=e.match(/^\w*/u)?.[0];if(n.has(r)||t[r]===void 0)return n;n.add(r);for(let e of t[r])Yh({primaryType:e.type,types:t},n);return n}function Xh({types:e,name:t,type:n,value:r}){if(e[n]!==void 0)return[{type:`bytes32`},ni(Kh({data:r,primaryType:n,types:e}))];if(n===`bytes`)return[{type:`bytes32`},ni(r)];if(n===`string`)return[{type:`bytes32`},ni(kn(r))];if(n.lastIndexOf(`]`)===n.length-1){let i=n.slice(0,n.lastIndexOf(`[`)),a=r.map(n=>Xh({name:t,type:i,types:e,value:n}));return[{type:`bytes32`},ni(Bi(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:n},r]}var Zh={checksum:new class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&t!==void 0&&(this.delete(e),super.set(e,t)),t}set(e,t){if(super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}(8192)}.checksum;ti(),Hf(),pp();function Qh(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=$r(yf(e));return n===`Bytes`?r:qf(r)}function $h(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=yd(yf(e));return n===`Bytes`?r:qf(r)}function eg(e){return rp(e)&&Qf(e)===32}Hf(),Qd(),pp(),gf();function tg(e,t={}){let{compressed:n}=t,{prefix:r,x:i,y:a}=e;if(n===!1||typeof i==`bigint`&&typeof a==`bigint`){if(r!==4)throw new cg({prefix:r,cause:new ug});return}if(n===!0||typeof i==`bigint`&&a===void 0){if(r!==3&&r!==2)throw new cg({prefix:r,cause:new lg});return}throw new sg({publicKey:e})}function ng(e){let t=(()=>{if(rp(e))return ig(e);if(Pf(e))return rg(e);let{prefix:t,x:n,y:r}=e;return typeof n==`bigint`&&typeof r==`bigint`?{prefix:t??4,x:n,y:r}:{prefix:t,x:n}})();return tg(t),t}function rg(e){return ig(qf(e))}function ig(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new dg({publicKey:e});return e.length===130?{prefix:4,x:BigInt(I(e,0,32)),y:BigInt(I(e,32,64))}:e.length===132?{prefix:Number(I(e,0,1)),x:BigInt(I(e,1,33)),y:BigInt(I(e,33,65))}:{prefix:Number(I(e,0,1)),x:BigInt(I(e,1,33))}}function ag(e,t={}){return xf(og(e,t))}function og(e,t={}){tg(e);let{prefix:n,x:r,y:i}=e,{includePrefix:a=!0}=t;return Wf(a?F(n,{size:1}):`0x`,F(r,{size:32}),typeof i==`bigint`?F(i,{size:32}):`0x`)}var sg=class extends P{constructor({publicKey:e}){super(`Value \`${mf(e)}\` is not a valid public key.`,{metaMessages:[`Public key must contain:`,"- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidError`})}},cg=class extends P{constructor({prefix:e,cause:t}){super(`Prefix "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidPrefixError`})}},lg=class extends P{constructor(){super(`Prefix must be 2 or 3 for compressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidCompressedPrefixError`})}},ug=class extends P{constructor(){super(`Prefix must be 4 for uncompressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidUncompressedPrefixError`})}},dg=class extends P{constructor({publicKey:e}){super(`Value \`${e}\` is an invalid public key size.`,{metaMessages:[`Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).`,`Received ${Qf(Gf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidSerializedSizeError`})}};Hf(),Qd();var fg=/^0x[a-fA-F0-9]{40}$/;function pg(e,t={}){let{strict:n=!0}=t;if(!fg.test(e))throw new yg({address:e,cause:new bg});if(n){if(e.toLowerCase()===e)return;if(mg(e)!==e)throw new yg({address:e,cause:new xg})}}function mg(e){if(Zh.has(e))return Zh.get(e);pg(e,{strict:!1});let t=e.substring(2).toLowerCase(),n=Qh(Sf(t),{as:`Bytes`}),r=t.split(``);for(let e=0;e<40;e+=2)n[e>>1]>>4>=8&&r[e]&&(r[e]=r[e].toUpperCase()),(n[e>>1]&15)>=8&&r[e+1]&&(r[e+1]=r[e+1].toUpperCase());let i=`0x${r.join(``)}`;return Zh.set(e,i),i}function hg(e,t={}){let{checksum:n=!1}=t;return pg(e),n?mg(e):e}function gg(e,t={}){return hg(`0x${Qh(`0x${og(e).slice(4)}`).substring(26)}`,t)}function _g(e,t){return pg(e,{strict:!1}),pg(t,{strict:!1}),e.toLowerCase()===t.toLowerCase()}function vg(e,t={}){let{strict:n=!0}=t??{};try{return pg(e,{strict:n}),!0}catch{return!1}}var yg=class extends P{constructor({address:e,cause:t}){super(`Address "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidAddressError`})}},bg=class extends P{constructor(){super(`Address is not a 20 byte (40 hexadecimal character) value.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidInputError`})}},xg=class extends P{constructor(){super(`Address does not match its checksum counterpart.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidChecksumError`})}},Sg=/^(.*)\[([0-9]*)\]$/,Cg=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,wg=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n));var Tg=2n**256n-1n;Hf(),Qd(),pp();function Eg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=Kg(t.type);if(a){let[n,o]=a;return Ag(e,{...t,type:o},{checksumAddress:r,length:n,staticPosition:i})}if(t.type===`tuple`)return Pg(e,t,{checksumAddress:r,staticPosition:i});if(t.type===`address`)return kg(e,{checksum:r});if(t.type===`bool`)return jg(e);if(t.type.startsWith(`bytes`))return Mg(e,t,{staticPosition:i});if(t.type.startsWith(`uint`)||t.type.startsWith(`int`))return Ng(e,t);if(t.type===`string`)return Fg(e,{staticPosition:i});throw new l_(t.type)}var Dg=32,Og=32;function kg(e,t={}){let{checksum:n=!1}=t;return[(e=>n?mg(e):e)(qf(Df(e.readBytes(32),-20))),32]}function Ag(e,t,n){let{checksumAddress:r,length:i,staticPosition:a}=n;if(!i){let n=a+Af(e.readBytes(Og)),i=n+Dg;e.setPosition(n);let o=Af(e.readBytes(Dg)),s=qg(t),c=0,l=[];for(let n=0;n48?Of(i,{signed:n}):Af(i,{signed:n}),32]}function Pg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=t.components.length===0||t.components.some(({name:e})=>!e),o=a?[]:{},s=0;if(qg(t)){let n=i+Af(e.readBytes(Og));for(let i=0;i0?Wf(t,e):t}}if(o)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:Wf(...s.map(({encoded:e})=>e))}}function Vg(e,{type:t}){let[,n]=t.split(`bytes`),r=Qf(e);if(!n){let t=e;return r%32!=0&&(t=Xf(t,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:Wf(Yf(F(r,{size:32})),t)}}if(r!==Number.parseInt(n,10))throw new o_({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:Xf(e)}}function Hg(e){if(typeof e!=`boolean`)throw new P(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:Yf(Kf(e))}}function Ug(e,{signed:t,size:n}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Kg(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}function qg(e){let{type:t}=e;if(t===`string`||t===`bytes`||t.endsWith(`[]`))return!0;if(t===`tuple`)return e.components?.some(qg);let n=Kg(e.type);return!!(n&&qg({...e,type:n[1]}))}Qd();var Jg={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new Qg({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new Zg({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new Xg({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new Xg({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function Yg(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(Jg);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var Xg=class extends P{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.NegativeOffsetError`})}},Zg=class extends P{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.PositionOutOfBoundsError`})}},Qg=class extends P{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.RecursiveReadLimitExceededError`})}};kt(),Hf(),Qd(),pp();function $g(e,t,n={}){let{as:r=`Array`,checksumAddress:i=!1}=n,a=typeof t==`string`?xf(t):t,o=Yg(a);if(Ef(a)===0&&e.length>0)throw new i_;if(Ef(a)&&Ef(a)<32)throw new r_({data:typeof t==`string`?t:qf(t),parameters:e,size:Ef(a)});let s=0,c=r===`Array`?[]:{};for(let t=0;t{if(typeof e==`string`){if(e.length>3&&e.length%2!=0)throw new lp(e);return xf(e)}return e})(),{recursiveReadLimit:1/0}),n)}function f_(e,t=`Hex`){if(e.bytes.length===0)return t===`Hex`?qf(e.bytes):e.bytes;let n=e.readByte();if(n<128&&e.decrementPosition(1),n<192){let r=p_(e,n,128),i=e.readBytes(r);return t===`Hex`?qf(i):i}return m_(e,p_(e,n,192),t)}function p_(e,t,n){if(n===128&&t<128)return 1;if(t<=n+55)return t-n;if(t===n+55+1)return e.readUint8();if(t===n+55+2)return e.readUint16();if(t===n+55+3)return e.readUint24();if(t===n+55+4)return e.readUint32();throw new P(`Invalid RLP prefix`)}function m_(e,t,n){let r=e.position,i=[];for(;e.position-r__(e))):y_(e)}function v_(e){let t=e.reduce((e,t)=>e+t.length,0),n=b_(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function y_(e){let t=typeof e==`string`?xf(e):e,n=b_(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function b_(e){if(e<=255)return 1;if(e<=65535)return 2;if(e<=16777215)return 3;if(e<=4294967295)return 4;throw new P(`Length is too large.`)}Qd(),pp(),gf();function x_(e,t={}){let{recovered:n}=t;if(e.r===void 0||e.s===void 0||n&&e.yParity===void 0)throw new F_({signature:e});if(e.r<0n||e.r>Tg)throw new I_({value:e.r});if(e.s<0n||e.s>Tg)throw new L_({value:e.s});if(typeof e.yParity==`number`&&e.yParity!==0&&e.yParity!==1)throw new R_({value:e.yParity})}function S_(e){return C_(qf(e))}function C_(e){if(e.length!==130&&e.length!==132)throw new P_({signature:e});let t=BigInt(I(e,0,32)),n=BigInt(I(e,32,64)),r=(()=>{let t=Number(`0x${e.slice(130)}`);if(!Number.isNaN(t))try{return M_(t)}catch{throw new R_({value:t})}})();return r===void 0?{r:t,s:n}:{r:t,s:n,yParity:r}}function w_(e){if(e.r!==void 0&&e.s!==void 0)return T_(e)}function T_(e){let t=typeof e==`string`?C_(e):e instanceof Uint8Array?S_(e):typeof e.r==`string`?D_(e):e.v?E_(e):{r:e.r,s:e.s,...e.yParity===void 0?{}:{yParity:e.yParity}};return x_(t),t}function E_(e){return{r:e.r,s:e.s,yParity:M_(e.v)}}function D_(e){let t=(()=>{let t=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof t==`number`&&typeof n!=`number`&&(n=M_(t)),typeof n!=`number`)throw new R_({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function O_(e){let[t,n,r]=e;return T_({r:n===`0x`?0n:BigInt(n),s:r===`0x`?0n:BigInt(r),yParity:t===`0x`?0:Number(t)})}function k_(e){x_(e);let t=e.r,n=e.s;return Wf(F(t,{size:32}),F(n,{size:32}),typeof e.yParity==`number`?F(N_(e.yParity),{size:1}):`0x`)}function A_(e){let{r:t,s:n,yParity:r}=e;return{r:F(t,{size:32}),s:F(n,{size:32}),yParity:r===0?`0x0`:`0x1`}}function j_(e){let{r:t,s:n,yParity:r}=e;return[r?`0x01`:`0x`,t===0n?`0x`:$f(F(t)),n===0n?`0x`:$f(F(n))]}function M_(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2==0?1:0;throw new z_({value:e})}function N_(e){if(e===0)return 27;if(e===1)return 28;throw new R_({value:e})}var P_=class extends P{constructor({signature:e}){super(`Value \`${e}\` is an invalid signature size.`,{metaMessages:[`Expected: 64 bytes or 65 bytes.`,`Received ${Qf(Gf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSerializedSizeError`})}},F_=class extends P{constructor({signature:e}){super(`Signature \`${mf(e)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.MissingPropertiesError`})}},I_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidRError`})}},L_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSError`})}},R_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidYParityError`})}},z_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidVError`})}};pp();function B_(e,t={}){return typeof e.chainId==`string`?V_(e):{...e,...t.signature}}function V_(e){let{address:t,chainId:n,nonce:r}=e,i=w_(e);return{address:t,chainId:Number(n),nonce:BigInt(r),...i}}function H_(e){return e.map(V_)}function U_(e){let{address:t,chainId:n,nonce:r,...i}=e;return{address:t,chainId:F(n),nonce:F(r),...A_(i)}}function W_(e){return e.map(U_)}uu(),Hf(),pp();function G_(e){return gg(K_(e))}function K_(e){let{payload:t,signature:n}=e,{r,s:i,yParity:a}=n;return ng(new Zl.Signature(BigInt(r),BigInt(i)).addRecoveryBit(a).recoverPublicKey(Gf(t).substring(2)))}function q_(e){let{address:t,hash:n,payload:r,publicKey:i,signature:a}=e;return t?_g(t,G_({payload:r,signature:a})):Zl.verify(a,yf(r),ag(i),...n?[{prehash:!0,lowS:!0}]:[])}Qd(),pp();var J_=n_(`(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data`);function Y_(e){if(typeof e==`string`){if(I(e,-32)!==`0x8010801080108010801080108010801080108010801080108010801080108010`)throw new Q_(e)}else x_(e.authorization)}function X_(e){Y_(e);let t=tp(I(e,-64,-32)),n=I(e,-t-64,-64),r=I(e,0,-t-64),[i,a,o]=$g(J_,n);return{authorization:B_({address:i.delegation,chainId:Number(i.chainId),nonce:i.nonce,yParity:i.yParity,r:i.r,s:i.s}),signature:r,...o&&o!==`0x`?{data:o,to:a}:{}}}function Z_(e){try{return Y_(e),!0}catch{return!1}}var Q_=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc8010.InvalidWrappedSignatureError`})}};_o(),k();async function $_(e,{blockHash:t,blockNumber:n,blockTag:r,hash:i,index:a,sender:o,nonce:s}){let c=r||`latest`,l=n===void 0?void 0:O(n),u=null;if(i?u=await e.request({method:`eth_getTransactionByHash`,params:[i]},{dedupe:!0}):t?u=await e.request({method:`eth_getTransactionByBlockHashAndIndex`,params:[t,O(a)]},{dedupe:!0}):(l||c)&&typeof a==`number`?u=await e.request({method:`eth_getTransactionByBlockNumberAndIndex`,params:[l||c,O(a)]},{dedupe:!!l}):o&&typeof s==`number`&&(u=await e.request({method:`eth_getTransactionBySenderAndNonce`,params:[o,O(s)]},{dedupe:!0})),!u)throw new po({blockHash:t,blockNumber:n,blockTag:c,hash:i,index:a});return(e.chain?.formatters?.transaction?.format||sd)(u,`getTransaction`)}_o();async function ev(e,{hash:t}){let n=await e.request({method:`eth_getTransactionReceipt`,params:[t]},{dedupe:!0});if(!n)throw new mo({hash:t});return(e.chain?.formatters?.transactionReceipt?.format||Gm)(n,`getTransactionReceipt`)}Qd();function tv(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;onv(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>nv(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function rv(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return rv(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&vg(n[r],{strict:!1}))return o}}kt(),Qd(),pp();function iv(e,t={}){let{prepare:n=!0}=t,r=Array.isArray(e)||typeof e==`string`?Tt(e):e;return{...r,...n?{hash:cv(r)}:{}}}function av(e,t,n){let{args:r=[],prepare:i=!0}=n??{},a=rp(t,{strict:!1}),o=e.filter(e=>a?e.type===`function`||e.type===`error`?ov(e)===I(t,0,4):e.type===`event`?cv(e)===t:!1:`name`in e&&e.name===t);if(o.length===0)throw new uv({name:t});if(o.length===1)return{...o[0],...i?{hash:cv(o[0])}:{}};let s;for(let e of o)if(`inputs`in e){if(!r||r.length===0){if(!e.inputs||e.inputs.length===0)return{...e,...i?{hash:cv(e)}:{}};continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===r.length&&r.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?nv(t,r):!1})){if(s&&`inputs`in s&&s.inputs){let t=rv(e.inputs,s.inputs,r);if(t)throw new lv({abiItem:e,type:t[0]},{abiItem:s,type:t[1]})}s=e}}let c=(()=>{if(s)return s;let[e,...t]=o;return{...e,overloads:t}})();if(!c)throw new uv({name:t});return{...c,...i?{hash:cv(c)}:{}}}function ov(...e){return I(cv((()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})()),0,4)}function sv(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})();return tv(typeof t==`string`?t:ue(t))}function cv(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})();return typeof t!=`string`&&`hash`in t&&t.hash?t.hash:Qh(Jf(sv(t)))}var lv=class extends P{constructor(e,t){super(`Found ambiguous types in overloaded ABI Items.`,{metaMessages:[`\`${e.type}\` in \`${tv(ue(e.abiItem))}\`, and`,`\`${t.type}\` in \`${tv(ue(t.abiItem))}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.AmbiguityError`})}},uv=class extends P{constructor({name:e,data:t,type:n=`item`}){let r=e?` with name "${e}"`:t?` with data "${t}"`:``;super(`ABI ${n}${r} not found.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.NotFoundError`})}},dv=`0x0000000000000000000000000000000000000000`;Qd(),pp();var fv=`0x6492649264926492649264926492649264926492649264926492649264926492`;function pv(e){if(I(e,-32)!==`0x6492649264926492649264926492649264926492649264926492649264926492`)throw new gv(e)}function mv(e){let{data:t,signature:n,to:r}=e;return Wf(e_(n_(`address, bytes, bytes`),[r,t,n]),fv)}function hv(e){try{return pv(e),!0}catch{return!1}}var gv=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-6492 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc6492.InvalidWrappedSignatureError`})}};uu(),On(),Un();function _v({r:e,s:t,to:n=`hex`,v:r,yParity:i}){let a=(()=>{if(i===0||i===1)return i;if(r&&(r===27n||r===28n||r>=35n))return r%2n==0n?1:0;throw Error("Invalid `v` or `yParity` value")})(),o=`0x${new Zl.Signature(Tn(e),Tn(t)).toCompactHex()}${a===0?`1b`:`1c`}`;return n===`hex`?o:Rn(o)}Cp(),kp(),Eo(),Rp(),da(),Si(),Hd(),Ai(),Ft(),On(),k(),xm();async function vv(e,t){let{address:n,chain:r=e.chain,hash:i,erc6492VerifierAddress:a=t.universalSignatureVerifierAddress??r?.contracts?.erc6492Verifier?.address,multicallAddress:o=t.multicallAddress??r?.contracts?.multicall3?.address,mode:s=`auto`}=t;if(r?.verifyHash)return await r.verifyHash(e,t);let c=(()=>{let e=t.signature;return Pt(e)?e:typeof e==`object`&&`r`in e&&`s`in e?_v(e):jn(e)})();try{if(s===`eoa`)try{if(Vd(bi(n),await pu({hash:i,signature:c})))return!0}catch{}return Z_(c)?await yv(e,{...t,multicallAddress:o,signature:c}):await bv(e,{...t,verifierAddress:a,signature:c})}catch(e){if(s!==`eoa`)try{if(Vd(bi(n),await pu({hash:i,signature:c})))return!0}catch{}if(e instanceof Sv)return!1;throw e}}async function yv(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,multicallAddress:o}=t,{authorization:s,data:c,signature:l,to:u}=X_(t.signature);if(await ih(e,{address:n,blockNumber:r,blockTag:i})===ki([`0xef0100`,s.address]))return await xv(e,{address:n,blockNumber:r,blockTag:i,hash:a,signature:l});let d={address:s.address,chainId:Number(s.chainId),nonce:Number(s.nonce),r:O(s.r,{size:32}),s:O(s.s,{size:32}),yParity:s.yParity};if(!await Sh({address:n,authorization:d}))throw new Sv;let f=await T(e,Sm,`readContract`)({...o?{address:o}:{code:Op},authorizationList:[d],abi:vp,blockNumber:r,blockTag:`pending`,functionName:`aggregate3`,args:[[...c?[{allowFailure:!0,target:u??n,callData:c}]:[],{allowFailure:!0,target:n,callData:ua({abi:xp,functionName:`isValidSignature`,args:[a,l]})}]]});if((f[f.length-1]?.returnData)?.startsWith(`0x1626ba7e`))return!0;throw new Sv}async function bv(e,t){let{address:n,factory:r,factoryData:i,hash:a,signature:o,verifierAddress:s,...c}=t,l=await(async()=>!r&&!i||hv(o)?o:mv({data:i,signature:o,to:r}))(),u=s?{to:s,data:ua({abi:Sp,functionName:`isValidSig`,args:[n,a,l]}),...c}:{data:Ip({abi:Sp,args:[n,a,l],bytecode:Dp}),...c},{data:d}=await T(e,hm,`call`)(u).catch(e=>{throw e instanceof xo?new Sv:e});if(En(d??`0x0`))return!0;throw new Sv}async function xv(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,signature:o}=t;if((await T(e,Sm,`readContract`)({address:n,abi:xp,args:[a,o],blockNumber:r,blockTag:i,functionName:`isValidSignature`}).catch(e=>{throw e instanceof So?new Sv:e})).startsWith(`0x1626ba7e`))return!0;throw new Sv}var Sv=class extends Error{};On(),Wa();function Cv(e,{emitOnBegin:t=!1,emitMissed:n=!1,onBlockNumber:r,onError:i,poll:a,pollingInterval:o=e.pollingInterval}){let s=a===void 0?!(e.transport.type===`webSocket`||e.transport.type===`ipc`||e.transport.type===`fallback`&&(e.transport.transports[0].config.type===`webSocket`||e.transport.transports[0].config.type===`ipc`)):a,c;return s?Em(Ua([`watchBlockNumber`,e.uid,t,n,o]),{onBlockNumber:r,onError:i},r=>Om(async()=>{try{let t=await T(e,Pm,`getBlockNumber`)({cacheTime:0});if(c!==void 0){if(t===c)return;if(t-c>1&&n)for(let e=c+1n;ec)&&(r.onBlockNumber(t,c),c=t)}catch(e){r.onError?.(e)}},{emitOnBegin:t,interval:o})):Em(Ua([`watchBlockNumber`,e.uid,t,n]),{onBlockNumber:r,onError:i},t=>{let n=!0,r=()=>n=!1;return(async()=>{try{let{unsubscribe:i}=await(()=>{if(e.transport.type===`fallback`){let t=e.transport.transports.find(e=>e.config.type===`webSocket`||e.config.type===`ipc`);return t?t.value:e.transport}return e.transport})().subscribe({params:[`newHeads`],onData(e){if(!n)return;let r=Tn(e.result?.number);t.onBlockNumber(r,c),c=r},onError(e){t.onError?.(e)}});r=i,n||r()}catch(e){i?.(e)}})(),()=>r()})}_o(),Wp(),Wa();async function wv(e,t){let{checkReplacement:n=!0,confirmations:r=1,hash:i,onReplaced:a,retryCount:o=6,retryDelay:s=({count:e})=>~~(1<{g?.(),h?.(),y(new go({hash:i}))},c):void 0;return h=Em(l,{onReplaced:a,resolve:v,reject:y},async t=>{if(p=await T(e,ev,`getTransactionReceipt`)({hash:i}).catch(()=>void 0),p&&r<=1){clearTimeout(b),t.resolve(p),h?.();return}g=T(e,Cv,`watchBlockNumber`)({emitMissed:!0,emitOnBegin:!0,poll:!0,pollingInterval:u,async onBlockNumber(a){let c=e=>{clearTimeout(b),g?.(),e(),h?.()},l=a;if(!m)try{if(p){if(r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p));return}if(n&&!d&&(m=!0,await Um(async()=>{d=await T(e,$_,`getTransaction`)({hash:i}),d.blockNumber&&(l=d.blockNumber)},{delay:s,retryCount:o}),m=!1),p=await T(e,ev,`getTransactionReceipt`)({hash:i}),r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p))}catch(n){if(n instanceof po||n instanceof mo){if(!d){m=!1;return}try{f=d,m=!0;let n=await Um(()=>T(e,fd,`getBlock`)({blockNumber:l,includeTransactions:!0}),{delay:s,retryCount:o,shouldRetry:({error:e})=>e instanceof ad});m=!1;let i=n.transactions.find(({from:e,nonce:t})=>e===f.from&&t===f.nonce);if(!i||(p=await T(e,ev,`getTransactionReceipt`)({hash:i.hash}),r>1&&(!p.blockNumber||l-p.blockNumber+1n{t.onReplaced?.({reason:a,replacedTransaction:f,transaction:i,transactionReceipt:p}),t.resolve(p)})}catch(e){c(()=>t.reject(e))}}else c(()=>t.reject(n))}}})}),_}_o();async function Tv(e,{serializedTransaction:t,throwOnReceiptRevert:n,timeout:r}){let i=await e.request({method:`eth_sendRawTransactionSync`,params:r?[t,r]:[t]},{retryCount:0}),a=(e.chain?.formatters?.transactionReceipt?.format||Gm)(i);if(a.status===`reverted`&&n)throw new ho({receipt:a});return a}k();async function Ev(e,{chain:t}){let{id:n,name:r,nativeCurrency:i,rpcUrls:a,blockExplorers:o}=t;await e.request({method:`wallet_addEthereumChain`,params:[{chainId:O(n),chainName:r,nativeCurrency:i,rpcUrls:a.default.http,blockExplorerUrls:o?Object.values(o).map(({url:e})=>e):void 0}]},{dedupe:!0,retryCount:0})}Rp();function Dv(e,t){let{abi:n,args:r,bytecode:i,...a}=t,o=Ip({abi:n,args:r,bytecode:i});return Bm(e,{...a,...a.authorizationList?{to:null}:{},data:o})}Si();async function Ov(e){return e.account?.type===`local`?[e.account.address]:(await e.request({method:`eth_accounts`},{dedupe:!0})).map(e=>yi(e))}oa(),k();async function kv(e,t={}){let{account:n=e.account,chainId:r}=t,i=n?aa(n):void 0,a=r?[i?.address,[O(r)]]:[i?.address],o=await e.request({method:`wallet_getCapabilities`,params:a}),s={};for(let[e,t]of Object.entries(o)){s[Number(e)]={};for(let[n,r]of Object.entries(t))n===`addSubAccount`&&(n=`unstable_addSubAccount`),s[Number(e)][n]=r}return typeof r==`number`?s[r]:s}async function Av(e){return await e.request({method:`wallet_getPermissions`},{dedupe:!0})}oa(),Hd();async function jv(e,t){let{account:n=e.account,chainId:r,nonce:i}=t;if(!n)throw new Fm({docsPath:`/docs/eip7702/prepareAuthorization`});let a=aa(n),o=(()=>{if(t.executor)return t.executor===`self`?t.executor:aa(t.executor)})(),s={address:t.contractAddress??t.address,chainId:r,nonce:i};return s.chainId===void 0&&(s.chainId=e.chain?.id??await T(e,Fd,`getChainId`)({})),s.nonce===void 0&&(s.nonce=await T(e,gd,`getTransactionCount`)({address:a.address,blockTag:`pending`}),(o===`self`||o?.address&&Vd(o.address,a.address))&&(s.nonce+=1)),s}Si();async function Mv(e){return(await e.request({method:`eth_requestAccounts`},{dedupe:!0,retryCount:0})).map(e=>bi(e))}async function Nv(e,t){return e.request({method:`wallet_requestPermissions`,params:[t]},{retryCount:0})}async function Pv(e,t){let{chain:n=e.chain}=t,r=t.timeout??Math.max((n?.blockTime??0)*3,5e3),i=await T(e,Ym,`sendCalls`)(t);return await T(e,Zm,`waitForCallsStatus`)({...t,id:i.id,timeout:r})}oa(),D(),_o(),Ai(),zu(),Ku(),vi(),td();var Fv=new _i(128);async function Iv(e,t){let{account:n=e.account,assertChainId:r=!0,chain:i=e.chain,accessList:a,authorizationList:o,blobs:s,data:c,dataSuffix:l=typeof e.dataSuffix==`string`?e.dataSuffix:e.dataSuffix?.value,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,pollingInterval:g,throwOnReceiptRevert:_,type:v,value:y,...b}=t,x=t.timeout??Math.max((i?.blockTime??0)*3,5e3);if(n===void 0)throw new Fm({docsPath:`/docs/actions/wallet/sendTransactionSync`});let S=n?aa(n):null;try{ed(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&o&&o.length>0)return await bu({authorization:o[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`.")})})();if(S?.type===`json-rpc`||S===null){let t;i!==null&&(t=await T(e,Fd,`getChainId`)({}),r&&Lm({currentChainId:t,chain:i}));let ee=e.chain?.formatters?.transactionRequest?.format,C=(ee||Hu)({...Ru(b,{format:ee}),accessList:a,account:S,authorizationList:o,blobs:s,chainId:t,data:c&&Di([c,l??`0x`]),gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,to:n,type:v,value:y},`sendTransaction`),te=Fv.get(e.uid),ne=te?`wallet_sendTransaction`:`eth_sendTransaction`,re=await(async()=>{try{return await e.request({method:ne,params:[C]},{retryCount:0})}catch(t){if(te===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[C]},{retryCount:0}).then(t=>(Fv.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(Fv.set(e.uid,!1),n):r});throw n}})(),ie=await T(e,wv,`waitForTransactionReceipt`)({checkReplacement:!1,hash:re,pollingInterval:g,timeout:x});if(_&&ie.status===`reverted`)throw new ho({receipt:ie});return ie}if(S?.type===`local`){let r=await T(e,zd,`prepareTransactionRequest`)({account:S,accessList:a,authorizationList:o,blobs:s,chain:i,data:c&&Di([c,l??`0x`]),gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,nonceManager:S.nonceManager,parameters:[...Ld,`sidecars`],type:v,value:y,...b,to:n}),g=i?.serializers?.transaction,x=await S.signTransaction(r,{serializer:g});return await T(e,Tv,`sendRawTransactionSync`)({serializedTransaction:x,throwOnReceiptRevert:_,timeout:t.timeout})}throw S?.type===`smart`?new Im({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new Im({docsPath:`/docs/actions/wallet/sendTransactionSync`,type:S?.type})}catch(e){throw e instanceof Im?e:Pd(e,{...t,account:S,chain:t.chain||void 0})}}async function Lv(e,t){let{id:n}=t;await e.request({method:`wallet_showCallsStatus`,params:[n]})}oa();async function Rv(e,t){let{account:n=e.account}=t;if(!n)throw new Fm({docsPath:`/docs/eip7702/signAuthorization`});let r=aa(n);if(!r.signAuthorization)throw new Im({docsPath:`/docs/eip7702/signAuthorization`,metaMessages:["The `signAuthorization` Action does not support JSON-RPC Accounts."],type:r.type});let i=await jv(e,t);return r.signAuthorization(i)}oa(),k();async function zv(e,{account:t=e.account,message:n}){if(!t)throw new Fm({docsPath:`/docs/actions/wallet/signMessage`});let r=aa(t);if(r.signMessage)return r.signMessage({message:n});let i=typeof n==`string`?Mn(n):n.raw instanceof Uint8Array?kn(n.raw):n.raw;return e.request({method:`personal_sign`,params:[i,r.address]},{retryCount:0})}oa(),k(),Ku(),td();async function Bv(e,t){let{account:n=e.account,chain:r=e.chain,...i}=t;if(!n)throw new Fm({docsPath:`/docs/actions/wallet/signTransaction`});let a=aa(n);ed({account:a,...t});let o=await T(e,Fd,`getChainId`)({});r!==null&&Lm({currentChainId:o,chain:r});let s=(r?.formatters||e.chain?.formatters)?.transactionRequest?.format||Hu;return a.signTransaction?a.signTransaction({...i,chainId:o},{serializer:e.chain?.serializers?.transaction}):await e.request({method:`eth_signTransaction`,params:[{...s({...i,account:a},`signTransaction`),chainId:O(o),from:a.address}]},{retryCount:0})}oa();async function Vv(e,t){let{account:n=e.account,domain:r,message:i,primaryType:a}=t;if(!n)throw new Fm({docsPath:`/docs/actions/wallet/signTypedData`});let o=aa(n),s={EIP712Domain:Vh({domain:r}),...t.types};if(Bh({domain:r,message:i,primaryType:a,types:s}),o.signTypedData)return o.signTypedData({domain:r,message:i,primaryType:a,types:s});let c=zh({domain:r,message:i,primaryType:a,types:s});return e.request({method:`eth_signTypedData_v4`,params:[o.address,c]},{retryCount:0})}k();async function Hv(e,{id:t}){await e.request({method:`wallet_switchEthereumChain`,params:[{chainId:O(t)}]},{retryCount:0})}async function Uv(e,t){return await e.request({method:`wallet_watchAsset`,params:t},{retryCount:0})}async function Wv(e,t){return Vm.internal(e,Iv,`sendTransactionSync`,t)}function Gv(e){return{addChain:t=>Ev(e,t),deployContract:t=>Dv(e,t),fillTransaction:t=>Id(e,t),getAddresses:()=>Ov(e),getCallsStatus:t=>Xm(e,t),getCapabilities:t=>kv(e,t),getChainId:()=>Fd(e),getPermissions:()=>Av(e),prepareAuthorization:t=>jv(e,t),prepareTransactionRequest:t=>zd(e,t),requestAddresses:()=>Mv(e),requestPermissions:t=>Nv(e,t),sendCalls:t=>Ym(e,t),sendCallsSync:t=>Pv(e,t),sendRawTransaction:t=>Rm(e,t),sendRawTransactionSync:t=>Tv(e,t),sendTransaction:t=>Bm(e,t),sendTransactionSync:t=>Iv(e,t),showCallsStatus:t=>Lv(e,t),signAuthorization:t=>Rv(e,t),signMessage:t=>zv(e,t),signTransaction:t=>Bv(e,t),signTypedData:t=>Vv(e,t),switchChain:t=>Hv(e,t),waitForCallsStatus:t=>Zm(e,t),watchAsset:t=>Uv(e,t),writeContract:t=>Vm(e,t),writeContractSync:t=>Wv(e,t)}}function Kv(e){let{key:t=`wallet`,name:n=`Wallet Client`,transport:r}=e;return rh({...e,key:t,name:n,transport:r,type:`walletClient`}).extend(Gv)}function qv({key:e,methods:t,name:n,request:r,retryCount:i=3,retryDelay:a=150,timeout:o,type:s},c){let l=nh();return{config:{key:e,methods:t,name:n,request:r,retryCount:i,retryDelay:a,timeout:o,type:s},request:Th(r,{methods:t,retryCount:i,retryDelay:a,uid:l}),value:c}}function Jv(e,t={}){let{key:n=`custom`,methods:r,name:i=`Custom Provider`,retryDelay:a}=t;return({retryCount:o})=>qv({key:n,methods:r,name:i,request:e.request.bind(e),retryCount:t.retryCount??o,retryDelay:a,type:`custom`})}Pu(),ss();function Yv(e,t={}){let{key:n=`fallback`,name:r=`Fallback`,rank:i=!1,shouldThrow:a=Xv,retryCount:o,retryDelay:s}=t;return(({chain:t,pollingInterval:c=4e3,timeout:l,...u})=>{let d=e,f=()=>{},p=qv({key:n,name:r,async request({method:e,params:n}){let r,i=async(o=0)=>{let s=d[o]({...u,chain:t,retryCount:0,timeout:l});try{let t=await s.request({method:e,params:n});return f({method:e,params:n,response:t,transport:s,status:`success`}),t}catch(c){if(f({error:c,method:e,params:n,transport:s,status:`error`}),a(c)||o===d.length-1||(r??=d.slice(o+1).some(n=>{let{include:r,exclude:i}=n({chain:t}).config.methods||{};return r?r.includes(e):i?!i.includes(e):!0}),!r))throw c;return i(o+1)}};return i()},retryCount:o,retryDelay:s,type:`fallback`},{onResponse:e=>f=e,transports:d.map(e=>e({chain:t,retryCount:0}))});if(i){let e=typeof i==`object`?i:{};Zv({chain:t,interval:e.interval??c,onTransports:e=>d=e,ping:e.ping,sampleCount:e.sampleCount,timeout:e.timeout,transports:d,weights:e.weights})}return p})}function Xv(e){return!!(`code`in e&&typeof e.code==`number`&&(e.code===Ho.code||e.code===Ko.code||e.code===as.code||Su.nodeMessage.test(e.message)||e.code===5e3))}function Zv({chain:e,interval:t=4e3,onTransports:n,ping:r,sampleCount:i=10,timeout:a=1e3,transports:o,weights:s={}}){let{stability:c=.7,latency:l=.3}=s,u=[],d=async()=>{let s=await Promise.all(o.map(async t=>{let n=t({chain:e,retryCount:0,timeout:a}),i=Date.now(),o,s;try{await(r?r({transport:n}):n.request({method:`net_listening`})),s=1}catch{s=0}finally{o=Date.now()}return{latency:o-i,success:s}}));u.push(s),u.length>i&&u.shift();let f=Math.max(...u.map(e=>Math.max(...e.map(({latency:e})=>e))));n(o.map((e,t)=>{let n=u.map(e=>e[t].latency),r=1-n.reduce((e,t)=>e+t,0)/n.length/f,i=u.map(e=>e[t].success),a=i.reduce((e,t)=>e+t,0)/i.length;return a===0?[0,t]:[l*r+c*a,t]}).sort((e,t)=>t[0]-e[0]).map(([,e])=>o[e])),await Dm(t),d()};d()}D();var Qv=class extends E{constructor(){super(`No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.`,{docsPath:`/docs/clients/intro`,name:`UrlRequiredError`})}};Ao(),qp();function $v(e,t={}){let{batch:n,fetchFn:r,fetchOptions:i,key:a=`http`,methods:o,name:s=`HTTP JSON-RPC`,onFetchRequest:c,onFetchResponse:l,retryDelay:u,raw:d}=t;return({chain:f,retryCount:p,timeout:m})=>{let{batchSize:h=1e3,wait:g=0}=typeof n==`object`?n:{},_=t.retryCount??p,v=m??t.timeout??1e4,y=e||f?.rpcUrls.default.http[0];if(!y)throw new Qv;let b=jh(y,{fetchFn:r,fetchOptions:i,onRequest:c,onResponse:l,timeout:v});return qv({key:a,methods:o,name:s,async request({method:e,params:t}){let r={method:e,params:t},{schedule:i}=Gp({id:y,wait:g,shouldSplitBatch(e){return e.length>h},fn:e=>b.request({body:e}),sort:(e,t)=>e.id-t.id}),[{error:a,result:o}]=await(async e=>n?i(e):[await b.request({body:e})])(r);if(d)return{error:a,result:o};if(a)throw new Oo({body:r,error:a,url:y});return o},retryCount:_,retryDelay:u,timeout:v,type:`http`},{fetchOptions:i,url:y})}}var ey=L({id:16600,name:`0G Newton Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-newton.0g.ai`}},testnet:!0}),ty=L({id:16601,name:`0G Galileo Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-galileo.0g.ai`}},testnet:!0}),ny=L({id:16661,name:`0G Mainnet`,nativeCurrency:{name:`0G`,symbol:`0G`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan.0g.ai`}},testnet:!1}),ry=L({id:16602,name:`0G Galileo Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-galileo.0g.ai`}},testnet:!0}),iy=L({id:995,name:`5ireChain`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Mainnet Explorer`,url:`https://5irescan.io/`}},testnet:!1}),ay=L({id:179,name:`ABEY Mainnet`,nativeCurrency:{name:`ABEY`,symbol:`ABEY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.abeychain.com`]}},blockExplorers:{default:{name:`Abey Scan`,url:`https://abeyscan.com`}},testnet:!1});$u();var oy=50000n,sy=Zu*32n;On(),Un(),k(),Ku();var cy={block:dd({format(e){let t=e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=cy.transaction?.format(e);return t.typeHex===`0x71`?t.type=`eip712`:t.typeHex===`0xff`&&(t.type=`priority`),t});return{l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTimestamp:e.l1BatchTimestamp?Tn(e.l1BatchTimestamp):null,transactions:t}}}),transaction:cd({format(e){let t={};return e.type===`0x71`?t.type=`eip712`:e.type===`0xff`&&(t.type=`priority`),{...t,l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?Tn(e.l1BatchTxIndex):null}}}),transactionReceipt:Km({format(e){return{l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?Tn(e.l1BatchTxIndex):null,logs:e.logs.map(e=>({...Ud(e),l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,transactionLogIndex:Dn(e.transactionLogIndex),logType:e.logType})),l2ToL1Logs:e.l2ToL1Logs.map(e=>({blockNumber:Tn(e.blockHash),blockHash:e.blockHash,l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,transactionIndex:Tn(e.transactionIndex),shardId:Tn(e.shardId),isService:e.isService,sender:e.sender,key:e.key,value:e.value,transactionHash:e.transactionHash,logIndex:Tn(e.logIndex)}))}}}),transactionRequest:Gu({exclude:[`customSignature`,`factoryDeps`,`gasPerPubdata`,`paymaster`,`paymasterInput`],format(e){return e.gasPerPubdata||e.paymaster&&e.paymasterInput||e.factoryDeps||e.customSignature?{eip712Meta:{...e.gasPerPubdata?{gasPerPubdata:kn(e.gasPerPubdata)}:{gasPerPubdata:kn(oy)},...e.paymaster&&e.paymasterInput?{paymasterParams:{paymaster:e.paymaster,paymasterInput:Array.from(Rn(e.paymasterInput))}}:{},...e.factoryDeps?{factoryDeps:e.factoryDeps.map(e=>Array.from(Rn(e)))}:{},...e.customSignature?{customSignature:Array.from(Rn(e.customSignature))}:{}},type:`0x71`}:{}}})};D();var ly=class extends E{constructor(){super([`Transaction is not an EIP712 transaction.`,``,`Transaction must:`,' - include `type: "eip712"`'," - include one of the following: `customSignature`, `paymaster`, `paymasterInput`, `gasPerPubdata`, `factoryDeps`"].join(` +`),{name:`InvalidEip712TransactionError`})}};function uy(e){return!!(e.type===`eip712`||`customSignature`in e&&e.customSignature||`paymaster`in e&&e.paymaster||`paymasterInput`in e&&e.paymasterInput||`gasPerPubdata`in e&&typeof e.gasPerPubdata==`bigint`||`factoryDeps`in e&&e.factoryDeps)}gi(),D(),Fp(),Ei();function dy(e){let{chainId:t,to:n,from:r,paymaster:i,paymasterInput:a}=e;if(!uy(e))throw new ly;if(!t||t<=0)throw new Pp({chainId:t});if(n&&!Ci(n))throw new hi({address:n});if(r&&!Ci(r))throw new hi({address:r});if(i&&!Ci(i))throw new hi({address:i});if(i&&!a)throw new E("`paymasterInput` must be provided when `paymaster` is defined");if(!i&&a)throw new E("`paymaster` must be provided when `paymasterInput` is defined")}Ai(),k();function fy(e,t){return uy(e)?my(e):mh(e,t)}var py={transaction:fy};function my(e){let{chainId:t,gas:n,nonce:r,to:i,from:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,customSignature:l,factoryDeps:u,paymaster:d,paymasterInput:f,gasPerPubdata:p,data:m}=e;return dy(e),ki([`0x71`,mu([r?kn(r):`0x`,c?kn(c):`0x`,s?kn(s):`0x`,n?kn(n):`0x`,i??`0x`,o?kn(o):`0x`,m??`0x`,kn(t),kn(``),kn(``),kn(t),a??`0x`,kn(p||oy),u??[],l??`0x`,d&&f?[d,f]:[]])])}D();var hy=class extends E{constructor({givenLength:e,maxBytecodeSize:t}){super(`Bytecode cannot be longer than ${t} bytes. Given length: ${e}`,{name:`BytecodeLengthExceedsMaxSizeError`})}},gy=class extends E{constructor({givenLengthInWords:e}){super(`Bytecode length in 32-byte words must be odd. Given length in words: ${e}`,{name:`BytecodeLengthInWordsMustBeOddError`})}},_y=class extends E{constructor({givenLength:e}){super(`The bytecode length in bytes must be divisible by 32. Given length: ${e}`,{name:`BytecodeLengthMustBeDivisibleBy32Error`})}};gn(),Un();function vy(e){let t=Fn(e);if(t.length%32!=0)throw new _y({givenLength:t.length});if(t.length>sy)throw new hy({givenLength:t.length,maxBytecodeSize:sy});let n=Fn(bd(t)),r=t.length/32;if(r%2==0)throw new gy({givenLengthInWords:r});let i=pn(Fn(r),{size:2}),a=new Uint8Array([1,0]);return n.set(a,0),n.set(i,2),n}k();var yy=e=>{dy(e);let t=by(e);return{domain:{name:`zkSync`,version:`2`,chainId:e.chainId},types:{Transaction:[{name:`txType`,type:`uint256`},{name:`from`,type:`uint256`},{name:`to`,type:`uint256`},{name:`gasLimit`,type:`uint256`},{name:`gasPerPubdataByteLimit`,type:`uint256`},{name:`maxFeePerGas`,type:`uint256`},{name:`maxPriorityFeePerGas`,type:`uint256`},{name:`paymaster`,type:`uint256`},{name:`nonce`,type:`uint256`},{name:`value`,type:`uint256`},{name:`data`,type:`bytes`},{name:`factoryDeps`,type:`bytes32[]`},{name:`paymasterInput`,type:`bytes`}]},primaryType:`Transaction`,message:t}};function by(e){let{gas:t,nonce:n,to:r,from:i,value:a,maxFeePerGas:o,maxPriorityFeePerGas:s,factoryDeps:c,paymaster:l,paymasterInput:u,gasPerPubdata:d,data:f}=e;return{txType:113n,from:BigInt(i),to:r?BigInt(r):0n,gasLimit:t??0n,gasPerPubdataByteLimit:d??50000n,maxFeePerGas:o??0n,maxPriorityFeePerGas:s??0n,paymaster:l?BigInt(l):0n,nonce:n?BigInt(n):0n,value:a??0n,data:f??`0x`,factoryDeps:c?.map(e=>kn(vy(e)))??[],paymasterInput:u||`0x`}}var xy={blockTime:1e3,formatters:cy,serializers:py,custom:{getEip712Domain:yy}},Sy=L({...xy,blockTime:200,id:2741,name:`Abstract`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.mainnet.abs.xyz`],webSocket:[`wss://api.mainnet.abs.xyz/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.mainnet.abs.xyz`}},contracts:{multicall3:{address:`0xAa4De41dba0Ca5dCBb288b7cC6b708F3aaC759E7`,blockCreated:5288},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:5263}}}),Cy=L({...xy,blockTime:200,id:11124,name:`Abstract Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.testnet.abs.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.testnet.abs.xyz`}},testnet:!0,contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:358349},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:431682}}}),wy=L({id:787,name:`Acala`,network:`acala`,nativeCurrency:{name:`Acala`,symbol:`ACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-acala.aca-api.network`],webSocket:[`wss://eth-rpc-acala.aca-api.network`]}},blockExplorers:{default:{name:`Acala Blockscout`,url:`https://blockscout.acala.network`,apiUrl:`https://blockscout.acala.network/api`}},testnet:!1}),Ty=L({id:47,name:`Acria IntelliChain`,nativeCurrency:{decimals:18,name:`ACRIA`,symbol:`ACRIA`},rpcUrls:{default:{http:[`https://aic.acria.ai`]}},blockExplorers:{default:{name:`Acria Explorer`,url:`https://explorer.acria.ai`}},testnet:!1}),Ey=L({id:1215,name:`ADF Chain`,nativeCurrency:{name:`ADDFILL`,symbol:`ADF`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.adftechnology.com`]}},blockExplorers:{default:{name:`ADF Mainnet Explorer`,url:`https://explorer.adftechnology.com`}},testnet:!1}),Dy=L({id:36900,name:`ADI_Chain`,nativeCurrency:{decimals:18,name:`ADI`,symbol:`ADI`},rpcUrls:{default:{http:[`https://rpc.adifoundation.ai`]}},blockExplorers:{default:{name:`ADI Explorer`,url:`https://explorer.adifoundation.ai`}},testnet:!1}),Oy=L({id:9990,name:`Agung Network`,nativeCurrency:{decimals:18,name:`Agung`,symbol:`AGNG`},rpcUrls:{default:{http:[`https://wss-async.agung.peaq.network`],webSocket:[`wss://wss-async.agung.peaq.network`]}},blockExplorers:{default:{name:`Subscan`,url:`https://agung-testnet.subscan.io`}},testnet:!0}),ky=L({id:168,name:`AIOZ Network`,nativeCurrency:{decimals:18,name:`AIOZ`,symbol:`AIOZ`},rpcUrls:{default:{http:[`https://eth-dataseed.aioz.network`]}},blockExplorers:{default:{name:`AIOZ Explorer`,url:`https://explorer.aioz.network`}},testnet:!1}),Ay=L({id:41455,name:`Aleph Zero`,nativeCurrency:{name:`Aleph Zero`,symbol:`AZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero.raas.gelato.cloud`]}},blockExplorers:{default:{name:`Aleph Zero EVM Explorer`,url:`https://evm-explorer.alephzero.org`,apiUrl:`https://evm-explorer.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4603377}}}),jy=L({id:2039,name:`Aleph Zero Testnet`,nativeCurrency:{name:`TZERO`,symbol:`TZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero-testnet.gelato.digital`],webSocket:[`wss://ws.alephzero-testnet.gelato.digital`]}},blockExplorers:{default:{name:`Aleph Zero EVM Testnet explorer`,url:`https://evm-explorer-testnet.alephzero.org`,apiUrl:`https://evm-explorer-testnet.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2861745}},testnet:!0}),My=L({id:10241024,name:`AlienX Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://explorer.alienxchain.io`}},testnet:!1}),Ny=L({id:10241025,name:`ALIENX Hal Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://hal-rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://hal-explorer.alienxchain.io`}},testnet:!0}),Py=L({id:8150,name:`Alpen Testnet`,nativeCurrency:{name:`Signet BTC`,symbol:`sBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.alpenlabs.io`]}},blockExplorers:{default:{name:`Alpen Explorer`,url:`https://explorer.testnet.alpenlabs.io`,apiUrl:`https://explorer.testnet.alpenlabs.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:290408}},testnet:!0}),Fy={gasPriceOracle:{address:`0x420000000000000000000000000000000000000F`},l1Block:{address:`0x4200000000000000000000000000000000000015`},l2CrossDomainMessenger:{address:`0x4200000000000000000000000000000000000007`},l2Erc721Bridge:{address:`0x4200000000000000000000000000000000000014`},l2StandardBridge:{address:`0x4200000000000000000000000000000000000010`},l2ToL1MessagePasser:{address:`0x4200000000000000000000000000000000000016`}};On();var Iy={block:dd({format(e){return{transactions:e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=sd(e);return t.typeHex===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?Tn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}),stateRoot:e.stateRoot}}}),transaction:cd({format(e){let t={};return e.type===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?Tn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}}),transactionReceipt:Km({format(e){return{l1GasPrice:e.l1GasPrice?Tn(e.l1GasPrice):null,l1GasUsed:e.l1GasUsed?Tn(e.l1GasUsed):null,l1Fee:e.l1Fee?Tn(e.l1Fee):null,l1FeeScalar:e.l1FeeScalar?Number(e.l1FeeScalar):null}}})};gi(),Ei(),Ai(),k();function Ly(e,t){return By(e)?zy(e):mh(e,t)}var Ry={transaction:Ly};function zy(e){Vy(e);let{sourceHash:t,data:n,from:r,gas:i,isSystemTx:a,mint:o,to:s,value:c}=e;return ki([`0x7e`,mu([t,r,s??`0x`,o?kn(o):`0x`,c?kn(c):`0x`,i?kn(i):`0x`,a?`0x1`:`0x`,n??`0x`])])}function By(e){return e.type===`deposit`||e.sourceHash!==void 0}function Vy(e){let{from:t,to:n}=e;if(t&&!Ci(t))throw new hi({address:t});if(n&&!Ci(n))throw new hi({address:n})}var R={blockTime:2e3,contracts:Fy,formatters:Iy,serializers:Ry},Hy=1,Uy=L({...R,id:888888888,name:`Ancient8`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 explorer`,url:`https://scan.ancient8.gg`,apiUrl:`https://scan.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Hy]:{address:`0xB09DC08428C8b4EFB4ff9C0827386CDF34277996`}},portal:{[Hy]:{address:`0x639F2AECE398Aa76b07e59eF6abe2cFe32bacb68`,blockCreated:19070571}},l1StandardBridge:{[Hy]:{address:`0xd5e3eDf5b68135D559D572E26bF863FBC1950033`,blockCreated:19070571}}},sourceId:Hy}),Wy=11155111,Gy=L({...R,id:28122024,name:`Ancient8 Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpcv2-testnet.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 Celestia Testnet explorer`,url:`https://scanv2-testnet.ancient8.gg`,apiUrl:`https://scanv2-testnet.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Wy]:{address:`0x942fD5017c0F60575930D8574Eaca13BEcD6e1bB`}},portal:{[Wy]:{address:`0xfa1d9E26A6aCD7b22115D27572c1221B9803c960`,blockCreated:4972908}},l1StandardBridge:{[Wy]:{address:`0xF6Bc0146d3c74D48306e79Ae134A260E418C9335`,blockCreated:4972908}}},sourceId:Wy}),Ky=L({id:31337,name:`Anvil`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}}),qy=L({id:33139,name:`ApeChain`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.apechain.com/http`],webSocket:[`wss://rpc.apechain.com/ws`]}},blockExplorers:{default:{name:`Apescan`,url:`https://apescan.io`,apiUrl:`https://api.apescan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:20889}},sourceId:42161}),Jy=L({id:3993,name:`APEX Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.apexlayer.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-testnet.apexlayer.xyz`,apiUrl:`https://exp-testnet.apexlayer.xyz/api`}},contracts:{multicall3:{address:`0xf7642be33a6b18D16a995657adb5a68CD0438aE2`,blockCreated:283775}},testnet:!0}),Yy=L({id:62606,name:`Apollo`,nativeCurrency:{decimals:18,name:`Apollo`,symbol:`APOLLO`},rpcUrls:{default:{http:[`https://mainnet-rpc.apolloscan.io`]}},blockExplorers:{default:{name:`Apollo Explorer`,url:`https://apolloscan.io`}}}),Xy=L({id:42161,name:`Arbitrum One`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:250,rpcUrls:{default:{http:[`https://arb1.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://arbiscan.io`,apiUrl:`https://api.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7654707}}}),Zy=L({id:421613,name:`Arbitrum Goerli`,nativeCurrency:{name:`Arbitrum Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://goerli.arbiscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:88114}},testnet:!0}),Qy=L({id:42170,name:`Arbitrum Nova`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://nova.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://nova.arbiscan.io`,apiUrl:`https://api-nova.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1746963}}}),$y=L({id:421614,name:`Arbitrum Sepolia`,blockTime:250,nativeCurrency:{name:`Arbitrum Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://sepolia.arbiscan.io`,apiUrl:`https://api-sepolia.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:81930}},testnet:!0}),eb=L({id:5042002,name:`Arc Testnet`,nativeCurrency:{name:`USDC`,symbol:`USDC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.arc.network`,`https://rpc.quicknode.testnet.arc.network`,`https://rpc.blockdaemon.testnet.arc.network`],webSocket:[`wss://rpc.testnet.arc.network`,`wss://rpc.quicknode.testnet.arc.network`]}},blockExplorers:{default:{name:`ArcScan`,url:`https://testnet.arcscan.app`,apiUrl:`https://testnet.arcscan.app/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),tb=L({id:7897,name:`Arena-Z`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arena-z.gg`]}},blockExplorers:{default:{name:`Arena-Z Explorer`,url:`https://explorer.arena-z.gg`,apiUrl:`https://explorer.arena-z.gg`}}}),nb=L({id:463,name:`Areon Network`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areon.network`],webSocket:[`wss://mainnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),rb=L({id:462,name:`Areon Network Testnet`,nativeCurrency:{decimals:18,name:`TAREA`,symbol:`TAREA`},rpcUrls:{default:{http:[`https://testnet-rpc.areon.network`],webSocket:[`wss://testnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},testnet:!0}),ib=L({id:463,name:`Areum`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areum.network`],webSocket:[`wss://mainnet-ws.areum.network`]}},blockExplorers:{default:{name:`Areum Explorer`,url:`https://explorer.areum.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),ab=L({id:11822,name:`Artela Testnet`,nativeCurrency:{name:`ART`,symbol:`ART`,decimals:18},rpcUrls:{default:{http:[`https://betanet-rpc1.artela.network`]}},blockExplorers:{default:{name:`Artela`,url:`https://betanet-scan.artela.network`,apiUrl:`https://betanet-scan.artela.network/api`}},contracts:{multicall3:{address:`0xd07c8635f76e8745Ee7092fbb6e8fbc5FeF09DD7`,blockCreated:7001871}},testnet:!0}),ob=L({id:10242,name:`Arthera`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer.arthera.net`,apiUrl:`https://explorer.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4502791}}}),sb=L({id:10243,name:`Arthera Testnet`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer-test.arthera.net`,apiUrl:`https://explorer-test.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:22051}}}),cb=L({id:42420,name:`AssetChain Mainnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://mainnet-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Explorer`,url:`https://scan.assetchain.org`,apiUrl:`https://scan.assetchain.org/api`}},testnet:!1,contracts:{}}),lb=L({id:42421,name:`AssetChain Testnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://enugu-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Testnet Explorer`,url:`https://scan-testnet.assetchain.org`,apiUrl:`https://scan-testnet.assetchain.org/api`}},testnet:!0,contracts:{multicall3:{address:`0x989F832D35988cb5e3eB001Fa2Fe789469EC31Ea`,blockCreated:17177}}}),ub=L({id:592,name:`Astar`,network:`astar-mainnet`,nativeCurrency:{name:`Astar`,symbol:`ASTR`,decimals:18},rpcUrls:{default:{http:[`https://astar.api.onfinality.io/public`]}},blockExplorers:{default:{name:`Astar Subscan`,url:`https://astar.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:761794}},testnet:!1}),db=L({id:3776,name:`Astar zkEVM`,network:`AstarZkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-zkevm.astar.network`]}},blockExplorers:{default:{name:`Astar zkEVM Explorer`,url:`https://astar-zkevm.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:93528}},testnet:!1}),fb=L({id:6038361,name:`Astar zkEVM Testnet zKyoto`,network:`zKyoto`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.startale.com/zkyoto`]}},blockExplorers:{default:{name:`zKyoto Explorer`,url:`https://zkyoto.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:196153}},testnet:!0}),pb=L({id:2340,name:`Atleta Olympia`,nativeCurrency:{decimals:18,name:`Atla`,symbol:`ATLA`},rpcUrls:{default:{http:[`https://testnet-rpc.atleta.network:9944`,`https://testnet-rpc.atleta.network`],ws:[`wss://testnet-rpc.atleta.network:9944`]}},blockExplorers:{default:{name:`Atleta Olympia Explorer`,url:`https://blockscout.atleta.network`,apiUrl:`https://blockscout.atleta.network/api`}},contracts:{multicall3:{address:`0x1472ec6392180fb84F345d2455bCC75B26577115`,blockCreated:1076473}},testnet:!0}),mb=L({id:1313161554,name:`Aurora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://aurorascan.dev`,apiUrl:`https://aurorascan.dev/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:62907816}}}),hb=L({id:1313161555,name:`Aurora Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://testnet.aurorascan.dev`,apiUrl:`https://testnet.aurorascan.dev/api`}},testnet:!0}),gb=L({id:205205,name:`Auroria Testnet`,network:`auroria`,nativeCurrency:{name:`Auroria Stratis`,symbol:`tSTRAX`,decimals:18},rpcUrls:{default:{http:[`https://auroria.rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Auroria Testnet Explorer`,url:`https://auroria.explorer.stratisevm.com`}},testnet:!0}),_b=L({id:785,name:`Autheo Testnet`,nativeCurrency:{decimals:18,name:`Autheo`,symbol:`THEO`},rpcUrls:{default:{http:[`https://testnet-rpc1.autheo.com`,`https://testnet-rpc2.autheo.com`]}},blockExplorers:{default:{name:`Autheo Testnet Block Explorer`,url:`https://testnet-explorer.autheo.com/`}}}),vb=L({id:43114,name:`Avalanche`,blockTime:1700,nativeCurrency:{decimals:18,name:`Avalanche`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://snowtrace.io`,apiUrl:`https://api.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11907934}}}),yb=L({id:43113,name:`Avalanche Fuji`,nativeCurrency:{decimals:18,name:`Avalanche Fuji`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax-test.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://testnet.snowtrace.io`,apiUrl:`https://api-testnet.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7096959}},testnet:!0}),bb=L({id:8333,name:`B3`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},sourceId:8453}),xb=L({id:1993,name:`B3 Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},testnet:!0,sourceId:168587773}),Sb=L({id:5165,network:`bahamut`,name:`Bahamut`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.bahamut.io`,`https://bahamut-rpc.publicnode.com`,`https://rpc2.bahamut.io`],webSocket:[`wss://ws1.sahara.bahamutchain.com`,`wss://bahamut-rpc.publicnode.com`,`wss://ws2.sahara.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://www.ftnscan.com`,apiUrl:`https://www.ftnscan.com/api`}}}),Cb=1,wb=L({...R,id:8453,name:`Base`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://basescan.org`,apiUrl:`https://api.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[Cb]:{address:`0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e`}},l2OutputOracle:{[Cb]:{address:`0x56315b90c40730925ec5485cf004d835058518A0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:5022},portal:{[Cb]:{address:`0x49048044D57e1C92A77f79988d21Fa8fAF74E97e`,blockCreated:17482143}},l1StandardBridge:{[Cb]:{address:`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`,blockCreated:17482143}}},sourceId:Cb}),Tb=L({...wb,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://mainnet-preconf.base.org`]}}}),Eb=L({id:123420001114,name:`Basecamp Testnet`,nativeCurrency:{decimals:18,name:`Camp`,symbol:`CAMP`},rpcUrls:{default:{http:[`https://rpc.basecamp.t.raas.gelato.cloud`]}},blockExplorers:{default:{name:`basecamp`,url:`https://basecamp.cloud.blockscout.com`}},testnet:!0}),Db=5,Ob=L({...R,id:84531,name:`Base Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://goerli.basescan.org`,apiUrl:`https://goerli.basescan.org/api`}},contracts:{...R.contracts,l2OutputOracle:{[Db]:{address:`0x2A35891ff30313CcFa6CE88dcf3858bb075A2298`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1376988},portal:{[Db]:{address:`0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA`}},l1StandardBridge:{[Db]:{address:`0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a`}}},testnet:!0,sourceId:Db}),kb=11155111,Ab=L({...R,id:84532,network:`base-sepolia`,name:`Base Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://sepolia.basescan.org`,apiUrl:`https://api-sepolia.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[kb]:{address:`0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1`}},l2OutputOracle:{[kb]:{address:`0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254`}},portal:{[kb]:{address:`0x49f53e41452c74589e85ca1677426ba426459e85`,blockCreated:4446677}},l1StandardBridge:{[kb]:{address:`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`,blockCreated:4446677}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1059647}},testnet:!0,sourceId:kb}),jb=L({...Ab,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://sepolia-preconf.base.org`]}}}),Mb=L({id:4337,name:`Beam`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc`],webSocket:[`wss://build.onbeam.com/ws`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets.avax.network/beam`}},contracts:{multicall3:{address:`0x4956f15efdc3dc16645e90cc356eafa65ffc65ec`,blockCreated:1}}}),Nb=L({id:13337,name:`Beam Testnet`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc/testnet`],webSocket:[`wss://build.onbeam.com/ws/testnet`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets-test.avax.network/beam`}},contracts:{multicall3:{address:`0x9bf49b704ee2a095b95c1f2d4eb9010510c41c9e`,blockCreated:3}},testnet:!0}),Pb=L({id:641230,name:`Bear Network Chain Mainnet`,nativeCurrency:{decimals:18,name:`BearNetworkChain`,symbol:`BRNKC`},rpcUrls:{default:{http:[`https://brnkc-mainnet.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkScan`,url:`https://brnkscan.bearnetwork.net`,apiUrl:`https://brnkscan.bearnetwork.net/api`}}}),Fb=L({id:751230,name:`Bear Network Chain Testnet`,nativeCurrency:{decimals:18,name:`tBRNKC`,symbol:`tBRNKC`},rpcUrls:{default:{http:[`https://brnkc-test.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkTestScan`,url:`https://brnktest-scan.bearnetwork.net`,apiUrl:`https://brnktest-scan.bearnetwork.net/api`}},testnet:!0}),Ib=L({id:80094,name:`Berachain`,blockTime:2e3,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},ensRegistry:{address:`0x5b22280886a2f5e09a49bea7e320eab0e5320e28`,blockCreated:877007},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:9310021}},rpcUrls:{default:{http:[`https://rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://berascan.com`}},ensTlds:[`.bera`],testnet:!1}),Lb=L({id:80069,blockTime:2e3,name:`Berachain Bepolia`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},rpcUrls:{default:{http:[`https://bepolia.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://bepolia.beratrail.io`}},testnet:!0}),Rb=L({id:80085,name:`Berachain Artio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},rpcUrls:{default:{http:[`https://artio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain`,url:`https://artio.beratrail.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:866924}},testnet:!0}),zb=L({id:80084,name:`Berachain bArtio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:109269},ensRegistry:{address:`0xB0eef18971290b333450586D33dcA6cE122651D2`,blockCreated:7736794},ensUniversalResolver:{address:`0x41692Ef1EA0C79E6b73077E4A67572D2BDbD7057`,blockCreated:7736795}},ensTlds:[`.bera`],rpcUrls:{default:{http:[`https://bartio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain bArtio Beratrail`,url:`https://bartio.beratrail.io`}},testnet:!0}),Bb=L({id:11501,name:`BEVM Mainnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet-1.bevm.io`]}},blockExplorers:{default:{name:`Bevmscan`,url:`https://scan-mainnet.bevm.io`,apiUrl:`https://scan-mainnet-api.bevm.io/api`}}}),Vb=L({id:3068,name:`Bifrost Mainnet`,nativeCurrency:{name:`BFC`,symbol:`BFC`,decimals:18},rpcUrls:{default:{http:[`https://public-01.mainnet.bifrostnetwork.com/rpc`]}},blockExplorers:{default:{name:`Bifrost Blockscout`,url:`https://explorer.mainnet.bifrostnetwork.com`}},testnet:!1}),Hb=L({id:53456,name:`BirdLayer`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.birdlayer.xyz`,`https://rpc1.birdlayer.xyz`],webSocket:[`wss://rpc.birdlayer.xyz/ws`]}},blockExplorers:{default:{name:`BirdLayer Explorer`,url:`https://scan.birdlayer.xyz`}}}),Ub=L({id:32520,name:`Bitgert Mainnet`,nativeCurrency:{decimals:18,name:`Brise`,symbol:`Brise`},rpcUrls:{default:{http:[`https://rpc-bitgert.icecreamswap.com`]}},blockExplorers:{default:{name:`Bitgert Scan`,url:`https://brisescan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2118034}},testnet:!1}),Wb=L({id:96,name:`KUB Mainnet`,nativeCurrency:{name:`KUB Coin`,symbol:`KUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitkubchain.io`]}},blockExplorers:{default:{name:`KUB Chain Mainnet Explorer`,url:`https://www.bkcscan.com`,apiUrl:`https://www.bkcscan.com/api`}}}),Gb=L({id:25925,name:`Bitkub Testnet`,network:`Bitkub Testnet`,nativeCurrency:{name:`Bitkub Test`,symbol:`tKUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.bitkubchain.io`]}},blockExplorers:{default:{name:`Bitkub Chain Testnet Explorer`,url:`https://testnet.bkcscan.com`,apiUrl:`https://testnet.bkcscan.com/api`}},testnet:!0}),Kb=L({id:200901,name:`Bitlayer Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`],webSocket:[`wss://ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer mainnet scan`,url:`https://www.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:2421963}}}),qb=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer testnet scan`,url:`https://testnet.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:4135671}},testnet:!0}),Jb=L({id:7171,name:`Bitrock Mainnet`,nativeCurrency:{name:`BROCK`,symbol:`BROCK`,decimals:18},rpcUrls:{default:{http:[`https://brockrpc.io`]}},blockExplorers:{default:{name:`Bitrock Explorer`,url:`https://explorer.bit-rock.io`}},testnet:!1}),Yb=L({id:199,name:`BitTorrent`,network:`bittorrent-chain-mainnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://bttcscan.com`,apiUrl:`https://api.bttcscan.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:31078552}}}),Xb=L({id:1028,name:`BitTorrent Chain Testnet`,network:`bittorrent-chain-testnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://testrpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://testnet.bttcscan.com`,apiUrl:`https://testnet.bttcscan.com/api`}},testnet:!0}),Zb=1,Qb=L({...R,id:81457,name:`Blast`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://blastscan.io`,apiUrl:`https://api.blastscan.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:212929},l2OutputOracle:{[Zb]:{address:`0x826D1B0D4111Ad9146Eb8941D7Ca2B6a44215c76`,blockCreated:19300358}},portal:{[Zb]:{address:`0x0Ec68c5B10F21EFFb74f2A5C61DFe6b08C0Db6Cb`,blockCreated:19300357}},l1StandardBridge:{[Zb]:{address:`0x697402166Fbf2F22E970df8a6486Ef171dbfc524`,blockCreated:19300360}}},sourceId:Zb}),$b=L({id:168587773,name:`Blast Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://sepolia.blastscan.io`,apiUrl:`https://api-sepolia.blastscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:756690}},testnet:!0,sourceId:11155111}),ex=1,tx=L({...R,id:60808,name:`BOB`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.gobob.xyz`],webSocket:[`wss://rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Explorer`,url:`https://explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:23131},l2OutputOracle:{[ex]:{address:`0xdDa53E23f8a32640b04D7256e651C1db98dB11C1`,blockCreated:4462615}},portal:{[ex]:{address:`0x8AdeE124447435fE03e3CD24dF3f4cAE32E65a3E`,blockCreated:4462615}}},sourceId:ex}),nx=L({id:288,name:`Boba Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://bobascan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:446859}}}),rx=L({id:28882,name:`Boba Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://testnet.bobascan.com`}},testnet:!0}),ix=11155111,ax=L({...R,id:808813,name:`BOB Sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://bob-sepolia.rpc.gobob.xyz`],webSocket:[`wss://bob-sepolia.rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Sepolia Explorer`,url:`https://bob-sepolia.explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:35677},l2OutputOracle:{[ix]:{address:`0x14D0069452b4AE2b250B395b8adAb771E4267d2f`,blockCreated:4462615}},portal:{[ix]:{address:`0x867B1Aa872b9C8cB5E9F7755feDC45BB24Ad0ae4`,blockCreated:4462615}}},testnet:!0,sourceId:ix}),ox=L({id:11100,name:`Bool Beta Mainnet`,nativeCurrency:{decimals:18,name:`BOL`,symbol:`BOL`},rpcUrls:{default:{http:[`https://beta-rpc-node-http.bool.network`]}},blockExplorers:{default:{name:`BoolScan`,url:`https://beta-mainnet.boolscan.com/`}},testnet:!1}),sx=L({id:3637,name:`Botanix`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.botanixlabs.com`],webSocket:[`wss://rpc.botanixlabs.com/ws`]}},blockExplorers:{default:{name:`Botanixscan`,url:`https://botanixscan.io`}}}),cx=L({id:3636,name:`Botanix Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://node.botanixlabs.dev`]}},blockExplorers:{default:{name:`Botanix Testnet Explorer`,url:`https://testnet.botanixscan.io`}},testnet:!0}),lx=L({id:6001,name:`BounceBit Mainnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-mainnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://bbscan.io`}},testnet:!1}),ux=L({id:6e3,name:`BounceBit Testnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-testnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://testnet.bbscan.io`}},testnet:!0}),dx=L({id:1039,name:`Bronos`,nativeCurrency:{decimals:18,name:`BRO`,symbol:`BRO`},rpcUrls:{default:{http:[`https://evm.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://broscan.bronos.org`}}}),fx=L({id:1038,name:`Bronos Testnet`,nativeCurrency:{decimals:18,name:`Bronos Coin`,symbol:`tBRO`},rpcUrls:{default:{http:[`https://evm-testnet.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://tbroscan.bronos.org`}},testnet:!0}),px=L({id:56,name:`BNB Smart Chain`,blockTime:750,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://56.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`BscScan`,url:`https://bscscan.com`,apiUrl:`https://api.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15921452}}}),mx=L({id:1017,name:`BNB Greenfield Chain`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://greenfield-chain.bnbchain.org`]}},blockExplorers:{default:{name:`BNB Greenfield Mainnet Scan`,url:`https://greenfieldscan.com`}},testnet:!1}),hx=L({id:97,name:`BNB Smart Chain Testnet`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://data-seed-prebsc-1-s1.bnbchain.org:8545`]}},blockExplorers:{default:{name:`BscScan`,url:`https://testnet.bscscan.com`,apiUrl:`https://api-testnet.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:17422483}},testnet:!0}),gx=L({id:223,name:`B2`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.bsquared.network`}}}),_x=L({id:1123,name:`B2 Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-explorer.bsquared.network`}},testnet:!0}),vx=L({id:200901,name:`Bitlayer`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`,`https://rpc.bitlayer-rpc.com`],webSocket:[`wss://ws.bitlayer.org`,`wss://ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://www.btrscan.com`}}}),yx=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`,`wss://testnet-ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://testnet.btrscan.com`}},testnet:!0}),bx=L({id:4999,name:`BlackFort Exchange Network`,nativeCurrency:{name:`BlackFort Token`,symbol:`BXN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.blackfort.network`,apiUrl:`https://explorer.blackfort.network/api`}}}),xx=L({id:4777,name:`BlackFort Exchange Network Testnet`,nativeCurrency:{name:`BlackFort Testnet Token`,symbol:`TBXN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.blackfort.network`,apiUrl:`https://testnet-explorer.blackfort.network/api`}},testnet:!0}),Sx=L({id:13370,name:`Cannon`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),Cx=L({id:7700,name:`Canto`,nativeCurrency:{decimals:18,name:`Canto`,symbol:`CANTO`},rpcUrls:{default:{http:[`https://canto.gravitychain.io`]}},blockExplorers:{default:{name:`Tuber.Build (Blockscout)`,url:`https://tuber.build`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2905789}}}),wx={estimateFeesPerGas:async e=>{if(!e.request?.feeCurrency)return null;let[t,n]=await Promise.all([Tx(e.client,e.request.feeCurrency),Ex(e.client,e.request.feeCurrency)]);return{maxFeePerGas:e.multiply(t-n)+n,maxPriorityFeePerGas:n}}};async function Tx(e,t){let n=await e.request({method:`eth_gasPrice`,params:[t]});return BigInt(n)}async function Ex(e,t){let n=await e.request({method:`eth_maxPriorityFeePerGas`,params:[t]});return BigInt(n)}Cn();function Dx(e){return e===0||e===0n||e==null||e===`0`||e===``||typeof e==`string`&&(Sn(e).toLowerCase()===`0x`||Sn(e).toLowerCase()===`0x00`)}function Ox(e){return!Dx(e)}function kx(e){return e.maxFeePerGas!==void 0&&e.maxPriorityFeePerGas!==void 0}function Ax(e){return e.type===`cip64`?!0:kx(e)&&Ox(e.feeCurrency)}On(),Ku();var jx={block:dd({format(e){return{transactions:e.transactions?.map(e=>typeof e==`string`?e:{...sd(e),...e.gatewayFee?{gatewayFee:Tn(e.gatewayFee),gatewayFeeRecipient:e.gatewayFeeRecipient}:{},feeCurrency:e.feeCurrency})}}}),transaction:cd({format(e){if(e.type===`0x7e`)return{isSystemTx:e.isSystemTx,mint:e.mint?Tn(e.mint):void 0,sourceHash:e.sourceHash,type:`deposit`};let t={feeCurrency:e.feeCurrency};return e.type===`0x7b`?t.type=`cip64`:(e.type===`0x7c`&&(t.type=`cip42`),t.gatewayFee=e.gatewayFee?Tn(e.gatewayFee):null,t.gatewayFeeRecipient=e.gatewayFeeRecipient),t}}),transactionRequest:Gu({format(e){let t={};return e.feeCurrency&&(t.feeCurrency=e.feeCurrency),Ax(e)&&(t.type=`0x7b`),t}})};$u(),gi(),D(),Fp(),Pu(),Ei(),Ai(),k();function Mx(e,t){return Ax(e)?Px(e,t):Ly(e,t)}var Nx={transaction:Mx};function Px(e,t){Ix(e);let{chainId:n,gas:r,nonce:i,to:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,accessList:l,feeCurrency:u,data:d}=e;return ki([`0x7b`,mu([kn(n),i?kn(i):`0x`,c?kn(c):`0x`,s?kn(s):`0x`,r?kn(r):`0x`,a??`0x`,o?kn(o):`0x`,d??`0x`,ph(l),u,...bh(e,t)])])}var Fx=Qu;function Ix(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a,feeCurrency:o}=e;if(t<=0)throw new Pp({chainId:t});if(a&&!Ci(a))throw new hi({address:a});if(r)throw new E("`gasPrice` is not a valid CIP-64 Transaction attribute.");if(Ox(i)&&i>Fx)throw new Cu({maxFeePerGas:i});if(Ox(n)&&Ox(i)&&n>i)throw new Mu({maxFeePerGas:i,maxPriorityFeePerGas:n});if(Ox(o)&&!Ci(o))throw new E("`feeCurrency` MUST be a token address for CIP-64 transactions.");if(Dx(o))throw new E("`feeCurrency` must be provided for CIP-64 transactions.")}var Lx={blockTime:1e3,contracts:Fy,formatters:jx,serializers:Nx,fees:wx},Rx=L({...Lx,id:42220,name:`Celo`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`CELO`},rpcUrls:{default:{http:[`https://forno.celo.org`]}},blockExplorers:{default:{name:`Celo Explorer`,url:`https://celoscan.io`,apiUrl:`https://api.celoscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13112599}},testnet:!1}),zx=17e3,Bx=L({...Lx,id:44787,name:`Alfajores`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`A-CELO`},rpcUrls:{default:{http:[`https://alfajores-forno.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Alfajores Explorer`,url:`https://celo-alfajores.blockscout.com`,apiUrl:`https://celo-alfajores.blockscout.com/api`}},contracts:{...Lx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:14569001},portal:{[zx]:{address:`0x82527353927d8D069b3B452904c942dA149BA381`,blockCreated:2411324}},disputeGameFactory:{[zx]:{address:`0xE28AAdcd9883746c0e5068F58f9ea06027b214cb`,blockCreated:2411324}},l2OutputOracle:{[zx]:{address:`0x4a2635e9e4f6e45817b1D402ac4904c1d1752438`,blockCreated:2411324}},l1StandardBridge:{[zx]:{address:`0xD1B0E0581973c9eB7f886967A606b9441A897037`,blockCreated:2411324}}},testnet:!0}),Vx=11155111,Hx=L({...Lx,id:11142220,name:`Celo Sepolia Testnet`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`S-CELO`},rpcUrls:{default:{http:[`https://forno.celo-sepolia.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Sepolia Explorer`,url:`https://celo-sepolia.blockscout.com/`,apiUrl:`https://celo-sepolia.blockscout.com/api`}},contracts:{...Lx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1},portal:{[Vx]:{address:`0x44ae3d41a335a7d05eb533029917aad35662dcc2`,blockCreated:8825790}},disputeGameFactory:{[Vx]:{address:`0x57c45d82d1a995f1e135b8d7edc0a6bb5211cfaa`,blockCreated:8825790}},l1StandardBridge:{[Vx]:{address:`0xec18a3c30131a0db4246e785355fbc16e2eaf408`,blockCreated:8825790}}},testnet:!0}),Ux=L({id:5858,name:`Chang Chain Foundation Mainnet`,nativeCurrency:{decimals:18,name:`CTH`,symbol:`CTH`},rpcUrls:{default:{http:[`https://rpc.cthscan.com`]}},blockExplorers:{default:{name:`Chang Chain explorer`,url:`https://cthscan.com`}}}),Wx=L({id:88888,name:`Chiliz Chain`,network:`chiliz-chain`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://rpc.chiliz.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`https://scan.chiliz.com`,apiUrl:`https://scan.chiliz.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8080847}}}),Gx=L({id:2882,name:`Chips Network`,network:`CHIPS`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://node.chips.ooo/wasp/api/v1/chains/iota1pp3d3mnap3ufmgqnjsnw344sqmf5svjh26y2khnmc89sv6788y3r207a8fn/evm`]}}}),Kx=L({id:4114,name:`Citrea Mainnet`,nativeCurrency:{name:`Citrea Bitcoin`,symbol:`cBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.citrea.xyz`]}},blockExplorers:{default:{name:`Citrea Explorer`,url:`https://explorer.mainnet.citrea.xyz`,apiUrl:`https://explorer.mainnet.citrea.xyz/api`}},testnet:!1}),qx=L({id:5115,name:`Citrea Testnet`,nativeCurrency:{name:`cBTC`,symbol:`cBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.citrea.xyz`]}},blockExplorers:{default:{name:`Citrea Explorer`,url:`https://explorer.testnet.citrea.xyz`,apiUrl:`https://explorer.testnet.citrea.xyz/api`}},testnet:!0}),Jx=L({id:61,name:`Ethereum Classic`,nativeCurrency:{decimals:18,name:`ETC`,symbol:`ETC`},rpcUrls:{default:{http:[`https://etc.rivet.link`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.com/etc/mainnet`}}}),Yx=1,Xx=L({...R,id:81224,name:`Codex`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.codex.xyz`]}},blockExplorers:{default:{name:`Codex Explorer`,url:`https://explorer.codex.xyz`,apiUrl:`https://explorer.codex.xyz/api`}},contracts:{...R.contracts,disputeGameFactory:{[Yx]:{address:`0x6A3855dc26e2beA8Ac73f82Cda79f3808B6C6F6C`}},portal:{[Yx]:{address:`0x52759C07A759c81BAab28AE1BE5A19e6450959bD`}},l1StandardBridge:{[Yx]:{address:`0xa6b1A05a592719B0C8a70c69eac114C48410aDE4`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},sourceId:Yx}),Zx=11155111,Qx=L({...R,id:812242,name:`Codex Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.codex-stg.xyz`]}},blockExplorers:{default:{name:`Codex Testnet Explorer`,url:`https://explorer.codex-stg.xyz`,apiUrl:`https://explorer.codex-stg.xyz/api`}},contracts:{...R.contracts,disputeGameFactory:{[Zx]:{address:`0x390e24E8324E56f13A8d48eB938b6f9De24CD205`}},portal:{[Zx]:{address:`0x037F161D12c829A9ca4742eEd9371830CA54fcB2`}},l1StandardBridge:{[Zx]:{address:`0xCf4df2bDB14C8FDB25FdacCEC10Ce5C4bAEDB3De`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},sourceId:Zx}),$x=L({id:112,name:`Coinbit Mainnet`,nativeCurrency:{name:`GIDR`,symbol:`GIDR`,decimals:18},rpcUrls:{default:{http:[`https://coinbit-rpc-mainnet.chain.sbcrypto.app`]}},blockExplorers:{default:{name:`Coinbit Explorer`,url:`https://coinbit-explorer.chain.sbcrypto.app`}},testnet:!1}),eS=L({id:52,name:`CoinEx Mainnet`,nativeCurrency:{name:`cet`,symbol:`cet`,decimals:18},rpcUrls:{default:{http:[`https://rpc.coinex.net`]}},blockExplorers:{default:{name:`CoinEx Explorer`,url:`https://www.coinex.net`}},testnet:!1}),tS=L({id:1030,name:`Conflux eSpace`,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evm.confluxrpc.com`],webSocket:[`wss://evm.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evm.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:68602935}}}),nS=L({id:71,name:`Conflux eSpace Testnet`,network:`cfx-espace-testnet`,testnet:!0,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evmtestnet.confluxrpc.com`],webSocket:[`wss://evmtestnet.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evmtestnet.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:117499050}}}),rS=L({id:1116,name:`Core Dao`,nativeCurrency:{decimals:18,name:`Core`,symbol:`CORE`},rpcUrls:{default:{http:[`https://rpc.coredao.org`]}},blockExplorers:{default:{name:`CoreDao`,url:`https://scan.coredao.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:11907934}},testnet:!1}),iS=L({id:1115,name:`Core Testnet`,nativeCurrency:{decimals:18,name:`tCore`,symbol:`TCORE`},rpcUrls:{default:{http:[`https://rpc.test.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet`,url:`https://scan.test.btcs.network`,apiUrl:`https://api.test.btcs.network/api`}},contracts:{multicall3:{address:`0xCcddF20A1932537123C2E48Bd8e00b108B8f7569`,blockCreated:29350509}},testnet:!0}),aS=L({id:1114,name:`Core Testnet2`,nativeCurrency:{decimals:18,name:`tCore2`,symbol:`TCORE2`},rpcUrls:{default:{http:[`https://rpc.test2.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet2`,url:`https://scan.test2.btcs.network`,apiUrl:`https://api.test2.btcs.network/api`}},contracts:{multicall3:{address:`0x3CB285ff3Cd5C7C7e570b1E7DE3De17A0f985e56`,blockCreated:3838600}},testnet:!0}),oS=L({id:21e6,name:`Corn`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Explorer`,url:`https://cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/21000000/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3228}},sourceId:1}),sS=L({id:21000001,name:`Corn Testnet`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Testnet Explorer`,url:`https://testnet.cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/21000001/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4886}},testnet:!0,sourceId:11155111}),cS=L({id:86608,name:`CpChain`,nativeCurrency:{decimals:18,name:`CpChain`,symbol:`CP`},rpcUrls:{default:{http:[`https://rpc.cpchain.com`]}},blockExplorers:{default:{name:`CpChain Explorer`,url:`https://explorer.cpchain.com`}},testnet:!1}),lS=L({id:44,name:`Crab Network`,nativeCurrency:{decimals:18,name:`Crab Network Native Token`,symbol:`CRAB`},rpcUrls:{default:{http:[`https://crab-rpc.darwinia.network`],webSocket:[`wss://crab-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://crab-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3032593}}}),uS=L({id:66665,name:`Creator`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.creatorchain.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.creatorchain.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0}),dS=L({id:102032,name:`Creditcoin Devnet`,nativeCurrency:{name:`Devnet CTC`,symbol:`devCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-devnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-devnet.creditcoin.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-devnet.blockscout.com`,apiUrl:`https://creditcoin3-dev.subscan.io`}},testnet:!0}),fS=L({id:102030,name:`Creditcoin`,nativeCurrency:{name:`Creditcoin`,symbol:`CTC`,decimals:18},rpcUrls:{default:{http:[`https://mainnet3.creditcoin.network`],webSocket:[`wss://mainnet3.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin.blockscout.com`,apiUrl:`https://creditcoin.blockscout.com/api`}},testnet:!1}),pS=L({id:102031,name:`Creditcoin Testnet`,nativeCurrency:{name:`Creditcoin Testnet`,symbol:`tCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-testnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-testnet.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-testnet.blockscout.com`,apiUrl:`https://creditcoin-testnet.blockscout.com/api`}},testnet:!0}),mS=L({id:25,name:`Cronos Mainnet`,nativeCurrency:{decimals:18,name:`Cronos`,symbol:`CRO`},rpcUrls:{default:{http:[`https://evm.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer`,url:`https://explorer.cronos.org`,apiUrl:`https://explorer-api.cronos.org/mainnet/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1963112}}}),hS=L({id:338,name:`Cronos Testnet`,nativeCurrency:{decimals:18,name:`CRO`,symbol:`tCRO`},rpcUrls:{default:{http:[`https://evm-t3.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer (Testnet)`,url:`https://explorer.cronos.org/testnet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:10191251}},testnet:!0}),gS=L({id:388,name:`Cronos zkEVM Mainnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM CRO`,symbol:`zkCRO`},rpcUrls:{default:{http:[`https://mainnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM (Mainnet) Chain Explorer`,url:`https://explorer.zkevm.cronos.org`}},contracts:{multicall3:{address:`0x06f4487d7c4a5983d2660db965cc6d2565e4cfaa`,blockCreated:72}}}),_S=L({id:282,name:`Cronos zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM Test Coin`,symbol:`zkTCRO`},rpcUrls:{default:{http:[`https://testnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM Testnet Explorer`,url:`https://explorer.zkevm.cronos.org/testnet`}},testnet:!0}),vS=L({id:3737,name:`Crossbell`,nativeCurrency:{decimals:18,name:`CSB`,symbol:`CSB`},rpcUrls:{default:{http:[`https://rpc.crossbell.io`]}},blockExplorers:{default:{name:`CrossScan`,url:`https://scan.crossbell.io`,apiUrl:`https://scan.crossbell.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:38246031}}}),yS=L({id:4158,name:`CrossFi Mainnet`,nativeCurrency:{decimals:18,name:`CrossFi`,symbol:`XFI`},rpcUrls:{default:{http:[`https://rpc.mainnet.ms`]}},blockExplorers:{default:{name:`CrossFi Blockchain Explorer`,url:`https://xfiscan.com`}},testnet:!1}),bS=L({id:33111,name:`Curtis`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.curtis.apechain.com`]}},blockExplorers:{default:{name:`Curtis Explorer`,url:`https://explorer.curtis.apechain.com`}},testnet:!0}),xS=L({id:7560,name:`Cyber`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://cyberscan.co`,apiUrl:`https://cyberscan.co/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),SS=L({id:111557560,name:`Cyber Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber-testnet.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet.cyberscan.co`,apiUrl:`https://testnet.cyberscan.co/api`}},contracts:{multicall3:{address:`0xffc391F0018269d4758AEA1a144772E8FB99545E`,blockCreated:304545}},testnet:!0}),CS=L({id:824,name:`Daily Network Mainnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.mainnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Mainnet Explorer`,url:`https://explorer.mainnet.dailycrypto.net`}},testnet:!1}),wS=L({id:825,name:`Daily Network Testnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.testnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Testnet Explorer`,url:`https://explorer.testnet.dailycrypto.net`}},testnet:!0}),TS=L({id:46,name:`Darwinia Network`,nativeCurrency:{decimals:18,name:`RING`,symbol:`RING`},rpcUrls:{default:{http:[`https://rpc.darwinia.network`],webSocket:[`wss://rpc.darwinia.network`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69420}}}),ES=L({id:55931,name:`Datahaven Testnet`,nativeCurrency:{decimals:18,name:`MOCK`,symbol:`MOCK`},rpcUrls:{default:{http:[`https://services.datahaven-testnet.network/testnet`],webSocket:[`wss://services.datahaven-testnet.network/testnet`]}},blockExplorers:{default:{name:`DhScan`,url:`https://testnet.dhscan.io/`,apiUrl:`https://testnet.dhscan.io/api-docs`}},contracts:{},testnet:!0}),DS=L({id:20240603,name:`DBK chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.dbkchain.io`]}},blockExplorers:{default:{name:`DBK Chain Explorer`,url:`https://scan.dbkchain.io`}},testnet:!1}),OS=L({...R,id:0x9a697f88076c8,name:`Dchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchain-2716446429837000-1.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchain-2716446429837000-1.sagaexplorer.io`,apiUrl:`https://api-dchain-2716446429837000-1.sagaexplorer.io/api`}},contracts:{...R.contracts}}),kS=L({...R,id:0x9a379ba03cf10,name:`Dchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchaintestnet-2713017997578000-1.jsonrpc.testnet.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io`,apiUrl:`https://api-dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io/api`}},contracts:{...R.contracts}}),AS=L({id:1130,network:`defichain-evm`,name:`DeFiChain EVM Mainnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.mainnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:137852}}}),jS=L({id:1131,network:`defichain-evm-testnet`,name:`DeFiChain EVM Testnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.testnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live/?network=TestNet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:156462}},testnet:!0}),MS=L({id:666666666,name:`Degen`,nativeCurrency:{decimals:18,name:`Degen`,symbol:`DEGEN`},rpcUrls:{default:{http:[`https://rpc.degen.tips`],webSocket:[`wss://rpc.degen.tips`]}},blockExplorers:{default:{name:`Degen Chain Explorer`,url:`https://explorer.degen.tips`,apiUrl:`https://explorer.degen.tips/api/v2`}}}),NS=L({id:53935,name:`DFK Chain`,nativeCurrency:{decimals:18,name:`Jewel`,symbol:`JEWEL`},rpcUrls:{default:{http:[`https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc`]}},blockExplorers:{default:{name:`DFKSubnetScan`,url:`https://subnets.avax.network/defi-kingdoms`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14790551}}}),PS=L({id:15,name:`Diode Prenet`,nativeCurrency:{decimals:18,name:`DIODE`,symbol:`DIODE`},rpcUrls:{default:{http:[`https://prenet.diode.io:8443`],webSocket:[`wss://prenet.diode.io:8443/ws`]}},blockExplorers:{default:{name:`Diode Explorer`,url:`https://diode.io/prenet`}},testnet:!1}),FS=L({id:513100,name:`DisChain`,nativeCurrency:{decimals:18,name:`DIS`,symbol:`DIS`},rpcUrls:{default:{http:[`https://rpc.dischain.xyz`]}},blockExplorers:{default:{name:`DisChain Explorer`,url:`https://www.oklink.com/dis`}}}),IS=L({id:53457,name:`DODOchain Testnet`,nativeCurrency:{decimals:18,name:`DODO`,symbol:`DODO`},rpcUrls:{default:{http:[`https://dodochain-testnet.alt.technology`],webSocket:[`wss://dodochain-testnet.alt.technology/ws`]}},blockExplorers:{default:{name:`DODOchain Testnet (Sepolia) Explorer`,url:`https://testnet-scan.dodochain.com`}},testnet:!0}),LS=L({id:2e3,name:`Dogechain`,nativeCurrency:{decimals:18,name:`Wrapped Dogecoin`,symbol:`WDOGE`},rpcUrls:{default:{http:[`https://rpc.dogechain.dog`]}},blockExplorers:{default:{name:`DogeChainExplorer`,url:`https://explorer.dogechain.dog`,apiUrl:`https://explorer.dogechain.dog/api`}},contracts:{multicall3:{address:`0x68a8609a60a008EFA633dfdec592c03B030cC508`,blockCreated:25384031}}}),RS=L({id:97476,name:`Doma Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc-testnet.doma.xyz`]}},blockExplorers:{default:{name:`Doma Testnet Explorer`,url:`https://explorer-testnet.doma.xyz`}},testnet:!0}),zS=L({id:42026,name:`Donatuz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.donatuz.com`]}},blockExplorers:{default:{name:`Donatuz Explorer`,url:`https://explorer.donatuz.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),BS=L({id:7979,name:`DOS Chain`,nativeCurrency:{decimals:18,name:`DOS Chain`,symbol:`DOS`},rpcUrls:{default:{http:[`https://main.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Explorer`,url:`https://doscan.io`,apiUrl:`https://api.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:161908}}}),VS=L({id:3939,name:`DOS Chain Testnet`,nativeCurrency:{decimals:18,name:`DOS Chain Testnet`,symbol:`DOS`},rpcUrls:{default:{http:[`https://test.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Testnet Explorer`,url:`https://test.doscan.io`,apiUrl:`https://api-test.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69623}},testnet:!0}),HS=L({id:23451,name:`DreyerX Mainnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Scan`,url:`https://scan.dreyerx.com`}}}),US=L({id:23452,name:`DreyerX Testnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`http://testnet-rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Testnet Scan`,url:`https://testnet-scan.dreyerx.com`}},testnet:!0}),WS=L({id:555888,name:`DustBoy IoT`,nativeCurrency:{name:`Ether`,symbol:`DST`,decimals:18},rpcUrls:{default:{http:[`https://dustboy-rpc.jibl2.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://dustboy.jibl2.com`,apiUrl:`https://dustboy.jibl2.com/api`}},contracts:{multicall3:{address:`0xFFD34aa2C62B2D52E00A361e466C229788f4eD6a`,blockCreated:526569}},testnet:!1}),GS=L({id:1100,name:`Dymension`,nativeCurrency:{name:`DYM`,symbol:`DYM`,decimals:18},rpcUrls:{default:{http:[`https://dymension-evm-rpc.publicnode.com`],webSocket:[`wss://dymension-evm-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Dym FYI`,url:`https://dym.fyi`}},testnet:!1}),KS=L({id:5424,name:`edeXa`,nativeCurrency:{name:`edeXa`,symbol:`EDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edexa.network`]}},blockExplorers:{default:{name:`edeXa Explorer`,url:`https://explorer.edexa.network`,apiUrl:`https://explorer.edexa.network/api/v2`}}}),qS=L({id:1995,name:`edeXa Testnet`,nativeCurrency:{name:`edeXa`,symbol:`tEDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.edexa.network`]}},blockExplorers:{default:{name:`edeXa Testnet Explorer`,url:`https://explorer.testnet.edexa.network`,apiUrl:`https://explorer.testnet.edexa.network/api/v2`}},testnet:!0}),JS=L({id:2026,name:`Edgeless Network`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edgeless.network/http`],webSocket:[`wss://rpc.edgeless.network/ws`]}},blockExplorers:{default:{name:`Edgeless Explorer`,url:`https://explorer.edgeless.network`}}}),YS=L({id:202,name:`Edgeless Testnet`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://edgeless-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://edgeless-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Edgeless Testnet Explorer`,url:`https://testnet.explorer.edgeless.network`}}}),XS=L({id:2021,name:`Edgeware EdgeEVM Mainnet`,nativeCurrency:{decimals:18,name:`Edgeware`,symbol:`EDG`},rpcUrls:{default:{http:[`https://edgeware-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://edgscan.live`,apiUrl:`https://edgscan.live/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18117872}}}),ZS=L({id:2022,name:`Beresheet BereEVM Testnet`,nativeCurrency:{decimals:18,name:`Testnet EDG`,symbol:`tEDG`},rpcUrls:{default:{http:[`https://beresheet-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://testnet.edgscan.live`,apiUrl:`https://testnet.edgscan.live/api`}}}),QS=L({id:41923,name:`EDU Chain`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.edu-chain.raas.gelato.cloud`]}},blockExplorers:{default:{name:`EDU Chain Explorer`,url:`https://educhain.blockscout.com/`}},testnet:!1}),$S=L({id:656476,name:`EDU Chain Testnet`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.open-campus-codex.gelato.digital/`],webSocket:[`wss://ws.open-campus-codex.gelato.digital`]}},blockExplorers:{default:{name:`EDU Chain Testnet Explorer`,url:`https://opencampus-codex.blockscout.com`,apiUrl:`https://opencampus-codex.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15514133}},testnet:!0}),eC=L({id:20,name:`Elastos Smart Chain`,nativeCurrency:{name:`ELA`,symbol:`ELA`,decimals:18},rpcUrls:{default:{http:[`https://api2.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc.elastos.io`}},testnet:!1}),tC=L({id:21,name:`Elastos Smart Chain Testnet`,nativeCurrency:{name:`tELA`,symbol:`tELA`,decimals:18},rpcUrls:{default:{http:[`https://api-testnet.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc-testnet.elastos.io`}},testnet:!0}),nC=L({id:52014,name:`Electroneum Mainnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.electroneum.com`}},testnet:!1}),rC=L({id:5201420,name:`Electroneum Testnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.thesecurityteam.rocks`}},testnet:!0}),iC=L({...R,id:1338,name:`Elysium Testnet`,nativeCurrency:{decimals:18,name:`LAVA`,symbol:`LAVA`},rpcUrls:{default:{http:[`https://elysium-test-rpc.vulcanforged.com`]}},blockExplorers:{default:{name:`Elysium testnet explorer`,url:`https://elysium-explorer.vulcanforged.com`}},testnet:!0}),aC=L({id:246,name:`Energy Mainnet`,nativeCurrency:{name:`EWT`,symbol:`EWT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.energyweb.org`]}},blockExplorers:{default:{name:`EnergyWeb Explorer`,url:`https://explorer.energyweb.org`}},testnet:!1}),oC=L({id:173,name:`ENI Mainnet`,nativeCurrency:{decimals:18,name:`ENI`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc.eniac.network`]}},blockExplorers:{default:{name:`ENI Explorer`,url:`https://scan.eniac.network`}},testnet:!1}),sC=L({id:6912115,name:`ENI Testnet`,nativeCurrency:{decimals:18,name:`ENI Testnet Token`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc-testnet.eniac.network`]}},blockExplorers:{default:{name:`ENI Testnet Explorer`,url:`https://scan-testnet.eniac.network`}},testnet:!0}),cC=L({id:119,name:`ENULS Mainnet`,nativeCurrency:{decimals:18,name:`NULS`,symbol:`NULS`},rpcUrls:{default:{http:[`https://evmapi2.nuls.io`]}},blockExplorers:{default:{name:`ENULS Explorer`,url:`https://evmscan.nuls.io`}},testnet:!1}),lC=L({id:7332,name:`Horizen EON`,nativeCurrency:{decimals:18,name:`ZEN`,symbol:`ZEN`},rpcUrls:{default:{http:[`https://eon-rpc.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`EON Explorer`,url:`https://eon-explorer.horizenlabs.io`}},contracts:{}}),uC=L({id:17777,name:`EOS EVM`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Explorer`,url:`https://explorer.evm.eosnetwork.com`,apiUrl:`https://explorer.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7943933}}}),dC=L({id:15557,name:`EOS EVM Testnet`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.testnet.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Testnet Explorer`,url:`https://explorer.testnet.evm.eosnetwork.com`,apiUrl:`https://explorer.testnet.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9067940}},testnet:!0}),fC=L({id:140,name:`Eteria`,nativeCurrency:{name:`Eteria`,symbol:`ERA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.eteria.io/v1`]}},blockExplorers:{default:{name:`Eteria Explorer`,url:`https://explorer.eteria.io`,apiUrl:`https://explorer.eteria.io/api`}}}),pC=L({id:42793,name:`Etherlink`,blockTime:4830,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.mainnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink`,url:`https://explorer.etherlink.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:33899}}}),mC=L({id:127823,name:`Etherlink Shadownet Testnet`,nativeCurrency:{decimals:18,name:`tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.shadownet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink Shadownet Testnet Explorer`,url:`https://shadownet.explorer.etherlink.com`}},testnet:!0}),hC=L({id:128123,name:`Etherlink Testnet`,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.ghostnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink Testnet`,url:`https://testnet.explorer.etherlink.com`}},testnet:!0}),gC=L({id:183,name:`Ethernity`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.ethernitychain.io`]}},blockExplorers:{default:{name:`Ethernity Explorer`,url:`https://ernscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),_C=L({id:20256789,name:`ETP Mainnet`,nativeCurrency:{decimals:18,name:`ETP Chain Native Token`,symbol:`ETP`},rpcUrls:{default:{http:[`https://rpc.etpscan.xyz`]}},blockExplorers:{default:{name:`ETP Scan`,url:`https://etpscan.xyz`}}}),vC=L({id:9001,name:`Evmos`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.org:8545`]}},blockExplorers:{default:{name:`Evmos Block Explorer`,url:`https://escan.live`}}}),yC=L({id:9e3,name:`Evmos Testnet`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.dev:8545`]}},blockExplorers:{default:{name:`Evmos Testnet Block Explorer`,url:`https://evm.evmos.dev/`}}}),bC=L({id:22052002,name:`Excelon Mainnet`,network:`XLON`,nativeCurrency:{decimals:18,name:`Excelon`,symbol:`xlon`},rpcUrls:{default:{http:[`https://edgewallet1.xlon.org`]}},blockExplorers:{default:{name:`Excelon explorer`,url:`https://explorer.excelon.io`}}}),xC=L({id:2,name:`Expanse Network`,nativeCurrency:{decimals:18,name:`EXP`,symbol:`EXP`},rpcUrls:{default:{http:[`https://node.expanse.tech`]}},blockExplorers:{default:{name:`Expanse Explorer`,url:`https://explorer.expanse.tech`}},testnet:!1}),SC=L({id:7200,name:`exSat Network`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan.exsat.network`,apiUrl:`https://scan.exsat.network/api`}}}),CC=L({id:839999,name:`exSat Testnet`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm-tst3.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan-testnet.exsat.network`,apiUrl:`https://scan-testnet.exsat.network/api`}}}),wC=L({id:250,name:`Fantom`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://250.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://ftmscan.com`,apiUrl:`https://api.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:33001987}}}),TC=L({id:64240,name:`Fantom Sonic Open Testnet`,network:`fantom-sonic-testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpcapi.sonic.fantom.network`]}},blockExplorers:{default:{name:`Fantom Sonic Open Testnet Explorer`,url:`https://public-sonic.fantom.network`}},testnet:!0}),EC=L({id:4002,name:`Fantom Testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpc.testnet.fantom.network`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://testnet.ftmscan.com`,apiUrl:`https://testnet.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8328688}},testnet:!0}),DC=L({id:12306,name:`Fibo Chain`,nativeCurrency:{decimals:18,name:`fibo`,symbol:`FIBO`},rpcUrls:{default:{http:[`https://network.hzroc.art`]}},blockExplorers:{default:{name:`FiboScan`,url:`https://scan.fibochain.org`}}}),OC=L({id:314,name:`Filecoin Mainnet`,nativeCurrency:{decimals:18,name:`filecoin`,symbol:`FIL`},rpcUrls:{default:{http:[`https://api.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://filfox.info/en`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3328594}}}),kC=L({id:314159,name:`Filecoin Calibration`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.calibration.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filscan`,url:`https://calibration.filscan.io`}},testnet:!0}),AC=L({id:3141,name:`Filecoin Hyperspace`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.hyperspace.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://hyperspace.filfox.info/en`}},testnet:!0}),jC=L({id:253368190,name:`Flame`,network:`flame`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.flame.astria.org`],webSocket:[`wss://ws.flame.astria.org`]}},blockExplorers:{default:{name:`Flame Explorer`,url:`https://explorer.flame.astria.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6829148}}}),MC=L({id:14,name:`Flare Mainnet`,nativeCurrency:{decimals:18,name:`Flare`,symbol:`FLR`},rpcUrls:{default:{http:[`https://flare-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Flare Explorer`,url:`https://flare-explorer.flare.network`,apiUrl:`https://flare-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3002461}}}),NC=L({id:114,name:`Flare Testnet Coston2`,nativeCurrency:{decimals:18,name:`Coston2 Flare`,symbol:`C2FLR`},rpcUrls:{default:{http:[`https://coston2-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston2 Explorer`,url:`https://coston2-explorer.flare.network`,apiUrl:`https://coston2-explorer.flare.network/api`}},testnet:!0}),PC=L({id:747,name:`Flow EVM Mainnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://mainnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Mainnet Explorer`,url:`https://evm.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}},blockTime:800}),FC=L({id:646,name:`Flow EVM Previewnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://previewnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Previewnet Explorer`,url:`https://previewnet.flowdiver.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}}}),IC=L({id:545,name:`Flow EVM Testnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://testnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Flow Diver`,url:`https://evm-testnet.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:137518}},testnet:!0,blockTime:800}),LC=L({id:9999999,name:`Fluence`,nativeCurrency:{name:`FLT`,symbol:`FLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.fluence.dev`],webSocket:[`wss://ws.mainnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.mainnet.fluence.dev`,apiUrl:`https://blockscout.mainnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:207583}}}),RC=L({id:123420000220,name:`Fluence Stage`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stage.fluence.dev`],webSocket:[`wss://ws.stage.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.stage.fluence.dev`,apiUrl:`https://blockscout.stage.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83227}},testnet:!0}),zC=L({id:52164803,name:`Fluence Testnet`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.fluence.dev`],webSocket:[`wss://ws.testnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.testnet.fluence.dev`,apiUrl:`https://blockscout.testnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96424}},testnet:!0}),BC=L({id:20993,name:`Fluent Devnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.devnet.fluent.xyz`]}},blockExplorers:{default:{name:`Fluent Devnet Explorer`,url:`https://devnet.fluentscan.xyz`}},testnet:!0}),VC=L({id:20994,name:`Fluent Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.fluent.xyz`]}},blockExplorers:{default:{name:`Fluent Testnet Explorer`,url:`https://testnet.fluentscan.xyz`}},testnet:!0}),HC=1,UC=L({id:478,name:`Form Network`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.form.network/http`],webSocket:[`wss://rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Explorer`,url:`https://explorer.form.network`}},contracts:{...R.contracts,addressManager:{[HC]:{address:`0x15c249E46A2F924C2dB3A1560CF86729bAD1f07B`}},l1CrossDomainMessenger:{[HC]:{address:`0xF333158DCCad1dF6C3F0a3aEe8BC31fA94d9eD5c`}},l2OutputOracle:{[HC]:{address:`0x4ccAAF69F41c5810cA875183648B577CaCf1F67E`}},portal:{[HC]:{address:`0x4E259Ee5F4136408908160dD32295A5031Fa426F`}},l1StandardBridge:{[HC]:{address:`0xdc20aA63D3DE59574E065957190D8f24e0F7B8Ba`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:HC}),WC=L({id:984122,name:`Forma`,network:`forma`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.forma.art`],webSocket:[`wss://ws.forma.art`]}},blockExplorers:{default:{name:`Forma Explorer`,url:`https://explorer.forma.art`}},contracts:{multicall3:{address:`0xd53C6FFB123F7349A32980F87faeD8FfDc9ef079`,blockCreated:252705}}}),GC=11155111,KC=L({id:132902,name:`Form Testnet`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia-rpc.form.network/http`],webSocket:[`wss://sepolia-rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Testnet Explorer`,url:`https://sepolia-explorer.form.network`}},contracts:{...R.contracts,addressManager:{[GC]:{address:`0xd5C38fa934f7fd7477D4800F4f38a1c5BFdF1373`}},l1CrossDomainMessenger:{[GC]:{address:`0x37A68565c4BE9700b3E3Ec60cC4416cAC3052FAa`}},l2OutputOracle:{[GC]:{address:`0x9eA2239E65a59EC9C7F1ED4C116dD58Da71Fc1e2`}},portal:{[GC]:{address:`0x60377e3cE15dF4CCA24c4beF076b60314240b032`}},l1StandardBridge:{[GC]:{address:`0xD4531f633942b2725896F47cD2aFd260b44Ab1F7`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0,sourceId:GC}),qC=L({id:80931,name:`Forta Chain`,nativeCurrency:{symbol:`FORT`,name:`FORT`,decimals:18},rpcUrls:{default:{http:[`https://rpc-forta-chain-8gj1qndmfc.t.conduit.xyz`]}},blockExplorers:{default:{name:`Forta Explorer`,url:`https://explorer.forta.org`}}}),JC=L({id:31337,name:`Foundry`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}}),YC=1,XC=L({...R,id:252,name:`Fraxtal`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.frax.com`]}},blockExplorers:{default:{name:`fraxscan`,url:`https://fraxscan.com`,apiUrl:`https://api.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[YC]:{address:`0x66CC916Ed5C6C2FA97014f7D1cD141528Ae171e4`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[YC]:{address:`0x36cb65c1967A0Fb0EEE11569C51C2f2aA1Ca6f6D`,blockCreated:19135323}},l1StandardBridge:{[YC]:{address:`0x34C0bD5877A5Ee7099D0f5688D65F4bB9158BDE2`,blockCreated:19135323}}},sourceId:YC}),ZC=17e3,QC=L({...R,id:2522,name:`Fraxtal Testnet`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.frax.com`]}},blockExplorers:{default:{name:`fraxscan testnet`,url:`https://holesky.fraxscan.com`,apiUrl:`https://api-holesky.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[ZC]:{address:`0x715EA64DA13F4d0831ece4Ad3E8c1aa013167F32`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[ZC]:{address:`0xB9c64BfA498d5b9a8398Ed6f46eb76d90dE5505d`,blockCreated:318416}},l1StandardBridge:{[ZC]:{address:`0x0BaafC217162f64930909aD9f2B27125121d6332`,blockCreated:318416}}},sourceId:ZC}),$C=1,ew=L({...R,id:33979,name:`Funki`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.funkichain.com`]}},blockExplorers:{default:{name:`Funki Mainnet Explorer`,url:`https://funkiscan.io`}},contracts:{...R.contracts},sourceId:$C}),tw=11155111,nw=L({...R,id:3397901,network:`funkiSepolia`,name:`Funki Sepolia Sandbox`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://funki-testnet.alt.technology`]}},blockExplorers:{default:{name:`Funki Sepolia Sandbox Explorer`,url:`https://sepolia-sandbox.funkichain.com/`}},testnet:!0,contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204}},sourceId:tw}),rw=L({id:122,name:`Fuse`,nativeCurrency:{name:`Fuse`,symbol:`FUSE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fuse.io`]}},blockExplorers:{default:{name:`Fuse Explorer`,url:`https://explorer.fuse.io`,apiUrl:`https://explorer.fuse.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:16146628}}}),iw=L({id:123,name:`Fuse Sparknet`,nativeCurrency:{name:`Spark`,symbol:`SPARK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fusespark.io`]}},blockExplorers:{default:{name:`Sparkent Explorer`,url:`https://explorer.fusespark.io`,apiUrl:`https://explorer.fusespark.io/api`}}}),aw=L({id:32659,name:`Fusion Mainnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.fusionnetwork.io`],webSocket:[`wss://mainnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10441605}},testnet:!1}),ow=L({id:46688,name:`Fusion Testnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.fusionnetwork.io`],webSocket:[`wss://testnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://testnet.fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10428309}},testnet:!0}),sw=17e3,cw=L({...R,name:`Garnet Testnet`,testnet:!0,id:17069,sourceId:sw,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.garnetchain.com`],webSocket:[`wss://rpc.garnetchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.garnetchain.com`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[sw]:{address:`0x57ee40586fbE286AfC75E67cb69511A6D9aF5909`,blockCreated:1274684}},l2OutputOracle:{[sw]:{address:`0xCb8E7AC561b8EF04F2a15865e9fbc0766FEF569B`,blockCreated:1274684}},l1StandardBridge:{[sw]:{address:`0x09bcDd311FE398F80a78BE37E489f5D440DB95DE`,blockCreated:1274684}}}}),lw=L({id:86,name:`GateChain Mainnet`,nativeCurrency:{name:`GateChainToken`,symbol:`GT`,decimals:18},rpcUrls:{default:{http:[`https://evm.nodeinfo.cc`],webSocket:[`wss://evm-ws.gatenode.cc`]}},blockExplorers:{default:{name:`Gate Scan`,url:`https://www.gatescan.org`,apiUrl:`https://gatescan.org/api`}},testnet:!1}),uw=L({id:63157,name:`Geist Mainnet`,nativeCurrency:{decimals:18,name:`Aavegotchi GHST Token`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://geist-mainnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:660735}}}),dw=L({id:16507,name:`Genesys Mainnet`,nativeCurrency:{decimals:18,name:`GSYS`,symbol:`GSYS`},rpcUrls:{default:{http:[`https://rpc.genesys.network`]}},blockExplorers:{default:{name:`Genesys Explorer`,url:`https://gchainexplorer.genesys.network`}},testnet:!1}),fw=11155111,pw=L({...R,id:91342,network:`giwa-sepolia`,name:`GIWA Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia-rpc.giwa.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-explorer.giwa.io`,apiUrl:`https://sepolia-explorer.giwa.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[fw]:{address:`0x37347caB2afaa49B776372279143D71ad1f354F6`}},portal:{[fw]:{address:`0x956962C34687A954e611A83619ABaA37Ce6bC78A`}},l1StandardBridge:{[fw]:{address:`0x77b2ffc0F57598cAe1DB76cb398059cF5d10A7E7`}}},testnet:!0,sourceId:fw}),mw=L({...pw,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://sepolia-rpc-flashblocks.giwa.io`]}}}),hw=L({id:251,name:`Glide L1 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l1-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l1-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),gw=L({id:253,name:`Glide L2 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l2-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l2-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),_w=L({id:100,name:`Gnosis`,nativeCurrency:{decimals:18,name:`xDAI`,symbol:`XDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.gnosischain.com`],webSocket:[`wss://rpc.gnosischain.com/wss`]}},blockExplorers:{default:{name:`Gnosisscan`,url:`https://gnosisscan.io`,apiUrl:`https://api.gnosisscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:21022491}}}),vw=L({id:10200,name:`Gnosis Chiado`,nativeCurrency:{decimals:18,name:`Gnosis`,symbol:`xDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.chiadochain.net`],webSocket:[`wss://rpc.chiadochain.net/wss`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.chiadochain.net`,apiUrl:`https://blockscout.chiadochain.net/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4967313}},testnet:!0}),yw=L({id:2345,name:`GOAT`,nativeCurrency:{decimals:18,name:`Bitcoin`,symbol:`BTC`},rpcUrls:{default:{http:[`https://rpc.goat.network`]}},blockExplorers:{default:{name:`Goat Explorer`,url:`https://explorer.goat.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),bw=L({id:1663,name:`Horizen Gobi Testnet`,nativeCurrency:{decimals:18,name:`Test ZEN`,symbol:`tZEN`},rpcUrls:{default:{http:[`https://gobi-testnet.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`Gobi Explorer`,url:`https://gobi-explorer.horizen.io`}},contracts:{},testnet:!0}),xw=L({id:60,name:`GoChain`,nativeCurrency:{decimals:18,name:`GO`,symbol:`GO`},rpcUrls:{default:{http:[`https://rpc.gochain.io`]}},blockExplorers:{default:{name:`GoChain Explorer`,url:`https://explorer.gochain.io`}},testnet:!1}),Sw=L({id:71402,name:`Godwoken Mainnet`,nativeCurrency:{decimals:18,name:`pCKB`,symbol:`pCKB`},rpcUrls:{default:{http:[`https://v1.mainnet.godwoken.io/rpc`]}},blockExplorers:{default:{name:`GW Scan`,url:`https://v1.gwscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15034}},testnet:!1}),Cw=L({id:5,name:`Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://5.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.etherscan.io`,apiUrl:`https://api-goerli.etherscan.io/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},ensUniversalResolver:{address:`0xfc4AC75C46C914aF5892d6d3eFFcebD7917293F1`,blockCreated:10339206},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6507670}},testnet:!0}),ww=L({id:440017,name:`Graphite Network`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Spectre`,url:`https://main.atgraphite.com`,apiUrl:`https://api.main.atgraphite.com/api`}},testnet:!1}),Tw=L({id:54170,name:`Graphite Network Testnet`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-test-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-test-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Testnet Spectre`,url:`https://test.atgraphite.com`,apiUrl:`https://api.test.atgraphite.com/api`}},testnet:!0}),Ew=L({id:1625,name:`Gravity Alpha Mainnet`,nativeCurrency:{name:`G`,symbol:`G`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gravity.xyz`]}},blockExplorers:{default:{name:`Gravity Explorer`,url:`https://explorer.gravity.xyz`,apiUrl:`https://explorer.gravity.xyz/api`}},contracts:{multicall3:{address:`0xf8ac4BEB2F75d2cFFb588c63251347fdD629B92c`,blockCreated:16851}}}),Dw=L({id:43419,name:`Gunz Mainnet`,nativeCurrency:{name:`GUN`,symbol:`GUN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gunzchain.io/ext/bc/2M47TxWHGnhNtq6pM5zPXdATBtuqubxn5EPFgFmEawCQr9WFML/rpc`]}},blockExplorers:{default:{name:`Gunz Explorer`,url:`https://gunzscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:70502}}}),Ow=L({id:260,name:`Guru Network Mainnet`,nativeCurrency:{name:`GURU Token`,symbol:`GURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-main.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/260`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://scan.gurunetwork.ai`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:271691}},testnet:!1}),kw=L({id:261,name:`Guru Network Testnet`,nativeCurrency:{name:`tGURU Token`,symbol:`tGURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/261`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://sepolia.gurunetwork.ai`}},testnet:!0}),Aw=L({id:5112,name:`Ham`,nativeCurrency:{decimals:18,name:`Ham`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.ham.fun`],webSocket:[`wss://rpc.ham.fun`]}},blockExplorers:{default:{name:`Ham Chain Explorer`,url:`https://explorer.ham.fun`,apiUrl:`https://explorer.ham.fun/api/v2`}}}),jw=L({id:216,name:`Happychain Testnet`,nativeCurrency:{symbol:`HAPPY`,name:`HAPPY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.happy.tech/http`],webSocket:[`wss://rpc.testnet.happy.tech/ws`]}},blockExplorers:{default:{name:`Happy Chain Testnet Explorer`,url:`https://explorer.testnet.happy.tech`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0}),Mw=L({id:11235,name:`HAQQ Mainnet`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLM`},rpcUrls:{default:{http:[`https://rpc.eth.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.haqq.network`,apiUrl:`https://explorer.haqq.network/api`}}}),Nw=L({id:54211,name:`HAQQ Testedge 2`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLMT`},rpcUrls:{default:{http:[`https://rpc.eth.testedge2.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.testedge2.haqq.network`,apiUrl:`https://explorer.testedge2.haqq.network/api`}}}),Pw=L({id:31337,name:`Hardhat`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),Fw=L({id:16666e5,name:`Harmony One`,nativeCurrency:{name:`Harmony`,symbol:`ONE`,decimals:18},rpcUrls:{default:{http:[`https://1666600000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Harmony Explorer`,url:`https://explorer.harmony.one`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:24185753}}}),Iw=L({id:177,name:`HashKey Chain`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://mainnet.hsk.xyz`]}},blockExplorers:{default:{name:`HashKey Chain Explorer`,url:`https://hashkey.blockscout.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),Lw=L({id:133,name:`HashKey Chain Testnet`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://testnet.hsk.xyz`]}},blockExplorers:{default:{name:`HashKey Chain Testnet explorer`,url:`https://testnet-explorer.hsk.xyz`}},testnet:!0}),Rw=L({id:1523903251,name:`Haust Network Testnet`,nativeCurrency:{decimals:18,name:`HAUST`,symbol:`HAUST`},rpcUrls:{default:{http:[`https://rpc-testnet.haust.app`]}},blockExplorers:{default:{name:`Haust Network Testnet Explorer`,url:`https://explorer-testnet.haust.app`}},testnet:!0}),zw=L({id:295,name:`Hedera Mainnet`,network:`hedera-mainnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/mainnet`}},testnet:!1}),Bw=L({id:297,name:`Hedera Previewnet`,network:`hedera-previewnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://previewnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/previewnet`}},testnet:!0}),Vw=L({id:296,name:`Hedera Testnet`,network:`hedera-testnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://testnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/testnet`}},testnet:!0}),Hw=L({id:8668,name:`Hela Mainnet`,nativeCurrency:{name:`HLUSD`,symbol:`HLUSD`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.helachain.com`]}},blockExplorers:{default:{name:`Hela explorer`,url:`https://mainnet-blockexplorer.helachain.com`}},testnet:!1}),Uw=L({id:42e3,name:`Helios Testnet`,network:`helios-testnet`,nativeCurrency:{symbol:`HLS`,name:`Helios`,decimals:18},rpcUrls:{default:{http:[`https://testnet1.helioschainlabs.org`]}},blockExplorers:{default:{name:`Helios Testnet Explorer`,url:`https://explorer.helioschainlabs.org/`}},testnet:!0}),Ww=L({id:43111,name:`Hemi`,network:`Hemi`,blockTime:12e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.hemi.xyz`}},testnet:!1}),Gw=L({id:743111,name:`Hemi Sepolia`,network:`Hemi Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`Hemi Sepolia explorer`,url:`https://testnet.explorer.hemi.xyz`}},testnet:!0}),Kw=L({id:68414,name:`Henesys`,nativeCurrency:{name:`NEXPACE`,symbol:`NXPC`,decimals:18},rpcUrls:{default:{http:[`https://henesys-rpc.msu.io`]}},blockExplorers:{default:{name:`Avalanche Explorer`,url:`https://subnets.avax.network/henesys`}}}),qw=L({id:17e3,name:`Holesky`,nativeCurrency:{name:`Holesky Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://ethereum-holesky-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://holesky.etherscan.io`,apiUrl:`https://api-holesky.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:77},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:4295055}},testnet:!0}),Jw=L({id:560048,name:`Hoodi`,nativeCurrency:{name:`Hoodi Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hoodi.ethpandaops.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://hoodi.etherscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2589}},testnet:!0}),Yw=L({id:2651420,name:`Horizen Testnet`,network:`horizen-testnet`,nativeCurrency:{symbol:`Sepolia Ether`,name:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://horizen-testnet.rpc.caldera.xyz/http`]}},blockExplorers:{default:{name:`Horizen Testnet Caldera Explorer`,url:`https://horizen-testnet.explorer.caldera.xyz`}},testnet:!0}),Xw=L({id:269,name:`High Performance Blockchain`,nativeCurrency:{name:`HPB`,symbol:`HPB`,decimals:18},rpcUrls:{default:{http:[`https://hpbnode.com`]}},blockExplorers:{default:{name:`hpbScan`,url:`https://hscan.org`}},testnet:!1}),Zw=L({id:190415,name:`HPP Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.hpp.io`],webSocket:[`wss://mainnet.hpp.io`]}},blockExplorers:{default:{name:`HPP Mainnet Explorer`,url:`https://explorer.hpp.io`}},testnet:!1}),Qw=L({id:181228,name:`HPP Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.hpp.io`],webSocket:[`wss://testnet.hpp.io`]}},blockExplorers:{default:{name:`HPP Sepolia Explorer`,url:`https://sepolia-explorer.hpp.io`}},testnet:!0}),$w=L({id:12323,name:`Huddle01 dRTC Chain`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle01.calderachain.xyz/http`],webSocket:[`wss://huddle01.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle01.calderaexplorer.xyz`,apiUrl:`https://huddle01.calderaexplorer.xyz/api`}},sourceId:42161}),eT=L({id:2524852,name:`Huddle01 dRTC Chain Testnet`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://huddle-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle-testnet.explorer.caldera.xyz`,apiUrl:`https://huddle-testnet.explorer.caldera.xyz/api`}},sourceId:421614}),tT=L({id:6985385,name:`Humanity`,nativeCurrency:{name:`H`,symbol:`H`,decimals:18},rpcUrls:{default:{http:[`https://humanity-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Humanity Mainnet Explorer`,url:`https://humanity-mainnet.explorer.alchemy.com`,apiUrl:`https://humanity-mainnet.explorer.alchemy.com/api`}},testnet:!1}),nT=L({id:7080969,name:`Humanity Testnet`,nativeCurrency:{name:`tHP`,symbol:`tHP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.humanity.org`]}},blockExplorers:{default:{name:`Humanity Testnet Explorer`,url:`https://humanity-testnet.explorer.alchemy.com`,apiUrl:`https://humanity-testnet.explorer.alchemy.com/api`}},testnet:!0}),rT=L({id:5234,name:`Humanode`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.mainnet.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.mainnet.stages.humanode.io`]}},blockExplorers:{default:{name:`Subscan`,url:`https://humanode.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4413097}}}),iT=L({id:14853,name:`Humanode Testnet 5`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.testnet5.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.testnet5.stages.humanode.io`]}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),aT=L({id:2911,name:`HYCHAIN`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://explorer.hychain.com`}},testnet:!1}),oT=L({id:29112,name:`HYCHAIN Testnet`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://testnet-rpc.hychain.com/http`}},testnet:!0}),sT=L({id:999,name:`HyperEVM`,nativeCurrency:{name:`HYPE`,symbol:`HYPE`,decimals:18},blockExplorers:{default:{name:`HyperEVMScan`,url:`https://hyperevmscan.io`}},rpcUrls:{default:{http:[`https://rpc.hyperliquid.xyz/evm`]}},testnet:!1}),cT=L({id:998,name:`Hyperliquid EVM Testnet`,nativeCurrency:{name:`HYPE`,symbol:`HYPE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hyperliquid-testnet.xyz/evm`]}},testnet:!0}),lT=L({id:73115,name:`ICB Network`,nativeCurrency:{decimals:18,name:`ICB Native Token`,symbol:`ICBX`},rpcUrls:{default:{http:[`https://rpc1-mainnet.icbnetwork.info`]}},blockExplorers:{default:{name:`ICB Explorer`,url:`https://icbscan.io`,apiUrl:`https://icbscan.io/api`}},testnet:!1}),uT=L({id:74,name:`IDChain Mainnet`,nativeCurrency:{decimals:18,name:`EIDI`,symbol:`EIDI`},rpcUrls:{default:{http:[`https://idchain.one/rpc`],webSocket:[`wss://idchain.one/ws`]}},blockExplorers:{default:{name:`IDChain Explorer`,url:`https://explorer.idchain.one`}},testnet:!1}),dT=L({id:13371,name:`Immutable zkEVM`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.immutable.com`]}},blockExplorers:{default:{name:`Immutable Explorer`,url:`https://explorer.immutable.com`,apiUrl:`https://explorer.immutable.com/api`}},contracts:{multicall3:{address:`0x236bdA4589e44e6850f5aC6a74BfCa398a86c6c0`,blockCreated:4335972}}}),fT=L({id:13473,name:`Immutable zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.testnet.immutable.com`]}},blockExplorers:{default:{name:`Immutable Testnet Explorer`,url:`https://explorer.testnet.immutable.com/`}},contracts:{multicall3:{address:`0x2CC787Ed364600B0222361C4188308Fa8E68bA60`,blockCreated:5977391}},testnet:!0}),pT=L({id:2525,name:`inEVM Mainnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://mainnet.rpc.inevm.com/http`]}},blockExplorers:{default:{name:`inEVM Explorer`,url:`https://inevm.calderaexplorer.xyz`,apiUrl:`https://inevm.calderaexplorer.xyz/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:118606}}}),mT=L({id:7233,name:`InitVerse Mainnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-mainnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseScan`,url:`https://www.iniscan.com`,apiUrl:`https://explorer-api.inichain.com/api`}},contracts:{multicall3:{address:`0x83466BE48A067115FFF91f7b892Ed1726d032e47`,blockCreated:2318}}}),hT=L({id:7234,name:`InitVerse Genesis Testnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-testnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseGenesisScan`,url:`https://genesis-testnet.iniscan.com`,apiUrl:`https://explorer-testnet-api.inichain.com/api`}},contracts:{multicall3:{address:`0x0cF32CBDd6c437331EA4f85ed2d881A5379B5a6F`,blockCreated:16361}},testnet:!0}),gT=L({id:1776,name:`Injective`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://sentry.evm-rpc.injective.network`],webSocket:[`wss://sentry.evm-ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://blockscout.injective.network`,apiUrl:`https://blockscout.injective.network/api`}},testnet:!1}),_T=L({id:1439,name:`Injective Testnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://k8s.testnet.json-rpc.injective.network`],webSocket:[`wss://k8s.testnet.ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://testnet.blockscout.injective.network`,apiUrl:`https://testnet.blockscout.injective.network/api`}},testnet:!0}),vT=1,yT=L({...R,id:57073,name:`Ink`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel.inkonchain.com`,`https://rpc-qnd.inkonchain.com`],webSocket:[`wss://rpc-gel.inkonchain.com`,`wss://rpc-qnd.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.inkonchain.com`,apiUrl:`https://explorer.inkonchain.com/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},...R.contracts,disputeGameFactory:{[vT]:{address:`0x10d7b35078d3baabb96dd45a9143b94be65b12cd`}},portal:{[vT]:{address:`0x5d66c1782664115999c47c9fa5cd031f495d3e4f`}},l1StandardBridge:{[vT]:{address:`0x88ff1e5b602916615391f55854588efcbb7663f0`}}},testnet:!1,sourceId:vT}),bT=11155111,xT=L({...R,id:763373,name:`Ink Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel-sepolia.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer-sepolia.inkonchain.com/`,apiUrl:`https://explorer-sepolia.inkonchain.com/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[bT]:{address:`0x860e626c700af381133d9f4af31412a2d1db3d5d`}},portal:{[bT]:{address:`0x5c1d29c6c9c8b0800692acc95d700bcb4966a1d7`}},l1StandardBridge:{[bT]:{address:`0x33f60714bbd74d62b66d79213c348614de51901c`}}},testnet:!0,sourceId:bT}),ST=L({id:8822,name:`IOTA EVM`,network:`iotaevm`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.iota.org`,apiUrl:`https://explorer.evm.iota.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25022}}}),CT=L({id:1075,name:`IOTA EVM Testnet`,network:`iotaevm-testnet`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.testnet.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.testnet.iotaledger.net`,apiUrl:`https://explorer.evm.testnet.iotaledger.net/api`}},testnet:!0}),wT=L({id:4689,name:`IoTeX`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.mainnet.iotex.io`],webSocket:[`wss://babel-api.mainnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://iotexscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:22163670}}}),TT=L({id:4690,name:`IoTeX Testnet`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.testnet.iotex.io`],webSocket:[`wss://babel-api.testnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://testnet.iotexscan.io`}},contracts:{multicall3:{address:`0xb5cecD6894c6f473Ec726A176f1512399A2e355d`,blockCreated:24347592}},testnet:!0}),ET=L({id:8017,name:`iSunCoin Mainnet`,nativeCurrency:{decimals:18,name:`ISC`,symbol:`ISC`},rpcUrls:{default:{http:[`https://mainnet.isuncoin.com`]}},blockExplorers:{default:{name:`iSunCoin Explorer`,url:`https://baifa.io/app/chains/8017`}}}),DT=L({id:680,name:`Jasmy Chain`,network:`jasmyChain`,nativeCurrency:{name:`JasmyCoin`,symbol:`JASMY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.jasmychain.io`],webSocket:[`wss://rpc.jasmychain.io`]}},testnet:!1}),OT=L({id:681,name:`Jasmy Chain Testnet`,network:`jasmyChainTestnet`,nativeCurrency:{name:`JasmyCoin`,symbol:`JASMY`,decimals:18},rpcUrls:{default:{http:[`https://rpc_testnet.jasmychain.io`],webSocket:[`wss://rpc_testnet.jasmychain.io`]}},testnet:!0}),kT=L({id:8899,name:`JB Chain`,network:`jbc`,nativeCurrency:{name:`JBC`,symbol:`JBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-l1.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-l1.jibchain.net`,apiUrl:`https://exp-l1.jibchain.net/api`}},contracts:{multicall3:{address:`0xc0C8C486D1466C57Efe13C2bf000d4c56F47CBdC`,blockCreated:2299048}},testnet:!1}),AT=L({id:88991,name:`Jibchain Testnet`,nativeCurrency:{name:`tJBC`,symbol:`tJBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.testnet.jibchain.net`,apiUrl:`https://exp.testnet.jibchain.net/api`}},contracts:{multicall3:{address:`0xa1a858ad9041B4741e620355a3F96B3c78e70ecE`,blockCreated:32848}},testnet:!0}),jT=L({id:81,name:`Japan Open Chain Mainnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Token`,symbol:`JOC`},rpcUrls:{default:{http:[`https://rpc-1.japanopenchain.org:8545`,`https://rpc-2.japanopenchain.org:8545`,`https://rpc-3.japanopenchain.org`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.japanopenchain.org`}},testnet:!1}),MT=L({id:10081,name:`Japan Open Chain Testnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Testnet Token`,symbol:`JOCT`},rpcUrls:{default:{http:[`https://rpc-1.testnet.japanopenchain.org:8545`,`https://rpc-2.testnet.japanopenchain.org:8545`,`https://rpc-3.testnet.japanopenchain.org`]}},blockExplorers:{default:{name:`Testnet Block Explorer`,url:`https://explorer.testnet.japanopenchain.org`}},testnet:!0}),NT=L({id:5734951,name:`Jovay Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.jovay.io`]}},blockExplorers:{default:{name:`Jovay Explorer`,url:`https://explorer.jovay.io/l2`}},testnet:!1}),PT=L({id:2019775,name:`Jovay Sepolia Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.zan.top/public/jovay-testnet`]}},blockExplorers:{default:{name:`Jovay Testnet Explorer`,url:`https://sepolia-explorer.jovay.io/l2`}},testnet:!0}),FT=L({id:45003,name:`Juneo JUNE-Chain`,nativeCurrency:{decimals:18,name:`JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/2`,apiUrl:`https://juneoscan.io/chain/2/api`}}}),IT=L({id:45013,name:`Juneo BCH1-Chain`,nativeCurrency:{decimals:18,name:`Juneo BCH1-Chain`,symbol:`BCH1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/BCH1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/12`,apiUrl:`https://juneoscan.io/chain/12/api`}}}),LT=L({id:45004,name:`Juneo DAI1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DAI1-Chain`,symbol:`DAI1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DAI1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/5`,apiUrl:`https://juneoscan.io/chain/5/api`}}}),RT=L({id:45010,name:`Juneo DOGE1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DOGE1-Chain`,symbol:`DOGE1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DOGE1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/10`,apiUrl:`https://juneoscan.io/chain/10/api`}}}),zT=L({id:45011,name:`Juneo EUR1-Chain`,nativeCurrency:{decimals:18,name:`Juneo EUR1-Chain`,symbol:`EUR1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/EUR1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/6`,apiUrl:`https://juneoscan.io/chain/6/api`}}}),BT=L({id:45008,name:`Juneo GLD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo GLD1-Chain`,symbol:`GLD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/GLD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/8`,apiUrl:`https://juneoscan.io/chain/8/api`}}}),VT=L({id:45014,name:`Juneo LINK1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LINK1-Chain`,symbol:`LINK1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LINK1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/13`,apiUrl:`https://juneoscan.io/chain/13/api`}}}),HT=L({id:45009,name:`Juneo LTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LTC1-Chain`,symbol:`LTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/11`,apiUrl:`https://juneoscan.io/chain/11/api`}}}),UT=L({id:45007,name:`Juneo mBTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo mBTC1-Chain`,symbol:`mBTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/mBTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/9`,apiUrl:`https://juneoscan.io/chain/9/api`}}}),WT=L({id:45012,name:`Juneo SGD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo SGD1-Chain`,symbol:`SGD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/SGD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/7`,apiUrl:`https://juneoscan.io/chain/7/api`}}}),GT=L({id:101003,name:`Socotra JUNE-Chain`,nativeCurrency:{decimals:18,name:`Socotra JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.socotra-testnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://socotra.juneoscan.io/chain/2`,apiUrl:`https://socotra.juneoscan.io/chain/2/api`}},testnet:!0}),KT=L({id:45006,name:`Juneo USD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USD1-Chain`,symbol:`USD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/4`,apiUrl:`https://juneoscan.io/chain/4/api`}}}),qT=L({id:45005,name:`Juneo USDT1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USDT1-Chain`,symbol:`USDT1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USDT1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/3`,apiUrl:`https://juneoscan.io/chain/3/api`}}}),JT=L({id:8217,name:`Kaia`,nativeCurrency:{decimals:18,name:`Kaia`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kaiascan.io`,apiUrl:`https://api-cypress.klaytnscope.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),YT=L({id:1001,name:`Kairos Testnet`,network:`kairos`,nativeCurrency:{decimals:18,name:`Kairos KAIA`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en-kairos.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kairos.kaiascan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),XT=L({id:1802203764,name:`Kakarot Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),ZT=L({id:920637907288165,name:`Kakarot Starknet Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),QT=L({id:24,name:`KardiaChain Mainnet`,nativeCurrency:{name:`KAI`,symbol:`KAI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kardiachain.io`]}},blockExplorers:{default:{name:`KardiaChain Explorer`,url:`https://explorer.kardiachain.io`}},testnet:!1}),$T=L({id:686,name:`Karura`,network:`karura`,nativeCurrency:{name:`Karura`,symbol:`KAR`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-karura.aca-api.network`],webSocket:[`wss://eth-rpc-karura.aca-api.network`]}},blockExplorers:{default:{name:`Karura Blockscout`,url:`https://blockscout.karura.network`,apiUrl:`https://blockscout.karura.network/api`}},testnet:!1}),eE=L({id:747474,name:`Katana`,network:`katana`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katana.network`]}},blockExplorers:{default:{name:`katana explorer`,url:`https://katanascan.com`}},testnet:!1}),tE=L({id:2222,name:`Kava EVM`,network:`kava-mainnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Explorer`,url:`https://kavascan.com`,apiUrl:`https://kavascan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3661165}},testnet:!1}),nE=L({id:2221,name:`Kava EVM Testnet`,network:`kava-testnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.testnet.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Testnet Explorer`,url:`https://testnet.kavascan.com/`,apiUrl:`https://testnet.kavascan.com/api`}},contracts:{multicall3:{address:`0xDf1D724A7166261eEB015418fe8c7679BBEa7fd6`,blockCreated:7242179}},testnet:!0}),rE=L({id:321,name:`KCC Mainnet`,network:`KCC Mainnet`,nativeCurrency:{decimals:18,name:`KCS`,symbol:`KCS`},rpcUrls:{default:{http:[`https://kcc-rpc.com`]}},blockExplorers:{default:{name:`KCC Explorer`,url:`https://explorer.kcc.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11760430}},testnet:!1}),iE=L({id:1783,name:`KiiChain`,network:`kii-chain`,nativeCurrency:{name:`Kii`,symbol:`KII`,decimals:18},rpcUrls:{default:{http:[`https://json-rpc.kiivalidator.com`]}},blockExplorers:{default:{name:`KiiExplorer`,url:`https://explorer.kiichain.io`}}}),aE=L({id:1336,name:`Kii Testnet Oro`,network:`kii-testnet-oro`,nativeCurrency:{name:`Kii`,symbol:`KII`,decimals:18},rpcUrls:{default:{http:[`https://json-rpc.uno.sentry.testnet.v3.kiivalidator.com`]}},blockExplorers:{default:{name:`KiiExplorer`,url:`https://testnet.explorer.kiichain.io`}},testnet:!0}),oE=L({id:7887,name:`Kinto Mainnet`,network:`Kinto Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kinto.xyz/http`]}},blockExplorers:{default:{name:`Kinto Explorer`,url:`https://explorer.kinto.xyz`}},testnet:!1}),sE=L({id:8217,name:`Klaytn`,nativeCurrency:{decimals:18,name:`Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-cypress.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://scope.klaytn.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),cE=L({id:1001,name:`Klaytn Baobab Testnet`,network:`klaytn-baobab`,nativeCurrency:{decimals:18,name:`Baobab Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-baobab.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://baobab.klaytnscope.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),lE=L({id:701,name:`Koi Network`,nativeCurrency:{decimals:18,name:`Koi Network Native Token`,symbol:`KRING`},rpcUrls:{default:{http:[`https://koi-rpc.darwinia.network`],webSocket:[`wss://koi-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://koi-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:180001}},testnet:!0}),uE=L({id:255,name:`Kroma`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.kroma.network`]}},blockExplorers:{default:{name:`Kroma Explorer`,url:`https://blockscout.kroma.network`,apiUrl:`https://blockscout.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:16054868}},testnet:!1}),dE=L({id:2358,name:`Kroma Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.sepolia.kroma.network`]}},blockExplorers:{default:{name:`Kroma Sepolia Explorer`,url:`https://blockscout.sepolia.kroma.network`,apiUrl:`https://blockscout.sepolia.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8900914}},testnet:!0}),fE=L({id:1983,name:`Krown`,nativeCurrency:{decimals:18,name:`Krown`,symbol:`KROWN`},rpcUrls:{default:{http:[`https://mainnet.krown.network`]}},blockExplorers:{default:{name:`Krown Explorer`,url:`https://explorer.krown.network`}},testnet:!1}),pE=L({id:12324,name:`L3X Protocol`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.l3x.com`],webSocket:[`wss://rpc-mainnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Mainnet Explorer`,url:`https://explorer.l3x.com`,apiUrl:`https://explorer.l3x.com/api/v2`}},testnet:!1}),mE=L({id:12325,name:`L3X Protocol Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.l3x.com`],webSocket:[`wss://rpc-testnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Testnet Explorer`,url:`https://explorer-testnet.l3x.com`,apiUrl:`https://explorer-testnet.l3x.com/api/v2`}},testnet:!0}),hE=L({id:360890,name:`LAVITA Mainnet`,nativeCurrency:{name:`vTFUEL`,symbol:`vTFUEL`,decimals:18},rpcUrls:{default:{http:[`https://tsub360890-eth-rpc.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`LAVITA Explorer`,url:`https://tsub360890-explorer.thetatoken.org`}},testnet:!1}),gE=L({id:232,name:`Lens`,nativeCurrency:{name:`GHO`,symbol:`GHO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lens.xyz`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://explorer.lens.xyz`,apiUrl:`https://explorer.lens.xyz/api`}}}),_E=L({id:37111,name:`Lens Testnet`,nativeCurrency:{name:`GRASS`,symbol:`GRASS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.lens.dev`],webSocket:[`wss://rpc.testnet.lens.dev/ws`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://block-explorer.testnet.lens.dev`,apiUrl:`https://block-explorer-api.staging.lens.dev/api`}},testnet:!0}),vE=L({id:21363,name:`Lestnet`,nativeCurrency:{name:`Lestnet Ether`,symbol:`LETH`,decimals:18},rpcUrls:{default:{http:[`https://service.lestnet.org`]}},blockExplorers:{default:{name:`Lestnet Explorer`,url:`https://explore.lestnet.org`}},testnet:!0}),yE=L({id:1891,name:`LightLink Pegasus Testnet`,network:`lightlink-pegasus`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.pegasus.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Pegasus Explorer`,url:`https://pegasus.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:127188532}},testnet:!0}),bE=L({id:1890,name:`LightLink Phoenix Mainnet`,network:`lightlink-phoenix`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.phoenix.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Phoenix Explorer`,url:`https://phoenix.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:125499184}},testnet:!1});oa(),k(),Hp(),zu(),Ku(),td();async function xE(e,t){let{account:n=e.account}=t;if(!n)throw new Fm;let r=aa(n);try{let{accessList:n,blockNumber:i,blockTag:a,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p,...m}=t,h=(typeof i==`bigint`?O(i):void 0)||a;ed(t);let g=e.chain?.formatters?.transactionRequest?.format,_=(g||Hu)({...Ru(m,{format:g}),account:r,accessList:n,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p},`estimateGas`),{baseFeePerGas:v,gasLimit:y,priorityFeePerGas:b}=await e.request({method:`linea_estimateGas`,params:h?[_,h]:[_]});return{baseFeePerGas:BigInt(v),gasLimit:BigInt(y),priorityFeePerGas:BigInt(b)}}catch(n){throw Vp(n,{...t,account:r,chain:e.chain})}}var SE={fees:{estimateFeesPerGas:CE,async maxPriorityFeePerGas({block:e,client:t,request:n}){let r=await CE({block:e,client:t,multiply:e=>e,request:n,type:`eip1559`});return r?.maxPriorityFeePerGas?r.maxPriorityFeePerGas:null}}};async function CE({client:e,multiply:t,request:n,type:r}){try{let i=await xE(e,{...n,account:n?.account}),{priorityFeePerGas:a}=i,o=t(BigInt(i.baseFeePerGas))+a;return r===`legacy`?{gasPrice:o}:{maxFeePerGas:o,maxPriorityFeePerGas:a}}catch{return null}}var wE=L({...SE,id:59144,name:`Linea Mainnet`,blockTime:2e3,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.linea.build`],webSocket:[`wss://rpc.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://lineascan.build`,apiUrl:`https://api.lineascan.build/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:42},ensRegistry:{address:`0x50130b669B28C339991d8676FA73CF122a121267`,blockCreated:6682888},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:22222151}},ensTlds:[`.linea.eth`],testnet:!1}),TE=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://api-goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0}),EE=L({...SE,id:59141,name:`Linea Sepolia Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia.linea.build`],webSocket:[`wss://rpc.sepolia.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.lineascan.build`,apiUrl:`https://api-sepolia.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:227427},ensRegistry:{address:`0x5B2636F0f2137B4aE722C01dd5122D7d3e9541f7`,blockCreated:2395094},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:17168484}},ensTlds:[`.linea.eth`],testnet:!0}),DE=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0}),OE=1,kE=L({...R,id:1135,name:`Lisk`,network:`lisk`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.lisk.com`,apiUrl:`https://blockscout.lisk.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[OE]:{address:`0x0CF7D3706a27CCE2017aEB11E8a9c8b5388c282C`}},multicall3:{address:`0xA9d71E1dd7ca26F26e656E66d6AA81ed7f745bf0`},l2OutputOracle:{[OE]:{address:`0x113cB99283AF242Da0A0C54347667edF531Aa7d6`}},portal:{[OE]:{address:`0x26dB93F8b8b4f7016240af62F7730979d353f9A7`}},l1StandardBridge:{[OE]:{address:`0x2658723Bf70c7667De6B25F99fcce13A16D25d08`}}},sourceId:OE}),AE=11155111,jE=L({...R,id:4202,network:`lisk-sepolia`,name:`Lisk Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia-api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-blockscout.lisk.com`,apiUrl:`https://sepolia-blockscout.lisk.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[AE]:{address:`0xA0E35F56C318DE1bD5D9ca6A94Fe7e37C5663348`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[AE]:{address:`0xe3d90F21490686Ec7eF37BE788E02dfC12787264`}},l1StandardBridge:{[AE]:{address:`0x1Fb30e446eA791cd1f011675E5F3f5311b70faF5`}}},testnet:!0,sourceId:AE}),ME=L({id:9496,name:`Load Alphanet`,nativeCurrency:{name:`Testnet LOAD`,symbol:`tLOAD`,decimals:18},rpcUrls:{default:{http:[`https://alphanet.load.network`]}},blockExplorers:{default:{name:`Load Alphanet Explorer`,url:`https://explorer.load.network`}},testnet:!0}),NE=L({id:1337,name:`Localhost`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),PE=L({id:15551,name:`LoopNetwork Mainnet`,nativeCurrency:{name:`LOOP`,symbol:`LOOP`,decimals:18},rpcUrls:{default:{http:[`https://api.mainnetloop.com`]}},blockExplorers:{default:{name:`LoopNetwork Blockchain Explorer`,url:`https://explorer.mainnetloop.com/`}},testnet:!1}),FE=L({id:42,network:`lukso`,name:`LUKSO`,nativeCurrency:{name:`LUKSO`,symbol:`LYX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.lukso.network`],webSocket:[`wss://ws-rpc.mainnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Mainnet Explorer`,url:`https://explorer.execution.mainnet.lukso.network`,apiUrl:`https://api.explorer.execution.mainnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:468183}}}),IE=L({id:4201,name:`LUKSO Testnet`,nativeCurrency:{decimals:18,name:`LUKSO Testnet`,symbol:`LYXt`},rpcUrls:{default:{http:[`https://rpc.testnet.lukso.network`],webSocket:[`wss://ws-rpc.testnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Testnet Explorer`,url:`https://explorer.execution.testnet.lukso.network`,apiUrl:`https://api.explorer.execution.testnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:605348}},testnet:!0}),LE=L({id:994873017,name:`Lumia Mainnet`,network:`LumiaMainnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Explorer`,url:`https://explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3975939}},testnet:!1}),RE=L({id:1952959480,name:`Lumia Testnet`,network:`LumiaTestnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Testnet Explorer`,url:`https://testnet-explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2235063}},testnet:!0}),zE=L({id:96370,name:`Lumoz`,nativeCurrency:{decimals:18,name:`Lumoz Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://rpc.lumoz.org`]}},blockExplorers:{default:{name:`Lumoz Scan`,url:`https://scan.lumoz.info`}},testnet:!1}),BE=L({id:105363,name:`Lumoz Testnet`,nativeCurrency:{decimals:18,name:`Lumoz Testnet Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://testnet-rpc.lumoz.org`]}},testnet:!0}),VE=L({id:1122,name:`LuxePorts`,network:`luxeports`,nativeCurrency:{name:`LuxePorts`,symbol:`LXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.luxeports.com`,`https://erpc.luxeports.com`],webSocket:[`wss://rpc.luxeports.com/ws`,`wss://erpc.luxeports.com/ws`]}},blockExplorers:{default:{name:`LXPScan`,url:`https://lxpscan.com`}},testnet:!1}),HE=L({id:721,name:`Lycan`,nativeCurrency:{decimals:18,name:`Lycan`,symbol:`LYC`},rpcUrls:{default:{http:[`https://rpc.lycanchain.com`,`https://us-east.lycanchain.com`,`https://us-west.lycanchain.com`,`https://eu-north.lycanchain.com`,`https://eu-west.lycanchain.com`,`https://asia-southeast.lycanchain.com`],webSocket:[`wss://rpc.lycanchain.com`,`wss://us-east.lycanchain.com`,`wss://us-west.lycanchain.com`,`wss://eu-north.lycanchain.com`,`wss://eu-west.lycanchain.com`,`wss://asia-southeast.lycanchain.com`]}},blockExplorers:{default:{name:`Lycan Explorer`,url:`https://explorer.lycanchain.com`}}}),UE=L({id:957,name:`Lyra Chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lyra.finance`]}},blockExplorers:{default:{name:`Lyra Explorer`,url:`https://explorer.lyra.finance`,apiUrl:`https://explorer.lyra.finance/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1935198}}}),WE=L({id:1,name:`Ethereum`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:12e3,rpcUrls:{default:{http:[`https://eth.merkle.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://etherscan.io`,apiUrl:`https://api.etherscan.io/api`}},contracts:{ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:23085558},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),GE=L({id:595,name:`Mandala TC9`,network:`mandala`,nativeCurrency:{name:`Mandala`,symbol:`mACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-tc9.aca-staging.network`],webSocket:[`wss://eth-rpc-tc9.aca-staging.network`]}},blockExplorers:{default:{name:`Mandala Blockscout`,url:`https://blockscout.mandala.aca-staging.network`,apiUrl:`https://blockscout.mandala.aca-staging.network/api`}},testnet:!0}),KE=L({id:169,name:`Manta Pacific Mainnet`,network:`manta`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.manta.network/http`]}},blockExplorers:{default:{name:`Manta Explorer`,url:`https://pacific-explorer.manta.network`,apiUrl:`https://pacific-explorer.manta.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:332890}}}),qE=L({id:3441006,name:`Manta Pacific Sepolia Testnet`,network:`manta-sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.sepolia-testnet.manta.network/http`]}},blockExplorers:{default:{name:`Manta Sepolia Testnet Explorer`,url:`https://pacific-explorer.sepolia-testnet.manta.network`,apiUrl:`https://pacific-explorer.sepolia-testnet.manta.network/api`}},contracts:{multicall3:{address:`0xca54918f7B525C8df894668846506767412b53E3`,blockCreated:479584}},testnet:!0}),JE=L({id:3441005,name:`Manta Pacific Testnet`,network:`manta-testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://manta-testnet.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Manta Testnet Explorer`,url:`https://pacific-explorer.testnet.manta.network`,apiUrl:`https://pacific-explorer.testnet.manta.network/api`}},contracts:{multicall3:{address:`0x211B1643b95Fe76f11eD8880EE810ABD9A4cf56C`,blockCreated:419915}},testnet:!0}),YE=L({id:5e3,name:`Mantle`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Explorer`,url:`https://mantlescan.xyz/`,apiUrl:`https://api.mantlescan.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:304717}}}),XE=L({id:5003,name:`Mantle Sepolia Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.sepolia.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.sepolia.mantle.xyz/`,apiUrl:`https://explorer.sepolia.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4584012}},testnet:!0}),ZE=L({id:5001,name:`Mantle Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.testnet.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.testnet.mantle.xyz`,apiUrl:`https://explorer.testnet.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:561333}},testnet:!0}),QE=L({id:5887,name:`MANTRA DuKong EVM Testnet`,nativeCurrency:{decimals:18,name:`MANTRA`,symbol:`MANTRA`},rpcUrls:{default:{http:[`https://evm.dukong.mantrachain.io`]}},blockExplorers:{default:{name:`MANTRAScan`,url:`https://mantrascan.io/dukong`}},testnet:!0}),$E=L({id:5888,name:`MANTRA EVM`,nativeCurrency:{decimals:18,name:`MANTRA`,symbol:`MANTRA`},rpcUrls:{default:{http:[`https://evm.mantrachain.io`],webSocket:[`https://evm.mantrachain.io/ws`]}},blockExplorers:{default:{name:`MANTRA Blockscout Explorer`,url:`https://blockscout.mantrascan.io`}}}),eD=L({id:22776,name:`MAP Protocol`,nativeCurrency:{decimals:18,name:`MAPO`,symbol:`MAPO`},rpcUrls:{default:{http:[`https://rpc.maplabs.io`]}},blockExplorers:{default:{name:`MAPO Scan`,url:`https://maposcan.io`}},testnet:!1}),tD=L({id:698,name:`Matchain`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://matchscan.io`}}}),nD=L({id:699,name:`Matchain Testnet`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://testnet.matchscan.io`}},testnet:!0}),rD=L({id:29548,name:`MCH Verse`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.oasys.mycryptoheroes.net`]}},blockExplorers:{default:{name:`MCH Verse Explorer`,url:`https://explorer.oasys.mycryptoheroes.net`,apiUrl:`https://explorer.oasys.mycryptoheroes.net/api`}},testnet:!1}),iD=L({id:4326,blockTime:1e3,name:`MegaETH`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.megaeth.com/rpc`],webSocket:[`wss://mainnet.megaeth.com/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`},blockscout:{name:`Etherscan`,url:`https://mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),aD=L({id:6343,blockTime:1e3,name:`MegaETH Testnet`,nativeCurrency:{name:`MegaETH Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://carrot.megaeth.com/rpc`],webSocket:[`wss://carrot.megaeth.com/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://testnet-mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`},blockscout:{name:`Blockscout`,url:`https://megaeth-testnet-v2.blockscout.com`,apiUrl:`https://megaeth-testnet-v2.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),oD=L({id:7078815900,name:`Mekong Pectra Devnet`,nativeCurrency:{name:`eth`,symbol:`eth`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mekong.ethpandaops.io`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.mekong.ethpandaops.io`}},testnet:!0}),sD=L({id:333000333,name:`Meld`,nativeCurrency:{decimals:18,name:`Meld`,symbol:`MELD`},rpcUrls:{default:{http:[`https://rpc-1.meld.com`]}},blockExplorers:{default:{name:`MELDscan`,url:`https://meldscan.io`}},contracts:{multicall3:{address:`0x769ee5a8e82c15c1b6e358f62ac8eb6e3abe8dc5`,blockCreated:360069}}}),cD=L({id:4352,name:`MemeCore`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.memecore.net`],webSocket:[`wss://ws.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Explorer`,url:`https://memecorescan.io`,apiUrl:`https://api.memecorescan.io/api`},okx:{name:`MemeCore Explorer`,url:`https://web3.okx.com/explorer/memecore`},memecore:{name:`MemeCore Explorer`,url:`https://blockscout.memecore.com`,apiUrl:`https://blockscout.memecore.com/api`}}}),lD=L({id:43521,name:`Formicarium`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.formicarium.memecore.net`],webSocket:[`wss://ws.formicarium.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.memecorescan.io`},okx:{name:`MemeCore Testnet Explorer`,url:`https://web3.okx.com/explorer/formicarium-testnet`},memecore:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.blockscout.memecore.com`,apiUrl:`https://formicarium.blockscout.memecore.com/api`}},testnet:!0}),uD=L({id:4200,name:`Merlin`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://scan.merlinchain.io`,apiUrl:`https://scan.merlinchain.io/api`}}}),dD=L({id:4203,name:`Merlin Erigon Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-erigon-rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-erigon-scan.merlinchain.io`,apiUrl:`https://testnet-erigon-scan.merlinchain.io/api`}},testnet:!0}),fD=L({id:571,name:`MetaChain Mainnet`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.metatime.com`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://explorer.metatime.com`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}}}),pD=L({id:1453,name:`MetaChain Istanbul`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://istanbul-rpc.metachain.dev`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://istanbul-explorer.metachain.dev`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}},testnet:!0}),mD=L({id:11,name:`Metadium Network`,nativeCurrency:{decimals:18,name:`META`,symbol:`META`},rpcUrls:{default:{http:[`https://api.metadium.com/prod`]}},blockExplorers:{default:{name:`Metadium Explorer`,url:`https://explorer.metadium.com`}},testnet:!1}),hD=1,gD=L({...R,id:1750,name:`Metal L2`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.metall2.com`],webSocket:[`wss://rpc.metall2.com`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.metall2.com`,apiUrl:`https://explorer.metall2.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[hD]:{address:`0x3B1F7aDa0Fcc26B13515af752Dd07fB1CAc11426`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},portal:{[hD]:{address:`0x3F37aBdE2C6b5B2ed6F8045787Df1ED1E3753956`}},l1StandardBridge:{[hD]:{address:`0x6d0f65D59b55B0FEC5d2d15365154DcADC140BF3`}}},sourceId:hD}),_D=L({id:82,name:`Meter`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpc.meter.io`]}},blockExplorers:{default:{name:`MeterScan`,url:`https://scan.meter.io`}}}),vD=L({id:83,name:`Meter Testnet`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpctest.meter.io`]}},blockExplorers:{default:{name:`MeterTestnetScan`,url:`https://scan-warringstakes.meter.io`}}}),yD=L({id:1088,name:`Metis`,nativeCurrency:{decimals:18,name:`Metis`,symbol:`METIS`},rpcUrls:{default:{http:[`https://metis.rpc.hypersync.xyz`,`https://metis-pokt.nodies.app`,`https://api.blockeden.xyz/metis/67nCBdZQSH9z3YqDDjdm`,`https://metis-andromeda.rpc.thirdweb.com`,`https://metis-andromeda.gateway.tenderly.co`,`https://metis.api.onfinality.io/public`,`https://andromeda.metis.io/?owner=1088`,`https://metis-mainnet.public.blastapi.io`],webSocket:[`wss://metis-rpc.publicnode.com`,`wss://metis.drpc.org`]}},blockExplorers:{default:{name:`Metis Explorer`,url:`https://explorer.metis.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2338552}}}),bD=L({id:599,name:`Metis Goerli`,nativeCurrency:{decimals:18,name:`Metis Goerli`,symbol:`METIS`},rpcUrls:{default:{http:[`https://goerli.gateway.metisdevops.link`]}},blockExplorers:{default:{name:`Metis Goerli Explorer`,url:`https://goerli.explorer.metisdevops.link`,apiUrl:`https://goerli.explorer.metisdevops.link/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1006207}}}),xD=L({id:59902,name:`Metis Sepolia`,nativeCurrency:{decimals:18,name:`Test Metis`,symbol:`tMETIS`},rpcUrls:{default:{http:[`https://sepolia.metisdevops.link`,`https://metis-sepolia-rpc.publicnode.com`,`https://metis-sepolia.gateway.tenderly.co`],webSocket:[`wss://metis-sepolia-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Metis Sepolia Explorer`,url:`https://sepolia-explorer.metisdevops.link`,apiUrl:`https://sepolia-explorer.metisdevops.link/api-docs`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:224185}}}),SD=L({id:7518,name:`MEVerse Chain Mainnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversemainnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://www.meversescan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:86881340}}}),CD=L({id:4759,name:`MEVerse Chain Testnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversetestnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.meversescan.io/`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:64371115}},testnet:!0}),wD=L({id:185,name:`Mint Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain explorer`,url:`https://explorer.mintchain.io`}},testnet:!1}),TD=L({id:1686,name:`Mint Sepolia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain Testnet explorer`,url:`https://testnet-explorer.mintchain.io`}},testnet:!0}),ED=L({id:124832,name:`Mitosis Testnet`,nativeCurrency:{name:`MITO`,symbol:`MITO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.mitosis.org`]}},blockExplorers:{default:{name:`Mitosis testnet explorer`,url:`https://testnet.mitosiscan.xyz`}},testnet:!0}),DD=1,OD=L({...R,id:34443,name:`Mode Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.mode.network`]}},blockExplorers:{default:{name:`Modescan`,url:`https://modescan.io`}},contracts:{...R.contracts,disputeGameFactory:{[DD]:{address:`0x6f13EFadABD9269D6cEAd22b448d434A1f1B433E`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2465882},l2OutputOracle:{[DD]:{address:`0x4317ba146D4933D889518a3e5E11Fe7a53199b04`}},portal:{[DD]:{address:`0x8B34b14c7c7123459Cf3076b8Cb929BE097d0C07`}},l1StandardBridge:{[DD]:{address:`0x735aDBbE72226BD52e818E7181953f42E3b0FF21`}}},sourceId:DD}),kD=11155111,AD=L({...R,id:919,name:`Mode Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.mode.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.mode.network`,apiUrl:`https://sepolia.explorer.mode.network/api`}},contracts:{...R.contracts,l2OutputOracle:{[kD]:{address:`0x2634BD65ba27AB63811c74A63118ACb312701Bfa`,blockCreated:3778393}},portal:{[kD]:{address:`0x320e1580effF37E008F1C92700d1eBa47c1B23fD`,blockCreated:3778395}},l1StandardBridge:{[kD]:{address:`0xbC5C679879B2965296756CD959C3C739769995E2`,blockCreated:3778392}},multicall3:{address:`0xBAba8373113Fb7a68f195deF18732e01aF8eDfCF`,blockCreated:3019007}},testnet:!0,sourceId:kD}),jD=L({id:143,name:`Monad`,blockTime:400,nativeCurrency:{name:`Monad`,symbol:`MON`,decimals:18},rpcUrls:{default:{http:[`https://rpc.monad.xyz`,`https://rpc1.monad.xyz`],webSocket:[`wss://rpc.monad.xyz`,`wss://rpc1.monad.xyz`]}},blockExplorers:{default:{name:`MonadVision`,url:`https://monadvision.com`},monadscan:{name:`Monadscan`,url:`https://monadscan.com`,apiUrl:`https://api.monadscan.com/api`}},testnet:!1,contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:9248132}}}),MD=L({id:10143,name:`Monad Testnet`,blockTime:400,nativeCurrency:{name:`Testnet MON Token`,symbol:`MON`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.monad.xyz`]}},blockExplorers:{default:{name:`Monad Testnet explorer`,url:`https://testnet.monadexplorer.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:251449}},testnet:!0}),ND=L({id:1287,name:`Moonbase Alpha`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`https://rpc.api.moonbase.moonbeam.network`],webSocket:[`wss://wss.api.moonbase.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonbase.moonscan.io`,apiUrl:`https://moonbase.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1850686}},testnet:!0}),PD=L({id:1284,name:`Moonbeam`,nativeCurrency:{decimals:18,name:`GLMR`,symbol:`GLMR`},rpcUrls:{default:{http:[`https://rpc.api.moonbeam.network`],webSocket:[`wss://wss.api.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonscan.io`,apiUrl:`https://api-moonbeam.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:609002}},testnet:!1}),FD=L({id:1281,name:`Moonbeam Development Node`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`http://127.0.0.1:9944`],webSocket:[`wss://127.0.0.1:9944`]}}}),ID=L({id:1285,name:`Moonriver`,nativeCurrency:{decimals:18,name:`MOVR`,symbol:`MOVR`},rpcUrls:{default:{http:[`https://rpc.api.moonriver.moonbeam.network`],webSocket:[`wss://wss.api.moonriver.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonriver.moonscan.io`,apiUrl:`https://api-moonriver.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1597904}},testnet:!1}),LD=L({id:2818,name:`Morph`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.morphl2.io`],webSocket:[`wss://rpc.morphl2.io:8443`]}},blockExplorers:{default:{name:`Morph Explorer`,url:`https://explorer.morphl2.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3654913}},testnet:!1}),RD=L({id:2810,name:`Morph Holesky`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-quicknode-holesky.morphl2.io`],webSocket:[`wss://rpc-quicknode-holesky.morphl2.io`]}},blockExplorers:{default:{name:`Morph Holesky Explorer`,url:`https://explorer-holesky.morphl2.io`,apiUrl:`https://explorer-api-holesky.morphl2.io/api?`}},testnet:!0}),zD=L({id:2710,name:`Morph Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.morphl2.io`]}},blockExplorers:{default:{name:`Morph Testnet Explorer`,url:`https://explorer-testnet.morphl2.io`,apiUrl:`https://explorer-api-testnet.morphl2.io/api`}},testnet:!0}),BD=L({id:5551,name:`Nahmii 2 Mainnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://l2.nahmii.io`]}},blockExplorers:{default:{name:`Nahmii 2 Explorer`,url:`https://explorer.n2.nahmii.io`}},testnet:!1}),VD=L({id:22222,name:`Nautilus Mainnet`,nativeCurrency:{name:`ZBC`,symbol:`ZBC`,decimals:9},rpcUrls:{default:{http:[`https://api.nautilus.nautchain.xyz`]}},blockExplorers:{default:{name:`NautScan`,url:`https://nautscan.com`}}}),HD=L({id:397,name:`NEAR Protocol`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.mainnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer.near.org`}},testnet:!1}),UD=L({id:398,name:`NEAR Protocol Testnet`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.testnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer-testnet.near.org`}},testnet:!0}),WD=L({id:245022926,name:`Neon EVM DevNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://devnet.neonevm.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://devnet.neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:205206112}},testnet:!0}),GD=L({id:245022934,network:`neonMainnet`,name:`Neon EVM MainNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://neon-proxy-mainnet.solana.p2p.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:206545524}},testnet:!1}),KD=L({id:47763,name:`Neo X Mainnet`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-1.rpc.banelabs.org`,`https://mainnet-2.rpc.banelabs.org`]}},blockExplorers:{default:{name:`Neo X - Explorer`,url:`https://xexplorer.neo.org`}},testnet:!1}),qD=L({id:12227332,name:`Neo X Testnet T4`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.banelabs.org/`]}},blockExplorers:{default:{name:`neox-scan`,url:`https://xt4scan.ngd.network`}},testnet:!0}),JD=L({id:1012,name:`Newton`,nativeCurrency:{name:`Newton`,symbol:`NEW`,decimals:18},rpcUrls:{default:{http:[`https://global.rpc.mainnet.newtonproject.org`]}},blockExplorers:{default:{name:`NewFi explorer`,url:`https://explorer.newtonproject.org/`}},testnet:!1}),YD=L({id:4242,name:`Nexi`,nativeCurrency:{name:`Nexi`,symbol:`NEXI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.chain.nexi.technology`]}},blockExplorers:{default:{name:`NexiScan`,url:`https://www.nexiscan.com`,apiUrl:`https://www.nexiscan.com/api`}},contracts:{multicall3:{address:`0x0277A46Cc69A57eE3A6C8c158bA874832F718B8E`,blockCreated:25770160}}}),XD=L({id:240,name:`Nexilix Smart Chain`,nativeCurrency:{decimals:18,name:`Nexilix`,symbol:`NEXILIX`},rpcUrls:{default:{http:[`https://rpcurl.pos.nexilix.com`]}},blockExplorers:{default:{name:`NexilixScan`,url:`https://scan.nexilix.com`}},contracts:{multicall3:{address:`0x58381c8e2BF9d0C2C4259cA14BdA9Afe02831244`,blockCreated:74448}}}),ZD=L({id:6900,name:`Nibiru`,nativeCurrency:{decimals:18,name:`NIBI`,symbol:`NIBI`},rpcUrls:{default:{http:[`https://evm-rpc.nibiru.fi`]}},blockExplorers:{default:{name:`NibiScan`,url:`https://nibiscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:19587573}}}),QD=L({id:200024,name:`Nitrograph Testnet`,testnet:!0,rpcUrls:{default:{http:[`https://rpc-testnet.nitrograph.foundation`]}},nativeCurrency:{name:`Nitro`,symbol:`NOS`,decimals:18},blockExplorers:{default:{url:`https://explorer-testnet.nitrograph.foundation`,name:`Nitrograph Explorer`}}}),$D=L({id:166,name:`Nomina`,nativeCurrency:{name:`Nomina`,symbol:`NOM`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.nomina.io`],webSocket:[`wss://mainnet.nomina.io`]}},blockExplorers:{default:{name:`Nomina Explorer`,url:`https://nomscan.io`}},testnet:!1}),eO=L({id:4090,network:`oasis-testnet`,name:`Oasis Testnet`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.oasis.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://oasis.ftnscan.com`,apiUrl:`https://oasis.ftnscan.com/api`}},testnet:!0}),tO=L({id:248,name:`Oasys`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.oasys.games`]}},blockExplorers:{default:{name:`OasysScan`,url:`https://scan.oasys.games`,apiUrl:`https://scan.oasys.games/api`}}}),nO=L({id:911867,name:`Odyssey Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://odyssey.ithaca.xyz`]}},blockExplorers:{default:{name:`Odyssey Explorer`,url:`https://odyssey-explorer.ithaca.xyz`,apiUrl:`https://odyssey-explorer.ithaca.xyz/api`}},testnet:!0}),rO=L({id:66,name:`OKC`,nativeCurrency:{decimals:18,name:`OKT`,symbol:`OKT`},rpcUrls:{default:{http:[`https://exchainrpc.okex.org`]}},blockExplorers:{default:{name:`oklink`,url:`https://www.oklink.com/okc`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10364792}}}),iO=L({id:311,name:`Omax Mainnet`,nativeCurrency:{decimals:18,name:`OMAX`,symbol:`OMAX`},rpcUrls:{default:{http:[`https://mainapi.omaxray.com`]}},blockExplorers:{default:{name:`Omax Explorer`,url:`https://omaxscan.com`}},testnet:!1}),aO=L({id:166,name:`Omni`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.omni.network`],webSocket:[`wss://mainnet.omni.network`]}},blockExplorers:{default:{name:`OmniScan`,url:`https://omniscan.network`}},testnet:!1}),oO=L({id:164,name:`Omni Omega`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://omega.omni.network`],webSocket:[`wss://omega.omni.network`]}},blockExplorers:{default:{name:`Omega OmniScan`,url:`https://omega.omniscan.network/`}},testnet:!0}),sO=L({id:309075,name:`One World Chain Mainnet`,nativeCurrency:{decimals:18,name:`OWCT`,symbol:`OWCT`},rpcUrls:{default:{http:[`https://mainnet-rpc.oneworldchain.org`]}},blockExplorers:{default:{name:`One World Explorer`,url:`https://mainnet.oneworldchain.org`}},testnet:!1}),cO=L({id:9700,name:`OORT MainnetDev`,nativeCurrency:{decimals:18,name:`OORT`,symbol:`OORT`},rpcUrls:{default:{http:[`https://dev-rpc.oortech.com`]}},blockExplorers:{default:{name:`OORT MainnetDev Explorer`,url:`https://dev-scan.oortech.com`}}}),lO=56,uO=L({id:204,name:`opBNB`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://opbnb-mainnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opBNB (BSCScan)`,url:`https://opbnb.bscscan.com`,apiUrl:`https://api-opbnb.bscscan.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:512881},l2OutputOracle:{[lO]:{address:`0x153CAB79f4767E2ff862C94aa49573294B13D169`}},portal:{[lO]:{address:`0x1876EA7702C0ad0C6A2ae6036DE7733edfBca519`}},l1StandardBridge:{[lO]:{address:`0xF05F0e4362859c3331Cb9395CBC201E3Fa6757Ea`}}},sourceId:lO}),dO=97,fO=L({id:5611,name:`opBNB Testnet`,nativeCurrency:{decimals:18,name:`tBNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://opbnb-testnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opbnbscan`,url:`https://testnet.opbnbscan.com`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3705108},l2OutputOracle:{[dO]:{address:`0xFf2394Bb843012562f4349C6632a0EcB92fC8810`}},portal:{[dO]:{address:`0x4386C8ABf2009aC0c263462Da568DD9d46e52a31`}},l1StandardBridge:{[dO]:{address:`0x677311Fd2cCc511Bbc0f581E8d9a07B033D5E840`}}},testnet:!0,sourceId:dO}),pO=L({id:1612,name:`OpenLedger`,nativeCurrency:{name:`Open`,symbol:`OPEN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.openledger.xyz`]}},blockExplorers:{default:{name:`OpenLedger Explorer`,url:`https://scan.openledger.xyz`}},testnet:!1}),mO=1,hO=L({...R,id:10,name:`OP Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.optimism.io`]}},blockExplorers:{default:{name:`Optimism Explorer`,url:`https://optimistic.etherscan.io`,apiUrl:`https://api-optimistic.etherscan.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[mO]:{address:`0xe5965Ab5962eDc7477C8520243A95517CD252fA9`}},l2OutputOracle:{[mO]:{address:`0xdfe97868233d1aa22e815a266982f2cf17685a27`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4286263},portal:{[mO]:{address:`0xbEb5Fc579115071764c7423A4f12eDde41f106Ed`}},l1StandardBridge:{[mO]:{address:`0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1`}}},sourceId:mO}),gO=5,_O=L({...R,id:420,name:`Optimism Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.optimism.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli-optimism.etherscan.io`,apiUrl:`https://goerli-optimism.etherscan.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[gO]:{address:`0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:49461},portal:{[gO]:{address:`0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383`}},l1StandardBridge:{[gO]:{address:`0x636Af16bf2f682dD3109e60102b8E1A089FedAa8`}}},testnet:!0,sourceId:gO}),vO=11155111,yO=L({...R,id:11155420,name:`OP Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.optimism.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://optimism-sepolia.blockscout.com`,apiUrl:`https://optimism-sepolia.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[vO]:{address:`0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1`}},l2OutputOracle:{[vO]:{address:`0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204},portal:{[vO]:{address:`0x16Fc5058F25648194471939df75CF27A2fdC48BC`}},l1StandardBridge:{[vO]:{address:`0xFBb0621E0B23b5478B630BD55a5f21f67730B0F1`}}},testnet:!0,sourceId:vO}),bO=L({id:62050,name:`Optopia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan.optopia.ai`}},testnet:!1}),xO=L({id:62049,name:`Optopia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan-testnet.optopia.ai`}},testnet:!0}),SO=L({id:291,name:`Orderly`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.orderly.network`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorer.orderly.network`}},testnet:!1}),CO=L({id:4460,name:`Orderly Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorerl2new-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`}},testnet:!0}),wO=L({id:41144114,name:`Otim Devnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`http://devnet.otim.xyz`]}},contracts:{batchInvoker:{address:`0x5FbDB2315678afecb367f032d93F642f64180aa3`}}}),TO=L({id:11297108109,name:`Palm`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}}}),EO=L({id:11297108099,name:`Palm Testnet`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}},testnet:!0}),DO=L({id:420420422,name:`Paseo PassetHub`,nativeCurrency:{name:`PAS`,symbol:`PAS`,decimals:18},rpcUrls:{default:{http:[`https://testnet-passet-hub-eth-rpc.polkadot.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout-passet-hub.parity-testnet.parity.io`}},testnet:!0}),OO=L({id:3338,name:`Peaq`,nativeCurrency:{decimals:18,name:`peaq`,symbol:`PEAQ`},rpcUrls:{default:{http:[`https://quicknode1.peaq.xyz`,`https://quicknode2.peaq.xyz`,`https://quicknode3.peaq.xyz`],webSocket:[`wss://quicknode1.peaq.xyz`,`wss://quicknode2.peaq.xyz`,`wss://quicknode3.peaq.xyz`]}},blockExplorers:{default:{name:`Subscan`,url:`https://peaq.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3566354}}}),kO=1,AO=L({id:424,network:`pgn`,name:`PGN`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Explorer`,url:`https://explorer.publicgoods.network`,apiUrl:`https://explorer.publicgoods.network/api`}},contracts:{l2OutputOracle:{[kO]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3380209},portal:{[kO]:{address:`0xb26Fd985c5959bBB382BAFdD0b879E149e48116c`}},l1StandardBridge:{[kO]:{address:`0xD0204B9527C1bA7bD765Fa5CCD9355d38338272b`}}},formatters:Iy,sourceId:kO}),jO=11155111,MO=L({id:58008,network:`pgn-testnet`,name:`PGN`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Testnet Explorer`,url:`https://explorer.sepolia.publicgoods.network`,apiUrl:`https://explorer.sepolia.publicgoods.network/api`}},contracts:{l2OutputOracle:{[jO]:{address:`0xD5bAc3152ffC25318F848B3DD5dA6C85171BaEEe`}},portal:{[jO]:{address:`0xF04BdD5353Bb0EFF6CA60CfcC78594278eBfE179`}},l1StandardBridge:{[jO]:{address:`0xFaE6abCAF30D23e233AC7faF747F2fC3a5a6Bfa3`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3754925}},formatters:Iy,sourceId:jO,testnet:!0}),NO=L({id:13381,name:`Phoenix Blockchain`,nativeCurrency:{name:`Phoenix`,symbol:`PHX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.phoenixplorer.com`]}},blockExplorers:{default:{name:`Phoenixplorer`,url:`https://phoenixplorer.com`,apiUrl:`https://phoenixplorer.com/api`}},contracts:{multicall3:{address:`0x498cF757a575cFF2c2Ed9f532f56Efa797f86442`,blockCreated:5620192}}}),PO=L({id:7070,name:`Planq Mainnet`,nativeCurrency:{decimals:18,name:`PLQ`,symbol:`PLQ`},rpcUrls:{default:{http:[`https://planq-rpc.nodies.app`,`https://evm-rpc.planq.network`,`https://jsonrpc.planq.nodestake.top`]}},blockExplorers:{default:{name:`Planq Explorer`,url:`https://evm.planq.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8470015}},testnet:!1}),FO=L({id:9745,name:`Plasma`,blockTime:1e3,nativeCurrency:{name:`Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plasma.to`]}},blockExplorers:{default:{name:`PlasmaScan`,url:`https://plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),IO=L({id:9747,name:`Plasma Devnet`,nativeCurrency:{name:`Devnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://devnet-rpc.plasma.to`]}},testnet:!0,contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),LO=L({id:9746,name:`Plasma Testnet`,nativeCurrency:{name:`Testnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plasma.to`]}},blockExplorers:{default:{name:`RouteScan`,url:`https://testnet.plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),RO=L({...xy,id:1612127,name:`PlayFi Albireo Testnet`,network:`albireo`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://albireo-rpc.playfi.ai`],webSocket:[`wss://albireo-rpc-ws.playfi.ai/ws`]}},blockExplorers:{default:{name:`PlayFi Albireo Explorer`,url:`https://albireo-explorer.playfi.ai`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`}},testnet:!0}),zO=L({id:242,name:`Plinga`,nativeCurrency:{name:`Plinga`,symbol:`PLINGA`,decimals:18},rpcUrls:{default:{http:[`https://rpcurl.mainnet.plgchain.com`]}},blockExplorers:{default:{name:`Plgscan`,url:`https://www.plgscan.com`}},contracts:{multicall3:{address:`0x0989576160f2e7092908BB9479631b901060b6e4`,blockCreated:204489}}}),BO=L({id:98865,name:`Plume (Legacy)`,nativeCurrency:{name:`Plume Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plumenetwork.xyz`],webSocket:[`wss://rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plumenetwork.xyz`,apiUrl:`https://explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:48577}},sourceId:1}),VO=L({id:98864,name:`Plume Devnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc.plumenetwork.xyz`],webSocket:[`wss://test-rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://test-explorer.plumenetwork.xyz`,apiUrl:`https://test-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:481948}},testnet:!0,sourceId:11155111}),HO=L({id:98866,name:`Plume`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plume.org`],webSocket:[`wss://rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plume.org`,apiUrl:`https://explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39679}},sourceId:1}),UO=L({id:98867,name:`Plume Testnet`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plume.org`],webSocket:[`wss://testnet-rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plume.org`,apiUrl:`https://testnet-explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:199712}},testnet:!0,sourceId:11155111}),WO=L({id:161221135,name:`Plume Testnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plumenetwork.xyz/http`],webSocket:[`wss://testnet-rpc.plumenetwork.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plumenetwork.xyz`,apiUrl:`https://testnet-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6022332}},testnet:!0,sourceId:11155111}),GO=L({id:631571,name:`Polter Testnet`,nativeCurrency:{decimals:18,name:`Polter GHST`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-polter.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://polter-testnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11245}},testnet:!0}),KO=L({id:137,name:`Polygon`,blockTime:2e3,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://polygon.drpc.org`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://polygonscan.com`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}}}),qO=L({id:80002,name:`Polygon Amoy`,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://rpc-amoy.polygon.technology`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://amoy.polygonscan.com`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3127388}},testnet:!0}),JO=L({id:80001,name:`Polygon Mumbai`,nativeCurrency:{name:`MATIC`,symbol:`MATIC`,decimals:18},rpcUrls:{default:{http:[`https://80001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://mumbai.polygonscan.com`,apiUrl:`https://api-testnet.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}},testnet:!0}),YO=L({id:1101,name:`Polygon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://zkevm.polygonscan.com`,apiUrl:`https://api-zkevm.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:57746}}}),XO=L({id:2442,name:`Polygon zkEVM Cardona`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cardona.zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://cardona-zkevm.polygonscan.com`,apiUrl:`https://cardona-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:114091}}}),ZO=L({id:1442,name:`Polygon zkEVM Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.public.zkevm-test.net`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://testnet-zkevm.polygonscan.com`,apiUrl:`https://testnet-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:525686}}}),QO=L({id:8008,name:`Polynomial`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://polynomialscan.io`}},testnet:!1,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),$O=L({id:80008,name:`Polynomia Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sepolia.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://sepolia.polynomialscan.io`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),ek=L({id:60603,name:`POTOS Mainnet`,nativeCurrency:{decimals:18,name:`POTOS Token`,symbol:`POT`},rpcUrls:{default:{http:[`https://rpc.potos.hk`]}},blockExplorers:{default:{name:`POTOS Mainnet explorer`,url:`https://scan.potos.hk`}},testnet:!1}),tk=L({id:60600,name:`POTOS Testnet`,nativeCurrency:{decimals:18,name:`POTOS Token`,symbol:`POT`},rpcUrls:{default:{http:[`https://rpc-testnet.potos.hk`]}},blockExplorers:{default:{name:`POTOS Testnet explorer`,url:`https://scan-testnet.potos.hk`}},testnet:!0}),nk=L({id:23023,name:`PremiumBlock Testnet`,nativeCurrency:{name:`Premium Block`,symbol:`PBLK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.premiumblock.org`]}},blockExplorers:{default:{name:`PremiumBlocks Explorer`,url:`https://scan.premiumblock.org`}},testnet:!0}),rk=L({id:369,name:`PulseChain`,nativeCurrency:{name:`Pulse`,symbol:`PLS`,decimals:18},testnet:!1,blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.pulsechain.com`],webSocket:[`wss://ws.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://ipfs.scan.pulsechain.com`,apiUrl:`https://api.scan.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),ik=L({id:943,name:`PulseChain V4`,testnet:!0,nativeCurrency:{name:`V4 Pulse`,symbol:`v4PLS`,decimals:18},blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.v4.testnet.pulsechain.com`],webSocket:[`wss://ws.v4.testnet.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://scan.v4.testnet.pulsechain.com`,apiUrl:`https://scan.v4.testnet.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),ak=L({id:490092,name:`Pumpfi Testnet`,nativeCurrency:{decimals:18,name:`PMPT`,symbol:`PMPT`},rpcUrls:{default:{http:[`https://rpc1testnet.pumpfi.me`]}},blockExplorers:{default:{name:`Pumpfi Testnet Scan`,url:`https://testnetscan.pumpfi.me`}},testnet:!0}),ok=11155111,sk=L({...R,name:`Pyrope Testnet`,testnet:!0,id:695569,sourceId:ok,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.pyropechain.com`],webSocket:[`wss://rpc.pyropechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://pyrope.blockscout.com`}},contracts:{...R.contracts,l1StandardBridge:{[ok]:{address:`0xC24932c31D9621aE9e792576152B7ef010cFC2F8`}}}}),ck=L({id:766,name:`QL1`,nativeCurrency:{decimals:18,name:`QOM`,symbol:`QOM`},rpcUrls:{default:{http:[`https://rpc.qom.one`]}},blockExplorers:{default:{name:`Ql1 Explorer`,url:`https://scan.qom.one`}},contracts:{multicall3:{address:`0x7A52370716ea730585884F5BDB0f6E60C39b8C64`}},testnet:!1}),lk=L({id:35441,name:`Q Mainnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.q.org`]}},blockExplorers:{default:{name:`Q Mainnet Explorer`,url:`https://explorer.q.org`,apiUrl:`https://explorer.q.org/api`}}}),uk=L({id:35443,name:`Q Testnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.qtestnet.org`]}},blockExplorers:{default:{name:`Q Testnet Explorer`,url:`https://explorer.qtestnet.org`,apiUrl:`https://explorer.qtestnet.org/api`}},testnet:!0}),dk=L({id:9,name:`Quai Network Mainnet`,nativeCurrency:{decimals:18,name:`Quai`,symbol:`QUAI`},rpcUrls:{default:{http:[`https://rpc.quai.network/cyprus1`]}},blockExplorers:{default:{name:`Quaiscan`,url:`https://quaiscan.io`,apiUrl:`https://quaiscan.io/api`}},testnet:!1}),fk=L({id:15e3,name:`Quai Network Testnet`,nativeCurrency:{decimals:18,name:`Quai`,symbol:`QUAI`},rpcUrls:{default:{http:[`https://orchard.rpc.quai.network/cyprus1`]}},blockExplorers:{default:{name:`Orchard Quaiscan`,url:`https://orchard.quaiscan.io`,apiUrl:`https://orchard.quaiscan.io/api`}},testnet:!0}),pk=L({id:5318007,name:`Reactive Lasna Testnet`,nativeCurrency:{decimals:18,name:`Lasna React`,symbol:`lREACT`},rpcUrls:{default:{http:[`https://lasna-rpc.rnk.dev`]}},blockExplorers:{default:{name:`Reactscan`,url:`https://lasna.reactscan.net`}},testnet:!0}),mk=L({id:111188,name:`re.al`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.realforreal.gelato.digital`]}},blockExplorers:{default:{name:`re.al Explorer`,url:`https://explorer.re.al`,apiUrl:`https://explorer.re.al/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:695}}}),hk=L({id:151,name:`Redbelly Network Mainnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.mainnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/151/etherscan/api`}},testnet:!1}),gk=L({id:153,name:`Redbelly Network Testnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.testnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.testnet.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/153_2/etherscan/api`}},testnet:!0}),_k=L({id:50342,name:`Reddio`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.reddio.com/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://reddio.cloud.blockscout.com`,apiUrl:`https://reddio.cloud.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:848849}},testnet:!1}),vk=L({id:50341,name:`Reddio Sepolia`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://reddio-dev.reddio.com`]}},blockExplorers:{default:{name:`Reddioscan`,url:`https://reddio-devnet.l2scan.co`,apiUrl:`https://reddio-devnet.l2scan.co/api`}},testnet:!0}),yk=1,bk=L({...R,name:`Redstone`,id:690,sourceId:yk,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.redstonechain.com`],webSocket:[`wss://rpc.redstonechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.redstone.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[yk]:{address:`0xC7bCb0e8839a28A1cFadd1CF716de9016CdA51ae`,blockCreated:19578329}},l2OutputOracle:{[yk]:{address:`0xa426A052f657AEEefc298b3B5c35a470e4739d69`,blockCreated:19578337}},l1StandardBridge:{[yk]:{address:`0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69`,blockCreated:19578331}}}}),xk=L({id:47805,name:`REI Mainnet`,nativeCurrency:{decimals:18,name:`REI`,symbol:`REI`},rpcUrls:{default:{http:[`https://rpc.rei.network`],webSocket:[`wss://rpc.rei.network`]}},blockExplorers:{default:{name:`REI Scan`,url:`https://scan.rei.network`}},testnet:!1}),Sk=L({id:1729,name:`Reya Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.reya.network`],webSocket:[`wss://ws.reya.network`]}},blockExplorers:{default:{name:`Reya Network Explorer`,url:`https://explorer.reya.network`}},testnet:!1}),Ck=L({id:4153,name:`RISE`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.risechain.com`],webSocket:[`wss://rpc.risechain.com/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.risechain.com`,apiUrl:`https://explorer.risechain.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),wk=L({id:11155931,name:`RISE Testnet`,nativeCurrency:{name:`RISE Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.riselabs.xyz`],webSocket:[`wss://testnet.riselabs.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.testnet.riselabs.xyz/`,apiUrl:`https://explorer.testnet.riselabs.xyz/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},testnet:!0}),Tk=L({id:753,name:`Rivalz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rivalz.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Rivalz Caldera Explorer`,url:`https://rivalz.calderaexplorer.xyz`}},testnet:!1}),Ek=L({id:570,name:`Rollux Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.rollux.com`],webSocket:[`wss://rpc.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxExplorer`,url:`https://explorer.rollux.com`,apiUrl:`https://explorer.rollux.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:119222}}}),Dk=L({id:57e3,name:`Rollux Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc-tanenbaum.rollux.com/`],webSocket:[`wss://rpc-tanenbaum.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxTestnetExplorer`,url:`https://rollux.tanenbaum.io`,apiUrl:`https://rollux.tanenbaum.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1813675}}}),Ok=L({id:2020,name:`Ronin`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://api.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Ronin Explorer`,url:`https://app.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:26023535}}}),kk=L({id:7668,name:`The Root Network`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://root.rootnet.live/archive`],webSocket:[`wss://root.rootnet.live/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:9218338}}}),Ak=L({id:7672,name:`The Root Network - Porcini`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://porcini.rootnet.app/archive`],webSocket:[`wss://porcini.rootnet.app/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://porcini.rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:10555692}},testnet:!0}),jk=L({id:30,name:`Rootstock Mainnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`RBTC`},rpcUrls:{default:{http:[`https://public-node.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.rsk.co`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4249540}}}),Mk=L({id:31,name:`Rootstock Testnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`tRBTC`},rpcUrls:{default:{http:[`https://public-node.testnet.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.testnet.rootstock.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2771150}},testnet:!0}),Nk=1,Pk=L({...R,id:12553,name:`RSS3 VSL Mainnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Mainnet Scan`,url:`https://scan.rss3.io`,apiUrl:`https://scan.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[Nk]:{address:`0xE6f24d2C32B3109B18ed33cF08eFb490b1e09C10`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14193},portal:{[Nk]:{address:`0x6A12432491bbbE8d3babf75F759766774C778Db4`,blockCreated:19387057}},l1StandardBridge:{[Nk]:{address:`0x4cbab69108Aa72151EDa5A3c164eA86845f18438`}}},sourceId:Nk}),Fk=11155111,Ik=L({...R,id:2331,name:`RSS3 VSL Sepolia Testnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Sepolia Testnet Scan`,url:`https://scan.testnet.rss3.io`,apiUrl:`https://scan.testnet.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[Fk]:{address:`0xDb5c46C3Eaa6Ed6aE8b2379785DF7dd029C0dC81`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:55697},portal:{[Fk]:{address:`0xcBD77E8E1E7F06B25baDe67142cdE82652Da7b57`,blockCreated:5345035}},l1StandardBridge:{[Fk]:{address:`0xdDD29bb63B0839FB1cE0eE439Ff027738595D07B`}}},testnet:!0,sourceId:Fk}),Lk=L({id:7225878,name:`Saakuru Mainnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.saakuru.network`]}},blockExplorers:{default:{name:`Saakuru Explorer`,url:`https://explorer.saakuru.network`}},testnet:!1}),Rk=L({id:5464,name:`Saga`,network:`saga`,nativeCurrency:{decimals:18,name:`gas`,symbol:`GAS`},rpcUrls:{default:{http:[`https://sagaevm.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Saga Explorer`,url:`https://sagaevm.sagaexplorer.io`}},contracts:{multicall3:{address:`0x864DDc9B50B9A0dF676d826c9B9EDe9F8913a160`,blockCreated:467530}}}),zk=L({id:202601,name:`Ronin Saigon Testnet`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://saigon-testnet.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Saigon Explorer`,url:`https://saigon-explorer.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18736871}},testnet:!0}),Bk=L({id:1996,name:`Sanko`,nativeCurrency:{name:`DMT`,symbol:`DMT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.sanko.xyz`]}},blockExplorers:{default:{name:`Sanko Explorer`,url:`https://explorer.sanko.xyz`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:37}},testnet:!1}),Vk=L({id:23294,name:`Oasis Sapphire`,network:`sapphire`,nativeCurrency:{name:`Sapphire Rose`,symbol:`ROSE`,decimals:18},rpcUrls:{default:{http:[`https://sapphire.oasis.io`],webSocket:[`wss://sapphire.oasis.io/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/mainnet/sapphire`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:734531}}}),Hk=L({id:23295,name:`Oasis Sapphire Testnet`,network:`sapphire-testnet`,nativeCurrency:{name:`Sapphire Test Rose`,symbol:`TEST`,decimals:18},rpcUrls:{default:{http:[`https://testnet.sapphire.oasis.dev`],webSocket:[`wss://testnet.sapphire.oasis.dev/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/testnet/sapphire`}},testnet:!0}),Uk=L({id:3109,name:`SatoshiVM Alpha Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://alpha-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://svmscan.io`,apiUrl:`https://svmscan.io/api`}}}),Wk=L({id:3110,name:`SatoshiVM Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet.svmscan.io`,apiUrl:`https://testnet.svmscan.io/api`}},testnet:!0}),Gk=L({id:534352,name:`Scroll`,blockTime:3e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.scroll.io`],webSocket:[`wss://wss-rpc.scroll.io/ws`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://scrollscan.com`,apiUrl:`https://api.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14}},testnet:!1}),Kk=L({id:534351,name:`Scroll Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.scroll.io`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://sepolia.scrollscan.com`,apiUrl:`https://api-sepolia.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9473}},testnet:!0}),qk=L({id:1329,name:`Sei Network`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc.sei-apis.com/`],webSocket:[`wss://evm-ws.sei-apis.com/`]}},blockExplorers:{default:{name:`Seiscan`,url:`https://seiscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}}}),Jk=L({id:5124,name:`Seismic Devnet`,nativeCurrency:{name:`Seismic Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://node-2.seismicdev.net/rpc`]}},blockExplorers:{default:{name:`Seismic Devnet Explorer`,url:`https://explorer-2.seismicdev.net`}},testnet:!0}),Yk=L({id:1328,name:`Sei Testnet`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc-testnet.sei-apis.com`],webSocket:[`wss://evm-ws-testnet.sei-apis.com`]}},blockExplorers:{default:{name:`Seiscan`,url:`https://testnet.seiscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:98697651}},testnet:!0}),Xk=L({id:11155111,name:`Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://11155111.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.etherscan.io`,apiUrl:`https://api-sepolia.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:751532},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:8928790}},testnet:!0}),Zk=1,Qk=L({...R,id:360,name:`Shape`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.shape.network`]}},blockExplorers:{default:{name:`shapescan`,url:`https://shapescan.xyz`,apiUrl:`https://shapescan.xyz/api`}},contracts:{...R.contracts,l2OutputOracle:{[Zk]:{address:`0x6Ef8c69CfE4635d866e3E02732068022c06e724D`,blockCreated:20369940}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1},portal:{[Zk]:{address:`0xEB06fFa16011B5628BaB98E29776361c83741dd3`,blockCreated:20369933}},l1StandardBridge:{[Zk]:{address:`0x62Edd5f4930Ea92dCa3fB81689bDD9b9d076b57B`,blockCreated:20369935}}},sourceId:Zk}),$k=11155111,eA=L({...R,id:11011,name:`Shape Sepolia Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.shape.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer-sepolia.shape.network/`,apiUrl:`https://explorer-sepolia.shape.network/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0,sourceId:$k}),tA=L({id:8118,name:`Shardeum`,nativeCurrency:{name:`Shardeum`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://api.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer.shardeum.org`}},testnet:!1}),nA=L({id:8082,name:`Shardeum Sphinx`,nativeCurrency:{name:`SHARDEUM`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://sphinx.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer-sphinx.shardeum.org`}},testnet:!0}),rA=L({id:109,name:`Shibarium`,network:`shibarium`,nativeCurrency:{name:`Bone`,symbol:`BONE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://shibariumscan.io`}},contracts:{multicall3:{address:`0x864Bf681ADD6052395188A89101A1B37d3B4C961`,blockCreated:265900}}}),iA=L({id:157,name:`Puppynet Shibarium`,nativeCurrency:{decimals:18,name:`Bone`,symbol:`BONE`},rpcUrls:{default:{http:[`https://puppynet.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://puppyscan.shib.io`,apiUrl:`https://puppyscan.shib.io/api`}},contracts:{multicall3:{address:`0xA4029b74FBA366c926eDFA7Dd10B21C621170a4c`,blockCreated:3035769}},testnet:!0}),aA=L({id:336,name:`Shiden`,nativeCurrency:{decimals:18,name:`SDN`,symbol:`SDN`},rpcUrls:{default:{http:[`https://shiden.public.blastapi.io`],webSocket:[`wss://shiden-rpc.dwellir.com`]}},blockExplorers:{default:{name:`Shiden Scan`,url:`https://shiden.subscan.io`}},testnet:!1}),oA=L({id:148,name:`Shimmer`,network:`shimmer`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.shimmer.network`,apiUrl:`https://explorer.evm.shimmer.network/api`}}}),sA=L({id:1073,name:`Shimmer Testnet`,network:`shimmer-testnet`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.testnet.shimmer.network`,apiUrl:`https://explorer.evm.testnet.shimmer.network/api`}},testnet:!0}),cA=L({id:97453,name:`Sidra Chain`,nativeCurrency:{decimals:18,name:`Sidra Digital Asset`,symbol:`SDA`},rpcUrls:{default:{http:[`https://node.sidrachain.com`]}},blockExplorers:{default:{name:`Sidra Chain Explorer`,url:`https://ledger.sidrachain.com`}}}),lA=L({id:380929,name:`Silent Data Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.silentdata.com`]}},blockExplorers:{default:{name:`Silent Data Mainnet Explorer`,url:`https://explorer-mainnet.rollup.silentdata.com`}},testnet:!1}),uA=L({id:2355,name:`Silicon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.silicon.network`,`https://silicon-mainnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconScope`,url:`https://scope.silicon.network`}}}),dA=L({id:1722641160,name:`Silicon Sepolia zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-sepolia.silicon.network`,`https://silicon-testnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconSepoliaScope`,url:`https://scope-sepolia.silicon.network`}},testnet:!0}),fA=L({id:98,name:`Six Protocol`,nativeCurrency:{decimals:18,name:`SIX`,symbol:`SIX`},rpcUrls:{default:{http:[`https://sixnet-rpc-evm.sixprotocol.net`]}},blockExplorers:{default:{name:`Six Protocol Scan`,url:`https://sixscan.io/sixnet`}},testnet:!1}),pA=L({id:391845894,name:`SKALE | Block Brawlers`,nativeCurrency:{name:`BRAWL`,symbol:`BRAWL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/frayed-decent-antares`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/frayed-decent-antares`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://frayed-decent-antares.explorer.mainnet.skalenodes.com`}},contracts:{}}),mA=L({id:1564830818,name:`SKALE Calypso Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/honorable-steel-rasalhague`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/honorable-steel-rasalhague`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3107626}}}),hA=L({id:974399131,name:`SKALE Calypso Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/giant-half-dual-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/giant-half-dual-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://giant-half-dual-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:103220}},testnet:!0}),gA=L({id:1026062157,name:`SKALE | CryptoBlades`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/affectionate-immediate-pollux`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/affectionate-immediate-pollux`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://affectionate-immediate-pollux.explorer.mainnet.skalenodes.com`}},contracts:{}}),_A=L({id:1032942172,name:`SKALE | Crypto Colosseum`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/haunting-devoted-deneb`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/haunting-devoted-deneb`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://haunting-devoted-deneb.explorer.mainnet.skalenodes.com`}},contracts:{}}),vA=L({id:2046399126,name:`SKALE Europa Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/elated-tan-skat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/elated-tan-skat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://elated-tan-skat.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3113495}}}),yA=L({id:1444673419,name:`SKALE Europa Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/juicy-low-small-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/juicy-low-small-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://juicy-low-small-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:110858}},testnet:!0}),bA=L({id:2139927552,name:`Exorde Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/light-vast-diphda`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/light-vast-diphda`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://light-vast-diphda.explorer.mainnet.skalenodes.com`}},contracts:{}}),xA=L({id:1273227453,name:`SKALE | Human Protocol`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/wan-red-ain`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/wan-red-ain`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://wan-red-ain.explorer.mainnet.skalenodes.com`}},contracts:{}}),SA=L({id:1482601649,name:`SKALE Nebula Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/green-giddy-denebola`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/green-giddy-denebola`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://green-giddy-denebola.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2372986}}}),CA=L({id:37084624,name:`SKALE Nebula Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/lanky-ill-funny-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/lanky-ill-funny-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:105141}},testnet:!0}),wA=L({id:278611351,name:`SKALE | Razor Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/turbulent-unique-scheat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/turbulent-unique-scheat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://turbulent-unique-scheat.explorer.mainnet.skalenodes.com`}},contracts:{}}),TA=L({id:1187947933,name:`SKALE Base`,nativeCurrency:{name:`Credits`,symbol:`CREDIT`,decimals:18},rpcUrls:{default:{http:[`https://skale-base.skalenodes.com/v1/base`],webSocket:[`wss://skale-base.skalenodes.com/v1/ws/base`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://skale-base-explorer.skalenodes.com/`}},testnet:!0}),EA=L({id:324705682,name:`SKALE Base Sepolia Testnet`,nativeCurrency:{name:`Credits`,symbol:`CREDIT`,decimals:18},rpcUrls:{default:{http:[`https://base-sepolia-testnet.skalenodes.com/v1/base-testnet`],webSocket:[`wss://base-sepolia-testnet.skalenodes.com/v1/ws/base-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://base-sepolia-testnet-explorer.skalenodes.com/`}},testnet:!0}),DA=L({id:1350216234,name:`SKALE Titan Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/parallel-stormy-spica`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/parallel-stormy-spica`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://parallel-stormy-spica.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2076458}}}),OA=L({id:1020352220,name:`SKALE Titan Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/aware-fake-trim-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/aware-fake-trim-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://aware-fake-trim-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:104072}},testnet:!0}),kA=L({id:984123,name:`Forma Sketchpad`,network:`sketchpad`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sketchpad-1.forma.art`],webSocket:[`wss://ws.sketchpad-1.forma.art`]}},blockExplorers:{default:{name:`Sketchpad Explorer`,url:`https://explorer.sketchpad-1.forma.art`}},testnet:!0}),AA=1,jA=L({...R,id:2192,network:`snaxchain-mainnet`,name:`SnaxChain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://explorer.snaxchain.io`,apiUrl:`https://explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[AA]:{address:`0x472562Fcf26D6b2793f8E0b0fB660ba0E5e08A46`}},l2OutputOracle:{[AA]:{address:`0x2172e492Fc807F5d5645D0E3543f139ECF539294`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[AA]:{address:`0x79f446D024d74D0Bb6E699C131c703463c5D65E9`}},l1StandardBridge:{[AA]:{address:`0x6534Bdb6b5c060d3e6aa833433333135eFE8E0aA`}}},sourceId:AA}),MA=11155111,NA=L({...R,id:13001,network:`snaxchain-testnet`,name:`SnaxChain Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://testnet-explorer.snaxchain.io`,apiUrl:`https://testnet-explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[MA]:{address:`0x206a75d89d45F146C54020F132FF93bEDD09f55E`}},l2OutputOracle:{[MA]:{address:`0x60e3A368a4cdCEf85ffB964e372726F56A46221e`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[MA]:{address:`0xb5afdd0E8dDF081Ef90e8A3e0c7b5798e66E954E`}},l1StandardBridge:{[MA]:{address:`0xbd37E1a59D4C00C9A46F75018dffd84061bC5f74`}}},testnet:!0,sourceId:MA}),PA=L({id:5031,name:`Somnia`,nativeCurrency:{name:`Somnia`,symbol:`SOMI`,decimals:18},rpcUrls:{default:{http:[`https://api.infra.mainnet.somnia.network`]}},blockExplorers:{default:{name:`Somnia Explorer`,url:`https://explorer.somnia.network`,apiUrl:`https://explorer.somnia.network/api`}},testnet:!1}),FA=L({id:50312,name:`Somnia Testnet`,nativeCurrency:{name:`STT`,symbol:`STT`,decimals:18},rpcUrls:{default:{http:[`https://dream-rpc.somnia.network`]}},blockExplorers:{default:{name:`Somnia Testnet Explorer`,url:`https://shannon-explorer.somnia.network/`,apiUrl:`https://shannon-explorer.somnia.network/api`}},contracts:{multicall3:{address:`0x841b8199E6d3Db3C6f264f6C2bd8848b3cA64223`,blockCreated:71314235}},testnet:!0}),IA=1,LA=L({...R,id:1868,name:`Soneium Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium.blockscout.com`,apiUrl:`https://soneium.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[IA]:{address:`0x512a3d2c7a43bd9261d2b8e8c9c70d4bd4d503c0`}},l2OutputOracle:{[IA]:{address:`0x0000000000000000000000000000000000000000`}},portal:{[IA]:{address:`0x88e529a6ccd302c948689cd5156c83d4614fae92`,blockCreated:7061266}},l1StandardBridge:{[IA]:{address:`0xeb9bf100225c214efc3e7c651ebbadcf85177607`,blockCreated:7061266}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},sourceId:IA}),RA=11155111,zA=L({...R,id:1946,name:`Soneium Minato Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.minato.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium-minato.blockscout.com`,apiUrl:`https://soneium-minato.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[RA]:{address:`0xB3Ad2c38E6e0640d7ce6aA952AB3A60E81bf7a01`}},l2OutputOracle:{[RA]:{address:`0x710e5286C746eC38beeB7538d0146f60D27be343`}},portal:{[RA]:{address:`0x65ea1489741A5D72fFdD8e6485B216bBdcC15Af3`,blockCreated:6466136}},l1StandardBridge:{[RA]:{address:`0x5f5a404A5edabcDD80DB05E8e54A78c9EBF000C2`,blockCreated:6466136}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},testnet:!0,sourceId:RA}),BA=L({id:19,name:`Songbird Canary-Network`,nativeCurrency:{decimals:18,name:`Songbird`,symbol:`SGB`},rpcUrls:{default:{http:[`https://songbird-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Songbird Explorer`,url:`https://songbird-explorer.flare.network`,apiUrl:`https://songbird-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13382504}}}),VA=L({id:16,name:`Songbird Testnet Coston`,nativeCurrency:{decimals:18,name:`Coston Flare`,symbol:`CFLR`},rpcUrls:{default:{http:[`https://coston-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston Explorer`,url:`https://coston-explorer.flare.network`,apiUrl:`https://coston-explorer.flare.network/api`}},testnet:!0}),HA=L({id:146,name:`Sonic`,blockTime:630,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Explorer`,url:`https://sonicscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:60}},testnet:!1}),UA=L({id:57054,name:`Sonic Blaze Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.blaze.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Blaze Testnet Explorer`,url:`https://testnet.sonicscan.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1100}},testnet:!0}),WA=L({id:64165,name:`Sonic Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.testnet.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Testnet Explorer`,url:`https://testnet.soniclabs.com/`}},testnet:!0}),GA=L({...xy,blockTime:200,id:50104,name:`Sophon`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.sophon.xyz`],webSocket:[`wss://rpc.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.sophon.xyz`}},contracts:{multicall3:{address:`0x5f4867441d2416cA88B1b3fd38f21811680CD2C8`,blockCreated:116}},testnet:!1}),KA=L({...xy,blockTime:200,id:531050104,name:`Sophon Testnet`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.testnet.sophon.xyz`],webSocket:[`wss://rpc.testnet.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.testnet.sophon.xyz`}},contracts:{multicall3:{address:`0x83c04d112adedA2C6D9037bb6ecb42E7f0b108Af`,blockCreated:15642}},testnet:!0}),qA=L({id:100021,name:`Sova`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sova.io`]}},blockExplorers:{default:{name:`Sova Block Explorer`,url:`hhttps://explorer.sova.io`}},testnet:!1}),JA=L({id:120893,name:`Sova Network Sepolia`,nativeCurrency:{decimals:18,name:`Sepolia Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.testnet.sova.io`]}},blockExplorers:{default:{name:`Sova Sepolia Explorer`,url:`https://explorer.testnet.sova.io`}},testnet:!0}),YA=L({id:88882,name:`Chiliz Spicy Testnet`,network:`chiliz-spicy-Testnet`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://spicy-rpc.chiliz.com`,`https://chiliz-spicy-rpc.publicnode.com`],webSocket:[`wss://spicy-rpc-ws.chiliz.com`,`wss://chiliz-spicy-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`http://spicy-explorer.chiliz.com`,apiUrl:`http://spicy-explorer.chiliz.com/api`}},testnet:!0}),XA=L({id:988,name:`Stable Mainnet`,blockTime:700,nativeCurrency:{name:`USDT0`,symbol:`USDT0`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stable.xyz`],webSocket:[`wss://rpc.stable.xyz`]}},blockExplorers:{default:{name:`Stablescan`,url:`https://stablescan.xyz`,apiUrl:`https://api.etherscan.io/v2/api?chainid=988`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2423647}},testnet:!1}),ZA=L({id:2201,name:`Stable Testnet`,blockTime:700,nativeCurrency:{name:`USDT0`,symbol:`USDT0`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.stable.xyz`],webSocket:[`wss://rpc.testnet.stable.xyz`]}},blockExplorers:{default:{name:`Stablescan`,url:`https://testnet.stablescan.xyz`,apiUrl:`https://api.etherscan.io/v2/api?chainid=2201`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:22364430}},testnet:!0}),QA=L({...SE,id:1660990954,name:`Status Network Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://public.sepolia.rpc.status.network`],webSocket:[`wss://public.sepolia.rpc.status.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepoliascan.status.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1578364}},testnet:!0}),$A=L({id:1234,name:`Step Network`,nativeCurrency:{name:`FITFI`,symbol:`FITFI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.step.network`]}},blockExplorers:{default:{name:`Step Scan`,url:`https://stepscan.io`}},testnet:!1}),ej=L({id:1514,name:`Story`,nativeCurrency:{decimals:18,name:`IP Token`,symbol:`IP`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:340998},ensRegistry:{address:`0x5dc881dda4e4a8d312be3544ad13118d1a04cb17`,blockCreated:648924},ensUniversalResolver:{address:`0xddfb18888a9466688235887dec2a10c4f5effee9`,blockCreated:649114}},rpcUrls:{default:{http:[`https://mainnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story explorer`,url:`https://storyscan.io`,apiUrl:`https://storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!1}),tj=L({id:1315,name:`Story Aeneid`,network:`story-aeneid`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1792},ensRegistry:{address:`0x5dC881dDA4e4a8d312be3544AD13118D1a04Cb17`,blockCreated:1322033},ensUniversalResolver:{address:`0x6D3B3F99177FB2A5de7F9E928a9BD807bF7b5BAD`,blockCreated:1322097}},rpcUrls:{default:{http:[`https://aeneid.storyrpc.io`]}},blockExplorers:{default:{name:`Story Aeneid Explorer`,url:`https://aeneid.storyscan.io`,apiUrl:`https://aeneid.storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!0}),nj=L({id:1516,name:`Story Odyssey`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://rpc.odyssey.storyrpc.io`]}},blockExplorers:{default:{name:`Story Odyssey Explorer`,url:`https://odyssey.storyscan.xyz`}},testnet:!0}),rj=L({id:1513,name:`Story Testnet`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://testnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story Testnet Explorer`,url:`https://testnet.storyscan.xyz`}},testnet:!0}),ij=L({id:105105,name:`Stratis Mainnet`,network:`stratis`,nativeCurrency:{name:`Stratis`,symbol:`STRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Stratis Explorer`,url:`https://explorer.stratisevm.com`}}}),aj=L({id:964,name:`Subtensor EVM`,nativeCurrency:{decimals:18,name:`TAO`,symbol:`TAO`},rpcUrls:{default:{http:[`https://lite.chain.opentensor.ai`]}},blockExplorers:{default:{name:`Taostats EVM Explorer`,url:`https://evm.taostats.io`,apiUrl:`https://evm.taostats.io/api`}},testnet:!1}),oj=L({id:8866,name:`SuperLumio`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.lumio.io`]}},blockExplorers:{default:{name:`Lumio explorer`,url:`https://explorer.lumio.io`}},testnet:!1}),sj=L({id:55244,name:`Superposition`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.superposition.so`]}},blockExplorers:{default:{name:`Superposition Explorer`,url:`https://explorer.superposition.so`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39}},testnet:!1}),cj=1,lj=L({...R,id:5330,name:`Superseed`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Explorer`,url:`https://explorer.superseed.xyz`,apiUrl:`https://explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,disputeGameFactory:{[cj]:{address:`0x8b097CF1f9BbD9cbFD0DD561858a1FCbC8857Be0`,blockCreated:20737481}},l2OutputOracle:{[cj]:{address:`0x693A0F8854F458D282DE3C5b69E8eE5EEE8aA949`,blockCreated:20737481}},portal:{[cj]:{address:`0x2c2150aa5c75A24fB93d4fD2F2a895D618054f07`,blockCreated:20737481}},l1StandardBridge:{[cj]:{address:`0x8b0576E39F1233679109F9b40cFcC2a7E0901Ede`,blockCreated:20737481}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:cj}),uj=11155111,dj=L({...R,id:53302,name:`Superseed Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Sepolia Explorer`,url:`https://sepolia-explorer.superseed.xyz`,apiUrl:`https://sepolia-explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},portal:{[uj]:{address:`0x7A0db8C51432d2C3eb4e8f360a2EeB26FF2809fB`,blockCreated:5523438}},l1StandardBridge:{[uj]:{address:`0x2B227A603fAAdB3De0ED050b63ADD232B5f2c28C`,blockCreated:5523442}}},testnet:!0,sourceId:uj}),fj=L({id:763375,name:`Surge Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-rpc.hoodi.surge.wtf`],webSocket:[`wss://l2-ws.hoodi.surge.wtf`]}},blockExplorers:{default:{name:`Surge Testnet Blockscout`,url:`https://explorer.hoodi.surge.wtf`}},testnet:!0}),pj=L({id:254,name:`Swan Chain Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.swanchain.org`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://swanscan.io`}},testnet:!1}),mj=L({id:20241133,name:`Swan Proxima Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-proxima.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://proxima-explorer.swanchain.io`}},testnet:!0}),hj=L({id:2024,name:`Swan Saturn Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://saturn-rpc.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://saturn-explorer.swanchain.io`}},testnet:!0}),gj=L({...R,id:1923,name:`Swellchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-mainnet.alt.technology`]}},blockExplorers:{default:{name:`Swell Explorer`,url:`https://explorer.swellnetwork.io`,apiUrl:`https://explorer.swellnetwork.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),_j=L({...R,id:1924,name:`Swellchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-testnet.alt.technology`]}},blockExplorers:{default:{name:`Swellchain Testnet Explorer`,url:`https://swell-testnet-explorer.alt.technology`,apiUrl:`https://swell-testnet-explorer.alt.technology/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),vj=L({id:94,name:`SwissDLT Mainnet`,nativeCurrency:{decimals:18,name:`BCTS`,symbol:`BCTS`},rpcUrls:{default:{http:[`https://rpc.swissdlt.ch`]}},blockExplorers:{default:{name:`SwissDLT Explorer`,url:`https://explorer.swissdlt.ch`}},testnet:!1}),yj=L({id:57,name:`Syscoin Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.syscoin.org`],webSocket:[`wss://rpc.syscoin.org/wss`]}},blockExplorers:{default:{name:`SyscoinExplorer`,url:`https://explorer.syscoin.org`,apiUrl:`https://explorer.syscoin.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:287139}}}),bj=L({id:5700,name:`Syscoin Tanenbaum Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.tanenbaum.io`],webSocket:[`wss://rpc.tanenbaum.io/wss`]}},blockExplorers:{default:{name:`SyscoinTestnetExplorer`,url:`https://tanenbaum.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:271288}}}),xj=L({id:239,name:`TAC`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ankr.com/tac`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://tac.blockscout.com`,apiUrl:`https://tac.blockscout.com/api`},native:{name:`TAC Explorer`,url:`https://explorer.tac.build`,apiUrl:`https://explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),Sj=L({id:2391,name:`TAC SPB Testnet`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://spb.rpc.tac.build`]}},blockExplorers:{default:{name:`TAC`,url:`https://spb.explorer.tac.build`,apiUrl:`https://spb.explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:471429}},testnet:!0}),Cj=L({id:167e3,name:`Taiko Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.mainnet.taiko.xyz`],webSocket:[`wss://ws.mainnet.taiko.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://taikoscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:11269}}}),wj=L({id:167009,name:`Taiko Hekla L2`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hekla.taiko.xyz`]}},blockExplorers:{default:{name:`Taikoscan`,url:`https://hekla.taikoscan.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59757}},testnet:!0}),Tj=L({id:167013,name:`Taiko Hoodi`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hoodi.taiko.xyz`],webSocket:[`wss://ws.hoodi.taiko.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://hoodi.taikoscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:581116}},testnet:!0}),Ej=L({id:167007,name:`Taiko Jolnir (Alpha-5 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.jolnir.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.jolnir.taiko.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:732706}},testnet:!0}),Dj=L({id:167008,name:`Taiko Katla (Alpha-6 Testnet)`,network:`tko-katla`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katla.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.katla.taiko.xyz`}}}),Oj=L({id:167005,name:`Taiko (Alpha-3 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.test.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.test.taiko.xyz`}}}),kj=L({id:841,name:`Taraxa Mainnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.mainnet.taraxa.io`}}}),Aj=L({id:842,name:`Taraxa Testnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.testnet.taraxa.io`}},testnet:!0}),jj=L({id:10218,name:`Tea Sepolia`,nativeCurrency:{name:`Sepolia Tea`,symbol:`TEA`,decimals:18},rpcUrls:{default:{http:[`https://tea-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Tea Sepolia Explorer`,url:`https://sepolia.tea.xyz`}},testnet:!0}),Mj=L({id:2017,name:`Telcoin Adiri Testnet`,nativeCurrency:{name:`Telcoin`,symbol:`TEL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.telcoin.network`]}},blockExplorers:{default:{name:`telscan`,url:`https://telscan.io`}},testnet:!0}),Nj=L({id:40,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.telos.net`]}},blockExplorers:{default:{name:`Teloscan`,url:`https://www.teloscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:246530709}}}),Pj=L({id:41,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.testnet.telos.net`]}},blockExplorers:{default:{name:`Teloscan (testnet)`,url:`https://testnet.teloscan.io/`}},testnet:!0});Ls(),wl(),Pl(),Wc(),xl();var Fj=Mc(BigInt(`0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff`)),Ij=Fj.create(BigInt(`-3`)),Lj=BigInt(`0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`),Rj=Cl({a:Ij,b:Lj,Fp:Fj,n:BigInt(`0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551`),Gx:BigInt(`0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296`),Gy:BigInt(`0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5`),h:BigInt(1),lowS:!1},Ps),zj=Rj,Bj=pl(Fj,{A:Ij,B:Lj,Z:Fj.create(BigInt(`-10`))}),Vj=Ml(zj.ProjectivePoint,e=>Bj(e[0]),{DST:`P256_XMD:SHA-256_SSWU_RO_`,encodeDST:`P256_XMD:SHA-256_SSWU_NU_`,p:Fj.ORDER,m:1,k:128,expand:`xmd`,hash:Ps}),Hj=Mc(BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff`)),Uj=Hj.create(BigInt(`-3`)),Wj=BigInt(`0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef`),Gj=Cl({a:Uj,b:Wj,Fp:Hj,n:BigInt(`0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973`),Gx:BigInt(`0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7`),Gy:BigInt(`0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f`),h:BigInt(1),lowS:!1},Is),Kj=pl(Hj,{A:Uj,B:Wj,Z:Hj.create(BigInt(`-12`))});Ml(Gj.ProjectivePoint,e=>Kj(e[0]),{DST:`P384_XMD:SHA-384_SSWU_RO_`,encodeDST:`P384_XMD:SHA-384_SSWU_NU_`,p:Hj.ORDER,m:1,k:192,expand:`xmd`,hash:Is});var qj=Mc(BigInt(`0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`)),Jj=qj.create(BigInt(`-3`)),Yj=BigInt(`0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00`),Xj=Cl({a:Jj,b:Yj,Fp:qj,n:BigInt(`0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409`),Gx:BigInt(`0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66`),Gy:BigInt(`0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650`),h:BigInt(1),lowS:!1,allowedPrivateKeyLengths:[130,131,132]},Fs),Zj=pl(qj,{A:Jj,B:Yj,Z:qj.create(BigInt(`-4`))});Ml(Xj.ProjectivePoint,e=>Zj(e[0]),{DST:`P521_XMD:SHA-512_SSWU_RO_`,encodeDST:`P521_XMD:SHA-512_SSWU_NU_`,p:qj.ORDER,m:1,k:256,expand:`xmd`,hash:Fs});var Qj=Rj,$j=Rj;Vj.hashToCurve,Vj.encodeToCurve,Hf();function eM(e){let{hash:t,payload:n,publicKey:r,signature:i}=e;return $j.verify(i,n instanceof Uint8Array?n:xf(n),og(r).substring(2),{lowS:!0,...t?{prehash:!0}:{}})}Hf();var tM=new TextEncoder;Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[t,e.charCodeAt(0)]);var nM={...Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[e.charCodeAt(0),t])),61:0,45:62,95:63};function rM(e){let t=e.replace(/=+$/,``),n=t.length,r=new Uint8Array(n+3);tM.encodeInto(t+`===`,r);for(let e=0,n=0;e>16,r[n+1]=t>>8&255,r[n+2]=t&255}let i=(n>>2)*3+(n%4&&n%4-1);return new Uint8Array(r.buffer,0,i)}Hf(),Qd(),pp();var iM=class extends P{constructor({majorType:e}){super(`Invalid CBOR major type: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidMajorTypeError`})}},aM=class extends P{constructor({additionalInfo:e}){super(`Invalid CBOR additional info: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidAdditionalInfoError`})}},oM=class extends P{constructor(){super(`64-bit integers are not supported in CBOR decoding.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.Unsupported64BitIntegerError`})}},sM=class extends P{constructor({tag:e}){super(`CBOR tagged data (tag ${e}) is not yet supported.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnsupportedTagError`})}},cM=class extends P{constructor({type:e}){super(`Invalid chunk type in indefinite-length ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidIndefiniteLengthChunkError`})}},lM=class extends P{constructor({value:e}){super(`Invalid CBOR simple value: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidSimpleValueError`})}},uM=class extends P{constructor(){super(`BigInt values are not supported in CBOR encoding.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnsupportedBigIntError`})}},dM=class extends P{constructor({token:e}){super(`Unexpected token: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnexpectedTokenError`})}},fM=class extends P{constructor({number:e}){super(`Number exceeds maximum safe integer (${2**53-1}): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.NumberTooLargeError`})}},pM=class extends P{constructor({size:e}){super(`String length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.StringTooLargeError`})}},mM=class extends P{constructor({size:e}){super(`Array length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ArrayTooLargeError`})}},hM=class extends P{constructor({size:e}){super(`Object size exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ObjectTooLargeError`})}},gM=class extends P{constructor({size:e}){super(`Byte string length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ByteStringTooLargeError`})}};function _M(e){if(e===void 0)return{length:1,encode:e=>e.pushUint8(247)};if(e===null)return{length:1,encode:e=>e.pushUint8(246)};if(typeof e==`boolean`)return{length:1,encode:t=>t.pushUint8(e?245:244)};if(typeof e==`number`)return _M.number(e);if(typeof e==`bigint`)throw new uM;if(typeof e==`string`)return _M.string(e);if(Array.isArray(e))return _M.array(e);if(e instanceof Uint8Array)return _M.byteString(e);if(e instanceof ArrayBuffer)return _M.byteString(new Uint8Array(e));if(ArrayBuffer.isView(e))return _M.byteString(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));if(e instanceof Map)return _M.map(e);if(typeof e==`object`)return _M.object(e);throw new dM({token:String(e)})}(function(e){function t(e){if(!Number.isSafeInteger(e)){let t=Math.fround(e);return Number.isNaN(e)||e===t?{length:5,encode(t){t.pushUint8(250),t.dataView.setFloat32(t.position,e,!1),t.position+=4}}:{length:9,encode(t){t.pushUint8(251),t.dataView.setFloat64(t.position,e,!1),t.position+=8}}}if(e>=0){if(e<=23)return{length:1,encode:t=>t.pushUint8(e)};if(e<=255)return{length:2,encode:t=>{t.pushUint8(24),t.pushUint8(e)}};if(e<=65535)return{length:3,encode:t=>{t.pushUint8(25),t.pushUint16(e)}};if(e<=4294967295)return{length:5,encode:t=>{t.pushUint8(26),t.pushUint32(e)}};throw new fM({number:e.toString(10)})}let t=-1-e;if(e>=-24)return{length:1,encode:e=>e.pushUint8(32+t)};if(t<=255)return{length:2,encode:e=>{e.pushUint8(56),e.pushUint8(t)}};if(t<=65535)return{length:3,encode:e=>{e.pushUint8(57),e.pushUint16(t)}};if(t<=4294967295)return{length:5,encode:e=>{e.pushUint8(58),e.pushUint32(t)}};throw new fM({number:e.toString(10)})}e.number=t;function n(e){let t=Sf(e),n=t.length;if(n<=23)return{length:1+n,encode(e){e.pushUint8(96+n),n>0&&e.pushBytes(t)}};if(n<=255)return{length:2+n,encode(e){e.pushUint8(120),e.pushUint8(n),e.pushBytes(t)}};if(n<=65535)return{length:3+n,encode(e){e.pushUint8(121),e.pushUint16(n),e.pushBytes(t)}};if(n<=4294967295)return{length:5+n,encode(e){e.pushUint8(122),e.pushUint32(n),e.pushBytes(t)}};throw new pM({size:n})}e.string=n;function r(t){let n=t.map(t=>e(t)),r=n.reduce((e,t)=>e+t.length,0),i=t.length;if(i<=23)return{length:1+r,encode(e){e.pushUint8(128+i);for(let t of n)t.encode(e)}};if(i<=255)return{length:2+r,encode(e){e.pushUint8(152),e.pushUint8(i);for(let t of n)t.encode(e)}};if(i<=65535)return{length:3+r,encode(e){e.pushUint8(153),e.pushUint16(i);for(let t of n)t.encode(e)}};if(i<=4294967295)return{length:5+r,encode(e){e.pushUint8(154),e.pushUint32(i);for(let t of n)t.encode(e)}};throw new mM({size:i})}e.array=r;function i(e){let t=e.byteLength;if(t<=23)return{length:1+t,encode(n){n.pushUint8(64+t),n.pushBytes(e)}};if(t<=255)return{length:2+t,encode(n){n.pushUint8(88),n.pushUint8(t),n.pushBytes(e)}};if(t<=65535)return{length:3+t,encode(n){n.pushUint8(89),n.pushUint16(t),n.pushBytes(e)}};if(t<=4294967295)return{length:5+t,encode(n){n.pushUint8(90),n.pushUint32(t),n.pushBytes(e)}};throw new gM({size:t})}e.byteString=i;function a(t){let n=Object.keys(t),r=n.map(n=>({key:e(n),value:e(t[n])})),i=r.reduce((e,t)=>e+t.key.length+t.value.length,0),a=n.length;if(a<=23)return{length:1+i,encode(e){e.pushUint8(160+a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=255)return{length:2+i,encode(e){e.pushUint8(184),e.pushUint8(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=65535)return{length:3+i,encode(e){e.pushUint8(185),e.pushUint16(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=4294967295)return{length:5+i,encode(e){e.pushUint8(186),e.pushUint32(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};throw new hM({size:a})}e.object=a;function o(t){let n=[];for(let[r,i]of t)n.push({key:e(r),value:e(i)});let r=n.reduce((e,t)=>e+t.key.length+t.value.length,0),i=t.size;if(i<=23)return{length:1+r,encode(e){e.pushUint8(160+i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=255)return{length:2+r,encode(e){e.pushUint8(184),e.pushUint8(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=65535)return{length:3+r,encode(e){e.pushUint8(185),e.pushUint16(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=4294967295)return{length:5+r,encode(e){e.pushUint8(186),e.pushUint32(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};throw new hM({size:i})}e.map=o})(_M||={});function vM(e){let t=e.readUint8(),n=t>>5,r=t&31;switch(n){case 0:return vM.readUnsignedInteger(e,r);case 1:return vM.readNegativeInteger(e,r);case 2:return vM.readByteString(e,r);case 3:return vM.readTextString(e,r);case 4:return vM.readArray(e,r);case 5:return vM.readMap(e,r);case 6:throw new sM({tag:r});case 7:return vM.readSimpleOrFloat(e,r);default:throw new iM({majorType:n})}}(function(e){function t(e,t){if(t<24)return t;if(t===24)return e.readUint8();if(t===25)return e.readUint16();if(t===26)return e.readUint32();throw t===27?new oM:new aM({additionalInfo:t})}function n(e,n){return t(e,n)}e.readUnsignedInteger=n;function r(e,n){return-1-t(e,n)}e.readNegativeInteger=r;function i(n,r){if(r===31){let t=[],r=0;for(;;){if(n.inspectUint8()===255){n.readUint8();break}let i=e(n);if(!(i instanceof Uint8Array))throw new cM({type:`byte string`});t.push(i),r+=i.length}let i=new Uint8Array(r),a=0;for(let e of t)i.set(e,a),a+=e.length;return i}let i=t(n,r);return n.readBytes(i)}e.readByteString=i;function a(n,r){if(r===31){let t=[];for(;;){if(n.inspectUint8()===255){n.readUint8();break}let r=e(n);if(typeof r!=`string`)throw new cM({type:`text string`});t.push(r)}return t.join(``)}let i=t(n,r);return jf(n.readBytes(i))}e.readTextString=a;function o(n,r){if(r===31){let t=[];for(;;){if(n.inspectUint8()===255){n.readUint8();break}t.push(e(n))}return t}let i=t(n,r),a=[];for(let t=0;t>15&1,n=e>>10&31,r=e&1023;if(n===0){if(r===0)return t?-0:0;let e=2**-14*(r/1024);return t?-e:e}if(n===31)return r===0?t?-1/0:1/0:NaN;let i=2**(n-15)*(1+r/1024);return t?-i:i}})(vM||={}),Hf(),Uint8Array.from([105,171,180,181,160,222,75,198,42,42,32,31,141,37,186,233]),pp(),Hf(),pp();function yM(e){let{challenge:t,metadata:n,origin:r,publicKey:i,rpId:a,signature:o}=e,{authenticatorData:s,clientDataJSON:c,userVerificationRequired:l}=n,u=xf(s);if(u.length<37||a!==void 0&&!Cf(u.slice(0,32),$h(Jf(a),{as:`Bytes`})))return!1;let d=u[32];if((d&1)!=1||l&&(d&4)!=4||(d&8)!=8&&(d&16)==16)return!1;let f=JSON.parse(c);return f.type!==`webauthn.get`||!f.challenge||qf(rM(f.challenge))!==t||r!==void 0&&!(Array.isArray(r)?r:[r]).includes(f.origin)?!1:eM({hash:!0,payload:vf(u,$h(Sf(c),{as:`Bytes`})),publicKey:i,signature:o})}function bM(e){return yM(e)}Qd(),pp(),gf();var xM=`0x01`,SM=`0x02`,CM=`0x03`,wM=`0x04`,TM=`0x7777777777777777777777777777777777777777777777777777777777777777`;function EM(e){let{signature:t,root:n}=e;return t.type===`keychain`?n?t.userAddress:EM({...e,signature:t.inner}):gg(DM(e))}function DM(e){let{payload:t,signature:n}=e;switch(n.type){case`secp256k1`:return K_({payload:t,signature:n.signature});case`p256`:case`webAuthn`:return n.publicKey;case`keychain`:return DM({payload:t,signature:n.inner})}}function OM(e){let t=e.endsWith(`7777777777777777777777777777777777777777777777777777777777777777`)?I(e,0,-Qf(TM)):e;if(Qf(t)===65){let e=C_(t);return x_(e),{signature:e,type:`secp256k1`}}let n=I(t,0,1),r=I(t,1),i=Qf(r);if(n===xM){if(i!==129)throw new IM({reason:`Invalid P256 signature envelope size: expected 129 bytes, got ${i} bytes`,serialized:t});return{publicKey:{prefix:4,x:ep(I(r,64,96)),y:ep(I(r,96,128))},prehash:tp(I(r,128,129))!==0,signature:{r:ep(I(r,0,32)),s:ep(I(r,32,64))},type:`p256`}}if(n===SM){if(i<128)throw new IM({reason:`Invalid WebAuthn signature envelope size: expected at least 128 bytes, got ${i} bytes`,serialized:t});let e=i-128,n=I(r,0,e),a,o;for(let t=37;t{let n=e.inner;return n.type===`p256`||n.type===`webAuthn`?{keyId:gg(n.publicKey)}:n.type===`secp256k1`&&t?.payload?{keyId:gg(K_({payload:t.payload,signature:n.signature}))}:{}})()}:{},type:n}}function AM(e){if(e.type===`secp256k1`)return{signature:D_(e),type:`secp256k1`};if(e.type===`p256`)return{prehash:e.preHash,publicKey:{prefix:4,x:ep(e.pubKeyX),y:ep(e.pubKeyY)},signature:{r:ep(e.r),s:ep(e.s)},type:`p256`};if(e.type===`webAuthn`){let t=e.webauthnData,n=Qf(t),r,i;for(let e=37;e{if(t.address)return t.address;if(t.publicKey)return gg(t.publicKey)})();if(!r)return!1;let i=kM(e);if(i.type===`secp256k1`)return r?q_({address:r,payload:n,signature:i.signature}):!1;if(i.type===`p256`)return _g(gg(i.publicKey),r)?eM({hash:i.prehash,publicKey:i.publicKey,payload:n,signature:i.signature}):!1;if(i.type===`webAuthn`)return _g(gg(i.publicKey),r)?bM({challenge:Gf(n),metadata:i.metadata,publicKey:i.publicKey,signature:i.signature}):!1;throw new LM(`Unable to verify signature envelope of type "${i.type}".`)}var FM=class extends P{constructor({envelope:e}){super(`Unable to coerce value (\`${mf(e)}\`) to a valid signature envelope.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.CoercionError`})}},IM=class extends P{constructor({reason:e,serialized:t}){super(`Unable to deserialize signature envelope: ${e}`,{metaMessages:[`Serialized: ${t}`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.InvalidSerializedError`})}},LM=class extends P{constructor(){super(...arguments),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.VerificationError`})}};Qd(),pp();function RM(e){return e.startsWith(`tempo`)?zM(e).address:e}function zM(e){if(!e.startsWith(`tempox`))throw new BM({address:e});let t=e.slice(6);return Uf(t,{strict:!0}),{address:mg(t)}}var BM=class extends P{constructor({address:e}){super(`Tempo address "${e}" has an invalid prefix. Expected "tempox".`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TempoAddress.InvalidPrefixError`})}};pp();function VM(e,t={}){if(typeof e.chainId==`string`)return HM(e);let n={...e,address:RM(e.address)};return t.signature?{...n,signature:t.signature}:n}function HM(e){let{address:t,chainId:n,nonce:r}=e,i=AM(e.signature);return{address:t,chainId:Number(n),nonce:BigInt(r),signature:i}}function UM(e){return e.map(e=>HM(e))}function WM(e){let[t,n,r,i]=e,a={address:n,chainId:t===`0x`?0:Number(t),nonce:r===`0x`?0n:BigInt(r)};return i&&(a.signature=OM(i)),VM(a)}function GM(e){let t=[];for(let n of e)t.push(WM(n));return t}function KM(e){let{address:t,chainId:n,nonce:r,signature:i}=e;return{address:t,chainId:F(n),nonce:F(r),signature:NM(i)}}function qM(e){return e.map(e=>KM(e))}function JM(e){let{address:t,chainId:n,nonce:r}=e,i=e.signature?MM(e.signature):void 0;return[n?F(n):`0x`,t,r?F(r):`0x`,...i?[i]:[]]}function YM(e){if(!e||e.length===0)return[];let t=[];for(let n of e)t.push(JM(n));return t}pp();function XM(e,t={}){if(`keyId`in e)return ZM(e);let n=e,r={...n,address:RM(n.address),...n.limits?{limits:n.limits.map(e=>({...e,token:RM(e.token)}))}:{}};return t.signature?{...r,signature:kM(t.signature)}:r}function ZM(e){let{chainId:t,keyId:n,expiry:r=0,limits:i,keyType:a}=e,o=AM(e.signature);return{address:n,chainId:t===`0x`?0n:ep(t),expiry:Number(r),limits:i?.map(e=>({token:e.token,limit:BigInt(e.limit)})),signature:o,type:a}}function QM(e){let[t,n]=e,[r,i,a,o,s]=t,c=(()=>{switch(i){case`0x`:case`0x00`:return`secp256k1`;case`0x01`:return`p256`;case`0x02`:return`webAuthn`;default:throw Error(`Invalid key type: ${i}`)}})(),l={address:a,expiry:o===void 0?void 0:iN(o),type:c,chainId:r===`0x`?0n:ep(r),...o===void 0?{}:{expiry:iN(o)},...s===void 0?{}:{limits:s.map(([e,t])=>({token:e,limit:rN(t)}))}};return n&&(l.signature=OM(n)),XM(l)}function $M(e){let{address:t,chainId:n,expiry:r,limits:i,type:a,signature:o}=e;return{chainId:n===0n?`0x`:F(n),expiry:typeof r==`number`?F(r):null,limits:i?.map(({token:e,limit:t})=>({token:e,limit:F(t)})),keyId:RM(t),signature:NM(o),keyType:a}}function eN(e){let{address:t,chainId:n,expiry:r,limits:i}=e,a=e.signature?MM(e.signature):void 0,o=(()=>{switch(e.type){case`secp256k1`:return`0x`;case`p256`:return`0x01`;case`webAuthn`:return`0x02`;default:throw Error(`Invalid key type: ${e.type}`)}})(),s=i?.map(e=>[e.token,tN(e.limit)]);return[[tN(n),o,t,typeof r==`number`||s?nN(r??0):void 0,s].filter(Boolean),...a?[a]:[]]}function tN(e){return e===0n?`0x`:F(e)}function nN(e){return e===0?`0x`:F(e)}function rN(e){return e===`0x`?0n:BigInt(e)}function iN(e){return e===`0x`?0:tp(e)}pp();var aN=`0x20c0`;function oN(e){if(typeof e==`string`){let t=RM(e);return pg(t),t}return Wf(aN,F(e,{size:18}))}var sN={legacy:`0x0`,eip2930:`0x1`,eip1559:`0x2`,eip4844:`0x3`,eip7702:`0x4`},cN={"0x0":`legacy`,"0x1":`eip2930`,"0x2":`eip1559`,"0x3":`eip4844`,"0x4":`eip7702`};function lN(e,t={}){if(!e)return null;let n=w_(e),r={...e,...n};return r.blockNumber=e.blockNumber?BigInt(e.blockNumber):null,r.data=e.input,r.gas=BigInt(e.gas??0n),r.nonce=BigInt(e.nonce??0n),r.transactionIndex=e.transactionIndex?Number(e.transactionIndex):null,r.value=BigInt(e.value??0n),e.authorizationList&&(r.authorizationList=H_(e.authorizationList)),e.chainId&&(r.chainId=Number(e.chainId)),e.gasPrice&&(r.gasPrice=BigInt(e.gasPrice)),e.maxFeePerBlobGas&&(r.maxFeePerBlobGas=BigInt(e.maxFeePerBlobGas)),e.maxFeePerGas&&(r.maxFeePerGas=BigInt(e.maxFeePerGas)),e.maxPriorityFeePerGas&&(r.maxPriorityFeePerGas=BigInt(e.maxPriorityFeePerGas)),e.type&&(r.type=cN[e.type]??e.type),n&&(r.v=N_(n.yParity)),r}var uN={...sN,tempo:`0x76`},dN={...cN,"0x76":`tempo`};function fN(e,t={}){if(!e)return null;let n=lN(e);return n.type=dN[e.type],e.aaAuthorizationList&&(n.authorizationList=UM(e.aaAuthorizationList),delete n.aaAuthorizationList),e.calls&&(n.calls=e.calls.map(e=>({to:e.to,value:e.value&&e.value!==`0x`?BigInt(e.value):void 0,data:e.input||e.data||`0x`}))),e.feeToken&&(n.feeToken=e.feeToken),e.nonceKey&&(n.nonceKey=BigInt(e.nonceKey)),e.signature&&(n.signature=AM(e.signature)),e.validAfter&&(n.validAfter=Number(e.validAfter)),e.validBefore&&(n.validBefore=Number(e.validBefore)),e.keyAuthorization&&(n.keyAuthorization=ZM(e.keyAuthorization)),e.feePayerSignature&&(n.feePayerSignature=D_(e.feePayerSignature),n.feePayerSignature.v=N_(n.feePayerSignature.yParity)),n}pp();function pN(e){let t={};return e.accessList!==void 0&&(t.accessList=e.accessList),e.authorizationList!==void 0&&(t.authorizationList=W_(e.authorizationList)),e.blobVersionedHashes!==void 0&&(t.blobVersionedHashes=e.blobVersionedHashes),e.blobs!==void 0&&(t.blobs=e.blobs),e.chainId!==void 0&&(t.chainId=F(e.chainId)),e.data===void 0?e.input!==void 0&&(t.data=e.input,t.input=e.input):(t.data=e.data,t.input=e.data),e.from!==void 0&&(t.from=e.from),e.gas!==void 0&&(t.gas=F(e.gas)),e.gasPrice!==void 0&&(t.gasPrice=F(e.gasPrice)),e.maxFeePerBlobGas!==void 0&&(t.maxFeePerBlobGas=F(e.maxFeePerBlobGas)),e.maxFeePerGas!==void 0&&(t.maxFeePerGas=F(e.maxFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(t.maxPriorityFeePerGas=F(e.maxPriorityFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(t.maxPriorityFeePerGas=F(e.maxPriorityFeePerGas)),e.nonce!==void 0&&(t.nonce=F(e.nonce)),e.to!==void 0&&(t.to=e.to),e.type!==void 0&&(t.type=sN[e.type]||e.type),e.value!==void 0&&(t.value=F(e.value)),t}pp();function mN(e){let t=pN({...e,authorizationList:void 0});e.authorizationList&&(t.authorizationList=qM(e.authorizationList)),e.calls&&(t.calls=e.calls.map(e=>({to:e.to?RM(e.to):e.to,value:e.value?F(e.value):`0x`,data:e.data??`0x`}))),e.feeToken!==void 0&&(t.feeToken=oN(e.feeToken)),e.keyAuthorization&&(t.keyAuthorization=$M(e.keyAuthorization)),e.validBefore!==void 0&&(t.validBefore=F(e.validBefore)),e.validAfter!==void 0&&(t.validAfter=F(e.validAfter));let n=(()=>{if(e.nonceKey===`random`)return Zf(6);if(typeof e.nonceKey==`bigint`)return F(e.nonceKey)})();return n&&(t.nonceKey=n),(e.calls!==void 0||e.feeToken!==void 0||e.keyAuthorization!==void 0||e.nonceKey!==void 0||e.validBefore!==void 0||e.validAfter!==void 0||e.type===`tempo`)&&(t.type=uN.tempo,delete t.data,delete t.input,delete t.to,delete t.value),t}Qd(),pp();function hN(e){let t=[];for(let n=0;neg(e)?e:$f(e))})}return t}function gN(e){if(!e||e.length===0)return[];let t=[];for(let{address:n,storageKeys:r}of e){for(let e=0;et===void 0?e:void 0).filter(Boolean);super(`Invalid serialized transaction of type "${n}" was provided.`,{metaMessages:[`Serialized Transaction: "${t}"`,r.length>0?`Missing Attributes: ${r.join(`, `)}`:``].filter(Boolean)}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TransactionEnvelope.InvalidSerializedError`})}},wN=class extends P{constructor({maxPriorityFeePerGas:e,maxFeePerGas:t}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${e?` = ${bN(e)} gwei`:``}) cannot be higher than the fee cap (\`maxFeePerGas\`${t?` = ${bN(t)} gwei`:``}).`].join(` +`)),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TransactionEnvelope.TipAboveFeeCapError`})}};Qd(),pp();var TN=`0x78`,EN=`0x76`,DN=`tempo`;function ON(e){let{calls:t,chainId:n,maxFeePerGas:r,maxPriorityFeePerGas:i,validBefore:a,validAfter:o}=e;if(!t||t.length===0)throw new FN;if(typeof a==`number`&&typeof o==`number`&&a<=o)throw new IN({validBefore:a,validAfter:o});if(t)for(let e of t)e.to&&pg(e.to,{strict:!1});if(n<=0)throw new SN({chainId:n});if(r&&BigInt(r)>2n**256n-1n)throw new xN({feeCap:r});if(i&&r&&i>r)throw new wN({maxFeePerGas:r,maxPriorityFeePerGas:i})}function kN(e){let t=u_(I(e,1)),[n,r,i,a,o,s,c,l,u,d,f,p,m,h,g]=t,_=Array.isArray(h)?h:void 0,v=_?g:h;if(!(t.length===13||t.length===14||t.length===15))throw new CN({attributes:{authorizationList:m,chainId:n,maxPriorityFeePerGas:r,maxFeePerGas:i,gas:a,calls:o,accessList:s,keyAuthorization:_,nonceKey:c,nonce:l,validBefore:u,validAfter:d,feeToken:f,feePayerSignatureOrSender:p,...t.length>12?{signature:v}:{}},serialized:e,type:DN});let y={chainId:Number(n),type:DN};rp(a)&&a!==`0x`&&(y.gas=BigInt(a)),rp(l)&&(y.nonce=l===`0x`?0n:BigInt(l)),rp(i)&&i!==`0x`&&(y.maxFeePerGas=BigInt(i)),rp(r)&&r!==`0x`&&(y.maxPriorityFeePerGas=BigInt(r)),rp(c)&&(y.nonceKey=c===`0x`?0n:BigInt(c)),rp(u)&&u!==`0x`&&(y.validBefore=Number(u)),rp(d)&&d!==`0x`&&(y.validAfter=Number(d)),rp(f)&&f!==`0x`&&(y.feeToken=f),o&&o!==`0x`&&(y.calls=o.map(e=>{let[t,n,r]=e,i={};return t&&t!==`0x`&&(i.to=t),n&&n!==`0x`&&(i.value=BigInt(n)),r&&r!==`0x`&&(i.data=r),i})),s?.length!==0&&s!==`0x`&&(y.accessList=hN(s)),m?.length!==0&&m!==`0x`&&(y.authorizationList=GM(m)),p!==`0x`&&p!==void 0&&(p===`0x00`||vg(p)?(y.feePayerSignature=null,vg(p)&&(y.from=p)):y.feePayerSignature=O_(p)),_&&(y.keyAuthorization=QM(_));let b=v?OM(v):void 0;if(b&&(y={...y,signature:b}),!y.from&&b)try{y.from=EM({payload:MN(AN(y)),signature:b,root:!0})}catch{}return ON(y),y}function AN(e,t={}){let{feePayerSignature:n,signature:r}=t,i=typeof e==`string`?kN(e):e;return i.from&&=RM(i.from),i.calls&&=i.calls.map(e=>({...e,...e.to?{to:RM(e.to)}:{}})),ON(i),{...i,...r?{signature:kM(r)}:{},...n?{feePayerSignature:T_(n)}:{},type:`tempo`}}function jN(e,t={}){let{accessList:n,authorizationList:r,calls:i,chainId:a,feeToken:o,gas:s,keyAuthorization:c,nonce:l,nonceKey:u,maxFeePerGas:d,maxPriorityFeePerGas:f,validBefore:p,validAfter:m}=e;ON(e);let h=gN(n),g=t.signature||e.signature,_=YM(r),v=i.map(e=>[e.to?RM(e.to):`0x`,e.value?F(e.value):`0x`,e.data??`0x`]),y=!1,b=(()=>{if(t.sender)return t.sender;if(t.format===`feePayer`&&g){let t=kM(g);if(t.type===`keychain`)return t.userAddress;if(t.type===`p256`||t.type===`webAuthn`)return gg(t.publicKey);if(t.type===`secp256k1`)return G_({payload:MN(AN(e)),signature:t.signature})}let n=t.feePayerSignature===void 0?e.feePayerSignature:t.feePayerSignature;return n===null?(y=!0,`0x00`):n?j_(n):`0x`})(),x=[F(a),f?F(f):`0x`,d?F(d):`0x`,s?F(s):`0x`,v,h,u?F(u):`0x`,l?F(l):`0x`,typeof p==`number`?F(p):`0x`,typeof m==`number`?F(m):`0x`,!y&&(typeof o==`bigint`||typeof o==`string`)?oN(o):`0x`,b,_,...c?[eN(c)]:[],...g?[MM(kM(g))]:[]];return Wf(t.format===`feePayer`?TN:EN,g_(x))}function MN(e,t={}){let n=NN(e,{presign:!0});return t.from?Qh(Wf(`0x04`,n,RM(t.from))):n}function NN(e,t={}){return Qh(jN({...e,...t.presign?{signature:void 0,...e.feePayerSignature===void 0?{}:{feePayerSignature:null}}:{}}))}function PN(e,t){let n=RM(t.sender);return Qh(jN({...e,signature:void 0},{sender:n,format:`feePayer`}))}var FN=class extends P{constructor(){super(`Calls list cannot be empty.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TxEnvelopeTempo.CallsEmptyError`})}},IN=class extends P{constructor({validBefore:e,validAfter:t}){super(`validBefore (${e}) must be greater than validAfter (${t}).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TxEnvelopeTempo.InvalidValidityWindowError`})}};pp();function LN(e){let t=e.account;return t?.keyType&&t.keyType!==`secp256k1`||e.calls!==void 0||e.feePayer!==void 0||e.feeToken!==void 0||e.keyAuthorization!==void 0||e.nonceKey!==void 0||e.signature!==void 0||e.validBefore!==void 0||e.validAfter!==void 0?`tempo`:e.type?e.type:Nd(e)}function RN(e){try{return LN(e)===`tempo`}catch{return!1}}async function zN(e,t){if(!RN(e)){if(t&&`type`in t&&t.type!==`secp256k1`)throw Error("Unsupported signature type. Expected `secp256k1` but got `"+t.type+"`.");if(t&&`type`in t){let{r:n,s:r,yParity:i}=t?.signature;return mh(e,{r:F(n,{size:32}),s:F(r,{size:32}),yParity:i})}return mh(e,t)}if(LN(e)===`tempo`)return BN(e,t);throw Error(`Unsupported transaction type`)}async function BN(e,t){let n=(()=>{if(e.signature)return e.signature;if(t&&`type`in t)return t;if(t)return kM({r:BigInt(t.r),s:BigInt(t.s),yParity:Number(t.yParity)})})(),{chainId:r,feePayer:i,feePayerSignature:a,nonce:o,...s}=e,c={...s,calls:s.calls?.length?s.calls:[{to:s.to||(!s.data||s.data===`0x`?`0x0000000000000000000000000000000000000000`:void 0),value:s.value,data:s.data}],chainId:Number(r),feePayerSignature:a?{r:BigInt(a.r),s:BigInt(a.s),yParity:Number(a.yParity)}:i?null:void 0,type:`tempo`,...o?{nonce:BigInt(o)}:{}};if(i===!0&&delete c.feeToken,n&&typeof e.feePayer==`object`){let t=AN(c,{signature:n}),r=PN(t,{sender:(()=>{if(e.from)return e.from;if(n.type===`secp256k1`)return G_({payload:MN(t),signature:n.signature});throw Error(`Unable to extract sender from transaction or signature.`)})()});return jN(t,{feePayerSignature:T_(await e.feePayer.sign({hash:r}))})}return i===!0?n?jN(c,{format:`feePayer`,sender:e.from,signature:n}):jN(c,{feePayerSignature:null}):jN({...c,...i?{feeToken:void 0}:{}},{feePayerSignature:void 0,signature:n})}pp(),oa(),Ku();function VN(e){if(!RN(e))return sd(e);let{feePayerSignature:t,gasPrice:n,nonce:r,...i}=fN(e);return{...i,accessList:i.accessList,feePayerSignature:t?{r:F(t.r,{size:32}),s:F(t.s,{size:32}),v:BigInt(t.v??27),yParity:t.yParity}:void 0,nonce:Number(r),typeHex:uN[i.type],type:i.type}}function HN(e){return Gm(e)}function UN(e,t){let n=e,r=n.account?aa(n.account):void 0;if(!RN(n))return Hu(e,t);t&&(n.calls=n.calls??[{to:e.to||(!e.data||e.data===`0x`?`0x0000000000000000000000000000000000000000`:void 0),value:e.value,data:e.data}]),n.feePayer===!0&&delete n.feeToken;let i=mN({...n,type:`tempo`});t===`estimateGas`&&(i.maxFeePerGas=void 0,i.maxPriorityFeePerGas=void 0),i.to=void 0,i.data=void 0,i.value=void 0;let[a,o]=(()=>{let e=r&&`keyType`in r?r.keyType:r?.source;return e?e===`webAuthn`?[`webAuthn`,`0x${`ff`.repeat(1400)}`]:[`p256`,`secp256k1`].includes(e)?[e,void 0]:[void 0,void 0]:[void 0,void 0]})(),s=r&&`accessKeyAddress`in r?r.accessKeyAddress:void 0;return{...i,...o?{keyData:o}:{},...s?{keyId:s}:{},...a?{keyType:a}:{},...n.feePayer?{feePayer:typeof n.feePayer==`object`?aa(n.feePayer):n.feePayer}:{}}}var WN=new Map;async function GN(e){WN.set(e,(WN.get(e)??0)+1),await Promise.resolve();let t=(WN.get(e)??0)>1;return queueMicrotask(()=>{let t=WN.get(e)??0;t<=1?WN.delete(e):WN.set(e,t-1)}),t}$u(),Ku();var KN=25,qN={blockTime:1e3,extendSchema:Dh(),formatters:{transaction:cd({exclude:[`aaAuthorizationList`],format:VN}),transactionReceipt:Km({format:HN}),transactionRequest:Gu({format:UN})},prepareTransactionRequest:[async(e,{phase:t})=>{let n=e;return t===`afterFillParameters`?(n.feePayer&&n.keyAuthorization?.signature.type===`webAuthn`&&(n.gas=(n.gas??0n)+20000n),n):(await(async()=>{if(n.nonceKey===`expiring`||n.feePayer&&n.nonceKey===void 0)return!0;let e=n.account?.address;return e&&n.nonceKey===void 0?await GN(e):!1})()?(n.nonceKey=Qu,n.nonce=0,n.validBefore===void 0&&(n.validBefore=Math.floor(Date.now()/1e3)+KN)):n.nonceKey!==void 0&&(n.nonce=typeof n.nonce==`number`?n.nonce:0),!n.feeToken&&n.chain?.feeToken&&(n.feeToken=n.chain.feeToken),n)},{runAt:[`beforeFillTransaction`,`afterFillParameters`]}],serializers:{transaction:((e,t)=>zN(e,t))},async verifyHash(e,t){let{address:n,hash:r,signature:i}=t;if(typeof i==`string`&&i.endsWith(`7777777777777777777777777777777777777777777777777777777777777777`)){let a=OM(i);if(a.type!==`keychain`){let i=await ih(e,{address:n,blockNumber:t.blockNumber,blockTag:t.blockTag});if(!i||i===`0xef01007702c00000000000000000000000000000000000`)return PM(a,{address:n,payload:r})}}return await T(e,vv,`verifyHash`)({...t,chain:null})}},JN=L({...qN,id:4217,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.tempo.xyz`}},name:`Tempo Mainnet`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.presto.tempo.xyz`],webSocket:[`wss://rpc.presto.tempo.xyz`]}}}),YN=L({...qN,id:42429,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.testnet.tempo.xyz`}},name:`Tempo Testnet (Andantino)`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.testnet.tempo.xyz`],webSocket:[`wss://rpc.testnet.tempo.xyz`]}}}),XN=L({...qN,id:31318,name:`Tempo Devnet`,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.devnet.tempo.xyz`}},nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.devnet.tempoxyz.dev`],webSocket:[`wss://rpc.devnet.tempoxyz.dev`]}}}),ZN=L({...qN,id:1337,name:`Tempo`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`http://localhost:8545`]}}}),QN=L({...qN,id:42431,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.moderato.tempo.xyz`}},name:`Tempo Testnet (Moderato)`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.moderato.tempo.xyz`],webSocket:[`wss://rpc.moderato.tempo.xyz`]}}}),$N=L({id:1559,name:`Tenet`,network:`tenet-mainnet`,nativeCurrency:{name:`TENET`,symbol:`TENET`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tenet.org`]}},blockExplorers:{default:{name:`TenetScan Mainnet`,url:`https://tenetscan.io`,apiUrl:`https://tenetscan.io/api`}},testnet:!1}),eP=L({id:752025,name:`Ternoa`,nativeCurrency:{name:`Capsule Coin`,symbol:`CAPS`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.zkevm.ternoa.network`]}},blockExplorers:{default:{name:`Ternoa Explorer`,url:`https://explorer-mainnet.zkevm.ternoa.network`}},testnet:!1}),tP=L({id:7,name:`ThaiChain`,nativeCurrency:{name:`TCH`,symbol:`TCH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.thaichain.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.thaichain.org`,apiUrl:`https://exp.thaichain.org/api`}},contracts:{multicall3:{address:`0x0DaD6130e832c21719C5CE3bae93454E16A84826`,blockCreated:4806386}},testnet:!1}),nP=L({id:8428,name:`THAT Mainnet`,nativeCurrency:{name:`THAT`,symbol:`THAT`,decimals:18},rpcUrls:{default:{http:[`https://api.thatchain.io/mainnet`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://that.blockscout.com`}},testnet:!1}),rP=L({id:361,name:`Theta Mainnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://explorer.thetatoken.org`}},testnet:!1}),iP=L({id:365,name:`Theta Testnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api-testnet.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://testnet-explorer.thetatoken.org`}},testnet:!0}),aP=L({id:108,name:`ThunderCore Mainnet`,nativeCurrency:{name:`TT`,symbol:`TT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.thundercore.com`]}},blockExplorers:{default:{name:`ThunderCore Explorer`,url:`https://explorer-mainnet.thundercore.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),oP=L({id:997,name:`5ireChain Thunder Testnet`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Thunder Explorer`,url:`https://testnet.5irescan.io/`}},testnet:!0}),sP=L({id:62092,name:`TikTrix Testnet`,nativeCurrency:{name:`tTTX`,symbol:`tTTX`,decimals:18},rpcUrls:{default:{http:[`https://tiktrix-rpc.xyz`]}},blockExplorers:{default:{name:`TikTrix Testnet Explorer`,url:`https://tiktrix.xyz`}},testnet:!0}),cP=L({id:6969,name:`Tomb Mainnet`,nativeCurrency:{name:`TOMB`,symbol:`TOMB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tombchain.com`]}},blockExplorers:{default:{name:`Tomb Explorer`,url:`https://tombscout.com`}},testnet:!1}),lP=L({...xy,id:61166,name:`Treasure`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.treasure.lol`],webSocket:[`wss://rpc.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Block Explorer`,url:`https://treasurescan.io`}},contracts:{multicall3:{address:`0x2e29fe39496a56856D8698bD43e1dF4D0CE6266a`,blockCreated:101}},testnet:!1}),uP=L({...xy,id:978658,name:`Treasure Topaz Testnet`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.topaz.treasure.lol`],webSocket:[`wss://rpc.topaz.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Topaz Block Explorer`,url:`https://topaz.treasurescan.io`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:108112}},testnet:!0}),dP=L({id:728126428,name:`Tron`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://tronscan.org`,apiUrl:`https://apilist.tronscanapi.com/api`}}}),fP=L({id:3448148188,name:`Tron Nile`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://nile.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://nile.tronscan.org`}},testnet:!0}),pP=L({id:2494104990,name:`Tron Shasta`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.shasta.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://shasta.tronscan.org`}},testnet:!0}),mP=L({id:8,name:`Ubiq Mainnet`,nativeCurrency:{name:`UBQ`,symbol:`UBQ`,decimals:18},rpcUrls:{default:{http:[`https://pyrus2.ubiqscan.io`]}},blockExplorers:{default:{name:`Ubiq Scan`,url:`https://ubiqscan.io`}},testnet:!1}),hP=L({id:19991,name:`Ultra EVM`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Explorer`,url:`https://evmexplorer.ultra.io`}}}),gP=L({id:18881,name:`Ultra EVM Testnet`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.test.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Testnet Explorer`,url:`https://evmexplorer.testnet.ultra.io`}},testnet:!0}),_P=L({id:1231,name:`Ultron Mainnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-rpc.net`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://ulxscan.com`}},testnet:!1}),vP=L({id:1230,name:`Ultron Testnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-dev.io`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://explorer.ultron-dev.io`}},testnet:!0}),yP=1,bP=L({...R,id:130,name:`Unichain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://mainnet.unichain.org/`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://uniscan.xyz`,apiUrl:`https://api.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[yP]:{address:`0x2F12d621a16e2d3285929C9996f478508951dFe4`}},portal:{[yP]:{address:`0x0bd48f6B86a26D3a217d0Fa6FfE2B491B956A7a2`}},l1StandardBridge:{[yP]:{address:`0x81014F44b0a345033bB2b3B21C7a1A308B35fEeA`}}},sourceId:yP}),xP=11155111,SP=L({...R,id:1301,name:`Unichain Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia.unichain.org`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://sepolia.uniscan.xyz`,apiUrl:`https://api-sepolia.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},portal:{[xP]:{address:`0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD`}},l1StandardBridge:{[xP]:{address:`0xea58fcA6849d79EAd1f26608855c2D6407d54Ce2`}},disputeGameFactory:{[xP]:{address:`0xeff73e5aa3B9AEC32c659Aa3E00444d20a84394b`}}},testnet:!0,sourceId:xP}),CP=L({id:8880,name:`Unique Mainnet`,nativeCurrency:{decimals:18,name:`UNQ`,symbol:`UNQ`},rpcUrls:{default:{http:[`https://rpc.unique.network`]}},blockExplorers:{default:{name:`Unique Subscan`,url:`https://unique.subscan.io/`}}}),wP=L({id:8882,name:`Opal Testnet`,nativeCurrency:{decimals:18,name:`OPL`,symbol:`OPL`},rpcUrls:{default:{http:[`https://rpc-opal.unique.network`]}},blockExplorers:{default:{name:`Opal Subscan`,url:`https://opal.subscan.io/`}},testnet:!0}),TP=L({id:8881,name:`Quartz Mainnet`,nativeCurrency:{decimals:18,name:`QTZ`,symbol:`QTZ`},rpcUrls:{default:{http:[`https://rpc-quartz.unique.network`]}},blockExplorers:{default:{name:`Quartz Subscan`,url:`https://quartz.subscan.io/`}}}),EP=L({id:18233,name:`Unreal`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.unreal-orbit.gelato.digital`]}},blockExplorers:{default:{name:`Unreal Explorer`,url:`https://unreal.blockscout.com`,apiUrl:`https://unreal.blockscout.com/api/v2`}},testnet:!0,contracts:{multicall3:{address:`0x8b6B0e60D8CD84898Ea8b981065A12F876eA5677`,blockCreated:1745}}}),DP=L({id:1480,name:`Vana`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.vana.org/`]}},blockExplorers:{default:{name:`Vana Block Explorer`,url:`https://vanascan.io`,apiUrl:`https://vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:716763}}}),OP=L({id:14800,name:`Vana Moksha Testnet`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.moksha.vana.org`]}},blockExplorers:{default:{name:`Vana Moksha Testnet`,url:`https://moksha.vanascan.io`,apiUrl:`https://moksha.vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:732283}},testnet:!0}),kP=L({id:2040,name:`Vanar Mainnet`,nativeCurrency:{name:`VANRY`,symbol:`VANRY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.vanarchain.com`]}},blockExplorers:{default:{name:`Vanar Mainnet Explorer`,url:`https://explorer.vanarchain.com/`}},testnet:!1}),AP=L({id:100009,name:`Vechain`,nativeCurrency:{name:`VeChain`,symbol:`VET`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.vechain.org`]}},blockExplorers:{default:{name:`Vechain Explorer`,url:`https://explore.vechain.org`},vechainStats:{name:`Vechain Stats`,url:`https://vechainstats.com`}}}),jP=L({id:106,name:`Velas EVM Mainnet`,nativeCurrency:{name:`VLX`,symbol:`VLX`,decimals:18},rpcUrls:{default:{http:[`https://evmexplorer.velas.com/rpc`]}},blockExplorers:{default:{name:`Velas Explorer`,url:`https://evmexplorer.velas.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:55883577}},testnet:!1}),MP=L({id:88,name:`Viction`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://vicscan.xyz`}},testnet:!1}),NP=L({id:89,name:`Viction Testnet`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://testnet.vicscan.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:12170179}},testnet:!0}),PP=L({id:888888,name:`Vision`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org`}},testnet:!1}),FP=L({id:666666,name:`Vision Testnet`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://vpioneer.infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org/?chain=vpioneer`}},testnet:!0}),IP=L({id:888,name:`Wanchain`,nativeCurrency:{name:`WANCHAIN`,symbol:`WAN`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:56891`,`https://gwan2-ssl.wandevs.org`]}},blockExplorers:{default:{name:`WanScan`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0xcDF6A1566e78EB4594c86Fe73Fcdc82429e97fbB`,blockCreated:25312390}}}),LP=L({id:999,name:`Wanchain Testnet`,nativeCurrency:{name:`WANCHAIN`,symbol:`WANt`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:46891`]}},blockExplorers:{default:{name:`WanScanTest`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0x11c89bF4496c39FB80535Ffb4c92715839CC5324`,blockCreated:24743448}},testnet:!0}),RP=L({id:9496,name:`WeaveVM Alphanet`,nativeCurrency:{name:`Testnet WeaveVM`,symbol:`tWVM`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.wvm.dev`]}},blockExplorers:{default:{name:`WeaveVM Alphanet Explorer`,url:`https://explorer.wvm.dev`}},testnet:!0}),zP=L({id:1111,name:`WEMIX`,network:`wemix-mainnet`,nativeCurrency:{name:`WEMIX`,symbol:`WEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://explorer.wemix.com`}}}),BP=L({id:1112,name:`WEMIX Testnet`,network:`wemix-testnet`,nativeCurrency:{name:`WEMIX`,symbol:`tWEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.test.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://testnet.wemixscan.com`,apiUrl:`https://testnet.wemixscan.com/api`}},testnet:!0}),VP=L({id:420420421,name:`Westend Asset Hub`,nativeCurrency:{decimals:18,name:`Westies`,symbol:`WND`},rpcUrls:{default:{http:[`https://westend-asset-hub-eth-rpc.polkadot.io`]}},blockExplorers:{default:{name:`subscan`,url:`https://westend-asset-hub-eth-explorer.parity.io`}},testnet:!0}),HP=L({testnet:!1,name:`Whitechain`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://explorer.whitechain.io`}},id:1875,rpcUrls:{default:{http:[`https://rpc.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25212237}}}),UP=L({testnet:!0,name:`Whitechain Testnet`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://testnet.whitechain.io`}},id:2625,rpcUrls:{default:{http:[`https://rpc-testnet.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`}}),WP=L({id:42070,name:`WMC Testnet`,nativeCurrency:{name:`WMTx`,symbol:`WMTx`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet-base.worldmobile.net`]}},blockExplorers:{default:{name:`WMC Explorer`,url:`https://explorer2-base-testnet.worldmobile.net`}},testnet:!0}),GP=1,KP=L({...R,id:480,name:`World Chain`,network:`worldchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan`,url:`https://worldscan.org`,apiUrl:`https://api.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-mainnet.explorer.alchemy.com`,apiUrl:`https://worldchain-mainnet.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[GP]:{address:`0x069c4c579671f8c120b1327a73217D01Ea2EC5ea`}},l2OutputOracle:{[GP]:{address:`0x19A6d1E9034596196295CF148509796978343c5D`}},portal:{[GP]:{address:`0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C`}},l1StandardBridge:{[GP]:{address:`0x470458C91978D2d929704489Ad730DC3E3001113`}}},testnet:!1,sourceId:GP}),qP=11155111,JP=L({...R,id:4801,name:`World Chain Sepolia`,network:`worldchain-sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan Sepolia`,url:`https://sepolia.worldscan.org`,apiUrl:`https://api-sepolia.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-sepolia.explorer.alchemy.com`,apiUrl:`https://worldchain-sepolia.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[qP]:{address:`0x8Ec1111f67Dad6b6A93B3F42DfBC92D81c98449A`}},l2OutputOracle:{[qP]:{address:`0xc8886f8BAb6Eaeb215aDB5f1c686BF699248300e`}},portal:{[qP]:{address:`0xFf6EBa109271fe6d4237EeeD4bAb1dD9A77dD1A4`}},l1StandardBridge:{[qP]:{address:`0xd7DF54b3989855eb66497301a4aAEc33Dbb3F8DE`}}},testnet:!0,sourceId:qP}),YP=L({id:103,name:`WorldLand Mainnet`,nativeCurrency:{decimals:18,name:`WLC`,symbol:`WLC`},rpcUrls:{default:{http:[`https://seoul.worldland.foundation`]}},blockExplorers:{default:{name:`WorldLand Scan`,url:`https://scan.worldland.foundation`}},testnet:!1}),XP=L({id:660279,name:`Xai Mainnet`,nativeCurrency:{name:`Xai`,symbol:`XAI`,decimals:18},rpcUrls:{default:{http:[`https://xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.xai-chain.net`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:222549}},testnet:!1}),ZP=L({id:37714555429,name:`Xai Testnet`,nativeCurrency:{name:`sXai`,symbol:`sXAI`,decimals:18},rpcUrls:{default:{http:[`https://testnet-v2.xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer-v2.xai-chain.net`}},testnet:!0}),QP=L({id:50,name:`XDC Network`,nativeCurrency:{decimals:18,name:`XDC`,symbol:`XDC`},rpcUrls:{default:{http:[`https://rpc.xdcrpc.com`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://xdcscan.com`}},contracts:{multicall3:{address:`0x0B1795ccA8E4eC4df02346a082df54D437F8D9aF`,blockCreated:75884020}}}),$P=L({id:51,name:`Apothem Network`,nativeCurrency:{decimals:18,name:`TXDC`,symbol:`TXDC`},rpcUrls:{default:{http:[`https://erpc.apothem.network`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://testnet.xdcscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59765389}}}),eF=L({id:1643,name:`XGR Mainnet`,nativeCurrency:{name:`XGR`,symbol:`XGR`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xgr.network`]}},blockExplorers:{default:{name:`XGR Explorer`,url:`https://explorer.xgr.network`}}}),tF=L({id:196,name:`X Layer Mainnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://xlayerrpc.okx.com`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer`,apiUrl:`https://www.oklink.com/api/v5/explorer/xlayer/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:47416}}}),nF=L({id:1952,name:`X1 Testnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://xlayertestrpc.okx.com`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer-test`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:624344}},testnet:!0}),rF=L({id:3721,name:`Xone Chain Mainnet`,nativeCurrency:{decimals:18,name:`XOC`,symbol:`XOC`},rpcUrls:{default:{http:[`https://rpc.xone.org`]}},blockExplorers:{default:{name:`Xone Mainnet Explorer`,url:`https://xonescan.com`,apiUrl:`http://api.xonescan.com/api`}},testnet:!1}),iF=L({id:33772211,name:`Xone Chain Testnet`,nativeCurrency:{decimals:18,name:`XOC`,symbol:`XOC`},rpcUrls:{default:{http:[`https://rpc-testnet.xone.org`,`https://rpc-testnet.xone.plus`,`https://rpc-testnet.knight.center`]}},blockExplorers:{default:{name:`Xone Testnet Explorer`,url:`https://testnet.xonescan.com`,apiUrl:`http://api.testnet.xonescan.com/api`}},testnet:!0}),aF=L({id:20250217,name:`Xphere Mainnet`,nativeCurrency:{decimals:18,name:`XP`,symbol:`XP`},rpcUrls:{default:{http:[`https://en-bkk.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xp.tamsa.io`}},testnet:!1}),oF=L({id:1998991,name:`Xphere Testnet`,nativeCurrency:{decimals:18,name:`XPT`,symbol:`XPT`},rpcUrls:{default:{http:[`http://testnet.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xpt.tamsa.io`}},testnet:!0}),sF=L({id:37,name:`CONX Chain`,nativeCurrency:{decimals:18,name:`XPLA`,symbol:`XPLA`},rpcUrls:{default:{http:[`https://dimension-evm-rpc.xpla.dev`]}},blockExplorers:{default:{name:`CONX Explorer`,url:`https://explorer.conx.xyz`}},testnet:!1}),cF=L({id:273,name:`XR One`,nativeCurrency:{decimals:18,name:`XR1`,symbol:`XR1`},rpcUrls:{default:{http:[`https://xr1.calderachain.xyz/http`],webSocket:[`wss://xr1.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr1.calderaexplorer.xyz`}},testnet:!1}),lF=L({id:144e4,name:`XRPL EVM`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xrplevm.org`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.xrplevm.org`,apiUrl:`https://explorer.xrplevm.org/api/v2`}},testnet:!1}),uF=L({id:1440002,name:`XRPL EVM Devnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xrplevm.org/`]},public:{http:[`https://rpc.xrplevm.org/`]}},blockExplorers:{default:{name:`XRPLEVM Devnet Explorer`,url:`https://explorer.xrplevm.org/`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:15237286}},testnet:!0}),dF=L({id:1449e3,name:`XRPL EVM Testnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.xrplevm.org`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.testnet.xrplevm.org`,apiUrl:`https://explorer.testnet.xrplevm.org/api/v2`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:492302}},testnet:!0}),fF=L({id:2730,name:`XR Sepolia`,nativeCurrency:{decimals:18,name:`tXR`,symbol:`tXR`},rpcUrls:{default:{http:[`https://xr-sepolia-testnet.rpc.caldera.xyz/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr-sepolia-testnet.explorer.caldera.xyz`}},testnet:!0}),pF=L({id:50005,name:`Yooldo Verse`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Explorer`,url:`https://explorer.yooldo-verse.xyz`}}}),mF=L({id:50006,name:`Yooldo Verse Testnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Testnet Explorer`,url:`https://explorer.testnet.yooldo-verse.xyz`}},testnet:!0}),hF=L({id:8408,name:`ZenChain Testnet`,nativeCurrency:{decimals:18,name:`ZTC`,symbol:`ZTC`},rpcUrls:{default:{http:[`https://zenchain-testnet.api.onfinality.io/public`],webSocket:[`wss://zenchain-testnet.api.onfinality.io/public-ws`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:230019}},blockExplorers:{default:{name:`Zentrace`,url:`https://zentrace.io`}},testnet:!0}),gF=L({id:383414847825,name:`Zeniq Mainnet`,nativeCurrency:{name:`ZENIQ`,symbol:`ZENIQ`,decimals:18},rpcUrls:{default:{http:[`https://api.zeniq.network`]}},blockExplorers:{default:{name:`Zeniq Explorer`,url:`https://zeniqscan.com`}},testnet:!1}),_F=L({id:543210,name:`Zero Network`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zerion.io/v1/zero`]}},blockExplorers:{default:{name:`Zero Network Explorer`,url:`https://explorer.zero.network`}},testnet:!1}),vF=L({id:7e3,name:`ZetaChain`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`ZETA`},rpcUrls:{default:{http:[`https://zetachain-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1632781}},blockExplorers:{default:{name:`ZetaScan`,url:`https://zetascan.com`}},testnet:!1}),yF=L({id:7001,name:`ZetaChain Athens Testnet`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`aZETA`},rpcUrls:{default:{http:[`https://zetachain-athens-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2715217}},blockExplorers:{default:{name:`ZetaScan`,url:`https://testnet.zetascan.com`}},testnet:!0}),bF=L({id:1337803,name:`Zhejiang`,nativeCurrency:{name:`Zhejiang Ether`,symbol:`ZhejETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zhejiang.ethpandaops.io`]}},blockExplorers:{default:{name:`Beaconchain`,url:`https://zhejiang.beaconcha.in`}},testnet:!0}),xF=L({id:32769,name:`Zilliqa`,network:`zilliqa`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.zilliqa.com`}},testnet:!1}),SF=L({id:33101,name:`Zilliqa Testnet`,network:`zilliqa-testnet`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://dev-api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.testnet.zilliqa.com`}},testnet:!0}),CF=1,wF=L({...R,id:48900,name:`Zircuit Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.zircuit.com`]}},blockExplorers:{default:{name:`Zircuit Explorer`,url:`https://explorer.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[CF]:{address:`0x92Ef6Af472b39F1b363da45E35530c24619245A4`}},portal:{[CF]:{address:`0x17bfAfA932d2e23Bd9B909Fd5B4D2e2a27043fb1`}},l1StandardBridge:{[CF]:{address:`0x386B76D9cA5F5Fb150B6BFB35CF5379B22B26dd8`}}},testnet:!1}),TF=11155111,EF=L({...R,id:48898,name:`Zircuit Garfield Testnet`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://garfield-testnet.zircuit.com/`]}},blockExplorers:{default:{name:`Zircuit Garfield Testnet Explorer`,url:`https://explorer.garfield-testnet.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[TF]:{address:`0xd69D3AC5CA686cCF94b258291772bc520FEAf211`}},portal:{[TF]:{address:`0x4E21A71Ac3F7607Da5c06153A17B1DD20E702c21`}},l1StandardBridge:{[TF]:{address:`0x87a7E2bCA9E35BA49282E832a28A6023904460D8`}}},testnet:!0}),DF=L({id:42766,name:`ZKFair Mainnet`,network:`zkfair-mainnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://scan.zkfair.io`,apiUrl:`https://scan.zkfair.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6090959}},testnet:!1}),OF=L({id:43851,name:`ZKFair Testnet`,network:`zkfair-testnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://testnet-rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://testnet-scan.zkfair.io`}},testnet:!0}),kF=L({id:810180,name:`zkLink Nova`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://explorer.zklink.io`}}}),AF=L({id:810181,name:`zkLink Nova Sepolia Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://sepolia.explorer.zklink.io`}}}),jF=L({...xy,blockTime:200,id:324,name:`ZKsync Era`,network:`zksync-era`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.era.zksync.io`],webSocket:[`wss://mainnet.era.zksync.io/ws`]}},blockExplorers:{default:{name:`ZKsync Explorer`,url:`https://explorer.zksync.io/`,apiUrl:`https://block-explorer-api.mainnet.zksync.io/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:3908235},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:45659388}}}),MF=L({...xy,id:260,name:`ZKsync InMemory Node`,network:`zksync-in-memory-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:8011`]}},testnet:!0}),NF=L({...xy,id:272,name:`ZKsync CLI Local Custom Hyperchain`,nativeCurrency:{name:`BAT`,symbol:`BAT`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15200`],webSocket:[`ws://localhost:15201`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),PF=L({...xy,id:270,name:`ZKsync CLI Local Hyperchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15100`],webSocket:[`ws://localhost:15101`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),FF=L({id:9,name:`ZKsync CLI Local Hyperchain L1`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15045`]}},blockExplorers:{default:{name:`Blockscout`,url:`http://localhost:15001/`,apiUrl:`http://localhost:15001/api/v2`}},testnet:!0}),IF=L({...xy,id:270,name:`ZKsync CLI Local Node`,network:`zksync-cli-local-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:3050`]}},testnet:!0}),LF=L({...xy,blockTime:200,id:300,name:`ZKsync Sepolia Testnet`,network:`zksync-sepolia-testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.era.zksync.dev`],webSocket:[`wss://sepolia.era.zksync.dev/ws`]}},blockExplorers:{default:{name:`ZKsync Explorer`,url:`https://sepolia.explorer.zksync.io/`,blockExplorerApi:`https://block-explorer-api.sepolia.zksync.dev/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:3855712}},testnet:!0}),RF=L({id:375,name:`zkXPLA Mainnet`,network:`zkxpla`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zkxpla.io`]}},blockExplorers:{default:{name:`zkXPLA Mainnet Explorer`,url:`https://explorer.zkxpla.io`,apiUrl:`https://explorer.zkxpla.io/api`}},testnet:!1}),zF=L({id:475,name:`zkXPLA Testnet`,network:`zkxpla-testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet-rpc.zkxpla.io`]}},blockExplorers:{default:{name:`zkXPLA Testnet Explorer`,url:`https://testnet-explorer.zkxpla.io`,apiUrl:`https://testnet-explorer.zkxpla.io/api`}},testnet:!0}),BF=1,VF=L({...R,id:7777777,name:`Zora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zora.energy`],webSocket:[`wss://rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.zora.energy`,apiUrl:`https://explorer.zora.energy/api`}},contracts:{...R.contracts,disputeGameFactory:{[BF]:{address:`0xB0F15106fa1e473Ddb39790f197275BC979Aa37e`}},l2OutputOracle:{[BF]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:5882},portal:{[BF]:{address:`0x1a0ad011913A150f69f6A19DF447A0CfD9551054`}},l1StandardBridge:{[BF]:{address:`0x3e2Ea9B92B7E48A52296fD261dc26fd995284631`}}},sourceId:BF}),HF=11155111,UF=L({...R,id:999999999,name:`Zora Sepolia`,network:`zora-sepolia`,nativeCurrency:{decimals:18,name:`Zora Sepolia`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zora.energy`],webSocket:[`wss://sepolia.rpc.zora.energy`]}},blockExplorers:{default:{name:`Zora Sepolia Explorer`,url:`https://sepolia.explorer.zora.energy/`,apiUrl:`https://sepolia.explorer.zora.energy/api`}},contracts:{...R.contracts,l2OutputOracle:{[HF]:{address:`0x2615B481Bd3E5A1C0C7Ca3Da1bdc663E8615Ade9`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83160},portal:{[HF]:{address:`0xeffE2C6cA9Ab797D418f0D91eA60807713f3536f`}},l1StandardBridge:{[HF]:{address:`0x5376f1D543dcbB5BD416c56C189e4cB7399fCcCB`}}},sourceId:HF,testnet:!0}),WF=5,GF=L({...R,id:999,name:`Zora Goerli Testnet`,nativeCurrency:{decimals:18,name:`Zora Goerli`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.rpc.zora.energy`],webSocket:[`wss://testnet.rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.explorer.zora.energy`,apiUrl:`https://testnet.explorer.zora.energy/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:189123},portal:{[WF]:{address:`0xDb9F51790365e7dc196e7D072728df39Be958ACe`}}},sourceId:WF,testnet:!0}),KF=c({abey:()=>ay,abstract:()=>Sy,abstractTestnet:()=>Cy,acala:()=>wy,acria:()=>Ty,adf:()=>Ey,adi:()=>Dy,agungTestnet:()=>Oy,aioz:()=>ky,alephZero:()=>Ay,alephZeroTestnet:()=>jy,alienx:()=>My,alienxHalTestnet:()=>Ny,alpenTestnet:()=>Py,ancient8:()=>Uy,ancient8Sepolia:()=>Gy,anvil:()=>Ky,apeChain:()=>qy,apexTestnet:()=>Jy,apollo:()=>Yy,arbitrum:()=>Xy,arbitrumGoerli:()=>Zy,arbitrumNova:()=>Qy,arbitrumSepolia:()=>$y,arcTestnet:()=>eb,arenaz:()=>tb,areonNetwork:()=>nb,areonNetworkTestnet:()=>rb,areum:()=>ib,artelaTestnet:()=>ab,arthera:()=>ob,artheraTestnet:()=>sb,assetChain:()=>cb,assetChainTestnet:()=>lb,astar:()=>ub,astarZkEVM:()=>db,astarZkyoto:()=>fb,atletaOlympia:()=>pb,aurora:()=>mb,auroraTestnet:()=>hb,auroria:()=>gb,autheoTestnet:()=>_b,avalanche:()=>vb,avalancheFuji:()=>yb,b3:()=>bb,b3Sepolia:()=>xb,bahamut:()=>Sb,base:()=>wb,baseGoerli:()=>Ob,basePreconf:()=>Tb,baseSepolia:()=>Ab,baseSepoliaPreconf:()=>jb,basecampTestnet:()=>Eb,beam:()=>Mb,beamTestnet:()=>Nb,bearNetworkChainMainnet:()=>Pb,bearNetworkChainTestnet:()=>Fb,berachain:()=>Ib,berachainBepolia:()=>Lb,berachainTestnet:()=>Rb,berachainTestnetbArtio:()=>zb,bevmMainnet:()=>Bb,bifrost:()=>Vb,birdlayer:()=>Hb,bitTorrent:()=>Yb,bitTorrentTestnet:()=>Xb,bitgert:()=>Ub,bitkub:()=>Wb,bitkubTestnet:()=>Gb,bitlayer:()=>Kb,bitlayerTestnet:()=>qb,bitrock:()=>Jb,blast:()=>Qb,blastSepolia:()=>$b,bob:()=>tx,bobSepolia:()=>ax,boba:()=>nx,bobaSepolia:()=>rx,boolBetaMainnet:()=>ox,botanix:()=>sx,botanixTestnet:()=>cx,bounceBit:()=>lx,bounceBitTestnet:()=>ux,bronos:()=>dx,bronosTestnet:()=>fx,bsc:()=>px,bscGreenfield:()=>mx,bscTestnet:()=>hx,bsquared:()=>gx,bsquaredTestnet:()=>_x,btr:()=>vx,btrTestnet:()=>yx,bxn:()=>bx,bxnTestnet:()=>xx,cannon:()=>Sx,canto:()=>Cx,celo:()=>Rx,celoAlfajores:()=>Bx,celoSepolia:()=>Hx,chang:()=>Ux,chiliz:()=>Wx,chips:()=>Gx,citrea:()=>Kx,citreaTestnet:()=>qx,classic:()=>Jx,codex:()=>Xx,codexTestnet:()=>Qx,coinbit:()=>$x,coinex:()=>eS,confluxESpace:()=>tS,confluxESpaceTestnet:()=>nS,coreDao:()=>rS,coreTestnet1:()=>iS,coreTestnet2:()=>aS,corn:()=>oS,cornTestnet:()=>sS,cpchain:()=>cS,crab:()=>lS,creatorTestnet:()=>uS,creditCoin3Devnet:()=>dS,creditCoin3Mainnet:()=>fS,creditCoin3Testnet:()=>pS,cronos:()=>mS,cronosTestnet:()=>hS,cronoszkEVM:()=>gS,cronoszkEVMTestnet:()=>_S,crossbell:()=>vS,crossfi:()=>yS,curtis:()=>bS,cyber:()=>xS,cyberTestnet:()=>SS,dailyNetwork:()=>CS,dailyNetworkTestnet:()=>wS,darwinia:()=>TS,datahavenTestnet:()=>ES,dbkchain:()=>DS,dchain:()=>OS,dchainTestnet:()=>kS,defichainEvm:()=>AS,defichainEvmTestnet:()=>jS,degen:()=>MS,dfk:()=>NS,diode:()=>PS,disChain:()=>FS,dodochainTestnet:()=>IS,dogechain:()=>LS,domaTestnet:()=>RS,donatuz:()=>zS,dosChain:()=>BS,dosChainTestnet:()=>VS,dreyerxMainnet:()=>HS,dreyerxTestnet:()=>US,dustboyIoT:()=>WS,dymension:()=>GS,edexa:()=>KS,edexaTestnet:()=>qS,edgeless:()=>JS,edgelessTestnet:()=>YS,edgeware:()=>XS,edgewareTestnet:()=>ZS,eduChain:()=>QS,eduChainTestnet:()=>$S,elastos:()=>eC,elastosTestnet:()=>tC,electroneum:()=>nC,electroneumTestnet:()=>rC,elysiumTestnet:()=>iC,energy:()=>aC,eni:()=>oC,eniTestnet:()=>sC,enuls:()=>cC,eon:()=>lC,eos:()=>uC,eosTestnet:()=>dC,eteria:()=>fC,etherlink:()=>pC,etherlinkShadownetTestnet:()=>mC,etherlinkTestnet:()=>hC,ethernity:()=>gC,etp:()=>_C,evmos:()=>vC,evmosTestnet:()=>yC,excelonMainnet:()=>bC,expanse:()=>xC,exsat:()=>SC,exsatTestnet:()=>CC,fantom:()=>wC,fantomSonicTestnet:()=>TC,fantomTestnet:()=>EC,fibo:()=>DC,filecoin:()=>OC,filecoinCalibration:()=>kC,filecoinHyperspace:()=>AC,fireChain:()=>iy,flame:()=>jC,flare:()=>MC,flareTestnet:()=>NC,flowMainnet:()=>PC,flowPreviewnet:()=>FC,flowTestnet:()=>IC,fluence:()=>LC,fluenceStage:()=>RC,fluenceTestnet:()=>zC,fluentDevnet:()=>BC,fluentTestnet:()=>VC,form:()=>UC,formTestnet:()=>KC,forma:()=>WC,formicarium:()=>lD,forta:()=>qC,foundry:()=>JC,fraxtal:()=>XC,fraxtalTestnet:()=>QC,funkiMainnet:()=>ew,funkiSepolia:()=>nw,fuse:()=>rw,fuseSparknet:()=>iw,fusion:()=>aw,fusionTestnet:()=>ow,garnet:()=>cw,gatechain:()=>lw,geist:()=>uw,genesys:()=>dw,giwaSepolia:()=>pw,giwaSepoliaPreconf:()=>mw,glideL1Protocol:()=>hw,glideL2Protocol:()=>gw,gnosis:()=>_w,gnosisChiado:()=>vw,goChain:()=>xw,goat:()=>yw,gobi:()=>bw,godwoken:()=>Sw,goerli:()=>Cw,graphite:()=>ww,graphiteTestnet:()=>Tw,gravity:()=>Ew,gunz:()=>Dw,guruNetwork:()=>Ow,guruTestnet:()=>kw,ham:()=>Aw,happychainTestnet:()=>jw,haqqMainnet:()=>Mw,haqqTestedge2:()=>Nw,hardhat:()=>Pw,harmonyOne:()=>Fw,hashkey:()=>Iw,hashkeyTestnet:()=>Lw,haustTestnet:()=>Rw,hedera:()=>zw,hederaPreviewnet:()=>Bw,hederaTestnet:()=>Vw,hela:()=>Hw,heliosTestnet:()=>Uw,hemi:()=>Ww,hemiSepolia:()=>Gw,henesys:()=>Kw,holesky:()=>qw,hoodi:()=>Jw,horizenTestnet:()=>Yw,hpb:()=>Xw,hpp:()=>Zw,hppSepolia:()=>Qw,huddle01Mainnet:()=>$w,huddle01Testnet:()=>eT,humanity:()=>tT,humanityTestnet:()=>nT,humanode:()=>rT,humanodeTestnet5:()=>iT,hychain:()=>aT,hychainTestnet:()=>oT,hyperEvm:()=>sT,hyperliquid:()=>sT,hyperliquidEvmTestnet:()=>cT,iSunCoin:()=>ET,icbNetwork:()=>lT,idchain:()=>uT,immutableZkEvm:()=>dT,immutableZkEvmTestnet:()=>fT,inEVM:()=>pT,initVerse:()=>mT,initVerseGenesis:()=>hT,injective:()=>gT,injectiveTestnet:()=>_T,ink:()=>yT,inkSepolia:()=>xT,iota:()=>ST,iotaTestnet:()=>CT,iotex:()=>wT,iotexTestnet:()=>TT,jasmyChain:()=>DT,jasmyChainTestnet:()=>OT,jbc:()=>kT,jbcTestnet:()=>AT,jocMainnet:()=>jT,jocTestnet:()=>MT,jovay:()=>NT,jovaySepolia:()=>PT,juneo:()=>FT,juneoBCH1Chain:()=>IT,juneoDAI1Chain:()=>LT,juneoDOGE1Chain:()=>RT,juneoEUR1Chain:()=>zT,juneoGLD1Chain:()=>BT,juneoLINK1Chain:()=>VT,juneoLTC1Chain:()=>HT,juneoSGD1Chain:()=>WT,juneoSocotraTestnet:()=>GT,juneoUSD1Chain:()=>KT,juneoUSDT1Chain:()=>qT,juneomBTC1Chain:()=>UT,kaia:()=>JT,kairos:()=>YT,kakarotSepolia:()=>XT,kakarotStarknetSepolia:()=>ZT,kardiaChain:()=>QT,karura:()=>$T,katana:()=>eE,kava:()=>tE,kavaTestnet:()=>nE,kcc:()=>rE,kii:()=>iE,kiiTestnetOro:()=>aE,kinto:()=>oE,klaytn:()=>sE,klaytnBaobab:()=>cE,koi:()=>lE,kroma:()=>uE,kromaSepolia:()=>dE,krown:()=>fE,l3x:()=>pE,l3xTestnet:()=>mE,lavita:()=>hE,lens:()=>gE,lensTestnet:()=>_E,lestnet:()=>vE,lightlinkPegasus:()=>yE,lightlinkPhoenix:()=>bE,linea:()=>wE,lineaGoerli:()=>TE,lineaSepolia:()=>EE,lineaTestnet:()=>DE,lisk:()=>kE,liskSepolia:()=>jE,loadAlphanet:()=>ME,localhost:()=>NE,loop:()=>PE,lukso:()=>FE,luksoTestnet:()=>IE,lumiaMainnet:()=>LE,lumiaTestnet:()=>RE,lumoz:()=>zE,lumozTestnet:()=>BE,luxeports:()=>VE,lycan:()=>HE,lyra:()=>UE,mainnet:()=>WE,mandala:()=>GE,manta:()=>KE,mantaSepoliaTestnet:()=>qE,mantaTestnet:()=>JE,mantle:()=>YE,mantleSepoliaTestnet:()=>XE,mantleTestnet:()=>ZE,mantraDuKongEVMTestnet:()=>QE,mantraEVM:()=>$E,mapProtocol:()=>eD,matchain:()=>tD,matchainTestnet:()=>nD,mchVerse:()=>rD,megaeth:()=>iD,megaethTestnet:()=>aD,mekong:()=>oD,meld:()=>sD,memecore:()=>cD,merlin:()=>uD,merlinErigonTestnet:()=>dD,metachain:()=>fD,metachainIstanbul:()=>pD,metadium:()=>mD,metalL2:()=>gD,meter:()=>_D,meterTestnet:()=>vD,metis:()=>yD,metisGoerli:()=>bD,metisSepolia:()=>xD,mev:()=>SD,mevTestnet:()=>CD,mint:()=>wD,mintSepoliaTestnet:()=>TD,mitosisTestnet:()=>ED,mode:()=>OD,modeTestnet:()=>AD,monad:()=>jD,monadTestnet:()=>MD,moonbaseAlpha:()=>ND,moonbeam:()=>PD,moonbeamDev:()=>FD,moonriver:()=>ID,morph:()=>LD,morphHolesky:()=>RD,morphSepolia:()=>zD,nahmii:()=>BD,nautilus:()=>VD,near:()=>HD,nearTestnet:()=>UD,neonDevnet:()=>WD,neonMainnet:()=>GD,neoxMainnet:()=>KD,neoxT4:()=>qD,newton:()=>JD,nexi:()=>YD,nexilix:()=>XD,nibiru:()=>ZD,nitrographTestnet:()=>QD,nomina:()=>$D,oasisTestnet:()=>eO,oasys:()=>tO,odysseyTestnet:()=>nO,okc:()=>rO,omax:()=>iO,omni:()=>aO,omniOmega:()=>oO,oneWorld:()=>sO,oortMainnetDev:()=>cO,opBNB:()=>uO,opBNBTestnet:()=>fO,openledger:()=>pO,optimism:()=>hO,optimismGoerli:()=>_O,optimismSepolia:()=>yO,optopia:()=>bO,optopiaTestnet:()=>xO,orderly:()=>SO,orderlySepolia:()=>CO,otimDevnet:()=>wO,palm:()=>TO,palmTestnet:()=>EO,paseoPassetHub:()=>DO,peaq:()=>OO,pgn:()=>AO,pgnTestnet:()=>MO,phoenix:()=>NO,planq:()=>PO,plasma:()=>FO,plasmaDevnet:()=>IO,plasmaTestnet:()=>LO,playfiAlbireo:()=>RO,plinga:()=>zO,plume:()=>BO,plumeDevnet:()=>VO,plumeMainnet:()=>HO,plumeSepolia:()=>UO,plumeTestnet:()=>WO,polterTestnet:()=>GO,polygon:()=>KO,polygonAmoy:()=>qO,polygonMumbai:()=>JO,polygonZkEvm:()=>YO,polygonZkEvmCardona:()=>XO,polygonZkEvmTestnet:()=>ZO,polynomial:()=>QO,polynomialSepolia:()=>$O,potos:()=>ek,potosTestnet:()=>tk,premiumBlockTestnet:()=>nk,pulsechain:()=>rk,pulsechainV4:()=>ik,pumpfiTestnet:()=>ak,pyrope:()=>sk,qMainnet:()=>lk,qTestnet:()=>uk,ql1:()=>ck,quai:()=>dk,quaiTestnet:()=>fk,reactiveTestnet:()=>pk,real:()=>mk,redbellyMainnet:()=>hk,redbellyTestnet:()=>gk,reddio:()=>_k,reddioSepolia:()=>vk,redstone:()=>bk,rei:()=>xk,reyaNetwork:()=>Sk,rise:()=>Ck,riseTestnet:()=>wk,rivalz:()=>Tk,rollux:()=>Ek,rolluxTestnet:()=>Dk,ronin:()=>Ok,root:()=>kk,rootPorcini:()=>Ak,rootstock:()=>jk,rootstockTestnet:()=>Mk,rss3:()=>Pk,rss3Sepolia:()=>Ik,saakuru:()=>Lk,saga:()=>Rk,saigon:()=>zk,sanko:()=>Bk,sapphire:()=>Vk,sapphireTestnet:()=>Hk,satoshiVM:()=>Uk,satoshiVMTestnet:()=>Wk,scroll:()=>Gk,scrollSepolia:()=>Kk,sei:()=>qk,seiTestnet:()=>Yk,seismicDevnet:()=>Jk,sepolia:()=>Xk,shape:()=>Qk,shapeSepolia:()=>eA,shardeum:()=>tA,shardeumSphinx:()=>nA,shibarium:()=>rA,shibariumTestnet:()=>iA,shiden:()=>aA,shimmer:()=>oA,shimmerTestnet:()=>sA,sidraChain:()=>cA,silentData:()=>lA,silicon:()=>uA,siliconSepolia:()=>dA,sixProtocol:()=>fA,skaleBase:()=>TA,skaleBaseSepoliaTestnet:()=>EA,skaleBlockBrawlers:()=>pA,skaleCalypso:()=>mA,skaleCalypsoTestnet:()=>hA,skaleCryptoBlades:()=>gA,skaleCryptoColosseum:()=>_A,skaleEuropa:()=>vA,skaleEuropaTestnet:()=>yA,skaleExorde:()=>bA,skaleHumanProtocol:()=>xA,skaleNebula:()=>SA,skaleNebulaTestnet:()=>CA,skaleRazor:()=>wA,skaleTitan:()=>DA,skaleTitanTestnet:()=>OA,sketchpad:()=>kA,snax:()=>jA,snaxTestnet:()=>NA,somnia:()=>PA,somniaTestnet:()=>FA,soneium:()=>LA,soneiumMinato:()=>zA,songbird:()=>BA,songbirdTestnet:()=>VA,sonic:()=>HA,sonicBlazeTestnet:()=>UA,sonicTestnet:()=>WA,sophon:()=>GA,sophonTestnet:()=>KA,sova:()=>qA,sovaSepolia:()=>JA,spicy:()=>YA,stable:()=>XA,stableTestnet:()=>ZA,statusNetworkSepolia:()=>QA,statusSepolia:()=>QA,step:()=>$A,story:()=>ej,storyAeneid:()=>tj,storyOdyssey:()=>nj,storyTestnet:()=>rj,stratis:()=>ij,subtensorEvm:()=>aj,superlumio:()=>oj,superposition:()=>sj,superseed:()=>lj,superseedSepolia:()=>dj,surgeTestnet:()=>fj,swan:()=>pj,swanProximaTestnet:()=>mj,swanSaturnTestnet:()=>hj,swellchain:()=>gj,swellchainTestnet:()=>_j,swissdlt:()=>vj,syscoin:()=>yj,syscoinTestnet:()=>bj,tac:()=>xj,tacSPB:()=>Sj,taiko:()=>Cj,taikoHekla:()=>wj,taikoHoodi:()=>Tj,taikoJolnir:()=>Ej,taikoKatla:()=>Dj,taikoTestnetSepolia:()=>Oj,taraxa:()=>kj,taraxaTestnet:()=>Aj,teaSepolia:()=>jj,telcoinTestnet:()=>Mj,telos:()=>Nj,telosTestnet:()=>Pj,tempo:()=>JN,tempoAndantino:()=>YN,tempoDevnet:()=>XN,tempoLocalnet:()=>ZN,tempoModerato:()=>QN,tempoTestnet:()=>YN,tenet:()=>$N,ternoa:()=>eP,thaiChain:()=>tP,that:()=>nP,theta:()=>rP,thetaTestnet:()=>iP,thunderCore:()=>aP,thunderTestnet:()=>oP,tiktrixTestnet:()=>sP,tomb:()=>cP,treasure:()=>lP,treasureTopaz:()=>uP,tron:()=>dP,tronNile:()=>fP,tronShasta:()=>pP,ubiq:()=>mP,ultra:()=>hP,ultraTestnet:()=>gP,ultron:()=>_P,ultronTestnet:()=>vP,unichain:()=>bP,unichainSepolia:()=>SP,unique:()=>CP,uniqueOpal:()=>wP,uniqueQuartz:()=>TP,unreal:()=>EP,vana:()=>DP,vanaMoksha:()=>OP,vanar:()=>kP,vechain:()=>AP,velas:()=>jP,viction:()=>MP,victionTestnet:()=>NP,vision:()=>PP,visionTestnet:()=>FP,wanchain:()=>IP,wanchainTestnet:()=>LP,weaveVMAlphanet:()=>RP,wemix:()=>zP,wemixTestnet:()=>BP,westendAssetHub:()=>VP,whitechain:()=>HP,whitechainTestnet:()=>UP,wmcTestnet:()=>WP,worldLand:()=>YP,worldchain:()=>KP,worldchainSepolia:()=>JP,x1Testnet:()=>nF,xLayer:()=>tF,xLayerTestnet:()=>nF,xai:()=>XP,xaiTestnet:()=>ZP,xdc:()=>QP,xdcTestnet:()=>$P,xgr:()=>eF,xoneMainnet:()=>rF,xoneTestnet:()=>iF,xphereMainnet:()=>aF,xphereTestnet:()=>oF,xpla:()=>sF,xrOne:()=>cF,xrSepolia:()=>fF,xrplevm:()=>lF,xrplevmDevnet:()=>uF,xrplevmTestnet:()=>dF,yooldoVerse:()=>pF,yooldoVerseTestnet:()=>mF,zenchainTestnet:()=>hF,zeniq:()=>gF,zeroG:()=>ey,zeroGGalileoTestnet:()=>ty,zeroGMainnet:()=>ny,zeroGTestnet:()=>ry,zeroNetwork:()=>_F,zetachain:()=>vF,zetachainAthensTestnet:()=>yF,zhejiang:()=>bF,zilliqa:()=>xF,zilliqaTestnet:()=>SF,zircuit:()=>wF,zircuitGarfieldTestnet:()=>EF,zkFair:()=>DF,zkFairTestnet:()=>OF,zkLinkNova:()=>kF,zkLinkNovaSepoliaTestnet:()=>AF,zkSync:()=>jF,zkSyncInMemoryNode:()=>MF,zkSyncLocalNode:()=>IF,zkSyncSepoliaTestnet:()=>LF,zkXPLA:()=>RF,zkXPLATestnet:()=>zF,zksync:()=>jF,zksyncInMemoryNode:()=>MF,zksyncLocalCustomHyperchain:()=>NF,zksyncLocalHyperchain:()=>PF,zksyncLocalHyperchainL1:()=>FF,zksyncLocalNode:()=>IF,zksyncSepoliaTestnet:()=>LF,zora:()=>VF,zoraSepolia:()=>UF,zoraTestnet:()=>GF}),qF=[wb,...Object.values(c({arbitrum:()=>Xy,arbitrumSepolia:()=>$y,base:()=>wb,baseSepolia:()=>Ab,berachain:()=>Ib,berachainBepolia:()=>Lb,bsc:()=>px,celo:()=>Rx,gnosis:()=>_w,hoodi:()=>Jw,katana:()=>eE,mainnet:()=>WE,optimism:()=>hO,optimismSepolia:()=>yO,polygon:()=>KO,sepolia:()=>Xk})).filter(e=>e&&e.id!==wb.id)],JF=Ky;({...JF}),{...JF};var YF=`#__bigint`;function XF(e,t){return JSON.parse(e,(e,n)=>{let r=n;return typeof r==`string`&&r.endsWith(YF)?BigInt(r.slice(0,-9)):typeof t==`function`?t(e,r):r})}function ZF(e,t,n){return JSON.stringify(e,(e,n)=>typeof t==`function`?t(e,n):typeof n==`bigint`?n.toString()+YF:n,n)}var QF=u(s(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=`~`;function i(){}Object.create&&(i.prototype=Object.create(null),new i().__proto__||(r=!1));function a(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function o(e,t,n,i,o){if(typeof n!=`function`)throw TypeError(`The listener must be a function`);var s=new a(n,i||e,o),c=r?r+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],s]:e._events[c].push(s):(e._events[c]=s,e._eventsCount++),e}function s(e,t){--e._eventsCount===0?e._events=new i:delete e._events[t]}function c(){this._events=new i,this._eventsCount=0}c.prototype.eventNames=function(){var e=[],t,i;if(this._eventsCount===0)return e;for(i in t=this._events)n.call(t,i)&&e.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e},c.prototype.listeners=function(e){var t=r?r+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,a=n.length,o=Array(a);i{if(n.cause instanceof e){if(n.cause.details)return n.cause.details;if(n.cause.shortMessage)return n.cause.shortMessage}return n.cause&&`details`in n.cause&&typeof n.cause.details==`string`?n.cause.details:n.cause?.message?n.cause.message:n.details})(),i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=n.docsOrigin??e.prototype.docsOrigin,o=`${a}${i??``}`,s=!!(n.version??e.prototype.showVersion),c=n.version??e.prototype.version,l=[t||`An error occurred.`,...n.metaMessages?[``,...n.metaMessages]:[],...r||i||s?[``,r?`Details: ${r}`:void 0,i?`See: ${o}`:void 0,s?`Version: ${c}`:void 0]:[]].filter(e=>typeof e==`string`).join(` +`);super(l,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsOrigin`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`showVersion`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.cause=n.cause,this.details=r,this.docs=o,this.docsOrigin=a,this.docsPath=i,this.shortMessage=t,this.showVersion=s,this.version=c}walk(e){return tI(this,e)}};Object.defineProperty(z,`defaultStaticOptions`,{enumerable:!0,configurable:!0,writable:!0,value:{docsOrigin:`https://oxlib.sh`,showVersion:!1,version:`ox@${eI()}`}}),z.setStaticOptions(z.defaultStaticOptions);function tI(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause?tI(e.cause,t):t?null:e}function nI(e,t={}){let{raw:n=!1}=t,r=e;if(n)return e;if(r.error)throw rI(r.error);return r.result}function rI(e){let t=e;if(t instanceof Error&&!(`code`in t))return new hI({cause:t,data:t,message:t.message,stack:t.stack});let{code:n}=t;return n===hI.code?new hI(t):n===aI.code?new aI(t):n===mI.code?new mI(t):n===fI.code?new fI(t):n===uI.code?new uI(t):n===pI.code?new pI(t):n===lI.code?new lI(t):n===gI.code?new gI(t):n===oI.code?new oI(t):n===sI.code?new sI(t):n===cI.code?new cI(t):n===dI.code?new dI(t):new hI({cause:t instanceof Error?t:void 0,data:t,message:t.message,stack:t instanceof Error?t.stack:void 0})}var iI=class extends Error{constructor(e){let{cause:t,code:n,message:r,data:i,stack:a}=e;super(r,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.BaseError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`stack`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=t,this.code=n,this.data=i,this.stack=a??``}},aI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Missing or invalid parameters.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidInputError`})}};Object.defineProperty(aI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3});var oI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Requested resource not found.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ResourceNotFoundError`})}};Object.defineProperty(oI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001});var sI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Requested resource not available.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ResourceUnavailableError`})}};Object.defineProperty(sI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002});var cI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Transaction creation failed.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.TransactionRejectedError`})}};Object.defineProperty(cI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003});var lI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Method is not implemented.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.MethodNotSupportedError`})}};Object.defineProperty(lI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004});var uI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Rate limit exceeded.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.LimitExceededError`})}};Object.defineProperty(uI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005});var dI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`JSON-RPC version not supported.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.VersionNotSupportedError`})}};Object.defineProperty(dI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006});var fI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Input is not a valid JSON-RPC request.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidRequestError`})}};Object.defineProperty(fI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600});var pI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Method does not exist.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.MethodNotFoundError`})}};Object.defineProperty(pI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601});var mI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Invalid method parameters.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidParamsError`})}};Object.defineProperty(mI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602});var hI=class e extends iI{constructor(t={}){super({cause:t.cause,code:e.code,data:t.data,message:t.message??`Internal JSON-RPC error.`,stack:t.stack}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InternalError`})}};Object.defineProperty(hI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603});var gI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Failed to parse JSON-RPC response.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ParseError`})}};Object.defineProperty(gI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700});var _I=class extends Error{constructor(e,t){super(t),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`ProviderRpcError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=e,this.details=t}},vI=class extends _I{constructor({message:e=`The user rejected the request.`}={}){super(4001,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UserRejectedRequestError`})}};Object.defineProperty(vI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001});var yI=class extends _I{constructor({message:e=`The requested method and/or account has not been authorized by the user.`}={}){super(4100,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnauthorizedError`})}};Object.defineProperty(yI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100});var bI=class extends _I{constructor({message:e=`The provider does not support the requested method.`}={}){super(4200,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedMethodError`})}};Object.defineProperty(bI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200});var xI=class extends _I{constructor({message:e=`The provider is disconnected from all chains.`}={}){super(4900,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.DisconnectedError`})}};Object.defineProperty(xI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900});var SI=class extends _I{constructor({message:e=`The provider is not connected to the requested chain.`}={}){super(4901,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.ChainDisconnectedError`})}};Object.defineProperty(SI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901});var CI=class extends _I{constructor({message:e=`An error occurred when attempting to switch chain.`}={}){super(4902,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.SwitchChainError`})}};Object.defineProperty(CI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902});var wI=class extends _I{constructor({message:e=`This Wallet does not support a capability that was not marked as optional.`}={}){super(5700,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedNonOptionalCapabilityError`})}};Object.defineProperty(wI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700});var TI=class extends _I{constructor({message:e=`This Wallet does not support the requested chain ID.`}={}){super(5710,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedChainIdError`})}};Object.defineProperty(TI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710});var EI=class extends _I{constructor({message:e=`There is already a bundle submitted with this ID.`}={}){super(5720,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.DuplicateIdError`})}};Object.defineProperty(EI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720});var DI=class extends _I{constructor({message:e=`This bundle id is unknown / has not been submitted.`}={}){super(5730,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnknownBundleIdError`})}};Object.defineProperty(DI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730});var OI=class extends _I{constructor({message:e=`The call bundle is too large for the Wallet to process.`}={}){super(5740,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.BundleTooLargeError`})}};Object.defineProperty(OI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740});var kI=class extends _I{constructor({message:e=`The Wallet can support atomicity after an upgrade, but the user rejected the upgrade.`}={}){super(5750,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.AtomicReadyWalletRejectedUpgradeError`})}};Object.defineProperty(kI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750});var AI=class extends _I{constructor({message:e=`The wallet does not support atomic execution but the request requires it.`}={}){super(5760,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.AtomicityNotSupportedError`})}};Object.defineProperty(AI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760});function jI(){let e=new QF.default;return{get eventNames(){return e.eventNames.bind(e)},get listenerCount(){return e.listenerCount.bind(e)},get listeners(){return e.listeners.bind(e)},addListener:e.addListener.bind(e),emit:e.emit.bind(e),off:e.off.bind(e),on:e.on.bind(e),once:e.once.bind(e),removeAllListeners:e.removeAllListeners.bind(e),removeListener:e.removeListener.bind(e)}}function MI(e,t={}){if(!e)throw new PI;return{...e,async request(t){try{let n=await e.request(t);return n&&typeof n==`object`&&`jsonrpc`in n?nI(n):n}catch(e){throw NI(e)}}}}function NI(e){let t=rI(e);if(t instanceof hI){if(!t.data)return t;let{code:e}=t.data;if(e===xI.code)return new xI(t);if(e===SI.code)return new SI(t);if(e===vI.code)return new vI(t);if(e===yI.code)return new yI(t);if(e===bI.code)return new bI(t);if(e===CI.code)return new CI(t);if(e===kI.code)return new kI(t);if(e===AI.code)return new AI(t);if(e===OI.code)return new OI(t);if(e===DI.code)return new DI(t);if(e===EI.code)return new EI(t);if(e===TI.code)return new TI(t);if(e===wI.code)return new wI(t)}return t}var PI=class extends z{constructor(){super("`provider` is undefined."),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.IsUndefinedError`})}};function FI(e,t){if(SL(e)>t)throw new OL({givenSize:SL(e),maxSize:t})}function II(e,t){if(typeof t==`number`&&t>0&&t>SL(e)-1)throw new kL({offset:t,position:`start`,size:SL(e)})}function LI(e,t,n){if(typeof t==`number`&&typeof n==`number`&&SL(e)!==n-t)throw new kL({offset:n,position:`end`,size:SL(e)})}var RI={zero:48,nine:57,A:65,F:70,a:97,f:102};function zI(e){if(e>=RI.zero&&e<=RI.nine)return e-RI.zero;if(e>=RI.A&&e<=RI.F)return e-(RI.A-10);if(e>=RI.a&&e<=RI.f)return e-(RI.a-10)}function BI(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;if(e.length>r)throw new AL({size:e.length,targetSize:r,type:`Bytes`});let i=new Uint8Array(r);for(let t=0;tt)throw new fL({givenSize:iL(e),maxSize:t})}function HI(e,t){if(typeof t==`number`&&t>0&&t>iL(e)-1)throw new pL({offset:t,position:`start`,size:iL(e)})}function UI(e,t,n){if(typeof t==`number`&&typeof n==`number`&&iL(e)!==n-t)throw new pL({offset:n,position:`end`,size:iL(e)})}function WI(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;let i=e.replace(`0x`,``);if(i.length>r*2)throw new mL({size:Math.ceil(i.length/2),targetSize:r,type:`Hex`});return`0x${i[n===`right`?`padEnd`:`padStart`](r*2,`0`)}`}function GI(e,t={}){let{dir:n=`left`}=t,r=e.replace(`0x`,``),i=0;for(let e=0;et.toString(16).padStart(2,`0`));function JI(e,t={}){let{strict:n=!1}=t;if(!e||typeof e!=`string`)throw new uL(e);if(n&&!/^0x[0-9a-fA-F]*$/.test(e)||!e.startsWith(`0x`))throw new dL(e)}function YI(...e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}function XI(e){return e instanceof Uint8Array?QI(e):Array.isArray(e)?QI(new Uint8Array(e)):e}function ZI(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(VI(n,t.size),tL(n,t.size)):n}function QI(e,t={}){let n=``;for(let t=0;ta||i>1n?r:r-a-1n}function sL(e,t={}){let{signed:n,size:r}=t;return Number(!n&&!r?e:oL(e,t))}function cL(e,t={}){let{strict:n=!1}=t;try{return JI(e,{strict:n}),!0}catch{return!1}}var lL=class extends z{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number \`${i}\` is not in safe${r?` ${r*8}-bit`:``}${n?` signed`:` unsigned`} integer range ${e?`(\`${t}\` to \`${e}\`)`:`(above \`${t}\`)`}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.IntegerOutOfRangeError`})}},uL=class extends z{constructor(e){super(`Value \`${typeof e==`object`?ZF(e):e}\` of type \`${typeof e}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexTypeError`})}},dL=class extends z{constructor(e){super(`Value \`${e}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexValueError`})}},fL=class extends z{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeOverflowError`})}},pL=class extends z{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SliceOffsetOutOfBoundsError`})}},mL=class extends z{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeExceedsPaddingSizeError`})}},hL=new TextEncoder;function gL(e){if(!(e instanceof Uint8Array)&&(!e||typeof e!=`object`||!(`BYTES_PER_ELEMENT`in e)||e.BYTES_PER_ELEMENT!==1||e.constructor.name!==`Uint8Array`))throw new DL(e)}function _L(e){return e instanceof Uint8Array?e:typeof e==`string`?yL(e):vL(e)}function vL(e){return e instanceof Uint8Array?e:new Uint8Array(e)}function yL(e,t={}){let{size:n}=t,r=e;n&&(VI(e,n),r=nL(e,n));let i=r.slice(2);i.length%2&&(i=`0${i}`);let a=i.length/2,o=new Uint8Array(a);for(let e=0,t=0;ethis.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}(8192)}.checksum;ti();function ML(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=$r(_L(e));return n===`Bytes`?r:QI(r)}function NL(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=yd(_L(e));return n===`Bytes`?r:QI(r)}function PL(e,t={}){let{compressed:n}=t,{prefix:r,x:i,y:a}=e;if(n===!1||typeof i==`bigint`&&typeof a==`bigint`){if(r!==4)throw new BL({prefix:r,cause:new HL});return}if(n===!0||typeof i==`bigint`&&a===void 0){if(r!==3&&r!==2)throw new BL({prefix:r,cause:new VL});return}throw new zL({publicKey:e})}function FL(e){let t=(()=>{if(cL(e))return LL(e);if(EL(e))return IL(e);let{prefix:t,x:n,y:r}=e;return typeof n==`bigint`&&typeof r==`bigint`?{prefix:t??4,x:n,y:r}:{prefix:t,x:n}})();return PL(t),t}function IL(e){return LL(QI(e))}function LL(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new UL({publicKey:e});return e.length===130?{prefix:4,x:BigInt(rL(e,0,32)),y:BigInt(rL(e,32,64))}:e.length===132?{prefix:Number(rL(e,0,1)),x:BigInt(rL(e,1,33)),y:BigInt(rL(e,33,65))}:{prefix:Number(rL(e,0,1)),x:BigInt(rL(e,1,33))}}function RL(e,t={}){PL(e);let{prefix:n,x:r,y:i}=e,{includePrefix:a=!0}=t;return YI(a?$I(n,{size:1}):`0x`,$I(r,{size:32}),typeof i==`bigint`?$I(i,{size:32}):`0x`)}var zL=class extends z{constructor({publicKey:e}){super(`Value \`${ZF(e)}\` is not a valid public key.`,{metaMessages:[`Public key must contain:`,"- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidError`})}},BL=class extends z{constructor({prefix:e,cause:t}){super(`Prefix "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidPrefixError`})}},VL=class extends z{constructor(){super(`Prefix must be 2 or 3 for compressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidCompressedPrefixError`})}},HL=class extends z{constructor(){super(`Prefix must be 4 for uncompressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidUncompressedPrefixError`})}},UL=class extends z{constructor({publicKey:e}){super(`Value \`${e}\` is an invalid public key size.`,{metaMessages:[`Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).`,`Received ${iL(XI(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidSerializedSizeError`})}},WL=/^0x[a-fA-F0-9]{40}$/;function GL(e,t={}){let{strict:n=!0}=t;if(!WL.test(e))throw new ZL({address:e,cause:new QL});if(n){if(e.toLowerCase()===e)return;if(KL(e)!==e)throw new ZL({address:e,cause:new $L})}}function KL(e){if(jL.has(e))return jL.get(e);GL(e,{strict:!1});let t=e.substring(2).toLowerCase(),n=ML(bL(t),{as:`Bytes`}),r=t.split(``);for(let e=0;e<40;e+=2)n[e>>1]>>4>=8&&r[e]&&(r[e]=r[e].toUpperCase()),(n[e>>1]&15)>=8&&r[e+1]&&(r[e+1]=r[e+1].toUpperCase());let i=`0x${r.join(``)}`;return jL.set(e,i),i}function qL(e,t={}){let{checksum:n=!1}=t;return GL(e),n?KL(e):e}function JL(e,t={}){return qL(`0x${ML(`0x${RL(e).slice(4)}`).substring(26)}`,t)}function YL(e,t){return GL(e,{strict:!1}),GL(t,{strict:!1}),e.toLowerCase()===t.toLowerCase()}function XL(e,t={}){let{strict:n=!0}=t??{};try{return GL(e,{strict:n}),!0}catch{return!1}}var ZL=class extends z{constructor({address:e,cause:t}){super(`Address "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidAddressError`})}},QL=class extends z{constructor(){super(`Address is not a 20 byte (40 hexadecimal character) value.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidInputError`})}},$L=class extends z{constructor(){super(`Address does not match its checksum counterpart.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidChecksumError`})}},eR=!1;uu();function tR(e){let{privateKey:t}=e;return FL(Zl.ProjectivePoint.fromPrivateKey(XI(t).slice(2)))}function nR(e={}){let{as:t=`Hex`}=e,n=Zl.utils.randomPrivateKey();return t===`Hex`?QI(n):n}function rR(e){return JL(iR(e))}function iR(e){let{payload:t,signature:n}=e,{r,s:i,yParity:a}=n;return FL(new Zl.Signature(BigInt(r),BigInt(i)).addRecoveryBit(a).recoverPublicKey(XI(t).substring(2)))}function aR(e){let{extraEntropy:t=eR,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=Zl.sign(_L(r),_L(i),{extraEntropy:typeof t==`boolean`?t:XI(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}var oR=/^(.*)\[([0-9]*)\]$/,sR=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,cR=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n));var lR=2n**256n-1n;function uR(e,t={}){let{recovered:n}=t;if(e.r===void 0||e.s===void 0||n&&e.yParity===void 0)throw new SR({signature:e});if(e.r<0n||e.r>lR)throw new CR({value:e.r});if(e.s<0n||e.s>lR)throw new wR({value:e.s});if(typeof e.yParity==`number`&&e.yParity!==0&&e.yParity!==1)throw new TR({value:e.yParity})}function dR(e){return fR(QI(e))}function fR(e){if(e.length!==130&&e.length!==132)throw new xR({signature:e});let t=BigInt(rL(e,0,32)),n=BigInt(rL(e,32,64)),r=(()=>{let t=Number(`0x${e.slice(130)}`);if(!Number.isNaN(t))try{return yR(t)}catch{throw new TR({value:t})}})();return r===void 0?{r:t,s:n}:{r:t,s:n,yParity:r}}function pR(e){if(e.r!==void 0&&e.s!==void 0)return mR(e)}function mR(e){let t=typeof e==`string`?fR(e):e instanceof Uint8Array?dR(e):typeof e.r==`string`?gR(e):e.v?hR(e):{r:e.r,s:e.s,...e.yParity===void 0?{}:{yParity:e.yParity}};return uR(t),t}function hR(e){return{r:e.r,s:e.s,yParity:yR(e.v)}}function gR(e){let t=(()=>{let t=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof t==`number`&&typeof n!=`number`&&(n=yR(t)),typeof n!=`number`)throw new TR({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function _R(e){uR(e);let t=e.r,n=e.s;return YI($I(t,{size:32}),$I(n,{size:32}),typeof e.yParity==`number`?$I(bR(e.yParity),{size:1}):`0x`)}function vR(e){let{r:t,s:n,yParity:r}=e;return[r?`0x01`:`0x`,t===0n?`0x`:aL($I(t)),n===0n?`0x`:aL($I(n))]}function yR(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2==0?1:0;throw new ER({value:e})}function bR(e){if(e===0)return 27;if(e===1)return 28;throw new TR({value:e})}var xR=class extends z{constructor({signature:e}){super(`Value \`${e}\` is an invalid signature size.`,{metaMessages:[`Expected: 64 bytes or 65 bytes.`,`Received ${iL(XI(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSerializedSizeError`})}},SR=class extends z{constructor({signature:e}){super(`Signature \`${ZF(e)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.MissingPropertiesError`})}},CR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidRError`})}},wR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSError`})}},TR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidYParityError`})}},ER=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidVError`})}};function DR({checksumAddress:e,parameters:t,values:n}){let r=[];for(let i=0;i0?YI(t,e):t}}if(o)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:YI(...s.map(({encoded:e})=>e))}}function MR(e,{type:t}){let[,n]=t.split(`bytes`),r=iL(e);if(!n){let t=e;return r%32!=0&&(t=nL(t,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:YI(tL($I(r,{size:32})),t)}}if(r!==Number.parseInt(n,10))throw new qR({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:nL(e)}}function NR(e){if(typeof e!=`boolean`)throw new z(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:tL(ZI(e))}}function PR(e,{signed:t,size:n}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function LR(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}var RR={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new HR({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new VR({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new BR({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new BR({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function zR(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(RR);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var BR=class extends z{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.NegativeOffsetError`})}},VR=class extends z{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.PositionOutOfBoundsError`})}},HR=class extends z{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.RecursiveReadLimitExceededError`})}};kt();function UR(e,t,n){let{checksumAddress:r=!1}=n??{};if(e.length!==t.length)throw new JR({expectedLength:e.length,givenLength:t.length});let i=kR(DR({checksumAddress:r,parameters:e,values:t}));return i.length===0?`0x`:i}function WR(e,t){if(e.length!==t.length)throw new JR({expectedLength:e.length,givenLength:t.length});let n=[];for(let r=0;r{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(cR);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[,e,t]=s;$I(o,{signed:e===`int`,size:Number.parseInt(t??``,10)/8})}if(r===`address`&&typeof o==`string`&&!XL(o))throw new ZL({address:o,cause:new QL});let c=r.match(sR);if(c){let[,e]=c;if(e&&iL(o)!==Number.parseInt(e,10))throw new iz({expectedSize:Number.parseInt(e,10),givenSize:iL(o)})}let l=i[r];l&&(fz(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new az({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new oz({primaryType:r,types:i})}function QR(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:ez(t),...e.types};ZR({domain:t,message:n,primaryType:r,types:i});let a=[`0x19`,`0x01`];return t&&a.push(nz({domain:t,types:i})),r!==`EIP712Domain`&&a.push(rz({data:n,primaryType:r,types:i})),YI(...a)}function $R(e){let{primaryType:t,types:n}=e,r=``,i=dz({primaryType:t,types:n});i.delete(t);let a=[t,...Array.from(i).sort()];for(let e of a)r+=`${e}(${(n[e]??[]).map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return r}function ez(e){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function tz(e){return ML(QR(e))}function nz(e){let{domain:t,types:n}=e;return rz({data:t,primaryType:`EIP712Domain`,types:{...n,EIP712Domain:n?.EIP712Domain||ez(t)}})}function rz(e){let{data:t,primaryType:n,types:r}=e;return ML(cz({data:t,primaryType:n,types:r}))}var iz=class extends z{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.BytesSizeMismatchError`})}},az=class extends z{constructor({domain:e}){super(`Invalid domain "${ZF(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidDomainError`})}},oz=class extends z{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{metaMessages:["Check that the primary type is a key in `types`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidPrimaryTypeError`})}},sz=class extends z{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidStructTypeError`})}};function cz(e){let{data:t,primaryType:n,types:r}=e,i=[{type:`bytes32`}],a=[lz({primaryType:n,types:r})];for(let e of r[n]??[]){let[n,o]=uz({types:r,name:e.name,type:e.type,value:t[e.name]});i.push(n),a.push(o)}return UR(i,a)}function lz(e){let{primaryType:t,types:n}=e;return ML(eL($R({primaryType:t,types:n})))}function uz(e){let{types:t,name:n,type:r,value:i}=e;if(t[r]!==void 0)return[{type:`bytes32`},ML(cz({data:i,primaryType:r,types:t}))];if(r===`bytes`)return i=`0x${(i.length%2?`0`:``)+i.slice(2)}`,[{type:`bytes32`},ML(i,{as:`Hex`})];if(r===`string`)return[{type:`bytes32`},ML(bL(i),{as:`Hex`})];if(r.lastIndexOf(`]`)===r.length-1){let e=r.slice(0,r.lastIndexOf(`[`)),a=i.map(r=>uz({name:n,type:e,types:t,value:r}));return[{type:`bytes32`},ML(UR(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:r},i]}function dz(e,t=new Set){let{primaryType:n,types:r}=e,i=n.match(/^\w*/u)?.[0];if(t.has(i)||r[i]===void 0)return t;t.add(i);for(let e of r[i])dz({primaryType:e.type,types:r},t);return t}function fz(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new sz({type:e})}gi(),Ei();function pz(e){if(typeof e==`string`){if(!Ci(e,{strict:!1}))throw new hi({address:e});return{address:e,type:`json-rpc`}}if(!Ci(e.address,{strict:!1}))throw new hi({address:e.address});return{address:e.address,nonceManager:e.nonceManager,sign:e.sign,signAuthorization:e.signAuthorization,signMessage:e.signMessage,signTransaction:e.signTransaction,signTypedData:e.signTypedData,source:`custom`,type:`local`}}function mz(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;ohz(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>hz(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function gz(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return gz(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&XL(n[r],{strict:!1}))return o}}kt();function _z(e,t={}){let{prepare:n=!0}=t,r=Array.isArray(e)||typeof e==`string`?Tt(e):e;return{...r,...n?{hash:xz(r)}:{}}}function vz(e,t,n){let{args:r=[],prepare:i=!0}=n??{},a=cL(t,{strict:!1}),o=e.filter(e=>a?e.type===`function`||e.type===`error`?yz(e)===rL(t,0,4):e.type===`event`?xz(e)===t:!1:`name`in e&&e.name===t);if(o.length===0)throw new Cz({name:t});if(o.length===1)return{...o[0],...i?{hash:xz(o[0])}:{}};let s;for(let e of o)if(`inputs`in e){if(!r||r.length===0){if(!e.inputs||e.inputs.length===0)return{...e,...i?{hash:xz(e)}:{}};continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===r.length&&r.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?hz(t,r):!1})){if(s&&`inputs`in s&&s.inputs){let t=gz(e.inputs,s.inputs,r);if(t)throw new Sz({abiItem:e,type:t[0]},{abiItem:s,type:t[1]})}s=e}}let c=(()=>{if(s)return s;let[e,...t]=o;return{...e,overloads:t}})();if(!c)throw new Cz({name:t});return{...c,...i?{hash:xz(c)}:{}}}function yz(...e){return rL(xz((()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})()),0,4)}function bz(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})();return mz(typeof t==`string`?t:ue(t))}function xz(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})();return typeof t!=`string`&&`hash`in t&&t.hash?t.hash:ML(eL(bz(t)))}var Sz=class extends z{constructor(e,t){super(`Found ambiguous types in overloaded ABI Items.`,{metaMessages:[`\`${e.type}\` in \`${mz(ue(e.abiItem))}\`, and`,`\`${t.type}\` in \`${mz(ue(t.abiItem))}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.AmbiguityError`})}},Cz=class extends z{constructor({name:e,data:t,type:n=`item`}){let r=e?` with name "${e}"`:t?` with data "${t}"`:``;super(`ABI ${n}${r} not found.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.NotFoundError`})}};function wz(...e){let[t,n=[]]=(()=>{if(Array.isArray(e[0])){let[t,n,r]=e;return[Tz(t,n,{args:r}),r]}let[t,n]=e;return[t,n]})(),{overloads:r}=t,i=r?Tz([t,...r],t.name,{args:n}):t,a=Ez(i),o=n.length>0?UR(i.inputs,n):void 0;return o?YI(a,o):a}function Tz(e,t,n){let r=vz(e,t,n);if(r.type!==`function`)throw new Cz({name:t,type:`function`});return r}function Ez(e){return yz(e)}function Dz(e){let{privateKey:t}=e;return FL($j.ProjectivePoint.fromPrivateKey(typeof t==`string`?t.slice(2):QI(t).slice(2)))}function Oz(e={}){let{as:t=`Hex`}=e,n=$j.utils.randomPrivateKey();return t===`Hex`?QI(n):n}function kz(e){let{extraEntropy:t=eR,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=$j.sign(r instanceof Uint8Array?r:yL(r),i instanceof Uint8Array?i:yL(i),{extraEntropy:typeof t==`boolean`?t:XI(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}function Az(e,t=0){if(!/^(-?)([0-9]*)\.?([0-9]*)$/.test(e))throw new jz({value:e});let[n=``,r=`0`]=e.split(`.`),i=n.startsWith(`-`);if(i&&(n=n.slice(1)),r=r.replace(/(0+)$/,``),t===0)Math.round(Number(`.${r}`))===1&&(n=`${BigInt(n)+1n}`),r=``;else if(r.length>t){let[e,i,a]=[r.slice(0,t-1),r.slice(t-1,t),r.slice(t)],o=Math.round(Number(`${i}.${a}`));r=o>9?`${BigInt(e)+BigInt(1)}0`.padStart(e.length+1,`0`):`${e}${o}`,r.length>t&&(r=r.slice(1),n=`${BigInt(n)+1n}`),r=r.slice(0,t)}else r=r.padEnd(t,`0`);return BigInt(`${i?`-`:``}${n}${r}`)}var jz=class extends z{constructor({value:e}){super(`Value \`${e}\` is not a valid decimal number.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Value.InvalidDecimalNumberError`})}},Mz=new TextEncoder,Nz=new TextDecoder,Pz=Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[t,e.charCodeAt(0)])),Fz={...Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[e.charCodeAt(0),t])),61:0,45:62,95:63};function Iz(e,t={}){let{pad:n=!0,url:r=!1}=t,i=new Uint8Array(Math.ceil(e.length/3)*4);for(let t=0,n=0;n>18],i[t+1]=Pz[r>>12&63],i[t+2]=Pz[r>>6&63],i[t+3]=Pz[r&63]}let a=e.length%3,o=Math.floor(e.length/3)*4+(a&&a+1),s=Nz.decode(new Uint8Array(i.buffer,0,o));return n&&a===1&&(s+=`==`),n&&a===2&&(s+=`=`),r&&(s=s.replaceAll(`+`,`-`).replaceAll(`/`,`_`)),s}function Lz(e,t={}){return Iz(yL(e),t)}function Rz(e){let t=e.replace(/=+$/,``),n=t.length,r=new Uint8Array(n+3);Mz.encodeInto(t+`===`,r);for(let e=0,n=0;e>16,r[n+1]=t>>8&255,r[n+2]=t&255}let i=(n>>2)*3+(n%4&&n%4-1);return new Uint8Array(r.buffer,0,i)}function zz(e){let t=e[4]===0?5:4,n=t+32,r=e[n+2]===0?n+3:n+2,i=BigInt(QI(e.slice(t,n))),a=BigInt(QI(e.slice(r)));return{r:i,s:a>Qj.CURVE.n/2n?Qj.CURVE.n-a:a}}async function Bz(e){try{let t=e.getPublicKey();if(!t)throw new Yz;let n=new Uint8Array(t),r=await crypto.subtle.importKey(`spki`,new Uint8Array(n),{name:`ECDSA`,namedCurve:`P-256`,hash:`SHA-256`},!0,[`verify`]);return FL(new Uint8Array(await crypto.subtle.exportKey(`raw`,r)))}catch(t){if(t.message!==`Permission denied to access object`)throw t;let n=new Uint8Array(e.attestationObject),r=e=>{let t=new Uint8Array([e,88,32]);for(let e=0;en[e+r]===t))return e+t.length;throw new Yz},i=r(33),a=r(34);return FL(new Uint8Array([4,...n.slice(i,i+32),...n.slice(a,a+32)]))}}var Vz=Uint8Array.from([105,171,180,181,160,222,75,198,42,42,32,31,141,37,186,233]);async function Hz(e){let{createFn:t=window.navigator.credentials.create.bind(window.navigator.credentials),...n}=e,r=Gz(n);try{let e=await t(r);if(!e)throw new Yz;let n=e.response,i=await Bz(n);return{id:e.id,publicKey:i,raw:e}}catch(e){throw new Yz({cause:e})}}function Uz(e={}){let{flag:t=5,rpId:n=window.location.hostname,signCount:r=0}=e;return YI(NL(eL(n)),$I(t,{size:1}),$I(r,{size:4}))}function Wz(e){let{challenge:t,crossOrigin:n=!1,extraClientData:r,origin:i=window.location.origin}=e;return JSON.stringify({type:`webauthn.get`,challenge:Lz(t,{url:!0,pad:!1}),origin:i,crossOrigin:n,...r})}function Gz(e){let{attestation:t=`none`,authenticatorSelection:n={residentKey:`preferred`,requireResidentKey:!1,userVerification:`required`},challenge:r=Vz,excludeCredentialIds:i,extensions:a,name:o,rp:s={id:window.location.hostname,name:window.document.title},user:c}=e,l=c?.name??o;return{publicKey:{attestation:t,authenticatorSelection:n,challenge:typeof r==`string`?yL(r):r,...i?{excludeCredentials:i?.map(e=>({id:Rz(e),type:`public-key`}))}:{},pubKeyCredParams:[{type:`public-key`,alg:-7}],...a&&{extensions:a},rp:s,user:{id:c?.id??ML(bL(l),{as:`Bytes`}),name:l,displayName:c?.displayName??l}}}}function Kz(e){let{credentialId:t,challenge:n,extensions:r,rpId:i=window.location.hostname,userVerification:a=`required`}=e;return{publicKey:{...t?{allowCredentials:Array.isArray(t)?t.map(e=>({id:Rz(e),type:`public-key`})):[{id:Rz(t),type:`public-key`}]}:{},challenge:yL(n),...r&&{extensions:r},rpId:i,userVerification:a}}}function qz(e){let{challenge:t,crossOrigin:n,extraClientData:r,flag:i,origin:a,rpId:o,signCount:s,userVerification:c=`required`}=e,l=Uz({flag:i,rpId:o,signCount:s}),u=Wz({challenge:t,crossOrigin:n,extraClientData:r,origin:a}),d=NL(eL(u));return{metadata:{authenticatorData:l,clientDataJSON:u,challengeIndex:u.indexOf(`"challenge"`),typeIndex:u.indexOf(`"type"`),userVerificationRequired:c===`required`},payload:YI(l,d)}}async function Jz(e){let{getFn:t=window.navigator.credentials.get.bind(window.navigator.credentials),...n}=e,r=Kz(n);try{let e=await t(r);if(!e)throw new Xz;let n=e.response,i=String.fromCharCode(...new Uint8Array(n.clientDataJSON)),a=i.indexOf(`"challenge"`),o=i.indexOf(`"type"`),s=zz(new Uint8Array(n.signature));return{metadata:{authenticatorData:QI(new Uint8Array(n.authenticatorData)),clientDataJSON:i,challengeIndex:a,typeIndex:o,userVerificationRequired:r.publicKey.userVerification===`required`},signature:s,raw:e}}catch(e){throw new Xz({cause:e})}}var Yz=class extends z{constructor({cause:e}={}){super(`Failed to create credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialCreationFailedError`})}},Xz=class extends z{constructor({cause:e}={}){super(`Failed to request credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialRequestFailedError`})}};async function Zz(e={}){let{extractable:t=!1}=e,n=await globalThis.crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},t,[`sign`,`verify`]),r=await globalThis.crypto.subtle.exportKey(`raw`,n.publicKey),i=FL(new Uint8Array(r));return{privateKey:n.privateKey,publicKey:i}}async function Qz(e){let{payload:t,privateKey:n}=e,r=await globalThis.crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},n,_L(t)),i=vL(new Uint8Array(r)),a=wL(CL(i,0,32)),o=wL(CL(i,32,64));return o>Qj.CURVE.n/2n&&(o=Qj.CURVE.n-o),{r:a,s:o}}var $z=`0x32323232`,eB={p256:`p256`,secp256k1:`secp256k1`,webauthnp256:`webauthn-p256`},tB={admin:`admin`,normal:`session`},nB={0:`minute`,1:`hour`,2:`day`,3:`week`,4:`month`,5:`year`},rB={address:`secp256k1`,p256:`p256`,secp256k1:`secp256k1`,"webauthn-p256":`webauthnp256`},iB={admin:`admin`,session:`normal`},aB={address:2,p256:0,secp256k1:2,"webauthn-p256":1},oB={day:2,hour:1,minute:0,month:4,week:3,year:5};function sB(e={}){let t=Oz();return fB({...e,privateKey:t})}async function cB(e){let{createFn:t,label:n,rpId:r,userId:i}=e,a=await Hz({authenticatorSelection:{requireResidentKey:!0,residentKey:`required`,userVerification:`required`},createFn:t,extensions:{credProps:!0},rp:r?{id:r,name:r}:void 0,user:{displayName:n,id:new Uint8Array(i??bL(n)),name:n}});return mB({...e,credential:{id:a.id,publicKey:a.publicKey},id:i?TL(i):RL(a.publicKey,{includePrefix:!1})})}function lB(e={}){let t=Oz();return hB({...e,privateKey:t})}async function uB(e={}){let t=await Zz();return gB({...e,keyPair:t})}function dB(e,t={}){let{chainId:n=e.chainId}=t,{expiry:r=0,id:i,prehash:a=!1,role:o=`admin`,type:s}=e,c=(()=>{let t=e.publicKey;return t===`0x`?t:s===`secp256k1`||s===`address`?iL(t)===20||oL(rL(t,0,12))===0n?rL(t,-20):JL(LL(t)):t})();return{...e,chainId:n,expiry:r,hash:_B({publicKey:c,type:s}),id:(i??c).toLowerCase(),prehash:a,publicKey:c.toLowerCase(),role:o,type:s}}function fB(e){let{chainId:t,expiry:n,feeToken:r,permissions:i,privateKey:a,role:o}=e;return dB({chainId:t,expiry:n,feeToken:r,permissions:i,privateKey(){return a},publicKey:RL(Dz({privateKey:a}),{includePrefix:!1}),role:o,type:`p256`})}function pB(e,t){let{chainId:n}=t,{publicKey:r}=e,i=iL(r)===20||oL(rL(r,0,12))===0n,a={};for(let t of e.permissions)t.type===`call`&&(a.calls??=[],a.calls.push({signature:t.selector,to:t.to===`0x3232323232323232323232323232323232323232`?void 0:t.to})),t.type===`spend`&&(a.spend??=[],a.spend.push({limit:t.limit,period:t.period,token:t.token}));return dB({chainId:n,expiry:e.expiry,permissions:a,publicKey:e.publicKey,role:tB[e.role],type:i?`address`:eB[e.type]})}function mB(e){let{credential:t,id:n,rpId:r}=e,i=RL(t.publicKey,{includePrefix:!1});return dB({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,id:n,permissions:e.permissions,privateKey:{credential:t,rpId:r},publicKey:i,role:e.role,type:`webauthn-p256`})}function hB(e){let{privateKey:t}=e,n=RL(Dz({privateKey:t}),{includePrefix:!1});return dB({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,permissions:e.permissions,privateKey:{privateKey(){return t}},publicKey:n,role:e.role,type:`webauthn-p256`})}function gB(e){let{chainId:t,expiry:n,feeToken:r,keyPair:i,permissions:a,role:o}=e,{privateKey:s}=i;return dB({chainId:t,expiry:n,feeToken:r,permissions:a,prehash:!0,privateKey:s,publicKey:RL(i.publicKey,{includePrefix:!1}),role:o,type:`p256`})}function _B(e){let{type:t}=e,n=vB(e.publicKey);return ML(UR([{type:`uint8`},{type:`bytes32`}],[aB[t],ML(n)]))}function vB(e){return iL(e)<32?tL(e,32):e}async function yB(e,t){let{address:n,storage:r,webAuthn:i,wrap:a=!0}=t,{privateKey:o,publicKey:s,type:c}=e;if(!o)throw Error(`Key does not have a private key to sign with. + +Key: +`+ZF(e,null,2));let l=n?tz({domain:{verifyingContract:n},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload,[u,d]=await(async()=>{if(c===`p256`){let{privateKey:t}=e;if(typeof t==`function`)return[_R(kz({payload:l,privateKey:t()})),!1];if(t instanceof CryptoKey)return[_R(await Qz({payload:l,privateKey:t})),!0]}if(c===`secp256k1`)return[_R(aR({payload:l,privateKey:o()})),!1];if(c===`webauthn-p256`){if(o.privateKey){let{payload:e,metadata:t}=qz({challenge:l,origin:`https://ithaca.xyz`,rpId:`ithaca.xyz`}),{r:n,s:r}=kz({hash:!0,payload:e,privateKey:o.privateKey()});return[CB({metadata:t,signature:{r:n,s:r}}),!1]}let{credential:t,rpId:n}=o,a=`porto.webauthnVerified.${e.hash}`,s=Date.now(),c=!0;if(r){let e=await r.getItem(a);c=!e||s-e>6e5}let{signature:{r:u,s:d},raw:f,metadata:p}=await Jz({challenge:l,credentialId:t.id,getFn:i?.getFn,rpId:n,userVerification:c?`required`:`preferred`}),m=f.response;if(!m?.userHandle)throw Error(`No user handle in response`,{cause:{response:m}});let h=TL(new Uint8Array(m.userHandle));if(e.id&&XL(e.id)&&!YL(e.id,h))throw Error(`supplied webauthn key "${e.id}" does not match signature webauthn key "${h}"`,{cause:{id:h,key:e}});return c&&r&&await r.setItem(a,s),[CB({metadata:p,signature:{r:u,s:d}}),!1]}throw Error(`Key type "${c}" is not supported.\n\nKey:\n`+ZF(e,null,2))})();return a?wB(u,{keyType:c,prehash:d,publicKey:s}):u}function bB(e,t={}){let{expiry:n=0,prehash:r=!1,publicKey:i,role:a=`admin`,type:o}=e,{feeTokens:s,orchestrator:c}=t,l=Object.entries(xB(e,{feeTokens:s})).map(([e,t])=>{if(e===`calls`)return t.map(({signature:e,to:t})=>({selector:e?cL(e)?e:Ez(e):$z,to:t??`0x3232323232323232323232323232323232323232`,type:`call`}));if(e!==`feeToken`){if(e===`spend`)return t.map(({limit:e,period:t,token:n})=>({limit:e,period:t,token:n,type:`spend`}));throw Error(`Invalid permission type "${e}".`)}}).flat().filter(Boolean);return e.role===`session`&&c&&l.push({selector:$z,to:c,type:`call`}),{expiry:n,permissions:l??[],prehash:r,publicKey:vB(i),role:iB[a],type:rB[o]}}function xB(e,t){let{permissions:n}=e,r=n?.calls?[...n.calls]:[],i=n?.spend?[...n.spend]:[],a=t.feeTokens?.filter(e=>e.feeToken);if(a&&a.length>0){let t=SB(e,{feeTokens:a});if(t){let e=-1,n=oB.year;for(let r=0;re.feeToken.symbol===t.symbol?!0:!e.feeToken.symbol||e.feeToken.symbol===`native`?t.address===dv:!1);if(!r)return;let i=Az(e.feeToken.limit,r.decimals);return{...r,value:i}}function CB(e){let{metadata:t,signature:n}=e;return UR(GR([`struct WebAuthnAuth { bytes authenticatorData; string clientDataJSON; uint256 challengeIndex; uint256 typeIndex; bytes32 r; bytes32 s; }`,`WebAuthnAuth auth`]),[{authenticatorData:t.authenticatorData,challengeIndex:BigInt(t.challengeIndex),clientDataJSON:t.clientDataJSON,r:$I(n.r,{size:32}),s:$I(n.s,{size:32}),typeIndex:BigInt(t.typeIndex)}])}function wB(e,t){let{keyType:n,prehash:r=!1,publicKey:i}=t,a=_B({publicKey:i,type:n});return WR([`bytes`,`bytes32`,`bool`],[e,a,r])}function TB(e){let t=typeof e==`string`?{address:e}:e,n=t.sign?`privateKey`:`porto`,{address:r,sign:i,signMessage:a,signTransaction:o,signTypedData:s,type:c}=pz({address:t.address,sign({hash:e}){if(n===`porto`)throw Error("`sign` not supported on porto accounts.");if(!t.sign)throw Error("`sign` not supported.");return t.sign({hash:e})},signMessage({message:e}){return this.sign({hash:Fh(e)})},signTransaction(){throw Error("`signTransaction` not supported on porto accounts.")},signTypedData(e){return this.sign({hash:Uh(e)})}});return{address:r,keys:t.keys??void 0,sign:i,signMessage:a,signTransaction:o,signTypedData:s,source:n,type:c}}function EB(e,t={}){let{keys:n}=t;return TB({address:JL(tR({privateKey:e})),keys:n,async sign({hash:t}){return _R(aR({payload:t,privateKey:e}))},source:`privateKey`})}function DB(e,t={}){let{key:n,role:r}=t;if(n!==null){if(typeof n==`object`)return n;if(e.keys&&e.keys.length>0)return typeof n==`number`?e.keys[n]:e.keys.find(e=>e.privateKey&&(!r||e.role===r))}}async function OB(e,t){let{storage:n,replaySafe:r=!0,wrap:i=!0,webAuthn:a}=t,o=DB(e,t),s=r?tz({domain:{verifyingContract:e.address},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload,c=o?({hash:e})=>yB(o,{address:null,payload:e,storage:n,webAuthn:a,wrap:i}):e.source===`privateKey`?e.sign:void 0;if(!c)throw Error(`cannot find key to sign with.`);return await c({hash:s})}var kB=()=>`IntersectionObserver`in window&&`IntersectionObserverEntry`in window&&`intersectionRatio`in IntersectionObserverEntry.prototype&&`isVisible`in IntersectionObserverEntry.prototype;function AB(e={}){let{prefix:t=`[Porto]`}=e,n=new Set;return{error:MB(console.error,{prefix:t}),errorOnce:MB(console.error,{memo:n,prefix:t}),log:MB(console.log,{prefix:t}),logOnce:MB(console.log,{memo:n,prefix:t}),warn:MB(console.warn,{prefix:t}),warnOnce:MB(console.warn,{memo:n,prefix:t})}}var jB=AB();function MB(e,t={}){let{memo:n,prefix:r}=t;return(...t)=>{let i=t.join(` `);n?.has(i)||(n?.add(i),e(`${r} ${i}`))}}function NB(){let e=navigator.userAgent.toLowerCase();return e.includes(`safari`)&&!e.includes(`chrome`)}function PB(){let e=navigator.userAgent.toLowerCase();return(e.includes(`firefox`)||e.includes(`fxios`))&&!e.includes(`seamonkey`)}function FB(){return window.navigator?.userAgentData?.mobile?!0:navigator.maxTouchPoints>1||/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(navigator.userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(navigator.userAgent.slice(0,4))}function IB(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),reject:t,resolve:e}}function LB(e){if(Array.isArray(e))return e.map(LB);if(typeof e==`function`)return;if(typeof e!=`object`||!e)return e;if(Object.getPrototypeOf(e)!==Object.prototype)try{return structuredClone(e)}catch{return}let t={};for(let[n,r]of Object.entries(e))t[n]=LB(r);return t}function RB(e,t){let n=[],r=new Set;for(let i of e){let e=t(i);r.has(e)||(r.add(e),n.push(i))}return n}function zB(){return typeof globalThis<`u`&&`crypto`in globalThis?globalThis.crypto.randomUUID():crypto.randomUUID()}function BB(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(BB.cache.get(n))return BB.cache.get(n);let r=e().finally(()=>BB.cache.delete(n));return BB.cache.set(n,r),r}(function(e){e.cache=new Map})(BB||={});function VB(e){return e}function HB(e,t={}){let{targetOrigin:n}=t,r=new Map;return VB({destroy(){for(let t of r.values())e.removeEventListener(`message`,t)},on(t,i,a){function o(e){e.data.topic===t&&(a&&e.data.id!==a||n&&e.origin!==n||i(e.data.payload,e))}return e.addEventListener(`message`,o),r.set(t,o),()=>e.removeEventListener(`message`,o)},async send(t,r,i){let a=zB();return e.postMessage(LB({id:a,payload:r,topic:t}),i??n??`*`),{id:a,payload:r,topic:t}},async sendAsync(e,t,n){let{id:r}=await this.send(e,t,n);return new Promise(t=>this.on(e,t,r))}})}function UB(e){let{from:t,to:n,waitForReady:r=!1}=e,i=!1,a=IB();t.on(`ready`,a.resolve);let o=VB({destroy(){t.destroy(),n.destroy(),i&&a.reject()},on(e,n,r){return t.on(e,n,r)},async send(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.send(e,t)},async sendAsync(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.sendAsync(e,t)}});return{...o,ready(e){o.send(`ready`,e)},waitForReady(){return a.promise}}}var WB={local:`http://localhost:5175/dialog/`,prod:`https://id.porto.sh/dialog`,stg:`https://stg.id.porto.sh/dialog`};function GB(e){return e}function KB(e={}){let{skipProtocolCheck:t,skipUnsupported:n}=e,r=e=>!n&&NB()&&e?.some(e=>[`wallet_connect`,`eth_requestAccounts`].includes(e.method));return typeof window>`u`?JB():GB({name:`iframe`,setup(e){let{host:n,internal:i,theme:a,themeController:o}=e,{store:s}=i,c=qB().setup(e),l=!1,u=new URL(n),d=document.createElement(`dialog`);d.dataset.porto=``,d.setAttribute(`role`,`dialog`),d.setAttribute(`aria-closed`,`true`),d.setAttribute(`aria-label`,`Porto Wallet`),d.setAttribute(`hidden`,`until-found`),Object.assign(d.style,{background:`transparent`,border:`0`,outline:`0`,padding:`0`,position:`fixed`}),document.body.appendChild(d);let f=document.createElement(`iframe`);f.setAttribute(`data-testid`,`porto`);let p=[`payment`,`publickey-credentials-get ${u.origin}`,`publickey-credentials-create ${u.origin}`];PB()||p.push(`clipboard-write`),f.setAttribute(`allow`,p.join(`; `)),f.setAttribute(`tabindex`,`0`),f.setAttribute(`sandbox`,`allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox`),f.setAttribute(`src`,tV(n)),f.setAttribute(`title`,`Porto`),Object.assign(f.style,{backgroundColor:`transparent`,border:`0`,colorScheme:`light dark`,height:`100%`,left:`0`,position:`fixed`,top:`0`,width:`100%`});let m=document.createElement(`style`);m.innerHTML=` dialog[data-porto]::backdrop { background: transparent!important; } - `,d.appendChild(m),d.appendChild(f);let h=yN({from:vN(window,{targetOrigin:u.origin}),to:vN(f.contentWindow,{targetOrigin:u.origin}),waitForReady:!0});o?._setup(h,!0);let g=window.matchMedia(`(max-width: 460px)`),_=()=>{h.send(`__internal`,{type:`resize`,width:g.matches?460:461})};g.addEventListener(`change`,_),h.on(`ready`,t=>{let n=e.internal.store.getState().chainIds.filter(e=>t.chainIds.includes(e));n.length===0&&(n=t.chainIds),s.setState(e=>({...e,chainIds:n})),h.send(`__internal`,{chainIds:n,mode:`iframe`,referrer:DN(),theme:a,type:`init`}),_()}),h.on(`rpc-response`,e=>{r([e._request])&&(f.src=f.src),kN(s,e)}),h.on(`__internal`,e=>{e.type===`switch`&&e.mode===`popup`&&(c.open(),c.syncRequests(s.getState().requestQueue))});let v=null,y=null,b=()=>ON(s),x=e=>{e.key===`Escape`&&ON(s)},S=new MutationObserver(e=>{for(let t of e){if(t.type!==`attributes`)continue;let e=t.attributeName;e&&e===`inert`&&d.removeAttribute(e)}});S.observe(d,{attributeOldValue:!0,attributes:!0});let C=!1,w=()=>{C&&(C=!1,d.removeEventListener(`click`,b),document.removeEventListener(`keydown`,x),d.style.pointerEvents=`none`,y?.focus(),y=null,Object.assign(document.body.style,v??``),document.body.style.overflow=v?.overflow??``)},ee=()=>{C||(C=!0,d.addEventListener(`click`,b),document.addEventListener(`keydown`,x),f.focus(),d.style.pointerEvents=`auto`,v=Object.assign({},document.body.style),document.body.style.overflow=`hidden`)},te=!1,ne=()=>{te||(te=!0,document.activeElement instanceof HTMLElement&&(y=document.activeElement),d.removeAttribute(`hidden`),d.removeAttribute(`aria-closed`),d.showModal())},re=()=>{if(te){te=!1,d.setAttribute(`hidden`,`true`),d.setAttribute(`aria-closed`,`true`),d.close();for(let e of d.parentNode?Array.from(d.parentNode.children):[])e!==d&&e.hasAttribute(`inert`)&&e.removeAttribute(`inert`)}};return{close(){c.close(),l=!1,h.send(`__internal`,{mode:`iframe`,referrer:DN(),type:`init`}),re(),w()},destroy(){c.close(),l=!1,w(),re(),c.destroy(),h.destroy(),d.remove(),S.disconnect(),g.removeEventListener(`change`,_)},open(){l||(l=!0,ne(),ee(),h.send(`__internal`,{mode:`iframe`,referrer:DN(),type:`init`}))},async secure(){let{trustedHosts:e}=await h.waitForReady(),n=(()=>{if(t)return!0;let e=window.location.protocol.startsWith(`https`);return e||sN.warnOnce(`Detected insecure protocol (HTTP).`,`\n\nThe Porto iframe is not supported on HTTP origins (${window.location.origin})`,`due to lack of WebAuthn support.`,`See https://porto.sh/sdk#secure-origins-https for more information.`),e})(),r=aN(),i=!!e?.includes(window.location.hostname),a=!!(r||i);return a||sN.warnOnce([`Warning: Browser does not support IntersectionObserver v2 or host "${u.hostname}" is not trusted by Porto.`,`This may result in the dialog falling back to a popup.`,``,`Add "${u.hostname}" to the trusted hosts list to enable iframe dialog: https://github.com/ithacaxyz/porto/edit/main/src/trusted-hosts.ts`].join(` -`)),{frame:a,host:i,protocol:n}},async syncRequests(e){let{methodPolicies:t}=await h.waitForReady(),n=await this.secure(),i=e?.every(e=>t?.find(t=>t.method===e.request.method)?.modes?.headless===!0),a=r(e.map(e=>e.request));if(!i&&(a||!n.protocol||!n.frame))c.syncRequests(e);else{let n=e.some(e=>EN(e.request,{methodPolicies:t,targetOrigin:u.origin}));!l&&n&&this.open(),h.send(`rpc-requests`,e)}}}},supportsHeadless:!0})}function CN(e={}){if(typeof window>`u`)return wN();let{type:t=`auto`,size:n=TN}=e;return xN({name:`popup`,setup(e){let{host:r,internal:i,themeController:a}=e,{store:o}=i,s=new URL(r),c=null,l=t===`page`||t===`auto`&&dN()?`page`:`popup`;function u(){c&&ON(o)}let d=(()=>{let e=setInterval(()=>{c?.closed&&ON(o)},100);return()=>clearInterval(e)})(),f;return a?._setup(null,!0),{close(){c&&=(c.close(),null)},destroy(){this.close(),window.removeEventListener(`focus`,u),f?.destroy(),d()},open(){if(l===`popup`){let e=(window.innerWidth-n.width)/2+window.screenX,t=window.screenY+100;c=window.open(AN(r),`_blank`,`width=${n.width},height=${n.height},left=${e},top=${t}`)}else c=window.open(AN(r),`_blank`);if(!c)throw Error(`Failed to open popup`);f=yN({from:vN(window,{targetOrigin:s.origin}),to:vN(c,{targetOrigin:s.origin}),waitForReady:!0}),a?._setup(f,!1),f.send(`__internal`,{mode:l===`page`?`page`:`popup`,referrer:DN(),theme:a?.getTheme()??e.theme,type:`init`}),f.on(`rpc-response`,e=>kN(o,e)),window.removeEventListener(`focus`,u),window.addEventListener(`focus`,u)},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(e){e.some(e=>EN(e.request))&&((!c||c.closed)&&this.open(),c?.focus()),f?.send(`rpc-requests`,e)}}},supportsHeadless:!1})}function wN(){return xN({name:`noop`,setup(){return{close(){},destroy(){},open(){},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(){}}},supportsHeadless:!0})}const TN={height:282,width:360};function EN(e,t={}){let{methodPolicies:n,targetOrigin:r}=t,i=n?.find(t=>t.method===e.method);return i&&i.modes?.headless?!!(typeof i.modes.headless==`object`&&i.modes.headless.sameOrigin&&r!==window.location.origin):!0}function DN(){return{icon:(()=>{let e=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: dark)"]`)?.href,t=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: light)"]`)?.href??document.querySelector(`link[rel="icon"]`)?.href;return e&&t&&e!==t?{dark:e,light:t}:window.matchMedia(`(prefers-color-scheme: dark)`).matches?e:t})(),title:document.title}}function ON(e){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>({account:e.account,error:new HM,request:e.request,status:`error`}))}))}function kN(e,t){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>e.request.id===t.id?t.error?{account:e.account,error:t.error,request:e.request,status:`error`}:{account:e.account,request:e.request,result:t.result,status:`success`}:e)}))}function AN(e){let t=new URL(e),n=new URLSearchParams(window.location.search);for(let[e,r]of n.entries())e.startsWith(`porto.`)&&t.searchParams.set(e.slice(6),r);return t.toString()}function jN(e){let t=new CustomEvent(`eip6963:announceProvider`,{detail:Object.freeze(e)});window.dispatchEvent(t);let n=()=>window.dispatchEvent(t);return window.addEventListener(`eip6963:requestProvider`,n),()=>window.removeEventListener(`eip6963:requestProvider`,n)}Object.freeze({status:`aborted`});function z(e,t,n){function r(n,r){var i;for(let a in Object.defineProperty(n,`_zod`,{value:n._zod??{},enumerable:!1}),(i=n._zod).traits??(i.traits=new Set),n._zod.traits.add(e),t(n,r),o.prototype)a in n||Object.defineProperty(n,a,{value:o.prototype[a].bind(n)});n._zod.constr=o,n._zod.def=r}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var MN=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const NN={};function PN(e){return e&&Object.assign(NN,e),NN}function FN(e,t){return typeof t==`bigint`?t.toString():t}function IN(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function LN(e){return e==null}function RN(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}var zN=Symbol(`evaluating`);function BN(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==zN)return r===void 0&&(r=zN,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}function VN(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function HN(...e){let t={};for(let n of e){let e=Object.getOwnPropertyDescriptors(n);Object.assign(t,e)}return Object.defineProperties({},t)}const UN=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function WN(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function GN(e){if(WN(e)===!1)return!1;let t=e.constructor;if(t===void 0)return!0;let n=t.prototype;return!(WN(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}const KN=new Set([`string`,`number`,`bigint`,`boolean`,`symbol`,`undefined`]);function qN(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function JN(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function YN(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function XN(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function ZN(e,t){let n=e._zod.def,r=HN(e._zod.def,{get shape(){let e={};for(let r in t){if(!(r in n.shape))throw Error(`Unrecognized key: "${r}"`);t[r]&&(e[r]=n.shape[r])}return VN(this,`shape`,e),e},checks:[]});return JN(e,r)}function QN(e,t){let n=e._zod.def,r=HN(e._zod.def,{get shape(){let r={...e._zod.def.shape};for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return VN(this,`shape`,r),r},checks:[]});return JN(e,r)}function $N(e,t,n){let r=HN(t._zod.def,{get shape(){let r=t._zod.def.shape,i={...r};if(n)for(let t in n){if(!(t in r))throw Error(`Unrecognized key: "${t}"`);n[t]&&(i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t])}else for(let t in r)i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t];return VN(this,`shape`,i),i},checks:[]});return JN(t,r)}function eP(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function nP(e){return typeof e==`string`?e:e?.message}function rP(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=nP(e.inst?._zod.def?.error?.(e))??nP(t?.error?.(e))??nP(n.customError?.(e))??nP(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function iP(e){return Array.isArray(e)?`array`:typeof e==`string`?`string`:`unknown`}var aP=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,FN,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})};const oP=z(`$ZodError`,aP),sP=z(`$ZodError`,aP,{Parent:Error}),cP=e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new MN;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>rP(e,a,PN())));throw UN(t,i?.callee),t}return o.value},lP=cP(sP),uP=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>rP(e,a,PN())));throw UN(t,i?.callee),t}return o.value})(sP),dP=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new MN;return a.issues.length?{success:!1,error:new(e??oP)(a.issues.map(e=>rP(e,i,PN())))}:{success:!0,data:a.value}})(sP),fP=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>rP(e,i,PN())))}:{success:!0,data:a.value}})(sP),pP=(e=>(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return cP(e)(t,n,i)})(sP),mP=(e=>(t,n,r)=>cP(e)(t,n,r))(sP),hP=e=>{let t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\s\\S]*`;return RegExp(`^${t}$`)},gP=/^-?\d+n?$/,_P=/^-?\d+(?:\.\d+)?/,vP=/^(?:true|false)$/i;var yP=/^null$/i,bP=/^undefined$/i;const xP=z(`$ZodCheck`,(e,t)=>{var n;e._zod??={},e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])});var SP={number:`number`,bigint:`bigint`,object:`date`};const CP=z(`$ZodCheckGreaterThan`,(e,t)=>{xP.init(e,t);let n=SP[typeof t.value];e._zod.onattach.push(e=>{let n=e._zod.bag,r=(t.inclusive?n.minimum:n.exclusiveMinimum)??-1/0;t.value>r&&(t.inclusive?n.minimum=t.value:n.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:`too_small`,minimum:t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),wP=z(`$ZodCheckMinLength`,(e,t)=>{var n;xP.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!LN(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag.minimum??-1/0;t.minimum>n&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{let r=n.value;if(r.length>=t.minimum)return;let i=iP(r);n.issues.push({origin:i,code:`too_small`,minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort})}}),TP=z(`$ZodCheckStringFormat`,(e,t)=>{var n,r;xP.init(e,t),e._zod.onattach.push(e=>{let n=e._zod.bag;n.format=t.format,t.pattern&&(n.patterns??=new Set,n.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:t.format,input:n.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),EP=z(`$ZodCheckRegex`,(e,t)=>{TP.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:`regex`,input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),DP={major:4,minor:1,patch:12},OP=z(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=DP;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=eP(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new MN;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=eP(e,t))});else{if(e.issues.length===t)continue;r||=eP(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(eP(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new MN;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new MN;return o.then(e=>t(e,r,a))}return t(o,r,a)}}e[`~standard`]={validate:t=>{try{let n=dP(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return fP(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}}),kP=z(`$ZodString`,(e,t)=>{OP.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??hP(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),AP=z(`$ZodNumber`,(e,t)=>{OP.init(e,t),e._zod.pattern=e._zod.bag.pattern??_P,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}let i=n.value;if(typeof i==`number`&&!Number.isNaN(i)&&Number.isFinite(i))return n;let a=typeof i==`number`?Number.isNaN(i)?`NaN`:Number.isFinite(i)?void 0:`Infinity`:void 0;return n.issues.push({expected:`number`,code:`invalid_type`,input:i,inst:e,...a?{received:a}:{}}),n}}),jP=z(`$ZodBoolean`,(e,t)=>{OP.init(e,t),e._zod.pattern=vP,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=!!n.value}catch{}let i=n.value;return typeof i==`boolean`||n.issues.push({expected:`boolean`,code:`invalid_type`,input:i,inst:e}),n}}),MP=z(`$ZodBigInt`,(e,t)=>{OP.init(e,t),e._zod.pattern=gP,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=BigInt(n.value)}catch{}return typeof n.value==`bigint`||n.issues.push({expected:`bigint`,code:`invalid_type`,input:n.value,inst:e}),n}}),NP=z(`$ZodUndefined`,(e,t)=>{OP.init(e,t),e._zod.pattern=bP,e._zod.values=new Set([void 0]),e._zod.optin=`optional`,e._zod.optout=`optional`,e._zod.parse=(t,n)=>{let r=t.value;return r===void 0||t.issues.push({expected:`undefined`,code:`invalid_type`,input:r,inst:e}),t}}),PP=z(`$ZodNull`,(e,t)=>{OP.init(e,t),e._zod.pattern=yP,e._zod.values=new Set([null]),e._zod.parse=(t,n)=>{let r=t.value;return r===null||t.issues.push({expected:`null`,code:`invalid_type`,input:r,inst:e}),t}}),FP=z(`$ZodAny`,(e,t)=>{OP.init(e,t),e._zod.parse=e=>e}),IP=z(`$ZodUnknown`,(e,t)=>{OP.init(e,t),e._zod.parse=e=>e}),LP=z(`$ZodDate`,(e,t)=>{OP.init(e,t),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=new Date(n.value)}catch{}let i=n.value,a=i instanceof Date;return a&&!Number.isNaN(i.getTime())||n.issues.push({expected:`date`,code:`invalid_type`,input:i,...a?{received:`Invalid Date`}:{},inst:e}),n}});function RP(e,t,n){e.issues.length&&t.issues.push(...tP(n,e.issues)),t.value[n]=e.value}const zP=z(`$ZodArray`,(e,t)=>{OP.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;eRP(t,n,e))):RP(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function BP(e,t,n,r){e.issues.length&&t.issues.push(...tP(n,e.issues)),e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function VP(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key "${n}": expected a Zod schema`);let n=XN(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function HP(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type;for(let i of Object.keys(t)){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>BP(e,n,i,t))):BP(a,n,i,t)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const UP=z(`$ZodObject`,(e,t)=>{if(OP.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=IN(()=>VP(t));BN(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=WN,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e]._zod.run({value:s[e],issues:[]},o);n instanceof Promise?c.push(n.then(n=>BP(n,t,e,s))):BP(n,t,e,s)}return i?HP(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}});function WP(e,t,n,r){for(let n of e)if(n.issues.length===0)return t.value=n.value,t;let i=e.filter(e=>!eP(e));return i.length===1?(t.value=i[0].value,i[0]):(t.issues.push({code:`invalid_union`,input:t.value,inst:n,errors:e.map(e=>e.issues.map(e=>rP(e,r,PN())))}),t)}const GP=z(`$ZodUnion`,(e,t)=>{OP.init(e,t),BN(e._zod,`optin`,()=>t.options.some(e=>e._zod.optin===`optional`)?`optional`:void 0),BN(e._zod,`optout`,()=>t.options.some(e=>e._zod.optout===`optional`)?`optional`:void 0),BN(e._zod,`values`,()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),BN(e._zod,`pattern`,()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>RN(e.source)).join(`|`)})$`)}});let n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(i,a)=>{if(n)return r(i,a);let o=!1,s=[];for(let e of t.options){let t=e._zod.run({value:i.value,issues:[]},a);if(t instanceof Promise)s.push(t),o=!0;else{if(t.issues.length===0)return t;s.push(t)}}return o?Promise.all(s).then(t=>WP(t,i,e,a)):WP(s,i,e,a)}}),KP=z(`$ZodDiscriminatedUnion`,(e,t)=>{GP.init(e,t);let n=e._zod.parse;BN(e._zod,`propValues`,()=>{let e={};for(let n of t.options){let r=n._zod.propValues;if(!r||Object.keys(r).length===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`);for(let[t,n]of Object.entries(r)){e[t]||(e[t]=new Set);for(let r of n)e[t].add(r)}}return e});let r=IN(()=>{let e=t.options,n=new Map;for(let r of e){let e=r._zod.propValues?.[t.discriminator];if(!e||e.size===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`);for(let t of e){if(n.has(t))throw Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,r)}}return n});e._zod.parse=(i,a)=>{let o=i.value;if(!WN(o))return i.issues.push({code:`invalid_type`,expected:`object`,input:o,inst:e}),i;let s=r.value.get(o?.[t.discriminator]);return s?s._zod.run(i,a):t.unionFallback?n(i,a):(i.issues.push({code:`invalid_union`,errors:[],note:`No matching discriminator`,discriminator:t.discriminator,input:o,path:[t.discriminator],inst:e}),i)}}),qP=z(`$ZodTuple`,(e,t)=>{OP.init(e,t);let n=t.items,r=n.length-[...n].reverse().findIndex(e=>e._zod.optin!==`optional`);e._zod.parse=(i,a)=>{let o=i.value;if(!Array.isArray(o))return i.issues.push({input:o,inst:e,expected:`tuple`,code:`invalid_type`}),i;i.value=[];let s=[];if(!t.rest){let t=o.length>n.length,a=o.length=o.length&&c>=r)continue;let t=e._zod.run({value:o[c],issues:[]},a);t instanceof Promise?s.push(t.then(e=>JP(e,i,c))):JP(t,i,c)}if(t.rest){let e=o.slice(n.length);for(let n of e){c++;let e=t.rest._zod.run({value:n,issues:[]},a);e instanceof Promise?s.push(e.then(e=>JP(e,i,c))):JP(e,i,c)}}return s.length?Promise.all(s).then(()=>i):i}});function JP(e,t,n){e.issues.length&&t.issues.push(...tP(n,e.issues)),t.value[n]=e.value}const YP=z(`$ZodRecord`,(e,t)=>{OP.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!GN(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[];if(t.keyType._zod.values){let o=t.keyType._zod.values;n.value={};for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...tP(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...tP(e,o.issues)),n.value[e]=o.value)}let s;for(let e in i)o.has(e)||(s??=[],s.push(e));s&&s.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:s})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(s.issues.length){n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>rP(e,r,PN())),input:o,path:[o],inst:e}),n.value[s.value]=s.value;continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...tP(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...tP(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}}),XP=z(`$ZodLiteral`,(e,t)=>{if(OP.init(e,t),t.values.length===0)throw Error(`Cannot create literal schema with no valid values`);e._zod.values=new Set(t.values),e._zod.pattern=RegExp(`^(${t.values.map(e=>typeof e==`string`?qN(e):e?qN(e.toString()):String(e)).join(`|`)})$`),e._zod.parse=(n,r)=>{let i=n.value;return e._zod.values.has(i)||n.issues.push({code:`invalid_value`,values:t.values,input:i,inst:e}),n}});function ZP(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const QP=z(`$ZodOptional`,(e,t)=>{OP.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,BN(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),BN(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${RN(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>ZP(t,e.value)):ZP(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}}),$P=z(`$ZodNullable`,(e,t)=>{OP.init(e,t),BN(e._zod,`optin`,()=>t.innerType._zod.optin),BN(e._zod,`optout`,()=>t.innerType._zod.optout),BN(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${RN(e.source)}|null)$`):void 0}),BN(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,n)=>e.value===null?e:t.innerType._zod.run(e,n)}),eF=z(`$ZodPipe`,(e,t)=>{OP.init(e,t),BN(e._zod,`values`,()=>t.in._zod.values),BN(e._zod,`optin`,()=>t.in._zod.optin),BN(e._zod,`optout`,()=>t.out._zod.optout),BN(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if(n.direction===`backward`){let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>tF(e,t.in,n)):tF(r,t.in,n)}let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>tF(e,t.out,n)):tF(r,t.out,n)}});function tF(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}const nF=z(`$ZodCodec`,(e,t)=>{OP.init(e,t),BN(e._zod,`values`,()=>t.in._zod.values),BN(e._zod,`optin`,()=>t.in._zod.optin),BN(e._zod,`optout`,()=>t.out._zod.optout),BN(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if((n.direction||`forward`)===`forward`){let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>rF(e,t,n)):rF(r,t,n)}else{let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>rF(e,t,n)):rF(r,t,n)}}});function rF(e,t,n){if(e.issues.length)return e.aborted=!0,e;if((n.direction||`forward`)===`forward`){let r=t.transform(e.value,e);return r instanceof Promise?r.then(r=>iF(e,r,t.out,n)):iF(e,r,t.out,n)}else{let r=t.reverseTransform(e.value,e);return r instanceof Promise?r.then(r=>iF(e,r,t.in,n)):iF(e,r,t.in,n)}}function iF(e,t,n,r){return e.issues.length?(e.aborted=!0,e):n._zod.run({value:t,issues:e.issues},r)}const aF=z(`$ZodReadonly`,(e,t)=>{OP.init(e,t),BN(e._zod,`propValues`,()=>t.innerType._zod.propValues),BN(e._zod,`values`,()=>t.innerType._zod.values),BN(e._zod,`optin`,()=>t.innerType._zod.optin),BN(e._zod,`optout`,()=>t.innerType._zod.optout),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(oF):oF(r)}});function oF(e){return e.value=Object.freeze(e.value),e}const sF=z(`$ZodTemplateLiteral`,(e,t)=>{OP.init(e,t);let n=[];for(let e of t.parts)if(typeof e==`object`&&e){if(!e._zod.pattern)throw Error(`Invalid template literal part, no pattern found: ${[...e._zod.traits].shift()}`);let t=e._zod.pattern instanceof RegExp?e._zod.pattern.source:e._zod.pattern;if(!t)throw Error(`Invalid template literal part: ${e._zod.traits}`);let r=t.startsWith(`^`)?1:0,i=t.endsWith(`$`)?t.length-1:t.length;n.push(t.slice(r,i))}else if(e===null||KN.has(typeof e))n.push(qN(`${e}`));else throw Error(`Invalid template literal part: ${e}`);e._zod.pattern=RegExp(`^${n.join(``)}$`),e._zod.parse=(n,r)=>typeof n.value==`string`?(e._zod.pattern.lastIndex=0,e._zod.pattern.test(n.value)||n.issues.push({input:n.value,inst:e,code:`invalid_format`,format:t.format??`template_literal`,pattern:e._zod.pattern.source}),n):(n.issues.push({input:n.value,inst:e,expected:`template_literal`,code:`invalid_type`}),n)});function cF(e,t){return new e({type:`string`,...YN(t)})}function lF(e,t){return new e({type:`number`,checks:[],...YN(t)})}function uF(e,t){return new e({type:`boolean`,...YN(t)})}function dF(e,t){return new e({type:`bigint`,...YN(t)})}function fF(e,t){return new e({type:`undefined`,...YN(t)})}function pF(e,t){return new e({type:`null`,...YN(t)})}function mF(e){return new e({type:`any`})}function hF(e){return new e({type:`unknown`})}function gF(e,t){return new e({type:`date`,...YN(t)})}function _F(e,t){return new CP({check:`greater_than`,...YN(t),value:e,inclusive:!0})}function vF(e,t){return new wP({check:`min_length`,...YN(t),minimum:e})}function yF(e,t){return new EP({check:`string_format`,format:`regex`,...YN(t),pattern:e})}const bF=z(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);OP.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>lP(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>dP(e,t,n),e.parseAsync=async(t,n)=>uP(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>fP(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),e.clone=(t,n)=>JN(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e))}),xF=z(`ZodMiniString`,(e,t)=>{kP.init(e,t),bF.init(e,t)});function B(e){return cF(xF,e)}const SF=z(`ZodMiniNumber`,(e,t)=>{AP.init(e,t),bF.init(e,t)});function V(e){return lF(SF,e)}const CF=z(`ZodMiniBoolean`,(e,t)=>{jP.init(e,t),bF.init(e,t)});function wF(e){return uF(CF,e)}const TF=z(`ZodMiniBigInt`,(e,t)=>{MP.init(e,t),bF.init(e,t)});function EF(e){return dF(TF,e)}const DF=z(`ZodMiniUndefined`,(e,t)=>{NP.init(e,t),bF.init(e,t)});function OF(e){return fF(DF,e)}const kF=z(`ZodMiniNull`,(e,t)=>{PP.init(e,t),bF.init(e,t)});function AF(e){return pF(kF,e)}const jF=z(`ZodMiniAny`,(e,t)=>{FP.init(e,t),bF.init(e,t)});function MF(){return mF(jF)}const NF=z(`ZodMiniUnknown`,(e,t)=>{IP.init(e,t),bF.init(e,t)});function PF(){return hF(NF)}const FF=z(`ZodMiniDate`,(e,t)=>{LP.init(e,t),bF.init(e,t)});function IF(e){return gF(FF,e)}const LF=z(`ZodMiniArray`,(e,t)=>{zP.init(e,t),bF.init(e,t)});function H(e,t){return new LF({type:`array`,element:e,...YN(t)})}const RF=z(`ZodMiniObject`,(e,t)=>{UP.init(e,t),bF.init(e,t),BN(e,`shape`,()=>t.shape)});function U(e,t){let n={type:`object`,shape:e??{},...YN(t)};return new RF(n)}function zF(e,t){return ZN(e,t)}function BF(e,t){return QN(e,t)}function VF(e,t){return $N(YF,e,t)}const HF=z(`ZodMiniUnion`,(e,t)=>{GP.init(e,t),bF.init(e,t)});function W(e,t){return new HF({type:`union`,options:e,...YN(t)})}const UF=z(`ZodMiniDiscriminatedUnion`,(e,t)=>{KP.init(e,t),bF.init(e,t)});function WF(e,t,n){return new UF({type:`union`,options:t,discriminator:e,...YN(n)})}const GF=z(`ZodMiniTuple`,(e,t)=>{qP.init(e,t),bF.init(e,t)});function G(e,t,n){let r=t instanceof OP;return new GF({type:`tuple`,items:e,rest:r?t:null,...YN(r?n:t)})}const KF=z(`ZodMiniRecord`,(e,t)=>{YP.init(e,t),bF.init(e,t)});function qF(e,t,n){return new KF({type:`record`,keyType:e,valueType:t,...YN(n)})}const JF=z(`ZodMiniLiteral`,(e,t)=>{XP.init(e,t),bF.init(e,t)});function K(e,t){return new JF({type:`literal`,values:Array.isArray(e)?e:[e],...YN(t)})}const YF=z(`ZodMiniOptional`,(e,t)=>{QP.init(e,t),bF.init(e,t)});function q(e){return new YF({type:`optional`,innerType:e})}const XF=z(`ZodMiniNullable`,(e,t)=>{$P.init(e,t),bF.init(e,t)});function ZF(e){return new XF({type:`nullable`,innerType:e})}function QF(e){return q(ZF(e))}const $F=z(`ZodMiniPipe`,(e,t)=>{eF.init(e,t),bF.init(e,t)}),eI=z(`ZodMiniCodec`,(e,t)=>{$F.init(e,t),nF.init(e,t)});function tI(e,t,n){return new eI({type:`pipe`,in:e,out:t,transform:n.decode,reverseTransform:n.encode})}const nI=z(`ZodMiniReadonly`,(e,t)=>{aF.init(e,t),bF.init(e,t)});function J(e){return new nI({type:`readonly`,innerType:e})}const rI=z(`ZodMiniTemplateLiteral`,(e,t)=>{sF.init(e,t),bF.init(e,t)});function iI(e,t){return new rI({type:`template_literal`,parts:e,...YN(t)})}Lf(),Jd(),op(),pf();function aI(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(fg);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[,e,t]=s;I(o,{signed:e===`int`,size:Number.parseInt(t??``,10)/8})}if(r===`address`&&typeof o==`string`&&!og(o))throw new sg({address:o,cause:new cg});let c=r.match(dg);if(c){let[,e]=c;if(e&&qf(o)!==Number.parseInt(e,10))throw new fI({expectedSize:Number.parseInt(e,10),givenSize:qf(o)})}let l=i[r];l&&(bI(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new pI({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new mI({primaryType:r,types:i})}function oI(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:cI(t),...e.types};aI({domain:t,message:n,primaryType:r,types:i});let a=[`0x19`,`0x01`];return t&&a.push(uI({domain:t,types:i})),r!==`EIP712Domain`&&a.push(dI({data:n,primaryType:r,types:i})),zf(...a)}function sI(e){let{primaryType:t,types:n}=e,r=``,i=yI({primaryType:t,types:n});i.delete(t);let a=[t,...Array.from(i).sort()];for(let e of a)r+=`${e}(${(n[e]??[]).map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return r}function cI(e){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function lI(e){return Hh(oI(e))}function uI(e){let{domain:t,types:n}=e;return dI({data:t,primaryType:`EIP712Domain`,types:{...n,EIP712Domain:n?.EIP712Domain||cI(t)}})}function dI(e){let{data:t,primaryType:n,types:r}=e,i=gI({data:t,primaryType:n,types:r});return Hh(i)}var fI=class extends P{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.BytesSizeMismatchError`})}},pI=class extends P{constructor({domain:e}){super(`Invalid domain "${df(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidDomainError`})}},mI=class extends P{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{metaMessages:["Check that the primary type is a key in `types`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidPrimaryTypeError`})}},hI=class extends P{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidStructTypeError`})}};function gI(e){let{data:t,primaryType:n,types:r}=e,i=[{type:`bytes32`}],a=[_I({primaryType:n,types:r})];for(let e of r[n]??[]){let[n,o]=vI({types:r,name:e.name,type:e.type,value:t[e.name]});i.push(n),a.push(o)}return Hg(i,a)}function _I(e){let{primaryType:t,types:n}=e,r=Uf(sI({primaryType:t,types:n}));return Hh(r)}function vI(e){let{types:t,name:n,type:r,value:i}=e;if(t[r]!==void 0)return[{type:`bytes32`},Hh(gI({data:i,primaryType:r,types:t}))];if(r===`bytes`)return i=`0x${(i.length%2?`0`:``)+i.slice(2)}`,[{type:`bytes32`},Hh(i,{as:`Hex`})];if(r===`string`)return[{type:`bytes32`},Hh(vf(i),{as:`Hex`})];if(r.lastIndexOf(`]`)===r.length-1){let e=r.slice(0,r.lastIndexOf(`[`)),a=i.map(r=>vI({name:n,type:e,types:t,value:r}));return[{type:`bytes32`},Hh(Hg(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:r},i]}function yI(e,t=new Set){let{primaryType:n,types:r}=e,i=n.match(/^\w*/u)?.[0];if(t.has(i)||r[i]===void 0)return t;t.add(i);for(let e of r[i])yI({primaryType:e.type,types:r},t);return t}function bI(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new hI({type:e})}pi(),Ci();function xI(e){if(typeof e==`string`){if(!bi(e,{strict:!1}))throw new fi({address:e});return{address:e,type:`json-rpc`}}if(!bi(e.address,{strict:!1}))throw new fi({address:e.address});return{address:e.address,nonceManager:e.nonceManager,sign:e.sign,signAuthorization:e.signAuthorization,signMessage:e.signMessage,signTransaction:e.signTransaction,signTypedData:e.signTypedData,source:`custom`,type:`local`}}Is(),Sl(),Hc();var SI=Ac(BigInt(`0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff`)),CI=SI.create(BigInt(`-3`)),wI=BigInt(`0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`);const TI=xl({a:CI,b:wI,Fp:SI,n:BigInt(`0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551`),Gx:BigInt(`0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296`),Gy:BigInt(`0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5`),h:BigInt(1),lowS:!1},Ns);var EI=Ac(BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff`)),DI=EI.create(BigInt(`-3`)),OI=BigInt(`0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef`);xl({a:DI,b:OI,Fp:EI,n:BigInt(`0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973`),Gx:BigInt(`0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7`),Gy:BigInt(`0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f`),h:BigInt(1),lowS:!1},Fs);var kI=Ac(BigInt(`0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`)),AI=kI.create(BigInt(`-3`)),jI=BigInt(`0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00`);xl({a:AI,b:jI,Fp:kI,n:BigInt(`0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409`),Gx:BigInt(`0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66`),Gy:BigInt(`0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650`),h:BigInt(1),lowS:!1,allowedPrivateKeyLengths:[130,131,132]},Ps);const MI=TI,NI=TI;Lf(),op();function PI(e){let{privateKey:t}=e,n=NI.ProjectivePoint.fromPrivateKey(typeof t==`string`?t.slice(2):Hf(t).slice(2));return Gh(n)}function FI(e={}){let{as:t=`Hex`}=e,n=NI.utils.randomPrivateKey();return t===`Hex`?Hf(n):n}function II(e){let{extraEntropy:t=!1,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=NI.sign(r instanceof Uint8Array?r:_f(r),i instanceof Uint8Array?i:_f(i),{extraEntropy:typeof t==`boolean`?t:Bf(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}Jd();function LI(e,t=0){if(!/^(-?)([0-9]*)\.?([0-9]*)$/.test(e))throw new RI({value:e});let[n=``,r=`0`]=e.split(`.`),i=n.startsWith(`-`);if(i&&(n=n.slice(1)),r=r.replace(/(0+)$/,``),t===0)Math.round(Number(`.${r}`))===1&&(n=`${BigInt(n)+1n}`),r=``;else if(r.length>t){let[e,i,a]=[r.slice(0,t-1),r.slice(t-1,t),r.slice(t)],o=Math.round(Number(`${i}.${a}`));r=o>9?`${BigInt(e)+BigInt(1)}0`.padStart(e.length+1,`0`):`${e}${o}`,r.length>t&&(r=r.slice(1),n=`${BigInt(n)+1n}`),r=r.slice(0,t)}else r=r.padEnd(t,`0`);return BigInt(`${i?`-`:``}${n}${r}`)}var RI=class extends P{constructor({value:e}){super(`Value \`${e}\` is not a valid decimal number.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Value.InvalidDecimalNumberError`})}};Lf();var zI=new TextEncoder,BI=new TextDecoder,VI=Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[t,e.charCodeAt(0)])),HI={...Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[e.charCodeAt(0),t])),61:0,45:62,95:63};function UI(e,t={}){let{pad:n=!0,url:r=!1}=t,i=new Uint8Array(Math.ceil(e.length/3)*4);for(let t=0,n=0;n>18],i[t+1]=VI[r>>12&63],i[t+2]=VI[r>>6&63],i[t+3]=VI[r&63]}let a=e.length%3,o=Math.floor(e.length/3)*4+(a&&a+1),s=BI.decode(new Uint8Array(i.buffer,0,o));return n&&a===1&&(s+=`==`),n&&a===2&&(s+=`=`),r&&(s=s.replaceAll(`+`,`-`).replaceAll(`/`,`_`)),s}function WI(e,t={}){return UI(_f(e),t)}function GI(e){let t=e.replace(/=+$/,``),n=t.length,r=new Uint8Array(n+3);zI.encodeInto(t+`===`,r);for(let e=0,n=0;e>16,r[n+1]=t>>8&255,r[n+2]=t&255}let i=(n>>2)*3+(n%4&&n%4-1);return new Uint8Array(r.buffer,0,i)}op();function KI(e){let t=e[4]===0?5:4,n=t+32,r=e[n+2]===0?n+3:n+2,i=BigInt(Hf(e.slice(t,n))),a=BigInt(Hf(e.slice(r)));return{r:i,s:a>MI.CURVE.n/2n?MI.CURVE.n-a:a}}async function qI(e){try{let t=e.getPublicKey();if(!t)throw new nL;let n=new Uint8Array(t),r=await crypto.subtle.importKey(`spki`,new Uint8Array(n),{name:`ECDSA`,namedCurve:`P-256`,hash:`SHA-256`},!0,[`verify`]),i=new Uint8Array(await crypto.subtle.exportKey(`raw`,r));return Gh(i)}catch(t){if(t.message!==`Permission denied to access object`)throw t;let n=new Uint8Array(e.attestationObject),r=e=>{let t=new Uint8Array([e,88,32]);for(let e=0;en[e+r]===t))return e+t.length;throw new nL},i=r(33),a=r(34);return Gh(new Uint8Array([4,...n.slice(i,i+32),...n.slice(a,a+32)]))}}Lf(),Jd(),op();const JI=Uint8Array.from([105,171,180,181,160,222,75,198,42,42,32,31,141,37,186,233]);async function YI(e){let{createFn:t=window.navigator.credentials.create.bind(window.navigator.credentials),...n}=e,r=QI(n);try{let e=await t(r);if(!e)throw new nL;let n=e.response,i=await qI(n);return{id:e.id,publicKey:i,raw:e}}catch(e){throw new nL({cause:e})}}function XI(e={}){let{flag:t=5,rpId:n=window.location.hostname,signCount:r=0}=e,i=Uh(Uf(n)),a=I(t,{size:1}),o=I(r,{size:4});return zf(i,a,o)}function ZI(e){let{challenge:t,crossOrigin:n=!1,extraClientData:r,origin:i=window.location.origin}=e;return JSON.stringify({type:`webauthn.get`,challenge:WI(t,{url:!0,pad:!1}),origin:i,crossOrigin:n,...r})}function QI(e){let{attestation:t=`none`,authenticatorSelection:n={residentKey:`preferred`,requireResidentKey:!1,userVerification:`required`},challenge:r=JI,excludeCredentialIds:i,extensions:a,name:o,rp:s={id:window.location.hostname,name:window.document.title},user:c}=e,l=c?.name??o;return{publicKey:{attestation:t,authenticatorSelection:n,challenge:r,...i?{excludeCredentials:i?.map(e=>({id:GI(e),type:`public-key`}))}:{},pubKeyCredParams:[{type:`public-key`,alg:-7}],...a&&{extensions:a},rp:s,user:{id:c?.id??Hh(vf(l),{as:`Bytes`}),name:l,displayName:c?.displayName??l}}}}function $I(e){let{credentialId:t,challenge:n,extensions:r,rpId:i=window.location.hostname,userVerification:a=`required`}=e;return{publicKey:{...t?{allowCredentials:Array.isArray(t)?t.map(e=>({id:GI(e),type:`public-key`})):[{id:GI(t),type:`public-key`}]}:{},challenge:_f(n),...r&&{extensions:r},rpId:i,userVerification:a}}}function eL(e){let{challenge:t,crossOrigin:n,extraClientData:r,flag:i,origin:a,rpId:o,signCount:s,userVerification:c=`required`}=e,l=XI({flag:i,rpId:o,signCount:s}),u=ZI({challenge:t,crossOrigin:n,extraClientData:r,origin:a}),d=Uh(Uf(u)),f=u.indexOf(`"challenge"`),p=u.indexOf(`"type"`),m={authenticatorData:l,clientDataJSON:u,challengeIndex:f,typeIndex:p,userVerificationRequired:c===`required`},h=zf(l,d);return{metadata:m,payload:h}}async function tL(e){let{getFn:t=window.navigator.credentials.get.bind(window.navigator.credentials),...n}=e,r=$I(n);try{let e=await t(r);if(!e)throw new rL;let n=e.response,i=String.fromCharCode(...new Uint8Array(n.clientDataJSON)),a=i.indexOf(`"challenge"`),o=i.indexOf(`"type"`),s=KI(new Uint8Array(n.signature));return{metadata:{authenticatorData:Hf(new Uint8Array(n.authenticatorData)),clientDataJSON:i,challengeIndex:a,typeIndex:o,userVerificationRequired:r.publicKey.userVerification===`required`},signature:s,raw:e}}catch(e){throw new rL({cause:e})}}var nL=class extends P{constructor({cause:e}={}){super(`Failed to create credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialCreationFailedError`})}},rL=class extends P{constructor({cause:e}={}){super(`Failed to request credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialRequestFailedError`})}};Lf();async function iL(e={}){let{extractable:t=!1}=e,n=await globalThis.crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},t,[`sign`,`verify`]),r=await globalThis.crypto.subtle.exportKey(`raw`,n.publicKey),i=Gh(new Uint8Array(r));return{privateKey:n.privateKey,publicKey:i}}async function aL(e){let{payload:t,privateKey:n}=e,r=await globalThis.crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},n,hf(t)),i=gf(new Uint8Array(r)),a=Sf(xf(i,0,32)),o=Sf(xf(i,32,64));return o>MI.CURVE.n/2n&&(o=MI.CURVE.n-o),{r:a,s:o}}const oL=`0x32323232`;Lf(),op(),pf();const sL={p256:`p256`,secp256k1:`secp256k1`,webauthnp256:`webauthn-p256`},cL={admin:`admin`,normal:`session`},lL={0:`minute`,1:`hour`,2:`day`,3:`week`,4:`month`,5:`year`},uL={address:`secp256k1`,p256:`p256`,secp256k1:`secp256k1`,"webauthn-p256":`webauthnp256`},dL={admin:`admin`,session:`normal`},fL={address:2,p256:0,secp256k1:2,"webauthn-p256":1},pL={day:2,hour:1,minute:0,month:4,week:3,year:5};function mL(e={}){let t=FI();return yL({...e,privateKey:t})}async function hL(e){let{createFn:t,label:n,rpId:r,userId:i}=e,a=await YI({authenticatorSelection:{requireResidentKey:!0,residentKey:`required`,userVerification:`required`},createFn:t,extensions:{credProps:!0},rp:r?{id:r,name:r}:void 0,user:{displayName:n,id:new Uint8Array(i??vf(n)),name:n}});return xL({...e,credential:{id:a.id,publicKey:a.publicKey},id:i?wf(i):Jh(a.publicKey,{includePrefix:!1})})}function gL(e={}){let t=FI();return SL({...e,privateKey:t})}async function _L(e={}){let t=await iL();return CL({...e,keyPair:t})}function vL(e,t={}){let{chainId:n=e.chainId}=t,{expiry:r=0,id:i,prehash:a=!1,role:o=`admin`,type:s}=e,c=(()=>{let t=e.publicKey;return t===`0x`?t:s===`secp256k1`||s===`address`?qf(t)===20||Yf(Kf(t,0,12))===0n?Kf(t,-20):ig(qh(t)):t})();return{...e,chainId:n,expiry:r,hash:wL({publicKey:c,type:s}),id:(i??c).toLowerCase(),prehash:a,publicKey:c.toLowerCase(),role:o,type:s}}function yL(e){let{chainId:t,expiry:n,feeToken:r,permissions:i,privateKey:a,role:o}=e,s=Jh(PI({privateKey:a}),{includePrefix:!1});return vL({chainId:t,expiry:n,feeToken:r,permissions:i,privateKey(){return a},publicKey:s,role:o,type:`p256`})}function bL(e,t){let{chainId:n}=t,{publicKey:r}=e,i=qf(r)===20||Yf(Kf(r,0,12))===0n,a={};for(let t of e.permissions)t.type===`call`&&(a.calls??=[],a.calls.push({signature:t.selector,to:t.to===`0x3232323232323232323232323232323232323232`?void 0:t.to})),t.type===`spend`&&(a.spend??=[],a.spend.push({limit:t.limit,period:t.period,token:t.token}));return vL({chainId:n,expiry:e.expiry,permissions:a,publicKey:e.publicKey,role:cL[e.role],type:i?`address`:sL[e.type]})}function xL(e){let{credential:t,id:n,rpId:r}=e,i=Jh(t.publicKey,{includePrefix:!1});return vL({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,id:n,permissions:e.permissions,privateKey:{credential:t,rpId:r},publicKey:i,role:e.role,type:`webauthn-p256`})}function SL(e){let{privateKey:t}=e,n=Jh(PI({privateKey:t}),{includePrefix:!1});return vL({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,permissions:e.permissions,privateKey:{privateKey(){return t}},publicKey:n,role:e.role,type:`webauthn-p256`})}function CL(e){let{chainId:t,expiry:n,feeToken:r,keyPair:i,permissions:a,role:o}=e,{privateKey:s}=i,c=Jh(i.publicKey,{includePrefix:!1});return vL({chainId:t,expiry:n,feeToken:r,permissions:a,prehash:!0,privateKey:s,publicKey:c,role:o,type:`p256`})}function wL(e){let{type:t}=e,n=TL(e.publicKey);return Hh(Hg([{type:`uint8`},{type:`bytes32`}],[fL[t],Hh(n)]))}function TL(e){return qf(e)<32?Wf(e,32):e}async function EL(e,t){let{address:n,storage:r,webAuthn:i,wrap:a=!0}=t,{privateKey:o,publicKey:s,type:c}=e;if(!o)throw Error(`Key does not have a private key to sign with. - -Key: -`+df(e,null,2));let l=(()=>n?lI({domain:{verifyingContract:n},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload)(),[u,d]=await(async()=>{if(c===`p256`){let{privateKey:t}=e;if(typeof t==`function`)return[d_(II({payload:l,privateKey:t()})),!1];if(t instanceof CryptoKey)return[d_(await aL({payload:l,privateKey:t})),!0]}if(c===`secp256k1`)return[d_(A_({payload:l,privateKey:o()})),!1];if(c===`webauthn-p256`){if(o.privateKey){let{payload:e,metadata:t}=eL({challenge:l,origin:`https://ithaca.xyz`,rpId:`ithaca.xyz`}),{r:n,s:r}=II({hash:!0,payload:e,privateKey:o.privateKey()});return[AL({metadata:t,signature:{r:n,s:r}}),!1]}let{credential:t,rpId:n}=o,a=`porto.webauthnVerified.${e.hash}`,s=Date.now(),c=!0;if(r){let e=await r.getItem(a);c=!e||s-e>6e5}let{signature:{r:u,s:d},raw:f,metadata:p}=await tL({challenge:l,credentialId:t.id,getFn:i?.getFn,rpId:n,userVerification:c?`required`:`preferred`}),m=f.response;if(!m?.userHandle)throw Error(`No user handle in response`,{cause:{response:m}});let h=wf(new Uint8Array(m.userHandle));if(e.id&&og(e.id)&&!ag(e.id,h))throw Error(`supplied webauthn key "${e.id}" does not match signature webauthn key "${h}"`,{cause:{id:h,key:e}});return c&&r&&await r.setItem(a,s),[AL({metadata:p,signature:{r:u,s:d}}),!1]}throw Error(`Key type "${c}" is not supported.\n\nKey:\n`+df(e,null,2))})();return a?jL(u,{keyType:c,prehash:d,publicKey:s}):u}function DL(e,t={}){let{expiry:n=0,prehash:r=!1,publicKey:i,role:a=`admin`,type:o}=e,{feeTokens:s,orchestrator:c}=t,l=Object.entries(OL(e,{feeTokens:s})).map(([e,t])=>{if(e===`calls`)return t.map(({signature:e,to:t})=>({selector:(()=>e?Zf(e)?e:Z_(e):oL)(),to:t??`0x3232323232323232323232323232323232323232`,type:`call`}));if(e!==`feeToken`){if(e===`spend`)return t.map(({limit:e,period:t,token:n})=>({limit:e,period:t,token:n,type:`spend`}));throw Error(`Invalid permission type "${e}".`)}}).flat().filter(Boolean);return e.role===`session`&&c&&l.push({selector:oL,to:c,type:`call`}),{expiry:n,permissions:l??[],prehash:r,publicKey:TL(i),role:dL[a],type:uL[o]}}function OL(e,t){let{permissions:n}=e,r=n?.calls?[...n.calls]:[],i=n?.spend?[...n.spend]:[],a=t.feeTokens?.filter(e=>e.feeToken);if(a&&a.length>0){let t=kL(e,{feeTokens:a});if(t){let e=-1,n=pL.year;for(let r=0;re.feeToken.symbol===t.symbol?!0:!e.feeToken.symbol||e.feeToken.symbol===`native`?t.address===Q_:!1);if(!r)return;let i=LI(e.feeToken.limit,r.decimals);return{...r,value:i}}function AL(e){let{metadata:t,signature:n}=e;return Hg(Wg([`struct WebAuthnAuth { bytes authenticatorData; string clientDataJSON; uint256 challengeIndex; uint256 typeIndex; bytes32 r; bytes32 s; }`,`WebAuthnAuth auth`]),[{authenticatorData:t.authenticatorData,challengeIndex:BigInt(t.challengeIndex),clientDataJSON:t.clientDataJSON,r:I(n.r,{size:32}),s:I(n.s,{size:32}),typeIndex:BigInt(t.typeIndex)}])}function jL(e,t){let{keyType:n,prehash:r=!1,publicKey:i}=t,a=wL({publicKey:i,type:n});return Ug([`bytes`,`bytes32`,`bool`],[e,a,r])}function ML(e){let t=typeof e==`string`?{address:e}:e,n=t.sign?`privateKey`:`porto`,{address:r,sign:i,signMessage:a,signTransaction:o,signTypedData:s,type:c}=xI({address:t.address,sign({hash:e}){if(n===`porto`)throw Error("`sign` not supported on porto accounts.");if(!t.sign)throw Error("`sign` not supported.");return t.sign({hash:e})},signMessage({message:e}){return this.sign({hash:Th(e)})},signTransaction(){throw Error("`signTransaction` not supported on porto accounts.")},signTypedData(e){return this.sign({hash:Nh(e)})}});return{address:r,keys:t.keys??void 0,sign:i,signMessage:a,signTransaction:o,signTypedData:s,source:n,type:c}}function NL(e,t={}){let{keys:n}=t,r=ig(E_({privateKey:e}));return ML({address:r,keys:n,async sign({hash:t}){return d_(A_({payload:t,privateKey:e}))},source:`privateKey`})}function PL(e,t={}){let{key:n,role:r}=t;if(n!==null){if(typeof n==`object`)return n;if(e.keys&&e.keys.length>0)return typeof n==`number`?e.keys[n]:e.keys.find(e=>e.privateKey&&(!r||e.role===r))}}async function FL(e,t){let{storage:n,replaySafe:r=!0,wrap:i=!0,webAuthn:a}=t,o=PL(e,t),s=(()=>r?lI({domain:{verifyingContract:e.address},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload)(),c=(()=>o?({hash:e})=>EL(o,{address:null,payload:e,storage:n,webAuthn:a,wrap:i}):e.source===`privateKey`?e.sign:void 0)();if(!c)throw Error(`cannot find key to sign with.`);return await c({hash:s})}op();function IL(e,t={}){return H_(e,t)}function LL(e,t,n){if(t===`Error`)return zL;if(t===`Panic`)return BL;if(Zf(t,{strict:!1})){let e=Kf(t,0,4);if(e===`0x08c379a0`)return zL;if(e===`0x4e487b71`)return BL}let r=U_(e,t,n);if(r.type!==`error`)throw new J_({name:t,type:`error`});return r}function RL(e){return W_(e)}const zL=IL({inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`}),BL=IL({inputs:[{name:`reason`,type:`uint8`}],name:`Panic`,type:`error`});O();var VL=class extends D{constructor(){super(`Function is not recognized.`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function,`,` - The address is not a contract.`],name:`FunctionSelectorNotRecognizedError`})}};za();function HL(e,t){let n=e.walk(e=>`data`in e);if(!n?.data)return e;if(n.data===RL(IL(`error FnSelectorNotRecognized()`)))return new VL;let r=null;for(let e of t.calls){let t=e;if(t.abi)try{if(!Ra({abi:t.abi,data:n.data}))continue;r=t}catch{}}return r?cs(n,{abi:r.abi,address:r.to,args:r.args,functionName:r.functionName}):e}Jd(),op();const Y=()=>iI([`0x`,B()],{message:`Needs string in format ^0x[A-Fa-f0-9]{40}$.`}),X=()=>iI([`0x`,B()],{message:`Needs string in format ^0x[A-Fa-f0-9]+$.`}),Z=()=>tI(X(),V(),{decode:e=>Xf(e),encode:e=>I(e)}),Q=()=>tI(X(),EF({message:`Required bigint`}),{decode:e=>Yf(e),encode:e=>I(e)});function UL(e){return W(e)}var WL=class extends P{constructor(){super(...arguments),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Schema.ValidationError`})}};function GL(e){let t=e,n=`Validation failed with ${t.issues.length} error${t.issues.length===1?``:`s`}:`;n+=` + `,d.appendChild(m),d.appendChild(f);let h=UB({from:HB(window,{targetOrigin:u.origin}),to:HB(f.contentWindow,{targetOrigin:u.origin}),waitForReady:!0});o?._setup(h,!0);let g=window.matchMedia(`(max-width: 460px)`),_=()=>{h.send(`__internal`,{type:`resize`,width:g.matches?460:461})};g.addEventListener(`change`,_),h.on(`ready`,t=>{let n=e.internal.store.getState().chainIds.filter(e=>t.chainIds.includes(e));n.length===0&&(n=t.chainIds),s.setState(e=>({...e,chainIds:n})),h.send(`__internal`,{chainIds:n,mode:`iframe`,referrer:ZB(),theme:a,type:`init`}),_()}),h.on(`rpc-response`,e=>{r([e._request])&&(f.src=f.src),$B(s,e)}),h.on(`account`,e=>{eV(s,e)}),h.on(`__internal`,e=>{e.type===`switch`&&e.mode===`popup`&&(c.open(),c.syncRequests(s.getState().requestQueue))});let v=null,y=null,b=()=>QB(s),x=e=>{e.key===`Escape`&&QB(s)},S=new MutationObserver(e=>{for(let t of e){if(t.type!==`attributes`)continue;let e=t.attributeName;e&&e===`inert`&&d.removeAttribute(e)}});S.observe(d,{attributeOldValue:!0,attributes:!0});let ee=!1,C=()=>{ee&&(ee=!1,d.removeEventListener(`click`,b),document.removeEventListener(`keydown`,x),d.style.pointerEvents=`none`,y?.focus(),y=null,Object.assign(document.body.style,v??``),document.body.style.overflow=v?.overflow??``)},te=()=>{ee||(ee=!0,d.addEventListener(`click`,b),document.addEventListener(`keydown`,x),f.focus(),d.style.pointerEvents=`auto`,v=Object.assign({},document.body.style),document.body.style.overflow=`hidden`)},ne=!1,re=()=>{ne||(ne=!0,document.activeElement instanceof HTMLElement&&(y=document.activeElement),d.removeAttribute(`hidden`),d.removeAttribute(`aria-closed`),d.showModal())},ie=()=>{if(ne){ne=!1,d.setAttribute(`hidden`,`true`),d.setAttribute(`aria-closed`,`true`),d.close();for(let e of d.parentNode?Array.from(d.parentNode.children):[])e!==d&&e.hasAttribute(`inert`)&&e.removeAttribute(`inert`)}};return{close(){c.close(),l=!1,h.send(`__internal`,{mode:`iframe`,referrer:ZB(),type:`init`}),ie(),C()},destroy(){c.close(),l=!1,C(),ie(),c.destroy(),h.destroy(),d.remove(),S.disconnect(),g.removeEventListener(`change`,_)},open(){l||(l=!0,re(),te(),h.send(`__internal`,{mode:`iframe`,referrer:ZB(),type:`init`}))},async secure(){let{trustedHosts:e}=await h.waitForReady(),n=(()=>{if(t)return!0;let e=window.location.protocol.startsWith(`https`);return e||jB.warnOnce(`Detected insecure protocol (HTTP).`,`\n\nThe Porto iframe is not supported on HTTP origins (${window.location.origin})`,`due to lack of WebAuthn support.`,`See https://porto.sh/sdk#secure-origins-https for more information.`),e})(),r=kB(),i=!!e?.includes(window.location.hostname),a=!!(r||i);return a||jB.warnOnce([`Warning: Browser does not support IntersectionObserver v2 or host "${u.hostname}" is not trusted by Porto.`,`This may result in the dialog falling back to a popup.`,``,`Add "${u.hostname}" to the trusted hosts list to enable iframe dialog: https://github.com/ithacaxyz/porto/edit/main/src/trusted-hosts.ts`].join(` +`)),{frame:a,host:i,protocol:n}},async syncRequests(e){let{methodPolicies:t}=await h.waitForReady(),n=await this.secure(),i=e?.every(e=>t?.find(t=>t.method===e.request.method)?.modes?.headless===!0),a=r(e.map(e=>e.request));if(!i&&(a||!n.protocol||!n.frame))c.syncRequests(e);else{let n=e.some(e=>XB(e.request,{methodPolicies:t,targetOrigin:u.origin}));!l&&n&&this.open(),h.send(`rpc-requests`,e)}}}},supportsHeadless:!0})}function qB(e={}){if(typeof window>`u`)return JB();let{type:t=`auto`,size:n=YB}=e;return GB({name:`popup`,setup(e){let{host:r,internal:i,themeController:a}=e,{store:o}=i,s=new URL(r),c=null,l=t===`page`||t===`auto`&&FB()?`page`:`popup`;function u(){c&&QB(o)}let d=(()=>{let e=setInterval(()=>{c?.closed&&QB(o)},100);return()=>clearInterval(e)})(),f;return a?._setup(null,!0),{close(){c&&=(c.close(),null)},destroy(){this.close(),window.removeEventListener(`focus`,u),f?.destroy(),d()},open(){if(l===`popup`){let e=(window.innerWidth-n.width)/2+window.screenX,t=window.screenY+100;c=window.open(tV(r),`_blank`,`width=${n.width},height=${n.height},left=${e},top=${t}`)}else c=window.open(tV(r),`_blank`);if(!c)throw Error(`Failed to open popup`);f=UB({from:HB(window,{targetOrigin:s.origin}),to:HB(c,{targetOrigin:s.origin}),waitForReady:!0}),a?._setup(f,!1),f.send(`__internal`,{mode:l===`page`?`page`:`popup`,referrer:ZB(),theme:a?.getTheme()??e.theme,type:`init`}),f.on(`rpc-response`,e=>$B(o,e)),f.on(`account`,e=>{eV(o,e)}),window.removeEventListener(`focus`,u),window.addEventListener(`focus`,u)},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(e){e.some(e=>XB(e.request))&&((!c||c.closed)&&this.open(),c?.focus()),f?.send(`rpc-requests`,e)}}},supportsHeadless:!1})}function JB(){return GB({name:`noop`,setup(){return{close(){},destroy(){},open(){},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(){}}},supportsHeadless:!0})}var YB={height:282,width:360};function XB(e,t={}){let{methodPolicies:n,targetOrigin:r}=t,i=n?.find(t=>t.method===e.method);return i&&i.modes?.headless?!!(typeof i.modes.headless==`object`&&i.modes.headless.sameOrigin&&r!==window.location.origin):!0}function ZB(){return{icon:(()=>{let e=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: dark)"]`)?.href,t=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: light)"]`)?.href??document.querySelector(`link[rel="icon"]`)?.href;return e&&t&&e!==t?{dark:e,light:t}:window.matchMedia(`(prefers-color-scheme: dark)`).matches?e:t})(),title:document.title}}function QB(e){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>({account:e.account,error:new vI,request:e.request,status:`error`}))}))}function $B(e,t){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>e.request.id===t.id?t.error?{account:e.account,error:t.error,request:e.request,status:`error`}:{account:e.account,request:e.request,result:t.result,status:`success`}:e)}))}function eV(e,t){let{account:n}=t;e.setState(e=>({...e,accounts:[TB({address:n.address,keys:n.capabilities?.admins?.map(e=>dB(e))??[]})]}))}function tV(e){let t=new URL(e),n=new URLSearchParams(window.location.search);for(let[e,r]of n.entries())e.startsWith(`porto.`)&&t.searchParams.set(e.slice(6),r);return t.toString()}function nV(e){let t=new CustomEvent(`eip6963:announceProvider`,{detail:Object.freeze(e)});window.dispatchEvent(t);let n=()=>window.dispatchEvent(t);return window.addEventListener(`eip6963:requestProvider`,n),()=>window.removeEventListener(`eip6963:requestProvider`,n)}Object.freeze({status:`aborted`});function B(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;en?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var rV=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}},iV={};function aV(e){return e&&Object.assign(iV,e),iV}function oV(e,t){return typeof t==`bigint`?t.toString():t}function sV(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function cV(e){return e==null}function lV(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}var uV=Symbol(`evaluating`);function dV(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==uV)return r===void 0&&(r=uV,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}function fV(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function pV(...e){let t={};for(let n of e)Object.assign(t,Object.getOwnPropertyDescriptors(n));return Object.defineProperties({},t)}var mV=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function hV(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function gV(e){if(hV(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(hV(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}var _V=new Set([`string`,`number`,`bigint`,`boolean`,`symbol`,`undefined`]);function vV(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function yV(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function bV(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function xV(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function SV(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.pick() cannot be used on object schemas containing refinements`);return yV(e,pV(e._zod.def,{get shape(){let e={};for(let r in t){if(!(r in n.shape))throw Error(`Unrecognized key: "${r}"`);t[r]&&(e[r]=n.shape[r])}return fV(this,`shape`,e),e},checks:[]}))}function CV(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.omit() cannot be used on object schemas containing refinements`);return yV(e,pV(e._zod.def,{get shape(){let r={...e._zod.def.shape};for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return fV(this,`shape`,r),r},checks:[]}))}function wV(e,t,n){let r=t._zod.def.checks;if(r&&r.length>0)throw Error(`.partial() cannot be used on object schemas containing refinements`);return yV(t,pV(t._zod.def,{get shape(){let r=t._zod.def.shape,i={...r};if(n)for(let t in n){if(!(t in r))throw Error(`Unrecognized key: "${t}"`);n[t]&&(i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t])}else for(let t in r)i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t];return fV(this,`shape`,i),i},checks:[]}))}function TV(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function DV(e){return typeof e==`string`?e:e?.message}function OV(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=DV(e.inst?._zod.def?.error?.(e))??DV(t?.error?.(e))??DV(n.customError?.(e))??DV(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function kV(e){return Array.isArray(e)?`array`:typeof e==`string`?`string`:`unknown`}var AV=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,oV,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},jV=B(`$ZodError`,AV),MV=B(`$ZodError`,AV,{Parent:Error}),NV=e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new rV;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>OV(e,a,aV())));throw mV(t,i?.callee),t}return o.value},PV=NV(MV),FV=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>OV(e,a,aV())));throw mV(t,i?.callee),t}return o.value})(MV),IV=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new rV;return a.issues.length?{success:!1,error:new(e??jV)(a.issues.map(e=>OV(e,i,aV())))}:{success:!0,data:a.value}})(MV),LV=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>OV(e,i,aV())))}:{success:!0,data:a.value}})(MV),RV=(e=>(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return NV(e)(t,n,i)})(MV),zV=(e=>(t,n,r)=>NV(e)(t,n,r))(MV),BV=e=>{let t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\s\\S]*`;return RegExp(`^${t}$`)},VV=/^-?\d+n?$/,HV=/^-?\d+(?:\.\d+)?$/,UV=/^(?:true|false)$/i,WV=/^null$/i,GV=/^undefined$/i,KV=B(`$ZodCheck`,(e,t)=>{var n;e._zod??={},e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),qV={number:`number`,bigint:`bigint`,object:`date`},JV=B(`$ZodCheckGreaterThan`,(e,t)=>{KV.init(e,t);let n=qV[typeof t.value];e._zod.onattach.push(e=>{let n=e._zod.bag,r=(t.inclusive?n.minimum:n.exclusiveMinimum)??-1/0;t.value>r&&(t.inclusive?n.minimum=t.value:n.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:`too_small`,minimum:typeof t.value==`object`?t.value.getTime():t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),YV=B(`$ZodCheckMinLength`,(e,t)=>{var n;KV.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!cV(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag.minimum??-1/0;t.minimum>n&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{let r=n.value;if(r.length>=t.minimum)return;let i=kV(r);n.issues.push({origin:i,code:`too_small`,minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort})}}),XV=B(`$ZodCheckStringFormat`,(e,t)=>{var n,r;KV.init(e,t),e._zod.onattach.push(e=>{let n=e._zod.bag;n.format=t.format,t.pattern&&(n.patterns??=new Set,n.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:t.format,input:n.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),ZV=B(`$ZodCheckRegex`,(e,t)=>{XV.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:`regex`,input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),QV={major:4,minor:3,patch:6},$V=B(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=QV;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=TV(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new rV;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=TV(e,t))});else{if(e.issues.length===t)continue;r||=TV(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(TV(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new rV;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new rV;return o.then(e=>t(e,r,a))}return t(o,r,a)}}dV(e,`~standard`,()=>({validate:t=>{try{let n=IV(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return LV(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),eH=B(`$ZodString`,(e,t)=>{$V.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??BV(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),tH=B(`$ZodNumber`,(e,t)=>{$V.init(e,t),e._zod.pattern=e._zod.bag.pattern??HV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}let i=n.value;if(typeof i==`number`&&!Number.isNaN(i)&&Number.isFinite(i))return n;let a=typeof i==`number`?Number.isNaN(i)?`NaN`:Number.isFinite(i)?void 0:`Infinity`:void 0;return n.issues.push({expected:`number`,code:`invalid_type`,input:i,inst:e,...a?{received:a}:{}}),n}}),nH=B(`$ZodBoolean`,(e,t)=>{$V.init(e,t),e._zod.pattern=UV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=!!n.value}catch{}let i=n.value;return typeof i==`boolean`||n.issues.push({expected:`boolean`,code:`invalid_type`,input:i,inst:e}),n}}),rH=B(`$ZodBigInt`,(e,t)=>{$V.init(e,t),e._zod.pattern=VV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=BigInt(n.value)}catch{}return typeof n.value==`bigint`||n.issues.push({expected:`bigint`,code:`invalid_type`,input:n.value,inst:e}),n}}),iH=B(`$ZodUndefined`,(e,t)=>{$V.init(e,t),e._zod.pattern=GV,e._zod.values=new Set([void 0]),e._zod.optin=`optional`,e._zod.optout=`optional`,e._zod.parse=(t,n)=>{let r=t.value;return r===void 0||t.issues.push({expected:`undefined`,code:`invalid_type`,input:r,inst:e}),t}}),aH=B(`$ZodNull`,(e,t)=>{$V.init(e,t),e._zod.pattern=WV,e._zod.values=new Set([null]),e._zod.parse=(t,n)=>{let r=t.value;return r===null||t.issues.push({expected:`null`,code:`invalid_type`,input:r,inst:e}),t}}),oH=B(`$ZodAny`,(e,t)=>{$V.init(e,t),e._zod.parse=e=>e}),sH=B(`$ZodUnknown`,(e,t)=>{$V.init(e,t),e._zod.parse=e=>e}),cH=B(`$ZodDate`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=new Date(n.value)}catch{}let i=n.value,a=i instanceof Date;return a&&!Number.isNaN(i.getTime())||n.issues.push({expected:`date`,code:`invalid_type`,input:i,...a?{received:`Invalid Date`}:{},inst:e}),n}});function lH(e,t,n){e.issues.length&&t.issues.push(...EV(n,e.issues)),t.value[n]=e.value}var uH=B(`$ZodArray`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;elH(t,n,e))):lH(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function dH(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...EV(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function fH(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key "${n}": expected a Zod schema`);let n=xV(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function pH(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>dH(e,n,i,t,u))):dH(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}var mH=B(`$ZodObject`,(e,t)=>{if($V.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=sV(()=>fH(t));dV(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=hV,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>dH(n,t,e,s,r))):dH(i,t,e,s,r)}return i?pH(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}});function hH(e,t,n,r){for(let n of e)if(n.issues.length===0)return t.value=n.value,t;let i=e.filter(e=>!TV(e));return i.length===1?(t.value=i[0].value,i[0]):(t.issues.push({code:`invalid_union`,input:t.value,inst:n,errors:e.map(e=>e.issues.map(e=>OV(e,r,aV())))}),t)}var gH=B(`$ZodUnion`,(e,t)=>{$V.init(e,t),dV(e._zod,`optin`,()=>t.options.some(e=>e._zod.optin===`optional`)?`optional`:void 0),dV(e._zod,`optout`,()=>t.options.some(e=>e._zod.optout===`optional`)?`optional`:void 0),dV(e._zod,`values`,()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),dV(e._zod,`pattern`,()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>lV(e.source)).join(`|`)})$`)}});let n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(i,a)=>{if(n)return r(i,a);let o=!1,s=[];for(let e of t.options){let t=e._zod.run({value:i.value,issues:[]},a);if(t instanceof Promise)s.push(t),o=!0;else{if(t.issues.length===0)return t;s.push(t)}}return o?Promise.all(s).then(t=>hH(t,i,e,a)):hH(s,i,e,a)}}),_H=B(`$ZodDiscriminatedUnion`,(e,t)=>{t.inclusive=!1,gH.init(e,t);let n=e._zod.parse;dV(e._zod,`propValues`,()=>{let e={};for(let n of t.options){let r=n._zod.propValues;if(!r||Object.keys(r).length===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`);for(let[t,n]of Object.entries(r)){e[t]||(e[t]=new Set);for(let r of n)e[t].add(r)}}return e});let r=sV(()=>{let e=t.options,n=new Map;for(let r of e){let e=r._zod.propValues?.[t.discriminator];if(!e||e.size===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`);for(let t of e){if(n.has(t))throw Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,r)}}return n});e._zod.parse=(i,a)=>{let o=i.value;if(!hV(o))return i.issues.push({code:`invalid_type`,expected:`object`,input:o,inst:e}),i;let s=r.value.get(o?.[t.discriminator]);return s?s._zod.run(i,a):t.unionFallback?n(i,a):(i.issues.push({code:`invalid_union`,errors:[],note:`No matching discriminator`,discriminator:t.discriminator,input:o,path:[t.discriminator],inst:e}),i)}}),vH=B(`$ZodTuple`,(e,t)=>{$V.init(e,t);let n=t.items;e._zod.parse=(r,i)=>{let a=r.value;if(!Array.isArray(a))return r.issues.push({input:a,inst:e,expected:`tuple`,code:`invalid_type`}),r;r.value=[];let o=[],s=[...n].reverse().findIndex(e=>e._zod.optin!==`optional`),c=s===-1?0:n.length-s;if(!t.rest){let t=a.length>n.length,i=a.length=a.length&&l>=c)continue;let t=e._zod.run({value:a[l],issues:[]},i);t instanceof Promise?o.push(t.then(e=>yH(e,r,l))):yH(t,r,l)}if(t.rest){let e=a.slice(n.length);for(let n of e){l++;let e=t.rest._zod.run({value:n,issues:[]},i);e instanceof Promise?o.push(e.then(e=>yH(e,r,l))):yH(e,r,l)}}return o.length?Promise.all(o).then(()=>r):r}});function yH(e,t,n){e.issues.length&&t.issues.push(...EV(n,e.issues)),t.value[n]=e.value}var bH=B(`$ZodRecord`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!gV(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...EV(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...EV(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&HV.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>OV(e,r,aV())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...EV(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...EV(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}}),xH=B(`$ZodLiteral`,(e,t)=>{if($V.init(e,t),t.values.length===0)throw Error(`Cannot create literal schema with no valid values`);let n=new Set(t.values);e._zod.values=n,e._zod.pattern=RegExp(`^(${t.values.map(e=>typeof e==`string`?vV(e):e?vV(e.toString()):String(e)).join(`|`)})$`),e._zod.parse=(r,i)=>{let a=r.value;return n.has(a)||r.issues.push({code:`invalid_value`,values:t.values,input:a,inst:e}),r}});function SH(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}var CH=B(`$ZodOptional`,(e,t)=>{$V.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,dV(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),dV(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${lV(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>SH(t,e.value)):SH(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}}),wH=B(`$ZodNullable`,(e,t)=>{$V.init(e,t),dV(e._zod,`optin`,()=>t.innerType._zod.optin),dV(e._zod,`optout`,()=>t.innerType._zod.optout),dV(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${lV(e.source)}|null)$`):void 0}),dV(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,n)=>e.value===null?e:t.innerType._zod.run(e,n)}),TH=B(`$ZodPipe`,(e,t)=>{$V.init(e,t),dV(e._zod,`values`,()=>t.in._zod.values),dV(e._zod,`optin`,()=>t.in._zod.optin),dV(e._zod,`optout`,()=>t.out._zod.optout),dV(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if(n.direction===`backward`){let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>EH(e,t.in,n)):EH(r,t.in,n)}let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>EH(e,t.out,n)):EH(r,t.out,n)}});function EH(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}var DH=B(`$ZodCodec`,(e,t)=>{$V.init(e,t),dV(e._zod,`values`,()=>t.in._zod.values),dV(e._zod,`optin`,()=>t.in._zod.optin),dV(e._zod,`optout`,()=>t.out._zod.optout),dV(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if((n.direction||`forward`)===`forward`){let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>OH(e,t,n)):OH(r,t,n)}else{let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>OH(e,t,n)):OH(r,t,n)}}});function OH(e,t,n){if(e.issues.length)return e.aborted=!0,e;if((n.direction||`forward`)===`forward`){let r=t.transform(e.value,e);return r instanceof Promise?r.then(r=>kH(e,r,t.out,n)):kH(e,r,t.out,n)}else{let r=t.reverseTransform(e.value,e);return r instanceof Promise?r.then(r=>kH(e,r,t.in,n)):kH(e,r,t.in,n)}}function kH(e,t,n,r){return e.issues.length?(e.aborted=!0,e):n._zod.run({value:t,issues:e.issues},r)}var AH=B(`$ZodReadonly`,(e,t)=>{$V.init(e,t),dV(e._zod,`propValues`,()=>t.innerType._zod.propValues),dV(e._zod,`values`,()=>t.innerType._zod.values),dV(e._zod,`optin`,()=>t.innerType?._zod?.optin),dV(e._zod,`optout`,()=>t.innerType?._zod?.optout),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(jH):jH(r)}});function jH(e){return e.value=Object.freeze(e.value),e}var MH=B(`$ZodTemplateLiteral`,(e,t)=>{$V.init(e,t);let n=[];for(let e of t.parts)if(typeof e==`object`&&e){if(!e._zod.pattern)throw Error(`Invalid template literal part, no pattern found: ${[...e._zod.traits].shift()}`);let t=e._zod.pattern instanceof RegExp?e._zod.pattern.source:e._zod.pattern;if(!t)throw Error(`Invalid template literal part: ${e._zod.traits}`);let r=t.startsWith(`^`)?1:0,i=t.endsWith(`$`)?t.length-1:t.length;n.push(t.slice(r,i))}else if(e===null||_V.has(typeof e))n.push(vV(`${e}`));else throw Error(`Invalid template literal part: ${e}`);e._zod.pattern=RegExp(`^${n.join(``)}$`),e._zod.parse=(n,r)=>typeof n.value==`string`?(e._zod.pattern.lastIndex=0,e._zod.pattern.test(n.value)||n.issues.push({input:n.value,inst:e,code:`invalid_format`,format:t.format??`template_literal`,pattern:e._zod.pattern.source}),n):(n.issues.push({input:n.value,inst:e,expected:`string`,code:`invalid_type`}),n)}),NH,PH=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function FH(){return new PH}(NH=globalThis).__zod_globalRegistry??(NH.__zod_globalRegistry=FH()),globalThis.__zod_globalRegistry;function IH(e,t){return new e({type:`string`,...bV(t)})}function LH(e,t){return new e({type:`number`,checks:[],...bV(t)})}function RH(e,t){return new e({type:`boolean`,...bV(t)})}function zH(e,t){return new e({type:`bigint`,...bV(t)})}function BH(e,t){return new e({type:`undefined`,...bV(t)})}function VH(e,t){return new e({type:`null`,...bV(t)})}function HH(e){return new e({type:`any`})}function UH(e){return new e({type:`unknown`})}function WH(e,t){return new e({type:`date`,...bV(t)})}function GH(e,t){return new JV({check:`greater_than`,...bV(t),value:e,inclusive:!0})}function KH(e,t){return new YV({check:`min_length`,...bV(t),minimum:e})}function qH(e,t){return new ZV({check:`string_format`,format:`regex`,...bV(t),pattern:e})}var JH=B(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);$V.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>PV(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>IV(e,t,n),e.parseAsync=async(t,n)=>FV(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>LV(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>yV(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),YH=B(`ZodMiniString`,(e,t)=>{eH.init(e,t),JH.init(e,t)});function V(e){return IH(YH,e)}var XH=B(`ZodMiniNumber`,(e,t)=>{tH.init(e,t),JH.init(e,t)});function H(e){return LH(XH,e)}var ZH=B(`ZodMiniBoolean`,(e,t)=>{nH.init(e,t),JH.init(e,t)});function QH(e){return RH(ZH,e)}var $H=B(`ZodMiniBigInt`,(e,t)=>{rH.init(e,t),JH.init(e,t)});function eU(e){return zH($H,e)}var tU=B(`ZodMiniUndefined`,(e,t)=>{iH.init(e,t),JH.init(e,t)});function nU(e){return BH(tU,e)}var rU=B(`ZodMiniNull`,(e,t)=>{aH.init(e,t),JH.init(e,t)});function iU(e){return VH(rU,e)}var aU=B(`ZodMiniAny`,(e,t)=>{oH.init(e,t),JH.init(e,t)});function oU(){return HH(aU)}var sU=B(`ZodMiniUnknown`,(e,t)=>{sH.init(e,t),JH.init(e,t)});function cU(){return UH(sU)}var lU=B(`ZodMiniDate`,(e,t)=>{cH.init(e,t),JH.init(e,t)});function uU(e){return WH(lU,e)}var dU=B(`ZodMiniArray`,(e,t)=>{uH.init(e,t),JH.init(e,t)});function U(e,t){return new dU({type:`array`,element:e,...bV(t)})}var fU=B(`ZodMiniObject`,(e,t)=>{mH.init(e,t),JH.init(e,t),dV(e,`shape`,()=>t.shape)});function W(e,t){return new fU({type:`object`,shape:e??{},...bV(t)})}function pU(e,t){return SV(e,t)}function mU(e,t){return CV(e,t)}function hU(e,t){return wV(CU,e,t)}var gU=B(`ZodMiniUnion`,(e,t)=>{gH.init(e,t),JH.init(e,t)});function G(e,t){return new gU({type:`union`,options:e,...bV(t)})}var _U=B(`ZodMiniDiscriminatedUnion`,(e,t)=>{_H.init(e,t),JH.init(e,t)});function vU(e,t,n){return new _U({type:`union`,options:t,discriminator:e,...bV(n)})}var yU=B(`ZodMiniTuple`,(e,t)=>{vH.init(e,t),JH.init(e,t)});function K(e,t,n){let r=t instanceof $V;return new yU({type:`tuple`,items:e,rest:r?t:null,...bV(r?n:t)})}var bU=B(`ZodMiniRecord`,(e,t)=>{bH.init(e,t),JH.init(e,t)});function xU(e,t,n){return new bU({type:`record`,keyType:e,valueType:t,...bV(n)})}var SU=B(`ZodMiniLiteral`,(e,t)=>{xH.init(e,t),JH.init(e,t)});function q(e,t){return new SU({type:`literal`,values:Array.isArray(e)?e:[e],...bV(t)})}var CU=B(`ZodMiniOptional`,(e,t)=>{CH.init(e,t),JH.init(e,t)});function J(e){return new CU({type:`optional`,innerType:e})}var wU=B(`ZodMiniNullable`,(e,t)=>{wH.init(e,t),JH.init(e,t)});function TU(e){return new wU({type:`nullable`,innerType:e})}function EU(e){return J(TU(e))}var DU=B(`ZodMiniPipe`,(e,t)=>{TH.init(e,t),JH.init(e,t)}),OU=B(`ZodMiniCodec`,(e,t)=>{DU.init(e,t),DH.init(e,t)});function kU(e,t,n){return new OU({type:`pipe`,in:e,out:t,transform:n.decode,reverseTransform:n.encode})}var AU=B(`ZodMiniReadonly`,(e,t)=>{AH.init(e,t),JH.init(e,t)});function Y(e){return new AU({type:`readonly`,innerType:e})}var jU=B(`ZodMiniTemplateLiteral`,(e,t)=>{MH.init(e,t),JH.init(e,t)});function MU(e,t){return new jU({type:`template_literal`,parts:e,...bV(t)})}function NU(e,t={}){return _z(e,t)}function PU(e,t,n){if(t===`Error`)return FU;if(t===`Panic`)return IU;if(cL(t,{strict:!1})){let e=rL(t,0,4);if(e===`0x08c379a0`)return FU;if(e===`0x4e487b71`)return IU}let r=vz(e,t,n);if(r.type!==`error`)throw new Cz({name:t,type:`error`});return r}var FU=NU({inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`}),IU=NU({inputs:[{name:`reason`,type:`uint8`}],name:`Panic`,type:`error`});D();var LU=class extends E{constructor(){super(`Function is not recognized.`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function,`,` - The address is not a contract.`],name:`FunctionSelectorNotRecognizedError`})}};function RU(e,t={}){return iv(e,t)}function zU(e){return ov(e)}Ha();function BU(e,t){let n=e.walk(e=>`data`in e);if(!n?.data)return e;if(n.data===zU(RU(`error FnSelectorNotRecognized()`)))return new LU;let r=null;for(let e of t.calls){let t=e;if(t.abi)try{if(!Va({abi:t.abi,data:n.data}))continue;r=t}catch{}}return r?ls(n,{abi:r.abi,address:r.to,args:r.args,functionName:r.functionName}):e}var X=()=>MU([`0x`,V()],{message:`Needs string in format ^0x[A-Fa-f0-9]{40}$.`}),Z=()=>MU([`0x`,V()],{message:`Needs string in format ^0x[A-Fa-f0-9]+$.`}),Q=()=>kU(Z(),H(),{decode:e=>sL(e),encode:e=>$I(e)}),VU=()=>kU(Z(),eU({message:`Required bigint`}),{decode:e=>oL(e),encode:e=>$I(e)});function HU(e){return G(e)}var UU=class extends z{constructor(){super(...arguments),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Schema.ValidationError`})}};function WU(e){let t=e,n=`Validation failed with ${t.issues.length} error${t.issues.length===1?``:`s`}:`;n+=` `;for(let e of t.issues)e&&(n+=` -`,n+=KL(e));return new WL(n)}function KL(e,t=0){let n=qL(e.path),r=`- ${n?`${n}: `:``}`,i=` `.repeat(t+1),a=r;switch(e.code){case`invalid_type`:{let t=e.expected,n=e.input?JL(e):`undefined`;a+=`Expected ${t}. ${e.message===`Invalid input`?``:e.message}`,n!==`undefined`&&(a+=`but received ${n}`);break}case`too_big`:{let t=e.maximum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at most`:`less than`} ${t}`;break}case`too_small`:{let t=e.minimum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at least`:`greater than`} ${t}`;break}case`invalid_format`:switch(e.format){case`regex`:a+=`Must match pattern: ${e.pattern}`;break;case`starts_with`:a+=`Must start with "${e.prefix}"`;break;case`ends_with`:a+=`Must end with "${e.suffix}"`;break;case`includes`:a+=`Must include "${e.includes}"`;break;case`template_literal`:a+=`Must match pattern: ${e.pattern}`;break;default:a+=`Invalid ${e.format} format`}break;case`not_multiple_of`:a+=`Number must be a multiple of ${e.divisor}`;break;case`unrecognized_keys`:{let t=e.keys.map(e=>`"${e}"`).join(`, `);a+=`Unrecognized key${e.keys.length>1?`s`:``}: ${t}`;break}case`invalid_union`:{let n=e.errors&&e.errors.length>0;a+=`Invalid union value.`,n&&e.errors.forEach(e=>{e.length>0&&e.forEach(e=>{a+=` -`,a+=i,a+=KL(e,t+1)})});break}case`invalid_key`:a+=`Invalid ${e.origin} key`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` -`,a+=i,a+=KL(e,t+1)});break;case`invalid_element`:a+=`Invalid ${e.origin} element at key "${e.key}"`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` -`,a+=i,a+=KL(e,t+1)});break;case`invalid_value`:{let t=e.values.map(e=>JSON.stringify(e)).join(`, `);e.values.length>1?a+=`Expected one of: ${t}`:a+=`Expected ${t}`;break}case`custom`:a+=e.message||`Custom validation failed`;break;default:a+=e.message||`Validation failed`}return a}function qL(e){return e.length===0?``:"at `"+e.map((e,t)=>typeof e==`number`?`[${e}]`:typeof e==`symbol`?`[${e.toString()}]`:/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)&&t>0?`.${e}`:t===0&&/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)?e:`["${e}"]`).join(``)+"`"}function JL(e){let t=e.input;if(t===void 0)return`undefined`;if(t===null)return`null`;let n=typeof t;return n===`object`?Array.isArray(t)?`array`:t instanceof Date?`date`:t instanceof Map?`map`:t instanceof Set?`set`:`object`:n}const YL=U({selector:X(),to:Y(),type:K(`call`)}),XL=U({limit:Q(),period:W([K(`minute`),K(`hour`),K(`day`),K(`week`),K(`month`),K(`year`)]),token:q(W([Y(),AF()])),type:K(`spend`)}),ZL=W([YL,XL]),QL=U({expiry:Z(),prehash:q(wF()),publicKey:X(),role:W([K(`admin`),K(`normal`)]),type:W([K(`p256`),K(`secp256k1`),K(`webauthnp256`)])}),$L=U({...QL.shape,permissions:J(H(ZL))});var eR;(function(e){e.AssetDiffAsset=W([U({address:q(W([Y(),AF()])),decimals:q(W([V(),AF()])),direction:W([K(`incoming`),K(`outgoing`)]),fiat:q(U({currency:B(),value:tI(B(),V(),{decode:e=>Number(e),encode:e=>String(e)})})),name:q(W([B(),AF()])),symbol:B(),type:K(`erc20`),value:Q()}),U({address:q(W([Y(),AF()])),direction:W([K(`incoming`),K(`outgoing`)]),fiat:q(U({currency:B(),value:tI(B(),V(),{decode:e=>Number(e),encode:e=>String(e)})})),name:q(W([B(),AF()])),symbol:B(),type:K(`erc721`),uri:B(),value:Q()}),U({address:AF(),decimals:q(W([V(),AF()])),direction:W([K(`incoming`),K(`outgoing`)]),fiat:q(U({currency:B(),value:tI(B(),V(),{decode:e=>Number(e),encode:e=>String(e)})})),symbol:B(),type:AF(),value:Q()})]),e.Response=qF(X(),J(H(J(G([Y(),J(H(e.AssetDiffAsset))])))))})(eR||={});var tR;(function(e){e.Request=J(H($L)),e.Response=J(H(U({...$L.shape,hash:X()})))})(tR||={});var nR;(function(e){e.Response=qF(X(),U({currency:B(),value:B()}))})(nR||={});var rR;(function(e){e.Request=U({feePayer:q(Y()),feeToken:q(Y()),nonce:q(Q())})})(rR||={});var iR;(function(e){e.Request=J(H(U({address:Y(),value:Q()})))})(iR||={});var aR;(function(e){e.Request=J(H(U({hash:X()}))),e.Response=J(H(U({hash:X()})))})(aR||={});const oR=U({eoa:Y(),executionData:X(),nonce:X(),signature:X()}),sR=U({...oR.shape,chainId:Z()}),cR=W([U({combinedGas:Q(),encodedFundTransfers:J(H(X())),encodedPreCalls:J(H(X())),eoa:Y(),executionData:X(),expiry:Q(),funder:Y(),funderSignature:X(),isMultichain:wF(),nonce:Q(),payer:Y(),paymentAmount:Q(),paymentMaxAmount:Q(),paymentRecipient:Y(),paymentSignature:X(),paymentToken:Y(),settler:Y(),settlerContext:X(),signature:X(),supportedAccountImplementation:Y()}),U({combinedGas:Q(),encodedFundTransfers:J(H(X())),encodedPreCalls:J(H(X())),eoa:Y(),executionData:X(),expiry:Q(),funder:Y(),funderSignature:X(),isMultichain:wF(),nonce:Q(),payer:Y(),paymentRecipient:Y(),paymentSignature:X(),paymentToken:Y(),prePaymentAmount:Q(),prePaymentMaxAmount:Q(),settler:Y(),settlerContext:X(),signature:X(),supportedAccountImplementation:Y(),totalPaymentAmount:Q(),totalPaymentMaxAmount:Q()})]);U({eoa:Y(),executionData:X(),nonce:Q()});const lR=U({address:W([Y(),AF()]),decimals:q(V()),deficit:Q(),fiat:q(U({currency:B(),value:B()})),name:q(B()),required:Q(),symbol:q(B())}),uR=U({additionalAuthorization:QF(U({address:Y(),chainId:Z(),nonce:Z(),r:X(),s:X(),yParity:Z()})),assetDeficits:q(H(lR)),authorizationAddress:q(W([Y(),AF()])),chainId:Z(),ethPrice:Q(),extraPayment:Q(),feeTokenDeficit:Q(),intent:cR,nativeFeeEstimate:U({maxFeePerGas:Q(),maxPriorityFeePerGas:Q()}),orchestrator:Y(),paymentTokenDecimals:V(),txGas:Q()}),dR=U({multiChainRoot:q(W([X(),AF()])),quotes:J(H(uR)).check(vF(1)),ttl:V()}),fR=U({...dR.shape,hash:X(),r:X(),s:X(),v:q(X()),yParity:q(X())}),pR=U({address:Y(),decimals:V(),feeToken:q(wF()),interop:q(wF()),nativeRate:q(Q()),symbol:B(),uid:B()}),mR=B().check(yF(/^[A-Z0-9]+$/));var hR=U({address:Y(),chainId:Z(),nonce:Z()}),gR=U({...hR.shape,r:X(),s:X(),yParity:Z()}),_R=U({data:q(X()),to:Y(),value:q(Q())}),vR;(function(e){e.Parameters=U({address:Y(),secret:B()}),e.Request=U({method:K(`account_getOnrampContactInfo`),params:J(G([e.Parameters]))}),e.Response=U({email:q(B()),phone:q(B()),phoneVerifiedAt:q(V())})})(vR||={});var yR;(function(e){e.Parameters=U({address:Y()}),e.Request=U({method:K(`account_onrampStatus`),params:J(G([e.Parameters]))}),e.Response=U({email:q(V()),phone:q(V())})})(yR||={});var bR;(function(e){e.Parameters=U({phone:B(),walletAddress:Y()}),e.Request=U({method:K(`account_resendVerifyPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(bR||={});var xR;(function(e){e.Parameters=U({email:B().check(yF(/^.*@.*$/)),walletAddress:Y()}),e.Request=U({method:K(`account_setEmail`),params:J(G([e.Parameters]))}),e.Response=AF()})(xR||={});var SR;(function(e){e.Parameters=U({phone:B(),walletAddress:Y()}),e.Request=U({method:K(`account_setPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(SR||={});var CR;(function(e){e.Parameters=U({chainId:Z(),email:B(),signature:X(),token:B(),walletAddress:Y()}),e.Request=U({method:K(`account_verifyEmail`),params:J(G([e.Parameters]))}),e.Response=AF()})(CR||={});var wR;(function(e){e.Parameters=U({code:B(),phone:B(),walletAddress:Y()}),e.Request=U({method:K(`account_verifyPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(wR||={});var TR;(function(e){e.Request=U({method:K(`health`),params:OF()}),e.Response=U({quoteSigner:Y(),status:B(),version:B()})})(TR||={});var ER;(function(e){e.Parameters=U({address:Y(),chainId:Z(),tokenAddress:Y(),value:Q()}),e.Request=U({method:K(`wallet_addFaucetFunds`),params:J(G([e.Parameters]))}),e.Response=U({message:q(B()),transactionHash:X()})})(ER||={});var DR;(function(e){e.Parameters=U({chainId:Z(),id:X()}),e.Request=U({method:K(`wallet_getAccounts`),params:J(G([e.Parameters]))}),e.Response=J(H(U({address:Y(),keys:tR.Response})))})(DR||={});var OR;(function(e){e.Parameters=U({address:Y()}),e.Request=U({method:K(`wallet_getAuthorization`),params:J(G([e.Parameters]))}),e.Response=U({authorization:gR,data:X(),to:Y()})})(OR||={});var kR;(function(e){e.Request=U({method:K(`wallet_getCapabilities`),params:q(G([J(H(V()))]))});let t=U({address:Y(),version:q(W([B(),AF()]))});e.Response=qF(X(),U({contracts:U({accountImplementation:t,accountProxy:t,legacyAccountImplementations:J(H(t)),legacyOrchestrators:J(H(W([U({orchestrator:t,simulator:t}),t]))),orchestrator:t,simulator:t}),fees:U({quoteConfig:U({constantRate:q(W([V(),AF()])),gas:q(U({intentBuffer:q(V()),txBuffer:q(V())})),rateTtl:V(),ttl:V()}),recipient:Y(),tokens:J(H(pR))})}))})(kR||={});var AR;(function(e){let t=W([K(`native`),K(`erc20`),K(`erc721`),B()]);e.Parameters=U({account:Y(),assetFilter:q(qF(X(),J(H(U({address:W([Y(),K(`native`)]),type:t}))))),assetTypeFilter:q(J(H(t))),chainFilter:q(J(H(Z())))}),e.Request=U({method:K(`wallet_getAssets`),params:J(G([e.Parameters]))}),e.Price=U({currency:B(),value:tI(B(),V(),{decode:e=>Number(e),encode:e=>String(e)})}),e.Response=qF(B(),J(H(UL([U({address:Y(),balance:Q(),metadata:ZF(U({decimals:V(),fiat:QF(e.Price),name:B(),symbol:B()})),type:K(`erc20`)}),U({address:ZF(K(`native`)),balance:Q(),metadata:ZF(U({decimals:V(),fiat:QF(e.Price),name:q(B()),symbol:q(B())})),type:K(`native`)})]))))})(AR||={});var jR;(function(e){e.Request=U({method:K(`wallet_getCallsStatus`),params:J(G([X()]))}),e.Response=U({id:B(),receipts:q(J(H(U({blockHash:X(),blockNumber:Z(),chainId:Z(),gasUsed:Z(),logs:J(H(U({address:Y(),data:X(),topics:J(H(X()))}))),status:X(),transactionHash:X()})))),status:V()})})(jR||={});var MR;(function(e){e.Parameters=U({address:Y(),chainIds:q(J(H(Z())))}),e.Request=U({method:K(`wallet_getKeys`),params:J(G([e.Parameters]))}),e.Response=qF(X(),tR.Response)})(MR||={});var NR;(function(e){e.Capabilities=U({authorizeKeys:q(tR.Request),meta:rR.Request,preCall:q(wF()),preCalls:q(J(H(oR))),requiredFunds:q(iR.Request),revokeKeys:q(aR.Request)}),e.ResponseCapabilities=U({assetDiffs:q(eR.Response),authorizeKeys:QF(tR.Response),feePayerDigest:q(X()),feeSignature:q(X()),feeTotals:q(nR.Response),revokeKeys:QF(aR.Response)}),e.Parameters=U({calls:J(H(_R)),capabilities:e.Capabilities,chainId:Z(),from:q(Y()),key:q(U({prehash:wF(),publicKey:X(),type:QL.shape.type}))}),e.Request=U({method:K(`wallet_prepareCalls`),params:J(G([e.Parameters]))}),e.Response=U({capabilities:e.ResponseCapabilities,context:U({preCall:q(VF(sR)),quote:q(VF(fR))}),digest:X(),key:QF(U({prehash:wF(),publicKey:X(),type:QL.shape.type})),signature:X(),typedData:U({domain:W([U({chainId:W([Z(),V()]),name:B(),verifyingContract:Y(),version:B()}),U({})]),message:qF(B(),PF()),primaryType:B(),types:qF(B(),PF())})})})(NR||={});var PR;(function(e){e.Capabilities=U({authorizeKeys:tR.Request}),e.Parameters=U({address:Y(),capabilities:e.Capabilities,chainId:q(V()),delegation:Y()}),e.Request=U({method:K(`wallet_prepareUpgradeAccount`),params:J(G([e.Parameters]))}),e.Response=U({capabilities:e.Capabilities,chainId:Z(),context:U({address:Y(),authorization:hR,chainId:Z(),preCall:oR}),digests:U({auth:X(),exec:X()}),typedData:U({domain:W([U({chainId:W([Z(),V()]),name:B(),verifyingContract:Y(),version:B()}),U({})]),message:qF(B(),PF()),primaryType:B(),types:qF(B(),PF())})})})(PR||={});var FR;(function(e){e.Request=U({method:K(`wallet_feeTokens`),params:q(OF())}),e.Response=qF(X(),J(H(U({address:Y(),decimals:V(),nativeRate:q(Q()),symbol:B()}))))})(FR||={});var IR;(function(e){e.Parameters=U({capabilities:q(U({feeSignature:q(X())})),context:U({preCall:q(VF(sR)),quote:q(VF(fR))}),key:q(U({prehash:wF(),publicKey:X(),type:QL.shape.type})),signature:X()}),e.Request=U({method:K(`wallet_sendPreparedCalls`),params:J(G([e.Parameters]))}),e.Response=U({id:X()})})(IR||={});var LR;(function(e){e.Parameters=U({context:U({address:Y(),authorization:hR,chainId:Z(),preCall:oR}),signatures:U({auth:X(),exec:X()})}),e.Request=U({method:K(`wallet_upgradeAccount`),params:J(G([e.Parameters]))}),e.Response=OF()})(LR||={});var RR;(function(e){e.Parameters=U({address:X(),chainId:Z(),digest:X(),signature:X()}),e.Request=U({method:K(`wallet_verifySignature`),params:J(G([e.Parameters]))}),e.Response=U({proof:QF(U({account:Y(),initPreCall:QF(oR),keyHash:X()})),valid:wF()})})(RR||={}),Jd(),op();async function zR(e,t){try{let n=`wallet_getAuthorization`,r=await Tm(()=>e.request({method:n,params:[pP(OR.Parameters,t)]}),{cacheKey:`${e.uid}.${n}.${t.address}`});return mP(OR.Response,r)}catch(e){throw tz(e),e}}async function BR(e,t={}){let n=(()=>{if(t.chainId)return[t.chainId];if(t.chainIds!==`all`)return t.chainIds?t.chainIds:[e.chain.id]})();try{let r=`wallet_getCapabilities`,i=await Tm(()=>e.request({method:r,params:n?[n]:void 0},{retryCount:0}),{cacheKey:`${e.uid}.${r}.${n?.join(`,`)}`}),a=(()=>t.raw?i:mP(kR.Response,i))();return t.chainIds?a:Object.values(a)[0]}catch(e){throw tz(e),e}}async function VR(e,t){let{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a}=t;try{let t=await e.request({method:`wallet_getAssets`,params:[pP(AR.Parameters,{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a})]}),o=mP(AR.Response,t),s=Object.entries(o).reduce((e,[t,n])=>(e[Xf(t)]=n,e),{}),c={};for(let e of Object.values(s))for(let t of e){let e=JSON.stringify(t.metadata);c[e]={...t,balance:t.balance+(c[e]?.balance??0n)}}return{...s,0:Object.values(c)}}catch(e){throw tz(e),e}}async function HR(e,t){let{id:n}=t;try{let t=await e.request({method:`wallet_getCallsStatus`,params:[n]});return mP(jR.Response,t)}catch(e){throw tz(e),e}}async function UR(e,t){let{address:n,chainIds:r}=t;try{let t=await e.request({method:`wallet_getKeys`,params:[pP(MR.Parameters,{address:n,chainIds:r})]});return mP(MR.Response,t)}catch(e){throw tz(e),e}}async function WR(e){let t=`health`,n=await Tm(()=>e.request({method:t}),{cacheKey:`${e.uid}.${t}`});return mP(TR.Response,n)}async function GR(e,t){let{address:n,capabilities:r,chain:i=e.chain,key:a}=t,o=t.calls.map(e=>({data:e.abi?Y_(X_(e.abi,e.functionName),e.args):e.data??`0x`,to:e.to,value:e.value??0n}));try{let t=await e.request({method:`wallet_prepareCalls`,params:[pP(NR.Parameters,{calls:o,capabilities:{...r,meta:{...r?.meta}},chainId:i?.id,from:n,key:a?{prehash:a.prehash,publicKey:a.publicKey,type:a.type}:void 0})]},{retryCount:0});return Object.assign(mP(NR.Response,t),{_raw:t})}catch(e){throw tz(e),$R(e,{calls:t.calls}),e}}async function KR(e,t){let{address:n,chain:r=e.chain,delegation:i,...a}=t;try{let t=await e.request({method:`wallet_prepareUpgradeAccount`,params:[pP(PR.Parameters,pN({address:n,capabilities:a,chainId:r?.id,delegation:i}))]},{retryCount:0});return mP(PR.Response,t)}catch(e){throw tz(e),$R(e),e}}async function qR(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;try{let t=await e.request({method:`wallet_sendPreparedCalls`,params:[pP(IR.Parameters,{capabilities:n,context:{preCall:r.preCall,quote:r.quote},key:i?{prehash:i.prehash,publicKey:i.publicKey,type:i.type}:void 0,signature:a})]},{retryCount:0});return mP(IR.Response,t)}catch(e){throw tz(e),$R(e),e}}async function JR(e,t){let{email:n,walletAddress:r}=t;try{let t=await e.request({method:`account_setEmail`,params:[pP(xR.Parameters,{email:n,walletAddress:r})]},{retryCount:0});return mP(xR.Response,t)}catch(e){throw tz(e),$R(e),e}}async function YR(e,t){let{context:n,signatures:r}=t;try{await e.request({method:`wallet_upgradeAccount`,params:[pP(LR.Parameters,{context:n,signatures:r})]},{retryCount:0})}catch(e){throw tz(e),$R(e),e}}async function XR(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;try{let t=await e.request({method:`account_verifyEmail`,params:[pP(CR.Parameters,{chainId:n,email:r,signature:i,token:a,walletAddress:o})]},{retryCount:0});return mP(CR.Response,t)}catch(e){throw tz(e),$R(e),e}}async function ZR(e,t){let{signature:n}=t,{signature:r,capabilities:{feeSignature:i,...a},...o}=t.response,s=ez({capabilities:a,...o}),c=Hh(Uf(JSON.stringify(s))),l=O_({payload:c,signature:o_(n)}),{quoteSigner:u}=await WR(e);return l===u}async function QR(e,t){let{address:n,chain:r=e.chain,digest:i,signature:a}=t;try{async function t(){return{proof:null,valid:await iv(e,{address:n,hash:i,signature:a})}}let o=await(async()=>{let o=await e.request({method:`wallet_verifySignature`,params:[pP(RR.Parameters,{address:n,chainId:r?.id,digest:i,signature:a})]},{retryCount:0}).catch(t);return o.valid?o:t()})();return mP(RR.Response,o)}catch(e){throw tz(e),e}}function $R(e,{calls:t}={}){if(!(e instanceof D))return;let n=e=>{try{if(e.name===`ContractFunctionExecutionError`){let t=e.cause.name===`ContractFunctionRevertedError`?e.cause.data:void 0;if(t)return LL([t.abiItem],t.errorName)}let t=e.walk(e=>!(e instanceof Error)&&e.code===3);if(!t)return;let{data:n,message:r}=t;return n===`0xd0d5039b`?IL(`error Unauthorized()`):{inputs:[],name:(r??n).split(`(`)[0],type:`error`}}catch{return}},r=HL(e,{calls:t??[]}),i=n(r);if(!(r===e&&!i))throw new nz(Object.assign(r,{abiError:i}))}function ez(e){if(typeof e==`object`&&e){if(Array.isArray(e))return e.map(ez);let t={};for(let n of Object.keys(e).sort())t[n]=ez(e[n]);return t}return e}function tz(e){if(e.name===`$ZodError`)throw GL(e)}var nz=class extends P{constructor(e){super(`An error occurred while executing calls.`,{cause:e,metaMessages:[e.abiError&&`Reason: `+e.abiError.name].filter(Boolean)}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Rpc.ExecutionError`}),Object.defineProperty(this,`abiError`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abiError=e.abiError}};const rz={anvil:{http:`http://localhost:9119`},prod:{http:`https://rpc.porto.sh`},stg:{http:`https://stg-rpc.porto.sh`}};function iz(e){return t=>{let n=e.public(t),r=e.relay(t);return Nv({key:iz.type,name:`Relay Proxy`,async request({method:e,params:t},i){return az(e)?r.request({method:e,params:t},i):n.request({method:e,params:t},i)},type:iz.type})}}(function(e){e.type=`relayProxy`})(iz||={});function az(e){return!!(e.startsWith(`wallet_`)||e.startsWith(`account_`)||e===`health`)}pf();var oz=new Map;function sz(e,t={}){let{config:n,id:r,store:i}=e._internal,{chains:a,relay:o}=n,s=i.getState(),c=t.chainId??s.chainIds[0],l=a.find(e=>e.id===c);if(!l)throw Error([`Could not find a compatible Porto chain on the given chain configuration.`,``,`Provided chains: [${a.map(e=>`${e.name} (id: ${e.id})`).join(`, `)}]`,`Needed chain (id): ${c}`,`Please add this chain (id) to your chain configuration.`].join(` -`));let u=iz({public:n.transports[l.id]??Fv(l.rpcUrls.default.http.map(e=>zv(e))),relay:o}),d=[r,df(l)].filter(Boolean).join(`:`);if(oz.has(d))return oz.get(d);let f=Xm({...t,chain:l,pollingInterval:1e3,transport:u});return oz.set(d,f),f}const cz=U({chainId:q(Z()),expiry:Z(),hash:X(),id:X(),prehash:q(wF()),publicKey:X(),role:W([K(`admin`),K(`session`)]),type:W([K(`address`),K(`p256`),K(`secp256k1`),K(`webauthn-p256`)])}),lz=J(H(UL([U({signature:B(),to:Y()}),U({signature:B()}),U({to:Y()})])).check(vF(1))),uz=U({limit:W([iI([V(),`.`,V()]),iI([V()])]).check(yF(/^\d+(\.\d+)?$/)),symbol:q(W([K(`native`),mR]))}),dz=U({addresses:J(H(Y()))}),fz=J(H(U({limit:Q(),period:W([K(`minute`),K(`hour`),K(`day`),K(`week`),K(`month`),K(`year`)]),token:q(Y())}))),pz=U({calls:q(lz),signatureVerification:q(dz),spend:q(fz)}),mz=U({...cz.shape,feeToken:q(ZF(uz)),permissions:q(pz)}),hz=U({address:Y(),chainId:q(Z()),expiry:V(),id:X(),key:zF(cz,{publicKey:!0,type:!0}),permissions:U({calls:lz,signatureVerification:q(dz),spend:q(fz)})}),gz=U({address:q(Y()),chainId:q(Z()),expiry:V().check(_F(1)),feeToken:ZF(uz),key:q(zF(cz,{publicKey:!0,type:!0})),permissions:U({calls:lz,signatureVerification:q(dz),spend:q(fz)})}),_z=hz;function vz(e,t){let{chainId:n,expiry:r,permissions:i,id:a,publicKey:o,type:s}=e,{address:c}=t;return{address:c,chainId:n,expiry:r,id:a,key:{publicKey:o,type:s},permissions:i??{}}}function yz(e){let{chainId:t,expiry:n,key:r}=e;return vL({chainId:t,expiry:n,permissions:e.permissions??{},publicKey:r.publicKey,role:`session`,type:r.type})}var bz;(function(e){e.GetCapabilitiesResponse=U({status:W([K(`supported`),K(`unsupported`)])})})(bz||={});var xz;(function(e){e.Request=W([wF(),U({chainId:q(Z()),label:q(B())})])})(xz||={});var Sz;(function(e){e.Request=UL([U({chainId:q(V()),domain:q(B()),expirationTime:q(IF()),issuedAt:q(IF()),nonce:B(),notBefore:q(IF()),requestId:q(B()),resources:q(J(H(B()))),scheme:q(B()),statement:q(B()),uri:q(B()),version:q(K(`1`))}),U({authUrl:W([B(),U({logout:B(),nonce:B(),verify:B()})]),chainId:q(Z()),domain:q(B()),expirationTime:q(IF()),issuedAt:q(IF()),notBefore:q(IF()),requestId:q(B()),resources:q(J(H(B()))),scheme:q(B()),statement:q(B()),uri:q(B()),version:q(K(`1`))})]),e.Response=U({message:B(),signature:X(),token:q(B())})})(Sz||={});var Cz;(function(e){e.GetCapabilitiesResponse=U({supported:wF(),tokens:J(H(pR))}),e.Request=W([mR,Y()])})(Cz||={});var wz;(function(e){e.Request=gz})(wz||={});var Tz;(function(e){e.GetCapabilitiesResponse=U({supported:wF()})})(Tz||={});var Ez;(function(e){e.GetCapabilitiesResponse=U({supported:wF()}),e.Request=U({id:q(W([X(),AF()]))}),e.Response=J(H(hz))})(Ez||={});var Dz;(function(e){e.Request=J(H(U({context:PF(),signature:X()}))),e.Response=e.Request})(Dz||={});var Oz;(function(e){e.Request=B()})(Oz||={});var kz;(function(e){e.GetCapabilitiesResponse=U({supported:wF(),tokens:J(H(pR))}),e.Request=J(H(UL([U({address:Y(),value:Q()}),U({symbol:mR,value:W([iI([V(),`.`,V()]),iI([V()])]).check(yF(/^\d+(\.\d+)?$/))})])))})(kz||={});var Az=U({...zF(cz,{id:!0,publicKey:!0,type:!0}).shape,credentialId:q(B()),privateKey:q(MF())}),jz;(function(e){e.Parameters=U({address:Y(),secret:B()}),e.Request=U({method:K(`account_getOnrampContactInfo`),params:J(G([e.Parameters]))}),e.Response=U({email:q(B()),phone:q(B()),phoneVerifiedAt:q(V())})})(jz||={});var Mz;(function(e){e.Parameters=U({address:Y()}),e.Request=U({method:K(`account_onrampStatus`),params:J(G([e.Parameters]))}),e.Response=U({email:q(V()),phone:q(V())})})(Mz||={});var Nz;(function(e){e.Parameters=U({email:B(),walletAddress:Y()}),e.Request=U({method:K(`account_resendVerifyPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(Nz||={});var Pz;(function(e){e.Parameters=U({email:B(),walletAddress:Y()}),e.Request=U({method:K(`account_setEmail`),params:J(G([e.Parameters]))}),e.Response=AF()})(Pz||={});var Fz;(function(e){e.Parameters=U({email:B(),walletAddress:Y()}),e.Request=U({method:K(`account_setPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(Fz||={});var Iz;(function(e){e.Parameters=U({chainId:Z(),email:B(),token:B(),walletAddress:Y()}),e.Request=U({method:K(`account_verifyEmail`),params:J(G([e.Parameters]))}),e.Response=AF()})(Iz||={});var Lz;(function(e){e.Parameters=U({code:B(),phone:B(),walletAddress:Y()}),e.Request=U({method:K(`account_verifyPhone`),params:J(G([e.Parameters]))}),e.Response=AF()})(Lz||={});var Rz;(function(e){e.Parameters=U({address:q(Y()),chainId:q(Z()),token:q(Y()),value:q(B())}),e.Request=U({method:K(`wallet_addFunds`),params:J(G([e.Parameters]))}),e.Response=U({id:X()})})(Rz||={});var zz;(function(e){e.Request=U({method:K(`eth_accounts`),params:q(PF())}),e.Response=J(H(Y()))})(zz||={});var Bz;(function(e){e.Request=U({method:K(`eth_chainId`),params:q(PF())}),e.Response=X()})(Bz||={});var Vz;(function(e){e.Request=U({method:K(`eth_requestAccounts`),params:q(PF())}),e.Response=J(H(Y()))})(Vz||={});var Hz;(function(e){e.Request=U({method:K(`eth_sendTransaction`),params:J(G([U({capabilities:q(U({feeToken:q(Cz.Request),merchantUrl:q(Oz.Request),preCalls:q(Dz.Request)})),chainId:q(Z()),data:q(X()),from:q(Y()),to:Y(),value:q(Q())})]))}),e.Response=X()})(Hz||={});var Uz;(function(e){e.Request=U({method:K(`eth_signTypedData_v4`),params:J(G([Y(),B()]))}),e.Response=X()})(Uz||={});var Wz;(function(e){e.Parameters=U({address:q(Y()),chainId:q(Z())}),e.Request=U({method:K(`wallet_getAdmins`),params:q(J(G([e.Parameters])))}),e.Key=Az,e.Response=U({address:Y(),chainId:Z(),keys:J(H(e.Key))})})(Wz||={});var Gz;(function(e){e.Capabilities=U({feeToken:q(Cz.Request)}),e.Parameters=U({address:q(Y()),capabilities:q(e.Capabilities),chainId:q(Z()),key:zF(cz,{publicKey:!0,type:!0})}),e.Request=U({method:K(`wallet_grantAdmin`),params:J(G([e.Parameters]))}),e.Response=U({address:Y(),chainId:Z(),key:Wz.Key})})(Gz||={});var Kz;(function(e){e.Parameters=gz,e.Request=U({method:K(`wallet_grantPermissions`),params:J(G([e.Parameters]))}),e.ResponseCapabilities=U({preCalls:q(Dz.Response)}),e.Response=U({...hz.shape,capabilities:q(MF())})})(Kz||={});var qz;(function(e){e.Parameters=U({address:q(Y())}),e.Request=U({method:K(`wallet_getAccountVersion`),params:q(J(G([e.Parameters])))}),e.Response=U({current:B(),latest:B()})})(qz||={});var Jz;(function(e){e.Parameters=U({address:q(Y()),chainIds:q(J(H(Z())))}),e.Request=U({method:K(`wallet_getPermissions`),params:q(J(G([e.Parameters])))}),e.Response=Ez.Response})(Jz||={});var Yz;(function(e){e.Capabilities=U({feeToken:q(Cz.Request)}),e.Parameters=U({address:q(Y()),capabilities:q(e.Capabilities),chainId:q(Z()),id:X()}),e.Request=U({method:K(`wallet_revokeAdmin`),params:J(G([e.Parameters]))}),e.Response=void 0})(Yz||={});var Xz;(function(e){e.Capabilities=U({feeToken:q(Cz.Request)}),e.Parameters=U({address:q(Y()),capabilities:q(e.Capabilities),id:X()}),e.Request=U({method:K(`wallet_revokePermissions`),params:J(G([e.Parameters]))}),e.Response=void 0})(Xz||={});var Zz;(function(e){e.Request=U({method:K(`wallet_switchEthereumChain`),params:J(G([U({chainId:X()})]))})})(Zz||={});var Qz;(function(e){e.Parameters=U({context:PF(),signatures:U({auth:X(),exec:X()})}),e.Request=U({method:K(`wallet_upgradeAccount`),params:J(G([e.Parameters]))}),e.ResponseCapabilities=U({admins:q(J(H(Wz.Key))),permissions:q(Ez.Response)}),e.Response=U({address:Y(),capabilities:q(e.ResponseCapabilities)})})(Qz||={});var $z;(function(e){e.Request=U({method:K(`personal_sign`),params:J(G([X(),Y()]))}),e.Response=X()})($z||={});var eB;(function(e){e.Request=U({method:K(`porto_ping`),params:q(OF())}),e.Response=K(`pong`)})(eB||={});var tB;(function(e){e.Capabilities=U({createAccount:q(xz.Request),email:q(wF()),grantAdmins:q(J(H(zF(cz,{publicKey:!0,type:!0})))),grantPermissions:q(wz.Request),preCalls:q(Dz.Request),selectAccount:q(W([wF(),U({address:Y(),key:q(U({credentialId:q(B()),publicKey:X()}))})])),signInWithEthereum:q(Sz.Request)}),e.Parameters=U({capabilities:q(e.Capabilities),chainIds:q(J(H(Z())))}),e.Request=U({method:K(`wallet_connect`),params:q(J(G([e.Parameters])))}),e.ResponseCapabilities=U({admins:q(J(H(U({...zF(cz,{id:!0,publicKey:!0,type:!0}).shape,credentialId:q(B())})))),permissions:q(Ez.Response),preCalls:q(Dz.Response),signInWithEthereum:q(Sz.Response)}),e.Response=U({accounts:J(H(U({address:Y(),capabilities:q(e.ResponseCapabilities)}))),chainIds:J(H(Z()))})})(tB||={});var nB;(function(e){e.Request=U({method:K(`wallet_disconnect`),params:q(PF())}),e.Response=void 0})(nB||={});var rB;(function(e){e.Parameters=AR.Parameters,e.Request=AR.Request,e.Response=AR.Response})(rB||={});var iB;(function(e){e.Request=U({method:K(`wallet_getCallsStatus`),params:G([X()])}),e.Response=U({atomic:wF(),chainId:Z(),id:B(),receipts:q(J(H(U({blockHash:X(),blockNumber:X(),gasUsed:X(),logs:J(H(U({address:Y(),data:X(),topics:J(H(X()))}))),status:X(),transactionHash:X()})))),status:V(),version:B()})})(iB||={});var aB;(function(e){e.Request=U({method:K(`wallet_getCapabilities`),params:q(W([J(G([W([X(),OF()])])),J(G([W([X(),OF()]),J(H(Z()))]))]))}),e.Response=qF(X(),U({atomic:bz.GetCapabilitiesResponse,feeToken:Cz.GetCapabilitiesResponse,merchant:Tz.GetCapabilitiesResponse,permissions:Ez.GetCapabilitiesResponse,requiredFunds:kz.GetCapabilitiesResponse}))})(aB||={});var oB;(function(e){e.Parameters=U({address:Y(),chainIds:q(J(H(Z())))}),e.Request=U({method:K(`wallet_getKeys`),params:J(G([e.Parameters]))}),e.Response=J(H(mz))})(oB||={});var sB;(function(e){e.Capabilities=U({feeToken:q(Cz.Request),merchantUrl:q(Oz.Request),permissions:q(Ez.Request),preCalls:q(Dz.Request),requiredFunds:q(kz.Request)}),e.Parameters=U({calls:J(H(U({data:q(X()),to:Y(),value:q(Q())}))),capabilities:q(e.Capabilities),chainId:q(Z()),from:q(Y()),key:q(zF(cz,{prehash:!0,publicKey:!0,type:!0})),version:q(B())}),e.Request=U({method:K(`wallet_prepareCalls`),params:J(G([e.Parameters]))}),e.Response=U({capabilities:q(U({...NR.ResponseCapabilities.shape,quote:q(fR)})),chainId:X(),context:U({account:U({address:Y()}),calls:e.Parameters.shape.calls,nonce:Q(),quote:q(VF(fR))}),digest:X(),key:zF(cz,{prehash:!0,publicKey:!0,type:!0}),typedData:U({domain:W([U({chainId:Z(),name:B(),verifyingContract:Y(),version:B()}),U({})]),message:qF(B(),PF()),primaryType:B(),types:qF(B(),PF())})})})(sB||={});var cB;(function(e){e.Capabilities=U({...tB.Capabilities.shape,label:q(B())}),e.Parameters=U({address:Y(),capabilities:q(e.Capabilities),chainId:q(Z())}),e.Request=U({method:K(`wallet_prepareUpgradeAccount`),params:J(G([e.Parameters]))}),e.Response=U({context:PF(),digests:U({auth:X(),exec:X()})})})(cB||={});var lB;(function(e){e.Capabilities=sB.Capabilities,e.Request=U({method:K(`wallet_sendCalls`),params:J(G([BF(sB.Parameters,{key:!0})]))}),e.Response=U({id:X()})})(lB||={});var uB;(function(e){e.Parameters=U({capabilities:sB.Response.shape.capabilities,chainId:X(),context:sB.Response.shape.context,key:sB.Response.shape.key,signature:X()}),e.Request=U({method:K(`wallet_sendPreparedCalls`),params:J(G([e.Parameters]))}),e.Response=J(H(U({capabilities:q(qF(B(),PF())),id:X()})))})(uB||={});var dB;(function(e){e.Parameters=U({address:Y(),chainId:q(Z()),digest:X(),signature:X()}),e.Request=U({method:K(`wallet_verifySignature`),params:J(G([e.Parameters]))}),e.Response=U({address:Y(),chainId:Z(),proof:q(PF()),valid:wF()})})(dB||={});const fB=WF(`method`,[Iz.Request,Rz.Request,zz.Request,Bz.Request,Vz.Request,Hz.Request,Uz.Request,qz.Request,Wz.Request,Jz.Request,Gz.Request,Kz.Request,cB.Request,Yz.Request,Xz.Request,Qz.Request,$z.Request,eB.Request,tB.Request,nB.Request,rB.Request,iB.Request,aB.Request,oB.Request,sB.Request,lB.Request,uB.Request,Zz.Request,dB.Request]);function pB(e,t){let n=dP(e,t);if(n.error){let e=n.error.issues.at(0);throw e?.code===`invalid_union`&&e.note===`No matching discriminator`?new NM:new RM(GL(n.error))}return{...t,_decoded:n.data}}async function mB(e){e.persist.hasHydrated()||await new Promise(t=>{e.persist.onFinishHydration(()=>t(!0)),setTimeout(()=>t(!0),100)})}function hB(e){if(e)return e.startsWith(`/`)?`${window.location.origin}${e}`:e}op(),pf();function gB(e){let{config:t,getMode:n,id:r,store:i}=e,{announceProvider:a}=t;function o(e={}){let a=s(),o=e.request??pB(fB,{method:`wallet_getCapabilities`,params:e.chainIds?[void 0,e.chainIds]:void 0});return Tm(()=>n().actions.getCapabilities({chainIds:e.chainIds,internal:{client:a,config:t,request:o,store:i}}),{cacheKey:`getCapabilities.${r}.${e.chainIds?.join(`,`)}`})}function s(t){let n=typeof t==`string`?Xf(t):t;return sz({_internal:e},{chainId:n})}let c=new Map,l=[],u=tN(),d=nN({...u,async request(e){await mB(i);let r=[`eth_accounts`,`eth_chainId`,`eth_requestAccounts`,`wallet_getAssets`,`wallet_getCapabilities`,`wallet_getKeys`,`wallet_getPermissions`,`wallet_getAccountVersion`,`wallet_connect`].includes(e.method);return gN(async()=>{let r;try{r=pB(fB,e)}catch(t){let n=t;if(!(n instanceof NM))throw n;if(e.method.startsWith(`wallet_`))throw new WM;return s().request(e)}let a=i.getState();switch(r.method){case`account_verifyEmail`:{if(a.accounts.length===0)throw new GM;let[e]=r._decoded.params,{chainId:o,email:c,token:l,walletAddress:u}=e,d=s(o);if(o&&o!==d.chain.id)throw new KM;let f=u?a.accounts.find(e=>ag(e.address,u)):a.accounts[0];if(!f)throw new UM;return await n().actions.verifyEmail({account:f,chainId:o,email:c,internal:{client:d,config:t,request:r,store:i},token:l,walletAddress:u})}case`wallet_addFunds`:{if(a.accounts.length===0)throw new GM;let{address:e,value:o,token:c}=r.params[0]??{},l=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!l)throw new UM;let d=s(),f=await n().actions.addFunds({address:l.address,internal:{client:d,config:t,request:r,store:i},token:c,value:o});return u.emit(`message`,{data:null,type:`assetsChanged`}),f}case`eth_accounts`:if(a.accounts.length===0)throw new GM;return a.accounts.map(bB);case`eth_chainId`:return I(a.chainIds[0]);case`eth_requestAccounts`:{if(a.accounts.length>0&&c.get(`eth_requestAccounts`))return a.accounts.map(bB);let e=s(),{accounts:o}=await n().actions.loadAccounts({internal:{client:e,config:t,request:r,store:i}});return i.setState(e=>({...e,accounts:o})),u.emit(`connect`,{chainId:I(e.chain.id)}),c.set(`eth_requestAccounts`,!0),setTimeout(()=>c.delete(`eth_requestAccounts`),1e3),o.map(bB)}case`eth_sendTransaction`:{if(a.accounts.length===0)throw new GM;let[{capabilities:e,chainId:o,data:c=`0x`,from:l,to:u,value:d}]=r._decoded.params,f=s(o);if(o&&o!==f.chain.id)throw new KM;let p=l?a.accounts.find(e=>ag(e.address,l)):a.accounts[0];if(!p)throw new UM;let{id:m}=await n().actions.sendCalls({account:p,asTxHash:!0,calls:[{data:c,to:u,value:d}],chainId:f.chain.id,internal:{client:f,config:t,request:r,store:i},merchantUrl:hB(t.merchantUrl??e?.merchantUrl)});return m}case`eth_signTypedData_v4`:{if(a.accounts.length===0)throw new GM;let[e,o]=r._decoded.params,c=a.accounts.find(t=>ag(t.address,e));if(!c)throw new UM;let l=s();return await n().actions.signTypedData({account:c,data:o,internal:{client:l,config:t,request:r,store:i}})}case`wallet_grantAdmin`:{if(a.accounts.length===0)throw new GM;let[{address:e,capabilities:o,chainId:c,key:l}]=r._decoded.params??[{}],d=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!d)throw new UM;let f=s(c);if(vB([...d.keys??[]])?.some(e=>e.publicKey?.toLowerCase()===l.publicKey.toLowerCase()))throw new RM({message:`Key already granted as admin.`});let{key:p}=await n().actions.grantAdmin({account:d,feeToken:o?.feeToken,internal:{client:f,config:t,request:r,store:i},key:l});i.setState(e=>{let t=e.accounts.findIndex(e=>d?ag(e.address,d.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],p]}:e)}});let m=vB([...d.keys??[],p]);return u.emit(`message`,{data:null,type:`adminsChanged`}),pP(Gz.Response,{address:d.address,chainId:f.chain.id,key:m.at(-1)})}case`wallet_grantPermissions`:{if(a.accounts.length===0)throw new GM;let[{address:e,chainId:o,...c}]=r._decoded.params??[{}],l=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!l)throw new UM;let d=s(o),{key:f}=await n().actions.grantPermissions({account:l,internal:{client:d,config:t,request:r,store:i},permissions:c});return i.setState(e=>{let t=e.accounts.findIndex(e=>l?ag(e.address,l.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],f]}:e)}}),u.emit(`message`,{data:null,type:`permissionsChanged`}),pP(Kz.Response,{...vz(f,{address:l.address})})}case`wallet_getAdmins`:{if(a.accounts.length===0)throw new GM;let[{address:e,chainId:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!c)throw new UM;let l=s(o),u=await n().actions.getKeys({account:c,internal:{client:l,config:t,request:r,store:i}}),d=vB(u);return pP(Wz.Response,{address:c.address,chainId:l.chain.id,keys:d})}case`wallet_prepareUpgradeAccount`:{let[{address:e,capabilities:a,chainId:o}]=r._decoded.params??[{}],{email:c,label:u,grantPermissions:d}=a??{},f=s(o),{context:p,digests:m}=await n().actions.prepareUpgradeAccount({address:e,email:c,internal:{client:f,config:t,request:r,store:i},label:u,permissions:d});return l.push(p.account),{context:p,digests:m}}case`wallet_getAccountVersion`:{if(a.accounts.length===0)throw new GM;let[{address:e}]=r._decoded.params??[{}],o=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!o)throw new UM;let c=s(),{current:l,latest:u}=await n().actions.getAccountVersion({address:o.address,internal:{client:c,config:t,request:r,store:i}});return{current:l,latest:u}}case`wallet_getKeys`:{if(a.accounts.length===0)throw new GM;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=a.accounts.find(t=>ag(t.address,e));if(!c)throw new UM;let l=s(),u=await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}});return pP(oB.Response,u)}case`wallet_getPermissions`:{if(a.accounts.length===0)throw new GM;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!c)throw new UM;let l=s(),u=await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}});return yB(u,{address:c.address})}case`wallet_revokeAdmin`:{if(a.accounts.length===0)throw new GM;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!l)throw new UM;let d=s();await n().actions.revokeAdmin({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>ag(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`adminsChanged`});return}case`wallet_revokePermissions`:{if(a.accounts.length===0)throw new GM;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>ag(t.address,e)):a.accounts[0];if(!l)throw new UM;let d=s();await n().actions.revokePermissions({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>ag(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`permissionsChanged`});return}case`wallet_upgradeAccount`:{let[{context:e,signatures:a}]=r._decoded.params??[{}],o=s(),c=l.find(t=>ag(t.address,e.account.address));if(!c)throw new UM;let{account:d}=await n().actions.upgradeAccount({account:c,context:e,internal:{client:o,config:t,request:r,store:i},signatures:a}),f=vB(d.keys??[]),p=yB(d.keys??[],{address:d.address});return i.setState(e=>({...e,accounts:[d]})),u.emit(`connect`,{chainId:I(o.chain.id)}),{address:d.address,capabilities:{admins:f,...p.length>0?{permissions:p}:{}}}}case`porto_ping`:return`pong`;case`personal_sign`:{if(a.accounts.length===0)throw new GM;let[e,o]=r._decoded.params,c=a.accounts.find(e=>ag(e.address,o));if(!c)throw new UM;let l=s();return await n().actions.signPersonalMessage({account:c,data:e,internal:{client:l,config:t,request:r,store:i}})}case`wallet_connect`:{let[{capabilities:e,chainIds:o}]=r._decoded.params??[{}],c=s(o?.[0]),l=c.chain.id,{createAccount:d,email:f,grantAdmins:p,grantPermissions:m,selectAccount:h,signInWithEthereum:g}=e??{},_={client:c,config:t,request:r,store:i},{accounts:v}=await(async()=>{if(f||d){let{label:e=void 0}=typeof d==`object`?d:{},{account:t}=await n().actions.createAccount({admins:p,email:f,internal:_,label:e,permissions:m,signInWithEthereum:g});return{accounts:[t]}}let e=a.accounts[0],{address:t,key:r}=(()=>{if(h)return typeof h==`object`?h:{address:void 0,key:void 0};for(let t of e?.keys??[])if(t.type===`webauthn-p256`&&t.role===`admin`)return{address:e?.address,key:{credentialId:t.credentialId??t.privateKey?.credential?.id,publicKey:t.publicKey}};return{address:void 0,key:void 0}})(),i={internal:_,permissions:m,signInWithEthereum:g};try{return await n().actions.loadAccounts({address:t,key:r,...i})}catch(e){if(e instanceof HM)throw e;if(t&&r)return await n().actions.loadAccounts(i);throw e}})();i.setState(e=>({...e,accounts:v}));let y=[l,...i.getState().chainIds.filter(e=>e!==l)];return u.emit(`connect`,{chainId:I(y[0])}),{accounts:v.map(e=>({address:bB(e),capabilities:{admins:e.keys?vB(e.keys):[],permissions:e.keys?yB(e.keys,{address:e.address}):[],...e.signInWithEthereum&&{signInWithEthereum:e.signInWithEthereum}}})),chainIds:y.map(e=>I(e))}}case`wallet_disconnect`:{let e=s();await n().actions.disconnect?.({internal:{client:e,config:t,request:r,store:i}}),i.setState(e=>({...e,accounts:[]})),u.emit(`disconnect`,new GM);return}case`wallet_getAssets`:{let[e]=r._decoded.params??[],{account:a,chainFilter:o,assetFilter:c,assetTypeFilter:l}=e,u=s(),d=await n().actions.getAssets({account:a,assetFilter:c,assetTypeFilter:l,chainFilter:o,internal:{client:u,config:t,request:r,store:i}}),f=Object.entries(d).reduce((e,[t,n])=>(e[I(Number(t))]=n,e),{});return pP(rB.Response,f)}case`wallet_getCallsStatus`:{let[e]=r._decoded.params??[],a=s();return await n().actions.getCallsStatus({id:e,internal:{client:a,config:t,request:r,store:i}})}case`wallet_getCapabilities`:{let[e,t]=r.params??[];return await o({chainIds:t,request:r})}case`wallet_prepareCalls`:{let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,key:u,from:d}=e,f=s(l),p=d??a.accounts[0];if(!p)throw new UM;if(l&&l!==f.chain.id)throw new KM;let{digest:m,...h}=await n().actions.prepareCalls({account:ML(p),calls:o,feeToken:c?.feeToken,internal:{client:f,config:t,request:r,store:i},key:u,merchantUrl:hB(t.merchantUrl??c?.merchantUrl),requiredFunds:c?.requiredFunds});return pP(sB.Response,{capabilities:h.capabilities,chainId:I(h.chainId??f.chain.id),context:{...h.context,account:{address:h.account.address},calls:h.context.calls??[],nonce:h.context.nonce??0n},digest:m,key:h.key,typedData:h.typedData})}case`wallet_sendPreparedCalls`:{let[e]=r._decoded.params,{chainId:a,context:o,key:c,signature:l}=e,{account:u}=e.context,d=s(a);if(a&&Xf(a)!==d.chain.id)throw new KM;return[{id:await n().actions.sendPreparedCalls({account:ML(u),context:o,internal:{client:d,config:t,request:r,store:i},key:c,signature:l})}]}case`wallet_sendCalls`:{if(a.accounts.length===0)throw new GM;let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,from:u}=e,d=s(l);if(l&&l!==d.chain.id)throw new KM;let f=u?a.accounts.find(e=>ag(e.address,u)):a.accounts[0];if(!f)throw new UM;let{id:p}=await n().actions.sendCalls({account:f,calls:o,chainId:d.chain.id,feeToken:c?.feeToken,internal:{client:d,config:t,request:r,store:i},merchantUrl:hB(t.merchantUrl??c?.merchantUrl),permissionsId:c?.permissions?.id,requiredFunds:c?.requiredFunds});return{id:p}}case`wallet_switchEthereumChain`:{let[e]=r._decoded.params,{chainId:a}=e,o=Xf(a);if(!t.chains.find(e=>e.id===o))throw new YM;let c=s(a);await n().actions.switchChain?.({chainId:c.chain.id,internal:{client:c,config:t,request:r,store:i}}),i.setState(e=>({...e,chainIds:[o,...e.chainIds.filter(e=>e!==o)]}));return}case`wallet_verifySignature`:{let[e]=r._decoded.params,{address:t,chainId:n,digest:i,signature:a}=e,o=s(n);return{...await QR(o,{address:t,digest:i,signature:a}),address:t,chainId:I(o.chain.id)}}}},{enabled:r,id:df(e)})}});function f(){let e=()=>{},t=()=>{};mB(i).then(()=>{o().catch(()=>{}),e(),e=i.subscribe(e=>e.accounts,e=>{u.emit(`accountsChanged`,e.map(bB))},{equalityFn:(e,t)=>e.every((e,n)=>e.address===t[n]?.address)}),t(),t=i.subscribe(e=>e.chainIds[0],(e,t)=>{e!==t&&u.emit(`chainChanged`,I(e))})});let n=_B(d,a);return()=>{e(),t(),n()}}let p=f();return Object.assign(d,{_internal:{destroy:p}})}function _B(e,t){if(!t||typeof window>`u`||!window.dispatchEvent)return()=>{};let{icon:n=`data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDIyIiBoZWlnaHQ9IjQyMiIgdmlld0JveD0iMCAwIDQyMiA0MjIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MjIiIGhlaWdodD0iNDIyIiBmaWxsPSJibGFjayIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDBfMV8xNSkiPgo8cGF0aCBkPSJNODEgMjg2LjM2NkM4MSAyODAuODkzIDg1LjQ1MDUgMjc2LjQ1NSA5MC45NDA0IDI3Ni40NTVIMzI5LjUxMUMzMzUuMDAxIDI3Ni40NTUgMzM5LjQ1MiAyODAuODkzIDMzOS40NTIgMjg2LjM2NlYzMDYuMTg4QzMzOS40NTIgMzExLjY2MiAzMzUuMDAxIDMxNi4wOTkgMzI5LjUxMSAzMTYuMDk5SDkwLjk0MDRDODUuNDUwNSAzMTYuMDk5IDgxIDMxMS42NjIgODEgMzA2LjE4OFYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAyMzQuODI4Qzg1LjQ1MDUgMjM0LjgyOCA4MSAyMzkuMjY2IDgxIDI0NC43MzlWMjcxLjUzMUM4My44NDMyIDI2OS42MzMgODcuMjYyMiAyNjguNTI2IDkwLjk0MDQgMjY4LjUyNkgzMjkuNTExQzMzMy4xODggMjY4LjUyNiAzMzYuNjA4IDI2OS42MzMgMzM5LjQ1MiAyNzEuNTMxVjI0NC43MzlDMzM5LjQ1MiAyMzkuMjY2IDMzNS4wMDEgMjM0LjgyOCAzMjkuNTExIDIzNC44MjhIOTAuOTQwNFpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgwLjg5MyAzMzUuMDAxIDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTlDODEgMzExLjY2NCA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2NCAzMzkuNDUyIDMwNi4xOVYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAxOTMuMjAxQzg1LjQ1MDUgMTkzLjIwMSA4MSAxOTcuNjM4IDgxIDIwMy4xMTJWMjI5LjkwM0M4My44NDMyIDIyOC4wMDYgODcuMjYyMiAyMjYuODk5IDkwLjk0MDQgMjI2Ljg5OUgzMjkuNTExQzMzMy4xODggMjI2Ljg5OSAzMzYuNjA4IDIyOC4wMDYgMzM5LjQ1MiAyMjkuOTAzVjIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNFpNMzM5LjQ1MiAyNDQuNzM5QzMzOS40NTIgMjM5LjI2NSAzMzUuMDAxIDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNDODEuMjE3NSAyNzEuMzg1IDgxLjQzODMgMjcxLjI0NSA4MS42NjI0IDI3MS4xMDlDODMuODMyNSAyNjkuNzk0IDg2LjMwNTQgMjY4LjkyNyA4OC45NTIzIDI2OC42MzVDODkuNjA1MSAyNjguNTYzIDkwLjI2ODQgMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMzAuMTgzIDI2OC41MjYgMzMwLjg0NiAyNjguNTYzIDMzMS40OTggMjY4LjYzNUMzMzQuNDE5IDI2OC45NTcgMzM3LjEyOCAyNjkuOTggMzM5LjQ1MiAyNzEuNTNWMjQ0LjczOVpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgxLjAyMSAzMzUuMjA2IDI3Ni42NjMgMzI5Ljg5MyAyNzYuNDYyQzMyOS43NjcgMjc2LjQ1NyAzMjkuNjQgMjc2LjQ1NSAzMjkuNTExIDI3Ni40NTVIOTAuOTQwNEM4NS40NTA1IDI3Ni40NTUgODEgMjgwLjg5MyA4MSAyODYuMzY2VjMwNi4xODhDODEgMzExLjY2MiA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2MiAzMzkuNDUyIDMwNi4xODhWMjg2LjM2NloiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNSIvPgo8cGF0aCBvcGFjaXR5PSIwLjMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTguMDE0NiAxMDRDODguNjE3NyAxMDQgODEgMTExLjU5NSA4MSAxMjAuOTY1VjE4OC4yNzZDODMuODQzMiAxODYuMzc5IDg3LjI2MjIgMTg1LjI3MiA5MC45NDA0IDE4NS4yNzJIMzI5LjUxMUMzMzMuMTg4IDE4NS4yNzIgMzM2LjYwOCAxODYuMzc5IDMzOS40NTIgMTg4LjI3NlYxMjAuOTY1QzMzOS40NTIgMTExLjU5NSAzMzEuODMzIDEwNCAzMjIuNDM3IDEwNEg5OC4wMTQ2Wk0zMzkuNDUyIDIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNEM4NS40NTA1IDE5My4yMDEgODEgMTk3LjYzOCA4MSAyMDMuMTEyVjIyOS45MDNDODEuMjE3NSAyMjkuNzU4IDgxLjQzODMgMjI5LjYxOCA4MS42NjI0IDIyOS40ODJDODMuODMyNSAyMjguMTY3IDg2LjMwNTQgMjI3LjMgODguOTUyMyAyMjcuMDA4Qzg5LjYwNTEgMjI2LjkzNiA5MC4yNjg0IDIyNi44OTkgOTAuOTQwNCAyMjYuODk5SDMyOS41MTFDMzMwLjE4MyAyMjYuODk5IDMzMC44NDYgMjI2LjkzNiAzMzEuNDk4IDIyNy4wMDhDMzM0LjQxOSAyMjcuMzMgMzM3LjEyOCAyMjguMzUyIDMzOS40NTIgMjI5LjkwM1YyMDMuMTEyWk0zMzkuNDUyIDI0NC43MzlDMzM5LjQ1MiAyMzkuMzkzIDMzNS4yMDYgMjM1LjAzNiAzMjkuODkzIDIzNC44MzVDMzI5Ljc2NyAyMzQuODMgMzI5LjY0IDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNMODEuMDcwNyAyNzEuNDgzQzgxLjI2NTMgMjcxLjM1NSA4MS40NjI1IDI3MS4yMyA4MS42NjI0IDI3MS4xMDlDODEuOTA4MyAyNzAuOTYgODIuMTU4MSAyNzAuODE3IDgyLjQxMTcgMjcwLjY3OUM4NC4zOTUzIDI2OS42MDUgODYuNjA1NCAyNjguODk0IDg4Ljk1MjMgMjY4LjYzNUM4OS4wMDUyIDI2OC42MjkgODkuMDU4IDI2OC42MjQgODkuMTExIDI2OC42MThDODkuNzEyNSAyNjguNTU3IDkwLjMyMjggMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMjkuNzM4IDI2OC41MjYgMzI5Ljk2NSAyNjguNTMgMzMwLjE5MiAyNjguNTM5QzMzMC42MzEgMjY4LjU1NSAzMzEuMDY3IDI2OC41ODcgMzMxLjQ5OCAyNjguNjM1QzMzNC40MTkgMjY4Ljk1NyAzMzcuMTI4IDI2OS45OCAzMzkuNDUyIDI3MS41M1YyNDQuNzM5Wk0zMzkuNDUyIDI4Ni4zNjZDMzM5LjQ1MiAyODEuMDIxIDMzNS4yMDYgMjc2LjY2MyAzMjkuODkzIDI3Ni40NjJMMzI5Ljg2NSAyNzYuNDYxQzMyOS43NDggMjc2LjQ1NyAzMjkuNjI5IDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTg4QzgxIDMxMS42NjIgODUuNDUwNSAzMTYuMTAxIDkwLjk0MDQgMzE2LjEwMUgzMjkuNTExQzMzNS4wMDEgMzE2LjEwMSAzMzkuNDUyIDMxMS42NjIgMzM5LjQ1MiAzMDYuMTg4VjI4Ni4zNjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjY5Ljg2OCAxMzEuNzUyQzI2OS44NjggMTI2LjI3OCAyNzQuMzE4IDEyMS44NCAyNzkuODA4IDEyMS44NEgzMTEuNjE4QzMxNy4xMDggMTIxLjg0IDMyMS41NTggMTI2LjI3OCAzMjEuNTU4IDEzMS43NTJWMTYxLjQ4NUMzMjEuNTU4IDE2Ni45NTkgMzE3LjEwOCAxNzEuMzk2IDMxMS42MTggMTcxLjM5NkgyNzkuODA4QzI3NC4zMTggMTcxLjM5NiAyNjkuODY4IDE2Ni45NTkgMjY5Ljg2OCAxNjEuNDg1VjEzMS43NTJaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzFfMTUiPgo8cmVjdCB3aWR0aD0iMjU5IiBoZWlnaHQ9IjIxMyIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDgxIDEwNCkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K`,name:r=`Porto`,rdns:i=`xyz.ithaca.porto`}=typeof t==`object`?t:{};return jN({info:{icon:n,name:r,rdns:i,uuid:hN()},provider:e})}function vB(e){return e.map(e=>{if(e.role===`admin`)try{return pP(Wz.Key,{id:e.id??e.publicKey,publicKey:e.publicKey,type:e.type,...e.type===`webauthn-p256`?{credentialId:e.privateKey?.credential?.id,privateKey:{credential:{id:e.privateKey?.credential?.id},rpId:e.privateKey?.rpId}}:{}})}catch{return}}).filter(Boolean)}function yB(e,{address:t}){return e.map(e=>{if(e.chainId&&e.role===`session`&&!(e.expiry>0&&e.expiry()=>{})}}async function SB(e){let{account:t,calls:n,permissionsId:r}=e;if(r!==void 0){if(r===null)return;let e=t.keys?.find(e=>e.publicKey===r&&e.privateKey);if(!e)throw Error(`permission (id: ${r}) does not exist.`);return e}let i=t.keys?.find(e=>!e.privateKey||e.role!==`session`||e.expirye.permissions?.calls?.some(e=>{if(e.to&&e.to!==t.to)return!1;if(e.signature){if(!t.data)return!1;let n=Kf(t.data,0,4);if(Zf(e.signature))return e.signature===n;if(W_(e.signature)!==n)return!1}return!0}))),a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);return i??a}function CB(e={}){let t=e.id??0;return{prepare(e){return wB({id:t++,...e})},get id(){return t}}}function wB(e){return{...e,jsonrpc:`2.0`}}function TB(){return null}const EB=gz;function DB(e){let{expiry:t,feeToken:n,permissions:r,publicKey:i,type:a}=e;return{expiry:t,feeToken:n??null,key:{publicKey:i,type:a},permissions:r??{}}}async function OB(e,t={}){if(!e)return;let n=t.chainId??e.chainId,r=e.expiry??0,i=e.feeToken,a=OL(e,{feeTokens:t.feeTokens}),o={chainId:n,expiry:r,feeToken:i,permissions:a,role:`session`};if(e?.key)return vL({...o,publicKey:e.key.publicKey,type:e.key.type??`secp256k1`});if(typeof globalThis.crypto?.subtle?.generateKey==`function`)try{return await _L(o)}catch(e){if(!kB(e))throw e}return mL(o)}function kB(e){if(!(e instanceof Error))return!1;let t=e.message?.toLowerCase()??``;return e.name===`TypeError`||e.name===`ReferenceError`||t.includes(`subtle`)||t.includes(`generatekey`)}Jd();const AB=/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/,jB=/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:[0-9]{1,5})?$/,MB=/^localhost(:[0-9]{1,5})?$/,NB=/^[a-zA-Z0-9]{8,}$/,PB=/^([a-zA-Z][a-zA-Z0-9+-.]*)$/,FB=/^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/,IB=/(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/;function LB(e){let{chainId:t,domain:n,expirationTime:r,issuedAt:i=new Date,nonce:a,notBefore:o,requestId:s,resources:c,scheme:l,uri:u,version:d}=e;{if(t!==Math.floor(t))throw new VB({field:`chainId`,metaMessages:[`- Chain ID must be a EIP-155 chain ID.`,`- See https://eips.ethereum.org/EIPS/eip-155`,``,`Provided value: ${t}`]});if(!(AB.test(n)||jB.test(n)||MB.test(n)))throw new VB({field:`domain`,metaMessages:[`- Domain must be an RFC 3986 authority.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${n}`]});if(!NB.test(a))throw new VB({field:`nonce`,metaMessages:[`- Nonce must be at least 8 characters.`,`- Nonce must be alphanumeric.`,``,`Provided value: ${a}`]});if(!RB(u))throw new VB({field:`uri`,metaMessages:[`- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${u}`]});if(d!==`1`)throw new VB({field:`version`,metaMessages:[`- Version must be '1'.`,``,`Provided value: ${d}`]});if(l&&!PB.test(l))throw new VB({field:`scheme`,metaMessages:[`- Scheme must be an RFC 3986 URI scheme.`,`- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1`,``,`Provided value: ${l}`]});let r=e.statement;if(r?.includes(` -`))throw new VB({field:`statement`,metaMessages:[`- Statement must not include '\\n'.`,``,`Provided value: ${r}`]})}let f=rg(e.address,{checksum:!0}),p=(()=>l?`${l}://${n}`:n)(),m=(()=>e.statement?`${e.statement}\n`:``)(),h=`${p} wants you to sign in with your Ethereum account:\n${f}\n\n${m}`,g=`URI: ${u}\nVersion: ${d}\nChain ID: ${t}\nNonce: ${a}\nIssued At: ${i.toISOString()}`;if(r&&(g+=`\nExpiration Time: ${r.toISOString()}`),o&&(g+=`\nNot Before: ${o.toISOString()}`),s&&(g+=`\nRequest ID: ${s}`),c){let e=` -Resources:`;for(let t of c){if(!RB(t))throw new VB({field:`resources`,metaMessages:[`- Every resource must be a RFC 3986 URI.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${t}`]});e+=`\n- ${t}`}g+=e}return`${h}\n${g}`}function RB(e){if(/[^a-z0-9:/?#[\]@!$&'()*+,;=.\-_~%]/i.test(e)||/%[^0-9a-f]/i.test(e)||/%[0-9a-f](:?[^0-9a-f]|$)/i.test(e))return!1;let t=zB(e),n=t[1],r=t[2],i=t[3],a=t[4],o=t[5];if(!(n?.length&&i&&i.length>=0))return!1;if(r?.length){if(!(i.length===0||/^\//.test(i)))return!1}else if(/^\/\//.test(i))return!1;if(!/^[a-z][a-z0-9+\-.]*$/.test(n.toLowerCase()))return!1;let s=``;return s+=`${n}:`,r?.length&&(s+=`//${r}`),s+=i,a?.length&&(s+=`?${a}`),o?.length&&(s+=`#${o}`),s}function zB(e){return e.match(/(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/)}function BB(e){let{scheme:t,statement:n,...r}=e.match(FB)?.groups??{},{chainId:i,expirationTime:a,issuedAt:o,notBefore:s,requestId:c,...l}=e.match(IB)?.groups??{},u=e.split(`Resources:`)[1]?.split(` -- `).slice(1);return{...r,...l,...i?{chainId:Number(i)}:{},...a?{expirationTime:new Date(a)}:{},...o?{issuedAt:new Date(o)}:{},...s?{notBefore:new Date(s)}:{},...c?{requestId:c}:{},...u?{resources:u}:{},...t?{scheme:t}:{},...n?{statement:n}:{}}}var VB=class extends P{constructor(e){let{field:t,metaMessages:n}=e;super(`Invalid Sign-In with Ethereum message field "${t}".`,{metaMessages:n}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Siwe.InvalidMessageFieldError`})}};async function HB(e){let{address:t,authUrl:n,message:r,signature:i,publicKey:a}=e,{chainId:o}=BB(r);return await fetch(n.verify,{body:JSON.stringify({address:t,chainId:o,message:r,signature:i,walletAddress:t,...a&&{publicKey:a}}),credentials:`include`,headers:{"Content-Type":`application/json`},method:`POST`}).then(e=>e.json())}async function UB(e,t,n){let{chainId:r=e.chain?.id,domain:i,uri:a,resources:o,version:s=`1`}=t,{address:c}=n,l=t.authUrl?WB(t.authUrl):void 0;if(!r)throw Error("`chainId` is required.");if(!i)throw Error("`domain` is required.");if(!t.nonce&&!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");if(!a)throw Error("`uri` is required.");let u=await(async()=>{if(t.nonce)return t.nonce;if(!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");let e=await(await fetch(l.nonce,{body:JSON.stringify({address:c,chainId:r,walletAddress:c}),headers:{"Content-Type":`application/json`},method:`POST`})).json().catch(()=>void 0);if(!e?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");return e.nonce})();return LB({...t,address:n.address,chainId:r,domain:i,nonce:u,resources:o,uri:a,version:s})}function WB(e,t=``){if(!e)return;let n=(()=>{if(typeof e==`string`){let t=e.replace(/\/$/,``);return{logout:t+`/logout`,nonce:t+`/nonce`,verify:t+`/verify`}}return e})();return{logout:GB(n.logout,t),nonce:GB(n.nonce,t),verify:GB(n.verify,t)}}function GB(e,t){return!t||!e.startsWith(`/`)?e:t+e}op();function KB(e){let t=Bf(e);return zf(`0x19`,Uf(`Ethereum Signed Message: -`+qf(t)),t)}function qB(e){return Hh(KB(e))}async function JB(e,t){let{account:n=e.account}=t,r=n?ML(n):void 0;if(!r)throw Error(`account is required.`);let{domain:{name:i,version:a}}=await $m(e,{address:r.address});if(!e.chain)throw Error(`client.chain is required`);return{chainId:e.chain.id,name:i,verifyingContract:r.address,version:a}}async function YB(e,t){let{account:n=e.account,chainIds:r}=t,i=n?ML(n):void 0;if(!i)throw Error(`account is required.`);let a=await UR(e,{address:i.address,chainIds:r});return Object.entries(a).flatMap(([e,t])=>t.map(t=>bL(t,{chainId:Number(e)})))}async function XB(e,t){let{account:n=e.account,calls:r,chain:i=e.chain,feePayer:a,merchantUrl:o,nonce:s,preCalls:c,requiredFunds:l,revokeKeys:u}=t,d=n?ML(n):void 0,f=t.key??(d?PL(d,{role:`admin`}):void 0),p=t.authorizeKeys?.some(e=>e.role===`session`),{contracts:m,fees:{tokens:h}}=await BR(e,{chainId:i?.id}),g=p?m.orchestrator.address:void 0,_=(t.authorizeKeys??[]).map(e=>DL(e,{feeTokens:h,orchestrator:g})),v=(()=>t.feeToken?t.feeToken:f?.permissions?.spend?.[0]?.token)(),y=typeof c==`boolean`?c:!1,b=typeof c==`object`?c.map(({context:e,signature:t})=>({...e.preCall,signature:t})):void 0,x={address:d?.address,calls:r??[],capabilities:{authorizeKeys:_,meta:{feePayer:a,feeToken:v,nonce:s},preCall:y,preCalls:b,requiredFunds:l,revokeKeys:u?.map(e=>({hash:e.hash}))},chain:i,key:f?DL(f,{feeTokens:h}):void 0},S=await(async()=>{if(o){let t=Xm({chain:e.chain,transport:zv(o)});return await GR(t,x).catch(t=>(console.error(t),GR(e,x)))}return await GR(e,x)})(),{capabilities:C,context:w,digest:ee,signature:te,typedData:ne}=S;if(o&&!await ZR(e,{response:S._raw,signature:te}))throw Error(`cannot verify integrity of \`wallet_prepareCalls\` response from ${o}`);return{capabilities:{...C,quote:w.quote},context:w,digest:ee,key:f,typedData:ne}}async function ZB(e,t){let{address:n,authorizeKeys:r,chain:i=e.chain}=t;if(!i)throw Error(`chain is required.`);let{contracts:a,fees:{tokens:o}}=await BR(e,{chainId:i.id}),s=t.delegation??a.accountProxy.address,c=r.some(e=>e.role===`session`)?a.orchestrator.address:void 0,l=r.map(e=>{let t=e.role===`session`?e.permissions:{};return DL({...e,permissions:t},{feeTokens:o,orchestrator:c})}),{capabilities:u,chainId:d,context:f,digests:p,typedData:m}=await KR(e,{address:n,authorizeKeys:l,chain:i,delegation:s}),h=ML({address:n,keys:r});return{capabilities:u,chainId:d,context:{...f,account:h},digests:p,typedData:m}}async function QB(e,t){let{account:n=e.account,chain:r=e.chain,webAuthn:i}=t;if(!r)throw Error("`chain` is required.");let a=n?ML(n):void 0;if(!a)throw Error("`account` is required.");let o=t.key??PL(a,t);if(!o&&!a.sign)throw Error("`key` or `account` with `sign` is required");let s=await Promise.all((t.preCalls??[]).map(async n=>{if(n.signature)return n;let{authorizeKeys:o,key:s,calls:c,revokeKeys:l}=n,{context:u,digest:d}=await XB(e,{account:a,authorizeKeys:o,calls:c,chain:r,feeToken:t.feeToken,key:s,preCalls:!0,revokeKeys:l}),f=await EL(s,{address:null,payload:d,webAuthn:i});return{context:u,signature:f}})),{capabilities:c,context:l,digest:u}=await XB(e,{...t,account:a,chain:r,key:o,preCalls:s}),d=await(async()=>o?await EL(o,{address:null,payload:u,webAuthn:i,wrap:!1}):await a.sign({hash:u}))();return await $B(e,{capabilities:c.feeSignature?{feeSignature:c.feeSignature}:void 0,context:l,key:o,signature:d})}async function $B(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;return await qR(e,{capabilities:n,context:r,key:i?DL(i):void 0,signature:a})}async function eV(e,t){let{email:n,walletAddress:r}=t;return await JR(e,{email:n,walletAddress:r})}async function tV(e,t){if(t.account){let{account:n}=t,r=[...n.keys??[],...t.authorizeKeys??[]].filter((e,t,n)=>n.findIndex(t=>t.id===e.id)===t),{digests:i,...a}=await ZB(e,{...t,address:n.address,authorizeKeys:r}),o={auth:await n.sign({hash:i.auth}),exec:await n.sign({hash:i.exec})};return await tV(e,{...a,signatures:o})}let{context:n,signatures:r}=t,i=ML(n.account);return await YR(e,{context:n,signatures:r}),i}async function nV(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;return await XR(e,{chainId:n,email:r,signature:i,token:a,walletAddress:o})}async function rV(e,t){let{address:n}=t,{authorization:r,data:i,to:a}=await zR(e,{address:n});return P_({authorization:{...r,nonce:BigInt(r.nonce),r:BigInt(r.r),s:BigInt(r.s)},data:i,signature:t.signature,to:a})}function iV(e,t){let{tokens:n}=t,r=n.filter(e=>e.interop);return e.map(e=>{if(e.address)return e;let t=r.find(t=>t.symbol===e.symbol);if(!t)throw Error(`interop token not found: ${e.symbol}`);return{address:t.address,value:LI(e.value,t.decimals)}})}async function aV(e,t){let{chain:n=e.chain}=t??{};return await BR(e,{chainId:n?.id}).then(e=>e.fees.tokens)}async function oV(e,t){let{addressOrSymbol:n}=t;return(await aV(e,t)).find(oV.predicate(n))}(function(e){function t(e){return t=>e?og(e)?ag(t.address,e):e===`native`?t.address===Q_:e===t.symbol:!1}e.predicate=t})(oV||={});async function sV(e,t){let{chain:n=e.chain,store:r}=t??{},i=r?.getState()??{},a=t?.addressOrSymbol??i.feeToken;return(await aV(e,{chain:n}).then(e=>e.filter(e=>e.feeToken)))?.find(e=>a?a===`native`&&e.address===`0x0000000000000000000000000000000000000000`||og(a)&&ag(e.address,a)?!0:a===e.symbol:!1)}Lf(),op(),pf();function cV(e={}){let t=e,{mock:n,multichain:r=!0,webAuthn:i}=t,a,o,s=(()=>{if(t.keystoreHost!==`self`&&!(typeof window<`u`&&window.location?.hostname===`localhost`))return t.keystoreHost})();return xB({actions:{async addFunds(){throw new WM},async createAccount(e){let{admins:t,email:r,label:o,permissions:c,internal:l,signInWithEthereum:u}=e,{client:d}=l,f=NL(D_()),p=await aV(d),m=n?gL():await hL({createFn:i?.createFn,label:o||`${f.address.slice(0,8)}\u2026${f.address.slice(-6)}`,rpId:s,userId:hf(f.address)}),h=await OB(c,{chainId:d.chain.id,feeTokens:p}),g=t?.map(e=>vL(e)),_=await tV(d,{account:f,authorizeKeys:[m,...g??[],...h?[h]:[]]});a=f.address,r&&o&&await eV(d,{email:o,walletAddress:_.address});let v=await(async()=>{if(!u)return;let e=await UB(d,u,{address:_.address}),t=await FL(f,{payload:qB(Uf(e))}),n=await rV(d,{address:_.address,signature:t});return{message:e,signature:n}})();return{account:{..._,signInWithEthereum:v}}},async getAccountVersion(e){let{address:t,internal:n}=e,{client:r}=n,{contracts:i}=await BR(r),{accountImplementation:a}=i,o=await JB(r,{account:ML(a)}).then(e=>e.version),s=await JB(r,{account:t}).then(e=>e.version).catch(()=>o);if(!s||!o)throw Error(`version not found.`);return{current:s,latest:o}},async getAssets(e){let{account:t,chainFilter:n,assetFilter:r,assetTypeFilter:i,internal:a}=e,{client:o}=a;return await VR(o,{account:t,assetFilter:r,assetTypeFilter:i,chainFilter:n})},async getCallsStatus(e){let{id:t,internal:n}=e,{client:r}=n,i=await HR(r,{id:t});return{atomic:!0,chainId:I(r.chain.id),id:t,receipts:i.receipts?.map(e=>({blockHash:e.blockHash,blockNumber:I(e.blockNumber),gasUsed:I(e.gasUsed),logs:e.logs,status:e.status,transactionHash:e.transactionHash})),status:i.status,version:`1.0`}},async getCapabilities(e){let{chainIds:t,internal:n}=e,{client:i}=n,a={atomic:{status:`supported`},atomicBatch:{supported:!0},feeToken:{supported:!0,tokens:[]},merchant:{supported:!0},permissions:{supported:!0},requiredFunds:{supported:!!r,tokens:[]}},o=await BR(i,{chainIds:t?t.map(e=>Xf(e)):`all`,raw:!0});return Object.entries(o).reduce((e,[t,n])=>({...e,[t]:{...a,...n,feeToken:{supported:!0,tokens:n.fees.tokens},requiredFunds:{supported:!!r,tokens:r?n.fees.tokens.filter(e=>e.interop):[]}}}),{})},async getKeys(e){let{account:t,chainIds:n,internal:r}=e,{client:i}=r,a=await YB(i,{account:t,chainIds:n});return mN([...a,...t.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{account:t,internal:n}=e,{client:r}=n,a=vL(e.key,{chainId:r.chain.id}),o=await sV(r,{addressOrSymbol:e.feeToken,store:n.store}),{id:s}=await QB(r,{account:t,authorizeKeys:[a],feeToken:o?.address,webAuthn:i});return await Wm(r,{id:s,pollingInterval:500}),{key:a}},async grantPermissions(e){let{account:t,internal:n,permissions:r}=e,{client:i}=n,a=await aV(i),o=await OB(r,{chainId:i.chain.id,feeTokens:a});if(!o)throw Error(`key to authorize not found.`);let s=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!s)throw Error(`admin key not found.`);let{context:c,digest:l}=await XB(i,{account:t,authorizeKeys:[o],key:s,preCalls:!0}),u=await EL(s,{address:null,payload:l});return await $B(i,{context:c,key:s,signature:u}),{key:o}},async loadAccounts(e){let{internal:t,permissions:r,signInWithEthereum:o}=e,{client:c}=t,l=await aV(c),u=await OB(r,{chainId:c.chain.id,feeTokens:l}),{digest:d,digestType:f,message:p}=await(async()=>{if(o&&e.address){let t=await UB(c,o,{address:e.address});return{context:void 0,digest:qB(Uf(t)),digestType:`siwe`,message:t}}return{context:void 0,digest:`0x`,message:void 0}})(),{address:m,credentialId:h,webAuthnSignature:g}=await(async()=>{if(n){if(!a)throw Error(`address_internal not found.`);return{address:a,credentialId:void 0}}if(e.address&&e.key)return{address:e.address,credentialId:e.key.credentialId};let t=await tL({challenge:d,getFn:i?.getFn,rpId:s}),r=t.raw.response,o=wf(new Uint8Array(r.userHandle)),c=t.raw.id;return{address:o,credentialId:c,webAuthnSignature:t}})(),_=await YB(c,{account:m,chainIds:[c.chain.id]}),v=ML({address:m,keys:[..._,...u?[u]:[]].map((e,t)=>t===0&&e.type===`webauthn-p256`?xL({...e,credential:{id:h,publicKey:qh(e.publicKey)},id:m,rpId:s}):e)}),y=PL(v,{role:`admin`}),b=await(async()=>{if(d!==`0x`)return g?jL(AL(g),{keyType:`webauthn-p256`,publicKey:y.publicKey}):await EL(y,{address:v.address,payload:d})})();if(u){let{context:e,digest:t}=await XB(c,{account:v,authorizeKeys:[u],preCalls:!0}),n=await EL(y,{address:null,payload:t});await $B(c,{context:e,key:y,signature:n})}let x=await(async()=>{if(o){if(f===`siwe`&&p&&b){let e=await rV(c,{address:v.address,signature:b});return{message:p,signature:e}}{let e=await UB(c,o,{address:v.address}),t=await FL(v,{payload:qB(Uf(e)),role:`admin`}),n=await rV(c,{address:v.address,signature:t});return{message:e,signature:n}}}})();return{accounts:[{...v,signInWithEthereum:x}]}},async prepareCalls(e){let{account:t,calls:n,internal:i,merchantUrl:a}=e,{client:o}=i,s=e.key??await SB({account:t,calls:n});if(!s)throw Error(`cannot find authorized key to sign with.`);let[c,l]=await Promise.all([aV(o),sV(o,{addressOrSymbol:e.feeToken,store:i.store})]),u=iV(e.requiredFunds??[],{tokens:c}),{capabilities:d,context:f,digest:p,typedData:m}=await XB(o,{account:t,calls:n,feeToken:l?.address,key:s,merchantUrl:a,requiredFunds:r?u:void 0}),h=f.quote?.quotes??[],g=h[h.length-1];return{account:t,capabilities:{...d,quote:f.quote},chainId:o.chain.id,context:{...f,account:t,calls:n,nonce:g?.intent.nonce},digest:p,key:s,typedData:m}},async prepareUpgradeAccount(e){let{address:t,email:r,label:a,internal:c,permissions:l}=e,{client:u}=c,[d,f]=await Promise.all([aV(u),sV(u,{store:c.store})]),p=n?gL():await hL({createFn:i?.createFn,label:a||`${t.slice(0,8)}\u2026${t.slice(-6)}`,rpId:s,userId:hf(t)}),m=await OB(l,{chainId:u.chain.id,feeTokens:d}),{context:h,digests:g}=await ZB(u,{address:t,authorizeKeys:[p,...m?[m]:[]],feeToken:f?.address});return r&&(o=a),{context:h,digests:g}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);try{let n=await sV(a,{addressOrSymbol:e.feeToken,store:r.store}),{id:s}=await QB(a,{account:t,feeToken:n?.address,revokeKeys:[o],webAuthn:i});await Wm(a,{id:s})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke admins.`);try{let n=await sV(a,{addressOrSymbol:e.feeToken,store:r.store}),{id:s}=await QB(a,{account:t,feeToken:n?.address,revokeKeys:[o],webAuthn:i});await Wm(a,{id:s})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async sendCalls(e){let{account:t,asTxHash:n,calls:a,chainId:o,internal:s,merchantUrl:c}=e,{client:l}=s,u=await SB({account:t,calls:a,permissionsId:e.permissionsId}),[d,f]=await Promise.all([aV(l),sV(l,{addressOrSymbol:e.feeToken,store:s.store})]),p=iV(e.requiredFunds??[],{tokens:d}),m=await QB(l,{account:t,calls:a,feeToken:f?.address,key:u,merchantUrl:c,requiredFunds:r?p:void 0,webAuthn:i,...o?{chain:{id:o}}:{}});if(n){let{id:e,receipts:t,status:n}=await Wm(l,{id:m.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new ZM({message:`Call bundle with id: `+e+` not found.`}):new MM({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return m},async sendPreparedCalls(e){let{context:t,key:n,internal:r,signature:i}=e,{client:a}=r,{id:o}=await $B(a,{context:t,key:n,signature:i});return o},async signPersonalMessage(e){let{account:t,data:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!o)throw Error(`cannot find admin key to sign with.`);let s=await FL(t,{key:o,payload:qB(n),webAuthn:i});return rV(a,{address:t.address,signature:s})},async signTypedData(e){let{account:t,internal:n}=e,{client:r}=n,a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!a)throw Error(`cannot find admin key to sign with.`);let o=uf(e.data),s=o.domain?.name===`Orchestrator`,c=await FL(t,{key:a,payload:lI(o),replaySafe:!s,webAuthn:i});return s?c:rV(r,{address:t.address,signature:c})},async upgradeAccount(e){let{account:t,context:n,internal:r,signatures:i}=e,{client:a}=r;return await tV(a,{context:n,signatures:i}),o&&await eV(a,{email:o,walletAddress:t.address}),{account:t}},async verifyEmail(e){let{account:t,chainId:n,email:r,token:a,internal:o,walletAddress:s}=e,{client:c}=o,l=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!l)throw Error(`cannot find admin key to sign with.`);let u=await FL(t,{key:l,payload:Hh(Uf(`${r}${a}`)),webAuthn:i});return await nV(c,{chainId:n,email:r,signature:u,token:a,walletAddress:s})}},config:e,name:`rpc`})}op();function lV(e={}){let{fallback:t=cV(),host:n=bN.prod,renderer:r=SN(),theme:i,themeController:a}=e,o=new Set,s=CB();function c(e){return nN({async request(t){let n=s.prepare(t);return e.setState(e=>{let t=e.accounts[0],r=t?.keys?.find(e=>e.role===`admin`&&e.type===`webauthn-p256`);return{...e,requestQueue:[...e.requestQueue,{account:t?{address:t.address,key:r?{credentialId:r?.credentialId,publicKey:r.publicKey}:void 0}:void 0,request:n,status:`pending`}]}}),new Promise((t,r)=>{let i=a=>{let s=a.find(e=>e.request.id===n.id);if(!s&&a.length===0){o.delete(i),r(new HM);return}s&&(s.status!==`success`&&s.status!==`error`||(o.delete(i),s.status===`success`?t(s.result):r(rN(s.error)),e.setState(e=>({...e,requestQueue:e.requestQueue.filter(e=>e.request.id!==n.id)}))))};o.add(i)})}},{schema:TB()})}return xB({actions:{async addFunds(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_addFunds`)throw Error(`Cannot add funds for method: `+n.method);return await c(r).request(n)},async createAccount(e){let{internal:t}=e,{client:n,config:r,request:i,store:a}=t,{storage:o}=r,s=c(a);return{account:await(async()=>{if(i.method===`wallet_connect`){let[{capabilities:e,chainIds:t}]=i._decoded.params??[{}],a=dV(e?.signInWithEthereum?.authUrl??r.authUrl,{storage:o}),c=i.params?.[0]?.capabilities?.signInWithEthereum,l=await OB(e?.grantPermissions,{chainId:n.chain.id}),u=l?pP(EB,DB(l)):void 0,{accounts:d}=await s.request({...i,params:[{capabilities:{...i.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:a||c?{...c,authUrl:a}:void 0},chainIds:t?.map(e=>I(e))}]}),[f]=d;if(!f)throw Error(`no account found.`);let p=f.capabilities?.admins?.map(e=>vL(e,{chainId:n.chain.id})).filter(Boolean),m=f.capabilities?.permissions?.map(e=>{try{let t=yz(mP(_z,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),h=await(async()=>{if(!f.capabilities?.signInWithEthereum)return;let{message:e,signature:t}=f.capabilities.signInWithEthereum;if(!a)return{message:e,signature:t};let{token:n}=await HB({address:f.address,authUrl:a,message:e,publicKey:f.capabilities?.admins?.[0]?.publicKey,signature:t});return{message:e,signature:t,token:n}})();return{...ML({address:f.address,keys:[...p??[],...m??[]]}),signInWithEthereum:h}}throw Error(`Account creation not supported on method: ${i.method}`)})()}},async disconnect(e){let{internal:t}=e,{config:n}=t,{storage:r}=n,i=await r.getItem(`porto.authUrl`)||void 0,a=dV(n.authUrl??i,{storage:r});a&&await fetch(a.logout,{credentials:`include`,method:`POST`}).catch(()=>{})},async getAccountVersion(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAccountVersion`)throw Error(`Cannot get version for method: `+a.method);return r.supportsHeadless?await c(i).request(a):t.actions.getAccountVersion(e)},async getAssets(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAssets`)throw Error(`Cannot get assets for method: `+a.method);if(!r.supportsHeadless)return t.actions.getAssets(e);let o=await c(i).request(a);return mP(rB.Response,o)},async getCallsStatus(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCallsStatus`)throw Error(`Cannot get status for method: `+a.method);return r.supportsHeadless?await c(i).request(a):t.actions.getCallsStatus(e)},async getCapabilities(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCapabilities`)throw Error(`Cannot get capabilities for method: `+a.method);return r.supportsHeadless?await c(i).request(a):t.actions.getCapabilities(e)},async getKeys(e){let{account:n,chainIds:i,internal:a}=e,{store:o}=a,s=await(async()=>{if(!r.supportsHeadless)return t.actions.getKeys(e);let a=await c(o).request({method:`wallet_getKeys`,params:[pP(oB.Parameters,{address:n.address,chainIds:i})]});return mP(oB.Response,a)})();return mN([...s,...n.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_grantAdmin`)throw Error(`Cannot authorize admin for method: `+n.method);let[i]=n._decoded.params,a=vL(i.key);if(!a)throw Error(`no key found.`);let o=await uV(t,e);return await c(r).request({method:`wallet_grantAdmin`,params:[{...n.params?.[0],capabilities:{...n.params?.[0]?.capabilities,feeToken:o}}]}),{key:a}},async grantPermissions(e){let{internal:t}=e,{client:n,request:r,store:i}=t;if(r.method!==`wallet_grantPermissions`)throw Error(`Cannot grant permissions for method: `+r.method);let[{address:a,...o}]=r._decoded.params,s=await OB(o,{chainId:n.chain.id});if(!s)throw Error(`no key found.`);let l=pP(EB,DB(s));return await c(i).request({method:`wallet_grantPermissions`,params:[l]}),{key:s}},async loadAccounts(e){let{internal:t}=e,{client:n,config:r,store:i}=t,{storage:a}=r,o=c(i),s=t.request;if(s.method!==`wallet_connect`&&s.method!==`eth_requestAccounts`)throw Error(`Cannot load accounts for method: `+s.method);return{accounts:await(async()=>{let[e]=s._decoded.params??[],{capabilities:t}=e??{},i=dV(t?.signInWithEthereum?.authUrl??r.authUrl,{storage:a}),c=s.params?.[0]?.capabilities?.signInWithEthereum,l=await OB(t?.grantPermissions,{chainId:n.chain.id}),u=l?pP(EB,DB(l)):void 0,{accounts:d}=await o.request({method:`wallet_connect`,params:[{...s.params?.[0],capabilities:{...s.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:i||c?{...c,authUrl:i}:void 0}}]});return Promise.all(d.map(async e=>{let t=e.capabilities?.admins?.map(e=>vL(e)).filter(Boolean),n=e.capabilities?.permissions?.map(e=>{try{let t=yz(mP(_z,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),r=await(async()=>{if(!e.capabilities?.signInWithEthereum)return;let{message:t,signature:n}=e.capabilities.signInWithEthereum;if(!i)return{message:t,signature:n};let{token:r}=await HB({address:e.address,authUrl:i,message:t,publicKey:e.capabilities?.admins?.[0]?.publicKey,signature:n});return{message:t,signature:n,token:r}})();return{...ML({address:e.address,keys:[...t??[],...n??[]]}),signInWithEthereum:r}}))})()}},async prepareCalls(e){let{account:n,internal:i}=e,{store:a,request:o}=i;if(o.method!==`wallet_prepareCalls`)throw Error(`Cannot prepare calls for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareCalls(e);let s=await uV(i,e),l=c(a),u=mP(sB.Response,await l.request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,feeToken:s}}]}));return{account:n,chainId:Number(u.chainId),context:u.context,digest:u.digest,key:u.key,typedData:u.typedData}},async prepareUpgradeAccount(e){let{internal:n}=e,{client:i,store:a,request:o}=n;if(o.method!==`wallet_prepareUpgradeAccount`)throw Error(`Cannot prepare upgrade for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareUpgradeAccount(e);let[{capabilities:s}]=o._decoded.params??[{}],l=await OB(s?.grantPermissions,{chainId:i.chain.id}),u=l?pP(EB,DB(l)):void 0,{context:d,digests:f}=await c(a).request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,grantPermissions:u}}]}),p=d.account.keys?.map(e=>e.id===l?.id?{...e,...l}:e);return{context:{...d,account:{...d.account,keys:p}},digests:f}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokeAdmin`)throw Error(`Cannot revoke admin for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(!o)return;if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);let s=await uV(r,e);return await c(i).request({...a,params:[{...a.params?.[0],capabilities:{...a.params?.[0]?.capabilities,feeToken:s}}]})},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokePermissions`)throw Error(`Cannot revoke permissions for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke permissions.`);return await c(i).request(a)}},async sendCalls(e){let{account:n,asTxHash:i,calls:a,chainId:o,internal:s,merchantUrl:l,requiredFunds:u}=e,{client:d,store:f,request:p}=s,m=c(f),h=await uV(s,e),g=await SB({account:n,calls:a,permissionsId:e.permissionsId});if(g&&g.role===`session`){if(!r.supportsHeadless)return t.actions.sendCalls(e);try{let e=await m.request(pP(sB.Request,{method:`wallet_prepareCalls`,params:[{calls:a,capabilities:{...p._decoded.method===`wallet_sendCalls`?p._decoded.params?.[0]?.capabilities:void 0,feeToken:h,merchantUrl:l,requiredFunds:u},chainId:o,from:n.address,key:g}]})),t=e.capabilities?.quote?.quotes??[];if(t.some((e,n)=>n===t.length-1&&t.length>1?!1:Yf(e.feeTokenDeficit)>0n))throw Error(`insufficient funds`);let r=await EL(g,{address:null,payload:e.digest,wrap:!1}),s=(await m.request({method:`wallet_sendPreparedCalls`,params:[{...e,signature:r}]}))[0];if(!s)throw Error(`id not found`);if(i){let{id:e,receipts:t,status:n}=await Wm(d,{id:s.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new ZM({message:`Call bundle with id: `+e+` not found.`}):new MM({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return s}catch{}}if(p.method===`eth_sendTransaction`)return{id:await m.request({...p,params:[{...p.params?.[0],capabilities:{feeToken:h,merchantUrl:l},...o?{chainId:I(o)}:{}}]})};if(p.method===`wallet_sendCalls`)return await m.request({method:`wallet_sendCalls`,params:[{...p.params?.[0],capabilities:{...p.params?.[0]?.capabilities,feeToken:h,merchantUrl:l},...o?{chainId:I(o)}:{}}]});throw Error(`Cannot execute for method: `+p.method)},async sendPreparedCalls(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_sendPreparedCalls`)throw Error(`Cannot send prepared calls for method: `+a.method);if(!r.supportsHeadless)return t.actions.sendPreparedCalls(e);let o=(await c(i).request(a))[0]?.id;if(!o)throw Error(`id not found`);return o},async signPersonalMessage(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`personal_sign`)throw Error(`Cannot sign personal message for method: `+r.method);return await c(n).request(r)},async signTypedData(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`eth_signTypedData_v4`)throw Error(`Cannot sign typed data for method: `+r.method);return await c(n).request(r)},async switchChain(e){let{internal:t}=e,{store:n,request:i}=t;if(i.method!==`wallet_switchEthereumChain`)throw Error(`Cannot switch chain for method: `+i.method);if(r.supportsHeadless)return await c(n).request(i)},async upgradeAccount(e){let{account:t,internal:n}=e,{store:r,request:i}=n;if(i.method!==`wallet_upgradeAccount`)throw Error(`Cannot upgrade account for method: `+i.method);return await c(r).request(i),{account:t}},async verifyEmail(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`account_verifyEmail`)throw Error(`Cannot verify email for method: `+n.method);return await c(r).request(n)}},config:e,name:`dialog`,setup(e){let{internal:t}=e,{store:s}=t,c=r.setup({host:n,internal:t,theme:i,themeController:a}),l=s.subscribe(e=>e.requestQueue,e=>{for(let t of o)t(e);let t=e.map(e=>e.status===`pending`?e:void 0).filter(Boolean);c.syncRequests(t).catch(()=>{}),t.length===0&&c.close()});return()=>{l(),c.destroy()}}})}async function uV(e,t){let{config:{feeToken:n}}=e,{feeToken:r}=t??{};return r??n}function dV(e,{storage:t}){if(!e)return;let n=WB(e,typeof window<`u`?window.location.origin:void 0);return n&&t.setItem(`porto.authUrl`,n),n}var fV=new Map,pV=e=>{let t=fV.get(e);return t?Object.fromEntries(Object.entries(t.stores).map(([e,t])=>[e,t.getState()])):{}},mV=(e,t,n)=>{if(e===void 0)return{type:`untracked`,connection:t.connect(n)};let r=fV.get(n.name);if(r)return{type:`tracked`,store:e,...r};let i={connection:t.connect(n),stores:{}};return fV.set(n.name,i),{type:`tracked`,store:e,...i}},hV=(e,t)=>{if(t===void 0)return;let n=fV.get(e);n&&(delete n.stores[t],Object.keys(n.stores).length===0&&fV.delete(e))},gV=e=>{if(!e)return;let t=e.split(` -`),n=t.findIndex(e=>e.includes(`api.setState`));if(n<0)return;let r=t[n+1]?.trim()||``;return/.+ (.+) .+/.exec(r)?.[1]},_V=(e,t={})=>(n,r,i)=>{let{enabled:a,anonymousActionType:o,store:s,...c}=t,l;try{l=(a??!1)&&window.__REDUX_DEVTOOLS_EXTENSION__}catch{}if(!l)return e(n,r,i);let{connection:u,...d}=mV(s,l,c),f=!0;i.setState=((e,t,a)=>{let l=n(e,t);if(!f)return l;let d=a===void 0?{type:o||gV(Error().stack)||`anonymous`}:typeof a==`string`?{type:a}:a;return s===void 0?(u?.send(d,r()),l):(u?.send({...d,type:`${s}/${d.type}`},{...pV(c.name),[s]:i.getState()}),l)}),i.devtools={cleanup:()=>{u&&typeof u.unsubscribe==`function`&&u.unsubscribe(),hV(c.name,s)}};let p=(...e)=>{let t=f;f=!1,n(...e),f=t},m=e(i.setState,r,i);if(d.type===`untracked`?u?.init(m):(d.stores[d.store]=i,u?.init(Object.fromEntries(Object.entries(d.stores).map(([e,t])=>[e,e===d.store?m:t.getState()])))),i.dispatchFromDevtools&&typeof i.dispatch==`function`){let e=i.dispatch;i.dispatch=(...t)=>{e(...t)}}return u.subscribe(e=>{switch(e.type){case`ACTION`:if(typeof e.payload!=`string`){console.error(`[zustand devtools middleware] Unsupported action format`);return}return vV(e.payload,e=>{if(e.type===`__setState`){if(s===void 0){p(e.state);return}Object.keys(e.state).length!==1&&console.error(` +`,n+=GU(e));return new UU(n)}function GU(e,t=0){let n=KU(e.path),r=`- ${n?`${n}: `:``}`,i=` `.repeat(t+1),a=r;switch(e.code){case`invalid_type`:{let t=e.expected,n=e.input?qU(e):`undefined`;a+=`Expected ${t}. ${e.message===`Invalid input`?``:e.message}`,n!==`undefined`&&(a+=`but received ${n}`);break}case`too_big`:{let t=e.maximum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at most`:`less than`} ${t}`;break}case`too_small`:{let t=e.minimum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at least`:`greater than`} ${t}`;break}case`invalid_format`:switch(e.format){case`regex`:a+=`Must match pattern: ${e.pattern}`;break;case`starts_with`:a+=`Must start with "${e.prefix}"`;break;case`ends_with`:a+=`Must end with "${e.suffix}"`;break;case`includes`:a+=`Must include "${e.includes}"`;break;case`template_literal`:a+=`Must match pattern: ${e.pattern}`;break;default:a+=`Invalid ${e.format} format`}break;case`not_multiple_of`:a+=`Number must be a multiple of ${e.divisor}`;break;case`unrecognized_keys`:{let t=e.keys.map(e=>`"${e}"`).join(`, `);a+=`Unrecognized key${e.keys.length>1?`s`:``}: ${t}`;break}case`invalid_union`:{let n=e.errors&&e.errors.length>0;a+=`Invalid union value.`,n&&e.errors.forEach(e=>{e.length>0&&e.forEach(e=>{a+=` +`,a+=i,a+=GU(e,t+1)})});break}case`invalid_key`:a+=`Invalid ${e.origin} key`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` +`,a+=i,a+=GU(e,t+1)});break;case`invalid_element`:a+=`Invalid ${e.origin} element at key "${e.key}"`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` +`,a+=i,a+=GU(e,t+1)});break;case`invalid_value`:{let t=e.values.map(e=>JSON.stringify(e)).join(`, `);e.values.length>1?a+=`Expected one of: ${t}`:a+=`Expected ${t}`;break}case`custom`:a+=e.message||`Custom validation failed`;break;default:a+=e.message||`Validation failed`}return a}function KU(e){return e.length===0?``:"at `"+e.map((e,t)=>typeof e==`number`?`[${e}]`:typeof e==`symbol`?`[${e.toString()}]`:/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)&&t>0?`.${e}`:t===0&&/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)?e:`["${e}"]`).join(``)+"`"}function qU(e){let t=e.input;if(t===void 0)return`undefined`;if(t===null)return`null`;let n=typeof t;return n===`object`?Array.isArray(t)?`array`:t instanceof Date?`date`:t instanceof Map?`map`:t instanceof Set?`set`:`object`:n}var JU=G([W({selector:Z(),to:X(),type:q(`call`)}),W({limit:VU(),period:G([q(`minute`),q(`hour`),q(`day`),q(`week`),q(`month`),q(`year`)]),token:J(G([X(),iU()])),type:q(`spend`)})]),YU=W({expiry:Q(),prehash:J(QH()),publicKey:Z(),role:G([q(`admin`),q(`normal`)]),type:G([q(`p256`),q(`secp256k1`),q(`webauthnp256`)])}),XU=W({...YU.shape,permissions:Y(U(JU))}),ZU;(function(e){e.AssetDiffAsset=G([W({address:J(G([X(),iU()])),decimals:J(G([H(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),name:J(G([V(),iU()])),symbol:V(),type:q(`erc20`),value:VU()}),W({address:J(G([X(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),name:J(G([V(),iU()])),symbol:V(),type:q(`erc721`),uri:V(),value:VU()}),W({address:iU(),decimals:J(G([H(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),symbol:V(),type:iU(),value:VU()})]),e.Response=xU(Z(),Y(U(Y(K([X(),Y(U(e.AssetDiffAsset))])))))})(ZU||={});var QU;(function(e){e.Request=Y(U(XU)),e.Response=Y(U(W({...XU.shape,hash:Z()})))})(QU||={});var $U;(function(e){e.Response=xU(Z(),W({currency:V(),value:V()}))})($U||={});var eW;(function(e){e.Request=W({feePayer:J(X()),feeToken:J(X()),nonce:J(VU())})})(eW||={});var tW;(function(e){e.Request=Y(U(W({address:X(),value:VU()})))})(tW||={});var nW;(function(e){e.Request=Y(U(W({hash:Z()}))),e.Response=Y(U(W({hash:Z()})))})(nW||={});var rW=W({eoa:X(),executionData:Z(),nonce:Z(),signature:Z()}),iW=W({...rW.shape,chainId:Q()}),aW=G([W({combinedGas:VU(),encodedFundTransfers:Y(U(Z())),encodedPreCalls:Y(U(Z())),eoa:X(),executionData:Z(),expiry:VU(),funder:X(),funderSignature:Z(),isMultichain:QH(),nonce:VU(),payer:X(),paymentAmount:VU(),paymentMaxAmount:VU(),paymentRecipient:X(),paymentSignature:Z(),paymentToken:X(),settler:X(),settlerContext:Z(),signature:Z(),supportedAccountImplementation:X()}),W({combinedGas:VU(),encodedFundTransfers:Y(U(Z())),encodedPreCalls:Y(U(Z())),eoa:X(),executionData:Z(),expiry:VU(),funder:X(),funderSignature:Z(),isMultichain:QH(),nonce:VU(),payer:X(),paymentRecipient:X(),paymentSignature:Z(),paymentToken:X(),prePaymentAmount:VU(),prePaymentMaxAmount:VU(),settler:X(),settlerContext:Z(),signature:Z(),supportedAccountImplementation:X(),totalPaymentAmount:VU(),totalPaymentMaxAmount:VU()})]);VU();var oW=W({address:G([X(),iU()]),decimals:J(H()),deficit:VU(),fiat:J(W({currency:V(),value:V()})),name:J(V()),required:VU(),symbol:J(V())}),sW=W({additionalAuthorization:EU(W({address:X(),chainId:Q(),nonce:Q(),r:Z(),s:Z(),yParity:Q()})),assetDeficits:J(U(oW)),authorizationAddress:J(G([X(),iU()])),chainId:Q(),ethPrice:VU(),extraPayment:VU(),feeTokenDeficit:VU(),intent:aW,nativeFeeEstimate:W({maxFeePerGas:VU(),maxPriorityFeePerGas:VU()}),orchestrator:X(),paymentTokenDecimals:H(),txGas:VU()}),cW=W({...W({multiChainRoot:J(G([Z(),iU()])),quotes:Y(U(sW)).check(KH(1)),ttl:H()}).shape,hash:Z(),r:Z(),s:Z(),v:J(Z()),yParity:J(Z())}),lW=W({address:X(),decimals:H(),feeToken:J(QH()),interop:J(QH()),nativeRate:J(VU()),symbol:V(),uid:V()}),uW=V().check(qH(/^[A-Z0-9]+$/)),dW=W({address:X(),chainId:Q(),nonce:Q()}),fW=W({...dW.shape,r:Z(),s:Z(),yParity:Q()}),pW=W({data:J(Z()),to:X(),value:J(VU())}),mW;(function(e){e.Parameters=W({address:X(),secret:V()}),e.Request=W({method:q(`account_getOnrampContactInfo`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(V()),phone:J(V()),phoneVerifiedAt:J(H())})})(mW||={});var hW;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`account_onrampStatus`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(H()),phone:J(H())})})(hW||={});var gW;(function(e){e.Parameters=W({phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_resendVerifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(gW||={});var _W;(function(e){e.Parameters=W({email:V().check(qH(/^.*@.*$/)),walletAddress:X()}),e.Request=W({method:q(`account_setEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(_W||={});var vW;(function(e){e.Parameters=W({phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_setPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(vW||={});var yW;(function(e){e.Parameters=W({chainId:Q(),email:V(),signature:Z(),token:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(yW||={});var bW;(function(e){e.Parameters=W({code:V(),phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(bW||={});var xW;(function(e){e.Request=W({method:q(`health`),params:nU()}),e.Response=W({quoteSigner:X(),status:V(),version:V()})})(xW||={});var SW;(function(e){e.Parameters=W({address:X(),chainId:Q(),tokenAddress:X(),value:VU()}),e.Request=W({method:q(`wallet_addFaucetFunds`),params:Y(K([e.Parameters]))}),e.Response=W({message:J(V()),transactionHash:Z()})})(SW||={});var CW;(function(e){e.Parameters=W({chainId:Q(),id:Z()}),e.Request=W({method:q(`wallet_getAccounts`),params:Y(K([e.Parameters]))}),e.Response=Y(U(W({address:X(),keys:QU.Response})))})(CW||={});var wW;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`wallet_getAuthorization`),params:Y(K([e.Parameters]))}),e.Response=W({authorization:fW,data:Z(),to:X()})})(wW||={});var TW;(function(e){e.Request=W({method:q(`wallet_getCapabilities`),params:J(K([Y(U(H()))]))});let t=W({address:X(),version:J(G([V(),iU()]))});e.Response=xU(Z(),W({contracts:W({accountImplementation:t,accountProxy:t,legacyAccountImplementations:Y(U(t)),legacyOrchestrators:Y(U(G([W({orchestrator:t,simulator:t}),t]))),orchestrator:t,simulator:t}),fees:W({quoteConfig:W({constantRate:J(G([H(),iU()])),gas:J(W({intentBuffer:J(H()),txBuffer:J(H())})),rateTtl:H(),ttl:H()}),recipient:X(),tokens:Y(U(lW))})}))})(TW||={});var EW;(function(e){let t=G([q(`native`),q(`erc20`),q(`erc721`),V()]);e.Parameters=W({account:X(),assetFilter:J(xU(Z(),Y(U(W({address:G([X(),q(`native`)]),type:t}))))),assetTypeFilter:J(Y(U(t))),chainFilter:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getAssets`),params:Y(K([e.Parameters]))}),e.Price=W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})}),e.Response=xU(V(),Y(U(HU([W({address:X(),balance:VU(),metadata:TU(W({decimals:H(),fiat:EU(e.Price),name:V(),symbol:V()})),type:q(`erc20`)}),W({address:TU(q(`native`)),balance:VU(),metadata:TU(W({decimals:H(),fiat:EU(e.Price),name:J(V()),symbol:J(V())})),type:q(`native`)})]))))})(EW||={});var DW;(function(e){e.Request=W({method:q(`wallet_getCallsStatus`),params:Y(K([Z()]))}),e.Response=W({id:V(),receipts:J(Y(U(W({blockHash:Z(),blockNumber:Q(),chainId:Q(),gasUsed:Q(),logs:Y(U(W({address:X(),data:Z(),topics:Y(U(Z()))}))),status:Z(),transactionHash:Z()})))),status:H()})})(DW||={});var OW;(function(e){e.Parameters=W({address:X(),index:J(H()),limit:H(),sort:G([q(`asc`),q(`desc`)])}),e.Request=W({method:q(`wallet_getCallsHistory`),params:Y(K([e.Parameters]))}),e.Transaction=W({chainId:Q(),transactionHash:Z()}),e.Capabilities=W({assetDiffs:J(ZU.Response),feeTotals:J($U.Response),quotes:J(Y(U(sW)))}),e.Entry=W({capabilities:e.Capabilities,id:Z(),index:H(),keyHash:Z(),status:H(),timestamp:H(),transactions:Y(U(e.Transaction))}),e.Response=Y(U(e.Entry))})(OW||={});var kW;(function(e){e.Parameters=W({address:X(),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getKeys`),params:Y(K([e.Parameters]))}),e.Response=xU(Z(),QU.Response)})(kW||={});var AW;(function(e){e.Capabilities=W({authorizeKeys:J(QU.Request),meta:eW.Request,preCall:J(QH()),preCalls:J(Y(U(rW))),requiredFunds:J(tW.Request),revokeKeys:J(nW.Request)}),e.ResponseCapabilities=W({assetDiffs:J(ZU.Response),authorizeKeys:EU(QU.Response),feePayerDigest:J(Z()),feeSignature:J(Z()),feeTotals:J($U.Response),revokeKeys:EU(nW.Response)}),e.Parameters=W({calls:Y(U(pW)),capabilities:e.Capabilities,chainId:Q(),from:J(X()),key:J(W({prehash:QH(),publicKey:Z(),type:YU.shape.type}))}),e.Request=W({method:q(`wallet_prepareCalls`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:e.ResponseCapabilities,context:W({preCall:J(hU(iW)),quote:J(hU(cW))}),digest:Z(),key:EU(W({prehash:QH(),publicKey:Z(),type:YU.shape.type})),signature:Z(),typedData:W({domain:G([W({chainId:G([Q(),H()]),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(AW||={});var jW;(function(e){e.Capabilities=W({authorizeKeys:QU.Request}),e.Parameters=W({address:X(),capabilities:e.Capabilities,chainId:J(H()),delegation:X()}),e.Request=W({method:q(`wallet_prepareUpgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:e.Capabilities,chainId:Q(),context:W({address:X(),authorization:dW,chainId:Q(),preCall:rW}),digests:W({auth:Z(),exec:Z()}),typedData:W({domain:G([W({chainId:G([Q(),H()]),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(jW||={});var MW;(function(e){e.Request=W({method:q(`wallet_feeTokens`),params:J(nU())}),e.Response=xU(Z(),Y(U(W({address:X(),decimals:H(),nativeRate:J(VU()),symbol:V()}))))})(MW||={});var NW;(function(e){e.Parameters=W({capabilities:J(W({feeSignature:J(Z())})),context:W({preCall:J(hU(iW)),quote:J(hU(cW))}),key:J(W({prehash:QH(),publicKey:Z(),type:YU.shape.type})),signature:Z()}),e.Request=W({method:q(`wallet_sendPreparedCalls`),params:Y(K([e.Parameters]))}),e.Response=W({id:Z()})})(NW||={});var PW;(function(e){e.Parameters=W({context:W({address:X(),authorization:dW,chainId:Q(),preCall:rW}),signatures:W({auth:Z(),exec:Z()})}),e.Request=W({method:q(`wallet_upgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=nU()})(PW||={});var FW;(function(e){e.Parameters=W({address:Z(),chainId:Q(),digest:Z(),signature:Z()}),e.Request=W({method:q(`wallet_verifySignature`),params:Y(K([e.Parameters]))}),e.Response=W({proof:EU(W({account:X(),initPreCall:EU(rW),keyHash:Z()})),valid:QH()})})(FW||={});async function IW(e,t){try{let n=`wallet_getAuthorization`,r=await Mm(()=>e.request({method:n,params:[RV(wW.Parameters,t)]}),{cacheKey:`${e.uid}.${n}.${t.address}`});return zV(wW.Response,r)}catch(e){throw $W(e),e}}async function LW(e,t={}){let n=(()=>{if(t.chainId)return[t.chainId];if(t.chainIds!==`all`)return t.chainIds?t.chainIds:[e.chain.id]})();try{let r=`wallet_getCapabilities`,i=await Mm(()=>e.request({method:r,params:n?[n]:void 0},{retryCount:0}),{cacheKey:`${e.uid}.${r}.${n?.join(`,`)}`}),a=t.raw?i:zV(TW.Response,i);return t.chainIds?a:Object.values(a)[0]}catch(e){throw $W(e),e}}async function RW(e,t){let{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a}=t;try{let t=await e.request({method:`wallet_getAssets`,params:[RV(EW.Parameters,{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a})]}),o=zV(EW.Response,t),s=Object.entries(o).reduce((e,[t,n])=>(e[sL(t)]=n,e),{}),c={};for(let e of Object.values(s))for(let t of e){let e=JSON.stringify(t.metadata);c[e]={...t,balance:t.balance+(c[e]?.balance??0n)}}return{...s,0:Object.values(c)}}catch(e){throw $W(e),e}}async function zW(e,t){let{id:n}=t;try{let t=await e.request({method:`wallet_getCallsStatus`,params:[n]});return zV(DW.Response,t)}catch(e){throw $W(e),e}}async function BW(e,t){try{let n=await e.request({method:`wallet_getCallsHistory`,params:[RV(OW.Parameters,t)]});return zV(OW.Response,n)}catch(e){throw $W(e),e}}async function VW(e,t){let{address:n,chainIds:r}=t;try{let t=await e.request({method:`wallet_getKeys`,params:[RV(kW.Parameters,{address:n,chainIds:r})]});return zV(kW.Response,t)}catch(e){throw $W(e),e}}async function HW(e){let t=`health`,n=await Mm(()=>e.request({method:t}),{cacheKey:`${e.uid}.${t}`});return zV(xW.Response,n)}async function UW(e,t){let{address:n,capabilities:r,chain:i=e.chain,key:a}=t,o=t.calls.map(e=>({data:e.abi?wz(Tz(e.abi,e.functionName),e.args):e.data??`0x`,to:e.to,value:e.value??0n}));try{let t=await e.request({method:`wallet_prepareCalls`,params:[RV(AW.Parameters,{calls:o,capabilities:{...r,meta:{...r?.meta}},chainId:i?.id,from:n,key:a?{prehash:a.prehash,publicKey:a.publicKey,type:a.type}:void 0})]},{retryCount:0});return Object.assign(zV(AW.Response,t),{_raw:t})}catch(e){throw $W(e),ZW(e,{calls:t.calls}),e}}async function WW(e,t){let{address:n,chain:r=e.chain,delegation:i,...a}=t;try{let t=await e.request({method:`wallet_prepareUpgradeAccount`,params:[RV(jW.Parameters,LB({address:n,capabilities:a,chainId:r?.id,delegation:i}))]},{retryCount:0});return zV(jW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function GW(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;try{let t=await e.request({method:`wallet_sendPreparedCalls`,params:[RV(NW.Parameters,{capabilities:n,context:{preCall:r.preCall,quote:r.quote},key:i?{prehash:i.prehash,publicKey:i.publicKey,type:i.type}:void 0,signature:a})]},{retryCount:0});return zV(NW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function KW(e,t){let{email:n,walletAddress:r}=t;try{let t=await e.request({method:`account_setEmail`,params:[RV(_W.Parameters,{email:n,walletAddress:r})]},{retryCount:0});return zV(_W.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function qW(e,t){let{context:n,signatures:r}=t;try{await e.request({method:`wallet_upgradeAccount`,params:[RV(PW.Parameters,{context:n,signatures:r})]},{retryCount:0})}catch(e){throw $W(e),ZW(e),e}}async function JW(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;try{let t=await e.request({method:`account_verifyEmail`,params:[RV(yW.Parameters,{chainId:n,email:r,signature:i,token:a,walletAddress:o})]},{retryCount:0});return zV(yW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function YW(e,t){let{signature:n}=t,{signature:r,capabilities:{feeSignature:i,...a},...o}=t.response,s=QW({capabilities:a,...o}),c=rR({payload:ML(eL(JSON.stringify(s))),signature:fR(n)}),{quoteSigner:l}=await HW(e);return c===l}async function XW(e,t){let{address:n,chain:r=e.chain,digest:i,signature:a}=t;try{async function t(){return{proof:null,valid:await vv(e,{address:n,hash:i,signature:a})}}let o=await(async()=>{let o=await e.request({method:`wallet_verifySignature`,params:[RV(FW.Parameters,{address:n,chainId:r?.id,digest:i,signature:a})]},{retryCount:0}).catch(t);return o.valid?o:t()})();return zV(FW.Response,o)}catch(e){throw $W(e),e}}function ZW(e,{calls:t}={}){if(!(e instanceof E))return;let n=e=>{try{if(e.name===`ContractFunctionExecutionError`){let t=e.cause.name===`ContractFunctionRevertedError`?e.cause.data:void 0;if(t)return PU([t.abiItem],t.errorName)}let t=e.walk(e=>!(e instanceof Error)&&e.code===3);if(!t)return;let{data:n,message:r}=t;return n===`0xd0d5039b`?NU(`error Unauthorized()`):{inputs:[],name:(r??n).split(`(`)[0],type:`error`}}catch{return}},r=BU(e,{calls:t??[]}),i=n(r);if(!(r===e&&!i))throw new eG(Object.assign(r,{abiError:i}))}function QW(e){if(typeof e==`object`&&e){if(Array.isArray(e))return e.map(QW);let t={};for(let n of Object.keys(e).sort())t[n]=QW(e[n]);return t}return e}function $W(e){if(e.name===`$ZodError`)throw WU(e)}var eG=class extends z{constructor(e){super(`An error occurred while executing calls.`,{cause:e,metaMessages:[e.abiError&&`Reason: `+e.abiError.name].filter(Boolean)}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Rpc.ExecutionError`}),Object.defineProperty(this,`abiError`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abiError=e.abiError}},tG={anvil:{http:`http://localhost:9119`},prod:{http:`https://rpc.porto.sh`},stg:{http:`https://stg-rpc.porto.sh`}};function nG(e){return t=>{let n=e.public(t),r=e.relay(t);return qv({key:nG.type,name:`Relay Proxy`,async request({method:e,params:t},i){return rG(e)?r.request({method:e,params:t},i):n.request({method:e,params:t},i)},type:nG.type})}}(function(e){e.type=`relayProxy`})(nG||={});function rG(e){return!!(e.startsWith(`wallet_`)||e.startsWith(`account_`)||e===`health`)}var iG=new Map;function aG(e,t={}){let{config:n,id:r,store:i}=e._internal,{chains:a,relay:o}=n,s=i.getState(),c=t.chainId??s.chainIds[0],l=a.find(e=>e.id===c);if(!l)throw Error([`Could not find a compatible Porto chain on the given chain configuration.`,``,`Provided chains: [${a.map(e=>`${e.name} (id: ${e.id})`).join(`, `)}]`,`Needed chain (id): ${c}`,`Please add this chain (id) to your chain configuration.`].join(` +`));let u=nG({public:n.transports[l.id]??Yv(l.rpcUrls.default.http.map(e=>$v(e))),relay:o}),d=[r,ZF(l)].filter(Boolean).join(`:`);if(iG.has(d))return iG.get(d);let f=rh({...t,chain:l,pollingInterval:1e3,transport:u});return iG.set(d,f),f}var oG=W({chainId:J(Q()),expiry:Q(),hash:Z(),id:Z(),prehash:J(QH()),publicKey:Z(),role:G([q(`admin`),q(`session`)]),type:G([q(`address`),q(`p256`),q(`secp256k1`),q(`webauthn-p256`)])}),sG=Y(U(HU([W({signature:V(),to:X()}),W({signature:V()}),W({to:X()})])).check(KH(1))),cG=W({limit:G([MU([H(),`.`,H()]),MU([H()])]).check(qH(/^\d+(\.\d+)?$/)),symbol:J(G([q(`native`),uW]))}),lG=W({addresses:Y(U(X()))}),uG=Y(U(W({limit:VU(),period:G([q(`minute`),q(`hour`),q(`day`),q(`week`),q(`month`),q(`year`)]),token:J(X())}))),dG=W({calls:J(sG),signatureVerification:J(lG),spend:J(uG)}),fG=W({...oG.shape,feeToken:J(TU(cG)),permissions:J(dG)}),pG=W({address:X(),chainId:J(Q()),expiry:H(),id:Z(),key:pU(oG,{publicKey:!0,type:!0}),permissions:W({calls:sG,signatureVerification:J(lG),spend:J(uG)})}),mG=W({address:J(X()),chainId:J(Q()),expiry:H().check(GH(1)),feeToken:TU(cG),key:J(pU(oG,{publicKey:!0,type:!0})),permissions:W({calls:sG,signatureVerification:J(lG),spend:J(uG)})}),hG=pG;function gG(e,t){let{chainId:n,expiry:r,permissions:i,id:a,publicKey:o,type:s}=e,{address:c}=t;return{address:c,chainId:n,expiry:r,id:a,key:{publicKey:o,type:s},permissions:i??{}}}function _G(e){let{chainId:t,expiry:n,key:r}=e;return dB({chainId:t,expiry:n,permissions:e.permissions??{},publicKey:r.publicKey,role:`session`,type:r.type})}var vG;(function(e){e.GetCapabilitiesResponse=W({status:G([q(`supported`),q(`unsupported`)])})})(vG||={});var yG;(function(e){e.Request=G([QH(),W({chainId:J(Q()),label:J(V())})])})(yG||={});var bG;(function(e){e.Request=HU([W({chainId:J(H()),domain:J(V()),expirationTime:J(uU()),issuedAt:J(uU()),nonce:V(),notBefore:J(uU()),requestId:J(V()),resources:J(Y(U(V()))),scheme:J(V()),statement:J(V()),uri:J(V()),version:J(q(`1`))}),W({authUrl:G([V(),W({logout:V(),nonce:V(),verify:V()})]),chainId:J(Q()),domain:J(V()),expirationTime:J(uU()),issuedAt:J(uU()),notBefore:J(uU()),requestId:J(V()),resources:J(Y(U(V()))),scheme:J(V()),statement:J(V()),uri:J(V()),version:J(q(`1`))})]),e.Response=W({message:V(),signature:Z(),token:J(V())})})(bG||={});var xG;(function(e){e.GetCapabilitiesResponse=W({supported:QH(),tokens:Y(U(lW))}),e.Request=G([uW,X()])})(xG||={});var SG;(function(e){e.Request=mG})(SG||={});var CG;(function(e){e.GetCapabilitiesResponse=W({supported:QH()})})(CG||={});var wG;(function(e){e.GetCapabilitiesResponse=W({supported:QH()}),e.Request=W({id:J(G([Z(),iU()]))}),e.Response=Y(U(pG))})(wG||={});var TG;(function(e){e.Request=Y(U(W({context:cU(),signature:Z()}))),e.Response=e.Request})(TG||={});var EG;(function(e){e.Request=V()})(EG||={});var DG;(function(e){e.GetCapabilitiesResponse=W({supported:QH(),tokens:Y(U(lW))}),e.Request=Y(U(HU([W({address:X(),value:VU()}),W({symbol:uW,value:G([MU([H(),`.`,H()]),MU([H()])]).check(qH(/^\d+(\.\d+)?$/))})])))})(DG||={});var OG=W({...pU(oG,{id:!0,publicKey:!0,type:!0}).shape,credentialId:J(V()),privateKey:J(oU())}),kG;(function(e){e.Parameters=W({address:X(),secret:V()}),e.Request=W({method:q(`account_getOnrampContactInfo`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(V()),phone:J(V()),phoneVerifiedAt:J(H())})})(kG||={});var AG;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`account_onrampStatus`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(H()),phone:J(H())})})(AG||={});var jG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_resendVerifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(jG||={});var MG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_setEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(MG||={});var NG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_setPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(NG||={});var PG;(function(e){e.Parameters=W({chainId:Q(),email:V(),token:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(PG||={});var FG;(function(e){e.Parameters=W({code:V(),phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(FG||={});var IG;(function(e){e.Parameters=W({address:J(X()),chainId:J(Q()),token:J(X()),value:J(V())}),e.Request=W({method:q(`wallet_addFunds`),params:Y(K([e.Parameters]))}),e.Response=W({id:Z()})})(IG||={});var LG;(function(e){e.Request=W({method:q(`eth_accounts`),params:J(cU())}),e.Response=Y(U(X()))})(LG||={});var RG;(function(e){e.Request=W({method:q(`eth_chainId`),params:J(cU())}),e.Response=Z()})(RG||={});var zG;(function(e){e.Request=W({method:q(`eth_requestAccounts`),params:J(cU())}),e.Response=Y(U(X()))})(zG||={});var BG;(function(e){e.Request=W({method:q(`eth_sendTransaction`),params:Y(K([W({capabilities:J(W({feeToken:J(xG.Request),merchantUrl:J(EG.Request),preCalls:J(TG.Request)})),chainId:J(Q()),data:J(Z()),from:J(X()),to:X(),value:J(VU())})]))}),e.Response=Z()})(BG||={});var VG;(function(e){e.Request=W({method:q(`eth_signTypedData_v4`),params:Y(K([X(),V()]))}),e.Response=Z()})(VG||={});var HG;(function(e){e.Parameters=W({address:J(X()),chainId:J(Q())}),e.Request=W({method:q(`wallet_getAdmins`),params:J(Y(K([e.Parameters])))}),e.Key=OG,e.Response=W({address:X(),chainId:Q(),keys:Y(U(e.Key))})})(HG||={});var UG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),chainId:J(Q()),key:pU(oG,{publicKey:!0,type:!0})}),e.Request=W({method:q(`wallet_grantAdmin`),params:Y(K([e.Parameters]))}),e.Response=W({address:X(),chainId:Q(),key:HG.Key})})(UG||={});var WG;(function(e){e.Parameters=mG,e.Request=W({method:q(`wallet_grantPermissions`),params:Y(K([e.Parameters]))}),e.ResponseCapabilities=W({preCalls:J(TG.Response)}),e.Response=W({...pG.shape,capabilities:J(oU())})})(WG||={});var GG;(function(e){e.Parameters=W({address:J(X())}),e.Request=W({method:q(`wallet_getAccountVersion`),params:J(Y(K([e.Parameters])))}),e.Response=W({current:V(),latest:V()})})(GG||={});var KG;(function(e){e.Parameters=W({address:J(X()),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getPermissions`),params:J(Y(K([e.Parameters])))}),e.Response=wG.Response})(KG||={});var qG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),chainId:J(Q()),id:Z()}),e.Request=W({method:q(`wallet_revokeAdmin`),params:Y(K([e.Parameters]))}),e.Response=void 0})(qG||={});var JG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),id:Z()}),e.Request=W({method:q(`wallet_revokePermissions`),params:Y(K([e.Parameters]))}),e.Response=void 0})(JG||={});var YG;(function(e){e.Request=W({method:q(`wallet_switchEthereumChain`),params:Y(K([W({chainId:Z()})]))})})(YG||={});var XG;(function(e){e.Parameters=W({context:cU(),signatures:W({auth:Z(),exec:Z()})}),e.Request=W({method:q(`wallet_upgradeAccount`),params:Y(K([e.Parameters]))}),e.ResponseCapabilities=W({admins:J(Y(U(HG.Key))),permissions:J(wG.Response)}),e.Response=W({address:X(),capabilities:J(e.ResponseCapabilities)})})(XG||={});var ZG;(function(e){e.Request=W({method:q(`personal_sign`),params:Y(K([Z(),X()]))}),e.Response=Z()})(ZG||={});var QG;(function(e){e.Request=W({method:q(`porto_ping`),params:J(nU())}),e.Response=q(`pong`)})(QG||={});var $G;(function(e){e.Capabilities=W({createAccount:J(yG.Request),email:J(QH()),grantAdmins:J(Y(U(pU(oG,{publicKey:!0,type:!0})))),grantPermissions:J(SG.Request),preCalls:J(TG.Request),selectAccount:J(G([QH(),W({address:X(),key:J(W({credentialId:J(V()),publicKey:Z()}))})])),signInWithEthereum:J(bG.Request)}),e.Parameters=W({capabilities:J(e.Capabilities),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_connect`),params:J(Y(K([e.Parameters])))}),e.ResponseCapabilities=W({admins:J(Y(U(W({...pU(oG,{id:!0,publicKey:!0,type:!0}).shape,credentialId:J(V())})))),permissions:J(wG.Response),preCalls:J(TG.Response),signInWithEthereum:J(bG.Response)}),e.Response=W({accounts:Y(U(W({address:X(),capabilities:J(e.ResponseCapabilities)}))),chainIds:Y(U(Q()))})})($G||={});var eK;(function(e){e.Request=W({method:q(`wallet_disconnect`),params:J(cU())}),e.Response=void 0})(eK||={});var tK;(function(e){e.Parameters=EW.Parameters,e.Request=EW.Request,e.Response=EW.Response})(tK||={});var nK;(function(e){e.Request=W({method:q(`wallet_getCallsStatus`),params:K([Z()])}),e.Response=W({atomic:QH(),chainId:Q(),id:V(),receipts:J(Y(U(W({blockHash:Z(),blockNumber:Z(),gasUsed:Z(),logs:Y(U(W({address:X(),data:Z(),topics:Y(U(Z()))}))),status:Z(),transactionHash:Z()})))),status:H(),version:V()})})(nK||={});var rK;(function(e){e.Parameters=OW.Parameters,e.Request=OW.Request,e.Transaction=OW.Transaction,e.Capabilities=OW.Capabilities,e.Entry=OW.Entry,e.Response=OW.Response})(rK||={});var iK;(function(e){e.Request=W({method:q(`wallet_getCapabilities`),params:J(G([Y(K([G([Z(),nU()])])),Y(K([G([Z(),nU()]),Y(U(Q()))]))]))}),e.Response=xU(Z(),W({atomic:vG.GetCapabilitiesResponse,feeToken:xG.GetCapabilitiesResponse,merchant:CG.GetCapabilitiesResponse,permissions:wG.GetCapabilitiesResponse,requiredFunds:DG.GetCapabilitiesResponse}))})(iK||={});var aK;(function(e){e.Parameters=W({address:X(),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getKeys`),params:Y(K([e.Parameters]))}),e.Response=Y(U(fG))})(aK||={});var oK;(function(e){e.Capabilities=W({feeToken:J(xG.Request),merchantUrl:J(EG.Request),permissions:J(wG.Request),preCalls:J(TG.Request),requiredFunds:J(DG.Request)}),e.Parameters=W({calls:Y(U(W({data:J(Z()),to:X(),value:J(VU())}))),capabilities:J(e.Capabilities),chainId:J(Q()),from:J(X()),key:J(pU(oG,{prehash:!0,publicKey:!0,type:!0})),version:J(V())}),e.Request=W({method:q(`wallet_prepareCalls`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:J(W({...AW.ResponseCapabilities.shape,quote:J(cW)})),chainId:Z(),context:W({account:W({address:X()}),calls:e.Parameters.shape.calls,nonce:VU(),quote:J(hU(cW))}),digest:Z(),key:pU(oG,{prehash:!0,publicKey:!0,type:!0}),typedData:W({domain:G([W({chainId:Q(),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(oK||={});var sK;(function(e){e.Capabilities=W({...$G.Capabilities.shape,label:J(V())}),e.Parameters=W({address:X(),capabilities:J(e.Capabilities),chainId:J(Q())}),e.Request=W({method:q(`wallet_prepareUpgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=W({context:cU(),digests:W({auth:Z(),exec:Z()})})})(sK||={});var cK;(function(e){e.Capabilities=oK.Capabilities,e.Request=W({method:q(`wallet_sendCalls`),params:Y(K([mU(oK.Parameters,{key:!0})]))}),e.Response=W({id:Z()})})(cK||={});var lK;(function(e){e.Parameters=W({capabilities:oK.Response.shape.capabilities,chainId:Z(),context:oK.Response.shape.context,key:oK.Response.shape.key,signature:Z()}),e.Request=W({method:q(`wallet_sendPreparedCalls`),params:Y(K([e.Parameters]))}),e.Response=Y(U(W({capabilities:J(xU(V(),cU())),id:Z()})))})(lK||={});var uK;(function(e){e.Parameters=W({address:X(),chainId:J(Q()),digest:Z(),signature:Z()}),e.Request=W({method:q(`wallet_verifySignature`),params:Y(K([e.Parameters]))}),e.Response=W({address:X(),chainId:Q(),proof:J(cU()),valid:QH()})})(uK||={});var dK=vU(`method`,[PG.Request,IG.Request,LG.Request,RG.Request,zG.Request,BG.Request,VG.Request,GG.Request,HG.Request,KG.Request,UG.Request,WG.Request,sK.Request,qG.Request,JG.Request,XG.Request,ZG.Request,QG.Request,$G.Request,eK.Request,tK.Request,nK.Request,rK.Request,iK.Request,aK.Request,oK.Request,cK.Request,lK.Request,YG.Request,uK.Request]);function fK(e,t){let n=IV(e,t);if(n.error){let e=n.error.issues.at(0);throw e?.code===`invalid_union`&&e.note===`No matching discriminator`?new lI:new mI(WU(n.error))}return{...t,_decoded:n.data}}async function pK(e){e.persist.hasHydrated()||await new Promise(t=>{e.persist.onFinishHydration(()=>t(!0)),setTimeout(()=>t(!0),100)})}function mK(e){if(e)return e.startsWith(`/`)?`${window.location.origin}${e}`:e}function hK(e){let{config:t,getMode:n,id:r,store:i}=e,{announceProvider:a}=t;function o(e={}){let a=s(),o=e.request??fK(dK,{method:`wallet_getCapabilities`,params:e.chainIds?[void 0,e.chainIds]:void 0});return Mm(()=>n().actions.getCapabilities({chainIds:e.chainIds,internal:{client:a,config:t,request:o,store:i}}),{cacheKey:`getCapabilities.${r}.${e.chainIds?.join(`,`)}`})}function s(t){let n=typeof t==`string`?sL(t):t;return aG({_internal:e},{chainId:n})}let c=new Map,l=[],u=jI(),d=MI({...u,async request(e){return await pK(i),BB(async()=>{let r;try{r=fK(dK,e)}catch(t){let n=t;if(!(n instanceof lI))throw n;if(e.method.startsWith(`wallet_`))throw new bI;return s().request(e)}let a=i.getState();switch(r.method){case`account_verifyEmail`:{if(a.accounts.length===0)throw new xI;let[e]=r._decoded.params,{chainId:o,email:c,token:l,walletAddress:u}=e,d=s(o);if(o&&o!==d.chain.id)throw new SI;let f=u?a.accounts.find(e=>YL(e.address,u)):a.accounts[0];if(!f)throw new yI;return await n().actions.verifyEmail({account:f,chainId:o,email:c,internal:{client:d,config:t,request:r,store:i},token:l,walletAddress:u})}case`wallet_addFunds`:{let{address:e,value:o,token:c}=r.params[0]??{},l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0],d=s(),f=await n().actions.addFunds({address:l?.address,internal:{client:d,config:t,request:r,store:i},token:c,value:o});return u.emit(`message`,{data:null,type:`assetsChanged`}),f}case`eth_accounts`:if(a.accounts.length===0)throw new xI;return a.accounts.map(yK);case`eth_chainId`:return $I(a.chainIds[0]);case`eth_requestAccounts`:{if(a.accounts.length>0&&c.get(`eth_requestAccounts`))return a.accounts.map(yK);let e=s(),{accounts:o}=await n().actions.loadAccounts({internal:{client:e,config:t,request:r,store:i}});return i.setState(e=>({...e,accounts:o})),u.emit(`connect`,{chainId:$I(e.chain.id)}),c.set(`eth_requestAccounts`,!0),setTimeout(()=>c.delete(`eth_requestAccounts`),1e3),o.map(yK)}case`eth_sendTransaction`:{let[{capabilities:e,chainId:o,data:c=`0x`,from:l,to:u,value:d}]=r._decoded.params,f=s(o);if(o&&o!==f.chain.id)throw new SI;let p=l?a.accounts.find(e=>YL(e.address,l)):a.accounts[0];if(l&&!p)throw new yI;let{id:m}=await n().actions.sendCalls({account:p,asTxHash:!0,calls:[{data:c,to:u,value:d}],chainId:f.chain.id,internal:{client:f,config:t,request:r,store:i},merchantUrl:mK(t.merchantUrl??e?.merchantUrl)});return m}case`eth_signTypedData_v4`:{if(a.accounts.length===0)throw new xI;let[e,o]=r._decoded.params,c=a.accounts.find(t=>YL(t.address,e));if(!c)throw new yI;let l=s();return await n().actions.signTypedData({account:c,data:o,internal:{client:l,config:t,request:r,store:i}})}case`wallet_grantAdmin`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,chainId:c,key:l}]=r._decoded.params??[{}],d=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!d)throw new yI;let f=s(c);if(_K([...d.keys??[]])?.some(e=>e.publicKey?.toLowerCase()===l.publicKey.toLowerCase()))throw new mI({message:`Key already granted as admin.`});let{key:p}=await n().actions.grantAdmin({account:d,feeToken:o?.feeToken,internal:{client:f,config:t,request:r,store:i},key:l});i.setState(e=>{let t=e.accounts.findIndex(e=>d?YL(e.address,d.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],p]}:e)}});let m=_K([...d.keys??[],p]);return u.emit(`message`,{data:null,type:`adminsChanged`}),RV(UG.Response,{address:d.address,chainId:f.chain.id,key:m.at(-1)})}case`wallet_grantPermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainId:o,...c}]=r._decoded.params??[{}],l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s(o),{key:f}=await n().actions.grantPermissions({account:l,internal:{client:d,config:t,request:r,store:i},permissions:c});return i.setState(e=>{let t=e.accounts.findIndex(e=>l?YL(e.address,l.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],f]}:e)}}),u.emit(`message`,{data:null,type:`permissionsChanged`}),RV(WG.Response,{...gG(f,{address:l.address})})}case`wallet_getAdmins`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainId:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!c)throw new yI;let l=s(o),u=_K(await n().actions.getKeys({account:c,internal:{client:l,config:t,request:r,store:i}}));return RV(HG.Response,{address:c.address,chainId:l.chain.id,keys:u})}case`wallet_prepareUpgradeAccount`:{let[{address:e,capabilities:a,chainId:o}]=r._decoded.params??[{}],{email:c,label:u,grantPermissions:d}=a??{},f=s(o),{context:p,digests:m}=await n().actions.prepareUpgradeAccount({address:e,email:c,internal:{client:f,config:t,request:r,store:i},label:u,permissions:d});return l.push(p.account),{context:p,digests:m}}case`wallet_getAccountVersion`:{if(a.accounts.length===0)throw new xI;let[{address:e}]=r._decoded.params??[{}],o=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!o)throw new yI;let c=s(),{current:l,latest:u}=await n().actions.getAccountVersion({address:o.address,internal:{client:c,config:t,request:r,store:i}});return{current:l,latest:u}}case`wallet_getKeys`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=a.accounts.find(t=>YL(t.address,e));if(!c)throw new yI;let l=s(),u=await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}});return RV(aK.Response,u)}case`wallet_getPermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!c)throw new yI;let l=s();return vK(await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}}),{address:c.address})}case`wallet_revokeAdmin`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s();await n().actions.revokeAdmin({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>YL(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`adminsChanged`});return}case`wallet_revokePermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s();await n().actions.revokePermissions({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>YL(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`permissionsChanged`});return}case`wallet_upgradeAccount`:{let[{context:e,signatures:a}]=r._decoded.params??[{}],o=s(),c=l.find(t=>YL(t.address,e.account.address));if(!c)throw new yI;let{account:d}=await n().actions.upgradeAccount({account:c,context:e,internal:{client:o,config:t,request:r,store:i},signatures:a}),f=_K(d.keys??[]),p=vK(d.keys??[],{address:d.address});return i.setState(e=>({...e,accounts:[d]})),u.emit(`connect`,{chainId:$I(o.chain.id)}),{address:d.address,capabilities:{admins:f,...p.length>0?{permissions:p}:{}}}}case`porto_ping`:return`pong`;case`personal_sign`:{if(a.accounts.length===0)throw new xI;let[e,o]=r._decoded.params,c=a.accounts.find(e=>YL(e.address,o));if(!c)throw new yI;let l=s();return await n().actions.signPersonalMessage({account:c,data:e,internal:{client:l,config:t,request:r,store:i}})}case`wallet_connect`:{let[{capabilities:e,chainIds:o}]=r._decoded.params??[{}],c=s(o?.[0]),l=c.chain.id,{createAccount:d,email:f,grantAdmins:p,grantPermissions:m,selectAccount:h,signInWithEthereum:g}=e??{},_={client:c,config:t,request:r,store:i},{accounts:v}=await(async()=>{if(f||d){let{label:e=void 0}=typeof d==`object`?d:{},{account:t}=await n().actions.createAccount({admins:p,email:f,internal:_,label:e,permissions:m,signInWithEthereum:g});return{accounts:[t]}}let e=a.accounts[0],{address:t,key:r}=(()=>{if(h)return typeof h==`object`?h:{address:void 0,key:void 0};for(let t of e?.keys??[])if(t.type===`webauthn-p256`&&t.role===`admin`)return{address:e?.address,key:{credentialId:t.credentialId??t.privateKey?.credential?.id,publicKey:t.publicKey}};return{address:void 0,key:void 0}})(),i={internal:_,permissions:m,signInWithEthereum:g};try{return await n().actions.loadAccounts({address:t,key:r,...i})}catch(e){if(e instanceof vI)throw e;if(t&&r)return await n().actions.loadAccounts(i);throw e}})();i.setState(e=>({...e,accounts:v}));let y=[l,...i.getState().chainIds.filter(e=>e!==l)];return u.emit(`connect`,{chainId:$I(y[0])}),{accounts:v.map(e=>({address:yK(e),capabilities:{admins:e.keys?_K(e.keys):[],permissions:e.keys?vK(e.keys,{address:e.address}):[],...e.signInWithEthereum&&{signInWithEthereum:e.signInWithEthereum}}})),chainIds:y.map(e=>$I(e))}}case`wallet_disconnect`:{let e=s();await n().actions.disconnect?.({internal:{client:e,config:t,request:r,store:i}}),i.setState(e=>({...e,accounts:[]})),u.emit(`disconnect`,new xI);return}case`wallet_getAssets`:{let[e]=r._decoded.params??[],{account:a,chainFilter:o,assetFilter:c,assetTypeFilter:l}=e,u=s(),d=await n().actions.getAssets({account:a,assetFilter:c,assetTypeFilter:l,chainFilter:o,internal:{client:u,config:t,request:r,store:i}}),f=Object.entries(d).reduce((e,[t,n])=>(e[$I(Number(t))]=n,e),{});return RV(tK.Response,f)}case`wallet_getCallsStatus`:{let[e]=r._decoded.params??[],a=s();return await n().actions.getCallsStatus({id:e,internal:{client:a,config:t,request:r,store:i}})}case`wallet_getCallsHistory`:{let[e]=r._decoded.params??[],a=s(),o=await n().actions.getCallsHistory({...e,internal:{client:a,config:t,request:r,store:i}});return RV(rK.Response,o)}case`wallet_getCapabilities`:{let[e,t]=r.params??[];return await o({chainIds:t,request:r})}case`wallet_prepareCalls`:{let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,key:u,from:d}=e,f=s(l),p=d??a.accounts[0];if(!p)throw new yI;if(l&&l!==f.chain.id)throw new SI;let{digest:m,...h}=await n().actions.prepareCalls({account:TB(p),calls:o,feeToken:c?.feeToken,internal:{client:f,config:t,request:r,store:i},key:u,merchantUrl:mK(t.merchantUrl??c?.merchantUrl),requiredFunds:c?.requiredFunds});return RV(oK.Response,{capabilities:h.capabilities,chainId:$I(h.chainId??f.chain.id),context:{...h.context,account:{address:h.account.address},calls:h.context.calls??[],nonce:h.context.nonce??0n},digest:m,key:h.key,typedData:h.typedData})}case`wallet_sendPreparedCalls`:{let[e]=r._decoded.params,{chainId:a,context:o,key:c,signature:l}=e,{account:u}=e.context,d=s(a);if(a&&sL(a)!==d.chain.id)throw new SI;return[{id:await n().actions.sendPreparedCalls({account:TB(u),context:o,internal:{client:d,config:t,request:r,store:i},key:c,signature:l})}]}case`wallet_sendCalls`:{let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,from:u}=e,d=s(l);if(l&&l!==d.chain.id)throw new SI;let f=u?a.accounts.find(e=>YL(e.address,u)):a.accounts[0];if(u&&!f)throw new yI;let{id:p}=await n().actions.sendCalls({account:f,calls:o,chainId:d.chain.id,feeToken:c?.feeToken,internal:{client:d,config:t,request:r,store:i},merchantUrl:mK(t.merchantUrl??c?.merchantUrl),permissionsId:c?.permissions?.id,requiredFunds:c?.requiredFunds});return{id:p}}case`wallet_switchEthereumChain`:{let[e]=r._decoded.params,{chainId:a}=e,o=sL(a);if(!t.chains.find(e=>e.id===o))throw new TI;let c=s(a);await n().actions.switchChain?.({chainId:c.chain.id,internal:{client:c,config:t,request:r,store:i}}),i.setState(e=>({...e,chainIds:[o,...e.chainIds.filter(e=>e!==o)]}));return}case`wallet_verifySignature`:{let[e]=r._decoded.params,{address:t,chainId:n,digest:i,signature:a}=e,o=s(n);return{...await XW(o,{address:t,digest:i,signature:a}),address:t,chainId:$I(o.chain.id)}}}},{enabled:[`eth_accounts`,`eth_chainId`,`eth_requestAccounts`,`wallet_getAssets`,`wallet_getCapabilities`,`wallet_getKeys`,`wallet_getPermissions`,`wallet_getAccountVersion`,`wallet_connect`].includes(e.method),id:ZF(e)})}});function f(){let e=()=>{},t=()=>{};pK(i).then(()=>{o().catch(()=>{}),e(),e=i.subscribe(e=>e.accounts,e=>{u.emit(`accountsChanged`,e.map(yK))},{equalityFn:(e,t)=>e.every((e,n)=>e.address===t[n]?.address)}),t(),t=i.subscribe(e=>e.chainIds[0],(e,t)=>{e!==t&&u.emit(`chainChanged`,$I(e))})});let n=gK(d,a);return()=>{e(),t(),n()}}let p=f();return Object.assign(d,{_internal:{destroy:p}})}function gK(e,t){if(!t||typeof window>`u`||!window.dispatchEvent)return()=>{};let{icon:n=`data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDIyIiBoZWlnaHQ9IjQyMiIgdmlld0JveD0iMCAwIDQyMiA0MjIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MjIiIGhlaWdodD0iNDIyIiBmaWxsPSJibGFjayIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDBfMV8xNSkiPgo8cGF0aCBkPSJNODEgMjg2LjM2NkM4MSAyODAuODkzIDg1LjQ1MDUgMjc2LjQ1NSA5MC45NDA0IDI3Ni40NTVIMzI5LjUxMUMzMzUuMDAxIDI3Ni40NTUgMzM5LjQ1MiAyODAuODkzIDMzOS40NTIgMjg2LjM2NlYzMDYuMTg4QzMzOS40NTIgMzExLjY2MiAzMzUuMDAxIDMxNi4wOTkgMzI5LjUxMSAzMTYuMDk5SDkwLjk0MDRDODUuNDUwNSAzMTYuMDk5IDgxIDMxMS42NjIgODEgMzA2LjE4OFYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAyMzQuODI4Qzg1LjQ1MDUgMjM0LjgyOCA4MSAyMzkuMjY2IDgxIDI0NC43MzlWMjcxLjUzMUM4My44NDMyIDI2OS42MzMgODcuMjYyMiAyNjguNTI2IDkwLjk0MDQgMjY4LjUyNkgzMjkuNTExQzMzMy4xODggMjY4LjUyNiAzMzYuNjA4IDI2OS42MzMgMzM5LjQ1MiAyNzEuNTMxVjI0NC43MzlDMzM5LjQ1MiAyMzkuMjY2IDMzNS4wMDEgMjM0LjgyOCAzMjkuNTExIDIzNC44MjhIOTAuOTQwNFpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgwLjg5MyAzMzUuMDAxIDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTlDODEgMzExLjY2NCA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2NCAzMzkuNDUyIDMwNi4xOVYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAxOTMuMjAxQzg1LjQ1MDUgMTkzLjIwMSA4MSAxOTcuNjM4IDgxIDIwMy4xMTJWMjI5LjkwM0M4My44NDMyIDIyOC4wMDYgODcuMjYyMiAyMjYuODk5IDkwLjk0MDQgMjI2Ljg5OUgzMjkuNTExQzMzMy4xODggMjI2Ljg5OSAzMzYuNjA4IDIyOC4wMDYgMzM5LjQ1MiAyMjkuOTAzVjIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNFpNMzM5LjQ1MiAyNDQuNzM5QzMzOS40NTIgMjM5LjI2NSAzMzUuMDAxIDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNDODEuMjE3NSAyNzEuMzg1IDgxLjQzODMgMjcxLjI0NSA4MS42NjI0IDI3MS4xMDlDODMuODMyNSAyNjkuNzk0IDg2LjMwNTQgMjY4LjkyNyA4OC45NTIzIDI2OC42MzVDODkuNjA1MSAyNjguNTYzIDkwLjI2ODQgMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMzAuMTgzIDI2OC41MjYgMzMwLjg0NiAyNjguNTYzIDMzMS40OTggMjY4LjYzNUMzMzQuNDE5IDI2OC45NTcgMzM3LjEyOCAyNjkuOTggMzM5LjQ1MiAyNzEuNTNWMjQ0LjczOVpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgxLjAyMSAzMzUuMjA2IDI3Ni42NjMgMzI5Ljg5MyAyNzYuNDYyQzMyOS43NjcgMjc2LjQ1NyAzMjkuNjQgMjc2LjQ1NSAzMjkuNTExIDI3Ni40NTVIOTAuOTQwNEM4NS40NTA1IDI3Ni40NTUgODEgMjgwLjg5MyA4MSAyODYuMzY2VjMwNi4xODhDODEgMzExLjY2MiA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2MiAzMzkuNDUyIDMwNi4xODhWMjg2LjM2NloiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNSIvPgo8cGF0aCBvcGFjaXR5PSIwLjMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTguMDE0NiAxMDRDODguNjE3NyAxMDQgODEgMTExLjU5NSA4MSAxMjAuOTY1VjE4OC4yNzZDODMuODQzMiAxODYuMzc5IDg3LjI2MjIgMTg1LjI3MiA5MC45NDA0IDE4NS4yNzJIMzI5LjUxMUMzMzMuMTg4IDE4NS4yNzIgMzM2LjYwOCAxODYuMzc5IDMzOS40NTIgMTg4LjI3NlYxMjAuOTY1QzMzOS40NTIgMTExLjU5NSAzMzEuODMzIDEwNCAzMjIuNDM3IDEwNEg5OC4wMTQ2Wk0zMzkuNDUyIDIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNEM4NS40NTA1IDE5My4yMDEgODEgMTk3LjYzOCA4MSAyMDMuMTEyVjIyOS45MDNDODEuMjE3NSAyMjkuNzU4IDgxLjQzODMgMjI5LjYxOCA4MS42NjI0IDIyOS40ODJDODMuODMyNSAyMjguMTY3IDg2LjMwNTQgMjI3LjMgODguOTUyMyAyMjcuMDA4Qzg5LjYwNTEgMjI2LjkzNiA5MC4yNjg0IDIyNi44OTkgOTAuOTQwNCAyMjYuODk5SDMyOS41MTFDMzMwLjE4MyAyMjYuODk5IDMzMC44NDYgMjI2LjkzNiAzMzEuNDk4IDIyNy4wMDhDMzM0LjQxOSAyMjcuMzMgMzM3LjEyOCAyMjguMzUyIDMzOS40NTIgMjI5LjkwM1YyMDMuMTEyWk0zMzkuNDUyIDI0NC43MzlDMzM5LjQ1MiAyMzkuMzkzIDMzNS4yMDYgMjM1LjAzNiAzMjkuODkzIDIzNC44MzVDMzI5Ljc2NyAyMzQuODMgMzI5LjY0IDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNMODEuMDcwNyAyNzEuNDgzQzgxLjI2NTMgMjcxLjM1NSA4MS40NjI1IDI3MS4yMyA4MS42NjI0IDI3MS4xMDlDODEuOTA4MyAyNzAuOTYgODIuMTU4MSAyNzAuODE3IDgyLjQxMTcgMjcwLjY3OUM4NC4zOTUzIDI2OS42MDUgODYuNjA1NCAyNjguODk0IDg4Ljk1MjMgMjY4LjYzNUM4OS4wMDUyIDI2OC42MjkgODkuMDU4IDI2OC42MjQgODkuMTExIDI2OC42MThDODkuNzEyNSAyNjguNTU3IDkwLjMyMjggMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMjkuNzM4IDI2OC41MjYgMzI5Ljk2NSAyNjguNTMgMzMwLjE5MiAyNjguNTM5QzMzMC42MzEgMjY4LjU1NSAzMzEuMDY3IDI2OC41ODcgMzMxLjQ5OCAyNjguNjM1QzMzNC40MTkgMjY4Ljk1NyAzMzcuMTI4IDI2OS45OCAzMzkuNDUyIDI3MS41M1YyNDQuNzM5Wk0zMzkuNDUyIDI4Ni4zNjZDMzM5LjQ1MiAyODEuMDIxIDMzNS4yMDYgMjc2LjY2MyAzMjkuODkzIDI3Ni40NjJMMzI5Ljg2NSAyNzYuNDYxQzMyOS43NDggMjc2LjQ1NyAzMjkuNjI5IDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTg4QzgxIDMxMS42NjIgODUuNDUwNSAzMTYuMTAxIDkwLjk0MDQgMzE2LjEwMUgzMjkuNTExQzMzNS4wMDEgMzE2LjEwMSAzMzkuNDUyIDMxMS42NjIgMzM5LjQ1MiAzMDYuMTg4VjI4Ni4zNjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjY5Ljg2OCAxMzEuNzUyQzI2OS44NjggMTI2LjI3OCAyNzQuMzE4IDEyMS44NCAyNzkuODA4IDEyMS44NEgzMTEuNjE4QzMxNy4xMDggMTIxLjg0IDMyMS41NTggMTI2LjI3OCAzMjEuNTU4IDEzMS43NTJWMTYxLjQ4NUMzMjEuNTU4IDE2Ni45NTkgMzE3LjEwOCAxNzEuMzk2IDMxMS42MTggMTcxLjM5NkgyNzkuODA4QzI3NC4zMTggMTcxLjM5NiAyNjkuODY4IDE2Ni45NTkgMjY5Ljg2OCAxNjEuNDg1VjEzMS43NTJaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzFfMTUiPgo8cmVjdCB3aWR0aD0iMjU5IiBoZWlnaHQ9IjIxMyIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDgxIDEwNCkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K`,name:r=`Porto`,rdns:i=`xyz.ithaca.porto`}=typeof t==`object`?t:{};return nV({info:{icon:n,name:r,rdns:i,uuid:zB()},provider:e})}function _K(e){return e.map(e=>{if(e.role===`admin`)try{return RV(HG.Key,{id:e.id??e.publicKey,publicKey:e.publicKey,type:e.type,...e.type===`webauthn-p256`?{credentialId:e.privateKey?.credential?.id,privateKey:{credential:{id:e.privateKey?.credential?.id},rpId:e.privateKey?.rpId}}:{}})}catch{return}}).filter(Boolean)}function vK(e,{address:t}){return e.map(e=>{if(e.chainId&&e.role===`session`&&!(e.expiry>0&&e.expiry()=>{})}}async function xK(e){let{account:t,calls:n,permissionsId:r}=e;if(r!==void 0){if(r===null)return;let e=t.keys?.find(e=>e.publicKey===r&&e.privateKey);if(!e)throw Error(`permission (id: ${r}) does not exist.`);return e}let i=t.keys?.find(e=>!e.privateKey||e.role!==`session`||e.expirye.permissions?.calls?.some(e=>{if(e.to&&e.to!==t.to)return!1;if(e.signature){if(!t.data)return!1;let n=rL(t.data,0,4);if(cL(e.signature))return e.signature===n;if(yz(e.signature)!==n)return!1}return!0}))),a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);return i??a}function SK(e={}){let t=e.id??0;return{prepare(e){return CK({id:t++,...e})},get id(){return t}}}function CK(e){return{...e,jsonrpc:`2.0`}}function wK(){return null}var TK=mG;function EK(e){let{expiry:t,feeToken:n,permissions:r,publicKey:i,type:a}=e;return{expiry:t,feeToken:n??null,key:{publicKey:i,type:a},permissions:r??{}}}async function DK(e,t={}){if(!e)return;let n={chainId:t.chainId??e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,permissions:xB(e,{feeTokens:t.feeTokens}),role:`session`};if(e?.key)return dB({...n,publicKey:e.key.publicKey,type:e.key.type??`secp256k1`});if(typeof globalThis.crypto?.subtle?.generateKey==`function`)try{return await uB(n)}catch(e){if(!OK(e))throw e}return sB(n)}function OK(e){if(!(e instanceof Error))return!1;let t=e.message?.toLowerCase()??``;return e.name===`TypeError`||e.name===`ReferenceError`||t.includes(`subtle`)||t.includes(`generatekey`)}var kK=/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/,AK=/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:[0-9]{1,5})?$/,jK=/^localhost(:[0-9]{1,5})?$/,MK=/^[a-zA-Z0-9]{8,}$/,NK=/^([a-zA-Z][a-zA-Z0-9+-.]*)$/,PK=/^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/,FK=/(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/;function IK(e){let{chainId:t,domain:n,expirationTime:r,issuedAt:i=new Date,nonce:a,notBefore:o,requestId:s,resources:c,scheme:l,uri:u,version:d}=e;{if(t!==Math.floor(t))throw new BK({field:`chainId`,metaMessages:[`- Chain ID must be a EIP-155 chain ID.`,`- See https://eips.ethereum.org/EIPS/eip-155`,``,`Provided value: ${t}`]});if(!(kK.test(n)||AK.test(n)||jK.test(n)))throw new BK({field:`domain`,metaMessages:[`- Domain must be an RFC 3986 authority.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${n}`]});if(!MK.test(a))throw new BK({field:`nonce`,metaMessages:[`- Nonce must be at least 8 characters.`,`- Nonce must be alphanumeric.`,``,`Provided value: ${a}`]});if(!LK(u))throw new BK({field:`uri`,metaMessages:[`- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${u}`]});if(d!==`1`)throw new BK({field:`version`,metaMessages:[`- Version must be '1'.`,``,`Provided value: ${d}`]});if(l&&!NK.test(l))throw new BK({field:`scheme`,metaMessages:[`- Scheme must be an RFC 3986 URI scheme.`,`- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1`,``,`Provided value: ${l}`]});let r=e.statement;if(r?.includes(` +`))throw new BK({field:`statement`,metaMessages:[`- Statement must not include '\\n'.`,``,`Provided value: ${r}`]})}let f=qL(e.address,{checksum:!0}),p=`${l?`${l}://${n}`:n} wants you to sign in with your Ethereum account:\n${f}\n\n${e.statement?`${e.statement}\n`:``}`,m=`URI: ${u}\nVersion: ${d}\nChain ID: ${t}\nNonce: ${a}\nIssued At: ${i.toISOString()}`;if(r&&(m+=`\nExpiration Time: ${r.toISOString()}`),o&&(m+=`\nNot Before: ${o.toISOString()}`),s&&(m+=`\nRequest ID: ${s}`),c){let e=` +Resources:`;for(let t of c){if(!LK(t))throw new BK({field:`resources`,metaMessages:[`- Every resource must be a RFC 3986 URI.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${t}`]});e+=`\n- ${t}`}m+=e}return`${p}\n${m}`}function LK(e){if(/[^a-z0-9:/?#[\]@!$&'()*+,;=.\-_~%]/i.test(e)||/%[^0-9a-f]/i.test(e)||/%[0-9a-f](:?[^0-9a-f]|$)/i.test(e))return!1;let t=RK(e),n=t[1],r=t[2],i=t[3],a=t[4],o=t[5];if(!(n?.length&&i&&i.length>=0))return!1;if(r?.length){if(!(i.length===0||/^\//.test(i)))return!1}else if(/^\/\//.test(i))return!1;if(!/^[a-z][a-z0-9+\-.]*$/.test(n.toLowerCase()))return!1;let s=``;return s+=`${n}:`,r?.length&&(s+=`//${r}`),s+=i,a?.length&&(s+=`?${a}`),o?.length&&(s+=`#${o}`),s}function RK(e){return e.match(/(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/)}function zK(e){let{scheme:t,statement:n,...r}=e.match(PK)?.groups??{},{chainId:i,expirationTime:a,issuedAt:o,notBefore:s,requestId:c,...l}=e.match(FK)?.groups??{},u=e.split(`Resources:`)[1]?.split(` +- `).slice(1);return{...r,...l,...i?{chainId:Number(i)}:{},...a?{expirationTime:new Date(a)}:{},...o?{issuedAt:new Date(o)}:{},...s?{notBefore:new Date(s)}:{},...c?{requestId:c}:{},...u?{resources:u}:{},...t?{scheme:t}:{},...n?{statement:n}:{}}}var BK=class extends z{constructor(e){let{field:t,metaMessages:n}=e;super(`Invalid Sign-In with Ethereum message field "${t}".`,{metaMessages:n}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Siwe.InvalidMessageFieldError`})}};async function VK(e){let{address:t,authUrl:n,message:r,signature:i,publicKey:a}=e,{chainId:o}=zK(r);return await fetch(n.verify,{body:JSON.stringify({address:t,chainId:o,message:r,signature:i,walletAddress:t,...a&&{publicKey:a}}),credentials:`include`,headers:{"Content-Type":`application/json`},method:`POST`}).then(e=>e.json())}async function HK(e,t,n){let{chainId:r=e.chain?.id,domain:i,uri:a,resources:o,version:s=`1`}=t,{address:c}=n,l=t.authUrl?UK(t.authUrl):void 0;if(!r)throw Error("`chainId` is required.");if(!i)throw Error("`domain` is required.");if(!t.nonce&&!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");if(!a)throw Error("`uri` is required.");let u=await(async()=>{if(t.nonce)return t.nonce;if(!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");let e=await(await fetch(l.nonce,{body:JSON.stringify({address:c,chainId:r,walletAddress:c}),headers:{"Content-Type":`application/json`},method:`POST`})).json().catch(()=>void 0);if(!e?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");return e.nonce})();return IK({...t,address:n.address,chainId:r,domain:i,nonce:u,resources:o,uri:a,version:s})}function UK(e,t=``){if(!e)return;let n=(()=>{if(typeof e==`string`){let t=e.replace(/\/$/,``);return{logout:t+`/logout`,nonce:t+`/nonce`,verify:t+`/verify`}}return e})();return{logout:WK(n.logout,t),nonce:WK(n.nonce,t),verify:WK(n.verify,t)}}function WK(e,t){return!t||!e.startsWith(`/`)?e:t+e}function GK(e){let t=XI(e);return YI(`0x19`,eL(`Ethereum Signed Message: +`+iL(t)),t)}function KK(e){return ML(GK(e))}function qK(e,t){let{as:n}=t,r=YK(e),i=zR(new Uint8Array(r.length));return r.encode(i),n===`Hex`?QI(i.bytes):i.bytes}function JK(e,t={}){let{as:n=`Hex`}=t;return qK(e,{as:n})}function YK(e){return Array.isArray(e)?XK(e.map(e=>YK(e))):ZK(e)}function XK(e){let t=e.reduce((e,t)=>e+t.length,0),n=QK(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function ZK(e){let t=typeof e==`string`?yL(e):e,n=QK(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function QK(e){if(e<=255)return 1;if(e<=65535)return 2;if(e<=16777215)return 3;if(e<=4294967295)return 4;throw new z(`Length is too large.`)}function $K(e){return eq(e,{presign:!0})}function eq(e,t={}){let{presign:n}=t;return ML(YI(`0x05`,JK(tq(n?{address:e.address,chainId:e.chainId,nonce:e.nonce}:e))))}function tq(e){let{address:t,chainId:n,nonce:r}=e,i=pR(e);return[n?$I(n):`0x`,t,r?$I(r):`0x`,...i?vR(i):[]]}async function nq(e,t){let{account:n=e.account}=t,r=n?TB(n):void 0;if(!r)throw Error(`account is required.`);let{domain:{name:i,version:a}}=await oh(e,{address:r.address});if(!e.chain)throw Error(`client.chain is required`);return{chainId:e.chain.id,name:i,verifyingContract:r.address,version:a}}async function rq(e,t){let{account:n=e.account,chainIds:r}=t,i=n?TB(n):void 0;if(!i)throw Error(`account is required.`);let a=await VW(e,{address:i.address,chainIds:r});return Object.entries(a).flatMap(([e,t])=>t.map(t=>pB(t,{chainId:Number(e)})))}async function iq(e,t){let{account:n=e.account,calls:r,chain:i=e.chain,feePayer:a,merchantUrl:o,nonce:s,preCalls:c,requiredFunds:l,revokeKeys:u}=t,d=n?TB(n):void 0,f=t.key??(d?DB(d,{role:`admin`}):void 0),p=t.authorizeKeys?.some(e=>e.role===`session`),{contracts:m,fees:{tokens:h}}=await LW(e,{chainId:i?.id}),g=p?m.orchestrator.address:void 0,_=(t.authorizeKeys??[]).map(e=>bB(e,{feeTokens:h,orchestrator:g})),v=t.feeToken?t.feeToken:f?.permissions?.spend?.[0]?.token,y=typeof c==`boolean`?c:!1,b=typeof c==`object`?c.map(({context:e,signature:t})=>({...e.preCall,signature:t})):void 0,x={address:d?.address,calls:r??[],capabilities:{authorizeKeys:_,meta:{feePayer:a,feeToken:v,nonce:s},preCall:y,preCalls:b,requiredFunds:l,revokeKeys:u?.map(e=>({hash:e.hash}))},chain:i,key:f?bB(f,{feeTokens:h}):void 0},S=await(async()=>o?await UW(rh({chain:e.chain,transport:$v(o)}),x).catch(t=>(console.error(t),UW(e,x))):await UW(e,x))(),{capabilities:ee,context:C,digest:te,signature:ne,typedData:re}=S;if(o&&!await YW(e,{response:S._raw,signature:ne}))throw Error(`cannot verify integrity of \`wallet_prepareCalls\` response from ${o}`);return{capabilities:{...ee,quote:C.quote},context:C,digest:te,key:f,typedData:re}}async function aq(e,t){let{address:n,authorizeKeys:r,chain:i=e.chain}=t;if(!i)throw Error(`chain is required.`);let{contracts:a,fees:{tokens:o}}=await LW(e,{chainId:i.id}),s=t.delegation??a.accountProxy.address,c=r.some(e=>e.role===`session`)?a.orchestrator.address:void 0,{capabilities:l,chainId:u,context:d,digests:f,typedData:p}=await WW(e,{address:n,authorizeKeys:r.map(e=>{let t=e.role===`session`?e.permissions:{};return bB({...e,permissions:t},{feeTokens:o,orchestrator:c})}),chain:i,delegation:s}),m=TB({address:n,keys:r});return{capabilities:l,chainId:u,context:{...d,account:m},digests:f,typedData:p}}async function oq(e,t){let{account:n=e.account,chain:r=e.chain,webAuthn:i}=t;if(!r)throw Error("`chain` is required.");let a=n?TB(n):void 0;if(!a)throw Error("`account` is required.");let o=t.key??DB(a,t);if(!o&&!a.sign)throw Error("`key` or `account` with `sign` is required");let s=await Promise.all((t.preCalls??[]).map(async n=>{if(n.signature)return n;let{authorizeKeys:o,key:s,calls:c,revokeKeys:l}=n,{context:u,digest:d}=await iq(e,{account:a,authorizeKeys:o,calls:c,chain:r,feeToken:t.feeToken,key:s,preCalls:!0,revokeKeys:l});return{context:u,signature:await yB(s,{address:null,payload:d,webAuthn:i})}})),{capabilities:c,context:l,digest:u}=await iq(e,{...t,account:a,chain:r,key:o,preCalls:s}),d=await(async()=>o?await yB(o,{address:null,payload:u,webAuthn:i,wrap:!1}):await a.sign({hash:u}))();return await sq(e,{capabilities:c.feeSignature?{feeSignature:c.feeSignature}:void 0,context:l,key:o,signature:d})}async function sq(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;return await GW(e,{capabilities:n,context:r,key:i?bB(i):void 0,signature:a})}async function cq(e,t){let{email:n,walletAddress:r}=t;return await KW(e,{email:n,walletAddress:r})}async function lq(e,t){if(t.account){let{account:n}=t,r=[...n.keys??[],...t.authorizeKeys??[]].filter((e,t,n)=>n.findIndex(t=>t.id===e.id)===t),{digests:i,...a}=await aq(e,{...t,address:n.address,authorizeKeys:r}),o={auth:await n.sign({hash:i.auth}),exec:await n.sign({hash:i.exec})};return await lq(e,{...a,signatures:o})}let{context:n,signatures:r}=t,i=TB(n.account);return await qW(e,{context:n,signatures:r}),i}async function uq(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;return await JW(e,{chainId:n,email:r,signature:i,token:a,walletAddress:o})}var dq=`0x8010801080108010801080108010801080108010801080108010801080108010`,fq=GR(`(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data`);function pq(e){if(typeof e==`string`){if(rL(e,-32)!==`0x8010801080108010801080108010801080108010801080108010801080108010`)throw new hq(e)}else uR(e.authorization)}function mq(e){let{data:t,signature:n}=e;pq(e);let r=rR({payload:$K(e.authorization),signature:mR(e.authorization)}),i=UR(fq,[{...e.authorization,delegation:e.authorization.address,chainId:BigInt(e.authorization.chainId)},e.to??r,t??`0x`]);return YI(n,i,$I(iL(i),{size:32}),dq)}var hq=class extends z{constructor(e){super(`Value \`${e}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc8010.InvalidWrappedSignatureError`})}};async function gq(e,t){let{address:n}=t,{authorization:r,data:i,to:a}=await IW(e,{address:n});return mq({authorization:{...r,nonce:BigInt(r.nonce),r:BigInt(r.r),s:BigInt(r.s)},data:i,signature:t.signature,to:a})}function _q(e,t){let{tokens:n}=t,r=n.filter(e=>e.interop);return e.map(e=>{if(e.address)return e;let t=r.find(t=>t.symbol===e.symbol);if(!t)throw Error(`interop token not found: ${e.symbol}`);return{address:t.address,value:Az(e.value,t.decimals)}})}async function vq(e,t){let{chain:n=e.chain}=t??{};return await LW(e,{chainId:n?.id}).then(e=>e.fees.tokens)}async function yq(e,t){let{addressOrSymbol:n}=t;return(await vq(e,t)).find(yq.predicate(n))}(function(e){function t(e){return t=>e?XL(e)?YL(t.address,e):e===`native`?t.address===dv:e===t.symbol:!1}e.predicate=t})(yq||={});async function bq(e,t){let{chain:n=e.chain,store:r}=t??{},i=r?.getState()??{},a=t?.addressOrSymbol??i.feeToken;return(await vq(e,{chain:n}).then(e=>e.filter(e=>e.feeToken)))?.find(e=>a?a===`native`&&e.address===`0x0000000000000000000000000000000000000000`||XL(a)&&YL(e.address,a)?!0:a===e.symbol:!1)}function xq(e={}){let t=e,{mock:n,multichain:r=!0,webAuthn:i}=t,a,o,s=(()=>{if(t.keystoreHost!==`self`&&!(typeof window<`u`&&window.location?.hostname===`localhost`))return t.keystoreHost})();return bK({actions:{async addFunds(){throw new bI},async createAccount(e){let{admins:t,email:r,eoa:o,label:c,permissions:l,internal:u,signInWithEthereum:d}=e,{client:f}=u,p=o??EB(nR()),m=await vq(f),h=n?lB():await cB({createFn:i?.createFn,label:c||`${p.address.slice(0,8)}\u2026${p.address.slice(-6)}`,rpId:s,userId:_L(p.address)}),g=await DK(l,{chainId:f.chain.id,feeTokens:m}),_=t?.map(e=>dB(e)),v=await lq(f,{account:p,authorizeKeys:[h,..._??[],...g?[g]:[]]});a=p.address,r&&c&&await cq(f,{email:c,walletAddress:v.address});let y=await(async()=>{if(!d)return;let e=await HK(f,d,{address:v.address}),t=await OB(p,{payload:KK(eL(e))});return{message:e,signature:await gq(f,{address:v.address,signature:t})}})();return{account:{...v,signInWithEthereum:y}}},async getAccountVersion(e){let{address:t,internal:n}=e,{client:r}=n,{contracts:i}=await LW(r),{accountImplementation:a}=i,o=await nq(r,{account:TB(a)}).then(e=>e.version),s=await nq(r,{account:t}).then(e=>e.version).catch(()=>o);if(!s||!o)throw Error(`version not found.`);return{current:s,latest:o}},async getAssets(e){let{account:t,chainFilter:n,assetFilter:r,assetTypeFilter:i,internal:a}=e,{client:o}=a;return await RW(o,{account:t,assetFilter:r,assetTypeFilter:i,chainFilter:n})},async getCallsHistory(e){let{internal:t,...n}=e,{client:r}=t;return await BW(r,n)},async getCallsStatus(e){let{id:t,internal:n}=e,{client:r}=n,i=await zW(r,{id:t});return{atomic:!0,chainId:$I(r.chain.id),id:t,receipts:i.receipts?.map(e=>({blockHash:e.blockHash,blockNumber:$I(e.blockNumber),gasUsed:$I(e.gasUsed),logs:e.logs,status:e.status,transactionHash:e.transactionHash})),status:i.status,version:`1.0`}},async getCapabilities(e){let{chainIds:t,internal:n}=e,{client:i}=n,a={atomic:{status:`supported`},atomicBatch:{supported:!0},feeToken:{supported:!0,tokens:[]},merchant:{supported:!0},permissions:{supported:!0},requiredFunds:{supported:!!r,tokens:[]}},o=await LW(i,{chainIds:t?t.map(e=>sL(e)):`all`,raw:!0});return Object.entries(o).reduce((e,[t,n])=>({...e,[t]:{...a,...n,feeToken:{supported:!0,tokens:n.fees.tokens},requiredFunds:{supported:!!r,tokens:r?n.fees.tokens.filter(e=>e.interop):[]}}}),{})},async getKeys(e){let{account:t,chainIds:n,internal:r}=e,{client:i}=r;return RB([...await rq(i,{account:t,chainIds:n}),...t.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{account:t,internal:n}=e,{client:r}=n,a=dB(e.key,{chainId:r.chain.id}),o=await bq(r,{addressOrSymbol:e.feeToken,store:n.store}),{id:s}=await oq(r,{account:t,authorizeKeys:[a],feeToken:o?.address,webAuthn:i});return await Zm(r,{id:s,pollingInterval:500}),{key:a}},async grantPermissions(e){let{account:t,internal:n,permissions:r}=e,{client:i}=n,a=await vq(i),o=await DK(r,{chainId:i.chain.id,feeTokens:a});if(!o)throw Error(`key to authorize not found.`);let s=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!s)throw Error(`admin key not found.`);let{context:c,digest:l}=await iq(i,{account:t,authorizeKeys:[o],key:s,preCalls:!0});return await sq(i,{context:c,key:s,signature:await yB(s,{address:null,payload:l})}),{key:o}},async loadAccounts(e){let{internal:t,permissions:r,signInWithEthereum:o}=e,{client:c}=t,l=await vq(c),u=await DK(r,{chainId:c.chain.id,feeTokens:l}),{digest:d,digestType:f,message:p}=await(async()=>{if(o&&e.address){let t=await HK(c,o,{address:e.address});return{context:void 0,digest:KK(eL(t)),digestType:`siwe`,message:t}}return{context:void 0,digest:`0x`,message:void 0}})(),{address:m,credentialId:h,webAuthnSignature:g}=await(async()=>{if(n){if(!a)throw Error(`address_internal not found.`);return{address:a,credentialId:void 0}}if(e.address&&e.key)return{address:e.address,credentialId:e.key.credentialId};let t=await Jz({challenge:d,getFn:i?.getFn,rpId:s}),r=t.raw.response;return{address:TL(new Uint8Array(r.userHandle)),credentialId:t.raw.id,webAuthnSignature:t}})(),_=TB({address:m,keys:[...await rq(c,{account:m,chainIds:[c.chain.id]}),...u?[u]:[]].map((e,t)=>t===0&&e.type===`webauthn-p256`?mB({...e,credential:{id:h,publicKey:LL(e.publicKey)},id:m,rpId:s}):e)}),v=DB(_,{role:`admin`}),y=await(async()=>{if(d!==`0x`)return g?wB(CB(g),{keyType:`webauthn-p256`,publicKey:v.publicKey}):await yB(v,{address:_.address,payload:d})})();if(u){let{context:e,digest:t}=await iq(c,{account:_,authorizeKeys:[u],preCalls:!0});await sq(c,{context:e,key:v,signature:await yB(v,{address:null,payload:t})})}let b=await(async()=>{if(o){if(f===`siwe`&&p&&y)return{message:p,signature:await gq(c,{address:_.address,signature:y})};{let e=await HK(c,o,{address:_.address}),t=await OB(_,{payload:KK(eL(e)),role:`admin`});return{message:e,signature:await gq(c,{address:_.address,signature:t})}}}})();return{accounts:[{..._,signInWithEthereum:b}]}},async prepareCalls(e){let{account:t,calls:n,internal:i,merchantUrl:a}=e,{client:o}=i,s=e.key??await xK({account:t,calls:n});if(!s)throw Error(`cannot find authorized key to sign with.`);let[c,l]=await Promise.all([vq(o),bq(o,{addressOrSymbol:e.feeToken,store:i.store})]),u=_q(e.requiredFunds??[],{tokens:c}),{capabilities:d,context:f,digest:p,typedData:m}=await iq(o,{account:t,calls:n,feeToken:l?.address,key:s,merchantUrl:a,requiredFunds:r?u:void 0}),h=f.quote?.quotes??[],g=h[h.length-1];return{account:t,capabilities:{...d,quote:f.quote},chainId:o.chain.id,context:{...f,account:t,calls:n,nonce:g?.intent.nonce},digest:p,key:s,typedData:m}},async prepareUpgradeAccount(e){let{address:t,email:r,label:a,internal:c,permissions:l}=e,{client:u}=c,[d,f]=await Promise.all([vq(u),bq(u,{store:c.store})]),p=n?lB():await cB({createFn:i?.createFn,label:a||`${t.slice(0,8)}\u2026${t.slice(-6)}`,rpId:s,userId:_L(t)}),m=await DK(l,{chainId:u.chain.id,feeTokens:d}),{context:h,digests:g}=await aq(u,{address:t,authorizeKeys:[p,...m?[m]:[]],feeToken:f?.address});return r&&(o=a),{context:h,digests:g}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);try{let{id:n}=await oq(a,{account:t,feeToken:(await bq(a,{addressOrSymbol:e.feeToken,store:r.store}))?.address,revokeKeys:[o],webAuthn:i});await Zm(a,{id:n})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke admins.`);try{let{id:n}=await oq(a,{account:t,feeToken:(await bq(a,{addressOrSymbol:e.feeToken,store:r.store}))?.address,revokeKeys:[o],webAuthn:i});await Zm(a,{id:n})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async sendCalls(e){let{account:t,asTxHash:n,calls:a,chainId:o,internal:s,merchantUrl:c}=e,{client:l}=s;if(!t)throw Error(`account required for relay mode`);let u=await xK({account:t,calls:a,permissionsId:e.permissionsId}),[d,f]=await Promise.all([vq(l),bq(l,{addressOrSymbol:e.feeToken,store:s.store})]),p=_q(e.requiredFunds??[],{tokens:d}),m=await oq(l,{account:t,calls:a,feeToken:f?.address,key:u,merchantUrl:c,requiredFunds:r?p:void 0,webAuthn:i,...o?{chain:{id:o}}:{}});if(n){let{id:e,receipts:t,status:n}=await Zm(l,{id:m.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new DI({message:`Call bundle with id: `+e+` not found.`}):new cI({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return m},async sendPreparedCalls(e){let{context:t,key:n,internal:r,signature:i}=e,{client:a}=r,{id:o}=await sq(a,{context:t,key:n,signature:i});return o},async signPersonalMessage(e){let{account:t,data:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!o)throw Error(`cannot find admin key to sign with.`);let s=await OB(t,{key:o,payload:KK(n),webAuthn:i});return gq(a,{address:t.address,signature:s})},async signTypedData(e){let{account:t,internal:n}=e,{client:r}=n,a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!a)throw Error(`cannot find admin key to sign with.`);let o=XF(e.data),s=o.domain?.name===`Orchestrator`,c=await OB(t,{key:a,payload:tz(o),replaySafe:!s,webAuthn:i});return s?c:gq(r,{address:t.address,signature:c})},async upgradeAccount(e){let{account:t,context:n,internal:r,signatures:i}=e,{client:a}=r;return await lq(a,{context:n,signatures:i}),o&&await cq(a,{email:o,walletAddress:t.address}),{account:t}},async verifyEmail(e){let{account:t,chainId:n,email:r,token:a,internal:o,walletAddress:s}=e,{client:c}=o,l=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!l)throw Error(`cannot find admin key to sign with.`);return await uq(c,{chainId:n,email:r,signature:await OB(t,{key:l,payload:ML(eL(`${r}${a}`)),webAuthn:i}),token:a,walletAddress:s})}},config:e,name:`rpc`})}function Sq(e={}){let{fallback:t=xq(),host:n=WB.prod,renderer:r=KB(),theme:i,themeController:a}=e,o=new Set,s=SK();function c(e,t){return new Promise((n,r)=>{let i=a=>{let s=a.find(t=>t.request.id===e);if(!s&&a.length===0){o.delete(i),r(new vI);return}s&&(s.status!==`success`&&s.status!==`error`||(o.delete(i),s.status===`success`?n(s.result):r(NI(s.error)),t.setState(t=>({...t,requestQueue:t.requestQueue.filter(t=>t.request.id!==e)}))))};o.add(i)})}function l(e){return MI({async request(t){let n=s.prepare(t);return e.setState(e=>{let t=e.accounts[0],r=t?.keys?.find(e=>e.role===`admin`&&e.type===`webauthn-p256`);return{...e,requestQueue:[...e.requestQueue,{account:t?{address:t.address,key:r?{credentialId:r?.credentialId,publicKey:r.publicKey}:void 0}:void 0,request:n,status:`pending`}]}}),c(n.id,e)}},{schema:wK()})}return bK({actions:{async addFunds(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_addFunds`)throw Error(`Cannot add funds for method: `+n.method);return await l(r).request(n)},async createAccount(e){let{internal:t}=e,{client:n,config:r,request:i,store:a}=t,{storage:o}=r,s=l(a);return{account:await(async()=>{if(i.method===`wallet_connect`){let[{capabilities:e,chainIds:t}]=i._decoded.params??[{}],a=wq(e?.signInWithEthereum?.authUrl??r.authUrl,{storage:o}),c=i.params?.[0]?.capabilities?.signInWithEthereum,l=await DK(e?.grantPermissions,{chainId:n.chain.id}),u=l?RV(TK,EK(l)):void 0,{accounts:d}=await s.request({...i,params:[{capabilities:{...i.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:a||c?{...c,authUrl:a}:void 0},chainIds:t?.map(e=>$I(e))}]}),[f]=d;if(!f)throw Error(`no account found.`);let p=f.capabilities?.admins?.map(e=>dB(e,{chainId:n.chain.id})).filter(Boolean),m=f.capabilities?.permissions?.map(e=>{try{let t=_G(zV(hG,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),h=await(async()=>{if(!f.capabilities?.signInWithEthereum)return;let{message:e,signature:t}=f.capabilities.signInWithEthereum;if(!a)return{message:e,signature:t};let{token:n}=await VK({address:f.address,authUrl:a,message:e,publicKey:f.capabilities?.admins?.[0]?.publicKey,signature:t});return{message:e,signature:t,token:n}})();return{...TB({address:f.address,keys:[...p??[],...m??[]]}),signInWithEthereum:h}}throw Error(`Account creation not supported on method: ${i.method}`)})()}},async disconnect(e){let{internal:t}=e,{config:n}=t,{storage:r}=n,i=await r.getItem(`porto.authUrl`)||void 0,a=wq(n.authUrl??i,{storage:r});a&&await fetch(a.logout,{credentials:`include`,method:`POST`}).catch(()=>{})},async getAccountVersion(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAccountVersion`)throw Error(`Cannot get version for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getAccountVersion(e)},async getAssets(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAssets`)throw Error(`Cannot get assets for method: `+a.method);if(!r.supportsHeadless)return t.actions.getAssets(e);let o=await l(i).request(a);return zV(tK.Response,o)},async getCallsHistory(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCallsHistory`)throw Error(`Cannot get history for method: `+a.method);if(!r.supportsHeadless)return t.actions.getCallsHistory(e);let o=await l(i).request(a);return zV(rK.Response,o)},async getCallsStatus(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCallsStatus`)throw Error(`Cannot get status for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getCallsStatus(e)},async getCapabilities(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCapabilities`)throw Error(`Cannot get capabilities for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getCapabilities(e)},async getKeys(e){let{account:n,chainIds:i,internal:a}=e,{store:o}=a;return RB([...await(async()=>{if(!r.supportsHeadless)return t.actions.getKeys(e);let a=await l(o).request({method:`wallet_getKeys`,params:[RV(aK.Parameters,{address:n.address,chainIds:i})]});return zV(aK.Response,a)})(),...n.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_grantAdmin`)throw Error(`Cannot authorize admin for method: `+n.method);let[i]=n._decoded.params,a=dB(i.key);if(!a)throw Error(`no key found.`);let o=await Cq(t,e);return await l(r).request({method:`wallet_grantAdmin`,params:[{...n.params?.[0],capabilities:{...n.params?.[0]?.capabilities,feeToken:o}}]}),{key:a}},async grantPermissions(e){let{internal:t}=e,{client:n,request:r,store:i}=t;if(r.method!==`wallet_grantPermissions`)throw Error(`Cannot grant permissions for method: `+r.method);let[{address:a,...o}]=r._decoded.params,s=await DK(o,{chainId:n.chain.id});if(!s)throw Error(`no key found.`);let c=RV(TK,EK(s));return await l(i).request({method:`wallet_grantPermissions`,params:[c]}),{key:s}},async loadAccounts(e){let{internal:t}=e,{client:n,config:r,store:i}=t,{storage:a}=r,o=l(i),s=t.request;if(s.method!==`wallet_connect`&&s.method!==`eth_requestAccounts`)throw Error(`Cannot load accounts for method: `+s.method);return{accounts:await(async()=>{let[e]=s._decoded.params??[],{capabilities:t}=e??{},i=wq(t?.signInWithEthereum?.authUrl??r.authUrl,{storage:a}),c=s.params?.[0]?.capabilities?.signInWithEthereum,l=await DK(t?.grantPermissions,{chainId:n.chain.id}),u=l?RV(TK,EK(l)):void 0,{accounts:d}=await o.request({method:`wallet_connect`,params:[{...s.params?.[0],capabilities:{...s.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:i||c?{...c,authUrl:i}:void 0}}]});return Promise.all(d.map(async e=>{let t=e.capabilities?.admins?.map(e=>dB(e)).filter(Boolean),n=e.capabilities?.permissions?.map(e=>{try{let t=_G(zV(hG,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),r=await(async()=>{if(!e.capabilities?.signInWithEthereum)return;let{message:t,signature:n}=e.capabilities.signInWithEthereum;if(!i)return{message:t,signature:n};let{token:r}=await VK({address:e.address,authUrl:i,message:t,publicKey:e.capabilities?.admins?.[0]?.publicKey,signature:n});return{message:t,signature:n,token:r}})();return{...TB({address:e.address,keys:[...t??[],...n??[]]}),signInWithEthereum:r}}))})()}},async prepareCalls(e){let{account:n,internal:i}=e,{store:a,request:o}=i;if(o.method!==`wallet_prepareCalls`)throw Error(`Cannot prepare calls for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareCalls(e);let s=await Cq(i,e),c=l(a),u=zV(oK.Response,await c.request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,feeToken:s}}]}));return{account:n,chainId:Number(u.chainId),context:u.context,digest:u.digest,key:u.key,typedData:u.typedData}},async prepareUpgradeAccount(e){let{internal:n}=e,{client:i,store:a,request:o}=n;if(o.method!==`wallet_prepareUpgradeAccount`)throw Error(`Cannot prepare upgrade for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareUpgradeAccount(e);let[{capabilities:s}]=o._decoded.params??[{}],c=await DK(s?.grantPermissions,{chainId:i.chain.id}),u=c?RV(TK,EK(c)):void 0,{context:d,digests:f}=await l(a).request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,grantPermissions:u}}]}),p=d.account.keys?.map(e=>e.id===c?.id?{...e,...c}:e);return{context:{...d,account:{...d.account,keys:p}},digests:f}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokeAdmin`)throw Error(`Cannot revoke admin for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(!o)return;if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);let s=await Cq(r,e);return await l(i).request({...a,params:[{...a.params?.[0],capabilities:{...a.params?.[0]?.capabilities,feeToken:s}}]})},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokePermissions`)throw Error(`Cannot revoke permissions for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke permissions.`);return await l(i).request(a)}},async sendCalls(e){let{account:n,asTxHash:i,calls:a,chainId:o,internal:s,merchantUrl:c,requiredFunds:u}=e,{client:d,store:f,request:p}=s,m=l(f),h=await Cq(s,e),g=n?await xK({account:n,calls:a,permissionsId:e.permissionsId}):void 0;if(g?.role===`session`&&n){if(!r.supportsHeadless)return t.actions.sendCalls(e);try{let e=await m.request(RV(oK.Request,{method:`wallet_prepareCalls`,params:[{calls:a,capabilities:{...p._decoded.method===`wallet_sendCalls`?p._decoded.params?.[0]?.capabilities:void 0,feeToken:h,merchantUrl:c,requiredFunds:u},chainId:o,from:n.address,key:g}]})),t=e.capabilities?.quote?.quotes??[];if(t.some((e,n)=>n===t.length-1&&t.length>1?!1:oL(e.feeTokenDeficit)>0n))throw Error(`insufficient funds`);let r=await yB(g,{address:null,payload:e.digest,wrap:!1}),s=(await m.request({method:`wallet_sendPreparedCalls`,params:[{...e,signature:r}]}))[0];if(!s)throw Error(`id not found`);if(i){let{id:e,receipts:t,status:n}=await Zm(d,{id:s.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new DI({message:`Call bundle with id: `+e+` not found.`}):new cI({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return s}catch{}}if(p.method===`eth_sendTransaction`)return{id:await m.request({...p,params:[{...p.params?.[0],capabilities:{feeToken:h,merchantUrl:c},...o?{chainId:$I(o)}:{}}]})};if(p.method===`wallet_sendCalls`)return await m.request({method:`wallet_sendCalls`,params:[{...p.params?.[0],capabilities:{...p.params?.[0]?.capabilities,feeToken:h,merchantUrl:c},...o?{chainId:$I(o)}:{}}]});throw Error(`Cannot execute for method: `+p.method)},async sendPreparedCalls(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_sendPreparedCalls`)throw Error(`Cannot send prepared calls for method: `+a.method);if(!r.supportsHeadless)return t.actions.sendPreparedCalls(e);let o=(await l(i).request(a))[0]?.id;if(!o)throw Error(`id not found`);return o},async signPersonalMessage(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`personal_sign`)throw Error(`Cannot sign personal message for method: `+r.method);return await l(n).request(r)},async signTypedData(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`eth_signTypedData_v4`)throw Error(`Cannot sign typed data for method: `+r.method);return await l(n).request(r)},async switchChain(e){let{internal:t}=e,{store:n,request:i}=t;if(i.method!==`wallet_switchEthereumChain`)throw Error(`Cannot switch chain for method: `+i.method);if(r.supportsHeadless)return await l(n).request(i)},async upgradeAccount(e){let{account:t,internal:n}=e,{store:r,request:i}=n;if(i.method!==`wallet_upgradeAccount`)throw Error(`Cannot upgrade account for method: `+i.method);return await l(r).request(i),{account:t}},async verifyEmail(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`account_verifyEmail`)throw Error(`Cannot verify email for method: `+n.method);return await l(r).request(n)}},config:e,name:`dialog`,setup(e){let{internal:t}=e,{store:s}=t,c=r.setup({host:n,internal:t,theme:i,themeController:a}),l=s.subscribe(e=>e.requestQueue,e=>{for(let t of o)t(e);let t=e.map(e=>e.status===`pending`?e:void 0).filter(Boolean);c.syncRequests(t).catch(()=>{}),t.length===0&&c.close()});return()=>{l(),c.destroy()}}})}async function Cq(e,t){let{config:{feeToken:n}}=e,{feeToken:r}=t??{};return r??n}function wq(e,{storage:t}){if(!e)return;let n=UK(e,typeof window<`u`?window.location.origin:void 0);return n&&t.setItem(`porto.authUrl`,n),n}var Tq=e=>!!e.dispatchFromDevtools&&typeof e.dispatch==`function`,Eq=new Map,Dq=e=>{let t=Eq.get(e);return t?Object.fromEntries(Object.entries(t.stores).map(([e,t])=>[e,t.getState()])):{}},Oq=(e,t,n)=>{if(e===void 0)return{type:`untracked`,connection:t.connect(n)};let r=Eq.get(n.name);if(r)return{type:`tracked`,store:e,...r};let i={connection:t.connect(n),stores:{}};return Eq.set(n.name,i),{type:`tracked`,store:e,...i}},kq=(e,t)=>{if(t===void 0)return;let n=Eq.get(e);n&&(delete n.stores[t],Object.keys(n.stores).length===0&&Eq.delete(e))},Aq=e=>{if(!e)return;let t=e.split(` +`),n=t.findIndex(e=>e.includes(`api.setState`));if(n<0)return;let r=t[n+1]?.trim()||``;return/.+ (.+) .+/.exec(r)?.[1]},jq=(e,t={})=>(n,r,i)=>{let{enabled:a,anonymousActionType:o,store:s,...c}=t,l;try{l=(a??!1)&&window.__REDUX_DEVTOOLS_EXTENSION__}catch{}if(!l)return e(n,r,i);let{connection:u,...d}=Oq(s,l,c),f=!0;i.setState=((e,t,a)=>{let l=n(e,t);if(!f)return l;let d=a===void 0?{type:o||Aq(Error().stack)||`anonymous`}:typeof a==`string`?{type:a}:a;return s===void 0?(u?.send(d,r()),l):(u?.send({...d,type:`${s}/${d.type}`},{...Dq(c.name),[s]:i.getState()}),l)}),i.devtools={cleanup:()=>{u&&typeof u.unsubscribe==`function`&&u.unsubscribe(),kq(c.name,s)}};let p=(...e)=>{let t=f;f=!1,n(...e),f=t},m=e(i.setState,r,i);if(d.type===`untracked`?u?.init(m):(d.stores[d.store]=i,u?.init(Object.fromEntries(Object.entries(d.stores).map(([e,t])=>[e,e===d.store?m:t.getState()])))),Tq(i)){let e=i.dispatch;i.dispatch=(...t)=>{e(...t)}}return u.subscribe(e=>{switch(e.type){case`ACTION`:if(typeof e.payload!=`string`){console.error(`[zustand devtools middleware] Unsupported action format`);return}return Mq(e.payload,e=>{if(e.type===`__setState`){if(s===void 0){p(e.state);return}Object.keys(e.state).length!==1&&console.error(` [zustand devtools middleware] Unsupported __setState action format. When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(), and value of this only key should be a state object. Example: { "type": "__setState", "state": { "abc123Store": { "foo": "bar" } } } - `);let t=e.state[s];if(t==null)return;JSON.stringify(i.getState())!==JSON.stringify(t)&&p(t);return}i.dispatchFromDevtools&&typeof i.dispatch==`function`&&i.dispatch(e)});case`DISPATCH`:switch(e.payload.type){case`RESET`:return p(m),s===void 0?u?.init(i.getState()):u?.init(pV(c.name));case`COMMIT`:if(s===void 0){u?.init(i.getState());return}return u?.init(pV(c.name));case`ROLLBACK`:return vV(e.state,e=>{if(s===void 0){p(e),u?.init(i.getState());return}p(e[s]),u?.init(pV(c.name))});case`JUMP_TO_STATE`:case`JUMP_TO_ACTION`:return vV(e.state,e=>{if(s===void 0){p(e);return}JSON.stringify(i.getState())!==JSON.stringify(e[s])&&p(e[s])});case`IMPORT_STATE`:{let{nextLiftedState:t}=e.payload,n=t.computedStates.slice(-1)[0]?.state;if(!n)return;p(s===void 0?n:n[s]),u?.send(null,t);return}case`PAUSE_RECORDING`:return f=!f}return}}),m},vV=(e,t)=>{let n;try{n=JSON.parse(e)}catch(e){console.error(`[zustand devtools middleware] Could not parse the received json`,e)}n!==void 0&&t(n)},yV=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function bV(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var xV=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return xV(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return xV(t)(e)}}}},SV=(e,t)=>(n,r,i)=>{let a={storage:bV(()=>localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=new Set,c=new Set,l=a.storage;if(!l)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let u=()=>{let e=a.partialize({...r()});return l.setItem(a.name,{state:e,version:a.version})},d=i.setState;i.setState=(e,t)=>(d(e,t),u());let f=e((...e)=>(n(...e),u()),r,i);i.getInitialState=()=>f;let p,m=()=>{if(!l)return;o=!1,s.forEach(e=>e(r()??f));let e=a.onRehydrateStorage?.call(a,r()??f)||void 0;return xV(l.getItem.bind(l))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(e=>{let[t,i]=e;if(p=a.merge(i,r()??f),n(p,!0),t)return u()}).then(()=>{e?.(p,void 0),p=r(),o=!0,c.forEach(e=>e(p))}).catch(t=>{e?.(void 0,t)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(l=e.storage)},clearStorage:()=>{l?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>m(),hasHydrated:()=>o,onHydrate:e=>(s.add(e),()=>{s.delete(e)}),onFinishHydration:e=>(c.add(e),()=>{c.delete(e)})},a.skipHydration||m(),p||f},CV=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},wV=(e=>e?CV(e):CV);function TV(e){return new Promise((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)})}function EV(e,t){let n,r=()=>{if(n)return n;let r=indexedDB.open(e);return r.onupgradeneeded=()=>r.result.createObjectStore(t),n=TV(r),n.then(e=>{e.onclose=()=>n=void 0},()=>{}),n};return(e,n)=>r().then(r=>n(r.transaction(t,e).objectStore(t)))}var DV;function OV(){return DV||=EV(`keyval-store`,`keyval`),DV}function kV(e,t=OV()){return t(`readonly`,t=>TV(t.get(e)))}function AV(e,t,n=OV()){return n(`readwrite`,n=>(n.put(t,e),TV(n.transaction)))}function jV(e,t=OV()){return t(`readwrite`,t=>(t.delete(e),TV(t.transaction)))}function MV(e){return e}function NV(){let e=typeof indexedDB<`u`?EV(`porto`,`store`):void 0;return MV({async getItem(t){let n=await kV(t,e);return n===null?null:n},async removeItem(t){await jV(t,e)},async setItem(t,n){await AV(t,pN(n),e)},sizeLimit:1024*1024*50})}function PV(){let e=new Map;return MV({getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)},sizeLimit:1/0})}var FV=typeof window<`u`&&typeof document<`u`;const IV={announceProvider:!0,chains:CM,mode:FV?lV({host:bN.prod}):cV(),relay:zv(rz.prod.http),storage:FV&&typeof indexedDB<`u`?NV():PV(),storageKey:`porto.store`};function LV(e={}){let t=e.chains??IV.chains,n=Object.fromEntries(t.map(t=>[t.id,e.transports?.[t.id]??zv()])),r={announceProvider:e.announceProvider??IV.announceProvider,authUrl:e.authUrl,chains:t,feeToken:e.feeToken,merchantUrl:e.merchantUrl,mode:e.mode??IV.mode,relay:e.relay??IV.relay,storage:e.storage??IV.storage,storageKey:e.storageKey??IV.storageKey,transports:n},i=wV(_V(yV(SV(e=>({accounts:[],chainIds:r.chains.map(e=>e.id),feeToken:r.feeToken,requestQueue:[]}),{merge(e,t){let n=e,i=r.chains.find(e=>e.id===n.chainIds[0])?.id??r.chains[0].id,a=[i,...r.chains.map(e=>e.id).filter(e=>e!==i)];return{...t,...n,chainIds:a}},name:r.storageKey,partialize:e=>({accounts:e.accounts.map(e=>pN(e)),chainIds:e.chainIds}),storage:r.storage,version:5})))),a=r.mode,o={config:r,getMode(){return a},id:hN(),setMode(e){return c?.(),a=e,c=e.setup({internal:o}),c},store:i},s=gB(o),c=a===null?()=>{}:a.setup({internal:o});return{_internal:o,config:r,destroy(){c(),s._internal.destroy()},provider:s}}const RV=Object.freeze(Object.values(xM)),zV=e=>RV.find(t=>t.id===e);var BV=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`)return;let t=e.trim();if(/^0x[0-9a-fA-F]+$/.test(t)){let e=Number.parseInt(t,16);return Number.isNaN(e)?void 0:e}if(/^\d+$/.test(t)){let e=Number.parseInt(t,10);return Number.isNaN(e)?void 0:e}};const VV=(e,t,n)=>{let r=BV(e);t(r),n(r==null?void 0:zV(r))},HV=async(e,t=`GET`,n)=>{let r={"Content-Type":`application/json`},i=typeof window<`u`?window.__SESSION_TOKEN__:void 0;i&&(r[`X-Session-Token`]=i);let a=await fetch(`http://127.0.0.1:9545${e}`,{method:t,headers:r,body:n===void 0?void 0:JSON.stringify(n)});if(!a.ok)throw Error(`API request failed: ${a.status} ${a.statusText}`);try{return await a.json()}catch{throw Error(`Invalid JSON response`)}},UV=e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t,2),WV=e=>{if(e==null)return UV(e);if(typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`){let t=e;try{let n=JSON.parse(t.message);return UV({...e,message:n})}catch{return UV(e)}}return UV(e)},GV=e=>!!e&&e.status===`ok`;var KV=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=u(s(((e,t)=>{t.exports=KV()}))());function qV(){(0,y.useEffect)(()=>{window.__PORTO__||(window.__PORTO__=LV())},[]);let[e,t]=(0,y.useState)([]),[n,r]=(0,y.useState)(!1),[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(null),[c,l]=(0,y.useState)(null),u=e.find(e=>e.info.uuid===c)??null,[d,f]=(0,y.useState)(),[p,m]=(0,y.useState)(),[h,g]=(0,y.useState)(),[_,v]=(0,y.useState)(null),[b,x]=(0,y.useState)(null),[S,C]=(0,y.useState)(null),w=(0,y.useRef)(null),ee=(0,y.useRef)(null),te=(0,y.useRef)(null),ne=async()=>{if(!u||n)return;let e=await u.provider.request({method:`eth_requestAccounts`});f(e?.[0]??void 0);try{let e=await u.provider.request({method:`eth_chainId`});VV(e,m,g)}catch{m(void 0),g(void 0)}},re=async()=>{if(!(!d||p==null)){try{await HV(`/api/connection`,`POST`,[d,p])}catch{return}r(!0)}},ie=async()=>{if(!u||!o)return;let{id:e,signType:t,request:n}=o,r=n.address,i=n.message;try{let n;switch(t){case`PersonalSign`:n=await u.provider.request({method:`personal_sign`,params:[i,r]});break;case`SignTypedDataV4`:n=await u.provider.request({method:`eth_signTypedData_v4`,params:[r,i]});break;default:throw Error(`Unsupported signType: ${t}`)}await HV(`/api/signing/response`,`POST`,{id:e,signature:n,error:null}),C(n),s(null)}catch(t){let n=typeof t==`object`&&t&&`message`in t&&typeof t.message==`string`?t.message:String(t);try{await HV(`/api/signing/response`,`POST`,{id:e,signature:null,error:n})}catch{}C(null),s(null)}},ae=async()=>{if(!u||!i?.request)return;let e=Mv({transport:Pv(u.provider),chain:h});try{let{from:t,input:n,to:r,...a}=i.request,o=await e.sendTransaction({...a,account:t||(await e.getAddresses())[0],...n?{data:n}:{},...r?{to:r}:{},chain:h});x(o),await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:o,error:null});let s=await uv(e,{hash:o});v(s)}catch(e){let t=typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?e.message:String(e);console.error(`send failed:`,t);try{await HV(`/api/transaction/response`,`POST`,{id:i.id,hash:null,error:t})}catch{}}},oe=(0,y.useCallback)(()=>{w.current&&=(window.clearInterval(w.current),null),ee.current&&=(window.clearInterval(ee.current),null),a(null),s(null),x(null),v(null),f(void 0),m(void 0),g(void 0),r(!1),HV(`/api/connection`,`POST`,null)},[]);return(0,y.useEffect)(()=>{te.current&&c&&te.current!==c&&oe(),te.current=c},[c,oe]),(0,y.useEffect)(()=>{e.length===1&&!u&&l(e[0].info.uuid)},[e,u]),(0,y.useEffect)(()=>{let e=e=>{let{info:n,provider:r}=e.detail;t(e=>e.some(e=>e.info.uuid===n.uuid)?e:[...e,{info:n,provider:r}])};return window.addEventListener(`eip6963:announceProvider`,e),window.dispatchEvent(new Event(`eip6963:requestProvider`)),()=>window.removeEventListener(`eip6963:announceProvider`,e)},[]),(0,y.useEffect)(()=>{if(!u)return;let e=e=>{n||f(e[0]??void 0)},t=e=>{n||VV(e,m,g)};return u.provider.on?.(`accountsChanged`,e),u.provider.on?.(`chainChanged`,t),()=>{u.provider.removeListener?.(`accountsChanged`,e),u.provider.removeListener?.(`chainChanged`,t)}},[u,n]),(0,y.useEffect)(()=>{if(!n||i||o)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/transaction/request`);GV(n)&&(window.clearInterval(t),e&&a(n.data))}catch{}},1e3);return w.current=t,()=>{e=!1,window.clearInterval(t),w.current===t&&(w.current=null)}},[n,i,o]),(0,y.useEffect)(()=>{if(!n||o||i)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await HV(`/api/signing/request`);GV(n)&&(window.clearInterval(t),e&&s(n.data))}catch{}},1e3);return ee.current=t,()=>{e=!1,window.clearInterval(t),ee.current===t&&(ee.current=null)}},[n,o,i]),(0,$.jsx)(`div`,{className:`wrapper`,children:(0,$.jsxs)(`div`,{className:`container`,children:[(0,$.jsx)(`div`,{className:`notice`,children:`Browser wallet is still in early development. Use with caution!`}),(0,$.jsx)(`img`,{className:`banner`,src:`banner.png`,alt:`Foundry Browser Wallet`}),e.length>1&&(0,$.jsx)(`div`,{className:`wallet-selector`,children:(0,$.jsx)(`label`,{children:(0,$.jsxs)(`select`,{value:c??``,onChange:e=>l(e.target.value||null),disabled:n,children:[(0,$.jsx)(`option`,{value:``,disabled:!0,children:`Select wallet…`}),e.map(({info:e})=>(0,$.jsxs)(`option`,{value:e.uuid,children:[e.name,` (`,e.rdns,`)`]},e.uuid))]})})}),e.length===0&&(0,$.jsx)(`p`,{children:`No wallets found.`}),u&&!d&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-connect`,onClick:ne,disabled:n,children:`Connect Wallet`}),u&&d&&!n&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-confirm`,onClick:re,disabled:!d||p==null,children:`Confirm Connection`}),u&&d&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Connected`}),(0,$.jsx)(`pre`,{className:`box`,children:`\ + `);let t=e.state[s];if(t==null)return;JSON.stringify(i.getState())!==JSON.stringify(t)&&p(t);return}Tq(i)&&i.dispatch(e)});case`DISPATCH`:switch(e.payload.type){case`RESET`:return p(m),s===void 0?u?.init(i.getState()):u?.init(Dq(c.name));case`COMMIT`:if(s===void 0){u?.init(i.getState());return}return u?.init(Dq(c.name));case`ROLLBACK`:return Mq(e.state,e=>{if(s===void 0){p(e),u?.init(i.getState());return}p(e[s]),u?.init(Dq(c.name))});case`JUMP_TO_STATE`:case`JUMP_TO_ACTION`:return Mq(e.state,e=>{if(s===void 0){p(e);return}JSON.stringify(i.getState())!==JSON.stringify(e[s])&&p(e[s])});case`IMPORT_STATE`:{let{nextLiftedState:t}=e.payload,n=t.computedStates.slice(-1)[0]?.state;if(!n)return;p(s===void 0?n:n[s]),u?.send(null,t);return}case`PAUSE_RECORDING`:return f=!f}return}}),m},Mq=(e,t)=>{let n;try{n=JSON.parse(e)}catch(e){console.error(`[zustand devtools middleware] Could not parse the received json`,e)}n!==void 0&&t(n)},Nq=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function Pq(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var Fq=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return Fq(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return Fq(t)(e)}}}},Iq=(e,t)=>(n,r,i)=>{let a={storage:Pq(()=>window.localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=0,c=new Set,l=new Set,u=a.storage;if(!u)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let d=()=>{let e=a.partialize({...r()});return u.setItem(a.name,{state:e,version:a.version})},f=i.setState;i.setState=(e,t)=>(f(e,t),d());let p=e((...e)=>(n(...e),d()),r,i);i.getInitialState=()=>p;let m,h=()=>{if(!u)return;let e=++s;o=!1,c.forEach(e=>e(r()??p));let t=a.onRehydrateStorage?.call(a,r()??p)||void 0;return Fq(u.getItem.bind(u))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(t=>{if(e!==s)return;let[i,o]=t;if(m=a.merge(o,r()??p),n(m,!0),i)return d()}).then(()=>{e===s&&(t?.(r(),void 0),m=r(),o=!0,l.forEach(e=>e(m)))}).catch(n=>{e===s&&t?.(void 0,n)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(u=e.storage)},clearStorage:()=>{u?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>h(),hasHydrated:()=>o,onHydrate:e=>(c.add(e),()=>{c.delete(e)}),onFinishHydration:e=>(l.add(e),()=>{l.delete(e)})},a.skipHydration||h(),m||p},Lq=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Rq=(e=>e?Lq(e):Lq);function zq(e){return new Promise((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)})}function Bq(e,t){let n,r=()=>{if(n)return n;let r=indexedDB.open(e);return r.onupgradeneeded=()=>r.result.createObjectStore(t),n=zq(r),n.then(e=>{e.onclose=()=>n=void 0},()=>{}),n};return(e,n)=>r().then(r=>n(r.transaction(t,e).objectStore(t)))}var Vq;function Hq(){return Vq||=Bq(`keyval-store`,`keyval`),Vq}function Uq(e,t=Hq()){return t(`readonly`,t=>zq(t.get(e)))}function Wq(e,t,n=Hq()){return n(`readwrite`,n=>(n.put(t,e),zq(n.transaction)))}function Gq(e,t=Hq()){return t(`readwrite`,t=>(t.delete(e),zq(t.transaction)))}function Kq(e){return e}function qq(){let e=typeof indexedDB<`u`?Bq(`porto`,`store`):void 0;return Kq({async getItem(t){let n=await Uq(t,e);return n===null?null:n},async removeItem(t){await Gq(t,e)},async setItem(t,n){await Wq(t,LB(n),e)},sizeLimit:1024*1024*50})}function Jq(){let e=new Map;return Kq({getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)},sizeLimit:1/0})}var Yq=typeof window<`u`&&typeof document<`u`,Xq={announceProvider:!0,chains:qF,mode:Yq?Sq({host:WB.prod}):xq(),relay:$v(tG.prod.http),storage:Yq&&typeof indexedDB<`u`?qq():Jq(),storageKey:`porto.store`};function Zq(e={}){let t=e.chains??Xq.chains,n=Object.fromEntries(t.map(t=>[t.id,e.transports?.[t.id]??$v()])),r={announceProvider:e.announceProvider??Xq.announceProvider,authUrl:e.authUrl,chains:t,feeToken:e.feeToken,merchantUrl:e.merchantUrl,mode:e.mode??Xq.mode,relay:e.relay??Xq.relay,storage:e.storage??Xq.storage,storageKey:e.storageKey??Xq.storageKey,transports:n},i=Rq(jq(Nq(Iq(e=>({accounts:[],chainIds:r.chains.map(e=>e.id),feeToken:r.feeToken,requestQueue:[]}),{merge(e,t){let n=e,i=r.chains.find(e=>e.id===n.chainIds[0])?.id??r.chains[0].id,a=[i,...r.chains.map(e=>e.id).filter(e=>e!==i)];return{...t,...n,chainIds:a}},name:r.storageKey,partialize:e=>({accounts:e.accounts.map(e=>LB(e)),chainIds:e.chainIds}),storage:r.storage,version:5})))),a=r.mode,o={config:r,getMode(){return a},id:zB(),setMode(e){return c?.(),a=e,c=e.setup({internal:o}),c},store:i},s=hK(o),c=a===null?()=>{}:a.setup({internal:o});return{_internal:o,config:r,destroy(){c(),s._internal.destroy()},provider:s}}var Qq=`http://127.0.0.1:9545`,$q=Object.freeze(Object.values(KF)),eJ=e=>$q.find(t=>t.id===e),tJ=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`)return;let t=e.trim();if(/^0x[0-9a-fA-F]+$/.test(t)){let e=Number.parseInt(t,16);return Number.isNaN(e)?void 0:e}if(/^\d+$/.test(t)){let e=Number.parseInt(t,10);return Number.isNaN(e)?void 0:e}},nJ=(e,t,n)=>{let r=tJ(e);if(t(r),r!=null){let e=eJ(r);e||={id:r,name:`Chain ${r}`,network:`chain-${r}`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[]},public:{http:[]}}},n(e)}else n(void 0)},rJ=async(e,t=`GET`,n)=>{let r={"Content-Type":`application/json`},i=typeof window<`u`?window.__SESSION_TOKEN__:void 0;i&&(r[`X-Session-Token`]=i);let a=await fetch(`${Qq}${e}`,{method:t,headers:r,body:n===void 0?void 0:JSON.stringify(n)});if(!a.ok)throw Error(`API request failed: ${a.status} ${a.statusText}`);try{return await a.json()}catch{throw Error(`Invalid JSON response`)}},iJ=e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t,2),aJ=e=>{if(e==null)return iJ(e);if(typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`){let t=e;try{let n=JSON.parse(t.message);return iJ({...e,message:n})}catch{return iJ(e)}}return iJ(e)},oJ=e=>!!e&&e.status===`ok`,sJ=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=s(((e,t)=>{t.exports=sJ()}))();function cJ(){(0,y.useEffect)(()=>{window.__PORTO__||(window.__PORTO__=Zq())},[]);let[e,t]=(0,y.useState)([]),[n,r]=(0,y.useState)(!1),[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(null),[c,l]=(0,y.useState)(null),u=e.find(e=>e.info.uuid===c)??null,[d,f]=(0,y.useState)(),[p,m]=(0,y.useState)(),[h,g]=(0,y.useState)(),[_,v]=(0,y.useState)(null),[b,x]=(0,y.useState)(null),[S,ee]=(0,y.useState)(null),C=(0,y.useRef)(null),te=(0,y.useRef)(null),ne=(0,y.useRef)(null),re=async()=>{if(!(!u||n)){f((await u.provider.request({method:`eth_requestAccounts`}))?.[0]??void 0);try{nJ(await u.provider.request({method:`eth_chainId`}),m,g)}catch{m(void 0),g(void 0)}}},ie=async()=>{if(!(!d||p==null)){try{await rJ(`/api/connection`,`POST`,[d,p])}catch{return}r(!0)}},ae=async()=>{if(!u||!o)return;let{id:e,signType:t,request:n}=o,r=n.address,i=n.message;try{let n;switch(t){case`PersonalSign`:n=await u.provider.request({method:`personal_sign`,params:[i,r]});break;case`SignTypedDataV4`:n=await u.provider.request({method:`eth_signTypedData_v4`,params:[r,i]});break;default:throw Error(`Unsupported signType: ${t}`)}await rJ(`/api/signing/response`,`POST`,{id:e,signature:n,error:null}),ee(n),s(null)}catch(t){let n=typeof t==`object`&&t&&`message`in t&&typeof t.message==`string`?t.message:String(t);try{await rJ(`/api/signing/response`,`POST`,{id:e,signature:null,error:n})}catch{}ee(null),s(null)}},oe=async()=>{if(!u||!i?.request)return;let e=Kv({transport:Jv(u.provider),chain:h});try{let{from:t,input:n,to:r,...a}=i.request,o=await e.sendTransaction({...a,account:t||(await e.getAddresses())[0],...n?{data:n}:{},...r?{to:r}:{},chain:h});x(o),await rJ(`/api/transaction/response`,`POST`,{id:i.id,hash:o,error:null}),v(await wv(e,{hash:o}))}catch(e){let t=typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?e.message:String(e);console.error(`send failed:`,t);try{await rJ(`/api/transaction/response`,`POST`,{id:i.id,hash:null,error:t})}catch{}}},se=(0,y.useCallback)(()=>{C.current&&=(window.clearInterval(C.current),null),te.current&&=(window.clearInterval(te.current),null),a(null),s(null),x(null),v(null),f(void 0),m(void 0),g(void 0),r(!1),rJ(`/api/connection`,`POST`,null)},[]);return(0,y.useEffect)(()=>{ne.current&&c&&ne.current!==c&&se(),ne.current=c},[c,se]),(0,y.useEffect)(()=>{e.length===1&&!u&&l(e[0].info.uuid)},[e,u]),(0,y.useEffect)(()=>{let e=e=>{let{info:n,provider:r}=e.detail;t(e=>e.some(e=>e.info.uuid===n.uuid)?e:[...e,{info:n,provider:r}])};return window.addEventListener(`eip6963:announceProvider`,e),window.dispatchEvent(new Event(`eip6963:requestProvider`)),()=>window.removeEventListener(`eip6963:announceProvider`,e)},[]),(0,y.useEffect)(()=>{if(!u)return;let e=e=>{n||f(e[0]??void 0)},t=e=>{n||nJ(e,m,g)};return u.provider.on?.(`accountsChanged`,e),u.provider.on?.(`chainChanged`,t),()=>{u.provider.removeListener?.(`accountsChanged`,e),u.provider.removeListener?.(`chainChanged`,t)}},[u,n]),(0,y.useEffect)(()=>{if(!n||i||o)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await rJ(`/api/transaction/request`);oJ(n)&&(window.clearInterval(t),e&&a(n.data))}catch{}},1e3);return C.current=t,()=>{e=!1,window.clearInterval(t),C.current===t&&(C.current=null)}},[n,i,o]),(0,y.useEffect)(()=>{if(!n||o||i)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await rJ(`/api/signing/request`);oJ(n)&&(window.clearInterval(t),e&&s(n.data))}catch{}},1e3);return te.current=t,()=>{e=!1,window.clearInterval(t),te.current===t&&(te.current=null)}},[n,o,i]),(0,$.jsx)(`div`,{className:`wrapper`,children:(0,$.jsxs)(`div`,{className:`container`,children:[(0,$.jsx)(`div`,{className:`notice`,children:`Browser wallet is still in early development. Use with caution!`}),(0,$.jsx)(`img`,{className:`banner`,src:`banner.png`,alt:`Foundry Browser Wallet`}),e.length>1&&(0,$.jsx)(`div`,{className:`wallet-selector`,children:(0,$.jsx)(`label`,{children:(0,$.jsxs)(`select`,{value:c??``,onChange:e=>l(e.target.value||null),disabled:n,children:[(0,$.jsx)(`option`,{value:``,disabled:!0,children:`Select wallet…`}),e.map(({info:e})=>(0,$.jsxs)(`option`,{value:e.uuid,children:[e.name,` (`,e.rdns,`)`]},e.uuid))]})})}),e.length===0&&(0,$.jsx)(`p`,{children:`No wallets found.`}),u&&!d&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-connect`,onClick:re,disabled:n,children:`Connect Wallet`}),u&&d&&!n&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-confirm`,onClick:ie,disabled:!d||p==null,children:`Confirm Connection`}),u&&d&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Connected`}),(0,$.jsx)(`pre`,{className:`box`,children:`\ account: ${d} chain: ${h?`${h.name} (${p})`:p??`unknown`} -rpc: ${h?.rpcUrls?.default?.http?.[0]??h?.rpcUrls?.public?.http?.[0]??`unknown`}`})]}),u&&d&&n&&!i&&!o&&!b&&!S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction To Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:`No pending transaction or signing request`})})]}),u&&d&&n&&!b&&i&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction to Sign & Send`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:UV(i.request)})}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ae,children:`Sign & Send`})]}),u&&d&&n&&!i&&o&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Message / Data to Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:WV(o.request)})}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ie,children:`Sign`})]}),u&&d&&b&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction Hash`}),(0,$.jsx)(`pre`,{className:`box`,children:b}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Receipt`}),(0,$.jsx)(`pre`,{className:`box`,children:_?UV(_):`Waiting for receipt...`})]})]}),u&&d&&n&&S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Signature Result`}),(0,$.jsx)(`pre`,{className:`box`,children:S})]})]})})}var JV=document.getElementById(`root`);if(JV)(0,v.createRoot)(JV).render((0,$.jsx)(y.StrictMode,{children:(0,$.jsx)(qV,{})}));else throw Error(`Root element with id "root" not found`); \ No newline at end of file +rpc: ${h?.rpcUrls?.default?.http?.[0]??h?.rpcUrls?.public?.http?.[0]??`unknown`}`})]}),u&&d&&n&&!i&&!o&&!b&&!S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction To Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:`No pending transaction or signing request`})})]}),u&&d&&n&&!b&&i&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction to Sign & Send`}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:oe,children:`Sign & Send`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:iJ(i.request)})})]}),u&&d&&n&&!i&&o&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Message / Data to Sign`}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ae,children:`Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:aJ(o.request)})})]}),u&&d&&b&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction Hash`}),(0,$.jsx)(`pre`,{className:`box`,children:b}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Receipt`}),(0,$.jsx)(`pre`,{className:`box`,children:_?iJ(_):`Waiting for receipt...`})]})]}),u&&d&&n&&S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Signature Result`}),(0,$.jsx)(`pre`,{className:`box`,children:S})]})]})})}var lJ=document.getElementById(`root`);if(lJ)(0,v.createRoot)(lJ).render((0,$.jsx)(y.StrictMode,{children:(0,$.jsx)(cJ,{})}));else throw Error(`Root element with id "root" not found`); \ No newline at end of file diff --git a/crates/wallets/src/wallet_browser/app/assets/styles.css b/crates/wallets/src/wallet_browser/app/assets/styles.css index b2c9cd369d41f..3e00c2bb172d8 100644 --- a/crates/wallets/src/wallet_browser/app/assets/styles.css +++ b/crates/wallets/src/wallet_browser/app/assets/styles.css @@ -1,2 +1,2 @@ -*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html,body,#root{color:#f8f8f8;background-color:#13151b;width:100%;height:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}button{color:#f8f8f8;cursor:pointer;background-color:#3a3f51;border:1px solid #e1e4e8;border-radius:4px;padding:8px 12px}button:hover{background-color:#50566e}select{color:#f8f8f8;cursor:pointer;background-color:#1e2026;border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px}option{color:#f8f8f8;background-color:#1e2026}pre{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere}.wrapper{flex-direction:column;justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex}.container{background-color:#3b3b3b;border-radius:8px;flex-direction:column;align-items:flex-start;max-width:600px;padding:16px;display:flex}.notice{color:#333;text-align:center;background-color:#fc0;border-radius:8px;width:100%;margin-bottom:16px;padding:8px;font-size:13px;font-weight:700}.banner{border-radius:8px;width:100%;height:auto}.wallet-selector,.wallet-connect,.wallet-send,.wallet-confirm{align-self:center;margin-top:16px}.title,.section-title{color:#f8f8f8}.title{margin-bottom:24px;font-size:36px}.section-title{margin-bottom:16px;font-size:24px}.box{border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px 12px;font-size:13px} +*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html,body,#root{color:#f8f8f8;background-color:#13151b;width:100%;height:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}button{color:#f8f8f8;cursor:pointer;background-color:#3a3f51;border:1px solid #e1e4e8;border-radius:4px;padding:8px 12px}button:hover{background-color:#50566e}select{color:#f8f8f8;cursor:pointer;background-color:#1e2026;border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px}option{color:#f8f8f8;background-color:#1e2026}pre{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere}.wrapper{flex-direction:column;justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex}.container{background-color:#3b3b3b;border-radius:8px;flex-direction:column;align-items:flex-start;max-width:600px;padding:16px;display:flex}.notice{color:#333;text-align:center;background-color:#fc0;border-radius:8px;width:100%;margin-bottom:16px;padding:8px;font-size:13px;font-weight:700}.banner{border-radius:8px;width:100%;height:auto}.wallet-selector,.wallet-connect,.wallet-send,.wallet-confirm{align-self:center;margin-top:16px}.wallet-send{margin-bottom:16px}.title,.section-title{color:#f8f8f8}.title{margin-bottom:24px;font-size:36px}.section-title{margin-bottom:16px;font-size:24px}.box{border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px 12px;font-size:13px} /*$vite$:1*/ \ No newline at end of file diff --git a/crates/wallets/src/wallet_browser/opts.rs b/crates/wallets/src/wallet_browser/opts.rs index f175a887df475..b8a30727115a5 100644 --- a/crates/wallets/src/wallet_browser/opts.rs +++ b/crates/wallets/src/wallet_browser/opts.rs @@ -9,10 +9,10 @@ use crate::wallet_browser::signer::BrowserSigner; /// Browser wallet options #[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Browser wallet options")] +#[command(next_help_heading = "Wallet options - browser wallet")] pub struct BrowserWalletOpts { /// Use a browser wallet. - #[arg(long, help_heading = "")] + #[arg(long)] pub browser: bool, /// Port for the browser wallet server. diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 62770e8a944c9..5a7ad7fe4bee9 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -3,7 +3,7 @@ use std::{ time::{Duration, Instant}, }; -use alloy_network::{Ethereum, Network, TransactionBuilder}; +use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, B256, ChainId}; use alloy_signer::Result; use tokio::sync::Mutex; @@ -15,7 +15,7 @@ use crate::wallet_browser::{ }; #[derive(Clone, Debug)] -pub struct BrowserSigner { +pub struct BrowserSigner { server: Arc>>, address: Address, chain_id: ChainId, diff --git a/crates/wallets/src/wallet_multi/mod.rs b/crates/wallets/src/wallet_multi/mod.rs index 03d5855ea873e..3ad8e9edbc70d 100644 --- a/crates/wallets/src/wallet_multi/mod.rs +++ b/crates/wallets/src/wallet_multi/mod.rs @@ -4,7 +4,7 @@ use crate::{ utils, wallet_browser::signer::BrowserSigner, }; -use alloy_network::{Ethereum, Network}; +use alloy_network::Network; use alloy_primitives::map::AddressHashMap; use alloy_signer::Signer; use clap::Parser; @@ -15,35 +15,19 @@ use serde::Serialize; use std::path::PathBuf; /// Container for multiple wallets. -#[derive(Debug)] -pub struct MultiWallet { +#[derive(Debug, Default)] +pub struct MultiWallet { /// Vector of wallets that require an action to be unlocked. /// Those are lazily unlocked on the first access of the signers. pending_signers: Vec, /// Contains unlocked signers. signers: AddressHashMap, - /// Browser signer - browser: Option>, } -impl Default for MultiWallet { - fn default() -> Self { - Self { - pending_signers: Default::default(), - signers: Default::default(), - browser: Default::default(), - } - } -} - -impl MultiWallet { - pub fn new( - pending_signers: Vec, - signers: Vec, - browser: Option>, - ) -> Self { +impl MultiWallet { + pub fn new(pending_signers: Vec, signers: Vec) -> Self { let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); - Self { pending_signers, signers, browser } + Self { pending_signers, signers } } fn maybe_unlock_pending(&mut self) -> Result<()> { @@ -54,18 +38,14 @@ impl MultiWallet { Ok(()) } - pub fn signers( - &mut self, - ) -> Result<(&AddressHashMap, Option<&BrowserSigner>)> { + pub fn signers(&mut self) -> Result<&AddressHashMap> { self.maybe_unlock_pending()?; - Ok((&self.signers, self.browser.as_ref())) + Ok(&self.signers) } - pub fn into_signers( - mut self, - ) -> Result<(AddressHashMap, Option>)> { + pub fn into_signers(mut self) -> Result> { self.maybe_unlock_pending()?; - Ok((self.signers, self.browser)) + Ok(self.signers) } pub fn add_signer(&mut self, signer: WalletSigner) { @@ -260,10 +240,9 @@ pub struct MultiWalletOpts { impl MultiWalletOpts { /// Returns [MultiWallet] container configured with provided options. - pub async fn get_multi_wallet(&self) -> Result> { + pub async fn get_multi_wallet(&self) -> Result { let mut pending = Vec::new(); let mut signers: Vec = Vec::new(); - let browser = self.browser_signer().await?; if let Some(ledgers) = self.ledgers().await? { signers.extend(ledgers); @@ -300,7 +279,7 @@ impl MultiWalletOpts { )); } - Ok(MultiWallet::new(pending, signers, browser)) + Ok(MultiWallet::new(pending, signers)) } pub fn private_keys(&self) -> Result>> { diff --git a/deny.toml b/deny.toml index b62a49e7297a9..641b5cd5175bd 100644 --- a/deny.toml +++ b/deny.toml @@ -110,4 +110,5 @@ allow-git = [ "https://github.com/tempoxyz/tempo", # Transitive dependency of Tempo "https://github.com/paradigmxyz/reth", + "https://github.com/stevencartavia/reth", ] diff --git a/flake.lock b/flake.lock index 550ffb9d26ef9..7f516069e6e23 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1773471952, - "narHash": "sha256-kIRggXyT8RzijtfvyRIzj+zIDWM2fnCp8t0X4BkkTVc=", + "lastModified": 1774076307, + "narHash": "sha256-v8axK9HGgVERw9oG3SKdsuE+ri0GPUZDyRBN4GLqQ1c=", "owner": "nix-community", "repo": "fenix", - "rev": "a1b770adbc3f6c27485d03b90462ec414d4e1ce5", + "rev": "556198cc6c69c0a13228a15e33b2360f333b0092", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1773476965, - "narHash": "sha256-Laaj25PvGeoP5SPhMfMGxvWqM6ZjZ6LdUeEhP6b3czY=", + "lastModified": 1773840656, + "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f82ce7af0b79ac154b12e27ed800aeb97413723c", + "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1773326183, - "narHash": "sha256-tj3piRd9RnnP36HwHmQD4O4XZeowsH/rvMeyp9Pmot0=", + "lastModified": 1774036669, + "narHash": "sha256-EWhsBSh/h1VcyLKXuTEyH8lNVB2ktuKVkqx8dkQ6hxk=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "6254616e97f358e67b70dfc0463687f5f7911c1a", + "rev": "0cf3e8a07f0e29825f5db78840e646a4bb519742", "type": "github" }, "original": { diff --git a/foundryup/foundryup b/foundryup/foundryup index 2dcbb5c2f6d0b..5aadb284917bb 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -3,7 +3,7 @@ set -eo pipefail # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -FOUNDRYUP_INSTALLER_VERSION="1.5.0" +FOUNDRYUP_INSTALLER_VERSION="1.6.1" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} @@ -334,7 +334,7 @@ main() { ensure mkdir -p "$FOUNDRY_VERSIONS_DIR" # Download and extract the binaries archive - say "downloading forge and cast for $FOUNDRYUP_TAG version" + say "downloading forge, cast, anvil, and chisel for $FOUNDRYUP_TAG version" if [ "$PLATFORM" = "win32" ]; then tmp="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" tmp="$tmp/foundry.zip" diff --git a/testdata/default/cheats/CurrentFilePath.t.sol b/testdata/default/cheats/CurrentFilePath.t.sol new file mode 100644 index 0000000000000..6628c5f455fc2 --- /dev/null +++ b/testdata/default/cheats/CurrentFilePath.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract CurrentFilePathTest is Test { + function testCurrentFilePath() public { + string memory filePath = vm.currentFilePath(); + // The path should be relative to the project root and point to this test file. + assertEq(normalizePath(filePath), "default/cheats/CurrentFilePath.t.sol"); + } + + function testCurrentFilePathIsNotEmpty() public { + string memory filePath = vm.currentFilePath(); + assertTrue(bytes(filePath).length > 0, "currentFilePath() should not return an empty string"); + } + + function normalizePath(string memory path) internal pure returns (string memory) { + return vm.replace(path, "\\", "/"); + } +} diff --git a/testdata/default/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol index c187e07465bb1..44bba4025e82d 100644 --- a/testdata/default/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -510,4 +510,19 @@ contract WriteJsonTest is Test { vm.removeFile(path); vm.writeJson("{\"a\": 123, \"b\": \"0x000000000000000000000000000000000000bEEF\"}", path); } + + function test_writeJson_createFile() public { + string memory path = "fixtures/Json/write_test_nonexistent.json"; + + // Write to a file that does not exist using the 3-arg overload + vm.writeJson(vm.toString(uint256(99)), path, ".x.y"); + + // Verify the file was created with the correct content + string memory json = vm.readFile(path); + uint256 value = abi.decode(vm.parseJson(json, ".x.y"), (uint256)); + assertEq(value, 99); + + // Clean up + vm.removeFile(path); + } } diff --git a/testdata/utils/Vm.sol b/testdata/utils/Vm.sol index e4b11e8d42e64..d9f9b52821f52 100644 --- a/testdata/utils/Vm.sol +++ b/testdata/utils/Vm.sol @@ -192,6 +192,7 @@ interface Vm { function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); function createWallet(uint256 privateKey) external returns (Wallet memory wallet); function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); + function currentFilePath() external view returns (string memory path); function deal(address account, uint256 newBalance) external; function deleteSnapshot(uint256 snapshotId) external returns (bool success); function deleteSnapshots() external; From e4788762ab2c6fe89d4cd8fe4199b10fee8ff557 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:50:42 +0700 Subject: [PATCH 267/391] Potential fix for pull request finding 'CodeQL / Cleartext logging of sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/fmt/src/ui.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 36407b3f0c9f2..83144a8714870 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1743,8 +1743,8 @@ yParity 0" ); let pretty = pretty_generic_header_response(block.header()); - assert!(pretty.contains("difficulty 1"), "{pretty}"); - assert!(pretty.contains("totalDifficulty 163591"), "{pretty}"); + assert!(pretty.contains("difficulty 1")); + assert!(pretty.contains("totalDifficulty 163591")); } #[test] From 363b5b008e823cc7522d0295f72b407772ce7756 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:51:30 +0700 Subject: [PATCH 268/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/forge/src/cmd/install.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index b56d774a2b2ca..26088a5263f0c 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -359,6 +359,20 @@ impl Installer<'_> { } fn remove_nested_git_dirs_inner(root: &Path, dir: &Path) -> Result<()> { + // Ensure we never recurse outside of the original root directory. + // If canonicalization fails or dir is not under root, stop recursing. + let root_canon = match root.canonicalize() { + Ok(p) => p, + Err(_) => return Ok(()), + }; + let dir_canon = match dir.canonicalize() { + Ok(p) => p, + Err(_) => return Ok(()), + }; + if !dir_canon.starts_with(&root_canon) { + return Ok(()); + } + let entries = match std::fs::read_dir(dir) { Ok(entries) => entries, Err(_) => return Ok(()), From 72d68d8329437586dc7f21fc5e7fcc08d4e81fa9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:52:13 +0700 Subject: [PATCH 269/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/forge/tests/cli/install.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index 838d791c5d0e7..ae9582b9b1fa0 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -619,19 +619,19 @@ forgetest!(flaky_install_no_git_cleans_nested_submodules, |prj, cmd| { } // There should be no .git file or directory anywhere under the installed dependency. - fn assert_no_git(dir: &Path) { + fn assert_no_git(dir: &Path, root: &Path) { for entry in fs::read_dir(dir).unwrap() { let entry = entry.unwrap(); let path = entry.path(); if path.file_name() == Some(".git".as_ref()) { panic!("found leftover .git at {}", path.display()); } - if path.is_dir() { - assert_no_git(&path); + if path.is_dir() && path.starts_with(root) { + assert_no_git(&path, root); } } } - assert_no_git(&dep_dir); + assert_no_git(&dep_dir, &dep_dir); }); forgetest_init!(sync_on_forge_update, |prj, cmd| { From 23c90411943f361df75001ca0f14129b7f3f3924 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:56:21 +0700 Subject: [PATCH 270/391] Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/traces/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 2c5965b1141f7..ebdbc6265e1fd 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -375,7 +375,7 @@ impl TraceMode { 3..=4 => std::cmp::max(self, Self::Call), // Enable step recording and state diff recording when verbosity is 5 or higher. // This includes backtraces (JUMP/JUMPDEST steps) and storage changes. - _ => std::cmp::max(self, Self::RecordStateDiff), + _ => if self == Self::Debug { self } else { std::cmp::max(self, Self::RecordStateDiff) }, } } From 786a917874122a5a89848472d58554c776e701f3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:57:23 +0700 Subject: [PATCH 271/391] Update crates/anvil/src/eth/backend/executor.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/anvil/src/eth/backend/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index d583933717567..6ebbd4a786b31 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -228,7 +228,7 @@ where } let receipt = if tx_type == FoundryTxType::Deposit { - let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); + let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce.saturating_sub(1)); let receipt = alloy_consensus::Receipt { status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used: self.gas_used, From 75e51091803fcd419e115df014ba65edcd634fcb Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:21:09 +0700 Subject: [PATCH 272/391] Change branch trigger from 'main' to 'master' (#403) https://github.com/Dargon789/hardhat-project/issues/2073 https://github.com/Dargon789/hardhat-project/pull/2078/commits/a98e69d5803ec208947f592c91749f248e2dd07c Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/Docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 5a2330e7d5d62..7b85ca2ae00c8 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,7 +4,7 @@ on: push: tags: ["*"] branches: - - "main" + - "master" pull_request: branches: ["**"] From 98cd6697e3a2ed41d9cdb7a5a2f117bda69f9c78 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:34:55 +0700 Subject: [PATCH 273/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/util.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 015a127525767..c1f56b38d9d5d 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -4,7 +4,7 @@ use std::{ env, fs::{self, File}, io::{Read, Seek, Write}, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, process::Command, sync::LazyLock, }; @@ -257,7 +257,33 @@ fn copy_dir_filtered_inner( "attempted to access path outside of allowed source base directory", )); } - let dst_path = dst.join(entry.file_name()); + let relative_src_path = canonical_src_path.strip_prefix(base_src).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "failed to derive relative path within source base directory", + ) + })?; + for component in relative_src_path.components() { + match component { + Component::Normal(name) => { + let name = name.to_string_lossy(); + if name.contains("..") || name.contains('/') || name.contains('\\') { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "invalid path component in source entry", + )); + } + } + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + } + } + let dst_path = base_dst.join(relative_src_path); if ty.is_dir() { // Skip build artifact directories at the root level From 0a1121d66a1b62298ac20f739bd7982749ee4962 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:30:54 +0700 Subject: [PATCH 274/391] Create apisec-scan.yml (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#345) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 110: Uncontrolled data used in path expression (#347) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 102: Artifact poisoning (#351) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md (#350) * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 109: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * Potential fix for code scanning alert no. 102: Artifact poisoning (#354) * Potential fix for code scanning alert no. 102: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#357) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * refactor(common): make ProviderBuilder generic over Network #13250 (#361) * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg --------- Signed-off-by: dependabot[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Matt D Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Tim Beiko Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#373) * Potential fix for code scanning alert no. 108: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Forge/master (#376) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar Co-authored-by: googleworkspace-bot * chore(deps): bump the cargo group across 1 directory with 9 updates (#377) Bumps the cargo group with 3 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing), [keccak](https://github.com/RustCrypto/sponges) and [slab](https://github.com/tokio-rs/slab). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `time` from 0.3.41 to 0.3.47 - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.41...v0.3.47) Updates `alloy-dyn-abi` from 1.3.0 to 1.5.7 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.3.0...v1.5.7) Updates `bytes` from 1.10.1 to 1.11.1 - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.1...v1.11.1) Updates `keccak` from 0.1.5 to 0.1.6 - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) Updates `lru` from 0.12.5 to 0.16.3 - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.3) Updates `protobuf` from 3.3.0 to 3.7.2 Updates `ruint` from 1.15.0 to 1.17.2 - [Release notes](https://github.com/recmo/uint/releases) - [Changelog](https://github.com/recmo/uint/blob/main/CHANGELOG.md) - [Commits](https://github.com/recmo/uint/compare/v1.15.0...v1.17.2) Updates `slab` from 0.4.10 to 0.4.12 - [Release notes](https://github.com/tokio-rs/slab/releases) - [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.12) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: time dependency-version: 0.3.47 dependency-type: direct:production dependency-group: cargo - dependency-name: alloy-dyn-abi dependency-version: 1.5.7 dependency-type: direct:production dependency-group: cargo - dependency-name: bytes dependency-version: 1.11.1 dependency-type: direct:production dependency-group: cargo - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect dependency-group: cargo - dependency-name: lru dependency-version: 0.16.3 dependency-type: indirect dependency-group: cargo - dependency-name: protobuf dependency-version: 3.7.2 dependency-type: indirect dependency-group: cargo - dependency-name: ruint dependency-version: 1.17.2 dependency-type: indirect dependency-group: cargo - dependency-name: slab dependency-version: 0.4.12 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create static.yml (#381) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Hardhat project (#389) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot * fix: update gas_params when changing spec via set_spec (#14210) * Change branch trigger from 'main' to 'master' (#403) https://github.com/Dargon789/hardhat-project/issues/2073 https://github.com/Dargon789/hardhat-project/pull/2078/commits/a98e69d5803ec208947f592c91749f248e2dd07c Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat(evm): add Tempo labels/fns/events to `CallTraceDecoder` (#14213) * fix(cast): resolve correct hardfork for historical tx execution (#14207) * fix(cast): resolve correct hardfork for historical tx execution * chore: add TODOs --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * perf(traces): skip precompile addresses in identify_addresses (#14198) * perf(traces): skip precompile addresses in identify_addresses Filter precompile addresses before sending to external identifiers (Sourcify/Etherscan), matching what prefetch_signatures already does. Also cache unknown fetch errors so they aren't re-fetched on subsequent trace arenas, and fix the cache merge logic to never downgrade a successful metadata lookup to None. Fixes #14196 Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * perf(traces): add Tempo precompiles to is_known_precompile Extend is_known_precompile to also cover Tempo precompile addresses and TIP20 fee token addresses. These are system contracts that will never resolve on Sourcify/Etherscan and should be skipped during external identification. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * refactor(traces): make is_known_precompile chain-aware Thread chain_id through CallTraceDecoder so is_known_precompile can gate network-specific precompiles by chain. Tempo precompiles and TIP20 tokens now only match on Tempo chains (4217, moderato, testnet), not on Ethereum mainnet or other chains. Add with_chain_id to CallTraceDecoderBuilder and wire it up in forge script, forge test, cast, and chisel. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * feat(traces): add Celo transfer precompile to is_known_precompile Add CELO_TRANSFER (0x...fd) to foundry-evm-core precompiles and gate it on Celo/CeloSepolia chain IDs in is_known_precompile. Optimism has no unique precompile addresses beyond P256VERIFY (0x100) which is already covered by the standard EVM precompile list. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * perf(traces): use raw chain ID matches instead of config parsing Replace foundry_config::Chain::from_id() calls with direct integer matches in is_known_precompile to avoid config parsing overhead on every trace node. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * refactor(traces): revert to Chain::from_id for readability Chain::from_id() is a cheap enum lookup, not a config parse. Revert to the more readable/maintainable approach. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * chore(traces): import Chain and NamedChain at top level Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 * chore: fix nightly rustfmt formatting Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d6da9-deee-77ac-a6fb-263872687ac6 --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(script): streamline `EvmOpts` resolution (#14217) * feat(evm): add `OpEvmNetwork` (#14214) fix(cast): decouple provider network from EVM network in `cast run` * feat(cli): `--tempo.fee-token` token id parsing (#14219) * fix(evm): use `AnyNetwork` for fork backend (#14220) fix(evm): use `AnyNetwork` for fork backend RPC layer * feat(forge): propagate `fee_token` to `deploy_tokens` in `CreateArgs::deploy` (#14221) * fix(evm): proper `fee_token` handling in `InspectorHandler` & `Cheatcodes` (#14225) * feat(cast): full `cast keychain` suite (#14222) * feat(evm): `FromAnyRpcTransaction` helper trait (#14223) * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: googleworkspace-bot Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer --- .cargo/config.toml | 6 +- .circleci/cargo.yml | 32 + .circleci/ci-web3-gamefi.yml | 26 + .circleci/ci.yml | 31 + .circleci/ci_cargo.yml | 37 + .circleci/ci_deploy.yml | 34 + .circleci/ci_v1.yml | 31 + .circleci/config.yml | 32 + .circleci/dev_stage.yml | 70 ++ .circleci/web3_defi_gamefi.yml | 26 + .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 + .deps/remix-tests/remix_tests.sol | 225 +++++ .github/ISSUE_TEMPLATE/bug_report.md | 39 + .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/workflows/Docker.yml | 62 ++ .github/workflows/apisec-scan.yml | 29 + .github/workflows/codeql.yml | 92 ++ .github/workflows/deploy.yml | 27 + .github/workflows/docker.yml | 62 ++ .github/workflows/google.yml | 117 +++ .github/workflows/npm.yml | 37 + .github/workflows/snyk-container.yml | 55 + .github/workflows/static.yml | 43 + .gitmodules | 6 + Cargo.lock | 7 + Cargo.toml | 2 + benches/LATEST.md | 71 +- benches/src/lib.rs | 16 +- counter/.github/workflows/test.yml | 43 + counter/.gitignore | 14 + counter/README.md | 66 ++ counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 + counter/src/Counter.sol | 14 + counter/test/Counter.t.sol | 24 + crates/anvil/src/config.rs | 22 +- crates/cast/src/cmd/call.rs | 12 +- crates/cast/src/cmd/keychain.rs | 544 ++++++++-- crates/cast/src/cmd/run.rs | 23 +- crates/cast/src/debug.rs | 3 +- crates/cast/tests/cli/main.rs | 106 +- .../cheatcodes/assets/cheatcodes.schema.json | 2 +- crates/cheatcodes/spec/src/lib.rs | 4 +- crates/cheatcodes/src/config.rs | 16 +- crates/cheatcodes/src/inspector.rs | 26 +- crates/chisel/src/dispatcher.rs | 9 +- crates/chisel/src/executor.rs | 1 + crates/cli/src/opts/tempo.rs | 25 +- crates/cli/src/utils/mod.rs | 3 + crates/cli/src/utils/suggestions.rs | 3 +- crates/cli/src/utils/tempo.rs | 15 + crates/common/Cargo.toml | 3 + crates/common/src/contracts.rs | 2 +- crates/common/src/transactions/builder.rs | 16 + crates/common/src/transactions/receipt.rs | 7 + crates/config/Cargo.toml | 4 + crates/config/assets/config.schema.json | 6 + crates/config/spec/Cargo.toml | 28 + crates/config/spec/src/lib.rs | 64 ++ crates/config/src/lib.rs | 62 +- crates/doc/src/parser/comment.rs | 6 + crates/doc/src/writer/buf_writer.rs | 20 +- crates/evm/core/Cargo.toml | 5 +- crates/evm/core/src/backend/mod.rs | 78 +- crates/evm/core/src/env.rs | 271 ++++- crates/evm/core/src/evm.rs | 28 +- crates/evm/core/src/precompiles.rs | 5 + crates/evm/evm/src/executors/builder.rs | 18 +- crates/evm/evm/src/executors/trace.rs | 2 +- crates/evm/hardforks/Cargo.toml | 1 + crates/evm/hardforks/src/lib.rs | 70 +- crates/evm/traces/Cargo.toml | 3 + crates/evm/traces/src/decoder/mod.rs | 176 +++- crates/evm/traces/src/decoder/precompiles.rs | 40 +- crates/evm/traces/src/identifier/external.rs | 22 +- crates/forge/Cargo.toml | 1 + crates/forge/src/cmd/config.rs | 2 +- crates/forge/src/cmd/create.rs | 12 +- crates/forge/src/cmd/test/mod.rs | 17 +- crates/forge/src/multi_runner.rs | 1 + crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 +++ crates/lint/src/sol/gas/keccak.rs | 98 +- crates/script/src/execute.rs | 10 +- crates/script/src/lib.rs | 68 +- crates/script/src/runner.rs | 29 +- crates/test-utils/src/rpc.rs | 1 + crates/test-utils/src/script.rs | 29 +- crates/test-utils/src/util.rs | 104 +- crates/verify/src/utils.rs | 2 +- npm/package.json | 2 +- npm/scripts/stage-from-artifact.mjs | 28 +- npm/src/const.mjs | 30 +- sleep.json | 955 ++++++++++++++++++ testdata/default/repros/Issue14212.t.sol | 56 + testdata/foundry.toml | 1 + 100 files changed, 4379 insertions(+), 427 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_deploy.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/config.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .github/workflows/static.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/cli/src/utils/tempo.rs create mode 100644 crates/config/assets/config.schema.json create mode 100644 crates/config/spec/Cargo.toml create mode 100644 crates/config/spec/src/lib.rs create mode 100644 crates/lint/src/linter.rs create mode 100644 sleep.json create mode 100644 testdata/default/repros/Issue14212.t.sol diff --git a/.cargo/config.toml b/.cargo/config.toml index 1ca035a75d78c..ca844fb33e15b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,12 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" bless-lints = "test -p forge --test ui -- --bless" +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" + # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..4168efef0971f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..53a505774ac88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 766beae8cc8d7..96d83592a4cc0 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -233,9 +233,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..0ba82305f82b2 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index f66ee73901ee1..03b5ad35d23ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4975,6 +4975,8 @@ dependencies = [ "itertools 0.14.0", "jiff", "num-format", + "op-alloy-network", + "op-alloy-rpc-types", "path-slash", "regex", "reqwest 0.13.2", @@ -5257,6 +5259,8 @@ dependencies = [ "futures", "itertools 0.14.0", "op-alloy-consensus", + "op-alloy-network", + "op-alloy-rpc-types", "op-revm", "parking_lot", "revm", @@ -5320,6 +5324,7 @@ dependencies = [ name = "foundry-evm-hardforks" version = "1.6.0" dependencies = [ + "alloy-chains", "alloy-hardforks", "alloy-op-hardforks", "alloy-rpc-types", @@ -5371,6 +5376,8 @@ dependencies = [ "serde_json", "solar-compiler", "tempfile", + "tempo-contracts", + "tempo-precompiles", "tokio", "tracing", "yansi", diff --git a/Cargo.toml b/Cargo.toml index 73efb0b87a05a..64a4f6617548c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/cli/", "crates/common/", "crates/config/", + "crates/config/spec/", "crates/debugger/", "crates/doc/", "crates/evm/core/", @@ -297,6 +298,7 @@ foundry-cli-markdown = { path = "crates/cli-markdown" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } +foundry-config-spec = { path = "crates/config/spec" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } foundry-evm-abi = { path = "crates/evm/abi" } diff --git a/benches/LATEST.md b/benches/LATEST.md index 7ea1049a2ac41..00776abc94003 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,73 +1,28 @@ # Foundry Benchmark Results -**Date**: 2025-10-02 12:14:23 +**Date**: 2026-01-27 03:38:34 -## Repositories Tested +## Summary -1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) -4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) - -## Foundry Versions - -- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) -- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) - -## Forge Test - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 3.17 s | 2.94 s | -| solady | 2.28 s | 2.10 s | -| Uniswap-v4-core | 7.27 s | 6.13 s | -| sparkdotfi-spark-psm | 43.04 s | 44.08 s | +Benchmarked 2 Foundry versions across 1 repository. -## Forge Fuzz Test +### Repositories Tested -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------ | ---------- | -| ithacaxyz-account | 3.18 s | 3.02 s | -| solady | 2.39 s | 2.24 s | -| Uniswap-v4-core | 6.84 s | 6.20 s | -| sparkdotfi-spark-psm | 3.07 s | 2.72 s | - -## Forge Test (Isolated) - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| solady | 2.26 s | 2.41 s | -| Uniswap-v4-core | 7.22 s | 7.71 s | -| sparkdotfi-spark-psm | 45.53 s | 50.49 s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -## Forge Build (No Cache) +### Foundry Versions -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 9.16 s | 9.08 s | -| solady | 14.62 s | 14.69 s | -| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | -| sparkdotfi-spark-psm | 13.17 s | 13.14 s | +- **stable**: forge Version: 1.5.0-dev (6e718be 2025-12-07) +- **nightly**: forge Version: 1.5.0-dev (6e718be 2025-12-07) ## Forge Build (With Cache) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 0.156 s | 0.113 s | -| solady | 0.089 s | 0.094 s | -| Uniswap-v4-core | 0.133 s | 0.127 s | -| sparkdotfi-spark-psm | 0.173 s | 0.131 s | - -## Forge Coverage - -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | -------- | ---------- | -| ithacaxyz-account | 14.91 s | 13.34 s | -| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | -| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | +| Repository | stable | nightly | +|------------|----------|----------| +| ithacaxyz-account | 0.345 s | 0.279 s | ## System Information -- **OS**: macos +- **OS**: linux - **CPU**: 8 -- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) +- **Rustc**: rustc 1.93.0 (254b59607 2026-01-19) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index b4b3de2c3ddf3..810980d70ad72 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 4863f5a82296e..03f4fcd05178c 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -37,10 +37,7 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{ - FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag, - spec_id_from_ethereum_hardfork, - }, + hardfork::{FoundryHardfork, OpHardfork}, utils::{ apply_chain_and_block_specific_env_changes, block_env_from_header, get_blob_base_fee_update_fraction, @@ -1269,16 +1266,8 @@ impl NodeConfig { let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { - // Auto-adjust hardfork if not specified, but only if we're forking mainnet. let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; - if alloy_chains::NamedChain::Mainnet == chain_id { - let hardfork: EthereumHardfork = - ethereum_hardfork_from_block_tag(fork_block_number); - - evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); - self.hardfork = Some(FoundryHardfork::Ethereum(hardfork)); - } Some(U256::from(chain_id)) } else { None @@ -1344,6 +1333,15 @@ latest block number: {latest_block}" chain_id }; + // Auto-detect hardfork from chain activation data if not explicitly set. + if self.hardfork.is_none() + && let Some(hardfork) = + FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp()) + { + evm_env.cfg_env.spec = SpecId::from(hardfork); + self.hardfork = Some(hardfork); + } + // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() && let Some(base_fee) = block.header.base_fee_per_gas() diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index df90641982630..6b0b1740da0ba 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -36,7 +36,7 @@ use foundry_config::{ use foundry_evm::{ core::{ FoundryBlock, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, + evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork}, }, executors::TracingExecutor, opts::EvmOpts, @@ -224,7 +224,15 @@ impl CallArgs { if self.tx.tempo.is_tempo() { self.run_with_network::().await } else { - self.run_with_network::().await + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + evm_opts.infer_network_from_fork().await; + + if evm_opts.networks.is_optimism() { + self.run_with_network::().await + } else { + self.run_with_network::().await + } } } diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index 6fdc1b1605606..c6a9646a6b2ab 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -1,10 +1,16 @@ -use alloy_network::TransactionBuilder; -use alloy_primitives::Address; -use alloy_provider::Provider; +use alloy_ens::NameOrAddress; +use alloy_network::EthereumWallet; +use alloy_primitives::{Address, U256, hex, keccak256}; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_signer::Signer; +use alloy_sol_types::{SolCall, sol}; use chrono::DateTime; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_cli::{ + opts::{RpcOpts, TransactionOpts}, + utils::LoadConfig, +}; use foundry_common::{ FoundryTransactionBuilder, provider::ProviderBuilder, @@ -12,13 +18,57 @@ use foundry_common::{ tempo::{self, KeyType, KeysFile, WalletType, read_tempo_keys_file, tempo_keys_path}, }; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; -use tempo_contracts::precompiles::IAccountKeychain::{KeyInfo, SignatureType}; +use tempo_contracts::precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, IAccountKeychain, + IAccountKeychain::{KeyInfo, SignatureType, TokenLimit}, +}; use yansi::Paint; -use crate::{ - cmd::{erc20::build_provider_with_signer, send::cast_send}, - tx::{CastTxSender, SendTxOpts}, -}; +use crate::tx::{CastTxBuilder, CastTxSender, SendTxOpts}; + +// Extended AccountKeychain ABI for functions not yet in the pinned tempo-contracts. +// These types mirror the T3+ precompile interface. +sol! { + #[derive(Debug)] + struct SelectorRule { + bytes4 selector; + address[] recipients; + } + + #[derive(Debug)] + struct CallScope { + address target; + SelectorRule[] selectorRules; + } + + #[derive(Debug)] + struct ExtTokenLimit { + address token; + uint256 amount; + } + + #[derive(Debug)] + struct KeyRestrictions { + uint64 expiry; + bool enforceLimits; + ExtTokenLimit[] limits; + bool allowAnyCalls; + CallScope[] allowedCalls; + } + + function authorizeKeyWithRestrictions( + address keyId, + uint8 signatureType, + KeyRestrictions calldata config + ) external; + + function setAllowedCalls( + address keyId, + CallScope[] calldata scopes + ) external; + + function removeAllowedCalls(address keyId, address target) external; +} /// Tempo keychain management commands. /// @@ -37,6 +87,7 @@ pub enum KeychainSubcommand { }, /// Check on-chain provisioning status of a key via the AccountKeychain precompile. + #[command(visible_alias = "info")] Check { /// The wallet (account) address. wallet_address: Address, @@ -49,31 +100,123 @@ pub enum KeychainSubcommand { }, /// Authorize a new key on-chain via the AccountKeychain precompile. + #[command(visible_alias = "auth")] Authorize { /// The key address to authorize. key_address: Address, /// Signature type: secp256k1, p256, or webauthn. - #[arg(long, default_value = "secp256k1", value_parser = parse_signature_type)] + #[arg(default_value = "secp256k1", value_parser = parse_signature_type)] key_type: SignatureType, /// Expiry timestamp (unix seconds). Defaults to u64::MAX (never expires). - #[arg(long)] - expiry: Option, + #[arg(default_value_t = u64::MAX)] + expiry: u64, /// Enforce spending limits for this key. #[arg(long)] enforce_limits: bool, + /// Spending limit in TOKEN:AMOUNT format. Can be specified multiple times. + #[arg(long = "limit", value_parser = parse_limit)] + limits: Vec, + + /// Call scope restriction in `TARGET[:SELECTORS[@RECIPIENTS]]` format. + /// TARGET alone allows all calls. `TARGET:transfer,approve` restricts to those selectors. + /// `TARGET:transfer@0x123` restricts selector to specific recipients. + #[arg(long = "scope", value_parser = parse_scope)] + scope: Vec, + + /// Call scope restrictions as a JSON array. + /// Format: [{"target":"0x...","selectors":["transfer"]}] or + /// [{"target":"0x...","selectors":[{"selector":"transfer","recipients":["0x..."]}]}] + #[arg(long = "scopes", value_parser = parse_scopes_json, conflicts_with = "scope")] + scopes_json: Option>, + + #[command(flatten)] + tx: TransactionOpts, + #[command(flatten)] send_tx: SendTxOpts, }, /// Revoke an authorized key on-chain via the AccountKeychain precompile. + #[command(visible_alias = "rev")] Revoke { /// The key address to revoke. key_address: Address, + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Query the remaining spending limit for a key on a specific token. + #[command(name = "rl", visible_alias = "remaining-limit")] + RemainingLimit { + /// The wallet (account) address. + wallet_address: Address, + + /// The key address. + key_address: Address, + + /// The token address. + token: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Update the spending limit for a key on a specific token. + #[command(name = "ul", visible_alias = "update-limit")] + UpdateLimit { + /// The key address. + key_address: Address, + + /// The token address. + token: Address, + + /// The new spending limit. + new_limit: U256, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Set allowed call scopes for a key. + #[command(name = "ss", visible_alias = "set-scope")] + SetScope { + /// The key address. + key_address: Address, + + /// Call scope restriction in `TARGET[:SELECTORS[@RECIPIENTS]]` format. + #[arg(long = "scope", required = true, value_parser = parse_scope)] + scope: Vec, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove call scope for a key on a target. + #[command(name = "rs", visible_alias = "remove-scope")] + RemoveScope { + /// The key address. + key_address: Address, + + /// The target address to remove scope for. + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + #[command(flatten)] send_tx: SendTxOpts, }, @@ -112,6 +255,149 @@ fn wallet_type_name(t: &WalletType) -> &'static str { } } +/// Parse a `--limit TOKEN:AMOUNT` flag value. +fn parse_limit(s: &str) -> Result { + let (token_str, amount_str) = s + .split_once(':') + .ok_or_else(|| format!("invalid limit format: {s} (expected TOKEN:AMOUNT)"))?; + let token: Address = + token_str.parse().map_err(|e| format!("invalid token address '{token_str}': {e}"))?; + let amount: U256 = + amount_str.parse().map_err(|e| format!("invalid amount '{amount_str}': {e}"))?; + Ok(TokenLimit { token, amount }) +} + +/// Parse a `--scope TARGET[:SELECTORS[@RECIPIENTS]]` flag value. +/// +/// Formats: +/// - `0xAddr` — allow all calls to target +/// - `0xAddr:transfer,approve` — allow only those selectors (by name or 4-byte hex) +/// - `0xAddr:transfer@0xRecipient` — selector with recipient restriction +fn parse_scope(s: &str) -> Result { + let (target_str, selectors_str) = match s.split_once(':') { + Some((t, sel)) => (t, Some(sel)), + None => (s, None), + }; + + let target: Address = + target_str.parse().map_err(|e| format!("invalid target address '{target_str}': {e}"))?; + + let selector_rules = match selectors_str { + None => vec![], + Some(sel_str) => parse_selector_rules(sel_str)?, + }; + + Ok(CallScope { target, selectorRules: selector_rules }) +} + +/// Parse comma-separated selectors, each optionally with `@recipient1,recipient2,...`. +/// +/// Example: `transfer,approve` or `transfer@0x123` or `0xd09de08a` +fn parse_selector_rules(s: &str) -> Result, String> { + let mut rules = Vec::new(); + + for part in s.split(',') { + let part = part.trim(); + if part.is_empty() { + continue; + } + + let (selector_str, recipients_str) = match part.split_once('@') { + Some((sel, recip)) => (sel, Some(recip)), + None => (part, None), + }; + + let selector = parse_selector_bytes(selector_str)?; + + let recipients = match recipients_str { + None => vec![], + Some(r) => r + .split(',') + .filter(|s| !s.trim().is_empty()) + .map(|addr_str| { + let addr_str = addr_str.trim(); + addr_str + .parse::
() + .map_err(|e| format!("invalid recipient address '{addr_str}': {e}")) + }) + .collect::, _>>()?, + }; + + rules.push(SelectorRule { selector: selector.into(), recipients }); + } + + Ok(rules) +} + +/// Parse a selector string: either a 4-byte hex (`0xd09de08a`) or a function name +/// (computed as the first 4 bytes of keccak256 of `name()`). +fn parse_selector_bytes(s: &str) -> Result<[u8; 4], String> { + let s = s.trim(); + if s.starts_with("0x") || s.starts_with("0X") { + let hex_str = &s[2..]; + if hex_str.len() != 8 { + return Err(format!("hex selector must be 4 bytes (8 hex chars), got: {s}")); + } + let bytes = hex::decode(hex_str).map_err(|e| format!("invalid hex selector '{s}': {e}"))?; + let mut arr = [0u8; 4]; + arr.copy_from_slice(&bytes); + Ok(arr) + } else { + let sig = if s.contains('(') { s.to_string() } else { format!("{s}()") }; + let hash = keccak256(sig.as_bytes()); + let mut arr = [0u8; 4]; + arr.copy_from_slice(&hash[..4]); + Ok(arr) + } +} + +/// Represents a single scope entry in JSON format for `--scopes`. +#[derive(serde::Deserialize)] +struct JsonCallScope { + target: Address, + #[serde(default)] + selectors: Option>, +} + +/// A selector entry can be either a plain string or an object with recipients. +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum JsonSelectorEntry { + Name(String), + WithRecipients { selector: String, recipients: Vec
}, +} + +/// Parse `--scopes` JSON flag value. +fn parse_scopes_json(s: &str) -> Result, String> { + let entries: Vec = + serde_json::from_str(s).map_err(|e| format!("invalid --scopes JSON: {e}"))?; + + let mut scopes = Vec::new(); + for entry in entries { + let selector_rules = match entry.selectors { + None => vec![], + Some(sels) => { + let mut rules = Vec::new(); + for sel_entry in sels { + let (selector_str, recipients) = match sel_entry { + JsonSelectorEntry::Name(name) => (name, vec![]), + JsonSelectorEntry::WithRecipients { selector, recipients } => { + (selector, recipients) + } + }; + let selector = parse_selector_bytes(&selector_str) + .map_err(|e| format!("in --scopes JSON: {e}"))?; + rules.push(SelectorRule { selector: selector.into(), recipients }); + } + rules + } + }; + scopes.push(CallScope { target: entry.target, selectorRules: selector_rules }); + } + + Ok(scopes) +} + impl KeychainSubcommand { pub async fn run(self) -> Result<()> { match self { @@ -120,10 +406,44 @@ impl KeychainSubcommand { Self::Check { wallet_address, key_address, rpc } => { run_check(wallet_address, key_address, rpc).await } - Self::Authorize { key_address, key_type, expiry, enforce_limits, send_tx } => { - run_authorize(key_address, key_type, expiry, enforce_limits, send_tx).await + Self::Authorize { + key_address, + key_type, + expiry, + enforce_limits, + limits, + scope, + scopes_json, + tx, + send_tx, + } => { + let all_scopes = + if let Some(json_scopes) = scopes_json { json_scopes } else { scope }; + run_authorize( + key_address, + key_type, + expiry, + enforce_limits, + limits, + all_scopes, + tx, + send_tx, + ) + .await + } + Self::Revoke { key_address, tx, send_tx } => run_revoke(key_address, tx, send_tx).await, + Self::RemainingLimit { wallet_address, key_address, token, rpc } => { + run_remaining_limit(wallet_address, key_address, token, rpc).await + } + Self::UpdateLimit { key_address, token, new_limit, tx, send_tx } => { + run_update_limit(key_address, token, new_limit, tx, send_tx).await + } + Self::SetScope { key_address, scope, tx, send_tx } => { + run_set_scope(key_address, scope, tx, send_tx).await + } + Self::RemoveScope { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await } - Self::Revoke { key_address, send_tx } => run_revoke(key_address, send_tx).await, } } } @@ -181,7 +501,7 @@ fn run_show(wallet_address: Address) -> Result<()> { Ok(()) } -/// `cast keychain check` — query on-chain key status. +/// `cast keychain check` / `cast keychain info` — query on-chain key status. async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) -> Result<()> { let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -195,7 +515,7 @@ async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) "wallet_address": wallet_address.to_string(), "key_address": key_address.to_string(), "provisioned": provisioned, - "signature_type": signature_type_name(&info.signatureType), + "signatureType": signature_type_name(&info.signatureType), "key_id": info.keyId.to_string(), "expiry": info.expiry, "expiry_human": format_expiry(info.expiry), @@ -245,76 +565,166 @@ async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) Ok(()) } -/// `cast keychain authorize` — authorize a key on-chain. +/// `cast keychain authorize` / `cast keychain auth` — authorize a key on-chain. +#[allow(clippy::too_many_arguments)] async fn run_authorize( key_address: Address, key_type: SignatureType, - expiry: Option, + expiry: u64, enforce_limits: bool, + limits: Vec, + allowed_calls: Vec, + tx_opts: TransactionOpts, send_tx: SendTxOpts, ) -> Result<()> { - let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let enforce = enforce_limits || !limits.is_empty(); + + let calldata = if !allowed_calls.is_empty() { + // Use the T3+ authorizeKey overload with KeyRestrictions when scopes are provided. + let sig_type_u8 = match key_type { + SignatureType::Secp256k1 => 0u8, + SignatureType::P256 => 1u8, + SignatureType::WebAuthn => 2u8, + _ => eyre::bail!("unknown signature type"), + }; + let restrictions = KeyRestrictions { + expiry, + enforceLimits: enforce, + limits: limits + .into_iter() + .map(|l| ExtTokenLimit { token: l.token, amount: l.amount }) + .collect(), + allowAnyCalls: false, + allowedCalls: allowed_calls, + }; + authorizeKeyWithRestrictionsCall { + keyId: key_address, + signatureType: sig_type_u8, + config: restrictions, + } + .abi_encode() + } else { + // Use the legacy authorizeKey when no scopes are needed. + IAccountKeychain::authorizeKeyCall { + keyId: key_address, + signatureType: key_type, + expiry, + enforceLimits: enforce, + limits, + } + .abi_encode() + }; - let config = send_tx.eth.load_config()?; - let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); - let provider = ProviderBuilder::::from_config(&config)?.build()?; + send_keychain_tx(calldata, tx_opts, &send_tx).await +} - let keychain = provider.account_keychain(); - let mut tx = keychain - .authorizeKey(key_address, key_type, expiry.unwrap_or(u64::MAX), enforce_limits, vec![]) - .into_transaction_request(); +/// `cast keychain revoke` / `cast keychain rev` — revoke a key on-chain. +async fn run_revoke( + key_address: Address, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = IAccountKeychain::revokeKeyCall { keyId: key_address }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} - if let Some(ref access_key) = tempo_access_key { - let signer = signer.as_ref().expect("signer required for access key"); - tx.set_from(access_key.wallet_address); - tx.set_key_id(access_key.key_address); +/// `cast keychain rl` — query remaining spending limit. +async fn run_remaining_limit( + wallet_address: Address, + key_address: Address, + token: Address, + rpc: RpcOpts, +) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; - let raw_tx = tx - .sign_with_access_key( - &provider, - signer, - access_key.wallet_address, - access_key.key_address, - access_key.key_authorization.as_ref(), - ) - .await?; + let remaining: U256 = + provider.get_keychain_remaining_limit(wallet_address, key_address, token).await?; - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - let cast = CastTxSender::new(&provider); - cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await?; + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&remaining.to_string())?)?; } else { - let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?); - let provider = build_provider_with_signer::(&send_tx, signer)?; - cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) - .await?; + sh_println!("{remaining}")?; } Ok(()) } -/// `cast keychain revoke` — revoke a key on-chain. -async fn run_revoke(key_address: Address, send_tx: SendTxOpts) -> Result<()> { +/// `cast keychain ul` — update spending limit. +async fn run_update_limit( + key_address: Address, + token: Address, + new_limit: U256, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = IAccountKeychain::updateSpendingLimitCall { + keyId: key_address, + token, + newLimit: new_limit, + } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain ss` — set allowed call scopes. +async fn run_set_scope( + key_address: Address, + scopes: Vec, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = setAllowedCallsCall { keyId: key_address, scopes }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain rs` — remove call scope for a target. +async fn run_remove_scope( + key_address: Address, + target: Address, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = removeAllowedCallsCall { keyId: key_address, target }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// Shared helper to send a keychain precompile transaction. +async fn send_keychain_tx( + calldata: Vec, + mut tx_opts: TransactionOpts, + send_tx: &SendTxOpts, +) -> Result<()> { let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; let config = send_tx.eth.load_config()?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); let provider = ProviderBuilder::::from_config(&config)?.build()?; - let keychain = provider.account_keychain(); - let mut tx = keychain.revokeKey(key_address).into_transaction_request(); + // Inject key_id for correct gas estimation with keychain signature overhead. + if let Some(ref ak) = tempo_access_key { + tx_opts.tempo.key_id = Some(ak.key_address); + } + + let builder = CastTxBuilder::new(&provider, tx_opts, &config) + .await? + .with_to(Some(NameOrAddress::Address(ACCOUNT_KEYCHAIN_ADDRESS))) + .await? + .with_code_sig_and_args(None, Some(hex::encode_prefixed(&calldata)), vec![]) + .await?; - if let Some(ref access_key) = tempo_access_key { + if let Some(ref ak) = tempo_access_key { let signer = signer.as_ref().expect("signer required for access key"); - tx.set_from(access_key.wallet_address); - tx.set_key_id(access_key.key_address); + let from = ak.wallet_address; + let (tx, _) = builder.build(from).await?; let raw_tx = tx .sign_with_access_key( &provider, signer, - access_key.wallet_address, - access_key.key_address, - access_key.key_authorization.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), ) .await?; @@ -322,10 +732,22 @@ async fn run_revoke(key_address: Address, send_tx: SendTxOpts) -> Result<()> { let cast = CastTxSender::new(&provider); cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await?; } else { - let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?); - let provider = build_provider_with_signer::(&send_tx, signer)?; - cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) - .await?; + let signer = match signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; + let from = signer.address(); + let (tx, _) = builder.build(from).await?; + + let wallet = EthereumWallet::from(signer); + let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() + .wallet(wallet) + .connect_provider(&provider); + + let cast = CastTxSender::new(provider); + let pending_tx = cast.send(tx).await?; + let tx_hash = *pending_tx.inner().tx_hash(); + cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await?; } Ok(()) diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 19b3b0daf0566..6a21db65026f6 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -32,9 +32,10 @@ use foundry_config::{ use foundry_evm::{ core::{ FoundryBlock as _, - evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, }, executors::{EvmError, Executor, TracingExecutor}, + hardforks::FoundryHardfork, opts::EvmOpts, traces::{InternalTraceMode, TraceMode, Traces}, }; @@ -139,6 +140,8 @@ impl RunArgs { if evm_opts.networks.is_tempo() { self.run_with_evm::().await + } else if evm_opts.networks.is_optimism() { + self.run_with_evm::().await } else { self.run_with_evm::().await } @@ -209,12 +212,18 @@ impl RunArgs { if let Some(block) = &block { evm_env.block_env = block_env_from_header(block.header()); - // TODO: we need a smarter way to map the block to the corresponding evm_version for - // commonly used chains + // Resolve the correct spec for the block using the same approach as reth: walk + // known chain activation conditions to find the latest active fork. Falls back + // to a blob-gas heuristic for unknown chains. if evm_version.is_none() { - // if the block has the excess_blob_gas field, we assume it's a Cancun block - if block.header().excess_blob_gas().is_some() { - evm_version = Some(EvmVersion::Prague); + if let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp( + evm_env.cfg_env.chain_id, + block.header().timestamp(), + ) { + evm_env.cfg_env.set_spec(hardfork.into()); + } else if block.header().excess_blob_gas().is_some() { + // TODO: add glamsterdam header field checks in the future + evm_version = Some(EvmVersion::Cancun); } } apply_chain_and_block_specific_env_changes::( @@ -242,7 +251,7 @@ impl RunArgs { None, )?; - evm_env.cfg_env.set_spec(executor.spec_id()); + evm_env.cfg_env.set_spec_and_mainnet_gas_params(executor.spec_id()); // Set the state to the moment right before the transaction if !self.quick { diff --git a/crates/cast/src/debug.rs b/crates/cast/src/debug.rs index 9af69a4167e38..823dedde99059 100644 --- a/crates/cast/src/debug.rs +++ b/crates/cast/src/debug.rs @@ -56,7 +56,8 @@ pub(crate) async fn handle_traces( let mut builder = CallTraceDecoderBuilder::new() .with_labels(labels.chain(config_labels)) .with_signature_identifier(SignaturesIdentifier::from_config(config)?) - .with_label_disabled(disable_label); + .with_label_disabled(disable_label) + .with_chain_id(Some(chain.id())); let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?; if let Some(contracts) = &known_contracts { builder = builder.with_known_contracts(contracts); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index a04ee9a6d9e45..f76a33eac5c3d 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1568,6 +1568,27 @@ Transaction successfully executed. "#]]); }); +// Regression test: pre-Berlin tx (block 12243999) must use Istanbul gas costs. +// Without correct hardfork resolution, this tx OOGs under Prague/Berlin cold SLOAD costs. +casttest!(run_pre_berlin_tx_uses_correct_spec, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + cmd.args([ + "run", + "-v", + "0xbb4dece05b8d41a2f79475f76daccf7abdd816f6813897cd02ef8509205ebecb", + "--quick", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +... +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that `cast --to-base` commands are working correctly. casttest!(to_base, |_prj, cmd| { let values = [ @@ -3075,7 +3096,7 @@ Executing previous transactions from the block. Traces: [33841] FiatTokenProxy::fallback(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) ├─ [26673] FiatTokenV2_2::approve(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) [delegatecall] - │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, value: 164054805 [1.64e8]) + │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, amount: 164054805 [1.64e8]) │ └─ ← [Return] true └─ ← [Return] true ... @@ -3083,6 +3104,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { @@ -3825,9 +3874,9 @@ Traces: │ └─ ← [Return] 0x0000000000000000000000000b55b053230e4effb6609de652fca73fd1c29804 ├─ [9750] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [staticcall] │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [delegatecall] - │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 - │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 - ├─ [65442] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::fulfillBasicOrder_efficient_6GL6yc() + │ │ └─ ← [Return] 62393 [6.239e4] + │ └─ ← [Return] 62393 [6.239e4] + ├─ [65442] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::selfCallPayVerifyCall537021665() │ ├─ [25070] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] │ │ ├─ [22067] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] │ │ │ ├─ [2369] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::pauseFlag() [staticcall] @@ -3846,22 +3895,22 @@ Traces: │ │ └─ ← [Return] │ ├─ [3250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] │ │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] - │ │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b - │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b + │ │ │ └─ ← [Return] 38539 [3.853e4] + │ │ └─ ← [Return] 38539 [3.853e4] │ ├─ [16411] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) │ │ ├─ [15711] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall] │ │ │ ├─ [12963] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) │ │ │ │ ├─ [12263] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) [delegatecall] - │ │ │ │ │ ├─ emit Transfer(param0: 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E, param1: 0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, param2: 1551) - │ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 - │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + │ │ │ │ │ ├─ emit Transfer(from: 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E, to: 0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, amount: 1551) + │ │ │ │ │ └─ ← [Return] true + │ │ │ │ └─ ← [Return] true │ │ │ └─ ← [Stop] │ │ └─ ← [Return] │ ├─ [1250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] │ │ ├─ [553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] - │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a - │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a - │ ├─ [5675] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::multicallN2M_001Taw5z() + │ │ │ └─ ← [Return] 40090 [4.009e4] + │ │ └─ ← [Return] 40090 [4.009e4] + │ ├─ [5675] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::selfCallExecutePay1395256087() │ │ ├─ [4148] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) │ │ │ ├─ [3693] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] │ │ │ │ ├─ [435] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback() @@ -4971,3 +5020,36 @@ casttest!(cast_decode_tx_tempo, |_prj, cmd| { casttest!(cast_decode_tx_invalid, |_prj, cmd| { cmd.args(["decode-tx", "0xinvalid"]).assert_failure(); }); + +// Test that `cast run --evm-version` correctly updates gas parameters for historical blocks. +// Mainnet tx 0xb856d9...d05d9647 is a Homestead-era tx (block 1,625,693). +// EXP gas pricing differs between Homestead (10 gas/byte) and Spurious Dragon+ (50 gas/byte). +// Without the fix, `set_spec()` only updated the spec discriminant but not the gas_params table, +// so the executor would use stale (latest) gas pricing even when `--evm-version homestead` is set. +casttest!(run_evm_version_updates_gas_params, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + let tx = "0xb856d9c8dffeaa317d89ed6abba861d007a708c54971da91233abcd2d05d9647"; + + // Run with --evm-version homestead: gas must match on-chain gasUsed (166651). + let homestead_output = cmd + .args(["run", tx, "--quick", "--rpc-url", rpc.as_str(), "--evm-version", "homestead"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!( + homestead_output.contains("Gas used: 166651"), + "expected Homestead gas (166651), got: {homestead_output}" + ); + + // Run with --evm-version spuriousDragon: higher gas due to EXP repricing (50 vs 10 gas/byte). + let sd_output = cmd + .cast_fuse() + .args(["run", tx, "--quick", "--rpc-url", rpc.as_str(), "--evm-version", "spuriousDragon"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!( + sd_output.contains("Gas used: 177241"), + "expected Spurious Dragon gas (177241), got: {sd_output}" + ); +}); diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index c98cfb69357bd..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 29e968aeb5bc6..202b590769857 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -178,7 +178,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 79d7d29869784..3435617cdc45e 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,6 +1,6 @@ use super::Result; use crate::Vm::Rpc; -use alloy_primitives::{U256, map::AddressHashMap}; +use alloy_primitives::{Address, U256, map::AddressHashMap}; use foundry_common::{ContractsByArtifact, fs::normalize_path}; use foundry_compilers::{ArtifactId, ProjectPathsConfig, utils::canonicalize}; use foundry_config::{ @@ -56,6 +56,8 @@ pub struct CheatsConfig { pub seed: Option, /// Whether to allow `expectRevert` to work for internal calls. pub internal_expect_revert: bool, + /// Fee token to use for Tempo transactions. + pub fee_token: Option
, } impl CheatsConfig { @@ -65,6 +67,7 @@ impl CheatsConfig { evm_opts: EvmOpts, available_artifacts: Option, running_artifact: Option, + fee_token: Option
, ) -> Self { let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); @@ -92,12 +95,19 @@ impl CheatsConfig { assertions_revert: config.assertions_revert, seed: config.fuzz.seed, internal_expect_revert: config.allow_internal_expect_revert, + fee_token, } } /// Returns a new `CheatsConfig` configured with the given `Config` and `EvmOpts`. pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self { - Self::new(config, evm_opts, self.available_artifacts.clone(), self.running_artifact.clone()) + Self::new( + config, + evm_opts, + self.available_artifacts.clone(), + self.running_artifact.clone(), + self.fee_token, + ) } /// Attempts to canonicalize (see [std::fs::canonicalize]) the path. @@ -222,6 +232,7 @@ impl Default for CheatsConfig { assertions_revert: true, seed: None, internal_expect_revert: false, + fee_token: None, } } } @@ -237,6 +248,7 @@ mod tests { Default::default(), None, None, + None, ) } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 11b3e4f0d0aab..58d7c924622e9 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -768,7 +768,7 @@ impl Cheatcodes { ) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { - ecx.cfg_mut().set_spec(spec_id); + ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id); } let gas = Gas::new(call.gas_limit); @@ -1025,7 +1025,9 @@ impl Cheatcodes { } tx_req.set_authorization_list(active_delegations); } - + if let Some(fee_token) = self.config.fee_token { + tx_req.set_fee_token(fee_token); + } self.broadcastable_transactions.push_back(BroadcastableTransaction { rpc, transaction: TransactionMaybeSigned::new(tx_req), @@ -1715,7 +1717,7 @@ impl Inspector> for Cheatcode ) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { - ecx.cfg_mut().set_spec(spec_id); + ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id); } let gas = Gas::new(input.gas_limit()); @@ -1792,16 +1794,18 @@ impl Inspector> for Cheatcode let rpc = ecx.db().active_fork_url(); let account = &ecx.journal().evm_state()[&broadcast.new_origin]; + let mut tx_req = TransactionRequestFor::::default() + .with_from(broadcast.new_origin) + .with_kind(TxKind::Create) + .with_value(input.value()) + .with_input(input.init_code()) + .with_nonce(account.info.nonce); + if let Some(fee_token) = self.config.fee_token { + tx_req.set_fee_token(fee_token); + } self.broadcastable_transactions.push_back(BroadcastableTransaction { rpc, - transaction: TransactionMaybeSigned::new( - TransactionRequestFor::::default() - .with_from(broadcast.new_origin) - .with_kind(TxKind::Create) - .with_value(input.value()) - .with_input(input.init_code()) - .with_nonce(account.info.nonce), - ), + transaction: TransactionMaybeSigned::new(tx_req), }); input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index a1497677aee18..411c599aad22b 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -158,17 +158,18 @@ impl ChiselDispatcher { result: &mut ChiselResult, // known_contracts: &ContractsByArtifact, ) -> eyre::Result { + let chain_id = session_config.evm_opts.get_remote_chain_id().await; + let mut decoder = CallTraceDecoderBuilder::new() .with_labels(result.labeled_addresses.clone()) .with_signature_identifier(SignaturesIdentifier::from_config( &session_config.foundry_config, )?) + .with_chain_id(chain_id.map(|c| c.id())) .build(); - let mut identifier = TraceIdentifiers::new().with_external( - &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id().await, - )?; + let mut identifier = + TraceIdentifiers::new().with_external(&session_config.foundry_config, chain_id)?; if !identifier.is_empty() { for (_, trace) in &mut result.traces { decoder.identify(trace, &mut identifier); diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 4aa22ffbf7a63..f5deb369ff963 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -227,6 +227,7 @@ impl SessionSource { self.config.evm_opts.clone(), None, None, + None, ) .into(), ) diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs index a24d764b79f81..a071e6774c21a 100644 --- a/crates/cli/src/opts/tempo.rs +++ b/crates/cli/src/opts/tempo.rs @@ -5,6 +5,8 @@ use clap::Parser; use foundry_common::FoundryTransactionBuilder; use std::str::FromStr; +use crate::utils::parse_fee_token_address; + /// CLI options for Tempo transactions. #[derive(Clone, Debug, Default, Parser)] #[command(next_help_heading = "Tempo")] @@ -16,7 +18,7 @@ pub struct TempoOpts { /// /// If this is not set, the fee token is chosen according to network rules. See the Tempo docs /// for more information. - #[arg(long = "tempo.fee-token")] + #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] pub fee_token: Option
, /// Nonce key for Tempo parallelizable nonces. @@ -135,3 +137,24 @@ impl TempoOpts { fn parse_signature(s: &str) -> Result { Signature::from_str(s).map_err(|e| format!("invalid signature: {e}")) } + +#[cfg(test)] +mod tests { + use super::*; + use foundry_evm::core::tempo::{ALPHA_USD_ADDRESS, BETA_USD_ADDRESS}; + + #[test] + fn parse_fee_token_id() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.fee-token", + "0x20C0000000000000000000000000000000000002", + ]) + .unwrap(); + assert_eq!(opts.fee_token, Some(BETA_USD_ADDRESS),); + + // AlphaUSD token ID is 1u64 + let opts_with_id = TempoOpts::try_parse_from(["", "--tempo.fee-token", "1"]).unwrap(); + assert_eq!(opts_with_id.fee_token, Some(ALPHA_USD_ADDRESS),); + } +} diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 6323288bec2b4..bfd722a5ac104 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -30,6 +30,9 @@ pub use abi::*; mod allocator; pub use allocator::*; +mod tempo; +pub use tempo::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs new file mode 100644 index 0000000000000..2c9e5d9604884 --- /dev/null +++ b/crates/cli/src/utils/tempo.rs @@ -0,0 +1,15 @@ +use std::str::FromStr; + +use alloy_primitives::{Address, hex}; + +/// Parses a fee token address. +pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ + Address::from_str(address_or_id).or_else(|_| Ok(token_id_to_address(address_or_id.parse()?))) +} + +fn token_id_to_address(token_id: u64) -> Address { + let mut address_bytes = [0u8; 20]; + address_bytes[..12].copy_from_slice(&hex!("20C000000000000000000000")); + address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); + Address::from(address_bytes) +} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 1486b8ef43fb2..610b1382156e5 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -45,6 +45,9 @@ alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true +op-alloy-network.workspace = true +op-alloy-rpc-types.workspace = true + revm.workspace = true solar.workspace = true diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 06b2c29945417..f079d0f4fbf3d 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index 938d95e4395b7..935c30f20bfe9 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -7,6 +7,8 @@ use alloy_primitives::{Address, B256, Signature, TxKind, U256}; use alloy_provider::Provider; use alloy_signer::Signer; use eyre::Result; +use op_alloy_network::Optimism; +use op_alloy_rpc_types::OpTransactionRequest; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_primitives::{ TempoSignature, @@ -343,6 +345,20 @@ impl FoundryTransactionBuilder for ::Transact } } +impl FoundryTransactionBuilder for OpTransactionRequest { + fn reset_gas_limit(&mut self) { + self.as_mut().gas = None; + } + + fn authorization_list(&self) -> Option<&Vec> { + self.as_ref().authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.as_mut().authorization_list = Some(authorization_list); + } +} + impl FoundryTransactionBuilder for ::TransactionRequest { fn reset_gas_limit(&mut self) { self.gas = None; diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs index 363eaedca8cea..9ca6cb02b10ee 100644 --- a/crates/common/src/transactions/receipt.rs +++ b/crates/common/src/transactions/receipt.rs @@ -7,6 +7,7 @@ use alloy_provider::{ use alloy_rpc_types::{BlockId, TransactionReceipt}; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +use op_alloy_rpc_types::OpTransactionReceipt; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionReceipt; @@ -22,6 +23,12 @@ impl FoundryReceiptResponse for TransactionReceipt { } } +impl FoundryReceiptResponse for OpTransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.inner.contract_address = Some(contract_address); + } +} + impl FoundryReceiptResponse for TempoTransactionReceipt { fn set_contract_address(&mut self, contract_address: Address) { self.contract_address = Some(contract_address); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 39c281e81be25..4c6978e0d9750 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -49,6 +49,9 @@ walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" @@ -60,3 +63,4 @@ tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..00a381eaf66ff --- /dev/null +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..89d40cf5606ce --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..5a362e963d956 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,64 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use foundry_config::Config; +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if let Ok(old_contents) = fs::read_to_string(file) + && normalize_newlines(&old_contents) == normalize_newlines(contents) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 92156f39a0aa8..e77033c75844a 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -163,6 +163,7 @@ pub use semver; /// /// Note that these behaviors differ from those of [`Config::figment()`]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -2755,7 +2756,7 @@ impl BasicConfig { "\ [profile.{}] {s} -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", +# See more config options: https://getfoundry.sh/config/reference/default-config\n", self.profile )) } @@ -4244,7 +4245,6 @@ mod tests { out = "myout" verbosity = 3 evm_version = 'berlin' - [profile.other] src = "other-src" "#, @@ -4876,28 +4876,48 @@ mod tests { } #[test] - fn test_parse_with_profile() { - let foundry_str = r" - [profile.default] - src = 'src' - out = 'out' - libs = ['lib'] - - # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options - "; - assert_eq!( - parse_with_profile::(foundry_str).unwrap().unwrap(), - ( - Config::DEFAULT_PROFILE, + fn test_extract_basic() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "mysrc" + out = "myout" + verbosity = 3 + evm_version = 'berlin' + [profile.other] + src = "other-src" + "#, + )?; + let loaded = Config::load().unwrap(); + assert_eq!(loaded.evm_version, EvmVersion::Berlin); + let base = loaded.into_basic(); + let default = Config::default(); + assert_eq!( + base, BasicConfig { profile: Config::DEFAULT_PROFILE, - src: "src".into(), - out: "out".into(), - libs: vec!["lib".into()], - remappings: vec![] + src: "mysrc".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings.clone(), } - ) - ); + ); + jail.set_env("FOUNDRY_PROFILE", r"other"); + let base = Config::figment().extract::().unwrap(); + assert_eq!( + base, + BasicConfig { + profile: Config::DEFAULT_PROFILE, + src: "other-src".into(), + out: "myout".into(), + libs: default.libs.clone(), + remappings: default.remappings, + } + ); + Ok(()) + }); } #[test] diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 431bf70df88f5..69f529c3eb6a2 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -155,6 +155,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index a62dff3f21582..e5dfbbaadce25 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,4 +1,4 @@ -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike}; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; use solang_parser::pt::{ EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, @@ -89,17 +89,6 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes dev content to the buffer, handling markdown lists properly. - /// If the content contains markdown lists, it formats them correctly. - /// Otherwise, it writes the content in italics. - pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { - for line in text.lines() { - writeln!(self.buf, "{line}")?; - } - - Ok(()) - } - /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -202,7 +191,8 @@ impl BufWriter { params: &EnumDefinition, comments: &Comments, ) -> fmt::Result { - let comments = comments.include_tags(&[CommentTag::Param]); + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); // There is nothing to write. if comments.is_empty() { @@ -215,7 +205,7 @@ impl BufWriter { self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; - for value in ¶ms.values { + for value in params.values.iter() { let param_name = value.as_ref().map(|v| v.name.clone()); let comment = param_name.as_ref().and_then(|name| { @@ -223,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 7bd5bc525330f..2517893fa32b0 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -23,7 +23,7 @@ foundry-evm-networks.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } -alloy-evm = { workspace = true, features = ["rpc"] } +alloy-evm = { workspace = true, features = ["rpc", "op"] } alloy-genesis.workspace = true alloy-hardforks.workspace = true alloy-json-abi.workspace = true @@ -53,6 +53,8 @@ revm = { workspace = true, features = [ "blst", ] } revm-inspectors.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"] } +op-alloy-network.workspace = true op-revm.workspace = true tempo-revm.workspace = true tempo-alloy.workspace = true @@ -77,4 +79,5 @@ url.workspace = true alloy-op-evm.workspace = true alloy-serde.workspace = true op-alloy-consensus.workspace = true +op-alloy-rpc-types.workspace = true foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index e999bb8a7d78a..e7c1345792aa3 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1,21 +1,22 @@ //! Foundry's main executor backend abstraction and implementation. use crate::{ - FoundryBlock, FoundryInspectorExt, FoundryTransaction, + FoundryBlock, FoundryInspectorExt, FoundryTransaction, FromAnyRpcTransaction, constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, evm::{ - BlockEnvFor, BlockResponseFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, - FoundryEvmFactory, FoundryEvmNetwork, HaltReasonFor, PrecompilesFor, SpecFor, - TransactionResponseFor, TxEnvFor, + BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory, + FoundryEvmNetwork, HaltReasonFor, PrecompilesFor, SpecFor, TxEnvFor, }, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, utils::get_blob_base_fee_update_fraction, }; use alloy_consensus::{BlockHeader, Typed2718}; -use alloy_evm::{Evm, EvmEnv, EvmFactory, FromRecoveredTx}; +use alloy_evm::{Evm, EvmEnv, EvmFactory}; use alloy_genesis::GenesisAccount; -use alloy_network::{BlockResponse, Network, TransactionResponse}; +use alloy_network::{ + AnyNetwork, AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, TransactionResponse, +}; use alloy_primitives::{Address, B256, TxKind, U256, keccak256, uint}; use alloy_rpc_types::BlockNumberOrTag; use eyre::Context; @@ -441,7 +442,7 @@ pub trait DatabaseExt: #[must_use] pub struct Backend { /// The access point for managing forks - forks: MultiFork, BlockEnvFor>, + forks: MultiFork, BlockEnvFor>, // The default in memory db mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with @@ -499,7 +500,7 @@ impl Backend { /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. pub fn spawn(fork: Option) -> eyre::Result { - Self::new(MultiFork::, BlockEnvFor>::spawn(), fork) + Self::new(MultiFork::, BlockEnvFor>::spawn(), fork) } /// Creates a new instance of `Backend` @@ -509,7 +510,7 @@ impl Backend { /// /// Prefer using [`spawn`](Self::spawn) instead. pub fn new( - forks: MultiFork, BlockEnvFor>, + forks: MultiFork, BlockEnvFor>, fork: Option, ) -> eyre::Result { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); @@ -548,7 +549,7 @@ impl Backend { /// as active pub(crate) fn new_with_fork( id: &ForkId, - fork: Fork>, + fork: Fork>, journaled_state: JournaledState, ) -> eyre::Result { let mut backend = Self::spawn(None)?; @@ -613,7 +614,7 @@ impl Backend { &self, ) -> &StateSnapshots< BackendStateSnapshot< - BackendDatabaseSnapshot>, + BackendDatabaseSnapshot>, SpecFor, BlockEnvFor, >, @@ -671,7 +672,7 @@ impl Backend { pub(crate) fn update_fork_db( &self, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork>, + target_fork: &mut Fork>, ) { self.update_fork_db_contracts( self.inner.persistent_accounts.iter().copied(), @@ -685,7 +686,7 @@ impl Backend { &self, accounts: impl IntoIterator, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork>, + target_fork: &mut Fork>, ) { if let Some(db) = self.active_fork_db() { merge_account_data(accounts, db, active_journaled_state, target_fork) @@ -710,22 +711,22 @@ impl Backend { } /// Returns the currently active `Fork`, if any - pub fn active_fork(&self) -> Option<&Fork>> { + pub fn active_fork(&self) -> Option<&Fork>> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork(idx)) } /// Returns the currently active `Fork`, if any - pub fn active_fork_mut(&mut self) -> Option<&mut Fork>> { + pub fn active_fork_mut(&mut self) -> Option<&mut Fork>> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork_mut(idx)) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db(&self) -> Option<&ForkDB>> { + pub fn active_fork_db(&self) -> Option<&ForkDB>> { self.active_fork().map(|f| &f.db) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB>> { + pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB>> { self.active_fork_mut().map(|f| &mut f.db) } @@ -748,7 +749,7 @@ impl Backend { /// Creates a snapshot of the currently active database pub(crate) fn create_db_snapshot( &self, - ) -> BackendDatabaseSnapshot> { + ) -> BackendDatabaseSnapshot> { if let Some((id, idx)) = self.active_fork_ids { let fork = self.inner.get_fork(idx).clone(); let fork_id = self.inner.ensure_fork_id(id).cloned().expect("Exists; qed"); @@ -894,7 +895,7 @@ impl Backend { &self, id: LocalForkId, transaction: B256, - ) -> eyre::Result<(u64, BlockResponseFor)> { + ) -> eyre::Result<(u64, AnyRpcBlock)> { let fork = self.inner.get_fork_by_id(id)?; let tx = fork.backend().get_transaction(transaction)?; @@ -924,7 +925,7 @@ impl Backend { evm_env: EvmEnvFor, tx_hash: B256, journaled_state: &mut JournaledState, - ) -> eyre::Result>> { + ) -> eyre::Result> { trace!(?id, ?tx_hash, "replay until transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -959,7 +960,7 @@ impl Backend { let mut evm = FEN::EvmFactory::default().create_evm(replay_db, evm_env); for tx in &txs_to_replay { - let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); + let tx_env = TxEnvFor::::from_any_rpc_transaction(tx)?; trace!(tx=?tx.tx_hash(), "committing transaction"); evm.transact_commit(tx_env).wrap_err("backend: failed committing transaction")?; } @@ -1352,7 +1353,7 @@ impl DatabaseExt for Backend { let fork = self.inner.get_fork_by_id_mut(id)?; fork.backend().get_transaction(transaction)? }; - let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); + let tx_env = TxEnvFor::::from_any_rpc_transaction(&tx)?; // This is a bit ambiguous because the user wants to transact an arbitrary transaction in // the current context, but we're assuming the user wants to transact the transaction as it @@ -1719,12 +1720,12 @@ pub struct BackendInner { pub created_forks: HashMap, /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling - pub forks: Vec>>>, + pub forks: Vec>>>, /// Contains state snapshots made at a certain point #[allow(clippy::type_complexity)] pub state_snapshots: StateSnapshots< BackendStateSnapshot< - BackendDatabaseSnapshot>, + BackendDatabaseSnapshot>, SpecFor, BlockEnvFor, >, @@ -1809,14 +1810,14 @@ impl BackendInner { /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork(&self, idx: ForkLookupIndex) -> &Fork> { + fn get_fork(&self, idx: ForkLookupIndex) -> &Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_ref().unwrap() } /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork> { + fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_mut().unwrap() } @@ -1826,35 +1827,32 @@ impl BackendInner { fn get_fork_by_id_mut( &mut self, id: LocalForkId, - ) -> eyre::Result<&mut Fork>> { + ) -> eyre::Result<&mut Fork>> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork_mut(idx)) } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id( - &self, - id: LocalForkId, - ) -> eyre::Result<&Fork>> { + fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork>> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork(idx)) } /// Removes the fork - fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork> { + fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].take().unwrap() } - fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork>) { + fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork>) { self.forks[idx] = Some(fork) } /// Returns an iterator over Forks pub fn forks_iter( &self, - ) -> impl Iterator>)> + '_ { + ) -> impl Iterator>)> + '_ { self.issued_local_fork_ids .iter() .map(|(id, fork_id)| (*id, self.get_fork(self.created_forks[fork_id]))) @@ -1863,7 +1861,7 @@ impl BackendInner { /// Returns a mutable iterator over all Forks pub fn forks_iter_mut( &mut self, - ) -> impl Iterator>> + '_ { + ) -> impl Iterator>> + '_ { self.forks.iter_mut().filter_map(|f| f.as_mut()) } @@ -1873,7 +1871,7 @@ impl BackendInner { id: LocalForkId, fork_id: ForkId, idx: ForkLookupIndex, - fork: Fork>, + fork: Fork>, ) { self.created_forks.insert(fork_id.clone(), idx); self.issued_local_fork_ids.insert(id, fork_id); @@ -1885,7 +1883,7 @@ impl BackendInner { &mut self, id: LocalForkId, fork_id: ForkId, - db: ForkDB>, + db: ForkDB>, journaled_state: JournaledState, ) -> ForkLookupIndex { let idx = self.forks.len(); @@ -1901,7 +1899,7 @@ impl BackendInner { &mut self, id: LocalForkId, new_fork_id: ForkId, - backend: SharedBackend>, + backend: SharedBackend>, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1926,7 +1924,7 @@ impl BackendInner { pub fn insert_new_fork( &mut self, fork_id: ForkId, - db: ForkDB>, + db: ForkDB>, journaled_state: JournaledState, ) -> (LocalForkId, ForkLookupIndex) { let idx = self.forks.len(); @@ -2102,7 +2100,7 @@ fn commit_transaction( evm_env: EvmEnvFor, tx_env: TxEnvFor, journaled_state: &mut JournaledState, - fork: &mut Fork>, + fork: &mut Fork>, fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn for<'db> FoundryInspectorExt< diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 27df53343c7ba..bcae56125d818 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -1,10 +1,17 @@ use std::fmt::Debug; +use alloy_consensus::Typed2718; pub use alloy_evm::EvmEnv; +use alloy_evm::FromRecoveredTx; +use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; use alloy_primitives::{Address, B256, Bytes, U256}; +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; use op_revm::{ OpTransaction, - transaction::{OpTxTr, deposit::DEPOSIT_TRANSACTION_TYPE}, + transaction::{ + OpTxTr, + deposit::{DEPOSIT_TRANSACTION_TYPE, DepositTransactionParts}, + }, }; use revm::{ Context, Database, Journal, @@ -574,14 +581,111 @@ impl< } } +/// Trait for converting an [`AnyRpcTransaction`] into a specific `TxEnv`. +/// +/// Implementations extract the inner [`alloy_consensus::TxEnvelope`] via +/// [`as_envelope()`](alloy_network::AnyTxEnvelope::as_envelope) then delegate to +/// [`FromRecoveredTx`]. +pub trait FromAnyRpcTransaction: Sized { + /// Tries to convert an [`AnyRpcTransaction`] into `Self`. + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result; +} + +impl FromAnyRpcTransaction for TxEnv { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + Ok(Self::from_recovered_tx(envelope, tx.from())) + } else { + eyre::bail!("cannot convert unknown transaction type to TxEnv") + } + } +} + +impl FromAnyRpcTransaction for OpTransaction { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + return Ok(Self { + base: TxEnv::from_recovered_tx(envelope, tx.from()), + enveloped_tx: None, + deposit: Default::default(), + }); + } + + // Handle OP deposit transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == DEPOSIT_TX_TYPE_ID + { + let mut fields = unknown.inner.fields.clone(); + fields.insert("from".to_string(), serde_json::to_value(tx.from())?); + let deposit_tx: TxDeposit = fields + .deserialize_into() + .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; + let base = TxEnv::from_recovered_tx(&deposit_tx, tx.from()); + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + return Ok(Self { base, enveloped_tx: None, deposit }); + } + + eyre::bail!("cannot convert unknown transaction type to OpTransaction") + } +} + +impl FromAnyRpcTransaction for TempoTxEnv { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + use alloy_consensus::Transaction as _; + if let Some(envelope) = tx.as_envelope() { + return Ok(TxEnv::from_recovered_tx(envelope, tx.from()).into()); + } + + // Handle Tempo transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == tempo_alloy::primitives::TEMPO_TX_TYPE_ID + { + let base = TxEnv { + tx_type: unknown.ty(), + caller: tx.from(), + gas_limit: unknown.gas_limit(), + gas_price: unknown.max_fee_per_gas(), + gas_priority_fee: unknown.max_priority_fee_per_gas(), + kind: unknown.kind(), + value: unknown.value(), + data: unknown.input().clone(), + nonce: unknown.nonce(), + chain_id: unknown.chain_id(), + access_list: unknown.access_list().cloned().unwrap_or_default(), + ..Default::default() + }; + let fee_token = + unknown.inner.fields.get_deserialized::
("feeToken").and_then(Result::ok); + return Ok(Self { inner: base, fee_token, ..Default::default() }); + } + + eyre::bail!("cannot convert unknown transaction type to TempoTxEnv") + } +} + #[cfg(test)] mod tests { use super::*; + use alloy_consensus::{Sealed, Signed, TxEip1559, transaction::Recovered}; use alloy_evm::{EthEvmFactory, EvmFactory}; + use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; use alloy_op_evm::OpEvmFactory; + use alloy_primitives::Signature; + use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo}; + use alloy_serde::WithOtherFields; use foundry_evm_hardforks::TempoHardfork; + use op_alloy_consensus::{OpTxEnvelope, transaction::OpTransactionInfo}; + use op_alloy_rpc_types::Transaction as OpRpcTransaction; use op_revm::OpSpecId; use revm::database::EmptyDB; + use tempo_alloy::primitives::{ + AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, + transaction::PrimitiveSignature, + }; use tempo_evm::TempoEvmFactory; #[test] @@ -652,4 +756,169 @@ mod tests { let evm_env = evm.ctx().evm_clone(); evm.ctx_mut().set_evm(evm_env); } + + fn make_signed_eip1559() -> Signed { + Signed::new_unchecked( + TxEip1559 { + chain_id: 1, + nonce: 42, + gas_limit: 21001, + to: TxKind::Call(Address::with_last_byte(0xBB)), + value: U256::from(101), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + B256::ZERO, + ) + } + + #[test] + fn from_any_rpc_transaction_for_eth() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + + let any_tx = >::from(rpc_tx); + let tx_env = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + assert_eq!(tx_env.caller, from); + assert_eq!(tx_env.nonce, 42); + assert_eq!(tx_env.gas_limit, 21001); + assert_eq!(tx_env.value, U256::from(101)); + assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); + } + + #[test] + fn from_any_rpc_transaction_for_op() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + let op_tx_env = OpTransaction::::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base, expected_base); + } + + #[test] + fn from_any_rpc_transaction_unknown_envelope_errors() { + let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { + hash: B256::ZERO, + inner: UnknownTypedTransaction { + ty: AnyTxType(0xFF), + fields: Default::default(), + memo: Default::default(), + }, + }); + let from = Address::random(); + let any_tx = AnyRpcTransaction::new(WithOtherFields::new(RpcTransaction { + inner: Recovered::new_unchecked(unknown, from), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + block_timestamp: None, + })); + + let result = TxEnv::from_any_rpc_transaction(&any_tx).unwrap_err(); + assert!(result.to_string().contains("unknown transaction type")); + } + + #[test] + fn from_any_rpc_transaction_for_op_deposit() { + let from = Address::random(); + let source_hash = B256::random(); + let deposit = TxDeposit { + source_hash, + from, + to: TxKind::Call(Address::with_last_byte(0xCC)), + mint: 1111, + value: U256::from(200), + gas_limit: 21000, + is_system_transaction: true, + input: Default::default(), + }; + + // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as AnyRpcTransaction. + let op_rpc_tx = OpRpcTransaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), + OpTransactionInfo::default(), + ); + let json = serde_json::to_value(&op_rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let op_tx_env = OpTransaction::::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base.caller, from); + assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); + assert_eq!(op_tx_env.base.value, U256::from(200)); + assert_eq!(op_tx_env.base.gas_limit, 21000); + assert_eq!(op_tx_env.deposit.source_hash, source_hash); + assert_eq!(op_tx_env.deposit.mint, Some(1111)); + assert!(op_tx_env.deposit.is_system_transaction); + } + + #[test] + fn from_any_rpc_transaction_for_tempo_eth_envelope() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + + let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(tx_env.inner.caller, from); + assert_eq!(tx_env.inner.nonce, 42); + assert_eq!(tx_env.inner.gas_limit, 21001); + assert_eq!(tx_env.inner.value, U256::from(101)); + assert_eq!(tx_env.fee_token, None); + } + + #[test] + fn from_any_rpc_transaction_for_tempo_aa() { + let from = Address::random(); + let fee_token = Some(Address::random()); + let tempo_tx = TempoTransaction { + chain_id: 42431, + nonce: 42, + gas_limit: 424242, + fee_token, + nonce_key: U256::from(4242), + valid_after: Some(1800000000), + ..Default::default() + }; + let aa_signed = AASigned::new_unhashed( + tempo_tx, + TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::new( + U256::ZERO, + U256::ZERO, + false, + ))), + ); + + // Build a concrete Tempo RPC transaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(TempoTxEnvelope::AA(aa_signed), from), + TransactionInfo::default(), + ); + let json = serde_json::to_value(&rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(tx_env.inner.caller, from); + assert_eq!(tx_env.inner.nonce, 42); + assert_eq!(tx_env.inner.gas_limit, 424242); + assert_eq!(tx_env.inner.chain_id, Some(42431)); + assert_eq!(tx_env.fee_token, fee_token); + } } diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs index acf1bcc088519..814f6e58d6fe1 100644 --- a/crates/evm/core/src/evm.rs +++ b/crates/evm/core/src/evm.rs @@ -6,6 +6,7 @@ use std::{ use crate::{ FoundryBlock, FoundryContextExt, FoundryInspectorExt, FoundryTransaction, + FromAnyRpcTransaction, backend::{DatabaseExt, JournaledState}, constants::{CALLER, DEFAULT_CREATE2_DEPLOYER_CODEHASH, TEST_CONTRACT_ADDRESS}, tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, @@ -23,6 +24,7 @@ use alloy_rlp::Decodable; use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; +use op_alloy_network::Optimism; use op_revm::OpHaltReason; use revm::{ Context, @@ -113,6 +115,14 @@ impl FoundryEvmNetwork for TempoEvmNetwork { type EvmFactory = TempoEvmFactory; } +// TODO: use `OpEvmFactory` once the FoundryEvmFactory impl is available for it. +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = EthEvmFactory; +} + /// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. pub type EvmFactoryFor = ::EvmFactory; pub type FoundryContextFor<'db, FEN> = @@ -134,7 +144,7 @@ pub trait FoundryEvmFactory: EvmFactory< Spec: Into + FromEvmVersion + Default + Display + Copy + Unpin + Send + 'static, BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin, - Tx: Clone + Debug + FoundryTransaction + Default + Send + Sync, + Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync, HaltReason: IntoInstructionResult, Precompiles = PrecompilesMap, > + Clone @@ -1119,6 +1129,22 @@ impl<'db, I: FoundryInspectorExt Result, Self::Error> { + self.inner.load_fee_fields(evm)?; + + match self.inspect_run_without_catch_error(evm) { + Ok(output) => Ok(output), + Err(e) => self.catch_error(evm, e), + } + } + /// Delegates to [`TempoEvmHandler::inspect_execution_with`], injecting the CREATE2 factory /// redirect exec loop. AA multi-call dispatch and gas adjustments are handled by the inner /// Tempo handler. diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs index fd569dc30dc07..7ee9c66106de7 100644 --- a/crates/evm/core/src/precompiles.rs +++ b/crates/evm/core/src/precompiles.rs @@ -54,6 +54,11 @@ pub const BLS12_MAP_FP2_TO_G2: Address = address!("0x000000000000000000000000000 /// The P256VERIFY precompile address. pub const P256_VERIFY: Address = address!("0x0000000000000000000000000000000000000100"); +/// The Celo transfer precompile address. +/// +/// See +pub const CELO_TRANSFER: Address = address!("0x00000000000000000000000000000000000000fd"); + /// Precompile addresses. pub const PRECOMPILES: &[Address] = &[ EC_RECOVER, diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 5dd3e550d766b..587d58bd2af74 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -19,8 +19,8 @@ pub struct ExecutorBuilder { stack: InspectorStackBuilder>, /// The gas limit. gas_limit: Option, - /// The spec. - spec: SpecFor, + /// The spec override. When `None`, the spec from `EvmEnv::cfg_env` is preserved. + spec: Option>, legacy_assertions: bool, } @@ -30,7 +30,7 @@ impl Default for ExecutorBuilder { Self { stack: InspectorStackBuilder::new(), gas_limit: None, - spec: Default::default(), + spec: None, legacy_assertions: false, } } @@ -52,10 +52,16 @@ impl ExecutorBuilder { /// Sets the EVM spec to use. #[inline] pub fn spec_id(mut self, spec: SpecFor) -> Self { - self.spec = spec; + self.spec = Some(spec); self } + /// Optionally sets the EVM spec. When `None`, the spec from `EvmEnv::cfg_env` is preserved. + #[inline] + pub fn spec_id_opt(self, spec: Option>) -> Self { + if let Some(spec) = spec { self.spec_id(spec) } else { self } + } + /// Sets the executor gas limit. #[inline] pub fn gas_limit(mut self, gas_limit: u64) -> Self { @@ -86,7 +92,9 @@ impl ExecutorBuilder { stack.gas_price = Some(tx_env.gas_price()); } let gas_limit = gas_limit.unwrap_or(evm_env.block_env.gas_limit()); - evm_env.cfg_env.set_spec(spec); + if let Some(spec) = spec { + evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec); + } Executor::new(db, evm_env, tx_env, stack.build(), gas_limit, legacy_assertions) } } diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 8d296dd1f1a06..54a77399fa2c9 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -37,7 +37,7 @@ impl TracingExecutor { .inspectors(|stack| { stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) }) - .spec_id(evm_spec_id::>(version.unwrap_or_default())) + .spec_id_opt(version.map(evm_spec_id::>)) .build(env.0, env.1, db); // Apply the state overrides. diff --git a/crates/evm/hardforks/Cargo.toml b/crates/evm/hardforks/Cargo.toml index a80a86b1a1e0b..9bf318028487a 100644 --- a/crates/evm/hardforks/Cargo.toml +++ b/crates/evm/hardforks/Cargo.toml @@ -14,6 +14,7 @@ repository.workspace = true workspace = true [dependencies] +alloy-chains.workspace = true alloy-hardforks = { workspace = true, features = ["serde"] } alloy-op-hardforks = { workspace = true, features = ["serde"] } alloy-rpc-types.workspace = true diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs index f85b74ad69048..73cfffe2077eb 100644 --- a/crates/evm/hardforks/src/lib.rs +++ b/crates/evm/hardforks/src/lib.rs @@ -5,6 +5,7 @@ use std::str::FromStr; +use alloy_chains::Chain; use alloy_rpc_types::BlockNumberOrTag; use foundry_compilers::artifacts::EvmVersion; use op_revm::OpSpecId; @@ -98,6 +99,22 @@ impl FoundryHardfork { Self::Tempo(h) => format!("{h}"), } } + + /// Auto-detect the active hardfork for a given chain at a specific timestamp. + /// + /// Tries Ethereum, then Optimism. Returns `None` for unknown chains. + pub fn from_chain_and_timestamp(chain_id: u64, timestamp: u64) -> Option { + let chain = Chain::from_id(chain_id); + if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) { + return Some(Self::Ethereum(fork)); + } + if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) { + return Some(Self::Optimism(fork)); + } + // TODO: add tempo support after https://github.com/tempoxyz/tempo/pull/3514 release + // providing TempoHardfork::from_chain_and_timestamp + None + } } impl From for FoundryHardfork { @@ -155,6 +172,15 @@ impl From for SpecId { } } +impl From for OpSpecId { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork), + _ => Self::default(), + } + } +} + /// Map an `EthereumHardfork` enum into its corresponding `SpecId`. pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { match hardfork { @@ -203,7 +229,7 @@ pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { } /// Trait for converting an [`EvmVersion`] into a network-specific spec type. -pub trait FromEvmVersion { +pub trait FromEvmVersion: From { fn from_evm_version(version: EvmVersion) -> Self; } @@ -313,4 +339,46 @@ mod tests { EthereumHardfork::London ); } + + #[test] + fn test_from_chain_and_timestamp_ethereum_mainnet() { + assert_eq!( + FoundryHardfork::from_chain_and_timestamp(1, 0), + Some(FoundryHardfork::Ethereum(EthereumHardfork::Frontier)) + ); + // Shanghai activated at timestamp 1681338455 on mainnet + assert_eq!( + FoundryHardfork::from_chain_and_timestamp(1, 1_681_338_455), + Some(FoundryHardfork::Ethereum(EthereumHardfork::Shanghai)) + ); + } + + #[test] + fn test_from_chain_and_timestamp_sepolia() { + let sepolia_chain_id = 11155111; + assert!(FoundryHardfork::from_chain_and_timestamp(sepolia_chain_id, u64::MAX).is_some()); + } + + #[test] + fn test_from_chain_and_timestamp_op_mainnet() { + let op_chain_id = 10; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_base() { + let base_chain_id = 8453; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_unknown_chain() { + assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); + } } diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 0d31cfca6a7ae..90d2db724cebc 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -32,6 +32,9 @@ alloy-primitives = { workspace = true, features = [ alloy-sol-types.workspace = true revm-inspectors.workspace = true +tempo-contracts.workspace = true +tempo-precompiles.workspace = true + async-trait.workspace = true eyre.workspace = true futures.workspace = true diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 9ac46918a5372..bf53210aebbb4 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -22,10 +22,19 @@ use foundry_evm_core::{ BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, }, + tempo::{ALPHA_USD_ADDRESS, BETA_USD_ADDRESS, THETA_USD_ADDRESS}, }; use itertools::Itertools; use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; use std::{collections::BTreeMap, sync::OnceLock}; +use tempo_contracts::precompiles::{ + IAccountKeychain, IFeeManager, IStablecoinDEX, ITIP20Factory, ITIP403Registry, IValidatorConfig, +}; +use tempo_precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS, STABLECOIN_DEX_ADDRESS, + TIP_FEE_MANAGER_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, + VALIDATOR_CONFIG_ADDRESS, nonce::INonce, tip20::ITIP20, +}; mod precompiles; @@ -94,6 +103,13 @@ impl CallTraceDecoderBuilder { self } + /// Sets the chain ID for network-specific precompile detection. + #[inline] + pub fn with_chain_id(mut self, chain_id: Option) -> Self { + self.decoder.chain_id = chain_id; + self + } + /// Sets the debug identifier for the decoder. #[inline] pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self { @@ -151,6 +167,9 @@ pub struct CallTraceDecoder { /// Disable showing of labels. pub disable_labels: bool, + + /// The chain ID, used to determine network-specific precompiles. + pub chain_id: Option, } impl CallTraceDecoder { @@ -192,6 +211,18 @@ impl CallTraceDecoder { (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()), (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()), (P256_VERIFY, "P256VERIFY".to_string()), + // Tempo + (TIP_FEE_MANAGER_ADDRESS, "FeeManager".to_string()), + (TIP403_REGISTRY_ADDRESS, "TIP403Registry".to_string()), + (TIP20_FACTORY_ADDRESS, "TIP20Factory".to_string()), + (STABLECOIN_DEX_ADDRESS, "StablecoinDex".to_string()), + (NONCE_PRECOMPILE_ADDRESS, "Nonce".to_string()), + (VALIDATOR_CONFIG_ADDRESS, "ValidatorConfig".to_string()), + (ACCOUNT_KEYCHAIN_ADDRESS, "AccountKeychain".to_string()), + (PATH_USD_ADDRESS, "PathUSD".to_string()), + (ALPHA_USD_ADDRESS, "AlphaUSD".to_string()), + (BETA_USD_ADDRESS, "BetaUSD".to_string()), + (THETA_USD_ADDRESS, "ThetaUSD".to_string()), ]), receive_contracts: Default::default(), fallback_contracts: Default::default(), @@ -200,11 +231,29 @@ impl CallTraceDecoder { functions: console::hh::abi::functions() .into_values() .chain(Vm::abi::functions().into_values()) + // Tempo + .chain(IFeeManager::abi::functions().into_values()) + .chain(ITIP20::abi::functions().into_values()) + .chain(ITIP403Registry::abi::functions().into_values()) + .chain(ITIP20Factory::abi::functions().into_values()) + .chain(IStablecoinDEX::abi::functions().into_values()) + .chain(INonce::abi::functions().into_values()) + .chain(IValidatorConfig::abi::functions().into_values()) + .chain(IAccountKeychain::abi::functions().into_values()) .flatten() .map(|func| (func.selector(), vec![func])) .collect(), events: console::ds::abi::events() .into_values() + // Tempo + .chain(IFeeManager::abi::events().into_values()) + .chain(ITIP20::abi::events().into_values()) + .chain(ITIP403Registry::abi::events().into_values()) + .chain(ITIP20Factory::abi::events().into_values()) + .chain(IStablecoinDEX::abi::events().into_values()) + .chain(INonce::abi::events().into_values()) + .chain(IValidatorConfig::abi::events().into_values()) + .chain(IAccountKeychain::abi::events().into_values()) .flatten() .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) .collect(), @@ -216,6 +265,8 @@ impl CallTraceDecoder { debug_identifier: None, disable_labels: false, + + chain_id: None, } } @@ -249,6 +300,12 @@ impl CallTraceDecoder { identifier: &'a mut impl TraceIdentifier, ) -> Vec> { let nodes = arena.nodes().iter().filter(|node| { + // Skip precompile addresses, they will never resolve externally. + if node.is_precompile() + || precompiles::is_known_precompile(node.trace.address, self.chain_id) + { + return false; + } let address = &node.trace.address; !self.labels.contains_key(address) || !self.contracts.contains_key(address) }); @@ -391,7 +448,7 @@ impl CallTraceDecoder { return DecodedCallTrace { label, ..Default::default() }; } - if let Some(trace) = precompiles::decode(trace, 1) { + if let Some(trace) = precompiles::decode(trace, self.chain_id) { return trace; } @@ -794,7 +851,7 @@ impl CallTraceDecoder { // Ignore known addresses. if n.trace.address == DEFAULT_CREATE2_DEPLOYER || n.is_precompile() - || precompiles::is_known_precompile(n.trace.address, 1) + || precompiles::is_known_precompile(n.trace.address, self.chain_id) { return false; } @@ -1392,4 +1449,119 @@ mod tests { assert_eq!(result, expected, "Output case failed for: {function_signature}"); } } + + // A mock identifier that records which addresses it was asked to identify. + struct RecordingIdentifier { + queried: Vec
, + } + impl TraceIdentifier for RecordingIdentifier { + fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { + self.queried.extend(nodes.iter().map(|n| n.trace.address)); + Vec::new() + } + } + + #[test] + fn test_identify_addresses_skips_evm_precompiles() { + use foundry_evm_core::precompiles::SHA_256; + + let decoder = CallTraceDecoder::new(); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + // Standard EVM precompile flagged by the inspector. + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: SHA_256, + depth: 1, + maybe_precompile: Some(true), + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + // Standard EVM precompile NOT flagged, caught by is_known_precompile. + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: SHA_256, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 2, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + assert_eq!(identifier.queried, vec![regular_addr]); + } + + #[test] + fn test_identify_addresses_skips_tempo_precompiles() { + use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES; + + // Decoder with Tempo chain ID (4217). + let mut decoder = CallTraceDecoder::new().clone(); + decoder.chain_id = Some(4217); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + // Tempo precompile — not flagged by inspector, caught by is_known_precompile + // only when chain_id is a Tempo chain. + let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0]; + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: tempo_precompile, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + // On a Tempo chain, the Tempo precompile should be filtered out. + assert_eq!(identifier.queried, vec![regular_addr]); + } + + #[test] + fn test_identify_addresses_does_not_skip_tempo_precompiles_on_other_chains() { + use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES; + + // Decoder with Ethereum mainnet chain ID (1). + let mut decoder = CallTraceDecoder::new().clone(); + decoder.chain_id = Some(1); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0]; + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: tempo_precompile, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + // On Ethereum, Tempo precompile addresses are regular contracts — should NOT be filtered. + assert_eq!(identifier.queried, vec![regular_addr, tempo_precompile]); + } } diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index fdbe744641cd7..5b0a320477fad 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -1,10 +1,14 @@ use crate::{CallTrace, DecodedCallData}; use alloy_primitives::{Address, B256, U256, hex}; use alloy_sol_types::{SolCall, abi, sol}; -use foundry_evm_core::precompiles::{ - BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, - BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, - MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, +use foundry_config::{Chain, NamedChain}; +use foundry_evm_core::{ + precompiles::{ + BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, CELO_TRANSFER, EC_ADD, EC_MUL, EC_PAIRING, + EC_RECOVER, IDENTITY, MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, + }, + tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS}, }; use itertools::Itertools; use revm_inspectors::tracing::types::DecodedCallTrace; @@ -50,8 +54,9 @@ interface Precompiles { } use Precompiles::*; -pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool { - address[..19].iter().all(|&x| x == 0) +pub(super) fn is_known_precompile(address: Address, chain_id: Option) -> bool { + // Standard EVM precompiles (all chains). + let is_standard = address[..19].iter().all(|&x| x == 0) && matches!( address, EC_RECOVER @@ -72,12 +77,29 @@ pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool { | BLS12_MAP_FP_TO_G1 | BLS12_MAP_FP2_TO_G2 | P256_VERIFY - ) + ); + if is_standard { + return true; + } + // Tempo precompiles and TIP20 fee tokens (only on Tempo chains). + if chain_id.is_some_and(|id| Chain::from_id(id).is_tempo()) + && (TEMPO_PRECOMPILE_ADDRESSES.contains(&address) || TEMPO_TIP20_TOKENS.contains(&address)) + { + return true; + } + // Celo transfer precompile (only on Celo chains). + if chain_id.is_some_and(|id| { + matches!(Chain::from_id(id).named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + }) && address == CELO_TRANSFER + { + return true; + } + false } /// Tries to decode a precompile call. Returns `Some` if successful. -pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option { - if !is_known_precompile(trace.address, _chain_id) { +pub(super) fn decode(trace: &CallTrace, chain_id: Option) -> Option { + if !is_known_precompile(trace.address, chain_id) { return None; } diff --git a/crates/evm/traces/src/identifier/external.rs b/crates/evm/traces/src/identifier/external.rs index e3ca8567cd95e..841c77c33f0de 100644 --- a/crates/evm/traces/src/identifier/external.rs +++ b/crates/evm/traces/src/identifier/external.rs @@ -191,12 +191,20 @@ impl TraceIdentifier for ExternalIdentifier { .map(|metadata| self.identify_from_metadata(address, metadata)); match self.contracts.entry(address) { Entry::Occupied(mut occupied_entry) => { - // Override if: - // - new is from Etherscan and old is not - // - new is Some and old is None, meaning verified only in one source - if !matches!(occupied_entry.get().0, FetcherKind::Etherscan) - || value.1.is_none() - { + let old = occupied_entry.get(); + // Only override when the new result is strictly better: + // - new has metadata and old doesn't, OR + // - both have metadata but new is from Etherscan and old is not. + // Never downgrade a successful lookup to None. + let should_replace = match (&old.1, &value.1) { + (None, Some(_)) => true, + (Some(_), None) => false, + _ => { + matches!(value.0, FetcherKind::Etherscan) + && !matches!(old.0, FetcherKind::Etherscan) + } + }; + if should_replace { occupied_entry.insert(value); } } @@ -317,6 +325,8 @@ impl Stream for ExternalFetcher { } Err(err) => { warn!(target: "evm::traces::external", ?err, "could not get info"); + // Cache the failure so we don't re-fetch on subsequent arenas. + return Poll::Ready(Some((addr, (pin.fetcher.kind(), None)))); } } } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c24dc0ba484d7..457eb1fc3222a 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -61,6 +61,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index 49716146c456b..fac66727c9d99 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -53,8 +53,8 @@ impl ConfigArgs { } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 2d013ef2a6c68..a6337a266c9a8 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -349,7 +349,7 @@ impl CreateArgs { let is_args_empty = args.is_empty(); let mut deployer = - factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { + factory.deploy_tokens(args.clone(), self.tx.tempo.fee_token).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { @@ -681,7 +681,11 @@ impl + Clone> DeploymentTxFactory { pub fn deploy_tokens( self, params: Vec, - ) -> Result, ContractDeploymentError> { + fee_token: Option
, + ) -> Result, ContractDeploymentError> + where + N::TransactionRequest: FoundryTransactionBuilder, + { // Encode the constructor args & concatenate with the bytecode if necessary let data: Bytes = match (self.abi.constructor(), params.is_empty()) { (None, false) => return Err(ContractDeploymentError::ConstructorError), @@ -699,7 +703,9 @@ impl + Clone> DeploymentTxFactory { // create the tx object. Since we're deploying a contract, `to` is `None` let mut tx = N::TransactionRequest::default(); tx.set_input(data); - + if let Some(fee_token) = fee_token { + tx.set_fee_token(fee_token); + } Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index d7902b37efe65..c0a2da8cd528b 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -41,7 +41,8 @@ use foundry_config::{ use foundry_debugger::Debugger; use foundry_evm::{ core::evm::{ - BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor, + BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, SpecFor, TempoEvmNetwork, + TxEnvFor, }, opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, @@ -353,6 +354,17 @@ impl TestArgs { decode_internal, ) .await? + } else if evm_opts.networks.is_optimism() { + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + ) + .await? } else { self.build_and_run_tests::( config, @@ -596,7 +608,8 @@ impl TestArgs { let mut builder = CallTraceDecoderBuilder::new() .with_known_contracts(&known_contracts) .with_label_disabled(self.disable_labels) - .with_verbosity(verbosity); + .with_verbosity(verbosity) + .with_chain_id(remote_chain.map(|c| c.id())); // Signatures are of no value for gas reports. if !self.gas_report { builder = diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 37b058ba1a67e..fd9b4b889c2e2 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -372,6 +372,7 @@ impl TestRunnerConfig { self.evm_opts.clone(), Some(known_contracts), Some(artifact_id.clone()), + None, )); ExecutorBuilder::default() .inspectors(|stack| { diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index cb942510bbb49..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,103 +1,27 @@ use super::AsmKeccak256; use crate::{ - linter::{LateLintPass, LintContext}, + declare_forge_lint, + linter::EarlyLintPass, sol::{Severity, SolLint}, }; -use solar::{ - ast::{self as ast, Span}, - interface::kw, - sema::hir::{self}, -}; +use solar_ast::{Expr, ExprKind}; +use solar_interface::kw; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "use of inefficient hashing mechanism; consider using inline assembly" + "hash using inline assembly to save gas" ); -impl<'hir> LateLintPass<'hir> for AsmKeccak256 { - fn check_stmt( - &mut self, - ctx: &LintContext, - hir: &'hir hir::Hir<'hir>, - stmt: &'hir hir::Stmt<'hir>, - ) { - let check_expr_and_emit_lint = - |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { - if let Some(hash_arg) = extract_keccak256_arg(expr) { - self.emit_lint( - ctx, - hir, - stmt.span, - expr, - hash_arg, - AsmContext { _assign: assign, _is_return: is_return }, - ); - } - }; - - match stmt.kind { - hir::StmtKind::DeclSingle(var_id) => { - let var = hir.variable(var_id); - if let Some(init) = var.initializer { - // Constants should be optimized by the compiler, so no gas savings apply. - if !matches!(var.mutability, Some(hir::VarMut::Constant)) { - check_expr_and_emit_lint(init, var.name, false); - } +impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { + fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(expr, _) = &expr.kind { + if let ExprKind::Ident(ident) = &expr.kind { + if ident.name == kw::Keccak256 { + ctx.emit(&ASM_KECCAK256, expr.span); } } - // Expressions that don't (directly) assign to a variable - hir::StmtKind::Expr(expr) - | hir::StmtKind::Emit(expr) - | hir::StmtKind::Revert(expr) - | hir::StmtKind::DeclMulti(_, expr) - | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), - hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), - _ => (), } } } - -impl AsmKeccak256 { - /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. - fn emit_lint( - &self, - ctx: &LintContext, - _hir: &hir::Hir<'_>, - _stmt_span: Span, - call: &hir::Expr<'_>, - _hash: &hir::Expr<'_>, - _asm_ctx: AsmContext, - ) { - ctx.emit(&ASM_KECCAK256, call.span); - } -} - -/// If the expression is a call to `keccak256` with one argument, returns that argument. -fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { - let hir::ExprKind::Call( - callee, - hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, - .., - ) = &expr.kind - else { - return None; - }; - - let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { - matches!(builtin.name(), kw::Keccak256) - } else { - return None; - }; - - if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } -} - -// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- - -#[derive(Debug, Clone, Copy)] -struct AsmContext { - _assign: Option, - _is_return: bool, -} diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 68df630bd1127..1da825accded9 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -340,6 +340,8 @@ impl ExecutedState { &self, known_contracts: &ContractsByArtifact, ) -> Result { + let chain_id = self.script_config.evm_opts.get_remote_chain_id().await; + let mut decoder = CallTraceDecoderBuilder::new() .with_labels(self.execution_result.labeled_addresses.clone()) .with_verbosity(self.script_config.evm_opts.verbosity) @@ -348,12 +350,12 @@ impl ExecutedState { &self.script_config.config, )?) .with_label_disabled(self.args.disable_labels) + .with_chain_id(chain_id.map(|c| c.id())) .build(); - let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_external( - &self.script_config.config, - self.script_config.evm_opts.get_remote_chain_id().await, - )?; + let mut identifier = TraceIdentifiers::new() + .with_local(known_contracts) + .with_external(&self.script_config.config, chain_id)?; for (_, trace) in &self.execution_result.traces { decoder.identify(trace, &mut identifier); diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index b5f8d9fb55097..4ba84333874bc 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -29,7 +29,7 @@ use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; use foundry_cli::{ opts::{BuildOpts, EvmArgs, GlobalArgs}, - utils::LoadConfig, + utils::{LoadConfig, parse_fee_token_address}, }; use foundry_common::{ CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, @@ -47,8 +47,9 @@ use foundry_config::{ use foundry_evm::{ backend::Backend, core::{ - Breakpoints, - evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, + Breakpoints, FoundryTransaction, + evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + tempo::PATH_USD_ADDRESS, }, executors::ExecutorBuilder, inspectors::{ @@ -138,7 +139,7 @@ pub struct ScriptArgs { pub batch_size: usize, /// Tempo fee token address for paying transaction fees. - #[arg(long = "tempo.fee-token", value_name = "ADDRESS")] + #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] pub fee_token: Option
, /// Skips on-chain simulation. @@ -244,12 +245,24 @@ pub struct ScriptArgs { } impl ScriptArgs { - pub async fn preprocess(self) -> Result> { + /// Loads config, resolves evm_opts (including network inference from fork), and returns them. + async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { + let (config, mut evm_opts) = self.load_config_and_evm_opts()?; + + // Auto-detect network from fork chain ID when not explicitly configured. + evm_opts.infer_network_from_fork().await; + + Ok((config, evm_opts)) + } + + async fn preprocess( + self, + config: Config, + mut evm_opts: EvmOpts, + ) -> Result> { let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); let browser_wallet = self.wallets.browser_signer::().await?; - let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; } else if self.evm.sender.is_none() { @@ -265,8 +278,13 @@ impl ScriptArgs { } } - let script_config = ScriptConfig::new(config, evm_opts, self.batch, self.fee_token).await?; + let fee_token = if evm_opts.networks.is_tempo() && self.fee_token.is_none() { + Some(PATH_USD_ADDRESS) + } else { + self.fee_token + }; + let script_config = ScriptConfig::new(config, evm_opts, self.batch, fee_token).await?; Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } @@ -274,10 +292,7 @@ impl ScriptArgs { pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let (_, mut evm_opts) = self.load_config_and_evm_opts()?; - - // Auto-detect network from fork chain ID when not explicitly configured. - evm_opts.infer_network_from_fork().await; + let (config, evm_opts) = self.resolved_evm_opts().await?; let is_tempo = evm_opts.networks.is_tempo(); @@ -287,7 +302,7 @@ impl ScriptArgs { if is_tempo { let batch = self.batch; - let bundled = match self.prepare_bundled::().await? { + let bundled = match self.prepare_bundled::(config, evm_opts).await? { Some(bundled) => bundled, None => return Ok(()), }; @@ -298,16 +313,22 @@ impl ScriptArgs { broadcasted.verify().await?; } Ok(()) + } else if evm_opts.networks.is_optimism() { + self.run_generic_script::(config, evm_opts).await } else { - self.run_generic_script::().await + self.run_generic_script::(config, evm_opts).await } } /// Prepares the bundled state (compile, simulate, bundle) and returns it /// for broadcasting, or returns `None` if there's nothing to broadcast /// (e.g., debug mode, no transactions, missing RPCs). - async fn prepare_bundled(self) -> Result>> { - let state = self.preprocess::().await?; + async fn prepare_bundled( + self, + config: Config, + evm_opts: EvmOpts, + ) -> Result>> { + let state = self.preprocess::(config, evm_opts).await?; let create2_deployer = state.script_config.evm_opts.create2_deployer; let compiled = state.compile()?; @@ -396,8 +417,12 @@ impl ScriptArgs { Ok(Some(bundled)) } - async fn run_generic_script(self) -> Result<()> { - let bundled = match self.prepare_bundled::().await? { + async fn run_generic_script( + self, + config: Config, + evm_opts: EvmOpts, + ) -> Result<()> { + let bundled = match self.prepare_bundled::(config, evm_opts).await? { Some(bundled) => bundled, None => return Ok(()), }; @@ -724,7 +749,7 @@ impl ScriptConfig { debug: bool, ) -> Result> { trace!("preparing script runner"); - let (evm_env, tx_env, fork_block) = self.evm_opts.env().await?; + let (evm_env, mut tx_env, fork_block) = self.evm_opts.env::<_, _, TxEnvFor>().await?; let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { @@ -766,6 +791,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), + self.fee_token, ) .into(), ) @@ -774,6 +800,10 @@ impl ScriptConfig { }); } + // Propagate fee token to the transaction environment so that internal EVM calls + // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. + tx_env.set_fee_token(self.fee_token); + Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } } diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 468514b0646f0..f07b4b7a3168d 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -6,7 +6,7 @@ use alloy_network::TransactionBuilder; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; -use foundry_common::TransactionMaybeSigned; +use foundry_common::{FoundryTransactionBuilder, TransactionMaybeSigned}; use foundry_config::Config; use foundry_evm::{ constants::CALLER, @@ -77,10 +77,14 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } - let mut tx_req = TransactionRequestFor::::default(); - tx_req.set_from(self.evm_opts.sender); - tx_req.set_input(code.clone()); - tx_req.set_nonce(sender_nonce + library_transactions.len() as u64); + let mut tx_req = TransactionRequestFor::::default() + .with_from(self.evm_opts.sender) + .with_input(code.clone()) + .with_nonce(sender_nonce + library_transactions.len() as u64); + + if let Some(fee_token) = script_config.fee_token { + tx_req.set_fee_token(fee_token); + } library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), @@ -110,11 +114,16 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } - let mut tx_req = TransactionRequestFor::::default(); - tx_req.set_from(self.evm_opts.sender); - tx_req.set_input(calldata); - tx_req.set_nonce(sender_nonce + library_transactions.len() as u64); - tx_req.set_to(create2_deployer); + let mut tx_req = TransactionRequestFor::::default() + .with_from(self.evm_opts.sender) + .with_input(calldata) + .with_nonce(sender_nonce + library_transactions.len() as u64) + .with_to(create2_deployer); + + if let Some(fee_token) = script_config.fee_token { + tx_req.set_fee_token(fee_token); + } + library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), transaction: TransactionMaybeSigned::new(tx_req), diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 229e48cd871a5..ca787105418b3 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -101,6 +101,7 @@ pub fn rpc_endpoints() -> RpcEndpoints { ("mainnet2", RpcEndpointUrl::Url(next_http_archive_rpc_url())), ("sepolia", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Sepolia))), ("optimism", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Optimism))), + ("base", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Base))), ("arbitrum", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Arbitrum))), ("polygon", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Polygon))), ("bsc", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::BinanceSmartChain))), diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 9f5f502c241d1..8345a1c2bf975 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 75c68b278fcf1..c1f56b38d9d5d 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -4,7 +4,7 @@ use std::{ env, fs::{self, File}, io::{Read, Seek, Write}, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, process::Command, sync::LazyLock, }; @@ -231,15 +231,59 @@ pub fn read_string(path: impl AsRef) -> String { /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + // Canonicalize the source once and treat it as the base directory for all traversal. + let base = src.canonicalize()?; + // Canonicalize the destination once and treat it as the base directory for all writes. + let dst_base = dst.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, &dst_base, true) } -fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { +fn copy_dir_filtered_inner( + src: &Path, + dst: &Path, + base_src: &Path, + base_dst: &Path, + is_root: bool, +) -> std::io::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + // Ensure that any path we operate on stays within the original source base directory. + let canonical_src_path = src_path.canonicalize()?; + if !canonical_src_path.starts_with(base_src) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + let relative_src_path = canonical_src_path.strip_prefix(base_src).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "failed to derive relative path within source base directory", + ) + })?; + for component in relative_src_path.components() { + match component { + Component::Normal(name) => { + let name = name.to_string_lossy(); + if name.contains("..") || name.contains('/') || name.contains('\\') { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "invalid path component in source entry", + )); + } + } + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + } + } + let dst_path = base_dst.join(relative_src_path); if ty.is_dir() { // Skip build artifact directories at the root level @@ -249,10 +293,58 @@ fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Re { continue; } + // Ensure that the destination directory stays within the allowed destination base. + let canonical_dst_dir = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // Directory does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_dir.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } fs::create_dir_all(&dst_path)?; - copy_dir_filtered_inner(&src_path, &dst_path, false)?; + copy_dir_filtered_inner(&src_path, &dst_path, base_src, base_dst, false)?; } else { - fs::copy(&src_path, &dst_path)?; + // Ensure that the destination file path stays within the allowed destination base. + let canonical_dst_path = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // File does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_path.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + fs::copy(&canonical_src_path, &canonical_dst_path)?; } } Ok(()) diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index b1bc0835779c2..a02dadf563922 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -304,7 +304,7 @@ pub fn deploy_contract( to: Option, ) -> Result { let mut evm_env = evm_env.clone(); - evm_env.cfg_env.set_spec(spec_id); + evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id); if to.is_some_and(|to| to.is_call()) { let TxKind::Call(to) = to.unwrap() else { unreachable!() }; diff --git a/npm/package.json b/npm/package.json index 2362816053f8c..d596a7930b609 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.0.2", "bun": "^1.3.1", "typescript": "^5.9.3" }, diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} diff --git a/testdata/default/repros/Issue14212.t.sol b/testdata/default/repros/Issue14212.t.sol new file mode 100644 index 0000000000000..0135e072bb72f --- /dev/null +++ b/testdata/default/repros/Issue14212.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +// https://github.com/foundry-rs/foundry/issues/14212 +// EthEvmNetwork uses Ethereum as its Network type, which cannot deserialize +// OP Stack deposit transactions (type 0x7e). These tests verify that the fork +// backend can handle blocks and transactions containing deposit txs. + +contract Issue14212Test is Test { + // Base block 30434326 contains a deposit tx at index 0: + // tx: 0x6fc82bcdcdeba0385c3910cd8e92074e51aaa9f21528dbc4c242f560a2f27bab + // type: 0x7e (deposit) + // from: 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001 + // to: 0x4200000000000000000000000000000000000015 + // + // A regular tx in the same block: + // tx: 0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2 + + /// vm.transact on an OP deposit tx should not revert with a deserialization error. + /// This exercises the fork backend's get_transaction codepath. + function test_transactDepositTxOnBase() public { + // Fork Base at the block before the deposit tx + vm.createSelectFork("base", 30434325); + + // Transact the deposit tx from the next block. + // This calls fork.backend().get_transaction() which uses FEN::Network + // to deserialize the response. With Network = Ethereum, this fails: + // "deserialization error: data did not match any variant of untagged enum BlockTransactions" + vm.transact(0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2); + } + + /// vm.rollFork to a tx hash in a block containing deposit txs should work. + /// This exercises the fork backend's get_full_block codepath. + function test_rollForkToTxOnBase() public { + vm.createSelectFork("base", 30434325); + + // Roll to a regular tx in block 30434326 which also contains deposit txs. + // This calls get_full_block internally which must deserialize the entire + // block including the deposit tx. + bytes32 txHash = 0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2; + vm.rollFork(txHash); + } + + /// vm.transact on an OP deposit tx on Optimism mainnet. + function test_transactDepositTxOnOptimism() public { + // Optimism block 127867197 contains deposit tx at index 0: + // tx: 0x56b04a2e66cba482270c6e68244a8faaa59a1e1878a04086a12514be2d6e14f9 + vm.createSelectFork("optimism", 127867196); + + // Transact a regular tx from the block containing deposit txs. + // The regular tx at index 1: + vm.transact(0xa003e419e2d7502269eb5eda56947b580120e00abfd5b5460d08f8af44a0c24f); + } +} diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 8c60909ff66c9..6307668074860 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -54,6 +54,7 @@ mainnet = "${RPC_MAINNET}" mainnet2 = "${RPC_MAINNET2}" sepolia = "${RPC_SEPOLIA}" optimism = "${RPC_OPTIMISM}" +base = "${RPC_BASE}" arbitrum = "${RPC_ARBITRUM}" polygon = "${RPC_POLYGON}" bsc = "${RPC_BSC}" From adc3642487d8d30468a8578a920727a9051afcf8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:25:44 +0700 Subject: [PATCH 275/391] Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cast/src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index c4ffcdf810d8d..afa3e903d8637 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -797,7 +797,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; + sh_println!("{decoded_tx}")?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; From 6bd1f5b0fe83a57165912500b90cb086f2c3963f Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Fri, 10 Apr 2026 12:27:31 +0700 Subject: [PATCH 276/391] Update docker.yml --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5a2330e7d5d62..7b85ca2ae00c8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,7 @@ on: push: tags: ["*"] branches: - - "main" + - "master" pull_request: branches: ["**"] From 4672ecd8e51dce9ffa5bc01af7c8b60b75b3cf42 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:47:05 +0700 Subject: [PATCH 277/391] Revise Foundry benchmark results and system info (#407) Updated benchmark results for Foundry versions and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/LATEST.md | 71 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/benches/LATEST.md b/benches/LATEST.md index 00776abc94003..7ea1049a2ac41 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,28 +1,73 @@ # Foundry Benchmark Results -**Date**: 2026-01-27 03:38:34 +**Date**: 2025-10-02 12:14:23 -## Summary +## Repositories Tested -Benchmarked 2 Foundry versions across 1 repository. +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [Vectorized/solady](https://github.com/Vectorized/solady) +3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) -### Repositories Tested +## Foundry Versions -1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) +- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) + +## Forge Test + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 3.17 s | 2.94 s | +| solady | 2.28 s | 2.10 s | +| Uniswap-v4-core | 7.27 s | 6.13 s | +| sparkdotfi-spark-psm | 43.04 s | 44.08 s | + +## Forge Fuzz Test -### Foundry Versions +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------ | ---------- | +| ithacaxyz-account | 3.18 s | 3.02 s | +| solady | 2.39 s | 2.24 s | +| Uniswap-v4-core | 6.84 s | 6.20 s | +| sparkdotfi-spark-psm | 3.07 s | 2.72 s | -- **stable**: forge Version: 1.5.0-dev (6e718be 2025-12-07) -- **nightly**: forge Version: 1.5.0-dev (6e718be 2025-12-07) +## Forge Test (Isolated) + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| solady | 2.26 s | 2.41 s | +| Uniswap-v4-core | 7.22 s | 7.71 s | +| sparkdotfi-spark-psm | 45.53 s | 50.49 s | + +## Forge Build (No Cache) + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 9.16 s | 9.08 s | +| solady | 14.62 s | 14.69 s | +| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | +| sparkdotfi-spark-psm | 13.17 s | 13.14 s | ## Forge Build (With Cache) -| Repository | stable | nightly | -|------------|----------|----------| -| ithacaxyz-account | 0.345 s | 0.279 s | +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 0.156 s | 0.113 s | +| solady | 0.089 s | 0.094 s | +| Uniswap-v4-core | 0.133 s | 0.127 s | +| sparkdotfi-spark-psm | 0.173 s | 0.131 s | + +## Forge Coverage + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | -------- | ---------- | +| ithacaxyz-account | 14.91 s | 13.34 s | +| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | +| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | ## System Information -- **OS**: linux +- **OS**: macos - **CPU**: 8 -- **Rustc**: rustc 1.93.0 (254b59607 2026-01-19) +- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) From 88997dc3ae1b1c4d2df5754625a1c1bae2cfd985 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:55:32 +0700 Subject: [PATCH 278/391] Revise Foundry benchmark results and system info (#408) Updated benchmark results for Foundry versions v1.3.6 and v1.4.0-rc1, including new test data and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/LATEST.md | 71 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/benches/LATEST.md b/benches/LATEST.md index 00776abc94003..7ea1049a2ac41 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,28 +1,73 @@ # Foundry Benchmark Results -**Date**: 2026-01-27 03:38:34 +**Date**: 2025-10-02 12:14:23 -## Summary +## Repositories Tested -Benchmarked 2 Foundry versions across 1 repository. +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [Vectorized/solady](https://github.com/Vectorized/solady) +3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) -### Repositories Tested +## Foundry Versions -1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) +- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) + +## Forge Test + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 3.17 s | 2.94 s | +| solady | 2.28 s | 2.10 s | +| Uniswap-v4-core | 7.27 s | 6.13 s | +| sparkdotfi-spark-psm | 43.04 s | 44.08 s | + +## Forge Fuzz Test -### Foundry Versions +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------ | ---------- | +| ithacaxyz-account | 3.18 s | 3.02 s | +| solady | 2.39 s | 2.24 s | +| Uniswap-v4-core | 6.84 s | 6.20 s | +| sparkdotfi-spark-psm | 3.07 s | 2.72 s | -- **stable**: forge Version: 1.5.0-dev (6e718be 2025-12-07) -- **nightly**: forge Version: 1.5.0-dev (6e718be 2025-12-07) +## Forge Test (Isolated) + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| solady | 2.26 s | 2.41 s | +| Uniswap-v4-core | 7.22 s | 7.71 s | +| sparkdotfi-spark-psm | 45.53 s | 50.49 s | + +## Forge Build (No Cache) + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 9.16 s | 9.08 s | +| solady | 14.62 s | 14.69 s | +| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | +| sparkdotfi-spark-psm | 13.17 s | 13.14 s | ## Forge Build (With Cache) -| Repository | stable | nightly | -|------------|----------|----------| -| ithacaxyz-account | 0.345 s | 0.279 s | +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | ------- | ---------- | +| ithacaxyz-account | 0.156 s | 0.113 s | +| solady | 0.089 s | 0.094 s | +| Uniswap-v4-core | 0.133 s | 0.127 s | +| sparkdotfi-spark-psm | 0.173 s | 0.131 s | + +## Forge Coverage + +| Repository | v1.3.6 | v1.4.0-rc1 | +| -------------------- | -------- | ---------- | +| ithacaxyz-account | 14.91 s | 13.34 s | +| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | +| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | ## System Information -- **OS**: linux +- **OS**: macos - **CPU**: 8 -- **Rustc**: rustc 1.93.0 (254b59607 2026-01-19) +- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) From 6dc7d961171a13e9f6b11a96cbce2127bd8b92fc Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:02:37 +0700 Subject: [PATCH 279/391] Revert "benches\LATEST.md (#350)" (#409) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. --- sleep.json | 955 ----------------------------------------------------- 1 file changed, 955 deletions(-) delete mode 100644 sleep.json diff --git a/sleep.json b/sleep.json deleted file mode 100644 index 5b430e1e663f6..0000000000000 --- a/sleep.json +++ /dev/null @@ -1,955 +0,0 @@ -{ - "results": [ - { - "command": "sleep 0.020", - "mean": 0.023726515413333333, - "stddev": 0.004602014051751124, - "median": 0.02267755758, - "user": 0.0013185473333333334, - "system": 0.0020899164444444446, - "min": 0.02109890308, - "max": 0.05602819808, - "times": [ - 0.02856005608, - 0.02346135008, - 0.02202502208, - 0.02139558708, - 0.02265920408, - 0.02121691608, - 0.02272505608, - 0.02114247908, - 0.02157142808, - 0.021514666079999998, - 0.02161920108, - 0.02335035008, - 0.02224331408, - 0.02228639708, - 0.02152537208, - 0.021732302079999998, - 0.02273370308, - 0.02115513608, - 0.02268494308, - 0.02244547308, - 0.023943647079999998, - 0.02324528508, - 0.02152617908, - 0.023991903079999998, - 0.02250884108, - 0.02342551708, - 0.02113216608, - 0.02168223108, - 0.02222267508, - 0.02273532108, - 0.02273995308, - 0.05602819808, - 0.02501500608, - 0.03121396008, - 0.02424400108, - 0.02459129108, - 0.02633760708, - 0.02377406808, - 0.02365474708, - 0.02406064008, - 0.02300910408, - 0.02437339208, - 0.02317403908, - 0.02257532008, - 0.02267017208, - 0.02356714508, - 0.02367204808, - 0.02258227108, - 0.02330384008, - 0.02225645108, - 0.02478414908, - 0.02484724308, - 0.02270765708, - 0.02339114708, - 0.02450795908, - 0.02348840008, - 0.044674490080000004, - 0.028041754080000002, - 0.022940745079999998, - 0.02259975308, - 0.022112378079999998, - 0.02271348408, - 0.02320266708, - 0.02284982108, - 0.02244050908, - 0.02238655808, - 0.022084648079999998, - 0.02241669808, - 0.02523103408, - 0.02256237908, - 0.03532525108, - 0.02232798408, - 0.02173793008, - 0.021903001079999998, - 0.02288046308, - 0.02368652508, - 0.02211418708, - 0.02265551308, - 0.02187778308, - 0.02191395108, - 0.02182523808, - 0.02185612208, - 0.02109890308, - 0.02294132008, - 0.02191512608, - 0.02264461208, - 0.02227651108, - 0.02307147508, - 0.02227169708, - 0.02177434208 - ], - "memory_usage_byte": [ - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3014656, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3141632, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3268608, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680 - ], - "exit_codes": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - }, - { - "command": "sleep 0.021", - "mean": 0.022889189941111117, - "stddev": 0.0007161191938371117, - "median": 0.02280623708, - "user": 0.0009166992592592593, - "system": 0.0016941181481481477, - "min": 0.02132554808, - "max": 0.02453766808, - "times": [ - 0.02311599608, - 0.02274468508, - 0.02193879008, - 0.02158843608, - 0.02329398008, - 0.02379494508, - 0.02260801308, - 0.02439507908, - 0.02448522508, - 0.02403379508, - 0.02298143008, - 0.02263027308, - 0.02229235308, - 0.02335063508, - 0.02377098008, - 0.02269184108, - 0.023631199079999998, - 0.02338021508, - 0.02198521708, - 0.02251586208, - 0.022295963079999998, - 0.02226397608, - 0.02453766808, - 0.02184453408, - 0.02289659908, - 0.02382663208, - 0.02347397108, - 0.02225926308, - 0.02207640608, - 0.02243237108, - 0.02278192608, - 0.02270514808, - 0.02245069008, - 0.023018867079999998, - 0.02399866208, - 0.02236840708, - 0.02366382208, - 0.02294188908, - 0.02155127708, - 0.02294999808, - 0.02132554808, - 0.02242025908, - 0.02202766108, - 0.02182175108, - 0.02272186608, - 0.02211805308, - 0.02319764908, - 0.022308045079999998, - 0.02345400908, - 0.022437877079999998, - 0.02273417808, - 0.02217370908, - 0.02254318408, - 0.023269922079999998, - 0.02384951108, - 0.02419476108, - 0.02439866908, - 0.02354840508, - 0.02304219108, - 0.02354960608, - 0.02382648708, - 0.02345751208, - 0.02367913708, - 0.02253067208, - 0.02215132608, - 0.022603942079999998, - 0.02284062808, - 0.02252907808, - 0.02220393508, - 0.023291509079999998, - 0.02399456908, - 0.02407123208, - 0.02279175108, - 0.02300624708, - 0.02309500408, - 0.023036532079999998, - 0.02303833108, - 0.02316846908, - 0.02228349608, - 0.02247140608, - 0.022482600079999998, - 0.02370720808, - 0.02220123708, - 0.02230588608, - 0.02333678708, - 0.02153336008, - 0.02203071908, - 0.02279195108, - 0.02353659108, - 0.02267460708, - 0.022536274079999998, - 0.022769262079999998, - 0.02314857808, - 0.02194885908, - 0.02355038408, - 0.02320035308, - 0.02307451408, - 0.02379926408, - 0.02330480208, - 0.02257055708, - 0.02330320308, - 0.02303003208, - 0.02327859908, - 0.02171311608, - 0.02282052308, - 0.02170123708, - 0.02254831308, - 0.02235855408 - ], - "memory_usage_byte": [ - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680 - ], - "exit_codes": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - }, - { - "command": "sleep 0.022", - "mean": 0.02415569324504855, - "stddev": 0.0009830972994273135, - "median": 0.02409406108, - "user": 0.001165289514563107, - "system": 0.001767603883495146, - "min": 0.02243173808, - "max": 0.02755932908, - "times": [ - 0.02456728108, - 0.02650439708, - 0.02480475408, - 0.02452974808, - 0.02300978308, - 0.02521451608, - 0.02543841408, - 0.02538411108, - 0.02475773908, - 0.02403843308, - 0.02426362708, - 0.02326921708, - 0.02447185308, - 0.02361749008, - 0.02410661008, - 0.02371481508, - 0.02327300908, - 0.02430165908, - 0.02328269108, - 0.02315262608, - 0.02380195808, - 0.02283639508, - 0.02491355808, - 0.02401717008, - 0.02556049408, - 0.02350359508, - 0.02400529208, - 0.02533555808, - 0.02467923308, - 0.02478442308, - 0.02422068708, - 0.02352175108, - 0.02481882108, - 0.02456148108, - 0.02314905108, - 0.024188183079999998, - 0.02483985908, - 0.02289141308, - 0.02364977308, - 0.02354907008, - 0.02379135508, - 0.026812933079999997, - 0.023360627079999998, - 0.02331436308, - 0.02504176308, - 0.02358805508, - 0.02409406108, - 0.02350689508, - 0.02303628508, - 0.02430972408, - 0.02516170908, - 0.02352843108, - 0.02274564308, - 0.02345165808, - 0.02429327308, - 0.02252948108, - 0.02445868508, - 0.02755932908, - 0.02522621808, - 0.02491753008, - 0.022858510079999998, - 0.02401968108, - 0.02409596908, - 0.02390450108, - 0.02373108808, - 0.027211489079999998, - 0.02537487108, - 0.02319182608, - 0.02390569508, - 0.02490164708, - 0.02384732708, - 0.02243173808, - 0.02367003008, - 0.02494288308, - 0.02436298308, - 0.02390639308, - 0.02423030808, - 0.02430082908, - 0.02320845908, - 0.02421546708, - 0.02530823508, - 0.02368935308, - 0.02306283708, - 0.023536658079999998, - 0.02359881208, - 0.02438320308, - 0.02477724008, - 0.02362231908, - 0.02419465008, - 0.02596891608, - 0.02307578608, - 0.02459456508, - 0.02384055408, - 0.02421387408, - 0.02510733208, - 0.02473580508, - 0.02243970708, - 0.02253156008, - 0.02550018108, - 0.02440877608, - 0.02281331608, - 0.02354148408, - 0.02352098308 - ], - "memory_usage_byte": [ - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680, - 3399680 - ], - "exit_codes": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ] -} From b3d01ec026f66567cbe77f421768d3704bc28706 Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Fri, 10 Apr 2026 13:06:16 +0700 Subject: [PATCH 280/391] Update Docker.yml --- .github/workflows/Docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 7b85ca2ae00c8..5a2330e7d5d62 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,7 +4,7 @@ on: push: tags: ["*"] branches: - - "master" + - "main" pull_request: branches: ["**"] From dcaffd59af1bb4e4815abc28f417fe60bec41f08 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:25:45 +0700 Subject: [PATCH 281/391] Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/evm/src/executors/invariant/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index db992516850a1..5f54436da2ce7 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -486,7 +486,7 @@ impl<'a> InvariantExecutor<'a> { is_last_call } else { self.config.check_interval == 1 - || (current_run.depth + 1).is_multiple_of(self.config.check_interval) + || (current_run.depth + 1) % self.config.check_interval as usize == 0 || is_last_call }; From d045cf9af29eb540c89b1306f29b45a26623979e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:07:07 +0700 Subject: [PATCH 282/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 07413fef5495c..fe448a5462bab 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -118,35 +118,37 @@ impl ScriptTester { fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); + let from_dir_canon = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); - // Only operate on regular files to avoid following symlinks or directories - let metadata = fs::symlink_metadata(&file)?; + + // Resolve each entry and ensure it stays within the trusted source directory. + let canonical_file = match file.canonicalize() { + Ok(path) if path.starts_with(&from_dir_canon) => path, + _ => continue, + }; + + // Only operate on regular files to avoid following symlinks or directories. + let metadata = fs::symlink_metadata(&canonical_file)?; let ftype = metadata.file_type(); if !ftype.is_file() { continue; } + let name = match file.file_name() { Some(name) => name, None => continue, }; - // Validate file name to avoid path traversal and absolute paths + // Validate file name to avoid path traversal and absolute paths. let name_str = name.to_string_lossy(); if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { - // Skip invalid (potentially dangerous) file names + // Skip invalid (potentially dangerous) file names. continue; } - // Verify canonicalized file is in from_dir to avoid symlink traversal - if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { - continue; - } - } else { - continue; - } - fs::copy(&file, to_dir.join(name))?; + + fs::copy(&canonical_file, to_dir.join(name))?; } Ok(()) } From 3d1f0e61a4ca5ee86fedb6425137557e71adfe65 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:17:05 +0700 Subject: [PATCH 283/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index fe448a5462bab..6c7d50ce1d0e4 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -119,6 +119,7 @@ impl ScriptTester { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); let from_dir_canon = from_dir.canonicalize()?; + let from_dir_canon = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { @@ -138,15 +139,16 @@ impl ScriptTester { } let name = match file.file_name() { - Some(name) => name, - None => continue, + // Verify canonicalized file is in canonicalized from_dir to avoid traversal + let canonical_file = match file.canonicalize() { + Ok(path) => path, + Err(_) => continue, }; - // Validate file name to avoid path traversal and absolute paths. - let name_str = name.to_string_lossy(); - if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + if !canonical_file.starts_with(&from_dir_canon) { // Skip invalid (potentially dangerous) file names. continue; - } + + fs::copy(&canonical_file, to_dir.join(name))?; fs::copy(&canonical_file, to_dir.join(name))?; } From 722b9b888c07d713c7d3fc91aeeba17af5e45b77 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:18:19 +0700 Subject: [PATCH 284/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From dc8de27bd4e366fb8844c00bc51e34831082cdd9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:19:34 +0700 Subject: [PATCH 285/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index f079d0f4fbf3d..06b2c29945417 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From 570d96224ffc0f11c2f45901dc1ce9d97ed17c66 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:56:32 +0700 Subject: [PATCH 286/391] Wagmi (e604566) (#413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * feat(anvil): add Tempo gas estimation and tx type tests (#14197) * ci: add missing ARBITRUM_RPC and ETH_SEPOLIA_RPC secrets to flaky/isolate workflows (#14233) * refactor(cli): introduce `RpcCommonOpts` (#14224) * ci: remove unused HTTP_ARCHIVE_URLS and WS_ARCHIVE_URLS secrets (#14234) * feat(invariant): show best value realtime in optimization mode (#14226) * feat(evm): implement `FoundryEvmFactory` for `OpEvmFactory` (#14228) * clippy: warn on `if_then_some_else_none` (autofix) (#14235) clippy: warn on if_then_some_else_none Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `explicit_into_iter_loop` (autofix) (#14236) clippy: warn on explicit_into_iter_loop Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(clippy): remove `literal_string_with_formatting_args` allow, no longer required (#14238) chore: remove literal_string_with_formatting_args allow The upstream clippy bug (rust-lang/rust-clippy#13885) was fixed in rust-lang/rust-clippy#13953 (merged Feb 2025). The workaround is no longer needed. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `single_char_pattern` (autofix) (#14237) clippy: warn on single_char_pattern Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): Tempo direct signer + gas calc logic (#14242) * clippy: warn on `unnested_or_patterns` (autofix) (#14239) clippy: warn on unnested_or_patterns Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `enum_glob_use` (clippy) (#14240) clippy: warn on enum_glob_use Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `from_iter_instead_of_collect` (autofix) (#14241) clippy: warn on from_iter_instead_of_collect Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `if_not_else` (autofix) (#14092) * clippy: warn on if_not_else Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor: use contains_key + remove instead of if-let remove Simpler branch flip that preserves the original structure. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: resolve new if_not_else and redundant_else findings after merge Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: flip if_not_else in keychain authorize Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: use multi-line format for blob_hashes if-else Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: rustfmt prefers single-line if-else here Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(cli): restore short flag for `--rpc-url` (#14246) * fix(cli): restore short flag for `--rpc-url` * fix: requirements * fix: short flag collision * fix: restore long * fix: help message * fix(cast): `batch-send` handling by clearing `to`/`value` fields (#14250) * chore(cast): remove redundant batch-mktx clearing (#14252) * fix(anvil): use <= for pending pool replacement underprice check (#14254) * fix(invariant): preserve delay semantics during shrinking (#14218) * fix(invariant): preserve delay semantics during shrinking * fix tests --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): extract `transaction_at_block_index` in fork (#14271) * chore(deps): weekly `cargo update` (#14270) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * chore(deny): ignore RUSTSEC-2026-0097 (#14272) * Update flake.lock (#14269) Co-authored-by: github-actions[bot] * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Addresses scfuzzbench/scfuzzbench#112. (#14266) * fix(forge): `script`/`test` Tempo selection when used w/ Anvil (#14258) * fix(anvil): parse Tempo expiring nonce fields from hex strings (#14259) * fix(ci): fix flaky_testdata vyper regression and p256 snapshot (#14278) * chore(evm): reorganize `foundry-evm-core::evm` mod (#14277) * refactor(anvil): extract `build_block_info` helper (#14251) refactor(anvil): extract build_block_info helper * feat(evm): auto-detect Tempo network on Anvil (#14279) * feat(evm): auto-detect Tempo network on Anvil * test(evm): add integration tests for network auto-selection via infer_network_from_fork Amp-Thread-ID: https://ampcode.com/threads/T-019d85e2-2767-70a1-a1bd-659d72a156f4 Co-authored-by: Amp --------- Co-authored-by: Amp * clippy: warn on `derive_partial_eq_without_eq` (autofix) (#14243) clippy: warn on derive_partial_eq_without_eq Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `flat_map_option` (autofix) (#14284) clippy: warn on flat_map_option Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): wildcard P256VERIFY gas in osaka snapshot (#14287) The P256VERIFY precompile gas cost was reduced from 6900 to 3450, but the flaky_osaka_can_run_p256_precompile snapshot still had the old value hardcoded. Replace with [..] for consistency with the rest of the snapshot. Closes #14282 Closes #14283 Amp-Thread-ID: https://ampcode.com/threads/T-019d8681-ae7d-712f-b4e7-eb6f595f82e3 Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `equatable_if_let` (manual) (#14285) * clippy: warn on flat_map_option Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on equatable_if_let Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `large_stack_frames`(manual) (#14289) clippy: warn on large_stack_frames Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `needless_continue` (manual) (#14288) clippy: warn on needless_continue Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on 6 small lints (1 finding each, fixed) (manual) (#14286) clippy: warn on trailing_empty_array, trait_duplication_in_bounds, trivial_regex, tuple_array_conversions, unnecessary_struct_initialization, unused_peekable Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * evm: only classify skip reverts from cheatcode address (#14264) * evm: require cheatcode origin for skip * evm/core: stop decoding magic skip in maybe_decode * evm/fuzz: guard skip decode by cheatcode reverter --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: warn on `unnecessary_self_imports` (manual) (#14293) clippy: warn on unnecessary_self_imports Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): auto-detect seconds vs milliseconds in evm_setTime (#14292) * fix(anvil): convert evm_setTime input from millis to seconds before use evm_setTime accepts a JavaScript-style millisecond timestamp for Ganache compatibility, but the offset was computed by subtracting current_call_timestamp (seconds) from the raw millisecond input. Convert the input to seconds first, then compute the offset correctly. Co-Authored-By: cui <1579517+cuiweixie@users.noreply.github.com> Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: move millis-to-seconds conversion to RPC dispatch layer The internal evm_set_time API accepts seconds (matching internal tests), while the JSON-RPC evm_setTime method accepts milliseconds for Ganache compatibility. Move the Duration::from_millis conversion to the RPC handler so both code paths are correct. Co-Authored-By: cui <1579517+cuiweixie@users.noreply.github.com> Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: auto-detect seconds vs milliseconds in evm_setTime Timestamps above 1e12 are treated as milliseconds (Ganache compat) and converted to seconds; values at or below that threshold are treated as seconds directly. This avoids silently downcasting a seconds timestamp to a nonsensical value. Co-Authored-By: cui <1579517+cuiweixie@users.noreply.github.com> Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * style: fix rustfmt Co-Authored-By: cui <1579517+cuiweixie@users.noreply.github.com> Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): add 7 day dependency cooldown (#14294) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: enable `string_lit_as_bytes` (manual) (#14296) clippy: enable `string_lit_as_bytes` Amp-Thread-ID: https://ampcode.com/threads/T-019d8707-07ff-77cf-b7f2-5dc0f64200ec Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: enable `option_as_ref_cloned` (manual) (#14295) clippy: enable `option_as_ref_cloned` Amp-Thread-ID: https://ampcode.com/threads/T-019d8707-07ff-77cf-b7f2-5dc0f64200ec Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): guard deposit tx parsing in Tempo mode (#14261) * fix(anvil): guard deposit tx parsing in Tempo mode * use the ensure_op_deposits_active check * clippy: enable `missing_const_for_fn` (autofix) (#14297) * clippy: enable `missing_const_for_fn` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8707-07ff-77cf-b7f2-5dc0f64200ec * fix: revert const on feature-gated non-const functions Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8707-07ff-77cf-b7f2-5dc0f64200ec --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * clippy: enable `useless_let_if_seq` (manual) (#14300) clippy: enable `useless_let_if_seq` Amp-Thread-ID: https://ampcode.com/threads/T-019d8707-07ff-77cf-b7f2-5dc0f64200ec Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: cui Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: Arsh Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: googleworkspace-bot --- .circleci/cargo.yml | 32 + .circleci/ci-web3-gamefi.yml | 26 + .circleci/ci.yml | 31 + .circleci/ci_cargo.yml | 37 + .circleci/ci_v1.yml | 31 + .circleci/config.yml | 32 + .circleci/dev_stage.yml | 70 + .circleci/web3_defi_gamefi.yml | 26 + .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 + .deps/remix-tests/remix_tests.sol | 225 ++++ .github/ISSUE_TEMPLATE/bug_report.md | 39 + .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/dependabot.yml | 2 + .github/workflows/Docker.yml | 62 + .github/workflows/apisec-scan.yml | 29 + .github/workflows/codeql.yml | 92 ++ .github/workflows/deploy.yml | 27 + .github/workflows/docker.yml | 62 + .github/workflows/google.yml | 117 ++ .github/workflows/npm.yml | 37 + .github/workflows/snyk-container.yml | 55 + .github/workflows/test-flaky.yml | 9 +- .github/workflows/test-isolate.yml | 9 +- .github/workflows/test.yml | 2 - .gitmodules | 6 + Cargo.lock | 191 +-- Cargo.toml | 25 +- benches/src/lib.rs | 22 +- counter/.github/workflows/test.yml | 43 + counter/.gitignore | 14 + counter/README.md | 66 + counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 + counter/src/Counter.sol | 14 + counter/test/Counter.t.sol | 24 + crates/anvil/core/src/eth/mod.rs | 2 +- crates/anvil/core/src/eth/transaction/mod.rs | 10 +- crates/anvil/rpc/src/error.rs | 2 +- crates/anvil/rpc/src/request.rs | 4 +- crates/anvil/rpc/src/response.rs | 2 +- crates/anvil/server/src/config.rs | 2 +- crates/anvil/server/src/ipc.rs | 2 +- crates/anvil/src/config.rs | 73 +- crates/anvil/src/eth/api.rs | 20 +- crates/anvil/src/eth/backend/cheats.rs | 2 +- crates/anvil/src/eth/backend/db.rs | 4 +- crates/anvil/src/eth/backend/executor.rs | 8 +- crates/anvil/src/eth/backend/fork.rs | 31 +- crates/anvil/src/eth/backend/info.rs | 2 +- crates/anvil/src/eth/backend/mem/mod.rs | 282 ++-- crates/anvil/src/eth/backend/mem/storage.rs | 23 +- crates/anvil/src/eth/fees.rs | 20 +- crates/anvil/src/eth/pool/mod.rs | 10 +- crates/anvil/src/eth/pool/transactions.rs | 16 +- crates/anvil/src/filter.rs | 2 +- crates/anvil/src/lib.rs | 18 +- crates/anvil/src/logging.rs | 2 +- crates/anvil/src/pubsub.rs | 2 +- crates/anvil/src/server/rpc_handlers.rs | 4 +- crates/anvil/src/tasks/block_listener.rs | 2 +- crates/anvil/src/tasks/mod.rs | 2 +- crates/anvil/tests/it/tempo.rs | 485 +++++++ crates/cast/src/args.rs | 7 +- crates/cast/src/base.rs | 10 +- crates/cast/src/cmd/batch_mktx.rs | 6 +- crates/cast/src/cmd/batch_send.rs | 8 +- crates/cast/src/cmd/create2.rs | 1 + crates/cast/src/cmd/erc20.rs | 4 +- crates/cast/src/cmd/interface.rs | 2 +- crates/cast/src/cmd/keychain.rs | 40 +- crates/cast/src/cmd/run.rs | 153 +-- crates/cast/src/cmd/storage.rs | 2 +- crates/cast/src/cmd/tip20.rs | 2 +- crates/cast/src/cmd/wallet/mod.rs | 2 +- crates/cast/src/lib.rs | 14 +- crates/cast/src/tx.rs | 9 +- crates/cast/tests/cli/main.rs | 48 +- crates/cheatcodes/src/error.rs | 4 +- crates/cheatcodes/src/evm/prank.rs | 4 +- crates/cheatcodes/src/fs.rs | 2 +- crates/cheatcodes/src/inspector.rs | 109 +- crates/cheatcodes/src/inspector/analysis.rs | 4 +- crates/cheatcodes/src/json.rs | 2 +- crates/cheatcodes/src/test/assert.rs | 14 +- crates/cheatcodes/src/test/expect.rs | 8 +- crates/cheatcodes/src/test/revert_handlers.rs | 8 +- crates/cheatcodes/src/utils.rs | 5 +- crates/chisel/src/dispatcher.rs | 6 +- crates/chisel/src/executor.rs | 30 +- crates/chisel/src/runner.rs | 2 +- crates/chisel/src/solidity_helper.rs | 9 +- crates/chisel/tests/it/repl/session.rs | 2 +- crates/cli-markdown/src/lib.rs | 10 +- crates/cli/Cargo.toml | 1 + crates/cli/src/opts/evm.rs | 80 +- crates/cli/src/opts/mod.rs | 2 + crates/cli/src/opts/rpc.rs | 58 +- crates/cli/src/opts/rpc_common.rs | 114 ++ crates/cli/src/opts/tempo.rs | 2 +- crates/cli/src/utils/cmd.rs | 7 +- crates/cli/src/utils/mod.rs | 18 +- crates/cli/src/utils/suggestions.rs | 3 +- crates/common/fmt/src/console.rs | 24 +- crates/common/fmt/src/dynamic.rs | 4 +- crates/common/fmt/src/ui.rs | 6 +- crates/common/src/comments/comment.rs | 14 +- crates/common/src/comments/inline_config.rs | 4 +- crates/common/src/comments/mod.rs | 10 +- crates/common/src/compile.rs | 18 +- crates/common/src/contracts.rs | 8 +- crates/common/src/io/shell.rs | 24 +- crates/common/src/io/stdin.rs | 4 +- crates/common/src/provider/curl_transport.rs | 2 +- crates/common/src/provider/mod.rs | 25 +- .../common/src/provider/runtime_transport.rs | 8 +- crates/common/src/retry.rs | 4 +- crates/common/src/selectors.rs | 6 +- crates/common/src/slot_identifier.rs | 4 +- crates/common/src/traits.rs | 2 +- crates/common/src/transactions/broadcast.rs | 6 +- crates/common/src/transactions/builder.rs | 15 + crates/config/src/error.rs | 6 +- crates/config/src/etherscan.rs | 4 +- crates/config/src/extend.rs | 6 +- crates/config/src/filter.rs | 8 +- crates/config/src/fix.rs | 4 +- crates/config/src/fmt.rs | 18 +- crates/config/src/fs_permissions.rs | 10 +- crates/config/src/fuzz.rs | 4 +- crates/config/src/inline/mod.rs | 6 +- crates/config/src/inline/natspec.rs | 13 +- crates/config/src/invariant.rs | 5 + crates/config/src/lib.rs | 32 +- crates/config/src/lint.rs | 4 +- crates/config/src/providers/ext.rs | 2 +- crates/config/src/providers/remappings.rs | 13 +- crates/config/src/providers/warnings.rs | 12 +- crates/debugger/src/debugger.rs | 2 +- crates/debugger/src/node.rs | 2 +- crates/debugger/src/tui/draw.rs | 6 +- crates/debugger/src/tui/mod.rs | 2 +- crates/doc/src/builder.rs | 4 +- crates/doc/src/document.rs | 10 +- crates/doc/src/parser/comment.rs | 28 +- .../doc/src/preprocessor/infer_hyperlinks.rs | 12 +- crates/doc/src/solang_ext/ast_eq.rs | 6 +- crates/doc/src/writer/as_doc.rs | 39 +- crates/doc/src/writer/buf_writer.rs | 22 +- crates/evm/core/Cargo.toml | 2 + crates/evm/core/src/backend/cow.rs | 2 +- crates/evm/core/src/backend/mod.rs | 14 +- crates/evm/core/src/backend/snapshot.rs | 4 +- crates/evm/core/src/buffer.rs | 21 +- crates/evm/core/src/bytecode.rs | 8 +- crates/evm/core/src/decode.rs | 13 +- crates/evm/core/src/evm.rs | 1170 ----------------- crates/evm/core/src/evm/eth.rs | 358 +++++ crates/evm/core/src/evm/mod.rs | 301 +++++ crates/evm/core/src/evm/op.rs | 424 ++++++ crates/evm/core/src/evm/tempo.rs | 526 ++++++++ crates/evm/core/src/fork/database.rs | 8 +- crates/evm/core/src/opts.rs | 79 +- crates/evm/core/src/utils.rs | 4 +- crates/evm/coverage/src/analysis.rs | 6 +- crates/evm/coverage/src/lib.rs | 8 +- crates/evm/evm/src/executors/builder.rs | 8 +- crates/evm/evm/src/executors/corpus.rs | 10 +- crates/evm/evm/src/executors/fuzz/mod.rs | 20 +- crates/evm/evm/src/executors/invariant/mod.rs | 178 ++- .../evm/evm/src/executors/invariant/result.rs | 10 +- .../evm/evm/src/executors/invariant/shrink.rs | 175 ++- crates/evm/evm/src/executors/mod.rs | 74 +- crates/evm/evm/src/executors/trace.rs | 79 +- crates/evm/evm/src/inspectors/chisel_state.rs | 2 +- .../evm/src/inspectors/revert_diagnostic.rs | 6 +- crates/evm/evm/src/inspectors/stack.rs | 30 +- crates/evm/fuzz/src/invariant/mod.rs | 10 +- crates/evm/fuzz/src/strategies/int.rs | 2 +- crates/evm/fuzz/src/strategies/invariants.rs | 6 +- crates/evm/fuzz/src/strategies/state.rs | 4 +- crates/evm/fuzz/src/strategies/uint.rs | 2 +- crates/evm/hardforks/src/lib.rs | 6 +- crates/evm/networks/src/lib.rs | 8 +- crates/evm/traces/src/backtrace/mod.rs | 6 +- crates/evm/traces/src/debug/mod.rs | 4 +- crates/evm/traces/src/decoder/mod.rs | 26 +- crates/evm/traces/src/folded_stack_trace.rs | 2 +- crates/evm/traces/src/identifier/external.rs | 2 +- crates/evm/traces/src/identifier/local.rs | 4 +- crates/evm/traces/src/identifier/mod.rs | 2 +- .../evm/traces/src/identifier/signatures.rs | 2 +- crates/evm/traces/src/lib.rs | 8 +- crates/fmt/src/lib.rs | 10 +- crates/fmt/src/pp/convenience.rs | 6 +- crates/fmt/src/pp/mod.rs | 2 +- crates/fmt/src/pp/ring.rs | 2 +- crates/fmt/src/state/common.rs | 76 +- crates/fmt/src/state/mod.rs | 36 +- crates/fmt/src/state/sol.rs | 68 +- crates/fmt/src/state/yul.rs | 6 +- crates/forge/Cargo.toml | 16 +- crates/forge/src/cmd/bind.rs | 18 +- crates/forge/src/cmd/bind_json.rs | 12 +- crates/forge/src/cmd/build.rs | 2 +- crates/forge/src/cmd/compiler.rs | 12 +- crates/forge/src/cmd/coverage.rs | 5 +- crates/forge/src/cmd/create.rs | 20 +- crates/forge/src/cmd/doc/mod.rs | 2 +- crates/forge/src/cmd/doc/server.rs | 4 +- crates/forge/src/cmd/eip712.rs | 2 +- crates/forge/src/cmd/fmt.rs | 2 +- crates/forge/src/cmd/inspect.rs | 11 +- crates/forge/src/cmd/install.rs | 2 +- crates/forge/src/cmd/selectors.rs | 4 +- crates/forge/src/cmd/snapshot.rs | 8 +- crates/forge/src/cmd/test/filter.rs | 10 +- crates/forge/src/cmd/test/mod.rs | 24 +- crates/forge/src/cmd/test/summary.rs | 2 +- crates/forge/src/coverage.rs | 4 +- crates/forge/src/lockfile.rs | 8 +- crates/forge/src/multi_runner.rs | 24 +- crates/forge/src/result.rs | 18 +- crates/forge/src/runner.rs | 15 +- crates/forge/tests/cli/lint.rs | 2 +- crates/forge/tests/cli/script.rs | 7 +- .../tests/cli/test_cmd/invariant/common.rs | 117 +- crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 ++ crates/lint/src/linter/mod.rs | 14 +- .../sol/codesize/unwrapped_modifier_logic.rs | 2 +- crates/lint/src/sol/gas/custom_errors.rs | 2 +- crates/lint/src/sol/gas/keccak.rs | 98 +- crates/lint/src/sol/high/incorrect_shift.rs | 2 +- crates/lint/src/sol/high/unchecked_calls.rs | 2 +- crates/lint/src/sol/info/interface_naming.rs | 2 +- .../lint/src/sol/info/multi_contract_file.rs | 2 +- .../lint/src/sol/info/screaming_snake_case.rs | 4 +- crates/lint/src/sol/med/unsafe_typecast.rs | 5 +- crates/lint/src/sol/mod.rs | 12 +- crates/macros/src/cheatcodes.rs | 2 +- crates/macros/src/console_fmt.rs | 4 +- crates/primitives/src/transaction/envelope.rs | 4 +- crates/script-sequence/src/reader.rs | 2 +- crates/script-sequence/src/sequence.rs | 85 +- crates/script-sequence/src/transaction.rs | 10 +- crates/script/Cargo.toml | 1 + crates/script/src/broadcast.rs | 16 +- crates/script/src/build.rs | 38 +- crates/script/src/execute.rs | 15 +- crates/script/src/lib.rs | 15 +- crates/script/src/receipts.rs | 2 +- crates/script/src/runner.rs | 18 +- crates/script/src/simulate.rs | 68 +- crates/script/src/verify.rs | 12 +- crates/sol-macro-gen/src/sol_macro_gen.rs | 16 +- crates/test-utils/src/ext.rs | 4 +- crates/test-utils/src/prj.rs | 8 +- crates/test-utils/src/script.rs | 40 +- crates/test-utils/src/ui_runner.rs | 2 +- crates/test-utils/src/util.rs | 2 +- crates/verify/src/etherscan/flatten.rs | 6 +- crates/verify/src/etherscan/mod.rs | 6 +- crates/verify/src/etherscan/standard_json.rs | 11 +- crates/verify/src/provider.rs | 4 +- crates/verify/src/retry.rs | 2 +- crates/verify/src/utils.rs | 18 +- crates/wallets/src/error.rs | 8 +- crates/wallets/src/utils.rs | 6 +- crates/wallets/src/wallet_browser/server.rs | 6 +- crates/wallets/src/wallet_browser/signer.rs | 2 +- crates/wallets/src/wallet_browser/state.rs | 2 +- crates/wallets/src/wallet_browser/types.rs | 6 +- crates/wallets/src/wallet_multi/mod.rs | 6 +- deny.toml | 2 + flake.lock | 18 +- npm/package.json | 2 +- npm/scripts/stage-from-artifact.mjs | 28 +- npm/src/const.mjs | 30 +- 282 files changed, 5929 insertions(+), 2936 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/config.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/cli/src/opts/rpc_common.rs delete mode 100644 crates/evm/core/src/evm.rs create mode 100644 crates/evm/core/src/evm/eth.rs create mode 100644 crates/evm/core/src/evm/mod.rs create mode 100644 crates/evm/core/src/evm/op.rs create mode 100644 crates/evm/core/src/evm/tempo.rs create mode 100644 crates/lint/src/linter.rs diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..4168efef0971f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..53a505774ac88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 54106f083501d..e32978eaded37 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,5 +4,7 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 ignore: - dependency-name: "softprops/action-gh-release" diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 766beae8cc8d7..96d83592a4cc0 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -233,9 +233,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index 00eb791f1c211..b1d0bc51e69a8 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -36,13 +36,20 @@ jobs: - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2 with: tool: nextest + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev + run: pip --version && pip install vyper==0.4.3 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests env: SVM_TARGET_PLATFORM: linux-amd64 - HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} run: cargo nextest run --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 89bf27be4050e..baf21395303ff 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -40,13 +40,20 @@ jobs: - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2 with: tool: nextest + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev + run: pip --version && pip install vyper==0.4.3 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests with isolation env: SVM_TARGET_PLATFORM: linux-amd64 - HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e67498f679bf..2bf4ba9fc8cd2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,8 +111,6 @@ jobs: - name: Test env: SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} - HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} - WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index 03b5ad35d23ea..fce125e17c4ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -468,14 +468,14 @@ dependencies = [ "foldhash 0.2.0", "getrandom 0.4.2", "hashbrown 0.16.1", - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", "proptest-derive", - "rand 0.9.2", + "rand 0.9.3", "rapidhash", "ruint", "rustc-hash", @@ -885,7 +885,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", @@ -1056,9 +1056,9 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b669bf35e50f130e98212b486b0df78d93e285963344e58937692705e1a21a" +checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" dependencies = [ "anstyle", "memchr", @@ -1196,7 +1196,7 @@ dependencies = [ "clap", "clap_complete", "ctrlc", - "ethereum_ssz 0.10.1", + "ethereum_ssz 0.10.3", "eyre", "fdlimit", "flate2", @@ -1215,7 +1215,7 @@ dependencies = [ "op-revm", "parking_lot", "rand 0.8.5", - "rand 0.9.2", + "rand 0.9.3", "reqwest 0.13.2", "revm", "revm-inspectors", @@ -1252,7 +1252,7 @@ dependencies = [ "foundry-common", "foundry-evm", "foundry-primitives", - "rand 0.9.2", + "rand 0.9.3", "revm", "serde", "serde_json", @@ -2350,7 +2350,7 @@ dependencies = [ "boa_interner", "boa_macros", "boa_string", - "indexmap 2.13.1", + "indexmap 2.14.0", "num-bigint", "rustc-hash", ] @@ -2382,7 +2382,7 @@ dependencies = [ "futures-lite", "hashbrown 0.16.1", "icu_normalizer", - "indexmap 2.13.1", + "indexmap 2.14.0", "intrusive-collections", "itertools 0.14.0", "num-bigint", @@ -2391,7 +2391,7 @@ dependencies = [ "num_enum", "paste", "portable-atomic", - "rand 0.9.2", + "rand 0.9.3", "regress", "rustc-hash", "ryu-js", @@ -2428,7 +2428,7 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.16.1", - "indexmap 2.13.1", + "indexmap 2.14.0", "once_cell", "phf 0.13.1", "rustc-hash", @@ -2711,7 +2711,7 @@ dependencies = [ "op-alloy-flz", "op-alloy-network", "rand 0.8.5", - "rand 0.9.2", + "rand 0.9.3", "rayon", "regex", "revm", @@ -2739,9 +2739,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -2915,9 +2915,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" +checksum = "406e68b4de5c59cfb8f750a7cbd4d31ae153788b8352167c1e5f4fc26e8c91e9" dependencies = [ "clap", ] @@ -4263,9 +4263,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -4290,9 +4290,9 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4329,7 +4329,7 @@ checksum = "61e332670cb19161dee3f15ae86bfb5e4361bb1716d7c1995bd309b5360cb630" dependencies = [ "alloy-dyn-abi", "alloy-primitives", - "indexmap 2.13.1", + "indexmap 2.14.0", ] [[package]] @@ -4350,9 +4350,9 @@ checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] name = "fastrand" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -4571,7 +4571,7 @@ dependencies = [ "path-slash", "proptest", "quick-junit", - "rand 0.9.2", + "rand 0.9.3", "rayon", "regex", "reqwest 0.13.2", @@ -4680,6 +4680,7 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-evm-networks", "foundry-linking", "foundry-wallets", "futures", @@ -4851,7 +4852,7 @@ dependencies = [ "p256", "parking_lot", "proptest", - "rand 0.9.2", + "rand 0.9.3", "revm", "revm-inspectors", "semver 1.0.28", @@ -4903,6 +4904,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-evm-networks", "foundry-wallets", "futures", "indicatif", @@ -5037,7 +5039,7 @@ dependencies = [ "fs_extra", "itertools 0.14.0", "path-slash", - "rand 0.9.2", + "rand 0.9.3", "rayon", "semver 1.0.28", "serde", @@ -5246,6 +5248,7 @@ dependencies = [ "alloy-rpc-types", "alloy-serde 2.0.0-rc.0", "alloy-sol-types", + "anvil", "auto_impl", "eyre", "foundry-cheatcodes-spec", @@ -5312,7 +5315,7 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "proptest", - "rand 0.9.2", + "rand 0.9.3", "revm", "serde", "solar-compiler", @@ -5386,7 +5389,7 @@ dependencies = [ [[package]] name = "foundry-fork-db" version = "0.24.1" -source = "git+https://github.com/foundry-rs/foundry-fork-db?branch=main#767dcd5e6e773dde1dd10faef677bb9291cc4bf7" +source = "git+https://github.com/foundry-rs/foundry-fork-db?branch=main#a29e143c9c44cf09525fc453f40ec1414fa36b51" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5481,7 +5484,7 @@ dependencies = [ "foundry-config", "idna_adapter", "parking_lot", - "rand 0.9.2", + "rand 0.9.3", "regex", "reqwest 0.13.2", "serde_json", @@ -5850,7 +5853,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.13.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -5919,6 +5922,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" @@ -6363,13 +6372,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -6687,9 +6696,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "cfg-if", "futures-util", @@ -6877,9 +6886,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] @@ -7033,7 +7042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a229930b29a9908560883e1f386eae25d8a971d259a80f49916a50627f04a42d" dependencies = [ "anyhow", - "indexmap 2.13.1", + "indexmap 2.14.0", "mdbook-core", "mdbook-html", "mdbook-markdown", @@ -7063,7 +7072,7 @@ dependencies = [ "handlebars", "hex", "html5ever", - "indexmap 2.13.1", + "indexmap 2.14.0", "mdbook-core", "mdbook-markdown", "mdbook-renderer", @@ -7732,8 +7741,8 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-serde 2.0.0-rc.0", "derive_more", - "ethereum_ssz 0.10.1", - "ethereum_ssz_derive 0.10.1", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "op-alloy-consensus", "serde", "sha2 0.10.9", @@ -7969,7 +7978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.13.1", + "indexmap 2.14.0", ] [[package]] @@ -8273,7 +8282,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.10+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -8314,7 +8323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.13.1", + "indexmap 2.14.0", "nix 0.31.2", "tokio", "tracing", @@ -8331,7 +8340,7 @@ dependencies = [ "bit-vec", "bitflags 2.11.0", "num-traits", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -8466,7 +8475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ee9342d671fae8d66b3ae9fd7a9714dfd089c04d2a8b1ec0436ef77aee15e5f" dependencies = [ "chrono", - "indexmap 2.13.1", + "indexmap 2.14.0", "newtype-uuid", "quick-xml 0.38.4", "strip-ansi-escapes", @@ -8522,7 +8531,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.3", "ring", "rustc-hash", "rustls", @@ -8599,9 +8608,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -8662,7 +8671,7 @@ version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" dependencies = [ - "rand 0.9.2", + "rand 0.9.3", "rustversion", ] @@ -9612,7 +9621,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.2", + "rand 0.9.3", "rlp", "ruint-macro", "serde_core", @@ -9776,9 +9785,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" dependencies = [ "aws-lc-rs", "ring", @@ -9969,7 +9978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.3", "secp256k1-sys 0.11.0", ] @@ -10130,7 +10139,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -10180,7 +10189,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.1", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -10507,7 +10516,7 @@ source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138d dependencies = [ "bumpalo", "index_vec", - "indexmap 2.13.1", + "indexmap 2.14.0", "parking_lot", "rayon", "rustc-hash", @@ -10519,7 +10528,7 @@ name = "solar-interface" version = "0.1.8" source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "annotate-snippets 0.12.14", + "annotate-snippets 0.12.15", "anstream 0.6.21", "anstyle", "derive_more", @@ -11221,9 +11230,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +checksum = "da322882471314edc77fa5232c587bcb87c9df52bfd0d7d4826f8868ead61899" [[package]] name = "thiserror" @@ -11450,7 +11459,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", @@ -11483,7 +11492,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", @@ -11498,7 +11507,7 @@ version = "0.24.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -11507,11 +11516,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.10+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", @@ -11588,7 +11597,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.1", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -11785,7 +11794,7 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "rand 0.9.2", + "rand 0.9.3", "rustls", "rustls-pki-types", "sha1", @@ -11795,9 +11804,9 @@ dependencies = [ [[package]] name = "turnkey_api_key_stamper" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4fc991208d20002cc9d05f21d5b6646078cc4809fb74c9dcc720c58ff16f060" +checksum = "34f72e05a07cb04163efff0c766521ebacbb268a851db83d419b7c56df90d046" dependencies = [ "base64 0.22.1", "hex", @@ -11811,9 +11820,9 @@ dependencies = [ [[package]] name = "turnkey_client" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0587a85462ebb2ceda8e01c38081fefb43d0819e652d59bbfc86ac3a1731f2" +checksum = "0c3b2cb46984c849dbd514bc9d5271620d3e52267ebd1cafd1c95d3fa3b4cf6d" dependencies = [ "mime", "prost 0.12.6", @@ -12233,9 +12242,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -12246,9 +12255,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ "js-sys", "wasm-bindgen", @@ -12256,9 +12265,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12266,9 +12275,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -12279,9 +12288,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -12303,7 +12312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.1", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -12342,7 +12351,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "semver 1.0.28", ] @@ -12419,9 +12428,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -12936,7 +12945,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -12967,7 +12976,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -12986,7 +12995,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "semver 1.0.28", "serde", @@ -13203,7 +13212,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.13.1", + "indexmap 2.14.0", "memchr", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index 73efb0b87a05a..0d328597b95c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,33 +48,56 @@ clear_with_drain = "warn" cloned_instead_of_copied = "warn" collection_is_never_read = "warn" dbg_macro = "warn" +derive_partial_eq_without_eq = "warn" empty_line_after_doc_comments = "warn" empty_line_after_outer_attr = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" +flat_map_option = "warn" +from_iter_instead_of_collect = "warn" +if_then_some_else_none = "warn" implicit_clone = "warn" +if_not_else = "warn" imprecise_flops = "warn" iter_on_empty_collections = "warn" iter_with_drain = "warn" iter_without_into_iter = "warn" +large_stack_frames = "warn" manual_assert = "warn" manual_clamp = "warn" manual_string_new = "warn" +missing_const_for_fn = "warn" mutex_integer = "warn" naive_bytecount = "warn" needless_bitwise_bool = "warn" +needless_continue = "warn" nonstandard_macro_braces = "warn" +option_as_ref_cloned = "warn" path_buf_push_overwrite = "warn" read_zero_byte_vec = "warn" redundant_clone = "warn" +single_char_pattern = "warn" redundant_else = "warn" +string_lit_as_bytes = "warn" string_lit_chars_any = "warn" suboptimal_flops = "warn" suspicious_operation_groupings = "warn" +trailing_empty_array = "warn" +trait_duplication_in_bounds = "warn" transmute_undefined_repr = "warn" +trivial_regex = "warn" +tuple_array_conversions = "warn" uninhabited_references = "warn" +unnecessary_struct_initialization = "warn" uninlined_format_args = "warn" +unnecessary_self_imports = "warn" +unnested_or_patterns = "warn" +unused_peekable = "warn" unused_rounding = "warn" use_self = "warn" +useless_let_if_seq = "warn" while_float = "warn" zero_sized_map_values = "warn" @@ -92,8 +115,6 @@ significant_drop_tightening = "allow" too_long_first_doc_paragraph = "allow" # Specific allows. -# until is fixed -literal_string_with_formatting_args = "allow" result_large_err = "allow" [workspace.lints.rust] diff --git a/benches/src/lib.rs b/benches/src/lib.rs index b4b3de2c3ddf3..ab3bec614e3cd 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } @@ -175,13 +185,13 @@ impl BenchmarkProject { .status() .wrap_err("Failed to run npm install")?; - if !status.success() { + if status.success() { + sh_println!(" ✅ npm install completed successfully"); + } else { sh_println!( " ⚠️ Warning: npm install failed with exit code: {:?}", status.code() ); - } else { - sh_println!(" ✅ npm install completed successfully"); } } Ok(()) diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index aee27d15c600e..1b5e231124a78 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -31,7 +31,7 @@ use self::serde_helpers::*; /// Wrapper type that ensures the type is named `params` #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] -pub struct Params { +pub struct Params { #[serde(default)] pub params: T, } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index f967dcd6ddd61..056d7a9602855 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -33,17 +33,17 @@ impl Typed2718 for MaybeImpersonatedTransaction { impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction - pub fn new(transaction: T) -> Self { + pub const fn new(transaction: T) -> Self { Self { transaction, impersonated_sender: None } } /// Creates a new impersonated transaction wrapper using the given sender - pub fn impersonated(transaction: T, impersonated_sender: Address) -> Self { + pub const fn impersonated(transaction: T, impersonated_sender: Address) -> Self { Self { transaction, impersonated_sender: Some(impersonated_sender) } } /// Returns whether the transaction is impersonated - pub fn is_impersonated(&self) -> bool { + pub const fn is_impersonated(&self) -> bool { self.impersonated_sender.is_some() } @@ -137,11 +137,11 @@ pub struct PendingTransaction { } impl PendingTransaction { - pub fn hash(&self) -> &TxHash { + pub const fn hash(&self) -> &TxHash { &self.hash } - pub fn sender(&self) -> &Address { + pub const fn sender(&self) -> &Address { &self.sender } } diff --git a/crates/anvil/rpc/src/error.rs b/crates/anvil/rpc/src/error.rs index 2e1d90e5be997..2e5877d5f2b8b 100644 --- a/crates/anvil/rpc/src/error.rs +++ b/crates/anvil/rpc/src/error.rs @@ -94,7 +94,7 @@ pub enum ErrorCode { impl ErrorCode { /// Returns the error code as `i64` - pub fn code(&self) -> i64 { + pub const fn code(&self) -> i64 { match *self { Self::ParseError => -32700, Self::InvalidRequest => -32600, diff --git a/crates/anvil/rpc/src/request.rs b/crates/anvil/rpc/src/request.rs index 5cb8510b80f2b..eade8b25fe3cd 100644 --- a/crates/anvil/rpc/src/request.rs +++ b/crates/anvil/rpc/src/request.rs @@ -84,7 +84,7 @@ impl From for serde_json::Value { } } -fn no_params() -> RequestParams { +const fn no_params() -> RequestParams { RequestParams::None } @@ -113,7 +113,7 @@ impl fmt::Display for Id { } } -fn null_id() -> Id { +const fn null_id() -> Id { Id::Null } diff --git a/crates/anvil/rpc/src/response.rs b/crates/anvil/rpc/src/response.rs index ff47ac77e7fdf..b7f2d9f478092 100644 --- a/crates/anvil/rpc/src/response.rs +++ b/crates/anvil/rpc/src/response.rs @@ -49,7 +49,7 @@ impl ResponseResult { Self::Success(serde_json::to_value(&content).unwrap()) } - pub fn error(error: RpcError) -> Self { + pub const fn error(error: RpcError) -> Self { Self::Error(error) } } diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index dd15959b113a6..d2a2d4430131e 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -27,7 +27,7 @@ impl ServerConfig { } /// Whether to enable CORS. - pub fn set_cors(mut self, cors: bool) -> Self { + pub const fn set_cors(mut self, cors: bool) -> Self { self.no_cors = !cors; self } diff --git a/crates/anvil/server/src/ipc.rs b/crates/anvil/server/src/ipc.rs index 4a9133ee6dd58..757cdb05b2a2e 100644 --- a/crates/anvil/server/src/ipc.rs +++ b/crates/anvil/server/src/ipc.rs @@ -23,7 +23,7 @@ pub struct IpcEndpoint { impl IpcEndpoint { /// Creates a new endpoint with the given handler - pub fn new(handler: Handler, path: String) -> Self { + pub const fn new(handler: Handler, path: String) -> Self { Self { handler, path } } diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 03f4fcd05178c..c9b63f08cb2f5 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -511,7 +511,7 @@ impl Default for NodeConfig { impl NodeConfig { /// Returns the memory limit of the node #[must_use] - pub fn with_memory_limit(mut self, mems_value: Option) -> Self { + pub const fn with_memory_limit(mut self, mems_value: Option) -> Self { self.memory_limit = mems_value; self } @@ -579,13 +579,13 @@ impl NodeConfig { /// Sets a custom code size limit #[must_use] - pub fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { + pub const fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { self.code_size_limit = code_size_limit; self } /// Disables code size limit #[must_use] - pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { + pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { if disable_code_size_limit { self.code_size_limit = Some(usize::MAX); } @@ -636,7 +636,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub const fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } @@ -645,7 +645,7 @@ impl NodeConfig { /// /// If set to `true` block gas limit will not be enforced #[must_use] - pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { + pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { self.disable_block_gas_limit = disable_block_gas_limit; self } @@ -654,14 +654,14 @@ impl NodeConfig { /// /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) #[must_use] - pub fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { + pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { self.enable_tx_gas_limit = enable_tx_gas_limit; self } /// Sets the gas price #[must_use] - pub fn with_gas_price(mut self, gas_price: Option) -> Self { + pub const fn with_gas_price(mut self, gas_price: Option) -> Self { self.gas_price = gas_price; self } @@ -685,7 +685,7 @@ impl NodeConfig { /// Sets the max number of transactions in a block #[must_use] - pub fn with_max_transactions(mut self, max_transactions: Option) -> Self { + pub const fn with_max_transactions(mut self, max_transactions: Option) -> Self { if let Some(max_transactions) = max_transactions { self.max_transactions = max_transactions; } @@ -704,14 +704,14 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee(mut self, base_fee: Option) -> Self { + pub const fn with_base_fee(mut self, base_fee: Option) -> Self { self.base_fee = base_fee; self } /// Disable the enforcement of a minimum suggested priority fee #[must_use] - pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { self.disable_min_priority_fee = disable_min_priority_fee; self } @@ -757,7 +757,7 @@ impl NodeConfig { /// Sets the hardfork #[must_use] - pub fn with_hardfork(mut self, hardfork: Option) -> Self { + pub const fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } @@ -811,21 +811,21 @@ impl NodeConfig { /// If set to `true` auto mining will be disabled #[must_use] - pub fn with_no_mining(mut self, no_mining: bool) -> Self { + pub const fn with_no_mining(mut self, no_mining: bool) -> Self { self.no_mining = no_mining; self } /// Sets the slots in an epoch #[must_use] - pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { self.slots_in_an_epoch = slots_in_an_epoch; self } /// Sets the port to use #[must_use] - pub fn with_port(mut self, port: u16) -> Self { + pub const fn with_port(mut self, port: u16) -> Self { self.port = port; self } @@ -850,7 +850,7 @@ impl NodeConfig { } #[must_use] - pub fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { + pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { self.no_storage_caching = no_storage_caching; self } @@ -886,7 +886,7 @@ impl NodeConfig { /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { + pub const fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id; self } @@ -900,7 +900,7 @@ impl NodeConfig { /// Sets the `fork_request_timeout` to use for requests #[must_use] - pub fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { + pub const fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { if let Some(fork_request_timeout) = fork_request_timeout { self.fork_request_timeout = fork_request_timeout; } @@ -909,7 +909,7 @@ impl NodeConfig { /// Sets the `fork_request_retries` to use for spurious networks #[must_use] - pub fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { + pub const fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { if let Some(fork_request_retries) = fork_request_retries { self.fork_request_retries = fork_request_retries; } @@ -918,7 +918,7 @@ impl NodeConfig { /// Sets the initial `fork_retry_backoff` for rate limits #[must_use] - pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { + pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { if let Some(fork_retry_backoff) = fork_retry_backoff { self.fork_retry_backoff = fork_retry_backoff; } @@ -929,7 +929,10 @@ impl NodeConfig { /// /// See also, #[must_use] - pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { + pub const fn fork_compute_units_per_second( + mut self, + compute_units_per_second: Option, + ) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { self.compute_units_per_second = compute_units_per_second; } @@ -938,35 +941,35 @@ impl NodeConfig { /// Sets whether to enable tracing #[must_use] - pub fn with_tracing(mut self, enable_tracing: bool) -> Self { + pub const fn with_tracing(mut self, enable_tracing: bool) -> Self { self.enable_tracing = enable_tracing; self } /// Sets whether to enable steps tracing #[must_use] - pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { + pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { self.enable_steps_tracing = enable_steps_tracing; self } /// Sets whether to print `console.log` invocations to stdout. #[must_use] - pub fn with_print_logs(mut self, print_logs: bool) -> Self { + pub const fn with_print_logs(mut self, print_logs: bool) -> Self { self.print_logs = print_logs; self } /// Sets whether to print traces to stdout. #[must_use] - pub fn with_print_traces(mut self, print_traces: bool) -> Self { + pub const fn with_print_traces(mut self, print_traces: bool) -> Self { self.print_traces = print_traces; self } /// Sets whether to enable autoImpersonate #[must_use] - pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { + pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { self.enable_auto_impersonate = enable_auto_impersonate; self } @@ -985,7 +988,7 @@ impl NodeConfig { } #[must_use] - pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { + pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { self.transaction_order = transaction_order; self } @@ -1024,14 +1027,14 @@ impl NodeConfig { /// Sets whether to disable the default create2 deployer #[must_use] - pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { self.disable_default_create2_deployer = yes; self } /// Sets whether to disable pool balance checks #[must_use] - pub fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { + pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { self.disable_pool_balance_checks = yes; self } @@ -1045,7 +1048,7 @@ impl NodeConfig { /// Enable features for provided networks. #[must_use] - pub fn with_networks(mut self, networks: NetworkConfigs) -> Self { + pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self { self.networks = networks; self } @@ -1066,12 +1069,12 @@ impl NodeConfig { /// Makes the node silent to not emit anything on stdout #[must_use] - pub fn silent(self) -> Self { + pub const fn silent(self) -> Self { self.set_silent(true) } #[must_use] - pub fn set_silent(mut self, silent: bool) -> Self { + pub const fn set_silent(mut self, silent: bool) -> Self { self.silent = silent; self } @@ -1541,7 +1544,7 @@ pub enum ForkChoice { impl ForkChoice { /// Returns the block number to fork from - pub fn block_number(&self) -> Option { + pub const fn block_number(&self) -> Option { match self { Self::Block(block_number) => Some(*block_number), Self::Transaction(_) => None, @@ -1549,7 +1552,7 @@ impl ForkChoice { } /// Returns the transaction hash to fork from - pub fn transaction_hash(&self) -> Option { + pub const fn transaction_hash(&self) -> Option { match self { Self::Block(_) => None, Self::Transaction(transaction_hash) => Some(*transaction_hash), @@ -1579,12 +1582,12 @@ pub struct PruneStateHistoryConfig { impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported - pub fn is_state_history_supported(&self) -> bool { + pub const fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } /// Returns true if this setting was enabled. - pub fn is_config_enabled(&self) -> bool { + pub const fn is_config_enabled(&self) -> bool { self.enabled } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index af27b01dca34e..1909e50548d4a 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -418,7 +418,7 @@ impl EthApi { } }) .unwrap_or_default(), - network: if self.backend.is_tempo() { Some("tempo".to_string()) } else { None }, + network: self.backend.is_tempo().then(|| "tempo".to_string()), }) } @@ -477,6 +477,10 @@ impl EthApi { /// Sets the specific timestamp and returns the number of seconds between the given timestamp /// and the current time. /// + /// The `timestamp` is in seconds. The `evm_setTime` JSON-RPC method accepts both seconds and + /// milliseconds (values above 1e12 are treated as milliseconds); the RPC handler normalises + /// the input to seconds before calling this function. + /// /// Handler for RPC call: `evm_setTime` pub fn evm_set_time(&self, timestamp: u64) -> Result { node_info!("evm_setTime"); @@ -485,7 +489,7 @@ impl EthApi { // number of seconds between the given timestamp and the current time. let offset = timestamp.saturating_sub(now); - Ok(Duration::from_millis(offset).as_secs()) + Ok(offset) } /// Set the next block gas limit @@ -625,6 +629,7 @@ impl EthApi { } /// Handler for RPC call: `anvil_getBlobByHash` + #[allow(clippy::large_stack_frames)] pub fn anvil_get_blob_by_versioned_hash( &self, hash: B256, @@ -1480,6 +1485,7 @@ impl EthApi { } /// Executes the [EthRequest] and returns an RPC [ResponseResult]. + #[allow(clippy::large_stack_frames)] pub async fn execute(&self, request: EthRequest) -> ResponseResult { trace!(target: "rpc::api", "executing eth request"); let response = match request.clone() { @@ -1753,7 +1759,15 @@ impl EthApi { "The timestamp is too big", )); } - let time = timestamp.to::(); + // evm_setTime accepts either seconds or milliseconds for Ganache compatibility. + // Timestamps above 1e12 are interpreted as milliseconds and converted to + // seconds; below that threshold they are treated as seconds directly. + let raw = timestamp.to::(); + let time = if raw > 1_000_000_000_000 { + Duration::from_millis(raw).as_secs() + } else { + raw + }; self.evm_set_time(time).to_rpc_result() } EthRequest::EvmSetBlockGasLimit(gas_limit) => { diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index c529b96dd81b3..d5d1638ef3b71 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -97,7 +97,7 @@ pub struct CheatsState { } impl CheatEcrecover { - pub fn new(cheats: Arc) -> Self { + pub const fn new(cheats: Arc) -> Self { Self { cheats } } } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index b27ee942c87bb..de9ad434252f3 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -226,7 +226,7 @@ pub trait Db: /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { - for (addr, account) in state.accounts.into_iter() { + for (addr, account) in state.accounts { let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) @@ -250,7 +250,7 @@ pub trait Db: }, ); - for (k, v) in account.storage.into_iter() { + for (k, v) in account.storage { self.set_storage_at(addr, k, v)?; } } diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 70d3599a765f1..3df14f6ea86f3 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -436,13 +436,11 @@ where trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out); trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used); - let contract_address = if pending.transaction.to().is_none() { + let contract_address = pending.transaction.to().is_none().then(|| { let addr = sender.create(nonce); trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); - Some(addr) - } else { - None - }; + addr + }); // TODO: replace `TransactionInfo` with alloy receipt/transaction types let transaction_index = tx_info.len() as u64; diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 09871d32c0120..bd3d1292f01b5 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -370,23 +370,8 @@ impl ClientFork { number: u64, index: usize, ) -> Result, TransportError> { - if let Some(block) = self.block_by_number(number).await? { - #[allow(clippy::collapsible_match)] - match block.transactions() { - BlockTransactions::Full(txs) => { - if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())); - } - } - BlockTransactions::Hashes(hashes) => { - if let Some(tx_hash) = hashes.get(index) { - return self.transaction_by_hash(*tx_hash).await; - } - } - BlockTransactions::Uncle => {} - } - } - Ok(None) + let block = self.block_by_number(number).await?; + self.transaction_at_block_index(block, index).await } pub async fn transaction_by_block_hash_and_index( @@ -394,8 +379,16 @@ impl ClientFork { hash: B256, index: usize, ) -> Result, TransportError> { - if let Some(block) = self.block_by_hash(hash).await? { - #[allow(clippy::collapsible_match)] + let block = self.block_by_hash(hash).await?; + self.transaction_at_block_index(block, index).await + } + + async fn transaction_at_block_index( + &self, + block: Option, + index: usize, + ) -> Result, TransportError> { + if let Some(block) = block { match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index def04501abaa8..c739cd26d1de6 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -18,7 +18,7 @@ pub struct StorageInfo { } impl StorageInfo { - pub(crate) fn new(backend: Arc>) -> Self { + pub(crate) const fn new(backend: Arc>) -> Self { Self { backend } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index f8dd6012a4295..be57f40a8fd0a 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -141,7 +141,8 @@ use tempo_chainspec::hardfork::TempoHardfork; use tempo_evm::evm::TempoEvmFactory; use tempo_primitives::TEMPO_TX_TYPE_ID; use tempo_revm::{ - TempoBlockEnv, TempoHaltReason, TempoTxEnv, evm::TempoContext, gas_params::tempo_gas_params, + TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv, evm::TempoContext, + gas_params::tempo_gas_params, }; use tokio::sync::RwLock as AsyncRwLock; @@ -182,7 +183,7 @@ impl fmt::Debug for BlockRequest { } impl BlockRequest { - pub fn block_number(&self) -> BlockNumber { + pub const fn block_number(&self) -> BlockNumber { match *self { Self::Pending(_) => BlockNumber::Pending, Self::Number(n) => BlockNumber::Number(n), @@ -351,12 +352,12 @@ impl Backend { } /// Returns the `TimeManager` responsible for timestamps - pub fn time(&self) -> &TimeManager { + pub const fn time(&self) -> &TimeManager { &self.time } /// Returns the `CheatsManager` responsible for executing cheatcodes - pub fn cheats(&self) -> &CheatsManager { + pub const fn cheats(&self) -> &CheatsManager { &self.cheats } @@ -368,12 +369,12 @@ impl Backend { } /// Returns the `FeeManager` that manages fee/pricings - pub fn fees(&self) -> &FeeManager { + pub const fn fees(&self) -> &FeeManager { &self.fees } /// The EVM environment data of the blockchain - pub fn evm_env(&self) -> &Arc> { + pub const fn evm_env(&self) -> &Arc> { &self.evm_env } @@ -407,7 +408,7 @@ impl Backend { } /// Returns the genesis data for the Beacon API. - pub fn genesis_time(&self) -> u64 { + pub const fn genesis_time(&self) -> u64 { self.genesis.timestamp } @@ -482,17 +483,17 @@ impl Backend { } /// Returns true if op-stack deposits are active - pub fn is_optimism(&self) -> bool { + pub const fn is_optimism(&self) -> bool { self.networks.is_optimism() } /// Returns true if Tempo network mode is active - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.networks.is_tempo() } /// Returns the active hardfork. - pub fn hardfork(&self) -> FoundryHardfork { + pub const fn hardfork(&self) -> FoundryHardfork { self.hardfork } @@ -581,7 +582,7 @@ impl Backend { } /// Returns an error if op-stack deposits are not active - pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { + pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { return Ok(()); } @@ -589,7 +590,7 @@ impl Backend { } /// Returns an error if Tempo transactions are not active - pub fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { + pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { if self.is_tempo() { return Ok(()); } @@ -636,7 +637,7 @@ impl Backend { } /// Returns whether the minimum suggested priority fee is enforced - pub fn is_min_priority_fee_enforced(&self) -> bool { + pub const fn is_min_priority_fee_enforced(&self) -> bool { self.fees.is_min_priority_fee_enforced() } @@ -1222,7 +1223,7 @@ impl Backend { I: Inspector>>, WrapDatabaseRef<&'db DB>: Database, { - let hardfork = TempoHardfork::from(evm_env.cfg_env.spec); + let hardfork = TempoHardfork::from(self.hardfork); let tempo_env = EvmEnv::new( evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)), TempoBlockEnv { inner: evm_env.block_env.clone(), timestamp_millis_part: 0 }, @@ -1292,7 +1293,7 @@ impl Backend { let mut evm = OpEvmFactory::default().create_evm_with_inspector(db, op_env, inspector); run!(evm) } else if self.is_tempo() { - let hardfork = TempoHardfork::from(evm_env.cfg_env.spec); + let hardfork = TempoHardfork::from(self.hardfork); let tempo_env = EvmEnv::new( evm_env .cfg_env @@ -1383,11 +1384,7 @@ impl Backend { gas_priority_fee: max_priority_fee_per_gas, max_fee_per_blob_gas: max_fee_per_blob_gas .or_else(|| { - if !blob_hashes.is_empty() { - evm_env.block_env.blob_gasprice() - } else { - Some(0) - } + if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() } }) .unwrap_or_default(), kind: match to { @@ -1415,8 +1412,10 @@ impl Backend { evm_env.cfg_env.disable_base_fee = true; } - // Deposit transaction? - if let Ok(deposit) = get_deposit_tx_parts(&other) { + // Deposit transaction? (only valid when op-stack deposits are active) + if self.ensure_op_deposits_active().is_ok() + && let Ok(deposit) = get_deposit_tx_parts(&other) + { tx_env.deposit = deposit; } @@ -1432,9 +1431,59 @@ impl Backend { ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { let mut inspector = self.build_inspector(); + // Extract Tempo-specific fields before `build_call_env` consumes `other`. + let tempo_overrides = self.is_tempo().then(|| { + let fee_token = + request.other.get_deserialized::
("feeToken").and_then(|r| r.ok()); + let nonce_key = request + .other + .get_deserialized::("nonceKey") + .and_then(|r| r.ok()) + .unwrap_or_default(); + let valid_before = request + .other + .get_deserialized::("validBefore") + .and_then(|r| r.ok()) + .map(|v| v.saturating_to::()); + let valid_after = request + .other + .get_deserialized::("validAfter") + .and_then(|r| r.ok()) + .map(|v| v.saturating_to::()); + (fee_token, nonce_key, valid_before, valid_after) + }); + let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let ResultAndState { result, state } = - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?; + if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides { + use tempo_primitives::transaction::Call; + + let base = tx_env.base; + let mut tempo_tx = TempoTxEnv::from(base.clone()); + tempo_tx.fee_token = fee_token; + + if !nonce_key.is_zero() || valid_before.is_some() || valid_after.is_some() { + // For gas estimation we don't have a signed tx, so generate a + // unique hash for expiring-nonce replay protection. The nonce + // manager needs a non-zero hash; the actual value doesn't matter + // because the state is discarded after estimation. + let estimation_hash = keccak256(base.data.as_ref()); + tempo_tx.tempo_tx_env = Some(Box::new(TempoBatchCallEnv { + nonce_key, + valid_before, + valid_after, + aa_calls: vec![Call { to: base.kind, value: base.value, input: base.data }], + tx_hash: estimation_hash, + expiring_nonce_hash: Some(estimation_hash), + ..Default::default() + })); + } + self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)? + } else { + self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)? + }; + let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); inspector.print_logs(); @@ -1797,6 +1846,7 @@ impl Backend { })) } + #[allow(clippy::large_stack_frames)] pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result> { let storage = self.blockchain.storage.read(); for block in storage.blocks.values() { @@ -2135,7 +2185,7 @@ impl Backend { } // Clear all storage and reinitialize with genesis - let base_fee = if self.fees.is_eip1559() { Some(self.fees.base_fee()) } else { None }; + let base_fee = self.fees.is_eip1559().then(|| self.fees.base_fee()); *self.blockchain.storage.write() = BlockchainStorage::new( &self.evm_env.read(), base_fee, @@ -2328,12 +2378,12 @@ where // get the range that predates the fork if any if let Some(fork) = self.get_fork() { - let mut to_on_fork = to; - - if !fork.predates_fork(to) { + let to_on_fork = if fork.predates_fork(to) { + to + } else { // adjust the ranges - to_on_fork = fork.block_number(); - } + fork.block_number() + }; if fork.predates_fork_inclusive(from) { // this data is only available on the forked client @@ -2403,6 +2453,56 @@ where self.do_mine_block(pool_transactions).await } + /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions. + fn build_block_info( + evm_env: &EvmEnv, + parent_hash: B256, + number: u64, + state_root: B256, + block_result: BlockExecutionResult, + transactions: Vec>, + transaction_infos: Vec, + ) -> BlockInfo { + let spec_id = *evm_env.spec_id(); + let is_shanghai = spec_id >= SpecId::SHANGHAI; + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + + let receipts_root = calculate_receipt_root(&block_result.receipts); + let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used); + let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| { + b.accrue_bloom(r.logs_bloom()); + b + }); + + let header = Header { + parent_hash, + ommers_hash: Default::default(), + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root: Default::default(), + receipts_root, + logs_bloom: bloom, + difficulty: evm_env.block_env.difficulty, + number, + gas_limit: evm_env.block_env.gas_limit, + gas_used: block_result.gas_used, + timestamp: evm_env.block_env.timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: Default::default(), + base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee), + parent_beacon_block_root: is_cancun.then_some(Default::default()), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None }, + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), + }; + + let block = create_block(header, transactions); + BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts } + } + async fn do_mine_block( &self, pool_transactions: Vec>>, @@ -2457,18 +2557,6 @@ where evm_env.block_env.timestamp = U256::from(self.time.next_timestamp()); let spec_id = *evm_env.spec_id(); - let is_shanghai = spec_id >= SpecId::SHANGHAI; - let is_cancun = spec_id >= SpecId::CANCUN; - let is_prague = spec_id >= SpecId::PRAGUE; - let gas_limit = evm_env.block_env.gas_limit; - let difficulty = evm_env.block_env.difficulty; - let mix_hash = evm_env.block_env.prevrandao; - let beneficiary = evm_env.block_env.beneficiary; - let timestamp = evm_env.block_env.timestamp; - let base_fee = - if spec_id >= SpecId::LONDON { Some(evm_env.block_env.basefee) } else { None }; - let excess_blob_gas = - if is_cancun { evm_env.block_env.blob_excess_gas() } else { None }; let inspector_tx_config = self.inspector_tx_config(); let gas_config = self.pool_tx_gas_config(&evm_env); @@ -2489,50 +2577,17 @@ where let included = pool_result.included; let invalid = pool_result.invalid; let not_yet_valid = pool_result.not_yet_valid; - let transaction_infos = pool_result.tx_info; - let transactions = pool_result.txs; let state_root = db.maybe_state_root().unwrap_or_default(); - - // 7. Build block header - let receipts_root = calculate_receipt_root(&block_result.receipts); - let cumulative_gas_used = block_result.gas_used; - let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used); - let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| { - b.accrue_bloom(r.logs_bloom()); - b - }); - - let header = Header { - parent_hash: best_hash, - ommers_hash: Default::default(), - beneficiary, + let block_info = Self::build_block_info( + &evm_env, + best_hash, + block_number, state_root, - transactions_root: Default::default(), - receipts_root, - logs_bloom: bloom, - difficulty, - number: block_number, - gas_limit, - gas_used: cumulative_gas_used, - timestamp: timestamp.saturating_to(), - extra_data: Default::default(), - mix_hash: mix_hash.unwrap_or_default(), - nonce: Default::default(), - base_fee_per_gas: base_fee, - parent_beacon_block_root: is_cancun.then_some(Default::default()), - blob_gas_used: cumulative_blob_gas_used, - excess_blob_gas, - withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), - }; - - let block = create_block(header, transactions); - let block_info: BlockInfo = BlockInfo { - block, - transactions: transaction_infos, - receipts: block_result.receipts, - }; + block_result, + pool_result.txs, + pool_result.tx_info, + ); // update the new blockhash in the db itself let block_hash = block_info.block.header.hash_slow(); @@ -2700,17 +2755,6 @@ where let parent_hash = self.blockchain.storage.read().best_hash; let spec_id = *evm_env.spec_id(); - let is_shanghai = spec_id >= SpecId::SHANGHAI; - let is_cancun = spec_id >= SpecId::CANCUN; - let is_prague = spec_id >= SpecId::PRAGUE; - let gas_limit = evm_env.block_env.gas_limit; - let difficulty = evm_env.block_env.difficulty; - let mix_hash = evm_env.block_env.prevrandao; - let beneficiary = evm_env.block_env.beneficiary; - let timestamp = evm_env.block_env.timestamp; - let base_fee = - if spec_id >= SpecId::LONDON { Some(evm_env.block_env.basefee) } else { None }; - let excess_blob_gas = if is_cancun { evm_env.block_env.blob_excess_gas() } else { None }; let inspector_tx_config = self.inspector_tx_config(); let gas_config = self.pool_tx_gas_config(&evm_env); @@ -2726,49 +2770,20 @@ where &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env), ); - let transaction_infos = pool_result.tx_info; - let transactions = pool_result.txs; - // Extract inner CacheDB (which implements MaybeFullDatabase) let cache_db = cache_db.0; let state_root = cache_db.maybe_state_root().unwrap_or_default(); - - let receipts_root = calculate_receipt_root(&block_result.receipts); - let cumulative_gas_used = block_result.gas_used; - let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used); - let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| { - b.accrue_bloom(r.logs_bloom()); - b - }); - - let header = Header { + let block_number = evm_env.block_env.number.saturating_to(); + let block_info = Self::build_block_info( + &evm_env, parent_hash, - ommers_hash: Default::default(), - beneficiary, + block_number, state_root, - transactions_root: Default::default(), - receipts_root, - logs_bloom: bloom, - difficulty, - number: evm_env.block_env.number.saturating_to(), - gas_limit, - gas_used: cumulative_gas_used, - timestamp: timestamp.saturating_to(), - extra_data: Default::default(), - mix_hash: mix_hash.unwrap_or_default(), - nonce: Default::default(), - base_fee_per_gas: base_fee, - parent_beacon_block_root: is_cancun.then_some(Default::default()), - blob_gas_used: cumulative_blob_gas_used, - excess_blob_gas, - withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), - }; - - let block = create_block(header, transactions); - let block_info = - BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }; + block_result, + pool_result.txs, + pool_result.tx_info, + ); f(Box::new(cache_db), block_info) } @@ -3680,11 +3695,8 @@ impl> Backend { let best_number = self.blockchain.storage.read().best_number; let blocks = self.blockchain.storage.read().serialized_blocks(); let transactions = self.blockchain.storage.read().serialized_transactions(); - let historical_states = if preserve_historical_states { - Some(self.states.write().serialized_states()) - } else { - None - }; + let historical_states = + preserve_historical_states.then(|| self.states.write().serialized_states()); let state = self.db.read().await.dump_state( at, diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 3cb4c869db14e..a80f6bfd1f5e8 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -85,7 +85,7 @@ impl InMemoryBlockStates { } /// Configures no disk caching - pub fn memory_only(mut self) -> Self { + pub const fn memory_only(mut self) -> Self { self.max_on_disk_limit = 0; self } @@ -112,7 +112,7 @@ impl InMemoryBlockStates { } /// Returns true if only memory caching is supported. - fn is_memory_only(&self) -> bool { + const fn is_memory_only(&self) -> bool { self.max_on_disk_limit == 0 } @@ -197,7 +197,7 @@ impl InMemoryBlockStates { } /// Sets the maximum number of stats we keep in memory - pub fn set_cache_limit(&mut self, limit: usize) { + pub const fn set_cache_limit(&mut self, limit: usize) { self.in_memory_limit = limit; } @@ -605,14 +605,15 @@ impl MinedTransaction { CallKind::Create2 => OperationType::OpCreate2, _ => return None, }; - let mut from = node.trace.caller; - let mut to = node.trace.address; - let mut value = node.trace.value; - if node.is_selfdestruct() { - from = node.trace.address; - to = node.trace.selfdestruct_refund_target.unwrap_or_default(); - value = node.trace.selfdestruct_transferred_value.unwrap_or_default(); - } + let (from, to, value) = if node.is_selfdestruct() { + ( + node.trace.address, + node.trace.selfdestruct_refund_target.unwrap_or_default(), + node.trace.selfdestruct_transferred_value.unwrap_or_default(), + ) + } else { + (node.trace.caller, node.trace.address, node.trace.value) + }; Some(InternalOperation { r#type, from, to, value }) }) .collect() diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index c6a8660e02425..e5cfa9ef7a08a 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -84,7 +84,7 @@ impl FeeManager { } /// Returns the base fee params used for EIP-1559 calculations - pub fn base_fee_params(&self) -> BaseFeeParams { + pub const fn base_fee_params(&self) -> BaseFeeParams { self.base_fee_params } @@ -93,11 +93,11 @@ impl FeeManager { } /// Returns true for post London - pub fn is_eip1559(&self) -> bool { + pub const fn is_eip1559(&self) -> bool { (self.spec_id as u8) >= (SpecId::LONDON as u8) } - pub fn is_eip4844(&self) -> bool { + pub const fn is_eip4844(&self) -> bool { (self.spec_id as u8) >= (SpecId::CANCUN as u8) } @@ -110,7 +110,7 @@ impl FeeManager { if self.is_eip1559() { *self.base_fee.read() } else { 0 } } - pub fn is_min_priority_fee_enforced(&self) -> bool { + pub const fn is_min_priority_fee_enforced(&self) -> bool { self.is_min_priority_fee_enforced } @@ -120,7 +120,7 @@ impl FeeManager { } pub fn excess_blob_gas_and_price(&self) -> Option { - if self.is_eip4844() { Some(*self.blob_excess_gas_and_price.read()) } else { None } + self.is_eip4844().then(|| *self.blob_excess_gas_and_price.read()) } pub fn base_fee_per_blob_gas(&self) -> u128 { @@ -210,7 +210,7 @@ impl FeeHistoryService where N::ReceiptEnvelope: TxReceipt, { - pub fn new( + pub const fn new( blob_params: BlobParams, new_blocks: NewBlockNotifications, cache: FeeHistoryCache, @@ -226,7 +226,7 @@ where } /// Returns the configured history limit - pub fn fee_history_limit(&self) -> u64 { + pub const fn fee_history_limit(&self) -> u64 { self.fee_history_limit } @@ -392,7 +392,7 @@ pub struct FeeDetails { impl FeeDetails { /// All values zero - pub fn zero() -> Self { + pub const fn zero() -> Self { Self { gas_price: Some(0), max_fee_per_gas: Some(0), @@ -402,7 +402,7 @@ impl FeeDetails { } /// If neither `gas_price` nor `max_fee_per_gas` is `Some`, this will set both to `0` - pub fn or_zero_fees(self) -> Self { + pub const fn or_zero_fees(self) -> Self { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; @@ -415,7 +415,7 @@ impl FeeDetails { } /// Turns this type into a tuple - pub fn split(self) -> (Option, Option, Option, Option) { + pub const fn split(self) -> (Option, Option, Option, Option) { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 666ffb1771dac..d6a99f50cd08f 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -124,11 +124,11 @@ impl Pool { }; trace!(target: "txpool", "Dropped transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); - let mut dropped = None; - if !removed.is_empty() { - dropped = removed.into_iter().find(|t| *t.pending_transaction.hash() == tx); + if removed.is_empty() { + None + } else { + removed.into_iter().find(|t| *t.pending_transaction.hash() == tx) } - dropped } /// Notifies listeners if the transaction was added to the ready queue. @@ -510,7 +510,7 @@ pub enum AddedTransaction { } impl AddedTransaction { - pub fn hash(&self) -> &TxHash { + pub const fn hash(&self) -> &TxHash { match self { Self::Ready(tx) => &tx.hash, Self::Pending { hash } => hash, diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 98c16fed375c9..df65822e1eab3 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -91,7 +91,7 @@ pub struct PoolTransaction { // == impl PoolTransaction == impl PoolTransaction { - pub fn new(transaction: PendingTransaction) -> Self { + pub const fn new(transaction: PendingTransaction) -> Self { Self { pending_transaction: transaction, requires: vec![], @@ -101,7 +101,7 @@ impl PoolTransaction { } /// Returns the hash of this transaction - pub fn hash(&self) -> TxHash { + pub const fn hash(&self) -> TxHash { *self.pending_transaction.hash() } } @@ -276,7 +276,7 @@ impl PendingTransactions { .and_then(|hash| self.waiting_queue.get(hash)) { // check if underpriced - if tx.transaction.max_fee_per_gas() < replace.transaction.max_fee_per_gas() { + if tx.transaction.max_fee_per_gas() <= replace.transaction.max_fee_per_gas() { warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]", tx.transaction.hash()); return Err(PoolError::ReplacementUnderpriced(tx.transaction.hash())); } @@ -466,11 +466,11 @@ impl ReadyTransactions { self.ready_tx.read().get(hash).cloned() } - pub fn provided_markers(&self) -> &HashMap { + pub const fn provided_markers(&self) -> &HashMap { &self.provided_markers } - fn next_id(&mut self) -> u64 { + const fn next_id(&mut self) -> u64 { let id = self.id; self.id = self.id.wrapping_add(1); id @@ -515,11 +515,7 @@ impl ReadyTransactions { if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } - if tx2.unlocks.is_empty() { - Some(tx2.transaction.transaction.provides.clone()) - } else { - None - } + tx2.unlocks.is_empty().then(|| tx2.transaction.transaction.provides.clone()) }; // find previous transactions diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index b96ac52b799d9..98db8a5455df9 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -74,7 +74,7 @@ impl Filters { } /// The duration how long to keep alive stale filters - pub fn keep_alive(&self) -> Duration { + pub const fn keep_alive(&self) -> Duration { self.keepalive } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 6b2345aa3685f..26e587e8b5123 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -310,7 +310,7 @@ impl Drop for NodeHandle { impl NodeHandle { /// The [NodeConfig] the node was launched with. - pub fn config(&self) -> &NodeConfig { + pub const fn config(&self) -> &NodeConfig { &self.config } @@ -387,7 +387,7 @@ impl NodeHandle { } /// Native token balance of every genesis account in the genesis block. - pub fn genesis_balance(&self) -> U256 { + pub const fn genesis_balance(&self) -> U256 { self.config.genesis_balance } @@ -397,14 +397,14 @@ impl NodeHandle { } /// Returns the shutdown signal. - pub fn shutdown_signal(&self) -> &Option { + pub const fn shutdown_signal(&self) -> &Option { &self._signal } /// Returns mutable access to the shutdown signal. /// /// This can be used to extract the Signal. - pub fn shutdown_signal_mut(&mut self) -> &mut Option { + pub const fn shutdown_signal_mut(&mut self) -> &mut Option { &mut self._signal } @@ -423,7 +423,7 @@ impl NodeHandle { /// /// # } /// ``` - pub fn task_manager(&self) -> &TaskManager { + pub const fn task_manager(&self) -> &TaskManager { &self.task_manager } } @@ -465,15 +465,15 @@ pub fn init_tracing() -> LoggingManager { let manager = LoggingManager::default(); let _ = if let Ok(rust_log_val) = std::env::var("RUST_LOG") - && !rust_log_val.contains("=") + && !rust_log_val.contains('=') { // Mutate the given filter to include `node` logs if it is not already present. // This prevents the unexpected behaviour of not seeing any node logs if a RUST_LOG // is already present that doesn't set it. - let rust_log_val = if !rust_log_val.contains("node") { - format!("{rust_log_val},node=info") - } else { + let rust_log_val = if rust_log_val.contains("node") { rust_log_val + } else { + format!("{rust_log_val},node=info") }; let env_filter: EnvFilter = diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index 9f0a8d85e9920..31737aa4a3e07 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -22,7 +22,7 @@ pub struct NodeLogLayer { impl NodeLogLayer { /// Returns a new instance of this layer - pub fn new(state: LoggingManager) -> Self { + pub const fn new(state: LoggingManager) -> Self { Self { state } } } diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index 6ada777082bd1..4ceb8be0bfc64 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -81,7 +81,7 @@ pub struct EthSubscriptionResponse { } impl EthSubscriptionResponse { - pub fn new(params: EthSubscriptionParams) -> Self { + pub const fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } } } diff --git a/crates/anvil/src/server/rpc_handlers.rs b/crates/anvil/src/server/rpc_handlers.rs index 321970bd03bcf..87e993c4ba75a 100644 --- a/crates/anvil/src/server/rpc_handlers.rs +++ b/crates/anvil/src/server/rpc_handlers.rs @@ -22,7 +22,7 @@ pub struct HttpEthRpcHandler { impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub const fn new(api: EthApi) -> Self { Self { api } } } @@ -45,7 +45,7 @@ pub struct PubSubEthRpcHandler { impl PubSubEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub const fn new(api: EthApi) -> Self { Self { api } } diff --git a/crates/anvil/src/tasks/block_listener.rs b/crates/anvil/src/tasks/block_listener.rs index d729c6b63686d..073368a8afdb9 100644 --- a/crates/anvil/src/tasks/block_listener.rs +++ b/crates/anvil/src/tasks/block_listener.rs @@ -20,7 +20,7 @@ where St: Stream, F: Fn(::Item) -> Fut, { - pub fn new(on_shutdown: Shutdown, block_stream: St, task_factory: F) -> Self { + pub const fn new(on_shutdown: Shutdown, block_stream: St, task_factory: F) -> Self { Self { stream: block_stream, task_factory, task: None, on_shutdown } } } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 3d0b38bbb64cd..10def5d64c04e 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -26,7 +26,7 @@ pub struct TaskManager { impl TaskManager { /// Creates a new instance of the task manager - pub fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { + pub const fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { Self { tokio_handle, on_shutdown } } diff --git a/crates/anvil/tests/it/tempo.rs b/crates/anvil/tests/it/tempo.rs index 29615d0cf8806..08c48aaee7716 100644 --- a/crates/anvil/tests/it/tempo.rs +++ b/crates/anvil/tests/it/tempo.rs @@ -2170,3 +2170,488 @@ async fn test_tempo_aa_nonce_too_high_rejected() { tokio::time::sleep(std::time::Duration::from_millis(100)).await; } } + +// ============================================================================ +// Gas Estimation: Tempo AA Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default().from(accounts[0]).to(PATH_USD).with_input(calldata), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + assert!( + gas_estimate > 21000, + "Tempo AA gas estimate should be greater than 21000, got: {gas_estimate}" + ); +} + +// ============================================================================ +// Gas Estimation: Tempo AA with 2D Nonce +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_with_2d_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no 2D nonce) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + // 2D nonce tx with nonce=0 (new nonce key) + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!("0x64")), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // New 2D nonce key (nonce=0) charges COLD_SLOAD + SSTORE_SET = 22100 gas + let nonce_key_delta = gas_estimate - baseline_gas; + assert!( + nonce_key_delta >= 22_100, + "2D nonce should add >= 22100 gas, got delta: {nonce_key_delta} \ + (baseline: {baseline_gas}, 2d_nonce: {gas_estimate})" + ); +} + +// ============================================================================ +// Gas Estimation: Tempo AA with Expiring Nonce +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_expiring_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no expiring nonce) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + let max_nonce_key = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(max_nonce_key)), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // At T0, expiring nonces are treated as 2D nonces (22100 gas). + // At T1+, this charges EXPIRING_NONCE_GAS = 13000 instead. + let expiring_delta = gas_estimate - baseline_gas; + assert!( + expiring_delta >= 22_100, + "Expiring nonce should add >= 22100 gas at T0, got delta: {expiring_delta} \ + (baseline: {baseline_gas}, expiring: {gas_estimate})" + ); +} + +// ============================================================================ +// Gas Estimation: T1 Hardfork Nonce Gas Costs +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_t1_nonce_costs() { + use tempo_chainspec::hardfork::TempoHardfork; + + let (_api, handle) = + spawn(NodeConfig::test_tempo().with_hardfork(Some(TempoHardfork::T1.into()))).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no nonce key) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + // TIP-1000: nonce=0 pays 250k for account creation + assert!( + baseline_gas > 250_000, + "T1 baseline should include 250k (TIP-1000), got: {baseline_gas}" + ); + + // Existing 2D nonce key (nonce=1) charges 5,000 gas + let nonce_2d_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(1), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!("0x64")), + ] + .into_iter() + .collect(), + }; + let nonce_2d_gas = provider.estimate_gas(nonce_2d_tx).await.unwrap(); + + let baseline_nonce1_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(1), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_nonce1_gas = provider.estimate_gas(baseline_nonce1_tx).await.unwrap(); + let nonce_2d_delta = nonce_2d_gas - baseline_nonce1_gas; + + assert!( + nonce_2d_delta >= 5_000, + "T1: existing 2D nonce key should add >= 5000 gas, got: {nonce_2d_delta}" + ); + + // TIP-1000: nonce=0 should cost 250k more than nonce=1 + let tip1000_delta = baseline_gas - baseline_nonce1_gas; + assert!( + tip1000_delta >= 250_000, + "T1: TIP-1000 delta should be >= 250000, got: {tip1000_delta}" + ); + + // Expiring nonce (nonce_key=MAX) at T1 should charge ~13K for ring buffer ops + // (2*COLD_SLOAD + WARM_SLOAD + 3*WARM_SSTORE_RESET), NOT 22K like at T0. + let max_nonce_key = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let valid_before = block.header.timestamp + 25; + + let expiring_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(max_nonce_key)), + ("validBefore".to_string(), serde_json::json!(valid_before)), + ] + .into_iter() + .collect(), + }; + let expiring_gas = provider.estimate_gas(expiring_tx).await.unwrap(); + + // Compare against baseline_nonce1 (nonce=1, no nonce key) since both the baseline (nonce=0) + // and expiring tx (nonce=0) include the 250k account creation cost. + let expiring_delta = expiring_gas - baseline_nonce1_gas; + assert!( + expiring_delta >= 13_000, + "T1: expiring nonce should add at least ~13K gas for ring buffer ops, got delta: {expiring_delta}" + ); +} + +// ============================================================================ +// Gas Estimation: 2D Nonce Estimate Sufficient for Real Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_2d_nonce_converges() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Estimate gas with 2D nonce + let nonce_key = U256::from(0x64); + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(format!("{nonce_key:#x}"))), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // Send the actual transaction with the estimated gas to verify it's sufficient + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(PATH_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: gas_estimate, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!( + receipt.status(), + "2D nonce transaction should succeed with estimated gas: {gas_estimate}" + ); + assert!( + receipt.gas_used() <= gas_estimate, + "Gas used ({}) should be <= estimate ({}) for 2D nonce tx", + receipt.gas_used(), + gas_estimate + ); +} + +// ============================================================================ +// Gas Estimation: Converges for Tempo Intrinsic Gas +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_converges_for_tempo_intrinsic_gas() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // Send the actual transaction with the estimated gas to verify convergence + let signer = dev_key(0); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(PATH_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: gas_estimate, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::ZERO, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "Transaction should succeed with estimated gas: {gas_estimate}"); + + assert!( + receipt.gas_used() <= gas_estimate, + "Gas used ({}) should be <= estimate ({})", + receipt.gas_used(), + gas_estimate + ); +} + +// ============================================================================ +// EIP-1559 Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_eip1559_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(500_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let base_fee = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .max_fee_per_gas(base_fee * 2) + .max_priority_fee_per_gas(base_fee / 10); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "EIP-1559 transaction should succeed"); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + transfer_amount, + "Recipient should receive transfer amount" + ); +} + +// ============================================================================ +// Legacy Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_legacy_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(250_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let gas_price = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .with_gas_price(gas_price); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "Legacy transaction should succeed"); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + transfer_amount, + "Recipient should receive transfer amount" + ); +} diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index c4ffcdf810d8d..125c613b49969 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -55,6 +55,7 @@ pub fn setup() -> Result<()> { } /// Run the subcommand. +#[allow(clippy::large_stack_frames)] pub async fn run_command(args: CastArgs) -> Result<()> { match args.cmd { // Constants @@ -195,10 +196,10 @@ pub async fn run_command(args: CastArgs) -> Result<()> { print_tokens(&tokens); } CastSubcommand::AbiEncode { sig, packed, args } => { - if !packed { - sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? - } else { + if packed { sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? + } else { + sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? } } CastSubcommand::AbiEncodeEvent { sig, args } => { diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 239dbdfb1010c..085df0b55e89a 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -314,7 +314,7 @@ impl NumberWithBase { } /// Creates a copy of the number with the provided base. - pub fn with_base(&self, base: Base) -> Self { + pub const fn with_base(&self, base: Base) -> Self { Self { number: self.number, is_nonnegative: self.is_nonnegative, base } } @@ -336,17 +336,17 @@ impl NumberWithBase { /// Returns a copy of the underlying number as an unsigned integer. If the value is negative /// then the two's complement of its absolute value will be returned. - pub fn number(&self) -> U256 { + pub const fn number(&self) -> U256 { self.number } /// Returns whether the underlying number is positive or zero. - pub fn is_nonnegative(&self) -> bool { + pub const fn is_nonnegative(&self) -> bool { self.is_nonnegative } /// Returns the underlying base. Defaults to [Decimal][Base]. - pub fn base(&self) -> Base { + pub const fn base(&self) -> Base { self.base } @@ -356,7 +356,7 @@ impl NumberWithBase { } /// Sets the number's base to format to. - pub fn set_base(&mut self, base: Base) -> &mut Self { + pub const fn set_base(&mut self, base: Base) -> &mut Self { self.base = base; self } diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index ec8725ae8c1ab..e2793f7de7571 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -10,7 +10,7 @@ use crate::{ use alloy_consensus::SignableTransaction; use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes}; use alloy_provider::Provider; use alloy_signer::Signer; use clap::Parser; @@ -115,9 +115,7 @@ impl BatchMakeTxArgs { // Set dummy "to" from first call let first_call_to = call_specs.first().map(|s| s.to); let builder = builder.with_to(first_call_to.map(Into::into)).await?; - let mut tx_builder = builder.with_code_sig_and_args(None, None, vec![]).await?; - tx_builder.tx.clear_kind(); - tx_builder.tx.set_value(U256::ZERO); + let tx_builder = builder.with_code_sig_and_args(None, None, vec![]).await?; if raw_unsigned { if eth.wallet.from.is_none() && !has_nonce { diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index 4f88913d929da..c381e5f7a1e2a 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -9,8 +9,8 @@ use crate::{ cmd::send::cast_send, tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}, }; -use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{Bytes, U256}; +use alloy_network::EthereumWallet; +use alloy_primitives::Bytes; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use clap::Parser; @@ -119,9 +119,7 @@ impl BatchSendArgs { let builder = builder.with_to(first_call_to.map(Into::into)).await?; // Use empty sig/args since we're using calls directly - let mut builder = builder.with_code_sig_and_args(None, None, vec![]).await?; - builder.tx.clear_kind(); - builder.tx.set_value(U256::ZERO); + let builder = builder.with_code_sig_and_args(None, None, vec![]).await?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); diff --git a/crates/cast/src/cmd/create2.rs b/crates/cast/src/cmd/create2.rs index a531c95856666..f7e5aae1f2d9e 100644 --- a/crates/cast/src/cmd/create2.rs +++ b/crates/cast/src/cmd/create2.rs @@ -209,6 +209,7 @@ impl Create2Args { let found = Arc::clone(&found); handles.push(std::thread::spawn(move || { // Read the first bytes of the salt as a usize to be able to increment it. + #[repr(C)] struct B256Aligned(B256, [usize; 0]); let mut salt = B256Aligned(salt, []); // SAFETY: B256 is aligned to `usize`. diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index 39f09a6426f81..07e7df1b67517 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -299,7 +299,7 @@ pub enum Erc20Subcommand { } impl Erc20Subcommand { - fn rpc_opts(&self) -> &RpcOpts { + const fn rpc_opts(&self) -> &RpcOpts { match self { Self::Allowance { rpc, .. } => rpc, Self::Approve { send_tx, .. } => &send_tx.eth.rpc, @@ -314,7 +314,7 @@ impl Erc20Subcommand { } } - fn erc20_opts(&self) -> Option<&Erc20TxOpts> { + const fn erc20_opts(&self) -> Option<&Erc20TxOpts> { match self { Self::Approve { tx, .. } | Self::Transfer { tx, .. } diff --git a/crates/cast/src/cmd/interface.rs b/crates/cast/src/cmd/interface.rs index 634fc96d83c31..2e2051822dafc 100644 --- a/crates/cast/src/cmd/interface.rs +++ b/crates/cast/src/cmd/interface.rs @@ -80,7 +80,7 @@ impl InterfaceArgs { }; // Build config for to_sol conversion. - let config = if flatten { Some(ToSolConfig::new().one_contract(true)) } else { None }; + let config = flatten.then(|| ToSolConfig::new().one_contract(true)); // Retrieve interfaces from the array of ABIs. let interfaces = get_interfaces(abis, config)?; diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index c6a9646a6b2ab..d14c043a1620a 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -231,7 +231,7 @@ fn parse_signature_type(s: &str) -> Result { } } -fn signature_type_name(t: &SignatureType) -> &'static str { +const fn signature_type_name(t: &SignatureType) -> &'static str { match t { SignatureType::Secp256k1 => "secp256k1", SignatureType::P256 => "p256", @@ -240,7 +240,7 @@ fn signature_type_name(t: &SignatureType) -> &'static str { } } -fn key_type_name(t: &KeyType) -> &'static str { +const fn key_type_name(t: &KeyType) -> &'static str { match t { KeyType::Secp256k1 => "secp256k1", KeyType::P256 => "p256", @@ -248,7 +248,7 @@ fn key_type_name(t: &KeyType) -> &'static str { } } -fn wallet_type_name(t: &WalletType) -> &'static str { +const fn wallet_type_name(t: &WalletType) -> &'static str { match t { WalletType::Local => "local", WalletType::Passkey => "passkey", @@ -546,7 +546,9 @@ async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) // Expiry: show human-readable date and whether it's expired. let expiry_str = format_expiry(info.expiry); - if info.expiry != u64::MAX { + if info.expiry == u64::MAX { + sh_println!("Expiry: {}", expiry_str)?; + } else { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() @@ -556,8 +558,6 @@ async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) } else { sh_println!("Expiry: {}", expiry_str)?; } - } else { - sh_println!("Expiry: {}", expiry_str)?; } sh_println!("Spending Limits: {}", if info.enforceLimits { "enforced" } else { "none" })?; @@ -579,7 +579,17 @@ async fn run_authorize( ) -> Result<()> { let enforce = enforce_limits || !limits.is_empty(); - let calldata = if !allowed_calls.is_empty() { + let calldata = if allowed_calls.is_empty() { + // Use the legacy authorizeKey when no scopes are needed. + IAccountKeychain::authorizeKeyCall { + keyId: key_address, + signatureType: key_type, + expiry, + enforceLimits: enforce, + limits, + } + .abi_encode() + } else { // Use the T3+ authorizeKey overload with KeyRestrictions when scopes are provided. let sig_type_u8 = match key_type { SignatureType::Secp256k1 => 0u8, @@ -603,16 +613,6 @@ async fn run_authorize( config: restrictions, } .abi_encode() - } else { - // Use the legacy authorizeKey when no scopes are needed. - IAccountKeychain::authorizeKeyCall { - keyId: key_address, - signatureType: key_type, - expiry, - enforceLimits: enforce, - limits, - } - .abi_encode() }; send_keychain_tx(calldata, tx_opts, &send_tx).await @@ -783,10 +783,10 @@ fn print_key_entry(entry: &tempo::KeyEntry) -> Result<()> { if let Some(key_address) = entry.key_address { sh_println!("Key Address: {key_address}")?; - if key_address != entry.wallet_address { - sh_println!("Mode: keychain (access key)")?; - } else { + if key_address == entry.wallet_address { sh_println!("Mode: direct (EOA)")?; + } else { + sh_println!("Mode: keychain (access key)")?; } } else { sh_println!("Key Address: (not set)")?; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 6a21db65026f6..aece03350df60 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -1,11 +1,6 @@ -use crate::{ - debug::handle_traces, - utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, -}; -use alloy_consensus::{BlockHeader, Transaction, transaction::SignerRecoverable}; - -use alloy_evm::FromRecoveredTx; -use alloy_network::{BlockResponse, TransactionResponse}; +use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_changes}; +use alloy_consensus::Transaction; +use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ Address, Bytes, U256, map::{AddressSet, HashMap}, @@ -18,9 +13,7 @@ use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{TraceResult, init_progress}, }; -use foundry_common::{ - SYSTEM_TRANSACTION_TYPE, is_known_system_sender, provider::ProviderBuilder, shell, -}; +use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_impersonated_tx, is_known_system_sender, shell}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ Config, @@ -30,17 +23,14 @@ use foundry_config::{ }, }; use foundry_evm::{ - core::{ - FoundryBlock as _, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, - }, + Env, executors::{EvmError, Executor, TracingExecutor}, - hardforks::FoundryHardfork, opts::EvmOpts, traces::{InternalTraceMode, TraceMode, Traces}, + utils::configure_tx_env, }; use futures::TryFutureExt; -use revm::{DatabaseRef, context::Block}; +use revm::{DatabaseRef, primitives::hardfork::SpecId}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -132,22 +122,6 @@ impl RunArgs { /// /// Note: This executes the transaction(s) as is: Cheatcodes are disabled pub async fn run(self) -> Result<()> { - let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); - let mut evm_opts = figment.extract::()?; - - // Auto-detect network from fork chain ID when not explicitly configured. - evm_opts.infer_network_from_fork().await; - - if evm_opts.networks.is_tempo() { - self.run_with_evm::().await - } else if evm_opts.networks.is_optimism() { - self.run_with_evm::().await - } else { - self.run_with_evm::().await - } - } - - async fn run_with_evm(self) -> Result<()> { let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::from_provider(figment)?.sanitized(); @@ -160,7 +134,7 @@ impl RunArgs { let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = ProviderBuilder::::from_config(&config)? + let provider = foundry_cli::utils::get_provider_builder(&config)? .compute_units_per_second_opt(compute_units_per_second) .build()?; @@ -182,52 +156,56 @@ impl RunArgs { )); } - let tx_block_number = tx - .block_number() - .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; + let tx_block_number = + tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; - let (block, (mut evm_env, tx_env, fork, chain, networks)) = tokio::try_join!( + let (block, (mut env, fork, chain, networks)) = tokio::try_join!( // fetch the block the transaction was mined in provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into), - TracingExecutor::::get_fork_material(&mut config, evm_opts) + TracingExecutor::get_fork_material(&mut config, evm_opts) )?; let mut evm_version = self.evm_version; - evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; + env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825). // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true. if !self.enable_tx_gas_limit { - evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + env.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); } - evm_env.cfg_env.limit_contract_code_size = None; - evm_env.block_env.set_number(U256::from(tx_block_number)); + env.evm_env.cfg_env.limit_contract_code_size = None; + env.evm_env.block_env.number = U256::from(tx_block_number); + + let mut parent_beacon_block_root = None; if let Some(block) = &block { - evm_env.block_env = block_env_from_header(block.header()); + env.evm_env.block_env.timestamp = U256::from(block.header.timestamp); + env.evm_env.block_env.beneficiary = block.header.beneficiary; + env.evm_env.block_env.difficulty = block.header.difficulty; + env.evm_env.block_env.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); + env.evm_env.block_env.gas_limit = block.header.gas_limit; + + if env.evm_env.cfg_env.spec >= SpecId::CANCUN { + parent_beacon_block_root = block.header.parent_beacon_block_root; + } - // Resolve the correct spec for the block using the same approach as reth: walk - // known chain activation conditions to find the latest active fork. Falls back - // to a blob-gas heuristic for unknown chains. + // TODO: we need a smarter way to map the block to the corresponding evm_version for + // commonly used chains if evm_version.is_none() { - if let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp( - evm_env.cfg_env.chain_id, - block.header().timestamp(), - ) { - evm_env.cfg_env.set_spec(hardfork.into()); - } else if block.header().excess_blob_gas().is_some() { - // TODO: add glamsterdam header field checks in the future - evm_version = Some(EvmVersion::Cancun); + // if the block has the excess_blob_gas field, we assume it's a Cancun block + if block.header.excess_blob_gas.is_some() { + evm_version = Some(EvmVersion::Prague); } } - apply_chain_and_block_specific_env_changes::( - &mut evm_env, + apply_chain_and_block_specific_env_changes::( + &mut env.evm_env, block, config.networks, ); @@ -241,8 +219,8 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::::new( - (evm_env.clone(), tx_env), + let mut executor = TracingExecutor::new( + env.clone(), fork, evm_version, trace_mode, @@ -251,7 +229,17 @@ impl RunArgs { None, )?; - evm_env.cfg_env.set_spec_and_mainnet_gas_params(executor.spec_id()); + if let Some(parent_beacon_block_root) = parent_beacon_block_root { + let timestamp: u64 = env.evm_env.block_env.timestamp.try_into().wrap_err("failed to convert block timestamp to u64")?; + executor.process_beacon_block_root(timestamp, parent_beacon_block_root)?; + } + + let mut env = Env::new_with_spec_id( + env.evm_env.cfg_env.clone(), + env.evm_env.block_env.clone(), + env.tx.clone(), + executor.spec_id(), + ); // Set the state to the moment right before the transaction if !self.quick { @@ -260,10 +248,10 @@ impl RunArgs { } if let Some(block) = block { - let pb = init_progress(block.transactions().len() as u64, "tx"); + let pb = init_progress(block.transactions.len() as u64, "tx"); pb.set_position(0); - let BlockTransactions::Full(ref txs) = *block.transactions() else { + let BlockTransactions::Full(ref txs) = block.transactions else { return Err(eyre::eyre!("Could not get block txs")); }; @@ -282,26 +270,22 @@ impl RunArgs { break; } - let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); + configure_tx_env(&mut env, tx); - evm_env.cfg_env.disable_balance_check = true; + env.evm_env.cfg_env.disable_balance_check = true; if let Some(to) = Transaction::to(tx) { trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); - executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( - || { - format!( - "Failed to execute transaction: {:?} in block {}", - tx.tx_hash(), - evm_env.block_env.number() - ) - }, - )?; + executor.transact_with_env(env.clone()).wrap_err_with(|| { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + env.evm_env.block_env.number + ) + })?; } else { trace!(tx=?tx.tx_hash(), "executing previous create transaction"); - if let Err(error) = - executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) - { + if let Err(error) = executor.deploy_with_env(env.clone(), None) { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), @@ -310,7 +294,7 @@ impl RunArgs { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), - evm_env.block_env.number() + env.evm_env.block_env.number ) }); } @@ -327,18 +311,17 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); - - if tx.as_ref().recover_signer().is_ok_and(|signer| signer != tx.from()) { - evm_env.cfg_env.disable_balance_check = true; + configure_tx_env(&mut env, &tx); + if is_impersonated_tx(tx.as_ref()) { + env.evm_env.cfg_env.disable_balance_check = true; } if let Some(to) = Transaction::to(&tx) { trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); - TraceResult::from(executor.transact_with_env(evm_env, tx_env)?) + TraceResult::try_from(executor.transact_with_env(env))? } else { trace!(tx=?tx.tx_hash(), "executing create transaction"); - TraceResult::try_from(executor.deploy_with_env(evm_env, tx_env, None))? + TraceResult::try_from(executor.deploy_with_env(env, None))? } }; @@ -361,8 +344,8 @@ impl RunArgs { } } -pub fn fetch_contracts_bytecode_from_trace( - executor: &Executor, +pub fn fetch_contracts_bytecode_from_trace( + executor: &Executor, result: &TraceResult, ) -> Result> { let mut contracts_bytecode = HashMap::default(); diff --git a/crates/cast/src/cmd/storage.rs b/crates/cast/src/cmd/storage.rs index b2a2c32251f6f..1295b49c19551 100644 --- a/crates/cast/src/cmd/storage.rs +++ b/crates/cast/src/cmd/storage.rs @@ -382,7 +382,7 @@ fn add_storage_layout_output>(project: }) } -fn is_storage_layout_empty(storage_layout: &Option) -> bool { +const fn is_storage_layout_empty(storage_layout: &Option) -> bool { if let Some(s) = storage_layout { s.storage.is_empty() } else { true } } diff --git a/crates/cast/src/cmd/tip20.rs b/crates/cast/src/cmd/tip20.rs index 24428aef36cf0..fc1cb6c9bec5e 100644 --- a/crates/cast/src/cmd/tip20.rs +++ b/crates/cast/src/cmd/tip20.rs @@ -145,7 +145,7 @@ impl Tip20TxOpts { } impl Tip20Subcommand { - fn rpc_opts(&self) -> &RpcOpts { + const fn rpc_opts(&self) -> &RpcOpts { match self { Self::Create { send_tx, .. } => &send_tx.eth.rpc, } diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 9381454f9b57b..8e0dd8dd3ed8c 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -313,7 +313,7 @@ impl WalletSubcommands { Self::New { path, account_name, unsafe_password, number, password, force } => { let mut rng = thread_rng(); - let mut json_values = if shell::is_json() { Some(vec![]) } else { None }; + let mut json_values = shell::is_json().then(std::vec::Vec::new); let path = if let Some(path) = path { match dunce::canonicalize(&path) { diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 2cc51a04d1ee9..ce5572acebc13 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -93,7 +93,7 @@ impl + Clone + Unpin, N: Network> Cast { /// # Ok(()) /// # } /// ``` - pub fn new(provider: P) -> Self { + pub const fn new(provider: P) -> Self { Self { provider, _phantom: PhantomData } } @@ -155,11 +155,9 @@ impl + Clone + Unpin, N: Network> Cast { } let res = call.await?; - let mut decoded = vec![]; - - if let Some(func) = func { + let decoded = if let Some(func) = func { // decode args into tokens - decoded = match func.abi_decode_output(res.as_ref()) { + match func.abi_decode_output(res.as_ref()) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract @@ -185,8 +183,10 @@ impl + Clone + Unpin, N: Network> Cast { "could not decode output; did you specify the wrong function return data type?" ); } - }; - } + } + } else { + vec![] + }; // handle case when return type is not specified Ok(if decoded.is_empty() { diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 42c6fe843e921..50c142000c14c 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -171,7 +171,7 @@ where N::ReceiptResponse: UIfmt + UIfmtReceiptExt, { /// Creates a new Cast instance responsible for sending transactions. - pub fn new(provider: P) -> Self { + pub const fn new(provider: P) -> Self { Self { provider, _phantom: PhantomData } } @@ -484,6 +484,11 @@ where let sender = sender.into(); self.prepare(&sender); + // For batch transactions with calls, clear `to` and `value` so the node correctly + // identifies this as an AA batch transaction. The `calls` field determines the actual + // targets. If `to` is set, `build_aa()` would add a spurious extra call. + self.tx.clear_batch_to(); + // resolve let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; self.resolve_auth(&sender, tx_nonce).await?; @@ -663,7 +668,7 @@ where /// Skips gas, fee and nonce filling. Use for read-only calls /// (eth_call, eth_estimateGas, eth_createAccessList). - pub fn raw(mut self) -> Self { + pub const fn raw(mut self) -> Self { self.fill = false; self } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 5b44e0a89e4a9..1267d7dcc4397 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3104,6 +3104,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { @@ -3841,28 +3869,28 @@ casttest!(flaky_osaka_can_run_p256_precompile, |_prj, cmd| { .assert_success() .stdout_eq(str![[r#" Traces: - [91537] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) ├─ [2241] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback(00) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000b55b053230e4effb6609de652fca73fd1c29804 ├─ [9750] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [staticcall] │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [delegatecall] │ │ └─ ← [Return] 62393 [6.239e4] │ └─ ← [Return] 62393 [6.239e4] - ├─ [65442] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::selfCallPayVerifyCall537021665() - │ ├─ [25070] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] - │ │ ├─ [22067] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] + ├─ [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::[..]() + │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] + │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] │ │ │ ├─ [2369] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::pauseFlag() [staticcall] │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 │ │ │ ├─ [120] PRECOMPILES::sha256(0x7b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d) [staticcall] │ │ │ │ └─ ← [Return] 0xc13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f │ │ │ ├─ [96] PRECOMPILES::sha256(0x424242424242424242424242424242424242424242424242424242424242424201000000c13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f) [staticcall] │ │ │ │ └─ ← [Return] 0xc544bd9a4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0 - │ │ │ ├─ [6900] P256VERIFY::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] + │ │ │ ├─ [..] P256VERIFY::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b - │ ├─ [5994] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::checkAndIncrementNonce(23) - │ │ ├─ [5608] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::checkAndIncrementNonce(23) [delegatecall] + │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::checkAndIncrementNonce(23) + │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::checkAndIncrementNonce(23) [delegatecall] │ │ │ └─ ← [Stop] │ │ └─ ← [Return] │ ├─ [3250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] @@ -3882,9 +3910,9 @@ Traces: │ │ ├─ [553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] │ │ │ └─ ← [Return] 40090 [4.009e4] │ │ └─ ← [Return] 40090 [4.009e4] - │ ├─ [5675] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::selfCallExecutePay1395256087() - │ │ ├─ [4148] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) - │ │ │ ├─ [3693] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] + │ ├─ [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::[..]() + │ │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) + │ │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] │ │ │ │ ├─ [435] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback() │ │ │ │ │ ├─ [55] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::fallback() [delegatecall] │ │ │ │ │ │ └─ ← [Stop] diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index fdc3260cc7638..fbdf2592891b8 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -153,12 +153,12 @@ impl Error { } /// Returns the raw data of this error. - pub fn data(&self) -> &[u8] { + pub const fn data(&self) -> &[u8] { unsafe { &*self.data } } /// Returns `true` if this error is a human-readable string. - pub fn is_str(&self) -> bool { + pub const fn is_str(&self) -> bool { self.is_str } diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 578728c4a4c41..0669dfa076822 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -26,7 +26,7 @@ pub struct Prank { impl Prank { /// Create a new prank. - pub fn new( + pub const fn new( prank_caller: Address, prank_origin: Address, new_caller: Address, @@ -49,7 +49,7 @@ impl Prank { /// Apply the prank by setting `used` to true if it is false /// Only returns self in the case it is updated (first application) - pub fn first_time_applied(&self) -> Option { + pub const fn first_time_applied(&self) -> Option { if self.used { None } else { Some(Self { used: true, ..*self }) } } } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 9f175c94d2f30..3f8bd91b0e8d9 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -549,7 +549,7 @@ fn get_artifact_code( // Try filtering by profile as well filtered.retain(|(id, _)| id.profile == running.profile); - if filtered.len() == 1 { Some(filtered[0]) } else { None } + (filtered.len() == 1).then(|| filtered[0]) }) .ok_or_else(|| fmt_err!("multiple matching artifacts found")), ) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 58d7c924622e9..8d86fb30f2431 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -304,12 +304,12 @@ pub struct GasMetering { impl GasMetering { /// Start the gas recording. - pub fn start(&mut self) { + pub const fn start(&mut self) { self.recording = true; } /// Stop the gas recording. - pub fn stop(&mut self) { + pub const fn stop(&mut self) { self.recording = false; } @@ -902,7 +902,7 @@ impl Cheatcodes { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` if prank.delegate_call && curr_depth == prank.depth - && let CallScheme::DelegateCall = call.scheme + && call.scheme == CallScheme::DelegateCall { call.target_address = prank.new_caller; call.caller = prank.new_caller; @@ -912,21 +912,23 @@ impl Cheatcodes { } if curr_depth >= prank.depth && call.caller == prank.prank_caller { - let mut prank_applied = false; - // At the target depth we set `msg.sender` - if curr_depth == prank.depth { + let prank_applied = if curr_depth == prank.depth { // Ensure new caller is loaded and touched let _ = journaled_account(ecx, prank.new_caller); call.caller = prank.new_caller; - prank_applied = true; - } + true + } else { + false + }; // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { + let prank_applied = if let Some(new_origin) = prank.new_origin { ecx.tx_mut().set_caller(new_origin); - prank_applied = true; - } + true + } else { + prank_applied + }; // If prank applied for first time, then update if prank_applied && let Some(applied_prank) = prank.first_time_applied() { @@ -1060,19 +1062,12 @@ impl Cheatcodes { if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code - let initialized; - let old_balance; - let old_nonce; - - if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { - initialized = acc.data.info.exists(); - old_balance = acc.data.info.balance; - old_nonce = acc.data.info.nonce; - } else { - initialized = false; - old_balance = U256::ZERO; - old_nonce = 0; - } + let (initialized, old_balance, old_nonce) = + if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { + (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce) + } else { + (false, U256::ZERO, 0) + }; let kind = match call.scheme { CallScheme::Call => crate::Vm::AccountAccessKind::Call, @@ -1548,7 +1543,7 @@ impl Inspector> for Cheatcode }, }; - if count != expected.count { Some((expected, count)) } else { None } + (count != expected.count).then_some((expected, count)) }) .collect::>(); @@ -1743,21 +1738,23 @@ impl Inspector> for Cheatcode && curr_depth >= prank.depth && input.caller() == prank.prank_caller { - let mut prank_applied = false; - // At the target depth we set `msg.sender` - if curr_depth == prank.depth { + let prank_applied = if curr_depth == prank.depth { // Ensure new caller is loaded and touched let _ = journaled_account(ecx, prank.new_caller); input.set_caller(prank.new_caller); - prank_applied = true; - } + true + } else { + false + }; // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { + let prank_applied = if let Some(new_origin) = prank.new_origin { ecx.tx_mut().set_caller(new_origin); - prank_applied = true; - } + true + } else { + prank_applied + }; // If prank applied for first time, then update if prank_applied && let Some(applied_prank) = prank.first_time_applied() { @@ -2214,13 +2211,14 @@ impl Cheatcodes { // Try to include present value for informational purposes, otherwise assume // it's not set (zero value) - let mut present_value = U256::ZERO; // Try to load the account and the slot's present value - if ecx.journal_mut().load_account(address).is_ok() + let present_value = if ecx.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { - present_value = previous.data; - } + previous.data + } else { + U256::ZERO + }; let access = crate::Vm::StorageAccess { account: interpreter.input.target_address, slot: key.into(), @@ -2241,12 +2239,13 @@ impl Cheatcodes { let address = interpreter.input.target_address; // Try to load the account and the slot's previous value, otherwise, assume it's // not set (zero value) - let mut previous_value = U256::ZERO; - if ecx.journal_mut().load_account(address).is_ok() + let previous_value = if ecx.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { - previous_value = previous.data; - } + previous.data + } else { + U256::ZERO + }; let access = crate::Vm::StorageAccess { account: address, @@ -2272,18 +2271,12 @@ impl Cheatcodes { }; let address = Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0)))); - let initialized; - let balance; - let nonce; - if let Ok(acc) = ecx.journal_mut().load_account(address) { - initialized = acc.data.info.exists(); - balance = acc.data.info.balance; - nonce = acc.data.info.nonce; - } else { - initialized = false; - balance = U256::ZERO; - nonce = 0; - } + let (initialized, balance, nonce) = + if let Ok(acc) = ecx.journal_mut().load_account(address) { + (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce) + } else { + (false, U256::ZERO, 0) + }; let curr_depth = ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); let account_access = crate::Vm::AccountAccess { @@ -2532,7 +2525,7 @@ fn disallowed_mem_write( } /// Returns true if the kind of account access is a call. -fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { +const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { matches!( kind, crate::Vm::AccountAccessKind::Call @@ -2602,7 +2595,7 @@ fn append_storage_access( } /// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor. -fn cheatcode_of(_: &T) -> &'static spec::Cheatcode<'static> { +const fn cheatcode_of(_: &T) -> &'static spec::Cheatcode<'static> { T::CHEATCODE } @@ -2610,11 +2603,11 @@ fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.signature.split('(').next().unwrap() } -fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str { +const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.id } -fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { +const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.signature } @@ -2676,7 +2669,7 @@ fn apply_dispatch( } /// Helper function to check if frame execution will exit. -fn will_exit(action: &InterpreterAction) -> bool { +const fn will_exit(action: &InterpreterAction) -> bool { match action { InterpreterAction::Return(result) => { result.result.is_ok_or_revert() || result.result.is_error() diff --git a/crates/cheatcodes/src/inspector/analysis.rs b/crates/cheatcodes/src/inspector/analysis.rs index 1e73fc3df783a..806caa50a2080 100644 --- a/crates/cheatcodes/src/inspector/analysis.rs +++ b/crates/cheatcodes/src/inspector/analysis.rs @@ -52,7 +52,7 @@ impl std::fmt::Debug for CheatcodeAnalysis { } impl CheatcodeAnalysis { - pub fn new(compiler: Arc) -> Self { + pub const fn new(compiler: Arc) -> Self { Self { compiler, struct_defs: OnceLock::new() } } @@ -80,7 +80,7 @@ struct StructDefinitionResolver<'gcx> { impl<'gcx> StructDefinitionResolver<'gcx> { /// Constructs a new generator. - pub fn new(gcx: Gcx<'gcx>) -> Self { + pub const fn new(gcx: Gcx<'gcx>) -> Self { Self { gcx, struct_defs: TypeDefMap::new() } } diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 24cfa5474a5ba..9af337e223eea 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -503,7 +503,7 @@ fn encode(values: Vec) -> Vec { /// Canonicalize a json path key to always start from the root of the document. /// Read more about json path syntax: pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { - if !path.starts_with('$') { format!("${path}").into() } else { path.into() } + if path.starts_with('$') { path.into() } else { format!("${path}").into() } } /// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index db0399d78b0ec..437d45e6b41f7 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -29,7 +29,7 @@ enum AssertionKind { } impl AssertionKind { - fn inverse(self) -> Self { + const fn inverse(self) -> Self { match self { Self::Eq => Self::Ne, Self::Ne => Self::Eq, @@ -40,7 +40,7 @@ impl AssertionKind { } } - fn to_str(self) -> &'static str { + const fn to_str(self) -> &'static str { match self { Self::Eq => "==", Self::Ne => "!=", @@ -450,11 +450,11 @@ impl_assertions! { (assertApproxEqRelDecimal_2Call, assertApproxEqRelDecimal_3Call), } -fn assert_true(condition: bool) -> Result<(), ()> { +const fn assert_true(condition: bool) -> Result<(), ()> { if condition { Ok(()) } else { Err(()) } } -fn assert_false(condition: bool) -> Result<(), ()> { +const fn assert_false(condition: bool) -> Result<(), ()> { assert_true(!condition) } @@ -467,10 +467,10 @@ fn assert_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a } fn assert_not_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left != right { - Ok(()) - } else { + if left == right { Err(ComparisonAssertionError { kind: AssertionKind::Ne, left, right }) + } else { + Ok(()) } } diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 851500e938c06..5ef5b46ff4919 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -155,7 +155,7 @@ impl From for CreateScheme { } impl CreateScheme { - pub fn eq(&self, create_scheme: Self) -> bool { + pub const fn eq(&self, create_scheme: Self) -> bool { matches!( (self, create_scheme), (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. }) @@ -1244,10 +1244,10 @@ pub(crate) fn get_emit_mismatch_message( { let (expected_name, expected_value) = &expected_params[param_idx]; let (_actual_name, actual_value) = &actual_params[param_idx]; - let param_name = if !expected_name.is_empty() { - expected_name - } else { + let param_name = if expected_name.is_empty() { &format!("param{param_idx}") + } else { + expected_name }; return format!( "{param_name}: expected={expected_value}, got={actual_value}", diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs index 3b0610ebf07a4..6ba333f63f402 100644 --- a/crates/cheatcodes/src/test/revert_handlers.rs +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -198,7 +198,10 @@ pub(crate) fn handle_expect_revert( // If we expect no reverts with a specific reason/reverter, but got a revert, // we need to check if it matches our criteria - if !matches!(status, return_ok!()) { + if matches!(status, return_ok!()) { + // No revert occurred, which is what we expected + Ok(success_return()) + } else { // We got a revert, but we expected 0 reverts // We need to check if this revert matches our expected criteria @@ -266,9 +269,6 @@ pub(crate) fn handle_expect_revert( } } } - } else { - // No revert occurred, which is what we expected - Ok(success_return()) } } else { ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 90e93f63999ad..fcaf6e9c6c3bb 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -225,11 +225,10 @@ impl Cheatcode for resumeTracingCall { impl Cheatcode for interceptInitcodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; - if !state.intercept_next_create_call { - state.intercept_next_create_call = true; - } else { + if state.intercept_next_create_call { bail!("vm.interceptInitcode() has already been called") } + state.intercept_next_create_call = true; Ok(Default::default()) } } diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 411c599aad22b..b4bf1c738ee85 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -72,12 +72,12 @@ impl ChiselDispatcher { } /// Returns the [`SessionSource`]. - pub fn source(&self) -> &SessionSource { + pub const fn source(&self) -> &SessionSource { &self.session.source } /// Returns the [`SessionSource`]. - pub fn source_mut(&mut self) -> &mut SessionSource { + pub const fn source_mut(&mut self) -> &mut SessionSource { &mut self.session.source } @@ -512,7 +512,7 @@ fn preprocess(input: &str) -> (bool, Cow<'_, str>) { let mut only_trivia = true; let mut new_input = Cow::Borrowed(input); for (pos, token) in solar::parse::Cursor::new(input).with_position() { - use RawTokenKind::*; + use RawTokenKind::{BlockComment, LineComment, Literal, Whitespace}; if matches!(token.kind, Whitespace | LineComment { .. } | BlockComment { .. }) { continue; diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index f5deb369ff963..da2c7f4caff02 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -549,9 +549,8 @@ impl Type { pt::Expression::Multiply(_, lhs, rhs) | pt::Expression::Divide(_, lhs, rhs) => { match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_)), Some(DynSolType::Int(_))) | - (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) | - (Some(DynSolType::Uint(_)), Some(DynSolType::Int(_))) => { + (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) | +(Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { Some(Self::Builtin(DynSolType::Int(256))) } _ => { @@ -822,7 +821,7 @@ impl Type { custom_type: &mut Vec, contract_name: Option, ) -> Result> { - if let Some("this") | Some("super") = custom_type.last().map(String::as_str) { + if let Some("this" | "super") = custom_type.last().map(String::as_str) { custom_type.pop(); } if custom_type.is_empty() { @@ -1081,12 +1080,12 @@ impl Type { match self { Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner)) | Some(DynSolType::FixedArray(inner, _)) => { + Some(DynSolType::Array(inner) | DynSolType::FixedArray(inner, _)) => { Some(*inner) } - Some(DynSolType::Bytes) - | Some(DynSolType::String) - | Some(DynSolType::FixedBytes(_)) => Some(DynSolType::FixedBytes(1)), + Some(DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_)) => { + Some(DynSolType::FixedBytes(1)) + } ty => ty, } } @@ -1096,7 +1095,7 @@ impl Type { /// Returns whether this type is dynamic #[inline] - fn is_dynamic(&self) -> bool { + const fn is_dynamic(&self) -> bool { match self { // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are // not dynamic, nor are tuples of non-dynamic types. @@ -1108,23 +1107,22 @@ impl Type { /// Returns whether this type is an array #[inline] - fn is_array(&self) -> bool { + const fn is_array(&self) -> bool { matches!( self, Self::Array(_) | Self::FixedArray(_, _) - | Self::Builtin(DynSolType::Array(_)) - | Self::Builtin(DynSolType::FixedArray(_, _)) + | Self::Builtin(DynSolType::Array(_) | DynSolType::FixedArray(_, _)) ) } /// Returns whether this type is a dynamic array (can call push, pop) #[inline] - fn is_dynamic_array(&self) -> bool { + const fn is_dynamic_array(&self) -> bool { matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) } - fn is_fixed_bytes(&self) -> bool { + const fn is_fixed_bytes(&self) -> bool { matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) } } @@ -1143,7 +1141,7 @@ fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option _ => None, }); match vis { - Some(pt::Visibility::External(_)) | Some(pt::Visibility::Public(_)) => { + Some(pt::Visibility::External(_) | pt::Visibility::Public(_)) => { match custom_type.first().unwrap().as_str() { "address" => Some(DynSolType::Address), "selector" => Some(DynSolType::FixedBytes(4)), @@ -1607,7 +1605,7 @@ mod tests { T: AsRef + std::fmt::Display + 'a, I: IntoIterator + 'a, { - for (input, expected) in input.into_iter() { + for (input, expected) in input { let input = input.as_ref(); let ty = get_type_ethabi(s, input, true); assert_eq!(ty.as_ref(), Some(expected), "\n{input}"); diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index 783f48031c2f0..1b2a530378701 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -62,7 +62,7 @@ impl ChiselRunner { /// ### Returns /// /// A new [ChiselRunner] - pub fn new( + pub const fn new( executor: Executor, initial_balance: U256, sender: Address, diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 1002b5533f116..c3212f8e89962 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -147,8 +147,8 @@ impl SolidityHelper { /// Validate that a source snippet is closed (i.e., all braces and parenthesis are matched). fn validate_closed(&self, input: &str) -> ValidationResult { - use RawLiteralKind::*; - use RawTokenKind::*; + use RawLiteralKind::Str; + use RawTokenKind::{BlockComment, CloseDelim, Literal, OpenDelim}; let mut stack = vec![]; for token in Cursor::new(input) { match token.kind { @@ -282,7 +282,10 @@ impl Helper for SolidityHelper {} fn token_style(token: &Token) -> Style { use solar::parse::{ interface::kw::*, - token::{TokenKind::*, TokenLitKind::*}, + token::{ + TokenKind::{Arrow, Comment, FatArrow, Ident, Literal}, + TokenLitKind::{HexStr, Str, UnicodeStr}, + }, }; match token.kind { diff --git a/crates/chisel/tests/it/repl/session.rs b/crates/chisel/tests/it/repl/session.rs index 92de13a012fd8..5076b00996ae7 100644 --- a/crates/chisel/tests/it/repl/session.rs +++ b/crates/chisel/tests/it/repl/session.rs @@ -68,7 +68,7 @@ impl ChiselSession { &self.project } - pub fn is_repl(&self) -> bool { + pub const fn is_repl(&self) -> bool { self.is_repl } diff --git a/crates/cli-markdown/src/lib.rs b/crates/cli-markdown/src/lib.rs index 732e22bfd193d..7702fcf15b9a7 100644 --- a/crates/cli-markdown/src/lib.rs +++ b/crates/cli-markdown/src/lib.rs @@ -24,7 +24,7 @@ pub struct MarkdownOptions { impl MarkdownOptions { /// Construct a default instance of `MarkdownOptions`. - pub fn new() -> Self { + pub const fn new() -> Self { Self { title: None, show_footer: true, show_table_of_contents: true, show_aliases: true } } @@ -35,19 +35,19 @@ impl MarkdownOptions { } /// Whether to show the default footer advertising `clap-markdown`. - pub fn show_footer(mut self, show: bool) -> Self { + pub const fn show_footer(mut self, show: bool) -> Self { self.show_footer = show; self } /// Whether to show the default table of contents. - pub fn show_table_of_contents(mut self, show: bool) -> Self { + pub const fn show_table_of_contents(mut self, show: bool) -> Self { self.show_table_of_contents = show; self } /// Whether to show aliases for arguments and commands. - pub fn show_aliases(mut self, show: bool) -> Self { + pub const fn show_aliases(mut self, show: bool) -> Self { self.show_aliases = show; self } @@ -456,7 +456,7 @@ fn get_alias_string(aliases: &[&str]) -> Option { Some(aliases.iter().map(|alias| format!("`{alias}`")).collect::>().join(", ")) } -fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { +const fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { if count == 1 { singular } else { plural } } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 172ad81d4858b..79b15381607aa 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,6 +18,7 @@ foundry-cli-markdown.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true +foundry-evm-networks.workspace = true foundry-wallets.workspace = true foundry-compilers.workspace = true diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 7af7c20fb021a..f818878b897e1 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -10,8 +10,10 @@ use foundry_config::{ value::{Dict, Map, Value}, }, }; +use foundry_evm_networks::NetworkConfigs; use serde::Serialize; +use crate::opts::RpcCommonOpts; use foundry_common::shell; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. @@ -39,31 +41,29 @@ use foundry_common::shell; #[derive(Clone, Debug, Default, Serialize, Parser)] #[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc pub struct EvmArgs { - /// Fetch state over a remote endpoint instead of starting from an empty state. - /// - /// If you want to fetch state from a specific block number, see --fork-block-number. - #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")] - #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] - pub fork_url: Option, + /// Common RPC options (URL, timeout, rate limiting, etc.). + #[command(flatten)] + #[serde(flatten)] + pub rpc: RpcCommonOpts, /// Fetch state from a specific block number over a remote endpoint. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "BLOCK")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, /// Number of retries. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "RETRIES")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "RETRIES")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retries: Option, /// Initial retry backoff on encountering errors. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "BACKOFF")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -73,7 +73,7 @@ pub struct EvmArgs { /// /// This flag overrides the project's configuration file. /// - /// See --fork-url. + /// See --rpc-url. #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, @@ -108,27 +108,6 @@ pub struct EvmArgs { #[serde(skip_serializing_if = "Option::is_none")] pub create2_deployer: Option
, - /// Sets the number of assumed available compute units per second for this provider - /// - /// default value: 330 - /// - /// See also --fork-url and - #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] - #[serde(skip_serializing_if = "Option::is_none")] - pub compute_units_per_second: Option, - - /// Disables rate limiting for this node's provider. - /// - /// See also --fork-url and - #[arg( - long, - value_name = "NO_RATE_LIMITS", - help_heading = "Fork config", - visible_alias = "no-rate-limit" - )] - #[serde(skip)] - pub no_rpc_rate_limit: bool, - /// All ethereum environment related arguments #[command(flatten)] #[serde(flatten)] @@ -140,6 +119,11 @@ pub struct EvmArgs { #[arg(long)] #[serde(skip)] pub isolate: bool, + + /// Network selection. + #[command(flatten)] + #[serde(skip)] + pub networks: NetworkConfigs, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -181,8 +165,27 @@ impl Provider for EvmArgs { dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into()); } - if self.no_rpc_rate_limit { - dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into()); + // Merge serde-skipped fields from the common RPC options. + if self.rpc.no_rpc_rate_limit { + dict.insert("no_rpc_rate_limit".to_string(), true.into()); + } + if self.rpc.accept_invalid_certs { + dict.insert("eth_rpc_accept_invalid_certs".to_string(), true.into()); + } + if self.rpc.no_proxy { + dict.insert("eth_rpc_no_proxy".to_string(), true.into()); + } + + // Only insert network flags when explicitly set via CLI to avoid overriding + // values from foundry.toml (NetworkConfigs is flattened in Config). + if self.networks.is_tempo() { + dict.insert("tempo".to_string(), true.into()); + } + if self.networks.is_optimism() { + dict.insert("optimism".to_string(), true.into()); + } + if self.networks.is_celo() { + dict.insert("celo".to_string(), true.into()); } Ok(Map::from([(Config::selected_profile(), dict)])) @@ -297,7 +300,10 @@ mod tests { #[test] fn compute_units_per_second_present_when_some() { - let args = EvmArgs { compute_units_per_second: Some(1000), ..Default::default() }; + let args = EvmArgs { + rpc: RpcCommonOpts { compute_units_per_second: Some(1000), ..Default::default() }, + ..Default::default() + }; let data = args.data().expect("provider data"); let dict = data.get(&Config::selected_profile()).expect("profile dict"); let val = dict.get("compute_units_per_second").expect("cups present"); diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index ce1234528a94b..fc619f482d2fb 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -5,6 +5,7 @@ mod evm; mod global; mod network; mod rpc; +mod rpc_common; mod tempo; mod transaction; @@ -15,5 +16,6 @@ pub use evm::*; pub use global::*; pub use network::*; pub use rpc::*; +pub use rpc_common::*; pub use tempo::*; pub use transaction::*; diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 15a5de678272a..3467ccc13f697 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -1,4 +1,4 @@ -use crate::opts::ChainValueParser; +use crate::opts::{ChainValueParser, RpcCommonOpts}; use alloy_chains::ChainKind; use clap::Parser; use eyre::Result; @@ -19,24 +19,9 @@ const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast"; #[derive(Clone, Debug, Default, Parser)] #[command(next_help_heading = "Rpc options")] pub struct RpcOpts { - /// The RPC endpoint, default value is http://localhost:8545. - #[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] - pub url: Option, - - /// Allow insecure RPC connections (accept invalid HTTPS certificates). - /// - /// When the provider's inner runtime transport variant is HTTP, this configures the reqwest - /// client to accept invalid certificates. - #[arg(short = 'k', long = "insecure", default_value = "false")] - pub accept_invalid_certs: bool, - - /// Disable automatic proxy detection. - /// - /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where - /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment - /// variables and system proxy settings will be ignored. - #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")] - pub no_proxy: bool, + /// Common RPC options (URL, timeout, rate limiting, etc.). + #[command(flatten)] + pub common: RpcCommonOpts, /// Use the Flashbots RPC URL with fast mode (). /// @@ -58,14 +43,6 @@ pub struct RpcOpts { #[arg(long, env = "ETH_RPC_JWT_SECRET")] pub jwt_secret: Option, - /// Timeout for the RPC request in seconds. - /// - /// The specified timeout will be used to override the default timeout for RPC requests. - /// - /// Default value: 45 - #[arg(long, env = "ETH_RPC_TIMEOUT")] - pub rpc_timeout: Option, - /// Specify custom headers for RPC requests. #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] pub rpc_headers: Option>, @@ -90,13 +67,11 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { - let url = match (self.flashbots, self.url.as_deref(), config) { - (true, ..) => Some(Cow::Borrowed(FLASHBOTS_URL)), - (false, Some(url), _) => Some(Cow::Borrowed(url)), - (false, None, Some(config)) => config.get_rpc_url().transpose()?, - (false, None, None) => None, - }; - Ok(url) + if self.flashbots { + Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else { + self.common.url(config) + } } /// Returns the JWT secret. @@ -110,25 +85,16 @@ impl RpcOpts { } pub fn dict(&self) -> Dict { - let mut dict = Dict::new(); - if let Ok(Some(url)) = self.url(None) { - dict.insert("eth_rpc_url".into(), url.into_owned().into()); + let mut dict = self.common.dict(); + if self.flashbots { + dict.insert("eth_rpc_url".into(), FLASHBOTS_URL.into()); } if let Ok(Some(jwt)) = self.jwt(None) { dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); } - if let Some(rpc_timeout) = self.rpc_timeout { - dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); - } if let Some(headers) = &self.rpc_headers { dict.insert("eth_rpc_headers".into(), headers.clone().into()); } - if self.accept_invalid_certs { - dict.insert("eth_rpc_accept_invalid_certs".into(), true.into()); - } - if self.no_proxy { - dict.insert("eth_rpc_no_proxy".into(), true.into()); - } if self.curl { dict.insert("eth_rpc_curl".into(), true.into()); } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs new file mode 100644 index 0000000000000..05b98582fa88f --- /dev/null +++ b/crates/cli/src/opts/rpc_common.rs @@ -0,0 +1,114 @@ +//! Common RPC options shared between `RpcOpts` and `EvmArgs`. + +use clap::Parser; +use eyre::Result; +use foundry_config::{ + Config, + figment::{ + self, Metadata, Profile, + value::{Dict, Map}, + }, +}; +use serde::Serialize; +use std::borrow::Cow; + +/// Common RPC-related options shared across CLI commands. +/// +/// This struct holds fields that both [`super::RpcOpts`] (cast) and +/// [`super::EvmArgs`] (forge/script) need, eliminating duplication and +/// making the two structs composable. +#[derive(Clone, Debug, Default, Serialize, Parser)] +pub struct RpcCommonOpts { + /// The RPC endpoint. + #[arg(short, long, visible_alias = "fork-url", env = "ETH_RPC_URL")] + #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] + pub rpc_url: Option, + + /// Allow insecure RPC connections (accept invalid HTTPS certificates). + /// + /// When the provider's inner runtime transport variant is HTTP, this configures the reqwest + /// client to accept invalid certificates. + #[arg(short = 'k', long = "insecure", default_value = "false")] + #[serde(skip)] + pub accept_invalid_certs: bool, + + /// Timeout for the RPC request in seconds. + /// + /// The specified timeout will be used to override the default timeout for RPC requests. + /// + /// Default value: 45 + #[arg(long, env = "ETH_RPC_TIMEOUT")] + #[serde(rename = "eth_rpc_timeout", skip_serializing_if = "Option::is_none")] + pub rpc_timeout: Option, + + /// Disable automatic proxy detection. + /// + /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where + /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment + /// variables and system proxy settings will be ignored. + #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")] + #[serde(skip)] + pub no_proxy: bool, + + /// Sets the number of assumed available compute units per second for this provider. + /// + /// default value: 330 + /// + /// See also + #[arg(long, alias = "cups", value_name = "CUPS")] + #[serde(skip_serializing_if = "Option::is_none")] + pub compute_units_per_second: Option, + + /// Disables rate limiting for this node's provider. + /// + /// See also + #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rate-limit")] + #[serde(skip)] + pub no_rpc_rate_limit: bool, +} + +impl figment::Provider for RpcCommonOpts { + fn metadata(&self) -> Metadata { + Metadata::named("RpcCommonOpts") + } + + fn data(&self) -> Result, figment::Error> { + Ok(Map::from([(Config::selected_profile(), self.dict())])) + } +} + +impl RpcCommonOpts { + /// Returns the RPC endpoint URL, resolving from CLI args or config. + pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + let url = match (self.rpc_url.as_deref(), config) { + (Some(url), _) => Some(Cow::Borrowed(url)), + (None, Some(config)) => config.get_rpc_url().transpose()?, + (None, None) => None, + }; + Ok(url) + } + + /// Builds a figment-compatible dictionary from these options. + pub fn dict(&self) -> Dict { + let mut dict = Dict::new(); + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); + } + if let Some(rpc_timeout) = self.rpc_timeout { + dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); + } + if self.accept_invalid_certs { + dict.insert("eth_rpc_accept_invalid_certs".into(), true.into()); + } + if self.no_proxy { + dict.insert("eth_rpc_no_proxy".into(), true.into()); + } + if let Some(cups) = self.compute_units_per_second { + dict.insert("compute_units_per_second".into(), cups.into()); + } + if self.no_rpc_rate_limit { + dict.insert("no_rpc_rate_limit".into(), true.into()); + } + dict + } +} diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs index a071e6774c21a..5220b7ee80fe1 100644 --- a/crates/cli/src/opts/tempo.rs +++ b/crates/cli/src/opts/tempo.rs @@ -76,7 +76,7 @@ pub struct TempoOpts { impl TempoOpts { /// Returns `true` if any Tempo-specific option is set. - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.fee_token.is_some() || self.nonce_key.is_some() || self.sponsor_signature.is_some() diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index eb4cf477d39f9..f88346949d1b6 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -112,7 +112,12 @@ pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { /// True if the network calculates gas costs differently. pub fn has_different_gas_calc(chain_id: u64) -> bool { - if let Some(chain) = Chain::from(chain_id).named() { + let chain = Chain::from(chain_id); + // Is either Tempo | TempoModerato | TempoTestnet | TempoDevnet + if chain.is_tempo() || chain.id() == 31318 { + return true; + } + if let Some(chain) = chain.named() { return chain.is_arbitrum() || chain.is_elastic() || matches!( diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index bfd722a5ac104..f1c6c435217b7 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -373,16 +373,16 @@ impl<'a> Git<'a> { .map(drop) } - pub fn root(self, root: &Path) -> Git<'_> { + pub const fn root(self, root: &Path) -> Git<'_> { Git { root, ..self } } - pub fn quiet(self, quiet: bool) -> Self { + pub const fn quiet(self, quiet: bool) -> Self { Self { quiet, ..self } } /// True to perform shallow clones - pub fn shallow(self, shallow: bool) -> Self { + pub const fn shallow(self, shallow: bool) -> Self { Self { shallow, ..self } } @@ -738,7 +738,7 @@ pub struct Submodule { } impl Submodule { - pub fn new(rev: String, path: PathBuf) -> Self { + pub const fn new(rev: String, path: PathBuf) -> Self { Self { rev, path } } @@ -746,7 +746,7 @@ impl Submodule { &self.rev } - pub fn path(&self) -> &PathBuf { + pub const fn path(&self) -> &PathBuf { &self.path } } @@ -771,11 +771,11 @@ impl FromStr for Submodule { pub struct Submodules(pub Vec); impl Submodules { - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.0.len() } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.0.is_empty() } } @@ -857,10 +857,10 @@ mod tests { let mut cwd_file = File::create(cwd_env).unwrap(); let mut prj_file = File::create(nested.join(".env")).unwrap(); - cwd_file.write_all("TESTCWDKEY=cwd_val".as_bytes()).unwrap(); + cwd_file.write_all(b"TESTCWDKEY=cwd_val").unwrap(); cwd_file.sync_all().unwrap(); - prj_file.write_all("TESTPRJKEY=prj_val".as_bytes()).unwrap(); + prj_file.write_all(b"TESTPRJKEY=prj_val").unwrap(); prj_file.sync_all().unwrap(); let cwd = env::current_dir().unwrap(); diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index 43c2a4621528b..d751ddba245ba 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -222,10 +222,10 @@ impl ConsoleFmt for U256 { let integer = amount / exp10; let decimal = (amount % exp10).to_string(); let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{integer}.{decimal}e{log}") - } else { + if decimal.is_empty() { format!("{integer}e{log}") + } else { + format!("{integer}.{decimal}e{log}") } } FormatSpec::Exponential(Some(precision)) => { @@ -234,10 +234,10 @@ impl ConsoleFmt for U256 { let integer = amount / exp10; let decimal = (amount % exp10).to_string(); let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{integer}.{decimal}") - } else { + if decimal.is_empty() { format!("{integer}") + } else { + format!("{integer}.{decimal}") } } } @@ -266,10 +266,10 @@ impl ConsoleFmt for I256 { let integer = (amount / exp10).twos_complement(); let decimal = (amount % exp10).twos_complement().to_string(); let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{sign}{integer}.{decimal}e{log}") - } else { + if decimal.is_empty() { format!("{sign}{integer}e{log}") + } else { + format!("{sign}{integer}.{decimal}e{log}") } } FormatSpec::Exponential(Some(precision)) => { @@ -279,10 +279,10 @@ impl ConsoleFmt for I256 { let integer = (amount / exp10).twos_complement(); let decimal = (amount % exp10).twos_complement().to_string(); let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{sign}{integer}.{decimal}") - } else { + if decimal.is_empty() { format!("{sign}{integer}") + } else { + format!("{sign}{integer}.{decimal}") } } } diff --git a/crates/common/fmt/src/dynamic.rs b/crates/common/fmt/src/dynamic.rs index 6388871c15c24..1882d7b3d49b0 100644 --- a/crates/common/fmt/src/dynamic.rs +++ b/crates/common/fmt/src/dynamic.rs @@ -107,7 +107,7 @@ struct DynValueDisplay<'a> { impl<'a> DynValueDisplay<'a> { /// Creates a new [`Display`](fmt::Display) wrapper for the given value. - fn new(value: &'a DynSolValue, raw: bool) -> Self { + const fn new(value: &'a DynSolValue, raw: bool) -> Self { Self { value, formatter: DynValueFormatter { raw } } } } @@ -242,7 +242,7 @@ impl From for StructDefinitions { } impl StructDefinitions { - pub fn new(map: TypeDefMap) -> Self { + pub const fn new(map: TypeDefMap) -> Self { Self(map) } diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 12948f896421c..3ab26593766e3 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -59,7 +59,9 @@ impl UIfmt for Option { impl UIfmt for [T] { fn pretty(&self) -> String { - if !self.is_empty() { + if self.is_empty() { + "[]".to_string() + } else { let mut s = String::with_capacity(self.len() * 64); s.push_str("[\n"); for item in self { @@ -71,8 +73,6 @@ impl UIfmt for [T] { } s.push(']'); s - } else { - "[]".to_string() } } } diff --git a/crates/common/src/comments/comment.rs b/crates/common/src/comments/comment.rs index 40b9452cb4aaf..c19ade41ce9c8 100644 --- a/crates/common/src/comments/comment.rs +++ b/crates/common/src/comments/comment.rs @@ -5,7 +5,7 @@ use solar::parse::{ interface::BytePos, }; -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum CommentStyle { /// No code on either side of each line of the comment Isolated, @@ -18,16 +18,16 @@ pub enum CommentStyle { } impl CommentStyle { - pub fn is_mixed(&self) -> bool { + pub const fn is_mixed(&self) -> bool { matches!(self, Self::Mixed) } - pub fn is_trailing(&self) -> bool { + pub const fn is_trailing(&self) -> bool { matches!(self, Self::Trailing) } - pub fn is_isolated(&self) -> bool { + pub const fn is_isolated(&self) -> bool { matches!(self, Self::Isolated) } - pub fn is_blank(&self) -> bool { + pub const fn is_blank(&self) -> bool { matches!(self, Self::BlankLine) } } @@ -46,7 +46,7 @@ impl Comment { self.span.lo() } - pub fn prefix(&self) -> Option<&'static str> { + pub const fn prefix(&self) -> Option<&'static str> { if self.lines.is_empty() { return None; } @@ -58,7 +58,7 @@ impl Comment { }) } - pub fn suffix(&self) -> Option<&'static str> { + pub const fn suffix(&self) -> Option<&'static str> { if self.lines.is_empty() { return None; } diff --git a/crates/common/src/comments/inline_config.rs b/crates/common/src/comments/inline_config.rs index 16ffd6993bfef..cf736d1ad1f33 100644 --- a/crates/common/src/comments/inline_config.rs +++ b/crates/common/src/comments/inline_config.rs @@ -46,7 +46,7 @@ impl InlineConfigItem> { vec!["all".to_string()] } else { match relevant.split_once(')') { - Some((id_str, _)) => id_str.split(",").map(|s| s.trim().to_string()).collect(), + Some((id_str, _)) => id_str.split(',').map(|s| s.trim().to_string()).collect(), None => return Err(InvalidInlineConfigItem::Syntax(s.into())), } }; @@ -331,7 +331,7 @@ struct NextItemFinder { } impl NextItemFinder { - fn new(offset: BytePos) -> Self { + const fn new(offset: BytePos) -> Self { Self { offset } } diff --git a/crates/common/src/comments/mod.rs b/crates/common/src/comments/mod.rs index a3e4a8f3ec743..812590b126e71 100644 --- a/crates/common/src/comments/mod.rs +++ b/crates/common/src/comments/mod.rs @@ -94,7 +94,7 @@ impl Comments { // Stop when we find a trailing or a non-mixed comment match cmnt.style { - CommentStyle::Mixed => continue, + CommentStyle::Mixed => {} CommentStyle::Trailing => return Some((cmnt, i)), _ => break, } @@ -317,9 +317,7 @@ impl<'ast> CommentGatherer<'ast> { res.push(line); continue; } - if !pos.is_last { - res.push(format_doc_block_comment(&line, self.tab_width)); - } else { + if pos.is_last { // Ensure last line of a doc comment only has the `*/` decorator if let Some((first, _)) = line.split_once("*/") && !first.trim().is_empty() @@ -327,6 +325,8 @@ impl<'ast> CommentGatherer<'ast> { res.push(format_doc_block_comment(first.trim_end(), self.tab_width)); } res.push(" */".to_string()); + } else { + res.push(format_doc_block_comment(&line, self.tab_width)); } } res @@ -380,7 +380,7 @@ fn format_doc_block_comment(line: &str, tab_width: Option) -> String { return (" *").to_string(); } - if let Some((_, rest_of_line)) = line.split_once("*") { + if let Some((_, rest_of_line)) = line.split_once('*') { if rest_of_line.is_empty() { (" *").to_string() } else if let Some(tab_width) = tab_width { diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 5078fc10f7f41..03bebb0583df6 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -89,14 +89,14 @@ impl ProjectCompiler { /// Sets whether to print contract names. #[inline] - pub fn print_names(mut self, yes: bool) -> Self { + pub const fn print_names(mut self, yes: bool) -> Self { self.print_names = Some(yes); self } /// Sets whether to print contract sizes. #[inline] - pub fn print_sizes(mut self, yes: bool) -> Self { + pub const fn print_sizes(mut self, yes: bool) -> Self { self.print_sizes = Some(yes); self } @@ -104,21 +104,21 @@ impl ProjectCompiler { /// Sets whether to print anything at all. Overrides other `print` options. #[inline] #[doc(alias = "silent")] - pub fn quiet(mut self, yes: bool) -> Self { + pub const fn quiet(mut self, yes: bool) -> Self { self.quiet = Some(yes); self } /// Sets whether to bail on compiler errors. #[inline] - pub fn bail(mut self, yes: bool) -> Self { + pub const fn bail(mut self, yes: bool) -> Self { self.bail = Some(yes); self } /// Sets whether to ignore EIP-3860 initcode size limits. #[inline] - pub fn ignore_eip_3860(mut self, yes: bool) -> Self { + pub const fn ignore_eip_3860(mut self, yes: bool) -> Self { self.ignore_eip_3860 = yes; self } @@ -132,7 +132,7 @@ impl ProjectCompiler { /// Sets if tests should be dynamically linked. #[inline] - pub fn dynamic_test_linking(mut self, preprocess: bool) -> Self { + pub const fn dynamic_test_linking(mut self, preprocess: bool) -> Self { self.dynamic_test_linking = preprocess; self } @@ -163,10 +163,10 @@ impl ProjectCompiler { let files = std::mem::take(&mut self.files); let preprocess = self.dynamic_test_linking; self.compile_with(|| { - let sources = if !files.is_empty() { - Source::read_all(files)? - } else { + let sources = if files.is_empty() { project.paths.read_input_files()? + } else { + Source::read_all(files)? }; let mut compiler = diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 06b2c29945417..e9f203ebf7539 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } @@ -484,7 +484,7 @@ pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 { /// # Safety /// /// `a` must be at least as long as `b`. -unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { +const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`, // however this function is very hot, and has been written to be as primitive as // possible for lower optimization levels. @@ -616,7 +616,7 @@ pub fn find_matching_contract_artifact( // If all artifact_ids in `possible_targets` have the same name (without ".", indicates // additional compiler profiles), it means that there are multiple contracts in the // same file. - if !target_id.name.contains(".") + if !target_id.name.contains('.') && possible_targets.iter().any(|(id, _)| id.name != target_id.name) { eyre::bail!( @@ -628,7 +628,7 @@ pub fn find_matching_contract_artifact( // same but `id.path` is different. let artifact = possible_targets .iter() - .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None }) + .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact)) .unwrap_or(target_artifact); Ok(artifact.clone()) diff --git a/crates/common/src/io/shell.rs b/crates/common/src/io/shell.rs index e462be70c75aa..f067ebfa95cd8 100644 --- a/crates/common/src/io/shell.rs +++ b/crates/common/src/io/shell.rs @@ -51,7 +51,7 @@ pub fn is_markdown() -> bool { /// The global shell instance. static GLOBAL_SHELL: OnceLock> = OnceLock::new(); -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] /// The requested output mode. pub enum OutputMode { /// Default output @@ -74,7 +74,7 @@ impl OutputMode { } /// The requested output format. -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum OutputFormat { /// Plain text output. #[default] @@ -153,7 +153,7 @@ enum ShellOut { } /// Whether messages should use color output. -#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize, ValueEnum)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, ValueEnum)] pub enum ColorChoice { /// Intelligently guess whether to use color output (default). #[default] @@ -204,7 +204,7 @@ impl Shell { } /// Creates a shell that ignores all output. - pub fn empty() -> Self { + pub const fn empty() -> Self { Self { output: ShellOut::Empty(std::io::empty()), output_format: OutputFormat::Text, @@ -264,22 +264,22 @@ impl Shell { } /// Gets the output format of the shell. - pub fn output_format(&self) -> OutputFormat { + pub const fn output_format(&self) -> OutputFormat { self.output_format } /// Gets the output mode of the shell. - pub fn output_mode(&self) -> OutputMode { + pub const fn output_mode(&self) -> OutputMode { self.output_mode } /// Gets the verbosity of the shell when [`OutputMode::Normal`] is set. - pub fn verbosity(&self) -> Verbosity { + pub const fn verbosity(&self) -> Verbosity { self.verbosity } /// Sets the verbosity level. - pub fn set_verbosity(&mut self, verbosity: Verbosity) { + pub const fn set_verbosity(&mut self, verbosity: Verbosity) { self.verbosity = verbosity; } @@ -287,7 +287,7 @@ impl Shell { /// /// If we are not using a color stream, this will always return `Never`, even if the color /// choice has been set to something else. - pub fn color_choice(&self) -> ColorChoice { + pub const fn color_choice(&self) -> ColorChoice { match self.output { ShellOut::Stream { color_choice, .. } => color_choice, ShellOut::Empty(_) => ColorChoice::Never, @@ -295,7 +295,7 @@ impl Shell { } /// Returns `true` if stderr is a tty. - pub fn is_err_tty(&self) -> bool { + pub const fn is_err_tty(&self) -> bool { match self.output { ShellOut::Stream { stderr_tty, .. } => stderr_tty, ShellOut::Empty(_) => false, @@ -489,7 +489,7 @@ impl ShellOut { impl ColorChoice { /// Converts our color choice to [`anstream`]'s version. - fn to_anstream_color_choice(self) -> anstream::ColorChoice { + const fn to_anstream_color_choice(self) -> anstream::ColorChoice { match self { Self::Always => anstream::ColorChoice::Always, Self::Never => anstream::ColorChoice::Never, @@ -498,7 +498,7 @@ impl ColorChoice { } } -fn supports_color(choice: anstream::ColorChoice) -> bool { +const fn supports_color(choice: anstream::ColorChoice) -> bool { match choice { anstream::ColorChoice::Always | anstream::ColorChoice::AlwaysAnsi diff --git a/crates/common/src/io/stdin.rs b/crates/common/src/io/stdin.rs index feba958c18e47..f9da53f305f28 100644 --- a/crates/common/src/io/stdin.rs +++ b/crates/common/src/io/stdin.rs @@ -101,9 +101,9 @@ pub fn read_bytes(read_line: bool) -> Result> { let mut buf = String::new(); stdin.read_line(&mut buf)?; // remove the trailing newline - if let Some(b'\n') = buf.as_bytes().last() { + if matches!(buf.as_bytes().last(), Some(b'\n')) { buf.pop(); - if let Some(b'\r') = buf.as_bytes().last() { + if matches!(buf.as_bytes().last(), Some(b'\r')) { buf.pop(); } } diff --git a/crates/common/src/provider/curl_transport.rs b/crates/common/src/provider/curl_transport.rs index ca43e8b34f2b3..f2f3a44361f52 100644 --- a/crates/common/src/provider/curl_transport.rs +++ b/crates/common/src/provider/curl_transport.rs @@ -66,7 +66,7 @@ pub struct CurlTransport { impl CurlTransport { /// Create a new curl transport with the given URL. - pub fn new(url: Url) -> Self { + pub const fn new(url: Url) -> Self { Self { url, headers: vec![], jwt: None } } diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 764a44db55baa..1d4c0ec9bfeaa 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -190,19 +190,19 @@ impl ProviderBuilder { /// response body has finished. /// /// Default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { + pub const fn timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } /// Sets the chain of the node the provider will connect to - pub fn chain(mut self, chain: NamedChain) -> Self { + pub const fn chain(mut self, chain: NamedChain) -> Self { self.chain = chain; self } /// How often to retry a failed request - pub fn max_retry(mut self, max_retry: u32) -> Self { + pub const fn max_retry(mut self, max_retry: u32) -> Self { self.max_retry = max_retry; self } @@ -221,7 +221,7 @@ impl ProviderBuilder { } /// The starting backoff delay to use after the first failed request - pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { + pub const fn initial_backoff(mut self, initial_backoff: u64) -> Self { self.initial_backoff = initial_backoff; self } @@ -229,7 +229,7 @@ impl ProviderBuilder { /// Sets the number of assumed available compute units per second /// /// See also, - pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + pub const fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { self.compute_units_per_second = compute_units_per_second; self } @@ -237,7 +237,10 @@ impl ProviderBuilder { /// Sets the number of assumed available compute units per second /// /// See also, - pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { + pub const fn compute_units_per_second_opt( + mut self, + compute_units_per_second: Option, + ) -> Self { if let Some(cups) = compute_units_per_second { self.compute_units_per_second = cups; } @@ -247,7 +250,7 @@ impl ProviderBuilder { /// Sets the provider to be local. /// /// This is useful for local dev nodes. - pub fn local(mut self, is_local: bool) -> Self { + pub const fn local(mut self, is_local: bool) -> Self { self.is_local = is_local; self } @@ -255,7 +258,7 @@ impl ProviderBuilder { /// Sets aggressive `max_retry` and `initial_backoff` values /// /// This is only recommend for local dev nodes - pub fn aggressive(self) -> Self { + pub const fn aggressive(self) -> Self { self.max_retry(100).initial_backoff(100).local(true) } @@ -279,7 +282,7 @@ impl ProviderBuilder { } /// Sets whether to accept invalid certificates. - pub fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { + pub const fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { self.accept_invalid_certs = accept_invalid_certs; self } @@ -288,7 +291,7 @@ impl ProviderBuilder { /// /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) /// where system proxy detection via SCDynamicStore causes crashes. - pub fn no_proxy(mut self, no_proxy: bool) -> Self { + pub const fn no_proxy(mut self, no_proxy: bool) -> Self { self.no_proxy = no_proxy; self } @@ -297,7 +300,7 @@ impl ProviderBuilder { /// /// When enabled, the provider will print equivalent curl commands to stdout /// instead of actually executing the RPC requests. - pub fn curl_mode(mut self, curl_mode: bool) -> Self { + pub const fn curl_mode(mut self, curl_mode: bool) -> Self { self.curl_mode = curl_mode; self } diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 8725a4fbe2fe7..5c4ff2a8fa7e5 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -97,7 +97,7 @@ pub struct RuntimeTransportBuilder { impl RuntimeTransportBuilder { /// Create a new builder with the given URL. - pub fn new(url: Url) -> Self { + pub const fn new(url: Url) -> Self { Self { url, headers: vec![], @@ -121,13 +121,13 @@ impl RuntimeTransportBuilder { } /// Set the timeout for the transport. - pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { + pub const fn with_timeout(mut self, timeout: std::time::Duration) -> Self { self.timeout = timeout; self } /// Set whether to accept invalid certificates. - pub fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { + pub const fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { self.accept_invalid_certs = accept_invalid_certs; self } @@ -136,7 +136,7 @@ impl RuntimeTransportBuilder { /// /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) /// where system proxy detection via SCDynamicStore causes crashes. - pub fn no_proxy(mut self, no_proxy: bool) -> Self { + pub const fn no_proxy(mut self, no_proxy: bool) -> Self { self.no_proxy = no_proxy; self } diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index a8e8188e43246..ec102d3ef0446 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -23,12 +23,12 @@ pub struct Retry { impl Retry { /// Creates a new `Retry` instance. - pub fn new(retries: u32, delay: Duration) -> Self { + pub const fn new(retries: u32, delay: Duration) -> Self { Self { retries, delay } } /// Creates a new `Retry` instance with no delay between retries. - pub fn new_no_delay(retries: u32) -> Self { + pub const fn new_no_delay(retries: u32) -> Self { Self::new(retries, Duration::ZERO) } diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index 27bb2d7ef4369..c8cf736d69e95 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -371,7 +371,7 @@ pub enum SelectorKind { impl SelectorKind { /// Returns the function selector if it is a function OR custom error. - pub fn as_function(&self) -> Option { + pub const fn as_function(&self) -> Option { match *self { Self::Function(selector) | Self::Error(selector) => Some(selector), _ => None, @@ -379,7 +379,7 @@ impl SelectorKind { } /// Returns the event selector if it is an event. - pub fn as_event(&self) -> Option { + pub const fn as_event(&self) -> Option { match *self { Self::Event(hash) => Some(hash), _ => None, @@ -444,7 +444,7 @@ pub struct RawSelectorImportData { } impl RawSelectorImportData { - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.function.is_empty() && self.event.is_empty() && self.error.is_empty() } } diff --git a/crates/common/src/slot_identifier.rs b/crates/common/src/slot_identifier.rs index 231a4478c293e..e737bc478bb62 100644 --- a/crates/common/src/slot_identifier.rs +++ b/crates/common/src/slot_identifier.rs @@ -124,7 +124,7 @@ impl SlotInfo { } /// Slot is of type [`DynSolType::Bytes`] or [`DynSolType::String`] - pub fn is_bytes_or_string(&self) -> bool { + pub const fn is_bytes_or_string(&self) -> bool { matches!(self.slot_type.dyn_sol_type, DynSolType::Bytes | DynSolType::String) } @@ -377,7 +377,7 @@ pub struct SlotIdentifier { impl SlotIdentifier { /// Creates a new SlotIdentifier with the given storage layout. - pub fn new(storage_layout: Arc) -> Self { + pub const fn new(storage_layout: Arc) -> Self { Self { storage_layout } } diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index 2f3540c453595..89d4d4c8f8575 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -221,7 +221,7 @@ impl TestFunctionKind { /// Returns `true` if this function is a unit test. #[inline] - pub fn is_unit_test(&self) -> bool { + pub const fn is_unit_test(&self) -> bool { matches!(self, Self::UnitTest { .. }) } diff --git a/crates/common/src/transactions/broadcast.rs b/crates/common/src/transactions/broadcast.rs index f44076f366d20..45a3ea038ff42 100644 --- a/crates/common/src/transactions/broadcast.rs +++ b/crates/common/src/transactions/broadcast.rs @@ -23,15 +23,15 @@ pub enum TransactionMaybeSigned { impl TransactionMaybeSigned { /// Creates a new (unsigned) transaction for broadcast - pub fn new(tx: N::TransactionRequest) -> Self { + pub const fn new(tx: N::TransactionRequest) -> Self { Self::Unsigned(tx) } - pub fn is_unsigned(&self) -> bool { + pub const fn is_unsigned(&self) -> bool { matches!(self, Self::Unsigned(_)) } - pub fn as_unsigned_mut(&mut self) -> Option<&mut N::TransactionRequest> { + pub const fn as_unsigned_mut(&mut self) -> Option<&mut N::TransactionRequest> { match self { Self::Unsigned(tx) => Some(tx), _ => None, diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index 935c30f20bfe9..b022db33eadaa 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -249,6 +249,14 @@ pub trait FoundryTransactionBuilder: TransactionBuilder { /// No-op for non-Tempo networks. fn convert_create_to_call(&mut self) {} + /// Clears the `to` and `value` fields for batch transactions that use `calls`. + /// + /// In Tempo AA batch transactions, targets are specified in the `calls` field, not in `to`. + /// If `to` is set, `build_aa()` would add a spurious extra call. Must be called after + /// `prepare()` sets `kind`/`to` but before gas estimation. + /// No-op for non-Tempo networks. + fn clear_batch_to(&mut self) {} + /// Signs the transaction using an access key (keychain mode). /// /// If `key_authorization` is provided and the key is not yet provisioned on-chain, @@ -440,6 +448,13 @@ impl FoundryTransactionBuilder for ::Tran } } + fn clear_batch_to(&mut self) { + if !self.calls.is_empty() { + self.inner.to = None; + self.inner.value = None; + } + } + fn sign_with_access_key( mut self, provider: &impl Provider, diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 4f9eddc5e3ad6..9881d29bd0aab 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -13,7 +13,7 @@ pub struct ExtractConfigError { impl ExtractConfigError { /// Wraps the figment error. - pub fn new(error: figment::Error) -> Self { + pub const fn new(error: figment::Error) -> Self { Self { error } } } @@ -22,7 +22,7 @@ impl fmt::Display for ExtractConfigError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut unique_errors = Vec::with_capacity(self.error.count()); let mut unique = HashSet::with_capacity(self.error.count()); - for err in self.error.clone().into_iter() { + for err in self.error.clone() { let err = if err .metadata .as_ref() @@ -152,7 +152,7 @@ impl SolidityErrorCode { /// The textual identifier for this error /// /// Returns `Err(code)` if unknown error - pub fn as_str(&self) -> Result<&'static str, u64> { + pub const fn as_str(&self) -> Result<&'static str, u64> { let s = match self { Self::SpdxLicenseNotProvided => "license", Self::VisibilityForConstructorIsIgnored => "constructor-visibility", diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 25772a8171303..314ec53d87d89 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -135,11 +135,11 @@ impl ResolvedEtherscanConfigs { self, chain: Chain, ) -> Option> { - for (_, config) in self.configs.into_iter() { + for (_, config) in self.configs { match config { Ok(c) if c.chain == Some(chain) => return Some(Ok(c)), Err(e) => return Some(Err(e)), - _ => continue, + _ => {} } } None diff --git a/crates/config/src/extend.rs b/crates/config/src/extend.rs index 671b391450493..5b4eb23322272 100644 --- a/crates/config/src/extend.rs +++ b/crates/config/src/extend.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; /// Strategy for extending configuration from a base file. -#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ExtendStrategy { /// Uses `admerge` figment strategy. @@ -26,7 +26,7 @@ pub enum ExtendStrategy { /// Supports two formats: /// - String: `extends = "base.toml"` /// - Object: `extends = { path = "base.toml", strategy = "no-collision" }` -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Extends { /// Simple string path to base file @@ -35,7 +35,7 @@ pub enum Extends { Config(ExtendConfig), } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ExtendConfig { pub path: String, #[serde(default)] diff --git a/crates/config/src/filter.rs b/crates/config/src/filter.rs index 97b2ef48d7509..d92badb424a06 100644 --- a/crates/config/src/filter.rs +++ b/crates/config/src/filter.rs @@ -138,11 +138,11 @@ impl FileFilter for SkipBuildFilters { /// Only returns a match if _no_ exclusion filter matches fn is_match(&self, file: &Path) -> bool { self.matchers.iter().all(|matcher| { - if !matcher.is_match_exclude(file) { - false - } else { + if matcher.is_match_exclude(file) { file.strip_prefix(&self.project_root) .map_or(true, |stripped| matcher.is_match_exclude(stripped)) + } else { + false } }) } @@ -180,7 +180,7 @@ impl SkipBuildFilter { } /// Returns the pattern to match against a file - pub fn file_pattern(&self) -> &str { + pub const fn file_pattern(&self) -> &str { match self { Self::Tests => ".t.sol", Self::Scripts => ".s.sol", diff --git a/crates/config/src/fix.rs b/crates/config/src/fix.rs index d3f3d96ee8584..ef7f1660525a3 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -21,11 +21,11 @@ impl TomlFile { Ok(Self { doc, path }) } - fn doc(&self) -> &toml_edit::DocumentMut { + const fn doc(&self) -> &toml_edit::DocumentMut { &self.doc } - fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { + const fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { &mut self.doc } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index bea82b9a45449..ac5227e1c059c 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -78,19 +78,19 @@ pub enum NumberUnderscore { impl NumberUnderscore { /// Returns true if the option is `Preserve` #[inline] - pub fn is_preserve(self) -> bool { + pub const fn is_preserve(self) -> bool { matches!(self, Self::Preserve) } /// Returns true if the option is `Remove` #[inline] - pub fn is_remove(self) -> bool { + pub const fn is_remove(self) -> bool { matches!(self, Self::Remove) } /// Returns true if the option is `Remove` #[inline] - pub fn is_thousands(self) -> bool { + pub const fn is_thousands(self) -> bool { matches!(self, Self::Thousands) } } @@ -178,15 +178,15 @@ pub enum MultilineFuncHeaderStyle { } impl MultilineFuncHeaderStyle { - pub fn all(&self) -> bool { + pub const fn all(&self) -> bool { matches!(self, Self::All | Self::AllParams) } - pub fn params_first(&self) -> bool { + pub const fn params_first(&self) -> bool { matches!(self, Self::ParamsAlways | Self::ParamsFirstMulti) } - pub fn attrib_first(&self) -> bool { + pub const fn attrib_first(&self) -> bool { matches!(self, Self::AttributesFirst) } } @@ -224,15 +224,15 @@ pub enum PreferCompact { } impl PreferCompact { - pub fn calls(&self) -> bool { + pub const fn calls(&self) -> bool { matches!(self, Self::All | Self::Calls) } - pub fn events(&self) -> bool { + pub const fn events(&self) -> bool { matches!(self, Self::All | Self::Events | Self::EventsErrors) } - pub fn errors(&self) -> bool { + pub const fn errors(&self) -> bool { matches!(self, Self::All | Self::Errors | Self::EventsErrors) } } diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index f8fba0c97a10e..e38e6040ac902 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -90,7 +90,7 @@ impl FsPermissions { } } - if max_path_len > 0 { Some(highest_permission) } else { None } + (max_path_len > 0).then_some(highest_permission) } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries @@ -112,12 +112,12 @@ impl FsPermissions { } /// Returns true if no permissions are configured - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.permissions.is_empty() } /// Returns the number of configured permissions - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.permissions.len() } } @@ -158,7 +158,7 @@ impl PathPermission { } /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { + pub const fn is_granted(&self, kind: FsAccessKind) -> bool { self.access.is_granted(kind) } } @@ -197,7 +197,7 @@ pub enum FsAccessPermission { impl FsAccessPermission { /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { + pub const fn is_granted(&self, kind: FsAccessKind) -> bool { match (self, kind) { (Self::ReadWrite, _) => true, (Self::Write, FsAccessKind::Write) => true, diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index d5bb4baa980ab..03808cc15cc9a 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -132,12 +132,12 @@ impl FuzzCorpusConfig { } /// Whether edge coverage should be collected and displayed. - pub fn collect_edge_coverage(&self) -> bool { + pub const fn collect_edge_coverage(&self) -> bool { self.corpus_dir.is_some() || self.show_edge_coverage } /// Whether coverage guided fuzzing is enabled. - pub fn is_coverage_guided(&self) -> bool { + pub const fn is_coverage_guided(&self) -> bool { self.corpus_dir.is_some() } } diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index b463c1555ef9d..270df14a6c291 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -97,7 +97,11 @@ impl InlineConfig { /// Returns a [`figment::Provider`] for this [`InlineConfig`] at the given contract and function /// level. - pub fn provide<'a>(&'a self, contract: &'a str, function: &'a str) -> InlineConfigProvider<'a> { + pub const fn provide<'a>( + &'a self, + contract: &'a str, + function: &'a str, + ) -> InlineConfigProvider<'a> { InlineConfigProvider { inline: self, contract, function } } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index f75d32913fb15..f1d5844101379 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -58,14 +58,15 @@ impl NatSpec { warn!(?abs_path, %contract, "could not parse natspec with solar"); } - let mut used_solc = false; - if !used_solar + let used_solc = if !used_solar && let Some(ast) = &artifact.ast && let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { solc.parse(&mut natspecs, &contract, node, true); - used_solc = true; - } + true + } else { + false + }; if !used_solar && !used_solc { warn!(?abs_path, %contract, "could not parse natspec"); @@ -129,7 +130,7 @@ struct SolcParser { } impl SolcParser { - fn new() -> Self { + const fn new() -> Self { Self { _private: () } } @@ -222,7 +223,7 @@ struct SolarParser<'a> { } impl<'a> SolarParser<'a> { - fn new(sess: &'a Session) -> Self { + const fn new(sess: &'a Session) -> Self { Self { sess } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 591af88efdee4..15f8d4608ac5f 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -79,4 +79,9 @@ impl InvariantConfig { pub fn new(cache_dir: PathBuf) -> Self { Self { failure_persist_dir: Some(cache_dir), ..Default::default() } } + + /// Returns true if generated invariant calls may advance block time or height. + pub const fn has_delay(&self) -> bool { + self.max_block_delay.is_some() || self.max_time_delay.is_some() + } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 92156f39a0aa8..8b11087aaefb3 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -162,7 +162,7 @@ pub use semver; /// the "default" meta-profile. /// /// Note that these behaviors differ from those of [`Config::figment()`]. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -648,7 +648,7 @@ impl From for DenyLevel { impl DenyLevel { /// Returns `true` if the deny level includes warnings. - pub fn warnings(&self) -> bool { + pub const fn warnings(&self) -> bool { match self { Self::Never => false, Self::Warnings | Self::Notes => true, @@ -656,7 +656,7 @@ impl DenyLevel { } /// Returns `true` if the deny level includes notes. - pub fn notes(&self) -> bool { + pub const fn notes(&self) -> bool { match self { Self::Never | Self::Warnings => false, Self::Notes => true, @@ -664,7 +664,7 @@ impl DenyLevel { } /// Returns `true` if the deny level is set to never (only errors). - pub fn never(&self) -> bool { + pub const fn never(&self) -> bool { match self { Self::Never => true, Self::Warnings | Self::Notes => false, @@ -1009,7 +1009,7 @@ impl Config { /// Normalizes optimizer settings. /// See - pub fn normalized_optimizer_settings(mut self) -> Self { + pub const fn normalized_optimizer_settings(mut self) -> Self { self.normalize_optimizer_settings(); self } @@ -1023,7 +1023,7 @@ impl Config { /// - with default settings, optimizer is set to false and optimizer runs to 200 /// - if optimizer is set and optimizer runs not specified, then optimizer runs is set to 200 /// - enable optimizer if not explicitly set and optimizer runs set to a value greater than 0 - pub fn normalize_optimizer_settings(&mut self) { + pub const fn normalize_optimizer_settings(&mut self) { match (self.optimizer, self.optimizer_runs) { // Default: set the optimizer to false and optimizer runs to 200. (None, None) => { @@ -1067,7 +1067,7 @@ impl Config { /// Cleans up any duplicate `Remapping` and sorts them /// /// On windows this will convert any `\` in the remapping path into a `/` - pub fn sanitize_remappings(&mut self) { + pub const fn sanitize_remappings(&mut self) { #[cfg(target_os = "windows")] { // force `/` in remappings on windows @@ -1150,7 +1150,8 @@ impl Config { paths: &ProjectPathsConfig, ) -> Result>, SolcError> { - let mut map = BTreeMap::new(); + let mut map: BTreeMap> = + BTreeMap::new(); if self.compilation_restrictions.is_empty() { return Ok(BTreeMap::new()); } @@ -1170,9 +1171,7 @@ impl Config { }) { let res: RestrictionsWithVersion<_> = res.clone().try_into().map_err(SolcError::msg)?; - if !map.contains_key(source) { - map.insert(source.clone(), res); - } else { + if map.contains_key(source) { let value = map.remove(source.as_path()).unwrap(); if let Some(merged) = value.clone().merge(res) { map.insert(source.clone(), merged); @@ -1187,6 +1186,8 @@ impl Config { ); map.insert(source.clone(), value); } + } else { + map.insert(source.clone(), res); } } } @@ -1335,7 +1336,7 @@ impl Config { /// /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of /// `auto_detect_solc` - pub fn is_auto_detect(&self) -> bool { + pub const fn is_auto_detect(&self) -> bool { if self.solc.is_some() { return false; } @@ -2182,9 +2183,8 @@ impl Config { } if let Ok(entries) = cache_dir.as_path().read_dir() { for entry in entries.flatten().filter(|x| x.path().is_dir()) { - match Chain::from_str(&entry.file_name().to_string_lossy()) { - Ok(chain) => cache.chains.push(Self::list_foundry_chain_cache(chain)?), - Err(_) => continue, + if let Ok(chain) = Chain::from_str(&entry.file_name().to_string_lossy()) { + cache.chains.push(Self::list_foundry_chain_cache(chain)?); } } Ok(cache) @@ -2329,7 +2329,7 @@ impl Config { // Normalize `deny` based on the provided `deny_warnings` value. if figment.extract_inner::("deny_warnings").unwrap_or(false) - && let Ok(DenyLevel::Never) = figment.extract_inner("deny") + && figment.extract_inner("deny") == Ok(DenyLevel::Never) { figment = figment.merge(("deny", DenyLevel::Warnings)); } diff --git a/crates/config/src/lint.rs b/crates/config/src/lint.rs index 840a226f280fe..9494fe18d9f8b 100644 --- a/crates/config/src/lint.rs +++ b/crates/config/src/lint.rs @@ -120,7 +120,7 @@ pub enum Severity { } impl Severity { - fn to_str(self) -> &'static str { + const fn to_str(self) -> &'static str { match self { Self::High => "High", Self::Med => "Med", @@ -131,7 +131,7 @@ impl Severity { } } - fn to_str_kebab(self) -> &'static str { + const fn to_str_kebab(self) -> &'static str { match self { Self::High => "high", Self::Med => "medium", diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs index a6404da946cd3..6423e37a4f2fa 100644 --- a/crates/config/src/providers/ext.rs +++ b/crates/config/src/providers/ext.rs @@ -56,7 +56,7 @@ pub(crate) struct TomlFileProvider { } impl TomlFileProvider { - pub(crate) fn new(env_var: Option<&'static str>, default: PathBuf) -> Self { + pub(crate) const fn new(env_var: Option<&'static str>, default: PathBuf) -> Self { Self { env_var, env_val: OnceCell::new(), default, cache: OnceCell::new() } } diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 0f308aa1ba523..88c0255217908 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -24,12 +24,12 @@ pub struct Remappings { impl Remappings { /// Create a new `Remappings` wrapper with an empty vector. - pub fn new() -> Self { + pub const fn new() -> Self { Self { remappings: Vec::new(), project_paths: Vec::new() } } /// Create a new `Remappings` wrapper with a vector of remappings. - pub fn new_with_remappings(remappings: Vec) -> Self { + pub const fn new_with_remappings(remappings: Vec) -> Self { Self { remappings, project_paths: Vec::new() } } @@ -267,8 +267,7 @@ impl RemappingsProvider<'_> { // if the configured _src_ directory is set to something that // `Remapping::find_many` doesn't classify as a src directory (src, contracts, // lib), then we need to manually add a remapping here - let mut src_remapping = None; - if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] + let src_remapping = if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] .contains(&config.src.as_path()) && let Some(name) = lib.file_name().and_then(|s| s.to_str()) { @@ -280,8 +279,10 @@ impl RemappingsProvider<'_> { if !r.path.ends_with('/') { r.path.push('/') } - src_remapping = Some(r); - } + Some(r) + } else { + None + }; // Eventually, we could set context for remappings at this location, // taking into account the OS platform. We'll need to be able to handle nested diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs index 9113fcd76fffa..930066b29cf73 100644 --- a/crates/config/src/providers/warnings.rs +++ b/crates/config/src/providers/warnings.rs @@ -89,14 +89,10 @@ impl WarningsProvider

{ // Add warning for deprecated keys. let deprecated_key_warning = |key| { DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { - if key == *deprecated_key { - Some(Warning::DeprecatedKey { - old: deprecated_key.to_string(), - new: new_value.to_string(), - }) - } else { - None - } + (key == *deprecated_key).then(|| Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), + }) }) }; let profiles = data diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs index ab0b66f658dc7..95d836ada6856 100644 --- a/crates/debugger/src/debugger.rs +++ b/crates/debugger/src/debugger.rs @@ -27,7 +27,7 @@ impl Debugger { } /// Creates a new debugger. - pub fn new( + pub const fn new( debug_arena: Vec, identified_contracts: AddressHashMap, contracts_sources: ContractSources, diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs index e74ae58d66323..9e84b847c5329 100644 --- a/crates/debugger/src/node.rs +++ b/crates/debugger/src/node.rs @@ -22,7 +22,7 @@ pub struct DebugNode { impl DebugNode { /// Creates a new debug node. - pub fn new( + pub const fn new( address: Address, kind: CallKind, steps: Vec, diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index daf2d01d1da84..5b6dceac4c890 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -290,11 +290,11 @@ impl TUIContext<'_> { lines.push(u_num, line, u_text); } - let first = if !last_has_nl { + let first = if last_has_nl { + 0 + } else { lines.push_raw(h_num, &[Span::raw(last), Span::styled(actual[0], h_text)]); 1 - } else { - 0 }; // Skip the first line if it has already been handled above. diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index e3aa94e09aab8..9f4a445a6fb45 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -34,7 +34,7 @@ pub struct TUI<'a> { impl<'a> TUI<'a> { /// Creates a new debugger. - pub fn new(debugger_context: &'a mut DebuggerContext) -> Self { + pub const fn new(debugger_context: &'a mut DebuggerContext) -> Self { Self { debugger_context } } diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 30b0f9cd81179..7ecada9d33e72 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -65,7 +65,7 @@ impl DocBuilder { } /// Set `should_build` flag on the builder - pub fn with_should_build(mut self, should_build: bool) -> Self { + pub const fn with_should_build(mut self, should_build: bool) -> Self { self.should_build = should_build; self } @@ -393,7 +393,7 @@ impl DocBuilder { Some(self.config.book.clone()) } else { let book_path = self.config.book.join("book.toml"); - if book_path.is_file() { Some(book_path) } else { None } + book_path.is_file().then_some(book_path) } }; diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index cabc230e13b14..bcf80089cfe56 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -89,7 +89,7 @@ pub enum DocumentContent { } impl DocumentContent { - pub(crate) fn len(&self) -> usize { + pub(crate) const fn len(&self) -> usize { match self { Self::Empty => 0, Self::Single(_) => 1, @@ -101,13 +101,7 @@ impl DocumentContent { pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> { match self { Self::Empty => None, - Self::Single(item) => { - if index == 0 { - Some(item) - } else { - None - } - } + Self::Single(item) => (index == 0).then_some(item), Self::Constants(items) => items.get_mut(index), Self::OverloadedFunctions(items) => items.get_mut(index), } diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 431bf70df88f5..ee0175a5e21b1 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -66,7 +66,7 @@ pub struct Comment { impl Comment { /// Create new instance of [Comment]. - pub fn new(tag: CommentTag, value: String) -> Self { + pub const fn new(tag: CommentTag, value: String) -> Self { Self { tag, value } } @@ -86,21 +86,17 @@ impl Comment { /// Returns [None] if the word doesn't match. /// Useful for [CommentTag::Param] and [CommentTag::Return] comments. pub fn match_first_word(&self, expected: &str) -> Option<&str> { - self.split_first_word().and_then( - |(word, rest)| { - if word == expected { Some(rest) } else { None } - }, - ) + self.split_first_word().and_then(|(word, rest)| (word == expected).then_some(rest)) } /// Check if this comment is a custom tag. - pub fn is_custom(&self) -> bool { + pub const fn is_custom(&self) -> bool { matches!(self.tag, CommentTag::Custom(_)) } } /// The collection of natspec [Comment] items. -#[derive(Clone, Debug, Default, PartialEq, Deref, DerefMut)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Deref, DerefMut)] pub struct Comments(Vec); /// Forward the [Comments] function implementation to the [CommentsRef] @@ -151,12 +147,24 @@ impl Comments { impl From> for Comments { fn from(value: Vec) -> Self { - Self(value.into_iter().flat_map(Comment::from_doc_comment).collect()) + Self(value.into_iter().filter_map(Comment::from_doc_comment).collect()) + } +} + +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) } } /// The collection of references to natspec [Comment] items. -#[derive(Debug, Default, PartialEq, Deref)] +#[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); impl<'a> CommentsRef<'a> { diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs index cb8d9d1bcf5f4..925a7cc8a2259 100644 --- a/crates/doc/src/preprocessor/infer_hyperlinks.rs +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -203,7 +203,7 @@ struct InlineLinkTarget<'a> { } impl<'a> InlineLinkTarget<'a> { - fn borrowed(section: &'a str, target_path: PathBuf) -> Self { + const fn borrowed(section: &'a str, target_path: PathBuf) -> Self { Self { section: Cow::Borrowed(section), target_path } } } @@ -261,12 +261,8 @@ impl<'a> InlineLink<'a> { self.exact_identifier().split('-').next().unwrap() } - fn exact_identifier(&self) -> &str { - let mut name = self.identifier; - if let Some(part) = self.part { - name = part; - } - name + const fn exact_identifier(&self) -> &str { + if let Some(part) = self.part { part } else { self.identifier } } /// Returns the content of the matched link. @@ -275,7 +271,7 @@ impl<'a> InlineLink<'a> { } /// Returns true if the link is external. - fn is_external(&self) -> bool { + const fn is_external(&self) -> bool { self.part.is_some() } } diff --git a/crates/doc/src/solang_ext/ast_eq.rs b/crates/doc/src/solang_ext/ast_eq.rs index e19d3469ce829..1252ac4eb32bf 100644 --- a/crates/doc/src/solang_ext/ast_eq.rs +++ b/crates/doc/src/solang_ext/ast_eq.rs @@ -100,10 +100,10 @@ where T: AstEq, { fn ast_eq(&self, other: &Self) -> bool { - if self.len() != other.len() { - false - } else { + if self.len() == other.len() { self.iter().zip(other.iter()).all(|(left, right)| left.ast_eq(right)) + } else { + false } } } diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 1502322ac87f4..e3358b2f437dd 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,12 +1,11 @@ use crate::{ - CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, - GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, - document::{DocumentContent, read_context}, - helpers::function_signature, + document::{read_context, DocumentContent}, parser::ParseSource, - solang_ext::SafeUnwrap, writer::BufWriter, + CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput, + CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID, }; +use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; use solang_parser::pt::{Base, FunctionDefinition, VariableAttribute}; use std::path::Path; @@ -63,7 +62,7 @@ impl AsDoc for CommentsRef<'_> { // Write dev tags let devs = self.include_tag(CommentTag::Dev); for d in devs.iter() { - writer.write_dev_content(&d.value)?; + writer.write_italic(&d.value)?; writer.writeln()?; } @@ -88,7 +87,7 @@ impl AsDoc for CommentsRef<'_> { impl AsDoc for Base { fn as_doc(&self) -> AsDocResult { - Ok(self.name.identifiers.iter().map(|ident| ident.name.clone()).join(".")) + Ok(self.name.identifiers.iter().map(|ident| ident.name.to_owned()).join(".")) } } @@ -107,7 +106,16 @@ impl AsDoc for Document { for item in items { let func = item.as_function().unwrap(); - let heading = function_signature(func).replace(',', ", "); + let mut heading = item.source.ident(); + if !func.params.is_empty() { + heading.push_str(&format!( + "({})", + func.params + .iter() + .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default()) + .join(", ") + )); + } writer.write_heading(&heading)?; writer.write_section(&item.comments, &item.code)?; } @@ -253,7 +261,15 @@ impl AsDoc for Document { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code)?; + + let filtered_comments: Comments = (*comments) + .iter() + .filter(|c| c.tag != CommentTag::Custom("variant".to_string())) + .cloned() + .collect::>() + .into(); + + writer.write_section(&filtered_comments, code)?; writer.try_write_variant_table(item, comments) })?; } @@ -319,10 +335,9 @@ impl Document { comments: &Comments, code: &str, ) -> Result<(), std::fmt::Error> { - let func_sign = function_signature(func); - let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.clone()); + let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); let comments = - comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc)); + comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); // Write function name writer.write_heading(&func_name)?; diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index a62dff3f21582..085664f85fa81 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,4 +1,4 @@ -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike}; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; use solang_parser::pt::{ EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, @@ -40,7 +40,7 @@ impl BufWriter { } /// Returns true if the buffer is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.buf.is_empty() } @@ -89,17 +89,6 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes dev content to the buffer, handling markdown lists properly. - /// If the content contains markdown lists, it formats them correctly. - /// Otherwise, it writes the content in italics. - pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { - for line in text.lines() { - writeln!(self.buf, "{line}")?; - } - - Ok(()) - } - /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -202,7 +191,8 @@ impl BufWriter { params: &EnumDefinition, comments: &Comments, ) -> fmt::Result { - let comments = comments.include_tags(&[CommentTag::Param]); + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); // There is nothing to write. if comments.is_empty() { @@ -215,7 +205,7 @@ impl BufWriter { self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; - for value in ¶ms.values { + for value in params.values.iter() { let param_name = value.as_ref().map(|v| v.name.clone()); let comment = param_name.as_ref().and_then(|name| { @@ -223,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 2517893fa32b0..3e1a485eea09e 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -54,6 +54,7 @@ revm = { workspace = true, features = [ ] } revm-inspectors.workspace = true op-alloy-consensus = { workspace = true, features = ["k256"] } +alloy-op-evm.workspace = true op-alloy-network.workspace = true op-revm.workspace = true tempo-revm.workspace = true @@ -80,4 +81,5 @@ alloy-op-evm.workspace = true alloy-serde.workspace = true op-alloy-consensus.workspace = true op-alloy-rpc-types.workspace = true +anvil.workspace = true foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index b6afcaafce3ec..87a92e9d97460 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -72,7 +72,7 @@ impl Debug for CowBackend<'_, FEN> { impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> { /// Creates a new `CowBackend` with the given `Backend`. - pub fn new_borrowed(backend: &'a Backend) -> Self { + pub const fn new_borrowed(backend: &'a Backend) -> Self { Self { backend: Cow::Borrowed(backend), pending_init: None } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index e7c1345792aa3..5c5fe29c3154c 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -610,7 +610,7 @@ impl Backend { /// Returns all snapshots created in this backend #[allow(clippy::type_complexity)] - pub fn state_snapshots( + pub const fn state_snapshots( &self, ) -> &StateSnapshots< BackendStateSnapshot< @@ -650,7 +650,7 @@ impl Backend { } /// Returns the set caller address - pub fn caller_address(&self) -> Option

{ + pub const fn caller_address(&self) -> Option
{ self.inner.caller } @@ -659,12 +659,12 @@ impl Backend { /// If an error occurs in a restored state snapshot, the test is considered failed. /// /// This returns whether there was a reverted state snapshot that recorded an error. - pub fn has_state_snapshot_failure(&self) -> bool { + pub const fn has_state_snapshot_failure(&self) -> bool { self.inner.has_state_snapshot_failure } /// Sets the state snapshot failure flag. - pub fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { + pub const fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { self.inner.has_state_snapshot_failure = has_state_snapshot_failure } @@ -696,7 +696,7 @@ impl Backend { } /// Returns the memory db used if not in forking mode - pub fn mem_db(&self) -> &FoundryEvmInMemoryDB { + pub const fn mem_db(&self) -> &FoundryEvmInMemoryDB { &self.mem_db } @@ -1669,7 +1669,7 @@ pub struct Fork { impl Fork { /// Returns a reference to the underlying [`SharedBackend`]. - pub fn backend(&self) -> &SharedBackend { + pub const fn backend(&self) -> &SharedBackend { &self.db.db } @@ -2006,7 +2006,7 @@ pub(crate) fn merge_account_data, ) { - for addr in accounts.into_iter() { + for addr in accounts { merge_db_account_data(addr, active, &mut target_fork.db); merge_journaled_state_data(addr, active_journaled_state, &mut target_fork.journaled_state); } diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs index df47882befb4f..915657ccde868 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/snapshot.rs @@ -27,7 +27,7 @@ pub struct BackendStateSnapshot { impl BackendStateSnapshot { /// Takes a new state snapshot. - pub fn new(db: T, journaled_state: JournaledState, evm_env: EvmEnv) -> Self { + pub const fn new(db: T, journaled_state: JournaledState, evm_env: EvmEnv) -> Self { Self { db, journaled_state, snap_evm_env: evm_env } } @@ -57,7 +57,7 @@ pub enum RevertStateSnapshotAction { impl RevertStateSnapshotAction { /// Returns `true` if the action is to keep the state snapshot. - pub fn is_keep(&self) -> bool { + pub const fn is_keep(&self) -> bool { matches!(self, Self::RevertKeep) } } diff --git a/crates/evm/core/src/buffer.rs b/crates/evm/core/src/buffer.rs index e600708f43173..16655e382da0f 100644 --- a/crates/evm/core/src/buffer.rs +++ b/crates/evm/core/src/buffer.rs @@ -2,7 +2,7 @@ use alloy_primitives::U256; use revm::bytecode::opcode; /// Used to keep track of which buffer is currently active to be drawn by the debugger. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum BufferKind { Memory, Calldata, @@ -11,7 +11,7 @@ pub enum BufferKind { impl BufferKind { /// Helper to cycle through the active buffers. - pub fn next(&self) -> Self { + pub const fn next(&self) -> Self { match self { Self::Memory => Self::Calldata, Self::Calldata => Self::Returndata, @@ -83,17 +83,12 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { -2 => Some(1), -1 => Some(32), 0 => None, - 1.. => { - if (stack_index as usize) <= stack_len { - Some(stack[stack_len - stack_index as usize].saturating_to()) - } else { - None - } - } + 1.. => ((stack_index as usize) <= stack_len) + .then(|| stack[stack_len - stack_index as usize].saturating_to()), _ => panic!("invalid stack index"), }; - if buffer_access.0.is_some() || buffer_access.1.is_some() { + (buffer_access.0.is_some() || buffer_access.1.is_some()).then(|| { let (read, write) = buffer_access; let read_access = read.and_then(|b| { let (buffer, offset, len) = b; @@ -103,8 +98,6 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { let (offset, len) = b; Some(BufferAccess { offset: get_size(offset)?, len: get_size(len)? }) }); - Some(BufferAccesses { read: read_access, write: write_access }) - } else { - None - } + BufferAccesses { read: read_access, write: write_access } + }) } diff --git a/crates/evm/core/src/bytecode.rs b/crates/evm/core/src/bytecode.rs index 1d5bfea4f7e38..4e571e209d2fd 100644 --- a/crates/evm/core/src/bytecode.rs +++ b/crates/evm/core/src/bytecode.rs @@ -33,25 +33,25 @@ impl<'a> InstIter<'a> { /// Returns a new iterator that also yields the program counter alongside the opcode and /// immediate data. #[inline] - pub fn with_pc(self) -> InstIterWithPc<'a> { + pub const fn with_pc(self) -> InstIterWithPc<'a> { InstIterWithPc { iter: self, pc: 0 } } /// Returns the inner iterator. #[inline] - pub fn inner(&self) -> &slice::Iter<'a, u8> { + pub const fn inner(&self) -> &slice::Iter<'a, u8> { &self.iter } /// Returns the inner iterator. #[inline] - pub fn inner_mut(&mut self) -> &mut slice::Iter<'a, u8> { + pub const fn inner_mut(&mut self) -> &mut slice::Iter<'a, u8> { &mut self.iter } /// Returns the inner iterator. #[inline] - pub fn into_inner(self) -> slice::Iter<'a, u8> { + pub const fn into_inner(self) -> slice::Iter<'a, u8> { self.iter } } diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 0e28d92d91cae..7e4fe8d4dc15c 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -127,10 +127,6 @@ impl RevertDecoder { /// /// See [`decode`](Self::decode) for more information. pub fn maybe_decode(&self, err: &[u8], status: Option) -> Option { - if let Some(reason) = SkipReason::decode(err) { - return Some(reason.to_string()); - } - // Solidity's `Error(string)` (handled separately in order to strip revert: prefix) if let Some(ContractError(Revert(revert))) = RevertReason::decode(err) { return Some(revert.reason); @@ -274,4 +270,13 @@ mod tests { ); assert_eq!(decoder.decode(data, None), "ValidationFailed(0x756688fe)"); } + + #[test] + fn maybe_decode_magic_skip_is_not_skip_marker() { + let decoder = RevertDecoder::new(); + let reason = decoder.maybe_decode(crate::constants::MAGIC_SKIP, None).unwrap(); + + assert_eq!(reason, "FOUNDRY::SKIP"); + assert!(SkipReason::decode_self(&reason).is_none()); + } } diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs deleted file mode 100644 index 814f6e58d6fe1..0000000000000 --- a/crates/evm/core/src/evm.rs +++ /dev/null @@ -1,1170 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use crate::{ - FoundryBlock, FoundryContextExt, FoundryInspectorExt, FoundryTransaction, - FromAnyRpcTransaction, - backend::{DatabaseExt, JournaledState}, - constants::{CALLER, DEFAULT_CREATE2_DEPLOYER_CODEHASH, TEST_CONTRACT_ADDRESS}, - tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, -}; -use alloy_consensus::{ - SignableTransaction, Signed, constants::KECCAK_EMPTY, transaction::SignerRecoverable, -}; -use alloy_evm::{ - EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, eth::EthEvmContext, - precompiles::PrecompilesMap, -}; -use alloy_network::{Ethereum, Network}; -use alloy_primitives::{Address, Bytes, Signature, U256}; -use alloy_rlp::Decodable; -use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; -use foundry_config::FromEvmVersion; -use foundry_fork_db::{DatabaseError, ForkBlockEnv}; -use op_alloy_network::Optimism; -use op_revm::OpHaltReason; -use revm::{ - Context, - context::{ - BlockEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContextTr, TxEnv, - result::{EVMError, ExecResultAndState, ExecutionResult, HaltReason, ResultAndState}, - }, - handler::{ - EthFrame, EvmTr, FrameResult, FrameTr, Handler, ItemOrResult, instructions::EthInstructions, - }, - inspector::{InspectorEvmTr, InspectorHandler}, - interpreter::{ - CallInput, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, - FrameInput, Gas, InstructionResult, InterpreterResult, SharedMemory, - interpreter::EthInterpreter, interpreter_action::FrameInit, return_ok, - }, - primitives::hardfork::SpecId, - state::Bytecode, -}; -use serde::{Deserialize, Serialize}; -use tempo_alloy::TempoNetwork; -use tempo_chainspec::hardfork::TempoHardfork; -use tempo_evm::evm::TempoEvmFactory; -use tempo_precompiles::storage::StorageCtx; -use tempo_revm::{ - TempoBlockEnv, TempoHaltReason, TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, - gas_params::tempo_gas_params, handler::TempoEvmHandler, -}; - -/// Converts a network-specific halt reason into an [`InstructionResult`]. -pub trait IntoInstructionResult { - fn into_instruction_result(self) -> InstructionResult; -} - -impl IntoInstructionResult for HaltReason { - fn into_instruction_result(self) -> InstructionResult { - self.into() - } -} - -impl IntoInstructionResult for OpHaltReason { - fn into_instruction_result(self) -> InstructionResult { - match self { - Self::Base(eth) => eth.into(), - Self::FailedDeposit => InstructionResult::Stop, - } - } -} - -impl IntoInstructionResult for TempoHaltReason { - fn into_instruction_result(self) -> InstructionResult { - match self { - Self::Ethereum(eth) => eth.into(), - _ => InstructionResult::PrecompileError, - } - } -} - -/// Foundry's supertrait associating [Network] with [FoundryEvmFactory] -pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static { - type Network: Network< - TxEnvelope: Decodable - + SignerRecoverable - + From::UnsignedTx>> - + for<'d> Deserialize<'d> - + Serialize - + UIfmt, - UnsignedTx: SignableTransaction, - TransactionRequest: FoundryTransactionBuilder - + for<'d> Deserialize<'d> - + Serialize, - ReceiptResponse: FoundryReceiptResponse, - >; - type EvmFactory: FoundryEvmFactory::TxEnvelope>>; -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct EthEvmNetwork; -impl FoundryEvmNetwork for EthEvmNetwork { - type Network = Ethereum; - type EvmFactory = EthEvmFactory; -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct TempoEvmNetwork; -impl FoundryEvmNetwork for TempoEvmNetwork { - type Network = TempoNetwork; - type EvmFactory = TempoEvmFactory; -} - -// TODO: use `OpEvmFactory` once the FoundryEvmFactory impl is available for it. -#[derive(Clone, Copy, Debug, Default)] -pub struct OpEvmNetwork; -impl FoundryEvmNetwork for OpEvmNetwork { - type Network = Optimism; - type EvmFactory = EthEvmFactory; -} - -/// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. -pub type EvmFactoryFor = ::EvmFactory; -pub type FoundryContextFor<'db, FEN> = - as FoundryEvmFactory>::FoundryContext<'db>; -pub type TxEnvFor = as EvmFactory>::Tx; -pub type HaltReasonFor = as EvmFactory>::HaltReason; -pub type SpecFor = as EvmFactory>::Spec; -pub type BlockEnvFor = as EvmFactory>::BlockEnv; -pub type PrecompilesFor = as EvmFactory>::Precompiles; -pub type EvmEnvFor = EvmEnv, BlockEnvFor>; - -pub type NetworkFor = ::Network; -pub type TxEnvelopeFor = as Network>::TxEnvelope; -pub type TransactionRequestFor = as Network>::TransactionRequest; -pub type TransactionResponseFor = as Network>::TransactionResponse; -pub type BlockResponseFor = as Network>::BlockResponse; - -pub trait FoundryEvmFactory: - EvmFactory< - Spec: Into + FromEvmVersion + Default + Display + Copy + Unpin + Send + 'static, - BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin, - Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync, - HaltReason: IntoInstructionResult, - Precompiles = PrecompilesMap, - > + Clone - + Debug - + Default - + 'static -{ - /// Foundry Context abstraction - type FoundryContext<'db>: FoundryContextExt< - Block = Self::BlockEnv, - Tx = Self::Tx, - Spec = Self::Spec, - Db: DatabaseExt, - > - where - Self: 'db; - - /// The Foundry-wrapped EVM type produced by this factory. - type FoundryEvm<'db, I: FoundryInspectorExt>>: Evm< - DB = &'db mut dyn DatabaseExt, - Tx = Self::Tx, - BlockEnv = Self::BlockEnv, - Spec = Self::Spec, - HaltReason = Self::HaltReason, - > + Deref> - + IntoNestedEvm - where - Self: 'db; - - /// Creates a Foundry-wrapped EVM with the given inspector. - fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: I, - ) -> Self::FoundryEvm<'db, I>; - - /// Creates a Foundry-wrapped EVM with a dynamic inspector, returning a boxed [`NestedEvm`]. - /// - /// This helper exists because `&mut dyn FoundryInspectorExt` cannot satisfy - /// the generic `I: FoundryInspectorExt>` bound when the context - /// type is only known through an associated type. Each concrete factory implements this - /// directly, side-stepping the higher-kinded lifetime issue. - fn create_foundry_nested_evm<'db>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: &'db mut dyn FoundryInspectorExt>, - ) -> Box + 'db>; -} - -impl FoundryEvmFactory for EthEvmFactory { - type FoundryContext<'db> = EthEvmContext<&'db mut dyn DatabaseExt>; - - type FoundryEvm<'db, I: FoundryInspectorExt>> = EthFoundryEvm<'db, I>; - - fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: I, - ) -> Self::FoundryEvm<'db, I> { - let eth_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); - let mut inner = eth_evm.into_inner(); - inner.ctx.cfg.tx_chain_id_check = true; - - let mut evm = EthFoundryEvm { inner }; - evm.inspector().get_networks().inject_precompiles(evm.precompiles_mut()); - evm - } - - fn create_foundry_nested_evm<'db>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: &'db mut dyn FoundryInspectorExt>, - ) -> Box + 'db> { - Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_nested_evm()) - } -} - -/// Get the call inputs for the CREATE2 factory. -fn get_create2_factory_call_inputs( - salt: U256, - inputs: &CreateInputs, - deployer: Address, -) -> CallInputs { - let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat(); - CallInputs { - caller: inputs.caller(), - bytecode_address: deployer, - known_bytecode: None, - target_address: deployer, - scheme: CallScheme::Call, - value: CallValue::Transfer(inputs.value()), - input: CallInput::Bytes(calldata.into()), - gas_limit: inputs.gas_limit(), - is_static: false, - return_memory_offset: 0..0, - } -} - -type EthRevmEvm<'db, I> = RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>>, - PrecompilesMap, - EthFrame, ->; - -pub struct EthFoundryEvm< - 'db, - I: FoundryInspectorExt>>, -> { - pub inner: EthRevmEvm<'db, I>, -} - -impl<'db, I: FoundryInspectorExt>>> Evm - for EthFoundryEvm<'db, I> -{ - type Precompiles = PrecompilesMap; - type Inspector = I; - type DB = &'db mut dyn DatabaseExt; - type Error = EVMError; - type HaltReason = HaltReason; - type Spec = SpecId; - type Tx = TxEnv; - type BlockEnv = BlockEnv; - - fn block(&self) -> &BlockEnv { - &self.inner.block - } - - fn chain_id(&self) -> u64 { - self.inner.ctx.cfg.chain_id - } - - fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { - (&self.inner.ctx.journaled_state.database, &self.inner.inspector, &self.inner.precompiles) - } - - fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { - ( - &mut self.inner.ctx.journaled_state.database, - &mut self.inner.inspector, - &mut self.inner.precompiles, - ) - } - - fn set_inspector_enabled(&mut self, _enabled: bool) { - unimplemented!("FoundryEvm is always inspecting") - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - self.inner.set_tx(tx); - - let mut handler = EthFoundryHandler::::default(); - let result = handler.inspect_run(&mut self.inner)?; - - Ok(ResultAndState::new(result, self.inner.ctx.journaled_state.inner.state.clone())) - } - - fn transact_system_call( - &mut self, - _caller: Address, - _contract: Address, - _data: Bytes, - ) -> Result, Self::Error> { - unimplemented!() - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; - - (journaled_state.database, EvmEnv { block_env, cfg_env }) - } -} - -impl<'db, I: FoundryInspectorExt>>> Deref - for EthFoundryEvm<'db, I> -{ - type Target = EthEvmContext<&'db mut dyn DatabaseExt>; - - fn deref(&self) -> &Self::Target { - &self.inner.ctx - } -} - -impl<'db, I: FoundryInspectorExt>>> DerefMut - for EthFoundryEvm<'db, I> -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.ctx - } -} - -/// Trait for converting a Foundry EVM wrapper into its inner `NestedEvm` implementation. -/// -/// Both [`EthFoundryEvm`] and [`TempoFoundryEvm`] wrap an inner revm EVM that implements -/// [`NestedEvm`]. This trait provides a uniform way to unwrap them. -pub trait IntoNestedEvm { - /// The inner type that implements [`NestedEvm`]. - type Inner: NestedEvm; - - /// Consumes the wrapper, returning the inner revm EVM. - fn into_nested_evm(self) -> Self::Inner; -} - -impl<'db, I: FoundryInspectorExt>>> - IntoNestedEvm for EthFoundryEvm<'db, I> -{ - type Inner = EthRevmEvm<'db, I>; - - fn into_nested_evm(self) -> Self::Inner { - self.inner - } -} - -/// Object-safe trait exposing the operations that cheatcode nested EVM closures need. -/// -/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.) -/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type. -pub trait NestedEvm { - /// The spec type. - type Spec; - /// The block environment type. - type Block; - /// The transaction environment type. - type Tx; - - /// Returns a mutable reference to the journal inner state (`JournaledState`). - fn journal_inner_mut(&mut self) -> &mut JournaledState; - - /// Runs a single execution frame (create or call) through the EVM handler loop. - fn run_execution(&mut self, frame: FrameInput) -> Result>; - - /// Executes a full transaction with the given tx env. - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, EVMError>; - - fn to_evm_env(&self) -> EvmEnv; -} - -impl<'db, I: FoundryInspectorExt>>> NestedEvm - for EthRevmEvm<'db, I> -{ - type Spec = SpecId; - type Block = BlockEnv; - type Tx = TxEnv; - - fn journal_inner_mut(&mut self) -> &mut JournaledState { - &mut self.ctx_mut().journaled_state.inner - } - - fn run_execution(&mut self, frame: FrameInput) -> Result> { - let mut handler = EthFoundryHandler::::default(); - - // Create first frame - let memory = - SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone()); - let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; - - // Run execution loop - let mut frame_result = handler.inspect_run_exec_loop(self, first_frame_input)?; - - // Handle last frame result - handler.last_frame_result(self, &mut frame_result)?; - - Ok(frame_result) - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, EVMError> { - self.set_tx(tx); - - let mut handler = EthFoundryHandler::::default(); - let result = handler.inspect_run(self)?; - - Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) - } - - fn to_evm_env(&self) -> EvmEnv { - self.ctx_ref().evm_clone() - } -} - -/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations. -pub type NestedEvmClosure<'a, Spec, Block, Tx> = - &'a mut dyn FnMut( - &mut dyn NestedEvm, - ) -> Result<(), EVMError>; - -/// Clones the current context (env + journal), passes the database, cloned env, -/// and cloned journal inner to the callback. The callback builds whatever EVM it -/// needs, runs its operations, and returns `(result, modified_env, modified_journal)`. -/// Modified state is written back after the callback returns. -pub fn with_cloned_context( - ecx: &mut CTX, - f: impl FnOnce( - &mut CTX::Db, - EvmEnv, - JournaledState, - ) - -> Result<(EvmEnv, JournaledState), EVMError>, -) -> Result<(), EVMError> { - let evm_env = ecx.evm_clone(); - - let (db, journal_inner) = ecx.db_journal_inner_mut(); - let journal_inner_clone = journal_inner.clone(); - - let (sub_evm_env, sub_inner) = f(db, evm_env, journal_inner_clone)?; - - // Write back modified state. The db borrow was released when f returned. - ecx.set_journal_inner(sub_inner); - ecx.set_evm(sub_evm_env); - - Ok(()) -} - -pub struct EthFoundryHandler< - 'db, - I: FoundryInspectorExt>>, -> { - create2_overrides: Vec<(usize, CallInputs)>, - _phantom: PhantomData<(&'db mut dyn DatabaseExt, I)>, -} - -impl<'db, I: FoundryInspectorExt>>> Default - for EthFoundryHandler<'db, I> -{ - fn default() -> Self { - Self { create2_overrides: Vec::new(), _phantom: PhantomData } - } -} - -// Blanket Handler implementation for FoundryHandler, needed for implementing the InspectorHandler -// trait. -impl<'db, I: FoundryInspectorExt>>> Handler - for EthFoundryHandler<'db, I> -{ - type Evm = RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>>, - PrecompilesMap, - EthFrame, - >; - type Error = EVMError; - type HaltReason = HaltReason; -} - -impl<'db, I: FoundryInspectorExt>>> - EthFoundryHandler<'db, I> -{ - /// Handles CREATE2 frame initialization, potentially transforming it to use the CREATE2 - /// factory. - fn handle_create_frame( - &mut self, - evm: &mut ::Evm, - init: &mut FrameInit, - ) -> Result, ::Error> { - if let FrameInput::Create(inputs) = &init.frame_input - && let CreateScheme::Create2 { salt } = inputs.scheme() - { - let (ctx, inspector) = evm.ctx_inspector(); - - if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { - let gas_limit = inputs.gas_limit(); - - // Get CREATE2 deployer. - let create2_deployer = evm.inspector().create2_deployer(); - - // Generate call inputs for CREATE2 factory. - let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); - - // Push data about current override to the stack. - self.create2_overrides.push((evm.journal().depth(), call_inputs.clone())); - - // Sanity check that CREATE2 deployer exists. - let code_hash = evm.journal_mut().load_account(create2_deployer)?.info.code_hash; - if code_hash == KECCAK_EMPTY { - return Ok(Some(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from( - format!("missing CREATE2 deployer: {create2_deployer}") - .into_bytes(), - ), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - was_precompile_called: false, - precompile_call_logs: vec![], - }))); - } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { - return Ok(Some(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 deployer bytecode".into(), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - was_precompile_called: false, - precompile_call_logs: vec![], - }))); - } - - // Rewrite the frame init - init.frame_input = FrameInput::Call(Box::new(call_inputs)); - } - } - Ok(None) - } - - /// Transforms CREATE2 factory call results back into CREATE outcomes. - fn handle_create2_override( - &mut self, - evm: &mut ::Evm, - result: FrameResult, - ) -> FrameResult { - if self.create2_overrides.last().is_some_and(|(depth, _)| *depth == evm.journal().depth()) { - let (_, call_inputs) = self.create2_overrides.pop().unwrap(); - let FrameResult::Call(mut call) = result else { - unreachable!("create2 override should be a call frame"); - }; - - // Decode address from output. - let address = match call.instruction_result() { - return_ok!() => Address::try_from(call.output().as_ref()) - .map_err(|_| { - call.result = InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 factory output".into(), - gas: Gas::new(call_inputs.gas_limit), - }; - }) - .ok(), - _ => None, - }; - - FrameResult::Create(CreateOutcome { result: call.result, address }) - } else { - result - } - } -} - -impl<'db, I: FoundryInspectorExt>>> - InspectorHandler for EthFoundryHandler<'db, I> -{ - type IT = EthInterpreter; - - fn inspect_run_exec_loop( - &mut self, - evm: &mut Self::Evm, - first_frame_input: <::Frame as FrameTr>::FrameInit, - ) -> Result { - let res = evm.inspect_frame_init(first_frame_input)?; - - if let ItemOrResult::Result(frame_result) = res { - return Ok(frame_result); - } - - loop { - let call_or_result = evm.inspect_frame_run()?; - - let result = match call_or_result { - ItemOrResult::Item(mut init) => { - // Handle CREATE/CREATE2 frame initialization - if let Some(frame_result) = self.handle_create_frame(evm, &mut init)? { - return Ok(frame_result); - } - - match evm.inspect_frame_init(init)? { - ItemOrResult::Item(_) => continue, - ItemOrResult::Result(result) => result, - } - } - ItemOrResult::Result(result) => result, - }; - - // Handle CREATE2 override transformation if needed - let result = self.handle_create2_override(evm, result); - - if let Some(result) = evm.frame_return_result(result)? { - return Ok(result); - } - } - } -} - -// Will be removed when the next revm release includes bluealloy/revm#3518. -type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt, I>; - -/// Tempo counterpart of [`EthFoundryEvm`]. Wraps `tempo_revm::TempoEvm` and routes execution -/// through [`TempoFoundryHandler`] which composes [`TempoEvmHandler`] with CREATE2 factory -/// redirect logic. -/// -/// Uses [`TempoEvmFactory`] for construction to reuse factory setup logic, then unwraps to the -/// raw revm EVM via `into_inner()` since the handler operates at the revm level. -pub struct TempoFoundryEvm< - 'db, - I: FoundryInspectorExt>>, -> { - pub inner: TempoRevmEvm<'db, I>, -} - -/// Initialize Tempo precompiles and contracts for a newly created [`TempoFoundryEvm`]. -/// -/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode, -/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`]. -/// -/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode -/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles -/// on Tempo nodes (no real EVM bytecode on-chain). -fn initialize_tempo_evm< - 'db, - I: FoundryInspectorExt>>, ->( - evm: &mut TempoFoundryEvm<'db, I>, - is_forked: bool, -) { - let ctx = &mut evm.inner.inner.ctx; - StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || { - if is_forked { - // In fork mode, warm up precompile accounts to avoid repeated RPC fetches. - let mut sctx = StorageCtx; - let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef])); - for addr in TEMPO_PRECOMPILE_ADDRESSES.iter().chain(TEMPO_TIP20_TOKENS.iter()) { - sctx.set_code(*addr, sentinel.clone()) - .expect("failed to warm tempo precompile address"); - } - } else { - // In non-fork mode, run full genesis initialization. - initialize_tempo_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER) - .expect("tempo genesis initialization failed"); - } - }); -} - -impl FoundryEvmFactory for TempoEvmFactory { - type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt>; - - type FoundryEvm<'db, I: FoundryInspectorExt>> = - TempoFoundryEvm<'db, I>; - - fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: I, - ) -> Self::FoundryEvm<'db, I> { - let is_forked = db.is_forked_mode(); - let spec = *evm_env.spec_id(); - let tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); - let mut inner = tempo_evm.into_inner(); - inner.ctx.cfg.gas_params = tempo_gas_params(spec); - inner.ctx.cfg.tx_chain_id_check = true; - - let mut evm = TempoFoundryEvm { inner }; - let networks = Evm::inspector(&evm).get_networks(); - networks.inject_precompiles(evm.precompiles_mut()); - - initialize_tempo_evm(&mut evm, is_forked); - evm - } - - fn create_foundry_nested_evm<'db>( - &self, - db: &'db mut dyn DatabaseExt, - evm_env: EvmEnv, - inspector: &'db mut dyn FoundryInspectorExt>, - ) -> Box + 'db> - { - Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_nested_evm()) - } -} - -impl<'db, I: FoundryInspectorExt>>> Evm - for TempoFoundryEvm<'db, I> -{ - type Precompiles = PrecompilesMap; - type Inspector = I; - type DB = &'db mut dyn DatabaseExt; - type Error = EVMError; - type HaltReason = TempoHaltReason; - type Spec = TempoHardfork; - type Tx = TempoTxEnv; - type BlockEnv = TempoBlockEnv; - - fn block(&self) -> &TempoBlockEnv { - &self.inner.block - } - - fn chain_id(&self) -> u64 { - self.inner.ctx.cfg.chain_id - } - - fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { - let evm = &self.inner.inner; - (&evm.ctx.journaled_state.database, &evm.inspector, &evm.precompiles) - } - - fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { - let evm = &mut self.inner.inner; - (&mut evm.ctx.journaled_state.database, &mut evm.inspector, &mut evm.precompiles) - } - - fn set_inspector_enabled(&mut self, _enabled: bool) { - unimplemented!("TempoFoundryEvm is always inspecting") - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - self.inner.set_tx(tx); - - let mut handler = TempoFoundryHandler::::default(); - let result = handler.inspect_run(&mut self.inner)?; - - Ok(ResultAndState::new(result, self.inner.inner.ctx.journaled_state.inner.state.clone())) - } - - fn transact_system_call( - &mut self, - _caller: Address, - _contract: Address, - _data: Bytes, - ) -> Result>, Self::Error> { - unimplemented!() - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - let revm_evm = self.inner.inner; - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = revm_evm.ctx; - (journaled_state.database, EvmEnv { block_env, cfg_env }) - } -} - -impl<'db, I: FoundryInspectorExt>>> Deref - for TempoFoundryEvm<'db, I> -{ - type Target = TempoContext<&'db mut dyn DatabaseExt>; - - fn deref(&self) -> &Self::Target { - &self.inner.ctx - } -} - -impl<'db, I: FoundryInspectorExt>>> DerefMut - for TempoFoundryEvm<'db, I> -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.ctx - } -} - -impl<'db, I: FoundryInspectorExt>>> - IntoNestedEvm for TempoFoundryEvm<'db, I> -{ - type Inner = TempoRevmEvm<'db, I>; - - fn into_nested_evm(self) -> Self::Inner { - self.inner - } -} - -/// Maps a Tempo [`EVMError`] to the common `EVMError` used by [`NestedEvm`]. -/// -/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains -/// an associated `Error` type, this mapping can be removed. -fn map_tempo_error(e: EVMError) -> EVMError { - match e { - EVMError::Database(db) => EVMError::Database(db), - EVMError::Header(h) => EVMError::Header(h), - EVMError::Custom(s) => EVMError::Custom(s), - EVMError::Transaction(t) => match t { - TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth), - t => EVMError::Custom(format!("tempo transaction error: {t}")), - }, - } -} - -impl<'db, I: FoundryInspectorExt>>> NestedEvm - for TempoRevmEvm<'db, I> -{ - type Spec = TempoHardfork; - type Block = TempoBlockEnv; - type Tx = TempoTxEnv; - - fn journal_inner_mut(&mut self) -> &mut JournaledState { - &mut self.ctx_mut().journaled_state.inner - } - - fn run_execution(&mut self, frame: FrameInput) -> Result> { - let mut handler = TempoFoundryHandler::::default(); - - let memory = - SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone()); - let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; - - let mut frame_result = - handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?; - - handler.last_frame_result(self, &mut frame_result).map_err(map_tempo_error)?; - - Ok(frame_result) - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, EVMError> { - self.set_tx(tx); - - let mut handler = TempoFoundryHandler::::default(); - let result = handler.inspect_run(self).map_err(map_tempo_error)?; - - let result = result.map_haltreason(|h| match h { - TempoHaltReason::Ethereum(eth) => eth, - _ => HaltReason::PrecompileError, - }); - - Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) - } - - fn to_evm_env(&self) -> EvmEnv { - self.ctx_ref().evm_clone() - } -} - -/// Tempo counterpart of [`EthFoundryHandler`]. Wraps [`TempoEvmHandler`] and injects CREATE2 -/// factory redirect logic into the execution loop. Delegates all [`Handler`] methods to -/// [`TempoEvmHandler`] for proper Tempo validation, fee collection, AA dispatch, and gas -/// handling. -/// -/// Will be removed when the next revm release includes bluealloy/revm#3518. -pub struct TempoFoundryHandler< - 'db, - I: FoundryInspectorExt>>, -> { - inner: TempoEvmHandler<&'db mut dyn DatabaseExt, I>, - create2_overrides: Vec<(usize, CallInputs)>, -} - -impl<'db, I: FoundryInspectorExt>>> Default - for TempoFoundryHandler<'db, I> -{ - fn default() -> Self { - Self { inner: TempoEvmHandler::new(), create2_overrides: Vec::new() } - } -} - -impl<'db, I: FoundryInspectorExt>>> Handler - for TempoFoundryHandler<'db, I> -{ - type Evm = TempoRevmEvm<'db, I>; - type Error = EVMError; - type HaltReason = TempoHaltReason; - - #[inline] - fn run( - &mut self, - evm: &mut Self::Evm, - ) -> Result, Self::Error> { - self.inner.run(evm) - } - - #[inline] - fn execution( - &mut self, - evm: &mut Self::Evm, - init_and_floor_gas: &revm::interpreter::InitialAndFloorGas, - ) -> Result { - self.inner.execution(evm, init_and_floor_gas) - } - - #[inline] - fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { - self.inner.validate_env(evm) - } - - #[inline] - fn validate_against_state_and_deduct_caller( - &self, - evm: &mut Self::Evm, - ) -> Result<(), Self::Error> { - self.inner.validate_against_state_and_deduct_caller(evm) - } - - #[inline] - fn reimburse_caller( - &self, - evm: &mut Self::Evm, - exec_result: &mut <::Frame as FrameTr>::FrameResult, - ) -> Result<(), Self::Error> { - self.inner.reimburse_caller(evm, exec_result) - } - - #[inline] - fn reward_beneficiary( - &self, - evm: &mut Self::Evm, - exec_result: &mut <::Frame as FrameTr>::FrameResult, - ) -> Result<(), Self::Error> { - self.inner.reward_beneficiary(evm, exec_result) - } - - #[inline] - fn validate_initial_tx_gas( - &self, - evm: &mut Self::Evm, - ) -> Result { - self.inner.validate_initial_tx_gas(evm) - } - - #[inline] - fn execution_result( - &mut self, - evm: &mut Self::Evm, - result: <::Frame as FrameTr>::FrameResult, - result_gas: revm::context::result::ResultGas, - ) -> Result, Self::Error> { - self.inner.execution_result(evm, result, result_gas) - } - - #[inline] - fn catch_error( - &self, - evm: &mut Self::Evm, - error: Self::Error, - ) -> Result, Self::Error> { - self.inner.catch_error(evm, error) - } -} - -/// CREATE2 factory redirect execution loop for Tempo. -fn create2_exec_loop< - 'db, - I: FoundryInspectorExt>>, ->( - create2_overrides: &mut Vec<(usize, CallInputs)>, - evm: &mut TempoRevmEvm<'db, I>, - first_frame_input: FrameInit, -) -> Result> { - let res = evm.inspect_frame_init(first_frame_input)?; - - if let ItemOrResult::Result(frame_result) = res { - return Ok(frame_result); - } - - loop { - let call_or_result = evm.inspect_frame_run()?; - - let result = match call_or_result { - ItemOrResult::Item(mut init) => { - if let Some(frame_result) = handle_create2_frame(create2_overrides, evm, &mut init)? - { - return Ok(frame_result); - } - - match evm.inspect_frame_init(init)? { - ItemOrResult::Item(_) => continue, - ItemOrResult::Result(result) => result, - } - } - ItemOrResult::Result(result) => result, - }; - - let result = handle_create2_result(create2_overrides, evm, result); - - if let Some(result) = evm.frame_return_result(result)? { - return Ok(result); - } - } -} - -/// Handles CREATE2 frame initialization, potentially transforming it to use the CREATE2 factory. -fn handle_create2_frame< - 'db, - I: FoundryInspectorExt>>, ->( - create2_overrides: &mut Vec<(usize, CallInputs)>, - evm: &mut TempoRevmEvm<'db, I>, - init: &mut FrameInit, -) -> Result, EVMError> { - if let FrameInput::Create(inputs) = &init.frame_input - && let CreateScheme::Create2 { salt } = inputs.scheme() - { - let (ctx, inspector) = evm.ctx_inspector(); - - if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { - let gas_limit = inputs.gas_limit(); - let create2_deployer = evm.inspector().create2_deployer(); - let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); - - create2_overrides.push((evm.journal().depth(), call_inputs.clone())); - - let code_hash = evm.journal_mut().load_account(create2_deployer)?.info.code_hash; - if code_hash == KECCAK_EMPTY { - return Ok(Some(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from( - format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(), - ), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - was_precompile_called: false, - precompile_call_logs: vec![], - }))); - } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { - return Ok(Some(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 deployer bytecode".into(), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - was_precompile_called: false, - precompile_call_logs: vec![], - }))); - } - - init.frame_input = FrameInput::Call(Box::new(call_inputs)); - } - } - Ok(None) -} - -/// Transforms CREATE2 factory call results back into CREATE outcomes. -fn handle_create2_result< - 'db, - I: FoundryInspectorExt>>, ->( - create2_overrides: &mut Vec<(usize, CallInputs)>, - evm: &mut TempoRevmEvm<'db, I>, - result: FrameResult, -) -> FrameResult { - if create2_overrides.last().is_some_and(|(depth, _)| *depth == evm.journal().depth()) { - let (_, call_inputs) = create2_overrides.pop().unwrap(); - let FrameResult::Call(mut call) = result else { - unreachable!("create2 override should be a call frame"); - }; - - let address = match call.instruction_result() { - return_ok!() => Address::try_from(call.output().as_ref()) - .map_err(|_| { - call.result = InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 factory output".into(), - gas: Gas::new(call_inputs.gas_limit), - }; - }) - .ok(), - _ => None, - }; - - FrameResult::Create(CreateOutcome { result: call.result, address }) - } else { - result - } -} - -impl<'db, I: FoundryInspectorExt>>> - InspectorHandler for TempoFoundryHandler<'db, I> -{ - type IT = EthInterpreter; - - /// Overrides `inspect_run` to load Tempo fee fields before delegating to the default - /// execution pipeline. This is necessary because the default `inspect_run` calls - /// `validate` → `validate_against_state_and_deduct_caller` directly, bypassing - /// `Handler::run` where `load_fee_fields` is normally invoked. - fn inspect_run( - &mut self, - evm: &mut Self::Evm, - ) -> Result, Self::Error> { - self.inner.load_fee_fields(evm)?; - - match self.inspect_run_without_catch_error(evm) { - Ok(output) => Ok(output), - Err(e) => self.catch_error(evm, e), - } - } - - /// Delegates to [`TempoEvmHandler::inspect_execution_with`], injecting the CREATE2 factory - /// redirect exec loop. AA multi-call dispatch and gas adjustments are handled by the inner - /// Tempo handler. - #[inline] - fn inspect_execution( - &mut self, - evm: &mut Self::Evm, - init_and_floor_gas: &revm::interpreter::InitialAndFloorGas, - ) -> Result { - let overrides = &mut self.create2_overrides; - self.inner.inspect_execution_with(evm, init_and_floor_gas, |_handler, evm, init| { - create2_exec_loop(overrides, evm, init) - }) - } - - fn inspect_run_exec_loop( - &mut self, - evm: &mut Self::Evm, - first_frame_input: <::Frame as FrameTr>::FrameInit, - ) -> Result { - create2_exec_loop(&mut self.create2_overrides, evm, first_frame_input) - } -} diff --git a/crates/evm/core/src/evm/eth.rs b/crates/evm/core/src/evm/eth.rs new file mode 100644 index 0000000000000..fae182ae639b6 --- /dev/null +++ b/crates/evm/core/src/evm/eth.rs @@ -0,0 +1,358 @@ +use super::*; + +pub type EthRevmEvm<'db, I> = RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>>, + PrecompilesMap, + EthFrame, +>; + +pub struct EthFoundryEvm< + 'db, + I: FoundryInspectorExt>>, +> { + pub inner: EthRevmEvm<'db, I>, +} + +impl<'db, I: FoundryInspectorExt>>> Evm + for EthFoundryEvm<'db, I> +{ + type Precompiles = PrecompilesMap; + type Inspector = I; + type DB = &'db mut dyn DatabaseExt; + type Error = EVMError; + type HaltReason = HaltReason; + type Spec = SpecId; + type Tx = TxEnv; + type BlockEnv = BlockEnv; + + fn block(&self) -> &BlockEnv { + &self.inner.block + } + + fn chain_id(&self) -> u64 { + self.inner.ctx.cfg.chain_id + } + + fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + (&self.inner.ctx.journaled_state.database, &self.inner.inspector, &self.inner.precompiles) + } + + fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + ( + &mut self.inner.ctx.journaled_state.database, + &mut self.inner.inspector, + &mut self.inner.precompiles, + ) + } + + fn set_inspector_enabled(&mut self, _enabled: bool) { + unimplemented!("FoundryEvm is always inspecting") + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + self.inner.set_tx(tx); + + let mut handler = EthFoundryHandler::::default(); + let result = handler.inspect_run(&mut self.inner)?; + + Ok(ResultAndState::new(result, self.inner.ctx.journaled_state.inner.state.clone())) + } + + fn transact_system_call( + &mut self, + _caller: Address, + _contract: Address, + _data: Bytes, + ) -> Result, Self::Error> { + unimplemented!() + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; + + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } +} + +impl<'db, I: FoundryInspectorExt>>> Deref + for EthFoundryEvm<'db, I> +{ + type Target = EthEvmContext<&'db mut dyn DatabaseExt>; + + fn deref(&self) -> &Self::Target { + &self.inner.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> DerefMut + for EthFoundryEvm<'db, I> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> + IntoNestedEvm for EthFoundryEvm<'db, I> +{ + type Inner = EthRevmEvm<'db, I>; + + fn into_nested_evm(self) -> Self::Inner { + self.inner + } +} + +impl FoundryEvmFactory for EthEvmFactory { + type FoundryContext<'db> = EthEvmContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = EthFoundryEvm<'db, I>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let eth_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + let mut inner = eth_evm.into_inner(); + inner.ctx.cfg.tx_chain_id_check = true; + + let mut evm = EthFoundryEvm { inner }; + evm.inspector().get_networks().inject_precompiles(evm.precompiles_mut()); + evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db> { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_nested_evm()) + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for EthRevmEvm<'db, I> +{ + type Spec = SpecId; + type Block = BlockEnv; + type Tx = TxEnv; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx_mut().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = EthFoundryHandler::::default(); + + // Create first frame + let memory = + SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + // Run execution loop + let mut frame_result = handler.inspect_run_exec_loop(self, first_frame_input)?; + + // Handle last frame result + handler.last_frame_result(self, &mut frame_result)?; + + Ok(frame_result) + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError> { + self.set_tx(tx); + + let mut handler = EthFoundryHandler::::default(); + let result = handler.inspect_run(self)?; + + Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + self.ctx_ref().evm_clone() + } +} + +pub struct EthFoundryHandler< + 'db, + I: FoundryInspectorExt>>, +> { + create2_overrides: Vec<(usize, CallInputs)>, + _phantom: PhantomData<(&'db mut dyn DatabaseExt, I)>, +} + +impl<'db, I: FoundryInspectorExt>>> Default + for EthFoundryHandler<'db, I> +{ + fn default() -> Self { + Self { create2_overrides: Vec::new(), _phantom: PhantomData } + } +} + +// Blanket Handler implementation for FoundryHandler, needed for implementing the InspectorHandler +// trait. +impl<'db, I: FoundryInspectorExt>>> Handler + for EthFoundryHandler<'db, I> +{ + type Evm = RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>>, + PrecompilesMap, + EthFrame, + >; + type Error = EVMError; + type HaltReason = HaltReason; +} + +impl<'db, I: FoundryInspectorExt>>> + EthFoundryHandler<'db, I> +{ + /// Handles CREATE2 frame initialization, potentially transforming it to use the CREATE2 + /// factory. + fn handle_create_frame( + &mut self, + evm: &mut ::Evm, + init: &mut FrameInit, + ) -> Result, ::Error> { + if let FrameInput::Create(inputs) = &init.frame_input + && let CreateScheme::Create2 { salt } = inputs.scheme() + { + let (ctx, inspector) = evm.ctx_inspector(); + + if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { + let gas_limit = inputs.gas_limit(); + + // Get CREATE2 deployer. + let create2_deployer = evm.inspector().create2_deployer(); + + // Generate call inputs for CREATE2 factory. + let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); + + // Push data about current override to the stack. + self.create2_overrides.push((evm.journal().depth(), call_inputs.clone())); + + // Sanity check that CREATE2 deployer exists. + let code_hash = evm.journal_mut().load_account(create2_deployer)?.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(Some(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from( + format!("missing CREATE2 deployer: {create2_deployer}") + .into_bytes(), + ), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + }))); + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(Some(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + }))); + } + + // Rewrite the frame init + init.frame_input = FrameInput::Call(Box::new(call_inputs)); + } + } + Ok(None) + } + + /// Transforms CREATE2 factory call results back into CREATE outcomes. + fn handle_create2_override( + &mut self, + evm: &mut ::Evm, + result: FrameResult, + ) -> FrameResult { + if self.create2_overrides.last().is_some_and(|(depth, _)| *depth == evm.journal().depth()) { + let (_, call_inputs) = self.create2_overrides.pop().unwrap(); + let FrameResult::Call(mut call) = result else { + unreachable!("create2 override should be a call frame"); + }; + + // Decode address from output. + let address = match call.instruction_result() { + return_ok!() => Address::try_from(call.output().as_ref()) + .map_err(|_| { + call.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + + FrameResult::Create(CreateOutcome { result: call.result, address }) + } else { + result + } + } +} + +impl<'db, I: FoundryInspectorExt>>> + InspectorHandler for EthFoundryHandler<'db, I> +{ + type IT = EthInterpreter; + + fn inspect_run_exec_loop( + &mut self, + evm: &mut Self::Evm, + first_frame_input: <::Frame as FrameTr>::FrameInit, + ) -> Result { + let res = evm.inspect_frame_init(first_frame_input)?; + + if let ItemOrResult::Result(frame_result) = res { + return Ok(frame_result); + } + + loop { + let call_or_result = evm.inspect_frame_run()?; + + let result = match call_or_result { + ItemOrResult::Item(mut init) => { + // Handle CREATE/CREATE2 frame initialization + if let Some(frame_result) = self.handle_create_frame(evm, &mut init)? { + return Ok(frame_result); + } + + match evm.inspect_frame_init(init)? { + ItemOrResult::Item(_) => continue, + ItemOrResult::Result(result) => result, + } + } + ItemOrResult::Result(result) => result, + }; + + // Handle CREATE2 override transformation if needed + let result = self.handle_create2_override(evm, result); + + if let Some(result) = evm.frame_return_result(result)? { + return Ok(result); + } + } + } +} diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs new file mode 100644 index 0000000000000..4dc6339706491 --- /dev/null +++ b/crates/evm/core/src/evm/mod.rs @@ -0,0 +1,301 @@ +use std::{ + fmt::Debug, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use crate::{ + FoundryBlock, FoundryContextExt, FoundryInspectorExt, FoundryTransaction, + FromAnyRpcTransaction, + backend::{DatabaseExt, JournaledState}, + constants::{CALLER, DEFAULT_CREATE2_DEPLOYER_CODEHASH, TEST_CONTRACT_ADDRESS}, + tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, +}; +use alloy_consensus::{ + SignableTransaction, Signed, constants::KECCAK_EMPTY, transaction::SignerRecoverable, +}; +use alloy_evm::{ + EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, eth::EthEvmContext, + precompiles::PrecompilesMap, +}; +use alloy_network::{Ethereum, Network}; +use alloy_op_evm::OpEvmFactory; +use alloy_primitives::{Address, Bytes, Signature, U256}; +use alloy_rlp::Decodable; +use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; +use foundry_config::FromEvmVersion; +use foundry_fork_db::{DatabaseError, ForkBlockEnv}; +use op_alloy_network::Optimism; +use op_revm::{ + DefaultOp, OpBuilder, OpContext, OpHaltReason, OpSpecId, OpTransaction, handler::OpHandler, + precompiles::OpPrecompiles, transaction::error::OpTransactionError, +}; +use revm::{ + Context, + context::{ + BlockEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContextTr, TxEnv, + result::{EVMError, ExecResultAndState, ExecutionResult, HaltReason, ResultAndState}, + }, + handler::{ + EthFrame, EvmTr, FrameResult, FrameTr, Handler, ItemOrResult, instructions::EthInstructions, + }, + inspector::{InspectorEvmTr, InspectorHandler}, + interpreter::{ + CallInput, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, + FrameInput, Gas, InstructionResult, InterpreterResult, SharedMemory, + interpreter::EthInterpreter, interpreter_action::FrameInit, return_ok, + }, + primitives::hardfork::SpecId, + state::Bytecode, +}; +use serde::{Deserialize, Serialize}; +use tempo_alloy::TempoNetwork; +use tempo_chainspec::hardfork::TempoHardfork; +use tempo_evm::evm::TempoEvmFactory; +use tempo_precompiles::storage::StorageCtx; +use tempo_revm::{ + TempoBlockEnv, TempoHaltReason, TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, + gas_params::tempo_gas_params, handler::TempoEvmHandler, +}; + +pub mod eth; +pub mod op; +pub mod tempo; + +pub use eth::*; +pub use op::*; +pub use tempo::*; + +/// Foundry's supertrait associating [Network] with [FoundryEvmFactory] +pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static { + type Network: Network< + TxEnvelope: Decodable + + SignerRecoverable + + From::UnsignedTx>> + + for<'d> Deserialize<'d> + + Serialize + + UIfmt, + UnsignedTx: SignableTransaction, + TransactionRequest: FoundryTransactionBuilder + + for<'d> Deserialize<'d> + + Serialize, + ReceiptResponse: FoundryReceiptResponse, + >; + type EvmFactory: FoundryEvmFactory::TxEnvelope>>; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct EthEvmNetwork; +impl FoundryEvmNetwork for EthEvmNetwork { + type Network = Ethereum; + type EvmFactory = EthEvmFactory; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct TempoEvmNetwork; +impl FoundryEvmNetwork for TempoEvmNetwork { + type Network = TempoNetwork; + type EvmFactory = TempoEvmFactory; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = OpEvmFactory; +} + +/// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. +pub type EvmFactoryFor = ::EvmFactory; +pub type FoundryContextFor<'db, FEN> = + as FoundryEvmFactory>::FoundryContext<'db>; +pub type TxEnvFor = as EvmFactory>::Tx; +pub type HaltReasonFor = as EvmFactory>::HaltReason; +pub type SpecFor = as EvmFactory>::Spec; +pub type BlockEnvFor = as EvmFactory>::BlockEnv; +pub type PrecompilesFor = as EvmFactory>::Precompiles; +pub type EvmEnvFor = EvmEnv, BlockEnvFor>; + +pub type NetworkFor = ::Network; +pub type TxEnvelopeFor = as Network>::TxEnvelope; +pub type TransactionRequestFor = as Network>::TransactionRequest; +pub type TransactionResponseFor = as Network>::TransactionResponse; +pub type BlockResponseFor = as Network>::BlockResponse; + +pub trait FoundryEvmFactory: + EvmFactory< + Spec: Into + FromEvmVersion + Default + Copy + Unpin + Send + 'static, + BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin, + Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync, + HaltReason: IntoInstructionResult, + Precompiles = PrecompilesMap, + > + Clone + + Debug + + Default + + 'static +{ + /// Foundry Context abstraction + type FoundryContext<'db>: FoundryContextExt< + Block = Self::BlockEnv, + Tx = Self::Tx, + Spec = Self::Spec, + Db: DatabaseExt, + > + where + Self: 'db; + + /// The Foundry-wrapped EVM type produced by this factory. + type FoundryEvm<'db, I: FoundryInspectorExt>>: Evm< + DB = &'db mut dyn DatabaseExt, + Tx = Self::Tx, + BlockEnv = Self::BlockEnv, + Spec = Self::Spec, + HaltReason = Self::HaltReason, + > + Deref> + + IntoNestedEvm + where + Self: 'db; + + /// Creates a Foundry-wrapped EVM with the given inspector. + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I>; + + /// Creates a Foundry-wrapped EVM with a dynamic inspector, returning a boxed [`NestedEvm`]. + /// + /// This helper exists because `&mut dyn FoundryInspectorExt` cannot satisfy + /// the generic `I: FoundryInspectorExt>` bound when the context + /// type is only known through an associated type. Each concrete factory implements this + /// directly, side-stepping the higher-kinded lifetime issue. + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db>; +} + +/// Trait for converting a Foundry EVM wrapper into its inner `NestedEvm` implementation. +/// +/// Both [`EthFoundryEvm`] and [`TempoFoundryEvm`] wrap an inner revm EVM that implements +/// [`NestedEvm`]. This trait provides a uniform way to unwrap them. +pub trait IntoNestedEvm { + /// The inner type that implements [`NestedEvm`]. + type Inner: NestedEvm; + + /// Consumes the wrapper, returning the inner revm EVM. + fn into_nested_evm(self) -> Self::Inner; +} + +/// Object-safe trait exposing the operations that cheatcode nested EVM closures need. +/// +/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.) +/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type. +pub trait NestedEvm { + /// The spec type. + type Spec; + /// The block environment type. + type Block; + /// The transaction environment type. + type Tx; + + /// Returns a mutable reference to the journal inner state (`JournaledState`). + fn journal_inner_mut(&mut self) -> &mut JournaledState; + + /// Runs a single execution frame (create or call) through the EVM handler loop. + fn run_execution(&mut self, frame: FrameInput) -> Result>; + + /// Executes a full transaction with the given tx env. + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError>; + + fn to_evm_env(&self) -> EvmEnv; +} + +/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations. +pub type NestedEvmClosure<'a, Spec, Block, Tx> = + &'a mut dyn FnMut( + &mut dyn NestedEvm, + ) -> Result<(), EVMError>; + +/// Clones the current context (env + journal), passes the database, cloned env, +/// and cloned journal inner to the callback. The callback builds whatever EVM it +/// needs, runs its operations, and returns `(result, modified_env, modified_journal)`. +/// Modified state is written back after the callback returns. +pub fn with_cloned_context( + ecx: &mut CTX, + f: impl FnOnce( + &mut CTX::Db, + EvmEnv, + JournaledState, + ) + -> Result<(EvmEnv, JournaledState), EVMError>, +) -> Result<(), EVMError> { + let evm_env = ecx.evm_clone(); + + let (db, journal_inner) = ecx.db_journal_inner_mut(); + let journal_inner_clone = journal_inner.clone(); + + let (sub_evm_env, sub_inner) = f(db, evm_env, journal_inner_clone)?; + + // Write back modified state. The db borrow was released when f returned. + ecx.set_journal_inner(sub_inner); + ecx.set_evm(sub_evm_env); + + Ok(()) +} + +/// Get the call inputs for the CREATE2 factory. +pub(crate) fn get_create2_factory_call_inputs( + salt: U256, + inputs: &CreateInputs, + deployer: Address, +) -> CallInputs { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat(); + CallInputs { + caller: inputs.caller(), + bytecode_address: deployer, + known_bytecode: None, + target_address: deployer, + scheme: CallScheme::Call, + value: CallValue::Transfer(inputs.value()), + input: CallInput::Bytes(calldata.into()), + gas_limit: inputs.gas_limit(), + is_static: false, + return_memory_offset: 0..0, + } +} + +/// Converts a network-specific halt reason into an [`InstructionResult`]. +pub trait IntoInstructionResult { + fn into_instruction_result(self) -> InstructionResult; +} + +impl IntoInstructionResult for HaltReason { + fn into_instruction_result(self) -> InstructionResult { + self.into() + } +} + +impl IntoInstructionResult for OpHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Base(eth) => eth.into(), + Self::FailedDeposit => InstructionResult::Stop, + } + } +} + +impl IntoInstructionResult for TempoHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Ethereum(eth) => eth.into(), + _ => InstructionResult::PrecompileError, + } + } +} diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs new file mode 100644 index 0000000000000..397744cab9d72 --- /dev/null +++ b/crates/evm/core/src/evm/op.rs @@ -0,0 +1,424 @@ +use super::*; + +pub type OpRevmEvm<'db, I> = op_revm::OpEvm< + OpContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>>, + PrecompilesMap, +>; + +/// Optimism counterpart of [`EthFoundryEvm`]. Wraps `op_revm::OpEvm` and routes execution +/// through [`OpFoundryHandler`] which composes [`OpHandler`] with CREATE2 factory redirect logic. +pub struct OpFoundryEvm< + 'db, + I: FoundryInspectorExt>>, +> { + pub inner: OpRevmEvm<'db, I>, +} + +impl FoundryEvmFactory for OpEvmFactory { + type FoundryContext<'db> = OpContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = OpFoundryEvm<'db, I>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let spec_id = *evm_env.spec_id(); + let mut inner = Context::op() + .with_db(db) + .with_block(evm_env.block_env) + .with_cfg(evm_env.cfg_env) + .build_op_with_inspector(inspector) + .with_precompiles(PrecompilesMap::from_static( + OpPrecompiles::new_with_spec(spec_id).precompiles(), + )); + inner.ctx().cfg.tx_chain_id_check = true; + + let mut evm = OpFoundryEvm { inner }; + let networks = Evm::inspector(&evm).get_networks(); + networks.inject_precompiles(evm.precompiles_mut()); + evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box> + 'db> + { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_nested_evm()) + } +} + +impl<'db, I: FoundryInspectorExt>>> Evm + for OpFoundryEvm<'db, I> +{ + type Precompiles = PrecompilesMap; + type Inspector = I; + type DB = &'db mut dyn DatabaseExt; + type Error = EVMError; + type HaltReason = OpHaltReason; + type Spec = OpSpecId; + type Tx = OpTransaction; + type BlockEnv = BlockEnv; + + fn block(&self) -> &BlockEnv { + &self.inner.ctx_ref().block + } + + fn chain_id(&self) -> u64 { + self.inner.ctx_ref().cfg.chain_id + } + + fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + let (ctx, _, precompiles, _, inspector) = self.inner.all_inspector(); + (&ctx.journaled_state.database, inspector, precompiles) + } + + fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + let (ctx, _, precompiles, _, inspector) = self.inner.all_mut_inspector(); + (&mut ctx.journaled_state.database, inspector, precompiles) + } + + fn set_inspector_enabled(&mut self, _enabled: bool) { + unimplemented!("OpFoundryEvm is always inspecting") + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + self.inner.ctx().set_tx(tx); + + let mut handler = OpFoundryHandler::::default(); + let result = handler.inspect_run(&mut self.inner)?; + + Ok(ResultAndState::new(result, self.inner.ctx_ref().journaled_state.inner.state.clone())) + } + + fn transact_system_call( + &mut self, + _caller: Address, + _contract: Address, + _data: Bytes, + ) -> Result>, Self::Error> { + unimplemented!() + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx; + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } +} + +impl<'db, I: FoundryInspectorExt>>> Deref + for OpFoundryEvm<'db, I> +{ + type Target = OpContext<&'db mut dyn DatabaseExt>; + + fn deref(&self) -> &Self::Target { + &self.inner.0.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> DerefMut + for OpFoundryEvm<'db, I> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.0.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> + IntoNestedEvm> for OpFoundryEvm<'db, I> +{ + type Inner = OpRevmEvm<'db, I>; + + fn into_nested_evm(self) -> Self::Inner { + self.inner + } +} + +/// Maps an OP [`EVMError`] to the common `EVMError` used by [`NestedEvm`]. +fn map_op_error(e: EVMError) -> EVMError { + match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::Transaction(t) => EVMError::Custom(format!("op transaction error: {t}")), + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for OpRevmEvm<'db, I> +{ + type Spec = OpSpecId; + type Block = BlockEnv; + type Tx = OpTransaction; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = OpFoundryHandler::::default(); + + let memory = + SharedMemory::new_with_buffer(self.ctx_ref().local.shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + let mut frame_result = + handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_op_error)?; + + handler.last_frame_result(self, &mut frame_result).map_err(map_op_error)?; + + Ok(frame_result) + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError> { + self.ctx().set_tx(tx); + + let mut handler = OpFoundryHandler::::default(); + let result = handler.inspect_run(self).map_err(map_op_error)?; + + let result = result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }); + + Ok(ResultAndState::new(result, self.ctx_ref().journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + EvmEnv::new(self.ctx_ref().cfg.clone(), self.ctx_ref().block.clone()) + } +} + +/// Optimism handler that composes [`OpHandler`] with CREATE2 factory redirect logic. +pub struct OpFoundryHandler< + 'db, + I: FoundryInspectorExt>>, +> { + inner: OpHandler< + OpRevmEvm<'db, I>, + EVMError, + EthFrame, + >, + create2_overrides: Vec<(usize, CallInputs)>, +} + +impl<'db, I: FoundryInspectorExt>>> Default + for OpFoundryHandler<'db, I> +{ + fn default() -> Self { + Self { inner: OpHandler::new(), create2_overrides: Vec::new() } + } +} + +impl<'db, I: FoundryInspectorExt>>> Handler + for OpFoundryHandler<'db, I> +{ + type Evm = OpRevmEvm<'db, I>; + type Error = EVMError; + type HaltReason = OpHaltReason; + + #[inline] + fn run( + &mut self, + evm: &mut Self::Evm, + ) -> Result, Self::Error> { + self.inner.run(evm) + } + + #[inline] + fn execution( + &mut self, + evm: &mut Self::Evm, + init_and_floor_gas: &revm::interpreter::InitialAndFloorGas, + ) -> Result { + self.inner.execution(evm, init_and_floor_gas) + } + + #[inline] + fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { + self.inner.validate_env(evm) + } + + #[inline] + fn validate_against_state_and_deduct_caller( + &self, + evm: &mut Self::Evm, + ) -> Result<(), Self::Error> { + self.inner.validate_against_state_and_deduct_caller(evm) + } + + #[inline] + fn reimburse_caller( + &self, + evm: &mut Self::Evm, + exec_result: &mut <::Frame as FrameTr>::FrameResult, + ) -> Result<(), Self::Error> { + self.inner.reimburse_caller(evm, exec_result) + } + + #[inline] + fn reward_beneficiary( + &self, + evm: &mut Self::Evm, + exec_result: &mut <::Frame as FrameTr>::FrameResult, + ) -> Result<(), Self::Error> { + self.inner.reward_beneficiary(evm, exec_result) + } + + #[inline] + fn validate_initial_tx_gas( + &self, + evm: &mut Self::Evm, + ) -> Result { + self.inner.validate_initial_tx_gas(evm) + } + + #[inline] + fn execution_result( + &mut self, + evm: &mut Self::Evm, + result: <::Frame as FrameTr>::FrameResult, + result_gas: revm::context::result::ResultGas, + ) -> Result, Self::Error> { + self.inner.execution_result(evm, result, result_gas) + } + + #[inline] + fn catch_error( + &self, + evm: &mut Self::Evm, + error: Self::Error, + ) -> Result, Self::Error> { + self.inner.catch_error(evm, error) + } +} + +impl<'db, I: FoundryInspectorExt>>> + InspectorHandler for OpFoundryHandler<'db, I> +{ + type IT = EthInterpreter; + + fn inspect_run_exec_loop( + &mut self, + evm: &mut Self::Evm, + first_frame_input: <::Frame as FrameTr>::FrameInit, + ) -> Result { + let res = evm.inspect_frame_init(first_frame_input)?; + + if let ItemOrResult::Result(frame_result) = res { + return Ok(frame_result); + } + + loop { + let call_or_result = evm.inspect_frame_run()?; + + let result = match call_or_result { + ItemOrResult::Item(mut init) => { + // Handle CREATE/CREATE2 frame initialization + if let FrameInput::Create(inputs) = &init.frame_input + && let CreateScheme::Create2 { salt } = inputs.scheme() + { + let (ctx, inspector) = evm.ctx_inspector(); + if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { + let gas_limit = inputs.gas_limit(); + let create2_deployer = evm.inspector().create2_deployer(); + let call_inputs = + get_create2_factory_call_inputs(salt, inputs, create2_deployer); + + self.create2_overrides + .push((evm.ctx_ref().journal().depth(), call_inputs.clone())); + + let code_hash = evm + .ctx() + .journal_mut() + .load_account(create2_deployer)? + .info + .code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from( + format!("missing CREATE2 deployer: {create2_deployer}") + .into_bytes(), + ), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + })); + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + })); + } + + init.frame_input = FrameInput::Call(Box::new(call_inputs)); + } + } + + match evm.inspect_frame_init(init)? { + ItemOrResult::Item(_) => continue, + ItemOrResult::Result(result) => result, + } + } + ItemOrResult::Result(result) => result, + }; + + // Handle CREATE2 override transformation if needed + let result = if self + .create2_overrides + .last() + .is_some_and(|(depth, _)| *depth == evm.ctx_ref().journal().depth()) + { + let (_, call_inputs) = self.create2_overrides.pop().unwrap(); + let FrameResult::Call(mut call) = result else { + unreachable!("create2 override should be a call frame"); + }; + let address = match call.instruction_result() { + return_ok!() => Address::try_from(call.output().as_ref()) + .map_err(|_| { + call.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + FrameResult::Create(CreateOutcome { result: call.result, address }) + } else { + result + }; + + if let Some(result) = evm.frame_return_result(result)? { + return Ok(result); + } + } + } +} diff --git a/crates/evm/core/src/evm/tempo.rs b/crates/evm/core/src/evm/tempo.rs new file mode 100644 index 0000000000000..4edc40a0973dd --- /dev/null +++ b/crates/evm/core/src/evm/tempo.rs @@ -0,0 +1,526 @@ +use super::*; + +// Will be removed when the next revm release includes bluealloy/revm#3518. +pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt, I>; + +/// Tempo counterpart of [`EthFoundryEvm`]. Wraps `tempo_revm::TempoEvm` and routes execution +/// through [`TempoFoundryHandler`] which composes [`TempoEvmHandler`] with CREATE2 factory +/// redirect logic. +/// +/// Uses [`TempoEvmFactory`] for construction to reuse factory setup logic, then unwraps to the +/// raw revm EVM via `into_inner()` since the handler operates at the revm level. +pub struct TempoFoundryEvm< + 'db, + I: FoundryInspectorExt>>, +> { + pub inner: TempoRevmEvm<'db, I>, +} + +/// Initialize Tempo precompiles and contracts for a newly created [`TempoFoundryEvm`]. +/// +/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode, +/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`]. +/// +/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode +/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles +/// on Tempo nodes (no real EVM bytecode on-chain). +pub(crate) fn initialize_tempo_evm< + 'db, + I: FoundryInspectorExt>>, +>( + evm: &mut TempoFoundryEvm<'db, I>, + is_forked: bool, +) { + let ctx = &mut evm.inner.inner.ctx; + StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || { + if is_forked { + // In fork mode, warm up precompile accounts to avoid repeated RPC fetches. + let mut sctx = StorageCtx; + let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef])); + for addr in TEMPO_PRECOMPILE_ADDRESSES.iter().chain(TEMPO_TIP20_TOKENS.iter()) { + sctx.set_code(*addr, sentinel.clone()) + .expect("failed to warm tempo precompile address"); + } + } else { + // In non-fork mode, run full genesis initialization. + initialize_tempo_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER) + .expect("tempo genesis initialization failed"); + } + }); +} + +impl FoundryEvmFactory for TempoEvmFactory { + type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = + TempoFoundryEvm<'db, I>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let is_forked = db.is_forked_mode(); + let spec = *evm_env.spec_id(); + let tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + let mut inner = tempo_evm.into_inner(); + inner.ctx.cfg.gas_params = tempo_gas_params(spec); + inner.ctx.cfg.tx_chain_id_check = true; + + let mut evm = TempoFoundryEvm { inner }; + let networks = Evm::inspector(&evm).get_networks(); + networks.inject_precompiles(evm.precompiles_mut()); + + initialize_tempo_evm(&mut evm, is_forked); + evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db> + { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_nested_evm()) + } +} + +impl<'db, I: FoundryInspectorExt>>> Evm + for TempoFoundryEvm<'db, I> +{ + type Precompiles = PrecompilesMap; + type Inspector = I; + type DB = &'db mut dyn DatabaseExt; + type Error = EVMError; + type HaltReason = TempoHaltReason; + type Spec = TempoHardfork; + type Tx = TempoTxEnv; + type BlockEnv = TempoBlockEnv; + + fn block(&self) -> &TempoBlockEnv { + &self.inner.block + } + + fn chain_id(&self) -> u64 { + self.inner.ctx.cfg.chain_id + } + + fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + let evm = &self.inner.inner; + (&evm.ctx.journaled_state.database, &evm.inspector, &evm.precompiles) + } + + fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + let evm = &mut self.inner.inner; + (&mut evm.ctx.journaled_state.database, &mut evm.inspector, &mut evm.precompiles) + } + + fn set_inspector_enabled(&mut self, _enabled: bool) { + unimplemented!("TempoFoundryEvm is always inspecting") + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + self.inner.set_tx(tx); + + let mut handler = TempoFoundryHandler::::default(); + let result = handler.inspect_run(&mut self.inner)?; + + Ok(ResultAndState::new(result, self.inner.inner.ctx.journaled_state.inner.state.clone())) + } + + fn transact_system_call( + &mut self, + _caller: Address, + _contract: Address, + _data: Bytes, + ) -> Result>, Self::Error> { + unimplemented!() + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + let revm_evm = self.inner.inner; + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = revm_evm.ctx; + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } +} + +impl<'db, I: FoundryInspectorExt>>> Deref + for TempoFoundryEvm<'db, I> +{ + type Target = TempoContext<&'db mut dyn DatabaseExt>; + + fn deref(&self) -> &Self::Target { + &self.inner.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> DerefMut + for TempoFoundryEvm<'db, I> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.ctx + } +} + +impl<'db, I: FoundryInspectorExt>>> + IntoNestedEvm for TempoFoundryEvm<'db, I> +{ + type Inner = TempoRevmEvm<'db, I>; + + fn into_nested_evm(self) -> Self::Inner { + self.inner + } +} + +/// Maps a Tempo [`EVMError`] to the common `EVMError` used by [`NestedEvm`]. +/// +/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains +/// an associated `Error` type, this mapping can be removed. +pub(crate) fn map_tempo_error( + e: EVMError, +) -> EVMError { + match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::Transaction(t) => match t { + TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth), + t => EVMError::Custom(format!("tempo transaction error: {t}")), + }, + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for TempoRevmEvm<'db, I> +{ + type Spec = TempoHardfork; + type Block = TempoBlockEnv; + type Tx = TempoTxEnv; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx_mut().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = TempoFoundryHandler::::default(); + + let memory = + SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + let mut frame_result = + handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?; + + handler.last_frame_result(self, &mut frame_result).map_err(map_tempo_error)?; + + Ok(frame_result) + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError> { + self.set_tx(tx); + + let mut handler = TempoFoundryHandler::::default(); + let result = handler.inspect_run(self).map_err(map_tempo_error)?; + + let result = result.map_haltreason(|h| match h { + TempoHaltReason::Ethereum(eth) => eth, + _ => HaltReason::PrecompileError, + }); + + Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + self.ctx_ref().evm_clone() + } +} + +/// Tempo counterpart of [`EthFoundryHandler`]. Wraps [`TempoEvmHandler`] and injects CREATE2 +/// factory redirect logic into the execution loop. Delegates all [`Handler`] methods to +/// [`TempoEvmHandler`] for proper Tempo validation, fee collection, AA dispatch, and gas +/// handling. +/// +/// Will be removed when the next revm release includes bluealloy/revm#3518. +pub struct TempoFoundryHandler< + 'db, + I: FoundryInspectorExt>>, +> { + inner: TempoEvmHandler<&'db mut dyn DatabaseExt, I>, + create2_overrides: Vec<(usize, CallInputs)>, +} + +impl<'db, I: FoundryInspectorExt>>> Default + for TempoFoundryHandler<'db, I> +{ + fn default() -> Self { + Self { inner: TempoEvmHandler::new(), create2_overrides: Vec::new() } + } +} + +impl<'db, I: FoundryInspectorExt>>> Handler + for TempoFoundryHandler<'db, I> +{ + type Evm = TempoRevmEvm<'db, I>; + type Error = EVMError; + type HaltReason = TempoHaltReason; + + #[inline] + fn run( + &mut self, + evm: &mut Self::Evm, + ) -> Result, Self::Error> { + self.inner.run(evm) + } + + #[inline] + fn execution( + &mut self, + evm: &mut Self::Evm, + init_and_floor_gas: &revm::interpreter::InitialAndFloorGas, + ) -> Result { + self.inner.execution(evm, init_and_floor_gas) + } + + #[inline] + fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { + self.inner.validate_env(evm) + } + + #[inline] + fn validate_against_state_and_deduct_caller( + &self, + evm: &mut Self::Evm, + ) -> Result<(), Self::Error> { + self.inner.validate_against_state_and_deduct_caller(evm) + } + + #[inline] + fn reimburse_caller( + &self, + evm: &mut Self::Evm, + exec_result: &mut <::Frame as FrameTr>::FrameResult, + ) -> Result<(), Self::Error> { + self.inner.reimburse_caller(evm, exec_result) + } + + #[inline] + fn reward_beneficiary( + &self, + evm: &mut Self::Evm, + exec_result: &mut <::Frame as FrameTr>::FrameResult, + ) -> Result<(), Self::Error> { + self.inner.reward_beneficiary(evm, exec_result) + } + + #[inline] + fn validate_initial_tx_gas( + &self, + evm: &mut Self::Evm, + ) -> Result { + self.inner.validate_initial_tx_gas(evm) + } + + #[inline] + fn execution_result( + &mut self, + evm: &mut Self::Evm, + result: <::Frame as FrameTr>::FrameResult, + result_gas: revm::context::result::ResultGas, + ) -> Result, Self::Error> { + self.inner.execution_result(evm, result, result_gas) + } + + #[inline] + fn catch_error( + &self, + evm: &mut Self::Evm, + error: Self::Error, + ) -> Result, Self::Error> { + self.inner.catch_error(evm, error) + } +} + +/// CREATE2 factory redirect execution loop for Tempo. +fn create2_exec_loop< + 'db, + I: FoundryInspectorExt>>, +>( + create2_overrides: &mut Vec<(usize, CallInputs)>, + evm: &mut TempoRevmEvm<'db, I>, + first_frame_input: FrameInit, +) -> Result> { + let res = evm.inspect_frame_init(first_frame_input)?; + + if let ItemOrResult::Result(frame_result) = res { + return Ok(frame_result); + } + + loop { + let call_or_result = evm.inspect_frame_run()?; + + let result = match call_or_result { + ItemOrResult::Item(mut init) => { + if let Some(frame_result) = handle_create2_frame(create2_overrides, evm, &mut init)? + { + return Ok(frame_result); + } + + match evm.inspect_frame_init(init)? { + ItemOrResult::Item(_) => continue, + ItemOrResult::Result(result) => result, + } + } + ItemOrResult::Result(result) => result, + }; + + let result = handle_create2_result(create2_overrides, evm, result); + + if let Some(result) = evm.frame_return_result(result)? { + return Ok(result); + } + } +} + +/// Handles CREATE2 frame initialization, potentially transforming it to use the CREATE2 factory. +fn handle_create2_frame< + 'db, + I: FoundryInspectorExt>>, +>( + create2_overrides: &mut Vec<(usize, CallInputs)>, + evm: &mut TempoRevmEvm<'db, I>, + init: &mut FrameInit, +) -> Result, EVMError> { + if let FrameInput::Create(inputs) = &init.frame_input + && let CreateScheme::Create2 { salt } = inputs.scheme() + { + let (ctx, inspector) = evm.ctx_inspector(); + + if inspector.should_use_create2_factory(ctx.journal().depth(), inputs) { + let gas_limit = inputs.gas_limit(); + let create2_deployer = evm.inspector().create2_deployer(); + let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); + + create2_overrides.push((evm.journal().depth(), call_inputs.clone())); + + let code_hash = evm.journal_mut().load_account(create2_deployer)?.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(Some(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from( + format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(), + ), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + }))); + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(Some(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: vec![], + }))); + } + + init.frame_input = FrameInput::Call(Box::new(call_inputs)); + } + } + Ok(None) +} + +/// Transforms CREATE2 factory call results back into CREATE outcomes. +fn handle_create2_result< + 'db, + I: FoundryInspectorExt>>, +>( + create2_overrides: &mut Vec<(usize, CallInputs)>, + evm: &mut TempoRevmEvm<'db, I>, + result: FrameResult, +) -> FrameResult { + if create2_overrides.last().is_some_and(|(depth, _)| *depth == evm.journal().depth()) { + let (_, call_inputs) = create2_overrides.pop().unwrap(); + let FrameResult::Call(mut call) = result else { + unreachable!("create2 override should be a call frame"); + }; + + let address = match call.instruction_result() { + return_ok!() => Address::try_from(call.output().as_ref()) + .map_err(|_| { + call.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + + FrameResult::Create(CreateOutcome { result: call.result, address }) + } else { + result + } +} + +impl<'db, I: FoundryInspectorExt>>> + InspectorHandler for TempoFoundryHandler<'db, I> +{ + type IT = EthInterpreter; + + /// Overrides `inspect_run` to load Tempo fee fields before delegating to the default + /// execution pipeline. This is necessary because the default `inspect_run` calls + /// `validate` → `validate_against_state_and_deduct_caller` directly, bypassing + /// `Handler::run` where `load_fee_fields` is normally invoked. + fn inspect_run( + &mut self, + evm: &mut Self::Evm, + ) -> Result, Self::Error> { + self.inner.load_fee_fields(evm)?; + + match self.inspect_run_without_catch_error(evm) { + Ok(output) => Ok(output), + Err(e) => self.catch_error(evm, e), + } + } + + /// Delegates to [`TempoEvmHandler::inspect_execution_with`], injecting the CREATE2 factory + /// redirect exec loop. AA multi-call dispatch and gas adjustments are handled by the inner + /// Tempo handler. + #[inline] + fn inspect_execution( + &mut self, + evm: &mut Self::Evm, + init_and_floor_gas: &revm::interpreter::InitialAndFloorGas, + ) -> Result { + let overrides = &mut self.create2_overrides; + self.inner.inspect_execution_with(evm, init_and_floor_gas, |_handler, evm, init| { + create2_exec_loop(overrides, evm, init) + }) + } + + fn inspect_run_exec_loop( + &mut self, + evm: &mut Self::Evm, + first_frame_input: <::Frame as FrameTr>::FrameInit, + ) -> Result { + create2_exec_loop(&mut self.create2_overrides, evm, first_frame_input) + } +} diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index da0f212072d22..6f539d356e5a9 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -56,15 +56,15 @@ impl ForkedDatabase { } } - pub fn database(&self) -> &CacheDB> { + pub const fn database(&self) -> &CacheDB> { &self.cache_db } - pub fn database_mut(&mut self) -> &mut CacheDB> { + pub const fn database_mut(&mut self) -> &mut CacheDB> { &mut self.cache_db } - pub fn state_snapshots(&self) -> &Arc>>> { + pub const fn state_snapshots(&self) -> &Arc>>> { &self.state_snapshots } @@ -92,7 +92,7 @@ impl ForkedDatabase { } /// Returns the database that holds the remote state - pub fn inner(&self) -> &BlockchainDb { + pub const fn inner(&self) -> &BlockchainDb { &self.db } diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 28df92da912e9..ee3ee216bcbab 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -4,11 +4,12 @@ use crate::{ fork::CreateFork, utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; +use alloy_chains::NamedChain; use alloy_consensus::BlockHeader; use alloy_network::{AnyNetwork, BlockResponse, Network}; use alloy_primitives::{Address, B256, BlockNumber, ChainId, U256}; use alloy_provider::{Provider, RootProvider}; -use alloy_rpc_types::BlockNumberOrTag; +use alloy_rpc_types::{BlockNumberOrTag, anvil::NodeInfo}; use eyre::WrapErr; use foundry_common::{ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, provider::ProviderBuilder}; use foundry_config::{Chain, Config, GasLimit}; @@ -142,7 +143,17 @@ impl EvmOpts { && let Ok(provider) = self.fork_provider_with_url::(fork_url) && let Ok(chain_id) = provider.get_chain_id().await { - self.networks = self.networks.with_chain_id(chain_id); + // If Anvil's chain, request anvil_nodeInfo to determine if the network is Tempo. + if chain_id == NamedChain::AnvilHardhat as u64 { + if let Ok(node_info) = + provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()).await + && node_info.network.is_some_and(|network| network == "tempo") + { + self.networks = NetworkConfigs::with_tempo(); + } + } else { + self.networks = self.networks.with_chain_id(chain_id); + } } } @@ -350,7 +361,7 @@ impl EvmOpts { /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled) /// - the assigned compute units, if `compute_units_per_second` is set /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise - fn get_compute_units_per_second(&self) -> u64 { + const fn get_compute_units_per_second(&self) -> u64 { if self.no_rpc_rate_limit { u64::MAX } else if let Some(cups) = self.compute_units_per_second { @@ -450,6 +461,68 @@ mod tests { use super::*; + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_default_anvil_selects_ethereum() { + let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await; + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(handle.http_endpoint()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + // Plain anvil (chain id 31337) without tempo flag -> Ethereum (no network flags set). + assert!(!evm_opts.networks.is_tempo()); + assert!(!evm_opts.networks.is_optimism()); + assert!(!evm_opts.networks.is_celo()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_tempo_anvil_via_node_info() { + let (_api, handle) = anvil::spawn(anvil::NodeConfig::test_tempo()).await; + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(handle.http_endpoint()); + // Networks not set -> should query anvil_nodeInfo to discover tempo. + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + assert!(evm_opts.networks.is_tempo(), "should detect tempo via anvil_nodeInfo"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_tempo_anvil_skips_rpc_when_already_set() { + // Use a URL that would fail if any RPC call were attempted (connection refused). + // This proves the early-return guard prevents all network requests. + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some("http://127.0.0.1:1".to_string()); + // Explicitly set tempo before calling infer (simulates --tempo CLI flag). + evm_opts.networks = NetworkConfigs::with_tempo(); + + evm_opts.infer_network_from_fork().await; + + // Should still be tempo, the early-return guard skips the RPC call. + assert!(evm_opts.networks.is_tempo()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn flaky_infer_network_tempo_moderato_rpc() { + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some("https://rpc.moderato.tempo.xyz".to_string()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + // Tempo Moderato has a known Tempo chain ID -> should be inferred via with_chain_id. + assert!(evm_opts.networks.is_tempo(), "should detect tempo from Moderato chain ID"); + } + #[tokio::test(flavor = "multi_thread")] async fn get_fork_pins_block_number_from_env() { let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint(); diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 0f6b75022f00a..76e1df1bd1778 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -16,7 +16,7 @@ pub use revm::state::EvmState as StateChangeset; /// Hints to the compiler that this is a cold path, i.e. unlikely to be taken. #[cold] #[inline(always)] -pub fn cold_path() { +pub const fn cold_path() { // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable. } @@ -49,7 +49,7 @@ pub fn apply_chain_and_block_specific_env_changes< block: &N::BlockResponse, configs: NetworkConfigs, ) { - use NamedChain::*; + use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet}; if let Ok(chain) = NamedChain::try_from(evm_env.cfg_env.chain_id) { let block_number = block.header().number(); diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 9f25d2e28baec..231da8a4b1d62 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -57,7 +57,7 @@ impl<'gcx> SourceVisitor<'gcx> { } } - fn checkpoint(&self) -> SourceVisitorCheckpoint { + const fn checkpoint(&self) -> SourceVisitorCheckpoint { SourceVisitorCheckpoint { items: self.items.len(), all_lines: self.all_lines.len(), @@ -181,7 +181,7 @@ impl<'gcx> SourceVisitor<'gcx> { first.line_index as u32 + 1..last.line_index as u32 + 2 } - fn next_branch_id(&mut self) -> u32 { + const fn next_branch_id(&mut self) -> u32 { let id = self.branch_id; self.branch_id = id + 1; id @@ -543,7 +543,7 @@ impl SourceAnalysis { } /// Returns all the mutable coverage items. - pub fn all_items_mut(&mut self) -> &mut Vec { + pub const fn all_items_mut(&mut self) -> &mut Vec { &mut self.all_items } diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index c1a9e0957d495..25ecebed244a8 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -225,7 +225,7 @@ impl HitMap { /// Returns the bytecode. #[inline] - pub fn bytecode(&self) -> &Bytes { + pub const fn bytecode(&self) -> &Bytes { &self.bytecode } @@ -480,7 +480,7 @@ impl fmt::Display for SourceLocation { impl SourceLocation { /// Returns the byte range as usize. - pub fn bytes(&self) -> Range { + pub const fn bytes(&self) -> Range { self.bytes.start as usize..self.bytes.end as usize } @@ -530,7 +530,7 @@ impl CoverageSummary { } /// Adds another coverage summary to this one. - pub fn merge(&mut self, other: &Self) { + pub const fn merge(&mut self, other: &Self) { let Self { line_count, line_hits, @@ -552,7 +552,7 @@ impl CoverageSummary { } /// Adds a coverage item to this summary. - pub fn add_item(&mut self, item: &CoverageItem) { + pub const fn add_item(&mut self, item: &CoverageItem) { match item.kind { CoverageItemKind::Line => { self.line_count += 1; diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 587d58bd2af74..a49716ca4fbc1 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -51,27 +51,27 @@ impl ExecutorBuilder { /// Sets the EVM spec to use. #[inline] - pub fn spec_id(mut self, spec: SpecFor) -> Self { + pub const fn spec_id(mut self, spec: SpecFor) -> Self { self.spec = Some(spec); self } /// Optionally sets the EVM spec. When `None`, the spec from `EvmEnv::cfg_env` is preserved. #[inline] - pub fn spec_id_opt(self, spec: Option>) -> Self { + pub const fn spec_id_opt(self, spec: Option>) -> Self { if let Some(spec) = spec { self.spec_id(spec) } else { self } } /// Sets the executor gas limit. #[inline] - pub fn gas_limit(mut self, gas_limit: u64) -> Self { + pub const fn gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } /// Sets the `legacy_assertions` flag. #[inline] - pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { + pub const fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { self.legacy_assertions = legacy_assertions; self } diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index 9e70c42e7aafb..850d3cdeaad90 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -230,7 +230,7 @@ impl fmt::Display for CorpusMetrics { impl CorpusMetrics { /// Records number of new edges or features explored during the campaign. - pub fn update_seen(&mut self, is_edge: bool) { + pub const fn update_seen(&mut self, is_edge: bool) { if is_edge { self.cumulative_edges_seen += 1; } else { @@ -239,7 +239,7 @@ impl CorpusMetrics { } /// Updates campaign favored items. - pub fn update_favored(&mut self, is_favored: bool, corpus_favored: bool) { + pub const fn update_favored(&mut self, is_favored: bool, corpus_favored: bool) { if is_favored && !corpus_favored { self.favored_items += 1; } else if !is_favored && corpus_favored { @@ -707,15 +707,15 @@ impl WorkerCorpus { self.evict_oldest_corpus()?; - let tx = if !self.in_memory_corpus.is_empty() { + let tx = if self.in_memory_corpus.is_empty() { + self.new_tx(test_runner)? + } else { let corpus = &self.in_memory_corpus [test_runner.rng().random_range(0..self.in_memory_corpus.len())]; self.current_mutated = Some(corpus.uuid); let mut tx = corpus.tx_seq.first().unwrap().clone(); self.abi_mutate(&mut tx, function, test_runner, fuzz_state)?; tx - } else { - self.new_tx(test_runner)? }; Ok(tx.call_details.calldata) diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 2b620eb65cd46..33152b73dda3c 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -196,10 +196,8 @@ impl FuzzedExecutor { config: FuzzConfig, persisted_failure: Option, ) -> Self { - let mut max_workers = Ord::max(1, config.runs / MIN_RUNS_PER_WORKER); - if config.runs == 0 { - max_workers = 0; - } + let max_workers = + if config.runs == 0 { 0 } else { Ord::max(1, config.runs / MIN_RUNS_PER_WORKER) }; let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } @@ -437,7 +435,7 @@ impl FuzzedExecutor { self.config.corpus.clone(), strategy.boxed(), // Master worker replays the persisted corpus using the executor - if worker_id == 0 { Some(&self.executor_f) } else { None }, + (worker_id == 0).then_some(&self.executor_f), Some(func), None, // fuzzed_contracts for invariant tests )?; @@ -598,7 +596,15 @@ impl FuzzedExecutor { }) => { inc_runs(); - let reason = rd.maybe_decode(&outcome.1.result, status); + // Only classify magic skip payloads when the revert originates from the + // cheatcode address. + let reason = if outcome.1.reverter == Some(CHEATCODE_ADDRESS) { + SkipReason::decode(&outcome.1.result) + .map(|reason| reason.to_string()) + .or_else(|| rd.maybe_decode(&outcome.1.result, status)) + } else { + rd.maybe_decode(&outcome.1.result, status) + }; worker.logs.extend(outcome.1.logs.clone()); worker.counterexample = outcome; worker.failure = Some(TestCaseError::fail(reason.unwrap_or_default())); @@ -648,7 +654,7 @@ impl FuzzedExecutor { } /// Determines the number of runs per worker. - fn runs_per_worker(&self, worker_id: usize) -> u32 { + const fn runs_per_worker(&self, worker_id: usize) -> u32 { let worker_id = worker_id as u32; let total_runs = self.config.runs; let n = self.num_workers as u32; diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index b3b08fd0ebcca..ebe3c29a005ca 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -41,7 +41,7 @@ use serde_json::json; use std::{ collections::{HashMap as Map, btree_map::Entry}, sync::Arc, - time::{Instant, SystemTime, UNIX_EPOCH}, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; mod error; @@ -122,6 +122,68 @@ pub struct InvariantMetrics { pub discards: usize, } +/// Campaign-level throughput metrics for invariant progress reporting. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +struct InvariantThroughputMetrics { + total_txs: u64, + total_gas: u64, +} + +impl InvariantThroughputMetrics { + const fn record_call(&mut self, gas_used: u64) { + self.total_txs += 1; + self.total_gas += gas_used; + } + + fn tx_per_sec(self, elapsed: Duration) -> f64 { + rate_per_sec(self.total_txs as f64, elapsed) + } + + fn gas_per_sec(self, elapsed: Duration) -> f64 { + rate_per_sec(self.total_gas as f64, elapsed) + } +} + +/// Converts a cumulative campaign total into an average per-second rate. +/// +/// Returns `0.0` during the initial zero-elapsed startup window to avoid +/// dividing by zero while progress reporting is warming up. +fn rate_per_sec(total: f64, elapsed: Duration) -> f64 { + let elapsed_secs = elapsed.as_secs_f64(); + if elapsed_secs > 0.0 { total / elapsed_secs } else { 0.0 } +} + +/// Builds the machine-readable invariant progress payload emitted during a +/// campaign. +/// +/// This keeps the existing corpus progress metrics together with cumulative and +/// derived throughput fields so downstream benchmark tooling can consume a +/// single JSON event shape. +fn build_invariant_progress_json( + timestamp_secs: u64, + invariant_name: &str, + corpus_metrics: &M, + optimization_best: Option, + throughput: InvariantThroughputMetrics, + elapsed: Duration, +) -> serde_json::Value { + let mut payload = json!({ + "timestamp": timestamp_secs, + "invariant": invariant_name, + "metrics": corpus_metrics, + "total_txs": throughput.total_txs, + "total_gas": throughput.total_gas, + "tx_per_sec": throughput.tx_per_sec(elapsed), + "gas_per_sec": throughput.gas_per_sec(elapsed), + }); + + if let Some(best) = optimization_best { + payload["optimization_best"] = json!(best.to_string()); + } + + payload +} + /// Contains data collected during invariant test runs. struct InvariantTestData { // Consumed gas and calldata of every successful fuzz call. @@ -190,12 +252,12 @@ impl InvariantTest { } /// Returns number of invariant test reverts. - fn reverts(&self) -> usize { + const fn reverts(&self) -> usize { self.test_data.failures.reverts } /// Whether invariant test has errors or not. - fn has_errors(&self) -> bool { + const fn has_errors(&self) -> bool { self.test_data.failures.error.is_some() } @@ -370,6 +432,8 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { let mut runs = 0; let timer = FuzzTestTimer::new(self.config.timeout); let mut last_metrics_report = Instant::now(); + let campaign_start = Instant::now(); + let mut throughput = InvariantThroughputMetrics::default(); let continue_campaign = |runs: u32| { if early_exit.should_stop() { return false; @@ -478,6 +542,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { current_run .fuzz_runs .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend }); + throughput.record_call(call_result.gas_used); // Determine if test can continue or should exit. // Check invariants based on check_interval to improve deep run performance. @@ -527,9 +592,12 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { invariant_test.test_data.failures.error = Some(InvariantFuzzError::Revert(case_data)); result::RichInvariantResults::new(false, None) - } else if !invariant_contract.is_optimization() { - // In optimization mode, keep reverted calls to preserve - // warp/roll values for correct replay during shrinking. + } else if !invariant_contract.is_optimization() + && !self.config.has_delay() + { + // Delay-enabled campaigns keep reverted calls so shrinking can + // preserve their warp/roll contribution when building the final + // counterexample. current_run.inputs.pop(); result::RichInvariantResults::new(true, None) } else { @@ -589,21 +657,33 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { if let Some(progress) = progress { // If running with progress then increment completed runs. progress.inc(1); - // Display metrics in progress bar. - if edge_coverage_enabled { - progress.set_message(format!("{}", &corpus_manager.metrics)); + // Display current best value and/or corpus metrics in progress bar. + let best = invariant_test.test_data.optimization_best_value; + if edge_coverage_enabled || best.is_some() { + let mut msg = String::new(); + if let Some(best) = best { + msg.push_str(&format!("best: {best}")); + } + if edge_coverage_enabled { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!("{}", &corpus_manager.metrics)); + } + progress.set_message(msg); } } else if edge_coverage_enabled && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT { - // Display metrics inline if corpus dir set. - let metrics = json!({ - "timestamp": SystemTime::now() - .duration_since(UNIX_EPOCH)? - .as_secs(), - "invariant": invariant_contract.invariant_function.name, - "metrics": &corpus_manager.metrics, - }); + // Display corpus metrics inline as JSON. + let metrics = build_invariant_progress_json( + SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(), + &invariant_contract.invariant_function.name, + &corpus_manager.metrics, + invariant_test.test_data.optimization_best_value, + throughput, + campaign_start.elapsed(), + ); let _ = sh_println!("{}", serde_json::to_string(&metrics)?); last_metrics_report = Instant::now(); } @@ -1085,18 +1165,16 @@ fn collect_data( run_depth: u32, ) { // Verify it has no code. - let mut has_code = false; - if let Some(Some(code)) = + let has_code = if let Some(Some(code)) = state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref()) { - has_code = !code.is_empty(); - } + !code.is_empty() + } else { + false + }; // We keep the nonce changes to apply later. - let mut sender_changeset = None; - if !has_code { - sender_changeset = state_changeset.remove(&tx.sender); - } + let sender_changeset = if has_code { None } else { state_changeset.remove(&tx.sender) }; // Collect values from fuzzed call result and add them to fuzz dictionary. invariant_test.fuzz_state.collect_values_from_call( @@ -1174,3 +1252,53 @@ pub(crate) fn execute_tx( .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), U256::ZERO) .map_err(|e| eyre!(format!("Could not make raw evm call: {e}"))) } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn invariant_progress_json_includes_throughput_fields() { + let mut throughput = InvariantThroughputMetrics::default(); + throughput.record_call(20); + throughput.record_call(30); + + let payload = build_invariant_progress_json( + 123, + "invariant_balance", + &json!({ "corpus_count": 7 }), + Some(I256::try_from(42).unwrap()), + throughput, + Duration::from_secs(10), + ); + + assert_eq!(payload["timestamp"], json!(123)); + assert_eq!(payload["invariant"], json!("invariant_balance")); + assert_eq!(payload["metrics"]["corpus_count"], json!(7)); + assert_eq!(payload["total_txs"], json!(2)); + assert_eq!(payload["total_gas"], json!(50)); + assert!((payload["tx_per_sec"].as_f64().unwrap() - 0.2).abs() < 1e-12); + assert!((payload["gas_per_sec"].as_f64().unwrap() - 5.0).abs() < 1e-12); + assert_eq!(payload["optimization_best"], json!("42")); + } + + #[test] + fn invariant_progress_json_zero_elapsed_reports_zero_rates() { + let mut throughput = InvariantThroughputMetrics::default(); + throughput.record_call(21_000); + + let payload = build_invariant_progress_json( + 456, + "invariant_zero_elapsed", + &json!({ "corpus_count": 1 }), + None, + throughput, + Duration::ZERO, + ); + + assert_eq!(payload["tx_per_sec"], json!(0.0)); + assert_eq!(payload["gas_per_sec"], json!(0.0)); + assert!(payload.get("optimization_best").is_none()); + } +} diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs index 75e4bd2cc1130..8c38dc854a7c8 100644 --- a/crates/evm/evm/src/executors/invariant/result.rs +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -51,7 +51,7 @@ pub(crate) struct RichInvariantResults { } impl RichInvariantResults { - pub(crate) fn new(can_continue: bool, call_result: Option>) -> Self { + pub(crate) const fn new(can_continue: bool, call_result: Option>) -> Self { Self { can_continue, call_result } } } @@ -183,10 +183,10 @@ pub(crate) fn can_continue( invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data)); return Ok(RichInvariantResults::new(false, None)); - } else if call_result.reverted && !is_optimization { - // If we don't fail test on revert then remove last reverted call from inputs. - // In optimization mode, we keep reverted calls to preserve warp/roll values - // for correct replay during shrinking. + } else if call_result.reverted && !is_optimization && !invariant_config.has_delay() { + // If we don't fail test on revert then remove the reverted call from inputs. + // Delay-enabled campaigns keep reverted calls so shrinking can preserve their + // warp/roll contribution when building the final counterexample. invariant_run.inputs.pop(); } } diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs index 2badd12300432..9dc21e24eb7fc 100644 --- a/crates/evm/evm/src/executors/invariant/shrink.rs +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -35,7 +35,7 @@ impl CallSequenceShrinker { } /// Advance to the next call index, wrapping around to 0 at the end. - fn next_index(&self, call_idx: usize) -> usize { + const fn next_index(&self, call_idx: usize) -> usize { if call_idx + 1 == self.call_sequence_len { 0 } else { call_idx + 1 } } } @@ -87,6 +87,37 @@ fn apply_warp_roll_to_env( } } +/// Builds the final shrunk sequence from the shrinker state. +/// +/// When `accumulate_warp_roll` is enabled, warp/roll from removed calls is folded into the next +/// kept call so the final sequence remains reproducible. +fn build_shrunk_sequence( + calls: &[BasicTxDetails], + shrinker: &CallSequenceShrinker, + accumulate_warp_roll: bool, +) -> Vec { + if !accumulate_warp_roll { + return shrinker.current().map(|idx| calls[idx].clone()).collect(); + } + + let mut result = Vec::new(); + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + + for (idx, call) in calls.iter().enumerate() { + accumulated_warp += call.warp.unwrap_or(U256::ZERO); + accumulated_roll += call.roll.unwrap_or(U256::ZERO); + + if shrinker.included_calls.test(idx) { + result.push(apply_warp_roll(call, accumulated_warp, accumulated_roll)); + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + } + + result +} + pub(crate) fn shrink_sequence( config: &InvariantConfig, invariant_contract: &InvariantContract<'_>, @@ -108,6 +139,7 @@ pub(crate) fn shrink_sequence( return Ok(vec![]); } + let accumulate_warp_roll = config.has_delay(); let mut call_idx = 0; let mut shrinker = CallSequenceShrinker::new(calls.len()); @@ -125,6 +157,7 @@ pub(crate) fn shrink_sequence( target_address, calldata.clone(), CheckSequenceOptions { + accumulate_warp_roll, fail_on_revert: config.fail_on_revert, call_after_invariant: invariant_contract.call_after_invariant, rd: None, @@ -144,7 +177,7 @@ pub(crate) fn shrink_sequence( call_idx = shrinker.next_index(call_idx); } - Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) + Ok(build_shrunk_sequence(calls, &shrinker, accumulate_warp_roll)) } /// Checks if the given call sequence breaks the invariant. @@ -153,7 +186,25 @@ pub(crate) fn shrink_sequence( /// persisted failures. /// Returns the result of invariant check (and afterInvariant call if needed) and if sequence was /// entirely applied. +/// +/// When `options.accumulate_warp_roll` is enabled, warp/roll from removed calls is folded into the +/// next kept call so the candidate sequence stays representable as a concrete counterexample. pub fn check_sequence( + executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + options: CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { + if options.accumulate_warp_roll { + check_sequence_with_accumulation(executor, calls, sequence, test_address, calldata, options) + } else { + check_sequence_simple(executor, calls, sequence, test_address, calldata, options) + } +} + +fn check_sequence_simple( mut executor: Executor, calls: &[BasicTxDetails], sequence: Vec, @@ -179,9 +230,59 @@ pub fn check_sequence( } } - // Check the invariant for call sequence. + finish_sequence_check(&executor, test_address, calldata, &options) +} + +fn check_sequence_with_accumulation( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + options: CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + let mut seq_iter = sequence.iter().peekable(); + + for (idx, tx) in calls.iter().enumerate() { + accumulated_warp += tx.warp.unwrap_or(U256::ZERO); + accumulated_roll += tx.roll.unwrap_or(U256::ZERO); + + if seq_iter.peek() != Some(&&idx) { + continue; + } + + seq_iter.next(); + + let tx_with_accumulated = apply_warp_roll(tx, accumulated_warp, accumulated_roll); + let mut call_result = execute_tx(&mut executor, &tx_with_accumulated)?; + + if call_result.reverted { + if options.fail_on_revert && call_result.result.as_ref() != MAGIC_ASSUME { + return Ok((false, false, call_failure_reason(call_result, options.rd))); + } + } else { + executor.commit(&mut call_result); + } + + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + + // Unlike optimization mode we intentionally do not apply trailing warp/roll before the + // invariant call: those delays would not be representable in the final shrunk sequence. + finish_sequence_check(&executor, test_address, calldata, &options) +} + +fn finish_sequence_check( + executor: &Executor, + test_address: Address, + calldata: Bytes, + options: &CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { let (invariant_result, mut success) = - call_invariant_function(&executor, test_address, calldata)?; + call_invariant_function(executor, test_address, calldata)?; if !success { return Ok((false, true, call_failure_reason(invariant_result, options.rd))); } @@ -190,7 +291,7 @@ pub fn check_sequence( // declared. if success && options.call_after_invariant { let (after_invariant_result, after_invariant_success) = - call_after_invariant_function(&executor, test_address)?; + call_after_invariant_function(executor, test_address)?; success = after_invariant_success; if !success { return Ok((false, true, call_failure_reason(after_invariant_result, options.rd))); @@ -201,6 +302,7 @@ pub fn check_sequence( } pub struct CheckSequenceOptions<'a> { + pub accumulate_warp_roll: bool, pub fail_on_revert: bool, pub call_after_invariant: bool, pub rd: Option<&'a RevertDecoder>, @@ -279,23 +381,7 @@ pub(crate) fn shrink_sequence_value( call_idx = shrinker.next_index(call_idx); } - // Build the final shrunk sequence, accumulating warp/roll from removed calls. - let mut result = Vec::new(); - let mut accumulated_warp = U256::ZERO; - let mut accumulated_roll = U256::ZERO; - - for (idx, call) in calls.iter().enumerate() { - accumulated_warp += call.warp.unwrap_or(U256::ZERO); - accumulated_roll += call.roll.unwrap_or(U256::ZERO); - - if shrinker.included_calls.test(idx) { - result.push(apply_warp_roll(call, accumulated_warp, accumulated_roll)); - accumulated_warp = U256::ZERO; - accumulated_roll = U256::ZERO; - } - } - - Ok(result) + Ok(build_shrunk_sequence(calls, &shrinker, true)) } /// Executes a call sequence and returns the optimization value (int256) from the invariant @@ -347,3 +433,48 @@ pub fn check_sequence_value( Ok(None) } + +#[cfg(test)] +mod tests { + use super::{CallSequenceShrinker, build_shrunk_sequence}; + use alloy_primitives::{Address, Bytes, U256}; + use foundry_evm_fuzz::{BasicTxDetails, CallDetails}; + use proptest::bits::BitSetLike; + + fn tx(warp: Option, roll: Option) -> BasicTxDetails { + BasicTxDetails { + warp: warp.map(U256::from), + roll: roll.map(U256::from), + sender: Address::ZERO, + call_details: CallDetails { target: Address::ZERO, calldata: Bytes::new() }, + } + } + + #[test] + fn build_shrunk_sequence_accumulates_removed_delay_into_next_kept_call() { + let calls = vec![tx(Some(3), Some(5)), tx(Some(7), Some(11)), tx(Some(13), Some(17))]; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + shrinker.included_calls.clear(0); + + let shrunk = build_shrunk_sequence(&calls, &shrinker, true); + + assert_eq!(shrunk.len(), 2); + assert_eq!(shrunk[0].warp, Some(U256::from(10))); + assert_eq!(shrunk[0].roll, Some(U256::from(16))); + assert_eq!(shrunk[1].warp, Some(U256::from(13))); + assert_eq!(shrunk[1].roll, Some(U256::from(17))); + } + + #[test] + fn build_shrunk_sequence_does_not_move_trailing_delay_backward() { + let calls = vec![tx(Some(3), Some(5)), tx(Some(7), Some(11))]; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + shrinker.included_calls.clear(1); + + let shrunk = build_shrunk_sequence(&calls, &shrinker, true); + + assert_eq!(shrunk.len(), 1); + assert_eq!(shrunk[0].warp, Some(U256::from(3))); + assert_eq!(shrunk[0].roll, Some(U256::from(5))); + } +} diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index eaf98d3dd8fdc..8f377e899bfd4 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -171,42 +171,42 @@ impl Executor { } /// Returns a reference to the EVM environment (block and cfg). - pub fn evm_env(&self) -> &EvmEnvFor { + pub const fn evm_env(&self) -> &EvmEnvFor { &self.evm_env } /// Returns a mutable reference to the EVM environment (block and cfg). - pub fn evm_env_mut(&mut self) -> &mut EvmEnvFor { + pub const fn evm_env_mut(&mut self) -> &mut EvmEnvFor { &mut self.evm_env } /// Returns a reference to the transaction environment. - pub fn tx_env(&self) -> &TxEnvFor { + pub const fn tx_env(&self) -> &TxEnvFor { &self.tx_env } /// Returns a mutable reference to the transaction environment. - pub fn tx_env_mut(&mut self) -> &mut TxEnvFor { + pub const fn tx_env_mut(&mut self) -> &mut TxEnvFor { &mut self.tx_env } /// Returns a reference to the EVM inspector. - pub fn inspector(&self) -> &InspectorStack { + pub const fn inspector(&self) -> &InspectorStack { &self.inspector } /// Returns a mutable reference to the EVM inspector. - pub fn inspector_mut(&mut self) -> &mut InspectorStack { + pub const fn inspector_mut(&mut self) -> &mut InspectorStack { &mut self.inspector } /// Returns the EVM spec. - pub fn spec_id(&self) -> SpecFor { + pub const fn spec_id(&self) -> SpecFor { self.evm_env.cfg_env.spec } /// Sets the EVM spec. - pub fn set_spec_id(&mut self, spec_id: SpecFor) { + pub const fn set_spec_id(&mut self, spec_id: SpecFor) { self.evm_env.cfg_env.spec = spec_id; } @@ -214,24 +214,24 @@ impl Executor { /// /// This is different from the gas limit imposed by the passed in environment, as those limits /// are used by the EVM for certain opcodes like `gaslimit`. - pub fn gas_limit(&self) -> u64 { + pub const fn gas_limit(&self) -> u64 { self.gas_limit } /// Sets the gas limit for calls and deployments. - pub fn set_gas_limit(&mut self, gas_limit: u64) { + pub const fn set_gas_limit(&mut self, gas_limit: u64) { self.gas_limit = gas_limit; } /// Returns whether `failed()` should be called on the test contract to determine if the test /// failed. - pub fn legacy_assertions(&self) -> bool { + pub const fn legacy_assertions(&self) -> bool { self.legacy_assertions } /// Sets whether `failed()` should be called on the test contract to determine if the test /// failed. - pub fn set_legacy_assertions(&mut self, legacy_assertions: bool) { + pub const fn set_legacy_assertions(&mut self, legacy_assertions: bool) { self.legacy_assertions = legacy_assertions; } @@ -957,7 +957,9 @@ impl RawCallResult { /// Converts the result of the call into an `EvmError`. pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { - if let Some(reason) = SkipReason::decode(&self.result) { + if self.reverter == Some(CHEATCODE_ADDRESS) + && let Some(reason) = SkipReason::decode(&self.result) + { return EvmError::Skip(reason); } let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason); @@ -965,7 +967,7 @@ impl RawCallResult { } /// Converts the result of the call into an `ExecutionErr`. - pub fn into_execution_error(self, reason: String) -> ExecutionErr { + pub const fn into_execution_error(self, reason: String) -> ExecutionErr { ExecutionErr { raw: self, reason } } @@ -1150,7 +1152,7 @@ impl FuzzTestTimer { } /// Whether the fuzz test timer is enabled. - pub fn is_enabled(&self) -> bool { + pub const fn is_enabled(&self) -> bool { self.inner.is_some() } @@ -1192,3 +1194,45 @@ impl EarlyExit { self.inner.load(Ordering::Relaxed) } } + +#[cfg(test)] +mod tests { + use super::*; + use foundry_evm_core::constants::MAGIC_SKIP; + + #[test] + fn cheatcode_skip_payload_is_classified_as_skip() { + let raw = RawCallResult:: { + result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"), + reverter: Some(CHEATCODE_ADDRESS), + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Skip(_))); + } + + #[test] + fn forged_skip_payload_from_non_cheatcode_is_execution_error() { + let raw = RawCallResult:: { + result: Bytes::from_static(MAGIC_SKIP), + reverter: Some(CALLER), + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Execution(_))); + } + + #[test] + fn skip_payload_without_reverter_is_execution_error() { + let raw = RawCallResult:: { + result: Bytes::from_static(MAGIC_SKIP), + reverter: None, + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Execution(_))); + } +} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 54a77399fa2c9..ac506855355dc 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,28 +1,26 @@ -use crate::executors::{Executor, ExecutorBuilder}; -use alloy_primitives::{Address, U256, map::HashMap}; +use crate::{ + Env, + executors::{Executor, ExecutorBuilder}, +}; +use alloy_primitives::{Address, FixedBytes, U256, address, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; use foundry_compilers::artifacts::EvmVersion; -use foundry_config::{Chain, Config, evm_spec_id}; -use foundry_evm_core::{ - backend::Backend, - evm::{BlockEnvFor, EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, - fork::CreateFork, - opts::EvmOpts, -}; +use foundry_config::{Chain, Config, utils::evm_spec_id}; +use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_networks::NetworkConfigs; use foundry_evm_traces::TraceMode; -use revm::{context::Transaction, state::Bytecode}; +use revm::{primitives::hardfork::SpecId, state::Bytecode}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled -pub struct TracingExecutor { - executor: Executor, +pub struct TracingExecutor { + executor: Executor, } -impl TracingExecutor { +impl TracingExecutor { pub fn new( - env: (EvmEnvFor, TxEnvFor), + env: Env, fork: CreateFork, version: Option, trace_mode: TraceMode, @@ -33,12 +31,12 @@ impl TracingExecutor { let db = Backend::spawn(Some(fork))?; // configures a bare version of the evm executor: no cheatcode and log_collector inspector // is enabled, tracing will be enabled only for the targeted transaction - let mut executor = ExecutorBuilder::default() + let mut executor = ExecutorBuilder::new() .inspectors(|stack| { stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) }) - .spec_id_opt(version.map(evm_spec_id::>)) - .build(env.0, env.1, db); + .spec_id(evm_spec_id(version.unwrap_or_default())) + .build(env, db); // Apply the state overrides. if let Some(state_overrides) = state_overrides { @@ -73,7 +71,7 @@ impl TracingExecutor { } /// Returns the spec id of the executor - pub fn spec_id(&self) -> SpecFor { + pub fn spec_id(&self) -> SpecId { self.executor.spec_id() } @@ -81,31 +79,56 @@ impl TracingExecutor { pub async fn get_fork_material( config: &mut Config, mut evm_opts: EvmOpts, - ) -> eyre::Result<(EvmEnvFor, TxEnvFor, CreateFork, Chain, NetworkConfigs)> { + ) -> eyre::Result<(Env, CreateFork, Chain, NetworkConfigs)> { evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); evm_opts.fork_block_number = config.fork_block_number; - let (evm_env, tx_env, fork_block) = - evm_opts.env::, BlockEnvFor, TxEnvFor>().await?; + let env = evm_opts.evm_env().await?; - let fork = evm_opts.get_fork(config, evm_env.cfg_env.chain_id, fork_block).unwrap(); - let networks = evm_opts.networks.with_chain_id(evm_env.cfg_env.chain_id); + let fork = evm_opts.get_fork(config, env.clone()).unwrap(); + let networks = evm_opts.networks.with_chain_id(env.evm_env.cfg_env.chain_id); config.labels.extend(networks.precompiles_label()); - let chain = tx_env.chain_id().unwrap().into(); - Ok((evm_env, tx_env, fork, chain, networks)) + let chain = env.tx.chain_id.unwrap().into(); + Ok((env, fork, chain, networks)) + } + + /// Processes the beacon block root by storing it in the appropriate storage slots. + pub fn process_beacon_block_root( + &mut self, + block_timestamp: u64, + beacon_root: FixedBytes<32>, + ) -> eyre::Result<()> { + const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); + const HISTORY_BUFFER_LENGTH: u64 = 8192; + + let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; + let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; + + let timestamp_slot = U256::from(timestamp_index); + let root_slot = U256::from(root_index); + + self.set_storage_slot(BEACON_ROOTS_ADDRESS, timestamp_slot, U256::from(block_timestamp))?; + + self.set_storage_slot( + BEACON_ROOTS_ADDRESS, + root_slot, + U256::from_be_bytes(beacon_root.into()), + )?; + + Ok(()) } } -impl Deref for TracingExecutor { - type Target = Executor; +impl Deref for TracingExecutor { + type Target = Executor; fn deref(&self) -> &Self::Target { &self.executor } } -impl DerefMut for TracingExecutor { +impl DerefMut for TracingExecutor { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.executor } diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs index 1e776d4bfcdfa..1388f3c18140f 100644 --- a/crates/evm/evm/src/inspectors/chisel_state.rs +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -16,7 +16,7 @@ pub struct ChiselState { impl ChiselState { /// Create a new Chisel state inspector. #[inline] - pub fn new(final_pc: usize) -> Self { + pub const fn new(final_pc: usize) -> Self { Self { final_pc, state: None } } } diff --git a/crates/evm/evm/src/inspectors/revert_diagnostic.rs b/crates/evm/evm/src/inspectors/revert_diagnostic.rs index fae1f3d22b6a1..e26a4ee9aaa9a 100644 --- a/crates/evm/evm/src/inspectors/revert_diagnostic.rs +++ b/crates/evm/evm/src/inspectors/revert_diagnostic.rs @@ -15,7 +15,7 @@ use std::fmt; const IGNORE: [Address; 2] = [HARDHAT_CONSOLE_ADDRESS, CHEATCODE_ADDRESS]; /// Checks if the call scheme corresponds to any sort of delegate call -pub fn is_delegatecall(scheme: CallScheme) -> bool { +pub const fn is_delegatecall(scheme: CallScheme) -> bool { matches!(scheme, CallScheme::DelegateCall | CallScheme::CallCode) } @@ -68,12 +68,12 @@ pub struct RevertDiagnostic { impl RevertDiagnostic { /// Returns the effective target address whose code would be executed. /// For delegate calls, this is the `bytecode_address`. Otherwise, it's the `target_address`. - fn code_target_address(&self, inputs: &mut CallInputs) -> Address { + const fn code_target_address(&self, inputs: &mut CallInputs) -> Address { if is_delegatecall(inputs.scheme) { inputs.bytecode_address } else { inputs.target_address } } /// Derives the revert reason based on the cached data. Should only be called after a revert. - fn reason(&self) -> Option { + const fn reason(&self) -> Option { if let Some((addr, scheme, _)) = self.non_contract_call { let reason = if is_delegatecall(scheme) { DetailedRevertReason::DelegateCallToNonContract(addr) diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 374dfca8febec..dcbb5a109ae9f 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -129,7 +129,7 @@ impl InspectorStackBuilder { /// Set the gas price. #[inline] - pub fn gas_price(mut self, gas_price: u128) -> Self { + pub const fn gas_price(mut self, gas_price: u128) -> Self { self.gas_price = Some(gas_price); self } @@ -157,28 +157,28 @@ impl InspectorStackBuilder { /// Set the Chisel inspector. #[inline] - pub fn chisel_state(mut self, final_pc: usize) -> Self { + pub const fn chisel_state(mut self, final_pc: usize) -> Self { self.chisel_state = Some(final_pc); self } /// Set the log collector, and whether to print the logs directly to stdout. #[inline] - pub fn logs(mut self, live_logs: bool) -> Self { + pub const fn logs(mut self, live_logs: bool) -> Self { self.logs = Some(live_logs); self } /// Set whether to collect line coverage information. #[inline] - pub fn line_coverage(mut self, yes: bool) -> Self { + pub const fn line_coverage(mut self, yes: bool) -> Self { self.line_coverage = Some(yes); self } /// Set whether to enable the trace printer. #[inline] - pub fn print(mut self, yes: bool) -> Self { + pub const fn print(mut self, yes: bool) -> Self { self.print = Some(yes); self } @@ -196,20 +196,20 @@ impl InspectorStackBuilder { /// Set whether to enable the call isolation. /// For description of call isolation, see [`InspectorStack::enable_isolation`]. #[inline] - pub fn enable_isolation(mut self, yes: bool) -> Self { + pub const fn enable_isolation(mut self, yes: bool) -> Self { self.enable_isolation = yes; self } /// Set networks with enabled features. #[inline] - pub fn networks(mut self, networks: NetworkConfigs) -> Self { + pub const fn networks(mut self, networks: NetworkConfigs) -> Self { self.networks = networks; self } #[inline] - pub fn create2_deployer(mut self, create2_deployer: Address) -> Self { + pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self { self.create2_deployer = create2_deployer; self } @@ -459,13 +459,9 @@ impl CheatcodesExecutor for InspectorStackInner { fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option
) { self.in_inner_context = enabled; - self.inner_context_data = if enabled { - Some(InnerContextData { - original_origin: original_origin.expect("origin required when enabling inner ctx"), - }) - } else { - None - }; + self.inner_context_data = enabled.then(|| InnerContextData { + original_origin: original_origin.expect("origin required when enabling inner ctx"), + }); } } @@ -541,13 +537,13 @@ impl InspectorStack { /// Set whether to enable call isolation. #[inline] - pub fn enable_isolation(&mut self, yes: bool) { + pub const fn enable_isolation(&mut self, yes: bool) { self.inner.enable_isolation = yes; } /// Set networks with enabled features. #[inline] - pub fn networks(&mut self, networks: NetworkConfigs) { + pub const fn networks(&mut self, networks: NetworkConfigs) { self.inner.networks = networks; } diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index 9c47a37680254..2d0e62ed4ec60 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -198,7 +198,7 @@ pub struct TargetedContract { impl TargetedContract { /// Returns a new `TargetedContract` instance. - pub fn new(identifier: String, abi: JsonAbi) -> Self { + pub const fn new(identifier: String, abi: JsonAbi) -> Self { Self { identifier, abi, @@ -225,15 +225,15 @@ impl TargetedContract { /// Returns specified targeted functions if any, else mutable abi functions that are not /// marked as excluded. pub fn abi_fuzzed_functions(&self) -> impl Iterator { - if !self.targeted_functions.is_empty() { - Either::Left(self.targeted_functions.iter()) - } else { + if self.targeted_functions.is_empty() { Either::Right(self.abi.functions().filter(|&func| { !matches!( func.state_mutability, alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View ) && !self.excluded_functions.contains(func) })) + } else { + Either::Left(self.targeted_functions.iter()) } } @@ -274,7 +274,7 @@ pub struct InvariantContract<'a> { impl<'a> InvariantContract<'a> { /// Creates a new invariant contract. - pub fn new( + pub const fn new( address: Address, invariant_function: &'a Function, call_after_invariant: bool, diff --git a/crates/evm/fuzz/src/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs index 68ff4e033da14..c96d7c2da6420 100644 --- a/crates/evm/fuzz/src/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -23,7 +23,7 @@ impl IntValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: I256, fixed: bool) -> Self { + const fn new(start: I256, fixed: bool) -> Self { Self { lo: I256::ZERO, curr: start, hi: start, fixed } } diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 0f7ba181be7ac..8ff473d4ebc93 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -119,9 +119,7 @@ fn select_random_sender( senders: Rc, dictionary_weight: u32, ) -> impl Strategy + use<> { - if !senders.targeted.is_empty() { - any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() - } else { + if senders.targeted.is_empty() { assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100"); proptest::prop_oneof![ 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address), @@ -142,6 +140,8 @@ fn select_random_sender( addr }) .boxed() + } else { + any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() } } diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index 39f53e0c81444..177fc7696ce4f 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -460,7 +460,7 @@ impl FuzzDictionary { } } - pub fn values(&self) -> &B256IndexSet { + pub const fn values(&self) -> &B256IndexSet { &self.state_values } @@ -499,7 +499,7 @@ impl FuzzDictionary { } #[inline] - pub fn addresses(&self) -> &AddressIndexSet { + pub const fn addresses(&self) -> &AddressIndexSet { &self.addresses } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index ad6eae502fa42..ebf21ad0d1687 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -23,7 +23,7 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool) -> Self { + const fn new(start: U256, fixed: bool) -> Self { Self { lo: U256::ZERO, curr: start, hi: start, fixed } } diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs index 73cfffe2077eb..d4372e8072633 100644 --- a/crates/evm/hardforks/src/lib.rs +++ b/crates/evm/hardforks/src/lib.rs @@ -79,15 +79,15 @@ impl FromStr for FoundryHardfork { } impl FoundryHardfork { - pub fn ethereum(h: EthereumHardfork) -> Self { + pub const fn ethereum(h: EthereumHardfork) -> Self { Self::Ethereum(h) } - pub fn optimism(h: OpHardfork) -> Self { + pub const fn optimism(h: OpHardfork) -> Self { Self::Optimism(h) } - pub fn tempo(h: TempoHardfork) -> Self { + pub const fn tempo(h: TempoHardfork) -> Self { Self::Tempo(h) } diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs index 7f8e04e02bc92..fed98398c4f27 100644 --- a/crates/evm/networks/src/lib.rs +++ b/crates/evm/networks/src/lib.rs @@ -19,7 +19,7 @@ use std::collections::BTreeMap; pub mod celo; -#[derive(Clone, Debug, Default, Parser, Copy, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Parser, Copy, Serialize, Deserialize, PartialEq, Eq)] pub struct NetworkConfigs { /// Enable Optimism network features. #[arg(help_heading = "Networks", long, conflicts_with_all = ["celo", "tempo"])] @@ -53,11 +53,11 @@ impl NetworkConfigs { Self { tempo: true, ..Default::default() } } - pub fn is_optimism(&self) -> bool { + pub const fn is_optimism(&self) -> bool { self.optimism } - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.tempo } @@ -88,7 +88,7 @@ impl NetworkConfigs { self.bypass_prevrandao } - pub fn is_celo(&self) -> bool { + pub const fn is_celo(&self) -> bool { self.celo } diff --git a/crates/evm/traces/src/backtrace/mod.rs b/crates/evm/traces/src/backtrace/mod.rs index b2232b42b410c..1b479954b8d59 100644 --- a/crates/evm/traces/src/backtrace/mod.rs +++ b/crates/evm/traces/src/backtrace/mod.rs @@ -302,7 +302,7 @@ impl<'a> Backtrace<'a> { } /// Returns true if the backtrace is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.frames.is_empty() } } @@ -346,7 +346,7 @@ struct BacktraceFrame { impl BacktraceFrame { /// Creates a new backtrace frame. - fn new(contract_address: Address) -> Self { + const fn new(contract_address: Address) -> Self { Self { contract_address, contract_name: None, @@ -379,7 +379,7 @@ impl BacktraceFrame { } /// Sets the byte offset. - fn with_byte_offset(mut self, offset: usize) -> Self { + const fn with_byte_offset(mut self, offset: usize) -> Self { self.byte_offset = Some(offset); self } diff --git a/crates/evm/traces/src/debug/mod.rs b/crates/evm/traces/src/debug/mod.rs index 4dda5ff720444..c3e0cca2cf80c 100644 --- a/crates/evm/traces/src/debug/mod.rs +++ b/crates/evm/traces/src/debug/mod.rs @@ -18,7 +18,7 @@ pub struct DebugTraceIdentifier { } impl DebugTraceIdentifier { - pub fn new(contracts_sources: ContractSources) -> Self { + pub const fn new(contracts_sources: ContractSources) -> Self { Self { contracts_sources } } @@ -65,7 +65,7 @@ struct DebugStepsWalker<'a> { } impl<'a> DebugStepsWalker<'a> { - pub fn new( + pub const fn new( node: &'a mut CallTraceNode, sources: &'a ContractSources, contract_name: &'a str, diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index bf53210aebbb4..907b096c435a5 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -84,7 +84,7 @@ impl CallTraceDecoderBuilder { /// Sets the verbosity level of the decoder. #[inline] - pub fn with_verbosity(mut self, level: u8) -> Self { + pub const fn with_verbosity(mut self, level: u8) -> Self { self.decoder.verbosity = level; self } @@ -98,14 +98,14 @@ impl CallTraceDecoderBuilder { /// Sets the signature identifier for events and functions. #[inline] - pub fn with_label_disabled(mut self, disable_alias: bool) -> Self { + pub const fn with_label_disabled(mut self, disable_alias: bool) -> Self { self.decoder.disable_labels = disable_alias; self } /// Sets the chain ID for network-specific precompile detection. #[inline] - pub fn with_chain_id(mut self, chain_id: Option) -> Self { + pub const fn with_chain_id(mut self, chain_id: Option) -> Self { self.decoder.chain_id = chain_id; self } @@ -362,7 +362,7 @@ impl CallTraceDecoder { self.revert_decoder.push_error(error); } - pub fn without_label(&mut self, disable: bool) { + pub const fn without_label(&mut self, disable: bool) { self.disable_labels = disable; } @@ -482,7 +482,9 @@ impl CallTraceDecoder { && !contract_selectors.contains(&selector) && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address)) { - let return_data = if !trace.success { + let return_data = if trace.success { + None + } else { let revert_msg = self.revert_decoder.decode(&trace.output, trace.status); if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") { @@ -493,8 +495,6 @@ impl CallTraceDecoder { } else { Some(revert_msg) } - } else { - None }; return if let Some(func) = functions.first() { @@ -598,20 +598,12 @@ impl CallTraceDecoder { "broadcast" | "startBroadcast" => { // Redact private key if defined // broadcast(uint256) / startBroadcast(uint256) - if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" { - Some(vec!["".to_string()]) - } else { - None - } + (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["".to_string()]) } "getNonce" => { // Redact private key if defined // getNonce(Wallet) - if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" { - Some(vec!["".to_string()]) - } else { - None - } + (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["".to_string()]) } "sign" | "signP256" => { let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs index 2b4e58b8665a7..4fc43594e1b39 100644 --- a/crates/evm/traces/src/folded_stack_trace.rs +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -189,7 +189,7 @@ impl FoldedStackTraceBuilder { } /// Exit execution of a function call. - pub fn exit(&mut self) { + pub const fn exit(&mut self) { self.exits += 1; } diff --git a/crates/evm/traces/src/identifier/external.rs b/crates/evm/traces/src/identifier/external.rs index 841c77c33f0de..6230c53a57fcd 100644 --- a/crates/evm/traces/src/identifier/external.rs +++ b/crates/evm/traces/src/identifier/external.rs @@ -360,7 +360,7 @@ struct EtherscanFetcher { } impl EtherscanFetcher { - fn new(client: foundry_block_explorers::Client) -> Self { + const fn new(client: foundry_block_explorers::Client) -> Self { Self { client, invalid_api_key: AtomicBool::new(false) } } } diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index 3e066c9511a28..05dab2ba44edd 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -29,14 +29,14 @@ impl<'a> LocalTraceIdentifier<'a> { Self { known_contracts, ordered_ids, contracts_bytecode: None } } - pub fn with_bytecodes(mut self, contracts_bytecode: &'a HashMap) -> Self { + pub const fn with_bytecodes(mut self, contracts_bytecode: &'a HashMap) -> Self { self.contracts_bytecode = Some(contracts_bytecode); self } /// Returns the known contracts. #[inline] - pub fn contracts(&self) -> &'a ContractsByArtifact { + pub const fn contracts(&self) -> &'a ContractsByArtifact { self.known_contracts } diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 521c42010f25b..40dd968527d68 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -102,7 +102,7 @@ impl<'a> TraceIdentifiers<'a> { } /// Returns `true` if there are no set identifiers. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.local.is_none() && self.external.is_none() } } diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index ef557a10683ea..3481620fa7c75 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -181,7 +181,7 @@ impl SignaturesIdentifier { /// - `cache_dir` is the cache directory to store the signatures. /// - `offline` disables the OpenChain client. pub fn new_with(cache_dir: Option<&Path>, offline: bool) -> Result { - let client = if !offline { Some(OpenChainClient::new()?) } else { None }; + let client = if offline { None } else { Some(OpenChainClient::new()?) }; let (cache, cache_path) = if let Some(cache_dir) = cache_dir { let path = cache_dir.join("signatures"); let cache = SignaturesCache::load(&path); diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 2c5965b1141f7..f9bf9c5e8471a 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -216,7 +216,7 @@ pub fn render_trace_arena_inner( String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } -fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { +const fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { match choice { shell::ColorChoice::Auto => revm_inspectors::ColorChoice::Auto, shell::ColorChoice::Always => revm_inspectors::ColorChoice::Always, @@ -237,7 +237,7 @@ impl TraceKind { /// /// [`Deployment`]: TraceKind::Deployment #[must_use] - pub fn is_deployment(self) -> bool { + pub const fn is_deployment(self) -> bool { matches!(self, Self::Deployment) } @@ -245,7 +245,7 @@ impl TraceKind { /// /// [`Setup`]: TraceKind::Setup #[must_use] - pub fn is_setup(self) -> bool { + pub const fn is_setup(self) -> bool { matches!(self, Self::Setup) } @@ -253,7 +253,7 @@ impl TraceKind { /// /// [`Execution`]: TraceKind::Execution #[must_use] - pub fn is_execution(self) -> bool { + pub const fn is_execution(self) -> bool { matches!(self, Self::Execution) } } diff --git a/crates/fmt/src/lib.rs b/crates/fmt/src/lib.rs index c73e4c9553c18..cf973d4d99dff 100644 --- a/crates/fmt/src/lib.rs +++ b/crates/fmt/src/lib.rs @@ -62,7 +62,7 @@ impl DiagnosticsResult { } /// Returns any result produced. - pub fn ok_ref(&self) -> Option<&T> { + pub const fn ok_ref(&self) -> Option<&T> { match self { Self::Ok(s) | Self::OkWithDiagnostics(s, _) | Self::ErrRecovered(s, _) => Some(s), Self::Err(_) => None, @@ -70,7 +70,7 @@ impl DiagnosticsResult { } /// Returns any diagnostics emitted. - pub fn err_ref(&self) -> Option<&E> { + pub const fn err_ref(&self) -> Option<&E> { match self { Self::Ok(_) => None, Self::OkWithDiagnostics(_, d) | Self::ErrRecovered(_, d) | Self::Err(d) => Some(d), @@ -78,12 +78,12 @@ impl DiagnosticsResult { } /// Returns `true` if the result is `Ok`. - pub fn is_ok(&self) -> bool { + pub const fn is_ok(&self) -> bool { matches!(self, Self::Ok(_) | Self::OkWithDiagnostics(_, _)) } /// Returns `true` if the result is `Err`. - pub fn is_err(&self) -> bool { + pub const fn is_err(&self) -> bool { !self.is_ok() } } @@ -234,7 +234,7 @@ pub fn format_ast<'ast>( gcx.sess.source_map(), true, config.wrap_comments, - if matches!(config.style, IndentStyle::Tab) { Some(config.tab_width) } else { None }, + matches!(config.style, IndentStyle::Tab).then(|| config.tab_width), ); let ast = source.ast.as_ref()?; let inline_config = parse_inline_config(gcx.sess, &comments, ast); diff --git a/crates/fmt/src/pp/convenience.rs b/crates/fmt/src/pp/convenience.rs index 65e66d5d42097..a4a2d693c6149 100644 --- a/crates/fmt/src/pp/convenience.rs +++ b/crates/fmt/src/pp/convenience.rs @@ -76,7 +76,7 @@ impl Printer { return true; } - self.out.ends_with(" ") + self.out.ends_with(' ') } pub fn is_beginning_of_line(&self) -> bool { @@ -138,14 +138,14 @@ impl Printer { } impl Token { - pub(crate) fn is_neverbreak(&self) -> bool { + pub(crate) const fn is_neverbreak(&self) -> bool { if let Self::Break(BreakToken { never_break, .. }) = *self { return never_break; } false } - pub(crate) fn is_hardbreak(&self) -> bool { + pub(crate) const fn is_hardbreak(&self) -> bool { if let Self::Break(BreakToken { blank_space, never_break, .. }) = *self { return blank_space == SIZE_INFINITY as usize && !never_break; } diff --git a/crates/fmt/src/pp/mod.rs b/crates/fmt/src/pp/mod.rs index 66b5a0170ea1e..89783f05f5bb8 100644 --- a/crates/fmt/src/pp/mod.rs +++ b/crates/fmt/src/pp/mod.rs @@ -215,7 +215,7 @@ impl Printer { { for i in self.buf.index_range().rev() { let token = &self.buf[i].token; - if let Token::End = token { + if matches!(token, Token::End) { // It's safe to skip the end of a box. continue; } diff --git a/crates/fmt/src/pp/ring.rs b/crates/fmt/src/pp/ring.rs index f958a0dd2fefc..198f3c0a1baee 100644 --- a/crates/fmt/src/pp/ring.rs +++ b/crates/fmt/src/pp/ring.rs @@ -11,7 +11,7 @@ pub(crate) struct RingBuffer { } impl RingBuffer { - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { Self { data: VecDeque::new(), offset: 0 } } diff --git a/crates/fmt/src/state/common.rs b/crates/fmt/src/state/common.rs index e3e37c5169329..b2b2767ad1e9a 100644 --- a/crates/fmt/src/state/common.rs +++ b/crates/fmt/src/state/common.rs @@ -37,12 +37,12 @@ impl<'ast> State<'_, 'ast> { let quote_pos = span.lo() + kind.prefix().len() as u32; self.print_str_lit(kind, quote_pos, symbol.as_str()); } - if !pos.is_last { + if pos.is_last { + self.neverbreak(); + } else { if !self.print_trailing_comment(span.hi(), None) { self.space_if_not_bol(); } - } else { - self.neverbreak(); } } self.end(); @@ -93,10 +93,10 @@ impl<'ast> State<'_, 'ast> { let config = self.config.number_underscore; let is_dec = !["0x", "0b", "0o"].iter().any(|prefix| source.starts_with(prefix)); - let (val, exp) = if !is_dec { - (source, "") - } else { + let (val, exp) = if is_dec { source.split_once(['e', 'E']).unwrap_or((source, "")) + } else { + (source, "") }; let (val, fract) = val.split_once('.').unwrap_or((val, "")); @@ -243,11 +243,12 @@ impl<'ast> State<'_, 'ast> { self.print_inside_parens(|state| { let span = get_span(&values[0]); state.s.cbox(state.ind); - let mut skip_break = true; - if state.peek_comment_before(span.hi()).is_some() { + let skip_break = if state.peek_comment_before(span.hi()).is_some() { state.hardbreak(); - skip_break = false; - } + false + } else { + true + }; state.print_comments(span.lo(), CommentConfig::skip_ws().mixed_prev_space()); print(state, &values[0]); @@ -601,9 +602,10 @@ impl<'ast> State<'_, 'ast> { let enabled = !self.inline_config.is_disabled(Span::new(block_lo, block_lo + BytePos(1))) && !self.handle_span(self.cursor.span(block_lo), true); - match self.peek_comment().and_then(|cmnt| { - if cmnt.span.hi() < block_lo { Some((cmnt.span, cmnt.style)) } else { None } - }) { + match self + .peek_comment() + .and_then(|cmnt| (cmnt.span.hi() < block_lo).then_some((cmnt.span, cmnt.style))) + { Some((span, style)) => { if enabled { // Inline config is not disabled and span not handled @@ -656,14 +658,15 @@ impl<'ast> State<'_, 'ast> { print(self, stmt); let is_disabled = self.inline_config.is_disabled(get_block_span(stmt)); - let mut next_enabled = false; - let mut next_lo = None; - if !is_last { + let (next_enabled, next_lo) = if is_last { + (false, None) + } else { let next_span = get_block_span(&block[i + 1]); - next_enabled = !self.inline_config.is_disabled(next_span); - next_lo = - self.peek_comment_before(next_span.lo()).is_none().then_some(next_span.lo()); - } + ( + !self.inline_config.is_disabled(next_span), + self.peek_comment_before(next_span.lo()).is_none().then_some(next_span.lo()), + ) + }; // when this stmt and the next one are enabled, break normally (except if last stmt) if !is_disabled @@ -759,10 +762,7 @@ impl<'ast> State<'_, 'ast> { if has_braces { self.word("{"); } - let mut offset = 0; - if let BlockFormat::NoBraces(Some(off)) = block_format { - offset = off; - } + let offset = if let BlockFormat::NoBraces(Some(off)) = block_format { off } else { 0 }; self.print_comments( pos_hi, self.cmnt_config().offset(offset).mixed_no_break().mixed_prev_space().mixed_post_nbsp(), @@ -824,27 +824,27 @@ impl Default for ListFormat { impl ListFormat { // -- GETTER METHODS ------------------------------------------------------- - pub(crate) fn prev_symbol(&self) -> Option<&'static str> { + pub(crate) const fn prev_symbol(&self) -> Option<&'static str> { if let ListFormatKind::Yul { sym_prev, .. } = self.kind { sym_prev } else { None } } - pub(crate) fn post_symbol(&self) -> Option<&'static str> { + pub(crate) const fn post_symbol(&self) -> Option<&'static str> { if let ListFormatKind::Yul { sym_post, .. } = self.kind { sym_post } else { None } } - pub(crate) fn is_consistent(&self) -> bool { + pub(crate) const fn is_consistent(&self) -> bool { matches!(self.kind, ListFormatKind::Consistent) } - pub(crate) fn is_compact(&self) -> bool { + pub(crate) const fn is_compact(&self) -> bool { matches!(self.kind, ListFormatKind::Compact) } - pub(crate) fn is_inline(&self) -> bool { + pub(crate) const fn is_inline(&self) -> bool { matches!(self.kind, ListFormatKind::Inline) } - pub(crate) fn breaks_with_comments(&self) -> bool { + pub(crate) const fn breaks_with_comments(&self) -> bool { self.breaks_cmnts } @@ -880,35 +880,35 @@ impl ListFormat { } } - pub(crate) fn without_ind(mut self, without: bool) -> Self { + pub(crate) const fn without_ind(mut self, without: bool) -> Self { if !matches!(self.kind, ListFormatKind::Inline) { self.no_ind = without; } self } - pub(crate) fn break_single(mut self, value: bool) -> Self { + pub(crate) const fn break_single(mut self, value: bool) -> Self { if !matches!(self.kind, ListFormatKind::Inline) { self.break_single = value; } self } - pub(crate) fn break_cmnts(mut self) -> Self { + pub(crate) const fn break_cmnts(mut self) -> Self { if !matches!(self.kind, ListFormatKind::Inline) { self.breaks_cmnts = true; } self } - pub(crate) fn with_space(mut self) -> Self { + pub(crate) const fn with_space(mut self) -> Self { if !matches!(self.kind, ListFormatKind::Inline) { self.with_space = true; } self } - pub(crate) fn with_delimiters(mut self, with: bool) -> Self { + pub(crate) const fn with_delimiters(mut self, with: bool) -> Self { if matches!(self.kind, ListFormatKind::Compact | ListFormatKind::Consistent) { self.with_delimiters = with; } @@ -947,14 +947,14 @@ pub(crate) enum BlockFormat { } impl BlockFormat { - pub(crate) fn with_braces(&self) -> bool { + pub(crate) const fn with_braces(&self) -> bool { !matches!(self, Self::NoBraces(_)) } - pub(crate) fn breaks(&self) -> bool { + pub(crate) const fn breaks(&self) -> bool { matches!(self, Self::NoBraces(None)) } - pub(crate) fn attempt_single_line(&self) -> bool { + pub(crate) const fn attempt_single_line(&self) -> bool { matches!(self, Self::Compact(_)) } } diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index e98eac6f4e5c1..89a9bf152c8c2 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -44,19 +44,19 @@ pub(super) struct CallContext { } impl CallContext { - pub(super) fn nested(size: usize) -> Self { + pub(super) const fn nested(size: usize) -> Self { Self { kind: CallContextKind::Nested, size, has_indent: false } } - pub(super) fn chained(size: usize, has_indent: bool) -> Self { + pub(super) const fn chained(size: usize, has_indent: bool) -> Self { Self { kind: CallContextKind::Chained, size, has_indent } } - pub(super) fn is_nested(&self) -> bool { + pub(super) const fn is_nested(&self) -> bool { matches!(self.kind, CallContextKind::Nested) } - pub(super) fn is_chained(&self) -> bool { + pub(super) const fn is_chained(&self) -> bool { matches!(self.kind, CallContextKind::Chained) } } @@ -201,11 +201,7 @@ impl<'sess> State<'sess, '_> { Self { s: pp::Printer::new( config.line_length, - if matches!(config.style, IndentStyle::Tab) { - Some(config.tab_width) - } else { - None - }, + matches!(config.style, IndentStyle::Tab).then(|| config.tab_width), ), ind: config.tab_width as isize, sm, @@ -440,8 +436,8 @@ impl State<'_, '_> { // - ends with ',' a line break or a space are required. // - ends with ';' a line break is required. prev_needs_space = match line.chars().next_back() { - Some('[') | Some('(') | Some('{') => self.config.bracket_spacing, - Some(',') | Some(';') => true, + Some('[' | '(' | '{') => self.config.bracket_spacing, + Some(',' | ';') => true, _ => false, }; } @@ -482,10 +478,10 @@ impl<'sess> State<'sess, '_> { } fn cmnt_config(&self) -> CommentConfig { - CommentConfig { ..Default::default() } + Default::default() } - fn print_docs(&mut self, docs: &'_ ast::DocComments<'_>) { + const fn print_docs(&mut self, docs: &'_ ast::DocComments<'_>) { // Intetionally no-op. Handled with `self.comments`. let _ = docs; } @@ -1065,12 +1061,12 @@ impl CommentConfig { Self { skip_blanks: Some(Skip::Trailing), ..Default::default() } } - pub(crate) fn offset(mut self, off: isize) -> Self { + pub(crate) const fn offset(mut self, off: isize) -> Self { self.offset = off; self } - pub(crate) fn no_breaks(mut self) -> Self { + pub(crate) const fn no_breaks(mut self) -> Self { self.iso_no_break = true; self.trailing_no_break = true; self.mixed_no_break_prev = true; @@ -1078,28 +1074,28 @@ impl CommentConfig { self } - pub(crate) fn trailing_no_break(mut self) -> Self { + pub(crate) const fn trailing_no_break(mut self) -> Self { self.trailing_no_break = true; self } - pub(crate) fn mixed_no_break(mut self) -> Self { + pub(crate) const fn mixed_no_break(mut self) -> Self { self.mixed_no_break_prev = true; self.mixed_no_break_post = true; self } - pub(crate) fn mixed_no_break_post(mut self) -> Self { + pub(crate) const fn mixed_no_break_post(mut self) -> Self { self.mixed_no_break_post = true; self } - pub(crate) fn mixed_prev_space(mut self) -> Self { + pub(crate) const fn mixed_prev_space(mut self) -> Self { self.mixed_prev_space = true; self } - pub(crate) fn mixed_post_nbsp(mut self) -> Self { + pub(crate) const fn mixed_post_nbsp(mut self) -> Self { self.mixed_post_nbsp = true; self } diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index f46455b2a6b5d..dc53558a20f33 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -93,7 +93,7 @@ impl<'ast> State<'_, 'ast> { let cmnts = self .comments .iter() - .filter_map(|c| if c.pos() < span.lo() { Some(c.style) } else { None }) + .filter_map(|c| (c.pos() < span.lo()).then_some(c.style)) .collect::>(); if let Some(first) = cmnts.first() @@ -364,7 +364,16 @@ impl<'ast> State<'_, 'ast> { self.print_word("{"); self.end(); - if !body.is_empty() { + if body.is_empty() { + if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() { + // Adjust the offset of the trailing break from comment printing + // so the closing brace is not indented + self.s.offset(-self.ind); + } else if self.config.bracket_spacing { + self.nbsp(); + }; + self.end(); + } else { // update block depth self.block_depth += 1; @@ -405,15 +414,6 @@ impl<'ast> State<'_, 'ast> { // restore block depth self.block_depth -= 1; - } else { - if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() { - // Adjust the offset of the trailing break from comment printing - // so the closing brace is not indented - self.s.offset(-self.ind); - } else if self.config.bracket_spacing { - self.nbsp(); - }; - self.end(); } self.print_word("}"); @@ -699,16 +699,17 @@ impl<'ast> State<'_, 'ast> { for cmnt in inner_cmnts.into_iter().rev() { self.comments.push_front(cmnt); } - let mut enabled = false; - if !self.handle_span(span, false) { + let enabled = if self.handle_span(span, false) { + false + } else { if !self.is_bol_or_only_ind() { self.space(); } self.ibox(0); print_fn(self); self.cursor.advance_to(span.hi(), true); - enabled = true; - } + true + }; // Print subsequent comments. for cmnt in post_cmnts { let Some(cmnt) = self.handle_comment(cmnt, false) else { @@ -1566,14 +1567,15 @@ impl<'ast> State<'_, 'ast> { } // Trailing comment handling. - let mut is_trailing = false; - if let Some(style) = self.print_comments( + let is_trailing = if let Some(style) = self.print_comments( span.hi(), CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), ) { skip_break = true; - is_trailing = style.is_trailing(); - } + style.is_trailing() + } else { + false + }; // Adjust indentation and line breaks. match (skip_break, end.is_some()) { @@ -1714,11 +1716,11 @@ impl<'ast> State<'_, 'ast> { self.call_stack.push(CallContext::chained(callee_size, chain_has_indent)); } - if !chain_has_indent { + if chain_has_indent { + self.s.ibox(self.ind); + } else { self.skip_index_break = true; self.cbox(0); - } else { - self.s.ibox(self.ind); } } @@ -2190,7 +2192,9 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); } - if !args.is_empty() { + if args.is_empty() { + self.end(); + } else { self.print_word("returns "); self.print_word("("); self.zerobreak(); @@ -2206,8 +2210,6 @@ impl<'ast> State<'_, 'ast> { ); self.print_word(")"); self.nbsp(); - } else { - self.end(); } if block.is_empty() { self.print_block(block, *try_span); @@ -2678,20 +2680,20 @@ enum MemberOrCallArgs { } impl MemberOrCallArgs { - fn size(&self) -> usize { + const fn size(&self) -> usize { match self { Self::CallArgs(size, ..) | Self::Member(size) => *size, } } - fn member_size(&self) -> usize { + const fn member_size(&self) -> usize { match self { Self::CallArgs(..) => 0, Self::Member(size) => *size, } } - fn has_comments(&self) -> bool { + const fn has_comments(&self) -> bool { matches!(self, Self::CallArgs(.., true)) } } @@ -2750,7 +2752,7 @@ impl<'ast> AttributeCommentMapper<'ast> { let before_limit = self.attributes[a].span.lo(); let inner_limit = self.attributes[a].span.hi(); let after_limit = - if !is_last { self.attributes[a + 1].span.lo() } else { self.limit_pos }; + if is_last { self.limit_pos } else { self.attributes[a + 1].span.lo() }; let mut c = 0; while c < self.comments.len() { @@ -2834,7 +2836,7 @@ impl<'ast> AttributeCommentMapper<'ast> { } } -fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool { +const fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool { match stmt { ast::StmtKind::Assembly { .. } | ast::StmtKind::Block { .. } @@ -2879,7 +2881,7 @@ fn item_needs_iso(item: &ast::ItemKind<'_>) -> bool { } } -fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool { +const fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool { matches!(expr_kind, ast::ExprKind::Binary(..)) } @@ -2899,7 +2901,7 @@ fn has_complex_successor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool { } } -fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool { +const fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool { matches!(expr_kind, ast::ExprKind::Call(..)) } @@ -2907,7 +2909,7 @@ fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool { /// Used to determine if `.field` after such a call should avoid breaking. /// E.g., `_lzSend({_dstEid: x, ...}).guid` → true (named args call) /// E.g., `someFunc(a, b).field` → false (positional args) -fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool { +const fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool { if let ast::ExprKind::Call(_, args) = expr_kind { matches!(args.kind, ast::CallArgsKind::Named(_)) } else { diff --git a/crates/fmt/src/state/yul.rs b/crates/fmt/src/state/yul.rs index 0f973624bf9ec..b67fdd5a26263 100644 --- a/crates/fmt/src/state/yul.rs +++ b/crates/fmt/src/state/yul.rs @@ -253,7 +253,9 @@ impl<'ast> State<'_, 'ast> { |s, stmt| { s.print_yul_stmt(stmt); s.print_comments(stmt.span.hi(), CommentConfig::default()); - if i != n_args { + if i == n_args { + s.print_trailing_comment(stmt.span.hi(), Some(span.hi())); + } else { let next_span = block[i + 1].span; s.print_trailing_comment(stmt.span.hi(), Some(next_span.lo())); if !s.is_bol_or_only_ind() && !s.inline_config.is_disabled(stmt.span) { @@ -270,8 +272,6 @@ impl<'ast> State<'_, 'ast> { } } i += 1; - } else { - s.print_trailing_comment(stmt.span.hi(), Some(span.hi())); } }, |b| b.span, diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c24dc0ba484d7..3be249dd24b1a 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -29,6 +29,7 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm.workspace = true +foundry-evm-networks.workspace = true foundry-linking.workspace = true comfy-table.workspace = true @@ -50,19 +51,19 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, optional = true } alloy-chains.workspace = true -alloy-consensus.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-network.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-rpc-types.workspace = true +alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true - -tempo-alloy.workspace = true +alloy-hardforks.workspace = true revm.workspace = true @@ -104,7 +105,6 @@ quick-junit = "0.5.2" [dev-dependencies] alloy-hardforks.workspace = true anvil.workspace = true -foundry-evm-networks.workspace = true forge-script-sequence.workspace = true foundry-test-utils.workspace = true @@ -122,7 +122,7 @@ asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] -aws-kms = ["foundry-wallets/aws-kms"] -gcp-kms = ["foundry-wallets/gcp-kms"] -turnkey = ["foundry-wallets/turnkey"] +aws-kms = ["dep:foundry-wallets", "foundry-wallets/aws-kms"] +gcp-kms = ["dep:foundry-wallets", "foundry-wallets/gcp-kms"] +turnkey = ["dep:foundry-wallets", "foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] diff --git a/crates/forge/src/cmd/bind.rs b/crates/forge/src/cmd/bind.rs index 9ef8edc88f5d7..e356dde7a2ead 100644 --- a/crates/forge/src/cmd/bind.rs +++ b/crates/forge/src/cmd/bind.rs @@ -202,7 +202,7 @@ impl BindArgs { .get_json_files(artifacts)? .filter_map(|(name, path)| { trace!(?path, "parsing SolMacroGen from file"); - if dup.insert(name.clone()) { Some(SolMacroGen::new(path, name)) } else { None } + dup.insert(name.clone()).then(|| SolMacroGen::new(path, name)) }) .collect::>(); @@ -235,7 +235,14 @@ impl BindArgs { let mut solmacrogen = self.get_solmacrogen(artifacts)?; sh_println!("Generating bindings for {} contracts", solmacrogen.instances.len())?; - if !self.module { + if self.module { + trace!(single_file = self.single_file, "generating module"); + solmacrogen.write_to_module( + bindings_root, + self.single_file, + !self.skip_extra_derives, + )?; + } else { trace!(single_file = self.single_file, "generating crate"); solmacrogen.write_to_crate( &self.crate_name, @@ -248,13 +255,6 @@ impl BindArgs { self.alloy_rev.clone(), !self.skip_extra_derives, )?; - } else { - trace!(single_file = self.single_file, "generating module"); - solmacrogen.write_to_module( - bindings_root, - self.single_file, - !self.skip_extra_derives, - )?; } Ok(()) diff --git a/crates/forge/src/cmd/bind_json.rs b/crates/forge/src/cmd/bind_json.rs index f29ba6f131409..113a32ba5fb7e 100644 --- a/crates/forge/src/cmd/bind_json.rs +++ b/crates/forge/src/cmd/bind_json.rs @@ -157,13 +157,13 @@ impl BindJsonArgs { let mut target_files = HashSet::new(); for (path, source) in &input.input.sources { - if !include.is_empty() { - if !include.iter().any(|matcher| matcher.is_match(path)) { + if include.is_empty() { + // Exclude library files by default + if project.paths.has_library_ancestor(path) { continue; } } else { - // Exclude library files by default - if project.paths.has_library_ancestor(path) { + if !include.iter().any(|matcher| matcher.is_match(path)) { continue; } } @@ -398,7 +398,7 @@ struct PreprocessorVisitor { } impl PreprocessorVisitor { - fn new() -> Self { + const fn new() -> Self { Self { updates: Vec::new() } } @@ -452,7 +452,7 @@ impl<'ast> Visit<'ast> for PreprocessorVisitor { var: &'ast ast::VariableDefinition<'ast>, ) -> ControlFlow { // Remove `immutable` attributes. - if let Some(VarMut::Immutable) = var.mutability { + if var.mutability == Some(VarMut::Immutable) { self.updates.push((var.span, "")); } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 2812f4d319b7b..55eff1174853e 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -215,7 +215,7 @@ impl BuildArgs { } /// Returns whether `BuildArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } diff --git a/crates/forge/src/cmd/compiler.rs b/crates/forge/src/cmd/compiler.rs index 39d38b8b249ba..29c2e30cbb0c5 100644 --- a/crates/forge/src/cmd/compiler.rs +++ b/crates/forge/src/cmd/compiler.rs @@ -92,15 +92,9 @@ impl ResolveArgs { }) .collect(); - let evm_version = if shell::verbosity() > 1 { - let evm = EvmVersion::default() - .normalize_version_solc(version) - .unwrap_or_default(); - - Some(evm) - } else { - None - }; + let evm_version = (shell::verbosity() > 1).then(|| { + EvmVersion::default().normalize_version_solc(version).unwrap_or_default() + }); ResolvedCompiler { version: version.clone(), evm_version, paths } }) diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index e5ed66215b0bd..ea034bce87185 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -55,7 +55,6 @@ pub struct CoverageArgs { /// If not specified, the report will be stored in the root of the project. #[arg( long, - short, value_hint = ValueHint::FilePath, value_name = "PATH" )] @@ -316,11 +315,11 @@ impl CoverageArgs { Ok(()) } - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.test.is_watch() } - pub fn watch(&self) -> &WatchArgs { + pub const fn watch(&self) -> &WatchArgs { &self.test.watch } } diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index a6337a266c9a8..122020571ad24 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -426,7 +426,14 @@ impl CreateArgs { } if dry_run { - if !shell::is_json() { + if shell::is_json() { + let output = json!({ + "contract": self.contract.name, + "transaction": &deployer.tx, + "abi":&abi + }); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + } else { sh_warn!("Dry run enabled, not broadcasting transaction\n")?; sh_println!("Contract: {}", self.contract.name)?; @@ -439,13 +446,6 @@ impl CreateArgs { sh_warn!( "To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more." )?; - } else { - let output = json!({ - "contract": self.contract.name, - "transaction": &deployer.tx, - "abi":&abi - }); - sh_println!("{}", serde_json::to_string_pretty(&output)?)?; } return Ok(()); @@ -504,7 +504,7 @@ impl CreateArgs { sh_println!("Starting contract verification...")?; let num_of_optimizations = if let Some(optimizer) = self.build.compiler.optimize { - if optimizer { Some(self.build.compiler.optimizer_runs.unwrap_or(200)) } else { None } + optimizer.then(|| self.build.compiler.optimizer_runs.unwrap_or(200)) } else { self.build.compiler.optimizer_runs }; @@ -672,7 +672,7 @@ impl + Clone> DeploymentTxFactory { /// Creates a factory for deployment of the Contract with bytecode, and the /// constructor defined in the abi. The client will be used to send any deployment /// transaction. - pub fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self { + pub const fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self { Self { client, abi, bytecode, timeout, _network: PhantomData } } diff --git a/crates/forge/src/cmd/doc/mod.rs b/crates/forge/src/cmd/doc/mod.rs index 602284ce94ebf..957636111a302 100644 --- a/crates/forge/src/cmd/doc/mod.rs +++ b/crates/forge/src/cmd/doc/mod.rs @@ -133,7 +133,7 @@ impl DocArgs { } /// Returns whether watch mode is enabled - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } diff --git a/crates/forge/src/cmd/doc/server.rs b/crates/forge/src/cmd/doc/server.rs index f8d447d52b1fb..d9a83adda8721 100644 --- a/crates/forge/src/cmd/doc/server.rs +++ b/crates/forge/src/cmd/doc/server.rs @@ -38,13 +38,13 @@ impl Server { } /// Set the port to serve on. - pub fn with_port(mut self, port: usize) -> Self { + pub const fn with_port(mut self, port: usize) -> Self { self.port = port; self } /// Set whether to open the browser after serving. - pub fn open(mut self, open: bool) -> Self { + pub const fn open(mut self, open: bool) -> Self { self.open = open; self } diff --git a/crates/forge/src/cmd/eip712.rs b/crates/forge/src/cmd/eip712.rs index 74bbb039cd507..a635820e66a9c 100644 --- a/crates/forge/src/cmd/eip712.rs +++ b/crates/forge/src/cmd/eip712.rs @@ -100,7 +100,7 @@ pub struct Resolver<'gcx> { impl<'gcx> Resolver<'gcx> { /// Constructs a new [`Resolver`] for the supplied [`Hir`] instance. - pub fn new(gcx: Gcx<'gcx>) -> Self { + pub const fn new(gcx: Gcx<'gcx>) -> Self { Self { gcx } } diff --git a/crates/forge/src/cmd/fmt.rs b/crates/forge/src/cmd/fmt.rs index 159f8d87c2a2e..b476cf97a130e 100644 --- a/crates/forge/src/cmd/fmt.rs +++ b/crates/forge/src/cmd/fmt.rs @@ -216,7 +216,7 @@ impl FmtArgs { } /// Returns whether `FmtArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } } diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index 139fd17b0cb96..4eedd0f8f6b08 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -251,15 +251,15 @@ fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> { for func in abi.functions.values().flatten() { let selector = func.selector().to_string(); let state_mut = func.state_mutability.as_json_str(); - let func_sig = if !func.outputs.is_empty() { + let func_sig = if func.outputs.is_empty() { + format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) + } else { format!( "{}({}) {state_mut} returns ({})", func.name, get_ty_sig(&func.inputs), get_ty_sig(&func.outputs) ) - } else { - format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) }; table.add_row(["function", &func_sig, &selector]); } @@ -370,7 +370,7 @@ fn print_method_identifiers( headers, |table| { for (method, identifier) in method_identifiers { - table.add_row([method, identifier]); + table.add_row([method.as_str(), identifier.as_str()]); } }, should_wrap, @@ -679,8 +679,7 @@ impl PartialEq for ContractArtifactField { type Eos = EvmOutputSelection; matches!( (self, other), - (Self::Abi | Self::Events, Cos::Abi) - | (Self::Errors, Cos::Abi) + (Self::Abi | Self::Events | Self::Errors, Cos::Abi) | (Self::Bytecode, Cos::Evm(Eos::ByteCode(_))) | (Self::DeployedBytecode, Cos::Evm(Eos::DeployedByteCode(_))) | (Self::Assembly | Self::AssemblyOptimized, Cos::Evm(Eos::Assembly)) diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index 81fa24312640f..3fcad958a6cc0 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -567,7 +567,7 @@ impl Installer<'_> { sh_println!("[{i}] {c} selected")?; return Ok(c.clone()); } - _ => continue, + _ => {} } } } diff --git a/crates/forge/src/cmd/selectors.rs b/crates/forge/src/cmd/selectors.rs index a0c319fe9c947..252544e7ea5e8 100644 --- a/crates/forge/src/cmd/selectors.rs +++ b/crates/forge/src/cmd/selectors.rs @@ -214,7 +214,7 @@ impl SelectorsSubcommands { .filter_map(|(k1, v1)| { second_method_map .iter() - .find_map(|(k2, v2)| if **v2 == *v1 { Some((k2, v2)) } else { None }) + .find_map(|(k2, v2)| (**v2 == *v1).then_some((k2, v2))) .map(|(k2, v2)| (v2, k1, k2)) }) .collect(); @@ -286,7 +286,7 @@ impl SelectorsSubcommands { .collect() }; - let mut artifacts = artifacts.into_iter().peekable(); + let mut artifacts = artifacts.into_iter(); #[derive(PartialEq, PartialOrd, Eq, Ord)] enum SelectorType { diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index 1a37bd02aa4eb..c8dc2ba72aae1 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -89,7 +89,7 @@ pub struct GasSnapshotArgs { impl GasSnapshotArgs { /// Returns whether `GasSnapshotArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.test.is_watch() } @@ -181,7 +181,7 @@ enum DiffSortOrder { } impl GasSnapshotConfig { - fn is_in_gas_range(&self, gas_used: u64) -> bool { + const fn is_in_gas_range(&self, gas_used: u64) -> bool { if let Some(min) = self.min && gas_used < min { @@ -355,7 +355,7 @@ impl GasSnapshotDiff { /// /// `> 0` if the source used more gas /// `< 0` if the target used more gas - fn gas_change(&self) -> i128 { + const fn gas_change(&self) -> i128 { self.source_gas_used.gas() as i128 - self.target_gas_used.gas() as i128 } @@ -418,7 +418,7 @@ fn diff( let mut diffs = Vec::with_capacity(tests.len()); let mut new_tests = Vec::new(); - for test in tests.into_iter() { + for test in tests { if let Some(target_gas_used) = snaps.get(&(test.contract_name().to_string(), test.signature.clone())).cloned() { diff --git a/crates/forge/src/cmd/test/filter.rs b/crates/forge/src/cmd/test/filter.rs index f39535a97bb92..c9219ec504d6a 100644 --- a/crates/forge/src/cmd/test/filter.rs +++ b/crates/forge/src/cmd/test/filter.rs @@ -46,7 +46,7 @@ pub struct FilterArgs { impl FilterArgs { /// Returns true if the filter is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.test_pattern.is_none() && self.test_pattern_inverse.is_none() && self.contract_pattern.is_none() @@ -176,22 +176,22 @@ pub struct ProjectPathsAwareFilter { impl ProjectPathsAwareFilter { /// Returns true if the filter is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.args_filter.is_empty() } /// Returns the CLI arguments. - pub fn args(&self) -> &FilterArgs { + pub const fn args(&self) -> &FilterArgs { &self.args_filter } /// Returns the CLI arguments mutably. - pub fn args_mut(&mut self) -> &mut FilterArgs { + pub const fn args_mut(&mut self) -> &mut FilterArgs { &mut self.args_filter } /// Returns the project paths. - pub fn paths(&self) -> &ProjectPathsConfig { + pub const fn paths(&self) -> &ProjectPathsConfig { &self.paths } } diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index c0a2da8cd528b..b9c1588b45072 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -505,10 +505,11 @@ impl TestArgs { let num_filtered = runner.matching_test_functions(filter).count(); if num_filtered == 0 { - let mut total_tests = num_filtered; - if !filter.is_empty() { - total_tests = runner.matching_test_functions(&EmptyTestFilter::default()).count(); - } + let total_tests = if filter.is_empty() { + num_filtered + } else { + runner.matching_test_functions(&EmptyTestFilter::default()).count() + }; if total_tests == 0 { sh_println!( "No tests found in project! Forge looks for functions that start with `test`" @@ -827,14 +828,9 @@ impl TestArgs { .iter() .filter_map(|(k, v)| { previous_snapshots.get(k).and_then(|previous_snapshot| { - if previous_snapshot != v { - Some(( - k.clone(), - (previous_snapshot.clone(), v.clone()), - )) - } else { - None - } + (previous_snapshot != v).then(|| { + (k.clone(), (previous_snapshot.clone(), v.clone())) + }) }) }) .collect(); @@ -958,7 +954,7 @@ impl TestArgs { } /// Returns whether `BuildArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } @@ -1051,7 +1047,7 @@ fn persist_run_failures(config: &Config, outcome: &TestOutcome) { let mut failures = outcome.failures().peekable(); while let Some((test_name, _)) = failures.next() { if test_name.is_any_test() - && let Some(test_match) = test_name.split("(").next() + && let Some(test_match) = test_name.split('(').next() { filter.push_str(test_match); if failures.peek().is_some() { diff --git a/crates/forge/src/cmd/test/summary.rs b/crates/forge/src/cmd/test/summary.rs index 2b3e29ff0ccc1..f8a72272af53c 100644 --- a/crates/forge/src/cmd/test/summary.rs +++ b/crates/forge/src/cmd/test/summary.rs @@ -17,7 +17,7 @@ pub struct TestSummaryReport { } impl TestSummaryReport { - pub fn new(is_detailed: bool, outcome: TestOutcome) -> Self { + pub const fn new(is_detailed: bool, outcome: TestOutcome) -> Self { Self { is_detailed, outcome } } } diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 040f51d587bf3..f7a20443b2dd3 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -115,7 +115,7 @@ pub struct LcovReporter { impl LcovReporter { /// Create a new LCOV reporter. - pub fn new(path: PathBuf, version: Version) -> Self { + pub const fn new(path: PathBuf, version: Version) -> Self { Self { path, version } } } @@ -270,7 +270,7 @@ pub struct BytecodeReporter { } impl BytecodeReporter { - pub fn new(root: PathBuf, destdir: PathBuf) -> Self { + pub const fn new(root: PathBuf, destdir: PathBuf) -> Self { Self { root, destdir } } } diff --git a/crates/forge/src/lockfile.rs b/crates/forge/src/lockfile.rs index e64cab6ff53a1..4f0f091076bb9 100644 --- a/crates/forge/src/lockfile.rs +++ b/crates/forge/src/lockfile.rs @@ -39,7 +39,7 @@ impl<'a> Lockfile<'a> { } /// Set the git instance to be used for submodule operations. - pub fn with_git(mut self, git: &'a Git<'_>) -> Self { + pub const fn with_git(mut self, git: &'a Git<'_>) -> Self { self.git = Some(git); self } @@ -302,7 +302,7 @@ impl DepIdentifier { } /// Marks as dependency as overridden. - pub fn mark_override(&mut self) { + pub const fn mark_override(&mut self) { match self { Self::Branch { r#override, .. } => *r#override = true, Self::Tag { r#override, .. } => *r#override = true, @@ -311,7 +311,7 @@ impl DepIdentifier { } /// Returns whether the dependency has been overridden. - pub fn overridden(&self) -> bool { + pub const fn overridden(&self) -> bool { match self { Self::Branch { r#override, .. } => *r#override, Self::Tag { r#override, .. } => *r#override, @@ -320,7 +320,7 @@ impl DepIdentifier { } /// Returns whether the dependency is a branch. - pub fn is_branch(&self) -> bool { + pub const fn is_branch(&self) -> bool { matches!(self, Self::Branch { .. }) } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index fd9b4b889c2e2..a3d105af848eb 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -246,11 +246,11 @@ impl MultiContractRunner { progress: Option<&TestsProgress>, ) -> SuiteResult { let identifier = artifact_id.identifier(); - let mut span_name = identifier.as_str(); - - if !enabled!(tracing::Level::TRACE) { - span_name = get_contract_name(&identifier); - } + let span_name = if enabled!(tracing::Level::TRACE) { + identifier.as_str() + } else { + get_contract_name(&identifier) + }; let span = debug_span!("suite", name = %span_name); let span_local = span.clone(); let _guard = span_local.enter(); @@ -440,12 +440,12 @@ impl MultiContractRunnerBuilder { } } - pub fn sender(mut self, sender: Address) -> Self { + pub const fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self } - pub fn initial_balance(mut self, initial_balance: U256) -> Self { + pub const fn initial_balance(mut self, initial_balance: U256) -> Self { self.initial_balance = initial_balance; self } @@ -455,27 +455,27 @@ impl MultiContractRunnerBuilder { self } - pub fn set_coverage(mut self, enable: bool) -> Self { + pub const fn set_coverage(mut self, enable: bool) -> Self { self.line_coverage = enable; self } - pub fn set_debug(mut self, enable: bool) -> Self { + pub const fn set_debug(mut self, enable: bool) -> Self { self.debug = enable; self } - pub fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { + pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { self.decode_internal = mode; self } - pub fn fail_fast(mut self, fail_fast: bool) -> Self { + pub const fn fail_fast(mut self, fail_fast: bool) -> Self { self.fail_fast = fail_fast; self } - pub fn enable_isolation(mut self, enable: bool) -> Self { + pub const fn enable_isolation(mut self, enable: bool) -> Self { self.isolation = enable; self } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 37388e65f5bcd..66be289ef252d 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -51,7 +51,7 @@ pub struct TestOutcome { impl TestOutcome { /// Creates a new test outcome with the given results. - pub fn new( + pub const fn new( known_contracts: Option, results: BTreeMap, allow_failure: bool, @@ -68,7 +68,7 @@ impl TestOutcome { } /// Creates a new empty test outcome. - pub fn empty(known_contracts: Option, allow_failure: bool) -> Self { + pub const fn empty(known_contracts: Option, allow_failure: bool) -> Self { Self::new(known_contracts, BTreeMap::new(), allow_failure, None) } @@ -391,19 +391,19 @@ pub enum TestStatus { impl TestStatus { /// Returns `true` if the test was successful. #[inline] - pub fn is_success(self) -> bool { + pub const fn is_success(self) -> bool { matches!(self, Self::Success) } /// Returns `true` if the test failed. #[inline] - pub fn is_failure(self) -> bool { + pub const fn is_failure(self) -> bool { matches!(self, Self::Failure) } /// Returns `true` if the test was skipped. #[inline] - pub fn is_skipped(self) -> bool { + pub const fn is_skipped(self) -> bool { matches!(self, Self::Skipped) } } @@ -784,7 +784,7 @@ impl TestResult { } /// Returns `true` if this is the result of a fuzz test - pub fn is_fuzz(&self) -> bool { + pub const fn is_fuzz(&self) -> bool { matches!(self.kind, TestKind::Fuzz { .. }) } @@ -877,7 +877,7 @@ impl fmt::Display for TestKindReport { impl TestKindReport { /// Returns the main gas value to compare against - pub fn gas(&self) -> u64 { + pub const fn gas(&self) -> u64 { match *self { Self::Unit { gas } => gas, // We use the median for comparisons @@ -924,12 +924,12 @@ impl Default for TestKind { impl TestKind { /// Returns `true` if this is a fuzz test. - pub fn is_fuzz(&self) -> bool { + pub const fn is_fuzz(&self) -> bool { matches!(self, Self::Fuzz { .. }) } /// Returns `true` if this is an invariant test. - pub fn is_invariant(&self) -> bool { + pub const fn is_invariant(&self) -> bool { matches!(self, Self::Invariant { .. }) } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 862fea4b6a22c..41cbaec88264e 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -32,6 +32,7 @@ use foundry_evm::{ invariant::{InvariantContract, InvariantSettings}, strategies::EvmFuzzState, }, + revm::primitives::hardfork::SpecId, traces::{TraceKind, TraceMode, load_contracts}, }; use itertools::Itertools; @@ -87,7 +88,7 @@ impl<'a, FEN: FoundryEvmNetwork> Deref for ContractRunner<'a, FEN> { } impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { - pub fn new( + pub const fn new( name: &'a str, contract: &'a TestContract, executor: Executor, @@ -358,10 +359,10 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { if setup.reason.is_some() { // The setup failed, so we return a single test result for `setUp` - let fail_msg = if !setup.deployment_failure { - "setUp()".to_string() - } else { + let fail_msg = if setup.deployment_failure { "constructor()".to_string() + } else { + "setUp()".to_string() }; return SuiteResult::new( start.elapsed(), @@ -500,7 +501,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { } } - fn revert_decoder(&self) -> &'a RevertDecoder { + const fn revert_decoder(&self) -> &'a RevertDecoder { &self.cr.mcr.revert_decoder } @@ -799,6 +800,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { invariant_contract.address, invariant_contract.invariant_function.selector().to_vec().into(), CheckSequenceOptions { + accumulate_warp_roll: invariant_config.has_delay(), fail_on_revert: invariant_config.fail_on_revert, call_after_invariant: invariant_contract.call_after_invariant, rd: Some(self.revert_decoder()), @@ -1096,7 +1098,8 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { address, &ITest::beforeTestSetupCall { testSelector: func.selector() }, ) { - debug!(?calldata, spec=%self.executor.spec_id(), "applying before_test_setup"); + let spec_id: SpecId = self.executor.spec_id().into(); + debug!(?calldata, spec=%spec_id, "applying before_test_setup"); // Apply before test configured calldata. match self.executor.to_mut().transact_raw( self.tcfg.sender, diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index 05e6288d96f0e..fd69907be2f09 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1155,7 +1155,7 @@ async fn ensure_lint_rule_docs() { let mut missing_lints = Vec::new(); for lint in REGISTERED_LINTS { let selector = lint.id().to_lowercase(); - let selector_with_space = selector.replace("-", " "); + let selector_with_space = selector.replace('-', " "); if !content.to_lowercase().contains(&selector) && !content.to_lowercase().contains(&selector_with_space) { diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index c2f92b9e95962..c026a09f69e8b 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1050,9 +1050,8 @@ forgetest_async!(check_broadcast_log, |prj, cmd| { let run_log = re.replace_all(&run_log, ""); // Clean up carriage return OS differences - let re = Regex::new(r"\r\n").unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, "\n"); - let run_log = re.replace_all(&run_log, "\n"); + let fixtures_log = fixtures_log.replace("\r\n", "\n"); + let run_log = run_log.replace("\r\n", "\n"); similar_asserts::assert_eq!(fixtures_log, run_log); }); @@ -3201,7 +3200,7 @@ contract CounterScript is Script { error: the following required arguments were not provided: --broadcast -Usage: [..] script --broadcast --verify --fork-url [ARGS]... +Usage: [..] script --broadcast --verify --rpc-url [ARGS]... For more information, try '--help'. diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs index 9f8450817eba8..8f71030bc0a7c 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/common.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -1653,46 +1653,39 @@ contract InvariantWarpAndRoll { } function invariant_warp() public view { - require(block.number < 200000, "max block"); + require(block.timestamp < 500000, "max timestamp"); } /// forge-config: default.invariant.show_solidity = true function invariant_roll() public view { - require(block.timestamp < 500000, "max timestamp"); + require(block.number < 200000, "max block"); } } "#, ); cmd.args(["test", "--mt", "invariant_warp"]).assert_failure().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for test/InvariantWarpAndRoll.t.sol:InvariantWarpAndRoll -[FAIL: max block] - [Sequence] (original: 6, shrunk: 6) - sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=6280 roll=21461 calldata=setNumber(uint256) args=[200000 [2e5]] +... +[FAIL: max timestamp] + [Sequence] (original: 5, shrunk: 5) + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=6280 roll=21461 calldata=setNumber(uint256) args=[500000 [5e5]] sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=92060 roll=51816 calldata=setNumber(uint256) args=[0] sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=198040 roll=60259 calldata=increment() args=[] sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=20609 roll=27086 calldata=setNumber(uint256) args=[26717227324157985679793128079000084308648530834088529513797156275625002 [2.671e70]] sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=409368 roll=24864 calldata=increment() args=[] - sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=218105 roll=17834 calldata=setNumber(uint256) args=[24752675372815722001736610830 [2.475e28]] invariant_warp() (runs: 0, calls: 0, reverts: 0) ... "#]]); cmd.forge_fuse().args(["test", "--mt", "invariant_roll"]).assert_failure().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for test/InvariantWarpAndRoll.t.sol:InvariantWarpAndRoll -[FAIL: max timestamp] - [Sequence] (original: 5, shrunk: 5) +... +[FAIL: max block] + [Sequence] (original: 6, shrunk: 6) vm.warp(block.timestamp + 6280); vm.roll(block.number + 21461); vm.prank([..]); - Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(200000); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(500000); vm.warp(block.timestamp + 92060); vm.roll(block.number + 51816); vm.prank([..]); @@ -1709,6 +1702,10 @@ Ran 1 test for test/InvariantWarpAndRoll.t.sol:InvariantWarpAndRoll vm.roll(block.number + 24864); vm.prank([..]); Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.warp(block.timestamp + 218105); + vm.roll(block.number + 17834); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(24752675372815722001736610830); invariant_roll() (runs: 0, calls: 0, reverts: 0) ... @@ -2069,3 +2066,89 @@ contract InvariantOptimizeWarpTest is Test { ... "#]]); }); + +// Regression test for delay-aware shrinking in check mode. +// Removed calls may still contribute warp/roll, so the final shrunk sequence must preserve those +// values in the remaining call. +forgetest_init!(invariant_shrink_preserves_warp_roll, |prj, cmd| { + prj.add_test( + "InvariantRollWarpShrink.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Roll { + uint256 public number; + + function increment() public { + require(block.number > 50000, "wrong block"); + number++; + } +} + +contract Warp { + uint256 public number; + + function increment() public { + require(block.timestamp > 500000, "wrong timestamp"); + number++; + } +} + +contract InvariantRoll is Test { + Roll public roll; + + function setUp() public { + roll = new Roll(); + } + + /// forge-config: default.fuzz.seed = "119" + /// forge-config: default.invariant.max_block_delay = 60480 + /// forge-config: default.invariant.show_solidity = true + function invariant_roll() public view { + require(roll.number() == 0, "number is not zero"); + } +} + +contract InvariantWarp is Test { + Warp public warp; + + function setUp() public { + warp = new Warp(); + } + + /// forge-config: default.fuzz.seed = "119" + /// forge-config: default.invariant.max_time_delay = 604800 + /// forge-config: default.invariant.show_solidity = true + function invariant_warp() public view { + require(warp.number() == 0, "max time"); + } +} +"#, + ); + + cmd.args(["test", "--mt", "invariant_roll"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: number is not zero] + [Sequence] (original: 3, shrunk: 1) + vm.roll(block.number + 52068); + vm.prank([..]); + Roll(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + invariant_roll() (runs: 0, calls: 0, reverts: 2) +... + +"#]]); + + cmd.forge_fuse().args(["test", "--mt", "invariant_warp"]).assert_failure().stdout_eq(str![[ + r#" +... +[FAIL: max time] + [Sequence] (original: 3, shrunk: 1) + vm.warp(block.timestamp + 656868); + vm.prank(0x00000000000000000000000000000000000012d2); + Warp(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + invariant_warp() (runs: 0, calls: 0, reverts: 2) +... + +"# + ]]); +}); diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/lint/src/linter/mod.rs b/crates/lint/src/linter/mod.rs index 355e61f3284ed..fc140643eb68a 100644 --- a/crates/lint/src/linter/mod.rs +++ b/crates/lint/src/linter/mod.rs @@ -62,7 +62,7 @@ pub struct LinterConfig<'s> { } impl<'s, 'c> LintContext<'s, 'c> { - pub fn new( + pub const fn new( sess: &'s Session, with_description: bool, with_json_emitter: bool, @@ -77,7 +77,7 @@ impl<'s, 'c> LintContext<'s, 'c> { if self.with_json_emitter { diag.help(help) } else { diag.help(hyperlink(help)) } } - pub fn session(&self) -> &'s Session { + pub const fn session(&self) -> &'s Session { self.sess } @@ -211,14 +211,14 @@ pub struct Suggestion { impl Suggestion { /// Creates a new [`SuggestionKind::Example`] suggestion. - pub fn example(content: String) -> Self { + pub const fn example(content: String) -> Self { Self { desc: None, content, kind: SuggestionKind::Example } } /// Creates a new [`SuggestionKind::Fix`] suggestion. /// /// When possible, will attempt to inline the suggestion. - pub fn fix(content: String, applicability: Applicability) -> Self { + pub const fn fix(content: String, applicability: Applicability) -> Self { Self { desc: None, content, @@ -231,13 +231,13 @@ impl Suggestion { } /// Sets the description for the suggestion. - pub fn with_desc(mut self, desc: &'static str) -> Self { + pub const fn with_desc(mut self, desc: &'static str) -> Self { self.desc = Some(desc); self } /// Sets the span for a [`SuggestionKind::Fix`] suggestion. - pub fn with_span(mut self, span: Span) -> Self { + pub const fn with_span(mut self, span: Span) -> Self { if let SuggestionKind::Fix { span: ref mut s, .. } = self.kind { *s = Some(span); } @@ -245,7 +245,7 @@ impl Suggestion { } /// Sets the style for a [`SuggestionKind::Fix`] suggestion. - pub fn with_style(mut self, style: SuggestionStyle) -> Self { + pub const fn with_style(mut self, style: SuggestionStyle) -> Self { if let SuggestionKind::Fix { style: ref mut s, .. } = self.kind { *s = style; } diff --git a/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs b/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs index 64f7b27134732..65d195e47ada5 100644 --- a/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs +++ b/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs @@ -79,7 +79,7 @@ impl UnwrappedModifierLogic { let (mut res, mut has_valid_stmt) = (false, false); for stmt in stmts { match &stmt.kind { - hir::StmtKind::Placeholder => continue, + hir::StmtKind::Placeholder => {} hir::StmtKind::Expr(expr) => { if !self.is_valid_expr(hir, expr) || has_valid_stmt { res = true; diff --git a/crates/lint/src/sol/gas/custom_errors.rs b/crates/lint/src/sol/gas/custom_errors.rs index e7dfa4ae1af56..b1b7a68310885 100644 --- a/crates/lint/src/sol/gas/custom_errors.rs +++ b/crates/lint/src/sol/gas/custom_errors.rs @@ -48,6 +48,6 @@ fn should_lint_require(args: &solar::ast::CallArgs<'_>) -> bool { } /// Checks if an expression is a string literal. -fn is_string_literal(expr: &Expr<'_>) -> bool { +const fn is_string_literal(expr: &Expr<'_>) -> bool { matches!(&expr.kind, ExprKind::Lit(lit, _) if matches!(lit.kind, solar::ast::LitKind::Str(..))) } diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index cb942510bbb49..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,103 +1,27 @@ use super::AsmKeccak256; use crate::{ - linter::{LateLintPass, LintContext}, + declare_forge_lint, + linter::EarlyLintPass, sol::{Severity, SolLint}, }; -use solar::{ - ast::{self as ast, Span}, - interface::kw, - sema::hir::{self}, -}; +use solar_ast::{Expr, ExprKind}; +use solar_interface::kw; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "use of inefficient hashing mechanism; consider using inline assembly" + "hash using inline assembly to save gas" ); -impl<'hir> LateLintPass<'hir> for AsmKeccak256 { - fn check_stmt( - &mut self, - ctx: &LintContext, - hir: &'hir hir::Hir<'hir>, - stmt: &'hir hir::Stmt<'hir>, - ) { - let check_expr_and_emit_lint = - |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { - if let Some(hash_arg) = extract_keccak256_arg(expr) { - self.emit_lint( - ctx, - hir, - stmt.span, - expr, - hash_arg, - AsmContext { _assign: assign, _is_return: is_return }, - ); - } - }; - - match stmt.kind { - hir::StmtKind::DeclSingle(var_id) => { - let var = hir.variable(var_id); - if let Some(init) = var.initializer { - // Constants should be optimized by the compiler, so no gas savings apply. - if !matches!(var.mutability, Some(hir::VarMut::Constant)) { - check_expr_and_emit_lint(init, var.name, false); - } +impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { + fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(expr, _) = &expr.kind { + if let ExprKind::Ident(ident) = &expr.kind { + if ident.name == kw::Keccak256 { + ctx.emit(&ASM_KECCAK256, expr.span); } } - // Expressions that don't (directly) assign to a variable - hir::StmtKind::Expr(expr) - | hir::StmtKind::Emit(expr) - | hir::StmtKind::Revert(expr) - | hir::StmtKind::DeclMulti(_, expr) - | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), - hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), - _ => (), } } } - -impl AsmKeccak256 { - /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. - fn emit_lint( - &self, - ctx: &LintContext, - _hir: &hir::Hir<'_>, - _stmt_span: Span, - call: &hir::Expr<'_>, - _hash: &hir::Expr<'_>, - _asm_ctx: AsmContext, - ) { - ctx.emit(&ASM_KECCAK256, call.span); - } -} - -/// If the expression is a call to `keccak256` with one argument, returns that argument. -fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { - let hir::ExprKind::Call( - callee, - hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, - .., - ) = &expr.kind - else { - return None; - }; - - let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { - matches!(builtin.name(), kw::Keccak256) - } else { - return None; - }; - - if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } -} - -// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- - -#[derive(Debug, Clone, Copy)] -struct AsmContext { - _assign: Option, - _is_return: bool, -} diff --git a/crates/lint/src/sol/high/incorrect_shift.rs b/crates/lint/src/sol/high/incorrect_shift.rs index 2ad60b9a793f7..8e7fcbf868109 100644 --- a/crates/lint/src/sol/high/incorrect_shift.rs +++ b/crates/lint/src/sol/high/incorrect_shift.rs @@ -29,7 +29,7 @@ impl<'ast> EarlyLintPass<'ast> for IncorrectShift { // TODO: come up with a better heuristic. Treat initial impl as a PoC. // Checks if the left operand is a literal and the right operand is not, indicating a potential // reversed shift operation. -fn contains_incorrect_shift<'ast>( +const fn contains_incorrect_shift<'ast>( left_expr: &'ast Expr<'ast>, right_expr: &'ast Expr<'ast>, ) -> bool { diff --git a/crates/lint/src/sol/high/unchecked_calls.rs b/crates/lint/src/sol/high/unchecked_calls.rs index df9747e1ff100..900a3793496a9 100644 --- a/crates/lint/src/sol/high/unchecked_calls.rs +++ b/crates/lint/src/sol/high/unchecked_calls.rs @@ -168,7 +168,7 @@ impl<'ast> Visit<'ast> for UncheckedCallChecker<'_, '_> { /// - `target.delegatecall(...)` /// - `target.staticcall(...)` /// - `target.call{value: x}(...)` -fn is_low_level_call(expr: &Expr<'_>) -> bool { +const fn is_low_level_call(expr: &Expr<'_>) -> bool { if let ExprKind::Call(call_expr, _args) = &expr.kind { // Check the callee expression let callee = match &call_expr.kind { diff --git a/crates/lint/src/sol/info/interface_naming.rs b/crates/lint/src/sol/info/interface_naming.rs index 06625a808f745..ba4bf13c48a81 100644 --- a/crates/lint/src/sol/info/interface_naming.rs +++ b/crates/lint/src/sol/info/interface_naming.rs @@ -3,7 +3,7 @@ use crate::{ sol::{Severity, SolLint, info::InterfaceFileNaming}, }; -use solar::ast::{self as ast}; +use solar::ast; declare_forge_lint!( INTERFACE_FILE_NAMING, diff --git a/crates/lint/src/sol/info/multi_contract_file.rs b/crates/lint/src/sol/info/multi_contract_file.rs index 34a0bc8f67904..7be1aaa983d71 100644 --- a/crates/lint/src/sol/info/multi_contract_file.rs +++ b/crates/lint/src/sol/info/multi_contract_file.rs @@ -3,7 +3,7 @@ use crate::{ sol::{Severity, SolLint, info::MultiContractFile}, }; -use solar::ast::{self as ast}; +use solar::ast; declare_forge_lint!( MULTI_CONTRACT_FILE, diff --git a/crates/lint/src/sol/info/screaming_snake_case.rs b/crates/lint/src/sol/info/screaming_snake_case.rs index a0d56c5a3cf34..ff3d5982b2fb3 100644 --- a/crates/lint/src/sol/info/screaming_snake_case.rs +++ b/crates/lint/src/sol/info/screaming_snake_case.rs @@ -56,9 +56,9 @@ pub fn check_screaming_snake_case(s: &str) -> Option { // Handle leading/trailing underscores like `heck` does let expected = format!( "{prefix}{name}{suffix}", - prefix = if s.starts_with("_") { "_" } else { "" }, + prefix = if s.starts_with('_') { "_" } else { "" }, name = heck::AsShoutySnakeCase(s), - suffix = if s.ends_with("_") { "_" } else { "" } + suffix = if s.ends_with('_') { "_" } else { "" } ); if s == expected { None } else { Some(expected) } } diff --git a/crates/lint/src/sol/med/unsafe_typecast.rs b/crates/lint/src/sol/med/unsafe_typecast.rs index 7ef4d44ea03e3..860e87b38a2f2 100644 --- a/crates/lint/src/sol/med/unsafe_typecast.rs +++ b/crates/lint/src/sol/med/unsafe_typecast.rs @@ -134,7 +134,7 @@ fn infer_source_types( } /// Checks if a type cast from source_type to target_type is unsafe. -fn is_unsafe_elementary_typecast( +const fn is_unsafe_elementary_typecast( source_type: &ElementaryType, target_type: &ElementaryType, ) -> bool { @@ -159,8 +159,7 @@ fn is_unsafe_elementary_typecast( } // Dynamic bytes to fixed bytes (potential truncation) - (ElementaryType::Bytes, ElementaryType::FixedBytes(_)) - | (ElementaryType::String, ElementaryType::FixedBytes(_)) => true, + (ElementaryType::Bytes | ElementaryType::String, ElementaryType::FixedBytes(_)) => true, // Address to smaller uint (truncation) - address is 160 bits (ElementaryType::Address(_), ElementaryType::UInt(target_size)) => target_size.bits() < 160, diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index f9afac2b00443..ba2e7f0d697bf 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -97,22 +97,22 @@ impl<'a> SolidityLinter<'a> { self } - pub fn with_description(mut self, with: bool) -> Self { + pub const fn with_description(mut self, with: bool) -> Self { self.with_description = with; self } - pub fn with_json_emitter(mut self, with: bool) -> Self { + pub const fn with_json_emitter(mut self, with: bool) -> Self { self.with_json_emitter = with; self } - pub fn with_lint_specific(mut self, lint_specific: &'a LintSpecificConfig) -> Self { + pub const fn with_lint_specific(mut self, lint_specific: &'a LintSpecificConfig) -> Self { self.lint_specific = lint_specific; self } - fn config(&'a self, inline: &'a InlineConfig>) -> LinterConfig<'a> { + const fn config(&'a self, inline: &'a InlineConfig>) -> LinterConfig<'a> { LinterConfig { inline, lint_specific: self.lint_specific } } @@ -147,7 +147,7 @@ impl<'a> SolidityLinter<'a> { .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { let included_ids: Vec<_> = lints .iter() - .filter_map(|lint| if self.include_lint(*lint) { Some(lint.id) } else { None }) + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) .collect(); if !included_ids.is_empty() { @@ -198,7 +198,7 @@ impl<'a> SolidityLinter<'a> { .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { let included_ids: Vec<_> = lints .iter() - .filter_map(|lint| if self.include_lint(*lint) { Some(lint.id) } else { None }) + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) .collect(); if !included_ids.is_empty() { diff --git a/crates/macros/src/cheatcodes.rs b/crates/macros/src/cheatcodes.rs index b6ac95df7120c..363c03c73c9e4 100644 --- a/crates/macros/src/cheatcodes.rs +++ b/crates/macros/src/cheatcodes.rs @@ -263,7 +263,7 @@ enum StructKind { } impl StructKind { - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Struct => "struct", Self::Error => "error", diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index e8a8e6319bd09..0c95841d1e653 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -81,9 +81,7 @@ fn derive_enum(e: &DataEnum) -> TokenStream { let fields: Punctuated = fields .enumerate() - .map(|(i, field)| { - field.ident.as_ref().cloned().unwrap_or_else(|| format_ident!("__var_{i}")) - }) + .map(|(i, field)| field.ident.clone().unwrap_or_else(|| format_ident!("__var_{i}"))) .collect(); if fields.len() != 1 { diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index 64b15d50318a1..721973d29badc 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -78,7 +78,7 @@ impl FoundryTxEnvelope { } } - pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> { + pub const fn sidecar(&self) -> Option<&TxEip4844WithSidecar> { match self { Self::Eip4844(signed_variant) => match signed_variant.tx() { TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar), @@ -107,7 +107,7 @@ impl FoundryTxEnvelope { } /// Returns `true` if this is a Tempo transaction. - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { matches!(self, Self::Tempo(_)) } diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs index 839ab77664202..4be6b42e4a35d 100644 --- a/crates/script-sequence/src/reader.rs +++ b/crates/script-sequence/src/reader.rs @@ -62,7 +62,7 @@ impl BroadcastReader { { // 1. Recursively read all .json files in the broadcast directory let mut broadcasts = vec![]; - for entry in walkdir::WalkDir::new(&self.broadcast_path).into_iter() { + for entry in walkdir::WalkDir::new(&self.broadcast_path) { let entry = entry?; let path = entry.path(); diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index fb0c01eb4fd1e..37c59fc47f17c 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -1,6 +1,7 @@ use crate::transaction::TransactionWithMetadata; -use alloy_network::{Network, ReceiptResponse}; +use alloy_network::ReceiptResponse; use alloy_primitives::{TxHash, hex, map::HashMap}; +use alloy_rpc_types_eth::TransactionReceipt; use eyre::{ContextCompat, Result, WrapErr}; use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, fs, shell}; use foundry_compilers::ArtifactId; @@ -11,7 +12,7 @@ use std::{ path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; - +# pub const DRY_RUN_DIR: &str = "dry-run"; #[derive(Clone, Serialize, Deserialize)] @@ -19,29 +20,13 @@ pub struct NestedValue { pub internal_type: String, pub value: String, } - -/// Sensitive values from the transactions in a script sequence -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveTransactionMetadata { - pub rpc: String, -} - -/// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveScriptSequence { - pub transactions: VecDeque, -} - +# /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound( - serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", - deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" -))] -pub struct ScriptSequence { - pub transactions: VecDeque>, - pub receipts: Vec, +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct ScriptSequence { + pub transactions: VecDeque, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] @@ -54,24 +39,20 @@ pub struct ScriptSequence { pub commit: Option, } -impl Default for ScriptSequence { - fn default() -> Self { - Self { - transactions: Default::default(), - receipts: Default::default(), - libraries: Default::default(), - pending: Default::default(), - paths: Default::default(), - returns: Default::default(), - timestamp: Default::default(), - chain: Default::default(), - commit: Default::default(), - } - } +/// Sensitive values from the transactions in a script sequence +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveTransactionMetadata { + pub rpc: String, +} + +/// Sensitive info from the script sequence which is saved into the cache folder +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveScriptSequence { + pub transactions: VecDeque, } -impl From<&ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &ScriptSequence) -> Self { +impl From<&ScriptSequence> for SensitiveScriptSequence { + fn from(sequence: &ScriptSequence) -> Self { Self { transactions: sequence .transactions @@ -82,7 +63,7 @@ impl From<&ScriptSequence> for SensitiveScriptSequence { } } -impl ScriptSequence { +impl ScriptSequence { /// Loads The sequence for the corresponding json file pub fn load( config: &Config, @@ -90,10 +71,7 @@ impl ScriptSequence { target: &ArtifactId, chain_id: u64, dry_run: bool, - ) -> Result - where - N::TxEnvelope: for<'d> Deserialize<'d>, - { + ) -> Result { let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = fs::read_json_file(&path) @@ -114,10 +92,7 @@ impl ScriptSequence { /// Saves the transactions as file if it's a standalone deployment. /// `save_ts` should be set to true for checkpoint updates, which might happen many times and /// could result in us saving many identical files. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> - where - N::TxEnvelope: Serialize, - { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { self.sort_receipts(); if self.transactions.is_empty() { @@ -166,10 +141,10 @@ impl ScriptSequence { Ok(()) } - pub fn add_receipt(&mut self, receipt: N::ReceiptResponse) { + pub fn add_receipt(&mut self, receipt: TransactionReceipt) { self.receipts.push(receipt); } - + # /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { self.receipts.sort_by_key(|r| (r.block_number(), r.transaction_index())); @@ -185,7 +160,7 @@ impl ScriptSequence { pub fn remove_pending(&mut self, tx_hash: TxHash) { self.pending.retain(|element| element != &tx_hash); } - + # /// Gets paths in the formats /// `./broadcast/[contract_filename]/[chain_id]/[sig]-latest.json` and /// `./cache/[contract_filename]/[chain_id]/[sig]-latest.json`. @@ -196,8 +171,8 @@ impl ScriptSequence { chain_id: u64, dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = config.broadcast.clone(); - let mut cache = config.cache_path.clone(); + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); let mut common = PathBuf::new(); let target_fname = target.source.file_name().wrap_err("No filename.")?; @@ -229,7 +204,7 @@ impl ScriptSequence { } /// Returns the list of the transactions without the metadata. - pub fn transactions(&self) -> impl Iterator> { + pub fn transactions(&self) -> impl Iterator { self.transactions.iter().map(|tx| tx.tx()) } @@ -240,7 +215,7 @@ impl ScriptSequence { .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } - +# /// Converts the `sig` argument into the corresponding file path. /// /// This accepts either the signature of the function or the raw calldata. diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs index 3e4b46d3fa327..da4448228e117 100644 --- a/crates/script-sequence/src/transaction.rs +++ b/crates/script-sequence/src/transaction.rs @@ -43,15 +43,15 @@ pub struct TransactionWithMetadata { pub is_fixed_gas_limit: bool, } -fn default_string() -> Option { +const fn default_string() -> Option { Some(String::new()) } -fn default_address() -> Option
{ +const fn default_address() -> Option
{ Some(Address::ZERO) } -fn default_vec_of_strings() -> Option> { +const fn default_vec_of_strings() -> Option> { Some(vec![]) } @@ -71,11 +71,11 @@ impl TransactionWithMetadata { } } - pub fn tx(&self) -> &TransactionMaybeSigned { + pub const fn tx(&self) -> &TransactionMaybeSigned { &self.transaction } - pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { + pub const fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { &mut self.transaction } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 381867dde73dd..4146e07d2248b 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -19,6 +19,7 @@ foundry-cli.workspace = true foundry-config.workspace = true foundry-common.workspace = true foundry-evm.workspace = true +foundry-evm-networks.workspace = true alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 2fd7ab6537a39..045da94168e34 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -345,14 +345,18 @@ impl BundledState { .chain(self.browser_wallet.as_ref().map(|b| b.address())) .collect(); - // For addresses without an explicit signer, try Tempo access key lookup. + // For addresses without an explicit signer, try Tempo keys.toml fallback. let mut access_keys: AddressHashMap<(WalletSigner, TempoAccessKeyConfig)> = AddressHashMap::default(); + let mut direct_signers: AddressHashMap = AddressHashMap::default(); let mut missing_addresses = Vec::new(); for addr in &required_addresses { if !signers.contains(addr) { match lookup_signer(*addr) { + Ok(TempoLookup::Direct(signer)) => { + direct_signers.insert(*addr, signer); + } Ok(TempoLookup::Keychain(signer, config)) => { access_keys.insert(*addr, (signer, *config)); } @@ -372,8 +376,11 @@ impl BundledState { } let signers = self.script_wallets.into_multi_wallet().into_signers()?; - let eth_wallets = + let mut eth_wallets: AddressHashMap = signers.into_iter().map(|(addr, signer)| (addr, signer.into())).collect(); + for (addr, signer) in direct_signers { + eth_wallets.insert(addr, signer.into()); + } SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; @@ -486,8 +493,9 @@ impl BundledState { }) .collect::>>()?; - let estimate_via_rpc = - has_different_gas_calc(sequence.chain) || self.args.skip_simulation; + let estimate_via_rpc = has_different_gas_calc(sequence.chain) + || self.script_config.evm_opts.networks.is_tempo() + || self.args.skip_simulation; // We only wait for a transaction receipt before sending the next transaction, if // there is more than one signer. There would be no way of assuring diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index bf8cfcddf6f64..f1462bb868f58 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -107,7 +107,7 @@ pub enum ScriptPredeployLibraries { } impl ScriptPredeployLibraries { - pub fn libraries_count(&self) -> usize { + pub const fn libraries_count(&self) -> usize { match self { Self::Default(libs) => libs.len(), Self::Create2(libs, _) => libs.len(), @@ -292,7 +292,15 @@ impl CompiledState { }; let (args, build_data, script_wallets, browser_wallet, script_config) = - if !self.args.unlocked { + if self.args.unlocked { + ( + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, + ) + } else { let mut froms = sequence.sequences().iter().flat_map(|s| { s.transactions .iter() @@ -305,7 +313,15 @@ impl CompiledState { .signers() .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; - if !froms.all(|from| available_signers.contains(&from)) { + if froms.all(|from| available_signers.contains(&from)) { + ( + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, + ) + } else { // IF we are missing required signers, execute script as we might need to // collect private keys from the execution. let executed = self.link().await?.prepare_execution().await?.execute().await?; @@ -316,23 +332,7 @@ impl CompiledState { executed.browser_wallet, executed.script_config, ) - } else { - ( - self.args, - self.build_data, - self.script_wallets, - self.browser_wallet, - self.script_config, - ) } - } else { - ( - self.args, - self.build_data, - self.script_wallets, - self.browser_wallet, - self.script_config, - ) }; // Collect libraries from sequence and link contracts with them. diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 1da825accded9..162d061462b66 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -232,8 +232,7 @@ impl RpcData { /// Iterates over script transactions and collects RPC urls. fn from_transactions(txs: &BroadcastableTransactions) -> Self { let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - let total_rpcs = - txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); + let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::>(); Self { total_rpcs, missing_rpc } } @@ -379,10 +378,10 @@ impl ExecutedState { ty: "unknown".to_string(), }); - let label = if !output.name.is_empty() { - output.name.clone() - } else { + let label = if output.name.is_empty() { index.to_string() + } else { + output.name.clone() }; returns.insert( @@ -477,10 +476,10 @@ impl PreSimulationState { ty: "unknown".to_string(), }); - let label = if !output.name.is_empty() { - output.name.clone() - } else { + let label = if output.name.is_empty() { index.to_string() + } else { + output.name.clone() }; sh_println!( "{label}: {internal_type} {value}", diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 4ba84333874bc..d8d83913edb40 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -59,6 +59,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{TraceMode, Traces}, }; +use foundry_evm_networks::NetworkConfigs; use foundry_wallets::MultiWalletOpts; use serde::Serialize; use std::path::PathBuf; @@ -249,8 +250,13 @@ impl ScriptArgs { async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - // Auto-detect network from fork chain ID when not explicitly configured. - evm_opts.infer_network_from_fork().await; + if self.fee_token.is_some() { + // If fee token is set directly select tempo + evm_opts.networks = NetworkConfigs::with_tempo(); + } else { + // Auto-detect network from fork chain ID when not explicitly configured. + evm_opts.infer_network_from_fork().await; + } Ok((config, evm_opts)) } @@ -289,6 +295,7 @@ impl ScriptArgs { } /// Executes the script + #[allow(clippy::large_stack_frames)] pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); @@ -323,6 +330,7 @@ impl ScriptArgs { /// Prepares the bundled state (compile, simulate, bundle) and returns it /// for broadcasting, or returns `None` if there's nothing to broadcast /// (e.g., debug mode, no transactions, missing RPCs). + #[allow(clippy::large_stack_frames)] async fn prepare_bundled( self, config: Config, @@ -532,7 +540,6 @@ impl ScriptArgs { bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); unknown_c += 1; } - continue; } let mut prompt_user = false; @@ -588,7 +595,7 @@ impl ScriptArgs { } /// We only broadcast transactions if --broadcast, --resume, or --verify was passed. - fn should_broadcast(&self) -> bool { + const fn should_broadcast(&self) -> bool { self.broadcast || self.resume || self.verify } } diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index dc6f8755d042b..c066a026e0a0f 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -27,7 +27,7 @@ pub enum TxStatus { impl From for TxStatus { fn from(receipt: R) -> Self { - if !receipt.status() { Self::Revert(receipt) } else { Self::Success(receipt) } + if receipt.status() { Self::Success(receipt) } else { Self::Revert(receipt) } } } diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index f07b4b7a3168d..015a411c8bd97 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -29,7 +29,7 @@ pub struct ScriptRunner { } impl ScriptRunner { - pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self { + pub const fn new(executor: Executor, evm_opts: EvmOpts) -> Self { Self { executor, evm_opts } } @@ -177,10 +177,7 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions) = if !setup { - self.executor.backend_mut().set_test_contract(address); - (true, 0, Default::default(), Some(library_transactions)) - } else { + let (success, gas_used, labeled_addresses, transactions) = if setup { match self.executor.setup(Some(self.evm_opts.sender), address, None) { Ok(RawCallResult { reverted, @@ -221,6 +218,9 @@ impl ScriptRunner { } Err(e) => return Err(e.into()), } + } else { + self.executor.backend_mut().set_test_contract(address); + (true, 0, Default::default(), Some(library_transactions)) }; Ok(( @@ -395,9 +395,11 @@ impl ScriptRunner { self.executor.tx_env_mut().set_gas_limit(mid_gas_limit); let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?; match res.exit_reason { - Some(InstructionResult::Revert) - | Some(InstructionResult::OutOfGas) - | Some(InstructionResult::OutOfFunds) => { + Some( + InstructionResult::Revert + | InstructionResult::OutOfGas + | InstructionResult::OutOfFunds, + ) => { lowest_gas_limit = mid_gas_limit; } _ => { diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 2d942670af066..05834ed8bb036 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,21 +9,15 @@ use crate::{ execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, }; -use alloy_chains::NamedChain; -use alloy_evm::revm::context::Block; use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; +use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{ContractData, shell}; -use foundry_evm::{ - core::{FoundryBlock, evm::FoundryEvmNetwork}, - traces::{decode_trace_arena, render_trace_arena}, -}; -use foundry_wallets::wallet_browser::signer::BrowserSigner; +use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -37,24 +31,23 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState { +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result> { + pub async fn fill_metadata(self) -> Result { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -71,7 +64,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if to.is_some() { + if let Some(TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -95,7 +88,6 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -108,8 +100,8 @@ impl PreSimulationState { /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque>, - ) -> Result>> { + transactions: VecDeque, + ) -> Result> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -129,7 +121,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = tx.to(); + let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() @@ -147,8 +139,7 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); - runner.executor.evm_env_mut().block_env.set_number(block_number); + runner.executor.env_mut().evm_env.block_env.number += U256::from(1); } let is_noop_tx = if let Some(to) = to { @@ -235,7 +226,7 @@ impl PreSimulationState { } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result)>> { + async fn build_runners(&self) -> Result> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { @@ -257,23 +248,22 @@ impl PreSimulationState { /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque>, + pub transactions: VecDeque, } -impl FilledTransactionsState { +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result> { + pub async fn bundle(mut self) -> Result { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -284,7 +274,7 @@ impl FilledTransactionsState { // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::::default(); + let mut manager = ProvidersManager::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a @@ -292,7 +282,7 @@ impl FilledTransactionsState { let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable(); while let Some(mut tx) = txes_iter.next() { - let tx_rpc = tx.rpc.clone(); + let tx_rpc = tx.rpc.to_owned(); let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; if let Some(tx) = tx.tx_mut().as_unsigned_mut() { @@ -307,7 +297,7 @@ impl FilledTransactionsState { // only estimate gas for unsigned transactions if let Some(tx) = tx.as_unsigned_mut() { trace!("estimating with different gas calculation"); - let gas = tx.gas_limit().expect("gas is set by simulation."); + let gas = tx.gas.expect("gas is set by simulation."); // We are trying to show the user an estimation of the total gas usage. // @@ -361,12 +351,6 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); - // Get the native token symbol for the chain using NamedChain - let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() - .native_currency_symbol() - .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -390,7 +374,7 @@ impl FilledTransactionsState { sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; sh_println!("\n==========================")?; } else { sh_println!( @@ -400,7 +384,6 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, - "token_symbol": token_symbol, }) )?; } @@ -423,7 +406,6 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -434,14 +416,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque>, - ) -> Result> { + transactions: VecDeque, + ) -> Result { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::::get_paths( + Some(ScriptSequence::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 0a2b171b379ba..ef10a1ce94082 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -78,7 +78,7 @@ impl VerifyBundle { cache_path: Some(project.paths.cache.clone()), lib_paths: project.paths.libraries.clone(), hardhat: config.profile == Config::HARDHAT_PROFILE, - config_path: if config_path.exists() { Some(config_path) } else { None }, + config_path: config_path.exists().then_some(config_path), }; let via_ir = config.via_ir; @@ -201,14 +201,14 @@ async fn verify_contracts( for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) { // create2 hash offset - let mut offset = 0; - - if tx.is_create2() + let offset = if tx.is_create2() && let Some(contract_address) = tx.contract_address { receipt.set_contract_address(contract_address); - offset = 32; - } + 32 + } else { + 0 + }; // Verify contract created directly from the transaction if let (Some(address), Some(data)) = (receipt.contract_address(), tx.tx().input()) { diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 6d98a26d79cb3..5921f020e2337 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -29,7 +29,7 @@ pub struct SolMacroGen { } impl SolMacroGen { - pub fn new(path: PathBuf, name: String) -> Self { + pub const fn new(path: PathBuf, name: String) -> Self { Self { path, name, expansion: None } } @@ -53,7 +53,7 @@ pub struct MultiSolMacroGen { } impl MultiSolMacroGen { - pub fn new(instances: Vec) -> Self { + pub const fn new(instances: Vec) -> Self { Self { instances } } @@ -238,7 +238,12 @@ edition = "2021" for instance in &self.instances { let name = instance.name.to_snake_case(); - if !single_file { + if single_file { + // Single File + let mut contents = String::new(); + write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; + write!(mod_contents, "{contents}")?; + } else { // Module write_mod_name(&mut mod_contents, &name)?; let mut contents = String::new(); @@ -249,11 +254,6 @@ edition = "2021" let contents = prettyplease::unparse(&file); fs::write(bindings_path.join(format!("{name}.rs")), contents) .wrap_err("Failed to write file")?; - } else { - // Single File - let mut contents = String::new(); - write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; - write!(mod_contents, "{contents}")?; } } diff --git a/crates/test-utils/src/ext.rs b/crates/test-utils/src/ext.rs index b14cf32a9dd3f..ba4f8421353b3 100644 --- a/crates/test-utils/src/ext.rs +++ b/crates/test-utils/src/ext.rs @@ -34,13 +34,13 @@ impl ExtTester { } /// Sets the path style. - pub fn style(mut self, style: PathStyle) -> Self { + pub const fn style(mut self, style: PathStyle) -> Self { self.style = style; self } /// Sets the fork block. - pub fn fork_block(mut self, fork_block: u64) -> Self { + pub const fn fork_block(mut self, fork_block: u64) -> Self { self.fork_block = Some(fork_block); self } diff --git a/crates/test-utils/src/prj.rs b/crates/test-utils/src/prj.rs index 47227a4298428..96fa065317bec 100644 --- a/crates/test-utils/src/prj.rs +++ b/crates/test-utils/src/prj.rs @@ -79,13 +79,13 @@ impl RemoteProject { } /// Whether to run `forge build` - pub fn set_build(mut self, run_build: bool) -> Self { + pub const fn set_build(mut self, run_build: bool) -> Self { self.run_build = run_build; self } /// Configures the project's pathstyle - pub fn path_style(mut self, path_style: PathStyle) -> Self { + pub const fn path_style(mut self, path_style: PathStyle) -> Self { self.path_style = path_style; self } @@ -545,7 +545,7 @@ pub struct TestCommand { impl TestCommand { /// Returns a mutable reference to the underlying command. - pub fn cmd(&mut self) -> &mut Command { + pub const fn cmd(&mut self) -> &mut Command { &mut self.cmd } @@ -768,7 +768,7 @@ impl TestCommand { } /// Does not apply [`snapbox`] redactions to the command output. - pub fn with_no_redact(&mut self) -> &mut Self { + pub const fn with_no_redact(&mut self) -> &mut Self { self.redact_output = false; self } diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 9f5f502c241d1..07413fef5495c 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } @@ -224,11 +247,12 @@ impl ScriptTester { trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - assert!( - !(!stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str())), - "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", - expected.as_str() - ); + if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { + panic!( + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); + } self } diff --git a/crates/test-utils/src/ui_runner.rs b/crates/test-utils/src/ui_runner.rs index 8ae0af75fd458..c23d4b5ae08c9 100644 --- a/crates/test-utils/src/ui_runner.rs +++ b/crates/test-utils/src/ui_runner.rs @@ -116,7 +116,7 @@ fn config<'a>( } let stdout_filters: &[(&str, &str)] = - &[(&env!("CARGO_PKG_VERSION").replace(".", r"\."), "VERSION")]; + &[(&env!("CARGO_PKG_VERSION").replace('.', r"\."), "VERSION")]; for &(pattern, replacement) in stdout_filters { config.stdout_filter(pattern, replacement); } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 75c68b278fcf1..d22e12736d832 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -174,7 +174,7 @@ pub fn get_vyper() -> Vyper { let path = VYPER.as_path(); let mut file = File::create(path).unwrap(); if let Err(e) = file.try_lock() { - if let fs::TryLockError::WouldBlock = e { + if matches!(e, fs::TryLockError::WouldBlock) { file.lock().unwrap(); assert!(path.exists()); return Vyper::new(path).unwrap(); diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index e603540a4c336..731779714776f 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -111,9 +111,9 @@ Diagnostics: {diags}", /// sanitized variant of the specific version so that it can be installed. This is merely /// intended to ensure the flattened code can be compiled without errors. fn strip_build_meta(version: Version) -> Version { - if version.build != BuildMetadata::EMPTY { - Version::new(version.major, version.minor, version.patch) - } else { + if version.build == BuildMetadata::EMPTY { version + } else { + Version::new(version.major, version.minor, version.patch) } } diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index bc074a3fe4c2f..7f0afd972c5bf 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -281,11 +281,11 @@ impl EtherscanVerificationProvider { builder = if let Some(api_url) = api_url { // we don't want any trailing slashes because this can cause cloudflare issues: let api_url = api_url.trim_end_matches('/'); - let base_url = if !is_etherscan { + let base_url = if is_etherscan { + base_url.unwrap_or(api_url) + } else { // If verifier is not Etherscan then set base url as api url without /api suffix. api_url.strip_suffix("/api").unwrap_or(api_url) - } else { - base_url.unwrap_or(api_url) }; builder.with_api_url(api_url)?.with_url(base_url)? } else { diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 848d5808fd77b..bb0549b9ad19e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -2,6 +2,8 @@ use super::{EtherscanSourceProvider, VerifyArgs}; use crate::{provider::VerificationContext, verify::ContractLanguage}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; +use foundry_compilers::artifacts::{Source, vyper::VyperInput}; +use std::path::Path; #[derive(Debug)] pub struct EtherscanStandardJsonSource; @@ -24,7 +26,14 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { serde_json::to_string(&input).wrap_err("Failed to parse standard json input")? } ContractLanguage::Vyper => { - let input = context.get_vyper_standard_json_input()?; + let path = Path::new(&context.target_path); + let sources = Source::read_all_from(path, &["vy", "vyi"])?; + let input = VyperInput::new( + sources, + context.compiler_settings.vyper.clone(), + &context.compiler_version, + ); + serde_json::to_string(&input).wrap_err("Failed to parse vyper json input")? } }; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index e59d712d6811c..01c1f6b829195 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -232,11 +232,11 @@ impl VerificationProviderType { ) } - pub fn is_sourcify(&self) -> bool { + pub const fn is_sourcify(&self) -> bool { matches!(self, Self::Sourcify) } - pub fn is_etherscan(&self) -> bool { + pub const fn is_etherscan(&self) -> bool { matches!(self, Self::Etherscan) } } diff --git a/crates/verify/src/retry.rs b/crates/verify/src/retry.rs index 4bc934149175f..7df2826ed8714 100644 --- a/crates/verify/src/retry.rs +++ b/crates/verify/src/retry.rs @@ -37,7 +37,7 @@ impl Default for RetryArgs { impl RetryArgs { /// Converts the arguments into a `Retry` instance. - pub fn into_retry(self) -> Retry { + pub const fn into_retry(self) -> Retry { Retry::new(self.retries, Duration::from_secs(self.delay as u64)) } } diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index a02dadf563922..9d4cacc97591a 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -43,12 +43,12 @@ pub enum BytecodeType { impl BytecodeType { /// Check if the bytecode type is creation - pub fn is_creation(&self) -> bool { + pub const fn is_creation(&self) -> bool { matches!(self, Self::Creation) } /// Check if the bytecode type is runtime - pub fn is_runtime(&self) -> bool { + pub const fn is_runtime(&self) -> bool { matches!(self, Self::Runtime) } } @@ -107,15 +107,15 @@ pub fn print_result( config: &Config, ) { if let Some(res) = res { - if !shell::is_json() { + if shell::is_json() { + let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; + json_results.push(json_res); + } else { let _ = sh_println!( "{} with status {}", format!("{bytecode_type:?} code matched").green().bold(), res.green().bold() ); - } else { - let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; - json_results.push(json_res); } } else if !shell::is_json() { let _ = sh_err!( @@ -397,10 +397,10 @@ pub fn is_host_only(url: &Url) -> bool { /// assert_ne!(version.build, BuildMetadata::EMPTY); /// ``` pub async fn ensure_solc_build_metadata(version: Version) -> Result { - if version.build != BuildMetadata::EMPTY { - Ok(version) - } else { + if version.build == BuildMetadata::EMPTY { Ok(lookup_compiler_version(&version).await?) + } else { + Ok(version) } } diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index 029da52cfdf86..a0f9628956469 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -57,19 +57,19 @@ pub enum WalletSignerError { } impl WalletSignerError { - pub fn aws_unsupported() -> Self { + pub const fn aws_unsupported() -> Self { Self::UnsupportedSigner("AWS KMS") } - pub fn gcp_unsupported() -> Self { + pub const fn gcp_unsupported() -> Self { Self::UnsupportedSigner("Google Cloud KMS") } - pub fn turnkey_unsupported() -> Self { + pub const fn turnkey_unsupported() -> Self { Self::UnsupportedSigner("Turnkey") } - pub fn browser_unsupported() -> Self { + pub const fn browser_unsupported() -> Self { Self::UnsupportedSigner("Browser Wallet") } } diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index f5282496ed0ca..6849125e3f084 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -124,9 +124,7 @@ pub fn create_keystore_signer( (Some(password), _) => Ok(Some(password.to_string())), (_, Some(password_file)) => { let password_file = Path::new(password_file); - if !password_file.is_file() { - Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) - } else { + if password_file.is_file() { Ok(Some( fs::read_to_string(password_file) .wrap_err_with(|| { @@ -135,6 +133,8 @@ pub fn create_keystore_signer( .trim_end() .to_string(), )) + } else { + Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) } } (None, None) => Ok(None), diff --git a/crates/wallets/src/wallet_browser/server.rs b/crates/wallets/src/wallet_browser/server.rs index ae5aaefb29444..0361416640c3a 100644 --- a/crates/wallets/src/wallet_browser/server.rs +++ b/crates/wallets/src/wallet_browser/server.rs @@ -87,17 +87,17 @@ impl BrowserWalletServer { } /// Get the server port. - pub fn port(&self) -> u16 { + pub const fn port(&self) -> u16 { self.port } /// Check if the browser should be opened. - pub fn open_browser(&self) -> bool { + pub const fn open_browser(&self) -> bool { self.open_browser } /// Get the timeout duration. - pub fn timeout(&self) -> Duration { + pub const fn timeout(&self) -> Duration { self.timeout } diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs index 5a7ad7fe4bee9..55f3bad5a4394 100644 --- a/crates/wallets/src/wallet_browser/signer.rs +++ b/crates/wallets/src/wallet_browser/signer.rs @@ -86,7 +86,7 @@ impl BrowserSigner { Ok(tx_hash) } - pub fn address(&self) -> Address { + pub const fn address(&self) -> Address { self.address } } diff --git a/crates/wallets/src/wallet_browser/state.rs b/crates/wallets/src/wallet_browser/state.rs index 0fa38da9cc110..c7c9859ebdcf4 100644 --- a/crates/wallets/src/wallet_browser/state.rs +++ b/crates/wallets/src/wallet_browser/state.rs @@ -52,7 +52,7 @@ impl BrowserWalletState { /// This relaxes certain security restrictions for local development. /// /// **WARNING**: This should only be used in a development environment. - pub fn is_development(&self) -> bool { + pub const fn is_development(&self) -> bool { self.development } diff --git a/crates/wallets/src/wallet_browser/types.rs b/crates/wallets/src/wallet_browser/types.rs index da4ac924a2b41..3e37498bb1e19 100644 --- a/crates/wallets/src/wallet_browser/types.rs +++ b/crates/wallets/src/wallet_browser/types.rs @@ -17,14 +17,14 @@ pub(crate) enum BrowserApiResponse { impl BrowserApiResponse<()> { /// Create a successful response with no data. - pub fn ok() -> Self { + pub const fn ok() -> Self { Self::Ok(()) } } impl BrowserApiResponse { /// Create a successful response with the given data. - pub fn with_data(data: T) -> Self { + pub const fn with_data(data: T) -> Self { Self::Ok(data) } @@ -121,7 +121,7 @@ pub struct Connection { impl Connection { /// Create a new connection instance. - pub fn new(address: Address, chain_id: ChainId) -> Self { + pub const fn new(address: Address, chain_id: ChainId) -> Self { Self { address, chain_id } } } diff --git a/crates/wallets/src/wallet_multi/mod.rs b/crates/wallets/src/wallet_multi/mod.rs index 3ad8e9edbc70d..a6d85581e19f0 100644 --- a/crates/wallets/src/wallet_multi/mod.rs +++ b/crates/wallets/src/wallet_multi/mod.rs @@ -292,14 +292,14 @@ impl MultiWalletOpts { pks.push(pk); } } - if !pks.is_empty() { + if pks.is_empty() { + Ok(None) + } else { let wallets = pks .into_iter() .map(|pk| utils::create_private_key_signer(pk)) .collect::>>()?; Ok(Some(wallets)) - } else { - Ok(None) } } diff --git a/deny.toml b/deny.toml index 641b5cd5175bd..a78f212fc8720 100644 --- a/deny.toml +++ b/deny.toml @@ -9,6 +9,8 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained "RUSTSEC-2025-0141", + # https://rustsec.org/advisories/RUSTSEC-2026-0097 rand is unsound with a custom logger + "RUSTSEC-2026-0097", ] # This section is considered when running `cargo deny check bans`. diff --git a/flake.lock b/flake.lock index 4663480be03f1..3b3ca69bf658b 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1775287186, - "narHash": "sha256-hYntDpbh8MuiYRmBf/6uHMpDOP2m7L7bXQXboWPg6WM=", + "lastModified": 1775891769, + "narHash": "sha256-EOfVlTKw2n8w1uhfh46GS4hEGnQ7oWrIWQfIY6utIkI=", "owner": "nix-community", "repo": "fenix", - "rev": "2517c7fb1eafc7259bb631267f1e1f813cf5f3bc", + "rev": "6fbc54dde15aee725bdc7aae5e478849685d5f56", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1775126147, - "narHash": "sha256-J0dZU4atgcfo4QvM9D92uQ0Oe1eLTxBVXjJzdEMQpD0=", + "lastModified": 1775888245, + "narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8d8c1fa5b412c223ffa47410867813290cdedfef", + "rev": "13043924aaa7375ce482ebe2494338e058282925", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1775228522, - "narHash": "sha256-+6eTD6EAabjow5gdjWRP6aI2UUwOZJEjzzsvvbVu8f8=", + "lastModified": 1775843361, + "narHash": "sha256-j53ZgyDvmYf3Sjh1IPvvTjqa614qUfVQSzj59+MpzkY=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "f4b77dc99d9925667246e2887783b79bdc46a50d", + "rev": "9eb97ea96d8400e8957ddd56702e962614296583", "type": "github" }, "original": { diff --git a/npm/package.json b/npm/package.json index 2362816053f8c..d596a7930b609 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.0.2", "bun": "^1.3.1", "typescript": "^5.9.3" }, diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** From 571d2e84fdb7e9a894e19ed4acd737b7f96a6ccd Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:09:18 +0700 Subject: [PATCH 287/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/forge/tests/cli/install.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index ae9582b9b1fa0..d61f134c5a6a5 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -620,14 +620,20 @@ forgetest!(flaky_install_no_git_cleans_nested_submodules, |prj, cmd| { // There should be no .git file or directory anywhere under the installed dependency. fn assert_no_git(dir: &Path, root: &Path) { + let canonical_root = root.canonicalize().unwrap(); for entry in fs::read_dir(dir).unwrap() { let entry = entry.unwrap(); let path = entry.path(); if path.file_name() == Some(".git".as_ref()) { panic!("found leftover .git at {}", path.display()); } - if path.is_dir() && path.starts_with(root) { - assert_no_git(&path, root); + + let metadata = fs::symlink_metadata(&path).unwrap(); + if metadata.is_dir() && !metadata.file_type().is_symlink() { + let canonical_path = path.canonicalize().unwrap(); + if canonical_path.starts_with(&canonical_root) { + assert_no_git(&path, root); + } } } } From e14aa93fa7b5e981a6647b39d2c47ceaa183022a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:33:56 +0700 Subject: [PATCH 288/391] chore(deps): bump DeterminateSystems/update-flake-lock (#419) Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from e80a657d7603606be0c69b117cfdc240f1e6af88 to ff43f160ef7014ae1a1fd85699fb6a44f436135b. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/e80a657d7603606be0c69b117cfdc240f1e6af88...ff43f160ef7014ae1a1fd85699fb6a44f436135b) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: ff43f160ef7014ae1a1fd85699fb6a44f436135b dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index e3541cd07c446..f6c01d65adfcc 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: DeterminateSystems/update-flake-lock@e80a657d7603606be0c69b117cfdc240f1e6af88 # main + - uses: DeterminateSystems/update-flake-lock@ff43f160ef7014ae1a1fd85699fb6a44f436135b # main with: pr-title: "Update flake.lock" pr-labels: | From 780a8c8a987da2a94bd6a3d58e2269334a429a9a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:25:14 +0700 Subject: [PATCH 289/391] Delete .circleci/config.yml (#420) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4168efef0971f..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello - From f76d97b48b7d6b4da35fa8a8c1c7e8f88a33fc83 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:37:11 +0700 Subject: [PATCH 290/391] Delete .circleci directory (#421) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 32 ---------------- .circleci/ci-web3-gamefi.yml | 26 ------------- .circleci/ci.yml | 31 --------------- .circleci/ci_cargo.yml | 37 ------------------ .circleci/ci_deploy.yml | 34 ----------------- .circleci/ci_v1.yml | 31 --------------- .circleci/dev_stage.yml | 70 ---------------------------------- .circleci/web3_defi_gamefi.yml | 26 ------------- 8 files changed, 287 deletions(-) delete mode 100644 .circleci/cargo.yml delete mode 100644 .circleci/ci-web3-gamefi.yml delete mode 100644 .circleci/ci.yml delete mode 100644 .circleci/ci_cargo.yml delete mode 100644 .circleci/ci_deploy.yml delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/dev_stage.yml delete mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 32b65e6a23cc5..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 2.1 -# -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml deleted file mode 100644 index ad53a8e498202..0000000000000 --- a/.circleci/ci-web3-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index 1b5df6d6e668e..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/ci_cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml deleted file mode 100644 index 5ba351727d22b..0000000000000 --- a/.circleci/dev_stage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- - - jobs: - my-job: - steps: - - run: echo "Hello, world!" - - run: - command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" - max_auto_reruns: 3 - auto_rerun_delay: 10s - - workflows: - dev_stage_pre-prod: - jobs: - - test_dev: - filters: # using regex filters requires the entire branch to match - branches: - only: # only branches matching the below regex filters will run - - dev - - /user-.*/ - - test_stage: - filters: - branches: - only: stage - - test_pre-prod: - filters: - branches: - only: /pre-prod(?:-.+)?$/ - - - build-test-deploy: - jobs: - - build: - filters: # required since `test` has tag filters AND requires `build` - tags: - only: /^config-test.*/ - - test: - requires: - - build - filters: # required since `deploy` has tag filters AND requires `test` - tags: - only: /^config-test.*/ - - deploy: - requires: - - test - filters: - tags: - only: /^config-test.*/ - branches: - ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/web3_defi_gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From cb0f6ed4120826489c8492c78f981f8e57a10989 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:40:18 +0700 Subject: [PATCH 291/391] Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 32 ---------------- .circleci/ci-web3-gamefi.yml | 26 ------------- .circleci/ci.yml | 31 --------------- .circleci/ci_cargo.yml | 37 ------------------ .circleci/ci_deploy.yml | 34 ----------------- .circleci/ci_v1.yml | 31 --------------- .circleci/dev_stage.yml | 70 ---------------------------------- .circleci/web3_defi_gamefi.yml | 26 ------------- 8 files changed, 287 deletions(-) delete mode 100644 .circleci/cargo.yml delete mode 100644 .circleci/ci-web3-gamefi.yml delete mode 100644 .circleci/ci.yml delete mode 100644 .circleci/ci_cargo.yml delete mode 100644 .circleci/ci_deploy.yml delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/dev_stage.yml delete mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 32b65e6a23cc5..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 2.1 -# -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml deleted file mode 100644 index ad53a8e498202..0000000000000 --- a/.circleci/ci-web3-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index 1b5df6d6e668e..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/ci_cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml deleted file mode 100644 index 5ba351727d22b..0000000000000 --- a/.circleci/dev_stage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- - - jobs: - my-job: - steps: - - run: echo "Hello, world!" - - run: - command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" - max_auto_reruns: 3 - auto_rerun_delay: 10s - - workflows: - dev_stage_pre-prod: - jobs: - - test_dev: - filters: # using regex filters requires the entire branch to match - branches: - only: # only branches matching the below regex filters will run - - dev - - /user-.*/ - - test_stage: - filters: - branches: - only: stage - - test_pre-prod: - filters: - branches: - only: /pre-prod(?:-.+)?$/ - - - build-test-deploy: - jobs: - - build: - filters: # required since `test` has tag filters AND requires `build` - tags: - only: /^config-test.*/ - - test: - requires: - - build - filters: # required since `deploy` has tag filters AND requires `test` - tags: - only: /^config-test.*/ - - deploy: - requires: - - test - filters: - tags: - only: /^config-test.*/ - branches: - ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/web3_defi_gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From c6eb37f9d99d15bfbda4cc6308a10b20c6026d3c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:50:46 +0700 Subject: [PATCH 292/391] Delete .circleci directory (#423) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 32 ---------------- .circleci/ci-web3-gamefi.yml | 26 ------------- .circleci/ci.yml | 31 --------------- .circleci/ci_cargo.yml | 37 ------------------ .circleci/ci_deploy.yml | 34 ----------------- .circleci/ci_v1.yml | 31 --------------- .circleci/config.yml | 32 ---------------- .circleci/dev_stage.yml | 70 ---------------------------------- .circleci/web3_defi_gamefi.yml | 26 ------------- 9 files changed, 319 deletions(-) delete mode 100644 .circleci/cargo.yml delete mode 100644 .circleci/ci-web3-gamefi.yml delete mode 100644 .circleci/ci.yml delete mode 100644 .circleci/ci_cargo.yml delete mode 100644 .circleci/ci_deploy.yml delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/dev_stage.yml delete mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 32b65e6a23cc5..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 2.1 -# -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml deleted file mode 100644 index ad53a8e498202..0000000000000 --- a/.circleci/ci-web3-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index 1b5df6d6e668e..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/ci_cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4168efef0971f..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello - diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml deleted file mode 100644 index 5ba351727d22b..0000000000000 --- a/.circleci/dev_stage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- - - jobs: - my-job: - steps: - - run: echo "Hello, world!" - - run: - command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" - max_auto_reruns: 3 - auto_rerun_delay: 10s - - workflows: - dev_stage_pre-prod: - jobs: - - test_dev: - filters: # using regex filters requires the entire branch to match - branches: - only: # only branches matching the below regex filters will run - - dev - - /user-.*/ - - test_stage: - filters: - branches: - only: stage - - test_pre-prod: - filters: - branches: - only: /pre-prod(?:-.+)?$/ - - - build-test-deploy: - jobs: - - build: - filters: # required since `test` has tag filters AND requires `build` - tags: - only: /^config-test.*/ - - test: - requires: - - build - filters: # required since `deploy` has tag filters AND requires `test` - tags: - only: /^config-test.*/ - - deploy: - requires: - - test - filters: - tags: - only: /^config-test.*/ - branches: - ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/web3_defi_gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From 1bf6b7acf63d47a580fffd831b838318be981fd8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:30:07 +0700 Subject: [PATCH 293/391] Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 32 ---------------- .circleci/ci-web3-gamefi.yml | 26 ------------- .circleci/ci.yml | 31 --------------- .circleci/ci_cargo.yml | 37 ------------------ .circleci/ci_v1.yml | 31 --------------- .circleci/config.yml | 32 ---------------- .circleci/dev_stage.yml | 70 ---------------------------------- .circleci/web3_defi_gamefi.yml | 26 ------------- 8 files changed, 285 deletions(-) delete mode 100644 .circleci/cargo.yml delete mode 100644 .circleci/ci-web3-gamefi.yml delete mode 100644 .circleci/ci.yml delete mode 100644 .circleci/ci_cargo.yml delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/dev_stage.yml delete mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 32b65e6a23cc5..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 2.1 -# -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml deleted file mode 100644 index ad53a8e498202..0000000000000 --- a/.circleci/ci-web3-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index 1b5df6d6e668e..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/ci_cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4168efef0971f..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello - diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml deleted file mode 100644 index 5ba351727d22b..0000000000000 --- a/.circleci/dev_stage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- - - jobs: - my-job: - steps: - - run: echo "Hello, world!" - - run: - command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" - max_auto_reruns: 3 - auto_rerun_delay: 10s - - workflows: - dev_stage_pre-prod: - jobs: - - test_dev: - filters: # using regex filters requires the entire branch to match - branches: - only: # only branches matching the below regex filters will run - - dev - - /user-.*/ - - test_stage: - filters: - branches: - only: stage - - test_pre-prod: - filters: - branches: - only: /pre-prod(?:-.+)?$/ - - - build-test-deploy: - jobs: - - build: - filters: # required since `test` has tag filters AND requires `build` - tags: - only: /^config-test.*/ - - test: - requires: - - build - filters: # required since `deploy` has tag filters AND requires `test` - tags: - only: /^config-test.*/ - - deploy: - requires: - - test - filters: - tags: - only: /^config-test.*/ - branches: - ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/web3_defi_gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From d5c743f6fbcd40c3ed4e8f38b33246b91e0130be Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:42:58 +0700 Subject: [PATCH 294/391] Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/cargo.yml | 32 ---------------- .circleci/ci-web3-gamefi.yml | 26 ------------- .circleci/ci.yml | 31 --------------- .circleci/ci_cargo.yml | 37 ------------------ .circleci/ci_v1.yml | 31 --------------- .circleci/config.yml | 32 ---------------- .circleci/dev_stage.yml | 70 ---------------------------------- .circleci/web3_defi_gamefi.yml | 26 ------------- 8 files changed, 285 deletions(-) delete mode 100644 .circleci/cargo.yml delete mode 100644 .circleci/ci-web3-gamefi.yml delete mode 100644 .circleci/ci.yml delete mode 100644 .circleci/ci_cargo.yml delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/dev_stage.yml delete mode 100644 .circleci/web3_defi_gamefi.yml diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml deleted file mode 100644 index 32b65e6a23cc5..0000000000000 --- a/.circleci/cargo.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 2.1 -# -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml deleted file mode 100644 index ad53a8e498202..0000000000000 --- a/.circleci/ci-web3-gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml deleted file mode 100644 index 1b5df6d6e668e..0000000000000 --- a/.circleci/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml deleted file mode 100644 index 46a18d45a5fca..0000000000000 --- a/.circleci/ci_cargo.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.88.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4168efef0971f..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/reference/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello - diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml deleted file mode 100644 index 5ba351727d22b..0000000000000 --- a/.circleci/dev_stage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- - - jobs: - my-job: - steps: - - run: echo "Hello, world!" - - run: - command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" - max_auto_reruns: 3 - auto_rerun_delay: 10s - - workflows: - dev_stage_pre-prod: - jobs: - - test_dev: - filters: # using regex filters requires the entire branch to match - branches: - only: # only branches matching the below regex filters will run - - dev - - /user-.*/ - - test_stage: - filters: - branches: - only: stage - - test_pre-prod: - filters: - branches: - only: /pre-prod(?:-.+)?$/ - - - build-test-deploy: - jobs: - - build: - filters: # required since `test` has tag filters AND requires `build` - tags: - only: /^config-test.*/ - - test: - requires: - - build - filters: # required since `deploy` has tag filters AND requires `test` - tags: - only: /^config-test.*/ - - deploy: - requires: - - test - filters: - tags: - only: /^config-test.*/ - branches: - ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml deleted file mode 100644 index edb6605e3f101..0000000000000 --- a/.circleci/web3_defi_gamefi.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference - -version: 2.1 -executors: - my-custom-executor: - docker: - - image: cimg/base:stable - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD -jobs: - web3-defi-game-project-: - - executor: my-custom-executor - steps: - - checkout - - run: | - # echo Hello, World! - -workflows: - my-custom-workflow: - jobs: - - web3-defi-game-project- From 88968f8d792e3612fa777011ceccef7c2cf65775 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:50:00 +0700 Subject: [PATCH 295/391] Revert "benches\LATEST.md (#350)" (#425) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. From c06c8603ef040a3eb89aab84fffbf979324add66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:35:15 +0000 Subject: [PATCH 296/391] chore(deps): bump actions/upload-pages-artifact from 3 to 4.0.0 Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.0.0. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- .github/workflows/static.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d15e9ca56b220..e8c0425176f25 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,7 +42,7 @@ jobs: uses: actions/configure-pages@v6 - name: Upload artifact if: github.ref_name == 'master' && github.event_name == 'push' - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: ./target/doc diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 0ba82305f82b2..4218958d38db5 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: # Upload entire repository path: '.' From 5345641085a448758322a15942b495593a9900a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:54:31 +0700 Subject: [PATCH 297/391] chore(deps): bump google-github-actions/get-gke-credentials (#417) Bumps [google-github-actions/get-gke-credentials](https://github.com/google-github-actions/get-gke-credentials) from 2.2.1 to 3.0.0. - [Release notes](https://github.com/google-github-actions/get-gke-credentials/releases) - [Changelog](https://github.com/google-github-actions/get-gke-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/get-gke-credentials/compare/6051de21ad50fbb1767bc93c11357a49082ad116...3da1e46a907576cefaa90c484278bb5b259dd395) --- updated-dependencies: - dependency-name: google-github-actions/get-gke-credentials dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/google.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml index 1295e430ca96a..569c7b00a4b7f 100644 --- a/.github/workflows/google.yml +++ b/.github/workflows/google.yml @@ -83,7 +83,7 @@ jobs: # Get the GKE credentials so we can deploy to the cluster - name: 'Set up GKE credentials' - uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + uses: 'google-github-actions/get-gke-credentials@3da1e46a907576cefaa90c484278bb5b259dd395' # google-github-actions/get-gke-credentials@v2 with: cluster_name: '${{ env.GKE_CLUSTER }}' location: '${{ env.GKE_ZONE }}' From 59e9169c268e2f2742a4c1905da61684376ff6a5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:27:28 +0700 Subject: [PATCH 298/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From e9dc73115b745638824972729feee41c108e2fdd Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:30:23 +0000 Subject: [PATCH 299/391] Revert 350 dargon789/gamefi (#424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * Forge/master (#376) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar Co-authored-by: googleworkspace-bot * chore(deps): bump the cargo group across 1 directory with 9 updates (#377) Bumps the cargo group with 3 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing), [keccak](https://github.com/RustCrypto/sponges) and [slab](https://github.com/tokio-rs/slab). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `time` from 0.3.41 to 0.3.47 - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.41...v0.3.47) Updates `alloy-dyn-abi` from 1.3.0 to 1.5.7 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.3.0...v1.5.7) Updates `bytes` from 1.10.1 to 1.11.1 - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.1...v1.11.1) Updates `keccak` from 0.1.5 to 0.1.6 - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) Updates `lru` from 0.12.5 to 0.16.3 - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.3) Updates `protobuf` from 3.3.0 to 3.7.2 Updates `ruint` from 1.15.0 to 1.17.2 - [Release notes](https://github.com/recmo/uint/releases) - [Changelog](https://github.com/recmo/uint/blob/main/CHANGELOG.md) - [Commits](https://github.com/recmo/uint/compare/v1.15.0...v1.17.2) Updates `slab` from 0.4.10 to 0.4.12 - [Release notes](https://github.com/tokio-rs/slab/releases) - [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.12) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: time dependency-version: 0.3.47 dependency-type: direct:production dependency-group: cargo - dependency-name: alloy-dyn-abi dependency-version: 1.5.7 dependency-type: direct:production dependency-group: cargo - dependency-name: bytes dependency-version: 1.11.1 dependency-type: direct:production dependency-group: cargo - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect dependency-group: cargo - dependency-name: lru dependency-version: 0.16.3 dependency-type: indirect dependency-group: cargo - dependency-name: protobuf dependency-version: 3.7.2 dependency-type: indirect dependency-group: cargo - dependency-name: ruint dependency-version: 1.17.2 dependency-type: indirect dependency-group: cargo - dependency-name: slab dependency-version: 0.4.12 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create static.yml (#381) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Hardhat project (#389) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot * Change branch trigger from 'main' to 'master' (#403) https://github.com/Dargon789/hardhat-project/issues/2073 https://github.com/Dargon789/hardhat-project/pull/2078/commits/a98e69d5803ec208947f592c91749f248e2dd07c Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revise Foundry benchmark results and system info (#408) Updated benchmark results for Foundry versions v1.3.6 and v1.4.0-rc1, including new test data and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "benches\LATEST.md (#350)" (#409) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. * Update Docker.yml * chore(deps): bump DeterminateSystems/update-flake-lock (#419) Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from e80a657d7603606be0c69b117cfdc240f1e6af88 to ff43f160ef7014ae1a1fd85699fb6a44f436135b. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/e80a657d7603606be0c69b117cfdc240f1e6af88...ff43f160ef7014ae1a1fd85699fb6a44f436135b) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: ff43f160ef7014ae1a1fd85699fb6a44f436135b dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/config.yml (#420) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#421) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "benches\LATEST.md (#350)" This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. * Update crates/config/spec/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update benches/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/test-utils/src/script.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Desant pivo Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: zerosnacks Co-authored-by: googleworkspace-bot --- .circleci/ci_deploy.yml | 34 ----------------------------- .github/workflows/nix.yml | 2 +- benches/src/lib.rs | 1 + crates/cli/src/utils/suggestions.rs | 2 +- crates/common/src/contracts.rs | 2 +- crates/config/spec/src/lib.rs | 3 +-- crates/lint/src/linter.rs | 2 +- crates/test-utils/src/script.rs | 1 + 8 files changed, 7 insertions(+), 40 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index e3541cd07c446..f6c01d65adfcc 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: DeterminateSystems/update-flake-lock@e80a657d7603606be0c69b117cfdc240f1e6af88 # main + - uses: DeterminateSystems/update-flake-lock@ff43f160ef7014ae1a1fd85699fb6a44f436135b # main with: pr-title: "Update flake.lock" pr-labels: | diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ab3bec614e3cd..516d73531007a 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -127,6 +127,7 @@ impl BenchmarkProject { let root = root_path.to_str().unwrap(); // Remove all files in the directory + let root_path = root_path.canonicalize()?; for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs index 5a362e963d956..77a3bb7ab11c2 100644 --- a/crates/config/spec/src/lib.rs +++ b/crates/config/spec/src/lib.rs @@ -40,8 +40,7 @@ mod tests { /// Checks that the `file` has the specified `contents`. If that is not the /// case, updates the file and then fails the test. fn ensure_file_contents(file: &Path, contents: &str) { - if let Ok(old_contents) = fs::read_to_string(file) - && normalize_newlines(&old_contents) == normalize_newlines(contents) + if fs::read_to_string(file).map(|old| normalize_newlines(&old) == normalize_newlines(contents)).unwrap_or(false) { // File is already up to date. return; diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..ca543dbc1fe83 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index c1a6cb53bdbff..21d5c36272670 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -120,6 +120,7 @@ impl ScriptTester { let from_dir = testdata.join("utils"); let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; + let from_dir = from_dir.canonicalize()?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); // Only operate on regular files to avoid following symlinks or directories From 0732d4cb24df139bffa7c7c2bb27e24c25447240 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:35:33 +0700 Subject: [PATCH 300/391] Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index c133fa01291c4..f6a753ebfd727 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -648,7 +648,7 @@ impl From for DenyLevel { impl DenyLevel { /// Returns `true` if the deny level includes warnings. - pub fn warnings(&self) -> bool { + pub const fn warnings(&self) -> bool { match self { Self::Never => false, Self::Warnings | Self::Notes => true, From 4706fb0079a6566758755f1912b308c96f3cdee3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:36:03 +0700 Subject: [PATCH 301/391] Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f6a753ebfd727..41b73b24d98c8 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -657,7 +657,7 @@ impl DenyLevel { /// Returns `true` if the deny level includes notes. pub fn notes(&self) -> bool { - match self { + pub const fn notes(&self) -> bool { Self::Never | Self::Warnings => false, Self::Notes => true, } From 48f901c1dc7d22fd1f5f8c546ef97178e27114f3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:36:23 +0700 Subject: [PATCH 302/391] Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 41b73b24d98c8..96e0d78680ade 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -664,7 +664,7 @@ impl DenyLevel { } /// Returns `true` if the deny level is set to never (only errors). - pub fn never(&self) -> bool { + pub const fn never(&self) -> bool { match self { Self::Never => true, Self::Warnings | Self::Notes => false, From 2b0d78659a5f65c14c473c90e27ae304d1e1657c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:36:51 +0700 Subject: [PATCH 303/391] Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 96e0d78680ade..a061745771171 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1335,7 +1335,7 @@ impl Config { /// /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of /// `auto_detect_solc` - pub fn is_auto_detect(&self) -> bool { + pub const fn is_auto_detect(&self) -> bool { if self.solc.is_some() { return false; } From fd2db65e4854577895afa2486eee4fd5246c9494 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:37:17 +0700 Subject: [PATCH 304/391] Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index a061745771171..2c7a6b4876de0 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -2329,7 +2329,7 @@ impl Config { // Normalize `deny` based on the provided `deny_warnings` value. if figment.extract_inner::("deny_warnings").unwrap_or(false) - && let Ok(DenyLevel::Never) = figment.extract_inner("deny") + && matches!(figment.extract_inner("deny"), Ok(DenyLevel::Never)) { figment = figment.merge(("deny", DenyLevel::Warnings)); } From 7e67b2b86437fce8571d29e7a1c0825f4d270bf3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:27:58 +0700 Subject: [PATCH 305/391] Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail From e525ac18f963a1b0f0641f46f1c59afffbb32643 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:32:16 +0000 Subject: [PATCH 306/391] fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#435) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: googleworkspace-bot From b05f4c4f5eadad2dd38876b0a4254bd3a5ccfa6a Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Tue, 21 Apr 2026 21:35:52 +0700 Subject: [PATCH 307/391] doc-script.js --- doc/doc-filelist.js | 1 + doc/doc-script.js | 228 +++++++++++++++++++++++++ doc/doc-style.css | 403 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 632 insertions(+) create mode 100644 doc/doc-filelist.js create mode 100644 doc/doc-script.js create mode 100644 doc/doc-style.css diff --git a/doc/doc-filelist.js b/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/doc/doc-script.js b/doc/doc-script.js new file mode 100644 index 0000000000000..7fa122605e7cb --- /dev/null +++ b/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +var sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/doc/doc-style.css b/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} From 37ca1c2c9a44a3cc1804ccc029773edbd56b93fc Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:52:02 +0000 Subject: [PATCH 308/391] doc-script.js (#438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#345) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 110: Uncontrolled data used in path expression (#347) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 102: Artifact poisoning (#351) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md (#350) * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 109: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * Potential fix for code scanning alert no. 102: Artifact poisoning (#354) * Potential fix for code scanning alert no. 102: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#357) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * refactor(common): make ProviderBuilder generic over Network #13250 (#361) * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg --------- Signed-off-by: dependabot[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Matt D Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Tim Beiko Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#373) * Potential fix for code scanning alert no. 108: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Forge/master (#376) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar Co-authored-by: googleworkspace-bot * Hardhat project (#378) * docs(config): complete foundry.toml configuration reference (#13198) * complete config * docs: mark optional fields in config README * use foundry book for full config * docs(config): point to Foundry Book for configuration reference * feat(invariant): add optimization mode for invariant testing (#13196) * feat(invariant): add optimization mode for invariant testing Adds optimization mode for invariant testing, similar to Echidna. This mode maximizes an `int256` return value from a function prefixed with `optimize_`. **Usage:** - Define an invariant function returning `int256` with `optimize_` prefix - Foundry will fuzz to find the sequence that maximizes this value - The best value and sequence are tracked and reported - Sequence shrinking is applied to find the minimal reproducing sequence **Example:** ```solidity function optimize_maxRoundingError() external view returns (int256) { return int256(pool.totalShares()) - int256(pool.expectedShares()); } ``` **Bug fix during implementation:** Fixed a bug in shrink logic where `vm.warp` and `vm.roll` values were not correctly accumulated when replaying shrunk sequences. This caused the "best" sequence to be non-reproducible because time/block values depended on removed calls. The fix: - Keeps reverted calls in sequence during optimization to preserve warp/roll - Accumulates warps/rolls from removed calls into kept calls during shrinking - Updates inspector's cheatcodes.block alongside executor.env Closes #12190 Amp-Thread-ID: https://ampcode.com/threads/T-019bea21-149c-728c-9556-850778b70ea3 Co-authored-by: Amp * refactor: isolate optimization shrinking logic from check mode - Keep shrink_sequence and check_sequence unchanged for regular invariant checks - Add shrink_sequence_value and check_sequence_value for optimization mode - Optimization mode handles warp/roll accumulation from removed calls - replay.rs dispatches to correct shrinking function based on target_value * refactor: cleanup shrink optimization code, extract helpers --------- Co-authored-by: Amp * fix: only classify setUp as test setup if it has no parameters (#13204) Contracts with `setUp(bytes memory)` (common in Gnosis Safe/Zodiac modules) were incorrectly classified as dev/test contracts and excluded from `forge build --sizes` output. Now `setUp` is only classified as a test `Setup` function when it has no parameters, matching Forge's actual test setup behavior. Fixes #11126 * chore: fix clippy lints (#13207) * feat(cast): add --flatten flag to cast interface (#13201) * feat(cast): add --all-in-one flag to cast interface Adds a new `--all-in-one` flag to `cast interface` that inlines inherited/library struct types directly into the generated interface. This addresses the issue where `cast interface` generates a separate `library` block for struct types that originate from inherited interfaces, making the generated interface less usable for some workflows. With `--all-in-one`, all types are consolidated into a single interface: ```solidity // Before (default): library IBase { struct TestStruct { address asset; } } interface Contract { function test(IBase.TestStruct memory) external; } // After (with --all-in-one): interface Contract { struct TestStruct { address asset; } function test(TestStruct memory) external; } ``` Uses alloy-json-abi's `ToSolConfig::one_contract(true)` option introduced in alloy-core 0.8.24. Closes #9960 * chore: cargo fmt * refactor: rename --all-in-one to --flatten per review * chore: fix rustfmt * Update flake.lock (#13212) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/edd5602' (2026-01-17) → 'github:nix-community/fenix/93523fa' (2026-01-24) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/adbff8b' (2026-01-15) → 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/be5afa0' (2026-01-16) → 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) Co-authored-by: github-actions[bot] * chore(deps): weekly `cargo update` (#13213) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 62 packages to latest compatible versions Updating alloy-chains v0.2.29 -> v0.2.30 Updating alloy-consensus v1.4.3 -> v1.5.2 Updating alloy-consensus-any v1.4.3 -> v1.5.2 Updating alloy-contract v1.4.3 -> v1.5.2 Updating alloy-eip5792 v1.4.3 -> v1.5.2 Updating alloy-eip7928 v0.3.0 -> v0.3.2 Updating alloy-eips v1.4.3 -> v1.5.2 Updating alloy-ens v1.4.3 -> v1.5.2 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-genesis v1.4.3 -> v1.5.2 Updating alloy-json-rpc v1.4.3 -> v1.5.2 Updating alloy-network v1.4.3 -> v1.5.2 Updating alloy-network-primitives v1.4.3 -> v1.5.2 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-provider v1.4.3 -> v1.5.2 Updating alloy-pubsub v1.4.3 -> v1.5.2 Updating alloy-rpc-client v1.4.3 -> v1.5.2 Updating alloy-rpc-types v1.4.3 -> v1.5.2 Updating alloy-rpc-types-anvil v1.4.3 -> v1.5.2 Updating alloy-rpc-types-any v1.4.3 -> v1.5.2 Updating alloy-rpc-types-beacon v1.4.3 -> v1.5.2 Updating alloy-rpc-types-debug v1.4.3 -> v1.5.2 Updating alloy-rpc-types-engine v1.4.3 -> v1.5.2 Updating alloy-rpc-types-eth v1.4.3 -> v1.5.2 Updating alloy-rpc-types-trace v1.4.3 -> v1.5.2 Updating alloy-rpc-types-txpool v1.4.3 -> v1.5.2 Updating alloy-serde v1.4.3 -> v1.5.2 Updating alloy-signer v1.4.3 -> v1.5.2 Updating alloy-signer-aws v1.4.3 -> v1.5.2 Updating alloy-signer-gcp v1.4.3 -> v1.5.2 Updating alloy-signer-ledger v1.4.3 -> v1.5.2 Updating alloy-signer-local v1.4.3 -> v1.5.2 Updating alloy-signer-trezor v1.4.3 -> v1.5.2 Updating alloy-signer-turnkey v1.4.3 -> v1.5.2 Updating alloy-transport v1.4.3 -> v1.5.2 Updating alloy-transport-http v1.4.3 -> v1.5.2 Updating alloy-transport-ipc v1.4.3 -> v1.5.2 Updating alloy-transport-ws v1.4.3 -> v1.5.2 Updating alloy-tx-macros v1.4.3 -> v1.5.2 Updating aws-lc-rs v1.15.3 -> v1.15.4 Updating aws-lc-sys v0.36.0 -> v0.37.0 Updating cc v1.2.53 -> v1.2.54 Updating clearscreen v4.0.2 -> v4.0.3 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating libm v0.2.15 -> v0.2.16 Unchanged matchit v0.8.4 (available: v0.8.6) Removing nix v0.29.0 Updating num-conv v0.1.0 -> v0.2.0 Updating opener v0.8.3 -> v0.8.4 Updating openssl-probe v0.2.0 -> v0.2.1 Updating proc-macro2 v1.0.105 -> v1.0.106 Updating process-wrap v8.2.1 -> v9.0.1 Updating quote v1.0.43 -> v1.0.44 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating socket2 v0.6.1 -> v0.6.2 Updating time v0.3.45 -> v0.3.46 Updating time-core v0.1.7 -> v0.1.8 Updating time-macros v0.2.25 -> v0.2.26 Updating uuid v1.19.0 -> v1.20.0 Updating watchexec-signals v5.0.0 -> v5.0.1 Updating watchexec-supervisor v5.0.1 -> v5.0.2 Updating web_atoms v0.2.1 -> v0.2.3 Updating windows v0.61.3 -> v0.62.2 Updating windows-collections v0.2.0 -> v0.3.2 Removing windows-core v0.61.2 Updating windows-future v0.2.1 -> v0.3.2 Removing windows-link v0.1.3 Updating windows-numerics v0.2.0 -> v0.3.1 Removing windows-result v0.3.4 Removing windows-strings v0.4.2 Updating windows-threading v0.1.0 -> v0.2.1 Updating zmij v1.0.15 -> v1.0.16 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * chore(anvil): clean up remaining dead code after Odyssey sunset (#13211) * fix(coverage): correct BRDA hit values for LCOV consistency (#13151) * fix(coverage): correct BRDA hit values for LCOV consistency Per the LCOV tracefile format specification, BRDA hit values should be: - "-" when the expression was never evaluated (line not executed) - "0" when the branch exists and was evaluated but never taken - "N" when the branch was taken N times Previously, we were outputting "-" for all branches with 0 hits, which caused genhtml to fail with "inconsistent" errors when a line was hit (DA shows hits > 0) but branches on that line showed "-". This fix tracks line hits in a first pass, then uses that information to determine whether to output "-" (line never hit) or "0" (line hit but branch not taken) for branches with 0 hits. Fixes foundry-rs/foundry#11548 * fix: clippy explicit_iter_loop warning * test(coverage): add brda_lcov_consistency test for BRDA hit values Verifies that BRDA outputs follow LCOV spec: - "0" when line was executed but branch not taken - "-" when line was never executed This catches the inconsistency that caused genhtml to fail. --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * feat(forge): add browser wallet support for `forge script` (#12952) * feat(script): add support for browser wallet * fix: browser wallet opts defaults * chore: bump foundry-browser-wallet v0.1.0 * ci: use shared cache mounts for parallel builds (#13231) perf(docker): use shared cache mounts for parallel builds Change cache mount sharing mode from `locked` to `shared` for cargo registry, git, and sccache directories. With `sharing=locked`, parallel builds must wait for exclusive access to each cache, causing builds to queue up even when compilation itself is fast. Both cargo and sccache handle concurrent access correctly, so `shared` is safe and allows parallel builds to proceed without blocking. Amp-Thread-ID: https://ampcode.com/threads/T-019bfc1d-3cee-70ca-9caa-01e33acdff46 Co-authored-by: Amp * fix(anvil): preserve withdrawals in SerializableBlock for state dump/load (#13227) * fix(anvil): preserve withdrawals in SerializableBlock for state dump/load * add: test * fix ci * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.1 to 3.15.2 (#13236) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.1 to 3.15.2. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/1d699fc25db3f9e079cd2f168ca007a4183389be...89ab342bd48ff7318caf8d39d6a330c7b1df8f2f) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.66.2 to 2.67.13 (#13235) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.66.2 to 2.67.13. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.66.2...710817a1645ef40daad5bcde7431ceccf6cc3528) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.13 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 (#13234) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/98357b18bf14b5342f975ff684046ec3b2a07725...c0f553fe549906ede9cf27b5156039d195d2ece0) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.42.1 to 1.42.2 (#13233) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.1 to 1.42.2. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/65120634e79d8374d1aa2f27e54baa0c364fff5a...a1d64977b4aa1709d6328d518aa753f4899352d8) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.42.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(docker): expose VERGEN_GIT_SHA to build environment (#13237) * docs(coverage): link to stack-too-deep guide in warnings (#13240) Update coverage warnings to point to the new Foundry Book guide instead of the GitHub issue, providing users with actionable techniques to resolve the error. Amp-Thread-ID: https://ampcode.com/threads/T-019bff7a-8502-72b7-af76-794f258e8c67 Co-authored-by: Amp * Polkadot/Kusama/PolkadotTestnet for RPC gas estimation (#12537) * adds polkadot testnet * adds polkadot testnet * bump alloy chains * fmt * Remove kluster * update rpc url * update rpc url * fmt * adds polkadot kusama * bump alloy chains * perf(anvil): avoid redundant block queries in ots_getBlockTransactions (#13243) * fix(anvil): use consistent chain_id fallback for blob params (#13241) * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * feat(primitives): introduce `NetworkWallet` impl for `EthereumWallet` (#13248) - Seamless support for eth/op/tempo txs - This aims to replace `WalletSigner`'s impl once `FoundryNetwork` will be used everywhere * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg * foundryup: tempo now distributes all binaries (#13337) Amp-Thread-ID: https://ampcode.com/threads/T-019c2ea2-963a-744a-8b1d-57709bc295be Co-authored-by: Amp * fix(config): handle vyper section with skip_serializing_if fields (#13318) * fix(config): handle vyper section with skip_serializing_if fields The vyper config section uses skip_serializing_if = Option::is_none on all fields, causing the default serialization to produce an empty dict. This led to all vyper keys being flagged as unknown. Add explicit VYPER_KEYS constant and special-case the vyper section in collect_standalone_section_warnings to use these known keys instead of deriving them from the (empty) default serialization. Fixes #13316 * test(config): add regression tests for vyper config warnings Tests for #13316: - no_false_warnings_for_vyper_config_keys: valid vyper keys in standalone section - no_false_warnings_for_nested_vyper_config_keys: valid vyper keys in profile - warns_on_unknown_vyper_keys: unknown vyper keys should still warn Amp-Thread-ID: https://ampcode.com/threads/T-019c28b9-9c8c-76bf-96a5-ff5a504c0507 Co-authored-by: Amp * fix build issues * fix(config): handle nested vyper section with skip_serializing_if fields The VyperConfig struct uses skip_serializing_if on all Option fields, causing the default serialization to produce an empty dict. This caused false warnings for valid vyper keys like optimize, path, and experimental_codegen when used in profile nested sections like [profile.default.vyper]. Uses the existing VYPER_KEYS constant for nested vyper sections, matching how standalone [vyper] sections are already handled. Fixes #13316 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c297e-a282-7188-8f79-5080d3e451a9 --------- Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: broken config test, currently blocking CI (#13340) fix broken config * skip checksum hash in create2 mining when case-insensitive (#13331) * kip checksum hash in create2 mining * fix the clippy * fix: avoid setting FOUNDRY_PROFILE: ci in template workflows, profile does not exist (#13339) avoid encoding FOUNDRY_PROFILE: ci, profile does not exist * perf(evm): wrap Executor.backend in Arc for copy-on-write cloning (#13327) * perf(evm): wrap Executor.backend in Arc for copy-on-write cloning During parallel fuzzing, each worker clones the Executor. Previously this deep-cloned the entire Backend (CacheDB, JournaledState, state snapshots), which could be 10-50MB per clone with 16 workers = 160-800MB wasted memory. This change wraps Backend in Arc and uses Arc::make_mut() for copy-on-write semantics. When workers only read state, they share the same backend. When a worker mutates, it gets its own copy. Expected impact: - ~80% memory reduction for parallel fuzz runs - Faster executor clone (pointer copy instead of deep clone) - No behavioral change: mutations still get isolated copies Amp-Thread-ID: https://ampcode.com/threads/T-019c2af1-f00b-723a-a3c3-25cbd6f3e92b Co-authored-by: Amp * test: update config test expectations for new mixed_case_exceptions Fix test expectations after 1bd687f0d added new values (ID, URL, API, JSON, XML, HTML, HTTP, HTTPS) to lint.mixed_case_exceptions defaults. Amp-Thread-ID: https://ampcode.com/threads/T-019c2af1-f00b-723a-a3c3-25cbd6f3e92b Co-authored-by: Amp * Update config.rs * Update config.rs * fix: restore "URI" in config test JSON expectations Amp-Thread-ID: https://ampcode.com/threads/T-019c2f68-f9df-76bc-ba4c-94fbe1789c9c Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * chore(ci): update time crate (#13348) * test(cast): ignore flaky_run_celo_with_precompiles (Celo RPC no longer supports debug_traceTransaction) (#13347) * fix(test-utils): create destination directory in copy_dir_filtered (#13350) * chore(wallets): Remove `NetworkWallet` impl for `WalletSigner` (#13343) - superseeded by `EthereumWallet`'s one, which is integrated in `TransactionBuilder` flow * fix(anvil): return error when querying future block number in with_database_at (#13267) * return error when querying future block number * fix test --------- Co-authored-by: onbjerg Co-authored-by: Matthias Seitz * chore: remove stale `tiny-keccak` references (#13358) * chore: remove stale tiny-keccak profile override * chore: remove stale tiny-keccak deny exception * chore(script): typo (#13353) * perf(cheatcodes): loop invariant code motion by hand (#13357) * chore(anvil): remove unnecessary clone operations (#13330) * perf(linking): replace double hash mpa lookup contains_key + [] with single get (#13361) * fix(verify): correct Sourcify API URL construction for custom chains (#13360) Update verify.rs * chore(common): remove dead `with_spinner_reporter` function (#13366) * resolve absolute and relative paths on Windows (#13364) * fix: unittest failed (#13371) * perf(anvil): reuse storage root from prove_storage instead of recompu… (#13363) perf(anvil): reuse storage root from prove_storage instead of recomputing * chore(deps): weekly `cargo update` (#13384) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 94 packages to latest compatible versions Updating alloy-consensus v1.5.2 -> v1.6.1 Updating alloy-consensus-any v1.5.2 -> v1.6.1 Updating alloy-contract v1.5.2 -> v1.6.1 Updating alloy-eip5792 v1.5.2 -> v1.6.1 Updating alloy-eips v1.5.2 -> v1.6.1 Updating alloy-ens v1.5.2 -> v1.6.1 Updating alloy-evm v0.26.3 -> v0.26.4 (available: v0.27.2) Updating alloy-genesis v1.5.2 -> v1.6.1 Updating alloy-json-rpc v1.5.2 -> v1.6.1 Updating alloy-network v1.5.2 -> v1.6.1 Updating alloy-network-primitives v1.5.2 -> v1.6.1 Updating alloy-op-evm v0.26.3 -> v0.26.4 (available: v0.27.2) Updating alloy-provider v1.5.2 -> v1.6.1 Updating alloy-pubsub v1.5.2 -> v1.6.1 Updating alloy-rlp v0.3.12 -> v0.3.13 Updating alloy-rlp-derive v0.3.12 -> v0.3.13 Updating alloy-rpc-client v1.5.2 -> v1.6.1 Updating alloy-rpc-types v1.5.2 -> v1.6.1 Updating alloy-rpc-types-anvil v1.5.2 -> v1.6.1 Updating alloy-rpc-types-any v1.5.2 -> v1.6.1 Updating alloy-rpc-types-beacon v1.5.2 -> v1.6.1 Updating alloy-rpc-types-debug v1.5.2 -> v1.6.1 Updating alloy-rpc-types-engine v1.5.2 -> v1.6.1 Updating alloy-rpc-types-eth v1.5.2 -> v1.6.1 Updating alloy-rpc-types-trace v1.5.2 -> v1.6.1 Updating alloy-rpc-types-txpool v1.5.2 -> v1.6.1 Updating alloy-serde v1.5.2 -> v1.6.1 Updating alloy-signer v1.5.2 -> v1.6.1 Updating alloy-signer-aws v1.5.2 -> v1.6.1 Updating alloy-signer-gcp v1.5.2 -> v1.6.1 Updating alloy-signer-ledger v1.5.2 -> v1.6.1 Updating alloy-signer-local v1.5.2 -> v1.6.1 Updating alloy-signer-trezor v1.5.2 -> v1.6.1 Updating alloy-signer-turnkey v1.5.2 -> v1.6.1 Updating alloy-transport v1.5.2 -> v1.6.1 Updating alloy-transport-http v1.5.2 -> v1.6.1 Updating alloy-transport-ipc v1.5.2 -> v1.6.1 Updating alloy-transport-ws v1.5.2 -> v1.6.1 Updating alloy-trie v0.9.3 -> v0.9.4 Updating alloy-tx-macros v1.5.2 -> v1.6.1 Updating anyhow v1.0.100 -> v1.0.101 Updating async-compression v0.4.37 -> v0.4.39 Updating aws-config v1.8.12 -> v1.8.13 Updating aws-runtime v1.5.18 -> v1.6.0 Updating aws-sdk-kms v1.98.0 -> v1.99.0 Updating aws-sdk-sso v1.92.0 -> v1.93.0 Updating aws-sdk-ssooidc v1.94.0 -> v1.95.0 Updating aws-sdk-sts v1.96.0 -> v1.97.0 Updating aws-sigv4 v1.3.7 -> v1.3.8 Updating aws-smithy-async v1.2.10 -> v1.2.11 Updating aws-smithy-http v0.62.6 -> v0.63.3 Updating aws-smithy-http-client v1.1.8 -> v1.1.9 Updating aws-smithy-json v0.61.9 -> v0.62.3 Updating aws-smithy-observability v0.2.3 -> v0.2.4 Updating aws-smithy-query v0.60.12 -> v0.60.13 Updating aws-smithy-runtime v1.9.8 -> v1.10.0 Updating aws-smithy-runtime-api v1.11.2 -> v1.11.3 Updating aws-smithy-types v1.4.2 -> v1.4.3 Updating clap v4.5.56 -> v4.5.57 Updating clap_builder v4.5.56 -> v4.5.57 Updating flate2 v1.1.8 -> v1.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating hyper-util v0.1.19 -> v0.1.20 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating interprocess v2.2.3 -> v2.3.1 Updating jiff v0.2.18 -> v0.2.19 Updating jiff-static v0.2.18 -> v0.2.19 Unchanged matchit v0.8.4 (available: v0.8.6) Updating memchr v2.7.6 -> v2.8.0 Updating nybbles v0.4.7 -> v0.4.8 Updating pest v2.8.5 -> v2.8.6 Updating pest_derive v2.8.5 -> v2.8.6 Updating pest_generator v2.8.5 -> v2.8.6 Updating pest_meta v2.8.5 -> v2.8.6 Updating proptest v1.9.0 -> v1.10.0 Unchanged rand v0.8.5 (available: v0.9.2) Updating rapidhash v4.2.1 -> v4.2.2 Updating regex v1.12.2 -> v1.12.3 Updating regex-automata v0.4.13 -> v0.4.14 Updating regex-lite v0.1.8 -> v0.1.9 Updating regex-syntax v0.8.8 -> v0.8.9 Unchanged reqwest v0.12.28 (available: v0.13.2) Updating schemars v1.2.0 -> v1.2.1 Updating schemars_derive v1.2.0 -> v1.2.1 Updating sval v2.16.0 -> v2.17.0 Updating sval_buffer v2.16.0 -> v2.17.0 Updating sval_dynamic v2.16.0 -> v2.17.0 Updating sval_fmt v2.16.0 -> v2.17.0 Updating sval_json v2.16.0 -> v2.17.0 Updating sval_nested v2.16.0 -> v2.17.0 Updating sval_ref v2.16.0 -> v2.17.0 Updating sval_serde v2.16.0 -> v2.17.0 Updating system-configuration v0.6.1 -> v0.7.0 Updating webbrowser v1.0.6 -> v1.1.0 Updating webpki-roots v1.0.5 -> v1.0.6 Updating zerocopy v0.8.37 -> v0.8.39 Updating zerocopy-derive v0.8.37 -> v0.8.39 Updating zlib-rs v0.5.5 -> v0.6.0 Updating zmij v1.0.18 -> v1.0.19 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * Update flake.lock (#13383) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/b2344f3' (2026-01-31) → 'github:nix-community/fenix/e1b28f6' (2026-02-07) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) → 'github:rust-lang/rust-analyzer/d2a00da' (2026-02-05) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) → 'github:NixOS/nixpkgs/ae67888' (2026-02-06) Co-authored-by: github-actions[bot] * perf(verify): reuse transaction from earlier RPC call instead of fetching twice (#13391) * perf(verify): reuse transaction from earlier RPC call instead of fetching twice * fix ci * fix(cast): --json support for erc20 cmds (#12727) * refactor(anvil): using is_ok since it's more robust (#13377) * fix: may div by zero (#13369) * refactor(primitives): turn `FoundryTransactionRequest` into an enum (#13278) - Combines Eth's, Op's, and Tempo's transaction requests to inherit Op/Tempo tx building * perf: avoid checksum (#13374) * docs: slim readme (#13393) * fix: correct trace message in dynamic linking preprocessor (#13394) * perf(invariant): avoid cloning state changeset in fuzz runs (#13398) Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore(deps): bump depot/build-push-action from 1.16.2 to 1.17.0 (#13405) Bumps [depot/build-push-action](https://github.com/depot/build-push-action) from 1.16.2 to 1.17.0. - [Release notes](https://github.com/depot/build-push-action/releases) - [Commits](https://github.com/depot/build-push-action/compare/9785b135c3c76c33db102e45be96a25ab55cd507...5f3b3c2e5a00f0093de47f657aeaefcedff27d18) --- updated-dependencies: - dependency-name: depot/build-push-action dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.18 to 2.67.27 (#13406) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.18 to 2.67.27. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/650c5ca14212efbbf3e580844b04bdccf68dac31...1e67dedb5e3c590e1c9d9272ace46ef689da250d) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.27 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.0 to 1.43.4 (#13407) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.0 to 1.43.4. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c...78bc6fb2c0d734235d57a2d6b9de923cc325ebdd) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci: use dedicated template for isolate flaky test failures (#13409) * chore(deps): bump depot/setup-action from 1.6.0 to 1.7.1 (#13408) * fix(primitives): `FoundryTransactionRequest` conversion w/ tempo variant (#13401) - Fix `TempoTransactionRequest` variant, as inner req was always set to default. - Added unit tests to assess `FoundryTransactionRequest` proper variant routing * return error instead of empty array when filter not found (#13415) * chore(config): remove unused enum accessor methods (#13414) * fix(cast): clean up temp dir in `cast storage` when etherscan cache is unavailable (#13418) * perf(primitives): avoid cloning receipts (#13396) * fix: constructor params and args check (#13375) * fix: correct path format in get_paths doc comment (#13388) * ci: replace merge_group with push on master (#13419) * ci(release): pin action-gh-release to v2.4.2 (#13420) v2.5.0 introduced a draft→finalize flow that races in matrix jobs, causing 'Too many retries' failures in the Create release step. See: https://github.com/softprops/action-gh-release/issues/704 Amp-Thread-ID: https://ampcode.com/threads/T-019c4c6d-c55a-752a-8b27-25413f485bed Co-authored-by: Amp * fix(anvil): handle disk cache write failures in state eviction (#13332) * feat(forge,chisel): realtime `console.log` (#13321) * fix(cast): remove duplicate receipt handling in Tempo transactions (#13378) * perf(traces): deduplicate addresses before external fetching (#13320) * fix: prevent panic on etherscan client creation failure in test command (#13395) * perf(config): skip redundant remapping detection in _with_root (#13389) * fix(common): remove trailing space in `state_root` match pattern (#13426) * chore(config): `curl` mode as config key (#13260) Co-authored-by: onbjerg * fix(config): normalize deny_warnings from env vars (#13434) * fix: correct dead condition in command error formatting (#13427) * add missing JSON output support for `erc20 decimals` (#13438) * fix(anvil): variable shadowing bug in ReadyTransactions::remove_with_markers (#13436) Update transactions.rs * Update flake.lock (#13448) * chore(deps): weekly `cargo update` (#13449) * feat(evm): `ForkDatabase`/`MultiFork` generic over `Network` (#13459) * feat(evm): `ForkDatabase`/`MultiFork` generic over `Network` bump `foundry-fork-db` * fix: typo * fix(cheatcodes): fix vm.expectRevert for direct precompile calls (#13460) Precompile calls don't create an interpreter frame, so `initialize_interp` never fires and `max_depth` never gets bumped beyond the cheatcode call depth. This causes the depth check in `handle_expect_revert` to fail with "call didn't revert at a lower depth than cheatcode call depth". Track `max_depth` in the `call` hook as well, accounting for the callee depth (`curr_depth + 1`). Amp-Thread-ID: https://ampcode.com/threads/T-019c63a2-2c36-7334-ab55-2931a174b59c Co-authored-by: Amp * fix(lint): remove unreachable macro arm in declare_forge_lint (#13452) * chore(flake): use nightly rustfmt (#13441) * chore(flake): use nightly rustfmt * chore(flake): update flake * feat: add `executeTransaction` cheatcode (#13437) feat: add executeTransaction cheatcode Port the executeTransaction cheatcode from tempoxyz/tempo-foundry. Executes RLP-encoded signed transactions in an isolated EVM context with full semantics (like --isolate mode). OP deposit and Tempo AA transactions return errors for now (marked with TODOs). * fix(forge): don't reset snapshot diff result on missing file (#13442) * fix(traces): check HTTP status before JSON parsing in Sourcify fetcher (#13446) * Update external.rs * chore: fmt * test: rm useless tests --------- Co-authored-by: Oliver Nordbjerg * feat(cheatcodes): add Ed25519 crypto cheatcodes (#13450) * feat(cheatcodes): add Ed25519 crypto cheatcodes Add four new cheatcodes for Ed25519 cryptography: - createEd25519Key(bytes32 salt) - deterministic key generation - publicKeyEd25519(bytes32 privateKey) - derive public key - signEd25519(namespace, message, privateKey) - sign with domain separation - verifyEd25519(signature, namespace, message, publicKey) - verify signatures Uses ed25519-consensus crate. Includes comprehensive unit tests for determinism, namespace separation, edge cases, and invalid inputs. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c5f04-a6ed-7015-9b4d-4464a35bc26c * chore: solidity test * test: fix assertions --------- Co-authored-by: Amp Co-authored-by: Oliver Nordbjerg * feat(lint): add missing visit methods to EarlyLintVisitor (#13454) Update early.rs * notify subscribers for txs promoted after block mining (#13464) * notify subscribers for txs promoted after block mining * refactor: extract notify_ready helper to deduplicate notification logic Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp --------- Co-authored-by: Matthias Seitz Co-authored-by: Amp * chore(deps): bump taiki-e/install-action from 2.67.27 to 2.68.0 (#13465) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.27 to 2.68.0. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/1e67dedb5e3c590e1c9d9272ace46ef689da250d...f8d25fb8a2df08dcd3cead89780d572767b8655f) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.68.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/cache-cargo-install-action from 3.0.1 to 3.0.2 (#13466) Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases) - [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/34ce5120836e5f9f1508d8713d7fdea0e8facd6f...2bfc3cedaf2ee5e7fa5d0ae034ccd5fb50cf8e1f) --- updated-dependencies: - dependency-name: taiki-e/cache-cargo-install-action dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.4 to 1.43.5 (#13467) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.4 to 1.43.5. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/78bc6fb2c0d734235d57a2d6b9de923cc325ebdd...57b11c6b7e54c402ccd9cda953f1072ec4f78e33) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 to a135ea602656a8348c5c34887131dd9f7a28bd8c (#13468) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 to a135ea602656a8348c5c34887131dd9f7a28bd8c. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/5adeaaaf36f64df54f62adb34aa5fbfdb0109d34...a135ea602656a8348c5c34887131dd9f7a28bd8c) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: a135ea602656a8348c5c34887131dd9f7a28bd8c dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.4.2 to 2.5.0 (#13469) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.2 to 2.5.0. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/5be0e66d93ac7ed76da52eca8bb058f665c3a5fe...a06a81a03ee405af7f2048a818ed3f03bbf83c7b) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * test: mark clone CLI tests as flaky (#13472) These tests hit real Etherscan/Sourcify APIs and fail intermittently due to rate limiting and network issues. They will now run in the nightly flaky test workflow with retries instead of blocking every PR. Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp * fix: bind TempDir guard in clone test to prevent premature cleanup (#13471) Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp * prevent balance overflow in anvil_addBalance (#13457) Co-authored-by: Matthias Seitz * chore(tests): bump forge-std version (#13482) * move `sccache --show-stats` into build RUN to show actual stats (#13483) * fix(anvil): correct blob_gas_used_ratio calculation in fee history (#13491) Update fees.rs * fix(cheatcodes): make vm.executeTransaction work in isolation mode (#13475) * fix(test): exclude ExecuteTransactionTest from isolation mode vm.executeTransaction already performs its own isolated execution (fresh EVM, cloned state, state merging). When isolation mode is enabled, the inspector's transact_inner intercepts CALLs at depth==1 inside the cheatcode's inner EVM, causing double-isolation that results in 'transaction reverted: 0x'. Amp-Thread-ID: https://ampcode.com/threads/T-019c6ad3-d3f0-70d3-8d78-38ccd8444e9e Co-authored-by: Amp * fix(cheatcodes): make vm.executeTransaction work in isolation mode Two bugs prevented vm.executeTransaction from working with --isolate: 1. Double isolation: executeTransaction creates its own inner EVM at depth=1, but the isolation inspector also intercepts CALLs at depth=1, causing a nested transact_inner. Fix: add set_in_inner_context() to CheatcodesExecutor trait and set it before/after the inner EVM run, matching how transact_inner already handles this. 2. Corrupted cfg env: executeTransaction modified env.cfg (disabled nonce checks, set initcode size limit) but never restored it. Subsequent isolated calls then failed nonce validation (NonceTooHigh). Fix: restore env.cfg from the cached copy alongside env.tx and basefee. Amp-Thread-ID: https://ampcode.com/threads/T-019c6ad3-d3f0-70d3-8d78-38ccd8444e9e Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test-utils: remove unused `IS_TTY` helper (#13492) Update util.rs * chore: update LATEST_SOLC to 0.8.34 (#13489) Amp-Thread-ID: https://ampcode.com/threads/T-019c7441-de53-7338-86cb-6d84f755016a Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(anvil): add --max-transactions CLI flag (#13495) * remove unimplemented anvil_enableTraces endpoint (#13499) * feat(fmt): pretty printing for generic block/transaction responses (#13497) feat(fmt): pretty printing for generic block/transaction reponses * Refactor `locked_read_to_string` to reuse `locked_read (#13494) Update fs.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(script-sequence): extract duplicated filter logic (#13500) * chore(broadcast): cleanup avg gas price calculation (#13509) * chore(deps): weekly `cargo update` (#13513) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * feat(common): generic `TransactionReceiptWithRevertReason` + pprinting (#13503) * refactor: Use `fs::write_pretty_json_file` in `MultiChainSequence::save` (#13510) * Update flake.lock (#13511) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/d0555da' (2026-02-14) → 'github:nix-community/fenix/6d86ae5' (2026-02-21) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/bbc84d3' (2026-02-13) → 'github:rust-lang/rust-analyzer/46a214b' (2026-02-20) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2343bbb' (2026-02-11) → 'github:NixOS/nixpkgs/d1c15b7' (2026-02-16) Co-authored-by: github-actions[bot] * fix(sol-macro-gen): correct identifier check in write_mod_name (#13508) * chore(deps): bump DeterminateSystems/update-flake-lock from a135ea602656a8348c5c34887131dd9f7a28bd8c to 5909792a83875ddb5dd4b18734534a98a74a709c (#13524) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.68.0 to 2.68.8 (#13523) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.2 to 3.16.1 (#13522) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): prevent panic in `utc_from_secs` for out-of-range timestamps (#13520) * ci(release): revert action-gh-release to v2.4.2 (#13527) * ci: ignore softprops/action-gh-release in dependabot (#13528) * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.16.1 to 3.16.3 (#13529) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.16.1 to 3.16.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/681d8e8bfdb5d7af56f113ba2425b1fb00ec9edc...73327eb48f028efaaf5013656ba216ca3cdeca7b) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.16.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(primitives): add `FoundryTransactionBuilder` trait (#13512) Co-authored-by: figtracer <1gusredo@gmail.com> * fix(wallets): use turnkey_unsupported() instead of hardcoded error (#13535) * chore(deps): bump taiki-e/install-action from 2.68.8 to 2.68.9 (#13534) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.8 to 2.68.9. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cfdb446e391c69574ebc316dfb7d7849ec12b940...7f491e26f71f4ec2e6902c7c95c73043f209ab79) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.68.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): preserve original error in update_url (#13531) Co-authored-by: Oliver Nordbjerg * fix: swap incorrect doc comments for archive RPC URL functions in rpc. (#13480) * Allow verifier-url for unknown Etherscan chains (#13079) Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: onbjerg * fix: remove unused `no_storage_caching()` method from `NodeConfig` (#13487) * fix: remove code duplication in `get_runtime_codes` (#13502) * forge: avoid repeated selector decoding in find command (#13516) * fix(cli): Git::is_repo_root always returns false (#13505) * fix(verify): remove unused functions from `VerificationContext` (#13481) Co-authored-by: onbjerg * add missing doc section keys to config validation (#13447) * fix(anvil): use EIP-2718 encoding for OP enveloped_tx (#13537) * fix: correct flag name in error message from --compiler-version to --… (#13539) * chore(ci): unblock ci, fix clippy lint (#13543) * add lint-fix * fix: resolve nightly clippy warnings - collapsible_match: collapse plain if into match guards, allow for if-let - iter_kv_map: use .values()/.keys() instead of .iter().flat_map(|(_, v)| v) - useless_conversion: remove unnecessary .into_iter() Amp-Thread-ID: https://ampcode.com/threads/T-019c9930-51be-760a-b2c7-9a029f851fee Co-authored-by: Amp * fix: add missing match arm for Occupied entry in remappings Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp * fix: revert incorrect .values() call on Vec in runner Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp * fix: resolve irrefutable let pattern warning in MultiForkHandler Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Amp * refactor(wallets): browser wallet generic `Network` (#13550) * fix(anvil): use individual tx gas instead of cumulative in fee history (#13552) * fix(anvil): clear tx pool on anvil_reset (#13544) * add EIP-3860 initcode size check in txpool validation (#13473) * fix: clarify Anvil storage caching builder naming (#13546) Co-authored-by: Amp Co-authored-by: Oliver Nordbjerg * refactor(evm): remove dead BackendError::Other variant (#13553) * fix(script): actually skip Vyper contract verification (#13484) Co-authored-by: onbjerg * feat(doc): Adding new 'Constants' section for constants and immutables (#13116) Co-authored-by: onbjerg Co-authored-by: Oliver Nordbjerg * fix(evm): avoid wrong CowBackend initialization in load_allocs and clone_account (#13554) Co-authored-by: Amp Co-authored-by: Matthias Seitz * chore: use `fs::write_pretty_json_file` in `ScriptSequence::save` (#13562) * fix(forge): apply --access-list in forge create (#13557) Co-authored-by: onbjerg * Update flake.lock (#13564) Co-authored-by: github-actions[bot] Co-authored-by: onbjerg * chore(deps): weekly `cargo update` (#13565) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * fix(cast): correct max_int boundary check for uint255 (#13568) * fix(fmt): correct total_difficulty attribute name (#13578) * chore(evm): use `AnyRpcTransaction` in `configure_tx_env`/`commit_transaction` (#13572) * feat(evm): add `AsEnvMut::set_env` (#13573) * fix(fmt): don't inline while/for/if blocks with multiple statements (#13566) * refactor(cheatcodes,evm): use ContextTr read accessors for env fields (#13582) Co-authored-by: Amp * chore(evm): simplify `CowBackend` init logic by using `Option` (#13584) * feat(anvil): add EIP-2935 blockhash histo… * chore(deps): bump the cargo group across 1 directory with 9 updates (#377) Bumps the cargo group with 3 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing), [keccak](https://github.com/RustCrypto/sponges) and [slab](https://github.com/tokio-rs/slab). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `time` from 0.3.41 to 0.3.47 - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.41...v0.3.47) Updates `alloy-dyn-abi` from 1.3.0 to 1.5.7 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.3.0...v1.5.7) Updates `bytes` from 1.10.1 to 1.11.1 - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.1...v1.11.1) Updates `keccak` from 0.1.5 to 0.1.6 - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) Updates `lru` from 0.12.5 to 0.16.3 - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.3) Updates `protobuf` from 3.3.0 to 3.7.2 Updates `ruint` from 1.15.0 to 1.17.2 - [Release notes](https://github.com/recmo/uint/releases) - [Changelog](https://github.com/recmo/uint/blob/main/CHANGELOG.md) - [Commits](https://github.com/recmo/uint/compare/v1.15.0...v1.17.2) Updates `slab` from 0.4.10 to 0.4.12 - [Release notes](https://github.com/tokio-rs/slab/releases) - [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.12) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: time dependency-version: 0.3.47 dependency-type: direct:production dependency-group: cargo - dependency-name: alloy-dyn-abi dependency-version: 1.5.7 dependency-type: direct:production dependency-group: cargo - dependency-name: bytes dependency-version: 1.11.1 dependency-type: direct:production dependency-group: cargo - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect dependency-group: cargo - dependency-name: lru dependency-version: 0.16.3 dependency-type: indirect dependency-group: cargo - dependency-name: protobuf dependency-version: 3.7.2 dependency-type: indirect dependency-group: cargo - dependency-name: ruint dependency-version: 1.17.2 dependency-type: indirect dependency-group: cargo - dependency-name: slab dependency-version: 0.4.12 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create static.yml (#381) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Hardhat project (#389) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot * foundry-rs#13763 (#398) * fix: update EtherlinkTestnet -> EtherlinkShadownet for alloy-chains v0.2.31 (#13763) Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> * chore(evm): make `FoundryCfg` generic over `Spec` (#13757) * chore(deps): weekly `cargo update` (#13760) Updating git repository `https://github.com/alloy-rs/alloy` Updating git repository `https://github.com/alloy-rs/evm.git` Updating git repository `https://github.com/foundry-rs/optimism` Updating git submodule `https://github.com/flashbots/op-rbuilder` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/runtimeverification/kontrol-cheatcodes` Updating git submodule `https://github.com/ethereum-optimism/lib-keccak` Updating git submodule `https://github.com/dapphub/ds-test` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/a16z/erc4626-tests.git` Updating git submodule `https://github.com/safe-global/safe-contracts` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/transmissions11/solmate` Updating git submodule `https://github.com/ethereum-optimism/superchain-registry` Updating git submodule `https://github.com/flashbots/rollup-boost` Updating git repository `https://github.com/foundry-rs/foundry-fork-db` Updating git repository `https://github.com/paradigmxyz/revm-inspectors.git` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/tempoxyz/tempo-std` Updating git repository `https://github.com/stevencartavia/reth` Locking 31 packages to latest compatible versions Updating alloy-chains v0.2.30 -> v0.2.31 Updating alloy-trie v0.9.4 -> v0.9.5 Adding anstream v1.0.0 Unchanged anstream v0.6.21 (available: v1.0.0) Updating anstyle v1.0.13 -> v1.0.14 Updating anstyle-lossy v1.1.4 -> v1.1.5 Adding anstyle-parse v1.0.0 Updating bon v3.9.0 -> v3.9.1 Updating bon-macros v3.9.0 -> v3.9.1 Updating c-kzg v2.1.6 -> v2.1.7 Updating cc v1.2.56 -> v1.2.57 Updating clap v4.5.60 -> v4.6.0 Updating clap_builder v4.5.60 -> v4.6.0 Updating clap_complete v4.5.66 -> v4.6.0 Updating clap_complete_nushell v4.5.10 -> v4.6.0 Updating clap_derive v4.5.55 -> v4.6.0 Updating clap_lex v1.0.0 -> v1.1.0 Updating colorchoice v1.0.4 -> v1.0.5 Updating console v0.16.2 -> v0.16.3 Removing darling v0.21.3 Removing darling_core v0.21.3 Removing darling_macro v0.21.3 Updating derive-where v1.6.0 -> v1.6.1 Updating evmole v0.8.2 -> v0.8.4 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating kasuari v0.4.11 -> v0.4.12 Unchanged matchit v0.8.4 (available: v0.8.6) Updating once_cell v1.21.3 -> v1.21.4 Unchanged op-revm v15.0.0 (available: v17.0.0) Updating portable-atomic-util v0.2.5 -> v0.2.6 Unchanged quick-junit v0.5.2 (available: v0.6.0) Unchanged rand v0.8.5 (available: v0.10.0) Unchanged rand v0.9.2 (available: v0.10.0) Unchanged revm v34.0.0 (available: v36.0.0) Updating schannel v0.1.28 -> v0.1.29 Updating serde_with v3.17.0 -> v3.18.0 Updating serde_with_macros v3.17.0 -> v3.18.0 Unchanged snapbox v0.6.24 (available: v1.1.0) Unchanged soldeer-commands v0.10.0 (available: v0.10.1) Unchanged soldeer-core v0.10.0 (available: v0.10.1) Unchanged strum v0.27.2 (available: v0.28.0) Updating tempfile v3.26.0 -> v3.27.0 Updating tinyvec v1.10.0 -> v1.11.0 Unchanged toml v0.9.12+spec-1.1.0 (available: v1.0.6+spec-1.1.0) Unchanged toml_edit v0.24.1+spec-1.1.0 (available: v0.25.4+spec-1.1.0) Updating tracing-subscriber v0.3.22 -> v0.3.23 Updating zerocopy v0.8.41 -> v0.8.42 Updating zerocopy-derive v0.8.41 -> v0.8.42 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * feat(cheatcodes): bubble-up `Network` generic to `Wallets` (#13768) * feat(script): generic `TxStatus` receipt type (#13770) feat(script): generic `TxStatus` * chore(script): idiomatic `BroadcastReader::into_tx_receipts` (#13771) * refactor(evm): simplify `Backend::initialize` and `CowBackend::backend_mut` (#13755) `initialize` now takes `(SpecId, Address, TxKind)` instead of `&Env`, since those are the only fields it reads. This removes the need for `backend_mut` to clone `EvmEnv` — it only needs `&TxEnv` now, because the `SpecId` already comes from `self.spec_id`. Moves towards aligning with alloy-evm's BlockExecutor ownership model where `EvmEnv` is block-scoped and `TxEnv` is tx-scoped. * feat(cheatcodes): make `Cheatcodes` context-generic (#13767) * feat(cheatcodes): make `Cheatcodes` context-generic * fix: merge conflict --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): add `--network` flag to `cast tx` for network-specific raw encoding (#13745) * feat(cast): add --network flag to `cast tx` for multi-network raw encoding Replace the `FoundryNetwork`-based `transaction_raw` workaround with proper network selection via a new `--network` / `-n` CLI flag. This moves raw tx encoding back into `Cast::transaction` dispatching to the correct provider (Ethereum, Optimism, or Tempo) based on the flag. - Add `NetworkVariant` enum (Ethereum/Optimism/Tempo) in foundry-cli opts - use it w/ `--network` through CastSubcommand::Tx and provider construction - Add test for Tempo raw tx encoding (`tx_raw_tempo`) * fix: network num args * feat(cast): `block --raw` network selection (#13754) * rebase PR 13745 * feat(cast): `block` for multi-network raw encoding Co-authored-by: figtracer <1gusredo@gmail.com> * fix: doctest --------- Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make mined_receipts generic (#13761) * chore(evm): split `Executor::env` into `evm_env` and `tx_env` fields (#13773) Split the getters/setters accordingly, and update all call sites. * refactor(cheatcodes): `CheatcodesExecutor` generic (#13774) refactor(cheatcodes): make `CheatcodesExecutor` use generic env types from `ContextTr` Replace concrete `EvmEnv`/`TxEnv` in `with_fresh_nested_evm` with `EvmEnv<::Spec, CTX::Block>` and `CTX::Tx` to support non-Eth networks. Add `FoundryContextTr` trait as fully generic counterpart to the concrete `FoundryContextExt`. Remove unused `clone_to_cfg_env`/`apply_cfg_env` from `FoundryCfg`. * fix(anvil): flaky `test_trace_filter()` (#13764) fix * chore(cast): granular bounds on `Cast` (#13776) * refactor(evm): `FoundryContextExt` generic types (#13778) * fix(cheatcodes): create file in writeJson/writeToml 3-arg overload (#13777) Closes foundry-rs/foundry#13775 Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make EthApi generic over `N: Network` (#13751) * refactor(anvil): make EthApi generic over N: Network * rm todo comments --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): move `CheatsCtxExt` trait to `foundry-evm-core` (#13781) Previously `CheatsCtxExt` was defined in `foundry-cheatcodes`. Move it to `foundry-evm-core`, and rename it to `EthCheatCtx` to make clear it pins the context to Ethereum-specific env types (`BlockEnv`/`TxEnv`/`CfgEnv`) as a temporary alias during the transition to fully generic EVM and cheatcodes. * refactor(evm): make `NestedEvm` trait generic with associated types (#13782) Add `Tx`, `Block`, and `Spec` associated types to `NestedEvm` so each network can specify its own environment types. The trait remains object-safe when used as `dyn NestedEvm`. Update `NestedEvmClosure` to pin the associated types to Eth types, and set the concrete types in the `FoundryEvm` implementation. * refactor(anvil): propagate `EthApi` to all holders (#13783) refactor(anvil): propagate EthApi to all holders * refactor(evm): rename `NestedEvmClosure` and move to `foundry-evm-core` (#13785) refactor(evm): rename `NestedEvmClosure` to `EthNestedEvmClosure` and move to `foundry-evm-core` * refactor(evm): remove `Env` abstraction from `Executor` impl (#13790) * refactor(anvil): remove redundant param (#13792) * refactor(cheatcodes): tighten verbose bounds to `EthCheatCtx` (#13791) * refactor(evm): remove `eth_*_mut()` from `FoundryContextExt` (#13789) * feat(script): generic `TransactionWithMetadata` + generic pprinting `TransactionMaybeSigned` (#13795) * refactor(evm): `DatabaseExt` generic over env types (#13797) refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * test(cast): mark flaky revert_reason_from and wildcard RPC-dependent tail (#13796) * fix(anvil): reject invalid versioned_hashes in beacon blobs endpoint (#13787) * fix(cheatcodes): prevent panic in expectRevert with empty bytes (#13769) * fix(cheatcodes): prevent panic in expectRevert with empty bytes When vm.expectRevert(bytes('')) catches a revert with non-empty data, decode_error in alloy-dyn-abi panics on the empty expected_reason (slice index out of range). Guard the decode_error call with a length check. Closes #13766 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: add regression test for expectRevert empty bytes panic Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: fix snapshot for expectRevert empty bytes regression test Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): add `DB` associated type to `FoundryJournalExt` (#13799) * refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * refactor(evm): replace dyn DatabaseExt in FoundryJournalExt with associated type Replace `&mut dyn DatabaseExt` return type in `FoundryJournalExt` with an associated type `type DB: DatabaseExt`. This removes 3 uses of `dyn DatabaseExt` while remaining backwards-compatible — `&mut DB` auto-coerces to `&mut dyn DatabaseExt` at call sites that still need it. `FoundryJournalExt` is never used as a trait object itself, so adding the associated type has no object-safety impact. --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(anvil): add `AnvilBlockExecutor` and `FoundryReceiptBuilder` (#13788) feat(anvil): add AnvilBlockExecutor and FoundryReceiptBuilder * fix(anvil): swap param order in get_next_block_blob_excess_gas to match callers (#13740) * feat(script): `Network`-generic `ScriptSequence` (#13803) * fix(config): add symmetric serialization for FuzzDictionaryConfig usize fields (#13723) * chore(evm): remove `Env::new_with_spec_id()` method (#13806) * fix(install): clean up nested submodules when using --no-git (#13779) * fix(install): clean up nested submodules when using --no-git Fixes #13688 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b * test: mark regression test as flaky_ instead of ignored Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * style: fix fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): use associated types in `with_cloned_context` (#13802) refactor(evm): use associated types in `with_cloned_context` closure signature * refactor(evm): propagate env types through `FoundryJournalExt` (#13808) * refactor(evm): simplify `FoundryCfg` to marker trait (#13810) * feat(anvil): add `AnvilBlockExecutorFactory` (#13811) * feat(script): `Network`-generic `ScriptSequenceKind` (#13809) * feature(evm): owned `Tx`/`Evm` getters and `Evm` setter for `FoundryContextExt` (#13812) * chore(evm): remove `Env::{clone_evm_and_tx,apply_evm_and_tx}` methods (#13813) * chore(evm): rename `InspectorExt` to `EthInspectorExt` (#13815) * refactor(evm): add `Fork::backend()` accessor (#13817) * refactor(evm): remove `Env` from `commit_transaction` and `replay_until` (#13816) * feat(script): generic `BundledState` impl (#13825) * chore: bump alloy chains (#13827) * chore(evm): remove `Env` abstraction (#13826) * chore(evm): remove `Env` abstraction completely * fix: nit Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(macros): use correct index for tuple struct fields in ConsoleFmt (#13829) * chore(evm): fix stale `Env` references in doc comments (#13828) The combined `Env` wrapper struct was removed in #13826. Update remaining doc comments that still reference it to use the current `EvmEnv`/`TxEnv` names instead. * refactor(anvil): wire `AnvilBlockExecutorFactory` into `Backend::mine_block` (#13814) refactor(anvil): wire AnvilBlockExecutorFactory into Backend::mine_block * feat(script): generic `ScriptTransactionBuilder` (#13830) Remove hardcoded `Ethereum` default from `TransactionWithMetadata`, making `ScriptTransactionBuilder`, `simulate_and_fill`, and broadcast reader calls `Network`-generic. Cheatcode broadcast helpers now explicitly use Ethereum. * fix(`foundryup`): bump foundryup version (#13832) bump foundryup version * fix(foundryup): tempo-foundry now ships all binaries (#13834) nit * chore(deps): bump taiki-e/install-action from 2.68.17 to 2.68.35 (#13821) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump oven-sh/setup-bun from 2.1.2 to 2.2.0 (#13819) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump Swatinem/rust-cache from 2.8.2 to 2.9.1 (#13818) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.5 to 1.44.0 (#13820) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): backend env helpers generic (#13836) Update `update_env_block` and `update_current_env_with_fork_env` to use generic `FoundryBlock`/`FoundryTransaction` bounds and `BlockHeader` trait methods via setters, replacing direct field access on `AnyRpcBlock`. * refactor(anvil): rm `TransactionExecutor`, mv revm callers to `AnvilBlockExecutorFactory` (#13835) refactor(anvil): rm , mv remai callers AnvilBlockExecutorFactory * refactor(script): extract `BrowserSigner` from `MultiWallet` (#13839) * refactor(anvil): mv `Backend` methods to generic impl, thread N through NodeConfig/spawn (#13840) * refactor(anvil): extract `block_env_from_header` utility (#13838) * chore(evm): clean-up `FoundryEvm` impl (#13844) * feat(cheatcodes): add `currentFilePath` cheatcode (#13735) * feat(cheatcodes): add `currentFilePath` cheatcode Add `vm.currentFilePath()` that returns the source file path of the currently running test or script contract, relative to the project root. This enables contracts to locate sibling files (calldata JSONs, markdown descriptions, configs) without hardcoding paths. A common pattern in proposal/script repos is overriding a `dirPath()` function with a hardcoded string — this cheatcode eliminates that boilerplate. Implementation leverages `CheatsConfig::running_artifact` which already tracks the entry-point `ArtifactId`. The `source` field is stripped of the project root prefix to return a consistent relative path. * fix: rustfmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfb9f-8fce-717d-b9de-fedd8ee7d555 Co-authored-by: Amp * fix: remove view from test functions, fix forge-fmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfba7-4986-77c6-9630-574261e9d580 Co-authored-by: Amp --------- Co-authored-by: Alex Netto Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify nested Evm handling (#13846) refactor(evm): simplify nested EVM handling - Replace `to_env()` (returning cfg+block+tx tuple) with `to_evm_env()` returning only `EvmEnv` (cfg+block), since tx is not needed by callers - Remove unused `with_cloned_context` generic type parameter `R` - Drop `set_tx` call in `with_cloned_context` since nested tx modifications should not propagate back to the outer context - Remove `Evm` useless `db_mut`, `precompiles`, `precompiles_mut`, `inspector`, `inspector_mut` provideds methods reimplementations * refactor(cheatcodes): extract fork env helper to reduce duplication (#13848) Extract `fork_env_op` helper that handles the common pattern shared by all fork-switching cheatcodes: clone EVM/tx env → run db operation → write env back. Deduplicates 7 call sites (rollFork × 4, selectFork, createSelectFork × 2). * refactor(cheatcodes): `BroadcastableTransaction` network-agnostic (#13849) refactor(cheatcodes): make BroadcastableTransaction network-agnostic Remove the `Network` type parameter from `BroadcastableTransaction` by storing raw EVM data (from, to, value, input, nonce, gas) as primitive fields instead of wrapping `TransactionMaybeSigned`. This prevents the `Network` generic from leaking through `Cheatcodes`, `RawCallResult`, and `ScriptResult`. The conversion to network-specific types (`TransactionMaybeSigned`) now happens in the script layer (`simulate.rs`) at the point where transactions are handed off to `ScriptTransactionBuilder`. * feat(evm): generic `NestedEvmClosure` (#13850) refactor(evm): generalize NestedEvmClosure with generic type params Replace `EthNestedEvmClosure` (pinned to Eth types) with generic `NestedEvmClosure` to support non-Eth networks. * feat(evm): `FoundryContextExt` generic impl (#13857) * feat(evm): wire Inspector and DatabaseExt Context generics (#13856) * refactor(anvil): make `PendingTransaction` generic over tx type (#13854) * chore(cheatcodes): remove `Cheatcodes` context generic (#13861) * refactor(evm): delegate to alloy's `EthEvmFactory` in `new_evm_with_inspector` (#13860) * chore: add `id` attributes to issue templates (#13864) * chore: add `id` attributes to issue templates Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 * chore: add `id` attributes to required issue template fields only Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` (#13863) * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` * fix: failed merge * fix(traces): fix verbosity trace mode and unify verbosity handling (#13859) * fix(traces): use max instead of min for verbosity trace mode Add regression tests for TraceMode::with_verbosity to ensure verbosity 5 raises the trace mode to at least Steps rather than lowering it. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: James Niken <155266991+dizer-ti@users.noreply.github.com> * fix(traces): unify verbosity handling in TraceMode with_verbosity now raises to RecordStateDiff at verbosity 5, matching the documented behavior of -vvvvv (storage changes + backtraces). This removes the separate with_state_changes(verbosity() > 4) call in trace_mode() which used the global shell verbosity instead of the local evm_opts.verbosity — these two values can diverge when --gas-report or --flamegraph bumps evm_opts.verbosity to 3. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(forge): only show backtraces at verbosity 5 Backtraces require opcode-level step recording which is expensive. Gate backtrace display at verbosity >= 5 (-vvvvv) instead of >= 3, matching the documented behavior and the step recording threshold. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): comprehensive unit tests for TraceMode verbosity levels Tests every verbosity level (0-5) × every TraceMode variant: - verbosity 0-2: noop across all modes - verbosity 3-4: raises to Call, never downgrades - verbosity 5: raises to RecordStateDiff, never downgrades - into_config correctness at each level (record_steps, record_state_diff) - monotonicity invariant: with_verbosity never lowers any mode Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): add integration test for backtrace verbosity levels Runs the same failing test at verbosity 1, 3, 4, and 5 with snapshot assertions to verify backtraces only appear at -vvvvv. Removes the monotonicity unit test in favor of concrete integration coverage. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): keep backtraces at verbosity >= 3, add source locations at 5 Backtraces are useful even without source locations — they show contract/function names at -vvv/-vvvv. Source file locations are only added at -vvvvv when step recording is enabled. Updates integration test to assert all three behaviors: - -vvv: backtrace with function names only - -vvvv: same, plus setup traces - -vvvvv: backtrace with source file:line:col locations Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: fix rustfmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): handle compiler warnings in snapshot Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): add [staticcall] to snapshot for pure function Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * perf(traces): RecordStateDiff should not enable debug-level recording RecordStateDiff now behaves as Steps + state diff, not Debug + state diff. This avoids recording full stack snapshots (memcpy per opcode), memory snapshots, and unfiltered opcode recording at -vvvvv. Before: 50k opcodes → 50k step records with full stack copies (~16MB) After: 50k opcodes → ~500 step records (JUMP/JUMPDEST only), no copies Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): assert Debug mode config is unchanged Locks in that Debug mode still enables full memory/stack snapshots, returndata, immediate bytes, and unfiltered opcode recording — ensuring the RecordStateDiff optimization doesn't affect the debugger or cheatcode recording paths. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): state diff needs unfiltered opcodes for SLOAD/SSTORE record_state_diff captures storage changes in the step() callback, which only fires for recorded opcodes. With a JUMP/JUMPDEST filter, SSTORE steps are skipped and state diffs are lost. RecordStateDiff now disables the opcode filter (like before) but still skips memory/stack snapshots — saving the expensive per-opcode memcpy. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): update JSON fixture for RecordStateDiff config Stack and memory snapshots are no longer recorded at -vvvvv since RecordStateDiff now uses Steps-level config. These fields are null in the JSON output instead of populated. Full debugger-level data is still available via --debug. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> * refactor(anvil): make Block generic over tx type (#13865) * refactor(evm): `FoundryContextExt` bound, use generic `Spec` in `EthCheatCtx` (#13866) * refactor(evm): use `TxEnv` directly in `DatabaseExt` instead of `TransactionRequest` (#13867) * chore(deps): weekly `cargo update` (#13878) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * Update flake.lock (#13877) Co-authored-by: github-actions[bot] * fix(deps): bump to Foundry browser wallet version 0.2.0 (#13890) bump to https://github.com/foundry-rs/foundry-browser-wallet/releases/tag/v0.2.0 * revert: "BroadcastableTransaction network-agnostic" (#13849) (#13891) * perf(anvil): remove redundant clone in create_access_list (#13887) * perf(evm): make `NestedEvm::to_evm_env` consuming to avoid useless clone (#13893) Change `to_evm_env(&self)` to `to_evm_env(self)` so the implementation can use `Evm::finish()` to destructure the EVM without an extra clone of `cfg_env` and `block_env`. Update `with_fresh_nested_evm` to return `EvmEnv` after consuming the EVM post-closure, so callers no longer need to capture the env inside a `NestedEvmClosure` (which only has `&mut dyn NestedEvm` and cannot call a consuming method). Fix the `sub_inner` / `sub_evm_env` ordering in `with_nested_evm` impls so the journal is cloned before `to_evm_env` consumes the EVM. * chore(common): consistency fix on `TransactionMaybeSigned::to()` (#13895) * feat(cheatcodes): Network/Evm generic `Cheatcodes` (#13894) * feat(cheatcodes): Network/Evm generic `Cheatcodes` * fix: tx create detection * fix(ci): adapt to `TransactionMaybeSigned::to()` return type change (#13896) * chore(cheatcodes): remove unused `cheats` param from `CheatcodesExecutor::console_log` (#13897) * refactor(evm): remove `tx_env` param from EVM constructor helper (#13903) Remove the `tx_env` parameter from `new_eth_evm_with_inspector`, `with_cloned_context`, and `with_fresh_nested_evm`. Instead of setting the transaction environment at EVM construction time (where it may become stale or be set redundantly), callers now pass the tx env directly to `evm.transact()` at execution time. This avoids some clones. * chore: remove dead code `paths_config` and `log_status` from inspector stack (#13905) chore: remove dead code in InspectorStackInner Remove two unused methods: - `InspectorStack::paths_config()`: zero callers across the codebase - `InspectorStackInner::log_status()`: zero callers across the codebase Also removes the now-unused `foundry_compilers::ProjectPathsConfig` import. * chore: remove no-op `spec_id` reassignment in `Executor::clone_with_backend` (#13906) chore: remove no-op spec_id reassignment in clone_with_backend After cloning `self.evm_env`, `evm_env.cfg_env.spec` already equals `self.spec_id()` (which simply returns `self.evm_env.cfg_env.spec`). The reassignment is a no-op. * feat(evm): `Backend` generic network (#13579) * feat(evm): `Backend` generic over `Network` * fix(evm): use `AnyNetwork` for fork env init to support non-standard chains When forking non-standard EVM chains (e.g. Moonbeam, Arbitrum), their block headers may lack standard Ethereum fields like `mixHash`. Using the generic `N`-typed provider (defaulting to `Ethereum`) caused deserialization failures for these chains. * feat(evm): add `TryAnyIntoTxEnv` trait for `AnyTxEnvelope` to `TxEnv` conversion Introduces TryAnyIntoTxEnv trait in foundry-evm-core to replace the TxEnv: FromRecoveredTx bound on Backend. This allows Backend to default to AnyNetwork: - TxEnvelope (Ethereum): delegates to FromRecoveredTx directly - AnyTxEnvelope: extracts inner TxEnvelope via as_envelope(), then delegates to FromRecoveredTx; returns error for unknown tx types Co-authored-by: Amp * chore: fix `cargo deny` failure --------- Co-authored-by: Amp * refactor(evm): `NestedEvm` based on revm's `Evm` instead of alloy-evm (#13908) * Add feature: `forge inspect linearization` to see a Solidity contract's linearized inheritance (#13704) * feat: Tempo wallet access key support for cast (#13909) * refactor(script-sequence): rename misleading `opcode` field to `call_kind` (#13907) * fix(fmt): swap valid_before/valid_after in TempoTransaction pretty print (#13910) * fix(fmt): prefer header total_difficulty for totalDifficulty (#13919) * feat(evm): `FoundryContextExt` impls for OP (#13925) * refactor(evm): simplify `NestedEvm` trait by removing `Block` and `Spec` (#13933) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits (#13922) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits Restructure the inspector extension traits for clarity and genericity: - Rename FoundryInspectorExt -> InspectorExt: the context-free base trait providing Foundry-specific inspector methods (console logging, network config) without any Inspector supertrait. - Rename EthInspectorExt -> FoundryInspectorExt: the combined trait that unifies Inspector + InspectorExt. Generic over any CTX that implements FoundryContextExt, rather than being hardcoded to specific BLOCK/TX/SPEC type parameters with for<'a> HRTB baked in. - Introduce EthEvmCtx<'db> type alias for the concrete Eth context (Context), keeping usage sites concise. This makes the trait hierarchy composable for multi-network support while keeping the Eth-specific default path ergonomic via the type alias during refactor. Co-authored-by: Amp * fix: use `EthEvmContext` instead of EthEvmCtx as default --------- Co-authored-by: Amp * fix(evm): restore `code_size_limit` config in `CfgEnv` (#13912) `cfg_env()` unconditionally set `limit_contract_code_size = Some(usize::MAX)`, ignoring the user's `code_size_limit` setting from foundry.toml. Use the configured value when present, falling back to `usize::MAX` (disabled). * perf(evm): remove unnecessary clones in do_call_end/do_create_end (#13913) perf(evm): remove unnecessary clones in `do_call_end`/`do_create_end` Both methods returned `CallOutcome`/`CreateOutcome` but all callers discarded the return value — the outcome is already mutated in-place via `&mut`. This removes the final `outcome.clone()` and changes the early-return in the `#[ret]` macro from `then_some(outcome.clone())` to `then(|| ())`, eliminating up to 2 clones per call/create end. * fix(wallets): browser wallet CLI help heading formatting (#13876) fix: browser wallet CLI help heading formatting - Set proper help_heading on BrowserWalletOpts to use 'Wallet options - browser wallet' consistently - Add next_help_heading to Erc20TxOpts to prevent transaction options from leaking into the browser wallet section Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(forge-script): Tempo access key support for forge script (#13917) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` (#13932) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` Extract the repeated ~20-line drain-and-reset block from 3 locations in the block mining loop into `AnvilInspector::finish_transaction()`. The method prints traces/logs, drains the tracer into `Vec`, and reinstalls fresh tracer + log collector for the next transaction. Introduces `InspectorTxConfig` to carry the print/tracing settings, replacing direct field access into the inspector internals. * style: fix rustfmt * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification (#13924) * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification The old trait converted `TxEnvelope` (the consensus-layer type) into `TxEnv`, requiring callers to separately pass the sender address. This was a leaky abstraction: the sender is already carried by the RPC transaction wrapper (TransactionResponse::from()), so callers had to extract it themselves before calling `try_into_tx_env`. The new trait `TryAnyToTxEnv` is generic over the output TxEnv type and takes the full RPC transaction as receiver, which means: - Implementations for `alloy_rpc_types::Transaction` and `AnyRpcTransaction` can call ToTxEnv / FromRecoveredTx internally without leaking address handling to callers. - A new impl for `op_alloy_rpc_types::Transaction` produces `OpTransaction`, enabling OP-stack fork replay without a separate code path. - `Backend` now constrains `N::TransactionResponse: TryAnyToTxEnv` instead of `N::TxEnvelope: TryAnyIntoTxEnv`, which is both more accurate and unlocks multi-network support. * fix: docs --------- Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify `replay_until` to use single `ForkDB` clone (#13931) * refactor: simplify replay_until to use single ForkDB clone Replace the per-transaction Backend + EVM creation in replay_until with a single cloned ForkDB and one persistent EthEvm instance. Before: for each tx in the block, commit_transaction would clone Fork + JournaledState, spawn a new Backend (including MultiFork), create a new FoundryEvm, transact, then apply_state_changeset. In a block with N preceding transactions, this meant N full clones. After: clone the fork's CacheDB once (SharedBackend is Arc-backed, so only the local cache layer is duplicated), create one EthEvm via EthEvmFactory, call transact_commit for each tx, then replace the fork's DB and refresh journaled states once at the end. Also: - Remove unused tx_env parameter from replay_until - Extract Fork::refresh_journaled_states helper to deduplicate the paired update_state calls in both replay_until and apply_state_changeset - Remove unused NoOpInspector import Amp-Thread-ID: https://ampcode.com/threads/T-019d2512-8074-72ac-92d8-e8f887911219 Co-authored-by: Amp * style: fix rustfmt * refactor: simplify tx collection loop and remove stale comments --------- Co-authored-by: Amp Co-authored-by: zerosnacks * fix(evm): use try_any_to_tx_env in replay_until (#13938) The merge of #13931 introduced a call to the old `try_into_tx_env` method which was renamed to `try_any_to_tx_env` in #13924. This fixes the docs CI build. Amp-Thread-ID: https://ampcode.com/threads/T-019d2952-9ec2-76f9-8f90-b7b3735d4ce3 Co-authored-by: Amp * chore(deps): bump taiki-e/install-action from 2.68.35 to 2.69.8 (#13915) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.35 to 2.69.8. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d...7bc99eee1f1b8902a125006cf790a1f4c8461e63) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.69.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.17.0 to 3.17.1 (#13914) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.17.0 to 3.17.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/131015bad844610e5e6300f8a143bf625d3e74f4...a18f73c54ca8525de051e73c31512a67f44df919) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.17.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): simplify `FoundryContextExt` spec handling (#13935) - Introduced `FoundryContextExt::Spec` type for convenience - Removed complex bounds on `ContextTr::Cfg` using instead the new `Spec` type - Simplified accordingly the rest of the code Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(anvil): remove some intermediate `Env` wrappers, pass `EvmEnv` directly (#13941) refactor(anvil): remove some intermediate Env wrapper, pass EvmEnv directly Replace usage of the `Env` struct wrapper with direct `EvmEnv` references in `new_eth_evm_with_inspector`, `validate_pool_transaction_for`, and `validate_for`. The optimism flag is now passed explicitly as `is_optimism: bool` instead of being extracted from `env.networks`. This eliminates unnecessary `Env::new(...)` construction in several hot paths (block mining, pending block simulation, tx replay) and simplifies the function signatures. * refactor(anvil): remove Env from storage, call env, and executor APIs (#13942) Continue the cleanup of the Env wrapper by passing EvmEnv directly in remaining call sites: - BlockchainStorage::new / Blockchain::new: drop the redundant spec_id parameter, derive it from evm_env.spec_id() instead - build_call_env: return (EvmEnv, OpTransaction) instead of Env, consumers destructure and use directly - new_eth_evm_with_inspector_ref: accept &EvmEnv instead of &Env, derive is_optimism from self.is_optimism() - build_tx_env_for_pending: take is_optimism: bool instead of NetworkConfigs + &EvmEnv - inspect_tx / replay_block_transactions_with_inspector: extract only evm_env from next_env(), avoid cloning the whole Env - api.rs: use spec_id() / chain_id() helpers instead of digging into env.evm_env.cfg_env fields directly - Various minor cleanups: inline single-use env write guards, use self.chain_id() / self.spec_id() helpers consistently --------- Signed-off-by: dependabot[bot] Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Nikki Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Edgar Richards Co-authored-by: Red Swan Co-authored-by: onbjerg Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Cleartext logging of sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/anvil/src/eth/backend/executor.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Change branch trigger from 'main' to 'master' (#403) https://github.com/Dargon789/hardhat-project/issues/2073 https://github.com/Dargon789/hardhat-project/pull/2078/commits/a98e69d5803ec208947f592c91749f248e2dd07c Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revise Foundry benchmark results and system info (#408) Updated benchmark results for Foundry versions v1.3.6 and v1.4.0-rc1, including new test data and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "benches\LATEST.md (#350)" (#409) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. * Update Docker.yml * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock (#419) Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from e80a657d7603606be0c69b117cfdc240f1e6af88 to ff43f160ef7014ae1a1fd85699fb6a44f436135b. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/e80a657d7603606be0c69b117cfdc240f1e6af88...ff43f160ef7014ae1a1fd85699fb6a44f436135b) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: ff43f160ef7014ae1a1fd85699fb6a44f436135b dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/config.yml (#420) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#421) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "benches\LATEST.md (#350)" (#425) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. * chore(deps): bump google-github-actions/get-gke-credentials (#417) Bumps [google-github-actions/get-gke-credentials](https://github.com/google-github-actions/get-gke-credentials) from 2.2.1 to 3.0.0. - [Release notes](https://github.com/google-github-actions/get-gke-credentials/releases) - [Changelog](https://github.com/google-github-actions/get-gke-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/get-gke-credentials/compare/6051de21ad50fbb1767bc93c11357a49082ad116...3da1e46a907576cefaa90c484278bb5b259dd395) --- updated-dependencies: - dependency-name: google-github-actions/get-gke-credentials dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry (#14347) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry When an access key is refreshed but not yet provisioned on-chain, the first charge payment strips key_authorization (assuming provisioned), the server rejects with verification-failed, and the retry must obtain a fresh 402 challenge — the server consumes challenge IDs on first use, so reusing the original challenge would fail again. Amp-Thread-ID: https://ampcode.com/threads/T-019d8bde-8cf7-778c-9c9c-727632e62bd4 Co-authored-by: Amp * refactor(mpp): extract select_challenge helper, improve retry robustness - Extract shared select_challenge() for consistent 402 diagnostics on both initial and verification-failed retry paths - Don't restore key_provisioned(true) on non-402 fresh probe responses to avoid bad state on transient server errors - Add rollback_pending() to key-not-provisioned retry to prevent dirty local state from producing wrong credential shapes Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019d9619-6c24-75af-b1e8-7d02d77e0561 --------- Co-authored-by: Amp * refactor(evm): remove useless Eth/Tempo EVM wrapper structs (#14350) * chore(mpp): add built-in rpc url mapping (#14353) add built-in rpc url mapping * fix(evm): skip isolation for CREATE2 factory redirect calls (#14360) * fix(evm): preserve CREATE2 redirect state across isolated transactions (#14363) * feat(init): add `SignatureVerifier` to tempo example (#14351) * feat(init): add `SignatureVerifier` to tempo example * chore: also call `recover()` * style: typo --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(config): validate `optimizer_runs` does not exceed u32::MAX (#14354) * fix(config): validate `optimizer_runs` does not exceed u32::MAX * fix clippy * ci: remove softprops/action-gh-release dependabot ignore (#14364) The draft-finalize race that caused `Too many retries` in matrix jobs (v2.5.0) was fixed in v2.5.2. We're already on v2.6.1, so the ignore rule is no longer needed. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): mine TIP20 virtual address master salt (#14365) * chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 (#14367) * chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#14368) * chore(deps): bump taiki-e/install-action from 2.75.0 to 2.75.5 (#14369) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.75.0 to 2.75.5. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cf39a74df4a72510be4e5b63348d61067f11e64a...a2352fc6ce487f030a3aa709482d57823eadfb37) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.75.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump anstream from 0.6.21 to 1.0.0 (#14329) Bumps [anstream](https://github.com/rust-cli/anstyle) from 0.6.21 to 1.0.0. - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0) --- updated-dependencies: - dependency-name: anstream dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * Update flake.lock (#14375) Co-authored-by: github-actions[bot] * fix(ci): handle stale branches in bump-tempo workflow (#14377) * fix(config): surface cleanup failures as warnings instead of hard errors (#14379) * fix(config): surface cleanup failures as warnings instead of hard errors Cache cleanup is best-effort by design. Instead of silently swallowing errors or making them fatal, return warnings from Config and emit them via sh_warn! at the CLI layer. All cleanup steps now run even if some fail. Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * fix: address CI - collapse nested ifs, remove disallowed eprintln Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * chore(tempo): bump rev + use extension traits (#14378) * feat(tempo): bump tempo rev and use TempoProviderExt::is_hardfork_active Bumps tempo crates from 6f4f5cc to bb08bb9 (latest main) and replaces the local is_hardfork_active helper with the new trait method on TempoProviderExt, which queries the tempo_forkSchedule RPC directly. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: propagate is_hardfork_active error instead of unwrap_or Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * refactor: use TempoAddressExt in tempo_labels and fee token parsing Replace is_tip20_prefix() with addr.is_tip20() in the TempoLabels inspector and use Address::TIP20_PREFIX instead of hardcoded hex bytes in token_id_to_address. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: enable reth-codec feature on tempo-primitives and fix rustfmt The rev bump split tempo crates into two copies (6f4f5cc for mpp-rs, bb08bb9 for foundry). The bb08bb9 copy lost the reth-codec feature that was previously unified from other workspace members. Explicitly enable it to restore Compact derives on TempoTxEnvelope/TempoHeader. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * chore: remove cargo-update workflow (#14382) Dependabot is now configured for weekly cargo updates, making this workflow redundant. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): return error for eth_getLogs with unknown blockHash instead of empty (#14371) Co-authored-by: Claude Opus 4.7 (1M context) * refactor(cast): cleanup `cast send` (#14385) * refactor(cast): cleanup `cast send` * docs: clarify `TempoNetwork` requirement * style: clippy * fix: review feedback * fix(anvil): refetch full fork blocks with missing tx cache (#14384) * docs: correct Tempo TIP-1022 documentation URL (#14387) * feat(verify): clearer error when `ETHERSCAN_API_KEY` selects etherscan (#14383) * feat: make asm-keccak a default feature in all binaries (#14389) * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(wallets): move `wallets` crate to `foundry-core` (#14348) * fix(cheatcodes): read broadcasts with the active network (#14388) * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#435) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: googleworkspace-bot * doc-script.js --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: googleworkspace-bot Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: cui Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az --- .cargo/config.toml | 6 +- .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 + .deps/remix-tests/remix_tests.sol | 225 ++++ .github/ISSUE_TEMPLATE/bug_report.md | 39 + .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/dependabot.yml | 2 - .github/workflows/Docker.yml | 62 ++ .github/workflows/apisec-scan.yml | 29 + .github/workflows/benchmarks.yml | 6 +- .github/workflows/bump-tempo.yml | 11 +- .github/workflows/codeql.yml | 92 ++ .github/workflows/crate-checks.yml | 2 +- .github/workflows/dependencies.yml | 19 - .github/workflows/deploy.yml | 27 + .github/workflows/docker.yml | 62 ++ .github/workflows/google.yml | 117 +++ .github/workflows/npm.yml | 37 + .github/workflows/release.yml | 8 +- .github/workflows/snyk-container.yml | 55 + .github/workflows/static.yml | 43 + .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 2 +- .gitmodules | 6 + Cargo.lock | 40 +- Cargo.toml | 29 +- benches/src/lib.rs | 16 +- counter/.github/workflows/test.yml | 43 + counter/.gitignore | 14 + counter/README.md | 66 ++ counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 + counter/src/Counter.sol | 14 + counter/test/Counter.t.sol | 24 + crates/anvil/Cargo.toml | 4 +- crates/anvil/core/src/eth/block.rs | 45 +- crates/anvil/core/src/eth/transaction/mod.rs | 22 +- crates/anvil/src/cmd.rs | 17 +- crates/anvil/src/config.rs | 231 ++--- crates/anvil/src/eth/backend/db.rs | 99 +- crates/anvil/src/eth/backend/fork.rs | 17 +- crates/anvil/src/eth/backend/info.rs | 15 +- crates/anvil/src/eth/backend/mem/mod.rs | 2 +- crates/anvil/src/eth/error.rs | 7 + crates/anvil/src/eth/pool/transactions.rs | 50 +- crates/anvil/src/filter.rs | 113 +- crates/anvil/src/pubsub.rs | 52 +- crates/anvil/src/server/beacon/handlers.rs | 42 +- crates/anvil/src/server/beacon/mod.rs | 12 +- crates/anvil/src/server/rpc_handlers.rs | 11 +- crates/anvil/src/service.rs | 71 +- crates/anvil/tests/it/fork.rs | 25 + crates/anvil/tests/it/logs.rs | 18 + crates/cast/Cargo.toml | 4 +- crates/cast/src/call_spec.rs | 34 + crates/cast/src/cmd/access_list.rs | 17 +- crates/cast/src/cmd/batch_mktx.rs | 45 +- crates/cast/src/cmd/batch_send.rs | 73 +- crates/cast/src/cmd/create2.rs | 92 +- crates/cast/src/cmd/erc20.rs | 130 +-- crates/cast/src/cmd/keychain.rs | 64 +- crates/cast/src/cmd/miner.rs | 52 + crates/cast/src/cmd/mod.rs | 1 + crates/cast/src/cmd/send.rs | 65 +- crates/cast/src/cmd/tip20.rs | 243 ----- crates/cast/src/cmd/tip20/create.rs | 113 ++ crates/cast/src/cmd/tip20/mine.rs | 249 +++++ crates/cast/src/cmd/tip20/mod.rs | 111 ++ crates/cast/src/lib.rs | 568 +++++----- crates/cast/src/tempo.rs | 21 + crates/cast/src/tx.rs | 53 +- crates/cast/tests/cli/main.rs | 28 + .../cheatcodes/assets/cheatcodes.schema.json | 2 +- crates/cheatcodes/spec/src/lib.rs | 4 +- crates/cheatcodes/src/config.rs | 3 + crates/cheatcodes/src/evm/fork.rs | 234 +++-- crates/cheatcodes/src/fs.rs | 122 ++- crates/cheatcodes/src/lib.rs | 31 +- crates/cheatcodes/src/test.rs | 52 +- crates/cheatcodes/src/utils.rs | 98 +- crates/chisel/Cargo.toml | 2 +- crates/chisel/src/executor.rs | 46 +- crates/cli/Cargo.toml | 2 + crates/cli/src/opts/network.rs | 16 - crates/cli/src/utils/suggestions.rs | 3 +- crates/cli/src/utils/tempo.rs | 5 +- crates/common/Cargo.toml | 1 + crates/common/fmt/src/ui.rs | 383 ++++--- crates/common/src/contracts.rs | 2 +- crates/common/src/provider/mpp/transport.rs | 340 +++--- crates/common/src/transactions.rs | 281 +++++ crates/config/Cargo.toml | 4 + crates/config/assets/config.schema.json | 6 + crates/config/spec/Cargo.toml | 28 + crates/config/spec/src/lib.rs | 64 ++ crates/config/src/endpoints.rs | 12 + crates/config/src/lib.rs | 130 ++- crates/doc/src/parser/comment.rs | 6 + crates/doc/src/writer/buf_writer.rs | 20 +- crates/evm/core/Cargo.toml | 1 - crates/evm/core/src/backend/cow.rs | 163 +-- crates/evm/core/src/backend/mod.rs | 676 ++++++------ crates/evm/core/src/evm/eth.rs | 146 +-- crates/evm/core/src/evm/mod.rs | 45 +- crates/evm/core/src/evm/op.rs | 52 +- crates/evm/core/src/evm/tempo.rs | 161 +-- crates/evm/core/src/tempo.rs | 17 +- crates/evm/core/src/utils.rs | 46 +- crates/evm/evm/Cargo.toml | 2 + crates/evm/evm/src/executors/builder.rs | 71 +- crates/evm/evm/src/executors/trace.rs | 79 +- crates/evm/evm/src/inspectors/stack.rs | 5 + crates/evm/evm/src/inspectors/tempo_labels.rs | 6 +- crates/evm/traces/src/lib.rs | 136 +-- crates/forge/Cargo.toml | 5 +- crates/forge/assets/tempo/MailTemplate.sol | 53 + crates/forge/assets/tempo/MailTemplate.t.sol | 121 ++- crates/forge/src/args.rs | 6 +- crates/forge/src/cmd/cache.rs | 21 +- crates/forge/src/cmd/config.rs | 2 +- crates/forge/src/cmd/inspect.rs | 175 +--- crates/forge/src/cmd/install.rs | 49 +- crates/forge/src/cmd/test/mod.rs | 159 +-- crates/forge/src/multi_runner.rs | 107 +- crates/forge/tests/cli/install.rs | 45 - crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 +++ crates/script-sequence/src/transaction.rs | 35 +- crates/script/Cargo.toml | 1 + crates/script/src/build.rs | 113 +- crates/script/src/execute.rs | 87 +- crates/script/src/lib.rs | 242 +---- crates/script/src/receipts.rs | 37 +- crates/script/src/runner.rs | 92 +- crates/script/src/sequence.rs | 58 +- crates/script/src/simulate.rs | 86 +- crates/script/src/verify.rs | 41 +- crates/test-utils/src/script.rs | 29 +- crates/test-utils/src/util.rs | 104 +- crates/verify/src/provider.rs | 4 + crates/verify/src/utils.rs | 140 ++- crates/verify/src/verify.rs | 44 +- crates/wallets/Cargo.toml | 69 -- crates/wallets/src/error.rs | 75 -- crates/wallets/src/lib.rs | 31 - crates/wallets/src/opts.rs | 330 ------ crates/wallets/src/signer.rs | 348 ------- crates/wallets/src/tempo.rs | 160 --- crates/wallets/src/utils.rs | 164 --- .../src/wallet_browser/app/assets/banner.png | Bin 559043 -> 0 bytes .../src/wallet_browser/app/assets/index.html | 14 - .../src/wallet_browser/app/assets/logo.png | Bin 49040 -> 0 bytes .../src/wallet_browser/app/assets/main.js | 71 -- .../src/wallet_browser/app/assets/styles.css | 2 - crates/wallets/src/wallet_browser/app/mod.rs | 7 - crates/wallets/src/wallet_browser/error.rs | 28 - crates/wallets/src/wallet_browser/handlers.rs | 195 ---- crates/wallets/src/wallet_browser/mod.rs | 976 ------------------ crates/wallets/src/wallet_browser/opts.rs | 50 - crates/wallets/src/wallet_browser/queue.rs | 83 -- crates/wallets/src/wallet_browser/router.rs | 106 -- crates/wallets/src/wallet_browser/server.rs | 218 ---- crates/wallets/src/wallet_browser/signer.rs | 103 -- crates/wallets/src/wallet_browser/state.rs | 139 --- crates/wallets/src/wallet_browser/types.rs | 127 --- crates/wallets/src/wallet_multi/mod.rs | 621 ----------- crates/wallets/src/wallet_raw/mod.rs | 62 -- deny.toml | 1 + doc/doc-filelist.js | 1 + doc/doc-script.js | 228 ++++ doc/doc-style.css | 403 ++++++++ flake.lock | 18 +- npm/scripts/stage-from-artifact.mjs | 28 +- npm/src/const.mjs | 30 +- 178 files changed, 6161 insertions(+), 8035 deletions(-) create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/dependencies.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .github/workflows/static.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/cast/src/cmd/miner.rs delete mode 100644 crates/cast/src/cmd/tip20.rs create mode 100644 crates/cast/src/cmd/tip20/create.rs create mode 100644 crates/cast/src/cmd/tip20/mine.rs create mode 100644 crates/cast/src/cmd/tip20/mod.rs create mode 100644 crates/cast/src/tempo.rs create mode 100644 crates/common/src/transactions.rs create mode 100644 crates/config/assets/config.schema.json create mode 100644 crates/config/spec/Cargo.toml create mode 100644 crates/config/spec/src/lib.rs create mode 100644 crates/lint/src/linter.rs delete mode 100644 crates/wallets/Cargo.toml delete mode 100644 crates/wallets/src/error.rs delete mode 100644 crates/wallets/src/lib.rs delete mode 100644 crates/wallets/src/opts.rs delete mode 100644 crates/wallets/src/signer.rs delete mode 100644 crates/wallets/src/tempo.rs delete mode 100644 crates/wallets/src/utils.rs delete mode 100644 crates/wallets/src/wallet_browser/app/assets/banner.png delete mode 100644 crates/wallets/src/wallet_browser/app/assets/index.html delete mode 100644 crates/wallets/src/wallet_browser/app/assets/logo.png delete mode 100644 crates/wallets/src/wallet_browser/app/assets/main.js delete mode 100644 crates/wallets/src/wallet_browser/app/assets/styles.css delete mode 100644 crates/wallets/src/wallet_browser/app/mod.rs delete mode 100644 crates/wallets/src/wallet_browser/error.rs delete mode 100644 crates/wallets/src/wallet_browser/handlers.rs delete mode 100644 crates/wallets/src/wallet_browser/mod.rs delete mode 100644 crates/wallets/src/wallet_browser/opts.rs delete mode 100644 crates/wallets/src/wallet_browser/queue.rs delete mode 100644 crates/wallets/src/wallet_browser/router.rs delete mode 100644 crates/wallets/src/wallet_browser/server.rs delete mode 100644 crates/wallets/src/wallet_browser/signer.rs delete mode 100644 crates/wallets/src/wallet_browser/state.rs delete mode 100644 crates/wallets/src/wallet_browser/types.rs delete mode 100644 crates/wallets/src/wallet_multi/mod.rs delete mode 100644 crates/wallets/src/wallet_raw/mod.rs create mode 100644 doc/doc-filelist.js create mode 100644 doc/doc-script.js create mode 100644 doc/doc-style.css diff --git a/.cargo/config.toml b/.cargo/config.toml index 1ca035a75d78c..ca844fb33e15b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,12 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" bless-lints = "test -p forge --test ui -- --bless" +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" + # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..53a505774ac88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a5a1184e9ac0f..1cd9930619634 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,8 +6,6 @@ updates: interval: "weekly" cooldown: default-days: 7 - ignore: - - dependency-name: "softprops/action-gh-release" - package-ecosystem: "cargo" directory: "/" diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index bebe02d5c50d4..96af1f4ab9a1e 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -131,7 +131,7 @@ jobs: run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Upload benchmark results as artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: benchmark-results path: | @@ -172,7 +172,7 @@ jobs: - name: Create PR for manual runs if: github.event_name == 'workflow_dispatch' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const branchName = '${{ needs.run-benchmarks.outputs.branch_name }}'; @@ -200,7 +200,7 @@ jobs: - name: Comment on PR if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; diff --git a/.github/workflows/bump-tempo.yml b/.github/workflows/bump-tempo.yml index e8f0ed8e4533f..ffbd68d772d81 100644 --- a/.github/workflows/bump-tempo.yml +++ b/.github/workflows/bump-tempo.yml @@ -34,15 +34,22 @@ jobs: run: | BRANCH_NAME="deps/bump-tempo-${LATEST_REV:0:7}" + # Skip if a PR already exists for this branch + EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number') + if [[ -n "$EXISTING_PR" ]]; then + echo "PR #${EXISTING_PR} already exists for ${BRANCH_NAME}, skipping." + exit 0 + fi + # Configure git git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Create and push branch + # Create and push branch (force-push in case a stale branch exists from a prior failed run) git checkout -b "$BRANCH_NAME" git add Cargo.toml Cargo.lock git commit -m "chore(deps): bump tempo dependencies to ${LATEST_REV:0:7}" - git push origin "$BRANCH_NAME" + git push --force-with-lease origin "$BRANCH_NAME" # Create PR gh pr create \ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index 4567d8da156e0..781d63759045e 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -29,7 +29,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@a2352fc6ce487f030a3aa709482d57823eadfb37 # v2.75.16 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml deleted file mode 100644 index 374b1781cac98..0000000000000 --- a/.github/workflows/dependencies.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Runs `cargo update` periodically. - -name: dependencies - -permissions: {} - -on: - schedule: - - cron: "0 0 * * SUN" # Run weekly on Sundays at midnight UTC - workflow_dispatch: # Needed so we can run it manually - -jobs: - update: - uses: tempoxyz/ci/.github/workflows/cargo-update-pr.yml@268b3ce142717ff86c58fbbcc3abc3f109f0fb8d # main - permissions: - contents: write - pull-requests: write - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..569c7b00a4b7f --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@3da1e46a907576cefaa90c484278bb5b259dd395' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 9b9b798435c90..007ef5f4286aa 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -233,9 +233,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15bb33f30f01b..2ad324e7ef9c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: # the changelog. - name: Create build-specific nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: TAG_NAME: ${{ steps.release_info.outputs.tag_name }} with: @@ -232,7 +232,7 @@ jobs: printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" - name: Upload build artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: retention-days: 1 name: ${{ steps.artifacts.outputs.file_name }} @@ -321,14 +321,14 @@ jobs: # Moves the `nightly` tag to `HEAD` - name: Move nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const moveTag = require('./.github/scripts/move-tag.js') await moveTag({ github, context }, 'nightly') - name: Delete old nightlies - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const prunePrereleases = require('./.github/scripts/prune-prereleases.js') diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..0ba82305f82b2 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index 6b32d5172c357..af4415b1e8ba1 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@a2352fc6ce487f030a3aa709482d57823eadfb37 # v2.75.16 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 982b470064582..afd91d3d72c05 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,7 +37,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@a2352fc6ce487f030a3aa709482d57823eadfb37 # v2.75.16 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6f2ccdf56bc1..f9ccb850dbc5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,7 +73,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@a2352fc6ce487f030a3aa709482d57823eadfb37 # v2.75.16 with: tool: nextest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index 9adfcb569b8bd..d15f7c092e642 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4954,6 +4954,7 @@ dependencies = [ "strsim", "strum", "tempfile", + "tempo-primitives", "tikv-jemallocator", "tokio", "tracing", @@ -4993,7 +4994,7 @@ dependencies = [ "alloy-transport", "alloy-transport-ipc", "alloy-transport-ws", - "anstream 0.6.21", + "anstream 1.0.0", "anstyle", "axum", "chrono", @@ -5248,6 +5249,7 @@ dependencies = [ "serde", "serde_json", "tempo-precompiles", + "tempo-primitives", "thiserror 2.0.18", "tokio", "tracing", @@ -5308,7 +5310,6 @@ dependencies = [ "serde", "serde_json", "tempo-alloy", - "tempo-chainspec", "tempo-contracts", "tempo-evm", "tempo-precompiles", @@ -5542,7 +5543,8 @@ dependencies = [ [[package]] name = "foundry-wallets" -version = "1.6.0" +version = "0.1.0" +source = "git+https://github.com/foundry-rs/foundry-core?rev=7f401c1397af90a0a94ef7424a48bbf3dc0248cc#7f401c1397af90a0a94ef7424a48bbf3dc0248cc" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5565,9 +5567,6 @@ dependencies = [ "dirs", "eth-keystore", "eyre", - "foundry-common", - "foundry-config", - "reqwest 0.13.2", "rpassword", "serde", "serde_json", @@ -11296,7 +11295,7 @@ dependencies = [ [[package]] name = "tempo-alloy" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", "alloy-contract", @@ -11314,6 +11313,7 @@ dependencies = [ "derive_more", "futures", "serde", + "tempo-chainspec", "tempo-contracts", "tempo-primitives", "tracing", @@ -11322,7 +11322,7 @@ dependencies = [ [[package]] name = "tempo-chainspec" version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-eips 2.0.0", "alloy-evm", @@ -11340,8 +11340,8 @@ dependencies = [ [[package]] name = "tempo-consensus" -version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11357,7 +11357,7 @@ dependencies = [ [[package]] name = "tempo-contracts" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-contract", "alloy-primitives", @@ -11367,8 +11367,8 @@ dependencies = [ [[package]] name = "tempo-evm" -version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11394,8 +11394,8 @@ dependencies = [ [[package]] name = "tempo-precompiles" -version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy", "alloy-evm", @@ -11414,8 +11414,8 @@ dependencies = [ [[package]] name = "tempo-precompiles-macros" -version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy", "proc-macro2", @@ -11426,7 +11426,7 @@ dependencies = [ [[package]] name = "tempo-primitives" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", "alloy-eips 2.0.0", @@ -11456,8 +11456,8 @@ dependencies = [ [[package]] name = "tempo-revm" -version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=39b9262#39b92627745eb1b0dbb99650d990ef9168376ded" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", "alloy-evm", diff --git a/Cargo.toml b/Cargo.toml index f83c8594b4a89..70d6eba6a6aec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/cli/", "crates/common/", "crates/config/", + "crates/config/spec/", "crates/debugger/", "crates/doc/", "crates/evm/core/", @@ -326,6 +327,7 @@ foundry-cli-markdown = { path = "crates/cli-markdown" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } +foundry-config-spec = { path = "crates/config/spec" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } foundry-evm-abi = { path = "crates/evm/abi" } @@ -338,7 +340,6 @@ foundry-evm-sancov = { path = "crates/evm/sancov" } foundry-evm-traces = { path = "crates/evm/traces" } foundry-macros = { path = "crates/macros" } foundry-test-utils = { path = "crates/test-utils" } -foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } foundry-primitives = { path = "crates/primitives" } @@ -355,6 +356,9 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features "rustls", ] } +## foundry-core +foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "7f401c1397af90a0a94ef7424a48bbf3dc0248cc", default-features = false } + ## alloy alloy-consensus = { version = "2.0.0", default-features = false } alloy-contract = { version = "2.0.0", default-features = false } @@ -419,7 +423,7 @@ revm-inspectors = { version = "0.38.1", features = ["serde"] } op-revm = { version = "18.0.0", default-features = false } ## cli -anstream = "0.6" +anstream = "1.0" anstyle = "1.0" dialoguer = { version = "0.12", default-features = false, features = [ "password", @@ -513,15 +517,16 @@ mpp = { git = "https://github.com/tempoxyz/mpp-rs", rev = "310c9a1f3fe485fa9c7a8 "client", "reqwest-rustls-tls", ] } -tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262", default-features = false } -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262", default-features = false, features = [ +tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9", default-features = false } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9", default-features = false, features = [ "serde", + "reth-codec", ] } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262", default-features = false } -tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262", default-features = false } -tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262", default-features = false, features = ["serde"] } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262" } -tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262" } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9", default-features = false } +tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9", default-features = false } +tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9", default-features = false, features = ["serde"] } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9" } +tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9" } ## Pinned dependencies. Enabled for the workspace in crates/test-utils. @@ -608,9 +613,9 @@ alloy-op-hardforks = { git = "https://github.com/foundry-rs/optimism", branch = # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "b139c57c2b54bc06a9e4c9783941f5bbd4bd3a1f" } ## tempo — unify crates.io versions (pulled by mpp) with git rev -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262" } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262" } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "39b9262" } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9" } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "bb08bb9" } # solar solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ca429168c2095..ab3bec614e3cd 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 0d0111c8f5636..b664266450d07 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -124,8 +124,8 @@ op-alloy-rpc-types.workspace = true tempo-alloy.workspace = true [features] -default = ["cli", "jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] +default = ["cli", "jemalloc", "asm-keccak"] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 60a51e7dda28b..6460971d0d2fb 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -4,33 +4,32 @@ use alloy_consensus::{ }; use alloy_eips::eip2718::Encodable2718; use alloy_network::Network; -use foundry_primitives::FoundryTxEnvelope; +use foundry_primitives::FoundryNetwork; +use std::fmt::Debug; use crate::eth::transaction::MaybeImpersonatedTransaction; -/// Type alias for a block containing potentially impersonated transactions. -pub type Block = alloy_consensus::Block>; +/// Type alias for Ethereum Block with Anvil's transaction type +pub type Block = alloy_consensus::Block; + +/// Anvil's concrete block info type. +pub type BlockInfo = TypedBlockInfo; /// Container type that gathers all block data, generic over a [`Network`]. #[derive(Clone, Debug)] -pub struct BlockInfo { - pub block: Block, +pub struct TypedBlockInfo { + pub block: alloy_consensus::Block>, pub transactions: Vec, pub receipts: Vec, } -/// Helper function to create a new block with Header and Anvil transactions, generic over the -/// transaction envelope with a default of [`FoundryTxEnvelope`]. +/// Helper function to create a new block with Header and Anvil transactions /// /// Note: if the `impersonate-tx` feature is enabled this will also accept /// `MaybeImpersonatedTransaction`. -pub fn create_block( - mut header: Header, - transactions: impl IntoIterator, -) -> Block +pub fn create_block(mut header: Header, transactions: impl IntoIterator) -> Block where - Tx: Encodable2718, - T: Into>, + T: Into, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); let transactions_root = calculate_transaction_root(&transactions); @@ -42,6 +41,24 @@ where Block::new(header, body) } +/// Generic helper function to create a block with any transaction type that supports encoding. +pub fn create_typed_block( + mut header: Header, + transactions: impl IntoIterator, +) -> alloy_consensus::Block +where + T: Encodable2718, +{ + let transactions: Vec<_> = transactions.into_iter().collect(); + let transactions_root = calculate_transaction_root(&transactions); + + header.transactions_root = transactions_root; + header.ommers_hash = EMPTY_OMMER_ROOT_HASH; + + let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None }; + alloy_consensus::Block::new(header, body) +} + #[cfg(test)] mod tests { use alloy_primitives::{ @@ -202,7 +219,7 @@ mod tests { let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - let block = ::decode(&mut data.as_slice()).unwrap(); + let block = Block::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 056d7a9602855..02bcc749bcb82 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -20,7 +20,7 @@ use std::ops::Deref; /// This is a helper that carries the `impersonated` sender so that the right hash /// can be created. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct MaybeImpersonatedTransaction { +pub struct MaybeImpersonatedTransaction { transaction: T, impersonated_sender: Option
, } @@ -33,17 +33,17 @@ impl Typed2718 for MaybeImpersonatedTransaction { impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction - pub const fn new(transaction: T) -> Self { + pub fn new(transaction: T) -> Self { Self { transaction, impersonated_sender: None } } /// Creates a new impersonated transaction wrapper using the given sender - pub const fn impersonated(transaction: T, impersonated_sender: Address) -> Self { + pub fn impersonated(transaction: T, impersonated_sender: Address) -> Self { Self { transaction, impersonated_sender: Some(impersonated_sender) } } /// Returns whether the transaction is impersonated - pub const fn is_impersonated(&self) -> bool { + pub fn is_impersonated(&self) -> bool { self.impersonated_sender.is_some() } @@ -93,14 +93,14 @@ impl Encodable for MaybeImpersonatedTransaction { } } -impl From> for FoundryTxEnvelope { - fn from(value: MaybeImpersonatedTransaction) -> Self { +impl From for FoundryTxEnvelope { + fn from(value: MaybeImpersonatedTransaction) -> Self { value.transaction } } -impl From for MaybeImpersonatedTransaction { - fn from(value: T) -> Self { +impl From for MaybeImpersonatedTransaction { + fn from(value: FoundryTxEnvelope) -> Self { Self::new(value) } } @@ -127,7 +127,7 @@ impl Deref for MaybeImpersonatedTransaction { /// Queued transaction #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingTransaction { +pub struct PendingTransaction { /// The actual transaction pub transaction: MaybeImpersonatedTransaction, /// the recovered sender of this transaction @@ -137,11 +137,11 @@ pub struct PendingTransaction { } impl PendingTransaction { - pub const fn hash(&self) -> &TxHash { + pub fn hash(&self) -> &TxHash { &self.hash } - pub const fn sender(&self) -> &Address { + pub fn sender(&self) -> &Address { &self.sender } } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 68bd83a1e6e89..9dac717e1f8a2 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -4,7 +4,6 @@ use crate::{ eth::{EthApi, backend::db::SerializableState, pool::transactions::TransactionOrder}, }; use alloy_genesis::Genesis; -use alloy_network::Network; use alloy_primitives::{B256, U256, utils::Unit}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; @@ -14,7 +13,6 @@ use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; use foundry_evm_networks::NetworkConfigs; -use foundry_primitives::FoundryReceiptEnvelope; use futures::FutureExt; use rand_08::{SeedableRng, rngs::StdRng}; use std::{ @@ -29,7 +27,6 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tempo_chainspec::hardfork::TempoHardfork; use tokio::time::{Instant, Interval}; #[derive(Clone, Debug, Parser)] @@ -231,8 +228,6 @@ impl NodeArgs { Some(hf) => { if self.evm.networks.is_optimism() { Some(OpHardfork::from_str(hf)?.into()) - } else if self.evm.networks.is_tempo() { - Some(TempoHardfork::from_str(hf)?.into()) } else { Some(EthereumHardfork::from_str(hf)?.into()) } @@ -642,17 +637,17 @@ impl AnvilEvmArgs { } /// Helper type to periodically dump the state of the chain to disk -struct PeriodicStateDumper { +struct PeriodicStateDumper { in_progress_dump: Option + Send + Sync + 'static>>>, - api: EthApi, + api: EthApi, dump_state: Option, preserve_historical_states: bool, interval: Interval, } -impl> PeriodicStateDumper { +impl PeriodicStateDumper { fn new( - api: EthApi, + api: EthApi, dump_state: Option, interval: Duration, preserve_historical_states: bool, @@ -676,7 +671,7 @@ impl> PeriodicStateDumper, dump_state: PathBuf, preserve_historical_states: bool) { + async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) { trace!(path=?dump_state, "Dumping state on shutdown"); match api.serialized_state(preserve_historical_states).await { Ok(state) => { @@ -694,7 +689,7 @@ impl> PeriodicStateDumper> Future for PeriodicStateDumper { +impl Future for PeriodicStateDumper { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index c9b63f08cb2f5..56a5e4ddcfa81 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -3,6 +3,7 @@ use crate::{ eth::{ backend::{ db::{Db, SerializableState}, + env::Env, fork::{ClientFork, ClientForkConfig}, genesis::GenesisConfig, mem::fork_db::ForkedDatabase, @@ -37,18 +38,19 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{FoundryHardfork, OpHardfork}, - utils::{ - apply_chain_and_block_specific_env_changes, block_env_from_header, - get_blob_base_fee_update_fraction, + hardfork::{ + FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag, + spec_id_from_ethereum_hardfork, }, + utils::{apply_chain_and_block_specific_env_changes, get_blob_base_fee_update_fraction}, }; -use foundry_primitives::FoundryTxEnvelope; +use foundry_primitives::FoundryNetwork; use itertools::Itertools; +use op_revm::OpTransaction; use parking_lot::RwLock; use rand_08::thread_rng; use revm::{ - context::{BlockEnv, CfgEnv}, + context::{BlockEnv, CfgEnv, TxEnv}, context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId, }; @@ -60,7 +62,6 @@ use std::{ sync::Arc, time::Duration, }; -use tempo_chainspec::hardfork::TempoHardfork; use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; @@ -424,12 +425,6 @@ impl NodeConfig { Self { enable_tracing: true, port: 0, silent: true, ..Default::default() } } - /// Returns a test config with Tempo network enabled. - #[doc(hidden)] - pub fn test_tempo() -> Self { - Self { networks: NetworkConfigs::with_tempo(), ..Self::test() } - } - /// Returns a new config which does not initialize any accounts on node startup. pub fn empty_state() -> Self { Self { @@ -511,35 +506,20 @@ impl Default for NodeConfig { impl NodeConfig { /// Returns the memory limit of the node #[must_use] - pub const fn with_memory_limit(mut self, mems_value: Option) -> Self { + pub fn with_memory_limit(mut self, mems_value: Option) -> Self { self.memory_limit = mems_value; self } - - /// Returns the base fee to use. - /// - /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+). + /// Returns the base fee to use pub fn get_base_fee(&self) -> u64 { - let default = if self.networks.is_tempo() { - TempoHardfork::from(self.get_hardfork()).base_fee() - } else { - INITIAL_BASE_FEE - }; self.base_fee .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) - .unwrap_or(default) + .unwrap_or(INITIAL_BASE_FEE) } - /// Returns the gas price to use. - /// - /// In Tempo mode, defaults to the hardfork-specific base fee. + /// Returns the base fee to use pub fn get_gas_price(&self) -> u128 { - let default = if self.networks.is_tempo() { - TempoHardfork::from(self.get_hardfork()).base_fee() as u128 - } else { - INITIAL_GAS_PRICE - }; - self.gas_price.unwrap_or(default) + self.gas_price.unwrap_or(INITIAL_GAS_PRICE) } pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { @@ -571,21 +551,18 @@ impl NodeConfig { if self.networks.is_optimism() { return OpHardfork::default().into(); } - if self.networks.is_tempo() { - return TempoHardfork::default().into(); - } EthereumHardfork::default().into() } /// Sets a custom code size limit #[must_use] - pub const fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { + pub fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { self.code_size_limit = code_size_limit; self } /// Disables code size limit #[must_use] - pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { + pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { if disable_code_size_limit { self.code_size_limit = Some(usize::MAX); } @@ -636,7 +613,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub const fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } @@ -645,7 +622,7 @@ impl NodeConfig { /// /// If set to `true` block gas limit will not be enforced #[must_use] - pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { + pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { self.disable_block_gas_limit = disable_block_gas_limit; self } @@ -654,14 +631,14 @@ impl NodeConfig { /// /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) #[must_use] - pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { + pub fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { self.enable_tx_gas_limit = enable_tx_gas_limit; self } /// Sets the gas price #[must_use] - pub const fn with_gas_price(mut self, gas_price: Option) -> Self { + pub fn with_gas_price(mut self, gas_price: Option) -> Self { self.gas_price = gas_price; self } @@ -685,7 +662,7 @@ impl NodeConfig { /// Sets the max number of transactions in a block #[must_use] - pub const fn with_max_transactions(mut self, max_transactions: Option) -> Self { + pub fn with_max_transactions(mut self, max_transactions: Option) -> Self { if let Some(max_transactions) = max_transactions { self.max_transactions = max_transactions; } @@ -704,14 +681,14 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub const fn with_base_fee(mut self, base_fee: Option) -> Self { + pub fn with_base_fee(mut self, base_fee: Option) -> Self { self.base_fee = base_fee; self } /// Disable the enforcement of a minimum suggested priority fee #[must_use] - pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { self.disable_min_priority_fee = disable_min_priority_fee; self } @@ -757,7 +734,7 @@ impl NodeConfig { /// Sets the hardfork #[must_use] - pub const fn with_hardfork(mut self, hardfork: Option) -> Self { + pub fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } @@ -811,21 +788,21 @@ impl NodeConfig { /// If set to `true` auto mining will be disabled #[must_use] - pub const fn with_no_mining(mut self, no_mining: bool) -> Self { + pub fn with_no_mining(mut self, no_mining: bool) -> Self { self.no_mining = no_mining; self } /// Sets the slots in an epoch #[must_use] - pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { self.slots_in_an_epoch = slots_in_an_epoch; self } /// Sets the port to use #[must_use] - pub const fn with_port(mut self, port: u16) -> Self { + pub fn with_port(mut self, port: u16) -> Self { self.port = port; self } @@ -850,7 +827,7 @@ impl NodeConfig { } #[must_use] - pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { + pub fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { self.no_storage_caching = no_storage_caching; self } @@ -886,7 +863,7 @@ impl NodeConfig { /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub const fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { + pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id; self } @@ -900,7 +877,7 @@ impl NodeConfig { /// Sets the `fork_request_timeout` to use for requests #[must_use] - pub const fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { + pub fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { if let Some(fork_request_timeout) = fork_request_timeout { self.fork_request_timeout = fork_request_timeout; } @@ -909,7 +886,7 @@ impl NodeConfig { /// Sets the `fork_request_retries` to use for spurious networks #[must_use] - pub const fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { + pub fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { if let Some(fork_request_retries) = fork_request_retries { self.fork_request_retries = fork_request_retries; } @@ -918,7 +895,7 @@ impl NodeConfig { /// Sets the initial `fork_retry_backoff` for rate limits #[must_use] - pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { + pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { if let Some(fork_retry_backoff) = fork_retry_backoff { self.fork_retry_backoff = fork_retry_backoff; } @@ -929,10 +906,7 @@ impl NodeConfig { /// /// See also, #[must_use] - pub const fn fork_compute_units_per_second( - mut self, - compute_units_per_second: Option, - ) -> Self { + pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { self.compute_units_per_second = compute_units_per_second; } @@ -941,35 +915,35 @@ impl NodeConfig { /// Sets whether to enable tracing #[must_use] - pub const fn with_tracing(mut self, enable_tracing: bool) -> Self { + pub fn with_tracing(mut self, enable_tracing: bool) -> Self { self.enable_tracing = enable_tracing; self } /// Sets whether to enable steps tracing #[must_use] - pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { + pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { self.enable_steps_tracing = enable_steps_tracing; self } /// Sets whether to print `console.log` invocations to stdout. #[must_use] - pub const fn with_print_logs(mut self, print_logs: bool) -> Self { + pub fn with_print_logs(mut self, print_logs: bool) -> Self { self.print_logs = print_logs; self } /// Sets whether to print traces to stdout. #[must_use] - pub const fn with_print_traces(mut self, print_traces: bool) -> Self { + pub fn with_print_traces(mut self, print_traces: bool) -> Self { self.print_traces = print_traces; self } /// Sets whether to enable autoImpersonate #[must_use] - pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { + pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { self.enable_auto_impersonate = enable_auto_impersonate; self } @@ -988,7 +962,7 @@ impl NodeConfig { } #[must_use] - pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { + pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { self.transaction_order = transaction_order; self } @@ -1027,14 +1001,14 @@ impl NodeConfig { /// Sets whether to disable the default create2 deployer #[must_use] - pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { self.disable_default_create2_deployer = yes; self } /// Sets whether to disable pool balance checks #[must_use] - pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { + pub fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { self.disable_pool_balance_checks = yes; self } @@ -1048,33 +1022,19 @@ impl NodeConfig { /// Enable features for provided networks. #[must_use] - pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self { + pub fn with_networks(mut self, networks: NetworkConfigs) -> Self { self.networks = networks; self } - /// Enable Tempo network features. - #[must_use] - pub fn with_tempo(mut self) -> Self { - self.networks = NetworkConfigs::with_tempo(); - self - } - - /// Enable Optimism network features. - #[must_use] - pub fn with_optimism(mut self) -> Self { - self.networks = NetworkConfigs::with_optimism(); - self - } - /// Makes the node silent to not emit anything on stdout #[must_use] - pub const fn silent(self) -> Self { + pub fn silent(self) -> Self { self.set_silent(true) } #[must_use] - pub const fn set_silent(mut self, silent: bool) -> Self { + pub fn set_silent(mut self, silent: bool) -> Self { self.silent = silent; self } @@ -1093,13 +1053,7 @@ impl NodeConfig { /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> Result> - where - N: alloy_network::Network< - TxEnvelope = foundry_primitives::FoundryTxEnvelope, - ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope, - >, - { + pub(crate) async fn setup(&mut self) -> Result> { // configure the revm environment let mut cfg = CfgEnv::default(); @@ -1122,13 +1076,20 @@ impl NodeConfig { } let spec_id = cfg.spec; - let mut evm_env = EvmEnv::new( - cfg, - BlockEnv { - gas_limit: self.gas_limit(), - basefee: self.get_base_fee(), + let mut env = Env::new( + EvmEnv::new( + cfg, + BlockEnv { + gas_limit: self.gas_limit(), + basefee: self.get_base_fee(), + ..Default::default() + }, + ), + OpTransaction { + base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, ..Default::default() }, + self.networks, ); let base_fee_params: BaseFeeParams = @@ -1146,7 +1107,7 @@ impl NodeConfig { let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { - self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await? + self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? } else { (Arc::new(TokioRwLock::new(Box::::default())), None) }; @@ -1156,16 +1117,16 @@ impl NodeConfig { // --chain-id flag gets precedence over the genesis.json chain id // if self.chain_id.is_none() { - evm_env.cfg_env.chain_id = genesis.config.chain_id; + env.evm_env.cfg_env.chain_id = genesis.config.chain_id; } - evm_env.block_env.timestamp = U256::from(genesis.timestamp); + env.evm_env.block_env.timestamp = U256::from(genesis.timestamp); if let Some(base_fee) = genesis.base_fee_per_gas { - evm_env.block_env.basefee = base_fee.try_into()?; + env.evm_env.block_env.basefee = base_fee.try_into()?; } if let Some(number) = genesis.number { - evm_env.block_env.number = U256::from(number); + env.evm_env.block_env.number = U256::from(number); } - evm_env.block_env.beneficiary = genesis.coinbase; + env.evm_env.block_env.beneficiary = genesis.coinbase; } let genesis = GenesisConfig { @@ -1188,8 +1149,7 @@ impl NodeConfig { // only memory based backend for now let backend = mem::Backend::with_genesis( db, - Arc::new(RwLock::new(evm_env)), - self.networks, + Arc::new(RwLock::new(env)), genesis, fees, Arc::new(RwLock::new(fork)), @@ -1215,6 +1175,10 @@ impl NodeConfig { .wrap_err("failed to create default create2 deployer")?; } + if let Some(state) = self.init_state.clone() { + backend.load_state(state).await.wrap_err("failed to load init state")?; + } + Ok(backend) } @@ -1227,10 +1191,10 @@ impl NodeConfig { pub async fn setup_fork_db( &mut self, eth_rpc_url: String, - evm_env: &mut EvmEnv, + env: &mut Env, fees: &FeeManager, ) -> Result<(Arc>>, Option)> { - let (db, config) = self.setup_fork_db_config(eth_rpc_url, evm_env, fees).await?; + let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); let fork = ClientFork::new(config, Arc::clone(&db)); Ok((db, Some(fork))) @@ -1244,7 +1208,7 @@ impl NodeConfig { pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, - evm_env: &mut EvmEnv, + env: &mut Env, fees: &FeeManager, ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); @@ -1269,8 +1233,16 @@ impl NodeConfig { let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { + // Auto-adjust hardfork if not specified, but only if we're forking mainnet. let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; + if alloy_chains::NamedChain::Mainnet == chain_id { + let hardfork: EthereumHardfork = + ethereum_hardfork_from_block_tag(fork_block_number); + + env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); + self.hardfork = Some(FoundryHardfork::Ethereum(hardfork)); + } Some(U256::from(chain_id)) } else { None @@ -1312,12 +1284,17 @@ latest block number: {latest_block}" let gas_limit = self.fork_gas_limit(&block); self.gas_limit = Some(gas_limit); - evm_env.block_env = BlockEnv { + env.evm_env.block_env = BlockEnv { + number: U256::from(fork_block_number), + timestamp: U256::from(block.header.timestamp()), + difficulty: block.header.difficulty(), + // ensures prevrandao is set + prevrandao: Some(block.header.mix_hash().unwrap_or_default()), gas_limit, // Keep previous `coinbase` and `basefee` value - beneficiary: evm_env.block_env.beneficiary, - basefee: evm_env.block_env.basefee, - ..block_env_from_header(&block.header) + beneficiary: env.evm_env.block_env.beneficiary, + basefee: env.evm_env.block_env.basefee, + ..Default::default() }; // Determine chain_id early so we can use it consistently @@ -1332,25 +1309,17 @@ latest block number: {latest_block}" // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); - evm_env.cfg_env.chain_id = chain_id; + env.evm_env.cfg_env.chain_id = chain_id; + env.tx.base.chain_id = chain_id.into(); chain_id }; - // Auto-detect hardfork from chain activation data if not explicitly set. - if self.hardfork.is_none() - && let Some(hardfork) = - FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp()) - { - evm_env.cfg_env.spec = SpecId::from(hardfork); - self.hardfork = Some(hardfork); - } - // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() && let Some(base_fee) = block.header.base_fee_per_gas() { self.base_fee = Some(base_fee); - evm_env.block_env.basefee = base_fee; + env.evm_env.block_env.basefee = base_fee; // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = fees.get_next_block_base_fee_per_gas( @@ -1369,7 +1338,7 @@ latest block number: {latest_block}" // Derive blob params using the fork block timestamp regardless of explicit base fee. let blob_params = get_blob_params(chain_id, block.header.timestamp()); - evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( + env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( blob_excess_gas, blob_params.update_fraction as u64, )); @@ -1396,13 +1365,13 @@ latest block number: {latest_block}" let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id - apply_chain_and_block_specific_env_changes::( - evm_env, + apply_chain_and_block_specific_env_changes::( + &mut env.evm_env, &block, self.networks, ); - let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(env.evm_env.block_env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) } else { @@ -1434,7 +1403,7 @@ latest block number: {latest_block}" compute_units_per_second: self.compute_units_per_second, total_difficulty: block.header.total_difficulty.unwrap_or_default(), blob_gas_used: block.header.blob_gas_used().map(|g| g as u128), - blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price, + blob_excess_gas_and_price: env.evm_env.block_env.blob_excess_gas_and_price, force_transactions, }; @@ -1482,7 +1451,7 @@ latest block number: {latest_block}" async fn derive_block_and_transactions( fork_choice: &ForkChoice, provider: &Arc, -) -> eyre::Result<(BlockNumber, Option>>)> { +) -> eyre::Result<(BlockNumber, Option>)> { match fork_choice { ForkChoice::Block(block_number) => { let block_number = *block_number; @@ -1544,7 +1513,7 @@ pub enum ForkChoice { impl ForkChoice { /// Returns the block number to fork from - pub const fn block_number(&self) -> Option { + pub fn block_number(&self) -> Option { match self { Self::Block(block_number) => Some(*block_number), Self::Transaction(_) => None, @@ -1552,7 +1521,7 @@ impl ForkChoice { } /// Returns the transaction hash to fork from - pub const fn transaction_hash(&self) -> Option { + pub fn transaction_hash(&self) -> Option { match self { Self::Block(_) => None, Self::Transaction(transaction_hash) => Some(*transaction_hash), @@ -1582,12 +1551,12 @@ pub struct PruneStateHistoryConfig { impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported - pub const fn is_state_history_supported(&self) -> bool { + pub fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } /// Returns true if this setting was enabled. - pub const fn is_config_enabled(&self) -> bool { + pub fn is_config_enabled(&self) -> bool { self.enabled } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index de9ad434252f3..14691862a225e 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -8,7 +8,6 @@ use std::{ use alloy_consensus::{BlockBody, Header}; use alloy_eips::eip4895::Withdrawals; -use alloy_network::Network; use alloy_primitives::{ Address, B256, Bytes, U256, keccak256, map::{AddressMap, HashMap}, @@ -22,7 +21,7 @@ use foundry_common::errors::FsPathError; use foundry_evm::backend::{ BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot, }; -use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; +use foundry_primitives::{FoundryNetwork, FoundryReceiptEnvelope, FoundryTxEnvelope}; use revm::{ Database, DatabaseCommit, bytecode::Bytecode, @@ -91,79 +90,6 @@ pub trait MaybeForkedDatabase { fn maybe_inner(&self) -> Result<&BlockchainDb, String>; } -/// `dyn Db` satisfies all `alloy_evm::Database` requirements via its supertraits, but the -/// blanket impl has an implicit `Sized` bound. Provide an explicit impl. -impl alloy_evm::Database for dyn Db {} - -/// A wrapper around [`CacheDB`]. -#[derive(Debug)] -pub struct AnvilCacheDB(pub CacheDB); - -impl> AnvilCacheDB { - pub fn new(inner: T) -> Self { - Self(CacheDB::new(inner)) - } -} - -impl> std::ops::Deref for AnvilCacheDB { - type Target = CacheDB; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl> std::ops::DerefMut for AnvilCacheDB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl + fmt::Debug> Database for AnvilCacheDB { - type Error = DatabaseError; - - fn basic(&mut self, address: Address) -> Result, Self::Error> { - self.0.basic(address) - } - - fn code_by_hash(&mut self, code_hash: B256) -> Result { - self.0.code_by_hash(code_hash) - } - - fn storage(&mut self, address: Address, index: U256) -> Result { - self.0.storage(address, index) - } - - fn block_hash(&mut self, number: u64) -> Result { - self.0.block_hash(number) - } -} - -impl> DatabaseRef for AnvilCacheDB { - type Error = DatabaseError; - - fn basic_ref(&self, address: Address) -> Result, Self::Error> { - self.0.basic_ref(address) - } - - fn code_by_hash_ref(&self, code_hash: B256) -> Result { - self.0.code_by_hash_ref(code_hash) - } - - fn storage_ref(&self, address: Address, index: U256) -> Result { - self.0.storage_ref(address, index) - } - - fn block_hash_ref(&self, number: u64) -> Result { - self.0.block_hash_ref(number) - } -} - -impl + fmt::Debug> DatabaseCommit for AnvilCacheDB { - fn commit(&mut self, changes: revm::state::EvmState) { - self.0.commit(changes) - } -} - /// This bundles all required revm traits pub trait Db: DatabaseRef @@ -226,7 +152,7 @@ pub trait Db: /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { - for (addr, account) in state.accounts { + for (addr, account) in state.accounts.into_iter() { let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) @@ -250,7 +176,7 @@ pub trait Db: }, ); - for (k, v) in account.storage { + for (k, v) in account.storage.into_iter() { self.set_storage_at(addr, k, v)?; } } @@ -504,7 +430,6 @@ impl TryFrom for BlockEnv { basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0), difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO), prevrandao: legacy.prevrandao.or(Some(B256::ZERO)), - slot_num: 0, blob_excess_gas_and_price: legacy .blob_excess_gas_and_price .map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice)) @@ -651,7 +576,7 @@ where #[serde(untagged)] pub enum SerializableTransactionType { TypedTransaction(FoundryTxEnvelope), - MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), + MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -683,13 +608,13 @@ impl From for Block { } } -impl From> for SerializableTransactionType { - fn from(transaction: MaybeImpersonatedTransaction) -> Self { +impl From for SerializableTransactionType { + fn from(transaction: MaybeImpersonatedTransaction) -> Self { Self::MaybeImpersonatedTransaction(transaction) } } -impl From for MaybeImpersonatedTransaction { +impl From for MaybeImpersonatedTransaction { fn from(transaction: SerializableTransactionType) -> Self { match transaction { SerializableTransactionType::TypedTransaction(tx) => Self::new(tx), @@ -706,10 +631,8 @@ pub struct SerializableTransaction { pub block_number: u64, } -impl> From> - for SerializableTransaction -{ - fn from(transaction: MinedTransaction) -> Self { +impl From> for SerializableTransaction { + fn from(transaction: MinedTransaction) -> Self { Self { info: transaction.info, receipt: transaction.receipt, @@ -719,9 +642,7 @@ impl> From> From - for MinedTransaction -{ +impl From for MinedTransaction { fn from(transaction: SerializableTransaction) -> Self { Self { info: transaction.info, diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index bd3d1292f01b5..b1bff4e66cd21 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -442,8 +442,10 @@ impl ClientFork { &self, hash: B256, ) -> Result, TransportError> { - if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(self.convert_to_full_block(block))); + if let Some(block) = self.storage_read().blocks.get(&hash).cloned() + && let Some(block) = self.convert_to_full_block(block) + { + return Ok(Some(block)); } self.fetch_full_block(hash).await } @@ -479,8 +481,9 @@ impl ClientFork { .get(&block_number) .copied() .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) + && let Some(block) = self.convert_to_full_block(block) { - return Ok(Some(self.convert_to_full_block(block))); + return Ok(Some(block)); } self.fetch_full_block(block_number).await @@ -509,15 +512,15 @@ impl ClientFork { } /// Converts a block of hashes into a full block - fn convert_to_full_block(&self, mut block: N::BlockResponse) -> N::BlockResponse { + fn convert_to_full_block(&self, mut block: N::BlockResponse) -> Option { let storage = self.storage.read(); let transactions = block .transactions() .hashes() - .filter_map(|hash| storage.transactions.get(&hash).cloned()) - .collect(); + .map(|hash| storage.transactions.get(&hash).cloned()) + .collect::>>()?; *block.transactions_mut() = BlockTransactions::Full(transactions); - block + Some(block) } } diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index c739cd26d1de6..72acc5e62a39e 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -1,10 +1,10 @@ //! Handler that can get current storage related data use crate::mem::Backend; -use alloy_consensus::TxReceipt; use alloy_network::{AnyRpcBlock, Network}; use alloy_primitives::B256; use anvil_core::eth::block::Block; +use foundry_primitives::{FoundryNetwork, FoundryReceiptEnvelope}; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. @@ -18,7 +18,7 @@ pub struct StorageInfo { } impl StorageInfo { - pub(crate) const fn new(backend: Arc>) -> Self { + pub(crate) fn new(backend: Arc>) -> Self { Self { backend } } @@ -39,17 +39,16 @@ impl StorageInfo { } } -impl StorageInfo -where - N::ReceiptEnvelope: TxReceipt, -{ +impl StorageInfo { + // TODO: receipts methods require N::ReceiptEnvelope generalization + /// Returns the receipts of the current block - pub fn current_receipts(&self) -> Option> { + pub fn current_receipts(&self) -> Option> { self.backend.mined_receipts(self.backend.best_hash()) } /// Returns the receipts of the block with the given hash - pub fn receipts(&self, hash: B256) -> Option> { + pub fn receipts(&self, hash: B256) -> Option> { self.backend.mined_receipts(hash) } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 991d48c7fd6a6..ed928ef25213d 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -2371,7 +2371,7 @@ where return Ok(fork.logs(&filter).await?); } - Ok(Vec::new()) + Err(BlockchainError::UnknownBlock) } /// Returns the logs that match the filter in the given range of blocks diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index d693590ab0b41..3b2ada43d7731 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -73,6 +73,8 @@ pub enum BlockchainError { BlockOutOfRange(u64, u64), #[error("Resource not found")] BlockNotFound, + #[error("unknown block")] + UnknownBlock, /// Thrown when a requested transaction is not found #[error("transaction not found")] TransactionNotFound, @@ -649,6 +651,11 @@ impl ToRpcResponseResult for Result { message: "filter not found".into(), data: None, }, + err @ BlockchainError::UnknownBlock => RpcError { + code: ErrorCode::ServerError(-32000), + message: err.to_string().into(), + data: None, + }, } .into(), } diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index df65822e1eab3..81059c7b48d20 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,16 +1,12 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; -use alloy_consensus::{ - Transaction, Typed2718, - crypto::RecoveryError, - transaction::{SignerRecoverable, TxHashRef}, -}; +use alloy_consensus::{Transaction, Typed2718}; use alloy_network::AnyRpcTransaction; use alloy_primitives::{ Address, TxHash, map::{HashMap, HashSet}, }; -use alloy_rlp::Encodable; use anvil_core::eth::transaction::PendingTransaction; +use foundry_primitives::FoundryTxEnvelope; use parking_lot::RwLock; use std::{cmp::Ordering, collections::BTreeSet, fmt, str::FromStr, sync::Arc, time::Instant}; @@ -77,7 +73,7 @@ pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] -pub struct PoolTransaction { +pub struct PoolTransaction { /// the pending eth transaction pub pending_transaction: PendingTransaction, /// Markers required by the transaction @@ -91,7 +87,7 @@ pub struct PoolTransaction { // == impl PoolTransaction == impl PoolTransaction { - pub const fn new(transaction: PendingTransaction) -> Self { + pub fn new(transaction: PendingTransaction) -> Self { Self { pending_transaction: transaction, requires: vec![], @@ -101,7 +97,7 @@ impl PoolTransaction { } /// Returns the hash of this transaction - pub const fn hash(&self) -> TxHash { + pub fn hash(&self) -> TxHash { *self.pending_transaction.hash() } } @@ -132,15 +128,10 @@ impl fmt::Debug for PoolTransaction { } } -impl TryFrom for PoolTransaction -where - T: SignerRecoverable + TxHashRef + Encodable + TryFrom, - >::Error: Into, - RecoveryError: Into, -{ +impl TryFrom for PoolTransaction { type Error = eyre::Error; fn try_from(value: AnyRpcTransaction) -> Result { - let typed_transaction = T::try_from(value).map_err(Into::into)?; + let typed_transaction = FoundryTxEnvelope::try_from(value)?; let pending_transaction = PendingTransaction::new(typed_transaction)?; Ok(Self { pending_transaction, @@ -155,7 +146,7 @@ where /// /// Keeps a set of transactions that are waiting for other transactions #[derive(Clone, Debug)] -pub struct PendingTransactions { +pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, /// mapping of the markers of a transaction to the hash of the transaction @@ -276,7 +267,7 @@ impl PendingTransactions { .and_then(|hash| self.waiting_queue.get(hash)) { // check if underpriced - if tx.transaction.max_fee_per_gas() <= replace.transaction.max_fee_per_gas() { + if tx.transaction.max_fee_per_gas() < replace.transaction.max_fee_per_gas() { warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]", tx.transaction.hash()); return Err(PoolError::ReplacementUnderpriced(tx.transaction.hash())); } @@ -298,7 +289,7 @@ impl PendingTransactions { /// A transaction in the pool #[derive(Clone)] -pub struct PendingPoolTransaction { +pub struct PendingPoolTransaction { pub transaction: Arc>, /// markers required and have not been satisfied yet by other transactions in the pool pub missing_markers: HashSet, @@ -346,7 +337,7 @@ impl fmt::Debug for PendingPoolTransaction { } } -pub struct TransactionsIterator { +pub struct TransactionsIterator { all: HashMap>, awaiting: HashMap)>, independent: BTreeSet>, @@ -403,7 +394,7 @@ impl Iterator for TransactionsIterator { /// transactions that are ready to be included in a block. #[derive(Clone, Debug)] -pub struct ReadyTransactions { +pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// /// this way we can determine when transactions where submitted to the pool @@ -466,11 +457,11 @@ impl ReadyTransactions { self.ready_tx.read().get(hash).cloned() } - pub const fn provided_markers(&self) -> &HashMap { + pub fn provided_markers(&self) -> &HashMap { &self.provided_markers } - const fn next_id(&mut self) -> u64 { + fn next_id(&mut self) -> u64 { let id = self.id; self.id = self.id.wrapping_add(1); id @@ -515,7 +506,11 @@ impl ReadyTransactions { if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } - tx2.unlocks.is_empty().then(|| tx2.transaction.transaction.provides.clone()) + if tx2.unlocks.is_empty() { + Some(tx2.transaction.transaction.provides.clone()) + } else { + None + } }; // find previous transactions @@ -689,8 +684,9 @@ impl ReadyTransactions { { warn!(target: "txpool", "ready replacement transaction underpriced [{:?}]", tx.hash()); return Err(PoolError::ReplacementUnderpriced(tx.hash())); + } else { + trace!(target: "txpool", "replacing ready transaction [{:?}] with higher gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash()); } - trace!(target: "txpool", "replacing ready transaction [{:?}] with higher gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash()); } unlocked_tx.extend(to_remove.unlocks.iter().copied()) @@ -708,7 +704,7 @@ impl ReadyTransactions { /// A reference to a transaction in the pool #[derive(Debug)] -pub struct PoolTransactionRef { +pub struct PoolTransactionRef { /// actual transaction pub transaction: Arc>, /// identifier used to internally compare the transaction in the pool @@ -745,7 +741,7 @@ impl Ord for PoolTransactionRef { } #[derive(Debug)] -pub struct ReadyTransaction { +pub struct ReadyTransaction { /// ref to the actual transaction pub transaction: PoolTransactionRef, /// tracks the transactions that get unlocked by this transaction diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index 98db8a5455df9..9c4de9ae7b519 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -4,8 +4,6 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::ToRpcResponseResult}, pubsub::filter_logs, }; -use alloy_consensus::TxReceipt; -use alloy_network::Network; use alloy_primitives::{TxHash, map::HashMap}; use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; @@ -13,6 +11,7 @@ use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; +use foundry_primitives::FoundryNetwork; use futures::{Stream, StreamExt, channel::mpsc::Receiver}; use std::{ pin::Pin, @@ -23,34 +22,23 @@ use std::{ use tokio::sync::Mutex; /// Type alias for filters identified by their id and their expiration timestamp -type FilterMap = Arc, Instant)>>>; +type FilterMap = Arc>>; /// timeout after which to remove an active filter if it wasn't polled since then pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters -pub struct Filters { +#[derive(Clone, Debug)] +pub struct Filters { /// all currently active filters - active_filters: FilterMap, + active_filters: FilterMap, /// How long we keep a live the filter after the last poll keepalive: Duration, } -impl Clone for Filters { - fn clone(&self) -> Self { - Self { active_filters: self.active_filters.clone(), keepalive: self.keepalive } - } -} - -impl std::fmt::Debug for Filters { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Filters").field("keepalive", &self.keepalive).finish_non_exhaustive() - } -} - -impl Filters { +impl Filters { /// Adds a new `EthFilter` to the set - pub async fn add_filter(&self, filter: EthFilter) -> String { + pub async fn add_filter(&self, filter: EthFilter) -> String { let id = new_id(); trace!(target: "node::filter", "Adding new filter id {}", id); let mut filters = self.active_filters.lock().await; @@ -58,6 +46,26 @@ impl Filters { id } + pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { + { + let mut filters = self.active_filters.lock().await; + if let Some((filter, deadline)) = filters.get_mut(id) { + let resp = filter + .next() + .await + .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); + *deadline = self.next_deadline(); + return resp; + } + } + warn!(target: "node::filter", "No filter found for {}", id); + ResponseResult::error(RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }) + } + /// Returns the original `Filter` of an `eth_newFilter` pub async fn get_log_filter(&self, id: &str) -> Option { let filters = self.active_filters.lock().await; @@ -68,13 +76,13 @@ impl Filters { } /// Removes the filter identified with the `id` - pub async fn uninstall_filter(&self, id: &str) -> Option> { + pub async fn uninstall_filter(&self, id: &str) -> Option { trace!(target: "node::filter", "Uninstalling filter id {}", id); self.active_filters.lock().await.remove(id).map(|(f, _)| f) } /// The duration how long to keep alive stale filters - pub const fn keep_alive(&self) -> Duration { + pub fn keep_alive(&self) -> Duration { self.keepalive } @@ -98,32 +106,7 @@ impl Filters { } } -impl Filters -where - N::ReceiptEnvelope: TxReceipt, -{ - pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { - { - let mut filters = self.active_filters.lock().await; - if let Some((filter, deadline)) = filters.get_mut(id) { - let resp = filter - .next() - .await - .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); - *deadline = self.next_deadline(); - return resp; - } - } - warn!(target: "node::filter", "No filter found for {}", id); - ResponseResult::error(RpcError { - code: ErrorCode::ServerError(-32000), - message: "filter not found".into(), - data: None, - }) - } -} - -impl Default for Filters { +impl Default for Filters { fn default() -> Self { Self { active_filters: Arc::new(Default::default()), @@ -138,26 +121,14 @@ fn new_id() -> String { } /// Represents a poll based filter -pub enum EthFilter { - Logs(Box>), +#[derive(Debug)] +pub enum EthFilter { + Logs(Box), Blocks(NewBlockNotifications), PendingTransactions(Receiver), } -impl std::fmt::Debug for EthFilter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Logs(_) => f.debug_tuple("Logs").finish(), - Self::Blocks(_) => f.debug_tuple("Blocks").finish(), - Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(), - } - } -} - -impl Stream for EthFilter -where - N::ReceiptEnvelope: TxReceipt, -{ +impl Stream for EthFilter { type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -183,11 +154,12 @@ where } /// Listens for new blocks and matching logs emitted in that block -pub struct LogsFilter { +#[derive(Debug)] +pub struct LogsFilter { /// listener for new blocks pub blocks: NewBlockNotifications, /// accessor for block storage - pub storage: StorageInfo, + pub storage: StorageInfo, /// matcher with all provided filter params pub filter: FilteredParams, /// existing logs that matched the filter when the listener was installed @@ -196,16 +168,7 @@ pub struct LogsFilter { pub historic: Option>, } -impl std::fmt::Debug for LogsFilter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive() - } -} - -impl LogsFilter -where - N::ReceiptEnvelope: TxReceipt, -{ +impl LogsFilter { /// Returns all the logs since the last time this filter was polled pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index 4ceb8be0bfc64..c62427eb9a4c8 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -3,11 +3,12 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, }; use alloy_consensus::{BlockHeader, TxReceipt}; -use alloy_network::{AnyRpcTransaction, Network}; +use alloy_network::AnyRpcTransaction; use alloy_primitives::{B256, TxHash}; use alloy_rpc_types::{FilteredParams, Log, Transaction, pubsub::SubscriptionResult}; use anvil_core::eth::{block::Block, subscription::SubscriptionId}; use anvil_rpc::{request::Version, response::ResponseResult}; +use foundry_primitives::FoundryNetwork; use futures::{Stream, StreamExt, channel::mpsc::Receiver, ready}; use serde::Serialize; use std::{ @@ -18,27 +19,16 @@ use std::{ use tokio::sync::mpsc::UnboundedReceiver; /// Listens for new blocks and matching logs emitted in that block -pub struct LogsSubscription { +#[derive(Debug)] +pub struct LogsSubscription { pub blocks: NewBlockNotifications, - pub storage: StorageInfo, + pub storage: StorageInfo, pub filter: FilteredParams, pub queued: VecDeque, pub id: SubscriptionId, } -impl std::fmt::Debug for LogsSubscription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("LogsSubscription") - .field("filter", &self.filter) - .field("id", &self.id) - .finish_non_exhaustive() - } -} - -impl LogsSubscription -where - N::ReceiptEnvelope: TxReceipt, -{ +impl LogsSubscription { fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if let Some(log) = self.queued.pop_front() { @@ -81,7 +71,7 @@ pub struct EthSubscriptionResponse { } impl EthSubscriptionResponse { - pub const fn new(params: EthSubscriptionParams) -> Self { + pub fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } } } @@ -95,28 +85,15 @@ pub struct EthSubscriptionParams { } /// Represents an ethereum Websocket subscription -pub enum EthSubscription { - Logs(Box>), - Header(NewBlockNotifications, StorageInfo, SubscriptionId), +#[derive(Debug)] +pub enum EthSubscription { + Logs(Box), + Header(NewBlockNotifications, StorageInfo, SubscriptionId), PendingTransactions(Receiver, SubscriptionId), FullPendingTransactions(UnboundedReceiver, SubscriptionId), } -impl std::fmt::Debug for EthSubscription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Logs(_) => f.debug_tuple("Logs").finish(), - Self::Header(..) => f.debug_tuple("Header").finish(), - Self::PendingTransactions(..) => f.debug_tuple("PendingTransactions").finish(), - Self::FullPendingTransactions(..) => f.debug_tuple("FullPendingTransactions").finish(), - } - } -} - -impl EthSubscription -where - N::ReceiptEnvelope: TxReceipt, -{ +impl EthSubscription { fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { Self::Logs(listener) => listener.poll(cx), @@ -159,10 +136,7 @@ where } } -impl Stream for EthSubscription -where - N::ReceiptEnvelope: TxReceipt, -{ +impl Stream for EthSubscription { type Item = serde_json::Value; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { diff --git a/crates/anvil/src/server/beacon/handlers.rs b/crates/anvil/src/server/beacon/handlers.rs index 73d533fa606c3..9310a7960293c 100644 --- a/crates/anvil/src/server/beacon/handlers.rs +++ b/crates/anvil/src/server/beacon/handlers.rs @@ -1,7 +1,6 @@ use super::{error::BeaconError, utils::must_be_ssz}; use crate::eth::EthApi; use alloy_eips::BlockId; -use alloy_network::Network; use alloy_primitives::{B256, aliases::B32}; use alloy_rpc_types_beacon::{ genesis::{GenesisData, GenesisResponse}, @@ -21,8 +20,8 @@ use std::{collections::HashMap, str::FromStr as _}; /// This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead. /// /// GET /eth/v1/beacon/blob_sidecars/{block_id} -pub async fn handle_get_blob_sidecars( - State(_api): State>, +pub async fn handle_get_blob_sidecars( + State(_api): State, Path(_block_id): Path, Query(_params): Query>, ) -> Response { @@ -33,9 +32,9 @@ pub async fn handle_get_blob_sidecars( /// Handles incoming Beacon API requests for blobs /// /// GET /eth/v1/beacon/blobs/{block_id} -pub async fn handle_get_blobs( +pub async fn handle_get_blobs( headers: HeaderMap, - State(api): State>, + State(api): State, Path(block_id): Path, Query(versioned_hashes): Query>, ) -> Response { @@ -44,31 +43,12 @@ pub async fn handle_get_blobs( return BeaconError::invalid_block_id(block_id).into_response(); }; - // Parse versioned hashes from query parameters - // Supports comma-separated format: ?versioned_hashes=0x...,0x... - let versioned_hashes: Vec = match versioned_hashes.get("versioned_hashes") { - Some(s) => { - let mut hashes = Vec::new(); - for hash in s.split(',') { - let hash = hash.trim(); - if hash.is_empty() { - continue; - } - match B256::from_str(hash) { - Ok(h) => hashes.push(h), - Err(_) => { - return BeaconError::new( - super::error::BeaconErrorCode::BadRequest, - format!("Invalid versioned hash: {hash}"), - ) - .into_response(); - } - } - } - hashes - } - None => Vec::new(), - }; + // Parse indices from query parameters + // Supports both comma-separated (?indices=1,2,3) and repeated parameters (?indices=1&indices=2) + let versioned_hashes: Vec = versioned_hashes + .get("versioned_hashes") + .map(|s| s.split(',').filter_map(|hash| B256::from_str(hash.trim()).ok()).collect()) + .unwrap_or_default(); // Get the blob sidecars using existing EthApi logic match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) { @@ -94,7 +74,7 @@ pub async fn handle_get_blobs( /// Only returns the `genesis_time`, other fields are set to zero. /// /// GET /eth/v1/beacon/genesis -pub async fn handle_get_genesis(State(api): State>) -> Response { +pub async fn handle_get_genesis(State(api): State) -> Response { match api.anvil_get_genesis_time() { Ok(genesis_time) => Json(GenesisResponse { data: GenesisData { diff --git a/crates/anvil/src/server/beacon/mod.rs b/crates/anvil/src/server/beacon/mod.rs index f0138747c8b00..9100186d436d5 100644 --- a/crates/anvil/src/server/beacon/mod.rs +++ b/crates/anvil/src/server/beacon/mod.rs @@ -3,20 +3,16 @@ use axum::{Router, routing::get}; use crate::eth::EthApi; -use alloy_network::Network; mod error; mod handlers; mod utils; /// Configures an [`axum::Router`] that handles Beacon REST API calls. -pub fn router(api: EthApi) -> Router { +pub fn router(api: EthApi) -> Router { Router::new() - .route( - "/eth/v1/beacon/blob_sidecars/{block_id}", - get(handlers::handle_get_blob_sidecars::), - ) - .route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs::)) - .route("/eth/v1/beacon/genesis", get(handlers::handle_get_genesis::)) + .route("/eth/v1/beacon/blob_sidecars/{block_id}", get(handlers::handle_get_blob_sidecars)) + .route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs)) + .route("/eth/v1/beacon/genesis", get(handlers::handle_get_genesis)) .with_state(api) } diff --git a/crates/anvil/src/server/rpc_handlers.rs b/crates/anvil/src/server/rpc_handlers.rs index c531ba2705713..060cd682a4277 100644 --- a/crates/anvil/src/server/rpc_handlers.rs +++ b/crates/anvil/src/server/rpc_handlers.rs @@ -11,18 +11,17 @@ use alloy_rpc_types::{ use anvil_core::eth::{EthPubSub, EthRequest, EthRpcCall, subscription::SubscriptionId}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; -use foundry_primitives::FoundryNetwork; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] pub struct HttpEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub const fn new(api: EthApi) -> Self { + pub fn new(api: EthApi) -> Self { Self { api } } } @@ -40,12 +39,12 @@ impl RpcHandler for HttpEthRpcHandler { #[derive(Clone)] pub struct PubSubEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl PubSubEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub const fn new(api: EthApi) -> Self { + pub fn new(api: EthApi) -> Self { Self { api } } @@ -134,7 +133,7 @@ impl PubSubEthRpcHandler { impl PubSubRpcHandler for PubSubEthRpcHandler { type Request = EthRpcCall; type SubscriptionId = SubscriptionId; - type Subscription = EthSubscription; + type Subscription = EthSubscription; async fn on_request(&self, request: Self::Request, cx: PubSubContext) -> ResponseResult { trace!(target: "rpc", "received pubsub request {:?}", request); diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index 88a47b55a1418..c0071505157ae 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -3,7 +3,6 @@ use crate::{ NodeResult, eth::{ - backend::validate::TransactionValidator, fees::FeeHistoryService, miner::Miner, pool::{Pool, transactions::PoolTransaction}, @@ -11,9 +10,7 @@ use crate::{ filter::Filters, mem::{Backend, storage::MinedBlockOutcome}, }; -use alloy_consensus::TxReceipt; -use alloy_network::Network; -use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; +use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; use futures::{FutureExt, Stream, StreamExt}; use std::{ collections::VecDeque, @@ -29,35 +26,28 @@ use tokio::{task::JoinHandle, time::Interval}; /// transactions for the next block, then those transactions are handed off to the backend to /// construct a new block, if all transactions were successfully included in a new block they get /// purged from the `Pool`. -pub struct NodeService -where - N::ReceiptEnvelope: TxReceipt, -{ +pub struct NodeService { /// The pool that holds all transactions. - pool: Arc>, + pool: Arc>, /// Creates new blocks. - block_producer: BlockProducer, + block_producer: BlockProducer, /// The miner responsible to select transactions from the `pool`. - miner: Miner, + miner: Miner, /// Maintenance task for fee history related tasks. - fee_history: FeeHistoryService, + fee_history: FeeHistoryService, /// Tracks all active filters - filters: Filters, + filters: Filters, /// The interval at which to check for filters that need to be evicted filter_eviction_interval: Interval, } -impl NodeService -where - Backend: TransactionValidator, - N: Network, -{ +impl NodeService { pub fn new( - pool: Arc>, - backend: Arc>, - miner: Miner, - fee_history: FeeHistoryService, - filters: Filters, + pool: Arc, + backend: Arc>, + miner: Miner, + fee_history: FeeHistoryService, + filters: Filters, ) -> Self { let start = tokio::time::Instant::now() + filters.keep_alive(); let filter_eviction_interval = tokio::time::interval_at(start, filters.keep_alive()); @@ -72,11 +62,7 @@ where } } -impl Future for NodeService -where - Backend: TransactionValidator, - N: Network, -{ +impl Future for NodeService { type Output = NodeResult<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -115,35 +101,27 @@ where } } -type MiningResult = (MinedBlockOutcome<::TxEnvelope>, Arc>); +type MiningResult = (MinedBlockOutcome, Arc>); /// A type that exclusively mines one block at a time #[must_use = "streams do nothing unless polled"] -struct BlockProducer { +struct BlockProducer { /// Holds the backend if no block is being mined - idle_backend: Option>>, + idle_backend: Option>>, /// Single active future that mines a new block - block_mining: Option>>, + block_mining: Option>>, /// backlog of sets of transactions ready to be mined - queued: VecDeque>>>, + queued: VecDeque>>>, } -impl BlockProducer -where - Backend: TransactionValidator, - N: Network, -{ - fn new(backend: Arc>) -> Self { +impl BlockProducer { + fn new(backend: Arc>) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } } } -impl Stream for BlockProducer -where - Backend: TransactionValidator + Send + Sync + 'static, - N: Network + 'static, -{ - type Item = MinedBlockOutcome; +impl Stream for BlockProducer { + type Item = MinedBlockOutcome; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); @@ -179,8 +157,9 @@ where panic!("miner task failed: {err}"); } }; + } else { + pin.block_mining = Some(mining) } - pin.block_mining = Some(mining) } Poll::Pending diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 1a81b79a9a3c3..e802d8a19be7b 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1449,6 +1449,31 @@ async fn test_immutable_fork_transaction_hash() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_block_by_number_full_refetches_missing_cached_transactions() { + let (api, _) = spawn(fork_config()).await; + + let block = + api.block_by_number_full(BlockNumberOrTag::Number(BLOCK_NUMBER)).await.unwrap().unwrap(); + let block_txs = block.transactions.as_transactions().unwrap(); + let original_len = block_txs.len(); + let missing_hash = *block_txs[0].tx_hash(); + + let fork = api.backend.get_fork().unwrap(); + { + let mut storage = fork.storage.write(); + assert!(storage.transactions.remove(&missing_hash).is_some()); + } + + let refreshed = + api.block_by_number_full(BlockNumberOrTag::Number(BLOCK_NUMBER)).await.unwrap().unwrap(); + let refreshed_txs = refreshed.transactions.as_transactions().unwrap(); + + assert_eq!(refreshed_txs.len(), original_len); + assert_eq!(refreshed_txs[0].tx_hash(), &missing_hash); + assert!(fork.storage.read().transactions.contains_key(&missing_hash)); +} + // #[tokio::test(flavor = "multi_thread")] async fn test_fork_query_at_fork_block() { diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index bea3fdec96ad6..df5086e505f9a 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -10,6 +10,7 @@ use alloy_provider::Provider; use alloy_rpc_types::{BlockNumberOrTag, Filter}; use anvil::{NodeConfig, spawn}; use futures::StreamExt; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { @@ -195,3 +196,20 @@ async fn watch_events() { assert_eq!(log.1.block_hash.unwrap(), hash); } } + +#[tokio::test(flavor = "multi_thread")] +async fn get_logs_unknown_block_hash_returns_error() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let unknown_hash = + B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let filter = Filter::new().at_block_hash(unknown_hash); + + let err = provider.get_logs(&filter).await.unwrap_err(); + assert!( + err.to_string().contains("unknown block"), + "expected `unknown block` in error, got: {err}" + ); +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 16a23081c87c1..dbd89ce88827d 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -99,8 +99,8 @@ anvil.workspace = true foundry-test-utils.workspace = true [features] -default = ["jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] +default = ["jemalloc", "asm-keccak"] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs index dcd6d0980fcfd..b39e024f38af5 100644 --- a/crates/cast/src/call_spec.rs +++ b/crates/cast/src/call_spec.rs @@ -8,9 +8,14 @@ //! - `0x123::transfer(address,uint256):0x789,1000` - Contract call with signature //! - `0x123::0xabcdef` - Contract call with raw calldata +use alloy_network::Network; use alloy_primitives::{Address, Bytes, U256, hex}; +use alloy_provider::Provider; use eyre::{Result, WrapErr, eyre}; +use foundry_cli::utils::parse_function_args; +use foundry_config::Chain; use std::str::FromStr; +use tempo_primitives::transaction::Call; /// A parsed call specification for batch transactions. #[derive(Debug, Clone)] @@ -98,6 +103,35 @@ impl CallSpec { Ok(Self { to, value, sig, args, data }) } + + /// Resolves this spec into a [`Call`], encoding function arguments if needed. + /// `i` is the 0-based index of this call; displayed as `i + 1` in error messages. + pub async fn resolve>( + &self, + i: usize, + chain: Chain, + provider: &P, + etherscan_api_key: Option<&str>, + ) -> Result { + let input = if let Some(data) = &self.data { + data.clone() + } else if let Some(sig) = &self.sig { + let (encoded, _) = parse_function_args( + sig, + self.args.clone(), + Some(self.to), + chain, + provider, + etherscan_api_key, + ) + .await + .map_err(|e| eyre!("Failed to encode call {}: {e}", i + 1))?; + Bytes::from(encoded) + } else { + Bytes::new() + }; + Ok(Call { to: self.to.into(), value: self.value, input }) + } } impl FromStr for CallSpec { diff --git a/crates/cast/src/cmd/access_list.rs b/crates/cast/src/cmd/access_list.rs index 9776502145afd..ae533483fb820 100644 --- a/crates/cast/src/cmd/access_list.rs +++ b/crates/cast/src/cmd/access_list.rs @@ -1,9 +1,10 @@ use crate::{ Cast, + rlp_converter::TryIntoRlpEncodable, tx::{CastTxBuilder, SenderKind}, }; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, Network}; +use alloy_network::{AnyNetwork, Network}; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; @@ -11,8 +12,13 @@ use foundry_cli::{ opts::{RpcOpts, TransactionOpts}, utils::LoadConfig, }; -use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; +use foundry_common::{ + fmt::{UIfmt, UIfmtHeaderExt, UIfmtSignatureExt}, + provider::ProviderBuilder, +}; +use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::WalletOpts; +use serde::Serialize; use std::str::FromStr; use tempo_alloy::TempoNetwork; @@ -62,13 +68,18 @@ impl AccessListArgs { if self.tx.tempo.is_tempo() { self.run_with_network::().await } else { - self.run_with_network::().await + self.run_with_network::().await } } pub async fn run_with_network(self) -> Result<()> where + N::TxEnvelope: Serialize + UIfmtSignatureExt, + N::Header: TryIntoRlpEncodable, N::TransactionRequest: FoundryTransactionBuilder, + N::TransactionResponse: UIfmt, + N::HeaderResponse: UIfmtHeaderExt, + N::BlockResponse: UIfmt, { let Self { to, mut sig, args, data, tx, rpc, wallet, block } = self; diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index 42596fe6cff4d..759d8ef507f20 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -10,18 +10,17 @@ use crate::{ use alloy_consensus::SignableTransaction; use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, NetworkTransactionBuilder}; -use alloy_primitives::{Address, Bytes}; +use alloy_primitives::Address; use alloy_provider::Provider; use alloy_signer::Signer; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, LoadConfig, parse_function_args}, + utils::{self, LoadConfig}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use tempo_alloy::TempoNetwork; -use tempo_primitives::transaction::Call; /// CLI arguments for `cast batch-mktx`. /// @@ -75,28 +74,10 @@ impl BatchMakeTxArgs { let chain = utils::get_chain(config.chain, &provider).await?; let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); - // Build Vec from specs let mut tempo_calls = Vec::with_capacity(call_specs.len()); for (i, spec) in call_specs.iter().enumerate() { - let input = if let Some(data) = &spec.data { - data.clone() - } else if let Some(sig) = &spec.sig { - let (encoded, _) = parse_function_args( - sig, - spec.args.clone(), - Some(spec.to), - chain, - &provider, - etherscan_api_key.as_deref(), - ) - .await - .map_err(|e| eyre!("Failed to encode call {}: {}", i + 1, e))?; - Bytes::from(encoded) - } else { - Bytes::new() - }; - - tempo_calls.push(Call { to: spec.to.into(), value: spec.value, input }); + tempo_calls + .push(spec.resolve(i, chain, &provider, etherscan_api_key.as_deref()).await?); } sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; @@ -144,23 +125,9 @@ impl BatchMakeTxArgs { Some(s) => s, None => eth.wallet.signer().await?, }; - let from = if let Some(ref access_key) = tempo_access_key { - access_key.wallet_address - } else { - Signer::address(&signer) - }; - - if tempo_access_key.is_none() { - tx::validate_from_address(eth.wallet.from, from)?; - } - - let (tx, _) = if tempo_access_key.is_some() { - tx_builder.build(from).await? - } else { - tx_builder.build(&signer).await? - }; let signed_tx = if let Some(ref access_key) = tempo_access_key { + let (tx, _) = tx_builder.build(access_key.wallet_address).await?; let raw_tx = tx .sign_with_access_key( &provider, @@ -172,6 +139,8 @@ impl BatchMakeTxArgs { .await?; alloy_primitives::hex::encode(raw_tx) } else { + tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?; + let (tx, _) = tx_builder.build(&signer).await?; let envelope = tx.build(&EthereumWallet::new(signer)).await?; alloy_primitives::hex::encode(envelope.encoded_2718()) }; diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index c381e5f7a1e2a..5fd18b0261897 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -6,23 +6,21 @@ use crate::{ call_spec::CallSpec, - cmd::send::cast_send, - tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}, + cmd::send::{cast_send, cast_send_with_access_key}, + tx::{self, CastTxBuilder, SendTxOpts}, }; use alloy_network::EthereumWallet; -use alloy_primitives::Bytes; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::TransactionOpts, - utils::{self, LoadConfig, parse_function_args}, + utils::{self, LoadConfig}, }; -use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; +use foundry_common::provider::ProviderBuilder; use std::time::Duration; use tempo_alloy::TempoNetwork; -use tempo_primitives::transaction::Call; /// CLI arguments for `cast batch-send`. /// @@ -79,25 +77,8 @@ impl BatchSendArgs { // Build Vec from specs let mut tempo_calls = Vec::with_capacity(call_specs.len()); for (i, spec) in call_specs.iter().enumerate() { - let input = if let Some(data) = &spec.data { - data.clone() - } else if let Some(sig) = &spec.sig { - let (encoded, _) = parse_function_args( - sig, - spec.args.clone(), - Some(spec.to), - chain, - &provider, - etherscan_api_key.as_deref(), - ) - .await - .map_err(|e| eyre!("Failed to encode call {}: {}", i + 1, e))?; - Bytes::from(encoded) - } else { - Bytes::new() - }; - - tempo_calls.push(Call { to: spec.to.into(), value: spec.value, input }); + tempo_calls + .push(spec.resolve(i, chain, &provider, etherscan_api_key.as_deref()).await?); } sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; @@ -139,38 +120,22 @@ impl BatchSendArgs { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; - let from = if let Some(ref access_key) = tempo_access_key { - access_key.wallet_address - } else { - Signer::address(&signer) - }; - - if tempo_access_key.is_none() { - tx::validate_from_address(send_tx.eth.wallet.from, from)?; - } - - let (tx_request, _) = if tempo_access_key.is_some() { - builder.build(from).await? - } else { - builder.build(&signer).await? - }; if let Some(ref access_key) = tempo_access_key { - let raw_tx = tx_request - .sign_with_access_key( - &provider, - &signer, - access_key.wallet_address, - access_key.key_address, - access_key.key_authorization.as_ref(), - ) - .await?; - - let cast = CastTxSender::new(&provider); - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout) - .await?; + let (tx_request, _) = builder.build(access_key.wallet_address).await?; + cast_send_with_access_key( + &provider, + tx_request, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; } else { + tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?; + let (tx_request, _) = builder.build(&signer).await?; let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() .wallet(wallet) diff --git a/crates/cast/src/cmd/create2.rs b/crates/cast/src/cmd/create2.rs index f7e5aae1f2d9e..056dbd73fcc83 100644 --- a/crates/cast/src/cmd/create2.rs +++ b/crates/cast/src/cmd/create2.rs @@ -3,13 +3,7 @@ use clap::Parser; use eyre::{Result, WrapErr}; use rand::{RngCore, SeedableRng, rngs::StdRng}; use regex::RegexSetBuilder; -use std::{ - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Instant, -}; +use std::time::Instant; // https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; @@ -195,71 +189,27 @@ impl Create2Args { sh_println!( "Starting to generate deterministic contract address with {n_threads} threads..." )?; - let mut handles = Vec::with_capacity(n_threads); - let found = Arc::new(AtomicBool::new(false)); let timer = Instant::now(); - - // Loops through all possible salts in parallel until a result is found. - // Each thread iterates over `(i..).step_by(n_threads)`. - for i in 0..n_threads { - // Create local copies for the thread. - let increment = n_threads; - let regex = regex.clone(); - let regex_len = regex.patterns().len(); - let found = Arc::clone(&found); - handles.push(std::thread::spawn(move || { - // Read the first bytes of the salt as a usize to be able to increment it. - #[repr(C)] - struct B256Aligned(B256, [usize; 0]); - let mut salt = B256Aligned(salt, []); - // SAFETY: B256 is aligned to `usize`. - let salt_word = unsafe { - &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() - }; - // Important: add the thread index to the salt to avoid duplicate results. - *salt_word = salt_word.wrapping_add(i); - - // Use checksum format only when case_sensitive is enabled. - // This avoids an extra keccak256 call per iteration when not needed. - let mut checksum_buf = [0u8; 42]; - let mut hex_buf = [0u8; 40]; - loop { - // Stop if a result was found in another thread. - if found.load(Ordering::Relaxed) { - break None; - } - - // Calculate the `CREATE2` address. - #[expect(clippy::needless_borrows_for_generic_args)] - let addr = deployer.create2(&salt.0, &init_code_hash); - - // Check if the regex matches the calculated address. - // When case_sensitive is true, use EIP-55 checksum format (requires keccak256). - // Otherwise, use lowercase hex to avoid the extra hash computation. - let s = if case_sensitive { - let _ = addr.to_checksum_raw(&mut checksum_buf, None); - // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 - // string is safe. - unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) } - } else { - // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits). - let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf); - unsafe { std::str::from_utf8_unchecked(&hex_buf) } - }; - if regex.matches(s).into_iter().count() == regex_len { - // Notify other threads that we found a result. - found.store(true, Ordering::Relaxed); - break Some((addr, salt.0)); - } - - // Increment the salt for the next iteration. - *salt_word = salt_word.wrapping_add(increment); - } - })); - } - - let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); - let (address, salt) = results.into_iter().next().unwrap(); + let regex_len = regex.patterns().len(); + let mut checksum_buf = [0u8; 42]; + let mut hex_buf = [0u8; 40]; + let (address, salt) = super::miner::mine_salt(salt, n_threads, move |salt| { + #[expect(clippy::needless_borrows_for_generic_args)] + let addr = deployer.create2(&salt, &init_code_hash); + // Use checksum format only when case_sensitive is enabled — it requires an extra + // keccak256 call, so we fall back to plain hex when case sensitivity is off. + let s = if case_sensitive { + let _ = addr.to_checksum_raw(&mut checksum_buf, None); + // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string. + unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) } + } else { + // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits). + let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf); + unsafe { std::str::from_utf8_unchecked(&hex_buf) } + }; + (regex.matches(s).into_iter().count() == regex_len).then_some((addr, salt)) + }) + .ok_or_else(|| eyre::eyre!("create2 salt mining failed: all threads panicked"))?; sh_println!("Successfully found contract address in {:?}", timer.elapsed())?; sh_println!("Address: {address}")?; sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?; diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index 07e7df1b67517..e629b8eca0d5a 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -1,17 +1,21 @@ use std::{str::FromStr, time::Duration}; -use crate::{cmd::send::cast_send, format_uint_exp, tx::SendTxOpts}; +use crate::{ + cmd::send::{cast_send, cast_send_with_access_key}, + format_uint_exp, + tx::{SendTxOpts, TxParams}, +}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; -use alloy_primitives::{U64, U256}; +use alloy_network::{Ethereum, EthereumWallet, Network}; +use alloy_primitives::U256; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; -use clap::{Args, Parser}; +use clap::Parser; use foundry_cli::{ - opts::{RpcOpts, TempoOpts}, + opts::RpcOpts, utils::{LoadConfig, get_chain, get_provider}, }; use foundry_common::{ @@ -42,32 +46,6 @@ sol! { } } -/// Transaction options for ERC20 operations. -/// -/// This struct contains only the transaction options relevant to ERC20 token interactions -#[derive(Debug, Clone, Args)] -#[command(next_help_heading = "Transaction options")] -pub struct Erc20TxOpts { - /// Gas limit for the transaction. - #[arg(long, env = "ETH_GAS_LIMIT")] - pub gas_limit: Option, - - /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[arg(long, env = "ETH_GAS_PRICE")] - pub gas_price: Option, - - /// Max priority fee per gas for EIP1559 transactions. - #[arg(long, env = "ETH_PRIORITY_GAS_PRICE")] - pub priority_gas_price: Option, - - /// Nonce for the transaction. - #[arg(long)] - pub nonce: Option, - - #[command(flatten)] - pub tempo: TempoOpts, -} - /// Creates a provider with a pre-resolved signer. pub(crate) fn build_provider_with_signer( tx_opts: &SendTxOpts, @@ -86,32 +64,6 @@ where Ok(provider) } -impl Erc20TxOpts { - /// Applies gas, fee, nonce, and Tempo options to a transaction request. - fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) - where - N::TransactionRequest: FoundryTransactionBuilder, - { - if let Some(gas_limit) = self.gas_limit { - tx.set_gas_limit(gas_limit.to()); - } - - if let Some(gas_price) = self.gas_price { - if legacy { - tx.set_gas_price(gas_price.to()); - } else { - tx.set_max_fee_per_gas(gas_price.to()); - } - } - - if !legacy && let Some(priority_fee) = self.priority_gas_price { - tx.set_max_priority_fee_per_gas(priority_fee.to()); - } - - self.tempo.apply::(tx, self.nonce.map(|n| n.to())); - } -} - /// Interact with ERC20 tokens. #[derive(Debug, Parser, Clone)] pub enum Erc20Subcommand { @@ -152,7 +104,7 @@ pub enum Erc20Subcommand { send_tx: SendTxOpts, #[command(flatten)] - tx: Erc20TxOpts, + tx: TxParams, }, /// Approve ERC20 token spending. @@ -173,7 +125,7 @@ pub enum Erc20Subcommand { send_tx: SendTxOpts, #[command(flatten)] - tx: Erc20TxOpts, + tx: TxParams, }, /// Query ERC20 token allowance. @@ -277,7 +229,7 @@ pub enum Erc20Subcommand { send_tx: SendTxOpts, #[command(flatten)] - tx: Erc20TxOpts, + tx: TxParams, }, /// Burn ERC20 tokens. @@ -294,7 +246,7 @@ pub enum Erc20Subcommand { send_tx: SendTxOpts, #[command(flatten)] - tx: Erc20TxOpts, + tx: TxParams, }, } @@ -314,7 +266,7 @@ impl Erc20Subcommand { } } - const fn erc20_opts(&self) -> Option<&Erc20TxOpts> { + const fn erc20_opts(&self) -> Option<&TxParams> { match self { Self::Approve { tx, .. } | Self::Transfer { tx, .. } @@ -383,8 +335,9 @@ impl Erc20Subcommand { ) => {{ let timeout = $send_tx.timeout.unwrap_or(config.transaction_timeout); if let Some(ref access_key) = tempo_keychain { - let signer = - pre_resolved_signer.as_ref().expect("signer required for access key"); + let signer = pre_resolved_signer + .as_ref() + .ok_or_else(|| eyre::eyre!("signer required for access key"))?; let $provider = ProviderBuilder::::from_config(&config)?.build()?; let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); @@ -393,8 +346,7 @@ impl Erc20Subcommand { &mut tx, get_chain(config.chain, &$provider).await?.is_legacy(), ); - apply_tempo_access_key::(&mut tx, Some(access_key)); - send_tempo_keychain( + cast_send_with_access_key( &$provider, tx, signer, @@ -551,49 +503,3 @@ impl Erc20Subcommand { Ok(()) } } - -/// Applies Tempo access key fields (from, key_id) to a transaction request. -/// -/// Note: `key_authorization` is intentionally not set here. It is only included -/// if the key is not yet provisioned on-chain (checked in [`send_tempo_keychain`]). -fn apply_tempo_access_key( - tx: &mut N::TransactionRequest, - config: Option<&TempoAccessKeyConfig>, -) where - N::TransactionRequest: FoundryTransactionBuilder, -{ - if let Some(config) = config { - tx.set_from(config.wallet_address); - tx.set_key_id(config.key_address); - } -} - -/// Sends a Tempo transaction using access key (keychain V2 mode). -/// -/// Signs the transaction with the access key and sends it via `send_raw_transaction`, -/// bypassing `EthereumWallet`. Only includes `key_authorization` if the key is not yet -/// provisioned on-chain. -async fn send_tempo_keychain>( - provider: &P, - tx: ::TransactionRequest, - signer: &WalletSigner, - access_key: &TempoAccessKeyConfig, - cast_async: bool, - confirmations: u64, - timeout: u64, -) -> eyre::Result<()> { - let raw_tx = tx - .sign_with_access_key( - provider, - signer, - access_key.wallet_address, - access_key.key_address, - access_key.key_authorization.as_ref(), - ) - .await?; - - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - - let cast = crate::tx::CastTxSender::new(provider); - cast.print_tx_result(tx_hash, cast_async, confirmations, timeout).await -} diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index 086d64e7c73e7..8b7d80786dfad 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -1,7 +1,7 @@ use alloy_ens::NameOrAddress; use alloy_network::EthereumWallet; use alloy_primitives::{Address, U256, hex, keccak256}; -use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_provider::ProviderBuilder as AlloyProviderBuilder; use alloy_signer::Signer; use alloy_sol_types::SolCall; use chrono::DateTime; @@ -12,7 +12,6 @@ use foundry_cli::{ utils::LoadConfig, }; use foundry_common::{ - FoundryTransactionBuilder, provider::ProviderBuilder, shell, tempo::{self, KeyType, KeysFile, WalletType, read_tempo_keys_file, tempo_keys_path}, @@ -29,7 +28,10 @@ use tempo_contracts::precompiles::{ }; use yansi::Paint; -use crate::tx::{CastTxBuilder, CastTxSender, SendTxOpts}; +use crate::{ + cmd::send::{cast_send, cast_send_with_access_key}, + tx::{CastTxBuilder, SendTxOpts}, +}; /// Tempo keychain management commands. /// @@ -582,7 +584,7 @@ async fn run_authorize( let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let calldata = if is_hardfork_active(&provider, TempoHardfork::T3).await { + let calldata = if provider.is_hardfork_active(TempoHardfork::T3).await? { // T3+ authorizeKey(address,SignatureType,KeyRestrictions) let restrictions = KeyRestrictions { expiry, @@ -632,7 +634,7 @@ async fn run_remaining_limit( let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let remaining: U256 = if is_hardfork_active(&provider, TempoHardfork::T3).await { + let remaining: U256 = if provider.is_hardfork_active(TempoHardfork::T3).await? { provider.get_keychain_remaining_limit(wallet_address, key_address, token).await? } else { // Pre-T3: use the legacy getRemainingLimit(address,address,address) @@ -693,22 +695,6 @@ async fn run_remove_scope( send_keychain_tx(calldata, tx_opts, &send_tx).await } -/// Returns `true` when the connected Tempo chain has the given hardfork active. -async fn is_hardfork_active>( - provider: &P, - hardfork: TempoHardfork, -) -> bool { - let Ok(chain_id) = provider.get_chain_id().await else { return true }; - let block_ts = provider - .get_block(Default::default()) - .await - .ok() - .flatten() - .map(|b| b.header.inner.inner.inner.timestamp) - .unwrap_or(0); - TempoHardfork::from_chain_and_timestamp(chain_id, block_ts).is_none_or(|h| h >= hardfork) -} - /// Shared helper to send a keychain precompile transaction. async fn send_keychain_tx( calldata: Vec, @@ -734,23 +720,19 @@ async fn send_keychain_tx( .await?; if let Some(ref ak) = tempo_access_key { - let signer = signer.as_ref().expect("signer required for access key"); - let from = ak.wallet_address; - let (tx, _) = builder.build(from).await?; - - let raw_tx = tx - .sign_with_access_key( - &provider, - signer, - ak.wallet_address, - ak.key_address, - ak.key_authorization.as_ref(), - ) - .await?; - - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - let cast = CastTxSender::new(&provider); - cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await?; + let signer = + signer.as_ref().ok_or_else(|| eyre::eyre!("signer required for access key"))?; + let (tx, _) = builder.build(ak.wallet_address).await?; + cast_send_with_access_key( + &provider, + tx, + signer, + ak, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; } else { let signer = match signer { Some(s) => s, @@ -764,10 +746,8 @@ async fn send_keychain_tx( .wallet(wallet) .connect_provider(&provider); - let cast = CastTxSender::new(provider); - let pending_tx = cast.send(tx).await?; - let tx_hash = *pending_tx.inner().tx_hash(); - cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; } Ok(()) diff --git a/crates/cast/src/cmd/miner.rs b/crates/cast/src/cmd/miner.rs new file mode 100644 index 0000000000000..346efb9a503a0 --- /dev/null +++ b/crates/cast/src/cmd/miner.rs @@ -0,0 +1,52 @@ +use alloy_primitives::B256; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + +/// Mines a salt by iterating B256 values in parallel until `check` returns `Some`. +/// +/// Each of the `n_threads` threads starts at `salt + thread_index` and steps by `n_threads`, +/// ensuring non-overlapping coverage. Returns `None` only if all threads panicked. +pub(crate) fn mine_salt(salt: B256, n_threads: usize, check: F) -> Option +where + T: Send + 'static, + F: FnMut(B256) -> Option + Clone + Send + 'static, +{ + let found = Arc::new(AtomicBool::new(false)); + let mut handles = Vec::with_capacity(n_threads); + + for i in 0..n_threads { + let increment = n_threads; + let found = Arc::clone(&found); + let mut check = check.clone(); + + handles.push(std::thread::spawn(move || { + #[repr(C)] + struct B256Aligned(B256, [usize; 0]); + + let mut salt = B256Aligned(salt, []); + // SAFETY: `B256` is aligned to `usize`. + let salt_word = unsafe { + &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() + }; + // Important: offset by thread index to avoid duplicate work across threads. + *salt_word = salt_word.wrapping_add(i); + + loop { + if found.load(Ordering::Relaxed) { + break None; + } + + if let Some(result) = check(salt.0) { + found.store(true, Ordering::Relaxed); + break Some(result); + } + + *salt_word = salt_word.wrapping_add(increment); + } + })); + } + + handles.into_iter().find_map(|h| h.join().ok().flatten()) +} diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 66f88a68f40a1..6a9d11f5dc61d 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -22,6 +22,7 @@ pub mod find_block; pub mod interface; pub mod keychain; pub mod logs; +pub(crate) mod miner; pub mod mktx; pub mod rpc; pub mod run; diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 1080286605099..08c535bef8a18 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, str::FromStr, time::Duration}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network}; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; use alloy_primitives::Address; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::{Signature, Signer}; @@ -271,24 +271,17 @@ impl SendTxArgs { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; - let from = ak.wallet_address; - - let (tx_request, _) = builder.build(from).await?; - - let raw_tx = tx_request - .sign_with_access_key( - &provider, - &signer, - ak.wallet_address, - ak.key_address, - ak.key_authorization.as_ref(), - ) - .await?; - - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - - let cast = CastTxSender::new(&provider); - cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await + let (tx_request, _) = builder.build(ak.wallet_address).await?; + cast_send_with_access_key( + &provider, + tx_request, + &signer, + &ak, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await // Case 4: // An option to use a local signer was provided. // If we cannot successfully instantiate a local signer, then we will assume we don't have @@ -354,3 +347,37 @@ where Ok(()) } + +/// Signs a transaction with a Tempo access key and sends it via `send_raw_transaction`. +/// +/// Sets `from` and `key_id` on the transaction before signing, making it idempotent for txs built +/// with [`CastTxBuilder`] (fields already set) and also with sol!-bindings (fields not yet set). +/// +/// NOTE: The default implementation returns an error. Only `TempoNetwork` supports this. +pub(crate) async fn cast_send_with_access_key>( + provider: &P, + mut tx: N::TransactionRequest, + signer: &WalletSigner, + access_key: &TempoAccessKeyConfig, + cast_async: bool, + confirmations: u64, + timeout: u64, +) -> Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, +{ + tx.set_from(access_key.wallet_address); + tx.set_key_id(access_key.key_address); + let raw_tx = tx + .sign_with_access_key( + provider, + signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); + CastTxSender::new(provider).print_tx_result(tx_hash, cast_async, confirmations, timeout).await +} diff --git a/crates/cast/src/cmd/tip20.rs b/crates/cast/src/cmd/tip20.rs deleted file mode 100644 index fc1cb6c9bec5e..0000000000000 --- a/crates/cast/src/cmd/tip20.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::{ - cmd::{erc20::build_provider_with_signer, send::cast_send}, - tx::{CastTxSender, SendTxOpts}, -}; -use alloy_ens::NameOrAddress; -use alloy_network::{Network, TransactionBuilder}; -use alloy_primitives::{B256, U256}; -use alloy_provider::Provider; -use alloy_sol_types::sol; -use clap::{Args, Parser}; -use foundry_cli::{ - opts::{RpcOpts, TempoOpts}, - utils::{LoadConfig, get_chain}, -}; -use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; -use std::str::FromStr; -use tempo_alloy::TempoNetwork; -use tempo_contracts::precompiles::{TIP20_FACTORY_ADDRESS, is_iso4217_currency}; - -sol! { - #[sol(rpc)] - interface ITIP20Factory { - function createToken( - string memory name, - string memory symbol, - string memory currency, - address quoteToken, - address admin, - bytes32 salt - ) external returns (address token); - } -} - -/// Returns a warning message for non-ISO 4217 currency codes used in TIP-20 token creation. -pub(crate) fn iso4217_warning_message(currency: &str) -> String { - let hyperlink = |url: &str| format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\"); - let tip20_docs = hyperlink("https://docs.tempo.xyz/protocol/tip20/overview"); - let iso_docs = hyperlink("https://www.iso.org/iso-4217-currency-codes.html"); - - format!( - "\"{currency}\" is not a recognized ISO 4217 currency code.\n\ - \n\ - If the token you are trying to deploy is a fiat-backed stablecoin, Tempo strongly\n\ - recommends that the currency code field be the ISO-4217 currency code of the fiat\n\ - currency your token tracks (e.g. \"USD\", \"EUR\", \"GBP\").\n\ - \n\ - The currency field is IMMUTABLE after token creation and affects fee payment\n\ - eligibility, DEX routing, and quote token pairing. Only \"USD\"-denominated tokens\n\ - can be used to pay transaction fees on Tempo.\n\ - \n\ - Learn more:\n \ - - Tempo TIP-20 docs: {tip20_docs}\n \ - - ISO 4217 standard: {iso_docs}" - ) -} - -/// TIP-20 token operations (Tempo). -#[derive(Debug, Parser, Clone)] -pub enum Tip20Subcommand { - /// Create a new TIP-20 token via the TIP20Factory. - #[command(visible_alias = "c")] - Create { - /// The token name (e.g. "US Dollar Coin"). - name: String, - - /// The token symbol (e.g. "USDC"). - symbol: String, - - /// The ISO 4217 currency code (e.g. "USD", "EUR", "GBP"). - /// This field is IMMUTABLE after creation and affects fee payment - /// eligibility, DEX routing, and quote token pairing. - currency: String, - - /// The TIP-20 quote token address used for exchange pricing. - #[arg(value_parser = NameOrAddress::from_str)] - quote_token: NameOrAddress, - - /// The admin address to receive DEFAULT_ADMIN_ROLE on the new token. - #[arg(value_parser = NameOrAddress::from_str)] - admin: NameOrAddress, - - /// A unique salt for deterministic address derivation (hex-encoded bytes32). - salt: B256, - - /// Skip the ISO 4217 currency code validation warning. - #[arg(long)] - force: bool, - - #[command(flatten)] - send_tx: SendTxOpts, - - #[command(flatten)] - tx: Tip20TxOpts, - }, -} - -/// Transaction options for TIP-20 operations. -#[derive(Debug, Clone, Args)] -#[command(next_help_heading = "Transaction options")] -pub struct Tip20TxOpts { - /// Gas limit for the transaction. - #[arg(long)] - pub gas_limit: Option, - - /// Gas price or max fee per gas for the transaction. - #[arg(long)] - pub gas_price: Option, - - /// Max priority fee per gas (EIP-1559). - #[arg(long)] - pub priority_gas_price: Option, - - /// Nonce for the transaction. - #[arg(long)] - pub nonce: Option, - - #[command(flatten)] - pub tempo: TempoOpts, -} - -impl Tip20TxOpts { - /// Applies gas, fee, nonce, and Tempo options to a transaction request. - fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) - where - N::TransactionRequest: FoundryTransactionBuilder, - { - if let Some(gas_limit) = self.gas_limit { - tx.set_gas_limit(gas_limit.to()); - } - - if let Some(gas_price) = self.gas_price { - if legacy { - tx.set_gas_price(gas_price.to()); - } else { - tx.set_max_fee_per_gas(gas_price.to()); - } - } - - if !legacy && let Some(priority_fee) = self.priority_gas_price { - tx.set_max_priority_fee_per_gas(priority_fee.to()); - } - - self.tempo.apply::(tx, self.nonce.map(|n| n.to())); - } -} - -impl Tip20Subcommand { - const fn rpc_opts(&self) -> &RpcOpts { - match self { - Self::Create { send_tx, .. } => &send_tx.eth.rpc, - } - } - - pub async fn run(self) -> eyre::Result<()> { - let (signer, tempo_access_key) = match &self { - Self::Create { send_tx, .. } => { - if send_tx.eth.wallet.from.is_some() { - send_tx.eth.wallet.maybe_signer().await? - } else { - (None, None) - } - } - }; - - let config = self.rpc_opts().load_config()?; - - match self { - Self::Create { - name, - symbol, - currency, - quote_token, - admin, - salt, - force, - send_tx, - tx: tx_opts, - } => { - if !is_iso4217_currency(¤cy) && !force { - sh_warn!("{}", iso4217_warning_message(¤cy))?; - let response: String = foundry_common::prompt!("\nContinue anyway? [y/N] ")?; - if !matches!(response.trim(), "y" | "Y") { - sh_println!("Aborted.")?; - return Ok(()); - } - } - - let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); - let provider = ProviderBuilder::::from_config(&config)?.build()?; - let quote_token_addr = quote_token.resolve(&provider).await?; - let admin_addr = admin.resolve(&provider).await?; - - let mut tx = ITIP20Factory::new(TIP20_FACTORY_ADDRESS, &provider) - .createToken(name, symbol, currency, quote_token_addr, admin_addr, salt) - .into_transaction_request(); - - tx_opts.apply::( - &mut tx, - get_chain(config.chain, &provider).await?.is_legacy(), - ); - - if let Some(ref access_key) = tempo_access_key { - let signer = signer.as_ref().expect("signer required for access key"); - tx.set_from(access_key.wallet_address); - tx.set_key_id(access_key.key_address); - - let raw_tx = tx - .sign_with_access_key( - &provider, - signer, - access_key.wallet_address, - access_key.key_address, - access_key.key_authorization.as_ref(), - ) - .await?; - - let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); - let cast = CastTxSender::new(&provider); - cast.print_tx_result( - tx_hash, - send_tx.cast_async, - send_tx.confirmations, - timeout, - ) - .await? - } else { - let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?); - let provider = build_provider_with_signer::(&send_tx, signer)?; - cast_send( - provider, - tx, - send_tx.cast_async, - send_tx.sync, - send_tx.confirmations, - timeout, - ) - .await? - } - } - }; - Ok(()) - } -} diff --git a/crates/cast/src/cmd/tip20/create.rs b/crates/cast/src/cmd/tip20/create.rs new file mode 100644 index 0000000000000..3417674f1d470 --- /dev/null +++ b/crates/cast/src/cmd/tip20/create.rs @@ -0,0 +1,113 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_ens::NameOrAddress; +use alloy_primitives::B256; +use alloy_sol_types::sol; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::provider::ProviderBuilder; +use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::{TIP20_FACTORY_ADDRESS, is_iso4217_currency}; + +sol! { + #[sol(rpc)] + interface ITIP20Factory { + function createToken( + string memory name, + string memory symbol, + string memory currency, + address quoteToken, + address admin, + bytes32 salt + ) external returns (address token); + } +} + +/// Returns a warning message for non-ISO 4217 currency codes used in TIP-20 token creation. +pub(crate) fn iso4217_warning_message(currency: &str) -> String { + let hyperlink = |url: &str| format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\"); + let tip20_docs = hyperlink("https://docs.tempo.xyz/protocol/tip20/overview"); + let iso_docs = hyperlink("https://www.iso.org/iso-4217-currency-codes.html"); + + format!( + "\"{currency}\" is not a recognized ISO 4217 currency code.\n\ + \n\ + If the token you are trying to deploy is a fiat-backed stablecoin, Tempo strongly\n\ + recommends that the currency code field be the ISO-4217 currency code of the fiat\n\ + currency your token tracks (e.g. \"USD\", \"EUR\", \"GBP\").\n\ + \n\ + The currency field is IMMUTABLE after token creation and affects fee payment\n\ + eligibility, DEX routing, and quote token pairing. Only \"USD\"-denominated tokens\n\ + can be used to pay transaction fees on Tempo.\n\ + \n\ + Learn more:\n \ + - Tempo TIP-20 docs: {tip20_docs}\n \ + - ISO 4217 standard: {iso_docs}" + ) +} + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + name: String, + symbol: String, + currency: String, + quote_token: NameOrAddress, + admin: NameOrAddress, + salt: B256, + force: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> eyre::Result<()> { + let (signer, tempo_access_key) = if send_tx.eth.wallet.from.is_some() { + send_tx.eth.wallet.maybe_signer().await? + } else { + (None, None) + }; + + let config = send_tx.eth.rpc.load_config()?; + + if !is_iso4217_currency(¤cy) && !force { + sh_warn!("{}", super::iso4217_warning_message(¤cy))?; + let response: String = foundry_common::prompt!("\nContinue anyway? [y/N] ")?; + if !matches!(response.trim(), "y" | "Y") { + sh_println!("Aborted.")?; + return Ok(()); + } + } + + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let quote_token_addr = quote_token.resolve(&provider).await?; + let admin_addr = admin.resolve(&provider).await?; + + let mut tx = ITIP20Factory::new(TIP20_FACTORY_ADDRESS, &provider) + .createToken(name, symbol, currency, quote_token_addr, admin_addr, salt) + .into_transaction_request(); + + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + if let Some(ref access_key) = tempo_access_key { + let signer = signer.as_ref().ok_or_else(|| eyre::eyre!("access key requires a signer"))?; + cast_send_with_access_key( + &provider, + tx, + signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?); + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/tip20/mine.rs b/crates/cast/src/cmd/tip20/mine.rs new file mode 100644 index 0000000000000..a5f9062482a01 --- /dev/null +++ b/crates/cast/src/cmd/tip20/mine.rs @@ -0,0 +1,249 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256, keccak256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::provider::ProviderBuilder; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use std::time::{Duration, Instant}; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{MasterId, TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +pub(super) struct Output { + pub(super) salt: B256, + pub(super) registration_hash: B256, + pub(super) master_id: MasterId, + pub(super) zero_tag_virtual_address: Address, +} + +pub(super) fn run( + master: Address, + salt: Option, + threads: Option, + seed: Option, + no_random: bool, +) -> Result { + if !master.is_valid_master() { + eyre::bail!( + "invalid master address {master}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + if let Some(salt) = salt { + let output = derive(master, salt); + if !has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + print_output(&output, None)?; + return Ok(output); + } + + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut salt[..]); + } + + sh_println!("Mining TIP-1022 salt for {master} with {n_threads} threads...")?; + + let timer = Instant::now(); + let output = mine(master, salt, n_threads, POW_BYTES)?; + print_output(&output, Some(timer.elapsed()))?; + Ok(output) +} + +pub(super) async fn register( + master: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!( + "--register requires a signer or Tempo keychain identity (for example --private-key or --from)" + ) + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != master { + eyre::bail!( + "registration sender mismatch: mined salt is for {master}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt}) on Tempo...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} + +fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Result { + let mut packed = [0u8; 52]; + packed[..20].copy_from_slice(master.as_slice()); + + crate::cmd::miner::mine_salt(salt, n_threads, move |salt| { + packed[20..].copy_from_slice(salt.as_slice()); + let registration_hash = keccak256(packed); + + has_pow(®istration_hash, pow_bytes).then(|| { + let master_id = MasterId::from_slice(®istration_hash[4..8]); + let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); + Output { salt, registration_hash, master_id, zero_tag_virtual_address } + }) + }) + .ok_or_else(|| eyre::eyre!("virtual master mining failed: all threads panicked")) +} + +fn derive(master: Address, salt: B256) -> Output { + let registration_hash = registration_hash(master, salt); + let master_id = MasterId::from_slice(®istration_hash[4..8]); + let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); + + Output { salt, registration_hash, master_id, zero_tag_virtual_address } +} + +fn registration_hash(master: Address, salt: B256) -> B256 { + let mut packed = [0u8; 52]; + packed[..20].copy_from_slice(master.as_slice()); + packed[20..].copy_from_slice(salt.as_slice()); + keccak256(packed) +} + +fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { + registration_hash[..pow_bytes].iter().all(|byte| *byte == 0) +} + +fn print_output(output: &Output, elapsed: Option) -> Result<()> { + let header = if let Some(elapsed) = elapsed { + format!("Found salt in {elapsed:?}\n") + } else { + String::new() + }; + + sh_println!( + r#"{header}Salt: {} +Registration hash: {} +Master ID: {} +Zero-tag address: {}"#, + output.salt, + output.registration_hash, + output.master_id, + output.zero_tag_virtual_address, + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, b256}; + + #[test] + fn derives_master_id_and_zero_tag_address() { + let master = address!("0x1234567890123456789012345678901234567890"); + let salt = b256!("0x0000000000000000000000000000000000000000000000000000000000000001"); + let output = derive(master, salt); + + assert_eq!( + output.registration_hash, + b256!("0x661db5481211842e0330ea3e4cf0b4e7e5abd2314161ce16e9a99e7460480f21"), + ); + assert_eq!(output.master_id, MasterId::from([0x12, 0x11, 0x84, 0x2e])); + assert_eq!( + output.zero_tag_virtual_address, + address!("0x1211842efdfdfdfdfdfdfdfdfdfd000000000000"), + ); + assert_eq!(output.master_id, MasterId::from_slice(&output.registration_hash[4..8])); + assert_eq!( + output.zero_tag_virtual_address, + Address::new_virtual(output.master_id, UserTag::ZERO), + ); + } + + #[test] + fn mines_pow_with_reduced_difficulty() -> Result<()> { + let master = address!("0x1234567890123456789012345678901234567890"); + let output = mine(master, B256::ZERO, 1, 1)?; + + assert_eq!( + output.salt, + b256!("0x000000000000000000000000000000000000000000000000f301000000000000"), + ); + assert_eq!(output.registration_hash[0], 0); + assert_eq!(output.master_id, MasterId::from_slice(&output.registration_hash[4..8])); + assert_eq!( + output.zero_tag_virtual_address, + Address::new_virtual(output.master_id, UserTag::ZERO), + ); + Ok(()) + } + + #[test] + fn has_pow_checks_leading_zero_bytes() { + let mut hash = B256::ZERO; + assert!(has_pow(&hash, 4)); + assert!(has_pow(&hash, 0)); + + hash[3] = 1; + assert!(!has_pow(&hash, 4)); + assert!(has_pow(&hash, 3)); + assert!(has_pow(&hash, 0)); + } + + #[test] + fn rejects_invalid_master_addresses() { + assert!(!Address::ZERO.is_valid_master()); + assert!(!address!("0x00000000fdfdfdfdfdfdfdfdfdfd000000000001").is_valid_master()); + assert!(!address!("0x20c0000000000000000000000000000000000001").is_valid_master()); + } +} diff --git a/crates/cast/src/cmd/tip20/mod.rs b/crates/cast/src/cmd/tip20/mod.rs new file mode 100644 index 0000000000000..e3c39b2c9bb18 --- /dev/null +++ b/crates/cast/src/cmd/tip20/mod.rs @@ -0,0 +1,111 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_ens::NameOrAddress; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use std::str::FromStr; + +mod create; +pub(crate) use create::iso4217_warning_message; +mod mine; + +/// TIP-20 token operations (Tempo). +#[derive(Debug, Parser, Clone)] +pub enum Tip20Subcommand { + /// Create a new TIP-20 token via the TIP20Factory. + #[command(visible_alias = "c")] + Create { + /// The token name (e.g. "US Dollar Coin"). + name: String, + + /// The token symbol (e.g. "USDC"). + symbol: String, + + /// The ISO 4217 currency code (e.g. "USD", "EUR", "GBP"). + /// This field is IMMUTABLE after creation and affects fee payment + /// eligibility, DEX routing, and quote token pairing. + currency: String, + + /// The TIP-20 quote token address used for exchange pricing. + #[arg(value_parser = NameOrAddress::from_str)] + quote_token: NameOrAddress, + + /// The admin address to receive DEFAULT_ADMIN_ROLE on the new token. + #[arg(value_parser = NameOrAddress::from_str)] + admin: NameOrAddress, + + /// A unique salt for deterministic address derivation (hex-encoded bytes32). + salt: B256, + + /// Skip the ISO 4217 currency code validation warning. + #[arg(long)] + force: bool, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, + + /// Mine a TIP-1022 salt for virtual address' master registration on Tempo. + #[command(visible_alias = "m")] + Mine { + /// Address that will call `registerVirtualMaster(bytes32)`. + #[arg(value_name = "ADDRESS")] + master: Address, + + /// Salt to validate directly instead of mining one. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Number of threads to use. Specifying 0 defaults to the number of logical cores. + #[arg(global = true, long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// The random number generator's seed, used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Don't initialize the salt with a random value, and instead use the default value of 0. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Submit `registerVirtualMaster(bytes32)` on Tempo after finding or validating the salt. + #[arg(long, conflicts_with_all = ["seed", "no_random"])] + register: bool, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, +} + +impl Tip20Subcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + name, + symbol, + currency, + quote_token, + admin, + salt, + force, + send_tx, + tx, + } => { + create::run(name, symbol, currency, quote_token, admin, salt, force, send_tx, tx) + .await?; + } + Self::Mine { master, salt, threads, seed, no_random, register, send_tx, tx } => { + let output = mine::run(master, salt, threads, seed, no_random)?; + if register { + mine::register(master, output.salt, send_tx, tx).await?; + } + } + } + Ok(()) + } +} diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ce5572acebc13..dedd15e460728 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -7,11 +7,7 @@ extern crate foundry_common; #[macro_use] extern crate tracing; - -use alloy_consensus::{ - BlockHeader, - transaction::{Recovered, SignerRecoverable}, -}; +use alloy_consensus::BlockHeader; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; @@ -22,7 +18,7 @@ use alloy_primitives::{ utils::{ParseUnits, Unit, keccak256}, }; use alloy_provider::{PendingTransactionBuilder, Provider, network::eip2718::Decodable2718}; -use alloy_rlp::{Decodable, Encodable}; +use alloy_rlp::Decodable; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockOverrides, Filter, FilterBlockOption, Log, state::StateOverride, }; @@ -35,12 +31,14 @@ use foundry_common::{ compile::etherscan_project, flatten, fmt::*, - fs, shell, + fs, + provider::ProviderBuilder, + shell, }; -use foundry_config::Chain; +use foundry_config::{Chain, Config}; use foundry_evm::core::bytecode::InstIter; +use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; use futures::{FutureExt, StreamExt, future::Either}; -use op_alloy_consensus as _; use rayon::prelude::*; use serde::Serialize; @@ -62,7 +60,6 @@ pub mod cmd; pub mod opts; pub mod base; -pub mod call_spec; pub(crate) mod debug; pub mod errors; mod rlp_converter; @@ -70,6 +67,8 @@ pub mod tx; use rlp_converter::Item; +use crate::rlp_converter::TryIntoRlpEncodable; + // TODO: CastContract with common contract initializers? Same for CastProviders? pub struct Cast { @@ -77,7 +76,14 @@ pub struct Cast { _phantom: PhantomData, } -impl + Clone + Unpin, N: Network> Cast { +impl + Clone + Unpin, N: Network> Cast +where + N::TxEnvelope: Serialize + UIfmtSignatureExt, + N::Header: TryIntoRlpEncodable, + N::TransactionResponse: UIfmt, + N::HeaderResponse: UIfmtHeaderExt, + N::BlockResponse: UIfmt, +{ /// Creates a new Cast instance from the provided client /// /// # Example @@ -93,7 +99,7 @@ impl + Clone + Unpin, N: Network> Cast { /// # Ok(()) /// # } /// ``` - pub const fn new(provider: P) -> Self { + pub fn new(provider: P) -> Self { Self { provider, _phantom: PhantomData } } @@ -155,9 +161,11 @@ impl + Clone + Unpin, N: Network> Cast { } let res = call.await?; - let decoded = if let Some(func) = func { + let mut decoded = vec![]; + + if let Some(func) = func { // decode args into tokens - match func.abi_decode_output(res.as_ref()) { + decoded = match func.abi_decode_output(res.as_ref()) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract @@ -183,10 +191,8 @@ impl + Clone + Unpin, N: Network> Cast { "could not decode output; did you specify the wrong function return data type?" ); } - } - } else { - vec![] - }; + }; + } // handle case when return type is not specified Ok(if decoded.is_empty() { @@ -288,6 +294,160 @@ impl + Clone + Unpin, N: Network> Cast { Ok(res) } + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use cast::Cast; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let block = cast.block(5, true, vec![], false).await?; + /// println!("{}", block); + /// # Ok(()) + /// # } + /// ``` + pub async fn block>( + &self, + block: B, + full: bool, + fields: Vec, + raw: bool, + ) -> Result { + let block = block.into(); + if fields.contains(&"transactions".into()) && !full { + eyre::bail!("use --full to view transactions") + } + + let block = self + .provider + .get_block(block) + .kind(full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; + + Ok(if raw { + let encoded = block.header().as_ref().try_rlp_encode()?; + format!("0x{}", hex::encode(encoded)) + } else if !fields.is_empty() { + let mut result = String::new(); + for field in fields { + result.push_str( + &get_pretty_block_attr::(&block, &field) + .unwrap_or_else(|| format!("{field} is not a valid block field")), + ); + + result.push('\n'); + } + result.trim_end().to_string() + } else if shell::is_json() { + serde_json::to_value(&block).unwrap().to_string() + } else { + block.pretty() + }) + } + + async fn block_field_as_num>(&self, block: B, field: String) -> Result { + Self::block( + self, + block.into(), + false, + // Select only select field + vec![field], + false, + ) + .await? + .parse() + .map_err(Into::into) + } + + pub async fn base_fee>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await + } + + pub async fn age>(&self, block: B) -> Result { + let timestamp_str = + Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); + let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); + Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) + } + + pub async fn timestamp>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, "timestamp".to_string()).await + } + + pub async fn chain(&self) -> Result<&str> { + let genesis_hash = Self::block( + self, + 0, + false, + // Select only block hash + vec![String::from("hash")], + false, + ) + .await?; + + Ok(match &genesis_hash[..] { + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { + match &(Self::block(self, 1920000, false, vec![String::from("hash")], false) + .await?)[..] + { + "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { + "etclive" + } + _ => "ethlive", + } + } + "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", + "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", + "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { + "optimism-mainnet" + } + "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { + "optimism-goerli" + } + "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { + "optimism-kovan" + } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } + "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { + "arbitrum-mainnet" + } + "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", + "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", + "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", + "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", + "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", + "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { + "polygon-pos-amoy-testnet" + } + "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", + "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { + "polygon-zkevm-cardona-testnet" + } + "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", + "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", + "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", + "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", + "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { + match &(Self::block(self, 1, false, vec![String::from("hash")], false).await?)[..] { + "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { + "avalanche-fuji" + } + _ => "avalanche", + } + } + "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", + "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", + _ => "unknown", + }) + } + pub async fn chain_id(&self) -> Result { Ok(self.provider.get_chain_id().await?) } @@ -550,6 +710,67 @@ impl + Clone + Unpin, N: Network> Cast { Ok(code.len().to_string()) } + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use cast::Cast; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; + /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?; + /// println!("{}", tx); + /// # Ok(()) + /// # } + /// ``` + pub async fn transaction( + &self, + tx_hash: Option, + from: Option, + nonce: Option, + field: Option, + to_request: bool, + ) -> Result { + let tx = if let Some(tx_hash) = tx_hash { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + self.provider + .get_transaction_by_hash(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? + } else if let Some(from) = from { + // If nonce is not provided, uses 0. + let nonce = U64::from(nonce.unwrap_or_default()); + let from = from.resolve(self.provider.root()).await?; + + self.provider + .raw_request::<_, Option>( + "eth_getTransactionBySenderAndNonce".into(), + (from, nonce), + ) + .await? + .ok_or_else(|| { + eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) + })? + } else { + eyre::bail!("tx hash or from address is required") + }; + + Ok(if let Some(ref field) = field { + get_pretty_tx_attr::(&tx, field.as_str()) + .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? + } else if shell::is_json() { + // to_value first to sort json object keys + serde_json::to_value(&tx)?.to_string() + } else if to_request { + serde_json::to_string_pretty(&Into::::into(tx))? + } else { + tx.pretty() + }) + } + /// Perform a raw JSON-RPC request /// /// # Example @@ -885,264 +1106,6 @@ impl + Clone + Unpin, N: Network> Cast { } } -impl, N: Network> Cast -where - N::HeaderResponse: UIfmtHeaderExt, - N::BlockResponse: UIfmt, -{ - /// # Example - /// - /// ``` - /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let block = cast.block(5, true, vec![]).await?; - /// println!("{}", block); - /// # Ok(()) - /// # } - /// ``` - pub async fn block>( - &self, - block: B, - full: bool, - fields: Vec, - ) -> Result { - let block = block.into(); - if fields.contains(&"transactions".into()) && !full { - eyre::bail!("use --full to view transactions") - } - - let block = self - .provider - .get_block(block) - .kind(full.into()) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - Ok(if !fields.is_empty() { - let mut result = String::new(); - for field in fields { - result.push_str( - &get_pretty_block_attr::(&block, &field) - .unwrap_or_else(|| format!("{field} is not a valid block field")), - ); - - result.push('\n'); - } - result.trim_end().to_string() - } else if shell::is_json() { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - }) - } - - async fn block_field_as_num>(&self, block: B, field: String) -> Result { - Self::block( - self, - block.into(), - false, - // Select only select field - vec![field], - ) - .await? - .parse() - .map_err(Into::into) - } - - pub async fn base_fee>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await - } - - pub async fn age>(&self, block: B) -> Result { - let timestamp_str = - Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); - Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) - } - - pub async fn timestamp>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, "timestamp".to_string()).await - } - - pub async fn chain(&self) -> Result<&str> { - let genesis_hash = Self::block( - self, - 0, - false, - // Select only block hash - vec![String::from("hash")], - ) - .await?; - - Ok(match &genesis_hash[..] { - "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Self::block(self, 1920000, false, vec![String::from("hash")]).await?)[..] { - "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { - "etclive" - } - _ => "ethlive", - } - } - "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", - "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", - "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { - "optimism-mainnet" - } - "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { - "optimism-goerli" - } - "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { - "optimism-kovan" - } - "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", - "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { - "fraxtal-testnet" - } - "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { - "arbitrum-mainnet" - } - "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", - "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", - "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", - "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", - "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", - "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { - "polygon-pos-amoy-testnet" - } - "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", - "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { - "polygon-zkevm-cardona-testnet" - } - "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", - "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", - "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", - "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", - "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Self::block(self, 1, false, vec![String::from("hash")]).await?)[..] { - "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { - "avalanche-fuji" - } - _ => "avalanche", - } - } - "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", - "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", - _ => "unknown", - }) - } -} - -impl, N: Network> Cast -where - N::Header: Encodable, -{ - /// # Example - /// - /// ``` - /// use alloy_provider::{ProviderBuilder, RootProvider, network::Ethereum}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, Ethereum>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let block = cast.block_raw(5, true).await?; - /// println!("{}", block); - /// # Ok(()) - /// # } - /// ``` - pub async fn block_raw>(&self, block: B, full: bool) -> Result { - let block_id = block.into(); - - let block = self - .provider - .get_block(block_id) - .kind(full.into()) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?; - - let encoded = alloy_rlp::encode(block.header().as_ref()); - - Ok(format!("0x{}", hex::encode(encoded))) - } -} - -impl, N: Network> Cast -where - N::TxEnvelope: Serialize + UIfmtSignatureExt, - N::TransactionResponse: UIfmt, -{ - /// # Example - /// - /// ``` - /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false, false).await?; - /// println!("{}", tx); - /// # Ok(()) - /// # } - /// ``` - pub async fn transaction( - &self, - tx_hash: Option, - from: Option, - nonce: Option, - field: Option, - raw: bool, - to_request: bool, - ) -> Result { - let tx = if let Some(tx_hash) = tx_hash { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - self.provider - .get_transaction_by_hash(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? - } else if let Some(from) = from { - // If nonce is not provided, uses 0. - let nonce = U64::from(nonce.unwrap_or_default()); - let from = from.resolve(self.provider.root()).await?; - - self.provider - .raw_request::<_, Option>( - "eth_getTransactionBySenderAndNonce".into(), - (from, nonce), - ) - .await? - .ok_or_else(|| { - eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) - })? - } else { - eyre::bail!("tx hash or from address is required") - }; - - Ok(if raw { - let encoded = tx.as_ref().encoded_2718(); - format!("0x{}", hex::encode(encoded)) - } else if let Some(ref field) = field { - get_pretty_tx_attr::(&tx, field.as_str()) - .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.clone()))? - } else if shell::is_json() { - // to_value first to sort json object keys - serde_json::to_value(&tx)?.to_string() - } else if to_request { - serde_json::to_string_pretty(&Into::::into(tx))? - } else { - tx.pretty() - }) - } -} - pub struct SimpleCast; impl SimpleCast { @@ -2352,22 +2315,15 @@ impl SimpleCast { /// # Example /// /// ``` - /// use alloy_network::Ethereum; /// use cast::SimpleCast as Cast; /// /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; - /// let tx_envelope = Cast::decode_raw_transaction::(&tx)?; + /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; /// # Ok::<(), eyre::Report>(()) - pub fn decode_raw_transaction>( - tx: &str, - ) -> Result { + pub fn decode_raw_transaction(tx: &str) -> Result { let tx_hex = hex::decode(tx)?; - let tx: N::TxEnvelope = Decodable2718::decode_2718(&mut tx_hex.as_slice())?; - if let Ok(signer) = tx.recover_signer() { - Ok(serde_json::to_string_pretty(&Recovered::new_unchecked(tx, signer))?) - } else { - Ok(serde_json::to_string_pretty(&tx)?) - } + let tx = Decodable2718::decode_2718(&mut tx_hex.as_slice())?; + Ok(tx) } } @@ -2402,6 +2358,44 @@ fn explorer_client( builder.build().map_err(Into::into) } +// Temporary workaround to handle tx raw encoding through FoundryNetwork +// TODO: Once the Network selection UI will be finalized, bring back --raw +// handling to `Cast::transaction` +pub async fn transaction_raw( + config: &Config, + tx_hash: Option, + from: Option, + nonce: Option, +) -> Result { + let provider = ProviderBuilder::::from_config(config)?.build()?; + let tx = if let Some(tx_hash) = tx_hash { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + provider + .get_transaction_by_hash(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? + } else if let Some(from) = from { + // If nonce is not provided, uses 0. + let nonce = U64::from(nonce.unwrap_or_default()); + let from = from.resolve(provider.root()).await?; + + provider + .raw_request::<_, Option<::TransactionResponse>>( + "eth_getTransactionBySenderAndNonce".into(), + (from, nonce), + ) + .await? + .ok_or_else(|| { + eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) + })? + } else { + eyre::bail!("tx hash or from address is required") + }; + + let encoded = tx.as_ref().encoded_2718(); + Ok(format!("0x{}", hex::encode(encoded))) +} + #[cfg(test)] mod tests { use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json}; diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs new file mode 100644 index 0000000000000..3c64491aaea63 --- /dev/null +++ b/crates/cast/src/tempo.rs @@ -0,0 +1,21 @@ +use alloy_primitives::Address; +use alloy_provider::Provider; +use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; + +pub use foundry_wallets::tempo::sign_with_access_key; + +/// Checks whether an access key is already provisioned on-chain. +/// +/// Queries the AccountKeychain precompile's `getKey` function. A key is considered +/// provisioned if the returned `keyId` is non-zero (i.e. the key exists and has not +/// been revoked). +pub async fn is_key_provisioned>( + provider: &P, + wallet_address: Address, + key_address: Address, +) -> bool { + match provider.get_keychain_key(wallet_address, key_address).await { + Ok(info) => info.keyId != Address::ZERO, + Err(_) => false, + } +} diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 50c142000c14c..131c96e6f6c17 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -4,7 +4,7 @@ use alloy_dyn_abi::ErrorExt; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_network::{Network, TransactionBuilder}; -use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, hex}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U64, U256, hex}; use alloy_provider::{PendingTransactionBuilder, Provider}; use alloy_rpc_types::{AccessList, Authorization, TransactionInputKind}; use alloy_signer::Signer; @@ -12,7 +12,7 @@ use alloy_transport::TransportError; use clap::Args; use eyre::{Result, WrapErr}; use foundry_cli::{ - opts::{CliAuthorizationList, EthereumOpts, TransactionOpts}, + opts::{CliAuthorizationList, EthereumOpts, TempoOpts, TransactionOpts}, utils::{self, parse_function_args}, }; use foundry_common::{ @@ -57,6 +57,55 @@ pub struct SendTxOpts { pub browser: BrowserWalletOpts, } +/// Transaction options shared across cast commands that submit on-chain transactions. +#[derive(Debug, Clone, Args)] +#[command(next_help_heading = "Transaction options")] +pub struct TxParams { + /// Gas limit for the transaction. + #[arg(long, env = "ETH_GAS_LIMIT")] + pub gas_limit: Option, + + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. + #[arg(long, env = "ETH_GAS_PRICE")] + pub gas_price: Option, + + /// Max priority fee per gas for EIP1559 transactions. + #[arg(long, env = "ETH_PRIORITY_GAS_PRICE")] + pub priority_gas_price: Option, + + /// Nonce for the transaction. + #[arg(long)] + pub nonce: Option, + + #[command(flatten)] + pub tempo: TempoOpts, +} + +impl TxParams { + pub(crate) fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if let Some(gas_limit) = self.gas_limit { + tx.set_gas_limit(gas_limit.to()); + } + + if let Some(gas_price) = self.gas_price { + if legacy { + tx.set_gas_price(gas_price.to()); + } else { + tx.set_max_fee_per_gas(gas_price.to()); + } + } + + if !legacy && let Some(priority_fee) = self.priority_gas_price { + tx.set_max_priority_fee_per_gas(priority_fee.to()); + } + + self.tempo.apply::(tx, self.nonce.map(|n| n.to())); + } +} + /// Different sender kinds used by [`CastTxBuilder`]. pub enum SenderKind<'a> { /// An address without signer. Used for read-only calls and transactions sent through unlocked diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 0df824f3588da..7d3b913b8780c 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3106,6 +3106,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index c98cfb69357bd..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 29e968aeb5bc6..202b590769857 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -178,7 +178,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 3435617cdc45e..a5e3d35a985ed 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -186,6 +186,9 @@ impl CheatsConfig { pub fn rpc_endpoint(&self, url_or_alias: &str) -> Result { if let Some(endpoint) = self.rpc_endpoints.get(url_or_alias) { Ok(endpoint.clone().try_resolve()) + } else if let Some(builtin_url) = foundry_config::builtin_rpc_url(url_or_alias) { + let url = RpcEndpointUrl::Url(builtin_url.to_string()); + Ok(RpcEndpoint::new(url).resolve()) } else { // check if it's a URL or a path to an existing file to an ipc socket if url_or_alias.starts_with("http") || diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 3fa95c2a3a298..295fb05517818 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,6 +1,6 @@ use crate::{ - Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, Result, Vm::*, - json::json_value_to_token, + Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxExt, CheatsCtxt, DatabaseExt, Result, + Vm::*, json::json_value_to_token, }; use alloy_dyn_abi::DynSolValue; use alloy_evm::EvmEnv; @@ -10,13 +10,14 @@ use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; use foundry_common::provider::ProviderBuilder; -use foundry_evm_core::{ - FoundryContextExt, backend::JournaledState, evm::FoundryEvmNetwork, fork::CreateFork, -}; +use foundry_evm_core::{Env, FoundryContextExt, backend::FoundryJournalExt, fork::CreateFork}; use revm::context::ContextTr; impl Cheatcode for activeForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self {} = self; ccx.ecx .db() @@ -27,103 +28,122 @@ impl Cheatcode for activeForkCall { } impl Cheatcode for createFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber } = self; persist_caller(ccx); - fork_env_op(ccx.ecx, |db, evm_env, _, inner| { - db.roll_fork(None, (*blockNumber).to(), evm_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.roll_fork(None, (*blockNumber).to(), &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(Default::default()) } } impl Cheatcode for rollFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { txHash } = self; persist_caller(ccx); - fork_env_op(ccx.ecx, |db, evm_env, _, inner| { - db.roll_fork_to_transaction(None, *txHash, evm_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.roll_fork_to_transaction(None, *txHash, &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(Default::default()) } } impl Cheatcode for rollFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); - fork_env_op(ccx.ecx, |db, evm_env, _, inner| { - db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(Default::default()) } } impl Cheatcode for rollFork_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); - fork_env_op(ccx.ecx, |db, evm_env, _, inner| { - db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(Default::default()) } } impl Cheatcode for selectForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; - fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { - db.select_fork(*forkId, evm_env, tx_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + db.select_fork(*forkId, &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(Default::default()) } } impl Cheatcode for transact_0Call { - fn apply_full( + fn apply_full>( &self, - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { txHash } = *self; transact(ccx, executor, txHash, None) @@ -131,10 +151,10 @@ impl Cheatcode for transact_0Call { } impl Cheatcode for transact_1Call { - fn apply_full( + fn apply_full>( &self, - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { forkId, txHash } = *self; transact(ccx, executor, txHash, Some(forkId)) @@ -142,7 +162,10 @@ impl Cheatcode for transact_1Call { } impl Cheatcode for allowCheatcodesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account } = self; ccx.ecx.db_mut().allow_cheatcode_access(*account); Ok(Default::default()) @@ -150,7 +173,10 @@ impl Cheatcode for allowCheatcodesCall { } impl Cheatcode for makePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account } = self; ccx.ecx.db_mut().add_persistent_account(*account); Ok(Default::default()) @@ -158,7 +184,10 @@ impl Cheatcode for makePersistent_0Call { } impl Cheatcode for makePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account0, account1 } = self; ccx.ecx.db_mut().add_persistent_account(*account0); ccx.ecx.db_mut().add_persistent_account(*account1); @@ -167,7 +196,10 @@ impl Cheatcode for makePersistent_1Call { } impl Cheatcode for makePersistent_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account0, account1, account2 } = self; ccx.ecx.db_mut().add_persistent_account(*account0); ccx.ecx.db_mut().add_persistent_account(*account1); @@ -177,7 +209,10 @@ impl Cheatcode for makePersistent_2Call { } impl Cheatcode for makePersistent_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db_mut().add_persistent_account(*account); @@ -187,7 +222,10 @@ impl Cheatcode for makePersistent_3Call { } impl Cheatcode for revokePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account } = self; ccx.ecx.db_mut().remove_persistent_account(account); Ok(Default::default()) @@ -195,7 +233,10 @@ impl Cheatcode for revokePersistent_0Call { } impl Cheatcode for revokePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db_mut().remove_persistent_account(account); @@ -205,14 +246,20 @@ impl Cheatcode for revokePersistent_1Call { } impl Cheatcode for isPersistentCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { account } = self; Ok(ccx.ecx.db().is_persistent(account).abi_encode()) } } impl Cheatcode for rpc_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { method, params } = self; let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; @@ -221,7 +268,7 @@ impl Cheatcode for rpc_0Call { } impl Cheatcode for rpc_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { urlOrAlias, method, params } = self; let url = state.config.rpc_endpoint(urlOrAlias)?.url()?; rpc_call(&url, method, params) @@ -229,7 +276,10 @@ impl Cheatcode for rpc_1Call { } impl Cheatcode for eth_getLogsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { @@ -271,7 +321,10 @@ impl Cheatcode for eth_getLogsCall { } impl Cheatcode for getRawBlockHeaderCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { blockNumber } = self; let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?; let provider = ProviderBuilder::::new(&url).build()?; @@ -293,22 +346,24 @@ impl Cheatcode for getRawBlockHeaderCall { } /// Creates and then also selects the new fork -fn create_select_fork( - ccx: &mut CheatsCtxt<'_, '_, FEN>, +fn create_select_fork( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; - fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { - db.create_select_fork(fork, evm_env, tx_env, inner) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let id = db.create_select_fork(fork, &mut evm_env, &mut tx_env, inner)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(id.abi_encode()) } /// Creates a new fork -fn create_fork( - ccx: &mut CheatsCtxt<'_, '_, FEN>, +fn create_fork>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { @@ -318,22 +373,25 @@ fn create_fork( } /// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction( - ccx: &mut CheatsCtxt<'_, '_, FEN>, +fn create_select_fork_at_transaction( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; - fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { - db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction) - }) + let (mut evm_env, mut tx_env) = Env::clone_evm_and_tx(ccx.ecx); + let (db, inner) = ccx.ecx.journal_mut().as_db_and_inner(); + let id = + db.create_select_fork_at_transaction(fork, &mut evm_env, &mut tx_env, inner, *transaction)?; + Env::apply_evm_and_tx(ccx.ecx, evm_env, tx_env); + Ok(id.abi_encode()) } /// Creates a new fork at the given transaction -fn create_fork_at_transaction( - ccx: &mut CheatsCtxt<'_, '_, FEN>, +fn create_fork_at_transaction>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { @@ -343,8 +401,8 @@ fn create_fork_at_transaction( } /// Creates the request object for a new fork request -fn create_fork_request( - ccx: &mut CheatsCtxt<'_, '_, FEN>, +fn create_fork_request>( + ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { @@ -363,33 +421,16 @@ fn create_fork_request( enable_caching: !ccx.state.config.no_storage_caching && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, + evm_env: EvmEnv { + cfg_env: ccx.ecx.cfg_mut().clone(), + block_env: ccx.ecx.block_mut().clone(), + }, evm_opts, }; Ok(fork) } -/// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes -/// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork, -/// createSelectFork). -fn fork_env_op( - ecx: &mut CTX, - f: impl FnOnce( - &mut CTX::Db, - &mut EvmEnv, - &mut CTX::Tx, - &mut JournaledState, - ) -> eyre::Result, -) -> Result { - let mut evm_env = ecx.evm_clone(); - let mut tx_env = ecx.tx_clone(); - let (db, inner) = ecx.db_journal_inner_mut(); - let result = f(db, &mut evm_env, &mut tx_env, inner)?; - ecx.set_evm(evm_env); - ecx.set_tx(tx_env); - Ok(result.abi_encode()) -} - -fn check_broadcast(state: &Cheatcodes) -> Result<()> { +fn check_broadcast(state: &Cheatcodes) -> Result<()> { if state.broadcast.is_none() { Ok(()) } else { @@ -397,9 +438,9 @@ fn check_broadcast(state: &Cheatcodes) -> Result<() } } -fn transact( - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, +fn transact>( + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, transaction: B256, fork_id: Option, ) -> Result { @@ -411,7 +452,7 @@ fn transact( // state of caller contract is not lost when fork changes). // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 -fn persist_caller(ccx: &mut CheatsCtxt<'_, '_, FEN>) { +fn persist_caller>(ccx: &mut CheatsCtxt<'_, CTX>) { ccx.ecx.db_mut().add_persistent_account(ccx.caller); } @@ -427,11 +468,7 @@ fn rpc_call(url: &str, method: &str, params: &str) -> Result { .map_err(|err| fmt_err!("failed to parse result: {err}"))?, ); - let payload = match &result_as_tokens { - DynSolValue::Bytes(b) => b.clone(), - _ => result_as_tokens.abi_encode(), - }; - Ok(DynSolValue::Bytes(payload).abi_encode()) + Ok(result_as_tokens.abi_encode()) } /// Convert fixed bytes and address values to bytes in order to prevent encoding issues. @@ -443,6 +480,9 @@ fn convert_to_bytes(token: &DynSolValue) -> DynSolValue { DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec()) } DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), + // Convert tuple values to prevent encoding issues. + // See: + DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()), val => val.clone(), } } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 3f8bd91b0e8d9..5e762ec62a035 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_network::{Ethereum, Network, ReceiptResponse}; +use alloy_network::{Network, ReceiptResponse}; use alloy_primitives::{Bytes, U256, hex, map::Entry}; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; @@ -792,7 +792,7 @@ impl Cheatcode for getBroadcastCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, *chainId, &state.config.broadcast, @@ -810,7 +810,7 @@ impl Cheatcode for getBroadcasts_0Call { let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(map_broadcast_tx_type(*txType)); - let broadcasts = reader.read::()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -830,7 +830,7 @@ impl Cheatcode for getBroadcasts_1Call { let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?; - let broadcasts = reader.read::()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -849,7 +849,7 @@ impl Cheatcode for getDeployment_0Call { let Self { contractName } = self; let chain_id = ccx.ecx.cfg().chain_id(); - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, chain_id, &ccx.state.config.broadcast, @@ -864,7 +864,7 @@ impl Cheatcode for getDeployment_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, *chainId, &state.config.broadcast, @@ -883,7 +883,7 @@ impl Cheatcode for getDeploymentsCall { .with_tx_type(CallKind::Create) .with_tx_type(CallKind::Create2); - let broadcasts = reader.read::()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -929,19 +929,22 @@ fn parse_broadcast_results( .collect() } -fn latest_broadcast( +fn latest_broadcast( contract_name: &String, chain_id: u64, broadcast_path: &Path, filters: Vec, -) -> Result { +) -> Result +where + N::TxEnvelope: for<'d> serde::Deserialize<'d>, +{ let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?; for filter in filters { reader = reader.with_tx_type(filter); } - let broadcast = reader.read_latest::()?; + let broadcast = reader.read_latest::()?; let results = reader.into_tx_receipts(broadcast); @@ -957,7 +960,9 @@ fn latest_broadcast( mod tests { use super::*; use crate::CheatsConfig; - use std::sync::Arc; + use alloy_primitives::{address, b256}; + use foundry_evm_core::evm::TempoEvmNetwork; + use std::{env, fs as stdfs, sync::Arc}; fn cheats() -> Cheatcodes { let config = CheatsConfig { @@ -1031,4 +1036,99 @@ mod tests { let err = result.unwrap_err().to_string(); assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder")); } + + fn unique_temp_dir(prefix: &str) -> PathBuf { + env::temp_dir().join(format!( + "foundry-cheatcodes-{prefix}-{}", + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() + )) + } + + #[test] + fn test_latest_broadcast_reads_tempo_sequences() { + let root = unique_temp_dir("tempo-broadcast"); + let broadcast_path = root.join("broadcast"); + let sequence_dir = broadcast_path.join("Counter.s.sol").join("31337"); + stdfs::create_dir_all(&sequence_dir).unwrap(); + + let tx_hash = "0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f"; + let block_hash = "0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66"; + let from = "0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"; + let contract_address = "0x20c0000000000000000000000000000000000000"; + let zero_bloom = format!("0x{}", "0".repeat(512)); + + let sequence = serde_json::json!({ + "transactions": [{ + "hash": tx_hash, + "transactionType": "CREATE", + "contractName": "Counter", + "contractAddress": contract_address, + "function": serde_json::Value::Null, + "arguments": serde_json::Value::Null, + "transaction": { + "type": "0x76", + "from": from, + "to": serde_json::Value::Null, + "data": "0x", + "value": "0x0", + "gas": "0x5208", + "nonce": "0x0", + "accessList": [], + "calls": [], + "nonceKey": "0x0", + "feePayerSignature": serde_json::Value::Null, + "validBefore": serde_json::Value::Null, + "validAfter": serde_json::Value::Null, + "keyAuthorization": serde_json::Value::Null, + "aaAuthorizationList": [] + }, + "additionalContracts": [], + "isFixedGasLimit": false + }], + "receipts": [{ + "type": "0x76", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": zero_bloom, + "transactionHash": tx_hash, + "transactionIndex": "0x0", + "blockHash": block_hash, + "blockNumber": "0x7", + "gasUsed": "0x5208", + "effectiveGasPrice": "0x1", + "from": from, + "to": serde_json::Value::Null, + "contractAddress": contract_address, + "feePayer": from + }], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1, + "chain": 31337, + "commit": serde_json::Value::Null + }); + + fs::write_json_file(&sequence_dir.join("run-1.json"), &sequence).unwrap(); + + let latest = latest_broadcast::<::Network>( + &"Counter".to_owned(), + 31337, + &broadcast_path, + vec![CallKind::Create], + ) + .unwrap(); + + assert_eq!( + latest.txHash, + b256!("04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f") + ); + assert_eq!(latest.blockNumber, 7); + assert!(matches!(latest.txType, BroadcastTxType::Create)); + assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); + assert!(latest.success); + + stdfs::remove_dir_all(root).unwrap(); + } } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index f3e922e5c14b4..7775d61841b2c 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -16,18 +16,15 @@ pub extern crate foundry_cheatcodes_spec as spec; extern crate tracing; use alloy_primitives::Address; -use foundry_evm_core::{ - backend::DatabaseExt, - evm::{FoundryContextFor, FoundryEvmNetwork}, -}; +use foundry_evm_core::backend::DatabaseExt; use revm::context::{ContextTr, JournalTr}; pub use Vm::ForgeContext; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; -pub use foundry_evm_core::evm::NestedEvmClosure; pub use inspector::{ BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, + CheatsCtxExt, NestedEvmClosure, }; pub use spec::{CheatcodeDef, Vm}; @@ -71,7 +68,7 @@ pub(crate) trait Cheatcode: CheatcodeDef { /// Applies this cheatcode to the given state. /// /// Implement this function if you don't need access to the EVM data. - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let _ = state; unimplemented!("{}", Self::CHEATCODE.func.id) } @@ -80,7 +77,7 @@ pub(crate) trait Cheatcode: CheatcodeDef { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { self.apply(ccx.state) } @@ -88,10 +85,10 @@ pub(crate) trait Cheatcode: CheatcodeDef { /// /// Implement this function if you need access to the executor. #[inline(always)] - fn apply_full( + fn apply_full( &self, - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let _ = executor; self.apply_stateful(ccx) @@ -99,19 +96,19 @@ pub(crate) trait Cheatcode: CheatcodeDef { } /// The cheatcode context. -pub struct CheatsCtxt<'a, 'db, FEN: FoundryEvmNetwork + 'db> { +pub struct CheatsCtxt<'a, CTX> { /// The cheatcodes inspector state. - pub(crate) state: &'a mut Cheatcodes, + pub(crate) state: &'a mut Cheatcodes, /// The EVM context. - pub(crate) ecx: &'a mut FoundryContextFor<'db, FEN>, + pub(crate) ecx: &'a mut CTX, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. pub(crate) gas_limit: u64, } -impl<'a, 'db, FEN: FoundryEvmNetwork> std::ops::Deref for CheatsCtxt<'a, 'db, FEN> { - type Target = FoundryContextFor<'db, FEN>; +impl std::ops::Deref for CheatsCtxt<'_, CTX> { + type Target = CTX; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -119,14 +116,14 @@ impl<'a, 'db, FEN: FoundryEvmNetwork> std::ops::Deref for CheatsCtxt<'a, 'db, FE } } -impl<'db, FEN: FoundryEvmNetwork> std::ops::DerefMut for CheatsCtxt<'_, 'db, FEN> { +impl std::ops::DerefMut for CheatsCtxt<'_, CTX> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { self.ecx } } -impl CheatsCtxt<'_, '_, FEN> { +impl CheatsCtxt<'_, CTX> { pub(crate) fn ensure_not_precompile(&self, address: &Address) -> Result<()> { if self.is_precompile(address) { Err(precompile_error(address)) } else { Ok(()) } } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 06e8cbdb66ab1..c6441007ab0ce 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -5,7 +5,11 @@ use alloy_chains::Chain as AlloyChain; use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; -use foundry_evm_core::{constants::MAGIC_SKIP, evm::FoundryEvmNetwork}; +use foundry_evm_core::{ + backend::{DatabaseExt, FoundryJournalExt}, + constants::MAGIC_SKIP, + env::FoundryContextExt, +}; use revm::context::{ContextTr, JournalTr}; use std::str::FromStr; @@ -15,28 +19,28 @@ pub(crate) mod expect; pub(crate) mod revert_handlers; impl Cheatcode for breakpoint_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } } impl Cheatcode for getFoundryVersionCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(SEMVER_VERSION.abi_encode()) } } impl Cheatcode for rpcUrlCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { rpcAlias } = self; let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode(); Ok(url) @@ -44,21 +48,21 @@ impl Cheatcode for rpcUrlCall { } impl Cheatcode for rpcUrlsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for rpcUrlStructsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for sleepCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { duration } = self; let sleep_duration = std::time::Duration::from_millis(duration.saturating_to()); std::thread::sleep(sleep_duration); @@ -67,21 +71,20 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skip_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { skipTest } = *self; - if skipTest { - // Skip should not work if called deeper than at test level. - // Since we're not returning the magic skip bytes, this will cause a test failure. - ensure!(ccx.ecx.journal().depth() <= 1, "`skip` can only be used at test level"); - Err([MAGIC_SKIP, &[]].concat().into()) - } else { - Ok(Default::default()) - } + skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) } } impl Cheatcode for skip_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { skipTest, reason } = self; if *skipTest { // Skip should not work if called deeper than at test level. @@ -95,14 +98,14 @@ impl Cheatcode for skip_1Call { } impl Cheatcode for getChain_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainAlias } = self; get_chain(state, chainAlias) } } impl Cheatcode for getChain_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainId } = self; // Convert the chainId to a string and use the existing get_chain function let chain_id_str = chainId.to_string(); @@ -111,12 +114,7 @@ impl Cheatcode for getChain_1Call { } /// Adds or removes the given breakpoint to the state. -fn breakpoint( - state: &mut Cheatcodes, - caller: &Address, - s: &str, - add: bool, -) -> Result { +fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result { let mut chars = s.chars(); let (Some(point), None) = (chars.next(), chars.next()) else { bail!("breakpoints must be exactly one character"); @@ -133,7 +131,7 @@ fn breakpoint( } /// Gets chain information for the given alias. -fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { +fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { // Parse the chain alias - works for both chain names and IDs let alloy_chain = AlloyChain::from_str(chain_alias) .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?; diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index fcaf6e9c6c3bb..491a321525d9e 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -8,7 +8,7 @@ use alloy_rlp::{Decodable, Encodable}; use alloy_sol_types::SolValue; use foundry_common::{TYPE_BINDING_PREFIX, fs}; use foundry_config::fs_permissions::FsAccessKind; -use foundry_evm_core::{constants::DEFAULT_CREATE2_DEPLOYER, evm::FoundryEvmNetwork}; +use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use foundry_evm_fuzz::strategies::BoundMutator; use proptest::prelude::Strategy; use rand::{Rng, RngCore, seq::SliceRandom}; @@ -33,7 +33,7 @@ pub struct IgnoredTraces { } impl Cheatcode for labelCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account, newLabel } = self; state.labels.insert(*account, newLabel.clone()); Ok(Default::default()) @@ -41,7 +41,7 @@ impl Cheatcode for labelCall { } impl Cheatcode for getLabelCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account } = self; Ok(match state.labels.get(account) { Some(label) => label.abi_encode(), @@ -51,7 +51,7 @@ impl Cheatcode for getLabelCall { } impl Cheatcode for computeCreateAddressCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { nonce, deployer } = self; ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64"); Ok(deployer.create(nonce.to()).abi_encode()) @@ -59,28 +59,28 @@ impl Cheatcode for computeCreateAddressCall { } impl Cheatcode for computeCreate2Address_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash, deployer } = self; Ok(deployer.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for computeCreate2Address_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash } = self; Ok(DEFAULT_CREATE2_DEPLOYER.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for ensNamehashCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(namehash(name).abi_encode()) } } impl Cheatcode for bound_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = U256::bound(current, min, max, state.test_runner()) else { bail!("cannot bound {current} in [{min}, {max}] range") @@ -90,7 +90,7 @@ impl Cheatcode for bound_0Call { } impl Cheatcode for bound_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = I256::bound(current, min, max, state.test_runner()) else { bail!("cannot bound {current} in [{min}, {max}] range") @@ -100,27 +100,27 @@ impl Cheatcode for bound_1Call { } impl Cheatcode for randomUint_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { random_uint(state, None, None) } } impl Cheatcode for randomUint_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { min, max } = *self; random_uint(state, None, Some((min, max))) } } impl Cheatcode for randomUint_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_uint(state, Some(bits), None) } } impl Cheatcode for randomAddressCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { Ok(DynSolValue::type_strategy(&DynSolType::Address) .new_tree(state.test_runner()) .unwrap() @@ -130,27 +130,27 @@ impl Cheatcode for randomAddressCall { } impl Cheatcode for randomInt_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { random_int(state, None) } } impl Cheatcode for randomInt_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_int(state, Some(bits)) } } impl Cheatcode for randomBoolCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_bool: bool = state.rng().random(); Ok(rand_bool.abi_encode()) } } impl Cheatcode for randomBytesCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { len } = *self; ensure!( len <= U256::from(usize::MAX), @@ -163,24 +163,24 @@ impl Cheatcode for randomBytesCall { } impl Cheatcode for randomBytes4Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u32 = state.rng().next_u32(); Ok(B32::from(rand_u32).abi_encode()) } } impl Cheatcode for randomBytes8Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u64 = state.rng().next_u64(); Ok(B64::from(rand_u64).abi_encode()) } } impl Cheatcode for pauseTracingCall { - fn apply_full( + fn apply_full( &self, - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to pause @@ -200,10 +200,10 @@ impl Cheatcode for pauseTracingCall { } impl Cheatcode for resumeTracingCall { - fn apply_full( + fn apply_full( &self, - ccx: &mut CheatsCtxt<'_, '_, FEN>, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, CTX>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to unpause @@ -223,18 +223,19 @@ impl Cheatcode for resumeTracingCall { } impl Cheatcode for interceptInitcodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; - if state.intercept_next_create_call { + if !state.intercept_next_create_call { + state.intercept_next_create_call = true; + } else { bail!("vm.interceptInitcode() has already been called") } - state.intercept_next_create_call = true; Ok(Default::default()) } } impl Cheatcode for setArbitraryStorage_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, false); @@ -243,7 +244,7 @@ impl Cheatcode for setArbitraryStorage_0Call { } impl Cheatcode for setArbitraryStorage_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, overwrite } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, *overwrite); @@ -252,7 +253,10 @@ impl Cheatcode for setArbitraryStorage_1Call { } impl Cheatcode for copyStorageCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful>( + &self, + ccx: &mut CheatsCtxt<'_, CTX>, + ) -> Result { let Self { from, to } = self; ensure!( @@ -276,7 +280,7 @@ impl Cheatcode for copyStorageCall { } impl Cheatcode for sortCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut sorted_values = array.clone(); @@ -287,7 +291,7 @@ impl Cheatcode for sortCall { } impl Cheatcode for shuffleCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut shuffled_values = array.clone(); @@ -299,7 +303,7 @@ impl Cheatcode for shuffleCall { } impl Cheatcode for setSeedCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { seed } = self; ccx.state.set_seed(*seed); Ok(Default::default()) @@ -308,11 +312,7 @@ impl Cheatcode for setSeedCall { /// Helper to generate a random `uint` value (with given bits or bounded if specified) /// from type strategy. -fn random_uint( - state: &mut Cheatcodes, - bits: Option, - bounds: Option<(U256, U256)>, -) -> Result { +fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, U256)>) -> Result { if let Some(bits) = bits { // Generate random with specified bits. ensure!(bits <= U256::from(256), "number of bits cannot exceed 256"); @@ -345,7 +345,7 @@ fn random_uint( } /// Helper to generate a random `int` value (with given bits if specified) from type strategy. -fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { +fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { let no_bits = bits.unwrap_or(U256::from(256)); ensure!(no_bits <= U256::from(256), "number of bits cannot exceed 256"); Ok(DynSolValue::type_strategy(&DynSolType::Int(no_bits.to::())) @@ -356,7 +356,7 @@ fn random_int(state: &mut Cheatcodes, bits: Option< } impl Cheatcode for eip712HashType_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeNameOrDefinition } = self; let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; @@ -366,7 +366,7 @@ impl Cheatcode for eip712HashType_0Call { } impl Cheatcode for eip712HashType_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bindingsPath, typeName } = self; let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; @@ -377,7 +377,7 @@ impl Cheatcode for eip712HashType_1Call { } impl Cheatcode for eip712HashStruct_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeNameOrDefinition, abiEncodedData } = self; let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; @@ -388,7 +388,7 @@ impl Cheatcode for eip712HashStruct_0Call { } impl Cheatcode for eip712HashStruct_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bindingsPath, typeName, abiEncodedData } = self; let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; @@ -399,7 +399,7 @@ impl Cheatcode for eip712HashStruct_1Call { } impl Cheatcode for eip712HashTypedDataCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { jsonData } = self; let typed_data: TypedData = serde_json::from_str(jsonData)?; let digest = typed_data.eip712_signing_hash()?; @@ -410,9 +410,9 @@ impl Cheatcode for eip712HashTypedDataCall { /// Returns EIP-712 canonical type definition from the provided string type representation or type /// name. If type name provided, then it looks up bindings from file generated by `forge bind-json`. -fn get_canonical_type_def( +fn get_canonical_type_def( name_or_def: &String, - state: &mut Cheatcodes, + state: &mut Cheatcodes, path: Option, ) -> Result { let type_def = if name_or_def.contains('(') { @@ -501,7 +501,7 @@ fn get_struct_hash(primary: &str, type_def: &String, abi_encoded_data: &Bytes) - } impl Cheatcode for toRlpCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; let mut buf = Vec::new(); @@ -512,7 +512,7 @@ impl Cheatcode for toRlpCall { } impl Cheatcode for fromRlpCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { rlp } = self; let decoded: Vec = Vec::::decode(&mut rlp.as_ref()) diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 4d130416b949c..e9a1936013272 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -65,7 +65,7 @@ foundry-test-utils.workspace = true rexpect = "0.6" [features] -default = ["jemalloc"] +default = ["jemalloc", "asm-keccak"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index da2c7f4caff02..8ebeca5ee2549 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -199,23 +199,20 @@ impl SessionSource { } async fn build_runner(&mut self, final_pc: usize) -> Result { - let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?; + let env = self.config.evm_opts.env().await?; let backend = match self.config.backend.clone() { Some(backend) => backend, None => { - let fork = self.config.evm_opts.get_fork( - &self.config.foundry_config, - evm_env.cfg_env.chain_id, - fork_block, - ); + let fork = + self.config.evm_opts.get_fork(&self.config.foundry_config, env.evm_env.clone()); let backend = Backend::spawn(fork)?; self.config.backend = Some(backend.clone()); backend } }; - let executor = ExecutorBuilder::default() + let executor = ExecutorBuilder::new() .inspectors(|stack| { stack .logs(self.config.foundry_config.live_logs) @@ -227,7 +224,6 @@ impl SessionSource { self.config.evm_opts.clone(), None, None, - None, ) .into(), ) @@ -235,7 +231,7 @@ impl SessionSource { .gas_limit(self.config.evm_opts.gas_limit()) .spec_id(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) - .build(evm_env, tx_env, backend); + .build(env, backend); Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone())) } @@ -549,8 +545,9 @@ impl Type { pt::Expression::Multiply(_, lhs, rhs) | pt::Expression::Divide(_, lhs, rhs) => { match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) | -(Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { + (Some(DynSolType::Int(_)), Some(DynSolType::Int(_))) | + (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) | + (Some(DynSolType::Uint(_)), Some(DynSolType::Int(_))) => { Some(Self::Builtin(DynSolType::Int(256))) } _ => { @@ -821,7 +818,7 @@ impl Type { custom_type: &mut Vec, contract_name: Option, ) -> Result> { - if let Some("this" | "super") = custom_type.last().map(String::as_str) { + if let Some("this") | Some("super") = custom_type.last().map(String::as_str) { custom_type.pop(); } if custom_type.is_empty() { @@ -958,7 +955,7 @@ impl Type { Ok(None) } } - other_expr => Ok(Self::ethabi(other_expr, intermediate)), + ty => Ok(Self::ethabi(ty, intermediate)), }; // re-run everything with the resolved variable in case we're accessing a builtin member // for example array or bytes length etc @@ -1080,12 +1077,12 @@ impl Type { match self { Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner) | DynSolType::FixedArray(inner, _)) => { + Some(DynSolType::Array(inner)) | Some(DynSolType::FixedArray(inner, _)) => { Some(*inner) } - Some(DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_)) => { - Some(DynSolType::FixedBytes(1)) - } + Some(DynSolType::Bytes) + | Some(DynSolType::String) + | Some(DynSolType::FixedBytes(_)) => Some(DynSolType::FixedBytes(1)), ty => ty, } } @@ -1095,7 +1092,7 @@ impl Type { /// Returns whether this type is dynamic #[inline] - const fn is_dynamic(&self) -> bool { + fn is_dynamic(&self) -> bool { match self { // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are // not dynamic, nor are tuples of non-dynamic types. @@ -1107,22 +1104,23 @@ impl Type { /// Returns whether this type is an array #[inline] - const fn is_array(&self) -> bool { + fn is_array(&self) -> bool { matches!( self, Self::Array(_) | Self::FixedArray(_, _) - | Self::Builtin(DynSolType::Array(_) | DynSolType::FixedArray(_, _)) + | Self::Builtin(DynSolType::Array(_)) + | Self::Builtin(DynSolType::FixedArray(_, _)) ) } /// Returns whether this type is a dynamic array (can call push, pop) #[inline] - const fn is_dynamic_array(&self) -> bool { + fn is_dynamic_array(&self) -> bool { matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) } - const fn is_fixed_bytes(&self) -> bool { + fn is_fixed_bytes(&self) -> bool { matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) } } @@ -1141,7 +1139,7 @@ fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option _ => None, }); match vis { - Some(pt::Visibility::External(_) | pt::Visibility::Public(_)) => { + Some(pt::Visibility::External(_)) | Some(pt::Visibility::Public(_)) => { match custom_type.first().unwrap().as_str() { "address" => Some(DynSolType::Address), "selector" => Some(DynSolType::FixedBytes(4)), @@ -1605,7 +1603,7 @@ mod tests { T: AsRef + std::fmt::Display + 'a, I: IntoIterator + 'a, { - for (input, expected) in input { + for (input, expected) in input.into_iter() { let input = input.as_ref(); let ty = get_type_ethabi(s, input, true); assert_eq!(ty.as_ref(), Some(expected), "\n{input}"); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 79b15381607aa..165154da88f63 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -21,6 +21,8 @@ foundry-evm.workspace = true foundry-evm-networks.workspace = true foundry-wallets.workspace = true +tempo-primitives.workspace = true + foundry-compilers.workspace = true solar.workspace = true diff --git a/crates/cli/src/opts/network.rs b/crates/cli/src/opts/network.rs index ffb63ad7048d4..f78af85842c95 100644 --- a/crates/cli/src/opts/network.rs +++ b/crates/cli/src/opts/network.rs @@ -1,6 +1,3 @@ -use alloy_chains::Chain; -use alloy_primitives::ChainId; - /// Network selection, defaulting to Ethereum #[derive(Clone, Debug, Default, clap::ValueEnum)] pub enum NetworkVariant { @@ -12,16 +9,3 @@ pub enum NetworkVariant { /// Tempo Tempo, } - -impl From for NetworkVariant { - fn from(chain_id: ChainId) -> Self { - let chain = Chain::from_id(chain_id); - if chain.is_tempo() { - Self::Tempo - } else if chain.is_optimism() { - Self::Optimism - } else { - Default::default() - } - } -} diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs index 2c9e5d9604884..647f52d316a6c 100644 --- a/crates/cli/src/utils/tempo.rs +++ b/crates/cli/src/utils/tempo.rs @@ -1,6 +1,7 @@ use std::str::FromStr; -use alloy_primitives::{Address, hex}; +use alloy_primitives::Address; +use tempo_primitives::TempoAddressExt; /// Parses a fee token address. pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ @@ -9,7 +10,7 @@ pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ fn token_id_to_address(token_id: u64) -> Address { let mut address_bytes = [0u8; 20]; - address_bytes[..12].copy_from_slice(&hex!("20C000000000000000000000")); + address_bytes[..12].copy_from_slice(&Address::TIP20_PREFIX); address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); Address::from(address_bytes) } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ac7a8c12eae71..706527093cc8e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,6 +17,7 @@ foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-primitives.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 964b26b978fbe..fd208b18da5a8 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -3,13 +3,13 @@ use std::num::NonZeroU64; use alloy_consensus::{ - BlockHeader, Eip658Value, Signed, Transaction as TxTrait, TxEip1559, TxEip2930, - TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718, + BlockHeader, Eip658Value, Receipt, ReceiptWithBloom, Signed, Transaction as TxTrait, TxEip1559, + TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718, transaction::TxHashRef, }; use alloy_network::{ - AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope, - BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse, + AnyReceiptEnvelope, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, + AnyTxEnvelope, BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse, }; use alloy_primitives::{ Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex, @@ -18,6 +18,7 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxReceipt}; use op_alloy_consensus::{OpTxEnvelope, TxDeposit}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; @@ -61,9 +62,7 @@ impl UIfmt for Option { impl UIfmt for [T] { fn pretty(&self) -> String { - if self.is_empty() { - "[]".to_string() - } else { + if !self.is_empty() { let mut s = String::with_capacity(self.len() * 64); s.push_str("[\n"); for item in self { @@ -75,13 +74,15 @@ impl UIfmt for [T] { } s.push(']'); s + } else { + "[]".to_string() } } } impl UIfmt for String { fn pretty(&self) -> String { - self.clone() + self.to_string() } } @@ -178,10 +179,37 @@ impl UIfmt for Signature { } } -/// Pretty-prints the common fields of any `TransactionReceipt`. -fn pretty_receipt>(receipt: &TransactionReceipt, tx_type: u8) -> String { - let mut pretty = format!( - " +impl UIfmt for AnyTransactionReceipt { + fn pretty(&self) -> String { + let Self { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + from, + to, + gas_used, + contract_address, + effective_gas_price, + inner: + AnyReceiptEnvelope { + r#type: transaction_type, + inner: + ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + }, + blob_gas_price, + blob_gas_used, + }, + other, + } = self; + + let mut pretty = format!( + " blockHash {} blockNumber {} contractAddress {} @@ -198,41 +226,31 @@ transactionIndex {} type {} blobGasPrice {} blobGasUsed {}", - receipt.block_hash.pretty(), - receipt.block_number.pretty(), - receipt.contract_address.pretty(), - receipt.inner.cumulative_gas_used().pretty(), - receipt.effective_gas_price.pretty(), - receipt.from.pretty(), - receipt.gas_used.pretty(), - serde_json::to_string(receipt.inner.logs()).unwrap(), - receipt.inner.bloom().pretty(), - receipt.state_root().pretty(), - receipt.inner.status_or_post_state().pretty(), - receipt.transaction_hash.pretty(), - receipt.transaction_index.pretty(), - tx_type, - receipt.blob_gas_price.pretty(), - receipt.blob_gas_used.pretty() - ); - - if let Some(to) = receipt.to { - pretty.push_str(&format!("\nto {}", to.pretty())); - } - - pretty -} - -impl UIfmt for TransactionReceipt { - fn pretty(&self) -> String { - pretty_receipt(self, self.transaction_type() as u8) - } -} + block_hash.pretty(), + block_number.pretty(), + contract_address.pretty(), + cumulative_gas_used.pretty(), + effective_gas_price.pretty(), + from.pretty(), + gas_used.pretty(), + serde_json::to_string(&logs).unwrap(), + logs_bloom.pretty(), + self.state_root().pretty(), + status.pretty(), + transaction_hash.pretty(), + transaction_index.pretty(), + transaction_type, + blob_gas_price.pretty(), + blob_gas_used.pretty() + ); + + if let Some(to) = to { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + // additional captured fields + pretty.push_str(&other.pretty()); -impl UIfmt for AnyTransactionReceipt { - fn pretty(&self) -> String { - let mut pretty = pretty_receipt(&self.inner, self.inner.inner.r#type); - pretty.push_str(&self.other.pretty()); pretty } } @@ -509,8 +527,8 @@ validAfter {}", self.nonce_key.pretty(), self.nonce.pretty(), self.fee_payer_signature.pretty(), - self.valid_before.pretty(), self.valid_after.pretty(), + self.valid_before.pretty(), ) } } @@ -715,39 +733,114 @@ impl UIfmt for SignedAuthorization { } } +impl UIfmt for FoundryReceiptEnvelope +where + T: UIfmt + Clone + core::fmt::Debug + PartialEq + Eq, +{ + fn pretty(&self) -> String { + let receipt = self.as_receipt(); + let deposit_info = match self { + Self::Deposit(d) => { + format!( + " +depositNonce {} +depositReceiptVersion {}", + d.receipt.deposit_nonce.pretty(), + d.receipt.deposit_receipt_version.pretty() + ) + } + _ => String::new(), + }; + + format!( + " +status {} +cumulativeGasUsed {} +logs {} +logsBloom {} +type {}{}", + receipt.status.pretty(), + receipt.cumulative_gas_used.pretty(), + receipt.logs.pretty(), + self.logs_bloom().pretty(), + self.tx_type() as u8, + deposit_info + ) + } +} + +impl UIfmt for FoundryTxReceipt { + fn pretty(&self) -> String { + let receipt = &self.0.inner; + let other = &self.0.other; + + let mut pretty = format!( + " +blockHash {} +blockNumber {} +contractAddress {} +cumulativeGasUsed {} +effectiveGasPrice {} +from {} +gasUsed {} +logs {} +logsBloom {} +root {} +status {} +transactionHash {} +transactionIndex {} +type {} +blobGasPrice {} +blobGasUsed {}", + receipt.block_hash().pretty(), + receipt.block_number().pretty(), + receipt.contract_address().pretty(), + receipt.cumulative_gas_used().pretty(), + receipt.effective_gas_price().pretty(), + receipt.from().pretty(), + receipt.gas_used().pretty(), + serde_json::to_string(receipt.inner.logs()).unwrap(), + receipt.inner.logs_bloom().pretty(), + self.state_root().pretty(), + receipt.status().pretty(), + receipt.transaction_hash().pretty(), + receipt.transaction_index().pretty(), + receipt.inner.tx_type() as u8, + receipt.blob_gas_price().pretty(), + receipt.blob_gas_used().pretty() + ); + + if let Some(to) = receipt.to() { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + // additional captured fields + pretty.push_str(&other.pretty()); + + pretty + } +} + pub trait UIfmtHeaderExt { fn size_pretty(&self) -> String; - fn total_difficulty_pretty(&self) -> String; } impl UIfmtHeaderExt for Header { fn size_pretty(&self) -> String { self.size.pretty() } - - fn total_difficulty_pretty(&self) -> String { - self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() - } } impl UIfmtHeaderExt for AnyRpcHeader { fn size_pretty(&self) -> String { self.size.pretty() } - - fn total_difficulty_pretty(&self) -> String { - self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() - } } impl UIfmtHeaderExt for TempoHeaderResponse { fn size_pretty(&self) -> String { self.inner.size.pretty() } - - fn total_difficulty_pretty(&self) -> String { - self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty() - } } pub trait UIfmtSignatureExt { @@ -783,6 +876,12 @@ impl UIfmtSignatureExt for OpTxEnvelope { } } +impl UIfmtSignatureExt for FoundryTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + self.clone().try_into_eth().ok().and_then(|envelope| envelope.signature_pretty()) + } +} + impl UIfmtSignatureExt for TempoTxEnvelope { fn signature_pretty(&self) -> Option<(String, String, String)> { let sig = match self { @@ -814,39 +913,31 @@ pub trait UIfmtReceiptExt { fn tx_type_pretty(&self) -> String; } -fn receipt_logs_pretty>(receipt: &TransactionReceipt) -> String { - serde_json::to_string(receipt.inner.logs()).unwrap_or_default() -} - -fn receipt_logs_bloom_pretty>(receipt: &TransactionReceipt) -> String { - receipt.inner.bloom().pretty() -} - -impl UIfmtReceiptExt for TransactionReceipt { +impl UIfmtReceiptExt for AnyTransactionReceipt { fn logs_pretty(&self) -> String { - receipt_logs_pretty(self) + serde_json::to_string(&self.inner.inner.inner.receipt.logs).unwrap_or_default() } fn logs_bloom_pretty(&self) -> String { - receipt_logs_bloom_pretty(self) + self.inner.inner.inner.logs_bloom.pretty() } fn tx_type_pretty(&self) -> String { - self.transaction_type().to_string() + self.inner.inner.r#type.to_string() } } -impl UIfmtReceiptExt for AnyTransactionReceipt { +impl UIfmtReceiptExt for FoundryTxReceipt { fn logs_pretty(&self) -> String { - receipt_logs_pretty(&self.inner) + serde_json::to_string(self.0.inner.inner.logs()).unwrap_or_default() } fn logs_bloom_pretty(&self) -> String { - receipt_logs_bloom_pretty(&self.inner) + self.0.inner.inner.logs_bloom().pretty() } fn tx_type_pretty(&self) -> String { - self.inner.inner.r#type.to_string() + (self.0.inner.inner.tx_type() as u8).to_string() } } @@ -971,7 +1062,7 @@ where "size" => Some(block.header().size_pretty()), "stateRoot" | "state_root" => Some(block.header().state_root().pretty()), "timestamp" => Some(block.header().timestamp().pretty()), - "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()), + "totalDifficulty" | "total_difficulty" => Some(block.header().difficulty().pretty()), "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()), "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()), "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()), @@ -1060,7 +1151,7 @@ requestsHash {}", header.timestamp().pretty(), fmt_timestamp(header.timestamp()), header.withdrawals_root().pretty(), - header.total_difficulty_pretty(), + header.difficulty().pretty(), header.blob_gas_used().pretty(), header.excess_blob_gas().pretty(), header.requests_hash().pretty(), @@ -1088,9 +1179,9 @@ fn fmt_timestamp(timestamp: u64) -> String { #[cfg(test)] mod tests { use super::*; - use alloy_network::Ethereum; use alloy_primitives::B256; use alloy_rpc_types::Authorization; + use foundry_primitives::FoundryNetwork; use similar_asserts::assert_eq; use std::str::FromStr; @@ -1488,47 +1579,51 @@ yParity 0" #[test] fn test_pretty_tx_attr() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: ::BlockResponse = serde_json::from_str(block).unwrap(); + let block: ::BlockResponse = + serde_json::from_str(block).unwrap(); let txs = match block.transactions() { BlockTransactions::Full(txes) => txes, _ => panic!("not full transactions"), }; - assert_eq!(None, get_pretty_tx_attr::(&txs[0], "")); - assert_eq!(Some("3".to_string()), get_pretty_tx_attr::(&txs[0], "blockNumber")); + assert_eq!(None, get_pretty_tx_attr::(&txs[0], "")); + assert_eq!( + Some("3".to_string()), + get_pretty_tx_attr::(&txs[0], "blockNumber") + ); assert_eq!( Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr::(&txs[0], "from") + get_pretty_tx_attr::(&txs[0], "from") ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::(&txs[0], "gas")); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::(&txs[0], "gas")); assert_eq!( Some("20000000000".to_string()), - get_pretty_tx_attr::(&txs[0], "gasPrice") + get_pretty_tx_attr::(&txs[0], "gasPrice") ); assert_eq!( Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr::(&txs[0], "hash") + get_pretty_tx_attr::(&txs[0], "hash") ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::(&txs[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr::(&txs[0], "nonce")); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr::(&txs[0], "nonce")); assert_eq!( Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr::(&txs[0], "r") + get_pretty_tx_attr::(&txs[0], "r") ); assert_eq!( Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr::(&txs[0], "s") + get_pretty_tx_attr::(&txs[0], "s") ); assert_eq!( Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr::(&txs[0], "to") + get_pretty_tx_attr::(&txs[0], "to") ); assert_eq!( Some("0".to_string()), - get_pretty_tx_attr::(&txs[0], "transactionIndex") + get_pretty_tx_attr::(&txs[0], "transactionIndex") ); - assert_eq!(Some("27".to_string()), get_pretty_tx_attr::(&txs[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr::(&txs[0], "value")); + assert_eq!(Some("27".to_string()), get_pretty_tx_attr::(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr::(&txs[0], "value")); } #[test] @@ -1551,7 +1646,7 @@ yParity 0" "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", - "difficulty": "0x1", + "difficulty": "0x27f07", "totalDifficulty": "0x27f07", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "size": "0x27f07", @@ -1564,77 +1659,83 @@ yParity 0" } ); - let block: ::BlockResponse = serde_json::from_value(json).unwrap(); + let block: ::BlockResponse = + serde_json::from_value(json).unwrap(); - assert_eq!(None, get_pretty_block_attr::(&block, "")); + assert_eq!(None, get_pretty_block_attr::(&block, "")); assert_eq!( Some("7".to_string()), - get_pretty_block_attr::(&block, "baseFeePerGas") + get_pretty_block_attr::(&block, "baseFeePerGas") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "difficulty") ); - assert_eq!(Some("1".to_string()), get_pretty_block_attr::(&block, "difficulty")); assert_eq!( Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()), - get_pretty_block_attr::(&block, "extraData") + get_pretty_block_attr::(&block, "extraData") ); assert_eq!( Some("653145".to_string()), - get_pretty_block_attr::(&block, "gasLimit") + get_pretty_block_attr::(&block, "gasLimit") ); assert_eq!( Some("653145".to_string()), - get_pretty_block_attr::(&block, "gasUsed") + get_pretty_block_attr::(&block, "gasUsed") ); assert_eq!( Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), - get_pretty_block_attr::(&block, "hash") + get_pretty_block_attr::(&block, "hash") ); - assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::(&block, "logsBloom")); + assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::(&block, "logsBloom")); assert_eq!( Some("0x0000000000000000000000000000000000000001".to_string()), - get_pretty_block_attr::(&block, "miner") + get_pretty_block_attr::(&block, "miner") ); assert_eq!( Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()), - get_pretty_block_attr::(&block, "mixHash") + get_pretty_block_attr::(&block, "mixHash") ); assert_eq!( Some("0x0000000000000000".to_string()), - get_pretty_block_attr::(&block, "nonce") + get_pretty_block_attr::(&block, "nonce") + ); + assert_eq!( + Some("436".to_string()), + get_pretty_block_attr::(&block, "number") ); - assert_eq!(Some("436".to_string()), get_pretty_block_attr::(&block, "number")); assert_eq!( Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), - get_pretty_block_attr::(&block, "parentHash") + get_pretty_block_attr::(&block, "parentHash") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr::(&block, "transactionsRoot") + get_pretty_block_attr::(&block, "transactionsRoot") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr::(&block, "receiptsRoot") + get_pretty_block_attr::(&block, "receiptsRoot") ); assert_eq!( Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()), - get_pretty_block_attr::(&block, "sha3Uncles") + get_pretty_block_attr::(&block, "sha3Uncles") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "size") ); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr::(&block, "size")); assert_eq!( Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()), - get_pretty_block_attr::(&block, "stateRoot") + get_pretty_block_attr::(&block, "stateRoot") ); assert_eq!( Some("1424182926".to_string()), - get_pretty_block_attr::(&block, "timestamp") + get_pretty_block_attr::(&block, "timestamp") ); assert_eq!( Some("163591".to_string()), - get_pretty_block_attr::(&block, "totalDifficulty") + get_pretty_block_attr::(&block, "totalDifficulty") ); - - let pretty = pretty_generic_header_response(block.header()); - assert!(pretty.contains("difficulty 1"), "{pretty}"); - assert!(pretty.contains("totalDifficulty 163591"), "{pretty}"); } #[test] @@ -1823,24 +1924,33 @@ to 0x20C0000000000000000000000000000000000000 } #[test] - fn test_ethereum_receipt_uifmt() { + fn test_foundry_tx_receipt_uifmt() { + use alloy_network::AnyTransactionReceipt; + use foundry_primitives::FoundryTxReceipt; + + // Test UIfmt implementation for FoundryTxReceipt let s = r#"{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x1234567890123456789012345678901234567890123456789012345678901234","transactionIndex":"0x0","blockHash":"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd","blockNumber":"0x1","gasUsed":"0x5208","effectiveGasPrice":"0x3b9aca00","from":"0x1234567890123456789012345678901234567890","to":"0x0987654321098765432109876543210987654321","contractAddress":null}"#; - let receipt: TransactionReceipt = serde_json::from_str(s).unwrap(); + let any_receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap(); + let foundry_receipt = FoundryTxReceipt::try_from(any_receipt).unwrap(); - let pretty_output = receipt.pretty(); + let pretty_output = foundry_receipt.pretty(); + // Check that essential fields are present in the output assert!(pretty_output.contains("blockHash")); assert!(pretty_output.contains("blockNumber")); assert!(pretty_output.contains("status")); assert!(pretty_output.contains("gasUsed")); assert!(pretty_output.contains("transactionHash")); assert!(pretty_output.contains("type")); + + // Verify the transaction hash appears in the output assert!( pretty_output .contains("0x1234567890123456789012345678901234567890123456789012345678901234") ); - assert!(pretty_output.contains("1 (success)")); - assert!(pretty_output.contains("0x0987654321098765432109876543210987654321")); + + // Verify status is pretty printed correctly (boolean true for successful transaction) + assert!(pretty_output.contains("true")); } #[test] @@ -1862,35 +1972,38 @@ to 0x20C0000000000000000000000000000000000000 "contractAddress": null }); - let receipt: ::ReceiptResponse = + let receipt: ::ReceiptResponse = serde_json::from_value(receipt_json).unwrap(); // Test basic receipt attributes assert_eq!( Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()), - get_pretty_receipt_attr::(&receipt, "blockHash") + get_pretty_receipt_attr::(&receipt, "blockHash") ); assert_eq!( Some("1".to_string()), - get_pretty_receipt_attr::(&receipt, "blockNumber") + get_pretty_receipt_attr::(&receipt, "blockNumber") ); assert_eq!( Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()), - get_pretty_receipt_attr::(&receipt, "transactionHash") + get_pretty_receipt_attr::(&receipt, "transactionHash") ); assert_eq!( Some("21000".to_string()), - get_pretty_receipt_attr::(&receipt, "gasUsed") + get_pretty_receipt_attr::(&receipt, "gasUsed") ); assert_eq!( Some("true".to_string()), - get_pretty_receipt_attr::(&receipt, "status") + get_pretty_receipt_attr::(&receipt, "status") + ); + assert_eq!( + Some("2".to_string()), + get_pretty_receipt_attr::(&receipt, "type") ); assert_eq!( - Some("EIP-1559".to_string()), - get_pretty_receipt_attr::(&receipt, "type") + Some("[]".to_string()), + get_pretty_receipt_attr::(&receipt, "logs") ); - assert_eq!(Some("[]".to_string()), get_pretty_receipt_attr::(&receipt, "logs")); - assert!(get_pretty_receipt_attr::(&receipt, "logsBloom").is_some()); + assert!(get_pretty_receipt_attr::(&receipt, "logsBloom").is_some()); } } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 895b16b3b4532..e9f203ebf7539 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } diff --git a/crates/common/src/provider/mpp/transport.rs b/crates/common/src/provider/mpp/transport.rs index bba15ebce4f90..aea72174c9756 100644 --- a/crates/common/src/provider/mpp/transport.rs +++ b/crates/common/src/provider/mpp/transport.rs @@ -228,59 +228,11 @@ where // held until the retry response is fully handled. let _pay_guard = self.provider.lock_pay().await; - let www_auth_values: Vec<&str> = resp - .headers() - .get_all(WWW_AUTHENTICATE_HEADER) - .iter() - .filter_map(|v| v.to_str().ok()) - .collect(); - - if www_auth_values.is_empty() { - return Err(TransportErrorKind::custom(std::io::Error::other( - "402 response missing WWW-Authenticate header", - ))); - } - - let challenges: Vec<_> = parse_www_authenticate_all(www_auth_values) - .into_iter() - .filter_map(|r| r.ok()) - .collect(); - - // Try each challenge until we find one with a matching key (chain + currency) - // in keys.toml. This handles servers that offer multiple chains and currencies - // (e.g. mainnet + testnet) — we pick the first one the user has a key for. - let mut last_resolve_err: Option = None; - let resolved_pair = challenges.iter().find_map(|c| { - let (chain_id, currency) = extract_challenge_chain_and_currency(c); - let currency = currency.and_then(|s| s.parse().ok()); - match self.provider.resolve_for(DiscoverOptions { chain_id, currency }) { - Ok(provider) => { - provider.supports(c.method.as_str(), c.intent.as_str()).then_some((provider, c)) - } - Err(e) => { - last_resolve_err = Some(e); - None - } - } - }); - - let (resolved, challenge) = resolved_pair.ok_or_else(|| { - // Surface the real config error (invalid key, bad key_authorization, etc.) - // instead of a generic "no supported challenge" message. - if let Some(err) = last_resolve_err { - return err; - } - let offered: Vec<_> = - challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); - TransportErrorKind::custom(std::io::Error::other(format!( - "no supported MPP challenge; server offered [{}]", - offered.join(", "), - ))) - })?; + let (resolved, challenge) = Self::select_challenge(&resp, &self.provider)?; debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP 402 challenge, paying"); - let credential = resolved.pay(challenge).await.map_err(|e| { + let credential = resolved.pay(&challenge).await.map_err(|e| { TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) })?; @@ -316,33 +268,7 @@ where self.provider.commit_topup_and_track_voucher(); let resolved = self.provider.resolve()?; - let credential = resolved.pay(challenge).await.map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!( - "MPP payment failed: {e}" - ))) - })?; - let auth_header = format_authorization(&credential).map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!( - "failed to format MPP credential: {e}" - ))) - })?; - - let voucher_resp = self - .client - .post(self.url.clone()) - .timeout(MPP_RETRY_TIMEOUT) - .headers(headers.clone()) - .header("content-type", "application/json") - .header(AUTHORIZATION_HEADER, &auth_header) - .body(body.clone()) - .send() - .await - .map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(e) - })?; + let voucher_resp = self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; let result = Self::handle_response(voucher_resp).await; if result.is_ok() { @@ -388,31 +314,8 @@ where debug!("MPP voucher stale, retrying with fresh voucher"); let resolved = self.provider.resolve()?; if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - let credential = resolved.pay(challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "MPP payment failed: {e}" - ))) - })?; - let auth_header = format_authorization(&credential).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "failed to format MPP credential: {e}" - ))) - })?; - - let final_resp = self - .client - .post(self.url.clone()) - .timeout(MPP_RETRY_TIMEOUT) - .headers(headers.clone()) - .header("content-type", "application/json") - .header(AUTHORIZATION_HEADER, auth_header) - .body(body.clone()) - .send() - .await - .map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(e) - })?; + let final_resp = + self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; let result = Self::handle_response(final_resp).await; if result.is_ok() { @@ -425,110 +328,42 @@ where } // Retry with key_authorization when the error explicitly indicates - // the access key is not provisioned on-chain. Unconditionally - // retrying caused "access key already exists" when the 402 was for - // a different reason (e.g. wrong currency, insufficient balance). + // the access key is not provisioned on-chain, or when verification + // failed and the key appears provisioned (first-time provisioning + // where key_auth was stripped but not yet provisioned on-chain). + // + // We fetch a fresh challenge because the server may have consumed + // the original challenge ID on first use. let needs_key_provisioning = problem_type.ends_with("/key-not-provisioned") || detail.contains("access key does not exist") || detail.contains("key is not provisioned"); - if needs_key_provisioning { - self.provider.set_key_provisioned(false); - let resolved = self.provider.resolve()?; - - if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - debug!( - "MPP 402 indicates key not provisioned, retrying with key_authorization" - ); - - let credential = resolved.pay(challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "MPP payment failed: {e}" - ))) - })?; - let auth_header = format_authorization(&credential).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "failed to format MPP credential: {e}" - ))) - })?; - - let final_resp = self - .client - .post(self.url.clone()) - .timeout(MPP_RETRY_TIMEOUT) - .headers(headers) - .header("content-type", "application/json") - .header(AUTHORIZATION_HEADER, auth_header) - .body(body) - .send() - .await - .map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(e) - })?; - - let result = Self::handle_response(final_resp).await; - if result.is_ok() { - self.provider.set_key_provisioned(true); - self.provider.flush_pending(); - } else { - self.provider.rollback_pending(); - } - return result; - } - } - - // Retry with key_authorization when verification failed and key appears - // provisioned — handles first-time provisioning where key_auth was stripped - // but the key was not yet provisioned on-chain. let needs_verification_retry = (problem_type.ends_with("/verification-failed") || detail.contains("verification-failed")) && self.provider.is_key_provisioned(); - if needs_verification_retry { + if needs_key_provisioning || needs_verification_retry { + debug!( + problem_type, + "MPP 402 key not provisioned/verification-failed, retrying with key_authorization" + ); self.provider.set_key_provisioned(false); - let resolved = self.provider.resolve()?; + self.provider.rollback_pending(); - if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - debug!( - "MPP 402 verification-failed with key provisioned, retrying with key_authorization" - ); + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; - let credential = resolved.pay(challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "MPP payment failed: {e}" - ))) - })?; - let auth_header = format_authorization(&credential).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( - "failed to format MPP credential: {e}" - ))) - })?; - - let final_resp = self - .client - .post(self.url.clone()) - .timeout(MPP_RETRY_TIMEOUT) - .headers(headers) - .header("content-type", "application/json") - .header(AUTHORIZATION_HEADER, auth_header) - .body(body) - .send() - .await - .map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(e) - })?; + let final_resp = + self.pay_and_retry(&fresh_challenge, &resolved, &headers, &body).await?; - let result = Self::handle_response(final_resp).await; - if result.is_ok() { - self.provider.set_key_provisioned(true); - self.provider.flush_pending(); - } else { - self.provider.rollback_pending(); - } - return result; + let result = Self::handle_response(final_resp).await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); } + return result; } self.provider.rollback_pending(); @@ -548,6 +383,125 @@ where result } + /// Pay a challenge and send the authenticated retry request. + async fn pay_and_retry( + &self, + challenge: &mpp::protocol::core::PaymentChallenge, + provider: &P::Provider, + headers: &reqwest::header::HeaderMap, + body: &[u8], + ) -> TransportResult { + let credential = provider.pay(challenge).await.map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) + })?; + + let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to format MPP credential: {e}" + ))) + })?; + + self.client + .post(self.url.clone()) + .timeout(MPP_RETRY_TIMEOUT) + .headers(headers.clone()) + .header("content-type", "application/json") + .header(AUTHORIZATION_HEADER, auth_header) + .body(body.to_vec()) + .send() + .await + .map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(e) + }) + } + + /// Fetch a fresh 402 challenge from the server (unauthenticated request). + /// + /// Returns `Ok(Some((provider, challenge)))` if the server returns a 402 + /// with a matching challenge. Returns `Ok(None)` with the response handled + /// if the server returns a non-402 status. Errors on network or parse failures. + async fn fetch_fresh_challenge( + &self, + headers: &reqwest::header::HeaderMap, + body: &[u8], + ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { + let fresh_resp = self + .client + .post(self.url.clone()) + .timeout(MPP_RETRY_TIMEOUT) + .headers(headers.clone()) + .header("content-type", "application/json") + .body(body.to_vec()) + .send() + .await + .map_err(TransportErrorKind::custom)?; + + if fresh_resp.status() != StatusCode::PAYMENT_REQUIRED { + // Non-402 → return whatever the server sent (could be success or error). + let result = Self::handle_response(fresh_resp).await; + return Err(result.err().unwrap_or_else(|| { + TransportErrorKind::custom(std::io::Error::other( + "unexpected success on unauthenticated fresh probe", + )) + })); + } + + Self::select_challenge(&fresh_resp, &self.provider) + } + + /// Parse `WWW-Authenticate` challenges from a 402 response and resolve + /// the first one matching a locally configured key (chain + currency). + fn select_challenge( + resp: &reqwest::Response, + provider: &P, + ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { + let www_auth_values: Vec<&str> = resp + .headers() + .get_all(WWW_AUTHENTICATE_HEADER) + .iter() + .filter_map(|v| v.to_str().ok()) + .collect(); + + if www_auth_values.is_empty() { + return Err(TransportErrorKind::custom(std::io::Error::other( + "402 response missing WWW-Authenticate header", + ))); + } + + let challenges: Vec<_> = parse_www_authenticate_all(www_auth_values) + .into_iter() + .filter_map(|r| r.ok()) + .collect(); + + let mut last_resolve_err: Option = None; + let resolved_pair = challenges.iter().find_map(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + match provider.resolve_for(DiscoverOptions { chain_id, currency }) { + Ok(p) => p.supports(c.method.as_str(), c.intent.as_str()).then_some((p, c.clone())), + Err(e) => { + last_resolve_err = Some(e); + None + } + } + }); + + resolved_pair.ok_or_else(|| { + if let Some(err) = last_resolve_err { + return err; + } + let offered: Vec<_> = + challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); + TransportErrorKind::custom(std::io::Error::other(format!( + "no supported MPP challenge; server offered [{}]", + offered.join(", "), + ))) + }) + } + async fn handle_response(resp: reqwest::Response) -> TransportResult { let status = resp.status(); debug!(%status, "received response from MPP transport"); diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs new file mode 100644 index 0000000000000..893e7806d1dbc --- /dev/null +++ b/crates/common/src/transactions.rs @@ -0,0 +1,281 @@ +//! Wrappers for transactions. + +use alloy_consensus::{Transaction, TxEnvelope, transaction::SignerRecoverable}; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_network::{ + AnyTransactionReceipt, Ethereum, Network, TransactionBuilder7702, TransactionResponse, +}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_provider::{ + Provider, + network::{AnyNetwork, ReceiptResponse, TransactionBuilder}, +}; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use eyre::Result; +use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +use serde::{Deserialize, Serialize}; + +/// Helper type to carry a transaction along with an optional revert reason +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TransactionReceiptWithRevertReason { + /// The underlying transaction receipt + #[serde(flatten)] + pub receipt: N::ReceiptResponse, + + /// The revert reason string if the transaction status is failed + #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] + pub revert_reason: Option, +} + +impl TransactionReceiptWithRevertReason +where + N::TxEnvelope: Clone, + N::ReceiptResponse: UIfmtReceiptExt, +{ + /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert + /// reason was not successfully updated + pub async fn update_revert_reason(&mut self, provider: &dyn Provider) -> Result<()> { + self.revert_reason = self.fetch_revert_reason(provider).await?; + Ok(()) + } + + async fn fetch_revert_reason(&self, provider: &dyn Provider) -> Result> { + // If the transaction succeeded, there is no revert reason to fetch + if self.receipt.status() { + return Ok(None); + } + + let transaction = provider + .get_transaction_by_hash(self.receipt.transaction_hash()) + .await + .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? + .ok_or_else(|| eyre::eyre!("transaction not found"))?; + + if let Some(block_hash) = self.receipt.block_hash() { + let mut call_request: N::TransactionRequest = transaction.as_ref().clone().into(); + call_request.set_from(transaction.from()); + match provider.call(call_request).block(BlockId::Hash(block_hash.into())).await { + Err(e) => return Ok(extract_revert_reason(e.to_string())), + Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), + } + } + eyre::bail!("unable to fetch block_hash") + } +} + +impl From for TransactionReceiptWithRevertReason { + fn from(receipt: AnyTransactionReceipt) -> Self { + Self { receipt, revert_reason: None } + } +} + +impl From> for AnyTransactionReceipt { + fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { + receipt_with_reason.receipt + } +} + +impl UIfmt for TransactionReceiptWithRevertReason +where + N::ReceiptResponse: UIfmt, +{ + fn pretty(&self) -> String { + if let Some(revert_reason) = &self.revert_reason { + format!( + "{} +revertReason {}", + self.receipt.pretty(), + revert_reason + ) + } else { + self.receipt.pretty() + } + } +} + +impl UIfmt for TransactionMaybeSigned { + fn pretty(&self) -> String { + match self { + Self::Signed { tx, .. } => tx.pretty(), + Self::Unsigned(tx) => format!( + " +accessList {} +chainId {} +gasLimit {} +gasPrice {} +input {} +maxFeePerBlobGas {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +to {} +type {} +value {}", + tx.access_list + .as_ref() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + tx.chain_id.pretty(), + tx.gas_limit().unwrap_or_default(), + tx.gas_price.pretty(), + tx.input.input.pretty(), + tx.max_fee_per_blob_gas.pretty(), + tx.max_fee_per_gas.pretty(), + tx.max_priority_fee_per_gas.pretty(), + tx.nonce.pretty(), + tx.to.as_ref().map(|a| a.to()).unwrap_or_default().pretty(), + tx.transaction_type.unwrap_or_default(), + tx.value.pretty(), + ), + } + } +} + +fn extract_revert_reason>(error_string: S) -> Option { + let message_substr = "execution reverted: "; + error_string + .as_ref() + .find(message_substr) + .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) +} + +/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt with revert reason +pub fn get_pretty_receipt_w_reason_attr( + receipt: &TransactionReceiptWithRevertReason, + attr: &str, +) -> Option +where + N: Network, + N::ReceiptResponse: UIfmtReceiptExt, +{ + // Handle revert reason first, then delegate to the receipt formatting function + if matches!(attr, "revertReason" | "revert_reason") { + return Some(receipt.revert_reason.pretty()); + } + get_pretty_receipt_attr::(&receipt.receipt, attr) +} + +/// Used for broadcasting transactions +/// A transaction can either be a [`TransactionRequest`] waiting to be signed +/// or a [`TxEnvelope`], already signed +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TransactionMaybeSigned { + Signed { + #[serde(flatten)] + tx: N::TxEnvelope, + from: Address, + }, + Unsigned(N::TransactionRequest), +} + +impl TransactionMaybeSigned { + /// Creates a new (unsigned) transaction for broadcast + pub fn new(tx: N::TransactionRequest) -> Self { + Self::Unsigned(tx) + } + + /// Creates a new signed transaction for broadcast. + pub fn new_signed( + tx: N::TxEnvelope, + ) -> core::result::Result + where + N::TxEnvelope: SignerRecoverable, + { + let from = tx.recover_signer()?; + Ok(Self::Signed { tx, from }) + } + + pub fn is_unsigned(&self) -> bool { + matches!(self, Self::Unsigned(_)) + } + + pub fn as_unsigned_mut(&mut self) -> Option<&mut N::TransactionRequest> { + match self { + Self::Unsigned(tx) => Some(tx), + _ => None, + } + } + + pub fn from(&self) -> Option
{ + match self { + Self::Signed { from, .. } => Some(*from), + Self::Unsigned(tx) => tx.from(), + } + } + + pub fn input(&self) -> Option<&Bytes> { + match self { + Self::Signed { tx, .. } => Some(tx.input()), + Self::Unsigned(tx) => tx.input(), + } + } + + pub fn to(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.kind()), + Self::Unsigned(tx) => tx.kind(), + } + } + + pub fn value(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.value()), + Self::Unsigned(tx) => tx.value(), + } + } + + pub fn gas(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.gas_limit() as u128), + Self::Unsigned(tx) => tx.gas_limit().map(|g| g as u128), + } + } + + pub fn nonce(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.nonce()), + Self::Unsigned(tx) => tx.nonce(), + } + } + + pub fn authorization_list(&self) -> Option> + where + N::TransactionRequest: TransactionBuilder7702, + { + match self { + Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()), + Self::Unsigned(tx) => tx.authorization_list().map(|auths| auths.to_vec()), + } + .filter(|auths| !auths.is_empty()) + } +} + +impl From for TransactionMaybeSigned { + fn from(tx: TransactionRequest) -> Self { + Self::new(tx) + } +} + +impl TryFrom for TransactionMaybeSigned { + type Error = alloy_consensus::crypto::RecoveryError; + + fn try_from(tx: TxEnvelope) -> core::result::Result { + Self::new_signed(tx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_revert_reason() { + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; + + assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); + assert_eq!(extract_revert_reason(error_string_2), None); + } +} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 39c281e81be25..4c6978e0d9750 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -49,6 +49,9 @@ walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" @@ -60,3 +63,4 @@ tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..00a381eaf66ff --- /dev/null +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..89d40cf5606ce --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..5a362e963d956 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,64 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use foundry_config::Config; +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if let Ok(old_contents) = fs::read_to_string(file) + && normalize_newlines(&old_contents) == normalize_newlines(contents) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index af25127beb585..64ee7e05fc75f 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -453,6 +453,18 @@ impl DerefMut for ResolvedRpcEndpoints { } } +/// Returns the URL for a built-in RPC alias, if one exists. +/// +/// Built-in aliases act as fallbacks: they are only used when the alias has **not** been +/// defined by the user in `[rpc_endpoints]` or resolved via MESC. +pub fn builtin_rpc_url(alias: &str) -> Option<&'static str> { + match alias { + "tempo" => Some("https://rpc.mpp.tempo.xyz"), + "moderato" => Some("https://rpc.mpp.moderato.tempo.xyz"), + _ => None, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 0cc28c93057dd..c0f8249f37dfd 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -45,7 +45,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use std::{ borrow::Cow, collections::BTreeMap, - fs, + fs, io, path::{Path, PathBuf}, str::FromStr, }; @@ -59,6 +59,7 @@ pub use utils::*; mod endpoints; pub use endpoints::{ ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, RpcEndpoints, + builtin_rpc_url, }; mod etherscan; @@ -856,6 +857,17 @@ impl Config { config.normalize_optimizer_settings(); config.normalize_hardfork_settings().map_err(ExtractConfigError::new)?; + // Validate optimizer_runs does not exceed u32::MAX (Solidity compiler limit) + if let Some(runs) = config.optimizer_runs + && runs > u32::MAX as usize + { + return Err(ExtractConfigError::new(Error::from(format!( + "`optimizer_runs` value {} exceeds maximum allowed value of {}", + runs, + u32::MAX + )))); + } + Ok(config) } @@ -1257,7 +1269,9 @@ impl Config { let project = builder.build(self.compiler()?)?; if self.force { - self.cleanup(&project)?; + // Warnings are intentionally dropped here because `sh_warn!` is a circular + // dependency. Callers that need warnings should call `cleanup()` directly. + let _ = self.cleanup(&project); } Ok(project) @@ -1286,21 +1300,40 @@ impl Config { } /// Cleans the project. + /// + /// Returns a list of warning messages for any non-fatal cleanup failures. Cleanup is + /// best-effort: all steps are attempted even if some fail. pub fn cleanup>( &self, project: &Project, - ) -> Result<(), SolcError> { - project.cleanup()?; + ) -> Result, SolcError> { + let mut warnings = Vec::new(); + + if let Err(err) = project.cleanup() { + warnings.push(format!("failed to clean project artifacts: {err}")); + } // Remove last test run failures file. - let _ = fs::remove_file(&self.test_failures_file); + if let Err(err) = fs::remove_file(&self.test_failures_file) + && err.kind() != io::ErrorKind::NotFound + { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + self.test_failures_file.display() + )); + } // Remove fuzz and invariant cache directories. - let remove_test_dir = |test_dir: &Option| { + let mut remove_test_dir = |test_dir: &Option| { if let Some(test_dir) = test_dir { let path = project.root().join(test_dir); - if path.exists() { - let _ = fs::remove_dir_all(&path); + if let Err(err) = fs::remove_dir_all(&path) + && err.kind() != io::ErrorKind::NotFound + { + warnings.push(format!( + "failed to remove test cache directory {}: {err}", + path.display() + )); } } }; @@ -1309,7 +1342,7 @@ impl Config { remove_test_dir(&self.invariant.corpus.corpus_dir); remove_test_dir(&self.invariant.failure_persist_dir); - Ok(()) + Ok(warnings) } /// Ensures that the configured version is installed if explicitly set @@ -1529,6 +1562,10 @@ impl Config { return Some(Ok(Cow::Owned(mesc_url))); } + if let Some(builtin) = crate::endpoints::builtin_rpc_url(maybe_alias) { + return Some(Ok(Cow::Borrowed(builtin))); + } + None } @@ -2138,63 +2175,108 @@ impl Config { } /// Clears the foundry cache. - pub fn clean_foundry_cache() -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_cache() -> eyre::Result> { if let Some(cache_dir) = Self::foundry_cache_dir() { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry cache for `chain`. - pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_chain_cache_dir(chain) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache for chain {chain} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_chain_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry cache for `chain` and `block`. - pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_block_cache_dir(chain, block) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache for chain {chain} block {block} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_block_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry etherscan cache. - pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_etherscan_cache() -> eyre::Result> { if let Some(cache_dir) = Self::foundry_etherscan_cache_dir() { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry etherscan cache at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_etherscan_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry etherscan cache for `chain`. - pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_etherscan_chain_cache_dir(chain) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry etherscan cache for chain {chain} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_etherscan_cache_dir for chain: {}", chain); } - Ok(()) + Ok(vec![]) } /// List the data in the foundry cache. diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 3e915b1b7f479..3d4c57fe5dc3e 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -151,6 +151,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 8081de7dac660..085664f85fa81 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,4 +1,4 @@ -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike}; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; use solang_parser::pt::{ EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, @@ -89,17 +89,6 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes dev content to the buffer, handling markdown lists properly. - /// If the content contains markdown lists, it formats them correctly. - /// Otherwise, it writes the content in italics. - pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { - for line in text.lines() { - writeln!(self.buf, "{line}")?; - } - - Ok(()) - } - /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -202,7 +191,8 @@ impl BufWriter { params: &EnumDefinition, comments: &Comments, ) -> fmt::Result { - let comments = comments.include_tags(&[CommentTag::Param]); + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); // There is nothing to write. if comments.is_empty() { @@ -215,7 +205,7 @@ impl BufWriter { self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; - for value in ¶ms.values { + for value in params.values.iter() { let param_name = value.as_ref().map(|v| v.name.clone()); let comment = param_name.as_ref().and_then(|name| { @@ -223,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 3db3c082fff14..03d569c17f500 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -59,7 +59,6 @@ op-alloy-network.workspace = true op-revm.workspace = true tempo-revm.workspace = true tempo-alloy.workspace = true -tempo-chainspec.workspace = true tempo-contracts.workspace = true tempo-evm.workspace = true tempo-precompiles.workspace = true diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 87a92e9d97460..8c80b21121c37 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -2,32 +2,29 @@ use super::BackendError; use crate::{ - FoundryInspectorExt, + Env, InspectorExt, backend::{ Backend, DatabaseExt, JournaledState, LocalForkId, RevertStateSnapshotAction, diagnostic::RevertDiagnostic, }, - evm::{ - EvmEnvFor, FoundryContextFor, FoundryEvmFactory, FoundryEvmNetwork, HaltReasonFor, SpecFor, - TxEnvFor, - }, fork::{CreateFork, ForkId}, }; -use alloy_evm::Evm; +use alloy_evm::{Evm, EvmEnv}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, TxKind, U256}; +use alloy_primitives::{Address, B256, U256}; +use alloy_rpc_types::TransactionRequest; use eyre::WrapErr; use foundry_fork_db::DatabaseError; use revm::{ Database, DatabaseCommit, bytecode::Bytecode, - context::{ContextTr, Transaction}, + context::TxEnv, context_interface::result::ResultAndState, database::DatabaseRef, - primitives::AddressMap, + primitives::{HashMap as Map, hardfork::SpecId}, state::{Account, AccountInfo, EvmState}, }; -use std::{borrow::Cow, collections::BTreeMap, fmt::Debug}; +use std::{borrow::Cow, collections::BTreeMap}; /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. /// @@ -45,35 +42,21 @@ use std::{borrow::Cow, collections::BTreeMap, fmt::Debug}; /// don't make use of them. Alternatively each test case would require its own `Backend` clone, /// which would add significant overhead for large fuzz sets even if the Database is not big after /// setup. -pub struct CowBackend<'a, FEN: FoundryEvmNetwork> { +#[derive(Clone, Debug)] +pub struct CowBackend<'a> { /// The underlying `Backend`. /// /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. - pub backend: Cow<'a, Backend>, - /// Pending initialization params for the backend on first mutable access. + pub backend: Cow<'a, Backend>, + /// The [SpecId] to initialize the backend with on first mutable access. /// `None` means the backend has already been initialized for the current call. - pending_init: Option<(SpecFor, Address, TxKind)>, -} - -impl Clone for CowBackend<'_, FEN> { - fn clone(&self) -> Self { - Self { backend: self.backend.clone(), pending_init: self.pending_init } - } + spec_id: Option, } -impl Debug for CowBackend<'_, FEN> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CowBackend") - .field("backend", &self.backend) - .field("pending_init", &self.pending_init) - .finish() - } -} - -impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> { +impl<'a> CowBackend<'a> { /// Creates a new `CowBackend` with the given `Backend`. - pub const fn new_borrowed(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), pending_init: None } + pub fn new_borrowed(backend: &'a Backend) -> Self { + Self { backend: Cow::Borrowed(backend), spec_id: Some(SpecId::default()) } } /// Executes the configured transaction of the `env` without committing state changes @@ -81,26 +64,25 @@ impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect FoundryInspectorExt>>( + pub fn inspect( &mut self, - evm_env: &mut EvmEnvFor, - tx_env: &mut TxEnvFor, + env: &mut Env, inspector: I, - ) -> eyre::Result>> { + ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state - self.pending_init = Some((evm_env.cfg_env.spec, tx_env.caller(), tx_env.kind())); + self.spec_id = Some(env.evm_env.cfg_env.spec); - let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector( + let mut evm = crate::evm::new_evm_with_inspector( self, - evm_env.clone(), + env.evm_env.clone(), + env.tx.clone(), inspector, ); - let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; + let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; - *tx_env = evm.tx().clone(); - *evm_env = evm.finish().1; + *env = Env::from(evm.cfg.clone(), evm.block.clone(), evm.tx.clone()); Ok(res) } @@ -115,42 +97,40 @@ impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> { /// Returns a mutable instance of the Backend. /// /// If this is the first time this is called, the backed is cloned and initialized. - fn backend_mut(&mut self) -> &mut Backend { - if let Some((spec_id, caller, tx_kind)) = self.pending_init.take() { + fn backend_mut(&mut self, evm_env: &EvmEnv, tx_env: &TxEnv) -> &mut Backend { + if let Some(spec_id) = self.spec_id.take() { let backend = self.backend.to_mut(); - backend.initialize(spec_id, caller, tx_kind); + let mut env = Env { evm_env: evm_env.clone(), tx: tx_env.clone() }; + env.evm_env.cfg_env.spec = spec_id; + backend.initialize(&env); return backend; } self.backend.to_mut() } /// Returns a mutable instance of the Backend if it is initialized. - fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { - if self.pending_init.is_none() { + fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { + if self.spec_id.is_none() { return Some(self.backend.to_mut()); } None } } -impl DatabaseExt for CowBackend<'_, FEN> { - fn snapshot_state( - &mut self, - journaled_state: &JournaledState, - evm_env: &EvmEnvFor, - ) -> U256 { - self.backend_mut().snapshot_state(journaled_state, evm_env) +impl DatabaseExt for CowBackend<'_> { + fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256 { + self.backend_mut(evm_env, &TxEnv::default()).snapshot_state(journaled_state, evm_env) } fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, - evm_env: &mut EvmEnvFor, - caller: Address, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, action: RevertStateSnapshotAction, ) -> Option { - self.backend_mut().revert_state(id, journaled_state, evm_env, caller, action) + self.backend_mut(evm_env, tx_env).revert_state(id, journaled_state, evm_env, tx_env, action) } fn delete_state_snapshot(&mut self, id: U256) -> bool { @@ -182,56 +162,81 @@ impl DatabaseExt for CowBackend<'_, FEN fn select_fork( &mut self, id: LocalForkId, - evm_env: &mut EvmEnvFor, - tx_env: &mut TxEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut().select_fork(id, evm_env, tx_env, journaled_state) + self.backend_mut(evm_env, tx_env).select_fork(id, evm_env, tx_env, journaled_state) } fn roll_fork( &mut self, id: Option, block_number: u64, - evm_env: &mut EvmEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut().roll_fork(id, block_number, evm_env, journaled_state) + self.backend_mut(evm_env, tx_env).roll_fork( + id, + block_number, + evm_env, + tx_env, + journaled_state, + ) } fn roll_fork_to_transaction( &mut self, id: Option, transaction: B256, - evm_env: &mut EvmEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut().roll_fork_to_transaction(id, transaction, evm_env, journaled_state) + self.backend_mut(evm_env, tx_env).roll_fork_to_transaction( + id, + transaction, + evm_env, + tx_env, + journaled_state, + ) } fn transact( &mut self, id: Option, transaction: B256, - evm_env: EvmEnvFor, + evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt< - ::FoundryContext<'db>, - >, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - self.backend_mut().transact(id, transaction, evm_env, journaled_state, inspector) + self.backend_mut(&evm_env, &tx_env).transact( + id, + transaction, + evm_env, + tx_env, + journaled_state, + inspector, + ) } fn transact_from_tx( &mut self, - tx_env: TxEnvFor, - evm_env: EvmEnvFor, + transaction: &TransactionRequest, + evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt< - ::FoundryContext<'db>, - >, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - self.backend_mut().transact_from_tx(tx_env, evm_env, journaled_state, inspector) + self.backend_mut(&evm_env, &tx_env).transact_from_tx( + transaction, + evm_env, + tx_env, + journaled_state, + inspector, + ) } fn active_fork_id(&self) -> Option { @@ -300,7 +305,7 @@ impl DatabaseExt for CowBackend<'_, FEN } } -impl DatabaseRef for CowBackend<'_, FEN> { +impl DatabaseRef for CowBackend<'_> { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -320,7 +325,7 @@ impl DatabaseRef for CowBackend<'_, FEN> { } } -impl Database for CowBackend<'_, FEN> { +impl Database for CowBackend<'_> { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -340,8 +345,8 @@ impl Database for CowBackend<'_, FEN> { } } -impl DatabaseCommit for CowBackend<'_, FEN> { - fn commit(&mut self, changes: AddressMap) { +impl DatabaseCommit for CowBackend<'_> { + fn commit(&mut self, changes: Map) { self.backend.to_mut().commit(changes) } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 5c5fe29c3154c..61f03d8608412 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1,35 +1,36 @@ //! Foundry's main executor backend abstraction and implementation. use crate::{ - FoundryBlock, FoundryInspectorExt, FoundryTransaction, FromAnyRpcTransaction, + Env, InspectorExt, constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, - evm::{ - BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory, - FoundryEvmNetwork, HaltReasonFor, PrecompilesFor, SpecFor, TxEnvFor, - }, + evm::new_evm_with_inspector, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, utils::get_blob_base_fee_update_fraction, }; use alloy_consensus::{BlockHeader, Typed2718}; -use alloy_evm::{Evm, EvmEnv, EvmFactory}; +use alloy_evm::{Evm, EvmEnv, FromRecoveredTx, rpc::TryIntoTxEnv}; use alloy_genesis::GenesisAccount; use alloy_network::{ - AnyNetwork, AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, TransactionResponse, + AnyNetwork, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope, TransactionResponse, }; use alloy_primitives::{Address, B256, TxKind, U256, keccak256, uint}; -use alloy_rpc_types::BlockNumberOrTag; +use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; use eyre::Context; use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender}; -pub use foundry_fork_db::{BlockchainDb, ForkBlockEnv, SharedBackend, cache::BlockchainDbMeta}; -use itertools::Itertools; +pub use foundry_fork_db::{BlockchainDb, SharedBackend, cache::BlockchainDbMeta}; use revm::{ - Database, DatabaseCommit, JournalEntry, + Database, DatabaseCommit, Journal, JournalEntry, bytecode::Bytecode, - context::{Block, BlockEnv, CfgEnv, ContextTr, JournalInner, Transaction}, - context_interface::{journaled_state::account::JournaledAccountTr, result::ResultAndState}, - database::{CacheDB, DatabaseRef, EmptyDB}, - primitives::{AddressMap, HashMap as Map, KECCAK_EMPTY, Log}, + context::{JournalInner, TxEnv}, + context_interface::{ + block::BlobExcessGasAndPrice, journaled_state::account::JournaledAccountTr, + result::ResultAndState, + }, + database::{CacheDB, DatabaseRef}, + inspector::{JournalExt, NoOpInspector}, + precompile::{PrecompileSpecId, Precompiles}, + primitives::{HashMap as Map, KECCAK_EMPTY, Log, hardfork::SpecId}, state::{Account, AccountInfo, EvmState, EvmStorageSlot}, }; use std::{ @@ -54,7 +55,7 @@ mod snapshot; pub use snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapshot}; // A `revm::Database` that is used in forking mode -type ForkDB = CacheDB>; +type ForkDB = CacheDB; /// Represents a numeric `ForkId` valid only for the existence of the `Backend`. /// @@ -81,19 +82,13 @@ pub type JournaledState = JournalInner; /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] -pub trait DatabaseExt: - Database + DatabaseCommit + Debug -{ +pub trait DatabaseExt: Database + DatabaseCommit + Debug { /// Creates a new state snapshot at the current point of execution. /// /// A state snapshot is associated with a new unique id that's created for the snapshot. /// State snapshots can be reverted: [DatabaseExt::revert_state], however, depending on the /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. - fn snapshot_state( - &mut self, - journaled_state: &JournaledState, - evm_env: &EvmEnv, - ) -> U256; + fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256; /// Reverts the snapshot if it exists /// @@ -103,16 +98,16 @@ pub trait DatabaseExt: /// **N.B.** While this reverts the state of the evm to the snapshot, it keeps new logs made /// since the snapshots was created. This way we can show logs that were emitted between /// snapshot and its revert. - /// This will also revert any changes in the `EvmEnv` and `TxEnv` and replace them with the - /// captured values from `Self::snapshot_state`. + /// This will also revert any changes in the `Env` and replace it with the captured `Env` of + /// `Self::snapshot_state`. /// /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, - evm_env: &mut EvmEnv, - caller: Address, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, action: RevertStateSnapshotAction, ) -> Option; @@ -131,8 +126,8 @@ pub trait DatabaseExt: fn create_select_fork( &mut self, fork: CreateFork, - evm_env: &mut EvmEnv, - tx_env: &mut F::Tx, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result { let id = self.create_fork(fork)?; @@ -146,8 +141,8 @@ pub trait DatabaseExt: fn create_select_fork_at_transaction( &mut self, fork: CreateFork, - evm_env: &mut EvmEnv, - tx_env: &mut F::Tx, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, transaction: B256, ) -> eyre::Result { @@ -168,7 +163,7 @@ pub trait DatabaseExt: /// Selects the fork's state /// - /// This will also modify the current `EvmEnv` and `TxEnv`. + /// This will also modify the current `Env`. /// /// **Note**: this does not change the local state, but swaps the remote state /// @@ -178,8 +173,8 @@ pub trait DatabaseExt: fn select_fork( &mut self, id: LocalForkId, - evm_env: &mut EvmEnv, - tx_env: &mut F::Tx, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -194,7 +189,8 @@ pub trait DatabaseExt: &mut self, id: Option, block_number: u64, - evm_env: &mut EvmEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -210,7 +206,8 @@ pub trait DatabaseExt: &mut self, id: Option, transaction: B256, - evm_env: &mut EvmEnv, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -219,18 +216,20 @@ pub trait DatabaseExt: &mut self, id: Option, transaction: B256, - evm_env: EvmEnv, + evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()>; /// Executes a given TransactionRequest, commits the new state to the DB fn transact_from_tx( &mut self, - tx_env: F::Tx, - evm_env: EvmEnv, + transaction: &TransactionRequest, + evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on @@ -387,6 +386,40 @@ pub trait DatabaseExt: fn set_blockhash(&mut self, block_number: U256, block_hash: B256); } +struct _ObjectSafe(dyn DatabaseExt); + +/// Extension trait for [`Journal`] providing borrow splitting and state replacement. +/// +/// Generic code accesses the journal via `ctx.journal_mut()` which returns `&mut impl JournalTr`. +/// This trait adds the ability to split the journal into its database and inner state components, +/// enabling direct [`DatabaseExt`] method calls with zero-copy borrow splitting. +pub trait FoundryJournalExt: JournalExt { + /// Returns mutable references to the database and journal inner state. + /// + /// Enables calling [`DatabaseExt`] methods directly, e.g.: + /// ```ignore + /// let (journal, env) = ctx.journal_and_env_mut(); // FoundryContextExt + /// let (db, inner) = journal.as_db_and_inner(); // FoundryJournalExt + /// db.select_fork(id, &env, inner)?; // DatabaseExt + /// ``` + fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState); + + /// Replaces the journal inner state. + /// + /// Used by sub-EVM execution to write back modified state after running a closure. + fn set_inner(&mut self, inner: JournaledState); +} + +impl FoundryJournalExt for Journal { + fn as_db_and_inner(&mut self) -> (&mut dyn DatabaseExt, &mut JournaledState) { + (&mut self.database, &mut self.inner) + } + + fn set_inner(&mut self, inner: JournaledState) { + self.inner = inner; + } +} + /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -419,7 +452,7 @@ pub trait DatabaseExt: /// Multiple "forks" can be created `Backend::create_fork()`, however only 1 can be used by the /// `db`. However, their state can be hot-swapped by swapping the read half of `db` from one fork to /// another. -/// When swapping forks (`Backend::select_fork()`) we also update the current `EvmEnv` of the `EVM` +/// When swapping forks (`Backend::select_fork()`) we also update the current `Env` of the `EVM` /// accordingly, so that all `block.*` config values match /// /// When another for is selected [`DatabaseExt::select_fork()`] the entire storage, including @@ -439,10 +472,11 @@ pub trait DatabaseExt: /// **Note:** State snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. +#[derive(Clone, Debug)] #[must_use] -pub struct Backend { +pub struct Backend { /// The access point for managing forks - forks: MultiFork, BlockEnvFor>, + forks: MultiFork, // The default in memory db mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with @@ -467,40 +501,16 @@ pub struct Backend { /// If this is set, then the Backend is currently in forking mode active_fork_ids: Option<(LocalForkId, ForkLookupIndex)>, /// holds additional Backend data - inner: BackendInner, + inner: BackendInner, } -impl Clone for Backend { - fn clone(&self) -> Self { - Self { - forks: self.forks.clone(), - mem_db: self.mem_db.clone(), - fork_init_journaled_state: self.fork_init_journaled_state.clone(), - active_fork_ids: self.active_fork_ids, - inner: self.inner.clone(), - } - } -} - -impl Debug for Backend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Backend") - .field("forks", &self.forks) - .field("mem_db", &self.mem_db) - .field("fork_init_journaled_state", &self.fork_init_journaled_state) - .field("active_fork_ids", &self.active_fork_ids) - .field("inner", &self.inner) - .finish() - } -} - -impl Backend { +impl Backend { /// Creates a new Backend with a spawned multi fork thread. /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. pub fn spawn(fork: Option) -> eyre::Result { - Self::new(MultiFork::, BlockEnvFor>::spawn(), fork) + Self::new(MultiFork::spawn(), fork) } /// Creates a new instance of `Backend` @@ -509,10 +519,7 @@ impl Backend { /// database. /// /// Prefer using [`spawn`](Self::spawn) instead. - pub fn new( - forks: MultiFork, BlockEnvFor>, - fork: Option, - ) -> eyre::Result { + pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` let inner = BackendInner { @@ -549,7 +556,7 @@ impl Backend { /// as active pub(crate) fn new_with_fork( id: &ForkId, - fork: Fork>, + fork: Fork, journaled_state: JournaledState, ) -> eyre::Result { let mut backend = Self::spawn(None)?; @@ -602,23 +609,16 @@ impl Backend { storage: Map, ) -> Result<(), DatabaseError> { if let Some(db) = self.active_fork_db_mut() { - db.replace_account_storage(address, storage.into_iter().collect()) + db.replace_account_storage(address, storage) } else { - self.mem_db.replace_account_storage(address, storage.into_iter().collect()) + self.mem_db.replace_account_storage(address, storage) } } /// Returns all snapshots created in this backend - #[allow(clippy::type_complexity)] - pub const fn state_snapshots( + pub fn state_snapshots( &self, - ) -> &StateSnapshots< - BackendStateSnapshot< - BackendDatabaseSnapshot>, - SpecFor, - BlockEnvFor, - >, - > { + ) -> &StateSnapshots> { &self.inner.state_snapshots } @@ -630,8 +630,8 @@ impl Backend { /// This will also grant cheatcode access to the test account pub fn set_test_contract(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting test account"); - self.inner.persistent_accounts.insert(acc); - self.inner.cheatcode_access_accounts.insert(acc); + self.add_persistent_account(acc); + self.allow_cheatcode_access(acc); self } @@ -639,18 +639,19 @@ impl Backend { pub fn set_caller(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting caller account"); self.inner.caller = Some(acc); - self.inner.cheatcode_access_accounts.insert(acc); + self.allow_cheatcode_access(acc); self } /// Sets the current spec id - pub fn set_spec_id(&mut self, spec_id: impl Into>) -> &mut Self { - self.inner.spec_id = spec_id.into(); + pub fn set_spec_id(&mut self, spec_id: SpecId) -> &mut Self { + trace!(?spec_id, "setting spec ID"); + self.inner.spec_id = spec_id; self } /// Returns the set caller address - pub const fn caller_address(&self) -> Option
{ + pub fn caller_address(&self) -> Option
{ self.inner.caller } @@ -659,12 +660,12 @@ impl Backend { /// If an error occurs in a restored state snapshot, the test is considered failed. /// /// This returns whether there was a reverted state snapshot that recorded an error. - pub const fn has_state_snapshot_failure(&self) -> bool { + pub fn has_state_snapshot_failure(&self) -> bool { self.inner.has_state_snapshot_failure } /// Sets the state snapshot failure flag. - pub const fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { + pub fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { self.inner.has_state_snapshot_failure = has_state_snapshot_failure } @@ -672,7 +673,7 @@ impl Backend { pub(crate) fn update_fork_db( &self, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork>, + target_fork: &mut Fork, ) { self.update_fork_db_contracts( self.inner.persistent_accounts.iter().copied(), @@ -686,7 +687,7 @@ impl Backend { &self, accounts: impl IntoIterator, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork>, + target_fork: &mut Fork, ) { if let Some(db) = self.active_fork_db() { merge_account_data(accounts, db, active_journaled_state, target_fork) @@ -696,7 +697,7 @@ impl Backend { } /// Returns the memory db used if not in forking mode - pub const fn mem_db(&self) -> &FoundryEvmInMemoryDB { + pub fn mem_db(&self) -> &FoundryEvmInMemoryDB { &self.mem_db } @@ -711,22 +712,22 @@ impl Backend { } /// Returns the currently active `Fork`, if any - pub fn active_fork(&self) -> Option<&Fork>> { + pub fn active_fork(&self) -> Option<&Fork> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork(idx)) } /// Returns the currently active `Fork`, if any - pub fn active_fork_mut(&mut self) -> Option<&mut Fork>> { + pub fn active_fork_mut(&mut self) -> Option<&mut Fork> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork_mut(idx)) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db(&self) -> Option<&ForkDB>> { + pub fn active_fork_db(&self) -> Option<&ForkDB> { self.active_fork().map(|f| &f.db) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB>> { + pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB> { self.active_fork_mut().map(|f| &mut f.db) } @@ -747,9 +748,7 @@ impl Backend { } /// Creates a snapshot of the currently active database - pub(crate) fn create_db_snapshot( - &self, - ) -> BackendDatabaseSnapshot> { + pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { if let Some((id, idx)) = self.active_fork_ids { let fork = self.inner.get_fork(idx).clone(); let fork_id = self.inner.ensure_fork_id(id).cloned().expect("Exists; qed"); @@ -785,21 +784,18 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize( - &mut self, - spec_id: impl Into>, - caller: Address, - tx_kind: TxKind, - ) { - self.set_caller(caller); - self.set_spec_id(spec_id); + pub(crate) fn initialize(&mut self, env: &Env) { + self.set_caller(env.tx.caller); + self.set_spec_id(env.evm_env.cfg_env.spec); - let test_contract = match tx_kind { + let test_contract = match env.tx.kind { TxKind::Call(to) => to, TxKind::Create => { - let nonce = - self.basic_ref(caller).map(|b| b.unwrap_or_default().nonce).unwrap_or_default(); - caller.create(nonce) + let nonce = self + .basic_ref(env.tx.caller) + .map(|b| b.unwrap_or_default().nonce) + .unwrap_or_default(); + env.tx.caller.create(nonce) } }; self.set_test_contract(test_contract); @@ -810,29 +806,29 @@ impl Backend { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect FoundryInspectorExt>>( + pub fn inspect( &mut self, - evm_env: &mut EvmEnvFor, - tx_env: &mut TxEnvFor, + env: &mut Env, inspector: I, - ) -> eyre::Result>> { - self.initialize(evm_env.cfg_env.spec, tx_env.caller(), tx_env.kind()); - let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector( + ) -> eyre::Result { + self.initialize(env); + let mut evm = crate::evm::new_evm_with_inspector( self, - evm_env.to_owned(), + env.evm_env.to_owned(), + env.tx.to_owned(), inspector, ); - let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; - *tx_env = evm.tx().clone(); - *evm_env = evm.finish().1; + let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; + + *env = Env::from(evm.cfg.clone(), evm.block.clone(), evm.tx.clone()); Ok(res) } /// Returns true if the address is a precompile pub fn is_existing_precompile(&self, addr: &Address) -> bool { - self.inner.precompiles().addresses().contains(addr) + self.inner.precompiles().contains(addr) } /// Sets the initial journaled state to use when initializing forks @@ -856,10 +852,7 @@ impl Backend { .fork_init_journaled_state .state .iter() - .filter(|(addr, _)| { - !self.is_existing_precompile(addr) - && !self.inner.persistent_accounts.contains(*addr) - }) + .filter(|(addr, _)| !self.is_existing_precompile(addr) && !self.is_persistent(addr)) .map(|(addr, _)| addr) .copied() .collect::>(); @@ -897,20 +890,20 @@ impl Backend { transaction: B256, ) -> eyre::Result<(u64, AnyRpcBlock)> { let fork = self.inner.get_fork_by_id(id)?; - let tx = fork.backend().get_transaction(transaction)?; + let tx = fork.db.db.get_transaction(transaction)?; // get the block number we need to fork - if let Some(tx_block) = tx.block_number() { - let block = fork.backend().get_full_block(tx_block)?; + if let Some(tx_block) = tx.block_number { + let block = fork.db.db.get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; Ok((fork_block, block)) } else { - let block = fork.backend().get_full_block(BlockNumberOrTag::Latest)?; + let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; - let number = block.header().number(); + let number = block.header.number(); Ok((number, block)) } @@ -922,69 +915,52 @@ impl Backend { pub fn replay_until( &mut self, id: LocalForkId, - evm_env: EvmEnvFor, + mut env: Env, tx_hash: B256, journaled_state: &mut JournaledState, - ) -> eyre::Result> { + ) -> eyre::Result>> { trace!(?id, ?tx_hash, "replay until transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); + let fork_id = self.ensure_fork_id(id)?.clone(); let fork = self.inner.get_fork_by_id_mut(id)?; let full_block = - fork.backend().get_full_block(evm_env.block_env.number().saturating_to::())?; + fork.db.db.get_full_block(env.evm_env.block_env.number.saturating_to::())?; - // Collect non-system transactions up to and including the target. - let txs = full_block - .transactions() - .txns() - .filter(|tx| !is_known_system_sender(tx.from()) && tx.ty() != SYSTEM_TRANSACTION_TYPE); - - let mut txs_to_replay = Vec::new(); - let mut target_tx = None; - for tx in txs { - if tx.tx_hash() == tx_hash { - target_tx = Some(tx.clone()); - break; + for tx in full_block.inner.transactions.txns() { + // System transactions such as on L2s don't contain any pricing info so we skip them + // otherwise this would cause reverts + if is_known_system_sender(tx.inner().inner.signer()) + || tx.ty() == SYSTEM_TRANSACTION_TYPE + { + trace!(tx=?tx.tx_hash(), "skipping system transaction"); + continue; } - txs_to_replay.push(tx.clone()); - } - - // Replay all preceding transactions using a single EVM + cloned ForkDB. - if !txs_to_replay.is_empty() { - let now = Instant::now(); - // Clone the fork's CacheDB once. The underlying SharedBackend is Arc-backed, - // so only the local cache layer is actually duplicated. - let replay_db = fork.db.clone(); - let mut evm = FEN::EvmFactory::default().create_evm(replay_db, evm_env); - - for tx in &txs_to_replay { - let tx_env = TxEnvFor::::from_any_rpc_transaction(tx)?; - trace!(tx=?tx.tx_hash(), "committing transaction"); - evm.transact_commit(tx_env).wrap_err("backend: failed committing transaction")?; + if tx.tx_hash() == tx_hash { + // found the target transaction + return Ok(Some(tx.inner.clone())); } - - // Extract the DB back and replace the fork's database with the replayed state. - fork.db = evm.into_db(); - - // Refresh journaled states from the updated database, preserving persistent - // accounts (cheatcode address, CREATE2 deployer, test contract, etc.). - fork.refresh_journaled_states(journaled_state, &persistent_accounts)?; - - trace!(elapsed=?now.elapsed(), count=txs_to_replay.len(), "replayed transactions"); + trace!(tx=?tx.tx_hash(), "committing transaction"); + + commit_transaction( + tx, + &mut env, + journaled_state, + fork, + &fork_id, + &persistent_accounts, + &mut NoOpInspector, + )?; } - Ok(target_tx) + Ok(None) } } -impl DatabaseExt for Backend { - fn snapshot_state( - &mut self, - journaled_state: &JournaledState, - evm_env: &EvmEnvFor, - ) -> U256 { +impl DatabaseExt for Backend { + fn snapshot_state(&mut self, journaled_state: &JournaledState, evm_env: &EvmEnv) -> U256 { trace!("create snapshot"); let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), @@ -999,8 +975,8 @@ impl DatabaseExt for Backend { &mut self, id: U256, current_state: &JournaledState, - evm_env: &mut EvmEnvFor, - caller: Address, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, action: RevertStateSnapshotAction, ) -> Option { trace!(?id, "revert snapshot"); @@ -1032,6 +1008,7 @@ impl DatabaseExt for Backend { // there might be the case where the snapshot was created during `setUp` with // another caller, so we need to ensure the caller account is present in the // journaled state and database + let caller = tx_env.caller; journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = current_state .state @@ -1050,7 +1027,7 @@ impl DatabaseExt for Backend { } } - *evm_env = snap_evm_env; + update_current_env_with_fork_env(evm_env, tx_env, snap_evm_env); trace!(target: "backend", "Reverted snapshot {}", id); Some(journaled_state) @@ -1097,6 +1074,7 @@ impl DatabaseExt for Backend { Some(id), transaction, &mut evm_env, + &mut TxEnv::default(), &mut self.inner.new_journaled_state(), )?; @@ -1108,8 +1086,8 @@ impl DatabaseExt for Backend { fn select_fork( &mut self, id: LocalForkId, - evm_env: &mut EvmEnvFor, - tx_env: &mut TxEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, active_journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, "select fork"); @@ -1123,8 +1101,8 @@ impl DatabaseExt for Backend { if let Some(active_fork_id) = self.active_fork_id() { self.forks.update_block( self.ensure_fork_id(active_fork_id).cloned()?, - evm_env.block_env.number(), - evm_env.block_env.timestamp(), + evm_env.block_env.number, + evm_env.block_env.timestamp, )?; } @@ -1140,8 +1118,8 @@ impl DatabaseExt for Backend { if let Some(active) = self.active_fork_mut() { active.journaled_state = active_journaled_state.clone(); - let caller = tx_env.caller(); - let caller_account = active.journaled_state.state.get(&caller).cloned(); + let caller = tx_env.caller; + let caller_account = active.journaled_state.state.get(&tx_env.caller).cloned(); let target_fork = self.inner.get_fork_mut(idx); // depth 0 will be the default value when the fork was created @@ -1206,11 +1184,11 @@ impl DatabaseExt for Backend { // another edge case where a fork is created and selected during setup with not // necessarily the same caller as for the test, however we must always // ensure that fork's state contains the current sender - let caller = tx_env.caller(); + let caller = tx_env.caller; fork.journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = active_journaled_state .state - .get(&caller) + .get(&tx_env.caller) .map(|acc| acc.info.clone()) .unwrap_or_default(); @@ -1229,8 +1207,7 @@ impl DatabaseExt for Backend { self.active_fork_ids = Some((id, idx)); // Update current environment with environment of newly selected fork. - tx_env.set_chain_id(Some(fork_evm_env.cfg_env.chain_id)); - *evm_env = fork_evm_env; + update_current_env_with_fork_env(evm_env, tx_env, fork_evm_env); Ok(()) } @@ -1241,7 +1218,8 @@ impl DatabaseExt for Backend { &mut self, id: Option, block_number: u64, - evm_env: &mut EvmEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); @@ -1256,7 +1234,7 @@ impl DatabaseExt for Backend { if active_id == id { // need to update the block's env settings right away, which is otherwise set when // forks are selected `select_fork` - *evm_env = fork_env; + update_current_env_with_fork_env(evm_env, tx_env, fork_env); // we also need to update the journaled_state right away, this has essentially the // same effect as selecting (`select_fork`) by discarding @@ -1305,7 +1283,8 @@ impl DatabaseExt for Backend { &mut self, id: Option, transaction: B256, - evm_env: &mut EvmEnvFor, + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?transaction, "roll fork to transaction"); @@ -1317,10 +1296,10 @@ impl DatabaseExt for Backend { // roll the fork to the transaction's parent block or latest if it's pending, because we // need to fork off the parent block's state for tx level forking and then replay the txs // before the tx in that block to get the state at the tx - self.roll_fork(Some(id), fork_block, evm_env, journaled_state)?; + self.roll_fork(Some(id), fork_block, evm_env, tx_env, journaled_state)?; // we need to update the env to the block - update_env_block(evm_env, block.header()); + update_env_block(evm_env, &block); // after we forked at the fork block we need to properly update the block env to the block // env of the tx's block @@ -1328,8 +1307,10 @@ impl DatabaseExt for Backend { .forks .update_block_env(self.inner.ensure_fork_id(id).cloned()?, evm_env.block_env.clone()); + let env = Env { evm_env: evm_env.clone(), tx: tx_env.clone() }; + // replay all transactions that came before - self.replay_until(id, evm_env.clone(), transaction, journaled_state)?; + self.replay_until(id, env, transaction, journaled_state)?; Ok(()) } @@ -1338,11 +1319,10 @@ impl DatabaseExt for Backend { &mut self, maybe_id: Option, transaction: B256, - mut evm_env: EvmEnvFor, + mut evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt< - ::FoundryContext<'db>, - >, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1351,9 +1331,8 @@ impl DatabaseExt for Backend { let tx = { let fork = self.inner.get_fork_by_id_mut(id)?; - fork.backend().get_transaction(transaction)? + fork.db.db.get_transaction(transaction)? }; - let tx_env = TxEnvFor::::from_any_rpc_transaction(&tx)?; // This is a bit ambiguous because the user wants to transact an arbitrary transaction in // the current context, but we're assuming the user wants to transact the transaction as it @@ -1363,12 +1342,13 @@ impl DatabaseExt for Backend { // So we modify the env to match the transaction's block. let (_fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; - update_env_block(&mut evm_env, block.header()); + update_env_block(&mut evm_env, &block); + let mut env = Env { evm_env, tx: tx_env }; let fork = self.inner.get_fork_by_id_mut(id)?; - commit_transaction::( - evm_env, - tx_env, + commit_transaction( + &tx, + &mut env, journaled_state, fork, &fork_id, @@ -1379,24 +1359,29 @@ impl DatabaseExt for Backend { fn transact_from_tx( &mut self, - tx_env: TxEnvFor, - evm_env: EvmEnvFor, + tx: &TransactionRequest, + evm_env: EvmEnv, + tx_env: TxEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn for<'db> FoundryInspectorExt< - ::FoundryContext<'db>, - >, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - trace!("execute signed transaction"); + trace!(?tx, "execute signed transaction"); self.commit(journaled_state.state.clone()); + let mut env = Env { evm_env, tx: tx_env }; let res = { + env.tx = tx.clone().try_into_tx_env(&env.evm_env)?; + let mut db = self.clone(); - let depth = journaled_state.depth + 1; - let mut evm = - FEN::EvmFactory::default().create_foundry_nested_evm(&mut db, evm_env, inspector); - evm.journal_inner_mut().depth = depth; - evm.transact_raw(tx_env)? + let mut evm = new_evm_with_inspector( + &mut db, + env.evm_env.to_owned(), + env.tx.to_owned(), + inspector, + ); + evm.journaled_state.depth = journaled_state.depth + 1; + evm.transact(env.tx)? }; self.commit(res.state); @@ -1570,7 +1555,7 @@ impl DatabaseExt for Backend { } } -impl DatabaseRef for Backend { +impl DatabaseRef for Backend { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -1606,8 +1591,8 @@ impl DatabaseRef for Backend { } } -impl DatabaseCommit for Backend { - fn commit(&mut self, changes: AddressMap) { +impl DatabaseCommit for Backend { + fn commit(&mut self, changes: Map) { if let Some(db) = self.active_fork_db_mut() { db.commit(changes) } else { @@ -1616,7 +1601,7 @@ impl DatabaseCommit for Backend { } } -impl Database for Backend { +impl Database for Backend { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { if let Some(db) = self.active_fork_db_mut() { @@ -1653,26 +1638,21 @@ impl Database for Backend { /// Variants of a [revm::Database] #[derive(Clone, Debug)] -pub enum BackendDatabaseSnapshot { +pub enum BackendDatabaseSnapshot { /// Simple in-memory [revm::Database] InMemory(FoundryEvmInMemoryDB), /// Contains the entire forking mode database - Forked(LocalForkId, ForkId, ForkLookupIndex, Box>), + Forked(LocalForkId, ForkId, ForkLookupIndex, Box), } /// Represents a fork #[derive(Clone, Debug)] -pub struct Fork { - db: ForkDB, +pub struct Fork { + db: ForkDB, journaled_state: JournaledState, } -impl Fork { - /// Returns a reference to the underlying [`SharedBackend`]. - pub const fn backend(&self) -> &SharedBackend { - &self.db.db - } - +impl Fork { /// Returns true if the account is a contract pub fn is_contract(&self, acc: Address) -> bool { if let Ok(Some(acc)) = self.db.basic_ref(acc) @@ -1682,22 +1662,11 @@ impl Fork { } is_contract_in_state(&self.journaled_state.state, acc) } - - /// Refreshes the given journaled state and the fork's own journaled state from the - /// database, preserving persistent accounts. - fn refresh_journaled_states( - &mut self, - journaled_state: &mut JournaledState, - persistent_accounts: &HashSet
, - ) -> Result<(), BackendError> { - update_state(&mut journaled_state.state, &mut self.db, Some(persistent_accounts))?; - update_state(&mut self.journaled_state.state, &mut self.db, Some(persistent_accounts))?; - Ok(()) - } } /// Container type for various Backend related data -pub struct BackendInner { +#[derive(Clone, Debug)] +pub struct BackendInner { /// Stores the `ForkId` of the fork the `Backend` launched with from the start. /// /// In other words if [`Backend::spawn()`] was called with a `CreateFork` command, to launch @@ -1720,16 +1689,9 @@ pub struct BackendInner { pub created_forks: HashMap, /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling - pub forks: Vec>>>, + pub forks: Vec>, /// Contains state snapshots made at a certain point - #[allow(clippy::type_complexity)] - pub state_snapshots: StateSnapshots< - BackendStateSnapshot< - BackendDatabaseSnapshot>, - SpecFor, - BlockEnvFor, - >, - >, + pub state_snapshots: StateSnapshots>, /// Tracks whether there was a failure in a snapshot that was reverted /// /// The Test contract contains a bool variable that is set to true when an `assert` function @@ -1749,48 +1711,12 @@ pub struct BackendInner { /// instead the use only one that's persistent across fork swaps. pub persistent_accounts: HashSet
, /// The configured spec id - pub spec_id: SpecFor, + pub spec_id: SpecId, /// All accounts that are allowed to execute cheatcodes pub cheatcode_access_accounts: HashSet
, } -impl Clone for BackendInner { - fn clone(&self) -> Self { - Self { - launched_with_fork: self.launched_with_fork.clone(), - issued_local_fork_ids: self.issued_local_fork_ids.clone(), - created_forks: self.created_forks.clone(), - forks: self.forks.clone(), - state_snapshots: self.state_snapshots.clone(), - has_state_snapshot_failure: self.has_state_snapshot_failure, - caller: self.caller, - next_fork_id: self.next_fork_id, - persistent_accounts: self.persistent_accounts.clone(), - spec_id: self.spec_id, - cheatcode_access_accounts: self.cheatcode_access_accounts.clone(), - } - } -} - -impl Debug for BackendInner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BackendInner") - .field("launched_with_fork", &self.launched_with_fork) - .field("issued_local_fork_ids", &self.issued_local_fork_ids) - .field("created_forks", &self.created_forks) - .field("forks", &self.forks) - .field("state_snapshots", &self.state_snapshots) - .field("has_state_snapshot_failure", &self.has_state_snapshot_failure) - .field("caller", &self.caller) - .field("next_fork_id", &self.next_fork_id) - .field("persistent_accounts", &self.persistent_accounts) - .field("spec_id", &self.spec_id) - .field("cheatcode_access_accounts", &self.cheatcode_access_accounts) - .finish() - } -} - -impl BackendInner { +impl BackendInner { pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids .get(&id) @@ -1810,58 +1736,51 @@ impl BackendInner { /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork(&self, idx: ForkLookupIndex) -> &Fork> { + fn get_fork(&self, idx: ForkLookupIndex) -> &Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_ref().unwrap() } /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork> { + fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_mut().unwrap() } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id_mut( - &mut self, - id: LocalForkId, - ) -> eyre::Result<&mut Fork>> { + fn get_fork_by_id_mut(&mut self, id: LocalForkId) -> eyre::Result<&mut Fork> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork_mut(idx)) } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork>> { + fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork(idx)) } /// Removes the fork - fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork> { + fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].take().unwrap() } - fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork>) { + fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork) { self.forks[idx] = Some(fork) } /// Returns an iterator over Forks - pub fn forks_iter( - &self, - ) -> impl Iterator>)> + '_ { + pub fn forks_iter(&self) -> impl Iterator + '_ { self.issued_local_fork_ids .iter() .map(|(id, fork_id)| (*id, self.get_fork(self.created_forks[fork_id]))) } /// Returns a mutable iterator over all Forks - pub fn forks_iter_mut( - &mut self, - ) -> impl Iterator>> + '_ { + pub fn forks_iter_mut(&mut self) -> impl Iterator + '_ { self.forks.iter_mut().filter_map(|f| f.as_mut()) } @@ -1871,7 +1790,7 @@ impl BackendInner { id: LocalForkId, fork_id: ForkId, idx: ForkLookupIndex, - fork: Fork>, + fork: Fork, ) { self.created_forks.insert(fork_id.clone(), idx); self.issued_local_fork_ids.insert(id, fork_id); @@ -1883,7 +1802,7 @@ impl BackendInner { &mut self, id: LocalForkId, fork_id: ForkId, - db: ForkDB>, + db: ForkDB, journaled_state: JournaledState, ) -> ForkLookupIndex { let idx = self.forks.len(); @@ -1899,7 +1818,7 @@ impl BackendInner { &mut self, id: LocalForkId, new_fork_id: ForkId, - backend: SharedBackend>, + backend: SharedBackend, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1924,7 +1843,7 @@ impl BackendInner { pub fn insert_new_fork( &mut self, fork_id: ForkId, - db: ForkDB>, + db: ForkDB, journaled_state: JournaledState, ) -> (LocalForkId, ForkLookupIndex) { let idx = self.forks.len(); @@ -1952,19 +1871,15 @@ impl BackendInner { self.issued_local_fork_ids.is_empty() } - pub fn precompiles(&self) -> PrecompilesFor { - let evm = FEN::EvmFactory::default().create_evm( - EmptyDB::default(), - EvmEnv::new(CfgEnv::new_with_spec(self.spec_id), Default::default()), - ); - evm.precompiles().clone() + pub fn precompiles(&self) -> &'static Precompiles { + Precompiles::new(PrecompileSpecId::from_spec_id(self.spec_id)) } /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { let mut journal = { let mut journal_inner = JournalInner::new(); - journal_inner.set_spec_id(self.spec_id.into()); + journal_inner.set_spec_id(self.spec_id); journal_inner }; journal @@ -1974,7 +1889,7 @@ impl BackendInner { } } -impl Default for BackendInner { +impl Default for BackendInner { fn default() -> Self { Self { launched_with_fork: None, @@ -1986,7 +1901,7 @@ impl Default for BackendInner { caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - spec_id: SpecFor::::default(), + spec_id: SpecId::default(), // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1998,15 +1913,25 @@ impl Default for BackendInner { } } +/// This updates the currently used env with the fork's environment +pub(crate) fn update_current_env_with_fork_env( + evm_env: &mut EvmEnv, + tx_env: &mut TxEnv, + fork_evm_env: EvmEnv, +) { + tx_env.chain_id = Some(fork_evm_env.cfg_env.chain_id); + *evm_env = fork_evm_env; +} + /// Clones the data of the given `accounts` from the `active` database into the `fork_db` /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. -pub(crate) fn merge_account_data( +pub(crate) fn merge_account_data( accounts: impl IntoIterator, active: &CacheDB, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork, ) { - for addr in accounts { + for addr in accounts.into_iter() { merge_db_account_data(addr, active, &mut target_fork.db); merge_journaled_state_data(addr, active_journaled_state, &mut target_fork.journaled_state); } @@ -2033,10 +1958,10 @@ fn merge_journaled_state_data( } /// Clones the account data from the `active` db into the `ForkDB` -fn merge_db_account_data( +fn merge_db_account_data( addr: Address, active: &CacheDB, - fork_db: &mut ForkDB, + fork_db: &mut ForkDB, ) { trace!(?addr, "merging database data"); @@ -2073,51 +1998,55 @@ fn is_contract_in_state(evm_state: &EvmState, acc: Address) -> bool { } /// Updates the evm env's block with the block's data -fn update_env_block( - evm_env: &mut EvmEnv, - header: &impl BlockHeader, -) { +fn update_env_block(evm_env: &mut EvmEnv, block: &AnyRpcBlock) { let block_env = &mut evm_env.block_env; - block_env.set_timestamp(U256::from(header.timestamp())); - block_env.set_beneficiary(header.beneficiary()); - block_env.set_difficulty(header.difficulty()); - block_env.set_prevrandao(header.mix_hash()); - block_env.set_basefee(header.base_fee_per_gas().unwrap_or_default()); - block_env.set_gas_limit(header.gas_limit()); - block_env.set_number(U256::from(header.number())); - - if let Some(excess_blob_gas) = header.excess_blob_gas() { - evm_env.block_env.set_blob_excess_gas_and_price( + block_env.timestamp = U256::from(block.header.timestamp()); + block_env.beneficiary = block.header.beneficiary(); + block_env.difficulty = block.header.difficulty(); + block_env.prevrandao = Some(block.header.mix_hash().unwrap_or_default()); + block_env.basefee = block.header.base_fee_per_gas().unwrap_or_default(); + block_env.gas_limit = block.header.gas_limit(); + block_env.number = U256::from(block.header.number()); + + if let Some(excess_blob_gas) = block.header.excess_blob_gas() { + evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( excess_blob_gas, - get_blob_base_fee_update_fraction(evm_env.cfg_env.chain_id, header.timestamp()), - ); + get_blob_base_fee_update_fraction(evm_env.cfg_env.chain_id, block.header.timestamp()), + )); } } /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an inspector. -fn commit_transaction( - evm_env: EvmEnvFor, - tx_env: TxEnvFor, +fn commit_transaction( + tx: &AnyRpcTransaction, + env: &mut Env, journaled_state: &mut JournaledState, - fork: &mut Fork>, + fork: &mut Fork, fork_id: &ForkId, persistent_accounts: &HashSet
, - inspector: &mut dyn for<'db> FoundryInspectorExt< - ::FoundryContext<'db>, - >, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { + if let Some(tx_envelope) = tx.as_envelope() { + env.tx = TxEnv::from_recovered_tx(tx_envelope, tx.from()); + } + let now = Instant::now(); let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let mut db: Backend = Backend::new_with_fork(fork_id, fork, journaled_state)?; + let mut db = Backend::new_with_fork(fork_id, fork, journaled_state)?; - let mut evm = - FEN::EvmFactory::default().create_foundry_nested_evm(&mut db, evm_env, inspector); - evm.journal_inner_mut().depth = depth + 1; - evm.transact_raw(tx_env).wrap_err("backend: failed committing transaction")? + let mut evm = crate::evm::new_evm_with_inspector( + &mut db as _, + env.evm_env.to_owned(), + env.tx.to_owned(), + inspector, + ); + // Adjust inner EVM depth to ensure that inspectors receive accurate data. + evm.journaled_state.depth = depth + 1; + evm.transact(env.tx.clone()).wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); @@ -2146,30 +2075,30 @@ pub fn update_state( /// Applies the changeset of a transaction to the active journaled state and also commits it in the /// forked db -fn apply_state_changeset( - state: EvmState, +fn apply_state_changeset( + state: Map, journaled_state: &mut JournaledState, - fork: &mut Fork, + fork: &mut Fork, persistent_accounts: &HashSet
, ) -> Result<(), BackendError> { // commit the state and update the loaded accounts fork.db.commit(state); - fork.refresh_journaled_states(journaled_state, persistent_accounts) + + update_state(&mut journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + update_state(&mut fork.journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + + Ok(()) } #[cfg(test)] mod tests { - use crate::{backend::Backend, evm::EthEvmNetwork, opts::EvmOpts}; + use crate::{backend::Backend, opts::EvmOpts}; use alloy_primitives::{U256, address}; use alloy_provider::Provider; use foundry_common::provider::get_http_provider; use foundry_config::{Config, NamedChain}; use foundry_fork_db::cache::{BlockchainDb, BlockchainDbMeta}; - use revm::{ - context::{BlockEnv, TxEnv}, - database::DatabaseRef, - primitives::hardfork::SpecId, - }; + use revm::database::DatabaseRef; #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cache() { @@ -2182,12 +2111,11 @@ mod tests { evm_opts.fork_url = Some(endpoint.to_string()); evm_opts.fork_block_number = Some(block_num); - let (evm_env, _, fork_block) = evm_opts.env::().await.unwrap(); + let env = evm_opts.env().await.unwrap(); - let fork = - evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap(); + let fork = evm_opts.get_fork(&Config::default(), env.evm_env.clone()).unwrap(); - let backend = Backend::::spawn(Some(fork)).unwrap(); + let backend = Backend::spawn(Some(fork)).unwrap(); // some rng contract from etherscan let address = address!("0x63091244180ae240c87d1f528f5f269134cb07b3"); @@ -2201,7 +2129,7 @@ mod tests { let meta = BlockchainDbMeta { chain: None, - block_env: evm_env.block_env, + block_env: env.evm_env.block_env, hosts: Default::default(), }; diff --git a/crates/evm/core/src/evm/eth.rs b/crates/evm/core/src/evm/eth.rs index bef827c6690f2..71313e9e34df8 100644 --- a/crates/evm/core/src/evm/eth.rs +++ b/crates/evm/core/src/evm/eth.rs @@ -1,111 +1,43 @@ -use super::*; - -type EthEvmHandler<'db, I> = - MainnetHandler, EVMError, EthFrame>; +use alloy_evm::{ + EthEvm, EthEvmFactory, Evm, EvmEnv, EvmFactory, eth::EthEvmContext, precompiles::PrecompilesMap, +}; +use foundry_fork_db::DatabaseError; +use revm::{ + context::{ + BlockEnv, ContextTr, Evm as RevmEvm, LocalContextTr, TxEnv, + result::{EVMError, ResultAndState}, + }, + handler::{ + EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, instructions::EthInstructions, + }, + inspector::InspectorHandler, + interpreter::{ + FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + }, + primitives::hardfork::SpecId, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + evm::{FoundryEvmFactory, NestedEvm}, +}; + +type EthEvmHandler<'db, I> = MainnetHandler, EVMError, EthFrame>; pub type EthRevmEvm<'db, I> = RevmEvm< EthEvmContext<&'db mut dyn DatabaseExt>, I, EthInstructions>>, PrecompilesMap, - EthFrame, + EthFrame, >; -pub struct EthFoundryEvm< - 'db, - I: FoundryInspectorExt>>, -> { - pub inner: EthRevmEvm<'db, I>, -} - -impl<'db, I: FoundryInspectorExt>>> Evm - for EthFoundryEvm<'db, I> -{ - type Precompiles = PrecompilesMap; - type Inspector = I; - type DB = &'db mut dyn DatabaseExt; - type Error = EVMError; - type HaltReason = HaltReason; - type Spec = SpecId; - type Tx = TxEnv; - type BlockEnv = BlockEnv; - - fn block(&self) -> &BlockEnv { - &self.inner.block - } - - fn chain_id(&self) -> u64 { - self.inner.ctx.cfg.chain_id - } - - fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { - (&self.inner.ctx.journaled_state.database, &self.inner.inspector, &self.inner.precompiles) - } - - fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { - ( - &mut self.inner.ctx.journaled_state.database, - &mut self.inner.inspector, - &mut self.inner.precompiles, - ) - } - - fn set_inspector_enabled(&mut self, _enabled: bool) { - unimplemented!("FoundryEvm is always inspecting") - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - self.inner.set_tx(tx); - - let result = EthEvmHandler::::default().inspect_run(&mut self.inner)?; - - Ok(ResultAndState::new(result, self.inner.ctx.journaled_state.inner.state.clone())) - } - - fn transact_system_call( - &mut self, - _caller: Address, - _contract: Address, - _data: Bytes, - ) -> Result, Self::Error> { - unimplemented!() - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; - - (journaled_state.database, EvmEnv { block_env, cfg_env }) - } -} - -impl<'db, I: FoundryInspectorExt>>> Deref - for EthFoundryEvm<'db, I> -{ - type Target = EthEvmContext<&'db mut dyn DatabaseExt>; - - fn deref(&self) -> &Self::Target { - &self.inner.ctx - } -} - -impl<'db, I: FoundryInspectorExt>>> DerefMut - for EthFoundryEvm<'db, I> -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.ctx - } -} - impl FoundryEvmFactory for EthEvmFactory { type FoundryContext<'db> = EthEvmContext<&'db mut dyn DatabaseExt>; - type FoundryEvm<'db, I: FoundryInspectorExt>> = EthFoundryEvm<'db, I>; + type FoundryEvm<'db, I: FoundryInspectorExt>> = + EthEvm<&'db mut dyn DatabaseExt, I, Self::Precompiles>; fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( &self, @@ -113,13 +45,10 @@ impl FoundryEvmFactory for EthEvmFactory { evm_env: EvmEnv, inspector: I, ) -> Self::FoundryEvm<'db, I> { - let eth_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); - let mut inner = eth_evm.into_inner(); - inner.ctx.cfg.tx_chain_id_check = true; - - let mut evm = EthFoundryEvm { inner }; - evm.inspector().get_networks().inject_precompiles(evm.precompiles_mut()); - evm + let mut eth_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + eth_evm.cfg.tx_chain_id_check = true; + eth_evm.inspector().get_networks().inject_precompiles(eth_evm.precompiles_mut()); + eth_evm } fn create_foundry_nested_evm<'db>( @@ -128,7 +57,7 @@ impl FoundryEvmFactory for EthEvmFactory { evm_env: EvmEnv, inspector: &'db mut dyn FoundryInspectorExt>, ) -> Box + 'db> { - Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).inner) + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner()) } } @@ -148,7 +77,7 @@ impl<'db, I: FoundryInspectorExt Result, EVMError> { + fn transact_raw(&mut self, tx: Self::Tx) -> Result> { self.set_tx(tx); let result = EthEvmHandler::::default().inspect_run(self)?; diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs index 1dcdeed34c6bf..708226be003a2 100644 --- a/crates/evm/core/src/evm/mod.rs +++ b/crates/evm/core/src/evm/mod.rs @@ -1,64 +1,39 @@ -use std::{ - fmt::Debug, - ops::{Deref, DerefMut}, -}; +use std::{fmt::Debug, ops::Deref}; use crate::{ FoundryBlock, FoundryContextExt, FoundryInspectorExt, FoundryTransaction, FromAnyRpcTransaction, backend::{DatabaseExt, JournaledState}, - constants::{CALLER, TEST_CONTRACT_ADDRESS}, - tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, }; use alloy_consensus::{SignableTransaction, Signed, transaction::SignerRecoverable}; use alloy_evm::{ - EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, eth::EthEvmContext, - precompiles::PrecompilesMap, + EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap, }; use alloy_network::{Ethereum, Network}; -use alloy_op_evm::{OpEvmFactory, OpTx}; -use alloy_primitives::{Address, Bytes, Signature, U256}; +use alloy_op_evm::OpEvmFactory; +use alloy_primitives::{Address, Signature, U256}; use alloy_rlp::Decodable; use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; use op_alloy_network::Optimism; -use op_revm::{ - L1BlockInfo, OpEvm, OpHaltReason, OpSpecId, OpTransaction, handler::OpHandler, - precompiles::OpPrecompiles, transaction::error::OpTransactionError, -}; +use op_revm::OpHaltReason; use revm::{ - Context, Database, Journal, MainContext, + Database, context::{ - BlockEnv, CfgEnv, ContextTr, Evm as RevmEvm, JournalTr, LocalContextTr, TxEnv, - result::{ - EVMError, ExecResultAndState, ExecutionResult, HaltReason, InvalidTransaction, - ResultAndState, - }, - }, - handler::{ - EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, instructions::EthInstructions, + JournalTr, + result::{EVMError, HaltReason, ResultAndState}, }, - inspector::{InspectorEvmTr, InspectorHandler}, + handler::FrameResult, interpreter::{ CallInput, CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InstructionResult, - SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, }, primitives::hardfork::SpecId, - state::Bytecode, }; use serde::{Deserialize, Serialize}; use tempo_alloy::TempoNetwork; -use tempo_chainspec::hardfork::TempoHardfork; use tempo_evm::evm::TempoEvmFactory; -use tempo_precompiles::storage::StorageCtx; -use tempo_revm::{ - TempoBlockEnv, TempoHaltReason, TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, - gas_params::tempo_gas_params, handler::TempoEvmHandler, -}; - -// Modified revm's OpContext with `OpTx` -pub type OpContext = Context, DB, Journal, L1BlockInfo>; +use tempo_revm::TempoHaltReason; pub mod eth; pub mod op; diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs index e98ee785ab705..1b9db85cf2781 100644 --- a/crates/evm/core/src/evm/op.rs +++ b/crates/evm/core/src/evm/op.rs @@ -1,10 +1,40 @@ -use super::*; - -type OpEvmHandler<'db, I> = OpHandler< - OpRevmEvm<'db, I>, - EVMError, - EthFrame, ->; +use std::ops::{Deref, DerefMut}; + +use alloy_evm::{Evm, EvmEnv, precompiles::PrecompilesMap}; +use alloy_op_evm::{OpEvmFactory, OpTx}; +use alloy_primitives::{Address, Bytes}; +use foundry_fork_db::DatabaseError; +use op_revm::{ + L1BlockInfo, OpEvm, OpHaltReason, OpSpecId, OpTransaction, OpTransactionError, + handler::OpHandler, precompiles::OpPrecompiles, +}; +use revm::{ + Context, Journal, MainContext, + context::{ + BlockEnv, CfgEnv, ContextTr, LocalContextTr, + result::{ + EVMError, ExecResultAndState, ExecutionResult, HaltReason, InvalidTransaction, + ResultAndState, + }, + }, + handler::{EthFrame, EvmTr, FrameResult, Handler, instructions::EthInstructions}, + inspector::{InspectorEvmTr, InspectorHandler}, + interpreter::{ + FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + }, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + evm::{FoundryEvmFactory, NestedEvm}, +}; + +// Modified revm's OpContext with `OpTx` +pub type OpContext = Context, DB, Journal, L1BlockInfo>; + +type OpEvmHandler<'db, I> = + OpHandler, EVMError, EthFrame>; pub type OpRevmEvm<'db, I> = op_revm::OpEvm< OpContext<&'db mut dyn DatabaseExt>, @@ -13,8 +43,8 @@ pub type OpRevmEvm<'db, I> = op_revm::OpEvm< PrecompilesMap, >; -/// Optimism counterpart of [`EthFoundryEvm`]. Wraps `op_revm::OpEvm` and routes execution -/// through [`OpHandler`]. +/// Wraps [`op_revm::OpEvm`] and routes execution through [`OpHandler`]. +/// It uses foundry's custom [`OpContext`] as op-revm's one is not compatible with [`OpTx`]. pub struct OpFoundryEvm< 'db, I: FoundryInspectorExt>>, @@ -188,7 +218,7 @@ impl<'db, I: FoundryInspectorExt::new(); let memory = - SharedMemory::new_with_buffer(self.ctx_ref().local.shared_memory_buffer().clone()); + SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone()); let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; let mut frame_result = @@ -217,6 +247,6 @@ impl<'db, I: FoundryInspectorExt EvmEnv { - EvmEnv::new(self.ctx_ref().cfg.clone(), self.ctx_ref().block.clone()) + self.ctx_ref().evm_clone() } } diff --git a/crates/evm/core/src/evm/tempo.rs b/crates/evm/core/src/evm/tempo.rs index 65194b50ddfe7..98c8cef9fac07 100644 --- a/crates/evm/core/src/evm/tempo.rs +++ b/crates/evm/core/src/evm/tempo.rs @@ -1,21 +1,36 @@ -use super::*; +use alloy_evm::{Evm, EvmEnv, EvmFactory}; +use alloy_primitives::Bytes; +use foundry_evm_hardforks::TempoHardfork; +use foundry_fork_db::DatabaseError; +use revm::{ + context::{ + ContextTr, LocalContextTr, + result::{EVMError, HaltReason, ResultAndState}, + }, + handler::{EvmTr, FrameResult, Handler}, + inspector::InspectorHandler, + interpreter::{FrameInput, SharedMemory, interpreter_action::FrameInit}, + state::Bytecode, +}; +use tempo_evm::{TempoBlockEnv, TempoEvmFactory, TempoHaltReason, evm::TempoEvm}; +use tempo_precompiles::storage::StorageCtx; +use tempo_revm::{ + TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, gas_params::tempo_gas_params, + handler::TempoEvmHandler, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + constants::{CALLER, TEST_CONTRACT_ADDRESS}, + evm::{FoundryEvmFactory, NestedEvm}, + tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, +}; // Will be removed when the next revm release includes bluealloy/revm#3518. pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt, I>; -/// Tempo counterpart of [`EthFoundryEvm`]. Wraps `tempo_revm::TempoEvm` and routes execution -/// through [`TempoEvmHandler`]. -/// -/// Uses [`TempoEvmFactory`] for construction to reuse factory setup logic, then unwraps to the -/// raw revm EVM via `into_inner()` since the handler operates at the revm level. -pub struct TempoFoundryEvm< - 'db, - I: FoundryInspectorExt>>, -> { - pub inner: TempoRevmEvm<'db, I>, -} - -/// Initialize Tempo precompiles and contracts for a newly created [`TempoFoundryEvm`]. +/// Initialize Tempo precompiles and contracts for a newly created EVM. /// /// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode, /// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`]. @@ -27,10 +42,10 @@ pub(crate) fn initialize_tempo_evm< 'db, I: FoundryInspectorExt>>, >( - evm: &mut TempoFoundryEvm<'db, I>, + evm: &mut TempoEvm<&'db mut dyn DatabaseExt, I>, is_forked: bool, ) { - let ctx = &mut evm.inner.inner.ctx; + let ctx = evm.ctx_mut(); StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || { if is_forked { // In fork mode, warm up precompile accounts to avoid repeated RPC fetches. @@ -52,7 +67,7 @@ impl FoundryEvmFactory for TempoEvmFactory { type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt>; type FoundryEvm<'db, I: FoundryInspectorExt>> = - TempoFoundryEvm<'db, I>; + TempoEvm<&'db mut dyn DatabaseExt, I>; fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( &self, @@ -62,20 +77,18 @@ impl FoundryEvmFactory for TempoEvmFactory { ) -> Self::FoundryEvm<'db, I> { let is_forked = db.is_forked_mode(); let spec = *evm_env.spec_id(); - let tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); - let mut inner = tempo_evm.into_inner(); - inner.ctx.cfg.gas_params = tempo_gas_params(spec); - inner.ctx.cfg.tx_chain_id_check = true; - if inner.ctx.cfg.tx_gas_limit_cap.is_none() { - inner.ctx.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap(); + let mut tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + tempo_evm.cfg.gas_params = tempo_gas_params(spec); + tempo_evm.cfg.tx_chain_id_check = true; + if tempo_evm.cfg.tx_gas_limit_cap.is_none() { + tempo_evm.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap(); } - let mut evm = TempoFoundryEvm { inner }; - let networks = Evm::inspector(&evm).get_networks(); - networks.inject_precompiles(evm.precompiles_mut()); + let networks = tempo_evm.inspector().get_networks(); + networks.inject_precompiles(tempo_evm.precompiles_mut()); - initialize_tempo_evm(&mut evm, is_forked); - evm + initialize_tempo_evm(&mut tempo_evm, is_forked); + tempo_evm } fn create_foundry_nested_evm<'db>( @@ -85,90 +98,7 @@ impl FoundryEvmFactory for TempoEvmFactory { inspector: &'db mut dyn FoundryInspectorExt>, ) -> Box + 'db> { - Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).inner) - } -} - -impl<'db, I: FoundryInspectorExt>>> Evm - for TempoFoundryEvm<'db, I> -{ - type Precompiles = PrecompilesMap; - type Inspector = I; - type DB = &'db mut dyn DatabaseExt; - type Error = EVMError; - type HaltReason = TempoHaltReason; - type Spec = TempoHardfork; - type Tx = TempoTxEnv; - type BlockEnv = TempoBlockEnv; - - fn block(&self) -> &TempoBlockEnv { - &self.inner.block - } - - fn chain_id(&self) -> u64 { - self.inner.ctx.cfg.chain_id - } - - fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { - let evm = &self.inner.inner; - (&evm.ctx.journaled_state.database, &evm.inspector, &evm.precompiles) - } - - fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { - let evm = &mut self.inner.inner; - (&mut evm.ctx.journaled_state.database, &mut evm.inspector, &mut evm.precompiles) - } - - fn set_inspector_enabled(&mut self, _enabled: bool) { - unimplemented!("TempoFoundryEvm is always inspecting") - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - self.inner.set_tx(tx); - - let mut handler = TempoEvmHandler::new(); - let result = handler.inspect_run(&mut self.inner)?; - - Ok(ResultAndState::new(result, self.inner.inner.ctx.journaled_state.inner.state.clone())) - } - - fn transact_system_call( - &mut self, - _caller: Address, - _contract: Address, - _data: Bytes, - ) -> Result>, Self::Error> { - unimplemented!() - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - let revm_evm = self.inner.inner; - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = revm_evm.ctx; - (journaled_state.database, EvmEnv { block_env, cfg_env }) - } -} - -impl<'db, I: FoundryInspectorExt>>> Deref - for TempoFoundryEvm<'db, I> -{ - type Target = TempoContext<&'db mut dyn DatabaseExt>; - - fn deref(&self) -> &Self::Target { - &self.inner.ctx - } -} - -impl<'db, I: FoundryInspectorExt>>> DerefMut - for TempoFoundryEvm<'db, I> -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.ctx + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner()) } } @@ -206,7 +136,7 @@ impl<'db, I: FoundryInspectorExt Result, EVMError> { + fn transact_raw(&mut self, tx: Self::Tx) -> Result> { self.set_tx(tx); let mut handler = TempoEvmHandler::new(); diff --git a/crates/evm/core/src/tempo.rs b/crates/evm/core/src/tempo.rs index 5e0cbefec56d3..eb29696858ff2 100644 --- a/crates/evm/core/src/tempo.rs +++ b/crates/evm/core/src/tempo.rs @@ -11,11 +11,14 @@ use tempo_contracts::{ ARACHNID_CREATE2_FACTORY_ADDRESS, CREATEX_ADDRESS, CreateX, MULTICALL3_ADDRESS, Multicall3, PERMIT2_ADDRESS, Permit2, SAFE_DEPLOYER_ADDRESS, SafeDeployer, contracts::ARACHNID_CREATE2_FACTORY_BYTECODE, + precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, NONCE_PRECOMPILE_ADDRESS, + SIGNATURE_VERIFIER_ADDRESS, STABLECOIN_DEX_ADDRESS, TIP_FEE_MANAGER_ADDRESS, + TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, VALIDATOR_CONFIG_ADDRESS, + VALIDATOR_CONFIG_V2_ADDRESS, + }, }; use tempo_precompiles::{ - ACCOUNT_KEYCHAIN_ADDRESS, NONCE_PRECOMPILE_ADDRESS, STABLECOIN_DEX_ADDRESS, - TIP_FEE_MANAGER_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, - VALIDATOR_CONFIG_ADDRESS, VALIDATOR_CONFIG_V2_ADDRESS, error::TempoPrecompileError, storage::{PrecompileStorageProvider, StorageCtx}, tip20::{ISSUER_ROLE, ITIP20, TIP20Token}, @@ -25,12 +28,6 @@ use tempo_precompiles::{ pub use tempo_contracts::precompiles::PATH_USD_ADDRESS; -// TODO: remove once we can re-export from tempo_precompiles instead. -pub const SIGNATURE_VERIFIER_ADDRESS: Address = - address!("0x5165300000000000000000000000000000000000"); -pub const ADDRESS_REGISTRY_ADDRESS: Address = - address!("0xFDC0000000000000000000000000000000000000"); - /// All well-known Tempo precompile addresses. pub const TEMPO_PRECOMPILE_ADDRESSES: &[Address] = &[ NONCE_PRECOMPILE_ADDRESS, @@ -92,7 +89,7 @@ pub fn initialize_tempo_genesis_inner( // Create PathUSD token: 0x20C0000000000000000000000000000000000000 let path_usd_token_address = create_and_mint_token( - address!("20C0000000000000000000000000000000000000"), + PATH_USD_ADDRESS, "PathUSD", "PathUSD", "USD", diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 76e1df1bd1778..826384b5ea259 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,4 +1,4 @@ -use crate::{EvmEnv, FoundryBlock}; +use crate::EvmEnv; use alloy_chains::Chain; use alloy_consensus::{BlockHeader, private::alloy_eips::eip7840::BlobParams}; use alloy_hardforks::EthereumHardfork; @@ -16,40 +16,22 @@ pub use revm::state::EvmState as StateChangeset; /// Hints to the compiler that this is a cold path, i.e. unlikely to be taken. #[cold] #[inline(always)] -pub const fn cold_path() { +pub fn cold_path() { // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable. } -/// Constructs a generic [`FoundryBlock`] from a block header. -pub fn block_env_from_header(header: &impl BlockHeader) -> BLOCK { - let mut block = BLOCK::default(); - block.set_number(U256::from(header.number())); - block.set_beneficiary(header.beneficiary()); - block.set_timestamp(U256::from(header.timestamp())); - block.set_difficulty(header.difficulty()); - block.set_prevrandao(header.mix_hash()); - block.set_basefee(header.base_fee_per_gas().unwrap_or_default()); - block.set_gas_limit(header.gas_limit()); - block -} - /// Depending on the configured chain id and block number this should apply any specific changes /// /// - checks for prevrandao mixhash after merge /// - applies chain specifics: on Arbitrum `block.number` is the L1 block /// -/// Should be called with proper chain id (retrieved from provider if not provided), works with any -/// [`FoundryBlock`] type. -pub fn apply_chain_and_block_specific_env_changes< - N: Network, - SPEC: Into + Copy, - BLOCK: FoundryBlock, ->( - evm_env: &mut EvmEnv, +/// Should be called with proper chain id (retrieved from provider if not provided). +pub fn apply_chain_and_block_specific_env_changes( + evm_env: &mut EvmEnv, block: &N::BlockResponse, configs: NetworkConfigs, ) { - use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet}; + use NamedChain::*; if let Ok(chain) = NamedChain::try_from(evm_env.cfg_env.chain_id) { let block_number = block.header().number(); @@ -58,9 +40,8 @@ pub fn apply_chain_and_block_specific_env_changes< Mainnet => { // after merge difficulty is supplanted with prevrandao EIP-4399 if block_number >= 15_537_351u64 { - evm_env - .block_env - .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into()); + evm_env.block_env.difficulty = + evm_env.block_env.prevrandao.unwrap_or_default().into(); } return; @@ -72,7 +53,7 @@ pub fn apply_chain_and_block_specific_env_changes< // (`mixHash`) is always zero, even though bsc adopts the newer EVM // specification. This will confuse revm and causes emulation // failure. - evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into())); + evm_env.block_env.prevrandao = Some(evm_env.block_env.difficulty.into()); return; } c if c.is_arbitrum() => { @@ -85,23 +66,22 @@ pub fn apply_chain_and_block_specific_env_changes< serde_json::from_value::(l1_block_number).ok() }) { - evm_env.block_env.set_number(l1_block_number); + evm_env.block_env.number = l1_block_number.to(); } } _ => {} } } - if configs.bypass_prevrandao(evm_env.cfg_env.chain_id) - && evm_env.block_env.prevrandao().is_none() + if configs.bypass_prevrandao(evm_env.cfg_env.chain_id) && evm_env.block_env.prevrandao.is_none() { // - evm_env.block_env.set_prevrandao(Some(B256::random())); + evm_env.block_env.prevrandao = Some(B256::random()); } // if difficulty is `0` we assume it's past merge if block.header().difficulty().is_zero() { - evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into()); + evm_env.block_env.difficulty = evm_env.block_env.prevrandao.unwrap_or_default().into(); } } diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 60a2c1433817a..6bf7163d6e023 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -28,6 +28,7 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", @@ -48,6 +49,7 @@ revm = { workspace = true, default-features = false, features = [ ] } revm-inspectors.workspace = true tempo-precompiles.workspace = true +tempo-primitives.workspace = true eyre.workspace = true parking_lot.workspace = true diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index a49716ca4fbc1..d3336fcc9013c 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,9 +1,6 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; -use foundry_evm_core::{ - backend::Backend, - evm::{BlockEnvFor, EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, -}; -use revm::context::{Block, Transaction}; +use foundry_evm_core::{Env, backend::Backend}; +use revm::primitives::hardfork::SpecId; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -14,36 +11,40 @@ use revm::context::{Block, Transaction}; /// [`InspectorStack`]: super::InspectorStack #[derive(Debug, Clone)] #[must_use = "builders do nothing unless you call `build` on them"] -pub struct ExecutorBuilder { +pub struct ExecutorBuilder { /// The configuration used to build an `InspectorStack`. - stack: InspectorStackBuilder>, + stack: InspectorStackBuilder, /// The gas limit. gas_limit: Option, - /// The spec override. When `None`, the spec from `EvmEnv::cfg_env` is preserved. - spec: Option>, + /// The spec ID. + spec_id: SpecId, legacy_assertions: bool, } -impl Default for ExecutorBuilder { +impl Default for ExecutorBuilder { #[inline] fn default() -> Self { Self { stack: InspectorStackBuilder::new(), gas_limit: None, - spec: None, + spec_id: SpecId::default(), legacy_assertions: false, } } } -impl ExecutorBuilder { +impl ExecutorBuilder { + /// Create a new executor builder. + #[inline] + pub fn new() -> Self { + Self::default() + } + /// Modify the inspector stack. #[inline] pub fn inspectors( mut self, - f: impl FnOnce( - InspectorStackBuilder>, - ) -> InspectorStackBuilder>, + f: impl FnOnce(InspectorStackBuilder) -> InspectorStackBuilder, ) -> Self { self.stack = f(self.stack); self @@ -51,50 +52,42 @@ impl ExecutorBuilder { /// Sets the EVM spec to use. #[inline] - pub const fn spec_id(mut self, spec: SpecFor) -> Self { - self.spec = Some(spec); + pub fn spec_id(mut self, spec: SpecId) -> Self { + self.spec_id = spec; self } - /// Optionally sets the EVM spec. When `None`, the spec from `EvmEnv::cfg_env` is preserved. - #[inline] - pub const fn spec_id_opt(self, spec: Option>) -> Self { - if let Some(spec) = spec { self.spec_id(spec) } else { self } - } - /// Sets the executor gas limit. #[inline] - pub const fn gas_limit(mut self, gas_limit: u64) -> Self { + pub fn gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } /// Sets the `legacy_assertions` flag. #[inline] - pub const fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { + pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { self.legacy_assertions = legacy_assertions; self } /// Builds the executor as configured. #[inline] - pub fn build( - self, - mut evm_env: EvmEnvFor, - tx_env: TxEnvFor, - db: Backend, - ) -> Executor { - let Self { mut stack, gas_limit, spec, legacy_assertions, .. } = self; + pub fn build(self, env: Env, db: Backend) -> Executor { + let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { - stack.block = Some(evm_env.block_env.clone()); + stack.block = Some(env.evm_env.block_env.clone()); } if stack.gas_price.is_none() { - stack.gas_price = Some(tx_env.gas_price()); - } - let gas_limit = gas_limit.unwrap_or(evm_env.block_env.gas_limit()); - if let Some(spec) = spec { - evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec); + stack.gas_price = Some(env.tx.gas_price); } - Executor::new(db, evm_env, tx_env, stack.build(), gas_limit, legacy_assertions) + let gas_limit = gas_limit.unwrap_or(env.evm_env.block_env.gas_limit); + let env = Env::new_with_spec_id( + env.evm_env.cfg_env.clone(), + env.evm_env.block_env.clone(), + env.tx, + spec_id, + ); + Executor::new(db, env, stack.build(), gas_limit, legacy_assertions) } } diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 93e02cdba870f..d2f9858981df8 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,28 +1,26 @@ -use crate::executors::{Executor, ExecutorBuilder}; -use alloy_primitives::{Address, U256, map::HashMap}; +use crate::{ + Env, + executors::{Executor, ExecutorBuilder}, +}; +use alloy_primitives::{Address, FixedBytes, U256, address, map::HashMap}; use alloy_rpc_types::state::StateOverride; use eyre::Context; use foundry_compilers::artifacts::EvmVersion; -use foundry_config::{Chain, Config, evm_spec_id}; -use foundry_evm_core::{ - backend::Backend, - evm::{BlockEnvFor, EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, - fork::CreateFork, - opts::EvmOpts, -}; +use foundry_config::{Chain, Config, utils::evm_spec_id}; +use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_networks::NetworkConfigs; use foundry_evm_traces::TraceMode; -use revm::{context::Transaction, state::Bytecode}; +use revm::{primitives::hardfork::SpecId, state::Bytecode}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled -pub struct TracingExecutor { - executor: Executor, +pub struct TracingExecutor { + executor: Executor, } -impl TracingExecutor { +impl TracingExecutor { pub fn new( - env: (EvmEnvFor, TxEnvFor), + env: Env, fork: CreateFork, version: Option, trace_mode: TraceMode, @@ -33,12 +31,12 @@ impl TracingExecutor { let db = Backend::spawn(Some(fork))?; // configures a bare version of the evm executor: no cheatcode and log_collector inspector // is enabled, tracing will be enabled only for the targeted transaction - let mut executor = ExecutorBuilder::default() + let mut executor = ExecutorBuilder::new() .inspectors(|stack| { stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) }) - .spec_id_opt(version.map(evm_spec_id::>)) - .build(env.0, env.1, db); + .spec_id(evm_spec_id(version.unwrap_or_default())) + .build(env, db); // Apply the state overrides. if let Some(state_overrides) = state_overrides { @@ -73,7 +71,7 @@ impl TracingExecutor { } /// Returns the spec id of the executor - pub const fn spec_id(&self) -> SpecFor { + pub fn spec_id(&self) -> SpecId { self.executor.spec_id() } @@ -81,31 +79,56 @@ impl TracingExecutor { pub async fn get_fork_material( config: &mut Config, mut evm_opts: EvmOpts, - ) -> eyre::Result<(EvmEnvFor, TxEnvFor, CreateFork, Chain, NetworkConfigs)> { + ) -> eyre::Result<(Env, CreateFork, Chain, NetworkConfigs)> { evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); evm_opts.fork_block_number = config.fork_block_number; - let (evm_env, tx_env, fork_block) = - evm_opts.env::, BlockEnvFor, TxEnvFor>().await?; + let env = evm_opts.env().await?; - let fork = evm_opts.get_fork(config, evm_env.cfg_env.chain_id, fork_block).unwrap(); - let networks = evm_opts.networks.with_chain_id(evm_env.cfg_env.chain_id); + let fork = evm_opts.get_fork(config, env.evm_env.clone()).unwrap(); + let networks = evm_opts.networks.with_chain_id(env.evm_env.cfg_env.chain_id); config.labels.extend(networks.precompiles_label()); - let chain = tx_env.chain_id().unwrap().into(); - Ok((evm_env, tx_env, fork, chain, networks)) + let chain = env.tx.chain_id.unwrap().into(); + Ok((env, fork, chain, networks)) + } + + /// Processes the beacon block root by storing it in the appropriate storage slots. + pub fn process_beacon_block_root( + &mut self, + block_timestamp: u64, + beacon_root: FixedBytes<32>, + ) -> eyre::Result<()> { + const BEACON_ROOTS_ADDRESS: Address = address!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"); + const HISTORY_BUFFER_LENGTH: u64 = 8192; + + let timestamp_index = block_timestamp % HISTORY_BUFFER_LENGTH; + let root_index = timestamp_index + HISTORY_BUFFER_LENGTH; + + let timestamp_slot = U256::from(timestamp_index); + let root_slot = U256::from(root_index); + + self.set_storage_slot(BEACON_ROOTS_ADDRESS, timestamp_slot, U256::from(block_timestamp))?; + + self.set_storage_slot( + BEACON_ROOTS_ADDRESS, + root_slot, + U256::from_be_bytes(beacon_root.into()), + )?; + + Ok(()) } } -impl Deref for TracingExecutor { - type Target = Executor; +impl Deref for TracingExecutor { + type Target = Executor; fn deref(&self) -> &Self::Target { &self.executor } } -impl DerefMut for TracingExecutor { +impl DerefMut for TracingExecutor { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.executor } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 0631905c1b33f..addaddc180efd 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -898,12 +898,17 @@ impl InspectorStackRefMut<'_, FEN> { .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone()))); let mut inner = std::mem::take(self.inner); + // Save pending CREATE2 redirects so frame_end in the nested EVM doesn't consume them. + // These belong to the outer EVM's frame lifecycle and must be restored after. + let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects); + let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner }); if let Some(cheats) = self.cheatcodes.as_deref_mut() { *cheats = cheatcodes.unwrap(); } + inner.pending_create2_redirects = saved_create2_redirects; *self.inner = inner; out diff --git a/crates/evm/evm/src/inspectors/tempo_labels.rs b/crates/evm/evm/src/inspectors/tempo_labels.rs index d2726772b13f7..f0e2e2279fb17 100644 --- a/crates/evm/evm/src/inspectors/tempo_labels.rs +++ b/crates/evm/evm/src/inspectors/tempo_labels.rs @@ -6,7 +6,7 @@ use revm::{ inspector::JournalExt, interpreter::{CallInputs, CallOutcome, interpreter::EthInterpreter}, }; -use tempo_precompiles::tip20::is_tip20_prefix; +use tempo_primitives::TempoAddressExt; /// Inspector that labels TIP20 token precompile addresses with their on-chain names. /// @@ -25,9 +25,7 @@ where CTX::Journal: JournalExt, { fn call(&mut self, ctx: &mut CTX, inputs: &mut CallInputs) -> Option { - if is_tip20_prefix(inputs.target_address) - && !self.labels.contains_key(&inputs.target_address) - { + if inputs.target_address.is_tip20() && !self.labels.contains_key(&inputs.target_address) { let bytes = ctx .db_mut() .storage(inputs.target_address, tempo_precompiles::tip20::slots::NAME) diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index f9bf9c5e8471a..5e8c452c8695a 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -216,7 +216,7 @@ pub fn render_trace_arena_inner( String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } -const fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { +fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { match choice { shell::ColorChoice::Auto => revm_inspectors::ColorChoice::Auto, shell::ColorChoice::Always => revm_inspectors::ColorChoice::Always, @@ -237,7 +237,7 @@ impl TraceKind { /// /// [`Deployment`]: TraceKind::Deployment #[must_use] - pub const fn is_deployment(self) -> bool { + pub fn is_deployment(self) -> bool { matches!(self, Self::Deployment) } @@ -245,7 +245,7 @@ impl TraceKind { /// /// [`Setup`]: TraceKind::Setup #[must_use] - pub const fn is_setup(self) -> bool { + pub fn is_setup(self) -> bool { matches!(self, Self::Setup) } @@ -253,7 +253,7 @@ impl TraceKind { /// /// [`Execution`]: TraceKind::Execution #[must_use] - pub const fn is_execution(self) -> bool { + pub fn is_execution(self) -> bool { matches!(self, Self::Execution) } } @@ -321,10 +321,7 @@ pub enum TraceMode { /// /// Used by debugger. Debug, - /// Step trace with storage change recording. - /// - /// Records JUMP/JUMPDEST steps (like `Steps`) plus storage diffs on SLOAD/SSTORE. - /// Does not enable memory/stack snapshots or unfiltered opcode recording. + /// Debug trace with storage changes. RecordStateDiff, } @@ -373,9 +370,9 @@ impl TraceMode { match verbosity { 0..3 => self, 3..=4 => std::cmp::max(self, Self::Call), - // Enable step recording and state diff recording when verbosity is 5 or higher. - // This includes backtraces (JUMP/JUMPDEST steps) and storage changes. - _ => std::cmp::max(self, Self::RecordStateDiff), + // Enable step recording for backtraces when verbosity is 5 or higher. + // We need to ensure we're recording JUMP AND JUMPDEST steps. + _ => std::cmp::min(self, Self::Steps), } } @@ -383,128 +380,23 @@ impl TraceMode { if self.is_none() { None } else { - // RecordStateDiff is Steps + state diff recording, not Debug + state diff. - // It should not enable memory/stack snapshots. - // State diff recording requires all opcodes (no filter) since it needs - // SLOAD/SSTORE steps, not just JUMP/JUMPDEST. - let effective = if self.record_state_diff() { Self::Steps } else { self }; TracingInspectorConfig { record_steps: self >= Self::Steps, - record_memory_snapshots: effective >= Self::Jump, - record_stack_snapshots: if effective > Self::Steps { + record_memory_snapshots: self >= Self::Jump, + record_stack_snapshots: if self > Self::Steps { StackSnapshotType::Full } else { StackSnapshotType::None }, record_logs: true, record_state_diff: self.record_state_diff(), - record_returndata_snapshots: effective.is_debug(), - // State diff needs all opcodes recorded to capture SLOAD/SSTORE. - record_opcodes_filter: if self.record_state_diff() { - None - } else { - (effective.is_steps() || effective.is_jump() || effective.is_jump_simple()) - .then(|| { - OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST) - }) - }, + record_returndata_snapshots: self.is_debug(), + record_opcodes_filter: (self.is_steps() || self.is_jump() || self.is_jump_simple()) + .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), exclude_precompile_calls: false, - record_immediate_bytes: effective.is_debug(), + record_immediate_bytes: self.is_debug(), } .into() } } } - -#[cfg(test)] -mod tests { - use super::*; - - // -- TraceMode::with_verbosity level tests -- - - #[test] - fn verbosity_0_through_2_is_noop() { - for v in 0..=2 { - assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::None, "v={v}"); - assert_eq!(TraceMode::Call.with_verbosity(v), TraceMode::Call, "v={v}"); - assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); - } - } - - #[test] - fn verbosity_3_and_4_raises_to_call() { - for v in 3..=4 { - assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::Call, "v={v}"); - // Already above Call — must not downgrade. - assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); - assert_eq!( - TraceMode::RecordStateDiff.with_verbosity(v), - TraceMode::RecordStateDiff, - "v={v}" - ); - } - } - - #[test] - fn verbosity_5_raises_to_record_state_diff() { - assert_eq!(TraceMode::None.with_verbosity(5), TraceMode::RecordStateDiff); - assert_eq!(TraceMode::Call.with_verbosity(5), TraceMode::RecordStateDiff); - assert_eq!(TraceMode::Steps.with_verbosity(5), TraceMode::RecordStateDiff); - assert_eq!(TraceMode::Debug.with_verbosity(5), TraceMode::RecordStateDiff); - // Already at the top — stays the same. - assert_eq!(TraceMode::RecordStateDiff.with_verbosity(5), TraceMode::RecordStateDiff); - } - - // -- into_config at each verbosity level -- - - #[test] - fn config_at_verbosity_0_is_none() { - let mode = TraceMode::None.with_verbosity(0); - assert!(mode.into_config().is_none()); - } - - #[test] - fn config_at_verbosity_3_records_calls_only() { - let cfg = TraceMode::None.with_verbosity(3).into_config().unwrap(); - assert!(!cfg.record_steps, "verbosity 3 should not record steps"); - assert!(!cfg.record_state_diff, "verbosity 3 should not record state diff"); - assert!(cfg.record_logs, "verbosity 3 should record logs"); - } - - #[test] - fn config_at_verbosity_5_records_steps_and_state_diff() { - let cfg = TraceMode::None.with_verbosity(5).into_config().unwrap(); - assert!(cfg.record_steps, "verbosity 5 must record steps for backtraces"); - assert!(cfg.record_state_diff, "verbosity 5 must record state diff"); - assert!(cfg.record_logs, "verbosity 5 must record logs"); - // RecordStateDiff should NOT enable expensive debug-level features. - assert!(!cfg.record_memory_snapshots, "verbosity 5 should not record memory snapshots"); - assert_eq!( - cfg.record_stack_snapshots, - StackSnapshotType::None, - "verbosity 5 should not record stack snapshots" - ); - // State diff requires all opcodes to capture SLOAD/SSTORE, so no filter. - assert!( - cfg.record_opcodes_filter.is_none(), - "verbosity 5 needs unfiltered opcodes for state diff" - ); - } - - #[test] - fn config_debug_mode_unchanged() { - // Debug mode must still enable full recording for the debugger. - let cfg = TraceMode::Debug.into_config().unwrap(); - assert!(cfg.record_steps); - assert!(cfg.record_memory_snapshots, "Debug must record memory snapshots"); - assert_eq!( - cfg.record_stack_snapshots, - StackSnapshotType::Full, - "Debug must record full stack snapshots" - ); - assert!(cfg.record_returndata_snapshots, "Debug must record returndata"); - assert!(cfg.record_immediate_bytes, "Debug must record immediate bytes"); - assert!(cfg.record_opcodes_filter.is_none(), "Debug must record all opcodes (no filter)"); - assert!(!cfg.record_state_diff, "Debug alone should not record state diff"); - } -} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 51335b2fe84cd..9a7a237855b97 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true @@ -117,8 +118,8 @@ tempfile.workspace = true alloy-signer-local.workspace = true [features] -default = ["jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] +default = ["jemalloc", "asm-keccak"] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] diff --git a/crates/forge/assets/tempo/MailTemplate.sol b/crates/forge/assets/tempo/MailTemplate.sol index 92911c3f562a9..2caed92d47517 100644 --- a/crates/forge/assets/tempo/MailTemplate.sol +++ b/crates/forge/assets/tempo/MailTemplate.sol @@ -2,23 +2,76 @@ pragma solidity ^0.8.13; import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; +/// @title Mail +/// @notice Send mail with TIP-20 token attachments on Tempo. +/// +/// Supports two modes: +/// 1. Direct — call `sendMail()` yourself (uses `msg.sender`). +/// 2. Relayed — sign a mail off-chain and let anyone deliver it on-chain. +/// +/// Relayed mode uses the [TIP-1020] `SignatureVerifier` precompile to verify the +/// sender's Tempo signature. Unlike Ethereum's `ecrecover`, this precompile: +/// - Supports secp256k1, P256, and WebAuthn signature types +/// - Reverts on invalid signatures instead of returning `address(0)` +/// - Maintains forward compatibility with future Tempo account types +/// +/// [TIP-1020]: contract Mail { + /// @notice Emitted when a mail is sent, either directly or via a relayer. event MailSent(address indexed from, address indexed to, string message, Attachment attachment); + /// @notice A TIP-20 token transfer bundled with a mail. struct Attachment { uint256 amount; bytes32 memo; } + /// @notice The TIP-20 token used for mail attachments. ITIP20 public token; + /// @notice Per-sender nonce to prevent signature replay on relayed mails (requires T3). + mapping(address => uint256) public nonces; + constructor(ITIP20 token_) { token = token_; } + /// @notice Send mail directly (sender = msg.sender). function sendMail(address to, string memory message, Attachment memory attachment) external { token.transferFromWithMemo(msg.sender, to, attachment.amount, attachment.memo); emit MailSent(msg.sender, to, message, attachment); } + + /// @notice Send mail on behalf of `from` using their off-chain Tempo signature (requires T3). + /// @dev The sender must have pre-approved this contract to spend their tokens. + function sendMail( + address from, + address to, + string memory message, + Attachment memory attachment, + bytes calldata signature + ) external { + bytes32 hash = getDigest(from, to, message, attachment); + + // `verify()` returns `false` on signer mismatch, reverts on malformed signatures. + require(StdPrecompiles.SIGNATURE_VERIFIER.verify(from, hash, signature), "invalid signature"); + + // `recover()` returns the signer address directly, reverts on malformed signatures. + require(StdPrecompiles.SIGNATURE_VERIFIER.recover(hash, signature) == from, "invalid signature"); + + nonces[from]++; + token.transferFromWithMemo(from, to, attachment.amount, attachment.memo); + emit MailSent(from, to, message, attachment); + } + + /// @notice Compute the digest a sender must sign to authorize a relayed mail. + function getDigest(address from, address to, string memory message, Attachment memory attachment) + public + view + returns (bytes32) + { + return keccak256(abi.encode(address(this), block.chainid, from, to, message, attachment, nonces[from])); + } } diff --git a/crates/forge/assets/tempo/MailTemplate.t.sol b/crates/forge/assets/tempo/MailTemplate.t.sol index f5a9976715026..b1749db5df0bf 100644 --- a/crates/forge/assets/tempo/MailTemplate.t.sol +++ b/crates/forge/assets/tempo/MailTemplate.t.sol @@ -8,6 +8,7 @@ import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; import {StdTokens} from "tempo-std/StdTokens.sol"; import {Mail} from "../src/Mail.sol"; +/// @notice Tests for direct mail sending (no signature verification). contract MailTest is Test { ITIP20 public token; Mail public mail; @@ -15,7 +16,7 @@ contract MailTest is Test { address public constant ALICE = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); address public constant BOB = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); - function setUp() public { + function setUp() public virtual { address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); @@ -35,11 +36,10 @@ contract MailTest is Test { Mail.Attachment memory attachment = Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); - vm.prank(ALICE); + vm.startPrank(ALICE); token.approve(address(mail), attachment.amount); - - vm.prank(ALICE); - mail.sendMail(BOB, "Hello Alice, this is a unit test mail.", attachment); + mail.sendMail(BOB, "Hello Bob, here is your invoice.", attachment); + vm.stopPrank(); assertEq(token.balanceOf(BOB), attachment.amount); assertEq(token.balanceOf(ALICE), 100_000 * 10 ** token.decimals() - attachment.amount); @@ -62,3 +62,114 @@ contract MailTest is Test { assertEq(token.balanceOf(ALICE), mintAmount - sendAmount); } } + +/// @notice Tests for relayed mail using the TIP-1020 SignatureVerifier precompile (requires T3). +/// forge-config: default.hardfork = "tempo:T3" +contract MailRelayTest is MailTest { + // secp256k1 keys (used by vm.sign / vm.addr) + uint256 internal constant ALICE_PK = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + uint256 internal constant BOB_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + + // P256 key (used by vm.signP256 / vm.publicKeyP256) + uint256 internal constant CAROL_P256_PK = 0x1; + address internal CAROL; + bytes32 internal carolPubX; + bytes32 internal carolPubY; + + uint256 internal constant P256_ORDER = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + uint256 internal constant P256N_HALF = 0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8; + + function setUp() public override { + super.setUp(); + + // Derive P256 public key and Tempo address for Carol + (uint256 x, uint256 y) = vm.publicKeyP256(CAROL_P256_PK); + carolPubX = bytes32(x); + carolPubY = bytes32(y); + CAROL = address(uint160(uint256(keccak256(abi.encodePacked(x, y))))); + } + + /// @notice Relayed send with a secp256k1 signature — Alice signs, Bob delivers. + function test_SendMailWithSecp256k1Signature() public { + token.mint(ALICE, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = + Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); + + vm.prank(ALICE); + token.approve(address(mail), attachment.amount); + + string memory message = "Hello Bob, here is your invoice."; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, digest); + + vm.prank(BOB); + mail.sendMail(ALICE, BOB, message, attachment, abi.encodePacked(r, s, v)); + + assertEq(token.balanceOf(BOB), attachment.amount); + assertEq(mail.nonces(ALICE), 1); + } + + /// @notice Relayed send with a P256 signature — Carol signs, Bob delivers. + function test_SendMailWithP256Signature() public { + token.mint(CAROL, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = + Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); + + vm.prank(CAROL); + token.approve(address(mail), attachment.amount); + + string memory message = "Hello Bob, signed with P256."; + bytes32 digest = mail.getDigest(CAROL, BOB, message, attachment); + (bytes32 r, bytes32 s) = vm.signP256(CAROL_P256_PK, digest); + s = _normalizeP256S(s); + + bytes memory sig = abi.encodePacked(uint8(0x01), r, s, carolPubX, carolPubY, uint8(0)); + + vm.prank(BOB); + mail.sendMail(CAROL, BOB, message, attachment, sig); + + assertEq(token.balanceOf(BOB), attachment.amount); + assertEq(mail.nonces(CAROL), 1); + } + + /// @notice Replaying the same signature fails (nonce incremented). + function test_ReplayReverts() public { + token.mint(ALICE, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = Mail.Attachment({amount: 50 * 10 ** token.decimals(), memo: "tip"}); + + vm.prank(ALICE); + token.approve(address(mail), attachment.amount * 2); + + string memory message = "tip"; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, digest); + bytes memory sig = abi.encodePacked(r, s, v); + + mail.sendMail(ALICE, BOB, message, attachment, sig); + + vm.expectRevert(); + mail.sendMail(ALICE, BOB, message, attachment, sig); + } + + /// @notice Submitting Bob's signature as Alice's fails. + function test_WrongSignerReverts() public { + Mail.Attachment memory attachment = Mail.Attachment({amount: 100, memo: "fake"}); + + string memory message = "spoofed"; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(BOB_PK, digest); + + vm.expectRevert("invalid signature"); + mail.sendMail(ALICE, BOB, message, attachment, abi.encodePacked(r, s, v)); + } + + /// @dev Normalize P256 s to low-s form (required by the precompile). + function _normalizeP256S(bytes32 s) internal pure returns (bytes32) { + uint256 sVal = uint256(s); + if (sVal > P256N_HALF) return bytes32(P256_ORDER - sVal); + return s; + } +} diff --git a/crates/forge/src/args.rs b/crates/forge/src/args.rs index 4f55f028a3690..c5779aebbcbc7 100644 --- a/crates/forge/src/args.rs +++ b/crates/forge/src/args.rs @@ -6,7 +6,7 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; use foundry_cli::utils; -use foundry_common::shell; +use foundry_common::{sh_warn, shell}; use foundry_evm::inspectors::cheatcodes::{ForgeContext, set_execution_context}; /// Run the `forge` command line interface. @@ -99,7 +99,9 @@ pub fn run_command(args: Forge) -> Result<()> { ForgeSubcommand::Clean { root } => { let config = utils::load_config_with_root(root.as_deref())?; let project = config.project()?; - config.cleanup(&project)?; + for warning in config.cleanup(&project)? { + let _ = sh_warn!("{warning}"); + } Ok(()) } ForgeSubcommand::Snapshot(cmd) => { diff --git a/crates/forge/src/cmd/cache.rs b/crates/forge/src/cmd/cache.rs index 432d7c11c19dd..0c1f22f2e6de6 100644 --- a/crates/forge/src/cmd/cache.rs +++ b/crates/forge/src/cmd/cache.rs @@ -4,6 +4,7 @@ use clap::{ builder::{PossibleValuesParser, TypedValueParser}, }; use eyre::Result; +use foundry_common::sh_warn; use foundry_config::{Chain, Config, NamedChain, cache}; use std::{ffi::OsStr, str::FromStr}; use strum::VariantNames; @@ -63,10 +64,13 @@ impl CleanArgs { clean_chain_cache(chain, blocks.clone(), etherscan)? } ChainOrAll::All => { - if etherscan { - Config::clean_foundry_etherscan_cache()?; + let warnings = if etherscan { + Config::clean_foundry_etherscan_cache()? } else { Config::clean_foundry_cache()? + }; + for warning in warnings { + let _ = sh_warn!("{warning}"); } } } @@ -128,17 +132,24 @@ impl FromStr for ChainOrAll { fn clean_chain_cache(chain: impl Into, blocks: Vec, etherscan: bool) -> Result<()> { let chain = chain.into(); + let mut warnings = Vec::new(); if blocks.is_empty() { - Config::clean_foundry_etherscan_chain_cache(chain)?; + warnings.extend(Config::clean_foundry_etherscan_chain_cache(chain)?); if etherscan { + for warning in warnings { + let _ = sh_warn!("{warning}"); + } return Ok(()); } - Config::clean_foundry_chain_cache(chain)?; + warnings.extend(Config::clean_foundry_chain_cache(chain)?); } else { for block in blocks { - Config::clean_foundry_block_cache(chain, block)?; + warnings.extend(Config::clean_foundry_block_cache(chain, block)?); } } + for warning in warnings { + let _ = sh_warn!("{warning}"); + } Ok(()) } diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index 49716146c456b..fac66727c9d99 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -53,8 +53,8 @@ impl ConfigArgs { } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index 4eedd0f8f6b08..9c1fb32775019 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -9,7 +9,6 @@ use foundry_common::{ find_matching_contract_artifact, find_target_path, shell, }; use foundry_compilers::{ - ProjectCompileOutput, artifacts::{ StorageLayout, output_selection::{ @@ -19,11 +18,9 @@ use foundry_compilers::{ }, solc::SolcLanguage, }; -use path_slash::PathExt; use regex::Regex; use serde_json::{Map, Value}; -use solar::sema::interface::source_map::FileName; -use std::{collections::BTreeMap, fmt, ops::ControlFlow, path::Path, str::FromStr, sync::LazyLock}; +use std::{collections::BTreeMap, fmt, str::FromStr, sync::LazyLock}; /// CLI arguments for `forge inspect`. #[derive(Clone, Debug, Parser)] @@ -79,13 +76,8 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let target_path = find_target_path(&project, &contract)?; - if field == ContractArtifactField::Linearization && !is_solidity_source(&target_path) { - eyre::bail!( - "linearization inspection is only supported for Solidity contracts (.sol targets)" - ); - } let compiler = ProjectCompiler::new().quiet(true); + let target_path = find_target_path(&project, &contract)?; let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact @@ -166,20 +158,16 @@ impl InspectArgs { .collect(); if shell::is_json() { return print_json(&all_libs); + } else { + sh_println!( + "Dynamically linked libraries:\n{}", + all_libs + .iter() + .map(|v| format!(" {v}")) + .collect::>() + .join("\n") + )?; } - sh_println!( - "Dynamically linked libraries:\n{}", - all_libs.iter().map(|v| format!(" {v}")).collect::>().join("\n") - )?; - } - ContractArtifactField::Linearization => { - print_linearization( - &mut output, - project.root(), - &target_path, - contract.name(), - wrap, - )?; } }; @@ -251,15 +239,15 @@ fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> { for func in abi.functions.values().flatten() { let selector = func.selector().to_string(); let state_mut = func.state_mutability.as_json_str(); - let func_sig = if func.outputs.is_empty() { - format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) - } else { + let func_sig = if !func.outputs.is_empty() { format!( "{}({}) {state_mut} returns ({})", func.name, get_ty_sig(&func.inputs), get_ty_sig(&func.outputs) ) + } else { + format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) }; table.add_row(["function", &func_sig, &selector]); } @@ -370,7 +358,7 @@ fn print_method_identifiers( headers, |table| { for (method, identifier) in method_identifiers { - table.add_row([method.as_str(), identifier.as_str()]); + table.add_row([method, identifier]); } }, should_wrap, @@ -418,111 +406,6 @@ fn print_table( Ok(()) } -fn print_linearization( - output: &mut ProjectCompileOutput, - root: &Path, - target_path: &Path, - target_name: Option<&str>, - should_wrap: bool, -) -> Result<()> { - let mut chain = Vec::new(); - let mut lowered = false; - let compiler = output.parser_mut().solc_mut().compiler_mut(); - compiler.enter_mut(|compiler| -> Result<()> { - let Ok(ControlFlow::Continue(())) = compiler.lower_asts() else { return Ok(()) }; - lowered = true; - - let hir = &compiler.gcx().hir; - let matching_contracts = hir - .contract_ids() - .filter(|id| { - let contract = hir.contract(*id); - if let Some(target_name) = target_name - && contract.name.as_str() != target_name - { - return false; - } - - matches!( - &hir.source(contract.source).file.name, - FileName::Real(path) if path == target_path - ) - }) - .collect::>(); - - let target_contract = match matching_contracts.as_slice() { - [id] => *id, - [] => { - if let Some(target_name) = target_name { - eyre::bail!( - "Could not find contract `{target_name}` in `{}`", - target_path.display() - ); - } - eyre::bail!("Could not find contract in `{}`", target_path.display()); - } - _ => { - eyre::bail!( - "Multiple contracts found in the same file, please specify the target : or " - ); - } - }; - - for (order, base_id) in hir.contract(target_contract).linearized_bases.iter().enumerate() { - let contract = hir.contract(*base_id); - let source = hir.source(contract.source); - let FileName::Real(path) = &source.file.name else { continue }; - let path = path.strip_prefix(root).unwrap_or(path); - chain.push(( - order, - path.to_slash_lossy().into_owned(), - contract.name.as_str().to_string(), - )); - } - - Ok(()) - })?; - - // `compiler.sess()` inside of `ProjectCompileOutput` is built with `with_buffer_emitter`. - let diags = compiler.sess().dcx.emitted_diagnostics().unwrap(); - if compiler.sess().dcx.has_errors().is_err() { - eyre::bail!("{diags}"); - } else { - let _ = sh_eprint!("{diags}"); - } - if !lowered { - eyre::bail!( - "unable to inspect linearization: failed to lower Solidity ASTs for `{}`", - target_path.display() - ); - } - - if shell::is_json() { - let contracts = chain - .into_iter() - .map(|(order, source, contract)| { - serde_json::json!({ - "order": order, - "source": source, - "contract": contract, - }) - }) - .collect::>(); - return print_json(&contracts); - } - - let headers = vec![Cell::new("Order"), Cell::new("Source"), Cell::new("Contract")]; - print_table( - headers, - |table| { - for (order, source, contract) in &chain { - table.add_row([order.to_string(), source.clone(), contract.clone()]); - } - }, - should_wrap, - ) -} - /// Contract level output selection #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ContractArtifactField { @@ -545,7 +428,6 @@ pub enum ContractArtifactField { Events, StandardJson, Libraries, - Linearization, } macro_rules! impl_value_enum { @@ -630,9 +512,6 @@ impl_value_enum! { Events => "events" | "ev", StandardJson => "standardJson" | "standard-json" | "standard_json", Libraries => "libraries" | "lib" | "libs", - Linearization => "linearization" | "linearizedInheritance" - | "linearized-inheritance" | "linearized_inheritance" - | "linearizedBases" | "linearized-bases" | "linearized_bases", } } @@ -666,9 +545,6 @@ impl TryFrom for ContractOutputSelection { Err(eyre!("StandardJson is not supported for ContractOutputSelection")) } Caf::Libraries => Err(eyre!("Libraries is not supported for ContractOutputSelection")), - Caf::Linearization => { - Err(eyre!("Linearization is not supported for ContractOutputSelection")) - } } } } @@ -679,7 +555,8 @@ impl PartialEq for ContractArtifactField { type Eos = EvmOutputSelection; matches!( (self, other), - (Self::Abi | Self::Events | Self::Errors, Cos::Abi) + (Self::Abi | Self::Events, Cos::Abi) + | (Self::Errors, Cos::Abi) | (Self::Bytecode, Cos::Evm(Eos::ByteCode(_))) | (Self::DeployedBytecode, Cos::Evm(Eos::DeployedByteCode(_))) | (Self::Assembly | Self::AssemblyOptimized, Cos::Evm(Eos::Assembly)) @@ -708,11 +585,7 @@ impl ContractArtifactField { pub const fn can_skip_field(&self) -> bool { matches!( self, - Self::Bytecode - | Self::DeployedBytecode - | Self::StandardJson - | Self::Libraries - | Self::Linearization + Self::Bytecode | Self::DeployedBytecode | Self::StandardJson | Self::Libraries ) } } @@ -759,10 +632,6 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result bool { - path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| ext.eq_ignore_ascii_case("sol")) -} - fn missing_error(field: &str) -> eyre::Error { eyre!( "{field} missing from artifact; \ @@ -793,14 +662,6 @@ mod tests { .to_string() .eq("Libraries is not supported for ContractOutputSelection") ); - } else if field == ContractArtifactField::Linearization { - let selection: Result = field.try_into(); - assert!( - selection - .unwrap_err() - .to_string() - .eq("Linearization is not supported for ContractOutputSelection") - ); } else { let selection: ContractOutputSelection = field.try_into().unwrap(); assert_eq!(field, selection); diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index 3fcad958a6cc0..b7aa4ca294098 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -197,7 +197,7 @@ impl DependencyInstallOpts { let rev = git.get_rev(tag_or_branch, &path)?; dep_id = Some(DepIdentifier::Branch { - name: tag_or_branch.clone(), + name: tag_or_branch.to_string(), rev, r#override: false, }); @@ -327,10 +327,8 @@ impl Installer<'_> { dep.tag = self.last_tag(path); } - // checkout the tag if necessary, using recursive checkout to properly clean up - // nested submodules that may exist on the default branch but not on the target tag. - // See: https://github.com/foundry-rs/foundry/issues/13688 - self.git_checkout(&dep, path, true)?; + // checkout the tag if necessary + self.git_checkout(&dep, path, false)?; trace!("updating dependency submodules recursively"); self.git.root(path).submodule_update( @@ -341,51 +339,12 @@ impl Installer<'_> { std::iter::empty::(), )?; - // remove nested .git directories from submodules before removing the top-level .git - Self::remove_nested_git_dirs(path)?; - // remove git artifacts fs::remove_dir_all(path.join(".git"))?; Ok(dep.tag) } - /// Recursively removes `.git` files/directories from nested submodules within `root`. - /// - /// Submodules typically have a `.git` file (not a directory) pointing to the parent's - /// `.git/modules/` directory. This cleans those up so the result is a plain folder tree. - fn remove_nested_git_dirs(root: &Path) -> Result<()> { - Self::remove_nested_git_dirs_inner(root, root) - } - - fn remove_nested_git_dirs_inner(root: &Path, dir: &Path) -> Result<()> { - let entries = match std::fs::read_dir(dir) { - Ok(entries) => entries, - Err(_) => return Ok(()), - }; - for entry in entries { - let entry = entry?; - let ft = entry.file_type()?; - - // never follow symlinks - if ft.is_symlink() { - continue; - } - - let path = entry.path(); - if path.file_name() == Some(".git".as_ref()) && path.parent() != Some(root) { - if ft.is_dir() { - fs::remove_dir_all(&path)?; - } else { - fs::remove_file(&path)?; - } - } else if ft.is_dir() { - Self::remove_nested_git_dirs_inner(root, &path)?; - } - } - Ok(()) - } - /// Installs the dependency as new submodule. /// /// This will add the git submodule to the given dir, initialize it and checkout the tag if @@ -567,7 +526,7 @@ impl Installer<'_> { sh_println!("[{i}] {c} selected")?; return Ok(c.clone()); } - _ => {} + _ => continue, } } } diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index da300c429e37e..8698adaddacfa 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -23,7 +23,7 @@ use foundry_cli::{ use foundry_common::{EmptyTestFilter, TestFunctionExt, compile::ProjectCompiler, fs, shell}; use foundry_compilers::{ ProjectCompileOutput, - artifacts::{Libraries, output_selection::OutputSelection}, + artifacts::output_selection::OutputSelection, compilers::{ Language, multi::{MultiCompiler, MultiCompilerLanguage}, @@ -40,16 +40,11 @@ use foundry_config::{ }; use foundry_debugger::Debugger; use foundry_evm::{ - core::evm::{ - BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, SpecFor, TempoEvmNetwork, - TxEnvFor, - }, opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, }; use rand::Rng; use regex::Regex; -use revm::context::Transaction; use std::{ collections::{BTreeMap, BTreeSet}, fmt::Write, @@ -320,11 +315,14 @@ impl TestArgs { let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; - // Determine executor verbosity. + // Determine print verbosity and executor verbosity. + let verbosity = evm_opts.verbosity; if (self.gas_report && evm_opts.verbosity < 3) || self.flamegraph || self.flamechart { evm_opts.verbosity = 3; } + let env = evm_opts.env().await?; + // Enable internal tracing for more informative flamegraph. if should_draw && !self.decode_internal { self.decode_internal = true; @@ -339,44 +337,23 @@ impl TestArgs { InternalTraceMode::None }; - // Auto-detect network from fork chain ID when not explicitly configured. - evm_opts.infer_network_from_fork().await; - - // Dispatch based on network type. - let (libraries, mut outcome) = if evm_opts.networks.is_tempo() { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - } else if evm_opts.networks.is_optimism() { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - } else { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - }; + // Prepare the test builder. + let config = Arc::new(config); + let runner = MultiContractRunnerBuilder::new(config.clone()) + .set_debug(should_debug) + .set_decode_internal(decode_internal) + .initial_balance(evm_opts.initial_balance) + .evm_spec(config.evm_spec_id()) + .sender(evm_opts.sender) + .with_fork(evm_opts.get_fork(&config, env.evm_env.clone())) + .enable_isolation(evm_opts.isolate) + .networks(evm_opts.networks) + .fail_fast(self.fail_fast) + .set_coverage(coverage) + .build::(output, env, evm_opts)?; + + let libraries = runner.libraries.clone(); + let mut outcome = self.run_tests_inner(runner, config, verbosity, filter, output).await?; if should_draw { let (suite_name, test_name, mut test_result) = @@ -450,43 +427,10 @@ impl TestArgs { Ok(outcome) } - /// Build the test runner and execute tests for a specific network type. - #[allow(clippy::too_many_arguments)] - async fn build_and_run_tests( - &self, - config: Config, - evm_opts: EvmOpts, - output: &ProjectCompileOutput, - filter: &ProjectPathsAwareFilter, - coverage: bool, - should_debug: bool, - decode_internal: InternalTraceMode, - ) -> eyre::Result<(Libraries, TestOutcome)> { - let verbosity = evm_opts.verbosity; - let (evm_env, tx_env, fork_block) = - evm_opts.env::, BlockEnvFor, TxEnvFor>().await?; - - let config = Arc::new(config); - let runner = MultiContractRunnerBuilder::new(config.clone()) - .set_debug(should_debug) - .set_decode_internal(decode_internal) - .initial_balance(evm_opts.initial_balance) - .sender(evm_opts.sender) - .with_fork(evm_opts.get_fork(&config, evm_env.cfg_env.chain_id, fork_block)) - .enable_isolation(evm_opts.isolate) - .fail_fast(self.fail_fast) - .set_coverage(coverage) - .build::(output, evm_env, tx_env, evm_opts)?; - - let libraries = runner.libraries.clone(); - let outcome = self.run_tests_inner(runner, config, verbosity, filter, output).await?; - Ok((libraries, outcome)) - } - /// Run all tests that matches the filter predicate from a test runner - async fn run_tests_inner( + async fn run_tests_inner( &self, - mut runner: MultiContractRunner, + mut runner: MultiContractRunner, config: Arc, verbosity: u8, filter: &ProjectPathsAwareFilter, @@ -505,11 +449,10 @@ impl TestArgs { let num_filtered = runner.matching_test_functions(filter).count(); if num_filtered == 0 { - let total_tests = if filter.is_empty() { - num_filtered - } else { - runner.matching_test_functions(&EmptyTestFilter::default()).count() - }; + let mut total_tests = num_filtered; + if !filter.is_empty() { + total_tests = runner.matching_test_functions(&EmptyTestFilter::default()).count(); + } if total_tests == 0 { sh_println!( "No tests found in project! Forge looks for functions that start with `test`" @@ -527,7 +470,7 @@ impl TestArgs { } sh_warn!("{msg}")?; } - return Ok(TestOutcome::empty(Some(runner.known_contracts.clone()), false)); + return Ok(TestOutcome::empty(Some(runner), false)); } if num_filtered != 1 && (self.debug || self.flamegraph || self.flamechart) { @@ -569,19 +512,17 @@ impl TestArgs { } } sh_println!("{}", serde_json::to_string(&results)?)?; - let kc = runner.known_contracts.clone(); - return Ok(TestOutcome::new(Some(kc), results, self.allow_failure, fuzz_seed)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } if self.junit { let results = runner.test_collect(filter)?; sh_println!("{}", junit_xml_report(&results, verbosity).to_string()?)?; - let kc = runner.known_contracts.clone(); - return Ok(TestOutcome::new(Some(kc), results, self.allow_failure, fuzz_seed)); + return Ok(TestOutcome::new(Some(runner), results, self.allow_failure, fuzz_seed)); } let remote_chain = - if runner.fork.is_some() { runner.tx_env.chain_id().map(Into::into) } else { None }; + if runner.fork.is_some() { runner.env.tx.chain_id.map(Into::into) } else { None }; let known_contracts = runner.known_contracts.clone(); let libraries = runner.libraries.clone(); @@ -609,8 +550,7 @@ impl TestArgs { let mut builder = CallTraceDecoderBuilder::new() .with_known_contracts(&known_contracts) .with_label_disabled(self.disable_labels) - .with_verbosity(verbosity) - .with_chain_id(remote_chain.map(|c| c.id())); + .with_verbosity(verbosity); // Signatures are of no value for gas reports. if !self.gas_report { builder = @@ -742,9 +682,7 @@ impl TestArgs { } } - // Extract and display backtrace for failed tests when verbosity >= 3. - // At verbosity 3-4 backtraces show contract/function names only. - // At verbosity 5 backtraces include source file locations. + // Extract and display backtrace for failed tests when verbosity >= 3 if !silent && result.status.is_failure() && verbosity >= 3 @@ -828,9 +766,14 @@ impl TestArgs { .iter() .filter_map(|(k, v)| { previous_snapshots.get(k).and_then(|previous_snapshot| { - (previous_snapshot != v).then(|| { - (k.clone(), (previous_snapshot.clone(), v.clone())) - }) + if previous_snapshot != v { + Some(( + k.clone(), + (previous_snapshot.clone(), v.clone()), + )) + } else { + None + } }) }) .collect(); @@ -920,10 +863,7 @@ impl TestArgs { // Reattach the task. match handle.await { - Ok(result) => { - let runner = result?; - outcome.known_contracts = Some(runner.known_contracts); - } + Ok(result) => outcome.runner = Some(result?), Err(e) => match e.try_into_panic() { Ok(payload) => std::panic::resume_unwind(payload), Err(e) => return Err(e.into()), @@ -954,7 +894,7 @@ impl TestArgs { } /// Returns whether `BuildArgs` was configured with `--watch` - pub const fn is_watch(&self) -> bool { + pub fn is_watch(&self) -> bool { self.watch.watch.is_some() } @@ -993,7 +933,7 @@ impl Provider for TestArgs { if let Some(etherscan_api_key) = self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) { - dict.insert("etherscan_api_key".to_string(), etherscan_api_key.clone().into()); + dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } if self.show_progress { @@ -1005,10 +945,7 @@ impl Provider for TestArgs { } /// Lists all matching tests -fn list( - runner: MultiContractRunner, - filter: &ProjectPathsAwareFilter, -) -> Result { +fn list(runner: MultiContractRunner, filter: &ProjectPathsAwareFilter) -> Result { let results = runner.list(filter); if shell::is_json() { @@ -1022,7 +959,7 @@ fn list( } } } - Ok(TestOutcome::empty(Some(runner.known_contracts), false)) + Ok(TestOutcome::empty(Some(runner), false)) } /// Load persisted filter (with last test run failures) from file. @@ -1047,7 +984,7 @@ fn persist_run_failures(config: &Config, outcome: &TestOutcome) { let mut failures = outcome.failures().peekable(); while let Some((test_name, _)) = failures.next() { if test_name.is_any_test() - && let Some(test_match) = test_name.split('(').next() + && let Some(test_match) = test_name.split("(").next() { filter.push_str(test_match); if failures.peek().is_some() { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 88bbc6156c812..12e2ce39691fe 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -10,6 +10,7 @@ use eyre::Result; use foundry_cli::opts::configure_pcx_from_compile_output; use foundry_common::{ ContractsByArtifact, ContractsByArtifactBuilder, TestFunctionExt, get_contract_name, + shell::verbosity, }; use foundry_compilers::{ Artifact, ArtifactId, Compiler, ProjectCompileOutput, @@ -17,8 +18,8 @@ use foundry_compilers::{ }; use foundry_config::{Config, InlineConfig}; use foundry_evm::{ + Env, backend::Backend, - core::evm::{EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, decode::RevertDecoder, executors::{EarlyExit, Executor, ExecutorBuilder}, fork::CreateFork, @@ -27,13 +28,13 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; - +use foundry_evm_networks::NetworkConfigs; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; +use revm::primitives::hardfork::SpecId; use std::{ borrow::Borrow, collections::BTreeMap, - ops::{Deref, DerefMut}, path::Path, sync::{Arc, mpsc}, time::Instant, @@ -50,7 +51,7 @@ pub type DeployableContracts = BTreeMap; /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. #[derive(Clone, Debug)] -pub struct MultiContractRunner { +pub struct MultiContractRunner { /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, @@ -71,24 +72,24 @@ pub struct MultiContractRunner { pub fork: Option, /// The base configuration for the test runner. - pub tcfg: TestRunnerConfig, + pub tcfg: TestRunnerConfig, } -impl Deref for MultiContractRunner { - type Target = TestRunnerConfig; +impl std::ops::Deref for MultiContractRunner { + type Target = TestRunnerConfig; fn deref(&self) -> &Self::Target { &self.tcfg } } -impl DerefMut for MultiContractRunner { +impl std::ops::DerefMut for MultiContractRunner { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.tcfg } } -impl MultiContractRunner { +impl MultiContractRunner { /// Returns an iterator over all contracts that match the filter. pub fn matching_contracts<'a: 'b, 'b>( &'a self, @@ -240,17 +241,17 @@ impl MultiContractRunner { &self, artifact_id: &ArtifactId, contract: &TestContract, - db: &Backend, + db: &Backend, filter: &dyn TestFilter, tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, ) -> SuiteResult { let identifier = artifact_id.identifier(); - let span_name = if enabled!(tracing::Level::TRACE) { - identifier.as_str() - } else { - get_contract_name(&identifier) - }; + let mut span_name = identifier.as_str(); + + if !enabled!(tracing::Level::TRACE) { + span_name = get_contract_name(&identifier); + } let span = debug_span!("suite", name = %span_name); let span_local = span.clone(); let _guard = span_local.enter(); @@ -284,7 +285,7 @@ impl MultiContractRunner { /// /// This is modified after instantiation through inline config. #[derive(Clone, Debug)] -pub struct TestRunnerConfig { +pub struct TestRunnerConfig { /// Project config. pub config: Arc, /// Inline configuration. @@ -293,11 +294,9 @@ pub struct TestRunnerConfig { /// EVM configuration. pub evm_opts: EvmOpts, /// EVM environment. - pub evm_env: EvmEnvFor, - /// Transaction environment. - pub tx_env: TxEnvFor, + pub env: Env, /// EVM version. - pub spec_id: SpecFor, + pub spec_id: SpecId, /// The address which will be used to deploy the initial contracts and send all transactions. pub sender: Address, @@ -309,11 +308,13 @@ pub struct TestRunnerConfig { pub decode_internal: InternalTraceMode, /// Whether to enable call isolation. pub isolation: bool, + /// Networks with enabled features. + pub networks: NetworkConfigs, /// Whether to exit early on test failure or if test run interrupted. pub early_exit: EarlyExit, } -impl TestRunnerConfig { +impl TestRunnerConfig { /// Reconfigures all fields using the given `config`. /// This is for example used to override the configuration with inline config. pub fn reconfigure_with(&mut self, config: Arc) { @@ -321,7 +322,7 @@ impl TestRunnerConfig { self.spec_id = config.evm_spec_id(); self.sender = config.sender; - self.evm_opts.networks = config.networks; + self.networks = config.networks; self.isolation = config.isolate; // Specific to Forge, not present in config. @@ -338,7 +339,7 @@ impl TestRunnerConfig { } /// Configures the given executor with this configuration. - pub fn configure_executor(&self, executor: &mut Executor) { + pub fn configure_executor(&self, executor: &mut Executor) { // TODO: See above let inspector = executor.inspector_mut(); @@ -350,7 +351,7 @@ impl TestRunnerConfig { inspector.tracing(self.trace_mode()); inspector.collect_line_coverage(self.line_coverage); inspector.enable_isolation(self.isolation); - inspector.networks(self.evm_opts.networks); + inspector.networks(self.networks); // inspector.set_create2_deployer(self.evm_opts.create2_deployer); // executor.env_mut().clone_from(&self.env); @@ -365,16 +366,15 @@ impl TestRunnerConfig { known_contracts: ContractsByArtifact, analysis: Arc, artifact_id: &ArtifactId, - db: Backend, - ) -> Executor { + db: Backend, + ) -> Executor { let cheats_config = Arc::new(CheatsConfig::new( &self.config, self.evm_opts.clone(), Some(known_contracts), Some(artifact_id.clone()), - None, )); - ExecutorBuilder::default() + ExecutorBuilder::new() .inspectors(|stack| { stack .logs(self.config.live_logs) @@ -382,14 +382,14 @@ impl TestRunnerConfig { .trace_mode(self.trace_mode()) .line_coverage(self.line_coverage) .enable_isolation(self.isolation) - .networks(self.evm_opts.networks) + .networks(self.networks) .create2_deployer(self.evm_opts.create2_deployer) .set_analysis(analysis) }) .spec_id(self.spec_id) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.evm_env.clone(), self.tx_env.clone(), db) + .build(self.env.clone(), db) } fn trace_mode(&self) -> TraceMode { @@ -397,6 +397,7 @@ impl TestRunnerConfig { .with_debug(self.debug) .with_decode_internal(self.decode_internal) .with_verbosity(self.evm_opts.verbosity) + .with_state_changes(verbosity() > 4) } } @@ -409,6 +410,8 @@ pub struct MultiContractRunnerBuilder { pub sender: Option
, /// The initial balance for each one of the deployed smart contracts pub initial_balance: U256, + /// The EVM spec to use + pub evm_spec: Option, /// The fork to use at launch pub fork: Option, /// Project config. @@ -421,6 +424,8 @@ pub struct MultiContractRunnerBuilder { pub decode_internal: InternalTraceMode, /// Whether to enable call isolation pub isolation: bool, + /// Networks with enabled features. + pub networks: NetworkConfigs, /// Whether to exit early on test failure. pub fail_fast: bool, } @@ -431,64 +436,75 @@ impl MultiContractRunnerBuilder { config, sender: Default::default(), initial_balance: Default::default(), + evm_spec: Default::default(), fork: Default::default(), line_coverage: Default::default(), debug: Default::default(), isolation: Default::default(), decode_internal: Default::default(), + networks: Default::default(), fail_fast: false, } } - pub const fn sender(mut self, sender: Address) -> Self { + pub fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self } - pub const fn initial_balance(mut self, initial_balance: U256) -> Self { + pub fn initial_balance(mut self, initial_balance: U256) -> Self { self.initial_balance = initial_balance; self } + pub fn evm_spec(mut self, spec: SpecId) -> Self { + self.evm_spec = Some(spec); + self + } + pub fn with_fork(mut self, fork: Option) -> Self { self.fork = fork; self } - pub const fn set_coverage(mut self, enable: bool) -> Self { + pub fn set_coverage(mut self, enable: bool) -> Self { self.line_coverage = enable; self } - pub const fn set_debug(mut self, enable: bool) -> Self { + pub fn set_debug(mut self, enable: bool) -> Self { self.debug = enable; self } - pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { + pub fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { self.decode_internal = mode; self } - pub const fn fail_fast(mut self, fail_fast: bool) -> Self { + pub fn fail_fast(mut self, fail_fast: bool) -> Self { self.fail_fast = fail_fast; self } - pub const fn enable_isolation(mut self, enable: bool) -> Self { + pub fn enable_isolation(mut self, enable: bool) -> Self { self.isolation = enable; self } + pub fn networks(mut self, networks: NetworkConfigs) -> Self { + self.networks = networks; + self + } + /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm - pub fn build>( + pub fn build>( self, output: &ProjectCompileOutput, - evm_env: EvmEnvFor, - tx_env: TxEnvFor, + env: Env, evm_opts: EvmOpts, - ) -> Result> { + ) -> Result { let root = &self.config.root; let contracts = output .artifact_ids() @@ -551,7 +567,8 @@ impl MultiContractRunnerBuilder { dcx.set_flags_mut(|f| f.track_diagnostics = false); // Populate solar's global context by parsing and lowering the sources. - let files: Vec<_> = output.output().sources.as_ref().keys().cloned().collect(); + let files: Vec<_> = + output.output().sources.as_ref().keys().map(|path| path.to_path_buf()).collect(); analysis.enter_mut(|compiler| -> Result<()> { let mut pcx = compiler.parse(); @@ -584,15 +601,15 @@ impl MultiContractRunnerBuilder { tcfg: TestRunnerConfig { evm_opts, - evm_env, - tx_env, - spec_id: self.config.evm_spec_id(), + env, + spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), sender: self.sender.unwrap_or(self.config.sender), line_coverage: self.line_coverage, debug: self.debug, decode_internal: self.decode_internal, inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, + networks: self.networks, early_exit: EarlyExit::new(self.fail_fast), config: self.config, }, diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index 35b785cb111de..f95f865f92bfb 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -591,51 +591,6 @@ async fn correctly_sync_dep_with_multiple_version() { assert_eq!(solday_v_245.rev(), submod_solday_v_245.rev()); } -// Regression test: `forge install --no-git` should clean up nested submodule contents -// when installing a tag that does not use submodules for its dependencies. -// https://github.com/foundry-rs/foundry/issues/13688 -forgetest!(flaky_install_no_git_cleans_nested_submodules, |prj, cmd| { - cmd.git_init(); - - // Install openzeppelin-contracts-upgradeable at v4.7.3 with --no-git. - // The default branch has submodules in lib/ (e.g. openzeppelin-contracts, erc4626-tests), - // but v4.7.3 does not use submodules for dependencies. - cmd.forge_fuse() - .args(["install", "--no-git", "OpenZeppelin/openzeppelin-contracts-upgradeable@v4.7.3"]) - .assert_success(); - - let dep_dir = prj.root().join("lib").join("openzeppelin-contracts-upgradeable"); - assert!(dep_dir.exists(), "dependency should be installed"); - - // The nested lib/ directory should either not exist or be empty — v4.7.3 does not use - // submodules so there should be no leftover submodule contents from the default branch. - let nested_lib = dep_dir.join("lib"); - if nested_lib.exists() { - let entries: Vec<_> = fs::read_dir(&nested_lib).unwrap().collect(); - assert!( - entries.is_empty(), - "nested lib/ should be empty after --no-git install at v4.7.3, found: {entries:?}" - ); - } - - // There should be no .git file or directory anywhere under the installed dependency. - fn assert_no_git(dir: &Path) { - for entry in fs::read_dir(dir).unwrap() { - let entry = entry.unwrap(); - let path = entry.path(); - assert!( - path.file_name() != Some(".git".as_ref()), - "found leftover .git at {}", - path.display() - ); - if path.is_dir() { - assert_no_git(&path); - } - } - } - assert_no_git(&dep_dir); -}); - forgetest_init!(sync_on_forge_update, |prj, cmd| { let git = Git::new(prj.root()); diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs index da4448228e117..cf3c92d7eb5e2 100644 --- a/crates/script-sequence/src/transaction.rs +++ b/crates/script-sequence/src/transaction.rs @@ -1,4 +1,3 @@ -use alloy_network::Network; use alloy_primitives::{Address, B256, Bytes}; use foundry_common::TransactionMaybeSigned; use revm_inspectors::tracing::types::CallKind; @@ -8,24 +7,18 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct AdditionalContract { #[serde(rename = "transactionType")] - pub call_kind: CallKind, + pub opcode: CallKind, pub contract_name: Option, pub address: Address, pub init_code: Bytes, } #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde( - rename_all = "camelCase", - bound( - serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", - deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" - ) -)] -pub struct TransactionWithMetadata { +#[serde(rename_all = "camelCase")] +pub struct TransactionWithMetadata { pub hash: Option, #[serde(rename = "transactionType")] - pub call_kind: CallKind, + pub opcode: CallKind, #[serde(default = "default_string")] pub contract_name: Option, #[serde(default = "default_address")] @@ -36,31 +29,31 @@ pub struct TransactionWithMetadata { pub arguments: Option>, #[serde(skip)] pub rpc: String, - pub transaction: TransactionMaybeSigned, + pub transaction: TransactionMaybeSigned, #[serde(default)] pub additional_contracts: Vec, #[serde(default)] pub is_fixed_gas_limit: bool, } -const fn default_string() -> Option { +fn default_string() -> Option { Some(String::new()) } -const fn default_address() -> Option
{ +fn default_address() -> Option
{ Some(Address::ZERO) } -const fn default_vec_of_strings() -> Option> { +fn default_vec_of_strings() -> Option> { Some(vec![]) } -impl TransactionWithMetadata { - pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { Self { transaction, hash: Default::default(), - call_kind: Default::default(), + opcode: Default::default(), contract_name: Default::default(), contract_address: Default::default(), function: Default::default(), @@ -71,15 +64,15 @@ impl TransactionWithMetadata { } } - pub const fn tx(&self) -> &TransactionMaybeSigned { + pub fn tx(&self) -> &TransactionMaybeSigned { &self.transaction } - pub const fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { + pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { &mut self.transaction } pub fn is_create2(&self) -> bool { - self.call_kind == CallKind::Create2 + self.opcode == CallKind::Create2 } } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 4146e07d2248b..65b75b8b39bb1 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -56,6 +56,7 @@ alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true thiserror.workspace = true +tempo-alloy.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index f1462bb868f58..7c5ea9e14d54a 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -18,9 +18,8 @@ use foundry_compilers::{ info::ContractInfo, utils::source_files_iter, }; -use foundry_evm::{core::evm::FoundryEvmNetwork, traces::debug::ContractSources}; +use foundry_evm::traces::debug::ContractSources; use foundry_linking::Linker; -use foundry_wallets::wallet_browser::signer::BrowserSigner; use std::{path::PathBuf, str::FromStr, sync::Arc}; /// Container for the compiled contracts. @@ -41,10 +40,7 @@ impl BuildData { /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to /// default linking with sender nonce and address. - pub async fn link( - self, - script_config: &ScriptConfig, - ) -> Result { + pub async fn link(self, script_config: &ScriptConfig) -> Result { let create2_deployer = script_config.evm_opts.create2_deployer; let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { let provider = ProviderBuilder::::new(fork_url).build()?; @@ -107,7 +103,7 @@ pub enum ScriptPredeployLibraries { } impl ScriptPredeployLibraries { - pub const fn libraries_count(&self) -> usize { + pub fn libraries_count(&self) -> usize { match self { Self::Default(libs) => libs.len(), Self::Create2(libs, _) => libs.len(), @@ -157,18 +153,17 @@ impl LinkedBuildData { } /// First state basically containing only inputs of the user. -pub struct PreprocessedState { +pub struct PreprocessedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, } -impl PreprocessedState { +impl PreprocessedState { /// Parses user input and compiles the contracts depending on script target. /// After compilation, finds exact [ArtifactId] of the target contract. - pub fn compile(self) -> Result> { - let Self { args, script_config, script_wallets, browser_wallet } = self; + pub fn compile(self) -> Result { + let Self { args, script_config, script_wallets } = self; let project = script_config.config.project()?; let mut target_name = args.target_contract.clone(); @@ -188,11 +183,12 @@ impl PreprocessedState { } }; + #[expect(clippy::redundant_clone)] let sources_to_compile = source_files_iter( project.paths.sources.as_path(), MultiCompilerLanguage::FILE_EXTENSIONS, ) - .chain([target_path.clone()]); + .chain([target_path.to_path_buf()]); let output = ProjectCompiler::new().files(sources_to_compile).compile(&project)?; @@ -236,33 +232,31 @@ impl PreprocessedState { args, script_config, script_wallets, - browser_wallet, build_data: BuildData { output, target, project_root: project.root().to_path_buf() }, }) } } /// State after we have determined and compiled target contract to be executed. -pub struct CompiledState { +pub struct CompiledState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: BuildData, } -impl CompiledState { +impl CompiledState { /// Uses provided sender address to compute library addresses and link contracts with them. - pub async fn link(self) -> Result> { - let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; + pub async fn link(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; let build_data = build_data.link(&script_config).await?; - Ok(LinkedState { args, script_config, script_wallets, browser_wallet, build_data }) + Ok(LinkedState { args, script_config, script_wallets, build_data }) } /// Tries loading the resumed state from the cache files, skipping simulation stage. - pub async fn resume(self) -> Result> { + pub async fn resume(self) -> Result { let chain = if self.args.multi { None } else { @@ -291,49 +285,35 @@ impl CompiledState { } }; - let (args, build_data, script_wallets, browser_wallet, script_config) = - if self.args.unlocked { + let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from().expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if !froms.all(|from| available_signers.contains(&from)) { + // IF we are missing required signers, execute script as we might need to collect + // private keys from the execution. + let executed = self.link().await?.prepare_execution().await?.execute().await?; ( - self.args, - self.build_data, - self.script_wallets, - self.browser_wallet, - self.script_config, + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.script_config, ) } else { - let mut froms = sequence.sequences().iter().flat_map(|s| { - s.transactions - .iter() - .skip(s.receipts.len()) - .map(|t| t.transaction.from().expect("from is missing in script artifact")) - }); - - let available_signers = self - .script_wallets - .signers() - .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; - - if froms.all(|from| available_signers.contains(&from)) { - ( - self.args, - self.build_data, - self.script_wallets, - self.browser_wallet, - self.script_config, - ) - } else { - // IF we are missing required signers, execute script as we might need to - // collect private keys from the execution. - let executed = self.link().await?.prepare_execution().await?.execute().await?; - ( - executed.args, - executed.build_data.build_data, - executed.script_wallets, - executed.browser_wallet, - executed.script_config, - ) - } - }; + (self.args, self.build_data, self.script_wallets, self.script_config) + } + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + }; // Collect libraries from sequence and link contracts with them. let libraries = match sequence { @@ -348,17 +328,12 @@ impl CompiledState { args, script_config, script_wallets, - browser_wallet, build_data: linked_build_data, sequence, }) } - fn try_load_sequence( - &self, - chain: Option, - dry_run: bool, - ) -> Result> { + fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { if let Some(chain) = chain { let sequence = ScriptSequence::load( &self.script_config.config, diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 162d061462b66..326960c23ac66 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -6,13 +6,13 @@ use crate::{ }; use alloy_dyn_abi::FunctionExt; use alloy_json_abi::{Function, InternalType, JsonAbi}; -use alloy_network::{AnyNetwork, Network, TransactionBuilder}; +use alloy_network::AnyNetwork; use alloy_primitives::{ Address, Bytes, map::{HashMap, HashSet}, }; use alloy_provider::Provider; -use alloy_rpc_types::TransactionInputKind; +use alloy_rpc_types::TransactionInput; use eyre::{OptionExt, Result}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; @@ -24,7 +24,6 @@ use foundry_common::{ use foundry_config::NamedChain; use foundry_debugger::Debugger; use foundry_evm::{ - core::evm::FoundryEvmNetwork, decode::decode_console_logs, inspectors::cheatcodes::BroadcastableTransactions, traces::{ @@ -33,7 +32,6 @@ use foundry_evm::{ render_trace_arena, }, }; -use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::join_all; use itertools::Itertools; use std::path::Path; @@ -41,11 +39,10 @@ use yansi::Paint; /// State after linking, contains the linked build data along with library addresses and optional /// array of libraries that need to be predeployed. -pub struct LinkedState { +pub struct LinkedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, } @@ -62,11 +59,11 @@ pub struct ExecutionData { pub abi: JsonAbi, } -impl LinkedState { +impl LinkedState { /// Given linked and compiled artifacts, prepares data we need for execution. /// This includes the function to call and the calldata to pass to it. - pub async fn prepare_execution(self) -> Result> { - let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; + pub async fn prepare_execution(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; let target_contract = build_data.get_target_contract()?; @@ -80,7 +77,6 @@ impl LinkedState { args, script_config, script_wallets, - browser_wallet, execution_data: ExecutionData { func, calldata, @@ -94,19 +90,18 @@ impl LinkedState { /// Same as [LinkedState], but also contains [ExecutionData]. #[derive(Debug)] -pub struct PreExecutionState { +pub struct PreExecutionState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, } -impl PreExecutionState { +impl PreExecutionState { /// Executes the script and returns the state after execution. /// Might require executing script twice in cases when we determine sender from execution. - pub async fn execute(mut self) -> Result> { + pub async fn execute(mut self) -> Result { let mut runner = self .script_config .get_runner_with_cheatcodes( @@ -128,7 +123,6 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data.build_data, }; @@ -139,7 +133,6 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: result, @@ -147,10 +140,7 @@ impl PreExecutionState { } /// Executes the script using the provided runner and returns the [ScriptResult]. - pub async fn execute_with_runner( - &self, - runner: &mut ScriptRunner, - ) -> Result> { + pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result { let (address, mut setup_result) = runner.setup( &self.build_data.predeploy_libraries, self.execution_data.bytecode.clone(), @@ -190,7 +180,7 @@ impl PreExecutionState { /// them instead. fn maybe_new_sender( &self, - transactions: Option<&BroadcastableTransactions>, + transactions: Option<&BroadcastableTransactions>, ) -> Result> { let mut new_sender = None; @@ -230,9 +220,10 @@ pub struct RpcData { impl RpcData { /// Iterates over script transactions and collects RPC urls. - fn from_transactions(txs: &BroadcastableTransactions) -> Self { + fn from_transactions(txs: &BroadcastableTransactions) -> Self { let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::>(); + let total_rpcs = + txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); Self { total_rpcs, missing_rpc } } @@ -281,33 +272,30 @@ pub struct ExecutionArtifacts { } /// State after the script has been executed. -pub struct ExecutedState { +pub struct ExecutedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, } -impl ExecutedState { +impl ExecutedState { /// Collects the data we need for simulation and various post-execution tasks. - pub async fn prepare_simulation(self) -> Result> { + pub async fn prepare_simulation(self) -> Result { let returns = self.get_returns()?; let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; - let mut txs: BroadcastableTransactions = - self.execution_result.transactions.clone().unwrap_or_default(); + let mut txs = self.execution_result.transactions.clone().unwrap_or_default(); // Ensure that unsigned transactions have both `data` and `input` populated to avoid // issues with eth_estimateGas and eth_sendTransaction requests. for tx in &mut txs { - if let Some(req) = tx.transaction.as_unsigned_mut() - && let Some(input) = req.input().cloned() - { - *req = req.clone().with_input_kind(input, TransactionInputKind::Both); + if let Some(req) = tx.transaction.as_unsigned_mut() { + req.input = + TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input()); } } let rpc_data = RpcData::from_transactions(&txs); @@ -326,7 +314,6 @@ impl ExecutedState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: self.execution_result, @@ -339,8 +326,6 @@ impl ExecutedState { &self, known_contracts: &ContractsByArtifact, ) -> Result { - let chain_id = self.script_config.evm_opts.get_remote_chain_id().await; - let mut decoder = CallTraceDecoderBuilder::new() .with_labels(self.execution_result.labeled_addresses.clone()) .with_verbosity(self.script_config.evm_opts.verbosity) @@ -349,12 +334,12 @@ impl ExecutedState { &self.script_config.config, )?) .with_label_disabled(self.args.disable_labels) - .with_chain_id(chain_id.map(|c| c.id())) .build(); - let mut identifier = TraceIdentifiers::new() - .with_local(known_contracts) - .with_external(&self.script_config.config, chain_id)?; + let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_external( + &self.script_config.config, + self.script_config.evm_opts.get_remote_chain_id().await, + )?; for (_, trace) in &self.execution_result.traces { decoder.identify(trace, &mut identifier); @@ -378,10 +363,10 @@ impl ExecutedState { ty: "unknown".to_string(), }); - let label = if output.name.is_empty() { - index.to_string() + let label = if !output.name.is_empty() { + output.name.to_string() } else { - output.name.clone() + index.to_string() }; returns.insert( @@ -402,7 +387,7 @@ impl ExecutedState { } } -impl PreSimulationState { +impl PreSimulationState { pub async fn show_json(&self) -> Result<()> { let mut result = self.execution_result.clone(); @@ -476,10 +461,10 @@ impl PreSimulationState { ty: "unknown".to_string(), }); - let label = if output.name.is_empty() { - index.to_string() + let label = if !output.name.is_empty() { + output.name.to_string() } else { - output.name.clone() + index.to_string() }; sh_println!( "{label}: {internal_type} {value}", diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index d8d83913edb40..684bd42baae89 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -2,7 +2,6 @@ //! //! Smart contract scripting. -#![recursion_limit = "256"] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -12,11 +11,10 @@ extern crate foundry_common; #[macro_use] extern crate tracing; -use crate::{broadcast::BundledState, runner::ScriptRunner}; +use crate::runner::ScriptRunner; use alloy_json_abi::{Function, JsonAbi}; -use alloy_network::Network; use alloy_primitives::{ - Address, Bytes, Log, U256, hex, + Address, Bytes, Log, TxKind, U256, hex, map::{AddressHashMap, HashMap}, }; use alloy_signer::Signer; @@ -29,7 +27,7 @@ use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; use foundry_cli::{ opts::{BuildOpts, EvmArgs, GlobalArgs}, - utils::{LoadConfig, parse_fee_token_address}, + utils::LoadConfig, }; use foundry_common::{ CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, @@ -46,11 +44,7 @@ use foundry_config::{ }; use foundry_evm::{ backend::Backend, - core::{ - Breakpoints, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, - tempo::PATH_USD_ADDRESS, - }, + core::Breakpoints, executors::ExecutorBuilder, inspectors::{ CheatsConfig, @@ -59,7 +53,6 @@ use foundry_evm::{ opts::EvmOpts, traces::{TraceMode, Traces}, }; -use foundry_evm_networks::NetworkConfigs; use foundry_wallets::MultiWalletOpts; use serde::Serialize; use std::path::PathBuf; @@ -124,25 +117,12 @@ pub struct ScriptArgs { #[arg(long)] pub broadcast: bool, - /// Batch all broadcast transactions into a single Tempo batch transaction. - /// - /// When enabled, all vm.broadcast() calls are collected and sent as a single - /// atomic type 0x76 transaction instead of individual transactions. - /// This provides atomicity (all-or-nothing execution) and gas savings. - #[arg(long)] - pub batch: bool, - - /// Number of calls per Tempo batch transaction. + /// Batch size of transactions. /// - /// When `--batch` is enabled, splits the collected calls into multiple batch - /// transactions of at most this many calls each. - #[arg(long, requires = "batch", default_value = "100")] + /// This is ignored and set to 1 if batching is not available or `--slow` is enabled. + #[arg(long, default_value = "100")] pub batch_size: usize, - /// Tempo fee token address for paying transaction fees. - #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] - pub fee_token: Option
, - /// Skips on-chain simulation. #[arg(long)] pub skip_simulation: bool, @@ -246,97 +226,34 @@ pub struct ScriptArgs { } impl ScriptArgs { - /// Loads config, resolves evm_opts (including network inference from fork), and returns them. - async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { - let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - - if self.fee_token.is_some() { - // If fee token is set directly select tempo - evm_opts.networks = NetworkConfigs::with_tempo(); - } else { - // Auto-detect network from fork chain ID when not explicitly configured. - evm_opts.infer_network_from_fork().await; - } - - Ok((config, evm_opts)) - } - - async fn preprocess( - self, - config: Config, - mut evm_opts: EvmOpts, - ) -> Result> { + pub async fn preprocess(self) -> Result { let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); - let browser_wallet = self.wallets.browser_signer::().await?; + + let (config, mut evm_opts) = self.load_config_and_evm_opts()?; if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; } else if self.evm.sender.is_none() { - // If no sender was explicitly set via --sender, auto-detect it from available signers: - // use the sole signer's address if there's exactly one, or fall back to the browser - // wallet address if present. + // If no sender was explicitly set via --sender and there's exactly one signer + // (e.g. from --account or --keystore), use that signer's address as the sender. + // This makes --account behave consistently with --private-key. if let Ok(signers) = script_wallets.signers() && signers.len() == 1 { evm_opts.sender = signers[0]; - } else if let Some(signer) = browser_wallet.as_ref().map(|b| b.address()) { - evm_opts.sender = signer } } - let fee_token = if evm_opts.networks.is_tempo() && self.fee_token.is_none() { - Some(PATH_USD_ADDRESS) - } else { - self.fee_token - }; + let script_config = ScriptConfig::new(config, evm_opts).await?; - let script_config = ScriptConfig::new(config, evm_opts, self.batch, fee_token).await?; - Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) + Ok(PreprocessedState { args: self, script_config, script_wallets }) } /// Executes the script - #[allow(clippy::large_stack_frames)] pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let (config, evm_opts) = self.resolved_evm_opts().await?; - - let is_tempo = evm_opts.networks.is_tempo(); - - if self.batch && !is_tempo { - eyre::bail!("--batch mode is only supported on Tempo networks"); - } - - if is_tempo { - let batch = self.batch; - let bundled = match self.prepare_bundled::(config, evm_opts).await? { - Some(bundled) => bundled, - None => return Ok(()), - }; - let bundled = bundled.wait_for_pending().await?; - let broadcasted = - if batch { bundled.broadcast_batch().await? } else { bundled.broadcast().await? }; - if broadcasted.args.verify { - broadcasted.verify().await?; - } - Ok(()) - } else if evm_opts.networks.is_optimism() { - self.run_generic_script::(config, evm_opts).await - } else { - self.run_generic_script::(config, evm_opts).await - } - } - - /// Prepares the bundled state (compile, simulate, bundle) and returns it - /// for broadcasting, or returns `None` if there's nothing to broadcast - /// (e.g., debug mode, no transactions, missing RPCs). - #[allow(clippy::large_stack_frames)] - async fn prepare_bundled( - self, - config: Config, - evm_opts: EvmOpts, - ) -> Result>> { - let state = self.preprocess::(config, evm_opts).await?; + let state = self.preprocess().await?; let create2_deployer = state.script_config.evm_opts.create2_deployer; let compiled = state.compile()?; @@ -358,8 +275,8 @@ impl ScriptArgs { if pre_simulation.args.debug { return match pre_simulation.args.dump.clone() { - Some(path) => pre_simulation.dump_debugger(&path).map(|_| None), - None => pre_simulation.run_debugger().map(|_| None), + Some(path) => pre_simulation.dump_debugger(&path), + None => pre_simulation.run_debugger(), }; } @@ -381,7 +298,7 @@ impl ScriptArgs { sh_warn!("No transactions to broadcast.")?; } - return Ok(None); + return Ok(()); } // Check if there are any missing RPCs and exit early to avoid hard error. @@ -390,7 +307,7 @@ impl ScriptArgs { sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; } - return Ok(None); + return Ok(()); } pre_simulation.args.check_contract_sizes( @@ -414,7 +331,7 @@ impl ScriptArgs { "\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more." )?; } - return Ok(None); + return Ok(()); } // Exit early if something is wrong with verification options. @@ -422,19 +339,6 @@ impl ScriptArgs { bundled.verify_preflight_check()?; } - Ok(Some(bundled)) - } - - async fn run_generic_script( - self, - config: Config, - evm_opts: EvmOpts, - ) -> Result<()> { - let bundled = match self.prepare_bundled::(config, evm_opts).await? { - Some(bundled) => bundled, - None => return Ok(()), - }; - // Wait for pending txes and broadcast others. let broadcasted = bundled.wait_for_pending().await?.broadcast().await?; @@ -507,9 +411,9 @@ impl ScriptArgs { /// /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns /// the user. - fn check_contract_sizes( + fn check_contract_sizes( &self, - result: &ScriptResult, + result: &ScriptResult, known_contracts: &ContractsByArtifact, create2_deployer: Address, ) -> Result<()> { @@ -540,6 +444,7 @@ impl ScriptArgs { bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); unknown_c += 1; } + continue; } let mut prompt_user = false; @@ -559,13 +464,15 @@ impl ScriptArgs { let mut offset = 0; // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(to) = to { + if let Some(TxKind::Call(to)) = to { if to == create2_deployer { // Size of the salt prefix. offset = 32; } else { continue; } + } else if let Some(TxKind::Create) = to { + // Pass } // Find artifact with a deployment code same as the data. @@ -595,7 +502,7 @@ impl ScriptArgs { } /// We only broadcast transactions if --broadcast, --resume, or --verify was passed. - const fn should_broadcast(&self) -> bool { + fn should_broadcast(&self) -> bool { self.broadcast || self.resume || self.verify } } @@ -608,12 +515,12 @@ impl Provider for ScriptArgs { fn data(&self) -> Result, figment::Error> { let mut dict = Dict::default(); - if let Some(etherscan_api_key) = + if let Some(ref etherscan_api_key) = self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) { dict.insert( "etherscan_api_key".to_string(), - figment::value::Value::from(etherscan_api_key.clone()), + figment::value::Value::from(etherscan_api_key.to_string()), ); } @@ -625,9 +532,8 @@ impl Provider for ScriptArgs { } } -#[derive(Serialize, Clone)] -#[serde(bound = "")] -pub struct ScriptResult { +#[derive(Default, Serialize, Clone)] +pub struct ScriptResult { pub success: bool, #[serde(rename = "raw_logs")] pub logs: Vec, @@ -635,30 +541,14 @@ pub struct ScriptResult { pub gas_used: u64, pub labeled_addresses: AddressHashMap, #[serde(skip)] - pub transactions: Option>, + pub transactions: Option, pub returned: Bytes, pub address: Option
, #[serde(skip)] pub breakpoints: Breakpoints, } -impl Default for ScriptResult { - fn default() -> Self { - Self { - success: Default::default(), - logs: Default::default(), - traces: Default::default(), - gas_used: Default::default(), - labeled_addresses: Default::default(), - transactions: Default::default(), - returned: Default::default(), - address: Default::default(), - breakpoints: Default::default(), - } - } -} - -impl ScriptResult { +impl ScriptResult { pub fn get_created_contracts( &self, known_contracts: &ContractsByArtifact, @@ -673,7 +563,7 @@ impl ScriptResult { .find_by_creation_code(init_code.as_ref()) .map(|artifact| artifact.0.name.clone()); return Some(AdditionalContract { - call_kind: node.trace.kind, + opcode: node.trace.kind, address: node.trace.address, contract_name, init_code, @@ -687,34 +577,24 @@ impl ScriptResult { } #[derive(Serialize)] -#[serde(bound = "")] -struct JsonResult<'a, N: Network> { +struct JsonResult<'a> { logs: Vec, returns: &'a HashMap, #[serde(flatten)] - result: &'a ScriptResult, + result: &'a ScriptResult, } #[derive(Clone, Debug)] -pub struct ScriptConfig { +pub struct ScriptConfig { pub config: Config, pub evm_opts: EvmOpts, pub sender_nonce: u64, /// Maps a rpc url to a backend - pub backends: HashMap>, - /// Whether to batch all broadcast transactions into a single Tempo batch transaction. - pub batch: bool, - /// Tempo fee token address for paying transaction fees. - pub fee_token: Option
, + pub backends: HashMap, } -impl ScriptConfig { - pub async fn new( - config: Config, - evm_opts: EvmOpts, - batch: bool, - fee_token: Option
, - ) -> Result { +impl ScriptConfig { + pub async fn new(config: Config, evm_opts: EvmOpts) -> Result { let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? } else { @@ -722,7 +602,7 @@ impl ScriptConfig { 1 }; - Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, fee_token }) + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() }) } pub async fn update_sender(&mut self, sender: Address) -> Result<()> { @@ -736,7 +616,7 @@ impl ScriptConfig { Ok(()) } - async fn get_runner(&mut self) -> Result> { + async fn get_runner(&mut self) -> Result { self._get_runner(None, false).await } @@ -746,7 +626,7 @@ impl ScriptConfig { script_wallets: Wallets, debug: bool, target: ArtifactId, - ) -> Result> { + ) -> Result { self._get_runner(Some((known_contracts, script_wallets, target)), debug).await } @@ -754,16 +634,15 @@ impl ScriptConfig { &mut self, cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>, debug: bool, - ) -> Result> { + ) -> Result { trace!("preparing script runner"); - let (evm_env, mut tx_env, fork_block) = self.evm_opts.env::<_, _, TxEnvFor>().await?; + let env = self.evm_opts.env().await?; let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { Some(db) => db.clone(), None => { - let fork = - self.evm_opts.get_fork(&self.config, evm_env.cfg_env.chain_id, fork_block); + let fork = self.evm_opts.get_fork(&self.config, env.evm_env.clone()); let backend = Backend::spawn(fork)?; self.backends.insert(fork_url.clone(), backend.clone()); backend @@ -777,7 +656,7 @@ impl ScriptConfig { }; // We need to enable tracing to decode contract names: local or external. - let mut builder = ExecutorBuilder::default() + let mut builder = ExecutorBuilder::new() .inspectors(|stack| { stack .logs(self.config.live_logs) @@ -798,7 +677,6 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), - self.fee_token, ) .into(), ) @@ -807,18 +685,13 @@ impl ScriptConfig { }); } - // Propagate fee token to the transaction environment so that internal EVM calls - // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. - tx_env.set_fee_token(self.fee_token); - - Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) + Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone())) } } #[cfg(test)] mod tests { use super::*; - use alloy_network::Ethereum; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -872,7 +745,7 @@ mod tests { ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]); assert!(args.disable_code_size_limit); - let result = ScriptResult::::default(); + let result = ScriptResult::default(); let contracts = ContractsByArtifact::default(); let create = Address::ZERO; assert!(args.check_contract_sizes(&result, &contracts, create).is_ok()); @@ -1097,19 +970,4 @@ mod tests { ]); assert!(args.with_gas_price.unwrap().is_zero()); } - - #[test] - fn test_priority_gas_price_cannot_exceed_gas_price() { - let args = ScriptArgs::parse_from([ - "foundry-cli", - "--broadcast", - "--with-gas-price", - "100", - "--priority-gas-price", - "200", - "Script", - ]); - // priority (200) > max_fee (100) — broadcast should reject this at runtime - assert!(args.priority_gas_price.unwrap() > args.with_gas_price.unwrap()); - } } diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index c066a026e0a0f..afa4e8ab03e4d 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,9 +1,10 @@ use alloy_chains::{Chain, NamedChain}; -use alloy_network::{Network, ReceiptResponse}; +use alloy_network::{Ethereum, ReceiptResponse}; use alloy_primitives::{TxHash, U256, utils::format_units}; use alloy_provider::{ PendingTransactionBuilder, PendingTransactionError, Provider, RootProvider, WatchTxError, }; +use alloy_rpc_types::TransactionReceipt; use eyre::{Result, eyre}; use forge_script_sequence::ScriptSequence; use foundry_common::{retry, retry::RetryError, shell}; @@ -19,25 +20,25 @@ pub struct PendingReceiptError { } /// Convenience enum for internal signalling of transaction status -pub enum TxStatus { +pub enum TxStatus { Dropped, - Success(R), - Revert(R), + Success(TransactionReceipt), + Revert(TransactionReceipt), } -impl From for TxStatus { - fn from(receipt: R) -> Self { - if receipt.status() { Self::Success(receipt) } else { Self::Revert(receipt) } +impl From for TxStatus { + fn from(receipt: TransactionReceipt) -> Self { + if !receipt.status() { Self::Revert(receipt) } else { Self::Success(receipt) } } } /// Checks the status of a txhash by first polling for a receipt, then for /// mempool inclusion. Returns the tx hash, and a status -pub async fn check_tx_status( - provider: &RootProvider, +pub async fn check_tx_status( + provider: &RootProvider, hash: TxHash, timeout: u64, -) -> (TxHash, Result, eyre::Report>) { +) -> (TxHash, Result) { let result = retry::Retry::new_no_delay(3) .run_async_until_break(|| async { match PendingTransactionBuilder::new(provider.clone(), hash) @@ -88,10 +89,10 @@ pub async fn check_tx_status( } /// Prints parts of the receipt to stdout -pub fn format_receipt( +pub fn format_receipt( chain: Chain, - receipt: &N::ReceiptResponse, - sequence: Option<&ScriptSequence>, + receipt: &TransactionReceipt, + sequence: Option<&ScriptSequence>, ) -> String { let gas_used = receipt.gas_used(); let gas_price = receipt.effective_gas_price(); @@ -181,9 +182,7 @@ pub fn format_receipt( #[cfg(test)] mod tests { use super::*; - use alloy_network::Ethereum; use alloy_primitives::B256; - use alloy_rpc_types::TransactionReceipt; use std::collections::VecDeque; fn mock_receipt(tx_hash: B256, success: bool) -> TransactionReceipt { @@ -199,11 +198,7 @@ mod tests { .unwrap() } - fn mock_sequence( - tx_hash: B256, - contract: Option<&str>, - func: Option<&str>, - ) -> ScriptSequence { + fn mock_sequence(tx_hash: B256, contract: Option<&str>, func: Option<&str>) -> ScriptSequence { let tx = serde_json::from_value(serde_json::json!({ "hash": tx_hash, "transactionType": "CALL", "contractName": contract, "contractAddress": null, "function": func, @@ -233,7 +228,7 @@ mod tests { #[test] fn format_receipt_without_sequence_omits_metadata() { let hash = B256::repeat_byte(0x42); - let out = format_receipt::(Chain::mainnet(), &mock_receipt(hash, true), None); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), None); assert!(!out.contains("Contract:")); assert!(!out.contains("Function:")); diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index de80b1bf98c47..4b8b3bbeba3e9 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -1,19 +1,13 @@ use super::{ScriptConfig, ScriptResult}; use crate::build::ScriptPredeployLibraries; use alloy_eips::eip7702::SignedAuthorization; -use alloy_evm::revm::context::Transaction; -use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_rpc_types::TransactionRequest; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; -use foundry_common::{FoundryTransactionBuilder, TransactionMaybeSigned}; use foundry_config::Config; use foundry_evm::{ constants::CALLER, - core::{ - FoundryTransaction, - evm::{FoundryEvmNetwork, TransactionRequestFor}, - }, executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, opts::EvmOpts, revm::interpreter::{InstructionResult, return_ok}, @@ -23,13 +17,13 @@ use std::collections::VecDeque; /// Drives script execution #[derive(Debug)] -pub struct ScriptRunner { - pub executor: Executor, +pub struct ScriptRunner { + pub executor: Executor, pub evm_opts: EvmOpts, } -impl ScriptRunner { - pub const fn new(executor: Executor, evm_opts: EvmOpts) -> Self { +impl ScriptRunner { + pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self { Self { executor, evm_opts } } @@ -39,9 +33,9 @@ impl ScriptRunner { libraries: &ScriptPredeployLibraries, code: Bytes, setup: bool, - script_config: &ScriptConfig, + script_config: &ScriptConfig, is_broadcast: bool, - ) -> Result<(Address, ScriptResult)> { + ) -> Result<(Address, ScriptResult)> { trace!(target: "script", "executing setUP()"); if !is_broadcast { @@ -79,18 +73,15 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } - let mut tx_req = TransactionRequestFor::::default() - .with_from(self.evm_opts.sender) - .with_input(code.clone()) - .with_nonce(sender_nonce + library_transactions.len() as u64); - - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } - library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionMaybeSigned::new(tx_req), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: code.clone().into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + ..Default::default() + } + .into(), }) }), ScriptPredeployLibraries::Create2(libraries, salt) => { @@ -116,19 +107,16 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } - let mut tx_req = TransactionRequestFor::::default() - .with_from(self.evm_opts.sender) - .with_input(calldata) - .with_nonce(sender_nonce + library_transactions.len() as u64) - .with_to(create2_deployer); - - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } - library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionMaybeSigned::new(tx_req), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: calldata.into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + to: Some(TxKind::Call(create2_deployer)), + ..Default::default() + } + .into(), }); } @@ -179,7 +167,10 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions) = if setup { + let (success, gas_used, labeled_addresses, transactions) = if !setup { + self.executor.backend_mut().set_test_contract(address); + (true, 0, Default::default(), Some(library_transactions)) + } else { match self.executor.setup(Some(self.evm_opts.sender), address, None) { Ok(RawCallResult { reverted, @@ -220,9 +211,6 @@ impl ScriptRunner { } Err(e) => return Err(e.into()), } - } else { - self.executor.backend_mut().set_test_contract(address); - (true, 0, Default::default(), Some(library_transactions)) }; Ok(( @@ -242,11 +230,7 @@ impl ScriptRunner { } /// Executes the method that will collect all broadcastable transactions. - pub fn script( - &mut self, - address: Address, - calldata: Bytes, - ) -> Result> { + pub fn script(&mut self, address: Address, calldata: Bytes) -> Result { self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false) } @@ -258,7 +242,7 @@ impl ScriptRunner { calldata: Option, value: Option, authorization_list: Option>, - ) -> Result> { + ) -> Result { if let Some(to) = to { self.call( from, @@ -314,7 +298,7 @@ impl ScriptRunner { value: U256, authorization_list: Option>, commit: bool, - ) -> Result> { + ) -> Result { let mut res = if let Some(authorization_list) = &authorization_list { self.executor.call_raw_with_authorization( from, @@ -378,7 +362,7 @@ impl ScriptRunner { /// it might be problematic when using `ffi`. fn search_optimal_gas_usage( &mut self, - res: &RawCallResult, + res: &RawCallResult, from: Address, to: Address, calldata: &Bytes, @@ -387,21 +371,19 @@ impl ScriptRunner { let mut gas_used = res.gas_used; if matches!(res.exit_reason, Some(return_ok!())) { // Store the current gas limit and reset it later. - let init_gas_limit = self.executor.tx_env().gas_limit(); + let init_gas_limit = self.executor.env().tx.gas_limit; let mut highest_gas_limit = gas_used * 3; let mut lowest_gas_limit = gas_used; let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.tx_env_mut().set_gas_limit(mid_gas_limit); + self.executor.env_mut().tx.gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?; match res.exit_reason { - Some( - InstructionResult::Revert - | InstructionResult::OutOfGas - | InstructionResult::OutOfFunds, - ) => { + Some(InstructionResult::Revert) + | Some(InstructionResult::OutOfGas) + | Some(InstructionResult::OutOfFunds) => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -422,7 +404,7 @@ impl ScriptRunner { } } // Reset gas limit in the executor. - self.executor.tx_env_mut().set_gas_limit(init_gas_limit); + self.executor.env_mut().tx.gas_limit = init_gas_limit; } Ok(gas_used) } diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index 7b9f21ae71f1e..c0a7e9deddca7 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,32 +1,23 @@ use crate::multi_sequence::MultiChainSequence; -use alloy_network::Network; use eyre::Result; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cli::utils::Git; -use foundry_common::{FoundryTransactionBuilder, fmt::UIfmt}; +use foundry_common::fmt::UIfmt; use foundry_compilers::ArtifactId; use foundry_config::Config; -use serde::{Deserialize, Serialize}; use std::{ fmt::{Error, Write}, path::Path, }; /// Format transaction details for display -fn format_transaction( - index: usize, - tx: &TransactionWithMetadata, -) -> Result -where - N::TxEnvelope: UIfmt, - N::TransactionRequest: FoundryTransactionBuilder, -{ +fn format_transaction(index: usize, tx: &TransactionWithMetadata) -> Result { let mut output = String::new(); writeln!(output, "### Transaction {index} ###")?; writeln!(output, "{}", tx.tx().pretty())?; // Show contract name and address if available - if !tx.call_kind.is_any_create() + if !tx.opcode.is_any_create() && let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) { writeln!(output, "contract: {name}({addr})")?; @@ -54,20 +45,12 @@ pub fn get_commit_hash(root: &Path) -> Option { Git::new(root).commit_hash(true, "HEAD").ok() } -pub enum ScriptSequenceKind -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ - Single(ScriptSequence), - Multi(MultiChainSequence), +pub enum ScriptSequenceKind { + Single(ScriptSequence), + Multi(MultiChainSequence), } -impl ScriptSequenceKind -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ +impl ScriptSequenceKind { pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { match self { Self::Single(sequence) => sequence.save(silent, save_ts), @@ -75,14 +58,14 @@ where } } - pub fn sequences(&self) -> &[ScriptSequence] { + pub fn sequences(&self) -> &[ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_ref(sequence), Self::Multi(sequence) => &sequence.deployments, } } - pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_mut(sequence), Self::Multi(sequence) => &mut sequence.deployments, @@ -97,28 +80,19 @@ where ) -> Result<()> { match self { Self::Single(sequence) => { - sequence.paths = Some(ScriptSequence::::get_paths( - config, - sig, - target, - sequence.chain, - false, - )?); + sequence.paths = + Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); } Self::Multi(sequence) => { (sequence.path, sequence.sensitive_path) = - MultiChainSequence::::get_paths(config, sig, target, false)?; + MultiChainSequence::get_paths(config, sig, target, false)?; } }; Ok(()) } - pub fn show_transactions(&self) -> Result<()> - where - N::TxEnvelope: UIfmt, - N::TransactionRequest: FoundryTransactionBuilder, - { + pub fn show_transactions(&self) -> Result<()> { for sequence in self.sequences() { if !sequence.transactions.is_empty() { sh_println!("\nChain {}\n", sequence.chain)?; @@ -133,11 +107,7 @@ where } } -impl Drop for ScriptSequenceKind -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ +impl Drop for ScriptSequenceKind { fn drop(&mut self) { if let Err(err) = self.save(false, true) { error!(?err, "could not save deployment sequence"); diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 5c5155aa1678b..05834ed8bb036 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,21 +9,15 @@ use crate::{ execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, }; -use alloy_chains::NamedChain; -use alloy_evm::revm::context::Block; use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; +use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{ContractData, shell}; -use foundry_evm::{ - core::{FoundryBlock, evm::FoundryEvmNetwork}, - traces::{decode_trace_arena, render_trace_arena}, -}; -use foundry_wallets::wallet_browser::signer::BrowserSigner; +use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -37,24 +31,23 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState { +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result> { + pub async fn fill_metadata(self) -> Result { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -71,7 +64,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if to.is_some() { + if let Some(TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -95,7 +88,6 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -108,8 +100,8 @@ impl PreSimulationState { /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque>, - ) -> Result>> { + transactions: VecDeque, + ) -> Result> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -129,7 +121,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = tx.to(); + let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() @@ -147,8 +139,7 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); - runner.executor.evm_env_mut().block_env.set_number(block_number); + runner.executor.env_mut().evm_env.block_env.number += U256::from(1); } let is_noop_tx = if let Some(to) = to { @@ -235,12 +226,12 @@ impl PreSimulationState { } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result)>> { + async fn build_runners(&self) -> Result> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { let n = rpcs.len(); - let s = if n == 1 { "" } else { "s" }; + let s = if n != 1 { "s" } else { "" }; sh_println!("\n## Setting up {n} EVM{s}.")?; } @@ -257,23 +248,22 @@ impl PreSimulationState { /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque>, + pub transactions: VecDeque, } -impl FilledTransactionsState { +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result> { + pub async fn bundle(mut self) -> Result { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -284,7 +274,7 @@ impl FilledTransactionsState { // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::::default(); + let mut manager = ProvidersManager::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a @@ -292,7 +282,7 @@ impl FilledTransactionsState { let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable(); while let Some(mut tx) = txes_iter.next() { - let tx_rpc = tx.rpc.clone(); + let tx_rpc = tx.rpc.to_owned(); let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; if let Some(tx) = tx.tx_mut().as_unsigned_mut() { @@ -307,7 +297,7 @@ impl FilledTransactionsState { // only estimate gas for unsigned transactions if let Some(tx) = tx.as_unsigned_mut() { trace!("estimating with different gas calculation"); - let gas = tx.gas_limit().expect("gas is set by simulation."); + let gas = tx.gas.expect("gas is set by simulation."); // We are trying to show the user an estimation of the total gas usage. // @@ -361,12 +351,6 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); - // Get the native token symbol for the chain using NamedChain - let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() - .native_currency_symbol() - .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -384,7 +368,15 @@ impl FilledTransactionsState { .unwrap_or_else(|_| "[Could not calculate]".to_string()); let estimated_amount = estimated_amount_raw.trim_end_matches('0'); - if shell::is_json() { + if !shell::is_json() { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; + sh_println!("\n==========================")?; + } else { sh_println!( "{}", serde_json::json!({ @@ -392,17 +384,8 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, - "token_symbol": token_symbol, }) )?; - } else { - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; - sh_println!("\n==========================")?; } } } @@ -423,7 +406,6 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, - browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -434,14 +416,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque>, - ) -> Result> { + transactions: VecDeque, + ) -> Result { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::::get_paths( + Some(ScriptSequence::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index ef10a1ce94082..1bfac6d9564bc 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -3,29 +3,28 @@ use crate::{ build::LinkedBuildData, sequence::{ScriptSequenceKind, get_commit_hash}, }; -use alloy_network::{Network, ReceiptResponse}; +use alloy_network::ReceiptResponse; use alloy_primitives::{Address, hex}; use eyre::{Result, eyre}; use forge_script_sequence::{AdditionalContract, ScriptSequence}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs, provider::VerificationProviderType}; use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts}; -use foundry_common::{ContractsByArtifact, FoundryReceiptResponse}; +use foundry_common::ContractsByArtifact; use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo}; use foundry_config::{Chain, Config}; -use foundry_evm::core::evm::FoundryEvmNetwork; use semver::Version; /// State after we have broadcasted the script. /// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all /// broadcasted transactions. -pub struct BroadcastedState { +pub struct BroadcastedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BroadcastedState { +impl BroadcastedState { pub async fn verify(self) -> Result<()> { let Self { args, script_config, build_data, mut sequence, .. } = self; @@ -38,7 +37,7 @@ impl BroadcastedState { ); for sequence in sequence.sequences_mut() { - verify_contracts::(sequence, &script_config.config, verify.clone()).await?; + verify_contracts(sequence, &script_config.config, verify.clone()).await?; } Ok(()) @@ -78,7 +77,7 @@ impl VerifyBundle { cache_path: Some(project.paths.cache.clone()), lib_paths: project.paths.libraries.clone(), hardhat: config.profile == Config::HARDHAT_PROFILE, - config_path: config_path.exists().then_some(config_path), + config_path: if config_path.exists() { Some(config_path) } else { None }, }; let via_ir = config.via_ir; @@ -166,7 +165,7 @@ impl VerifyBundle { evm_version: Some(evm_version), show_standard_json_input: false, guess_constructor_args: false, - compilation_profile: Some(artifact.profile.clone()), + compilation_profile: Some(artifact.profile.to_string()), language: None, creation_transaction_hash: None, }; @@ -180,8 +179,8 @@ impl VerifyBundle { /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. -async fn verify_contracts( - sequence: &mut ScriptSequence, +async fn verify_contracts( + sequence: &mut ScriptSequence, config: &Config, mut verify: VerifyBundle, ) -> Result<()> { @@ -201,14 +200,12 @@ async fn verify_contracts( for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) { // create2 hash offset - let offset = if tx.is_create2() - && let Some(contract_address) = tx.contract_address - { - receipt.set_contract_address(contract_address); - 32 - } else { - 0 - }; + let mut offset = 0; + + if tx.is_create2() { + receipt.contract_address = tx.contract_address; + offset = 32; + } // Verify contract created directly from the transaction if let (Some(address), Some(data)) = (receipt.contract_address(), tx.tx().input()) { @@ -269,8 +266,8 @@ async fn verify_contracts( Ok(()) } -fn check_unverified( - sequence: &ScriptSequence, +fn check_unverified( + sequence: &ScriptSequence, unverifiable_contracts: Vec
, verify: VerifyBundle, ) { diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 06f83886d0fd2..c1a6cb53bdbff 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index d22e12736d832..a6df9cda5e976 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -4,7 +4,7 @@ use std::{ env, fs::{self, File}, io::{Read, Seek, Write}, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, process::Command, sync::LazyLock, }; @@ -231,15 +231,59 @@ pub fn read_string(path: impl AsRef) -> String { /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + // Canonicalize the source once and treat it as the base directory for all traversal. + let base = src.canonicalize()?; + // Canonicalize the destination once and treat it as the base directory for all writes. + let dst_base = dst.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, &dst_base, true) } -fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { +fn copy_dir_filtered_inner( + src: &Path, + dst: &Path, + base_src: &Path, + base_dst: &Path, + is_root: bool, +) -> std::io::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + // Ensure that any path we operate on stays within the original source base directory. + let canonical_src_path = src_path.canonicalize()?; + if !canonical_src_path.starts_with(base_src) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + let relative_src_path = canonical_src_path.strip_prefix(base_src).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "failed to derive relative path within source base directory", + ) + })?; + for component in relative_src_path.components() { + match component { + Component::Normal(name) => { + let name = name.to_string_lossy(); + if name.contains("..") || name.contains('/') || name.contains('\\') { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "invalid path component in source entry", + )); + } + } + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + } + } + let dst_path = base_dst.join(relative_src_path); if ty.is_dir() { // Skip build artifact directories at the root level @@ -249,10 +293,58 @@ fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Re { continue; } + // Ensure that the destination directory stays within the allowed destination base. + let canonical_dst_dir = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // Directory does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_dir.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } fs::create_dir_all(&dst_path)?; - copy_dir_filtered_inner(&src_path, &dst_path, false)?; + copy_dir_filtered_inner(&src_path, &dst_path, base_src, base_dst, false)?; } else { - fs::copy(&src_path, &dst_path)?; + // Ensure that the destination file path stays within the allowed destination base. + let canonical_dst_path = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // File does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_path.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + fs::copy(&canonical_src_path, &canonical_dst_path)?; } } Ok(()) diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 01c1f6b829195..773430c4bf785 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -223,6 +223,10 @@ impl VerificationProviderType { // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan. if has_key { + sh_eprintln!( + "ETHERSCAN_API_KEY is set, defaulting to Etherscan verifier. \ + Unset it or pass `--verifier sourcify` (or another provider) to override." + )?; return Ok(Box::::default()); } diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 9d4cacc97591a..c6344dd9d2731 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -1,7 +1,7 @@ use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; +use alloy_consensus::BlockHeader; use alloy_dyn_abi::DynSolValue; -use alloy_evm::EvmEnv; -use alloy_primitives::{Address, Bytes, TxKind}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_provider::{ Provider, network::{AnyNetwork, AnyRpcBlock}, @@ -18,16 +18,13 @@ use foundry_common::{abi::encode_args, compile::ProjectCompiler, ignore_metadata use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; use foundry_evm::{ - constants::DEFAULT_CREATE2_DEPLOYER, - core::{decode::RevertDecoder, evm::EthEvmNetwork}, - executors::TracingExecutor, - opts::EvmOpts, - traces::TraceMode, - utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, + Env, constants::DEFAULT_CREATE2_DEPLOYER, core::decode::RevertDecoder, + executors::TracingExecutor, opts::EvmOpts, traces::TraceMode, + utils::apply_chain_and_block_specific_env_changes, }; use foundry_evm_networks::NetworkConfigs; use reqwest::Url; -use revm::{bytecode::Bytecode, context::TxEnv, database::Database, primitives::hardfork::SpecId}; +use revm::{bytecode::Bytecode, database::Database, primitives::hardfork::SpecId}; use semver::{BuildMetadata, Version}; use serde::{Deserialize, Serialize}; use yansi::Paint; @@ -43,12 +40,12 @@ pub enum BytecodeType { impl BytecodeType { /// Check if the bytecode type is creation - pub const fn is_creation(&self) -> bool { + pub fn is_creation(&self) -> bool { matches!(self, Self::Creation) } /// Check if the bytecode type is runtime - pub const fn is_runtime(&self) -> bool { + pub fn is_runtime(&self) -> bool { matches!(self, Self::Runtime) } } @@ -107,15 +104,15 @@ pub fn print_result( config: &Config, ) { if let Some(res) = res { - if shell::is_json() { - let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; - json_results.push(json_res); - } else { + if !shell::is_json() { let _ = sh_println!( "{} with status {}", format!("{bytecode_type:?} code matched").green().bold(), res.green().bold() ); + } else { + let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; + json_results.push(json_res); } } else if !shell::is_json() { let _ = sh_err!( @@ -268,16 +265,16 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, -) -> Result<(EvmEnv, TxEnv, TracingExecutor)> { +) -> Result<(Env, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; let create2_deployer = evm_opts.create2_deployer; - let (evm_env, tx_env, fork, _chain, networks) = - TracingExecutor::::get_fork_material(fork_config, evm_opts).await?; + let (env, fork, _chain, networks) = + TracingExecutor::get_fork_material(fork_config, evm_opts).await?; - let executor = TracingExecutor::::new( - (evm_env.clone(), tx_env.clone()), + let executor = TracingExecutor::new( + env.clone(), fork, Some(fork_config.evm_version), TraceMode::Call, @@ -286,25 +283,31 @@ pub async fn get_tracing_executor( None, )?; - Ok((evm_env, tx_env, executor)) + Ok((env, executor)) } -pub fn configure_env_block(evm_env: &mut EvmEnv, block: &AnyRpcBlock, config: NetworkConfigs) { - let number = evm_env.block_env.number; - evm_env.block_env = block_env_from_header(&block.header); - evm_env.block_env.number = number; - apply_chain_and_block_specific_env_changes::(evm_env, block, config); +pub fn configure_env_block(env: &mut Env, block: &AnyRpcBlock, config: NetworkConfigs) { + env.evm_env.block_env.timestamp = U256::from(block.header.timestamp()); + env.evm_env.block_env.beneficiary = block.header.beneficiary(); + env.evm_env.block_env.difficulty = block.header.difficulty(); + env.evm_env.block_env.prevrandao = Some(block.header.mix_hash().unwrap_or_default()); + env.evm_env.block_env.basefee = block.header.base_fee_per_gas().unwrap_or_default(); + env.evm_env.block_env.gas_limit = block.header.gas_limit(); + apply_chain_and_block_specific_env_changes::(&mut env.evm_env, block, config); } pub fn deploy_contract( - executor: &mut TracingExecutor, - evm_env: &EvmEnv, - tx_env: &TxEnv, + executor: &mut TracingExecutor, + env: &Env, spec_id: SpecId, to: Option, ) -> Result { - let mut evm_env = evm_env.clone(); - evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id); + let env = Env::new_with_spec_id( + env.evm_env.cfg_env.clone(), + env.evm_env.block_env.clone(), + env.tx.clone(), + spec_id, + ); if to.is_some_and(|to| to.is_call()) { let TxKind::Call(to) = to.unwrap() else { unreachable!() }; @@ -313,7 +316,7 @@ pub fn deploy_contract( "Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx." ); } - let result = executor.transact_with_env(evm_env, tx_env.clone())?; + let result = executor.transact_with_env(env)?; trace!(transact_result = ?result.exit_reason); @@ -343,14 +346,14 @@ pub fn deploy_contract( Ok(Address::from_slice(&result.result)) } else { - let deploy_result = executor.deploy_with_env(evm_env, tx_env.clone(), None)?; + let deploy_result = executor.deploy_with_env(env, None)?; trace!(deploy_result = ?deploy_result.raw.exit_reason); Ok(deploy_result.address) } } pub async fn get_runtime_codes( - executor: &mut TracingExecutor, + executor: &mut TracingExecutor, provider: &impl Provider, address: Address, fork_address: Address, @@ -386,6 +389,35 @@ pub fn is_host_only(url: &Url) -> bool { matches!(url.path(), "/" | "") } +/// Wraps a failed verification error with guidance when `--verifier-url` looks misconfigured for +/// the Etherscan provider. Returns `err` untouched when no hint applies. +/// +/// The hint only fires when the Etherscan verifier is active: it requires an API endpoint +/// (typically `/api`). Sourcify, Blockscout, etc. accept host-only URLs, so we leave their +/// errors alone. +pub fn wrap_verifier_url_error( + err: eyre::Error, + verifier_url: Option<&str>, + using_etherscan: bool, +) -> eyre::Error { + let Some(verifier_url) = verifier_url else { return err }; + let url = match Url::parse(verifier_url) { + Ok(url) => url, + Err(url_err) => { + return err.wrap_err(format!("Invalid URL {verifier_url} provided: {url_err}")); + } + }; + if is_host_only(&url) && using_etherscan { + return err.wrap_err(format!( + "Verifier `etherscan` requires an API endpoint, but `--verifier-url` is host-only: `{verifier_url}`.\n\ + Fixes (pick one):\n\ + - Append the API path, e.g. `--verifier-url {verifier_url}/api`\n\ + - Switch verifier, e.g. `--verifier sourcify` (works with host-only URLs)" + )); + } + err +} + /// Given any solc [Version] return a [Version] with build metadata /// /// # Example @@ -397,10 +429,10 @@ pub fn is_host_only(url: &Url) -> bool { /// assert_ne!(version.build, BuildMetadata::EMPTY); /// ``` pub async fn ensure_solc_build_metadata(version: Version) -> Result { - if version.build == BuildMetadata::EMPTY { - Ok(lookup_compiler_version(&version).await?) - } else { + if version.build != BuildMetadata::EMPTY { Ok(version) + } else { + Ok(lookup_compiler_version(&version).await?) } } @@ -414,4 +446,38 @@ mod tests { assert!(is_host_only(&Url::parse("https://blockscout.net/").unwrap())); assert!(is_host_only(&Url::parse("https://blockscout.net").unwrap())); } + + #[test] + fn wrap_verifier_url_error_passes_through_when_no_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, None, true); + assert_eq!(wrapped.to_string(), "upstream failure"); + } + + #[test] + fn wrap_verifier_url_error_adds_hint_for_host_only_etherscan_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("https://contracts.tempo.xyz"), true); + let msg = format!("{wrapped:#}"); + assert!(msg.contains("host-only"), "message: {msg}"); + assert!(msg.contains("--verifier-url https://contracts.tempo.xyz/api"), "message: {msg}"); + assert!(msg.contains("--verifier sourcify"), "message: {msg}"); + } + + /// Sourcify and other non-etherscan verifiers accept host-only URLs; we must not emit the + /// hint for them, otherwise we would mislead the user into editing a correct URL. + #[test] + fn wrap_verifier_url_error_does_not_hint_for_non_etherscan_provider() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("https://contracts.tempo.xyz"), false); + assert_eq!(wrapped.to_string(), "upstream failure"); + } + + #[test] + fn wrap_verifier_url_error_reports_invalid_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("not a url"), true); + let msg = format!("{wrapped:#}"); + assert!(msg.contains("Invalid URL"), "message: {msg}"); + } } diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index cab8462ebb25e..27f7975d283c2 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -4,7 +4,7 @@ use crate::{ RetryArgs, etherscan::EtherscanVerificationProvider, provider::{VerificationContext, VerificationProvider, VerificationProviderType}, - utils::is_host_only, + utils::wrap_verifier_url_error, }; use alloy_primitives::{Address, TxHash, map::HashSet}; use alloy_provider::Provider; @@ -20,7 +20,6 @@ use foundry_config::{ Chain, Config, SolcReq, figment, impl_figment_convert, impl_figment_convert_cast, }; use itertools::Itertools; -use reqwest::Url; use semver::BuildMetadata; use std::path::PathBuf; @@ -282,25 +281,28 @@ impl VerifyArgs { { sh_println!("Constructor args: {args}")? } - self.verifier.verifier.client(self.etherscan.key().as_deref(), self.etherscan.chain, self.verifier.verifier_url.is_some())?.verify(self, context).await.map_err(|err| { - if let Some(verifier_url) = verifier_url { - match Url::parse(&verifier_url) { - Ok(url) if is_host_only(&url) => { - return err.wrap_err(format!( - "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" - )) - } - Err(url_err) => { - return err.wrap_err(format!( - "Invalid URL {verifier_url} provided: {url_err}" - )) - } - _ => {} - } - } - - err - }) + // `client()` picks Etherscan when `--verifier etherscan` is passed, or when + // `ETHERSCAN_API_KEY` is set and no other provider was explicitly chosen. This mirrors + // that selection closely enough to decide whether the host-only URL hint applies. + let etherscan_key = self.etherscan.key(); + let using_etherscan = self.verifier.verifier.is_etherscan() + || (etherscan_key.as_deref().is_some_and(|k| !k.is_empty()) + && !matches!( + self.verifier.verifier, + VerificationProviderType::Blockscout + | VerificationProviderType::Oklink + | VerificationProviderType::Custom + )); + self.verifier + .verifier + .client( + etherscan_key.as_deref(), + self.etherscan.chain, + self.verifier.verifier_url.is_some(), + )? + .verify(self, context) + .await + .map_err(|err| wrap_verifier_url_error(err, verifier_url.as_deref(), using_etherscan)) } /// Returns the configured verification provider diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml deleted file mode 100644 index 0f30346c72039..0000000000000 --- a/crates/wallets/Cargo.toml +++ /dev/null @@ -1,69 +0,0 @@ -[package] -name = "foundry-wallets" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -foundry-config.workspace = true - -alloy-primitives.workspace = true -alloy-signer = { workspace = true, features = ["eip712"] } -alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } -alloy-signer-ledger = { workspace = true, features = ["eip712"] } -alloy-signer-trezor.workspace = true -alloy-network.workspace = true -alloy-consensus.workspace = true -alloy-sol-types.workspace = true -alloy-dyn-abi.workspace = true - -# browser wallet -axum.workspace = true -foundry-common.workspace = true -serde_json.workspace = true -tokio = { workspace = true, features = ["macros"] } -uuid.workspace = true -webbrowser = "1.0.6" - -# aws-kms -alloy-signer-aws = { workspace = true, features = ["eip712"], optional = true } -aws-config = { version = "1", default-features = true, optional = true } - -# gcp-kms -alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } - -# turnkey -alloy-signer-turnkey = { workspace = true, features = ["eip712"], optional = true } - -tempo-primitives.workspace = true -alloy-rlp.workspace = true - -async-trait.workspace = true -clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -derive_builder = "0.20" -dirs.workspace = true -eyre.workspace = true -rpassword = "7" -serde.workspace = true -thiserror.workspace = true -toml.workspace = true -tower.workspace = true -tower-http = { workspace = true, features = ["cors", "set-header"] } -tracing.workspace = true -eth-keystore = "0.5.0" - -[dev-dependencies] -reqwest = { workspace = true, features = ["json"] } - -[features] -aws-kms = ["dep:alloy-signer-aws", "dep:aws-config"] -gcp-kms = ["dep:alloy-signer-gcp"] -turnkey = ["dep:alloy-signer-turnkey"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs deleted file mode 100644 index a0f9628956469..0000000000000 --- a/crates/wallets/src/error.rs +++ /dev/null @@ -1,75 +0,0 @@ -use alloy_primitives::hex::FromHexError; -use alloy_signer::k256::ecdsa; -use alloy_signer_ledger::LedgerError; -use alloy_signer_local::LocalSignerError; -use alloy_signer_trezor::TrezorError; - -#[cfg(feature = "aws-kms")] -use alloy_signer_aws::AwsSignerError; - -#[cfg(feature = "gcp-kms")] -use alloy_signer_gcp::GcpSignerError; - -#[cfg(feature = "turnkey")] -use alloy_signer_turnkey::TurnkeySignerError; - -use crate::wallet_browser::error::BrowserWalletError; - -#[derive(Debug, thiserror::Error)] -pub enum PrivateKeyError { - #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] - InvalidHex(#[from] FromHexError), - #[error( - "Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?" - )] - ExistsAsEnvVar(String), -} - -#[derive(Debug, thiserror::Error)] -pub enum WalletSignerError { - #[error(transparent)] - Local(#[from] LocalSignerError), - #[error("Failed to decrypt keystore: incorrect password")] - IncorrectKeystorePassword, - #[error(transparent)] - Ledger(#[from] LedgerError), - #[error(transparent)] - Trezor(#[from] TrezorError), - #[error(transparent)] - #[cfg(feature = "aws-kms")] - Aws(#[from] Box), - #[error(transparent)] - #[cfg(feature = "gcp-kms")] - Gcp(#[from] Box), - #[error(transparent)] - #[cfg(feature = "turnkey")] - Turnkey(#[from] TurnkeySignerError), - #[error(transparent)] - Browser(#[from] BrowserWalletError), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - InvalidHex(#[from] FromHexError), - #[error(transparent)] - Ecdsa(#[from] ecdsa::Error), - #[error("foundry was not built with support for {0} signer")] - UnsupportedSigner(&'static str), -} - -impl WalletSignerError { - pub const fn aws_unsupported() -> Self { - Self::UnsupportedSigner("AWS KMS") - } - - pub const fn gcp_unsupported() -> Self { - Self::UnsupportedSigner("Google Cloud KMS") - } - - pub const fn turnkey_unsupported() -> Self { - Self::UnsupportedSigner("Turnkey") - } - - pub const fn browser_unsupported() -> Self { - Self::UnsupportedSigner("Browser Wallet") - } -} diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs deleted file mode 100644 index 435da79ace3d8..0000000000000 --- a/crates/wallets/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! # foundry-wallets -//! -//! Utilities for working with multiple signers. - -#![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg))] - -#[macro_use] -extern crate foundry_common; - -#[macro_use] -extern crate tracing; - -pub mod error; -pub mod opts; -pub mod signer; -pub mod tempo; -pub mod utils; -pub mod wallet_browser; -pub mod wallet_multi; -pub mod wallet_raw; - -pub use opts::WalletOpts; -pub use signer::{PendingSigner, WalletSigner}; -pub use tempo::TempoAccessKeyConfig; -pub use wallet_browser::opts::BrowserWalletOpts; -pub use wallet_multi::MultiWalletOpts; -pub use wallet_raw::RawWalletOpts; - -#[cfg(feature = "aws-kms")] -use aws_config as _; diff --git a/crates/wallets/src/opts.rs b/crates/wallets/src/opts.rs deleted file mode 100644 index 49a15a6365979..0000000000000 --- a/crates/wallets/src/opts.rs +++ /dev/null @@ -1,330 +0,0 @@ -use crate::{signer::WalletSigner, tempo::TempoAccessKeyConfig, utils, wallet_raw::RawWalletOpts}; -use alloy_primitives::Address; -use alloy_signer::Signer; -use clap::Parser; -use eyre::Result; -use serde::Serialize; - -/// The wallet options can either be: -/// 1. Raw (via private key / mnemonic file, see `RawWallet`) -/// 2. Keystore (via file path) -/// 3. Ledger -/// 4. Trezor -/// 5. AWS KMS -/// 6. Google Cloud KMS -/// 7. Turnkey -/// 8. Browser wallet -#[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct WalletOpts { - /// The sender account. - #[arg( - long, - short, - value_name = "ADDRESS", - help_heading = "Wallet options - raw", - env = "ETH_FROM" - )] - pub from: Option
, - - #[command(flatten)] - pub raw: RawWalletOpts, - - /// Use the keystore in the given folder or file. - #[arg( - long = "keystore", - help_heading = "Wallet options - keystore", - value_name = "PATH", - env = "ETH_KEYSTORE" - )] - pub keystore_path: Option, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[arg( - long = "account", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAME", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_path" - )] - pub keystore_account_name: Option, - - /// The keystore password. - /// - /// Used with --keystore. - #[arg( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD" - )] - pub keystore_password: Option, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[arg( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD_FILE", - env = "ETH_PASSWORD" - )] - pub keystore_password_file: Option, - - /// Use a Ledger hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - /// - /// Ensure the AWS_KMS_KEY_ID environment variable is set. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] - pub aws: bool, - - /// Use Google Cloud Key Management Service. - /// - /// Ensure the following environment variables are set: GCP_PROJECT_ID, GCP_LOCATION, - /// GCP_KEY_RING, GCP_KEY_NAME, GCP_KEY_VERSION. - /// - /// See: - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] - pub gcp: bool, - - /// Use Turnkey. - /// - /// Ensure the following environment variables are set: TURNKEY_API_PRIVATE_KEY, - /// TURNKEY_ORGANIZATION_ID, TURNKEY_ADDRESS. - /// - /// See: - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))] - pub turnkey: bool, - - /// Tempo access key private key. - /// - /// When set, the transaction is signed with this access key on behalf of - /// `--tempo.root-account`. - #[arg( - long = "tempo.access-key", - help_heading = "Wallet options - Tempo", - value_name = "PRIVATE_KEY", - env = "TEMPO_ACCESS_KEY" - )] - pub tempo_access_key: Option, - - /// Tempo root account address (the `from` address for keychain transactions). - /// - /// Required when `--tempo.access-key` is set. - #[arg( - long = "tempo.root-account", - help_heading = "Wallet options - Tempo", - value_name = "ADDRESS", - requires = "tempo_access_key", - env = "TEMPO_ROOT_ACCOUNT" - )] - pub tempo_root_account: Option
, -} - -impl WalletOpts { - /// Attempts to resolve a signer from the configured wallet options. - /// - /// Returns the signer and, for Tempo keychain mode, an [`TempoAccessKeyConfig`] describing the - /// root wallet and provisioning data. - /// - /// Returns `Ok((None, None))` if no wallet option was configured and no Tempo fallback - /// matched. - pub async fn maybe_signer( - &self, - ) -> Result<(Option, Option)> { - trace!("start finding signer"); - - // If a Tempo access key is provided on the CLI, use it directly. - if let Some(ref access_key) = self.tempo_access_key { - let root_account = self.tempo_root_account.ok_or_else(|| { - eyre::eyre!("--tempo.root-account is required when --tempo.access-key is set") - })?; - let signer = utils::create_private_key_signer(access_key)?; - let key_address = signer.address(); - let config = TempoAccessKeyConfig { - wallet_address: root_account, - key_address, - key_authorization: None, - }; - return Ok((Some(signer), Some(config))); - } - - let get_env = |key: &str| { - std::env::var(key) - .map_err(|_| eyre::eyre!("{key} environment variable is required for signer")) - }; - - let signer = if self.ledger { - utils::create_ledger_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) - .await? - } else if self.trezor { - utils::create_trezor_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) - .await? - } else if self.aws { - let key_id = get_env("AWS_KMS_KEY_ID")?; - WalletSigner::from_aws(key_id).await? - } else if self.gcp { - let project_id = get_env("GCP_PROJECT_ID")?; - let location = get_env("GCP_LOCATION")?; - let keyring = get_env("GCP_KEY_RING")?; - let key_name = get_env("GCP_KEY_NAME")?; - let key_version = get_env("GCP_KEY_VERSION")? - .parse() - .map_err(|_| eyre::eyre!("GCP_KEY_VERSION could not be parsed into u64"))?; - WalletSigner::from_gcp(project_id, location, keyring, key_name, key_version).await? - } else if self.turnkey { - let api_private_key = get_env("TURNKEY_API_PRIVATE_KEY")?; - let organization_id = get_env("TURNKEY_ORGANIZATION_ID")?; - let address_str = get_env("TURNKEY_ADDRESS")?; - let address = address_str.parse().map_err(|_| { - eyre::eyre!("TURNKEY_ADDRESS could not be parsed as an Ethereum address") - })?; - WalletSigner::from_turnkey(api_private_key, organization_id, address)? - } else if let Some(raw_wallet) = self.raw.signer()? { - raw_wallet - } else if let Some(path) = utils::maybe_get_keystore_path( - self.keystore_path.as_deref(), - self.keystore_account_name.as_deref(), - )? { - let (maybe_signer, maybe_pending) = utils::create_keystore_signer( - &path, - self.keystore_password.as_deref(), - self.keystore_password_file.as_deref(), - )?; - if let Some(pending) = maybe_pending { - pending.unlock()? - } else if let Some(signer) = maybe_signer { - signer - } else { - unreachable!() - } - } else { - // No explicit wallet option was provided. Try Tempo wallet as a fallback - // if `--from` is set. - if let Some(from) = self.from { - match crate::tempo::lookup_signer(from)? { - crate::tempo::TempoLookup::Direct(signer) => { - return Ok((Some(signer), None)); - } - crate::tempo::TempoLookup::Keychain(signer, config) => { - return Ok((Some(signer), Some(*config))); - } - crate::tempo::TempoLookup::NotFound => {} - } - } - - return Ok((None, None)); - }; - - Ok((Some(signer), None)) - } - - pub async fn signer(&self) -> Result { - self.maybe_signer().await?.0.ok_or_else(|| { - eyre::eyre!( - "\ -Error accessing local wallet. Did you pass a keystore, hardware wallet, private key or mnemonic? - -Run the command with --help flag for more information or use the corresponding CLI -flag to set your key via: - ---keystore ---interactive ---private-key ---mnemonic-path ---aws ---gcp ---turnkey ---trezor ---ledger ---browser - -Alternatively, when using the `cast send` or `cast mktx` commands with a local node -or RPC that has unlocked accounts, the --unlocked or --ethsign flags can be used, -respectively. The sender address can be specified by setting the `ETH_FROM` environment -variable to the desired unlocked account address, or by providing the address directly -using the --from flag." - ) - }) - } -} - -impl From for WalletOpts { - fn from(options: RawWalletOpts) -> Self { - Self { raw: options, ..Default::default() } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{path::Path, str::FromStr}; - - #[tokio::test] - async fn find_keystore() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - let password_file = keystore.join("password-ec554"); - let wallet: WalletOpts = WalletOpts::parse_from([ - "foundry-cli", - "--from", - "560d246fcddc9ea98a8b032c9a2f474efb493c28", - "--keystore", - keystore_file.to_str().unwrap(), - "--password-file", - password_file.to_str().unwrap(), - ]); - let signer = wallet.signer().await.unwrap(); - assert_eq!( - signer.address(), - Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() - ); - } - - #[tokio::test] - async fn illformed_private_key_generates_user_friendly_error() { - let wallet = WalletOpts { - raw: RawWalletOpts { - interactive: false, - private_key: Some("123".to_string()), - mnemonic: None, - mnemonic_passphrase: None, - hd_path: None, - mnemonic_index: 0, - }, - from: None, - keystore_path: None, - keystore_account_name: None, - keystore_password: None, - keystore_password_file: None, - ledger: false, - trezor: false, - aws: false, - gcp: false, - turnkey: false, - tempo_access_key: None, - tempo_root_account: None, - }; - match wallet.signer().await { - Ok(_) => { - panic!("illformed private key shouldn't decode") - } - Err(x) => { - assert!( - x.to_string().contains("Failed to decode private key"), - "Error message is not user-friendly" - ); - } - } - } -} diff --git a/crates/wallets/src/signer.rs b/crates/wallets/src/signer.rs deleted file mode 100644 index ddbd5a47fe978..0000000000000 --- a/crates/wallets/src/signer.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::error::WalletSignerError; -use alloy_consensus::SignableTransaction; -use alloy_dyn_abi::TypedData; -use alloy_network::TxSigner; -use alloy_primitives::{Address, B256, ChainId, Signature, hex}; -use alloy_signer::Signer; -use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; -use alloy_signer_local::{MnemonicBuilder, PrivateKeySigner, coins_bip39::English}; -use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; -use alloy_sol_types::{Eip712Domain, SolStruct}; -use async_trait::async_trait; -use std::{collections::HashSet, path::PathBuf}; -use tracing::warn; - -#[cfg(feature = "aws-kms")] -use alloy_signer_aws::{AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient}; - -#[cfg(feature = "gcp-kms")] -use alloy_signer_gcp::{ - GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier, - gcloud_sdk::{ - GoogleApi, - google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, - }, -}; - -#[cfg(feature = "turnkey")] -use alloy_signer_turnkey::TurnkeySigner; - -pub type Result = std::result::Result; - -/// Wrapper enum around different signers. -#[derive(Debug)] -pub enum WalletSigner { - /// Wrapper around local wallet. e.g. private key, mnemonic - Local(PrivateKeySigner), - /// Wrapper around Ledger signer. - Ledger(LedgerSigner), - /// Wrapper around Trezor signer. - Trezor(TrezorSigner), - /// Wrapper around AWS KMS signer. - #[cfg(feature = "aws-kms")] - Aws(AwsSigner), - /// Wrapper around Google Cloud KMS signer. - #[cfg(feature = "gcp-kms")] - Gcp(GcpSigner), - /// Wrapper around Turnkey signer. - #[cfg(feature = "turnkey")] - Turnkey(TurnkeySigner), -} - -impl WalletSigner { - pub async fn from_ledger_path(path: LedgerHDPath) -> Result { - let ledger = LedgerSigner::new(path, None).await?; - Ok(Self::Ledger(ledger)) - } - - pub async fn from_trezor_path(path: TrezorHDPath) -> Result { - let trezor = TrezorSigner::new(path, None).await?; - Ok(Self::Trezor(trezor)) - } - - pub async fn from_aws(key_id: String) -> Result { - #[cfg(feature = "aws-kms")] - { - let config = - alloy_signer_aws::aws_config::load_defaults(BehaviorVersion::latest()).await; - let client = AwsClient::new(&config); - - Ok(Self::Aws( - AwsSigner::new(client, key_id, None) - .await - .map_err(|e| WalletSignerError::Aws(Box::new(e)))?, - )) - } - - #[cfg(not(feature = "aws-kms"))] - { - let _ = key_id; - Err(WalletSignerError::aws_unsupported()) - } - } - - pub async fn from_gcp( - project_id: String, - location: String, - keyring: String, - key_name: String, - key_version: u64, - ) -> Result { - #[cfg(feature = "gcp-kms")] - { - let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring); - let client = match GoogleApi::from_function( - KeyManagementServiceClient::new, - "https://cloudkms.googleapis.com", - None, - ) - .await - { - Ok(c) => c, - Err(e) => { - return Err(WalletSignerError::Gcp(Box::new(GcpSignerError::GoogleKmsError( - e, - )))); - } - }; - - let specifier = KeySpecifier::new(keyring, &key_name, key_version); - - Ok(Self::Gcp( - GcpSigner::new(client, specifier, None) - .await - .map_err(|e| WalletSignerError::Gcp(Box::new(e)))?, - )) - } - - #[cfg(not(feature = "gcp-kms"))] - { - let _ = project_id; - let _ = location; - let _ = keyring; - let _ = key_name; - let _ = key_version; - Err(WalletSignerError::gcp_unsupported()) - } - } - - pub fn from_turnkey( - api_private_key: String, - organization_id: String, - address: Address, - ) -> Result { - #[cfg(feature = "turnkey")] - { - Ok(Self::Turnkey(TurnkeySigner::from_api_key( - &api_private_key, - organization_id, - address, - None, - )?)) - } - - #[cfg(not(feature = "turnkey"))] - { - let _ = api_private_key; - let _ = organization_id; - let _ = address; - Err(WalletSignerError::turnkey_unsupported()) - } - } - - pub fn from_private_key(private_key: &B256) -> Result { - Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?)) - } - - /// Returns a list of addresses available to use with current signer - /// - /// - for Ledger and Trezor signers the number of addresses to retrieve is specified as argument - /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy - /// derivation paths - /// - for Local and AWS signers the result contains a single address - /// - errors when retrieving addresses are logged but do not prevent returning available - /// addresses - pub async fn available_senders(&self, max: usize) -> Result> { - let mut senders = HashSet::new(); - - match self { - Self::Local(local) => { - senders.insert(local.address()); - } - Self::Ledger(ledger) => { - // Try LedgerLive derivation path - for i in 0..max { - match ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await { - Ok(address) => { - senders.insert(address); - } - Err(e) => { - warn!("Failed to get Ledger address at index {i} (LedgerLive): {e}"); - } - } - } - // Try Legacy derivation path - for i in 0..max { - match ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await { - Ok(address) => { - senders.insert(address); - } - Err(e) => { - warn!("Failed to get Ledger address at index {i} (Legacy): {e}"); - } - } - } - } - Self::Trezor(trezor) => { - for i in 0..max { - match trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await { - Ok(address) => { - senders.insert(address); - } - Err(e) => { - warn!("Failed to get Trezor address at index {i} (TrezorLive): {e}",); - } - } - } - } - #[cfg(feature = "aws-kms")] - Self::Aws(aws) => { - senders.insert(alloy_signer::Signer::address(aws)); - } - #[cfg(feature = "gcp-kms")] - Self::Gcp(gcp) => { - senders.insert(alloy_signer::Signer::address(gcp)); - } - #[cfg(feature = "turnkey")] - Self::Turnkey(turnkey) => { - senders.insert(alloy_signer::Signer::address(turnkey)); - } - } - Ok(senders.into_iter().collect()) - } - - pub fn from_mnemonic( - mnemonic: &str, - passphrase: Option<&str>, - derivation_path: Option<&str>, - index: u32, - ) -> Result { - let mut builder = MnemonicBuilder::::default().phrase(mnemonic); - - if let Some(passphrase) = passphrase { - builder = builder.password(passphrase) - } - - builder = if let Some(hd_path) = derivation_path { - builder.derivation_path(hd_path)? - } else { - builder.index(index)? - }; - - Ok(Self::Local(builder.build()?)) - } -} - -macro_rules! delegate { - ($s:ident, $inner:ident => $e:expr) => { - match $s { - Self::Local($inner) => $e, - Self::Ledger($inner) => $e, - Self::Trezor($inner) => $e, - #[cfg(feature = "aws-kms")] - Self::Aws($inner) => $e, - #[cfg(feature = "gcp-kms")] - Self::Gcp($inner) => $e, - #[cfg(feature = "turnkey")] - Self::Turnkey($inner) => $e, - } - }; -} - -#[async_trait] -impl Signer for WalletSigner { - /// Signs the given hash. - async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_hash(hash)).await - } - - async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_message(message)).await - } - - fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) - } - - fn chain_id(&self) -> Option { - delegate!(self, inner => inner.chain_id()) - } - - fn set_chain_id(&mut self, chain_id: Option) { - delegate!(self, inner => inner.set_chain_id(chain_id)) - } - - async fn sign_typed_data( - &self, - payload: &T, - domain: &Eip712Domain, - ) -> alloy_signer::Result - where - Self: Sized, - { - delegate!(self, inner => inner.sign_typed_data(payload, domain)).await - } - - async fn sign_dynamic_typed_data( - &self, - payload: &TypedData, - ) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await - } -} - -#[async_trait] -impl TxSigner for WalletSigner { - fn address(&self) -> Address { - Signer::address(self) - } - - async fn sign_transaction( - &self, - tx: &mut dyn SignableTransaction, - ) -> alloy_signer::Result { - delegate!(self, inner => TxSigner::sign_transaction(inner, tx)).await - } -} - -/// Signers that require user action to be obtained. -#[derive(Debug, Clone)] -pub enum PendingSigner { - Keystore(PathBuf), - Interactive, -} - -impl PendingSigner { - pub fn unlock(self) -> Result { - match self { - Self::Keystore(path) => { - let password = rpassword::prompt_password("Enter keystore password:")?; - match PrivateKeySigner::decrypt_keystore(path, password) { - Ok(signer) => Ok(WalletSigner::Local(signer)), - Err(e) => match e { - // Catch the `MacMismatch` error, which indicates an incorrect password and - // return a more user-friendly `IncorrectKeystorePassword`. - alloy_signer_local::LocalSignerError::EthKeystoreError( - eth_keystore::KeystoreError::MacMismatch, - ) => Err(WalletSignerError::IncorrectKeystorePassword), - _ => Err(WalletSignerError::Local(e)), - }, - } - } - Self::Interactive => { - let private_key = rpassword::prompt_password("Enter private key:")?; - Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?) - } - } - } -} diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs deleted file mode 100644 index e0a05d1212eae..0000000000000 --- a/crates/wallets/src/tempo.rs +++ /dev/null @@ -1,160 +0,0 @@ -use alloy_primitives::{Address, hex}; -use alloy_rlp::Decodable; -use eyre::Result; -use std::path::PathBuf; -use tempo_primitives::transaction::SignedKeyAuthorization; - -use crate::{WalletSigner, utils}; - -/// Wallet type: how this wallet was created. -#[derive(Clone, Copy, Default, serde::Deserialize)] -#[serde(rename_all = "lowercase")] -enum WalletType { - #[default] - Local, - Passkey, -} - -/// Cryptographic key type. -#[derive(Clone, Copy, Default, serde::Deserialize)] -#[serde(rename_all = "lowercase")] -enum KeyType { - #[default] - Secp256k1, - P256, - WebAuthn, -} - -/// A single entry from Tempo's `keys.toml`. -#[derive(serde::Deserialize)] -#[allow(dead_code)] -struct KeyEntry { - #[serde(default)] - wallet_type: WalletType, - #[serde(default)] - wallet_address: Address, - #[serde(default)] - chain_id: u64, - #[serde(default)] - key_type: KeyType, - #[serde(default)] - key_address: Option
, - #[serde(default)] - key: Option, - #[serde(default)] - key_authorization: Option, - #[serde(default)] - expiry: Option, - #[serde(default)] - limits: Vec, -} - -/// Per-token spending limit stored in `keys.toml`. -#[derive(serde::Deserialize)] -struct StoredTokenLimit { - #[allow(dead_code)] - currency: Address, - #[allow(dead_code)] - limit: String, -} - -/// The top-level structure of `~/.tempo/wallet/keys.toml`. -#[derive(serde::Deserialize)] -struct KeysFile { - #[serde(default)] - keys: Vec, -} - -/// Configuration for a Tempo access key (keychain mode). -/// -/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer -/// is an access key that signs on behalf of the root wallet. This struct carries the metadata -/// needed to construct the correct transaction. -#[derive(Debug, Clone)] -pub struct TempoAccessKeyConfig { - /// The root wallet address (the `from` address for transactions). - pub wallet_address: Address, - /// The access key's address (derived from the private key that actually signs). - pub key_address: Address, - /// Decoded key authorization for on-chain provisioning. - /// - /// When present, callers should check whether the key is already provisioned on-chain - /// (via the AccountKeychain precompile) before including this in a transaction. - pub key_authorization: Option, -} - -/// Result of looking up an address in Tempo's key store. -pub enum TempoLookup { - /// A direct (EOA) signer was found — `wallet_address == key_address`. - Direct(WalletSigner), - /// A keychain (access key) signer was found — `wallet_address != key_address`. - Keychain(WalletSigner, Box), - /// No matching entry was found. - NotFound, -} - -/// Returns the path to Tempo's keys file. -/// -/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. -fn keys_path() -> Option { - let base = std::env::var_os("TEMPO_HOME") - .map(PathBuf::from) - .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; - Some(base.join("wallet").join("keys.toml")) -} - -/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. -fn decode_key_authorization(hex_str: &str) -> Result { - let bytes = hex::decode(hex_str)?; - let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; - Ok(auth) -} - -/// Looks up a signer for the given address in Tempo's `keys.toml`. -/// -/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, -/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, -/// or [`TempoLookup::NotFound`] if no entry matches. -pub fn lookup_signer(from: Address) -> Result { - let path = match keys_path() { - Some(p) if p.is_file() => p, - _ => return Ok(TempoLookup::NotFound), - }; - - let contents = std::fs::read_to_string(&path)?; - let file: KeysFile = toml::from_str(&contents)?; - - for entry in &file.keys { - if entry.wallet_address != from { - continue; - } - - let Some(key) = &entry.key else { - continue; - }; - - // Direct mode: wallet_address == key_address (or key_address is absent). - let is_direct = - entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); - - let signer = utils::create_private_key_signer(key)?; - - if is_direct { - return Ok(TempoLookup::Direct(signer)); - } - - // Keychain mode: the key is an access key signing on behalf of wallet_address. - let key_authorization = - entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; - - let config = TempoAccessKeyConfig { - wallet_address: entry.wallet_address, - // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address - key_address: entry.key_address.unwrap(), - key_authorization, - }; - return Ok(TempoLookup::Keychain(signer, Box::new(config))); - } - - Ok(TempoLookup::NotFound) -} diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs deleted file mode 100644 index 6849125e3f084..0000000000000 --- a/crates/wallets/src/utils.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::{PendingSigner, WalletSigner, error::PrivateKeyError}; -use alloy_primitives::{B256, hex::FromHex}; -use alloy_signer_ledger::HDPath as LedgerHDPath; -use alloy_signer_local::PrivateKeySigner; -use alloy_signer_trezor::HDPath as TrezorHDPath; -use eyre::{Context, Result}; -use foundry_config::Config; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -fn ensure_pk_not_env(pk: &str) -> Result<()> { - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into()); - } - Ok(()) -} - -/// Validates and sanitizes user inputs, returning configured [WalletSigner]. -pub fn create_private_key_signer(private_key_str: &str) -> Result { - let Ok(private_key) = B256::from_hex(private_key_str) else { - ensure_pk_not_env(private_key_str)?; - eyre::bail!("Failed to decode private key") - }; - match PrivateKeySigner::from_bytes(&private_key) { - Ok(pk) => Ok(WalletSigner::Local(pk)), - Err(err) => { - ensure_pk_not_env(private_key_str)?; - eyre::bail!("Failed to create wallet from private key: {err}") - } - } -} - -/// Creates [WalletSigner] instance from given mnemonic parameters. -/// -/// Mnemonic can be either a file path or a mnemonic phrase. -pub fn create_mnemonic_signer( - mnemonic: &str, - passphrase: Option<&str>, - hd_path: Option<&str>, - index: u32, -) -> Result { - let mnemonic = if Path::new(mnemonic).is_file() { - fs::read_to_string(mnemonic)? - } else { - mnemonic.to_owned() - }; - let mnemonic = mnemonic.split_whitespace().collect::>().join(" "); - - Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) -} - -/// Creates [WalletSigner] instance from given Ledger parameters. -pub async fn create_ledger_signer( - hd_path: Option<&str>, - mnemonic_index: u32, -) -> Result { - let derivation = if let Some(hd_path) = hd_path { - LedgerHDPath::Other(hd_path.to_owned()) - } else { - LedgerHDPath::LedgerLive(mnemonic_index as usize) - }; - - WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| { - "\ -Could not connect to Ledger device. -Make sure it's connected and unlocked, with no other desktop wallet apps open." - }) -} - -/// Creates [WalletSigner] instance from given Trezor parameters. -pub async fn create_trezor_signer( - hd_path: Option<&str>, - mnemonic_index: u32, -) -> Result { - let derivation = if let Some(hd_path) = hd_path { - TrezorHDPath::Other(hd_path.to_owned()) - } else { - TrezorHDPath::TrezorLive(mnemonic_index as usize) - }; - - WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| { - "\ -Could not connect to Trezor device. -Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." - }) -} - -pub fn maybe_get_keystore_path( - maybe_path: Option<&str>, - maybe_name: Option<&str>, -) -> Result> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - Ok(maybe_path - .map(PathBuf::from) - .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) -} - -/// Creates keystore signer from given parameters. -/// -/// If correct password or password file is provided, the keystore is decrypted and a [WalletSigner] -/// is returned. -/// -/// Otherwise, a [PendingSigner] is returned, which can be used to unlock the keystore later, -/// prompting user for password. -pub fn create_keystore_signer( - path: &PathBuf, - maybe_password: Option<&str>, - maybe_password_file: Option<&str>, -) -> Result<(Option, Option)> { - if !path.exists() { - eyre::bail!("Keystore file `{path:?}` does not exist") - } - - if path.is_dir() { - eyre::bail!( - "Keystore path `{path:?}` is a directory. Please specify the keystore file directly." - ) - } - - let password = match (maybe_password, maybe_password_file) { - (Some(password), _) => Ok(Some(password.to_string())), - (_, Some(password_file)) => { - let password_file = Path::new(password_file); - if password_file.is_file() { - Ok(Some( - fs::read_to_string(password_file) - .wrap_err_with(|| { - format!("Failed to read keystore password file at {password_file:?}") - })? - .trim_end() - .to_string(), - )) - } else { - Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) - } - } - (None, None) => Ok(None), - }?; - - if let Some(password) = password { - let wallet = PrivateKeySigner::decrypt_keystore(path, password) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; - Ok((Some(WalletSigner::Local(wallet)), None)) - } else { - Ok((None, Some(PendingSigner::Keystore(path.clone())))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_private_key_signer() { - let pk = B256::random(); - let pk_str = pk.to_string(); - assert!(create_private_key_signer(&pk_str).is_ok()); - // skip 0x - assert!(create_private_key_signer(&pk_str[2..]).is_ok()); - } -} diff --git a/crates/wallets/src/wallet_browser/app/assets/banner.png b/crates/wallets/src/wallet_browser/app/assets/banner.png deleted file mode 100644 index 2a3752b97fc0c082483daaa3e94a29cec296599b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 559043 zcmXt9RaDdu6aDQjv2-^`x3nNqyCB^y(h|}k-MfT>bV+xKA}JyztO`g-cO%{1vHSgB zzNb5{bIzQZd+xpSURPU%2%jDw001I2RYiROfc{rvL7|ZUpeA;J;Xgp>Y8on0&@y4r z1@SRKn4Q;x9LzmAK^V*s0Wl@F&{HN>9#(D<^hFROC3$hE73Lux27tWWLiUrr?t3GfkrW`9}5g~4>}&jw@eO>=Ve zrZusm)N#_|?J*BuZ;{lCjyP?p-s3HfbK?bhmaLfzRF43F1yECzHw>6-H`6()dQ2hS zgPkf^z(6zIh)x|-rg^HhaQgt89k_p$1#%5u?k+bTEnx$1{mpioPOz$|S<~{r%~iL2 zh~duX@O5tET5O~Dea&s<^EmOv^e={w-1N&vbpu<0s%uYmJaXXqlQIwItxd@9Lh{QK zd+VRO?_bAWD}P%3)BKA=so!OnImDIETrH9&sfT1$CpBm)WZfXu=2G^0lOgZ{7!t{iq$YxytBB@oOt1DvSJHi;kIGeQ9EBP0yD@h4^p@PL?O|?wQ<*`zPHZac+f7WKioS>R+&OMOv8VS3T(k1 z9JV+UlTgkHOen39@2Iaitb`dZF_uIZnI>%VRzgsdl?47>My^CBueDi=oojofp@)rW z(ZivU9jEEtlT;vGmg$_DO8m(H0xJ@!{PH&po6iJ;6f5{~q|E}m7dvv*V7?ZCMlvk@ zN#AV;sx~K%Vn2R598**K}CWBQ=i-%(O1KWU80K!Go*Sg(lys!X-Fz%U$yaUGX z8EiI?Ud`f>!O&#+K9=2SG0%=p2e-GeW2C5Sf||mahZ+#@sDu}5i~Qq!^_#nqngGe{ z&eNUAFUe52$VZKLbv;!rA~Sx{PBy#&`rGTsfo~IWXJBdDcg)$+Kkni&mOB2$c@Ku; zBdZtM96-30hF~srt*KFXr^~`>sFYZ}N{4mY%7$}?y}^*QTeAdA3HOsw`ui6@e<}aW z7h^F>w(?CQjW~Q+EZ&N*8X7qeHW8~v&b^v*L`5cb{93uahr~Q+(@fRvl!3%4K*~rN zv6;2-IGAMhakLbyZaVd?PIpLhZfw+_vG^0j7qZ}<|K zjd@LlOt5DA2TO|}#Ar>JY7=W(Z!=2+!HO%r;E_|9A}*X7bxpFr!UC>al!pX#97Ywq zp~{ALjRkmiF5{*HL5+Pk?EG>*EW0{8Dl%gOLA&D~inzp5ZnsIS2>mB4*~{>Uu~`R38g4bp(F!|%IQNxvWWn>;f2V80 zVZrT##6wC23vN;d+ao0GBJIJ&5|R;33_r4&0_?ZW5{YDG^#8Ib*}h8}8=f^2#|>Fr z!KrGueWh{?GtwbLQkMnV3QL#{w?+7?%GX+V>@}8`_Zi&Keci~5b2Kj1v=OS@&sy`b z4)k4n`+@i`)Gz<Yl#Lv43Wz9iihv@V{_ zzdaWxlM)cS8(mDxTr8U&DX^wRQq#62Y)UNLMI+snbOw=NUgCTdJMNIT96bO%X6C-5 z+Q$eepx8?U=I+cIFdlYtOXMMT*Raelcx@S6Wvp||9H4!Q@UR{Ooj4@mnEyTNb2L}zW0-c@mHy2Cfpyr5MA~t zG`jo?Vgl^<_lv6OSaml2OXi+3836GSaQr+Xbf=C#>^5k!?1=~LMP_z};{b##mvP~= zsFi$inMnHg<;V8D7t5)@px_*xM|*ys=#w?6CkNgRIF{EKMqIate4z>p7hCWPpL_Ao zl#MvYk8H#TawtNDNk*Ob2{HY@>`U=BPc;e_oZi$JzKm>b!@z4gA}|^8tN99ZWtywO z8+aZ3l*o#cW#Oa;-04W?{E&C#ZKmYvsiQi* z5fQOef*$(*Z5awN?dvAo$?-=*E>cwS-i6CxSbzmubnSd?xD6md4I;hNyHtUp)Kx&} z?^}51(;|R99Tr9{Dl8nck1kx{Kp{r(ECFjBPs~xREZ**DjP}$y!QY*j3OZy`DNPnN z0@-tDDg9vICQQIWo;n()CjBF?UellgqDU9O?1G8aI*-i2)L;R{pjD7b@9l89{f-#X zOdJ&1$p&cD%7UjE35qCz1=m=tuGjZ(nZm)KEG$5eaiK_Rm*~=6FljTszW5;M&ix_5 zMeq;0EU#9CJFnr&QzB%cdRrmxCNU_{-W^_0U4>wvb!RWgFva`KBh4 zb|}#QTM(F^yu>)X?fTGBJas1%sP0zHzc#}i&NjHOCNzzY{1%)JKWec3Tx0XTDg%^E zVEq`XzM*t7_sjpCRiD;G)V@Y7)}C-F^^8#8k^~1^Za{~%99_?RiASQmr@h`YsZAHo zKPSn%StVfaryYih`wN9I%K6LYKo|S(>csYc&TPrS2D0M)d^23l6@%t#8M}VovPpKo zwHnO=c&{U(d{?;myebDo@p*^noYcv}Be24dSDDvgwx(++PciF*AKOxHCCC zY(PdKXoWH#*jL%ImJ+Qvf_{YY0!7m}h}13Lyh7o(-Lw~MYrlEdh2eHbRdujxJUh!` zzaSrV#WPr#RVWAOFP4`o5DXN(L_Y@$*VC{#0cp+})Bx!~Cl_#u3bRN7bNqz;>`VnW zQ9?X_fboFnVb8R%Q`*$3{Y_nsh~Ysa5RjuVe5g2bXT58j=plQb8YdYznM%Ua{$@NA z=EC3c-kJN6_5iB_o`4dE53%ND^f*Kbe~sf$Ah-AqUOZcO}$2RBY68uv;h zzVjQrZ-dv4ToR`XY!AvP3nH~4$z7#JXz*M2)5;GRNkYO$Z(WU~x^{9Q5+MhcOkYWp z>EIRQr@9@-{Isj!KPh{ay+m7ph<1--}V(C4t_e--n2Bz4*oRge8u! zi~__fueO>h)gSPxY5NCT|J%vK`Hp+t0d;|=FsB_ikAjgzd3BRe3v6&uT;5lMu4fRx zz`_xH+AmmAT;#l@q)_Z0YLmiHn-!z|hu}*JQ+aVesdI@gr4*gow_L0#!jWzN{#k3) zF`w1R6Ohc8?_Cp(kYYxFSE7xr0o4N>bcWr`Sj^Qu5xA()#N4j9K}yi$|AhMPt8< zzT7tu?q)Hk|f+Ep&C@1Z9yD>g{_owv8=%^SmZ*dLAOKO}m`R;!3KZC3m`3 z?nfW0l*N%%R1(ASlm{EWr!?{DfhHg6nCY$ku*hj*+q#mt^!X@@IX+eQLXB)rYAu98 z+5_S~^4j4njMyzHTT-b1gzhwkqy+fu&AVCeZ9VlbYKQ<-#DdL4{A3&0)n(wy(1;59 zb;{ki5T^;8i4BcaV%T=sNdk^{!+Yd3FJ4I8B?E+vJJG#x44%0fZnGN}#{bNrqdOLe z!@n^Tjo*O*k7xna8YEsdLOBe}Q#guu-EFIL=LZmz49N5y{~=jHc;U&|{wQGl`?2(j zxA&-56#0I054%0nzCQ>vdZvKDZnV!P=K+t#J%`tj=n=$RJUk%2SeSY2_7f||MB7aW z)DJu_RpR@{R%yt3l7qnvXul4jn_p9Vxs^BO<>JK4TK}-P>A-{`03)|r?ECP0?xp0? z#llIEEh3`o4<6-c#MMdtup%n^gsB@$%dpbfLQ_Wl-lwafn9 zLS_0Og(XY~dc<|b`R}&3ysM7BktL!)H#pVDzix3e@%wum@V(K&vKqvinK_Fr3{&cr zm%R|R8m@9IiZuO7KOSfloY0yV6`IVNXMmh*6~U+1-1gU&Gs>`8NHGZqIkNw{5{Bz1 zXw0i~VHYaTkQ@&#N$@3Wg_T%`Jfk>xM^u3)1?%Ww|tKVKwnclL! zOlHiB)+Hb{V80#bf(AZ(Znqqu@LS+k-er#22UZ-rj~10+y(XQ8x!OCb@nyI6NCA4# z^}odCLra)VFBL&~CZv*9ab$H_K=!TB(k*u^=u|P(Qss~$ZdV(O`8t_Y5r_i~2-}J) zHM*v`kp~ewRupResJ$}Ol5dIZG~+;OIDx!qjw7cplL}?^fGc0=kkhp4+fF`QO8!XA zdnjnXoF-tFyhyIVzXZgGuGIv}LCvqE1O8C-gVbp};a$)+p#7mcbmIjiRVOl?_eG|) z=|oKlp5Bhf_ldjl}PTYyL5~(iFH*|Ik^_`fQhYz$w%4;Z|xFjtY4X=kv=i$d93*nI$Bawj@Ev{RX{FY1#IdOU=mfC$vy$Bejs} zPauX`p^Vq1C5)!5EF!@34KO1P5NRZST@5FM_so1_eQupK@eMJrNRO-7MbSc`E!l!t z@lYL$;-x@x+3IIDL^exL#ae&tOP~+UZekZjq1ac^^K|Q4ib!XnjZ-0mJ`Nl7_2R2< z0X5h{g+5n?7)5a`;#Lr?IF!^ZNvcgSYK~`{4w2AV&Y0)qZjd3zc<0f3i$KdSz=kma zZ@4RHEr6$r;7UAHNY?Nc|Av(dz$jlQywzF90S~;|x-qH4L;#fP)(@P2T08;D(%;5G zs~C-hP)oT1#X%pb3K>($iE5R@~jl=xZds_ z(^4??4obg}NZ#YuHZ=J@`7y;Y0QD3ntVC+!^IsDxMr*D_N8X1D@6w=TaGjyCc>ErFopAv);wqEy=$^aoOZ&MlQU+ zXJJinZ?UQoDRCtljvG)Y{t63_r?Y?k&_@78G5`77SsRzh`^=;Cvn&+0_lV_3B^c}b zj#+E>m46~cRrK@bsw90#JAJ!stxNe%E(>Q1yMUyUeu<#_S z@5fjU&Cz5_GrYs9cARy3-kTL(G(Ovj6>+-bom!YtQ^-5cc*8f`P4iQ?pnNuGZY1?( zM6nvJQ@6;Q$RrUJuo2H+0arzQ1(?(8qEch0#_+TljC_|?)a8Jf0fkQJp#q2_oG*`V z^69gYoy=nHSC_O@w(?d{C(Il}(G9^3*I$rr2=z%Tp{28ptt$s799`^ykN+?|BA#OJM?wz|Nh zfV?+{-1mh>4=vax@kG`_>ln6+5V>MUYpa$}PU<`&u<*DM8&3c8j8t&*6y`!~8m)2& z5(OXsPDQSoh140=Hvu0F#z{xJFvWRD`H9s%=1c}02;L>4)}fmuqG;qPD-cCWCA{Lt z#q&N7iyGHa1*s+pX#Sx!?@DU(+(k%J9tfo%Z+(;PHtVY}96TyJK8`9qKCZIi< z4-_e?(E6Fwi=%^V;#I3>W7XHgw&*Sb6yzFSJfj%^Eh#R3c)W2?yzUmjLTLl%agJ#2 zRE3!F{P~`*%S&kBNQ6#SfWap#-0C!p*hZhcey@ZiovV&6+xAY}7_T*&_X+F+vjL@C zpk8d@-4QJkEwUrw898{unf%l72D4G`v&>cfh2ksb3#1(=#9Q=jj~#=n`%g%9;m}@$ z>ufa9xl_zzzbJx4DO})jsBx9ghtqHUZDqX!ue<=j6zIm4Oqo&(3u@^9Q|2AuM2UP& zvsEBl)DI^Su(lFk%$<8G|Jos?N;cimO7pah{Vm3b4H4I0g~T9^_etOV~96W zK4X}zFlp?OD+t!{+I12bQ2h30zuMQGDF7z-8h>%%X{}PwadX3Nrz)Vqj%q?Ku&K@t z5yAV``@~>RK?Cp8BD804d60$p_bDCJ`%HvdMPW!wk;>0jejdCrZrThwq#p57Vg}pPs2@z2i@t-x`6u>}ke};KZn~$jxEHvh6rWbd z{3(qTB>+cKBx{UTq?;i4i`}Q%4?ehQWfYEGAr<3G%I^w&BSta~ng`xrXX5@s4-Hqa z9sASyeB%3&O0cDuahZoKQHuYf0I60P&p>KBshL7n30KVKHI z6BRhARtORhe<(fP`^y$aiOy_5C{bF=B8C%C`60-3KodK{aBX_)Xr$8)GrTEh@4m(n z*6#PW2$NzOZGN$1C>KXow2&)*aragPK@8@Z9=JycG_(?o-+R|gq))UNa$AGI89Brz ztoQ6!ps$ISW{-az$z4iuSNaj;ft!c!?!Fy5oMT>oMjV0)k_x$B(-bm`MLcj*v4wnQ zM7l$Q0`c!K%DBnDL7nB>%UYzmPQc+_?&=R8P&wKPmVymWTY0&K_ui|HM}zX$XL_5b zn+k1h;*U&b8jH(YFLCGhBU{II(IsS_w%3?kVQVTZ4!?CKjdVX#ELs+z6K_kJUUB5N z+6&lM#4BVkmbmN(K`mCD#6j&eD^z~(&hGS-~IJ`X(@ zjb!^d{Jc>Qf`v+6Qzn%BY#92xx{VrD4~qKT{*3yay6eXoUI-!{D5D8_ISmdVEC&;o zj$2PO@|1#Tf9XD+cYi9AnKBivW*q(?xGSjNJ;fO$EdmC9kIdLP?Z21{4G2#2^QoTuMgl4Ym!kuPl9e93%dAr&9(!}5c9<7RPWS_O`3i2=L2v~+eD zELwGTOlz-5JAewiaby16ifv*nYlpX72Bv19$YcT&Rl?+|`DlyIioa;)uHda_VLjR` zu>66GK-M!W5lDvI%IPrn`wKJesh5qmoz$3BiLUM-YRx^1H z8^#R=he;MvV*!uu?3o@iFV4+7FOxP-zse0RfMsd|Pa-zfp~#@sO|>fU)(vI?O!@d< zU^N+u5gyV7RL@}-3Ja+{O4u{3WHUtORaqxxu956AvdFl=ae<3;O(+LZq9qw z^;%Yhew5L*+qG85iNUb(X6@ub750s(8LY5eg`l znhNBWn(=8zWpF`Fl7j`PG~2P4x&WT(9^zPLzH7Vp;3Mp)kzu-7!T9T84l0*8HVBnR zTaNKQ_w~peNsgKW)Vi|d_7$yRRtIn6XhsA8f@VC@^jiiN!GvSjn)QInk)*-%Hm#5BlDeqv#vvj!C2^&{VPoagHGkqq4}3ntVimus2~zA zoTp9TbO1TOWlwQULvAGxcuRgCGG7c>l6`{{R-?(?1IS@PST64LBfJH-D{RT&gOjrw z^wt-E{#a-H1NqGN%qAo>@>%9hcFe;|fwLFMQMQB75ewh}74oG9?o8zc)y)6|U3RS0 zag)#DHCRT5<fH7$H@4zH(Ky4i8^%RCF5E*ik zuz`8E)@i1s`Y5m5NrS95Hc2hWl5%p+fpk_!=T>9jQuR z`g6=6C>MeZc`sQfVOUQBaa~B15{07@Kdn$zubi2E(COgSD0c*KI&CzQYLl7_ju7F- ziz!d2lRsmj`d4o>BxB2{f7GW)>k~5Q75KX`2APs2032)EB#xmkEo(o3B%DJ_Su6h` zN@KzF7dg=aZ<%XUS862CN$FuvEs5O)7V@H-tMV|t^km=!{Vz=k* z8^XK}V^0b%Te`OTe9MgG2_|ubeS>~}YTr+L`cGb*5~=)j5K_fTI)Vcq!4fhUIb592 z!uOfzWs#y@dS~~*|51zrXpa|?z8%Q7;<#!Z`;s}_aaKJn{TzY6%p*MNqCdmUgm6|&ra4-JBzd@xx>QA zfEHN;?n&y*J`FSdXNVo(^2PT@fFO243+Y~Pao$oV?8r9|_4Pf8p_78c7_J>3s;Mq* zo}xHcX!;kLlqZJQyehWRkMeGm9&ZghLZYwxye);)CLz3%;%`^K@8j+&4k8BPqUH9O zN9qTTEQ(f&_bb{rdz0#Xd#^%2iUSgswb`75)y6sy1Vh85?%vieOlkV88U|t$XI7n(C)-3h+vK#(Ioj#4eeW zelYx{bgWjTzOaeGG>`-jT}mFfzcAw|=hU@+T@XmjtyTNc4d@4(Ma_&5+d!_tID}SnA6qAR^3~+CDw8C0Ak1z_=z3D@ByDiTmkt_ z8JPO4lkkwwK=bg;o$1_+K9=ZTj@~yeYcq3?2^+V?d8wCD8hS}UvH)WA^IIiEVWy5A zkP`)C@uqpe+~Ml~z_k5P?g-u_YQTfbdEe1pTE#fyE{8RIA?b57|3giSF7DCm`?HHs zmRLzns)A#DH->j_8(t#Jd;Sn@Al`1i*p+*Yrs4%SFN-D+dK~~1Yuf_BdvnGn4LtK$ zz)*8RhHg?9KFjp4V}kz>1L4RBs9hu2jB10q9gp3Uoe_9LNsYc>K_Z%>A0{b9#1`RDAM?&CPK)iNDkldB^4Y zG%)bTpNQi%2^REqbew#ZFy*s}iZL{!BS9Eiub94Xxra+ELw<&7I$zhQn6!jh(DEnR zRC}|pQXi41=4M%JwD5NpsnJG5FPrv6}R8-{{pBY#(lW1&EW< z$!q=6o01u!lV}-IvO0yg=2U)`*a?35x_|>wU>l;`@o$*&c zuVEyiy*YU%Sjd#OQPRi|#p4<(A_mpZh%b9#If>L|8?B{w?7F$Vt%zMfP6(Ho1&3b{ znRI2g2~aAm{9!5qkpaEDA;Ygbj_NV3nUAyfOHJIq+Tgl3q8;#8UiK|*t>T}jRD0JP z9$O7P+_ZKaPTXiM=1^TPTJ1|Ig6}-IvJ)5^<_I5(Gi0iHAi_eE}XFz#IcCJ;F z;l8GS+a@S2Mej|~5)*Dd8s@G``2X=X+%Nx&G;&k0czJ;QPuSveevTuiW ztu!`?f?2z`(j3eTBqQw;Jz`H|XtMsbOC{2BJ3y=hS2FO6EQ)f#pC4oYb!Nxu zzM(0MDO$x(op<|K*W@mPrswU5?tnA8ld8Rr`OqDNd!zl2+6 zbMY4SUIg2iQL~ZDMqW3MFS^Rtwllks}a2$V9XU|zlBMExa4nokE_PtS)B<_%plc>O_%00YFnh%F}I3KYBq z#J7XsDaG5Ec;Vz!Aad$ENqx^mGzkaa~O z*rFg$;#(KmI0S62aI4W|^_V2wJDuYh)jt8K>-1{cj7Wukon^r&*n1j3y;F+_AjPuI zWfB2bNM933Ji7Ha0V{84%;f3)RGmwZkdRdMYC+BknDk)2>y9fODF6JJTR~4|`k|Eg zh1v&tWvpPExpKP#DffOwMHeQK*j@tUuNK4M`$@n1s(P+9vC$tY?JaPx<(zZl@i%f- zvrnK$R_Vlmgj>ew&)Ujf=ikqlO1*hctbZnNMEt_kKfPX_?BhNJEiE%ko`iGetUdSKx8~(DHMFojOI)8w+79fU2C2CJR@+1WTJL?(dnQQ(#)N;+FgV&#ar7??i?h z^cY>Pf+zp%L(lWj1uHJFxHCS9wCma2=oeCz-J%T2PHk>IP;)RK04?iV{+*Gw^i(pj zdojvCKZ`Sy#IIq zO1)GH^Rf|1s~BS*1ixnh$}zA}X@!qURv&Z`7C&i5@FrGrO&IXb=8n}a7-r|P=+Xo2 zOR}-akR}6Gr4HBsH2IyJt&CEpFwc%_`5vxxq;F_=R>D6c<5=3VvEdc)1)#m-&GY$= ztr16nA6}%4_4op@R@bJa_29tcguPnGfK_ykQ!cT!*Ri>uiVG)#^P+>_Ob!2raUCky zE77mik(|GGL=Z?~AKH<@tL_3Lu@S`&^5lTw1Ca^Pdj!$2uDsB^$310f1E?s9He5ja zFdB@I0@#tlbBzdjNo6C8s(UYjQaO`M{GoqsQ(m=XH-X}IdfOUtJX zFhVI++{26I?*O$&zG-#`WBq$Sc0#H4f3~yL4+`oA{GMD3lODlp%dNSdpW^{j8+v}8 z8U248sTvahDF{FU6qkswYgK9Cc)uc_*;=O8C+=U??olmuAMUk{EODxM3Od+mfC-o ze2k2w?v+gK7Gb$NG8lf9arRNDqv`{&4|;U;7;+!bX%(Z0YQhX}o$aXI7FM$#Hxc?4 z5oz7rM#uF%(7krI_pd!i@K9M(qwYWKQ5a%Q5j+42c!Jt0bZzqwWxDYN{q78Ph|Uky zokB~u-5wGcT)k#Rvme5&Xa5@mAQyWBkUXQ>+PIbKcUc1NH%Acxp{*H0*8ImQJMlbH zdEc^0VWx4%?700P$>(skafs#vESGpi?O|3&1_UQeeu?bfy#W>IHlzQn^I$)~1nua3 zw&0)!taS%E4(%P9pci~g>rKzVNcC5ggq?z21&r0X(0!Ma+j3az{M`in4aY6s&z$sk zmR-62Wr!r;8Wo-R(clF4U)NO4`h{R{Tx+HBgEAHW`?fhy^Kk=Z@o4G$tA@f)Xo44d zmy{^|x^6?R%I)1%Hcp~zm6w4KVLV-@FEZ1w2ClvrcZtxS*>B=~#*$Y6mb9m0^_=!U z$8^jnQb-*jZwDl?mzsc2?rrC5uL(M#B9L2=RkyNF17-D~$uC(3U@0+yTAQHU#=z}= z(VlLK5TpmRFPGW)&ck9*fK$4w%BpS-lJ7jO@elpX$AS1Uv;s7;IvpusAG!IAdHe2a z_80nUi5mVLP{oOIMb6(<2Y|qv!4zopSN;PRZ;+qf&5IMH%$y(t-F?xm6(JAIrSUqKi)wBD;pKJ{<<`*ulb8Qc2%T zq2Xr@Q2AsX^=cK1V|N#vwu^t% zrbMk9_|sFihEeQkZwL^R8Z9PiK~~~cx&0XrJh1oYl|^sV9tv$xBR%b>*BBfwp94Y* zmF`EM_fxqanpo9+$z7evaUdPKxLDad zKE7)KUKZJGsn!Mv>7G&4Y3_p3s~u4BE$a4Glx+fjBcK{eS%{6?ZTW^Vp^ zvhyG>TP);vSA40Q&$Lcg`N~&j2?6-7n4h=b%`ktOAk7qdq|J~kay&vaWbkH$z4^+g zufd6Y7{L)#Nm=M`woN2~G?rilpj#mj^I(0g(jLqjjGXr09HV;tRt6zJfw6piVablT zF~_>h#yZCSEnbJY%8v&IZr*aC>3AZr3E;0WIJOu-M4@K^Q6K;Y&N{|$hsMa;>5&f? z)>Z`Pg&LS6LdABun!9OW9J7bH(31cdQh;-MWF{!)Jko$oPU;tU#ubWsX$v4`0UkmY z2#gf!8w`*eLOK1J9%=sS41%zbqErO{ZmeqYM@MtreNS>_+}g>L(!%%#oSVxNqj?Zs zJy+`wCLS-CA9Acnj$|H68F#v3-x61Iw`2rjaBu?rzcF!1en#4E+y9xjo1WzLtLQf( z3J+~d9PO64jpy>sHPU{ya=t}cLe`yB4|zZBuDmS%#t+-NiPA_)fB*VWf&udx8(Q|2 zz|N1BkSMt!>E25E{SCaD^|uPU;wwfzo#-2BKW8X=6wA zKFy>9_24?u51;v~h~5S-`Y8@2Jm%^09~T73e0gj!IY?0QoYC{M9EA#`EZ^_e?mdo4 zEme(2k&HCF8LJ%Ob7>`R`s@-+-kGJZD7m3Ftf$K$9AV25LfWncWwIDMRFc^V;tzql zKu@jG!_bD|Hwg;RleKBF#0wN^K%77cxeA25{s-HHyduyIir{|&0HE}7Oq$;}+4qmN zd_=R?wf&!DbDx1465s#8aktStXUAYNWqqdSKjq-e{@8Afs6+}I`R=wqVJUjK7w^k1 z9A+q1^dc2)@ppf#gbVp(BJB(|eO!6DT#2eRnWo|ysT%jCzA}!JaspsZXb&ya^=KVt zVE#;10A-dSZu-0*sRrVFg{%bF@7Vh?$^;ZAT=aybeH4MDJ4iyO|HsX03z>PV1af9O`aHRh@Tz}blUIKSg`MPOGexdF5Mb>yBm zo1*hsPxw%0cCc`xN$l71KKSrCaBly%V6@9$Q1M-bO3yR(j1S-}kK~7~EG@HNOzhz~ zh4+0NoLaFk?v;FA{6Tw9FLKn*RO~_|SAmzOh7S`xGVn6$d2JZs{e9)tZ%&Xh9!sal z4qp*BZGN!vq-BPh^Y53g$3BCkQUH=&YL4gyCZTNQIdDxD>2%$*2uOeKXpSi*;^x9`l%j=I~vxq}w?v)Mul z+Kz!7W$SWX=2^nWYb~7M(_KuYzTX@dlKhJAr?!os%dA!Q1BoRp?)V+>DX1ZBA0zbFaDkeJS-2)keS3xix8R5b31PJRFJp>>^aOei zbnZdRmd_6KnpqT~D=RQ@d@rg_FLe?1=YIVsUaP5?%l7myu{r+|fwDIr$d2pi@_Wi`yt{tx5zn*e`n$8;pB0CIEC;1#Dzna4V_*RON8xmTFvqL zt#RseU?uQxie@t9KR;4w5~7#}UTi$CVrD{+@dmM#L_9e1CBP95@yl!77F0aO)Vo9c zq~tMiNk53CTN5aSsiD2L+C|OcJDIm%wguHY$F$Z(|?ww@!d=KXhodr$RVGN!TjHRf#|{Zv<2#w zJ&(WPD(Kv&r^|Ojcxdl;oe_71BC@Km(9!E{{Z1C>uk*HS@ZW#m4E);X$0%z2#g4?F zGx{q4T9`Sypj5^V&^=`L>JFyl@SU}_6lE^EyzJTtCq$hx(?FzEki4I&`SB0vz}t6! z6_fJspF5rkq^SIy$&5JdT79<@gyo>peV+c;FvTf0E#~eSIII0Q{-a|%P)E;}5a!D` zx7cxepZ9tg6jSwv2lFhONw0siT#SG9Yw()OcJ}%pZOCZfeIg1C<<7RT{cVpj+>u^v;2o>DGOxdu4^6jlqbOh!3mx zJMVWD2-PR^%u*@x3}VIb^eC@5l)>g~RbE|qwpWCTPU=8i@ZTn^l``jPWh&>~+-Fz= z3`*T|1srE1#9mV1dxq)d$T2L-^Cv644kJig*lXNgQS$Bdyl6$=xns6%iwA0svX^4@ zH|e-(o|Y?ad%q?vawiC2sMbXG(L(@VuEt03cMjPLqm+g~Pr-oACh_i26ev*eGwNg$ zN7al}TXlYfn;ldB5%f68^r=SDJL%B2r~b|x??vv{1CW;TJhxhLJi2fGqZuzD+2mZ5 zr4+z${f|8M+y4|#@-T3~+K>6#k}-R%D>C3>Ci6?ai&d(DdazVM2}UKjnkjM%j>X*1 z*uhAPjNwtNH%RFjFKNp9G!xl*+Xi%gl4q{Ee1?kwZV_`NcU$vjfEiX^CH9IqYEQ@p z)P8k}4mw0s(cOQU$5Dq7Jh((+lyCgmuxIFTYfkR}6Qq>kM7JM(+btCc+q^CAo=#y@ z2Buxc5fqDLgu0;tF((eTolU@p^)2%dQj$t~7giyq&oFoO3Kg-l^3_ue7a zn%D~dEGCfq0{1F24WWKPzT5We=!c7EfkIosXTb&1`z1B+j>wb&mN!fu4S8vtGeqh- z>Yp{|x>qxBC2?iz70@?+mllfThFp+>-0?$-G~ji9^}KZM-y>&4Paw(J>t;qH>-2%U zlq$K!159;*q0&v>p=Ehdgm&~nZ;%e1z|4uxBk!6 zpa^^oN-Ot&7a&tKo%+*%`dZYg=}0DH|2X}5vQEnHfvWa{C6c&Iot5d zP+{&?Lwy9|uDYY-ZmaM;(Dt=Q=3cqn)E*Ih$91FdtWIc$1hDsGUUUNmobE>ov%!-h*IM^s%9HmAnN;f`afuHD ztLY(e5kS=_8hPBgvhkYgafN*0^!ybs19EEy+!zP592%#i#M;KyEF@p4_tF-1lUF2B zohKj0>*_`=QLp@@LS?sh@`Z9(tdM|uVj;Dc)TK=cPv^?QkGL~Iom5pm3|AEYBQi`AAKAiCWFo5Rg z7bASHGII4C_!LME)1knKb*JjVUcndMmGjKB81V3y#m~Z?knlZ)mA#B{OvR%g*B|Wd zE|~ps=Ak{8-*$z@Xh-&S;#{8JjN;ZAW0W{RlU#L!T^`e8=ceLFw++mz!`9)#oc2HN zNI2MB39Jf5R^KmrA>@sV)-bd9D2q>Lw!1jUfd{cgoNqv~#LC63O2F^^qDcxMhrmnV zmS0wrEu%{rkN-#QMKml9^5nmN1fX3Ea^Ag*9_Z5ttSZ?@v`_?L7XKn>?68Hw!r{iZ zVX8Zab}fAvPWwGE-F>DyMCj_=_*>Pa>OyS!xl2 zePt&rPwM_n^?TNGT$~md8CweMIfi(i{<#Chj*`iEU#sLFR96g2S&c&6FA|(hpP4d` z)C?0iG5jLZ(orQ-tXLrp-}`CRBsA#m4U4Lm&G6+q!h7>e@qSSEWj<9M3Z14X1 z`kRx0{HMNbLJo4bUMD`9i%S@ za@;E=Z^XZ*Ep}Q-5}Rm?3hhd+ODSEhu1dH5ju+VGrQ19Ez*c0f@51^`B2YN);I6nk zeGl@nblmo3=sSgiWSQ_1>=4wu#UQ6+C)(FM_&Vz!-}hlw=gq!gOUhPYSp!AiAPbD< z2$Q#fDz;xx962CXx*SmtE7({>%qw{xAMv&NxHCGo!chAjalip?7e$>s_J#CRy*|^j z;QF0dDtj`=q4@OT#T;PnakIsCjoFSs7#o0VI=0t7))O7$!;OLMaxGnxVap5+i=(sq zPD{ak`7!`yN))k+vtke)J*fFs1q4Wx$b6}qVM$*1u-D+QMYGbNRFMcrh~lz08_kM3 z@b8`I(2WBXI_0cW4A=cFvpiB6$Ig}|6>G>KB%4qoT;w}GKY{rnG3)KEW35ImzQc8a zXL5ZeLZ#t=pP>c?iXG~!;eE{eol1LF!7y4^glMgtoY8$eOy9tD@`LAoUX*^uK?V_V zU60R`_DqTQCI|jTf-R=Vw-iKL$4m05uA93@)RSL{8nA?>|6woP7kBC}1s)7q0NI~x zLKU_bb8$!1F`36Tzs$|o8JmgVo)(Of`WxA~|9r0b z$hGWa8A)0?x=a9mxn}$PXzQ07Kq1io<-|L&d+VC_IqFj#zvq8F+GwC#d9ShVJQx+O zwqMbg^%6ekIsdn7slJuS0g<^8+jf6N!|_Usccb0oxtF7=&pB}?-=D}>vx??HC(&{=`0 zdI2#ZYql(yzx!J|*A<@imKd##f3|@4iHKZ^`qgA||2(*E@`&gc67vqj-mn@zVoQ^p zg4*gtH!PrEjUXaAV6zUwH{Z9A;>VK}5ZTV)=rzWnx`bb1+)Q`YCb*WSSZL-so9PK$ zLg=gagmsyO5hOAuDY~BMv4K2Ze4Y2YzZ83~+9uH=(Qn9t%N_^g(Ou(ovx&T6kt9=6 zO1acLeA0uhEu~iXntQ5)y&;W^aqtbiM#=O;5>ga2sgu3;Cj4^*TQ`L3wG6;n9z18Q-xHp05vJDdW5rA2{m zkO1u4&);sh{G*i5ejR%m{sdJx!HB$Acpj?<2jwfcFOr3=-3KGoK$Ta^p?tVJX9&cC zd*;6@1jW-CJ?v&3C8PkuNDq?>6lA;(B{rLx@^CT1s-|XE#d3d0b{A2~6cprbcW6nD@$U}%^95O7^=c-4n(vsZV;3AjZA?q|Uk1J0 zmk?o^BRUg-Vfk}Ie3}wL5nyRGQab(q1>Nd&eKRTG^qk-oUQ`z{&N!a)q$ys)_)%(D z^aZuq!3JkHt}~=}BfT>o`9@fxf-MCt5g+jVVvI=r6q_$QGTiecSaS>7^7AL1Y=gJW zvVO&0ZOzOzL$4WASi=8kI_tM4|27Oi+t^08fOI1Wf|8P>OHvdBkx-G8l8%j#E-4Y| zmPSC}i?{(wmz2^2l#uS;^YY94Cp@3yc%7lu_Cp9iM^&9>)g)pl;f z)`g=BP4mq&&U;nj;kV&C*0~jq%$hY!0NSHeVw<9(RnbX#w*pBV_^}yKWcaTKsT*rA;U7HEA$LUqaj zsqTJ5dS%^Y8MTu6l*G&O=W0c|Wa&ZHpsCK$dyTrlSPIg6a1r%eB9HEsr1B5l6p3Xf z&2dh`|7MG=dJ&_040{P|*Q5oLp4^{7@4eZ+ltZ(J8wcB7;GkYae?Fj=5DmpEpeQ8Z z=-=QsuZs5MCsQ7R)^q71h*?t9dMd{eBr6U8E*A1`-H`++fC1MX9{%mx14Ik@8-W9t zWW;I4X(^;HpTRr?R60MeZz3N6ujD;w%kiNfnB0ZpaBI&*CEuM8nWlkJbHQ!B-<+wYG6ZuYgOM69={#Yo(OtFhvM zU2Vl(oKK6FO_G-(NIQdWM~8re!l2YcqC0Ek_;u`~leuGLGb{=rL&S6gGq!%&7+gh6 zaQY5gUisT{HT(?1WHZREjdmk5JWcZLfrpN2*E!_=y+tkjQ4^>JPRH1BHnsA?c|%Pf z>4UdF$T!yt<6HpCol-#%rU#_BYKWK`~!is@eICyCui>~bD?&h@U~1wkLbr} zpza%gZDmP&_gZ}*EkZ?u9vGXo7dRAjASR(LxT6)L!kMPVWSavkMLyXpH>l14Kca-U zEABUF&u`7)OI5rh3W(%g7F)7H#hO?rgC+wnu&OIme%Q>EXA5Uk-a=SMd5oF*LlVy2 zIHyI+7^tNq^od0G+7xDiDm}DdTmL0kBVbRLv;_-{?7uktmQzescOuVmieTt_IARS4 z&)Vzu=8q0Is{AE05~kxJ7*^1c<#Wr-#mR%iCmmcTd-UiT`V@8bo-IAEIj`&Q5Dq)9jqPhXNU;NY)${!jH zJa)Pr_dZDn!7av>@r`qNZ{-8U>Tt zV4b{7dF4}^6JD;j(96`;DD;lE9urI}j1%EQq#i%@zT332pdf!W;!Eg1Ky&x~eha(n zNa;6_`cgHP(-@pCy6!1hVe@vyGo?xL-_+V2<)Xhyh*nDw7VX-1XO2;@&{vKxi638$Yg6Gj{J2~V4V-e=d-2O%Jh)ES z5dlgn=&{h{|GK`SjitAE4s$9G52GYh2%zLsrXq5u+l+=h&bscG8sHWIFHmYdBbLSQ zsR-D2VSR=ON=lH?C)9V2@H48yiw4rBmo6C>uCEED#^^c-Yodtb!Kx%Q+sBo%sTyor zFudS!a482)w>qD~lEm_2D1keuy_9nz#xaPkZr(3q z85!SsN#@IMsStcFO`JUiDwQ~l*Ej2QO!)jAFR>r&CUA^;UHP<@0HhoHyYlD~A;)O9 zQBqIO4E8|-I$-uS_8AR;q<|FM--Wcla+Fb)1B@WEZ;7Ie(jFn;CmC}T)VEc*)*|D- zw+>c)F6&o4ja1i5mNWUb5&gZbljQA5@n#gvMdrKy+$cXwszwT2>>lJoTwOU`tS&dr z!)U}K6H4kt%A+LYz)gCdP$aG$qh@>|KltyCD`dy$M}mIzF&!C!QrDD4*S~@E_|Yrn zTaMWhq%v)oyH0SX59pnUxCv?xE$FR%drIh6Rf)-B{~?Mykwt>S$M!|RlKwZ&Q}I`O zrc%WJat=Pq7|vgPtf&2ADdKiN8G+0iQcy|{#d>mWZkk_c^Ay{SN7LLU-Hz{t#4@Z& zinvYa0G`vDHHh6f|MBgkdy+}t@=mkAZ1XWg_@k|d9NvLBcGBZZDa2RZ40m-x;#t=B$!_m^PLB zFYVOBRiO7mYWbQDP)Nh>kX~p2;Y9d753ybQlU4_nVF=I-Ss{_*hf$H@2R|C}M zYXJiUk@feVv1c>&5KLgp0eJlp_L>3dONMv6#GHZ_ZnbgOIhc|(RO2W5^1PZGaQSjU zt4Vm&2I_5#zV%K6gkwOXnUir4DFX{^amCFf`$K=Spse z0x^srps&kYO#D>quC4@@^T5bX6i#wR4zQ-<87@I(z$fO+1Y8KtN`s8N0%rVkS=(xc zc}vPg*VQ04l#&e)rnM?%^7pb!VumM%$ulR*a(ACOe{20nZ0rywd;iC1Te@W_d)=SB z3Bh6FpmWjj1Z%^zuO`c6Yh;r-EWlAfd-J?9yJQl%QIBQgh)Dh#V91r+Qg<2fGyW*_ zCHG$b9f~djSKraG^8SduEL9eCO^?cK%S8O&w1Qbx%NwdV=;gyxSJQ{f2?>RWp{5M_%4alze!R$vAo^giI#*xEq zlCsB*$^I~FBvTgY3Vwba*Ac6D<5Pv7tZemmZNb$JW!w9)zvpTs_G}m@feX_+lk1Zv zXj;OK$mPP$Q+f6tvb>+baIQ1iOcbX4j>YD*vj$fDHyE{)*z4$gEZTTz`+SFYvc8F< z1LQtOH<`MdB%(Y7A)S~Yh7Y= zsr#?3`3<6a2wtY)@UJI4@2#a+O*oza++*!+4UwzElBBSaW=&F|6d(z0u zK8ZTK_p#==VFn!umna{37%^jZ&J)mm6?+BJhMC4Xi*}GOP4WNM3!di9cDDHjCp_Xo zw+6jidtog)mKhAgZ0qbvgYx1M9?fCVY={r(eTf>2!FHxcn6Io{A8-5m3W?>gCCz z#Hst*dMP@??Bz4%JWQVPi5DdrmrsCM~$&?-eXP@&4=$7clY!Yn!})APPbh5Pt1uf(zFcB|w>EWN>e_*lLHueB!#8`w!g*d?6-GJ&WY-NQX5D z)|GBV4jsM3qu0qpUP%IGvt&tW=!ep12X)ud!U1TdL zsEinZD+Vh<09!cD=!8oTsNR3yJAHmYv_h~x`z1>lm6M6-T2O7P{ zQb}>;R|aHvT_XiE_~$n(u>(Hu4gH#tmzD=rQJ5J*5g@WQdOHK-pj710f&dQy%65KV zi!*)V(W;=|9J$xzD-Wm~_x4YE34p#SBbX9uKnzsc{Me8_LsWA=fw~yPtcywYIBm8l zNK@YW+pt6-_P$d5cPH5FevdW7bCsWu9dC5Fqg@QK5vLEo90+kL z-{~+Cb@cu<5UL~5sBONjt8X2Mk%-Aol_C0S<+whu!5Y0ona`f@@sDk%Kp*$=^5x-I zod?0sXwAvok%7W(X`w7wiRMsQ@DlF4!}If^b{?y88AidMu1vG>axS=s&(9^P-`j6$ zJ9$h003Q*P9g+mI-vLbCy3G8F9b_SAkL6t&UMG1e@)x@D^Zt>ZjAn%=Rl8ckv#iuD z=ikq4!#n0iZv+h1zR%)h*m4{5hIYI;z5{)+NkinBc1)0Oi3jbM>sa@n7S&7n@3 zURUmrza%CXpF(%1kT7-5;z-sIi;gXHWnnc6x~2v;VTYiP7dh^NJF{26)(k{2-nF2u z94;LyL4I9$#-FtccWug*P1h)RdXVICPlQ5h0hBf*g!RiAr+ykac1(&apDkG3Oz^V4*mL7AHpzIwTveE5B%8z1vtuDuT?zE|4@ro zR+TRynIY6fILpx9wIEJO-%b*E%56yH8d{8s(;hm-h|>@}jsaf5tx<`T7&W*6e#R8lT#|nd9u&F(Ga)k_Y$vaOGc7u860OBM-kk7MB&C z3HTJk!V*>ZLx$ngn9Xqx`x^Ln+kz>~Q#n{a<41=7&OAGWSRYM$e&+36tFL!euOC-= zBT+zwP>2>X)oTQk$%^I-Ln$jZHoM)tA;Fr*1MY`| zTQ!#>QJ}gWrZojRXE{1<>w-O>x*jNU zEk$?VJu`6HjO`M1;VOPpV3y-3QZo`FrKdyC>|T@H0eu8*IM|NBk!gJOdDYEArV z)4eH!O?H{7=c)d&`ZH(QJQK&#k11@tqhk{qBO#C{Wd;m{8#kG{-y6w6=)coyop|)D zkzy1J13395bap@WEUqxEfJHQZA-~x;<_H2FExwiQc~xi|@qYns$4ag`(+|Gb1b;)9 z8iGMh&L@pX{h7qSu$g3-1u%x0_z(G`2a*IMmz28tS&P7f!j2)&_=qzfiVTl^do~0& zajBCtWQC%j6jq;p1hq1i)i522v)(L^^#7u=|0MJWJ;AGNKTymMV*-b7ZCzNJ(q}gzS3wC$c8*}7kPPFif_hwyLf#_Y2Bdz^lk|WJU zh*p7HRyZ+ggZ(1{?_Ka-ZYUJ`)t_-OZ4B}cFuO#nigO#ogjYnqK1cwX4SsT)1GL6> z>5qXt2;cHUjsh@Dve*9b?>aM;8GltwLgs0JKJ{0uNywH_v!!4 zWLMZKgpxKSVU2&5A0IybZhS45`EuBcOE)I>$UqWv+WaZ{tA6J7wA))zX#96m#k;v3 zpLF~9*_&tlS|pXbqJ)zia-4fp9{+o>6)T;RSGx#+>9R^5|6Y2eHT=rTGA0Slo;>4L&bRfnA_0U*2@V&uLB4@a?-0AsNhGZYD&a=+<^t zzojaZe7o`5p4U}eC_-0|In8!GPfRx@c3;5Pe-g6RUwd2Ps^((X;0v1JY^NMjM1;`- zg!$=20U`2X4rdbkhrx_Hr?L0j;-O2|2}Y{q%IP3KP&@X~^J1OZlTaFs2gCN)9_ifu4%B4V!s?dArq#a`e~?TJKq?CJpGk9KgT%&mfCh%h!i1Pic#|D)e~7-8j%% zSNHX*w-T0p5K;Ryuj*LGp?95p_nQr|1e4LHki*e9bVJr!9y!3oE-&J$sH(h&{sX)r z#?4@v0HYJ^QNVO}V4LU#QQj)WXr}xsLQG%v#%1JJhvIV`iocWF96Fk1Rz^d5``tI}B)I2QRTV9)-0&`<=p)I%y{JXmP<^-bR;F14Zudn3i1sSou#~h*v3x!* z&suoRR?AmPjUG4_IEP-G8#kzzZszvx7!FskVw!hzPYk-il?xvmTy8r${^Bk*&SUuu zpvJl1hdcZ7C*ONd!9G<`)PwU;#IpCzhk#R90%QjNP3{uoOGXF~y6^UF_s z1*!v(wX?`ab8{Jnwb*vM# zlSAfnH^`t2+z;9yp;)t^#RvXu{Ys}&Fn7Mw!@;z3Vh;gS!0vWd$@j$riMgKvJ`p?% zEbnG>4ho7Q$y;Dzn#60^5OKkbnK6^-*~?Y8-bzd03#5-C<8%9v8sq!WpCtzZx4XE- zrUpMOuJ};_lZ(Ym0>SIVSu3B&Le}Sh)>pd#&t;u^uA|sLj7;+=^^-?-l_H4Q+NaYnL!D2o19ew)# z9d)xA_4)cW~wYZ)Bwm%q#x%BhccH+suD8s=tr7Il8-0V|y;}1-({#r9*^s zu7ekYrbm=e4kyNo?ttW{g6muK%3l=!^5Z(KL!!j0q%3|&a1g@esVD zrzP=Im)aWKn!R@_TBsy)nxDmXN4DoqF=^~1MFnGyYwi-C;Q%4b*84tdUi#Ej1HBrc zriPTjB$$NMB4s8VKR?%bBmPFws2sCwi0Y@{yh+4&<}LfLLKz?HOe8x-8P#e1Y3)(b z7PfX`V6e$U6OdN-6EE-2P@%=p*5I{dS-=NBOV2lJ&<6LWndt$v}cMZ`UMld0i_ zu>@q{w)foc8pr(U@Nat|!tY`o{8n=3LB*`x@@J7@+17W3j3K6)6yUZu2u zO{Bhbe1b`fxQJuJ-M)2IVRkiQ@JWSPVhAG@R zuAK>{oHx}l0MjAI$BKCbf4L1e7lEbvq!nuiLsJt#$gN?dKftD%V*O2@pdll8%CQOb z?Csp&GDr#g)(ZcIDi(g!c8>XlhnP(`R1h({@UHxNbZDP?dBJJhI&Jbj-=h04-FkLW z&);-T-SF~$r^@Qpr&dZMs^1)sOp2`^$1q%IC8#AmuI~FwlC>cXy$opk>=4AW@-p;z z#$<~B>hYm~iEqbB%T0w5(tuBws~i&h2TU`FscyYUGhb}g!l#>NUDM!eqSvCun^&uH znyZ+Xlnj?c-|~g20=*X#BR|LKezAX0T7>=Vp{O%CRl0`A)6$b}=(+RZDZW4sLI+oy z0ZCtDD=lAe^E5MyythuAz$9O1uL_g@X90FgJJsRrih=Fm4CB|uNMtUB<^fsaTo^7j ztN?Ps;oOD$h?bzt;J>|WuZPoK1AYW>M%v5%GScRSiF0^Zs}b!8_3Hbt;MmMbt%B;~ zSE`E5!q9?~lCy~Az$mubn2V~V8#w>GulD?R;hWw0zpgv>fI`a=PEHECd$kRFvSREz93fys3niuCY=DfAUOJkG@WaI9 z6$jxekO85qo~c?SH&9XT}tMIxN(J_y-s% zY4uS5h8->CFqM7e1EHO7S zLgn{NS{{b%WKYbSaVN0otF#$A#k`s9?hN}u(3k*M^lO!U@yg*;TVmV^`+oIir~qC8 zUtT}DkvwS|Pm>rHZXLYqu5i+`@y+IY!ey}NDxxdQ^C$M?X+&o0DYHy|PySN*vgr$o9w?b9NIoth2!Sfs<)r`)LJ#8$%NKDR1SoMhTPd*~YLuVBKVd*}HNHXz&K6$LG>6=)!4 zpa+J(Hq==i1K!4iLpCB+_kjOwHv>iPj%-L}4{|m_)Hy?k&ZsdjN26Q7-F}AU%Q3Hb zONcxP^VOo8eA)Oy^R8|hSnv+iO(?svHifwie!lF~2kL--kl?Sm0=15P5nNi@s$J4a z7MK&$Ps7=J@ZH`~f}b^SRWIMP9HNq^W0IDr{PF6J{Tq#B)2Z)PtO4btdlj!&1kYOG{ z5j24e0H_Vaz^T3WF6a&@aQ}HFseIG>V;Bt3+=s~+2enfI6YI+wf$Jl?NKZ%N8g}<} zgIzBJ&rSQqjc0H7Su}v{7mH3%pPh`YU#jlI2QS}Vjcpomu-xAH9S2l?#hTLO>L0hc zL+QO2(l-ffCa!(4;Z@v3L7CSO1U%9$po0u1VK4L8q0GFR*A^VYbVNi0JChA{GDIQICiUXjKC zvv7GQ!<#VQWa7Nv!CtAShu;DDw}`yQ&lf%hK1-tNK1vVXY~WCk&3;~EdSU*!-jgMn zb~M^5=73RW5@>hinuHL4BC0XB(KbOdW=G+sSl@W>W%IT~Ox`wwm_x(|mYt`9=@e=W zJJQe5ijBSC0@-k#%l&xOPY)^?DhbK2@rc#eQ6%S$gb7%<}2$KX7{xpbh~hi zkoVrw=46Ij8%(^$u6W2uk~ zz~-+9qmZkyvnGq7C}Y8+N$1n+Co1YU-$ceI2XL*zeK5JtDn4+y`!5g}K$#-|4aJ0XeaIAtac!1(x?&bsp3KS#Ajs53cvFyf*|sL)W6& zP=0`;w>W^vlVtS){>-T!p*;n5u8J=u3quQ)0rE40uV@w`H+*lMpBk-cyp&Zs{|NVlXgBhTy>NK@VxvMw4UM~4^F77VX zH$Lt>wmc%@zbzVi>ZFiePg@&%WFE>FU5`vqMVXTTF?)KF>7{pMCi)`T;`=(-hmepdKGTNiAa);0p7_x`(_1UZQ*w3Q z({VJKTuO#DRI-M7zVu~%d;>G$E>Ix9C!Iwog#5VCa$WHzz|REMz-le9wBsW8a{#|~ zyIYMGBmx+=>ztsCh;UI@##86tb?SC3Kpn*_cpf zkj#JZwOwtkER!-m_4&sA%7jk^bjM1mmtTfFNZqodt72v+gr=E-ZXk z>Gk#E0BW3>0DE_?311Eq)GUP^BUSoDB`MXxY;GYdnD@9QAe%htX&#a}FJLE%GA|O{ z3%n7+GNSShfdq!*SOn&LtL_$OK5`Tm<-QOJYOQyd5>6S}Xh&?({Yvu6zt<)#`dRuc zyQT@v2p<##GJ>*yXb(-H1mf^*J&4piHeW-COf zVKCF(ZMnmgY0`#``M30_lpD6hSs$3Rkk`(Yo3!>8p5%^Oyq~X9lEWVwM621FM(pOb z{mJ!bYF;O@srzsGI<=czpA?tQ^fl_4N{SVOk)>XXq?^LP%^IC6CBl|U`e30BE~>5vbM{G#Tm?EtUX%9xLD32 z>j4Pxg++``s7Vw?0IfYyCB%Xhr%Q~%g}gSVuYeS%KCPm6019Tk&e-;wj~S$*23RVo ziSn$@={buL9Gb@7e|pfi%s`l2e{czMcTX60i{q9zvYF_=g@Mwa)g_S6`&G=)*?ZJJ zdEUYGQ}f|mK@h&Dp+8iIn@y^C_ch1wKQYm0|ITeeq@fb?{6W;eU!f?=7yBjC6s-R- z-Fm@pUh(W+ezwtf4hAjYr-Uq4|MGux9pWrSc)-Z7@V*4?UPzN7JK&+l!^%_M0roH+U_9>ohQ(K&l{Fg&s?LTiw#s8 z1vWU2k=c|Z34L-f$Z_rR>%do>D2I?@zcIVVZt}W^O0A=?+jQcrFKCS9W5jySP1AVz zN{N~i-gkM%`fG7Dc=9g_QaMB_@gSZ~Tof0pQk8Z;)@D7_PkB`QNR+hM<(@QuKbpCK zO=xS_Hb`eH_w~2iotj2$+?33JYs#857)^N}TC&>$%P0Mj2>+@1r=V#xKu3iC^7NT# z5gaFoa)7Z~X~`X{7U*FaJn`Rrj5u<)q@R4uaeB(#aMWf5;QmJHL50s5xpC&3u9GYW z9gk=h20xq3s*}5Uj+LWfyd57;LF07l)$@j%wQsYmD^}2?TXIlL8|<4$m@1LQ#chhy zfd6nO7arZ1eWE5ugf;E)qKZN+6dZ0KpWw@{nM*F9vzBDy#NqarQIx~?2;B-fvBV04 z+(18J??^(-!|=Uz6Xf*(@h&$FI!3=vi+Vw~4C4h&T7FsCO)XWQYMF8=wX*MfFwJd{ z*7K%nN9ZbrUd}%|orh^Sg`oUKt5J)@zjfYF|JdB(VOu;2<`tvez%Re-x6_?J#l03g z)<|?qn;+McWzMP{5zM9DFS@(0T%_T0$%etKUf;l6tCNDq0tz7M9tVDb_eMq$(U8ZO zoM7%?4*+{OLya&T%uHB9A2jJhQ$+^>faakO@Q}2R921Y2$b|QD ziRp#st0{nYfW6sgH>_xD!U9tyI(Mgap>%>!_zz3yr)FO{u=Hc<5as>JN%K@ln|>HE zlG^=*%{GZ$OYgp&5X11_fz|Z;(${36l9izi-PMq5vQTQPOJ`C7{|c{8l6Y4S17(KV zneTTA0YNYm%@OYD>+1dTtiw;a0x%anK6~;oHac8X{d6xgJm|NR-T<$0MZ^g5p&|0{ z&^GMXul~RJlCAeD9ZrW0(Ub(*y+W?tQ4(N!qSOeL5Bc_$3z5gJXmYs|$}M72a8=Ya zmvZBEB0p9IYF59Sh}en0iJ$T^rh9MI?eSj^tuwb1-flbKUKpC!U6K6c?rT@&Sm_5> zaxJ2GAOT>Cb`!aW`z~#QSUiqxgci7!oGmwT0WI^-UlH#oe#I9UxV|m2d z*f={b0|tnQe+wRg>%4-fXppa_LK0MmyG>ockJIJpP$F<61cDC79AEyjB1^)|IlQI= z9vB$fabL=!`K@@FxstK3#KuQeBhh~PqMKFk@4Nly@6*B1(OuKAND>O>wAlwS^b)&; zTZRY8eRUAgvVW-QY4G#AC7pSK&N5>oM>$mToFg?faHxFu;5^9cH-Y<4>FzhWQGO$I zM*K@vEktb=o3G3+`wi$Hi-(vU^+ta8tA%D^dqu0L4{@q`l-FnstTRuYyr3RiA zCO)CclmD%AWKKa{dmLs6KCP>j0B#NXi+mUpBR&-kLJx$L6k!*Bi(R7q5B?rlrT`A4 zQJdTnuzi9vf9UJ2GXIb`vN|UEZ1x|$bsrdNXb>l30*z4-x1;b5YLP1Mvj&qT3TD(w zdP^sVew?d;69v>2F|!>NB(br@uyo*5Oq7S2&dI2 z_QLuhKRM=k%l2P~CiESM7+#AW!rZSv%>M5HB1&H)Ye5%_Bo=hB{|bAeSrfPFl9^-L zLJ58{r|l4&gmZ{R=OmEK_@|BX5=u1%d=k#IZ%lQ@;$sI~s~%W&H$4s3yCquWtA6WY zgOudY(eWQa>1UtoZo5r2n3g?OfKyAom3a&5{v5uo9n4nvgDS^jbq$TqjaAEK4zYt^ zy49-;7bD~f0H0S-AsUnipaGdqSjL*eKJcUNakEPlW(P~@eK$_K-miDZVmuN5g$yA( zH@c1Qmha_Zh)ADhr}SUXnKN{unYh} zeaLy+C|EDaVr(HmJ#jlLc%C+o*!`7cRfbDuOy9(@e_Cp7HU*s0N@* zx_i91qNGTg$2Vu(bJ^CsO)r_p0|PQ463Gy?oBt7O$gu&gD&f~|L^-0`)EF>AX;Al9 z2!!-xW9Hot;BOa>{WmQf1NS3~wyCF-!pcxEaRyrhoCa=+G#EeJ9vHND+Tt(^ntv?X zWt1OPGLQoxbZb5zqqCCwVcXt4%tW>DM#wlfN(ik>{45pp@%Ix);NE`C$q@jSgqoj5 zEUI)R`}0GySk?})LR2GTkViE#0WiG37o5wT$#GOXYp4~1?KvI0{HL$mpPH}hvS4^< zz??m13mQT9Z`JO?B$C8moW64>aVWqo=p=P(4#O&(URPxDcL%iZf0*(nlV;;imep@j%HPEW+d6Hk?*4ODp>sJxDGw=mR@5Xv>~j!Nq9f|$>j%=XX`F;qJ+ zJ^m=Ta$3`mN!6Fnl*k$Ztf{0BKcZ(C2N&v3ccedno>igWQ_#id9Q0S%1)dr7Y5m$T zmFc~DgWNW9@r>lh6d}}x5@9C`Q!~oZx#T1b1r%<_i%CUo?wfGmfcc^KLo>_wQ~QX& z^m7K&3HYrsTnG(D{q^)(S#5d#A(#=`;}S2r6rEyi-Cfq%D;^z=Aqr*j`WaHiU&fPF zW8YeD17I{utGSD+H+T(kS5R*kiZ@hnX21gpVFd%_v46N{uwpd_WnU8$Dyy z6MUTo?7oL0&~m9CewK!HV?g=)zex2;Z?kg#tI8-^eV#V->@ zL9dUnn_S2kf}#S;$Q-Pw~bXzJrq!0e*P~nvidvq$s1uaiJL`G9|!sL@g-zW+?ogX6(3LeT&2x z)a{>dxp>GOFK~IwJYdW$`Id?nyZN`#>s|$^uio>)*;bnQmcVFgvtAH1m9yOWx7|J@ z^h8O_{c`EC5Mu~P5*Hj#Xc^`*fU*$3XForM~v%bf+R>1v0e6eOel zbbZXdeTD509WEB~k&(CUpA@N9jd^sI%Q7WNWU4rQH!Z55C=dED_isLC=ys&dr5G=D zNn&yahnmGE#e*;5&Hf)I^JmrnP#BcGBV-0X=UzzdQ;G%G=p7CxgbSpG9RJm>d`5S@ zd!2RA<%q^iqw^N6zJe=vXxw_J0g}^p6I2GH)!M$>+Bg+1zUm_G%G3l5{N31C!AFjX z2&w|R_%eRMK7Fb2mx4Xr!(&T&u#%oYk}vi@={}aBV5jPrt>CI$8{h!kIUaL;W8)lV zYH+o^u6hcx`vA9jc*Z4AC14}n8idg~h1S_07zI_glyH=s3S}f%(Do{C-xBMlwaNf6 ztsoSbYrei=lhOB1MwpS5M){eEc{R^|H`dU8(o$HJ^pNf};CB11itd3HpD*M$i||RH zSmSnL8XSj=*7UF#m6ZLs|3S3unLpQa=bj9myGq#83!Y5vk|H&=E`r~&fa2-T4C(kV zrSynDyBD3@V3XCuLdOcoQO8C4d@bC%CZOfobB6(?xW( zV2rzo>=V7RF!YT1fB@f-DOR$Du7dBB+sQZ6+%6gaF8rC>)os0&uLm|#10bALC{7Ev z{cs6wd!ac$EG4h?JnW|Mf=-&00_u4ar0z?0tX*pjZx*M1@UnEb9ZQA5hG7`N_HFx# zI1$d?5C~XPQz3ix7|;sL&&p%k$ErTzO%JF0+=l)FyRPxrewxS1Udn|$N<96viS$=9 zZaY(A0?Br)#4st`K&N^PNP zK=#WYeXHDUICZU%xQOTcwO9L(E|ABFP$13{nzrKg_N>Me?c_p;{u=3pPCKF)o{FPV z#9f)q%=A8j{0GfZ{0!Np#)RK^{bn6e3GTdic3}R0HD-QLRI|X_`83lYcv-t=>Ej z^I?7IlAQB3ll1Scabg9ygUq*9WRIbi3#{lzbo|OyRFYVB2>AdW{qV2}& zjnXF>*`nEC9Xv|x6m*CIH@5t*^xo=}V>y?l^NBKV0B3VNck$hcbj{GDhgUrJcjP&5 zx-npqG^wwaljoQWg})L1mHEevd1=@rb9+uI#?8G)J!C`DJYH*6~FC6 zmF99ZVl54Kt$MgsTCs+$UU+^~Mn-6pXH-*kj8%j)(5RTa$qCN)$mzAme5o{b)oj1p zMTwa}C!pqhxNHCYdDuDxuR2CR%$Qa=DlY_RK~GxFy{5=V=jAC!Q0FI$6Sm0%WTy~r z^9QwetpT}B;KjcIK8Fjvzv9>IZ&Z6u#Qfu|%d9WMZ83Ux{2-9^#B@UHx09~>U$_2o z;=g)a{;FDmyx0h7+&~_pCw-_fUIA__80nL2*Ma*o^vsw5VZe@(6wKvI+NIe_olZXr zx)j4xVJsEsVBeXTHbBaM;3+ZXI-3cg@L9G6Y_%Y`+{1C|7(f1yuwzgH*J`wiUyl8% z6YKOr;O;huh`R!P%2xI$bIeE#2RAN!34nG6ye}ft9=cq=^v1L!egK#@}D4qb~P9UHLLVnkQa z;;iwqV=7IXCkr$y?(7@8I}*{wv>7UH#q+!cjVZ}u`B&wWAxlr;tzoTN`GDHOvg>9m z)r)1jfVO?|m*dorgu=`j^2rU;Q<|guo5~a%imBIj%8<3k6&gWrThN{zS8uPns{kFs zi=ciY4B1jEi&uj3sHr)1A_a~_`?;!0eD@vBI+!~d@K{MIUn*#&RrF|q&1g^#FjK`n z4h$5vYrvMNj=bIX&es^D#4rMgv+fEJ!<`g1w+(?SFTs6Rwnw$M_HID)&XCi9>$EM2 zHG1Hol{=(739{=HsAF@R7}xjI_It-SYnbuXLaZ!|1jqwlU7`bD<8)pH>X{FV9+2!< zJt2Ga6jQu8Ou>OKzWoHOKGZCMX9CKDG{3p%#B2b)FRaoq@3fZx4`ja(9#P=YrE%g6rI`e<1-uI8+XJ(AC@B23PC0kju%^)fi6(MBG zUfHvhn6VVCM7AP?qP#=)EHko(P!cKGcOv^TGw18`!}mWpkH3H@xwHNqVyBUz zr=>KiW0D!!5;3}2yraU>bHk=FT*`lGDf?oIp&tSBU7 zJCjXN*t}4y`ZW0qC*6hLnHm6q1*cCK+tOvu>{&3A3Xj5lC2+)>y(O!Ou-kYnvWeBR z83!1~TdBm%?SCx+PqqHub(?M;%y3 z&wn6zabYTnN$O%mh0ZJFFu93d*|f%CS5ov*_4wFRug_Bm2%d|>7JrpN)Xer%9c`Dsi3-f z>f;pgT)G0?>r1=p`BR47v{JZ3z#Gz(?OXsrX)fei5F4# zy>R*Jq_N5KFgcoz&#~pvqHRQysh~TZ$a#KJx*yJ^U*99oJfRPSz!lHv74NpZ7%^F5 zQURrEr~aexjWW8H>YNcbzd=&wr$4|*KfEqq4d&0r_kuOvb(gxGxvRWqULgIlTtsr@ z*CwSvllwc%dspck$II7LwY06oOUjEmug`dN>w{EqF|pNVQY23|GETTgp=Jh;$Q8*R zR<2s@WG%a}gwrrynP2|d$g1Y*J&aS>bLAzS+D~u8RfFL0HZh84sq#7-S4UYu!9MQC z6CT?`KUb2Q!?ixijMpkB_Zt=IA?>$+RsYq$v;-wpfhLJlzYjLUWUaw7`VmA~8fXgL zN>#o++(ER9-LhXASzgtIf7%@+vqqh@;40!lN6TmU&5-{%(qnbh-BGV*T)Pn0j`Mcz zBH!?l91zPI8l=Zg5al3x4(6f2_mN57r0<=dr(WuSjk-h>Z#*{SiTlHaXel|yVI@Jb z#V<%SRm@wkd=xCb4z+u7y4 zV~?M5J`pSt7l7(cyTv3)gFHpfhmTxDALXm7f>1fy&^CfE($4=9NxU1E6DPEp^Ke@t zl5{q0mnfx7&{bq~{_J)HAFC7m2;4qC&W+VVto;Z+#yNYSD?(~S4UU~_uq%Svw`P++ z9|X(cM3)>U!nULFtPh!_fV4f0yPhdzsOQm(%omiEr<=-vC#5SINDf0&E%z` z7CzvLXY{wRE}yoKFD^bhjnmz>_d%8`cHOky+c?hyD!DQoj)t@46^!uCNL;wgRPU?q zJ+1J7c<06w#p#oyQBh5cIfaNw`cMAxYa0L7)0BF)qE6AKuv z1cCZS?Cq@-j8+P(^!t*OXdNK(91eljl!sJlW1T#AauCCQ z-x|xVSjRN#@vYVwT1f2tcRlAA3jY`?&lmajnoK+{8IQz+VL)P9!x|nwn@$cEm1bhR zq+`fkX&?&Gg}KTj|va&rF*u*=p01{OxQR6jTosw~ynf2e0UX-2DN#L&@epI=p?@zkjh^Y_n0Z-=}jt=S#~S z)l$UYP7ZfY?7gG8zSy^hP1<>k4l76@cW>xoX{n%K*;_;Oi9tyY;q=oz3p=vf_z|=E zXNnS2Z>uiwtcB(r(L)&joH(TwaY7UB7ikJclIg0Wi$JIrRmoh^)aat z14l)WUOJ|71p&7e5CdpZmjm$WD(o+ajt=0UP1yi6KpgT~iu- zqwpI`JJC=y3YFd@(|A$^kz6a$Fxz-)|A;1PWrCncTHRd{Q^L_r6;F6k4`ta(;30|%>s5(&L^2aYQErY@_~Pvpn?@%D)SycN&4Nk zqm@|~>#*^ws)pZJW^~Js5|>w>9>j(}1Vvypb7ZkF0vv&@??%%VzmWsg)Na6oYpaT^ z&TL{2Z)*=cJn3q#PyH>hcE&^l+VlG9dWO4HEE&EWxQb(i$9AH3!l>sT!Uy@E$PFI9 zdi>QE6fv&5ngHh+8INd9US>FIahuoD!1XhA(wid=Fm_;Pq_ zJI9@*c9d)6xH`DQ1-t$LJ^ZzB$9ms8xclbb+$TfFy@xsFk_loCO;4}f$14dov^u=P zAmbWxM^(QQ@3bv9oItPg=E&IPJrk`AW(U(gif`W~tz@688LpMH9~ljQx)LEheZ&_j zFJr2jC-T+s2m^S`UdSX<1FIS$RIfv`dQN4R2e%Tc;HcF7VzG-ytvop15_$IBjNH}r z+cw8|jzfSt_Hx8R(*r}d$1qeby*I&(SDLYd-lSG!k-Zb^e1&bL){jujoxUFEuTJ4pO;^u^+qiv3(OKu1u0|Wll@EO(oKe!=bPccEeYMcPGp>X*#lPK zNfYVJQR~{w2|~C>+O~aPnE1U-f%vPbtG3A8C_+!k4b`NG2iMbUr+1SXpSVMD=K?$x zK#Kox!-smV#^wa>x|?dn+)187xb!4k`+r}3Lf_wy`D2mU?RhHPLy#V!cRqhP2SJ!1 z@CcI(Td999!?$m5d3P+Dqw1e%mou&;dMU zS&JX9MueSXLI}8{l5qXkVc`{zQ&N~^F10)OF)!xP$+glFfri`iZ+{Y{nq91@#3ZfdNPndlbJcd)#`EPPG_dS3OCLElkp{1#vo^smmFJx%Ph4LR`qMZG zmq-rT0l6M}BFX>if4rhGaU|J;iO^p*KZK9bd~9~(?ckLQN}Bc1y}x)wNNSTcYea=C z?4;4C8*X|_qG#L1c{hxU{^o41bROk`43ImC;No=WKH<$OP^43~lVU(wG!RBIP>H(- zCyS_?e3znm3V^@TA1W`)9V_IcL){Pmsb9#W=&gTav?#4_-?(q`9Uw-z2)sXsxkjvq zp*U%|M1pFk8w>vSYi`(6GVZW=?8hZ3q`1)Q@E8Xkz zLUq7KVgnr27D{d!+xf#W0M7<)4Lzh+xLi1*F5J`;aP8;9T*q@fYc;vhoFc>oRPXpE zFmj#?KZBeXI|8wy#?2LkJE8tvG9I4hY~`=k@B{p6W5)pD=&B=3yyDn`Ty_=jYS^C2 zoN>%n?%O|_=mJ&|DAO{QOcUtvr5yoH5u-0TiA& zrR}oz<>(Vd@uNG{}PZVZ@3#7%OU%>2fvvq0u5_scUrY0Sls>= zS02Cb;nn;tqV-zeO#Xu=p6SX^iY`JYSUe0Sb5h5iPQmK! zTPHeMSz=b3|AtlU^=ZuDdNUqmpS|!dCeQyk$yW$5*I^4Zdxm;o2kPLinizgQt3Nu- zTnvATfq=o=039}TEDxavad|qB^62xO6bvCAK@vDBg+Nf&C?k8sSH-UwOEkCD7M9Pr zUizkGX+*Y-j8UT9jVH$;sivyXvvc7Bc6buBJ$5jRSPnJDe?)AZqCowk3O6zz5lcsI z7y3(@{uKz!t_}J)y!gN?SLEqHf@bY%rdf(8;fS#d7mKLvP7+8kOd%O`CF(ggnm|{sq{1Z(t$kCdodN&IG(qY2J(9N;8E_ z8T9^M|CU7Lq{2s^JD`&q3K*__h@*;CZincq))D|HqTG21Ok@O~j(l zH6-#Iuzf_H?y{zA5Bf=5zv&G+70d4O8PBc_EY9EqH&@qvDffhci)injxtIqsbSRJU z=OhZ(NLO?9YvT)_))~s&KfeX!p9Qj8>s!%xnZ~qR^us06=^vliyWJ=4%TwR#x3%MM zS+A`xOzAs4l;b2NqubL6r5}_>1wZ?JqW!{2_J%$1KOtTHP)Y8MZuc!@j+k)}%hmK4 z+F$G8(=kBZ+c1T-e)5B~AHyA!2b=H1dT6E{L{{fNPk)MBqB9hVa==8KD*aMwD1Bh% zW|u$)H#|; z1GUy_*3q15*5s*%Q<`X?hjq8u_6Y3_?=bUbRvpT&h2E_U7QHmrXW4187Zvt>`;@+| zfbeQ~U=Rw9sVYRpB!Lb^@R}}v*XWQi`!>&61*CTGK^ zztFO%C&!$I5zfc=8RsbIKE@ZB7I!>{Ui4==FoT!UI4}-=CzLA& zUeGGJk-w8_U@cDsd4e5KLrg~`R_Xty!KZ!2{uXp_fYTC_kV)&6xPP?Dhh)-eF$ zB}8nN8p9m^6|&4vuCl9iF3P_`jgn135>&=NLHxMecP`BV-pPe=QdffwCcNe$N~M3r zlUa^(dZH&H=aEQ&RqAUr$rPF&tZfsR>M$;vyM~i^^jvB32B29$W{&+0AM(ZJa)1e_Nu6=NrFH0 zV=|6>-z!|i*jgtUu+?;|J-ox-J!lIbJf~$!>Y#=NtssV3ZIHmf3%B-V9XTzc!TQ2sTVI=@ zfMs>!V*HyRAQuX*(5x3hHOr|L-XB6j>Wgq)+?H2zM5L!+mM)ghomUEg4kk=%`|Exbi zbZ%R(MuD-a`)iIa__~_dg>JS6J<-Dn%RP~v%D1`7=tf$Vx;^+rbe6nGNp)=1s|RBb(&+aGiB-K%i9bk57vhwDkG z*1YtQ0|8^G^}`=ssNuK--W{B{Fz#1Wh$L9!u?PgJpJB9kx;raL3S9q!@O~sh*hEdE zKo(34%>_5*NE`8zpsBIZ<@*Ic%NF93Eq_|DvVpJ5_B=LNKmAprh(C;CtPvA0>+&}VxINkr-YPX6Q0Q)2G725=Y@Gb8 z>2Bf-q3!d#^Jnqy@S(Ub8E`t1{_~2Ox|G}YEA70;_Mwd&Gk%{5NV7lfv!Qi-Er!|( zwH0Z5Yh(3dI#_m*&-H=AKzUQ;5{-p1K;S5-+?O~$?C)ipU{HIKm#XvcJhV4FA(tV^RVm8DpRkiN)GbvSm!@7QOREy;FVV*kv`MJtt#S+ZG;mXTljh7CKn=i+F1$a z&EHAB4Q`o|xYEHHwtx30nIA=g+!))xm)(399-Ix10pVP^57AQ0gz+B;`0VoW@x+_` zPP9V??=vhZWeq<2&oR3``oGNO4MT8oZ!hQ*@pOLDdBo_7cB+80Hv7$soLvm_;$Qel zCNih(KD-XSpm7sD_AH&fJ(m+dK74%yrK#7+A#49%QhTgT4puZyFvjCp4ck!y(|OUL z4`tzeLTrbD@Dz4K#sJ^n9Ht-tame*zb>DCv!&OKwW+8Hi6-vOG%<1e;1L=4oKOL*o`=lAjU+BCniS+3>?Y zALIN+KI%du9CShB?Svt^$&AFx_^QK{{BP=x6**C<;mzLBjj0|A`IjI+C3b%${PH;R z`axK=$gm?AF9|pJb7Ps!aF=3CBGM#M;mMulakcWUMt8tXx88s?pTN;wxGrG;!2HO~ z-LakRziqbTt+bO#;2Stw!jzFEE!!v$-)R^yO!o~qgq?>%?q~I@q9?Nqy_V)iafo4Vu153gJS+`1~BB2X@ zdi%dA^*e3rMED?iEa&vg?j^Xo?D3!HA3e&v6A|G82UoYQh+&21On8!5z*vu#DNrMm z$4FZ+wINV_T)qWK6*7rCf1dx1^%H(n?&)pt_|L0BiF<64$bL=m3;-5V9X%gGxwdT4 z@PNgF*t}Cyv%%;*|6EOe1GjL2yp;tC5e~A(58Cg{_idW10A1A>yUn5IV5nbizVkMz z*J^U904?yu-I#Xxy4|2xTsAs)#z)-bAe;J|fBi#af6l~j@x5STo5JLc+vciJuJM_) zgxF#R;JBxJKrP*5JcH~u~TbREkg35q)yy(j^?;}zSN0CwhMg=g3BQ z4K5ke#AUaVGmZJKH)d)1=?U+6IR@BOb)S)eMV>dFSZaMuDsaR$KA-rid?LP>?cg?ky5_R_N&jYK-8y77;TnJG67Tk!zMlXsMzTwp% zoNi+1a&7lks$*fiYQ}_%&g-ywZnOOOBNKv+ISfWRK1}kqRJ}2&XJ7R21Mz5qn$Ndj zzme)PmSN9n5{t2R;oE#+;Klgt9(2N#l0HVf>H6=pDF8R1&jR6QDYskPB20F>l&nqw zXYQm0#$CHB0DLBX(xX}}P+a*yc;pevk#?=)9yI@y5Zq(x8;5o4O9$5P`8aI|OWnk? z9LnDiM!epwh9DU#fON(6kEO*$ijgY{*4P=cd&ZeGnjM1zmHQ|2oR2Z`ZCmPuPfP4@ zbv-(LY1Zc1hC>p8^Q5cWJ~sCXc5W=M09DpPw%?0Sx+T-?T51|u@=%-Pyzk4eUcy{N z6}bQ_3YXjdV7F<20xZ?fTEQ>ABM!^&Vh<_2nFv@wsq=)tpA86Wc?v8L;j{JAj5hiF zr1_A@WV`fB-&Ic`bhv|p(+qMeu$zd3fv~75Rw&DdF~Bc)86K^I@IOvsa<>AbR^Bgf z46ILuw)IN6fif*?Q7RiB&<30*TQ^|W^sA)zOYFB@mt7iS&bSLY!eQcpOVVq&5Ricg z$B*RrLfaBoz=bOex#*Y+l}z78Q@GxIa!S5LI#f_5?^|OiV!mx@k6;y^fU*z7;1acW zKt}dr=n4}eX)X9iU3vWB;GOi>ujn5$w{xmd%?xoRsmR=q`R2tP;=2JDQZK#sm)DF$ z_CmiE_y^o z9qBdOZlpCQ{>vd#6#0OYGRHTRUY0bdOXmN<)PHDrP;s~cSpmwM!+YMdfQjS`kMWCv z^?ze2RDbKhN;Oxj3**9&BYQ^bp-#2*sqDPja|ttT0%1+!JJLqW#2D*^W)Vqpd87BG zcKMjypIncO0^&PQ55Z9C*;NZ+lGzm^LLm2vzr#`#;j@LhlT!G|6`QchbRaH7f5CiG67=c)1!8TBkUu`iM9PeUYS0`jGCexP8a zMugtZ-cD>7DcXUldj>f9ohB=?JSU~a`4Y!Qa2Np)1jwV+_hX5B>q)<>R4JFJg~)z1 z{_kktmyTJb_i$ALK1cS*?rR|Oo@)mV-os6;;f(C#0JxsNk2tw3zz-}oD^#cl$|!XX z#Pe5&{|=}wr`XS$ZwDs&^aXA|uB^=O0?_JYH7Uy+%O76b&ZRs0n8Wg3{ZjjM#KP@T zN+$=46RGO%PS*!7oz--07PF8K0jyup_^nwcggR?Gx>JTZ>?*ZJynu?_OLQSh&Q~35 zD2)9dd1% zO=#-h6A z1VrM^*(%lC&6#R5hI1c~UUY~k&=Pgx#v~~xxc=~Rs^4_{=HbJ`nI%8>Km&^P0Llrw z!S;`OE3W8sGe#*OF;V5Z4%g3=z#sNqixW?z@UKagc)g~-o@eH@#%irDTr&l_x7ope zP?2T$^W=U1t4_9=hG$cb|9fI*i)?+XMt(K6X|S79PH&M=RS{>BoLoG`GBDz z3+@oyZ)F`(4h3_UU9MAp44noR3|0cbgfprxhK=$He-2H*Mhs-6ND*Zh8lAq< z>-qgPksnSqn?BgW)sk{M53Yw#tw?A3GCd7wN!Ggz_p$U~( z$Pxwm`ALE#Hg(`12RYb++Bu!f!7rUTykQqst9Hj;-iZ%zLL-BU$A4f@$8OICxouA| z{z57VjH4rVrF{?-Pm3Z@aCQal!NC4m`^oI}b>|OzSy#jifyrmbcjGhZ*^Eh#=ec7( znJD1{Mn(%<)r0c9yM0oPuivHL5l$9hIb_Q7R~}7*1=#pQUjqYvgMm~8n1X4NIC$~6 zt{g(0ss30c+54c6Bkoh8D7oov0RxsFgh7P$L{8Av&t9}6!iG_I?F~9f1-F^s$r5uS zoBykJre5uFJ<>(Ks0sABq2&xz*~I3UYrklK@am_^vrZ9l;P1Aa8v3dv7AIVoy;oRd&n-pt=F9!`($IH{}9lV>N; z@RtYI@NMBR{A>R%wQ8H+wr?aRqK@bw-}>h&C8vYc89b0>jkCN*+I{HL<9#ty^IGfp zQ1Uy*oF~A*TZ>+NT~EV}ktA@!6Q-=gQ>!d5E6uDw4&AIFtJHqgRV92p5+3q^WKw>V zsY>k__3$JtCY2zmVk_{bxgKeCp0VgMI1l6gLq`K6p5YWFwf{U-K$Pb`@Uowz0yTMI<9b@0}BI15j zks1(xqoV`6ty~HpXirl&vAvsrG3W=UezWG$r=u>Y!c#Sk7tfDU55Z%Rvmu_n$K~!< z_FuJF@N8?**!Z%On*esRR2(>gVANyJC>b{Il(xNVZF>(U8JEij&&U9OA1L1saFVo5 z4Ne(HyjiKgbOd7)OLn?T?NA@}n$)qXO3XCXlOrAO*hOK-ctW%>Bn6Vw=J|z}w(HEQ zE*D;2#6ahnAaMrDG;W>FxGF$dd=aA@&4_~YSV((0x->RbsH{Cg_va9d+yES9SEGLW z*5oo!P!Z?QNYv=zC50}y#z?vb2*PFDxuW|`zxYCtYlr<}I`)>tG{Q6i zrThjEjhRfUhT;Q=YuiRGI?9{ZSZr*svN~T`iWVk?Re{0y6ESDdY?P_5#31pGeWLj6 zNdm-5LEz=^Nnml&RZD5V)FRBvN+dG?{Zi+PerudN$M4Tw?|2%Hu(SVB>zDE7@>*(Y z+q)jT|5B7m9VCvFYhXf3By-t!g1R#>x$>vIRBY~_PTD$lW||}nsi)r&PkyAUwND?6 zKA7|U^{=2IK1K66-F1lO%KeS_dZbiqjOhFRK~3oCkm$^Dy%Z;wL6~=W{uXjg^d0;k z1Gr3Q@g=EQ<)UOP#n~BR0vkhHU-vxMRpibct}v$(ZxOUjt&D|9haqQz5w_CN9a`h| z2dcc^1h|e&o7tk^`|qwHZX5}Cqm6=C3EI($@RF|SxM4YTjcfc1M6(a2x`*gEb+V4+ zztLeqL~*NF56x+FLB~qCjn)Oxwl%lk%>H?Vy#M{)G|L)VaoFYarj|DT-z$?t3OXnH z0+Yt{yGT4IM%Ph7)dsGSC5_F}Z&or4@3-QT=(y~@ApVQIC34U;S&$GO)y16A2!Gw` zm;y6dQqi;HRr-IVr27W|3-7|gn_J@Jq4lPJ$C`Bzu8DV!i$*)31;N!hyS$VrwrCX- zh<;L(lGmy`ZnORQKd;Rh`c-FL=J znE>M^AnVsJ6(R;bMAF(VgM7bP~y#46Y21KZ+ExUT_z))+=C55XDo=z(EopzgzdQn9W$=p1zGr?963LgIK$@2F+ofNsrBhX3DpTPyf>KoRV zZL#oR6BRe-0;^uN)KJ+?F&aO+J&=yK(y1c)cEFFslZdmzpSeIgYB9Xa?W;#yx?!sm>of2v71Hx0Xz6XhrPWIZy5Ly;^Hg!3(w17%TJ?m;iwT zw1MfdtIYF-hz^cD*lG%V;YHgFlO-Ay+`>fX&xo~0G|~Gevh}izE55pYwqGJ57dtSN zTSkcxDbe+rKi^I8=+?@aJ+Ck@#&=L1uI$~rN z#Fg$ntUd^PecmxQ^H;QDDd}Noqs~YLkE`qa>`dHQ(5Lo*$`R)3`g)gC3O`aqgaTGX zlE7h|rPSLq`^$L6MSaF6vv= z8pGN`5dqSZ52V`x(Duf8qISXJvRNkVl#3q$#zB!^=Qe_bF2Wx3(DTml32(3KK;oahkC(zygj)hb2CbH4S zjCdTblD;^B~1k?bNg#KtZ^2f2&oPoFxR2+ zCx`<}E@#>Oa*w3ej+`IVgUW<#^qVRY;0j63JqRwz4jxL%D4YZe8)F1WX3medp)LWD zaFpUk+Ytd}>RsdT4ZzvhS^eVMRFnd}=KQ3;f3c9D1S>~}A<2(ZS&H+i_G5zSw1hoV z^fT_wY(3JS0cb?z8v}v|oPjC@NsPf+y zMqWtc{CUpxI>j_;&4j0lMP>&rjGg_U*3{v>I+R;aOh)6|Kqxm@*2x=fE!@iW{ zYN^krd2WL4YA4;ki6rum&NN_j45?F#R5oWGw;Y+vPB_N0IlIEsdF9C9e*FFB!L(C|kb$e!y%PoG|x!$(=zd!>vZSc;W*Z$a-f`nVs@TSwYe}yM`km5^uM8n-^6!%pl<(#(<1V(6-!yK7AkPh_2k7*OP%A# zrBKdtCDEb8(VJsrq}9q0&`~h~f5eMij`5I;-iBm!XNG;&rd+0VIQ)L#wOaz0c8^3T zUM*p9Wi4cVbj&0~nxKeM72@~Q$QC>6YIb+2;}kaP?iqz#Gthqpp^#JyOLW8Gucoq# zt(H@}cOMDIyu}pY&(_@62ZKNzlyT9=X47N9e4>%EH;}}JvT;Ns zFy;mdZ7oj#mZbD(%OjV~ww%qz(cLy$m`!)Ho_}Fw2{xA=0Z5v__PG#y#xLRbxa$(8`+Zkja}kPKcM2z=V`zu(U~^~3 z@EZCzFJ@RyrTK)EP;BJcm_~cakfAn7;^!g#(+oD&H&u6t8cZCm-#p(S&HAssWCLE& zoSbhm{%P~XpWB^^vVJ~k_#E8G+fS7B+0k3s4?g!E_geKo4f@8%1ft zvzJ1}JQF%8{arJg8%w~Z0{$fOdSu0+z?8ycGb~V~WnLe0#Pvj_ov`b-y9ywy6{>Np zdELo0+z*_!_f0pLMr3qK4%gxq(!)6SP|X&Y@KHdZ^xzj3*?*VVCh-D5Ndvs35JMfn zBK?rv%M>p8o7LtvcV_!*8KUT_!cZyIlHP$@d${J$$xabfGCCZ(+}+<*1ymsM*$!8iO`zB=_5RfdBJkOME7dwL9G*KZK^H!~g{-fHaz`M=^Kg#4gL@STq;H zNc}gAT_f5h$c{C?fY|P#Yfl%%+Fjb!xot-I5tDe?)+RIA6)>+A-|jcZe}Hzkqr~z5 zj0|&o8CgYA+h0iUB)Zf z)n~s}FFhTI*(u?A>0|!H_ zk0vT`cRE4k`V!i}rgG`}>Bk=-hq{D)wG-zu;PigbW5mb|+Cga=FA^L#?|Wml?b>f@ z;Z^yBM7(2cde=Vw-w?6=`ulwi1-w)AulrS`ztC<-qDR)P=62aR7Vwbt;_vGVW0Qun zw_Y77Tm@OIBU{JZcY5;1O=> zSNxe~d1O&QD%VsHADNb>?asX3%kKhjZf@>welOOzGR?plMC|*S3d;!K%^E8{C2~QU z`*)>mdcTC=b+K{#%_RxH>E+1z&m8?raJ;lNozdV)A^KbN!3RZI_Qb&uwpa+osriV9 zP#*ULKS;SMO@-l#P%Y>G#lpG5QbMzkXfrG{$*3{a{N>oZm1xe?;iz@#QhxnAJu$D@ z>~s`s=g0co;$Jm>qrZ&ISn*sa$h))Rx1Phqh21z)azu|3Dbu;@p(>@sZA(6{`BElJ z83HiZzoR}!2FTl0QDbEYZ>eN;R1gM?QOcL!QGVUW+~{cj?8R^>;L^mK^5W}Fv(cry zE}?{Q^`%a@e_kz{eb@;WNoN@6hQyX6xGtWla0#7^))h(+B$AO4#DB=V?R3`&`f8z)%7>l6!K-SG-VG*(21z}X%Bi!7zm8HVZYBDR z4YJ#VH$Q%&xsyMr;eP(2#uQ-A8&=1F)8{4BtDSFrby_x0EC{`=6J5LZ}d~_5!-ez(`IQc#o_`GuR zC%$~T?-VD&8}whEB+y((Z-@bmBi_kiyzh4NYoka8@DDT31$!h%br#eo)G?v*VV?IQOj5*^w)CCdunn%4Fo);6_ ztO^jHhORnIEQQm^`)!(pg;Nh*-_w~+AH`sTNC;7(=0|yEDD@NUhYNSFEJyG=EOK(M z{^%oh%=iIfm2H2)A-8`jJ=3^l{KTlHaI!@UvlZ>BFj&18-E#C-+n1~5!+)OsC-w-D zVbsP^TCh~2|1UpBXcvQzUw-;yK#@GUU+M&u5SkhLC(UFn*;hqUMKgr*^O1Z=%3tYQ zSx-%pugN)1Fn8aYl$M|&S;{_7Qyc8}R-Jc~-@#a`UvGw}Ty`qMi*dFDy*WF>@8R}~ z|NNfl{I+I4Qs2k1D=J=Eib8Lf#f>ua9`$G_-9>v(Jr^nKfRBbF44MW|ar z30j3g*O*!HY4_(P>JnOHWgalC&?ukbEH-Q{phet8QFP9L%|!Ui!hh@j5`G=w6$kml zdHXGAv7k?xxvWqE+w6viOca1S9t(KU8SNWT_WZT|d96c>1$E0-0ybV8x*8<>_#?`DkO z{igab@}%w6%KUA4yAZrqI?FyDsDgw_17Zjrn92azKm_v;1v%|_F1;=%h7 zX?_Adho8QS@IpGj!eLW-Y;(V|b6)&s#7)uzwlt^u{hB4BdbTf@)C~(Yq(a$Z@byOC zxrtwgbUv|zGscZipxPu$Itz|-t@X%O=)tYJz;5D6D%_FNtNMGu3L<=V?T93025{T7 zU2gcT#qjd2uNgCmPy7%P5`!6!%lWXhQoy$Mw&nmfLMgO#GmllSS?5no1l3=^53p#k zi1PvWb|;2ii@(^6V1l=D_81`+g=hj%H7Q5p*^>duM_nO^R@^aG;~zSfKG7j6jEHgu z0)($3u}Y5DoZ0qr%KpIGd$%ecrxkQ~8Hk+MVmf<$FGV4@_nS#`NxsantBa>(pEB76 zf7!m)d%)NR%j}z)p1T2lpOKr3+Je8$?XJ!cBfi2pAB!s^TleJIe573U26-|D`Z9kV z<6w-;<4!CTug(b=t{*_+ZtCZd=>{dVF_oDoL5C$H28;U~-_Gj`3*h{Z`#%ewhhn7j8Ecv`c>CU7eNE z5+^S&A8*#%B)23*8h0;Yg97M%m$&8m>0@{n0+%2tr_1jXZw;WfHp}j}f4J}p@KYa_ z@r!O5IaFj`o~`@5EE9^8vxt-#jOJ|u*^hGm-XVk6y-9ioYQ89hRb$Rd2SA!IXvuV<{#i4HV!Q zi{t6v@t@;I6J)R_AT|Z|B+o4&@ZAOE4tq=q0(wdCaZHj1z{?mPcK^}1ByP@Tb8|4x z7EF@hWl&$?1eAWhH;qe`5Kop{>X`=s4^?QsOr(hj^3LqZb+; zGMpC^NnOA$5?39tlHlMVJWS_j(DQ6drnNoWCcVpC97*w#B+*ooE-PWZHp2gQ1xceW ztMq5MgUeUAao;#*q2k0Ys7|)gk>^NOJQ8lQUf{kCwqN&xFD$HMU3ReNeP#HblVJdRh)wlw(5xax(KhA$yL&OX7yK|km+5!FI z=MU)?I|nrEmV6dmy$c&{ehauALr$MGV8F~s@B!^atRmx#{yXpxA;BT?6|dgDy=Vugs^hFm*E&8c0xc%|QI-Tg zDP*o}oOpFB+yd}d5;dHuHEcG}3wFb z{`r5CH;gBkbxStYDGJo}3oL+sii89g4~Ft^iitIs79tIj1-5WHp9VC9b?3zxI&CdZ z&$AYMAw5&7dveFr9qUVxjZHG0eFPdtJ}2!-l$2yG!_QyCMO*uy&(O(Z)Wq9xSF-2F z^w&3C@-s77PSySp|729y1^>hERR6_Ei2b|`5B@R!Srh+z=JSTe0JMD%3kYBX@FTD= zJX#0EGIMSY#&WemQHS%CuQ#tf?%h0j!ifAgSW~k^%=S=uXOqHXV-$b}!~x#%`Ig*! zSnM%q{u;2WmTLM+-R?T1nHP`xTl-sEPlPUQ2VCWXOrxDRPze6RxNjcGP+$az3-JP# zYRquhKx|aPMM{&7wxD-rb_L_Tixl^VRodtOKMTM;X|#02f|2mL(DNPq{0^yTGcjnc zK56cwAK43I8wPKR3D|gzu>2oQXZ{b>|Ni0EIkVXJeVviDkR`G=Qxr;;QfOf)Q52Gp zm}84~6s1LrOeGW{6iUs=S}L+{WkSfluQPML^ZnuTKb*%o=f3af^SVl;9jVtPDqsB_ z8dL)e@_s-W^OAfi5q1-382>wEQ_EMxeN|NuqiZIqQ&8YxT_oT#D)Hyj#7040`6KUV z*38?-rWcfd0RBF&c2Kk!RN3br zF8~f=QqQUpU1KW7_4tFzpC`>GxW@o|-Eh7C;(9h9u=}t6+`L}zjg-U<%@~S;t zm?cknorrF2XBcx#q)lNaXxJthVfj~cE^7K7K(=uT&_+=*zf5@!8ZJz3Z81|-0)0@t z5(QV1A?hXfE&c#~CHp@Y&6 z;w6G^dmfIfJL?6&Pi1DGEX~GJQ8h?q;q2Yk{4y> zP9V>#fXjb_f*L=~)J}Lxt{2-) zg~;f|&PLfGdSe+*ZrL+48iAq?A%m){L>n1A-97170O-YOo!U$8&h4(2;Jt3Eq{ljp zd^q!vxWVV`U{DVdER>G;N@dwZxocw`^6-)H0OlE`=J*V{Z;F>%3{d!0A-ls&ft zrFRwH_fZG=UwhzAA}9YSa2z-ErdF>D6K^D z!CgU3=JTnarIe(#n#3u|sz@zUendPWUbiFzbLVBzUS5++<#k=*?yCBbpy1D@)dwFf9*YKiqY;JEe%>8$ z&rS9}B=P$FI*Lv>lAKn};vZECh$U)0yyXy1Wm{1;(ENV%{%{@j`DR7PF1f{^p3|3e z1 zklbW>jj|l~ZNU;%h^;FI^?q;kk3Xg?Q2r@D{-gS_IS;CxKm$DdG3m)5S7JXrLw$UI zfTsz7jx$z2w$F1z&v9Qx5()e<)aYp+dAN>;0}~m#Jln^w@R{sj@mj>$s8f0$oxM}C zNX^MdzA2A;w+OF8e|Agi0W9ueF$^Rcfzue;_xlIH=myHG+xEfM+`;+F)1}%Mq@$X_ zg-a_ugJxYk)fExsE^M%Bu(vDR0j&)(eZTU2yAhY2p_r$Lk09h9C*6blI=x0EPshimLN2`L6t-Vb^dX)T z0~pb}-nS<+;>Iv|M{2e8C!)#@9x}|wLene(IT{>p^#q2UNPqc2(&-8DIA$ta_8zE_Dg}aeY!~B@}tipu68Xqy z1dpMsnpTP|mlfLt?T(XT>!@$$1}Wt&YGyFj?oW>e8xLfEzWI{+LB4)Nbw>~3>B8z^ z-J`~e8=GxZyGC}cUKsSw!!%QwIe#k{dC-Sp8L1)vP8*2>ilhSak_;wlM$CZ+WHTxK z?!i4FpaXK*vqRdUKnT-H1SBtUEal83$z3HM4{M-@mDcCUf8JYP*r?qXL|VFi30tb1 zzMWhG+Tr|tj3UgO$u@x4OI$iy<04HF#}J+*3Bq)L8&PcU?-eBm^(CfKTp83%U#!%W z@YqayCX~JxT(bsN-{{H9Ts4|X4J>V)#Xy#dl{OrwB%kfTaDsFW1T4v1HDm@X=>y$3~h?E7Zs{M*CeEM9&A~>p3VsoWIb0$E&#iM7JX2~K1wmD-SmC~UN!NO z{pxiy*@3eL=bkj6H>0@LE!Iz^UWoVXNiB{8B~7Gbu^N|5CM$(f)g_YML5G0ELeMlr&a_jFrXEFYFQ{fBve&VrU z8V>x4lGF&a$UXmg%_{{U#{eE&e;wm{s{O(6ehPyxIPg1FXXUhNd_C$HIK^4LrIcyo z?rVw6U;a}6s{{mzZP5QHq+@h`s^oWAk$uXuuZ80LwzX}0<2NJ*x;qKWO9qKs)}*N7 zBBFS2M#r^Y?P!M5B2*ErDpqvh51T zoO2+gQlD>0=A4UW82XvNhbffOx7ltxvVL}+6ukBRZ4MqVUJE(;h|unk%V9$yvO(5$ zkJ7=i>vW2{B#B?)5d_fG{ zZkITp&LrAroJK$LL|gQvT*kmMb~1CncS80ub4C>T5iSc|lv2xIZC&CEzgGr@5NV|h z%{!j{^hwtAkK`W5I>{@YrGAdPad(Ac5A^cn@G@NZ$&bw0I^uF(cD!*_!)fL`<&h*& zp9{&K!9%>{)ptSVSb4h#H%TTV4?Vqlr|IRur1^M2@-^)E>pf_cb?JqL1$tT`{aQ3< zF0lFTrw|9k@hZ2MmXsIuM)0A@+m?IhCt_c9h>3`5q83|#^N8RrKmy|qUt*1~fhK++ zo3z}@@FEC|`M&o$Zw3a3_=!7#X^&gn@+x{Q^bpvlNwvmXJ8QN0@=;y@g&>rEOB&3U z5`(S9kt0|jI-f=)os<*E-@r3x?EdpYXCBh8gz2@`t{zzUJ}k1+l@W z;dDOdEUg2lc9D083Xa%-s8dTHoKOuv_!v&*EjTLLq=-mKqEd7f-r(qy(t=4zJMq(I zEGxl0_$)@><$~UmYx`wdEp+BZwm;FS)b%P~{!+ca-ST3ijdJC~?{srmQq?fOc84%O zn6GMDUwt^;UCdZt9UjxOeT*uXPLOtBk>1w6K_VwX-YaKk=q&Tz-GEE+M(+;G`BkkD zejlA*oS^EjG8G;phF~XjT8^a@H*pq+@Pc>11+NcP{Fb(kb+}XDwdE&2qxe$n3r}%O z34vb@T06XsPMJE}VbVu|<=hY4O_Sq9+BC$ck~2rp*>LFY1>Q!?-YR5(pxzBx^~R7|}{s(A!H+ ztH2ST+=U*<+W0RaF{#A=}eCN)COMI;^Ga`pL_1u68y4xq04@hG3GoEId zn~BptT9&5OyXS+S3Suv4$|ao9$F6L z_-TNn&JNi8RB_tH*T^~8btSu;r=;5tP+oV%3<%cA-$=6+qYZ+bO=P*hAO0sy#{mHnF^JZxBYQHmLWHbHy$CD@j@CCwdIbMmgH{=R4@ z{O@Etc6>kcV>ys{!;`*~OapOXt$VUiSx~*T1bXt?ny8K+3q*JTB%Mtb=QTop$%(@r zS*2^Q%pJC~)~?J_y@!6t9WyBq>0gp-AsN;y&MnM4mDmZ@2)dV0>WC5Q2FAnlMo-O# z8u0n$JLxq=8MpEHkORpFtN)v#H^Fm%#y=<+9|&im(Ko;4Dg(Ct6+$>C>cpza+x?J5 zBHSRq8K1WE1f6ooq~jt04;7;uwA^Tqp`Nrwk}Lazh$7k65(H;Wro47Q-#?)G4i%S{ zo|v5R>I14cB9>elqq|H!^#yH^^#8v76Ee616Rf(6%qM~zfyb`%FgHI)F*o=(EVhwP z@Y1%?(2nyj(sGvBc5kSMMZjAn@yv69kUMbh4lBn_Ol)>aB^tG!YczyhmksmB z;E!Sk2fbRiJ{#MM{#zsb9p8ClpD9}-yz9|dgzuO7qxPW@)XkHH)?cKqDI6tk;GXGF zR{lPgN49vJrBb)$-G3?8ATK??r?6Xjtsp`&0-q1;>PeD$r|z z;pE}ARGphn9oRqT|57v6Lz*4}2K3mRxyL5X$*D}U9qTW`G$Hv6XKLW=96&pC;#E!) zbhHFVbOI+Ztr`d!!Qoc#ny=fM2wxg;#xjox(#{{QXxVukUM`S%;WoHEbHaR{mXHc8$%|seKdac2T zQG_=j&W>uI#CH5tl-2uATnaLVxpVYOuXf;~s+A~j7#v=3X;v>3eNQbu68USowcgJv z?KfTFooJsceswV2nEW>s@6x;an))v6t!KedkYh8l?I<1Hz5SwyQsdMA#Q=dd~@n5P!(n{T|7PSLg&iOXWbXhGd!1!phul^Od>)JW_k^XCvCh1C@VO0aTS zUHX3~^p5P=Z;#6JwN1i2j=e8k51*&H zu8CRXZcuADOHM(%F3wXIlK>j$PvSIx<(+bhw(a=V!_vN>oW;YWwl8KbUFy}K|qYEY=W!$4Zpqy&n z#WL?Oq{>!;lxpfkXq`yUG=8pNob(+%82acGKJ;k-sDaFH<1S$vZKg<(gPM1Jpn=`5 zt{HvX{CgmpQFoDZiBD7!KJFxI6o7dv)Ic~FU=TyqW)6rVTm2p-`XUoRh+6B?dt}yJ7PY}Z-hB8`geHZ z$?2;w_t9 zzWrBn@w#_F#JvPqUN22E$*N)8Hql~uvvd)9s0FP7FTD= zs9UX?e%JTs&aPQ&pgU-@WnSOqTtp#tVATj`2pB zC<9?uP~Wn`z_f3{J(TSKZ5by{yZ&Q(l34$J+dP zqNvXo^Wx@4_f-G2cXmt}F>!Lz->6+`X)t~=x4LpS7tKn{P>iopre*!I#t@G(hPni- zqxSzs+CUH4Z5npJP_shsGMwRj!f$#-kn*93Jacdc`pQq_g6-B(E`%yseqnGK=BQJb ztPmI?WaD!fp`KWqfgen<@!>cJ8D?8t<3KE7W`=_yg3mz6w%><`%vX~xlKlEY1A}A@ z=5}@*rbDu?*>7KT{K(jl16wsRF%D-uh+<(*W!+{*SA)rfv*lm!TPM8- z7A@dzvAosDiMz>1Jg)$9#BkFH#wD_B%RiD1YSMU}qsmKWEIytBrXy!af2pk^lD}US zmwzUo!toyYkZD4Fy^x8cvAFsV_dpp*e!HEV^#_&Ed{E-c&rKV8^#cA|c^TpmQgE<( zU2}*ObBZwd&CN8Vf8o=w8`F9*HF$0%H1xXZkOHCVA>=HAxTgwf6sn1kXPcswQh{~| zw4G;HO_TvRxg3?jco!BBdl`hCy~;dxQ9pmluO5$uxkfje*ACLb7OvpI z;|}jh-qj(2z0#X&XJ59IssPFXyVE$MqwIk5ge@?ogdjCw;0+VoUVLE(ng9KiR7zD@ zD?T$#(nV3_}XHSH3uj#l^s2=4N8Ax*rdW1^6@AWKp-W>{25Ec3S6?+e;)YFoD6Y2j|Uq|V<){& zL&qCPt8=#V>8f_3Px(46hE*{AWgv1d-z7~MsF7dUtqb- zS^D-`dWW2r9W-|V0^~P6dm%wG3Lh0J(b3}Rq+0C9pP1T#9|(6~;N?_>E#d(GG|Y>k zC31(jal}TIRGkzjbocbCtCU4h)YG`kb>xA8 ze1@1<hUGm=b?gjh%Rp&Xa-z0er?shi=q6L`k3Of9njTdR1Xpzx)f9K@c z7DpL9t>y2&CESnc*{-!=h0wc!H(H#yqN`pTF!^@h!0YSkAZrVXvx+m3MY(VcX5%+j zi?#v2h~J)X+j(={8>wbGJlCZJxX4U35Zy`D5a!0zZhkoEcpTAq&>nZPYh~{nzbQfn zoa~U6%PrBqbMm;bA>!_KQJ=63#ntO=Ei-Xz=Wwb*V?jcyGyVW=#~-Z!0iq)LG_Xtb zy5q^m#R@&$iH$BRnYehF@3$OkV#v~qIR+{x;@&)iU|2!)3GSc6&2=8(v@-(E58N4s zm!PjAg8jKAd|J9nZyyRSZtL%wVXH2aCqw8UYA zuiP+dT!x-q6mbdYrd0aGp`T{mf$ZPGm)-1>ndFVbswIq<%xV)SZl!;Ro%E&IP~bhSa=_oSWXxNHx$y79Uupaq|54!(0; zm{sTbjwkrL%=O`i9XxjFnpNpRQkHmX6- zD)+zoZ^$hO_qwWHxOHLCx6ovOw0&I-W?ogUJiM|Pyrc0q!v?Veppsp_cMOPtTrgGBc`U^6#70XE zjJ)G~EBM>~Z`;=&b;{r%{F*`vJ!O*Jer$cc<45(eCUf>_<-+WS(T@-QqKzdB+f_r$ zj0Lj*>M6hL;h8PuT-k>)*w;8G>=I!Deg7nj_1_e#vX&;}BZ4O7gr8!m-jC2vFd*vF z&_lAnA2%-r6;O#R^hqE4_{~pHF}Jo5=rpC)V#y< zp27lPZ8pylM~mXBgJxzvNvzLGR-pZQhdAh9QX3pz3V+-yI&bv?TH4*>BS5(Ub*ShE zLnk=UD~N{kZ&J#9%$)@D%N*)ad-Ef1GxAC6SKgC^D!Z0ZhYFAiyF-*@SlN8>ed-Sb zL9&$D*Jd#+@y0_ByCpxs0Vh-*J4dr-wY@(qA!zd|JYDx~Q>&k)80`WQwsw5=3g&1c! zB4H!e@^B(~d+TrM64#qv9uo`M1=U?^K1mO}s=EkNCHq!zKTIR)4>G>Xx1?VSq_%=_ zi6iJ_jXiGrbC-mM<&Mdx~(O$ zou2z|+Xe!GYorbTxvd-{cK4(Q`z(GLO&rM9T3MxjJ1KOyuTOgKwZ8OM5(-;fiE0rN z7Q*{uuV}8kW1-p0rKFZ!nl&pQEfytTA9?le(05P2>E#-k1^bF|w_H*&=lehO!w7%``v94@?BEIHiXo)LjkHmNHMXVL^_c~97p8&q-lY)pjk)p)O)=&-nj=(oatg$IUxOXH_`dyAJxnEmX1Q< zX>1Qa64HyB{*I6;F?{jqF#no?IAU{raZw~--^so?kfX=*7XV6a$uNA|o zLZl<5A36)eTds~7A}6|jo6ZFA@}+Hd5oL8|KLpjgh#rt#6$5Yl@Tmvo(w{3es(}Q7 z-27x-_)d4QA-md4j5YfMC7lqO4rri;FJ=5`(eq`YiNo}a<;8cJ9AQwI_1%9-Bv4p% z2T*+q@+GvoFu!`9a_?K!7)Jb1Va~RvKNGz{4iBRq66veyf|2G%g3|}h=WJ?ZWPp2l z_jyK2wnWEEL58L}d?8S++)vQxM$PVhCC6b)cElbNuoXg}R+F&Q( zg>Nn3q49#q=!T%VHtHyhJeP>##K=-sF1;uX*?zYfcd>-p{mI}=OwsJSd-qi%AB6xi zfT8e;)k=lRP+#p`Z{Gj9yv22#)4+?ZupF+qaS(luC#Rw{Wh7D5x)%zL&^ZT*iD>O0 zM??Sp!;9XmR5Ej4uL#^J4);`kDHgx9R-Fv^196#8jsn?;)@;6HRQf4stE=EqOUISp ziltvny5QDRcI@pinqI4yEEExArtCJIwZ5%6c+_tx>fUK_eY?=dESmPW2TFbtO1%aeraR_~#7 z^IIPI=taW*S~+?+++te=_xer~f%5ri{w?S;)a#HSvs9xto5TC@VnHSrF=m|Y(bKj% z(x0Yo&6t(>_hmAT{TOP7_r=gs?+VxL{!ISolk%&_;4Jx#DsZPt-p@hvEX5-9^*4(j zzAi2%Wnbr1ETKP)ejcvwzcAFzj-J1SnpCss>oYl2YV=SU+DvU>p(JC-_R{2h&OID5 z3R;DfJA)E{&&!;Yw^_S1Fqs9|)L?YWB0)C6zu_c!B1QrAtKquDr~xbQ>6T(l5V|@$ z?ES2j(ugq)W!|vGI>(lb@y$JqyL=te_!Ugcn&rQmU`)QjsgUo6$WWmWjMSJ_GDplQ3wmx!iYL>603stTebG7{mjog9l{UEM((dT7##6+2jy6tC|2?=$0mKPxX5m% zg=BGLw!>?Z;2J1+BH$$De&>Crfd;Asw4QU?ep5a@QhIg{_++oRN$m+d5AXsJk80hM zgo2*my*xry-b9~Y)V(j`3>}>|mMle4vs)}ZA*0c?7A*2)WbOU-4^(9|b6KGkwdBlC z(Fe0~q~&@I`u!ZJ#~1~g~e1i4|&6RDJVQOs+a zId_``Fv0yWGIh41S<|cyOoC{$tKoV|Aw zZQ!SZqGMEJ`c7U-s(H6T-X!f0><(Ul4gM%UPH(hyWHhOa2G%+kQTs#0>-*#zo*rey z_Hq=qh@2Jtsac#w?$trVHWoi#a;e=5>>;8;e(pBrv$0CwiKC7>ymR6o532LD2BURA zu!@JbF(zYTJI&ji_EeIoX0HRKA96*$W5~ZuZRXO5<4LEG?{hqs=@Y_0^2+loPcF)W zM9A+?b@s}SXtTjHx!B})VC7?;EPm~+qc>8NtNoxXzX$V#isL~s(tbm$G5E4Y+?(Ji zJ=)T+L6D|ZDcHUqj`JrrGee0`N!<1snKi%wf97Pcb1FQ1N9K(zm1CIWi%qr3hf-PPFI&U1A`T5+zncUDR; ziWt|0{y^!4FE?8Pcx_&1e`9|g!)|li(i)R|=jJ}opDC_a(@weJ7~AxBtR6@!TvdqL z9I=5WgBD{QbdW66r4!m(kUeu6eq|0yKfn+IugkBJKQg0jheu`fX9AA>(>j*Kj z!@mK>Ds)2dSudMCx-kDn35{Tpu1@4|KE0NuP%k`;FS>&V0h%0Y*}&lsuq4i5k2K>i z9PtEb;zX61w?xUEp4W0WkzNQ4piX`8!jtr}q{+fSM zjrmLgH!cedH-jE|)7H8}NOw#QNn;L~>sNoGF$U_=_`qp|KSqH^&=BN;wte-~85WmI z@(q=Y^S_mN&mjAKK7q=~^B7#x+$1Hz3IfcpIZJuQiNd4pFGWFBFdDp;H!t$k*UgU% z>D=#CE~uP;8U0piBFS3#A~1bteZ2LR5~p_?kI1aS@$D&Ns&D+}pjOi7xuIHDUuX}U zlMdH;Wf$UaT7JkN$u6x2b;Dcb!J!}Sj>|P4?&yMX7~&0_=o14YnBX@zQ|gWGgxgup z`Dm@UVD#SV(pKkYz1!REp?+^s*}9!z3Ukfgagv(ODYkw7bF!S;yZQWRoH*@p|M2aV z)UrmK^p*Q*^_MA)nk(zO_$V3BsYeQ}FTv7Ro43O56KKO}^JmJPT({lO^3`Y|#dFJ% zy#!9htKG{rILyFg+{#sGU?U=c9I(X4m$b`p+9gxfDF* z6BR>j;6D$>E?Wq~M^1WPp(<%Bz~s%OYv^kX^v~IIJU{8a%~Gf?Scj^LE9SR?ANX{C zkbApdVT45!7R^IL+cV?o2cEq!MJ}HRI~TFp21;9T`Q@bdOYKF;l*H3k2~a63yBs}@ zb^nR=`zNp)DZ5{7MUt_NnM`dopM#PVj{>qhQK|I@>NK~#G0DecWX*F+?7l6;mXiVu zo;+Uv>qEOk@vM;S1YY9mcP?mPFWwm-mg1y;-CX~!+GBpfsb;q(JMmglftAeB&Og3D zHvuDSh;2QD{BbTsKQ48Ycui6HfsXkD=xqVO@)3Q!P5QKBqDO+dQ;W{j8F5qJb-RYr zPBvBqPh#$ZwX@QaPM`Qd(uga|oK2c5i$HqKuw;b7Y-|{msEsMRl z9u_rSEwhS}^19I2HBu_adV*1sX@lb;HMj>8o^b?U|&5L z5o=z%Ax1s|SW~Y7Q)K^DTNQx+49Wqc7sz6m>_^#zV*q^kZ0)o($cZk5Edyu`So}d~ zf>+;&A!CTwlAzp8!*}wdEcTIcsEzDy-@n_NIOt*&nJd z+kEB7~3Lb~B+OjuKYi=Z~5IFZm{8MNTU7#wJ<&--%0}4-9?NfM%lp33Yg!w}8o>{*0GG@y_jUn~t6)rDuuTH4sF z;nejs0>@(h8A|tOp)!1|O_8wfzPs-pCZ0Ola9d|GZXZ@V0Z2@F_cpxx7Pmur6h#ZZ ze41SqD!0mZtPe1#EWADSF`Ip&kcYP!eR~gWUJZV9b%x){g2M+R+FZ_H(QSV|Xa5C4 zIHZA*Jj=KPkc57oapmhTh`<4NdMhvjixsanCW}=8kfHap2ruDkEk+qMkP-*p_;i@r zue^@EG7p%%`iXrc*ojz-#B^oo+Bu++`BAb zxd+Y)>RC%U>|gZeguP?B^NiT1$KxosX@32jl~=Z52Bt8R-B{p+z+IH+K4VB>N_yvH ztL@wK{Su+lUTMd-E{`u|e|=Bt*_aIotO^~n=VsNPTP8V zg;^TTi@bU#c|pxkk6~SLrE0W|>P?6p;rYI4T){JU%<9Kv1US=d5wQ zAmF_7TuRhio*awjrV)~rkaLz8n&eHMG!q}tLPdJ1eS1vumA@(uW$zu87s~Ry{7yxQ zM-{pkOYqulPTmK^t42zM=dco5`>+4^{1n@lR*VT|lpff6v4q@WS56~{0KoYp%1K%# zHK9M*;T_S!EYcT_g&P}9{YagyUeR-4N8SBPmtoTnwGAeAvrtaO%FBW-oZBS!`35f( z!OiANDARNx{zaN9mN@^}VXe&J))As02-Ln9035JD1^UI#ehM)TH2opmK~Zs0%F!AG zPS#*dLEojBT`q9YW0O{;@H=^JP@D#{KjOzZaxj+cMe&8DkDY&z7VosNwHVwRn_}IM zMn7qh=LB)i&k6BWMA}-Zc6lnt{=#W;(wx+mQ{Q$hZuP+3-4!ZMo)u2cC)sDW-O)#` z-GIE;|MMK5+<6F7{=>w|*JNiuoax#NGl9|`FPe!iR>C{^eS(Pzb*yZK2(kC z$+I5gOK8~T-aR`u0lDv&0eXc0l5Y;ihyRr9iQgNrlA*>-{0v@Loc7+y7Pvibe?>2U zs76S3az3tcHHGHWKK(*_{=@l?P%lsnd2M@QbDqOEj){nlJx%b3O0wn#1>a%74%gA~ z_Gr(`Lg1=RCCQN^kc57HzS*g_1wOv^hs|mcK%RaE&-k!VL$%I>NYTzs#!A|ZeY;8| z_TJ~sq!@0^DRxI|B*aX!KvrxDaaAZ_(RXi>ni}zdW`SuXLesW4*+=48Z zuSr(9bbARt{+O4Le$mUzX6>18oJb%_aAxsfDwNfK5+Uwt^Yv-$6MTGvcBg!NzLQD{A=h@V!>H za>RSo0rWVA=@8ZWGg}4750AH`H|@6zuTI~B6$HPvBa~tG#D@N+Oj8PtMTlAS z>I!Y*HIihm3_jr(eFYoo_4Hz{YcTg!X}r;7tOj`zR1|oJs`UvGsVxc}_5N3^@$tP^){|B0V==*1>qWys+f(!q==Zg1 zey!awFUKzS!6^!AArVF7z(<4Dn!nqunmkGtq0MAz^ube6oz>^5*P+&JDQ#T(@!I+m zU4vc4CYc-E&Wh>sC%5b^`GQz=7|o0vYx7WFtqRqxy~?0OF!?OzS)>q*gNM}vq=od~$F@fd} z7dOy5n|d~_5tCk(TLwMW$9f3&xmLOu)>`o+boDStFHOX>XiV-b@U=nHN2V~6yMdG9 ze%l|-X0{z?#wWgp`R^A3p*(9CBVgYmP!bi`4RV}0T>kE2eGrNPOgK_O{Eoi72=}8nF79Bxsp>@j5w{A2GRzWh5kQ%EOPJ_mTM2v$j6KIO{O6 z=jfHFff5aSgyzI6ehV38*-#j1MC9HieOJE5#FLRzVY6@0?+>L#&Z!@-#JiIjF;~Cs z4~S`h4pH1`*{sMnj}ClsVpZt|l6gc%_Fk<@UmUdMOEO6jX~|eK&&bS+(2v4iw>dzt zc5|&Xh0*Le>xbwE@x^~)T_|d`V{`MT*BCXcbLJ9HaN|C`Vna>5wS|vZ5GWA6N6OrPSm6;4Dtrv_ zQ^ktQF+w5Jt@^jIcHHEsikoV(H=$kKg2&wupC z#Y+3YOZhJ@chbWj?(fNMI6wc=T}v;sYPbE6ThA*6t5yb0tvd0I$E)FMb-teouur$)(=zcgGOzCDfX{|vvk&`dkI zdr%cE7XMBVLCM{Opt17EiC3Hw?XGlrIAh5X8@C@@{Vo?$eqVcAq>-R4xL(_>YY5Bo zm_ECoo&pzDSQkv+>L6vVrcqn)ycua@Yv|(yz&H+YCF(>w+^0J>JyR;+-aNJqoWbYp zFo?Hlr6$(_0Z8%@`6N8m@*aDAe`gCP6<{OX<_6qW362eWOj4yfs1n>-TlEaLq=yDo zA0-FAJL-Mqb@w6W;fEe z@@s;hmlBppYt$Q*vwfGs+4J;bFKW#uI_fZ^SBsw6lsSrqb9?4`#}7?K0dV%p`0rv~ z#2$LNRJp~)w0jl1?aPAwDP;Z47EIImS_%h_|5rYWWBmOS&H91GoBkD6^Gp}SWgcI8 zeHl1PMfkOtXJG6!=Wb*`$`n4JLq3hY=`Lt08t^ODG}J?*>>I1JuN=&b0iZeyr5U{0r3em(jF)mwa~_FmRlSb(S%bU0v%FC6+O zZ^1eff1j~_`ujP=*y4DPJ3y@sjb*()va_;`Yc-WXdhns^u*Gvwxc;OV_u^6Wa?ZP*un-dd=K z)Iqg8B21AjueYe^PaG0t5*a&zL=Ffv=8OqqC~b#vYxij8{X%yV&}{KT`q11#saSP} zQW!6q#0wGaUOnPGVR`W~7ni=YQWKkRn_rdq5#s0{X{iS(X{1>}dYziByJ*aGXr`bL@ zp+e3ujKemDE5Vc@0ZNBa3ir-me5%U%Uq|L>;t0k{S#kw`B#=# zY}V1n*KE{N#iFQY&W&e9>T>&CuTuBQkHd=%UH zwy;r_S5pF(+hH#tJ$KVN#=Z5D*J$x85hoQI64+UZYdSJ2##Ko2S=z(Gh|0yPTj<>M z*Dp!)FDBQTBEHGmd6-THDA1%q!D#4o0I6)FOO2y?m=Pw_z2{+S*RBKH_^YxK&!Zj4 zp+pd|I6DpGWVyY5F-)=;aS}6d&^NkGCc;>5{i}%PrbhojOHwsXAB!Z8i#uYvsGGLm zUi4~XgW*Qc$2&2SM!+`aB0guqDGFTWRfFSPNKKrguytzTdpYG-RJXfncFjMVdqM>K z&4Z!fw`oH$5XSR;av5(t7yg$8049LEF5yR3{$;GLv&yq@ETx*c6_d_`jpne1L4dx#QskRy*7AHQA+>r0F}+u#@e z)TGeadjGHWAdI>4B(JrPPT-&D|GGjN4NEo%#ALBGVk!Ve}9+z>`WBBHalSW!-#>nz+3jN|%M97FL=j5Nh@w>V4oQj@B@#;Z z%%Mc7Xhn$$k;+mOvdk!=>`Mq`LiT0s+svHre17=;1@pt4$2r%1-LKd4N$p??z22w_ zod48Y?1t!z{X}c2B(A}Wcl^7E1L;cAtnUY-t$fI%X0_+29J;^u4(z+G9`xgX-4dBm zf1ciVN@8DXkqZCmnJw}3bj0)hcLGJI`2V)9RAQOdV^zyLimtt=Xt5G*-4XR4FQ@V^ zbob?vg0nc-f$x7?kaUGGqghM)qwSMBgR`4>0*4MZ=vC7v1Rj&cnR1R7t~!{RDa&l| zj7}aSZ=>Fmgp9Q(nsHQF8_%%Q+L~4YhT6iq^NfRM_N9vb$rlVDec}axowy z=>myha!l-Fj+Guj_1wC6_I>XP2cBI5ih{OQv=xW|e%JXzi9_+fPSu9hv<-C2?RDrZ zcjo7R;zYnnvq@KY$3s*}_gdN>s1@Te;54U~gzOb+*=+n+?j@S&P|;OztbJ2q5_un1 zZxb7u5kP{{eV6;p%n*ABmjAG>aQ?_*1pF^}%ZjD!_bsq#efbW6N9(x(`ZBn*F}ywc zg5stuHsvS6iQK~~ls8QuYK54_C@QjBLtQjb)(J6|Aa1$Na>J_+uk%g}3v4jT|B zAkT+6TpDqD+N07|oL1uaxDn|5IHT-wGD=g>$1QnEJkQQ{6$KsHowe)3#G5ZU-xR&&&@G&y$%KN!GfUBq)ANcp4PFsSUTmHNs0(a|Jzu#VS zdj^UZPn^G4zc5b#owS1xd7dFiP*sJj+uaX3sU-)#uxyinSaYI%7&!uUhWST^$|rq1 z40;RaFeP87i-ByIDCYKli-8+Jg`B8T(rBhdiC_P=#FFdK`P=sEAx7CT`}voUjj| zl@*$>`cp7QBLOExH^gt=+>t#5frA1t-n_%coaPwo@ z6aFDVf6>BietJ{*Qvp%rA5k(!QAM9$@6XV8Md*?}dNOEVj}4nX%3EXy=a})W7dQ7% z)ln=N`WD+@dAhOXM|aFa>NPx$P5bn#w3$EE;6{#?EeLQ7F|8&--;x$nPO+pP({^i% z*3L{&mP#)?Vzk~%O8aRnO}h-F?mXJ48&2M7o)8Z`wft>{zK~`l73$#c{hZS&j#sS{ zXXg-p>z~_tpx+twH&pY?JQ-h+bIX0eDiwO|p%YX}{PxE;sx)6#b z3U3Q{on6q|A$X?N_s18i)^e+n+V0<)ce#&t>KCEB^b)V&qpt6U(4QMF%wOnzyLf-) zY<;RKYE?UBkfX1eHFTt*LV@$Qy1n#!&Q*U~sa-ZN^S8*`eunQXw(V7P$zA7C8aGCF z2Yqz!Lhg$u*Byk2)9KFFi=i{e$ck%aXex$Tb3LLO2a1NTHZMhOkyscJJP!5Dq-S}N zKI530Mo+q4ous2(8gEI#l|CZ=NP0{1MzCi%wZiQ^1(>iVdV95z>ta0=>PTL3Nmydu z&LHf`pV>cV#T>cfr@K+HQ6mN3?wXinffM*-^ zHnHJ{qRK~$HWXtU=foB_Hrwwx82(N*&&rFEfT8LklUDoZC=5PCc*{mh1tNuI9WB=L zjoCn^UeRRMu1;nmVjZaBhLRGabHE=>Ritj+@W#jOfz|aOxyY1BvxY&nT2%@4SGfe_d{ip%pTham6W{BN4HUm(#$K-wUWe7;3OBh#n)L2 z7t8Q3S3Cl*rFMao;IT6)BsI$nN7Qp*%|7GdsSJCM>pdK54D` z`6V4;H?mlxO+J?(*MBIc{hLrK1HUnQSDsC|$6eMkkS|%=6Z3ff^mE$EHe_(?0q8IdW}-eVOUfsS_~kRr4O{!*@70{%lFe8?kyE9OPz?md)QMXAvm=zCZh@N)mG&A?5{F_7c}Zji%M9H7j}gi zPXxU_Gt=*&m~X#_qcG2?w@{H~hd_i29%Qkvh}@^XT89pHN?Z9}uSiOeSwDDk*Fhq= z6BB${p#Pm}_XHuPcWs$0a)R?MU?KN^t-K6=dlz-k+l3Ov$mZv4Th}&z>D&U27 zXo~qZFj(sLvs|HT_Q7M`gZu2wE9AKiODenBJ$Kz_EqGui-R?R3*$3(gV>K{(eU7*Z z^!H#zX?bsWm!Q^VX4EIjN_i%IEj>x7sP^EiRojp>&r~> zI?oYoyDZ6Kieyf@7c^VSt5p@dTXHGx;sir_y5iwP|3KnxNJASs;0yJ*O5OK1CkzaW z!o?tC%DG{i04h-1Li(o7DWUU2v`s&1Gt%x4_zK^fu+dD=x)yC!LW9k2Q^ea=Q%=jd zhMH<$e{E(}r5MenF*tnCP$1v)G>n|%XuhQN15REQ<=m-_^w&Ei53qFRS==Un)r?cgi_m6UUF+w{ z`w0|giT9V#%E-*5SD$eoo3h?X5H*xlki_Bvd; z0jspOZOMSprs5Be%fIpd#4MXAOitNB1zx!Wgxqu|+WU1VZWDP_hB=Nj5$JOb+>}5_ zC=+8;Gt;665yRkw2o2fuIZXiv@~YGG3GZN2NwOw^+!6y7lH_nVwi)OkV>qU8_s$v! zpr{p`%c3@doR8SYi0Y#XCysJ5txV3!;~WxLc27#J6tCPW+k*+zgLO%NMt2y2ngsSv z%hM-IYH2#%<@PM_0x8-1HHJi?u$N<_0O=N*HETL*9u@4LriYVbYLtEyxQaG@E#yC9 zp~BUlokmPfo5Dv{i!~ETM<1-Gue&)`UEaIy$}S`Y{=i}BSB*U{<~;!hsYr?-y!B*q z-lG_0l~YrF?u45tmZSNeOPm(RV~Jtyg&hi*t*VE^hShHHAS!AV;&&-0#4e2|{nK1P z#9s1Zmig}ogd^pJ=&rZ?;=P}o&2**(KzD?PWS^w<0`(5ITu}z4A@EbCG)SLXyEv3a zFi3GFvqC1&5K$pn*WEa??Q99yJPneCsDE4cU=5_0TTp3fd(P5HkbL6CQ73ej-Ss$OJH@69V}onUNa=Q!Fa5kz8HbL#`-^Fs{N}tH6zjaHo>naeCwu0j*6AZ{1-Pf zD;G)1J=3%kN|6a^>53NAPip^iOV$!L9O0u|>_9TFO^j9Sd9v6n&-l8bN5=Vo)&dR+ zEEhN4kXtBCP>8?aoPw_WK^5i|oI&nm`I+)o z`K(IbvaQ&{3IHWPTKNQS2e%`%kZ?xVj`QMeh)Y4??4`9?VKCE!+!95_|I#s#-1Lld zK@db}fxU!FvILziE#f)`6F`Y;1%4+!6ozFw6N{|&jsvJ+Y9_itTbs&+V zcWF{wT7M?>0Av5cqN*zO$z0jyhs6WbO?Rl&TlsrB9MH*3dszwC4$Tt_9{uxp-?Nyx zrxWz1!lQY5(95#d=>10;3vapnoN}33L7e+WBb)nt`K7@_8QZ?)y&#V+?`GKefmVi+ zKFOZiW{y$21sCK>kv~8;ROp!)2Lm$av8YFIchy!~LMd0nnyzr(&n_A_L^Gla%^-=( zdW*hcCKp;ru9zMvezW|aK+UW#4whZ6yb!$8hxnnj*i5$7v;;8w!?~Zh0L9BS!ry@VXmUe3}(P^C(Y5A|~8rs9jTz7$+NIUEd{XIpz305hsyqHf@ zq&VTgU$ZMY4gdbjm;5y_(T>I)FoT_1g?lgTAUk+@!TQ%kn6A`I=n4^75lGh|PyZFH zMPKv#4}8K*5MDaawVMdb4e;kMii(y)bYHCQ?#A@APo;muG(?z6y{BX`!1UHaRJmrW zjEB{6=zH+5E+$xi`9c|Ta5cHI^>hMSS0Xiif+OKvkhzBSran!?{+8X2iXujy9QwOY zR1e8ifB@w$n^B52Y#VwP%##gvYUC~arCDs)Pa$H}8yD(mmR}=_1D@!EY2huF%x+7J z1A)I?LAt-7{ImVa*I5v&cY0;*MXKV>#T`~;vN6FIz4FozWaFfC;RVbc;Y1~<@ZfgI`_Oxsj=v(0d!pC1Di*yQn`DzmcM1qJSLA59ju8Dr6-W0#hHTFCQ$3bRx74-)v3 z?W;gQ#h88`qeK|%9XX;`BlC)DjK0U8D=0!6rF*?L?FY(Zo^Wb(r*_Cv`aHPHj^9eX zzm)k>`AU?X1aOr8GB#klTJVS=@Kdz>_sVqDFpe2@xvPOviDzH(NdFC%bpxhBcs`JDuSP55pO_14*FKD@Dv@O0 ziGxKG^ADPiALFpzUN`bEe@*)*l1L{f&Qszi`v%7;cCN=PIw~D`gKjzi{Niy`Ly8bNCrpz>MBVkNpoh z@^>vC`v(G&rjKHxCsFx3iA1916}0~Y=8)n!_(sA2BwLTC$jJ0km(VMidN@Qc$0(ajUzj{+`C6#yT)z0pPf&=U=?Bds99I`t@HgMZ+d# z0J}l7JnlDcj#f9qtfKw!ev)eZr^Q%wLvxCBeD2Yp`@YDoB@wro^m1;WIsGDv$X_si zeACiOgiBxC=oV;QLh_^_I@3Ex>UTFUbM~T@$6NL_`CHe$al}QF+iSpQRELi9*na%Q z;X=r?Kt+1YshOubzi=2n?Fd?}et#4k^&eT0rpZZR2A@?kf^q81$?=HZ*%hXg+Ae>> zhDf8k=6ZRYDqG2nDR?E$Sme7#9-g(AsZeqKtS*tKx$j7}HfSqqGgToeRZqZxn)MSNEQ0i~5w@bdTQf+o_pnx4{%+QMmc7^ZJUyn%F58?Tahy z2gWd3`=_y*4a5F*d;!~Lufeo=xfbXQt~BK+Fnh|FhV>9` zl6lQk(JfxMkm~~k?mu8=>02wZbG$&R4D&RUD$jp#D$%=lm)@lp@)r)&d1w|{m&|}h z$FOkhT5~9LaXu-exfI3Pq5KwmHNL;%WpqIu4T8opnqNg+ljT*_%UUeQE<_SKJVi;u zd@eRO>8io;v^bs~#{|z|NODN{?=q}0Yx`#+6_4k|;$ij3l-mF5k!hb8CHUOO?P*Sz zIVjXQaS)D#@-7G$ZUyzYu{`MB5O6w$YE8t@+l8oye5vTP`-eIndo`IXkzPQ$1BE2+#2*yfkm_21Z(r0z&o zNBait#}`hQRfsE*TgHVwl1Z`SB07vOfe|VdIc(>u`uKQds*QZh?gKTmVtb@=^>?l= z-fD^ZCU&BKD;2Cxi;g0Ufq3NhGGz{UpNbCx%|DbrAtb@ojr$n?%jk`dQSdhyN}a=c zq}FmN;y2m^>c7xbDz>4?3YxOuB$<9}LBo(xcpQpvV_c~F%PuMgLHI;NW=p!V@Xau) zb5d0Srceo3xFKOFikB}CFrR(!pRh)t&u-Mm#&@sKrL5UOi%-k@76R8J*;TiHc^&*_V3h;d+14u8LAfVl7dV8Gb4b5 zzak3Oi0yaUBcI5{C0`g{sQ;Y&U;5{k6G>rXvTp2NG-_J3wF_AiL|yOg9nejRZ;DYa z`Z-cg*<)bKUM5$x7~VMM4f4s`$rC(PV#rSw6uyZ*u9_{aDsb(u-&;gK zOh7B=FrkM^a5(z(;(c~`H?r%ba3=~q*p^Pq(<$4SWb1pe-cYcw>&W#A`DJbSy>-i} z5cO&sVOLRn9CAoh%NouTgszC;Tmru{i!t@O<8|n@{;&^YzVNzt`~8JlxupG6HR*br zX8>LsNaH3b0!e(<8wGpT{9J0lfS)0#g**j8$2Y9;^ zn*{W;8k!zjgO)Dn5_=2Vp!Mpz18;G{_-r*+)PC+J<8?8w(Sf*ygdv!KsW$>8GdT+I z1hfW#0aa#Yi&*l_(ShIwt1bIcS^N{lCMDLuJ1pEX;2W{4&A9dh2bVl6e zD-T)e@Y5{j%^D|NUL&nn(zMpTlD^(oV<+jr9+<-4(0L*1(tw{A+P}69MCQ!a(qxZg z-FEGV4`UqKxwL_l)g6bU`oTUN2Fto~bk{QNcM*3s6*OYu8nRAjq>? z@@NG=gz7;;`YSZk5SB;js<(azRgw2@!Leh6DSFhnxUe(7BIzN+iHxt@dK}u9kRS!= z{-o6uDB@~jp;O$+Y-Hbm=zZFtQBS%x0+jXDdTt}@F3v#H-;XQdz(a$Fnk3};b9O>2 z6|8*pJRkDQS`glWvYXlzRKXpHQ}^GW8fZ(nHB36hlTPTO3iCwbi?FLL-(mD=-Ulc6 z6>_hK_x002ARqBcRxET`*4bpOv!zpdPoz#ZIPDoZ^aIhjyAJby!^f_>^G(o!EH$|} zHteLS;qPk~y(~R}{Pbg3p$kt;=?A4E5B_jEO1@wIpTX|4BXylJ~-8cJd zn=fRO`6VE{<03hpv{kXBCG%_Mc7yD=HJ9U0lrJ1#&)Lgq6vl#qOFVb>)j>&lz~k-U z9&?3blb8sf8~BThpB(UCmEQz%Kq~<}1$Pri3oNPQBCKalV3{Z!vaCTc@7Cl9dWsZo{$f&!2zAN(BuA2cOF7oz-SK^~PB z%6Ne;wHYpczovn#COEX;F7%BQg;j0c?l|F7hV5BmyjP|Y0UP~0@}4kPGmP@Z z6|{)8PGww~t5p8R@p^%7S5C(hYw_?AeEpEq`BM z*IhcH1kx@U^n1t7%LJ9b8;da?&|UQSFz{f~9Dn+N5jhX)lpzX3p}c`_e7PuU#Zp-7 zUymoWc(;KujR`y4E73@kCCCRAkSGb9Qa^!{}Xto#R<&5P-_Jh7zX zTX{deg%LUK*~m&X&z-qW0(>*4H)cW`ayzYx*ir8#q@fqzBz@%@*RHCrp6gBq( zw7tOJ{Q7~dxoD4YR-cv%Sc9!~PiwfEePnN{3t}EiPaV-I+|lz~uR6<6 zB*gPLBzX}Ry~m^dG2(zcZ(dw>!_B(e4&G^`?Q-EB#<6xr{h7+SHW>~4N5SmFpMMKS ztp~gRyGWC4#JTLW@HmHZSr24wAJ%m=KsK!Z_AjDmulxsjh&kK@S=xyDUz*R3LVA0P z&DI_ygR9tV=OT%kFbRM+ryz6jYk*{UA0+#*VnixUU0dCAZ}y88>a!FN*1p2`eB1=r zwSLSo`GPjc^7zO-aJC`Mwum&!{pFsUBUyj+{Kb|W^v3h!+p>sIMWk>S(!^ox1s+Db zDxM%K{ZEk_JW@L{vlF7OAu?P>MrS%7A{PaLXV-loqeL~2Y_Oagx8Vdw0n_nH+4^Mp z4FT5&>G>ee%tI;`1@$l9>d5)!xdUhuLRthM3w82i+DS}LDplguShW#c-qMgLTzXEA zY1{*m#SJ_m>Nojnx9M6nt;UV1mCYLIDKZ@0j{(eEsQ!|u{;G)_K9tlUWNq==J)|q- zAQ~ti-(d8$4y(Q#dEv-ZbH%Fhjke_@^DcA$9!5gWy#u~IN_iR>aJ^UX2mauMYZUyf zJpJ4b-OJCDADr{phtV*~(ZBNXN$qdWFp#{Sr-R5X%i@3z0W#|0zhV6^ zL20vCj(gkaoHe58JrE+ighYfd>D40z_H)aW{82wDP{Q)ogNa}}E<%2GMdv>LlNy{V z4mzYvy_r4@gi_sQm8AM7$bUuP*$4eXlpks$QO?mmGEA2^v+|b$^|hodxev4PAy0I# zOp3{s;XJrGDU~6Hn}~lAc1Av-O+tnoh-jSBz_%#cM*)ZAZXRz zMr3k)EWO43Pbz}U4$@BZ>cn2~W@oiHVg)~&S7%jO2;1F-xQTXk=s7&%*u(L~(uHT@ zMppRRP;;PL?&onlvd5bF%%i9Lb|s*jc%`vJjwr!}P~Hw!Wf3lHRoe~zw22uH)l0F< z#=)m@`F1{kqzAi&DLf6|l3TQyK<<{HY{jM;K2s)F-s>~}q;Y`ZebitCv$2Y?bCWH$ z{O~B+h&Uv5{#|tVJ^+Ni?U>%Qadop091Ab`T`@8`F9saH&Fr3&u~fSfP465D3+>U8 zYkk}}vggwY&N<9HWaIr1frKFg^UX~hi#uJ0;tj+LuGyb- zgo6qbx))C#RTPw6BB_GxTKYMdiMgr7ucW-{y`3~6klNZlFfZU~MU6UsLKOe-yoX7!0k!A1{BZ-jg`EQXKJeQl)7Rc(L^iih zuO3QEzO?L%g8xZn1qFCS$Av-$F_ZgCW$v>FEco=F^q(6^B3+)J!a|IH(O5hhf6edi z{)B5khK%pI(=pk0Zee^?%$TV)KluTAx=x|@kOaa;am-S|UwgqUSUi%Ag&Ar_py)fv ze3ns-WoLtx!%}g+KoetLd>aTlZ^N)>B`$&*bWhaCjtY^QC3`A-aFX&I^X84F)6zie zYa~}Ead&m+3^Y}4a7u8~czUe8-jQA`YQ2iJiB9aBKzRD&eN}M-LcJ-N?h-i$bsnl?8Moz6nch zYzl?5#8+n;s*Vp9nJMi=eU~AR?xSm!qFr|GCp&X*pAbuJdC5ulYZ-W2J}0y`U*T55 z-LTJb#!z##*}I%YIgWB;M0oONq&o`Ze+^|q_gZt`^@cWH8MT+MpZQwVzD9ePbeBBb zjvZbfo#lhV+;BJ#o8Pl+)DNy$07P--tCZP>`*Z;Wi%!r_{xbZR)raxF^^0bA95E^J ze6@fqCzs-wStqpwiK9ZwYYk>QaQz=-JaXb-byTq;<){eS`2qb2uSeQq94~TZj2n5h zPS(IU+FSCHTigrv0Q!CVoG1VmC_S09ejEx&^I8HV*;6QRoP%8AAf!@WCTI3kH=?ik zmb>v)A*#F9QD99RP>m^ilH`)#Brs|(173chZ1eM{S41m+7VfyZ6or#3hnA&Lg?4a< zCk&#-tu3oRWBf~4M$^MU?1Qj@B0M7OUhU4~8VM4qEI(RL6STCx`rtQz1(J9Tyuo$C zKzZpGF*y%PG3MuA4LZICa~H5&`Fpro zxbb^!%RUrvq~4`&%f!zyA2}Q;~H+CF1eJ<`GJCO(-x=C5VHajaFB~BhPfXh zE4WBR->Jzya$+HUsH+fpBWUJ2{K?UvYF6Uty5uBh@~wG0`hK0QTU?g>Bj8a6KTu43 zYhLv#Tv4oMA^N)@I}1F*%*=KLs92SYCz%W$0{lF2Cc2%!X(>HrlEhdWJNg>&y4!vj zM0}c}J>uDnF?x_X9!->+FoE!ER1SjhN!}wBA^??XB-V-DOv?h3T5A~k3@GM@QjXN; zSx;f%Xr%CzTp$q(tZd!>RV40%9l$iG3flpKzz2NkkUF#I1jg31HHnHQSdb4XQBA;o zT-Vu3<@~jh5sZcP7UpFphWj;ECeTS-^yg9LYoS|tH_(LQlk}?wveqd02zWQ@yFwY0 zFy8h1ZSL`Z4cF(Nn()slr`rcE$KXP!9a7*>wXv_LH@q2(x++9=qikDynaohNebZ1VKkdGex;)`a+Q ze`q*^_K&U+(>eXD*}H}3cj~)ch`_OW9{)VEa{<})5N#w%Xkj0bkg@gw z^=-vl6=&G9%{GCU#&;d1p#|~F^#iZGGws5Ij*?VWgmh^R1uNXj9F`^{fbg=Ha%(2pMHdo_rW^rG&DJz$J6zefqKK}R_4 z0FfG7J?{RG=6(FsmncH%aTKO?5wsRt8L^+;p3v$V@>X$SXf1 z1E=LRs45B$@T`TXp|HRSAiap4APDMXUkhaqse7ZST}WX5*2S%7aNm%a-d0&wn;&}O z_42~WeT-YK@Q-{TxDV2rH4&SOKAR&-=!4iu`R5qZM^}&Q1#iec?~^?ag4H)TGXdAV zPY_C^u>;E+0SjVQM3$l}yx)DwW4PJ=kw4ENj|*(`bXerfwzU*2 zeT?_c#$Xe;)!y>BF}gk7O17~6ETm!sytdPC6D+QH??^!eIQlW9-d}fY^NNDF6xSw3 z2+E(Unbmjz#r}Op7ix^Y{MPdKOLNT{2iNQQ%T~01>?HhsjisYEVx%*NNls798mldy z9Xjwu{&o7kzQQkZZM?yu;zNM&bKnQkxQ^sML1Js&AJS4+XQQ>-Gjeg2X*l32%bdFS zd1iTGAqr?n%bkRa(#GX*#yBb&wrE_N3^edRPdzO_tj}&zY}n20NKg8(s&kHs$e*Cc zhQGv~!IRy<1>vcos4u=tE3s`7di@qg*Jnj@3k=7ASC`*}`JXR2FH@_nRu64irYWkj z;>(5wZ;hV2^|ET9dmD=R%_`+S;WW?YLVDFcWcPofOjkarmNW2!^GGNu2E56v70{LQ zXZbvnLn%Gp>4Y7W%0l}F&bi7FpVlBd`Zl^Hh}Q3E6SKFzg9;Q&k9uf8=TjsGAGcV* z(sey++Ombx@Y$b3X&7#}zSNR}C${^vS!f9(L@i+=vvgI);v4^n{PE<)%o9e39wgl@ z(UH?~lpyT7eRS*7Be5U-XLQ#Qbbcik^?mO+Reu1(vC5ys5cf15&y^Dme zp(wFUlQ~I;j!2FBn@$Oj7S>KB|-z+U9Y zQF4ict90T5Dy`>@yKKgVYEKy*Xd6^`=SCy*#xdaXfIk_KWvIJiS&@;iD|W{IL{2>Y z#~(WVO$<)Anz|X?zqdoNJp4L7&euQc@5?_Qh<)wkaRb3>^Znoqdh-Er31TyhYDxOpDjDo6yJy`yhBdr3>*}D-H_wgWxMlg09Ez0@8wa7Uv|k}l}eB*z~9aN z-6#oK#_s}kv>WJ86&l1CO3@-aaFeEA`bOO+F|V%?c!7s3LCX;1FL5iNG)v9R0YhwDPJe^YnBnvLA6svr_04?nse zX^qNbQO4)vuT7?p-*$kQ865^Txy+_+={Qi_U55uuiS*4rZeb$27`ixgDzrD%VWEJbF8P;t%gHK9~3Rs@Ro~t{57kO4q;W6_-b?8%mLf z?K_3De#5B*7Ko~(Og82AKPf*NCzk)kfEjZDqP-uD;L(|O`)5{jK7Fjw&EU- zgif@QAA%`4IE&1;RHRrtglaPbVFFq(;-O99rlJ3PM|5()9vj>^MZ@iIH#$lma!94Qs?pnh44K4PY?HE6rg_2A6I z)jo9qwr^!ElSYuQ^XHM(q4$!!*-l#1bmJQGbBz~|F{NcP-40D#0n>N}|GPrAt{(q;Z! z@+`LhXnz;_yJ@HBAa9^)BWjE{i+jdBcAHl)w74Qn)!oDFu|&N@MhEK=`E)ikNVb%#9MMPGT!Yu_I+*J z<Bi`sSO;nO86CV{`TCb# zmX0FG!J-m!ks(lP1GqUJ-K75-<5DEdlD-<{?%w=uDd%FLfR`PB5&RnMTRJ1!e!Bp3 zI~dh6I>F5KI`x4)H7gGXKFB5&;=kY*b_bV=t$>X6oP#XI1t?^)W|($)!F?O`_~w0E zUDh>iXc(yCdpq#pUz$52f;iEWf#iEu?589L;}h?t6E9SWLr*5IMxp(;Y#vg3tsviU zg_Y-YX8Vk~Xh2NAH!jUu$|Bh%Yh3MtW062Z@*~Wa7X0oF`v=_g!%={M1*g|$)3M{mcl`oUHigE;g}($KGj^no0cF)T)t=|?1B z0PATID8w=)Dca~~X7itw-tHpBaMAgK5=rzVF4h87vf*Qet>gVtxGEH!cBbM{L_oeu zz~RlwNWfGxcc1sN0pPti{bQyNBJTET>;vlCK=|kTGM@W=;5(`i29qq{kOgOR&_ZaM!{aGD$qnwtUUw?6s<5x9#*>cfQ3~Mmu4gREXzA|hezjyMhn{~0h6 z`%lu+`KD|5_`vsGCMY{$BJ1YndSuimh38YCRBTOu8{>mLTu^9KF9|}mFAHd{+o8p& zI^w3mk<_IdrCM-IG5XaA`TLm>)axxhfgtC`c|_KD*)AgUnvC~xo$;ceXS^Hxz*`*o z5~}bwyccimM>Y;@NAkj5|6qw8D zX4VP3#2h;D(5kZ&u&q0&DnH)#k#X-!kGsp#(FYF0GH7-sPbgiXPl7c_WG*Dyp(<5c z4@S{eqv?t0kZ1D#>QZc%{)4j*&|XBoV~hZmN}An^umlmL^vuO``_7pY#XThMhWn{a z33F;F<&rkoTR!Et3E;E78!#f`=-T05kdNJJk8vTw^x5Tz+CCG_*WrTL^Rm?2_}uH? z`JcrYT27cgUOxc(fJ&5uOFcP~MT{fJmlYELwV$(}|M-{_D@5zX7XC@dJ5wFB)AI;C z29)rH0c{jPGzR}}+?)*L+&|!m#Kh*kqJ$gor#y-Wr%uH6X73#@j}3Ey z|JLCUwvsH>%*}uGGY;c_PKEpuuz>&P^1Ym}w#)ow+rZ^xs@q*QH2&_L7z8HMO%^V| zlb7?YjZWFTKn2A2N6p z^Mz}*P>Xj(mB!FoiYbd9q1t?Ndgxo+Tc;E2?mI(H1!x(#K!Hesg~zC%gGk{C!Xo=l zAJzD=S7qT+yj^{3vdQiLE`9mo`Axp*Jqf;JGWJOm$B~27^-MB#^a*$^Y+LF{ITYil6YlZt-!f#6jp27S8AVX0Hf?UwHP0 zot(eg9%gloI?7#n@u4m3eq3mEsk^{(FY%4N7mqgm3Eli!kw|+=+bz@a{Iez4+jbWj zFpvt_uL!<_iy~k%$q5W?yA1+g8Xwl^!q<3O?hmgmxVC5CoMA(Eoc%&J4DkGhJP%%%OZ5OfT)m5I6o7ltpP$QU2n2W5QUq30~Ssuzn}zHQ1RL-=vlx zaswHQBVG6cT7L{2fNLeFUn%G165oh()M6tQxnGcuzYdo+PKF-;L^ptIw}_y_ZL3#~ zF;U5h*Q-lU=l^6ZtnL1|3tEpfl?vr_oa4@q|MSH62bV>P;O6R{T~FKC!L5G0vifo~ zK{1E`-eGT^VU^i{^iwUPAI%oC!%WHnM6@u?edoezg1S!S+rNcYOWmzVw! z%Z-EU+u9=>+8(JN6!te~?!%i3`;Xlec8zcl-9x>PnkQ7c=JndQJ7qk{czL!5SF9wj zws}}-loop9uY$Q!w40H@vQ7Oq?tQ)X)p9gg1@IeKWb^CP*A`#ZlF=B8F4fyIbo*uP7tq(Y+Ep*w!eLBoTQI+85^53YZ zU=YhMc;ALf?4m&yRHzCrV1<8Hd`8hVx5W_Do@#a43H2CC6$lt0__t31APFktvGjyx z6XznQb=j34KrHORW>tC=2p7h@`MB#I8O%atS>napVF}aJZO0JSIHI=5u;+dcoVFK) zGiQ(?${}UeU zU^d9F3MsB%Xj$)=qh0S>K(?MRd*K0O`^!5O&<`*>AIKXHxoKox_^@~VrN74?rE~Hf zyETM7d#L77DH^ziSb0Baad(y1eZ-4*r{Yu^LWw-`~m+V3Ar<%hpyNOW=5+y zb-1YARZpB@r&=52fD&ARd#!yNVjsgYcBCixEh3*6DWZ^9E~3US;KDk*%0?`B&EDKRbWN0bm@oU4 z4yz{TG_u5FAj@$7bA3G9KPPRvyMIqGpm5y2BXT$@_RH8zW324DgfXU9f*FHSMYATM z3x@>^43(He%7L)|tu4+hTkPwgA7T=5Mi2{PnjZ zjGYSQ*@r2>HF%aDymEMIwEe&hRgYuBQaa?G=Z@4`2eRBdb*WMo*s=Sd_#;e52OLN$?b9xE)ri9+&Fj?&UV+MW!f|4c|;Reb;#U!JIiU9@uer7xTC3 z$1sZMj1ZCPFFWQt2#h3q8o^CqOvaI?2lTZ#Hm9y1MP1e{S}1Bn4ANHap?MU!ob$*GyX8 zy&KPb5C0I3Tag^KP+B>97kCavh> zW~Kg=9FAG>k*K|>+R!-AD&$hOgUKucZ;`9PdIv8S*_qyZ0IuyiQtTB&%%V~>eZ6xH zO|Kq65XP=w8&!VMzN2Qx3in`&Kb{F%`8E1MIvRLG=IgV?U;azE@hHj&Gn6@_atD-F zh?o7{karJ(|3Yz70}W99wWd=E9HF6XFV)(Z8>t`3HinoRSRQS9Mqo+rig{ zTb#wv%h7SCO9KkT>AQ{RWthVolZUs81>HG~XBFEae$S5K5En69MeSWHKh^vs6r|6i zR8tQe0s?u)S@?6CogeTvW9fQq!{^g0TOo%OU_CuJo*-B*86ALPS;vQ{BuMS`*jM*_ z$a~@9bO@Y^>$K#F2v7^kCANWOwEqyr7bP=Yje5N#$FVYOdM?q5Y^)jHLnP2ft4~;d ztxKHgtAw$a1mSgyO*QgRCr(kO6(SRuXP739SlxyXR;b|*7S;JLJGl%GKt)QVspHZB z+l#Y8(|6N;x>gLLh7D+&L36#B+*Q7x0wz8vxf6ERgvh$epAhMa0}7wt z==7az3OLbKH<{qT(=fo(>CAmPv>s4Q%4@Zd%!u+8FLVs2I`^D@yS0Be7HSG~i(Q~f z>UL$;nSmgVLP8BMSl8tK;d6XQjV|R=arJ|+DC5fkZ1fiIUN0cOVTZdATN zS}Y=WORge)ehQOZ(#iH|y>)in;(!+Hg4}K*^?{Ki5{aF`XCSpfMQeN8=$7q8)pt{P zEF5O<#mA$e#Y$v#s@aD$7hR9l!PDCG*HDjK>nnbDhhB?vE5Ahb29c+Z;NgSJ4LhSR z{gFLyloAUN1Fobr@dBd4Mt;?rNqXPA!>w!X)elSa)I z!fhdFVEe)UNEjA@murQd()N8u9Dlc0u+VaNps{lTOBymaTyI5k?hYeeQ0i3@ z{7N+Sk{r4;AeuUbl`-ZqVwAy$<;&XrJbSSlX}iqF-c8|Y=Yi=e_ku|DA4}d2zeFM1 zY-(E+>DB*d0rcBZ%uOCgaLE?x;B;->y8Gj;+mk)vb%7ClW4fEiC89p4IIysy0s{i` z17-6(eQR_l)x&t`SX(3G&G}&ggTHH(7XyagPCxG%0D$xA?biL2UvnupOuOnBr`DV4 z_%He(XJyGf6qXRFIR2kdp=e&zdjYB$8H=s6vX__eO`AsunBsjN9Bq`x&a)GOa-MgE zD|sHKH#_>nzHcB6OHSc}Ztkae!d7s>&=PHoS8!fub_aL%T^{0#_m>z%Hm_1V!Ek8g zgIEn_DS(~kY)!!J6C5xbO0^Y)|AIa*xvhDl%HPB^Lr>% z{Pg~-v$W8pG--nmPtqR|P;cnOzorJj<_!!u8`f%oN8vA8BP2=rH z*`t4zKW0dJW8Y1@Q9Vju5-g*It72x3-ophw)4RQHr!McnggYJnNWBN<8ztC~ACKA5 zSzvl8^2;+zlZ)+qMG%+0xnF3JL5Qcd9NLdp_k*&VrQ`--APUbRO81c-h^cjGnDPAo zNIDaDsNTPi-)DCFzLae&A$yi6%SDrBT({|KeLt6%lHOp|2(qXDo7 zMdovgK0s+ks5)Ody5msbi~e_hNmb&a!EcA6X#RuSeoiR70%yY2eic%HK}W(>F}>wD zg8|l({nhi3HK$P5_4EN=InCjbGJ$TPQB}G`%57)`0S%V zQ`j+&U&R)dxq-(lDs;zHOoyAQB00K-RCv-$jOWm zZw7sV$6V`JUL*_M(VC@+PHmJmUx-E6TgJ|-P~Y0fo4bh@+YS2#tB=q7nj4Z&VZ`@? zysQEA5mqmjTnz%&^V9i-li^Vjw^yIHN?uQdU0o(c{@5J=IXNGwy6S;!odS*P-7h&e ze^HY#9Rjmp_Y_gsqz>rCGZhV%>8Zbhr{W8aBz%kN59OMA$UfkeSm}aSjDQE4zuesq z{Qgb7nJAP=EC2d+!?%pXEl1E6k>xWRRPF(fBD>@PGE`$;yMh>(0BN=OHM$O1eA57A zKxRWLl|FoE2>!8t!AF(+`QWXjPX|Pwar9dA_4@R>u`p9`8yS?V*`tYvz%jI&8F9?9 zc>=~k{pY3l%o!_V^diVDOM&@o^Oho4)ALr72$1sE5)=#wg}33E@N0NOVP-RT^MZEM zsmH=7=}In-4cNKZy+fYv=HfQV++V2zezITIrtd=+s5ADU?ub+JyWU}m@!p?A#o=)m zJ@18EIoSA>q|+d3*wG3oq3+x4r~To4-KErIGBI}@Vu--Wf$S5)l<%;7#Bmb}iqzP= zGX_|hQtxr3eq+S7E~m~w@x6v?rzM%8rpR2&a{l1p=XEdHiXM8y9vIp9N)sa`B``$0 z!F{1LZ3dq{6lBH^Z(21VQcuNLIWIU%XX%%rgdN-P-!<1sF=5~5o}gMQT|oF1pJ!@i zoZqh3qX>yMnXeJFGclZ(fcUC#Udlwi9KU{;dccmX^1j$-pBLA|H^=$;Cy^TY{O#5j z=ZAtv_*|9(S6N;^O?W~&m%0F?x#o%k^U8;~;g2MiS`_{94YABVO~$uW?Ykfmy`2bX zNuEP(>CpVs3y72|h~|(Mxs^uB9Mu`2nr7utpMZX)joC-1q3jGEruxNVM9U5%yy-RD znz)ZAFH(wGeSugcO$~ZTg=MeEsYPWHvUf=cAH^L{Elu2`eSwmL78W6&jyDsdwTZRM zoG^=xw}x{>A>-VcIV}Q&VY}|7$tNl@oYE0PmQXU*FNWhysXBWNQRgw#!``^Ut&7o~ zleBcd@S_-!HE#3W^&Gx0pW673zdFj4vyOPJ-^zJz>GJ;3Vw)ZCLjz1Cyfk^ub<>BB zvKRQH&lodw00Elj88_<57hHXY?Y!z`O)3?tj&BURp$MB>vStc3u$c>pg3F@REpNxL zV&E%Nt<5JXqVx|rUB2a{A%YhVW!Z+|tYBgC)EY-ijX9zVV)MN-_SRKK`$66%^D0L{z8 z-*g&AoO#P0x`xF-IQSg|qdEn)pHR9Gwi3M&ymzfFZtm7InNBF}DJS~R2dH*X=j#nC zkf1|F#O zJi5a_p;|s^KC`O|**buuZLhX%y^lu`XyGH(e84{LOMn*92wopJMC|24p15o8V&n<- z%mHS>3n*|KdG3D8^_}DwIN6<0dq+)*T(Ot^$Mv;PlN%lh1juiS&NT)q9nFGyORs*Z z{;C~VnHV=0dP2CzH((9fo1yLUieJF#eiQ5gT0o|==9L@vt>qOlh(anYci1*XPTGln&K|a z3&U?Q=AmigpT$)*M@XxH-vw|oekc`~*T*m3GzS*1#_kEZXMwk@wx>ummYBc41bgC> z)r((p!oZCaq2GrTq2WIV>Lo0Mj>6B;($0@`4Zy8GJ3(cS?6~>hbJBjklGXPB{P+}v>-5!>R z!!e)$#W_a?66pR5X20J1;u*IY1Bx|BN;sSV0Rhkt6II=n;-9RRoaG}o(JS#pLm&)? zQU015!8Luz)|>n39dXNwCU&agtO0{8aEI{ zJvs3Yk1F*Mp0pr?sN27Hz;7X)^L*r=^f;gEiO<*D<)9T#f*bYH25XxPN*eLrqDL!l ze%9sPyg`~4Z6ew7s08qT<7raD_wZrhb5_#lF(=}i+5WEbSB=NSO^h|-Z68Zjyvri5 zQz{e385@*NujuHV!uug>cCKbgI4oN6O$9C+dgKtS0euE87JqCa((6MIQe}Y==1A-;h(t016EHkoxP^k$YXp(li*n z&8N4qP>o!BDpa}DE6sFN^MO^Ze%}mFusEr=u!L-uNR(yhZ9uC3Wu4!agoEW6OG_ig zPeF;N1#JOyWIH)epvVxu@C~y$HFeChQWA&&aa8}rqL=CH`)iY>>o>jay5%XB0`Srd z;XE+#SX8rO{@MFK*^d~rLQ2Eo5j6v2W)@^TIj^u0Nbc;>qO75lIm#rxmR>OO>e`j~ z!-kG+y>{1UU~v>2M6=Q%QcB;41Fmz^iFgEv1sb>5ypk;JD^{RT$PbAxvNy8o-AP}^3JEudSnTUT;3;C2yfzd!BO&69oW|p~Efo zD?ltmx}U%0s7p#W#U=q^9=&l=9}0>FwhAT!A*`EU_P!9oFox-pN|ZnRpg6tl%C;bM zu^Q#^2e@E#Zd8<_7|z&D2pMok*`40CC~>JYOelY4Q2bmOOwbDpf;xf9>*b%v?t_)5 z)t$9xccZuvZH*dDrUR(RaB>f}!lBNQ^QPo%eZo$$v?$oqMff9B7Zx6|R(;~(9*my+B&gsC zkz>5-hSbGMP3zVWNE1HEf&m0f8HJ{!TU_>%;Uv#_goiz*N;&6Kc}~7?=vE{;M#n}x z#J=(guvirVKAdLNAeEF&h4Wu-YR{c(n#rED!vGHMa^uEx?IqBoT zW%xxxzmFSQ>llGnM}E&Pb1u1yR}o~~7qV;fe`-YY+}9slRU-c5UdoEsFh^4O+wm2u zrWeReQC-8{?W=dXV&*9h7Kf(SsE?+G4r$z$ijhYm4jOY5F3v5B{fcA>*>4`=BeMKN zemszE^p4=J`h6D-Fo)}FdUC4j<=?;=<>kgrY`)~J2YAL)8`Q`H9(|CSL)|G6n9Rvl z>jQG^6H6c8i6ksSj_%PhNcIhlj##3zYU*1d5y`4q(?oFg@k>}>@to~4zuV$50DH=l z|1^TlAJidZblPrjBF!|>-m=sha@2!7T8oTOhJWJMGzZ*oyhW_;@>pk+=LDLaz%wSq z!c36-;Nw{$lfTMy^;mN^r8Mu~mVj}^{eu}8Ca1a#dBO=!3!7V5mqiN`z-O(?z-(6o zUp)Ek6^oUpD0acn7(T%>#PIA6Mj<3m{D&G0{P)LzZlVNtL+cOWhtf`O<1c<&(MFC`&=WrToKQ`1SjI`_zN*ao+5tUxUS>GY2ohNqE{Y*c-smj*VA2L4we< z-^UMw7ENm?^5P^(4nZh$q$y+!1-C%+=ndq4;A;MJBtkhjs*Rs(2XPRS`eN+DQ7hnq zugE+|;)|X}dN`3u)%6X<5-oVR5hkOhFJZ2j8}l=`!4^)20x(7K2LI(W-bJAW<`0L1 zCzzxi^3%X=D_8`6Zz@tS+3bU|e&PO(CvR0a(Mdcr&x8H}qz3}--n4xYskre;m{=}F zX3^8HOb>m93{zm$CEd3Ukd9`~KG1ze$5B61NzrGny z-3_^1VqQs~+WDJftgQ}|w<8yxp*jj|dUK+(v0tgd-9s4|MxCxH?3X7tK2T416{xql zwirKQcq2xY`I?U=K_gXc@qu8?&jJ7sZt?@+XOL!fBwmN?hR_#T`MRfg$rT*>@ik-$ zKtwDt5VWx8%*v>qa!}@O)_>_nJM?|^35R-H!~kek06UXkgSac&CgbGA9xJ9<$mKFF3lajJhBp zz%-b&76uhvY0&5sjFSNH2Fnr(mR^k17oNXcj_*W@H~#a{-HOxI?cqTu^Y!o^C+mRw zf|gG}(LoxXJjD6<`T4%R^rJwSzf=k9et779sa!L{r-2MSs`j4JFwFgt>0W~3y%D?P!Km_2_LFAQf% zB3XWrEF-T~l)a(?0dgl|vO2o)GnczGVQxNE;9TGeosRwOnnun-^U*b2J~vmtlJ%bb zCIg%TX(+Bo^7Iga(YAKcj`8_?2bGI*>BSH*dOV1Wpg@8)Bf%8zF1yg`8RG*FQP-9r zdmYBBORi^mi1;-xS62^_udn1XGr0&P_K@PmBaa&)0u9WT3pfbZfwD|OW`)d^gzxVV zH6C)nVd}K-!A3EKvz)bJ>gFoRx$`I6RE+p|`;!M$l}n73dV6^J5O%S$+GAp zO*Ce$LND@ij|rQ(?AY5;0l^#e3(%#m>1Yh?b&8(=bA68jO+Oo2XXtZ_*^XG z(@pj8(8Mv!VHoUPjF~4XLyfFw7eIL6WHN8QKC&f<&wQ-Vii2@6;Ss|3eBZGZQ-GY^ zeFZr1{IRY;SC+dv!hpzavX+w6kYxIx%^IKW(V3;(W3|(s#B|it@486CHcisa^+_p(505uz{)cOUwj^_&J z?lbB&WQ({;9dk$SVOtTi*_dnQ&cLF?H~r0|gaL(mH{#e(6HD+lz!kt*97yalc6Kkk zl9ruRsSXS%WMI))wo>K;d{bCc^h1l#1KIYQxXMG06V-co^G?LqO=Vz&G7r)XctyV| z1cLc5h8I0IhQ=xpVLXk?%fF=z-U>OMn+z?W304Wf#YC7Yn6>mr=n7jv049nzdzhAZ7+KJ z^p+{_h>@EWF5q8uHm{w2&SoulYy6@Kg{Ub+b1q1M%YHBthl>=F#$UA z4?QLjUBK&nIH8eZAtGt+96RcF$sCI|SQ0vhWG*ggEUpiSs?U7Ao5UeLL_1t@-v0pofaTziT;7-yirt!9 z<~@&`j)AlDhvwo}*VsNqEN+>*#<5qGxi#=IW}5@IEcUyCruh=punZ6D+AEU72fSD< zg5;gDPq{-YQdN3B-UmZ8XQ1B1KRUss*2$QpwxbxtBclQvpmDQr77ey5~Z|P(|2To7rOc(s^*^gkCL{-=juL z(P<4l(JBSxOH;l`yUp3Bn@G~M0Wr37^gD*ap8f-phs7@!fdP+fABAROLf8xK z!!USpBp2LRK2$a{AQ8lgxzE|7BXylKIrFf1M^V5kn@5WM@E3JL==$_Tfy+?+MD4^d zch&0DLm$HC`OYJ}(`uM~NEw&fmQr{aghRkHhhS;92zRJhBT&RCz^UIoA zeLK$Qkh3O1A1_tJgsD$T2;7fNq3>$^bDjrXqI)4AW~N1Dn**wKC*al;*>IhtonxB zk2zB_RJ3(m2E0auBm#gZC;HA?gy$JzYmt7detRWS#+f6qk?o6WZ;g5ns zMqW&#*BdmSk;}`Er6$ugcQzRxKS$p)kbCln?VYv8iMBj6Z@caW{EkDXRcT42~}Ql z7ErB`3<>WM$_#$wd_06EZ@t{S&4bpUpoH>#j1>tKJ@;&bXhgpK>UBQ7p}7d#v*80f z2}C}(BcCb05eXvUpMrpUFpaH$?@GL)spQ(Og*l(Rlarn`RhD-?n2o z5Vi(EZ#M54q@4uCS2&1elVbon?sS`|-m_5!T?5*|;{4g+uQyJDIw&L>^;x1FoYdL$ zdy|q(KgYZ!6TX@Nep~Bzt zA5pIY;AS#KCTEw@W%gEA)Vh2wn9ofEmrXXyRj_1XSnzkH%kGR%JQ{JPi|FM9^nE^B zRO!lc(yK^<`MNkGX9d0c4y@_8;p>2Vh9}5c{);b={+F`w#j6^4Xwc5eT+l5Q1&mm; z06#R7YnQ*$`2GW*vn75%|n*5yKvD_(^!OOJR!(vb?dDvqd~FMNiO!)E}*k&{38~e zVsCi)$7uhi(@fjNvL*PNx*+M`?TS`EXX#7r9eZtd+nn_Shml}HELZftBdGnp5wwTm z>QiTmhfWOSi>}9d(6l!M`6xYPYpRrzn58t?1WImv{KnGIRZv=QVb!*oCB@2bqVjLp zf7)~37eq%M;+C_apGXFQw&tD{H$tCC?<^H7q!r*p>_GMGQW4Mt?p%^PFHh$4|*xmaG8WN`uKCiC$M<)RHIGe})t+;e8iW+SD9^^pBKAf({1g0Vji=siGg_Fw_O?;^q=cOqFPk(Ne4+6iC zfTqX@ge~!`=GgFzYeShuH(L%V%#V>#fgcyaHi)@{@#&%Eaekaiz01+Qw?kcG>aaJa;hFOM!Fc_7!*e2xLOl$$ z*9ElL{MYpy!{Rz8j#K-qn%pO$lv=(;Xs3Mp#Kum(BFbsT{>0c`!0@h(F07)y622=) z9F6Cj20E4|-&Y9y@mXAg6n)2A@J391cGh#3ba!BN))w7kHEv+&sY35pYVmv_a%vW- z#XUxLR>X^<&4ihwIUP`3)o*1SS;`s*0PV*-)@!SrHO`^dijp@DJenDD8XD$pQ|B$! zdjQ_zF32J;abY_m{lfG0E}(AgJBecca5EnDJ~2@}w4e&4b>tS@ZN{V9B^bvxpL3_E zuU|o|kT+iM2zYT9e+F#yhazv1h^Hm)zEPww1oK|r$uohj%8~yi#Pff1yB9B!2Q)Z0 z3#;3Fy%0hx@(XZ7jvgzAE=ah&wUEzX1+N`vQ@<+@Ff!KirBM`in^Jjp{wnBHIL4uG z*#9um?%4xWc8}Z)1%Wxu4&*dg-Wkrv3lmfQ$g^Pcu@UBoDzm&fOFGjXd?~d!bby>1 z8p}Sq9ljd{Pqj^{8KNH^m~k}*ZzjJM%_j1|Pay0=xuqxy(If{t{3`a@?frZ3(7+ah zE<_wmS*P~j=!LED!hI9@m5EIU(f1r^?-NBgP)@4CJ?I7j-WM825Yrh2F_8ME^dN@f zhpUVprv-gP6Tf)%e-z1o$9dfpA+Jn65&uH;vI{BB^Q!rEj=TXGZ?}w6f8DUU7WDbd zY5|Adv3ygT5yxXi4f{CvetGLU*^NaFYDG?bq;A?eB|lsRqkIj~jP4yZ=VID0A(2lK zJt5C^)Ay9+nE0Dfz=L~vwOamo53hoZ{gHCX>~w52>Il08VyCVkAr#2^*G>UoQ6ARa zDc9lLCGZuo)nWG-@u!AwGxcHY3nZH@ZY_y`K!A#<#eaZ&jxv8`^D{PCCvOc(WAgf5 z(K8`&C{316FEkEhy#V@C#&@033^{UWS7!?(yB%h5+H{doZiyXvhZ08e|70KoQJeGv z{?QYc{m*W73FZ~Azjb=(+qBYqp15L6iksX6{eJ6M45#IXI>d{VAYtt{t@)XIL7>d7 zqj!-1V%}lQ9ON01e0ukppT*`E@A*T)PJGN$#n-nn_WXuiLw*Be8DBiXkg=yT?iBFD zlY^gpTnB%PQE9Bs8NK)Qe^~!45MyIuhO8y*utE*q1;4kI+}4aV{kctUffpjxh+GUb z^5M9!{UTj53UDglVQj+QM(Ld-TY+ox~>4CGXRi~K+ad2pZQAi&_n)Q{3T+y4Y8FsW>z)s>0V zK$yCLQk1BoU?8Tz^5h55B<`193o`)oX$D!x4Ur-hpwv76-ieaq;bxuCy`6_W7fXx(E0^x z{d4g#qc>_W5Lj{2i;?K9&5 zUT;ew%Jswj-ML9dS9vp=SO<%R1InbQDQ&{`J3WttR&Y0R?$@K|LNOnfnq3?C2zJg6 z{FAdK^v0R(TGG-YXES+Ey{8_=(>{}T3Be-p*RS$COjh+vuMZf81jD?ZlLndCl6P+G zW#x{bo%a0vu0HY@4{K@|L3|MEx`7{&)uY-zK6*)KiVCCYE&Y%c)UrFw2=cjjuo!W04%d*bLk0~g&C@C{Lra=e3z7@5Hahx@r6w&u!OGS1d zm-@jrM>xlp+e2j`6QbK-Nf7yH`O-2XlXK@D>yK={;$H3nl-@}oJ|RdnwT+_w5IgHD z!k}dCyDxgB%NQ-%Tq|lS!lU^v)>2Vn0KJ=}!$}jQl*qwn(Wb_*t~^Fql)R?u7xLoo zCRZcNeR!=)El2H}jr)hBranPy#rls~W2%oSY)EwreE|Ofa>}HY-*BWs1jO*uxb%4U zqouxv&2saH$i4D~?_L($4{%ABJ(E`Ns+1PJ=ARVt=pN%i>9G|a26^+qLoEb;C=Em` z8O?B(giXXv#_RGAQWbw}+*1_}>Mc5PNJ8x@>i$Wq(!8Gzs(F)Q=k5YK7S(~SyoflO zuxH3yYR^6@OfAHen$T~n8=4g3SkggE=6f%39Zx%v3fDc9HLo>PJb1_JR)8TRs-2;) zz;}}T#-A&wN5Y33!ZJ5YWF3_ggbhFdkeXJ97##y12E)bAcQx7lvxFEUB%GH2wUR@Y z!#^OuUGWoCXWd8XB+S(1{~D29t)xIm@(>X)`7_Z45wrcR#&4y_{^5U>V9^U9KWC%< zo3~Uds)Acnq1^2}**O=PX&5N**VOhYDq05!?MChqn6{$GTJjj;tAiBEa5=J%a#s^B z_+R5>Na(d8$DVb>p#Nrc7qcJx?#cKGVmI1ErOBI#t?BHiid;U3wdQ48Fnf3%?FXoK z`1#x^U=f?+M^r8GY%*lb{^5B5!fo9k!!M}nN?$&*A1LCS=3{&zy+C|WPJ4~1(d9S9 zd5Q#ekQw|#-tj;XJiZnB&iwvR)Er`#zB$ngSw2HPZo}?TeAkX#*c+RCyiln(`_>Mp z2(l8j8A)}ZZ9~rwU0Vu^m<^23E_zdeu5eF1DFted1Ln56G&RQ*pq zzDWi>a@xrA94dujtB2t_*5NKhHOzg6I5$=q^G5IEP}>$gepM(=U{0a><*X2A_TR9* zOF|S(ZQdRbjj^@76uLq5a&`j(SCRTZfv+| z0R=O@_S;CVB**rLfBwUKM=pk#MZM0Ve&CoN6(YX@liT6fk2umf-`Q%Pr?h=Ghvvx} z%~v0+WTQlcoV?KywE^J4@>kJ&S^6hci+I5rc>FED9b(S^`31LGNQ}Qccp@%J?+^oF zNhQI=Gu>yHZGBrWGp-A*?Gpw!4!`N55NRET#CsE;d|gKd_H;CVO#Lc${GzCyM{L(; z=#u^BB2rDwE0l&e*iEjN)cKIz#-5j3LW2uLrCqVc@cjvd02Rlx=}p47$(P8Cqal2y z4FR;@hv_$=u5h@Pf`=^m;{Ddz-qvipNqfSpasYhhO^T`7v^Ye7HKl(bd4H^a(^mv# zFCJ~{cfAya|8#KQ8R_;imaPqJF3!x^hz-fKS`R9vApXJn*3^R??bVc%%*k|Npoif) zQMcJZ&-g*BDi06STm~`3w!D+uybLde>Q>~X=?jZNRfll>h;Fx$&0*LXm7MPlGq}g7 zD8PgLa4e#A$Ta{Jf(bf1pgFh(oLYV)6JAT+;OJ`I>Gr4Zg~n_6hu%*Qy}1({t5kXh zb!aK}#7a?2I)|F=h7;j&>f(ICG+bG<^f>iMs5B7x{p~dZU!S+JcX5Ui!+<}qUP4@( zkk4+5hQ^@G=r3}vp0FH|d_QGY|G)F5rc=Gh${z6DlJU^MG!q%4#Sc6Cz&04ZQz%$k z&-O7POu*^XWucGeQT&t;h7fEH=^BYKL?DQ}T_7@lf3;!((hwgXxeTUo=zswBu4bu0 znGf?X#=f620j)woV+ig3sc z0z3-|ujU$^Ct_&NLbaGh7kIIZ&-pPR30c@~(QLv|7NS8);3TRszjlj$kKgGVqkvJq9`wdNbQI)WBFm;5Sxdvf~yBR*nnv~s#ZZnRW8B$yFy?3iTb$aN2KmL4y z#jF@BTEBLellPnvuO$w%2i`zMB$8gQxW44k;ZfCsDju#g>GhxC529&)sFY5cw?Qzg zie6NYBcO^Jb^V-mFLDYW?rPp3DJ1rZ=TN1Qg?pHIrbKCA$hef6qSn~^+f$j{pB9I7 zDrVQ#Sg7X!8Th*O#b{w-Njb{fpMeZjFkA;4wBt zWapf+KKVx|YW@g01A9#v_70HClxx9WUXu_NwmuBL~X?> zrAIC`?gRiBRz0_yzRfVZ_ib=Dls1Lf)!&yAtK5-|+;O2+QjQb*kXJJ4xvyVgrBC;a zO80?fY!C`{?7AKM;saGp-Yi^-#T-@D%LbMCXzQ|L3{ltGtbE>n%#{1(+XI#UXudwi zf9$-qwGw)??5dQ&qMtWJZbeRWd2WljC0jGTe~?#ESReprg7X`}3#7fQ47QLRHyO5e zv(RMY=!RE+kWSh_e$ndX$hO@sh}-~YIuFl0u_zmq#UPOH;_~hsYaezgD7Xj=YBUZ> zjo7G2!>OR=KS^eD@)U3=jgOJ_+s!QZ#4{`zCAqB0rkAOyYRjlo`|+h`L>ZUT1;9CI z!!Q5(EugWXVOdK$tXQdUzWrGGK3Wu?wIGp+gp8<{PD>rx+Gu#=G4@di>R=*bT_&KE7cmS-kGUg=DmKS3tqXEh$kbv&;j3 zaZ4@Bi?Hu3h?kAUY|p#9p*yf6oOD&CMD?~{%MtQ5-2J!a^Ot`c0mec;?K(H=;WzTl zAHPC#*hvaYn(Z@Mc{WmTJhWDjm80vtZm2O17fZG>1r}poYeem92sG!flS_Y1)wPa*1 zCTqPmMuEe&ph)BAwu4RZHLji)Gyw7~Bf&P~M>yJvk^2V(7<(>(Uz}9_j&Gc?3;ck= zwKf5VcdP?!Jez7s4~+pTBZ^Rn%fsHzw*9g0Xv-!}z4w09vSYXwW*ttY4SW}MBzB@k zB$=(-M7bB%v>sr$qX;6*iy?P~zbi+fI7{xWm?S}l5_hs8Mv)OK4CTBQ$c8Sjcm=@P zqHdYgAczu#S7o#i;j~w#mx>-ZQ#I*`%&sxx#7+8u1|Ku!2`tGUskvh?IrFc1I+YiS zWH2Uceo^OejH~2CD=B@wAN#?15NZQ8t%qHP8YbHuwC?{H^dRdEUHK>|I&&`wy}u4+ z8BSaSM^h5taNUiJ^xIS{r|=qrChCPbF~qgw(AsZoz`AQU%Ip=Bldt=>93kID@L#vq z5{II?=ob_XiKiJ;YF)OoYa;~sVqLeU@j}VwI5IgP04t-uBs0)<0>$sqez(7taD>WEjQm1DwllQzqbi(DaufC;PpFwD}N8NIZs&`u+9bI z!~5jO5!(k7tZ8XB4CfFpm#RZN>FRHBtE;4^FChKT8tK#M%V;o9;)|j|>m%ZD5khTW zY{P-Q2=y$Axw8v7l!JJMsl_d=y#G{vc+aCcU z)?{qZqILNt`1p2{hopL1gA*ji$}mHdpy4ZYeEti z9(^WOF6t*U3&s^|Ya++Ti0{dkVBPP7U+MlbMn(`%*& zt|P$e@N-q{uT#h|nWh``_fij+PBM{E55A)#Zjug#M-IE?U2P(KJCMN7EWLB-(7oSJ z{I4QN+3LeF^EKR-cSKv49z~hfJN6jtvyFX@_$-S>6q(_=A{Kbq|x&gAw5m88$J66wHNNWFJEIW5n>Y&o#7Bhs|z zEIf?2tHvf>*vn(Bv1iYVz@1)|az}>?oj~>EUN|~K1P+iVnAPn6hv?gzmxPJDgnV`| z_V_Xe+jVj1wtu%|;(G)Mw?P`t>|CFm4l2djE>a$s;-95J$T680k$O%`23K@5oB6P- zkmb69E0~}4*j-L%$4PAm>&ql_yFsz7{CVmNB)1%D-$cx|+Ae=jhV;Ri@F4I8$sRKj~Trj?gG@%b$?n!FQGHF$>*dKu0%Jeg2@x+6My8T ztT8I8v|Sk01B{#gf#39DS2Ac4_}iN+S3apS{&CKCd@SI5vT)}p*v8{`LgUrZBbxikl^+Ty zWNS=PufhOT&<}dTZ6t$}`k2VO_4*ba&^u>MS~2J!7vrOiV5#1voHW362m8_47~o~D zlN;)Us$6VaK_uppv!5nwq7cr%YlCb*ce=v*Qe_0^5)&nVQ|qp!5xgbiyPd&eXUSuB#wF0N8`h9hx#3K;>DOx)3hj(B^%7F#cvOO${ zx|EL&2mk=`6NZTRl5;i!U72|fZjwFOH2`^vTvoWlSt$VNZrWw)>m{FOWUPbuKX4S%LB>`dqhDI zoAwLNcl)yWqZBY3bp^nCVA}_r#2GFh;U>PGN=P-T^s#{kFj{O)z0D&W0@givxexX2 zu-zX-UI3KN%&nR4DE$ye@)oD%s!!x<^R%m@jvPigTo(!HeHOjk@N(z=;wpi|94DxU zf`Q|a1toI^w2dEnsQ#(Z7P!xV%X`34i@}*PQ1rs}7E~kywuye--;$7HjnbAXB})Rh ztI##2Oaxi`OhO`T2VWYyp!Me9VY2L@g*oPSTxGX7! zHYCxkj0VMfAgBSJN`<_3KO=M4JJg$kk8ag-{Xm>@224%+2JdGkeGUV83)1EDB(^|8I}sz%)S z+VW06=&%ZEmUt2C0&dXKH#P+JqQ?Z!b*g@pS?^HzVKIh1&F8Im~`uz0XPF zOq)xwK(R`0iU`=W^5<`#%w;24h|nTZIsUq0B#b(7d0^|oKzAlGxRF;G9Fr$3JY5Fu z?S|4cX)VwX4e0<0HOlQhyU@{9eDH4_0az$W>#*2A?o6%@Q8uXVJ%9(gNyAHv z;o(vEY*|u-gNIJhF}Lvwuk4!Dn0~6r5#+lX>V6+zgBVFF?x!f@{mB-pJVZ2Z8^9@o z=s6iz1^3b=#-<~&euZIx7vW(NX_g0|M&S_Y=RBbSfU@4q)&7DG(Eog`9W94bslW^63+qh1mX zs5f%$nR{WQpd@LI>rp1v0HxQg&EVvBLab=ya{$yJChZLZqNvDR@VCe-dMa?=-*4aK zr$}cZ`*dvS?Jp#nv0M&fc?~Wu_NC;(=&Gv*!Tx=q;@pS`fTPR4>Hoo2NNkmQRCfs3 z#*PYH>i}X{@vEFVErxG8yHS?%19MoswATK)7%a#TSZP6<(8T5q?6x@kFSIK0_uw5z zAeq~cxZ$SzcCx#?y8!ZX`b>+;!c{3qU*-6)=!pjthw12$F=e+06*$^f@;~I~0&^1i z+==tZ&d~j=$QiuvAc*6Q zf|EdhEWa}J1OhBCrTC!WpcW`I9j=6!$@HfvQIy^_fvV82f8@3o2eRH15(-JvX(Lcg}TO~UmJzz&n~cS&29837_CSA@H4wSqSNAyQEIT=WTop zb(!_O{fnu$88%F4R0M8VQ`m*e?6rrLgnL`e=UUM%Vvp_hG=U54yFvGOg&@=N#YevP zp;spC^l5(34vG1}X4KFpAGFi;I~|?}4u3EN0SMY1l%XgXVix=VXgc#~DBu78U-!(I z8T-C(V~etj$TmZYNK^`?j8xW8lFBl-rKpIK5M{hmq^xCEGm@wjiYQ`)Y?ZMyX6F9R z=bYc~f9E*IjQf6F*X#LwJZPQZvwY0NH?fG)4qEZoM4n>i25`8uVXp ze??MAMs1xZM(6}}ouI3hU7B}`q;5Ur;>M{4KGhvph*$+e71R-7cDC{M1;ZD|>x!E)G^c|MyHdxsVG=D#X&9_~<*B!&6S!aR? zwJ0wc&=hDYPB%sw;TPeQq}X&}pfn-}S(yF+!&f2F+6~m;3eo*33?hQ~lG>0hX5`ln ze>PvJ}Vzw*63K=%kq1v!JLTn~5?k1`^_>HwDvriByn^l~l4Wec;Xn{!@Ar2OoK`ZkHHY~yMsLu^L)pCXz{(W#hDR6_>3~`C#FB99KcZ*=+D;&q~Y`6 zM=4*XM!?o5^h-&WaMKx8(imdQ={~N~lj~Xv&i;KcY%%M&7o3-wBlfRt_RTUyFZKA!K-gP z{x^F5Z-+~CcX>>IBW-fRK{#o%knQ}JOwRoWPQ}ffSBfGLsUtj~T;n??}~9ah)ntbDS%b-Z7G)3TnMS8bqk8^Y$H{LEs(l zBFu61)2EStKc6_@@R#bJ*YQetUNIuq4jfbMMd7eQR4hY0Fg}c}EkXCuaA3S>HuPBR zAX6;ZL#c2dSKCZCDBZ!m7b;jjW{iHa{Q~e{yLi)1{tdh6jV-l(ieR9?x7jw2T}kir z&N}hM{%8!USn;Dj?7CXWbsVYP@wi(5fJuO}3O4vcL7>>R6x{Mbhx0~T2M=So2(9UB z_s^Ja>cg_l-GE#Q)ajuIicBJ7LMUFFPK6#M=*&58O2r+pv0Y z&)0$zZE0{zw!*JC{?#Q*;#gsM(`N%=xh?dpG$y>+#-PNxz972th5*x%-o1(m&z^-? z><@69x}X+u?`2h-rzm6O=B3=iAIB|nS(L2A8iz>2)GbL(=^;P%WtuBafYaYqab?+~k~lFOoO8<8TeDvf&$@bn-yz|EferWonEN92Jur-N zXph=&-xBVF8L%h(C!~~6{iiL!43XrIfF=tGyzXEs8O%)_%8-zT>|D+{y$~W}sFLmi zJa}hX$yezQ4+dl)F)V|)4YU}eNKRmVHD?>JJ+m?T+Hyw4{A2Kz{muPS`t%r=0i>Yj z<`VG_67$n^!{BiA)qIqvyQG+_-RE0nUWH_E;sD%mSSzjKaI!kY)AIDNY4@0fU=(BfxD7nY=T0=LX(AmEP`FC(R6vj3dQ(hYE13Sd zLUsTI?7nAfOAW!`p6JI&Jjr|u+H`<&6D)fnqrXm26`yO&1gY{vRAG>YaBA&<5T{TG zK8&p0tnr|{N0B%A)y8`xs&hXz>pv=}` zDksRC2P~L#r#d*D6C}yeyB}Ig2XCSKG=aO1$T;TpesIpT>Kf{*6-WYvN5c0fYE6M1 zj%U)?F+$ZMeXK-XsV?hQzw)!^jxJO13C3>y0fS7^BN?I_eD!R;60fhvs;@p9eGBmt zUV~mk5-$ESSjL`H+?W@f4ws!8B^tXS^2;);=|1f6d^#V=eu(lPvUg#A`+#U{LT?YR z*-M?HR0n7#g~Fug~xI+8kT<>5u!1df2Ez-vP^@s)Q}>I(qPDUL+~rd~%!P zV?!Z&5-uG*i01wlffeA^oSgHht8oXfO%j=WtEO-RJKzaKGR7h2lS)Z7(61HZX-v=D zcxF(>V1NXn#ePPwghl-1D7W$PE0L1QUloSlW66;eB^PJ=+Q9qNXmO7gwFAP&;$tp% zALG&38Lr!(k4%n_i8a^;>3ysBY&sJlcL{Xn_8KdJL~yVbT-jm=7v56iLfk(2js+^3yRTNUe(lm!dO|nHJnmGJ_BM#i<;~uE zPJmGe8eF*Sy!MhuvYu`oD`>3cZHHa)kwFpmiGTJd$S}3*jLK%ERmQ`*bZBJND zCVKSE$s}{;S0j4yy=DK}gd&ZqR+jlg`RRUVr5iN|I*3?s{vEiBNiEox{BO+3^0dM}2l$7X} za*2T>l!s2T5no;e?BTW5g4W(z&@UbTGGc!ybrw&0NsVZwedapV4h`?z|0vcT$83&m zO{Egi&Nxh|t>Aeudx6e7yqwpHJT-QWu(lK4qAPY{+ehPaWab2JRXEdzMz^)lJR;li>v9VV#XW%VJ}BJ^=H6F;zq%@cE`GQM zDbb5s1u|&38wRBtqZg_#74!=H446xfu6b+VEmuvY(EInx^{iQ%)ZI{whoHKBv5)DhZF6A*Y*LLL4G%5JL|aUQRLx26|m zPLDCVnR*X+w(YJc-5(o~g6{4(!U*v$ng=bI#GFwP=+ws%l7vAG@)!cf^kSV8WmqyZ z!19FhZGPsa6XuHS^RIFGm0ve~zPgv!elsiHWaXbeRe5HblY2Sdhf{d&1M%r7u?<9UWkbuwrP!bUujUo$tm zwM&(Ww5u3(pHwFud4%utNq%AW91-WPB_s%;Cf@QXP1>UgLS(mdcKv_7x3~hCrDN|g zom;UwZe8lIUZQ;*Bd?m)tI`>bzCMQ0JVF3#y;}4;LN+PM%fdxak6!PfKqm_t&;w`; zDHLuR!RPLUi`b?0UL-WBUsVPq1M3H2e^9(da<~AuYwSVM%l{_a!U7hWD()pIE` zNAAA!%tmDNzNJ_r6k#>EYI*Z&obCy3#|rJNIDApEEBpcYYJsL>x$^zGL=p z)6rseeD3Zu;%T9Y4}Ri%V7*tN_B~Gf60GxmZ^nIMrAQ`*roo=#W$uK%QV>a;F0q?; zJMIy_blF@8giWJn*C}p|z(#+=_wr(RuW)%7W7iGQWFzt(Ajb7=g~Hi|KPTR4gN>wL z<|~LNARDa@ioiopLOO)+A|icA*sM+5uQA1%i|-Nh(}2Jer@coDus7Ie&yI6LT7cc$ z09V0^`||BYoj!E&(*f<=6B;Y$lVw7<5rPrgA#h)obh?(&o6k$n%Ftc~Xvj82iWCGW zc{Xqn#C{Qzh^6wVM=e&V=!kMW92@oTjKi2jp8HeK*IJV zq8E-{kzt%y*mA3E$jf~-%k`52y#w|3y|$tTdUz<5ypynPkFyjoL@O;YMW+rg>^J%o z{%1G555c*~XhF+6H*#Jd3H-a`#6~eKO$XXtkqUbzjkD)J#R=X^t{?2{_#CMCj?cI`+Ue^B;)l{Fy8D zrn#K2blhN_Sb3+Q1sRDG-U-fz@Sc}w{UtAT?P;36@bh?!EpQK9=2moypoZejs5vr> zcmrj6f9OI3Pu6M)wj!hY!pzN2pOSg9T~tMCmyR=2G^aR>Q|@?WP#&!e1Rfm6{1J@*$DtUZA|7atusubx zbkNCINGXh*Yf( zv?H`qZXN&h+G$y%*ET2QwvmP}w=7oP#Bnoc7_%cC2D9Qu1UU=v7L}Vij7;~!2h$Sv z<`wM$m+4TKx(F%hV&(u6vnXiIbCw*?qx)UxIZiS-L$vrPDbc<_L?(IlTZxXEh^{lo zBwC%jo*WQLJ(s9=vpucOtoXwh1gYFHwem1VvS7m!MyX%eWghYQ^5>;DFeSZK)s0`0 zY)5^B$~7Z(@qpLdj@X2`L4g3;j6kp`I@JyzF)F~vYst$fPV+wTWwX7fo+OU3h%m68 z6MKXbWBO_&JnCzcVpiy@g9g;#xsA$;n|~>)Ez88cpo)bQ5|IT!pPLpfTv%F){Mxf# znA*l`4&CYe`@5AJT|GTTdBBnMZ?}qi8pEHD_r{Z}eyHj}{}FTt_n=rqkeaqdK1q~S zUqL)@Cr^ZMJ`Hl^`$OXnbU#>gjXd`9Y_qMW7=1g&K?C;f!>~`)2u1)}AlDHOB2B1J zU{bAZ6)<@Tb$!wFU(>(KAfSxuF!(LpK*mzun5~++I31M^3SmqS0oC&|q>^csbKsGX zMQ8Bn9?a}J;vmSCBH05xdtjONsHxbQs~VfRQ@j6u)M2MGY$0Xv)xX`W4i=IJBiqCI z8Q*x2@k>)m{GNWTCA}5rmuMw_a;j*(KuwwIuq&xkrd(ck#} z(rOadbM+>Q68}zqW~uW8#l1aXw2OOe$>s+}&Jtuo5$yT>yh5lwB8(9;wHZAobfE@E z@Fk~Veaekw;HlUQZim^!iryN-%=Af+riGu1Ssoi{Orzhw56U^EeC*DCfl^I~e#YBA zJzDo_K^YMRm=d2G-+mP|-%$jcu$zT>^`IUFqqj)gaaf*xRh&LhJ``iTTucBbR{Z`2 z+!yIZ`f9H3yV8&@RurkX;l6(P0pS>sa5#Uv$I>lV^1zsgHa6mU9>!2EM`RVawx zi=|)I<7TrxD`o$Vmp|`#bv3@&4v2Rk2e`gYh^Glv`9=CRf_yMAh|?hX@;gU z=Ztpvr>awz4gb zYt5(JwF>Xju8dpC8b0}_<|+rTc^-^XEtjU^I}5y{>2AnBGD z_Z&NC13{M{_aCdO1`FA(sti0Yv!%GRJsvaq(TflSX7t|-ngxiW1&jL)c7*Cc9-xjG2LnA9nn zx3oG2^L*rCTROfk{=Ug^H$?_y0hvRT6(v9NTW4Ea%L*ZzC!1#aW*|Ry;qZt-Sol^X zAh>?U&XE4%vWajAg|Skp2`a~qKMJ|=)2lflLY7jj8};) z%1_F1-vo9gyLVxA^z_KJ%|!#AdMPJK-ip-wNr8%ZdH+4I68%*ad6CtL-1~qWnWDTl z9g%idw2MUrLh&sZcL_dnpO=l0MD2W3iLq8rBm_u%K`ze^uB~D{;OuV1_j}{RuZc^y zcM+uJcm~CBp8;e7Ix+cf7N3nvWV$FQ@K;A;YdEYxK8Y2)fgPqwRWjAw{x- zTj=Dz)f|K&qM1{njhv+nt99$;m>_x-~AS7)S2!By;ez9zgTa3Pt%Lwlwj^MBH((?_vPQ;LGiEkU|-thi}V^)Lvx!j-H}g@ZdJ`ou1_n% zLtVPRYp!N(5M5UO<}3=(uUtIT%z(hwdG4W%IkcgkGs;I$n5-sDKUxxR_-7!SWzn^E zD1yEjTPAH$g(c~*A}^ElAZrC_a5$cYkLsKAl7d9g7B}$j69~_JsL`>*nFC4juz?qw z{Xg^5|E#(^gPrk|>|+GaCRAzHgfH){)`!eO8t>H?{Xyw4OfeQaMRjUeQ<=lmI zuH~{&Rn=xjg&`8iR24y}MuPCi=rhIbl1o`qoX$b;0#ClP!Ff|WhJ`g@3$AA;D)#n|@Kpo@#4uuBpkO!0 zSN79VWUuLUJ|Hm;l4aEk5Q;B5e$%4p=?`8Kc;;55u#@!^a>^1rmy!-t%LV8^e|?eR z5{?rVu2F0=BvMeCp-1o;DyyJ@kMWkIif-tzr6M^xEEy_u5>z6?Ve>1Rf(%~?ejPkG z1j^QAKcbVRN&bp}Z!DFookh7@CJLhFGL8r`Hew!^VP9SW7JJ122}RHV`es0pk11Lt zNOj)S9}aggI@3&neURU!cd|EcvCBT4w7G1TSQiLwZ82bOxCNxHg;2$mvoIG9W9piYaL#cH&3 z9VX{dPbnpzt?Ss&X6NTH|7f+IXV2D-a^+>>GlXHDM*;oU6FOv^%ZZbFq!_G(+)~V( zv|E-PF_r}v0W&=2+``5xv+C$hb2z(~`>9rg;9KZpcE1kIq{G9lNTlWG?-;qqiIamuos3BVFgg+b;HP$oXOhz$gjDk3je+Q1|hppR1uOCD8RZ~hLdOiqY?YK z_ep+NwFNM6z3KA069jJ|d4rxFdK(q26u}*c`&~k$koS1On?p@cH zNw8IM7N0z1%qy~&#>^apIj*B%crPcXwsafF1`+xKFePm7rP|Mw{5t(XIrD|BE4L3o z_8ea_H~dcNL8FC6${sT@S6ACn9`A(AJ6}FW&a=!#Y!9_YsyP*ms>*J>_I>XlVlfs= zRut~!VKuxyulnvDiU#>5tk^X8+C@?en&CkB0b(IO>TCz)6jUQi=RWZ=&APp7i#X}_ z($6#EpbDtECNebBFpTKvVqs!0KOIe8&`hZvf?)p4O4Lin>xGRMP=ei-#`;}7PbZy9 z646dF@DFI`60z^dN2Y3wELAXdX)*Ozyfk#rXC>eU%SxPgjOcV;S7|$u3h6{We2g^x z>2`fBx_?vjLeyl%DO312?|wf4NAUXAGC$DVKbLVcOs{52F{|KT)P*;zc?%~y9gkhi zy~S%v5thP@*h>xbWQyL3-S(;#bIee+3}f=99%`*nc~SUn*rjRD*hTNF>;$OPc$E#I z2IoioxU|>*i7?Ktz}4_WNyavKEnR~=)0yuqb618+6G>Fe;jCBEAhxO>NA$Jf1Ik3$ zp}UbB;_t}cn>qW|J2Ys)GeTWpvOnYkFnyf!4p06(U}Rjy72)z8KDLo!KfzeH)yu!v zwgR_OAcZHR7Mqk#Si34lP)p;FP^zMPx(cg|r30w7*p@8Sg{dtvma>B~{qCp*wO@|utjcK9_x z)w#YIq=YWAneQH%hj9V17w3%!eSd zI%pXg1t9mZ>I;EaFqMy_#&E3;QnA6pE{uJLp+FtLlgAj+a89rGz?4MRC~sYHM24{Y zD#S-Bj}>_`(R`K^I>W_oHM$1*E7KDi3}nNqpnk zwW=?p-a^#k71}N2jiSSO)pZ1tBsY4id>2ji7(e}<_GC80j8^>1?eU4@lSRup9!Rw@ zbpD9M zdo1ZJL7XWA0rWEEqeNDE{H3mIMG|Dvq9C8}aRN)VOu6?vmGa+Rk+i?kc)h1+uTPWH z&Peb-4Zzbg9#aTHb@G);y&=6m_Q=2OKwx6Te@qIG$^M)J(6JGUd*2;&Jifz1f`6B1 z3$CY~7|uS8bAJ(7rT3UGlkCgCTs}^lMg`NjF%yu#tdkYIWozb4+ls0|{hAYI-=`-= zu4ff8zfXSLM?7FixC1#PW!?RYn&4iToBD5XZh5rOqghrRYH9iT6sr0N+`ljH)B$Af z{3SN!iowI7C&s6HvX?s5|B(G9y+@)0G*HA0DY3us zV@NLX%AdSXU@so%!b2lxJ0VRG9>n6gbf)Q48Pp>~=!DLn1J#GXb1{jxvotLwa&ooW zo^kCEq#_4Xnux#~^@9BH?@Ln?09S%M+Vqhi94ij~o3n5|?TC`NVnx#>WrO-NNTGmO zX4%%N{2{r2#ID=?V1X3d@8%&ASoiLYxRfFx_I;@q=@0xCiIGFZsn?*D&GGo=t!pJ8 zQ^t)wpoa@zDE+1bUq5=!fVh>9hfGxc-h{U;2K;RUZ2L;~=Q^P80@qgJNm68+(T7#y zjY0|BdmZ2Dtb9Ovakk0^rAbeeh$yuAVBQdK`#{D{fqAk>9VSS^GNzDXRc0LN=m5nF z*`=lZhw}UmG>dUUk#ykd>u)wFA$Y#6ijY><=D-KHlqaj>;%(UDS-h)5&?OwWm3w&_ zqZ6O7gU=#RU@^8`2{^ELV9EjD@&ol2f_iefJA~c|tM}ZDny?i-S8u#oQCUI1#JB-nd z>=BZiMRu`A8zIK=2Xt&txZHguV0gugF0c!03VGoXeh5M zL6*xo?(uL}=tcr7)1%KxHr|Ztwa9MPTW``KNX~~GfDC6V&q_zQ42507j*3YHeoYwC745l7DkdJ)Yx8uOs%K&OC7t3Tc8>> z0_{`{KWVA(3cGTOQno*eQ`u~>49KZ1-ZP)m*#R?@CSTcocK%oVHi%^%i0nn?*#sni zQq>}^PBv52_6)-uTA?vT%TN$T$3nAI-mj;awHcg%1rf2rC`PlISYTGKx%}%6$J;15 zMZ!PSARqZT@ks36Pe)TeT45mVtwPA@#=Vy~;W2P!gJ7l%d+p1PZ4T#!5nUomkThd;$|O4C))zgsWs$5 zLeW+Q0hH2CC3?=rZW}4|zEi@a62}t;V)s!V+b?2D!v~huwxU#R-O<1W_~X{E65xDR zFzb#VnxqS}zS{}YSBeJ&)j<1nEL)W;<{nlZ)ez-Cdb+VJBgn2qfn9*Gm3}7^s$ClL zRvPVQ&#*SZE@<#otCubF4@K-GyoAhIZu;(py3G7+ivH3}RtYko9o{;Q7y$ZNz+g{# zG{g;}LY)a{^%cPxe#UAn@$f4CplnWWX z$Bw)#R@|0Qy#aQO!G=*Sys4FjyE|!>hf?ttsA8T>ko}B{(EAMs1($HUy4K%AW0W7< z?VB0ble&HWPFl#SgFFZ29jU5X*MqqdDvL#YPA|PbeeX1hMjheDyx}9Abb?QgDn4Hv zsz%tMCWIO2Y-z;x(<-i@K!72l4ka)Z4_B}78-G#rVZug8`*_Yfk?>r3pEfQa|R%GfMX z9CZc{y-jx@YcZc;yyqIhos;MZO&$rmDiuF-Xj>>S=?DMjQ}|wjfw>aOg>~9FarhmI z;g13d=URiTnkrI`Jx4xEj&6r~zT5nBdqOL5N5-ARVXw4R+%2rwb5wya31ED7UiQUf zFJSfOC~DXX_tlcv13%J)6poW3-_94wkrx%err}hajIttt54ux^p|A={27a?sGFI|Q z%CI-Ycp~TuZtRBiN%wxvzZbfMmx2#SM+S#?Yq8odLIfU1J25L}^UUJ`&q{;&?Hl_E zrwqJ-g)|<%YHzRjuxVQLP@j!5)yDnb=;tror0=7h`GNcqjO`LDjydg=hYG)m-N;V# zMwg(26@0$-oe1Hu2)(BVG5+ni6Fhy}qT}*!x#R*S@j}zfjI? zWV8``kQc5t9$~B6@@w7qI4^#!GtB;=4BmQ+zFrTqz%3(B&$2y73US=#p{$sgqnl!2 z1ru^nnmLpxTg#Vgcq=DI^x+?*i0^k`v2qH>{-#w=wc3MNi-$F?KhaXtFH}NH z(TFYc3-glTM|B5+v4>2_LSkYWh@k!^PwRXeVrCn|1&h$k0A3h4pUkMj80|JB=dmS= zz=XPQHcXg?^r{MP439>&vNSEMI4KFOb~y4ODEiRFRiFJOx^~>-xCm{YtVDT`Koo_h zm-bheI28+{Q?0P*)R@<+7Pt10w_M-8-)4P4)@t3|&34IbD->>l5t~EOR{%E`(03Ve zyZvg8qXx?F2Tk4#Z7Dp%D%ucGcyCc-){388WG*PMKKLb?hG1ij9;10UmW$5xizA6xMz5>@XHJXMXEqQ?!r3Z z=_26reFAnk8^viRkv%8N`7^Bs`Bo9G@I-F`Cy6rm_`hc;g7he5yHjg>wjcBsh{-wC z$-S>zFoDxI3w607Q`)nXro2a}SPcxc7dYLPAW0L&8wyVxssqJ*bdBuzZAcUcClhe- z3bQ8usN;sGX!q!h3omhTF4QZ6K2)ZvyXG0&E4P_4>2cqL*4h(%)F?z@_e}5}eX#s|oK$)y zAAX5qGezRiaGa$j<^XH&NjMNE<_?Gi!0aI*AGOkI9gzIXpkch<#V`Fz){K;Qqz}hG z>6z92!fnu?b*07sIW<+8!hKoWcCb6LMeo?FiLcCwjE&yTpg`JQXx; zi6F*P5F~DtBm+Jm!KVM63G$9t2_f+o`4GILz5)Rtmg5V#z^Dt=4j7UuRoo=<xo0#o?$-;~ zwXEJuYqC4vwQVHLw31K>okzr8RH-D@N@r7h>;4QMjyI?NJBqEUz-SR!qxGXP0n8@l zPQ>UjZqoO9Y>xsvxQi3ujAR|a_7z6CE)mDhY1qDxTab0e0~gq31z4+${yn$O+WAjc z;gsD|IEH~*lwouRlcM;FZ^!!WLSh9=?kW(?xdHxXkc@1U&NK9FVC^DE61NLFpJ~Bw zX;xDGkPk_em`KElsSa7u@^D$b~%z~4UBoBo1{c0=_hZsVz zDiL9`HT0Qagp0=US5NriFTp zP)1c024?y+U&}|)K=N*i46)BLW=oYFc+N011Wbz97Dn|a&`b?bXX`D9MgoInwIt}X`X9eeHsi<{nN~V9)%Ps zI2bajZH6~4)cObrQ1_5T`l7|*<5_~kPNIvmG^J`6`A9_c;+yx)q3cub@vq|r%D1)N zES~|HLOfAQ>|8l_GF1<0|C^BqZvCbu$WRNy2f2kU2`0uU_YUF+%W92=+Mv)kH;K`n zLwEPoC5vOD@S(!{y(9|!E_hwHYdO;T7XkF{qnKzg);&u5$j&IqiFoo+wo5R+5rby@ zY>VsYd=Ew6ikSYQLa4*~bXT@dHSqnck5;10FN6=`|C+O;tnaDY>&fF>CP@nt5QCD~o%>G9ADf;|5AN#Uj~Z?r_0>K`>1j#-k?3+z#8scv zP2Q6BiXxAuCaHQg4@SwI2-QV*#LN;Q zsO3*a1uajtXDCq;RI{5|h6Z35d-ug6|CBQ%eRf?o?{+pvZvUTk0;w_Ru3)j99W1CJ zC^q@Ivbk6H6EfDPYD1*O|EuN!-heYtWWv)&D7~k+`74ETR%u~BfN&9AG-6-;-QMrc z@qPO1LB6hVS4RDT4y$UG?-juM@e90F0S`U`(udhy9e$zvRtEV36%Rd9&#VI(NyQHY zbB(aD+o3=Rj|Op(d4;3TII7dx)z^g*irTcWj2VnB?D-qIBStp^@99Ul=(%Pbdw+Jc zUJ<7##Z73+?X0-itAxf7M*&8cE8Az&RotmU&29PoZh!JKR5x`qAEhV*j2IXu z6I27$duX@Vzc|NE@8|CvL5AGddCA!L`}0Qt;?^B69X8l-FV6FGe!mTCq-gfQ96e;g z9Z6slQF^b(2A9y;Ece#J$9!;Qgi1zX?V=)}d(J4>JWT92On7h$|_ZRdBz+(x~(vVXp~ zb?O;uI1t$)FVNfW+tURV6E2O-N5VytUQFyfcP8k`i`TlyJ$|En{Sf2yPm$?LCcdgvS6fTsI7rXW*&53)Ebrl#rjkr+k1VRVOW~I&`y2BuJFDK;>a6JLGYC!Dr5Xnp4i^zcvR_8ex9%9Np{vMDKJ&kaWN!M*=bpPLU)*OvX2PT+y3 zd31W20P+naUjdt$MbrcAk&7v(SEPjR;1<{RHWJf~9zYZ!wDc`rMT?;~eImK^+) zCMFB&(4;}fJDg?-@>dk|QB84ojQD{*@Qo&;lL7cg9H`8N`sO&_58(*e zAk6mS2JP!Y;VfmYSQowk&mkR-$~SOcheGO6ayts5Q%+oy)kUAg!Y|M*#u{`*7xt^h z0z%&~s?&E^jZNv)dnY2pwamU@>r@bZPQQY*q9F+h^_6CeB-n%DEX|MZ$b3#81YbID z!)ef`Kk>uK9R)4xeM4bxL*1z$Qw)g1{5ydE!QbvDGfF{bqoFXVO~}IRjq0KBG<@c- zd!Hq-yKt}!DHJfNV)Qht#Pike?GZcuw}BFLOTc0Xuf4&O3;m?E-=)j&^8y`(n{6w! zA+ud(Wba72^KC^%@QPz3pJ{>$<(l&W`v$+|LQ`u$Kl@XTgVI;HuFHw7@eRkXzNT#y z+b@jWm3pUN=?oE0@2`p*|~88sg0Xa%!AF0nTU0saf))`Ju7AJ|E`*$l!GF* zN>3l-G58m%DVx=3R52@qwG?65J=5oSPhgpMju_56EGCCWi8C%W)TK=PUY}M|;G+|9 z>Ds@|Pwt%+{Y` z4F-yXNnsbVgV2O+pqWs4J#g<~qm){ggCO`+b1om_Q1Z`W-&`%Su6RQOgX?QCL(HZe zTu|>8}rc?3yVzgx&@$`;zEj|zr5)-lkwC?u0^bTd`CK0?{oD5;$xqR+-qMKTy}m2)GNN0LevXhyf4J(2=R#S z-|+3W@Kd4_HDGi^)OeQ+edLz^j@3wA@{<`J#q}LumjWJc+I%AePC_4W6&xdEzP%ww zAM9-{-2Bx|n!mdWicFJ#{5MSrE6fh%t5O9UyoEE3w^Uvoy>j?LYa{oD>lNi6%5Ch| zJ+Ej|snqiWGIXcp71ce=!W@0%VF>u+H+P$TXsNLU6PLYalTBqg~cRr7I(ax+k z$!^p;u6;%ePsoZTNMWT=>VZZCUnyg0Mxsj3U!cA+ddKqaz^MB^5Z`HX0RbvQ-vh#0M6{(u@mt71Aj}yg?;$W0mE}TN@M-p>%;!Gc7kw<*97zn%h)dt+nKm)t7;Bgp0BV2m&aMF|33?G zzccSX%kXR&P}&vHDF`02RX^;$eioL*=*ip@$M^t=kYVC0c|MY4q62=qCecj0^Om># zB(j@o_s=Y$(F_I?OqlZBb4~%s4J@7S}G4Rj5vi%$vMjUpK!OdulL@nrM3fIBaB7J zj)f<#iy?Qp*uJrgD*0_%{)}&x79?9Ecc?y?KUtr?^Cegx-MR7{prF$CNfWA7K_g=~ zV8!Iudh@&SojxS)wIa%ojVV#yb9*UmCzn@XtS#8sy1PQ>u(`R*Tq0Uu@H@`^Z^|OE zKN^-oewi;&zLr7^Vp22@e?2%J^Q7s7t(7sM9_4ZrC1k}FyZyvqv~t`NEj}=+9L2VK zck2NEsI|BRJ?y2RK3$}Ycq}kwyX)@G+i$io3|9av6;I!2F?`BS5#7dR+5z)K9<1$zE~se^B>*wxG7Vo!{wrTxJhOKhIMYe)#v%uMd_0_Parrw__F<6 z4pnwf03lqbZfL!na;65P>qlgQ9Rrv|&}GA|TzznYkbV-3Y#hQ@F~Bv>6a+Gul33>K zQK)J4*NIFt9RquG`GlJ8v)iuDD>w<|UYVp>KvHt7l=;&e^C!YD-xxtUj%L*SJ^5`* zYji2F&#QNQTv7S?=8DuLMSqHTVWp7!Zlu~9Xm^c>#(iSqjPa`9XQ8+` zX(LdI4Di`26QNd#82iyCb*Bmf2*H~i}-J;08N$8Z5EA$#f z!)DY?E3GXmPP_Ru9ZQjay<@NM={zKmAyI`5HyJ-}fEwXM@&@nydP_(EZGBmUy5Gop z$M#1aD`WTae71L@5(wiSRJ%KQr|3tN+^vL2yFmC{`6wsGCu6rw-!H>~LGdb0dK~>P zFodo>NzB)$9Tg1Z79Pf=%6mV5Cqi+R%5b5fbCohw?3Nynx^cYN8(r&tJD}&bzWl2T z-7PjwDStm8GE-hE!JZ>HvetaVe;TGD<3vTiyq7=`JIFiT3QN*EUxIs#a5)%8=}5y- z!okXV0zJfId2&Hc#JcOG>U~IV;)E4EWw=F>#Qr~;&cqwa|NZ~3dlqBg_jT+lYqC@_ zL)IkP6=e$VB%ufqbE7ONYegtR_ADh!WJZW66r!xzx0vk9%-rAkob&tr19Q%tb1$#^ zx}ML+1Dp@#C=Z|JI0=ZXy{(hi44;kndLd_+#df~)^zn6s;~IG!(2mOPf|`bga10FK z>k^FYE#%MH_^q(%e4|4prumll$xa>Dj{^AOGt7!s^%CQBzz)({8VvMe>`%WxE)Yl9-8gKR6MUH?iRJ00rQ%9lqi z_>Kbj^#jE1+eMEr70?tp`H7>7%TSyWT0MaSUL&8;IJvU?K2J!P-e*DF8Yu^iHg%R$3j30c!<=P%g#5p6r2MJlNet@0{0Dt@^y8xc z;9t?vq1DityKq|D=#8L9%@rQ36p4Edp<6tcTfUSyW-VRc$q9G9EZ?%R+A+{Tnq+|# z9N-BTS4}KktD)<6Am+69!(U%K%43}ge6}eMYtZiJ{6Dp1RuhwBABB%3VITj8_eez4 z8OTY^o+64hDzH8Z_;n*^NGE#_KtCz`jPTbN7x)yzkdE#_R=8-vSK_@bdR-8F6{1T5 zMm1&t3K^knCc*(S3f5L-Mvqw7zk z&U;GU-T;v|nbX)4fcyB<8r?H%1QcJ21s*twDu}%qeIjh%$4n3yx{gl0%g(AEiO;L5 zjWvtzY3HU2AzzJJUKX?AtgPe%O^;@s{-gwR;pm;eI`r6q=0l0Rd!$6}+0SO;8w}1Y z+FP_LBRxfxU}3YQ#eOOYOKAgtZD+1QO`%Q?5OD=09wv)Z|B% z!nGWX_H!wf*g&U7#Um5T6&vKeLhi=y>_8prH-xfYk{^^J4P(Ji;-|?p)IG?CD7d+Q zf%5|^#(lqCX_s-!^A7VOms($lIA_Bv9!%8!DXY&)@UJVUvTpsH9UXs|&DV$ap{){* zvH1i$oMt#5-Og-u!fJq<;G8GMlLK~OiL{;P?`(B%%}H$5=kZY;uAwVMU=1Bo69?^I zXEWkny7a3m{4M0V!}r{xf(R=1)8#n1;2ro!1bD3gJ^8Vxq)mvw7v2{R{AE$F=-7xt z)=u4fAY#ir!)QV?9s8SPV%tR`OO9NFcw0bfAdrV@#2nAYy+Y-o((dSE0}L!Q8gmO_ z;Rb)%EB8>z{2b*S6lbVLmlnp&;x-fPy3x@Mif2oG>UVHVvVhU(cdIxe+~0p!fQ~pB z*!RB=4k(^mJR8qk$4J4b>F$VhGmXXJ)K#?Th$M|%a%SE_{(%O4GWhW7roky>8DikJ zN1M;y_JF`Yij%D7x1!M$J1G+=_UJ^sRxdWF*IZ3GB^KlEzgTHY#Rhre`!)h@&l@THKpAZV= zBxo&yX>I#fcy?EJQDnad#1B~wl!O}{?<-$AqaOswHNhNAicRYG7W{ z6N=r2R8VnGh3s8#A?`zeK*_%C92Ky4Pu5w;sG)e5j~Cq-q(*}t*JkqRs7>q-WRmyf zI4g11s^@ogIbu;y!uHJ`g^alP`$1cNt|sY_uLiZ2r-S}+$IM8qXk1H_NC+^V=D-1d zAZyZ-^tNi^kJt~0s7TEt9(8Lqx1(Mu@vNJ8`FiEPSyteq>98fc$dS zHPZCUEug0yp*B5b8I>@=j@1Bthb3}T@4VR~SoK%qI!gNNLh^hLGrvPErZDW_2;=%C zoG6j!6x__3yKSD%^jGJ!V@9Olq?D!ag$dGdRwyZ;b&_FskqgA}X1rtYH6ju?%nh1H zR>{8d{;7QU86%T^TU}~^2mV{2YfX3iGs>mwS3oa2+Of32JcT(ze{q%4ZgV~8a(rtV zBGybM9-6-%Ky*QFL<~3N@(C+lAGGlT<2;{8)E^ z2>8)N49QK%r-qK~7~RRIzkM#`q~C^|>@hZkp%3B#yE{jIcO-ES1!pqFgaJRII07?g z*0aYj(w4Rw;3U)`&H)5^K+V#VuG9P34g`Z~%V$q`)HQ$*^pm};1Y%1a^5e4)nJ;m^ z^Nf69SGRR)3H>q~8rb^Kkj)W%fF3@GxU;HvUr|I|yd8ALuE`sbVXn^ zw?Ao~@-SC#ILX~%9y#R-*{tB4yzT}LXc~O73GJDqJS^-r*>07e&izHMM_S|0tmDGh zB3wK^NdW6;piiTyl{Hoj+j<;s^KiKXtAIRP09PSmn)I#ElUDX6#Tr;4J4UTrY(O=d%7r@s=p*cNuXhOME6Aju!}4a-6O)hvWT&Q^YaJyN zI3?yGMEjfMEWlf{SDzSn0QeMPp8mP)0wy_95kF?(4()S;@64HRyA@Ns?mzg8eQoKn zrk>EQVn3AY@U}17`D-D)?jON&khJ~q1}d6u>IgMTdrc5!mUPqloVR`ax(DP8u+m(6_Xs{7-1Dfh3spel~gJ=?mcY!lj@=NVG zFggBPkkximRbUWqadnq5=$V@Y%^-3GVb(Z-H?`|Oen9)E$B5V}%MhRw2D^B}{~64a zcdgPh^Y#=#U%18^wLs~4lK1OUtYJz15VSQqzCdnZ9vqbOO5&iIOq_o1UAPpw9C8Wc z0`7#~S5bFU0g~uxB3ez*1c?={Lb_G)8VR2nxDE_a3(k0se~=Ar9nae4$cYe(k)Z40 z@K@RhHqAwx>F4B*wUvn2L5BaLv}bk2ebR8|o_XrJc0U9#9QZ-zo0d(>BII*0y*_#s z393DMRMLo2TGa~xPjs2^_Q5!I`86${IJdk_Y3i#I#lgbdP(_ItbE@I*zt{GdXAcvE z=B>YEidts=$Kk*j*is!J1i9C3qw|nH?h4inL9x9ihnM)czGv>)Uea9DB(gt1<9LjV zjd;NpF+#+;qEn9#pQhgQ`8{W)=DCVR1UbLCwz_6b)A6+oh{tKt%#$7mN&phcVQ9bz#oCEb0e|?;>;JgeV>%>|JBJi4a?C z-~!VrMR&QqH^dz760#oNAP7M0r}v2Cs53j0Q+LAJrZa^edeUi#A2KNQj*#yh^p#5+ z{!$sUHw&cEonIglv8VfadY+_@X*#dFh2RLU-CoQ!5Ok{}b>I(;?)II$%WN2q!mShU z!vENb#It&JPZaXUIyg-UJL@iErM}EJ0QjUr@X0 z&wEltrD4|R=lO@<|7-?593mSq#e^t-$kh=02k>nNi+$f?@_Xlq-|EEGr;E|xf zYu3Uz@&Tv!KXLtKU38t@pS*Xb<>4_+s~4XG#{*baIVM%V^ZP<|#_9B`6VpbkDIvVn zm`L2%W$GkKi5qRt;G>Z>msa|QvN+K&9$e?IQMUReM*LX%9GU-EeyS_R`lv2^z6Wqw ztj?RJ-+8)T#LLgyxn3}j_2l-zfGq6Te|VwLw8M6avu3JPyvATD& zn+rQ>2X1r6$16^Vn8}UDu@gD9<40~KaENn4o%K+{73N^I2|dh^IC_S=a+EzhX50DP zV;*;tBj5{T4{uvY(jIktlcxb)R0E^?m|2;yJu>2k!u|UOa#-qi*2?1In2CGG;9Dvg zrJXdd^@YIpGiW!mEMyjlo>b~4H&2mE{=z)d8|7po-M}A=)8l`UB|Ov%e57Wb^THe* zCOb4M8cuI&k8*774xy;$M~*}SxCN|qzK+cSmW5Z)3ERtRKFqVr!v9X|(2I-`d(V!7 zXenTOu^SRD#R3)5r*7B)$=m~Jr!s_c!IR@S()X?GOIk0lanvvc|Ba4T>8h(w*>$ZF zdQmyk}4Qf!d`4i(o{dE3mLc=(u5wk z3#e1oD?Nxo!M#4ge576MbHT$y?9`L&)MlimEEak#Kof>fXm*dyzQwCE(FjkDy-2}L zt83d`8jtK{$HdRSk^x~*InL)2e-3!7R*;H|J_eLg`lk;LCDT9R#oqCKK!-@Ic5x#~ z^xeeyGA-tA@y%am7ni#}O2DO{ULIb?dET3$UIi1n?bi!xRxA5PhoAZVZc-+hTn`P< zlZyGmOmHxNsG@mairPs`RKXZ+IFVQAB62-Lt)PRh``da;YnPeGMm2+WRj5|GSYS1o zgQ4r&R;XkWFG7KmV_&1{zG5J&Gr&Jb;TDUCS3i{fAc6$S+2KXX`XWtWcn~?i5&0if zZo)_HMg=PGk_C~nSI3s$%z9H;mPJyykx37P7wNtOREZiyO1tQ(6a1*aax?0NwlpuH zj!W{QFyQ^l=8&UXz7JU}USA9Xn>|AUHp#ij>@7SyaSzNIC{3ZVxT$rhra@k-S0PO2 zmkv16b+g3SpeFrB1KIaiuiXg$XeL;*>UQ~Y1^xN=Rn$-10Ji%@iEwWp{|VJ8@}G#0 zH38lQCkvLha-9zN#2M(7fmDGk4Ul~Q>Xd{8ca{xZPAZ&=|H{da6FjCA^KRx(Upk9l zWwJX@97WwbrB9u{)a|TX%5JW$2)!a#8h&1JZd=QgXL$dP<^U<=2_#~c@U_8-S$N_l z?z5rO+17@)JBvpo8k!!`)7Xe6VD~}^JF!=8H)2^$n)rZAeb-(b{((9p@B2zJcyvPf zCkwE|&}P=H*onh8pwfK;9OJkNoSHNUG!|f?$x%KUa1_K;ZXpyfKre8g_)dc5Z6NbGka4);}ws?C#9}W%t1+tlm5g1nCAgp{y8mx z(+V57=nm@60Nu8&_tRE?kzP*zvWG?@b3ZaMXX{;_s^Iko$$Ze*;eapxHx2#3o?l2V zv@17*)Y>C+HP0a=KG{{sNw$jv<3NxruOj)OcaTer2NQ%qgnWN!qa!WsTjSHQmpJ^a zMWbZaCA7IW=Q<%_`_5r_Rbd&F(WGR!q3qUJ^;U{lFOJgy;bu_ENH-*+l3v zuM#`v8Y%ZtZKi-WG#z7(@Oh09W_h#f=cI32N<(y=qv))bLkBI8l*86uFZSpt!yj-# z91Ay$0&Pw!Qx@yxKcCRp|3jPIuzy725+-Ih>YB%C_uTVP*yRS_Yxpvg73Zm-J9@mZ z20V0M0RIurv~aPRzn!2b%ai-1@#w7~cucFQMBHa9KlPysgU-q^Rfd_AZ& z#MMCRsbBIOCg@}%&ADw+Yn_lI3QpcgyX-cZ^!ed;WM@-yfp7(}!G4R`l`GyqGKm#; z9vd)2wciiT<%s@tV2t_VU859@EOv^fPK9D2TstV4jVNO{Y_}d;cC_fL?S{H8a7Nn_ zRn%AuM+|S<@@>9yL0LE)^g=`~sx5I>l$)lUm6$@8dc43_34#!&hXX5@&7H-}oVegG zFGD;(YoO0e{~R?~a93yBp#4xVIgR;hYvPNuhIPN42KWn6)n|WFk?KfJH3m^*E$nbQ zJ^wlRBc`W!sI}NhAuOM{*E;!>X574W`+lH_?05U6gcm?5#f+HrpIZd~{Ra_$vkuc4 zLR!MCS$Gnc4V6~!*UCNX^0#E@6xzppg;0R}^{pNDG=8Mvhe z)B3P3nk+xItP}>UfJO(kaXWFR?;N3B)yRST`r|jYV=BBecj8WT8UO|J}yYhLJ zocPX%I%NZMzefizEc(Y6>U?ySn|| z0r$Z;6N1xH;NR@s*0wnwo71^o3ZppzW>g$9t=&i*qPagR5$nbK;(rsa) zXv|PzAuK_}KZ-nFiv{`kW_A|o?=m}Vc{-IPZ~3KZ8*6>n@I73@7TxoAUpvK%Ne1i7 zt6-_DDmHp>A=bBOTkIK*Uv!rW0!fU6vsZpHqN;x{u}XYpV)65HgzO{4vu^KyDet6$ zh6Z774Z&Np8@F6@$R7ftyE1ljdtUStdZeES{c@fJN-s{aGA;Ck?%#*N;9;ZL{7S6P z@YY3>Fo_hVL@FXE2sE{P|AJ{XbSjt0*R#m>PZ7cFgf8uhM|Xgc4>R@Os9hD<;SWVg z5)J&LxB!3Lhi%HGYLcQkopO6rV4HGyr-x2a-6U0DEtG*S@Db3yQFgxEJ+-7!h+iJ2 zwD8Ve(Rxbs055MEGYSN=FeP07FckHIf@`FgriJ)rZ^!bVWd6hZp~a;vPhol1t_rvMx&z}Xj;Eqy zg-7}8DLYe4SgP~JLLFCeeDNE`8RXir-KJm8|H$VVTx`gds~+@P?VD3-xE?f%X zc}J_wohm;U#}B`}Q2qqDC5&w`FwVx^3*jPtWlklU(5JttKpx2g5?k`H3O5JU3gQ^C zS0@^R=R`1Cq^y=oc{OPFI@{y{DA?)W=K)E+y%4@bn3}n?mV#qk`C{wvC36tdHtj5i z$jLF|Kkk%8`pQEYHdB-_=Oag{Tzt6Mqg!s_qRoqZ9>t*+XGix!vJqC4@8kMHjuj8w zp;|7r10)QY%NO-$=k}m2J8uCZV@&E?BlHmwsu8K*7FG$H&z=aS;@pL`@_iWb z^#tsWa_(&GC_7z8>IZUUkEG z&ykC4y*qc!rr`*9)Goq$)K7_@iAx0BXFhO!-lq zk|H12nC|}hTFmU2LY|*T+IM#!pjpUA_0PX3gA1lyM9XYG!3Nv=dq_bhTW9)ZJ}M0{ zy=_tI24I8y`aVHt&E*Jq8+m&Wm7B-yuvac?o~KuYRQ4Yne2u?;vzL;<)t$Ia(Ut%F z_o8Fc{62H@6c|{biL%sx*Ul|k z>`{mE<_3;}vRusZ;!K-Komiv<+wBPg`}q0QWXF{6#l@-qVY)qMy_;{(nS(T0?cBj4 zozyWp!Lt*o9Le9~Y@Zi%AN-0tX24V~M{E?)-OC{y*@V(hnXi!yh5e^yt2aw|ws(gVQ_|jTX%h;bzZ8pagJ>b6s z7=~D$vEZ)on`XcfV{bo338VW41(PODv@+m#v5jo5^6%v|pWrPtfCYG6%L!E<&7`%+Iv;eQ=&!@0YCil~ zJ&=b&`D4U-_HwUp1KZZnPg4ukmvTwq-vBWu)jFOzU#g>= zAYiBaO5DeJ6t#GG`rcj*>b(>3-|x6-N&|o96-}1$?vhs4(P#cBubmmb8Z=1)+=Vo1 zaKw+JtPyOIAbvtL>%&1=oS;s`6LK^?IOytXreZ%r?z|iOn-)()e?qcB8(WrxMVsgNUqnE)W@h%uT(h$(bV`WC)2Mw~2RO9}-A;YK zh_nRFpHpEvsve(!{ zwVZm47@`y2BW+;kZRWy`+#xXEc?6W-ktRZzf{t<#SHUW%#CH5wuFXt->AimsbOoCg z<5ZVfViOJGQJygoh~mcMW8YeXe#VF>XS~@;W=7rM{Q0zqtLD`_LiGNqeA!3k^>0E) z3|pH%l`}x-$KyfrR?WWh!0=+w(d>KYohCDOcvfo)8PDLbPguzD0@EC<{~tJrP%LnM zQmhYKCr3dGXy0_%4J0t{nB_UBWKI>$3afkOV#NiFsr*z=1IY*bsnTymTQ$E)mz`bi zx>9%VRlf)ExuB$kf1VusInz>e_Ip^V>2RDKeOE&UB!Kp!;CZ%WBj*nT9NoA_TuMi7 zpmNc(C%LE;=eU(o7V)caa$&@YZa6F!$MffrqXJq}WV?Es{G|psJi2O`=)ED+idyW= z0WSs*MrQjri(zNah^FXYlfk#fGk1?pp*|Vh3gDn@gEj2TzwG zFBT-SH(#;maT>kV%V{{S#?u_Y_{VMoe|?I4-G=T0yo)UU4LWTLNk|U!dh;h^0R4L> z#kh*o4N*pc7hlYpOAoB*s>1TDCqX;^Kdly`)LeKoBVtwU8M$EFfu7Qh8Q2RO+!;#s z-ZVBGIJEXUI}Ej}sOUaj8D?0!?#C#|&uE5{{4DcT4qxdD>nPIBV2jwylxsxO{xwwK zNN#$TSV3N&E%D7=8X+JwWk8>@$TFBtSK9<078BW`kVmLeOCpZ2^3Ci!T;;y21b*Oq z4pP{nyq>4z3AXr7^7zoQ=9F27+L7qD1W%38qM9m8M?e&OesZaGF42v?}$`% z19VTLc43>`my2iNO9F%f4Pe7?@61=G232BB#sUvKlLf0F0YmKn_bdc{fMKs5Et1kiME)^2zwrw6w-`dFz4i!ct)0 z^cmW^W(jl2A{YZf<1kCYIyX&rD+g-ZSin!9Fk~-;IkySpXAWt7CFo=LgW;*jzhQrF z^w{298O7m2=HRifrfz@aVwq9?Kh4m@ucsPyV&)KEo{JN0B4wyEGAy&8Z6Ufq({3-? z$(@&KiiiKaIRu^S=av9t;$-QBYV}@Zs!6A3e5KOR9Pt|`v-Xw0#2*>VmReF?Nu&V> zG?5oJ1!_d7`{nBSvSlq(r||Crap@cPN%b?lL{I45T;P)vVoF#Har|(|Q6Gdy5E2)U znYTM!ghu@ZFe?-Xg&i{D@1v~ohsh??lFue@R4VzxPyj3R<~o8QJ`6p_hixyavp(y% zus$l2YbbL}Fl#NxY2{aLQHJ5_=qWL99H;(t`@Dx?WIvi6ga7$!m&ZJ?lbYuebN&$L z1@xJeCt+dtEG9FiLcIWdZ9eo4JNys|XCqaao_{NQEfF)DLrbb;2Gp6L6U2Qvq+t4* z7obW*zYq4QaC%Olq(?sa@g3Uep9zuKdb7fM>V}C5?_|as(4IvY45RBGKZ`>}w-CO) zXU-fviTJGZeL^&P^?i`%*Y~U?QSN+0w|PScd<+i!ss2WG>Oo(|=E2`eK?oQx-mGTP zC9RYmYZ7})>i??|;#U;Qn6Hs@zlk%2!d_&fJYLCU?;vYibLab!V<>S~E0oU!S!-*e zA2Q)eaNuh&#gJrLOLw}O*@nJ{6!ZGoC_D4RC$iX;HtkO%>7Ud@p6a{^3LF&o?qT6e z4s5bqWJM<)__jh+hqY4~u*xy4__483v&SIvc)7miSm7&F(0_S}nRkKRUX+6mTlc3j zz1xjyh`{rim=Z%JQkRt1b`7g}wu$cWj z{x64o*VYdP3E??p^{HXc?wG#21yA@TiOrd5HV!~kQ5+tA^acDx4=7MigFtM8tsC=v zSe6#kVOtqXa(~s!3*n~`vmI~-9sLEX2$W4g^4n6}ST%3nwUoX~^7GPFj7#%w?=JjU zi?4q0J0SVIYMNgS8$8m#=F=2QeZvnh{B2LMVA=iPN2PlYt%GEt1kPnz``PLnGkdmM z|1}*pgV!C#c3X9IihiKFRmNxnnkl`Zp}rE7<+K^bZfn+3$MZ|Z!Gs#)PqSS&H-Gda z7mo3-v+KgQ1yNsr+Be}lroE1jA<^sJPP7Z>t_i|=Q?qw@vIZ8FE=OLAyI1iLO5Z}x z)~P0Z0ged~HKR1KCrjhm&K;1-G1F7;G%iXBb*rMUi^G4n3x5a+`BXl*KrY4;?;Pe5 z46E6RkYVN72_&YR6w1Er6C0aasP7R{QPwT&i%%1OBPi~~Uy4QyGT1^qdypAl>461y zD`2!0aC5xh9DWbQc((|RK5DA#c0^iA%qxbGi>a_w23=%3pO@$l9lQ`dd4_g+SbIej zuLte7MqT^Lrv7U)VA+?_w7t#P{M%Eq_)H}qT*6iuVG)%yR@u|TMzn%{C^$7Y zxzo08ojvgK{a|NBr%b|AJkKQ3eR-E*)Ot9ykp6H3S8@NG`BEX|Al&Co{&#GZ(hqWV zu}C6_wjk;c?0*~5%1sq%o%Gq`CP}<1P81AoC)|Y|?u>eF?S{!~Kcg9{GfXDE-LZk+ zmywUWL|G?oE`D3MUW!DqK%LP<>mo#C^z2{c_mP?=TS5TH5n-0t^ATM^yM4AOD03M{ zQO^QcZiVwW?#+Ioo0G!4_{gj^&_5)gra$%pLK@ezUOQ_KBi`c7xf)7*wp+%UI6Z&` zXL!uv;0)SiiBc>lm2U%5{5%k~hrL{3{^ie5G}yQ*p|9AU2!P~)%fpM$eU2X?cE%_O z3$?wMdzPYfb-MPyWheOhu^)-#(zxrs(@fi5E*~$!1ti&D&nV`GZD7ZnvXxhmq@PG3 z{KV#XfVJ@3Bo^Wq=^NaB=^JFfS&CQqaKZfd^Scdfzra6K`v9fx_s$Q7Y6yC0x| z;(`G}zyYyb|D_X-#OE(byJ5Ah!6{c02@I;+g5-mezNJicgZ*e zxf_%v&T?^LOyO8#W;c9ZrT9=0hikLkgD{!v7@EgP;_;_?{U0Qd1y71_-N!6|6Af|4CA+Bn%1OX682P#;yY zkrac006I&Lh+&q=@8ItKeKA+cj)NSjeBskdDA3kkqx3*=1FFJCL-6$b_jr9*4m;b^jD34|L@p;Tc^^_? z2^2ZCBLg@wdEej?EG(Y)f!Ta*K02c>ZWtBvhpm#j&|pw?uc*xavL+)-xjUsZlSa?9 zcTQj%`g=+3Z&*E%j?j>{LZlBpxPq`Pvl`bq%MA4#7{f1Cn_LY zRLy;Dyd*;c+EU(26oQ(c4kMqVqSt*Wf=$pZ_F^pa>x8Uv&t7>FXA}lbx&w4 zH_YIfP;@#zZO85kpr88SZHIUuzI)DSV5sGgKf_VUUNst}){CefW5pADlR6Jx+P6c< zSAlha1*$-*dzKO>j)b78o9SFrP)G24sw@5mKuM5p{Fwc)qP629Aa`l4K0-nQ{^j@+ z;X9g?4T}3ZcxHtto;>wOhGreN&wlq`&rhQo*_&o;R6P6| ziV+&y>}$2?865tr>4n|u!@g!5%f5Vlv)E})XD?$&vqaobd&GS+1;E~-X>3_-zMqkg zD|Wp=EKIajz)v18W&tKyW1x1(K7}lHck@8&wS9j=9Y=Og567TromFuU zqQ=6);8&et{9tV({FY$CW^LIf9t3*vWc?Cz0)KBAbX>fnorg~1yenOBqYKo#yf?>z zeZVs3f-pokIlFnYQRn*}j2aMS3f_Uoutqo75E~!VF*HmN3Jo0f{4CVgax6^Zg418! z_}yz^D+oqB!R$Ed9kC_@S^=9{T92mdpSqE-kMvRZ8>zMoa**V+Y_dX&Q{wq`ga@hju~Ad{ETL-&T2iTX2c@ghe10tLNT2l zy6F{BKB{z&Oks#^fOb=GA9at>e*iB_{P1e4EZrWpaRyBdRqg|w?+87>Vw;dVHv(zttrv1OrqRr|$uxQ6*N6Dknv}Q@7A?-M-JQ4%Xi7Ue5 z?`0nsJT%#`)7|aa;+rD3Up2glIY6tby)2$SgIfsuZ^`~Uk|zaG9`qsf2)|KZOKk2L z^xX{l%spg>aCcw0l1S}hxN@u1q0?EZvM&VTmz?@2&5DB;_j41gn@^ue|4oq8;US{9 z1X1D`;t9CJ&e`S9=-q4Nfu}y`%!WNd<@@y^IphhZhVeUs#iKv&JM(6}fl@_LF$2v{ z2BqPSA%1x1H8_5D7mce;L}Y}ZXcX)SBIOQ>*_(qwiI=yRrj>X}=1C`?!cHGobI z-OrmbH35H7O6=^c_*}-XJ*Wueqf=BIM(u!H^dypODhgPLtEgI3J0Xps5_ndWXa%o? z0iY(HD-sg6llkDOvd}5x!*{<4`i5|JbzU3?I}KUPYp5(rUn;7Zhsx&#{*yF2BpGss z)^);xG5OCW9IJNJfb?ZAT%89MT+#61F$U5Masy(jLEZhYzLq}fdu%Y}f{;%%WyBQy zcJ{i>Pek3E$MyRM_{eAIKucEqG2#r0Jq9LnI6P}&&^cxh!CN`${@+<=2l&-}d(lVi zv&G*xOCP+e@f#4P{T2uqD~?4ij-5YL)`>WG1wN)r7BNIM@mn`I2}1^ocx!O&9{m+7 ztT1792fR33#OOtr6l41t?aEQvoW#c=mr|@H`2p|UN;YbJ_`&M{QBVj`&A(LbVSXDr zGZW$_ST^?U#ek3z<=Vq!cTE`~O;n^YSl z;>NEov2a%5r#ndo?wC5`33K^F$16^LQhg(Niza6jFob?&xxh1IwT+l7fCK2Cb_~1A zE?ZH@HRd_)puP{j?}_*j8+>--{2MG>-Pk%vcn7&^<)V5MN5jD$%e6)1T0%#-v1 zDC;9E9-8M0Et*A;_jLsr_qda)x!Xn@f|@HDZ(I;!*P#9shW*d&>y^PPz`LKUp@9wW zfY?tODCxH|7jr`&)aj9*r6Jz@X!v#c6L8>O`P;G$C7A?@os_HkeQHZ^&GD~Mm%NqUxe;3M^Q7~Pn2HgH4F9>2|_fZ5_<2_(4I z>O7fcK`TCYW6IG3AWd#wMgW$}Qo9!{x?n0w_xQeikKMTIqVws$N9m9L)4800y-5^3 z0K9u4npLzoH9ZD8YKISP&UR*JIkQr-p``;ce<&WW{P%>>3C>!lGABMx5q?Z^r`ZHU%mRX)S@`_W<=cFd|kpt(77S zR7|Yc+pZA5VU?3b7F70mLAf}RPSz|i(A7hS3z66q;NjAbE?V~l=3?cyb{8m(U9bjC zBU?Y?WK-5Hbe+};sCIS-I|<`$Rzvl?Ba4*}v;Eez{MM3jkPjXScg*`BB&` zF}AufW=ES* zVPHeczWWkY6#oNYIEn87ijxR|5N;6ko1!erR60JV;pY7H?6nvUgVKmEcMm4ZUfs|8 zY?)Yr1+27#8gyJ4D9!KaFnx^rWS(dWfpHLnHVo$>y~Xj8DkT(Avr87A z`L*cFj_j~Z6gbG1U=Dd5)E2_P%=4aao*x;f@jWFU&YzE8MKZVu(su`250C%7z8c@d zz^cJFz|U-G(zof;3;6@wZCAjV^}!stGEt8`^z+4Q3IW&U#)8kEKTm0485_VfWF~-Tp;mWiKAja1?p^~uZf7<4S7kMx zg|6N#y?dAc<>1XS6t^gfr+(EL4OZ6nX8Afo1IMu3d*C5G4vuwlf`RlDzd`jkyTcBV zf<#eBayy=~{J;z<=CS7nYH<$Y>?9=?dhg_;S%Gt;Izb*p4Ojld^`xAn)rH;K_ zOEy1xlTnGoa&3qgp&jLv%!99~VZ)n3pZ?2Oy2u6>GM@$My4^%vDjQob0jlNz!tEj&M?a+Bcas)~qoKS~2$oGSeVXC_$(2_Wr35;%1e*?OJF%f7{{| z6!s-SaactOse|-jK5+jBp8-2iyTk4XA3Kk{YxUgqvPxG4_PZShtN(kYNf* z&sVsBk?(%-Jm5d@(}e`$##ikIk>RKbRhSsO4_t??AJ#5G;{}MmMwhSRi09c+;wfDl zp7;4JjYi3@aM911H52V`1DRdfdY{xVxySPtY3D37VUh&i04o2>F-^ z!&pPd_Hy=Q=xO`7KCE5F`-VLd0&dcS+QGxh;KoC6tZx^a;rRn6EExRI?EH_vft<8ciqJEu<=%dd z)!?D{8Zo?jl2MzEP!X6bbOzHSOtRwz0N0h&Hk*%EI$tsaR?`55sP}{bi+* znC|b34mV5NYqR??jU2M70%M!r$Dl29VWPicS#Vecjvn!k=@W!zEepK3CH)`|sF^o# z{~qS$O?w>R0VT?nSa2=8X(Hz-!WcgwvG1^0JF!-IsI%0K_K zyha_bM-jc|;~Rz4Ss55k?OEr?AVkRLaBe%H^LTo87(C9G>h=(nb7ciyI&zpnDPq<# zXS#Ha$ItCk+YsSq`qVv_M^O)h6->fW&DZ}%6B+TQU~ARl0J1J2|H6Q1`WnhBSqgYS zS*UFdRldH9O1JniE@ux+RY@wyKE<&UC+5E2buyzLmKwbGE~{l!7wzS5nTTWdq!1y} zBqdD(>x1c&Yk0nDFw5mLY}QBrV{fXg@_PG3I%s@<}O+-thu7{|H$@WbBk7=-yMo z@cwd~8m13I0=n$V#4!%BGWfsXvMO|F=<=p{s9o>C`l#7Lj-m$j+tO+L>+7?=fep||E75}`4dvHrppUF;2i;Jua>0j zBMp8^8`X)Ry8xMpBOx7a3iRRyw9m1=T4O5x&(h+!LKWsVFEEP#I`SHd4g-aw%}ov5 zhM{sp)h8QbglT^?Rj@BlsQfs;sF|F}_dpQ5rbnpAXn4FoAs_Wu=w6ENnVPPJUsaj` zzVALs+>$rC=4*5t#hL2&U4fVbXd8>|@~G=USJ(K;5hBJX9Gws}M>&Q{74q?)N{->d z%`h%G%=}#DJJP7T@XsazP>{|kwqaFqlA@`Ra zkpyWL{&Cjf?BC^V=*IsOE^41%N!Zz0UQ+T`oMpSpNvw`Nz%{X+%OedRidc(1%HH|{ z&{Nu%@!^l#pmPpPg9i%0*)ZhFwJr7>O_&Nj(e)3cG>v5k0FPTJu-S(-uuif!>FNK5N5HCtZ;@L z$iA}bQsGihi=w!M!d;|PLOYsc;*`P0?Whakb6bj>=K^oc%m#UQKVRrI)E9c4k+>O%z*{YW|N z$ic`Y!pCc9AK#qyA@xg313jweZvV0oUwIt5dof(frEb2*xOvQ01e_Cw97skl^dN?E z4?>eo0X0)gDF=4qd<^r=%i5-W zH9?#9lqO_!pvgsTPFxPf-vw_J^=8+koczeZ7Bd|0k>)Pq@+RXZuhsPnje&k zb?;)Q22|CvYF9ZCtD`(gA$@WsFW6~^Z-b|(nmp;OhzU>=Bzp%NmD9s~_4llh7Dh>z zSitPL!nX>ujW$TJL}7pgeYT8;r{L%@N34D{ya2$ zo&JOxb>BSf^y36=grdf$7XQKS1#_y&BTKX#`MhDR%Cbv_>4~DxzI}JqJW6}5S7tAr zg#xeS0o+*%hvA(p$E5F@9=g2sNE$EvO4eS15KsF*n$E->syFWc_nF;3*6fCaLiV*Z zBN0(Zp@=DpB6}e+N7j@zQLkCn7Ph5*L{CJ z@AvB!yD48z{5)JsO7?PORbS?U4v8NUq;Knkql@_;KDtOp4&ER7cJ=!t@<$ItIUPOw zRrxpDDHqsCf{@;yTZcO9!rxD5H|<|H5&~o`u5z2h#U3qOTeQGm4?`ZjIYaJ9UZFkB zMpxw^PBAD(Cvbm*s?erg{oR?cA1g18d=!LTPzxMVk051bhnOhnQ21&T2rP=jfFXu@ zb9WM}9LrFms$ZoL05j|goly*DTb+h4pvzpKL9^`ciRzE3dV4Ocb>gDU!3>D}IMR8y@_W13WMa{Cbu9A~=8ei5fp=OK{DOm*T=v zxwSxP3SfrAX9Nzn-U$mxDQ3Mh>@xq#*7w|QsFJ$cnSMaBdh=(B3j2nj2efmI+ z;09TmkK6E-oF;*x9EX$VUx5~@q$OCCD=`PK3r8*px#!)dts3f7tL{V} zctC;;z{zN>+|@;+2M{xLoNa*tD%idgIzLcq7v?@Cvmz5**{2QI->ggC94|}Ta<%`C zzTJ@p6fyS({m5yUt7uLZwgG3zWB8uAP^JrBym1ZV`#%1fuPWRptlA7vFy()L3<((M z94m^Gsuro*Mh5WyjY6&DDrDu<#1n10;O&OKiM!9UOP;=8F#;E*D?*D$ogm&03y7)e z$vPYc$l+q#7+r3a%Y-O`Af-e9tl|Q# zASUzTDs4U+is_-65OW*`Q@KUWBcDqtoHlc+1u0B0W>j;HWrj^YSO?yc(*^IwN=SB-3jOr zz;f#M4tA;JiujJW#wOd<7qi&UYYo#T92kvP!87w}4cXW^F0cz)#Bn5aq1PpMzY8<7@7danPU z6k&?7p7!RVa-oj&VvKJg0aZx)rF(scAl>Ip?#|6df!yt{|5hK4vYO|ajLPa7_U;Er zE0cDErF-%}sGGHw6kcHsfQ9Bn%AdDe2Q*gC?cHo*s^+0dy-dGue5@=zr65#|m%>8M zm@PO}3l|o6bzHN`UWK2SOxpc81F5hv*HZG8TiOUAjxbGFIJs}X80NG z{B1A<1>h-3^l?RabQYgu@`rYU)lUF>w3e=*(b&dFnFB}k|| z;AQ&j+q*L6c*0R?x8h!s?r$6>fc%H$czQx2ioW0BcitYk$!EA0A{18ft#n`bA2nRJ zIA>wiDMnGI#Ua7%KPAUj%A6X2tZe?Pg#;q!Tkfkj_i8VO0f+-_G6n0l&$m5{BONBP zs?LsD(waaag7pSbP!roDS%KmUxZ4yS9-U|&m6&-586Mh>uKy)s5`fxUpTQmb6t0(j z1A2AYv`Jm~(}zl$&C6XaE%LzhzX?75zZ%9{DHYJ3v)2v^bwQP@&RrObXwijU%@|N7$D1I9sCd}zbegOJ&W0v9tk!$V7Q z|AEkX%nqg>>DTxF&@(@L2kXDk9GME}(-XkX=tF{wn&XvU?&@_4VaY!K1a~9b+N)14 zp)`d;q$S{DanvO|@d1?dJ7sd*CD{+&{B{zyhtT|#0e)7ARxRTk=xX8Vn}=Kt>V4q! zr?PiZ3s$%46u>=1RT>DQI07N(rz#6>>F_FS3D#9`8$Tj;@iP$Qb^U=mlAs-__Pz)0 z8|2$H^~lPr_Y|=Dk-c}c7>xo%|E+#P-vlzoHJ5ioMk4Ju&{>mzQG|8YJjfNkONFo@ z7wq(?TfgE^`hx27z9%)Avhqv(LC%?hqWYq)=XE%}T-%Q$|5Vc{M>7ocTl&Pc(ayuy zx71>-3ROAC@6HfIQ8DT>!I?Hxu0>UyQACnVdG3{9K}cxRKjZ7qg8oy^n_L_qrT_yH zbQ58H;{Xa%M4(JMTW$vk<_F%vPp+6=RW}UrDD^*ez51-2e*x#e%)w>$(xx@f3gZ{b z{5t_v+Uh#jp4xckC^_hmP~in|ZH8g5dBejOL(K)^XlDjLb-x1b;D6}c9`XmpoXI=5 znn3S6ha1Fc%ikoFb)O+pPD$9~yI_Cl1Xopypu9pBK_3jE)NX9ew}t?x^c}FVolP6J zwY>U+OGXpJ2u~1jsAri~^SR#d`Hd7G`ETXn0bkQVoHVF;(Fed~SW$@@EX-?C>fpc3 zUVcgE-M+3BAYTclxR^aT&=iYxgCdM}>f9^%o!ysqc2*)5F zIbEk2=l&t)jo9*NN;7Kc*~59W#GACVQ%)hQ2MRG1I8fa|Ohy)vs>;beb$VvL+2#!sXuzQ+>nv2bt z`;R67Y}`=r-!Yn$KJ;*^0T~(Pq1~ z3erUfxqA|gMK{UU4c|Y~Clz@2w|ks&gjvf&Z3x9RMldxI)VwLUGAGz|UPDmTF!_9MG8d|FFV&pArn*+OONA=x z3wkqq$C8RYItNWCO~=4quD-C<+>iG$LsTgCJmnTQg%@7kSFnoaqO%?};Y;u1EjMUQ zc_0H!(cV&V6JCDtv1rO!K)I(DjxYI5aj4Sa3Ad}Q>cSmN~ z#jdw5SAAv6i;+{|>MwxfnN#052LyFiOETgku+USNpdpD!{fTlIUo9V&41$NAPIL=X z;A_U8%C#*(fSvq*uhrjz*LdeA2e%(^ucg)k4aqs3ldYM*AlKc!^OwAZotG@JY0K5C zSS1`M!*`owax9oeNH_)#NW)j^Jta?)X~7;Zh?MHCQTb=E*tWo;(Hkw( zzoTcd3&~SgJ3;sAr{^oFFJUj2?q#KHpB zV~GlX%c3UJ=>NrOp*ckn1+n}o6sZ%Vc~bABEhi1!vXmOBX(U8wrI}VBqPUA^dD1xE zZDSw=?Y+sW!+IWlUvoCNSux*HiQ1|Zoz)*vFZJ>G5t@Vs=7^{N^)ZK|zJ#>vNy@Zr z`<&~Xpbd*NkyVsUj6Zx+p1-=kW-kIh3_HEq-tPZM?DD#|>EIUT*ne*6h$%28?r8dbK$D#b-P=P< zyR^U16L;hdeB%5F8WtsbWvYtNUE$)Qtnb|S{gmDkNPD_N&TYZJ8v38&*J*6;ndea+ z+XVqXi}gtf&kDXu7S`SoD3Cx`9ml|DfDuO19!q~{QZn*)c{J-yBl|$pc)M~|vlQ)c z!Q_LI7W{%n(cu4=N@)(6sTF=aF~l#OJc<0EaAFvA);N9tPEPe{IRnHj% zBN828e<<&Adok*~*59UB|MQ357mLGJ!dO|0dkSNCDGuzr+a)a62~W_cdZP*n6VY)? zmJJ0B8=2IQ>8oE}fdB65KRBO*GkvQB+0;z=KXaQgQ-(Tte?G*4_a^jMA9kz-?KiLq z;9F3mlo^9h8{}*v>}fy?bF1m(yoL7FIK&K1jRX%N zaV`{?*M24NYzw<=cr7xrVea|9>4Mo5iTo!Vw;@=Fz7!zcU6#MKkLn^HphPpFg*cI; zk4FolU>OQaU=If16`>mL&`(zlqhyH58_CI*ofUss=aa~)4hlK9fu(pC2>@HL;m@G@in#Xgm$yjB;eHNkU(a}Wm5bkG}9-7ua1zS6Mg z;Ac0?BwOn;kUcBN&KD%grSH`)Zh23CMl3O3MS=9qk{l@Dn}!5VbA(>{D?^lisV9WQ znvl8StIbsk$gl6Sij!tTkF&0QTppediMq%kj<91?TSnsFwyEI5^mVT3-m@S-Ua-wZ zem^O4u>k7Vm$b2tB22=Dc+xq3=GLEkxQNmuXVSPW_1y(OPg5UokR86EQ5r1yRfy^-l=WAZ zvJccmmhD}&<(naYFrB0ATnS<2m%Y1ieBKA_T|SnT&bHl#{f5vXM|mrkE|ad^5`?e( zYB7-)A*i$XD&|kY@yy8{aKQ}T4}d`bBp#1woa1?$S%3WaSHouC_kt`+-=+hG0+56Q z6YY!O{SK-K>990O;3TcD5ILHNqx8^jSc3d%j-Q7EKMoa9baTSB0ul0H11G3Eui>^_ z`LO3GrSAMpWg`94S?HQhL9&?$$qry3(oTv#WOz`iW2fuSRZ)!rOd)^xiGu$YzEsI- z62OqT)-QVyyU$y3W;~}(Z186Dk9(y+xeeQZW2AV)2Qe54x^paS#XB#TqnO}*l)3^Z z81DJ?g-z%8C#ZM?QiBMm>61S=ya^UmH?ER9#LcdIaVfXY#|k5veAr9tqGn*rYGqgSrJ=hO}sSNyT- z>FTu6K4AWgZMhNp>p5`ogoV}zcL*1z=;8it?5B+FYSM>4u@i<{gi2iy}xa3|c zm_`D{X4<>YPcLuGCce%x;@1p;hM>rTylW4ksWbV#CPRP!YA_01k%?Ec1)Pt{_9lUW z(Vb6xdg0bad)-nu4`Cf}1M#2k_56uqKz7RD7+^n!fp2ttkq+iKrOE+ply@o=6wf z!-T)ghk1H&6C*q%F9mAh(~Zs1c=x)1{hD!X6z4{!0T@UfW1`@|Jx(xQfrR#Ct5x`mEa!XXaxk_0Mg z{FtivjdyNm*vtMgd13;&$PR(8=Hev(i0l}Nm$}Kihn-4~iaQ9rZ+(qZ(bsJWa)*iZ zoD^w-r8q?p6@9`Wq2|4s3b?V|{$YG*efxzGXvNnoxWKovT;ZR(bpBS>RKOo+PCC(> zLCa@v>&|&y_)|9jVj4DvZ^#I=;D9;YI1mpRvS<&Nqjt|B8#5XxXrj~Spi5;0?%#a% zkCM?>;R8r$9b#$67|{pA7x0WN6HjNr?~s46n_-2ZW6d_ix}YR=pY8fX#Ill&MYLKL{!c}qqf^ZEzzFxSaXdmld| zB8dCPT9@^|S#u5fo;AQtX`Juhba1!%K`qPQxq-v&)o*nrE4EYp0=-(b znGwsTEz(Ld^zbtxjyUga;EC_3YYDCjO)+FDpWQRoFfY8~8AYxXGT!BNIchq%6^+3J zgm430AX3Vu?#3?7VPl}bBC~`bCkXOyVFs9 z#v1Sr zORdXPf~tlBi{6Mg4GHHq`LA^(5Gjcb^)M8`15T4d(s-Sd!~)r9+nQRcf=YZYo?dI`xv-wwplY2Nk-W})BhlF7W zT+IGeWg$rU$!N*vYhNtnQH-JFpA_Py@POs$BLvu)rL3hzI0Siar+9*Un1JjtS`5nk z0?6kDeFa;AEnNavxAzcQybdX8sO9(-;wn!gh}Na@AI73Pl7puen=r zZ;&NP##WE#zjtn+E%jo0l$FE2f2Jn-CT3mn7r-AMv3#qkQBhu&*rbUHNW&M-<|RW1 z|J7lX8T?89m3(@ryKNaBC;7*s8#r{Wl}HVblQQq`lyj>PAUppv{23r-oU9Fwjg^?h z`3aDavBowYkom5z9Z_~mlaG?)bi@a(2VfE&woZTkY7d;pi{$kn^?!3U61NNzDMa`* zCQ?C&>Mm2#6=Bu=;=gARM`#ZE^aqEK%Cb!zM^#wv{8-LA#VxYy`(38{l-K<96d`~k zJ?RS9;p8-Us3Jz9h5wK%Psy!(jPLB>PvCaw{hJ4Vs6aH->b(^_ue0;EGM4hg4K7R6?fJY zDdRB0^amfn?w<&a#^F3YsXC_Bul>S&MZnRQz;fV>vfyVaPrd~*9()mqn6A5DUbvU=J09?}nE|716v@_vfv@WadrIRUnJ)k2%GTFJu)Ot04Uo>ZaJ0tx-WCyK5BB z3(soqwNHnJ-Fo@Wwx?@z9l*0r^rM6JdI#9^v>B_-A5AyMkc8cE)OPAeeCJ;K%%~VX zicP3zE0D}(3)F7g76Sn;@CwcGdW7SNXg^o^4nA&VgK^N{A|Hc9i7-=xEfJ0qTeD0~ z?5rHU^9=-7q`sm3Dza^+Ep3lCRa{sfJ@_r^!?m;oX3hlS;L0Z_uT`<8dOm;mWBcYd zI7?R6iuFZTEN)O>ef-~AWvzyHgRyX7CUig^XBo&Im%UsN8gQ?~a_KoI$5+0GUZV{( z>7S(kIBa*DzN5qg(i0O@G-3PgVNa+`^f2n+OCc1viuxc!$=`8(zL~j|9nBZiCOK7& zkG$O2^13+mQ|q0PUTWWZ)a1r6yy@K%q@T2L;RER zwVu2|38tM%p0RZ26;%_ebkK$tefSk1L`L8*3uRXOQ%p8(CX#q250u{>N{t7zj zhjQSo*&f7bGVVK|l=krTZLSogf*RgTn4dRRf$pr#Zj+X-DqovR0 z6HhB<27idbsw2!>0BAUy{ct9x&4)3^`|2`WE-}$Upm7v3~Z5;YbI9M5B?gEW5lq)755;JZ=B z2YwovE(QKTStMlDM{`Q)<23$GUdv#)JaED%J{}mhzWs}1MyW1t_jd_7mG-S~B&#~E zR)%o-e&0I08@!WY^6cA^YttEdvcK@kb&+kk@3iGwE4dGBWsOkgn%liPazpbFczlY^ z>exkol^%M%Y)7NX~MJO#}8$a2hrMEc^cTkB>#dxY^LHq{=lcJUtJ zb*5?*uA&%1KuUpo0LMAOfPi53Gl=L=V*~7_C7w4B7<$OHNg=PE=7+g};4jf@B`WX!8@KrxcR3#Ds2rM{kM4Mc`H( z1}gL3DicfE&!-Pi_@rId$R$ODXN1>|gNOt`q+AKyLM?Q!QblIwc!pP zq_0wYa}K-u)!Pjc@fcM&N?T$Bn}_~&IDa3YC|}M10o;^5e@vvOu9lADgOz@+-B(xq z>x~A0?g1ET8Z}Ulzte_5RI}G@2|Ch#054IvdH+cRpyvf4Q$+gX&7k$hAK6>@ZT1%x z68HHVEX9SRDgNmAPoMXgS|&h8?QnK-{amm%xSbHcO!i_E5qFJ504&Q@oJAL+=K#+R zJV_Pg0fxnatrOXPYdgzLeu$Ay@3rFbNI|3+*nlk;ZqkvnqI5#xaw@W`hg};&cOp=t zfzx(i`L2S4sEt#=#R^-rT=xhla=~Sz0wl_qiDEi#;mTe*LsOAeOS>Qn^HTyqk?r(w z*RZ`fJpDH6^RHiypt4Jc_ulhH9pxiPuu?J7^qKqxl(+LWA!n6I`7G;_C>rMltbhn^ zqs_TE3deK44p4*(Zi;l9H(-DeCQ*fG6#R#!f4$Z39cvf)YYptkK`$&N)DQr%1=Tnz!R0(9Sq>T8{FdT85%@pSMvY6#U8dCQ32+pDIkT z1p+O{hsdlS>?_EBd0qL0v^TJ@?L@)tdr$_X$`A(Cuk9be&f@S-!ObD*KT9zG+NXNA z4I@f^ApK7Y=ZI`cEEHlR7MTcsMpt=<@LA9RVW2i>>h0KOU1mptz^%AoBu<|83`HEx z;=Ss+vfNQ}(Y^i%I5hm0t5AjHhr^6#|BjjL()muy-cXf-(~r`~ZD7hQs9NsUHs2k; zEou#4Bl_Oy%1|TNHlxx*4;Fp0sJpe_)(}=;`t2EV+;b@T^{2fUurNU~5cPF3aMq;; z$02C_=-Vnlhh6uT8(ak<_{0TFC;0D53*go4n5rDIRT_Vq*zQpj<^7)>FFe2%ph5D3 zRD{E#Tz877xypkyB!J=W;UuyEM4;F~conaz$9W5mVePyPZk;*s^9?oa20!UoOY@|TH{G=&(xDquXX+r&RBCZf5T!{FYysEK)F?36_vQU zGWuGW(TG6=@l#TAQn&s^ZKspsuMkTU(r<@^ZY zfvt+_(i5*`PZl)pD@K{TA}mC|UvS=iQ^dLHRW^aa)->AZO8Y(h zpt_nQTzCgs*b5`SzsrLGqlTuf_6pHUBI2QA;IN4r#S23z#rc4*$FBpq1}?*N3)uY5 zg4*}Z&9?{WPc_x5(0!#c9rzO-Pkj2(BG?QtkiBgN@zK||Cy+Gr#tSsV4LMf*YS_v7 z-dTQ|((b+bTeX8eK`@DCb`DrJOKtx{J_z@Dn;Uq18W}+TnzeLn-s0PzKziTzR^z_n z{Xic28zc$S=8Y{^76KhPzsxu`?pW0G*KL6%jL$|7j;bqb>L`HRe-ewzTXZ2Q=-Pg- z<w`*_b4&bpbaaWWWL3R%va=Jp{wcyX!0)5E z6ln$0&Dx$rJI^Iue)PL8ONMgEQaAH-ewwSqS(YRRQFb1nr?{Sd9{y>^ekE8~m+?59 zQ9R;TCz9QW&Hvnc*)*JT8O^z*;~=+atleW7#`O)?L-xJAPs^|iEOec@>2kL~U5Km5 zoJP{TdnvkbJ~=}!)WM2%N{s;0Hr;grKv7h|G%TY>ZNf7$Q!U%^cKoAFEgUhVdq*Jo z@C@zFZZ?@J1GJ!rSdukdasEl)X!t9nF^M)>jUrT|QwL&5qe>~&{9_L~a}qzFXI=F6 zcN6I-mBJFr(y$x0f_;yh@nKTC7?Y1jcAA6RgU896#+zHG>u1llIR)mfkt=%?v;A3y zDPo3!jtz+Q6&@-t`1|&BmWYpz?U5`D<7#!tMfg&t@Ydu|mdyTPQKuLX77mBX9;fLM z7Kf)VtVtK)^TvSR;r_A zogHYEdm8!LXTZA25+{{*$UT2t_w*Mx=zB@uKq5Bb3iHz6WS1`SS5LY5Aq5p658|Oj zY?V}Z_4NoZJHTb%d`V#U^Cnh%DbN|csAn*sTD4_7NpoV$;{$eofo<^43W5unso{tK z3s`_P%awgi%XzJ$t%KFIp$`i#cmEZ>RvyG`cu4x6kqiNp_qiFuR~E5Vv>iI%cf|gx zEWw`gRXW{K+`v&r6~#FrJY{UBNTDa@>gt@>Lr(|vb{r|XjAQ&~hK6z2vV$0A?dA5y zFV=QT*J%a$S12O4nUk_Re>03`(uuCOa9K~F{n55x(N}9BqI3AXeZsAZhBrmKd=^Um1-2gfbr8$t&FRz|yqr`w6-`m2ulb!c zfZ6raE-!q7SYKX>(FF3BRiI0(cLI_T-OG_3w-N@B{o(r#Ed{2EqQWFOVdf8)PUw%} z7`DOvR5c+I{PJZpx$crM1TG+7W@mjCx9+B$>jM+mT#ic(9KsOQf5}he{_@!+ONm9n zd?d4%ookNF4mU-u8~G>YMRAMvPF&V`GF20wR+RDf&teJ%L&|z+R!ig| ztWB~EH2NThG4ynUJtAce52{}VZciIN zC$g|#gG^WDDnRzFrxWFJDk{xh*c^a`FulsL1)pyyy<6e6ly+TKb}(ig;fEMv;CQsg zl^hgAYM<2rW{Qb`a*UJob-#@7Sl$_5i;q6H&`Re1shsbGibKOzRyLn&KVT$t{GPBn^b!^^4mCmeG( zT>K}$sAzak-+rn~$C6GDG}$1_4e}_!97iVwS@(xt3^NvS_$JLI-0>K-r=I=20p+~k zjSz8ZtoJMa#9Dz4cIq9fqKl@iK-ZBE=%R&5X@x>=`C~?tAV7?y176@cUH{ftN7h|= z(0OMwO|h19yjG-a+>Jb^PZ2=D{H7NJ!_3eN%bMgP&kSeq9hpJDcHEv>7x?C}@bcO* zdo&~OLVd@!zxX{=vc(ugMS=ZWZGXiE&Ph@@Xmozo`EOx>m z34*mqnXF{~Yw>Nk81+{10X8FjFlmAR*GC zQS&m}m@q?zhHNkB+sIQC0s5eP(_QVtzx4idN8$?9IV)W{4vv9>eYm}s;5f*zSV184 z+uO_GT+4gP#~UeyRu-Qq<#;DBtdOF`fa3o0p;R;qvW|&%vKv9usESXghxE zx|QsaER%9ZvWY6C`bX-vI?TL4IS*@@h!38iR|)fqjAHl?HF5XdMl9M&mEaI%{uY5b zRLfLi8p3IP+U5#@{NumCiIKSi!XNkdmQP}77oVejY40;7T_cZW|Jc~{SA&Mq4=W;G z@ywXo6?Fv^nkD7)OeOX5rkv#Py>C#yNe&I4os>=ZP%7oo z`>J+U?DNP;y0yG^#51JIV>AgzbW{1(KD{~c`QvLYip{(#Y2UpB=J(_S8kHi5@E?)4 z!&t}7939LKDmra4+nV1I#o(NQ=~*`1L}){x6O^MF1#!dc|F)f=NuPzo&HHvIqUQX3 zuGM7io8m2l#1gWlCy}_U&}YnhLPl?7IL3sXI8BdX36w71JD?{7;YzX2{)wmG#0TGf^mh6=t8|_Z)jHZ7b*AtEl3e5Da-(PHf!w)S z*$P2I;cfo}GdT?1gkCz1Apw{KQfbg@TKk*qrRN145n*NJH6W+t!q`f$rv-}dR11(! zRjzS-dCbEc0MXJ?Jn7b!0M{XidVVX@`*-hB>LR&nF#ZElIDmwox+Lk=hBI1p?Vz2& zq=;`DSg)p6fu@O?`R$d>+>h1&v>9h8+@30oC!y@@D|L@C_ptB|8E@C5qaxI~AUFZH zEnvVg%UT`T33VG%Q{)@f==hm;7*Klr0k?C17$pX9dJxs^Pb7S6N04iTDJP;6Y+q!)3f#(mIC3^R< zrgOV1P;*k4H^`&EkT(|o0|9}eL|~}SaQX|1HVPj5NGJGxA&1V1Jd!?%`H5#qe@v9Mo9y6%|ySYFX7*Yt(26&7{obbJfJ)**nyBZK_Bd}2MU-^%? zj)L7KXuJJD1<@8mB}x!$h*R7hb)?)rwv9Xw_F25innvF5BK zpC;$H@PI(*h8IhIbvGw1p;(h;br+qcjfVw65FzX)JD$k!epc7XifY^-pN7f02>G-& zh+ct%4w4(5=VHe(!Vki1gRb8A(@n9!&S_x~S-+X`^6PJowDQ$Lel96QR9Z zulZ+UaC6{aNbg<+(%YZ6IZfW$Ws?9w%5mk0gc_AOsO{H;6;f;lw0AI-`RPUi%!AEJ zJd~%1R3}>yLr-N~JdNpVe$yWa`HkXZk?Q>SZ9aS7n4KH7%lSz&Is`t{w#|!*523-d zo7J2zYt#W9K;na6Y>WKMe$7>QU1+tS8?F09;cs2e;zfuWrnR8#=1?gMmfQ!p@034l zI7?59v*XWrZ|w?^2B;l)0)S1@K_4;A7+y1MV$F_RO1{QwKi4TYd$=uDS?E(?)a3Yq z@BeJ!Z8>Ks8l8Crhq;ArhE$knJj$1Q1X4@qv%?5>OC~(Bgok**G5ofYK?Pd#MVgCt z&gQP2pcDw2f?NV+Sl2mpYOKKg+8U>Abi$i*>WDD$&^3!^L#jF&PmsApEYW9j;hvoL zK1O?tSfLlb&`*O1-D6#oKpZF^vt|<%5;((w^@limN>XsHkZIgUt+!|Wsvx>3ygNd%GM~A$S3;!&%^wtv-ZxTZ zJ1pk_m73E5v>|d0avQoW&f8!zMx#O@Iw9!WdG7`1ip_Z_5vh8Q<-2#hkiZ3~fL*{y z1}H<3zsE*h(7H*890{l%1ch5>p`iwwC2H{9lI>qD?Z&hO2AQ?;f!4vlA8P{iz=LwA z>d$zaFlx(%Q%YKiA9?47@IZ8X@L0PA8zXE!9T;_9P;A=4f&gJKJ5|^rU%M3@nkSr( zT;OOH-?jlmUjZ1Avzv^~@uEP|de^2sqIC+&_GOz80}h zYybQz?#Xx^LNzbe&2D+H!oub~8LkE^Y&lEJ2QAy7Mailulk^f#G&674Rq(b1d>rQn zC$DLUv}PYnuUM^nn)4he8yJ3&jL#y0M6~95 zMy!$XlU?I*c6bzLsjpCF{vGDqV3D`C_HT2rF#(tp8AJ74BD_a6*vFE6k?uqtaa2wPTSkrD#Aowqsa3SyX zzyxGri8}OvI*8RoQ6pesMeL`u)}4s<&heBCN9cYI`}~N;b+lcF=|BD}c~kwBYk$0a zzA2>il!&1mZ$m>qdi2$>%@SS4D#=vwoif9!1cflMLiWK-a_ zsj*d;uRNL@j7ixRfwi1SQIUZ#S^;8M^STsp`j5P_q})zljCgDUHbtZ zsqaYDra!xSe)K*#S7|NEH50%7V*T;uO6ElIwThT_pl}K=fuUf=fAdRu?>QO zW90Rj5~v@?_+Sf7?G8YrzIRW8mp<;uY?LHj41z6>L`~cUDF#K{AIWD*gDp#S&IyA_ zg$C&SYrLlJHu?A?iHqQu?7zMRrpQ|mlH_SXk;rsyx|S7emR3!opQ~3t_)iS%pH&rO zWauL;Xn1fZ0Eb&{RhUtG>;F~TKSflEeYJT@c%IURs~hK&OHzmPt|LJ|M?zmBph{VN zn<>#W6L{qCarf6F`DNc%KfF(!E&4Frv>M*u@a32$xBv}YrLvcT!r#9=pX-ejvEFEa zIL&v5&%5@nYsuy%qJ8^~30Z(cV5d^vdI^Frz}w+dK;tO=Fs>8l=hg`n`Qd{_ohEM?%`zjA zb!W;}g|zrpKu=EB&^1Re$Gu+$0|oK~2u|MWpZPkkwIl_dB4a?ns`6T2e0LI!vAhr~ zvsiG`j>CpKIDy1xcROdaaFZ_QQir?1&322#&=*7g4ZLMbSKoieG6w4MkG;!tvqWZ} zE}s0B>hG2SP@8h&5L%h;EQM;=gQBP-aS^3KTpMJ6$(bEi-FCb8Q7kwnYzES*3Wm3#k}1?c+~U~r%J^KXuWd9AnSEpKG|RLCZd`O1xbZ$}*n2|4X#x*mwtcETJ@XImYjyF0e#x#6Kpdiz4Qi;zVqq z?e34SghoHQZy(l#t)^tUkGh%Sop+ZauR~i{l(yetD?DHjBa$O%cUuobCD^T~f%^|K zgjpITpJ-opMOE+OiCKjA%elK8BFOA_X;+-}GdU(!#fv zexpTvL$)91oDGXqtVWeX`o3DdD*T+P`b%yTy8~w)J0tJZUiSL39JJ63pT+^&Y3R8X z|N6VJSXa?puR*xI+@?%Xz*!2y^E2}kcc>z=e1o(lVWb2SU3gN4suCP@Uyofvla3bd zB*hZxy0TW7j8@x|bdl8*+a*p7TU8vEKI)l zgt%vInNTE4xsZ1A2YCoZlAzY}!ry#NjM)3%hoQ5n_J3U#&|LRaTubbEFzXzQ20R|J+5d817=-~?S?#9Ddl z@wxmTFP70y&b6<)8=ya+7hbxtJOyZV!OH1MfDz@M-K>(>wz=*;%j+85=h2-SWK?;=lrC>MET@Tum#$A8q zO5jmu-5y@d$}RZ8rJ2{@;K5q2-)Dw4MyQF<%8lrE0u*UGyuYi6-Tn;BkPfC}gw|E7 zQN9k6`gsh+2p|mj^h1fn@?+0rxinwoG3C^wySK|>7&&W*A+-z#M1dTk0GWH>M6E+v9z<1tITB#mYphsw`}0k9p0&3a@oA^+bvoAr znBehE$I$Bhi>DXECUG@{cSi5U9ey+c8r`gLFHYc*KSXZnFdjo z+$cs6Y*0k#3j@?>59*{r{m!!==d%#M+nv20&mW7ujHp}+=LQG6Ca>BG0mt5*-)p_( zD4I+`2l6?kPm9s3$jl|4p!TWAy&2EwW&gFlJw8!MS@F>@?GkW-!?|TBNBB+Y3gybW z+YUC&g^jS$A+gEfYY|uCpHq$z+a{OM&rzoxC5?p`2FKK>KNW}&NuotIau|%>tJ(Lr zZd|4#*l7x9ogFw&n&ye3&L{jXFa|iF-vY$5kcA5lLz~W&aDhMU$fr>Ag;cFtPzmkmjOr8h|Rk^rFGYC2#cLQ~&roM|ewYI_rgX1y}h zf9mi=OPJ_`pl^Zxr?Pua?Mk7X3$zWOBs=VV2r{umVTkdjwe`K25$np(SKPg=aL2Ok!hAKvhc%+8!?-druJOb zP|H)OYJGFC_wn`hZZz=xe>9zmJCtwOhM#vf!`S!TAWKq|J=-fzC+J3fEGm}BOh=eh6eJg*;z#!YC5RJ6rN z+U{JZq#AbZ98UHHlBp(dvx1C22x8U^q!)MOF_-ohr|a^V0gfk#ya@Yq`4~_ae#jqm z;#c#`+H*lN9LsO2BXIIc^T?Tri!iHeRv&n-6s*wra+6>xL??s+TyWUwgU!M8KR;cd z^a^C8^!%s|5H5b%xsi{y;eD%Ko6HMZnI+#jFeE9YyKoq(Klq^Rb@RpCRdyU61!lI7 z-DOItBN&L0-0i*@d+_U_Q-8TX!CHZ!{iux-#%} zZ%Qlm#huRvlLp<^GVL><`g2iZI^TTY`2kRWE<8-KYtNwCusKK8SdAA6=hT+OmdX)R zvA+1lkX3b8NGvM|QVC(~);_n+lBrkHL=5xi|S5o6759e}!hevM@dF(0yK- zF4i7Wy~w9|TPH^x<*QhEeQCtlPBnDR3u?rrh-1jY#J{d|^m*j-crR1-OiXr*H^+{I z^!>c%o8aO;v=mN%Y6yibrA4Ij^610nLl7X)efp1~2FaudNQ6CArMMCnr|wfqd0qAI z(u96yb3SnIydnY{QtDdoJHL|Bd;!((GG0=HdlQr}_|GQdsBVd0`wF+)LELTW(64=_ zmnZJ(vaCM=YH71>)mn`T_VS*1A$-pg)+8cZo95HG?Z|*{;(O5C6B?Yq-;2Vh5(){L zd8EfECmC=fXVM-+RfWaT!i{|JE0iefj4EbKlvsBh&f$lxm!Zx3l*o|7x0-H9(K~Oj ze%Zw&oGELhojk(}uR$?RH4YvU`rBhtfOkcZR)B@R8Ib}0NJqs~7hr7;R|$Z10zNEc%4jsy;yH4A5P)u zp&a&X=LWkV5}K6JZ14|7#}eYtJ?<3pD$&0e7S5@%h%Q|zSrXakcj8`%x;yO()rAPh(q6606;>n>l8uTgde1G9IZlMho?m{WC$d2 zeZKc8p{1`s^8CTs>2yPwv+a<>z9Wr@2G?5#Uj>^xbcj2UC5fmh!936gyP{G~VaucB z)wMRy`l1wh;ECnV5(+Kop0sCucwF$Gl*nd4aDfp*&X?Mm3Lb)7^;uSU?>~gppW&(>(3Q3oqseP z*ot@YF`?hFMysQU&*OzSN|r!v-(nTw;E$a%hsO%!b(2dp?%*-H7HMv{jv~aoD-~{9 z@9P#32L-7Fej0KXO+8Fo^|Z3W+1PhWQhAbp_*~Vr9xh|2qiOl1#xJ=gMuuZa z_G8lMsoxMY#HnwhS(cy635|sh0Ui4Y?}*$PE~1hEE&UXddSUzyJcF^v!%vVj0&s)= z{xH9`Xi9&lZY};?W8i}(uC-1gtu+*?%eDc{2xERX*mE8buWOXXNdn7l zg(|rbS`%Mv;;XjFO%Z=V(lKN>?)_2+5ADp0Ik3SocE%`TT$YT^JyG_D?RRg!-=`Lp z+16^*N^rwfbZ}+y!wcQZr_v-rE+F?^8qf|Y+kU5f=u78NLhx_1zE9qoM801~=09lw zHWJUrF9UiukqjN^p9!^ts}oN}qD(l64=**(T{pGznCC3;u1+469Ya{tz+X})XoMed8|Jt_Y`(_p;s_V^0z07eR`*QQ z_C^$3g4*okhufE);M?2$rX}G_c=eg5YnGoVd(tQV>^>In4tOD7N7yzoFqN4pOL8;| z=jeLyTP=Wn>2kZVhmY3IHnLe*e~?Xo7ubdTvFD=SNCTdb=e2^sFNg_dAMB${%CapP zc_a_z#RexLqIHFSg@0m-(6|tw3UP3XW zYBMr#5`I-gmej3>)Z%GZmsxU8CX(AozMLdH>6-!&{9iO97f5V3>2V1tz=PL6z3&Oy zj(e0M!_*sNJ&&fL2WdP9;Zn(a!;1Jy&jUF6S!4}24F5@4M*?+8-XAn1h+NV0ILeJ3H1Sf3VWtSDN8gkr@yWWMQk5gdQDBaP;W2Gu;Y2Z`yHGt`r zJ3Y}Lkt;(Y?>cz=B)F?~8pq)G1p_Ahxx5X~W|b<)#DlSedhe6)JhagyUG)C1x9oxe z{h;hKL3C@{MKz>)WAb71rO_CV9~a-gZ+}fPYH=ftH;$}#*Ff?4P4o0wg>AkeI0Ls> z*3LQg`8BA}wk)xf$x?z_u-AVJ(!}}PeG+3cCH-SASZ4U5^u>fo1DHHbvU_&d$HV`z zwY=O!55{R7i00AL`Ax_-;{0QDISAKh?o)?+pM_Rz!d)meT@^1-d})+xxwv&Wo}A6 z+_8nK&NT4dA5RQ>*2O{tvP=}9KfqbQ!!>~88=Ct3yxL882ok-P3O#*nS~G8YC00?4 zs=#c)ke&jPpF`h1q1vcwKjfS{tQS<-eE|8_kg7Re+X4J`3JMz0w8IZB!s5NweTb4i z%EG!2BMy(`9}J%fd0kd|nBJaol)X_86-gW^{#Ix7{-hZhg-rU~5O_scvNZsx9n&f$ zAccM<3tS0d$3}ECSUcOnCJesQeXn*s|wN)eme5a80IH=0YRy4F zT}*RD6b`R-{O`0>Yy`Wop6st^`DgQ!8u}9f-^nufC=|A5rD0)#9*M+$Qx4$yq#-)B za93XJmHm(SBZdiG&VN9JDQ9|V|5n>xqzb2Y1EO}$I|fixXArS|%d zo6R))Aa_pb(WLBHdhg*XKs(XROS=UO`EY%%w$!z=TM z;T|U6QP#RfBGLvRamTa$Aoi98-Ncd@hzet|NsVsC8v>Qj2#{Ss86R&wxZ$Ka_#GwA zNw$%(kf*hb-TCv@QWt%I{u<>am-OK6C%GqVZmM3Ci$>1;NLhF}|1p&B6iu=LrWKe8 z!u8J`t{TBP`(2i|M$1gcJLFo}!HFu||GuP9V0Z*^(3l@auj@U`YW6M-I54}yv~UL( z%Np!*p%iBw+0Zjeyr4hG570cD6p*H2l{m)d<YE7ci$v@e%{}T z?GgV$=r?xx`nCtS;$T&SX~^_WAv>ADDSiMX%R=}MaQdYYw+ZhQknb(Z?XgXhKZDx* zYJ4+}BAeLo)9vokqETt%RVzZinB=##*HfCm>gLTd3C#MM0D$zX> z6tWqm@pf+Mrl4$f0Q;LJ(9$BeZ!G=FulIx1=DZ`&@fGt^RaoK>xIHbAj;wWkMQpC4 z_=(BJH22%!Bd=-=Cu!Z?{?od(8<;~JFVlPrSmK#=nXW7qQ!-(Aa$Cwi$N19P_r;2l z*5*q$8=`Pr{{BQI990uEwtD%&oB@Cz0->hu>agmJHi$_LpFmk)Ao{={j(*^o3`r*7 zA>ULCf1jp4_8&9J7T{)+Yb3*e4}SyhcH9yAsFU33p*|8 zNVBVoT=AQw&p7c9K?{~_;RbxzezqF`{z*}r)9gcyO`mdqjs+qjhtaWqy>y|gAl8zX z^bwGjEzD=7j|Bx#A9nx1!}jo=awPwHr!c8B#oPhYxX=8Y%w>x6<<3(=Xzj*VC5=Bg zFwb|qVARZf=$CWKRI*a5wE2Z~^ou8fZzeUV%$`}6RndPX1Kh6B z=nn=VAeN8BPeT{13DcUM^n9V}%t(E&e028|EW$d9(~P%}0)l7|WiUQ=Y~wZN=`3;O)r&pF68jBtcPnfRkHW`(0&}?)WmAt z`-n%&<1qPGUbx$Ep0Hqxl;8Aq_r*={M-GG+Nf5I*ID3A1Q}}APp-jMy%3mBrjE?Hk z3cCp){VIi3W&oypNA{^qy0|gJb&-~9(Xs`9aAem?44ktzlIxRc2mezv5!2`2G#fPe zxaJk~&GZ(#YW0%c1J7i=x>UA#=?XlUN%Z_RYzZFtFujw?NXE3g z^XM0l6Hm|Ys4-LitlXIwUk`_o^;!m|lrdK)D|k`*>YV6YbwLa%1f_Yb?=YJ*IceD! ze^;dp`Qmb}f*KG=fIlGoM>NiJd%CzHVLOg;(}#$n?9m2?6HbxHc2O8_YADkr1?eU4 z0x+~eSb-;0=zVKqO^Oxj>+7!T?8X_`PW4?MjwbP#^;`zrXMR7n&9*c^h40aqL7IMO z?>P*$JAgKac5+gn29*x9x{0ZQb3+yTxwBq_6hcY>>2wrkY`;Y3V{!27zBcWU8c$Ed z;?LaQv>~n1c+Nd=f&+F&f5ZD-&r%dkW(_BKQGJ6#e+V4b?vaWnSSb+csRH18>bz7QH9U6v>Idf&h_BE@8q#ddSZ1C;fxLn$KX zU%tDiNsVhOYkqqdSvchig=Ug=L@dD|SMD@if`&N;&BePXzjNE1iodekk{SR!6-k!J zt||%%x}3%wRT}g$B!uIueKT?MIVDup{ z2&uEz;mZ8L8pm(K83L7bID7OM>F*~?_Pr9G?`at`OJW# z`0^*TEAC3ka`CAh$1~f}JPe~V6LE2?lgMA(l&^)!Q0)GDoQ>fMsbAN`x`v@_o~S5s|HJjqf^MF6_N^8gVJLh4Rh(e0~Y2Ww4_qj^$+)1Akm z_WLol9}l(UADkX}B8qf@u8Zs42dsbHILvUng;Wz8hse<8<8!SOVM< zpZ|OAt>92edJzWF;_xju1a~1#miH7_v8(g@~u>9`+8O@rW)nH-EJ&(oFxN6 z-f2E!{&2gu^I~Sj9Flo-Zsb2ntc;>E(Eww7O^#5b+6#t%?dsJ#WlM_KqYIHf7FdI7 zIlx8U!TQMhGEJTx1a~>V8k;Q|SfqCz-W`0#XNGF`Wsf-5(+7xB3?o5_l9H3aqGb_ekAjhzv6&rI1&`U?U-)ijm`~#qU zmuZ~?>#J$?-HfYq?r6&I3C7zQj)?tFfHR&(v1D#I5ZMQb#P@PKAaSBE=8*!5_xbw` zeDG0Xom_-p=s#Wby^;r63!woA!7h|=2SlrB6$^0UMpg3I!$_7+XdguCw!CKA?GfFy zvoZ@OagG{5W4n@ocR^rEBFT9wI6lVhmLN$ep*M_VuY1at_Y8Of-wuDjj8rC%n@VEU ze`s(1^@l$ky_7IxIwlG?A}I`wP1AX-4Tme2L}hX?yy7(8`RFb?Py0aS4)S|pk1Z7N z4}8yub`}1$C6KjfC#s=(XG5Y8_Y)pc;Lw)L8j`CEVS?(*9`DNAZ-}C=dp{X}z{#9) za!@-2^b2I|D6gKr7p_Ih(bfCm(;q+wW`*x}OjUHt)}(;%#SrJua1Up0P?LJlWEJ^O z!Ks@N*C`Sm`28DiK7*P$K&GAZl_4AQ!;#$K6&NT*7mQj;qbC*DBdK!U*((E;58PeFM4K^81-8`S8|lK{qq)G&bzDvXEqO#^$uzD+BSumVlZ+ zIzR{!$1ZZw=;=rHk}eG?;z&gH2sfWopHm*#Z%a8Cp@<&fB+880y~EUt^8#~xUx~GW z#ea9-Ze9`D8sU4qGk@PG7{464rRm2<`oK7yA(J{!FKtPsnVWZ4sguICU(Cw?w?$rW z-_Sk5&ksvFW9w|t_0?gR#*WM8`I#y@KLj%C#xRRK=VC=M!B;QHCxCLC|D)uezNs6% zSBYDWCs=;e%Mv6t*^va3tNdnr{@V(fNr4ajIah)#U(8p2 z0E>gr6gUQbAxO1SHPwQfXiH1)M9PY>_?iN<&tz+6zQF7aLk`ew~T3SU8UzXdb zToJ0CeTq=OA+~&ADcV!rDW9y?$1FVD6} z-%lCQZW@Ov7q0mo_P)2oHc($S>dn6lB7#GCoC5iKyo|u3t;N{C!fQhAtQH9yXS8_@ zGfCRTcATY!A{{o>U)J~tIoUNg>J7+Hw@p(~T^ix%g$zK1cQz(p^_VSp&ZX0Odr&aKmg z%gI7vD6%9`QpBHwh5=iDnr%pQ-V(SwdA{2WaiM$(a3obV9ZoS?O+)(~GTMkJgc{XT zI6^OMCySC=DTACgOWp@WD<}0-xBEEvHd{G=DlSj|{4w=;aft=0N$B|US01J7nA}lIoBz)O zu=8ti*$f9fY5V7}TsGuXqX!oLtiBVu-h4B7k$2#u1tIBP@;uFMhWyfB;- zBN_Ui0LVtvtIm^lr%Mw;i%dxI)v__{JpuEeTS@7Ib-DIpsHanb6|cA@ z>TCwDO-Plx7AMFfpmUAOutX2&Q>q55-m>ufXJ=4OAf_?Y1fh89KHvfzZvKl-9+rRw zXzZEHBUYF)J-R}9)YPCU(Q=9An0aCf%rD6)fIALo&liFhY8QbfN6H0ug)yW36pd?N za4Uu^%Y8^&L4BUV6Il%x(xsghSmO^V zv5Ndx^w>QUA!d{dNeRjv<;bObDR9s3@PncE(UpE&^jkkvP#Ca5X|8z1&@kAab)903 zdeXo?Q)#kHiPIT$VnQ+fW>EuHy-2q=vKOu7%` zsmSJm$_(y=(9<&EwkKc2gzKQ2-oBGDYQ@v~mg?q^+dP23T$u~_yQiz%x%t4|3p66 zR)Kj>_=zBAe%uiy^S?!zi^2qfDQ*Q*`MNVx+_>OrenXI_RiE>9UVB2n2UO)f1t!;d zLA=Z8oi)_ieV`Ql6ab^-?@oDZrZUef8c`$+^{;XQ5oT$mV{F|$hE+D4Hf+V}L)_5W zJ96@%3!0ns*2`miGXyV7%>zxmQ#d%Q%gVHe@&woio`(KeO=8zKrPV4S-c)+?v!Q0M zka;hUDeP&#Q0sgLEA5Br{tyhA!=%Ke#uOTpcAZw2Tlm`lrn`z2%KPlfl-2Jf%DTQWboU*9Fy@6 zsi;b2J{lr%?w8C(U|*HYLyIy`r4?`)uGS?ZKF_z&&WnO4Vw|AQcXWaUdoP>)gG6tb z&RjUekNFP-psC!js7Pp_eL^j^0X6ZPvoKIbV7cHgRLfFNjrwvHDqGT0h1Tt!D6YO` zvIvoBMP#(*O~VAGw+^=4I=4`EF|o$ZTNK%&Yt~$_IoMYXsangAyfM0Ir4xSoY&nx- zT#o%;-!6fGi{c@k(?Pun_Zp$x^EOV$S!|-k;4I@*GWZ_M!QY15sX>3$E><6MaMvQ! z!7#ThFM{Ks=Q4ELi)=xef-Z`-c>dtx0|hV)Y}lVuRFKmnt6GvYF6iW-4f%@LSx?o= zdwd(UHo8w*-NnBDgQL7N;nYMyzT1@V%X2zK%%Al_DSu%Ze{wA=Z&+d0)skJk+5d+^ zcY~D)xve)d*+71q2~7c=dzhD|9);rGOY+^+FqtaDza^ToM_BPO{vSl&1D9N0eagRWyPu7L=2@TA`R^0= zN_?k70y`NA(Jp~pi^#^OSf4cMlI2}~z>SIDV><}chope}eBG&jZ0_V09H=ei z5a)eh1tuwYMrO1*x6-~rE|Mx-Tm;;?>EjOHK_uE-hVD;Es!#csJ&Lv zS~O+Uk2ETOpAyD9%8hRPY}>#Anl0e^iaNeWVv?_3fR{E(t}vfED?DFGlHWq!0^k>L zqc`dgYro*4X5V)AylK^=LKMBv!wxs)v4X@%&|h7u6Rccg*-nc|kSo3zF19srV#Hu6 z0SWi*0tO{kL9@YfH8@~q!EGPC{0xD%b4xu9MSeB@rU9kdkGyALYDW@B0mtdw?QB{6 z3OmsB!I*aR8*+ZT!ZV=aw-$JL@$cj}_z7l~ixho)5NJX$TY<^*ZSNrvT9V4y(=`q{ zd)beRfwYBolRqmjC1ezER7q$d>X4-BfzziYlNMfC7>t)3)3=ldr19qGeV`GsD8wX*oPR5aelX?d@3$j$mQ{W zZiZ^B!4)s5Nrx;T^PP{Fzcy~q=Y3x-Tu*9^N}e)IP3lFvc87}f7ky2WiN zva&9zi`8yhbC(w`1%iad9-%5JpXS2F5#_N(#Yc>gGuzGIw5eC@pyOK9EKGmk8RjVh zhm#^zpR+>&?+b#e5pIA{K`la!(Jw8JZ||t0@EFp>!KC-y?~dvg1@=I62ctWY5S5$u zpK1g*h}zyeILC2Q=593#HlnLUp~~clJXF^x93=z^P}g|+%}^jAfiq>EVsj)v-iM16 zKK%<@;XMs}1&B(}FRTC7b(srAj)c0eUzZvKAR%07BcNl;NJrrP2Co6A2FYo`(|*(Fab^43ONCCvV!-wt;!^UN~IW@ z5&iMLrjHFZW_Ek%GnZ87K17Jmc5{R3_{GmCa1l(&kV;$gfn#K2?rLWD zt4MZ~b`B`Jv${W{;M!VQWI&%4(r{S0sazX1!?BAfHOQ`txn{bb^1!aC5PjK$BsL=x zh(zD~PHl?51ol`%yGjJQ*PlP->UnZIPn24>S~;6c1ZSV7Ie;&Xt{6r=8IxB8eh9(( zpc;i&s8;{EsQ*UUfbOF6qTuKc1uS`sgA4_`Ti zr!(Q)7D~iNg6m@-jIR%v6$^e`;!`YHk5L#!g$~$jagksfa21fx)`2`%C?aLKs~*bY zA>YR2S{5~{Bi0>jS6^Nr?SYN8!Rqrk1qHG}`7G%K^FH@tMN@6#blrM$pOv7~+DAk5 zEj(Q)Tj&~lVp#%BJy0hRY9$1S<>2fsbmiHf>}!jAg#q4wT|-4M{M&DJT<(|27p@bBp9GXl?I2-9P-0gX362DVG$Zy2RKS|9l!p0;j^hT zgnW`A9rl3nQ;rA2Zu@!J{AM?Y|K=p0=fbT;P}Bsns^N5qJTA9VJHn4{*+VYvb#}*M zR^^VN4(+9Fn*IaAoHp`Ku-uB~shqV8Gmbmrc=S9+} zq;|*|NPC)6D8JdpzScwQ_RLBDuuQPox=iLsS!>oi5`?!qVH_{aZls5X#l}!wcM>*7 zYaa#KaMS{PD+F_q$I+{15N_u494D|HDNrYKh`G3H!%2Uw>!XK(7)a7DKjq z-lZJ;^DdXWuv3<`v^H$d`4x+XNpZ*oqiO+leCXbrk#NXSJc@;rnfhgMlD4t&PP8G> z4tlCe{A0vkmn(ui9BNyis@}&>J;?>5!ym1#xAFjY$mA8@qL5e5!lf)D){9hdM~(6G z03}FDljrQAL}333M>Jqxzui_V66v*aasknEs8%i0Rj}vM_fY75v@y1)4x`Lvv(T8)f^$qx+X|-RGsX4IJkT%t#(o=pFx~+Yk5jqJcOI}Z@I^frQ-*Wjo_A@v+w7D))T2CDz?$V#pG3;HvSaFmfQ7*0O^n*h+qGYKdLTsG|ky_=0gk8^4*zQIuK*Z;qqG3mqZ{2y@i5 z6r@K~$PMgNZhO1!rpw5Kz#~i%DVWC5ieuGXni~?*1K+Ls-~HB+h8qe|QI>k-ubL4r zc20SLiz}Kfn43bq1%xFGb=lO9i;8Ds7>*m+Tg|+lqp!4376)n%Ad8J(Q1JK}Z!uQ8 zBOPcC%GKI~x#6`myoIn|gZj%LdD> z_jpo^Ni3U?{rc*PTq&qs+RQwUylCP2dI(j?%oc&8q^r?19R1om);Bx}g7KWx@iw`~ zSbu#*uMu2hA2$QmIz1=ufBK=SFu74IO_lAwc*IO4DfqFUb;rbsl!39Ic&qn9HiG4W zOa&{TeA|_P=e?(optt30UYDkUY#fOt5i2dQoGJMr_NC9y*P!Djil#ly^WHmfZNv>j z9ylRGbB{u1X^>~CSkHxF)T&NVV+aabm|yuyGy^6{s6h)rAKnhKwH(|@jwFfNj2GQ{ z4d@FzZFuInKs94C+^%Pm+8kNgC-?zer8h`$(hY5Uv>{Q9#z}#6jPOfo`_5=>lM$>e zl@Bfau3%|8(xo9TxD&)md3~qH2+& zoXAClax_Nk#P~Ohez6@~CX$1fQ3>)iylA*OmTuqFp)MbAq3E}RbK`Ge?Og>!Jo#Q5 zix=F>Y%o&nHl=(0hkMWX3MMEI%G+#pJOAYg_aat<+c~Ll8o;wmv)QX3`Z6NE&MaMm zh$qRPFT;wG=lNwpAt{_utlkucIrSbtv6Ouw04%tp|Iy1Y_<<;fcPYQqrBBb}gB<4% zBwT)s&bLJ`?w5qiS{lo4jU~#0YYK=XFMC{Y?V2&_dXkvS)i-yixZfn)w~$}^Vbw4* z^5fn0UBElC;^7za(5X?rXy!KEGhAAOmWx`yDZ2Q33z}7YHp?8=jNIfL~#I9`UwP8%Hn>q z%I3d{?ev-v^-NIv!=`g1`zrDtWsk*FMY{bZ(_TH)*Zjp?+aP+TyMXo!u?y5SK zc~QKFDHFiv(X$8;Nsi2pyFP9H{H1QO3&}y#2f}DrVKcafkafp&9t7d}qfyt24v75r zx;^Je=3u|+=qd!eBZj9Id$r)8V_Y6L?>FYxOc=}Xx)~ehS z%7nZYYg9@etQE;HF3BxUy-uM0lJwnoXh-jKzg;~=fxntWyA=EYto{(K`m!|qBjgL= z-RG6#O(8pA-2laww()aSzsez5fW}`>#5k#$&kx%lqc?4mgel|qQ^yzs zuT*F*!L*~QXbVxQ*y`j*Xyy^M08Llob|?zaT(mwYZIjOV9j#6pAvo!?0l~!tfoW(t za$gIra9iUEWU^qS2$G;%ieZ2Xk;K4jl)41U#>$wVHqG3Jv@n`4zN7VWKu07c^{2a! zjeL#zSS{`mUtfqLEg>~DQPDuzKR8K?OOXor%3rEnL=}j#`%NGzWY)X!m2kg#(g+k& zg}sV=N37LMp4^L3BfaAs6cPcvX3Y!@3qDA~scGwZ&kv5>2Z-D0Zku_aL#1 zb*um|c5tPXj})8UjM{v~{>?Fa-yDMs-l>ZOIe2Pg?2E3{oZOj^`|$Iv7+w+G&fOfU zogS*K7rsgT9@sueavA$VpFg@Y^u%F18oc|Db|*+ijm($ZoE!jbS1eaBsgRO6P)E)A zpen^d9jKua6|o@6w3?SIi*>?;w9)Lc5LS zpgb^ats0snP19b=*K*oF=7R*8fkPy>%oDZUX`mQuEzZR0Q98 zl)ZhXi4rdHp~yEu)C5x0rNbA>5oKEML!pWQ9+u%Qh)AHKcGJ6!Iq!Kfy)M5+ZtHFv zMjnGssM33{LI1h94dFn@Exw+2S1!aUf0oM&{Jq-;4h4F9{(~y{;d{|fpux=unoBdJ z+K8jsVbv46fXmvAN)FX0j1`dwK;T-yyatL3f@t)Rx8qst!AG|O3eLay*>1F<`C-Gb z+(qBQQN0#pnteO8JQQW6PTRKK4h}{+fyK)W7ngm_Uknys-Q~^tzAE>!;l3{t5(@~p{?9I`}dcFSts8u$c? zOdHVD?T~|uvx)5Tk6R+30daLChOw@c9+q5m{r2cI@esJtZavA-x`N~@yp;caY_YPc z8g!v@Tt2&c8BV@R!3bdBr}tO&ZV`+kq<|ODFVZ-Jl6`#H(v)Yh&$bUpZ0OY;XhG=< zR7SI+ZNZz?z9I!Lk2uyc;(-*r(UJ{Xma6+xch~f!)%Al*E;^yQgzOxbd<;>mG`BD4 z^<;hnGUkyfCbmCADzMpbZM5Ev&qr|M`k9Uv36_j?}DPP#2Z*o1j~^6OkUmHY<)f0 z9H(IcaQ3 zj8clRHfW8v{T@i*`3KiPh9~kLz-yd|sQy{G~MzuDncC61Mjwc0m{UPte;(~r2-hxfopedqq0 zW?P?46M4l#gO0)d@9Gc7`2YI2-vHW+xMAQ3Sjr6_xb6D&5RHCh zscbJw&{ZX((sRD=zpBhjR;vh)i2B59^dCOE{4Z~xMZq`Z|NHyO=I;Sw)Mxz~Jr#M{ zL0LTR`z(<7K04iBht5Flrh1?mc6Xs{>{^lR4z=STwnL6AN?ifDZ8Cy%$$MgdR=bg| zK{UvPa$gB#g`<6CsXEZ=ccd}xQ-KU6V$uRuU3V-94t~_jp#a^r!1&1mb*K`(>EUQ@ z{72)&jH4^#p4@Tuo&!_jbCVjM2!q3}zw;l-THOf%%0lg%=zV*QGz&Z54W}p{6@JbL zPKCUF>>~GomrXI^beFx$8@uB^>ZcRtI)&HHU9YOfe00^pM(jr=1r+CAF#|tArTiOB zXuW##94Arw+COP)Vh3y{o|?-opMk8VLs1c5Lsss3si z>mD85v#iE$==ceVP~+}}qO%}qQ8cZ28k7xSA3)kn08WE3u3_nnmz=gb8zDsP-T&H8 zZp*-Z@%YJJ^M51-A{#Cc6`7GU{#l83&5~tTwRvNhb=4#S=t|*r-6KME#^i5E2XWT0 z(lg+kEtO)q5aE}L5hL79@C2Wjt`JEx{&L}0BfW|qu8ylq7dbRHmsyYb;V+->rw8Zo z_T(=MN!Yal8;P7ZVM-a^$If-`*u?_JID7Lx(EoMk+@vco)H{B%=)XYcvj^OCcan2H zz*4Ej8*D9;^JyM%37q>ouMtdJeLBkO8ax|Zo+AjX{>EImlb}r+`Iv!$0UQOTpU&Kv z>_PnTD1037Oy%>%yel0M07?<6_YWST?~-lQziMuWs|4Vuq6Zq(b8dGGA3>xNfH3Ge zjdPkf`F^(%p-0PIT?}oT-kNUztTI0 zk8MZ!X2crB=AH5bmCZ9ogC+&_cHQ%vk8M9^>Q7Dcix7EUAal!xGBN_Q8$xi?cnTP< z)f3iP0pR~S0&bv#q2P)a0pO&>bI_@5=jrfryJfo;JyVxzFrxit7D8uf!hh6rYGAngcdGGBLC1KE!sjuyk?|C(>|Hjd9^o`u+q>|<2rlxe%W?=as*AkBDw5>z#; zeKo1#BgS(>@T)P@!FgnOh-H!?6RfxIa@V`&^KlDKS2J;R89e>RDCB$iv0^Hp+9QYI zgh-7lTW#i(1B3An=pGRWg{Ijj-Q<7&5RlJlL3VvNGcs!5lfn!h$Kl60=-3yaHcIv5 zP;*Vc=~DG6XFX!{rIx+=uv&6KHqu^vbZR;JGm36PWLJ}^!$$ z3-Zu8QaYnF;IH+7(K$xJIZ6-*dl}SNxv>l!S}K(1+@O_9s(;FxMjxdlH#4{~vWZ@_~Kwf*=cGeL$r$xXC6lpx|XrF1f- zcjP0w{^vPK)-}AU1)S%Be1P;swJzR=`7!>3;-NT`118o&%I?8Tg7RxjazD zW)w3B53hUWiej?#j|4z%;Wx3Y{}LG=>$O-{hy~3UaDo=1>Ds{vk%w;zqK^Dk4NuON zv6KTk<*^^(ob7>~M=lO#r@q`IIuD*evDne)>AJi}|K(;q zf0r*0@0Wi|c8E4*HqIFa(qx|_os|26{3{S{FXh=&OWw2>jjIK{Mj|7AG$VKy`35fg z)R{m&g+2zP_H9G!2G8K$D2E21bLppoan=qH#p_>?&fI+N9s?Wc-6p_i`kuX+-+2V5 zR?XR=6p>2S2&R;zctJ{a!5Tu*KXPOHX)W8{A?i8Z0tm!cF2?sjxhT zS`MB+UW_lc*iuXg(~Iv^=Wz1+!A-{=Omf@{W!(D@8GVvU6MHf64jwXeG9woOlB#41cvc232|={E0a6(R|7Y7SPKzS&}<2s(L%{ zH6vWRJCgnInvO*f>O_I5gvQ2=@&f-e;i=6AE5A$T!k7gDEDaRfrTe?95Tm#9WM1z9 z_lfn1^8w8Q7^k&?CK0kfmcGfnaCU{+)#X+FTESl`9o1^R6S#Tsr{-$*m&7k9vpM=R ziTi3s>s);x=yC`CcYgPa0(?JrF`NQr$&j4wyN?h;3$LU>U`yYOYcpmK{{i%H+hnZC z174!J0+~1E=($H)74{fE=hD5;?E@I?U8L7+eIfAzO7t&2MH4)S4t-8@J2~R+8VOZk zM?%%$I&mRUT0s1<05Ch9qqcoqLGKf9$226$yWwrqdT-zfpSh_@7&lED+H*slCB6$) zDUubf`9ml`xKZlY)6@&#E(J#8!veEEFk2=*$n8>W6KD_6a%!Ou{veY!hZS8t=Qq5iad`eKsgHXRxFs$IX&wI1l1)((LF^uGIwZV?>`x1Wl}}Qcg>t=X zzPRQ;CgdkPh_Te6cC7U7b5J+O;xS2*Ozd0~NCfpn;u0t+o*CeUh*E%16SLar(2!yxF^=dZc|13A~gI z4+6t$Pj&*316m=@T{kQD%lKlaq#>8*%~$WR{ETTVb?=Vb9Bq;BHwUMu6VGJjLR}9f zohI@k>5*;f>{_<2qZ>9~Rc$h?2(;2r#D636D5wTgbxKFhC@I%=6^MxO^PYER7-q4a zi&8sFSs^1`hbXevcwciV+MpLbdIuifsQ#~%@;iwz*Ch5Ad1rSM(xqP%OUe0f4EiS|a^fx7D00RcLg2_}Ob#YW~Sb$sjbg@0!K#5px z85M;mtt__)(no}2Jca)~m`zO-CHkGTNQ9PCcK%vF1+rp`<&=*h9`j%-54}XAf2j63 zI(_#beC`}$p@(t<)qx`0NwZx(N>dQ=t7N$PqcFy~?t*DgFFaDfWoC5=RyoY!{z0%M zwnFD!`9OZt;ct|APq>j_Bs=jxG@bcBRPX=B?{j9e@B5O8${s~Xm?FY+x}Mjo9us|W z$3X1^c$3*Ec_a`Pkta%S^d17a?(9xeCBv-Y=FroY2hvcg9@yKWz>^~PIsuu#e6jgs zb;H!h41LM|?CG*6hcX6YVtCN>$SIap7P4A)h>N24J_Smp);*F0IdH#FQ8pKG6&AB+ zXhlQ490RH}?HoKgHhl1}%}WuoBtchX6m2ZW28TpKm_1j+{>>j#K;rwa$p*dTZG=Yx<-}UWhfP*6Jse|xD^a(1oqltCvgI~%p zM@!K@g~R>eC^xrD`%L$7&H+AU17%NjeTuqH2A4&vmP7ehy|W<5`|SsIpZYJ>0Zn>D zxlW=9zxmAuJSDJRa@}GgdRnnCjCv!q9idxC>tq z%x~>re!=xg>r2&^GY_8hz{$T}RpJADzW1tMg>_5UE*$hhybJ#A<1W%~F#aAo0~nic z)bfX(3=0uqnqiEInFfRU_6T6eS{dPVK{n~)kFwn1t|uLEoHaW45u4?RD4MEg^-q$R zk=Me=+x9qbD+3dEH_7*K=4u>?z1XOo+xXh7UKx{=w^)}Xjywdu5T^f2M@%!-Gmrc< zgi~6-h+Td9%G;$0Op4{eySfhh7{3bncn!)!KSZO)Em))z2Zs##IJcGj3^+W|gpdw% zkssZSapVvoa>c^!Hx>kk#f?^O$1C$1E{x`iyG6ft`n@v!O&Je#U=Mmy`JZUW zOLN4K3sEa9WcOj1$9v$x!(5R{-}bEwk@r3py>FyU?-dpyy+G3nwwr)qkQ| zSS^D%3!bE(_|z~A;}xn3F&gbbph~+-H9Md5qx`GIxf92>eq)itaB92i^Z@KfU&%F4Q{17qvu=iS_uo zSbFIN!5)2|zjx_I-YoLpao`Le(j^+em%A5(;!mQ36SjX&SP{kC*dGPRc~JLL5p#L^ zblN3|xKn#BaEjCVRyTCn@HtbHEf@iQA~?`(z2tc_o6j2QpicxAxX(Az<|X}Q7V z+Jn6FA-lNs)z3k_`yOD-6^JyTKK|(ECMU9lU~kmB0Dl7IIP$PD5ggkci5^s8+LZ<#1UZ5k!L191fN3mR{Vj|J`ZuN*vi)1;;9-(}Mk_oFBu zABYu3CgK+7V9A>_ir`TPS=J-xg$yGxEND$0tQPA`xd?tt7teG|7vGA?xp?X@>|J+4 z6e!(L*aM`(pz_oW%!QQPRlnPMGJjfzyhP`hZg+d#9N!L~z;oOM%cq^l!ZN<#T7X9T zv7$3?`?)8_qxbCz8{ZPvOQ9T&_Naevcg zkyoQ&H(Czp+63(pm5O!BPLJP8{x2>HvFtI7J(#<10`n%G|9;tt{dP2LU_iEMkeZRuMvfFQ?Vjv+QR+imD|%!}Oxy+dEQ$bN4v z526LqNxtCw=bZ>iAo}Sq9?6nnvVuW;x}2tMBHK-k2I%&W`}O$-=S2wF=%JV5_4Ly8wCzw|^7q@$tg z7lBb%az7m2WG3dPYCjdbWQ+n|KjJ$MsxU`_6{xnz)!QHjfAePscT9L@1lk zgSyu*6!=pAyx3^`N(zNnjSjyS^M$MfC~D%gH1xXN`76AL1O1+K_ue0j-w$q{-=KyL z#jK&|uUV&*uVznkn$(^IZqM6sMf@oOL_MMkTuivsGI+!}8SaP-Sr>t?OXgzI((CC)t!&C3?UP!6D`EBSB6Hwrc) z2bXGlhxd774G37|jz1=(T5YTCryz9y3z$<=n-8sEjyyh&UO#?&;wX7Re-Mx}$XVn> zoYWcEZBx3ge&^wlJx2c+Q3Ii^lgh2>>xL zSs&_WOz(lb(68URmHXkbx3QjT+&l4^tBDg4=OJ4u3V zjxQGyrSaZJr3=92_Ztr#k@x>~7?ap$SPBJ4zRYe1Sxfo@(xU>L8j1UYt{!RUW!t#$ zEoi&c=P-+X^DD|Oo|levj(p!3^s_vMl^8$p?GP8<&M&pIliPmfoGiJarS8gbH9YpZ zR+lIrhhrKg;l1Z0CO|8z@wlfxBMg*d?8vf#-VcBx!~@fsxz}Lu=^lZ6+ee}SsPNH*Xt!l2(WHNFUEI%q3?e6T^>1tz6PiyU-9oOoSAQMbcDP!4*7)UmA@~?nWVhD zqE|WZ^W=9b-%N@U`-}-4n)gL}(Zt!gQ(zhmgplEj?l%oH=48A@*(Ty&n zSH*9QkKTLCeF*y^+j3jAIMM{t+rRw8IGWI(j)551b`LMO2xNc5BGTX)kbRG*P;OW| zh11Yc^hbDd-h?WoE7_{QWudfvaE-c{x$cdNCAnJ3@K%Xu==y35bE!sabpF|ns1i#!@96yB^b|{U;4tv8mH>i zG;ow78UQ$-F2On0Udf$H92)v5^@>wb>Ryc61oy5DA;T;8;_dz}s(ja9xaG6R8AaQH z`nf_^KuJKO__QsCDeSuc#FokwP&2ZZ)EMtIKTk&-an`CRgxhA55&9I>nlBWh0xF8kYfHoi1 zG`5YhZ3!(4_gqsX;FT`&@#bNd14uh5LW+fC(>K*=zwgRW)XdRakD)FhTfbXoSGl!m zDEDIRaW%%-l3}k@o!XFGvH*?-M}Hei2A+G@zHW|Ofr>+JMItTz1n*T@8Dv2mP~5|R zQ%m7S)#5^YiLC|lyM{@W&YPTq$ae1{uG~ZS&OOlpl%=?s8$7puQ6KTtW(t86sM9hF zecm{x&DUcN+R5DgVQO79Sy00Z_iItdM8S*<`Lf<1#Js6UiG$^04I@ZK^g;4Izw6^Ij`XN@waPx2t1pzYr6r<*DY zE^YM!OC)A+6*Zo{{qupMa!6^fjTnD{w180ba`bc6*%x2FfBq|Jj;Nq1 zcIN6%1-C?s+}=)&JG+ymvI`dGdC!1C`9G+U*2qVrthN5EYt88{KZRLvzHr> zH-|NRL+5@mZS=13OZ7%#X^_?TS9(6nUAD8_FOV49^PM77(ZF_vj6sx(X5yP2nj_CK-SlE^zmT@2eB zC&l1R>+dbT`iInb`N7K5zktV-M@7RMe+YrO5!@)--cU(Id+%d*ZujE1BS<;x03ZH_ zT>f0kuM@^#uECRb?>xlwkE*1etn^N7xHb-=B1Fxf<%q90rUK>$5N1%0CBDhKD zk&GEnwLq#j^&!AaF7mzn0F(R$X1M)9TQom_%jMH6XM@F|fV=fVC~2})D;DnJ3Tj48 z7P@i38zw(yoeon)+SwPfM-B_bUK?$!{aa+*v_P#a7NR?-082i!`r_&D2-}WFnD2B2eL4m(n$I4*Tvka4qxOx6MQ|`WFtfhq1^VzvamKtS2fA0Q4nJQc1z9Id2$av*CqbNRmWWFIn}nt-xZ}tL@TNe#8xKNVjR;p%ZJj! z1?$&sR79%V3Wn?UhJrl@+a z4^xK0r&^{p2Y`G90n0%(I~v{)kjFf)9+uLf)=Wss1@1R;Qbl)5^pnwSY05BZ_=lb8 z{9$XlR7}UC`N1aW%OPYH)Hl29fMn*gN~3Mv?Q^>Zd#he%;Em}Qu7W$2*f~j7Kf7M}ornH7l~W6k|Zn2P9||D$#V6qPb!mGP#M|`zv9Q*#2Om$7v{~%Qe-oH5-ql_Av=Xc*M zL=T=$*|En2v~l=&97_KQ!J@6tJ1+PEVr!v4a8nVaWl5)6MSjlDI=`miUeouFuE7;d z9ozV6XvbOeg-MAj0~ZP1^%=_`X$J{6M|NUItk=-7Jb@&RALvvsFu+pQ-pbPM;%+(6 z*g)fmdy+_D?L^xXc%z4yz{WZtXdf4;cSA2}D9UsG2ZVf9VE2akK6QT=i@ zvl*3DMAvrgu`{JSfwYGTNfMt264-d5Ny}EAkEhLX-p_5{qf|LE=^sH1`Og=~9N6(f z0?`>ma=9);5}SPwmpn%&gHPSex*gKAO1gX}EhU;!P%$hjo#YMO=jG@pA&`X8(IARw zg^~)nd7IJm+u+6lgxEC^#K18J-fwmqT^vESX_G;5UIY5)k_c^+1MB9ucm(tYF%D`p zGM=I;i?n;Vs|fY1+i?J#=ADwszk4P|%KhkuPJ@y!E>$ zJOAGcP$k~46OHbey8?GL=m`l4#HH3fI#95wf3n3wz+(m+hANhE5Q3>#P-v&3_Y2N! zb|3Eh@zAy8ZbU-_z)kjdZiWiM@{;7 z5$LvL+|lc?fKh3;ojQ#JNAvg`5f3Nm@cD?Cnz`@G{ohbGZiEnpp^%`gDbSo2!X!8&7$Bk*WdC#r1BG@N$kWj zPIHiw^T$I)#rfn;)9+~^VCQyT?hn14l=HbT3q_}_>#RdFbpHGI-Axzq)_rica%+0- zbHUfp`w{Pil4cQ-+x{}T_wT(3AC?k^Yzn?eaI1>;#8kl*l5ZPo7GOI;+7|LsOpmW3 zt^kxSYh}d;sUt&&qyTR->+WHk&^2V19oXQ51Xq(*53r{RPu{ax1xh01D9E>IO+Olt zML33_geTK2e9na*q(!*{vSi3#aXomt6nkYms6-1N9ikcpQ($U{HwfdWvr*G!_AjaT z`5LTs1yytfYar>^n@(JCxnm6MAal?v$H7erW4$j&pAuAin|(1LjrZq|jpBvex;>-R zr%zd5Q@rXV7RrT!2|#FRAUC}N7rxWjt9xo94l1fgF`|0_MUKBg<6ir5~Nx?ii6b_WF9d{UMxZ7_DG|f15eR;C^~W zZI|pOVMqwifpK_XeQ!|B*IHD+y>dJgSNdeRV&?<*lKBf=e756I$WI8FwlSL_Eo1oZ zasiu9J|*#aUxN+a{r_;5R0Ci>kyC z^5%n>LR~9WuJd=@u`=|o9UYq4&pHC%#f_oAoL<{>!d<+gP71By2pw(X4bU7V&0!;y zr<`d`6H1;SAwR6k!S1w=vJ5xPV@suAGhUSzQ?It8))HB zVmuQ72VaOZeTls4<;$N(-kWp2v#?|zPNzp)ojx!3Ksm`MEhFW8^ByxVzWG+=$(jrEgi^Cld^>`f}u@9^+0Yps2r-_M+?~EH>Zpfs9TEDt_9t!U$6EpX_scy|E#DbGa zHWuDo5WTQC>n97^p~F8=wuTARFP(Iq5g}uMT=^qI~u#+fhUJLSM)ykZc==vqX{X-kKPwk%~C zdYTAi*o>Aj8zA2s`O&aY0x02_j(t^m$WExeA*k}4KUARkCrKN-k}e6^{eDkd%iA~9 zAcTos(GG#8b6BNUU?~BGc`HRWN!G*A@En$6v~cm&a%&?3(Yb*-t=kdJD_K2_ZX?p` zr&=5_bTRU~czs1s_TY#-uiVd{23~I+nd!=Bf_J4fKc*^SxRO!hJwx78M?_Qa{TYEJ z87HJb>Wk|(!jWpgC2_FTSXhVtfE%;zgYHPYGlQCNlv)!i-o#~PxC47jUweTvCLQ^1 zq3$fyY({>8Z#+)k7Qz4F9b|@P-ST^!FndlA)AJBWE?WK5z|n<)=y8fRmKMgC8p%_sL=4CmGYe=-~IQnm3r?Z3>3P)2_rE%6*wd`5D>+HBnUf) z7YCP_8@)rCt3eD@)jDoYQ#x&FJb$87fEi`Z7R+f_a^pkX-L5rLwJ~l8X7)yHi!P&M zQKk}4fySz_v~7&D?*y+igZMB)Y(c?8n+k5JC+N~9EzY1+aClU|Sc3dmEbazIS9p*V zz{#HjhN6Mr1(i#HX9x=Z4BSO@5Htd1!-aDfng>d6PHl8vOFl0oi>>aKnCzWEgZH*z z%3=EMQYl~oNiq8lllPyp?8&zkB!s|l+SwP{4?!Cj z$Uz)8=6adWULj8H9hq_9U#}?bek%1+->SkFSB}M7uX3I0AVPebr(B<8<#n?ZE0=-| z%CVR%juHdHaDB28w$%EKbPo!fm`?Q zOJF5;pxy)ZNx=#ofwX52E>T;tN#31e|INHa-0op?tH3C`iuX(1AwEb`kl2R%C_uRq zDYLl$58UjPf${c*@g4cx#*MCxwI3Y_`eT>2dHISE1kY=iHPxyq(QA3c9|&k9hrF z7L8Y_=%3})+YWyUx;J43L{=+cgS6GLQ_EiiK$*n?HU1fxayPwir0SRM^oGf17DHB)UpYZC#lMRwygT@o0 zke+Az^GqoY+@x5Z^Nj>RNo_J@ZH~VRi}&v`4BVPM$GTdxTrl18`uY`UFif8K_I2S7 zTtI$3U5tm~gc&?({q+4n>~0=Ic?r0K2yiO!n5`Fjh%X>tf*FAeG4y1Vg6Z8YW zzPZy^jyg~e2Y$+D6g9P`vp0gMCq+5fbmr5@gXF7W%b?;XV{=;1Hwh5w-clpmC%w|m(~ z+$HB9$w8Oz$5W|K9AUE!U^Y)(Hahs|`WaOmX9Xg!uEQQV^+Xp~;(_j|Am3||Wi5JX zh%gkri|kgAVmjMEQql?t@-hf6VY}W1M*+vUINF*y7|Z-}mWc9S`K14o8nVCRm<=jw z?qF^QnYi8-J9Q4`WJVCE~#VcGk_kYA1gt-`N8)AZl3JT>~-t^IQI!4xvZ>$BV8u< zR=!A$HR(nB3fb7!imr&tsFf&h&?*7#z(lYiDM~hA%Tq(rKM=S*w52&egx}&ZL(Fck z_ih1Au>>>Egy+vLRsX^avL3f^E`jtDQx~lR?f-#O-FX*wVJ24<_^>(a7lOs_&wikT zaiha$yDhtL5+q*_*hR$LOcmh*z0x>v-ki;AP4B`4cz>kcIy&z@%t5{wAsNdcf`4yXBKl(VTl4wGQ8&%1dSS;Px-Y>t^b&OKFGx=eF}gM81xt^K6(BO6F@+;)yl#3hZxU}gx{z3wn8t1_U>YvXoj;$ z0pRVYt0b584o=iP&^kiL-%Oz}&vY^4#w>q?bRTMfYK6ewvA^;8;A~4ojc?w(^y}#@ zx$VdeVFMuO#9Fg=Wu(B}?|F8BrIL{degSk$3#*CjAJx32pHzs(?VR~bDCeiYs8!Ga zao8R`N*^u=48Mfb_>kvd*1i87Y&%w=U!a|KU5#=rH6t5 z<{pT6U1X}R@}xE_F#vvN*&8VZecd~3FJh~NA$kR6AQH96@-xC#+$J!ZdW;j0PEj9heb>-}(%h|qbY zE>4ULExWT6Ddz@&DF4I?8$G?P4o%)flL>Ek?OqCfxt$IZ5@v4ItH69*@BS@8+FtY- z98<~@@%aY7^^-ylt3GN8eAo6L)=ZFStUGXy-I_PKfPSSB_5Fy+um8Iy$ z$#BVyQq)7isTtPwU|wK~so2TIz`&qWQf0MnFK>?>RM<}l)2Kui3YGqYx%90tH-r@D zds?Y}Tw#ft{l8}A$ptg0-RixazrbVH?rfVJaSg9k5q)?lh=-S;PjK4MCs89cJk{=*>Ug1G;eWLRM6RoiULPCJp?8@;b zuf}}~k^@>0CjyH+udT%}jOmXLcOI5r-}S7_tD&TZHdWVI_xE+}P3OD`y?9=i1!XQ; zzvOaI&@Sj4gGdBl!-EVZZvpd#k?ehFQ@Qa78ubADJQrdoimdN7-@0XzLK~pWOJL`Z z*;54XJSSQx>vG-7r$i|Fzj)OKQPy(n4sCQdR{ucGoF{%DZ)5~Xn5kMPpLgWx=i*Og z?}xy}(tSfpI~ZMIpu;a)gT%Tuxzj~fhVpyaPX!W`HU40vR%`CI;?b~{=nbNstq&o8 zbEFyf#s8qroFve*agd7#QlRsrLu{I=#CH+%69|HicB1B068}mb$R~(aa(ADLv#jVB z@@{?c`0+6B{xcb0>72@=P&1i}SqsTo7x;Oze9M1goxi-EJ8S(oFkcR25w!EZ>vcy2b^zsb>L0#j(NCqtp90RI=||LrtVO_)a#oiNtE|hZ1)3 zQa*{%e~Vy?bTr9VCwH0?jE)siB`DB)rEg*Un_IGit9x1g5~5I-1EEJ&z-H-cUY>vr zp?C0wHKDfK>wBzD;P|PK9oT+eQj&V-W{1z+`Wo5QwR_pbeSIVscz$u@1RAK!&KOHL z_ve1!%#ny{Olvj&rCl7qyuN;h;mVuz+gG&q$>Nsxf;-o>%)_9EXFgsC$KanK;{tAJkh>ujQVGU{AcSZv z|5|(3%+>P;F^pLdgyBnKJw-k{9#NL|m~zo3N)hFhBW>LQIdNN4m zMi2a{nBjk1Z*@ZBO&;Br9PU#QqZ*Ldho!mtWIV)4Mo&MF^w z?%_4ktK)C_CEl6Pgofy=TY8*8rXnQ|7x{+YV9S_3;MBg7ETH1ZdcRAZEWCQXHv&4k z$mCY9vx6uLbwN8o2jm-CSzhjXU8hBJ@cHSV4;1M?ul|&V+KL2@;%|t^lFkZ5Kl2V<7B8ovof)PLkFaFgX|v`X8>3KR!S3{x7(5<<}DE z*jEXe5M`;#uc>Pv)$vi@rcFMk*@BC_Hof?!OXsyqug1CJf#J!4jMh-1CJG? zARZ}%KT`6R=6v;}Y34Vveuw9%27OhXp;*ZpX zx)uey#1R|pX{L@A*Vtk5MGixqyArip2-j`$Bzl*}k7Z1J6eCJDWh_RT+6P=4)K7ul zi6g^W*F+H6rRR^Vev|B3LruI%R!hB?wL~~RK`we4Rk(2LJs7CdD<2(QSy}jxNSuW| zhgh+FbTOOnD|Ize;2ZR6a$ag8Y zqWMC|5>I7O|LVq>cPk4Ljax&@Tfz^}dxwETq0l^ZtG6y<5-&9Z#i8LBp@c~^)m6}5 zm7~ui%P`L$>hIlui`^5%Gd(^DAm=vMgpgC{(Qx1#bg(^tyD9>XlKQT1rhjL0^AZ!+ zMBxAqF!eo=Wi$LQhp?_^iY0R5#I&E|0$|&LR7k~Fe+ExajagD&oSff7XTV~X;7&L+ zlk$#TQzo*i(-;RuT3G)?|8l#hGY4Aggv9_Ug3#4RO3RDYmiK3exst5Fs?N9{>_x8<$yu#R zbB35Ik<`R&qbi5@pui$SGyBh8!Z997#yc_86v9IOb!9^&eHYv z>$NV0^MOy`3V#adZy><=da}PTMVEEpo^$OA^}s7Svccs;rC$Y7>h6aDef3plT(rzL z?rWb~+Dlk>eZB%YEfMTTJmu;P|HxgA09y(uf&hMShToP;@6+2i;QFH%)Q{|MhKLiQ zoPK_y7yn6r=6cmR98+#GGDvVsbW>{xLGb-g&LZ*P5i-(NVah*Iz(|>g3YO zCJ9TBI|ahz?_rnS$d6+g20;BGX3|6FZ7whsA>!cQ-Iw#kKsvDX)#?D_Cu+r`LhnLG z-*s;7zOm0kaJ!R;?eFdr6!u}3;5Cp4lFzOBMhus`hSwcYDY#7Ny*;Oz_8{*b8R+HR z-1Zl|eVI;61&s&k?@Fm}$B&e8x7Nr7jD3G}|ILiw@t{S6ZVZu3VgLJ*yum>%xFF-f zV_N?WU%>T&KVA@Wc8;|Xa)MWXu^wC`Sn3d_t<}@)(YhVE#C8x?bSvq z*cr0i;MFMfbU;z@}-jvnnq0Qro)bM0=8TMJFwdhzQlJ=`;F=cGFSfS z5g0<&3Vgk-QZJ9lCZKDESX&RIiw9AP)E_3$pJrXo6Ay#v&FFVMV9-=?Fgr?v z{2ko*?-W@V!_Pn$Hs|N(JyGWCmtn15EgfPAk!!Nw5`inuE0Is5cm0gat%bp-78w~Z z2WFTT?WPEm;SK(_LOllOwTRd2UWe<4tY{s7rMt~*zb}4f?dtg<@I55PHKUW7s+wrc zzEaooP_^#(2}GYI1ofC7tde)awwloP@R;XA2E2$bGC1-r2-GWWUonX9;~1$VXrBpj zHK2MaQFHS^B6|yqybNbbz6Jghp*d<5Hg|3ed%-fcAM2IoShHhu{`G&{|Dqf-~nN#2B|FPg5rKD?2NQg?*Y9mYN4*6_UUScX;xLDEgEgvpPma)mj~+# z`fSmUY-Q{GMweY*_HAM5pBrsh8V^^$er}v)pTQdhR;mRxq>>J_mXFBW6 zM~#d-edQ6^d53<7uIz87S~;7@UHTrL<0`uu?R-h_VS@k`Xp!8z{1xOJ$t_L4DTava zgZqR!ojlCp?X;%J3GWW~tYS#Jp+cHDV&!3rAH7+n_vdR4da?oBT(}zVf~|7_n>4=z z+B`j}sa@uj`ST0U_8Ek!zY^|w#X`>%gMV&r8vNRM73`xQ#3JQ-lr@wyZHv744VdOz z!sVDR#xFP4SCX(v0A`e<_FJl@2I3eXeT!`w>vg>NfnfFgWkj?-e4)3ltyL9d3rWSn zfPgWOVYEWv?dO&_V8fSsNi->s`DS3iVP9^}w&rHTaw?y#q{P zuUgn?Z}>NqmE<~dum2oT^@ikXkE?(ysLO6`6<{ZZ%ObO5AAUHu%=|9V^RqNH_dxK4 zS?Wlgtw01HrsTIKG6OsPfNWnw%SuMT-;|+>q#5wp^6sN;j5HC;YW6=Ah4asEW0;L; zy6wLyI>ViY$vvu~NwL0v>T7Uyl#50*)|^oAGNS{ES5hdQ7M44JZA4MUh^V zKChGi3ha{HF_qx4kr%NVA|M_inG-+!BA9O{&qRN7A#pT%VzYrOgPjpqh4;j=x3P~kaC3P3erGDoLSy@{v_Ng4i( z_X*OezuUA*)?h#2(@v{g^zY`?#RXhOa!Q);6h)hvTt_U|rhk$|PsvhY2yW!e`56oF z=Z(iS$t`*Fm*hdMX#=ijc#_cF0Cv_82dct z-j)~w=s&<$Ch7aJJvf4xqM~_VNAZq^EM;K*h|3lz&LF-p%R(vsxP&6TvyS(7kbh9wr*@pZ6S&A^}Xs80M zUoR{mc>o|2`C(Ek>|Nq<5Rwrv$9gt8%{8_$`4N zyNr0hW2o)Zw=#fs6-SQtE5V>N%qMgGP%rxBJL>m$;`+XSDWdn>B3vz{DDwWe_TM*l z4;$ervS6O5k#7fN5Y+zaKBsu4<6=9y;)u%Fy&Ag^vw8-j_(GIIU{~B6ddj^rqB74= z-b;@d_iv_4&En)xC8_Rh=a>2&d@~|vSW$9_S)bBX;C_C;o5RqFyOPK=UfMoM*ShWA z*33$7gHle|y2u&qgC;5mcIXmbDl^iOKi=WFLmJe?^cX_fj~rg1O=Vl+ry>t&TXK=_ z?;?xtg<84MSZmOUoJ)YnhZu)9&{1wuN7SKVX6P(3g!SF4iV0XJ%`-pa=r{Q1?*Hmt9}4l`c;NkX zUGIbgeMk7iO-`@8!_dE%V$z^J>y#BL8zJ(6PKK>CY6?_*mB}gKKJL$Pa`0E(uj*kv z|13YbWUy z)h4Of69=-3r$!#3zozO;vd3_7JLs}5%%#7Wqb=b+$&Y5M!p|D$2`*d>LX%%MtQlP4+}`f|`0$7@+xAJv;g{_`dX z73OOa4KZJSl8#r{g{`Xy&hI6$PxB#j#K>iR9m zjo2#aEO*X9^uYu#Q`UxIFRNu+azAMg_h))5>{2^d!7jA(wLZ-SCuWF#%i%STYlv zum1s@#gT1jsXqOb`ls#VZ!UwY@nv7+)ZHlq!bt4B&N7=+8KSJSGN=f`TjR@HZhZ~H zEseDQ^|5_ktj}C;8ujgF7iY63OMDL4p@N~yjjw9;=ZPaJo{b|V4)rD`|u`88q|A)LU$c`#A6&UUK zTfCN#t{VB#_G%q79CF$bOM85ROs&QsWaRS^o1I-w!S~30@6Yw@I%pF3^%Cv|k1X6~ z(6$*F@;{)$wZxxlP5_)OjnF&uL|qZxj!nA6YgGj-N8?YjgAUl94$p$d_I$mV`|pt; z@i3-%O`(dVy*VcZ__>Jwrdv@l$d>X;wqG(FC&4Myo#ab9!rlL5$WdO$A6)(~D3p^~ zJb>hmz&YpXa^@_-5SIQobeugt`t#ybKeZ8)Nk&-zxeJzwQT4Ag=NncOc&leHgPV>? z@V65(L6=l{Y##>CmAU;qs3)y%NBoQS>1hJ#d0q6v4!o@*IY09;pZ5#InHQS;IClWj ze~0GW2a1#?cO+d~p1$-DT9&j5n`^dxi{9{L4bG!>!A3Y>Wj7G*L zi!|r1)l_+?3Lzv4)QV>a)PSCexevnH8>Z5ZFIk0r8^Xfy@D$Y$JzED@bU6!PPlS{4 zM4bbm0QOV{u^>1H((Wi#Jj6%819X&Nhj?n|x_{~X$X#_h`_Oo~7~SBR1UP+_o3Tzk z4WPz!2njI$_7wa}-mgn6)gZrgs6QvZ26`b1`RD64V3ND6H_|gJwkx#Uj1@EJp)o%v zgnU0zlIq^1Km>F%5G@NjQaPEhz?&voU(+rEtKE!#Zm5N;rSC#(7iZMb*n(|i-B_+~ zq)qvK-z{9X^s5rv#?G3as+UmfNr_sV?mP#2fDwcflI0+yd->`AHYE*sz0Pq51PjiZ z0QVpK0o4XRlb%^i5KG}o`Dgx;^Lh!piD0;r6b{X;c9zaXbP(|S3@S&RU;wk)`^P>D08sq77>e^prY>Ogzl^j*)iM3+wN{DxLy!Uoc;uarqI zJ1IE{ia-WLK+ywwqNjS;O?xTto;(L`qPFgoH@EYQe~W5ZPG9*bL)AE&>N&j#J$lzs zeWpQ?eh9>1wY>-oS&cP15InT-GW^}%P|t!yCtIpDCg37ss%4=vwO&SQ$aGNP z6JJ&kg2iq>G3ffM7Dt0E$fanG#^u?P)-s51m$oP0sfMwe3g5U6#kqAg)p6EnJ zu5RrjM_$sN6mC;szS{YQDx^#P<^8vcEf3?78_P4XANGh*gpf<3EAoKhsGH7fJF*X~ z*gUg0{0uX$u!{RWjK~o<4QA$CJ&Itfxl+iSIVg&8&_{f#&@OQk|8^Ex~rD zYf2U-w}Y_^xbKt>t3{o+q!uGNKXe#+6$3Lww`?-y7`t+}iif&$rg1&`+mXxqzEHn8 zq?iBhi0Td`n_XAB1?*>aLqb1PyScp%!B7IBjy*6ST_hA0@q@U9X(xFRH1fRvhV}vK z*6Qqy`wHZGhuv4#Z?8vUk7KcX#{Y=H;HOA063sCx)AK=lB~?t3 zDtQZ%q|XwO^QQxp$GeMe!>x6=`f4s`*i8cK!o_~ z@r`CY-qISIuGTK(wU7Yp%OoiKqs+{X8Sou>-3Crb} zUu{Z@VAU1Z?r@pY5Cik!yLk}t$$46(XWV0c0u#pvb^v0b7$^b}7g?HJq5>pm9YI@F(VCsqtxorPvfdTzRg^CX1IKtiezsh< zU|OTc6bLWbU5oiPd%q%}L(}Q%@wLuVaZ`A;E8yH<_6~}V@AlmY+-rAI9aFoL7*yBC zPdL5e1#ko+`B!!R>*p3bw^QT?#~7%`S5Q-Off^xv!X4PKoYv9#L%g&|O$Ux-u>jVD z0rk!l@)KfQT&UQ6Z@)M&iof(YU4aI*vrth^Mt5v37`6MC#~x3VFKPbl$$&Q^;KW+}1PiF1r@!)AGEAYSWjd|MyG<^`ON+HCyd|Is&3n-e!TSer`?eiGd{{iJA+sGogo|m^ z6{=YGqiX|lkq4l#7b0{%)&fhF%O?kIHH7Ag^UR~3KNL0!z|clfej$2B&EAJHD7TOO z_tDMd;YB(i;L8#6(OrYc$4m3s&U6Niy#;{VAMgTNW&Nf?^av$(PrS9Ad{589=(NxN zdJ{%W)2BCt9rMomU}d9Bn~!vb&GF_VW9-q^w+nOQx#0b3TCEEdQ_N2_>AovypRote zpsXyV)rvn-BIHq=0t__oZa*hp8-f}947oq(F8s-ij?5~4;OM}QWBk(<|1Q4o*47RQ zPMIq=9lm^@40%Hg<1ZE>9DqjkB1JqZT~K1@bEKp?o;jheO0UU+=&di~rCiePrDfLK z05qYHF5(Q>d;>Y(ONO=BSOX_!G!IB-vUe#4p2M9RI|FsVx^l&axd`s;nYsXw>0L(>1X|kAy2b4jBd9}%g(92k6-6otRXsIg($cXmM-(6hb~>dKb`}yzQ3^$250=D6AOSy%A78!?K>YLwYXoP`<@bVzg8! zorZj0Yxy0qks8mw-)o{6uEpoGCZ^8nCaC4FI1I+2Z^CY`gb@|eI2qZ+P^U#p!wsOu)hmZ?`B9IIwm;J7m7T`VQ9R9=aCS-qv%i|-&T@c&IaS*M5&+l zgkZUHt6k(??$t*VWIh~F{c{7({6VUf5L^7cKY`bE_wkL+B&zmHv905>8X3+Qw58% z*QBJ`9(zvvL&M$D^s{$zUA9{)4ZjW5BM@Tw>h?nkAdu@adW)rfnx?JB*{oQ?sdbyu zcPZEz(rOgh-#scdftpvqD`mpuk;KKP0pQ4pQC1z_aZ+K;VbEL<99=~@1&_W2Vq6Xi z5c`R^AJ_cy=%!)f-ixp93*Rv}TvAG15hMrMp}z)?!$?AtS5f8$D!mgT-VtD2yznX3 zIqh^t+qZ?}_<-fbBJNOtJ1h^XnQ0Z5$I~ol*peyNnx;-wzeiddTtVzePSS z@Qe*n0RmnY=pG|vaY&1}nsSrAK9}(Zl~T#~A(g6~>HT2( zGi6h3O>>(thc2-Sd(uHHo>A8TpE?jPA31VO9T}5> zk@Kk>5{27MBb0gg(e;hVpZw3>Q9XL&vJE7q&EJ3JYqr{#y6}30@!{@WSoo#87|0u_ttsLRa|_39R6cSO>?VdJPYo&VH{AJM3y3=@gmyxq8-CA&eBZ(VwNNqe-QEjw0v zTRY}OkIM#wmt}Z4ULBM|16Ag@W|zmK@XX0U@rocrK;>ySeqlkt1M|(MY2vBT(3^|(pfZFJ1Ya!YI|GlZ4<#Xx5714`@Nw5*vd*LVB~0aSse zN-nUUEL3_m(jDV|%Gs^+(TCq7gJ+<574z1-zOwa=iMVd)PDQ>2*u@6Y*HUx?QR^%6 zwjCRc{^5@eK(urzhPD^M96YXdTn59AhSK^bwNU43G(za?J1c!Gmuia9PeH=HJW)27 zl`ui)ru+F|VzyR4d%3-SfbdU$*pTIlaTWp+aP+@gZVY2Zv#;q%nRKQqRAe&bwb5N{ z*|ENt!Z^A|_ZEk4Tm45b27`SPS(ayzN7qHa(93BHoPI%SWz$vb_H~BsrcdUxC zaPjlx{GnjoUZ|kc?mAcbw+{ir$&S$SGyh@N-N=ToaFbwVaX4Mq9BDYC>Rta*6?$1I zj*%7fEt&mJ2Wm2s$cBVDn8o;Wd^oDUxi=ft=&hscTh%n4-gm7yvjn0jV}=B`YiIAK zsC_hC``Yw#<5>39^PuAWAT)3+XZ7Q$-sPKI>)$!Sd8AKGMVs&e%neL>)vj&LSjOop zg+yW&6YmTC7_e&;BwwYZqkfxSjcC?Gwrm)hMP&p6-yxzH`tlwd=B{bT!r^WKNl0|| zCD^|@J&ykL{8;4mnwfCkPG4g=Zda`51J)xv*@?gTZ=l7!V9nvmDrnul96cN^hG_0n zM1qyiCN&YyZl!l0JxMWqHC0)&uY+=hGo{lmwK@9C1Fro@fi|a%$uomqFXZYGu&YW7 zAnJ&kh8dnL=)>E-R96gLsz>Kt)CEb3TJYzkmvU7NwVnl)v|~nEzg!=@I0bY);wdNi zdf%S092RD9FLo-@4<6Ye4XSL+u&M_TE~W?%{9eH#QNsCo@qbd|xOp`5gfC z&f5Lk3JU40o(Pijj9*muz}%lb8UO79y()HcME}_n1@ERdB7XZ>vR(@T3veAI8#qTU zE66Pj{n&-k;xKRk)X&M{hPQx>$kRYE@Cbyeg4G;h*lD5n70R2NX_&GBYOzI9K4muOEby-GkUg@9Z;1vD^Z9*OXkPkx3V zv=gV}WnvN`2^!GLydV$Yd-Rr}y3*`uRKv}`r2DALjI<=HjM5RwQ7g4^qx}S#v3AZa zuIp0G48lqEs19o+*7MS(6;aVoH>T{mWb#73W}dBi=54Ct7yh5$EVFaH)_msV13aSz z?lgL^)8yq6F&tRIjAv!g?~ETsc@*<5`zM~gk9`XiZt0xd*WQtUKEb?`zPXF@W3rON~F4)Wrk`>s; z%`d8x=!}`yOIRG9c)Sbwz-bUujzJW@$43BDqp(fU{>^7ib(7;b&73-$b73NItr~X_ z%t?#sj}RpvW!n0t5x}m)%M)_odX6mD=|%oxlSaTu;Q zeHQ!I2tHStN4%AT0MIZhf!hO?o2AflQJ@Z7&V)fXAd7FCnHv-bV9%Pnu?lXXD!-~L zjxQWo8w}tpa&2*PgFco}JT%EIHV}2IaC+k1q58MBn1Y*-!ybTx9F(-;D&BC?EucGi z9$}0dD|^R zMQE{P7fTEnP=a6+{nHpXpmag;a0F}x3%-AN4R7&LoZ7Y5K<d;kR^R50V zvypz|AyQ}L2?Y>*Rlj{R2O-bbC5v51$rj&bIkvV zgnbUdr95Q|Q69(PayedVeEbJx{||i7y}O-BT-;AC$G<%ligZE7>X^#w&~YHSakQSI zSVhuaJhC{k9h4;dXQ;tgp&8Yeeshb1CJOpl&gUK+U_LS4O0dF{qv=HX?QpmFrw)jy zVQMf0Xx#Qf01sFiv)XgR#ZlimbdBkxNtix{nC>+6KD0kHc!O2 zYz24>U!9#Ta;x$ZfyqGd)67pi{WG&RHAP{<+*8jB4}j8|eun|!R(?FAh ziLTD1K$mlPWkn`4gP!L#KmshWL|A0WC>a1<4Izmc6GuZ zZ7Dhj9VoF_iG2}56g&2Jfap6qa=GJX+#A2`S4O|>l$#j1W2^^O=-cgA4UbGYlE;X4 zr!eioZ51h@fXqQ-GsfIcM+U&Jb&&3=Y2bhX#X;w%Ywn{miJ4Ac&>eCyZ}dP1CTcfr z$y~Q~=L_~NEF|9d3K%!gu7C;qCqM2*Z6PuQ)X{fr{l}i~m``8+%02+`O(#M(pY`j$ zLJK_!K^!-0b+yTZzb3M_qhL~h%iErpSV2Uf4E!X3xUA71@2$Y@x0vznT?{PZv0Ej! zI_m5G1cQUv?Zoq693q^CIkt2zlp{I{{ zSTuHi{?!-UJoUPl{#Mn}`Bawg%)* z<{*2SJo!^Q0coitHzC`Dgpj!DNPVVhMpriL`EJ4;4mB*!nQS1~2%qlO6s};|1~dM6 z-2wA|?Oburd-xI)X7|o5&qc*Vj1f68w;rx!j}3;xdV4^Xyd*B34b2MqxZSTshM*!| zoP{#L->N=w>79s8`?0f%@g~v!dlOvSifR?&LNLjf*>GP+{`s1Koyt=WzRhJ-`>m!I zl`y}_E+p|4{l>IZ|A1zcfRX$9QH`EC4=w1ayb#XJ`|$yczo8(xEed$oh>HbA(VxFC z$Bh3OK$n8hb_6Qivt4J(4v^q_O((pvLlypk?~=}+)^@u<>F;;c&TIz`XPyM&zcmmB zMM5@5BSfq!_sYLZdDG~W86P6Beo#Nuv*sb|q&Bo(X-K^G<_pX?rE2<%3*YMs3i(;* zWI@(1Gz_*E7C`v>-fXLU|LZi&Pv>tTy6>aqIsMa2vf=x+$XLWWMW6nM&|^ zzg4{u2mso@%e}R!49d5;i;ph#tj%~ez#T8VJybys^1wX&XJT)zwJaju2)PEodx>1I zet(3YonpF&-xgHET;hrsXb05{ejxWmxn!a9UDU?M=-BxOh~Dkf8Fe0_2Bs4mJ8hxF zj-N>r{|0<^u_XRK3y}6zr}tr86ZW(ysgk!cDIbp*gPTVo3;57oEB0WVou*y}xuxMi z?SHQ!M1nehf1!bQ5r5UY&`gBv}O%) zt0D>=z_{n*VwK&TE7H&kdT+4Lgew{T5Myl(W52M5Ik}cwZ6rqx5L#s&-B?dbnwDNs=yFQtka6KoP+czhg;m ztg{yM4br@_Oz>GHWbWHFjFKY>K#}RcTazjt^9!zmgk*ur?+ecrBR5ZfP%akQ1;#*# z2;nc{d;NXWaeB6iE-(gBjKyFQ_=*V=r^jTZ;AmCHBYvx#ViG^rZ0;_ge2iGLNAvYw zG&fD4&xRDzez2|y)87oqXGqc3>(iMdFMzQuImm$Y7NXgZ?r+{I-Rx?7@5o@)Rsr?# zR5=LehZA{-Q2|m%U@5lrJDwC7Y0ga})fm0PI4hE0D`lpJWAGXXnFkR9>P9-8hZ0~G ziR_@m!pY#RDTe1Afc1fQE{9Kayt9TnMzuxlZJ?<2g$tKiUJqEEYL~y>v#~XKHsF*c zO?UV^^7=TaRD>$^>NJ3C;ePwrq;nfl2mbJqrDL-_T=jA5-(CBkeHmon$+}rBiz#nF zGd59@-#Gf*_YH%XD%e^fcFBOw&)iu~%G(Z(5JG&Ae{9l8r%P=X`0_3E^%7cKlO+QV zA3So`e8-vaeOC?^#3}r1RPmZ8>TjXny^Pj5-cpWjp0#+Dba>S3gXaSplsNQ}i#}8* zOw|Skylht;-G{sV86KjJ;t7RSw@z%-8s1qD`c2FUU?NJ$?Y+xtxTspwjkX^;_OVB* zj$P0Tu{^mdmO20?@zfb8l}hyYJK-uB;?e{h(L0REB5e=`eNxfl)e6!-PKh~8-J#x8 z=0FO><%lTMe8U_F477Z4!ryqd1Uv7hg)HHSMi-Q|p5}{@y?ud?%0-ByU4wq-#@gXs zK)_=~q4P_o*JoMCU}RI153ywr97$<=ymEsxIp7;TwY|5NE%cuaZ`58Im%Z-vQ4(vsR?1bzQ@FaUEY{`YU5AH+*meiL!G<4O1hnL%AkP)GtHg@?}UIHW8vS+zY^ zs9s>bhZdyk&&#liV-pf=c4m8KIv6~@!j`<=^#rgkMU;&R2b;D1Yz=B6;e9K^FB-SBAkZy2I|sh(Y|l z^4)>I;E#J9x8>Cvr0NI09>cgEvBFMoh$A18S^bOE6y=G8G9)#ZiJlz=s@V4X=oNLg zm1p^72)E!UD7s=m2!h^JUmZP-I9|alt0A7hJIa>O5<84|%EX96c*bC35ycy|`|&F+ zW{^sj^$ z*k_ws9z!BH81-I|+W8HA0}o+fB@Wa$$k`?!aTS9jDRf8IR{7hvHgz9##nD9Ki5f4A z1RM2)`B=RZn0L6>-Wb|q&A#oxT}JAx53bTAy1_T^fgi>YU5ZX5VKgKzjJ)#bWbtvS zQnkyj{s-DN&@p#<3DN51MBmgA8*mL~fAOVpv>o=LSCshuU)u%0U7v=@h+YunY)kza zd%r8^dx>cXo=-0rYi~SnEdz9L>E^6HC3~AF0$h`~h(lj@XKOrui3$X3Klma# zf@n=j%pHwX*HvlxV)hcbH00L5wt52x3Q&JM^^!tzypArj=-=FIiYStOdXW|9Y$;E<^a#Hh!o6#ys;b8O9VQnoZJZYOcVEj!KiizmPn|w%{8Is+W@zL z8v8ss2|bmg+N&6IjGJ|F(}jY*D3Vl5`VBnO`b7+RLyldAj_`*3q^r`Q(rr~lHH`Q7 z#&GREXaEb||MwY$G=u_aP!O}()`c31X$WX>S*P_Z0UaqxX@o;lKnulkKeXMy8x0)k za#OfkvUTBN3q(jj=~(*FSRXh^^ zz}gl%2YyGq*r}BZ*uwkdn?kgFx;z$PTa6(K1 zx8*7Q1Y&)W>SeN;gzV(wUU4H~K#Pg_r#VIR_iimT)kc%(7^L5$ktLIX+3%Y+Gty4; z6O>E7Lm0r&_t4Lx?Bg>d^zH(19!Ls#oN&I2m@g3ZX?jSaSK()({o)__%Fh-sL7zMd zmTQ}Zkr_*NFSEz%y(;IXGnDG1V!ctWREZ;z9qS)S!FOn=ZR-FRDHLi; z2h#hH;eGG+YQ=+3%n>t&1-^N+yj_ zNN0=GClV?_J%gB!a0(*6Ml}`A&`l{uhAy%nH)*B^Ze87>j`V?;D^UAXjG!3r0R$HX zjc@}3QrD4;{@DL*3)-<3tRv_`sCa)Uv5^n?*^npNYA6Vr9HkQXARCDGmBh`q>?pb# z`@|kqGvYP)zU4?}yt~sDPj#|t;&BTQgd+d{s=HTU| zcQZ?Iko`zb5$h{o$gB6rYv_zQc=>w(ChU@5LR4lR9d@9*{mNYH`5j!8rgY=M(RfPU z{1z`=sbS6S4sr;Pc5yAw?x$Y`9~(pj+x*}nkuiugLJWr9m8k$4f5>?)@GE`YZ)IDY zoNb0|Uk&FYEnT@3M={F8iE4nTS;|DlrAIFP9M9-463O=4%CE_nX4MIxc$gJ(#-vjvi_ZFrWo2cfv9yAM?x9!F#{oNst?_=q~3{&^CU?odBw57Ivfc=QMaEOV55{#Uup(K0DuiKZ%(%Ym2?MsD)5 zj}raN7WZm-5%W#yS_3Dee^x(J+>H*x=Tp)njhJAV##Jy>^0 zgT9bE1#X;%ff|tB+srK~*?jfA9z4Tt=syPUu(DV+B>uwDrPbP&sPze|m{>V%!cOvT z$rvEl z8mV*xP@O?4r}Iy73@w`UozI{p?HMP_u3n|zA9N&&2#UapZGIS@?c(LFw4OuN-~|-I zw>vD$MnuaqhiwP>a&Kc->kn`mUvO~WZfWO};Ot#J`MhMpnu#R{;R557piK@DQVxpw zH&@J+uF!fxpfN6g*8F(+AnxV7Hk+mx^v+LhPA{3`wIFD;o6f}{JbU|}hhXLh+t3c2 zwa+my2|srdlge|9%vo9C}~#vy|#24D%y?n`?)8>ztv^T9PXASCyN5egl{a0#ztv(kqmguoGx! z=6)NnN`=N1{GmvrRxH3$qsy34M(lJ_79s_KBjkaiNR-3lmq5y^%x~LbJ(r?UGJ<}k z#EuOD_Xn{C4tw=`Bg9)vT6DlWXkF2kelT^wUYOw+P;(SL=z6pOjAILqE4Ih{=Sas# zMl5OE2?`IAMqJIKB39#1)TZ{@bPF1MUHth4ZPr^)Yk@lvJrBR~$e-&&~seRWTW zz)&FjLvDczKIX9BBvDvZobjO#0$dDUE-i-R=PK}*xA)CJA}@qql|(#`6=14stE%iY z{l9y?Qzp9l+F-PgVeaMriD?nR;AK?cgXKLFf&!?&ilNIAg2T!inrtsKn?)$LOFoEuB zX8H(|0tbn05J7-0E=oGWpW*(Ab6cQ~V#0;&VXF?4Ikk2gU_XGs3`EDu3@#P17xx1j zlH{6KV#pVa8gW0Ys*)fHsVWK$z2Ib3(Fmp_D5uNG@rd^pb{N>Z)%k)4vrEBg`^sl2 zLOdWCgLulsUI#jMllTwIpY*4DON*8}_z|PeP`huH;dIu8&EOU2z*pweOQ4uJyN8Rc zJ>SMrj{y25dWterpnsTh>Lg88vZ~h3$*p5Fi9f#?vM##ANpQu)2&2Jk=fxat;N3%fiAKl~$1xO$!*S5ykh#=-W-A_cJz=wmlTcqd{= zN^CVKarvG|*Trt#-$)vfeQZGLxh2U3&Soy&aZRzu*!P(CG?#gPLq5rYW?;{0VdoJL z2|cqH^bZAmykr^7&5P5$g>;oX${C)JOR-UshG!fI_c_yzXRGSS@)c<++fATuS zO*|nQ(4cy|V;1%$VU`_{Pq@kBR=X>J<+}y~xWdd98c_%v5u1mB$q#SPr zQ?(c+qV%#G@Iw)58ocKy!J1uKAqC^Bzsx+y%UnrHWS>}aq>)7*ETK*L3J58_Cx_nh z-=c7KGU02a&y6s!O;`zN7ctDQpSu12OG$N>u*cE zIm`)#gw3QJVeK6J$-$!JZD*SU|2s%WYICN}Mbzl9>#H`GVLVca&Mh;T>UlTH+S-=3 zD0eW;@3$XFCT9V4K2m7^h7DRaAg>TDrsbAzjHp|0H8lk`32zVSep^}KBOtD`a_v~c z1V6HdUXY+)tz~)ryP`xe1?^k}%0H|NU7c?A*u2o?VW({J0iEICdU;d?Pw=I$30jo7 zt7R=Gnc@tFoAdUui2+=a$9k{ZeRSWm3(&kj?n*O!T1|*kceg=E6J@BeRhEi`bg1F9*lU{D z>gO6c2R61ghho5emCR|M)0e+#py${Ma-R2^>p3N+SpTjxzhirM8E59NiX}GYM^VQq z)|h{Z-EYxQ4d(^5zW&)GN)w0ju{O5V&&-3p}^c_&fSvb{OVcxuzehsx&O&kyc z%Kk_HU9q%ii_t$WOnRT5B^!TV?;7Zw?zFMgvuXX1-{RYS1UifX_A{4_M!mE-B=sKG z1$0)1T=l94FWHTWT(I&(!Ag*%JIET5LEK*LyC;*e#tjR~0Y}$iK$w%as#O_z#mRW% z0`=AIz>R|G}j_L+O}<0m7u1 zlgu+La}{J%xJ(l|WaWECojtxIUoS}hGAYv7=Dl;()gA=n@3NxkIE8t1})0h>!Z1j!$&kn`Vy+WYPK z1$DtFKv$K<@T4lvpB@mN-54$`u*|qzw<%z8xX&%X9A$nsU?FuJ#yi-`$7y9{j z!TWSf*Xb3%J$7B6O>f_*?Q^}#*WP+_;4dH20pyMSYid33ifMJBhm-@$wj==K8XsxL ze_J_=vEFrUazKu=Um{u3UG9A;2iK{k8~lt*^#%k1u4-Wp!K7rIY;Mz22Z?L#dO@Kdv`q+@t1>Qq)9y)(A)KLerw=ZL&=KT z+rzyjM|ZvWt`Z8F8<8b@<2!CPuGc1Fk~wG&F6_f!!^xk=Ve^{xi5vgC8gw?C9uXxz z4Y?QJ!k$zzCEbvCGQDx#6r9l$;sfKA<;KL`F3;2fPz`!~IndYl=( z!OKTq$|80u(WPYYS9uvXIZyp($owW@Lsb9%MLX;LKaMlqS4zbV(*9&)IE&Z$msdH%fS1oNsB&P| zR+9!d;7R2X?eLj<6>NpYHyX4ejmZDkX7qJkNekRS9?LUq4(_Y+8O>}(8#vPebdsf_ z(`bF-S?qXkeEcrtE4+V}glySuT&()6#4atSU?fj$54l-_`k~{H^_2f2Yo!$N{@C1= zjryx*wHybmR6N}r`nQ&mhx+o70n(6!UPI>$g}}FUZxj7fM&1*dlqAXRksP$?RS@tj zh777kc8t6jFIymR(yJxV2_Ff(gR?Nzni(asmf3G34Rq0=FajZ_Q{eo;Q(cUA3_fW3 z-ILuK?;3)XV=Tb;Thq92;KHicZcW6&5d`HP;Uro8NbT;ke zMC+>4YHh9=00dxZX6g{RYBL_k#m5#)T4T&hso#j%PYmWF>Tv$+$5*hl89XqLU%hbYG}qWuR>`NGr2*LpB{0nWzsSz1Jmyc|Yhw4k|DY{`;w1Y)={__4& zJVQ8xKwlHrW<+X_@L~ED_;{`bA~)WNaQ^7$1hHv2tvimrfO8FPxPhd?$Y#_P%&H!r z){9TI3Gile@xR|C3;d_T&ACZ+OP5xZQdiX;+!h-KP18MIgTmc_AxvMvj=cGkS;JtN z@sSn+I1`I}Hk+ceheNoME%@1kA=ZqP0-X^mvj3GUW~yj^@MZjQlwP0F5;w?48%$WQW@33z@Ybv{}vIDO%qq(T(G<)RvLm!#!swe)QGcq85nUX%% zWA=eWoqG5X98-o5NRUOr#RKGU{p{hk{;|gLOJZ6xnG>AQXk2>wU%6rTLJ{q0;Lp+i zU{)#N(E~B|%Ju5tWwYb-B>C0MVqQ`{EC^3}%C`^9-7(B1&3xGU8&uL)7w07dnW~726p+y@%4;OH;E|f@0i#)0#RnOV|Y$gqNDv5tNQo z#l2rvN9xvB)YZ0r>h_L;=QjQHeppjdtr*j5OXoBA>N5;J)xrJo+`p$0Tv+kSHU(x$ zm9)5LWnSKwRIo7KHNDrCex(fJ8O6%Z-E-cYntL(|ecF|vhA1n+Sb1QWYNvdfY?87_ zT&VFSg?$z$soLjev)C@+W{)E$uQNrq1tfl!wz<&HKAEz6w%PL!BX|Sc3beku68wOZ z;14MthI8Z#pK{J;;>F126RuGPi2-r!Huz;B$e}2(fR)`@uik%In|u>Kpo%38Y##Z+ zyA0*59Ib_@553kUbI`<#_s)+gU^#)AA0c>;6L<9?U2AT)HCI5a>5rCaiALxn`N0|_hx~;c1DCAi3Js+Y zJd=VZu+4E!(F@g>lvn#ItbCM)p}g zt-oAr_DG z(K`CqzFk#j3VjNy;F1K@`R^5u$~-$66!)`&UGymk_;Yf{K7^+bG<=Fy(3ghRU)1Wo zIyR_>uRU)IeknP7Z@=8?@%kYQUk3*MGw>~ga)-}{ZbB~WPBfK6`^59H=Fm)Q3z*r% zAm`VjcmEYtB(*f1MH7_hS9fxy0?@Fp2w3&?rSW~MC9D?Vp)Of?oEtxH3vTV06z3h? z8)HJ0(;S?>Uv_V#=o*RvTGXz>cn%2v(LjDKEZ9UvDk%i6e3q7?(1SJn0dxMlDo-zD z6TdiZ=nI8G0*Sorjq1fk1qg_`+`{iVE-jlL zPu@ubwm61!27UXlx*~(6e*j?3>3lnuY{XHU=e6pJ;38yTSo(y`>L`ZTxS+ZZ6OYBf zON@RFz2e~9pGShU0Fbw~t8V#TL0xgk=FNpPfF*SQ4hPPxwbpiunwPCEJCvKlimq^r zAe0;$hddu~BGq?AT4JiNdy=jJPA;m#^_Z_WGC$-k9rx!FWtbf34Cq#3jAeCw));#=hSD9)4}Q=ZzrrY%)oY`5dctS`t{Xg!Mt37fp#p z+#f;18Ej@zOt016iC~2oP1{m8+k}E|Eha<}gIJMnT4JHfrrqIwP7bG#vlL9Y%k=X6 z`m`BBgVJw8?XMJJu6x)>{?Y#0x8n)MCdDLz#Zq=#pU|iJuXK9^n6I+8E zy}*f&KB3EC{U;93L#8*)u~(v~32Qu@QE`8}>6jzb!OhszKL!ef>3I0Gla_j!CL<{AWPG+Qhqe?J zdaz{IA3w!61#(#@73hHA2{pnb#a4C3U{9cj5%0ImBL0CO%vC-EuzWI8_c(|)n@RY8lH z(G@2DN7I@AL-oFY{64cYcCs&1B$b#-me7n+v{(`eVWcFx%9dpgDT>fyskbspB`Km1 zGE-8bP_ktkB6}w5U}nyDK0kc_g?Zd_&V8=y`Fg(6zOHB}!jHa>UwSpG&Z5}dec#?Z zX_}0+K-nwfPpAPewVjKsNk_1%LEx8x&1DQVg6Jzg*LISB zV;!l|0B%Xhx`AQU@S@d$ZA^hC>+j;?S81IBZ~>j+F(4w!mU84PDspI3wQpyGQH_J2 z7BRf6h=+jF#N_=-+UJ}`3+9<$ZY8-wp*b^l3pT^|SFntItXZ$8Ovj^@Gn=D6I-82(Gi-p} zRdj(k_{rVtKHuwH4zX4xJCF&d*6G@-tq1W01$fgnRB1=e;_3JwKxkIKY`y1-2eSAA zd7r%`1}lT;O^y9qXvA}goBWP%2}ZNa;IE@}m0v(klsnu0;vxL9RoDckYQIB0(MW44 z_73+H6?u0bGnXOxABeNq0HgpFO+K&=h^?hu`COcvEDMyt(2W!~=+Fn(ds=KjNZp2# z;5zbxbuh_I%%j95%3gPgn3kp6^oHvckF`e~=_$z0EP6LE5HCR&;C*wJh;j6j>xQ?% z4ycvwb`|e;^%cB9SG75@GS@?=#qB{Z#=fbJ3r?uPBTH+)3v6fE(p6>YLpEXag2%xi z?#H4=IVvc4ZIW(Q_p?^NJD{_FA=arQ06Kx;^S_K+8kf^>ANrqVULSd8!Whk7T zkAI<-TqL&h+M99tDb zQzG4doGZm)gaZ8KSv4Fk2qn$(3FrKyzQV%Da~4AU1F0%ZWR@BTd@!Z{XgzA(_b2Y( z6k`15_c!4KSw~dO%3Wtc_%3_XHlr!JaxcbFm%#7`GL~w5Pd(%-c%ZVVk;U7*IWx?A zLQ306Z=_gG)pfJo-u^qb#w$;?lvuaqA!3B%ekr!%BjA}faKR+TA@3{_85G0lZze&q8~=$M1kH95 zTesW6U9nL~s&<6&UJ8O2%fKC0b-_?~fQu*@jN);Sa=t8QcstEvy+z7n@v7mMSrc&V zkmq?NqceG9vA0@`B{+`TspJ(+oKUUIz~(Mf_BSwH+*tZ%Qna~2@5RAmc$O?Afvov- zoM_SpGL#51@ak8tbFZ1u29fxQ>UOeX zR~M2aMOtPv_p?!sx>V;#F{<);(}VASOECm%HJzJ*nFV9xUwNg^cM{w*3L9s7FF68aI#Paf8sN8x5mcB?h4k8BAg>S)^`Xd<5a^?`ymM=ay z;k4;CrNb|#=e#7a0kfQYcZ-1X+1Y1rzmIjjcP5#0ME?U;gcqxyKR>Vm_8cJ#zKNa& zo!6zBI}C_zy6X@Ps|@hwd7ide4>0gfr(N!08*rcka*!vTf{R<|ys6uZf%XElSl)G>eNEBW(9S-yFPuG1-hXV*%|g5M){cwzo; zxfmOHT!ni1xy4_M)?ov=NBK7x8_ak6-~K^VJ0^$Dcrp075vmin1TCT=c87pDvTwt4 zX`2^}*LLlGA_}nE8PLZpelFjafRCIJ-vN9icX1N_GWi%;w%*nF5^po$w&rwP`vuh_ zW(4{g`!7!uUBr<*-2JOV&@}V5#!!I>wvKE$n-@GPmLY+PNR`$nAcxUkkcOVEEYBiH zaT0T^5?OZH2PGKSg4gjl&R2mjR#=6WGKEj0xZ5}0zX{!27#)&eje-u-IEn+&o^HH$ zOcp4z>$p#c(Z<}ecPCyMt>5!$tCSDs-hzgwc?jn&YDkpUsk&Y^l4l-v)3x}eSKhXF zC9bOBFUPn4Gyj7n{ZPrz?__}jmN#Ohy3sc@>@>=)y*P=M?kX%q0|BHb-E-_Oa>3L z1tXdSsTHs`I8=27M|}j_+O=RB^3)(e2v%hCy+2clkY{P4%gRr^?aISy5Kk! z%tp$swJVv(2O6-oP$%6d$w^c_rl{yc9@|8$IaeLI{_8Lg_Nr!lghm#l=bi~D0q^!| zF(M>F6j1J5%dG;g?D>!S+;JIA(o4YbqUePPJgG$XS1=cvq7A)R`Zc>-j&u{1i7xD= zmwvMqmx3z7f+@LlgYw}v`OWwSOr;aHr5K3)ffnmdl%O!mq{!TKe?rc)K# zeuM6Pa5l!#MA?2ylzv5mnq?QXPBdUZI_e14S_O23%hym=9ROBQ4rEJCJoW=0X82xz z#Ak88{g?Q2vuQFT-|2h*F+#2;cyh_lUqRMcnY{%j%M=f=!|(kulP>34Vqta1PRr<; zH((9rw;kEplJbfEcWIPc`PnYc9B;7o6xQj5fpV-BGzR36+iFpIcYc_3#a!Pajbo(j zzU%ub>dTi+r{cEEa6B)`P}5KzWzmOb;Ksvs2R4QmPv-@rV#KI20arg;_~knH-qw;a zRTvWBDAagwV7hHYb|MNFdJi}tl3V`U{nbE>@x0DkzHQrgcy@MH zq5u9H-!T5x7kXWv*YYZVf?S-^|15ct6RbUQz6vVa>p$FH`-N<%6&y}=e`#PE`ZnyO z;0yQa8s`uhlr0+#zWik&Lw1gS4@EZaDVMS~*RfYlU9m-Hh>u$cs)B;=OYQ9ji zppcL)@t0!*>!lT^rN^0lul}`t2Q>|ZV6f|bg&KjQf3o7tpAwg|^unL9vLs#RS+Zb~ zpKc0F=8n#MUZGc zF?}^?84g6KAXPNv`=)dQOPdv$&Dn>3I6lXPSjd5@cmjtL+PS!#Fgf@d9Q8%Xef*O6 zl?n3)u`77&R+J{qr*dADbrcjDq(vAgnM$#AK)9Kc>3?c=fL08j!*gt=!u#G}$y0)= zZA&SpaU;2Ky#bYpZIb{v?yG)KHD0gw!i-k_>n$K%KRtiwhE?wtWy@8+3LbbkZvn;I zgp3?SrI!3Av%ppQVU%xLy~7+#KqX6@;N4%(`qAy&thV@ybNGSQDL8(e=VfqzMZX5I z`6U15SZ+@xTW855DZWJP|QQQWz?HH+)g8f*24&t}DZv{6K<3@Q!G{ z9_uya`Z-4~&N@D`r zQtsh?pm#*uJUO(mMb2rw}Rt`KYL{Pl#c@U|WJ>90OwzW*(4n;C(` z&}UfW(&WiRNPgW6h!c;4q)7>Cz#ANLav4Waf9e`=D0jvPEdAQTHuW)b!QSm$|Lkma4C|Kj3Xk!pue)%W;Z2jJMtYa>xH+2J^#T?~} z2px7KKgK?Er(M7U>H{@u5IH(lO(J`?e}UN|9NEeuv#IaCE~V(vM%D?AHm@_*h%&RV z9Xy$CxoEqNJlf<7UATrOZ-w2Q!|sc*8s}=i488(bztoy%|`uemv^nv2P|5B~`DM0_1JCX~%zA+m*|cY`+?dljwm zG~dGA{n_eLrNPg5d-ucQl0P&u=d-+FA||KClEr&vGzC4$@pyIbKtaVThiKY1&jLsO zsHVPu85lDwb**B3#kcj*P5?(09~4T){>2@sBh|D^T1g?(LUAO6#?{|8;)}b{akTu9 zJ3mkBh)^oWYyyR7@)!8aty@QBDn;3f>mZ>1buP~pDgY8uXwJWXk3*srwUDrn(3MeY z5&h?W!Z=A_B~*|c)j`oPunr7oq0QAN^UHyZ8fa-N6VPwUGxkFyi3g9Mt4?O}=Lg>^ zYvid3`m<-LfFM};o>Cx9z_9E;&PE)C5)}azjPEvxn>9oqAOLiH#WhXH($m0A&?~i~ z4m!zIHx@3+t$CRWdmcp0%r!CaVGxst{{WrP)j|RF(3^{}4a84CH}B^na;s=*7IiJU zz;4Ux;iy#x3eNk0iODHuCtkd5Y$Iq~zWfDQjr`bDQqQ~$l8KGEuo#j4O$w}D%juyc zO$Pt4n7VfkV7=2em!KMzTcS`L33d_ExnR|a={8&2ehkYR8t9JD!WG&YJx@ae`C%`(+0E zr7!J~i=-O##5)HO!XD>F=t4W5$Un_fP^k*zcWX)OJ>WMgq>bzj=bgRJ+jovJ0go%sa5pw`$FyBNr3-obZC4NMud2g=&To?VIwm9DsPsP52UjXzsI3TRCphwM?oO@z zCRzh0go0WbIre_A6&mAM?HP3(78%b|?4>9ove$8^_8Al*3+xT;uetsxRzmL2Zd^tT zEU8c-xZlEzBLqmv4gTOoul;7Sc}cq_vx`8!;4nIaXP6VYQ|j{1Mm+e5WBsJJ`Xdd2 zD8*Eb_Hlk(5Ik107Fd#3#dqHA2nJBBS@T?SXv!|$+IQ&&HQ?%O-Ps=+#;6{3 zcCIA+O)&qPzT)P5-vZvfGC0Rql3{$tj3>q644EgR3TW#HRtLvE4h#{6E#@1*ZOel|Bn%^$am)BGGY4d-W&{9PTNEZn zD@s$GTGK{%Ia|7ed)HRVTeU;tV$a4NWmRN#z<9OkB(^>Y3)MpGtYSNHI$U$WT*;nuMGD> zTmv9x7($@d;Hk>&O}tkh4FP(OUIjQh6+6Ef7WDpgP~T@~EvYy81cF5u#l<#Ti+1xQ zPic{EgYJsG8Vxx1QOS?sp67W>!4MnJ)K?W81q}*SCO6Z-YJHz4K`Lp?<0k44Jzz5K z7(e~njUP2^jtOv@u?9cUzD5plxKDsQRZU~QuzON zr^?S3&9_twGX<-aj8Y`rfmXGYX`Mg;&SBJ7eI_8OsMgEiq!^tlh!u;8rvRZO;zpX3MBbPIjf6wl>Z zMB_c=F9ndKGQz=AL5JE=KLW(&{XLno=RxZJmP!*eczLEIRIM$y^1@}b+=Ux_%TQ_q zfz_mxIDZoE!g;KlyY09C%d0+IfiSX&k9m2#wd0@h-5eJ4+VQwh{*{eF;9b^se^L-N zIzfxzAg$!|P-N>DltmY%b=eoq4ZX`Z61y;1pQLw`UbtcQdhWFrG5(*oKc8Qh22TJD z&2VAW`o}D@>HQF++HREoL^9+k%WV;bLQgZjp@mi&W=vS()<{yU^g*%}%G244iIo`g zUTQN+3d2}C&@b}}Y-n{_(TCyVQt(qTLJWk$G*)7jJzk?8nt|lUs<+fYHMEWE9nXB` zD$tPsvNsvdm9k!NAj2zu91gNCGB9Q^goRcjTb-9sB#ccb##4-0{^Xa?#oUF_a z`{zT$iT_J4C@Go4;YSDt$d+1_khNzy|Qu;Q^K zUl20Q;kAxW2Z{hSH7Q}<$#+u-Jz48$D3{*!GSTE{pompcQ_9iJ*~W^}8+VhuYWEiv zKilqPw2B}dk18*$sg#{`juAUC9_-VFt(Y6yMdK(61+? z6vhvJ$p=qwIMRR zuYsf=>5{qpDLuCC!3R~?!VDrw4je;5-{V`!_|v%6^zs5ffaqbU8!1n33w@#CBo(1} z`K7?_vIS0$DuZEbqYm%mT>4cIoI0jInik!-bi118sm}H>3xW_C8U826ac?U<6S&v>q!mP@Y2HIcd2nHsy{I~Q&*nZt9S(dihl7gLJV!J zw`%9}M17)(!g&E*v`w$6jHnf;xWRZykRj{{uUrGKWdR>U3OvVJpgc!0p0d*T=VjI$ zKn-vCAnbmP`aqQQwgHo<3WR_GkTRnjlpnQtj8fjI(K>BMEv%m`b$ED3a?}1&X=Te( z^!bnY`!ANJ{8qxvyngKOPC<1)6(>N{Sq<}lTXh&g*s{z3ASc~+9yO^>azFS;0=XW_ zH@+p-R)~nnl`4D+X-M|s>!g2{ag8rZw92c7?>xDQ3Ob4r1qu@Z7va52V;!syov|YV z7hWE!y@8YGcq}}=I)Ctb$jqwKAqnd5rQ$@O+#gu<&Mj%5-P~YI*x>fp?n2M&NeM>T zyL>mB+km73W{}SYtPeXt?jf`N9KLIeQS0n=RFIR5jB)j0sSGOIT6~m`jVGG}dZM$J z9xu=fRc9*9V7`JL8g+{m{sp%0@>dchW({%SCg2h$*cfME6R#pg?S79o+l~R(K==K` zKA>q=6A{=)!%G=|LAijcMo?{?zVFV z$Nte<8Yu~gC7PyA9o-BKiC)##Xryw{9DG!BO zI_EJ^$Fou(g7VS&my%TSq&51u2GjeT4i5ZtTm%WRZH6CzfmSVx&_5fYj@BzbJZeo% zsD}U>Pr0Z;Ld9H?K$Ss+X$g2o^l8n}WZr#ukYoQ+qT1cq_at>6zq*fJ_(78CQ^Wouq$8f1NvTo=msR(7YXxXTRG{qae&}u=u@4Op3KIP)@Hgc z5~Z6pBAL8iHgvOM;NDUwCJYV2v^cF@8YyBlJN4Ww`_6J+OjK?0lwD+2D%19<#t3Bn z{{e|7F*4E~sZRX!aY2Ceb5o4g%KQD4r)u_A>yc`j&H}n^R6cN9iMHjR1 zN>Fn3Hy`MjOhy6-e;MPU3%kYu$58bXrl70ZC?s5PRlIu`-9|$h3W;|5V3V||H4t~E z*#O6UmaTE=^}|$7-WB+6sME)fbGANLRVLZJ;90vcf+@%0N@|R^|3`#WtNZw)_%=DZ zIM085zRO>_ZvPL-%~=l1Q|Ja`Cr3o?<`l-)Xj)2fT{)_lCGb%>Fvc{dcT3 zJ-mFwS9@^rmF6Z!Cl+kOs{%&I*6H9(f{q16E}_48L{ncjM}pSPT^IYa!;mBbmezff zrxm?`->hd-Zh7~$p*D!H-6aKk#5j^s(9dP|{%)0l2}0jFsp-dtNq>z7t*)Njz_z)- z!0rY64r2e_FLqw1jkX+jhkk*%Qu2S>l7*}cW16pd6DKo2doqjhz2Q;a9hvUp%(DkN zjy%MLfn-76n0F+aNXbU#rA>hYDkopVMp&u^Bv2bPeZLBv{8vg=qyMP?D>-Qf?T7~l z?qKbWKn|KyL=>cypd$H)T%x|YbzQnOXqA2VPF+KkJD<+FpRs~yQ#e)V!i#5g9VNbu z&X;-mxCH^*--dwo`|`KfuDt%Ekla07$T5`hVjuk^g?1SE&&6r&CxQK9okXhOG{JXh zK0Xktx+)>|JI>%oZ$t%FYoKn^K!h9|pM|R43bS6LCxa*CKZHUHUdiDaR4-(+2`r>j z)+Harl&XL%o{cy%7;`Q~UY|P39w{_sE~2cnj-#cfA6#bNy$BBBs2=9L@~bCUKg2~{ zqY7bW=Q1vCZrJ@`k7wvmQl=`e`lmP*cQ?x#H6A!ADPjVTKr9t7`Sz}u)VwWCzoTw& zSp~dA_+eP*&^LqS=5Pb#a9h5(63`S4|6rtb`0K&|-@8YDYSmNSOA`jHo3NGfLmefNJdNJAtNs&wH-NnuIb06qoJ~!Gk%zfTgiYsjrzq-1WJCVst`6H6xa4 zhA9DUP^<0(eW?k@-MRG?c-YtO`R~cMCFqN7>xDspsbM z_hL@}G6Tqc=&p*c-&DL&Km?KeTm)BUwSrKg*#?^&g13)VL_2b%M|7sp;R6ue|% zKUlsHQ=>lDJm!r^Ar|6717u$YsCXufIMV*yW)60sjOD>xo(!6#geBuP2s`#@>CeSxXap|ym( zl1DWS?DNeyR*olI9{dBzZ#gHul=W@GvjM6(!us#C(=p-Tk;Q#nxk=@qSEBYQLAj$Q zVzD0=YkUh-t~RTXXH*K9zYh18RL!-!@vET|fAzbU1>A;kRx(IEPz+@xw3eJ!p#u0d znCe8&n0@bXb4S1y#S~#uUYjlSXQyY1%j}Y@&HkCG0l_@!?#)W!EfOFsbRJfiaSJoN4{tRyeg#`BDn*1 z!idyx7yxS>j^Pdm>QU?w;fW}5l%;lNOFss>f9MzgeSS5#``l~C03qZfb^Rr^o>vyT=q83&nO-_$qvjm&|#JUGZvd%G$%LzIG z4^q7_a{Y63D?a7I*$e->p^q2Tj|2mM-s_HK1*Y*QE6A`WjQ%B6bJN2(G1VR@%cWvz>PA= zHsCJAJr`7OM69z77ZKVPIroRmf+EL%rVY3QR@(=#@6O9@F{1E;nA0@}{Hcbi-HDqJN2 z9zN}<9n^A}?ELz47^IW$S> zxuP`-RBy0xV7)D>b)+a;oc@dPq2td@*>~V46Sn z7MZl9E_;QdWj0agFr@^L)o^-f^$+$B-BY5?Pz4TRpzZAiHY`jdmR}Q;QF>dFaY-~; z@#<8C%=*q-BdD4Q|xu zbW6Yx2_b>cZwKEEKSLg+EqOEE^v^OUm4gomoSEz3nx21#NKPsP^4(*BtJdPN%e+~tXPS^`icM@)M`3`_0qzeb#-cw z`wVr4xzj+q4#?Qw`Zf4iP(SQ>6KG+rP0+FmZ3Up?7 zAr+zxBxy4ChX%qQESooj`Uh2|xD&sn-Yr^#H}X_7z>1x9rlKRGc)=aXsKHjgvAtN= zE+b&8#n}sO7m&pu4n10pfOgRT9vt~iKr6B@A?yWXVetCAKaH9wV?$`SRMRVncOB1N zj@p+G!-vBp9M)!UB6hElvz?H2_pbj?{W~4t#O|XmK>>?{ZC^yc4T^w_FHOmX4S2Y!?^Cc=#|c(z=v^o zBW~KibVvG0F%n=D9~;Pir1=%eOM3EaB}LGB5!Ddz;7Dp1HLQo%KdW)uX|b4&%v>}k z&@T00_E@79PzkM((8n-!*J20Q{1SVIk9m#gJ2b@Gk@i9yoCk^`WL4JEhd+=vujW<1 z|6v)%SzP4ro}~d}RVqb-m)&>L#Lj7+^EsE>aYK7~Z-)BN#$FVIl`PD{8#*kM}jX#rj}Z76ML9!3{OK@W~-(~w0;r2_k9bbbbvsWd*Tgc{ED1HvI>EXnhb0SzgyW284}$eHy9a zA&UZMkMlw^@$P4EA5m9a7y*lW)>8ERY>n^uz%KkS~8+B4vM8_Cu`&Jw# zB_vdJo%4k|3zkjs`Gd0rhAE2p+!yu#TIX6vUwVSPr zIk`4;!GNld_Vs!Jb8Pp@g94140^`a9-pn3amAwe@{SlgxT;m*~_X^m_LpeGa_zTdN zRT1UpdxO`vf=@ZcRP|M!@s#8VN+}A}o-=31-@gy+0JYgqpu)+|RUPjxs|hF08$U%K z+YH%bL)Hnqf~l87V0VxEN`Mf#Yma(}2hD=|$;8WK6Glazc3^>-8UP3jt-4DkuE2QP zV&l`|zBY&3u3&d_rUU01n$}F20x#rQxu(SH9^1ZSy3N&Q^^Xb`II{E8iJi` zlngh?WuIq{0md4fV{NvQf?pf}r{3Wh+N3|W;K{iHqM$mVIYOL6|7-GZsd78!q_Hy9 z8B3c6b`x!fF?%MCP_01ZoTqB!cO5xjg4djlZzCII_9N~K&R5q!Bj*GLJ-BA{>jV5X& zbl@N+M(5A0Y)}3MKsFb=^VIISxeaMB8&7&9bmD3E;)Ro{9k6$qs<0I;9p5 z4E#oe@2wl(&PCzvH>2zK3aP|j?C7mfs*7mJ;R4A=rfZ8q%@LT8k*HmQyo21StEsRc z^j*AvDEKQuC5qRay#f>65e0d%QV(jVE{C4NY_R&-a0VqYqep$Uqce!e4iF^m>6XPM zVjyc*eM>A^ifaD0>-JyMUcS}Sg6Q^l<($@azXzuziYUbcpF2GHsd8Wmwfm)OpfE)H zu=l*6SRh$PM1l3&sE^i13vHXHJ35l1L6hP~cW?_NZ$BbO?UX!PM(74->;+dKmJM~$ zN!h>5{R;qJmqTtTFE`yt6lp8CJ=7y%1BvkSo|(o3&cL6`(b$p&$vdc@6gTeLdwJHI zOA#{Osz!{)%PIS8Euanel-9nKE#2^U+OFonM`TZiUDk?InS=UXhFsnASQH|UGZSa6 zB}jq-H)KkVHJhCb^%HOGJFE{C?eV_WtpqyIyj7mmKH`X!JQAaAXrGR(NLQ)qY z5n8z7^j!pp32V{bwcleXeRXj#j;WDxt<#}k^^MW5J$p@Agu%P~VU{iIJ%=*nQcTc< zTf5lbnOnH#dc2=9>Cy^SPoKvh#3@F)0cAl05lEH{SVyBigi1{2;#gtH^Bc9TFE(6f z-hUW+bhQ$7^3t3{ThHFkp1qIdhzkBU^2BSyKU0$xQf_SgrvFa@_{8{Vgmpsk; z)t#SDT)3V0_z#kofb10d`%KxAO5d|C^94DzO!;q>m2sif!aI5O2g)h_<2XIwg1({% z*OPhrMO06Sl||he3T&tQ^#2Rq3QqW{u;gDN*Pv3V#XMAh%`R0&Zx|IaBD4v49FZ5m zbq|KE5TiznIgVnNA!nr7m!KU)`!7JYT}fJzfxZ){!Y+lx_wRf>*l|f%{3-oQX zbKJq3H8OwkWg`#$&md!)!9Ld{6=}x(uzzu`j{xn=$@)G(H3JWjgk-`3Wrb>WnA)XyiX|VP7y-R;pcT@`Om$gWJPfiTA7yk7oO+5}`pd1Ot zzSs^oYl*g%9Z$B4ZU7Z47tXC0ROno^wGjn=k`O8a>v==#CFP6$cscnFTU-$c&??zkstAd zci{!S`ZV(U7HqU8{YgA670PhJq^aMfn))q8@^tX!%M)Oub< zwrju5G;3_5Zuj_Q$yz?z0dzgpQ?#eOTFQ{8X`)mj)Ui9t{Gv6;UItmKW09Q)6K`xs zU2nh=W_3^fhFhDAlgOU$u0{}T3YY!jdKW?X4wSn(y~`53uFg-{hpPQ24)b)_$8}Vw zl~CB4M)crML1M`%6_MOR=Z`N|-psxyz%L!Xvo%fLF)q1uCnq*Huf6eJim4v^cM+tpt{~n zLyT?4m*v%q;v%H8YP29_U?w|Vq$2cq8}1}bL){{gb@kd{9O=f5Q~|0y%jfALy+%D| ziR)uI0h^v)Qz@cw!42QhyJvhBdNLm$1rH%oCUSQ_S^^vWk_cGyU14QH4npUAJ8jZ~ z+Ev1v+5o=clCSHY5oZP4EyfM+&gsPddlm}q#n7fQzz@%tqEd^R#h#Bd`Bw0XAVBqgaM-#z&e!Zan?o6I&U!@JloV&k6_iE~j@_RQg|z-|gC# zAEtU!8!9ZcdP7W(6d9>YUk!srPQ^DrY_((!lb#^00fJ2~@~2T8GnMSelB=yBDH%!h z4}$F*!FJg0>4I=8NN$^_Fan*3rD$bZ`g%|(_|c_#nH$!!UsYf>I9iR3toA`9vokX_ zH)>15vov?V%IEXrug96Y_G7q=oxsLRe@D;n%MUNj2_osyCpY?zpM8s_yujESVLHl< z#@AbG!D7WiNwcsO^Zvp?#8DPh5#Xitur5UTbQ*IGvhSg~Zeow3UF1oQbrIiz))sxM zeSkPx{5$JcMI?ATP+QQ>oLHSgRL=Y>*$%5@tF$>hr1qo8%Iqtsw^3ARD>%z_He;O_ zg?22>PCyM3WT(F(uWsYDzztWCL@l_!q>ph8!*T&_5qEK5;4Z08Qg?YQ45cAIH+fSS z@H#3D6d_Is{SDc80EMjx5Jkb^tcKS_0G_V+5vjLPj%vg6zv_@&F|tvG`djnje<$r1 zfszX5b+>lLF|czzREE%<1TIj+=%_--Y%+bd$2QBU5-uH zz6R#Z@6a_%E^m@Y9!mtE!ZN?D$|&% zuy)pvE>)zue%;xE6{R6IwJe>}*m3h&2O=$xZ@ql&MmuGJp`nA(xrQI#4P(dKX~RPWM4yHwX!@Q>#za*|(olHU`hDgCaWd6vQ_6QKqH&(5IKSQyeBV z*xv=VHq^kmyf<}w(Vd|mdg=ZU)gSmkADVWgwL0Bi@?93(S{;*uSM>K)pzIdEiTug? zR%+0PWS_;8az4GkN{GM4KF>=*$9H9?r*}qPo|U<40n$9zK;gro*CU5yxclWwacC5m zG72r65f%~@qgTVp4_@Z_wm3kqp^OFeLIC1TdDoi4xv9mX+!1BJ-!sRa<<)-&4|ydQ z@r-2Rr(WSp(78n{IUBTRh8}Rx68Vm88(~UsfgLYEKO99&GGAQ~4lK7Q(|&CtfTBHY zy+IuX+FRZmpHxpLzb!x4b07SjtEL6qDhj7+pQzE_t%M;_u2d%<7ha(CDv82;xj09x zR@pdl?W({iOkM~hIwrXfh}sK2YyneKVLQ}VpmkeygE`m*_0H_3l!-`P0_-hpSd1lm z4|&LytN`+;!pS`+oi`%)n~)IBL1&${eK{J3qr(Dy@-=If!WBhGa-`q3q(>8jgcsE_ z*n?n?;24o{{H(oDdmvl08$=tzA69%cM!<$LC-XK7?%Inw ztds{FivcewDQ-*5{F{4{7oV5>>`$oSZ(mV-8q$7j_eltn5*p*T zf?Fu5O9^i98p<@gAA47Ynt=N9_W8x*_1M) zKlQ-l8{!YZaINmHMb7{Uk0S`xl9ieT+RSP_1hMzF8A-``ga@4mV}~G})1YDCK-27- z<^Zx>sfS<}j?JSx-1Pq(T512!Ft(Gq_*Tbul&inxoVDNCURW!uIqJr5{!HD)U_aMQ<}cCwJa!>mO2Ny_vHP$>1lYh#phXxL(Z;~nwvp98Mfcr zZAD;g5zrGbAuEAbzOl~pCbxtaI^yMGp2}_$2gZ#ir_bKl#(MrpFji>{qGl(~8G5ud zTRlLc@F6|glBZFu=kzpCX3R1eDCB5dNBQ{z4A<^HE43B<7e&D5d^+}9F1LO_<2(JA zz?)wZRJRiYI+$0sQKwSPfjub2!j+Fbl8ys&MFHM=u5vx4arMG%idF9Q7EVi~dKn6S zj~HemlMl@_T9W1lIC-w`OU^J&uAgA3v7dbNH2sN@G7O}`h5G=Daveo792yUs>O$_w zk4?DsEYhfo_6uy7{>{5 za&SAc(MSy(>Bvq1Yrk##aV%HOV??-#vK>))s3GLK|5OD2ur7I(U7c3)jr{dN#d-?7&eym`{PDbs1%Lv z?OkO_poVc)Ho(Wxn$&4zzoz{$;4~}25m=Ccc6uHen{x)XN)rBbKp6@fXiY@t^yJy1 zqn~d2ibVg!(G1utAyTR1`^JQ4>Lp74@!jGgO8S}O?`}M3R?%kl_ud0ef*MH6_ym9S z07LDN;9OUe#L2ArqNP_)@8^V<+#-%GVjI*o7urZUi3`DpL<;Z2bRPNkl5=M5xxaQn zmvEQpnudE}S3TO`L!?PO=M>#+13U8!*uN9s_8rwInYLbv|(N5QFYh%S@vpiU-4CcUkj>i1c?*kd8HYN?Drgni#!UXEwYM|s!1 z4_a6_x(l531VB4lr^9#QIf z*~~0ggW2HHWqR)TNw~`6&AO7={A(EB*#?Vkw3BG)mc1P7&=W`~IopUDFA0#?@E^L< z4013yTIK@pxNuFMxMmiGV@<>=!sozo2?zj|$r6UW3WQ56W|woM@Mv8G9Z~n4)xasR za?lq{x$h5)s^vxZ+{h*t07VQ42R%PA(7j>GItwaWqOH0aN3e!pPH&Z9r6wUtzZ1zC zA(C(+bp(9Czen)C=z(v~M(=?_zWx8vbl(3|{_+2RUDp}*-uoDpWM%JjNyA7~Dk4tw zjv^Gzt^RgqD$YvP1Ufoa=i&-_PyyADkb~d7bC;`51S$ISQ4U zb%#Pn=k|Rc&-rGn6VWrn^F$~Km&)gn(M*2${DH^_peD>?-*mBPc_6?O@iVx@e)q~p zY(4w6wBaylHLn+-bwN}9dl|NT&tAImf96tu4`?Au`oQ9UAem{K#uZ-k9|ZsC%x*LA z1ec80{f#M$kb1E~Ci{nXN+QT+bCcK0PvgdC2*u-EyDh*hE@3nIOVPa288%f*#r8jP zWUe~z3w(Z-DEY`8DMqS?MolIU(D4^q;)+NlQj0OwYFba=YSTBWtr_E>&_Q(nE}$Wm zx9^wIMPSlJA9H2@IahhMe_+p_SYJV~2=$6)9%pBub6ngLfD?xhK8U>F(TxOio1*1d zmg{f!o77o%ZsyK`Z-9P9ozb^B|MT~`R76q)Anv*D$doe5kLO^OMbq zlG&{PK}1}rkLC(gf9?8QTqthbOdV*#Ml=|VPPw=<=0Bn(a2AH#43`n8NnkIs!*DVB z8Y{}X5|161NB6-ov%he!`r-G}8{5T6JR@Mzg5(d=R3HVe)IY+9myaz*+g}wWIVy0K zI#=*t4GvJ}-^_jytpxi`;|hg^O~AlWapJ81CEmeJ@5Hun_jn%W1a|-a{EOAb$PZWZqqoK9crhEo`d)IC*xSg4fxWiZ<)v6m?wi@(wTq4fkm*@YDH|P-Ee>zyu?DO;s>~y!oUxptqD(v z5>a?_)LUFtnRM1e?$<(hZH}V6QdPCll~)_KdvknZIikq%q!_+5Lq; z1U%T;bNpHiR1JhC6707wH66ti32bi?*#2DVS&FCgZuB3l8&Iw(;@H-rYp6NHF(HeC zw#hwzKwK=N*Ka@&yItewemnJ+%QJa=;T`lt6=!DD4quHb*4Ucz=^AeKg)&Hl7MF;m zvKc1>wN!&6OT$fRt8@cg^~+mNbpts)7PFhKCTzNZB}Efoy>k3Bomh6~Y7gu}f2?X0 zfBS4xyyk3dG_v%}-_>^4z7yxR^|kvxNb-XC!Er+Mz^jeIlc$fef2~!@Z4kIX;E{{e zlWu4!QQCV+De^R9irD-2Pt-Xil;g`dzI&tT4>hY&SD`&Cb3@35Wz9X1{h7}O%6xp< ziZ@k%%jW>7qQ&-i;AdWvr%T(X_xGrBT#JL!F~6?RJAHg``0ZfrM*vO{NE%0E?a%rz zmbtr+!v>t0eH^}TFXs*(6N))?mj`btR{P_a)PFwGLs8`l1glHS_f8#nKxmWsgJA`Z zM=oAGc5z!m<0qQ_&^OhbmvrCB<2>jdw#;Hl*ca2}jb1IVmIkA;!VCedyZ~pE2om9= z{xz_xK5Zw=%g^e!o9rouX+^)lIgxLZ+g3Kbks=5E22amFZt!B1c5H=&iLQ`U(b>ShB-~%`(5QIE-o?MRRnJ$teIF*TK8}pMxD@2d+g|i=)m|<396(6BqIU)r zO*`xsDo7J6n`e#nZ3nG6y=`~#ha5d`wUdc|ZDlB4OVBRGASDO1bt{f`!nhY>XMG!} zr{7U(KZEi@<6m?U0jR}8N?1d#B9P~|h6rLKNxHkGYIoc=QS^74y7$pfxgOdZ&?Z4= zzB>E97)?$MGE9fICqL0#<1t&;Ecox@O2=Q5yW|%39jDEOxozw$>UXYGu1}+tA=PlO zK-JZ`pf_mr3vtFNI3-C4-H#dxm0mBVi{HS$4w_^h_%rc*3u87a^s^E{Rk-sYaMS=_ zq{RJyIJ`EpL#`zosG!UIYG}eA@^KN4V=QDPPKGjX`o<`>3m&ZTe)}*@EMq%{V|6h@5-l)5t>sAkcMgg_mIkRe7(E3$Q8q(}IGv#(r|6e|rq1o*0dSa* zD#};tQ4|`geGy)>M~!IUGTX@_zW&9hrWL4yQ&h5B9ljIx)tB!CdXhiY9@oL)x_<$E z8maStvhSm(`JCO*3SkNgA-s>2b9d&06XjR#N43ZnpyFKU1bIImP5N71QGX34>tXNK zsNrP`)NmsMe@;H8rl1XQ4EYoe+hpCvXl z`X9q{N*KOyua}t`hMtt4kLo>2oeUw0vBmISuJQ`i$mU86)%eVQE_EH-ZZfLkdB&rCbUg{l)S zebt631rAU!uKhE0%q(~Koe|DzGqL)%uPX7kJM9I$9PFx2*nHLfKK{_IsmO%GsaM+XKMm0k%V@vzXIEvXN&x<7%ha*paq zW4f%A(t7>Z#%dD-ywI)V-N*3r*clr=-M_f3KZMWf*fZ@L@r`zp<(`3C#f)pVEyqdV z+F`~P|B{Tkzmb;aj=MROCN8&0`b*LDQX)79%soM1e@PH5Y>l*=x@iH^p5}6y;OH&u zB}Hcg7x|P(Nv4Hdp6BIs9}Yfv&EtG%>?FZi`18!2$hawbP-UH>mk{7qM|ZQ%J?V+N zf(Ikqg+HK(tHg3qBpG_fkIS1P%VmLjkWpgN9*5lG?$VHSoPAQZ}-?ghAQ16me=*$xHs?SYbWNG5^vG8ihi&>m^jT~UH zmls_oXR?POc}|^)At15j8s6@?03OzT{d+DjaI|#_s7gtcDt-MjKQv^9WvhT*jrLzZ zgN#j}jzH+!hn9zFlu??bygzCPF@5{b^Mc%{I*Q7>s#?3nXt~MQ z-qNR6)I8%1E zEIrNGq>`}rESESpGHe6ci*^;|6K}>2@iM~bt+Cnfa_CV{<$OTy6uxcx?mJe~Jsbyk zepZUc**_tEm|}vy3WYdIxAHW-dO)k?=B;Y_#7%-d(C%CHtB8E_UFkzDnYe>d_L<8> z%R(90fc$T&3l3u1AVYK)C+rYW_QHxI^r~|#{k2w)UqV(pwjIF8w-e|N;;)+-*X`_1 zz$H$#pmxs*U)aQ}9khg!gm0y4-fWKf_)2d;jlF>jBIpiNvxBknyI9YxJ1^|oZNii= zgIok@;p_CYkdAZU9#K*EJoH1YmzFnozq~y7@F|eITENA0dU$Ems_5{3NKcXz>rf1$iJ55l2EbjMc=uU0 zzYWNk1xF!CXnjT+d;~2!KImo*y1}0IvxcYdGI&uJ#y1J*+okK?z+4@Ezd(L@7g|pH zL>>i(1xM)}DSaA}@F}+G3L$<1vY)z}%#p@JFv|)@EVX^s(-Fi!c{G z%YU$r?!70s|2CfFLXtn8hz8tR*$NesLJaKT*m0bnI_F~u0_anQBh_mZs=Tpfc1xV@ zAGy@awMOC23E;z~v4dkEz476M#qZ`JZ0(TsYz-e?8gNr$Z`B6z7*nd?-N~nrXOFWk zY+1Yj>h=O9A;hV9MhJ(SI_XC6L{eFcRq@+a&ICqcrguT8zAXCIAE$Q^q00aN(zmBW z-WAGs1cj?7Ez4%Xx8~g`DHhqyDn2{4e;u1I}_$XyDe? z&7das)9CPdiU_B^DYiO8dnYmd@8x|Z=Iu9NH21<`VCCnR-) zpeK^LV!IHtP256CBf0xoqJqbZeK&E)qF_MW&PEVeS1Cc7Uf0nMhuR&ku=ZV@*lMFs z(!%|cIq748?q5w5W!wmcVzl{TZ~`r93;)r%^h8_{+*d6Wh8pm;ct7cQf;Old*N|ei zNI^87()Bp&KrRlQa=pz35tt8{Kl9-)T}K)GEM4NtpRTmy5fPp>Zi05KT5y&Ep?lPG z3S5gR??a%9BbKFU{HtjtkZl<^>5K*;j2r2>6uCo*$Mv&4$7M5XJ+KU=BczaNA=x-X zaoTrk1lFLXW&A!_1IJy6USFAK30qi_io@-=85(YESJ{UC51eu%bQhqo$v?X3swN!0 zpg}zrloZB zaUjddI4v>-Poe^WfPqio1w^o40Bi?)rvPm+o4l8!e8G(SPY(N>E$5cjzxN!FJhn`n z4thFobBEo>N$nl0JJnNPN#gCe!g@LrilzU-E}Nh`;JtCj%T&1-SNx#)pVRu?_=HW2 z!Fdl(@}mtPHqs7>B$GMR55rOV@tw?p^ahU9F<{9F(eKO0Li9-ac$e#O zXD(?nWRHNDtvfFwKZUAvYWgy6lwopv2QF?fXRab3E9)h6U=Q+wcl_I}s1ZU$Oz>Jm zcfn`T*yjX_Wy0A;45rXUbpi8oz(XzyfpS);YI#*C z&kBQVV~SW1^gug$=+K8nkM6hvkdC_nd(N zfzg)h4K>n-wqk+dd$~v`_ywa@*5-5n9n{UlhW@OEz8s})R`;9oF;p9Yv58Z^QGPJc z1z>;X&c7ZOtfm8aQZMzY+}b04>1uO%tWG%}0ta`$63GGianP++fSLjt%luZv;*U8j zMW5sT;ZWL|{6KsNwM`T}10;En-+uu!O0k-lkF~149d&;Crs%N~ll{q;SF=B5Y<&Bm z=SHn9t5qd``<7&F!v%Oi|G+>7QT95>{Ga92Z_6#MZ-RQ3mT67W!XkDNy!cSr+>jHD zLjTwTgzo2zt294rr^G>E|&!@OqU3ByugUsYXv_R%;WYErIvHQPY ze;den{34k7Y(S{Oy1zh0B$YIG6k?5jIBa?)fV7&y15@~EE|N$H7i(;d8i0U#SX2Z3 z1Edw036ZGDrc=IB+sTCiJ#*|$FI=}$7*QwqvEnT!_VmAufVq_^12|h5^rxkYrsm$> zZhp7%e$L=JHRx_krYm}56|}G3l1FETxb}joNL?A^ZuEWV)Lfk1LmjWb+bk@KCyA7| z1)_KH!7KxQq~ylCwcJlRP(%)qGBe-u*b#XYwUcpZDy=%Jn` zG+eDh#>k}ttAFOP>fglDe}cmY6rF`f#kRo_9Nn}|9F#l`4u_sNgpRS>o4TyQu5Gk@ z9=w6Vok!TQ@3?^72Sk{Xg6&eaJ0g$&#(eVD(UzS=>C-Oj0(dk7C3uBROt!wO1}dwor#G=AXx3R4`VYUD zQA-Wx3iek6A-S7k#MxWNmcmQfe6KKvD?=$_%-=#RPjOa{4tAS!@XHMb-QU9)1j)iP zv$-u7njogEVbv=U(_?C2Js;WgO$mTm@+=A^=X+GX4%$!*sHP!NiZ5pAHjZtGX0C8aL9=fZdw&dZ|y{ zv}OD}4dqCoKEOiv4CBn%x51}~1|xnH{XN)o7vQNNYd?`1`J!Nc-6ln_b%9{$!N-uBj6)-WVI0?h2 zh7BHla}3M4J<81kmGP!tNdoWg%j<$sKw8G+YFT?AU6{bsGJzThPLA08QF>a$NUVA7 zmNbXE-6co;R_bbE2ln9Cwg1qSreLd+thxH`KC?~*WXz6)3L*^C(~E=6X4ir?POsvD z2hNFE@P~sNLiOOwHJ3ai)a&SUtXjX+6MmMgiF$j*fA9(ci=xbDu4omWV*G4Jtg}HO ztC3SIBTwPet@aF=(jK)_`AN@Zqv=f6qA>?yH1(2w>?tZ==^C>rm^+hTiW)dc=7^Q1LHE{24Fn13K&~jBUlF|q)a8-F zWN)Hc3;D*6WLv2ET$5nCH56k*tI%6mXaSa@v>Hvn@S0dLktlF+=z|v@BzFlt)D7A4 zF~0#~tK}7)KxwoLM{s~vq$kD0z=yvBH9T~y0ACXdH~0tAG6!lB#z2*5{knsv66w8f zyc{@!O%77a1JM*Ayr#UD=W*S|#`9}SlbkQj`97rp<9}aIzcsyQJQA;k-j^Y1LU$_v zJ-CF%hr1ht4AnMFeB$AMzgxeg;YuGjeY1RXeVTxl2vtG4LVD*+;oqt>MH{}<5I&U1 z@Z++8zp}NNci2jgHI(sAMZH@k>|L8G# zH){oF!QcbWDxYOSw`>>aTfns+0m??`#iD_`kJ{WJ>G9s3xH8T`YfiFF8!kPY*+;)4 z{H!@j#F?+h3}2;7J(KeoXF+73v5{|Zq$^M3w5DQrA1272DkG{U~)&^qQxT4SS!YZT} zFk>~S7VKp%du4|avSP36pTm{TK9vjhPayUMkA9bA1Vg*FGez#8y8P-P39&i$>hbWc zefE669PIEw5)X98ob1`VISP%hqqHytQYP(HfoDSLYv`WSy*F3a*iD^%#Yc`b^}Ses zDU_NQc~}A+Oi1O5)E^1^LUy9!jdgQ zKCu%?*!m~Z=a*TVX^a=$+WiNiVB>>s$m(?xTJ*VpD?dX$@VEcArX4;chIE*p5pa** zr9#RY9PRCtLE|?QwMv6;?C<)ddsu<-PfIxU=6277UK_9P!=(d#~v%5$iF zDH}Hi*Vf*uKjB6-u+_GP|E^nu3x1)p4EE|aEgAHC+};~eXV(?JwvXdss8Y-JGKC~0 z>&yooVQ*Ni4D>lbE*J9!Fa9{I43dHKpX`+TVyH4^x>wpKmn%I(g3-l~nHuyv2MGC! z@fbYg1dTZOapH<`5A{d8y9Q#*-!iy^Lq`)}zB+f6lgdtS6~lq?OZ3RJ*MUF4WMws* zJXKKz=WP|6#6<*A@D9plm%ySB0J^a5rsuf_HLtN)mI`2ge$VR%vL=W$bywnQxfe~Uj*ThigbH$COZtoSeDX_8^x@o*4 zD9Z#dJ$+SxJ4Jz;QXmXkpwOi1hilnyAD~)%g06zhoo89M%gdBWqe+MV8Sh4DI2z8* zGF;wIqI63l5+Wih3bcSQOX)UDVatVqOhUqll6KfrnZuw_skN`|-BpjxpYGzm79= z`W^rGiYuyi4UW*c{=WRGZJCQfMDm^#zEK?gp$?9ha-=h26i~*4_Bc@afb$pgQ9tVOn_XI}UBMRK(9C!LVRY@pMX+3!o^Eyvt*o#5G=*>l}*Ymc=Cg|^ru{zw4*#4(S z@{DM~lAHuS8h$mh-f2`&7EO10kIJIIg!tk1YjuKf>8w7Q@C#cHF1&MrEh@kE$DU+>JUG*N zYpV_jgv9@i_0CJ8yVyz<_oBIbZivXGT7&9w#&bfkErA5}d>9Olz$sFkLNHw}X4reG z^ZNA{TM>BmN*K_8iYeU{V~15Rv}P1F>Xbvc_XKjy1xHv<4M30-=#Y@+#eZh_*etw>MfG`!hb(|~-Rb>t zyE&kQJ<9-WQmwD z&+o`*jG`x*jsRGs1Kr!|lsLRJgs5is`5e(=aR0(2<*?`yH(H-f0E=J+Q!vxA`=*MkTMPrdw}sPOVCY+EXGu^ zCjHD=yl5cKM3^L^g1{dJ^zjry#$8LB3mIIcx4MX*+Mr@JD8Nz)owM4ZjhSEt;=#8x zz-k0V0XaBpy;!M5WV50wJ%@&=CoXrM*x^`{-?1?`93-R0sfyaBC4J#R$x&Y0tzetv zddOUfA5^w(yyP&#x>OxCGim*!8!DihEP%!3q~)PDZ)@IjARoS&DC+U(=pH`6@e=L^ zmLXDtBs~zlA97XUYlk>hUGak4zhqIA+PCyVk4V_n)_LcT(~$XbtYSbaLg&X6&#}$6 zC#x$rmTqw{*Vw{KKe^&)uA$Ytwe&O?$4v@8zzANI5Qgr0jqvojgH3V)wS-HZb0=>e zbb54dw@lEbcu%eD z+aifnHVZL3&^Hg9^wn1%>H(42wk`q6&OE%XZYcyLk23O+*!Bd20(AwX{(bY->f?7R1B>m z!j#3}<88P8?&i7TBxcQT;DUC?f1v&y3ed4bK7`XxyMvCu;Lljd@B1<-k+y6vDW8hvu?smMS9T&cwaBB02wzjuhZVg$lj zu{CkqGW>ZOy0Q=P22^IU+z)Q_oLtQ|0jj2|EDv$)<6xxC$Uw(JuiWV~e^V4ugU-u; z$yL20TO?=!ZT<~_(%h<}%tQI%R4Y?2&flJYo-2=i@Ar+?G=N<8>hVpx{5}ZMe#E2s zlYZqlc3H`r%(JYvA_+=0*B(F33_g=AZMjKTxMtYz#b+RUQub6SiQD^fb-6a;O$heX zTHA*caCQgYc+JCh95O7*@lgm@MaaIu2!^0^r@75q6Opnf5mSzaneNW%SU<~t157|1 z2QdmHah{QUl=(PEv)R8{5?}_{roGqVcy_&dYrS@X_wv%k0b?>+b7utFV@&=nPvPTr zcFcHi!ej*vX;cmO~fBU*UJ`y;G6+uAnaTFxC5W?A);j z4B4Yf?xLjp&LSD-01uS;^wD5TIzMG|VYn+_7wqD%_YqiY_}PDl?WG*EGAy)>+u5At zux5OESBHD&(l>5F_un4SMtEDcuRG6o|&jW8yE?9uVE$t(9 zCfVOnLP8vJsjJCGrQJ(6cjGKMZK&oxX`rZy2bfPxb*wHPK|>C~!CRfrUm-GmPW)v5 z>zU&ZV3a+6p}m@Rgj&0lolXF`aAi$0AsVvP)HMKJy8)k8`wQ6ROmu)`svoQg+U3)G zcP^ll#O^lRT)iXBatX*Kt>61tV z4Z|JZm+9ggqI&cheazwegWmYag0n&C{szM-V|6h|X)S|E#7^e^#CuK_^=#c{Y_acD#?|s(EbSb%)9)=alz@wV)Yo5~xx1k8N&V6L|a#gKJq>1p&yWct3+0|~!1tX{NKgO4F^ zlo8A&Y_7oED&h{jP_1Sf1(`l(CPHZ+-Y59b^VcWX zGYQl~tdaK1S6A15bps#a_SWlb4jtrW!h&rQeT!U|-jzOf_7>E&=^`IVB0B=$ruqED zA}t%jL^~ke7nySx30T_vc{^Dk0AD*xz9)&UwR^+ur~uy0$@vSK_F$j@>2fI?g4$*&s|XOizGc<=HR9uOQ!*nh%B1xH4`4u1m*p`3+*^RNuUx6u~D>urizO!OZl*94Rr;YWqKkA(_A?DcSRQvI_FUEUXI2oPhbtOS< zB14jj!;h<=a;Q)o@O*Q9`->ba9+;UK|C28vc5gEtbjdqE2%gEQ;&*xz>RJ)iHo3{k zOSV^-yk%gOzfPPj3)pk95Awh<_%XDeISZB22jmgoOXmwO1nxiOswI%TZ@u(sE z&$&culNdo-8R7WXco~;`xIK7rH`T=u8cI6;X%KsCCI*JsLoR3*FS3_9fqQ}ybPFiZ zwCH3>K$5g2p2_e8pZ?CaDCY5e`sppgQapcomqz%EW7E>M`g7dGsM_V(ND5KdrNn(NT4qf z%5#JQuO-K&aR*-%p!339x5PeasMJPy93hOifT){z{!G*DXYbuKFNJQ6t=Gs!Iy2p%0Z|JHAt=BBbIQLL1Y57mrr%5CNKI+tb-wgHmS3 zH?H(!kyqm!i$~~whwI{8HLvJM9DrK6NOP{JiX7uk7R|}6 zz+K#7ySGGf#KXAuvRar_s$r?5{7QJ({GZEBi>+`fzwn_r{oNanl~UEAv|2vbTTzW~ z9$>1KKNG;B-`kJsnRh@5?mAu=MqV&DMH;8fNV~u@+(!x2ko~rU3-?$<82A!BdPOfl znO!+#6uU2D@=%i>cK$g(_9UU?Shff2zMk&w)9BUsJ!7+`+X##Z`aTiHq|uj(A=ZM{ zrJqFRe7RVvm3nJ$${dlHc+GMWw^+4ar3Y?OLoWy2+*Gq(6}?L?U2`+tl$+0X!>e=a>U$#I zye4;j3U47>6i`jqbx+u5Z~>d8Tt<`RobMb~jm%errs4>Q0Hb+yxws;JDf`iEg$+*0 zXP(wQ;7> z?>B!=EVS15?JH}R^6Z&)6aL--L!ZP_*6 zHlSRvTq(nz91ghg0lO`T07Mb)Z_aYcRMIVyHMzjm0PiqCdD_MwPqbvW8Yctu=IseH zMZ(@cA{RFv5%1xRzyln($+f94?j>O=a{Y^PD*WIH2yWaJ$N20skrpe*bj zX7P7J7UKw&x_(TUf-uRxI5RyE7#7agYZWhBIUd;^pc-Tj|Y@kFjYh`j!r9~XwG~b|>->{>dwW#pQ%JR&E zOw^W^EVn;}`c=2$rVKtDJ%|d#ZjCke*MrNn5xEgCJM3|3a<^ds()NNrlnd|uCCo_C z6);&f>wt)-k??X529@%VfIp7=F4@>xLSGqrVcT4c)_EPZC@i%1gok{L&+&uR@ z68uP+bARBS?3Dka(q(=|m-<(bU33&|YMxGPdD^J|S%n?d($aVzF`KxptQxs=Y zHILwv_uYiXwQX_1HL6fvGMGNvUgcmno9wK}zOO+$_Y{4#eu`6w-@Zs^f9d+An_Hyz zjyv?gJjuc#9hdYDT@y~vjL3b;I0CUS>P2&}nm0Rm`hU)sK#BIF3~E4rlSQtT&3?A!CVt;+6LKqCS@pgxr6ets7;*XhpWPuXG^0UdnCb6 zQKaAdDaUZZFCKCGFw)Ok1Z=(rPZy$Fhwb-f*kznK2|yZ~g4d0mAGze?H!P(I?-pS! z{8Pquc2}~Ny|{2H;_9AbVpG^{BgPi=>#XvCc7LE!lvtP(v^yug4GB<=&~Bx6~3|d zq7a?rzU*oTGz#!$mk?z%@CuP3E2mZGFtrni+2ap+GQQBTN{uh?b2J-#roXJgr_Db{ z4BH9KS@47Am%PEoj>3@U-lzW(^OpiT5?O4V%*x3%u8n$SDsgnQkL$ZNMSvlod-HTF zPrDt|Y`O;?HED2m8W#5eA6C6Dw%O+kH^ivnyhUr9QwU> zeF)Q&#RnJgZAD)cTmEtc_oNy2;7~q0LGG-6I&=xO z&tQFeSNw+^{t-yGkTcZ<(M$Hv*)|w?L1aMc=zOP0>wq*juSln<6Va+{v zopsFm`D(`=2@nX|D*~7B49;O4R359uA^ixFY`sKAX6Q{5PO-;7K_01qA;d&4f=juZ zPsPDi&h>ZPL6-!_O5sV1UW&u8=W#1JXR=s2N15zXbJ^x(lq+}uWQDHGe1_r)B+gEV zxJHo9-R;cKyUmXjUP#=10hDeg@|9iyt{)AT=#zV3 zEU9P*QaFf;%Q0&&8{$_-&w%!$sREG|$1|CCEDXwgv@Yf8ahYfIU1lRcpz$-Id`z7X3~bP2Be+k}%(JbJtJbQh ztSTvdJ6{ee#q8(1-!GxXka|e%Tq>!ed;B#G+3EN;<@zp9RqvjI{GN_!{u7GT#QR}7 zt=Vv%mKhPe=5`eYwiA`0I{74HW`%*=S7TT*rhT=MZgWBV0sS#?T=J|r{XBnp$j8{GI>c(D9&|lh}J8M>UTM93;6lK`{jh$7A4e;$!4|Sal zpwso@$31*SxttTY+t)Cm$5NmN@bB*?Pt9FuXikYXa9;jzX6E1`W)NMoY6I-x(8~AX z?d!lZ0!Cn3@61ey_WIii)`F})n%Mzf$-GmdMfvR@Qse`$p5oXkfELc*mTLF2^h^1d z2ZAE)7#{-cS!5BeJVjT!McJ*V+ZQR_(*;x5fvX=Mna%wc5dcZ!dXu2+&DTp03>>gS zI(Rf3>Kw#R@pd1I2>kD=j)loQ7JElKhE+HYGB^%;pSG9euHJ%VJiDXF80LN)HzAD{ zFRL7~rTo1O?7x@(%s|6LQ`MgExayo6nsEgchrTm3`0*9(NuRNM2sjW{CZpR8lCooc zF9MQ`2tFdDkDh-gDD#MAl+~atIcnum5DuOl9hIr+)vAV;iJs!6OZfC3ip-v#u$_;* z;vl~bf2vO+A#^fm3G0vrp{V|9sn*VL^IM%o7HvDsSw>=?1R-%S>vI{M3ty%k$5Ec5 z>tRB(A8ud?5ut0Ul0se5lv5f`W%Z#zmbv>Rnu6m;E1BHUC6&M01IL$bHoL2Er3@bQ+7pJ6|~8p0Zx}eYRD1ovoE) zJz~ibK_{2?B{j+tFyiYs;fVu#aL3zU%?T_2*a z+Gj{4wOx=prJ82I#`X+hjY)!iH+RJBdm?QqK%t1tK2$<%c9oUL-%xohDE!PxT~6a( zNv4SicfZmuh<}R=jnYiD)c|_9_kYQUFb~#eGY12MDY_}`9l5Q~Ha_IwKE!m zP?I0@mCZ3)`It{8V*B5N*^pra3yxl&-+SfHkmvuHXyDZ0?O7q19 ztB*ZD0>2CiDEXjYpF@N+!t-BnkevDW$ zqTelJ2Oq-j{IgHEK&wMbZxnwR^f0`Zn_rrH%tTm~W|g15!gUR_aJs~1IoBNNi?6w6 zXMeqc9!Q`Mm$5oIqWBr(w7jwZpi9=XQii^u>ifX?5$cwq|7_vuzSx~y&N~V#2WIWXKxn2}?AnT? zX+|?$NzheQZV7(Dx{o%?U8op1>;k<60qX6kE}&H+AqTXAs!f{>s+mC7-n+%F2OQkN zrQg-B-k9XCgbAhoj*SCFi3)3~0?0k=!y#xF4ljy)AsDPeBMWx>4+4%y%Y@sc9pJ^$ z3@%+9<7(y<>PRN5e-vG9d`Axp46X~1?8OfAGem+LhiqcywTPofuq7GHYafo%m;z|W zHZLE4{e}nFop|b%!P#4m6>m7*uPgdh-n}@mdt=aC3H@(8FGz>FU58Z3Gy6jf@`DGy zEb(P*2K9aXVyw-Mts=SdxCQ2X=Az_t7R$k=SGSFO*VBI|_dU3gj4?z||DNnDQ!1+~LiUHrvF)c#H;~liOC#whLv|unC>pv26Tj<#q zd`uAY!5C0Gd}ZG`-e*L}lNbGmxyKa0QbCdd?PtMj>cruvmq*Fl`cFOjVVZ1#V$OI{ z2(TwG4*8XJ(x!RbVr&c)iv*2a2Y%{-E>Ah&d^%28$!u}`W&hHSh$o*f*L-ALjK3T?rO9V`S*#z* z`-9mK!5h)n`aMu}ZgQ9U{aooyt7c4K)6^sS_Zoeu2)-uCSjs}O_Yn4F9Yn6~nU``X zTY5GoGz-OnPW1`rt;{2P;MP-fPBaeXoGUjm72DST=>EFXWojmlDGM$H+`)HIU0gGl z+1go>sTbm8_!s|4q7{VtcyGE#0hlL-GX=~c+D_9EkgWApX&wBHKx82K?1#7YZziDu6$YHS>f+HO_B7*5Pv?AIc+qAS6SQA;yHN zN?}s9(FJJpI)rq;hce5FC~pAoFa5@TbK&9t>LY_HUsU?uAr)ju;(buHd;*}1O{yl} z+Y4rz3r*gz6Z!!v3AGhoS7+r~Zu#(jt#;ux*2UWPZrSU!p288NEjakz zmR~eLct%{AWv5YDMc^*T;$hN>)Cb0!%@hd8u66|86VuJ;eXpFw2e z@1HunE*X-=!?{8`_A2nI+m@HZ%jZy2>Gn40QKAUxLEH0V_U2VoN$%494E5gLXi<9N z|D)-=>Au4jgpf1ep zNBv%dJ_jEVD&DrY(wAyqvX;<08WedV~%D_j8LWqW$ zt8*uG0g_Lm^CrE5vVSKUFJq>Q8~<&ir@?c4NC|d67Kr`Cnp@Hq?Ed7My&v%7oiH~9 zdM)&lKQg!X|74GBDEAM= zdo|RddXxM6EDs-?Ap?;B2n^e$K@|*7)Wj-wY_imjhWxSQbz17u3n6x0GUL6$#s*_c z#!1d5&B~qsKEVH0l|Ns98C|DFmiPNX(0k+{Po9Q9v?i$L(|B^OoT=4Zb6&aPp6~cG zB@YZ=??EH8-Z2LFNf0Z3*XJ$hx1@yM9C_1GtkIAwc$u^=MQw%DxHCOw!dn4 zV`;=lyBzA1wx<8wcZKN&QS~9;(f@e-wA41Cs*sI~P>&e$6s-CUeXocjBhGBw8&|5v z&!4+b`*Lk*z0eLFZZ4tl{iLl;iG&ef7@xnZu7}HYL1K({L;}Y>bjGvO%4wp=AE+<* zfN|3OF`oKux0rIe0(y`rGVj0E9}GCNM^M77l}T9G{v1zd9I}*~n%tPz-xIW>AM5%8 z4shSjlwb(uSur_c?t%+mvygr&n`9|X{4mFzn1Soa~5Rr z@Vnp}wExHk?Fv|ESuSIkDs=BRh3G8j&Tw?<|1L|| z4$mB96s}%B_y`OO@Q^biW6ql{hsW#Y%)fmV-m0sfqwB}64M8oFSPSt%I6FbG4!-RG z4_m`s) z*BNVCFk%DV#U9Xym;n&pia8&gz$>Is?#c2oSEH#(>u^ z3)E8a_3YaHhZVrAF^S@MawO}UNcJFjL%eyNQ+1ndSyd3l4p12MYn?1!KY?grS?4_H zWzEf{Ezm*@xYNn*&F3hI8O93%t|X=C))i+s@-EC=w1@z|MR{^tEfvs!`vO59JkVQ4 z(YWJ6i2a;Uhd|}6TF$>EXyvC{6b>QZc;h9-;2S@{Bi!EG%TaW|rM$){p$J!G6xt*d z|9qEAb+?ojnv`gwPE=5!W+$CX1*`9eL3B~snPhM?=SDu{{zvT-iB;AG4h2DYLcOmX z{Z#~%&HwSr+sn~gv;i}x&GUWA>&XI8NbLN}P3pD#LW6)l>A;yGh2ur1!@3{gI%DMX zAiUj^I8Q8k(PCjNv4)@?-*Ysac=;m!&as8CV18;G=O`987e8-9kV3tJ_tEt%SAVa9 zu9Jl)Ykzfol0XNc=vwID@;2l6gJ|E1|y>KjJr=0DhK*K1M8qWR3(Lc5FRvU#|}c;-Q9 z=w0&nO`8g?sC*Z4Ys8l=h;1Yi*3(;PZ=1~v^c=8a%Kfws-Cf-Wufh($ykQd~SXvwP zz|y~oVqgG!+W}-gw41u$oj6;V$XyiT)ed)*t_=SYXCcb z*~EG?!1Q};Uw9-9%bw)-*OCbzu6|)Ub)#nSY2+5pe~XLzO!8Z@_dA?&b?vd`wPEBR zv|%r>hd+P7YW#&6}HtuA8fVpTnY+b zB^LNojMMnRVjr31$ehDc1p-AOK}i<2&5SkNj^iSQdT<%AvltX5Ch$Sqg;Gb}LcLWc zUAbGS^s>)tix4R94cND#Mq-wn0DMB_u_Z1$3AK$Mj~Evv$x%!E(Tk4{L+3?1pF?*x zRmy_$nJ^@Wc6&$DB>Bz9bB7cP+>Ckrc+F<#BM*JWU;`CIjApP>>f|_bXadT> zg?~wU?F1nUiq|pQ7sLpgRlEccJ^X$NLr=~%MiEkwwq5H@I$SZwXP$CA5~OMp`=5*FOl}rV}6ilR8Q2^j@{z-|+SQzujn?+}Urbx01Z+ zbgqHyjwxt;gPpKLr*=^Ur?g*64=b-eJ*573zZ5TXm(#6X5FxJ-B*=i+T@aori1Mzs zL@3hE;To^34*w`}!**P1(t^HQ7*%xr{hQ`fha=h25^#UyoEQ_iWyf{eBQtKHKZq;V zOw7TNcyyydNag|U`av0B&o{bxJt4P zt(d_2D}A~9_PB!YP&)OS+PUNT;HFyDe{QFb^Uneo3b3VB;q4yHD~Z87$Krw4-QPgw@2y`|^^yX|H#gg@wvl;<0)!fv%nX9I7`Ll82 z)sI1}XeWrXj2d!-$(q#20B=%EqEiDBDDZDXnQkhhaY-nf0Brq%W67cWYUM%px7$xz zO=1QTkLO4F7Jz7CWXBF5lL7Qd^;4kpRbO6oEm#*;@-k1x(x;1~=Ed*jXaHeP1@&qep-F7K_cewI8~oz3z=; znG&88DPi7bCdmXN#bfFpqI-_a;9VY&5d+Xr=)>lE3CmS*H#|>GFz?=I*Zv1% zZxfDUQvUOb6Ci|EQpq&R#zzN3u+n)wQ+-xW{5P@DOe(h*K>*h!#|aCg-b5OU^WQ2X z$oweVHhd)J_O(*iBnr)@9lf74{} z$=F3<<>OfuuiC)V)FuPG%AuxxB?-l+%P(P77huy~sOkwss`qXh=-A7A?hnS-#hU%fv8Z0}|!toxA(=$^H!iu{7;e~k=EnqtYClu=}{tj4RLa!;IvAyRh)&x>gr(OJHnhE zlPg~B3O%X?7C&CJ;6!cF^Mv-)1;0QQ_mL6z7;G+BoO9R9<0^RBR^KcMmJ}Ng}*% zC5Q*i?O-?L@*j4}v?7!@G>`;}Bills%l-SQ_&gO{uvpuC2}dkf*5dZLG77M2^&X;DoTpWK3OvB$>h;?iqE{dN?8Dh1({5Dhy*jQo6G(&8d|TB>dp6dph*=GSr+>EMzHHPiMq{%ohLN> zD(*D2615MHe&A`5)=dy3w}NRZf%8ThH2`lb*>$l@O zgRa>y9+QI7L=e2-)5E_^i#hqUo?7<0Y&UF}glRra*$@lX-j3zh=;Jy&_1|6zZ$EJiC`9q&FXfe&rhv;pSRZDhr@WN!Ns#DF{b%jFes?3|mg||beQx5(h1Y6xx{R)|= zqUBs^Yo{b|Ws2m1t)ifrTuaH=GptP}OEL$t;p1J{^WQn1d{m8bBORTEhXtuXNG;v8 zI5fc(7(in1%o(;!-w0xi$>fp(@DPhV@K%+1QR&6~kagKjZEqOGzoo`G^7?6$$t_;U>!$-a0^b$zFsQu+MDA2JxRlnm+lboQq;TLV*S@`j5=h;2aX#0?CGfh zoT*yuyP5H%;x$^oPYZuNM-ng2Tqrib>?l`XAQT9Bc>Rd3LHSBfOY>exmjLF3Q3Y49 zDy@M!w2fU9MUL>=*jzHlS> zao*T^{xUlxp}6<`zTS5bSh3~L`x$ou7c zk396=ZOGHs6pHBstyv-dA>xV}~X>!v7x_TZfO^p3$?|@-SkjZ8|i+k?bi@yg3 z{LYyXh7w^1VkE;!8?6waH>1b?-RjyFT_s~zx&t0APRy>l8IoXL_DOM)L=|Cqw&0lX zZ5LD=FEB>?z%Vc9U$yjb;!3#irUi@|nKUkD zS_<{?;VbX&r#2LFOky(Dc&3N1*WsG^|hDSZ5^f zrtZWJ`2&_TLu)f&^m^mSsu0t00i9qBq+(!1R8xp9x?CrLMikgNHV-VBusew&KY@z@ zPWEGYqKm2o32+9ELFgtRgoT)IC`||#c^Iv?3n7dpZl=^=gCMg8dnu$DR;ldlio03+ zN$Ckhfp;GUr%)3YIAe(s|8mQCc$5L@SPP#XpDWsI^Y=190o)nEocJ_6CW59GPtyOz z`Id9O+`Re&dv_6E4l@(RqG#h}pML=LTN;`@L@PpLKp+wM2H^yeAxArW{fDjmNgvC7 z&O`q>ToIpf{q`+k^UNjlYyH3xZ@~{@tJeMFMMa z)3=ZrOmWSx#6pcn{?*0c^BLd-gc!6Fjx0O*_;iG*;J$1_)ZuV*)cprIPUu{vNGnw2NI+!pMrQi*amVT zR{8zj18fzsa~%6_yg2aw>nuP~4V>oTa?hU>cB$|&nNNuGbs^IS;?J&^>b zi`(0FzGi$;h7QQG1ZW*2)p+`55;&>sdlYiZfPR|rlGp;qTHyIRxP*d6_3_#zqQ^EW z!Z{5wWCzJ8UaX5YGFMkDZJ9RB*U5kGBk_W+F%muRqb@4`*xuy_elLkR)B`a$EJkAc zbJ9&6*t`cYaFth*#gD#tX%^Pql7BuB(m0c!Y}7E6{;=rYZcffRqyN`~>APQ`JF2Yu zZ=KIrZ6}%g>wbKm|3Ly0u=r3k*IERL@=^b)nI^AyzMgaWQ1Yr9w;;uTS+xE;wZ6wG z>mp~N{^B$%=ekKU8#{tQg(@;(i(h z9R4XGxq158icjlrN!{g|w|iC@5GP+EJ^DMydt6B_gML`t#Mp_CV}%XlmY;o(5km!e zW~P$eb3iUXvIpWRL^@MG{BCpke${flq8ZiSL@3xQO)->4K8d1d?URt_2ROfO+(0eb z71>GY-Vq0y43`))szf|c=oy~Y;bZ;XcL#h*B!sJi`dv@-TOskR?}glM=KYyN+EBzv z7~=6a4)?ojE6xsCIA;Dsi0Xs$SYBWha1wM;Tc{z%k9}jg!42-aLIzIykvPvR3Sg>M z@z5W}INS>^%Ep60rwStF2vyFEyD={v8$Za8M^)nuNt~?rceMF=2lP4C>eNpj5;=7U6#c+-N8 zQ@mhhGhe44n;T#FP@Wjgj8^CkA9-t`=Jm*#NS6UOeiUmzGyu=b@iyJ~3u>rN(pcoW z`0NG`hxSRDxvQ_~5;iP38KTNAb=F80}2Ap9jBynM@#+Fd$?!Ot^*?d3A78dsQRk4IF*lt1`A4S zmvr}N~VrinDuWyugp`}9OnsGo1hE=?3 zjAOjxcZ~S&fQgxC5^sX|92P!~oeK#mZz<%tzIjP?IuJ&lTZjlDoW!{Z zM6HTrX1`ETiP1OGs}e681Kxh!-9BUu%#r`*%|P_$XHh1f-OnVqyb_UX}GBB08mfzw|IM0`#yAM;QFbZz7AY30aDy!Ie13y%Pt<9MJqe< zaU32`l}c1dqZO#p^n$;xubKWmftD0G>LTCrkq4f&bV5xbj3* z9#v}$c#T$;{3^iTQwAe%4PVm)*^CNRB;f{JS)J8a{ZXtjefeRz;sws=epgOKMFlTz z-0Pb%lE1~p8oSIN9!02}_?i#A{mT@y?aefpZ7qqm&O#k zffY}9@2rDm5bxKd5KatUg%%*oMQyIxFZMo+?O%f5@&%HrkX>t0$Hpx!QJG@rv`jpD zU$GZ7ic;06+zzlKSV8xl1J}>`p4<(s16<0p6QqOZdpJ)Oux@>ssb^jx?IK{5gowsq zBGdKHwsG9KNc(mFR_o@VXWkXvky@vbJnXN>PE)A*SoXlV;hn|-_-*(~h)DUh&#h2- z3(&p6i=Nz|nG_xa2==RVHhC;9t@xC#3OnB`uHdjNqHATA=rESJnd3U~ug zwXGMY(k_ z=wS&}>a1n#T`qWYX;bM#MNN-WN@&Lm#5~uzt75a6({`VnJ zqTfAt)6`D}=^;8qSerjP2Xyg9v_H=;Or|Y8OdOuao;*1%MtVN&+G1eGtSIpo06Rzs6Fd^5 z5AnbF3;AK zb{S^maTIMnE8q6HU-lwPy8%A8MNe`mUyKKROI^7Jtlh-s-^~%h6@he-pL@e`&ISfx zu#L#N_I1^(8ywYRPvvbw?S;m7lF>@2OlaY)U%NLU6$V!%zym-6M)uCHeJaSA0-?6a zS5;{W9PXju!bjQf&IM!U4IybCWNrfk;cU0|SXe{^(Tvdt0gf^{impvz&#q=wrE^4J zxr0)aHj7n2b!TQWD`7nnNT9s839|~s3%%vbVSTsX#3ed>_#1nz7RsXj^? zY3MK3(NpdDx6!lgq~LytvgWODf-vJBpU4L}C@>mt6(1@Nwqpz|GSYAuzC}hJT=5!)3mOLc zJ}Yj;_J1MNA9=`fe`)(@xpx2@J+cLKf(W%Q z-63Yhy*H(ACG1#I6N%XZ{k}?aa1r_pByHAL`9qkgW=z}nTFA#VJ!0Y1uRrZn`4 zj=On9WxQ;^B*No}%hR~iCz)7vFSGkkK-LvRU^&YLPj^!#anZ8p$1H3>ws4Cc(gVAc zzSr01le2R)~tsPmf~=s z*HkL9{Y#59C$)V|w|r zAX0|qI8RnQgIGeWeOWKziUI;+hGV{k@>)_h6weqYj~JSe&G@@#x{1iX8k0e;e&K64S0eIs6zBsb`WJq0db& zweEp42GtWgw?sOv+wIUynwKnkbUjkvS_RyP=|m7m-{-}1(-rY3;uy7p&K>lkVTEtM z4ok*8-cU1CMYI=M>%gS;7;%3M+ZIs%YdV}Ewdq4wH%|*FJ=|9@w(c86$)t^6Z`}ka zR|j0&2r;;QYd=)}!S$z&t)a?CjNy*8jTd8n+wM>SX~&+SGHkSoRcEQ=1`4E|!+D&g z@xTrhhua*_*IBM-^<$Ql4rpHS8lkDON6CrGT0078rA~>UEE}dzAWVcv7z%kUAWAtIiJ@r)Oj*eUTl|| z)xN5D95+o7JE`me=0R-n6=_ zv5tm{)^NVUtbOA9Mhx6u<|c1c^U?K&qcm|^3q}6#ey&!Z7Gd+z|6q!PxSPtX0WG*h zfbb}uOQ)Zf*a2b>?r;P#SqX<0zpYA5p>BgFAsxq8xk$SnOzaVMlbCTMUycbvpFC1T ze~IiSqZ}C}dg#uB>j{$J8FY&u@8Nfqc+-!|XLa)Q_epuZ)!+oXc#POBBF8#viDC-G z_+v8MtG*uRtcgV5QI~SXh3gt1mH1ufCD2#^!S%Xg&(@|qZVubQy-kA5PaL8K_pp076FN(=#76mGw$13SA|=2~gV$-(fRSpsb}2;LE!J zyU%K`@hfUCcs-8dvCFer{M@+J)$Ms_VtH6+vFL!4bF>clqersGy;}cxlU>0YyFnWi zZ)gInfXhsrRW*(NmC#U(ZW{P}h(-4YAQnUwbh`2cUPyGy=%=l;CUM)_X(VK6Zr_c` zUvIpde`|Pg0$@=2ZtU`eq8?ftr3%f!ly+QyRiB3_S_bgzwo}8JH)v~eX%{JA#@6OP zqC~>lJCmh}K5qXhskgQ!N;zrYTk!a|Rk@IP&t(kQ%&qq8Gp@Q4uj#unjc&Ca$<)xzih0#JF;3O|otwZr?w}KmpY=-4DfYxFWS7^9 zr1}DYsJB^rTzyULE8qFt_o7`-8+&jRy_x zDZ=7Upg<-LDhm_^Z5ME)NsCg_%h}mD1N@qAbOvF^J*nhYnkV&%YJkFM?l*M>e&u`*|wdILt}ExNYiH znT&U&gXAA@-63z}j)m;@3dt#~Q-XXRvp$Z$H1niqk^-SCCiK~nvgs>$xb~K?!#nh( zkTOA${Z>Ge8sMlqm7dOu`QV#9xOm2n{tuR~?D)0JYYSP_bSSQ~$^6_ZKWsu|;b;#Q zYxw#}t)lbNyNjD#(bDC8V{vj2-FmaPMBe81Zr>hXnv3oz(;tMI*Wr~_QD>-BCjGmE z%9!dIf#UFDFHow(7q23OJgo6#?iATdj|7E_j1RMBLyNxOrk5+Mq^P|BQ=1B^|NOSL z?7$1}TiLlOzeV=EIpz|6?6#)$$YYgeF0WO0`Rs5${gdp3%cH5MHmu2p4$cY`idt4$ z>RDM?N`uyrhyKjn0i&)roUJCFLDeGR*T&4cX)ihI zB6?WD4(ae>gA;pH98d{tnFSqD3ra#QYa+<3riLU&tXLdSfwsJSZK1m?z~0%S%} z3GUL~R%kB(zCxE(2m~Kav`S^54_nS7e@87zed^7l==*(Mt+X$K;KI3q;xBe@4(hJi zaTEPui2b{6f6tX{;#HPCy*PqYCdZCWvSK=ucx%#GYT{ygf+ zD;R%i7@My>iiu2P~L$;0&~D`{nz|GTX-c6 zAg9s)iqyh?{EM#kx>RR@i2*{h(^M}p|C2AfKU@BSN zz?7k~e08=SsZ1D7DdpJKv=-gUHm!xUXxX|$#ka&uEfyA|hb9$QpP@>kgv@g;tffz# zU05tz_$~%dvqIX=1F4~n8x&}(kR-k{WUDvvAoD)^-3+#`LYN$jwQIF+*s(tFL!E(p zJ|3w~L$nF!Ol3Tc=N8}RnRCu_rsJk{cu#@?7>PlLWkz;rrFvH8=q$@*43G+Y=J;L;v9L|H<(aO0Lf#dK(;#`iJwb{^9{0h~FlF(W+hMY?Pd zRXO3v4R;va*{dcFT%@JM-T}4fdm4SP;600Nz&9~De_vwofacgXz8u$sV)e2Qo`l%! z>wP!qJ}v7Ewneskb7;SYjumVCzD(;h(%fyh{xy7Uchx*@P64HWzu|IWO zIbSd37ETsGmS?ifuLxEqY%pb+W^KzA?}mywI07(!6?Z*?*%>wi%pwYid61YQ}VJb3e>O5`@HaSNQ8i>~?*~Nqd)o9e$ng>PW;75)0Xj3?aQW~cFrzrLWK`zMzd zLJ7+kX<7|O&?|f3!Fvilb6y#pS^gNw0GMz#@q$_A8;E=^I}a%7O@4u*^$_aupEeY( z(z`|zSRn=&&#hQkf4%rjH1J|c48$q>s%`;V4%XXkL(@1s|AE!%4hft>_rl)&kK2G< zn`>0OPCM@*5Iy9k)7@IQ^!A<)xj( zT9(@<0uBH@tW-y8+|AGO3nfy+h8E3{i=5?_=q?no|9!3ae^1h-Ppc@Y(JPCC(j|?l z(C-TWv5TPb4NZ}_($M#GY=#!g4vVBMTxttKJPh3PWR$w9w`F1VSIzXDG_I6iJg7~y zpbBlD?tSQm%O-XD;i=s|uPQDod8J96WG9<3gZ0&0H>kT&U)d9AVogRFENa%J3^AEf&-7 zyo^Jh=xr2SRiGFik$S%R9kIt{8#?*GZ#-?OU%n~s`5`))3wg@294COU^oB!Xm#9Ms zI7uY$VhKDmy#h(5e*B7%&{htd@I1H8@Ro-zMI}ZddMQ89sS=)8X>GyMu0cE%(&16% z(!dQ~Sc|ksuXu8Bm65VhfLUsQk3WcM4e=Xwl`B$B7G(f{lf2@)hEML$=8J!n^)%(B z8|XCekzDT}KzaVm0adt~lO^?D0O;Qar~83nmRlkEyvX_4_gIZ!koht{LPqBswr;gh zE*dBu2;0{3g_o7ijxcSO55B>`0H(aqJI~MqNn5=c zE8oP?;98JzuVtyinM|KXVmP2DlodHIXZB;qBX;kO-X+Oh<|Y;1%aZ(=IsatbAw6E1%@Dopj6x>~S<-;*Q0f_h5U_glcGug|2dA$j`CMvYPk-JXmy0v9`K@>x^@sM` zxo>Y061p(DRVc5=GHTcZ)DI>{y}B>#umdU}g}=Lx+^`}Y{a#Bw_3l=0ToB~aHT*1q zvojH4FD(Ck$a@PEpZGocz4$IP+g#zpxc*c*oJ=pol1m4>o>sZ#whWnzUtpIypoLA8 z3ECRe(eeR8)1=W4D7bgy%pnMwtKhO}gI%jWKEfHdfWB{8J_T|W( z);YT?7c12zFlRCA&nsSS5wzV~kY-@C6I=!2yq){O4s*0f0--{cBxVqNo%2rdK<7z& zeT!$%`}==$xgt_kJbDl9!48OLcWGfvKMxKobLSup?X4P?z-q|mJZ5F{q3^)3UYA9xhYGfITkW-3g+X?+v%~hnEyIqCLIRn^eG)t zJ%6A|Pvlk1-@ze%>3k$Qf>$29BOuprXn%%|1Bg`ZQ}ti1b)^Z3Ah-oVXM8e`bQG~Z+Pqp z@ibv@uJ|gJ0%f;YRAXmD{@igt`#oUv{#5iaAAeaRxR1xVF!|JXq3O3tzA{ zjT{75@#L(GJaAZK#S<(q>pT_H28Aow^vFhlGt-NiE*h1n{!7spJGz0n0ZU$URJ#=a zv^e1;wm0(_4?rpyuHTi{JMhpLByzH@`TP1#<%#|w(zr8f1N}k(=xzHaq)mI@Tm13D zctlPClgF-FY&Y}LuIN5nSa7I+`jC~9Tussy9M{A1T?6W^+?qaW;S8Sg#6O(<@Ig4Vv^uVJ(xgeF zk!E-wRV4s!MX!K%PYn8DXX?8D;C0WMLtGZ)&?=)@o$}PYqKv^z7|l`o@LczzF5&E+ z;7jZfk=I-79dl(1dRs~L5{DPu&(>$Xd`gfwTsd_|Uq)c4Y`J!$s`YmnnPshuh?l{4 zG9N3q-PYg910*$edF1&b(X$*MB~Fs#B`Zv-X~iAy zEDDp1fCO~u(@rRUltwmzQ(&e#vYZ(Y6xQ|?u5IUffDbf-gppKxSQMr*Jn9u9L-89s zc~LbL+=Oao#k1kH7Y|Op{`IUscS|I`PTo!Sj>iXg$Pn`qmSE|FYqK$xn~Swej+La@BqrhLM#o!oH@OmG*Imw?n4I zaiOA>a+>hcm-n{*zi*8tOQS_w5KpFY6?S9gogm`)eDftB;4&80x92KpT!brW|0(YA zBzUj>w)p{zMcDfQAL{|>Hm!ymhI~9>A_BHMw=o>0Xj!y=f5xJHBTHNR(nUBcquM2H z^rdH0K|@e{mFBNv0o2#@`kX&Heg2M^#JtaUh&<;rcgezk%bOF6%?V^K1pTS=jVJ-P z?sNF%mCHW=B`Ff+QQkm8vG5IPnzj(LEJuliwb+*db^8Z;=ILOPx2U=9{zJ^-6gNBL z;GTNojNbk3~L5%n~e<9DkKTYL(^OW~Yd`(N>}(wIL?zSc$rKgS|yTp2W3 zx{#O0{34D*p!RY-!9@Bw+s#v95J~CGcGS~R9RvqP371Dqrp<-oCvFTf3?~=94dOIO zc6&t2?&qoDriMBUQg7(z!=41H<(4M%5kxzQPP9=rn@KW|h7q3i3$_wh>b zjKpD3%~NDC>2z7WQUam}haOk|n`O_{L6OJ@q38V0-)SNe_L1*{Wb}1i68q)DM~0>4 zkWtuS-;`++B%qmSXMj8`Dz#dR>DGtptAgXK+vL6(QJ#EpXL6E@CjLE4$5II^w&6F9 z1~S5XK0(r{`?!P2$R9pUu)e{C1PDTZUk94oYMlQJA-}J_TX0@dQ!=! zoHum+U{2H@?+DnWp2%c?{Wzpz+J8#+tIB84Iss>>{`&%six*vR11Icoxj({up*8`k zoI00KXiYiq-te;Y@6#zz4rLEe6_!>L3aqDij!|l%WXn(lH5WxYr0u_*d=jqbA>hSo@H-U!?I#e*OsR^K-^Y{Cc&Kp_-92m%ZrYR2pQc%4?Q=G{^>! z2NZc_dz21a?kU2l*g)3)$=Toh%YFKcw`~B1~TL85uQFDa=VQxhGcH zGCU2=?&Nk0K(46P1@wk*9Y^@f*#*CsiOt%*G2<(}RUF;Kk`K0+AeQ_Dq{ z!31c7=c`Xc{;vAv`P@eUdvk;3)~H>%xnAcLDm*px&;CfA`HaPz1&V%Fq zj@aoYzr_9{9F6ULg?^d3=C6Feoq!%q3vcGs3nJ@^=5sPpz;9=p^2iQeJ(X_}58jNr zG$!%oIA7KDGhWn2dGT(4Xh$%$3!o2Dj!07L$Yuw8{Q7-gFYBlLGsi)D;Zake6SJ=F7Pxh+iaEPJ_ykH@u&2Mu^2GNK4980+R?bl?;GI>zO4!Q3aM>Aedp#C~sljX7fKjMT?HLMiVWx@l>bdDg5aRKUF5M z3r?=C2NuU9=~rUf;y+L|HL!2?CxYuZvu>L(Q3AC4M=?o#d)l&uh#1+1Dhpvb5tAaD zMg`>SchRW)13~IIKJ<2B+653NCWk6t;MzHt;OslXDi+$(E2Y63zei$`0*L=9e)l)+ z0B}KO=E_H4CPwMVvTfklI#t|X#5)V3)#sqqI<;K4t$LY!9@z3PS$xA=m}v?;_W_#k zR(nL6FmU!pxz9M30(R+GbVTz2K{Oar7<W+pQ`pW$oaSP*f5UT{dd=WiX*1Xvtn{f+Q75XmY0B zflhCxPlCNmFiK>_a1wfs%4N|i2&7B2Tw6yy-?KYxZ39MCc0*QmBmG_S|)0z3OS6picHq~ypk&$*vr_#xwwvAjYk~|7VLx9 z=PEqmk}Ul%R(}i3vt|<=!oSx`xqLeL#xG=SS6{=|j7y>y!}rY(bDfLs&JVSK#SnGI zc3j1!7@f7hX#y0w?hG_b)?EtyB&fS7b!FuCPhHp+@J_aq}+^#Z-!RQQay(&OX^9--a4ORL*V`#d~_ zs-c;asKL3XT?Vet$0e_&pFh{KHS45X?ZbCNmkAn09Gz@mE%V2RtllC47w|0iP&h6lrQ-2 zq7eucC0U`?>YMZ{cBVx5R`nFI`pgLGE+%`{zoxH<|>CUB&oc zK>wb4la8fchf3IA*n~fd(WooSqMoe9Ndl~qxzj;F2&StdMw&Ba{~chytoNsmV zL-4rrdmuz052SU;op}waY0dLP7fgm|ut{GB};I6cffhTmA*fc~lt8^MhW%z$6UuTzP=g z*lV8?$F3fpb@CG9K{myERg$e&QxZ#fbWjtcVmB90$m1E`s9tIfN9{n zs*^*3ID~tsg~6%IWo$%oMntO%+I{KUp?nZbNT1b29k%W!$hAhX&@(ps7+MqT*;;6a+h_X;=TFOG1Q!J_bkAk=LeB!xfl~<73N;Myd4|f*oD|;;l4gX zO%F&FZ`kX%;#7Dh=X+sY1*ye6PT>YK37XJM1@2 z%5LljNs`8#qQ~U$uw2W``MsQti|^C;r^b=jYRJ1q6N#YGOsUggES+YnL0N4aDsX}- zWQOP^DM?6%wx6Y;LUNPKzTD7B$9~gMJA?{$sUIuUKRa+_+643y9HzKkZ3D=UtCbMp zWqLft@EAszFlpAj((fmmX)*GW-`;g>T9^W9L8a1_I=()XBzQX)d$%o$H_84hCeADU z)YGJY=66B2ov=)5+|v#-iZ!I+rOn|g0mQ)*_VPfA+{W^A9fv?ee8Efs3^q%0p%e7I6Z5n?1v(1tO{m9G-bX$rSQjnvftLBlDx z8(!t$Z8lma%*A=M(0|w6osJ!w;pIgp3q@x0LkOx{&N7+rrBfU3BK6fn20bu!lZykNIJNx6kMzCm?e877;~aCWye7aHI@)^gIvpWp2YkB^Y`O*J zk7t5tEaT)5hLXDA_EsqlTj&Jr`5hFI-1`yuug1Dy+aC|&v7W`Uu7(VjMwe9I3W*9c zKH5raG8U0*k`!w~&B6ytkC2NNFh`~F@Zwo}65W`Ku3tXEurkpTr@X$5IP<$aulgRY zcotmohqMUvLOkUW@(kl-K+>(rD?$@xOHr@Emy?>50mys&*Q@ov&v>uUdgKi|2&X!hFVWtGCa+(ET>+nL(#pjX)>l^7IaUswlHQ;-U3^5P%Wh z@y_}77L{!;C;Ib$&C-IB=$wit?mH`bZ9RSLsE(8%1|#U%Im+vvP3e??h!?`E(AqMe~3j^!YGYdxU-@WFhR8+V3E5zb{}pkT$+m zLa}9BF0~;eM9|i$3uZFzHPvvYXT%McoD&+ZqaT`ATKe(4dJPkUPGd{IUOdh>(mETc zSYy(?4cGnk@vbUtRG)o>u_|HyK;f3J1&{Cja++?z*ox1qNCh`y6o1ZIYsa`4qi%ML zwi|~rCYNhZ)I*H@hzSyhXZC>S-`dZk?63atirVG~Yfj`hdPpbiGv=P_lXCY?_0z5) z3JlNLvWvX*J*K6+Q*D{&2U!|0$YT_-7udbxX*AF47xp0vS&;4A(sh5GSJ9vb&s@IN zHl>W*NZx)!t%GvL43UqWc^>zmOWRJ&exvAm!xv|~puaWa^6p=5M+ej>Lz`WLXJooJ zzwrVyNouIwImO4|!9s&C>KEN`d6WHCZuP|{kJ^Ilh2c^t2S56H{Rbl8TZ@$JFF$V1 z7JYk{BTCYR122QE&}9wpW1^D@XwE}KK$?z4&M|L6_m}Nufy(ubej&w5xWE-|_%5P& z)&J1H=}GtQy|qwJF;sb&avKX9F-Apv5g|*=atxap_8W8ymscrJlqr>@0}QE4*vc!E z(ODZF{we0ylww*eAKT_~<5n%=S=PjkbV2&BYrp|j{czcR>7!bTjZ1{XM1{>W>&A7( zB2K8vX2%Ur=|~>|hhH*s*MJru{YT}-{e~wWv1=}zU^_fB&a-DTrRfG^A!*ubERNRN zed?xL3eURa$DB>PNKr)`Wi0B7vy)=OXuOxhG2Rt%_1(aQ^>O!ZK=_+TZ<7k;5G0`v z!uAK4wVS&bcfToedCTwpFoEpPr46)^*d$ORNdFG}btusM5%m78_iziX1ENC7f8+~Z zk98Oul$E{XhH=EZ10&guTh)Cpnk6-O<+gv$h?LtI!qnSpMpDx_=hx?TVXD5Ea+)Yj z6DNxM9#*R3&P1RX%)G*Q>p~sXq4?T-!ey~lO*mXL{57PhM_BvqSc@=a^3fheS zDJF{uGXzb+`Jefo{A4-xahm-hO5CjDY~~WolmrBxRCp?v+n? z6>H(*)UWCHi}&(qA^l}MjIQe_XTRT${k0g|hSh+#2I>AmrQ&O-s#y6}fY`q$!D>UC z9yspY+XDb(f>jwzi0=%*wFR4U?OZZYeBX z^OUDk$tAFs1alx{@i)@;i@Y7ZylmglzCe4tmQLhaJHjml=^uR~7*YuYU3W?fvG1@( z_4ncP7sMB8qzH)V+K8-5lx@uZjk!?xfmFF0)>jJIk0b!6v#bJ+seFez3ECyV;JdFD zRsXuI2jb@I;A>#6(>ap>h>R{t3tAt#6RVqF`lnQ2^Zc>ZA8x%6U5-MLXx1@zbldng zD3!fwgs(X&2_h^m`X9zv2+dCjkU#q>>B@OxgoQ!5k#wy&BTqW&ssX{q2aZgxx_jQWSkd8n0L3#W-QzzY+=2y!#+yF2Sy02&R2}Gq<9G(zR*YxF_J%IR8;**2{yW5 z{b&U5Gz|d0Q$k>0N1f9Wt#VdCAz|=fA8=A6fm%T{o^><`OiosFx&%SNDvulD*~Rn1 z@1+*1QTk(k`D>?nxopDO4wcW7*L^Jy;GgRj@1Bfb@%~ATzfK+R0$%aqw=r_XE#u^u zK1Zd5K}|%a06;Cy?Laq_clKrSfipNptxVfy<|OR_VM3_@qOKzyu*+)0SAAf`jq@oc zp)FFNPola89Hni!lE$#)V)O}_|NvD+}hes-ddtUI8R)r(i# zI64#{j(UV~{xwCaVu)VIm$b>TZ}#nv+6host|X(A3kMfOo_=!Sc(NMA{tQQ;B~^bK z7KH&ne+U)a1jA)z4V5h9dZtq3^*7fRxY!&m$ct(SR&OfQp+7eTk%-38oC}fVm|`BN zm}N{g13Lt<8A7JzAC$%UsYneAAahY1@k>=uAn`Y z&{0`sHE2??792r4Y0D>S&D`U{U7a@?{?>E^Y+O+;P@ze{c58osW^DRT_`l2DdtZIQ zl8^M6_Hh;5|c%)FgxiG%zpOZ9{5n?oXO(3J*nd<%r-Pe7Ug% z8CN`lKB)XIpI61Uxh`_l71pN3P=$#!}Vdk?&8aSmsMHVI^@E!6C6Mwk!#Bf58;?@=9sW*GnwrNMw57 zE4)u@t;ki)HH+hnWz`pF3vLT)kcGB3i+;Bpi0-*yE#KPxX{V+P$sCMIp4>jq^1BK@ zDI#2n^)}zn0N`29E4A0a1*T{6Jlm6uh2Ey}Td+Kw0dO%8A|8Gizoi^34Y-NXBB<{lehA=ZQhqieA1 z=+b)Dn}&9V_?q9Ihj<_jT2diHA039OTBb z0~b6(w^HuH#bYQ$_}L|BmpwpN=4mC)Zbko>^5B7VbN|yGLb^Q6{>cWHz=ra zNAh=gu|$uo1dLN9L=OiV`_YIEDMl%VhHM}z7gNHBU3kj6)e zU^IEHJ>A=!xbN+SA~wDSaXazv<6wur%NzKp6WlkZ{g9Kcj9kI6_Oy-to#AF7*$3_v zv=Tkz+O}lJ0`DoaoK6MOLn2conNxcWYJ01BdV+lK8*{tirNClsFoqvYmJqW9{3=k4 zD6s$Co5m^l4YhSZ&XZ%v_Q8)HWdHIbO|{xbCTjzU-qEks_fapeS~hdMjL4(S1G~cH zymplrFg%;OX}&wQ^&k3Z%t^U~qyO_v^e!apu04ddz5snhU3(7oj=>H@(FujPuK`O; zf{Gs9%7|}Hu&mfEKv^(dxVRMvV|I~Uc&IlF11E37{B)S|F~jZ;-nC>VH`PWc-632*26DpZP)=zP5GuzgJ7FH=ED*A&;T8tE_8S z&hTK@SV*iUBlh>Qo~l@6!DB+;Hm+i0<0s+MCFseT^QXLl6z|81St>)qWaWe~iW)r=8*nJk3VRAa<cpWtY~63G zS}@7H;q7Pc!2+{N+JiSgis*!k;b)W}PD5!v))y0XLzv1u0x63k&(mjSy$yA{6Yk#! z#P8hR(SCr;48W&x_KNZUWZw$tou#2j3?aj%IOPG&OUi*&nU*#eW9uvfC=(4 zxLEuxBoeR>?z(#1?U;uVaq4)B2`C(tGzdNg2R|$8D}{M3mm}L+K7XB=$)kX-6_+$9E(Fb!ns;EFMln*3rVJ4Of)!W`Z*`+ z795*XFJNhBsbF@EP2G!(!7Pc%u*>L$SxaX6hjZDe3}jjIMFt2Ivv$JZ(0=m%6N?@@ z*d_%SiWY^pl5t_=KTvHdimZUP7oZL}KvXe@oxG$6*98`c(F$uTQMJ+BDOdRD7@Bk7p zgM+>%D^u=;jiI+HaP+~3qtN1*y(}eaGu#@ez|y=7j8|R#_1SCk+3m40;OZ5_gVY4L2`CWrYVHZY zw2KCX7eiD7k1D#~tXR={(;!XUs%$HW+$4+-PdoW_T;CI|Q6#u-ph+FNEx! zmy+Y1%8P9;14&oz6I%iP!Ht=bCDRX=a|XJx;-2rb;O4M46D0?n)uv#rSJ!U8QXpWj*5P4w0U z&x6F2%3a|iFHGa(FW?n}uh(4b5nV^obFYW4+8KP15fyVhEuLC(v}*MgZ3y%8uo5H% zLAuWt`J*|JWA7p8f*0}v`L_oNCX?%~QLeAs?0OUQ4s62~`&b&j1`|PPxI?-kH`><;eD@r~2>SeRk@B1mX>y-tpQWx@-nbG#=!YMRpqiAKcV7w#@^1#zR04 zYz&hcfCm#Vg;_l+P`hRq(|YU}@0FpqF`jTe)sRczyzT3Bt=|mc_7eDb_R7XsVX<&@ zlU@{ZynZvbFS1G+Tmr(CK-Yv^j784q;ZAmJ?Wj+eVh`^b>_a;VPK-2FaPXb&wI#9Y z_qX7)U@6Y9>Mhf$Z9S5Jj5JhjYORsa;^L(`ztmiz|WWdu(S#- z5VCt{%O#R$;~n+~@-qVY&VfZ|UD=#!n*`fX$7^xW9qY-Dy+`BAU2_1nY2;Pg`rI>G z8^nAC-kCFv??QP?`jjeEjiZSBPG%`l6NuY1u!9BYS#jdGoyKWP=eZ4sEtF&x(8o^~NIlq8^(e9EC-82B_n@=@ukCj@zdDt7VsX>oQibjB)5yLG;ar^Z;ymYiKPX&egweZz~K`vrXIX5UcvB;6NRx z`F%&+fux3x>F`@Ye??udzkW0HWwLvGcMx6z!-W>yNUVGMc>eyesMzL$@s!p6*pm+qo8?3VF-=6^`@F1KUzmzUm!o^X%hxMNv z@AH19vAmYpBZO+Z{I$rtMI9*%o)VtBlTe8>&WL>Lb#V3c60R#b!k}AjXbHrq;AJAt z38qGWW^7G8tbn0FUgH++Ce6-m3u&7V<^b3j&^E9V=vmLN{5}Scd6OW}MA9+XUVaxH z*a|)gge0i4a;(5loVW36S;0;MQQF_INHOmWBcb$0sPB6Cn?|r|~;D zFr14#CRSH{p_&5Pi0!+}uP#~vbR+CikpN}sKwUVyu-Lkpx{afAY-5$Q1#)aHux)plYPb{CWuY@_xqO4Y3u*qTrg9o(z?>VzV~}QDJO(6?v>=iCREE=Tq}}9 z7<%aTY_B6al9K-4f06BNjhB$!dJRMt2jlnICCGMfFY&c$duXCX|ZicVv)!5kI3ds-~Wo0 z_I}hgP%pI|wu>6oF=1?vF0Ud%e*rM~cedaj{nry&<+*6K)~lz7IYK3JmNQT43!Xb) z`qyi_s{j&PncAJHw+hze^-4<$FYL44(e>$Fa#WIOb&RW@^iv#SMLF91dtknx9UUGq zxP?~&#j|H5N!d@Pp`q8;>ThkXxS!@p8<3$LorFZe) zzd+>Ti;&n~SG-(QAO2I0I~Dci;-Z4nX_W9;Q3?y4~~7i zX91VvQYc72_XrfvyiY@>PUN`hmfHgTG|UVUm$8%Rt9r`)AUv7Y<~MlV97kO$u;cg* zuiA_k3>7;vdifwpW#)>{9ch@#t=UB7wEK!XS}aujT70>;&*1%eU8*7@@isVBYFCgO z)Ts4Vcx^Q@S6Ksm$sbWSmd16d=5{1b&LiaHixdyCZOOtR5AMg&# z>`S8-E`V1$CHU?xG}LnIOmHNh`)f}o&gGCkxO+%bXU_n(Vm*&e$U z%chD6JB6j{{fp^D5Z6et8y}w78|jSAJeQcs{!bM6g!#psq8Lk&AmM4+p`CzN*!~QL z;z(I$r9JqZBEv8$<;XaKkWh85&ucvYp=6-lGJ5Ab>&li$FdttZI%_ULb(m9Pap~pN z7afz8ADLagHI$#)j{N)2uf9T5#34-1n-ve<$WcLythXP#P>pB@;l#ZJ`; zfXb(CpA@!DO)Q7V0)EI#gdz9m$Ld`^hhwiZV$(Om-sVfLRc!J(zH1#v-gI?AD!}_+ zZglIZ#v;z`I!qYOPjUy-@I_Wd4lxI3&p^dhW4*G(wP(L*!=Mi) zm5CCeSzHu)d^^*BCe+@PQD zB%@9uRtqDwG+9;1zE*qQ98pE%xQO#mJ*y7{Ur>F7NgRsVhLollAHdyj#xS^HT%>Y?Jn7l& zC{JxTT{8klMq}x}(AHaHu+9%Zuq6}W8=IB(2a+je{t$Nn{B#=iIDzzDS`vRJ%V-EB zP`75SIm~I5JPvx3gBpFVT^p9)CDHL2BtGy15zk8Q;ULX@@!%X@T%n^e7tHTAK3IC* zWFai$)ls~0?*~};nEfFP;Mp`4CiR0O>F6IoQvOHoPdZ}7eS+YU6|>SlHE0IEyjSbY zlb|nGX=g0EjgMPE1iD3saXzr|>i39)X8C~4ZC;=fDWq$e6x-~vnEUXSvECEvjkSw+ z^3P3ysMc5mLM&#Sv)`nxJm(#W>Gk@go<>g;R@EEB#DPJDF&y>InDqJP+;h_%aJkm9 zNV@>Yj5wiZ`ufPx$26Kd17Bv9uDvYrUUvK& zBxw90jxo`fQiby77FkG-ylZp~#__V15Z%8|PX~YK$lx3pgN3&cyOEMf2k{Qo7PRor zj{M5k_N#$5DwQzF;gVtgHRi|)NLOl zCkZL?2l}cclat;1Kskxj34`3zqG~`Y5~cOX2y~|#%w2SN5OasB3u-r7z6XnPb16K0 zS8$h+%|ANrf&T~xkh5C}Aw5gnZIT6H9ApJUHiYE~*7rE%4t(~$$je6AzL@zA3CNdO zpbP8nd9HJ;k%o0QJd(3g6$<#Leb9ZWrMT3Y5Pw$HsrnUQgZb~FV>E+N%!kIcZj zgg;;qirotQ)ZnQS4k4I@PVz{W|4icx9MOl)YC_^|XRd87Uj8K-83;Lu8@OR1CK24` zRyT_^6ybW`UmWA@S)r7b`-{_Cw4p`K(tl}7p{<_7?O6c%7{Q0J{EP>4P3z=tnlakK z8}-Gw9X%SE_&xhOy4MzSt&-zWq^@{GlR&yNUGSyY~w4&Z$(A(472UZTihewvdZ?gv_+UDxotS(kk0f8dN!oE8-L>_#29hVKVHnp zDXZ=gI**3<*O(1pnbU+D#PdR!!>I)nv5mCG>qgyy@f~*4*r--E0f=FuF%^p7n0c`NmACL zs5pYXM+|oM;eEG*U1I||y`qHT+gtSTg(2LKtFYR6WM75E{d?^7?CllmAdx#Ih=4l0 zfaW&Sj86~Nw!GO9`yQosbjK*0a?bE0u83{;`t2C51;Bf*$RRMI5zv#N2Y8AhWpi|D zrl5$e&p9hi-v_KA$SqH^IQV1w$eM^O;&CQalT(%?14gaIuK(TjsoM3Fe^U6__?rFv zlwj~qo+@@`rbL+9e4PtsKYc+VuOKgUba<){i`Z?dwk-x%T1!8%cZdPSI9*jrhMoKe z_`*hHW#@6geWrLZ(8h7x&dj)`TKteD@&wB3*|x`BU-o*WRB{-A5B{)mdo;R*`4SLu#TtuZRBZfWe6!;3h9al*D?gF^lPV z)n$Bk1LzcD%N&~4qIr0_yy=k&fDp6PFqlp|4J1|#bds? z9iNKavz^Dap~?pg@Oug7@;Kf^+lz7=d(jdu55=9~DlhJ`*99Q&$UU$R#Jvd>aGrG) z=AKpmiR2-77R=W18l^f@F?PH8#&zA=LtMSbEiiqe$NSu~@(>ASQV~ zgZnd)Ov2P}lL5D&3`zE9PO|#^S#g4srDDg7bfxiB`Du}!U zsezOcAUU>4Hu%yIm5c=G@E48>F~}c4;jNT|ElTIahhK~K=3zbq96a>}nmOhl{vV)< zQk(lNx~F0`xPp|G$Nk~QuG9I8OSHDl>kPoU4+Qo;lqb|&`gj=4`EnNc5_MDJl%((V zJGaaI^~a?W+F#r`$*CX;+aaSgE$q})Otc@XdaQoSTJ4X&!4M_ntw0fNjpaLlzzvJ6 zuX}@6?kUkC(>JSsj6U3VPsBS)C>`}xD1Qu*M#h7c8_tVRtmXSK%&tVexxA&XNfb$} zu}kD9y3kHOk;mJ=@(Rw`l}x;M3&Aq_ov+V^gM^=9p5wL3Uqh_v7$b~?=*!j_w8_a7 zahJTb`w;x;o^~6y~nFjwjb z)dcLfgWyyO^5^_Bb{=oECh~h>^55w4O4DsL2A(IE!Sbr1>t}~bA&I<}1}C05m0@?C z52_tt*9D5}q4mTFGc)rS!;0+N5|vWG`b_kA57 zTBTVIkxd0s)IlXIEofAUnDSE(L`Aqui!)>QL4rn^;18vf8k(Ggk?-L=>I1Gi6r-iH zX%#HP1#KYDL%E?A@XMQ9sr`-?v34#D2VX*Ll}t4$R*!`vOO7?)8*5nE?0wkB4KK|< zufmwWIlgqH;fK=}J+ca~E)R}URGB3osJ!Fdf?j^woX64CSJMQ;%+LnGsrsaa7dxdp zSrb88tKRBqk0%sd_bf)@I%yOhK3`n)s)uhJZ!lb&WE^y7(`c{4>z{V!+C+?*4)s4s z_TI7cv#Y$OJ$!1vwNT6)U&|7?mjI?xUTz4rG;F?ICXC14*a$W%@r$76Cq)#+-!m<=5lA8PA~OA@VnAk}(M(Mh-GOtqAy{SyNu%3IuL>LK7djxYi@nrP*?d zR8Mw=7^8CKJZ+o{kykx{13Mf|K+4}%l#IZkd%*W&&XwQy~`A(oD zA%_k2_lFPQsozZD%55N>z%umfhb;fS;*hPIqR+QA?xN^G^&}~h8|y`q;C~yzm)g1- z9-#NuR=*z^{s;yCLAPzA@8au&XI4jv=HER`;x<``-J7l#Fqpn6Y^*!5T}uSdjZ+$Vx0@7v-mDPXf?wt?{`|(f&ho5oRLx3uJDedY+aLYrtw3tfJKtHd|+hWt6CN z0*nL4!NG2@UGkjmz)_n1>=Go~EC+t@F_!I5DwQmEpG}pI&`{Sct($iG`&+C;Xx~DH z<=IS0>dMQ|mX}&zK*_dY#vZ0$-RG2s^kGKK(j!lc$!6e0K!TMa2eH~S+Poc{S!WG? z%(E=qJS$!rI4ytsy53awkLHdJivyQ4^E)rTl3X3WhJ(jA=~{`=co&9Z@@0v(Sgvx2k zc?rF#XDrj1LI~3=sJqt;qdNiOszHaa@r~{DHK0(qTrq_*-au)<1hp3dyRjMOOuuuV zXjKq;{=r7CsGx(GSZXTx#ePTxzhL8rYz6nUK)i$Qz-lFM?$Ma{zINH&lTE&$f(DI2 zaiXazXqYhSC{*6y;ogp3e}toiu~Ouak|HQh+C!B0VY6-UbfIWqD)!*|< zdjmb@+^hX)8XHwlEL==}1CqBeZIqx|O!3@GV(+jn-oFY%;Xy164&&PH^$FH&0pdnb zjxyuG@|(99-`kgBf7?>nQcDdTy*tlHl1_73sDXbi6cP1z?4-3Hf+W=q@-_$y#IKZ| zrj2v~EEg(Xa2W|&fozrE#{BJH^>Q+&f1jehw$?9AkMQpo1s}Np1yjWdt@uxe1Ygr_QrEs%q8}) z44(8#YV~gdclk=k;}Iod&058axjn(UYtE;>Wg=GJEJe8g#AH*!KKUENkI>62Ry^Bc zPAU>94=DcvODSIsU7z1R=H-ve_N3ZC4Lqrq1DlVxALb)v6Syf9A1L8pfi2^u5eR>9 z(BP=$XL51o@zSuWpRs7X1bq!UAWvTmK8APc{mLi&lzI;S#3}MwMV-w6Gw#2kkRh^q z40Bulz6vmP0KAq*`bf&*?lW`c^TUH8*g$Pf#`z(P!**f!P!rD8H!l0?&{xigNb7S3 zkVT%&1i{)rTqqxWkS|1MKJd~p9HT4EPasj)s#+Tbmx2DTW4V56~2@qKzlqx-*8LE%1eu#>mPI21gSUrjQifuS|wvY8T_!_72cF$UB+8qGo=U5U!q*H zIBjoTx8V_1=ik#;lx(N4^l>u%R?gLL&kA)+< zfExR=F({acA6NW|1Zq=Chg$`@*poAOVgTwe<)==~ps5R?OI#TyAKdp-ac^~J{!(Lr znTe?I2k0^eDZ)%b7F@okX!(Z`ku_@_#MGOm@wkD9WAx?8+4Hh@*n4r{fqPhu`blf| z(Aye@3H4j&3eNmB?hJ4RJoE4x{^CXFv1&|8_9g{Kr&%6y;yJW_yM?w?^=^@7@0B*O zoSV`Ysgb{k3 z6q&x?4sij5pH5$3`u0yHs*BrlSjl2^XYeVzs?Tg#;4t=qBx$u2%wUBlz<1U{MJfAg z0;YJcT=@ix%quD+1|0SCB==x^eVjQvgPtoX@UW4Rls~vV;7`j@bD(XnV@m>gl1|)o z=5M@lq_g6-sswfL7NST{U46nitQXrd@S({$CaVEoQyCYE%ea`?_r0i#lwg#WTTw z`QgI|A3SR*5;zdfmm_nYP@Lbc&jbnl%;u+{z9=o=_~ExAvU)9Ih70>C17NL|Jd5FE z;pyMebs29UWX9^1B<*S}36M&B-%3mOx+!|Df>3kNh;#z>e(I6<{-GRqTYp9<-@%zO zV<9JafMBZ^VYx&cDey_j)K^D-A-b55{Y944$PE(=b;sj|a#70*7>B|mOYykWrRPyb zh!gkh+TLFk_~^Nor_zZZ+kOh$LVlv=CLeNw27)T;M^8v?py>C(rJp-GDD^9qiN>5{>L=K>vZgd+W~4gR(fcoWmXeWOl}=3r)wE}_Hk;mDiI zv=?bPn1ij7-apBQZ>B4m}PtV2p9R=VNRrEf=B1!#Tty7k^nur zw`d!*F+IM8u_p@nFD1haEmfFD2uQ{`zf{T#3aQxzfLlJi+7!x+ph(Z0zwRRtww^zBwG$6rnqM2e89&80Sp?$2QSbFsEb0kMpN=D;y>S*6zIm+XzJ3VuTO= z*U2xvS=f4bo0yO%Xmj;X?DwCzESs!dp>^sBRKrQ+wvNM8NF56%44R+|AfM)^Xfhsk zfttyEn)L|2W~kxscLI`)e9u8mw+9|G9!ix!G!}nfCns=R`9tPf8eZ@<+i<(a67-tT zUjt|z^Lo$_F7+R(UJHIX2njHre2c^WIom{~Z4)2`zzCnqIVDOdjk5GK8{=?Vn3@wC z(BUwN`N~Cvd(~645{m||%#ibS@;cg}V28K!|aobM6vOdYHOud@GgiHuBN95V?Dn#4)}Ms3)?8r%QS zcH{bTl<)hqCtp*D776TbBj6r3ic+?B>^_gv!`84)LjsHmrMom?C@(x({rC)MVm8!_ z$fczGVaLwBdNrtB1=hThkSgx9b}Rc4y)N+3{7=mMBdzTc!rN&V9B;pXvh1E#SM479 z18%dLb_Vt+_$yGF*|jTWyS7-WjDG*X%Sl!(PXjuB8?lSD18-@kUIdSXMV=;t?ZkWZ zK!z)(zDv*jP%Cl~3TV=~l82>gzy8%{ZZvjh4%4_c$CbiL70nCRMNTy}XZFmT9caRn z4nmyHX%BHE4j@+8T|m}W`4FhaDVT2SXypuh3dR|6sT?=Igel$bP0Ok=^;%UIcF!0Orhnb zRE|g63WBV5g}}++1Ju=4kU*YbI|?!W(9Q|DYy(YroMWQ%a{UrGL{zU7=i;Wdem5raBxx_V|w^vLS(|4Pgel@%`81`9@yzjvR7KVB?ZZx0C_%{^!i&P94{TT}cm z`#RM$2!S1xY7e}#==`hS&#LHTh+vuJyq^RO!BGD79j6#0wKwejkgMq~(D`1M5+eP@ zH(KeY2!=7sOgIK~#CbmfMq(p6_3gzSpZvXb&hV5ggbZRyVx%2hcO%Cj(>mn&&BD=$ zY!c93HfY6kzwoV`+5CqQ=kG(05|EW@#biOCLYv2ixKl9mp0o-c0mc!2S|cPbNACdM z0s$Z<1l-a=wVu8^F#(Q{9DZ``UC1E{0+x@R7oa706|9LlQMx z(=H&>79EjN0o^Bu5^z&*JggK*Ft~?xk#FXF%_9choz`=0k�RvS!A(2hLV7oRUGp z#P>QpD4u>OQ%*&X^VGuAtCO@6K~d_y6bP3`x@Fd7^#ZM z0tXyS0rBPYSKBxhOAVT9#9k=_4i_uy#MPG3l|Jp>mvNF-#sfLCcfUhnR}OJ zuyiyszCx{jj!NvsbpJE>?{!t#0xUo=`u$mhl6CfH2$R?NR?Eea&kF;`^;83nc+mwj zX(jAn63fk*bLDJ4=oX}^~0nV1zy=63MH7PJ6e|!n+G14Ir8G`xAqvxto<2 zFGC(_CSBi%piu4}!dyz^78MqicLAjU@U#1(46^Z|6j&1HA5;y0gVel@&aPhIn`kS; zav$MbH~OvZysU578*tBbMXTTVbr8ttyYU+LOb^Mg7HsZnIq^$w%CZ*%m;xoa!_-d*w=BwlM5wirE zhPM8dr_&YWra2Sb-V`g2oIW#1 zfbt36_&oo2zzih5UmYrfbxsPES874uz$IQ?yD2D1FnXKWI|g;l+Q{S@eD_(3mMr{W zUZ)68k|#2szMt1Qdloz*PR{In`~%7J1bg7N_ptClWT?`e;md5l#?R+n!>4wb7Q{7U<@ zV2iW{gvC;xMwT9LBJr6tOHwsu_Q>oVvp{ng z8ZHFr@|VY#MIPxyqD9ucOu%;mV2Duhnhf%zD}lPggK@5uG+xa`$`F)_pFd}PA1NgC z1V4z5dj;)11TL}9h7EUmwl*ltqYukUHPqbhL!_kwhlYTQTzN0Q1%_w>te{mzJE_Fj zm~GB8%jhfatNjB|+2n1wkVPURio1uRIt85z3-LKfNx=Y~@)J#f6sRNLfX}V_Pdf!E z4-luZ>b>d0)N7x_MCcM<*5$W6=A0f?v_n%{J~q}Xy=%zyQxCoz;jmQDu@>P~i0GM+ z&R~gWart@X+LRDOo#z|FALZ8T#%~DwYK%4_-GY6c>ipFhhpFN5osyTng|fv8RB7n= zF+k8m-p^eGrv%{g+!Fl16Yfa9faDD58F5UAr|_|ddGQp&ABk^ZQ~bBoM#H;R0TZz8 z(4lw=@)ig>5~0*ssVXoE*{mq|uiD0uLm`OUML`@o)CF9$L#8N1#4IvyD{WXv z*?t2ytm{7-V-pS6glA6;?ntI@=1vXTa$C%}LSnA)eGyY=&k_9WlEPD|OW^y_qQY5` zFJx^_LCLINH~>G7DPTd>lS{hqhoLoI@>6ZmJU1(`yY&S-c1!BM8vYFq3*aQ@%yiL; z!zMqh)ypq)o4<$@gg%fvxh0WiOIsVzf>=kGnI#mTQ%8&eoqW0qBOkOUb-+A@*6-l6 zUx<+k?n7DiGv(0K<_9w{VO182jN+sV4#nV8<<1JU;bW$fHAHY@GlD!ieMgj$+5yP_ z%$C42yCQ!aJ;5$W*vkBVtP=nBTOetO;J+CSC>F(m^>P%9AX$IfgWG_qG2u25W{}^_ z`xxBTE;j-=Fn3Wh@t^idPOdJqv+4!ptGx1NiwmgifggvTQ(Dp4g-G*S8Vo!r*wY87 z`#U`48Jh86hgS!+o*KK~z0=iyKFANK$E=WP4fGme#! zRaxJx6ABF!l@P}$L`K8NI3J~G84bzEsU$0e5OPi^I|?C{V}!EFKF;TN9>4p(|AuqU z96f0`NPY4mC# zE#|fk`8LHV=0zvw8=3K_5=bvXZZ<2_?vJf`lheg6i>ozaol%?Z-9~-USWfe~qo#aO{`*lA}Or%uE2`5kl*b z>B*d*ye-p|ym(AxT|T7tZWs9k%2v&{_4RqLAx__nA{FPi{{{7FWAJwgC3 zY%ioiQz2;b4=Rdd`K;LP^5$mwhPd3)y3eszGcw&j4%JBMBvUUa$tDnxrT%*u`+V^G z=m9=*4Hp6Vv-8)M^h9=b-%_-uc=s0IH{lDM+VyOZRdO=*&~WX{N-@xUGWyT1lA0|R zxgU69kULHr)y&g?|-KiwC($N{AK4tPz*CGRQ|-LVFSz_Y4&K7LNB(ACZ9H z(wn~)3qdZ5=Et}bIBtU`b5FLv6dZ-B@>ZAt8tfM*$vCPl>#*TiW&P%fbv3J+5X0|)QJtZ+xLE80dN3mkfF}a;_n#V-X$%7 zV$wUMI;Q2#XzpdE{AmQ%+siy$eUiLS{UR+T7}o}n7L;qUJTqDZPt*MHULVNK6`C?u zbV94PJ=|djrnVz@On};E*x{f*+jSe}F7QuxL#TRbxbqEq!J%%yL(OJU=caG{nzn&xlZPT)W?oY%2@ z-CHT&ke{9@r}3Zx@xKNCHarMEJ%!ez46gj%E{K<8lme0kMZ-^u5MG!8uf*wtv*m>D zi{Jx(ilZAG2%?&!t*czJ7W+1X3W)2&v_d3^D| zSfVNEzL6^rQAzqnJ7|Qemy24A%CSz*Bq(PEAL@R_*_RyvBel7Tw9 zcSJ787iY{P2D~h=cGQ!(DNROcIC!KA)O%YE=ILedxEmF#v#M>Nrg-@OSXf6^xk z&KVz$t2;b~6mo4CbJrW7xF{}v%`&m}C>@Wkg%<ie;4yyM(4M5FM=i=H!mDvHbu7(H-1A9+pQ?BC4zXEx7e6#VixSDD93c4U{?b=!82 zxkxC9xB9#;83XDKRKst?4L`dqS9pg4t~LrP8wpb!JadB07UXUsHhVzIjs!2nwh9q@ zN>4f+8G1hce*NHF;q}_PRLXYzki>UT?HE6UK9C?kldb-i#1Q{H3+$rbfyJ6a^~^)JC^rF9<=37a>QOWq5<@J_^jy~ z2^Nbk#Muw}KG(4H>=IiVBP~(MpcGD=U)Z#575B@iX7}l9x{r~y{#JIYO9A8#}5ej4varC z`iQP(Xqu0GH2fmHNY6p>D;m&@ydvnwPcX8=Ah#R*j5-^7&A)Q`PWHjHsAwlMVSL^g zV#g%9y1F06!z9KPYh1#63_a5eZSMkQ!1@1x$rxtJ>)xqCXBpb7gTFSMD0As2>ny)a z_DG|`B{?H@@I!Pm;Qs*vG*FZ$$e-1&6gYn;5Q=p4h2D-($*xyC$~WGd#|RvGYxf zA}V@^P;;hZ5ZFw*Q7^3vt&cx@DHn>eteE20X&<}MjFi;zhR3>3L1NosXLi+%-E)D9 zu|7-cLWImm7@CESjwUW0X?e#O#r^9(f}ZNX7%RO;-TA&Y7x5%s5ZA(K%xVe10DU z`RITEC~li>qlY5>ysFMPNQgzB2L-fUdHo2-Q;|wY`xL?m`?Pqo6M-uxo&SbQf6x;zN6NOY?Lv*rXnru35Q^5;VrN~|VrK7&Q%K&gltA1yE<45P0I-@) zzjv*H5?3yWsgTS;Wqt^Xe;rUI*p0LF zg(C)J!Iy@#pp#sNviP=s+yBC%zx|;L`NCa?(5~S-ND+|9Puo{j5q^f$D=y?90a_HTj$af`Atd~HqC?@N~`zCx+gD8(m?rX1ic zoX2`#oGmeJmbdJSS0a%=?Iu5Yx;#ZPm^a3k_RRm|s8@C_QEjIDO5Kt=;OPcLXL3h1 zwv??suwlob4(>hmHexskbuE?Dtlnh0-O(G_Y>@8Q!^c;2^c@S(v+ zNhmRIW_8;nB1mfA#_NN%&G6f((rVIKOS0*)F7%QI z*&g}hd}a0#E6PKYk-a~WkthxFS(l^tV@O^OBkvZ5A4MOCPJ1l|w2Cl3A z|3dZm)RSLWrUP5dk%POZwN()S(QxpYShtsuM96)^7dM+%mr;55!^=1zfcPUZEt5g4 zTVl!Q_{RIBqC;FwIaZQK7`wb$1M0qEeV>YkrG!XVrxa^lBR zZh7K-D;?H}PAJ>E}QQz^MFj1H@^(FP3v>%w`T5c8ufZI}^uPov&VSePbhoxp|HSD7-ei_JZRpTfFO z=W&$N@UAg*-SOvfQ#zaNe{jog`zL2f8#sI);rmMe61K|y__Dp~B9~G867fTY*|}{+mbMgm=e9BzF0*#5 zV;|CUlEK@eyWQUeZp2|I(mIO#6gjAuVZ77Ujn4H0Ha!cOu4BQ|<;i!|U*1gH75Bv% z#kK`^)qKMcQhe4<|94m*PbTD?oa*ZEhj;VQFZODlp!#7bh1&+V8VW3!hc;}h_XXas z9LF80r8agh`++rmIferVfZoE#irI{RVe<3T4HA$=CA7w^L2{*BXq_>7WO~geZ)o&; zD@XV=IO8-I_6!074UffJHJa8W=683?DEXEBitZ)0qL%|z2k)-2BP!&&@3rUmwYC1a zU@x_od<7Fl(6_#qMJ}vNJ?_nI14Q3BhZXFsXuk&S6e}e>g3|^69h89uYq7Vm zFz;g8B2^M+P{;Do2iIGc+4nMXosIf%O_3mjAk+9oZ-JTz;C@u{@V(Rz1Athi+>w%2 zHrLfRkanl*dcDV7e1*w_)y5&}!fc4Z6&nIrT;Eu(r>_fr;g)K+;aLH@fJ)VyH?dQk z|5Ut;1ZqQ<^46-CM8k=YqID0E?>Qfe-@mi;yI#-}Oeu08#m=u9MR*7Q@tLFAv!jDr z@zTD)@a3qq3e*=6Yjk!+H_J4Oy&THO!BXVFAe7_ZU7K07=1xXu4l?DugceA~j3=V# z1R;vT$*09xX66F0^!ro4SF1OZ3Zs8O>&t+Pm1t__n1)!gd~o11tA;O4V$(fU4F|s} zYuW{I`^O4BWnUZnp5?15tEmf#*!}EWd}xB7;UtLXv*zC!d^jV5@joWGXK$CW-0851 zQ*Q9DwohG7x6Z$*i~s(w?Kf8-t$g;*)Sz1q9u!2kZcW>vZwMsbNYE4hOG=*);97mG z!AvL$y0ZC!DpqW|-=!mxD#`kz_%qKBOHyi35C!HXQm@fh+Yzx{d*5)PIn7GI4bvh7 z#~4i6y15nS&s33mHQ#5=k&`GJ$h86k4t0noN@5B|0vcxMhA%27(=*=A=KklI1$);P6I@I%y@ zf5^X@VGeU}hhiFD_3yJkUB?x{5fpO~4tn2aX2yLqyA*O7KHe?nonXAni3 z9EfT-CTt1Y#V4}nYj1%Zz3`?Mx1EC`OTUc1gvfvhl-Or+@&z~!W32=?7mk=$wwYK^ z<8RI#fcfdE8`+P)7v4&MCK7q`HVtFqt7$=6I95_$-v;bW?Sa{o<+kG6-e@wkBE zZTi9iIVo7KY2CrWerdb5HS8+0KGz`s@ZI6MSn~Xh*-fY})3Jg&U$x4D1n) zdCB_K(yI<~BvvVVBV=!eW;T5X8bqNv^@cV;<_61~QNOjrSHt$wC|FO3q7NG$Ne#a1 z`%%KH=MJLsi<*h`Mi&^pCUlAIqTSjD%Jdkn=FqM6u+W;i=1wH5n$3$dP3^Z{kKqa) zoFp{8g3#O6YytO(s}XIx0H-z9~-|0R`PlN13Y(BlbQXbr* zJCC%#8CzIQ{yDxB!!v2Vv&@bzEz7XjD4%Esi-n<}BdaW{L?UT~LW zOizFM<;L8~djq>oanoEiNX=@OaC|#B{5M9Q;J35k`5Mgm<5DNa-^Ak=BJjy+P^lzw zCu;d14@$OBNksJ+!%z%H7&@>3kHSE9EbQm@mxb7FQ9Zz1sbnyKC$X6X4AUB(5z1|4 z^mN~7EZMP_odON8Yqtd+JY%2<583T|uL0Yx&lYE#E|@ug=DB*!oO8KxRs-}VTxk~; z1XD{;8V@mfB)A!H^Wje_P!x569~v`0?EBo+@RK{Az(?`!()E`$LE10e4?%#!|Of=CJw zs}UfXo`Ckjuafi+fKe0jd1A1UrvYqDu-1q2u5^V>OukVB6f2i{VO_RYcDAq|SL-j{Ovxh+_-I9^4|K&CCl&|1~vW zxqqo8(AFe&XH_D{*@Z`>A8R z8G^#hGuJuwQs{`y4ieV~hYEr|hV(d`WPm*Q6>o&Pf=b_aVd3)+@QgkGbXO?2Eec)? z72`+>*y>I94=CpBNMI)qyxdc+Bd;a?wijvakyeIY|Hn&N85hJEY+|PS`vG z*+XuI`738sd*_x%h)xsm$Aeeh=2LDSk> zLeQNGmiBsPBEqM=eF01EdW-%8ID;|gxnU$i>zwcR#V*MFrQ_Zi=k4kqA{UYn}D_sv)=7X zyX?yVFhX5}REtFlscMU68eSZOxKrQD=I!Mv9aTR+Pc%jg$my>OWDRfb#xO7K<@kF= zjSc?FpM@&pop;ffkQadrMO2#r{1v-w*3z^3H0#_MfLd|vA#|pi(x9_0Ujh#Q^m&n~ zS@9cD$=bNKv&w|bY;mbmQ!>iq85y36`MAbnenAcK$4_N&dra-0FxeEo+*N50^FnII| zs~{P?E^rwnBhLfL+=#o(%EP(cn5Aj8>h*9HG15h6P7SMAQA^RouLG5r&qJA`S3pv{ zM@;tTZ3k&xsOVp^EPjsm>So06yj6Jbb)^AbYSYBz&AcO@ix&Ia1< zq3dZ>+R>jr7w--@F^U3ARwy5Vk zU6cshY|l6C!*Fh#YSkw$VDMgv(FAA-7Bjaa#xX_Q;1>R=*W%+PDz4G&BvSRn967nA zZ8G)ibc+fN9s)X>$m*jx+k@=o50d$iubd9a9eFYfX?ZoqKHWAC;3>lGzr4@HDw33< zqt%tEWN-Ii>)kVj9a(YHP=x^H)yr!kAPB>ZteEJUp3WYY!pXchQfI1Uz{5?NKb6+a zDzT!kpdYt+yKg|>d#L{w;{xGov_z2NUhwteh!CyXr7P8JXjjV8aH41LupkuVWe>lF zcl5)}Rurha&`{ibKJg(uazlWLv~n2%c`49Gz1lENHH}F|1R{|iX{y~#v3fm_5_h%Z z$85DHHx{$46zw!^Kk)PB^C@#Ui$l3vRHvLK`hoM<0c#n1#W&=rEbBc{K?2`V#+0<~rhk>*em}F2kxN@0=gpGX z9k`Ywt5(IPx=nX{NbA6jt`szqB?toZe^_(~M6D|Voc&4lv^)jjSH9xOdo(aSzC+ZT zmV8imza;8R6HiVB%l+RNWKmUl-VaaSF*_n`!2B)^g1?lEk>Rk%BCex2=6gpiiralG z5sTquPxZ;RYeA{xyXZ_+bXpbUjO;!Xpu$gn5f16>OWZIDWF{Q{(v4OXSyg_Hn^T*2-@W$uN@Ff5Ia9PU?j&k2OA_y{C6+>QNdWrLYerI{08zEj~p zp#ZanriDFE{%Ig8Dd-C`Sc*CPE(E;iW4sRrDu+Nd-ThZr!|$1xolSHDGuD<~Bnov| zDR#%Sne%+(j+!L_G%^UUz^}Nuqkhk8-6q%eYpb=EdI_+U*^A8Y%*-9AH+%$oiU2-Y zpW^uh-uXDKe7nRKnk+7PUN#@8^~NKhgD($0t6~L-daez4q3i?pl_PQn+lK=zg+n0y zqxbfJm6w~jcfc$-*n%3kF?p3gl;PEicFU6^aYI~1e0+?bR?#4zX{s7e&@P%GyCFqzU}Ls-Bs5k-6{W!mO!88;H+<kSRDThz)5%`k91(hV}FogzD(t}-3%%N36m(9 z2X+;RGv{Whr1P$?DR8Mj!)mqm*xo3p;e&ug3RR^0g4V6V`Dci$8{IYHCiT=v@vo~$ z<2wD>U;p#lrIH9e_m6nNiuA%S`lrInTpkocn zt7f~%QMjC{M9ny!l;}f%%*8Ca<}ClmT=esL-Co&S#BSk&FYzZBCzmG^+a`+ag#bnJ z@A2oky1k>0TaVEdA@qB?VI)X3dq>s<+a{&Fdo!F?k$2Wuftk>RtDsxEQ=lQt|Bt>V zxt(zPz5c07t<(v6H=a3J#X_G3F+IWanbf0;+SfnQc753Up;di{dwV2WQpIsD#Sx0@Cmih;Dc zUt{dM&YvtnanYXcum0Vbv(F!w=_^DU12b5WaOi~qkq7$Ki%8LPp~7{ z)bKy$a>Nw4Nl4-l5_SmXCesIXA7NF4^#?o)m{;_%+UP?vR0%}ClyK9scD9FX_fSbF zd_;moL#^&dGo(Qfo9-tGY0I5^U#7}!(CnfQ{sG*mrlmk}9OJqN6za{s;Y%ff3crd} zf_0x?;U0bINX2=DoutDH4?EbDdHSpONZPwi+@`_AR@Uz5Q{op?XgvE@3{a~MS#s{z z&K+>b2xJ&uxzl0bZ`CSlBp-i?>&c@?r>nlpvoa*8LhfiQ@})fnUEs zZQg=qkBw1I>fipc~HyAn`=L`KThB2V6O+s%7anf zwMW}_T#R14HA{PleR$l?{5$y~OTPB2>upm%(7W9%03E7%MaluXKJ9pEtjt5UThPSD z>us)MwFdJ01v-%c_T&YY4T{P7lNoN(V}{*`;@n5#m~gZzqwo$&!5d7$ z=flDv6QO%^`H2UEj%+3km?dBO#A1>Vji z@RBImbqv6(Nt8(mtPiX#z=>F44AzX^Sq5B;O~J3{Ezq%NJk4^xC?IbhKv%OEDq`S9 zI?~jOwdP?w0YtF_e!xIWfU(7a|1hJn12?Is{==ajp2W870Prg#0``~HVofpbLJvKa z-Q|o6(W0F}bxV`qFV`yWL}z$wJ7W@f40%xbOcyp{exm6+-pQT9d1uLRblh<{M@aB@ zl-^ny1D!D3epf;Lm?GoqwG}x7@aPocB1xvAw*4JA4F;Jp3;oHnrATWIBQ8^`jg z;r%WBA$)c$Bz*Scuc^X#bmpO-{zEvX)a->@7LQBIvTj_zl|J;2-&=_{?#QlbLRwH; zgIAJT!q+i6Tq(Y=?R)^p{nvZd3&*;%BQ`_)Ah6s+GGS~m%`}l<%V5@|Puk#6&!2}* zS95xn|16>~9~emIvFG|A3-0{g>CNxIE2Ke_tTuP8Z9wPYp)Oe+o>gc;mQbIJvw!&L z2}`a>qdOO!{Rs2#+|DX)VSL6xu`!@3ydJRqIJ~H-9sf@Tnj}EU%H7f{fM*dsoF1v*&lQCM+hRX)=e^T2cN@s3S6@kAITL~I?xkiTPaxk~_?tRRg#Fde z@ekIs52sz0eXVI@Ig+Tg)_YeqCXfURSAiQrP4V79I#F;PwO5+O<((YoMzX9m=97_H$UK*{5#;CXfqV}5N6BikYeF3 zRL~#Y-y`+;<~nQvZF=C>&V7Xi%F?e+{eGbY&2TPz{FJ?VuM79?B=-W1dj!f+M=>kt zat3%O{Dhwo0qwZmwZLw1OwN-QNide@e4;4?ow9MN9++r7X@DHryhCIS6PJ<>q@7#Y z<#5`}%**8HSg@L6K2z<-lYu!wJrzTtmXYs%xa z_(H=y#<}vrKtoB@uN6&?cu?c#$ozgq&5uPMCspth9Mg{ltbzFDU9tK&M(V8hEtg&ydSm0Zu<2;q+l-!S>+a_!^@{|9w&^qkeUY~RKIKDKPzzDmDO6%&& zcZ`Nf0#4?iCwAhTIzY~XkB11U28@5h5A1}f#T!BYA77L)jwris?Mw4t)JI|9;uyUiS%A5z%Q8}apI=anol0vC zQLztps9QDOCXciBe?msl9U*|89ts-(TXP}`nVrg3OLcQ&C;jNwUPjaw|BEyY7Z6xb zvQl5FLfRmw?07leJHdQqcT+TVSyH7ugh+vE&*UzkG{AOoNH~fYQ*u#Uske!`{oDlV$ zp(b@cp&?OKkuv;Yl6eYv2)&RE1cS?{5Abo1qV1zalC~c5#ke!@630hVpftxvD6nGU z09pu?Y!sGx_L3D~bV>>`zRH7*pj_lwrn%KX-CM~+}+|_n*dHiGqosDHX8^3yk0={T%OJYyH2OU!}GU?SKh*| z+ZQD4dne8h$W6=Zp9G1-fqUD{3MuDYY@{*7!rx65|7n)ul8eCO3pl_*dHr4eyQVPoU+Lc%`=)0=t%9pW|g4dr5+cw34 zi&M>Sa)=}yte98uD%VFH#>c3U3d@U$&!$4epa~ez_(*&^M=_ai#7Ra#czpP_>9Pc< z=K(?B>U;j|6YfIm+PvV<)sl=&`in=X%!mG021=~Hjh9q@r(P-P%6y~Q`uWPmqu0I% z^+SlyjC1u@Txuyn<^b6S_SZQIpF|D(Oa0aI>?!*Hr-&nFKL?`pNcSpDZ+lM;J(=GR z$b1zqYZiH7pXrzGt+{RUsY{pp*x846T_~@$IEuCJFqYLlYs}kt6Vx?S#sPkM&L-0T zi;3PS>AJ21V0$OrYosie?ZYT~r*L^=-dUbBE!uc({;KlrAv+ORrGzzB3qbKh>e{0> z0>}37F_n@!Afiir%??VSAKZdwk<%nji3n-#fKM%AX8PZ~{Lo_d5Ph3S^u!TSnSVoo zp8DO+?x%$kBo*j#Da~X(m$Ov6h}VaC@bk)U#S~jW|iMJ$=JmI&i2_dWY4E45ii{!K>q?|^OVW59|DAo z?a#n?DGcvlB6Ch?$D3!XVHIB)*WfHNSe8i;Ti6QoPj5O-UhBBV1JibuVOEPkqx%E) zEcP1XAdWNIyxyPF2wa}-vC)Q|U@cz8Rp`7Kvoa1<(k16Jgd*-`99M|lo%fOQ`)9jm z@t5S}!Be-XC0sg%DNvSj>F|GW*8Eug6bVC?NG|5SjuN|be}>*Xj`7r_RtR*r%&~V9 z$=^fJU^=8O=o7}7rX=d5x^ zq69T}N&MsaM~5zEoPJ$iP>*DLthPLwKrA8Xdd(?qqt6>IC4)E#F}A1MyMy~z!rqsw zk-OEL==Q|!-&~CG?d>c2!h!$={5FI7iruZ@@T6pZr|GRS?clnRL^IYHtv5(J85uQ9 zX1}vxeA;*JVt@LD+0+DZ++joU$HrJJe}ui9(fNg8E+Z&_Jli*gc5 z7?_uAkNo_F{@+=Y!+W_6KKRF3Xw)Odn?=w3fgzQ$U+aO*g27=+1h1!0T)R1W^3KWS zLyp_;?&K)4&Ag!h-iU?%h|qpXQsW*Hy($w50!+62crqS(P4Zj_Yj!xJt$d>@m#gCIOX<#z~a|gqObsVeM8);|d-K_@_UGDki){>bA{LeRfGu&FIO} z{+`YJs-Q4cFmTK4#7`vuY5pCg_B~rj*fl3L*FK6ddW%H+&Z<#JiCHy6y1~#zAFpq?0ZVfAp5{@MPVV7v`g1a+ zVGj^Gwf*87)sExIxoW3koRng|W`C?(aS~?T!Yp`h_y~j1)sKR_Ak<6(Tuz<1DNhc1 zv~=WkTazCMWk5QB^4PoYBLpr3U3_WIHF}>d<2dqCn0TR7l0z|!<4!a({*1e?D|vO` z)8h@~5TJbog?WGMzpVPj2~#zYV`X*JsVkX+cCX&BA`kSP+UO78aAXUMi5RCz1-kI) z%;R|u7B7clz>g2YH$Z<02+#}zvoiT*cI~dEu z91&OB?%6d(@m+Ao4QND*kI29g;dduLcUefGGu8DL=i|57rQ+lma(U>>-(Udk`}_l3?8Ny45=hE{s@R$XV^6qB5$qk!lVp%A$xU9hYb_C_*u=BAG)ta`!J8 zwNK@80A^9A5QpuOj`Vjy8#dDg?cZxgo9DE@Mvo&Fd*&B3{4ZX)KWIAr*S2ok;MJuI z9$iGze^%imj&i;JBK-cUi?r`KTNhi27;)-r96rD&pZnB()!z}euV@RFR_5t|#v zFJ6BcA%~E27p}W}yV*u(`eyud*gSfJvK2)B7?MYo>dym>o`1II|B@R(r^dY#0!MuRE2Ocjq8pL(7v%+L`<~R?$uN?d*Zu&Nmb;qH+*b%Xyq$fd zKt=`BHnoBN;g}51;G&)V`@m;H^@$yhc=9@Ot}n4Px_yYM%GPmA%oiYHuqk`tpE`k3 zgxHj9wvQ3GIz6xcwXJ^F$LUD(M{@q0_?agoEPTt1DNAk^g3@n7iQ5Lg)_+4fspD+< z_F+$+VD@3_6{-&!HT!``vMG{b=udwXB}cn08wlLHbzXvLGj|P7#2EijBrs03B}$|m zGhbKzu=`NWZ34a&6D<0lIOEadk4#1QLg{jlHL(9ZT(ad>iV=o9L$Kr{!D;DdP_x@A zdna*IIk4~$YQ6Tzw=Tp8??(R24NidFD0pc3-5prp=tT75=CPAVdF8y2@g)m5w03xv zb)6FqHt08UYDhCdZfji)e^T3+3TyGEn#jK?36q)fl9` zct2-c6#~m}>txsZG8kK@o`G`X=i7($4DOtpb2^dqx;dR#+UL4ykt%kXH97v*#^Ovd z5>Z^?nX_2sKy|PSu(|YB{P&ux1l^vUZ+Xz}YOdx-O)(*cLVLBe;O+o>O#5KPF-q!IXiYVs%ZaxuYMuWSYY;kK;0uJCmwV_ z31uZD@Kxwshc(3lZ`9a&%r{)(kSB$KZMlIZN2JhP?|evnj{H3Wt?(35-?REn)9CMK zUo8tOfMxEWQ_JnnK$M_5M18W+r71wMi;~V;nK-kXDX~|I=tJ7Y{LpE7GP-=&Ks=(^ z_;R&$TIlh`a~}m>S8G?3vCQenwCUx)q}b7YN7G$Fp+@4jM?joHi4UB5*yt%eCE0c(^i}stCywnlTRX8g(=&a984t6 z5->`6T&}0h&SsTXFReVj_j}lj<^C76)LHj!U%<44NRMQXYItmeL4JxuB>)}g_b}3> z$&X6zkEdD#-=EhSG>mu?S_P_AJM@xSF>|3^76A?Xv0OSl0o1<%U|6VnLJ$~_-!Neg zS@$Geg)k6Bo{X_45><&@b-RhQm)@3AU=AX1>ug0AAYR;6Yf<0ls}Lx0haZAlRiE;_aCC(Pfy1IU4d?FBO_nIfFb^GmxVgz|^gcLqbX z&pNu^UBWDtqr3?qe9;v(WB7$Yz6wed@PsUQQ#=p2@lZTSpt5Sg_tbJ>D%=zC8X`v{ zDZ$Xjd9*si>00bsZ&BkjgsmL`25Yjv9Ow+2*?yTnwDPJ+@N*1nolhGcR-OpPV#qGY z(9vJW2_kSkVNt$^KI2_OcgqN>th3TiJ8+`4hAaEOBmG2l(|&kjw%H&JWS;B%Np-z2 zJR}B;D<=}CaO_T1X0Oir6qz+CU;8H$kamZgx7$~>P*eDblIpN8e)Nb(NfDQj|+(?cb!gcXWcEm`7A z9a+>ZXB7iQYh-%uz8b93Kt1zy+6RZ;Kl)Z(gKgJ@Ds@~aQ9@X~@1KoJH2>o#e8|y% zoL=II)GLC4KL9v|cB2^trk?%Km+Nz*ufsxn+TfoKTO&Oh;^fiE&%a+e{2q?GL4A=f zJ+hE(g+#-pU#+{QQ%?R73~?F#uQvs;OgsM8S*S~T-J+=!`)Ik zU_!gq&3-ysd7LiVO-kd*q0r)lYhr?S*Zn3i2RfmI*Rfeb_O1fSb7r-u+t$Dm6nY^(%>!e?_fjxiK^gv&Sw# zQm{Z!AcKl!QET6)Fp~V!DleoMo6+k6^mPf8D%?GI^FJ8*k>rX8aq!-JpZ8Y<$*05B7ntxxaYaQ4ucsdNnQxoNK? zQ;g@sMJwAK$9al#+3~amvnz-w%l$aQiGyfK{C!3UR(1qp{9;;+whc_^c|o^NHP{^* z@a~7eu;GbaG&=4OG$80S*|pKm^Xc%&+^D(~KK$go`PVO7Np;9&UdAL>mcV9!O^aBY zjpe1)E*Xa)eoB9Wv^N#!Kf1x&LK5K{2O!nFo1c?hfP) zs=KVJSZKXJtW9WOTH@h}QGsjzc_x~-wa3iHoP)4r_-Gp-5>y{Xiu<2=Srn$vi3lC; zJpJh@<-c!pn!j$p!wrZk)yxjNuL``R%6}1K(@iWtf~yJFL3Z-aBUA{eE4M!8tj%VF zXSKt@H7OaALz5U!^IVn%P={cj%eSwRKZ_*Xf*+)Dbvs;N`l)VQp_$dwoSJu|1=C?` z1-(}*eSoo7hFv}(Mm~h_>CRZL6@T0H$5=%ql_3{eNBg7oc4PINc19YME*}Rmgj&2& zWUlQ7p`K$PS&Cup0xGN?>}=-~1)y)aFZ{v?^({wwJ^NltGT^WwU;ofK!vOuz&MJ4K zfTgO(9e2RNvoL+0nwW!L5V!=~v;KA!oBmSQw+M?Ey$6>_|Db6_H3 z`PiOP%GjxR;G%+ENHs_*y&Pk2A)IMDq^}QMb|yav7hVn%))ox5!b{}ZEAs#dC&5gDlm zI+*}VDoT)#$CpI?jT)C3mgHk!gEW$=EYb)*WKK&v+!`Hn7cSurVH{a@h`&0ERxQo} z@`YDuL5(pIG4A&qGe}mzd~+o4VbD?w`79d#3I;j;$hjBNSP{Ls;0L7aAN};{Ana0n z3tbR-;GHRXnorLnx(Iq%BE4Vgzv`4Pzhjn>87Gjxx(0sCB%R4sGWM#p@f@d?wNdrtTWpG_rd1~RjyF2o$qnPZc)nnTxdW1W_^*}Al;T31m95^On|A1qP!QbrO) zmkEGbP-F*rp$IR7&T^BV8*p{cH_bEMJ$f9BQKQvH++5E}lX$cDbe~sl{o}XXibwqCa1R?jVvfY~5$rkV9PBSht$v zibY#4c-C@`tAXp6n|ae|1rBorti9!K%MX_*b0b$2iy2dNFHrw?_hCp2j}n>1Jd1XD z*YT){oX!@sGCQ8gEh>p@ingvLey?;F1IIt&F&Y%%NhWz^VG2a4f+La_0|Mn%#xuW@ z)3jT*LJr*pzx*6*1Q?^jzg8{NrL^aN7hDcRCn5qJ^lzp5)z#peq|_0BI}#;?&MsVq z=z0~oJ3?7@CszIw+KDU5+)|og-zSNoke4OhwHySz;&yo70*XujGRC!9ndE>e5FWL?Foi4sx6?d-VbI-zi*JukzD>MAw85vyyp!+{=Yqf?VH6 z{(UN=i3V&ac0PHBD|{=Qo4>hV1fE`yO2+*_xEQ*7pDy%Vbar}+j@daeP}nH%DuQBL zVjTO&WpsCnj=r0DhKRp*Rz&?+q@%2@=-pYYJA629!Mv#>+4|?=)DD8}qoQToPg^Z; zI&t(vFJ1FefYA}sr%VuWIc9H2hR^eTm<2)1&ZrM+4jkZnAFh2EI#@tJU1+=y_Uy$` z)B$h9DLR>}?{@iDkE{B;%(}0==s8_gB#ZaH&-K_!9ZC51``uFd=YZ3Xw(FJ9bap*!g}BdX`1Vj#D5^EFYU+)6A?4V0MD~Z;!)E?| z10z)MEx~I+DUbT8A;4-Q37yTeQQ&%}%BJuJte!j0>=Z}INfNkelv!C3le;*Jqwgub zc33}%d~kPL)Zi2zX`JEwrobF@pcB*f?Z$Rd0gh@jdUXN>wt$}vChJ;4(k zMkN?~tS~f}y_}*!K^ZBTWhBw8qO6*)`BpiVS#^-7RkWiNDw@EXRx8z10sML+><;@OV>0^>#99|pDb}6Xp}vZ z;ZzU_eP5!6F;e+q6^$yp5~nwSq1f6)H6J|7N+K)~EFmugj2DV#xByl2>v1!cBs?iOlmEOXuH{z5ySNc-_s1|q5$pWWOLA%1Q zKC{oxsiY_|fBFqZ|KewCX-K}5GcE^vP`ot`idc#Y$d1}Flk=H-^WohWF6Wc2hu`P| zbz}G!*1vRN6%x(+(0p=xSKw`9*tZzsnhpJ9MDL>P@b5@hbC(x&)PMq%fG!~AzI$T_ zZ%35RXu6Hb)8*bRdI#Q)YP9(+djgRr8>JFzaDIFrfOHP9I?4VY4*AcS}T4FiqSEw2V>pV`h@SpMt=M*AIrA~vK?aK ziWW7Jz+81m+(N3umaPLcVGeWu;@oVbtjnGRdW(4Nxx_U@0g6G)MvF9s>c%jD zLPRy$DL?RZR+j>Yc4Cr!M2txKavKNmNbktX-+I$e)(7BsJf)qtIycd8d<#F>_B8z& z7?0|9yNd>k+%#kONY#*VgXYJDBW~2*D;!?OF14j%-*6?TXBD`tceuL~PLJQ^<3dJ$ zDw{R4gWh>`l@3#1rFFH1uMY=(xoj(ju`5I?B)4IYkg^Og@LAZ9rNr>H?o8Ya-+T3G z*a@jh4s_y<`Zje*4isWSJ{*Jjfzwx!{lj6Rz6JnC96tCCwbCFqNvK-E=dCs&H;lYbT%~qv=M6}t7 zl5fB$icR2|MDN}w_l+bD9y-Qp_Nrz>6*i7uzb=gVpG*=dM{OhIx_Zxz^{?nh?S$$V z`U0tW3q8eMr#MsdIA~U;{n|$QsJp(F29!ViqP?Tzz=MmVGf-f3m+n12E7~g*@TCnp zDXlrsJ?czjRK?ZZ#QS~=a?b$UcabN21uyLAG#7E=St_7fuLP$)>Yx9rpmTk|U{17F zJa@B4v?evwIkLN0(8%=AB#y*#XzR!*H`pOHFq2bX#xcKfy{ zegwm;p_5|@c=Na?CNy2wT}%hry)2^5NAnn3Shh>??7QtC z?dE#(=AXUUm&bp79b}*V@5|2F8FnJuxuX=-BO^53xffB z6vaOXa^h-5%13z9frSX6x`8-6g51B&8^{)BU&{VZt^gZMSARop^kmvNOn|{%S+;6i zeNQlO=cjM_XX>_ol6OPN&D^BHK3ug6U^_|-$cFlfp*xR}bfsQ~+)5+XQg10#2(A9x z^ZpElY56j@dGNXjC3=cU79&t|UKMaNUo3X6iGwG!jopBCEk5Z^jT!yR_$#-o;5`!Do7vUr}(Jn2&S!qER~WyQbV>6 zZ;R1>-#Z|~K3}IjE3UBmE@^*YZ19~)Cy^NPj5Z-S{gRbpl1U%hDAO_W$45Rjrgybg z{>RcMU}0oZnR43u70TK?$kiPq{k}thAKpM8KZCZ_0XS(#P?!Olv*wX5j^3?$N@8~F zF{+PBcTDRN48+>m!nHA`=liMi##d)UqIh6xx-t!Ok&nr>HN>6Fvq!4w_X{OvSL4Y) zzc~r3qEv-|g#)G2hC?58v+@PBq6&4ennA4B_E3A=uqe6Y8GT3A=qq46Gc(OUcHXhT zY5Q8-^Xs73pb(b7WfHg5pbI8;XU~81=I}$FAXglajd11=5_h@b=L&un3**9)tEJQF-uU;EmOXLOk>_gD=h&8fOR6inJUlSknrjm-C+I zkess5&W-Y7A8Yv;CwRiH$<&neah5b>-raT*iBb1m9ZbD1L zj@*u&c(!dGvmfQ8pYYatIovfGI66Y(OT zNQlsE`;gON8?55~l-kPLKL6j=CgK@hIrV0HBgnMG=`Rp}q@S>096R`yYu^KG7}J^|*xNm(vgk=W+RW5oY2K;`8^4|Nh+NIpEnPZ3vzC5k z_QBT!o8y#Yw|^8^H6!cOBeKxi81gWIP2yKS)vSyY@`N`wC>Y19AKpk^=mHkKPc;^? z%Ywu|D4)-BL}Hl2_UNa=V(A$%Z73!28D;cfv}4kSBzKc9M4viwxY|RWNDNr2DKD!$ zKr52q6j|#Z^QG~}T--39M`Uv$uSH}%Rv8v~h)7=7kQZ;Cb{L}e*I4OgLLz2uzf>=# z*DhX|2>ta;|L(Skv)9L2YK#D~hJiDo5Js0DYXqEV-o-vqq2*XD{-OE5TG6&bTG{#w zq>A%FzL`0cvYE+~CX{Bqu(%Hi>w25xK=JEEk!IHCrPuB_Luth|D?@+v(6x?n;ub>( z(o3j$r-XP(SlX3Y%cN091KFdIBz#*Cqf87B7FeKiI5UYmMI5J+u@89aU zOy01B<#pvV&bNC7=L){Rx~D2u%8N(6`8$N-lR#yT4?Mk4he58~huZf1Jb8*VDg;Z@ zW4U2e5Nfi=dw$|vg2F+brNw5?6HQ0yU6pIVPB@j7SeQj@*pFgW49jhel*pBtsu=pCW z6h4uzi(6i|AocdXIFj?K-t4j^9|xi$vW)O(fIW?`l;YgoY1s{5L z6~*uR|1JQk0x(=p7Fgjm98~3(#-5xP^{Nmf%lm*cdHxFuG#>n6b{w0)!0w=Tv}~gJ zp>}<-+d%&;*CDi*&-OzHC0`ME5Y~VkwPVbC3rGS&eUMrp>hJ_`F?cKwH2sl}>4kLyptu+r@zk+Use!@(tY`b-%cqs&1CQ*#tE}j$AGLr(-e{ zqE$YeblUd3`jmW545IbGy~f}E0xpgxPF4LWik^8O=9sxAiyfhr50Bfk$#S*F&~-Sv z4LpkvM!p+#oYhV|kbYS9h3n(#^9w-@ns7O4Ggi~f1I^`VBH%SS33P^4)IkD7Qlg(} zAfGb-)HAqodp@3b#*P4T~yBwbl@_Kv9-Fc%e zmWPmZiF-+Gb*1KQJY%QPUaObeQ_JAo{9@5Jq8@q~Ly|LCTYaA9wAGa6C6ick>Eh1! z0w;iF6ZWgdDq`?L=QV~hPxSYdAJaJO_G&CvVLt(CS`sAXKHv6U9NEtcm?H`Qy1t&e zvO?u}+$rRn8!>KD*qiXjfOT=PNQFOejFgvw{{=CtM**CFuUSdZC!K&ylp-c_$1EIj zia-L6z{oNuJw5{OeVdfLT6uxj7qd%=;+D=&60^EiIaV0MCzR@!kr>T%c`pOoqFuK% z3HY|A5@&EY0WqrHWnSry>(qRWRoO!u8Qgs;@U!|81N$;ug|2RnCAbmMfrUqKgjC9V zueBvqE*vbF!hwz)08fxh7*ZqY2y|gxUj(v#lU&2eSsPdb^$8@VJjVE&#tlzL zFmmka;sJ<$dBNl$Fd8~6rEPnHJT%&Sw4X>kew+HWq!qO|__w6!$tAv%P}+d6c8Cd& z6aP7O&%YOY$zyK{giF3vW%(EI6QQ>!zsp3y`_v48_mlwvCXYM8Rj>IHG&;VZLU)6M z5DDU5B=GuhY7Sq@!B9T!W+sk6dEWkO?8*|iD!9t#KKk*8Uh=b=P$wSFaFcaLi@CQL zdHGptJxrXW45T3f#CM!|t%;4>)_8zD#^%*QoDuqV`)52B9pG&O%*nDu`_5%bbp zzZQApATI?Q2WX{wy?0b!yXO`i{#rBVJH8gwusTon+)@3q>fUJHRmmVFo=WR21hC@Q zu?Y#%BA4%Og=D#cXxLn#=KQYL$Mw1apZM$PsY%2dQ(?ZTD+97wjji!E?|TrXq!G3F zmY>}U#rnK(mDP{j5tA83UWD5z_~3SNGP!e03S34j9DOEal$-bXjps?gdsQ;q(Sl&Z z_f0WP|HiO}SGAvDUx;qtF34_k*y%=E(Gzr@@nEeaVcW=lWIO! zE16ARtGT6?XdY5-iIY*A)3L*kt5iinEzk157)_EqzV+XO7?tir5BkcC4&4lG$`&QD9xBKF3Ae((Kwg z*C{xL;@{o4R&^Chdw$Lu8>6Dqw3 zuPhZT*3Z_?I(knZ=kE?4DU%p1|3VH>TusmEsWU*5Suncu3CG7F0%hSR8I4Cwxrd+h=2D4zK^ zCBpc=q-K}x)Xv09C5lzyKeJfGevn8ks6~Y)Em)6{o26ki^cW>1!Shzhk(tSay@(yeg!>VdL{#Gg zN_LuD!acbeMM#weS4dK?8}G~?z5t0CSy*c!!l#<4M^E) zEIfHaJ6XHTA?}tI^yp{EjX=Ar0wwgEH*PreZ`T^|3xhC>kB1^q0VcuNKF(XX6A|Tg z3N+mJ%ALjx=DmfK2Ko@{#*#oEP|)p-O!DuT41q6Gj{5mS`6vH|m$#kPCc{ z29DqscP0r9|BA2Bb?2;h`0dDMe+`2cs{CYKCC}^HA(G3kD_QWX5%lRx(D3_B#d9|o zabg?>D+c8zBFurWe^S1`>B-OWB=W%5N#xRN)4E}6qurdle3B}O3cNmjN$sd?8=m3+ z@o2SN>ejw)=tBJ^lmB@Ads0VE>?5JM+;-d%sMuB2gy~bJ1ZE@SLY}*!<(ZKbmiwQb zR^B9Xj_MgGvpyEl$C6$lNeAHy+>a?9M!+lf=r`)|7iX-KQ`aDwqw;Gk&M6gft}Do` zdMWU#RxzS`zJVwzM$6a_2h=b>aUD>AGh);wOU43evXXq1tLS^Kp}U=SC;c0VrrwXQ zJ|0SX$*0wi>N9Up)htxLfc1FpDn@$6d)!@9l*2)3P`rlizW%F~SSlB%2O>UQUCrRc z*y|XoF@_$NugA4C@evA;0(69S?dPGX?CwZ|2>GL@P4F#|%yox9ZOhlb$yImf8^RPR zy@McMRcPtccH?X4z8*WBX(a=GYyt2i8+}lpoT@`I+a9^IwJ4uXHZ_~r*vh*itf7qx zU7wnuvMmfa@l1$+;qlkDFHp1(5`u0lR7kRFmFK_@I|-5yUg|Swa-_3Gy~!@~f`^HH z1(ly;DdexLNA;W4kJonm(B;}11&_}z{rH4gAWJgxfCcV6t1 zBS0cU4_7@?ZJpE%OV~UyhChVL7H;s$^r2+&Xih`)W0XqQ-59pFXj*Y)oNvUEoH#~Z z>YP5Ed?%U|L8v@)7m7s%+oRxmYup7TcuOIjdYu3yK5Y;NH$J}vDX!qT>yn{FFmrr4 z9Ro|Dn9T8xEM2{O!+H$N>+YMnakqGGYriu6hTHmOYqIpw6<9;h5oVv3A`LwH`@?f7*Q{f6xM6p!^~QLpe&iMeNa)fH>=4KKAGHz;)uR9*9Ne z7c=7C-@$iJntw?AgkpB2q9W*Aj(IUsZ}p?+?;6xI7`wgEMI-r&yZIe0owlA{616;c zc>fr7=~MK3P$}`)g1#NTfQ?3r0}bGBARYDgKS^4#B82&cY=<92Y|y0dG0DWsp99;_jS==5j8w69vuWByZYWYVk)|gYSG^ zA0S$OzR6F|X!c`jJ^^TpENSqPBF7>I3fNu8+4bkO-XAm|#FU(xwEtSC0}i9~Wc{IX zc1FUn2bMVpR^A}LH>J?s_LidaUf!bx0w2Mn!Sr``C&7>6Z=lM>MglV3uD^h1eA=Wn zVZ;iH(bF2apJwZUC|hHpGx?HUe;*95}p4{vbg@ZxEC3 zfXYge;eH}>Hh(upsDrhYC0QBifZPhxTof3(QGLZX_m^!-EaRSF2Fi?S6?}`Abd&z$ zuhz$R^Y^(O9m6FPz!&A}4=>0cLdZ!9z{V#Zucn9%YR}rCoA=mA_l9cjX z8@jIm#8BGh1ko-Lps&Shq>bfEK6=R#3@chG_=xPXMc4$^h9R1cWc6~MRnL-L@Ddr1 z!W@HTP}U*po^$a(7)c6gKY@_JYqwjEY577SJGEfZ1KB_FbT@j<{eEt3?n0sl3K-_n_rb5>N^JbR9`auQQWw?2iNx3>j@rUFAw^AZyD> zJ6bO!X@m47WSFr*LTsi-;(Z)U*miH?%<1o ziO_PCt-FcSR^7^#68Kx!&*_>8{Ih+uAQMgRaVvyf;KWKjiF(9DHC(DG zSS)rGnM1Aa1vR={MutN0N2&~Tnu1DE7tk)$X#WNt#oB$VAy>2A4G%&kN`&1(@S#09 zHhwKYg<5c#N)%}YG$-QUQ~|Wm=Ykrz?gQY7W%ow1B?0Xw2YbF1ewJBvRLrZxjOJ_t zF|}!3wh`y4x}r(IY5lu87_`t6LSIHXo*hmL&&?*LUgSzTSB9alw9l1z?~6L}rOfW3Cnaia8&H#`w9;bMU_JZ(O( zEf$W}gRZ}=h)8*!FzxYmuKUd90a7%c5If)7nZ+?T1cz@;-6LK`>eXM~VE(&&U1Cd- z0Hii<8qn%|`Q3El`Gl0=Wt_W@ZihfTx0fb;>~}DBRUL4YwrqyyZbI$%w?!iVMlLtL z91s(R&e&iH&;K|x;W0etFhdE_Brfh=0q_?F6yAh>eNbZ`D$+rg4;>v@Ed*;}Z*gH= zJ+?O&(~H(ZU+=H4>ko_YSeKT*3M>byonJWHbTN({>`;za70)x$ zF1i~!zA3$V{)TZL)3!i}@wVn|bLDfxI|HIO$k+Xk`dK|qo^*SY{5ebGDMnBFVV`R8 zN@AN!ofnqCNBOgt@T=F`-tgK?4r&|S5Cr&Pn^Sq(5;{B)XNBN+O9*!c9`V^h?M?to zs5H6UabXGYdcTAKie}Y*3?H+Weiv-1UpA&G+d<8$6n3Gz2_D3PbDdE8R~|5$UjRdc zjbKGKdhB|w_Z*hIbp9Ww@~nKi+pLk;0n2QgS>Mi_zD1jsYM)qIhN*t zd)742Zhz}IYZk`#{kdBSJmk_O@zVt0DI>H4dmBne)5`$}&V%d#t@t?Wc0Gs z&mlzS89C_7Q^h|BCsXV76+nnqNM9-y;;3pjxOoU{%kK^*hbazH=-Ba%YI(hm@5dHK z7Qou%-hrQInTwtI$bf!q;z#OT^VHq9c?&(5!POv*x7y6}=I$MskVM!Ttk#FFt9g>7 zPn-~<8f2)&4AEHV#q5KxWL!m|;sKySY}UM-NR!_MmIM#-S{GXgd$proF>pUQ!jf20 zaoWb2Gz9{=xxCbX4z%$EIw%Yzpbs7jdP|&u;yQX*4DF6RIfY}B{R!324jdeE;Ur2C z(KA&;NUT=i&{dYNn(^L&rQQauefI*ueiECs1NW!3 zI8$X`(=TBjCp{5k_! zQi7v0TsahWXuMoY?*mE7y>DuE?I~7$r-#@HF2RkemD)Q3w*|~=7=7B6IANOU><&%! zORDg;mOu7}iX?#quFpbOQQ$j3Eh(bc5e0ozh{JxMjc$(zLXxCTadLPG;uR?5PR*_6r8 zL;1k(&joDtLm&4`;NIl1VYdWiQF4My9@d(8k*QDSI6b|hsx+y?Bx~_sj1=a#<||EWz6)`16}Ro_A1&^pc2;niY7)KX%MDEnUj5Ahq7FPf z6)Eg*ctW(#)+1*NQ5e+%y8Yolp3jjPc|l~1ySge@CI=p`UDApv6oGUsWrS#OQsHyt z@Fti}-vcG$r6wSEV&AK0Z9;U$g;YL@IpZf`C;n+HZm(3Z+Pd)}ULXaUo^W@2-2Zp~ zB!`CFrSUQ3=;;hB(OmSvi=!z=@>3PVTy}PMy%!bOcAIMyoIa~W8DMwS@e)E&nff2M z`i-?|&Oh1G2QuB}e7_AA3@3uL%bQdG49XFAAt42?$N9VeIoE=sd_;2pAFsW-DAPc| zZeAuHsbd7+PqE(QYNL9VBN&JtMO+6DEl?MZSBFB(A)!d4>=Dt}>3b(IPvk&c{6#&l zyO5Y7?1^qY3s*c9)Ag3=hWE|A%`5Y;s{^Yyuk3*@&YXfH1qmuZoAn+0dI!nZarH(D zKfD4lv_T&ijXC0BEYN^{yeWRc;bP36>14ZJ=Ex`?uzy8SGo4kUX-WWf1-fQC-$4bs z`@5DOjvv86ArOZ;&Y})0i8Eu|far0kd3((L86xPob~hJkSskI9SOb`yrGn`m7JL9m zS|Igu@M1eQ!m#fqyH5!$w1Nmpiql@DY84?P>6XNcZ)xT8u0pWYLC_807v#B7@}-C0 zM^ZDN?h5^RAT7F>n^v#Cv%aj{BZoH>xF1C=irb0tImU~^&itf?lvAVt1F`JK(jMQl zLw`f3CDE$R)AxWLmtH1lmA2X~Q9WCL))VXNK2FLAzZ1`c4>2VT_OuW`KYJ(IrfPzE zD-$v(cW6fbWJU=@lP^LojM}8y?w4YjNW;e4CPZ3BC~P!I98;&zx#8l#Lmv=@a^h1k zDB@)?MQ#R0vT!-F*of7K&*Nz)+Ob=M)}T$n%aM+v7xXL-sN4qMF!XCT*GA0l*cUhT zxtwM^i!Ry8lO~gsV+c-kS-B#UoJT^N%tM;YQKJS6?#zTb=veNVE$w!=7@2zUu#x%2l& zKN-u95CwPO*c_0AH_C8`dkNG$%!xZvMQQ05To=4+)LQ|?mX?p zPkYStGi1~h7GzNnE8mNiCxLz73%=n%*=o)hv=42XCP;e$j5q^t>_ASSd8*g~8_0Q#3AUCB(f_*FS& zW-RBJ<{U;$@4@>vDNotGmoGa6jNR)s#!wy#>fNdvI?<#`P$2AJc<7_xF>;xFla7`@ zRJnUA>k6!vfF?Agp~2t@3HsYUBcw~x!xm7vEWr*OPy;@f|BX`X*EhDlYnf878gkC|4TdaN?_3233bSKQ zyfaBOB3-PX|H$S2GUCMNUq6w3h18h|F;8n9ClPpEot7z1+BY*@Xg!PKXAN$=HPP6+1erYKpSeyMvStxW9ZBD58Ykx6O>S| zdyU6Waq_q_EH0mt18K^g`M~`58$Q~!DJiFS7b=kvMw859chcr-{GJ~OOSs<8{L4qW zp1l4+k!M;fpJlZ*SkO-#`ZaK-lI_(+-F!6gz>L0q4B_7kVQ{Ul=+5&zp}er)dv)&B zdj1U%92_Yr^b*wIyFV!VHXqg*4Jbs*JqYB|KEP*<2db#PYLV*yw7d=mO$2f|L>yOv zpD&|=++_))vUF&HUPs9pTUYz%B*2mG)rE zk=G_|4@l}mdQa4s+y44;Qtp6ySOP>oiB~f_FMfmcv)i4e`@LcV0{UyY^6-tR&U92sIIe44^r*+-DrP#z~W` zFC@nG)2@xBATp3a(8*`G*^_m&*whr&`ty=NEXo|3h!I#y&=uvltr`3tN7QtXBsBdq zkq7h(kz(M5CEe3BuG5H$n4>n_E4a9QfEizq6Nucxdi^MU75h zA)1+v7oK#VzIyU+ZQD|zeY?~x8Q_Xyy4DGRZVVoalHofJG&MjfGzpD|{jFNd;afZ( zC9bz~bvvM=V1@Y=bKu_hFVxVt;GUlm&asot6HNTG$81Me<9ge``0D(;n$`C8mf3=R zLG>vu+a)&+^S^TW|1JQT`WuI5E*{7IyWres3<>xxa)KyAhw0f^dIRYgp?l|6-X9>MegGN1 zlpDNc5?y)-Lve+lVHq_;&WY!N76w$$7^U;}`yov|*gS`aekr z+@=EouY|i91vlU_bo%dJaO#Exk>KB$9CI%5c03IH!}w3avk* z8VQyf(ouccEI}rVlJB^>?stu~6)(prBix93g#9GuEx3q?j@dm+kh+D3ySt6Z4o)6qlT&leQc7F??T-+o@g6X*^PX$JARkE6+uv@Ldw z&(@OX`Yy+AmuwU_rIyJbDQ?x>OLvNXUcC1a_;DTz0qVodj=%oI8&en$g)6Z za+7B6L56Rdv&yy%%inom(-y02x2(nyvBP`){AFQCl0`JTu0e_7?+?&O_;v*w`SC-@ z4oI)4=fq)2n+0$ICdtEZV<)7J?J388Gox9+BsT4M8VbiU&%)QG4pwp`*tyY+Ly7k;X3fpk%{V zp6QB0>Pul?IX7o+#T);rt@ep4FinIh4Nc1hlDL=lJlPo2k39j1aPP~A=`a0T{+*KN zRT?x`oH-;m9(tnznC>pOt5>Vpe3bJ`8ut6hB_jI5MfohR1s8tFB~bbZ&M|R8F%=OL z`>z{AD<)ryR2IstNI|14g$i@%0sg=u-=VCDLVIm7mKqIUOYS?fSz+iP(v7dw88{YJ?L0dRA{ zoZr#Du_*B&`|)qXW^{4|Ouxe>lu583x@k)cVs4CK;cF~etR}tH)m})4#xaecOaVB{)UXgeZ0r*|J!pk^)XQ4DzP(K zvOW>RN%#1{xOKvzo3gZYR>2#{O>zJ6+_3`boAiq7*UFwj(=;^5MBMK&psQh`MnKDg zd5s%%zz_W@@69?09>Z3j!@~*S9FD1Z1nxEQWJ=z-_zpWf=b%p?Ise{QkV4}##Q`(J z6>;tzxh)sgXJm_jGG>V*tn{vt^LVnq_%J&@FZmod-e#Je$fXZQ>%yC1SNrVMi}TIl z<=wMTtSUvFxA8B3V)gY0Kx(_7IpL$>+5O#;C?SP^LOhR&gF>zYTWNs~qVsp}lcixB zYx0hwttXoU6Q8D2e0)P6$%A|Hv=n~Izm-}= zNNS9QJPs)D^O6u@%Q#e{@wRY-GUzH%=oENhH!C?KH=@VXXwb%BojlDC?v&0icJ%Bu3p zIb~EXtNo{P|9wU2UGAH~j%KDv!~&U3NdT6&npr(()H-W5|ktq>YKdd(Xx09 z{Ft=sUyzOU*7`3UbKV3)w?UPKR+q`J-=eJ>=lv6Yl9V3m=TXdt z6||#^z3JLPWUZAm_xw0TR7DVX>LkC(Sw+?^rH%TK*avCCCQn`fF-(%)Bt}$rJW?z*qnY9!6kGN zm0?GC&wj97|MK~c6r|y`THsy*@EpF$SqV>mGsr-F$?$JraP!gH#C6!OBlH0Me%nRT z?t`CQC^&dU{V~XP@a7US!8)qbe}%K|V9g5pFh-*w2Mf-}^)yOKN%vu-`Vl9_CoBoT zXJQcFHXMg%sL9iURCcb;@^%0pzt&1EytI-(qwG`JtYtxb1+GFhIz&B zW+Sq@Yt%dkqJsEtL`=S=S} zK$0ci&IQ*dkIxku!?cwH=C?3$g5NEdW^O%ejvBaVYsA%(aF{a^&^>bD1J_U?`e{D< zbFs9^Y;?7L4>@%t)g19-X5swy{HuD@Wz6OR^x=ZSpSg?c->?+y!INm+(~ z3oe`+*z@c~#@QQxO+eD?uiX+MQ)3|%sd+&UFxjOc46Y(X3U8l|mo$B`H^2=VJP@x= zqCoaF`~X7{5hrOy!P(F~VfwBoDQg#>A$MZo=I5e-vibSy1sY^9L;W`F$vWZh>D177 z;)Cu(hsQUljBxLB0nwzCdCa+N15P5XD325dQ9%bwz%l zswQ#wro?QNY5o-vy%CB+in-l$>~XPvytT8rT9px~~zN`OBI zc`WOhLSc6uMz%&%*A+(;-YM}c@vb91uee@+bJ6<8uHd5Gmq9Z$-&~8WvPu;I2jNpM z_@|Z*r9|KSr|ji-8m@hLoc5$ziHKX&wGg5Yp9X#N)H-%CL-E6-jW4!6{RnPkV**H- z!iB<)@<^~ITktHyP>Zbd*d@Cx`R9rM$daDyDt=PyGF46){Y(c);-hn~PoE9F{;zx7 z8B>3$Gmm6}lH#Q)&@J-dbx}*4Q+?bG&aDQlT7myYYDUwYyiU`^M7}rmt8UA_ht?qh z9(LLvjbX(rh&1c=t9u#u$^le@6*4_2?DcEZhAXSi+$j~-i~BstNh#ui|NVZ21nsGg z$Cg&G0w9?=a_Kpo0=;bJwYQBonO%GLLQ9Hrn7?a(142+3VN(2`g3Mcv$biWwi1NEj znRX@xYWAS`$dF!wX=l4LWiHC7zz8&*OcDS7Ej>^yK5+e@c+8l?@p4hYYM2dJ7=nKq-6NKA|>WxhxBeT~_D&hpPGiywrlc@X2AEDiWxYT zV3;Z3(L1xM;@Jm9IdMLt^c+CZZA@zaljnq-0nJ= zp+#KX{^EQ$mc|Rq7)qmpLDU=bBgyD7#S%m|NG$q=FmkAib8amM%Fbt9ww+)XP3Bs? zp4;d$LLO&!^EwV$LvWt)s5-KCfsO)uaTM=3);*?#=OfrvBd1vp<$(C96 zIOlxM_lNKOAKbs(_jSFV&)4Gt8(Rwf@e2O4I>nG4qE;5Yr>2~gB2f@W8~@JdmMXO; zcY;M@M&(?6>Zl@_F)yCNqxybuckMPmIwL&z%+lu;4vkS5kuA)nmAx-2{k8C4PG^_f zVBww<(Gd&$sjU`RY}B|>(@s$fTn3hs*@ER>xK4b14^}Kmx1WBAc9v2u2L;FDd^C7$ z9wT@Q2#mx4lpgdh%b}E+iNhyVz>>HKXy^=H$IMWZ=K-a{2+*86RacDuBRcMR|4sPQJj+{;|6o9F zzQRgun=pYvM>(7NUxD(9>zwob9?-!CtOQAzX`nC;aLd*v#fvHnfdVs$egzSj%~bpu z9dGcInRugps9HC{>AKUr5xZIxJ%3LhY}XKs7>R(u3k|=KAiO(F7uJeNyA0w62!hXG zZ9&_Cs-ufT@L=8aKh#ZTMw(`^`JPDjMu@*i>gY{sRK!m7^t6!!SxIkz*|AMQex7<= zldMCxpC|bw4BAj&avh#|Z`cg-agMjzHa^xS6bnn;tHa%XdH*5ZWf}_zRw_@Bf>&5R zsCf7+OJTKY6vgWjOlHX>Ut1Sl5&V1gL?yxwE39$~@jQuBtGZh`*TdpMq{6%k*X}J_ z)v{KG!;_Ig$|Aow6tfrBmWYaA`v>kqT}=osijz(U$~9r5F;!sj;Kn2Wgvj3`2!)!{ zEi}(Ya32rQ`nHe#NQp<4!#TJw0Ix@g><>@b2xA;Dok8F)++WZlbmlAd+nGSPuP!;m z=gNaMyu>akfJ<~jc}`#t?OY;T{GaHw-R$v+$kizWEo$sM^)LSUb$XleHV$LAif$8g zg$k!}BgvRb#;-XagyeXwPJ~gZ&7u8^9ZQwUty+%s=WK)UWZ~&vCM|O43#9Vb`J(H0 zOfD|5gjnbbbZwOtd!&>mqtx^0I2wap2Q|n zs#2N_U8O+{(ysz|bRw;&tDSm#h^So{J(gw=xB=wd5jWv%y6vo~R^&CC$-ra&fWzGb z@*1THL9=qez^LRs`>yDmY0> zYK9YdpxEctXsxY^Pgqu>5Gk*D}2V6gg~u#IJ%dY_#%YCo9{jF%dTNrFMK^J5hE63n)$HxaB1x7 zpL_7rEgYmAaf4Mis0LQc2K8dwiDG>Az!YisfesYQ!qSq#DRhrFa5>sg0GgXxdybGU zGXeQ3=fBI|FfQl}vxS)BFGlE3npo={zv{!=eCR`;76FoRBDBP?Ham^`fn5Be(&HQ` zdt{z#u%&F@6T1@2^q$vo>0|Pj_H86#z*rT%moSdQiq&k2+Z1 z97=lsXGMUfwGBTFTaVw^~`BHAftG_t(hqZ74Cs zwR>k+jbum<_Hu6cZue0a+?mc;34%$_gcD8I_ZqjcQIrExx0}XpDN~LWFWWk7BJ!3) zj|be-ke;m?X|08GgpPeT3pNq(0F0ljycA6iEW*-r{DJ2Wx#!xks-^YUANm8{_uEew z84-`Ax@d(98%~@jl7jGBl`*-Bx3uyWget?(sJHjhk-3iyo_bz$8}_=7GNg0q-9}up zC8PqEn?-!^t4H5-PQ-6PkdI4zM$80CSA?Yj1eOjl5b-A9;5wxy1TCU+XToj$MsU19!QW@f_UrvRW9?j%q5>RsxC zo$}w)Yc4W9pN87fkrVLzVIHMhBT(TPYLX_GgW=m!uE^tSl}OjKKpKJQtG3fR(QIcK ziq@xNa6I{W`*FFwxAaxBjYkiOkHBs5awSyh09twa_EGp~LY7)8ibxap%E`f@3lJxX z!kQ)PBbYU-jTT4Fr`Z|WWa`iF*NDrN%xLh(Mf{6 zuj^mbqHUzr)M%-cj8cPQ`{xJ@YV#k{!2Rm%f-%*+c9nay6wTE6+9ziE;z8@rdu;h!W3_K)QqnQzAj4nS{;Ou^L&s^ zF1j`}67lXZwGbW4Ox$12WkUEL>7Q8F4+Im)rMte=#Rot3k_Z?Q4~agtHJuhC8m@jw z>uf%h!m_Yorid&cjRmKhImm+b)Pk2_*y!}aXfN3Cw_P74Wcp2YmW4P(&DuZUAUBSG zF?FdeyNgJ>9(h&l$eA6|OXlc0Bp|GKT(k@ZwU|s22J|%jP^59#S~v%Ejn&*=6|fQO z6)9{2b@mbU9{N@|hO+|Z{$PwlWGaqoq6is+2N)b9@PywFfNj>$l&&l{fz^0**=}t} zEdP=rGYzT!q`p*qn0+KAgzN7MyWayk8I>vf`p&O-lYa1klN~3YR7hc>Sh$Exhg7Dg zJ!7dOU)>)f)8@ul2u;FnTbvs70luG@hftKX7fflO2i2An0o%AC^UGJrqC&K;?W2N; z%c+HDz+!sx{^*YzA5l6(pHe8dd&NZ5?w~Vu39i3GBkgTFBkru>n=z6)98DM@6WQ3@zGRUF9#JK}$!sqCy%*8l z9WcK)k-Pf@11>vGc|uSPp&RaU>Gkw4g;YH~IbD8w;3o@Q;4Xgi55HbA;Gnuqn!NTd zymd?R&ds8NY76*?;=11YY}T?a-?w8^yNFJvuGAx z7xRZe04~FX8_GXHpFvqvd_%E2{%m)cG$R(6d*S;%xx1(N$sQsf@ow~32XX}+arFTb zg18gCcy;&lR~M&S!WXon^rOa=bAO}^k5rq=G4yJv@Zl2fTu6{knVh`pW-LS-1=a81 z#dyr)noM+~LK0>>KvdJX>R!==8u}~MF5TR5FJSLR>a~WHyUc}us2O9R@ddgJW%hJX z=$>_gdgJ?ig2Zm$_b;w*m~aBFVT${SGIHMzU|)WTj=|*ViML?c#R!0evd^yWxWoy! zFgyVY)NX!ll<>tpd1Zt#3abT%toHtMyPIkaX;BaesE0bf+ABzZ&T| zGP_Of&GqrknY^O@3@~~T*-|JUW#c#CSoQDeF8uQ&-iG4D_i=7q0;gD%wk=_9uQQ{u_-lOPt198#SRU#YVgpZVHdmRo2SgyS7+bR4Dq zpebQExLWQnUyYdDf@9;b(9QCPC##>+DMW*wj|BP6>vM+W$X4e$T7CX`B|}$j0ZjUl za_#wMl$~?VX&cBDEKXd7WnZ5!dS}TR{b$3*Yd;PPzQT$R_3!`voVr@Y1n}Z}m7JtA zaKQc&T(w{(w4*cqHGo&8_UytT<{PyQ?5CWBYVz}t3@2o6Na4bel@aF{etL+S~I7_%p_zXVkpZ+WL<+Is| zyub{WrjgbI9X)NjYr{w8Jo0OgF#PM{y!63%;s->odZXFe2;&Dd7in-6D5HFZy1%=e@Vf(@X!Eys~x(guVZiPI$Or(8^g&EI0jR4;|z zq>dwM%zg>%Pwk*MNyo8F%e1eZL)M-}`!{%Kz|58f8CMby2;}QxZnhd%1grUW7ssEJ?%|7xA5W#F5kVoKEs$?y|b; zYSoQ~A!a0H7Sqt>|20-Niy4D)#C(omkFglQ0T#?hvY&5~K2PK?Kx85Wt-&uIokT+m zz@qMeS_@Zpt224XB2?$w0m56jpxibZc$+nx?yH4r8*(X8KWtCtU130|V2zVqf;+MP zKk>0|R#M-JoU%fgm}>zXweOE>e2@)2|3M|nHJOI96Q07fxS?dgc^xKM85C%!dae2H zsAY~#7;B#Q41~Y7^;vCCKk?z~D@OOp0APWc;n~X()BTr+DQd^vjx@$b%;a7{9)|cg zS6?lfzL=R9&V^R?I*mEJ1crfD0LfqeF_S;6oVDD7cv&loH5sM5Db*^%u7y)_q~?C^ z{x98`#GMuOwc@z>J`2?F@C46Nn;tF)&KCJ;8Mlm|?{9fedFf53y!DeR7Kb6qOS_!w zqWmYuX37TYsJvG0`AE%-nnH8T3WNEM@yUZ?(WT7qJsfRAI$;Y8tmT97a?#D!Ai6*{ zqK+c$?%$&8zz5heF%U07cHdYVbdXrm>vXVpnG!%*lmc@42jB^B8ZV4(ANG_LdVQY` z*iBfo;pR}sRyrW&GK18u$D{4d=q)3DYs!NY**;3_G$X}O!yQM)9{%~^SzXmtX148n zPxDnx;h^}4)0X;LlrNu@5PMpv#6b$I!dzDHV~1vXXK|$_m0SlI_q-9{1LG*{RnLQ7 zre~TRboYyy4WBoJYpx;XE!A2)l^u(-gC|J9i1C0f2X}4 z>c0fr^HdTV0uUVxJO`B+>|`Uv4*jlD{_a|`;7coQTl zvgA>Zdu0~)qup?g*E|n>=|(b-<`LnO_y8Bk4jx7utaI$+yFMD7ek@7QF>fAUa;p zMBi=Se-xnm3rZDEMfe~NayHf)Z~Okl(SL!Siy8)Bp7f~ATbs|)$fXZCS6QfYdAdjR zPt;MRxCq%f&fxK!?XCzM4r*Y-{oki&;E8-LNe6H21%8&aKYaGG#gTsxg~{`jy*ckA z_)gv*~ zA5TF<@--D0mppKzB6x`pzM;~LUwH)WS0G8%nLp^j9qnYx%El7I_zz9*f?Cq3DW#+L zB$D>NobMzHmtLoXvAAbn)VLl(ymiUnhEVuWfN%q(jh9b=Xyz+OwrE{Wbeca% z!4P@Ji(=rLhpp#NibKo7MOWfhU#!6XRem)1{%E}f{Mm_uPH^Mx(N;=00kVFdU?I;{ z;=c}8a28C!{WV>rf^<1HsOI@pYmMMj{eo_~ceX6Bq|0%qAXZghM8`>@usoz%*L}7~ zNR4&BoE(LuO~gXj%FROBU(kJiBAfLH%(9LNYn6T_?lklE|9cqyAV%2V`gWKcmey!= znb<=t=^zy#|48QWG~?YUd&^jb!J~+wqQp?y34!>bQk47mIZo(8KQowb~l zd2)3kIIEr_oK2OykIYPthp`{!PDpjmOtGS#O40jeA>Z>cf#+yKC$K+AxJMkcATWf7 z(4Qr8QWixd?;OG44fJXEGt7H|xMKKi)i2d8lUAKYP5R|Y$#Hn?!_-#ljf0|GzeRQY00eOE(Yokhe+*IyBXou?xX^m$l`Ytie+VxgVW^#xB>te;lyLKit0gbr zP^jzStyrie$R_t3sQnii>(hUcE3?z0lrS->I!ywWb<3L3;%xU6|*P* zvZ&E~=YF5s(R{c!Dd>a?yc{t6i&dm@#;d+u-5@ICJfLl#Rm(!_S5QazyV6d|Fg6A| z04+bf!6yM1Xno9lqwTz{@3Oi9+tZI)n6ymrkikWg=BTJLn%h!+JY+X6Uvj#mqVVMuIZvPujO|)I}zs(nvL5DlP|RD-R(HKWa`GVe+LsRi*kvixp|WpHn= zekWDapYbj*#C*2S2ru2c*(O@_?_`+NaeFz*@?;Op@0Y08VSPL>>s}k-*P^$8#qksV zC3drvpfun9#q{S1?~91B5f3dNq)D?G`y>iJxsj9%ROaBD_c~);mcxy2e)%cpWlLEq zp{3n5l-_5lgSi|6oU*h5+`k=B{A=@^^O(2XZ+%u0M4Nj*{|G$M_)J~CX?wc9OW{Iw zzSx^;5hz5$ds19guM;Ld5b*q9Q9a56?IMVT*FGS<-`ICk99aQFTta`EX2@hy8G?(` z-`gogHXlWxJ#G^~-~_7AXq0-cP}u7Wo*uWE4^JjInf4!zAj@0DF54pmahFad9*M#?^a*oxK6eC^?sRd|O9KKKQnVgVC6KC*My?75>t{c63YcB*ac9q73b zetFR>e3_LWu+mS&1O^z&Ly8G7TYW;7+}&=zVHT6VnJ=GBaH}cJok2St$(EOr+XDxC zNNm`^V`fcQvpPTY0RuXyw;G#M`M?k?4aRnun3H01+C|17Qzu#ICshmdC}7Jh*Tv9>EsRUL+*I{Y?sHVZ}4K zTpY>u?-=7MP)OwF`lCPBigKkNA%Ca?26(F(k^ep3Le2g|4BngS#e~8Jfu6%fyQ3R( z8&l~&JrIxeJD2b8thdqw$7!-rCWM3A>HE)tYA=0i6a0nhF*cnJVlJbuh!eXzdChSZ z$f|e~t@Rgo7Hgz!etl#T2iErZYvz_$IXBKp+v1$^KAiNn3huF3C@?}W@g|rm25;Vp z;!~=oFubz~HhVK@nV26qoH1`?U9@f*}=mcov(`cLS?$5PLm zGy@Xm7S@v1&wei|fBj6tkN(`3cR7Pq_CGa)WmSs}}`eFu#d88BFj16LgysYX#>xqPE5UiB^N=t=~c~L_b_DKVvwI6E4O}Fpt{e){aX)mx%vdr^3PtDymnM9tzGi9+FXtV^w~=mN1R{p; zo_CZ_uE5*F!K*%OcZ zHv(}<#gb2rK*uXBwi80X`rj8JbO*ljN09*j^B=7`0c!QBttX4z!g%FOnU>61!^ilg zjQ_lo5P<&sAbI=Z1Q3lR2=5nN>ivLy9;{jaV1WS*NX!ifGwiq+hfZ0g`uKud2!Z(z zxo|@!TrH~B4GoNF!d*mXQwJC_JSf{})B*4qAr;s~uWG9PDF&j%;wTb}E#f2m1hCeJ z;Oy1e^QYwiPe95AsN>Cy2Vai6%cL3PYd2(nMb+8wq~ zf;xMzj~rfjni1A4L7yg(?SV{FdMgasmm?Dw-mspz00Nr28PYT(qrdNpI&0`(IX1Zf zkboGI3y#@H+Vq} zu^)5c3=9ezV|JLu0ee`(Oh|Oi%qx_kjfqxRiXZ&T${)i@@R}0F0NSZ911|h35JxD# zpocQ{ub@nlFE*}=qN!H`3TrjY{8|h`^oC`LCmNf*&+reGc925tb5}e3RnfOYE0x~j z3;EFCdE{NoOHrx>SOiAcg`HyhWJ zubDZw;|HPmytUgT4?pAQHNq(AYz*6XL$fFkDOha0_InQ(Kf#1+HfAiDLnoTDnPp&b zFPCBMZ%ib7_I1VwW2PKDr?_}vnE&42H@!H+fyvshe(}Mk)77ID{H-I47=3lpHH9@JF)RpxZ6~=$ zUV`6UK8*Y>1i^rdAvnc}L&GjMKiH-n)B6*gspUxGES1D?YRHmF#qPFL z*IL%Xnxlt4l?S-kc3nwkE-FLA`6;=cuyO_Kx#R4sze3WT@3sGXc+Z*fk=Pp#@}bg_ z{#UA1dmBAz>#XK)_|cZuM-9ruplwTC5yDav2kfa1E?ulvg!IoV31B*u8&M37f@1Jcm2Ew;L}hrY+2eCLOdSfPT~8 z<3KlUKWG}Qn|OZI9hNIY5AwPNybv-XcY=I9E>oWFb$0|8Ycg%+%87Z{ckO+kMOUk`*O+i-1?1cCvTw!f za1nqn*LB(A!(ZrZ$@DbPwd)^vdrM98otHVgoy!b=Kc!7Fd@q9nrfNNE;tsFbdD&D& zyB?3&{|*ywD?>)#<0Cl0ukbNo(a_WGkSSAKbF*8+3^XM<;z?eFc}fh#O(kjch?<+gCD$`oFvvD?1Yl z54^r33In?j_*HET_Bk5^?{NA&Q=UO*z)b1d-ueKsw;Q?7U;ca`v6Jyde^y1Z66b?# z3VqOm5F6}vT=Sc^jMd-T*czvt&9o$7B-wCBar6G#BqUv$Lc)dPBoV0bsGX8RROpPO zOWXEmvOQBiQ~PcG#KG6+bbZvINe)h#<@x!mf4!v2G|7EAD`riMnA);@*ago0)5TP^ z1H4HWRY-xlq=?wa4N=iFeD;EyT6QR7aw#x_V-!X?z3{z*wxKmHI-y>{w?m-#Zjz3e zn28cw;58#^G#`olmi^H+=T%nucGB8(s+CZ=jXbssx$Yy{#>Z4ZRa3?xpg^FeV#=gE zNQwT>$iF#aUJt5=Ixon3Lyuv*dO%vhuWe{A)(m<|*7VZ&S;R3)?KnD;p(#Z8M;X85 z-6r0=e6%RO9$;{M`1Ymw>hHhXF9m4V@NWNyNUJ)=>_|c9CJ{O-c1`dDT-W%XT*HDH+@Fz06uh<+9topN$eHi|ff$$Ub!$ywkl1weQo1<>f4ej$%}wQ8K+jS zQXF;e0heTTJ`DpQHY!C>_RcPBr6Wh+~TS&`C zYB!-wnd#E*yx10p)G3?SaewA9vLT`utoqJ6Dcwf+ADb*u34)M$l756dOW>SloPO0r zb5pI2G`{cHjBH8A04p7)2 ze4OTK=RLaFQa5ETKvQnB0&oAj;nG!j>TdJvAUKB@VUgyE_WQg+b7I}4lj?FA53{BQ zEMnfn7$?Q2RGUrfLBks*zwsIdOt3N`EOQ*Bs7XixzZ^?Gl>Z+4GZ11MnssIV?{aWy z_nj=M%93?Q$0aV?Q*bN$zFqNZ;Fz4Z+&CL4WS0`(Imr72zKLHF+dJUafhtgkaoo#v z>+3%%m>oJo(L^+W?*Do3Uojkz?0jlaPOBMKI|p}t<+cn8owh@xb8lqh>4LwBc&oaV zPFzLGb@{9CLDp!b4z$i6rormIe<$zn?#;kcvxYb&1Qq~({0Li5Wo}6%;otj*ix?aI zOFj%PaGqrHxCZ1>XZz9N4s5CJ#3@qx6`+Lj0*CX>Uk-YcFn6Pm)K!3o@8Y_8*#71y z0%RgON^op*d_#CjQ|;r`8=FJp-gQxQqz-1HZ#uaA!1lwncOk#p!38jIGyRa;|GY9t z2{UjYq&z$Aow~(lx~`9P!(>LD&LAhh1jmU3ryjx#uZ7zjXSCzCK3|4Ip@8j2nF#Lk zAvyQIn zHhjFcKa+H5O=G9t^$ui{yZ7�+7tSwE!fhATkCci&OxCU|7RqZqSV&>(n5_6-z0s zncZ_YV?JE=dD~QZWEjt@6gHUJ?Dq23Gv`;Hrv?RO{4wv^WN~T;an9RIGsw4$OAfuf zrF|^WYhX$*e;P!_CD3=ahas`*Wf; zg5E#!{W^T0p_IJ3(VY5_pe64>AFd-Kw0V30WY12Z{pO2K%3u8!hlmI@8!7p1`BYBE zmxKDlz9^Dc+}L51~Qall)?J<9Sd!tXt*EZR_PSAu?44AfKL3y& zOtDG%TPtJk+O&AwH{_ml4RDphJzm~@k;)072`!&8_9^SV{TO*_{71pxL)F^5d1Q@r zs^Vu5N@j;+*FvrZ%YsX=q@xdN`vYL&^V-4s>`yxuwwXkU4Z!lxMDl2=k4$cUZDYzL&(p zUL8Jvck}go=n`U!T^Gk58AtuFDcTbq;7WCHW}jp;=!B}?yUN#;b$Z!9Pw4M zA&vhy82+x=@7QmwyL9iIA?g8#ic7OS+byIdeLIy9XY=Ojy**R zrSx%s6r2NHY7$i$erxS`k*1@aAFXqp)`Ec0ubXaZj#v`5x~!Xpl7$IWtxk`sL9e@! zyOK~w#xUy=vV8J2&eE1U*@rGO$jS_gS>KC{{#oIRbZg-8;>C6+4~TeO3?u-aiQCV z;-$WCmL7?5(YG{z;DK1x#6>&8Ft0L&d%T_3+xZ$l_#woKqVxH>=IMkN zB9hxabwR7;FlhUw`I%FHHf{TUW?Sn2q{F4(KtOBNCt(UB7h?P$#2-V`Ul9|dJcJDN z90F!JDYkX~4M9(M^#%BPbQR6W*v0YtOYSTvAo;2bWU>}CPL-I8;y61u(a0#?hj8SV z%jv#df9Pc7aB9@Str`b~hGYFrpA~Q7tSPQTd3fj1@!VH@=4qQf8|Yu>CSLUfk||?q zqjluxWU#a+BWO~A9!9OJ|Fqp+4G;$uT7ukV@;{d!T(N`F|Wiwm`(p_aTQ7a9Svp<(C{*2rj$o{Z4s^CAYQOd!MEg)p~Yr0ddwC2;A*0`pz;Z+^!?t2PTL* z&v?wOQYx5##16>27;I*p`<|t^9srvrl9Cj9ihZSn+j&q zt&6Mr?H=G8V)OK_h~?)ZDiG46acHUHP<%0MP5hL@@tWDBxewpdF?d(5SyRs z@%G#e5pfQp3;1eR*)Is?N?77TU2OLca?EG;^l`_TKQGZ~H#;i!So^!vUUiXFRN6a! zF|quL8W+`fTBd8>EbmbO^PI(c!Q2eWZm8@}aJ7D;X~|EMhFE~Wo*5RK8!4Ib+O8^b z6f&P{zyv+GjUkBH-6W8nP9k8kcJYa4!+8!?(!sn^#{Hs7iPH^jNQY=RqP8Ik4xOj= zH5(KmW7u4F{;h>`s)LIwIoOeIqkhw=VsB}#g1DHWf83ss466Ff2}r_|egCol@WnLv zo$=hC&uPnTL|@;ce%|mair^OPg#uzpp{_^{U#hWp7Sx^j4kz-C3Y<{S zlfkC%dDQ;O>jzpZXUNIdpdQZ6Xvh|k=2XVOqBBH!7=YGu*;FMp)xR)ezl||Yt-qzZ z?ady4NIdBO2R2Y_OVIa#S?X=93;tMhIGO6Mz}wb(4PsL%&c(z~V09h5ho67UdFK2` z+W4ejcFOuvYJx~z{fP_g6}!}!N1A~^Tet5sHm8)tpEn_?a9~OA4Y^u{?s*>nX>NKg z?~wM!$d-7@zHP3%yfNKk>J6DE8a=9W^r+4#TnC> zp5amdSe48FA+%0#h+7LjL0JukwdlF@ZzlH{F51rG)t~Z(dnzHJrp6EQ7e?`#t1nKX zag~aVu`wFS6oWs_o|TTNvYE5Z^$qsVgt@Y}o_($wjXM}&0pEPHlYUZ4Mh6~y{AJlU zw_l@U+M#w&TA^{rc>knsEPD)dMm?IP;(#h(&-Yyss&=W#C_e4JH+TO-2RHhM1D~lD zRDQ|u?Tn4w$(;B1*;!o@%h4_CEs@!K`_G!=Pw>SuX;^pqK4rmOp`w41<)X*A5D@IN z1A$*EbRq}o>(dFJ;2>7)v{Ub^_sH7dzGV1x(-QBQ!NyReTGYU?wX8ve)4-(#{4&_)z>!^U zze@CFLBJqtavKlM4|&VyD&G6<-U9}P(ZFvUn0dah;?~ggrUSA^Z17L?~HQ6Vz5yvw=KWp^hBKfcS6y-^iZ}DM|U`gCA}J{$_adV5AoMH_i56?K9o=9^d#`or(FZhN6~knkB3zfG`S-8DiJ3``w}ya z)gltZqRx}>jYp}WAbUGPf_POXAuR9~#jLU+ABD@qwe2yPL97yd&^nv@+Wg+{J(Pk@ z+w$r%S?4FrUXDe1exUG9;h+#2{vbVp7N2zB+n zAJ#hdqQlBs3c%R3Gyl^BU(tICU%w2zI(@F!T2ap6h<$V>S%I2IQ)Ge0d(Kbx8+K9! zR?5_2d|p6J+V}pj?~nG#Y5}^Ejz0$SRgWIT{JQVDK78dmLa$Cj*^R;&?czM>!-@)) zIa^`C5fPoC((`(ZqLd*P_f#R3{pe*`WK$voQIs?=FtqX)=aN+oYB|b+#xsUR{mr7T z5p68f*LFuza>utQL>+Jwgb$X0#zNA{_tS*BV|`QEF7$E4iV=^64npnCHsJo z-WM2MlIlCzN|qJ<6DN5U6VkIZs(@er6#IFnrxlog*02mZP`i4(CFm!-p>eqdgV`4{OwsOzza+V z&OY7B5Z!R1?sdVTjWWw%hnA=U^N&K^RKyPp>(B1@ZiJFvmb^fd$|>br+$nCl(+|i+ zoy`*i#>6dfO9z{G6-;`hllMV^rBeQAqSbA6*M)ah`(XoG{8ca?2|eQZ4stjX*x2Ph zRi1b*l4?nZlj@-roi_tPe=T!=W3TfEo0r^hl3*(F?S`y&vO{ks>Co{{Ao02NKLe}z zmA`tf##>7%8i)(8#rV$WBytX;JbeSP*&+{Qs1%b? zOO?Pzkq>g|sF?B+G7SA+naukS4?l2wW%V#GnZLoU@QB0QJ(bxWQ^HT&3ft_E#3@i| zzwl{TX^WT;fS#4InDMr)7>FvfR7fbmtlV+tlnaIU5H4*npzG}3UA4aX9?RcE8JY^7 z&8^=x_5{~H!P4j;Bff7FnY$g#KbKN?+I!r-n16HM`dg(D7vji)*DOSg40X*DsMf)^ zYNNlDs3Cj+F7=G@z3OLSTRH5T`F3I$aD1Md9q~cX+B3u~{g1L^@|w#%DHuF=pJ32 z8NC8B=B5lXRGM&pi89`OBhR2`ZmO%iePYD9h0hnh)&DZhQ2(+3HBQPt90??8fi}622YL5?whFQa%N86 zFZp@SY1VNm;o(QVktcDLpYKZ7$(}gDUcBqt??PJF8pr#)QNz*x>{5*WSWl15u|Y~r zH#pW@1|={yK;nB2h$!p=TeHvYk!IGWTQ{|M+CbFHYR`Sm$I@^aDMCDO@17hTwlJJ@ zf!sxdvR~5bmNKYq4k#`b+!r84R(5@!2Qw3C1X*edr@Jc=L6`k13}yJ8^oM&*hcWTfcQ&+T6S&UA|uy7{#f)dt-HH) zRh34XYGO1UrcN=KYX8gg?SJ?w=2P=@(@EycNTmLPDNae`a0ViSILveb?4F1;GP)WN5m-xf@0ut^L)DS{a^$k8`?>k z(m}KK9NK&N6x#`SZ$mB?vHTTeR~= zWK7(oXg5VXN=)(zfa<#Z0SgNum3lu=(cF91VZ{GGWPRp>HS460x9XbkpG%X9syQtG zCEuh_n;l*!pCPte=Hg@{Gqv5b2wN*iQ;Vp@y!I$m$2O>g(O zpl7!z<89L+^pIQFm}8MccPn%y`$>qV$ zPee^&Omwi3lH|8d4);+*p1;Bw&WlbmH}8LgBME~c09oQGSCBtv0uF~|ch(%8il)_w ziATaBtfsRvj}C`KYah_JJp2i|HtTG#F+~?868*{DlC${H9{i&pR6W#fTAPUOEqJQ3o^^3c^A^B#0s|3(mUc9FjA9 zb8{3AHtJPj;aD{Bb9%wZWj^Y&sWG|x?nB}zaDS7X$T_|?`zbEK92bieT%8F`Px%slssCkYGY3uPn%Je3yugprAgw8WcY2GLkK(t!5BkH5J4l zf0dqX5OftckTMb*vfSqMag}R17r*f1e>9! zBRgd!=UAbvQ1;G9L^87uMc&B{*+ll<`+R+V`2G|3eZ8*h`FuQhfC4!&wg=t3TH?N| z>aAibdGAZdZ5Fl0+krJUtTNV6J z$&MNqpvWaaZ%*wkv`&w%w(+nVLrs$}`4K^^BD z!=EfgGQ$!Cj!&pfVa+nz`+Mg6D2cJl+xQ3=wsu6tw%@d(V#D>!P8cq$k*fD-Ik5j9Aq}_& zIRdN>2(GazLurs%;C|yd>?&OhkASa+Yir`hOX9J=q9uWu$z!IszmYdQZ1;{+y(53x zICkic)%EGDdymRlqRiC_zigWB zGpEN&LS{an4a`yI?;+Tl#6He&oG<9K7LIcpFFPUfC!0 zAGi<-eh-Q3qOA1y4jnyN7iTz@UP2+k?>Pgh@7P|$x-02*Rlf{u(od+hmCE$Xjbzh* z<0TEtDIw?&lmgxhj>C|y?+;;?WQa_efzel_a>y@A`=uff3P<5L*nVEf-T+tKf_DUE zE*~RWfypT{=>AsLtn7UJN~dQVraZtuN5Er*KJwnZ zQo*2X41cUduz$Y$^u|(r*3I?hlVg=U%ojztRyEkg1peGXRqS#OnIz4Gf;{aEf0jTv z!~J4%s4C$V)!UL8ei)SjxV9O^IJ}ZBtL81EP7tEG^pr-tAo&a5HwmNTo|Sb$gK2)o z%Zd2luTpks*Bs`T3)zW6INoIl(F{ar5i;Z7F?=GcZ=acaFyvfnWR%#<%xp0s(GR>m z-=*F^m$K38#~)M6M)R(=Hm!?J?n$do-w?8DS!D@4v{9yp%{C50`Nw!vBW;1yhXVVm zp1ht$cT}Enai5mEn8}9kS)XaegsBBx+E2<@QWd&Nza^7ZShxif&EOqww4;a+Ir3)P zjU~sb1(3YQC1+}A{|Iw+{l8PunotJfiG-%#pJLt-GLpkG?YHEI&TUe5gu*tH8uXc- zA(v5gw~27?hC-852+_Zg(7?HhA(Bc7&v`o`DTt4l^G*7ldtE{C3gY`$k=H1fvEC+qS=b)^aP2dF zf1)aIOUudPOMFtC!nMDV^xQed@Lyx=%LHpP9r-dpEKJ@Jh^{YuD(e-64hEy_8W?i> zcCTYR{A@+c_;kE{^#>W!cs@v1QvdeUq7nB3gwfnFkY1(FGxmI%PIc#5$R6peY)L5yi71DkyGsQi@5;jsoE-Mae>*6 zV13D8DnnNGelvFa*UP`tQWTDQw9`t_KylouubJ(dcOb5s+LRGmpvR!i_Pvo zs1aHqDN0SLB@)dcOxB)kMhB*NWQuC$GKd|N@pHc&@2681=Z#xEH#q9nS`oSVIDzqLviLs3zw%M#rW<(=Gc>27v>v`3kBex$ zRmw=tsEZDAOmKthd9i*1)$M@iVoi?gEoa=HA17oTEs*<^=k*AJ<`j=M2j*}8JQlq_ z5Kx!i51;~XA3k2v8~-DxRt@rh!6?Ydu^#G6F(17{yR|6n$qNVC2oh=Q75Tx%Z3@&E zU;-1LYhAnxn;`N~!)bsU5Ro&_VF)t%roND#Ks$K#cM+kB4P5$#xB6WN2Oh|nva-kS z7%OqAJD9Bg%f`udy<*rSg=*4g`6E^Ka!1#-ewQ~s62M+JMf|&I{DKfxzs>%!0nTZN zb$a2u|5vAhjOZzeS!I%l6!@JssCZJk6n*Z+9Dy$AEm2*ZI6rM-_PNU}nhDGNPK#|Q ze|ScN+7wMv$hw0+p>wdrAED=-<%Z4({nXUziQXH!E(+^v;#lw=b&Ad?7G#?=wvm$c#yJ^kz# zM4XA7zqFxGQ!lO_W^fF337`u-U`T`&BC# zHs>mwlP~gqL*OoN4_wh@@V(lWQ_RtdxZ#(bzzyf!q{Dz(s-TR3egk z?FQ@bT)@0Fh5yo`=G#qr8i~4PdmTRRFW)+#6PCTa#kFzb5916(zK$bdszmKtKFp=! zFNDeh2$~}_|2E9#uUlkdZtvR&hHta0{J3pFUjG*bBGqG;5t!D6%y&Xs=vgi;F-m(;<<2^>bKxro(%do_h3X0o zIB%?gV3n>M>A|1hn%Pek(y!f8B{7kF@pwK@Y zPS6z1Z~S~$EylF$a?N9#DJFQC1B+K+x;P*Re|jaW9EyI^Kq6jQ_O;(A$E=tI<~4{3 zmURvFiHrKw_j6}Cu#E^coX1AxcGw_f+h%!W^sh-aD#sWykuVI&hJd4!Qbq#y&U_CtWm=rJW?B+RoQ$uxKFO=i+LcoR5;VG?Eeu@yDYo{`4v%uS z`4X7H-Y#W@F8|i9!tULy#`#hR`|co4F0fo@_n`o`QD?}{i*Z~=!z7^FwJ|+SpUj*E9ocJw7Nr?=RSvp8Vqw4Eu-C&Ywdd-Zjgu-HtbVv z&i+dtY-u9v6^FYbv07C?mirl&^n-T2Z-46(!sG6a4ym}#h(1qANUHSN6ABlC5JjQWL_xT zU?LRMPG2g0wKnpaS3*RLf*Hf(B{HCe<#qHmT_8-ZK($F~Jd*s^uqmVS!zEi4w!Q2G zw6*9_qouxXqWJRIJNd99~h{ zWOQEj&UHl~f59$TVrJwk`x$=ft>>8y!4O7VSK%Hs9MD}e2{?Bw)?UkORnLpi{#Vno zj!bi@d2Mza>wD(Nh^8P(SR(r6z4c~z#`qR5=z=qjpH^MRwxEMQIgPz*B1$dicy}Ee zNLP0vqARkfa_`3;w76CNx}|9E^Qc~pYqWf0((z#Ro0UBpX5)T6+=$f-nDTz{qgMZ%94 zD;`b>&=axjd9Z!Vp*c%3{>9+r5m};j$M@X55%z9UR_V^58QYv*-A?RNp~*v+;9D zh??^O0dm8Jg#_I>S|^s|NlW1ry$UuoVvJH2i;Tzn4Ve=o(fc3qYsYYz%cA3q&#ikYCsR^AwM)e;%{BzWjuDY5@__JCLgO+u%A}^s5!!q3ij;JoIr0(C8#yl|d z|5<=n0W$Y2OP-GP`ItXvlp^!&g;#o;pv2CK1-(3E>5PMW6o^9UzH{ZiY69F6xDg2e z7i!QR{x3ZqUnG>hnhIYg2npG56`X*weJj9f*Kpn9J7WE7DOF@{cJVk}d#_hX*wW7K z-AqQ&Hw5c0Df_{Ehm-(|V*Gt}zcR8*L<6LEH?R>DPxx(Eu%(fe3IZ~-AGV*@b8DI|5iMXpJ|2&|MV{+qx(XW*^IeahE(?AC&4dIy z&3mz?%AMc>@1n&;($9o`jj#`+F6}9-1B=i)KbC1DdV)2f&@);PA-N$!jVM9rqM7L_$LmLHDB1t z1h%xb9?8no`;~FP*V>P4;yZQ-Gi7bE1>DNa-gy{3Fw0d?srjRb!x5tipMVkkH_`z- zMI3C|bDtLGtTbZIN0kbAeZg1ti`TV$a+(@iM;GLyEX{ey)LLh(Q@PBhHD7;JrP-=v z>37d8b2y5rQ7|476~V^xWYMB~Jq#?18<`lcW=U^Nf z_oeQWUBJj}p-vRDFEA$B#xs5Z8afwW)cyC`XoMFjV*c6}9USzH?jCqO#B5bbojfua z6=EayV(?={dd+)I6C|?e#iqvP7911@-vGFLJi`Ng-Xi6L>gwR3)e`?mVplwBx%d^s zY82(4AQr-XclXrLOoFama+ERg_Gxkg+$6NBFP%{N(zG|RXjdKVm$+NzoziGXwG(0E z&tsiBbP&0T%HWyo<`?fNP574m8J#S2`@~VrE2{?o`cB9*=D)L^Ua*%3#nldx8l}72 zV+-nWf|W4zWS{gdJVE5zu+8O=nY)UzB}Vk`%!emj`YsZGlzrGk3W>~2^XRDnNq3PX zZqVD&M2aW&ugAYv6W!OHMp0?#uP5Xi;BAy5W!hrt{IcYKh3*`)MB&pjUf&na{-(j6 zV)w7ze;TZG(LP-lv+(?>@#Ei{`+uMCp8Zrz|I#a)7fWPtyr$%~;*51wv3qu3V*bIM zj#g2jwQK0isNUFttv&P3vm0#w&q_Vsx3GU7^C-jF6J}mEpQx~T@SI`)GXl{BvjoNF zBe}QojN^VtuQDwX+7lwN(@FOf)uOobi(y!)~=vq=nfZdAd+Bg*V9<& zmmsbI){2(Tor4%3x+SFkv$m`d7GE&OuvAd4zuSm#OAdMA0`Db!Uo#So1aYlzWOKGHsZLy+Z@^6S)6_2YI(O!{ifY}z&#l)oi1tNzg-Vq=w zrBE!JK#{6{I|_`zDj8K(`57O5PnVP8GBgYY`;LEsbTzDAuNKnE?|TyvO}{8UZPVG= zJxvSFzD-Qnc&3on4nr{977Yuyoo}(_|C^rN>`#o>QtTBHCHln2>sN?c-($?y^iP)P z2>KYtd3XAs6%`UJXpK=)Gg1@*JHLm!z-r5e7(D=gI3G;`BPG=IxZaj+`I0T8vHJcd z-4VS@(?mEZdg1-_zZ|Qx*q4df8UqGWRA5`okSIY69<>lcK%Lzih7XLVO7!Nm6Ugp8 zufOy4N}X3?LWV2aP%Zm6e4`Op;Bb}p1_q>=@uMAneSXWp!}uK$cFBp8Na`b{X4maa|9mzaqGc@O#xU#ADqhUciS3ntXez2!4 zKzhaQpOw7+rN%ypFYB9b;!D$QSl`Y097X?7vwwOHcQ)Cb0FOL&yrSwt^Z89qcEn^S zR1;lq@dMC)#(j{T;stR34V3`G+=;`z->t8p!@QHe&L+%w9&DRJ``xLjQw%2ksV`Nq zBZZvTC1G*;ZRpe2z%T4OtR}?xe}o4?Zwuis3tNdbFZ*wz8x}a)*toLo>ZfF2s zXk#5najTvzH|-qXe9;QPV(e6X??%DAtrB%cL6epBThs055)MZLpz+#rIq7xOHRGNq zr_>hAm6w!<|IDL|NbbE=s7z_+uCa|+V0UR|*z9*IK6Wr6cLD5oIOV%9JQ0f@-*0e}{kRupAP^p#QQ<0vYJ7&d4=3&4 z!Iv&nwU)lF$K2;jAu1%ytu1Pq+^f^Sb<83X=b8S#G*7sMT|a-z_9pNs5&E7QLu4q| zl|w@?u^;<~4K)a#5^npfRVH{_UL$RWhw1$H&vO+KO5EFapCPhu?w!||nQDoWRO}rh z5WAJ6GN^IBE;sZ?-b@fyDAquh=&O5MPZ0c`?B9<%UzGYYU|U-V^CSJyl)-GBE?5dQ1wYu6 z%}4#<#J^6O6{2TX7`%U-0O?5XrN}%rYrk{V6c&b2t%ULZMgC}8|CiG1AV>Y&_Q_cs zCRoCWTK5X2U*DvnbQ^&!$IpmSqC!x_dpcj2X(eq`{i*^;L(-Z0-bYgN*BF(F)X%@| zdq$q=%pGb`xmOV$VWa5xK=18qvTK2DBRBqx=!xsr#SP@{_d z<|GYtW4Pp(|HE&}vZz9*G>GkWBLXCYnaa9-{WJc`n_it#WrO|oEUIU;^3VLnrJZTUjkV5#D=<+X7#TpA8z0Pi9KZe*|ig?M^zrN{7GcI=DyW&PF_A`;=v4%4J64|cI0 zvHJoUf=9XU`zOA%`%$6h=6JIY0=U_eB&6+#j>+`+hA}BS(xK!ge+d)Jc~WOqUg$dO z|9C~6??RcjRrh=s^-;*C{@c!Fn9T5;iskV~$~Sudeny*BL_yW>33B87T*L6+mn#Jj zk7kR=ENaSQq4AkU%Z~Ed$u^14R{`KwE=6M6vNMl0K?UG&KL0EzYgWE9UgkN|RW27Q!&EfqT_F=a`JqLfE*Lhq4SmGADgbO9UT3%xha-QAVkA4<Q@`udS7J5lpUz=JEVO4q>4aaCn&k)@b87-K{;)zy|!Mr+s}7?k;I{r zc5P{XILhPP=}Jk@t!Nzvzumaa4qoNn^fyFbVo~ysGYn`unc-;V&0wpx7dTJ!oPNev zElBjFV!L5V=QF7T1$}Kub0RlT{A}tO((nx6c1N4;XNxX4jeI|?8(@?%J*n8-Bgqfh z-O%{BOp=w%1BV3zVq0*&G!jd9i+HjnD4==|KP0gOa%{y=Z@Jz=h=dst}v>*&*i@18o0G5@yUBiuSQ7Y_Ge?T zLE{G8ikY(H1q^zqzJ=#ZNX|wgbRxO_l8|Vgl9XVZjDqM(7IrJrTMQeZO1QCH`yenV0JhcRltC~%{%G?7(ZQXw7|16< zv}a?Til+YWn{N-pUG}1?HPqJNZV>MT*`Gr0w=RPW^TM;z*V$(880gX-e|@osIbLY> z91@@5=Q&_y#m>g;_6ofE489B0shlvNu;(Wa#qEF7wk+UWd?d*@W9+k^;~8Nl~>Y`vP%WpP$WhM9>p ztbkuFWDEURR$Y*EK^D})I9yYEqKResvE9FYKb#vR2cjTZ47fp-l84r;JX3QRBI#Qm>GE*9pMV9m>8jM5}M4>}fd zWPd)6#c7?nR$(YV{+V4eUIkw>ACk)iFCE!`hLMY`zd(_c9rhbT1kXeXI&N?ZKf$MW z5!HLY-8I=hQ)nTSO(pQMyKGHV9NQs>7=Au$+rl=l+R+2dnVk$9-wg0q%9ik_Qb^_P zhF1;*>1~NK28IC9W-x#LnzgNkrVPFhZL5OIi>Hd7xSh+aFrbOQ^%1m#5QNv!rPvKS z(+tI^%A%u}PJ@5J4 zp7Vj-?We?@gACt{Uy*ZEMw8;jcGGb6g=#@WrMG z)@$EWEQ)PWi_Ui5RZuFW3+VY=b9cui_-!a}r=Dxjcekg!ck`uhJSRSfq2UB45;dwdl1oG5MUP6S?MHOxX_CvY*m2ZxWF?GBwFitUz^q^j zv)>Q!|ApHS6S~FaLvkf58i!xP0Y`~YV1}_5V)@XHDwLXv8%XMXZ+8p7m4NRsTWKzU z8F6z;!`ch)$r5(+>GqAqq-gP7Gvr7E3)I+NhG3_gWI1ahTu|BLSK;O(0+AM0kE10b z_$(3{TnRU-bJ#I?EV)YKwPK!~$L!y>^_uGmQT;%f`%z@kb2?!lo4E8@^xYEOKD&N5$Z|FRxJM1j+ zOU!t_O6PjDKoOa9YOQdfXpLsaW1o`Q(cunXJ891I)j6(vLpR6ZpFSRahw1%#D?)ev zI?)Fcs&E}$mp|BNWiGA_gMKbeP*sF2mtXDVv&)Ep6li6~C?z4b`EEeJD1G6gt!^_z z=;f5HT@48tO3S>7&r|WIud%G8!|)SuM@~t(ufp#OEB$n~0w^vGYJ<^gfiNi0MWibz z45864!?uT?Q%2>7%ll4!m|JXBB#qL9B#GguM5xpP*S$mq!{hnR_(p zZJOJqf@=;#k$WPpxFpB&8iOFi42iBeGHH3vM9t3(d91J+MFeCgO)Av0;R^Sj5K)W# z%Ev}XV5MN;inH$Rru8s1@{4BcBI+5_Pdj(kALkFyc`tAy_wbc>Q5(lALe7@bfE_M+ z|Ni|$vKz58GJMR|_Xp0b&~d^Pu zCFh18ezQH~rqg!OudnJNkKT-JR+1$ps`l)hRg)`Vv~^mFNb^7pVb^~zK1-MDyx17L z0(;MKqMDegXustGPNA>F-Z&_u4Llx1l4gk zRmFa>&U_u}eV7`2JGk_&#%Yb~{AQ_!&yY$g*KaC{?1H-1+2G zW}LH|T<$JC>=~J?7S^G%kNs_;p;>q+ZggwIT@r>)nM+$91a0E$eGJp8_ z81EMdIs$=ZlS&EP47!vL8{VjLE;KwSz`rOs#aOp7YAujD_~SpH08cq`F(_f(Q;-We zw8^dIY15)WgMJMel8EL$qWmK`kBK2bR=qRIkyGE-?L&K`z$LR~3}3J&H7bd5r^8O< zUMu&#@j@M=^`!OO{ha)c&~+qphGvZHQ#6olWc_|M!ey&-^%w4ZZuQ>XvQ4zvlcT^z zAAyI-HjHDJ+1DrvD+yL%)Y~Sw%M3Kpuirt<33?h0WYJXLjCbsd7c0r4hA|Hj@O|-y zx%S{S#Y5~=^HNCrg;D`T=meJS(L`QnOF0>l)n4FbL+^T$+p}<3r7)W3UG2eiB`IvF z?uKG5;WmZ7zro@kTejYZ!_#Fej9&PjycLaJmRFJlv?iY%B_+hh<$+~vNT@sUcOVfz z$TI%^xN1wl__5QajKUA|dxslAiMj(427PV?X)j@E$@_ew7AH|*o_U|X0Nsx1{&l|H zB8ADP6&2-t+nMzQE7#}~Y~Q5vq!)`Ce0zD8KwwwXEGC!2vr`bY8mrG9Og$*0a5LD) znE?H``??5uwX*UEB^au-RT&Az`m%e-=#1ejl`)$XOf8rUh~RH=;eW;V8T^wAlLu{& zchA?vu0X8R=reqAu@i!Ed1yR5lBQ z(-D1(R*w)tWVfw255~}(8G_O=ceAu9yI0l86;sIzXyyo+Z^TgH@AwYE< zG;a&aEOl+_Nt-GnR($C0p-&i4DapW2h0WB4HdT9zK7$w#$B zO*RuI?>UEGJNl|(4XN>QMYco1r9Ry|?2)H>lut~p6%UEBk_Ixql6)iT24Su?Ff>Wt zUCm`|J_D0#(`PTW(g6AqdKjHP&h|CpMicaj<0i2?ni#y3l_>JA;3+JuQEAuCgwr&u z=S-U1PGZAy%itsVzEAkwgwO!z%X{I++E9t94&+@Ttz0M773W2DHWap9Rqyb{8x~I| zG<=sOqe}1A_}W4mmf6R=?YVb$0PWmz-UF!Ic(SKipNH*p>7}M`T3|`<(g~*r%25C_0TsZ!UeYs z7~X?Yr#mfM;_fJpl*<>tXyYsh&$isRE&q5Sux9l6+=h|lxD;o72WG4-dr8@&1Scs< z*^K_fnc7!)UMJ#=1#-l!3$_+6Ypm4s-2@}hLr5<%eA}v0o@=)JDPQoX?OxX`CbV!} z`{o9jV&UkmbB-U~HqMYkPD^;m3`=oj8@}B92~5ZVjL^+|7$wdMQ36z=*bQGnJlV>u z6t~<>N|8+OJI&6~#`8lJQ0F(xA+^Fj$><@e!<PM zRI#PJgsw7-dOe}DN+fz!QeS>_^k$r=$B)jrWlhLKKAMB&t@h^gRlXeW zE)!Ml?iWBlF@)vlbZ0Hw6W?g|q08r&u;H_0p)PXoClr7bj8nZp`-|O!op;N;0Vu_t z#!uDlS`VSpY*Lo@K;GeMJg83q_u2@AlyH152D!=3yc;xf^ z<6SW}vs&y<^~~27N>PiTbFc1Xh^)z>0reZ!qUh}>#L94ey4ef7qxbJ5bLXsn4waRikx$YhI|!L~alHH$VDfs^ z1o^k#s*Bd(3kSE)ee+&YMM>I{jPBG;fpsu_MaMqQPiqW;+i?JD8~m4jb|Rx zm$sL!!}!Sg?#rj!mr5hEqaGrBbtlzDSf_6}<(>+z2>Yl*XoBO2#j%TJ&fbdKm``pT z=CJ;>!}QEtU(}e!H~P)p&F25l0(gqT_b(;|?M5g0BPsD^z4j4q^RoudU{$RYbDJ=g z@$w!vH>nZ(YC)v!p>%oMxC})gxSMC7Xi63gA(w+ew$djmshy!@>lWN)(sdb=xT-_h1Q6z?!kkwV;=WJB%mlO9!C z4xa13qIK%|GlX^IR5DS|DawJxkt(X z8FKT^l&hR^LIcH}$sD42>Z6=@Bv3wPI%gAl#|-{!s4EaBcC`5OKor!(K&j-Y7zCOY z6`i2X&)B?bq<|BN0{d?`a!+}LxE?{`tvKOO)~tsYmP9Bw$k1+_Mh5&PUSB}kuyVA? zK_56QmsCkyQy>3ZIEda$&7_!hQCvR~`QbTNwKOT!PmdcT|4)#`%sWjm?%i(8zA%TC z+HGkzTtNM<|7{M_Qvn2MmYR#R1Ak$R#|KPAoE*dG+YHFJNX}%yKF^F={LrCogL8CJ z{??(hS$_>?HsGV3D2>jTBmp0_?XnfmT(jYSnp}y(ORgTi!0l4{EQ$6F!LEjzU*tW8!zDNeJ?PJ zf@FLvm#=3Y&zaRXD%c_Wcf914l!98Gk8!YXdoGIeTH*_O8RpD+VQz^*SGX%Lq7M2YPQ7+qFCE>6nA2%cOv}NmTMTIslqX^flpQ7w+x6 z8Wk>ZMbJq43VSO9%1je%jy!)p*|6IDe3o+*ea$8pO2ifR)1k$DWp-Qc`@$WNbA*h^-zBu&uDS;5MHSK7Yt% zs5l==mvC!0%=2oqnWtUx>OqL(?d}Vr3EvZ8CZL7w3e{&!2NnVA|Up^_< zb_Yux=B}SFU?s#e>-Pmx)65Y#!VKMrHbXo6J}p8mRtuPJGY;0D4{-_^%f41M&g#WS z5MuF68{Sj-vXl!K^rb?hLZB#5d91=ye*J@ZAsdQ4zQ($%u8RR%ctwt5_nFDyhFKNa z^uqfTE(P z)(s2E_sAD*0nCpDbvz%C1U6pu9cGusH6{yb)L@BW{f+`MY<@NWsqo*f{4YxOFTR=p-B-JRLo?@03xNv1`hOT+S_+_YDPWA$Sa+zs^=I97A`unEr3n}I zsp9+KTt3lvr_S^1J+J{h-Eq}?BADyR_ctla=&tB>qvRN^1N?95$_^8h&E^M+S^G|W zt$R{*^G)L$bOZN)YKJ@=f%A|d)u5;3xR-TcP$d-!d2{E|<^j%I1}A-`-Q|!WYpOdW z8Y1f>61PieCox>y*8WfzPN9xFn>biHI5-fmxTO#52vmzz6k=nQ9fnXKGG!jFkX6$? z_|$4Vv>;25&Sq`_R`Uj0H7WUOoEuP}Km?OLYHg(gUgjL0d-Lm&7nO)@daRLlPsOKx z+IOH5c40ugH}qXqFRpj^*TnD_zg3YQ?Tef*i`WSsq1(XHNx8{mKZ0;!Mo1_P(i^5j ziL1cRnXa3dx>H`VOFSVci>gfAfP~=aC)w5VJBrv$6#-riRJ0j!VKcV>uG)QfnjZ&V zoauU4>dQY{zu&uVGJoPPj8xu0j{Lpc&E|U4WmfWnNBr(fxZ)NYKyTnFRJN2=){ppz z05-EUZ{rD3(Y+LXX(DOu@@r@J`r_+8{B+LKf}=E55zoZt?(i1Y=JV|FFctbd6ZvyCkXYcM~yM+hVxNDo;jwMJ%R}9c6d}rrNA` z0B|J&gq+;qz;N!4fKpjm73qK~ih#+9VePa1+i478=4n!Hjw6A{gWOYwE?=7TZAZNw zwm@Lcj9c+<4xZWiPxP9+YfrpLHHMXkvbu!elk9em)J1m_CoXZmsYql1#|5gqmLX5aCI6~?KPyW%Bq?vcsuLR)`;YI zCPmA+0;IFATGWEt`kM#@b_sThD8kLXCVUa6gJQ$dGE0`S%RsfBhGMEwI~rl2#WpjDwe`){)$Zsmfcop{k^?P0sja%% zRw`QU_qqRH`&zHB1U*?iqw%I3))Cs)C|<(hNp*qCd8A)Z7-tOr>#SZ;u{)V$MrU*s z1Pxw~^KiYI-jhOse*@tXLFGfxS!cQNIVhOr&(sStP7}`@p|_J7PjdQo?7s80Yu3jb z%kHmpGO}-G4=dEfp-}8Jj<7-~7p|M_!dkh$eqd`EFaZMC>^!tGV0{ z*-VNRv9s}1p%*C|sAE~$@-kwE*y5?u}!p9n%$miV)> z`|!(%g*9;>5^E8-$<{uJpdA?Q0nZz5sABJ4`1yw!L&tBm)~C!aMd={JAq_$Nx~QJ0 z*Fjb~r-Q_y5TbMXwyX_z%(dMd~ zvfqJh8Mm{oXrCWSs(}Y{a>+a&cafPMjcj>Ogq5mntz7ySuMUs5BSJHT3BTI>`=$Tk zcz?lv@;TloIm%al^w|{Iw(JKFyuJzU&x~NIKqVD!V+eDg)w%0Wz`v3X8GAi%>Yo^zt!kcmtV$ zx6E?{cLz{XO1Dhb(&fDBE|~%iG{`H$@2D6xUELn7$8+nb$&kqfdTtqs#INkZraNqQ z$G?IJt7)K_E^#&I#RVnQGK>F4zARh1xqdQ+jGVTfd+GuVA6Ka7m0!#FnHyv3!N_*8 zDuF;1`AQOJhzeddekxdh_Mh3s&!=NC8+d*eL(Wc&<_Shw*)yPPtA-p)H8w=AE_{!r zBubqW*G$8e%9gQ%*;4X*K`5G)u@D!-&yCchP<81=;?2mgURXw<%A$eZ;~R(mioYZN zDT^`X*5YazDuu8HDL&J3o**@k8xi_ZHRzj#D&36!`M z&C(#gy?o_bR-5O1Ujz3UXmM_cuIX~x6Xe3N(_$MOm<~x)JGMoi02_ueyLXKZ#MGa2 zE8r5A0hALvy~+PkiTl<|@7t61MUo>OJ1RaW)Odwj*Nd)82i=K?9h(|Xo2#%!Y3b)K zQ@ynh1Yq)C#hpheu|rZXY=z@Cjp)hxLjJ*{4aSmB(m2z`zi8oGgtak1e#>=**zdG zf{uT`F|$@Ah;mxwNtC&HAxj#yIHRpWm*YL>1;-kBKMH6T`kD-(zCt0*#76h99JI)9 z`FJ!EYdP?`DfYuP!4ijD90AodX_q&Ef&Lp;pQVfctbnFy7mCoTVvm1uwl`)EZ%9)~ zG0`iPJ3?ysT?n}GvgU{PA9Hv^%4^g($Jfk&e=JHEMabGZ;pAfTs`XNS(`J8x{O|28 zAuJsgQvWUMW|jf9)ic$Pm#mMqZbg9Fmafv398(Sw=fCxWFztZG>|=aRq&D1q2gqs=m5*H3Um0ZL%vMpa6aniQr@K=~~NtCPtYH2sAQ zxBA3hMp~$I6=|)KYsG{}Xm(|z)xK9JCat+*wGC;L67LCWZq>T zbOsj(RKasKc$1D8!4ljs%7>2&-i@5>58^_5Vfed%e{f(%HTidE&+)HFPJ2N6CU}DQ zI1X)1XV*{a<`#VKHf^HRbBiBA3QfHN<`p?-ftnkJWN!1g7R3}yvtb2C;j*% z-;k7<)6Gv>3d=3ejvAh4Csd10Kl)@~BWP(qdr~=9SyOo(4{z<@d(bZ%cRyji`-m1` zs|fBC&=VPUY?k-K6CBXkYbFR%`u)nk^Tp_4Eed|-NgP`A)qg?M&3`8=Db2LbD|FVH zI;II}cuCd>-C^rOK$j5J3lQxGx2bRmnFFaz{In>BA-9S8?>aQ16NI^IgO4zpFQGLl zYeGjlGkOy1s#!i#{Yin@V@`qpX1~^2Erxj$Q(-ehBXDBBZj$j~&|g-8=kA`#v=4s?az4C0VbNeadB{>T*;9n=&Qr*-Yv>8A_;_$!u#* z9r=h6RHB{w!y1?VkEXM1i|TvZ@SYjEOFD)W1?iR;qy?md$_>ls_fRvI7 z(!$V4Du--@Q=tl%z8x3 zB!*jVnrmLy5c3EVCF#KdK`8;x3po6U(Uv=$7&fMKbx==nw8NFE4B~5VvgFjsO_su$ z$RAw9TL2(#Q7gAM6vnk7$r+(ok@d8rc>=3|i4I2gwwaXcJ&>L)D(yGVFa44uAo#=Z z+Rl!0D^4k`2IELQ0Llf-sZlGK%fm){;G6uM`i%RySktSpq?R|LFqHe4x3ng0g4Sc!5vFmNkN;@P!@zk9S&n?`sN|?yTPm9?e9A|j$NERz5e!J zgekH60UKbuL--SIYMUg$bF|j%D~~yGCQEwu)1ab;0}$&ECYuV7u1fCHVj{Gie&J_F zD|dbJfbEa6AePgr9eB$1kC>_B;Bi3TZ{a_F;9WYw_+gLALAH-YcFF_9BFM=fXMmCP zmXHM2maviDtW8_#M}VxR=9Ssm-tjWPn&TiuAQbThF!as|g%~M57?h|!)lfTzP|-^ZG^?Vp;=}8eWgDBZ zEV=;_6e)0R?TBmPsuw~34Y$e^)B%z|GRx2 zk>P{q>rq?uyu1Ib-pc(;<9$VCi!UGXn;a@1qxC!iaN&dL-yylF@qDF}Dt|fgxV@ly zs03t}#%SySRunie+9RbcUZ<9jwbA#k2-kLtq$-GtQpWB(8SOa5qtmgsm~&G6&6cH( zItVNbkqrU3Bco+!=G~?%hTtmvfCmJzicQ7wR`^igU=($>F`%2bkz$pdj%6roe z>1oTZVgYTk+IW*9hF^O5s<&BvN|a7Cx!7tm%hU}QXgsowS9_V?wFyJS zT7u$ncEg^pHIjf*xIO}@kjItb1J5^cJRhWF6e7F9dmslHgq6^m$sw7Nk#Ld2Z9LV( z4S`1czG~c85IT9@uG#yc&6b4hcoy-ZMazbX06h}!3pu=g>(ggJ=ea~4Mi47DtIAdu zalS)?@B6q?L$EQ4b7Qf%8<+=d>Mn>FP^Z94k5a>-ju6V|RJo_`vMQ+5bTz}?x+^P} z1e1$T+<>X$L)$Z}@xPX=bA9;_puq$14bNF>=JPA!s3>kQzsoc}0z_H16YHS&I`C7# z1_X@5GBXPu(NP(xn3!@z;bfp@H||SVm8bo8t~V{hhLDd}Rrs0&fbMgn$fxUkh40c^ zZT@VeDQw4xIA{BU#_$cW*8Gq|y^mvADg!6eZ<@f-Kq6c1AXn9T3AshS7^B%H9!fQ) z?Qt=9(`AY0J< zbx9>Lfyc9YezMIdtjFo68GI8`B`21sNjf*_&YDoLWLd0cASJ#mNe<_h zy(>9_wHajOME-0Y(_g)D%l`z7jFg*IU9vIIry7~0C+fKC_>Aex`8NQZPH}-3jvbqq z?gmeRH1a(h)hp}AcjGA-A3=FH;I!g*J0g-Vy-o*1tcf*Jo}_a@GL&UG9Mv>wQ5EAH z?qHGJb2Ad?H$~UBOk#9|tB!HmpXlj9<$^ZDh*)>a1MI{1p%;ShfR3MQpY^Z>r&5kh zgWYk3+x^G3eWK;NH``u{h}WNDX_4jkfC3Huic&Ss%I!+nL^Mg5gQ9#~;Pz>VWvBI5 zPZf%|7&AOE?WzeJTc9m~9J%mM1o%$1<+k>U@~MrsltgEVmGc{!q}GpmoPVL^W?1|U zrYgGjz}&ONuy~Cs28z6oxlDiSOO88AL+T81Q4joo)(BPjMMYpf9FvRV99=I_09DwJ zqD-fok><-6)i;}Z0e~vWGvaYh15gBjgemz~&}k^wA=|angHuJsx&HW2yi@h63+WnB zr|)S?WhM6rQa%1K?CsD${H~Uj-J9~y-DSY_`C@LaNBDoNz1UH7|Im-!C1Jd^EL-)z zy&XzGB8iQRDGN$2u+qDp>#cVQ%w&Eu7hi$<3xa#LS2aX)_KDUjo>&per>yKGKjBt{ zo3>{cMw9rpW~d_OuWKe1$KH9bBSH6TXQV_6aqSL6;;sG9F6KMpM}Db}ueP{tY_8$V zr09!s>?6qGBV&R9cPWF42im2BmVSqT6qH)+;^4Ib0IC*hMEgJr6{!)=6{?cqH@8%D z+e@zJY~F7a${Ka>{01*IGjOqS3FUF1F!?qdYUC$dz$zzvBL&=(!)Z5?`cHV}();gP z>+3cP!CZy?w`z^cU-5VXGBp<8lsyO%#UCn5-?GFzYMoy$t!bGNqunQY&tH*-^H07} zi)fhTe)m`lJ?pp;(nI>J`}8iXko;IE)jkk?0*e74BEL2{1%BNq5GI8agHA^x*Mw$a zHd&-xB1ydC+q6ExN;RIOWda3!@qqigQrus5H9hX%%6QLfvf7#OwR% z`+YzGd(3|kX)!#lt=iPRXWBU*#CO2=E^oOc@QbU&6g!9X5eG*I_Hn=hqMN$zz-73rLx4Kizhp~^?JI80=Lvtr6#EHh;^1U5Eb}vKq4Tqiq%I1cy&xsy= z_O_6{M-Md`k`Ww{w z#L0MUa`4a)9P%X^<1=zVi%tXxl>76-Fu!^dYsmCB_f`*FQF2h<9@f>FVs2+wUUs zGH}xlq=Qh{&6@m`BMAiP`ul4`Nm>a{GKBRivFPqZZx=aPg{p!rGvz_CO-+~%Qbg=2 zSnXE={4A{{R=(E=cQ2AY9}7uS>{&qAq;m0s!_Z!OO`lFEnc;edG(HrsFjDwNOlQ`L zeVYp5LW)sj%CqJ@5@dMvEj@rhImWYz+D`hko{xz@>p1!&Bt~O1gR-o?qv3k~Kil(0 z0LB-JioF z6~wekw1tlgEO%e4ngBU?Fzrv9>8i5u6-~NqkLDbfRnUy_IH9W&zEE0)u|;H(DVFz} zxw_?}x$pf~<+pi_9(QVXB+E)?z9!PPu^D*+3Eir>z$`5)8gOsXtlHF$8+@T;YAw;N z?Q)K=VrM3Aj7eyqZ0nmCjY#hKG~-5v9tY3I5U*eJW|li0SK^2kRd$altE~M!-r|zA z`VT)*Wy6sPtg3}ErKecMF$^ol zj>{|c2dH*@5=k58Tq**hq6@cR^Bo8e*WqS?h*W^X1JxmTG2<%X6#_!Q{z~CLfyi(! z2v27B_tu3)&zlVW)ACBOeUG9UZf4;#=a#?wWPcd78Wb!bXj1?WL?6~w3%vFH{c*S# zJ^>DZ4g24}ST)k-dh2Bvo>ux!J0*Zn{4j;nj;_#P##&7hm5Dk9ZGVYaEfMV8^qRX> zaij`Rsrtz(994A3y4XE!x{pi53;J`4w@{DARtG_lz&41rWZPPHam&`f02VQ?R72uI znC$4r0}N2&n}BM`W_-@+27^=iFI67dyjd{N&GOWNa^%dx=E6KpCegs@yvjLIFo zD(g?$({wgN9L(KxuXS`^R@>i}9xB40qEua6K<(I@LvQ-(Hwj~kN$&3obAlRJt>Ld8 zqJtNh0O;qMJoUv-9=r6#K1U$#vIIimb2J1K>Qa9SyGdPIP*!*#&F=Yl-yK6xKKVb8dd=G|;bAiyX{y|MBH2=*DoM#Gu zER$&1t`1IASnmvL;`Md_^h&Oy2iHIt;q7ry-@kf&0wuy12TDX`u(12Tegq! zo^jX7ntUnDu6+H}5fkTjNGV3&b#z`k!Gyql!CSXrdi}mB_j-}>`>?(cg;Doon)QAW z78?^IA1UK+!W{pi5)>YqAlh==Fk`RlIz^O_M2`RlRX~@t>Yp~biL@tMi(~lxGPFVl z&CWxHoB3ih=4QaOvh6<#SCS%qgxUn8%aeWrtbbOD`2NxMA@qH&;(I7A%_Vaa9fZi(-q}TnY5hDGOUQcvkmTQ=@q(AkeEswTQr(;_R+lq%2pTfGx zh5HqFKMME6{mJ$-Jd5)QOuGk{kLcmYCUw^*RL%F6BBeRd=@}7X+lr(q0&^{mtbX|m z9nv-0R_ZglM`zZAA=iNY&cUi*7Cg|Z=ZnHbp4v?}UXB7YAlmv3RYC+iqR<9|5e+%* zHeU;z)yE1}W5>F8f>>F99$PJl@fI9igM=#@Z390tphwbWQC*yB{b{sr`D&Z4D7|7cTdkV<+m9E(GkLRV;^Y+HZR?F;J6J>SbWm?qab_S`i^oeZ z)1d+SCHyR)%x?g$X=5CVy-My6(T0m`KcATWw3pi-WB@AyJ(2A=|HB1&$yZ} z+si-y)qfd|uWB?Ff5GK?e?`69O>M=vAt7BK7zHPZE3v<;3mW9UBek+IerJc?hz2{C zHlG&ev7KKF;AI~6FI@KYrvj(PQ1E6=?;5!vY{(6;-9Z@FGkIzb+g3oDQz*i zQ#PscAt-Hg8S8rMIx2~4!=f33^OZ>-rufR3qX_PCx93a-@X4mDDc_b<5YV>bV3jz> zYR}1Ct~e4${oxj=6M}{2-yb@to23=+4sz<}{#UzXIUHO$aO@Xp#0&LWxls6T`3S+7 zXf3g(mlrrl;uUbn&FReL^;q42+`D*r-#AcQ3u1cAq;R`VqyN%Xgd_R#DeqOH^8ln9 z)LA)5_@)^|VS?Y5K!g%K9q!6sl40En)>0M(u%UXmNk}E)6hcHl9gZ*47c`M#Pa^7a z(so_pZ}sbP8WQdgM!2u>@E^$AUPk|B&BEpQ!IW}LxBtPL4;YXIzCr8xeN47A@(EyAX%9IQe>n`@$wUS`6!xxg+p_2eUiPmv>1INEbUr$Yx;zw7j z1-%Isfmy0l(39t=Qx(+((oM zF(I-rL^;GIr_hDW@no!^?qhC&!pq07;wSRt)BB4GXOUME_%z*e*(vt~JU-yt>;cAG z07X{)y1+yhNnD8{n*}!*M4*1pC?q3w9x^^^1H1vCv%N0PAYSC>6Z1Q#X8QQdZuJ(n zcXiJ~^qC>Gb>DA%Mc9G4gf=HJqHELN7mBYKIum#(w}lEPj>3XvJQ|4YCQ+rjN_W$R zkFDpH&dlJ%CkinVVPR(L4i=U?I5eF96l{e*GWVCthFxl5k@4OfP6U7rFEex<%!R~0 zy#Z!e5PSz+g3N6dMi<~(2a*GK;{s)9eQkl5u*i#aoOLR^qmz`5^`x0s>+6I7&z8MF1fKpmaPu3Pq zB>fRK#Fk(K9$@WH36Ju{Oowh#gV5&$7z;P>gyGLZauC<&E!JYgJLhtYw~oU*{{(D7 zLs`Q8d{0}jl*;{C*+uJZDQHFp2kkAX-80(f+r0Qo{I`uraz!Bq-m;*Od$l-?#c~mf zcn7|x0|5s8K~MFJqKI|LR1${I0_C@R zHw{rx#G?n8p_lmauA4yS;&cmypzl+hnfim#u3qnWzl%ah?JjBt62Nxv-*F#y^~}mg zs4ziiiFMmS42E@^`th;ElVZg%5LX89as4mki4Db6v-mZ(y!FpNu}m%*6Z9YDB!A~3 z%7H9~8@wkrt(@kH6%X`9udrCxvPGjQavo!h-7dv4MVHT)?a4sc)B6x-T1Cj&m^8b- zJ=d?BBRCrcVA~B%l5hvz3SnYZ4?GbO*2AQ-fzbdFz8NDQc`> zOc2n-ijV?L6Ns5p5fg(_5fqcmb5hfLE6JNucL@?-VY>pz&dg2=4t5iItw+5oPcuam0!Jp!eczNVXOc8 zI$^X(?$_Eny$H2l4%u7tD}s_qtWZdH&@~1Il>MgElz*R2Y~Tye5e%@Me5yM7aQa4YuYyrS{Q6^MP`8rjL zwWmi9q3{RM_FwYUwT1jlA=b$%*fs1z`lJVGpKu&;w^p_$hAE_8pI&Ym(Wk5K%d3 zA1C)Flb+VdF-;=>Y_X!h`#*@$0-WiFwzW=m&h`^HeAZzv9WV*`lsNE)tW|ypB*cX1i*`E8tSbcNIk$odG8XM+Ur|5}Qaop{T=1;G z756OZDKH&W5z-RXgR57)npeujiy6Fb_f=w+qkax9X9A2`ll?EA;sWmOiyuX$Ig*3? z%k*%lxi8C=5fe%C50dm9r;~`V3JDV$_y4Q3dSFGZG0F0vbS7J!h_5^)n6 z{f%|m@JfY9e~OXRS@Jq2MR1pBQz=5hixT}<!UiA?lq<7<##MgzmD_)9984t69KeZ+*4l++Y= z8F)S1ms3KVy*%7*&B~gOV*w^mz_1N}RWb+da(ExH{X{!GjQr`FZ6=*!tO;BXKVH6> z?Prl8fIgp`YPiY9M_OcRY2?@KQ2hPV{Rd7DWxK@uVY_q)U@|y6U?v)OAwY@qq1|FB z5xkvV$&aLVbaAcJp-@*3U1;}fY zqQtP%;!m#MzEutbNSwxn+qxgRi#~kOtsB8c6*x&fDgNbXmq~tqEcQ$CWDGA5Q?jG* zZJXE^F~$ioMB!ySTq~nsFyeXdgMA`@iSW@!LiM(uJISZ;3`y{N1B6t%J8HV^g^8rm zCBlg(r8nGf&CQS4)E>48)do2xh|P2R*S<{CUH!jiWiQTnewM(qI+#yoa7P248* z&Ijj%L^enFZB;_v-PECC5HATV27F&lp$UAe!$?9mz-C;W{mk?gx-9_q*@t}gcSEPA zCwpy}C24ue?<<80Pb%COoPm##R|WV<=9*2lG0XpY1~hPiY{8umySWdft|D1bet?xC z!wLB0KWz_=^BcFx^$;0THb6`QE=&MhI7+L=mOgSpoV-qpg=9)^=V=K63n#AkVjDqF z)R2d6D9Xpn9GpU4-`I|bA_?G&Q_n=+NeODM(*RcBY|^y{=gp~b3S1;ngT+|Mg(@2{ zLk0cY1M@jx6Wrdq$ns#Dmsny11wlyys%qY11gHhako=a|q~cJTkY6!+OIV$kn8wPE zUNNCIIsADARpnLw&yO1G{b^5*)>nzwR9?lFnUvY%m4l$a!OE7vY{!abK?=FWl}g9W z8_gj5S?AxF#|PUhrl5IBT!{EMF`;D_(vr-3oFl5-W?v3u+BN*a)*p(;BudM4J;4p) z6r{}9`pYL|BaG^nFTz8leP#Uf6#POxp1+W=ml51^_bTD7zk&afj9^p;W>`GQYWJ;VtP*&GG6RXvDc_zlAi8>fE%X zEDNBRFB}_H0a(LGp_b0rG$x8jJr}QOX#@zLy9Dq zN6?2d2pUA=`MI^saDMQa+TNT5?SFXQvq-s>a6z;dNLbPLFLRF7dwYG{%+&+lgQVoW zFbi3W=m6RtV)dt>`abd*E^w#$aK>wpbn7bvZ{aaUv0>ul2E}-vR=Mw?uYVrSa(XUM zZ2H(@4_tuttX%>Ow!f_QZu$gja-$^ovSJBtefI>9!sgHoU?$BA zx|*)?0$}(yJ5DQsJ|o6&z!9S=>xJ2^HEi~fDX?8;nE*F7_|UrJL22Xk=d7?at_YvB zU!(hTa^ATABCT^mQuS~K?Ovcrj@;e}AT7&wXJ6GYAy&7apJm^fRdYPhD?L(Ahrf=b z<4`NfgY`-8bx5rU7xT3*!or^alqkH!$Pjo`uJfH+5JfbIfv0b6-D zmdyP+Zpn5t!u3fe7?*yw9+JTFA|x_kw8`sbll8|fS`*R@)0wXn=v>G4h&fM{-5Ui1 zyaDJIn+Qmn285%;_QEyThAc`OWAhk9+bsc`nhebxx`4dksHq8MUdljJv+~E zmR|}y#zT%CW>8YtiiK=%N+de_F~)S>MuUKS1zCLb(PapB=j;G&!|`bbyn^vzn2wj2 z71Inw_fvUil1w-<)UpcinI1n(Tt6Lg067UvYIMWQuH&H42_l>@tBkKewY&;0>Z1+Y z%ZvDJaLSbzE`3DjVt+5#aJZ9B@jK#Y*uM(G1~rmwf-9MA(+k-@VTKw9?eVS`Q^Bz} zQ(;Z*K1WIDD8L6`2h7MWrxgr4j1CBw=lw)4b4xj=B4BbHGjA<-E&f4;Cr}3 zTT4cZ^h`emOkhLS3Lx0HTj^8LzkQlJxcmnTfP@zDio^+l?z50k{eg~l_tCC%_y9M3 zARSPC8a!k;A()Lj&P7ee^ybM2zA6zujK4{|DZYkn-iH{YGON<>JDDD_k@8-)Q)-0a z0vx)rrVX$YxB%yIfiDh%cHo6FLs1T6aoz%w3`3Kt1|1|0)1$zn3;>-9o3(pQ-MIR) z6*SJRkiomk_WjSqLizq;q+D0pht|H)cbbP@Yy!X<*Wn$eUBPp)-$-?STwC(ZmEW(e z4($_rnUL+st^Wvqm=2~kjtZOeu6iDI%?fOEiLIdsKHHe z(F^TM1vT9JAX?}5#g_r^e}@p;_N2s{;&wh;^CwVNPKHvTF72gQVoP?x6#HL^&SoLzlEv12){N%H&!iR!ml|OQc0~sW0FYr!uzXZr!U0JVgUH$;bp7Nn_ z=@UVbP}B<6GOiOU(y&q35FEliHA8>ak&BhP^~ynvX0R9?oK4zyVk;K#fTsux*4ofc zcC-A`Td#H-Mw{(IF+I+Sl$l)9dH6W)W|=X2Z`qkx5Sfm_UDn5yS2@wB1dqeo38hwi zR**7zZDz^2QqdOy=NX7HfKbfm=hQ3CF{YHzO}g^c7^b2UP^i&Uret4#<4;wT&)mmh zoO|IB3zB7}fp}M}sG01K^aoK@t6T!6G<>!ndQDQ5Z zH+eO`ZoIZR4LZLvz5JzLLj5HcTiGglBhmWrduY%x_bPLa7%cE?SoWk1t#}?ktn#(t zvZD%f`S3C5^(}t)w-+_o*S8%vda9Yt*$>?jvd8np@p3A(vj2^ z7nK?H;8++n@+T0ClQUTJ`{cCj{RvH)b0u*@Qgm!0_FWscln#goUGAYfB;Kfp7j^t~ zKh)vHGVKDMUvMc}k^}Tj!C7}(4N@pPG{sjqjbHU2?A>fLc4r%lW;y(zfO)WV<@%1) zi&6;BMa3yK^jYdZ!paC}R|Gd^;j+!FI10gl+XH#p?(Yr|ef>j?X_8JXi+4}wyp*{d zMK!96V>Z4!Mx}_T8QZ8adh!%lU`W17Aih1^vv%~igr6(}Vili%%OcWl)%SH`SZ1C4 zzh};J&x8;@o`BJz`hB~<%7y=T$TVxVQk;-PB)=LG?{GN>14x~UiB=BZ`19Pn_bkXI za_7CG!AGaPaQh&n5C8++`FRoVpCo~y>4;QZpb+4i z2~&_~1f`7LtcTUuonR-)p!z;?Ix1K5gJsPBE!S9t8A>F-p)gve2>INO+Bv^qgo)m* zyE1wf*N?z|WFxb=ydV2`UEgH)_45OXD>!V6t*<|(8gJ`TD|u z-mUiF*GY#R=otZu$rkv63&7l0>+hW(3qyf0?0Bs(JKVD}TKcwp-qX z7JJAcoO?&X=ZGH?bYXLKFf_p7V@Y|oLW;WU;v&VL3l%g;aePgZLwZ4(8nhvd?K1-o^+r(jZ9hBF#o>$vq2uziUYjQrvP*n+w5y!Q*ACl3guP5y z$&K~tmhIMmQ2SNG%yW?z%+8P#=85By|78XL(YsD>)KvHMNwe$mT$S70R>?Biv+&pW zC_+jK3hft_002%|IQ5)vU~zC#3+;vUSt%#Q&hhGc&8KXdISl;#pu- zx%Flp?n&n7E><@76A|_16w@;XE~HY7@Kl{%kgDgiXuv3glT*vi-gEt7nQo=X^FY$4 zL99l{*Z$4D;>BQK5{#asqP~6G$y0nlx6MTikNE*>3<#Nrav*gP9~(gcC3*t0btV+W z8GeU99dU$>-%=pwe^z4up`cGj->p2=Js^O!;u#~~2!J`N(ukL%JL*OqlMkWQ;{n6VPpNun!-i1clySGzXL@cBJO zn<0jufrEYvj#QE_Hd{^Hveu}~68J$%e*8MFyY1=_aN)-leFpKXiiwHYcp?O%odFYA z|71G$CXZ<>J9v6Uc-<@Ai>=IKc=(}=niphE-r8X9d)wln3dZ&c<5ND#d97KP$_h$4 z5pOpQq$&ILnJMFJl%)KpMo;z0WVk7#Z=Ydm%^boS%fIcr`%FB78=?640#1HyUGYTp z&4^GZeiU#)Mh4Fvg<-42ahqD35ZROz%r}2hBXsaES>)!}(ETon7Y+e>&rs~+0vs)H zI&4`y+jt>{;Im6fI$i_(U9E2^|#9O}-gs4j1K>TbJKOy)fkpuraDN>|($-D*$Zyiw0xDJ9NEK!Hd>iqFDZ`Ay#xnBVeo!H8aH&=_!W>^m)MAeS&`5yQ4 zH)Hx|)>x0v0@ijAy>HMjT@#cuWH!Y4KtEFvaJ<54XKU6X#B8&~L+;k!=L6{pFsrO? z!jqD-M3GX&*7L4gGn+le<@BQh%l{ElD*g2NA z1#soghp7Uc&rF6&;e)si0=mlu>zFCmBa(l82z}AloAm$70=#sa0^9+xt{5=Yn!*C( zCz_c9+KQ#&Z2tl37YuVY*bYW9!X*0CCelH;CM{6qUTPjKcEtL6Q#%>0SrrGI)wHPg z<854YKP>f_uIuBd-N@@1C*m6h#x>hYmruTaRuF5Ay$&U!lp_cjyDKY`eA;ZC-+{nK zin7wh0|9MTHB*abjn3*o&;z6}V(z%MF*j;xZSKw0E3B`~YWto|MYh=68|+HI($@RN zilu<|=MRoJRCerGtt;u{h@S6YP2KHNNa)_H&^YIhuvp&ErJrv61sr}cvhD910YA7&5CaDz#SuP5M9Z%Di8pF!wg_=jgC#F|L{DL0NnqttMYnxlSufwwg`a=Ub-RBr3Hn|+;_OP`-n%?c5V#u==o}9C zR%amk$aTlQ1hV$ft12Uezs?WgbKmQl7BwTVS${yW=@-e|hRbf=vgZq9WDzt7is^dB z@ID?bK{vExx5ubas^N6nx}I!(9~~!In_Z@Xka*AUe+ClB*ptA|A5Z3Gu8J1~lh@h$ zXAvw>N$npEiN;R|p8@A(k#(#okXG5vfrps*gtV6ZwJ|6lMhsH!MHWY99u`mFjKQDW z)D;2)N-ScN{q}Q9$JP>*&CmPZ9oLXm?yH+Q$fqdGeV1H687-|=u#l}a<@2QdbP?F< zqi`|}K5Q3b-vr7Dp=%h&zc=(C$5HF?S)lS8NENL>GDs9#eXL=wOCp=YOj8i}-G|xq zQOHXtC`QsP*vYl-(QWB-=Y8u>1PD*6=Rb&q_QDq-o92V_%hm_ZKz*1zPCWS?Mo#GgI_1Tgcv^eduguHNfsW(ur4{|!l zWyq)1BpRsG3vAv3cQG^$tgqtGm`+{^!T=5$1pff2=o#XI5W;c%V}=Kj^YB40>Ukl3 z{N6jyMBClM5|D#uHLDO`_GNS*0+iMfr7l6k*UI`6L_x1sP zxWCzXE*f4q&M z7B~ns;LF2gI#u$DoVtxRK|JJ62m*fpo4dI*JV0Radd;?@V>d|p zbSMF>r^~vJxSjR`hcXzQC%*zJhlp~U+jX+Y*Z%;Cj(X-8gfD$gKlunKUdSSwk_CD) z9BbeS7rPjHa^Vr}e+sykIq&$L%M&2TI)z6SK6o9P2c(59C7-I$Qh*ZZv#lNU{z4Z% z*O?x^Z{9O@2g zehrl82@C7&_g|8{9x7jOjCz^UAWZ|Iw&}jFx`pm%)ut$~=L=wug}$5mHnLjKYhBfx z(&g7i%ClAx-}fxXtUZS&F7Dj0@bl{L(jK3PiRVnLU@wq=5SE%~A&Z>KhxsltJ)#N6 zSnO2mM&6Hu=e!>6^`Y#-?JpMYC1d$aad>J`^vGx!rSy+VFW@||Gq*Tt!K!t{<*FDfPICFAS?TZ z50ykcNb&sz)A6OhiTcNr`k%&Wbo|*Y@ktTqE5W$7hlK%4JYdiC-MHkcoP1VI(Ij(Y zR*{e^^R-0*0NP8UdiIO~$aaM6;?l^}$rqT5J&J@vulyKcuW`MT=bSwtz7inCeLUza zm?jf^*t-LYB{)p3!PfU|-gFQ!0;!XCOvfExUuITiwmF&Imj&MZEmVByXMwUynxQAc z(|rQ_eCAIq&_eNvRq+P}i}9e8M%f;36)q*v=*g@IH`>TT)>+jI3H-(4{*KUn1moiX zp8aii&Czei{()QkPWjr)*y=3GA@>hscdOy^ zDx0X5}JyiYkxaJY6C`yj8F5J9|PG{HuD$YuXx`>CX` z!`+PS?M@-ao(azs0thjFbPP`=rElj!;3Mo1!M=|FjrEv?1zA=a4e|22#>WU#Ek6D7 zu!A1H5QEdhgJ1>0S}Be+mp>%jg#IJ+K5AEar~Z(Y&;xwyFdR(Q`o%-ii)+CkR_@DP zPAQ&e?~&mE+_Tn}KYrWS7X^ffW}UGWM-|_THl7%J@o_ST!13p)dB1ye2)s4HzRJ(p zEcvX^-27Ph57SKA!-qYZ268)BTQ5QuWB(n8Mdmu3joFPYqVl9zZgcO73}I@xDaW@{ z9x-ai$wf3&o?0I|_6`>!SaROAI$|F=JUsY!H1o=;5kkElsPUH-k?N{jb9^A?KOQDp z$9#W)@U-9$q>ll)yu+@`Jk%qJYzSx79#7A=u*KX@d$G_at;MCcp`>uu+?~>YRb-lh zg&8uRt3~($Bv@dew|I8Q?M%07$$5I`Yit(KM)5;~`%&k&T@IWM8vzL}c+>^M$7@lk zE5IWK*ZV%S{tg=+zA+W*PpbQyw($0f3@MfY)439t0lYtAAci866J@W)OC0p#TDZ)h zqZ!ei_dZejilu_(aEl$YMtq8|#N#pWNZdo~Lc-YenSasRTB7Ch%~qW5iNT>BW3enS zBLljms%&@ApC$-}TZqhSG43KQ5v?3Lx+5Q9Wh#2J`DJndwKtBXz(Fq@<+fGipM;rI zDM7ee>SU-pG)eC4Ei<+Ha~Xm>}NfK zzLaoiSF!nhSAHWpya=kDJju1D2&fI{T@eHDs>kO4+o!x1*`X^ghsaZ)y+NP%b+n9a zW$};R_S^mXsTGWu@)P_{Gq5a_q}l6T{OQ9Lw0({%uil&a|DW+ojefG6GXWa zqnP{iK;2k=jdkz5Py9$PXkZzWXPyB`T%OL?{`!Fn->h3!LKK*F@#GMo zs|OtxRq3B^Igcy7Z|j)hkKJeb#E&S29{~9^Me>{hpYBh1T=cFnW>%ia7i4xz7ayXa z?cJ_!4`g1IWho9q&~5TMXJDH`7si^yL_;B;>fF@ZZJI3L8wH5m=I-HJ0D6Gf_ux;A zi)z|rd2kGxoQk>&I3A2wkL+Rx>?oH0IDk%udQ35CPPByoB@t}J7h$hEegi-d@V(Jk zu%f%}zl(*5{dTDcGA?nV;t3LN|Vs7a(E*)ZOXkWdE33Rt7gybLM@I*Q*W~-YG81(hBzf6hA4VO)~l&boNXn)*jiBEj` z;eB$4(w*@yz$!aRaLb-mXVDi#y(lt(#8#7_WWcUP;oKEM3o0T9=Y)(v;GBC!*_ZzW zgDf!@KH5d>&#T;192-Np)o!{LfNW)e1n2V87o;V8E~Ms?$|d?!X=*q5=3n+Ax`SuF zmggMorswUSD7L2L4;D60h(8qnD#H}x7;^_G$9KQ(#FdKP1a+JiT{1)ISfXAP`Q!!P z48gai;pP~}@TIkElIOmwm9q+ws?@?dQ1kL*?BhuynAphOk8Qwp1cz$`nS}Fx58!)9 zc&HGZAPlGpFKJ=$V)fRC4rYR1f5GrxP_K^ZO1LOXs<^4TfLtKJQvh9=RbA+yR*_9R zYno*f{(m%`^+S{2-^S0qjqa3YpmZt-D6t_RC@J0X5d@_L0jb?ciHcw&IYLPV0g+G` zp|l_&ok~h~ZF~6s@a#{xW9NPD^SZ9r?cTo~rB|feoRXBFFal;4R;S#wR=) z28qpo;-Q^bxT|{HgA%O=>L;t>n>z<(x=3b&h)TiYv%9v{CqwFRt@?0qT9WE_kz`yj z8WDc~{@eAL@Z6@Pg~{+Q$qa1i!Uc(Z z7295!eUO;DrGK~>%8glzcSOT>PaSxtomwC~7XP;W@O0rfmG4wP6J33F1M^I`^Z0AC zblXLWm6xm`J^u_o0Q1qiYjPpsQuyTlH~0ObiK$m0gCIi7bLy@ftUh31O0G2@RhG}S zKEFBmakTM7Qn?xmc^(W9iwyM@rGGh&|LkZ@8g2L;-Pm01T&5W}I&e25(-kG-{5ZrI z^J$YoklzgsnN1qrG9J!|P;&Oj`z!R;T8`|;4g_#L5%Rl9_0TdrHt`-ytj3=u$3^o% zKG7m!d8(j44@&IIq#p_QGQCzL*?o+Zc`}$D87*S}JDq0^r{nOa_t2J;*5-8sqBI)` zGqdMqWq5PJY_xoXxK9Vh_!A@#n3N!XnVZVHQrg4C6V0yp84xZBPU=Me$blqfG|u#b z6iz%}^sn$JV~@fo2&(IPv)I#27lT-sul>*gx#=9UDmB*0s=MwsPW_W$Y!(o%?M`6z zqTD?3>JMX#FY<2q$T+q)R=2rt@wcH!#kh3yeHHmV38SV~AUl0Re8GUYd=hl(Ty?W* zw{eS##feE_*dy=9r-h@a0Txz8L5hK@kNYE7C2$ggqiS=Lym5OX9i%)}FM0hDnhOfa zM@V0RY#_0QJ6McasHLOF{Uz&A!!P>&wL79~1mvu25X>)o|j$^*W?5tH!$9D>kjQvk(Z@~@KvP_Q^tA25 zFA0B1?ViAw(~_GXOC184Ob}Eb@593zixP?bHs1T_>-St2a0`lGT0;wi>7OkN3dZ_n z#4T5FD)#H2z$?J02Ufn5%z7 z#7WM8UbI%b$JzCQq7;(H) z9?uL9`W!w@cyoYo%3Ys*mbIR*c;s|EN4{Z!8=B7v93BxvxwIj@54Hc6Ny|GFXbk>CZeCOg(1=y&qqe3eT&JjUw@;M2^fQzP$RkXt!T8NriCGsX&5;gXUvQnjRr_ylj$D8iDe(MJ+u?N+^BMoCSQW3|X}{_Z<rj z>S!ygy4aGqACLXRlnk`d+&o<{9o)tt{7%8e?b17P%tD|tM@JR^907!=nA@R){3}3b zw1;%I4Y$Oa{T>ZqO63>2{JT7<3sx%FR0&Tw;L@+c$|AwyC!F$9I&WXc1Vk2}> zeu3#Ebl}h`uYfp2e+svhk&x77kp*}Eh^6=%AFq{<_&hd!SQ4Tk5a2)_JaG~<*7X7d z$$2H9`cYuTn}gUYXjUV;>qV4xmflUQZm?3V{g0a(N&;61>%|scFOTvIVE*qJ)Pzm) z54M!>eWpi%X50A-^r9Y(<|4dh0?Z>1eEIp9DgBvSDUT+_iVQdf_GX0u$~@(^OQsF# zFU=?ZoS%(VyeXH~aW}X12N0AL)M_yjucl*xBzR=TfZZyP01o8ro%M_S$%2pmI7%-# z1^xT{5B#1jQGwq@fuEevX%E=Ra>%fc@tC2Q3W$L3gzSrk zKX1VB?%GU0ClS?1fnUlbJ!gPJm<=IS=ze6&5$-{q4(O$U?ZOAU$!gB4_mdU+1F~GA zV(A4t*B=jwm8Lm2<27B!rrzu(X#y9@vH--^%8~t{*d(EWo60K!&{H$F(Dw;XIW4+> zI-GhPnd+`6-_2V{`E*%!b4f|KjJi(E=pDFDtrDOLor{D$$nIAa76ruA=0#qf8$ZJs z`r_LN^ItBgTkA?wif#Lw;E1k!gUW)*bmA9aP=+)y7&S8A-P^ga$r`06TlO_(k-w>b z&pQn8Miu>1N`H5r+y2qVO3&**7FF-uR+6Oi;HQ^&b^Ja4l!>QFR$&1J2N6cu2v5vv zXQc$+aNH_TEL2!FZjW=mu8eT#?oo?%p7%VZdESohWcRU?%OzFsvr7@~Ws~x6%_?>& zh>7u<=Q=GWwdGF^rt4=KZK!$`}ChZP<`xbRiP7!4MT<#ymX_0wZorjviU#efs zyo8fK6hXlBlm27G9r99$8eft%DM?HRg+@6WjNBTaBrvW^3)FOm+h1a}rkYCGWJWtb ztISNhprvv7M4@!$lU;DqQGS5N#8JSr2+L1oF)SQYb>^|_Ol90-`4DkVT|{g*Ua=!f03x~r|aCwls^(9c3U%@*Ngqk|nmln0@y zmZ8T4xp824E#1>L%3A-|E(k#qJ*&N)|(9KJcwG%pb*c?`ICa^Xf=c zJwu=OhR*AKl#3V_YT_|C`T@&WAgdb6Xi_Hm@2#o8-@GCEl2LZY^<4I$$p1Zoj+e1l z=SX=J=*0$we-foi;avDzFUc}QS7nUbG?IuBajCOnW~ zhw2poBO=7{T_~$e*eGtRMBG4!2I##1&m&QYJ@CJykYd`YB1Y_9|ET5mqw*(PJT(@J z2Wns0{Kj16z?y^CzOwl46d$5YI152JogYL@^O>-v7N@U z?2AKf^VsDUIvdibqui6J6`3Q#)6%@oNu19~@3dzdZh*Y}tGK>~Vg$?mq zMU0^5Y=Yj&M|_W@!+Jfj9;P-CiZWYclbdpBq^vlV_SU0iPEd1SxUoOp^(fE3HZwVU@f9V&qZqGnAzHbW{rfo?#bo+8!l~|QXPh3@`CAEGB-60!2 zWw9e=9Hm-dkCUQo{UDZ@^4uK%+rEJq2`eIhD9gVSKzw-iQuTAzeS4RbtR@JMtmY`= z27YpPeYxzb^zGJV7HlxkXHdQQ?bPOw`D)cci9pt4#IGKbZD+{rgD;1!7aZYk#B>$i z?xuzL*7dDs^qt+%-daK65e4(7zS79>&<{_(DAM5Ssp1+2Q6Z_A0g~AnLMGeBhnR-> z$&?ROiM%9wJ;dVcjm}$gVx%w8MoY+oC%Cx@KTo|$ z$THb~GOU1^VqE=O8zr`_3QGfwYeYci94{8^ZYtabf?d}+9gWX?mHW1S+_m!>{MWnv zL4CVXFySth^jdJM+KgTLuAtLZEMN^}7FT!6X)8}e*EP&@?wv&^S>=~QEumr$EZC&Vq9Y(4I4nr$1 zIkA>`MG7`{XRPT1D~J*LQ4wSPz)6ye_5WD_ifY_=gOnJ$zvOm>95dp;#SX4Wr1j!4 zYi{1m%BgufM4eY3kxaXvd-J_Mzh6s(q;mNc>F+vu%N|v=&IVFX4lgz-*l507v1bq6O$}qB)^4f|QD&lMj9k zK5>FPz!9n+!khC)Y53RuZ#0TgRhs#uWTXq_-mmSv8l<9U+$Ws=-go155-a=D|=YdL8k3!kRK)2prx@>l)&<>M!gRUXxl!TTUs({Sc}!$MS#gW;^b7M zO2+ch4NV~UL`rq&f}9Wk@8o1-`Ph)l3*C6<~>j&G4=i8Al=sYAAf!9=cidz7}TRi zQxp~pgz@|n6kb0Rx+trP>Tf3c(3VqgAW7MnR0TDSe;5trrAyywCV!EF4ty)R%%G!h zaM*a?lbK=;;;X9p=^dxWyJYP_+_ZUz)a4UH|KBkk*_}z{d0|y`upYug%ek&oV>b;B zW`jM>;Z;rEkvC~Oxvs>vY7(g?-mT3C(w*vKh_@g8gC9}YHAQ;6o+zJ|4h!3jq3BH@ z7P`#MN{yQ%{)F=NCvYWtdiout{O6NRcZ7=e*q*^dpHw)_?)Pcz^mA3d*xsKTQb5_* zq3A_~YrN_i{rjF1WM&&LLEeN$xj&2%`E z0dIji7g;jpTLP7%Sm2gQC}@Phw%xiy{`!vmi~etmlI*n}#$F*Xjs(^r1!n}l%2&_5 zRdG1Q+EtSD4x|zC6>p&HK$YI&vxZ`DvpnR-L&1nSP`0 ztE0MySmI|NNZZoasg7*aQnTp_LRHszkDxnJaD>>7=}&v=?1~%pN*{XFKmkHX()rO3 z{3`z)AWY?K>TeZfg{(1R22Hv%trDvUFv`gQ>paVe;59u7s79d1ZnM6QL7;o73A{FUUuT{o%||Rt7}JO%s-?3ueI)15vooM zkKlB7{&Tfy6>Xv`R`G6wo*fiCX#8Kq4!#ULSj2x z`ARDVc}KgSl{Q~Lll8mY9ex25GRa@|)hG74Iqai^`#tDHo+|qryr=M#WEpH_$-Zqh zb(rH*ypJ2SyPf-BR+&C_l|gyRlSpOuAG6mFxt%?MfSw^%-a^{bGm*G#&&z6zVY+ zlAS4%VJPML^fztLq~5aCkaJ2R?6KWQ_m@s$&!)NC$1LxaWB=UA=r%~uS|xIhL>c+q zq^{}Z-kVP+>b}HruI*#)8WT|tmjMM7Zu?3Ec{$~JHBR#Qnl+?odGh6=46Jc>`UA2T zB))iG)4^B*I3GvpAeut$*C7CzywE{jCs0ZFIz-pUabkJr{yVhg5;Pu1b@dTNbFUn^ zU6zp7xOC4q{bi8}l?Nr6-&Sz=7wNH>mtEL2mTtKJ($9w7Dhq4V!l~yUPYms*eRy&Oj#=38I;5p***Y;;@h;BG}!7v>%PI`pQUJJNy8<(v7IoS z`O@N>v*yJcYX%y;7%Abc%bw?2^3G55k&Kl|HnU^bUN}20UK0jM>tS!$@W#j>6?hJH zSD{Jzq2x3@_BRkYl4prlJUTCQBhsvx+Zd+bq4IY;`~B%%ALf;iLz}A|MD@ozLb=}z zj}_KWC6V!l^7Bu5l2j$fc|W|oFv+a zz`@I#x4{8r0nX;#Us7N}>63gMhc{8e79UKG+g{fu**ZB6JvsiO*eu#hZcJugBGUca z`ZtgJw~IuBHk7~V=wL5cWW*u?@;st|$U*`@Sk+kQN3E8D<$&=n5XDNhA&@9u|GAY zOpjtoym~;J6|O-bPIE0?vE`Q>LX0fP_Yn(oS72)n?1D`lf|=$YhJ`>S{MZ|*-Sw?5VpilQgeO6d2asOBR*Yr4{qSl zr(IFU_F84Km@LoW+C4}ghwTav@l;{XUtvk(*?&LouMy7wL0pC5ugbM$v+@xruPOMJ^?{~WL>Wr2fFgf&UWc=>dz@P? z527wo`}+JqFd2G|igM-hJJ~Zrp>wGL8}j2)if$G%Tbb03+pAg;KDXy)FU$wb()|nlu6~_kFH+2do zB`voz$zUlg!TY^`1~N7gRB}dds9Vr*#x>ON#j&96Ty+nAcyH-S0pl=?$*O!XR0%xT zqz$c^Ly>z4_i*J@i~Z)X{!@_&os+4bb}`#l8mc~+jOCe|#`8zd&zwxIQ1Y=mNB#~Y zwJu_|iG$l`-*>N^pI-<#yc6APZa5W|B-jun@Oql^fct_|R_0Ev^9L)+A56*I(5%af zh9Yo5;)USLjBa3%LR3bY0GNYp^}fKfrT0>V0NjD;avM$y#AeW&us^=n#|BFWQvOBq zP~)%XQ#kwyx|ADszVoa8u;%RN?dSAjvSyVTZ-+7acd4!zsLy_+^Jx0h$i9*&IYIzx z0?LM8l&2d$o~*@D0c#>T$ryONtY?;IS`1N!s~z(F_Wti zC+iFha3D}(65oEP`C8X9h9pPGIxh>v%9`Rv$?@N&KWrDOp3tcRQ*sn#^+e^AqYD)w z2GnJ^{SP0w0=ggSO!a~RcL3F|X|fkwwVB?OJ*9<*2?S=U#W!!}$r`_q_8r&XrV%Ib zyW=)BPVLU*$wbn}z5S|sDg@k%Zu>xDXvZuA`SKovB@^PiOaUidS8`PD_`fFUJ@4Km zBs8uY11_^W3A^Csi4Qm#fBOqF10cu)?n10?#_8anh<7KR^bh4+Kb2c_9Fv8aAV82rO%_cqy)i=D4a_nH#bKdp+_p-E%VkNQB3S&5G$QC}$ zku)f1*qr^xf(^4w-Z=6L?iUzKQx75OE8G~5V}^f29%sPq$wVd~nc`h!GL|Vsvk^kN zU`geyCNv^=D zR`^4Ao9O_fd*2(X8&FpfCZI9uclaohrHi%gGl&xCC686e)3zz_8M30iK$RVR{qIsR zOsjb3LA)4gsaHK|Sz9L&B)VptG6hGcI;B~R0Bc1|n-yD~HTZnOF-;M?z_A{5QX&689EU`f1WqJBxnDju(v)?XP^9$VC z$U4st^972_M}yE+!~q1(-G*fkhVFT&kegp{E@RGtAZp+tcohI^B$^84O!_S|mZQa} zT8;RR4lgj4ll_LtP`gUL3=6eHgH3kLnQq@5ua5Oy^CQG}7)Rzi4s z=0w}L*73`PN>$n)xrdG7iU?=y_ofURwIf`o!>RBZ{eh{>NAgFVZ|hBk-*eEDo=>yQ z_f3MKsR%Z|DkHE+*&^Avd>Mm@`%z~YfY(|m`P`Cf!|Zp-N8B_fXqJrQXG%6()@g(2 zKO>sl5Ft?lji5ZHdCHC%6rZis08;ap(85QP=Zep}ni^D7RcE5znc*)HKNq%BO=!j6 zo5QY6J=e}L|Ju9}hfYMi_lq(kz z=;-6fD3?D!xAD)Uxc}uvm&k~CZo{HgJ!Z8CCO zPNRJvUY%4XH{bJ(!)1i!Wb|}=(pG*OI|-yYEvGRd{P5=ULj2Y~e;mghdcqxua5-5V zER&j1=ub#_OW=wuQ%vfc>?Lz@1#H@=zc3fXE4=NZ#LuI!5gYfI;?kqUIw_=|oGVMd zt7i(`>7W8~ZSO)UDP*Hqxa8$V*WW)0tGmyPPwVM5G9$AlVkfAC2C$?N%!=&FtNRnx>v%{dqetxCsM%2e z?^%4v74roxb>0bgqZhH}lDBGbI{$fb=IjY4%%ApI2-DF82FeK=u+`Qt=MO|qCSy8T zw=g`cEzF>g=j$2Dhbq8zZrU73WFRi}Id)^uMU&mvZ9_&a%Qs?@k9b;dXnJxqCGw-0 zeEiaujef~I-z1Z5{%xncRY*$4TGjOxF|HR?-u*8tpnhBZii=xNfpsx}XCuGXEGY++A-F80QT8F{04pS|#G3;xUwMtI ztCDKtXBI6eu3B<(92RWb0~V_>&_n{sC8L4LBPldhml2*3#s9ZB_=zmZHnDI(aueT@ z;x~0ZJ6RY({e{?dz{U(TcNk2`We3fXd71@>SyvlgdWdR`Myfc(?osNvW0-y@Jf1NJ zl<+0R73vrnaC!8x3VwL-p(1-C62!0EC(TM?TfURQun+X0tGN$*7){h!LOe$vTQ(T{W_j zIn0!K<2M$4bIs@BLKm$HrpH_UvHf_FSLg#e-`A4rLKPSeWXeuPVmiwF&WgbS!fm-= z6IZfrW#d6AW|hY5ezGA`QxV$0%h?W$g{EYg-X+uPwCn>e$5Sn=qFC^k6&s^5?`tjX z&gYbFSWy`ruokf+1k*Jtxq8BM?$j&71rgL(dC1`^UJhyUUo(0e_Ogp&Yv3oXN1bey zVN|1~vcGq_gm~f*ktc(9Kr)5A{q?cP6gWp4{pM2S=V3Lk2HaHUt`DX)oZs6VLf>iD z0k?)yJ8>;GQ0?jAOdo9eTGPRlJp@5V5ZQ@KiJ*C^K?xSGpYN%lDI27C*~Ds%rcnIU zxa?$(iw5%C1Oza5t3EjsA~ilUK;2HS(7Qi)Rv02o=7~E-_xYN-6=kbg5GZ#p!z{tz zDFi>GWtKgu+c;K*b-y6Xgy3`JW>$-{xq=VyoP;wmgSZjJ^T1h~OG=Fh|1}6z3_$Tn zLu%GLy?nzoh<|Oo_}8w+cORuPn_32q=Ls}dKONdcrIo3B-|o#29cK7Kb{HJ^_8lDR zbO5lm3qucCu_hdLH_xxL6oOWN&W&y<6WWH{_s*(Q;fDjI>%X}b?7lZ}bR@@NZ1!8_bv(8w!wU)1O#&&`sxUu=3-&JNpxp&<8ioFTz8oM(l5z-#CJ;!neT+lBsa&_E`C2KWmrvWa9es4IT*)z zep$lM|JUMRh*FqolDN9mJ}g$!ix_J#OO4{`#Z?>%au?F>ea`(g zMGv}UkQ<@LHg#{OPT$fE=IAp*r+$l#?+q*B{dm16=KO|bDGZr9Nu-d%z>8z|*>wUJf9*5wiAGxK9 zex+cdrQ$F=2%?#O5SD&F$%bA$ia}iN-Ebj9kMi>uS_OHP^2*Y1;UapVW>$Sj-3o^Q z2E?gtS?8&yje{c#VW1r9(YoJ!aqQqeh(iJy!eE?M6vk5!a6J|fpjKyR1rAtGXOM$T z5x}#h=a`SIKc~4Oa0aigafC_REWC~f6DED?g}AwD@>f!@BK-3%(cyA3FEMsYjuX1e zFl2v`AbaD*-ukD(?_QsIN*$k3w9$dTyWPSPm$reBLUpQvqunt>^X+K9BEMNJS?O%x9Yr$pIe>1q?@uMVL4(8^KdEC_^+S$*9cT<>@yXsSn zbG2Da!Re8rU&yxMe+0iGdVkY$@xcBR4B#wCt1!-*5B}#yqz7OZgbeZ4XO?CkOwnFI z^ZOu^(`3>6G-R>bgRpzz&rH%p{ve~7kEOqp^-uI|lFhcSt~m+sh@Ir+N%%@546G@? z;Uv&ARA&*+|IM)TLh<|h%ucB%asOcj**8~NOx@M@88W|y5LV!k*_ULieq{U8$<($O zC`}J=lpKG9(Ia+`!V$%kC(+(Ta$k?+kPHfGNRQw2W*pRxdM6lrl>SF@H4Q9781bpr zN^E#^L@rJ9zh(&{*lFmQ32_N9@9?z4x{zrK8qO(;0iAT>Kk{e@H>$MySvfhus9?w5Qi}7~v(G`K8DNtk^!E?WS&Ut4 zpyVPu8SuBhk>YbkX#c_Wqhw(o<7&rxR*C>c#7cvNB__`CWA@bM@b9y<)RK6rMSi?l zXSrNQLp!eE(i?I9HMDR=lJUlBygrWh1S2t7S13Ac^v$oCN&6vpGp?3=ttP_m0jLkZ z9PRpy`x%+8gcSQ1unQdb#6lhC@Oz9xu`=WzkzolzBJQ_A!=^z8x--{p2!sgsDoY&jh52;x_x>dfA!y32WXOOX#C;fV1#rxmDWuR#&B(TMat5DUi8{welE9u93? zO?r(h`>2bH$!=__DEEk~h}p$iu-EC1Ln`!GRk_ntGEx*k=7XMt!b3R&w|Y8cus6kl z)%s}%D{-oae6Xj-40IG;NZ)idc`Kz@NhvGPd*Sv@s}j}}^g_;g!AaC3>;o#&ftxo~ zEMwody>Bi4{YcSIGq?W-_QAr8SC+l2tApyJj48^%`2Q|o%vX)eh^hnl7ay*~x8ZJP ztoaD&>GNGZv%PtU=bk^?+f<=4qG{$zvhVJ0igB*zZ&@>;D!M94c6zN)VE8Yp1`u# zP?VQ#O%0I>fuN>6$SLcG8oD%xSxj)-NU zzU`BGE@fsFlIUfu`lYw-f9>^*exWX(e>FyYbK{}$?Xxx^<@-dkx+eZG^uPCEj5`@+ zshv-Qr^kDO0}Xd$LrgqQlICz835vf-92WrtePXC<`%U)UGX`X7Mr~^6g6n!)$SD!3 z@(0{n4mYSa1OuS(rbh0kVd2IavU2Nk@kuj1GrqHS8wY|_06*;8&d8M|VNXtCCQSX6H?ofq{*^NHpGv;aWC1vo}qHWDg z^Hj$sNo3MJ4TmT6hO3HD;S1#{w)#1)q=4I_(bvQi@R&8!~TWanCCZwszSX zI_)G>TF6cPClVqjQD-mbDAH|&fD-O$PBeT@4@q6>5_)5*juF(sBH84xWaSCM8ux{$ zhD!?Q>h#oI{!!?S1|1v&9yhen`BguUf9&(iGi}J?AdkP13d;_M4{ENUrG%le2y_BX6bc34$!kAw( zd8G5l(#R^Y#MN@I6*&bY#NjYbk|*ha2_F4GVndP@PJR_f-l-icnLmB$4*etRMn6Up zwh0oXAApdE5V$($v;egq-9Unh)%h(xJ&G?xyD_EE9US@+nk!d}3abdKo~8L3pM9I} zkj&*XSeFBp%vJYJ0Ceqs9dg3v#n z5}F6n6qO(0+Gz0;abaSX2*CccOJ43+8)`c}tOR?&@VMlou`ymXIQ^7nv@W@YA<#Or z%tK`yy@FhHic})gGTHayO8?j)ZyUk2J4F=M*h9juWKVN=Xqjc>x-oZx0DgIttrAyv z_+^jm+h!^@zowW!BN<;If6 zZooyVgbbCKL+IYZCgDAp9hv5BFaxwIAQxbM4PcH&m`64`_C$1dqN? z`H_Scbr-iV&rZhU?C$J~&^ivV{GcGD8^WpezLsP0jvamJ>N`u#&Io}M%#JwVqtLrS zaFs4wVj>19Suuc3vLFwmwhpy3sjTr6nM5)|Li;g*y@-O#F6|<NrQ7#Q)DD*U%5D?BmtFM;&=BY%=p zSi_mM+zbE@qv#kUxZ^RY3FLxyyIWdaPs@?<{kn5m=Kom@vJ|@5ANvQWNq_&@3vM$w zpG1@#Jh?GjA6=MLef!?yq~aolz6(&OrY!7~0O|D{+8^X+)uk`9Y0!%pEZvdY?y)A2GE7Ida0&GsS>B$gexJVjVU+>b3cL?M$K&c~dskzIm6 z>U-^ev|hZ_8>(69{n{T}Hur>j=Iis^O22;%Rxj(E>`1XwRDqicY&`oSXK;p_zg}Q4 zbrQ=wRu=BKJHWYaVtfx{xhP?zikpJ)($QPKIzUUA`Jirmk%8bs}4Gnj|EZqSJ5ZF}t!RrzH>y0nwi z)JM5&s$uUks)FO_ONMO;d0ps-bk>hVP|KMyJQ-%pv`n;6k$isu5Q~!U-|sQNkk@v6bfs zCG;d^Zq!V3;zxy0FhBOY%Yz(DO8xamBAx)_Af0bjHNCdq;KzoUNYjYoWXKwk-`Fqc zboT~$XxJZ3cplDOc{ZT)yWj&HCjd{Zp#o*_&H5zm1VOpVh}9Rm{zw?jO}sgyUu~QF2qp*-UK*z755nEQOTUy8z5U>s7vUEui*Ph=+D3mF zIk|Fkf6XIU@Vjo>T*HmyFu{zgOteFci?dBW$jFKr_^m!dk(~cYgv?Uub_Prde0hq% zHiI&1l`x;uMuvm=ALSs5a#*d%J^2#?ufJ{e_%cW(2h-g-{q)us6KujHxBC_~5L#&K!KNY3SPD@{wh+@S}J&$tob|rsarnc<_1N309>9fc)nKN~%PlF_i zGmZ%A?++-eKaHm~_Gs{EsJ&Hw{ZyJj^MtwSJWfgAc?<|F64CXg?`NVWq7FK~Ygp`1 z1?{i*kS^O^pQ3%C1Px{#??-c!XpT*Bwfeh(S*MYCOMMC7g}mV@pQ^O`OrJyh+13Sl zVe><|vw(00K|ghC0D!Wx8j7r$)zw40ehPqeBz5`QIa3$?7)ju@* zJGqTuEDGTpntvC88g^XTgZ|x%OP5 zPN;|Fd`4n2^QD6z97Mdp^y1BP-JesO!{_p_cT8fqzuuqbb@Ti-L!mO)pcg~KDa1gC zpT#RSW`K z#*9Kphh`G>D+rIpL6Mw^o59zjBV5G&`!q6yr+p7bYcYI`sc12m`)!Vm)zE6lPnNU3 zfBnSS$^UqdS~Up9s`clYl`@D(%^t=s`WA6_()PuVKcF}zx^jyMVOP2#_j@MToY z=v9i__YTZ3@r~kvCxEdq&7)o^K1q>Nx>e}K>63Z|87wZ}%!vRK7W=uD>y{vbcb?S?f)^LF}e!DX<_4-UiM!UYncW zXK`k7zKhJ%gPhXs2fFH~sW)BzlH%C(ukutIu|oXANcs_4WJ|2E2ZN%7;bZDD79~i( zXY=`HalSO`4l*2MKr9l{!mZc1zj18mQM6WI!OTz=B{VVR&RXQ=qy* z)oVYBws172-h~MO@bl;K)GhfdCH#F2rO<{4TmtdMX@b*CITktYbF^(I&$*Vxvx>-b z^gHrJ^IA;!tgSqHdBOA>e9DhLsvcjseD`ADmPD6>&b(CL$?SUP;Wpbd@W})1a_YtoGY_(9Wz^hlrp5-a_J4EyTniHgvvK z+$lj!uNWMI7Xr*Sa5^PE7-$_*TY@Uc&l=`Izy{NYdp#o)A1YY{d=$&{DXdGgA88^@ zoUDc>+=6MUnp~L&o*qRD@O4DJEfJGuV9vj!XLN>Hhjhtzys_af)$wTe?)i;|Y>4L` zSdqJSD}T-&wammtFYhlu+7#?O9(i4zW~RnZx0uQ1`A%u&Fq!UKu2BpMnr^^PexSMd zx`=%+o6ls29wKn0rM?z0+rq1(3m86cZqscqkrVEtKWt;hKdyN5B$G7okU-c=x8y_2 zGtwIa$e9fun%i%jafk+_lw8A=AEW4|IW%S*pnBqLuRXR(cmz^Gdeq5PQ*&Aa?jOGA@xcjytuP1 zq^7asEIp7urQ`|-xtLctYZQE1|1FSwg%w@}KLjgDs?T@@JN7Op!^-x8t}}~o-6u1T zUqU{%NalRHA8Y|D8ElUzkaZ1e`z_HNw`N>b}9mnNDoY*E}w zz+WC4gu%e>>CbJ5g%`&E;><;K%_rZ}(x!W!j&t(=wMs;oFF5HkUGhGI1T)|Wy7+~> za+D=>s**ZGcrHNxdGhNyfF+LR9xme)bcb(=0Uu_#@2TP|d-|2B&F|-42*o{QKZAFH zw7@b&0Q$^(1#CVT?$0mQ_ zzBq7_dz4*prsjTE7x|e1oF(vygsV9!udQ=Z@(_Kr(E9XxLjVPBIY7_={nsn3Xi+Gq zV&hG*jYT$#iO4a|&K6NUW=9J@A~(A^GjzQMjp-^pnY1mRf~Z2@Ko2icFlRxKyLv>J zMzEHPmVbd9m)9n8UC{RgV=~|4jy&N)_O72;a5eKbwOl}h% zu>CR9UD9OyGO-t{kaccG1HMS1CMe<>3|P~Snh!M_sy~AS`Fc_9=-sJZl26rlW*`b^ zA?r)%a7%vYM>TcdKF^$9=hnGUif3@QW+#x^K{Q#wQ|+`sD~hF&?atJ*!yca!8u%qQ zyfl9g8pQaXkugA&@7Z-fj+$SQQFTAF)}wJhn`>*%f+e2$qhRh7|y1eL=J5%9Jlk4*f2q4%ZrMuwXl6jS2 zFtjn+RFyh_^E!@}$2&*Hx`LhI(=JJZ8RP%abe{24{{J8UTxZ)OJL3pZ2xTRlBMM2B zWR|0pnVCJ#Wv@h((Xh&@tZ!E4K^dtiitHpK>tv7PoWI|V|NZN6-MSvvXS`p}=POt4 z_5d@Am~~-qWnN?Q?LYRb2=K*S9skx%ecf4d*YrY92nkZet6xVaC7OB!in6z!q@NWKYKk? zknXu1zbD61wK(AZiu~}cUVv3JLeZ=+0#02ks z!r4shV*>L&p(US{(-QW0rvu)iAH9xDBl~=raa8{CZLq}y- z60!f|^~35iEvFbn36qPO*zNE;x0)ky4-0T4?Fx!1E-ZZrvxTKi^`T5?{-{Vazi4C-&J2 zB8E05MG51fm@cnO6hfYV_J(zZhYQu2S6PHZ#c)D<#~^XuBfZvdENO})ypSS?D0gto z>4)(b1mc40D1X2ipGk2p;*>B##X5xq+tuwmvJ-Vd&D*ou3-(J(fbyF5@EN)8?(W9$9tV7d{A|t=M2xB1rDh3A z;j`&C){cUJ?g6tqOUrYC&IxS^D3jp^CL`}h*RiFZQfizdZe)DNZ^-@o>DVjgPUB*3 zv_^oK6VuBavS2M{It0V=sT(!-=iHD8%c=u=h3*5f^Ezr&q~8>ELtA90FLgpm4N-hA z#GeWRE7{o+T`jZZuU~-`0_>%L-~JATmD1BWvnh7s=vsgihRD#HYPsw*G$!SBkfd

rBeAOeU8Pju{{gJ*;Wj-4$EeX;&5q$;RNBQ0`-f~iEA%T+!?b2@aq^v&6XleF| zan;8+5Pg2YKRS@yI9+!&5rsq82B!mEsPc+wYID@$eN{aB&Aa;pViSiI<9GL6g`cp; zP7s$m8ix570>TwF9lHRY`D_@*Vk2MdJ-Ec+f6j2%U>u|o0%oAu=j^}3@swYm?s#M$ zs+V^@))bb&b2ox5GWL z`jw0gS3(@1Hx|pP_ja@jQR+s^vkfo-bNYySwJL<=H&$Y2fUJbcyL@B$@=f#oyGlrA zO5?#xdsjo^z2Vad|A{LU|6Yz@7xepD@Ec78cg7wESUaC4E~sd}vsjNT)t+5mNkhCi#fQ)gCcj>0gDM!YITej2@5w#YE%$ifw}948 z0?#@k&58QYE^6n9#eGog9ZCv!yE^xrMi4+wrapbglY$8-EIluv`KanF;lKT=r{X2G zS5bww?n(w43-xK+rfuIzS+C{R;w!SX@msLhnXml)?+Z8#IUhn#3btt~|82Q!k#e`& zZECZ4uI?NEo8b3th*~C}I<-N*=Ke`+>3s#qu%|85*bV^^k)*efrx6fG_6-6OkD@VX z*47FQf7cd@1-YgTWG7~)45)Ub>G^hflO@T9uJ9t69lFVwX3to=rG-oRPE(i64Q8$i zQ-0ne+&6J+Eo}bloH)XCsjl;B&Y_tt`nO~NXLG@|&vT90^Sc^eL`K2^q~!?Pm4p1Atunv>izb|zd4HK`e{&IM0=7x<-7UsaZFOMiYK z`U*?of@e|1&RWLmDBNl6OJ+D?u}Rzzt#;-dyt|dZ&2bWt(4Jpk>fDPJ1zp#2 zPs`odTF|-)qpyaoy>-A-1mHmgKn_V<-rkrWz>MX}z`H!6tX0LxPssV4A0X&ii!|1) za^?_6pG&6a*_nN1-}w-4YC(z6=!I`a9}h9ivW!NOKeX`mv^?LYw>aTQO0ZOt*_`y{ z8|RP14<4Cg?oXz-h_O4o`0IO-OJqewzDtZyjovnc;E84y@Zbql(R#jDzsliL;8sqU z!|1=)tfT~JG*f^4(e+1O7nMHGzc&fF!+azC!X9Hv>xIJKW3!(+F_nU43R7V;;mVn~ zF7Pxp{P0WLt9xm(;649;-$fVQ~x=(j(0X-W7nr+?t zW-h>WKjukqW^MY7iSrQ;?I?eAHge>9*tFwjUI=G4CO!tB{}u9pK64%2?)w83ea@t$ zeir`yv#aaZzuLk_!S#%uY4U;zLXwAiS^lTw5a@9XESe|)}8yyjSwfU>h&SWrBIoC3Hx#TG$n@(7M%UCzDjn&ru!ld|P zFY&UIW8Wz>PB`l~^meAMw(9FGp!_YL3q6+Rsvzhz#HPsZ6gpTc-7UyOnqZUBV^S3| zc*Q7p!QJk(a+N|<9C%-dF%9L025*)3qE(>3f0rn7uJ(DkR zT_`t|VRaVVhKp|A<)qXX-{68C?M(4j0}PP!x|g&r!+OV1^0r9C8+*lQ91=$fr#Ph6 z3QN>E^H!F8CO<;MuKQ!l1YcTdB(AiIacs6OCbr@>^itY&+Wkp0;g%s%GjeWv* z@Ly4H>{?RtB$DEh!erU^S|^=RvOZxgf3WZWg~~;)oI4PzSj2u7Cg{m2&I*(4_Qlc< zq;*?{`d-UKAcmy(#^7s>wWEjzdbZp4zWp z`eb!P?a{u28%r6S zdqe$%UlpYIK{w)#!^KCFv(3DU`j9wqnywO3OLjq+`daXQ;_XA{R?u61u;H@6wy(!I z@p>W_8?&{sTPsI}xx>M781T2nls;?4peHxFt{5NwId!zk z^&N)!CEufgOi5JZd-HtXK0m30qN9nwUjjcr6Xd9M4r0WoS22yanOpxpojk!pwulaC z(y{w19e%=(?!h~5_l*`A65}1REF+D1$lzC7>Io=*2sm(Ey)c!J!ntta26RAr=@I9^ zy6#_|%TQIRSLCgKHme(=6E;Yy^|!VccVhs9Ve5lbY{GtM;;Y>&Yh3}uA zd=~t?#%vf=!J?QU#g8Ihw|8ZcJzTddsaX+J>Q z=*(2Jw}umbBRH|STg`ziwpb$Uf|c7YAFJ*f$BuVVYmh?mJY>~I;i2JPsAaXI=tN8a zTTwrvj_EsMfpBk&p!%oqT}#7 z#Dfw-aGV?BW}-54!Si;{%^1PPpaOhu)sF|t`wf+hOO@a=`5hDCwG%+E)kj zt^ERDf!~L$)b-cg_g+(Pyz(5@?=brw_A=h2-6X=4bgpYf1=+o#1~R5&u90*8h8l5? zIHJg%FmFoK$#~MwRCLSeA+P(XBpdt6jEnudd!=@Mtr}Oj_O#E@W95G64-xa^-s0eC z#M+i&=aDi#+C?S=L9@=A{O3YTBrwG&(*9b5Nx(YN?!BXT4cux#Y;(*&b9$ieU;0)Z zB3CbY^aEn%3ol^T^6@N9oag9A4&czVx}&1t9+=5h&+47ob`hF~TqIFdKcwPJvaf9d z>cVr}gnf()9uqgZ$K1Q^_(ry68sa-HNcqb6_mnrP$?5>%*-J0D`{xp?;nk?EerIiD zg#XuHlQ||ex_9VzzjIHXd&WR;z!c89NCMV7(tf^?_ZV0L${11)gQ=BQx$k!I;q=DiQ0L%*p+$9`!AJ zwdoMGA{-584cb(3)Q&KOKY-99pd|5@Az0d}Am8|g03k5LQd7=ifvmxcbTfpXp+S+T z3K)OC5-OPG_yYGSr^9dgpU{-_N2!utOq|)k3GpBCx2RYF9icu3Qg>1`@Y1=zk~ijz z<*?Lm9~oN)aEar8hYba(8ac`vY9bGfDId!4WumN0^t~A3-(8uSsjeGFow1_ib883doI_ipVfh88-Vt4h8|CCGM?`~*C`;8<-eN_t)F9IYKYj+?^TBs;> z?`GdN-8>b3I0d2^J|r6DL+tI6$$1Hwwh`fLzzM0R-}ae37YNWFvJmg(IahD1i!_Ml zW~&wb&_I6-pG`%6TCfpIHg3PtcDhkhWAYL+^7|i#WC26+HzPjF$J;&Pa=EVXQ6xu} zc@{dd>^lFgRK+f)an5InM~OAdEV~OV^qTZtu^5Egn!T08o`Uo$SuM9o0mIbWdH2Bd zhi=ghwGhQdB@&udnC8ZGKzY!Itvh)t***)MbjJtT2Ne94Hi)q5Bi-$+4;9x^cOKbmXg7fo9JqRr*|ruN@?fSu(|W zmw*7ybHJj7|3`S{g&BV#qggg5J@j*Z*OA3mVz1 zma@V@tdCx1Op9jzg4i`@nSty6)wX4LqY$In_ffqZzYvy}@s!x@Un9V>`-+VQ*CewW zAOQc87fNKGmVbU=h!s9XNl=8(WHGKEuRx;CU(V6a37h>qVdeJ3M6)?K)oCnf*oQHH zci*?_dej{0Z=K4M41v&u`UG!bY_&&73!ZBjqwh2!JI`6`dw(>Jp z`Hf7L51l?g{a+fVVJjVZ%x6MwerKnI51&{L{=4{gJKu#;5uCyf+XC8Wq%))VM?-Ha zh9tUmm=|3&{}4RUy?qxnuzgbB;|Y(d}N++bycuH;=)` zc?Dj=irc;Rt)mF49cw08ktks;@MV-cw?b|+p+QJD`&_74VH=0R(IZA&!{~_OqOLDjA&?(6hI8A z{g@a$PODboABWDhJMmxezf-quXX>Kl4lXXKnRsruKwo-)S;{plL8O*pf~{}jzBl=M z^@Hu&&k{$7Ufi)e_6zx7fSok7oPE2pVwAA|RcoxR@h8wLdyAB^qlr>-SGuk$kkUrLz%_s>0vE%ZpHBntx>yqGCg=smx){;uuj>gbK^vF|lUxzaCs| zR|Z@=b@dzJPZ@01klGxoO7=5blkJkm=CZ)>UgovnFC6L|fI)bA$r^{naMt=j6k9c3TJ&M0@Z^V#YNG_Ck6`E~AO#`b28)imb`Y*zRoITZsO zPlh@5nPB;2DO0J*>I^^DKQ8pZ%|c$vo*S4c9|xWsKojoNcRFQMN4qnwpt;fV0&z8^ z>Hb=a)5f662avfH_s*1@Bdu$_(XuWDKkU=56&z--X{<>A1eYDC9c`(s)Ob5L&p2JySG>c4Ok{u-d%6_kl`FAb&pvCqilv5$YCa& z06*&Jb_&J2Pk%AM$BB8~D4;pyJGW2;{$ST3gu2_qm17Pkn$;qD6&jpw5-rxie>|Cos@FK@AX+Yh*Ls8;y2 z@WY3&T;H;<-xYE{)VAbT9%`Ufc~+0q*Iz4ZnOhDnPCbAA@v7urOsc-qQUkIK*aEW)>BhoTni5_GAq{Ai&AgFq7%i4026KEi$MWM zvp(nGI3`CdD^7Y0S+c<=k+1Mk87HvzN;|n(;bOV-1lh(*Z6L&p#t^{e8baFgQBT1( z>PrjE>^8^#!hh`k=3)&SToYYfIKZ2>;Gq95JX4tJm1HaM@_kAl@0C)Wfu0dlhcf;mIQ2UQHgrR4B^4MK5X?9Uc;(vI5qoO{}X7F#627ff@!SZgrqx{#B^fznhd zMuPgR$51LWMH!GPch-AJR~`pAxCu!iWsY&iA)t#a?D53YuLCttk+K-?&ex#TKry3* zUa6x&VgFlP!eFrmnaSwT5||UONwAJbXqC8bh!S_GHU^Y)zrSx*OvRqWYS)hueeZZa z{%p+UUVdBHOr78l;7?Eb6^isSZ1X?Wln2ax5IaV$*9r@$gH>%z$=BqI5 zy?9KnFn{spy_4G~$SSH(5bdCBQoF?)Ns@?;Mp-@;6UYmhVudS7K>s!dRrt{fCof5J z*Vcb9^U{@4`|k5?|Nb?^)f&n#RpLX`U0tGoywfk4V*+yViS0v%4l}+>xrhh+4gx>| zDJ#REJ~oD&TBC2b9J`Coh~aw`@b#EWxc6_r^JI}L*O{_7$%*})zETATb*Oa=JJLpH zLy(VT{>u7D|6eOkkOG!lZ#%U4+J#>3wQ**OQ#?H&Nz7mliMf~Oc%T2qvXLVHu*at? z--^@3tzk}*`8UL5v+ZT%o-y5(i7J9H@Xea3whE5*dJEfGHLC$a@G21~z*^t8e$t~QtUH2ux^7AN7E8+;M5AE~yt zB|`3>du)W>t#wCR|530GwJBg2ja91_-MweemFo0Jh3D6t$_GBgeI`>C)DeQaxx#k{ED$Kj7IL9gM)iMenmiY;?wcrT};eMBQUTeofI^ z#O?(dT~ZXbI}8rEL-qf*FzP~X4>1*XOxGaAtz#)kn{fel?*jj|Kbr|Owr<$mdB>)P zI4_kpyq40Gwenp|xS^e0BujQaEP6iq38ISLZ||tMi4CQ_AX93Sw#@ED*E+E&>OS0q z5SCJaqQ0Lqfs+17aE=tt{D2)`9zr(#Ed1*=qR4YJr}e>d2DoYWdbxOxz>0jnKCI^>j9#oCnbJ`m2uG4h+;+FH#3ZB5r3VM-!(_dv$?GY!joaT-0(rw+7P%XN*-FV*WOxYSfc=Ozub^Xv2?H>`-fro z>YoA%09bQDdFWiEq*2YPePf8{f7fb4N(T}z#^}U)re_YVYP#^D1T7HPBhfso&(>$J9@*IoB zt%9M(mP6OK2|jkH0IMwaK1>V(Aw(6`_q$KX&$ z)SQ?UnshVo!{^zxo2#UU)@S_xl21TWj&l$|Q-rc5*zi&iUl|9<@C-anB#Spmuk@uy zi)hWeH|a)Y7oui0!cOq)`|Hv6UqOmJYh_{KGDORN%=}~IpJyQ8zIqFl60O>bPf$qY zPCRwjA2ke+_KDh~0ZvOAA)ZRYkbv4ZLBP@4{A&BObkb9~|6Q!OMD5fh>eFxGR9`t0g>zpz-8Nv)epROuV?wJ#C@H2cgzC3XZ z3SdL#I+6`pDIR*zVicm{ASUz{3Ol5PH`Q6odT4+PXg>yTmhO9WHc!!BC{pIIBZQau zNlqkWjN6Xo`vpODIMi^Bmn~4aHpS|pgMs_Sq>dnNgnw4=xs6xiYdw5I)sz3yK?l3s z837+nxJjdTQa5j2!3gaAno}=&T{j-thRtu5AE=$YchBGCX;2@y~Pzz0TR z$X4tmG>L*so7CR@__h?02#%V4f^NwDa17SzZTeS!9%UNL>8Zduxp_+fQ70FNBw}E5 zAQG_`z?9-Y(0%8*R_Fr+hl}ffWoI??ZDy^0RC{*yvrnp@mbvul^NT20vxMW?d`lS3 zv2waMC$W4S4VXQkMcUctIxGh!spGg5$IJcRqS9i%v{UHk&`$I6HT=sL^ranT_N@Tb2Pl4xF2;k7>&L z0G>%iB*IoNJX9WCx$ya~sm2uzp(=zUkB6dfSW82T*6&}&2)dr;lCw#3!xpLDefsJ- zZ0P9)j`%tMQ#qUTqks0>d$S(wlkdrBq8e@vBS1@IEpAfJBZ(yndkTL3FEafqc!nIP zyV4%I1X1=>K*6a3B{9>#9FLg966}m{gYnI*VKtFPl-{=Tb+TKn?EH>ORs1%bbDSP}>unI);quMzVU6Y1VR%zMDNY7b)yb17#lvS{gTRI5q{l_Bg^{?J zIK#tuz81!3;qU*c765&UHOO9Tafqa`HmSDcsXox!w#?C0mtPb_op>`JqT_jJ-JI}_$*hs=@s@Ak@p1cP=ms+`-&) z1ygr*xWA2N{hrH8mgTj0v+%6ow{$VGu5b1AE$C#DP@8|q>)iLObp<+kMBo8rE^umr zQ77mvd48KA^%JCvc^un08xC{m9O7I?)+m94F-Fyf!)hx6zF-EFce4QH9EFD&GU)G5 z9&2m^eZHD{yrd7uDa;m8&v2~HZfim~ye@|d%*xNl9*V7f|1nyY);h(!}ivNsZG3^}j8v?=l zfk==9>+SY;GqP)d*;S}4a*ZsmdLXaLas*}h!GQuzVvajeMV?6_RX@ayoOCaVs0tGfO4A)6ZwIGM)a$%l>9l zCTJpOMuuOb(J)cP+&hnDX(NbsMFMSPJH>U3*8Ey1`Ejq38mU4^kHc4p1l=#hhFIQS%}mHGNAN z2|s)TUL_pxpsYA=z{B^YR^JqSioEn=uskL!4w*`Z?|V_PpJ5WAB9Zm zvhlzHi%*Yv1Og7@`efnjJOw#^EQ?N;Cf^%O?R1Dlw23fg9vvPEbOS9#ixF(Q$&V3d za><+3CqNPKJPcAG_f3Ujh&H1Pwl=us*3({a)on|@2}A-oDE2N zc!Xk!Qf~{eXGb*egq(V=`3VIkxAl2*ZvjrknRSa@VmYI$ku*TZ-WbJNzZBwhy1EM0#<+3eOgWU*|xij?An1hP-#PHD2M zDh$njSpS`M8;-;?kxUT8gGfKm_VTFf25t7Ff>72twollL-p?XJFQ2a?j#I>7$bjAR z<*$iP?>X@vJo|Ab-*d+Y2THHy7axv-*yb3+H@4RomjmxDtDW4BW)ZpD_hp4o zIx($zT+>yc7xmIdH!@;C8TUH@{tixbU)$E|YDigU!>({un{GqBK1fBtXTV1=v@|Y{ z2npt=5DM93c1nKBrq}&Dh8zZixQ4-9zW3ahPhu&o2PC#Q$M&Jmp+^`}Sn9o-BDg$- zi?@VzFRq=sIOc`Kz`A{VY0TjH8o`)seJ%!sr{7mq(As%p9EqEhJQm?Aap#%@$DvrC z0tfZ@s*Y>&5lM?{X6G3(cL=|h%@%&s7iIS@`-~o0Gw~wxKBvXg#KQj!((Bt<@#>K# zVyy`E=O?niXs8U_lL1!`!Cv7vzd$lah+R;2m-jEa!t(!mQRU(jCM|Uh+XVD^WAt#J zPFb4$wANX=!mE5hsg~~gdTc%J>Uk^1-FftV2a|g0?)rTHtn$`^U}kC!e^!AjWAFyb zfE`%USt=xk@0xE-r@==sPypdVaB7&u+w>n+v&p4K$s6FonV=0(l^b}o+B>SxmEp25 z{18noTU%=nbT|+sp|)bm+;>(=u^HpVr5%4~&6!3&!74_W-t%}g|!j4J0Fv023#v(HOE1J}_j)QB# zb9@Ta`X35V-DMJCT|b0Cgovl_JJyeFt{~x~AQWSw^9ln#V%R7JDJFQQw#7l*j zevkMTMJWxK}(&3<|Oj8p&n75IGzV@-i+eTQEU9%^xiO#Odp4Unn*|N1vCJD4YZd^GXN zR&uPDSsbA&emj~oKjJbLaAM+wk-2PWWd4x{2i~OFPXX;mJRL?O^z(peAf!=3=`ttR zK=peRL(og^k|9Qf+cR;>z}3kJMQx&MCM0Kw9?Jf80V2-Y>{nUeeY$64W}bkY6bHjg zZJnu{2a8(T`-p%-FnmgQPkNE%=oQ+Z^Y_3@$1^M_swOOms;c!gkEoeWklk7GE!7n5 z3HtY8WrdJnt`M}T&Z5)5pZN<+H${vy2~Sjvv#|6SlWjI{iy8^Q0;x8)t^Pj?06YAa zM*KQ?uwgWLI$lamD&x03AB7l_TDH>fR`yIn* zyb|+NQW_|hBi3J)+6jx<5c30@Vk;9K77g5hB8USAP{@u)nH>C+#VHvX-gF&t4+pOl zQh+D!nbgZj1gSdYx_u~*Z~cl5NnBuit9bA1hrAgM#QqJ-frf9y>MYJWaIT)@+R{X+ zne9x1^Ou+HOh`nx9`D!_rYNGmUddwc!01^8)qqT{VY$G@bia_N{g*m?C>i%^&_}pV zz5m!j;!)RJ&tQyPKy;?OpMUNEB_76GA$qP(F1us*wJt&F9ZUws2Om1#@Z)eCP~sp} z+T10K+hMG94o1FV+W~80$NOF5rfDI0|DK;j^@)pR?x^q`AQO9r%)~(!8qwxVV-&zx zp2Tz+6=`1aFr|;Y?yv4Jn%oI^e`MmugbZ^px@1xE4G-?KM!+p2I0kWPIQB&OEs>d` zVfp^Q(yeSpRp*$7=IK&=xvq@f5EzM&-syqwR!qK^WO#bmRG@>kDm!#t2@K}xLG=Mr_rz}5mC z^_r8Pfa=r3MT$J8?OF`R=y!0>J5N&Jo0!#uT}Qg}o9S>;CG-V2EIn;aXvtn`7az~& z)&H@wT2kqA@u$QY*GWAE-M0I?G}+BvQ+nlQi=#k=rKb7N-tYN%f?CXS;IiKpFq_kj z#Obf^osI#Ejy;8&4}Tq&f~X(GR}oRzcqZTljZR)n;(kQBRMhjZRz2V!7U}^z{N)2Qk8F%A+R^ipt0g&CWacop3(~ zo@>2aKoWuKR`daq6z!T`4nfQn{+$AelbU}BXOl6-Vglffx@#2lXrw)g^5D5iH}kos z(B3#X(nq?fujHycc(fc0e%b3mRDh-dHkR4hK`7(l_<5)xqA>l_Yu zD@EV3$m~gzm=8ivI``n=$sBYB<6z7|S!FPdCtlp5pWa}^fwIYRiCmTrr8$M4*~*9c z4_X(NI_~IKAV^pc1K}hkf(+aTiVTSH8jo{EHoEk^Dgop5`5(85Z(LWSMMwI>L-z~0 zPi2PJ9+jgxyeHf5nVf>pCCwnBFnscwS5Vh)toBXt(^KOBam(Z^$i-4Ojn}+dhA+8% z0jvCRlWj`tPXX@zPfUs_Y-iyz$~`Ii9i3w_k^sN}Jt7oTuFA?2D&Ka^{-L$qx@kDsqZFY~6#L7U+Xd6ZjY3lE=o2Ru=4DuT@4%UPqBeeD*jRzc5ip zyma;rB>s-vq?#J4?5a(^FP+Wvqp#cLY@+ZRD8-mzG#YzT{x+^O>W#puM~Q{koQ5}K zj{?rIcUFwIb{I)DJfb!?X8-;>fu8E8JFb8j1Q1|lARwu|_HRhTuo^>jz?{nx635~T zs;tY27)`k1F-LVPIlTo2OLEp_v#VJ_Xk;ZP9q!9-~)yK&vX>Y^%bSFbf-fn8%3TLXNtFv0hp(@V(8Q6Pqt5dL%Y~-+Xw7|{yLPhE+ z=xEk>Sgrh%JI!|cTluy7z5*%MnBp-&x_=Z*p)?0#{vqqlAeMx6|V!bi#%zCvs4}u<+?5O=Y zY=&O{)-}hyekVML+i6H*vn|zy=gW1CZ=LGP#u=QpiV0bery-FYvAyMZ4}!HQeCWv) zYqsLLa4DJRAN_|Np56i7H{e$h%r#e$u*jtaOwhzSpcYRrRd=;Q7_ZQ z8pxYaW|uG=1;0dc9tFE>Fw}GVLpxByzMB6p>J;3%5%5W}?6Jk(g3|4RYQ@}ve?)8s z^*5?_8fzo{C0rzRxbs#!EqEzs;#0lS>!lY!L(aoQF2xQ?sXyt)1?2Bsrrp06oPYbG z47j6n3RVy-2g0IZ)(GlCG(;QXhSwlY%w$^K{OubkB7(Sqc`oIa^r%XZTpY(x`^L65 z(w(3c6V5Or;X>QDiqd55EsV8JOyckBgnnj;Z<>5!vi$?=ke^0CyW2jsUg@rqc4VnD z(xk^)a51=k#QY+-`Y678G4+^GO#g3vb{3>PQ*Y|-pB0P&@LXA@vX~?3d!mkhNXd}Q z?+M91pmCTIB?!zI4B?q+1XV#18ft3sV62Ya>;k!^jOaxWv5+jwAgLg{^}3`+g`|Nb zXgsDEhWULEXH7XBGGKH^|GY3?)cpEm8eH-~LpVYOs%xrt^j(?QG1}^HrIRTPeXW=0 z*Xy~617h4EXSBCpTS_on(#JR21`>8}t+||0xcnJVdrH1Z6`v=l?&X$vCsaM@8zK<- zH!Uj(=aq;v?argGai;%0R8IPKMtnbqU?7ug2HM0MKkPPlIK7dYhqMsXlknd<_y^5p zfiOPs-DUF~Z~POPrF^!@^o=0qVV>>q---o20J-rf;{)& zW)}QnXvel12b4fpQcrZRxECuWS6ei>S~ByMu#Y&>(;NhRtqr`!v~QQ9oV&kOR!3b* zr^l62_NtIXSMWQ@soZ>b;;urjv7Ovv$96|ayaKt~r~B$T8UA8<4ZFE$9kZ8>qbZ1h z28vv+TsaR!_2iU=bCeO=cO^l`kL`y^K&%R5dKEmvu7CN*cmM=aJMUdLzATsX{r4da z5V)Un2^x(Who%4dqWB!bH~21hWb4nc5oM1%Z}UajJ8@v< z1U62Va2xzX;7OqFt2^bPHyUtHW^oaB34hC~OYS?^TT{cDPu(_wW2ieaXbq?gOup`uYWqOApQFP5Zj4u}0>OY#WuYtXkP zG_P5$Vu}L(OvM{cDta%;|S33VewPDy+E4U^m;^CP>c{CXILNGGBea8 z#v6cnW{0@&EPW#D)dVZPAR(+@#~mcqIrHyZi&aTu&4*(IrztQlc!uR3B7~EN68m=K zNhLXxkq1%Pzz|zq@T*ow_clp5-X7vaf#&CKShrfBFo4MH@pNT++FYMuf5Vu>`d;{e zTYfKY@6rbv5SCpF=x5hFd5Ke$pmA>wA`z6NF+nn`c~G%!OrZGc#cqwFinf;Qo3MBN z$I{Cea?Uh~J)k{cICjz7*bUqnzgr1cq1~euh5{7(PQ-c4ks(wS(EZBQidr}O!+ERk z7H!Wi6O9rD6AGN8G2Hsi{#T$ag7M%!qZ`p!r^(BZ7J`)!(_8uyC>di6{yRgIVCfw3 z`IjhNem!33MsCYtX=wCghI{b9oYJ)@g9lx&xnWI*mzJmCwr>a}DbV4XP=BQ5`XqG z78a^K`cy1nQ%x|hwNBBi0sP1QP!-lgD05Q6nC1{~fomWCn;y}0Oz0W??VKwhZ?n7$ z2?}5mx4n} zx{_|YUP)y+k^2rQ4FB6MaW(o1Ml0s|Ho=ZPlWGe=zoq`if8g_(esH3p)14jrTPSto z{$bNoSy3sXm%d3jfil*iUTrue`93IofUVftDU^1y+UMz0 zoB}hU?z+kPo7f(CjtaTU2=L9Xt8vWDLAbBM5Qw=Vsj7}TFCiugN)iM_^PstpA2@JG znb?WBL9O_UJE=YwB~K)g7=(X#MhePPlP6%|C*+}v!Vg2!d=?)|ktu7?Npf}{O8p#s zu6+A}SITkQXL_O?#>J$(_Q&<|ps#IPb>sZd}?Jx#=-9m2a`te^h!GB9#NcFsWWW;)L5&c)QzOw=z?R{d=ML1B||EKZR*2j>UF z`-B<#2;ZbLO?kQFKok^CuEQKm{GHH>NTJDtlBo(rKuI_AP$f`U7#aDw7!;K-{*2b( zlXIvm>Q83KFW{4~U+wc4jWM=M3f4;nL zbmBgljDqMe;H`AvUvK`+hNRBV>K8GnpJ`fr7g>199!JO3T-twJlr4p6^|V}Ki3GM5-yhV_RZi?1 zCcO3RBan}LA%v+O0e!odk`4-=*WEd0H}-d#AXr;k0-ugGZYv;~+@NTl%>1ih0B2sS zZ$Hqkv6l6AZoU2PiE&Ms>Z@0#*jz+RtAMLv6cC5N-GwC}&FxZn<}v(3nI12GMXb6a zl>g9)Ld1nbyAGFTs5;77Q=6;$KoXdM-6C%X^P0n}G?apmWNSu@`U2HR>EG*^xgG3Qt@y@!X6-@2vq94#L+gCXWvb&6%v z?#dG1P&@)S`#zHAKR}lHBqxyFp`r}-F9|`4GwEC3gJc#-*iSg(*3CsY9vi?UNBM<< zZV-DS;l0BXoLxr|n`iEW<|)ox*;Bi3`6#cTA(Q*(S=>>_-4XTmhc#gKOamvQm$MPeW^Ya1ykfZCNfOZ~d>5gK+1kV{^`6*zdQ^^)boG(S&6bPW3b#E> zzE5dMqeycgOvh94;Yej0lkDW@2FJgN9OoeQd9rI5*BoN|Ljrh*4tPKOWs~sT;w__m z^Cr@?b&&N>3D-(7g%am-({+O60x0|tnM(Iv?HD@MTFRlICf zvv~g=?ooT3NlY4L$EK>QG5j^VKI7GYhq--@2{L;oo|!~k{5tESAyQt7rPL~=u3H{w zWg{Nyh)s)j`h`F^vpE9zFbhP*gDQN7I=t$M3Gcv(4tagPnbg`MV72ZOhu`PWNR~1Dn22SP_`kvEZNsN z&pbbT|Aw>N_xpWa*Xy!}JR||)%$c}LX1;~EZrmi%TPts38hg1Zc7tBe%>LbojKf&i5m-rn@3e-8imBnxq2Ax1}j(mDcjSwsO183-$(1JYyKEe z3Wl|)H0|kt^`%Sg5A1j1Ff~xiQV&k-SC>PsvAM7djUwc%E3}t|J=Sd>9n@htO}xIC z5f*oNKTdl$xCpy_!y|U!yYwy~YXAxu6-`;Dy=}b3Z{@i+{3-)wkk>63KtwhvsaWIz zbKij4f@$wS$Q$iJlS+|b{Z73iu8|NlJ6CQ%R^724$WWbKd`IWYejHG3Ojq7Tg4w%2M@Aw>Y zZDON6rB18a0`Kr>KyDj*hooSR6~GEIXHYJ7Lp1nt1fx$EYyydVwPL^S*zb!zULzV0 zk8SZg8l5v3Qe9&{3JUNQUW?l_PwaGj^6BsUdnbN=DL(W5qwLn!<{67Ig7WHu@xz>m zB6H3Ux)CbJBGPF%?bj14N^qX{RUNsyt@XisQixm)+KY8w9=KzSsQEx8M;1p*5VNBj z=AYw{V`LEPKeftRa=@Z7x-{B7+!sq(+aF_tN%TNG#!J<1>GT(DkA_TlSj?VTth;(^ zCp2pfLfj)mv+h6B>bR+O&@TS^x0A(X17n37dacD4T%(MhjVqe<-*^*v!s9Xr$HWL> zjt?_`e5G+^99>$t(6#4i5#8VvqpI|4RicLZ zprrMlCNDqNSaG%7F~obGzzQ-tTeXq4Xd(Yu`^?P6^#bVzE|yyU^Q76b#M9^d_kxpu z&%;o{$)P*D#I`{lkOd~QuDC$agI#FA?Xz^-!CLzN3&N!-+h5yNYUtBWBgB{P@L z2gV>Ce$axB@JMK82yV8DaiDG!u|6+bYho7|vyh0@9&0-7mbB0r*kBt>QU@j?05!Sq zUZm{-_RJ@~(RvHYw#nUIzVN=D+J@7;c&WAeRh5_3b`6gSFXw+_-wQ^7!#~-wGk@WC z#~fQ5Uj2&vEwIFRAV&XJy=9#PU(+A@xCY{h6h^HcV9q&h2J?$qit2;Q2Y*&?KH?(v zfL2ceRS>jtH#WPh|8;oFPm$XCBC7jx+{sNnGzpMQhdwM^dY|^K>S*HC@RAGz)(Q*E&CR#eZ?%U4tzA>Vm?V*kY?ql%UFdTtX*3)LbgHB@$k@8{ zbB8AD@XkEM#R3{(tn*Bn9|Bqi`(P?3aITHn zq^L7*H2oOlz6$8<0N*fxg*P|;G%)I;1|$dMe*rL}AkV&MuMJH~cc6B|9LQgiG3Z}p zEOlAsb694F&_gmvZ~8+LH#Ho5~luXD@<5m+8$y9d3g{!MTL(gOQvhUs~NIoVrqe?T(?>;CDtJ7|$>m$e?AekHoJs z-d=W%eR_z8FSD00ZMF*JVE4pZyP^rlD!s|W4dA7 zg}x5JVMpX=dS4V2bfJsr$jrIi-}~;&N!%DeBo%TZ_?eM<^7LrQeGBgtJ{@^V0cYnXQcCEgXGbxV`hn-*>ht0kxEICb z21)ad7M>YU3%^^ed6BI-v3I|<^C4-^FI9}gZJ;u9L((4%^=5j)A`t2R9ZWrQ;d{e?;i?!BA@o33gE3ikXkudUsd-;z|j7LDl;;&p0og z#YL=9=yIL8c(5;IY12WOITAIX1!{Jg{GxTX^xlr?f&#A&()_I4`{@`&&TR`HxKz^& zSBMa(N-&w1bx2@UuHE=4(7*hG`6&)j8hz6CU-b2x^S(U3Lw^_tgi=+PYTL_Gi$3q$ zH=L7)%m~ndq6XN}K)V(>DB&EkC=pA;P42@MYOT{lPk z9<>qzqQJt*w(DKq8K(w9)V;(Ngg( zidD5<3{5bWi;H&Ex*?U;!HZPQKC3Z9C4p%|p^lZ+o zFmQEv;0MRMLhOJ25lhY9YQ-TYTYYkYVWVHcX{NH_fRHq^hOOG$x+4eg{$ufsG;=*h zl?v2xC!$9N?;1p99EdWxY5*Ld_FbTZXmwfPFGSL%6cO@v9;OEWn+t^YP_9lqJ~U?1 z=tTPvUwt^JhL`Y60&?DuXrAxEi&Fpo?H`o_F2WRb4DXYs!AdW%{H`>l@K7)wS?F8y z=Zl718vOV0(D#A^JeW;w9_EG#NKxgH@RA&XDS}i6L?H+EZC@xjm*WjZO#i-6vhD&= zdJ5Zi_7DWGSoL3oMt8ULmp37|FBeSDbz-?=*Zj11y2PNQ#aR;=8`JTS%$fkC=)JEz`Pf?DE}Uxu5+?DJ-Xk7 z6m*5>8&>3-`Aa3+c$Ka}Ux(F=uX|M>q$PMHcCJLPl zn8qvcePc7Kra1Co2-IcvjfYkIc99~dg9nZR*2wu9;DC%i|Fq95bK6YQ2r!Z-D+0x8 z^zwJ}r{Lc3X6s(&INuQ1kamyOxv`y9G@TMvkgQnL8@@8ZD2X4=5%^bu^d_x}! z&2_w7?`lW`?^YHbQ9s#ywRA7h;IJoK`iw->6xnD?EcqXlkbl_K(*0rf6L_2;-c7OzMIRf3d3F%P*pl4 zI9@u~v z9)hfn%V--WVPSv^Y%pFah#(Dh(wkVwRuIvA#Ah{kRwFzuW)5L!hO(qJ2>RCN$mzC} zUbw`Dp{n;ycTM@ne`;%B;qA8fv2ZzOIV7NovFFQS>#Rw37n?K=bC7wr4;J#=vLzp8 ze+n}bg*`-cAEfX<$s68Et3R+ICj&}G1?XmiJ-K> z{om|1X`_YgT(GfVz^tWQjSRY8aoLbctJ2(&{;Jjdq|980=|cgk<3Yf}MxB`1lht;a zNcRa6kPdsZJ~9#%V*GG|W(Og1fc3rgKKK}!*Gzlk5}LE*wMUm#G~=P*d~=P4aSV6+ zo5*jY(3+Oj`%=mS)PbLZU;A_xRd;bq}}3i{MPp!;pVUO(}L zrwi(SYz%I38#%9JjDg|dfz#)T>_z!Vb-3tl~HKVV8x{h&N`wq(Yo1^N7cH$LjhD1ss@U<+&W z9%kml%q%DaY&L8I9Io7q3&`e&$%_M~WKbRy3O;iC?Sz@SII3brchGY4&Q%nGS(N9F z!+mNM59-c#I#r&^aTTV|Z2UJyJFr#8Wr0rJB<_sP`y;p)paUe%v% z$Bbo5!$n#glv{|Twf7ZjI(P7qqSS+{W7ucZq9F1(UQ%yWVQPn!0(CI@sxA)E2O-Ph z^iH8iwVC=RO-*g>q{DY$UysH~_cdh4kC&*9=FccjMfHwCMDcga-To06ptnbJ@wA(VRm8CcDw=yi{ou)XNlRN$!lG&4bqIe4 zn7>eaGWfB58tm_cD8k?cNObzRBp^m<2D}Os-;|vjF$G-a!`@v#EdD>0My6K#-De6H@ihQbAhq&C<5p7fiFSy8s;F=qqgzYY4NO#A1_bYM* zjxtfcQcOOUVgkgF2hT8zb|4ks-K(km2^!ZX%WP_+J0qok0K!cSnHUux*n0K^8Oy2= z<3d7KHmKJO!0Ym^J|HO4Hvxt+r3Vc&JW9bb3gg*h- zx?(es49r|H=YxPx{HK$WrqA;8 zFXi65k|L4yL`m}uj`f~{%vcwtZ=A6JOzuPRD`#5MFB@LN0hn)4d+0d$zU8!?V#Gc& z43oK8%jri@2upE53bFo&#LhU$qsoHNy-Fh^1N`L4HNVF;0y-?OlLb!C!4SQGXl;}y z;sQt99wN4SEVh2?QRBGEkWFGse7((GqO^`1=6jkiM{rq12jLcKt*aAnFwtSSn zaDqUEwd7(m4O4eizN#XCPTfXDUKHabj>Z1tdjmLX^cqbGHu)n&{RZLX$?Ta2St4LR z$m4KESB@n9cb5ch9&-@~+ab*-tlEj}8TLbu?=V@mjvamG(3jox??pV&H9|7vW2ti- zwYJi-`Ee=eZF`pN@Efw$aJSyT?Jc*Glf^$p_Cp@L==YjW}ZQ zyNR-g9p#6PVKRxS5OT3$+tu{vKU-oLk@-|^iaW?#^12DKn*5d(3G|)dk02Nz`IRQ! z*P*q=pGkJbOQlERjav-u|8^Y?QXb%!;grHZ``E0Ix@V{TmW))B(Eyy`KwP2W%Lq85!oOvmXQ zVV6q7E)g{>mrq*VZo}O*-)z@W^k>V=+V8&Lp)+IOL`3#J&wO?=ydc`_n&{3>S{HWl zhg81i0~}%v^spouq+6iz2S>K2-k|Yd=H`}^HLwm4^xa~ZPsPG_bf@^xY2|Tjpl9+v zGd_veJ@JHykl3XGLtud=xeWx2fKAy;l(O^SqLC<|2npj!u86q`ww%5+us&>>MS=X* zQm%i7z8pYWcSB0aNA>7y7V5A*{_q2frOY}zE9jitik%E~qm8kCT&?Zuie*HoM$yo| zVUXF+#mt48PjAF{Apd0aBAD1!|F`eQxt%Z-Zb<2$wLMtG2g1vgFQK`ua9)HUQ%VZq z_wW&Y*Yaxc$NMQA)DdlJ1wgRT~yqdpUQ9^3pByubGO9MC%A%y#4{e=190 zN~03{$vzm@x7R==xNo~&Kb+mPlQDBoegXGx^j;BESDG{C zb-;yb4YU1e`PeJ!YgB}9KZH+$kV#IqmS z*tVCd#B=it`^t=_MKw_3XZ}E?blI;wkw6Z*ixhi75|HwHCOgdtZKLuZ$7u=2kh=*` z#Zj# zN%{Z0ya#1T;D{C!(qId9`x}lu=pFNy)vjs*FK@_8SpQT@6#v&w)7h`BYWZRRK6RAK zMqM~9)@PRVkGtTdfgI`Qew#nxB_Q5^i`~54$!K%fgAGJ9Ja`2a)N_J{uDF+r6=FUZ(v(oHI83j69CP%yAZ1 zi`T5tgR10LJn7O%fEge2KK(Z#R~JYjp(~e9Tu4rX6Zn`(ZRqGAKOb^ev<=_i>ex3H zkKCF8!`zhUz||xF9a9^OB}zyHuK1on+J6<4O7f01+W334M0@ofe9J&rjdBuXlCd&Io9a7MR1B;I=(Y1iBeb)p{a;{VFYc z^hBWXp#v0s?zlu$L4*1Jv^EHbsiu^JL>`LrR${7F1>8L?R~g+$zW${y2QP&n*o+T^ zh_kZqPwREJ$;X8p8N`W72W{ZOfoi&<&mV6p0Zd^{DrvQ176!5#O;FV zLuMa?3B|ecUhh?A7N5jxZc|P*wU*wRVhEwxkNwrC_2B8K0KW+diD3pC{yjb*?+-fF zxoa6E=dVK4_jz;k@;DJccX$;sco)wMqV`O`Ny1KO=c zHktsg2?r{1yevLf6PusS){T7vAB?EHA5hnY( zEY%h!Ya)({-~O(&P>tvz@ag8D-5v((ZQ1ngZ=`cfk=JKqp>Y6gpmJ}@Y!$S6B?;+A z04tpJsT+7mb907=7_1st)r1A+A_t5su6tq^Z;N$f-%YJj+)UPTDo8$0tN9u*b^Bh)lHx7E((l+@u5K|82cw>Mi<0Wh$ zN~^0<1Fje;j-LH9Oc4WpCm`<7$Ytdp18iG^{9C3IM-Saj7(0B^ima;Tt7~zhBa>cp zo$?^*Ke&CcR;b+h7oimKVk0IFA0_*4pZTttXS;b=S^9cZ>b0jQA0$VXb=2f4K^2Eg z4x6OL?lw56HMUa^R?e~c=KuJx6$Sy!r>2K~u-gYl+~&rN&`uIjF3(pe)vmr`&Adtx zUZiPzZLH6x$MV9Ydw;V4(CAO9`T6EbRNhe`3ZJc)`DSYXqd->H)O0Dp$z~v+=J~?W zCR^mTE#_0~IVgfg>x>B02lw#9;&0@n9&c}HpEea zvlfHREjVr<`>F05p#Wlqnu|s4us0V-bxdqRuz}H@)s>}^vF47KV>he*6m(&s+J6U_ zdAQYQ24vb%&j1hndee3fC;W@-(Xwfq!G;ggj86qO3|A$!%m&lDG}JD7tc6Q3cU|Ya z0#e9Tkne$^(!AFL^z^}-v-q7LPrSRZ0WDORGqaH3(*_4|+*o9%D~GB*y4b?ku27b;3HQ!*B(59nKJ471htSw-k{5fM)215G1=(t(u5 z#qu80%6n%Egf9b}3#h%VHG^*1e%^^-IA!OULqyP;Fd~Hbv7PV`>uskv#WI{5%sMm7 zckNqd1jyi!b)d`7ED(a|ki0(E6EvmizyIWkm_@$So#;5WF$vr!F>9gNKk(X;xIB_YL}bwq!rf)SgP*Je_cAk!AcyGb8=@4sA@GxopZlUhwkvv4AMu4J z|JJ9CTMycGc~Tu8*k7_?ohit%@BU*|bmE+t>*Df1Jo(J&^;^Rt&yv1QW`6yYW$O1Y zUXr`{h)&$-P4$%Ync9`T$i|bql;yMa`tp6sxZqE6q5eTnIw{YUM(-OJ5sf#4i6G~=+TXRs9OUrZOOc$~HQ zlac&#*?j3IgR@Sr`&}ZgaTC_GY803AmSSE_!LTRG(No$OkKZcfCLHNBVrM&F9-@St zewfWVe<#8Gj~=NBrypl7|M1~`VTy}3fnL0+*A!;c-+75l{Yua#)nhTKlFe&9`)1cA zD_=R)?Mg(x`(5^?PYry7fzHLZlkC0q;9{-v3`QEs=0>@u`44>hvr-A)_UM_{MaX09wm7g4&i3|j;LAzC~aSyHa`B&JvI74eG1Qbs$TX#+*PSKamM@eDfn zm}@W_vT8r|UhN=md>Oq##)Onr)GMUnMLxdu6c3!5Lf6kN6F=R2K`TEdqKS*RQ(Ij* zH>E^iUI96}wAw&TuqFO>tM|h5=Oc`|%N!-RZq*Bbwt}h74_Jvl)Pt9Vha|O-;7JTM z2r(ORDG(^GHQuxBJmb`}=r97)Z)}^jX5y2}cl4frOiRUl^`OpdiSGv@UShm{o5Gby zcq!;e+}z4@U}cCsardQ<&*v4%^BWNY_Z3LI6m>l8dNkuhhR!>t(vpKD(b7?Br&(Ij z=}2F^gTq7DW4N4de2(;pKj75r3J?%c=H_zeN$6k+gK>1vNWuH$mSk#IJf6-c0s?UK z9Hn`KcVv33 zoi^Ox^M-LowtjiDWJ4uJHE3m`c(yn4VALl?nUNw2g#Kc;I;_k^_N$t~6u2PjL245c=F-belI9|%5LUTdLN(z<~o!kOG)vly3prHg9J$y59tXh&)>cKaS9w< zZXo*A>5&5D+TBH*l?rY1mKyfgH-FUAu^Icce5AnOnT`^Vz_&z^e9$_E^EeIL}(`9l&kbMIg@ZI?)okQq8vHA;(EDn`9{hQ;cH}s{}1{K~CiHJIhw#GNQC$ z_=Wi@n~bIZhlc4OstAC_jrM?0GlQmg5oXFG{+(`Ul%X8z{0hAxFu8l~&y9KZx3T9h zUqZY`5l(%wge+YUygB|jI93BrJt#%Jj0Y+ZMkP}md7~~RNGX>X;YXe=A34TNRdjVE zA?Xm(XP*Jv8Xmt8|QY2BzAH+HvuYPE|cRf6A9FNE__7iun2V{bayR8 z#q$ZO(}!G{-EH!PR@d-{3_RD#cI4DqYj2Kh#@t&PyIlvTYbT`7Qya6jtU2|} zZ5M&QxQW#62shX+$SlS>|>H}<6F#Lbz&+S}Pd&ADbWo4$3lqzj~{jS9EjF^`ANh;7DQ znAoV6S;2^VWmPrB$aU>o{1G@2!xcQ(3K5|3hZm(?*`r#jZ_xqu21 z6LK8GqJZk!AWcAS*?3$4J~T|mDdL|XS>HsuC;u54W-+XhoOt{ zia|)nP57Ad{jX!2IA!L8msh>mTA#44FXNFbP{oCeKd_V{h=;AV0lULsvSngvNt9kv zZ@2P23zi50uJKbO<8*wEs1%e_dHwY@+~FSvU`1QZRaBad*+Kq@p&9_M1Yzwugvg^ZBJc1(9DKnR2hN$}As>ag`J20eJ47$%r1wQ{aM&L8A8+y{?XVVZPRSB8 zlk00xc20Rcvl}Ov!-qd;pkVswQOfJlMAyyDpn^Aj1qDB@SG7N>pd2}UwDID-^<|^I z9$^AAKX`}7znYGaK_5#m!Q2s-?HN(Jw~;VM#_PkAJl;NBTK;TFn2zQw7Kk^Yh5+b7 zFXx0M!GVp$>wWAge=rWjUft0LvbSI+m%G63rHI#B`ESD))*`#~?G&6IeNS2bgzCtq z6o`K|YZd?FtE)Z8s9X$>+fG?a<^sE6vab>|EmqVvfuBYgpP^_fm0n>W@9C?MU%=N%$k#o02Q z7_lhhv|9odk4IjgSJ{LwJjS0=?zRyVYN7A0+`)PFC)nG&U{^r_@C=jD2pN5-V<;ui zB!%GEhb{*^9csQUd}Y$ZAE-{9YcEz@oPIscmG6q-*bq+MAOrPPt*C4=_`=tw4^;_4 zKJ9HMB>6_x?7PtAtJ5i~7c2hbW};Ovhy{9EY7m(Mp1)3m7GF+2;zHhRgL-h8ndG$-;fm)EwOuFP zUw6UhJ^A==N^6bF$jfunD>D1qMr@z%m1oxUl?7Q?W6f#?j>1E2jG@^7c>&tGPIGs=$qb+JV{Th7A1b*-!yqY2 zc34+$$}Ol7Ah&m3RGyo0Ng@RD-~z7{S+2V9QBWy3&;)4HO0Dp7Z0Z zh?Je0NwY!o-nEh!59qqBOa3b0M@__$>gc-VR}Y^aT&1{u;zgc7+Gn`saFl3(R~L$4 z1|u2-%5{j&U;a^sE<{0|;xQ6f>fhI^s+2OQW9?njiu=8;=l>nN1nw;dgi!nlcj9X& zoEnZnA>+jqQ78*84n?N*bwfoEALD)8NIoa%6mh?@%Pa$K?=vp~bRKdL$lC3=g*y|Y z0h5lAA_k^5?x_!sOt9mK2MS{kYOSo=MaSGNSg(J!bEm$ARiJ zRw`xg<{c0<)Y~ijS4f3l{`A&zbhO1l8u8lm<}<5E($5B(z-E_adX{a5z-N))*>W%E zZJwBGU`RTTcPjEZ%YxoT3!cBUy!BFPB(k~Bswm!+ZTf(=n#nQ-c9&Gh7 zq`JgRGkDjydHCA^WTt9t<@gW%aO*lRhQrr4)#n#;Kxz>9eLgu0H8*>+Mzc=<@zj%! z4RG%zsU~$xex}{s+@YGZ?%5sgIA=?8%e`t1%cAYzH(}0Wi@~{jS&yO|4(6lO!%=Hq zI9Z?!CkvHgI=HHC0lEp$9_(}m@06I+)2ERxMbtSQ^pSq6HsA9~GFpATPrY zLSR?6u0?54A8_}?PyA5L7yM*(e;ejgFw7~MmP70nZdY2l$-sf@4pO2| z@?gNYD5cf&{ZY@=U;6KOqIL`EILS^(t~eB4svXe7C3MohTv+*mxJU_-mJJlzF$u-# z{f@Z;Z_YBL*gJ-5q5XC~k~x9) z)t*fbXjty7{DC?c(^?aGC0z1HwYV{^?_J!cuwL@cl0HuYLa3!DV#J*RlL7l_vb^t1$P@0`l;Dg< zUB5)^IKLT_OIj@5>Hx)28D;SZ{T4Z%1gwh&({rWtO4Nz5wJ5lmp3INQikTR@M7a|J z#)+$+SHXlFeV;t_f?=iKy~}J@nli`S$f-WMbq$>5qIyp;CP2q?yMHH%U~d`3E6gd= z-C`*?Q3eM>J_vf`ek+~6HoL!Jeycr06_q5CS9jkl?!_q%-QWT-y#sfPtRwkxwe6U^ ze{Y0q?hsPdyPd9vSPea9IiPmp+e~y7YnG<0pPSFVe>ag|L-ZC4ox(!KbL`Y+cZfOG38L1#{#QsJV-vokMw3GI3henvba zA;0e>{>-=7y~8feh@t>XjDPeWDI@MEn`)BS^r|!b=77c z`1;>zA`bwdk)PZQz463H$b!`$e9Ti123lefH$m@q5Q`}<(g$Y<#myi-H^q;jUu@2Q zBX{--p}U5Ca};({A6t_VK>h&E@;EF*pO@(iq~4y>`ttEO?24)8A-mnV@ImHj{L!P3 zTPLx&GM=gG-|h>N?QL2v$${zDxu?Q$9jNuoi*NdrkJn)8&*kwZy$-PXnw<0-`h9>( z{Rk{YIV$+MK*(JUOZDpA)f!v~nB(o=`IzuLjV#pog>$(Et0+Z|?xQC1IsGRXn)mt- zl7ZXa3hW$`Yna(#6qubfTWjB%+QvRLu7s%S5?a2B^S`%Shtx)BFAwJt}FH%P^__%Z+M*+6ZlE_|K%~PUeVwstp(an*>IVc zaGn&(NT7n9{7DK9nv3bP=vY;|W3o?&Hi#kGf6m_Q-p(AO6oTbIX2AT{ir3WH{-XZYsHpv5Ui~L8Q66x5# z^UE!oK>hU>Z{!1xq5)PHss=9g_=1SmFXl4f1T6vtnl%a@1SVXpp0szm`+*wAU!Zm# z?jRw5_IDnFJ@(x~Mv`sVyC~vhWepq$c7r(n8-RjJ)cNMlW`2KPXvJY~r z@DInCRLb0|B8H!L1azhkuLecvTSyPu2m;n>Ir9bg)=d zc+ZD8r(Mt@ACo&(@u8s@2E62AG?)jJVY_eG)xBc$y2ia*5B}7Q{gFWI1Jhsj0SxWu`wd(~& z7$f)buRQYRgp(-_muDU}pOt9qw*4Xh%*ods7(uL`lju6Z8#LO}56Dwe78u59HnSxl zWGDZh+{f0Pc>l47iawwiV!ofZ1G1;p?%5@B0}HeO!R(_mKNOfA7x)~F_8zvM1UYzc zfO5#gaeryAcIqW@#EVOxme$P8%z%{oH^Xm+Lq5Vg)G$F$?+?x;zQ6=Iz53<-TpVPL zXQzoEdYeb&xU4pimRhm}rJ8GQ_V!Mhb^~VpdZej;zS}p99|5`kdUJLfPaOw`9_}AyYgPNzg2Hk+2_MJJ;VgYE?f_Vuv_XI&KJAZ6zvhsx!xQNkc=2)kcgfe%* zsR|E%@H24fkmLfUVCA4VN1RW> z&$&f&=77{Bg{??e-%woa)#pTu5W(@BExyAcY6G(9HsXdJ@2-U(AL<8w6v z<2V-|ZOS<8HwzDsF?K+XHw4muMBx>gf6H1n^}m;T6qUln#h7a0#?>`}Qe=id?&EOi zo>$lFTh_0c=UimU7&NC{bw-=>bGcE$xu>%*7hsHowA~`;)Q!2JGa>y3+R4N!Et5YV~&XN zKK6yl6f!8~j2e?{u6nS8oBhGdoyPoK3clLVwi*u6uO)!aFF#C9>pQh@6?*AAaRSvR zLbH95DJ;0*dC=qWX@Dp*-&7xAKhO~K`trHbE6+H2P)E|t@5i^MTY4S*Lf%PxcFYe;}`oMYo zYWJVrrt1QDL{1s$G7|wRI##^Q=SmQ*DiH}Fm&l|9-%9d{%9N=wwM$fHiL{`NM1*rQB4NNmbq13B{QB(H#lAn z*%H3{K}je_oeRht(bXF~K1+bJO6&%23LTi}iG%M@HG7m6^o60Z2mLpGL+1l8c*tk_ z0tlsxb_z(14pCdX6&}@J;n_NpfjKYCJZWTwqF6W12U7N=j?nm?WDS}`TU1{cA37z1 z>^uyFOldjK`E#B3x`a$9a9-q78lBA;Bw)cWEQH%g&lp@AaNn-ois5FL)p|`nQ{=#a}z$Uz}eK06hroDOw68HJt8;c*i>$>15>VDf{}tQe?pdA`DCWM^35Qrw=a54_xr)Gy3Rml;l>Aqn*nJ%P$e(YCdW>hIAz_{w(iHOj;eGqYnD z`a(+qr#g$759Ef|f16$TIi{ZNeD$IzMdvS{q`zNRoXT;Zo6JT+^M6-wrtM+oG$I`1ihO6y$t*> zaj{-(?GT0+cc3>vf&EVKdPz3;?@<{R#xWy<7dsjAJ=PN|PMo^|*ZADeO1a-x2PB{; zbs~X{d%kQE$?s?$m{e|Nk*ZK+8hH=soqnZ48C8huN3&mNWrpF%kJNes3)If_E6fD& zL&I!F;}7{r_<64H#E&PqeAEpvysN*?jk45RceC z@(GN5)rsUKL6e;$@_g9Wf4J~*JZfBbPJnL^Kb`SZ1;_2oobgE!Mym7^NMz%GujjSN z{J82>nt=?(A7>zmXc$1Te5~PeMvPFo37x|kH{m6HI9`rp{xJWi%aCkuda*@QP=+~0 zT$Xf@BW=ZZju|-Fb*qr8+rlQ>+d?uB%6h@;oZh0D`BKJsGy*RKbg=i{3LVFbq@u0G zJj~uQCCKPeO7mWbc(v~gKeE6xNaarGfER+lL^T&)Ox}J5%)!jYouJGCqG~cP^3hvQ zt@ja-4Kob0I0a_O0EaE{eUy#gzrnqO=L`8jz;BKx`7^lDgX)F=;>wMaa2qdmoUoXG zrwx<((gZmL8SeqoCuMmPRKVSU_nlgr@p)&%c(Zw)cFy;47zNr(6*7Kdr+vy=%v$Z? z^ke3p6h#N!y+F)`!>AOog%1e9aF8|S@TtK1Hf+TDbkpcsX;8LU8OT{2$Xf;#3c!fH z#7%t_%#DbG3|hT9_Fj0Q!q3e0$Lld4g9jialzr*t-6?Qs$*kSG0KYF*rP*m~PT5j~ zFFZ!Zx+z-p_(hehYNzefUW;p=+bkzHP>EW(y{R!LiLB=l&!%6!WmsuhX)Z>@62>g} zKVS=ZWUigEa$VUmiXDQYf{ud?`xM+2%dKB~v8i)Ipjqfi;zN;K(-6_J{m#CHn%~eC3<&Wn+UK`Mbd8Ot32{c6FOLo`B<|13fY2uyrIcnk(5&E( zS!*rM_13<^gSzJ}AObh#C^j=|CP42#-9ZLK zdtZ)*aY8fj8-EC#yCjf%>CFlpR;Bt(TTjk zc;9e~PfN!=0UxhK*T!9+?c`L(jaq<8Z8#E|eIl4MV87|>7hbr)a9~$IL=^g6_vX)9 z~@aIPQ`*t%WH!0o^qk@_Ls%=TRU;v_N`=g0UB)99OK8*wb|KTeH zdH%Wyr=D~|zZgjOB8UTaM#LNe=v4)~XRXsGGId+(D1gMVImOEL7Uo}K2gWa*h=EA; z`v<*+#2_s`l&2;-Ys=8#1~_E_(_sDm8bKvLi7k{eotep6%zAVNb3akPDoS3o z^nl6huF&{;1}7&`^TqUe`&lUQAXvnMjqHc^Da?!)+@$&zz8r}L(Yp|Bz5~o})!U20 zkCdp&WuSR$bX*2Be6T&q-+HMqxVcg=|sz)kKE{V--}V79|23B<;J zWH9w}=%=#Kt8+IVX}E7^<8k80_;r$w9kRT2bNs@gyjlAlqBT?zJI-FHX*hkBUF{C^cJOt0_;&ZkKJ@(o5$f^X*1+o(99qjX4u8`r8+|Lg*%> zMCNM<_%n#eDUNRRt%izw?4`N#a2&L`nvlKwVl8!LmHzNj9)G~5o|80Ldd75s!@8qI zXi>}|4wXrfzuo%esJcP$h2Y50P*X1ODPTwX!uuD5obQ&OPo$6f~MsEbTGX;^=_%5X!yTH$uR9k9u>X;fLA_R6jL?WDo>I@%q2u(OCxt&8z~DMFTn6)HrN0;%SjM~ zc+`0zHXu$KfH2I21b`y|JQV1h!m*O&WcbZ=1d8jMk2NVLqvWSPoMhALHJz7xqM*O( z93G)v#Y{L7t9e}zp0@115&20Wc`vZ^1Ai_dbq=Bm3x^(SV&5a`TBOF|k1lfQ@;v(5 z`ad&Kj<0^)F^A}?;2Ky8Rixdt#zh!zLeyg*3K1!SNeFt?ln3}q{0BE-LWb@gq8g=O z{<%D5iXS?jm0M7y9QYD5E!E1k08)2TjVr zv;Y@=98Gi|+=B{`H1JAoe1@gHm<%v^O;Fkm%TD=@4mh34^hicn+spEDsOINimuAK8 zmqV1EKc3fJ;fKeXY_KQurj7ALU6i#T{8rotQ{bpV+;g(#GjxwDzOHKLZzbQnp@NlE!-drn!v!n zhbSAoB^Gw7{^{)9oZvg?{=;|{b#xxkkHk~1fH za$DqMeuw1RDXtvZ)n`@9XzCYN_%I;qpxE|l#)Ape zKzuB6f>VEoKpGQ-g-mqoF?Ip`p|^0!$3R@3rTtteKiT4mYEV5Ju@m$9$IE!~6O8C} z^l6XOqZ|LNksx!3c25?x=9)uV!t`aUuJuuWpx-$F?N9Yl`^e4GR!$pGkAml7`c2>* zt08e-qENv|C7i;4o;vV4{swfK17~9hl<@12B!)(qs(pNNj(p_8HqR+d3~;*CkL-BH(VJz!R7cb3?@k4t~Lx=n!=1fDZ%bedl~_dN7mPUa`gj(4sery-wT5Yb(L^tB1Fx6`|8$WgqZE7xCkACYf&} zWR@6@R7pv?)Fcz+Z^TT1erT@YIe>y_UjK=bI>Gy_?>G(bI%Ld63>a}yLisnVg`mm{t(Ri)>dlPN84w9PKeGFqSlL)JpZr55q6m|i)V~&l zQd6YvNi>(gH%lCE}M?rG6v+LTsShbU{uANQW||ErFiE{7orv9&A=WC*q+DnI z^@sZ?pK&Bo*h$W_lULODd#o`1PY;nt=KOn9oA!1fIzr@=tiw3v(Qx74U(w6gnwQK~ zB_h4=v1^qh_%cm=R@#2WoscqH;Cd}v>L~|thl^`gOqus00qt}dkn~X+y{1HHi5*X$ zdFPB~bg@IMLbE7Q8lqu=9UiX|6^l|30()U2HAqmbq1bRd!x*pM5J>yo`J1^@iX~!7 zb)XjRTqWs8zbG{?GhI2lufi{amphLFY$$Z?A@)o@_-FFZ$6lV654t&@A+--Or%}f4 zmr>z8lEm4=A(|yrVL;s4hcVU0z5XfZ$Air0%ZwrRiW=?V-6IF zXB$%H;oJQprXaK6MRVzb!Q(33p-di98jm5evsoSfHy()`I>7Bb>)SSY>5x?iwE5S; znm||p?-x}Cp%5NgZZ!~WLfAx7IdFio=Kp>7E*H#tTCDj%Nd&jJNmbB?=wq;%Xiks6 zt2OV&_eqa=pyQYbT^_X1eETY?oytXbuod}-4s5tfjSFFZbu&s#CyC4Zpk2$RNpdT3 z@^RPq$XcOYPO56{AthY`w|`LEczfK8!a9=Ax7~TCvy=S@u}^7Awyfh7snwf6QO7AV z{BWs2fEcXn#ym$r2gQJ4EC($<1$9xj8%@%Chph={QZI1QvMU8oksCLoSH7_SKMSxV zL!~iRTM_P$FrQl0rybD%+yFWDW$p_j2@eFN9 z>Cn;1}ghK^6IVsg$w z_sItOPc3KNb`zbW_!{IW_s>0#niSOfd8okpTB3pg+HrzsSxb@4`nRPPx~Aabk-jS} zNB6$|1f-KiUHkYlM3m2QY<1={I;z;SDdz!pPi0kS-MIdbYgiNVf!`*3!#v7`C3 zri&aEtXD)?PM?L-P*k?|-jq+kfzMFEx9tj=b{I65zYV`C3LDjI1urv4h3r3KXwh&! zH;)pj{`|v#5T2jq117H+JQR;XQ2A&Y%VxqWV+%PH!%3@}E%a6$#Qo zL9IRLL=tI%;kpOhhXxeZ7_9-t$Z<{uq*O`nQ1^u^EWpo)oZZCDQc- znGK>q_|np=-(74DKss@v<}9e6RZ#U-RS=rm8tnj>5fYtC$3(Q?VKmK_p@FJ^QI>S+ zdFYlHRRs!f#0)gLijrp0IwSZ^zeip%y?b<5qlMvr+rBn$o>1hvMDFd5pQzBPo>V{T z%&QUlKzGy~TVEuT{IcRpxk-cHKkfG;iP6xN$PACsr{^tUDPu`RC5qzD_6sYLm+!ED zRZY9Elo`UN&#AvDCdYHp7_NyGfOn=2ra+AkNN^6eXy^cy65NRrorgOYctDnYTx=B_7}DbD zk&xTv*Y}Y&2kLIMiVl`r?O~+oz#%);N|~39f${EZC%As_*xmZ_#{tF?6#;6A=A6IDk^rqew!= zJcKGx%tJ2Yre>}9!tKNy97%{X?$m^%M8IWHAx%((>Fc0yDjOSZHqZRxcG0Ty+ngwC z!hqKe6Mp_$2C(liM8wiK1YwC-6cOKjZFY2EdC35Nj!w*l$L6Z%-mmU77H_yTRz+gz z%XE6{(8W<%+(4t6C`X8tHf#+Ac`)T9840DF^nG9h?F7LJWPw3==}K0c3VXLhCwX*N zKrUyW5Y$aKsr*h5cg8Di4DNSN?Vo=5XES}7)qR`e7M0=1h#n7<;PT=M8D$?utHPcDq{o&{34$~$2s#FP6xk$fQy;$(%+c<15M8k8pT?r|w zH$7rVYZrkSQpL!a=L>Nv6>?O@k@?YSPLNXt%O89bPyUQW4oQKBpub6JnKIkisPfQ_ zz1jD3`E`Bm$D(5xx+Mu`=13XllJsX%BM|?<*M$Xjg$3cbOJ(hr4&TI+MB8la9X>3r z7T00|B<>>*Oc~9b#y=C(Btp&wwnA(~Qu1d>$fuV`%2`AT+RRu(GGU%L}xoN!QtgWK3hy0d1v1o z6vIa~I02J3?{**~p{z7>Aogi8eMxa2IBz-g(eu@16svuK!aX+J#Z)RP&wNf3IQ+m? z0aINIhL;?A!jrP1MIRp0aMo>!e?v)+jOlP1RF>l=U0Lw|;~sZ#;sF(QdV@j3-9{ou zCO4;fP`JJ1(9SM|h@Qtv!HUdt?9@w5v&jAs1dpRVi<)7FKLdSkQcxx{BQR2!^<6-U zNf^)8+mW#K71^^p_M-IUD9A6(rS?wloj|BB&y~8pbOUCEr#0nnakWLw_@&H3Dw)&* z1#tT`Y97N=`i($MZBdq}vG-|EDdQI2`x=DE^MDRio*zksdDH?N5kA`S!Q`c=KP40$ zu!ZJ^d7&J(m_08K)Pkkn!R%nvs1t)S?*lF!(eOvz;2<0F=s!otzJ)EGl%n*p)vJz7V~^Y4lA>fLO>{*gn~b4LytMB-ay5a!f9_Rgp;%Z zrF=Pc$4?FT3#)Vh$rJpv-zzhLy4g0~#^qdscqKZLj!FCoI}~%nZ>SLl*W^LBm^TSKqMrrHx2Bq%&RbEZy0phZd z`)due8e9m!omhhMuY$(RA&Dm(&&%R+C5e^n^v0xz88Lm+Fa=1XiCH`*pQ6Tx6S2`PP zwwneXIe#T{;45%y28uL3S`@P*}011G)hkiy$xff0Nx^_Z}2qb1(aJE+@Qo{ znugAHWg6a5TiL!;^)3_+iVBL|LiAM4$rpbvJJ2JK2a%junN-bo$M0P+?2m@Py>oJ~XVI*YR~_>)Y3cYipCbck!l!>}RV9Rd(y>FVLCH948Mf zTL;N25iJ@5J~&1UoO(`LUq~ro_>c8I1b!tq0hQ|qYjsT=H0_l1WXYuui6-)*xsJj< z#%K1xG3kq2_B2p){>l{;G51_Wvv(>U&9#dt^CbMP^g(9KLmlM+wF@~bO0=iQ zKp|cKf%7x5M{NOf8b>P&N72|I?*!d6updxAGWf_`DFml%2#&Sve)S*OOFI~ zlo44D4RH|5${w<|HynMk&0PgajmHOWbG9^Obm~g@0IfZ2IyP(&Mk_TLTpA1(U^PGJ z?4(cL2kX-5O_#oVxT08zP4H&KrGyH|)kyGz;Oq-7F06_$RsfFr4eUe-2S6v#sC5;l z{{6y+BJdOD!Rp=?C^I`8S6U5H+|c{4U%a4l-RlT4=u1*_XdZ4aVc_p`$%o#)U_dP0 z4m~93`Rn+k|8_^^80yfP-m53Wr|!3=2&A11#VX$PcgJWca9^1J&?fMi(#&M;WF+$k7f=NBbnG6+yG1!ZxCXWn$3H+UtkGlv@CKq= z!kV1@GuiO&xA!#o7&@SI1yKx91m)~5H3}}J!&-J<({lydovBwo5J|CE@a}*8r zPXKtJ-9%YDLVaOe$d@o=UC$cC>w%$hBCLv&g>Y~WNXOH%+@TR|ksKFD$F#O2{s(Mf zOrl>m#{;vNo}Cxwf2Xc1?z4J?=;r3-6rFUXoVz#Jami-NqYhmf3a@*6-vTAmRy>hseKVGNbP?hO= zU+vxG^Yp(LCN;+E3m=za6!c(jqjdQ{=&K(na|BJSE_V`PEJ7u&H;V7`v^K zvAFM&T0S*fWT1EZX{X+L^gpfBd6qp2mUK+b%f_j~PXa9cV#TZ{(Ep$r+M_t%a)1&?MIt#P5!8GQdpJKI206Fm- zUS``+;|2(;)SxVes!!_LXY%Rx3#3&5cK*dwv;;>ul=Og*%TCg?%Nr)$FyO3H}DxTU*|=XzSjPkQmzIy=MD-$ zo@MXrw!?z@{LjIkJR9CfVOj95W9ZV>Z^#q@1FNvE`GrT))FhaUA)0}yI~wrIG@xKN#yxx~&_sF)r=5t2~xqdR^#4~JH;(|ERH!+)+63O$*68=k7Q?3>;r zzX|VKPv8@~b7`%FrNBtXkmYyUkPGaAvTuRx0%gw$D15I6atoI;^)TufTBG?X?^L7{ zo+#)!`Q<|fFm6g9tJsC}(MX(7El$Hob>3uWs^KsBG#60aACzXkm29Kg^<41_-m zpSRzBx$VE80~C*e8~mgg$R;<3UmR5N(4Hqr$-zo5IgT^_Mc?9PwnqNT4(&_*aPsM| zvr8FOwWQ{QhqQgv=o)6jq8vC{@*ud4BRH7^0pq*V#_Tzv9FWFTS$oe9$)k%ex}4_LDFrBXm8?J=C$H0 zS(uj55FWdx5l&%n7~~bg`x#bltZlDbb}c7;tm!sHpR;7)dhDjzFYL+06h0kb)uc`n)s$$L>{et4! zuxvw!s?`VmOjZ%|Od;hdqu=l5!4O!AG&Xl5>Ix~S_TBeJe~8dm%VLp8vl&bN!4L!B zXpThn?eas1-WL|pc$zfN0)?(FKU8T*<9t!AQgc?|WUo?2c-L>ypYT4@doy3lSN^dFDVN)ov)y zl6Kb!geEexa7sPnjf|_RwR^ftO-TH$ie9ER~wx9$1Dorf=b)RgS*ap}o5S#eI5b&|&*@)#I6| zJrwMJycH>w5vGJU2ME9)S^+oX{Z)ORd%&>U@naS;H}h%oqb@kcknX-_7lsm&8Mq4R zpZ|88ImkntcW@UieG24vhfm;h?%x>`2ll2dR|X^Zt9`um>mF#R)u8#V>^AmJXRFCj z*>&O39xxnfyqA|=royg4hMk3Tx`DMesAMfO0+v_aJwOw*qxgxm7o0eGci6&-sd8aL z!1~Cpm6wWsCQ{zEY4zfQVtVkW(F;o7+*da)dro;0a(o@U#I~W2%!jAIUCExk`+*OR zJbH|?2ZW52$fK0>h*3c9f|-f)K~p(&$j{-f3ymn7`Ht69?Q72`&yvP94}f?~42kyl zSrT$q9SH93O`&X)u9E(t`0Z9v*es!&_c2))@zh&NQJmSFcvb2haqyIbXE-jAC>a^W!!CP& zo${0=rhRt;<0%1(kfJ|Jx8)LdfU&Tr;Y3HfgfXIbS0sF@;PuY8(PNB~ix@&)Q=sEz zbB4{6+rPc^kqxm6s)pV|R}OaMMyZU=*{&dkv6y@on&8Vld6<*3|L;ZM*y;J@!^jaK zqS0pIk50YnpX2oTtcO`rLD+0<2p+x;_L$?~#?YKCWN4CvPNez$3k3=`QPmNQ z<5J^Qg!ACUU(RnJ|F5&<&&I1t45nRGM=3FLiHAQtCwXK1+Q`?ZzdijE{2XOM9LQMY zl<7f-lxJ~uoRYwbv!+D}^;MdzZF(4$n4rydCJNQyX{Fb|bg5CIy_UAp{WsO>PjMPv z3>AidZ*Ad#4E)y@24L+jkyU}K7byJ$SR5u#fQzOMF^3+Qj_2>=qQ<^$cm!2{adzNf zg#V3{9Ey*ktOo7AXvI%I#FL8JD5p@(aPtC0m-gg_A0c+#pB3&pL3d{CddK9R=TD^M{sB8;XA^D32v_VQWysRwkHMty_R}g0{c~YEpOuL~;{rzpD7!u`4VDA1+cpzp4=I3S`G@kyx zqn{%&hfwCMfjP#JyyhYc0?NsfU_1qBW#J|S;Y^Iv*67VK?S@ihd^V!pwqNJHd{#*= z8e8mJ!8O+0C%(^Mv}4UQb@*=f*`@%lJ-0E@7!)y&VW}YYSvAVdbNW6j`gw?Fh2$w! z!A55#nIjJE2QwHAlRF8n4pY7ycGzoR1G7o#H|A-pM(u$-?^;OF&FqoZYgd_HY8$8K z0!vZGRy?%$PN|FW0~f6|^dzGPqpv5O3y&Y{I)1!vwSRT;#<+)gczL6hTWQ|)QO5?+ z9PxWOy`}5YCj|+`XI}D>9vuRL7;1;H(Ok!todGiIWFW5m92y7!EM_waatxKD;vwrc z)X3T2$RixhU5wg+2PYs#-2@+Oe+Wyf0=%94X>ss;-EL`#%SoWDo3r`-zAd>h_j%QS zC{mM=O)bXS0J^w!cXUp3kRiKTV%`wJUPsY)T;KQ`QSOzM5!ibG3NQWZFSkUw+!%H7 z*>25Cc@~q_(nv*Z#Vy8A<;GT*<3WNINb}aT4j$?l>|xa>FabL^bR?@Nk^IUa09||& z9q{l!py#%w7z_5`>6N25ZN9Jbmf$8St)R{H(#~$@-`aaEvyyWnf8b>Ek-XToEfx>6 zKYaD-qq`9&BiA;Pc9u4ts_}emGqv_t6+{tRIPML68klIMGZijDSMJ;%lYpTDu*O3( zH;01t42*fSV3n{C>_N}~krqcW;UDle}T?>idzGM2d3W~PvfBu zzz2@v?Ko7jYA#qGu6mznN!lj_AHmK)M=N2-653F+gFXtB?|TCl6EUI$(6}iq2}d7+ zDZXfW4+m{lgNkH#QnH2Em zjcMtL`%kZ!W*}GpS^(0Ub%d1Mgm!m54+r*J=zYsKD&P>|HkN*v2N7-qIUZ8$Y!CB8cY!`X&28Py9nQ{5c_6X0z2K4*9`#!Dc)(3rPu4U` z!%hT&$D!Za5Q}FMAa4cs_GP+wS2;@J<+V|>VF`E=03I>s0 zy4@N;3x;87R}n5ncNa#T!q&*>jcjJ(ppR|FS$FjmLh124;I1k)qQ%1K5J&7qxEsKD zR*AP}$&?5z9tl0I^OS+Dj~t)DCxYJm!C4H3VlTET07h771PrU4*rdFj6j zmW1urd0S^0EcyN!*g=@`Qrr0%-8y~FX0&SIo6aRE?yP`w?YqVh3rmZ+PG0= zB4;o$0I(>4Q6E%@uc1ZDT`k3P#9N8Z3oa#fNlzAl?VIh~AEF^6}h_MR-aT^I7t^YDMHUsu~K>yJ!{!+Ohl zk$#J?LqtF-U>>FKnhm;yHg3=xjhyDquKP<@{EZmrdGnBm`TOTgn#*;+;tCc129B%(a z8YU?{r?Pz@DJT1TLo30m0QXMhVzKAuJ|0phTBHWAqlPS{r;G7>yDH za$ag*6z+*?yNX#mWg%GeSbysN%SKCvKoO!NQs0V$)7!5&z&1J{280NKLDn9XKz@ZY zHb?K4=mNH87!Og2Fbxevsr`0a20}z4a7NQhJ9+j(?dGj9gNL7e!NuK%%-Cz}LxDvM zyW>*FqMx^_Mn0OxH*%V({h~d1O&VzVJX%^_`F8MGiO1_z=TqOFrZ1rS8rVfGE`cj; z`r0lA|HvznB_lAoYA82g5PqvcyRB zPRb&0NGGh_5%||aE)dyJcxTdmt7$-p7$v+b2#C4y7hKv+<6tk`mE8n=Xt-Q&bA9Rs zyqX>F$gXsXHrj3X8A4d8TJo0d(ZCP(5!bY_SVaAY1&pkQTaE8<&BB?fN+_=VTE zB?bZe%2jSs`#5OlCRw63u&DJR!1H%=X*#g^($XwrTCxQ`$Tuxn&Ga~QF~)|JE$HUm z`0PaAL#dONXVCi9Op4$AD6c!ZQ2dmnLX5BoREEtF`||9R>!y7TLnLr?@5#`$A*SzK zL!ms|{jw8o-e1ro7%}2covr|+=o&cFgY*1kaCIpWir7;81DiH>u#wD(zi&Tl<494+ z9^XO=OEKm`IlO3NRelMiqM&@^_0vRb-f~ohV!$80F5y1a!{Ek5S6U|Gb8r8%$&wgu z5<4sw1lD5*jk_!t9{u=PR2ThK6?6GDxx;|>l@kifdLI34x@aqf2{7PdHUxfqhM4n! zRaC(PoBa^{*{-6u*PzyHDCl^~bLyc+G`CR$^8mL)z#2SA{P;qn-w zJ`RItE*+U@gkFKO%tkqY>8MG_Kg1@%em$+SBrY~im@d{^kXEy)qTlpyzf4?38<3qZNJ*4O>(8HqSqnJx_9H4UN*|!?h!4L{Aop!C#Z?_G8|c z)bKW9NE|t%kd30-`Y9ITXq8glTlQ$R47K@IeTjP%eE<33Pgm8 zwA;`=zs3GUOfAk=(AI!oAqDrZ@wcrXl)vt9rrfQtkrSGKe{ZsOYm#omlGy%8)>b=u zS%{;}a&8<-Rwm(#1C0qgN$2*WUWLNL9K@&Wb9oM5%>qJ)7;_JOfhU^gcT>1}rgb>W}zcimNOuk&JXi0fW0!wg07UTtV*;EvEva^YK10%x z)jbVofoSGI)~7k1`w6is#Re!oy6%OK;mM609qo;*&YHJ9I`_E;J(Zg3>n#E1K>Z-8 ziOnnNJZgUV>;n@jGFT@*SrIC~|BzNo8o`PDViTr`2B^1lEp8cFd7K+TkP_Q-Z}Xhb z+V;fM{}{1v&tC8z6FGZ<4A% zHZ^$p)9kmjW#qw?6XF7*=Yn!i=nC?Y@qW!*8ajGcF+L65GsAV@)^~(_1Mn1Ho%Pw# z^YPsHOldhryaooQM;$3FcbG69exH2u*sp5d;D_Zt|DFQIH~pJHo_Lq(0PQxe;!CkC zs1rK>K8I4&s$1U4O98OeNBwiIrIm_W=!m{dJFc2L*`8h~^N4@3DeT#?3Bl+|=ll<; zhFtur^Em>_2611h9rrkwUtnm0NY^odRm`&#Jf&H&`ua2T#epS7^QKsDJ`SETYc9yR zKx6rEa*QtgUyl~bI!8Px&RDQ}Z?c*hs}u=5IpIH`;Gf5l@NUlTYp}h)vBIlEki5p+ zsUGIs8rXK;YXKx1ZG}%+pe0}Z*mpztmjcN@DgmB};J};xI!kj~0Z-e#PuoR?q}lkL z(NmGS5&1K1FRz0`aNp+AQP;}`+`mvdx?!U*nQI^|XPS*qbQ`>e*-4uP)MD zPZDLDH1;GsOE5dG)h(JM7ZB_4Cy@0{TEF#9b#Y_W^hap-ih4S{txXKhm zE$-)O!&9b`4~y$wWO>iY9aO3k_$e+0Z$d$)1YpK6kdvd`bDKQto-jCNvsWbWg&KsQ z>xGYcstQ4X!*|kRAhp*d?EIgu$8lcsj5<$;g0(fG|AC@CKi>yw_uW$8Pion3QSG3n z%(2y1!Aw8@h`0INSK@#4@hcygzo?4z+^DX(kn7s%l6`l$OPx|?B_cwe+Ss>fRHmPM zWh}|MeRl#~O)GE#L%V$^-j3*e{D&m3(P32nvgy#bjL)I!8GNbmec)KwWLi=&PLs?* z_WI-(X=Id=PFq+&N+_C^rb$6>-H|(23V#q6T2h#~Q(F_P{v&qf6R`Z&dg|g ziOwMDw*M`IAxAbn@6C64jlB5anp9R7IqIZvvxV5-Gc14-f9CRBGWg=aT%jyRBm5>tnsqDx0} zkJEZZ@$jJPTH#nLGSoi=t=G+Mf0eRZ4Ve5W_*mVR*U%_Kt+Vr|`=Q3%7nA3Ux;ho~ z%p9c>THCj0X8-(Iyi{CX$0>A1wH|3t)bGS7hVKcqKk3{b*mLmXZnh!GdGkZ9 z;&3k0sOjl-Cj3vigQjN;lWE!MFK!VFrgrUL)_%zEe*wxrIH98f9oyob3X&rxfMDM{TZa2YWv*_0WAD6=3CMo~KZh;c4P&1ZnjI_HjaD#ofnwLn z*{u#)m&zVgxc+SiuL7$^3=2P!OB0nTkhBhprHYd^rWIsIltyExCBy9MYeX*krPDi) z63kJgZ=lE_hg!>PwI(2QkIM2-6pc@tA5e^azwb>xZZcL^x|+dO%j`DyxoTD;|FA+- z`2CXtO_zd;jWwrhbvUbnq%-%C)9dhmf3stvuR8D z+Uyr&tt7XXjk!B-tB6By1}|6rSST6Zf*n_Dv6&stzba4dkzAU)zEsWQ+dp>0zW7zC zW&GP#jc<*eD%yp&^RGP2KEE(M(pLSjH~e-+ZR1J7o6#DQepc>eDc*u*qx0^IHws^W z(zrG|Q@#VP_HiY!$CoA)UwZUmpZy7+*VG2mqG{!pVmbR15AoO!9Xgs&qjg4yny~fc z&c2>6_2p+DKpH>ehit;*it4?Y@_YJ)wOm*X);oYevzx zFG*Z2a$HhUSdXvT{Xn(1!EVgyM%X_g-v-kubG9Cr3s;bhOpwwI0=L=}uWx zYo+ws?H$Ut(1>a8J2MA@gm$H6Uti-cil^NC{_eoanfr%%@nDuSO`LgG5*9Q7`^=z< zFz{Jj%z6=4p$DU+NOI$);>)$Gr z&N|6%7qDm)jh*k-CyHo>P5sp{&pELff#-Fj-emAgMB(eD!#6cD`pW}Ew`88)>h$tZ zi+*l?#~G^@>Wh!6AFsPfQAvFYtnU>Q%+LywaJ~_|m`(vmYr+c*^=`!3wQPFd)cVvQ z!=+)J7yqe(jVDqUvy)(eyW|74Le{lLCl8@69{~dNb)BrCci^SuX`YZO}*)hC%L-iE}6Io`|=X?9h$~gV^YFy z(L^3l7fSA42^dGZZJ5^?&P`O_=4u&em}BNzCGYxC<8DdsNc@8qj;*Uw*Wc~x>S9Pk z5~OL~*tGeocP!k`cCoAOIZWx3##8=Erg^E!C2?!5y0GaO<<5)bMt-U4XDBvuvj%dw z1a8o+-2`9cDoGMY8@8<)DsI8C~ zsv1(MNQBJUxWKqcON3PBXWm?VIwYHnBX~1JC2pmoHKe0#t`cjLQ#M@At7+OWSASh! zGxNLG0v8nSs+sPN?@D{3_RFZ~l&lKqHeGs%p~f9SPViGN!gw}d>gch8)Z=rG*Rw@Q z(K7I0uxTLA#^IyG)#%qA^#_gq9l2)M-sPY@CdF{+d_NZF-0pt=Epkzd7%Z}gS5xX{ zd+&`1Fyp@q{b*K<`~8a4h2p~r``n0o3gpV3|I)@ivo6ICnH4TPS{Hm`*t`BQ zKl9zBYP#M?Qd`PI$~BNV)tKf=M#?s^w_uY_pAoa2*xy-+55>yrBM&r6kM@^G;^6Ml3#tS{;%M*fT8 z366{m{$u{~oK?IZU{?PKBx9_f@pkDMpkK(sflst6TZr!`PGj#G?kUij%;Tv<^?UxU z(ZyTi)`(o*4`9zG{)eiVKe7S@i;A?0sRy4bhsuMlx>TLgSv-pVzI(MpQK>D(mK zmNNqT7LGc~6{Up!h|RC9PB{Qd-oEu(CY{nLsjS#jd(7`cz_0zWJArj!zEUA$rpX7R z8#Q@M-k$IemVaCA6t0*v(KjZ%TJ|hLI5OM4bz`pCkG~(H^TYXgSc7X@a3Jj()8psv zXup>~>BHS4lT{hUnp5*nxazWZtkiL$8v9L~zI(AX-EeGCos8aB9!n#9u04>G6Ox&Go`3;6HBpDvk z=p!&pC`GEqMj?D$_&2C-?~uXM3jvLadp*wPH8+V%NqxO^=12J()&_aK)gw){;zOCX zqr$~2sh@rhsJk*`jq@3GL9#DzpA}A$u=$bEep*QQmGIZsnun^oua2Er&~eUGJ^fKs z^a3Nb(%aYT+02V~46?MU>`xMR$Wyxm1P=B#(!mc1;_)GUu)5OYJxeQ}|?1z?j?~z*i|M7%H@K`>y@?$&k zH_pAZ(&J%YW6|9cQ-&?Wf=8#0ILCfG@$$THiu&z_qKcH74bn_ebY5lme}>PozXBNIm#AiL*LXSPw-M$w%NEh>743Tu!^A7#aJpGR~WI;4E^(6J~7O*>q?zYLgw%Yh}x4!YsXm|=K>;cY(FUsT-_J|L;^Hs>w zg_YsUE(-&%D&kwUf?Mm{Z?^XT^-TXU+a_^PzX$76i%Hx!MYxkHf-BJI_j&ROD%x46 zoV%()!M}R5^)Zyr77F~zuf$y78XSFp>27tSXq@Bn>W12Gsn!^&5OccW2ti}pc90zObOprZ?Zij>H-A1SnqrxL%PWC5?1u&*ciV0x^a7cF6s z|FUTqXI!<1M3k42^1e$N;kIvNMSY_3JViB1n!O_0ryo`GQTwi+8n&5xI_{!-v+ZY* z&90T5(3A9O7O842^*;I3k+{v0y06V#Kg{aVCSLM;EhK(9ZG5u#Nd8R;{on)s&n5S~ zE{oeRi+b>K*}+#z*Z-ekXhYwph-9rnw)X1t*Ja-R^b}Bfxo-0*#Ope(K0;@8Oosd* zwWrMH@OE~~=%~Bn`j4MI3EJ()o72`UJNH`}Sh^R!keU)dkAZgtarn~4Gkj^DpYDb) z$>pelF2}*{;e0JIWPpHrI&*t!_LH8$aS{lfexV#-e{7X*%dfRt|$BTAO0hJRSo)Vt3eT)UA*PHLx z=AnL%JYY%B9zE2wwRCgw<^JHr>l^C%pptvHT5jiNuBKDY^6@WBt6WOv|7beze=7g~ zk6&lln~buKk-Z{29Nq}Y-t!AYDhx@Oq<2sPm2#Dr6*fF)ANL&)KSKcH$m4Q zdlqENRj>SZGtrdgUwrH-NdyNUbIlZ|3p~{OWeuk^Vhhf*m`?qIhOuxxB>)SIu7W=EZIqmW)KYFcD^Ddl=@Azv|2I;9j(}_ud z1%(uD`IqX4umi0FtI`f?zu4a^GSjixZ|`ca=QP~VEfLcmY?68&!`*l4n?te0xc+7C z!M*@XSs|LRaHVSz@^$qq5qLCva^z2}VS-nc$Dk`tH=ER=zMcg!o4Vog4~h%hi{DkuIvfh63VOrkIf{1|s zEyhBz%oo<4Lu#X&m!X}Ag3niU!RnQTR&i4pGcE9Z7-z`_dWrw7wYg<$W^LcYZ3&yL zJ!3HQj*Ir+jH5c0sV6yreA!i#RjJmBK4B6-C@RGxZ`r#(*AiLhcX|H}h(D4`$T!3p zcEexd1(*(R%ky*bXNCL@CC-2Tyt&!X@Il4V36OG|3ddu7DMQlCuymyg5AB?v60IgB zU&##mh3opf)C7`-DjHGDx$6EZquYvtqf_QYD5f0|L^GO zzaJ{?aM|%(kDQ!(HQ$)}MN^sUe#nlCsuyU`mKWqj%G3%Q4w_)k?%dcLrmS0jW)R<1 zGcFh`{i+NlB!?vRn^?I$hx&GcVn=sFl zXGAB4Y&q5m{82%Lzw7RLh1QODnc3FsX?{RKJwhTE`IOqVd34Sh4R|{vQcGQdef2D| zRK{6a%nw-SoN6h)hGX54NU2*(C3LI4q?sVq} zeIo*Qp!=H0)i4;a^v z`S9Y~UwKC5v67zlZjZ(Ct2ejv{c~sRev>wfUyC8!d9rrusPxq`o&HpB`p-<|M3mh- zkoP*S_5v`Q?;l&^+&O8+Sf`P9(67L@e^B9$S!jT;+qGA4{40WGM16bkHHa7|OHh@= z7rkwd>=i9G2Beoc)QbcUy!ZqF=C{O-137dO=b=G&JCK=!`LxVC>6XA|Cy~|-LB?cKQS{j zSNN>LS#9~gWhx8TchbyTB=d`i6oIbloD&@c)h2}bB%cdt6>&E6!yO&%!^W+7h9Eyt_-((9nP{L^n7kdWmfe=w17 zr*p&M8k~*bnU{*FJ@I}>%e~Vfi2N$(Q=McDu^OCYf>jZMgCF0bC=^LpRiu(ZMQ-#s zrqW&x{luix7A9&6nbKcMv0=^~_At__+}^8%)F`kphVnB+oa5b)a*O;6O|cd;;RcQ0 z=z;r=c9#}Hz@SR}YM3kwIDwXyLyB2^eKtvfy;voGp!yhkcTb9+0g?+D62O{h6kiO! zE&br@g0HPMg#2TKq&)^^%yZ`>^O#S~p-^AKu95Vos&hxP4OR$n<0UkJ<1g)RHV+p+ zsi2{U9|eotLQhFJ6A_O6fDE+1uMF_*4wjU>t;kIK)A8V(yAe@Jt(1CHo9C=M>G z9Je{CLOg?a{#LrU}?Irv>DR^;(V-x_wv#UynpojKLpSes4 z_N_M6tYkI&nC6q4H}dd~V?^>TjZ-ysN=qn#_P{NyH#x#c;=UId;sx-87R3r-Ara^f zb72#R4S5Z{@sfky7V{Q9s#_R`t_k&4tL4`aFw6JhKUq`(BW~^+sd^dW z{Aw5$16R*pXhk*C(MA0gpr=R~C)RBIVl8AMVhqXvUau?p?5bIqG^HjBO7IZ?C6-g) zgNA7n`7()G^CQ{QaF3rH^TF%;4u5LW@a3aEs>u+nGt~UU;YFZYLC9BkIF@;}^IRCV zyVZy%QL>y*zO+4i_THl|_)}Cx;eS>9A4Q4YD^Oq4V@HZU$-$(%@ z>?nEtvcd_FN5{hPpNl?Aw4ulQb$AlK5d~5lC;gBtIK+PkQ4gRRT3mQ8ybc>kMgAdy zWEXNRwZ>`SCRl40e!zO$39y_qL*$bweV4ILeCpyCE?q|a`>|6@*u>`to-g`~qVu#i ztnY0P`jPAvO=Q_A9;`Y{Ir-^wfKOcw7^0y!Orwn$rc~Z#-J0Kdu<@D8Z*})s!%Ijp zNxzFngg-EACl~OOP)uHjC6aakzZS2(9}N96_C4p)yJ>c<-;Qdb_hQd`imT9`FVp*F z5Tg|vk?IOHkN;Z9)7@#X{6~J_!K}vrX93FK0Q)V7ohFME3yST{fum>-uuDwgfxOBh z2{D8S%qlwcytoBeX!epox|A@y11zZ%utgr71>%x0A0%Jj$GR_FZKs^8P#VyHam*+G zBV<@j2tfmR$wMyHC<%h;^SQt?2)!fl4%#cbn^^+#^^$S~Tjs&WB~bjJC=JfIHX44I znJrhkrB;4yndStvkU2e{NV8L)$Ko96fn%-=QTo_OB;?=JoUS$a(XV(mj0uqDXDM#Q zsgV-dX9d{_tZBpWrQ|m}MnMs`j)(>Am7AU%)iBaN>7JAUSehpLzXTI&N*Al5Oy1s!Dr zjURAe2Qf^>WW@MMwaVnq1}G^d_GqPC?8NZ>uUcNJaC1IzXY=>-Ml}D0t?t&wpV(a@ z+{VH1e<9iR%h}g<3EThf=vB4|=2r#X@)ksW{T(dJUJ=xNJ|c1s0pR$5A;579moP9|gzp*4T~f%eW4wFY_H^bosOUb-2+He++M5f=T0w!mJwWGd)mkE|@9 zBMWHW*#LGw1|i#W3PjD8j|?K~&~siyqs{*)c|ddG^HiEqTKylak&{!qmud>|Od*Qo zNkzoD&dlvd+&ro!Bp#!tI-jUGG2SZkh)N=yxH7vb4bo;&BjQ0SNdT*j{StUS^u0lx z%w&9f=7bpnvH>cE{D=aw%;%6#B+$`EzUV|PAQ~8w`9?VEDg5U)%>xCaK_Ne6#c_fu zlm#LFrh=0iwbb4OA^nrl#Lcy%y4(eScuqq4O5toqC)jWEPmR)~`dRZC@%+=fb3-+$ zJc4{1Tq;MSjqiT%)LZERB5d!Xhi2x>&%^sxPqO5T?e+|~k1v-bi=p~D<3TZ$EY_d; z1GN~XM!S)BFALa94zkER;UNbFk(xR88G(l^4LQI18@#^K0(M8i#-H?=`-uq5t!-%L zzU19Igy3Ua%Ws~&?y0Kk*Y1r9R(hHnG1{ahe2eBq>-PK>#*A+E_~w&IS?!%%syP7q zuK?`6nikY5;Cc&HNERCe`>a5WwSN}fY&dgAio6Y&1I9tU0@6Ud646!>hy`+c>9EFt z8D&ckfdB1

L|DLh0i6${yFiFXulW?%2RMFZNE?&md(C02eZy?xp8#gnEEV)WHw$ z_~k$C?fwxdn=~quk6@1iGECuw`&=W2et?%!_t#opAi0@q2FW^l$HEg59O~?JT>O|5 z%XdW*9LQpALa%iZtC)Zh?kOmFj3m8Ba+kRXeWQT7PK5MnLbsNY5rX+?^*N*w5_JLA zRRGO1mYB;c&Jk)HSZBobbr2^~u;YNf=eZ>usSN%}{ZP;8J@;!_)2r(pWOE}lE}wtP zV1gI+GXLpH7+^!zkBclzIc{!|}Y_i8(mEU!2eeI%>y|`@I&wkkd%903!xxKj2V#589hdcG4V4LvZ zC7pvx<|Ub%#1;ve(Z5)?I#HeUk!L{4)(9?~7;DWbFtvfUy8hO*2PiwOHI#FeFxftLFjnp}GxfBIM4v@7H~~_}>fId++1ahGQ+K2x1T<6Te(%eb9L& z4boCES-otJG1U>rBu?3B-?*;Sll4r7HhO(v7L+LN-+;DC2F!s@kYVZr-fq;Usq% zPaz-l8>VR*D3L-LO{(VnAuR{YUp7>eLf5E>lYf$YqF)0{&|TF~YE%^8zF+_&I7{ls zP&6j7+Ozf0*bByNU_q8SR~&OnS2n#`UvL2T&w+#$Y|$x+!u~)sqgun?Id2vP3z2}g zUVAr9k22;w0Om9zr7E8ud$T`4eqTFp%gnzP?#f+Q@Sx-ZaRG5&COd-bv3)LmtLo_6 z|0*Ruk{rQ79|n0Wp1Ts`SEB5 zoyKt08s8?4DdPyYm{4v7wBga7d?{ZoaMh1}0EYs8x?fEY+a0mFHZf4VhW$SEx;ap- z2wWHR>m+WtgeWJ_Aq$HUufV(J5U%;}xAENNoV#pb95ng+wf=aOMu7T|+uKjgh_DP< z;p6h7N#THa35C4P`myem&NsRof6j3tnF^{E{g%g24H0|(B&%|i-KISslN`2mqAVh< zI^uR&f2Qf2rbxCh@tZ6NjhbfvM}sH);$ANzMMs~HeO>;9C9ygPfP?MPft!c>>NME7 zkHkh6aG&H}^=PaTf_pS3SOD2Flg7=XrYX*i4tSn6kpd#=&PdTm|)~0zm!x22c2a2Ej z$FXg5$B}$x|E(Ts%`zo@gWK=6WG5rATLp~Hj6GC!NaZ*BeLA&?mgm|d9fls;D4M#&74A}Gm_?V_)(bjauvtUFBqklubg*;k(R-wR9t z6C}{!X;Ew{KPz_4^A%K~eX!?Eoy3)3bV(%JzKDf5P@zUN7neJ8kIg;kl>p1hGRfNV z)u|LJXi}shy-Y$8aC%Uj_hP1pOz1zrp949S5*7#vY`Zxj<;V_h1l|}aHAUS*lK+M7 zK9SqvW`}XfP=JoWRqS&ZAAMZB<>)(u?(F-3RfHBk_EYln20w7Q?x+Jt#iB0=6_Gg8 z$UT;^Ud;I&RIJOzpy&?pjNL@j-!0vYYCs*LPVQ4aKkM$P%63 z2@VLqt_QBRET)!79bSKL-Xa~o zb)5F)3d}Xs0!NH|G`qO>@#h=A!ROx{lrE{dC?vDqN+>`$^c0bixmgF=b<2D#!7rj~ zXdB^;T7EXQ25I&tSBg}t=g1GNx$XE6cV6(-DlreqH!C$v2^k9DqI9=@nZK)6K?(lH zJP33*-5~Q@RNq6Wtwt|GYU#;2$WX{`nrkSQ-#nZ{q&s9>XUqt4H~kV#$o&fnXEo(9 zQtTj4sIYYDGB+=T|4g$afe{$uF3`<*Pf^v8}8eNEg56m7VdO1*M3T z%W==q)PgxMw=q8*RlNkXtYBJG{^gPHUJn5DDN=3^8gjpQs|ESJlB5VsLPkZ)@ZGQ$ zj3vH68nXR^(&`=2p#mdMx&3Ds?Z}B2x5D0noWJ;>jS5f-AFXjn7&6Lg8=gOn4Gfist@vZusLE$i(`!hQOBwNeQNSg)Z?>u ziHzWws!g02oXAWaQ?LVA**XxB_BOgdbK zUF}Lig_@eXb4d$~g+zN9E-~#a`IjPvaLBSpR)QG~-hh~7M+aG+NA3!AZ| zMW&9u#R|`4LWmNdRub-FHbaKf>Bgm?ne*ZO0kv+M+9ty{Az)RzH(VuF6id0f^XHs$d7E$QSO89L*^7(5YH0L z>4%JvX^?Jfiqmn>D;Uw(W1LFb@SH(O2cff$2ljpzTk~zW${KV%0d{uARkdP(+i<^y zNB6P-b2m53-NQw}l*^<1Mq$kL48t9TyoaxZx~*`1w>YUnx;VWa&rJAz9&WC54m&*cM0g*zeKNYI?jh8zi#XU zYii1>Sgo}P&dtHUwMvk->+7*y#}+4HicXuq&{3urA*vCjq}VghXmaE%SP_RU_y_#G z6(s_*V*r2u(}V*!P_Bj;cv$mv#q7ZIC67_SK%gOglJm;E3A9)mqEuq9HR^0BcF`L@ ze0)qx@e;K#9lCZ>bKCPmbHlOj>eK`8n($~E6g_a18_-pMzwNo}s&WY&s?`una!d<| zg-0qL&y>bb5=4(Lr7E}QRQt0j`I*fYpPm_)a1MpTyV0Ymu#W$X{E!@I;>c{%y1=Uk z#)7ZLRcI1ypxzV=Y(1afXpw5a;h^)%fk!sXsxF6T<;3*kCOC9ms;1s zDx6V4%j=9nZcp-8eDM3CN?^W!!a0;A`16?f&pzfq5wAgc@u2EG876DydxXO0R;M}c^5ul@h9!Szbb6C3f60P+ z=*;Ul(Ma|A=-vBuOLhMQ{sC+v0BT0$KU!RVHlS_OImnN{_m+wcMK(?gOyg6|)XuRn zK|qOEs3%R>Z>{s}>G4x{J3yQknKwmwT1xq;YE?vjoW9TdWA|>G%`NJgOvfWi3lq+5 znb>#9-G6jF`r}_{KyjiItpA{JIIs_d=#r3(GD7WGp!9~wR={l9@|K#ToF8+Zj|9@E z#4OsTAXro(=G_2D@q+GsK$p0TpL1$4nR@hRK1I58;+ScI7&1RlWE^?np-y{2#)xm^gUxQ8J1N?Pr1Y^s($?c`J4 zE;hPpQcF>No;>R4^_6J>usKIfL1+tO2^?J_lZ43BP21|jim zRs*nnOvj4|$v63DFJ~oR6XNzyUb`WU!*7@|e!GlE&q3HQ#F)KN^`(=Ms!X77@2oGp zKSb!#{h0d=F^6n{OK*-n3Zwl75EhUchqvgDvPGkz7qYMU=oO#{T&c#CUB_Ij96r$0 z@)5u}b)~{oyoUfPs1U~RNJq-6vH+Vhu*vcs4F9<&6x5%ycT zwE2-Q(zTSW{IGf(+s#o56>Ylx#JhWe-yMO2QtcXUpiziO1<}7q^4>`DUBL= z1+y3nD+>*HoZA4g6ICNI%BzOorqJEB)*^nR`z688SvSl;CE3WDlD3(2q9u(Uc&wWR z%#cQZSEnZ`lW>D)k)gXoJZRz9-ADB$i1lmy#QhtJT*id&>T)rT3KRDW1u6hwxDOB*43kFXmS7r>tE zdcLI@^q250th63UKMjdujPwJLU)QY_aQS0$471Bz&0NpVS&VRs#%(O5^$8a?3Taa{tr0;vTW)5&Jt;$oL|0&U?B}Bq`GyCG<2N2w_4Up z#CV;i9AHYHa%vW89aWjRzY69pYu(UT8n5=1C^ z6)O8&&FI(n1sVaJ8V6Q#me+%RlYQt)p6A9MY9Qk{Jru?pA{v2EjkW}=9^Tx;Ok_9e zQ+Jp{gvlZKNcGqCGi9U?WYgn3Ol%;GUgaVb-;-{b)jkEvSv7xOuvOgHyqtc9o_D9` zjmYNIks&h`=JXU6CVefn`&eX=%)}Pz3tSgq97A`1V4}NoWJS4Z9xvG?6+nuz{L7-Y z2^V5T@pD4-=)e$Qn`|AdW&~}EV58(rSFL!eOLWasAawffZ6pN`eIrD>3SD5$hU^98 zvLPu75Nkyp6TD(9(D`35?9w=#pK;_pW&E9QlEeFGy(QxEvoED)j=Lvmz}zlLpPCz? zdbUnwB;=Pd`PZmZ?k`05?Z&Bx?L33Y9!R;>;T}ea$(|bOv^=VS0v~QsAa=&VeIURY z7TU5=H_uJ`V#U98in;EaOb4J7vE(YnY}xMXVy&)&xI2Mnt68E@4CkL zDjXd69S9CS2nQ`Z-WDd9NLfECrFXU%Ajoh75{A)`(NFXCs$ngGe(PR8S}6*#GLyik zk8kKdrQY86vL)*eqULARnjt|o<8=3!!o*mk+nmH$J6i4Pu#i$<@uOV5?53PDVv$)8 zdCS0J%+4cUcTxvmT0c&_q3X=lhok$fR%D{S-(DQau!($`tzO1uoD;)4iVVIx?S0K$ z3eD$JiqpEYU%WqB9^{^+6?M#?v82}qm<$z^!|Sqjgn-1wt9#i!DDP>(4>&(HHWIUE zDIj#R(GQ{aZzz77`?qZ}U@teE>&rw92|b7lh*k}^Hec+6w{jq06{1g=N^5stNO+dDPv zGzJ}uljQH2jJ>}-kJ&(9)Za-@q zeDiN1MyAbs&FzHa18pRaX4qKyZ1t_HAf@;&M5=+tL;eI)Ygu`ND+qJ^om;x;IZG>3 zH}KMi&NEoO)u+|s`HSP0V=*rt!dlWbS)Ises^()dxsTs(&(N@;Wy**4lY4WHC_1*ItM@)hfS>aq6btxNn1oDzhBqwbJe z$yFHrDJ!TSAjS`ul-qhevFi!Q`N8mwZjmnb+(E^O7g&L5DgwqZdKH|p9fbS=Kn)p^ zg&DD55lq@vK7am9i26Eb=!JXr;>Bm!9a=g!EEi^Hd*M5c^<(egUe4< z$32&5@Jq75->>s0^?S8`40HU~XcyFaPuzX}1k|IcJczbK4dI~X_*W4KRUoX+eIh5k z@j&&%SHy>mT9l#YX=Pe7BJy-+<^k`Rc~>y2jSkV#NN<| zblQmjHe)!r8`d+}=?GpQ$`-$?tods#@@Fifs%z14(cu*)nRR1`>qUrQ-Oq{s2nE+R zn1z%Yzf}xj+6KPzz`MS9Ela`m_zyRRKlm@_l0rX^2Gzg z_br>ra%Sfry-n}llCwI+zuU3=Gu(TU+QM|XHb0~NjUwbt0PRY1pu?N6f#Kn#cK?Nm zT^0c+PWcHr!Y--9=evuVwuJ21xRcW$DXo@ROYY0oVsDaE#yOZrxqIaFmFH-Kl?h%- zbzb`?s4=0h;mBZ*yRt@MPmD|;NZ`!tAMWQ(ON$!I%$?fpq;S`I9e?FgJ>~czHEVwz zI3gY1STY;6os3*N*ea(YJNrUe`l!i&kXh`pf5U<5!ouCH+VTPT7O|fZNc>j94w7g! z2muywIax64K5^-j`?ZR&ZI1ZVl%D7}91(xzNzIJ=+N~w&<~WfV*wQ16hp5Cipsf=f z^AvD2`^NT^8yLm_VLkB`($pRp$^-mHw@ye*{3h?a zO%Atuvh5Sv{6|G8^Rj)JEH1L&ESsVYyc~4j)gXvEUAnSLw{5GkhPzLE{Y>}!-R`!@ zA2L>i?wW{u;d5@gABsHP0pE-|Uemms`}}=gx|aL_oHXMT(UKdCD6`|Q3{{^xW0VyOalcQm4{F0j_Vnl&CW%D>HLhjrduBnIK-ms-e| z9(%C-#|MzuplL4TXLMuBQf+n(>Dq8+Z9<&(D~bYP0+1Gc5(wgntk>ZtPcz>mQOW$K z+8g16d3#R5&e$aP%Au#3E7rsh(h8*QHOUvy-z1qP5`ZwIG<}i0;_^zZ*K_wD=Uavv zT!T9!Jne>e+)QO;jN87P;}VR5^zW7gsI7akY*PmXwL`9|52GSUBGcK9A_FnY_V2vRVJY2os&MrCc;L*|5alVi) z8CjZ*Q3;AALEJIRu5NsQ{X|vA(J4r3Wq@#^mxT>oDhDpb&CL&PzMc1@YTl>xepGbE zOenO>zg#5Y+THh+yd?VpeWHfXXscYfZ);st!?9tKTO>L$%Ubqwm%xNgO&w?IMHvq0$P0~Pa-P|` z&sEdN*=BCkoGY+hOIG5wopR*3&mY3ac4R4yG3n`bNJ*#>sm}5V$j>=v{qQvI!jyll8xxWU$M3V{;n6 zjbX=4y^8d0hj=o8jx-(&rNGm>pB}!=n0g#v_fu7jv4CPvriI&_Fag5py$tMt*ki zz=NNzE-wZcQ167I(rXR>{a)4L5;G&OA53Eqk)kbRS`38dv5XxYw+Entm*3|SDPLWC zNZ1noQg&%TGC109X-oMwW^5>atY1-t(0ntrYUre3JAGR20CuBpd7y}qc0+Y)&e)%mu{$)?$ruF*Slq9vp@Or z`VcJ!OrE%^V5T7ZcPahZ3*K#xsuVLk<3M>=L^^rV0R1k{K~{B6Y$2^t$856y>!HcS zHOM0=o7cpeCc@{p{o*9Jl}8gcY=tMf7q$Ol!rV0fnZ4`gwm&d5^lT$R09!=5Pv*AP z*(_Uh>3~OgdzSmxG~ZKERw(k-fGIp#HiF3gut37+j@a?Vdu7qlc#>yNP73PBt3GB=BVJL|+jO3P0dVl2hHS^iY%qrcQ0=$p?D} z+-Fw`kM2FDWGOyL8eZCUZ-yw!d$N?nmcJ3J@7;2lfD}{f-S_~l?_J3a1VtYF?3lf- zaLaC^Gu~i1LvgS5hPTjMEM#_ zJQuDi8gA-HnVGxeoT0gY(Q}8`;!U(HnQ-uDQNP0n)tB%jeWSKaaQyhP(1jQ@fMx;8 zz6(L5EWldGSTY`RC{ST&`MG1g=^AMyjH>Qby!H5LIP;>`(#&8D!JzF?>Q#LU{YXcy zufx8KB-ekCTL@-a1J#N9H~5uKZlJrWd5(zc1^roPE7yXe+Nl(82M(_%UiMT~Inh5! zrFdbDfLtLFjVcbmJaCDqwP!?LMOhRdc&>5p(QSa$Qq9CDFO4h{`jVv?jhsfM@IB9e zZfa>%=)0j{c#+=NbdlvuTSk6IfUwB9Qx^=5G=Em{Es#;lRVcxFo-kN?G=7`YL|o4J zRU0%@drYZ0A5q|3JtJ`YBx9BQb_j_V>nv9m>zb!;RGAO(Y=CI^$#d0`$~#caQwz|! zn6EG1$CcK7%I73(&L*0^b>_j2^FtKxM`h7s&7j{j>}iV;_C(Bx3Kt;W5T3K@6?uh9 zi-S#n@^0t24ly!0vF5o81t8w5yA9VM?QORh0d^APU$k^*7B2;Y0g$9j)m-s-Rm&Yh z&>f{$sA5a0n||W{_wuIdQA)=F{S|lU#Y{t;lhU7n3rEiYE^`Q}vd?Q2jB;|bNgDri z&vGHs#Hd@AE-IXdZM&4dIb?>)MwePH9U^58?yI5zmPBaCuu8uoi>1CNEZkssLMOwX#ORZ*~i$^n`VgQ`-W8mv` zdO*!?=*_T2Wu6;=@`=~@UF~K0z!Ts%^qJfd$$Mk-daO1~Z(dlL_Dd-b)K6_Q=HT~H zL~G@CRrd?Q8E5FlO3l9pf(lZZ`Ju4a!_3O1wW?%^kwde}q~3e`Uk8SYgGoEX7d>WACB6&`6cbK=z;7-uA^JPO(L*Tubn z1W-I;wyeO!+!0mM1yZBv$)!rb9~bO2g#v^hr-r`2xCE(pbG7cXQJ@N=xF-zX&|lM} zB+DCNO4OvT`3B57NZ#JngS@*-bg6vlwe^PR@E>CP(l!%Z5yI#$sa?bWW3nciX`Q9* zu^M2vvd8H3pjNw#*e*&?5GuW{darX4y>WeKUP4N#E-L&WnG85js{g`&P*D6)5K-vf zRH`%95xOa))Br`N2n7|vbVz^X-(V2ZZ%}o4*=njmOEKAvwgA*u>eP}PFBJU|SHA>O_%k>E; z4CqtkT3#%)sC%+TzO&@4dT_)2*3TY{l99QWM5?yNeWfbH*N)nUqY_EM44MZq$+W~$If}xyvq86})<@v$ zZvJj{WQM3T@;#zVEb5|d_Rs9xT;Y6eF+p$B_mIdGP4<=|01ts7H~~=slFMApdOVZ& zStmVmQB6GDX;cb3LaDEzg*{6I`HvTNq^>B7?F6;@t0r>1C*JJ){2S(rV7EwHiK)%4 z?U{;vZkgdQ^+rc>r?F#a%9? zrx3!K1<3iG53bNGy(!0Q#Zcp7^1W2VuQV7h`Z-aQ5EJ&mgjBl%C@PZt1z;etp$ zeF49dLi~28?PMDtyjBMJu~4a>j7&%b&NwOTYovq87lh|-8rIM@l<%^59PNKDIF=Ll z3JCAxG|i_@d8QSS=iC+d5jXJs7>tZ|WLyPWXn?mEURe?e>vQyTNQ=WUrjginnf&Lt zgH>wcbU_83o+hy`nnY~gdSuS};PQfGdEwQo#mOX098o4%w!_oq-nfl#n%wsy!3mxB z<~emyhd`5~r zMdwi|dH2!|NGyl|;pO%qV32@5C6q zLZ9H?N$5L9FO^CD2LwmM`7d3F#LNqq-`sRZ>G}!+5>j95G3iC*q&~EV*Ul+?wLxH9 zLdQ`1aaytR@wN^hIekN`x}9#B;gq6RvxRQunh$Wt5B-`%su>XP=oE#5$Yr=A#eXo( zh2ic+9#hWCHX}w(|GYafhva{Ledg|X1r)kbAmWH89Oqe7sK;2wiICIQB=jmz&-0yA zSoDjfDV#(gC+X9)Esl9e+jv1gwu7x%x{nw}4@8o%pmr2amtnobdDeSd0*^T=A;EZk z8A7nof}^kD*{K`*OyrNMo`+YN+uu*P2X{x_$IR0KVr9?&>s?|K=l0r)OR-9hFrUAx_U1a?EIrN4SqP&zF_=Lzy{7=4J!p&meIytf zUGXn0M2iB|M_zVI8RZJxP$MrK=m6HLKr`5=+wXh!M2)16rVpfDyvso%&N*p_fFgk2y?iE3InVK&@rw8uT}AIaB$sOP?LSa`D-a=+zutW>|j z?04PNC=FSc%83u#S{G|}KPLB*WFA0i5OGt|p<3>Jma#R0+$^B-5Bk0;_UIM@9 zPaTrLTSTHBC(tY}-&qy%-_L17;G~EBMs`9Zp0R$ofDVFvLKmpt+RwAJ0KsmTNaC)n z9{68ev1?-+8>T= zU|DIpJ-H2C>4HF)2R&wOy&&QDpMa^TBwnRQaJvfky<&4flVUvwFeVC8V<}mq)hk}E z5p)%_G@f_~sEW$;^9@ih#n4-X^h^zx04W>5AE`oNSl#{;Y!UR%waNDqXisz;IVv_M z?H}=_@X+HzYLY1?oa4qNNhNSdrz&=$puO(<@%+Ctj0eR1I@mmUEktn-F2YGX5LR6= z!c??nR_{7qgGE=g&h>bGQ!-Oy9I3Q?uxg2UkMPsrO&K6chjCW`Z1`FjLDj7o9n9@l zG{45BT~p|y%s}j#aZhp4Q^H2Sx%`-#=eAhEzLJk| zV^#n#=;N?;<;DTP9Ij~H?8ouCqm&_n+8}|Z>oqC3%xG|Vo?Gy}M5kCKFm6uBs>ATQ zzZq8?EssP@_lq9Ji^3eB!%zA!L7kHfvM} zdA#j^YcBkc)(&XgbL>$~hDY*8e7?f(yrdwh5&7?I6WheDurx~ij8JoKnJDIdeoG@~ zVwt34fDL@wb~P8w$Y+tZh(rMQu3PP3xNya5;Ux)Ul2Cl<8`FOA|vYuf7I@e@6 z=GBpBdR#Xfu({ z_Th_(Q8Mup)&>T9@$wP4#g+$Sr+{t?SLes3(aL{;$PycMZO3kY@Z@&m&b0cnd$} zNw|DMahlQ}^`M>tAic(ts?0;91#fQ|W+Dsc0uc3AX$Bn&B;CJ$_VCCN(I_;1yq}s? z{|IK9?4RngDtI^Vs*%NkRbhY_k^=PCjejK!Upe9Tzi$P;qPuz2-X8HEfOJ;tGe*)4 zw!43T)V%~CNK}RLg%>*;1;{`=E*2$0@V3~>RibDc`?3n~Z!$gpZhB z0_j}3_&cZwKbKnl5KvsK0_Y$G@&F?6#CHuj|3}kVctzEPYkZ%XVP@!&ZiY??rIZ>a z6%-T|B?JKhrIi+#LyLf*bO@s$0v0Mr!ys6EB4L2!07`eq#O1EL?muwWUhC}j#(v-D z_h9@#e)i;yebR8WIB>P7{aEN1e=q9Oc`skaSTZZTN86-j(ikNk?IqXQ0l38C%5VRR z_gY`?5ftJ3q1u(R73PkSJMPq)^1m`{b|O%xORsm?L~C9GIsAu4lv@uQ>aqfp^$>y3 z@=rz99gy!S@%}SR`WCEyUrqYda0W~H4#Y5@@RBgu)L@p13CByf)?OTW}J1N&xoQBUbpa?@nEN+gyYJOQpBNLxWI=wmgti6UgxRZs=7naR*)ur=6HW^w<=?*VYfGPAsH$ zTq|0OvhYHm-d{x}KaL;LE?sHoW(&dd1CPJL$MuoC2%S9-HV}Y#NaMx|q(r2AF+Z>W zLDp;kja>|Mvmaxfva0hBSX+4^(NHNXTta-iW~@g485$XR`e;NeYw>4Cq`uS_d_atx zGJv58eTwo&bMbLG7yNPjHAG3eLQ?q2m|%e)_q`iMkxEQ{!NmfGkxZAOROG27=R$p$010@t?*En3Qw!Rm4*v9Prm= z;>^RQ0|E>8(}B=9QG?|adUqlG9)J(M5a&8UPvO77K}+e*`zP94$saRUPpM#0*Z$n_ znLch+TEQ-=qq1`&`D03`9HM+^I<(;Ex9`fJ=VAByG68ewwa`eYR*Y509Tn@HvQ`5m z^aZgHzy~fu9GqW(IeGaBTP$03w1u@2O8TYNgFc)Bq0?%dg-2JPqU(6u8d>%A6Qo(s z)e{Xw-Sv+$k%`m0jJAT@wY8dYu9E9a1{s|1NIMtI5BXmwpr% zFYT%3svKQcofo)qO6FhN#Zw%LdJ;)!&qbW~t{(fy5p-0Nu4g<~4_@_KD=}%QL~f-t zL)}hk3=`_<^$QyKs{IT6?6N3@efe--K{?8~)Vt?)c{=KE1KebaOAabJss9d>AhyuK z|G}B`0eVL@PjrhjRQC9)d4ar`n`FO#0FK|Hi1ynosiIH(hbcsWru(0wibvyj236ev z{RlIKChm^*N8-Q>a1>48D^X991C$Spv*?ZU+CZ;P6aqgd9M7iuO# z(RcTIO=uK6{K8n&2=&eIbY(C>C}YJ4o%7K<>1_t~!Pv{negroE z-7Qgn#mH z_*kP*bT}HrHO2Q5zmUxmczBVk{N-*7vLgBmA`x9@;BgWbc)J9zHu4IS-uwT^R|D~f zU^y_Ck;i!Q9`lL|KMBXWJ-^aSRNEUnwKL-QWIeBw9x7PKWBtK^cXRkw_w}edVQx1G zaBR_Ln+d(N`ZUdd<`=@8E&3;ixjSMr$jB8oH_UzBaj$OW?b9d}u*wX-e{I&FfQ4Lq)GZzR9?p)#J`)u}JQ**9ewbqEJY3bz!BFK()y2lmRl%csR* zAI|LKr<8ylE7m5%;Tk&P=~j7b6%(EE_yqRswIjJ|8vj@fI-f;z*dM9hQ}yW|EYNvt z+&q+&fKqYlyTZ=Rl*kHw6|#S$NBV+YJQ$SL&j0!4Qy|?K;?0>5mpA?=Xzpi)O&ONl z^xvwy2qCCrZdW65jA*=Mg+n_13HKF!Re)PK9)Ju_D`88s+kU;$T^uef09FulJ6Ea| zP!#;KOux)Y{O~I&&7|i)T-)tR?f#l88ct|FgAU=@k^V^wR_%8rQ9akdCEtZ3QiQ{Z zKqFp)Ln{_oPrqwg>_8FFgqX30TAMT}Tg7CLw{>gVIrAVlr>5)arq|_nx;lDOH`#Hi z*RIdLLEgm?bo@zbSqQSce@hfI*7?vw#(r0VKGFAyq_grCrzq5tS~hX2Qx~_wUlI_d ze2M71r>$wVbMuu`M1}40{6zCd62NN$mYrnD2C2`%pD(kg^ZTgJ(pw)aFz$Rpd9z_D z4diRLkw|)jVK!`U&rJ8;Qqgh}pzfD)O$%P=Kiuov`Al8Zy0(5}z?_<2y4l_NAG=DA z>4{#j_tQ-30T4htYg#b_?LKC;ZZpJP3D^RSRh}N@IQ3fUOvtNu8^jlHtk#cN&dR@8 zHW8Me)mPk~ee&!c_(xnZz59*v!s*UrF&QJ@AuZwgfxp)6iMTnVGZ4Da%U-S+;`XH- zMd)0azjzKRG>nD>KyCo}2z%AU6~Zy3b+Ht(Cr^vu!=yKGpS+kx3rGsMyyo|t^d1G? zpBf(HsDKy8t?M7XVP_onZ+b_{Aq>wy-bj6^%?)}p!1>8@M$cSgvJKa`WnV+Lr zIuEbyn ztRw?;S!U!|f2HtN{Q`wAE4c*V=BHpnj}XL!5QqN_f4WwLUA8$y;XSAe;Vg4bt1E{n z3+=I?BfdnDx3a)f+zm~UUW>M4249y1dF55p??NxdddwlOc1$Vz(|dSfBZP)tBvN|w zf$K4$IfmgII_LBf3sOyNaGqGozk`GB+Ljs_k`pnrC~4dZPLAo*NpJ>10BRm+xa3X& zGq6wHe@UzH%k}#96FFGG!wWiqa*h!v?0_V1x!k@j)r)w+rAvCjPcC?BaY;pN;4Uke zOE5=DI{+C>syU)A_HxSupAL1!P|7_sosswc)h(Z-768=nzF93^2@j3|1LyWN7V9Pg zGLEf0qJDR|%bPET4Z!>R9n2J!g?bSfN4B3}&ddXLf@JOSCYxZqE4ZiLXYi40?3EJY zp4C2*ml0Ak=JGI6o`sw8ltV4%nyDK|{d0AI6<|X%k*#dta9^tpv z@j2Xic<=IpYhP>s`&QZ0RB5r#K}xDmVd1poV}^0zj}cXPT-W8tW+IdReis?8%#Ji( zc9^G3Vb4|%J#&4ZTL!jw27@+%v3xA#Zrwt31SN*i_8sw)TVPi+ztuJOWj)Hd5R92l z17}%P0RK|X~SHsFjzje0ZVg({Vp(zj7R%SQZOoFjk1Bm`S~bDLb^oSyRD12aDE z^X=d7fsY(&VhhX;w+@Wb276amh3y$7J7b)}5DHpwNIDBIUtlI^6Y}@h={Q{oLi$@# zkVsIBV^nb953}sZCnR%7YiZe;cv(KP?Hewhp?PbPbCYkgI~Q$3i1l)I;9HKc=EBRW zHHMk0-@neBpqzynW-AoUm;Udein3+rZ)-$dT+g!VV*V3D<*pX5`ph9N;h(kENYZga zGAr*bo^`2U)NlZe`2MRVxM4#eFoiHgI( zvi1$|6|62xR4 z0lUihADuY^P+bI@bLqb0s7#%7VvqOy)GU6bW5X*;k07*nVn}r$;5IZ&9c-%^ae8LUiVRrSWX{^b9)1AAAL9@C|4%mkAs~{4Pd7bR9BO|U=wa{8o}6G_~#i2@HvPKqVQtqagpi$4l5TNWhNS z?+i111NmyL*hcvJkaXz%V~kTKv2ssoFK@fO{3q!Po|`BPn&-T~MEuSMrcgC>>Hh1I zAYJ3x97E~`AR8~U;(RCW({dQ5pP;*A99Rf~?#fYgGXSUADubUB#DHH$Oy=W~Yy?-{ z2(?Cw4bULyp6Z;#V+6%XAo>;CgE&+pVSR?emPOpTvL!_Ss#qyE(fX#jw6?3E z*(71i=I`eY1Jj{LdrK$lKA%C-9)55)`%QK1ccjz>hQ}ob1t++Q`o3&`_*jeZ{6W_q zNFe}it)<67#p6a}7y0OM|R1LHM+=@fubuS^~Y|jaiCzZbANa2!uPA{T8 z?2zW_@D{Ul5PWC;w{7lV>*8(I1muCv3t_s(7*<fo+VgWcaHc=Jc07SkozDS% z*9VbY?S13`&AokzW_YxB^y-1;N~g=5PFxS<{$RLC)vieiKDIghJ|*tRX9k~R6i$h2 zGOe)Xw$RM6D3xQ5pZ>d^_@JefJ-=5;@aopuTGNk|;F<$lTf@0};t_@&67g5WoiinEDx>2~k!ed)(=@ zMuC_VAvl9&>lO>a4pP{9S46?jwP#eiGGna|OJzk3_Pl1I{UEVc8T=Q=rs996aeaqf znMM0%Cu<~WMaPy6jNXtY)i6#CE*K!dVpZiw;~&N{r0*6cMn2~H@nGc)Mi*xN9C?O1 zmN9c+Rp&W)ZFWj&PTwWKPU$B2=B7)BO~d@3{rdFXaj0k8PfjLs=OOz(O;Rwu0OA}% zCT4?E^w;ZvErhv^_xHD_){nv;Mcwr2yb(!5a!koi2P!$tBxK6;Wm_~H9m!IxfUfrx zL_uW!(w+i|&^-~YJ8b;4Yly{!+;?c83MqLy+Ln|y1B^ix!84j4 z($Xo(f)pF_DWhn!=C<|;r9xgSHaLJ+LZ0$i@A1!gn^!TuPs}<(JL~pp zbnS>j6%5zE(<1;3v0&640HKujMxdJq6mH!y?|9d;Z*!BWsVffl6Z#Kh05<*o9bWRl zT2jv15cPBFO^hY$2S#Wk-$+N6^xTVg`JRO}m+W2$a|;f&dtGO``zs)h>%l&QSmXHm z)lI{tZ>M#=u(Bv~MA`g73;V1zR`vVvozB_|Grh%D&jR++IPC&<+Kyl08|};N(|R29 za!;6Xbti2%v>~`pL#{%DIIhbs8~$HvVO5n!ECKJC~~poi4%0{3V%+VvVPTbY%l4ZTR6nD=mgw)OD@c zv$?p-Nh}mu9QbaGO3^X}jo*G5*z7O4vKfbpa0(y{Q`tQ8L`mWuSjy)9cKHPDa5>|Q zs`Fbt4d)6B{)+de1s@f9SZt4y>wCVvqZ<>oaD&9l`ki4Sxmx#^xZ^42Jwtp!GumaG zdP;oHUX^8fz!;1D-U@B{KAvSDJ;H4MFqD6vQ<@RK;xj|gk;p)6Xc&v$qo#k`XFne- zKF{c?*r(^YMm-;Dx#)<9YBWQhl##tqkEO@mMfm%k+jpoRHV2tV+=WygJ0StM?|7(j z4I}W=j`j;&W5hRZJUIY2t;FIqbo!)Vp;D6n#gcko{+Z}wB5LIxtc%XlV+ut~WHo1| zdlfEgfBF}`s|waJ)2!D|pG#*anG#R$#DLVA;Ok;?{GgOt}O;xjqu%MUQsv>K!=O{hvGHjj%8;0-G-mNWoYjDc9YtENmx<5 zVpk-W^sT7W`jhzgqapEclbfp{f;a^Z4zqQA!aq32^6e4)gaaCZGLd75Z&xC zp>*wVb7skG9%f0H%{pgrmN~G|?`b~g)&mLrsutzKqu-Xc-Vf@z&0bi|7@cNc8I2p54R~Sr6In z(e-{i-eQby3Zr4#+%ldkx<&6Bzq4cP!1C=ot)dc6eiDxTrBP#IX?lU?3G4+AP+~Bd zd1-!ICkdA0ia!^>C%~v4d87rsSg>XntE^Y!7~p}<_wIUm;Az-nINavH)8MicO#Oah z=ku4e$Ku^@FG}o%jGt_s&AtqfSiA(CZO(8N1AiC4DtD^hujTR&ZA;sud7HZBOz^9~ zJvcniD;X)af$0&A8U`E2DiTp|#clu+I+JU%1zKd89xwdNunYZ#UK` zq7$h91#=Q9NaJP0*!v3Gt>_~Z^k?C9`uTEM`26YqQ##)R#b=(Kb-J?u=B0cNAJ$w zwbncExA_)c`JPe3YBr{Z{8a@?)7v<-TnIOsoK?!9SG=E#!6!W+F2A$CO&mL2$BD-m zinAS122ni*#$6z3Zy${*?9f1|aO^u44i;E?VC;$G=2z3H)tq=ib@sSeHbfm{;zR```A@ACgR=|mF~4#bn-_9mUlZ`VHO6tIzkI_i(ev*> zq16GpZ)^Lnob;PLbKCp#xW)eC-q!7g-$gPpr(7pKY&9f6OvK@9OytgaDRrM2guI6a zxCiysCy~R8_0sO4jtN13cWsdshTd$DP&Bw${hl137k%IgUCe~Yjr^%bxr#;>ydmUr zk6m`_35wMWeX!TUwwH0L)oyGWSYy3$w&W8lC)9QR1LZ6*%`SN+gf09d4?J@E6DMa@hcu692JGa(94fzjZTb!RVtbyxdl3Ih>%O}{o&tJ9ypC;F z#-QlERNg7u&=eIBxUzlqjL}hNamm-jMlKE6ej%;##xW;b=jJ@4^yrnla=m|8RGF+ zu9H`vyLV^{?#|A1h)-!cQC2UCR*v6)1kCzc9|#cy;0R;T6Jyg?a|^{Z5e?1>lkWlW z&q89ocYGm2^hWQ;puSgYh1=>Qr&CntKiC_ix)VROw%_*7M9ZfF%#wcXpMIMQ05 zFNteImqlZbNFVAKXB4Eppr`E|c$aSp*8(9aXFZWY8s+kS$duraj`&{OUepY`$$!{c zDt8ToqhU32VoVcUelTJvmiNZc)F}R+S6AZ3ywKsq`&QN4Waq=%JgHl~Vk_4$KJ5F` z?KuqpvW%&QP_CXASh6W2LT^p=CfDCv&TedX839Y?&8B&+D$wT#y zfc;~Jaqj&kB6gM=q_e*mk@XScL`KwW@HY%Af}OjXz##8nBt3MJI(h_DDMqY9>E?&A z@N3`xoPO4&S=nn~$F`OD{TvvPXKWKWKl%HJ8NU->lhAp&<~X8h5GPW3({a}s!S7zZ zI!*wDNX8Bk4XUD%7RRNHYK?D4=Tt&b^7eURD^_`EybrK_mBqP`FYW+86|-LMGvTEO z@2&Cbi7RTPk|*^M_>AN{;u+ zJQ1V>`>jhc!>^b3ZT>>TNFXo2SCA@a<)^uWTxyEDbm2jXWYGa>&fsFduJyMo|BVHH zK*+P<&}0+Yw)afd35e6N57U9tbCg4`2Iwc)$yyXUA`bfqy%O`t-p;mf)!3+gEcha; z#lO^j`4H-Y=-G(Rdp!9S1UgZvxAx<5cto2k7r;^ib4qlp9`BtQuGJpQ7zZswCql+v z@Hl^8n&MBOOP^yVEYf9<#JIUntT*)qXBk;Oo&wlh^VKx-gW^NE{x!zG)E@8Z~;Gonan9dteiK z+~q_7lLU>I;~M(CnLP`s4{qgr@3AkCCP45)xd*FD!nBsX&f7<^c;DK7ij&>m8BLJk z+xp-Sk-)6X!g9F{2|IEecs>{mW~u1Yg0#nR@CU*O#OfX7E5=gou}`$ z@(S5XFHJ{v2?&3=OVSm#CS~>VEcrydvZJ+(8~{vS)|W68vkE-S;Me${2eB zq~@!ijR5uUI-1kE)s4M zj)Wt4HpL}mAZhpr!#=jvvfHZnoq`%CdSWlz^A9>6x@6J^r#vXC(j3WivwFu9~nUX4U)2JO{le8&N&YInvlOg=v*>xEl06d*hNEkL_qGWaOe7C)7R%=>zXvbi~_$aVJuv5&j^#| zjrFEU9;>1ipW5Sx0SE|apCLB4>?HHE(fQ9K*iIZs8z4Nme0xhg^Nv0HQyx2|#2-Ii zepbl_q%bQZM)-XKC5Y}64c$FpJG0=0w}xDQUi8=VKC&Pdnzk4jsj+_U3sL3%O*`*T zf<3|Ib*L4wRA`UWuYa(xF3Z@Y)Q;OLVX)F%kgSw0sa$yhZRvI{<(WU7(Ks_xVXH!asBwxNOWqnbEac@`iQ0JVQpdEyWT^2` z^}^2Umg_B0+=+e0yKRP1j-52*n>F`|cJw{f?~09*|3fW&%D&86SSWkXmv<$X`43A1 zS1exi5swg_8QpM$iKK-P*o|X41g8Top+UvP@gu8mTSLZ2#b-t;Y=2>MnXP}Je_m-j z4ziq$SpzSMD;ko)l2QMochN#Hi6W{jSMXP1+>xkR#;CCQiM(t}HdhC+{AV}R)JqwW z982j+uRk%qCK-nB{N&LI56zV)GhW{lAF)T2j~m=G_;tKz*KCr0G-u9`(+6e-(&6AO zqciQ}QfeeIVDIx9`eyKYTlvq43O=BlE)eb!xN_th)di&+DbGmn4>y+lqx+fIZ1bM;+eEEF z)ozgBcT6)}(N#7VYZP7iub!@DE8v-}Aw=Hc0Awwn!=f|iPU>t$wxLOz#=Z^^WvC*4 zlQGM5|8w&Tv6%J4QXt-Ocj)NGS$6Pa`=so<=WYF;QWm%gi3~p`PN#kw+v4wbg_*cKhGQ&ORLnSjlJMI#Yxo3P7Hfw zcsbo_>9K6J)4&~6Z>(F|^yai0o_B7J zP-(~;1a916;CYrY#1R2p%Xa2xiH{fe1GTHvUJ#DAu@jCtKx8d@Jj~63zd+qhA?})( z3Z@0G^<{JRC2x?6bRNpCeCL5RmY19V8ci`ek{lv0@vlh;ZSP=Y5ESLaI-BfhQ)LsR z3}?qI#uUmm)|>*Zv#Kd-M1_?13lP$VB6 zHERs$Z+CXihBzP?V#QnV)7D3mIWHn{zBR+>fk{7MDP8O2*culUT3rKipMC#{y&ji3V{0Zt+|Evgfc)^#*Xg_U zYy+&iy~qTtxC`RIy5O0>O%9m)gG{_od&mA16?-S0TTty_XJbK=C;vp)!>v<(7d>~L z@s0W=Mz9`aguFnYh0xKgfDQ0)8vaoaKxvRPV9EicalLUpUYP=dMq2fIEOvaD)TgBC~5jE8&+4z=BRLa(zI zblWbv>)x0H5bj&eW}H zrXFS@KZN*3r<_zwQO-6um(nwdksTE^7(x%^Np%t}zeDU;s)y=Go8nJFfFwzmy#8im zzPazUI+2OhvJF*#9@+PuUkDt6;sA-Q_CqzF*3zw)J_~hE{(l$1C8xgoCrB}eO|#-d z4&Et6@qAy9Z~t8q0)wE6sim>8-~+$Er6X=?007K#h|W;tYSRIB)%_2kh`MrXyeIHADT(T}jxWCi4AEF}^d<24w0d`DD6Gj|Q|nlmB-nF8 z8$3!seCH}+N0hMq6-YkOwS2sLg{tMLY`=?Aie{D}L-Si_ax30ra5ROWTv++@X%6!y zeQ|cIk3E|P|4-Ux+wZBHbdTZo8diH^5w-gT^A`tX&)#ftRfbLa?9UG`c|=Ir7B}M% zze&1>O*GGEet-r(PKv?Y@0KXCdf z!~kUsS*ZA1n;33PUWFVPx*^z>_URDCwr2MJH`ME+2=SlV#>E2LCQ_b^mVT>^isp3A z$=$CR#Em4J`aLnzKZ%#7buKVek!}}OuPigo{K785bbb4rssGgarb-OorHfwFEt?^4)RxviO7FBB_j90 z0V{sdkP8|cX^-&__fuT)9PV5uAlr6T-P)&uEr*tR(3H=O90-&O-Lv6`HX$6O0r}J& z3FM`EZ*aAm71mWOE)5x^=yRMUi$Mdog#4Rxj0uK6SXC&tz=6majkM-Mtk@;?wibOs z`^K;k#u-Lf%D~~kCN(W!-w#kM;LaiIT7M;Po;Q(ZbBNb~V~_>BZmdDr_^O{$TRmUm zjp(9AaKSeLwesnYJ&vpCieC9feN^HW?_2BXoWQ8+w%D`vIdf^MJ;IFZzz?#6r0~#fqjN+q}T(drg+T z$>+_RS9Y_)FDe9RTdrFV7w30vFBeEwOoda6F~oxlc6qU+h8@S2akZzkD)4hF>;#PmXtx2@2cP!4Ld+&titky_npj3#>EoA%q-UVwdty?X zJvMRo&CF&Oe5VVlLG4j)eZCtxIcE@s#JIiZmGrIGX=jG&_$Wnq-W}Mge`&v1T+@Mh zmEMoPG2&G}mY*IqBBz{FrbJ#2Q!&^;AFzHd3&SKt6&pEJ1Snr(1dlJbliA#=E(hwD zH>NO?c;3A&O!u3YbtWJ$AUXWJb*rGdDk|ek5_#ruPFaKwZI1KxP5JI2^`*glbwgWQ z;ku*`{nyl_rJQ{h?z0ng_qHgxrgC;8KqQba-Pd=e1qp%s-KrnD4zAZFEUW>-j^1PR zJ^-2Ma$BIt-lGjRNmKn^$=~+T6z8zG-<;NX@P)I|X*I{tPaolLZF>M*vOo4w_d@G0 zP4L{A>03x%KgQ&66(gxZddGNJvn;nkGBlF}ZTCm<3lhHl>T3km&Dw~qN~_BuFF zkr{s(3fy*y9$>~-pb#s+oLKb42hUvWpEjSy6!gz~YwZ^qR&aplhLg0wX40=jRhI)$ zCct>w#X+Mz((+3O!Q6lOjUOa$<{2dGO1|PG?Nq+Eu2c(noj!>>5KpMV?AWaexYu~3 zU!IOvTDb~|ItBoah)+O`{i|UKqHfZhC(_;3dzMJ=wdqc%w-aU>_H230g**4(PFf|g zkY4ew@!UTTEHELizE=f%ucZn2F#48RU}1@`;GS9~hFj z^9E-sXuo=cM|y+)ot+$PjC*8^dau69e_x_`J3`tFU(M9P7pDf!eRk+Vvv0(y$AhpP zCy{9QaU2hvVO6kZX(+YLzg!&Tm~U2Iez9Qcd$xE3gq3k@hho7Yb%el2sDp03;WY88 zMms<`DPR5k4;`;mo*(T-9Km?}6(8(c`qS7i^n2O5^qVc!XZdPVB(`Ej#6T!&q|pFI_MqUu72J!q+0i~E>V`TdPFbnn56s` zYR}!qk4tLc$CWy#`t6~hXz3*FJ+As4FBVD&1a}kK+F@a?(U3*D_A`h;^HwxSf_9d_ ze-!@7_`AZKVSMWmbLCkcngj~}4pSJ{I3?~z>>F-<;hX8@h{$){GU{u z4K3Uu;24p;$Z)?o54q4K(pH7FrhBz?&IM#{9y~jd$`+sKQQ|K`sBFV+ht^Fr{?q|M z;9tE9qxn4U5v+ZVji86NUU2oNKR3quceC^~KaduyCvrTH5+?|)PRu$9AvNZ(Lu~V} zzsa~@hEZy8@o_-O0n)!;vOmX694|j)Tvx4)Ah6@(poFupPSV7Slq3Pg@XZB4a0hH) z?*(mCV0T`VAhCi(R=qJGpFRuM!tP>s$hDVIJH^^| zCkDJzDhp-YoBXdvkyVPnFz!YfZ8@i29Lr?jHU8Ca{n3}`eLX}=D{+g>x+j1+K)y1A zvp<^>D))=eG*9oHA%` zsPb1B*H1P6d+8U0Az7K)|A$<`;AoHFQVtnegC z^zi#6Up@Q%b4_!Mp>~A!J9JqiLQIGYDzf{UJK5cXRr^x+hqWSR6MG6#S^2CfLxYli zY;`ML9SoFfMqO;*b#PZlQfk57_%YAvVx1lgaZRfF$IA;V6dC6@nBi-)G~fIRCje9p zFPw^dmFxl3DE0zusUmKSkaBPj^;Fe1w9?r2m6?>;>S+4 z%^0d36?%0DneK7rW(P4ZV9&-2I)Uu5NAmF9x+WQYUD*He;WI0Q>}m0Tm0Ai*`Cm7s zqLZAKwxy_H*6T+7(*T6#r1+x=XgxY+U{P=3&owQ>DVxwcWv2*G=rUe#*5{D+vgd<9*sZZ|klP10Rxw%zMH`f31wW_=XM3 zMYRfsH71mj8G8SYzL^5B8`Ia|^xSienHrvC&wp17)y?^b0N}MQJn1wWZ>P}N7yf%K z?<<0u#3>-ApyKh)U|+!okfi_wXa-kbuigQK&3?asOrVM?%>#7%;NmV?rZb1Rt%)(`>^4pHLS za#g!_U)+Mk>yL!c&KVS}!5l={ibF@=SsyVnYL#>c1(;3uD?(z)#f}!C%u2 z9*6_!G({PFohg$7`QtNCD!pTPu6NrblL-O!94w^rNXY5nzag@P8S1wZf9||xlY$#f zoRpC5(IxM$5rVlWyXMCYg5IKWVfhUuir>HmwVPc^)Qo!X5GyyMs^6dQpdfB%K=`}J zai#H%++OU&SS?+zk~7`8l5gux^s`3?pa~dQGlOM3i-Y7VzZCHPkIk_tQyWtL4&Cl9 z*n1Nwcnd7)@C_af^xs=Bd zWJtp`6Rp@+^yE&}wGgY57?7@i`JpWw0u5Nqw=Vb6gAX}dDaGyo%8!M^Pmr~k@s1mh zz;(C8*B7FpbMjT@;OC~fK0IuI0j zFyUiKf6b2cUAS3vf&S-M--5PX=NMKJCk!L~a@&ucVL$B|=ZJMciIv^~Bz89PmCu0g z9eQS8;C_z~sFq-}7682~W~YzqnR_TBB5VxkV!%74PHB#zJ%PH6vW?P2dQO^vTe2+1 zCy%Wkd_kEkjD)FN2lBo{;JSbhrzvXSA(~|F*F2i#b5J~z1j$0aXU>9ZW)QN8n}Aj1 z)!RNW%&=(bY1y5+OMt-W8ijy&zYE_>dcZ@H_&DY{Cgmkqd=ylWJNx3aP7?QcYF~*x z^J-g2DQW|YZ|k@c=Pr|V)~sZp zP=xcmAgF?HXwuL3i>E!kJwVn~WYsv~oEG7pDcKE4F&eY$^YAz!`1IqBDYnP>TU3b` z9oSjhqN8n}%K}Se>5UC9W$2KKr7l}Vjm*E#+1GDutSFu`**Kxx0eH{=i?r_Sh)A;L-%VPuu?C6d_5 zdRai?jt-czE`4O1Db|&7@1~IbaCCaB%rp5N-DamScG!`h{uTzz#Xr4?HHV| z{WD6!d2ScTps}MuZU$R4_&?Sdg8-WF19@?cJNc%5f3$p}N8TP(Qop&W4H^%!U2An(*@l=%i%T z5hT`*(1#$Ly5vIteIGp0C(LWBONgoKq;A$nNt+}#jBO_GOWaRAmzyJ(~XZ^_=DU?RN_@<+hYKZGfW%(L*L zn4-(b^gq&M=G4e}2}X3rpHT7BECHP}A~V7xD|KF{4UqG?IX_j+P>KC^7s2 zJjtX5kzN-gHF2k)pk1r*QG+)|gohDj&ywHMADW|dL^L!qMRphzAojCb>LJ2-$(aiB=_{778BA@}G+eA9=zU4e0Dc{vu6 z>c8h9^Lho*=W%N(O5Dw#uBilQX4WLkuGM5QP+6AF=~WM2k}BKy9~`OWA0UEjZ8&ULPHojI@P{kR`b z-ZVS7EOTq8CI1{+44Om)BlnsFs~tBi-n;M9$rplc8ia?v4nB6r%*aYj*iZjE;$A$Z zJK1_;;8bzH-TU1p+=E?{IdjqcbPU=2wrGF9)~p-@mV3uizQxdKKvJrG8s!)lVqOM0 z3MlKi*vH-!)QST!ePzmsLOTYnvpVL&!;oA;nn22v?@fN`m3 z$DSug%gZ31Bf&?xnfk3%+ln9@0F5+=xMel*k>ul%WLS9|79LK?CEvwt+Gsm*#C+IR z0L-K3J8G?6$ZBV^vp7(m4 zINy3Uz-jo(c31`;=>opTqdVHUS~!YA;gbZcnfkjK>~C`i zd8$Xt2K`tpd^R@66+O+?ilF%pkH70Ij>LHOlRCLCzy%Dtx$c#`A~n95WJs-GVQ z%qWx1e@IRz=vDC!R$dMX1;iu8HJa=WnyC8@HsHiiF z#5{zb;#xo;&+V+`CtXo=meV)f@Mcks_VwytlTaYpE(;NgiIH)ZpzeXzWlkiG|LLi3 z+_KmQ#W-er9enK)WnS3pInWv55MG1_-}||Go|(`vmOa_xk6`21ZlyX!K`vXJ(wFR< zp?9_4YQo(n7f+XVQL{HI%QjNy&%l!HLr?E&dR>n$VYq@p^nH1Y0$ol<&*_)CX8UQC z5JnX~bt+a5e2=T$<9rvi9Jd&lP9djvZe5N;)F=3!WR3R4-`*Ng{a3Bff}}4nbkluG zw(k17Y|5G{L^WMB@l|^Pl6fy?BO)hBy`MKMco^4?-j$#zOpaXnR`)4>_8~EuB#Bze zuF;uVDwL+%&e!%C%xW*1K3EDfOMtoKHZ3+q4Z+)uxtr^g(PRo{H64IckeC{o2P(lZ z>MlLpd}@nzl_HCSPkegdFwU^+ZHa&pU(wBDDWbXHdKOeCLuN2`-_N6qilAZqeT05! z+5;=@&pAS}zo|B7W?yz`hW`~S^}Io~bR(FOB4WVJIYt%lf;1od80ei2pWnl2_)A)* z2_H%$l5+z+&|ny)#&W*Ti6-oV9@Vp-H78~)R8(C}k9ksDLswEliuQuPEtabAnnv+5 zuPXTqx3!J-9WgM#?H=jiETf`GjG6uK0Ry%gikg)eW$a&+sgg3okXP$d*sHYCPPa~u znbP)jkmjlaO>ixO_A17woa&1v1DJ;#JLmR=9=LB0zE0TwX~}PM9j}`k0iwi1 zjzx+pO>4I-zpoEn#DU)($z0?BFa=2snNQD8z}M0X(d+huJ4z)eKJU91?)gx%w@fb! zjNrK;25v=M98CW?@)ow`BR2;#T=gG_JTPfmo>1`ys`U_M{)&;KQFf&TCqobJ+0o`9 znsjV>kXXBC3LZT*4fDJ>MOFC~RDa9s-&7e-8*~ ztwIj?UF|D%AQu~$d&IQOy3u<3o#K~AC2>F6$9N-nF#mu=yF1!r^iUg=lWK%4v*8jS~h zm%bOCUX1>XWk@hG%Wql(w%?H+^VhOP*&HdFxDi@a8Azr$Oex-QE(K!PGKq|BP zmNdI|m}8nY%I)r)7pvAePgw|Odt}pgeJgcxd4wyBQ7VayU#o2QFrC2L z#U3iSKRVPMC5H#jZ#XELXQB0`6+KPw8;jMS?P%t#r-NR|m;EON8N#1r4#yX9G^JkK zVJ}3XAxbu!f};uZQy%Dm0&iS|craFqJ+hW`M5nx=zWa}taDBi@<*MYegr_#ZyP$~H zF>L_?h*>LLYLNhZ)l9$U-Bu5|$5`vy35iB-rn1?O<%QjBKae8S3ELm< zro%^*ffo2|RW)@pfix&p#pV1?5dt+b2c+3v#pIx6*BE}ju8(sVQ1hAXuy0)ABt$iQ z;^ASW2LN_+rH?UmpO2s#IejP1a^#w@1aQVJNRHmPdP8H&V%=^w;!S%IqP zae&;!=bO>r9nhQ2yCWHM`bok77>rady1uGggm7@=(rp=IDc8F7?`V zWn6kyUgWnIoi&N@o1=wa;JDMu?wn#o@ErZA?BIP^5tV(@1L&;3+`V8B-nmHzk)5i; z?d1^TBx#R>wtn9l^eFqtubOC@C2~gPaZ>SU54*twX9so zT^z3WWqUbibYnbbqjsO@CJo&PrMzdgKpiLNNJI`=))4|c`#0#iNZC*AoqNnm0yq88 zqP0HJ>-B9ZYRv^-i3}p|AyCvaFK*+zX28yD{lr$^KirNLa@WXoyM;-+Kr4C?vP*j9 zTj-;Q{C19;@7x0(eEbvn9TW2kleH7b3FD0CC+LdP)br~yi@@YsDqIPzKf;7+_Ym{uFKv#Lbs||Jrv;1Xkr}nhd$zeCOS2V>k6E*E4%Cyo^>M ze}yX#ZCEQK!A#IQF&y=Ce>HGa5@egZj@?Id@Blg3jR%F}1W9jjF3XnT9|!YImo^#? z#ALock-vv?eJ=+n<__>X883n7m<`pmGDtVH-2H;r&&DhN3 zbE-qTE}g#Aer+!2_JcYNA!EzP4Joz9FQ6Ze>N zDF?Ar)u^zrDDTY|CQPSIm^u40*Xe!H%D4A2R{Pbx2X@R}&h1@8AF3k#z#2U)=i)%` zvSgffyp4QfwzKLPo(kj0j^QL_f86CfqJXLWaxxFLViZ2K1Spf!EN`y9OEMg9(5;cO={lX32PHWa z=*2qM{e<87TFg+R6l)Iy2Ls0K?$x2eK?`5HAy)nt?1l}FM2;MW*S(UhTOUB><0DU2 z$IMo}e6WKV@yA7*xPT{H%GO}Txbh<9i$ezxZ9PliHUq&9%P$^X|nVNkNo?UaVj{Tm9Q+ zWFUvdgxr6T1$uc&k)bg?-E%cS<~w%no}{7hWyP=fDY@IxRElgi?gh_ z6*L!!i;1vn0ea^DyA20pmdqI_B8ZyjfoDiW&?{^=CpqN{?9xq#w1FWvn*20T<=>RZ zima@D>AB}a61Em{uvMbLl1culfXK(~3ggWVMG#q&0zj}^>bg_FBp2f7zWWJLM`Op@ z1;tnjdmh+Yj6B$4Eig^w4>hC76Zd7~Ih&d=<-&~$jbDo0v9D$n{_#~^NATnQb-uP#992Cl}i1Dz7HQC&T&|lqMle*&j2z(}p@^~_}G8eCe+`}+b zCEp`d?M39_h;YGu<(mn@DB|QO`;4@PSSznr`&W+aYh%T3(x0&S`;kimWqcK(K6ccJ zvdaPG<^Y7qu6%uht+~3O9ZwD4oIHBm0wW6^YKy| z2D&mnbC54>;LT`%nSx0-CmiDPn0#I?E#WH4B{k|t_!WLYc^@tV;%(l@)D{_bL8dKy zjEnATC+Ww0GKF3-b5-jW>P`*^CO7&$Wbxnmc6fHgGHE47jelwvwxjYiuN?oMkJWa*&XPv9t? zXv*xNpR&`T8a#~7oP~X7pSEA-?mtQnM;jXg+HG`k64f8aAn(cuYULq!1&n%+kAVTf zi+@)8-`H@X7UF+C79vPMr|!f_6U&QqS%;r}oO0bz=IHxqWgrIIA+5)=6G=@L9XCO{ z3|QX>uW2cM4c~#pJ7Y_o!i9G;Y=w8MPd#rCg)T@W<&@cecZ+_wk$*=94^TuDC@64x z^3j_Xm-x1PNF+LF$|2rc7?-$&P%<* z)Cn&`u$AytbNjEChy}E;_StX$?f&4+n;sk7A)mZrdGZ}C@S*l0mfTwzd$zba7p~tm zt(aaiWJ#ctuRnDmLMPtx5zm4SsEhrhhf_1?r2KZ#@DoKX>mp|0^h-E)@0`Ljpfoy2 z4Zvw}N=|nOGZT--EG8>%MV5mTqf>A0@$SqWiQ#e56Dlae0Opp-OIswewP==2LJ?`- zI54!$GNrw;TtI5U>-Ig+v{p7-h`Hw19^!ViO?N#_R5rkBXF=F5)RN7%GCR=j>1fS6 znlLr|?G{@9V%&1RIuH7SAS}Q|Zl83$HQB*ST=_{~iGlfmp)9TLcrwlkZo5@eBhE*W zi__+I_Y9NN2_)(Q^+d^^At@&hUoiW@ zS=)Jlk3T(3(jQouW6pBtt;Aj#Ura5D=tO+Kfh6{{5YC%j-xop;H-|ohny{7g8jfi4 z4^*P;=B<2ooHba`_Z@M#4W>jpD&h)(#1jy@$?yOnUq~=_SaHnc^~(Sw!`0%MB(l2! zQt})9@B35DtuBU6cS-E6Mx(oj&}442m=E~@BFyF#WC-)eSPwSA>Zk@P)`hlU{P#HXP`EFW#Q!!#|DMO(Ah2H((k z#8Yy@Iv@Or5!`ML=fmvy#a34j;|OgT-%%`D;6b4zu-&WqYQW~6%N%u@lOY6m8KNz< zP@&ne_;Kvio7h@VeyyByl8Id=&3li^8=r;``2s@wcekr-kP_eRu|o?wsY&nHPLbM8wE zI&RS)?lKtB8Uv`aJX%x#GK640ZW;CFL^L%=T5sbanf6$mL7E<<3Sr4&jPpoKk#f7* z#O$w{-OW64-yn+OGsTUNA3n_?JtaH-vP7NG{>LZF3HX;QWzi#LJjE9=AQxYL>d$`d z3o0bCQKILujQxc)$Fd9k?j~ zPK$-&XNamDnL0T44=xO1)Zl|1YffBfLY0p|QsSh7H!pEQSCAlfoLoLc6oy~;+{hOL z(R48(83G{+P*h25ON&PD)?VO;6V?G0E?<8dWUAU1L_M_zA6ZmUb+%Z>-r(wY*>}z6 zPv`n-7PMH8*+cg0LffC(yPGTlqf-phQy@0os~Dah_QFA9Zbz6w|6z~Bu#1okiLT!= z+Ag~!{{6bmVfu2QC%t&b|Lu?b!Jg|Ig33$0(wMeqQWO6U?zQVwQ+j(<;C+wNZFD>b z0X(>P3vZGq@f(ElbV$hxE*hLN-^onK--YDK%xA4!3z$S683xyVT0ku*7etZ{@zL&_ zNn;^l%|DU58QIhK>wrChmX4};juejioYl3)7sIN&jH?;9r9L%vR+J5FMLnqgbN=P_ zwk1?#1dNWx{M5vWM2y5;n>{T9X(9BqF4;<9<_F|q#LV$l9=R;B@ zx*`tb?q&q>e_PeQ3C+GnEo1b6B$Bga8;1kcfIR>KA{Q6NH z&fsnzn#U8C`!`rRXzYrNeD}K#&i(nX2=k5uFdj$nAK%Tsd@&knkMb++(@f(Etjb&U zav@GIi{x$jM1OK74>rz~-1ESqhx^zIBT`~nt71+>>NpN!Oq4G0Fa#3H@4i@!C`!(s zd-lc`P;g*puBP_CpVmD)nNI?q+7~#ga}*2wE-(b3zz?Y3H0=WOfLZ!JfR~Z{NNy?? z)dKAU^U@o8g0j!J;-dcWU)bjiuHx9#0L`&R5x{S#bT0BO@R9wDHM=y*>Y(R$6aXv^ z?&d7^+csN(=bYbnF)V9U-12b{+2E|^P_rN`1HqvH`cD}& zL?oR$bqLEO1Kr~}m><_lb~EMnp?9NtU76ih zD7Nm)Y3BgM;EuVq)qZm$Gcp%dwNoqjy?0AlWmo5j&%5%+Kv#KV=`1N8ni38&Ay1M~ z;V#r@6qhOa|fGyNPJ)Pg}-37jt( zxxVQXS6G`!djk%CjGLA8Nx|INyy|o{ZBO5IKHU%xzqYf?*>yu z*)ua%I!PwJ{6PPzj?6GB=~-1}#6Z4jK`h&f#7>uD)xCG~05*9N2jW*zQUaK)0FaOM zRF!4uCIbo{Tv<%Y{^J<`w0gXl9-w+j`sHTNb$4>S+zD@Bg77<-^zje(jla7lAMAdk znk#@vT$@d<%lOIr*E_Sf?#XuQqjC+uwbPY6iHh17kQjQ$v8PcCcHxC>@F@nVK44qn z(XozxNpnC0-B`24w{7TE7F4C?JeLyF`2&ge4q1ecrBRoc)l(fqS+C-k=oA_w=sP}@&6&Pc)P)Fr{#QzL<6n|?vza-*T zgLKQKOdan%YXy?))%TS;QK+w)`bWG0YH7rm$?wyL35FA}mFhLX7@({;o(Cy|<#45gq#u(d}6|qF!Eo1_QWB63zo# zY{f_lSqZYy-mSpi%s(??g9CmtfxuuVzxX4y&26$uiy{r}N^T~Oh8WiJ8$zcA^f+~( z1bL)+By+S`vvM|l1=3wHX6Cm?9UcHh_=qON zqYe^O;aJ}CwPige_A&-0R31>klVC$=O14Ep{r778X;8I3E)49l3g4 zYv)+wyO$h3)&ynuC?{cQ3#^BEthQ%p*pFdnsAryvC}_hzJ8pjgxOd$hS|9omvfMcy zeoOq(Ta6hNh{?UEWD5Y zl?hfNl-09eK}i_+DCGNcbZ)F#eFf70VWNkMxxwNgg9og$-wH-jpmOW$k!Fx|sM~J8N#aDYvjWLtR>XhC-@RA%_>n4>e}uH&s=|uxM1TDq*1JmQ zw}Hp^Mz8(s1NnWmS!$#;mdFaS&rIpZtaj=$%rStd3z3}zU??zT5FQKz?Wqu&0 z@*jN{tEeSX0$k%)o0?2Nj;k<-=R5w(thd{$@|_~C$L<4^sS(Nfd-Gx(`x&Xz(HFY$ zQVKADcQbT(pB3*@6p>tgb?I%;si!OVw1DBGqDvE9v$;xm$IQbU+5aMq{Uvz7F70^1} zjkHqW_$y0BJL8Ea_(1XV=y1CR@T=>&|L>9PiI<9PRBf-QvHU|wT@mEYNm+uv=`a+& z{k?dZ>?kmG0qvjFgnn;e3P1T6x!=fxR0Rkp!eP1I) zzO3OlStc|>ABe9-s^w20FME&pyzQ^{W}Rxfx_8I5WGVCC{pAHFw&ujm8MrCVVe7PU z-Yk>*Ma6dEu-Wx6P{947n87Nzje(n?L$Bc>L1q?8>*T-o+)<~=2k0lZ#26m4M0q}L z4$M7(_AccN@$65v?0Ywmi zW>$OD>@M7s6)Spv%K?mqQ zb8Qhk=lL~U86E^KeZ0zRpH0uKU#K;BP5PiBOrVckW(l_C{0I)13;5c82KM~ACfC{X zo}P&MQtB29eSlgw-jwr{D>xAu$Fb(%pgs|;vblXQduxNKv!n%A#aVEqHLUf09BxKx zwC8;r#F%&JBTj}}%_Cyawxu|;-f1@f=U?tK)h9meI}Fmss?@c9<8RS}kXQgK z-WgXaJV?5@uEA=c zG`O>x^$z!D?}e4OB9p1vI-5>UA%=|MCh#|96Y4y^APYqy5dQXl&`*y2Fqt5su!nWU z=f4yJ`6qaL2BJR}o{d)AlyzKC#J(i;--wu8BOT8BBRs%g0kJAdaf>iKi`zyiC8rKoqo;GF3Ag#0pEakC<^{?kjzl7FS|{FW(4lA1a+ z2RHaGrj)PiqG9!PoBqyk39Z`-g!Qk0iMknOq4JY+1(`j=)~upg_7YbHYpcVZPIe#E zKPhg0s@?zj(>6X-^miGiZJz4CtftH>b6DZWj8YYwoM@vTXJs|9xx%PiJNHvcJzm|c zKgNLIa0sa2%E#F`1t4@a&OhCHxx2+@D^Z2{FcBr9t1&7K*I!|P-hK4CIaE?@(L<3m zCFX2Pl_(pcneQusEojmE2KY7fMPZBB$E%Z1P(A!w9}8IvuPj8EXB{J84xWxS{OZP9 z@_QFN4{qne%Fk((O&30L$vL=LRr+>1jitH&osp_-OYhMQlYEZ}*)rBfPrM@M@0-dU z3{n58Ck8i9kd?P{?3Fn4SS^~=7=ih+$Yp+Hf)kkhJ^GqeY2dN@dH+C40Ep}DG*?w9 z3U;0Ed4(lx0l%j2ldo1H?@={(D&5v?`7pRQ^vv7OvGb(98|HIQ+m?JUF)Vpf9k4jV z-DKFII%Cs|Zx8IwY7xWIT*%iIVMw531Ewh&A^x^Lq3Z{awu16qX{+FgE=4gu|19Yx zk;IKFx{svtFkD<1w*#Uaf3ZKh2$)Bj$D^G&PM+enXL4yab$o5%D~qT+sVPQtdci76 zN@XK~kcBpNsedG0y6R#T8kC@`YS`}ynSZn9!oc-4^V9)j99eIHV(Y=%kFaUYflD_a zfG-}gjcr_cBn29HREAK_yu@c?XRz|zA6VfDjFyvI-@DlX!EJCG7^47wY2Fe;?l-sM zKXcnz9F~+hf#)@&wdKe7Hfh8a{OfN;7QD5a;E_ z&-teT#ijgAqeI2E!HeffS?{KfOSU;(D{Z+i#SOpBPXn(Y8({JVx72a06)60p-$ps> zfoHY?C($|||7=iyur7MOZ^260VpJ(Y9qwC~|Bk%G##rvA+z}1mX>1PV6DE_vk3ZU( zKk3-+JC|Ae%7#Z$g8{VFM;U2P*={cBal{62!7Eot`M=63U)n!rJrShr=>#-vYxbE; zQtQQmRn1&M;t8zFl9O-j0_KE+)sdaUd33*3AzOxTgJnlG=b#s~?v)CZ*4$w{w3mC5 zWl+RY8{Zb4CaFPgY(gaE!6B+!!Yyw-Q27!ao*S}yDqeJgwY3w<>zusdCXZ6UG0x%# zsMqW(#ZVM1ymhc;4uxy<18;bXE{_J@FR*S=#YgG9fPLQk-GJlM=l^cKOoAW{u5xiX z06=f{+m2iTOw?~bM)q9*!`Y{f^JZ~Csml=+1aO|LF-yP94W!|+EL?O(@9#TjV|qjc z?KIB&&adBn^|_I!^1Kd*O{?4e$19Z)SfxeJlTX_J*rz9Iu7;a_)u)thhyQ-9i~%DJ zfan9?Iw{n5@RJUBV5bb25v=aS@Mj-oj-y?98Qi`0_i&+>zHk+Izh7bj0w8o+vWX+< z^$m9fp>UA_MQFq>vI#77IMggz|BE1|$ zVe=G^TNB7m*1Gmnyb}P0t|)RF%eUVDSfj@Ou>g}p{LCZawsdoG_)MV(HN5=V5gFCb z(A-UhS8Auc>|UYFtU)%8y(YOEw6OLdYo1nyfD0}u3u-Ap;6ZQxFzkp1O`$kOR=PNN z0H##IMPSlrUi^EPIQ-=%zy;s90iDTn5owWuQwb6hsFe56radF{ykm0hPw4Dx&DNdf zKcc!+S1gALk))AHKTmv+XaK>R?Xvy<8XFtmUCCD4RV^-E zo5Uo&!OV+V!u(WoXr6fnO7FVU8-j$jke>R{MG6kGU4Xm6FD}bV`$DGue%u)O><~y5 z!=L+O4tKaCYXYc}bLsiL$Yz`LcO_>nO~BB>UfvoX$9EwQ)mHZi_LkLzJ5M4JyTCN= zhZvgJc!Beb!#Qu{U;7; zPR1jmyAX|kBtDRWW1{4NJ}|r@Y4H|HP-9a9vVh~Wtr&3IaqB%LdrAsD|6G;bD8m-e z7q7_LEd08z+J`da1V!~>r}uz49440j6?m5ILa6}56bG#vQEXE1P&eQN!be+j{DBVvdok*)p1a&A*y#n}kfkol5K-`& z>CtCWb5aQh(6MV`JQiPLve5JT4DqG%P!XDm+JvTTAP*(7cntb?hvi^Ot%)E;L0?ImVqAG zX7Q@b%WFgI6}%+CIrr^PcgKpsxNQKGUpFc)v15CG2OW!?j}Rr0O(R~@Z`SI6sr1Qx zkwdrIn(+OdECwcqN7q#dfOjA31D+@ zN`Z~r8*AfU{_W=j9}d5Uo5w|PU@rWQM#V?z{TIvhr@GpJ2zmddeGW01i-@t)Z1wA1 zPBYQs$I~+B)*j%8+INAOhnRH@D>&3w|0n&2e`#ODDE?<17gNdz{B6kmwyRWaBMwqf zm9XA&u)A}mn}dVf(3SEUsUFETVFbt#UvK}XiT5$Z5dNV~zZo;|GmkaP8Gd`z=qYPx zC@*W-wHKJ^K%J}xBy5tNoFV7u3OF{T}5A$Ld@MfYh> z(WzEH6MsOeq7>>o0_=5WIqZ|inkXHrl5)Y#-_e18q|NwSqt5zt{u54)o)BlG)W^Q6 z8D}In0AKuaj7j#(kw@P~Ub6+F-%Y$u>RgnqZ_Kg_T45NiD)K%RP(<3F}t9N&I1N-bsF%exS>ad9>! z1|?Hu`tgS55iJbEQ5L_Z__gx+S#CpjOh@%`kYdD8HeOG)6+~P#axvb z-*x#pUGfl8x!R&1q|1 zqhCyGB%e^3eIy5zkCOA2>-j7C+xQmbk*PCxIf~C=*uB(C;3oJ+pkGuB<=~ zA_wzG;oAETb$Pjt9TJ;g>h%n30>nbuCbO3(BL_bD8A1f1(EvHj2&`@t@o}e2Rq?GF z=%q`l%Any=$ByFMBTnrXqYc@tf-ZKxM2C^uojteCbMTS?2YVefg_-XW1HdINUY{M3 zEaiyT%A2QYYbiATUr~}6@c3TEfL?N%on2`d9D@oqJ^%IWkSavHf@ty z?|CHV;i8iryfdLPOMkVw1b~#zcBrifmGf^*4f^4U^`|lash&Apa&xUv2*iSQ887M5 zszKi`v+dO=YbnOTr(tvLc^1cpeUZ;A&jKK3ZKW3Z21w;M;*I)KWOtM$PA01(?+*g4 zB~cQS(>uWg`PUM%S7GVNIO*Ox0WeN2%}$%&M>!r=BHjj}yjmJhGO4~C6iHbkx$(&N zm@nyook4^0=D*RHUGRHDmcUm2#~DMPlvmQ0aB%EFknUbD6oeeilU0;6eIvGhzF1~w z?(F8sZeIHk(tmx|)pLBtf*&ZRV0F&-(jbzIDb|3(6X(qPxp3A#nZ~ujinCq!KF`=rAjo;Z~Rf7poAuj}`(Ew0h z>9*f}WW*rm-Xd~T@%z`aCr7~SbRwWMCm6R5Hge=EI)SFckrVyl;lBzw;XmtMcp)|~ zfP3V&=T-FYPIJjS7CY}9W-iLZ$NP>}eTm#v*MPzR=exY2)o&+A8ai^I_o_T?$r~L= z&QSQ_f_T}D`Up*2+Hw78D?>>^*Ok|uEG@Tt4mE$5X6K`K%*C1UzB`WxGUNC~HFVB@ zVE-3e5y&;M2Ov1Tr(pO=UR6aMwfGFyzEVNlc@VBMvh1F^J;J>j-A2FWujH8?lfo4=RIEl0z4Mr;5EwojRbPV!7Flgr#Q~1i%Fo=zH?a zzY#?EJDB?qfT;kEKV`jL3sSVs4Dw^WgKgUrN_WSoFn)bv&C>`l3HN&##a}l=U0+yT z|4`=6{y=zc)i_jpD)uHTJUjo!^XSGP*#6rcYJoiv;J;JQKfeDd4?lYTfr3n^kvL)P zh&D}vmGU7%% zu4w}}og)tP@lhWo&OiZDjJ@*YlWi+)2JVU*1)$Tkz&=jG2Gu4%N=FFDr--u4Uzgb( z_AOfl80ee`2%RA17&5!p!I1F+h87_N6rqGIlIyvuva45?(58UD0|h3<%KPuz{5rBa zvN?`9F;jq`@r zaecZsRu}*$)~nU=M$4qw%q1AjVF!3He1O?Z;B~y?J!YYjb$I<~FURIBPu~hGjT7tz zcanhfLz{lEmLLf8@&Mz@-j?aM27K-qOxj&h!d@gW^FrAmb$G{Z?PyBSlZwD}e1JZ1 z;S~EsmI4H6tv2-7Fl7QAt2=q({4r>0I9%Ts=bJ5OdT6l|dH4qkZEbH}Ronz&HJWf- z^xA?f079>Um^>iHjL&%FIpLP+b?KmJXOb7*To<|bJMl`LrELa%#y@h5t995aWP&Fy zwvk8Xiy)7U*Il|Po^Pq<;9h(yCVDY_30c!W@bp;^{QzV$c-@Nnn1L)E2S2)Q1sLZ& zpQYMOBJyUiAI~jD1l?Jb$hG3J>#{UW?EKd4EzOT#UNWETxW__iujYc!4SLm-hP7;1 zIUwANlbnF|I~aL@IKA3gFRHhTyqexOV&O#D`Ceu6#Zh~j<-sm!lYvsR_u-~Dog7cqul^=Ftd^jtD4n<^LKxXs{oP|84 z_=Y;0Y!H>1i;+NMFy&%*rXwHZ6l}qU5PLqcb0`;fBI%$8FS+p)(k=Ljiv*SGwtnd% z977U1F!UST=Ku{h$gtvi;4Vr)CkL<)A{!wbVyy&FP_$R=G;(?<7SC6QI$ z7FK?X&VR-c_RMQ)Y+&yZzznnb(KteKmBIqS*p$>a%A5y#RJT$``cO%Q0H7V;RA#t9 zUrZR&TIYSN7Z}YQXEf-g$<3=q#K((rYSd~RIgs(W4e6|veTNgp1ILk*yYjxfwfC%F zi3_DHqX{=jmv>|rjJ|ZzErAW>Nw{%pYl7s2Y?SeO+;=d#`b(ONW%G&^Bth#1rKJ|n zei{aqL12n+~9jd1R)BG$Be#LP;4{gbYy30NLfWZ;cxjwXX?)7s|quJ>U-hiwAKAj1^F5a8MZp;sJyDLrXLj7BsaQEhj^lL;7+gH3Ffx~ zT4*x_z`7qk479aZ&OeX{{i-96>QH+0-LE>fGTAdD`+EpkOY|C;ZzG^y>w-oX(4;Az z7c6^u-dp|BnhvBT0d(sd>-7%8!JY;AYc3o%Qo!S%7yf7d2D`$0^$Rx#0JIM>gI89M zJ(s0^===R|Zx>~tZVAWSZqzmh<<`BLxxjDOAqa!(VXh*VQIh~9gY z2FHX%-zH6gH4HG8G=P&)QiwtTjw>jy@(LHRKKd0k^z^sm`hSMyaj;i3HGT{J{9q^B#xnCY*hl$*s+Hjzpo0P z?nH435zc(uDfC3%Zo}@|r-dlzM;m<(#6I8SKO4t0p9)P-f9(K4Rc5;bV*QjlJdEso z89j0zaq>qmQAN3Hv&5w+WA?C(0tYv|PY0aRLrq~tAv;0v;IqKVD@vXJ48oA(7OIRh zqYhge528=5&^ly$f~uv+Jm6J)B7R&=Y!6c1v|{~y1YP!i-rK2;%BL@r%v<{L7yyGC zUuDCn3*Y{pZg|%`U{eQszM4Bp%T&0MR2Vs`SWCdJOH0B-5i7MHWa?9;=n2PPo%p<$ zT`Bg?;pnsyXqq!ayXu23)`4Vny@J#pP^wUyce`n;k{y>Tcf*fi}+c9b>Y zaCqBIXVu`!e>8|9KqSqbJG}lnvg+_J)VeP*`yJkl%(L#`kLbQpYbS3~m(MVsp-mcb z%xfQ;TvS;XE$*EY4NZ@oL~_T#>Um-6x)soe^~nxbK@+|BJ}2W4 z@I-dU?zPl{W5I zYOoxPh1`$!prDF9{PU$h*%D_xkUh3Gkf*uB1$7jU;TTaI>}H@gmC)D6OYONBhfK~@ z)$QvA;c*Z{T((>A=)#uG#|lsK)?bl(GeMQWXCgG_7H`HYcfHtq3NT!{ogfUW)_h*pp{@4+(MD{3D;ahWX`uKV^?75l%XP=6IG#cV%UA6(O$! z7X9q4x9n|9o4CQ&_Ukb?_d&XUf&Sxn7tmlA=NT+>AAH2TF{eBre`ixrg2D3*wJ0e} zGQ&jg4g!Bc3`Jw!)e@FcQ4v7fBTuWdTYd04(j}168^fNfN9YZ>b^ac1&tYi$(oF+a z9inT%fEvKs6uZ+y@mjnU=^VM-1)W8oN7-XaVIuF8qHf(Q$DHiD52*292Oh9b~6)#f#a*~dd2A@sNFi1xF?K~sYfS-&^J^WvQ=== z+SMQwCF+EF1_8s!{!>wc>b>yk+%_R3F#0i+rG}bs@Dk!s^>k*8dEVfAC9(u*v<`qz4YG#ji=md`!?58U~t99g11HrTc;!Ip<# z%K+~hespseR?lK4RiaznT-++LaVO?h=3f6gw#Romb8452p--7Y&hYDf*;~iaq}||P zPRk{LO_bsW#qSVZGs1ru)^0PT${akaFUM?pPO^meP;ZyFf51|oAh*%b zBp?!qPy6C-gFskjvzovOP}$ zj1<;Fv@9&Y3y2h1r&INW{y)nHq-eVy(~%vkp)DeK(zGG*2@(WQXfg#=1Py`q_aOb_*@{EgGAGz*E)3`eDW@?-}5UUelA&%x)*iu z>8FaNw?jvFbWu8I_(2sQDnJML6PrZg-(RYFIKhl|#?&!El(G?ULV5YmoWO`c)q_Xt zOy5|xf)I_1>@;fHYcTOe<2Wu<^;eLD>*ed%qEdy@v^!5%lkMfm9Q&?{Kt;mjCw>C~ z)sTfgDCRI0R1ds12@^o-OJ1;i{|$61I31_H@TmVC#Ccxb^%g&2lpnT6s;3`sYo7yt z0A6a}|8=vfgLUsqwMJe32QIVko7dZ}oFhZ9u1GMjepT;ccO^Z-RduWWE~`y{EsG=L z;y3*s_?r;z$Iio|VjL;pi}fe8aU^ql+^W$^klFP4(2x$#F~%+(?2BQ$@7eI#W&RN@ z($UMEP~7g1buT`ST+KrJBw$Gntf8Ygko2}=;n54&AGPxFf|%n)|!|y?of*b&)Iql=hWCY zX<+um;y?I?nHxKhtBov~!7y*of4>i8O@DmB_3}a5Wpu0F2hMA9n9L|sVC}K^Q<|}@ zfP{^6^`~ky-r(E*bYTW z;h@^Oq6w#jAutKpnM<;Zi}USKJra0O4HYWEW9WMtR1c8y9i{P>ga1~VA-rFpleZ^9 zr9J*2{4)&@1@1N!j>W`|fcHH_lVrT1dh>hktSWK8sQhcv=@T9>3&IR)UMG%W<$E@+%>%>n{U^ zO)D|U8L4iY=z8`T8Pz{XfZtj5WKZvm1}0xd(A=!lWfH%e*{3hLro5?#4mQWg2WED& znk>-IdHf#TRV?5PT*malcxRB|>!t$_(nu@gsqte=Wd4>VYoLZC3GR@_X`d86?j^dTkI_e~$ zwJM#*l>b;is_Np>i6Sh0;|5sNq&9;egx+8hbx=|OV3BFJKC}}bDMqOMhj&>1n!KNf zt}mz%`UqukR6pQjFXyVNvAz!6PJcq_U%!FWysSWUU}x<1R5CC8kax=WqoZR_*VYXO zuuxs@jDe51g)anM0TO#d@8W20%s@$^iXOOxV!zyU8BewYsr~kE4l;=$hmvY$@7d#w zQ7|9uobz~?Il>KF>J#-|K)ym89{Up|4~T9dine-vypDLMPF92!uoY~@* z>8yNCdhi=mUt#F}$Ot2c2Qo(Tap$p{7u{|~Fi+nc@hFjj6CltyBW{X%K}DO##w|Ut zhoqFi=;r6o#NP0n!EyUB&P5@>#@O~{p?t_3!Dk%8OjN3fSgB-Ls-H}T!krsMOSFeL zAOZ3j+ouV7EZK9qYu+M+HC{R9a|UJL??&cll*tt3>s>_Pap6LQn#jRUAi_=X81g`s zax?-hc|DFhzu=a32f5yvls^kb+VJM@r~n!t??&~Y(#$g#=|}VnZz)l@)J2(F>@hFt`WqZNz-Eh(@dmgAT3G7XFBs2D#DYf_M+Vj z^&iji1ock~-m>h%jh+85Za;kNsD#px&OLj@Q3ah4w(zECy;dWA|D3;(!GWk-jSBrM zPYwxug3hZY86Fl751M~{@3-Sa*A{517xE3>K}ISV@}+;kCsi5*9)D^DIwU+d zJ&~LKC1m51>;A7`mSOE2#tUDEGsZ^w?l^Gbyg|MWI4j8%f=FLrou7B#c>Uggw>-}> zAHy9TJ6*qemG-uR7#8(~Bk-LWOW_o_Ne&FuWvjfa7Y&cz=W1cunxeBmV#;_?q$zE! z40M`0wPD?T2+RFO#scx)ao2G+H8RZFZkkV9K7GwD{`4M^SLcV6;{mO{tL}k1HBIpz zFCWw@Q2myYv!h94Sni?zd*CEwC!~F4&zv3?v>3x<2edKP*y`%~+ROE+Lq!h%!MivJ z4^&7Gr^5bg3U{gfApuq;U^gKcgR)U8!IM1>nux4lZL$pyYP>|hIykbLyM(=-;0)Nk zIpGs{yO)QbgJ99U>C2?uLW>FDG)9DjHTj=#Stf^$R7$lW-92@^1IdCU*3PDs79(%Y z3qOy?2;uz_fEV8hd$K`|8s0cP15H3tAG`kA6M@|~Bk00wzoTFNEMS-kcP6j4D-%ZD#Smq$^F8uy%)ZEtPj9`N-@E;HKn+FHN2a#qUn5$~p+u1Rt<4pj zS)n||u;a6F7v^OzbV8^}j?Y3|FuS({pDpG*#SL2kEly=6@bRyhsnhmtax12LH|^hX z8)n+t6(hH-mHDqe_4~eJa3t`aPtm(! z2W;E~L8veo2ZCr)w^q;FfR<9XXDQmUz`zMlJq0+?;OsGaK!gT^A1DVs89K$O%uDOB zrR2W4aRf8D3;Y7bWhDj^Wy8_JBn{pe-ZXRgBkIniXUiwr0~C8aOW^*57U6R?O<6$| z-9+UW|J~4*=KLVVg_^XJW_3RqGSM2go&9TldlV6$Kw=Aku{3+Vbo@(4$n?ToLhyWM zB#W3J!e(RgXpk{Flxw%^_HmyvF6C>YRqJr=pQD%-md|XlYpFz zb>~WHDc2M)q_fv}@aq<=-ySvJ7b-MWlmsuODwp>-sB3-BcaWr&dQfSFQ-W@FGBT~J z+avurtli6GJFZ3EDR9ST?Pv(xT{XxAIW^!bRMmzMxLOC6 z!pJ$cS1nvg5Z1Q_fl+o@NYAp@1nb9xaNi?fzeB>e1wbp1IRJ3%tP_>ZKRDEvoBueA z(bU9f?d9G!r%B*757hlAxn1V3h3*WlpWb_4yPtRGrteMCg7K&Cxi+jHEmnmJ(c*>g zlu$HX%EZ!U>Vg$dAa1}l9C>|gQKDy$w$02jU3g$W3MJHx!3`4@U-du3khju?L?w92 zicY(s3E1dLPzS{L(a6nu1li0iuo<>;S&9308qi&m!W{C{l^Gg!Wl zb5uaOm%r$r8X&|S_WyWA_Q<){5D>$Cm>7 zksp@|v2hP7w--z8z*#j^wxKv_^c*mVIfL)WW>XZ3@a-_7 zI}yf*pN4kc!VMyu^M6TPr)^kG% zfF^2eyz5#gvz(FY42xZ=d=;@r_yya$J`m=dy&h`=BG5NNBs%v!uAMvZ!K#|s@@L}G zsPrxI?s<*JuafgM0ug3Bs*0(t{qUg2hLqpP_4mXTH_k=lWuyg7B!DyoBL{7mSgZ%;9kjJMJn$G znP)LgbK=Y>Wo-|<@Yb*0kCU2&n63)VV&RdOYoUzOr>&8spZCTNanvGj%o(sAPXSPrH0Uin&Hd@8xG1+Bl~{&YW5Ly->eqift_Zi_p6`c)1(IH#F=kY8Dx=(9V(^-9l^+4@ zHu0Wwe^Fe22Ri<&No^~ofVJiLUGsPK-;?MikD!`ARigoGufKr>E%H&$jXWXsWx!zPEkmwFTpau*`Lcns2 znr4tY7lsHFYnh`yX%un}R`8NZy+Wju=-@(3F(US@9O0a}@K zbNKJA+3JY>YeTkA;oVq}qTEY*ZU+E!lSca=rPoVJ9|a_|Xp-%6&%zQ!Xuk*073u^P z;zP*q#Fgrb@y`zfZ~cP)yFD6n$ZY0QL3g7y=_}MO$Hjld?3@Q0%pGCAm1bNg0kwLF zwpb89c%i;(sA>*@jKVjAd~`6zhL^#Gqg9>gNU3V;)XIYgFYY?;FRc=Xec`NLKmUy< z@EDGMac~0&VQ3Mwy%%|@`%n+BFvRQ3^3>OF3=NuuAjegf&ny^MVDVYT{b&n0^7U<# zqg>bKxuaKlY&N-S3-q8$?cfrnjX~Qd;3!A+zWO1EOqPGHf8S7|C+bu`#+;U^O<`Qs zKYFs$FEX4`PaVn9%pb`8ar0oFF?Bz4bA3t70VO<(Yb=SC8 z`v8OaOEgC1vUp^WY?E+Rza$-y>Qc$~bEWag7AFK&~7T#rZ-DBYO(NrABu-KS@=Tr7&x~#xJlxSwT`cA+5mf0 z5(1t=q)36E1xMyJAz)(3X_pJZQG)DXpB8L>1pfK83DAAchSm&0z#7HtX5tCb_6&MT zr6n+bnsJb%A%j$(95rvzugJR_`#|9PtVZh2V^TubhqU7X+r%ZCdkeKYYX>8;tT5mj zFc4C&mz;=t{rNP3b}?Wj?1u?$#cpg!7{V`6*emDbP-8?;p~#{2L_~xm?h@7B54|pX z+JN?Tw&?B3d>Avd}_s=n2BxB5QXl@k~M1Y z-+eokb3VXxQlZy*p$m~!YTw?h|LDUL90uLSK8!jxwZG*%8*jq_i$6Cs*X!E1lsuPw zinTR*?8MXO-X^Tjh*Yr|tl_4^+7KV}_>F(a`srtr6m&2%Cy8Cs@4ISzpJrmo8c)j^ z>~w1z-_+Z+3(!X|u2~)=(XX5h-GifV86v?}`Itv7Kyi&Ubc_D49*0uRR`flI>#EL` zPh?TPOd?Uzi%0fNnI)mnk=1_}5P9hQ*Fx?5DDKm3?=Mub9oF9>!jjd8U{xVmPltnEYQxxOKGkQTg#38)Gjk0NXpExB1K^#+q7ol0#fH?=)|Ifdho=ErgARjDS<+Tk zZ_w)v=~<9L$%}*Frk+Gi?=VJhoC#iHzM369j4qaFZmW;f&7?kY2)mI=fN>Gx?~;@c z%PE3s%eT2Xhv!jpG5m}BKY@&1_re|YL8<^ISWm5H_0S2V>8b$DD&FlasQ;Z)c}fN> zr(A3D!gR>5NbdsOym0!K%BI!nyl<-s$3H>1`0R-2sQyepM(OdqvpSHk@gT;{{p`=Q z@TjObiHh(_kxDP{MAYf>KVo#Ez6#jQqtl4;)fB#NtH53?15$&*6Y`1XNRshD|AK#n zg7fm(Lvu;mx`pYIgbnDiGRL^QI#wu{$NI1e$NlY_+0Reonk|Vgz5A54M;Hct7wW9J z?g&S7oJz(JwBcQ9=m`=>GC{QZ@|~;A+X&u({GIaO<`L4<<=dwk3 zX6)@@v%FkQiFp=tIbe#Sz?u--3GhWplthc-V~0bV_(`o0ygU@m6)4%1B@3_DJa+mN z*T@GyMwex(58NWf{Qe}&J83l#|3$B@0sMytn`0}ZM#dWp-b=<*#0s~l-^YmUOBG|i z-pz_UdPsp6F9w3VAzvIYTd`JxsuKW)mmq}#*U6xJ_!d9s%BqW^2JR={3A7^3oBx7d zDAZN`;`br9f^#?KVx?)jaVV(~sBgQ?g(RUXkb@#Q{$1FfVnp%xF~}zo1tGX(F}7p( zSh?g;@`N;4!?hnaH+g$#tUAl;ZNw21!;Hn#HMkGQD6!;i*`G*+KicV=oOF;g4ZL)8 z22mMqJ$8qyMxGcIjf+7N%F7ZMat+e}@1zf2AXH6oZSGcg{A9krqZaiD=qI3*VLB&} z@K$G>VxYy!`ngbKO!k%k9tWZAwtmz%y>m|)opWJl*^Te;Qu`O&HcIU_!?1zu9vr=} zGS}+d1hIdMT?f8)HT4Fm$C@ll!>TLlQ0WU69eX9MyJ#DT)aA2~)R2{}_-?JZjrtxh72NAUJ zo{>YVZKh>61{q0j$$&qV?hm$*tBpU%p>48H#8b+}Y6T*V1P zHfcqs8m&J+z5FN-OtOg{-Ehvde&F(|{v!S=^5|F0`-U^U8Qs;dZjtH#xO$MA`dGSW zc&+vkKTTIy?GC4(1&(|N7}Sw+?k1xA&|Gi|WzbpfWD`=u@B7EJU4@GPQ&1yajf?{|6$vPG9HHvI>B`oI*=t zdS&&&*|R(Tdiolm6cvaFrUJhv99$d6L5f%oMAIcb#{c^Z_g_T^o_ZWl$MW`U=F|A1 zYa~8Ey^cCuW!|^*&OnSsKH6?CRmV%8fBY(OVF!0F!mc=7WoUkVhk5caSmn61B*=4Z z=x{c@@T&$lElClQ5GMsrnYjSRY)zL9bI3p!MHT~fn;S-sn;WO*)|Q-)hnn9~z7;Mh z13v%R`e;Pwrt5N}x`Sz_7O;$fii(SHwCL`-7M9$GOH*-IbuilI%5FAv@D785LT89m zw=HU*p>{OKF*sdBClnQ(mtfQ{&FbPmGznhJ8B=`_R3ua>gddM#$F9V zSSC+Q*7<&OoSak+V@5`+p1<+q@(`dkJ+2Nv*7`BX_BsLa@A&Ty|M#}sdc}3*ul9DE z15;Gt!wC&+bt|=mY|NQXh zpz{R(`1J3H1by$W8YNlTSen22DTvI7?>pNAjLQWV?JdwTZ+c^^Ae3&EPR-39nTJXj zAr1-n9o^6q(P>NOqYtLNeid!xCwu*M6@;fY=z|@FuretsZl>gNeXgijiUxde`Z#Ly zgW6{OsZrv*SI5gEdf*+Z_gsE>m%`Vxcvl_neobH^gkmmyHq!oPbOqrl5tA_K2f;Rq7whQ=M^>xF57P zz^B(st%wOnCsS)mF7l8(>mzEuRz2y(934Ry?t76TSVjXHFHnKC0jCG;oqgrJaIJCR z1KoM=iTU}F8HckF??lw0Gg*>KZ19b$mTx1Hw7N`TbKG#i2*d$A&LE<3ir3Z%4Ss*& zLD!D`cbkb}>63X|_<$ojOWu6V7kq3B*u9r{qui+DJ&F~@>~6e=l2C+w6guKV!K`5R zS2{`Hahle~BY5z{x4(ZHxq5Ftntj`?z4j+67CCzW{)PV8`E6(~{jpOglOVOYfQO=f zZ&Oq=1q^}1to<;eApnR4O+P$u<7i*(0oywHzR^Q=-%s1ldOS}30y4R>KKK{o%SFDh3tTSi`@~NZrP_A!?tvx7DA#UQ zJVth{C(1TWF}fVGO-#A8MqE6^Gk>XgLAkd>P54(|;}KqKBI8tjUHltQqQlvgSJhsh z+kSI7P4T!l9`CCHO${c|ZND>H^?@a)#IJL0>8rLCQ;Q}cQTjF8IEbba^;T5t9mJ|i z6l&k(pdDM%WqbW->#AI&sGclz`z1-L)1p%Kee53-;h$rF(at0ijwWykoDN?;Bgz>t zZ|EKlANvN>JF8rF?GWD=$F%L41pB0h%{cGy(G<{}FPLdi*~%e!KSWjB1S|5<2+PX# zR#?!Pz6Z6xys|pZ$(4;Qgm5gXVFdwyOn5qKeI1x_!`qiH zRzb18gjIBK{WYAWK_Dm10T5{Htvl^X6?0*tji=nj`={R2*2s$>AA4UgFVT|PYnSs9 zN(;;RN+Iw91bX=XuhxrUw=igOj9|GMeb^-b$Kj4VT^XCgCjuObMC!qX(Hi>Febr0f z+PskcC(lK*EKH1Q#@V=WG9vmN$#jf*io?_=vkKA>0a;+qhrfd);}*||if)3g{vkcc z0MRyzOQ@)$I^;-RW}5yJC_r_@P|6c{Zm@|~6H*PM`!=n^M+gRd@5AJ!;r>^^)!uHk z9gN{~&iIK5ts9Mg77Rn8#@qBuwtG6o0%x;L;Ywz%JThF{JkQEJcxL9E0Cn%wPYw^7 z%O7x~pHm0T$?h@SX@I&=4C!XlgApF#_F{l~ z)TWn{3<%bC55-VAe6+B@NnNhMznCBA(~tz!GR>C|d5jcQxpQH|spi@wN_pJfH+pf} zq%tw$xF(66;s_hQ1sYJz9zYXq;44W#zgpsfQXC%o#dI0CQqwX%oF`BrbUc&WL>Mjb zZ3u(u`*WdEsEK+fvVu7 zJuIM<*vxW=~;w44${ptR>^l7RSe3c-k^73yvJf2^66B z*WqYFfj2HTs4ULN3soed^Yx*^7mtjXw(|2hsPFkz0i8q1mv%{GbYIF_GZzGRz$@C6 zqa?PSgW!j`aGX4^HgK^q6$4qn$(DtV;L@&tIahRm-(xLPy(X6o3J=FL4)`C0k;@hc zrx=2Spz(AhAgSAvy`8ZA>)8(-!8RVWsfnP^KM0$*H0cARxq7M~J>TrW@o(>HAE&(D z8DyhtnLg5IX6-n5{>Z>F+_uNzCXhuQI`JOz^4+u=Z1A#&UUNYLAH6MW26P+tDJm3#NSVE|$)& zhu$p;FfXEkhG#N$X62PIq=w)jF!rmX3`wmuCx2Lv@c9hK+4h@jzzQfb?>4QYqi`vi zx2)vTrrXxlv)wVRt2OI*z`vI+3cQxbI5w;F?r2%bm92|hQd}&zNH`pNXAdljPWx}7 zW%T{t?@2xs&Peui_AiW84}nKezB}OQq~TArv9x_XZiUzsK313+!l!zS zefiLGU<fqqM4oI* z3hy-)Ut4$6Q2e=Ya4)LvJE_eA*ez}1PyN@GleD!{Yp$p`i6Duy*2jHU`c7P&s^!c7s)!ZSVrX6H89`XZs zk8((eN6ld?Av<~M?KhYdVfxhp-Zaaa{UBv&^oXe9x6RF0MI#U29}zZT(>(|P- zh^6t7yo@z*U_tabRDA$K5ir0nE!Ki>5*$%S`uXAD3~A5-S;~>m3a9*h4q^7tj>}nG z0h(O29%hip)~iTT|DNMt7W$SZevO%hygi8UJ}I=lo^0`(Nzvp|S68|p1|P-{avNf2 z4>mkh_c8m9M4t)t%eOl&K)ol#&BE?bW>^9@fn4;&n;J>O3a$GE#N~%CocC>{wgV4; zs)j#_++&5YR+1!1Nmz+B@ct%=u`w)BRe(no3(+41alB%FNel_g5DnZGE(DjeKH5c% zztT1R#FNIUV#_7Da}~XqrQ84k!<&!)OWmp5bBhE1yK})_cub$CpJXkRm9$$Y;5Y|? z7e1W>wc%35a2jL+n^H!_PZ*-+@v0KVIF6hBCZ_z?a zO}m#_hN%yjH2m`c`Jth~4>_6UYU?orJgfcYKUgV88Cjd|E$!}oE!a?<^S=XrL3r&! zcF#cwI9uPQ=f0&P2DA5NCI}U-#zBGN6S|i;s7UgQ@%N2C%TGVLeCA?hzvN#R&M-${Il<9AumO|MtHhq9vP+96QDb`z~JSkha>Y7Qls=5cETrhO#!0N^@1K zOx-_&YKQyO7M@^tQpNe9doK{fpvlOsf_j6NPS&RUc+&cE^phkyO7_6fYtf#n1ZQZ?7|Iq!msFyw zvjwrVLEc?3|3OQ(gH?*IRPYj;+R&x^P0h=F-KHWctFMET@L1%y`Vd(hBjIz@GnW8X z-WQ=IXD3$c=p&z19t8v0AmEFXHqb~47bPKyvx^&jQR!msZp1SmXzHSi^9zI1Lrl-r z`dYiKg4*Jty;4*NB;S(X+BoD7i1E|fmNe15P?B2^t@nuQ(@2oJ8foyM<{v(RN z>)JUHq-I*>a`oXYTl>cx&1J5d8*Lsa*i(z(R{v>o&w!Vo8?y24FF9jGh%-J?zX90i zfo?-JXxkb6bpSO@d6<_~-db_j4`kI~2(T&?N0l#M<@D6&<`pB!9&s~&ZPu{ya$elk_?TUiz;-?ph8z`g7aIy$xCJ$6C8{Shj+&sp9iXq27i3AQ=7Kh9JgaNS%P60|s{MRGoQdc5N((D#JzSjG-3}@tD9cV9zJES_ zfYZIc<0^$A6AWDtCntb66iN@8QP;fi$`CAVyRWkBZvhF+ok)j$TAnzE>TK7cthxgy43wK^DKf9xRU*Xl=S0g4Hw(?GL z5T*?Bpx?8m+}x^PoOfTMXl{vzYv*kckTz>f`yF1LQ_ZV5z+9G3(+`;rIrL8c<878HA<4CLQ(2|E|zX#IUccd(j~pNidB=mRu$%v4nNIDI{1}!qz%jGz_u*pBA2r zUH2%;Oy7ryo^`>XLL#Qix9UhV9dqxG&7O zy`7m#nHv+y5MgD0SLP&`*}`1%o^R$&_QtWc(&mU?nf8Ey=TJV>7mm7l@@3r)=OQxR z9k<==izKO&(Zbdn7j|sd7`wi`gsR^nKJ%9zcOAVKSlz){7FlG#a|30mgTeM*6qNoe zQHs8`f*`+6)c;sb?yLDz^!!pvuf(5FS^RWCO|G~iYCwV~l+7-oktAtGHeC`5)mq9Y z3u8%YO?xV}*GcmeRM3iY3Q!e(9{KT^uwCs=#jQ@bY*VW*i*iiHQ&))AsmxBH|3D=lqU6u962`h-RFG}~OE<7n#lAw@<3kG}oGd9L zV@G3sDiDbfv2Z2}llRIF>4)sg6NMzMKan;*pXY9v&Mv&eZ4)CK0vf$oedH1I%m?a8YjUOBQ3tAewyQDpfkWG`g#51@c4mpDJ^J@wsQr!VZlDiU%Y z3i}8t3^n$-^Uh^g|lH8<9mHK&9WGhMuB0;nJ|2`RK`|{N0s#>jT1L zV|)jsy#a5g>a96DFM^jG9`BlEiBxYkSaR|Zi41YXl8>E&%dir+z5jf=MB`O7O-Foj za8OH|Egd4sjGHw~fiK}nCZq||f18tIls@a#i}>3$6CSr^q9tt@_m3DyT~%G8ooW;W zBpG~9R5mJ2iC`?2^wdrUjuY5SyN90dgVeIf4;A3>^Pal{9>qGqJzuM1eY@Uu+RUcxnHq|! zeQ`CiG_&?I-7dHw%%6U{;Yic#d+C30_j|uPc@gpW`X^kDUB7 zIpLnO%iltf-(zdSfZcyeFya4fbEf*uVGYt=tFl+cjFwm~`mXbCX_AZJ#?WVl*fo6} zXzYEGdfF(w%8rME&h^|Qu*3P1=6a%4Un%B@%!0DbpT?L^U%B8DIF%1u0y;2F(`6uF zLV<&R`wYJ-u(0qtz6Z8Cf+C^kUzGm|7#h@K1R1v`U?StK30^M}&C6Fr^#lG4pI z)9)0ptF`0k)BV==_asAY2fT)Y6Z8WwvIvp}-*nDuHQgos^eqpXv2AL_tlB08OkH6W zo2Z3ajw6cJ@EJw42{XLJV)tatmZ{j91${lfWz>wm_#@GSr?}MH`26-k{-whL`wl>@ z+1nTPN)yyKPjY}8#ma)z;znK^W;FGEL+XDE#OIo@-P~syp@1{_3wZwoalekD_8{&B z9?kVF*_3CC+5!wuX`0JTA?H|Dm^mQnf&nmq=O^KT$n;+=t?h!VN>rA>>Rql==Bu1a zYP8tLX3@JEly{yTxreNK*~91NOD1(ZPy^7b=@7<~X3C`n+g4&-LcaVJH;c{NTlF-= zKDtlU4m$dcc-LK<3nH7p=Blj=-Qj+vpf@XX&esQ98e=FZvBNrWRg(+kHnveSUuOXw z&53cZ;Td9n$~{crP^fX7a13R$$y4c}N=oQx40roQZjyX4vbS@h_4yW6H@F;=0x?1oZ`tV_;MUAR_yBgHfCoSp+g;y{ z%^kBIH3MWkFfN@BSrX&}KqCBy{2g6djeosV(?YpPuEr76tSIP{JAFK-6ghkD_cc|! zPmI*u)e6+6iB6J^9@Gx84OAJ%kU@gz;`nzPlAL+T$WLOV>)eAkwb(^=)^f9T7)Sml zWR3L6>uno#@)G8O?=4WYTs*rn=PIU?9XdDc{`5k+mT31=TnS5zSNZOHx?9TTCKx}f9vg<;6G4aRt zN>gO_QXb|Z*-Gs=H&OC<0~m>9yjjk#M0n%ZC)i?Q(tF$w*@J1^m$ zcuS=0f6VaRd+NE7mwG$7Z+A|$eQS@0NXn2d4^5)gOYA=DVwUl&?&x6`oo)f6#ni%# zts{C@efA5Lgsm)ke+IPQ*N2g~5gt{{=kqR(^F5qXlx)cL(_06G=Q`r+sm4H((d0^0 zpdVBi2aoHjLC?AV9$pZyKkfLNe$Fg%bx+q+f_Dpv40|%lSjcu*wrXNihFK^Cl5&K93pnDBj>+Idv`NOjc+%lhnk)`2==UN z7;-jyIl8ay`2SYDLZe7cAq{VJjpr7q`VM-Um(9ev&xfpMyZ@Z}$0%pDxzoCqO@AnbOgJGws(Vtu0o4iQ)tMBH6Jl7+b2p`h zKdfdZe|rjC&hvaoJPKF?6L$pe6p`pXS5!TqIP|;MD*5S+$p=#6e{+1VZ@3W0uvBNG zPvLY;a{02FHkC&=Ex=FQ&)K+8JYN@$)0}?3Bp-(KM}$Z7uTDNkeav={Yj=%=?>@@X z@!+$PVW;M6PeZNl-%}1gdsQUr>74aw+}5>}U4@hav-wj)sk01)hg%t6mlR&hL9`Xw z{(9B*Zy566%=#fLhH^fwV;E8Rx-Nv@#oO;Wf&mb1 zw^j-cXZkuhq9)i*{^-%Kje7+-ct!<_fNWI z2@v>EjfZf80Knc!_0m;_UVNd$cj#vhgHqF&XL@%HCt!kS4E$e2DS4B6G0vTK{v;~z9;~-E*jy*1vUzT{)=G%^&2-0*v9Wd@!%r ze=U)B5xKEmlV0rVMknI=S?iML0ojYi|3}lAxI^`Zef&AI+n4O?*kuVNdra0!h3wfz zh)VcHmdG4?iYz56GM1vUwc3gaMNw3g5F@f>8T&rxo%g!l_b-_1I&;o*-OqjB-_M7l z*7ISoR`z!h+xKJg5M$qZsRsxXg89rOZBgO+Rt+zH4QT&+oqb1Dn!lg7-qdY_L>f0T zc>t8Rs3&yUS2$iP8(&|hgz?3C&&3~_uVhCBh9NeWHm2grKWvn+(XL8U`-HJTI{`qX zKbV)&SAruo`r%_aI4VoZr5IXhys` zaZb%uueS?N=FBN2=f<5ECATGZm_JDpf7ZjoB87i(Th~A2GykK??Nz}TfV%g=&Zy!5Bnmil z%4^zvG_A;_KeU*7xszUuK*Qek()E(`R{kHBm$^o~*bxgk@&^K$ZZ=lWi-ZMcjZ*5| zBO|)(b;q<8qPr1KN8jAL7s0;mZ(y$Fq#I~`!@~!h2(~x0>Bj^}A@IAJWOw*;kl=n` zE#FVXbj;~R23m6ivXx@Z=ZFBV{^tZ(EPkk|0|Nq1=;|!>X&&|7C{u;awO-lCkaDJ= zhKHmjskEhsLb7w{iW~MF$kWJ_wCM3;UUca-XT28bG)XRPpdH_X@A(M&lUeOq46sZd zDFsxDB(DgZz{SrA=!ftm+X*euLtqh^Z9lXsJR8i!-Ih)6fHJwaD?ch`Px#mJ+M(%2 zFE{_=4>!v!=7Zi1KPFNwERNBRv1+gb)>udr4Hul8YxSRVmJ*BGm_^8Kh2C-@_W(TQ z%api_6e_lXH$0nu@~*s)!x?45kNDl{8mRo#SiqSxa}4HHBiQh$62ndaSj8k2p2qp6 z9N+^RXv<$GNXJ_ycrWWuO_4@NZkoph0QwUNgWceO?Tr6?x$Lr=$Vf9kByY7_?8bFl z2RHNQnG}wwzvaL19%W9?od9m&Fao%?i)fbl3p*;b&gGkByQLvZ-cqoA^K0*PR zm0CQM`7tvnEq9Kk^92s;(fIwaj4NcJ0EFH(j*g98X$SDa+~RG;z0LdSA*&Rs(FF@y(bm=P6Rvs)6yf#0J5l=9ya%9uar&U@6~&3*Z0(m;iM7Ea z5*izWndwjP!k;L#R~Vwq5FG1$lsVxUH4l zyMX4>yPo2#2|>O9Jn_Wl@<~RLG)|j<-dX#%KM&Ksp}QnlPeY{53J@*93L2;*8Gl)1 z{23E1@EGxW2%d9kqQIf{#RcIS-lP~2LYe@}r&D^?Z`YIeX5q+}IeYJkZ?lI5u>{Un zDH-pTkWSX0d1Tp8mn7Y`SX~U45U8f>1Uy7IQ>p{Y`CILr;wZMAB%h|%xLAIUNJ3@y zoEKnugaiMkOTbkdj!7oXW9+1Kvj7=rz}@6OIy$uP-?0L`zcH7sU7NWr1C{ zw+OG86AN~$5BHOj z1T4k)(t0c9_e|M4cdU=(@0tjKw6tx?Aqj4TyzcyxsmU@WInWb{bO#~|D5D}RIOsY2 zai_=i?%KWBWs$=qoM(~rns)n&wD9BmZYJC+l~A*7&?O^JqnGxPy+Rwe^>F= z!R9N0rjJ`$UfpD@)rcM8psz5Z7%?|S&u~I2O?r3#e;0ru8+Jpe|EFj^Q!@89ef9dV z6L-WrcWn8k-&$-nSnzzaYzCa?W6y_gdZxItm8`KQ|eDVI1WhVXl_7?B6ywxA%%&A#j=oU zQQ+}W6_D>IZVqbf~NP zO*8qR;V#c90@2V*Kf5~^v&&bD^uHWzQu&1zC}_3otoEZ=Scy%I?bE6P>?N{0;u<7ZG3z}k zkSJ{X!YnQuLgV2^$LAj&=u2et?6m^m{^yvtLzQsadgjQ3TRYMpX1MbG&*xkw%xS2b ziabpouDPu%vD5#2Z8QBAC(&ptRF;?w_1Rz{_+$&k7pRF+^N^--^6Jt4<5X6OVeEpY zc2XuDO)4GToOA1{_7Q*;u*qjIyz5c`R#j(K-$o}ko8gGL+X~lYXP}7-Z1Nkvre zb=%k&4c*K27@ohKlq(x!Y++cn(7?N2Rw6%x=U&R3wjU_1X@XS}Q9JgCIbluXMGy++ zsW-8M$oj~0H_Yu{ZC-vKr*B}D!&v7cR(W$VC1L#Y13q>M+8!D|ZEREn*>Qi?(DAMl zmDE9v&zpP44UkZC-q)Cr6R`l5=B;n$G2r(;K;5Njx-2r*K62F|9;E#gVJ}`1JwhjTh-{x);g~B@2|E% z0aoy!cMb2Ms?lYHV`^iBz#1tQrlam?g940zBg_e6r#)?oQVZ%oW7+pR876E=yp;5;u8N z_X|MEg2V}HyC1SE3dOs)fIt$sjU=$ab1Ute8uiHih2XFqE2lDqPd$f?uc&CqSfL+KWVfn z9CR*coDw6+pj9!~QUMYy2K(+-mj&(rmvR>06OkKaMNC0G!%`ocbA~zhByMfuf}(lxEALP?Hx3!^ddV zDCt7frQC&5u%*o8A-Jvv5+bc8n?e# zM8L1`k8U`%)}I%n2%Q0(TLI4nrFR#z_@|xr?wZ8BN!jyy^YL^BT}BgJ$MCX|kJQCo zcr_(0OR+f`GsoHJ^BU1Qv~DHQgM!&E&paWL&w!w;KPMie&7?DNRAn*8B`dRC;^ii- zPZ6DFeonp*Lw4VMy*~8ruoiH^k&~PL?$LCjA9-Xt&cjfz0;d7}x9nNy02V$4pXa*; zE8n-(0=9%}oL~UN9_6N)B?7^_Aoj+Noc$HbSLWE~rN3C zYi@ON9K=;7x7|+d@D8>7(ty2An76?lO{DM73(Xu#%yq_T#-zfIRpHXn)bgwF`{-1f zqClQEe0)3duKlFsbB#{j>xL+e{&Jk&9H+m4ippWo7kjCY!rb{Hyo{=kJijh_CRW1< zs9yJ3degm}L%iGfL7K_nZ%=jf+N-7?fOwfTQu^JyAtQyd3=xf&Z_`$%zdZ%iqWoDJ!iiO>1 zqzeY0Wx1rkAPxA=+^cFNp*`UEve&t3*nBSKq<|nae!+wDHXJS}OgePWN=#6=f&CAa zE<_!BxtQQw`(O{crVcG(TWOR*seK<#(!s?)mN;M?(f8%EIf?}jBIUL9MhK-ee;*r3 zx*y$e-oTG%R^sI;BO)iQg2t6N4zPyOXgE*I_cAtKIRHykvNgW`!vSmi--Q!I5OB}& zR4T`2153%wPGnK-w`^pgb1{2><`he$m?^(Qh=}IQxH{|0{(9tz{ktzsQxpEe@os8q zS%_y>G(RCGD`k|?lo`nx)MCQgXMg({`Pm4oR{&xw_)D3>(C}WKm_vb=rEB&B7z+;q z(m=Y`BF9^Iv3c2jfwpZ4bBWnE1C}7%FE0lmarncc_Gc8Hiirny0UQjJ()cWf(ziq;5YYimh_4(805D3Eb3`5=Lb{TV$5>wA)UMWSVGr4RmmgM5n#ib) zl6_8m@LlKcW3`Wb$`VGIE7!8t2b_3rmY6;dcUFDxs`$sm&2P^SA&+X=Rt=ur14D=& z}*6~X9=e+QtH7IYP z>Sh)HS_UskdAo=a%vZO8{hV*zZg5kOqxVC+I98T?RQKvtyAwqRA3L2l37z%(;?#Z)3y+l+ zYMu;{AZkIP5DfscYr(&*e9IqF)J=lUcK1iL=5ltc%E-F8{-2wAjS$2y3as2-Ejf$*! z>RWrgee39@dcZElO=PrhNhw+qE+0ik7*S2`x_@WA2kueqEO)#Tpe{(+P21gX8mW;N zCVhrF-o{#`Hp?-alcAMEv!QCYn`4uqE+!=s$vp_}r~ikve?H_$e}8*Vck8u+;Dv)t zW^YVcYzzpNA(`V?8}|7RI`^*&FpYjk-(D2$JiBfw%bonr)C+I3;%@qgHhGD)Di?y6 z{)lRcUu|jDVDDvAuy&=nlAbV)k8Kverf8l=2yA)cj{+!{`kIDLX*$bd1x`b@f2;dd zmN#64b|1>g662_&{vW4K1HI$bc~Hy+=DU9E<6+`O%nx5Qm5e_RGLK4``FU9uU`z&;+BEhJ zm=Sp4Lw17ZFBAzho%bw48I=$2MP&*j(=k@>b9-4jySe~XYCf8GEb&W;A~o*FgQ+>Q zmDnA8ZxIymTE`f)LSkZ#8ZE>2c1^nT_+dwM&F|BA`ZxwNm9+4lonx;^2exjMV6&aT zavaE$u49Q9TIH|LUPT&=i1i@=_!uVl-fR}jM0Nb99P`^Enet@ukj{V7KY4s}9~x>J zkWx&-)yVCXbdj&xOWNLR-*3jKS@H}2ywu&93L9z`}Gi;VGmn1A$4=g#8M7HMoQ%Im{d^mu1 zjMbOnEm6LB^OZ*7{@jntG@ENqoW8pU3hf1mmr!prBw%+WxU=ctj;IXyaX@*1?E)|P z#a&kvZFVvr{0scwwx5>;r+;sJn@SF!^d7jG*DwUGk#k#CqW5wcNGJ5gpnx?m;giV)5X5Gz-gJUM( zByGF(u>H&Z9e3Q|T$?GhxhrX@e|Y=C8DNPAWr3^xr+M@uJ(i&C2{E?6XX_62oRV%v;Vlo++8Kaz@tp!KeGsSf;@Gj+ zbKp)dkhnG8GmE}=HiJM*VuM$1SZx5y4Kq1lK8)u@0cS~(zhwl)A=b{lxO~2=d#{6l zKn-{nH`SNc9Xx6^bwei%^vfuRP?ZSuf_hDl@=EE-qD@f~_Qw6+9yHH+L_HmQm6Zvd z{_~4Yw;_-=zVESXj=t)Vd@>FKu_c;8VQwFAR6K#q#tTc>BQ>{-|I2)<(Pl}q-TcLX!op9-BZ6yShZi{%o=PB=hx*9G zfBTc7OrYWDr|)E}V<|p{**&$r6QoqEX@75{%LzWL%RZk`uj_SAf)qa#mC9_;DFq5ikC(>_4a$(cVf(IJQu(xd9=WlYuI=Zm3Sw-(@!UL@Z5igEOp3fVSfnIi$22^=Yv|5( zd7*1;+wgY&YrEB0fulWenwJyUFqaf1nW1eRQUoj{q~J7oPR3H1m)dfYbPP-N0|GL4 zlXMjbpqi5n($rzqvb$$&9g5C@tH-csIV*VP)|x*5G_UMi|F_wEFv#f&J-F`}y~CWu zFhuP~j)fyjuV-wRe(bJZUmUb*@2AY7=Jp`n*?4mIE##s zA=~8uO>_Q{Q>@)v>yhJw&Z7KuUM1?M4&;fnE2_`p-pYmh{N>(|*ZZ$~)en4a#+Q3V z4lF&lQ1^U0xraFRLS$Lo66t}69-;~vN%8>Qxt;oHuJz3LKrY0HI=b)ox8{}yc}s9^ z=rClvZTHH?uXlU8X;iC(nGu5=z|nHskMr{OUB5#ho@;X0^glH*7Qj>6V-Y0j5&M11 z>slo4O06i&6VbKx2p9ex=)LUS7E#7UQa33iA+m^5=-YQKYtkiQ#Fs)L+ul;`8}T#x zjG>g>*lX{NI-B-wI1(*JLY3+q-k3J5N*wzDJ?4F}K*G?(Hn&h*}IT6ysnq(+kVlvvD|^&)mCt z1!DC)C^KvM64%~wT_eJShEeNb0yEZ+n%4Ok>lRDE5a)p;@opBau^ljPlCcQcgHAKu+znI z+0))Fv%0~if~gN3QyzD?zvl<}&&gjc32-L35(ut@6iDUYMf6vA-q#uOP5&su&81@C zX$nt>?hqh%WPR@|R*^9PtNb;T9jsa?O4k?EiEBDciY?I)mIl0_k{?NjhT`#!xd{o=PyEc&LDL? z3qSbC-hA-u795!@M?ZWCOl&OZn`7u&HX z#wHq+$Ggp7CA5JZhWF&(g`+7c(fv;kgKTbMbI>aEY`j(0(J0|`S?+U{ z6!k@lHio7}iS(o4NcDto6Mw|w+)rM}Xrox=2+O(JxKM8KkZ!Y1FGw6t|25XW&Sk%G zatOFcQa1>TKF!}~)r~0|f$2fuWr@Z?5D&h5ZVt zuSry+oX4yDoyleB(DQaMI3n1j6XAW6mXxO{Tzq$qN9B%z*MW2%s-4n|Wh1~fhV1Dv zMZ?B0x*C*2;E^XH_9@l+VWF`E8x&A{!kMe+^&5154+{K4OpxteQ!;JY0FWM7u z9_y;$YKOd|(VFjoXyihqS1330ZDYVcld%I_hWxBb_ zFli^gG$T1kmnOH=p$2{=x&h4359tq8xJSwc6P6@j6(*yvD*@ zk=7OFK>3=6D4~37Pe{aIyehJCx9(cPXEU5~Plo#SaHPkl-it2dc!q=4aq|W)yWhw^ z&E@LtT(b^qeb1?VMCqN?8ZZ3|D2H?ucqUxS4^9B(7twk4_})-4LtqrFaSKn1RN2=U zj96HI)$bA#$3dA~9pa##5h(D|Vjs?FolYd6P?;{@1nj(UrNv0*9Ivi7s>mcW7 zqls^&MzP8Ng|kgqzGfzXQOR_i`I$&sWK@GzgC2b@{__huKHe+w#hh`mU|>|ZWmJ~-sH<#VTsQE`CT@OTqYqP%eLPt>g}`USC}h`W1vYoVs#a;T2M*%2SJ zA?V2(1zR^AeqaEj>ml#coH?=aOl((kW`Ty{)>qw68-;1tBFG78Rq?PCfiq-QlSeaV z`TG8c-M-**+)5mH z4S@x^4N+2=wJ?A~PgoItuC*4uzEDyJ7)#Hp)i3?UW8iDpS+;XGrYIZEd??;|jh%;Y zy7|D&i~sOMN!p+9h2@x`#KjwVfBsEsP%ax3OLQWC!}L$>dWlRn<%tBf#+D!%Y`C*k z)Oj|chs8qmZCYZ75o=X{r-vh#3QO-eul=qZE*5Yhe#d7?sOs{99aCdqYf*qy zwfo^dzFHtcU57{n@}fk4NQ)DmMh$ER8=v7gp4)v_f=uHRk>lcSjZ?hkmdw1>{cP9` zkUW^|{PeDJq|ChJgl!Z#~Ag!#ZG)-Gy2Tqctm7xr-N*R+FnysuE=PmC0`LU3oZ;12lKJ^#$7 zKjl*46&?-${{ql-HAF-Vt`>sy!l!ype!^;n8zAu*LdJ@M^b2pW>VSHmlZzBO{$o@*%284WOgiN-xs*N*n6yZEZE%PB`%Q0$GM_l9m^WN9h4rn(*(9c3{Duwc=Ou~3Z9vbI zyzujavRAZcF~YNyx7Hd-SVFth*FMw&0@T9e<(bMGo=xhTw$<>Sy3{U0#8M>er@}GiT#^({$2`SI}3!^Y*)-9Yif_ zln=b4YMDI_QnT=gCw>a}#(&vHbJyg`(SMy6uS_PCH|cFyNz!6avb5w@n1w$Mc^6_H zDKR|Qn3dmrR|Y!oOpluhcF@U-;A2PAB56mTOx)_g)m%e-VG#4>NSw*Gtd#kSpUz`U zi=zocP~WW_wAWR4>P zvwR0nD;s#UefZS&I`oTyKab(C%6hYdRn-7Iy;My@!rtWSW~?NR9(<^<^XwbNy!O`M z)g!lh))l(m=sjG^M%g9}<2Qy1Qsm~(u@Dhl{7~y)m4!U4N{L5_aM`-ja!>Msn{5zh zE7vJ(ZJs<$9iBsXnbI!YrLH?UetuslfL^V=mlpGB-#R3}w{Wm*@K8@9+EYduhOx)^ z8$4J3{Mo>BQ=5cRbg(_I&v9k&d}{my%ELSYKzL)iBPi!4tE=uj(2x1K@2cS?&|yh# zzk#s!Y(|9Sz&OxQiwz^gTk4^PpBZQ722rDen4;P9SbGWvW#9>g?Hhs}p;_%c#IPi( zK|&CuxmDyNIl@;779k%Tsd3TmDIUxjTJ8}6^lPq+en1u|(M^a#O(~3jE#iZhwsZTN zxl1ZXJHJ%LJWW&x;a8+36@)4aQumIm=NqM&%uog7qYbwf1ivhloW{Nd-v*uTLGChq zY^OOXk7 zyWd-zuxleRI^y<-_^+!nj?UF9Ww#6&)V+>>+eySAsHL^;&tRc{EK|-H3B9#dVtlZ0 zQly2Rj9Y|n8PS8!cs569Z6?iAoN zF6}R0Bz*8MR2ogbjH_So(Jm5FmN@tCKp-l!S2$|5xFR?&_+HZ4_FU$p`GZdvb#rRW z*vUCXWS!{2zu_hi|6339neadxYy7!V^xcXswDUt2(C@e;G#$o+Wvn}m8+<@mI4wR_ zd=9*Z0+ReKz*lyv4>z#^*GH8B2np`JaGITS3smbsXQk=qZVUUt6*zj^zmJ{BQ$HY! zP=~}%jdpa$lTJr8SYoKwf3W6$i^khWItMhxQHQf6|2|KXWkpCC8fjpb|EiZ9IE&5H zPLz}bsu$(!zKesm0+~3Hdt`sVl?-@^_^Xl>|Gt_#Kl)?}A6di=OF;kK*B>iM0srR+ zusOaXc>2q%!xAa`V6hEGmsR009A%+3Ue!vE#L8TjpJaE8<^JNwW13A$CSQg3^_j*D z;Ofrq&7yRgqiJl?vx#dd=<8UIoLO*6ldSX(md7P-dAZ#s3`5mE6j|+88NJN|L+zE^ zS&2T91wd^73&Yh<>>G*uVTA!drM1G9yGLG~I}{07$k0+)4iQe+Y?s2kzlsDxk!`P9 zECgT_7kgE;oLOIeQN))-R6r&9tonfREy-LNpo@-oT&xv%r+BLTNta;&QK2G;3>I@~ z7x#5EAW`KD3)&=;ZWz3A>I#}#@4!%D5ye-Ka-S6NT(j_w-_n?p6rbK z`e_2mYJDr=fhrfEtIgQxqy2OW^xq1bL@vBDTrYQrRS6vOkYNuXri#vja10>r;)Q?; z%2$xr>&&zVkW!un9zl6%`?WInVIImnXP@xzVj^(TxX}Mq(5Cty7YA_Nl-V+w z-K(;sCXU%s>pD&IO*xBvDw4>I|DyiHh1tUyP=~b*Doa-|)7Lqi>T_y(zuRps0?ZU= zq+1}1gIC{;Hc#|vGOd(1Ld_=dHX_4HF@FXMZv$dQzyi)7_CA6R?na-W2j1a;(m81@ z*aH(#MUDu4nIPKJyZLgb+dJ-KwGU#02Q(L6`u7L}%nf|r`>O*#5-xicy0A8aNm4{@ z0H9Kheb*P;yiaf)G1(WRhnJu?9ik8^rCLLqw(F{G?|&Vv=@vrJB2?{kIOwdZ-?Cxa z2c0z&T^gnAh0*b0>4=hj?M&;*Pcf3VinSSk8(T51sr^Ppzs|Oweu=&*l=u3L*^aa% zM8HMz-Jl=Atpp?~anWAcvy;SHoH2kJt;zY_Wo4`}Mv^LvIBR&MQUB`N#rF+Q-awK#@SHyxkOeE6-s-=#6i^2tKwQQ&ozxg7e53O7jVC@ z?!MTb=-&rj)QMqykD(r|&B+K7!QjFIr&$zo<8s_t=I{cQ`o4u6%|>*qEnfRmYTaq1QNvUF^>PEz~uH-%4|! zSRos_v-?Gl zx1g-ii+}JhVv*iYG>VUW<`a^>IWmhkwqg3^)6JhEKbOa!A#G;Sbs}q+u>t02jBzht zsT8%&OWjocrUAHuml7NO$Z)C}kgejmUO#M_Hd0rLNr+zDI+y%~q~33p3g6~gO72@M z`WUdKT)KO5&C$orwRP_4lP=eFw z`oGiE;?%&$Z8v+G1i!Z493lnIDm|sj=FaZpn`7a4|LGqHT^RazKd7(vkwU3@+|t1V z{$JW}EcZXc(o#k3mi>>uPzcmgG9jvjV9<4kXooLQ<_w^{v`F-a|XlTn&23A7Lx;X3M2RnoY@ZxeP4@vnk2M6q zsp8O2$_B#@t@-vT7yNuf1e%Hiy$%~P_);h8ovrTAa4_IM*AxJnF3mi3LE*l{XR*VF z!79oDL&`zS@}>=Y@hy6~N_LDJD&`D$vp8UJQY`4=H8Z8w!{XrSTaw0p6N%&Tvr*e$ zf66|f=S4f{o>u%^*|#I$0O9E|Ioj~i2d`dpO$3#p1@AF$nx)4bmUf$8E zf9KGc`;N8ev7xYDmWT>|5`6pO3x&(#0szV0qOc|-b}~Ayc{~4zUEc=p+~rByn*)_f zOWsN%HeNpvQ|ZgfA0AD7dHoI)JH*$#Y9381taDuZ8t(o4Pzt-me6z{iQo9wl|Axg{ za9x#6;J;Ts9nv2YgrsZNxpxa$H!5yr{q)$Pd7Ho93fy2 zEC3Djk=8(ZFrYuWFt%|m$?@hfC_sMM-5E*e!^zh!pO_MQ$)SsCD#oiB@4+9Bi`2a# zmFITGgXY)5y*>9i1u~d#;Xs2L!urunqlLn{|-L#qj>QNtmipAb& zT}DVC*}G>*8jZ_SVR47t45!(VLoSdDhD5QnmN6Awm~-7zY>tX4BhYw&$!|I=#zkKVrCSl2J~)VL&%$B z?Hk0g3il_-MIE{T=4MBTgWb|&E1;>m=oO2BZaWIFPi@BsL42iu!MO|CKZS{QAy<(M zMT+@ncTv{tW+PaE5_P;uj-6gI01Vk^0|y{$`L9MeXV*MLT_Xi`_&#BUQR7ZDzkj_P zU3bGCDqdRfqd0MG+~=Ydcp$=~tXCKd!*9K&FYtzZTT(hKL3Odx21#L%MH8BhlnZq0 zP~HA3++P}2{XZ?Km+$>3ns}r_X^pL+Ihf)v>PD|!ZvAsNNzw>#A; zuRFfnvPnf7lj@5BHBj<1qL@Nz*s$0R`h}>4h6OXYE}8x;xRRFR??>&Hq~)Hive{H! z0AzgIE%$!ZOB7x^6f$nak!H$iEbDLCN6SZ)myn*lEZIja9-tY2uL!Gb-0Qa_cZF=H zfvLD04J!ZW=TGe8Ow579UBya7+ewpZgNBP zD;e?Kx#v-s^Kn>K;+zCBzmim9AMd!mhK`A{9l%rf9Ec@sw%uBPIsGHINtYaF#dCTF zb48Z!Y_KZ#b$tqoQ&rkJxJwSW@d3`fn0D`|6UYZe7~AvGp((YP6jw zpJ&eHV=c(5*5`%vhEwlXjW>S!tu1#zQY{VjJCvZjJI;wu*Yqlt%HLRPNyXB99yC`p z2CqDe?N(*oYuYr&iEvdSp@vX~yl)!WiG3CL4l9z?!K)lt$p0Qae6_(kVJqT+WDN!# z`;#ywV~0q(X&6)v>F-;-o3HUP?x{tX^zPwN!T4wys18THcbkW_K)=3!a9JWV)~+qq zWSjBhjzpsRAC8WVa0^Lzxhq5jJRN(-op*{icfN_xJ~I?ewwqK%Z{Ry9e(yCLpI|!z zQ9ZH@;ScW5R=q-AJv>x%DZU;ZBII-bR%Ay!{|R33pUUeYNMb?=Sc7?t0I(G!g@&q- z3W|Ok7^7dN_L*??vc@;XHkB|m?skBRFWzm7CB|dwAij;!DP7)7{fe@|@8ADUr+(SD zbECOwsAFL<#+Rc+_W%-#)}#;w6kNFI(y^RWBWI}x4IvZ~I&H#CihXh)O9Q`?V=NNH%s=WL5V{gd3Q8K1WC1egtc zX>}wqkNEg2UPoTF1H~dx5nSJmdTkv0{?u%458=a)!~Z=A$(Iq~gRZxTq0}BBSX9V* zEia58xf1hmH2=K%iAk63`vV!%Ui&on5@!iJg#(*L0{n`fK`I6ak#v6V??V?SC~+)^ zCmr68CKtM#NNk~9EEc6s-9I#mIq=4QCNe#C*UkBkqWXpZY&YA-GnORMxBB)I2ZNw9 zHv=~h2vMVLlnhpmAkOT0;j`0oo;wZHAF)}^$gQ|%^`XIiyMIv7iXkl;3e z*TOyup48>g1*eQ8STWoc;Yh5kY0cz}&^2-lM0o_UcpKl#hIM(cVT zUs+7g=dO4b83}JN6!D{F56~fJPKP)DtK7*D1SsSHHbhVR8^9PfSl1tw`Z+|UN6@KU-DB`2JH;n!}GOw5SV9*7p zhvVPXQhoZu41{4x=Sa&G1-Tm|c4Eo7*@hqiqH)77L055ULEmRhWJ>0bp=F+5;L2m1QJqZ?5J|CV#9|Tc}Rv-?+z4 z;LJm$E7)Z3zavW7dc+PH)8|>0g3>L;mBE$fYOFMi9k#%6sA#s)Yr_-Xi7H;7f=TFb z2OhpHM^3u%c7C`&3Zin5<>BG^#GY>H{rao&(I0?7kW1yM*w(`aS*DmaeE<9;H+}do z=IQgQmaw7syOmcDhd&VxVZW;;w1OX|WYrw(ZEKT)6;UqJu|*>k0hGXfpZsADToVF{q*+r3EpCp&QuL514`W;8O{k@w((k`Brg^?D@gVbDNNbvb z2>P0K&H6oH!*S#wicOPr(dF1V@FqQD2zy=0*v3@!`oy(Oi!jud&38p7;H*k6#lgA) zZnlG5FdCD@E-Az!|-F+s%socJ(7Dq?M8e>MnJfn_95`ejAN!Tu!Nug$l9obV8=qdXXw&i|RwD_YRGX>9v0=wBWi@vvo^Tu;Noi`z7!UhY4o z`l#u9=ka*nwD_E3zq7Mmo&zl6wkmDRVHjf^T-ou($xmQfgTcRI3_dO4%M1NRJHaR(k7qgdNJZ{=%hfuyc7u6wHF-$ctk>(K)~T-LF3HVp)(Z7S=bzD0nYwWAbtJZs&kz-J`uPqrA_2opuZp^@ zP<+iL)$JTQw0~?+LlwfU-b;sc8tSL3EV>3>=Aa-LN-ylOKiTq)Ae{8S^F)0`S{h*+s7B zv+r9((aZZuvS>y@a}hA!WjV|8d@jguJ^R=G$(om%7rNcOYn;weCoQ}e{D=9v+A_m z#P_2VWt#_pYvK<@fi2j6dx!EK5Kx{t5O=vZli{y}`a*nXl!+(W?0OSO3evyfcx7g> zScp$xLK42e{u#w5DJ}yAx-ek7$4!mFm}6cJ{=b(aBtB{ zNxwgDa`@eZW8T}|g8vZVzQQAJG;6U3JsEzkhB#R9E)4(>IXY8qb8so2<}V%+)UAdt z*XT@XUf#_`o}++VK~)>s(e>Q#s5w5_`8g{q&F@3&#!>;}c;W#7fgQ>-3zdQ@y5SM> zr4L>g!h3AU*Y@Xtmw#JLnp27zY-bCn-zYt4bgh-e)e z!vl!%VDiNoA$!c2?p8ME`VFnOCdaVh(a=7T{!6R`DD2uhZLJOaqyPBQbIswKOeOCo zBKWhO(_7g6aG?9u#&Rhs4|NuqKA)w$Ryo@pwNc#MEprcAyT?InG!kAxx7pGQITc+L z_5*Dt_lqg$rP2GNpvR5B*7GA*i0tm^8IyVTn|C~v*x-Tt9zVYC1s_#2g_#3Op0vVi zSHDkF?*7U+k8AmE73Q?;YI%A&&)%2nwUv%#DVcg50SQu%KKSk5GZjj_uE1Z zt?#^WxlBUJeKp^OAEpRv5m=0il>bLM7zHAGo9BF2GxphX)%_1m=lw|K|G)9qea_(= z``9Dn*rcq4GS0Dyj7pS5l2P`EGR|!)3Q;8EkRoMOiZYJ9R|&nXLyCsI*E!$w`QiHy z+&|r~`*lB`*L6KEQBN?-L-qtCt-O_CsrhXRfFg78cQ^yMwF#}%Mz=6M%2D#bgwHPJ z4j_e58Vy_*Ipd&(;TU@v(6X&dN~<`i|EW7>x)WFUmp-WMx<`%whlJO=>V7ztck8PA zGm>7yB@{3&WEUjiLnYl{zW z=dqL#oA;b{lk(InMH?35Gc&dZamdPFM8#rR$8XyYwpphVqThKX>^XhpIW#c`i42|m zU`}Wjsr`1>T%}Fo#rE~;0m;w#UxQDm`PD^BLUr_=zAQKT-y|`7lSVrUP8^{oxrU4D zYaLs0df)r(IC!Xx5Pfs>^7qC)B3c=yAEY}k!X4jmOmRv08;xY+xZ`WBvAopv5bwU zk_{25a^TOtD*xXuvx-ZC+nM*yYxw?s($!8IzVGXW;Q<$jA9y<0jpc@|D0&PK2!-m) z%n+5bqRv)gwoD+QH06=T?}~PFw34{XgL5%agn7)2$N(}C0naLh15coSm8DXU32|VI zQ+`c9=L|!UwAbA^?*95zDkwmV4&@2D1OG2aX_;j$l1^Q+k0P zX+Y8noUTnNe|J5$@*2Qi0($}3Gw3NKt4)}x`2AT%x@r;CF)*8U;!qGvcOFs)OZ+#M z9`gqK&s%P-)3H}E#2bKOcUMNK#O?u4`~@KQnfD^mp8=qhGiVBD%&rNpP%cs5FHZ}8 z#(%#0pI6DB6G&JYhZXQ(1eL9VgtP8J3{s5r^~qZH12u7yP4u->-ehy z0IXbS;-wol^Y$Md68EJ(_fBN^Ar$9b-*YRgT)4mp`#3W?f0qsHS=LzFY<;}8$Q7NLr|n}%B$hVEb23Rh65Lx*f*k4lR4P)56?SthZ63S zK#gVV^&!tO5)R&XrXfOOxrFDfW~jT@YJ)!T^QcNQ30N*fj}vX30(ZsY5X0LaFCVNoDjl+2+!H&DOeYk&2D+=w z=2vb%VR?x6xmPFW&(AIoD@b=6G2)5|xK~sZv>;Uu@B1yBxcuU|(9LhoyzALow$R{H zqbM6RIUIip)vbchhKgkTU#3DM#*h-m2)7IXQP) zAB?@w8b5~9&5b|WY)rUGd&j5bT(sQ9gei+1xG!SbQUOTMH}N@$Q0e>tpeF!xpM^TR z`FAtdI0fM>v5D_yx_m3Y$fwEAH?7@HIIsNRxG&O5BV(9O&n!>O`reg@rV$p z$!fxV9c`_G_G>8K1Hs4EJTUUFq(?7J28m~xiI~K7yv}zVjN9c0x_8hG=-(4&V-0_Ig zeCJ7gp@N|JOx*+Btp)QYVNsi{dx3{B{f+Mx?7!dmPMfU17Q1T=R-%ulSJ9jjm8_dt zcxKspnWJ1vDtG4r0>=M6tx$%<=9W-1m-G3nbWJ{O?MMH=7ob7RVNYWI^Rcyz7MjEn zXjeZLnB`d4-BEeMS8|;60G-n((dcVOy*@xox&`+iq7@pK7d$0!w$nie_6s+TQJw&k zCy*{?@Fu9adcKY$6uKc;3=ONHO2P!EA%u}2*jyX@@Te<=O~ksaiP@FMe)YFWn8Uqt zgwNl|Vk3AYV>;OgNHf?8?6R7pE^oeTXmNgU8f%X6D9^6|2nJ$sd^K0#yQ zA=%L5Xi_YcY8dx3a^3`bzT>>7F^mhZ&wGO`@EAH@N>lGcPYkzW8`!0QppVj<$AI+s zUnhW-xrBBiU^TsWC~R3c8mzSL6VGj(WOuHfJr4Vr14>Y!DnJ8|3-BL&;M0eommN=& z5dcAo;mXaJHx1X;56IG2Ms{~C8&mbFzXAHuOj9w1y%EYO0{TuY=YktjX+lKmgi ze4(6h@X}c;K=CpLrs5B}p5XP3LeaOnQe?)>EgPlS!6Zx%{UY+*1?dkKb_CgW|R?)G-pU z1VAsIj{LZ*M7%gobmClem6&GQrjn{Gy@f)eAJo;#?XJDk{b|>`L zUViO=)93lfE0Lc2zt)eLEu~$~|D2sy1WvT2)}W5_T@`kIiA)+=1NBDo6n&kjobhF^ zFRad6e>d~ZKQ-0_K3c_l?)-zvd5&Q|2B6*yTv$I~%Dd{t03%_0+Ker0q+(PdJZGNN zim`42L3?X~t6$?RgRCPL?~EueU1SA8y+2u_95$M6_IR4Gh$Fg-mNHdQuNztJTxzB%6rk2T2 zA)m8JH(;fVqdZCKd52=-h?52kx}qt%y;jw8<+N=(Uq+4+PG?r6OntN@7_n!2egyY~ z9O43G-h}5%())Law-IDhm=j4Brat&~06>rfmK1;dV1&sYBHybHNBnOIyy{!`LTp+W z2)v&Swz+Q#h1VDG`=v@7^6GZG`+bd!Ina@SzuHnaS^>=KOmX6>urO8O){sw>X75WV zhZ5825#(g#(~>vF`MGxT-8Iib=&hGES^4+BMvALnSv>sSM2mTJCzzKI1e6gqvmE|z zTHxowb$#dOIW)^lQ6hNlh?1=dj08N^TM+)18RWo!K9$!{TWXGtAEZ>@vavj3Ug&kN z6ka-3-TNpx)|}f_Dq~HkUGe*?OXvHkG^fSIkZFZe{YW9bt-fAY$T3Xqi z!#hq>66R>AE5NCx4?lj!k|Xrjd53G0YifXlxu)<>yI1V9C_uh%CJC8}68Mi)zqkp$ zNJpG{w`O%Iy#Khz(+Atun@QkjBeX8CiBFVP#}olTr^<1hsn#RuLOoePA=~%nrx;CU#t~z|qW~79ReOpAa?4 zKD&31;I%2W-uoNymEHc~?c4}obk&HO1@1EcIn>1(M)l zejBte0-{1(9wJ9z_ii3NC7eC|{8;0-PQPKhwkmEzgNg!$(loa295<++)HV}5BXtq!U<#%_=4@nkictsa5OIkNmWNGy{5S9Xz=|HgqVFbYpf%Iqah@n8Bn@br* zcG-OcEr>8sJo0p#e4N^mQ#E7W&`weekU`W zL5z`-_bZhs2ZrdKPItrQ>n2QhU_%nWUVS;JqO+14ZP?`xmklE^^2T_)9Uie%MU_4FPho(hQ_#e(=v{37$1eXC*=`jj&BQ3 zPCv4}dzD%r@!#jZ=xMjZpLm|Tn>408(~kz#_4V~-l-F?vpFTheOe0Z=lzWSoXIQSl zNWvRhzOD_D+>EcGr$t3|)8@^RkT_Aa%u;EQu$kXq zafBI3xhkfcG6uadpAsYIHy+D0Ro%3(`;5FPaZq@)J^4 zsd&cwY0n#f*I{^hIpk}rw+y(;m!G*E+dS#|8cr>UxGmre!1ttIO0jaF3!*hVW~9tS z^}$sPemp>paVbo*#IfNtJ4I5gL!Kp+XK9)`Y#&*fhL94+=>4-XD)Q?nDD)1>z$w@7v~OV4Y6uce-f_2Ox^FgqF(>Zrl;LMy08ZT&9_f9k5`q16Gbvr+}`u z-%ZMi8M^u7-L%8YztJZvB-eah>W!a3#RY@#A#Z;qcn>aTb#D zBnyx6A(NC7DgU*>DdeW67FW@E0kV*A$LoXwBS6w%#QlXVF$e%?-_{qr*-~_2`<16u ztNC^ng#Q7)K!VT)S~=$uj(^@Bt8o(6Hd%R?`0rwjVlueG`#1j>^?VBanoHn2%2`TX zB8|W3VBPgsH=cE+0a89GX3wODtd#vti^TGfR4)U4@C8(INS@xy9cX~aUn&&fGXw@R z0U^toSCwkI+ih%wlbY-F$hWoDV26!zjoD{iD?!*LF+n8d5YKQ`2)#hWoMdrKnHHmaM1o za~}$sJ}qee&#fylMbz4_v+Vj z%Res4p)j05ZG7aXd(mG1dnUWHK9luQzBpptFzVY;2=wZtHbJu7WOhod$S*l3XLRL> z6U!{?z~{*$wz`c!2=`;GS`y<22H$Db|4p^Vt3=-P8BZuZ^hQ0G&3|LEdr6%0n3H!_J`{g4!q(6Xw@K@7eiBItchxkqUAJ zu|E)tls9l!7j4!Iery65=M_8{kvctK;EvE+ad75);IQ5c>=rxZ7erO4SbY)a*Fqzw zT$qco$NHa>208Qm?#p9f!w{lF2{7JnuVSxp6(NJ15<$)#bgI3cf7ZI*KvlU$g;5zj zwAX_~9Fwvfk$j2fvM0Akim}`uJ*x$qcsX6|9KaNTft!Sk+5`2DiL!IYh`dN=Vfgp4 zurJjM?651)3K59GFOz+{$QZDlty5sR5;=Xa>)j$k@H+ov(SCtXsmOZTb&$iey#mcs z&En_bf;~!_W*}{PE|RU^nsdk_zlUYXIj$&0g9F7AbfyC!^rdvHmSuUdIh+odDahL) z7O|Qdf;{0wzi@+fqR2o~X2b+F#hS_a!fCRqIRBD#PJ`+*T2Ht`>fJ+&U|NuGnogzZ zIrjCFrC2*rGPPm^^ZbtY*%lG4UE2pxN8iYrFJJ9tAN8nozSGah=xYV|H}{yVyhxzFOG z+1d%j=igrH;s-m=9UC*3xz+&OTpM_npXU1_bRZR)jFZr-eUOPXO2C)u3$1EI`h55n zS{taA7W?Su(twPuD`hal|jVL0X| zS~RcCeCgj>cz@;niy5@+ROoFx&L4`__;uo===(a&vu>)-;Bil+-xDS}*@=uHUoCAQ zY=><@9tr6v}N!a3eb<06H5U(_gFBB$sR@%4p92T>y_Tjk5B7!iUy+zW|39USR834 z4SkhSo`F{Kw2tssK@DYn>-6b;P!Py7O;|(%KBlwp_U+QUHfi$Vp3jiJqj$(0sIG%Zli{hn5 zKAnV8l#Mz3a)wx&caLdh|NfWt4ZfsSs*(!w>H*!MqfX7dx%cknxrUcAz321`Q?+`8KQ7>B!nPYkX?S8 zr9TIlo=}N1o=I%L50v5?7^aerusFAfY%M(bt(UxeU&3PU)$}r>{-ZpRsdJyD7T4c5 zd@j`e{_pH%P>to{hAsI)oOB%bG;|>)vhciPD2x(t@HtU6K|ixW+ta$3(IBj(2Gpk#`T&x2+~X zxxQ0}(ES*(N1WO1(vWDd!mXI^utIo%1oiGoZiY+H#Aff#S2?FNgmo#$(Ec+{?%klM zl|oC{v|xJKR>MPe<)N`3?9*4Wl9Ut&`7hH)G;OC6Nb@M?FbcQLR3&x; zXsIHC{);T?T>dn>n|f)l{p(-G^@iWKGW9_@GSzU_Akeod(xiA-3#0aUkZKixOvk5! ztr~=fa!TcsHLEj(5lH)-Fiync`dEIeP>ZE>WjNk{?9>$y~%Uv4&*7BXnu%)w~s$u&w*xR80)by^X z=qVigcVadQi(#I%3y;E&i4aUNJ1dHUq*B^r-=%j$&+CrP-DFcEyWGT+ zAs%2lu+`V==1|KLW9DKjotz53fn%x69;adML}*%>dUtW^x~G5ER7(LeApbQ|iDxfMxcJQ+Mup z#Bi?TZn~CrUl{aes}4(07YN4^DBo1+UHfqdA1VK=v>W4@9gJMR?nnlQfS64C{ zp?w%i<3f~~>Bpu1NcN8&1eHkA*0n#cAi~RN^pESi6?4AyhwLk4=y2~AK&0DI)auI+ zPYsd$KdM15RAY?Or~sARzGaw}Ku2zA9bBL&d}5*#pn;4*2>-OD)LvautI7 zZ$gKa_(~;h)E~Gv`PfDk8pnkTgtUp|b8FGo2-Ee(F$C!t>3v=lkC=6U3wEXAQ^0nB z_OFD>gC7BVUy?dRHz2jGAIm(s9bfY@8Yg;_5)CM{b|L7I$>&y}`mP?w3#$S9!=X$z zzCD+pua5!2?SVIsl}hR5RS+qZSwi~g@C$ov7-2M#n%IGs_I#a241f409e>*#-SF=?@FUCpDiIthv#7{H{=w(VY7c?Pq=TwgQ)i*=gYtr<2EgzftIwfhZ_l@jhWM>7d<`Ok7;{Aqv! zsFRgyV23?b1P%hRBEylVgB|Y+6AWmdSW0U5elU}S&-rm2Nu|!ypD#S5-Xcr$bzq=_ z378m9WU@|J+t)f8$rOlH=v-p8;li#PPeyZ}_D{n8N^%gSNZ1uW&8fmT8I#82!i%18 z*)_eqBdxnbdmppaLlz!!WOb&=?C<`bK9So$pz-T&gJAsMiV86nF8~j=sseWg++)7j z=#W);)t|H)fWi$lrgKzwK&EMk)VzP(aqo4Nc0(jQ{fhKh@tixzvqem(B4j>>gox7B zN+84`!b7zV2E2I@lC;q7|E8nCiRnW-;#_3ybSu)i9EJSj`7gcALKWutArb^^mJksC znPr1kr!k{L#Cd|{@ed^}$yaE9U(0XK|NS;m9#dP|Gw?zGi^rD#VD-h@;FZtABOk}k zO!fG;eboPI;S37CD3ONh1b3cENisL_2$IZ%0k-OK)XnK@G?fG8dBJs&=|N_o1rqCr zo)bHU;7P1M*Py63V4@bE$+?aktlCR~z$HY@7vFS*;NQoSuqH}RMFs`55u?JwWAdV~ z;KIeBRRNJ4Oq9~VR?x%e@QQJ6TWh=a#JENHTj99}%w z0UJ5R!^`!``DN!Jdl~#V(uc(pC?24~x?dDClDH&s?y|?kRMC~HG^91;*Ef1KkuTcr zvDl0-N%X=~Qnz>Umz^up?h?eeEybNR8}7OpKpn$W46Sm|BoyH30nTTFT-klvx4oeE z1G%+17J@Ud?rX;P81M{Yes#5W>9s3t>IzO{6UE`WJ2t%I-pF40#%uqG1#hEPLuanH zt8uj4KPx+_P#QJ;fcEK6$jwcwj}MzUY9O2#p>~WBhd&pJG?rW4e#4DiV%O7jb!#I| z;$os)OogVmm7cEk)46Ep-4k3{m5;KKPROJ&5`IDfI0`m^$!j*$weQ^H41B>R`PonW zs>op39&id>{c`O@cz$pSzJFf9X<+AXd-83(DU)>{4@SB0M~5~w28nY_oeQSp^hTH~ zJ>!=w@88xA;K5vp_%$;XDRCr}=j_I{Gv`VK<3>J?abO~|3uIG+X@|lWXm~n)caP?& z`xQg>qGr>kd!@Ny8OPo-4HO8S#nYu8d+Q)x1Q0=wTD<`1jb|ytEp<(x$##LbIq{KT-R9 z@C)%D^~m0#jAU6O*OH8GGASkwH#?{=plML^kvII?DA5nO^3ef$Uq&Zu8qsY=Fu}O; zv=+F|O*l~^2*kAPv?sTmY=AJMb>KPpAL4Y$nhQtG#S!@A45UU23H8xs^Y9v0U?w#_ zl1V*Xx0Ok9a%LkFM8I&QH-mlUJvNrHg&hb5gkX2|S@dk}!}SQ4ZLx09E5`^=M5y%1 zzEdT<4=UybH3)sAwXK8g0g?$I5WIXV*8>MVwVxn?r}hW8bT@JttuLjT3x(srf{d1H zqRL#muaCb2uda8-GFtO~0dlKxKS35F&hg{zh~wu7|3QgUWh6#Q*0vBJC=mu%xDl1* zr)&mN65qZ&NU1TdIig&NV zok%aqqMJZM=kmW8tHECwYDCCGL0*mtW7)gE#`?@YR>NnZF|UJ4i#CoD&#{-o=2*eF zsI!{CZ>rh|{L=7N^Vw}FM80PO*h-mW@xR5yu;MG{5n~?+F2n6Ltwk}Z+fRjwu04DZ zynk$*a;aP2+|iM?*UD9`Z!W8ybrTi}Fpgm8vn>LL_7gKws0zKu}mZp3Yyd_!=&{d3u`&LPtbc4Z;HNEYDh7%27f_uCUKx596UGwxJyiVgRg`yUKm zA(mf2SQhbH zp`Y4#!JQn6j2ISs6L!0+DZ(UmVYPqsU(~0eIw_q zIlKb=e}7uVr}7(u8i>@#o)eULcTbSzYI2|8{<-2ZEZ)s=W@JF@ z+%xy?0nbG)DX})|P?LD+tbf9t;9cbIN&EZ6+uze`KG&E>q| zD`fW-7}f%S)Ph#pShJ)9ZoT?A$75+s>*7K5_kRqTbQXbzN+y)HB3}qIMDJw%zGrvd3O@DchVC{ec=cg*vW0o$aQY6_iOfjfKhN%Q=#d@Y99<8uLt?n%C*6nZ zI~XSx44ox-uZt^-VsxTV$ShR^<|y^==JfW;Q{naCII$QcEeVl)|G=V$;i%xm zQ*wTq(u%j&{j=O@V#=ma;Q^^e>j{eo`-6=eA{|HWDw+cvk`&f)xq=7K^D}0KdKYpw zhW0qh1x3{f;x|}c%`$y)L^qCodO+m*b7ksppq`e>-4=^fTKXXC*lnTHR9S(o^ugs2 zm$S~rh)VUBRHT@xWjn@i@O1ep;}-t)yzI7+)m$UMv(sp6P1fP5V7RxaVlc1{sX5J^ z;R&kN2gASe1&9wd&pvLruITJ|^Abo$Cgu$Y!6pk#qIRw?eUJ8ExYk$_Om@gT{J9q9 zE7R_A&;6DL?MLY+KjfvVZooku@lQhgSoj2bi5x*a%8&1o0n4vMdUPTjj&l zApjrIM+mm;eE#HnzZ$tY8+soNgf|xTFE#w?xH9|}X}=!;ZbGU5ZF3cr_zsQ3VqA!T z57XzqZ7l1{_u&BR`xcA^`JxB1WRs z8twd%Zp#xe1vH!6$3NY>Zl;!2{|@Hlx@IyBF{Z$Z+Te@cbDF>vo*3lQm_70 z@R6m4RFMI>y!8CM9`lhgw$+L=_whb16gWqxsOJ#&0XiYz<;AIVh)tZRSh-v6q#?-e z+#f#(HsVfQ^F-pxV(_##bXE?piKvOK(i*{-cZRL2#IuTOuo6`N#<5ZI;VCA=&~B*SOf;g3jIn5-u9d24(of)mYN61OZtZH!>o zJ8g6XCr_4B()@G_hH9o(nlx!0T5!VtPdqDas9RS<%p^`nXwT#tdAf{Y<<`wUWR@E0 zDjnyWgy#Rpm|-eGR^AxdGJm)~`|i>W2YY1E!WnuwY9~NAE?EB2Q3FAjKHgeEG-sx2 zHGG=)5)gm}JDJ3xM2}$MyFZ=UVq|MA003E=bW)Vi=N;=aM;)&M~x0}J6Er(H-yn6|LzR=3zF!3VobcsXx<5POV+QTB$ z0v+I7O6+R5yMU@m<0Z%a3$K4T{lKeMV~K5a$8af~5za#LKVtyawqx2m)F^(iR5q!< z%i4D^@CE#H5TZE<%+zvw+{vogy=ii^&MzVW5}&gAz|~oMuQN_(VVh|&^9D9lgE$Q` zSZQMBuTeO5AaBSEtr(KcIT|g!!O{GR*Bdk(&rO2zv_81pY4hN%9XIYCL%VNO9yysx zcY$gFK4du=+xdlRv@LrGhfuWy0D(;0XnGm>_RrS%uXlsL#o9nzUY#5}i1?85LT%FI znl(uTnf%uC(-nj?^J|6R^}a;&oU`u{_NQ1L)lRB#J>|lVpOP>Hs>Xmhz=|!C^`Na@ z0z4I@o$wJ}vOjpJGF&LvyqA(0lJ&3TU*j2Wiej*(8(D6J&w%`0La{OO(21PB*2)Hq zAbh}Y6AUFy9T;RP#vBvx!e%Gf#7K%evvOO6r<~%TjTp(*xYZDKg`7FPp+NkE1JNC$w%!*qi|lRZ-5)0clL2 z(oE(td}*%zahqY*?`@l04OHIXRtyuIF9o5rm)}ut<2WT($B7Pf!rZC$O>j~IXc~?@ zLd6<`_dc(EzLT$N$@(HN1TvG$7-8hi)Gss>3a6QIK;w991UxA2_i$;4{(S?MWG_~W zLg+FV%;6{D-#Yp==mt%J-yhMCN^@kqMt;FV9C;I%cQ_RG=TkO2FIEVYP8T6--ac$Z za5-Iy-prVsGv@>4i~;EkWQ0EX=tRmRwz50HqIm5foYK_gr4)x9g#_^u*){nB(b$(; zPd;q#B#1)-f11whFw}<^p$kt(MiD4~J?ZJG2J^BVh@0up2Wc^J(y9 z&&C#Tz2c!5%h5lgt!yOOiwn#dtp&DEox91`gyk%Yk zdzMu0bc7LDl3KpIeq(PF<0I;YuIzs6&&w7Pe?jAgAD{ROpJrnzb&$Fv;t4ifZM|BV z<(5QLsZ29GUtODO(=20kEJX5?g~NXw*sTndvBw5gi{&-lI?_WEngq78F&)fLB1@^( zql9JtPv8?5bog0|MOVjc(L~Id=dgnDDOJoL1U3HBNakTi#1>|YMkoqx^QV7wh$K*i z>DWct21pi}#pkSONGL$QMNmW$a6X4c+`s>(?Wu*^)42taDrwA`##G5zatLEIbgWgX zazw4^GLmtS!;e97$N2^PfkAU!9?(h05%=%HLN4B4e_Z`S42VFZyRJ9%pJV}8IHfLJ zl<{J(!M%hBAf?mH^_Jk9?>ovW>$Ec)F~-~a&lEb>+3Vj)>Mvn$uU^_Zd#KSQ;?HBk zcY%0cU1}ApT^|3S{)J9RN1@h{`U`aWkqZ)FfI(`IxEgE-I~k0;E2kDoQ(CpaXzAjs z&_|4wJOXq9{OZ2lY5<%VIdTN%A&NYsUWnw<#+*7JYRKgm{ly=?I74I#i$1?|Dg4;d z*tDI?C5j_>!>|0#wCB_0b#${J+3pcPHkLN>q1<>hV=wcTBJRe)nWKc)#st5{uTNKE zxIFK@IG-n!&lPOFH6`gbEnTLyzLwg5ma-k1sz8=l7SX0heD1f6RgqLe{3K zuTk(yY&H|a$~iJ43IBIt;Jh}Cxdg(s8_H*^UoEP!;s1&|;8uENYi%+@QgyRB1A2W_yMlByRIjMW(l()Kg zY4%g+LjAk~{asqnVSQWe10hLGnVnXuxTn%eeEy+*ulo~Qv?yT-wXJTwr<6?N`C@nq zSZ|6sa*zeclE*<_6OwH4E9stBl)s8ovQFa8@!S#Q-OT3Chf#+ngY??yJp{Ui=)or( zgy6V%nE%Xvx+XS3icIHCm!b6S$j*ae+u$9=*9MDxMJ#lApT4k%^13`1=$Ad64 zhOv;-Y%X`Ghqc{6F}4Nzwq$Y9z zI8b+0e4!G>=J%r}5|nsZ;`Y)fc-&Yds-)V#qcdssV0D=2U-G`Gu{7A`%dL4Q;~O7| zS8b34KyWbwI%<8^>o1nfQP}#?WI#Z!?7tNXeHj0h=}3A?DD0wB`0g~mSHi5F?>Xnt z_idz0^wH*qhLr%M)y-~+aerf$17@;p4)ZB%SCIDNE*}!Cp~Tm49M{i#je?1#kx(J0 z6K{K7(Bv$aYl{K)M0ooXvgc**wu52<61Rsw>zm@kI;ZWt1^i^=g5+JApL+c|Z_uYf zZYbNhE`YlL#cpqY5L4%*F&$H9*M$T8&mm$8TNZQ`w zM=_JxI5LA7$PjCLP^k5OWG&znRY0Qu%oo?RAxl;IXloyJZ2OiAiX7C+ot^pFEOHtN zByT!<%z6JydKT~f**b8#yFMH`nXR`=}E+)o2H_}RbJvRki^IFtNG-FUu8ABHOs ztr_>L=W#apq;;CBF?h3|_wJir=j-2^KmSEjWJG7&wKkrt@6iMxIN;pt{ggjoj!Htp zLU~#0rr1XSqT~{NHc=aA#$=;`fC5o%y+gF}xr=eI6F1KWk_^7Fm0{Qpl-R%?ww9kI zIPquGINsK)C#f~O7csUmkEOXg;di=(q}J@DAd<59J>EA{Rmd8fdz`HBAQE5|b;6SC zLxte~thv;rZ`k7oYewcmbMq{&iY1xO$y7_J^IfA$w8L&yX6(V4D%Cu-&PaXMJ zI1}e91^FV^nZClGX53CqwA!y`FYQhIv7YbGZgf*Q5!Q(nS+xoMTDr$g|2C!~(?Sg? z2HeeAj01yFVp@Q>xJ5l<29I4v#{GS4Hr4bqm~r=U;3a6#FZbnAXCSxkQ<|=~X5qa+ z5d04}iBN^P=2 ziW_9oq4&bCp)M}rybd>`$p<>k!}@f!tqUH`lKfH z-0)WKfX&=%&P#9PKXsWh3Ea6ko?PL38|Ycjyyw*PK4?RjMheTIy7LyrZ2$Hv2ZluM zMs%?eCYNr{p~kw^5H}r0bt-D8PNy&g>Xj_yPO=T`vd!lQx*tv2blDhG)rzA*gA2c_DmVA=0NG&r+8xzKDTPJ&<#HP%1%{y zONXfTs4=|-TRXNnbxd2rjVUW)eEHyuXn`jC<&qzJEQ zKLiK^#|TfBD{d8RaG%7QKs9Tl6bUuZ?M`5K=AP#mPM8gA6DIf|5>qxq#Osd!;C-s> z99bu0PBDc{=>WKH+?BN=GXD3;b4$Ti#{q_BitKdk1(h?H8-FSFjkC%x4{6b7cFbOJ zN(0&BVBA)@FZ6fEw!T!ry?gfw3NOk-SS6HI2h$&>^@=of+`C`#St*tcroB=+-VdTN z? zh{wZEcjF@l7asObdAi&^D(Ren;R?pxmd>lNJE46(AO|I!YT{t=5}H{w-{Ja>%yE17 z{bk(7?iSxaiIu(R{NK^C-T7+b0-~FO!*Gt;kkDw&1>29c=jz5!37ZA@8=I)Y?Lk0@ zxVSifFr3k{&v69Y^VyF5B01f)``fW)y{>P_EX>m5^buB*3}P- zS^J|O{`xpZZS@n>35~88Qi?xdEU`te>|?NPl^kt4{6ViYl+zCqrA=?(3y_Y-TgjH9 z=e+}pS81DG#bYM_8fj@Pp~iAE*3m3i-%xGApv8?YXUEUFO>(Vy^R$75UomOnE4v07 zzL%iH=0;_!-G__9u6}7?rPL8do?^=Rbkb|xZ}N*MyN)whE12xBieNVZT;Tbvc~Bh} z7Z4f0{tdA2JIElMSHv9VLKbHRq%&s@n-##;V6uz7%Ku733Jlr zCI?KSTALZ`RqCBq%63Bg@&hi16?~_7x7* z-OywkOUGF|9=Q(2FE~H!4t;l%?`Euss*K%#di3gPO8!LPS3?t@ymLqT!)`)@(=Pb5bF8S^4MayDk`y$3Us6)xvE$%76b*vp80?7 zeNO#wM27M-r!}U~DSDGbBEkl{{q1$=C@&`|&2ZEsn-<~-2SpR4KE@k9`I(g6=w6_S#4sVef8mRPO#osxjX4Vu@tMzJ3#Mu@<#Z9q+u{O9d1 z9E_QCGC1+%U!cn_?TniyCH}n|FYWkUoA~Zrhuv!lATQ>rUm|fQrmzLwx|u7ySY3Dj z;z=i!kRV2{QK1!)!_Gnd3i3kzLsthmannklXFiryPi z>bXR_7_Wa2Pw}?UHIW4@Jh&?WDVxd{+d8G3W-yJtX_q|%8-HUozSwT;M$)Lb@+E66 z8jy3}2VL8Zti7Q{upgdL*zpTNd#mPGz~ubnEm00g*VyMY%?;XP2DtI&VSf4!hd%9H z6b5!aah>JRt9y4q_sv1axu#1J>>1KG8lNg>=ou&|2K{_`mXSXCX8d+vs@c^*f_&p2 zL>7IT5fWP!_k!=R=U5vgdNy@n=d^*RA_+@ikb5Jx#rCv-Aod!@cl)j7fLK&aGDBV%VB32CqiAcAWx`R!W)?)U#;dJ9?C^{0c`VYfJruwEdBr*k6meUZBtdMJi3s2h zLKeTOO91<37X%Q1x7WIRo@*P;_Wmf*a2o6DAC`4{?&GDZdijPLWNNY&wuw|tDsZ*+ zht9%LD<{sG?_TKWtTMf%T2nHZEQL~N9SQmwIaG+(2Lo#2_Kh=-9KU{Iq}=EqZY)f$ z5;FmurUixhb#TEEfY0UF3@>Mm{&_BlVg9>WECkE=b-I|VgpnXl&c*5YKmiuBjvK<+YFORiw;P4 z5L=XtiiZps38$_n8l0jtsw6~E)=AFf{1VRm`DZ8Z(PqC($lg2U>GRVynw2s?-ibBc zAez=44PNkQm*)DZml zx>!@#^@!B}j;Wo#@+PT+@If*tieC2wrk+9zH2h_XAW?*sGxl;mIHWN7Gq&vd^Ry6B!V@_j>u9#Q;?X^cr znd`Q%UeP@%$?{VC(X*E)vo8H*9HlqG`}K0QQ;|oyyGs;7unc!U7kXMe0C1qN@XHZJ zQJufwpH~moy)$XQJh$_I)!D^)C^oBPg0BHUbJ+1$cZsm!0({{$I(4A-PUOzuOs4-c zYvE^xxApm-m|VHqGFMDu9$tnG+rgRFy-0I!37Zt3;Qj;wSUugZxWRgysG=sc@iOw zXc-kxFba(z#(zQPlJp@Sli?M_-bH+GR6&rUlq~-G z1TsgJn{F5FZlg=?+*~{LpV&X`j{YKRo$t00;A>*vH3fL~yY%dGGa3pi4BI*7(KAm3 z{Z&u8w1&LZQ_nt&YaiKKPf0QF6M}m#SICeO(?ctXoSTH^**fMuo^2tBGbh}+;|is- zuhH-F&@SFE98WsPfwk?{qmR>TxxwVo=J-RoJ4DHCcD8k&X}`$gz4RpSv1~Yfo0FJ^&lw zs^`}LKo{C30_cYgdo_1eaZ=z0HoD8-Y8A|pxO_7Qfr115RnR~V=xL}Ga6g%LEjJuZfyLAXTL#jNu>fV^bez%sSAG58<~K` zRI&FJ*DqG=ryjqCPyo%K0N)pAjsU9c4Ipp>;1+IP^zc1xQX_gBK3i9Q#KP@H&nNX! z;0DCTeGn4W2UvX@U9$a1X45q4%Ist`<@OIy{I7HZ_{B3mp;#}F=}7)7CR|#HkHa7* z+&tx3njwG!-XcW+i06+-l6L`=4#z+)sWl+5%dxn-n68ZJq@0-!GzO@?LwXWmBB>s1 zg&GD&=ey?-uK$k3{qKr4JqG(7G|F`*u2>u|X?|Wrlk!KHgvxBi-tLf{SchoZsR44M zMS9ZcBnZHY=7B3J?t(9(azEN4%@exx9E0@#H4HrqaQpzgCqG*-B7YqFgy#+aKNvT5 z7Yuo{$oySmy4bO&&hA&u|FZzlGN7JeST;At(8S(cGz&U!UCtWM`FFr(zOUehe+R(gFdX`3sd$w2JO*-y;UFjEzk~1>n2|H^KA^&D1hn(-Qsxa| zu3k_nr30%%SUMn}fzk>3Q{p!y^BE1WLyUcx@Zch#;jscv**un~Zhw&jN4p0U{h&i; zW&JvaaL6ZosQJ;qZd|D6o1zwm!=qq)HKo z#I9=dZok|7JU#gb+YAyJZWU?=d<0o12r#{$Dvrg7O1SgdH73 zlhLenA#$#vi7a|-w~h>!&y7`X6WBIu^5747FQ>@kkI)1sDrMn#^^G+={pLX8&R(`q=Nyyklj>&W(El5S%QE_w+;% zt!lK)=HRLRL?9Droij#!FgX(z!m^_P!*7uX?-+xZ#syoIlYn-eWWh7JV7UW2H7Y>; zeTd+3;cJ66Gi`j~`*1sx*R~Y5Vlu24Xo6W5t9e~4NW}zXPbfaBuI$nTqfW=Hb!Ky_ znMK<9@`3b~T>6AWSK?H~$pm&z?Oeptr#Tr!s}Ks5!;PNt|Kw~( zbB0hx0g6FyojMX|2KHZxgw*8jxc{*lnb=%Gk1^3w`~<{)A||6{6QDx4mgmQ zn8P>zF#-3<6)!jIlP8tWF)xN^_Re`R6v6*_$@BBUD{?3PcS zRvg$lgpqxQ<$<8Qeo_L*2Qap4z^nK!|TMhl#dhF?W zy5`|vB_3DIP+9QxhzGjr_HGW}5G|AHf!px%*(4!>j@ z*qgAVAGQbk5%AZZz+v7%;ylKzPx#feXgn~{@eZF5pWHKgnK{&dIi3zAf>D8`D^AN~gK z??-C+gI0S@57l~115f>aGRGm9Fk#FZRfYhw^U}6{t~~I9}P{3t$$F=q{yW{zEC7JJ8WH?mmdii z_61T;pJbSM_(&^Uvx}b!jJ|8e^Hi&x|Iy>5CT2g&HZcw8ku&4&%Ne53zl$^o7$?B; zxIB~@Bvxg3#9QYW$KOB+*!k=$4^V>1?8 z3`#^ozGw~#eW3IBOK6)vWK;=SZ8%7!{=OFfjc8aK-(~!nyO^|TmzQLKR-q{(|J1WG z5*jT?X?-{~t|0|eO}y8cXF(w~Z)@PBh; zMWojwgjPd9(mds+o=_Ska1^VNUkR}|{70KSo>{jJeEu(TiX`(78GG8&j-+DwZ~2cXeOVO)4|Kl{~|uw(_u!C6Dme$M#{uQTO9sy#nn&EbW*knk0P$iLpT%; zhX>L2{{6jr^x-*f{p|)zB<4G-*WUmgV~fW-m>QL{&e;W8$xA#cWpJ5yo!0MTQyt)A zfaTSh|C>|_(WooQ$M3HVjU2GD zLjkPrkgfysXVzxHxsQ>HBcI+Je{uab73SZP+Y7pvxZGMOE+=&s3;%f7whi~4GfWF1 zIVg_!Y;}LXq&P0y*AxQ#7VJ-&QVI@c^wa<-*y>wZJ0PW0H1PDec`k-J$A|P8meU77 zz?@%uOO2_|=kEOIOb~p29Qjr5IE`vfV9OW*yr3UF{#}>3@3-mzWpE{>h>mk!Zi>O9 z?%SuOk?Z|d;L)ILL#3hE3R?IFQr{0tY% zf=Lqx9`2b6-!zezD6w|EBi-8j>i~^zd^PFnMno^QY%#V0GPJ8tFDj6wgCYyx%|&_R zfH%VD>RCtyh~3egE8BOU7Xb?;crRGhz@cMZa!FH`^t#n~W3$f?BcCqu?xH@fzYpq$ z!*!6^Yd}bU|3>f(09bx1(&9{udVQcb%tRgZM%WEQo^`5Twzjkm)se~A4;T^!igWyF z=ni0TL~T1j^(03ErLB*0geHIE16xhs7B4u;hjmhN=zc{{(@EQ*JI_!Rx~~N80s*jG z%2_1x;IrANerifav+)Bi=g>RTq^V@R4diVfGbr$0F#Qw6VM2G0_e-F|UbT44M=)z8 zmEeyLl?gI!F|EG6?NFyno}y5nx=#g&02<%7R`o54jx--W6xK3k1cX}DC(zd^P_Id^ zg>Z&^=yKm)q#dBi`eaRPA(g|TX|Y3hRk)4%2@P!EwKIWL z->1TLUonkG{o7ZHd&`V*PbBzOt8PeMSEGQe6X_#Q%`nP&61w~z5q|iH&nv>E4L!_% z$(RB1HU!|w53@0lX?7CF-Is>wzOVAxT~QAOynunMProRvelS?x{lEYKGz+&on+oWj$Q{E0c_0%2>hgv z>@%%cE>Au+Nes+xKKt!8Ro;S36v#$);Z&a`8!Zj@Ltqhr?BTtZ$D&X$wO!YIc|oV@ zdv3|nx9fN90OuPV=f7))Q)wQ3*e?_Vq$i+GCByMLMj!HwvZhY`ekp@HY5V*cXmiN> zbC1-OO-PX#a7cR4=Dj5kSkes-nxGr=vWwQd%db(KT`WE!B7c?r6!p3lz3_`(kh6HQ zL|V&}lg0kSkdU`2CxPMZ1qnS`FxQan&=^A%MW-mTwmrNe#Cd)YK&tXY=_$8AS4)E3 zfZ|rs{SikSlePtcbxx1ZIjM6frcq_n)~g0eIizOG@^D#f4VgYvgl#$kV@A%FOyx|WEuw()aQ0vsOrGb9ku5#xM+KqNf7ki$U!lT8z?%A5Rhoq3wJ{$shH~$Ws;3|ZO zlh5q;Enbpb?Q(5x#U#;*c;D#(T_oa*=1dLy-qe{i%6Gs@4st0(?4ozQ=yl$>Pc%{Z`zPByG7 zrED*+iH9NI+KQ%E?N+YViuXDtD@dG)ROf_%RzT@y`$D|nCB;Y{qD4^NTgj&d61QNq ze;%CQB*#f4qtCH+Sm9OxqzEue&-8$tQgVLOZ#6ib;U7o7u*WIG>kKS_Qg|2QCQK7L zmlSZ{*e>kiV<;8a==z!rU_UuKf2s6is^pjhHN3kg@{%z2K#rk?gIB6f;`d0>^fSs$ zo^?;_!RGKjEl(?|dv5HJJuEjwNbeMsIW^B~u*9*AqpfdRh9}GucnbGG^ZLvr#=*O>&XnkJZ*hV2~vx~8G zhA+by4V~x^K#Csqovh|+M!8GXw8(^jk{sm8SAp5v=`pMYV^7*%a6S~(WN^PuYwfwJ zABOH}r|D{aP3dElTjo0wY)$g;`tq~jj5Oo=tH9eGuq2bd0?$dS;KmWqovFL4S?7Tz zs^uTfwl9E6G9_O32f=mMr--!_o`MPzgaRcBpLQ{GAX_J%mO{I$=9?enTYKeqnTt?w z*SzS*NSmZ!&ClHUy7yC0aIN3NNZ7fgZFP>=SiZvupw^gFUqR1-WMw{Uj5W^v_3!Q# zARMSoQ2BZVn6E5Gc{L6jKv5TjnDWt?{Vi%6@t1FTHCQ2H%Oyl(K{^S9#4c6+$6su^ZdjdDBR(zB59y0AA=qbA9zxs28O;R5wh7{RH2Fc?VAJ3Ghv#sX$ z(}v}Yv%)pshm<*yNF;umN@Ywy`D{XPyKGV&A;KqYy4VcwQ}u1sZ7pH))ydfMqb- z?Na}_HRj39K~maUxx>OyxM$4)t5agHdv}KiN>*ISER(}v&zA(x=;e#1ztPxqnQ%aT zqYJ&SZF#Z1aq;h=uGR*Uh3XAJyb>NCv z^T@`Q(x$B&(FqEY1L{{*9T|Ds-BUM+cbF#W)(sn3g+LDo2$$ZU-m3GvS@}d=-%i;E zfV|pgAwrMnPvlDC7ew6Qm6Y?w*;AmH1_Up-=eFp3_@z^Az~Zt9}O zby*H|MM?m;wG7#z1$56MILIx1y~X~-oe~fSQ&;$I`?f<$8`C|cC{V4?&EjswFZki_bwQw{o?1J$MW>}NiM_zm ztMNRzV1k23BxSTg9y*HXuhV+zZ$K67IG#FV?u4N}h2aO=YIaj@KRoJx7v>ZIeqH#w z*0)2pKO(K@CX@OeQmR1_2e?vxh}^|w>1?Sp(~bRHlFEYRu==O)nRF&p*?1=QWJ?KA zE~`n3q@9=lBRbeX-ZSL5Lpr)cfs?Zlh^vCozq|bOU__`kZ=w)D+g3!@dt>a1^z++z zg03OO(B0X)$?%45V5}8heUwN|b)8`T|qw)YVpAz6-0t0~~F0JOQ zL2XP5z_Cs-i&YvkxR6`2@hvdy4xQJ}fIiT*ZZFb$KmiB3a^l|*gNi5MlKUe-FRfw@EMW*KcGi5_1|Xk< za1#sNf_f#K+n$9D;gEfy$N#7!eHOK-o;xzL>{b*x_%j>`!ZhqP)ph5|Yt> z=^3aQRHf2pKrvU~_@ZhDa+n^8uzN}aL~P<-{OB1oB&icp8S%Yi# z>rx!14|%4)|NBbBNjll-qZp(SNjLKn{hxV5Yu^HV0AuW9j*(}vPkF^5F}KyE5Ab5C z=aGVXx;~KaG89{vSqGy4|2%#o-(5N25GcNg*FTosVMm-_$f%Vq!YEPWVR-}gF{n9M zEx^|Y$=Lc`x;eI*_)i_eTQw6(iVvepT=;JFSc3D!(#IhTmfi;|@F^c0#Gn5O< zg~rw`Uv-?UR~V-ZN;cqS1o^fa5OH_*CAO8N>sDlyNUSfv{@yGOc4(h#jk@(dMFHmV zZ|B}yj0S*jyD2MkuZgF4Td6XSIMRyC*CPH~52gwdIyr1g%z+hVYhJ}I@F?n&h%akP z1eu@TVk5+r`KD-GCN*l2R_5QIhx{js(P3PLpK0^0WNG)Swo-V$YVlXPqtXiT^42diZ6{F1Qw?7O|Yx3WLJ|HPjGWj&CzyN|TKhv*6yxOBNZAxP0| zOPQsmmHT3A7}O+oUOlr%e|mcs#$Hi?*e{zSCo2qTf#TW2;Fhj@Zgg-eqj!7`%G?jk z3ts`p2w7Xck~(~5+1GSqBQjc$Pj6X#FV1*K_>e9{)zw3Tz?xb1b5H}1&XA2M4R!jfwjb$~Rpehwc<8to%lp6}I$ zx=KpVpXG2U{u~Szq5#sM$f(bzC-rgc6$ zUxb{w+>PDxDIoig=T{YjX1MhF`%jE7dE2~m)mq_n`NQ(32V#00O8nKoQ(_d(z8mQ7 zN9?IB=ds~aDtEiQvhm;7seyCpimC+cFXDi=9Dw>OWf zn=Bo#JY-8Ig=@54S~O060a$;<@J8>wP-b{e!8`Z1f3~G)T^U_>-pu)LS^~)_4-fmP zgTgi&-e;}ojS!Kai9!PC#1BvjZ(8e{35SeYa)@JYkmUN!OpR`aRRTPjP2&!whE-|; z8wn3yL7Tc#{fHiVEa1sBGuwNo4;AgAt%z`pWEG8I+;sDvmQD*$F#-p;!z!Ib`zB= zA9zOrJ8Dr(YpU}mR*Zk-zpDPXCsO>tYrw9VkRJP}{19Owb*J9< zj>$hV9Ye~nnnDBrz=NCl{tyQ5T*aq+(>*ScVzPAYfdE(NhOei52c0tbCMYU*+(R_Z zGZS}tSe${+K|>9pmVm{NpxP3~EJf<@+a&zm4m6+nJRdihLuS!NflUpwW+7C`M_g5V zVO#K!9@%hBpGea`{ZjYzL>(GrtLAUC!24x&V=eow-pVjdl@$S*4~ASVZK zwCCmpt@ACd?9)qtxkpK9hOP^XioQEc>uN8=tlvL~#r!-Jn&p7=JqIF4q>>(_Jm5g~ zc@nrGq#|z}w&uk>5y#ZQJ9mIQa!KSV{*SVCloDtJ?~v_s5K8#zP`gkwbX5&!zeaik z-Df@bp|h{glHZAx&bqbuh7MX7?YZ+;Vq_sb#~hU+WHRd4xZwq`g*_#0!XrE@2g}1? z^te|9H77~t0X5x_Ek1df5mbMU+{e2}pUsQ%<^#27xqs306UY$+ieL+#%&l;;Sxo>O zESR48vj>c*6!6%z4P$1JbqQqsUoU{@P7A`>Huc##Don(ERX*9FG*f5?}+X^^Oe|?fWBl`u0`4Pc)5P@MNuP}dm%SCcg+}wvvm-6^%(|iWdjpJCOyJqaC z7-r2eT`?E1G~mDU@RQ9+pm68y>QK041RYiZm{sF?PK4k{o=kLDZf4)zLt$Ig2Y)x# z&_5k8y5OCx*osg@f=8;;X zmgy%ln~>@ktSD17EuMDwoUG`QtCA@AaVh3Bd~QQ9>;rdv8jp4|SBU-M&$9=QzBnRZ zzOaG!T3n~+qdYSB4}a)({GY4yozULv>Q_TAlg_kYe9+&wYBKAIuDq{@(j5L4Dnsq3 zbWD}rZCu#Vfr~-#?O}^DS((taANLOE<_T&lA8y}*I8Yr{Yc{ERLfBMgAx-Uw8^>hR zfm0k%zUj*5xy2a0ye}|jloA-ZIGL%3M&D<9Cm$pz2WVYk`@8^5k*#=fsH4kIfzfiY z>qMP9fTpabeVE8r|HL-W!{<0+`CSu1(}-8`8o$mvw?iEI9JV%ZFV%FX+kwz4;3GfE zickcwZlCk|i+1_1E!Ogn%#UlC5!rtBDmrNegv?N?dIaE-<9xk zetPndL|4IJToYPCTjd%gR3jfW4$bPIh}?d)tbl)oMO)C^dvkM%xj#dhU2O3Vy8`1K z({R?^5@ntQ{n6LwyuV&HhMZb9D*Ra3{TmANbO9RQ1LDtU7(v?SDp1gsIdt2cdN;Fh zAUAHZcdkw)mOwJI(w`H6I3G6!P!v5BYnu1z&!PMf%-=(T8P|E z%+|ao4NG56!}J|CthIpOKuLBH`L2TTpWo+?^+dp!$9jc#f=ah<{{7ZzmSeiaR7y5YuWe2TZ$Le14Xe{FiJp0{Y8O69gFiHan^wzuUy2=9-9 zYAQa(9KLOv#0f|Q0;{ZtbzF1E`vRx2W3dayZW>suQ0VkTxU_hxe0;?S0hRnhIc4vx zd%UmqdJG(gK4{Hu60Z>#kjfJ+X$^><#vD!OlV0s&hSg95WGGmIh_I`tUND`cC-Fn@ zG3#l!0KniOsS{Qd5(sF$|Yu~jg(i%~6azMQKA zf?LX_00t~-q&#x$ECw(oe~quv>sEbRS)LqoTo{S0mrBQ;R#!`0QiS^ot$jZ(#X8?N z?qA-BC{U3lj?mA-H>|CHdHeSforaep5ST0L2k*jKZ`^8J2sptAD2+x>OekgqE0b}9 z{_b0!uG~8<4yOmMx~*YXZ&6CTC8)Iwp|9Drc^z6mb&)kc)r^eXMQWL zI@nma!f+QHxU$K7#a5HA;&a>3G1Q#%8^;oafkHa}+>rie@BGu!QTr+MgBN=7FUlAg@JCFxDgF{f0-TP+Urz>P^a zGlU88k>ABi3i@QKEiRQ*Wp@#%OyYE;dIqh4NZX@%`$&_mtOOvahU_Z#;~E6VW*dqu zf>3C#EoG!w=_uGH-^$#x@93eXiIAwLR~=X-0gEKIN71(PynKL`{mVzp+hx(3vz9HK z_s2T|_y@OgH~+H#ncTDXDw$r*;Ybd+wb5bzg=WKdg~mEtueh*sQgUuUf7D_FM|oOw zH7Vj{HJp(RgOE87S=2%71XFe^0Nb z1`bgSPF8ZttAJ_cD709ri&?Pb z*TPK9P^RMJbH=yf^O1XGS3uR{Bzp9k8i34d=jh4@4%&VbI3mro(Hyoh(6zD~4=Yp5 zGvHMAFW%h8y1VYhSAuzOu)5Hwa?{VVwvf!#?3$YSxp-2;TNAB1;*>eS7$X7Ac|5UH zN=WfQep~^zKakn^Lox^X0Nn=|=_pSb^U>vR!r#9p;7f^Q(F?^Bhms?|Ebk?Wa8WthRSn(-^WLSfP-J$+R zfCTOQB&-G>N>xCT#f%o`@|jT$)*`m_D$nDlZbj;;={5^CP2ZoBlL8)tI!mz8V0^28 zffyj@o-wJ!uyTPfLIrOi=nIVS#se9iK!jouZO~oAlXR>3KZd5R=wW6 ze(Rc{%IkvOyotD8LQd;4% zJeS77lMC~Wpy1yOXIB5EoSOdEGCFq~9uqZZq6G1_1j`AfyJKc86dQ&Kq1FWdX*CQR z1r-ps`}ft+5LJv@IfTLpDj*j zfR}De9u>Q$%wWy^sX%V3W*k(~0Kc53M-uJ~C5tk&>4B~*v)7NDlVQzoLvD<^ z?;nG|PzldwRn#)Q7+;>jznqnvRCX&=+Nw;49J%d2P~?7WaJL>)RYkzOWCAUmA`@vX zfCI||TmS1#uAFC`-{Zf9{M^)A?I5)i#Qc<5h>zFjdf0dWqbHcB+#`N<(8=D1?%i3D zy>TpKRgugBwu@OR!yLpxy-`-M8Z5I9$33qadJthw0hBtFLT*!hXGayoVU>66RO}X& zVIJw?t`{m)30TMzQNFpRJxt-5cjrLs)2d~zho{4D$a}NX5*QATKfb%JU5G#h^2rAB z!%^v}A1GUQKV>zxOQp1r8*UVEscrm$!>k8+b_t4Fv>r;Yq^_DO$*bO#y753c%4gpW zeU+w2L(a9Juk**|-bb5D(Y7&_*!NqBq~>CGtPJelb08Myx%RQ_@BBTwm!E_Ug`uuD zO#o+LT}00yWxpEdQjHuXZs@YX8XD_{khglMqTT^?&Qz#$5D1RxxJcNLD*gND7vKEL z{L2u11BZ2o|FP7Q)v--x1(EiJq5Fk@jf}@oDF4i%0ygbwVfIP&TmB8H8nVV`NGz$pH_Alz?soHJpAG^==O2Twz!9H zan9_XY5&{}u0K@+P{C%ke;F7qR)TNuLJS_pfBQ~}zAg>r!ao2X^PiHKt{Lg=BG6;^ zLLVAsz?yOrv`K$L&eUq*hfbNK)>TIfIS}UGsd)6G$Niq49$q zxCx^%A^K~&G^SA?+N^)|VC(l>u>`&Uy>M=PiQwcU{eMH}sb7f}uapddiX%4s2i5WZ zFAYgD-ac!yzaDMroL2O;*sjnS!?&;IN%I*oWm&`Q1g?w?{q!H|9}s<(^8=?ZaGu*c zR=*#^jgpAP@1m<}V?7FaksmfLKV!)gP-#aV5DtX9MiQ%<9%$}s9r3z!9t^Y3P(_uS ze+6IpaQf}m&jn}y<{b@wNbs*F?No4Lk3MPuLG(Ee4OeIaEbr(VP5tC+I_p2__at1M zANnN^jrlTjy`8qfN3wb?yuA>0vEzB{(NA}QoP+CYoYR-@H|erXkAar#Pd1fU-#h=xl!7Wz7WwUr zNjPJC{bk_BS+v};R8E|yUgM{76VjYX-_D(RDc6v%Cd>rEqWITbju!@hwbN+-2Cb9r zvXu}$uS)JeZ-zYtq+e=Lfa+oXaWL@4OB`Ic&G@FTq}_X8QY$&u8&YvAAegBSvfy%& z7P1Y&Q}#QaNy%rYF)Bh&_=gtW;OFgVtlJqwhdM59zS%mw>}(-CR`XR{$Zo~AaoYE} zVh5O|p$XTb?yhHB^1|A@6h59eQ+FV1fiOWJ&uRMvEGT51xz69BFDxxsqs094w;#{7 z9EjrKr=9XmFbp~Hj726OyxOn-`*t;okMH#Q=6Vy$3!GzX#7}jm>qEYnThVm<&!N30 zP4O(8e~cU@XS{_qJ1(&QNb5){2GZoooIlVbViW0A){}(@&{c`heZY4G5IDI=43k>t zHMqx89YV+!sHl~St_LPy?O!4LJ6@#&rr}3T*KPoRLnMh6lsRI2kZE%YRG&j|emddz zE^z_&tNyw+{1P$La>(W!vV!M(^ zlAAyfIlzq6Bv#%UI1H^P-EbM?<-)Jf2KRKGDcrnBoBb;_geZFPNs^KO0MlWK70k-% z37{@*U&8F|p&yfDBfnhT4?Z-sL?qj&a!kYWP=L@jdjrwL{p=BEp?{-MQ|E28Kulxv zi<wnczt9Hompck{2y;gT+dC2H|xAO(yaXj%oFfimKRAUEK%oUD+om@keCnjOd} z34Ddlx-L=j-S+WgHC-bmuPMc6RxNra|xaY zeO$JI#Tf`8mCB-IdQWHQa=yl55^$d8ILTUhV+9_4U7cIRt?Ptqp0|v;Q9}_?AG5vm z4#jYoPLo<5e#4{)l}3t);(T@atj<6$s+p?w;w{pp164OeX}>pJP;FusrvU~sa4Ebj zc$J`las$SdrcHHfyR9zj0(g}tH`P86DNO~}PwYc1kEWD(E?kuK(*g&6rH{Bn`=B$95Zikqytw7OG?&nhL#aF{KV(Ce$4@3lN`P z4gYA6phq-jBD~0d6wX>;xUHIZ9GS7*02Xy!g#Y&rzp&68AqBoDU!lv%mb}W?C?E!g zCGjfWW~dJJHs=b3?I!&PI2|>*iVcwt2VED_zma7r`j&CblHlv!2zLIK&MYiJ^-!h9+&U6 zNP;%x9G;-dn8M<=$@Dv?*555w|Do*{`AZpLNu|xtCNn)d&{!c3nTa*0PMCZe&7wBIy*v1{iD4nljS_1dLFIP*|>+i1;>mqpRGCKWN z>z7wkz&zHE%G}G0mo$-WGyOaG?2yrbSn!Ny41sd^`nRRiLOpJocTY$s+qZB^v8_JypE@dyHH>rvk6}fAgVNs{(H{nnW4tj+&sM*;fz$Zx(5FM@Q zJUg4ngL)dCW9S^wW3bK3ePR#EYW$)c^98`dGN8Ru7vuwE?$%{%4c?SLkCUR2rd@8M zTj(`?$AdNK#LWdcgMN_HCec>4pZNF?9sJh%f$#m8LwErKtR9>4T6sEm88#9Im7fBH zUz=ZtgWhNU=A`Kq+v?z2*_ZfxG16^VdS<$axj}ZAvSVw$9E4two)_oKv5Gf)Tl=B~ z(^$sdOEgjDmZnENfJX9{vl53&En3cBMD|An*NU2UT)r)uJkI#l9`*NoomSXjLV5rA zv6uil1U%B)Vk)cTeMA$Pxz4ravX$p-KPcXd6pSb-h(G*c+eIQhP$tN#s!CnI?TZo5 zm!;t6*T<+54)cxSoL4DSS{Ud!vg3<@A~_%zI30d_aiCsqFNkd|x?(0ZK3$|;(7V#7 zD7j+~*=`>?XMs7D4^cpiF*X-DD{AJi?FrMRSXy*MxWL-Z6e)4THayBz6^yR?Drp}z z5y$X`|8@m~i78jZFeQ;K=tDaMuDUe$QjWh(V?>l|^g+|TX}uqJ`o!1+nq;t_a!HWB zkbq0c8iHg?Ms8%=+wtA!8m-~sTgOU$duSTZP8@VIa-iK-CMJ<(b0dGK^0%6ZMe*91 zy4*M_bw@Ckz_m#n!dSqrRfU`5DPUr&34K`RFA@NGo@#38j}~i2v~tGFeJ(5{Efy- zkmMplBJ}F_%bvbP7!~s#oOOI4QX9R*js0-c{Zfii&?dgzERD-ZNvirEP3Qej<^TWj z>zw1*TlP4|R%UitCnGeFvXYUqWs|IPA**4pcfu(nA{1pihe#q$+4~q}kIaKJzP>+v z{)X%Jx?RuL^YwV#@0x1t2EfQ1A{BSY_7P1BEaWLZ0bQ@&!)3ve=y3u0yhgW5pobi6 zemM9&39qAs?kAB_MPd*47!HMoDE*e)bWz&(^F+&8(3C=kKk8?p{@;(=kxdEEq_TS1V3gr>ofRJzVGg^G9@t{4JU)@U*>;#@P@= zLDkuzE7s22@~6Y?O^u?w0j?H$W19y_iLc*&YPDCUT2S#Uf3jVp^d^U+ zWy!|x1Z+dGAEgtc5xtM97N+)^f3;Ru;1to}WK%{km#JK1I`?NOk39lGLwpxa=Yn`m zB`VxgwBX#ia0N)*by}-x`x7qQ?%`7;PRFNRjP_Ka3)}t_RMmRQJZ|^n*qebCWN8KH z#Ar>N0g;k(U?7}i5diGKZs2)$VDrGXt<|=>j`YTFogK$m*oyBKx`>`zf{}UO@gijJ zcfa|;AnPL;@so56vlvwoTF<#oy&({j;Oc38)}!6fRT?X5qwNDf>zJ99UT zkF7=u#E=s=&4fyduI9C!PmanV6$u`F>WqPq%CT(h#LkP(BA2=V9K1G80?b&408phq z;$`5QbmU;wg(u|FKoP)!%LG;C-lrzIRaW;Zj?uaLJ~J&}&?(;GZ{>c z@6obvaxC8oGM8PP&|8VR{|}FIG`R!>meD9Nq9O!@{RLk(%(m;l3*e?rpYtEL6Q}Lg z9j6dikTp#zhi}u>3iuz0?sY86k&M-xoQ=)Q~ zI{MUFeow2}MmNV!F>j!i5B}RTu7~`C1bdK+iF7Q^-w*$0{!*xzw|TXVZT|8_2Q9^< z?4aUzv9GZIq9X?rv#)o1$lZGCtq3Uqxe9qrv*SolOCVu>KgI?^ZkWgtV?N)1|Da{Y z*E{#?5|?~*A}6th0gSR$eM!gq{l@#2O(yR5_s6hCl>CkZc$1wx|8psbV^|^D?!jRY zY0M(+5aRe>7Jz;9fjm(6C{vHm^wPf@REZY~e?YdnUAZBuoC5cq zryExoyr^Q=4%ss4_sYjD?%B$?(0+Q(wb{aEbSiABmrXk5dsFi$^2bWzCZPjyUgEl9 z#_Wm1%lRWIS!xs+NiX1`N~qV@ac;?&#IeGNGX81(zhSkHG1c{SI0xUAhnG)2M^cg_n6*YKuImQYmMo?gn|viBELzX@Hvm~X0zaNHw_-lr~q z6iBmo0VA8A)cBU8HUm~HiC^1Z1ljOG0@!5}Z?_pSBKhi@i-o&yPlY&lCX;_eQ_0 zqF|~h>a+V5Ms?T2VB|l(jMUAKpOUm}BEpkf_Ophm9Lq?;kqSr`gE@D+4mR zQVNzZ#`A3e#@zwB>f0uP(&R_Lf3VyS5Lj+v>1CQ1^b26i)H;8 z<-K6LWZap%D>t=gA1SrLie7H|%bSX~zE#oVXE9U$Ma`!)6#a#-uUMN=bo2bg29d_G*n?`0-=#{c; zK0c>dtKessLARqTz5dR#&JhBc^6WGs$OAOauMD1Wz5%-41N;>+#CI*+k|$8g-VYH_ z<0aX6damf?f)Mh?C)`EfC)y$+qiR;lelPN*q-Z|&gJIlSL#BMa8K{wr$V$lg z3*3;$CH5at{G(=abq|jQB#KngJA3dmKlbiiFf$4LM+y7^Gt9=!e#m>)p%<%6{CUzd z#lM%-Xzr3ac(Zpo`PFOLsq9C6D-lQU8^($h+uGtIY$C$sOkXE;E$=GR^{hk$NA4MF z!B+b1Rlbj#EUv8L!vl*| zkl{M;^%@~cj?A&2D5WO>MZKFHR!cL`*K{_>?;N>AgEp0tJ|2*~L@es)9u&M4jym2= z<#r!X-YS9t+<&N_cMjheT_C*AdrCq(T>_$sYrrn7pON(*)EMVFqWZuhNHhh$&!J^O zb@+q&neP@NDj0tQ9YjN48g8e=Do8+Qz?~VufkGz2PccaSBiiDvxxqn$uc|1RXuAk( z0W0fy&z6ln=f~~B#CueR9P0O%H4g);uOD|G!`fF1_v-Q_z3BE5j3}2jQ;QDu{#{B^ zWx*72X>!O#uX7LWO+2|ZZoc>_O1;|ePosOW{jDuk+TVFa*YGVFFF_a^0^_T2IBNMQ2=U6}{P!1NUj+xed{>fP^*d^0hhc3(t8} z7MO%xhag+^)R~cf2+yxGk5q?nY#xu_eJi&k)_(9T%)jbLMVARiWXiHk4G95jdSBnBWf9%U0YK&Z*w2H4fIB*0-u&LP{;i8MWNQ%+~Txm%tZf8rB8*nnm7K({&E&r@O@xc0B+T~jFQTXo9 zJhd_ojlVQ}ZRZRqf=ZdQkotmRl9OB)WM?6XT0b}vQ5d3+n@9j=W9SqRANwyXN3s$9 zB`4~coX?i>fg8KiaSIWDBhm>6LA!^N{UeH7t}HS)J2E$${s;zp!ZOo{8qO4}*yshd z+2q4J-<9tJRgLDuqsfpMql-5!jvLMb$8K0|vYF)+9`rw)=2#xu22P`l-rU5%_Zf$^ zc3FS-SFIz00Odd`=B0-?6~@a%T72+;8;svxAJYWuvgi*l(DZ#PKTGoPf*>H*GqUhA zq{%-4u*@{XSJ#N0rhjU?S`(XYwRh}lqiFHpoDU)T)qsc3_hWOm0JP)I+&hlF_ke{1 z(ge0qo3>V+aOXXtY3gGAkKks7xme7c?+2p0x^vj2FACB-VdG%L(uZP);oon!F@JMN zN1eL;>jR4v_f)EE3q**Hkf3GE$Yb$FGbZ`?w!1w_x zShv-m}WCP%NS!N~t^@ZR! z;yBhArlNm^eenq3(|PQwh!$xk*lvLQ`s3Kk0f7pOvae`=$4vAIL$cMXuWkTkUJnUq zozYsQE!?^@-w5>Fj&@)fSsc>hzmOqCIYuX)ehB~qP|+$EldlV*$jKjmCV6UJrzkFA zE#bh=$q{Gn+&1fwySdfB+zk+&Jy^byD{Q!e1n5;*y12t2kxqWuHez65`Vu{9-)jUJi|(YyU0!cY%>YDGgZg8@oF z`?L-zVjdv4WP&gDmcxKjl)qXT!S1b<3EcxPc$}>aEx}fLyMgkpSF9~M^4$%~eL8{8 zBew-J*|83d-h-jr+B0=QE)!`IAnYh;&32Am;?wj>{8I-2{zrMv4e9DzspyHN%6dAhvdMj5`Si;yDWsp*i1*_RXPuT4E zeX0a#nyx%4J&2P&gb&lKzVb*2{CJ;btIDSG@!>Dt^n;RE#pmv&hj!W%c zU;k&f;=$Ln$moSRFIL>9E`;!9QsyEM3k38n-bnCz39~jhaLCHKFp*mJXJy%pi(r1; zVr8HBBlMX7S@XQ*xZYD0Gn>Zfj~I99p1@0Xp>Wfj>)B5nBI$7}^meTzUsu)L)9pzX znKiT&+&|F_m)OHj+Oao#ReD&RL7FEv?V`tC%v?$+km~R?ru8tOo&K}Cxz88e!fY5o ztva+BWV#UW?Wg-obip>0rqE#}EM>o@8yxTzw8Lx0UgKVprQ?1P!hGves3?o3$D6;x zlYGG7wR+js^^5!FY=)4fb57N^94FmBP`)S}FK5cTfxK={a{kIg2vxBVX=@lW7+>KM z16+og{pMHt7Ps^0X|VV+;#Yqvk=QfD)dR&|lw)~sc;=T^zrVa)1ofV!h7BBdeX}dF zkrty3CEeXLzFS**Y6IXjX7@k%s4Nfi;bMT!fQ|E4@;t9pt~Ed7U+=az9N3Nc==K%> z!*E_u9g?03Tv^W|b9VO6N39BZr`P}FLz^^rAa4wRciqdU)^dCk7y)`q99XgWw-xWU z6R79!-j5s1`0ry#8TEP0-MhP;1Lws2%h~OwLxL&M!DFsk`jBK=myl!`mi)V|k%GUB zA$6A@aBSu0S~K7q0R6wYKuni!xQ!i=P?v%6C!6scN1sA2Olsr_YB^aJ1XGA|r? zc;Abj!yD!BoAFb^Eq?1p1eq=gPM3j~mJar40N@JD&k>Q+NDuI!Xope$>CSqr>aV`+ zu*rN_n`c5U1qjf^k$XoCr{SUrwj~)(N!7_ z*)+lUF*&}O{(bS>j|E@X$xdkUzkRY6bGCql?S-d+$CD@SuZtKx?nORn#Uhp-w{_8v zng2KX>KHM8eDvzb`$Vx2dk$u1?KqOIH!I>)puKLS2?z;);3q78W%^rXJ4!c19iRv6 zA!$@J8u`|nb!q_}urM{Dm+fKJJ;#(?MtJg7Y;7hgaIs`XL4(5eYXeFB+Vq zA@4Zbvn9TVT`7u)`W!>___6oS4g2^KyAZ=p2LN5Tq(T$A8-6>tOvGQF84xEoHD%en z9;Do0hv4Op9opKmWg=dilmD}b8ABXJhL>QQ|C$O0&ym>@@a5lhQ}M#=hd$a#X

< ziL!x5@7Gd!z%q){WB8~cM4mqKG<+xkKvlFISBaT(N;V<7amIO1mJdg^6$j!z8EG?! zrq#l`fQ01jFfKB2>GN5JL=rvV?;(hK-4!x9)^6i@P!JVl21!pz_qCWC0vV zk5$xd3;f5lo%JF9=JIruk9wEw1K4GLg~>=;B)zBV5#rDDJge&puGu@+vxlhl9~4Ie zuOu^3Rn%Wr+~I0H=X^&Ul^$7sCHX-90))dL`COIl59_=1^l7RB->pDU4rRy+hUOe;kz-X z({ii?cj$}A{P)Lkb3@+~bHmV#=l>)17>{Ur0fa2DA#0Yp7VOP&1)mPlxW|Ef#l8FN zJ7jC^o%w(IF{!7isRv?G$u~#*@Twx+X1{c{(5>y&h?FdSD@42lRgjltTH#>kPg=fh zN{SLf{oTLIP-E?heYH|qH^LlS-4q!Xt#oYzcINmtVj>s~gSW)O>>iWpomvd+eUB{B z<6Ye7`h&CXHzw@LJZXeGb`*{1i<@MmO0)Tz0y9^S{NAPFKf`CoONuhK5NuLg?r+;K z*AFHfSsBHA9nUVuXpMWcvx-a>({&#WsXU#0E{@jN1moyfgbMxr8#~9vn62!^+TP*O z{y~Wki84UmISJ#Zqe8fr!aOLuqKzI5!w-L0Af3y21_H#b(`9|-*ZHulvb?l^zIX81 zPc6RM*MiV%PTTo=Mb>}%zsrv5WCdr6mD98A6pITWo-T_fWAAJ-L2(88V_u_9I|rSk z!Nl8m%7U3b&!xc!5}8VtJpG})kz)KR{FDY~c<2XQN5&hVbNx- z>eczxw?~n=o73FR*B=<&rV&59~HCNK(zOQcfr_H+={;qHNle*6cLD z{Av9AK$qr?zC|T;dE3uo4y6v)?n_yau9@PJp%dzMmsUduv^V?9!W}<}POWWSES#wg>)q(? zH74(V`;eM?lg7d?d&JeffNSHlWe+G+ZO^SOBEe`e!;b1u>I$Rz(L2`+nVYJDz++ z^mVNs6L0OgOxoN{wye!}FF1a8>3dwhmNAy~DR$&_cI?Og@Qrfj(YMIo%oLNK3inAx z7o+PTD}&G8K58;FgMOzM(rIkaV%`ZgrX!5E!KV`x^nrKj0vs-V_BiKeO zd%0ptjEHYkk7l`_;reQv9uJDLvz*Q8vnDXVu@>Pc)WyO9r-oZBx?85l32ya+ZqS$u z^uhK~F*BZB6rDNM+b1&r9=l*)BDPsyN)Yrkw;$0v52}QgR^saz9A4wqh3_)Neg!XY zDKSD#AiE{IV(Yo)=`*Li^VFgEIBf23mT)>K;M6v)BX34uD?@cOFS|6Kcq_-jMgzI? zY%M$0ZL>z`pQ!T_dYVLkCD3Vbr5GH4ef8VG=mV|b^>l&hRqqgk`e=Rm!@8h|h>BXs z^!kq+%pG|i8)6D4ZTZo%Es!3=r|G69&#IfK_VfbM-Vk~}tRO-m9X0ua{wAALtkb|y_KUDYa~4GIZ){4u)3*$0-^1CJ~;*P|skRDK6B;hcE2Oq*ALE z|7*88!kdjgd*ra`lj9O%V`IfM;ODAFWZR~xc_!-eC|>JX(zUB6Ken3~KYutQPE{w& zZrvnbXT$YX>+f_k#4oK49ryNot@``FD+#qO&+esnukjRFt{5P)d49<$n%7HSDw;ws z0#GV?Y^d56QY+sw?JS@6w{D=}da<#9t|K-2wPx1*ft5agFu#x$FsO_qE^lr|s6ci3 z1@-n*P8$mct^Z2&3>xQP&$7jz=YeuIQM76I)0TDUVm+!bZ|Sq?COYL&7<9I%f?DiF zDKUM_jN4&oM7NvcM2TjlpjRhGbtl819O9#l^fI_0B;}+~pRYz!ZmIPFijfC*(IR4) zWRY@7+|=r6%so)WC$E_pKXln|_Y~^uY&}BqD)4+ll>Wuk%EXyV)5X*;I^-W1o%rl@ z^MQJO(a->0$Y$8pq=sfs6f;*x2kJ5+wQ9OTT0Fg;W3nMAD=(`oB*b0yj6vag5ER_> zp$TL;X1aOpISXlqh%j$*baNGYl#pI^cw$pCn!)=}`!U_H-wjn5_1pW;BPEa2vJ0+K zVtWi}SL)GEMN{{8Vty=Jo1&s$)Z@yJj6OLpLzz*t!rMXjuq?o}v-e183#H3LyGY{$ zI`+YCSw_eR?>YXv=>UdJG3I^DKWs923TA(*N*sJj9FycantK>=J)u+=ztv~d1If1q zj{E)=N*UsQ+s4beimTjsJ`9g+!9(&2CfV3Us$Odna|cbmz!2Au8%rUjs*Zb)Z^)+m ztI_Y2i_JVV#EDRj-lMTdTrO>NslTn@)pxw`!uKJ9Yqpt_d@kw?M{K{hrFGWHCyTMl zxRwCwo1w=H^0iFFvRj~djN{3_+x*~IPLE+9WKLi~FDP)r)EJuTJvoKdAnqW}RP8>4 z?pn}Rai_xnkUxvs!cQKpd_`MYa0mCskVRr0EL}9;G7*xHQS^0YJj@6qS*FLhzP=BunfUNIBCEHu13wAZP!! z;+mhY25!dGV#^UZP6UrfSE$d0`h_7%d#C!;dPdCK)3nKmcWqIGKmwg0Ww0fTZSKLl z?Fb5u8g8OAaqBoKQ=QK^N5D*Wd>mhPH5*LIwQkeDR~B;Fg+b=(A=d(LSGZqR&(`|l zQmaX-Uq1O$IO#rXnkXf=sB@<#5IE0DIFhNT<%HC}EP(>6MR|u(NuHu>PIeK4B*Cnf z49xiAUjVaHgtfGhR;+dNIl}b;p{W&FJ??9gadKuj98Ss}ku0br|v$Tmr95>pI zRPrLwS0Ffgf@=|CCB8#b%g3@z-9>vhq-oO3a`R#7%b%nv1aCar0^lrue`no+=Tnd> z;b+!lc#eoE4JtnjOU`O95~7wtJ)S6eabkiA4MR={Du?+1OiAb`3ve1xV1(>HH9%wQ zK0o%0*{Q_}cp$GWj6Xm5YVL1tveR-~gC^`U-)FSHur9Pga3}wQzlJ>SIwUe1d5!98 ziyJ0O#6?f&s>UCp!R)wBTAH6uk5WEX5rJRZQoF^E^yFb_2`Fht&KdeMYMZvm)VxCw zsQ!mOQG*4mkcr z+@tmy=$?Z|>*9B)CVSY?_!k2+x&g3$GP6oPml7cM*p~qi7l8IPr)VCzj|Lz7M%Pif zAFI3R>oKYLJAANcR3288kv2&3OLfwV7W>YyB9O)7;;lh=r`wW2x|wZMts8@;Gyt+i zC_Owia=i^dAj7vm)bKcy=0)eZ;FY5Pjs^j;-9fbG??E%xG_4sunIq!C^zrqrBJ}hz zz3^j#WR$M$_d!-M7)~E#j>OUaL6ILsq8~uiMd0-&pinr~Xoo`gh&Xd@yL)BOR9|}; zdzk~3LrA&-FXdy;ri?+l)?}dx)C5Ym2N>H5ZghTg?*CPyT7Bd<_s!ZSwW{uo20{f` z{P^`$@2G{U^Ae#-ak>S3d}0!8Y3k4TdnkQhO+}kWnu3)8?lEA@aDs=&NBQO+EnOpW zcL3Go=x~p`&o_Z#W|Qk;D_17z?-YO+dasMD-u%nEdorVsTS5wJFtW^q7qy4XA^3wu z4KAvz@Et)B_j#1c-EJ{Gd*i-`-Pj9J@L%DNYI!8AeTTL!6|V~avBBoDp|{crae{>X z!KLWpIN?%`TBEj^YCb0qWF}I%c22)k9f%Ij5yGSz z1Y7fi=f0Q`-<|_ddP*=QWSTNW6is11`UoALIDmCx>ob`*ZW=Ne1Ihe=;Eh*|W;1?e z0k3e<(kj3qO;zwaDX1W+hTWU)2bS;MyUk`6cis^C>R+hp-bkj05I((H=q?u;DIR;z zd>L_bHhvs^QOSh6_9SxD8h);e92<;F@@Z!_&xVwb%@(j?vLRj&<~1-~I8U5Nf1i_} ze)z|iy*3}BbhmIW&uA%Ek^`3wn5k)A7QYhu3KT9ye;GzZ@aVQIP_jkl1dbJ7qG9CL z7%rqiD_3@8eg(ku<)nAyW;aCp#OV$UQ-#&N(TaZylQ2YEi7Q-CEH!}tMle;{m-E;* zc*@Qi|JZ(N>bSHY zq-Olt=|p~~=MX)r&9g>Q#K61$zOV%XhKB-;&Yp~!ituSo|*X;7u!_(Dq(X$p9pp1EX=6e}PZS?~c zM>>^=_WiKwXEZYWy7|09KAQfxVc-lo^$C~Da-z=-*}b~Z5j{C90fy9F=K`YdJq60= z(P9t@Zh|ZuWbI34mlUXFaxF6EiTmeX;T*WPb!qSI?N<-e(;z4)?n(4zb85Z@p61WK3UCCGE2&8A*y|2$Cb#xhu>ZV-;YZK-ed5Yo0G>^gGYI@jR2>&BPgWi0)>h5qBw`8RUlNtmYe; zc^JkeF$Z;&ww$6J-x1I4D*d$H%5cF00T1Nl6f9*LC zrg2i%S=L$*a}V$~;{6xy!od%74hk5G>H@QAMjKD<|Z+pTL=vKA#sCT5Y#* zvN>x`wSw*FsvDp$Q_G_$3|~~*bi-E6pYG(#03K31zvv1bP&HF5t-SMf>wKh#4+d+c zC0@jxq>+|Y7#37gBk~y%2GpF<`{tFJ_zugDyl{7Qz0<+Sa8tMEZBw?ya%ic zaQfQtTDM&8Ls-0d@Gi!YX%chKVvY9KdC6f&+J}VnsCIh)Suljj($_!xKa$>5C}?zz zdDqkFntyOjYl+c1Z~^k7n74&WPe$`&iR_VsCd9E$jM2s?R%8N9^EtR=c1f&3Gi_ z-B4=q;V!nYpY1x--~&773FaD~kS&|;qGCuBHbM&LL0lhW+Zu<)-v8(YtYOXKro^MJ zOm9ma!*+>|X$|gEdtC8tH(^TsW7SSMw8EVmR6MuXcc~j&ev(3+^J$c?qNrv%96j*y zsdg8X7}i=qX)N>l@Pv2G(@&HzlMW4+P=+YwJL04v-#BqMdt142?Z*G%UieIn#ev|F z)&^E_Z)kuxn4g@X3}hKt>ziDneoVmzS4(G|@~$Ms>Rl6f_c9&*!1c2`R5L^^58meE z-`zyL=C$dnBK>&QStLzVPG*>mHzd$LZbV*Zj>)IG^TAZp zjTGEYmQU}UgK0vlf3o1a&cH>g(x4&Hsit$=$#tqofND=k9i{yQLp=kSk}O$(K_2Bh z$xMc(CahMYurd+pT&No0VY~t&8a(6KofY#coY9`5TFSJTRq7cbZB8N~VqXAMv?mt` z5K5gG$H!=+?aQw-KJpX~Izf^4db;oC1}|G;3cI&BHixjTfSa1VgTmxa+}zwx3R&0L zF-bXc*d#T9<^_4TR|fMDr_3825g{o}U^gJ8X4PHZFoq+FeRufsjuc=@Ys8d*n<&P0 z=mO7z#dT)mK@`ywbaKK80M{s7;<)q~FoPLyL^7l?F5l8!5W*!g{?qZD!}L?%yeD3G z0z@b93aJVpqfVV9qfMY;3+$lr%cout6gRb~=n1d08lx^&_lw`x2w9-yUGCT9azKt2 znK~)Lewjeca_}}^zsg?fBIzOxEwwDQvbg)9BlHfsB5SsWOx3FlTMlsxHHe7G=gZ7L zMTWTzyzZej1HVHy{2)?;(4B6ld>R~`{vW>p;H307QiIx(gM6dTE^L(Xj|xB3DwX~V z^!r@9aClUCqm!AI9*)6%HrMyWGnnM83h<~11~*?mk7V9uyM5qRzCZg^2MS{!Gwyl2 z(K&Z>bgTa7hi()9XWTEq!$7|cbTSmmiu%HWQrXtihoI~W>e6WNT*$aUTm_K)GX2d( zunZNKsQh|aMAY40rabExCsph>;S86m!M>5s#4CNt!dy&&oIulcWVHt`F|^ zIK?P;qF;$E3~>82dEpyX+P?NU?fNh7GvK(>LmJ}Qb>L5nEiMNjyoe(LEXee!Rq{#~-*42veYHxy@mB+r+I z0(F?vj)1|Uq9Xyu*Z390^QcsM{9L0kQ{i?jbZRx)BR|PZ<(C^*nKS!~P6KjW(eOV% zaCeZ-l1fKMpT`L#p7vyP;AK!u1a`n>XGjX$$4{@_MVA6CUSzglB}iL^c|i;}rSlO} zENQem#M2B$4M4*`PicSdwX~lXR~0+%CkL!dr3+Fo2*8f-576P~SWX7iW#*xPH+3`< z66{6kl}*Usu}Fq9hA-Ut|EhS>Q3`{kB-9)>Ppe4Bye_(J|KAG4mPu{4(nNi%Mk%hXbcElq~&W=gWoK2CMIn za6lkk7IBaR5-zIcV(=u%Q`k;5=w1%P@2;0B%`9Jo#^5;Jf5ScgQO$5X^XN}nd3mxs1B3d| z=ub_c#rR6I<-p*-;{i>aY!9@SleuZWg>&b?_?rH5DqlMnwPhCcvvl4rrbmWTwNhiu z4pBaGq)gSOF1Fo-<|tz$fYtyQhw;7F_(DgtGUpKLIkOGp_gJ^K4g>?K9W(yYzltR^ z)jIu6w$ZK8TP(P`exe!+Ac5J9Ut_P`gGQIcSpcv1x)N$s%z?)L*pOOqU%9&lzV5-F zeIfy|m~RGTsW<#(!$_+9mYxv}#t+8+Fxo^Ur5WW-*7yjukj6E=N`JLi6bc=o0iL2o z^?DTXuCL0tv^?%k99}J&l|CN>uYGIJhkpyiSX0&9pu}mU@UD#-OTB^%+*-qFkLC9x z7f_Q97YUTZ@xrLlC1Yud)BOpjPh8XkniwrfOg92eXe7_)dApDpB5MlW+YY7Pwj|G(Uq@r8uJd{M48Z9@=&^?YV>^k5I3S~ znfwbntOOHf$B&)vIc9MQs5%0O{ZGdsX!yt^_QJ8Avco+_ARk&23r)Ixl^mvM2nl;P zjTQ3-Tp&x~vR6?I%)pm#^u${NwQ5kmN!tC%_;VgH{c0xfkWiCGIG|6U`G%%?vz>{T z^a0!?cIJycp5}7Bb>e3owmh$nv>deK#MZX&DW%w-dWjds6+@v!eqcv=e;TrdHU6jD zyYq5BCfd?CAEfA_SBlTh4QKE)Io2-V22b!!ARZ!2TS@$h{~!zf2CL!*PLb10T`CU0 z3(q=H1(1o;kn*^I9XdA@>B7WP2CaV$wPdd*%Bl3kd!H)?>chEnLje40Vus7d-!!!i zgk8h2Y=Sa^pbd2QxP%`!7X~p0AYol|=RjKE#ci*CXgddFoV1-E>IWGYWhx~aY&SoZ z4u&q(+c)2m#Wm5>Sgfs>+bC3>ia;>y3;#vMG;y%+qeJKciT4c;_1fUls9Tiy`1V>7 zpjDd?E3&&DJ2%ZqhBKb4uD)HNyVGINu+ntV_k<+w~WcvlHp%x_qad9hl{Wf-QAFrY%l?W zccX*{w4w&++C4Y{_EU#5(&wxe)#-kf&7wzL5)T(D==I$Xi>le@>#XTd^kk{#ney=j zXe)0A@Sf{xj1Ovu93f4f(k{ zuAwnU@z-={pS%%g+sT=|d79mfCRiZ;!Im-;9q9mLXSjrJ>}k-(+-}YG@t8)&-{@V4 z_Erk6|5Q_in=FmMv|VZ~Gw8hl^>6$0GuiB-qzBhk&>u3=++1CKq6`!tnPZNb*e9cd zfWJ<+*S<3JQ8JYP8A|G7plt$spJQw=loikbB1GnWCYEHs07jgU@qdZc5q3W2u$>y- zPNN?0uV%dUGzkH7jZsL7Mk0diP_b^1`8Dr-rmD342HWR&AC@d~1D{bkODahE?ufeE z`+TXSmUYMegbI6ub9&SVeAcbxc(Y3Jzpps|w3$ODga6(`d@fDg!g9nrDIdOA~4l;d=si5D&H8Z`+{1-Os)AH`&kh97uZ_+WU$-dBxQ;%2g}M++fbQcBqw_P z+SfN{`>=G4$8n0>tT@wYrh1!*0~J zV3)JcO#`~^HoU&l-r73C#Ox4h2KoA0iVZTp>NgCmLg3CVH{X{x$4H~v0PKheAt7jD z^Xi_!F>oh+%0m9WDH+a=8(>5hhv~klz@7eW{0)4J%pb-k)APl+@CJly^U+DrrxY;g zgfoX|Kkbn+R8psTu-D+5fc6ElF8Ab9AT|_Twnn?2(^bd9_p_zA{dH|!JVSinU`qR| zo%IRunvhJ%3GY03m&FrF7}L;Zc@6-N;Xu}!-7@+QJdJ*bcrum2`rjRZ4-!`AfxGzy z@a0GSahkj>T3sHg7GedKZ=3((OS40#3$M%oB1CEu*Ul)MI`t6Dy1RuHxj5Qm!|MQ9 z>g<&evef_MlowN}qSo69BS?Wa0rH=KfC3N)yRl_IA(p3Gb86Lo@V$MCCK8{PU@c$g z1#$o7kCtmUN{ZFz5i7Krh79AZ>#6hH08A{tgPJk*+iAakMpPcNL=BhSxcJ?8;k!(Z z{-L9P@EqbJZ5GznbkNHa?1$zZ+X(K|{XsO0A2l4vV@I>;An7eFeLAOQ)};+^2MXdY2@2 z^}K<#^mxDLB7i2qvnTu;G%p}Jy(T(|NWxFA0y;)9cSu)rUup1NWv4X!b9)ut$*dFz z$l1Zg#+((sRrIa_zOg{kB1MF8FR}!(6RmxhZ+iV#E5s3f>6#(oUt`Irp8JX>@#V|w zs26_)r(__j$cLUFcByq8iebr{G;ukJo7yLQ^E)Hv4f8AI=$T!h5!&ztO3-2TO^AqK zor>cR6u*59lf&F&>}No!3bheCe3@?p{KJr-TBetZM(fr=@aubes%r?>#k##KnuuC4 z8VO1jJ!)X{`uc`inZ{CaBk!Nh$gA?0wyZWroX;nrzibYwO3>R%@MmAC=R6_r+SxGc zFth%R$C>EMIv83n0p|nL_XoN`63%7*y8;%D=fOw@d!|&)_y<2{c!s~5lO~>wJV|=C z`PR^_WBVxp>+GMmZfLLQj=*!lA`sd$!;j0`#;8pW@Dy)Yhb%#SCyw3)Cesnxg68z4F{$8rG z$-BfAc@+BQ5l-Yr<1+8mxA@5z$$H2%6bD5;Jmn35-s#tcQpf1l0T4K3d;U^_zT#9j zk(c1*fO~dGb$@--OWa;xloP`p6;l1qwDWAJpCP>=TstFhi3OMl2um~kR2RbC8uf_K zXK)FWC3i62L4hy%K)-|PibJkZa?u`?RR~O0Y#g9qW85}xf260S!DJjqpZ)6ks0FfA za##ZoVTc{9VI?FQ0Ih{sZdw9i<|A#2EJT7cAuXn3z<3N;Wow_6E)xvzc_R^HC`RLt zfY61x?4SuZQ8V;Ap-za;Jz_1@KwVs{lQZ@r{SPcHdy=~0;`m*_7LEHtt=hN2fR)rM zUw{ai?(GPef2+z*b3=^P;qjB2kEo+!85%Fnoq;oQA+BtLbELqxoY8P?_3F8in=yb| zRUQcEvjTg&AvXqCfyZP9`M6iE`BM;|yJTrzSl;FHz!q)v!0mDB={@JGz<6$n%L#;Zthw+PO~NQ+BCIlv&Y7Q;Z_+Dw=;ufG+q zUe4~RzM?O>Y@-DeNq9fO`Fu)hHKpQc_ra?qTDx8z=R(Nh`=VXEV&c8i<)dQyf0N8;l=3%!xbNLnRa?H`MawjBY=w{&Xc$?0XF~E zAy$ma3ETbBtwG{WQ6ZZXo_b8D@iQ(M>bXMCT2;?hu_^~fDXF7>HADGof#-tkpP-&9 zAQhg7KCd-3`z1h0flU zAYw$+cB^d~5qYdNoaQ|ZU5NpG?<~c`>*s z6Z7jtO;ua_u?lEUVVnD>G%A@)LyLRO7hfdIiT%EGi+wr2Fl505bqU%felz=vu5;;m zK(Mk|7W_+#Zu%12?hoeU?zaXh%$?78-tX^PaZ4RBzcctDO$;#NfLZ<)2juBW&Z%i6 zRi9^wq4rf#7rPKozPhYuuBDzb+JX_{`E-~kfji{DEf`<7izXYg@-(2)*7h{ZQ zCPu)GzmSQ}WDhG=d+;|+qyf{nYbuaJDB)t8_1Wn0L;`|KBKdt_$DB=ZJN9l=pGxjz z>km?{`SMn7w|DekJ%Q&qX++K%L;XLTc=3QmV@sP0h1YF=^z@7p$I(fQvuCSPwaN zE1esUf#fK_P&EvBXKyweb32jF)H-kUl@LfrDP&j^Cy-5J;QN|p$e`KQM!?ao;aP)Iml{-kgd@`Vn%Pa61zfgxlN+iJgjOwN9jG`kHr z$~^0l=$xI5%RNo}n5}yMPCM71mATKur~F-KJWaYm`pYDuk<+ubZpz@wY0Gb3M#}X@ zxXI5ekL1LMm0g@D_0|8LmUB@!j6cV}QO9*I*-gD1%m*vj-r^Dw*0~2#N-LtZ_)w)qkbqROodnfqS<2SGaCFhz(qGjeVNt}{UVK?WP!QaJ z_^gcHbJent8Mt#wz1U5CWW`8rzE+A>VZ#hjdAHQdr*KfdF-r+bU+@%ZON6 zVM<@++OtehwwTJh51e`7L~4-``gznDw=CMo7YIc#k}lDwSy9L0L~y>)sd$u5=)Fij zg3DpP848@|qQkunt@LVIwg8MD+>#>*#@KSe?|qw8W5Z-P&D{dRTJeW^48Uhjl;H+B zXD*(WDD*96Z6!E5d|Q%qLnjWMD(bV|R6WNu;&4VB=;(SZm>0RFaZ z@CP8Hm{@aUvz}KAX$)BCdNk?=&TP=lN-(6+TEs#e`gSy493gT}(kg1f}iil3H>>b@_E>L0lEdyxC|#v_PCD6Faz zo~QBgw${dWcsYEjI(x2<&LOMTmGylcR%kM5y8YJa?NpaYRh#ALh$Qp(?SfY=*1iMD z5zt<)G-+`aC6qN^bjXyc z2~oNIboVa+`L#m1pu_kDVsnRF#|*ZoiR_s7rseW!VCvhK~!u2e)^BO~OBL<5yiRv}qg_l<-W5mDi4 zSji|##JwV+j54B(OKBl1nfJcGe);|j=kYkNb6(H+PH#b#iC*5ubPI&O`L|x6k1)g` ziuv#FzyP_Z_J-aj{DD;>?{Wacwq@#|4rMF;Q)F*AWIMIsE!I<3Hwd_MET8VsjS^)u z6QQmFV&!8_to-tcG)y+4GFIZE!b5}pqkLp528RTzqxz}AkTOAvCYm*3HJHA-uh!i$8VJ0s-S`n|k z<-<1j?C0rCM|<8$S@X)#mSUSlO3zuR(AD|DeNI+n1rD+5!>I9AdhO5R+1$1F&U=T4 zAtM2V;V?gvknQ;lF=Pi}(}RD4fL!DLC&t+Hx_#9im_w>%K%| z#A0}%?P~ZXc(1mi6nm9?1loD-j7VY8w&@Ad zwSVzt@jrKXJvW_ZgEoK9yt|zVt{a*2l=K=C3mk8$7Zh_?g+%AYc7Qnk7l9Im!_ed{y$Dj_^cg*#jj921hWkRA zO7mD>0MXk4BSeO|-yFlbitHj$#Sqc~m~{f-BZ3yJ`c;Jfq+2Vq zdmOO}KNHTg?+FO%5gTNB|K%}jC_g17V+e9NMTHw=4X`POHiaw&33n`3i)N@r!1^8{ zPv)0a*L6G3Iuvs^^tGO>oI_Dw1X);l z>LK8~Cbo3%vk)bVE>5lLuHzZh;^~P5ug&Mllf&NqH+$nN1OPC+S zT_G1-=HoZWv)fn>?|z=XPdUBGx}p9!%J`FnJpRFsG0%it392Go%+=fcn=)B)>@z3L z9nI#j>SaT6bD3_`&)AzV?TBYD_1mM@YL%vi0*oe#bQtW^iTPmnPnXS0E`uR8OJ~j_-{DQJ zU=6*-#5{Wsf<)O@7SS#KXl4|$;Lsq168_SC2pxQn)j_j)20L^w6l8#8Vm3n5eI6YA zzZQT93##x)4Wr%NO`Yh>4kSh2&;he%E{aqRguj6c{q+)eousxw$r!PolaWZ!%JR{b zZPJ4YKwEMKu6?z^U$B4PzIV`SK~{9-#`lHXl*T5)_0v|Pv-^2!LKxkdU=XHd8OssQ ze#tUbLzMWa$0~bjnjJZVN4c$d93u-Sx6enq>pjLeUurkzPw1-_FlR%~0@{P4{YD5c zw|QVTc6QQ)`R=oKcE^`r6@ol-14rR7J^)OvM4nvu-Ery|Ame?8(HjK2a{677ze!Qx z6-MYDzDE;;9b(>qoBS|{So-m?tJXYil*`_a@LsNdzr&zy-Z6Zkcn$f?E4L#@t!X27 zMAwEX&_bl=df;gN^)}{jVe^19b&pmmj#l)P&t%_1D;|LCFzR)lvlmkz78Sp>m{bwF zjnwk0ofAS`hAn7k>|*|<7XIm7HJ(}^c_=)RhWlJqpcD;5k(3u#_J-Ur|0~5B3z)%s z?bKU=0&MlO)vjtV@Ky(cM-RLPskq-S^Mmhc)y{U>06lo6fX#~dbbGB+a#E&cwdwne z!ijFR&)lioZd2Uf^-7|^814O&e~Np;R{-3NTVU+}3ze>~oO}877M}7H7K)T$39jGH zjp>7B%lSW-zs?4pl7RaPPtbL%=3{SO`@^7`;OSU0oz5C4L&xl7b(bL9i;ve5|18y6 zzThq$Y7YvM_L3F4g>ykKin2$+hMd=OfNaz*CeLU=PD9Cn3n5#O5!2CYqCU*F`N!^y ziM7>aC)H3GaW!z*l0znz+AUym$bMi;QpyO7Nr8JLSGLggONaChN?l7xE9A3^OYpA8 zN$eC$HWGV%G^(L=qgl1y*o3lWvv#AB2=K*~`Mu6In_O+--hZ>Z{Z~EsA;T}Ym1*V$ z7nXuN^6~?Ey?tyRMA?{jX!ab0vkoc+O>uW(kgk99xpl-a0Gb}aARQ*S?1TlomK_$i zB}0dU?UlIqu5No}8lXg&+qH-LV{v^q-bjESWlMPXG-yYD(6U@1Ch9&z*lJRncC#Aw7QD7(8 zP0yD{%Y*CE!2bc{PlLxgdZ;d zyI$4j%Yxr>TyLP0QwqG)hL~aw^-b1kjOQt^c8(>Q3gz8VW;yXw=e8?U@(4vsAks&r zRpyx7W_gfVa$vT!!KTb$nXk4+50ng?^MPJ>^R6-xL)j2S(i*mqWbY5eNgQ60-nc_g z>N2@*{JDbg{hTgr{K86ZUAb{jvUX4%HG-|DFHVw=w*OC8C<`^MkqU8BM~mx`suNSQ z?-z?qi+DNtRsLVj$y(jt;!0H-vStB{kpDx1vz?6;Q!g#&69?@+nh~mVs4MK_jZ^Qs z7;ha9=kAq!uzk)9KEjUbymUZQ>O>1I6G(_MK29|>)uNWyOw4ej>VVb*z7dB5UN8p= zUL{+CFq4JZ>)Vkte50l$HC4^}Fx;P3C=$c??`IDxA&RTjTVOtVi=zlo4Od7Ft_&5G zpxWv_DjT#UC%T)Wb2jHEQ!w^BJMLZ^P7#J{L>N*N2ax}$SK-RZ>hSOo5^|3aZ{zna~Sm9MDCv|5J>GbGsIUS2!vonvz zrT-K5RD9s_7h*rSCQFsT#_o~_=yqdnzPykw3v_S?Qm@G%hj}dO?lBMVdiYWEkW9{0 zd9pQX>RCQhIyPTJq7lJkbZRDZ%x^M}_zb1_f;Tw!+RD*GPytk1iDx~TA0-{;Rt)pK zGJXN+9z(+Q;FirA*PQ_Gu^zP-A)7(s1Zp(UlUyIAfod_@N1y>yCWAG@STA~K?jDM3 z+$Ii=A%P2cO;N=jfR<@eAAJlzzqfm1CEp3E-{5SP*L)W&rk`D&P2lPOP|QdwIS>Ek z=eT`3KD5&7za6F>jmx=lLNbVjT2;lFLRhFriT+4Qiu4|PTt( zxji7f5c4xKP#x{D73?RnqBI0Uh#(whCE9ukXNx*QDP6L|5)?-e>9XwAk5Q_PE!^d^ ztG{%)>|3L{Zfu)GZ(#Yjf$PTD0`K2cw+}4k?TE%aKKlHP6d}Yqex?oGD_yr|xUnU% zF>i-T{lD-+Dp1HS`tr+T82G;ltOnJk6_*na4b)44ZJa|7|3jK!k((gBh4;WHZ8ttn z>Q?e^%&?;8hty$s?z74K2bC9Dn<+hD2Tt&3p6tiZc?d3_^ClBEsk0qSuPlnmvrZGBdiqwEi1snGYoR;K-O<~@B z2e-d)wBUtUIP~FJFzp_jo&}x&)nFm)1jt;pk^Irri`M%?Eh2k&AihRj@|ChcQbP9z zW?|pE;CwA)Y?J~(1SCcPBuPou;t$$*LOf^@sC}4t^fQ~=@pnwbDNLdCsju=17=5`G zr3#!l=cLk7u*a))Dj#n|LP5UzAa{~p&;9gb6BfV7K7ajU6_NTdJ-@9C+wz;m+ospS zetVdoc3$8Pjn_Wir?@t5J9zvk3l4mG#D$r0!dJ&%KmPIzp(uh+(m1)cCG$7QKIP0# z#y@Bs3D#{@G6JQ)rukZoOtmNfS?B=kkmg@zgC5z7nS`6pD?X)07vSg5zkP9uOS;$! z6pFYC=9K3m32N&J?=6t`6R#Hz3#=D96{Egof@L0uMtA>5Dv5tEM2URl+4^J#QiIkK zSR9h!G`)0`jUgWnGcnENhVf8t_6=jLzt-lMRlGps0?IFA6q#G}7ZoAhs_j&ZIIW|w zKrJysRdL4$Q9lddn1+tc0*QMYgEVldFfEr`&kRH zZ<5I@+77K$1y**zO;Fx;w1L-C;a09q?E8WIXd^}rQ*~d_q6({(=irV-84SDqmG#Wl z?*VspaPDhH4Ccf(Q=qN`E;Qg>*mv6$S0F=FScubk}?F8gfb$eYDdvuPcig?D`tGg zniTY&J^uF0GP>qzST{C@?Snz-J}W+2AR{>b)@^FvRtsT2PTd2*$@HD z{~^DlbNIx-95;?D<%MqDE?U{OrjLG_r-Okp!1O&btf!U8LkkHwg`0MM#YwM7iqVf` z%C3Ogh)Ub9I;9q%i?3}jG;%wCCx}!BnNX!hZ%yt_$cHcLlif_N_7GE<9~i&$t93zp ze?_M+c)US2br&nb><~50 z=#K<3GRSFBO>A{|xUkazIW$q^I1C>ZVLd#uE-srbyI1!jA96A5108*l$nX1e*Mf$0g!x%~Zw zX4i@4%W(F6x~$;zD{l0HL$B=dJGm^-m7l)-pAVRrJvx_yWtpRvkLRo}QJwc~AySAx zOqA`bY}L=|V5HK5*9ogGrq3bVV~=NAoG^mWCCQ$Prv1=d=v}&=so15XYWZPi#yd9r ziHc@+ao6|s5XEE8s&JLQn`|a+NBKBABN&Oz;)AyLQ03&<7pS^3?$km7mbN*pDb9OM z{rF!sfg4=$U2SckWxv_?G71*WQEDp%lX#0_+rE^WvRi8od43yMGe0>=Qkpz=Sg|Jv zeq>1eraZs0{$cJw^?#RyuPm&q7f*Nyl`*zGoWt1xOxUXy*_4UnsRSgAZONIn?Y2?=RA&%$6j zN>zx`!_m9mAFBKoGB(z^&Fq#EPMx6tfr+rbn+C(A;`Y#w4Oe4w<=@LTGWdh`zw@7r z$+^%)w+?C&KL`J@#%SBjr4WEihonF&<(U8u9kmUl%)2_*+ZNmSsu&?6f8y zf=yI@=yRmh@PhL#n|_fx8L*wxzceNazc0ASA?3>5bBB%(^7Tm4W#&J8Mg`Ol9rv(d z5F)nVAY{sUew*zTN>U{2)b)2KzgupE&m_Z+db#=%l@oKJXU6ixXG(jW78)W$LNjBR z9R5V$yu!DT^c%NH?zyz1tt7F4RK9WAMU#p3)#`(;l?q5*e@IeUcRXlwGf!4$yoEg^)@YIy+GrAZ_dYJTjdqdF zYC|KQ?25BPBB8&>C9by>!duz{1D_mkYMfqFyj!Xhj0V4z)FH%QU2H$VC9eg19Nx?` zjo(pw)7o-)o%j&3<~X!Kwdu|(N+UR4sm|AU_Xv7i0%&{rbc1&zMljiZ+y6Dhk(gMRF?z5MjF5eUJ2A z@l~*_TsC77wu@rzZZf`_BF3pYjJ+eOsI&1#iI%aRUd$CyhEyHJlFKR5=HLub#h2&) z0wt@-#P2&<3%X@baRXEZw&fPbyE@!M?L3&3`}fOx=pzE2o_&M`ZZdvwSLR%vu>ZN; zhI4e!6IWY+#J{m!8QPa+A)k_bw{gOG@KO`V2R_AZM@#vxg)UUQLn8H@+jo=!{HHeC zB@_8S^>!=_7TMk$xlw)(=!ynyKcc<-;nv1Djq`~D^tsJT5k&^?QmR+V*BfW+6`&UV zUORWeQgfpgC|3+0^i&UcrHpZ3avZ&Y)9G~&mDnA>0-w@3lri$sF`KdBDd_X3{+N~z zkL^#CPzg|wiH8~5>f1+Be?$HPQZPvQo4p?ivv16pK{_<|JlrKwEdPgrld&qQb5v7N zVW4AN#_SSWJmQA*ujcd6rHDZ)5sX&zMsZ2%oCF@rnsmSk>>Y7ie>;lSd%*>E|813H zO*DW|3Dzk>40p!&JN>Re#3mB|HyZ41G`X$)EAXcQs%QAd3Nv?%=vVpR;d_zq*klrN z3?<9H4iIbmKxMdZH+A(cl{(khv|;!)_BQl0@A;ka&N%+|yS3Iq{*CEd4D}2Y?^D(s zwB}O}dRE&aVpW9X54V<;SWMYdcNrOpM{j#dC{#|TcGQKT8Cv@e52zRu`7St%(A7~{ zN#t|mH={UvP3l4wb;n|$82Q#zQQ^AkZkS!4@`sr&s=Ee1vB2s@#UAcshG^5c_me7x ziznqHd#bPfyN$cXMd|142bQip%sptBl&YUob-y%nNpR=MhPuH} zBlG$O)CQeQL82dpy|RmJ)czK zAb2OLm`wk+5u@8=+iP#vn*j=nFQGi2VC~F3e)eI{_U?u2I-A6nEEBLBy@IdduNhj@ zn7)v}#Zfn`^-DT=&p%A#%_U4AF#vse7|(jU9(W%}Y@tX)2ihvu)(*)8g-)sB_{v!r zXR_z@8fjlp_IHmMa{0%P82a1%FfyJ7)w!&abtmf+fb>9MxO8!NpE#>yDb=Q9+P))} zYIdFJG4gN^W9TBJ(_nfCu|t3K^~`qdi_Mq0o$ua5NHqloA4Cf;eBBfBZ$sPoH0D^8 z)7b}oBi37HN*<%US1sxjHR_^i?@cT@uOF;E4(A8@rW2rjU9vY?32&XTk$om#cdWa- zR_6##aQfusVb%ANmIEN0_h+g?6nm|?hSWCa_BA1B?g$?U!Wrx1k!Mm;5*>F)ZthKv z{9+feCH~)TYoM#P+u^PJn$2b8{H?zPi*N7}W#|U6aR)XHgab{kovIc5r?{E(`CGu! zE*&1KDX+;Y5E7z2hbL3dFW5&)c}4_|f=YWrgn2CM3kFcl!0@)j*C4zCo_>#9$F7_+ z_ME~zJcArDdi->3?7uArP`qm8CCF~)vCkt{yZ_C`Jf%OGVjtNLjk-x5GzQETLh)d8 zel6Ro>|5!eOyP>y^KtVvxk0d09?J&0r=J2qkpGuYc#V9}A~}P?w0q4Dl+35y)V8vO zkdnjvyUUplX3?86p)+P{(-*p79|&>Jp$J&>k9#XaBEQhXPk|J%7z@6mBL|GWH9#xp zwPg?yCEa>8#I||I2gNCcE#PW8sQ4&z{Gv?dF)?ryS``4#!Kf4os;2uOd^lJVa2vR^ZoIx0=0zOvg^Y8W=sNCoLUX0 zhR-is{)@H!B1~~&j9{f+RyKn^3`Hi?@s&XEt%gmF(_MK=`X$57y=c8womc4OwbV9o ziV~$AbIHEv0{WC6{r(;^WE^_?AYybH9!iUgeyA-(-NkJuaGl)?u5R(_HeKQn_KtOU z#Zn}^zHWGD@f=MdJQ<4%r&?_~lWcApZA>L8u>>1T>Ki~Z?iR!vMcv0gg~5tRHfPmy z1H2p+G%lbvxVC-BPaw5+`*}J)x*G;zJ8=BJVcCbmgBM9GBK7M)Z@|Ln@fy0JfObL% zgOGsSGcV7@Q>rZslR>ok8eJM!o0fGT!%a7C3BCvmQ4zWp!-Z*w|eD{P(Sn z=}UnW1kW=(h2{rM{A=3H^Sev3+L-WiS409sHENZR#|=pAY2ios$KxVA^I8WI7LCNj z8t3S(5B6Jpv9xFu69J(XY3qjT-||1S8rLz%6^mCLniD?ElKJ5WqNKjh9_QoDmWubX zQjgehx4i=@QGG2*zwxd|66+5$^;Dr78}|E-n=9!6V{CA8iO`q#y`Ni1xp z>Ry2IrgD2nFTqJ1Lo!P@1jTSsH3H)WSpGd056a5yiazU@PCk8N_Z!2CICI8?@u&vE z8h{V?UFFpZkwREsv6LQ=wb3QxBh`Q4823Xt5CQUkpurc}XLfHrdw9ILl}McyM%wlp z*4;f-y@OnJ-PzI)Dn4Ii?A2+HLu)TGIr%G5-KL_OqY1WPr88BC`!Yx zEX|d~zvYbvzm2X*`N)|WS9|zyR~?cPbyb8@6iBpQh54FUjXkG<$CWvA!!(@mPiaEK zn+(f>AA<`%+#}q!k7ZapyF-qsoeXjS(^uy}0UZ0P;^&$}!M~Jd-*&sC?)t@hs5no# zw>@l8yY7b}FA(E&2ihU`T!|Bi9uLD5NNZ5Nv3~{NN>6Eo269fvyzKa%xv8o0s*t<| z4HjDpP(x%koN8k>*5246%N=}9pP62;lR51arn|_qh8|Y9?}C5pB8A}{EYU<+&zF7) zv|%YB;uW6vqiU?qb*CwUZs>XFG(USXC8^R%%$nSzr)KVYQvWBPIeBO7cY>yI`|>Xu z?%UekIdUv7-CNy-MK?~y&GGsIFVta!{vGea$4EWa4rO^i#Crx(VsYpsa_a`w2_37BZ6IUU#7Zw<+ynHgt_%rA%Jj}QpnUsR(v1d?m%2(v?kZzvzZ*jSK-RM@p zUQuFNt8W+I{bdhtSI+HD^hn`#>h7+5G&J?i1TH{!XECrtp%l3NqsR-bB*oc;4@nGi>fRSzO=LR=;`FuygZ4~wb4SF$|R)U(C|2(_V@ReRM5_oIF{=l43 z=oaL{-L33Hmpk1&_(SBUE7lJyGk^PaKd=ySz z7_Tl%oEZOZN!@hdq#cZ%V3Jp3JSwdMSAX!w5fV%yLaqrTt^FGM44r%OcQ8|;;#kB0 z4Rza+bF1ajWZ5lNNYB6E#EiA?zl|S)jvw~Qy*K*vBketGem%=&r4z0pk;>Pv#=aA- zcAdbgY3J6x;iJZ`+lvk>_DBD^l>9(0Ja75PniK@s`{+r)T9O?B%wRh;C8-tOXIy3^ z=er6YyWrQ@)^XE^a_^IBd4gZhHR)6>7=FU?Si2=84bs_&M(_(>lCq_N8)G7_=}JzN zOWf*1O|TTNpCaJR0VGE2r=O4ocf7>J|;zCVaC-*LB}3A8(OD-&^j( zwS>if{XLf~PY4Sgi{FVkU?5l{+kN6+AU1!zWPH)QiLA!8)IuEON)LkZ@Kxp5GbraR^)#q^`)wdJY{^^!x??naw}j#`}=&&{Oxb0q|d9`fWM}aHf?lDMW73>^-!V2tI;NsciWz zuU8#*t*G)d*l5MV$B%!C)Zqhq-%OTQO_auk-nT)tY;vdS`299?2|gnxSyfNj!w&%F zhK+@(Gg^`3@`-bkm-d1qsEct&Qt?6L6IXS0v0`_%zX(Q3c$)J`86;mG<+C#Y@t_#( zzQd9xomx!4yY4w_avb_ul5QWBVg4tVtJCxir_^}ivBbuob7`VKPa#9xw_+f=^k5n8 z(tQ5ZhWi*FKTYx2*Ny?L$&gM(E(4Q{z!p3C546I6*Wf;0=YoAlfmBuZSanz@%TILg zET)(>WrmFIG~;M&!dJKt67f+``Qq?x>%gWr9+-zwip0Accin|I-wt0v2casr28p0b zJl>z2vmsf)ozz~hm|qX9*}7%A@cH5j?F$9x`IzwQIS)?v=7LVbU;2aB%)4t9vC2eN zVMPq(kI5n&zWFGxjPzSYvD7d5LRXGy|CxHiU2CWN zl}0Vv=LzW^ZA%@%QMt`6%p@L60$^XvYjlF_*n9Ls8}>6N^3PbVt4?F@+o}Y8I&8vz zh+WeTj#nKmsjc*G`F~n*qNJ>D9jE;u zc*o)~VQjE%q>olb0VlOcQ_W}Kt zprLN*Ztt?_7cHMoG1r2&-iUA&Uj20Y=19Iv`Qp$bACcwZ+sSZ-_a1VwuiIt#Uxd5k z<%a`03Zmi2VNeqDrZ!rq^Xjn!97$Vl9H$a?D)jqJj(0MSiNxuPfl;E^xduF`QqPg%@4X zqlmYOfiiB=2tP3Z)=d!P-~As%0*wAysde;x|3q=oYS8)nvSQa3v#1t{sl^$N5`ban l<5_i2G&9banYsf8V83UP4*tby(M?_u*z7-OQEuiL|3BTs8$bX6 diff --git a/crates/wallets/src/wallet_browser/app/assets/index.html b/crates/wallets/src/wallet_browser/app/assets/index.html deleted file mode 100644 index 052f6448dadde..0000000000000 --- a/crates/wallets/src/wallet_browser/app/assets/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Foundry - - - - -

- - diff --git a/crates/wallets/src/wallet_browser/app/assets/logo.png b/crates/wallets/src/wallet_browser/app/assets/logo.png deleted file mode 100644 index 1f29796cdda68977f0dae3817c35cba6dc30b116..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49040 zcmW)nb6BKp8^*J(-HgpP-fY{pU7KAqakK3ka{djQ#Hc36Yia z8Mp}Pt|l!8Q9Dg^1pEMFEvhIA0nreT{Avmd0ntk(Da}@?i6%?iXK)X$Df^kOaZB43RW=MG1UtKHM9Pc$`1@ zObZ98r_ zYUtCva$?uV`%BYNhNf}$bss$G2z7|3v<_He@86O1~1RN0*B(uihbQw!Nc=i zVslFimF<#DQc0})Uyp15o2iE(ntcE3eWtdxq^`%E82{HZ6pqQuKE|fW>t74%%Inq# znbz+rA3??=IJqAaN8j_`&AS76M~K`npJ?ETzs??Zq}}QrW!uZfr{Zeq6y_VL2&v2C zE8hH)CY`$6Pd8R#DqV0M#<$LK>V3P<_PE{~yP4r$b!htQ@w=id%Z~Xdx8wQ#jDLV< z(?9vIpSu#r6sP+!k^1dB1RZ6H8k(d#@cIXKxP~m>{U(mqWP1bOgyvhG6?oRaCVzFl zkTVV7H~ASn<54iVF)!FdGj#W>X-0@&HNV$u&2=Qe6?kEVG^`grxyzgPx0fI)@MCP= zMa(B7?``{Bp|O^d5n26Os&&=BZAI8>$!(j04{IDtK_4%hU>e0N&Q9v9*BRgvg_x{+ z&+~B=vj5xN67)fi`*fffX8_mO_oUEchW%g4(N+0@tMHOVy)V~6)8DmwlEqDrZVU`b zirx%pndxLxD1w5DdufUXZy#{p>vr`wb~8LzY-vtDjC&t8{EUxDHZ!dAd^QZ-#;Ed% zd0n;$f&bv;MV_VmHin_LeJA@lh>lXL5mkTguB5d7M-NV}PKJK(`&}J;&-FNiyFB?6 zg9hh7S+>)EhxMid2Z@)%5CS z{yV9@kbeq%4I_QsawM;PusG-;Z*otK?ypUS-$l1qAv~$Si)*H9R)qW%^SPB);rDknizXS}srN-~V?`#IMH=nm zj3lgxl;nz3>?h)P7Kvo+{1j|2klLs$o0h*(O0vRaPULIOIX^88M*g`l4d>@+-6NlQ zkl$iJVDVCL4`@(m(PJao?L%hfdTpyOEMMO&ippqK>~hdK=w-DISi9GA6zlxTb1BJh zy)(eA@hx>;^ze&N-YB>ORn>(}t)L@vFx{B7hHQJybay?$GQ-@QP$i*)$DSB^ExLWl zgCuL=r4-H56~X@NDJ*FxfLbu4&;7Gh?C@9-qoK3H63L?F~*nhvib3z zKHn5E^VpN(at;&`^2N>g#}gsd$foZ2*>1e>L23Rpze*kSn;=4FVCmBLyDpZ<1yUOK z=f!_@w)n5?z9c@Aj#c~X6K(B%8?niZ%cW!jE&A-hci)La>Aug&vrGj%?Wfnh-Oeks z2w6>LELuP@(_j2)7UP_^DZ0me5^RyV>1ohSw=B!Vk0`<%M~L9fC!+pE5yO~-{gjvd zgd>#R#!R$9x!2}N^6qS9Rx_t`x*B$<*#GE^lR3Fou;sn~QNY$BuOwi24(wi|6@u3f zpWEcKo*2OVl4nmXdARH!_6UJ=$59lQq)F_4zEw4?KVC+J->>YSkJEoOxizNsA5+@W zRUgP5iwGZNm>;yC)OO-w&NL1pF;v$lj9!VFkHi^X=Xz~q#`NClaf`+^hJ4EldVdN# zqW+4ff`x=62`dIA8qx@&44vB%#8B796G&l?3Y&uhU$rT0nuv{4q9LJ3SO)dJKc9#e zhV+l);>^2EFg=_bxKG{vjt{&oww74vErc~MQaM*=C#FFP=}xhi{VpeO%4NLr+sN7G zwa(MV38D8GT zr`aj_EegYMvht>j+G_B}%0bWb_$@0eWJVV6np(R1(&)mkmsrJ8v3e0EN|}CCEr?cx zRzwH6lc|?Tq5Y>{6yem-VHcAeAVUhkhR6I{-E_3d-p1rM|&J|>zS8^sSb z7E?w~5vk0(r0TkaQ9$L18{t69lBtPjAt;Ueo3fR)l=1fJfI7($%A4A|tbkyaL(KoLtoDT7v zxkkIpbVI+REi}JgPaHm#!+tVgC1afYX=Z?fT}86NpL{RS`}#rNanb#-v4*WIfGOig zEl|Q!_9?jwt8}bmd;8mEggv2vw~NZC?vis#aPy@=v@lB97nzfPO}k-gYy`Fh2Eh!!0=ljM!;l~`%uo@F=RM#V!*c{OLTJQI zG0`<+bVk9GRcqr6bveluxGNCnHBBHL7Mz=Z&x&?u?~PsxCw-t&3g+FD47gwXaZ>v- zo2KvNGY@>8JnzCEPc!O3`Q-O3oP6)Vo2%3XTGfAaBARhrF-WR4e!H zy>O4OVwCfCYSwUv?5d)8|Ne9UluAPnA}ttXOlWF?wUmT7)V0n{O@=}fz@YsVDrG+< z%_$+XAQ0i*lOymQ(zIxhU)-Dd%bBVQtvxTxougAf2vJg;%A!&k^Z8Y-On@&o*#& z=b8ZY1?b@+LeD$0N?&5U8a+TT@bxi%ju}vL^2baA5$G3R>eg<3b1zy~omTgGdY`6x z%Mr+6IWnO<&m>Q;;VsoTO|Z$!B%p(A&qKQZkC~1G7buGn-ztpax16Qvk)V-NBQcFw<2%8rQiMT z45+N$kJpB8bGoIZ=#o@<9fNB+j{ET0UjllLOWiN$ol9wm?O3Up%|%N2 z78z15DwnFj9xukLQ=!{JzJO|}5v`O&KYAw@H1HrDOMpA8$v5aOI5=k)sY8=isb*`@ z`Pc&mn>~`Io#_{{^iN`k;^XHV!$;d51A5E~jLg#ILZS*g5e3z*eb}LXp*-4B;sCUk zxE&wx<+vO#u&oS%;||h~9ynZa#6h)bO0GxnO%Uh=Pd?s{pBIE*{>(tpgcTiMhS5`J z2IPOH2qmxZ6}jbJ0>A8JSf=TQJ|Xg*2dUR?F})lLf8Z;Ky#yrf?9_cAmr|%Gc5jvX?{XpV_bt56cnxU{_st}zvK%rovq$>Ti zkdHjxY@iwQCz>Y5rQhR|flHsn`n>uz&cH61F6u;#O;!+c|dyrRt<|LZYv zuB||r5iEwPd&Ko&3-hghMO5PdIP>Sz_s^>EFbk?LEew73Q#)LDhha7PVM499MMV4C zMD_+oG~9@%-$uX0gCOLC)~yg{LauA1{t}*l022t>jEG(wZ*f7_rQJAaq6QGp9mXHe zOQxb%(G3ebiFf$X@%IP*1|!elS%efVJ+W4tRfh;-&t*IU!U>l4tx)qgf$LG^2o(J5 zV4ytcHB>h=u^_Bt*dC!(pdZ&PK#ZQb+v}ThTY~=JL;JaedH(FZd&V0282>L$4Wuv^( zqy9JTWAYMI)`@?Eib?;R(x7&s2!brDXl5h|x=(mBs?~p1#7J{(vQ%s)uV`3#VM9@# z)bYtZ_3d2eG(R?b)|$>85$U>ooPHdYiRyWuC-^*N2$UADA|v?@EsE(g9nlgns*j-x zUS1BM8y{wv$8|_p;U3}T$>f>^i8qfYr&PD^M30e25G6&<%Qg4Y+~IXBLqjriaPn;h zLnn!b!IdU;cHlq}&KX zv;U?vmI}=$LU^fZll;a_etn|A&}*G*VcvP{VQzJRV8tzw|EatJMD(U`CPgVWM9cSN(!Mk!ntp1=wlYo|oRP~y>njjoAj0|h z&aC4QN6lq{kst-jB*MzbSC~%dR{@XB{b=MJM_uj`LmVTH4oN^Gl zEcQo0J9?M$lA)L3<-(QR2BDRnTA_7J6vEZFF@!qFN@agRmcih{W>&$>$tfJQWY+nN zEnenm#IxWg(GrjIBza~!gujf7Lic<~FtyX8Zlk`HxkWbBb6}bqhBNiR_#0?Rgc(U+ zTxu3a1=3E1kOZ$$ZU^tYQjzrJPgn5#7hUez(`;ufK~EEH65}*i=6iZ`CnXmbfe)Rq zL)veW*p^+7Fs&)*u+wiU03%KzIhHu4rMKQ&{ds|`XO2C4uNOA;M_yYn3Rz0Do;%=pxjr#qc zVvmGT2DxGR;GN)o8S(kdv9p*2r~vX`W}kL$=FSZtu#&34?8chH1{CvdxVc3G!xSAx z)ZK`t9>Me0xgNh6FS>4~mBB)RFOwWI4$K-Y8E_ieRRtIcxK;GH*fNBF9NX62b_Ag+ z^yDkHC@ovy78Bk>B8~vHJMNchR}*PscG#C66LTOwfLzpC_s}%N(t{9hqe3XPr0I; z5B2&yl&_)4!4m{i?ZqhOUN?F7hQeu-+HB!KI4U7osQ)(b-rrmTnB^i0p?mKaF3b@W zGGoH)v34&~;*=UKEQ3||EG|PWZ`0o6ZoD$+8cH=UM(f>xcoz8(R#XHkog)3|6@;yX z;5hs3uP6WB3i@C17@0B8TW6@L4QkNQgUBaq8GlTjM*-!A?lsR4Lpj zvPM6oR#GskDnf%g$25XxC!pvgS#?VNqEWeQQyeRHx1iQnJg65-#Mek>RJ|eQr|KDQ zf5n53Sy_Rjn<@(ZFD_IQT?WYAo^&HWK@WjH_DSKr+JqvN-Zd2F2r3rpl7 zr%oE_uAr{(kN)d2;=C@OmMq7@RJS(!+72uED^3nR%hctEbbY6ewP8Z%9PC##)#IO@ zH`SW|iKsiv^;(-0p|IMPgIs_$2-^gQh2Qs8=J<2<(FOic%v`^HYAH@TcXaem`J}uU zpUq=BEwk|3bjIxs6^fKjOf@tkm6G3qS7x1Xu1wFc^9gdOw9*^mu7#KObRXvCQ8yyj zyBOquKdEP2h4Zur6e5_bFXhTTu*c@TMLfSlIb<=Y--!pzh&f-k<=e})rT&Dq@z7H% zn-vB0z@>XaW(UGEb}#f3otLj0fTqGTN~NOP3-{W3-yDk|wHGLD2Sbjo(tBU21`#X~ z4{CXnEgmnoqqN@3);>jawNL>pLJ@&4BP4*K*qB;<$6|NNiRD!QzXHERHm#VvK3>TWSw>+N zCATe`#pFCjs~p2#x2YDf;m?Mg(~aO6JrO$FPxm931wR_vmZn0f)%!))Ia^%ze!N}y zFZdN-U>**`5jrI38I7DL?gCN|ISw(8%GPWJqa2|rH-meUSH-Ry?4Dd2?1M5#K?II` z^BmKM(gS@@1+%J*I@8hpQsxF)PWpcH)nov&t%vE+EZ@iDi10zu*ku*5*V@&+!8Q_C z=^FwAXW}tU?pHMmp{fBnBj;aa@+B~0nfUtM=1JZ75o`P8UyD*OT$Nrpp95{6X9w9v zh_J=@qY+I>uYX$=6-aFq8zL|G0Ml#4WVmk{T=WPLZJdmM;_2xIcKr71$G!jojq}mP zJ}sgj+#b@QWW1_@AgWk+93)B1w_K^JTPuo5^q%9&s_+tj77a}uk3~&)cp?`Or^8$? zw7vi%0@b|FaY!UE8$*x9B~b`{FiJ>qpcgtkZpc1|vpJcslAmGALoY+jmbL>c$hH%0 zn^tDDb@oZd_DfJt=&mxtW>!2`9o>UocS)h^Xx2-K8Wt{YlJA%vpd@+Xv? zZC~kQd)u)D>w8THwH0}VF|WJ}LgJGM7_D~}AytVAehmHJp7EOGJ;Oz>ZRAiosUEB9Q zZV^fr*Di*1I6GDB?6*u+Cq3gy z-|Xd1VOi&4o?q+i;L3H+%lWFttT+9G4;gI6*Q)A9ITfvvKyTS`g{E@Pe1VtT4?(%m zv54;&^ONVry8Yc4$Wk@b3WDuP&Dauo7w``X1X;Rh zM(u#T5Y=%QtmDGXic^IZGN1?KNSdZy)hd&o@<$NwG<^Mi!) z6+VzH-Nau#PNoby1tUtQ@yXNz{$ZfrL#`!*n=c;I8ct$&rm%x+WEQ3`XKm;zLTPUB zSuEH&EkmjK3yx0K2QC~+f7sYMNKFI<)ab4|RmIw&O0Boz8&lXEWiA=(8W7c3`Y4C? z$dCEYUYpTt15z}to3TUbW*$us38N`|ugph>Wp?D`UaZ-j#)l(fQ_Yg@`ApIO@!0~d zEy+Ustwx8O(4v3ctd!LEZz12%$j!#k^J$F%4>x*easRlup2zGrZR3DDXQ^0%JN25b zsuehh;|zhuB6VF%*}4ku*71ZyRD$uI`ffh|Xvq?eOwlh1^BG(vx)_RRg^v9eZEvrS z#71mdc1ezP0J1~l`k75`kd5F}RBhfgQ+MniE+%TqDQ2phO>Ucv4Te1&G5rHEg{L60 zb1Os4E-OAmrp?P;w7dQGS9_LoH~GTl<5n;-XaJSxbc?ALrlMi?^*X&5<}j zuPNH7L5`Hs(bLe&n;lSd*|Io;n+wxZe>6+d^*NVPwT*lt6*8kb`ZH24c@`;` z^qsr+*4$?lIZfDi4d^kkLZ#z~GG(5?lsg7;)o8AUlnUym*XTiy;*q0xwuZM|S)egA z{!m(;8}Ou@f1URMm!{fp0W)N8GQr2vsaVnb^~^80uMrs~ShV2# zS0}E;Yn19}0*nrh83B&Hy1o~SfEl|Y^WzW%2M#6us9nFGM4|Cy&NGjhR+8QBy&2fm z_XUiUvfJvBtNo%PX3XW)MrKQWFJN&>=3O(f>`*eBmy$78`&&{3!x*_y*U4kT=j`odL_rp3z-foL%0HG{$glSATq)QAd-g>g{=01dI36UCu z7{zj95v_@JifNycbfNjjcCqP#LtmnJdSQpuZ{rnBVg&7(OV=g7Q2v9z-oti`!H+yR zB(L-N?-G0jol>G2UDaXez|7oh#14Q4%jAoDg;Vai8k0f-FOnLg&KQw3bz1wl@`hEi zr8Yq7i%6(heJZh3i}gaEMttaSTtr(k@5?I{unfHWvWW^Z5m0&Ucq2uNSj_ovH@rCs zt_U#*9i)emB2zA{XOM+?4&cuq5#2kiXYg6W7I=$r8`3-k*^GzCu63R}7SG=OTr$Qu za;bRp_4Bm^pDwb-dci@DrcOBwIU6^^B1FqTNWUL7t~c@SMM$#?QgGgthMOFoYUrJOuMHn!dNHk?MNv~zCD8<|-sC%dgBLBrcff^2lVF;Q#3_6vQN3Ry-I%Br zgnSZ0icyL5MVtwzLwZus5ns7Cq8$YY8Xx0lEx9tx&?0V2NjF|gCl#HH8>6U^U6|5f z{L$a`fuamlJs=EoWJ?sLVCl5hNWu7hde-R}Y-k#6WDIkjXno{TI}3v8s{0&6yr1&_ ztV8~eGz}(LL1=3*e!1)r%Du~f`9D!$@_1d%RLc!&6ADm#egJ46E{P`{>SRLD-xgy$ z$z}(9P2S*r6S}uCuf$(7z^xxkxid6Hd94#Z{#{pWXz@!NF9k2>v1R3)5HA}myzK)g z`B;YDg{`I5vFo6fAT=L#7LKYFY33H|c4uIY#vC`$U;z%DY5-^%OHD&2?l_EL0{vFR zp|am`+S!5brwt=U)z*dzIWG444fIs`rnTJpy8v`}DwY|GB9M7(LMF(;YVCblzYuf$ zt~ary7g1cj{DH1)Qj`Do?E6g`YdH_mzT>K*drav$1*jfxY}yt5?AMEeANAR)H} zOvIX2_E@HwbKpoQIy9ijaX zS|&M&?OWMSfQcL!Uk80W1VwrGSN8;o%bk52A@OqvF{s@&aFCUzV})KE=0L>zFk1Pr zL`8hS;~?tg!Ny#YS1$Er8DOG3mufZaC-?JKVbno&FyGK8Brhh+$x4`s{mt_`@6ezDk8(wN^$RYVwXdCIicLpDV2N}j>gRG`<#233f< z-2)6C9*;zI`1r6GZwJBU>5uoj9dsNLu;>8$K{LA4QRC{TspAnNn{fS)6>c==k}!f2 z_kd+C-#hF`ywX|1afKSp+k!X2x95bV zBTrolS+SKB#HPEMUD*i^Zo|JYy(%6{32Vz5AIZ5zVb6lyfuFtSLLe6PP>vG+@N zOfNTB(ZtZQK{(c}z|{VQ)K&~PWEXl6NLtCQ6{LduWI%>vyi%Ms>awFT3Qy^qyV&@P z&n_yaf?^>g($!8CGXh6G2Sa`tawc9s|F4ZW&)v7k!*^a520qn8p|~kz#CbC0q6+&z ziV+w{^w#z2aaARmpBiB#kU1Iek($l+!`tD-@D&LCC)R8S`D&DOVx~uJ>PdY)m%YTrn3NT9|)BGORy}Z&I zSM-aCoqGE;s%1^r+SJ&%u40 z-YD zM9xqw@5+5kMS_lxv`9%s*!+q9O+~Sburmip|H(rwI@5x;xeVC<4MfRv=Yu1JXZ;xpsWf#g&HRB`+4?i1A)-oaWLdB z&)6{d6QDQu(5~NDu!_@9BrL&SqXdfni35U60rCC9WrMQtI-He=G0{H;T4wTO+!dbn z!Du>kqB?lp8z_>NN$mu(3q|xa`-8$fCA>=T(d|#8m%fT5DP&bnzXb~iMNCVN>(a3j zpvs&W$)vfDf1XxrBih;%t|Uq-$K`I_UgBexQVToo@Hk0kaXGzuh#Y(~{`*Z2JS3ZU zrBYPZ4dvW}*_@%o0MSr}v+65W4DEq_do!FUfDj_`p_E zP|(YwfaOgF=T8MiU!vUYbMX?wP4?YsXE4fVzDlp5wW#;s1hD4Rd4|W!;CyTqOVdfS z0<{sXHn<^iQ9TsrI`OvD=p!d^uIj(1sP?0i`12m&KH#wA?E^!@TZ_uwaJwXm`X2Nep?x#X26%bBzs4 zVgs~aM7fE`lfknDgIJU6U`qe>s^&4oc(J06Obg7mxL>Wg=ASwl$lOxe3yXabNAo0M04k0j zImB^iC796Wc+j>?)&_hrFpp%%?D|Xc)7d{4(}8)^@aO4!LBNa%(xt9wo8FWN3nwo& zY+QKM_WJykmVs5HiwQx|R}p#JwK!RY!Bs6o6bqHa)!$vmhkcl$mWbYY)i)VMV!uKo z$8l3|H;jztsfkCUS+NDobeE>F)W4PeRnY=aqorhXsI&3-N)CWtVD%fh{Jwc>#F|vz zFuHV>ra1HMO!vKpXJc5R34(i|1 zlt(|w2K5%4efjnfpWl_J^Cm^bDq6sFJ8PtS7WxGD9Q;BnoncZSO07@ecF;2tM@-0D z4NSjW+7*v45_}=mI**Y}*O;gKw;TZ>J9{?wB#}k}?4>Ljq`ZbI26qC&{h^5YVEjp| znk%bbHG_G1*Qu`nHRxt6wPIruR5Q(Nwyk#}pYtQ_P)m19jw;{f_6`T|0q=}6jy_@) zs{Iy^f=mijUrf8Z?qc`TFWoBI^7yFm#(KA|p}hGvbW?E@A$53Z*=+3o^qB6W+%f=tcNyd=Q}_$7f?5cjnr3!Cp6Tw~`o}87 zL|RmDQmw-gG2Uxlkq7L!%=lK;G~0%FAe_TYm|kD5=_jmf-k+EMK2RwB()8$qEFoeJ zN4^O=RZMiYGY(dKgFv}ktEyV+Y%SiTAmZKqL#1jV*{)F zR}IHDW3p*##&#rN9V+j&>ez-#{rLhrrXR$HU6X0Hw99uH7XpEwLH6AYdVXcZEK;Pj znf!o2b*BI~=-uri%s!MpqkYJma>XMIl^dZ8o=qLa1*epc^ECC)#I?Y)%ZFrsC$Ad4 zYP3%wM`#UuDfm2oAR_SIogDQ( zo~7O(4!#$+#Y8_$OdB3V(E|lmN*-$J!t&U=+O9AQC~E5bi&Er#k^1&nN_Ic{$T5pH z9>(4+(-2~bG(?b*K}=0uM(oLBiX8aD^k2?R{}@KW5%^+T?>h${JmT!&s&^D0ND2u$ zEa~T9q$#(h<2Z)A1If;rsHpzeudnNmJ`Q0h*zZ!dSaz!P_!&1L=Z_?3r-FXJr4?}E z)CfZ{B%(*?sdojh8SWWr?N)!s!q(5(xX3GqrVm_UWJ|=RH6Q-*-i?JmgzpD}P3%oM zuwnEawEq0)=9>xZq9c=#G3!l^hmA1F*wV26cxR?!0>6QPX=Ag(K`iHYQv0%gyXHOV zJs@PkH31~CeA)}BOLdU3_+{Ow3;AlWtQm#qQrXFXZes4X6#^e_mzT0raEgLhLwS8I zhmUHg1y$)TbgI-1sZtk=v+zx;X<9vxs92?bkjtrw`R(Gi>pf!=^nFSJa}cl3O~gV6yCx zg_0Rcx-yyK!92`(d>0Z9WT>9CcEZiT42cZ;pMJ>Vk@mc%cqa4^ABd2Te`W&l#N_P| z6{;n@l~#y3!Uqh_GTN@FaWZ5s|MR@ieTvsj;>994k0oyZ^D!$h5_^8A!#XflH;63r z7s{NqhV;^D7o)%pri;O>ulmaLf^c1AIfl>EThZN#ia8CPgE#^SA6JN2X7lLFO^|QV z7MHcrFa0`uqb$_*gmC-ENF3KL5ZwCx4mhwjyk(1`*`1J*F>z&&J(d3KS6ULtcim$N z7pGZX&Y{zCQ`EN4Lzh-$^4FzdJKmSbBb)#J$V?d|zEnpH+VZdQtYsetjlT)@!yNFr zSh4nz+xVctF(5&r#q&~8-4=NQ7GEAt`5Z;l3b9Xh87KC3L+5*O%t$oLGTE$;nE1-BUrBH7Gc1W z!n34n{EiZS1(6*OdY{m7c%!4ARbt~hqN6w-IrL#CIEV1K#WqKVQE#B(>e*+SYR-26UYO5hx zrWP%1JP;XktjD{w6_Ep-h>)}X4UbUvbEff`V2U3rFPZ6|`-(gX?^DfFX0?l#;Xa6p zZ_wqc5}jUHE?aLF5hOTNF{X5;99RPWlev#MEpb6zhrEQ1+>%$J5xIW%ovyhYf_IRS-+`RsmOoT%cUON zOTJCcr-%g~9}5jpJ5YsDfvONLeyNE{&}R?XTX8Du*XOR$ROON7ZB)!J+kKJ!nR?V- zW*>MT1MJArck>CHU2Ux>Wut~iIwE;{GzBGtVc;UmNafG9T1n;aSc@E(DR>6TD<$=Q;D`Q`@N`%Tq6^%7E;B+8VOo5m zgbPZ3>qUKM*(b4lHeHq-g3y(R%mS{* zXy9Q(7W`g0Mz%`ZrMvER`&vEY%bGu5Xb9ionzneOXRw;~`?EJ~0yx((&wRDn;8UtU z!^HZJn1u08yX99WBBImJ{y^Xf9>IXQ#2VSXRR#XXu⪙0PES-FemGH=wF0v+kS@F z&ukPKsd@Sni;%4&y%C)Wjmn&4Nofnrx0}G8POy{l8YkOA;+ii`T-`h;M^2`xB^}AZ z;Pu+%j=}$5>CnvLO}&UF`IJDA{xQjr>UXEfO7{Q{fG47T;<*o4FZ@qd5JSsIwLvmn ztBPuGam8|PG*MWohoNFzytyz_R%{4cELub#AF(_`ABcfyodbzZ1tw~3W!V*Vb{WT? z>++}ZnMS&_35$srTYdco1FOUIQ{B(&7RQ!7E=K3^{Q;D{ttH%j@^aokf!?UT*6pTWIXCJiiAUS56(<6&hAm zwG>*bACoTk?}tTvFs%P^Bv5#DYl1%C4m^q+RvTY#Uw4{4MFA)ZxiOAf1YNz!NOJR( zBzP0Ww`LhK%gd@_eMO!U-8UKnww{8D2j!aqv6_ml~7jc<5c37u$*~ zyM)u1pDhS|&<0av;tl&9E9m^gs!yup>g~R3;x%H&;1DB7u;%;?c4~9U-6QD-eKo*` z%`WejjCyTZEh4h}F=teF$-MVn7uLlZ5&*1lJ-7 z+awEf#q=6p?v{^Yz$CpB1WmB@A8oChC!Le5G`Z>{kkCPv?Z)#ru^s5LLztUHCI`UA8HZEZ>$V$J9w)VDEN#&+m$~l#<&+Q` z+n4n!n5#rNKoN-<|)jskIl2~)H zomq@(hfy1&8gYLXnT4E|In<8FKo+g_sV@vFYN#}ZSm!d=37rr&Xovs=-~Q7~Mjt1j zl4v)VZQ6O|fd|FicqAA&{}J%KsHJ~~OUD%bw$GDZp>T<3dZAWL=7Gg$syK8qcSgN=HQgO;ii5X=9h$K>3Q5?)ygtl zp^j?BG8ZcP+Q4phH8TtqZB4A~=VxU8E`9`l8XW3oT969Js&nUpOa47Xxdu3=hYPXi zv=1k6XXErNKzj?6abl)9h`xj{x%_zB{Met^<<2g?A~tO)K`;;AyYJ+nvDBz-)-x#4 zpFK*rS$;#Avdiq@w9N zrZGb0JymH8_-rtL-RCfuybynHNnxU2o=B~a!_0J6OSj)kL>Cm9aBU^_T8d*Vd}V6v z+;ugqEChrKxt69XwM_T(<<4Jq7gdFEKZ)R~GR(zhx1%P5L3))yI*su+B*r2}>_77< z#OyCh!wZ!ZEC~{&_;+0>H#%kpp>$m3V#O`a%g3v3-opRz`~s;VebOoE)VG_!)s>&r zac;vhUlf#vjp+(WMU)wZ(m0hoU?=-Ht=0H;Vd9dRHdtX*wWJz(#=q#)vfj%kg^!2K z(BCsQBj3VA{c_REYfHt-_pE98eatX2aN;(a>a<6p*3>^I&cwjd=M3|bwm5ZQzY{E# zVd5|kD@GULFueNn-9I*xw^p(pIF&>BXNQlIt~7WaB;z{`$tK_Fa;}CAdjm@~wD)uH zlztKCN7bW{xNTy$NOr$u^C6a*E0hxjM%GpH_F@ej#p4Q3B#fO;((@pP2WObws*?n{?w`r zy4KH0Hu9)Wxu&3)DA602Yb66yEN%2iQLy$=inIOE z;m=pE(Vh0&IeGEN&^vxk5rgdRCQ4i7O*(0jw{NPI6$So^oZnw;99SxZmu8=&j-GsG zD@khY(Bz2)YTC=d3%2?bRXpv2GMormX^Rn2xmscW$zLkf+fq>p{fK@PjqJ?4=1$=Z zsVZM&G}n~2#Vj7S2-ap(PeBZ8G>gD@1@-xCBhP!>AFr-cISHrW9{gt|yhbGAsFeVI~YHjWZ8^$glJNa1T#IPbvN@_X7v5aS*=qP9-D{@H*%t3LP`(kqG$-Ee$s_@5URm|c`PibtHflD&?PiKc zTk}i4n#x|!@JvfT5`(~(yc6pV`ci`Adc}u%X`aSbgN$sMzP-2HpLQ6K2V`}rNDRbz za0+r-oGNf6KdhvFq<^XAc3>KQBk(br=peXSPt%~DI)!)pqzpt5MOwXD8e%a*VQ@o| z;lFBD?kQwpI7iBkZXj(NoZm*{UR@ropcW^_8e z1~pzs7IVOYfjn3{p@yUjZHbTI2-8MCaLAWkM_SfzIQ&*BJ7tG5Gk13h$ZE=_;yhK! zn4ST}#BmH|FK_QuI^gC=HnJMoTR;H)w=yYUJ330)wQuN~T7fEJDukOJLNL`Q9(DgB zM%nzY2-IxEam^{3@(w7h+z5ujZ4`?Pu55uoenvpdcB^di_RDbmlY~-%tRS9&2#P6Y zFtS*xOXYsg3|pR6re)gG!M_tcjsOeaFxPp_=|I{8daRxTMjTV8Rv=KHv9?Z4j?vyO zWL8|v-r29ESYcDYsY`;j86rYDmKD(5z3loC|5Y-Jx^-aCT$AmdDCZnDN#Vg*0YKg0XnnfY1-v()P*GaSx z6jUQGn-y6JOGW%;(X_D6!*KGN1JLy6_y@7i_I6b1!--2L)^FPg=(vXqRF{bfRYZTo zAl~l5v&X*%f`+D(QxZ?}smK&2#d>!ife&d;dGAHxN1bcD@cU!CL8cmWKSfHRB5KbQ z(0to6RTPb=23Rm1qzU3!`qUJ@iO@L5pn)^K%zh)#$na9|{BKEnVRF0x-VIAkw3#<3 z`9Aa-;?6ZrD)~mX`G{_2$fUmDPg>(~fEfbPEqY_ovkX;s-fwzZ3OwfnXe9>EL(}xU ze`m&1-dhRmWXEe&?uF#SVKWM}JaLO=$=y@=?Ja%iA1{FIhh-z=*#w$c27A$f7uF+LGR zq7ulh3bIY+nrg`vz9X{0uuLG9NEp5%u&^lB#FM-kT5OVzt<@se%4RP+gEibvq;ASs zAIvS*s36a4bO2S3B#kQ)`Y^tq)}Ue`3HBGc)`!HRrAt90mr9Y}>4Lz0WM#2UOIidM zlYLk2-0l2#?o%z;vi{&?z2oB85;xThfuRnw`M*ipb2|pbVPgcz$!g^N3j`ME6@DgE znyAJ(AMXSlzn`k9knxD9;sj{01CcEhh#AYMNFkUyBQM@N2u4lg5xVzW&l@;}* z+|8)o*wq8H;v3sIf7;sz(vb9LjnfpY&y?V=A=KMAy&j+R(TQCLG098IGA{Y_)z1JQ zF{&$){5JoK{Rzc;t6^#jJEfR{X$&Qo+Ok&8*EBUcrh8(~%Kepphv@pX_JC9P>M~EZ zx2>`OhE~?)KWZ(p9tTh@zUN8pLwjSx_~SkqSKQgna`)4}=ok!bKeyq&1J&>=S&M>! z8iljrb%gLMxx?L1n)Re`oVU>m!_t9Y$tA)Vm6*Ay*FEEQ-aj}i@#?hPI3t{lb8^Pg z`+Ds~n3|T*$XYad-%;xNDGfDCJHG(G%d7yuuG?Iq&o4u^-Ggpj)XbSq)<70{7MBFB zSs(#xPr_1q+`ptSy@c5DkM;7&E8GkM#_vaV6(WMoKyx*8bTkIk^68aQBH3{=E`9(1 zQFPXiaQ|@}*KykEJatY@ZkRmX-As2(jbZY1cXu)X+}oZReg0pe$au&UG(svoGrxxFmFc_AJO}O87vub>)2r=^A6ze4HaS zVjPr*pyK~d3yem_42(tgpuCRWLh>W-Nq$`%94|^Jl4j6r(hi*NWJ}5v0C$pfcv4VC zNo|Tiu(a`?R|89aB+E=$DFeeC_betRWS|wkX1IAwP2+g9E_dK1=jK@?eE zupXv>D*j}K55+0yQQ^duM7i&Hgj1=y2ID-*p!h>P&}B6L?dwp#VB@AnnolQB8|MyL zo}7ys{7BgQte5jNsC%3V&z=Q%+C(8MQks<)Hd*0W1%Cl#5YF!Lr_{gxs^Ig`BnW4jf&y10B7BI$$-{xV&Tq2Dp3zUV*VLDhir4B^akO^|FCmJ;ANq73V z8S7|TOE!+pI4tfseCpMOebEDhj4m0La*X#JYqL(;$JthQ5^XEqxj0%3kh!d{%mL!);@5Fh~T}3TJR3d z4Sf=}5_`rU%abKLO^_9NMLy|yL3cTiS;e?L{HPrLPva1udx7aD)kJAb?Pp5z8D6#~ z^{U$do(RsnNi|jKPxM?bHD>=lTSFwGus+!2D(P^n?*T#*Dr`Ndv9XR(l4p|Jm|`;F zgW18g_M&fTTEbDJ**5dh#`v)onM)QiG92J6`xgkDnXof=@J+0&=)=xBk9u{Y%${Nz zD>MS)H=>4tbh4_YHwn~0D!bfxzWHkY%3^3U1Vbz8WoO0WN=>)^Ab+v!r`+K{Y2KN?Kk38P;5*vHkjo)IA3E8|)15|Ct#^itMzM|6xr{ zR`Mk8R=T`l`cBMj{CiAcZq;;^X-N^j$5(lIa zFHucFOt(3N@<1J5y7O44fAiA+W6*w0WS8&jI*-zESQ1N|-AKFtN4|K8bS5 zaR>lw8{X$WF!4s}GF$>-MsqVA#M}x=)HqYM98jHOXSwb%Cq67teo!!HxOf}5qXN{k znPHknV+=FDR^V`uozrJ=IKk)%ZASwzV2ldomx-B?8lmb8+(9xmD(fjEp74R392eQV zVeRr+g!qgzB=_yR`#IR8rA4*ftQE;xJO8s1D2~Gp;fBy(iJ zO@fO9ON}Bl&|jgbP*|9Cic>iNOy9;IT9RfscyR7PbFKj~N&`$wYbH_k2CIc7{Zvg>^&A5Um1^_fE(|xSg%mPKmNClz&7Qmjj^n< z(KXeoidvKux3^KZnkkeaQbVaH%!66h$yN-OrpPFBC<#A?v{fpkI~p{A4N6ZWxlnjE zodeylAK)_4+>OL#c{{#(q;uQQ0jFMu9sv!qTtDJ1JlC$$SLRD-TR2Efl|}c2fkz}? zx=$FjVziNyd&ddqW)5kcf6Tdp9+$+ZP^e|11jGG~#HZ_NwmO8!HGE7W(=*iiOyv!W zTZHZMGIW%Z-GbgU_&<_R%Rp57_JhB9BUUqnwfdvYExRI(qv}B(4dj~$MGf5z#+3*e zL!fi9PL}gtkvBXMSp(hTb)0NX362M19hc}<(01{@+S9l3!7U6NM-TM&pvWTas)Hg?0zg**{1s+uYrBP zb~s795p|=9q!0Aj>~5OcNSP}^2gP9yVSFO8j<5cIlX_mtxCLbR6LWlL7aytB_j50b zFlf`6K`mAwaQ{HEignd5$`FpO0D2!1uZ) zvQYi~*kFJ&F%3X*Xz-6V&{ChLm{YOIFr^GF#Dky+n1J}!ahCQteC*eb*b6o39nj`9Vhgu6C#^&4rKP#`|p=bwF$M+I9awXIA^ zv=eyMO=xM#H)Ot=j8YLS{EV~eBoq%m!YpZukNme7UC88M`HIU$`Fs9aDt&4alIP)mN2Z@(<8nx;hfg2((k!GQ{)_qJ# zROU13jsur}vV4Vl4QQH%7~Y0u4Q64oeo&&>cxn=j`aUGBX<{)tbgEn|&Q@U)4g#R$ zM6E`^UxhcE*GZ}k*~E!xPYg&M5VK5&e3=fAs_5sIoEp$xyrpI!JOFVBVaASEGxLx( z=7$KAD}KJ=BzytEGMXMtYq>O3(3DH6&P|%l@6IR+!#9umlTlDu%>}sX2rTkK7onmP zIGtk(T|)_LA~-ZVX+`Q}hQjc-ZZ7M>GlocgL%f)Dt~4@-aV-~7{r$O7bFebOS(H`v z>2}!AjrqTiX$dg%5qyBMQbi%*NdNaksR#Fv${=q%`42~CYqy(6ScI@v&FbEYXg!j=E=g*-;Cds73UqYZ>P zf;B7=t_H&4FXrOfi<_j#ayA@`@l0`XpVyQ}C(i%7X|2vf^jnj0b%xX`99uOCWeRyH zp5UgX@dv3!2L14=pcV136wT6mDdR_#?A;E(vj8){BbGTmGE!Yn!Gnu!Qmwmmm5SPy#`kvs3sKtq-eXbgBD;he`qbsk9tyU|`ei=)b^j z=nhF|SBTBBMOvXtpi#+NbXtM{zj7!VT?rrFBk~#ewjf|Z+n6Ix=Ln;JD7FHIOvW~Y zfAFHP6li4Eyx4qoQw)xiRlXae=&zoan#TJ%ow!^r3T2Fa|MU0HGZ+y6*fDir0s6a; zR*Qd9U1AqT{|3jqeGr+4_3Aeff}?>NUdwq@=j6moF6-!H3L5=V`8s-|X$V6TNG6ax z7WYg^{*P?(zB+*#& zIR){=eoJJ;ehz>5gCzo~W~Ad!t~Qt-AIBf@`54|!+`cPv6@D1Di+J|p(*1QjGge~r z;m&sYpRCVZCiZRVseZ z){A?}#J+(AGt1*AROI zK#NX~%-qz=J2$X1xaA#GlU7BNSYoFosg^q2xh6y7RHgCL-ny^?F^eM|x?G7MY9mRI zz6vqsqkA@xH=YuTM7s}2<#0FstmK2O?$b}(WM0&gg|-j5&}Hyr?1rz}`do`HzRyc?xx{tIw4;^f}l%vz}DpzqTY&q8)=onTx za4NZ(fH*F3rVJFBRUf&jzBb#Ra`6L_CPrN0nLFMA^{o&>e7><*hSi$*b zW)k%vy6N=i2>6X8#nn0HJPnFXI`v*2*nvYZw)gq*jJ{;Aoz5O-Ue}?cd`;vDpb%`= zyE3@B*<4Ujs{!InIj?XNZz&Fsg9G!?BQKmAb>R($Hu22t&m$$Wd4%oZ2y2gnB(g)b zHd(Qn&*-a;KPk6ldW?5 z8Qna$D*TgI(k;0`h3Tk2O6C)$2zx#_>Mt#|nQBevtUAC(+Y$Si@GO!f@r$VC&^Kve^{Q7N_!F}DN2i@+n6G^rs_jRL&BNUUFXjU(!y7fPDM;ue0syM zfn-FOdPw>>BpGRw{>Oj%PK>y??Ik%FBdzg1;80xl`G`T|T*o}7e=^zz67u!$Bb&rV z!(|xkzmnP5*s>%K<6=w=XQ+p{rZtOc>yrdcgV@LvM7<%t^;w*Hlj{RcN|Y8Fg!cR{ znO52Y=NVZLnH1nb1mDHMl~O7XJxp0MI4g7k@Go&mO-sOj_OwO>$7=eG@GA`OO{1+R zEMC~KQg`eQ6=;)r!1lZ|EU`t|81Vy;%7Fsx-mn#jYND>9Bj1IjyqG#p(!R#U) zBYq#J%lWE#o4%wYYKWGE{Ct-kO=^Q;SH9 zQm6ZQI`%adng;#5mOdw2#^do-Ad6_=DsQ_H>ZYK*f(jN23bljO z-~q-0bbW50Z18lYY3|d-hP>fcvi00w6O|FUqG7+NwXY{FFcel3Wb5#w{T?gJn8JA~ z#-C6mjWdjd zfqAex?bp1NFV+Uu#O^*R zEdNNPP$%3hsuifO!d5EqMm4;5IaCpK+>p{lV@NdFMoBM+U8 zqIsSQDsTq*CpstNTBJEMNf(F=ceemhzqM3jDHe8k)U+W%-&>1Lq68^+O*@5@WcCO? z8)vt~7Jem+LHLW|q66w7aEQ&Hn^a?lTA}*QHvfcNjmGh5vFdlLBC;6tRt2%@mmmtn zuW~UOCs5O({mpj_!jJ822F3Y^w9J3xLr~8cO2{An;z|4Lnl7ek**zy^ zG*A&8c~K7k6#d5)gFRhtOWZNK>$BXP3e9p#AMz~j-1++(C1$!L5a$f~zh0PB5aq#) zp{Y#l$gluoQp(%k5Zx7WkPC0A%iCr4Pt1(Uu@h`q8(7sf0SR>Axs= z=e3JV(TYqr*P?td(TDeJ{)jijxxLFzxxCAkX!y}BpTK9uwMg806iDMZgJ?of37!pT z#2atXK@Bx(cb6$tsvBL_vQ&&JS3B*ENFYWy%O1~{w=UvrX|U{Q@ZQkpknRe1EyZ^=nvsUYGi7Isg(dd? zzY=e9jfSyfaWSVI|A8DGyhAd4Tg=f}NJug;{@@~PiGNBQlYB+DrV=@py1u1lQFj@P zMq9!>N-qxuW{|3o%1JpmyX^o8XXMl-7?17rdN_K7Z-JgA9P`SzCOFf<XBn@de7llwMenVW$NVt`S&)ZMz6Ku@W z?OpuOg$o-&jY6Ju`z&W(Z(O5OB9sR|J0?P%f0T%puAAxDK3~l+Ktt_|u2Sz`M|Nj@ z^qqm?M#!Nl>v61VZy&$7x%PU&KNjtwQhjG<@yFbgk@uVldFSGZDUoGxbiN8RXjdmu zZ6t0VWR}x__)hQu8bUY~6dfaqY9&SI11vr9ReJBl|G{@$RU}DBT{Oo4NUcP8>PZfr z`9?;@7pqyByHrE?mA2Vq5TZ2jZRFSQs}P@3L5lH4k7jQ)-X(-{?_*n$2{P_gx$avG z|0>#j(VIo9M_55}-`2i!i>GlX%odZctmcpD|0w;lhl)j4tNRM((r zVqvf?ilrrNnUiOh+1n`it)7YdVA2M5$XR=qZ6fCB4)T3QD2GF=kMu;}MWJF>q3 zgtn-mY^j1g@wI0jGXVbnL$9@d24X##)bhvx#^NKn3+h8B4yyA6PiZoUw&{yY7rwa*Pn>Z%-2G7 zBaHSe%p$F$>OJM}B|Oofi0U-O!eqVeRE2T8PYOUVXVtLMTWRM<$x)9-VZ7{3bnt9$ zlvi4GeYiFFdm<<1k;VmCBvLV7fw8KH#4U~^lOyds*|7c~Aoc+Em5HXG7;X&s+>R3M z3N9vRTyJ%oszP`9-hDh@jXtoS>GX-czTr?3EcNvx^FU5W(iIm2Y~U*A zc1;WxG+9UJjAkCl=_z+2weOQQ=3foVz=4=OJJ=^$+pOYY>`H_vr0SLW zbx~{v+Ir{FZeHg)VPxhgIq-dYlxgX1W)~!K`PP^_@ohp%70C&SB<8w@j1LJ&2{Xct z_ch`ttlzC}5TZ%9pWReE{KtveuX%9(0^+HwGd{>ec_|FVw)BEue84N>$nlN#bJ~)Sp zwjP97rB}#~d%vH8qpZ!`R11y!mFMoH#G_bD0SWb-g3;J#|52kS$NYMvvc*w`cYF(o zppRe;4J+!y7&U*hUh@+ASAZP7QrJ{&ML+lgTBboLJ%D66$73*?mvpyE7&5@J1pEiMdt_J#?_#r*u&~WvYrD4Bk|T)veLPz20n>rfi_t5(`!4(|Y@6eEUTL zae6}TY)#I;-ujfgXxdyT=qR@uSvX8P^;1~6hY_H@gZwPBe8TuHC~0&&g~djbq5f9mfqJ3ma*9K&IM^hxB) zg+HtcdCf2gM|)LU57Xp2LzGaEp_ZK}M4FBnIsG3gCdefE;R1qR+C4rov0rMgib@E( z`uyPOP;@Fb`@Glm^u_>JBMpQVjjSBN^}zDp^HCxu6dhMbcFC4F{@=&#JJyF(dyhNz zB+~c)1egyB$;%@6WDJvk5^sbO#MuWsXl9+9$HjFip(-6Y6*@o*3HR!proM!KOz3{F(+l7se+;>& z|2wEOOM2>X6Igm^SibVf(40OMNhbKH4>h-4A@n>5j46s|l-J`cM8a`)5mWNpkymGu zgqp@yjp!<>#y~B7M&jaQ4b&9ZGKn7m>)xhvzzUa?C!~btl;!wv=lAFb5ugo8)oOob zS2Y||LZ2Uw0A>Y?&j_4!VC`UuJ0m1gfM3VhBVAOoVn=HO8cq0Hesjc(wi|GkP z9JPwGFCJ0)J{4J2b4tYb2)Zmo{vmz661`*D^~0BLgbCxVdkKGNL&Yz}$KRna!vsD^ zX6a@z1jikbQjJ_#buBV-5GBN<>Y%ulVloGNBzBgxaN6RA4{j9ks3?gaNM;v5`Gquf2W5iK%>;Ib}H+?sOo03x6g9hvHn7{ z8?xMZG7UKG`Nx0Q;4Xq$AgreMdCxMMx*gbj2bLBBBywgI=pXH(N&lgK$;Q`sL8{$c z!n|4tRh>_>=q^4kcTa91c0O-Spw8j&)ENl zRsJTRJ*uh$2BHeXry?=8+eeEB7R;32CX$y7K^Guw(p(Up8FK%0eSyaN$cDRy%?7F+ zo?ffWjHeTHn^FEvG^C@!G4?NF-!K?(WX>LGJ?5r1(g)MrYU4EDfS5RedG99h$N>A` znCL*M?E8BMe^Y-obz~im`L?%PVL&M}3qNCAGq$boWUA(RMvr-?Og1Y&ZxOMS66U{o zRJ{Qw(rPBBdMEbp6?F7gY)NOhBV(b(#e31q7`~M41P?Ik9WfHhed3Bpw#EXu)g3!Y z*21||5iUqjfr16dROvBzfwKjAj$ODe3dIMUeE)yl&fABk^qBuHGwl7?Yw3m^=BP8o z+EfZnJSJ%{S%*$y-*RpPjgdkJy?v_7OYp*qI9!^Vr%iEHj;J_qGfUJ+O4C5hD@lnd zSemmjCA0%>jAv;HWv^Uhz{xUH8~WJv*r~E`H2>?z^#bY?yUUwMcLTjI$@J+1=oXh! zhkSP(FgT0UHwk>I>7YnEYf!3!$_IX6+~}$k!AaF`gjS=&PjI^yAtJJ#F5+=o)wd_)xiqAT^*IVsqwr12ga~yBeU{-@_o^AQd6E`cDDBmlyfKS^*kh4UCS}_d zL9vSu@j_}vIsAdsV?|%z#c^C8mSKm+Efly9NJ(bffpZqhvm~Y%V^i5vsn8Q1#&(dmz=}Dj$Xlh(YT{RLy8P$NCBOOht*`z%9&?3!}2i} zer`(XunGeQ+GgUgSA~Zhk3nSlZow|17TB5Yn0C?09c#&$JYP=`-bT`pD*yDoE$R@^GB<02w)+CbF?>xd= zU$(#BIE>E5ne~%a5dvar$~r&lBcz2VF~g)%okm)n{AfQC&||x=0t&!a9fQ_>>I^YX z2-#e2Tm9y4tshgpi%M=W=_f#@whFF`#n@GuDrYnN(~;yG!dW5a&2Nj5)0x$EGM1Hi z>mvjBBAi*Dk}if^EMP!A!pMoT?2s%G2pg8ahQD*$P(4;mauYwoP-Lc&VOs5FXMx;H zb(mn>Jo^i&$fLjPkhOcf1bCZGR@@zcO%ac+kHJ@_@2Y6t2B=)X(DN3&08+@KkrEy{ zaR9Q&Rt@X!&a6Cuev+&BRWT%4EvndaBmgkTpA?@q&20IeNW$ANI~sc+{rwpG@254@ zV~e_v88gphSw;!gVwL;$FWoXJ#;Q*6RMRe53(C)b@S=z>bcMcfa9%e#Og2zao`v$ zC?}lONttcl#|}h%4>kWXd$1?eGI4YeDz6G}Rr^6sU8qC#f*Q+t#s&OVlbR8q;v;F^ zWYNvrP*oOu`<3i+1}AzEjt(7ZF4dYyros$29IG1_avxj!fR^fAnePqoPfu(-R>Y}u z8ACBTc@GnBTs|v@3)Kqrmc6hB*gSZ-p_UMRQud2gMH7Op_gatwF-S0<2$X`cJpR6L zTKDD}lr!Uh>K&K0$6g{t^CSLb<5ek%moy4@(Ik!_p$>}AG7$`!wixHqAf;2ZJW=s8 zQhg)ZUbIk0EUptBT<2z+-u%g*x3X|(G_Bi%!Mm!<(OtMpF}SEp7;_c}6_-2LSIgxf zM`?1PG38@S8;b4rLOexb#}xWrrYk9)Jl?c6~Fi9bZ=lm8C9h~_WyyPrs){r?)_Oh zN~G`u*3G<*;SH9QZo_oc%}}t5EVc$_dUi(qe1~ zr-GY!J$gW{J^_#@6<8K7nCupbiZ29mK33{XKGAo$b%1Fj-VzA^hTVt==Z&bCViXXL zswAQDu*A-O1%B2AI6ZVL#;Z>pM+Gd8tT}y7&%(SY*Eg8VjzZ&U+CnOrUEd~seNiBo zhVki!NERwGI*f#$uYTAOa=wej7;yx7)2M(8S14xxbWL=+( zsKzK6yzq+Wny?r5O6tp}ykp9W&xYfRb&c<2Es6&btj|(%BmJp`^riDr|y^0lLk9z-&N#phP64W-(re45wyjxj|8!lpt zZ$ZE;TiRPyIT(z&cVX(vX+x8!I3a~%C5&JMby=8@1`}C{<_D#awDww?lga-L)6}BM zuj-L=p+0Wtws{RZo9mNryKf391hZ6UhZ(0CxW;VWZYMK^k~7O@lO^FLpOjk`EC%eu zf$F#$HeYSpNJ~}O0#qbR70XHKJA-eqoRD}msGa`T5C&lVvajc2BboRxBofPOpN91A z#uA=pM+D290Q_|rhGwVx|^Za4=Mcnm1q@seXNtt zQgJb(YkEV4a0Up735lzg=OkDq$saO*BdeoCd0n9ph)*1~6(K2$Yh5##pCRKc)$ya^ zoCPjQRA>W0eHC41%Pvub5Xk9ipE6ITDu-iCXoy7eYFDXu0<1JpNG;1!2Di>id3?6L?i|5WaOOtQvc+Q|vm z#uu$J(hVRmZUz(aw?vUq7T&}mR+LYclPJ^3$CehuvuOSO{>+z1gW=dy-$0Pof?M-{ z2HZNe;)SdFtTl$^9{fH@i@p-yPR0L($F7_4rTK{RMWNk2!wQ#B0MH99d) zoI8R`g258Lfh8Z@`zCs_N9c2&_fTiW9c?k1`w4r`ufp`|KAPfbtoUHuI{8krvGnDW ztk-EvEVzShADk5L`#WLcTRg?}Uob?gVplbvBf!1PcE!Ohg3Nl+KD51eRq{dA8$B{7 zs#6OPT#yQesF+WlD0VK3DAG)Nbg%~s^g_@!LU*7n8TuX~Q>U4bae6W~hGv6^b6}z5 zkgsqg9pF0Jr_CkA8lJ4(k;* z4(=-VNGxyY0fjnK)cBh+mJf&-kVjX1HzAW}hSR*XMfLQ{Fa4^$*cKc;59BoL8V+8* zEG+nC1EUe|D9ti7nd5LU{K zpoD#?SIhBwhrv^BrC?vdjB7HAC~ctdKJ1$q&O<%ASA~p`8qfagk{0Q8v8YHY7(lU0 zEq?v#nAJe+kG-|jBal2Yda57J@vnCq%TGRutH)#hhGhg_#?A)T zm)7g_Usap$YLs2)kyOWvqzqd{#jaBgvPBU>VTO&w9Ie`5!&o({ zF{q&?POR*3T6%z>vRl6N?gk>x_rK0P{I`W%!!5&o_8*xKh(3bIv!s{(de>XCNq&Ce z&0R_3-QcF#aK+d-*SM@!88xQhP;0NKPiZ8vm^ds9KpF_mT%?8p62)VHu+&R2+R;v$ zWxPv(YzvFMbd4{Z!whU&N}RUGAi8hrs?2ruU! zc)@Mkj-R=z-#ztX$@V5^xc7dOwkU>gmejDZO84*GYlTI;51AQ#_%WO-~&b(&`+ng3|R-1t$Mep0LfT6 zYaEy9=W+ETS|#5^B+mUR5vZ$g*^{U0)JE`LvEpP3_NdJ)@8m7v!BV~LA=R>rC?+Y2 zIitf;yQR4dwY)1lsMx*Nwth5i<}gbnmd04Ts44xPxEh#8Z?Q$eU$NsaR^1ltMJ7w; z&4~L7r^ilm2g-avWQtx7PM~-7ZX?n6EHhpC*XmlZycHnq-F?b2b;RT_zzx4suR)%C z_0|IY>N?73?%onLWHb>WYC1yjCAczQU(`AmefR;$yro5)6loqThQFHhF$mB&jzsl- zi;4Hdr~6E})q{9pWYv5P`#DY+&A>XmGQH9F><<0cU*LR7N$$$Jm?J@toh)ne6U=c| zJruD`;O^s@1{k;#*5AMPr<{ZxF$Q>W6DQpEdH(}m zN?aGsh@`uQDAS=WDOQ#p^Hd$tS5ecTQ6R7$Zq*bZ`F*)lJLkj24F@Do;4LFyX_HH? zBh!8Za}(*;ckVOda^PE1&HLg%&x(KV2Cg9SPkN2#On7a=OOUoSB4e&HeQwi) z+Su_eK%HR$$k>9tqRq!t6@*64-Tl%{c3Iv zmt8ZO^SLk~rQ_~L(a<9tGo712EL7QN<`JS?YIa)qUQnn0Gzr$JbwB>%NZP|`U^<7~ z;mRinxGtJaZp{k?cgN|05q1-lMrGsZQ+|cVYl^%0|1fwl56xjVLY_Y015Y(?IJSLT ziY(WWpHz7qPHZoOfB#SdOJRs#?fnrIjC}02(K#%Q}f*m#DGIP)K`Hf!QHl% zS&a$8F*V8z%Ew&ioRo1}OHh0fqU65_v9dOY1*AYx79m6Uo?T zuvW8NlMB&IFcL>AnkER6D&&6S{TpI3%p~t?0ec-VRiUcJABk(ztBD{kTN5bL_giC= z)AOgF)h#4a%6ZXimYjRFBqT#2+yK1oi)NB|qdDI@v1%o2lwc}sYjl^{H{D|YAL=p@ z%K)zM>-Z1z9mn;zkB&+HWiOrdOq`-J%)(MxdRlq)Ea^D*LvP4g94K|tBVNr-NYHCJ^Zoa1dL5wr-xc{!3!yc0BW9m|^3fIf09U_+WK{zMNX?2g&| z;ZdXd;AH8@chnBvBEJ6H?_MYa*A`9E7fs&<4{cP-Mf27v&f8iPi2{VvAZr;P@cNd> zfBhJE7$BZsd@MUd>^olCMWMjz*E>C7B~AU;T;aNA7n;8zI5!&uBv)QFa|)+-{Ms~W zmr!>?LdVwTuJ(O1A{0*Ec8!#><-5zF{X#_=UV>t^Z%Akz1T(dRoiUUAE5Rth)Lvbz z5988T387lVl}I6V8b8~h19YGM5Rq6R&Vpz7JyOmMn^_cR=V&Mfo}aPk@3I+bMg>;I zSW?d4wK6{O+uN>O)rmY8kU@{1hIZ(QCVAPW6I)xXY-1?pP*OFsLy35WeJWeBVkAo) zUA1k2U$!2h`y(r*``wvFlQnm@Gei(Mn1%y~cEf^)p((}2xe>@nNJ3?W40*#BrX7aP ztd=nRz!a`A^1HdH;r}v~`gvdUbooiKb*g0M0`_rjDKH3Zs@Z~#Y|cVx`V2Y2d-f}9x= zyx8TlBxRhfz+0-N_}z**!l)BNyYQ=G8dc$_CCt>EB%BelYafPyINfGd$ej-3jb@RX zMqAOc*spC>>n4rhguuS}YaHmfj&o!Mhr$G zoz9A07*a~T3#`gT-v&ANvGNR0|HN&Su_GZXrcS!Rb(_gpc=-G}KQiY6B9-)0j@pGJ zrP5eeC70=tN~`&av1LXI=byUL??FV4ScvBWYN3h%eO`Vj{fkiG(%L6)0yfNkG^Mvu*SZrdbvdf}HG;Dfing8S#Z;RP!9HzsWUE4*=$rZY}A_b;VA?NLGLhI_A z#@Ja(7pS?O{U96@7Cxdwta;|IJJf~`4sJCVT4+V-Ph!|i>bF1a#avstY9*bgXySV^ z%7`h(?mPLtIEmwwz5{RlLZm?}Km7Pqcn6#@FG6uvng}N2tTVNj?u0>7jBtMP8Zs3A zAR_{ESB2(@D%>Ely$-~YnuNJwtTV(}9oz6?S_qbXf$~h*G_*#RhhXbbh`!ON=u0j0 zsh`5~z2Ay|=@%=4;;Sh;#97f;!32xP`4DBBjXhqLbU&&26Pr_2pmP{HiQFBdO=E^1 z`!{}fR3*pY6~*86tZe{YR{vHTL2387IGd|GASzlTkAi)BrSPvVC&y-=GWog;omU*1 z4{yoVHP>?KBx4p`(AsK+0KOqM_In?B>%Jl)U!~tUVyWp=I(tOP)i6(^#VZ|axrxHg z=^_oK!$KOWyvQw}f{}&M%gjXI{gg?1m92*2uyZ)$eEN=F)(}AAai$=6khR|*nXwtx zN}}i0@2HB%GCz87Rp6|Y-;+>a@a^NIE4bh95XF_}{pQ8qjO0OHQfcKlNFItxR8+O( zPbKqAK!Q86yyx&qM^p#C&f3%|w$}l@{>74qb$~_pZl5*-Z)7Ts)b{ORW(&*831CYM zf5H-is^Zi6d9AN(H=DBOFeh!k{&pY1Oa8*dqq-t3>*Si9A9&C@Jr=~8dwBR=f7u)Y zoV7rMG=2VB=285B@i{w`(-f%Fjp*kKp^1RVY4+*+22KR#me-2j zPgzSULia_Pa2Pjzzt}5csA=JMP}`Y&4P-rO46;}6 zz*M3n0llA3S+jL7TQ0S`v!daybE1iZ;3_tbDS2@aa`7&Mk#J63U2W3cGf$|-x0c^* zm3hQkh1-9}_Y`tcm`-EcY_}SZzeKb-y0EGUCy~xRA!y&R1rBYX3VR-hGkdH)Ir9*s zoDhyZ4qXCaR-HXdvGE9Lt8j{wzMkI{+ruB)uKiRTL`D(g*zWjHF$WT+s;(N!gDsSN z%jp#vvWiuG)gHZQlN74)qm4^SQzIc9%Si*sQ5MdrahVtO!H0w!gslxJ;sGqET_fudn^yF%tpO_dW=TyDiWvGua~zA|R1Tc?-Z`Az+XHy_3oD{74{vXD%6@ zCFZ@xS)T5724NxrUicQh*J_}k4r@7ox^mZFWyx(v)<2!K7LmALKxB5)@~I)JolSSK zu{H%o$HXEs_F!j+jg&Ll{CtJzNPRX$X`HZTC@XAhinB9LjdjyrhA|nUZA%NlHLFC^&BQ2lKgb=J{9=-C;)WM=Zu}C|%^}54 zDemu6LfW;tEUN#J>;W#r@v&(m@R3DpNOq+(kLfvnU-{5%7W}`0HnGR1jfHQAy_-I0 zBsG@?fk;NzmhK`rBK5$(sf8r|YJRaQOS}8Iu4-rsDRs<$ZCO3?NLJ5!Lr{B65G`mg zrM`c)>q`NI8A6{G`IJqQu$yC^bEnH%mkny!Yr$Q}?^r0oq%P(ifRG}d+1iLi7gaPP z#!ViF#G}49iKWY1hUY^zCxHTyK3+XpP1> z^}1c8mk{5=_%a63lgg1UF;QgXCsL<_WOlO_{+RPcw7?yr*8^MYLEbZGq}#E=Qn?)d5NypQAZb$zOk5AJ;LMn=p$(3RFuy6MGQ{ zreKEov+9V%_NqnGni#=*B5QZlF(-6Z`ZJ!h~XB0vek#$OEjzO5)8 zfty878s34RA2&sODM9VrJ_KowT48zDg}g%54M^>3dAcUFEvyU&LirJPtPZ(tU^Lj1 z(vX+lWTPc9~7BvSJFhj16MX7^BQAMc4Ktp5x4Q53tkB0e&?yd~}2B^%pB+7=COtYh> zGiQA1ES9aj(HQwGb)*OUvEnF;Ig$I>ngj-F+8G zDUr4SHH6{r@Wk6h5SdP8M&_iKy3N26Qi0W;G@ZYQ_QdwhOB0!@$CvSc1@?OcyS7hH z1xTmWDK~LR7gFHp8QI9^FJVdQk98!99*B}G7A-p3I^{>4H5NoU@O#E*{^n_=>uRz= z8M+x6fM9YqIStZthv76~Ats+G*v12)P-WRRS8g*F3wM9|e=tOJo&52Y;o~@iSID<6 z{Ne{%XOS=B+W)hB1=6cSjN9_I3yy>uSyS{TCuL9r8jX)6J!43+Y(SNFo^!S)Gzts* zL^ZTLxukidGK#ic-dmMEPl{EhT#q zQ+Q-1fW|21L|u@4!~2xR%|qlvuLM8Qxq?Qbmep(LRZtkeD2}vBYVJ^V@hm~EV^f-S zNZ}*f2qvqW%YabQ=-jA9B({lLTU*uGu#o3oG2Y_Bg2)O+tcQ$J`cfOA-`<}W8oG)= zW%~p81EI_A;Zu$XwbU!ZsEL34zo0kqRNO_AyjDYDgXDzPd;_@r{?+IYYQD?$nS-TA z{MC{U<)rO7O$ZeBS)Hy=f9FdN=s4@o2lkqPk0b>lq|u}-v4r({hxT)?H7IbkZ~_!k zwSGyDCfO_Jk>H?((x68*{iUw0$j7wt>P5yxBkxEwaV0H$H1#*0F@v)Z9YkD9hR7-( zRLl7n^5aJZZTrukF_vT2=j9nA>ymp~4ypmkxSAMwRuD#nvxapPWVQSEoijeE?x5F} zt64urw2)NGJS*&xeHHUglyeGGi(LTXiBqBMcwEc`{WY*~;9f!D8Wmo`0m5^FX9}j@ z-dGsSl_U^7mo!NCN3t-gCdYUMq~njjBQq1E>3hZ?UGlbnDkP1DhNl!sZ%-+t-Yxr_ z@z6`^eyhc{p%C-?u(|vSq2R;c57jqtqux!n*yllR-VQSQ+hx!(V>kb^pG1lwxHnrv z(V{&yhU^VL2PKX(*7(a7=*X;dp!1;1ukp^wFxqj5xeBe|$4lGXCuJaYY@FR8v5?+j z*#C0zdo8(ZDtU<0P^-Lt)bej>w!0T4hl>52K>)`KoKH;L$bFD>xGLm;Uc~#4$&Gl0 zTDAFL9r%rV$WyO|x$Cw^YiGZCdbv4m#aaaQdx|9dzVr&n9}@_>ijbb#&G-(f8oaGb zs=e}LohDX43?fHdyu~_#xg&N;dMjd+(2m)?GbxF%j_-&Y_C?pj?5AU192AS;C;iXp zR}wqHipm>C8~UFKIiE)Z_$7NoKy8)k|%pEgn*TRB`-&L27*_V|X& zvhq;+g8rkZlTqins~1aFfV0ssBnk=eWGzw~a5{aoM)*#bw)C zwzZbsvhQr$wq47sRo{%&vfcCP_w;|g>h8L(Q^$F{b%Mrp2{mp_UXWy_FLVKwf^Jw- zy3O3@-B^jw6#aB})p_n&Aw*D(e5=Zza z7_)mgeCwN-Je6BsC%rHSZ6c$sm(%2mqLrQDV!f7nA?};pM z(T8fJBdcsuGWG&f0(a-HZ%&5&bxY+-UOS+Xt<7^lg9IKxH_hJ%Jn0DkfaRBT#+9c?hdTwx zc1rs@;iA8&p-AX|nGvFvAYHkCA9D)}tLTC!%s!hj<1+KklAkV4^T zB$>1Be?P!4=#l>hP*%P@0PQIRFzxvoLctna!PTqn^jA)~L5EHUa;K%?t4~~(*Z{MS zg&0Mn;*KTs{<3M+cHppUByxlV;`_gO-d(9^+yt4olvRX7{|^g!18kn_a=1M_km>cB zzp=lnXMHCeQYdd)GJ;4JdY49ZClp&=nWdSPqw)nTx?%C0=hWh|&G>r`4%TX=@=U)w0OEvR>)Lzr7=$;rcRxJR(ecS%rKvtO z#&W30lynJ-X+SRRz7EWK5%4Ds-f!OyZSR|uJX@c2qM3Z!cs!Zdl~o~l=fXAk0`rTM zGXC7Ykl#cuEQ`PdLL;V`gi{$)Q}nVVJp&0uT;KRk{ZGAWDOwB*E;z(t^X1vDXWeVZ z0_9~_fRNp)T%nFpx;OQ(4e+@d1Zd(WbC}EC16~!HnrqcIl0h6y+^`A(n|-%l2}}Dq zVdrQ<5bbNL_w}}sh61F0)nG)$cvTI#moIt#I$6qY(PB#U-ZYWUmZXdm28C5s8Dbnm zn;bK8c;pXDUg}mNo*3+C%jK37mJbYbBw+4(0%-}6VodCaeilc-9r}m$ciLI`KAkN# z@6GS9{S3RdIhhwcFIbg^-4A&#g~5-=8-hk(6DFCCI!vqrF3`$QvpCGqG1o*!XR!dC zPpr#|$Ae@np-w!M&1Uc>-^<0ISS;xV?{Vj~Z(I+7`SeO!^P!mldfVsL3Ew3zU%xOs zLdTIHX_?+ufSbM1FixE*pnEJ}&h)S0KSOLzpKty(R_73w#!qB3tc@LfqDG;08srXc z@*-f(#z%6s6hdGa(M-j?AR_GXL*#HVnnrph(&$R6F+@BQdGK5?gvIxgG*9-BIZD1RDoXm=$FyUE$vq=dVyZ^8$ z$VKptHgp-MTK0N-JhYrYX2r@5o)4PbX=dp7E$BJ@T1-dD7l1qvs*iFOex2M|%^QsG z8jsaRBb()Pwkg%2*OivLc#}>>F3=90{xp$B%TWLf1bpJ^rEIV|35T~oXC=L;?No0G zmD@^)c5Rkd5GS1Ipu^@y!XVr$_!xIXD?=T|3=V+gXjC8URgGA(Ja!@+Q(SXY0iU)0 zahlwhVHuiiPEn%9BJi>WE#ot!0QRMVxD}60@VL#e5~kf@lPaSddI>pHF|ir-;V$34 z*^`*n6INAe!Ln;3)+&@Wo!C77hZSvqStt!%Scv-M~ z+54B^{rbG8T|2vi=(rXD#?2(1fpO>$nv)C{tc0kUsVS;^*|Ri)kZ5dJ4ZL=8(McFh zh9e4srQA?r#n!Zv*6xLuP>NQTGG-GBpSLh!N9)ilj^l)&N?4FW;%wp}-OR@_?->FbyfDJ}+j!rqiu=;^Xp+6yg` zw9pv}tca!^<}5+6Mg^_`?||&&ciB*`fSN7t@WJEpfxTe?6IOI3@qAtcv>@S*1>$hF zr+r>HN-BF;j0t5pM3UFIAQ+v3fQAXyYyK%=@DT6;CGi7t-GOuY3^Ye`$gAQD%1ccc zf#dYQQ*ZIkvI)Ol_uHIziC2L^J3*2yrSw;A`L{>>fZ3Nv_DWIs@i{3HqKRhrP{Gq8 z5A^(ULt3cJN%Qj=n!m&oc$LOUx}kz^DWr#1uBJA#RT#Wlkbk|>@H z-Op!XvUuvV$BU07U zMJafitPaJES2?Sl2S1y{$Nze@j@(AyXhnY)GYu2tHl; zmo{!)&*2x^g5Di}_Mc87`y`rk;(d>Uao*y(ktu7qtNIzpVVaWn!iNnr-O0T=_^&kOr7_v5vp1LP(k`mNz1u(1 zU_P7Z-#ZD)>lb2l=z2dq_xBI21PQm!i)dcc)739!@EYTqicgw) zcGoit=ckOrYHZ3O7QqOBoXGAlygXCv*np-R2S#e=?_CVi$}Y_nE5ga67f?toI9&a` zcAH1!mSo67X2U8~F_>SaXSd=zJ9_Ij2_zP}%#a{=a<|mBwuA+n*AasgS>sHW=<&8_ zr7PuEvnj1>i6o05+VJG2>7|^Q;~C?2*aO!BmJ2p;c1v`86pBrNK7O@y@}+54qBr#s z9t$BE)E8phQc8`?D+ZkE-*U>Tv`QoX6oCDD8LSuCK^WH?O%31-t(4<=kp5n|mz7Lf zg_^|Wyfah_A|pwhe#`wp;ZSk8`ymp+uOn-qvIpUstil2wx>BAVnwYP<^ev}=Ji5Kz*?7p9)ZZ}h_i(K0W zeDC%yrHjTAE#RIrJ{+#bfr)hH6z76`I<)1BzqH4n*>ONI8Vji?$YWLqcGkHMY&4!q zV;cuLS{g2f{C7r!B4p4jRD_txaEcLt2S4-8b)E5G#jzzBxUMv3Gbs*gKI5$qelB@c z!cEHMc1epyceaAMrC!{wRx~{@0@WPX-yZA4UwabG5SC`a@a-y9JW68lju>1Z3(&-B z#UtytRz)=($NRrWPW6YnYG`|CMCrHAD~OE_#S9u*xDI*At=GN01HDS*4XiGXVLsZs zj<4Pl(8nci3F8GNh(q6h=;7iV5v&ml~0YG5l{RcPkT=AGxa0*wv+&0 zM4BE;!0w6@)=_s|N}f`nls4iQW-aSEu!x+OwSQczYaDK-^btzLLwv{^+3ob{-^%DF z=!xGwpnt4dmx@dCS{H?jDW>SVj3Xl^q23{~qxq*-s?s;hT%|8wAqa5+?lUT)2#9i- z2Rs#|dB_XVs)LlbzZoX^slUo}Sj6!_&UZbmU_GzBDIp-EJ;wK@1z8ge4EUMupfO4LlCw7_kuTEu6>!HG>M`02SLcrC{(G?L#*()Eu_kk%E(ry zB@Hd4uAj9z#_PDgkr6@VCa()sNSr5p?o3%w0vyEsMUW2T_q?tFmm`vG(5x7zis`#e z=fCxK1QAL9w4*?3$PloX|9CEd8Ve2~znX!W&$7(%e7goX3SfDcUx~6AItB=4X%376Y%$_erq^oyjl^7TFN$2K&8W%H1;hLfqz2XxBLm6uTq@~t z{V@a#s+IK$O>CXVM*cLX$59;H1>#o z$qh&JQii%m6MTSaclQQM=6%6yF4d9w*X&P5468}qr6yiI%C)cnlk}QP$kftf0$D`I z5F%;eBpTAtorHFe`8xP-)J8H$a7|Ynsa0hK>X9;xoymP5_T{Ru z$k2XUusu06ZC#N5MgZZbuGVP0cxrb+Fx(hFH(sJ}{Vu=e(sVVV>?|C4M&j1JqSv9k zngwiL-u)7+4G*%a5#W&{=y9ABDCvhfz>@#(wKPLdPG6r(#{Xfp*+()2i2{=l-Cf-; zao{mcD8d{n7Q7bJ3XBpBJ3CR&0Q9@)$4mk13s;rB^|Fn4Fc%PFzwZD2F9;Xv39xJkp-xBOYt$assql(R!Hx`Q?T9}E+bh|^g}!<%s)|)N2g?rT-BR0LKeR=hwz@C@_o7>8mZ%+3KIEh5w;eA+#~;UNCh7ba&! zqV<}kJ!kXkKGEz5>0cLw*{6>cnr7rUc~I|FVZ>fm8tnldmLjCZtLTnrzzCl?8j5CkJ+plj|-(#~@jP z;B$JlJExVlh_KM;j*LC^<1g}fp_Sl7oSjGwwu(t7x2fSc_j`6+wHkq~L{Q5_iFkur zCcyw!1BW%bmI_Ng5#uy%r8tWZNM#fCD7k7?=J^+^PHuW1(LmU2%8CAh71>I<>YpO( z{%_>P#~F{8kOc)h5IPvzP(}asMg3-Pp~pHW8^!o_fEI&1qOjWfX8JB_hz8o9@`ckE z;9k~ujj`WEDlH+M^9t4VZw5f6>-hxl^<80l1$C>A6-a5)$DlJi*J90olEu4t>Zpgp z`guImP$lsEn|Xc8k#?w`#&L1<#qtwO$KD*$GWH#2)FYG$;MFi)FhZ@%Y5!xhjF*w) zH>Zl*?HRcg&Q(wRD!Z9#s@QWq5P624&hc z$b`S{2-XrlFdhm3L?_t;pe(!}NUmca9-;CypSI6N8%!^b#B#%0}N z&!5f&$|?Qe3NGvOj*1PyxXGwsYZI3)-IH_WnhFuVfwC_tIB%RW)t%8N4)qgeDA$+% z%&t-1-DC=Gjt3+VjJ5pw1SurSrCha-JaP>cixu1)XtiNzQx9nO_ODwZTyhrl@gQ~)bry~79FRC3Wp(pREArM zu+0JN9T*5-W~*34JQu{Ug1Uo8g(_RMA91lUpT_R-@K`cPSRyLY^Vrl|N6NwmCf17B zCv*4!q7VyaB%T|vDT-qM?i0yZixQvkK%gw$j)15J7220qk1s~aSI8+%+Hy^)@`(^> z+${KO+wE^Xv1&5I94FEqp7M2$AQ7j#{8}(Kjt88 z+z*5$V>ae?7%)^6l_{OQ@ve9LwcUylRV;j5M6y1`BZI$#yp9#xoe-zFh9d{+#~K8QktT(t;|mh>^Zho@BS>MrEEFa08(Bk&rP+~EqW>0 zOZhx`Oij&dAB@e>&kplyJMy7`j|G$t4+nKrD0(PnR16OGLbm%V-9zXaIz=}@Htm1atp*l^3WO+PCrCPv zVDxtHzl@31j>^%D(w?yU8n(1^49(#9u4p zj}RfLU}e+2wQT=0GU)!Z_A^ zI#rx7_BD4i2~(lLwmyw&OotZ2Wol}wYDgdAG@1O^Ho5IUs0<_(iSvb-7O9Q=3^tCG zDz?4X0{kU5v>n1yqI`GgvNi_&2^kg+OQ$Yv$5O*v0X4>@GE1MNtu;wMy9kYn1q!}4 z*2fkId7bhI#tFTzr;cK41RU`$vovLKo|b= zvtE@GH({xOA29go`&V6)`l?x2)=i*Z-5NzW_%Q$Q|q9$;ea&`$Qdi z^kO3?m!V9!l^;{h3X+gDZe0~UESDAN$U&VXj!dTn-qukOzF`vKg*pR*>^aH^`oP|; zVKZQY{u6;RggJf}!QXs7dYa+{KiU<%1f0j^i&vuVdm`kFSKRCcO1BdE1(e12R5
KwB-&;7~Gwqq4WT36H|1MgF;E@NwJ z_9ncfpq2r-%#u2vOjUyDzYg&k_BH|Iv57(P)SZ2)rRC~VLyjeBr5TIZ%9ymo;)<%= z6BBXu7VA;X1eh??bcpti?q!f>7wf=kT{cOESkk_Zuh&pG8a|AYPtL!eiOYPH0tiP{ zKEN<>9V|zNL^dLWew!$_6W*epYz?w25u8iu*)}4LAsUlW){*2@&Y9!=Mc(hBL@@j{*|x17osU`z7YrY~;Tw z4F1acUK@TpnRl}j7u1wK%&O%4XKkisMCuiD<-KovNlMf^sI<))=d=0|$fHU1i>h)| zALi%eX1^J_{HSS(FyU^Ss};)J$Y90DuQ;!N+<;DpM&Q|_WLxSTZK;!qH1qRP=s|YM zm(QE;LN~sRptwf+XptqPIk~B5e31oVs!iIi1K0nf^XF^cXC%QB_5SZ(LVS@XxiSX zMsrGts2@gTow=+@Upzt2qnh`53~Q)8miR7SJQzOQvPt~`2WpypEFy9k4vZ@9K$LtKO_Qf*+z+{au6>O({+ zk=)lZAD##7PlP)E^MP7D&TPlcRh^6#1FVMRJ7d5XlbGV;Y^IW*wsr;CU z#UOEOKV0sYV~GWyi1TMk?C&=gtHaLQ4|7^2(hOOoyW_#X3tZ}F)@z!AjskEXYd=#X z%8qQ@Q)j)BT*pJH1wvtrnnWqb+ZSkhcd-c&nMU<&R6j_X(Im+CZBSL(J{Q^vHY6(Scue3Wi6{S5>L zT{uH7D55wx=62p=!rv1ZYT9-rCILok=2>AzPkQuFtapj+sJ=VOPFifAaym*ps(3KZd!Ih{eedz7WL|R)i2}fK@ zHKj@`M`E9d2C4njvrDK48Jx>k_hT}@&&JrQ`63~%tXfgNt*ifL4YLSzDO%8vnf&au zE3gty@^0=p~+i;@L+QO)@`4b9u9(uf%(|b$z-ReKc?=Q9>Lz~U$WH@V1 z-)=2n0$CMF8dC)?MlHfa>-qzRSyIs$t^7wwUKS%i{Ixb)5f;>EYt+_;&|R z`g}OU@&w7VhDl}|o925^V#%!0P*MbTEeWP()sF zM%`2n1W&189`~e%MGb&r*ZWR)*t2}l7Cb*>xOt1*r1f;@z#&v2T zy^!#Ft?;e8s~>o>@@d#fH(CNy9mR+~gEMI5Z6-96vItW{FHhzA1rxuGgX}c_VAeQd z2~J9AtrL{61~!n6TEwBnlWIt??v7nHKHm}K454dL4HHD}P>e3QpINJCKC6E5SpIYU zDKwPJ)HU9JOg=%zFMD=(GY7B|7=590$tG1mvUS>7;vXIrLd9`fZdr497V`}GJx9;` z&6`FM&3XM0Xzn8UKtQ#vm;Fn@#L-kSX%YrJ`efJe2=aenjnD6n#Msg|fZEk3phPI* zLy-8+Khr>jhj-ZM6G(t+&0}L!XCM<$O5x@lwh^TZ+x5=kvWv9Aj6q)?dn(3T5t*81 z5qHwhQEOk)418gHO4E21kO&pfyeKF&5k$J1R!pLN%OW^4w_Z$68RWwM&3>{APWoib z*6$Mrm|De)29`Fj#Bw*WP?HrKUR1C-RXYfs7nYTk^iLqhjX~SV(b4?}w~enKhpz|5 zmaUYI7iZIw=RQkbA%jke)U}-!)z##NH4BXx!<7i!e#t{gq&1jCv`PdMbj1oZN_~y# z^3P(?(s!096M)!>#X1H)3c+f*3vUKSz?s0II7&>MEo{iU1zY7^&T+P6bw^1T!WyT8Q4 zM#Zbio6De9cW8s;LpM8qMeLt9yyL}-RkP5z<*(68&$vhy zesXlg^?;oGcrRPSTIPU|BSF`=2mE%5O$FG{owqGpvyAyfV;C=hY8kD9a4!;?<|%l6 z5`?WysskX9M8Cthz`h<%Pf;|P0=h&6MZw#}cpM%1F_B{8UX7nyc;Xap5o}~d8HD~X zml0KAi#9bsE&jy#h@x0&F~X|4F#b{D?*?$6&6gN0r3f%$c8xRx zp)cxm>IHj2UK~UB6h~xfRb3|7iM^wNsTk_RlBJ|#SO3wTj0rX%-U88=L|)WImETm~ zLk;;?@^hd#js53F0*r*2d>&S)ag3WRJ&N=6dI%by50vINKf+sdar?U(fgr7aU+jU| zCbk3Sfkh3Xv}I<9!4(oY=56fQogAmMWjX*tMOhqQpgH9YgIkjL;P!@nAtDdV{1(x` zfEb+ZK}szvxLTY}Ku>DGHc*;hcVUp9Cha~-5XLKp`E{GwDU%iwL!LFrn4meqiWfc8 zWfoV@q21ggw+)he z6$}SWo#XAI$q#&$WmjvMNU^~9$yj`P=%W&7XuXdInPFuf9->bcmDMda873f|{3^w- zuWoTwSL6r>7))xkN$TO@GoVtWU)L7s`Ti(%?trX&bI?I$Yn z9TnBhka_Pm@A2pJl}g`~Tq|Zfdnh_wZ1s;L@vP69^D3N06}jrbE0muP-%7*K=;%Rv z!7{7bOJ%I5@&l(K^h4Ft!!VM|j?x+F_6RMAHs%OOZetIL075Q>)%t^L5Gb%s__Xb_ z>aGC<9`U^}j(yc1mHp=e)o=PRZ?Cp{8}1LKYQXiJK)~^3GV{xa`>Q`WMPRaO2Ct^X=45 zrl8Cpy|1?C>3B=VQA?Ay>;-2?=T#}vW^p!OOvw-6J&b%N5n4~(u^NV5(klXEuK{9E z#or-5Vnj=U;g$p?!0Hea9>2St$lTioEqTVUoOWdX09ET-RqD}JIHa$UcRufa3_&CkCZIyPQhp<-qB?~jrY3{Xb3&|T z!4E|7-Wc=bvnkrtSs2q5)=PHzU)f-J*6bQlE0^>ri6_;+k^06QQ>UozgVdaW=4`tT zalhIb0F8aOIlGyHnA4E3*d|Sg9JTK_Dwz>KTMg(S&HNLQwg@{w>{WQNzHI343vs!M zXU%;p^uiIZ>IOLahbluPZGbC6$A18JPGLwtVL#&Fa*Kx7guZ~4Tv_2pIuKv!6j1rbx+J|=(BLz(er$ODH{q?WWQ;NjBtn(e-ha7v zFJ>A(?pRj$25#6r&}a%FD*w}&fTX|EVjM=${r>oIky_&K8X_if$G?k7ES*jg5qj<= z;q*-bJtkBNX zHvZxhLai*W%V5mso&k*bpF-QB>RrtJ`GAjHp>muz_km$P_KGN|!3)4Gi;^=LP+p-j zeJ?I`)xG#ZYlDg7gZ=VT=7*nqo6JN*ypoMbgP`xFh_=tg&q1vO%ST{B`A`>nae=>< zw5)4eeR-8(z;3rvxA1X+mWOn{r)TwT(1R${hW-V7l*X1MnUW0yP5uwut6wul>(|${%;!L8=Lm6|B3pRSH*C2G!B^}54!=c=+C|Vk6=Js z!)Z1}T?+gg&OKeeeDecNU$n;N6B30kwJ}bLz)f=Jz3-*YE-;9g;~BP|CVLR*?lJ?@ zHX~<=@}UHODjwbgS+k|twEn0b35gt;iz$uCDR3DWYVkr-czIMR_T~%+r`BoiofP22TYGtT41-r(+8GI zO@^$xb3u{+8}smOIYq{G|MwwVk+I2tm^->3SF4By5gAsMmlID^xg2Ha1VOy37xt}$ zny+~-f^NY~(W^mu(|5hic^nY$;Dp~Em1O>Ejp2c_b4{)3Z8xJFCV=V|TZy$Q4_x1)5S>!{$Ae?n~db=--B+?=zzKF4&08_)1 zKZ*=hbpu1!Ghg3NJ_tPDnD{vMBBNh(D7(#btAwt=le!+?b3`m{(U z=>RMLkJc{qUqU`STI5!!dic>$oS*pHs8?XtBWnxtlG;@Yu=>w+4ZVN zjpiJE9^x|xz=U`z;v6vx`&am{E7Ae2dacb58{vW#F1ldIf8{$ooaX)#6N61G*dU+e zwo#T3ZOWRaq3|l%aN#95A0aP}+keb_W=qwe+B+ zucu-07j~q5nmg<`gVd0M=8$-iSDi?^$tG&5146_=YMa-UQSXi1NmW=DUO!(nWe*Uf zi4ZQS{sS8+bvo`;oB@Bx`bxd2tEf!7mZ8G(S5b{k2LKisheRyku?H;YNlhf&)B)SR zzmjZa;xI|#mP$iuA)>5Wvnoub} zj3>y&GnI5>QC6iD3+_KQ7@r-1FldMnG5y8o;X=sTw7pf@@rK$Bt{};C0FBN-wRx#a{n)2|g<9U;1b}`IKUNS!mU9AH5;38L{qQsE(a*?=iMiQ~ zl*TT1M`0U_u_2lP^hSfRTupI`E`941`(#Ko)GD{6MqcNc_8DKb0^RO(32K_(N%>!P zWNk`BV%6P$I?a>Ct-xE3afSJ&N4Q)%N983iY}G!$_FpcDs3vfM+PpbIe`w8ym5FA* zIsBn=@f)*>YNkVnwL|vz-{TQuC_@4hRW!>9G@7S?;_%lv2i8t2t1)e2cp+p^nhivc z`xgu}OOOWU3fWI?F+4Oe5KY@oBm+!?A*D;415XyN8CG5qF_2XXS6QBpRo|soai`M? zU5@Nc{W1Xw*$U0jn-tCzfC zEmb>%6_qvk;AQGeJ5|)#6+i@IrdNGE@t*F{QY@gi-Z?KMVw_*)q}lgeZ%r$JRW4$N zoi}u$dX=4nU&L@6@abS3$c|CD*^7Ox8aWl?eR=lrd>;WvI^G|zx634SGj~HtihS@| z#+4JIolTg(f6N;%q35z!dt}9ke`0wD4!%97$xyhtM4|<@J~|n`yDfzCwzZyhaWZ7b zU-(3@h}&+;Xr@g*TFU}wWQ0@57s~un#7=cnML>7d8_5z#titA&2V(!W8!&e>SLJ_(=TL*Pyy9N7yI!C1yK4;la4>0svyvBMx3}Em z*Cj)=xL9-SB@(b%q#jVPKZr}0NE7%EO9Je=nMa9A^XU5~tn&CWl|vsA5RfCBnKos2 z;thzP4?nW=^`Qt1wN@RBfvl+&^c})pK)04t*?eh7*Z!{Wb2$v7<~!Kh70`}7Ae)42 zC1=opCn3Pn4dhfTlb_$R!xOyj;!!SlZ}Xv-9fr#&kb;Yy;Ub(oi9sB_7Fx5Y7Z*si;C+SsJ_>|0NQ!& zSHOix-!R-ngRG2X!j-!<&yU(v-W>X&8rc42rX(EP&ztR-l+0z4!9m56MHmigMM0^s z3RYGiW3WIf;iT{`$b!4u2%FvwbkbrAgj}}2Xa#q-zPx!SCe>jzlf!!Z#+@K^=Tqr zlOma>oM8hH#B|>P$ELduW)C08hF=kQ{&QQXa51z*=l-m#|Bx0%%KUaf+Q1)mDAQqW z_CpEdDG&t9P9zR8lqupnFjSYX6)uA$uf5(az!*6uya&*8SE1%9ItK~cgJGkrRT34c zWYy`)?QYVcs1q66K;R~*fDn`~I#O=0ww$%$9|ge1LJDlxpRVKGNRa++=}@MksG+rf zWqwa_%h{R(!tu-i18*_v;g5A^DPh&TN|SQg9bOs~!Y@wf&j?fe=Ut{lhOt5qTftHl zy}MRxDMwoQH|x>!BQg-4kycKAWI?ZiA7cC6k9?Cqq$vewrg<0&MW7BWAZ2getF$`F ziGk2ZuMmw6{R$+Ec(XWU=uAff{r}`*Nj#HLkD&cT3-my7CgiJIr~m!fe9d)f5g?D- zE~LqF8lXvf(UjPflhe_lWeU;gbkn8+-5(}IwEcwplU(G1o$&8TN>$d?x|n&QY1dd> zC$PnX%7u85s(?ELcIcU|rAP>JMw*i^;hE4$CCv}@Z%Fx&xq(SSP)0$ImnBdqpHQ%> zyuO5c)Odfi=y6<_$}5{NHGPM{S{L{e3}0S>zFlWH=ClQV7el0vp~{#Ib@i<8p84^h zZGT)Zh1KAucz`MKfMY$!=L3rVGVz$T5k#Nm*c33fGE)d|tz-^Hgklb*NKT&=BC|Eb$FFy?bY`W`!Q4~QjeWjk<=tV zFd~gxrmCD95l_U@dRzMORDJ`>gy{lyJgxa6?PK73447Kb?L^6{jtK$EpEBu>(0Lc# zZn1B!qEw{+L<}hrTZ;rl!ccz-SGy&~dvf_941+o*CN8fFK!x@|t3QiW{PyHSrrrVE zvNs0Meah6>p(F4{ybq^NHy1g~+f0qfR4iLAmZ=1DmdR9YC`0+9Ia6F)<;{=*jC9PE z&oX!#Lff61+Qy;E2}$&@Cjs(TT(Ex;_azs3=MKBkd;?>*G8ox1|GFZBF>-`3F|h=< z%Yky!G=cZ6dG&=O-S@oGeiI$FOa?X4*(U%y?6~pkWIo7Qew)0O@mdQ((7+M zM}5q~E3NubaaLX|sy$hoZ z0R+krwN`0FJU568t_dz(7PlW#R#08Y4nxOdx5vdK*FQkKIOpZ%JA1f{^B3>~M8dwiz)Sd=Vv8P}ENAGU8(ASUFRJ}bX%+B#;4T!< za*Ya3zib603i@APkCbpozWTkqkZ)ZXMd=F^Fo!@ zVv4V4)En=%m|X?j9ELd$D_T%n17OM)<@|v@8qD0Jxic79f0bsfCIcovbTqo(~b9cUtoX1tN#WaCSGrgeIoeD+9@Kc=+k zKyvh;FY8dCyb$G8qZLvzG9LU~&{bn-o=v?hs`5<~DDZetsk6I+7WeR}uI+CC^~Rzl zPzhzomFyOX8puV|>V&)e$~a+=#+xdWQPgZ!sH>xGW&pRiU?S3vd?D9=wLo{#_GRH9 zsPs%(p_E^}7v@x(?M-nN^45Ikc`ptsom#K`Fm+k{sF|Y)(PqSQP=J2qBhUr=d#k3% zYu+$>yazzcuL+v%*?t(y>3m1CCaZE_w96sGWWNCP;+e0&bU6K+_@xh!p1jJlFTvQ5 z|DqXU@BEU}eoKV9Vt18+bU_vE9n?4T>hEn?o5YIw)7URkl~Pg~UuLmbMN$$*+i1I> z?Gtkl{}whb{Exn^-ykFeGSXyaUFh6ptAZ#M3|QITBI`i4`GxK2=j&!zl3!thM}Guy z7JSTfei!L8qon_%K%W6cpUD-^r?HwqZhdW9J9YXm1gZkA?yj*XnU)o!aN%gZkFbVt zv_8R*cYZ|fZuvn)7l}Izw#d9Gag42fdgM7JF@NU%=L$kE)mLn_*BGVSQg~mlV)o46 zPX6AbyrU3|)`bfH?c?tC+)bH|HU|zz;WhlKw|_=X=cAf-jU3)Vh4Gs7uQ62#tsw71 zuwJrh=KN$%j=?d;BGzq~EIVx1tV_2d2?6yE36ls~WW?1aZhR(GDh)uffPl0NkO;b7 zcrcTKjC(f#>AI+~qxv6(f*r=WMlZ+oap%H0T|d(*Kos#(x8p8$=~l10ak_?w)|3{mT|14GkWghd!-CL!IF^E`f9f?-qZUq<;?9v}hg8Nc@k1tIMC6zG z{@wa?ec>{4_*9G+P-Ia0#WXj;3$lXFY83Q;tpzyB diff --git a/crates/wallets/src/wallet_browser/app/assets/main.js b/crates/wallets/src/wallet_browser/app/assets/main.js deleted file mode 100644 index 6d89aa9e303f1..0000000000000 --- a/crates/wallets/src/wallet_browser/app/assets/main.js +++ /dev/null @@ -1,71 +0,0 @@ -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function ee(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var oe=/\/+/g;function se(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function ce(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(ee,ee):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function le(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,le(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+se(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(oe,`$&/`)+`/`),le(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(oe,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=d()})),p=s((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,ie());else{var t=n(l);t!==null&&se(x,t.startTime-e)}}var S=!1,ee=-1,C=5,te=-1;function ne(){return g?!0:!(e.unstable_now()-tet&&ne());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&se(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?ie():S=!1}}}var ie;if(typeof y==`function`)ie=function(){y(re)};else if(typeof MessageChannel<`u`){var ae=new MessageChannel,oe=ae.port2;ae.port1.onmessage=re,ie=function(){oe.postMessage(null)}}else ie=function(){_(re,0)};function se(t,n){ee=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(ee),ee=-1):h=!0,se(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,ie()))),r},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),m=s(((e,t)=>{t.exports=p()})),h=s((e=>{var t=f();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=s((e=>{var t=m(),n=f(),r=g();function i(e){var t=`https://react.dev/errors/`+e;if(1he||(e.current=me[he],me[he]=null,he--)}function ve(e,t){he++,me[he]=e.current,e.current=t}var ye=ge(null),be=ge(null),xe=ge(null),Se=ge(null);function Ce(e,t){switch(ve(xe,t),ve(be,e),ve(ye,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?sf(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=sf(t),e=cf(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}_e(ye),ve(ye,e)}function we(){_e(ye),_e(be),_e(xe)}function Te(e){e.memoizedState!==null&&ve(Se,e);var t=ye.current,n=cf(t,e.type);t!==n&&(ve(be,e),ve(ye,n))}function Ee(e){be.current===e&&(_e(ye),_e(be)),Se.current===e&&(_e(Se),hp._currentValue=pe)}var De,Oe;function ke(e){if(De===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);De=t&&t[1]||``,Oe=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ae=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ke(n):``}function Me(e,t){switch(e.tag){case 26:case 27:case 5:return ke(e.type);case 16:return ke(`Lazy`);case 13:return e.child!==t&&t!==null?ke(`Suspense Fallback`):ke(`Suspense`);case 19:return ke(`SuspenseList`);case 0:case 15:return je(e.type,!1);case 11:return je(e.type.render,!1);case 1:return je(e.type,!0);case 31:return ke(`Activity`);default:return``}}function Ne(e){try{var t=``,n=null;do t+=Me(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var Pe=Object.prototype.hasOwnProperty,Fe=t.unstable_scheduleCallback,Ie=t.unstable_cancelCallback,Le=t.unstable_shouldYield,Re=t.unstable_requestPaint,ze=t.unstable_now,Be=t.unstable_getCurrentPriorityLevel,Ve=t.unstable_ImmediatePriority,He=t.unstable_UserBlockingPriority,Ue=t.unstable_NormalPriority,We=t.unstable_LowPriority,Ge=t.unstable_IdlePriority,Ke=t.log,qe=t.unstable_setDisableYieldValue,Je=null,Ye=null;function Xe(e){if(typeof Ke==`function`&&qe(e),Ye&&typeof Ye.setStrictMode==`function`)try{Ye.setStrictMode(Je,e)}catch{}}var Ze=Math.clz32?Math.clz32:et,Qe=Math.log,$e=Math.LN2;function et(e){return e>>>=0,e===0?32:31-(Qe(e)/$e|0)|0}var tt=256,nt=262144,rt=4194304;function it(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function at(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=it(n))):i=it(o):i=it(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=it(n))):i=it(o)):i=it(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function ot(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function st(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ct(){var e=rt;return rt<<=1,!(rt&62914560)&&(rt=4194304),e}function lt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function ut(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function dt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),xn=!1;if(bn)try{var Sn={};Object.defineProperty(Sn,`passive`,{get:function(){xn=!0}}),window.addEventListener(`test`,Sn,Sn),window.removeEventListener(`test`,Sn,Sn)}catch{xn=!1}var Cn=null,wn=null,Tn=null;function En(){if(Tn)return Tn;var e,t=wn,n=t.length,r,i=`value`in Cn?Cn.value:Cn.textContent,a=i.length;for(e=0;e=nr),ar=` `,or=!1;function sr(e,t){switch(e){case`keyup`:return er.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function cr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var lr=!1;function ur(e,t){switch(e){case`compositionend`:return cr(t);case`keypress`:return t.which===32?(or=!0,ar):null;case`textInput`:return e=t.data,e===ar&&or?null:e;default:return null}}function dr(e,t){if(lr)return e===`compositionend`||!tr&&sr(e,t)?(e=En(),Tn=wn=Cn=null,lr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Nr(n)}}function Fr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Fr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ir(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=qt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=qt(e.document)}return t}function Lr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Rr=bn&&`documentMode`in document&&11>=document.documentMode,zr=null,Br=null,Vr=null,Hr=!1;function Ur(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Hr||zr==null||zr!==qt(r)||(r=zr,`selectionStart`in r&&Lr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Vr&&Mr(Vr,r)||(Vr=r,r=Gd(Br,`onSelect`),0>=o,i-=o,Fi=1<<32-Ze(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),Wi&&Li(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),Wi&&Li(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return Wi&&Li(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),Wi&&Li(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===ie&&Ra(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ga(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=Si(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=xi(o.type,o.key,o.props,null,e.mode,c),Ga(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Ti(o,e.mode,c),c.return=e,e=c}return s(e);case ie:return o=Ra(o),b(e,r,o,c)}if(de(o))return h(e,r,o,c);if(ce(o)){if(l=ce(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Wa(o),c);if(o.$$typeof===ee)return b(e,r,fa(e,o),c);Ka(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=Ci(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ua=0;var i=b(e,t,n,r);return Ha=null,i}catch(t){if(t===Ma||t===Pa)throw t;var a=_i(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ja=qa(!0),Ya=qa(!1),Xa=!1;function Za(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Qa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function $a(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function eo(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,eu&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=mi(e),pi(e,null,n),t}return ui(e,r,t,n),mi(e)}function to(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,pt(e,n)}}function no(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var ro=!1;function io(){if(ro){var e=Ca;if(e!==null)throw e}}function ao(e,t,n,r){ro=!1;var i=e.updateQueue;Xa=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(M&f)===f:(r&f)===f){f!==0&&f===Sa&&(ro=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:Xa=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),lu|=o,e.lanes=o,e.memoizedState=d}}function oo(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function so(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=w.T,s={};w.T=s,qs(e,!1,t,n);try{var c=i(),l=w.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ks(e,t,Ea(c,r),ju(e)):Ks(e,t,r,ju(e))}catch(n){Ks(e,t,{then:function(){},status:`rejected`,reason:n},ju())}finally{fe.p=a,o!==null&&s.types!==null&&(o.types=s.types),w.T=o}}function Is(){}function Ls(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Rs(e).queue;Fs(e,a,t,pe,n===null?Is:function(){return zs(e),n(r)})}function Rs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:pe,baseState:pe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jo,lastRenderedState:pe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function zs(e){var t=Rs(e);t.next===null&&(t=e.alternate.memoizedState),Ks(e,t.next.queue,{},ju())}function Bs(){return da(hp)}function Vs(){return Uo().memoizedState}function Hs(){return Uo().memoizedState}function Us(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=ju();e=$a(n);var r=eo(t,e,n);r!==null&&(Nu(r,t,n),to(r,t,n)),t={cache:va()},e.payload=t;return}t=t.return}}function Ws(e,t,n){var r=ju();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Js(e)?Ys(t,n):(n=di(e,t,n,r),n!==null&&(Nu(n,e,r),Xs(n,t,r)))}function Gs(e,t,n){Ks(e,t,n,ju())}function Ks(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Js(e))Ys(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,jr(s,o))return ui(e,t,i,0),tu===null&&li(),!1}catch{}if(n=di(e,t,i,r),n!==null)return Nu(n,e,r),Xs(n,t,r),!0}return!1}function qs(e,t,n,r){if(r={lane:2,revertLane:Ad(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Js(e)){if(t)throw Error(i(479))}else t=di(e,n,r,2),t!==null&&Nu(t,e,2)}function Js(e){var t=e.alternate;return e===A||t!==null&&t===A}function Ys(e,t){Do=Eo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Xs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,pt(e,n)}}var Zs={readContext:da,use:Ko,useCallback:No,useContext:No,useEffect:No,useImperativeHandle:No,useLayoutEffect:No,useInsertionEffect:No,useMemo:No,useReducer:No,useRef:No,useState:No,useDebugValue:No,useDeferredValue:No,useTransition:No,useSyncExternalStore:No,useId:No,useHostTransitionStatus:No,useFormState:No,useActionState:No,useOptimistic:No,useMemoCache:No,useCacheRefresh:No};Zs.useEffectEvent=No;var Qs={readContext:da,use:Ko,useCallback:function(e,t){return Ho().memoizedState=[e,t===void 0?null:t],e},useContext:da,useEffect:Ss,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),bs(4194308,4,Os.bind(null,t,e),n)},useLayoutEffect:function(e,t){return bs(4194308,4,e,t)},useInsertionEffect:function(e,t){bs(4,2,e,t)},useMemo:function(e,t){var n=Ho();t=t===void 0?null:t;var r=e();if(Oo){Xe(!0);try{e()}finally{Xe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Ho();if(n!==void 0){var i=n(t);if(Oo){Xe(!0);try{n(t)}finally{Xe(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ws.bind(null,A,e),[r.memoizedState,e]},useRef:function(e){var t=Ho();return e={current:e},t.memoizedState=e},useState:function(e){e=is(e);var t=e.queue,n=Gs.bind(null,A,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:As,useDeferredValue:function(e,t){return Ns(Ho(),e,t)},useTransition:function(){var e=is(!1);return e=Fs.bind(null,A,e.queue,!0,!1),Ho().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=A,a=Ho();if(Wi){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),tu===null)throw Error(i(349));M&127||$o(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,Ss(ts.bind(null,r,o,e),[e]),r.flags|=2048,vs(9,{destroy:void 0},es.bind(null,r,o,n,t),null),n},useId:function(){var e=Ho(),t=tu.identifierPrefix;if(Wi){var n=Ii,r=Fi;n=(r&~(1<<32-Ze(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=ko++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[bt]=t,o[xt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch($d(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Kc(t)}}return Zc(t),qc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Kc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=xe.current,Zi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Hi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[bt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Zd(e.nodeValue,n)),e||Ji(t,!0)}else e=of(e).createTextNode(r),e[bt]=t,t.stateNode=e}return Zc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Zi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[bt]=t}else Qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Zc(t),e=!1}else n=$i(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(bo(t),t):(bo(t),null);if(t.flags&128)throw Error(i(558))}return Zc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Zi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[bt]=t}else Qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Zc(t),a=!1}else a=$i(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(bo(t),t):(bo(t),null)}return bo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Yc(t,t.updateQueue),Zc(t),null);case 4:return we(),e===null&&Vd(t.stateNode.containerInfo),Zc(t),null;case 10:return aa(t.type),Zc(t),null;case 19:if(_e(xo),r=t.memoizedState,r===null)return Zc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Xc(r,!1);else{if(cu!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=So(e),o!==null){for(t.flags|=128,Xc(r,!1),e=o.updateQueue,t.updateQueue=e,Yc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)bi(n,e),n=n.sibling;return ve(xo,xo.current&1|2),Wi&&Li(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&ze()>yu&&(t.flags|=128,a=!0,Xc(r,!1),t.lanes=4194304)}else{if(!a)if(e=So(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Yc(t,e),Xc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!Wi)return Zc(t),null}else 2*ze()-r.renderingStartTime>yu&&n!==536870912&&(t.flags|=128,a=!0,Xc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Zc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ze(),e.sibling=null,n=xo.current,ve(xo,a?n&1|2:n&1),Wi&&Li(t,r.treeForkCount),e);case 22:case 23:return bo(t),po(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Zc(t),t.subtreeFlags&6&&(t.flags|=8192)):Zc(t),n=t.updateQueue,n!==null&&Yc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&_e(Oa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),aa(_a),Zc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function $c(e,t){switch(Bi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return aa(_a),we(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Ee(t),null;case 31:if(t.memoizedState!==null){if(bo(t),t.alternate===null)throw Error(i(340));Qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(bo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return _e(xo),null;case 4:return we(),null;case 10:return aa(t.type),null;case 22:case 23:return bo(t),po(),e!==null&&_e(Oa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return aa(_a),null;case 25:return null;default:return null}}function el(e,t){switch(Bi(t),t.tag){case 3:aa(_a),we();break;case 26:case 27:case 5:Ee(t);break;case 4:we();break;case 31:t.memoizedState!==null&&bo(t);break;case 13:bo(t);break;case 19:_e(xo);break;case 10:aa(t.type);break;case 22:case 23:bo(t),po(),e!==null&&_e(Oa);break;case 24:aa(_a)}}function tl(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){ld(t,t.return,e)}}function nl(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){ld(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){ld(t,t.return,e)}}function rl(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{so(t,n)}catch(t){ld(e,e.return,t)}}}function il(e,t,n){n.props=ac(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){ld(e,t,n)}}function al(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){ld(e,t,n)}}function ol(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){ld(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){ld(e,t,n)}else n.current=null}function sl(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){ld(e,e.return,t)}}function cl(e,t,n){try{var r=e.stateNode;ef(r,e.type,n,t),r[xt]=t}catch(t){ld(e,e.return,t)}}function ll(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&_f(e.type)||e.tag===4}function ul(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||ll(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&_f(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function dl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=dn));else if(r!==4&&(r===27&&_f(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(dl(e,t,n),e=e.sibling;e!==null;)dl(e,t,n),e=e.sibling}function fl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&_f(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(fl(e,t,n),e=e.sibling;e!==null;)fl(e,t,n),e=e.sibling}function pl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);$d(t,r,n),t[bt]=e,t[xt]=n}catch(t){ld(e,e.return,t)}}var ml=!1,hl=!1,gl=!1,_l=typeof WeakSet==`function`?WeakSet:Set,vl=null;function yl(e,t){if(e=e.containerInfo,rf=wp,e=Ir(e),Lr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(af={focusedElem:e,selectionRange:n},wp=!1,vl=t;vl!==null;)if(t=vl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,vl=e;else for(;vl!==null;){switch(t=vl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),$d(o,r,n),o[bt]=e,Mt(o),r=o;break a;case`link`:var s=ip(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Pr(s,h),v=Pr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,w.T=null,n=Du,Du=null;var o=Cu,s=Tu;if(Su=0,wu=Cu=null,Tu=0,eu&6)throw Error(i(331));var c=eu;if(eu|=4,Yl(o.current),Vl(o,o.current,s,n),eu=c,Cd(0,!1),Ye&&typeof Ye.onPostCommitFiberRoot==`function`)try{Ye.onPostCommitFiberRoot(Je,o)}catch{}return!0}finally{fe.p=a,w.T=r,ad(e,t)}}function cd(e,t,n){t=Di(n,t),t=dc(e.stateNode,t,2),e=eo(e,t,2),e!==null&&(ut(e,2),Sd(e))}function ld(e,t,n){if(e.tag===3)cd(e,e,n);else for(;t!==null;){if(t.tag===3){cd(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(xu===null||!xu.has(r))){e=Di(n,e),n=fc(2),r=eo(t,n,2),r!==null&&(pc(n,r,t,e),ut(r,2),Sd(r));break}}t=t.return}}function ud(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new $l;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(ou=!0,i.add(n),e=dd.bind(null,e,t,n),t.then(e,e))}function dd(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,tu===e&&(M&n)===n&&(cu===4||cu===3&&(M&62914560)===M&&300>ze()-_u?!(eu&2)&&Bu(e,0):du|=n,pu===M&&(pu=0)),Sd(e)}function fd(e,t){t===0&&(t=ct()),e=fi(e,t),e!==null&&(ut(e,t),Sd(e))}function pd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),fd(e,n)}function md(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),fd(e,n)}function hd(e,t){return Fe(e,t)}var gd=null,_d=null,vd=!1,yd=!1,bd=!1,xd=0;function Sd(e){e!==_d&&e.next===null&&(_d===null?gd=_d=e:_d=_d.next=e),yd=!0,vd||(vd=!0,kd())}function Cd(e,t){if(!bd&&yd){bd=!0;do for(var n=!1,r=gd;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ze(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,Od(r,a))}else a=M,a=at(r,r===tu?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||ot(r,a)||(n=!0,Od(r,a));r=r.next}while(n);bd=!1}}function wd(){Td()}function Td(){yd=vd=!1;var e=0;xd!==0&&df()&&(e=xd);for(var t=ze(),n=null,r=gd;r!==null;){var i=r.next,a=Ed(r,t);a===0?(r.next=null,n===null?gd=i:n.next=i,i===null&&(_d=n)):(n=r,(e!==0||a&3)&&(yd=!0)),r=i}Su!==0&&Su!==5||Cd(e,!1),xd!==0&&(xd=0)}function Ed(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&tf(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Bf(e,t,n){var r=zf;if(r&&typeof t==`string`&&t){var i=Yt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),Pf.has(i)||(Pf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),$d(t,`link`,e),Mt(t),r.head.appendChild(t)))}}function Vf(e){If.D(e),Bf(`dns-prefetch`,e,null)}function Hf(e,t){If.C(e,t),Bf(`preconnect`,e,t)}function Uf(e,t,n){If.L(e,t,n);var r=zf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Yt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Yt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Yt(n.imageSizes)+`"]`)):i+=`[href="`+Yt(e)+`"]`;var a=i;switch(t){case`style`:a=Jf(e);break;case`script`:a=I(e)}Nf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),Nf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Yf(a))||t===`script`&&r.querySelector(Qf(a))||(t=r.createElement(`link`),$d(t,`link`,e),Mt(t),r.head.appendChild(t)))}}function Wf(e,t){If.m(e,t);var n=zf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Yt(r)+`"][href="`+Yt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=I(e)}if(!Nf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),Nf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Qf(a)))return}r=n.createElement(`link`),$d(r,`link`,e),Mt(r),n.head.appendChild(r)}}}function Gf(e,t,n){If.S(e,t,n);var r=zf;if(r&&e){var i=jt(r).hoistableStyles,a=Jf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Yf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=Nf.get(a))&&tp(e,n);var c=o=r.createElement(`link`);Mt(c),$d(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,ep(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Kf(e,t){If.X(e,t);var n=zf;if(n&&e){var r=jt(n).hoistableScripts,i=I(e),a=r.get(i);a||(a=n.querySelector(Qf(i)),a||(e=p({src:e,async:!0},t),(t=Nf.get(i))&&np(e,t),a=n.createElement(`script`),Mt(a),$d(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function qf(e,t){If.M(e,t);var n=zf;if(n&&e){var r=jt(n).hoistableScripts,i=I(e),a=r.get(i);a||(a=n.querySelector(Qf(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=Nf.get(i))&&np(e,t),a=n.createElement(`script`),Mt(a),$d(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function F(e,t,n,r){var a=(a=xe.current)?Ff(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Jf(n.href),n=jt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Jf(n.href);var o=jt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Yf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),Nf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Nf.set(e,n),o||Zf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=I(n),n=jt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Jf(e){return`href="`+Yt(e)+`"`}function Yf(e){return`link[rel="stylesheet"][`+e+`]`}function Xf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Zf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),$d(t,`link`,n),Mt(t),e.head.appendChild(t))}function I(e){return`[src="`+Yt(e)+`"]`}function Qf(e){return`script[async]`+e}function $f(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Yt(n.href)+`"]`);if(r)return t.instance=r,Mt(r),r;var a=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Mt(r),$d(r,`style`,a),ep(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Jf(n.href);var o=e.querySelector(Yf(a));if(o)return t.state.loading|=4,t.instance=o,Mt(o),o;r=Xf(n),(a=Nf.get(a))&&tp(r,a),o=(e.ownerDocument||e).createElement(`link`),Mt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),$d(o,`link`,r),t.state.loading|=4,ep(o,n.precedence,e),t.instance=o;case`script`:return o=I(n.src),(a=e.querySelector(Qf(o)))?(t.instance=a,Mt(a),a):(r=n,(a=Nf.get(o))&&(r=p({},n),np(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Mt(a),$d(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,ep(r,n.precedence,e));return t.instance}function ep(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function op(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function sp(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function cp(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Jf(r.href),a=t.querySelector(Yf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=dp.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Mt(a);return}a=t.ownerDocument||t,r=Xf(r),(i=Nf.get(i))&&tp(r,i),a=a.createElement(`link`),Mt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),$d(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=dp.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var lp=0;function up(e,t){return e.stylesheets&&e.count===0&&pp(e,e.stylesheets),0lp?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function dp(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)pp(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var fp=null;function pp(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,fp=new Map,t.forEach(mp,e),fp=null,dp.call(e))}function mp(e,t){if(!(t.state.loading&4)){var n=fp.get(e);if(n)var r=n.get(null);else{n=new Map,fp.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=_()}))(),y=f(),b,x=o((()=>{b=`1.2.3`})),S,ee=o((()=>{x(),S=class e extends Error{constructor(t,n={}){let r=n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details,i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...i?[`Docs: https://abitype.dev${i}`]:[],...r?[`Details: ${r}`]:[],`Version: abitype@${b}`].join(` -`);super(a),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiTypeError`}),n.cause&&(this.cause=n.cause),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.shortMessage=t}}}));function C(e,t){return e.exec(t)?.groups}var te,ne,re,ie=o((()=>{te=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,ne=/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/,re=/^\(.+?\).*?$/}));function ae(e){let t=e.type;if(oe.test(e.type)&&`components`in e){t=`(`;let n=e.components.length;for(let r=0;r{ie(),oe=/^tuple(?(\[(\d*)\])*)$/}));function ce(e){let t=``,n=e.length;for(let r=0;r{se()}));function ue(e){return e.type===`function`?`function ${e.name}(${ce(e.inputs)})${e.stateMutability&&e.stateMutability!==`nonpayable`?` ${e.stateMutability}`:``}${e.outputs?.length?` returns (${ce(e.outputs)})`:``}`:e.type===`event`?`event ${e.name}(${ce(e.inputs)})`:e.type===`error`?`error ${e.name}(${ce(e.inputs)})`:e.type===`constructor`?`constructor(${ce(e.inputs)})${e.stateMutability===`payable`?` payable`:``}`:e.type===`fallback`?`fallback() external${e.stateMutability===`payable`?` payable`:``}`:`receive() external payable`}var de=o((()=>{le()}));function w(e){return we.test(e)}function fe(e){return C(we,e)}function pe(e){return Te.test(e)}function me(e){return C(Te,e)}function he(e){return Ee.test(e)}function ge(e){return C(Ee,e)}function _e(e){return De.test(e)}function ve(e){return C(De,e)}function ye(e){return Oe.test(e)}function be(e){return C(Oe,e)}function xe(e){return ke.test(e)}function Se(e){return C(ke,e)}function Ce(e){return Ae.test(e)}var we,Te,Ee,De,Oe,ke,Ae,je,Me,Ne,Pe=o((()=>{ie(),we=/^error (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,Te=/^event (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/,Ee=/^function (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)(?: (?external|public{1}))?(?: (?pure|view|nonpayable|payable{1}))?(?: returns\s?\((?.*?)\))?$/,De=/^struct (?[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?.*?)\}$/,Oe=/^constructor\((?.*?)\)(?:\s(?payable{1}))?$/,ke=/^fallback\(\) external(?:\s(?payable{1}))?$/,Ae=/^receive\(\) external payable$/,je=new Set([`memory`,`indexed`,`storage`,`calldata`]),Me=new Set([`indexed`]),Ne=new Set([`calldata`,`memory`,`storage`])})),Fe,Ie,Le,Re=o((()=>{ee(),Fe=class extends S{constructor({signature:e}){super(`Failed to parse ABI item.`,{details:`parseAbiItem(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiitem-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiItemError`})}},Ie=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownTypeError`})}},Le=class extends S{constructor({type:e}){super(`Unknown type.`,{metaMessages:[`Type "${e}" is not a valid ABI type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSolidityTypeError`})}}})),ze,Be,Ve,He,Ue,We,Ge=o((()=>{ee(),ze=class extends S{constructor({params:e}){super(`Failed to parse ABI parameters.`,{details:`parseAbiParameters(${JSON.stringify(e,null,2)})`,docsPath:`/api/human#parseabiparameters-1`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiParametersError`})}},Be=class extends S{constructor({param:e}){super(`Invalid ABI parameter.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParameterError`})}},Ve=class extends S{constructor({param:e,name:t}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`"${t}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SolidityProtectedKeywordError`})}},He=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidModifierError`})}},Ue=class extends S{constructor({param:e,type:t,modifier:n}){super(`Invalid ABI parameter.`,{details:e,metaMessages:[`Modifier "${n}" not allowed${t?` in "${t}" type`:``}.`,`Data location can only be specified for array, struct, or mapping types, but "${n}" was given.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidFunctionModifierError`})}},We=class extends S{constructor({abiParameter:e}){super(`Invalid ABI parameter.`,{details:JSON.stringify(e,null,2),metaMessages:[`ABI parameter type is invalid.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidAbiTypeParameterError`})}}})),Ke,qe,Je,Ye=o((()=>{ee(),Ke=class extends S{constructor({signature:e,type:t}){super(`Invalid ${t} signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidSignatureError`})}},qe=class extends S{constructor({signature:e}){super(`Unknown signature.`,{details:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`UnknownSignatureError`})}},Je=class extends S{constructor({signature:e}){super(`Invalid struct signature.`,{details:e,metaMessages:[`No properties exist.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidStructSignatureError`})}}})),Xe,Ze=o((()=>{ee(),Xe=class extends S{constructor({type:e}){super(`Circular reference detected.`,{metaMessages:[`Struct "${e}" is a circular reference.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`CircularReferenceError`})}}})),Qe,$e=o((()=>{ee(),Qe=class extends S{constructor({current:e,depth:t}){super(`Unbalanced parentheses.`,{metaMessages:[`"${e.trim()}" has too many ${t>0?`opening`:`closing`} parentheses.`],details:`Depth "${t}"`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`InvalidParenthesisError`})}}}));function et(e,t,n){let r=``;if(n)for(let e of Object.entries(n)){if(!e)continue;let t=``;for(let n of e[1])t+=`[${n.type}${n.name?`:${n.name}`:``}]`;r+=`(${e[0]}{${t}})`}return t?`${t}:${e}${r}`:`${e}${r}`}var tt,nt=o((()=>{tt=new Map([[`address`,{type:`address`}],[`bool`,{type:`bool`}],[`bytes`,{type:`bytes`}],[`bytes32`,{type:`bytes32`}],[`int`,{type:`int256`}],[`int256`,{type:`int256`}],[`string`,{type:`string`}],[`uint`,{type:`uint256`}],[`uint8`,{type:`uint8`}],[`uint16`,{type:`uint16`}],[`uint24`,{type:`uint24`}],[`uint32`,{type:`uint32`}],[`uint64`,{type:`uint64`}],[`uint96`,{type:`uint96`}],[`uint112`,{type:`uint112`}],[`uint160`,{type:`uint160`}],[`uint192`,{type:`uint192`}],[`uint256`,{type:`uint256`}],[`address owner`,{type:`address`,name:`owner`}],[`address to`,{type:`address`,name:`to`}],[`bool approved`,{type:`bool`,name:`approved`}],[`bytes _data`,{type:`bytes`,name:`_data`}],[`bytes data`,{type:`bytes`,name:`data`}],[`bytes signature`,{type:`bytes`,name:`signature`}],[`bytes32 hash`,{type:`bytes32`,name:`hash`}],[`bytes32 r`,{type:`bytes32`,name:`r`}],[`bytes32 root`,{type:`bytes32`,name:`root`}],[`bytes32 s`,{type:`bytes32`,name:`s`}],[`string name`,{type:`string`,name:`name`}],[`string symbol`,{type:`string`,name:`symbol`}],[`string tokenURI`,{type:`string`,name:`tokenURI`}],[`uint tokenId`,{type:`uint256`,name:`tokenId`}],[`uint8 v`,{type:`uint8`,name:`v`}],[`uint256 balance`,{type:`uint256`,name:`balance`}],[`uint256 tokenId`,{type:`uint256`,name:`tokenId`}],[`uint256 value`,{type:`uint256`,name:`value`}],[`event:address indexed from`,{type:`address`,name:`from`,indexed:!0}],[`event:address indexed to`,{type:`address`,name:`to`,indexed:!0}],[`event:uint indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}],[`event:uint256 indexed tokenId`,{type:`uint256`,name:`tokenId`,indexed:!0}]])}));function rt(e,t={}){if(he(e))return it(e,t);if(pe(e))return at(e,t);if(w(e))return ot(e,t);if(ye(e))return st(e,t);if(xe(e))return ct(e);if(Ce(e))return{type:`receive`,stateMutability:`payable`};throw new qe({signature:e})}function it(e,t={}){let n=ge(e);if(!n)throw new Ke({signature:e,type:`function`});let r=ut(n.parameters),i=[],a=r.length;for(let e=0;e{ie(),Re(),Ge(),Ye(),$e(),nt(),Pe(),mt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*(?:\spayable)?)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,ht=/^\((?.+?)\)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,gt=/^u?int$/,_t=/^(?:after|alias|anonymous|apply|auto|byte|calldata|case|catch|constant|copyof|default|defined|error|event|external|false|final|function|immutable|implements|in|indexed|inline|internal|let|mapping|match|memory|mutable|null|of|override|partial|private|promise|public|pure|reference|relocatable|return|returns|sizeof|static|storage|struct|super|supports|switch|this|true|try|typedef|typeof|var|view|virtual)$/}));function yt(e){let t={},n=e.length;for(let r=0;r{ie(),Re(),Ge(),Ye(),Ze(),Pe(),vt(),xt=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*)(?(?:\[\d*?\])+?)?$/}));function Ct(e){let t=yt(e),n=[],r=e.length;for(let i=0;i{Pe(),St(),vt()}));function Tt(e){let t;if(typeof e==`string`)t=rt(e);else{let n=yt(e),r=e.length;for(let i=0;i{Re(),Pe(),St(),vt()}));function Dt(e){let t=[];if(typeof e==`string`){let n=ut(e),r=n.length;for(let e=0;e{Ge(),Pe(),St(),vt()})),kt=o((()=>{de(),le(),wt(),Et(),Ot()}));kt();function T(e,t,n){let r=e[t.name];if(typeof r==`function`)return r;let i=e[n];return typeof i==`function`?i:n=>t(e,n)}function At(e,{includeName:t=!1}={}){if(e.type!==`function`&&e.type!==`event`&&e.type!==`error`)throw new sn(e.type);return`${e.name}(${jt(e.inputs,{includeName:t})})`}function jt(e,{includeName:t=!1}={}){return e?e.map(e=>Mt(e,{includeName:t})).join(t?`, `:`,`):``}function Mt(e,{includeName:t}){return e.type.startsWith(`tuple`)?`(${jt(e.components,{includeName:t})})${e.type.slice(5)}`:e.type+(t&&e.name?` ${e.name}`:``)}var Nt=o((()=>{cn()}));function Pt(e,{strict:t=!0}={}){return!e||typeof e!=`string`?!1:t?/^0x[0-9a-fA-F]*$/.test(e):e.startsWith(`0x`)}var Ft=o((()=>{}));function It(e){return Pt(e,{strict:!1})?Math.ceil((e.length-2)/2):e.length}var Lt=o((()=>{Ft()})),Rt,zt=o((()=>{Rt=`2.47.6`}));function Bt(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause!==void 0?Bt(e.cause,t):t?null:e}var Vt,E,D=o((()=>{zt(),Vt={getDocsUrl:({docsBaseUrl:e,docsPath:t=``,docsSlug:n})=>t?`${e??`https://viem.sh`}${t}${n?`#${n}`:``}`:void 0,version:`viem@${Rt}`},E=class e extends Error{constructor(t,n={}){let r=n.cause instanceof e?n.cause.details:n.cause?.message?n.cause.message:n.details,i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=Vt.getDocsUrl?.({...n,docsPath:i}),o=[t||`An error occurred.`,``,...n.metaMessages?[...n.metaMessages,``]:[],...a?[`Docs: ${a}`]:[],...r?[`Details: ${r}`]:[],...Vt.version?[`Version: ${Vt.version}`]:[]].join(` -`);super(o,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`metaMessages`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.details=r,this.docsPath=i,this.metaMessages=n.metaMessages,this.name=n.name??this.name,this.shortMessage=t,this.version=Rt}walk(e){return Bt(this,e)}}})),Ht,Ut,Wt,Gt,Kt,qt,Jt,Yt,Xt,Zt,Qt,$t,en,tn,nn,rn,an,on,sn,cn=o((()=>{Nt(),Lt(),D(),Ht=class extends E{constructor({docsPath:e}){super([`A constructor was not found on the ABI.`,`Make sure you are using the correct ABI and that the constructor exists on it.`].join(` -`),{docsPath:e,name:`AbiConstructorNotFoundError`})}},Ut=class extends E{constructor({docsPath:e}){super(["Constructor arguments were provided (`args`), but a constructor parameters (`inputs`) were not found on the ABI.","Make sure you are using the correct ABI, and that the `inputs` attribute on the constructor exists."].join(` -`),{docsPath:e,name:`AbiConstructorParamsNotFoundError`})}},Wt=class extends E{constructor({data:e,params:t,size:n}){super([`Data size of ${n} bytes is too small for given parameters.`].join(` -`),{metaMessages:[`Params: (${jt(t,{includeName:!0})})`,`Data: ${e} (${n} bytes)`],name:`AbiDecodingDataSizeTooSmallError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`params`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`size`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e,this.params=t,this.size=n}},Gt=class extends E{constructor({cause:e}={}){super(`Cannot decode zero data ("0x") with ABI parameters.`,{name:`AbiDecodingZeroDataError`,cause:e})}},Kt=class extends E{constructor({expectedLength:e,givenLength:t,type:n}){super([`ABI encoding array length mismatch for type ${n}.`,`Expected length: ${e}`,`Given length: ${t}`].join(` -`),{name:`AbiEncodingArrayLengthMismatchError`})}},qt=class extends E{constructor({expectedSize:e,value:t}){super(`Size of bytes "${t}" (bytes${It(t)}) does not match expected size (bytes${e}).`,{name:`AbiEncodingBytesSizeMismatchError`})}},Jt=class extends E{constructor({expectedLength:e,givenLength:t}){super([`ABI encoding params/values length mismatch.`,`Expected length (params): ${e}`,`Given length (values): ${t}`].join(` -`),{name:`AbiEncodingLengthMismatchError`})}},Yt=class extends E{constructor(e,{docsPath:t}){super([`Arguments (\`args\`) were provided to "${e}", but "${e}" on the ABI does not contain any parameters (\`inputs\`).`,`Cannot encode error result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the inputs exist on it.`].join(` -`),{docsPath:t,name:`AbiErrorInputsNotFoundError`})}},Xt=class extends E{constructor(e,{docsPath:t}={}){super([`Error ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`].join(` -`),{docsPath:t,name:`AbiErrorNotFoundError`})}},Zt=class extends E{constructor(e,{docsPath:t,cause:n}){super([`Encoded error signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${e}.`].join(` -`),{docsPath:t,name:`AbiErrorSignatureNotFoundError`,cause:n}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.signature=e}},Qt=class extends E{constructor(e,{docsPath:t}={}){super([`Function ${e?`"${e}" `:``}not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` -`),{docsPath:t,name:`AbiFunctionNotFoundError`})}},$t=class extends E{constructor(e,{docsPath:t}){super([`Function "${e}" does not contain any \`outputs\` on ABI.`,`Cannot decode function result without knowing what the parameter types are.`,`Make sure you are using the correct ABI and that the function exists on it.`].join(` -`),{docsPath:t,name:`AbiFunctionOutputsNotFoundError`})}},en=class extends E{constructor(e,{docsPath:t}){super([`Encoded function signature "${e}" not found on ABI.`,`Make sure you are using the correct ABI and that the function exists on it.`,`You can look up the signature here: https://4byte.sourcify.dev/?q=${e}.`].join(` -`),{docsPath:t,name:`AbiFunctionSignatureNotFoundError`})}},tn=class extends E{constructor(e,t){super(`Found ambiguous types in overloaded ABI items.`,{metaMessages:[`\`${e.type}\` in \`${At(e.abiItem)}\`, and`,`\`${t.type}\` in \`${At(t.abiItem)}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`],name:`AbiItemAmbiguityError`})}},nn=class extends E{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`,{name:`BytesSizeMismatchError`})}},rn=class extends E{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid encoding type.`,`Please provide a valid ABI type.`].join(` -`),{docsPath:t,name:`InvalidAbiEncodingType`})}},an=class extends E{constructor(e,{docsPath:t}){super([`Type "${e}" is not a valid decoding type.`,`Please provide a valid ABI type.`].join(` -`),{docsPath:t,name:`InvalidAbiDecodingType`})}},on=class extends E{constructor(e){super([`Value "${e}" is not a valid array.`].join(` -`),{name:`InvalidArrayError`})}},sn=class extends E{constructor(e){super([`"${e}" is not a valid definition type.`,`Valid types: "function", "event", "error"`].join(` -`),{name:`InvalidDefinitionTypeError`})}}})),ln,un,dn,fn=o((()=>{D(),ln=class extends E{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset "${e}" is out-of-bounds (size: ${n}).`,{name:`SliceOffsetOutOfBoundsError`})}},un=class extends E{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (${e}) exceeds padding size (${t}).`,{name:`SizeExceedsPaddingSizeError`})}},dn=class extends E{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} is expected to be ${t} ${n} long, but is ${e} ${n} long.`,{name:`InvalidBytesLengthError`})}}}));function pn(e,{dir:t,size:n=32}={}){return typeof e==`string`?mn(e,{dir:t,size:n}):hn(e,{dir:t,size:n})}function mn(e,{dir:t,size:n=32}={}){if(n===null)return e;let r=e.replace(`0x`,``);if(r.length>n*2)throw new un({size:Math.ceil(r.length/2),targetSize:n,type:`hex`});return`0x${r[t===`right`?`padEnd`:`padStart`](n*2,`0`)}`}function hn(e,{dir:t,size:n=32}={}){if(n===null)return e;if(e.length>n)throw new un({size:e.length,targetSize:n,type:`bytes`});let r=new Uint8Array(n);for(let i=0;i{fn()})),_n,vn,yn,bn,xn=o((()=>{D(),_n=class extends E{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number "${i}" is not in safe ${r?`${r*8}-bit ${n?`signed`:`unsigned`} `:``}integer range ${e?`(${t} to ${e})`:`(above ${t})`}`,{name:`IntegerOutOfRangeError`})}},vn=class extends E{constructor(e){super(`Bytes value "${e}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`,{name:`InvalidBytesBooleanError`})}},yn=class extends E{constructor(e){super(`Hex value "${e}" is not a valid boolean. The hex value must be "0x0" (false) or "0x1" (true).`,{name:`InvalidHexBooleanError`})}},bn=class extends E{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed ${t} bytes. Given size: ${e} bytes.`,{name:`SizeOverflowError`})}}}));function Sn(e,{dir:t=`left`}={}){let n=typeof e==`string`?e.replace(`0x`,``):e,r=0;for(let e=0;e{}));function wn(e,{size:t}){if(It(e)>t)throw new bn({givenSize:It(e),maxSize:t})}function Tn(e,t={}){let{signed:n}=t;t.size&&wn(e,{size:t.size});let r=BigInt(e);if(!n)return r;let i=(e.length-2)/2;return r<=(1n<{xn(),Lt(),Cn()}));function kn(e,t={}){return typeof e==`number`||typeof e==`bigint`?O(e,t):typeof e==`string`?Mn(e,t):typeof e==`boolean`?An(e,t):jn(e,t)}function An(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(wn(n,{size:t.size}),pn(n,{size:t.size})):n}function jn(e,t={}){let n=``;for(let t=0;ta||i{xn(),gn(),On(),Nn=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),Pn=new TextEncoder}));function Fn(e,t={}){return typeof e==`number`||typeof e==`bigint`?zn(e,t):typeof e==`boolean`?In(e,t):Pt(e)?Rn(e,t):Bn(e,t)}function In(e,t={}){let n=new Uint8Array(1);return n[0]=Number(e),typeof t.size==`number`?(wn(n,{size:t.size}),pn(n,{size:t.size})):n}function Ln(e){if(e>=Hn.zero&&e<=Hn.nine)return e-Hn.zero;if(e>=Hn.A&&e<=Hn.F)return e-(Hn.A-10);if(e>=Hn.a&&e<=Hn.f)return e-(Hn.a-10)}function Rn(e,t={}){let n=e;t.size&&(wn(n,{size:t.size}),n=pn(n,{dir:`right`,size:t.size}));let r=n.slice(2);r.length%2&&(r=`0${r}`);let i=r.length/2,a=new Uint8Array(i);for(let e=0,t=0;e{D(),Ft(),gn(),On(),k(),Vn=new TextEncoder,Hn={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function Wn(e,t=!1){return t?{h:Number(e&qn),l:Number(e>>Jn&qn)}:{h:Number(e>>Jn&qn)|0,l:Number(e&qn)|0}}function Gn(e,t=!1){let n=e.length,r=new Uint32Array(n),i=new Uint32Array(n);for(let a=0;a>>0)+(r>>>0);return{h:e+n+(i/2**32|0)|0,l:i|0}}var qn,Jn,Yn,Xn,Zn,Qn,$n,er,tr,nr,rr,ir,ar,or,sr,cr,lr,ur,dr=o((()=>{qn=BigInt(2**32-1),Jn=BigInt(32),Yn=(e,t,n)=>e>>>n,Xn=(e,t,n)=>e<<32-n|t>>>n,Zn=(e,t,n)=>e>>>n|t<<32-n,Qn=(e,t,n)=>e<<32-n|t>>>n,$n=(e,t,n)=>e<<64-n|t>>>n-32,er=(e,t,n)=>e>>>n-32|t<<64-n,tr=(e,t,n)=>e<>>32-n,nr=(e,t,n)=>t<>>32-n,rr=(e,t,n)=>t<>>64-n,ir=(e,t,n)=>e<>>64-n,ar=(e,t,n)=>(e>>>0)+(t>>>0)+(n>>>0),or=(e,t,n,r)=>t+n+r+(e/2**32|0)|0,sr=(e,t,n,r)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0),cr=(e,t,n,r,i)=>t+n+r+i+(e/2**32|0)|0,lr=(e,t,n,r,i)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0)+(i>>>0),ur=(e,t,n,r,i,a)=>t+n+r+i+a+(e/2**32|0)|0})),fr,pr=o((()=>{fr=typeof globalThis==`object`&&`crypto`in globalThis?globalThis.crypto:void 0}));function mr(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function hr(e){if(!Number.isSafeInteger(e)||e<0)throw Error(`positive integer expected, got `+e)}function gr(e,...t){if(!mr(e))throw Error(`Uint8Array expected`);if(t.length>0&&!t.includes(e.length))throw Error(`Uint8Array expected of length `+t+`, got length=`+e.length)}function _r(e){if(typeof e!=`function`||typeof e.create!=`function`)throw Error(`Hash should be wrapped by utils.createHasher`);hr(e.outputLen),hr(e.blockLen)}function vr(e,t=!0){if(e.destroyed)throw Error(`Hash instance has been destroyed`);if(t&&e.finished)throw Error(`Hash#digest() has already been called`)}function yr(e,t){gr(e);let n=t.outputLen;if(e.length>>t}function wr(e){return e<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255}function Tr(e){for(let t=0;te().update(Dr(t)).digest(),n=e();return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=()=>e(),t}function Ar(e){let t=(t,n)=>e(n).update(Dr(t)).digest(),n=e({});return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=t=>e(t),t}function jr(e=32){if(fr&&typeof fr.getRandomValues==`function`)return fr.getRandomValues(new Uint8Array(e));if(fr&&typeof fr.randomBytes==`function`)return Uint8Array.from(fr.randomBytes(e));throw Error(`crypto.getRandomValues must be defined`)}var Mr,Nr,Pr,Fr=o((()=>{pr(),Mr=new Uint8Array(new Uint32Array([287454020]).buffer)[0]===68,Nr=Mr?e=>e:Tr,typeof Uint8Array.from([]).toHex==`function`&&Uint8Array.fromHex,Pr=class{}}));function Ir(e,t=24){let n=new Uint32Array(10);for(let r=24-t;r<24;r++){for(let t=0;t<10;t++)n[t]=e[t]^e[t+10]^e[t+20]^e[t+30]^e[t+40];for(let t=0;t<10;t+=2){let r=(t+8)%10,i=(t+2)%10,a=n[i],o=n[i+1],s=Yr(a,o,1)^n[r],c=Xr(a,o,1)^n[r+1];for(let n=0;n<50;n+=10)e[t+n]^=s,e[t+n+1]^=c}let t=e[2],i=e[3];for(let n=0;n<24;n++){let r=Wr[n],a=Yr(t,i,r),o=Xr(t,i,r),s=Ur[n];t=e[s],i=e[s+1],e[s]=a,e[s+1]=o}for(let t=0;t<50;t+=10){for(let r=0;r<10;r++)n[r]=e[t+r];for(let r=0;r<10;r++)e[t+r]^=~n[(r+2)%10]&n[(r+4)%10]}e[0]^=qr[r],e[1]^=Jr[r]}xr(n)}var Lr,Rr,zr,Br,Vr,Hr,Ur,Wr,Gr,Kr,qr,Jr,Yr,Xr,Zr,Qr,$r,ei,ti=o((()=>{dr(),Fr(),Lr=BigInt(0),Rr=BigInt(1),zr=BigInt(2),Br=BigInt(7),Vr=BigInt(256),Hr=BigInt(113),Ur=[],Wr=[],Gr=[];for(let e=0,t=Rr,n=1,r=0;e<24;e++){[n,r]=[r,(2*n+3*r)%5],Ur.push(2*(5*r+n)),Wr.push((e+1)*(e+2)/2%64);let i=Lr;for(let e=0;e<7;e++)t=(t<>Br)*Hr)%Vr,t&zr&&(i^=Rr<<(Rr<n>32?rr(e,t,n):tr(e,t,n),Xr=(e,t,n)=>n>32?ir(e,t,n):nr(e,t,n),Zr=class e extends Pr{constructor(e,t,n,r=!1,i=24){if(super(),this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,this.enableXOF=!1,this.blockLen=e,this.suffix=t,this.outputLen=n,this.enableXOF=r,this.rounds=i,hr(n),!(0=n&&this.keccak();let a=Math.min(n-this.posOut,i-r);e.set(t.subarray(this.posOut,this.posOut+a),r),this.posOut+=a,r+=a}return e}xofInto(e){if(!this.enableXOF)throw Error(`XOF is not possible for this instance`);return this.writeInto(e)}xof(e){return hr(e),this.xofInto(new Uint8Array(e))}digestInto(e){if(yr(e,this),this.finished)throw Error(`digest() was already called`);return this.writeInto(e),this.destroy(),e}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,xr(this.state)}_cloneInto(t){let{blockLen:n,suffix:r,outputLen:i,rounds:a,enableXOF:o}=this;return t||=new e(n,r,i,o,a),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=a,t.suffix=r,t.outputLen=i,t.enableXOF=o,t.destroyed=this.destroyed,t}},Qr=(e,t,n)=>kr(()=>new Zr(t,e,n)),Qr(6,144,224/8),Qr(6,136,256/8),Qr(6,104,384/8),Qr(6,72,512/8),Qr(1,144,224/8),$r=Qr(1,136,256/8),Qr(1,104,384/8),Qr(1,72,512/8),ei=(e,t,n)=>Ar((r={})=>new Zr(t,e,r.dkLen===void 0?n:r.dkLen,!0)),ei(31,168,128/8),ei(31,136,256/8)}));function ni(e,t){let n=t||`hex`,r=$r(Pt(e,{strict:!1})?Fn(e):e);return n===`bytes`?r:kn(r)}var ri=o((()=>{ti(),Ft(),Un(),k()}));function ii(e){return ai(e)}var ai,oi=o((()=>{Un(),ri(),ai=e=>ni(Fn(e))}));function si(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;o{D()})),li,ui=o((()=>{ci(),li=e=>si(typeof e==`string`?e:ue(e))}));function di(e){return ii(li(e))}var fi=o((()=>{oi(),ui()})),pi,mi=o((()=>{fi(),pi=di})),hi,gi=o((()=>{D(),hi=class extends E{constructor({address:e}){super(`Address "${e}" is invalid.`,{metaMessages:[`- Address must be a hex value of 20 bytes (40 hex characters).`,`- Address must match its checksum counterpart.`],name:`InvalidAddressError`})}}})),_i,vi=o((()=>{_i=class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&(super.delete(e),super.set(e,t)),t}set(e,t){if(super.has(e)&&super.delete(e),super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=super.keys().next().value;e!==void 0&&super.delete(e)}return this}}}));function yi(e,t){if(xi.has(`${e}.${t}`))return xi.get(`${e}.${t}`);let n=t?`${t}${e.toLowerCase()}`:e.substring(2).toLowerCase(),r=ni(Bn(n),`bytes`),i=(t?n.substring(`${t}0x`.length):n).split(``);for(let e=0;e<40;e+=2)r[e>>1]>>4>=8&&i[e]&&(i[e]=i[e].toUpperCase()),(r[e>>1]&15)>=8&&i[e+1]&&(i[e+1]=i[e+1].toUpperCase());let a=`0x${i.join(``)}`;return xi.set(`${e}.${t}`,a),a}function bi(e,t){if(!Ci(e,{strict:!1}))throw new hi({address:e});return yi(e,t)}var xi,Si=o((()=>{gi(),Un(),ri(),vi(),Ei(),xi=new _i(8192)}));function Ci(e,t){let{strict:n=!0}=t??{},r=`${e}.${n}`;if(Ti.has(r))return Ti.get(r);let i=wi.test(e)?e.toLowerCase()===e?!0:n?yi(e)===e:!0:!1;return Ti.set(r,i),i}var wi,Ti,Ei=o((()=>{vi(),Si(),wi=/^0x[a-fA-F0-9]{40}$/,Ti=new _i(8192)}));function Di(e){return typeof e[0]==`string`?ki(e):Oi(e)}function Oi(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}function ki(e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}var Ai=o((()=>{}));function ji(e,t,n,{strict:r}={}){return Pt(e,{strict:!1})?Fi(e,t,n,{strict:r}):Pi(e,t,n,{strict:r})}function Mi(e,t){if(typeof t==`number`&&t>0&&t>It(e)-1)throw new ln({offset:t,position:`start`,size:It(e)})}function Ni(e,t,n){if(typeof t==`number`&&typeof n==`number`&&It(e)!==n-t)throw new ln({offset:n,position:`end`,size:It(e)})}function Pi(e,t,n,{strict:r}={}){Mi(e,t);let i=e.slice(t,n);return r&&Ni(i,t,n),i}function Fi(e,t,n,{strict:r}={}){Mi(e,t);let i=`0x${e.replace(`0x`,``).slice((t??0)*2,(n??e.length)*2)}`;return r&&Ni(i,t,n),i}var Ii=o((()=>{fn(),Ft(),Lt()})),Li,Ri,zi=o((()=>{Li=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,Ri=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/}));function Bi(e,t){if(e.length!==t.length)throw new Jt({expectedLength:e.length,givenLength:t.length});let n=Ui(Vi({params:e,values:t}));return n.length===0?`0x`:n}function Vi({params:e,values:t}){let n=[];for(let r=0;r0?Di([t,e]):t}}if(i)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:Di(a.map(({encoded:e})=>e))}}function Ki(e,{param:t}){let[,n]=t.type.split(`bytes`),r=It(e);if(!n){let t=e;return r%32!=0&&(t=mn(t,{dir:`right`,size:Math.ceil((e.length-2)/2/32)*32})),{dynamic:!0,encoded:Di([mn(O(r,{size:32})),t])}}if(r!==Number.parseInt(n,10))throw new qt({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:mn(e,{dir:`right`})}}function qi(e){if(typeof e!=`boolean`)throw new E(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:mn(An(e))}}function Ji(e,{signed:t,size:n=256}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Zi(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}var Qi=o((()=>{cn(),gi(),D(),xn(),Ei(),Ai(),gn(),Lt(),Ii(),k(),zi()})),$i,ea=o((()=>{Ii(),fi(),$i=e=>ji(di(e),0,4)}));function ta(e){let{abi:t,args:n=[],name:r}=e,i=Pt(r,{strict:!1}),a=t.filter(e=>i?e.type===`function`?$i(e)===r:e.type===`event`?pi(e)===r:!1:`name`in e&&e.name===r);if(a.length===0)return;if(a.length===1)return a[0];let o;for(let e of a)if(`inputs`in e){if(!n||n.length===0){if(!e.inputs||e.inputs.length===0)return e;continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===n.length&&n.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?na(t,r):!1})){if(o&&`inputs`in o&&o.inputs){let t=ra(e.inputs,o.inputs,n);if(t)throw new tn({abiItem:e,type:t[0]},{abiItem:o,type:t[1]})}o=e}}return o||a[0]}function na(e,t){let n=typeof e,r=t.type;switch(r){case`address`:return Ci(e,{strict:!1});case`bool`:return n===`boolean`;case`function`:return n===`string`;case`string`:return n===`string`;default:return r===`tuple`&&`components`in t?Object.values(t.components).every((t,r)=>n===`object`&&na(Object.values(e)[r],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>na(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function ra(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return ra(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&Ci(n[r],{strict:!1}))return o}}var ia=o((()=>{cn(),Ft(),Ei(),mi(),ea()}));function aa(e){return typeof e==`string`?{address:e,type:`json-rpc`}:e}var oa=o((()=>{}));function sa(e){let{abi:t,args:n,functionName:r}=e,i=t[0];if(r){let e=ta({abi:t,args:n,name:r});if(!e)throw new Qt(r,{docsPath:ca});i=e}if(i.type!==`function`)throw new Qt(void 0,{docsPath:ca});return{abi:[i],functionName:$i(At(i))}}var ca,la=o((()=>{cn(),ea(),Nt(),ia(),ca=`/docs/contract/encodeFunctionData`}));function ua(e){let{args:t}=e,{abi:n,functionName:r}=e.abi.length===1&&e.functionName?.startsWith(`0x`)?e:sa(e),i=n[0];return ki([r,(`inputs`in i&&i.inputs?Bi(i.inputs,t??[]):void 0)??`0x`])}var da=o((()=>{Ai(),Qi(),la()})),fa,pa,ma,ha=o((()=>{fa={1:"An `assert` condition failed.",17:`Arithmetic operation resulted in underflow or overflow.`,18:"Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).",33:`Attempted to convert to an invalid type.`,34:`Attempted to access a storage byte array that is incorrectly encoded.`,49:"Performed `.pop()` on an empty array",50:`Array index is out of bounds.`,65:`Allocated too much memory or created an array which is too large.`,81:`Attempted to call a zero-initialized variable of internal function type.`},pa={inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`},ma={inputs:[{name:`reason`,type:`uint256`}],name:`Panic`,type:`error`}})),ga,_a,va,ya=o((()=>{D(),ga=class extends E{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`,{name:`NegativeOffsetError`})}},_a=class extends E{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`,{name:`PositionOutOfBoundsError`})}},va=class extends E{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`,{name:`RecursiveReadLimitExceededError`})}}}));function ba(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(xa);return n.bytes=e,n.dataView=new DataView(e.buffer??e,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var xa,Sa=o((()=>{ya(),xa={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new va({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new _a({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new ga({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new ga({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}}}));function Ca(e,t={}){return t.size!==void 0&&wn(e,{size:t.size}),Tn(jn(e,t),t)}function wa(e,t={}){let n=e;if(t.size!==void 0&&(wn(n,{size:t.size}),n=Sn(n)),n.length>1||n[0]>1)throw new vn(n);return!!n[0]}function Ta(e,t={}){return t.size!==void 0&&wn(e,{size:t.size}),Dn(jn(e,t),t)}function Ea(e,t={}){let n=e;return t.size!==void 0&&(wn(n,{size:t.size}),n=Sn(n,{dir:`right`})),new TextDecoder().decode(n)}var Da=o((()=>{xn(),Cn(),On(),k()}));function Oa(e,t){let n=typeof t==`string`?Rn(t):t,r=ba(n);if(It(n)===0&&e.length>0)throw new Gt;if(It(t)&&It(t)<32)throw new Wt({data:typeof t==`string`?t:jn(t),params:e,size:It(t)});let i=0,a=[];for(let t=0;t48?Ca(i,{signed:n}):Ta(i,{signed:n}),32]}function Fa(e,t,{staticPosition:n}){let r=t.components.length===0||t.components.some(({name:e})=>!e),i=r?[]:{},a=0;if(La(t)){let o=n+Ta(e.readBytes(za));for(let n=0;n{cn(),Si(),Sa(),Lt(),Ii(),Cn(),Da(),Un(),k(),Qi(),Ra=32,za=32}));function Va(e){let{abi:t,data:n,cause:r}=e,i=ji(n,0,4);if(i===`0x`)throw new Gt({cause:r});let a=[...t||[],pa,ma].find(e=>e.type===`error`&&i===$i(At(e)));if(!a)throw new Zt(i,{docsPath:`/docs/contract/decodeErrorResult`,cause:r});return{abiItem:a,args:`inputs`in a&&a.inputs&&a.inputs.length>0?Oa(a.inputs,ji(n,4)):void 0,errorName:a.name}}var Ha=o((()=>{ha(),cn(),Ii(),ea(),Ba(),Nt()})),Ua,Wa=o((()=>{Ua=(e,t,n)=>JSON.stringify(e,(e,n)=>{let r=typeof n==`bigint`?n.toString():n;return typeof t==`function`?t(e,r):r},n)}));function Ga({abiItem:e,args:t,includeFunctionName:n=!0,includeName:r=!1}){if(`name`in e&&`inputs`in e&&e.inputs)return`${n?e.name:``}(${e.inputs.map((e,n)=>`${r&&e.name?`${e.name}: `:``}${typeof t[n]==`object`?Ua(t[n]):t[n]}`).join(`, `)})`}var Ka=o((()=>{Wa()})),qa,Ja,Ya=o((()=>{qa={gwei:9,wei:18},Ja={ether:-9,wei:9}}));function Xa(e,t){let n=e.toString(),r=n.startsWith(`-`);r&&(n=n.slice(1)),n=n.padStart(t,`0`);let[i,a]=[n.slice(0,n.length-t),n.slice(n.length-t)];return a=a.replace(/(0+)$/,``),`${r?`-`:``}${i||`0`}${a?`.${a}`:``}`}var Za=o((()=>{}));function Qa(e,t=`wei`){return Xa(e,qa[t])}var $a=o((()=>{Ya(),Za()}));function eo(e,t=`wei`){return Xa(e,Ja[t])}var to=o((()=>{Ya(),Za()}));function no(e){return e.reduce((e,{slot:t,value:n})=>`${e} ${t}: ${n}\n`,``)}function ro(e){return e.reduce((e,{address:t,...n})=>{let r=`${e} ${t}:\n`;return n.nonce&&(r+=` nonce: ${n.nonce}\n`),n.balance&&(r+=` balance: ${n.balance}\n`),n.code&&(r+=` code: ${n.code}\n`),n.state&&(r+=` state: -`,r+=no(n.state)),n.stateDiff&&(r+=` stateDiff: -`,r+=no(n.stateDiff)),r},` State Override: -`).slice(0,-1)}var io,ao,oo=o((()=>{D(),io=class extends E{constructor({address:e}){super(`State for account "${e}" is set multiple times.`,{name:`AccountStateConflictError`})}},ao=class extends E{constructor(){super(`state and stateDiff are set on the same account.`,{name:`StateAssignmentConflictError`})}}}));function so(e){let t=Object.entries(e).map(([e,t])=>t===void 0||t===!1?null:[e,t]).filter(Boolean),n=t.reduce((e,[t])=>Math.max(e,t.length),0);return t.map(([e,t])=>` ${`${e}:`.padEnd(n+1)} ${t}`).join(` -`)}var co,lo,uo,fo,po,mo,ho,go,_o=o((()=>{$a(),to(),D(),co=class extends E{constructor({v:e}){super(`Invalid \`v\` value "${e}". Expected 27 or 28.`,{name:`InvalidLegacyVError`})}},lo=class extends E{constructor({transaction:e}){super(`Cannot infer a transaction type from provided transaction.`,{metaMessages:[`Provided Transaction:`,`{`,so(e),`}`,``,`To infer the type, either provide:`,"- a `type` to the Transaction, or","- an EIP-1559 Transaction with `maxFeePerGas`, or","- an EIP-2930 Transaction with `gasPrice` & `accessList`, or","- an EIP-4844 Transaction with `blobs`, `blobVersionedHashes`, `sidecars`, or","- an EIP-7702 Transaction with `authorizationList`, or","- a Legacy Transaction with `gasPrice`"],name:`InvalidSerializableTransactionError`})}},uo=class extends E{constructor({storageKey:e}){super(`Size for storage key "${e}" is invalid. Expected 32 bytes. Got ${Math.floor((e.length-2)/2)} bytes.`,{name:`InvalidStorageKeySizeError`})}},fo=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=so({chain:r&&`${r?.name} (id: ${r?.id})`,from:t?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Request Arguments:`,f].filter(Boolean),name:`TransactionExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},po=class extends E{constructor({blockHash:e,blockNumber:t,blockTag:n,hash:r,index:i}){let a=`Transaction`;n&&i!==void 0&&(a=`Transaction at block time "${n}" at index "${i}"`),e&&i!==void 0&&(a=`Transaction at block hash "${e}" at index "${i}"`),t&&i!==void 0&&(a=`Transaction at block number "${t}" at index "${i}"`),r&&(a=`Transaction with hash "${r}"`),super(`${a} could not be found.`,{name:`TransactionNotFoundError`})}},mo=class extends E{constructor({hash:e}){super(`Transaction receipt with hash "${e}" could not be found. The Transaction may not be processed on a block yet.`,{name:`TransactionReceiptNotFoundError`})}},ho=class extends E{constructor({receipt:e}){super(`Transaction with hash "${e.transactionHash}" reverted.`,{metaMessages:[`The receipt marked the transaction as "reverted". This could mean that the function on the contract you are trying to call threw an error.`,` `,`You can attempt to extract the revert reason by:`,"- calling the `simulateContract` or `simulateCalls` Action with the `abi` and `functionName` of the contract","- using the `call` Action with raw `data`"],name:`TransactionReceiptRevertedError`}),Object.defineProperty(this,`receipt`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.receipt=e}},go=class extends E{constructor({hash:e}){super(`Timed out while waiting for transaction with hash "${e}" to be confirmed.`,{name:`WaitForTransactionReceiptTimeoutError`})}}})),vo,yo,bo=o((()=>{vo=e=>e,yo=e=>e})),xo,So,Co,A,wo,To,Eo=o((()=>{oa(),ha(),Ha(),Nt(),Ka(),ia(),$a(),to(),cn(),D(),oo(),_o(),bo(),xo=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d,stateOverride:f}){let p=so({from:(t?aa(t):void 0)?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});f&&(p+=`\n${ro(f)}`),super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Raw Call Arguments:`,p].filter(Boolean),name:`CallExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},So=class extends E{constructor(e,{abi:t,args:n,contractAddress:r,docsPath:i,functionName:a,sender:o}){let s=ta({abi:t,args:n,name:a}),c=s?Ga({abiItem:s,args:n,includeFunctionName:!1,includeName:!1}):void 0,l=s?At(s,{includeName:!0}):void 0,u=so({address:r&&vo(r),function:l,args:c&&c!==`()`&&`${[...Array(a?.length??0).keys()].map(()=>` `).join(``)}${c}`,sender:o});super(e.shortMessage||`An unknown error occurred while executing the contract function "${a}".`,{cause:e,docsPath:i,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],u&&`Contract Call:`,u].filter(Boolean),name:`ContractFunctionExecutionError`}),Object.defineProperty(this,`abi`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`args`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`contractAddress`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`formattedArgs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`functionName`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`sender`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abi=t,this.args=n,this.cause=e,this.contractAddress=r,this.functionName=a,this.sender=o}},Co=class extends E{constructor({abi:e,data:t,functionName:n,message:r,cause:i}){let a,o,s,c;if(t&&t!==`0x`)try{o=Va({abi:e,data:t,cause:i});let{abiItem:n,errorName:r,args:a}=o;if(r===`Error`)c=a[0];else if(r===`Panic`){let[e]=a;c=fa[e]}else{let e=n?At(n,{includeName:!0}):void 0,t=n&&a?Ga({abiItem:n,args:a,includeFunctionName:!1,includeName:!1}):void 0;s=[e?`Error: ${e}`:``,t&&t!==`()`?` ${[...Array(r?.length??0).keys()].map(()=>` `).join(``)}${t}`:``]}}catch(e){a=e}else r&&(c=r);let l;a instanceof Zt&&(l=a.signature,s=[`Unable to decode signature "${l}" as it was not found on the provided ABI.`,`Make sure you are using the correct ABI and that the error exists on it.`,`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${l}.`]),super(c&&c!==`execution reverted`||l?[`The contract function "${n}" reverted with the following ${l?`signature`:`reason`}:`,c||l].join(` -`):`The contract function "${n}" reverted.`,{cause:a??i,metaMessages:s,name:`ContractFunctionRevertedError`}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`raw`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`reason`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`signature`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=o,this.raw=t,this.reason=c,this.signature=l}},A=class extends E{constructor({functionName:e,cause:t}){super(`The contract function "${e}" returned no data ("0x").`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function "${e}",`,` - The parameters passed to the contract function may be invalid, or`,` - The address is not a contract.`],name:`ContractFunctionZeroDataError`,cause:t})}},wo=class extends E{constructor({factory:e}){super(`Deployment for counterfactual contract call failed${e?` for factory "${e}".`:``}`,{metaMessages:[`Please ensure:`,"- The `factory` is a valid contract deployment factory (ie. Create2 Factory, ERC-4337 Factory, etc).","- The `factoryData` is a valid encoded function call for contract deployment function on the factory."],name:`CounterfactualDeploymentFailedError`})}},To=class extends E{constructor({data:e,message:t}){super(t||``,{name:`RawContractError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=e}}})),Do,Oo,ko,Ao=o((()=>{Wa(),D(),bo(),Do=class extends E{constructor({body:e,cause:t,details:n,headers:r,status:i,url:a}){super(`HTTP request failed.`,{cause:t,details:n,metaMessages:[i&&`Status: ${i}`,`URL: ${yo(a)}`,e&&`Request body: ${Ua(e)}`].filter(Boolean),name:`HttpRequestError`}),Object.defineProperty(this,`body`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`headers`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`status`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.body=e,this.headers=r,this.status=i,this.url=a}},Oo=class extends E{constructor({body:e,error:t,url:n}){super(`RPC Request failed.`,{cause:t,details:t.message,metaMessages:[`URL: ${yo(n)}`,`Request body: ${Ua(e)}`],name:`RpcRequestError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=t.code,this.data=t.data,this.url=n}},ko=class extends E{constructor({body:e,url:t}){super(`The request took too long to respond.`,{details:`The request timed out.`,metaMessages:[`URL: ${yo(t)}`,`Request body: ${Ua(e)}`],name:`TimeoutError`}),Object.defineProperty(this,`url`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.url=t}}})),jo,Mo,No,Po,Fo,Io,Lo,Ro,zo,Bo,Vo,Ho,Uo,Wo,Go,Ko,qo,Jo,Yo,Xo,Zo,Qo,$o,es,ts,ns,rs,is,as,os,ss=o((()=>{D(),Ao(),jo=-1,Mo=class extends E{constructor(e,{code:t,docsPath:n,metaMessages:r,name:i,shortMessage:a}){super(a,{cause:e,docsPath:n,metaMessages:r||e?.metaMessages,name:i||`RpcError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name=i||e.name,this.code=e instanceof Oo?e.code:t??jo}},No=class extends Mo{constructor(e,t){super(e,t),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=t.data}},Po=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ParseRpcError`,shortMessage:`Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.`})}},Object.defineProperty(Po,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700}),Fo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidRequestRpcError`,shortMessage:`JSON is not a valid request object.`})}},Object.defineProperty(Fo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600}),Io=class e extends Mo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotFoundRpcError`,shortMessage:`The method${n?` "${n}"`:``} does not exist / is not available.`})}},Object.defineProperty(Io,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601}),Lo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidParamsRpcError`,shortMessage:[`Invalid parameters were provided to the RPC method.`,`Double check you have provided the correct parameters.`].join(` -`)})}},Object.defineProperty(Lo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602}),Ro=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InternalRpcError`,shortMessage:`An internal error was received.`})}},Object.defineProperty(Ro,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603}),zo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`InvalidInputRpcError`,shortMessage:[`Missing or invalid parameters.`,`Double check you have provided the correct parameters.`].join(` -`)})}},Object.defineProperty(zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3}),Bo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ResourceNotFoundRpcError`,shortMessage:`Requested resource not found.`}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`ResourceNotFoundRpcError`})}},Object.defineProperty(Bo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001}),Vo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`ResourceUnavailableRpcError`,shortMessage:`Requested resource not available.`})}},Object.defineProperty(Vo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002}),Ho=class e extends Mo{constructor(t){super(t,{code:e.code,name:`TransactionRejectedRpcError`,shortMessage:`Transaction creation failed.`})}},Object.defineProperty(Ho,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003}),Uo=class e extends Mo{constructor(t,{method:n}={}){super(t,{code:e.code,name:`MethodNotSupportedRpcError`,shortMessage:`Method${n?` "${n}"`:``} is not supported.`})}},Object.defineProperty(Uo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004}),Wo=class e extends Mo{constructor(t){super(t,{code:e.code,name:`LimitExceededRpcError`,shortMessage:`Request exceeds defined limit.`})}},Object.defineProperty(Wo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005}),Go=class e extends Mo{constructor(t){super(t,{code:e.code,name:`JsonRpcVersionUnsupportedError`,shortMessage:`Version of JSON-RPC protocol is not supported.`})}},Object.defineProperty(Go,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006}),Ko=class e extends No{constructor(t){super(t,{code:e.code,name:`UserRejectedRequestError`,shortMessage:`User rejected the request.`})}},Object.defineProperty(Ko,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001}),qo=class e extends No{constructor(t){super(t,{code:e.code,name:`UnauthorizedProviderError`,shortMessage:`The requested method and/or account has not been authorized by the user.`})}},Object.defineProperty(qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100}),Jo=class e extends No{constructor(t,{method:n}={}){super(t,{code:e.code,name:`UnsupportedProviderMethodError`,shortMessage:`The Provider does not support the requested method${n?` " ${n}"`:``}.`})}},Object.defineProperty(Jo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200}),Yo=class e extends No{constructor(t){super(t,{code:e.code,name:`ProviderDisconnectedError`,shortMessage:`The Provider is disconnected from all chains.`})}},Object.defineProperty(Yo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900}),Xo=class e extends No{constructor(t){super(t,{code:e.code,name:`ChainDisconnectedError`,shortMessage:`The Provider is not connected to the requested chain.`})}},Object.defineProperty(Xo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901}),Zo=class e extends No{constructor(t){super(t,{code:e.code,name:`SwitchChainError`,shortMessage:`An error occurred when attempting to switch chain.`})}},Object.defineProperty(Zo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902}),Qo=class e extends No{constructor(t){super(t,{code:e.code,name:`UnsupportedNonOptionalCapabilityError`,shortMessage:`This Wallet does not support a capability that was not marked as optional.`})}},Object.defineProperty(Qo,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700}),$o=class e extends No{constructor(t){super(t,{code:e.code,name:`UnsupportedChainIdError`,shortMessage:`This Wallet does not support the requested chain ID.`})}},Object.defineProperty($o,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710}),es=class e extends No{constructor(t){super(t,{code:e.code,name:`DuplicateIdError`,shortMessage:`There is already a bundle submitted with this ID.`})}},Object.defineProperty(es,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720}),ts=class e extends No{constructor(t){super(t,{code:e.code,name:`UnknownBundleIdError`,shortMessage:`This bundle id is unknown / has not been submitted`})}},Object.defineProperty(ts,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730}),ns=class e extends No{constructor(t){super(t,{code:e.code,name:`BundleTooLargeError`,shortMessage:`The call bundle is too large for the Wallet to process.`})}},Object.defineProperty(ns,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740}),rs=class e extends No{constructor(t){super(t,{code:e.code,name:`AtomicReadyWalletRejectedUpgradeError`,shortMessage:`The Wallet can support atomicity after an upgrade, but the user rejected the upgrade.`})}},Object.defineProperty(rs,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750}),is=class e extends No{constructor(t){super(t,{code:e.code,name:`AtomicityNotSupportedError`,shortMessage:`The wallet does not support atomic execution but the request requires it.`})}},Object.defineProperty(is,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760}),as=class e extends No{constructor(t){super(t,{code:e.code,name:`WalletConnectSessionSettlementError`,shortMessage:`WalletConnect session settlement failed.`})}},Object.defineProperty(as,`code`,{enumerable:!0,configurable:!0,writable:!0,value:7e3}),os=class extends Mo{constructor(e){super(e,{name:`UnknownRpcError`,shortMessage:`An unknown RPC error occurred.`})}}}));da(),cn(),D(),Eo(),Ao(),ss();var cs=3;function ls(e,{abi:t,address:n,args:r,docsPath:i,functionName:a,sender:o}){let s=e instanceof To?e:e instanceof E?e.walk(e=>`data`in e)||e.walk():{},{code:c,data:l,details:u,message:d,shortMessage:f}=s;return new So(e instanceof Gt?new A({functionName:a,cause:e}):[cs,Ro.code].includes(c)&&(l||u||d||f)||c===zo.code&&u===`execution reverted`&&l?new Co({abi:t,data:typeof l==`object`?l.data:l,functionName:a,message:s instanceof Oo?u:f??d,cause:e}):e,{abi:t,args:r,contractAddress:n,docsPath:i,functionName:a,sender:o})}Si(),ri();function us(e){return yi(`0x${ni(`0x${e.substring(4)}`).substring(26)}`)}var ds,fs,ps,ms,hs=o((()=>{ds=(function(){let e=typeof document<`u`&&document.createElement(`link`).relList;return e&&e.supports&&e.supports(`modulepreload`)?`modulepreload`:`preload`})(),fs=function(e){return`/`+e},ps={},ms=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=fs(t,n),t in ps)return;ps[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:ds,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})}}));function gs(e,t,n,r){if(typeof e.setBigUint64==`function`)return e.setBigUint64(t,n,r);let i=BigInt(32),a=BigInt(4294967295),o=Number(n>>i&a),s=Number(n&a),c=r?4:0,l=r?0:4;e.setUint32(t+c,o,r),e.setUint32(t+l,s,r)}function _s(e,t,n){return e&t^~e&n}function vs(e,t,n){return e&t^e&n^t&n}var ys,bs,xs,Ss,Cs=o((()=>{Fr(),ys=class extends Pr{constructor(e,t,n,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=t,this.padOffset=n,this.isLE=r,this.buffer=new Uint8Array(e),this.view=Sr(this.buffer)}update(e){vr(this),e=Dr(e),gr(e);let{view:t,buffer:n,blockLen:r}=this,i=e.length;for(let a=0;ar-a&&(this.process(n,0),a=0);for(let e=a;el.length)throw Error(`_sha2: outputLen bigger than state`);for(let e=0;e{Cs(),dr(),Fr(),ws=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),Ts=new Uint32Array(64),Es=class extends ys{constructor(e=32){super(64,e,8,!1),this.A=bs[0]|0,this.B=bs[1]|0,this.C=bs[2]|0,this.D=bs[3]|0,this.E=bs[4]|0,this.F=bs[5]|0,this.G=bs[6]|0,this.H=bs[7]|0}get(){let{A:e,B:t,C:n,D:r,E:i,F:a,G:o,H:s}=this;return[e,t,n,r,i,a,o,s]}set(e,t,n,r,i,a,o,s){this.A=e|0,this.B=t|0,this.C=n|0,this.D=r|0,this.E=i|0,this.F=a|0,this.G=o|0,this.H=s|0}process(e,t){for(let n=0;n<16;n++,t+=4)Ts[n]=e.getUint32(t,!1);for(let e=16;e<64;e++){let t=Ts[e-15],n=Ts[e-2],r=Cr(t,7)^Cr(t,18)^t>>>3;Ts[e]=(Cr(n,17)^Cr(n,19)^n>>>10)+Ts[e-7]+r+Ts[e-16]|0}let{A:n,B:r,C:i,D:a,E:o,F:s,G:c,H:l}=this;for(let e=0;e<64;e++){let t=Cr(o,6)^Cr(o,11)^Cr(o,25),u=l+t+_s(o,s,c)+ws[e]+Ts[e]|0,d=(Cr(n,2)^Cr(n,13)^Cr(n,22))+vs(n,r,i)|0;l=c,c=s,s=o,o=a+u|0,a=i,i=r,r=n,n=u+d|0}n=n+this.A|0,r=r+this.B|0,i=i+this.C|0,a=a+this.D|0,o=o+this.E|0,s=s+this.F|0,c=c+this.G|0,l=l+this.H|0,this.set(n,r,i,a,o,s,c,l)}roundClean(){xr(Ts)}destroy(){this.set(0,0,0,0,0,0,0,0),xr(this.buffer)}},Ds=Gn(`0x428a2f98d728ae22.0x7137449123ef65cd.0xb5c0fbcfec4d3b2f.0xe9b5dba58189dbbc.0x3956c25bf348b538.0x59f111f1b605d019.0x923f82a4af194f9b.0xab1c5ed5da6d8118.0xd807aa98a3030242.0x12835b0145706fbe.0x243185be4ee4b28c.0x550c7dc3d5ffb4e2.0x72be5d74f27b896f.0x80deb1fe3b1696b1.0x9bdc06a725c71235.0xc19bf174cf692694.0xe49b69c19ef14ad2.0xefbe4786384f25e3.0x0fc19dc68b8cd5b5.0x240ca1cc77ac9c65.0x2de92c6f592b0275.0x4a7484aa6ea6e483.0x5cb0a9dcbd41fbd4.0x76f988da831153b5.0x983e5152ee66dfab.0xa831c66d2db43210.0xb00327c898fb213f.0xbf597fc7beef0ee4.0xc6e00bf33da88fc2.0xd5a79147930aa725.0x06ca6351e003826f.0x142929670a0e6e70.0x27b70a8546d22ffc.0x2e1b21385c26c926.0x4d2c6dfc5ac42aed.0x53380d139d95b3df.0x650a73548baf63de.0x766a0abb3c77b2a8.0x81c2c92e47edaee6.0x92722c851482353b.0xa2bfe8a14cf10364.0xa81a664bbc423001.0xc24b8b70d0f89791.0xc76c51a30654be30.0xd192e819d6ef5218.0xd69906245565a910.0xf40e35855771202a.0x106aa07032bbd1b8.0x19a4c116b8d2d0c8.0x1e376c085141ab53.0x2748774cdf8eeb99.0x34b0bcb5e19b48a8.0x391c0cb3c5c95a63.0x4ed8aa4ae3418acb.0x5b9cca4f7763e373.0x682e6ff3d6b2b8a3.0x748f82ee5defb2fc.0x78a5636f43172f60.0x84c87814a1f0ab72.0x8cc702081a6439ec.0x90befffa23631e28.0xa4506cebde82bde9.0xbef9a3f7b2c67915.0xc67178f2e372532b.0xca273eceea26619c.0xd186b8c721c0c207.0xeada7dd6cde0eb1e.0xf57d4f7fee6ed178.0x06f067aa72176fba.0x0a637dc5a2c898a6.0x113f9804bef90dae.0x1b710b35131c471b.0x28db77f523047d84.0x32caab7b40c72493.0x3c9ebe0a15c9bebc.0x431d67c49c100d4c.0x4cc5d4becb3e42b6.0x597f299cfc657e2a.0x5fcb6fab3ad6faec.0x6c44198c4a475817`.split(`.`).map(e=>BigInt(e))),Os=Ds[0],ks=Ds[1],As=new Uint32Array(80),js=new Uint32Array(80),Ms=class extends ys{constructor(e=64){super(128,e,16,!1),this.Ah=Ss[0]|0,this.Al=Ss[1]|0,this.Bh=Ss[2]|0,this.Bl=Ss[3]|0,this.Ch=Ss[4]|0,this.Cl=Ss[5]|0,this.Dh=Ss[6]|0,this.Dl=Ss[7]|0,this.Eh=Ss[8]|0,this.El=Ss[9]|0,this.Fh=Ss[10]|0,this.Fl=Ss[11]|0,this.Gh=Ss[12]|0,this.Gl=Ss[13]|0,this.Hh=Ss[14]|0,this.Hl=Ss[15]|0}get(){let{Ah:e,Al:t,Bh:n,Bl:r,Ch:i,Cl:a,Dh:o,Dl:s,Eh:c,El:l,Fh:u,Fl:d,Gh:f,Gl:p,Hh:m,Hl:h}=this;return[e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h]}set(e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h){this.Ah=e|0,this.Al=t|0,this.Bh=n|0,this.Bl=r|0,this.Ch=i|0,this.Cl=a|0,this.Dh=o|0,this.Dl=s|0,this.Eh=c|0,this.El=l|0,this.Fh=u|0,this.Fl=d|0,this.Gh=f|0,this.Gl=p|0,this.Hh=m|0,this.Hl=h|0}process(e,t){for(let n=0;n<16;n++,t+=4)As[n]=e.getUint32(t),js[n]=e.getUint32(t+=4);for(let e=16;e<80;e++){let t=As[e-15]|0,n=js[e-15]|0,r=Zn(t,n,1)^Zn(t,n,8)^Yn(t,n,7),i=Qn(t,n,1)^Qn(t,n,8)^Xn(t,n,7),a=As[e-2]|0,o=js[e-2]|0,s=Zn(a,o,19)^$n(a,o,61)^Yn(a,o,6),c=Qn(a,o,19)^er(a,o,61)^Xn(a,o,6),l=sr(i,c,js[e-7],js[e-16]);As[e]=cr(l,r,s,As[e-7],As[e-16])|0,js[e]=l|0}let{Ah:n,Al:r,Bh:i,Bl:a,Ch:o,Cl:s,Dh:c,Dl:l,Eh:u,El:d,Fh:f,Fl:p,Gh:m,Gl:h,Hh:g,Hl:_}=this;for(let e=0;e<80;e++){let t=Zn(u,d,14)^Zn(u,d,18)^$n(u,d,41),v=Qn(u,d,14)^Qn(u,d,18)^er(u,d,41),y=u&f^~u&m,b=d&p^~d&h,x=lr(_,v,b,ks[e],js[e]),S=ur(x,g,t,y,Os[e],As[e]),ee=x|0,C=Zn(n,r,28)^$n(n,r,34)^$n(n,r,39),te=Qn(n,r,28)^er(n,r,34)^er(n,r,39),ne=n&i^n&o^i&o,re=r&a^r&s^a&s;g=m|0,_=h|0,m=f|0,h=p|0,f=u|0,p=d|0,{h:u,l:d}=Kn(c|0,l|0,S|0,ee|0),c=o|0,l=s|0,o=i|0,s=a|0,i=n|0,a=r|0;let ie=ar(ee,te,re);n=or(ie,S,C,ne),r=ie|0}({h:n,l:r}=Kn(this.Ah|0,this.Al|0,n|0,r|0)),{h:i,l:a}=Kn(this.Bh|0,this.Bl|0,i|0,a|0),{h:o,l:s}=Kn(this.Ch|0,this.Cl|0,o|0,s|0),{h:c,l}=Kn(this.Dh|0,this.Dl|0,c|0,l|0),{h:u,l:d}=Kn(this.Eh|0,this.El|0,u|0,d|0),{h:f,l:p}=Kn(this.Fh|0,this.Fl|0,f|0,p|0),{h:m,l:h}=Kn(this.Gh|0,this.Gl|0,m|0,h|0),{h:g,l:_}=Kn(this.Hh|0,this.Hl|0,g|0,_|0),this.set(n,r,i,a,o,s,c,l,u,d,f,p,m,h,g,_)}roundClean(){xr(As,js)}destroy(){xr(this.buffer),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}},Ns=class extends Ms{constructor(){super(48),this.Ah=xs[0]|0,this.Al=xs[1]|0,this.Bh=xs[2]|0,this.Bl=xs[3]|0,this.Ch=xs[4]|0,this.Cl=xs[5]|0,this.Dh=xs[6]|0,this.Dl=xs[7]|0,this.Eh=xs[8]|0,this.El=xs[9]|0,this.Fh=xs[10]|0,this.Fl=xs[11]|0,this.Gh=xs[12]|0,this.Gl=xs[13]|0,this.Hh=xs[14]|0,this.Hl=xs[15]|0}},Ps=kr(()=>new Es),Fs=kr(()=>new Ms),Is=kr(()=>new Ns)})),Rs,zs,Bs=o((()=>{Fr(),Rs=class extends Pr{constructor(e,t){super(),this.finished=!1,this.destroyed=!1,_r(e);let n=Dr(t);if(this.iHash=e.create(),typeof this.iHash.update!=`function`)throw Error(`Expected instance of class which extends utils.Hash`);this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,i=new Uint8Array(r);i.set(n.length>r?e.create().update(n).digest():n);for(let e=0;enew Rs(e,t).update(n).digest(),zs.create=(e,t)=>new Rs(e,t)}));function Vs(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name===`Uint8Array`}function Hs(e){if(!Vs(e))throw Error(`Uint8Array expected`)}function Us(e,t){if(typeof t!=`boolean`)throw Error(e+` boolean expected, got `+t)}function Ws(e){let t=e.toString(16);return t.length&1?`0`+t:t}function Gs(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);return e===``?lc:BigInt(`0x`+e)}function Ks(e){if(Hs(e),dc)return e.toHex();let t=``;for(let n=0;n=pc._0&&e<=pc._9)return e-pc._0;if(e>=pc.A&&e<=pc.F)return e-(pc.A-10);if(e>=pc.a&&e<=pc.f)return e-(pc.a-10)}function Js(e){if(typeof e!=`string`)throw Error(`hex string expected, got `+typeof e);if(dc)return Uint8Array.fromHex(e);let t=e.length,n=t/2;if(t%2)throw Error(`hex string expected, got unpadded hex of length `+t);let r=new Uint8Array(n);for(let t=0,i=0;tlc;e>>=uc,t+=1);return t}function oc(e,t,n){if(typeof e!=`number`||e<2)throw Error(`hashLen must be a number`);if(typeof t!=`number`||t<2)throw Error(`qByteLen must be a number`);if(typeof n!=`function`)throw Error(`hmacFn must be a function`);let r=gc(e),i=gc(e),a=0,o=()=>{r.fill(1),i.fill(0),a=0},s=(...e)=>n(i,r,...e),c=(e=gc(0))=>{i=s(_c([0]),e),r=s(),e.length!==0&&(i=s(_c([1]),e),r=s())},l=()=>{if(a++>=1e3)throw Error(`drbg: tried 1000 values`);let e=0,n=[];for(;e{o(),c(e);let n;for(;!(n=t(l()));)c();return o(),n}}function sc(e,t,n={}){let r=(t,n,r)=>{let i=vc[n];if(typeof i!=`function`)throw Error(`invalid validator function`);let a=e[t];if(!(r&&a===void 0)&&!i(a,e))throw Error(`param `+String(t)+` is invalid. Expected `+n+`, got `+a)};for(let[e,n]of Object.entries(t))r(e,n,!1);for(let[e,t]of Object.entries(n))r(e,t,!0);return e}function cc(e){let t=new WeakMap;return(n,...r)=>{let i=t.get(n);if(i!==void 0)return i;let a=e(n,...r);return t.set(n,a),a}}var lc,uc,dc,fc,pc,mc,hc,gc,_c,vc,yc=o((()=>{lc=BigInt(0),uc=BigInt(1),dc=typeof Uint8Array.from([]).toHex==`function`&&typeof Uint8Array.fromHex==`function`,fc=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),pc={_0:48,_9:57,A:65,F:70,a:97,f:102},mc=e=>typeof e==`bigint`&&lc<=e,hc=e=>(uc<new Uint8Array(e),_c=e=>Uint8Array.from(e),vc={bigint:e=>typeof e==`bigint`,function:e=>typeof e==`function`,boolean:e=>typeof e==`boolean`,string:e=>typeof e==`string`,stringOrUint8Array:e=>typeof e==`string`||Vs(e),isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>typeof e==`function`&&Number.isSafeInteger(e.outputLen)}}));function bc(e,t){let n=e%t;return n>=Ic?n:t+n}function xc(e,t,n){let r=e;for(;t-- >Ic;)r*=r,r%=n;return r}function Sc(e,t){if(e===Ic)throw Error(`invert: expected non-zero number`);if(t<=Ic)throw Error(`invert: expected positive modulus, got `+t);let n=bc(e,t),r=t,i=Ic,a=Lc,o=Lc,s=Ic;for(;n!==Ic;){let e=r/n,t=r%n,c=i-o*e,l=a-s*e;r=n,n=t,i=o,a=s,o=c,s=l}if(r!==Lc)throw Error(`invert: does not exist`);return bc(i,t)}function Cc(e,t){let n=(e.ORDER+Lc)/Bc,r=e.pow(t,n);if(!e.eql(e.sqr(r),t))throw Error(`Cannot find square root`);return r}function wc(e,t){let n=(e.ORDER-Vc)/Hc,r=e.mul(t,Rc),i=e.pow(r,n),a=e.mul(t,i),o=e.mul(e.mul(a,Rc),i),s=e.mul(a,e.sub(o,e.ONE));if(!e.eql(e.sqr(s),t))throw Error(`Cannot find square root`);return s}function Tc(e){if(e1e3)throw Error(`Cannot find square root: probably non-prime P`);if(n===1)return Cc;let a=i.pow(r,t),o=(t+Lc)/Rc;return function(e,r){if(e.is0(r))return r;if(Ac(e,r)!==1)throw Error(`Cannot find square root`);let i=n,s=e.mul(e.ONE,a),c=e.pow(r,t),l=e.pow(r,o);for(;!e.eql(c,e.ONE);){if(e.is0(c))return e.ZERO;let t=1,n=e.sqr(c);for(;!e.eql(n,e.ONE);)if(t++,n=e.sqr(n),t===i)throw Error(`Cannot find square root`);let r=Lc<(e[t]=`function`,e),{ORDER:`bigint`,MASK:`bigint`,BYTES:`isSafeInteger`,BITS:`isSafeInteger`}))}function Oc(e,t,n){if(nIc;)n&Lc&&(r=e.mul(r,i)),i=e.sqr(i),n>>=Lc;return r}function kc(e,t,n=!1){let r=Array(t.length).fill(n?e.ZERO:void 0),i=t.reduce((t,n,i)=>e.is0(n)?t:(r[i]=t,e.mul(t,n)),e.ONE),a=e.inv(i);return t.reduceRight((t,n,i)=>e.is0(n)?t:(r[i]=e.mul(t,r[i]),e.mul(t,n)),a),r}function Ac(e,t){let n=(e.ORDER-Lc)/Rc,r=e.pow(t,n),i=e.eql(r,e.ONE),a=e.eql(r,e.ZERO),o=e.eql(r,e.neg(e.ONE));if(!i&&!a&&!o)throw Error(`invalid Legendre symbol result`);return i?1:a?0:-1}function jc(e,t){t!==void 0&&hr(t);let n=t===void 0?e.toString(2).length:t;return{nBitLength:n,nByteLength:Math.ceil(n/8)}}function Mc(e,t,n=!1,r={}){if(e<=Ic)throw Error(`invalid field: expected ORDER > 0, got `+e);let{nBitLength:i,nByteLength:a}=jc(e,t);if(a>2048)throw Error(`invalid field: expected ORDER of <= 2048 bytes`);let o,s=Object.freeze({ORDER:e,isLE:n,BITS:i,BYTES:a,MASK:hc(i),ZERO:Ic,ONE:Lc,create:t=>bc(t,e),isValid:t=>{if(typeof t!=`bigint`)throw Error(`invalid field element: expected bigint, got `+typeof t);return Ic<=t&&te===Ic,isOdd:e=>(e&Lc)===Lc,neg:t=>bc(-t,e),eql:(e,t)=>e===t,sqr:t=>bc(t*t,e),add:(t,n)=>bc(t+n,e),sub:(t,n)=>bc(t-n,e),mul:(t,n)=>bc(t*n,e),pow:(e,t)=>Oc(s,e,t),div:(t,n)=>bc(t*Sc(n,e),e),sqrN:e=>e*e,addN:(e,t)=>e+t,subN:(e,t)=>e-t,mulN:(e,t)=>e*t,inv:t=>Sc(t,e),sqrt:r.sqrt||(t=>(o||=Ec(e),o(s,t))),toBytes:e=>n?Qs(e,a):Zs(e,a),fromBytes:e=>{if(e.length!==a)throw Error(`Field.fromBytes: expected `+a+` bytes, got `+e.length);return n?Xs(e):Ys(e)},invertBatch:e=>kc(s,e),cmov:(e,t,n)=>n?t:e});return Object.freeze(s)}function Nc(e){if(typeof e!=`bigint`)throw Error(`field order must be bigint`);let t=e.toString(2).length;return Math.ceil(t/8)}function Pc(e){let t=Nc(e);return t+Math.ceil(t/2)}function Fc(e,t,n=!1){let r=e.length,i=Nc(t),a=Pc(t);if(r<16||r1024)throw Error(`expected `+a+`-1024 bytes of input, got `+r);let o=bc(n?Xs(e):Ys(e),t-Lc)+Lc;return n?Qs(o,i):Zs(o,i)}var Ic,Lc,Rc,zc,Bc,Vc,Hc,Uc,Wc=o((()=>{Fr(),yc(),Ic=BigInt(0),Lc=BigInt(1),Rc=BigInt(2),zc=BigInt(3),Bc=BigInt(4),Vc=BigInt(5),Hc=BigInt(8),Uc=[`create`,`isValid`,`is0`,`neg`,`inv`,`sqrt`,`sqr`,`eql`,`add`,`sub`,`mul`,`pow`,`div`,`addN`,`subN`,`mulN`,`sqrN`]}));function Gc(e,t){let n=t.negate();return e?n:t}function Kc(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw Error(`invalid window size, expected [1..`+t+`], got W=`+e)}function qc(e,t){Kc(e,t);let n=Math.ceil(t/e)+1,r=2**(e-1),i=2**e;return{windows:n,windowSize:r,mask:hc(e),maxNumber:i,shiftBy:BigInt(e)}}function Jc(e,t,n){let{windowSize:r,mask:i,maxNumber:a,shiftBy:o}=n,s=Number(e&i),c=e>>o;s>r&&(s-=a,c+=nl);let l=t*r,u=l+Math.abs(s)-1,d=s===0,f=s<0,p=t%2!=0;return{nextN:c,offset:u,isZero:d,isNeg:f,isNegF:p,offsetF:l}}function Yc(e,t){if(!Array.isArray(e))throw Error(`array expected`);e.forEach((e,n)=>{if(!(e instanceof t))throw Error(`invalid point at index `+n)})}function Xc(e,t){if(!Array.isArray(e))throw Error(`array of scalars expected`);e.forEach((e,n)=>{if(!t.isValid(e))throw Error(`invalid scalar at index `+n)})}function Zc(e){return il.get(e)||1}function Qc(e,t){return{constTimeNegate:Gc,hasPrecomputes(e){return Zc(e)!==1},unsafeLadder(t,n,r=e.ZERO){let i=t;for(;n>tl;)n&nl&&(r=r.add(i)),i=i.double(),n>>=nl;return r},precomputeWindow(e,n){let{windows:r,windowSize:i}=qc(n,t),a=[],o=e,s=o;for(let e=0;e12?c=s-3:s>4?c=s-2:s>0&&(c=2);let l=hc(c),u=Array(Number(l)+1).fill(o),d=Math.floor((t.BITS-1)/c)*c,f=o;for(let e=d;e>=0;e-=c){u.fill(o);for(let t=0;t>BigInt(e)&l);u[a]=u[a].add(n[t])}let t=o;for(let e=u.length-1,n=o;e>0;e--)n=n.add(u[e]),t=t.add(n);if(f=f.add(t),e!==0)for(let e=0;e{Wc(),yc(),tl=BigInt(0),nl=BigInt(1),rl=new WeakMap,il=new WeakMap}));function ol(e){e.lowS!==void 0&&Us(`lowS`,e.lowS),e.prehash!==void 0&&Us(`prehash`,e.prehash)}function sl(e){let t=el(e);sc(t,{a:`field`,b:`field`},{allowInfinityPoint:`boolean`,allowedPrivateKeyLengths:`array`,clearCofactor:`function`,fromBytes:`function`,isTorsionFree:`function`,toBytes:`function`,wrapPrivateKey:`boolean`});let{endo:n,Fp:r,a:i}=t;if(n){if(!r.eql(i,r.ZERO))throw Error(`invalid endo: CURVE.a must be 0`);if(typeof n!=`object`||typeof n.beta!=`bigint`||typeof n.splitScalar!=`function`)throw Error(`invalid endo: expected "beta": bigint and "splitScalar": function`)}return Object.freeze({...t})}function cl(e,t){return Ks(Zs(e,t))}function ll(e){let t=sl(e),{Fp:n}=t,r=Mc(t.n,t.nBitLength),i=t.toBytes||((e,t,r)=>{let i=t.toAffine();return ec(Uint8Array.from([4]),n.toBytes(i.x),n.toBytes(i.y))}),a=t.fromBytes||(e=>{let t=e.subarray(1);return{x:n.fromBytes(t.subarray(0,n.BYTES)),y:n.fromBytes(t.subarray(n.BYTES,2*n.BYTES))}});function o(e){let{a:r,b:i}=t,a=n.sqr(e),o=n.mul(a,e);return n.add(n.add(o,n.mul(e,r)),i)}function s(e,t){let r=n.sqr(t),i=o(e);return n.eql(r,i)}if(!s(t.Gx,t.Gy))throw Error(`bad curve params: generator point`);let c=n.mul(n.pow(t.a,yl),bl),l=n.mul(n.sqr(t.b),BigInt(27));if(n.is0(n.add(c,l)))throw Error(`bad curve params: a or b`);function u(e){return rc(e,_l,t.n)}function d(e){let{allowedPrivateKeyLengths:n,nByteLength:r,wrapPrivateKey:i,n:a}=t;if(n&&typeof e!=`bigint`){if(Vs(e)&&(e=Ks(e)),typeof e!=`string`||!n.includes(e.length))throw Error(`invalid private key`);e=e.padStart(r*2,`0`)}let o;try{o=typeof e==`bigint`?e:Ys($s(`private key`,e,r))}catch{throw Error(`invalid private key, expected hex or `+r+` bytes, got `+typeof e)}return i&&(o=bc(o,a)),ic(`private key`,o,_l,a),o}function f(e){if(!(e instanceof h))throw Error(`ProjectivePoint expected`)}let p=cc((e,t)=>{let{px:r,py:i,pz:a}=e;if(n.eql(a,n.ONE))return{x:r,y:i};let o=e.is0();t??=o?n.ONE:n.inv(a);let s=n.mul(r,t),c=n.mul(i,t),l=n.mul(a,t);if(o)return{x:n.ZERO,y:n.ZERO};if(!n.eql(l,n.ONE))throw Error(`invZ was invalid`);return{x:s,y:c}}),m=cc(e=>{if(e.is0()){if(t.allowInfinityPoint&&!n.is0(e.py))return;throw Error(`bad point: ZERO`)}let{x:r,y:i}=e.toAffine();if(!n.isValid(r)||!n.isValid(i))throw Error(`bad point: x or y not FE`);if(!s(r,i))throw Error(`bad point: equation left != right`);if(!e.isTorsionFree())throw Error(`bad point: not in prime-order subgroup`);return!0});class h{constructor(e,t,r){if(e==null||!n.isValid(e))throw Error(`x required`);if(t==null||!n.isValid(t)||n.is0(t))throw Error(`y required`);if(r==null||!n.isValid(r))throw Error(`z required`);this.px=e,this.py=t,this.pz=r,Object.freeze(this)}static fromAffine(e){let{x:t,y:r}=e||{};if(!e||!n.isValid(t)||!n.isValid(r))throw Error(`invalid affine point`);if(e instanceof h)throw Error(`projective point not allowed`);let i=e=>n.eql(e,n.ZERO);return i(t)&&i(r)?h.ZERO:new h(t,r,n.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(e){let t=kc(n,e.map(e=>e.pz));return e.map((e,n)=>e.toAffine(t[n])).map(h.fromAffine)}static fromHex(e){let t=h.fromAffine(a($s(`pointHex`,e)));return t.assertValidity(),t}static fromPrivateKey(e){return h.BASE.multiply(d(e))}static msm(e,t){return $c(h,r,e,t)}_setWindowSize(e){v.setWindowSize(this,e)}assertValidity(){m(this)}hasEvenY(){let{y:e}=this.toAffine();if(n.isOdd)return!n.isOdd(e);throw Error(`Field doesn't support isOdd`)}equals(e){f(e);let{px:t,py:r,pz:i}=this,{px:a,py:o,pz:s}=e,c=n.eql(n.mul(t,s),n.mul(a,i)),l=n.eql(n.mul(r,s),n.mul(o,i));return c&&l}negate(){return new h(this.px,n.neg(this.py),this.pz)}double(){let{a:e,b:r}=t,i=n.mul(r,yl),{px:a,py:o,pz:s}=this,c=n.ZERO,l=n.ZERO,u=n.ZERO,d=n.mul(a,a),f=n.mul(o,o),p=n.mul(s,s),m=n.mul(a,o);return m=n.add(m,m),u=n.mul(a,s),u=n.add(u,u),c=n.mul(e,u),l=n.mul(i,p),l=n.add(c,l),c=n.sub(f,l),l=n.add(f,l),l=n.mul(c,l),c=n.mul(m,c),u=n.mul(i,u),p=n.mul(e,p),m=n.sub(d,p),m=n.mul(e,m),m=n.add(m,u),u=n.add(d,d),d=n.add(u,d),d=n.add(d,p),d=n.mul(d,m),l=n.add(l,d),p=n.mul(o,s),p=n.add(p,p),d=n.mul(p,m),c=n.sub(c,d),u=n.mul(p,f),u=n.add(u,u),u=n.add(u,u),new h(c,l,u)}add(e){f(e);let{px:r,py:i,pz:a}=this,{px:o,py:s,pz:c}=e,l=n.ZERO,u=n.ZERO,d=n.ZERO,p=t.a,m=n.mul(t.b,yl),g=n.mul(r,o),_=n.mul(i,s),v=n.mul(a,c),y=n.add(r,i),b=n.add(o,s);y=n.mul(y,b),b=n.add(g,_),y=n.sub(y,b),b=n.add(r,a);let x=n.add(o,c);return b=n.mul(b,x),x=n.add(g,v),b=n.sub(b,x),x=n.add(i,a),l=n.add(s,c),x=n.mul(x,l),l=n.add(_,v),x=n.sub(x,l),d=n.mul(p,b),l=n.mul(m,v),d=n.add(l,d),l=n.sub(_,d),d=n.add(_,d),u=n.mul(l,d),_=n.add(g,g),_=n.add(_,g),v=n.mul(p,v),b=n.mul(m,b),_=n.add(_,v),v=n.sub(g,v),v=n.mul(p,v),b=n.add(b,v),g=n.mul(_,b),u=n.add(u,g),g=n.mul(x,b),l=n.mul(y,l),l=n.sub(l,g),g=n.mul(y,_),d=n.mul(x,d),d=n.add(d,g),new h(l,u,d)}subtract(e){return this.add(e.negate())}is0(){return this.equals(h.ZERO)}wNAF(e){return v.wNAFCached(this,e,h.normalizeZ)}multiplyUnsafe(e){let{endo:r,n:i}=t;ic(`scalar`,e,gl,i);let a=h.ZERO;if(e===gl)return a;if(this.is0()||e===_l)return this;if(!r||v.hasPrecomputes(this))return v.wNAFCachedUnsafe(this,e,h.normalizeZ);let{k1neg:o,k1:s,k2neg:c,k2:l}=r.splitScalar(e),u=a,d=a,f=this;for(;s>gl||l>gl;)s&_l&&(u=u.add(f)),l&_l&&(d=d.add(f)),f=f.double(),s>>=_l,l>>=_l;return o&&(u=u.negate()),c&&(d=d.negate()),d=new h(n.mul(d.px,r.beta),d.py,d.pz),u.add(d)}multiply(e){let{endo:r,n:i}=t;ic(`scalar`,e,_l,i);let a,o;if(r){let{k1neg:t,k1:i,k2neg:s,k2:c}=r.splitScalar(e),{p:l,f:u}=this.wNAF(i),{p:d,f}=this.wNAF(c);l=v.constTimeNegate(t,l),d=v.constTimeNegate(s,d),d=new h(n.mul(d.px,r.beta),d.py,d.pz),a=l.add(d),o=u.add(f)}else{let{p:t,f:n}=this.wNAF(e);a=t,o=n}return h.normalizeZ([a,o])[0]}multiplyAndAddUnsafe(e,t,n){let r=h.BASE,i=(e,t)=>t===gl||t===_l||!e.equals(r)?e.multiplyUnsafe(t):e.multiply(t),a=i(this,t).add(i(e,n));return a.is0()?void 0:a}toAffine(e){return p(this,e)}isTorsionFree(){let{h:e,isTorsionFree:n}=t;if(e===_l)return!0;if(n)return n(h,this);throw Error(`isTorsionFree() has not been declared for the elliptic curve`)}clearCofactor(){let{h:e,clearCofactor:n}=t;return e===_l?this:n?n(h,this):this.multiplyUnsafe(t.h)}toRawBytes(e=!0){return Us(`isCompressed`,e),this.assertValidity(),i(h,this,e)}toHex(e=!0){return Us(`isCompressed`,e),Ks(this.toRawBytes(e))}}h.BASE=new h(t.Gx,t.Gy,n.ONE),h.ZERO=new h(n.ZERO,n.ONE,n.ZERO);let{endo:g,nBitLength:_}=t,v=Qc(h,g?Math.ceil(_/2):_);return{CURVE:t,ProjectivePoint:h,normPrivateKeyToScalar:d,weierstrassEquation:o,isWithinCurveOrder:u}}function ul(e){let t=el(e);return sc(t,{hash:`hash`,hmac:`function`,randomBytes:`function`},{bits2int:`function`,bits2int_modN:`function`,lowS:`boolean`}),Object.freeze({lowS:!0,...t})}function dl(e){let t=ul(e),{Fp:n,n:r,nByteLength:i,nBitLength:a}=t,o=n.BYTES+1,s=2*n.BYTES+1;function c(e){return bc(e,r)}function l(e){return Sc(e,r)}let{ProjectivePoint:u,normPrivateKeyToScalar:d,weierstrassEquation:f,isWithinCurveOrder:p}=ll({...t,toBytes(e,t,r){let i=t.toAffine(),a=n.toBytes(i.x),o=ec;return Us(`isCompressed`,r),r?o(Uint8Array.from([t.hasEvenY()?2:3]),a):o(Uint8Array.from([4]),a,n.toBytes(i.y))},fromBytes(e){let t=e.length,r=e[0],i=e.subarray(1);if(t===o&&(r===2||r===3)){let e=Ys(i);if(!rc(e,_l,n.ORDER))throw Error(`Point is not on curve`);let t=f(e),a;try{a=n.sqrt(t)}catch(e){let t=e instanceof Error?`: `+e.message:``;throw Error(`Point is not on curve`+t)}let o=(a&_l)===_l;return(r&1)==1!==o&&(a=n.neg(a)),{x:e,y:a}}else if(t===s&&r===4)return{x:n.fromBytes(i.subarray(0,n.BYTES)),y:n.fromBytes(i.subarray(n.BYTES,2*n.BYTES))};else{let e=o,n=s;throw Error(`invalid Point, expected length of `+e+`, or uncompressed `+n+`, got `+t)}}});function m(e){return e>r>>_l}function h(e){return m(e)?c(-e):e}let g=(e,t,n)=>Ys(e.slice(t,n));class _{constructor(e,t,n){ic(`r`,e,_l,r),ic(`s`,t,_l,r),this.r=e,this.s=t,n!=null&&(this.recovery=n),Object.freeze(this)}static fromCompact(e){let t=i;return e=$s(`compactSignature`,e,t*2),new _(g(e,0,t),g(e,t,2*t))}static fromDER(e){let{r:t,s:n}=hl.toSig($s(`DER`,e));return new _(t,n)}assertValidity(){}addRecoveryBit(e){return new _(this.r,this.s,e)}recoverPublicKey(e){let{r,s:i,recovery:a}=this,o=ee($s(`msgHash`,e));if(a==null||![0,1,2,3].includes(a))throw Error(`recovery id invalid`);let s=a===2||a===3?r+t.n:r;if(s>=n.ORDER)throw Error(`recovery id 2 or 3 invalid`);let d=a&1?`03`:`02`,f=u.fromHex(d+cl(s,n.BYTES)),p=l(s),m=c(-o*p),h=c(i*p),g=u.BASE.multiplyAndAddUnsafe(f,m,h);if(!g)throw Error(`point at infinify`);return g.assertValidity(),g}hasHighS(){return m(this.s)}normalizeS(){return this.hasHighS()?new _(this.r,c(-this.s),this.recovery):this}toDERRawBytes(){return Js(this.toDERHex())}toDERHex(){return hl.hexFromSig(this)}toCompactRawBytes(){return Js(this.toCompactHex())}toCompactHex(){let e=i;return cl(this.r,e)+cl(this.s,e)}}let v={isValidPrivateKey(e){try{return d(e),!0}catch{return!1}},normPrivateKeyToScalar:d,randomPrivateKey:()=>{let e=Pc(t.n);return Fc(t.randomBytes(e),t.n)},precompute(e=8,t=u.BASE){return t._setWindowSize(e),t.multiply(BigInt(3)),t}};function y(e,t=!0){return u.fromPrivateKey(e).toRawBytes(t)}function b(e){if(typeof e==`bigint`)return!1;if(e instanceof u)return!0;let r=$s(`key`,e).length,a=n.BYTES,o=a+1,s=2*a+1;if(!(t.allowedPrivateKeyLengths||i===o))return r===o||r===s}function x(e,t,n=!0){if(b(e)===!0)throw Error(`first arg must be private key`);if(b(t)===!1)throw Error(`second arg must be public key`);return u.fromHex(t).multiply(d(e)).toRawBytes(n)}let S=t.bits2int||function(e){if(e.length>8192)throw Error(`input is too large`);let t=Ys(e),n=e.length*8-a;return n>0?t>>BigInt(n):t},ee=t.bits2int_modN||function(e){return c(S(e))},C=hc(a);function te(e){return ic(`num < 2^`+a,e,gl,C),Zs(e,i)}function ne(e,r,i=re){if([`recovered`,`canonical`].some(e=>e in i))throw Error(`sign() legacy options not supported`);let{hash:a,randomBytes:o}=t,{lowS:s,prehash:f,extraEntropy:g}=i;s??=!0,e=$s(`msgHash`,e),ol(i),f&&(e=$s(`prehashed msgHash`,a(e)));let v=ee(e),y=d(r),b=[te(y),te(v)];if(g!=null&&g!==!1){let e=g===!0?o(n.BYTES):g;b.push($s(`extraEntropy`,e))}let x=ec(...b),C=v;function ne(e){let t=S(e);if(!p(t))return;let n=l(t),r=u.BASE.multiply(t).toAffine(),i=c(r.x);if(i===gl)return;let a=c(n*c(C+i*y));if(a===gl)return;let o=(r.x===i?0:2)|Number(r.y&_l),d=a;return s&&m(a)&&(d=h(a),o^=1),new _(i,d,o)}return{seed:x,k2sig:ne}}let re={lowS:t.lowS,prehash:!1},ie={lowS:t.lowS,prehash:!1};function ae(e,n,r=re){let{seed:i,k2sig:a}=ne(e,n,r),o=t;return oc(o.hash.outputLen,o.nByteLength,o.hmac)(i,a)}u.BASE._setWindowSize(8);function oe(e,n,r,i=ie){let a=e;n=$s(`msgHash`,n),r=$s(`publicKey`,r);let{lowS:o,prehash:s,format:d}=i;if(ol(i),`strict`in i)throw Error(`options.strict was renamed to lowS`);if(d!==void 0&&d!==`compact`&&d!==`der`)throw Error(`format must be compact or der`);let f=typeof a==`string`||Vs(a),p=!f&&!d&&typeof a==`object`&&!!a&&typeof a.r==`bigint`&&typeof a.s==`bigint`;if(!f&&!p)throw Error(`invalid signature, expected Uint8Array, hex string or Signature instance`);let m,h;try{if(p&&(m=new _(a.r,a.s)),f){try{d!==`compact`&&(m=_.fromDER(a))}catch(e){if(!(e instanceof hl.Err))throw e}!m&&d!==`der`&&(m=_.fromCompact(a))}h=u.fromHex(r)}catch{return!1}if(!m||o&&m.hasHighS())return!1;s&&(n=t.hash(n));let{r:g,s:v}=m,y=ee(n),b=l(v),x=c(y*b),S=c(g*b),C=u.BASE.multiplyAndAddUnsafe(h,x,S)?.toAffine();return C?c(C.x)===g:!1}return{CURVE:t,getPublicKey:y,getSharedSecret:x,sign:ae,verify:oe,ProjectivePoint:u,Signature:_,utils:v}}function fl(e,t){let n=e.ORDER,r=gl;for(let e=n-_l;e%vl===gl;e/=vl)r+=_l;let i=r,a=vl<{let r=d,a=e.pow(n,l),o=e.sqr(a);o=e.mul(o,n);let s=e.mul(t,o);s=e.pow(s,c),s=e.mul(s,a),a=e.mul(s,n),o=e.mul(s,t);let p=e.mul(o,a);s=e.pow(p,u);let m=e.eql(s,e.ONE);a=e.mul(o,f),s=e.mul(p,r),o=e.cmov(a,o,m),p=e.cmov(s,p,m);for(let t=i;t>_l;t--){let n=t-vl;n=vl<{let a=e.sqr(i),o=e.mul(t,i);a=e.mul(a,o);let s=e.pow(a,n);s=e.mul(s,o);let c=e.mul(s,r),l=e.mul(e.sqr(s),i),u=e.eql(l,t);return{isValid:u,value:e.cmov(c,s,u)}}}return p}function pl(e,t){if(Dc(e),!e.isValid(t.A)||!e.isValid(t.B)||!e.isValid(t.Z))throw Error(`mapToCurveSimpleSWU: invalid opts`);let n=fl(e,t.Z);if(!e.isOdd)throw Error(`Fp.isOdd is not implemented!`);return r=>{let i,a,o,s,c,l,u,d;i=e.sqr(r),i=e.mul(i,t.Z),a=e.sqr(i),a=e.add(a,i),o=e.add(a,e.ONE),o=e.mul(o,t.B),s=e.cmov(t.Z,e.neg(a),!e.eql(a,e.ZERO)),s=e.mul(s,t.A),a=e.sqr(o),l=e.sqr(s),c=e.mul(l,t.A),a=e.add(a,c),a=e.mul(a,o),l=e.mul(l,s),c=e.mul(l,t.B),a=e.add(a,c),u=e.mul(i,o);let{isValid:f,value:p}=n(a,l);d=e.mul(i,r),d=e.mul(d,p),u=e.cmov(u,o,f),d=e.cmov(d,p,f);let m=e.isOdd(r)===e.isOdd(d);d=e.cmov(e.neg(d),d,m);let h=kc(e,[s],!0)[0];return u=e.mul(u,h),{x:u,y:d}}}var ml,hl,gl,_l,vl,yl,bl,xl=o((()=>{al(),Wc(),yc(),ml=class extends Error{constructor(e=``){super(e)}},hl={Err:ml,_tlv:{encode:(e,t)=>{let{Err:n}=hl;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length&1)throw new n(`tlv.encode: unpadded data`);let r=t.length/2,i=Ws(r);if(i.length/2&128)throw new n(`tlv.encode: long form length too big`);let a=r>127?Ws(i.length/2|128):``;return Ws(e)+a+i+t},decode(e,t){let{Err:n}=hl,r=0;if(e<0||e>256)throw new n(`tlv.encode: wrong tag`);if(t.length<2||t[r++]!==e)throw new n(`tlv.decode: wrong tlv`);let i=t[r++],a=!!(i&128),o=0;if(!a)o=i;else{let e=i&127;if(!e)throw new n(`tlv.decode(long): indefinite length not supported`);if(e>4)throw new n(`tlv.decode(long): byte length is too big`);let a=t.subarray(r,r+e);if(a.length!==e)throw new n(`tlv.decode: length bytes not complete`);if(a[0]===0)throw new n(`tlv.decode(long): zero leftmost byte`);for(let e of a)o=o<<8|e;if(r+=e,o<128)throw new n(`tlv.decode(long): not minimal encoding`)}let s=t.subarray(r,r+o);if(s.length!==o)throw new n(`tlv.decode: wrong value length`);return{v:s,l:t.subarray(r+o)}}},_int:{encode(e){let{Err:t}=hl;if(ezs(e,t,Or(...n)),randomBytes:jr}}function Cl(e,t){let n=t=>dl({...e,...Sl(t)});return{...n(t),create:n}}var wl=o((()=>{Bs(),Fr(),xl()}));function Tl(e,t){if(Dl(e),Dl(t),e<0||e>=1<<8*t)throw Error(`invalid I2OSP input: `+e);let n=Array.from({length:t}).fill(0);for(let r=t-1;r>=0;r--)n[r]=e&255,e>>>=8;return new Uint8Array(n)}function El(e,t){let n=new Uint8Array(e.length);for(let r=0;r255&&(t=r(ec(nc(`H2C-OVERSIZE-DST-`),t)));let{outputLen:i,blockLen:a}=r,o=Math.ceil(n/i);if(n>65535||o>255)throw Error(`expand_message_xmd: invalid lenInBytes`);let s=ec(t,Tl(t.length,1)),c=Tl(0,a),l=Tl(n,2),u=Array(o),d=r(ec(c,e,l,Tl(0,1),s));u[0]=r(ec(d,Tl(1,1),s));for(let e=1;e<=o;e++)u[e]=r(ec(El(d,u[e-1]),Tl(e+1,1),s));return ec(...u).slice(0,n)}function kl(e,t,n,r,i){if(Hs(e),Hs(t),Dl(n),t.length>255){let e=Math.ceil(2*r/8);t=i.create({dkLen:e}).update(nc(`H2C-OVERSIZE-DST-`)).update(t).digest()}if(n>65535||t.length>255)throw Error(`expand_message_xof: invalid lenInBytes`);return i.create({dkLen:n}).update(e).update(Tl(n,2)).update(t).update(Tl(t.length,1)).digest()}function Al(e,t,n){sc(n,{DST:`stringOrUint8Array`,p:`bigint`,m:`isSafeInteger`,k:`isSafeInteger`,hash:`hash`});let{p:r,k:i,m:a,hash:o,expand:s,DST:c}=n;Hs(e),Dl(t);let l=typeof c==`string`?nc(c):c,u=r.toString(2).length,d=Math.ceil((u+i)/8),f=t*a*d,p;if(s===`xmd`)p=Ol(e,l,f,o);else if(s===`xof`)p=kl(e,l,f,i,o);else if(s===`_internal_pass`)p=e;else throw Error(`expand must be "xmd" or "xof"`);let m=Array(t);for(let e=0;eArray.from(e).reverse());return(t,r)=>{let[i,a,o,s]=n.map(n=>n.reduce((n,r)=>e.add(e.mul(n,t),r))),[c,l]=kc(e,[a,s],!0);return t=e.mul(i,c),r=e.mul(r,e.mul(o,l)),{x:t,y:r}}}function Ml(e,t,n){if(typeof t!=`function`)throw Error(`mapToCurve() must be defined`);function r(n){return e.fromAffine(t(n))}function i(t){let n=t.clearCofactor();return n.equals(e.ZERO)?e.ZERO:(n.assertValidity(),n)}return{defaults:n,hashToCurve(e,t){let a=Al(e,2,{...n,DST:n.DST,...t}),o=r(a[0]),s=r(a[1]);return i(o.add(s))},encodeToCurve(e,t){return i(r(Al(e,1,{...n,DST:n.encodeDST,...t})[0]))},mapToCurve(e){if(!Array.isArray(e))throw Error(`expected array of bigints`);for(let t of e)if(typeof t!=`bigint`)throw Error(`expected array of bigints`);return i(r(e))}}}var Nl,Pl=o((()=>{Wc(),yc(),Nl=Ys})),Fl=c({encodeToCurve:()=>lu,hashToCurve:()=>cu,schnorr:()=>iu,secp256k1:()=>Zl,secp256k1_hasher:()=>su});function Il(e){let t=Wl,n=BigInt(3),r=BigInt(6),i=BigInt(11),a=BigInt(22),o=BigInt(23),s=BigInt(44),c=BigInt(88),l=e*e*e%t,u=l*l*e%t,d=xc(xc(xc(u,n,t)*u%t,n,t)*u%t,Jl,t)*l%t,f=xc(d,i,t)*d%t,p=xc(f,a,t)*f%t,m=xc(p,s,t)*p%t,h=xc(xc(xc(xc(xc(xc(m,c,t)*m%t,s,t)*p%t,n,t)*u%t,o,t)*f%t,r,t)*l%t,Jl,t);if(!Xl.eql(Xl.sqr(h),e))throw Error(`Cannot find square root`);return h}function Ll(e,...t){let n=Ql[e];if(n===void 0){let t=Ps(Uint8Array.from(e,e=>e.charCodeAt(0)));n=ec(t,t),Ql[e]=n}return Ps(ec(n,...t))}function Rl(e){let t=Zl.utils.normPrivateKeyToScalar(e),n=M.fromPrivateKey(t);return{scalar:n.hasEvenY()?t:j(-t),bytes:$l(n)}}function zl(e){ic(`x`,e,ql,Wl);let t=Il(tu(tu(e*e)*e+BigInt(7)));t%Jl!==Kl&&(t=tu(-t));let n=new M(e,t,ql);return n.assertValidity(),n}function Bl(...e){return j(ru(Ll(`BIP0340/challenge`,...e)))}function Vl(e){return Rl(e).bytes}function Hl(e,t,n=jr(32)){let r=$s(`message`,e),{bytes:i,scalar:a}=Rl(t),o=j(ru(Ll(`BIP0340/nonce`,eu(a^ru(Ll(`BIP0340/aux`,$s(`auxRand`,n,32)))),i,r)));if(o===Kl)throw Error(`sign failed: k is zero`);let{bytes:s,scalar:c}=Rl(o),l=Bl(s,i,r),u=new Uint8Array(64);if(u.set(s,0),u.set(eu(j(c+l*a)),32),!Ul(u,r,i))throw Error(`sign: Invalid signature produced`);return u}function Ul(e,t,n){let r=$s(`signature`,e,64),i=$s(`message`,t),a=$s(`publicKey`,n,32);try{let e=zl(ru(a)),t=ru(r.subarray(0,32));if(!rc(t,ql,Wl))return!1;let n=ru(r.subarray(32,64));if(!rc(n,ql,Gl))return!1;let o=nu(e,n,j(-Bl(eu(t),$l(e),i)));return!(!o||!o.hasEvenY()||o.toAffine().x!==t)}catch{return!1}}var Wl,Gl,Kl,ql,Jl,Yl,Xl,Zl,Ql,$l,eu,tu,j,M,nu,ru,iu,au,ou,su,cu,lu,uu=o((()=>{Ls(),Fr(),wl(),Pl(),Wc(),yc(),xl(),Wl=BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f`),Gl=BigInt(`0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141`),Kl=BigInt(0),ql=BigInt(1),Jl=BigInt(2),Yl=(e,t)=>(e+t/Jl)/t,Xl=Mc(Wl,void 0,void 0,{sqrt:Il}),Zl=Cl({a:Kl,b:BigInt(7),Fp:Xl,n:Gl,Gx:BigInt(`55066263022277343669578718895168534326250603453777594175500187360389116729240`),Gy:BigInt(`32670510020758816978083085130507043184471273380659243275938904335757337482424`),h:BigInt(1),lowS:!0,endo:{beta:BigInt(`0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee`),splitScalar:e=>{let t=Gl,n=BigInt(`0x3086d221a7d46bcde86c90e49284eb15`),r=-ql*BigInt(`0xe4437ed6010e88286f547fa90abfe4c3`),i=BigInt(`0x114ca50f7a8e2f3f657c1108d9d44cfd8`),a=n,o=BigInt(`0x100000000000000000000000000000000`),s=Yl(a*e,t),c=Yl(-r*e,t),l=bc(e-s*n-c*i,t),u=bc(-s*r-c*a,t),d=l>o,f=u>o;if(d&&(l=t-l),f&&(u=t-u),l>o||u>o)throw Error(`splitScalar: Endomorphism failed, k=`+e);return{k1neg:d,k1:l,k2neg:f,k2:u}}}},Ps),Ql={},$l=e=>e.toRawBytes(!0).slice(1),eu=e=>Zs(e,32),tu=e=>bc(e,Wl),j=e=>bc(e,Gl),M=Zl.ProjectivePoint,nu=(e,t,n)=>M.BASE.multiplyAndAddUnsafe(e,t,n),ru=Ys,iu={getPublicKey:Vl,sign:Hl,verify:Ul,utils:{randomPrivateKey:Zl.utils.randomPrivateKey,lift_x:zl,pointToBytes:$l,numberToBytesBE:Zs,bytesToNumberBE:Ys,taggedHash:Ll,mod:bc}},au=jl(Xl,[[`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7`,`0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581`,`0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262`,`0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c`],[`0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b`,`0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14`,`0x0000000000000000000000000000000000000000000000000000000000000001`],[`0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c`,`0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3`,`0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931`,`0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84`],[`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b`,`0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573`,`0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f`,`0x0000000000000000000000000000000000000000000000000000000000000001`]].map(e=>e.map(e=>BigInt(e)))),ou=pl(Xl,{A:BigInt(`0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533`),B:BigInt(`1771`),Z:Xl.create(BigInt(`-11`))}),su=Ml(Zl.ProjectivePoint,e=>{let{x:t,y:n}=ou(Xl.create(e[0]));return au(t,n)},{DST:`secp256k1_XMD:SHA-256_SSWU_RO_`,encodeDST:`secp256k1_XMD:SHA-256_SSWU_NU_`,p:Xl.ORDER,m:1,k:128,expand:`xmd`,hash:Ps}),cu=su.hashToCurve,lu=su.encodeToCurve}));Ft(),Lt(),On(),k(),hs();async function du({hash:e,signature:t}){let n=Pt(e)?e:kn(e),{secp256k1:r}=await ms(async()=>{let{secp256k1:e}=await Promise.resolve().then(()=>(uu(),Fl));return{secp256k1:e}},void 0);return`0x${(()=>{if(typeof t==`object`&&`r`in t&&`s`in t){let{r:e,s:n,v:i,yParity:a}=t,o=fu(Number(a??i));return new r.Signature(Tn(e),Tn(n)).addRecoveryBit(o)}let e=Pt(t)?t:kn(t);if(It(e)!==65)throw Error(`invalid signature length`);let n=fu(Dn(`0x${e.slice(130)}`));return r.Signature.fromCompact(e.substring(2,130)).addRecoveryBit(n)})().recoverPublicKey(n.substring(2)).toHex(!1)}`}function fu(e){if(e===0||e===1)return e;if(e===27)return 0;if(e===28)return 1;throw Error(`Invalid yParityOrV value`)}async function pu({hash:e,signature:t}){return us(await du({hash:e,signature:t}))}D(),Sa(),Un(),k();function mu(e,t=`hex`){let n=hu(e),r=ba(new Uint8Array(n.length));return n.encode(r),t===`hex`?jn(r.bytes):r.bytes}function hu(e){return Array.isArray(e)?gu(e.map(e=>hu(e))):_u(e)}function gu(e){let t=e.reduce((e,t)=>e+t.length,0),n=vu(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function _u(e){let t=typeof e==`string`?Rn(e):e,n=vu(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function vu(e){if(e<2**8)return 1;if(e<2**16)return 2;if(e<2**24)return 3;if(e<2**32)return 4;throw new E(`Length is too large.`)}Ai(),Un(),k(),ri();function yu(e){let{chainId:t,nonce:n,to:r}=e,i=e.contractAddress??e.address,a=ni(ki([`0x05`,mu([t?O(t):`0x`,i,n?O(n):`0x`])]));return r===`bytes`?Rn(a):a}async function bu(e){let{authorization:t,signature:n}=e;return pu({hash:yu(t),signature:n??t})}$a(),to(),D(),_o();var xu=class extends E{constructor(e,{account:t,docsPath:n,chain:r,data:i,gas:a,gasPrice:o,maxFeePerGas:s,maxPriorityFeePerGas:c,nonce:l,to:u,value:d}){let f=so({from:t?.address,to:u,value:d!==void 0&&`${Qa(d)} ${r?.nativeCurrency?.symbol||`ETH`}`,data:i,gas:a,gasPrice:o!==void 0&&`${eo(o)} gwei`,maxFeePerGas:s!==void 0&&`${eo(s)} gwei`,maxPriorityFeePerGas:c!==void 0&&`${eo(c)} gwei`,nonce:l});super(e.shortMessage,{cause:e,docsPath:n,metaMessages:[...e.metaMessages?[...e.metaMessages,` `]:[],`Estimate Gas Arguments:`,f].filter(Boolean),name:`EstimateGasExecutionError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=e}},Su,Cu,wu,Tu,Eu,Du,Ou,ku,Au,ju,Mu,Nu,Pu=o((()=>{to(),D(),Su=class extends E{constructor({cause:e,message:t}={}){let n=t?.replace(`execution reverted: `,``)?.replace(`execution reverted`,``);super(`Execution reverted ${n?`with reason: ${n}`:`for an unknown reason`}.`,{cause:e,name:`ExecutionRevertedError`})}},Object.defineProperty(Su,`code`,{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(Su,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/execution reverted|gas required exceeds allowance/}),Cu=class extends E{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${eo(t)} gwei`:``}) cannot be higher than the maximum allowed value (2^256-1).`,{cause:e,name:`FeeCapTooHighError`})}},Object.defineProperty(Cu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas higher than 2\^256-1|fee cap higher than 2\^256-1/}),wu=class extends E{constructor({cause:e,maxFeePerGas:t}={}){super(`The fee cap (\`maxFeePerGas\`${t?` = ${eo(t)}`:``} gwei) cannot be lower than the block base fee.`,{cause:e,name:`FeeCapTooLowError`})}},Object.defineProperty(wu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas less than block base fee|fee cap less than block base fee|transaction is outdated/}),Tu=class extends E{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}is higher than the next one expected.`,{cause:e,name:`NonceTooHighError`})}},Object.defineProperty(Tu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too high/}),Eu=class extends E{constructor({cause:e,nonce:t}={}){super([`Nonce provided for the transaction ${t?`(${t}) `:``}is lower than the current nonce of the account.`,"Try increasing the nonce or find the latest nonce with `getTransactionCount`."].join(` -`),{cause:e,name:`NonceTooLowError`})}},Object.defineProperty(Eu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce too low|transaction already imported|already known/}),Du=class extends E{constructor({cause:e,nonce:t}={}){super(`Nonce provided for the transaction ${t?`(${t}) `:``}exceeds the maximum allowed nonce.`,{cause:e,name:`NonceMaxValueError`})}},Object.defineProperty(Du,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/nonce has max value/}),Ou=class extends E{constructor({cause:e}={}){super([`The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account.`].join(` -`),{cause:e,metaMessages:[`This error could arise when the account does not have enough funds to:`,` - pay for the total gas fee,`,` - pay for the value to send.`,` `,"The cost of the transaction is calculated as `gas * gas fee + value`, where:"," - `gas` is the amount of gas needed for transaction to execute,"," - `gas fee` is the gas fee,"," - `value` is the amount of ether to send to the recipient."],name:`InsufficientFundsError`})}},Object.defineProperty(Ou,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/insufficient funds|exceeds transaction sender account balance/}),ku=class extends E{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction exceeds the limit allowed for the block.`,{cause:e,name:`IntrinsicGasTooHighError`})}},Object.defineProperty(ku,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too high|gas limit reached/}),Au=class extends E{constructor({cause:e,gas:t}={}){super(`The amount of gas ${t?`(${t}) `:``}provided for the transaction is too low.`,{cause:e,name:`IntrinsicGasTooLowError`})}},Object.defineProperty(Au,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too low/}),ju=class extends E{constructor({cause:e}){super(`The transaction type is not supported for this chain.`,{cause:e,name:`TransactionTypeNotSupportedError`})}},Object.defineProperty(ju,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/transaction type not valid/}),Mu=class extends E{constructor({cause:e,maxPriorityFeePerGas:t,maxFeePerGas:n}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${t?` = ${eo(t)} gwei`:``}) cannot be higher than the fee cap (\`maxFeePerGas\`${n?` = ${eo(n)} gwei`:``}).`].join(` -`),{cause:e,name:`TipAboveFeeCapError`})}},Object.defineProperty(Mu,`nodeMessage`,{enumerable:!0,configurable:!0,writable:!0,value:/max priority fee per gas higher than max fee per gas|tip higher than fee cap/}),Nu=class extends E{constructor({cause:e}){super(`An error occurred while executing: ${e?.shortMessage}`,{cause:e,name:`UnknownNodeError`})}}}));function Fu(e,t){let n=(e.details||``).toLowerCase(),r=e instanceof E?e.walk(e=>e?.code===Su.code):e;return r instanceof E?new Su({cause:e,message:r.details}):Su.nodeMessage.test(n)?new Su({cause:e,message:e.details}):Cu.nodeMessage.test(n)?new Cu({cause:e,maxFeePerGas:t?.maxFeePerGas}):wu.nodeMessage.test(n)?new wu({cause:e,maxFeePerGas:t?.maxFeePerGas}):Tu.nodeMessage.test(n)?new Tu({cause:e,nonce:t?.nonce}):Eu.nodeMessage.test(n)?new Eu({cause:e,nonce:t?.nonce}):Du.nodeMessage.test(n)?new Du({cause:e,nonce:t?.nonce}):Ou.nodeMessage.test(n)?new Ou({cause:e}):ku.nodeMessage.test(n)?new ku({cause:e,gas:t?.gas}):Au.nodeMessage.test(n)?new Au({cause:e,gas:t?.gas}):ju.nodeMessage.test(n)?new ju({cause:e}):Mu.nodeMessage.test(n)?new Mu({cause:e,maxFeePerGas:t?.maxFeePerGas,maxPriorityFeePerGas:t?.maxPriorityFeePerGas}):new Nu({cause:e})}var Iu=o((()=>{D(),Pu()}));Pu(),Iu();function Lu(e,{docsPath:t,...n}){return new xu((()=>{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}function Ru(e,{format:t}){if(!t)return{};let n={};function r(t){let i=Object.keys(t);for(let a of i)a in e&&(n[a]=e[a]),t[a]&&typeof t[a]==`object`&&!Array.isArray(t[a])&&r(t[a])}return r(t(e||{})),n}var zu=o((()=>{}));function Bu(e,t){return({exclude:n,format:r})=>({exclude:n,format:(e,i)=>{let a=t(e,i);if(n)for(let e of n)delete a[e];return{...a,...r(e,i)}},type:e})}var Vu=o((()=>{}));function Hu(e,t){let n={};return e.authorizationList!==void 0&&(n.authorizationList=Uu(e.authorizationList)),e.accessList!==void 0&&(n.accessList=e.accessList),e.blobVersionedHashes!==void 0&&(n.blobVersionedHashes=e.blobVersionedHashes),e.blobs!==void 0&&(typeof e.blobs[0]==`string`?n.blobs=e.blobs:n.blobs=e.blobs.map(e=>jn(e))),e.data!==void 0&&(n.data=e.data),e.account&&(n.from=e.account.address),e.from!==void 0&&(n.from=e.from),e.gas!==void 0&&(n.gas=O(e.gas)),e.gasPrice!==void 0&&(n.gasPrice=O(e.gasPrice)),e.maxFeePerBlobGas!==void 0&&(n.maxFeePerBlobGas=O(e.maxFeePerBlobGas)),e.maxFeePerGas!==void 0&&(n.maxFeePerGas=O(e.maxFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(n.maxPriorityFeePerGas=O(e.maxPriorityFeePerGas)),e.nonce!==void 0&&(n.nonce=O(e.nonce)),e.to!==void 0&&(n.to=e.to),e.type!==void 0&&(n.type=Wu[e.type]),e.value!==void 0&&(n.value=O(e.value)),n}function Uu(e){return e.map(e=>({address:e.address,r:e.r?O(BigInt(e.r)):e.r,s:e.s?O(BigInt(e.s)):e.s,chainId:O(e.chainId),nonce:O(e.nonce),...e.yParity===void 0?{}:{yParity:O(e.yParity)},...e.v!==void 0&&e.yParity===void 0?{v:O(e.v)}:{}}))}var Wu,Gu,Ku=o((()=>{k(),Vu(),Wu={legacy:`0x0`,eip2930:`0x1`,eip1559:`0x2`,eip4844:`0x3`,eip7702:`0x4`},Gu=Bu(`transactionRequest`,Hu)}));function qu(e){if(!(!e||e.length===0))return e.reduce((e,{slot:t,value:n})=>{if(t.length!==66)throw new dn({size:t.length,targetSize:66,type:`hex`});if(n.length!==66)throw new dn({size:n.length,targetSize:66,type:`hex`});return e[t]=n,e},{})}function Ju(e){let{balance:t,nonce:n,state:r,stateDiff:i,code:a}=e,o={};if(a!==void 0&&(o.code=a),t!==void 0&&(o.balance=O(t)),n!==void 0&&(o.nonce=O(n)),r!==void 0&&(o.state=qu(r)),i!==void 0){if(o.state)throw new ao;o.stateDiff=qu(i)}return o}function Yu(e){if(!e)return;let t={};for(let{address:n,...r}of e){if(!Ci(n,{strict:!1}))throw new hi({address:n});if(t[n])throw new io({address:n});t[n]=Ju(r)}return t}var Xu=o((()=>{gi(),fn(),oo(),Ei(),k()})),Zu,Qu,$u=o((()=>{2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n)),Zu=2n**16n-1n,Qu=2n**256n-1n}));function ed(e){let{account:t,maxFeePerGas:n,maxPriorityFeePerGas:r,to:i}=e,a=t?aa(t):void 0;if(a&&!Ci(a.address))throw new hi({address:a.address});if(i&&!Ci(i))throw new hi({address:i});if(n&&n>Qu)throw new Cu({maxFeePerGas:n});if(r&&n&&r>n)throw new Mu({maxFeePerGas:n,maxPriorityFeePerGas:r})}var td=o((()=>{oa(),$u(),gi(),Pu(),Ei()}));to(),D();var nd=class extends E{constructor(){super("`baseFeeMultiplier` must be greater than 1.",{name:`BaseFeeScalarError`})}},rd=class extends E{constructor(){super(`Chain does not support EIP-1559 fees.`,{name:`Eip1559FeesNotSupportedError`})}},id=class extends E{constructor({maxPriorityFeePerGas:e}){super(`\`maxFeePerGas\` cannot be less than the \`maxPriorityFeePerGas\` (${eo(e)} gwei).`,{name:`MaxFeePerGasTooLowError`})}};D();var ad=class extends E{constructor({blockHash:e,blockNumber:t}){let n=`Block`;e&&(n=`Block at hash "${e}"`),t&&(n=`Block at number "${t}"`),super(`${n} could not be found.`,{name:`BlockNotFoundError`})}};On(),Vu();var od={"0x0":`legacy`,"0x1":`eip2930`,"0x2":`eip1559`,"0x3":`eip4844`,"0x4":`eip7702`};function sd(e,t){let n={...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,chainId:e.chainId?Dn(e.chainId):void 0,gas:e.gas?BigInt(e.gas):void 0,gasPrice:e.gasPrice?BigInt(e.gasPrice):void 0,maxFeePerBlobGas:e.maxFeePerBlobGas?BigInt(e.maxFeePerBlobGas):void 0,maxFeePerGas:e.maxFeePerGas?BigInt(e.maxFeePerGas):void 0,maxPriorityFeePerGas:e.maxPriorityFeePerGas?BigInt(e.maxPriorityFeePerGas):void 0,nonce:e.nonce?Dn(e.nonce):void 0,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,type:e.type?od[e.type]:void 0,typeHex:e.type?e.type:void 0,value:e.value?BigInt(e.value):void 0,v:e.v?BigInt(e.v):void 0};return e.authorizationList&&(n.authorizationList=ld(e.authorizationList)),n.yParity=(()=>{if(e.yParity)return Number(e.yParity);if(typeof n.v==`bigint`){if(n.v===0n||n.v===27n)return 0;if(n.v===1n||n.v===28n)return 1;if(n.v>=35n)return n.v%2n==0n?1:0}})(),n.type===`legacy`&&(delete n.accessList,delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas,delete n.yParity),n.type===`eip2930`&&(delete n.maxFeePerBlobGas,delete n.maxFeePerGas,delete n.maxPriorityFeePerGas),n.type===`eip1559`&&delete n.maxFeePerBlobGas,n}var cd=Bu(`transaction`,sd);function ld(e){return e.map(e=>({address:e.address,chainId:Number(e.chainId),nonce:Number(e.nonce),r:e.r,s:e.s,yParity:Number(e.yParity)}))}Vu();function ud(e,t){let n=(e.transactions??[]).map(e=>typeof e==`string`?e:sd(e));return{...e,baseFeePerGas:e.baseFeePerGas?BigInt(e.baseFeePerGas):null,blobGasUsed:e.blobGasUsed?BigInt(e.blobGasUsed):void 0,difficulty:e.difficulty?BigInt(e.difficulty):void 0,excessBlobGas:e.excessBlobGas?BigInt(e.excessBlobGas):void 0,gasLimit:e.gasLimit?BigInt(e.gasLimit):void 0,gasUsed:e.gasUsed?BigInt(e.gasUsed):void 0,hash:e.hash?e.hash:null,logsBloom:e.logsBloom?e.logsBloom:null,nonce:e.nonce?e.nonce:null,number:e.number?BigInt(e.number):null,size:e.size?BigInt(e.size):void 0,timestamp:e.timestamp?BigInt(e.timestamp):void 0,transactions:n,totalDifficulty:e.totalDifficulty?BigInt(e.totalDifficulty):null}}var dd=Bu(`block`,ud);k();async function fd(e,{blockHash:t,blockNumber:n,blockTag:r=e.experimental_blockTag??`latest`,includeTransactions:i}={}){let a=i??!1,o=n===void 0?void 0:O(n),s=null;if(s=t?await e.request({method:`eth_getBlockByHash`,params:[t,a]},{dedupe:!0}):await e.request({method:`eth_getBlockByNumber`,params:[o||r,a]},{dedupe:!!o}),!s)throw new ad({blockHash:t,blockNumber:n});return(e.chain?.formatters?.block?.format||ud)(s,`getBlock`)}async function pd(e){let t=await e.request({method:`eth_gasPrice`});return BigInt(t)}On();async function md(e,t){let{block:n,chain:r=e.chain,request:i}=t||{};try{let t=r?.fees?.maxPriorityFeePerGas??r?.fees?.defaultPriorityFee;if(typeof t==`function`){let r=await t({block:n||await T(e,fd,`getBlock`)({}),client:e,request:i});if(r===null)throw Error();return r}return t===void 0?Tn(await e.request({method:`eth_maxPriorityFeePerGas`})):t}catch{let[t,r]=await Promise.all([n?Promise.resolve(n):T(e,fd,`getBlock`)({}),T(e,pd,`getGasPrice`)({})]);if(typeof t.baseFeePerGas!=`bigint`)throw new rd;let i=r-t.baseFeePerGas;return i<0n?0n:i}}async function hd(e,t){let{block:n,chain:r=e.chain,request:i,type:a=`eip1559`}=t||{},o=await(async()=>typeof r?.fees?.baseFeeMultiplier==`function`?r.fees.baseFeeMultiplier({block:n,client:e,request:i}):r?.fees?.baseFeeMultiplier??1.2)();if(o<1)throw new nd;let s=10**(o.toString().split(`.`)[1]?.length??0),c=e=>e*BigInt(Math.ceil(o*s))/BigInt(s),l=n||await T(e,fd,`getBlock`)({});if(typeof r?.fees?.estimateFeesPerGas==`function`){let t=await r.fees.estimateFeesPerGas({block:n,client:e,multiply:c,request:i,type:a});if(t!==null)return t}if(a===`eip1559`){if(typeof l.baseFeePerGas!=`bigint`)throw new rd;let t=typeof i?.maxPriorityFeePerGas==`bigint`?i.maxPriorityFeePerGas:await md(e,{block:l,chain:r,request:i}),n=c(l.baseFeePerGas);return{maxFeePerGas:i?.maxFeePerGas??n+t,maxPriorityFeePerGas:t}}return{gasPrice:i?.gasPrice??c(await T(e,pd,`getGasPrice`)({}))}}On(),k();async function gd(e,{address:t,blockTag:n=`latest`,blockNumber:r}){return Dn(await e.request({method:`eth_getTransactionCount`,params:[t,typeof r==`bigint`?O(r):n]},{dedupe:!!r}))}Ku(),Un(),k();function _d(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Rn(e)):e.blobs,i=[];for(let e of r)i.push(Uint8Array.from(t.blobToKzgCommitment(e)));return n===`bytes`?i:i.map(e=>jn(e))}Un(),k();function vd(e){let{kzg:t}=e,n=e.to??(typeof e.blobs[0]==`string`?`hex`:`bytes`),r=typeof e.blobs[0]==`string`?e.blobs.map(e=>Rn(e)):e.blobs,i=typeof e.commitments[0]==`string`?e.commitments.map(e=>Rn(e)):e.commitments,a=[];for(let e=0;ejn(e))}Ls();var yd=Ps;Ft(),Un(),k();function bd(e,t){let n=t||`hex`,r=yd(Pt(e,{strict:!1})?Fn(e):e);return n===`bytes`?r:kn(r)}k();function xd(e){let{commitment:t,version:n=1}=e,r=e.to??(typeof t==`string`?`hex`:`bytes`),i=bd(t,`bytes`);return i.set([n],0),r===`bytes`?i:jn(i)}function Sd(e){let{commitments:t,version:n}=e,r=e.to??(typeof t[0]==`string`?`hex`:`bytes`),i=[];for(let e of t)i.push(xd({commitment:e,to:r,version:n}));return i}var Cd=6,wd=4096,Td=32*wd,Ed=Td*Cd-1-1*wd*Cd;D();var Dd=class extends E{constructor({maxSize:e,size:t}){super(`Blob size is too large.`,{metaMessages:[`Max: ${e} bytes`,`Given: ${t} bytes`],name:`BlobSizeTooLargeError`})}},Od=class extends E{constructor(){super(`Blob data must not be empty.`,{name:`EmptyBlobError`})}},kd=class extends E{constructor({hash:e,size:t}){super(`Versioned hash "${e}" size is invalid.`,{metaMessages:[`Expected: 32`,`Received: ${t}`],name:`InvalidVersionedHashSizeError`})}},Ad=class extends E{constructor({hash:e,version:t}){super(`Versioned hash "${e}" version is invalid.`,{metaMessages:[`Expected: 1`,`Received: ${t}`],name:`InvalidVersionedHashVersionError`})}};Sa(),Lt(),Un(),k();function jd(e){let t=e.to??(typeof e.data==`string`?`hex`:`bytes`),n=typeof e.data==`string`?Rn(e.data):e.data,r=It(n);if(!r)throw new Od;if(r>761855)throw new Dd({maxSize:Ed,size:r});let i=[],a=!0,o=0;for(;a;){let e=ba(new Uint8Array(Td)),t=0;for(;te.bytes):i.map(e=>jn(e.bytes))}function Md(e){let{data:t,kzg:n,to:r}=e,i=e.blobs??jd({data:t,to:r}),a=e.commitments??_d({blobs:i,kzg:n,to:r}),o=e.proofs??vd({blobs:i,commitments:a,kzg:n,to:r}),s=[];for(let e=0;e{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}On();async function Fd(e){return Dn(await e.request({method:`eth_chainId`},{dedupe:!0}))}oa(),zu(),td();async function Id(e,t){let{account:n=e.account,accessList:r,authorizationList:i,chain:a=e.chain,blobVersionedHashes:o,blobs:s,data:c,gas:l,gasPrice:u,maxFeePerBlobGas:d,maxFeePerGas:f,maxPriorityFeePerGas:p,nonce:m,nonceManager:h,to:g,type:_,value:v,...y}=t,b=await(async()=>{if(!n||!h||m!==void 0)return m;let t=aa(n),r=a?a.id:await T(e,Fd,`getChainId`)({});return await h.consume({address:t.address,chainId:r,client:e})})();ed(t);let x=a?.formatters?.transactionRequest?.format,S=(x||Hu)({...Ru(y,{format:x}),account:n?aa(n):void 0,accessList:r,authorizationList:i,blobs:s,blobVersionedHashes:o,data:c,gas:l,gasPrice:u,maxFeePerBlobGas:d,maxFeePerGas:f,maxPriorityFeePerGas:p,nonce:b,to:g,type:_,value:v},`fillTransaction`);try{let n=await e.request({method:`eth_fillTransaction`,params:[S]}),r=(a?.formatters?.transaction?.format||sd)(n.tx);delete r.blockHash,delete r.blockNumber,delete r.r,delete r.s,delete r.transactionIndex,delete r.v,delete r.yParity,r.data=r.input,r.gas&&=t.gas??r.gas,r.gasPrice&&=t.gasPrice??r.gasPrice,r.maxFeePerBlobGas&&=t.maxFeePerBlobGas??r.maxFeePerBlobGas,r.maxFeePerGas&&=t.maxFeePerGas??r.maxFeePerGas,r.maxPriorityFeePerGas&&=t.maxPriorityFeePerGas??r.maxPriorityFeePerGas,r.nonce&&=t.nonce??r.nonce;let i=await(async()=>{if(typeof a?.fees?.baseFeeMultiplier==`function`){let n=await T(e,fd,`getBlock`)({});return a.fees.baseFeeMultiplier({block:n,client:e,request:t})}return a?.fees?.baseFeeMultiplier??1.2})();if(i<1)throw new nd;let o=10**(i.toString().split(`.`)[1]?.length??0),s=e=>e*BigInt(Math.ceil(i*o))/BigInt(o);return r.maxFeePerGas&&!t.maxFeePerGas&&(r.maxFeePerGas=s(r.maxFeePerGas)),r.gasPrice&&!t.gasPrice&&(r.gasPrice=s(r.gasPrice)),{raw:n.raw,transaction:{from:S.from,...r}}}catch(n){throw Pd(n,{...t,chain:e.chain})}}oa(),vi(),td();var Ld=[`blobVersionedHashes`,`chainId`,`fees`,`gas`,`nonce`,`type`],Rd=new Map,N=new _i(128);async function zd(e,t){let n=t;n.account??=e.account,n.parameters??=Ld;let{account:r,chain:i=e.chain,nonceManager:a,parameters:o}=n,s=(()=>{if(typeof i?.prepareTransactionRequest==`function`)return{fn:i.prepareTransactionRequest,runAt:[`beforeFillTransaction`]};if(Array.isArray(i?.prepareTransactionRequest))return{fn:i.prepareTransactionRequest[0],runAt:i.prepareTransactionRequest[1].runAt}})(),c;async function l(){return c||(n.chainId===void 0?i?i.id:(c=await T(e,Fd,`getChainId`)({}),c):n.chainId)}let u=r&&aa(r),d=n.nonce;if(o.includes(`nonce`)&&d===void 0&&u&&a){let t=await l();d=await a.consume({address:u.address,chainId:t,client:e})}s?.fn&&s.runAt?.includes(`beforeFillTransaction`)&&(n=await s.fn({...n,chain:i},{phase:`beforeFillTransaction`}),d??=n.nonce);let f=!((o.includes(`blobVersionedHashes`)||o.includes(`sidecars`))&&n.kzg&&n.blobs||N.get(e.uid)===!1||![`fees`,`gas`].some(e=>o.includes(e)))&&(o.includes(`chainId`)&&typeof n.chainId!=`number`||o.includes(`nonce`)&&typeof d!=`number`||o.includes(`fees`)&&typeof n.gasPrice!=`bigint`&&(typeof n.maxFeePerGas!=`bigint`||typeof n.maxPriorityFeePerGas!=`bigint`)||o.includes(`gas`)&&typeof n.gas!=`bigint`)?await T(e,Id,`fillTransaction`)({...n,nonce:d}).then(t=>{let{chainId:r,from:i,gas:a,gasPrice:o,nonce:s,maxFeePerBlobGas:c,maxFeePerGas:l,maxPriorityFeePerGas:u,type:d,...f}=t.transaction;return N.set(e.uid,!0),{...n,...i?{from:i}:{},...d&&!n.type?{type:d}:{},...r===void 0?{}:{chainId:r},...a===void 0?{}:{gas:a},...o===void 0?{}:{gasPrice:o},...s===void 0?{}:{nonce:s},...c!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxFeePerBlobGas:c}:{},...l!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxFeePerGas:l}:{},...u!==void 0&&n.type!==`legacy`&&n.type!==`eip2930`?{maxPriorityFeePerGas:u}:{},...`nonceKey`in f&&f.nonceKey!==void 0?{nonceKey:f.nonceKey}:{}}}).catch(t=>{let r=t;return r.name===`TransactionExecutionError`&&r.walk?.(e=>{let t=e;return t.name===`MethodNotFoundRpcError`||t.name===`MethodNotSupportedRpcError`||t.message?.includes(`eth_fillTransaction is not available`)})&&N.set(e.uid,!1),n}):n;d??=f.nonce,n={...f,...u?{from:u?.address}:{},...d?{nonce:d}:{}};let{blobs:p,gas:m,kzg:h,type:g}=n;s?.fn&&s.runAt?.includes(`beforeFillParameters`)&&(n=await s.fn({...n,chain:i},{phase:`beforeFillParameters`}));let _;async function v(){return _||(_=await T(e,fd,`getBlock`)({blockTag:`latest`}),_)}if(o.includes(`nonce`)&&d===void 0&&u&&!a&&(n.nonce=await T(e,gd,`getTransactionCount`)({address:u.address,blockTag:`pending`})),(o.includes(`blobVersionedHashes`)||o.includes(`sidecars`))&&p&&h){let e=_d({blobs:p,kzg:h});if(o.includes(`blobVersionedHashes`)){let t=Sd({commitments:e,to:`hex`});n.blobVersionedHashes=t}if(o.includes(`sidecars`)){let t=Md({blobs:p,commitments:e,proofs:vd({blobs:p,commitments:e,kzg:h}),to:`hex`});n.sidecars=t}}if(o.includes(`chainId`)&&(n.chainId=await l()),(o.includes(`fees`)||o.includes(`type`))&&g===void 0)try{n.type=Nd(n)}catch{let t=Rd.get(e.uid);t===void 0&&(t=typeof(await v())?.baseFeePerGas==`bigint`,Rd.set(e.uid,t)),n.type=t?`eip1559`:`legacy`}if(o.includes(`fees`))if(n.type!==`legacy`&&n.type!==`eip2930`){if(n.maxFeePerGas===void 0||n.maxPriorityFeePerGas===void 0){let{maxFeePerGas:t,maxPriorityFeePerGas:r}=await hd(e,{block:await v(),chain:i,request:n});if(n.maxPriorityFeePerGas===void 0&&n.maxFeePerGas&&n.maxFeePerGas{if(Array.isArray(r))return r;if(i?.type!==`local`)return[`blobVersionedHashes`]})();try{let n=await(async()=>{if(t.to)return t.to;if(t.authorizationList&&t.authorizationList.length>0)return await bu({authorization:t.authorizationList[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`")})})(),{accessList:o,authorizationList:s,blobs:c,blobVersionedHashes:l,blockNumber:u,blockTag:d,data:f,gas:p,gasPrice:m,maxFeePerBlobGas:h,maxFeePerGas:g,maxPriorityFeePerGas:_,nonce:v,value:y,stateOverride:b,...x}=r?await zd(e,{...t,parameters:a,to:n}):t;if(p&&t.gas!==p)return p;let S=(typeof u==`bigint`?O(u):void 0)||d,ee=Yu(b);ed(t);let C=e.chain?.formatters?.transactionRequest?.format,te=(C||Hu)({...Ru(x,{format:C}),account:i,accessList:o,authorizationList:s,blobs:c,blobVersionedHashes:l,data:f,gasPrice:m,maxFeePerBlobGas:h,maxFeePerGas:g,maxPriorityFeePerGas:_,nonce:v,to:n,value:y},`estimateGas`);return BigInt(await e.request({method:`eth_estimateGas`,params:ee?[te,S??e.experimental_blockTag??`latest`,ee]:S?[te,S]:[te]}))}catch(n){throw Lu(n,{...t,account:i,chain:e.chain})}}function Vd(e,t){if(!Ci(e,{strict:!1}))throw new hi({address:e});if(!Ci(t,{strict:!1}))throw new hi({address:t});return e.toLowerCase()===t.toLowerCase()}var Hd=o((()=>{gi(),Ei()}));function Ud(e,{args:t,eventName:n}={}){return{...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,blockTimestamp:e.blockTimestamp?BigInt(e.blockTimestamp):e.blockTimestamp===null?null:void 0,logIndex:e.logIndex?Number(e.logIndex):null,transactionHash:e.transactionHash?e.transactionHash:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,...n?{args:t,eventName:n}:{}}}function Wd(e){let{abi:t,args:n,functionName:r,data:i}=e,a=t[0];if(r){let e=ta({abi:t,args:n,name:r});if(!e)throw new Qt(r,{docsPath:Gd});a=e}if(a.type!==`function`)throw new Qt(void 0,{docsPath:Gd});if(!a.outputs)throw new $t(a.name,{docsPath:Gd});let o=Oa(a.outputs,i);if(o&&o.length>1)return o;if(o&&o.length===1)return o[0]}var Gd,Kd=o((()=>{cn(),Ba(),ia(),Gd=`/docs/contract/decodeFunctionResult`})),qd,Jd=o((()=>{qd=`0.1.1`}));function Yd(){return qd}var Xd=o((()=>{Jd()}));function Zd(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause?Zd(e.cause,t):t?null:e}var P,Qd=o((()=>{Xd(),P=class e extends Error{static setStaticOptions(t){e.prototype.docsOrigin=t.docsOrigin,e.prototype.showVersion=t.showVersion,e.prototype.version=t.version}constructor(t,n={}){let r=(()=>{if(n.cause instanceof e){if(n.cause.details)return n.cause.details;if(n.cause.shortMessage)return n.cause.shortMessage}return n.cause&&`details`in n.cause&&typeof n.cause.details==`string`?n.cause.details:n.cause?.message?n.cause.message:n.details})(),i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=n.docsOrigin??e.prototype.docsOrigin,o=`${a}${i??``}`,s=!!(n.version??e.prototype.showVersion),c=n.version??e.prototype.version,l=[t||`An error occurred.`,...n.metaMessages?[``,...n.metaMessages]:[],...r||i||s?[``,r?`Details: ${r}`:void 0,i?`See: ${o}`:void 0,s?`Version: ${c}`:void 0]:[]].filter(e=>typeof e==`string`).join(` -`);super(l,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsOrigin`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`showVersion`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.cause=n.cause,this.details=r,this.docs=o,this.docsOrigin=a,this.docsPath=i,this.shortMessage=t,this.showVersion=s,this.version=c}walk(e){return Zd(this,e)}},Object.defineProperty(P,`defaultStaticOptions`,{enumerable:!0,configurable:!0,writable:!0,value:{docsOrigin:`https://oxlib.sh`,showVersion:!1,version:`ox@${Yd()}`}}),P.setStaticOptions(P.defaultStaticOptions)}));function $d(e,t){if(Ef(e)>t)throw new zf({givenSize:Ef(e),maxSize:t})}function ef(e,t){if(typeof t==`number`&&t>0&&t>Ef(e)-1)throw new Bf({offset:t,position:`start`,size:Ef(e)})}function tf(e,t,n){if(typeof t==`number`&&typeof n==`number`&&Ef(e)!==n-t)throw new Bf({offset:n,position:`end`,size:Ef(e)})}function nf(e){if(e>=of.zero&&e<=of.nine)return e-of.zero;if(e>=of.A&&e<=of.F)return e-(of.A-10);if(e>=of.a&&e<=of.f)return e-(of.a-10)}function rf(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;if(e.length>r)throw new Vf({size:e.length,targetSize:r,type:`Bytes`});let i=new Uint8Array(r);for(let t=0;t{Hf(),of={zero:48,nine:57,A:65,F:70,a:97,f:102}}));function cf(e,t){if(Qf(e)>t)throw new up({givenSize:Qf(e),maxSize:t})}function lf(e,t){if(typeof t==`number`&&t>0&&t>Qf(e)-1)throw new dp({offset:t,position:`start`,size:Qf(e)})}function uf(e,t,n){if(typeof t==`number`&&typeof n==`number`&&Qf(e)!==n-t)throw new dp({offset:n,position:`end`,size:Qf(e)})}function df(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;let i=e.replace(`0x`,``);if(i.length>r*2)throw new fp({size:Math.ceil(i.length/2),targetSize:r,type:`Hex`});return`0x${i[n===`right`?`padEnd`:`padStart`](r*2,`0`)}`}function ff(e,t={}){let{dir:n=`left`}=t,r=e.replace(`0x`,``),i=0;for(let e=0;e{pp()}));function mf(e,t,n){return JSON.stringify(e,(e,n)=>typeof t==`function`?t(e,n):typeof n==`bigint`?n.toString()+hf:n,n)}var hf,gf=o((()=>{hf=`#__bigint`}));function _f(e){if(!(e instanceof Uint8Array)&&(!e||typeof e!=`object`||!(`BYTES_PER_ELEMENT`in e)||e.BYTES_PER_ELEMENT!==1||e.constructor.name!==`Uint8Array`))throw new Rf(e)}function vf(...e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t);for(let t=0,r=0;t1||r[0]>1)throw new Lf(r);return!!r[0]}function Af(e,t={}){let{size:n}=t;return n!==void 0&&$d(e,n),tp(qf(e,t),t)}function jf(e,t={}){let{size:n}=t,r=e;return n!==void 0&&($d(r,n),r=Nf(r)),Ff.decode(r)}function Mf(e){return af(e,{dir:`left`})}function Nf(e){return af(e,{dir:`right`})}function Pf(e){try{return _f(e),!0}catch{return!1}}var Ff,If,Lf,Rf,zf,Bf,Vf,Hf=o((()=>{yc(),Qd(),pp(),sf(),pf(),gf(),Ff=new TextDecoder,If=new TextEncoder,Lf=class extends P{constructor(e){super(`Bytes value \`${e}\` is not a valid boolean.`,{metaMessages:["The bytes array must contain a single byte of either a `0` or `1` value."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesBooleanError`})}},Rf=class extends P{constructor(e){super(`Value \`${typeof e==`object`?mf(e):e}\` of type \`${typeof e}\` is an invalid Bytes value.`,{metaMessages:["Bytes values must be of type `Bytes`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.InvalidBytesTypeError`})}},zf=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeOverflowError`})}},Bf=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SliceOffsetOutOfBoundsError`})}},Vf=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Bytes.SizeExceedsPaddingSizeError`})}}}));function Uf(e,t={}){let{strict:n=!1}=t;if(!e||typeof e!=`string`)throw new sp(e);if(n&&!/^0x[0-9a-fA-F]*$/.test(e)||!e.startsWith(`0x`))throw new cp(e)}function Wf(...e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}function Gf(e){return e instanceof Uint8Array?qf(e):Array.isArray(e)?qf(new Uint8Array(e)):e}function Kf(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(cf(n,t.size),Yf(n,t.size)):n}function qf(e,t={}){let n=``;for(let t=0;ta||i>1n?r:r-a-1n}function tp(e,t={}){let{signed:n,size:r}=t;return Number(!n&&!r?e:ep(e,t))}function np(e,t={}){let{size:n}=t,r=xf(e);return n&&($d(r,n),r=Nf(r)),new TextDecoder().decode(r)}function rp(e,t={}){let{strict:n=!1}=t;try{return Uf(e,{strict:n}),!0}catch{return!1}}var ip,ap,op,sp,cp,lp,up,dp,fp,pp=o((()=>{Hf(),Qd(),sf(),pf(),gf(),ip=new TextEncoder,ap=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`)),op=class extends P{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number \`${i}\` is not in safe${r?` ${r*8}-bit`:``}${n?` signed`:` unsigned`} integer range ${e?`(\`${t}\` to \`${e}\`)`:`(above \`${t}\`)`}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.IntegerOutOfRangeError`})}},sp=class extends P{constructor(e){super(`Value \`${typeof e==`object`?mf(e):e}\` of type \`${typeof e}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexTypeError`})}},cp=class extends P{constructor(e){super(`Value \`${e}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexValueError`})}},lp=class extends P{constructor(e){super(`Hex value \`"${e}"\` is an odd length (${e.length-2} nibbles).`,{metaMessages:[`It must be an even length.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidLengthError`})}},up=class extends P{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeOverflowError`})}},dp=class extends P{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SliceOffsetOutOfBoundsError`})}},fp=class extends P{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeExceedsPaddingSizeError`})}}}));function mp(e){return{address:e.address,amount:F(e.amount),index:F(e.index),validatorIndex:F(e.validatorIndex)}}var hp=o((()=>{pp()}));function gp(e){return{...typeof e.baseFeePerGas==`bigint`&&{baseFeePerGas:F(e.baseFeePerGas)},...typeof e.blobBaseFee==`bigint`&&{blobBaseFee:F(e.blobBaseFee)},...typeof e.feeRecipient==`string`&&{feeRecipient:e.feeRecipient},...typeof e.gasLimit==`bigint`&&{gasLimit:F(e.gasLimit)},...typeof e.number==`bigint`&&{number:F(e.number)},...typeof e.prevRandao==`bigint`&&{prevRandao:F(e.prevRandao)},...typeof e.time==`bigint`&&{time:F(e.time)},...e.withdrawals&&{withdrawals:e.withdrawals.map(mp)}}}var _p=o((()=>{pp(),hp()})),vp,yp,bp,xp,Sp,Cp=o((()=>{vp=[{inputs:[{components:[{name:`target`,type:`address`},{name:`allowFailure`,type:`bool`},{name:`callData`,type:`bytes`}],name:`calls`,type:`tuple[]`}],name:`aggregate3`,outputs:[{components:[{name:`success`,type:`bool`},{name:`returnData`,type:`bytes`}],name:`returnData`,type:`tuple[]`}],stateMutability:`view`,type:`function`},{inputs:[{name:`addr`,type:`address`}],name:`getEthBalance`,outputs:[{name:`balance`,type:`uint256`}],stateMutability:`view`,type:`function`},{inputs:[],name:`getCurrentBlockTimestamp`,outputs:[{internalType:`uint256`,name:`timestamp`,type:`uint256`}],stateMutability:`view`,type:`function`}],yp=[{name:`query`,type:`function`,stateMutability:`view`,inputs:[{type:`tuple[]`,name:`queries`,components:[{type:`address`,name:`sender`},{type:`string[]`,name:`urls`},{type:`bytes`,name:`data`}]}],outputs:[{type:`bool[]`,name:`failures`},{type:`bytes[]`,name:`responses`}]},{name:`HttpError`,type:`error`,inputs:[{type:`uint16`,name:`status`},{type:`string`,name:`message`}]}],bp=[{inputs:[{name:`dns`,type:`bytes`}],name:`DNSDecodingFailed`,type:`error`},{inputs:[{name:`ens`,type:`string`}],name:`DNSEncodingFailed`,type:`error`},{inputs:[],name:`EmptyAddress`,type:`error`},{inputs:[{name:`status`,type:`uint16`},{name:`message`,type:`string`}],name:`HttpError`,type:`error`},{inputs:[],name:`InvalidBatchGatewayResponse`,type:`error`},{inputs:[{name:`errorData`,type:`bytes`}],name:`ResolverError`,type:`error`},{inputs:[{name:`name`,type:`bytes`},{name:`resolver`,type:`address`}],name:`ResolverNotContract`,type:`error`},{inputs:[{name:`name`,type:`bytes`}],name:`ResolverNotFound`,type:`error`},{inputs:[{name:`primary`,type:`string`},{name:`primaryAddress`,type:`bytes`}],name:`ReverseAddressMismatch`,type:`error`},{inputs:[{internalType:`bytes4`,name:`selector`,type:`bytes4`}],name:`UnsupportedResolverProfile`,type:`error`}],[...bp],[...bp],xp=[{name:`isValidSignature`,type:`function`,stateMutability:`view`,inputs:[{name:`hash`,type:`bytes32`},{name:`signature`,type:`bytes`}],outputs:[{name:``,type:`bytes4`}]}],Sp=[{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],stateMutability:`nonpayable`,type:`constructor`},{inputs:[{name:`_signer`,type:`address`},{name:`_hash`,type:`bytes32`},{name:`_signature`,type:`bytes`}],outputs:[{type:`bool`}],stateMutability:`nonpayable`,type:`function`,name:`isValidSig`}]})),wp=o((()=>{})),Tp,Ep,Dp,Op,kp=o((()=>{Tp=`0x608060405234801561001057600080fd5b5060405161018e38038061018e83398101604081905261002f91610124565b6000808351602085016000f59050803b61004857600080fd5b6000808351602085016000855af16040513d6000823e81610067573d81fd5b3d81f35b634e487b7160e01b600052604160045260246000fd5b600082601f83011261009257600080fd5b81516001600160401b038111156100ab576100ab61006b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156100d9576100d961006b565b6040528181528382016020018510156100f157600080fd5b60005b82811015610110576020818601810151838301820152016100f4565b506000918101602001919091529392505050565b6000806040838503121561013757600080fd5b82516001600160401b0381111561014d57600080fd5b61015985828601610081565b602085015190935090506001600160401b0381111561017757600080fd5b61018385828601610081565b915050925092905056fe`,Ep=`0x608060405234801561001057600080fd5b506040516102c03803806102c083398101604081905261002f916101e6565b836001600160a01b03163b6000036100e457600080836001600160a01b03168360405161005c9190610270565b6000604051808303816000865af19150503d8060008114610099576040519150601f19603f3d011682016040523d82523d6000602084013e61009e565b606091505b50915091508115806100b857506001600160a01b0386163b155b156100e1578060405163101bb98d60e01b81526004016100d8919061028c565b60405180910390fd5b50505b6000808451602086016000885af16040513d6000823e81610103573d81fd5b3d81f35b80516001600160a01b038116811461011e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015457818101518382015260200161013c565b50506000910152565b600082601f83011261016e57600080fd5b81516001600160401b0381111561018757610187610123565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101b5576101b5610123565b6040528181528382016020018510156101cd57600080fd5b6101de826020830160208701610139565b949350505050565b600080600080608085870312156101fc57600080fd5b61020585610107565b60208601519094506001600160401b0381111561022157600080fd5b61022d8782880161015d565b93505061023c60408601610107565b60608601519092506001600160401b0381111561025857600080fd5b6102648782880161015d565b91505092959194509250565b60008251610282818460208701610139565b9190910192915050565b60208152600082518060208401526102ab816040850160208701610139565b601f01601f1916919091016040019291505056fe`,Dp=`0x608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572`,Op=`0x608060405234801561001057600080fd5b506115b9806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e14610325578063bce38bd714610350578063c3077fa914610380578063ee82ac5e146103b2576100f3565b80634d2301cc1461026257806372425d9d1461029f57806382ad56cb146102ca57806386d516e8146102fa576100f3565b80633408e470116100c65780633408e470146101af578063399542e9146101da5780633e64a6961461020c57806342cbb15c14610237576100f3565b80630f28c97d146100f8578063174dea7114610123578063252dba421461015357806327e86d6e14610184575b600080fd5b34801561010457600080fd5b5061010d6103ef565b60405161011a9190610c0a565b60405180910390f35b61013d60048036038101906101389190610c94565b6103f7565b60405161014a9190610e94565b60405180910390f35b61016d60048036038101906101689190610f0c565b610615565b60405161017b92919061101b565b60405180910390f35b34801561019057600080fd5b506101996107ab565b6040516101a69190611064565b60405180910390f35b3480156101bb57600080fd5b506101c46107b7565b6040516101d19190610c0a565b60405180910390f35b6101f460048036038101906101ef91906110ab565b6107bf565b6040516102039392919061110b565b60405180910390f35b34801561021857600080fd5b506102216107e1565b60405161022e9190610c0a565b60405180910390f35b34801561024357600080fd5b5061024c6107e9565b6040516102599190610c0a565b60405180910390f35b34801561026e57600080fd5b50610289600480360381019061028491906111a7565b6107f1565b6040516102969190610c0a565b60405180910390f35b3480156102ab57600080fd5b506102b4610812565b6040516102c19190610c0a565b60405180910390f35b6102e460048036038101906102df919061122a565b61081a565b6040516102f19190610e94565b60405180910390f35b34801561030657600080fd5b5061030f6109e4565b60405161031c9190610c0a565b60405180910390f35b34801561033157600080fd5b5061033a6109ec565b6040516103479190611286565b60405180910390f35b61036a600480360381019061036591906110ab565b6109f4565b6040516103779190610e94565b60405180910390f35b61039a60048036038101906103959190610f0c565b610ba6565b6040516103a99392919061110b565b60405180910390f35b3480156103be57600080fd5b506103d960048036038101906103d491906112cd565b610bca565b6040516103e69190611064565b60405180910390f35b600042905090565b60606000808484905090508067ffffffffffffffff81111561041c5761041b6112fa565b5b60405190808252806020026020018201604052801561045557816020015b610442610bd5565b81526020019060019003908161043a5790505b5092503660005b828110156105c957600085828151811061047957610478611329565b5b6020026020010151905087878381811061049657610495611329565b5b90506020028101906104a89190611367565b925060008360400135905080860195508360000160208101906104cb91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16818580606001906104f2919061138f565b604051610500929190611431565b60006040518083038185875af1925050503d806000811461053d576040519150601f19603f3d011682016040523d82523d6000602084013e610542565b606091505b5083600001846020018290528215151515815250505081516020850135176105bc577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b826001019250505061045c565b5082341461060c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610603906114a7565b60405180910390fd5b50505092915050565b6000606043915060008484905090508067ffffffffffffffff81111561063e5761063d6112fa565b5b60405190808252806020026020018201604052801561067157816020015b606081526020019060019003908161065c5790505b5091503660005b828110156107a157600087878381811061069557610694611329565b5b90506020028101906106a791906114c7565b92508260000160208101906106bc91906111a7565b73ffffffffffffffffffffffffffffffffffffffff168380602001906106e2919061138f565b6040516106f0929190611431565b6000604051808303816000865af19150503d806000811461072d576040519150601f19603f3d011682016040523d82523d6000602084013e610732565b606091505b5086848151811061074657610745611329565b5b60200260200101819052819250505080610795576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161078c9061153b565b60405180910390fd5b81600101915050610678565b5050509250929050565b60006001430340905090565b600046905090565b6000806060439250434091506107d68686866109f4565b905093509350939050565b600048905090565b600043905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b606060008383905090508067ffffffffffffffff81111561083e5761083d6112fa565b5b60405190808252806020026020018201604052801561087757816020015b610864610bd5565b81526020019060019003908161085c5790505b5091503660005b828110156109db57600084828151811061089b5761089a611329565b5b602002602001015190508686838181106108b8576108b7611329565b5b90506020028101906108ca919061155b565b92508260000160208101906108df91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060400190610905919061138f565b604051610913929190611431565b6000604051808303816000865af19150503d8060008114610950576040519150601f19603f3d011682016040523d82523d6000602084013e610955565b606091505b5082600001836020018290528215151515815250505080516020840135176109cf577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b8160010191505061087e565b50505092915050565b600045905090565b600041905090565b606060008383905090508067ffffffffffffffff811115610a1857610a176112fa565b5b604051908082528060200260200182016040528015610a5157816020015b610a3e610bd5565b815260200190600190039081610a365790505b5091503660005b82811015610b9c576000848281518110610a7557610a74611329565b5b60200260200101519050868683818110610a9257610a91611329565b5b9050602002810190610aa491906114c7565b9250826000016020810190610ab991906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060200190610adf919061138f565b604051610aed929190611431565b6000604051808303816000865af19150503d8060008114610b2a576040519150601f19603f3d011682016040523d82523d6000602084013e610b2f565b606091505b508260000183602001829052821515151581525050508715610b90578060000151610b8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b869061153b565b60405180910390fd5b5b81600101915050610a58565b5050509392505050565b6000806060610bb7600186866107bf565b8093508194508295505050509250925092565b600081409050919050565b6040518060400160405280600015158152602001606081525090565b6000819050919050565b610c0481610bf1565b82525050565b6000602082019050610c1f6000830184610bfb565b92915050565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f840112610c5457610c53610c2f565b5b8235905067ffffffffffffffff811115610c7157610c70610c34565b5b602083019150836020820283011115610c8d57610c8c610c39565b5b9250929050565b60008060208385031215610cab57610caa610c25565b5b600083013567ffffffffffffffff811115610cc957610cc8610c2a565b5b610cd585828601610c3e565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b610d2281610d0d565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610d62578082015181840152602081019050610d47565b83811115610d71576000848401525b50505050565b6000601f19601f8301169050919050565b6000610d9382610d28565b610d9d8185610d33565b9350610dad818560208601610d44565b610db681610d77565b840191505092915050565b6000604083016000830151610dd96000860182610d19565b5060208301518482036020860152610df18282610d88565b9150508091505092915050565b6000610e0a8383610dc1565b905092915050565b6000602082019050919050565b6000610e2a82610ce1565b610e348185610cec565b935083602082028501610e4685610cfd565b8060005b85811015610e825784840389528151610e638582610dfe565b9450610e6e83610e12565b925060208a01995050600181019050610e4a565b50829750879550505050505092915050565b60006020820190508181036000830152610eae8184610e1f565b905092915050565b60008083601f840112610ecc57610ecb610c2f565b5b8235905067ffffffffffffffff811115610ee957610ee8610c34565b5b602083019150836020820283011115610f0557610f04610c39565b5b9250929050565b60008060208385031215610f2357610f22610c25565b5b600083013567ffffffffffffffff811115610f4157610f40610c2a565b5b610f4d85828601610eb6565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000610f918383610d88565b905092915050565b6000602082019050919050565b6000610fb182610f59565b610fbb8185610f64565b935083602082028501610fcd85610f75565b8060005b858110156110095784840389528151610fea8582610f85565b9450610ff583610f99565b925060208a01995050600181019050610fd1565b50829750879550505050505092915050565b60006040820190506110306000830185610bfb565b81810360208301526110428184610fa6565b90509392505050565b6000819050919050565b61105e8161104b565b82525050565b60006020820190506110796000830184611055565b92915050565b61108881610d0d565b811461109357600080fd5b50565b6000813590506110a58161107f565b92915050565b6000806000604084860312156110c4576110c3610c25565b5b60006110d286828701611096565b935050602084013567ffffffffffffffff8111156110f3576110f2610c2a565b5b6110ff86828701610eb6565b92509250509250925092565b60006060820190506111206000830186610bfb565b61112d6020830185611055565b818103604083015261113f8184610e1f565b9050949350505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061117482611149565b9050919050565b61118481611169565b811461118f57600080fd5b50565b6000813590506111a18161117b565b92915050565b6000602082840312156111bd576111bc610c25565b5b60006111cb84828501611192565b91505092915050565b60008083601f8401126111ea576111e9610c2f565b5b8235905067ffffffffffffffff81111561120757611206610c34565b5b60208301915083602082028301111561122357611222610c39565b5b9250929050565b6000806020838503121561124157611240610c25565b5b600083013567ffffffffffffffff81111561125f5761125e610c2a565b5b61126b858286016111d4565b92509250509250929050565b61128081611169565b82525050565b600060208201905061129b6000830184611277565b92915050565b6112aa81610bf1565b81146112b557600080fd5b50565b6000813590506112c7816112a1565b92915050565b6000602082840312156112e3576112e2610c25565b5b60006112f1848285016112b8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600080fd5b600080fd5b600080fd5b60008235600160800383360303811261138357611382611358565b5b80830191505092915050565b600080833560016020038436030381126113ac576113ab611358565b5b80840192508235915067ffffffffffffffff8211156113ce576113cd61135d565b5b6020830192506001820236038313156113ea576113e9611362565b5b509250929050565b600081905092915050565b82818337600083830152505050565b600061141883856113f2565b93506114258385846113fd565b82840190509392505050565b600061143e82848661140c565b91508190509392505050565b600082825260208201905092915050565b7f4d756c746963616c6c333a2076616c7565206d69736d61746368000000000000600082015250565b6000611491601a8361144a565b915061149c8261145b565b602082019050919050565b600060208201905081810360008301526114c081611484565b9050919050565b6000823560016040038336030381126114e3576114e2611358565b5b80830191505092915050565b7f4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000600082015250565b600061152560178361144a565b9150611530826114ef565b602082019050919050565b6000602082019050818103600083015261155481611518565b9050919050565b60008235600160600383360303811261157757611576611358565b5b8083019150509291505056fea264697066735822122020c1bc9aacf8e4a6507193432a895a8e77094f45a1395583f07b24e860ef06cd64736f6c634300080c0033`})),Ap,jp,Mp,Np,Pp,Fp=o((()=>{D(),Ap=class extends E{constructor({blockNumber:e,chain:t,contract:n}){super(`Chain "${t.name}" does not support contract "${n.name}".`,{metaMessages:[`This could be due to any of the following:`,...e&&n.blockCreated&&n.blockCreated>e?[`- The contract "${n.name}" was not deployed until block ${n.blockCreated} (current block ${e}).`]:[`- The chain does not have the contract "${n.name}" configured.`]],name:`ChainDoesNotSupportContract`})}},jp=class extends E{constructor({chain:e,currentChainId:t}){super(`The current chain of the wallet (id: ${t}) does not match the target chain for the transaction (id: ${e.id} – ${e.name}).`,{metaMessages:[`Current Chain ID: ${t}`,`Expected Chain ID: ${e.id} – ${e.name}`],name:`ChainMismatchError`})}},Mp=class extends E{constructor(){super([`No chain was provided to the request.`,"Please provide a chain with the `chain` argument on the Action, or by supplying a `chain` to WalletClient."].join(` -`),{name:`ChainNotFoundError`})}},Np=class extends E{constructor(){super(`No chain was provided to the Client.`,{name:`ClientChainNotConfiguredError`})}},Pp=class extends E{constructor({chainId:e}){super(typeof e==`number`?`Chain ID "${e}" is invalid.`:`Chain ID is invalid.`,{name:`InvalidChainIdError`})}}}));function Ip(e){let{abi:t,args:n,bytecode:r}=e;if(!n||n.length===0)return r;let i=t.find(e=>`type`in e&&e.type===`constructor`);if(!i)throw new Ht({docsPath:Lp});if(!(`inputs`in i)||!i.inputs||i.inputs.length===0)throw new Ut({docsPath:Lp});return ki([r,Bi(i.inputs,n)])}var Lp,Rp=o((()=>{cn(),Ai(),Qi(),Lp=`/docs/contract/encodeDeployData`}));function zp({blockNumber:e,chain:t,contract:n}){let r=t?.contracts?.[n];if(!r)throw new Ap({chain:t,contract:{name:n}});if(e&&r.blockCreated&&r.blockCreated>e)throw new Ap({blockNumber:e,chain:t,contract:{name:n,blockCreated:r.blockCreated}});return r.address}var Bp=o((()=>{Fp()}));function Vp(e,{docsPath:t,...n}){return new xo((()=>{let t=Fu(e,n);return t instanceof Nu?e:t})(),{docsPath:t,...n})}var Hp=o((()=>{Eo(),Pu(),Iu()}));function Up(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}var Wp=o((()=>{}));function Gp({fn:e,id:t,shouldSplitBatch:n,wait:r=0,sort:i}){let a=async()=>{let t=c();o();let n=t.map(({args:e})=>e);n.length!==0&&e(n).then(e=>{i&&Array.isArray(e)&&e.sort(i);for(let n=0;n{for(let n=0;nKp.delete(t),s=()=>c().map(({args:e})=>e),c=()=>Kp.get(t)||[],l=e=>Kp.set(t,[...c(),e]);return{flush:o,async schedule(e){let{promise:t,resolve:i,reject:o}=Up();return n?.([...s(),e])&&a(),c().length>0?(l({args:e,resolve:i,reject:o}),t):(l({args:e,resolve:i,reject:o}),setTimeout(a,r),t)}}}var Kp,qp=o((()=>{Wp(),Kp=new Map})),Jp,Yp,Xp,Zp=o((()=>{Wa(),D(),bo(),Jp=class extends E{constructor({callbackSelector:e,cause:t,data:n,extraData:r,sender:i,urls:a}){super(t.shortMessage||`An error occurred while fetching for an offchain result.`,{cause:t,metaMessages:[...t.metaMessages||[],t.metaMessages?.length?``:[],`Offchain Gateway Call:`,a&&[` Gateway URL(s):`,...a.map(e=>` ${yo(e)}`)],` Sender: ${i}`,` Data: ${n}`,` Callback selector: ${e}`,` Extra data: ${r}`].flat(),name:`OffchainLookupError`})}},Yp=class extends E{constructor({result:e,url:t}){super(`Offchain gateway response is malformed. Response data must be a hex value.`,{metaMessages:[`Gateway URL: ${yo(t)}`,`Response: ${Ua(e)}`],name:`OffchainLookupResponseMalformedError`})}},Xp=class extends E{constructor({sender:e,to:t}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${t}`,`OffchainLookup sender address: ${e}`],name:`OffchainLookupSenderMismatchError`})}}}));function Qp(e){let{abi:t,data:n}=e,r=ji(n,0,4),i=t.find(e=>e.type===`function`&&r===$i(At(e)));if(!i)throw new en(r,{docsPath:`/docs/contract/decodeFunctionData`});return{functionName:i.name,args:`inputs`in i&&i.inputs&&i.inputs.length>0?Oa(i.inputs,ji(n,4)):void 0}}var $p=o((()=>{cn(),Ii(),ea(),Ba(),Nt()}));function em(e){let{abi:t,errorName:n,args:r}=e,i=t[0];if(n){let e=ta({abi:t,args:r,name:n});if(!e)throw new Xt(n,{docsPath:tm});i=e}if(i.type!==`error`)throw new Xt(void 0,{docsPath:tm});let a=$i(At(i)),o=`0x`;if(r&&r.length>0){if(!i.inputs)throw new Yt(i.name,{docsPath:tm});o=Bi(i.inputs,r)}return ki([a,o])}var tm,nm=o((()=>{cn(),Ai(),ea(),Qi(),Nt(),ia(),tm=`/docs/contract/encodeErrorResult`}));function rm(e){let{abi:t,functionName:n,result:r}=e,i=t[0];if(n){let e=ta({abi:t,name:n});if(!e)throw new Qt(n,{docsPath:im});i=e}if(i.type!==`function`)throw new Qt(void 0,{docsPath:im});if(!i.outputs)throw new $t(i.name,{docsPath:im});let a=(()=>{if(i.outputs.length===0)return[];if(i.outputs.length===1)return[r];if(Array.isArray(r))return r;throw new on(r)})();return Bi(i.outputs,a)}var im,am=o((()=>{cn(),Qi(),ia(),im=`/docs/contract/encodeFunctionResult`}));async function om(e){let{data:t,ccipRequest:n}=e,{args:[r]}=Qp({abi:yp,data:t}),i=[],a=[];return await Promise.all(r.map(async(e,t)=>{try{a[t]=e.urls.includes(`x-batch-gateway:true`)?await om({data:e.data,ccipRequest:n}):await n(e),i[t]=!1}catch(e){i[t]=!0,a[t]=sm(e)}})),rm({abi:yp,functionName:`query`,result:[i,a]})}function sm(e){return e.name===`HttpRequestError`&&e.status?em({abi:yp,errorName:`HttpError`,args:[e.status,e.shortMessage]}):em({abi:[pa],errorName:`Error`,args:[`shortMessage`in e?e.shortMessage:e.message]})}var cm=o((()=>{Cp(),ha(),$p(),nm(),am()})),lm=c({ccipRequest:()=>dm,offchainLookup:()=>um,offchainLookupAbiItem:()=>pm,offchainLookupSignature:()=>fm});async function um(e,{blockNumber:t,blockTag:n,data:r,to:i}){let{args:a}=Va({data:r,abi:[pm]}),[o,s,c,l,u]=a,{ccipRead:d}=e,f=d&&typeof d?.request==`function`?d.request:dm;try{if(!Vd(i,o))throw new Xp({sender:o,to:i});let{data:r}=await hm(e,{blockNumber:t,blockTag:n,data:Di([l,Bi([{type:`bytes`},{type:`bytes`}],[s.includes(`x-batch-gateway:true`)?await om({data:c,ccipRequest:f}):await f({data:c,sender:o,urls:s}),u])]),to:i});return r}catch(e){throw new Jp({callbackSelector:l,cause:e,data:r,extraData:u,sender:o,urls:s})}}async function dm({data:e,sender:t,urls:n}){let r=Error(`An unknown error occurred.`);for(let i=0;i{xm(),Zp(),Ao(),Ha(),Qi(),Hd(),Ai(),Ft(),cm(),Wa(),fm=`0x556f1830`,pm={name:`OffchainLookup`,type:`error`,inputs:[{name:`sender`,type:`address`},{name:`urls`,type:`string[]`},{name:`callData`,type:`bytes`},{name:`callbackFunction`,type:`bytes4`},{name:`extraData`,type:`bytes`}]}}));async function hm(e,t){let{account:n=e.account,authorizationList:r,batch:i=!!e.batch?.multicall,blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,accessList:s,blobs:c,blockOverrides:l,code:u,data:d,factory:f,factoryData:p,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:b,value:x,stateOverride:S,...ee}=t,C=n?aa(n):void 0;if(u&&(f||p))throw new E("Cannot provide both `code` & `factory`/`factoryData` as parameters.");if(u&&b)throw new E("Cannot provide both `code` & `to` as parameters.");let te=u&&d,ne=f&&p&&b&&d,re=te||ne,ie=te?vm({code:u,data:d}):ne?ym({data:d,factory:f,factoryData:p,to:b}):d;try{ed(t);let n=(typeof a==`bigint`?O(a):void 0)||o,u=l?gp(l):void 0,d=Yu(S),f=e.chain?.formatters?.transactionRequest?.format,p=(f||Hu)({...Ru(ee,{format:f}),accessList:s,account:C,authorizationList:r,blobs:c,data:ie,gas:m,gasPrice:h,maxFeePerBlobGas:g,maxFeePerGas:_,maxPriorityFeePerGas:v,nonce:y,to:re?void 0:b,value:x},`call`);if(i&&gm({request:p})&&!d&&!u)try{return await _m(e,{...p,blockNumber:a,blockTag:o})}catch(e){if(!(e instanceof Np)&&!(e instanceof Ap))throw e}let te=(()=>{let e=[p,n];return d&&u?[...e,d,u]:d?[...e,d]:u?[...e,{},u]:e})(),ne=await e.request({method:`eth_call`,params:te});return ne===`0x`?{data:void 0}:{data:ne}}catch(n){let r=bm(n),{offchainLookup:i,offchainLookupSignature:a}=await ms(async()=>{let{offchainLookup:e,offchainLookupSignature:t}=await Promise.resolve().then(()=>(mm(),lm));return{offchainLookup:e,offchainLookupSignature:t}},void 0);if(e.ccipRead!==!1&&r?.slice(0,10)===a&&b)return{data:await i(e,{data:r,to:b})};throw re&&r?.slice(0,10)===`0x101bb98d`?new wo({factory:f}):Vp(n,{...t,account:C,chain:e.chain})}}function gm({request:e}){let{data:t,to:n,...r}=e;return!(!t||t.startsWith(`0x82ad56cb`)||!n||Object.values(r).filter(e=>e!==void 0).length>0)}async function _m(e,t){let{batchSize:n=1024,deployless:r=!1,wait:i=0}=typeof e.batch?.multicall==`object`?e.batch.multicall:{},{blockNumber:a,blockTag:o=e.experimental_blockTag??`latest`,data:s,to:c}=t,l=(()=>{if(r)return null;if(t.multicallAddress)return t.multicallAddress;if(e.chain)return zp({blockNumber:a,chain:e.chain,contract:`multicall3`});throw new Np})(),u=(typeof a==`bigint`?O(a):void 0)||o,{schedule:d}=Gp({id:`${e.uid}.${u}`,wait:i,shouldSplitBatch(e){return e.reduce((e,{data:t})=>e+(t.length-2),0)>n*2},fn:async t=>{let n=t.map(e=>({allowFailure:!0,callData:e.data,target:e.to})),r=ua({abi:vp,args:[n],functionName:`aggregate3`}),i=await e.request({method:`eth_call`,params:[{...l===null?{data:vm({code:Op,data:r})}:{to:l,data:r}},u]});return Wd({abi:vp,args:[n],functionName:`aggregate3`,data:i||`0x`})}}),[{returnData:f,success:p}]=await d({data:s,to:c});if(!p)throw new To({data:f});return f===`0x`?{data:void 0}:{data:f}}function vm(e){let{code:t,data:n}=e;return Ip({abi:Ct([`constructor(bytes, bytes)`]),bytecode:Tp,args:[t,n]})}function ym(e){let{data:t,factory:n,factoryData:r,to:i}=e;return Ip({abi:Ct([`constructor(address, bytes, address, bytes)`]),bytecode:Ep,args:[i,t,n,r]})}function bm(e){if(!(e instanceof E))return;let t=e.walk();return typeof t?.data==`object`?t.data?.data:t.data}var xm=o((()=>{kt(),_p(),oa(),Cp(),wp(),kp(),D(),Fp(),Eo(),Kd(),Rp(),Bp(),k(),Hp(),zu(),Ku(),qp(),Xu(),td(),hs()}));Kd(),da(),xm();async function Sm(e,t){let{abi:n,address:r,args:i,functionName:a,...o}=t,s=ua({abi:n,args:i,functionName:a});try{let{data:t}=await T(e,hm,`call`)({...o,data:s,to:r});return Wd({abi:n,args:i,functionName:a,data:t||`0x`})}catch(e){throw ls(e,{abi:n,address:r,args:i,docsPath:`/docs/contract/readContract`,functionName:a})}}var Cm=new Map,wm=new Map,Tm=0;function Em(e,t,n){let r=++Tm,i=()=>Cm.get(e)||[],a=()=>{let t=i();Cm.set(e,t.filter(e=>e.id!==r))},o=()=>{let t=i();if(!t.some(e=>e.id===r))return;let n=wm.get(e);if(t.length===1&&n){let e=n();e instanceof Promise&&e.catch(()=>{})}a()},s=i();if(Cm.set(e,[...s,{id:r,fns:t}]),s&&s.length>0)return o;let c={};for(let e in t)c[e]=((...t)=>{let n=i();if(n.length!==0)for(let r of n)r.fns[e]?.(...t)});let l=n(c);return typeof l==`function`&&wm.set(e,l),o}async function Dm(e){return new Promise(t=>setTimeout(t,e))}function Om(e,{emitOnBegin:t,initialWaitTime:n,interval:r}){let i=!0,a=()=>i=!1;return(async()=>{let o;t&&(o=await e({unpoll:a})),await Dm(await n?.(o)??r);let s=async()=>{i&&(await e({unpoll:a}),await Dm(r),s())};s()})(),a}var km=new Map,Am=new Map;function jm(e){let t=(e,t)=>({clear:()=>t.delete(e),get:()=>t.get(e),set:n=>t.set(e,n)}),n=t(e,km),r=t(e,Am);return{clear:()=>{n.clear(),r.clear()},promise:n,response:r}}async function Mm(e,{cacheKey:t,cacheTime:n=1/0}){let r=jm(t),i=r.response.get();if(i&&n>0&&Date.now()-i.created.getTime()`blockNumber.${e}`;async function Pm(e,{cacheTime:t=e.cacheTime}={}){let n=await Mm(()=>e.request({method:`eth_blockNumber`}),{cacheKey:Nm(e.uid),cacheTime:t});return BigInt(n)}D();var Fm=class extends E{constructor({docsPath:e}={}){super([`Could not find an Account to execute with this Action.`,"Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client."].join(` -`),{docsPath:e,docsSlug:`account`,name:`AccountNotFoundError`})}},Im=class extends E{constructor({docsPath:e,metaMessages:t,type:n}){super(`Account type "${n}" is not supported.`,{docsPath:e,metaMessages:t,name:`AccountTypeNotSupportedError`})}};Fp();function Lm({chain:e,currentChainId:t}){if(!e)throw new Mp;if(t!==e.id)throw new jp({chain:e,currentChainId:t})}async function Rm(e,{serializedTransaction:t}){return e.request({method:`eth_sendRawTransaction`,params:[t]},{retryCount:0})}oa(),D(),Ai(),zu(),Ku(),vi(),td();var zm=new _i(128);async function Bm(e,t){let{account:n=e.account,assertChainId:r=!0,chain:i=e.chain,accessList:a,authorizationList:o,blobs:s,data:c,dataSuffix:l=typeof e.dataSuffix==`string`?e.dataSuffix:e.dataSuffix?.value,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,type:g,value:_,...v}=t;if(n===void 0)throw new Fm({docsPath:`/docs/actions/wallet/sendTransaction`});let y=n?aa(n):null;try{ed(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&o&&o.length>0)return await bu({authorization:o[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`.")})})();if(y?.type===`json-rpc`||y===null){let t;i!==null&&(t=await T(e,Fd,`getChainId`)({}),r&&Lm({currentChainId:t,chain:i}));let b=e.chain?.formatters?.transactionRequest?.format,x=(b||Hu)({...Ru(v,{format:b}),accessList:a,account:y,authorizationList:o,blobs:s,chainId:t,data:l?Di([c??`0x`,l]):c,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,to:n,type:g,value:_},`sendTransaction`),S=zm.get(e.uid),ee=S?`wallet_sendTransaction`:`eth_sendTransaction`;try{return await e.request({method:ee,params:[x]},{retryCount:0})}catch(t){if(S===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[x]},{retryCount:0}).then(t=>(zm.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(zm.set(e.uid,!1),n):r});throw n}}if(y?.type===`local`){let t=await T(e,zd,`prepareTransactionRequest`)({account:y,accessList:a,authorizationList:o,blobs:s,chain:i,data:l?Di([c??`0x`,l]):c,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,nonceManager:y.nonceManager,parameters:[...Ld,`sidecars`],type:g,value:_,...v,to:n}),r=i?.serializers?.transaction,b=await y.signTransaction(t,{serializer:r});return await T(e,Rm,`sendRawTransaction`)({serializedTransaction:b})}throw y?.type===`smart`?new Im({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new Im({docsPath:`/docs/actions/wallet/sendTransaction`,type:y?.type})}catch(e){throw e instanceof Im?e:Pd(e,{...t,account:y,chain:t.chain||void 0})}}oa(),da();async function Vm(e,t){return Vm.internal(e,Bm,`sendTransaction`,t)}(function(e){async function t(e,t,n,r){let{abi:i,account:a=e.account,address:o,args:s,functionName:c,...l}=r;if(a===void 0)throw new Fm({docsPath:`/docs/contract/writeContract`});let u=a?aa(a):null,d=ua({abi:i,args:s,functionName:c});try{return await T(e,t,n)({data:d,to:o,account:u,...l})}catch(e){throw ls(e,{abi:i,address:o,args:s,docsPath:`/docs/contract/writeContract`,functionName:c,sender:u?.address})}}e.internal=t})(Vm||={}),D();var Hm=class extends E{constructor(e){super(`Call bundle failed with status: ${e.statusCode}`,{name:`BundleFailedError`}),Object.defineProperty(this,`result`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.result=e}};function Um(e,{delay:t=100,retryCount:n=2,shouldRetry:r=()=>!0}={}){return new Promise((i,a)=>{let o=async({count:s=0}={})=>{let c=async({error:e})=>{let n=typeof t==`function`?t({count:s,error:e}):t;n&&await Dm(n),o({count:s+1})};try{i(await e())}catch(e){if(sUd(e)):null,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Dn(e.transactionIndex):null,status:e.status?Wm[e.status]:null,type:e.type?od[e.type]||e.type:null};return e.blobGasPrice&&(n.blobGasPrice=BigInt(e.blobGasPrice)),e.blobGasUsed&&(n.blobGasUsed=BigInt(e.blobGasUsed)),n}var Km=Bu(`transactionReceipt`,Gm);oa(),D(),ss(),da(),Ai(),On(),k();var qm=`0x5792579257925792579257925792579257925792579257925792579257925792`,Jm=O(0,{size:32});async function Ym(e,t){let{account:n=e.account,chain:r=e.chain,experimental_fallback:i,experimental_fallbackDelay:a=32,forceAtomic:o=!1,id:s,version:c=`2.0.0`}=t,l=n?aa(n):null,u=t.capabilities;e.dataSuffix&&!t.capabilities?.dataSuffix&&(u=typeof e.dataSuffix==`string`?{...t.capabilities,dataSuffix:{value:e.dataSuffix,optional:!0}}:{...t.capabilities,dataSuffix:{value:e.dataSuffix.value,...e.dataSuffix.required?{}:{optional:!0}}});let d=t.calls.map(e=>{let t=e,n=t.abi?ua({abi:t.abi,functionName:t.functionName,args:t.args}):t.data;return{data:t.dataSuffix&&n?Di([n,t.dataSuffix]):n,to:t.to,value:t.value?O(t.value):void 0}});try{let t=await e.request({method:`wallet_sendCalls`,params:[{atomicRequired:o,calls:d,capabilities:u,chainId:O(r.id),from:l?.address,id:s,version:c}]},{retryCount:0});return typeof t==`string`?{id:t}:t}catch(n){let s=n;if(i&&(s.name===`MethodNotFoundRpcError`||s.name===`MethodNotSupportedRpcError`||s.name===`UnknownRpcError`||s.details.toLowerCase().includes(`does not exist / is not available`)||s.details.toLowerCase().includes(`missing or invalid. request()`)||s.details.toLowerCase().includes(`did not match any variant of untagged enum`)||s.details.toLowerCase().includes(`account upgraded to unsupported contract`)||s.details.toLowerCase().includes(`eip-7702 not supported`)||s.details.toLowerCase().includes(`unsupported wc_ method`)||s.details.toLowerCase().includes(`feature toggled misconfigured`)||s.details.toLowerCase().includes(`jsonrpcengine: response has no error or result for request`))){if(u&&Object.values(u).some(e=>!e.optional)){let e="non-optional `capabilities` are not supported on fallback to `eth_sendTransaction`.";throw new Qo(new E(e,{details:e}))}if(o&&d.length>1){let e="`forceAtomic` is not supported on fallback to `eth_sendTransaction`.";throw new is(new E(e,{details:e}))}let t=[];for(let n of d){let i=Bm(e,{account:l,chain:r,data:n.data,to:n.to,value:n.value?Tn(n.value):void 0});t.push(i),a>0&&await new Promise(e=>setTimeout(e,a))}let n=await Promise.allSettled(t);if(n.every(e=>e.status===`rejected`))throw n[0].reason;return{id:Di([...n.map(e=>e.status===`fulfilled`?e.value:Jm),O(r.id,{size:32}),qm])}}throw Pd(n,{...t,account:l,chain:t.chain})}}Ii(),Cn(),On();async function Xm(e,t){async function n(t){if(t.endsWith(`5792579257925792579257925792579257925792579257925792579257925792`)){let n=Sn(Fi(t,-64,-32)),r=Fi(t,0,-64).slice(2).match(/.{1,64}/g),i=await Promise.all(r.map(t=>Jm.slice(2)===t?void 0:e.request({method:`eth_getTransactionReceipt`,params:[`0x${t}`]},{dedupe:!0}))),a=i.some(e=>e===null)?100:i.every(e=>e?.status===`0x1`)?200:i.every(e=>e?.status===`0x0`)?500:600;return{atomic:!1,chainId:Dn(n),receipts:i.filter(Boolean),status:a,version:`2.0.0`}}return e.request({method:`wallet_getCallsStatus`,params:[t]})}let{atomic:r=!1,chainId:i,receipts:a,version:o=`2.0.0`,...s}=await n(t.id),[c,l]=(()=>{let e=s.status;return e>=100&&e<200?[`pending`,e]:e>=200&&e<300?[`success`,e]:e>=300&&e<700?[`failure`,e]:e===`CONFIRMED`?[`success`,200]:e===`PENDING`?[`pending`,100]:[void 0,e]})();return{...s,atomic:r,chainId:i?Dn(i):void 0,receipts:a?.map(e=>({...e,blockNumber:Tn(e.blockNumber),gasUsed:Tn(e.gasUsed),status:Wm[e.status]}))??[],statusCode:l,status:c,version:o}}D(),Wp(),Wa();async function Zm(e,t){let{id:n,pollingInterval:r=e.pollingInterval,status:i=({statusCode:e})=>e===200||e>=300,retryCount:a=4,retryDelay:o=({count:e})=>~~(1<{let s=Om(async()=>{let r=e=>{clearTimeout(p),s(),e(),m()};try{let s=await Um(async()=>{let t=await T(e,Xm,`getCallsStatus`)({id:n});if(c&&t.status===`failure`)throw new Hm(t);return t},{retryCount:a,delay:o});if(!i(s))return;r(()=>t.resolve(s))}catch(e){r(()=>t.reject(e))}},{interval:r,emitOnBegin:!0});return s});return p=s?setTimeout(()=>{m(),clearTimeout(p),f(new Qm({id:n}))},s):void 0,await u}var Qm=class extends E{constructor({id:e}){super(`Timed out while waiting for call bundle with id "${e}" to be confirmed.`,{name:`WaitForCallsStatusTimeoutError`})}},$m=256,eh=$m,th;function nh(e=11){if(!th||eh+e>$m*2){th=``,eh=0;for(let e=0;e<$m;e++)th+=(256+Math.random()*256|0).toString(16).substring(1)}return th.substring(eh,eh+++e)}oa();function rh(e){let{batch:t,chain:n,ccipRead:r,dataSuffix:i,key:a=`base`,name:o=`Base Client`,type:s=`base`}=e,c=e.experimental_blockTag??(typeof n?.experimental_preconfirmationTime==`number`?`pending`:void 0),l=n?.blockTime??12e3,u=Math.min(Math.max(Math.floor(l/2),500),4e3),d=e.pollingInterval??u,f=e.cacheTime??d,p=e.account?aa(e.account):void 0,{config:m,request:h,value:g}=e.transport({account:p,chain:n,pollingInterval:d}),_={account:p,batch:t,cacheTime:f,ccipRead:r,chain:n,dataSuffix:i,key:a,name:o,pollingInterval:d,request:h,transport:{...m,...g},type:s,uid:nh(),...c?{experimental_blockTag:c}:{}};function v(e){return t=>{let n=t(e);for(let e in _)delete n[e];let r={...e,...n};return Object.assign(r,{extend:v(r)})}}return Object.assign(_,{extend:v(_)})}k();async function ih(e,{address:t,blockNumber:n,blockTag:r=`latest`}){let i=n===void 0?void 0:O(n),a=await e.request({method:`eth_getCode`,params:[t,i||r]},{dedupe:!!i});if(a!==`0x`)return a}D();var ah=class extends E{constructor({address:e}){super(`No EIP-712 domain found on contract "${e}".`,{metaMessages:[`Ensure that:`,`- The contract is deployed at the address "${e}".`,"- `eip712Domain()` function exists on the contract.","- `eip712Domain()` function matches signature to ERC-5267 specification."],name:`Eip712DomainNotFoundError`})}};async function oh(e,t){let{address:n,factory:r,factoryData:i}=t;try{let[t,a,o,s,c,l,u]=await T(e,Sm,`readContract`)({abi:sh,address:n,functionName:`eip712Domain`,factory:r,factoryData:i});return{domain:{name:a,version:o,chainId:Number(s),verifyingContract:c,salt:l},extensions:u,fields:t}}catch(e){let t=e;throw t.name===`ContractFunctionExecutionError`&&t.cause.name===`ContractFunctionZeroDataError`?new ah({address:n}):t}}var sh=[{inputs:[],name:`eip712Domain`,outputs:[{name:`fields`,type:`bytes1`},{name:`name`,type:`string`},{name:`version`,type:`string`},{name:`chainId`,type:`uint256`},{name:`verifyingContract`,type:`address`},{name:`salt`,type:`bytes32`},{name:`extensions`,type:`uint256[]`}],stateMutability:`view`,type:`function`}];$u(),gi(),D(),Fp(),Pu(),Ei(),Lt(),Ii(),On();function ch(e){let{authorizationList:t}=e;if(t)for(let e of t){let{chainId:t}=e,n=e.address;if(!Ci(n))throw new hi({address:n});if(t<0)throw new Pp({chainId:t})}uh(e)}function lh(e){let{blobVersionedHashes:t}=e;if(t){if(t.length===0)throw new Od;for(let e of t){let t=It(e),n=Dn(ji(e,0,1));if(t!==32)throw new kd({hash:e,size:t});if(n!==1)throw new Ad({hash:e,version:n})}}uh(e)}function uh(e){let{chainId:t,maxPriorityFeePerGas:n,maxFeePerGas:r,to:i}=e;if(t<=0)throw new Pp({chainId:t});if(i&&!Ci(i))throw new hi({address:i});if(r&&r>Qu)throw new Cu({maxFeePerGas:r});if(n&&r&&n>r)throw new Mu({maxFeePerGas:r,maxPriorityFeePerGas:n})}function dh(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(t<=0)throw new Pp({chainId:t});if(a&&!Ci(a))throw new hi({address:a});if(n||i)throw new E("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid EIP-2930 Transaction attribute.");if(r&&r>Qu)throw new Cu({maxFeePerGas:r})}function fh(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a}=e;if(a&&!Ci(a))throw new hi({address:a});if(t!==void 0&&t<=0)throw new Pp({chainId:t});if(n||i)throw new E("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid Legacy Transaction attribute.");if(r&&r>Qu)throw new Cu({maxFeePerGas:r})}gi(),_o(),Ei();function ph(e){if(!e||e.length===0)return[];let t=[];for(let n=0;njn(e)),n=e.kzg,r=_d({blobs:t,kzg:n});f===void 0&&(f=Sd({commitments:r})),p===void 0&&(p=Md({blobs:t,commitments:r,proofs:vd({blobs:t,commitments:r,kzg:n})}))}let m=ph(u),h=[O(n),i?O(i):`0x`,l?O(l):`0x`,c?O(c):`0x`,r?O(r):`0x`,a??`0x`,o?O(o):`0x`,d??`0x`,m,s?O(s):`0x`,f??[],...bh(e,t)],g=[],_=[],v=[];if(p)for(let e=0;e{if(t.v>=35n)return(t.v-35n)/2n>0?t.v:27n+(t.v===35n?0n:1n);if(n>0)return BigInt(n*2)+BigInt(35n+t.v-27n);let e=27n+(t.v===27n?0n:1n);if(t.v!==e)throw new co({v:t.v});return e})(),r=Sn(t.r),i=Sn(t.s);l=[...l,O(e),r===`0x00`?`0x`:r,i===`0x00`?`0x`:i]}else n>0&&(l=[...l,O(n),`0x`,`0x`]);return mu(l)}function bh(e,t){let n=t??e,{v:r,yParity:i}=n;if(n.r===void 0||n.s===void 0||r===void 0&&i===void 0)return[];let a=Sn(n.r),o=Sn(n.s);return[typeof i==`number`?i?O(1):`0x`:r===0n?`0x`:r===1n?O(1):r===27n?`0x`:O(1),a===`0x00`?`0x`:a,o===`0x00`?`0x`:o]}k();function xh(e){if(!e||e.length===0)return[];let t=[];for(let n of e){let{chainId:e,nonce:r,...i}=n,a=n.address;t.push([e?kn(e):`0x`,a,r?kn(r):`0x`,...bh({},i)])}return t}Si(),Hd();async function Sh({address:e,authorization:t,signature:n}){return Vd(bi(e),await bu({authorization:t,signature:n}))}vi();var Ch=new _i(8192);function wh(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(Ch.get(n))return Ch.get(n);let r=e().finally(()=>Ch.delete(n));return Ch.set(n,r),r}D(),Ao(),ss(),k(),Wa();function Th(e,t={}){return async(n,r={})=>{let{dedupe:i=!1,methods:a,retryDelay:o=150,retryCount:s=3,uid:c}={...t,...r},{method:l}=n;if(a?.exclude?.includes(l)||a?.include&&!a.include.includes(l))throw new Uo(Error(`method not supported`),{method:l});return wh(()=>Um(async()=>{try{return await e(n)}catch(e){let t=e;switch(t.code){case Po.code:throw new Po(t);case Fo.code:throw new Fo(t);case Io.code:throw new Io(t,{method:n.method});case Lo.code:throw new Lo(t);case Ro.code:throw new Ro(t);case zo.code:throw new zo(t);case Bo.code:throw new Bo(t);case Vo.code:throw new Vo(t);case Ho.code:throw new Ho(t);case Uo.code:throw new Uo(t,{method:n.method});case Wo.code:throw new Wo(t);case Go.code:throw new Go(t);case Ko.code:throw new Ko(t);case qo.code:throw new qo(t);case Jo.code:throw new Jo(t);case Yo.code:throw new Yo(t);case Xo.code:throw new Xo(t);case Zo.code:throw new Zo(t);case Qo.code:throw new Qo(t);case $o.code:throw new $o(t);case es.code:throw new es(t);case ts.code:throw new ts(t);case ns.code:throw new ns(t);case rs.code:throw new rs(t);case is.code:throw new is(t);case 5e3:throw new Ko(t);case as.code:throw new as(t);default:throw e instanceof E?e:new os(t)}}},{delay:({count:e,error:t})=>{if(t&&t instanceof Do){let e=t?.headers?.get(`Retry-After`);if(e?.match(/\d/))return Number.parseInt(e,10)*1e3}return~~(1<Eh(e)}),{enabled:i,id:i?Mn(`${c}.${Ua(n)}`):void 0})}}function Eh(e){return`code`in e&&typeof e.code==`number`?e.code===-1||e.code===Wo.code||e.code===Ro.code:e instanceof Do&&e.status?e.status===403||e.status===408||e.status===413||e.status===429||e.status===500||e.status===502||e.status===503||e.status===504:!0}function L(e){let t={formatters:void 0,fees:void 0,serializers:void 0,...e};function n(e){return t=>{let r=typeof t==`function`?t(e):t,i={...e,...r};return Object.assign(i,{extend:n(i)})}}return Object.assign(t,{extend:n(t)})}function Dh(){return{}}function Oh(e,{errorInstance:t=Error(`timed out`),timeout:n,signal:r}){return new Promise((i,a)=>{(async()=>{let o;try{let s=new AbortController;n>0&&(o=setTimeout(()=>{r?s.abort():a(t)},n)),i(await e({signal:s?.signal||null}))}catch(e){e?.name===`AbortError`&&a(t),a(e)}finally{clearTimeout(o)}})()})}function kh(){return{current:0,take(){return this.current++},reset(){this.current=0}}}var Ah=kh();Ao(),Wa();function jh(e,t={}){let{url:n,headers:r}=Mh(e);return{async request(e){let{body:i,fetchFn:a=t.fetchFn??fetch,onRequest:o=t.onRequest,onResponse:s=t.onResponse,timeout:c=t.timeout??1e4}=e,l={...t.fetchOptions??{},...e.fetchOptions??{}},{headers:u,method:d,signal:f}=l;try{let e=await Oh(async({signal:e})=>{let t={...l,body:Ua(Array.isArray(i)?i.map(e=>({jsonrpc:`2.0`,id:e.id??Ah.take(),...e})):{jsonrpc:`2.0`,id:i.id??Ah.take(),...i}),headers:{...r,"Content-Type":`application/json`,...u},method:d||`POST`,signal:f||(c>0?e:null)},s=new Request(n,t),p=await o?.(s,t)??{...t,url:n};return await a(p.url??n,p)},{errorInstance:new ko({body:i,url:n}),timeout:c,signal:!0});s&&await s(e);let t;if(e.headers.get(`Content-Type`)?.startsWith(`application/json`))t=await e.json();else{t=await e.text();try{t=JSON.parse(t||`{}`)}catch(n){if(e.ok)throw n;t={error:t}}}if(!e.ok){if(typeof t.error?.code==`number`&&typeof t.error?.message==`string`)return t;throw new Do({body:i,details:Ua(t.error)||e.statusText,headers:e.headers,status:e.status,url:n})}return t}catch(e){throw e instanceof Do||e instanceof ko?e:new Do({body:i,cause:e,url:n})}}}}function Mh(e){try{let t=new URL(e),n=(()=>{if(t.username){let e=`${decodeURIComponent(t.username)}:${decodeURIComponent(t.password)}`;return t.username=``,t.password=``,{url:t.toString(),headers:{Authorization:`Basic ${btoa(e)}`}}}})();return{url:t.toString(),...n}}catch{return{url:e}}}var Nh=`Ethereum Signed Message: -`;Ai(),Lt(),k();function Ph(e){let t=typeof e==`string`?Mn(e):typeof e.raw==`string`?e.raw:jn(e.raw);return Di([Mn(`${Nh}${It(t)}`),t])}ri();function Fh(e,t){return ni(Ph(e),t)}Wa(),D();var Ih=class extends E{constructor({domain:e}){super(`Invalid domain "${Ua(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]})}},Lh=class extends E{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{docsPath:`/api/glossary/Errors#typeddatainvalidprimarytypeerror`,metaMessages:["Check that the primary type is a key in `types`."]})}},Rh=class extends E{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`],name:`InvalidStructTypeError`})}};cn(),gi(),Ei(),Lt(),k(),zi(),Wa();function zh(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{let n={...t};for(let t of e){let{name:e,type:r}=t;r===`address`&&(n[e]=n[e].toLowerCase())}return n};return Ua({domain:!i.EIP712Domain||!t?{}:a(i.EIP712Domain,t),message:(()=>{if(r!==`EIP712Domain`)return a(i[r],n)})(),primaryType:r,types:i})}function Bh(e){let{domain:t,message:n,primaryType:r,types:i}=e,a=(e,t)=>{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(Ri);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[e,t,n]=s;O(o,{signed:t===`int`,size:Number.parseInt(n,10)/8})}if(r===`address`&&typeof o==`string`&&!Ci(o))throw new hi({address:o});let c=r.match(Li);if(c){let[e,t]=c;if(t&&It(o)!==Number.parseInt(t,10))throw new nn({expectedSize:Number.parseInt(t,10),givenSize:It(o)})}let l=i[r];l&&(Hh(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new Ih({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new Lh({primaryType:r,types:i})}function Vh({domain:e}){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function Hh(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new Rh({type:e})}Qi(),Ai(),k(),ri();function Uh(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:Vh({domain:t}),...e.types};Bh({domain:t,message:n,primaryType:r,types:i});let a=[`0x1901`];return t&&a.push(Wh({domain:t,types:i})),r!==`EIP712Domain`&&a.push(Gh({data:n,primaryType:r,types:i})),ni(Di(a))}function Wh({domain:e,types:t}){return Gh({data:e,primaryType:`EIP712Domain`,types:t})}function Gh({data:e,primaryType:t,types:n}){return ni(Kh({data:e,primaryType:t,types:n}))}function Kh({data:e,primaryType:t,types:n}){let r=[{type:`bytes32`}],i=[qh({primaryType:t,types:n})];for(let a of n[t]){let[t,o]=Xh({types:n,name:a.name,type:a.type,value:e[a.name]});r.push(t),i.push(o)}return Bi(r,i)}function qh({primaryType:e,types:t}){return ni(kn(Jh({primaryType:e,types:t})))}function Jh({primaryType:e,types:t}){let n=``,r=Yh({primaryType:e,types:t});r.delete(e);let i=[e,...Array.from(r).sort()];for(let e of i)n+=`${e}(${t[e].map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return n}function Yh({primaryType:e,types:t},n=new Set){let r=e.match(/^\w*/u)?.[0];if(n.has(r)||t[r]===void 0)return n;n.add(r);for(let e of t[r])Yh({primaryType:e.type,types:t},n);return n}function Xh({types:e,name:t,type:n,value:r}){if(e[n]!==void 0)return[{type:`bytes32`},ni(Kh({data:r,primaryType:n,types:e}))];if(n===`bytes`)return[{type:`bytes32`},ni(r)];if(n===`string`)return[{type:`bytes32`},ni(kn(r))];if(n.lastIndexOf(`]`)===n.length-1){let i=n.slice(0,n.lastIndexOf(`[`)),a=r.map(n=>Xh({name:t,type:i,types:e,value:n}));return[{type:`bytes32`},ni(Bi(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:n},r]}var Zh={checksum:new class extends Map{constructor(e){super(),Object.defineProperty(this,`maxSize`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&t!==void 0&&(this.delete(e),super.set(e,t)),t}set(e,t){if(super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}(8192)}.checksum;ti(),Hf(),pp();function Qh(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=$r(yf(e));return n===`Bytes`?r:qf(r)}function $h(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=yd(yf(e));return n===`Bytes`?r:qf(r)}function eg(e){return rp(e)&&Qf(e)===32}Hf(),Qd(),pp(),gf();function tg(e,t={}){let{compressed:n}=t,{prefix:r,x:i,y:a}=e;if(n===!1||typeof i==`bigint`&&typeof a==`bigint`){if(r!==4)throw new cg({prefix:r,cause:new ug});return}if(n===!0||typeof i==`bigint`&&a===void 0){if(r!==3&&r!==2)throw new cg({prefix:r,cause:new lg});return}throw new sg({publicKey:e})}function ng(e){let t=(()=>{if(rp(e))return ig(e);if(Pf(e))return rg(e);let{prefix:t,x:n,y:r}=e;return typeof n==`bigint`&&typeof r==`bigint`?{prefix:t??4,x:n,y:r}:{prefix:t,x:n}})();return tg(t),t}function rg(e){return ig(qf(e))}function ig(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new dg({publicKey:e});return e.length===130?{prefix:4,x:BigInt(I(e,0,32)),y:BigInt(I(e,32,64))}:e.length===132?{prefix:Number(I(e,0,1)),x:BigInt(I(e,1,33)),y:BigInt(I(e,33,65))}:{prefix:Number(I(e,0,1)),x:BigInt(I(e,1,33))}}function ag(e,t={}){return xf(og(e,t))}function og(e,t={}){tg(e);let{prefix:n,x:r,y:i}=e,{includePrefix:a=!0}=t;return Wf(a?F(n,{size:1}):`0x`,F(r,{size:32}),typeof i==`bigint`?F(i,{size:32}):`0x`)}var sg=class extends P{constructor({publicKey:e}){super(`Value \`${mf(e)}\` is not a valid public key.`,{metaMessages:[`Public key must contain:`,"- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidError`})}},cg=class extends P{constructor({prefix:e,cause:t}){super(`Prefix "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidPrefixError`})}},lg=class extends P{constructor(){super(`Prefix must be 2 or 3 for compressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidCompressedPrefixError`})}},ug=class extends P{constructor(){super(`Prefix must be 4 for uncompressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidUncompressedPrefixError`})}},dg=class extends P{constructor({publicKey:e}){super(`Value \`${e}\` is an invalid public key size.`,{metaMessages:[`Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).`,`Received ${Qf(Gf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidSerializedSizeError`})}};Hf(),Qd();var fg=/^0x[a-fA-F0-9]{40}$/;function pg(e,t={}){let{strict:n=!0}=t;if(!fg.test(e))throw new yg({address:e,cause:new bg});if(n){if(e.toLowerCase()===e)return;if(mg(e)!==e)throw new yg({address:e,cause:new xg})}}function mg(e){if(Zh.has(e))return Zh.get(e);pg(e,{strict:!1});let t=e.substring(2).toLowerCase(),n=Qh(Sf(t),{as:`Bytes`}),r=t.split(``);for(let e=0;e<40;e+=2)n[e>>1]>>4>=8&&r[e]&&(r[e]=r[e].toUpperCase()),(n[e>>1]&15)>=8&&r[e+1]&&(r[e+1]=r[e+1].toUpperCase());let i=`0x${r.join(``)}`;return Zh.set(e,i),i}function hg(e,t={}){let{checksum:n=!1}=t;return pg(e),n?mg(e):e}function gg(e,t={}){return hg(`0x${Qh(`0x${og(e).slice(4)}`).substring(26)}`,t)}function _g(e,t){return pg(e,{strict:!1}),pg(t,{strict:!1}),e.toLowerCase()===t.toLowerCase()}function vg(e,t={}){let{strict:n=!0}=t??{};try{return pg(e,{strict:n}),!0}catch{return!1}}var yg=class extends P{constructor({address:e,cause:t}){super(`Address "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidAddressError`})}},bg=class extends P{constructor(){super(`Address is not a 20 byte (40 hexadecimal character) value.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidInputError`})}},xg=class extends P{constructor(){super(`Address does not match its checksum counterpart.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidChecksumError`})}},Sg=/^(.*)\[([0-9]*)\]$/,Cg=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,wg=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n));var Tg=2n**256n-1n;Hf(),Qd(),pp();function Eg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=Kg(t.type);if(a){let[n,o]=a;return Ag(e,{...t,type:o},{checksumAddress:r,length:n,staticPosition:i})}if(t.type===`tuple`)return Pg(e,t,{checksumAddress:r,staticPosition:i});if(t.type===`address`)return kg(e,{checksum:r});if(t.type===`bool`)return jg(e);if(t.type.startsWith(`bytes`))return Mg(e,t,{staticPosition:i});if(t.type.startsWith(`uint`)||t.type.startsWith(`int`))return Ng(e,t);if(t.type===`string`)return Fg(e,{staticPosition:i});throw new l_(t.type)}var Dg=32,Og=32;function kg(e,t={}){let{checksum:n=!1}=t;return[(e=>n?mg(e):e)(qf(Df(e.readBytes(32),-20))),32]}function Ag(e,t,n){let{checksumAddress:r,length:i,staticPosition:a}=n;if(!i){let n=a+Af(e.readBytes(Og)),i=n+Dg;e.setPosition(n);let o=Af(e.readBytes(Dg)),s=qg(t),c=0,l=[];for(let n=0;n48?Of(i,{signed:n}):Af(i,{signed:n}),32]}function Pg(e,t,n){let{checksumAddress:r,staticPosition:i}=n,a=t.components.length===0||t.components.some(({name:e})=>!e),o=a?[]:{},s=0;if(qg(t)){let n=i+Af(e.readBytes(Og));for(let i=0;i0?Wf(t,e):t}}if(o)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:Wf(...s.map(({encoded:e})=>e))}}function Vg(e,{type:t}){let[,n]=t.split(`bytes`),r=Qf(e);if(!n){let t=e;return r%32!=0&&(t=Xf(t,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:Wf(Yf(F(r,{size:32})),t)}}if(r!==Number.parseInt(n,10))throw new o_({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:Xf(e)}}function Hg(e){if(typeof e!=`boolean`)throw new P(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:Yf(Kf(e))}}function Ug(e,{signed:t,size:n}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function Kg(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}function qg(e){let{type:t}=e;if(t===`string`||t===`bytes`||t.endsWith(`[]`))return!0;if(t===`tuple`)return e.components?.some(qg);let n=Kg(e.type);return!!(n&&qg({...e,type:n[1]}))}Qd();var Jg={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new Qg({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new Zg({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new Xg({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new Xg({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function Yg(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(Jg);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var Xg=class extends P{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.NegativeOffsetError`})}},Zg=class extends P{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.PositionOutOfBoundsError`})}},Qg=class extends P{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.RecursiveReadLimitExceededError`})}};kt(),Hf(),Qd(),pp();function $g(e,t,n={}){let{as:r=`Array`,checksumAddress:i=!1}=n,a=typeof t==`string`?xf(t):t,o=Yg(a);if(Ef(a)===0&&e.length>0)throw new i_;if(Ef(a)&&Ef(a)<32)throw new r_({data:typeof t==`string`?t:qf(t),parameters:e,size:Ef(a)});let s=0,c=r===`Array`?[]:{};for(let t=0;t{if(typeof e==`string`){if(e.length>3&&e.length%2!=0)throw new lp(e);return xf(e)}return e})(),{recursiveReadLimit:1/0}),n)}function f_(e,t=`Hex`){if(e.bytes.length===0)return t===`Hex`?qf(e.bytes):e.bytes;let n=e.readByte();if(n<128&&e.decrementPosition(1),n<192){let r=p_(e,n,128),i=e.readBytes(r);return t===`Hex`?qf(i):i}return m_(e,p_(e,n,192),t)}function p_(e,t,n){if(n===128&&t<128)return 1;if(t<=n+55)return t-n;if(t===n+55+1)return e.readUint8();if(t===n+55+2)return e.readUint16();if(t===n+55+3)return e.readUint24();if(t===n+55+4)return e.readUint32();throw new P(`Invalid RLP prefix`)}function m_(e,t,n){let r=e.position,i=[];for(;e.position-r__(e))):y_(e)}function v_(e){let t=e.reduce((e,t)=>e+t.length,0),n=b_(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function y_(e){let t=typeof e==`string`?xf(e):e,n=b_(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function b_(e){if(e<=255)return 1;if(e<=65535)return 2;if(e<=16777215)return 3;if(e<=4294967295)return 4;throw new P(`Length is too large.`)}Qd(),pp(),gf();function x_(e,t={}){let{recovered:n}=t;if(e.r===void 0||e.s===void 0||n&&e.yParity===void 0)throw new F_({signature:e});if(e.r<0n||e.r>Tg)throw new I_({value:e.r});if(e.s<0n||e.s>Tg)throw new L_({value:e.s});if(typeof e.yParity==`number`&&e.yParity!==0&&e.yParity!==1)throw new R_({value:e.yParity})}function S_(e){return C_(qf(e))}function C_(e){if(e.length!==130&&e.length!==132)throw new P_({signature:e});let t=BigInt(I(e,0,32)),n=BigInt(I(e,32,64)),r=(()=>{let t=Number(`0x${e.slice(130)}`);if(!Number.isNaN(t))try{return M_(t)}catch{throw new R_({value:t})}})();return r===void 0?{r:t,s:n}:{r:t,s:n,yParity:r}}function w_(e){if(e.r!==void 0&&e.s!==void 0)return T_(e)}function T_(e){let t=typeof e==`string`?C_(e):e instanceof Uint8Array?S_(e):typeof e.r==`string`?D_(e):e.v?E_(e):{r:e.r,s:e.s,...e.yParity===void 0?{}:{yParity:e.yParity}};return x_(t),t}function E_(e){return{r:e.r,s:e.s,yParity:M_(e.v)}}function D_(e){let t=(()=>{let t=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof t==`number`&&typeof n!=`number`&&(n=M_(t)),typeof n!=`number`)throw new R_({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function O_(e){let[t,n,r]=e;return T_({r:n===`0x`?0n:BigInt(n),s:r===`0x`?0n:BigInt(r),yParity:t===`0x`?0:Number(t)})}function k_(e){x_(e);let t=e.r,n=e.s;return Wf(F(t,{size:32}),F(n,{size:32}),typeof e.yParity==`number`?F(N_(e.yParity),{size:1}):`0x`)}function A_(e){let{r:t,s:n,yParity:r}=e;return{r:F(t,{size:32}),s:F(n,{size:32}),yParity:r===0?`0x0`:`0x1`}}function j_(e){let{r:t,s:n,yParity:r}=e;return[r?`0x01`:`0x`,t===0n?`0x`:$f(F(t)),n===0n?`0x`:$f(F(n))]}function M_(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2==0?1:0;throw new z_({value:e})}function N_(e){if(e===0)return 27;if(e===1)return 28;throw new R_({value:e})}var P_=class extends P{constructor({signature:e}){super(`Value \`${e}\` is an invalid signature size.`,{metaMessages:[`Expected: 64 bytes or 65 bytes.`,`Received ${Qf(Gf(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSerializedSizeError`})}},F_=class extends P{constructor({signature:e}){super(`Signature \`${mf(e)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.MissingPropertiesError`})}},I_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidRError`})}},L_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSError`})}},R_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidYParityError`})}},z_=class extends P{constructor({value:e}){super(`Value \`${e}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidVError`})}};pp();function B_(e,t={}){return typeof e.chainId==`string`?V_(e):{...e,...t.signature}}function V_(e){let{address:t,chainId:n,nonce:r}=e,i=w_(e);return{address:t,chainId:Number(n),nonce:BigInt(r),...i}}function H_(e){return e.map(V_)}function U_(e){let{address:t,chainId:n,nonce:r,...i}=e;return{address:t,chainId:F(n),nonce:F(r),...A_(i)}}function W_(e){return e.map(U_)}uu(),Hf(),pp();function G_(e){return gg(K_(e))}function K_(e){let{payload:t,signature:n}=e,{r,s:i,yParity:a}=n;return ng(new Zl.Signature(BigInt(r),BigInt(i)).addRecoveryBit(a).recoverPublicKey(Gf(t).substring(2)))}function q_(e){let{address:t,hash:n,payload:r,publicKey:i,signature:a}=e;return t?_g(t,G_({payload:r,signature:a})):Zl.verify(a,yf(r),ag(i),...n?[{prehash:!0,lowS:!0}]:[])}Qd(),pp();var J_=n_(`(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data`);function Y_(e){if(typeof e==`string`){if(I(e,-32)!==`0x8010801080108010801080108010801080108010801080108010801080108010`)throw new Q_(e)}else x_(e.authorization)}function X_(e){Y_(e);let t=tp(I(e,-64,-32)),n=I(e,-t-64,-64),r=I(e,0,-t-64),[i,a,o]=$g(J_,n);return{authorization:B_({address:i.delegation,chainId:Number(i.chainId),nonce:i.nonce,yParity:i.yParity,r:i.r,s:i.s}),signature:r,...o&&o!==`0x`?{data:o,to:a}:{}}}function Z_(e){try{return Y_(e),!0}catch{return!1}}var Q_=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc8010.InvalidWrappedSignatureError`})}};_o(),k();async function $_(e,{blockHash:t,blockNumber:n,blockTag:r,hash:i,index:a,sender:o,nonce:s}){let c=r||`latest`,l=n===void 0?void 0:O(n),u=null;if(i?u=await e.request({method:`eth_getTransactionByHash`,params:[i]},{dedupe:!0}):t?u=await e.request({method:`eth_getTransactionByBlockHashAndIndex`,params:[t,O(a)]},{dedupe:!0}):(l||c)&&typeof a==`number`?u=await e.request({method:`eth_getTransactionByBlockNumberAndIndex`,params:[l||c,O(a)]},{dedupe:!!l}):o&&typeof s==`number`&&(u=await e.request({method:`eth_getTransactionBySenderAndNonce`,params:[o,O(s)]},{dedupe:!0})),!u)throw new po({blockHash:t,blockNumber:n,blockTag:c,hash:i,index:a});return(e.chain?.formatters?.transaction?.format||sd)(u,`getTransaction`)}_o();async function ev(e,{hash:t}){let n=await e.request({method:`eth_getTransactionReceipt`,params:[t]},{dedupe:!0});if(!n)throw new mo({hash:t});return(e.chain?.formatters?.transactionReceipt?.format||Gm)(n,`getTransactionReceipt`)}Qd();function tv(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;onv(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>nv(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function rv(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return rv(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&vg(n[r],{strict:!1}))return o}}kt(),Qd(),pp();function iv(e,t={}){let{prepare:n=!0}=t,r=Array.isArray(e)||typeof e==`string`?Tt(e):e;return{...r,...n?{hash:cv(r)}:{}}}function av(e,t,n){let{args:r=[],prepare:i=!0}=n??{},a=rp(t,{strict:!1}),o=e.filter(e=>a?e.type===`function`||e.type===`error`?ov(e)===I(t,0,4):e.type===`event`?cv(e)===t:!1:`name`in e&&e.name===t);if(o.length===0)throw new uv({name:t});if(o.length===1)return{...o[0],...i?{hash:cv(o[0])}:{}};let s;for(let e of o)if(`inputs`in e){if(!r||r.length===0){if(!e.inputs||e.inputs.length===0)return{...e,...i?{hash:cv(e)}:{}};continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===r.length&&r.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?nv(t,r):!1})){if(s&&`inputs`in s&&s.inputs){let t=rv(e.inputs,s.inputs,r);if(t)throw new lv({abiItem:e,type:t[0]},{abiItem:s,type:t[1]})}s=e}}let c=(()=>{if(s)return s;let[e,...t]=o;return{...e,overloads:t}})();if(!c)throw new uv({name:t});return{...c,...i?{hash:cv(c)}:{}}}function ov(...e){return I(cv((()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})()),0,4)}function sv(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})();return tv(typeof t==`string`?t:ue(t))}function cv(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return av(t,n)}return e[0]})();return typeof t!=`string`&&`hash`in t&&t.hash?t.hash:Qh(Jf(sv(t)))}var lv=class extends P{constructor(e,t){super(`Found ambiguous types in overloaded ABI Items.`,{metaMessages:[`\`${e.type}\` in \`${tv(ue(e.abiItem))}\`, and`,`\`${t.type}\` in \`${tv(ue(t.abiItem))}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.AmbiguityError`})}},uv=class extends P{constructor({name:e,data:t,type:n=`item`}){let r=e?` with name "${e}"`:t?` with data "${t}"`:``;super(`ABI ${n}${r} not found.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.NotFoundError`})}},dv=`0x0000000000000000000000000000000000000000`;Qd(),pp();var fv=`0x6492649264926492649264926492649264926492649264926492649264926492`;function pv(e){if(I(e,-32)!==`0x6492649264926492649264926492649264926492649264926492649264926492`)throw new gv(e)}function mv(e){let{data:t,signature:n,to:r}=e;return Wf(e_(n_(`address, bytes, bytes`),[r,t,n]),fv)}function hv(e){try{return pv(e),!0}catch{return!1}}var gv=class extends P{constructor(e){super(`Value \`${e}\` is an invalid ERC-6492 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc6492.InvalidWrappedSignatureError`})}};uu(),On(),Un();function _v({r:e,s:t,to:n=`hex`,v:r,yParity:i}){let a=(()=>{if(i===0||i===1)return i;if(r&&(r===27n||r===28n||r>=35n))return r%2n==0n?1:0;throw Error("Invalid `v` or `yParity` value")})(),o=`0x${new Zl.Signature(Tn(e),Tn(t)).toCompactHex()}${a===0?`1b`:`1c`}`;return n===`hex`?o:Rn(o)}Cp(),kp(),Eo(),Rp(),da(),Si(),Hd(),Ai(),Ft(),On(),k(),xm();async function vv(e,t){let{address:n,chain:r=e.chain,hash:i,erc6492VerifierAddress:a=t.universalSignatureVerifierAddress??r?.contracts?.erc6492Verifier?.address,multicallAddress:o=t.multicallAddress??r?.contracts?.multicall3?.address,mode:s=`auto`}=t;if(r?.verifyHash)return await r.verifyHash(e,t);let c=(()=>{let e=t.signature;return Pt(e)?e:typeof e==`object`&&`r`in e&&`s`in e?_v(e):jn(e)})();try{if(s===`eoa`)try{if(Vd(bi(n),await pu({hash:i,signature:c})))return!0}catch{}return Z_(c)?await yv(e,{...t,multicallAddress:o,signature:c}):await bv(e,{...t,verifierAddress:a,signature:c})}catch(e){if(s!==`eoa`)try{if(Vd(bi(n),await pu({hash:i,signature:c})))return!0}catch{}if(e instanceof Sv)return!1;throw e}}async function yv(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,multicallAddress:o}=t,{authorization:s,data:c,signature:l,to:u}=X_(t.signature);if(await ih(e,{address:n,blockNumber:r,blockTag:i})===ki([`0xef0100`,s.address]))return await xv(e,{address:n,blockNumber:r,blockTag:i,hash:a,signature:l});let d={address:s.address,chainId:Number(s.chainId),nonce:Number(s.nonce),r:O(s.r,{size:32}),s:O(s.s,{size:32}),yParity:s.yParity};if(!await Sh({address:n,authorization:d}))throw new Sv;let f=await T(e,Sm,`readContract`)({...o?{address:o}:{code:Op},authorizationList:[d],abi:vp,blockNumber:r,blockTag:`pending`,functionName:`aggregate3`,args:[[...c?[{allowFailure:!0,target:u??n,callData:c}]:[],{allowFailure:!0,target:n,callData:ua({abi:xp,functionName:`isValidSignature`,args:[a,l]})}]]});if((f[f.length-1]?.returnData)?.startsWith(`0x1626ba7e`))return!0;throw new Sv}async function bv(e,t){let{address:n,factory:r,factoryData:i,hash:a,signature:o,verifierAddress:s,...c}=t,l=await(async()=>!r&&!i||hv(o)?o:mv({data:i,signature:o,to:r}))(),u=s?{to:s,data:ua({abi:Sp,functionName:`isValidSig`,args:[n,a,l]}),...c}:{data:Ip({abi:Sp,args:[n,a,l],bytecode:Dp}),...c},{data:d}=await T(e,hm,`call`)(u).catch(e=>{throw e instanceof xo?new Sv:e});if(En(d??`0x0`))return!0;throw new Sv}async function xv(e,t){let{address:n,blockNumber:r,blockTag:i,hash:a,signature:o}=t;if((await T(e,Sm,`readContract`)({address:n,abi:xp,args:[a,o],blockNumber:r,blockTag:i,functionName:`isValidSignature`}).catch(e=>{throw e instanceof So?new Sv:e})).startsWith(`0x1626ba7e`))return!0;throw new Sv}var Sv=class extends Error{};On(),Wa();function Cv(e,{emitOnBegin:t=!1,emitMissed:n=!1,onBlockNumber:r,onError:i,poll:a,pollingInterval:o=e.pollingInterval}){let s=a===void 0?!(e.transport.type===`webSocket`||e.transport.type===`ipc`||e.transport.type===`fallback`&&(e.transport.transports[0].config.type===`webSocket`||e.transport.transports[0].config.type===`ipc`)):a,c;return s?Em(Ua([`watchBlockNumber`,e.uid,t,n,o]),{onBlockNumber:r,onError:i},r=>Om(async()=>{try{let t=await T(e,Pm,`getBlockNumber`)({cacheTime:0});if(c!==void 0){if(t===c)return;if(t-c>1&&n)for(let e=c+1n;ec)&&(r.onBlockNumber(t,c),c=t)}catch(e){r.onError?.(e)}},{emitOnBegin:t,interval:o})):Em(Ua([`watchBlockNumber`,e.uid,t,n]),{onBlockNumber:r,onError:i},t=>{let n=!0,r=()=>n=!1;return(async()=>{try{let{unsubscribe:i}=await(()=>{if(e.transport.type===`fallback`){let t=e.transport.transports.find(e=>e.config.type===`webSocket`||e.config.type===`ipc`);return t?t.value:e.transport}return e.transport})().subscribe({params:[`newHeads`],onData(e){if(!n)return;let r=Tn(e.result?.number);t.onBlockNumber(r,c),c=r},onError(e){t.onError?.(e)}});r=i,n||r()}catch(e){i?.(e)}})(),()=>r()})}_o(),Wp(),Wa();async function wv(e,t){let{checkReplacement:n=!0,confirmations:r=1,hash:i,onReplaced:a,retryCount:o=6,retryDelay:s=({count:e})=>~~(1<{g?.(),h?.(),y(new go({hash:i}))},c):void 0;return h=Em(l,{onReplaced:a,resolve:v,reject:y},async t=>{if(p=await T(e,ev,`getTransactionReceipt`)({hash:i}).catch(()=>void 0),p&&r<=1){clearTimeout(b),t.resolve(p),h?.();return}g=T(e,Cv,`watchBlockNumber`)({emitMissed:!0,emitOnBegin:!0,poll:!0,pollingInterval:u,async onBlockNumber(a){let c=e=>{clearTimeout(b),g?.(),e(),h?.()},l=a;if(!m)try{if(p){if(r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p));return}if(n&&!d&&(m=!0,await Um(async()=>{d=await T(e,$_,`getTransaction`)({hash:i}),d.blockNumber&&(l=d.blockNumber)},{delay:s,retryCount:o}),m=!1),p=await T(e,ev,`getTransactionReceipt`)({hash:i}),r>1&&(!p.blockNumber||l-p.blockNumber+1nt.resolve(p))}catch(n){if(n instanceof po||n instanceof mo){if(!d){m=!1;return}try{f=d,m=!0;let n=await Um(()=>T(e,fd,`getBlock`)({blockNumber:l,includeTransactions:!0}),{delay:s,retryCount:o,shouldRetry:({error:e})=>e instanceof ad});m=!1;let i=n.transactions.find(({from:e,nonce:t})=>e===f.from&&t===f.nonce);if(!i||(p=await T(e,ev,`getTransactionReceipt`)({hash:i.hash}),r>1&&(!p.blockNumber||l-p.blockNumber+1n{t.onReplaced?.({reason:a,replacedTransaction:f,transaction:i,transactionReceipt:p}),t.resolve(p)})}catch(e){c(()=>t.reject(e))}}else c(()=>t.reject(n))}}})}),_}_o();async function Tv(e,{serializedTransaction:t,throwOnReceiptRevert:n,timeout:r}){let i=await e.request({method:`eth_sendRawTransactionSync`,params:r?[t,r]:[t]},{retryCount:0}),a=(e.chain?.formatters?.transactionReceipt?.format||Gm)(i);if(a.status===`reverted`&&n)throw new ho({receipt:a});return a}k();async function Ev(e,{chain:t}){let{id:n,name:r,nativeCurrency:i,rpcUrls:a,blockExplorers:o}=t;await e.request({method:`wallet_addEthereumChain`,params:[{chainId:O(n),chainName:r,nativeCurrency:i,rpcUrls:a.default.http,blockExplorerUrls:o?Object.values(o).map(({url:e})=>e):void 0}]},{dedupe:!0,retryCount:0})}Rp();function Dv(e,t){let{abi:n,args:r,bytecode:i,...a}=t,o=Ip({abi:n,args:r,bytecode:i});return Bm(e,{...a,...a.authorizationList?{to:null}:{},data:o})}Si();async function Ov(e){return e.account?.type===`local`?[e.account.address]:(await e.request({method:`eth_accounts`},{dedupe:!0})).map(e=>yi(e))}oa(),k();async function kv(e,t={}){let{account:n=e.account,chainId:r}=t,i=n?aa(n):void 0,a=r?[i?.address,[O(r)]]:[i?.address],o=await e.request({method:`wallet_getCapabilities`,params:a}),s={};for(let[e,t]of Object.entries(o)){s[Number(e)]={};for(let[n,r]of Object.entries(t))n===`addSubAccount`&&(n=`unstable_addSubAccount`),s[Number(e)][n]=r}return typeof r==`number`?s[r]:s}async function Av(e){return await e.request({method:`wallet_getPermissions`},{dedupe:!0})}oa(),Hd();async function jv(e,t){let{account:n=e.account,chainId:r,nonce:i}=t;if(!n)throw new Fm({docsPath:`/docs/eip7702/prepareAuthorization`});let a=aa(n),o=(()=>{if(t.executor)return t.executor===`self`?t.executor:aa(t.executor)})(),s={address:t.contractAddress??t.address,chainId:r,nonce:i};return s.chainId===void 0&&(s.chainId=e.chain?.id??await T(e,Fd,`getChainId`)({})),s.nonce===void 0&&(s.nonce=await T(e,gd,`getTransactionCount`)({address:a.address,blockTag:`pending`}),(o===`self`||o?.address&&Vd(o.address,a.address))&&(s.nonce+=1)),s}Si();async function Mv(e){return(await e.request({method:`eth_requestAccounts`},{dedupe:!0,retryCount:0})).map(e=>bi(e))}async function Nv(e,t){return e.request({method:`wallet_requestPermissions`,params:[t]},{retryCount:0})}async function Pv(e,t){let{chain:n=e.chain}=t,r=t.timeout??Math.max((n?.blockTime??0)*3,5e3),i=await T(e,Ym,`sendCalls`)(t);return await T(e,Zm,`waitForCallsStatus`)({...t,id:i.id,timeout:r})}oa(),D(),_o(),Ai(),zu(),Ku(),vi(),td();var Fv=new _i(128);async function Iv(e,t){let{account:n=e.account,assertChainId:r=!0,chain:i=e.chain,accessList:a,authorizationList:o,blobs:s,data:c,dataSuffix:l=typeof e.dataSuffix==`string`?e.dataSuffix:e.dataSuffix?.value,gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,pollingInterval:g,throwOnReceiptRevert:_,type:v,value:y,...b}=t,x=t.timeout??Math.max((i?.blockTime??0)*3,5e3);if(n===void 0)throw new Fm({docsPath:`/docs/actions/wallet/sendTransactionSync`});let S=n?aa(n):null;try{ed(t);let n=await(async()=>{if(t.to)return t.to;if(t.to!==null&&o&&o.length>0)return await bu({authorization:o[0]}).catch(()=>{throw new E("`to` is required. Could not infer from `authorizationList`.")})})();if(S?.type===`json-rpc`||S===null){let t;i!==null&&(t=await T(e,Fd,`getChainId`)({}),r&&Lm({currentChainId:t,chain:i}));let ee=e.chain?.formatters?.transactionRequest?.format,C=(ee||Hu)({...Ru(b,{format:ee}),accessList:a,account:S,authorizationList:o,blobs:s,chainId:t,data:c&&Di([c,l??`0x`]),gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,to:n,type:v,value:y},`sendTransaction`),te=Fv.get(e.uid),ne=te?`wallet_sendTransaction`:`eth_sendTransaction`,re=await(async()=>{try{return await e.request({method:ne,params:[C]},{retryCount:0})}catch(t){if(te===!1)throw t;let n=t;if(n.name===`InvalidInputRpcError`||n.name===`InvalidParamsRpcError`||n.name===`MethodNotFoundRpcError`||n.name===`MethodNotSupportedRpcError`)return await e.request({method:`wallet_sendTransaction`,params:[C]},{retryCount:0}).then(t=>(Fv.set(e.uid,!0),t)).catch(t=>{let r=t;throw r.name===`MethodNotFoundRpcError`||r.name===`MethodNotSupportedRpcError`?(Fv.set(e.uid,!1),n):r});throw n}})(),ie=await T(e,wv,`waitForTransactionReceipt`)({checkReplacement:!1,hash:re,pollingInterval:g,timeout:x});if(_&&ie.status===`reverted`)throw new ho({receipt:ie});return ie}if(S?.type===`local`){let r=await T(e,zd,`prepareTransactionRequest`)({account:S,accessList:a,authorizationList:o,blobs:s,chain:i,data:c&&Di([c,l??`0x`]),gas:u,gasPrice:d,maxFeePerBlobGas:f,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:h,nonceManager:S.nonceManager,parameters:[...Ld,`sidecars`],type:v,value:y,...b,to:n}),g=i?.serializers?.transaction,x=await S.signTransaction(r,{serializer:g});return await T(e,Tv,`sendRawTransactionSync`)({serializedTransaction:x,throwOnReceiptRevert:_,timeout:t.timeout})}throw S?.type===`smart`?new Im({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:`/docs/actions/bundler/sendUserOperation`,type:`smart`}):new Im({docsPath:`/docs/actions/wallet/sendTransactionSync`,type:S?.type})}catch(e){throw e instanceof Im?e:Pd(e,{...t,account:S,chain:t.chain||void 0})}}async function Lv(e,t){let{id:n}=t;await e.request({method:`wallet_showCallsStatus`,params:[n]})}oa();async function Rv(e,t){let{account:n=e.account}=t;if(!n)throw new Fm({docsPath:`/docs/eip7702/signAuthorization`});let r=aa(n);if(!r.signAuthorization)throw new Im({docsPath:`/docs/eip7702/signAuthorization`,metaMessages:["The `signAuthorization` Action does not support JSON-RPC Accounts."],type:r.type});let i=await jv(e,t);return r.signAuthorization(i)}oa(),k();async function zv(e,{account:t=e.account,message:n}){if(!t)throw new Fm({docsPath:`/docs/actions/wallet/signMessage`});let r=aa(t);if(r.signMessage)return r.signMessage({message:n});let i=typeof n==`string`?Mn(n):n.raw instanceof Uint8Array?kn(n.raw):n.raw;return e.request({method:`personal_sign`,params:[i,r.address]},{retryCount:0})}oa(),k(),Ku(),td();async function Bv(e,t){let{account:n=e.account,chain:r=e.chain,...i}=t;if(!n)throw new Fm({docsPath:`/docs/actions/wallet/signTransaction`});let a=aa(n);ed({account:a,...t});let o=await T(e,Fd,`getChainId`)({});r!==null&&Lm({currentChainId:o,chain:r});let s=(r?.formatters||e.chain?.formatters)?.transactionRequest?.format||Hu;return a.signTransaction?a.signTransaction({...i,chainId:o},{serializer:e.chain?.serializers?.transaction}):await e.request({method:`eth_signTransaction`,params:[{...s({...i,account:a},`signTransaction`),chainId:O(o),from:a.address}]},{retryCount:0})}oa();async function Vv(e,t){let{account:n=e.account,domain:r,message:i,primaryType:a}=t;if(!n)throw new Fm({docsPath:`/docs/actions/wallet/signTypedData`});let o=aa(n),s={EIP712Domain:Vh({domain:r}),...t.types};if(Bh({domain:r,message:i,primaryType:a,types:s}),o.signTypedData)return o.signTypedData({domain:r,message:i,primaryType:a,types:s});let c=zh({domain:r,message:i,primaryType:a,types:s});return e.request({method:`eth_signTypedData_v4`,params:[o.address,c]},{retryCount:0})}k();async function Hv(e,{id:t}){await e.request({method:`wallet_switchEthereumChain`,params:[{chainId:O(t)}]},{retryCount:0})}async function Uv(e,t){return await e.request({method:`wallet_watchAsset`,params:t},{retryCount:0})}async function Wv(e,t){return Vm.internal(e,Iv,`sendTransactionSync`,t)}function Gv(e){return{addChain:t=>Ev(e,t),deployContract:t=>Dv(e,t),fillTransaction:t=>Id(e,t),getAddresses:()=>Ov(e),getCallsStatus:t=>Xm(e,t),getCapabilities:t=>kv(e,t),getChainId:()=>Fd(e),getPermissions:()=>Av(e),prepareAuthorization:t=>jv(e,t),prepareTransactionRequest:t=>zd(e,t),requestAddresses:()=>Mv(e),requestPermissions:t=>Nv(e,t),sendCalls:t=>Ym(e,t),sendCallsSync:t=>Pv(e,t),sendRawTransaction:t=>Rm(e,t),sendRawTransactionSync:t=>Tv(e,t),sendTransaction:t=>Bm(e,t),sendTransactionSync:t=>Iv(e,t),showCallsStatus:t=>Lv(e,t),signAuthorization:t=>Rv(e,t),signMessage:t=>zv(e,t),signTransaction:t=>Bv(e,t),signTypedData:t=>Vv(e,t),switchChain:t=>Hv(e,t),waitForCallsStatus:t=>Zm(e,t),watchAsset:t=>Uv(e,t),writeContract:t=>Vm(e,t),writeContractSync:t=>Wv(e,t)}}function Kv(e){let{key:t=`wallet`,name:n=`Wallet Client`,transport:r}=e;return rh({...e,key:t,name:n,transport:r,type:`walletClient`}).extend(Gv)}function qv({key:e,methods:t,name:n,request:r,retryCount:i=3,retryDelay:a=150,timeout:o,type:s},c){let l=nh();return{config:{key:e,methods:t,name:n,request:r,retryCount:i,retryDelay:a,timeout:o,type:s},request:Th(r,{methods:t,retryCount:i,retryDelay:a,uid:l}),value:c}}function Jv(e,t={}){let{key:n=`custom`,methods:r,name:i=`Custom Provider`,retryDelay:a}=t;return({retryCount:o})=>qv({key:n,methods:r,name:i,request:e.request.bind(e),retryCount:t.retryCount??o,retryDelay:a,type:`custom`})}Pu(),ss();function Yv(e,t={}){let{key:n=`fallback`,name:r=`Fallback`,rank:i=!1,shouldThrow:a=Xv,retryCount:o,retryDelay:s}=t;return(({chain:t,pollingInterval:c=4e3,timeout:l,...u})=>{let d=e,f=()=>{},p=qv({key:n,name:r,async request({method:e,params:n}){let r,i=async(o=0)=>{let s=d[o]({...u,chain:t,retryCount:0,timeout:l});try{let t=await s.request({method:e,params:n});return f({method:e,params:n,response:t,transport:s,status:`success`}),t}catch(c){if(f({error:c,method:e,params:n,transport:s,status:`error`}),a(c)||o===d.length-1||(r??=d.slice(o+1).some(n=>{let{include:r,exclude:i}=n({chain:t}).config.methods||{};return r?r.includes(e):i?!i.includes(e):!0}),!r))throw c;return i(o+1)}};return i()},retryCount:o,retryDelay:s,type:`fallback`},{onResponse:e=>f=e,transports:d.map(e=>e({chain:t,retryCount:0}))});if(i){let e=typeof i==`object`?i:{};Zv({chain:t,interval:e.interval??c,onTransports:e=>d=e,ping:e.ping,sampleCount:e.sampleCount,timeout:e.timeout,transports:d,weights:e.weights})}return p})}function Xv(e){return!!(`code`in e&&typeof e.code==`number`&&(e.code===Ho.code||e.code===Ko.code||e.code===as.code||Su.nodeMessage.test(e.message)||e.code===5e3))}function Zv({chain:e,interval:t=4e3,onTransports:n,ping:r,sampleCount:i=10,timeout:a=1e3,transports:o,weights:s={}}){let{stability:c=.7,latency:l=.3}=s,u=[],d=async()=>{let s=await Promise.all(o.map(async t=>{let n=t({chain:e,retryCount:0,timeout:a}),i=Date.now(),o,s;try{await(r?r({transport:n}):n.request({method:`net_listening`})),s=1}catch{s=0}finally{o=Date.now()}return{latency:o-i,success:s}}));u.push(s),u.length>i&&u.shift();let f=Math.max(...u.map(e=>Math.max(...e.map(({latency:e})=>e))));n(o.map((e,t)=>{let n=u.map(e=>e[t].latency),r=1-n.reduce((e,t)=>e+t,0)/n.length/f,i=u.map(e=>e[t].success),a=i.reduce((e,t)=>e+t,0)/i.length;return a===0?[0,t]:[l*r+c*a,t]}).sort((e,t)=>t[0]-e[0]).map(([,e])=>o[e])),await Dm(t),d()};d()}D();var Qv=class extends E{constructor(){super(`No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.`,{docsPath:`/docs/clients/intro`,name:`UrlRequiredError`})}};Ao(),qp();function $v(e,t={}){let{batch:n,fetchFn:r,fetchOptions:i,key:a=`http`,methods:o,name:s=`HTTP JSON-RPC`,onFetchRequest:c,onFetchResponse:l,retryDelay:u,raw:d}=t;return({chain:f,retryCount:p,timeout:m})=>{let{batchSize:h=1e3,wait:g=0}=typeof n==`object`?n:{},_=t.retryCount??p,v=m??t.timeout??1e4,y=e||f?.rpcUrls.default.http[0];if(!y)throw new Qv;let b=jh(y,{fetchFn:r,fetchOptions:i,onRequest:c,onResponse:l,timeout:v});return qv({key:a,methods:o,name:s,async request({method:e,params:t}){let r={method:e,params:t},{schedule:i}=Gp({id:y,wait:g,shouldSplitBatch(e){return e.length>h},fn:e=>b.request({body:e}),sort:(e,t)=>e.id-t.id}),[{error:a,result:o}]=await(async e=>n?i(e):[await b.request({body:e})])(r);if(d)return{error:a,result:o};if(a)throw new Oo({body:r,error:a,url:y});return o},retryCount:_,retryDelay:u,timeout:v,type:`http`},{fetchOptions:i,url:y})}}var ey=L({id:16600,name:`0G Newton Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-newton.0g.ai`}},testnet:!0}),ty=L({id:16601,name:`0G Galileo Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-galileo.0g.ai`}},testnet:!0}),ny=L({id:16661,name:`0G Mainnet`,nativeCurrency:{name:`0G`,symbol:`0G`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan.0g.ai`}},testnet:!1}),ry=L({id:16602,name:`0G Galileo Testnet`,nativeCurrency:{name:`A0GI`,symbol:`A0GI`,decimals:18},rpcUrls:{default:{http:[`https://evmrpc-testnet.0g.ai`]}},blockExplorers:{default:{name:`0G BlockChain Explorer`,url:`https://chainscan-galileo.0g.ai`}},testnet:!0}),iy=L({id:995,name:`5ireChain`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Mainnet Explorer`,url:`https://5irescan.io/`}},testnet:!1}),ay=L({id:179,name:`ABEY Mainnet`,nativeCurrency:{name:`ABEY`,symbol:`ABEY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.abeychain.com`]}},blockExplorers:{default:{name:`Abey Scan`,url:`https://abeyscan.com`}},testnet:!1});$u();var oy=50000n,sy=Zu*32n;On(),Un(),k(),Ku();var cy={block:dd({format(e){let t=e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=cy.transaction?.format(e);return t.typeHex===`0x71`?t.type=`eip712`:t.typeHex===`0xff`&&(t.type=`priority`),t});return{l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTimestamp:e.l1BatchTimestamp?Tn(e.l1BatchTimestamp):null,transactions:t}}}),transaction:cd({format(e){let t={};return e.type===`0x71`?t.type=`eip712`:e.type===`0xff`&&(t.type=`priority`),{...t,l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?Tn(e.l1BatchTxIndex):null}}}),transactionReceipt:Km({format(e){return{l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,l1BatchTxIndex:e.l1BatchTxIndex?Tn(e.l1BatchTxIndex):null,logs:e.logs.map(e=>({...Ud(e),l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,transactionLogIndex:Dn(e.transactionLogIndex),logType:e.logType})),l2ToL1Logs:e.l2ToL1Logs.map(e=>({blockNumber:Tn(e.blockHash),blockHash:e.blockHash,l1BatchNumber:e.l1BatchNumber?Tn(e.l1BatchNumber):null,transactionIndex:Tn(e.transactionIndex),shardId:Tn(e.shardId),isService:e.isService,sender:e.sender,key:e.key,value:e.value,transactionHash:e.transactionHash,logIndex:Tn(e.logIndex)}))}}}),transactionRequest:Gu({exclude:[`customSignature`,`factoryDeps`,`gasPerPubdata`,`paymaster`,`paymasterInput`],format(e){return e.gasPerPubdata||e.paymaster&&e.paymasterInput||e.factoryDeps||e.customSignature?{eip712Meta:{...e.gasPerPubdata?{gasPerPubdata:kn(e.gasPerPubdata)}:{gasPerPubdata:kn(oy)},...e.paymaster&&e.paymasterInput?{paymasterParams:{paymaster:e.paymaster,paymasterInput:Array.from(Rn(e.paymasterInput))}}:{},...e.factoryDeps?{factoryDeps:e.factoryDeps.map(e=>Array.from(Rn(e)))}:{},...e.customSignature?{customSignature:Array.from(Rn(e.customSignature))}:{}},type:`0x71`}:{}}})};D();var ly=class extends E{constructor(){super([`Transaction is not an EIP712 transaction.`,``,`Transaction must:`,' - include `type: "eip712"`'," - include one of the following: `customSignature`, `paymaster`, `paymasterInput`, `gasPerPubdata`, `factoryDeps`"].join(` -`),{name:`InvalidEip712TransactionError`})}};function uy(e){return!!(e.type===`eip712`||`customSignature`in e&&e.customSignature||`paymaster`in e&&e.paymaster||`paymasterInput`in e&&e.paymasterInput||`gasPerPubdata`in e&&typeof e.gasPerPubdata==`bigint`||`factoryDeps`in e&&e.factoryDeps)}gi(),D(),Fp(),Ei();function dy(e){let{chainId:t,to:n,from:r,paymaster:i,paymasterInput:a}=e;if(!uy(e))throw new ly;if(!t||t<=0)throw new Pp({chainId:t});if(n&&!Ci(n))throw new hi({address:n});if(r&&!Ci(r))throw new hi({address:r});if(i&&!Ci(i))throw new hi({address:i});if(i&&!a)throw new E("`paymasterInput` must be provided when `paymaster` is defined");if(!i&&a)throw new E("`paymaster` must be provided when `paymasterInput` is defined")}Ai(),k();function fy(e,t){return uy(e)?my(e):mh(e,t)}var py={transaction:fy};function my(e){let{chainId:t,gas:n,nonce:r,to:i,from:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,customSignature:l,factoryDeps:u,paymaster:d,paymasterInput:f,gasPerPubdata:p,data:m}=e;return dy(e),ki([`0x71`,mu([r?kn(r):`0x`,c?kn(c):`0x`,s?kn(s):`0x`,n?kn(n):`0x`,i??`0x`,o?kn(o):`0x`,m??`0x`,kn(t),kn(``),kn(``),kn(t),a??`0x`,kn(p||oy),u??[],l??`0x`,d&&f?[d,f]:[]])])}D();var hy=class extends E{constructor({givenLength:e,maxBytecodeSize:t}){super(`Bytecode cannot be longer than ${t} bytes. Given length: ${e}`,{name:`BytecodeLengthExceedsMaxSizeError`})}},gy=class extends E{constructor({givenLengthInWords:e}){super(`Bytecode length in 32-byte words must be odd. Given length in words: ${e}`,{name:`BytecodeLengthInWordsMustBeOddError`})}},_y=class extends E{constructor({givenLength:e}){super(`The bytecode length in bytes must be divisible by 32. Given length: ${e}`,{name:`BytecodeLengthMustBeDivisibleBy32Error`})}};gn(),Un();function vy(e){let t=Fn(e);if(t.length%32!=0)throw new _y({givenLength:t.length});if(t.length>sy)throw new hy({givenLength:t.length,maxBytecodeSize:sy});let n=Fn(bd(t)),r=t.length/32;if(r%2==0)throw new gy({givenLengthInWords:r});let i=pn(Fn(r),{size:2}),a=new Uint8Array([1,0]);return n.set(a,0),n.set(i,2),n}k();var yy=e=>{dy(e);let t=by(e);return{domain:{name:`zkSync`,version:`2`,chainId:e.chainId},types:{Transaction:[{name:`txType`,type:`uint256`},{name:`from`,type:`uint256`},{name:`to`,type:`uint256`},{name:`gasLimit`,type:`uint256`},{name:`gasPerPubdataByteLimit`,type:`uint256`},{name:`maxFeePerGas`,type:`uint256`},{name:`maxPriorityFeePerGas`,type:`uint256`},{name:`paymaster`,type:`uint256`},{name:`nonce`,type:`uint256`},{name:`value`,type:`uint256`},{name:`data`,type:`bytes`},{name:`factoryDeps`,type:`bytes32[]`},{name:`paymasterInput`,type:`bytes`}]},primaryType:`Transaction`,message:t}};function by(e){let{gas:t,nonce:n,to:r,from:i,value:a,maxFeePerGas:o,maxPriorityFeePerGas:s,factoryDeps:c,paymaster:l,paymasterInput:u,gasPerPubdata:d,data:f}=e;return{txType:113n,from:BigInt(i),to:r?BigInt(r):0n,gasLimit:t??0n,gasPerPubdataByteLimit:d??50000n,maxFeePerGas:o??0n,maxPriorityFeePerGas:s??0n,paymaster:l?BigInt(l):0n,nonce:n?BigInt(n):0n,value:a??0n,data:f??`0x`,factoryDeps:c?.map(e=>kn(vy(e)))??[],paymasterInput:u||`0x`}}var xy={blockTime:1e3,formatters:cy,serializers:py,custom:{getEip712Domain:yy}},Sy=L({...xy,blockTime:200,id:2741,name:`Abstract`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.mainnet.abs.xyz`],webSocket:[`wss://api.mainnet.abs.xyz/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.mainnet.abs.xyz`}},contracts:{multicall3:{address:`0xAa4De41dba0Ca5dCBb288b7cC6b708F3aaC759E7`,blockCreated:5288},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:5263}}}),Cy=L({...xy,blockTime:200,id:11124,name:`Abstract Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.testnet.abs.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.abscan.org`},native:{name:`Abstract Explorer`,url:`https://explorer.testnet.abs.xyz`}},testnet:!0,contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:358349},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:431682}}}),wy=L({id:787,name:`Acala`,network:`acala`,nativeCurrency:{name:`Acala`,symbol:`ACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-acala.aca-api.network`],webSocket:[`wss://eth-rpc-acala.aca-api.network`]}},blockExplorers:{default:{name:`Acala Blockscout`,url:`https://blockscout.acala.network`,apiUrl:`https://blockscout.acala.network/api`}},testnet:!1}),Ty=L({id:47,name:`Acria IntelliChain`,nativeCurrency:{decimals:18,name:`ACRIA`,symbol:`ACRIA`},rpcUrls:{default:{http:[`https://aic.acria.ai`]}},blockExplorers:{default:{name:`Acria Explorer`,url:`https://explorer.acria.ai`}},testnet:!1}),Ey=L({id:1215,name:`ADF Chain`,nativeCurrency:{name:`ADDFILL`,symbol:`ADF`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.adftechnology.com`]}},blockExplorers:{default:{name:`ADF Mainnet Explorer`,url:`https://explorer.adftechnology.com`}},testnet:!1}),Dy=L({id:36900,name:`ADI_Chain`,nativeCurrency:{decimals:18,name:`ADI`,symbol:`ADI`},rpcUrls:{default:{http:[`https://rpc.adifoundation.ai`]}},blockExplorers:{default:{name:`ADI Explorer`,url:`https://explorer.adifoundation.ai`}},testnet:!1}),Oy=L({id:9990,name:`Agung Network`,nativeCurrency:{decimals:18,name:`Agung`,symbol:`AGNG`},rpcUrls:{default:{http:[`https://wss-async.agung.peaq.network`],webSocket:[`wss://wss-async.agung.peaq.network`]}},blockExplorers:{default:{name:`Subscan`,url:`https://agung-testnet.subscan.io`}},testnet:!0}),ky=L({id:168,name:`AIOZ Network`,nativeCurrency:{decimals:18,name:`AIOZ`,symbol:`AIOZ`},rpcUrls:{default:{http:[`https://eth-dataseed.aioz.network`]}},blockExplorers:{default:{name:`AIOZ Explorer`,url:`https://explorer.aioz.network`}},testnet:!1}),Ay=L({id:41455,name:`Aleph Zero`,nativeCurrency:{name:`Aleph Zero`,symbol:`AZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero.raas.gelato.cloud`]}},blockExplorers:{default:{name:`Aleph Zero EVM Explorer`,url:`https://evm-explorer.alephzero.org`,apiUrl:`https://evm-explorer.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4603377}}}),jy=L({id:2039,name:`Aleph Zero Testnet`,nativeCurrency:{name:`TZERO`,symbol:`TZERO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alephzero-testnet.gelato.digital`],webSocket:[`wss://ws.alephzero-testnet.gelato.digital`]}},blockExplorers:{default:{name:`Aleph Zero EVM Testnet explorer`,url:`https://evm-explorer-testnet.alephzero.org`,apiUrl:`https://evm-explorer-testnet.alephzero.org/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2861745}},testnet:!0}),My=L({id:10241024,name:`AlienX Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://explorer.alienxchain.io`}},testnet:!1}),Ny=L({id:10241025,name:`ALIENX Hal Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://hal-rpc.alienxchain.io/http`]}},blockExplorers:{default:{name:`AlienX Explorer`,url:`https://hal-explorer.alienxchain.io`}},testnet:!0}),Py=L({id:8150,name:`Alpen Testnet`,nativeCurrency:{name:`Signet BTC`,symbol:`sBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.alpenlabs.io`]}},blockExplorers:{default:{name:`Alpen Explorer`,url:`https://explorer.testnet.alpenlabs.io`,apiUrl:`https://explorer.testnet.alpenlabs.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:290408}},testnet:!0}),Fy={gasPriceOracle:{address:`0x420000000000000000000000000000000000000F`},l1Block:{address:`0x4200000000000000000000000000000000000015`},l2CrossDomainMessenger:{address:`0x4200000000000000000000000000000000000007`},l2Erc721Bridge:{address:`0x4200000000000000000000000000000000000014`},l2StandardBridge:{address:`0x4200000000000000000000000000000000000010`},l2ToL1MessagePasser:{address:`0x4200000000000000000000000000000000000016`}};On();var Iy={block:dd({format(e){return{transactions:e.transactions?.map(e=>{if(typeof e==`string`)return e;let t=sd(e);return t.typeHex===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?Tn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}),stateRoot:e.stateRoot}}}),transaction:cd({format(e){let t={};return e.type===`0x7e`&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?Tn(e.mint):void 0,t.sourceHash=e.sourceHash,t.type=`deposit`),t}}),transactionReceipt:Km({format(e){return{l1GasPrice:e.l1GasPrice?Tn(e.l1GasPrice):null,l1GasUsed:e.l1GasUsed?Tn(e.l1GasUsed):null,l1Fee:e.l1Fee?Tn(e.l1Fee):null,l1FeeScalar:e.l1FeeScalar?Number(e.l1FeeScalar):null}}})};gi(),Ei(),Ai(),k();function Ly(e,t){return By(e)?zy(e):mh(e,t)}var Ry={transaction:Ly};function zy(e){Vy(e);let{sourceHash:t,data:n,from:r,gas:i,isSystemTx:a,mint:o,to:s,value:c}=e;return ki([`0x7e`,mu([t,r,s??`0x`,o?kn(o):`0x`,c?kn(c):`0x`,i?kn(i):`0x`,a?`0x1`:`0x`,n??`0x`])])}function By(e){return e.type===`deposit`||e.sourceHash!==void 0}function Vy(e){let{from:t,to:n}=e;if(t&&!Ci(t))throw new hi({address:t});if(n&&!Ci(n))throw new hi({address:n})}var R={blockTime:2e3,contracts:Fy,formatters:Iy,serializers:Ry},Hy=1,Uy=L({...R,id:888888888,name:`Ancient8`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 explorer`,url:`https://scan.ancient8.gg`,apiUrl:`https://scan.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Hy]:{address:`0xB09DC08428C8b4EFB4ff9C0827386CDF34277996`}},portal:{[Hy]:{address:`0x639F2AECE398Aa76b07e59eF6abe2cFe32bacb68`,blockCreated:19070571}},l1StandardBridge:{[Hy]:{address:`0xd5e3eDf5b68135D559D572E26bF863FBC1950033`,blockCreated:19070571}}},sourceId:Hy}),Wy=11155111,Gy=L({...R,id:28122024,name:`Ancient8 Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpcv2-testnet.ancient8.gg`]}},blockExplorers:{default:{name:`Ancient8 Celestia Testnet explorer`,url:`https://scanv2-testnet.ancient8.gg`,apiUrl:`https://scanv2-testnet.ancient8.gg/api`}},contracts:{...R.contracts,l2OutputOracle:{[Wy]:{address:`0x942fD5017c0F60575930D8574Eaca13BEcD6e1bB`}},portal:{[Wy]:{address:`0xfa1d9E26A6aCD7b22115D27572c1221B9803c960`,blockCreated:4972908}},l1StandardBridge:{[Wy]:{address:`0xF6Bc0146d3c74D48306e79Ae134A260E418C9335`,blockCreated:4972908}}},sourceId:Wy}),Ky=L({id:31337,name:`Anvil`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}}),qy=L({id:33139,name:`ApeChain`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.apechain.com/http`],webSocket:[`wss://rpc.apechain.com/ws`]}},blockExplorers:{default:{name:`Apescan`,url:`https://apescan.io`,apiUrl:`https://api.apescan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:20889}},sourceId:42161}),Jy=L({id:3993,name:`APEX Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.apexlayer.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-testnet.apexlayer.xyz`,apiUrl:`https://exp-testnet.apexlayer.xyz/api`}},contracts:{multicall3:{address:`0xf7642be33a6b18D16a995657adb5a68CD0438aE2`,blockCreated:283775}},testnet:!0}),Yy=L({id:62606,name:`Apollo`,nativeCurrency:{decimals:18,name:`Apollo`,symbol:`APOLLO`},rpcUrls:{default:{http:[`https://mainnet-rpc.apolloscan.io`]}},blockExplorers:{default:{name:`Apollo Explorer`,url:`https://apolloscan.io`}}}),Xy=L({id:42161,name:`Arbitrum One`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:250,rpcUrls:{default:{http:[`https://arb1.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://arbiscan.io`,apiUrl:`https://api.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7654707}}}),Zy=L({id:421613,name:`Arbitrum Goerli`,nativeCurrency:{name:`Arbitrum Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://goerli.arbiscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:88114}},testnet:!0}),Qy=L({id:42170,name:`Arbitrum Nova`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://nova.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://nova.arbiscan.io`,apiUrl:`https://api-nova.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1746963}}}),$y=L({id:421614,name:`Arbitrum Sepolia`,blockTime:250,nativeCurrency:{name:`Arbitrum Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rollup.arbitrum.io/rpc`]}},blockExplorers:{default:{name:`Arbiscan`,url:`https://sepolia.arbiscan.io`,apiUrl:`https://api-sepolia.arbiscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:81930}},testnet:!0}),eb=L({id:5042002,name:`Arc Testnet`,nativeCurrency:{name:`USDC`,symbol:`USDC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.arc.network`,`https://rpc.quicknode.testnet.arc.network`,`https://rpc.blockdaemon.testnet.arc.network`],webSocket:[`wss://rpc.testnet.arc.network`,`wss://rpc.quicknode.testnet.arc.network`]}},blockExplorers:{default:{name:`ArcScan`,url:`https://testnet.arcscan.app`,apiUrl:`https://testnet.arcscan.app/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),tb=L({id:7897,name:`Arena-Z`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arena-z.gg`]}},blockExplorers:{default:{name:`Arena-Z Explorer`,url:`https://explorer.arena-z.gg`,apiUrl:`https://explorer.arena-z.gg`}}}),nb=L({id:463,name:`Areon Network`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areon.network`],webSocket:[`wss://mainnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),rb=L({id:462,name:`Areon Network Testnet`,nativeCurrency:{decimals:18,name:`TAREA`,symbol:`TAREA`},rpcUrls:{default:{http:[`https://testnet-rpc.areon.network`],webSocket:[`wss://testnet-ws.areon.network`]}},blockExplorers:{default:{name:`Areonscan`,url:`https://areonscan.com`}},testnet:!0}),ib=L({id:463,name:`Areum`,nativeCurrency:{decimals:18,name:`AREA`,symbol:`AREA`},rpcUrls:{default:{http:[`https://mainnet-rpc.areum.network`],webSocket:[`wss://mainnet-ws.areum.network`]}},blockExplorers:{default:{name:`Areum Explorer`,url:`https://explorer.areum.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:353286}},testnet:!1}),ab=L({id:11822,name:`Artela Testnet`,nativeCurrency:{name:`ART`,symbol:`ART`,decimals:18},rpcUrls:{default:{http:[`https://betanet-rpc1.artela.network`]}},blockExplorers:{default:{name:`Artela`,url:`https://betanet-scan.artela.network`,apiUrl:`https://betanet-scan.artela.network/api`}},contracts:{multicall3:{address:`0xd07c8635f76e8745Ee7092fbb6e8fbc5FeF09DD7`,blockCreated:7001871}},testnet:!0}),ob=L({id:10242,name:`Arthera`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer.arthera.net`,apiUrl:`https://explorer.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4502791}}}),sb=L({id:10243,name:`Arthera Testnet`,nativeCurrency:{name:`Arthera`,symbol:`AA`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.arthera.net`]}},blockExplorers:{default:{name:`Arthera EVM Explorer`,url:`https://explorer-test.arthera.net`,apiUrl:`https://explorer-test.arthera.net/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:22051}}}),cb=L({id:42420,name:`AssetChain Mainnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://mainnet-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Explorer`,url:`https://scan.assetchain.org`,apiUrl:`https://scan.assetchain.org/api`}},testnet:!1,contracts:{}}),lb=L({id:42421,name:`AssetChain Testnet`,nativeCurrency:{decimals:18,name:`Real World Asset`,symbol:`RWA`},rpcUrls:{default:{http:[`https://enugu-rpc.assetchain.org`]}},blockExplorers:{default:{name:`Asset Chain Testnet Explorer`,url:`https://scan-testnet.assetchain.org`,apiUrl:`https://scan-testnet.assetchain.org/api`}},testnet:!0,contracts:{multicall3:{address:`0x989F832D35988cb5e3eB001Fa2Fe789469EC31Ea`,blockCreated:17177}}}),ub=L({id:592,name:`Astar`,network:`astar-mainnet`,nativeCurrency:{name:`Astar`,symbol:`ASTR`,decimals:18},rpcUrls:{default:{http:[`https://astar.api.onfinality.io/public`]}},blockExplorers:{default:{name:`Astar Subscan`,url:`https://astar.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:761794}},testnet:!1}),db=L({id:3776,name:`Astar zkEVM`,network:`AstarZkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-zkevm.astar.network`]}},blockExplorers:{default:{name:`Astar zkEVM Explorer`,url:`https://astar-zkevm.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:93528}},testnet:!1}),fb=L({id:6038361,name:`Astar zkEVM Testnet zKyoto`,network:`zKyoto`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.startale.com/zkyoto`]}},blockExplorers:{default:{name:`zKyoto Explorer`,url:`https://zkyoto.explorer.startale.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:196153}},testnet:!0}),pb=L({id:2340,name:`Atleta Olympia`,nativeCurrency:{decimals:18,name:`Atla`,symbol:`ATLA`},rpcUrls:{default:{http:[`https://testnet-rpc.atleta.network:9944`,`https://testnet-rpc.atleta.network`],ws:[`wss://testnet-rpc.atleta.network:9944`]}},blockExplorers:{default:{name:`Atleta Olympia Explorer`,url:`https://blockscout.atleta.network`,apiUrl:`https://blockscout.atleta.network/api`}},contracts:{multicall3:{address:`0x1472ec6392180fb84F345d2455bCC75B26577115`,blockCreated:1076473}},testnet:!0}),mb=L({id:1313161554,name:`Aurora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://aurorascan.dev`,apiUrl:`https://aurorascan.dev/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:62907816}}}),hb=L({id:1313161555,name:`Aurora Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.aurora.dev`]}},blockExplorers:{default:{name:`Aurorascan`,url:`https://testnet.aurorascan.dev`,apiUrl:`https://testnet.aurorascan.dev/api`}},testnet:!0}),gb=L({id:205205,name:`Auroria Testnet`,network:`auroria`,nativeCurrency:{name:`Auroria Stratis`,symbol:`tSTRAX`,decimals:18},rpcUrls:{default:{http:[`https://auroria.rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Auroria Testnet Explorer`,url:`https://auroria.explorer.stratisevm.com`}},testnet:!0}),_b=L({id:785,name:`Autheo Testnet`,nativeCurrency:{decimals:18,name:`Autheo`,symbol:`THEO`},rpcUrls:{default:{http:[`https://testnet-rpc1.autheo.com`,`https://testnet-rpc2.autheo.com`]}},blockExplorers:{default:{name:`Autheo Testnet Block Explorer`,url:`https://testnet-explorer.autheo.com/`}}}),vb=L({id:43114,name:`Avalanche`,blockTime:1700,nativeCurrency:{decimals:18,name:`Avalanche`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://snowtrace.io`,apiUrl:`https://api.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11907934}}}),yb=L({id:43113,name:`Avalanche Fuji`,nativeCurrency:{decimals:18,name:`Avalanche Fuji`,symbol:`AVAX`},rpcUrls:{default:{http:[`https://api.avax-test.network/ext/bc/C/rpc`]}},blockExplorers:{default:{name:`SnowTrace`,url:`https://testnet.snowtrace.io`,apiUrl:`https://api-testnet.snowtrace.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7096959}},testnet:!0}),bb=L({id:8333,name:`B3`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},sourceId:8453}),xb=L({id:1993,name:`B3 Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.b3.fun/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.b3.fun`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}},testnet:!0,sourceId:168587773}),Sb=L({id:5165,network:`bahamut`,name:`Bahamut`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.bahamut.io`,`https://bahamut-rpc.publicnode.com`,`https://rpc2.bahamut.io`],webSocket:[`wss://ws1.sahara.bahamutchain.com`,`wss://bahamut-rpc.publicnode.com`,`wss://ws2.sahara.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://www.ftnscan.com`,apiUrl:`https://www.ftnscan.com/api`}}}),Cb=1,wb=L({...R,id:8453,name:`Base`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://basescan.org`,apiUrl:`https://api.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[Cb]:{address:`0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e`}},l2OutputOracle:{[Cb]:{address:`0x56315b90c40730925ec5485cf004d835058518A0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:5022},portal:{[Cb]:{address:`0x49048044D57e1C92A77f79988d21Fa8fAF74E97e`,blockCreated:17482143}},l1StandardBridge:{[Cb]:{address:`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`,blockCreated:17482143}}},sourceId:Cb}),Tb=L({...wb,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://mainnet-preconf.base.org`]}}}),Eb=L({id:123420001114,name:`Basecamp Testnet`,nativeCurrency:{decimals:18,name:`Camp`,symbol:`CAMP`},rpcUrls:{default:{http:[`https://rpc.basecamp.t.raas.gelato.cloud`]}},blockExplorers:{default:{name:`basecamp`,url:`https://basecamp.cloud.blockscout.com`}},testnet:!0}),Db=5,Ob=L({...R,id:84531,name:`Base Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://goerli.basescan.org`,apiUrl:`https://goerli.basescan.org/api`}},contracts:{...R.contracts,l2OutputOracle:{[Db]:{address:`0x2A35891ff30313CcFa6CE88dcf3858bb075A2298`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1376988},portal:{[Db]:{address:`0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA`}},l1StandardBridge:{[Db]:{address:`0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a`}}},testnet:!0,sourceId:Db}),kb=11155111,Ab=L({...R,id:84532,network:`base-sepolia`,name:`Base Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.base.org`]}},blockExplorers:{default:{name:`Basescan`,url:`https://sepolia.basescan.org`,apiUrl:`https://api-sepolia.basescan.org/api`}},contracts:{...R.contracts,disputeGameFactory:{[kb]:{address:`0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1`}},l2OutputOracle:{[kb]:{address:`0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254`}},portal:{[kb]:{address:`0x49f53e41452c74589e85ca1677426ba426459e85`,blockCreated:4446677}},l1StandardBridge:{[kb]:{address:`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`,blockCreated:4446677}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1059647}},testnet:!0,sourceId:kb}),jb=L({...Ab,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://sepolia-preconf.base.org`]}}}),Mb=L({id:4337,name:`Beam`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc`],webSocket:[`wss://build.onbeam.com/ws`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets.avax.network/beam`}},contracts:{multicall3:{address:`0x4956f15efdc3dc16645e90cc356eafa65ffc65ec`,blockCreated:1}}}),Nb=L({id:13337,name:`Beam Testnet`,network:`beam`,nativeCurrency:{decimals:18,name:`Beam`,symbol:`BEAM`},rpcUrls:{default:{http:[`https://build.onbeam.com/rpc/testnet`],webSocket:[`wss://build.onbeam.com/ws/testnet`]}},blockExplorers:{default:{name:`Beam Explorer`,url:`https://subnets-test.avax.network/beam`}},contracts:{multicall3:{address:`0x9bf49b704ee2a095b95c1f2d4eb9010510c41c9e`,blockCreated:3}},testnet:!0}),Pb=L({id:641230,name:`Bear Network Chain Mainnet`,nativeCurrency:{decimals:18,name:`BearNetworkChain`,symbol:`BRNKC`},rpcUrls:{default:{http:[`https://brnkc-mainnet.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkScan`,url:`https://brnkscan.bearnetwork.net`,apiUrl:`https://brnkscan.bearnetwork.net/api`}}}),Fb=L({id:751230,name:`Bear Network Chain Testnet`,nativeCurrency:{decimals:18,name:`tBRNKC`,symbol:`tBRNKC`},rpcUrls:{default:{http:[`https://brnkc-test.bearnetwork.net`]}},blockExplorers:{default:{name:`BrnkTestScan`,url:`https://brnktest-scan.bearnetwork.net`,apiUrl:`https://brnktest-scan.bearnetwork.net/api`}},testnet:!0}),Ib=L({id:80094,name:`Berachain`,blockTime:2e3,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},ensRegistry:{address:`0x5b22280886a2f5e09a49bea7e320eab0e5320e28`,blockCreated:877007},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:9310021}},rpcUrls:{default:{http:[`https://rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://berascan.com`}},ensTlds:[`.bera`],testnet:!1}),Lb=L({id:80069,blockTime:2e3,name:`Berachain Bepolia`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},rpcUrls:{default:{http:[`https://bepolia.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berascan`,url:`https://bepolia.beratrail.io`}},testnet:!0}),Rb=L({id:80085,name:`Berachain Artio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},rpcUrls:{default:{http:[`https://artio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain`,url:`https://artio.beratrail.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:866924}},testnet:!0}),zb=L({id:80084,name:`Berachain bArtio`,nativeCurrency:{decimals:18,name:`BERA Token`,symbol:`BERA`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:109269},ensRegistry:{address:`0xB0eef18971290b333450586D33dcA6cE122651D2`,blockCreated:7736794},ensUniversalResolver:{address:`0x41692Ef1EA0C79E6b73077E4A67572D2BDbD7057`,blockCreated:7736795}},ensTlds:[`.bera`],rpcUrls:{default:{http:[`https://bartio.rpc.berachain.com`]}},blockExplorers:{default:{name:`Berachain bArtio Beratrail`,url:`https://bartio.beratrail.io`}},testnet:!0}),Bb=L({id:11501,name:`BEVM Mainnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet-1.bevm.io`]}},blockExplorers:{default:{name:`Bevmscan`,url:`https://scan-mainnet.bevm.io`,apiUrl:`https://scan-mainnet-api.bevm.io/api`}}}),Vb=L({id:3068,name:`Bifrost Mainnet`,nativeCurrency:{name:`BFC`,symbol:`BFC`,decimals:18},rpcUrls:{default:{http:[`https://public-01.mainnet.bifrostnetwork.com/rpc`]}},blockExplorers:{default:{name:`Bifrost Blockscout`,url:`https://explorer.mainnet.bifrostnetwork.com`}},testnet:!1}),Hb=L({id:53456,name:`BirdLayer`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.birdlayer.xyz`,`https://rpc1.birdlayer.xyz`],webSocket:[`wss://rpc.birdlayer.xyz/ws`]}},blockExplorers:{default:{name:`BirdLayer Explorer`,url:`https://scan.birdlayer.xyz`}}}),Ub=L({id:32520,name:`Bitgert Mainnet`,nativeCurrency:{decimals:18,name:`Brise`,symbol:`Brise`},rpcUrls:{default:{http:[`https://rpc-bitgert.icecreamswap.com`]}},blockExplorers:{default:{name:`Bitgert Scan`,url:`https://brisescan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2118034}},testnet:!1}),Wb=L({id:96,name:`KUB Mainnet`,nativeCurrency:{name:`KUB Coin`,symbol:`KUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitkubchain.io`]}},blockExplorers:{default:{name:`KUB Chain Mainnet Explorer`,url:`https://www.bkcscan.com`,apiUrl:`https://www.bkcscan.com/api`}}}),Gb=L({id:25925,name:`Bitkub Testnet`,network:`Bitkub Testnet`,nativeCurrency:{name:`Bitkub Test`,symbol:`tKUB`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.bitkubchain.io`]}},blockExplorers:{default:{name:`Bitkub Chain Testnet Explorer`,url:`https://testnet.bkcscan.com`,apiUrl:`https://testnet.bkcscan.com/api`}},testnet:!0}),Kb=L({id:200901,name:`Bitlayer Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`],webSocket:[`wss://ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer mainnet scan`,url:`https://www.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:2421963}}}),qb=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`]}},blockExplorers:{default:{name:`bitlayer testnet scan`,url:`https://testnet.btrscan.com`}},contracts:{multicall3:{address:`0x5B256fE9e993902eCe49D138a5b1162cBb529474`,blockCreated:4135671}},testnet:!0}),Jb=L({id:7171,name:`Bitrock Mainnet`,nativeCurrency:{name:`BROCK`,symbol:`BROCK`,decimals:18},rpcUrls:{default:{http:[`https://brockrpc.io`]}},blockExplorers:{default:{name:`Bitrock Explorer`,url:`https://explorer.bit-rock.io`}},testnet:!1}),Yb=L({id:199,name:`BitTorrent`,network:`bittorrent-chain-mainnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://bttcscan.com`,apiUrl:`https://api.bttcscan.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:31078552}}}),Xb=L({id:1028,name:`BitTorrent Chain Testnet`,network:`bittorrent-chain-testnet`,nativeCurrency:{name:`BitTorrent`,symbol:`BTT`,decimals:18},rpcUrls:{default:{http:[`https://testrpc.bittorrentchain.io`]}},blockExplorers:{default:{name:`Bttcscan`,url:`https://testnet.bttcscan.com`,apiUrl:`https://testnet.bttcscan.com/api`}},testnet:!0}),Zb=1,Qb=L({...R,id:81457,name:`Blast`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://blastscan.io`,apiUrl:`https://api.blastscan.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:212929},l2OutputOracle:{[Zb]:{address:`0x826D1B0D4111Ad9146Eb8941D7Ca2B6a44215c76`,blockCreated:19300358}},portal:{[Zb]:{address:`0x0Ec68c5B10F21EFFb74f2A5C61DFe6b08C0Db6Cb`,blockCreated:19300357}},l1StandardBridge:{[Zb]:{address:`0x697402166Fbf2F22E970df8a6486Ef171dbfc524`,blockCreated:19300360}}},sourceId:Zb}),$b=L({id:168587773,name:`Blast Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.blast.io`]}},blockExplorers:{default:{name:`Blastscan`,url:`https://sepolia.blastscan.io`,apiUrl:`https://api-sepolia.blastscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:756690}},testnet:!0,sourceId:11155111}),ex=1,tx=L({...R,id:60808,name:`BOB`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.gobob.xyz`],webSocket:[`wss://rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Explorer`,url:`https://explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:23131},l2OutputOracle:{[ex]:{address:`0xdDa53E23f8a32640b04D7256e651C1db98dB11C1`,blockCreated:4462615}},portal:{[ex]:{address:`0x8AdeE124447435fE03e3CD24dF3f4cAE32E65a3E`,blockCreated:4462615}}},sourceId:ex}),nx=L({id:288,name:`Boba Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://bobascan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:446859}}}),rx=L({id:28882,name:`Boba Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.boba.network`]}},blockExplorers:{default:{name:`BOBAScan`,url:`https://testnet.bobascan.com`}},testnet:!0}),ix=11155111,ax=L({...R,id:808813,name:`BOB Sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://bob-sepolia.rpc.gobob.xyz`],webSocket:[`wss://bob-sepolia.rpc.gobob.xyz`]}},blockExplorers:{default:{name:`BOB Sepolia Explorer`,url:`https://bob-sepolia.explorer.gobob.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:35677},l2OutputOracle:{[ix]:{address:`0x14D0069452b4AE2b250B395b8adAb771E4267d2f`,blockCreated:4462615}},portal:{[ix]:{address:`0x867B1Aa872b9C8cB5E9F7755feDC45BB24Ad0ae4`,blockCreated:4462615}}},testnet:!0,sourceId:ix}),ox=L({id:11100,name:`Bool Beta Mainnet`,nativeCurrency:{decimals:18,name:`BOL`,symbol:`BOL`},rpcUrls:{default:{http:[`https://beta-rpc-node-http.bool.network`]}},blockExplorers:{default:{name:`BoolScan`,url:`https://beta-mainnet.boolscan.com/`}},testnet:!1}),sx=L({id:3637,name:`Botanix`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.botanixlabs.com`],webSocket:[`wss://rpc.botanixlabs.com/ws`]}},blockExplorers:{default:{name:`Botanixscan`,url:`https://botanixscan.io`}}}),cx=L({id:3636,name:`Botanix Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://node.botanixlabs.dev`]}},blockExplorers:{default:{name:`Botanix Testnet Explorer`,url:`https://testnet.botanixscan.io`}},testnet:!0}),lx=L({id:6001,name:`BounceBit Mainnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-mainnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://bbscan.io`}},testnet:!1}),ux=L({id:6e3,name:`BounceBit Testnet`,nativeCurrency:{name:`BounceBit`,symbol:`BB`,decimals:18},rpcUrls:{default:{http:[`https://fullnode-testnet.bouncebitapi.com`]}},blockExplorers:{default:{name:`BB Scan`,url:`https://testnet.bbscan.io`}},testnet:!0}),dx=L({id:1039,name:`Bronos`,nativeCurrency:{decimals:18,name:`BRO`,symbol:`BRO`},rpcUrls:{default:{http:[`https://evm.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://broscan.bronos.org`}}}),fx=L({id:1038,name:`Bronos Testnet`,nativeCurrency:{decimals:18,name:`Bronos Coin`,symbol:`tBRO`},rpcUrls:{default:{http:[`https://evm-testnet.bronos.org`]}},blockExplorers:{default:{name:`BronoScan`,url:`https://tbroscan.bronos.org`}},testnet:!0}),px=L({id:56,name:`BNB Smart Chain`,blockTime:750,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://56.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`BscScan`,url:`https://bscscan.com`,apiUrl:`https://api.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15921452}}}),mx=L({id:1017,name:`BNB Greenfield Chain`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`BNB`},rpcUrls:{default:{http:[`https://greenfield-chain.bnbchain.org`]}},blockExplorers:{default:{name:`BNB Greenfield Mainnet Scan`,url:`https://greenfieldscan.com`}},testnet:!1}),hx=L({id:97,name:`BNB Smart Chain Testnet`,nativeCurrency:{decimals:18,name:`BNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://data-seed-prebsc-1-s1.bnbchain.org:8545`]}},blockExplorers:{default:{name:`BscScan`,url:`https://testnet.bscscan.com`,apiUrl:`https://api-testnet.bscscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:17422483}},testnet:!0}),gx=L({id:223,name:`B2`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.bsquared.network`}}}),_x=L({id:1123,name:`B2 Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bsquared.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-explorer.bsquared.network`}},testnet:!0}),vx=L({id:200901,name:`Bitlayer`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.bitlayer.org`,`https://rpc.bitlayer-rpc.com`],webSocket:[`wss://ws.bitlayer.org`,`wss://ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://www.btrscan.com`}}}),yx=L({id:200810,name:`Bitlayer Testnet`,nativeCurrency:{name:`Bitcoin`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.bitlayer.org`],webSocket:[`wss://testnet-ws.bitlayer.org`,`wss://testnet-ws.bitlayer-rpc.com`]}},blockExplorers:{default:{name:`Bitlayer(BTR) Scan`,url:`https://testnet.btrscan.com`}},testnet:!0}),bx=L({id:4999,name:`BlackFort Exchange Network`,nativeCurrency:{name:`BlackFort Token`,symbol:`BXN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.blackfort.network`,apiUrl:`https://explorer.blackfort.network/api`}}}),xx=L({id:4777,name:`BlackFort Exchange Network Testnet`,nativeCurrency:{name:`BlackFort Testnet Token`,symbol:`TBXN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.blackfort.network/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.blackfort.network`,apiUrl:`https://testnet-explorer.blackfort.network/api`}},testnet:!0}),Sx=L({id:13370,name:`Cannon`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),Cx=L({id:7700,name:`Canto`,nativeCurrency:{decimals:18,name:`Canto`,symbol:`CANTO`},rpcUrls:{default:{http:[`https://canto.gravitychain.io`]}},blockExplorers:{default:{name:`Tuber.Build (Blockscout)`,url:`https://tuber.build`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2905789}}}),wx={estimateFeesPerGas:async e=>{if(!e.request?.feeCurrency)return null;let[t,n]=await Promise.all([Tx(e.client,e.request.feeCurrency),Ex(e.client,e.request.feeCurrency)]);return{maxFeePerGas:e.multiply(t-n)+n,maxPriorityFeePerGas:n}}};async function Tx(e,t){let n=await e.request({method:`eth_gasPrice`,params:[t]});return BigInt(n)}async function Ex(e,t){let n=await e.request({method:`eth_maxPriorityFeePerGas`,params:[t]});return BigInt(n)}Cn();function Dx(e){return e===0||e===0n||e==null||e===`0`||e===``||typeof e==`string`&&(Sn(e).toLowerCase()===`0x`||Sn(e).toLowerCase()===`0x00`)}function Ox(e){return!Dx(e)}function kx(e){return e.maxFeePerGas!==void 0&&e.maxPriorityFeePerGas!==void 0}function Ax(e){return e.type===`cip64`?!0:kx(e)&&Ox(e.feeCurrency)}On(),Ku();var jx={block:dd({format(e){return{transactions:e.transactions?.map(e=>typeof e==`string`?e:{...sd(e),...e.gatewayFee?{gatewayFee:Tn(e.gatewayFee),gatewayFeeRecipient:e.gatewayFeeRecipient}:{},feeCurrency:e.feeCurrency})}}}),transaction:cd({format(e){if(e.type===`0x7e`)return{isSystemTx:e.isSystemTx,mint:e.mint?Tn(e.mint):void 0,sourceHash:e.sourceHash,type:`deposit`};let t={feeCurrency:e.feeCurrency};return e.type===`0x7b`?t.type=`cip64`:(e.type===`0x7c`&&(t.type=`cip42`),t.gatewayFee=e.gatewayFee?Tn(e.gatewayFee):null,t.gatewayFeeRecipient=e.gatewayFeeRecipient),t}}),transactionRequest:Gu({format(e){let t={};return e.feeCurrency&&(t.feeCurrency=e.feeCurrency),Ax(e)&&(t.type=`0x7b`),t}})};$u(),gi(),D(),Fp(),Pu(),Ei(),Ai(),k();function Mx(e,t){return Ax(e)?Px(e,t):Ly(e,t)}var Nx={transaction:Mx};function Px(e,t){Ix(e);let{chainId:n,gas:r,nonce:i,to:a,value:o,maxFeePerGas:s,maxPriorityFeePerGas:c,accessList:l,feeCurrency:u,data:d}=e;return ki([`0x7b`,mu([kn(n),i?kn(i):`0x`,c?kn(c):`0x`,s?kn(s):`0x`,r?kn(r):`0x`,a??`0x`,o?kn(o):`0x`,d??`0x`,ph(l),u,...bh(e,t)])])}var Fx=Qu;function Ix(e){let{chainId:t,maxPriorityFeePerGas:n,gasPrice:r,maxFeePerGas:i,to:a,feeCurrency:o}=e;if(t<=0)throw new Pp({chainId:t});if(a&&!Ci(a))throw new hi({address:a});if(r)throw new E("`gasPrice` is not a valid CIP-64 Transaction attribute.");if(Ox(i)&&i>Fx)throw new Cu({maxFeePerGas:i});if(Ox(n)&&Ox(i)&&n>i)throw new Mu({maxFeePerGas:i,maxPriorityFeePerGas:n});if(Ox(o)&&!Ci(o))throw new E("`feeCurrency` MUST be a token address for CIP-64 transactions.");if(Dx(o))throw new E("`feeCurrency` must be provided for CIP-64 transactions.")}var Lx={blockTime:1e3,contracts:Fy,formatters:jx,serializers:Nx,fees:wx},Rx=L({...Lx,id:42220,name:`Celo`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`CELO`},rpcUrls:{default:{http:[`https://forno.celo.org`]}},blockExplorers:{default:{name:`Celo Explorer`,url:`https://celoscan.io`,apiUrl:`https://api.celoscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13112599}},testnet:!1}),zx=17e3,Bx=L({...Lx,id:44787,name:`Alfajores`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`A-CELO`},rpcUrls:{default:{http:[`https://alfajores-forno.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Alfajores Explorer`,url:`https://celo-alfajores.blockscout.com`,apiUrl:`https://celo-alfajores.blockscout.com/api`}},contracts:{...Lx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:14569001},portal:{[zx]:{address:`0x82527353927d8D069b3B452904c942dA149BA381`,blockCreated:2411324}},disputeGameFactory:{[zx]:{address:`0xE28AAdcd9883746c0e5068F58f9ea06027b214cb`,blockCreated:2411324}},l2OutputOracle:{[zx]:{address:`0x4a2635e9e4f6e45817b1D402ac4904c1d1752438`,blockCreated:2411324}},l1StandardBridge:{[zx]:{address:`0xD1B0E0581973c9eB7f886967A606b9441A897037`,blockCreated:2411324}}},testnet:!0}),Vx=11155111,Hx=L({...Lx,id:11142220,name:`Celo Sepolia Testnet`,nativeCurrency:{decimals:18,name:`CELO`,symbol:`S-CELO`},rpcUrls:{default:{http:[`https://forno.celo-sepolia.celo-testnet.org`]}},blockExplorers:{default:{name:`Celo Sepolia Explorer`,url:`https://celo-sepolia.blockscout.com/`,apiUrl:`https://celo-sepolia.blockscout.com/api`}},contracts:{...Lx.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1},portal:{[Vx]:{address:`0x44ae3d41a335a7d05eb533029917aad35662dcc2`,blockCreated:8825790}},disputeGameFactory:{[Vx]:{address:`0x57c45d82d1a995f1e135b8d7edc0a6bb5211cfaa`,blockCreated:8825790}},l1StandardBridge:{[Vx]:{address:`0xec18a3c30131a0db4246e785355fbc16e2eaf408`,blockCreated:8825790}}},testnet:!0}),Ux=L({id:5858,name:`Chang Chain Foundation Mainnet`,nativeCurrency:{decimals:18,name:`CTH`,symbol:`CTH`},rpcUrls:{default:{http:[`https://rpc.cthscan.com`]}},blockExplorers:{default:{name:`Chang Chain explorer`,url:`https://cthscan.com`}}}),Wx=L({id:88888,name:`Chiliz Chain`,network:`chiliz-chain`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://rpc.chiliz.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`https://scan.chiliz.com`,apiUrl:`https://scan.chiliz.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8080847}}}),Gx=L({id:2882,name:`Chips Network`,network:`CHIPS`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://node.chips.ooo/wasp/api/v1/chains/iota1pp3d3mnap3ufmgqnjsnw344sqmf5svjh26y2khnmc89sv6788y3r207a8fn/evm`]}}}),Kx=L({id:4114,name:`Citrea Mainnet`,nativeCurrency:{name:`Citrea Bitcoin`,symbol:`cBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.citrea.xyz`]}},blockExplorers:{default:{name:`Citrea Explorer`,url:`https://explorer.mainnet.citrea.xyz`,apiUrl:`https://explorer.mainnet.citrea.xyz/api`}},testnet:!1}),qx=L({id:5115,name:`Citrea Testnet`,nativeCurrency:{name:`cBTC`,symbol:`cBTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.citrea.xyz`]}},blockExplorers:{default:{name:`Citrea Explorer`,url:`https://explorer.testnet.citrea.xyz`,apiUrl:`https://explorer.testnet.citrea.xyz/api`}},testnet:!0}),Jx=L({id:61,name:`Ethereum Classic`,nativeCurrency:{decimals:18,name:`ETC`,symbol:`ETC`},rpcUrls:{default:{http:[`https://etc.rivet.link`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.com/etc/mainnet`}}}),Yx=1,Xx=L({...R,id:81224,name:`Codex`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.codex.xyz`]}},blockExplorers:{default:{name:`Codex Explorer`,url:`https://explorer.codex.xyz`,apiUrl:`https://explorer.codex.xyz/api`}},contracts:{...R.contracts,disputeGameFactory:{[Yx]:{address:`0x6A3855dc26e2beA8Ac73f82Cda79f3808B6C6F6C`}},portal:{[Yx]:{address:`0x52759C07A759c81BAab28AE1BE5A19e6450959bD`}},l1StandardBridge:{[Yx]:{address:`0xa6b1A05a592719B0C8a70c69eac114C48410aDE4`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},sourceId:Yx}),Zx=11155111,Qx=L({...R,id:812242,name:`Codex Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.codex-stg.xyz`]}},blockExplorers:{default:{name:`Codex Testnet Explorer`,url:`https://explorer.codex-stg.xyz`,apiUrl:`https://explorer.codex-stg.xyz/api`}},contracts:{...R.contracts,disputeGameFactory:{[Zx]:{address:`0x390e24E8324E56f13A8d48eB938b6f9De24CD205`}},portal:{[Zx]:{address:`0x037F161D12c829A9ca4742eEd9371830CA54fcB2`}},l1StandardBridge:{[Zx]:{address:`0xCf4df2bDB14C8FDB25FdacCEC10Ce5C4bAEDB3De`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},sourceId:Zx}),$x=L({id:112,name:`Coinbit Mainnet`,nativeCurrency:{name:`GIDR`,symbol:`GIDR`,decimals:18},rpcUrls:{default:{http:[`https://coinbit-rpc-mainnet.chain.sbcrypto.app`]}},blockExplorers:{default:{name:`Coinbit Explorer`,url:`https://coinbit-explorer.chain.sbcrypto.app`}},testnet:!1}),eS=L({id:52,name:`CoinEx Mainnet`,nativeCurrency:{name:`cet`,symbol:`cet`,decimals:18},rpcUrls:{default:{http:[`https://rpc.coinex.net`]}},blockExplorers:{default:{name:`CoinEx Explorer`,url:`https://www.coinex.net`}},testnet:!1}),tS=L({id:1030,name:`Conflux eSpace`,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evm.confluxrpc.com`],webSocket:[`wss://evm.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evm.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:68602935}}}),nS=L({id:71,name:`Conflux eSpace Testnet`,network:`cfx-espace-testnet`,testnet:!0,nativeCurrency:{name:`Conflux`,symbol:`CFX`,decimals:18},rpcUrls:{default:{http:[`https://evmtestnet.confluxrpc.com`],webSocket:[`wss://evmtestnet.confluxrpc.com/ws`]}},blockExplorers:{default:{name:`ConfluxScan`,url:`https://evmtestnet.confluxscan.org`}},contracts:{multicall3:{address:`0xEFf0078910f638cd81996cc117bccD3eDf2B072F`,blockCreated:117499050}}}),rS=L({id:1116,name:`Core Dao`,nativeCurrency:{decimals:18,name:`Core`,symbol:`CORE`},rpcUrls:{default:{http:[`https://rpc.coredao.org`]}},blockExplorers:{default:{name:`CoreDao`,url:`https://scan.coredao.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:11907934}},testnet:!1}),iS=L({id:1115,name:`Core Testnet`,nativeCurrency:{decimals:18,name:`tCore`,symbol:`TCORE`},rpcUrls:{default:{http:[`https://rpc.test.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet`,url:`https://scan.test.btcs.network`,apiUrl:`https://api.test.btcs.network/api`}},contracts:{multicall3:{address:`0xCcddF20A1932537123C2E48Bd8e00b108B8f7569`,blockCreated:29350509}},testnet:!0}),aS=L({id:1114,name:`Core Testnet2`,nativeCurrency:{decimals:18,name:`tCore2`,symbol:`TCORE2`},rpcUrls:{default:{http:[`https://rpc.test2.btcs.network`]}},blockExplorers:{default:{name:`Core Testnet2`,url:`https://scan.test2.btcs.network`,apiUrl:`https://api.test2.btcs.network/api`}},contracts:{multicall3:{address:`0x3CB285ff3Cd5C7C7e570b1E7DE3De17A0f985e56`,blockCreated:3838600}},testnet:!0}),oS=L({id:21e6,name:`Corn`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Explorer`,url:`https://cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/21000000/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3228}},sourceId:1}),sS=L({id:21000001,name:`Corn Testnet`,nativeCurrency:{decimals:18,name:`Bitcorn`,symbol:`BTCN`},rpcUrls:{default:{http:[`https://21000001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Corn Testnet Explorer`,url:`https://testnet.cornscan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/21000001/etherscan/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4886}},testnet:!0,sourceId:11155111}),cS=L({id:86608,name:`CpChain`,nativeCurrency:{decimals:18,name:`CpChain`,symbol:`CP`},rpcUrls:{default:{http:[`https://rpc.cpchain.com`]}},blockExplorers:{default:{name:`CpChain Explorer`,url:`https://explorer.cpchain.com`}},testnet:!1}),lS=L({id:44,name:`Crab Network`,nativeCurrency:{decimals:18,name:`Crab Network Native Token`,symbol:`CRAB`},rpcUrls:{default:{http:[`https://crab-rpc.darwinia.network`],webSocket:[`wss://crab-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://crab-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3032593}}}),uS=L({id:66665,name:`Creator`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.creatorchain.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.creatorchain.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0}),dS=L({id:102032,name:`Creditcoin Devnet`,nativeCurrency:{name:`Devnet CTC`,symbol:`devCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-devnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-devnet.creditcoin.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-devnet.blockscout.com`,apiUrl:`https://creditcoin3-dev.subscan.io`}},testnet:!0}),fS=L({id:102030,name:`Creditcoin`,nativeCurrency:{name:`Creditcoin`,symbol:`CTC`,decimals:18},rpcUrls:{default:{http:[`https://mainnet3.creditcoin.network`],webSocket:[`wss://mainnet3.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin.blockscout.com`,apiUrl:`https://creditcoin.blockscout.com/api`}},testnet:!1}),pS=L({id:102031,name:`Creditcoin Testnet`,nativeCurrency:{name:`Creditcoin Testnet`,symbol:`tCTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cc3-testnet.creditcoin.network`],webSocket:[`wss://rpc.cc3-testnet.creditcoin.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://creditcoin-testnet.blockscout.com`,apiUrl:`https://creditcoin-testnet.blockscout.com/api`}},testnet:!0}),mS=L({id:25,name:`Cronos Mainnet`,nativeCurrency:{decimals:18,name:`Cronos`,symbol:`CRO`},rpcUrls:{default:{http:[`https://evm.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer`,url:`https://explorer.cronos.org`,apiUrl:`https://explorer-api.cronos.org/mainnet/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1963112}}}),hS=L({id:338,name:`Cronos Testnet`,nativeCurrency:{decimals:18,name:`CRO`,symbol:`tCRO`},rpcUrls:{default:{http:[`https://evm-t3.cronos.org`]}},blockExplorers:{default:{name:`Cronos Explorer (Testnet)`,url:`https://explorer.cronos.org/testnet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:10191251}},testnet:!0}),gS=L({id:388,name:`Cronos zkEVM Mainnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM CRO`,symbol:`zkCRO`},rpcUrls:{default:{http:[`https://mainnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM (Mainnet) Chain Explorer`,url:`https://explorer.zkevm.cronos.org`}},contracts:{multicall3:{address:`0x06f4487d7c4a5983d2660db965cc6d2565e4cfaa`,blockCreated:72}}}),_S=L({id:282,name:`Cronos zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Cronos zkEVM Test Coin`,symbol:`zkTCRO`},rpcUrls:{default:{http:[`https://testnet.zkevm.cronos.org`]}},blockExplorers:{default:{name:`Cronos zkEVM Testnet Explorer`,url:`https://explorer.zkevm.cronos.org/testnet`}},testnet:!0}),vS=L({id:3737,name:`Crossbell`,nativeCurrency:{decimals:18,name:`CSB`,symbol:`CSB`},rpcUrls:{default:{http:[`https://rpc.crossbell.io`]}},blockExplorers:{default:{name:`CrossScan`,url:`https://scan.crossbell.io`,apiUrl:`https://scan.crossbell.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:38246031}}}),yS=L({id:4158,name:`CrossFi Mainnet`,nativeCurrency:{decimals:18,name:`CrossFi`,symbol:`XFI`},rpcUrls:{default:{http:[`https://rpc.mainnet.ms`]}},blockExplorers:{default:{name:`CrossFi Blockchain Explorer`,url:`https://xfiscan.com`}},testnet:!1}),bS=L({id:33111,name:`Curtis`,nativeCurrency:{name:`ApeCoin`,symbol:`APE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.curtis.apechain.com`]}},blockExplorers:{default:{name:`Curtis Explorer`,url:`https://explorer.curtis.apechain.com`}},testnet:!0}),xS=L({id:7560,name:`Cyber`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://cyberscan.co`,apiUrl:`https://cyberscan.co/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),SS=L({id:111557560,name:`Cyber Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://cyber-testnet.alt.technology`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet.cyberscan.co`,apiUrl:`https://testnet.cyberscan.co/api`}},contracts:{multicall3:{address:`0xffc391F0018269d4758AEA1a144772E8FB99545E`,blockCreated:304545}},testnet:!0}),CS=L({id:824,name:`Daily Network Mainnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.mainnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Mainnet Explorer`,url:`https://explorer.mainnet.dailycrypto.net`}},testnet:!1}),wS=L({id:825,name:`Daily Network Testnet`,nativeCurrency:{decimals:18,name:`Daily`,symbol:`DLY`},rpcUrls:{default:{http:[`https://rpc.testnet.dailycrypto.net`]}},blockExplorers:{default:{name:`Daily Testnet Explorer`,url:`https://explorer.testnet.dailycrypto.net`}},testnet:!0}),TS=L({id:46,name:`Darwinia Network`,nativeCurrency:{decimals:18,name:`RING`,symbol:`RING`},rpcUrls:{default:{http:[`https://rpc.darwinia.network`],webSocket:[`wss://rpc.darwinia.network`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69420}}}),ES=L({id:55931,name:`Datahaven Testnet`,nativeCurrency:{decimals:18,name:`MOCK`,symbol:`MOCK`},rpcUrls:{default:{http:[`https://services.datahaven-testnet.network/testnet`],webSocket:[`wss://services.datahaven-testnet.network/testnet`]}},blockExplorers:{default:{name:`DhScan`,url:`https://testnet.dhscan.io/`,apiUrl:`https://testnet.dhscan.io/api-docs`}},contracts:{},testnet:!0}),DS=L({id:20240603,name:`DBK chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.dbkchain.io`]}},blockExplorers:{default:{name:`DBK Chain Explorer`,url:`https://scan.dbkchain.io`}},testnet:!1}),OS=L({...R,id:0x9a697f88076c8,name:`Dchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchain-2716446429837000-1.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchain-2716446429837000-1.sagaexplorer.io`,apiUrl:`https://api-dchain-2716446429837000-1.sagaexplorer.io/api`}},contracts:{...R.contracts}}),kS=L({...R,id:0x9a379ba03cf10,name:`Dchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://dchaintestnet-2713017997578000-1.jsonrpc.testnet.sagarpc.io`]}},blockExplorers:{default:{name:`Dchain Explorer`,url:`https://dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io`,apiUrl:`https://api-dchaintestnet-2713017997578000-1.testnet.sagaexplorer.io/api`}},contracts:{...R.contracts}}),AS=L({id:1130,network:`defichain-evm`,name:`DeFiChain EVM Mainnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.mainnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:137852}}}),jS=L({id:1131,network:`defichain-evm-testnet`,name:`DeFiChain EVM Testnet`,nativeCurrency:{name:`DeFiChain`,symbol:`DFI`,decimals:18},rpcUrls:{default:{http:[`https://eth.testnet.ocean.jellyfishsdk.com`]}},blockExplorers:{default:{name:`DeFiScan`,url:`https://meta.defiscan.live/?network=TestNet`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:156462}},testnet:!0}),MS=L({id:666666666,name:`Degen`,nativeCurrency:{decimals:18,name:`Degen`,symbol:`DEGEN`},rpcUrls:{default:{http:[`https://rpc.degen.tips`],webSocket:[`wss://rpc.degen.tips`]}},blockExplorers:{default:{name:`Degen Chain Explorer`,url:`https://explorer.degen.tips`,apiUrl:`https://explorer.degen.tips/api/v2`}}}),NS=L({id:53935,name:`DFK Chain`,nativeCurrency:{decimals:18,name:`Jewel`,symbol:`JEWEL`},rpcUrls:{default:{http:[`https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc`]}},blockExplorers:{default:{name:`DFKSubnetScan`,url:`https://subnets.avax.network/defi-kingdoms`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14790551}}}),PS=L({id:15,name:`Diode Prenet`,nativeCurrency:{decimals:18,name:`DIODE`,symbol:`DIODE`},rpcUrls:{default:{http:[`https://prenet.diode.io:8443`],webSocket:[`wss://prenet.diode.io:8443/ws`]}},blockExplorers:{default:{name:`Diode Explorer`,url:`https://diode.io/prenet`}},testnet:!1}),FS=L({id:513100,name:`DisChain`,nativeCurrency:{decimals:18,name:`DIS`,symbol:`DIS`},rpcUrls:{default:{http:[`https://rpc.dischain.xyz`]}},blockExplorers:{default:{name:`DisChain Explorer`,url:`https://www.oklink.com/dis`}}}),IS=L({id:53457,name:`DODOchain Testnet`,nativeCurrency:{decimals:18,name:`DODO`,symbol:`DODO`},rpcUrls:{default:{http:[`https://dodochain-testnet.alt.technology`],webSocket:[`wss://dodochain-testnet.alt.technology/ws`]}},blockExplorers:{default:{name:`DODOchain Testnet (Sepolia) Explorer`,url:`https://testnet-scan.dodochain.com`}},testnet:!0}),LS=L({id:2e3,name:`Dogechain`,nativeCurrency:{decimals:18,name:`Wrapped Dogecoin`,symbol:`WDOGE`},rpcUrls:{default:{http:[`https://rpc.dogechain.dog`]}},blockExplorers:{default:{name:`DogeChainExplorer`,url:`https://explorer.dogechain.dog`,apiUrl:`https://explorer.dogechain.dog/api`}},contracts:{multicall3:{address:`0x68a8609a60a008EFA633dfdec592c03B030cC508`,blockCreated:25384031}}}),RS=L({id:97476,name:`Doma Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc-testnet.doma.xyz`]}},blockExplorers:{default:{name:`Doma Testnet Explorer`,url:`https://explorer-testnet.doma.xyz`}},testnet:!0}),zS=L({id:42026,name:`Donatuz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.donatuz.com`]}},blockExplorers:{default:{name:`Donatuz Explorer`,url:`https://explorer.donatuz.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),BS=L({id:7979,name:`DOS Chain`,nativeCurrency:{decimals:18,name:`DOS Chain`,symbol:`DOS`},rpcUrls:{default:{http:[`https://main.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Explorer`,url:`https://doscan.io`,apiUrl:`https://api.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:161908}}}),VS=L({id:3939,name:`DOS Chain Testnet`,nativeCurrency:{decimals:18,name:`DOS Chain Testnet`,symbol:`DOS`},rpcUrls:{default:{http:[`https://test.doschain.com`]}},blockExplorers:{default:{name:`DOS Chain Testnet Explorer`,url:`https://test.doscan.io`,apiUrl:`https://api-test.doscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:69623}},testnet:!0}),HS=L({id:23451,name:`DreyerX Mainnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Scan`,url:`https://scan.dreyerx.com`}}}),US=L({id:23452,name:`DreyerX Testnet`,nativeCurrency:{name:`DreyerX`,symbol:`DRX`,decimals:18},rpcUrls:{default:{http:[`http://testnet-rpc.dreyerx.com`]}},blockExplorers:{default:{name:`DreyerX Testnet Scan`,url:`https://testnet-scan.dreyerx.com`}},testnet:!0}),WS=L({id:555888,name:`DustBoy IoT`,nativeCurrency:{name:`Ether`,symbol:`DST`,decimals:18},rpcUrls:{default:{http:[`https://dustboy-rpc.jibl2.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://dustboy.jibl2.com`,apiUrl:`https://dustboy.jibl2.com/api`}},contracts:{multicall3:{address:`0xFFD34aa2C62B2D52E00A361e466C229788f4eD6a`,blockCreated:526569}},testnet:!1}),GS=L({id:1100,name:`Dymension`,nativeCurrency:{name:`DYM`,symbol:`DYM`,decimals:18},rpcUrls:{default:{http:[`https://dymension-evm-rpc.publicnode.com`],webSocket:[`wss://dymension-evm-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Dym FYI`,url:`https://dym.fyi`}},testnet:!1}),KS=L({id:5424,name:`edeXa`,nativeCurrency:{name:`edeXa`,symbol:`EDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edexa.network`]}},blockExplorers:{default:{name:`edeXa Explorer`,url:`https://explorer.edexa.network`,apiUrl:`https://explorer.edexa.network/api/v2`}}}),qS=L({id:1995,name:`edeXa Testnet`,nativeCurrency:{name:`edeXa`,symbol:`tEDX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.edexa.network`]}},blockExplorers:{default:{name:`edeXa Testnet Explorer`,url:`https://explorer.testnet.edexa.network`,apiUrl:`https://explorer.testnet.edexa.network/api/v2`}},testnet:!0}),JS=L({id:2026,name:`Edgeless Network`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.edgeless.network/http`],webSocket:[`wss://rpc.edgeless.network/ws`]}},blockExplorers:{default:{name:`Edgeless Explorer`,url:`https://explorer.edgeless.network`}}}),YS=L({id:202,name:`Edgeless Testnet`,nativeCurrency:{name:`Edgeless Wrapped ETH`,symbol:`EwETH`,decimals:18},rpcUrls:{default:{http:[`https://edgeless-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://edgeless-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Edgeless Testnet Explorer`,url:`https://testnet.explorer.edgeless.network`}}}),XS=L({id:2021,name:`Edgeware EdgeEVM Mainnet`,nativeCurrency:{decimals:18,name:`Edgeware`,symbol:`EDG`},rpcUrls:{default:{http:[`https://edgeware-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://edgscan.live`,apiUrl:`https://edgscan.live/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18117872}}}),ZS=L({id:2022,name:`Beresheet BereEVM Testnet`,nativeCurrency:{decimals:18,name:`Testnet EDG`,symbol:`tEDG`},rpcUrls:{default:{http:[`https://beresheet-evm.jelliedowl.net`]}},blockExplorers:{default:{name:`Edgscan by Bharathcoorg`,url:`https://testnet.edgscan.live`,apiUrl:`https://testnet.edgscan.live/api`}}}),QS=L({id:41923,name:`EDU Chain`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.edu-chain.raas.gelato.cloud`]}},blockExplorers:{default:{name:`EDU Chain Explorer`,url:`https://educhain.blockscout.com/`}},testnet:!1}),$S=L({id:656476,name:`EDU Chain Testnet`,nativeCurrency:{decimals:18,name:`EDU`,symbol:`EDU`},rpcUrls:{default:{http:[`https://rpc.open-campus-codex.gelato.digital/`],webSocket:[`wss://ws.open-campus-codex.gelato.digital`]}},blockExplorers:{default:{name:`EDU Chain Testnet Explorer`,url:`https://opencampus-codex.blockscout.com`,apiUrl:`https://opencampus-codex.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15514133}},testnet:!0}),eC=L({id:20,name:`Elastos Smart Chain`,nativeCurrency:{name:`ELA`,symbol:`ELA`,decimals:18},rpcUrls:{default:{http:[`https://api2.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc.elastos.io`}},testnet:!1}),tC=L({id:21,name:`Elastos Smart Chain Testnet`,nativeCurrency:{name:`tELA`,symbol:`tELA`,decimals:18},rpcUrls:{default:{http:[`https://api-testnet.elastos.io/eth`]}},blockExplorers:{default:{name:`Elastos Explorer`,url:`https://esc-testnet.elastos.io`}},testnet:!0}),nC=L({id:52014,name:`Electroneum Mainnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.electroneum.com`}},testnet:!1}),rC=L({id:5201420,name:`Electroneum Testnet`,nativeCurrency:{name:`ETN`,symbol:`ETN`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.electroneum.com`]}},blockExplorers:{default:{name:`Electroneum Block Explorer`,url:`https://blockexplorer.thesecurityteam.rocks`}},testnet:!0}),iC=L({...R,id:1338,name:`Elysium Testnet`,nativeCurrency:{decimals:18,name:`LAVA`,symbol:`LAVA`},rpcUrls:{default:{http:[`https://elysium-test-rpc.vulcanforged.com`]}},blockExplorers:{default:{name:`Elysium testnet explorer`,url:`https://elysium-explorer.vulcanforged.com`}},testnet:!0}),aC=L({id:246,name:`Energy Mainnet`,nativeCurrency:{name:`EWT`,symbol:`EWT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.energyweb.org`]}},blockExplorers:{default:{name:`EnergyWeb Explorer`,url:`https://explorer.energyweb.org`}},testnet:!1}),oC=L({id:173,name:`ENI Mainnet`,nativeCurrency:{decimals:18,name:`ENI`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc.eniac.network`]}},blockExplorers:{default:{name:`ENI Explorer`,url:`https://scan.eniac.network`}},testnet:!1}),sC=L({id:6912115,name:`ENI Testnet`,nativeCurrency:{decimals:18,name:`ENI Testnet Token`,symbol:`ENI`},rpcUrls:{default:{http:[`https://rpc-testnet.eniac.network`]}},blockExplorers:{default:{name:`ENI Testnet Explorer`,url:`https://scan-testnet.eniac.network`}},testnet:!0}),cC=L({id:119,name:`ENULS Mainnet`,nativeCurrency:{decimals:18,name:`NULS`,symbol:`NULS`},rpcUrls:{default:{http:[`https://evmapi2.nuls.io`]}},blockExplorers:{default:{name:`ENULS Explorer`,url:`https://evmscan.nuls.io`}},testnet:!1}),lC=L({id:7332,name:`Horizen EON`,nativeCurrency:{decimals:18,name:`ZEN`,symbol:`ZEN`},rpcUrls:{default:{http:[`https://eon-rpc.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`EON Explorer`,url:`https://eon-explorer.horizenlabs.io`}},contracts:{}}),uC=L({id:17777,name:`EOS EVM`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Explorer`,url:`https://explorer.evm.eosnetwork.com`,apiUrl:`https://explorer.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:7943933}}}),dC=L({id:15557,name:`EOS EVM Testnet`,nativeCurrency:{decimals:18,name:`EOS`,symbol:`EOS`},rpcUrls:{default:{http:[`https://api.testnet.evm.eosnetwork.com`]}},blockExplorers:{default:{name:`EOS EVM Testnet Explorer`,url:`https://explorer.testnet.evm.eosnetwork.com`,apiUrl:`https://explorer.testnet.evm.eosnetwork.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9067940}},testnet:!0}),fC=L({id:140,name:`Eteria`,nativeCurrency:{name:`Eteria`,symbol:`ERA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.eteria.io/v1`]}},blockExplorers:{default:{name:`Eteria Explorer`,url:`https://explorer.eteria.io`,apiUrl:`https://explorer.eteria.io/api`}}}),pC=L({id:42793,name:`Etherlink`,blockTime:4830,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.mainnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink`,url:`https://explorer.etherlink.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:33899}}}),mC=L({id:127823,name:`Etherlink Shadownet Testnet`,nativeCurrency:{decimals:18,name:`tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.shadownet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink Shadownet Testnet Explorer`,url:`https://shadownet.explorer.etherlink.com`}},testnet:!0}),hC=L({id:128123,name:`Etherlink Testnet`,nativeCurrency:{decimals:18,name:`Tez`,symbol:`XTZ`},rpcUrls:{default:{http:[`https://node.ghostnet.etherlink.com`]}},blockExplorers:{default:{name:`Etherlink Testnet`,url:`https://testnet.explorer.etherlink.com`}},testnet:!0}),gC=L({id:183,name:`Ethernity`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.ethernitychain.io`]}},blockExplorers:{default:{name:`Ethernity Explorer`,url:`https://ernscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),_C=L({id:20256789,name:`ETP Mainnet`,nativeCurrency:{decimals:18,name:`ETP Chain Native Token`,symbol:`ETP`},rpcUrls:{default:{http:[`https://rpc.etpscan.xyz`]}},blockExplorers:{default:{name:`ETP Scan`,url:`https://etpscan.xyz`}}}),vC=L({id:9001,name:`Evmos`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.org:8545`]}},blockExplorers:{default:{name:`Evmos Block Explorer`,url:`https://escan.live`}}}),yC=L({id:9e3,name:`Evmos Testnet`,nativeCurrency:{decimals:18,name:`Evmos`,symbol:`EVMOS`},rpcUrls:{default:{http:[`https://eth.bd.evmos.dev:8545`]}},blockExplorers:{default:{name:`Evmos Testnet Block Explorer`,url:`https://evm.evmos.dev/`}}}),bC=L({id:22052002,name:`Excelon Mainnet`,network:`XLON`,nativeCurrency:{decimals:18,name:`Excelon`,symbol:`xlon`},rpcUrls:{default:{http:[`https://edgewallet1.xlon.org`]}},blockExplorers:{default:{name:`Excelon explorer`,url:`https://explorer.excelon.io`}}}),xC=L({id:2,name:`Expanse Network`,nativeCurrency:{decimals:18,name:`EXP`,symbol:`EXP`},rpcUrls:{default:{http:[`https://node.expanse.tech`]}},blockExplorers:{default:{name:`Expanse Explorer`,url:`https://explorer.expanse.tech`}},testnet:!1}),SC=L({id:7200,name:`exSat Network`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan.exsat.network`,apiUrl:`https://scan.exsat.network/api`}}}),CC=L({id:839999,name:`exSat Testnet`,nativeCurrency:{decimals:18,name:`BTC`,symbol:`BTC`},rpcUrls:{default:{http:[`https://evm-tst3.exsat.network`]}},blockExplorers:{default:{name:`exSat Explorer`,url:`https://scan-testnet.exsat.network`,apiUrl:`https://scan-testnet.exsat.network/api`}}}),wC=L({id:250,name:`Fantom`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://250.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://ftmscan.com`,apiUrl:`https://api.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:33001987}}}),TC=L({id:64240,name:`Fantom Sonic Open Testnet`,network:`fantom-sonic-testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpcapi.sonic.fantom.network`]}},blockExplorers:{default:{name:`Fantom Sonic Open Testnet Explorer`,url:`https://public-sonic.fantom.network`}},testnet:!0}),EC=L({id:4002,name:`Fantom Testnet`,nativeCurrency:{decimals:18,name:`Fantom`,symbol:`FTM`},rpcUrls:{default:{http:[`https://rpc.testnet.fantom.network`]}},blockExplorers:{default:{name:`FTMScan`,url:`https://testnet.ftmscan.com`,apiUrl:`https://testnet.ftmscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8328688}},testnet:!0}),DC=L({id:12306,name:`Fibo Chain`,nativeCurrency:{decimals:18,name:`fibo`,symbol:`FIBO`},rpcUrls:{default:{http:[`https://network.hzroc.art`]}},blockExplorers:{default:{name:`FiboScan`,url:`https://scan.fibochain.org`}}}),OC=L({id:314,name:`Filecoin Mainnet`,nativeCurrency:{decimals:18,name:`filecoin`,symbol:`FIL`},rpcUrls:{default:{http:[`https://api.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://filfox.info/en`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3328594}}}),kC=L({id:314159,name:`Filecoin Calibration`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.calibration.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filscan`,url:`https://calibration.filscan.io`}},testnet:!0}),AC=L({id:3141,name:`Filecoin Hyperspace`,nativeCurrency:{decimals:18,name:`testnet filecoin`,symbol:`tFIL`},rpcUrls:{default:{http:[`https://api.hyperspace.node.glif.io/rpc/v1`]}},blockExplorers:{default:{name:`Filfox`,url:`https://hyperspace.filfox.info/en`}},testnet:!0}),jC=L({id:253368190,name:`Flame`,network:`flame`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.flame.astria.org`],webSocket:[`wss://ws.flame.astria.org`]}},blockExplorers:{default:{name:`Flame Explorer`,url:`https://explorer.flame.astria.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6829148}}}),MC=L({id:14,name:`Flare Mainnet`,nativeCurrency:{decimals:18,name:`Flare`,symbol:`FLR`},rpcUrls:{default:{http:[`https://flare-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Flare Explorer`,url:`https://flare-explorer.flare.network`,apiUrl:`https://flare-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3002461}}}),NC=L({id:114,name:`Flare Testnet Coston2`,nativeCurrency:{decimals:18,name:`Coston2 Flare`,symbol:`C2FLR`},rpcUrls:{default:{http:[`https://coston2-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston2 Explorer`,url:`https://coston2-explorer.flare.network`,apiUrl:`https://coston2-explorer.flare.network/api`}},testnet:!0}),PC=L({id:747,name:`Flow EVM Mainnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://mainnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Mainnet Explorer`,url:`https://evm.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}},blockTime:800}),FC=L({id:646,name:`Flow EVM Previewnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://previewnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Previewnet Explorer`,url:`https://previewnet.flowdiver.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6205}}}),IC=L({id:545,name:`Flow EVM Testnet`,nativeCurrency:{decimals:18,name:`Flow`,symbol:`FLOW`},rpcUrls:{default:{http:[`https://testnet.evm.nodes.onflow.org`]}},blockExplorers:{default:{name:`Flow Diver`,url:`https://evm-testnet.flowscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:137518}},testnet:!0,blockTime:800}),LC=L({id:9999999,name:`Fluence`,nativeCurrency:{name:`FLT`,symbol:`FLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.fluence.dev`],webSocket:[`wss://ws.mainnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.mainnet.fluence.dev`,apiUrl:`https://blockscout.mainnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:207583}}}),RC=L({id:123420000220,name:`Fluence Stage`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stage.fluence.dev`],webSocket:[`wss://ws.stage.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.stage.fluence.dev`,apiUrl:`https://blockscout.stage.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83227}},testnet:!0}),zC=L({id:52164803,name:`Fluence Testnet`,nativeCurrency:{name:`tFLT`,symbol:`tFLT`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.fluence.dev`],webSocket:[`wss://ws.testnet.fluence.dev`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.testnet.fluence.dev`,apiUrl:`https://blockscout.testnet.fluence.dev/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96424}},testnet:!0}),BC=L({id:20993,name:`Fluent Devnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.devnet.fluent.xyz`]}},blockExplorers:{default:{name:`Fluent Devnet Explorer`,url:`https://devnet.fluentscan.xyz`}},testnet:!0}),VC=L({id:20994,name:`Fluent Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.fluent.xyz`]}},blockExplorers:{default:{name:`Fluent Testnet Explorer`,url:`https://testnet.fluentscan.xyz`}},testnet:!0}),HC=1,UC=L({id:478,name:`Form Network`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.form.network/http`],webSocket:[`wss://rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Explorer`,url:`https://explorer.form.network`}},contracts:{...R.contracts,addressManager:{[HC]:{address:`0x15c249E46A2F924C2dB3A1560CF86729bAD1f07B`}},l1CrossDomainMessenger:{[HC]:{address:`0xF333158DCCad1dF6C3F0a3aEe8BC31fA94d9eD5c`}},l2OutputOracle:{[HC]:{address:`0x4ccAAF69F41c5810cA875183648B577CaCf1F67E`}},portal:{[HC]:{address:`0x4E259Ee5F4136408908160dD32295A5031Fa426F`}},l1StandardBridge:{[HC]:{address:`0xdc20aA63D3DE59574E065957190D8f24e0F7B8Ba`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:HC}),WC=L({id:984122,name:`Forma`,network:`forma`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.forma.art`],webSocket:[`wss://ws.forma.art`]}},blockExplorers:{default:{name:`Forma Explorer`,url:`https://explorer.forma.art`}},contracts:{multicall3:{address:`0xd53C6FFB123F7349A32980F87faeD8FfDc9ef079`,blockCreated:252705}}}),GC=11155111,KC=L({id:132902,name:`Form Testnet`,nativeCurrency:{decimals:18,name:`Ethereum`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia-rpc.form.network/http`],webSocket:[`wss://sepolia-rpc.form.network/ws`]}},blockExplorers:{default:{name:`Form Testnet Explorer`,url:`https://sepolia-explorer.form.network`}},contracts:{...R.contracts,addressManager:{[GC]:{address:`0xd5C38fa934f7fd7477D4800F4f38a1c5BFdF1373`}},l1CrossDomainMessenger:{[GC]:{address:`0x37A68565c4BE9700b3E3Ec60cC4416cAC3052FAa`}},l2OutputOracle:{[GC]:{address:`0x9eA2239E65a59EC9C7F1ED4C116dD58Da71Fc1e2`}},portal:{[GC]:{address:`0x60377e3cE15dF4CCA24c4beF076b60314240b032`}},l1StandardBridge:{[GC]:{address:`0xD4531f633942b2725896F47cD2aFd260b44Ab1F7`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},testnet:!0,sourceId:GC}),qC=L({id:80931,name:`Forta Chain`,nativeCurrency:{symbol:`FORT`,name:`FORT`,decimals:18},rpcUrls:{default:{http:[`https://rpc-forta-chain-8gj1qndmfc.t.conduit.xyz`]}},blockExplorers:{default:{name:`Forta Explorer`,url:`https://explorer.forta.org`}}}),JC=L({id:31337,name:`Foundry`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`],webSocket:[`ws://127.0.0.1:8545`]}}}),YC=1,XC=L({...R,id:252,name:`Fraxtal`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.frax.com`]}},blockExplorers:{default:{name:`fraxscan`,url:`https://fraxscan.com`,apiUrl:`https://api.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[YC]:{address:`0x66CC916Ed5C6C2FA97014f7D1cD141528Ae171e4`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[YC]:{address:`0x36cb65c1967A0Fb0EEE11569C51C2f2aA1Ca6f6D`,blockCreated:19135323}},l1StandardBridge:{[YC]:{address:`0x34C0bD5877A5Ee7099D0f5688D65F4bB9158BDE2`,blockCreated:19135323}}},sourceId:YC}),ZC=17e3,QC=L({...R,id:2522,name:`Fraxtal Testnet`,nativeCurrency:{name:`Frax`,symbol:`FRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.frax.com`]}},blockExplorers:{default:{name:`fraxscan testnet`,url:`https://holesky.fraxscan.com`,apiUrl:`https://api-holesky.fraxscan.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[ZC]:{address:`0x715EA64DA13F4d0831ece4Ad3E8c1aa013167F32`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[ZC]:{address:`0xB9c64BfA498d5b9a8398Ed6f46eb76d90dE5505d`,blockCreated:318416}},l1StandardBridge:{[ZC]:{address:`0x0BaafC217162f64930909aD9f2B27125121d6332`,blockCreated:318416}}},sourceId:ZC}),$C=1,ew=L({...R,id:33979,name:`Funki`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.funkichain.com`]}},blockExplorers:{default:{name:`Funki Mainnet Explorer`,url:`https://funkiscan.io`}},contracts:{...R.contracts},sourceId:$C}),tw=11155111,nw=L({...R,id:3397901,network:`funkiSepolia`,name:`Funki Sepolia Sandbox`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://funki-testnet.alt.technology`]}},blockExplorers:{default:{name:`Funki Sepolia Sandbox Explorer`,url:`https://sepolia-sandbox.funkichain.com/`}},testnet:!0,contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204}},sourceId:tw}),rw=L({id:122,name:`Fuse`,nativeCurrency:{name:`Fuse`,symbol:`FUSE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fuse.io`]}},blockExplorers:{default:{name:`Fuse Explorer`,url:`https://explorer.fuse.io`,apiUrl:`https://explorer.fuse.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:16146628}}}),iw=L({id:123,name:`Fuse Sparknet`,nativeCurrency:{name:`Spark`,symbol:`SPARK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.fusespark.io`]}},blockExplorers:{default:{name:`Sparkent Explorer`,url:`https://explorer.fusespark.io`,apiUrl:`https://explorer.fusespark.io/api`}}}),aw=L({id:32659,name:`Fusion Mainnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.fusionnetwork.io`],webSocket:[`wss://mainnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10441605}},testnet:!1}),ow=L({id:46688,name:`Fusion Testnet`,nativeCurrency:{name:`Fusion`,symbol:`FSN`,decimals:18},rpcUrls:{default:{http:[`https://testnet.fusionnetwork.io`],webSocket:[`wss://testnet.fusionnetwork.io`]}},blockExplorers:{default:{name:`FSNscan`,url:`https://testnet.fsnscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10428309}},testnet:!0}),sw=17e3,cw=L({...R,name:`Garnet Testnet`,testnet:!0,id:17069,sourceId:sw,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.garnetchain.com`],webSocket:[`wss://rpc.garnetchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.garnetchain.com`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[sw]:{address:`0x57ee40586fbE286AfC75E67cb69511A6D9aF5909`,blockCreated:1274684}},l2OutputOracle:{[sw]:{address:`0xCb8E7AC561b8EF04F2a15865e9fbc0766FEF569B`,blockCreated:1274684}},l1StandardBridge:{[sw]:{address:`0x09bcDd311FE398F80a78BE37E489f5D440DB95DE`,blockCreated:1274684}}}}),lw=L({id:86,name:`GateChain Mainnet`,nativeCurrency:{name:`GateChainToken`,symbol:`GT`,decimals:18},rpcUrls:{default:{http:[`https://evm.nodeinfo.cc`],webSocket:[`wss://evm-ws.gatenode.cc`]}},blockExplorers:{default:{name:`Gate Scan`,url:`https://www.gatescan.org`,apiUrl:`https://gatescan.org/api`}},testnet:!1}),uw=L({id:63157,name:`Geist Mainnet`,nativeCurrency:{decimals:18,name:`Aavegotchi GHST Token`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://geist-mainnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:660735}}}),dw=L({id:16507,name:`Genesys Mainnet`,nativeCurrency:{decimals:18,name:`GSYS`,symbol:`GSYS`},rpcUrls:{default:{http:[`https://rpc.genesys.network`]}},blockExplorers:{default:{name:`Genesys Explorer`,url:`https://gchainexplorer.genesys.network`}},testnet:!1}),fw=11155111,pw=L({...R,id:91342,network:`giwa-sepolia`,name:`GIWA Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia-rpc.giwa.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-explorer.giwa.io`,apiUrl:`https://sepolia-explorer.giwa.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[fw]:{address:`0x37347caB2afaa49B776372279143D71ad1f354F6`}},portal:{[fw]:{address:`0x956962C34687A954e611A83619ABaA37Ce6bC78A`}},l1StandardBridge:{[fw]:{address:`0x77b2ffc0F57598cAe1DB76cb398059cF5d10A7E7`}}},testnet:!0,sourceId:fw}),mw=L({...pw,experimental_preconfirmationTime:200,rpcUrls:{default:{http:[`https://sepolia-rpc-flashblocks.giwa.io`]}}}),hw=L({id:251,name:`Glide L1 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l1-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l1-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),gw=L({id:253,name:`Glide L2 Protocol XP`,nativeCurrency:{name:`GLXP`,symbol:`GLXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc-api.glideprotocol.xyz/l2-rpc`],webSocket:[`wss://rpc-api.glideprotocol.xyz/l2-rpc`]}},blockExplorers:{default:{name:`Glide Protocol Explore`,url:`https://blockchain-explorer.glideprotocol.xyz`}},testnet:!1}),_w=L({id:100,name:`Gnosis`,nativeCurrency:{decimals:18,name:`xDAI`,symbol:`XDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.gnosischain.com`],webSocket:[`wss://rpc.gnosischain.com/wss`]}},blockExplorers:{default:{name:`Gnosisscan`,url:`https://gnosisscan.io`,apiUrl:`https://api.gnosisscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:21022491}}}),vw=L({id:10200,name:`Gnosis Chiado`,nativeCurrency:{decimals:18,name:`Gnosis`,symbol:`xDAI`},blockTime:5e3,rpcUrls:{default:{http:[`https://rpc.chiadochain.net`],webSocket:[`wss://rpc.chiadochain.net/wss`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.chiadochain.net`,apiUrl:`https://blockscout.chiadochain.net/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4967313}},testnet:!0}),yw=L({id:2345,name:`GOAT`,nativeCurrency:{decimals:18,name:`Bitcoin`,symbol:`BTC`},rpcUrls:{default:{http:[`https://rpc.goat.network`]}},blockExplorers:{default:{name:`Goat Explorer`,url:`https://explorer.goat.network`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),bw=L({id:1663,name:`Horizen Gobi Testnet`,nativeCurrency:{decimals:18,name:`Test ZEN`,symbol:`tZEN`},rpcUrls:{default:{http:[`https://gobi-testnet.horizenlabs.io/ethv1`]}},blockExplorers:{default:{name:`Gobi Explorer`,url:`https://gobi-explorer.horizen.io`}},contracts:{},testnet:!0}),xw=L({id:60,name:`GoChain`,nativeCurrency:{decimals:18,name:`GO`,symbol:`GO`},rpcUrls:{default:{http:[`https://rpc.gochain.io`]}},blockExplorers:{default:{name:`GoChain Explorer`,url:`https://explorer.gochain.io`}},testnet:!1}),Sw=L({id:71402,name:`Godwoken Mainnet`,nativeCurrency:{decimals:18,name:`pCKB`,symbol:`pCKB`},rpcUrls:{default:{http:[`https://v1.mainnet.godwoken.io/rpc`]}},blockExplorers:{default:{name:`GW Scan`,url:`https://v1.gwscan.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:15034}},testnet:!1}),Cw=L({id:5,name:`Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://5.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.etherscan.io`,apiUrl:`https://api-goerli.etherscan.io/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},ensUniversalResolver:{address:`0xfc4AC75C46C914aF5892d6d3eFFcebD7917293F1`,blockCreated:10339206},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6507670}},testnet:!0}),ww=L({id:440017,name:`Graphite Network`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Spectre`,url:`https://main.atgraphite.com`,apiUrl:`https://api.main.atgraphite.com/api`}},testnet:!1}),Tw=L({id:54170,name:`Graphite Network Testnet`,nativeCurrency:{name:`Graphite`,symbol:`@G`,decimals:18},rpcUrls:{default:{http:[`https://anon-entrypoint-test-1.atgraphite.com`],webSocket:[`wss://ws-anon-entrypoint-test-1.atgraphite.com`]}},blockExplorers:{default:{name:`Graphite Testnet Spectre`,url:`https://test.atgraphite.com`,apiUrl:`https://api.test.atgraphite.com/api`}},testnet:!0}),Ew=L({id:1625,name:`Gravity Alpha Mainnet`,nativeCurrency:{name:`G`,symbol:`G`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gravity.xyz`]}},blockExplorers:{default:{name:`Gravity Explorer`,url:`https://explorer.gravity.xyz`,apiUrl:`https://explorer.gravity.xyz/api`}},contracts:{multicall3:{address:`0xf8ac4BEB2F75d2cFFb588c63251347fdD629B92c`,blockCreated:16851}}}),Dw=L({id:43419,name:`Gunz Mainnet`,nativeCurrency:{name:`GUN`,symbol:`GUN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.gunzchain.io/ext/bc/2M47TxWHGnhNtq6pM5zPXdATBtuqubxn5EPFgFmEawCQr9WFML/rpc`]}},blockExplorers:{default:{name:`Gunz Explorer`,url:`https://gunzscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:70502}}}),Ow=L({id:260,name:`Guru Network Mainnet`,nativeCurrency:{name:`GURU Token`,symbol:`GURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-main.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/260`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://scan.gurunetwork.ai`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:271691}},testnet:!1}),kw=L({id:261,name:`Guru Network Testnet`,nativeCurrency:{name:`tGURU Token`,symbol:`tGURU`,decimals:18},rpcUrls:{default:{http:[`https://rpc-test.gurunetwork.ai`,`https://rpc.gurunetwork.ai/archive/261`]}},blockExplorers:{default:{name:`Guruscan`,url:`https://sepolia.gurunetwork.ai`}},testnet:!0}),Aw=L({id:5112,name:`Ham`,nativeCurrency:{decimals:18,name:`Ham`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.ham.fun`],webSocket:[`wss://rpc.ham.fun`]}},blockExplorers:{default:{name:`Ham Chain Explorer`,url:`https://explorer.ham.fun`,apiUrl:`https://explorer.ham.fun/api/v2`}}}),jw=L({id:216,name:`Happychain Testnet`,nativeCurrency:{symbol:`HAPPY`,name:`HAPPY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.happy.tech/http`],webSocket:[`wss://rpc.testnet.happy.tech/ws`]}},blockExplorers:{default:{name:`Happy Chain Testnet Explorer`,url:`https://explorer.testnet.happy.tech`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0}),Mw=L({id:11235,name:`HAQQ Mainnet`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLM`},rpcUrls:{default:{http:[`https://rpc.eth.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.haqq.network`,apiUrl:`https://explorer.haqq.network/api`}}}),Nw=L({id:54211,name:`HAQQ Testedge 2`,nativeCurrency:{decimals:18,name:`Islamic Coin`,symbol:`ISLMT`},rpcUrls:{default:{http:[`https://rpc.eth.testedge2.haqq.network`]}},blockExplorers:{default:{name:`HAQQ Explorer`,url:`https://explorer.testedge2.haqq.network`,apiUrl:`https://explorer.testedge2.haqq.network/api`}}}),Pw=L({id:31337,name:`Hardhat`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),Fw=L({id:16666e5,name:`Harmony One`,nativeCurrency:{name:`Harmony`,symbol:`ONE`,decimals:18},rpcUrls:{default:{http:[`https://1666600000.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Harmony Explorer`,url:`https://explorer.harmony.one`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:24185753}}}),Iw=L({id:177,name:`HashKey Chain`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://mainnet.hsk.xyz`]}},blockExplorers:{default:{name:`HashKey Chain Explorer`,url:`https://hashkey.blockscout.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),Lw=L({id:133,name:`HashKey Chain Testnet`,nativeCurrency:{decimals:18,name:`HashKey EcoPoints`,symbol:`HSK`},rpcUrls:{default:{http:[`https://testnet.hsk.xyz`]}},blockExplorers:{default:{name:`HashKey Chain Testnet explorer`,url:`https://testnet-explorer.hsk.xyz`}},testnet:!0}),Rw=L({id:1523903251,name:`Haust Network Testnet`,nativeCurrency:{decimals:18,name:`HAUST`,symbol:`HAUST`},rpcUrls:{default:{http:[`https://rpc-testnet.haust.app`]}},blockExplorers:{default:{name:`Haust Network Testnet Explorer`,url:`https://explorer-testnet.haust.app`}},testnet:!0}),zw=L({id:295,name:`Hedera Mainnet`,network:`hedera-mainnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/mainnet`}},testnet:!1}),Bw=L({id:297,name:`Hedera Previewnet`,network:`hedera-previewnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://previewnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/previewnet`}},testnet:!0}),Vw=L({id:296,name:`Hedera Testnet`,network:`hedera-testnet`,nativeCurrency:{symbol:`HBAR`,name:`HBAR`,decimals:18},rpcUrls:{default:{http:[`https://testnet.hashio.io/api`]}},blockExplorers:{default:{name:`Hashscan`,url:`https://hashscan.io/testnet`}},testnet:!0}),Hw=L({id:8668,name:`Hela Mainnet`,nativeCurrency:{name:`HLUSD`,symbol:`HLUSD`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.helachain.com`]}},blockExplorers:{default:{name:`Hela explorer`,url:`https://mainnet-blockexplorer.helachain.com`}},testnet:!1}),Uw=L({id:42e3,name:`Helios Testnet`,network:`helios-testnet`,nativeCurrency:{symbol:`HLS`,name:`Helios`,decimals:18},rpcUrls:{default:{http:[`https://testnet1.helioschainlabs.org`]}},blockExplorers:{default:{name:`Helios Testnet Explorer`,url:`https://explorer.helioschainlabs.org/`}},testnet:!0}),Ww=L({id:43111,name:`Hemi`,network:`Hemi`,blockTime:12e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.hemi.xyz`}},testnet:!1}),Gw=L({id:743111,name:`Hemi Sepolia`,network:`Hemi Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.hemi.network/rpc`]}},blockExplorers:{default:{name:`Hemi Sepolia explorer`,url:`https://testnet.explorer.hemi.xyz`}},testnet:!0}),Kw=L({id:68414,name:`Henesys`,nativeCurrency:{name:`NEXPACE`,symbol:`NXPC`,decimals:18},rpcUrls:{default:{http:[`https://henesys-rpc.msu.io`]}},blockExplorers:{default:{name:`Avalanche Explorer`,url:`https://subnets.avax.network/henesys`}}}),qw=L({id:17e3,name:`Holesky`,nativeCurrency:{name:`Holesky Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://ethereum-holesky-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://holesky.etherscan.io`,apiUrl:`https://api-holesky.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:77},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:4295055}},testnet:!0}),Jw=L({id:560048,name:`Hoodi`,nativeCurrency:{name:`Hoodi Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hoodi.ethpandaops.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://hoodi.etherscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2589}},testnet:!0}),Yw=L({id:2651420,name:`Horizen Testnet`,network:`horizen-testnet`,nativeCurrency:{symbol:`Sepolia Ether`,name:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://horizen-testnet.rpc.caldera.xyz/http`]}},blockExplorers:{default:{name:`Horizen Testnet Caldera Explorer`,url:`https://horizen-testnet.explorer.caldera.xyz`}},testnet:!0}),Xw=L({id:269,name:`High Performance Blockchain`,nativeCurrency:{name:`HPB`,symbol:`HPB`,decimals:18},rpcUrls:{default:{http:[`https://hpbnode.com`]}},blockExplorers:{default:{name:`hpbScan`,url:`https://hscan.org`}},testnet:!1}),Zw=L({id:190415,name:`HPP Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.hpp.io`],webSocket:[`wss://mainnet.hpp.io`]}},blockExplorers:{default:{name:`HPP Mainnet Explorer`,url:`https://explorer.hpp.io`}},testnet:!1}),Qw=L({id:181228,name:`HPP Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.hpp.io`],webSocket:[`wss://testnet.hpp.io`]}},blockExplorers:{default:{name:`HPP Sepolia Explorer`,url:`https://sepolia-explorer.hpp.io`}},testnet:!0}),$w=L({id:12323,name:`Huddle01 dRTC Chain`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle01.calderachain.xyz/http`],webSocket:[`wss://huddle01.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle01.calderaexplorer.xyz`,apiUrl:`https://huddle01.calderaexplorer.xyz/api`}},sourceId:42161}),eT=L({id:2524852,name:`Huddle01 dRTC Chain Testnet`,nativeCurrency:{name:`Ethereum`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://huddle-testnet.rpc.caldera.xyz/http`],webSocket:[`wss://huddle-testnet.rpc.caldera.xyz/ws`]}},blockExplorers:{default:{name:`Huddle01 Caldera Explorer`,url:`https://huddle-testnet.explorer.caldera.xyz`,apiUrl:`https://huddle-testnet.explorer.caldera.xyz/api`}},sourceId:421614}),tT=L({id:6985385,name:`Humanity`,nativeCurrency:{name:`H`,symbol:`H`,decimals:18},rpcUrls:{default:{http:[`https://humanity-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Humanity Mainnet Explorer`,url:`https://humanity-mainnet.explorer.alchemy.com`,apiUrl:`https://humanity-mainnet.explorer.alchemy.com/api`}},testnet:!1}),nT=L({id:7080969,name:`Humanity Testnet`,nativeCurrency:{name:`tHP`,symbol:`tHP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.humanity.org`]}},blockExplorers:{default:{name:`Humanity Testnet Explorer`,url:`https://humanity-testnet.explorer.alchemy.com`,apiUrl:`https://humanity-testnet.explorer.alchemy.com/api`}},testnet:!0}),rT=L({id:5234,name:`Humanode`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.mainnet.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.mainnet.stages.humanode.io`]}},blockExplorers:{default:{name:`Subscan`,url:`https://humanode.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4413097}}}),iT=L({id:14853,name:`Humanode Testnet 5`,nativeCurrency:{name:`HMND`,symbol:`HMND`,decimals:18},rpcUrls:{default:{http:[`https://explorer-rpc-http.testnet5.stages.humanode.io`],webSocket:[`wss://explorer-rpc-ws.testnet5.stages.humanode.io`]}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),aT=L({id:2911,name:`HYCHAIN`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://explorer.hychain.com`}},testnet:!1}),oT=L({id:29112,name:`HYCHAIN Testnet`,nativeCurrency:{name:`HYTOPIA`,symbol:`TOPIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hychain.com/http`]}},blockExplorers:{default:{name:`HYCHAIN Explorer`,url:`https://testnet-rpc.hychain.com/http`}},testnet:!0}),sT=L({id:999,name:`HyperEVM`,nativeCurrency:{name:`HYPE`,symbol:`HYPE`,decimals:18},blockExplorers:{default:{name:`HyperEVMScan`,url:`https://hyperevmscan.io`}},rpcUrls:{default:{http:[`https://rpc.hyperliquid.xyz/evm`]}},testnet:!1}),cT=L({id:998,name:`Hyperliquid EVM Testnet`,nativeCurrency:{name:`HYPE`,symbol:`HYPE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hyperliquid-testnet.xyz/evm`]}},testnet:!0}),lT=L({id:73115,name:`ICB Network`,nativeCurrency:{decimals:18,name:`ICB Native Token`,symbol:`ICBX`},rpcUrls:{default:{http:[`https://rpc1-mainnet.icbnetwork.info`]}},blockExplorers:{default:{name:`ICB Explorer`,url:`https://icbscan.io`,apiUrl:`https://icbscan.io/api`}},testnet:!1}),uT=L({id:74,name:`IDChain Mainnet`,nativeCurrency:{decimals:18,name:`EIDI`,symbol:`EIDI`},rpcUrls:{default:{http:[`https://idchain.one/rpc`],webSocket:[`wss://idchain.one/ws`]}},blockExplorers:{default:{name:`IDChain Explorer`,url:`https://explorer.idchain.one`}},testnet:!1}),dT=L({id:13371,name:`Immutable zkEVM`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.immutable.com`]}},blockExplorers:{default:{name:`Immutable Explorer`,url:`https://explorer.immutable.com`,apiUrl:`https://explorer.immutable.com/api`}},contracts:{multicall3:{address:`0x236bdA4589e44e6850f5aC6a74BfCa398a86c6c0`,blockCreated:4335972}}}),fT=L({id:13473,name:`Immutable zkEVM Testnet`,nativeCurrency:{decimals:18,name:`Immutable Coin`,symbol:`IMX`},rpcUrls:{default:{http:[`https://rpc.testnet.immutable.com`]}},blockExplorers:{default:{name:`Immutable Testnet Explorer`,url:`https://explorer.testnet.immutable.com/`}},contracts:{multicall3:{address:`0x2CC787Ed364600B0222361C4188308Fa8E68bA60`,blockCreated:5977391}},testnet:!0}),pT=L({id:2525,name:`inEVM Mainnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://mainnet.rpc.inevm.com/http`]}},blockExplorers:{default:{name:`inEVM Explorer`,url:`https://inevm.calderaexplorer.xyz`,apiUrl:`https://inevm.calderaexplorer.xyz/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:118606}}}),mT=L({id:7233,name:`InitVerse Mainnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-mainnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseScan`,url:`https://www.iniscan.com`,apiUrl:`https://explorer-api.inichain.com/api`}},contracts:{multicall3:{address:`0x83466BE48A067115FFF91f7b892Ed1726d032e47`,blockCreated:2318}}}),hT=L({id:7234,name:`InitVerse Genesis Testnet`,nativeCurrency:{decimals:18,name:`InitVerse`,symbol:`INI`},rpcUrls:{default:{http:[`https://rpc-testnet.inichain.com`]}},blockExplorers:{default:{name:`InitVerseGenesisScan`,url:`https://genesis-testnet.iniscan.com`,apiUrl:`https://explorer-testnet-api.inichain.com/api`}},contracts:{multicall3:{address:`0x0cF32CBDd6c437331EA4f85ed2d881A5379B5a6F`,blockCreated:16361}},testnet:!0}),gT=L({id:1776,name:`Injective`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://sentry.evm-rpc.injective.network`],webSocket:[`wss://sentry.evm-ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://blockscout.injective.network`,apiUrl:`https://blockscout.injective.network/api`}},testnet:!1}),_T=L({id:1439,name:`Injective Testnet`,nativeCurrency:{decimals:18,name:`Injective`,symbol:`INJ`},rpcUrls:{default:{http:[`https://k8s.testnet.json-rpc.injective.network`],webSocket:[`wss://k8s.testnet.ws.injective.network`]}},blockExplorers:{default:{name:`Injective Explorer`,url:`https://testnet.blockscout.injective.network`,apiUrl:`https://testnet.blockscout.injective.network/api`}},testnet:!0}),vT=1,yT=L({...R,id:57073,name:`Ink`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel.inkonchain.com`,`https://rpc-qnd.inkonchain.com`],webSocket:[`wss://rpc-gel.inkonchain.com`,`wss://rpc-qnd.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.inkonchain.com`,apiUrl:`https://explorer.inkonchain.com/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},...R.contracts,disputeGameFactory:{[vT]:{address:`0x10d7b35078d3baabb96dd45a9143b94be65b12cd`}},portal:{[vT]:{address:`0x5d66c1782664115999c47c9fa5cd031f495d3e4f`}},l1StandardBridge:{[vT]:{address:`0x88ff1e5b602916615391f55854588efcbb7663f0`}}},testnet:!1,sourceId:vT}),bT=11155111,xT=L({...R,id:763373,name:`Ink Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-gel-sepolia.inkonchain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer-sepolia.inkonchain.com/`,apiUrl:`https://explorer-sepolia.inkonchain.com/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},disputeGameFactory:{[bT]:{address:`0x860e626c700af381133d9f4af31412a2d1db3d5d`}},portal:{[bT]:{address:`0x5c1d29c6c9c8b0800692acc95d700bcb4966a1d7`}},l1StandardBridge:{[bT]:{address:`0x33f60714bbd74d62b66d79213c348614de51901c`}}},testnet:!0,sourceId:bT}),ST=L({id:8822,name:`IOTA EVM`,network:`iotaevm`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.iota.org`,apiUrl:`https://explorer.evm.iota.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25022}}}),CT=L({id:1075,name:`IOTA EVM Testnet`,network:`iotaevm-testnet`,nativeCurrency:{decimals:18,name:`IOTA`,symbol:`IOTA`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.iotaledger.net`],webSocket:[`wss://ws.json-rpc.evm.testnet.iotaledger.net`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.evm.testnet.iotaledger.net`,apiUrl:`https://explorer.evm.testnet.iotaledger.net/api`}},testnet:!0}),wT=L({id:4689,name:`IoTeX`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.mainnet.iotex.io`],webSocket:[`wss://babel-api.mainnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://iotexscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:22163670}}}),TT=L({id:4690,name:`IoTeX Testnet`,nativeCurrency:{decimals:18,name:`IoTeX`,symbol:`IOTX`},rpcUrls:{default:{http:[`https://babel-api.testnet.iotex.io`],webSocket:[`wss://babel-api.testnet.iotex.io`]}},blockExplorers:{default:{name:`IoTeXScan`,url:`https://testnet.iotexscan.io`}},contracts:{multicall3:{address:`0xb5cecD6894c6f473Ec726A176f1512399A2e355d`,blockCreated:24347592}},testnet:!0}),ET=L({id:8017,name:`iSunCoin Mainnet`,nativeCurrency:{decimals:18,name:`ISC`,symbol:`ISC`},rpcUrls:{default:{http:[`https://mainnet.isuncoin.com`]}},blockExplorers:{default:{name:`iSunCoin Explorer`,url:`https://baifa.io/app/chains/8017`}}}),DT=L({id:680,name:`Jasmy Chain`,network:`jasmyChain`,nativeCurrency:{name:`JasmyCoin`,symbol:`JASMY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.jasmychain.io`],webSocket:[`wss://rpc.jasmychain.io`]}},testnet:!1}),OT=L({id:681,name:`Jasmy Chain Testnet`,network:`jasmyChainTestnet`,nativeCurrency:{name:`JasmyCoin`,symbol:`JASMY`,decimals:18},rpcUrls:{default:{http:[`https://rpc_testnet.jasmychain.io`],webSocket:[`wss://rpc_testnet.jasmychain.io`]}},testnet:!0}),kT=L({id:8899,name:`JB Chain`,network:`jbc`,nativeCurrency:{name:`JBC`,symbol:`JBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-l1.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp-l1.jibchain.net`,apiUrl:`https://exp-l1.jibchain.net/api`}},contracts:{multicall3:{address:`0xc0C8C486D1466C57Efe13C2bf000d4c56F47CBdC`,blockCreated:2299048}},testnet:!1}),AT=L({id:88991,name:`Jibchain Testnet`,nativeCurrency:{name:`tJBC`,symbol:`tJBC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.jibchain.net`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.testnet.jibchain.net`,apiUrl:`https://exp.testnet.jibchain.net/api`}},contracts:{multicall3:{address:`0xa1a858ad9041B4741e620355a3F96B3c78e70ecE`,blockCreated:32848}},testnet:!0}),jT=L({id:81,name:`Japan Open Chain Mainnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Token`,symbol:`JOC`},rpcUrls:{default:{http:[`https://rpc-1.japanopenchain.org:8545`,`https://rpc-2.japanopenchain.org:8545`,`https://rpc-3.japanopenchain.org`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.japanopenchain.org`}},testnet:!1}),MT=L({id:10081,name:`Japan Open Chain Testnet`,nativeCurrency:{decimals:18,name:`Japan Open Chain Testnet Token`,symbol:`JOCT`},rpcUrls:{default:{http:[`https://rpc-1.testnet.japanopenchain.org:8545`,`https://rpc-2.testnet.japanopenchain.org:8545`,`https://rpc-3.testnet.japanopenchain.org`]}},blockExplorers:{default:{name:`Testnet Block Explorer`,url:`https://explorer.testnet.japanopenchain.org`}},testnet:!0}),NT=L({id:5734951,name:`Jovay Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.jovay.io`]}},blockExplorers:{default:{name:`Jovay Explorer`,url:`https://explorer.jovay.io/l2`}},testnet:!1}),PT=L({id:2019775,name:`Jovay Sepolia Testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://api.zan.top/public/jovay-testnet`]}},blockExplorers:{default:{name:`Jovay Testnet Explorer`,url:`https://sepolia-explorer.jovay.io/l2`}},testnet:!0}),FT=L({id:45003,name:`Juneo JUNE-Chain`,nativeCurrency:{decimals:18,name:`JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/2`,apiUrl:`https://juneoscan.io/chain/2/api`}}}),IT=L({id:45013,name:`Juneo BCH1-Chain`,nativeCurrency:{decimals:18,name:`Juneo BCH1-Chain`,symbol:`BCH1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/BCH1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/12`,apiUrl:`https://juneoscan.io/chain/12/api`}}}),LT=L({id:45004,name:`Juneo DAI1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DAI1-Chain`,symbol:`DAI1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DAI1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/5`,apiUrl:`https://juneoscan.io/chain/5/api`}}}),RT=L({id:45010,name:`Juneo DOGE1-Chain`,nativeCurrency:{decimals:18,name:`Juneo DOGE1-Chain`,symbol:`DOGE1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/DOGE1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/10`,apiUrl:`https://juneoscan.io/chain/10/api`}}}),zT=L({id:45011,name:`Juneo EUR1-Chain`,nativeCurrency:{decimals:18,name:`Juneo EUR1-Chain`,symbol:`EUR1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/EUR1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/6`,apiUrl:`https://juneoscan.io/chain/6/api`}}}),BT=L({id:45008,name:`Juneo GLD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo GLD1-Chain`,symbol:`GLD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/GLD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/8`,apiUrl:`https://juneoscan.io/chain/8/api`}}}),VT=L({id:45014,name:`Juneo LINK1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LINK1-Chain`,symbol:`LINK1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LINK1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/13`,apiUrl:`https://juneoscan.io/chain/13/api`}}}),HT=L({id:45009,name:`Juneo LTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo LTC1-Chain`,symbol:`LTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/LTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/11`,apiUrl:`https://juneoscan.io/chain/11/api`}}}),UT=L({id:45007,name:`Juneo mBTC1-Chain`,nativeCurrency:{decimals:18,name:`Juneo mBTC1-Chain`,symbol:`mBTC1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/mBTC1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/9`,apiUrl:`https://juneoscan.io/chain/9/api`}}}),WT=L({id:45012,name:`Juneo SGD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo SGD1-Chain`,symbol:`SGD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/SGD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/7`,apiUrl:`https://juneoscan.io/chain/7/api`}}}),GT=L({id:101003,name:`Socotra JUNE-Chain`,nativeCurrency:{decimals:18,name:`Socotra JUNE-Chain`,symbol:`JUNE`},rpcUrls:{default:{http:[`https://rpc.socotra-testnet.network/ext/bc/JUNE/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://socotra.juneoscan.io/chain/2`,apiUrl:`https://socotra.juneoscan.io/chain/2/api`}},testnet:!0}),KT=L({id:45006,name:`Juneo USD1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USD1-Chain`,symbol:`USD1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USD1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/4`,apiUrl:`https://juneoscan.io/chain/4/api`}}}),qT=L({id:45005,name:`Juneo USDT1-Chain`,nativeCurrency:{decimals:18,name:`Juneo USDT1-Chain`,symbol:`USDT1`},rpcUrls:{default:{http:[`https://rpc.juneo-mainnet.network/ext/bc/USDT1/rpc`]}},blockExplorers:{default:{name:`Juneo Scan`,url:`https://juneoscan.io/chain/3`,apiUrl:`https://juneoscan.io/chain/3/api`}}}),JT=L({id:8217,name:`Kaia`,nativeCurrency:{decimals:18,name:`Kaia`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kaiascan.io`,apiUrl:`https://api-cypress.klaytnscope.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),YT=L({id:1001,name:`Kairos Testnet`,network:`kairos`,nativeCurrency:{decimals:18,name:`Kairos KAIA`,symbol:`KAIA`},rpcUrls:{default:{http:[`https://public-en-kairos.node.kaia.io`]}},blockExplorers:{default:{name:`KaiaScan`,url:`https://kairos.kaiascan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),XT=L({id:1802203764,name:`Kakarot Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),ZT=L({id:920637907288165,name:`Kakarot Starknet Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.kakarot.org`]}},blockExplorers:{default:{name:`Kakarot Scan`,url:`https://sepolia.kakarotscan.org`}},testnet:!0}),QT=L({id:24,name:`KardiaChain Mainnet`,nativeCurrency:{name:`KAI`,symbol:`KAI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kardiachain.io`]}},blockExplorers:{default:{name:`KardiaChain Explorer`,url:`https://explorer.kardiachain.io`}},testnet:!1}),$T=L({id:686,name:`Karura`,network:`karura`,nativeCurrency:{name:`Karura`,symbol:`KAR`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-karura.aca-api.network`],webSocket:[`wss://eth-rpc-karura.aca-api.network`]}},blockExplorers:{default:{name:`Karura Blockscout`,url:`https://blockscout.karura.network`,apiUrl:`https://blockscout.karura.network/api`}},testnet:!1}),eE=L({id:747474,name:`Katana`,network:`katana`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katana.network`]}},blockExplorers:{default:{name:`katana explorer`,url:`https://katanascan.com`}},testnet:!1}),tE=L({id:2222,name:`Kava EVM`,network:`kava-mainnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Explorer`,url:`https://kavascan.com`,apiUrl:`https://kavascan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3661165}},testnet:!1}),nE=L({id:2221,name:`Kava EVM Testnet`,network:`kava-testnet`,nativeCurrency:{name:`Kava`,symbol:`KAVA`,decimals:18},rpcUrls:{default:{http:[`https://evm.testnet.kava.io`]}},blockExplorers:{default:{name:`Kava EVM Testnet Explorer`,url:`https://testnet.kavascan.com/`,apiUrl:`https://testnet.kavascan.com/api`}},contracts:{multicall3:{address:`0xDf1D724A7166261eEB015418fe8c7679BBEa7fd6`,blockCreated:7242179}},testnet:!0}),rE=L({id:321,name:`KCC Mainnet`,network:`KCC Mainnet`,nativeCurrency:{decimals:18,name:`KCS`,symbol:`KCS`},rpcUrls:{default:{http:[`https://kcc-rpc.com`]}},blockExplorers:{default:{name:`KCC Explorer`,url:`https://explorer.kcc.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11760430}},testnet:!1}),iE=L({id:1783,name:`KiiChain`,network:`kii-chain`,nativeCurrency:{name:`Kii`,symbol:`KII`,decimals:18},rpcUrls:{default:{http:[`https://json-rpc.kiivalidator.com`]}},blockExplorers:{default:{name:`KiiExplorer`,url:`https://explorer.kiichain.io`}}}),aE=L({id:1336,name:`Kii Testnet Oro`,network:`kii-testnet-oro`,nativeCurrency:{name:`Kii`,symbol:`KII`,decimals:18},rpcUrls:{default:{http:[`https://json-rpc.uno.sentry.testnet.v3.kiivalidator.com`]}},blockExplorers:{default:{name:`KiiExplorer`,url:`https://testnet.explorer.kiichain.io`}},testnet:!0}),oE=L({id:7887,name:`Kinto Mainnet`,network:`Kinto Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.kinto.xyz/http`]}},blockExplorers:{default:{name:`Kinto Explorer`,url:`https://explorer.kinto.xyz`}},testnet:!1}),sE=L({id:8217,name:`Klaytn`,nativeCurrency:{decimals:18,name:`Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-cypress.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://scope.klaytn.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:96002415}}}),cE=L({id:1001,name:`Klaytn Baobab Testnet`,network:`klaytn-baobab`,nativeCurrency:{decimals:18,name:`Baobab Klaytn`,symbol:`KLAY`},rpcUrls:{default:{http:[`https://public-en-baobab.klaytn.net`]}},blockExplorers:{default:{name:`KlaytnScope`,url:`https://baobab.klaytnscope.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:123390593}},testnet:!0}),lE=L({id:701,name:`Koi Network`,nativeCurrency:{decimals:18,name:`Koi Network Native Token`,symbol:`KRING`},rpcUrls:{default:{http:[`https://koi-rpc.darwinia.network`],webSocket:[`wss://koi-rpc.darwinia.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://koi-scan.darwinia.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:180001}},testnet:!0}),uE=L({id:255,name:`Kroma`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.kroma.network`]}},blockExplorers:{default:{name:`Kroma Explorer`,url:`https://blockscout.kroma.network`,apiUrl:`https://blockscout.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:16054868}},testnet:!1}),dE=L({id:2358,name:`Kroma Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://api.sepolia.kroma.network`]}},blockExplorers:{default:{name:`Kroma Sepolia Explorer`,url:`https://blockscout.sepolia.kroma.network`,apiUrl:`https://blockscout.sepolia.kroma.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:8900914}},testnet:!0}),fE=L({id:1983,name:`Krown`,nativeCurrency:{decimals:18,name:`Krown`,symbol:`KROWN`},rpcUrls:{default:{http:[`https://mainnet.krown.network`]}},blockExplorers:{default:{name:`Krown Explorer`,url:`https://explorer.krown.network`}},testnet:!1}),pE=L({id:12324,name:`L3X Protocol`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.l3x.com`],webSocket:[`wss://rpc-mainnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Mainnet Explorer`,url:`https://explorer.l3x.com`,apiUrl:`https://explorer.l3x.com/api/v2`}},testnet:!1}),mE=L({id:12325,name:`L3X Protocol Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.l3x.com`],webSocket:[`wss://rpc-testnet.l3x.com`]}},blockExplorers:{default:{name:`L3X Testnet Explorer`,url:`https://explorer-testnet.l3x.com`,apiUrl:`https://explorer-testnet.l3x.com/api/v2`}},testnet:!0}),hE=L({id:360890,name:`LAVITA Mainnet`,nativeCurrency:{name:`vTFUEL`,symbol:`vTFUEL`,decimals:18},rpcUrls:{default:{http:[`https://tsub360890-eth-rpc.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`LAVITA Explorer`,url:`https://tsub360890-explorer.thetatoken.org`}},testnet:!1}),gE=L({id:232,name:`Lens`,nativeCurrency:{name:`GHO`,symbol:`GHO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lens.xyz`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://explorer.lens.xyz`,apiUrl:`https://explorer.lens.xyz/api`}}}),_E=L({id:37111,name:`Lens Testnet`,nativeCurrency:{name:`GRASS`,symbol:`GRASS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.lens.dev`],webSocket:[`wss://rpc.testnet.lens.dev/ws`]}},blockExplorers:{default:{name:`Lens Block Explorer`,url:`https://block-explorer.testnet.lens.dev`,apiUrl:`https://block-explorer-api.staging.lens.dev/api`}},testnet:!0}),vE=L({id:21363,name:`Lestnet`,nativeCurrency:{name:`Lestnet Ether`,symbol:`LETH`,decimals:18},rpcUrls:{default:{http:[`https://service.lestnet.org`]}},blockExplorers:{default:{name:`Lestnet Explorer`,url:`https://explore.lestnet.org`}},testnet:!0}),yE=L({id:1891,name:`LightLink Pegasus Testnet`,network:`lightlink-pegasus`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.pegasus.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Pegasus Explorer`,url:`https://pegasus.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:127188532}},testnet:!0}),bE=L({id:1890,name:`LightLink Phoenix Mainnet`,network:`lightlink-phoenix`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://replicator.phoenix.lightlink.io/rpc/v1`]}},blockExplorers:{default:{name:`LightLink Phoenix Explorer`,url:`https://phoenix.lightlink.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:125499184}},testnet:!1});oa(),k(),Hp(),zu(),Ku(),td();async function xE(e,t){let{account:n=e.account}=t;if(!n)throw new Fm;let r=aa(n);try{let{accessList:n,blockNumber:i,blockTag:a,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p,...m}=t,h=(typeof i==`bigint`?O(i):void 0)||a;ed(t);let g=e.chain?.formatters?.transactionRequest?.format,_=(g||Hu)({...Ru(m,{format:g}),account:r,accessList:n,data:o,gas:s,gasPrice:c,maxFeePerGas:l,maxPriorityFeePerGas:u,nonce:d,to:f,value:p},`estimateGas`),{baseFeePerGas:v,gasLimit:y,priorityFeePerGas:b}=await e.request({method:`linea_estimateGas`,params:h?[_,h]:[_]});return{baseFeePerGas:BigInt(v),gasLimit:BigInt(y),priorityFeePerGas:BigInt(b)}}catch(n){throw Vp(n,{...t,account:r,chain:e.chain})}}var SE={fees:{estimateFeesPerGas:CE,async maxPriorityFeePerGas({block:e,client:t,request:n}){let r=await CE({block:e,client:t,multiply:e=>e,request:n,type:`eip1559`});return r?.maxPriorityFeePerGas?r.maxPriorityFeePerGas:null}}};async function CE({client:e,multiply:t,request:n,type:r}){try{let i=await xE(e,{...n,account:n?.account}),{priorityFeePerGas:a}=i,o=t(BigInt(i.baseFeePerGas))+a;return r===`legacy`?{gasPrice:o}:{maxFeePerGas:o,maxPriorityFeePerGas:a}}catch{return null}}var wE=L({...SE,id:59144,name:`Linea Mainnet`,blockTime:2e3,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.linea.build`],webSocket:[`wss://rpc.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://lineascan.build`,apiUrl:`https://api.lineascan.build/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:42},ensRegistry:{address:`0x50130b669B28C339991d8676FA73CF122a121267`,blockCreated:6682888},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:22222151}},ensTlds:[`.linea.eth`],testnet:!1}),TE=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://api-goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0}),EE=L({...SE,id:59141,name:`Linea Sepolia Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia.linea.build`],webSocket:[`wss://rpc.sepolia.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.lineascan.build`,apiUrl:`https://api-sepolia.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:227427},ensRegistry:{address:`0x5B2636F0f2137B4aE722C01dd5122D7d3e9541f7`,blockCreated:2395094},ensUniversalResolver:{address:`0x4D41762915F83c76EcaF6776d9b08076aA32b492`,blockCreated:17168484}},ensTlds:[`.linea.eth`],testnet:!0}),DE=L({id:59140,name:`Linea Goerli Testnet`,nativeCurrency:{name:`Linea Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.goerli.linea.build`],webSocket:[`wss://rpc.goerli.linea.build`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli.lineascan.build`,apiUrl:`https://goerli.lineascan.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:498623}},testnet:!0}),OE=1,kE=L({...R,id:1135,name:`Lisk`,network:`lisk`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout.lisk.com`,apiUrl:`https://blockscout.lisk.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[OE]:{address:`0x0CF7D3706a27CCE2017aEB11E8a9c8b5388c282C`}},multicall3:{address:`0xA9d71E1dd7ca26F26e656E66d6AA81ed7f745bf0`},l2OutputOracle:{[OE]:{address:`0x113cB99283AF242Da0A0C54347667edF531Aa7d6`}},portal:{[OE]:{address:`0x26dB93F8b8b4f7016240af62F7730979d353f9A7`}},l1StandardBridge:{[OE]:{address:`0x2658723Bf70c7667De6B25F99fcce13A16D25d08`}}},sourceId:OE}),AE=11155111,jE=L({...R,id:4202,network:`lisk-sepolia`,name:`Lisk Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sepolia-api.lisk.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia-blockscout.lisk.com`,apiUrl:`https://sepolia-blockscout.lisk.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[AE]:{address:`0xA0E35F56C318DE1bD5D9ca6A94Fe7e37C5663348`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[AE]:{address:`0xe3d90F21490686Ec7eF37BE788E02dfC12787264`}},l1StandardBridge:{[AE]:{address:`0x1Fb30e446eA791cd1f011675E5F3f5311b70faF5`}}},testnet:!0,sourceId:AE}),ME=L({id:9496,name:`Load Alphanet`,nativeCurrency:{name:`Testnet LOAD`,symbol:`tLOAD`,decimals:18},rpcUrls:{default:{http:[`https://alphanet.load.network`]}},blockExplorers:{default:{name:`Load Alphanet Explorer`,url:`https://explorer.load.network`}},testnet:!0}),NE=L({id:1337,name:`Localhost`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`http://127.0.0.1:8545`]}}}),PE=L({id:15551,name:`LoopNetwork Mainnet`,nativeCurrency:{name:`LOOP`,symbol:`LOOP`,decimals:18},rpcUrls:{default:{http:[`https://api.mainnetloop.com`]}},blockExplorers:{default:{name:`LoopNetwork Blockchain Explorer`,url:`https://explorer.mainnetloop.com/`}},testnet:!1}),FE=L({id:42,network:`lukso`,name:`LUKSO`,nativeCurrency:{name:`LUKSO`,symbol:`LYX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.lukso.network`],webSocket:[`wss://ws-rpc.mainnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Mainnet Explorer`,url:`https://explorer.execution.mainnet.lukso.network`,apiUrl:`https://api.explorer.execution.mainnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:468183}}}),IE=L({id:4201,name:`LUKSO Testnet`,nativeCurrency:{decimals:18,name:`LUKSO Testnet`,symbol:`LYXt`},rpcUrls:{default:{http:[`https://rpc.testnet.lukso.network`],webSocket:[`wss://ws-rpc.testnet.lukso.network`]}},blockExplorers:{default:{name:`LUKSO Testnet Explorer`,url:`https://explorer.execution.testnet.lukso.network`,apiUrl:`https://api.explorer.execution.testnet.lukso.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:605348}},testnet:!0}),LE=L({id:994873017,name:`Lumia Mainnet`,network:`LumiaMainnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Explorer`,url:`https://explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3975939}},testnet:!1}),RE=L({id:1952959480,name:`Lumia Testnet`,network:`LumiaTestnet`,nativeCurrency:{name:`Lumia`,symbol:`LUMIA`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.lumia.org`]}},blockExplorers:{default:{name:`Lumia Testnet Explorer`,url:`https://testnet-explorer.lumia.org/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2235063}},testnet:!0}),zE=L({id:96370,name:`Lumoz`,nativeCurrency:{decimals:18,name:`Lumoz Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://rpc.lumoz.org`]}},blockExplorers:{default:{name:`Lumoz Scan`,url:`https://scan.lumoz.info`}},testnet:!1}),BE=L({id:105363,name:`Lumoz Testnet`,nativeCurrency:{decimals:18,name:`Lumoz Testnet Token`,symbol:`MOZ`},rpcUrls:{default:{http:[`https://testnet-rpc.lumoz.org`]}},testnet:!0}),VE=L({id:1122,name:`LuxePorts`,network:`luxeports`,nativeCurrency:{name:`LuxePorts`,symbol:`LXP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.luxeports.com`,`https://erpc.luxeports.com`],webSocket:[`wss://rpc.luxeports.com/ws`,`wss://erpc.luxeports.com/ws`]}},blockExplorers:{default:{name:`LXPScan`,url:`https://lxpscan.com`}},testnet:!1}),HE=L({id:721,name:`Lycan`,nativeCurrency:{decimals:18,name:`Lycan`,symbol:`LYC`},rpcUrls:{default:{http:[`https://rpc.lycanchain.com`,`https://us-east.lycanchain.com`,`https://us-west.lycanchain.com`,`https://eu-north.lycanchain.com`,`https://eu-west.lycanchain.com`,`https://asia-southeast.lycanchain.com`],webSocket:[`wss://rpc.lycanchain.com`,`wss://us-east.lycanchain.com`,`wss://us-west.lycanchain.com`,`wss://eu-north.lycanchain.com`,`wss://eu-west.lycanchain.com`,`wss://asia-southeast.lycanchain.com`]}},blockExplorers:{default:{name:`Lycan Explorer`,url:`https://explorer.lycanchain.com`}}}),UE=L({id:957,name:`Lyra Chain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.lyra.finance`]}},blockExplorers:{default:{name:`Lyra Explorer`,url:`https://explorer.lyra.finance`,apiUrl:`https://explorer.lyra.finance/api/v2`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1935198}}}),WE=L({id:1,name:`Ethereum`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:12e3,rpcUrls:{default:{http:[`https://eth.merkle.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://etherscan.io`,apiUrl:`https://api.etherscan.io/api`}},contracts:{ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:23085558},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),GE=L({id:595,name:`Mandala TC9`,network:`mandala`,nativeCurrency:{name:`Mandala`,symbol:`mACA`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-tc9.aca-staging.network`],webSocket:[`wss://eth-rpc-tc9.aca-staging.network`]}},blockExplorers:{default:{name:`Mandala Blockscout`,url:`https://blockscout.mandala.aca-staging.network`,apiUrl:`https://blockscout.mandala.aca-staging.network/api`}},testnet:!0}),KE=L({id:169,name:`Manta Pacific Mainnet`,network:`manta`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.manta.network/http`]}},blockExplorers:{default:{name:`Manta Explorer`,url:`https://pacific-explorer.manta.network`,apiUrl:`https://pacific-explorer.manta.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:332890}}}),qE=L({id:3441006,name:`Manta Pacific Sepolia Testnet`,network:`manta-sepolia`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://pacific-rpc.sepolia-testnet.manta.network/http`]}},blockExplorers:{default:{name:`Manta Sepolia Testnet Explorer`,url:`https://pacific-explorer.sepolia-testnet.manta.network`,apiUrl:`https://pacific-explorer.sepolia-testnet.manta.network/api`}},contracts:{multicall3:{address:`0xca54918f7B525C8df894668846506767412b53E3`,blockCreated:479584}},testnet:!0}),JE=L({id:3441005,name:`Manta Pacific Testnet`,network:`manta-testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://manta-testnet.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Manta Testnet Explorer`,url:`https://pacific-explorer.testnet.manta.network`,apiUrl:`https://pacific-explorer.testnet.manta.network/api`}},contracts:{multicall3:{address:`0x211B1643b95Fe76f11eD8880EE810ABD9A4cf56C`,blockCreated:419915}},testnet:!0}),YE=L({id:5e3,name:`Mantle`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Explorer`,url:`https://mantlescan.xyz/`,apiUrl:`https://api.mantlescan.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:304717}}}),XE=L({id:5003,name:`Mantle Sepolia Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.sepolia.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.sepolia.mantle.xyz/`,apiUrl:`https://explorer.sepolia.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4584012}},testnet:!0}),ZE=L({id:5001,name:`Mantle Testnet`,nativeCurrency:{decimals:18,name:`MNT`,symbol:`MNT`},rpcUrls:{default:{http:[`https://rpc.testnet.mantle.xyz`]}},blockExplorers:{default:{name:`Mantle Testnet Explorer`,url:`https://explorer.testnet.mantle.xyz`,apiUrl:`https://explorer.testnet.mantle.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:561333}},testnet:!0}),QE=L({id:5887,name:`MANTRA DuKong EVM Testnet`,nativeCurrency:{decimals:18,name:`MANTRA`,symbol:`MANTRA`},rpcUrls:{default:{http:[`https://evm.dukong.mantrachain.io`]}},blockExplorers:{default:{name:`MANTRAScan`,url:`https://mantrascan.io/dukong`}},testnet:!0}),$E=L({id:5888,name:`MANTRA EVM`,nativeCurrency:{decimals:18,name:`MANTRA`,symbol:`MANTRA`},rpcUrls:{default:{http:[`https://evm.mantrachain.io`],webSocket:[`https://evm.mantrachain.io/ws`]}},blockExplorers:{default:{name:`MANTRA Blockscout Explorer`,url:`https://blockscout.mantrascan.io`}}}),eD=L({id:22776,name:`MAP Protocol`,nativeCurrency:{decimals:18,name:`MAPO`,symbol:`MAPO`},rpcUrls:{default:{http:[`https://rpc.maplabs.io`]}},blockExplorers:{default:{name:`MAPO Scan`,url:`https://maposcan.io`}},testnet:!1}),tD=L({id:698,name:`Matchain`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://matchscan.io`}}}),nD=L({id:699,name:`Matchain Testnet`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.matchain.io`]}},blockExplorers:{default:{name:`Matchain Scan`,url:`https://testnet.matchscan.io`}},testnet:!0}),rD=L({id:29548,name:`MCH Verse`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.oasys.mycryptoheroes.net`]}},blockExplorers:{default:{name:`MCH Verse Explorer`,url:`https://explorer.oasys.mycryptoheroes.net`,apiUrl:`https://explorer.oasys.mycryptoheroes.net/api`}},testnet:!1}),iD=L({id:4326,blockTime:1e3,name:`MegaETH`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.megaeth.com/rpc`],webSocket:[`wss://mainnet.megaeth.com/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`},blockscout:{name:`Etherscan`,url:`https://mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),aD=L({id:6343,blockTime:1e3,name:`MegaETH Testnet`,nativeCurrency:{name:`MegaETH Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://carrot.megaeth.com/rpc`],webSocket:[`wss://carrot.megaeth.com/ws`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://testnet-mega.etherscan.io`,apiUrl:`https://api.etherscan.io/v2/api`},blockscout:{name:`Blockscout`,url:`https://megaeth-testnet-v2.blockscout.com`,apiUrl:`https://megaeth-testnet-v2.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),oD=L({id:7078815900,name:`Mekong Pectra Devnet`,nativeCurrency:{name:`eth`,symbol:`eth`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mekong.ethpandaops.io`]}},blockExplorers:{default:{name:`Block Explorer`,url:`https://explorer.mekong.ethpandaops.io`}},testnet:!0}),sD=L({id:333000333,name:`Meld`,nativeCurrency:{decimals:18,name:`Meld`,symbol:`MELD`},rpcUrls:{default:{http:[`https://rpc-1.meld.com`]}},blockExplorers:{default:{name:`MELDscan`,url:`https://meldscan.io`}},contracts:{multicall3:{address:`0x769ee5a8e82c15c1b6e358f62ac8eb6e3abe8dc5`,blockCreated:360069}}}),cD=L({id:4352,name:`MemeCore`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.memecore.net`],webSocket:[`wss://ws.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Explorer`,url:`https://memecorescan.io`,apiUrl:`https://api.memecorescan.io/api`},okx:{name:`MemeCore Explorer`,url:`https://web3.okx.com/explorer/memecore`},memecore:{name:`MemeCore Explorer`,url:`https://blockscout.memecore.com`,apiUrl:`https://blockscout.memecore.com/api`}}}),lD=L({id:43521,name:`Formicarium`,nativeCurrency:{decimals:18,name:`M`,symbol:`M`},rpcUrls:{default:{http:[`https://rpc.formicarium.memecore.net`],webSocket:[`wss://ws.formicarium.memecore.net`]}},blockExplorers:{default:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.memecorescan.io`},okx:{name:`MemeCore Testnet Explorer`,url:`https://web3.okx.com/explorer/formicarium-testnet`},memecore:{name:`MemeCore Testnet Explorer`,url:`https://formicarium.blockscout.memecore.com`,apiUrl:`https://formicarium.blockscout.memecore.com/api`}},testnet:!0}),uD=L({id:4200,name:`Merlin`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://scan.merlinchain.io`,apiUrl:`https://scan.merlinchain.io/api`}}}),dD=L({id:4203,name:`Merlin Erigon Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://testnet-erigon-rpc.merlinchain.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet-erigon-scan.merlinchain.io`,apiUrl:`https://testnet-erigon-scan.merlinchain.io/api`}},testnet:!0}),fD=L({id:571,name:`MetaChain Mainnet`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.metatime.com`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://explorer.metatime.com`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}}}),pD=L({id:1453,name:`MetaChain Istanbul`,nativeCurrency:{name:`Metatime Coin`,symbol:`MTC`,decimals:18},rpcUrls:{default:{http:[`https://istanbul-rpc.metachain.dev`]}},blockExplorers:{default:{name:`MetaExplorer`,url:`https://istanbul-explorer.metachain.dev`}},contracts:{multicall3:{address:`0x0000000000000000000000000000000000003001`,blockCreated:0}},testnet:!0}),mD=L({id:11,name:`Metadium Network`,nativeCurrency:{decimals:18,name:`META`,symbol:`META`},rpcUrls:{default:{http:[`https://api.metadium.com/prod`]}},blockExplorers:{default:{name:`Metadium Explorer`,url:`https://explorer.metadium.com`}},testnet:!1}),hD=1,gD=L({...R,id:1750,name:`Metal L2`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.metall2.com`],webSocket:[`wss://rpc.metall2.com`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.metall2.com`,apiUrl:`https://explorer.metall2.com/api`}},contracts:{...R.contracts,l2OutputOracle:{[hD]:{address:`0x3B1F7aDa0Fcc26B13515af752Dd07fB1CAc11426`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0},portal:{[hD]:{address:`0x3F37aBdE2C6b5B2ed6F8045787Df1ED1E3753956`}},l1StandardBridge:{[hD]:{address:`0x6d0f65D59b55B0FEC5d2d15365154DcADC140BF3`}}},sourceId:hD}),_D=L({id:82,name:`Meter`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpc.meter.io`]}},blockExplorers:{default:{name:`MeterScan`,url:`https://scan.meter.io`}}}),vD=L({id:83,name:`Meter Testnet`,nativeCurrency:{decimals:18,name:`MTR`,symbol:`MTR`},rpcUrls:{default:{http:[`https://rpctest.meter.io`]}},blockExplorers:{default:{name:`MeterTestnetScan`,url:`https://scan-warringstakes.meter.io`}}}),yD=L({id:1088,name:`Metis`,nativeCurrency:{decimals:18,name:`Metis`,symbol:`METIS`},rpcUrls:{default:{http:[`https://metis.rpc.hypersync.xyz`,`https://metis-pokt.nodies.app`,`https://api.blockeden.xyz/metis/67nCBdZQSH9z3YqDDjdm`,`https://metis-andromeda.rpc.thirdweb.com`,`https://metis-andromeda.gateway.tenderly.co`,`https://metis.api.onfinality.io/public`,`https://andromeda.metis.io/?owner=1088`,`https://metis-mainnet.public.blastapi.io`],webSocket:[`wss://metis-rpc.publicnode.com`,`wss://metis.drpc.org`]}},blockExplorers:{default:{name:`Metis Explorer`,url:`https://explorer.metis.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2338552}}}),bD=L({id:599,name:`Metis Goerli`,nativeCurrency:{decimals:18,name:`Metis Goerli`,symbol:`METIS`},rpcUrls:{default:{http:[`https://goerli.gateway.metisdevops.link`]}},blockExplorers:{default:{name:`Metis Goerli Explorer`,url:`https://goerli.explorer.metisdevops.link`,apiUrl:`https://goerli.explorer.metisdevops.link/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1006207}}}),xD=L({id:59902,name:`Metis Sepolia`,nativeCurrency:{decimals:18,name:`Test Metis`,symbol:`tMETIS`},rpcUrls:{default:{http:[`https://sepolia.metisdevops.link`,`https://metis-sepolia-rpc.publicnode.com`,`https://metis-sepolia.gateway.tenderly.co`],webSocket:[`wss://metis-sepolia-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Metis Sepolia Explorer`,url:`https://sepolia-explorer.metisdevops.link`,apiUrl:`https://sepolia-explorer.metisdevops.link/api-docs`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:224185}}}),SD=L({id:7518,name:`MEVerse Chain Mainnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversemainnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://www.meversescan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:86881340}}}),CD=L({id:4759,name:`MEVerse Chain Testnet`,nativeCurrency:{decimals:18,name:`MEVerse`,symbol:`MEV`},rpcUrls:{default:{http:[`https://rpc.meversetestnet.io`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.meversescan.io/`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:64371115}},testnet:!0}),wD=L({id:185,name:`Mint Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain explorer`,url:`https://explorer.mintchain.io`}},testnet:!1}),TD=L({id:1686,name:`Mint Sepolia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.mintchain.io`]}},blockExplorers:{default:{name:`Mintchain Testnet explorer`,url:`https://testnet-explorer.mintchain.io`}},testnet:!0}),ED=L({id:124832,name:`Mitosis Testnet`,nativeCurrency:{name:`MITO`,symbol:`MITO`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.mitosis.org`]}},blockExplorers:{default:{name:`Mitosis testnet explorer`,url:`https://testnet.mitosiscan.xyz`}},testnet:!0}),DD=1,OD=L({...R,id:34443,name:`Mode Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.mode.network`]}},blockExplorers:{default:{name:`Modescan`,url:`https://modescan.io`}},contracts:{...R.contracts,disputeGameFactory:{[DD]:{address:`0x6f13EFadABD9269D6cEAd22b448d434A1f1B433E`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:2465882},l2OutputOracle:{[DD]:{address:`0x4317ba146D4933D889518a3e5E11Fe7a53199b04`}},portal:{[DD]:{address:`0x8B34b14c7c7123459Cf3076b8Cb929BE097d0C07`}},l1StandardBridge:{[DD]:{address:`0x735aDBbE72226BD52e818E7181953f42E3b0FF21`}}},sourceId:DD}),kD=11155111,AD=L({...R,id:919,name:`Mode Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.mode.network`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepolia.explorer.mode.network`,apiUrl:`https://sepolia.explorer.mode.network/api`}},contracts:{...R.contracts,l2OutputOracle:{[kD]:{address:`0x2634BD65ba27AB63811c74A63118ACb312701Bfa`,blockCreated:3778393}},portal:{[kD]:{address:`0x320e1580effF37E008F1C92700d1eBa47c1B23fD`,blockCreated:3778395}},l1StandardBridge:{[kD]:{address:`0xbC5C679879B2965296756CD959C3C739769995E2`,blockCreated:3778392}},multicall3:{address:`0xBAba8373113Fb7a68f195deF18732e01aF8eDfCF`,blockCreated:3019007}},testnet:!0,sourceId:kD}),jD=L({id:143,name:`Monad`,blockTime:400,nativeCurrency:{name:`Monad`,symbol:`MON`,decimals:18},rpcUrls:{default:{http:[`https://rpc.monad.xyz`,`https://rpc1.monad.xyz`],webSocket:[`wss://rpc.monad.xyz`,`wss://rpc1.monad.xyz`]}},blockExplorers:{default:{name:`MonadVision`,url:`https://monadvision.com`},monadscan:{name:`Monadscan`,url:`https://monadscan.com`,apiUrl:`https://api.monadscan.com/api`}},testnet:!1,contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:9248132}}}),MD=L({id:10143,name:`Monad Testnet`,blockTime:400,nativeCurrency:{name:`Testnet MON Token`,symbol:`MON`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.monad.xyz`]}},blockExplorers:{default:{name:`Monad Testnet explorer`,url:`https://testnet.monadexplorer.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:251449}},testnet:!0}),ND=L({id:1287,name:`Moonbase Alpha`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`https://rpc.api.moonbase.moonbeam.network`],webSocket:[`wss://wss.api.moonbase.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonbase.moonscan.io`,apiUrl:`https://moonbase.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1850686}},testnet:!0}),PD=L({id:1284,name:`Moonbeam`,nativeCurrency:{decimals:18,name:`GLMR`,symbol:`GLMR`},rpcUrls:{default:{http:[`https://rpc.api.moonbeam.network`],webSocket:[`wss://wss.api.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonscan.io`,apiUrl:`https://api-moonbeam.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:609002}},testnet:!1}),FD=L({id:1281,name:`Moonbeam Development Node`,nativeCurrency:{decimals:18,name:`DEV`,symbol:`DEV`},rpcUrls:{default:{http:[`http://127.0.0.1:9944`],webSocket:[`wss://127.0.0.1:9944`]}}}),ID=L({id:1285,name:`Moonriver`,nativeCurrency:{decimals:18,name:`MOVR`,symbol:`MOVR`},rpcUrls:{default:{http:[`https://rpc.api.moonriver.moonbeam.network`],webSocket:[`wss://wss.api.moonriver.moonbeam.network`]}},blockExplorers:{default:{name:`Moonscan`,url:`https://moonriver.moonscan.io`,apiUrl:`https://api-moonriver.moonscan.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1597904}},testnet:!1}),LD=L({id:2818,name:`Morph`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.morphl2.io`],webSocket:[`wss://rpc.morphl2.io:8443`]}},blockExplorers:{default:{name:`Morph Explorer`,url:`https://explorer.morphl2.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3654913}},testnet:!1}),RD=L({id:2810,name:`Morph Holesky`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-quicknode-holesky.morphl2.io`],webSocket:[`wss://rpc-quicknode-holesky.morphl2.io`]}},blockExplorers:{default:{name:`Morph Holesky Explorer`,url:`https://explorer-holesky.morphl2.io`,apiUrl:`https://explorer-api-holesky.morphl2.io/api?`}},testnet:!0}),zD=L({id:2710,name:`Morph Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.morphl2.io`]}},blockExplorers:{default:{name:`Morph Testnet Explorer`,url:`https://explorer-testnet.morphl2.io`,apiUrl:`https://explorer-api-testnet.morphl2.io/api`}},testnet:!0}),BD=L({id:5551,name:`Nahmii 2 Mainnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://l2.nahmii.io`]}},blockExplorers:{default:{name:`Nahmii 2 Explorer`,url:`https://explorer.n2.nahmii.io`}},testnet:!1}),VD=L({id:22222,name:`Nautilus Mainnet`,nativeCurrency:{name:`ZBC`,symbol:`ZBC`,decimals:9},rpcUrls:{default:{http:[`https://api.nautilus.nautchain.xyz`]}},blockExplorers:{default:{name:`NautScan`,url:`https://nautscan.com`}}}),HD=L({id:397,name:`NEAR Protocol`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.mainnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer.near.org`}},testnet:!1}),UD=L({id:398,name:`NEAR Protocol Testnet`,nativeCurrency:{decimals:18,name:`NEAR`,symbol:`NEAR`},rpcUrls:{default:{http:[`https://eth-rpc.testnet.near.org`]}},blockExplorers:{default:{name:`NEAR Explorer`,url:`https://eth-explorer-testnet.near.org`}},testnet:!0}),WD=L({id:245022926,name:`Neon EVM DevNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://devnet.neonevm.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://devnet.neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:205206112}},testnet:!0}),GD=L({id:245022934,network:`neonMainnet`,name:`Neon EVM MainNet`,nativeCurrency:{name:`NEON`,symbol:`NEON`,decimals:18},rpcUrls:{default:{http:[`https://neon-proxy-mainnet.solana.p2p.org`]}},blockExplorers:{default:{name:`Neonscan`,url:`https://neonscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:206545524}},testnet:!1}),KD=L({id:47763,name:`Neo X Mainnet`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-1.rpc.banelabs.org`,`https://mainnet-2.rpc.banelabs.org`]}},blockExplorers:{default:{name:`Neo X - Explorer`,url:`https://xexplorer.neo.org`}},testnet:!1}),qD=L({id:12227332,name:`Neo X Testnet T4`,nativeCurrency:{name:`Gas`,symbol:`GAS`,decimals:18},rpcUrls:{default:{http:[`https://testnet.rpc.banelabs.org/`]}},blockExplorers:{default:{name:`neox-scan`,url:`https://xt4scan.ngd.network`}},testnet:!0}),JD=L({id:1012,name:`Newton`,nativeCurrency:{name:`Newton`,symbol:`NEW`,decimals:18},rpcUrls:{default:{http:[`https://global.rpc.mainnet.newtonproject.org`]}},blockExplorers:{default:{name:`NewFi explorer`,url:`https://explorer.newtonproject.org/`}},testnet:!1}),YD=L({id:4242,name:`Nexi`,nativeCurrency:{name:`Nexi`,symbol:`NEXI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.chain.nexi.technology`]}},blockExplorers:{default:{name:`NexiScan`,url:`https://www.nexiscan.com`,apiUrl:`https://www.nexiscan.com/api`}},contracts:{multicall3:{address:`0x0277A46Cc69A57eE3A6C8c158bA874832F718B8E`,blockCreated:25770160}}}),XD=L({id:240,name:`Nexilix Smart Chain`,nativeCurrency:{decimals:18,name:`Nexilix`,symbol:`NEXILIX`},rpcUrls:{default:{http:[`https://rpcurl.pos.nexilix.com`]}},blockExplorers:{default:{name:`NexilixScan`,url:`https://scan.nexilix.com`}},contracts:{multicall3:{address:`0x58381c8e2BF9d0C2C4259cA14BdA9Afe02831244`,blockCreated:74448}}}),ZD=L({id:6900,name:`Nibiru`,nativeCurrency:{decimals:18,name:`NIBI`,symbol:`NIBI`},rpcUrls:{default:{http:[`https://evm-rpc.nibiru.fi`]}},blockExplorers:{default:{name:`NibiScan`,url:`https://nibiscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:19587573}}}),QD=L({id:200024,name:`Nitrograph Testnet`,testnet:!0,rpcUrls:{default:{http:[`https://rpc-testnet.nitrograph.foundation`]}},nativeCurrency:{name:`Nitro`,symbol:`NOS`,decimals:18},blockExplorers:{default:{url:`https://explorer-testnet.nitrograph.foundation`,name:`Nitrograph Explorer`}}}),$D=L({id:166,name:`Nomina`,nativeCurrency:{name:`Nomina`,symbol:`NOM`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.nomina.io`],webSocket:[`wss://mainnet.nomina.io`]}},blockExplorers:{default:{name:`Nomina Explorer`,url:`https://nomscan.io`}},testnet:!1}),eO=L({id:4090,network:`oasis-testnet`,name:`Oasis Testnet`,nativeCurrency:{name:`Fasttoken`,symbol:`FTN`,decimals:18},rpcUrls:{default:{http:[`https://rpc1.oasis.bahamutchain.com`]}},blockExplorers:{default:{name:`Ftnscan`,url:`https://oasis.ftnscan.com`,apiUrl:`https://oasis.ftnscan.com/api`}},testnet:!0}),tO=L({id:248,name:`Oasys`,nativeCurrency:{name:`Oasys`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.oasys.games`]}},blockExplorers:{default:{name:`OasysScan`,url:`https://scan.oasys.games`,apiUrl:`https://scan.oasys.games/api`}}}),nO=L({id:911867,name:`Odyssey Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://odyssey.ithaca.xyz`]}},blockExplorers:{default:{name:`Odyssey Explorer`,url:`https://odyssey-explorer.ithaca.xyz`,apiUrl:`https://odyssey-explorer.ithaca.xyz/api`}},testnet:!0}),rO=L({id:66,name:`OKC`,nativeCurrency:{decimals:18,name:`OKT`,symbol:`OKT`},rpcUrls:{default:{http:[`https://exchainrpc.okex.org`]}},blockExplorers:{default:{name:`oklink`,url:`https://www.oklink.com/okc`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:10364792}}}),iO=L({id:311,name:`Omax Mainnet`,nativeCurrency:{decimals:18,name:`OMAX`,symbol:`OMAX`},rpcUrls:{default:{http:[`https://mainapi.omaxray.com`]}},blockExplorers:{default:{name:`Omax Explorer`,url:`https://omaxscan.com`}},testnet:!1}),aO=L({id:166,name:`Omni`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.omni.network`],webSocket:[`wss://mainnet.omni.network`]}},blockExplorers:{default:{name:`OmniScan`,url:`https://omniscan.network`}},testnet:!1}),oO=L({id:164,name:`Omni Omega`,nativeCurrency:{name:`Omni`,symbol:`OMNI`,decimals:18},rpcUrls:{default:{http:[`https://omega.omni.network`],webSocket:[`wss://omega.omni.network`]}},blockExplorers:{default:{name:`Omega OmniScan`,url:`https://omega.omniscan.network/`}},testnet:!0}),sO=L({id:309075,name:`One World Chain Mainnet`,nativeCurrency:{decimals:18,name:`OWCT`,symbol:`OWCT`},rpcUrls:{default:{http:[`https://mainnet-rpc.oneworldchain.org`]}},blockExplorers:{default:{name:`One World Explorer`,url:`https://mainnet.oneworldchain.org`}},testnet:!1}),cO=L({id:9700,name:`OORT MainnetDev`,nativeCurrency:{decimals:18,name:`OORT`,symbol:`OORT`},rpcUrls:{default:{http:[`https://dev-rpc.oortech.com`]}},blockExplorers:{default:{name:`OORT MainnetDev Explorer`,url:`https://dev-scan.oortech.com`}}}),lO=56,uO=L({id:204,name:`opBNB`,nativeCurrency:{name:`BNB`,symbol:`BNB`,decimals:18},rpcUrls:{default:{http:[`https://opbnb-mainnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opBNB (BSCScan)`,url:`https://opbnb.bscscan.com`,apiUrl:`https://api-opbnb.bscscan.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:512881},l2OutputOracle:{[lO]:{address:`0x153CAB79f4767E2ff862C94aa49573294B13D169`}},portal:{[lO]:{address:`0x1876EA7702C0ad0C6A2ae6036DE7733edfBca519`}},l1StandardBridge:{[lO]:{address:`0xF05F0e4362859c3331Cb9395CBC201E3Fa6757Ea`}}},sourceId:lO}),dO=97,fO=L({id:5611,name:`opBNB Testnet`,nativeCurrency:{decimals:18,name:`tBNB`,symbol:`tBNB`},rpcUrls:{default:{http:[`https://opbnb-testnet-rpc.bnbchain.org`]}},blockExplorers:{default:{name:`opbnbscan`,url:`https://testnet.opbnbscan.com`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3705108},l2OutputOracle:{[dO]:{address:`0xFf2394Bb843012562f4349C6632a0EcB92fC8810`}},portal:{[dO]:{address:`0x4386C8ABf2009aC0c263462Da568DD9d46e52a31`}},l1StandardBridge:{[dO]:{address:`0x677311Fd2cCc511Bbc0f581E8d9a07B033D5E840`}}},testnet:!0,sourceId:dO}),pO=L({id:1612,name:`OpenLedger`,nativeCurrency:{name:`Open`,symbol:`OPEN`,decimals:18},rpcUrls:{default:{http:[`https://rpc.openledger.xyz`]}},blockExplorers:{default:{name:`OpenLedger Explorer`,url:`https://scan.openledger.xyz`}},testnet:!1}),mO=1,hO=L({...R,id:10,name:`OP Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.optimism.io`]}},blockExplorers:{default:{name:`Optimism Explorer`,url:`https://optimistic.etherscan.io`,apiUrl:`https://api-optimistic.etherscan.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[mO]:{address:`0xe5965Ab5962eDc7477C8520243A95517CD252fA9`}},l2OutputOracle:{[mO]:{address:`0xdfe97868233d1aa22e815a266982f2cf17685a27`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:4286263},portal:{[mO]:{address:`0xbEb5Fc579115071764c7423A4f12eDde41f106Ed`}},l1StandardBridge:{[mO]:{address:`0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1`}}},sourceId:mO}),gO=5,_O=L({...R,id:420,name:`Optimism Goerli`,nativeCurrency:{name:`Goerli Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://goerli.optimism.io`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://goerli-optimism.etherscan.io`,apiUrl:`https://goerli-optimism.etherscan.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[gO]:{address:`0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:49461},portal:{[gO]:{address:`0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383`}},l1StandardBridge:{[gO]:{address:`0x636Af16bf2f682dD3109e60102b8E1A089FedAa8`}}},testnet:!0,sourceId:gO}),vO=11155111,yO=L({...R,id:11155420,name:`OP Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.optimism.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://optimism-sepolia.blockscout.com`,apiUrl:`https://optimism-sepolia.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[vO]:{address:`0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1`}},l2OutputOracle:{[vO]:{address:`0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1620204},portal:{[vO]:{address:`0x16Fc5058F25648194471939df75CF27A2fdC48BC`}},l1StandardBridge:{[vO]:{address:`0xFBb0621E0B23b5478B630BD55a5f21f67730B0F1`}}},testnet:!0,sourceId:vO}),bO=L({id:62050,name:`Optopia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan.optopia.ai`}},testnet:!1}),xO=L({id:62049,name:`Optopia Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.optopia.ai`]}},blockExplorers:{default:{name:`Optopia Explorer`,url:`https://scan-testnet.optopia.ai`}},testnet:!0}),SO=L({id:291,name:`Orderly`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.orderly.network`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorer.orderly.network`}},testnet:!1}),CO=L({id:4460,name:`Orderly Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`]}},blockExplorers:{default:{name:`Orderly Explorer`,url:`https://explorerl2new-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz`}},testnet:!0}),wO=L({id:41144114,name:`Otim Devnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`http://devnet.otim.xyz`]}},contracts:{batchInvoker:{address:`0x5FbDB2315678afecb367f032d93F642f64180aa3`}}}),TO=L({id:11297108109,name:`Palm`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}}}),EO=L({id:11297108099,name:`Palm Testnet`,nativeCurrency:{decimals:18,name:`PALM`,symbol:`PALM`},rpcUrls:{default:{http:[`https://palm-mainnet.public.blastapi.io`],webSocket:[`wss://palm-mainnet.public.blastapi.io`]}},blockExplorers:{default:{name:`Chainlens`,url:`https://palm.chainlens.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:15429248}},testnet:!0}),DO=L({id:420420422,name:`Paseo PassetHub`,nativeCurrency:{name:`PAS`,symbol:`PAS`,decimals:18},rpcUrls:{default:{http:[`https://testnet-passet-hub-eth-rpc.polkadot.io`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://blockscout-passet-hub.parity-testnet.parity.io`}},testnet:!0}),OO=L({id:3338,name:`Peaq`,nativeCurrency:{decimals:18,name:`peaq`,symbol:`PEAQ`},rpcUrls:{default:{http:[`https://quicknode1.peaq.xyz`,`https://quicknode2.peaq.xyz`,`https://quicknode3.peaq.xyz`],webSocket:[`wss://quicknode1.peaq.xyz`,`wss://quicknode2.peaq.xyz`,`wss://quicknode3.peaq.xyz`]}},blockExplorers:{default:{name:`Subscan`,url:`https://peaq.subscan.io`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3566354}}}),kO=1,AO=L({id:424,network:`pgn`,name:`PGN`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Explorer`,url:`https://explorer.publicgoods.network`,apiUrl:`https://explorer.publicgoods.network/api`}},contracts:{l2OutputOracle:{[kO]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3380209},portal:{[kO]:{address:`0xb26Fd985c5959bBB382BAFdD0b879E149e48116c`}},l1StandardBridge:{[kO]:{address:`0xD0204B9527C1bA7bD765Fa5CCD9355d38338272b`}}},formatters:Iy,sourceId:kO}),jO=11155111,MO=L({id:58008,network:`pgn-testnet`,name:`PGN`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.publicgoods.network`]}},blockExplorers:{default:{name:`PGN Testnet Explorer`,url:`https://explorer.sepolia.publicgoods.network`,apiUrl:`https://explorer.sepolia.publicgoods.network/api`}},contracts:{l2OutputOracle:{[jO]:{address:`0xD5bAc3152ffC25318F848B3DD5dA6C85171BaEEe`}},portal:{[jO]:{address:`0xF04BdD5353Bb0EFF6CA60CfcC78594278eBfE179`}},l1StandardBridge:{[jO]:{address:`0xFaE6abCAF30D23e233AC7faF747F2fC3a5a6Bfa3`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3754925}},formatters:Iy,sourceId:jO,testnet:!0}),NO=L({id:13381,name:`Phoenix Blockchain`,nativeCurrency:{name:`Phoenix`,symbol:`PHX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.phoenixplorer.com`]}},blockExplorers:{default:{name:`Phoenixplorer`,url:`https://phoenixplorer.com`,apiUrl:`https://phoenixplorer.com/api`}},contracts:{multicall3:{address:`0x498cF757a575cFF2c2Ed9f532f56Efa797f86442`,blockCreated:5620192}}}),PO=L({id:7070,name:`Planq Mainnet`,nativeCurrency:{decimals:18,name:`PLQ`,symbol:`PLQ`},rpcUrls:{default:{http:[`https://planq-rpc.nodies.app`,`https://evm-rpc.planq.network`,`https://jsonrpc.planq.nodestake.top`]}},blockExplorers:{default:{name:`Planq Explorer`,url:`https://evm.planq.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:8470015}},testnet:!1}),FO=L({id:9745,name:`Plasma`,blockTime:1e3,nativeCurrency:{name:`Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plasma.to`]}},blockExplorers:{default:{name:`PlasmaScan`,url:`https://plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),IO=L({id:9747,name:`Plasma Devnet`,nativeCurrency:{name:`Devnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://devnet-rpc.plasma.to`]}},testnet:!0,contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}}}),LO=L({id:9746,name:`Plasma Testnet`,nativeCurrency:{name:`Testnet Plasma`,symbol:`XPL`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plasma.to`]}},blockExplorers:{default:{name:`RouteScan`,url:`https://testnet.plasmascan.to`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!0}),RO=L({...xy,id:1612127,name:`PlayFi Albireo Testnet`,network:`albireo`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://albireo-rpc.playfi.ai`],webSocket:[`wss://albireo-rpc-ws.playfi.ai/ws`]}},blockExplorers:{default:{name:`PlayFi Albireo Explorer`,url:`https://albireo-explorer.playfi.ai`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`}},testnet:!0}),zO=L({id:242,name:`Plinga`,nativeCurrency:{name:`Plinga`,symbol:`PLINGA`,decimals:18},rpcUrls:{default:{http:[`https://rpcurl.mainnet.plgchain.com`]}},blockExplorers:{default:{name:`Plgscan`,url:`https://www.plgscan.com`}},contracts:{multicall3:{address:`0x0989576160f2e7092908BB9479631b901060b6e4`,blockCreated:204489}}}),BO=L({id:98865,name:`Plume (Legacy)`,nativeCurrency:{name:`Plume Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plumenetwork.xyz`],webSocket:[`wss://rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plumenetwork.xyz`,apiUrl:`https://explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:48577}},sourceId:1}),VO=L({id:98864,name:`Plume Devnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc.plumenetwork.xyz`],webSocket:[`wss://test-rpc.plumenetwork.xyz`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://test-explorer.plumenetwork.xyz`,apiUrl:`https://test-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:481948}},testnet:!0,sourceId:11155111}),HO=L({id:98866,name:`Plume`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://rpc.plume.org`],webSocket:[`wss://rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.plume.org`,apiUrl:`https://explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39679}},sourceId:1}),UO=L({id:98867,name:`Plume Testnet`,nativeCurrency:{name:`Plume`,symbol:`PLUME`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plume.org`],webSocket:[`wss://testnet-rpc.plume.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plume.org`,apiUrl:`https://testnet-explorer.plume.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:199712}},testnet:!0,sourceId:11155111}),WO=L({id:161221135,name:`Plume Testnet (Legacy)`,nativeCurrency:{name:`Plume Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.plumenetwork.xyz/http`],webSocket:[`wss://testnet-rpc.plumenetwork.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer.plumenetwork.xyz`,apiUrl:`https://testnet-explorer.plumenetwork.xyz/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:6022332}},testnet:!0,sourceId:11155111}),GO=L({id:631571,name:`Polter Testnet`,nativeCurrency:{decimals:18,name:`Polter GHST`,symbol:`GHST`},rpcUrls:{default:{http:[`https://geist-polter.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://polter-testnet.explorer.alchemy.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:11245}},testnet:!0}),KO=L({id:137,name:`Polygon`,blockTime:2e3,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://polygon.drpc.org`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://polygonscan.com`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}}}),qO=L({id:80002,name:`Polygon Amoy`,nativeCurrency:{name:`POL`,symbol:`POL`,decimals:18},rpcUrls:{default:{http:[`https://rpc-amoy.polygon.technology`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://amoy.polygonscan.com`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:3127388}},testnet:!0}),JO=L({id:80001,name:`Polygon Mumbai`,nativeCurrency:{name:`MATIC`,symbol:`MATIC`,decimals:18},rpcUrls:{default:{http:[`https://80001.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://mumbai.polygonscan.com`,apiUrl:`https://api-testnet.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:25770160}},testnet:!0}),YO=L({id:1101,name:`Polygon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://zkevm.polygonscan.com`,apiUrl:`https://api-zkevm.polygonscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:57746}}}),XO=L({id:2442,name:`Polygon zkEVM Cardona`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.cardona.zkevm-rpc.com`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://cardona-zkevm.polygonscan.com`,apiUrl:`https://cardona-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:114091}}}),ZO=L({id:1442,name:`Polygon zkEVM Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.public.zkevm-test.net`]}},blockExplorers:{default:{name:`PolygonScan`,url:`https://testnet-zkevm.polygonscan.com`,apiUrl:`https://testnet-zkevm.polygonscan.com/api`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:525686}}}),QO=L({id:8008,name:`Polynomial`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://polynomialscan.io`}},testnet:!1,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),$O=L({id:80008,name:`Polynomia Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sepolia.polynomial.fi`]}},blockExplorers:{default:{name:`Polynomial Scan`,url:`https://sepolia.polynomialscan.io`}},testnet:!0,contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),ek=L({id:60603,name:`POTOS Mainnet`,nativeCurrency:{decimals:18,name:`POTOS Token`,symbol:`POT`},rpcUrls:{default:{http:[`https://rpc.potos.hk`]}},blockExplorers:{default:{name:`POTOS Mainnet explorer`,url:`https://scan.potos.hk`}},testnet:!1}),tk=L({id:60600,name:`POTOS Testnet`,nativeCurrency:{decimals:18,name:`POTOS Token`,symbol:`POT`},rpcUrls:{default:{http:[`https://rpc-testnet.potos.hk`]}},blockExplorers:{default:{name:`POTOS Testnet explorer`,url:`https://scan-testnet.potos.hk`}},testnet:!0}),nk=L({id:23023,name:`PremiumBlock Testnet`,nativeCurrency:{name:`Premium Block`,symbol:`PBLK`,decimals:18},rpcUrls:{default:{http:[`https://rpc.premiumblock.org`]}},blockExplorers:{default:{name:`PremiumBlocks Explorer`,url:`https://scan.premiumblock.org`}},testnet:!0}),rk=L({id:369,name:`PulseChain`,nativeCurrency:{name:`Pulse`,symbol:`PLS`,decimals:18},testnet:!1,blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.pulsechain.com`],webSocket:[`wss://ws.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://ipfs.scan.pulsechain.com`,apiUrl:`https://api.scan.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),ik=L({id:943,name:`PulseChain V4`,testnet:!0,nativeCurrency:{name:`V4 Pulse`,symbol:`v4PLS`,decimals:18},blockTime:1e4,rpcUrls:{default:{http:[`https://rpc.v4.testnet.pulsechain.com`],webSocket:[`wss://ws.v4.testnet.pulsechain.com`]}},blockExplorers:{default:{name:`PulseScan`,url:`https://scan.v4.testnet.pulsechain.com`,apiUrl:`https://scan.v4.testnet.pulsechain.com/api`}},contracts:{ensRegistry:{address:`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14353601}}}),ak=L({id:490092,name:`Pumpfi Testnet`,nativeCurrency:{decimals:18,name:`PMPT`,symbol:`PMPT`},rpcUrls:{default:{http:[`https://rpc1testnet.pumpfi.me`]}},blockExplorers:{default:{name:`Pumpfi Testnet Scan`,url:`https://testnetscan.pumpfi.me`}},testnet:!0}),ok=11155111,sk=L({...R,name:`Pyrope Testnet`,testnet:!0,id:695569,sourceId:ok,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.pyropechain.com`],webSocket:[`wss://rpc.pyropechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://pyrope.blockscout.com`}},contracts:{...R.contracts,l1StandardBridge:{[ok]:{address:`0xC24932c31D9621aE9e792576152B7ef010cFC2F8`}}}}),ck=L({id:766,name:`QL1`,nativeCurrency:{decimals:18,name:`QOM`,symbol:`QOM`},rpcUrls:{default:{http:[`https://rpc.qom.one`]}},blockExplorers:{default:{name:`Ql1 Explorer`,url:`https://scan.qom.one`}},contracts:{multicall3:{address:`0x7A52370716ea730585884F5BDB0f6E60C39b8C64`}},testnet:!1}),lk=L({id:35441,name:`Q Mainnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.q.org`]}},blockExplorers:{default:{name:`Q Mainnet Explorer`,url:`https://explorer.q.org`,apiUrl:`https://explorer.q.org/api`}}}),uk=L({id:35443,name:`Q Testnet`,nativeCurrency:{decimals:18,name:`Q`,symbol:`Q`},rpcUrls:{default:{http:[`https://rpc.qtestnet.org`]}},blockExplorers:{default:{name:`Q Testnet Explorer`,url:`https://explorer.qtestnet.org`,apiUrl:`https://explorer.qtestnet.org/api`}},testnet:!0}),dk=L({id:9,name:`Quai Network Mainnet`,nativeCurrency:{decimals:18,name:`Quai`,symbol:`QUAI`},rpcUrls:{default:{http:[`https://rpc.quai.network/cyprus1`]}},blockExplorers:{default:{name:`Quaiscan`,url:`https://quaiscan.io`,apiUrl:`https://quaiscan.io/api`}},testnet:!1}),fk=L({id:15e3,name:`Quai Network Testnet`,nativeCurrency:{decimals:18,name:`Quai`,symbol:`QUAI`},rpcUrls:{default:{http:[`https://orchard.rpc.quai.network/cyprus1`]}},blockExplorers:{default:{name:`Orchard Quaiscan`,url:`https://orchard.quaiscan.io`,apiUrl:`https://orchard.quaiscan.io/api`}},testnet:!0}),pk=L({id:5318007,name:`Reactive Lasna Testnet`,nativeCurrency:{decimals:18,name:`Lasna React`,symbol:`lREACT`},rpcUrls:{default:{http:[`https://lasna-rpc.rnk.dev`]}},blockExplorers:{default:{name:`Reactscan`,url:`https://lasna.reactscan.net`}},testnet:!0}),mk=L({id:111188,name:`re.al`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.realforreal.gelato.digital`]}},blockExplorers:{default:{name:`re.al Explorer`,url:`https://explorer.re.al`,apiUrl:`https://explorer.re.al/api/v2`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:695}}}),hk=L({id:151,name:`Redbelly Network Mainnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.mainnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/mainnet/evm/151/etherscan/api`}},testnet:!1}),gk=L({id:153,name:`Redbelly Network Testnet`,nativeCurrency:{name:`Redbelly Native Coin`,symbol:`RBNT`,decimals:18},rpcUrls:{default:{http:[`https://governors.testnet.redbelly.network`]}},blockExplorers:{default:{name:`Routescan`,url:`https://redbelly.testnet.routescan.io`,apiUrl:`https://api.routescan.io/v2/network/testnet/evm/153_2/etherscan/api`}},testnet:!0}),_k=L({id:50342,name:`Reddio`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.reddio.com/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://reddio.cloud.blockscout.com`,apiUrl:`https://reddio.cloud.blockscout.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:848849}},testnet:!1}),vk=L({id:50341,name:`Reddio Sepolia`,nativeCurrency:{name:`Reddio`,symbol:`RED`,decimals:18},rpcUrls:{default:{http:[`https://reddio-dev.reddio.com`]}},blockExplorers:{default:{name:`Reddioscan`,url:`https://reddio-devnet.l2scan.co`,apiUrl:`https://reddio-devnet.l2scan.co/api`}},testnet:!0}),yk=1,bk=L({...R,name:`Redstone`,id:690,sourceId:yk,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.redstonechain.com`],webSocket:[`wss://rpc.redstonechain.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.redstone.xyz`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[yk]:{address:`0xC7bCb0e8839a28A1cFadd1CF716de9016CdA51ae`,blockCreated:19578329}},l2OutputOracle:{[yk]:{address:`0xa426A052f657AEEefc298b3B5c35a470e4739d69`,blockCreated:19578337}},l1StandardBridge:{[yk]:{address:`0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69`,blockCreated:19578331}}}}),xk=L({id:47805,name:`REI Mainnet`,nativeCurrency:{decimals:18,name:`REI`,symbol:`REI`},rpcUrls:{default:{http:[`https://rpc.rei.network`],webSocket:[`wss://rpc.rei.network`]}},blockExplorers:{default:{name:`REI Scan`,url:`https://scan.rei.network`}},testnet:!1}),Sk=L({id:1729,name:`Reya Network`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.reya.network`],webSocket:[`wss://ws.reya.network`]}},blockExplorers:{default:{name:`Reya Network Explorer`,url:`https://explorer.reya.network`}},testnet:!1}),Ck=L({id:4153,name:`RISE`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.risechain.com`],webSocket:[`wss://rpc.risechain.com/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.risechain.com`,apiUrl:`https://explorer.risechain.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}}}),wk=L({id:11155931,name:`RISE Testnet`,nativeCurrency:{name:`RISE Testnet Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.riselabs.xyz`],webSocket:[`wss://testnet.riselabs.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.testnet.riselabs.xyz/`,apiUrl:`https://explorer.testnet.riselabs.xyz/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`}},testnet:!0}),Tk=L({id:753,name:`Rivalz`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rivalz.calderachain.xyz/http`]}},blockExplorers:{default:{name:`Rivalz Caldera Explorer`,url:`https://rivalz.calderaexplorer.xyz`}},testnet:!1}),Ek=L({id:570,name:`Rollux Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.rollux.com`],webSocket:[`wss://rpc.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxExplorer`,url:`https://explorer.rollux.com`,apiUrl:`https://explorer.rollux.com/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:119222}}}),Dk=L({id:57e3,name:`Rollux Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc-tanenbaum.rollux.com/`],webSocket:[`wss://rpc-tanenbaum.rollux.com/wss`]}},blockExplorers:{default:{name:`RolluxTestnetExplorer`,url:`https://rollux.tanenbaum.io`,apiUrl:`https://rollux.tanenbaum.io/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1813675}}}),Ok=L({id:2020,name:`Ronin`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://api.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Ronin Explorer`,url:`https://app.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:26023535}}}),kk=L({id:7668,name:`The Root Network`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://root.rootnet.live/archive`],webSocket:[`wss://root.rootnet.live/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:9218338}}}),Ak=L({id:7672,name:`The Root Network - Porcini`,nativeCurrency:{decimals:18,name:`XRP`,symbol:`XRP`},rpcUrls:{default:{http:[`https://porcini.rootnet.app/archive`],webSocket:[`wss://porcini.rootnet.app/archive/ws`]}},blockExplorers:{default:{name:`Rootscan`,url:`https://porcini.rootscan.io`}},contracts:{multicall3:{address:`0xc9C2E2429AeC354916c476B30d729deDdC94988d`,blockCreated:10555692}},testnet:!0}),jk=L({id:30,name:`Rootstock Mainnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`RBTC`},rpcUrls:{default:{http:[`https://public-node.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.rsk.co`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:4249540}}}),Mk=L({id:31,name:`Rootstock Testnet`,network:`rootstock`,nativeCurrency:{decimals:18,name:`Rootstock Bitcoin`,symbol:`tRBTC`},rpcUrls:{default:{http:[`https://public-node.testnet.rsk.co`]}},blockExplorers:{default:{name:`RSK Explorer`,url:`https://explorer.testnet.rootstock.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2771150}},testnet:!0}),Nk=1,Pk=L({...R,id:12553,name:`RSS3 VSL Mainnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Mainnet Scan`,url:`https://scan.rss3.io`,apiUrl:`https://scan.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[Nk]:{address:`0xE6f24d2C32B3109B18ed33cF08eFb490b1e09C10`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14193},portal:{[Nk]:{address:`0x6A12432491bbbE8d3babf75F759766774C778Db4`,blockCreated:19387057}},l1StandardBridge:{[Nk]:{address:`0x4cbab69108Aa72151EDa5A3c164eA86845f18438`}}},sourceId:Nk}),Fk=11155111,Ik=L({...R,id:2331,name:`RSS3 VSL Sepolia Testnet`,nativeCurrency:{name:`RSS3`,symbol:`RSS3`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.rss3.io`]}},blockExplorers:{default:{name:`RSS3 VSL Sepolia Testnet Scan`,url:`https://scan.testnet.rss3.io`,apiUrl:`https://scan.testnet.rss3.io/api`}},contracts:{...R.contracts,l2OutputOracle:{[Fk]:{address:`0xDb5c46C3Eaa6Ed6aE8b2379785DF7dd029C0dC81`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:55697},portal:{[Fk]:{address:`0xcBD77E8E1E7F06B25baDe67142cdE82652Da7b57`,blockCreated:5345035}},l1StandardBridge:{[Fk]:{address:`0xdDD29bb63B0839FB1cE0eE439Ff027738595D07B`}}},testnet:!0,sourceId:Fk}),Lk=L({id:7225878,name:`Saakuru Mainnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.saakuru.network`]}},blockExplorers:{default:{name:`Saakuru Explorer`,url:`https://explorer.saakuru.network`}},testnet:!1}),Rk=L({id:5464,name:`Saga`,network:`saga`,nativeCurrency:{decimals:18,name:`gas`,symbol:`GAS`},rpcUrls:{default:{http:[`https://sagaevm.jsonrpc.sagarpc.io`]}},blockExplorers:{default:{name:`Saga Explorer`,url:`https://sagaevm.sagaexplorer.io`}},contracts:{multicall3:{address:`0x864DDc9B50B9A0dF676d826c9B9EDe9F8913a160`,blockCreated:467530}}}),zk=L({id:202601,name:`Ronin Saigon Testnet`,nativeCurrency:{name:`RON`,symbol:`RON`,decimals:18},rpcUrls:{default:{http:[`https://saigon-testnet.roninchain.com/rpc`]}},blockExplorers:{default:{name:`Saigon Explorer`,url:`https://saigon-explorer.roninchain.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:18736871}},testnet:!0}),Bk=L({id:1996,name:`Sanko`,nativeCurrency:{name:`DMT`,symbol:`DMT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.sanko.xyz`]}},blockExplorers:{default:{name:`Sanko Explorer`,url:`https://explorer.sanko.xyz`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:37}},testnet:!1}),Vk=L({id:23294,name:`Oasis Sapphire`,network:`sapphire`,nativeCurrency:{name:`Sapphire Rose`,symbol:`ROSE`,decimals:18},rpcUrls:{default:{http:[`https://sapphire.oasis.io`],webSocket:[`wss://sapphire.oasis.io/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/mainnet/sapphire`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:734531}}}),Hk=L({id:23295,name:`Oasis Sapphire Testnet`,network:`sapphire-testnet`,nativeCurrency:{name:`Sapphire Test Rose`,symbol:`TEST`,decimals:18},rpcUrls:{default:{http:[`https://testnet.sapphire.oasis.dev`],webSocket:[`wss://testnet.sapphire.oasis.dev/ws`]}},blockExplorers:{default:{name:`Oasis Explorer`,url:`https://explorer.oasis.io/testnet/sapphire`}},testnet:!0}),Uk=L({id:3109,name:`SatoshiVM Alpha Mainnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://alpha-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://svmscan.io`,apiUrl:`https://svmscan.io/api`}}}),Wk=L({id:3110,name:`SatoshiVM Testnet`,nativeCurrency:{name:`BTC`,symbol:`BTC`,decimals:18},rpcUrls:{default:{http:[`https://test-rpc-node-http.svmscan.io`]}},blockExplorers:{default:{name:`blockscout`,url:`https://testnet.svmscan.io`,apiUrl:`https://testnet.svmscan.io/api`}},testnet:!0}),Gk=L({id:534352,name:`Scroll`,blockTime:3e3,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.scroll.io`],webSocket:[`wss://wss-rpc.scroll.io/ws`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://scrollscan.com`,apiUrl:`https://api.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:14}},testnet:!1}),Kk=L({id:534351,name:`Scroll Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia-rpc.scroll.io`]}},blockExplorers:{default:{name:`Scrollscan`,url:`https://sepolia.scrollscan.com`,apiUrl:`https://api-sepolia.scrollscan.com/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:9473}},testnet:!0}),qk=L({id:1329,name:`Sei Network`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc.sei-apis.com/`],webSocket:[`wss://evm-ws.sei-apis.com/`]}},blockExplorers:{default:{name:`Seiscan`,url:`https://seiscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}}}),Jk=L({id:5124,name:`Seismic Devnet`,nativeCurrency:{name:`Seismic Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://node-2.seismicdev.net/rpc`]}},blockExplorers:{default:{name:`Seismic Devnet Explorer`,url:`https://explorer-2.seismicdev.net`}},testnet:!0}),Yk=L({id:1328,name:`Sei Testnet`,nativeCurrency:{name:`Sei`,symbol:`SEI`,decimals:18},rpcUrls:{default:{http:[`https://evm-rpc-testnet.sei-apis.com`],webSocket:[`wss://evm-ws-testnet.sei-apis.com`]}},blockExplorers:{default:{name:`Seiscan`,url:`https://testnet.seiscan.io`,apiUrl:`https://api.etherscan.io/v2/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:98697651}},testnet:!0}),Xk=L({id:11155111,name:`Sepolia`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://11155111.rpc.thirdweb.com`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://sepolia.etherscan.io`,apiUrl:`https://api-sepolia.etherscan.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:751532},ensUniversalResolver:{address:`0xeeeeeeee14d718c2b47d9923deab1335e144eeee`,blockCreated:8928790}},testnet:!0}),Zk=1,Qk=L({...R,id:360,name:`Shape`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.shape.network`]}},blockExplorers:{default:{name:`shapescan`,url:`https://shapescan.xyz`,apiUrl:`https://shapescan.xyz/api`}},contracts:{...R.contracts,l2OutputOracle:{[Zk]:{address:`0x6Ef8c69CfE4635d866e3E02732068022c06e724D`,blockCreated:20369940}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1},portal:{[Zk]:{address:`0xEB06fFa16011B5628BaB98E29776361c83741dd3`,blockCreated:20369933}},l1StandardBridge:{[Zk]:{address:`0x62Edd5f4930Ea92dCa3fB81689bDD9b9d076b57B`,blockCreated:20369935}}},sourceId:Zk}),$k=11155111,eA=L({...R,id:11011,name:`Shape Sepolia Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.shape.network`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer-sepolia.shape.network/`,apiUrl:`https://explorer-sepolia.shape.network/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1}},testnet:!0,sourceId:$k}),tA=L({id:8118,name:`Shardeum`,nativeCurrency:{name:`Shardeum`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://api.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer.shardeum.org`}},testnet:!1}),nA=L({id:8082,name:`Shardeum Sphinx`,nativeCurrency:{name:`SHARDEUM`,symbol:`SHM`,decimals:18},rpcUrls:{default:{http:[`https://sphinx.shardeum.org`]}},blockExplorers:{default:{name:`Shardeum Explorer`,url:`https://explorer-sphinx.shardeum.org`}},testnet:!0}),rA=L({id:109,name:`Shibarium`,network:`shibarium`,nativeCurrency:{name:`Bone`,symbol:`BONE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://shibariumscan.io`}},contracts:{multicall3:{address:`0x864Bf681ADD6052395188A89101A1B37d3B4C961`,blockCreated:265900}}}),iA=L({id:157,name:`Puppynet Shibarium`,nativeCurrency:{decimals:18,name:`Bone`,symbol:`BONE`},rpcUrls:{default:{http:[`https://puppynet.shibrpc.com`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://puppyscan.shib.io`,apiUrl:`https://puppyscan.shib.io/api`}},contracts:{multicall3:{address:`0xA4029b74FBA366c926eDFA7Dd10B21C621170a4c`,blockCreated:3035769}},testnet:!0}),aA=L({id:336,name:`Shiden`,nativeCurrency:{decimals:18,name:`SDN`,symbol:`SDN`},rpcUrls:{default:{http:[`https://shiden.public.blastapi.io`],webSocket:[`wss://shiden-rpc.dwellir.com`]}},blockExplorers:{default:{name:`Shiden Scan`,url:`https://shiden.subscan.io`}},testnet:!1}),oA=L({id:148,name:`Shimmer`,network:`shimmer`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.shimmer.network`,apiUrl:`https://explorer.evm.shimmer.network/api`}}}),sA=L({id:1073,name:`Shimmer Testnet`,network:`shimmer-testnet`,nativeCurrency:{decimals:18,name:`Shimmer`,symbol:`SMR`},rpcUrls:{default:{http:[`https://json-rpc.evm.testnet.shimmer.network`]}},blockExplorers:{default:{name:`Shimmer Network Explorer`,url:`https://explorer.evm.testnet.shimmer.network`,apiUrl:`https://explorer.evm.testnet.shimmer.network/api`}},testnet:!0}),cA=L({id:97453,name:`Sidra Chain`,nativeCurrency:{decimals:18,name:`Sidra Digital Asset`,symbol:`SDA`},rpcUrls:{default:{http:[`https://node.sidrachain.com`]}},blockExplorers:{default:{name:`Sidra Chain Explorer`,url:`https://ledger.sidrachain.com`}}}),lA=L({id:380929,name:`Silent Data Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.silentdata.com`]}},blockExplorers:{default:{name:`Silent Data Mainnet Explorer`,url:`https://explorer-mainnet.rollup.silentdata.com`}},testnet:!1}),uA=L({id:2355,name:`Silicon zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.silicon.network`,`https://silicon-mainnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconScope`,url:`https://scope.silicon.network`}}}),dA=L({id:1722641160,name:`Silicon Sepolia zkEVM`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-sepolia.silicon.network`,`https://silicon-testnet.nodeinfra.com`]}},blockExplorers:{default:{name:`SiliconSepoliaScope`,url:`https://scope-sepolia.silicon.network`}},testnet:!0}),fA=L({id:98,name:`Six Protocol`,nativeCurrency:{decimals:18,name:`SIX`,symbol:`SIX`},rpcUrls:{default:{http:[`https://sixnet-rpc-evm.sixprotocol.net`]}},blockExplorers:{default:{name:`Six Protocol Scan`,url:`https://sixscan.io/sixnet`}},testnet:!1}),pA=L({id:391845894,name:`SKALE | Block Brawlers`,nativeCurrency:{name:`BRAWL`,symbol:`BRAWL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/frayed-decent-antares`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/frayed-decent-antares`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://frayed-decent-antares.explorer.mainnet.skalenodes.com`}},contracts:{}}),mA=L({id:1564830818,name:`SKALE Calypso Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/honorable-steel-rasalhague`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/honorable-steel-rasalhague`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3107626}}}),hA=L({id:974399131,name:`SKALE Calypso Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/giant-half-dual-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/giant-half-dual-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://giant-half-dual-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:103220}},testnet:!0}),gA=L({id:1026062157,name:`SKALE | CryptoBlades`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/affectionate-immediate-pollux`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/affectionate-immediate-pollux`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://affectionate-immediate-pollux.explorer.mainnet.skalenodes.com`}},contracts:{}}),_A=L({id:1032942172,name:`SKALE | Crypto Colosseum`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/haunting-devoted-deneb`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/haunting-devoted-deneb`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://haunting-devoted-deneb.explorer.mainnet.skalenodes.com`}},contracts:{}}),vA=L({id:2046399126,name:`SKALE Europa Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/elated-tan-skat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/elated-tan-skat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://elated-tan-skat.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:3113495}}}),yA=L({id:1444673419,name:`SKALE Europa Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/juicy-low-small-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/juicy-low-small-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://juicy-low-small-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:110858}},testnet:!0}),bA=L({id:2139927552,name:`Exorde Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/light-vast-diphda`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/light-vast-diphda`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://light-vast-diphda.explorer.mainnet.skalenodes.com`}},contracts:{}}),xA=L({id:1273227453,name:`SKALE | Human Protocol`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/wan-red-ain`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/wan-red-ain`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://wan-red-ain.explorer.mainnet.skalenodes.com`}},contracts:{}}),SA=L({id:1482601649,name:`SKALE Nebula Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/green-giddy-denebola`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/green-giddy-denebola`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://green-giddy-denebola.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2372986}}}),CA=L({id:37084624,name:`SKALE Nebula Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/lanky-ill-funny-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/lanky-ill-funny-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:105141}},testnet:!0}),wA=L({id:278611351,name:`SKALE | Razor Network`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/turbulent-unique-scheat`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/turbulent-unique-scheat`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://turbulent-unique-scheat.explorer.mainnet.skalenodes.com`}},contracts:{}}),TA=L({id:1187947933,name:`SKALE Base`,nativeCurrency:{name:`Credits`,symbol:`CREDIT`,decimals:18},rpcUrls:{default:{http:[`https://skale-base.skalenodes.com/v1/base`],webSocket:[`wss://skale-base.skalenodes.com/v1/ws/base`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://skale-base-explorer.skalenodes.com/`}},testnet:!0}),EA=L({id:324705682,name:`SKALE Base Sepolia Testnet`,nativeCurrency:{name:`Credits`,symbol:`CREDIT`,decimals:18},rpcUrls:{default:{http:[`https://base-sepolia-testnet.skalenodes.com/v1/base-testnet`],webSocket:[`wss://base-sepolia-testnet.skalenodes.com/v1/ws/base-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://base-sepolia-testnet-explorer.skalenodes.com/`}},testnet:!0}),DA=L({id:1350216234,name:`SKALE Titan Hub`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.skalenodes.com/v1/parallel-stormy-spica`],webSocket:[`wss://mainnet.skalenodes.com/v1/ws/parallel-stormy-spica`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://parallel-stormy-spica.explorer.mainnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2076458}}}),OA=L({id:1020352220,name:`SKALE Titan Testnet`,nativeCurrency:{name:`sFUEL`,symbol:`sFUEL`,decimals:18},rpcUrls:{default:{http:[`https://testnet.skalenodes.com/v1/aware-fake-trim-testnet`],webSocket:[`wss://testnet.skalenodes.com/v1/ws/aware-fake-trim-testnet`]}},blockExplorers:{default:{name:`SKALE Explorer`,url:`https://aware-fake-trim-testnet.explorer.testnet.skalenodes.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:104072}},testnet:!0}),kA=L({id:984123,name:`Forma Sketchpad`,network:`sketchpad`,nativeCurrency:{symbol:`TIA`,name:`TIA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.sketchpad-1.forma.art`],webSocket:[`wss://ws.sketchpad-1.forma.art`]}},blockExplorers:{default:{name:`Sketchpad Explorer`,url:`https://explorer.sketchpad-1.forma.art`}},testnet:!0}),AA=1,jA=L({...R,id:2192,network:`snaxchain-mainnet`,name:`SnaxChain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://explorer.snaxchain.io`,apiUrl:`https://explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[AA]:{address:`0x472562Fcf26D6b2793f8E0b0fB660ba0E5e08A46`}},l2OutputOracle:{[AA]:{address:`0x2172e492Fc807F5d5645D0E3543f139ECF539294`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[AA]:{address:`0x79f446D024d74D0Bb6E699C131c703463c5D65E9`}},l1StandardBridge:{[AA]:{address:`0x6534Bdb6b5c060d3e6aa833433333135eFE8E0aA`}}},sourceId:AA}),MA=11155111,NA=L({...R,id:13001,network:`snaxchain-testnet`,name:`SnaxChain Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://testnet.snaxchain.io`]}},blockExplorers:{default:{name:`Snax Explorer`,url:`https://testnet-explorer.snaxchain.io`,apiUrl:`https://testnet-explorer.snaxchain.io/api`}},contracts:{...R.contracts,disputeGameFactory:{[MA]:{address:`0x206a75d89d45F146C54020F132FF93bEDD09f55E`}},l2OutputOracle:{[MA]:{address:`0x60e3A368a4cdCEf85ffB964e372726F56A46221e`}},multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`},portal:{[MA]:{address:`0xb5afdd0E8dDF081Ef90e8A3e0c7b5798e66E954E`}},l1StandardBridge:{[MA]:{address:`0xbd37E1a59D4C00C9A46F75018dffd84061bC5f74`}}},testnet:!0,sourceId:MA}),PA=L({id:5031,name:`Somnia`,nativeCurrency:{name:`Somnia`,symbol:`SOMI`,decimals:18},rpcUrls:{default:{http:[`https://api.infra.mainnet.somnia.network`]}},blockExplorers:{default:{name:`Somnia Explorer`,url:`https://explorer.somnia.network`,apiUrl:`https://explorer.somnia.network/api`}},testnet:!1}),FA=L({id:50312,name:`Somnia Testnet`,nativeCurrency:{name:`STT`,symbol:`STT`,decimals:18},rpcUrls:{default:{http:[`https://dream-rpc.somnia.network`]}},blockExplorers:{default:{name:`Somnia Testnet Explorer`,url:`https://shannon-explorer.somnia.network/`,apiUrl:`https://shannon-explorer.somnia.network/api`}},contracts:{multicall3:{address:`0x841b8199E6d3Db3C6f264f6C2bd8848b3cA64223`,blockCreated:71314235}},testnet:!0}),IA=1,LA=L({...R,id:1868,name:`Soneium Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium.blockscout.com`,apiUrl:`https://soneium.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[IA]:{address:`0x512a3d2c7a43bd9261d2b8e8c9c70d4bd4d503c0`}},l2OutputOracle:{[IA]:{address:`0x0000000000000000000000000000000000000000`}},portal:{[IA]:{address:`0x88e529a6ccd302c948689cd5156c83d4614fae92`,blockCreated:7061266}},l1StandardBridge:{[IA]:{address:`0xeb9bf100225c214efc3e7c651ebbadcf85177607`,blockCreated:7061266}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},sourceId:IA}),RA=11155111,zA=L({...R,id:1946,name:`Soneium Minato Testnet`,nativeCurrency:{name:`Sepolia Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.minato.soneium.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://soneium-minato.blockscout.com`,apiUrl:`https://soneium-minato.blockscout.com/api`}},contracts:{...R.contracts,disputeGameFactory:{[RA]:{address:`0xB3Ad2c38E6e0640d7ce6aA952AB3A60E81bf7a01`}},l2OutputOracle:{[RA]:{address:`0x710e5286C746eC38beeB7538d0146f60D27be343`}},portal:{[RA]:{address:`0x65ea1489741A5D72fFdD8e6485B216bBdcC15Af3`,blockCreated:6466136}},l1StandardBridge:{[RA]:{address:`0x5f5a404A5edabcDD80DB05E8e54A78c9EBF000C2`,blockCreated:6466136}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}},testnet:!0,sourceId:RA}),BA=L({id:19,name:`Songbird Canary-Network`,nativeCurrency:{decimals:18,name:`Songbird`,symbol:`SGB`},rpcUrls:{default:{http:[`https://songbird-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Songbird Explorer`,url:`https://songbird-explorer.flare.network`,apiUrl:`https://songbird-explorer.flare.network/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:13382504}}}),VA=L({id:16,name:`Songbird Testnet Coston`,nativeCurrency:{decimals:18,name:`Coston Flare`,symbol:`CFLR`},rpcUrls:{default:{http:[`https://coston-api.flare.network/ext/C/rpc`]}},blockExplorers:{default:{name:`Coston Explorer`,url:`https://coston-explorer.flare.network`,apiUrl:`https://coston-explorer.flare.network/api`}},testnet:!0}),HA=L({id:146,name:`Sonic`,blockTime:630,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Explorer`,url:`https://sonicscan.org`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:60}},testnet:!1}),UA=L({id:57054,name:`Sonic Blaze Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.blaze.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Blaze Testnet Explorer`,url:`https://testnet.sonicscan.org`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1100}},testnet:!0}),WA=L({id:64165,name:`Sonic Testnet`,nativeCurrency:{decimals:18,name:`Sonic`,symbol:`S`},rpcUrls:{default:{http:[`https://rpc.testnet.soniclabs.com`]}},blockExplorers:{default:{name:`Sonic Testnet Explorer`,url:`https://testnet.soniclabs.com/`}},testnet:!0}),GA=L({...xy,blockTime:200,id:50104,name:`Sophon`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.sophon.xyz`],webSocket:[`wss://rpc.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.sophon.xyz`}},contracts:{multicall3:{address:`0x5f4867441d2416cA88B1b3fd38f21811680CD2C8`,blockCreated:116}},testnet:!1}),KA=L({...xy,blockTime:200,id:531050104,name:`Sophon Testnet`,nativeCurrency:{decimals:18,name:`Sophon`,symbol:`SOPH`},rpcUrls:{default:{http:[`https://rpc.testnet.sophon.xyz`],webSocket:[`wss://rpc.testnet.sophon.xyz/ws`]}},blockExplorers:{default:{name:`Sophon Block Explorer`,url:`https://explorer.testnet.sophon.xyz`}},contracts:{multicall3:{address:`0x83c04d112adedA2C6D9037bb6ecb42E7f0b108Af`,blockCreated:15642}},testnet:!0}),qA=L({id:100021,name:`Sova`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.sova.io`]}},blockExplorers:{default:{name:`Sova Block Explorer`,url:`hhttps://explorer.sova.io`}},testnet:!1}),JA=L({id:120893,name:`Sova Network Sepolia`,nativeCurrency:{decimals:18,name:`Sepolia Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.testnet.sova.io`]}},blockExplorers:{default:{name:`Sova Sepolia Explorer`,url:`https://explorer.testnet.sova.io`}},testnet:!0}),YA=L({id:88882,name:`Chiliz Spicy Testnet`,network:`chiliz-spicy-Testnet`,nativeCurrency:{decimals:18,name:`CHZ`,symbol:`CHZ`},rpcUrls:{default:{http:[`https://spicy-rpc.chiliz.com`,`https://chiliz-spicy-rpc.publicnode.com`],webSocket:[`wss://spicy-rpc-ws.chiliz.com`,`wss://chiliz-spicy-rpc.publicnode.com`]}},blockExplorers:{default:{name:`Chiliz Explorer`,url:`http://spicy-explorer.chiliz.com`,apiUrl:`http://spicy-explorer.chiliz.com/api`}},testnet:!0}),XA=L({id:988,name:`Stable Mainnet`,blockTime:700,nativeCurrency:{name:`USDT0`,symbol:`USDT0`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stable.xyz`],webSocket:[`wss://rpc.stable.xyz`]}},blockExplorers:{default:{name:`Stablescan`,url:`https://stablescan.xyz`,apiUrl:`https://api.etherscan.io/v2/api?chainid=988`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2423647}},testnet:!1}),ZA=L({id:2201,name:`Stable Testnet`,blockTime:700,nativeCurrency:{name:`USDT0`,symbol:`USDT0`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.stable.xyz`],webSocket:[`wss://rpc.testnet.stable.xyz`]}},blockExplorers:{default:{name:`Stablescan`,url:`https://testnet.stablescan.xyz`,apiUrl:`https://api.etherscan.io/v2/api?chainid=2201`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:22364430}},testnet:!0}),QA=L({...SE,id:1660990954,name:`Status Network Sepolia`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://public.sepolia.rpc.status.network`],webSocket:[`wss://public.sepolia.rpc.status.network/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://sepoliascan.status.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1578364}},testnet:!0}),$A=L({id:1234,name:`Step Network`,nativeCurrency:{name:`FITFI`,symbol:`FITFI`,decimals:18},rpcUrls:{default:{http:[`https://rpc.step.network`]}},blockExplorers:{default:{name:`Step Scan`,url:`https://stepscan.io`}},testnet:!1}),ej=L({id:1514,name:`Story`,nativeCurrency:{decimals:18,name:`IP Token`,symbol:`IP`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:340998},ensRegistry:{address:`0x5dc881dda4e4a8d312be3544ad13118d1a04cb17`,blockCreated:648924},ensUniversalResolver:{address:`0xddfb18888a9466688235887dec2a10c4f5effee9`,blockCreated:649114}},rpcUrls:{default:{http:[`https://mainnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story explorer`,url:`https://storyscan.io`,apiUrl:`https://storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!1}),tj=L({id:1315,name:`Story Aeneid`,network:`story-aeneid`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:1792},ensRegistry:{address:`0x5dC881dDA4e4a8d312be3544AD13118D1a04Cb17`,blockCreated:1322033},ensUniversalResolver:{address:`0x6D3B3F99177FB2A5de7F9E928a9BD807bF7b5BAD`,blockCreated:1322097}},rpcUrls:{default:{http:[`https://aeneid.storyrpc.io`]}},blockExplorers:{default:{name:`Story Aeneid Explorer`,url:`https://aeneid.storyscan.io`,apiUrl:`https://aeneid.storyscan.io/api/v2`}},ensTlds:[`.ip`],testnet:!0}),nj=L({id:1516,name:`Story Odyssey`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://rpc.odyssey.storyrpc.io`]}},blockExplorers:{default:{name:`Story Odyssey Explorer`,url:`https://odyssey.storyscan.xyz`}},testnet:!0}),rj=L({id:1513,name:`Story Testnet`,nativeCurrency:{decimals:18,name:`IP`,symbol:`IP`},rpcUrls:{default:{http:[`https://testnet.storyrpc.io`]}},blockExplorers:{default:{name:`Story Testnet Explorer`,url:`https://testnet.storyscan.xyz`}},testnet:!0}),ij=L({id:105105,name:`Stratis Mainnet`,network:`stratis`,nativeCurrency:{name:`Stratis`,symbol:`STRAX`,decimals:18},rpcUrls:{default:{http:[`https://rpc.stratisevm.com`]}},blockExplorers:{default:{name:`Stratis Explorer`,url:`https://explorer.stratisevm.com`}}}),aj=L({id:964,name:`Subtensor EVM`,nativeCurrency:{decimals:18,name:`TAO`,symbol:`TAO`},rpcUrls:{default:{http:[`https://lite.chain.opentensor.ai`]}},blockExplorers:{default:{name:`Taostats EVM Explorer`,url:`https://evm.taostats.io`,apiUrl:`https://evm.taostats.io/api`}},testnet:!1}),oj=L({id:8866,name:`SuperLumio`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.lumio.io`]}},blockExplorers:{default:{name:`Lumio explorer`,url:`https://explorer.lumio.io`}},testnet:!1}),sj=L({id:55244,name:`Superposition`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.superposition.so`]}},blockExplorers:{default:{name:`Superposition Explorer`,url:`https://explorer.superposition.so`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:39}},testnet:!1}),cj=1,lj=L({...R,id:5330,name:`Superseed`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Explorer`,url:`https://explorer.superseed.xyz`,apiUrl:`https://explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,disputeGameFactory:{[cj]:{address:`0x8b097CF1f9BbD9cbFD0DD561858a1FCbC8857Be0`,blockCreated:20737481}},l2OutputOracle:{[cj]:{address:`0x693A0F8854F458D282DE3C5b69E8eE5EEE8aA949`,blockCreated:20737481}},portal:{[cj]:{address:`0x2c2150aa5c75A24fB93d4fD2F2a895D618054f07`,blockCreated:20737481}},l1StandardBridge:{[cj]:{address:`0x8b0576E39F1233679109F9b40cFcC2a7E0901Ede`,blockCreated:20737481}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`}},sourceId:cj}),uj=11155111,dj=L({...R,id:53302,name:`Superseed Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.superseed.xyz`]}},blockExplorers:{default:{name:`Superseed Sepolia Explorer`,url:`https://sepolia-explorer.superseed.xyz`,apiUrl:`https://sepolia-explorer.superseed.xyz/api/v2`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},portal:{[uj]:{address:`0x7A0db8C51432d2C3eb4e8f360a2EeB26FF2809fB`,blockCreated:5523438}},l1StandardBridge:{[uj]:{address:`0x2B227A603fAAdB3De0ED050b63ADD232B5f2c28C`,blockCreated:5523442}}},testnet:!0,sourceId:uj}),fj=L({id:763375,name:`Surge Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://l2-rpc.hoodi.surge.wtf`],webSocket:[`wss://l2-ws.hoodi.surge.wtf`]}},blockExplorers:{default:{name:`Surge Testnet Blockscout`,url:`https://explorer.hoodi.surge.wtf`}},testnet:!0}),pj=L({id:254,name:`Swan Chain Mainnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.swanchain.org`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://swanscan.io`}},testnet:!1}),mj=L({id:20241133,name:`Swan Proxima Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc-proxima.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://proxima-explorer.swanchain.io`}},testnet:!0}),hj=L({id:2024,name:`Swan Saturn Testnet`,nativeCurrency:{name:`Swan Ether`,symbol:`sETH`,decimals:18},rpcUrls:{default:{http:[`https://saturn-rpc.swanchain.io`]}},blockExplorers:{default:{name:`Swan Explorer`,url:`https://saturn-explorer.swanchain.io`}},testnet:!0}),gj=L({...R,id:1923,name:`Swellchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-mainnet.alt.technology`]}},blockExplorers:{default:{name:`Swell Explorer`,url:`https://explorer.swellnetwork.io`,apiUrl:`https://explorer.swellnetwork.io/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),_j=L({...R,id:1924,name:`Swellchain Testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://swell-testnet.alt.technology`]}},blockExplorers:{default:{name:`Swellchain Testnet Explorer`,url:`https://swell-testnet-explorer.alt.technology`,apiUrl:`https://swell-testnet-explorer.alt.technology/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1}}}),vj=L({id:94,name:`SwissDLT Mainnet`,nativeCurrency:{decimals:18,name:`BCTS`,symbol:`BCTS`},rpcUrls:{default:{http:[`https://rpc.swissdlt.ch`]}},blockExplorers:{default:{name:`SwissDLT Explorer`,url:`https://explorer.swissdlt.ch`}},testnet:!1}),yj=L({id:57,name:`Syscoin Mainnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.syscoin.org`],webSocket:[`wss://rpc.syscoin.org/wss`]}},blockExplorers:{default:{name:`SyscoinExplorer`,url:`https://explorer.syscoin.org`,apiUrl:`https://explorer.syscoin.org/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:287139}}}),bj=L({id:5700,name:`Syscoin Tanenbaum Testnet`,nativeCurrency:{decimals:18,name:`Syscoin`,symbol:`SYS`},rpcUrls:{default:{http:[`https://rpc.tanenbaum.io`],webSocket:[`wss://rpc.tanenbaum.io/wss`]}},blockExplorers:{default:{name:`SyscoinTestnetExplorer`,url:`https://tanenbaum.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:271288}}}),xj=L({id:239,name:`TAC`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.ankr.com/tac`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://tac.blockscout.com`,apiUrl:`https://tac.blockscout.com/api`},native:{name:`TAC Explorer`,url:`https://explorer.tac.build`,apiUrl:`https://explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0}}}),Sj=L({id:2391,name:`TAC SPB Testnet`,nativeCurrency:{name:`TAC`,symbol:`TAC`,decimals:18},rpcUrls:{default:{http:[`https://spb.rpc.tac.build`]}},blockExplorers:{default:{name:`TAC`,url:`https://spb.explorer.tac.build`,apiUrl:`https://spb.explorer.tac.build/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:471429}},testnet:!0}),Cj=L({id:167e3,name:`Taiko Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.mainnet.taiko.xyz`],webSocket:[`wss://ws.mainnet.taiko.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://taikoscan.io`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:11269}}}),wj=L({id:167009,name:`Taiko Hekla L2`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hekla.taiko.xyz`]}},blockExplorers:{default:{name:`Taikoscan`,url:`https://hekla.taikoscan.network`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59757}},testnet:!0}),Tj=L({id:167013,name:`Taiko Hoodi`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.hoodi.taiko.xyz`],webSocket:[`wss://ws.hoodi.taiko.xyz`]}},blockExplorers:{default:{name:`Etherscan`,url:`https://hoodi.taikoscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:581116}},testnet:!0}),Ej=L({id:167007,name:`Taiko Jolnir (Alpha-5 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.jolnir.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.jolnir.taiko.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:732706}},testnet:!0}),Dj=L({id:167008,name:`Taiko Katla (Alpha-6 Testnet)`,network:`tko-katla`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.katla.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.katla.taiko.xyz`}}}),Oj=L({id:167005,name:`Taiko (Alpha-3 Testnet)`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.test.taiko.xyz`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.test.taiko.xyz`}}}),kj=L({id:841,name:`Taraxa Mainnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.mainnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.mainnet.taraxa.io`}}}),Aj=L({id:842,name:`Taraxa Testnet`,nativeCurrency:{name:`Tara`,symbol:`TARA`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.taraxa.io`]}},blockExplorers:{default:{name:`Taraxa Explorer`,url:`https://explorer.testnet.taraxa.io`}},testnet:!0}),jj=L({id:10218,name:`Tea Sepolia`,nativeCurrency:{name:`Sepolia Tea`,symbol:`TEA`,decimals:18},rpcUrls:{default:{http:[`https://tea-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Tea Sepolia Explorer`,url:`https://sepolia.tea.xyz`}},testnet:!0}),Mj=L({id:2017,name:`Telcoin Adiri Testnet`,nativeCurrency:{name:`Telcoin`,symbol:`TEL`,decimals:18},rpcUrls:{default:{http:[`https://rpc.telcoin.network`]}},blockExplorers:{default:{name:`telscan`,url:`https://telscan.io`}},testnet:!0}),Nj=L({id:40,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.telos.net`]}},blockExplorers:{default:{name:`Teloscan`,url:`https://www.teloscan.io/`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:246530709}}}),Pj=L({id:41,name:`Telos`,nativeCurrency:{decimals:18,name:`Telos`,symbol:`TLOS`},rpcUrls:{default:{http:[`https://rpc.testnet.telos.net`]}},blockExplorers:{default:{name:`Teloscan (testnet)`,url:`https://testnet.teloscan.io/`}},testnet:!0});Ls(),wl(),Pl(),Wc(),xl();var Fj=Mc(BigInt(`0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff`)),Ij=Fj.create(BigInt(`-3`)),Lj=BigInt(`0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`),Rj=Cl({a:Ij,b:Lj,Fp:Fj,n:BigInt(`0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551`),Gx:BigInt(`0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296`),Gy:BigInt(`0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5`),h:BigInt(1),lowS:!1},Ps),zj=Rj,Bj=pl(Fj,{A:Ij,B:Lj,Z:Fj.create(BigInt(`-10`))}),Vj=Ml(zj.ProjectivePoint,e=>Bj(e[0]),{DST:`P256_XMD:SHA-256_SSWU_RO_`,encodeDST:`P256_XMD:SHA-256_SSWU_NU_`,p:Fj.ORDER,m:1,k:128,expand:`xmd`,hash:Ps}),Hj=Mc(BigInt(`0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff`)),Uj=Hj.create(BigInt(`-3`)),Wj=BigInt(`0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef`),Gj=Cl({a:Uj,b:Wj,Fp:Hj,n:BigInt(`0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973`),Gx:BigInt(`0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7`),Gy:BigInt(`0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f`),h:BigInt(1),lowS:!1},Is),Kj=pl(Hj,{A:Uj,B:Wj,Z:Hj.create(BigInt(`-12`))});Ml(Gj.ProjectivePoint,e=>Kj(e[0]),{DST:`P384_XMD:SHA-384_SSWU_RO_`,encodeDST:`P384_XMD:SHA-384_SSWU_NU_`,p:Hj.ORDER,m:1,k:192,expand:`xmd`,hash:Is});var qj=Mc(BigInt(`0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`)),Jj=qj.create(BigInt(`-3`)),Yj=BigInt(`0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00`),Xj=Cl({a:Jj,b:Yj,Fp:qj,n:BigInt(`0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409`),Gx:BigInt(`0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66`),Gy:BigInt(`0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650`),h:BigInt(1),lowS:!1,allowedPrivateKeyLengths:[130,131,132]},Fs),Zj=pl(qj,{A:Jj,B:Yj,Z:qj.create(BigInt(`-4`))});Ml(Xj.ProjectivePoint,e=>Zj(e[0]),{DST:`P521_XMD:SHA-512_SSWU_RO_`,encodeDST:`P521_XMD:SHA-512_SSWU_NU_`,p:qj.ORDER,m:1,k:256,expand:`xmd`,hash:Fs});var Qj=Rj,$j=Rj;Vj.hashToCurve,Vj.encodeToCurve,Hf();function eM(e){let{hash:t,payload:n,publicKey:r,signature:i}=e;return $j.verify(i,n instanceof Uint8Array?n:xf(n),og(r).substring(2),{lowS:!0,...t?{prehash:!0}:{}})}Hf();var tM=new TextEncoder;Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[t,e.charCodeAt(0)]);var nM={...Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[e.charCodeAt(0),t])),61:0,45:62,95:63};function rM(e){let t=e.replace(/=+$/,``),n=t.length,r=new Uint8Array(n+3);tM.encodeInto(t+`===`,r);for(let e=0,n=0;e>16,r[n+1]=t>>8&255,r[n+2]=t&255}let i=(n>>2)*3+(n%4&&n%4-1);return new Uint8Array(r.buffer,0,i)}Hf(),Qd(),pp();var iM=class extends P{constructor({majorType:e}){super(`Invalid CBOR major type: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidMajorTypeError`})}},aM=class extends P{constructor({additionalInfo:e}){super(`Invalid CBOR additional info: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidAdditionalInfoError`})}},oM=class extends P{constructor(){super(`64-bit integers are not supported in CBOR decoding.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.Unsupported64BitIntegerError`})}},sM=class extends P{constructor({tag:e}){super(`CBOR tagged data (tag ${e}) is not yet supported.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnsupportedTagError`})}},cM=class extends P{constructor({type:e}){super(`Invalid chunk type in indefinite-length ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidIndefiniteLengthChunkError`})}},lM=class extends P{constructor({value:e}){super(`Invalid CBOR simple value: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.InvalidSimpleValueError`})}},uM=class extends P{constructor(){super(`BigInt values are not supported in CBOR encoding.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnsupportedBigIntError`})}},dM=class extends P{constructor({token:e}){super(`Unexpected token: ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.UnexpectedTokenError`})}},fM=class extends P{constructor({number:e}){super(`Number exceeds maximum safe integer (${2**53-1}): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.NumberTooLargeError`})}},pM=class extends P{constructor({size:e}){super(`String length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.StringTooLargeError`})}},mM=class extends P{constructor({size:e}){super(`Array length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ArrayTooLargeError`})}},hM=class extends P{constructor({size:e}){super(`Object size exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ObjectTooLargeError`})}},gM=class extends P{constructor({size:e}){super(`Byte string length exceeds maximum (4294967295): ${e}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cbor.ByteStringTooLargeError`})}};function _M(e){if(e===void 0)return{length:1,encode:e=>e.pushUint8(247)};if(e===null)return{length:1,encode:e=>e.pushUint8(246)};if(typeof e==`boolean`)return{length:1,encode:t=>t.pushUint8(e?245:244)};if(typeof e==`number`)return _M.number(e);if(typeof e==`bigint`)throw new uM;if(typeof e==`string`)return _M.string(e);if(Array.isArray(e))return _M.array(e);if(e instanceof Uint8Array)return _M.byteString(e);if(e instanceof ArrayBuffer)return _M.byteString(new Uint8Array(e));if(ArrayBuffer.isView(e))return _M.byteString(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));if(e instanceof Map)return _M.map(e);if(typeof e==`object`)return _M.object(e);throw new dM({token:String(e)})}(function(e){function t(e){if(!Number.isSafeInteger(e)){let t=Math.fround(e);return Number.isNaN(e)||e===t?{length:5,encode(t){t.pushUint8(250),t.dataView.setFloat32(t.position,e,!1),t.position+=4}}:{length:9,encode(t){t.pushUint8(251),t.dataView.setFloat64(t.position,e,!1),t.position+=8}}}if(e>=0){if(e<=23)return{length:1,encode:t=>t.pushUint8(e)};if(e<=255)return{length:2,encode:t=>{t.pushUint8(24),t.pushUint8(e)}};if(e<=65535)return{length:3,encode:t=>{t.pushUint8(25),t.pushUint16(e)}};if(e<=4294967295)return{length:5,encode:t=>{t.pushUint8(26),t.pushUint32(e)}};throw new fM({number:e.toString(10)})}let t=-1-e;if(e>=-24)return{length:1,encode:e=>e.pushUint8(32+t)};if(t<=255)return{length:2,encode:e=>{e.pushUint8(56),e.pushUint8(t)}};if(t<=65535)return{length:3,encode:e=>{e.pushUint8(57),e.pushUint16(t)}};if(t<=4294967295)return{length:5,encode:e=>{e.pushUint8(58),e.pushUint32(t)}};throw new fM({number:e.toString(10)})}e.number=t;function n(e){let t=Sf(e),n=t.length;if(n<=23)return{length:1+n,encode(e){e.pushUint8(96+n),n>0&&e.pushBytes(t)}};if(n<=255)return{length:2+n,encode(e){e.pushUint8(120),e.pushUint8(n),e.pushBytes(t)}};if(n<=65535)return{length:3+n,encode(e){e.pushUint8(121),e.pushUint16(n),e.pushBytes(t)}};if(n<=4294967295)return{length:5+n,encode(e){e.pushUint8(122),e.pushUint32(n),e.pushBytes(t)}};throw new pM({size:n})}e.string=n;function r(t){let n=t.map(t=>e(t)),r=n.reduce((e,t)=>e+t.length,0),i=t.length;if(i<=23)return{length:1+r,encode(e){e.pushUint8(128+i);for(let t of n)t.encode(e)}};if(i<=255)return{length:2+r,encode(e){e.pushUint8(152),e.pushUint8(i);for(let t of n)t.encode(e)}};if(i<=65535)return{length:3+r,encode(e){e.pushUint8(153),e.pushUint16(i);for(let t of n)t.encode(e)}};if(i<=4294967295)return{length:5+r,encode(e){e.pushUint8(154),e.pushUint32(i);for(let t of n)t.encode(e)}};throw new mM({size:i})}e.array=r;function i(e){let t=e.byteLength;if(t<=23)return{length:1+t,encode(n){n.pushUint8(64+t),n.pushBytes(e)}};if(t<=255)return{length:2+t,encode(n){n.pushUint8(88),n.pushUint8(t),n.pushBytes(e)}};if(t<=65535)return{length:3+t,encode(n){n.pushUint8(89),n.pushUint16(t),n.pushBytes(e)}};if(t<=4294967295)return{length:5+t,encode(n){n.pushUint8(90),n.pushUint32(t),n.pushBytes(e)}};throw new gM({size:t})}e.byteString=i;function a(t){let n=Object.keys(t),r=n.map(n=>({key:e(n),value:e(t[n])})),i=r.reduce((e,t)=>e+t.key.length+t.value.length,0),a=n.length;if(a<=23)return{length:1+i,encode(e){e.pushUint8(160+a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=255)return{length:2+i,encode(e){e.pushUint8(184),e.pushUint8(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=65535)return{length:3+i,encode(e){e.pushUint8(185),e.pushUint16(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};if(a<=4294967295)return{length:5+i,encode(e){e.pushUint8(186),e.pushUint32(a);for(let t of r)t.key.encode(e),t.value.encode(e)}};throw new hM({size:a})}e.object=a;function o(t){let n=[];for(let[r,i]of t)n.push({key:e(r),value:e(i)});let r=n.reduce((e,t)=>e+t.key.length+t.value.length,0),i=t.size;if(i<=23)return{length:1+r,encode(e){e.pushUint8(160+i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=255)return{length:2+r,encode(e){e.pushUint8(184),e.pushUint8(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=65535)return{length:3+r,encode(e){e.pushUint8(185),e.pushUint16(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};if(i<=4294967295)return{length:5+r,encode(e){e.pushUint8(186),e.pushUint32(i);for(let t of n)t.key.encode(e),t.value.encode(e)}};throw new hM({size:i})}e.map=o})(_M||={});function vM(e){let t=e.readUint8(),n=t>>5,r=t&31;switch(n){case 0:return vM.readUnsignedInteger(e,r);case 1:return vM.readNegativeInteger(e,r);case 2:return vM.readByteString(e,r);case 3:return vM.readTextString(e,r);case 4:return vM.readArray(e,r);case 5:return vM.readMap(e,r);case 6:throw new sM({tag:r});case 7:return vM.readSimpleOrFloat(e,r);default:throw new iM({majorType:n})}}(function(e){function t(e,t){if(t<24)return t;if(t===24)return e.readUint8();if(t===25)return e.readUint16();if(t===26)return e.readUint32();throw t===27?new oM:new aM({additionalInfo:t})}function n(e,n){return t(e,n)}e.readUnsignedInteger=n;function r(e,n){return-1-t(e,n)}e.readNegativeInteger=r;function i(n,r){if(r===31){let t=[],r=0;for(;;){if(n.inspectUint8()===255){n.readUint8();break}let i=e(n);if(!(i instanceof Uint8Array))throw new cM({type:`byte string`});t.push(i),r+=i.length}let i=new Uint8Array(r),a=0;for(let e of t)i.set(e,a),a+=e.length;return i}let i=t(n,r);return n.readBytes(i)}e.readByteString=i;function a(n,r){if(r===31){let t=[];for(;;){if(n.inspectUint8()===255){n.readUint8();break}let r=e(n);if(typeof r!=`string`)throw new cM({type:`text string`});t.push(r)}return t.join(``)}let i=t(n,r);return jf(n.readBytes(i))}e.readTextString=a;function o(n,r){if(r===31){let t=[];for(;;){if(n.inspectUint8()===255){n.readUint8();break}t.push(e(n))}return t}let i=t(n,r),a=[];for(let t=0;t>15&1,n=e>>10&31,r=e&1023;if(n===0){if(r===0)return t?-0:0;let e=2**-14*(r/1024);return t?-e:e}if(n===31)return r===0?t?-1/0:1/0:NaN;let i=2**(n-15)*(1+r/1024);return t?-i:i}})(vM||={}),Hf(),Uint8Array.from([105,171,180,181,160,222,75,198,42,42,32,31,141,37,186,233]),pp(),Hf(),pp();function yM(e){let{challenge:t,metadata:n,origin:r,publicKey:i,rpId:a,signature:o}=e,{authenticatorData:s,clientDataJSON:c,userVerificationRequired:l}=n,u=xf(s);if(u.length<37||a!==void 0&&!Cf(u.slice(0,32),$h(Jf(a),{as:`Bytes`})))return!1;let d=u[32];if((d&1)!=1||l&&(d&4)!=4||(d&8)!=8&&(d&16)==16)return!1;let f=JSON.parse(c);return f.type!==`webauthn.get`||!f.challenge||qf(rM(f.challenge))!==t||r!==void 0&&!(Array.isArray(r)?r:[r]).includes(f.origin)?!1:eM({hash:!0,payload:vf(u,$h(Sf(c),{as:`Bytes`})),publicKey:i,signature:o})}function bM(e){return yM(e)}Qd(),pp(),gf();var xM=`0x01`,SM=`0x02`,CM=`0x03`,wM=`0x04`,TM=`0x7777777777777777777777777777777777777777777777777777777777777777`;function EM(e){let{signature:t,root:n}=e;return t.type===`keychain`?n?t.userAddress:EM({...e,signature:t.inner}):gg(DM(e))}function DM(e){let{payload:t,signature:n}=e;switch(n.type){case`secp256k1`:return K_({payload:t,signature:n.signature});case`p256`:case`webAuthn`:return n.publicKey;case`keychain`:return DM({payload:t,signature:n.inner})}}function OM(e){let t=e.endsWith(`7777777777777777777777777777777777777777777777777777777777777777`)?I(e,0,-Qf(TM)):e;if(Qf(t)===65){let e=C_(t);return x_(e),{signature:e,type:`secp256k1`}}let n=I(t,0,1),r=I(t,1),i=Qf(r);if(n===xM){if(i!==129)throw new IM({reason:`Invalid P256 signature envelope size: expected 129 bytes, got ${i} bytes`,serialized:t});return{publicKey:{prefix:4,x:ep(I(r,64,96)),y:ep(I(r,96,128))},prehash:tp(I(r,128,129))!==0,signature:{r:ep(I(r,0,32)),s:ep(I(r,32,64))},type:`p256`}}if(n===SM){if(i<128)throw new IM({reason:`Invalid WebAuthn signature envelope size: expected at least 128 bytes, got ${i} bytes`,serialized:t});let e=i-128,n=I(r,0,e),a,o;for(let t=37;t{let n=e.inner;return n.type===`p256`||n.type===`webAuthn`?{keyId:gg(n.publicKey)}:n.type===`secp256k1`&&t?.payload?{keyId:gg(K_({payload:t.payload,signature:n.signature}))}:{}})()}:{},type:n}}function AM(e){if(e.type===`secp256k1`)return{signature:D_(e),type:`secp256k1`};if(e.type===`p256`)return{prehash:e.preHash,publicKey:{prefix:4,x:ep(e.pubKeyX),y:ep(e.pubKeyY)},signature:{r:ep(e.r),s:ep(e.s)},type:`p256`};if(e.type===`webAuthn`){let t=e.webauthnData,n=Qf(t),r,i;for(let e=37;e{if(t.address)return t.address;if(t.publicKey)return gg(t.publicKey)})();if(!r)return!1;let i=kM(e);if(i.type===`secp256k1`)return r?q_({address:r,payload:n,signature:i.signature}):!1;if(i.type===`p256`)return _g(gg(i.publicKey),r)?eM({hash:i.prehash,publicKey:i.publicKey,payload:n,signature:i.signature}):!1;if(i.type===`webAuthn`)return _g(gg(i.publicKey),r)?bM({challenge:Gf(n),metadata:i.metadata,publicKey:i.publicKey,signature:i.signature}):!1;throw new LM(`Unable to verify signature envelope of type "${i.type}".`)}var FM=class extends P{constructor({envelope:e}){super(`Unable to coerce value (\`${mf(e)}\`) to a valid signature envelope.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.CoercionError`})}},IM=class extends P{constructor({reason:e,serialized:t}){super(`Unable to deserialize signature envelope: ${e}`,{metaMessages:[`Serialized: ${t}`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.InvalidSerializedError`})}},LM=class extends P{constructor(){super(...arguments),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureEnvelope.VerificationError`})}};Qd(),pp();function RM(e){return e.startsWith(`tempo`)?zM(e).address:e}function zM(e){if(!e.startsWith(`tempox`))throw new BM({address:e});let t=e.slice(6);return Uf(t,{strict:!0}),{address:mg(t)}}var BM=class extends P{constructor({address:e}){super(`Tempo address "${e}" has an invalid prefix. Expected "tempox".`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TempoAddress.InvalidPrefixError`})}};pp();function VM(e,t={}){if(typeof e.chainId==`string`)return HM(e);let n={...e,address:RM(e.address)};return t.signature?{...n,signature:t.signature}:n}function HM(e){let{address:t,chainId:n,nonce:r}=e,i=AM(e.signature);return{address:t,chainId:Number(n),nonce:BigInt(r),signature:i}}function UM(e){return e.map(e=>HM(e))}function WM(e){let[t,n,r,i]=e,a={address:n,chainId:t===`0x`?0:Number(t),nonce:r===`0x`?0n:BigInt(r)};return i&&(a.signature=OM(i)),VM(a)}function GM(e){let t=[];for(let n of e)t.push(WM(n));return t}function KM(e){let{address:t,chainId:n,nonce:r,signature:i}=e;return{address:t,chainId:F(n),nonce:F(r),signature:NM(i)}}function qM(e){return e.map(e=>KM(e))}function JM(e){let{address:t,chainId:n,nonce:r}=e,i=e.signature?MM(e.signature):void 0;return[n?F(n):`0x`,t,r?F(r):`0x`,...i?[i]:[]]}function YM(e){if(!e||e.length===0)return[];let t=[];for(let n of e)t.push(JM(n));return t}pp();function XM(e,t={}){if(`keyId`in e)return ZM(e);let n=e,r={...n,address:RM(n.address),...n.limits?{limits:n.limits.map(e=>({...e,token:RM(e.token)}))}:{}};return t.signature?{...r,signature:kM(t.signature)}:r}function ZM(e){let{chainId:t,keyId:n,expiry:r=0,limits:i,keyType:a}=e,o=AM(e.signature);return{address:n,chainId:t===`0x`?0n:ep(t),expiry:Number(r),limits:i?.map(e=>({token:e.token,limit:BigInt(e.limit)})),signature:o,type:a}}function QM(e){let[t,n]=e,[r,i,a,o,s]=t,c=(()=>{switch(i){case`0x`:case`0x00`:return`secp256k1`;case`0x01`:return`p256`;case`0x02`:return`webAuthn`;default:throw Error(`Invalid key type: ${i}`)}})(),l={address:a,expiry:o===void 0?void 0:iN(o),type:c,chainId:r===`0x`?0n:ep(r),...o===void 0?{}:{expiry:iN(o)},...s===void 0?{}:{limits:s.map(([e,t])=>({token:e,limit:rN(t)}))}};return n&&(l.signature=OM(n)),XM(l)}function $M(e){let{address:t,chainId:n,expiry:r,limits:i,type:a,signature:o}=e;return{chainId:n===0n?`0x`:F(n),expiry:typeof r==`number`?F(r):null,limits:i?.map(({token:e,limit:t})=>({token:e,limit:F(t)})),keyId:RM(t),signature:NM(o),keyType:a}}function eN(e){let{address:t,chainId:n,expiry:r,limits:i}=e,a=e.signature?MM(e.signature):void 0,o=(()=>{switch(e.type){case`secp256k1`:return`0x`;case`p256`:return`0x01`;case`webAuthn`:return`0x02`;default:throw Error(`Invalid key type: ${e.type}`)}})(),s=i?.map(e=>[e.token,tN(e.limit)]);return[[tN(n),o,t,typeof r==`number`||s?nN(r??0):void 0,s].filter(Boolean),...a?[a]:[]]}function tN(e){return e===0n?`0x`:F(e)}function nN(e){return e===0?`0x`:F(e)}function rN(e){return e===`0x`?0n:BigInt(e)}function iN(e){return e===`0x`?0:tp(e)}pp();var aN=`0x20c0`;function oN(e){if(typeof e==`string`){let t=RM(e);return pg(t),t}return Wf(aN,F(e,{size:18}))}var sN={legacy:`0x0`,eip2930:`0x1`,eip1559:`0x2`,eip4844:`0x3`,eip7702:`0x4`},cN={"0x0":`legacy`,"0x1":`eip2930`,"0x2":`eip1559`,"0x3":`eip4844`,"0x4":`eip7702`};function lN(e,t={}){if(!e)return null;let n=w_(e),r={...e,...n};return r.blockNumber=e.blockNumber?BigInt(e.blockNumber):null,r.data=e.input,r.gas=BigInt(e.gas??0n),r.nonce=BigInt(e.nonce??0n),r.transactionIndex=e.transactionIndex?Number(e.transactionIndex):null,r.value=BigInt(e.value??0n),e.authorizationList&&(r.authorizationList=H_(e.authorizationList)),e.chainId&&(r.chainId=Number(e.chainId)),e.gasPrice&&(r.gasPrice=BigInt(e.gasPrice)),e.maxFeePerBlobGas&&(r.maxFeePerBlobGas=BigInt(e.maxFeePerBlobGas)),e.maxFeePerGas&&(r.maxFeePerGas=BigInt(e.maxFeePerGas)),e.maxPriorityFeePerGas&&(r.maxPriorityFeePerGas=BigInt(e.maxPriorityFeePerGas)),e.type&&(r.type=cN[e.type]??e.type),n&&(r.v=N_(n.yParity)),r}var uN={...sN,tempo:`0x76`},dN={...cN,"0x76":`tempo`};function fN(e,t={}){if(!e)return null;let n=lN(e);return n.type=dN[e.type],e.aaAuthorizationList&&(n.authorizationList=UM(e.aaAuthorizationList),delete n.aaAuthorizationList),e.calls&&(n.calls=e.calls.map(e=>({to:e.to,value:e.value&&e.value!==`0x`?BigInt(e.value):void 0,data:e.input||e.data||`0x`}))),e.feeToken&&(n.feeToken=e.feeToken),e.nonceKey&&(n.nonceKey=BigInt(e.nonceKey)),e.signature&&(n.signature=AM(e.signature)),e.validAfter&&(n.validAfter=Number(e.validAfter)),e.validBefore&&(n.validBefore=Number(e.validBefore)),e.keyAuthorization&&(n.keyAuthorization=ZM(e.keyAuthorization)),e.feePayerSignature&&(n.feePayerSignature=D_(e.feePayerSignature),n.feePayerSignature.v=N_(n.feePayerSignature.yParity)),n}pp();function pN(e){let t={};return e.accessList!==void 0&&(t.accessList=e.accessList),e.authorizationList!==void 0&&(t.authorizationList=W_(e.authorizationList)),e.blobVersionedHashes!==void 0&&(t.blobVersionedHashes=e.blobVersionedHashes),e.blobs!==void 0&&(t.blobs=e.blobs),e.chainId!==void 0&&(t.chainId=F(e.chainId)),e.data===void 0?e.input!==void 0&&(t.data=e.input,t.input=e.input):(t.data=e.data,t.input=e.data),e.from!==void 0&&(t.from=e.from),e.gas!==void 0&&(t.gas=F(e.gas)),e.gasPrice!==void 0&&(t.gasPrice=F(e.gasPrice)),e.maxFeePerBlobGas!==void 0&&(t.maxFeePerBlobGas=F(e.maxFeePerBlobGas)),e.maxFeePerGas!==void 0&&(t.maxFeePerGas=F(e.maxFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(t.maxPriorityFeePerGas=F(e.maxPriorityFeePerGas)),e.maxPriorityFeePerGas!==void 0&&(t.maxPriorityFeePerGas=F(e.maxPriorityFeePerGas)),e.nonce!==void 0&&(t.nonce=F(e.nonce)),e.to!==void 0&&(t.to=e.to),e.type!==void 0&&(t.type=sN[e.type]||e.type),e.value!==void 0&&(t.value=F(e.value)),t}pp();function mN(e){let t=pN({...e,authorizationList:void 0});e.authorizationList&&(t.authorizationList=qM(e.authorizationList)),e.calls&&(t.calls=e.calls.map(e=>({to:e.to?RM(e.to):e.to,value:e.value?F(e.value):`0x`,data:e.data??`0x`}))),e.feeToken!==void 0&&(t.feeToken=oN(e.feeToken)),e.keyAuthorization&&(t.keyAuthorization=$M(e.keyAuthorization)),e.validBefore!==void 0&&(t.validBefore=F(e.validBefore)),e.validAfter!==void 0&&(t.validAfter=F(e.validAfter));let n=(()=>{if(e.nonceKey===`random`)return Zf(6);if(typeof e.nonceKey==`bigint`)return F(e.nonceKey)})();return n&&(t.nonceKey=n),(e.calls!==void 0||e.feeToken!==void 0||e.keyAuthorization!==void 0||e.nonceKey!==void 0||e.validBefore!==void 0||e.validAfter!==void 0||e.type===`tempo`)&&(t.type=uN.tempo,delete t.data,delete t.input,delete t.to,delete t.value),t}Qd(),pp();function hN(e){let t=[];for(let n=0;neg(e)?e:$f(e))})}return t}function gN(e){if(!e||e.length===0)return[];let t=[];for(let{address:n,storageKeys:r}of e){for(let e=0;et===void 0?e:void 0).filter(Boolean);super(`Invalid serialized transaction of type "${n}" was provided.`,{metaMessages:[`Serialized Transaction: "${t}"`,r.length>0?`Missing Attributes: ${r.join(`, `)}`:``].filter(Boolean)}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TransactionEnvelope.InvalidSerializedError`})}},wN=class extends P{constructor({maxPriorityFeePerGas:e,maxFeePerGas:t}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${e?` = ${bN(e)} gwei`:``}) cannot be higher than the fee cap (\`maxFeePerGas\`${t?` = ${bN(t)} gwei`:``}).`].join(` -`)),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TransactionEnvelope.TipAboveFeeCapError`})}};Qd(),pp();var TN=`0x78`,EN=`0x76`,DN=`tempo`;function ON(e){let{calls:t,chainId:n,maxFeePerGas:r,maxPriorityFeePerGas:i,validBefore:a,validAfter:o}=e;if(!t||t.length===0)throw new FN;if(typeof a==`number`&&typeof o==`number`&&a<=o)throw new IN({validBefore:a,validAfter:o});if(t)for(let e of t)e.to&&pg(e.to,{strict:!1});if(n<=0)throw new SN({chainId:n});if(r&&BigInt(r)>2n**256n-1n)throw new xN({feeCap:r});if(i&&r&&i>r)throw new wN({maxFeePerGas:r,maxPriorityFeePerGas:i})}function kN(e){let t=u_(I(e,1)),[n,r,i,a,o,s,c,l,u,d,f,p,m,h,g]=t,_=Array.isArray(h)?h:void 0,v=_?g:h;if(!(t.length===13||t.length===14||t.length===15))throw new CN({attributes:{authorizationList:m,chainId:n,maxPriorityFeePerGas:r,maxFeePerGas:i,gas:a,calls:o,accessList:s,keyAuthorization:_,nonceKey:c,nonce:l,validBefore:u,validAfter:d,feeToken:f,feePayerSignatureOrSender:p,...t.length>12?{signature:v}:{}},serialized:e,type:DN});let y={chainId:Number(n),type:DN};rp(a)&&a!==`0x`&&(y.gas=BigInt(a)),rp(l)&&(y.nonce=l===`0x`?0n:BigInt(l)),rp(i)&&i!==`0x`&&(y.maxFeePerGas=BigInt(i)),rp(r)&&r!==`0x`&&(y.maxPriorityFeePerGas=BigInt(r)),rp(c)&&(y.nonceKey=c===`0x`?0n:BigInt(c)),rp(u)&&u!==`0x`&&(y.validBefore=Number(u)),rp(d)&&d!==`0x`&&(y.validAfter=Number(d)),rp(f)&&f!==`0x`&&(y.feeToken=f),o&&o!==`0x`&&(y.calls=o.map(e=>{let[t,n,r]=e,i={};return t&&t!==`0x`&&(i.to=t),n&&n!==`0x`&&(i.value=BigInt(n)),r&&r!==`0x`&&(i.data=r),i})),s?.length!==0&&s!==`0x`&&(y.accessList=hN(s)),m?.length!==0&&m!==`0x`&&(y.authorizationList=GM(m)),p!==`0x`&&p!==void 0&&(p===`0x00`||vg(p)?(y.feePayerSignature=null,vg(p)&&(y.from=p)):y.feePayerSignature=O_(p)),_&&(y.keyAuthorization=QM(_));let b=v?OM(v):void 0;if(b&&(y={...y,signature:b}),!y.from&&b)try{y.from=EM({payload:MN(AN(y)),signature:b,root:!0})}catch{}return ON(y),y}function AN(e,t={}){let{feePayerSignature:n,signature:r}=t,i=typeof e==`string`?kN(e):e;return i.from&&=RM(i.from),i.calls&&=i.calls.map(e=>({...e,...e.to?{to:RM(e.to)}:{}})),ON(i),{...i,...r?{signature:kM(r)}:{},...n?{feePayerSignature:T_(n)}:{},type:`tempo`}}function jN(e,t={}){let{accessList:n,authorizationList:r,calls:i,chainId:a,feeToken:o,gas:s,keyAuthorization:c,nonce:l,nonceKey:u,maxFeePerGas:d,maxPriorityFeePerGas:f,validBefore:p,validAfter:m}=e;ON(e);let h=gN(n),g=t.signature||e.signature,_=YM(r),v=i.map(e=>[e.to?RM(e.to):`0x`,e.value?F(e.value):`0x`,e.data??`0x`]),y=!1,b=(()=>{if(t.sender)return t.sender;if(t.format===`feePayer`&&g){let t=kM(g);if(t.type===`keychain`)return t.userAddress;if(t.type===`p256`||t.type===`webAuthn`)return gg(t.publicKey);if(t.type===`secp256k1`)return G_({payload:MN(AN(e)),signature:t.signature})}let n=t.feePayerSignature===void 0?e.feePayerSignature:t.feePayerSignature;return n===null?(y=!0,`0x00`):n?j_(n):`0x`})(),x=[F(a),f?F(f):`0x`,d?F(d):`0x`,s?F(s):`0x`,v,h,u?F(u):`0x`,l?F(l):`0x`,typeof p==`number`?F(p):`0x`,typeof m==`number`?F(m):`0x`,!y&&(typeof o==`bigint`||typeof o==`string`)?oN(o):`0x`,b,_,...c?[eN(c)]:[],...g?[MM(kM(g))]:[]];return Wf(t.format===`feePayer`?TN:EN,g_(x))}function MN(e,t={}){let n=NN(e,{presign:!0});return t.from?Qh(Wf(`0x04`,n,RM(t.from))):n}function NN(e,t={}){return Qh(jN({...e,...t.presign?{signature:void 0,...e.feePayerSignature===void 0?{}:{feePayerSignature:null}}:{}}))}function PN(e,t){let n=RM(t.sender);return Qh(jN({...e,signature:void 0},{sender:n,format:`feePayer`}))}var FN=class extends P{constructor(){super(`Calls list cannot be empty.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TxEnvelopeTempo.CallsEmptyError`})}},IN=class extends P{constructor({validBefore:e,validAfter:t}){super(`validBefore (${e}) must be greater than validAfter (${t}).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TxEnvelopeTempo.InvalidValidityWindowError`})}};pp();function LN(e){let t=e.account;return t?.keyType&&t.keyType!==`secp256k1`||e.calls!==void 0||e.feePayer!==void 0||e.feeToken!==void 0||e.keyAuthorization!==void 0||e.nonceKey!==void 0||e.signature!==void 0||e.validBefore!==void 0||e.validAfter!==void 0?`tempo`:e.type?e.type:Nd(e)}function RN(e){try{return LN(e)===`tempo`}catch{return!1}}async function zN(e,t){if(!RN(e)){if(t&&`type`in t&&t.type!==`secp256k1`)throw Error("Unsupported signature type. Expected `secp256k1` but got `"+t.type+"`.");if(t&&`type`in t){let{r:n,s:r,yParity:i}=t?.signature;return mh(e,{r:F(n,{size:32}),s:F(r,{size:32}),yParity:i})}return mh(e,t)}if(LN(e)===`tempo`)return BN(e,t);throw Error(`Unsupported transaction type`)}async function BN(e,t){let n=(()=>{if(e.signature)return e.signature;if(t&&`type`in t)return t;if(t)return kM({r:BigInt(t.r),s:BigInt(t.s),yParity:Number(t.yParity)})})(),{chainId:r,feePayer:i,feePayerSignature:a,nonce:o,...s}=e,c={...s,calls:s.calls?.length?s.calls:[{to:s.to||(!s.data||s.data===`0x`?`0x0000000000000000000000000000000000000000`:void 0),value:s.value,data:s.data}],chainId:Number(r),feePayerSignature:a?{r:BigInt(a.r),s:BigInt(a.s),yParity:Number(a.yParity)}:i?null:void 0,type:`tempo`,...o?{nonce:BigInt(o)}:{}};if(i===!0&&delete c.feeToken,n&&typeof e.feePayer==`object`){let t=AN(c,{signature:n}),r=PN(t,{sender:(()=>{if(e.from)return e.from;if(n.type===`secp256k1`)return G_({payload:MN(t),signature:n.signature});throw Error(`Unable to extract sender from transaction or signature.`)})()});return jN(t,{feePayerSignature:T_(await e.feePayer.sign({hash:r}))})}return i===!0?n?jN(c,{format:`feePayer`,sender:e.from,signature:n}):jN(c,{feePayerSignature:null}):jN({...c,...i?{feeToken:void 0}:{}},{feePayerSignature:void 0,signature:n})}pp(),oa(),Ku();function VN(e){if(!RN(e))return sd(e);let{feePayerSignature:t,gasPrice:n,nonce:r,...i}=fN(e);return{...i,accessList:i.accessList,feePayerSignature:t?{r:F(t.r,{size:32}),s:F(t.s,{size:32}),v:BigInt(t.v??27),yParity:t.yParity}:void 0,nonce:Number(r),typeHex:uN[i.type],type:i.type}}function HN(e){return Gm(e)}function UN(e,t){let n=e,r=n.account?aa(n.account):void 0;if(!RN(n))return Hu(e,t);t&&(n.calls=n.calls??[{to:e.to||(!e.data||e.data===`0x`?`0x0000000000000000000000000000000000000000`:void 0),value:e.value,data:e.data}]),n.feePayer===!0&&delete n.feeToken;let i=mN({...n,type:`tempo`});t===`estimateGas`&&(i.maxFeePerGas=void 0,i.maxPriorityFeePerGas=void 0),i.to=void 0,i.data=void 0,i.value=void 0;let[a,o]=(()=>{let e=r&&`keyType`in r?r.keyType:r?.source;return e?e===`webAuthn`?[`webAuthn`,`0x${`ff`.repeat(1400)}`]:[`p256`,`secp256k1`].includes(e)?[e,void 0]:[void 0,void 0]:[void 0,void 0]})(),s=r&&`accessKeyAddress`in r?r.accessKeyAddress:void 0;return{...i,...o?{keyData:o}:{},...s?{keyId:s}:{},...a?{keyType:a}:{},...n.feePayer?{feePayer:typeof n.feePayer==`object`?aa(n.feePayer):n.feePayer}:{}}}var WN=new Map;async function GN(e){WN.set(e,(WN.get(e)??0)+1),await Promise.resolve();let t=(WN.get(e)??0)>1;return queueMicrotask(()=>{let t=WN.get(e)??0;t<=1?WN.delete(e):WN.set(e,t-1)}),t}$u(),Ku();var KN=25,qN={blockTime:1e3,extendSchema:Dh(),formatters:{transaction:cd({exclude:[`aaAuthorizationList`],format:VN}),transactionReceipt:Km({format:HN}),transactionRequest:Gu({format:UN})},prepareTransactionRequest:[async(e,{phase:t})=>{let n=e;return t===`afterFillParameters`?(n.feePayer&&n.keyAuthorization?.signature.type===`webAuthn`&&(n.gas=(n.gas??0n)+20000n),n):(await(async()=>{if(n.nonceKey===`expiring`||n.feePayer&&n.nonceKey===void 0)return!0;let e=n.account?.address;return e&&n.nonceKey===void 0?await GN(e):!1})()?(n.nonceKey=Qu,n.nonce=0,n.validBefore===void 0&&(n.validBefore=Math.floor(Date.now()/1e3)+KN)):n.nonceKey!==void 0&&(n.nonce=typeof n.nonce==`number`?n.nonce:0),!n.feeToken&&n.chain?.feeToken&&(n.feeToken=n.chain.feeToken),n)},{runAt:[`beforeFillTransaction`,`afterFillParameters`]}],serializers:{transaction:((e,t)=>zN(e,t))},async verifyHash(e,t){let{address:n,hash:r,signature:i}=t;if(typeof i==`string`&&i.endsWith(`7777777777777777777777777777777777777777777777777777777777777777`)){let a=OM(i);if(a.type!==`keychain`){let i=await ih(e,{address:n,blockNumber:t.blockNumber,blockTag:t.blockTag});if(!i||i===`0xef01007702c00000000000000000000000000000000000`)return PM(a,{address:n,payload:r})}}return await T(e,vv,`verifyHash`)({...t,chain:null})}},JN=L({...qN,id:4217,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.tempo.xyz`}},name:`Tempo Mainnet`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.presto.tempo.xyz`],webSocket:[`wss://rpc.presto.tempo.xyz`]}}}),YN=L({...qN,id:42429,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.testnet.tempo.xyz`}},name:`Tempo Testnet (Andantino)`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.testnet.tempo.xyz`],webSocket:[`wss://rpc.testnet.tempo.xyz`]}}}),XN=L({...qN,id:31318,name:`Tempo Devnet`,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.devnet.tempo.xyz`}},nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.devnet.tempoxyz.dev`],webSocket:[`wss://rpc.devnet.tempoxyz.dev`]}}}),ZN=L({...qN,id:1337,name:`Tempo`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`http://localhost:8545`]}}}),QN=L({...qN,id:42431,blockExplorers:{default:{name:`Tempo Explorer`,url:`https://explore.moderato.tempo.xyz`}},name:`Tempo Testnet (Moderato)`,nativeCurrency:{name:`USD`,symbol:`USD`,decimals:6},rpcUrls:{default:{http:[`https://rpc.moderato.tempo.xyz`],webSocket:[`wss://rpc.moderato.tempo.xyz`]}}}),$N=L({id:1559,name:`Tenet`,network:`tenet-mainnet`,nativeCurrency:{name:`TENET`,symbol:`TENET`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tenet.org`]}},blockExplorers:{default:{name:`TenetScan Mainnet`,url:`https://tenetscan.io`,apiUrl:`https://tenetscan.io/api`}},testnet:!1}),eP=L({id:752025,name:`Ternoa`,nativeCurrency:{name:`Capsule Coin`,symbol:`CAPS`,decimals:18},rpcUrls:{default:{http:[`https://rpc-mainnet.zkevm.ternoa.network`]}},blockExplorers:{default:{name:`Ternoa Explorer`,url:`https://explorer-mainnet.zkevm.ternoa.network`}},testnet:!1}),tP=L({id:7,name:`ThaiChain`,nativeCurrency:{name:`TCH`,symbol:`TCH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.thaichain.org`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://exp.thaichain.org`,apiUrl:`https://exp.thaichain.org/api`}},contracts:{multicall3:{address:`0x0DaD6130e832c21719C5CE3bae93454E16A84826`,blockCreated:4806386}},testnet:!1}),nP=L({id:8428,name:`THAT Mainnet`,nativeCurrency:{name:`THAT`,symbol:`THAT`,decimals:18},rpcUrls:{default:{http:[`https://api.thatchain.io/mainnet`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://that.blockscout.com`}},testnet:!1}),rP=L({id:361,name:`Theta Mainnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://explorer.thetatoken.org`}},testnet:!1}),iP=L({id:365,name:`Theta Testnet`,nativeCurrency:{name:`TFUEL`,symbol:`TFUEL`,decimals:18},rpcUrls:{default:{http:[`https://eth-rpc-api-testnet.thetatoken.org/rpc`]}},blockExplorers:{default:{name:`Theta Explorer`,url:`https://testnet-explorer.thetatoken.org`}},testnet:!0}),aP=L({id:108,name:`ThunderCore Mainnet`,nativeCurrency:{name:`TT`,symbol:`TT`,decimals:18},rpcUrls:{default:{http:[`https://mainnet-rpc.thundercore.com`]}},blockExplorers:{default:{name:`ThunderCore Explorer`,url:`https://explorer-mainnet.thundercore.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:0}},testnet:!1}),oP=L({id:997,name:`5ireChain Thunder Testnet`,nativeCurrency:{name:`5ire Token`,symbol:`5IRE`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.5ire.network`]}},blockExplorers:{default:{name:`5ireChain Thunder Explorer`,url:`https://testnet.5irescan.io/`}},testnet:!0}),sP=L({id:62092,name:`TikTrix Testnet`,nativeCurrency:{name:`tTTX`,symbol:`tTTX`,decimals:18},rpcUrls:{default:{http:[`https://tiktrix-rpc.xyz`]}},blockExplorers:{default:{name:`TikTrix Testnet Explorer`,url:`https://tiktrix.xyz`}},testnet:!0}),cP=L({id:6969,name:`Tomb Mainnet`,nativeCurrency:{name:`TOMB`,symbol:`TOMB`,decimals:18},rpcUrls:{default:{http:[`https://rpc.tombchain.com`]}},blockExplorers:{default:{name:`Tomb Explorer`,url:`https://tombscout.com`}},testnet:!1}),lP=L({...xy,id:61166,name:`Treasure`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.treasure.lol`],webSocket:[`wss://rpc.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Block Explorer`,url:`https://treasurescan.io`}},contracts:{multicall3:{address:`0x2e29fe39496a56856D8698bD43e1dF4D0CE6266a`,blockCreated:101}},testnet:!1}),uP=L({...xy,id:978658,name:`Treasure Topaz Testnet`,nativeCurrency:{decimals:18,name:`MAGIC`,symbol:`MAGIC`},rpcUrls:{default:{http:[`https://rpc.topaz.treasure.lol`],webSocket:[`wss://rpc.topaz.treasure.lol/ws`]}},blockExplorers:{default:{name:`Treasure Topaz Block Explorer`,url:`https://topaz.treasurescan.io`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:108112}},testnet:!0}),dP=L({id:728126428,name:`Tron`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://tronscan.org`,apiUrl:`https://apilist.tronscanapi.com/api`}}}),fP=L({id:3448148188,name:`Tron Nile`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://nile.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://nile.tronscan.org`}},testnet:!0}),pP=L({id:2494104990,name:`Tron Shasta`,nativeCurrency:{name:`TRON`,symbol:`TRX`,decimals:6},rpcUrls:{default:{http:[`https://api.shasta.trongrid.io/jsonrpc`]}},blockExplorers:{default:{name:`Tronscan`,url:`https://shasta.tronscan.org`}},testnet:!0}),mP=L({id:8,name:`Ubiq Mainnet`,nativeCurrency:{name:`UBQ`,symbol:`UBQ`,decimals:18},rpcUrls:{default:{http:[`https://pyrus2.ubiqscan.io`]}},blockExplorers:{default:{name:`Ubiq Scan`,url:`https://ubiqscan.io`}},testnet:!1}),hP=L({id:19991,name:`Ultra EVM`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Explorer`,url:`https://evmexplorer.ultra.io`}}}),gP=L({id:18881,name:`Ultra EVM Testnet`,nativeCurrency:{decimals:18,name:`Ultra Token`,symbol:`UOS`},rpcUrls:{default:{http:[`https://evm.test.ultra.eosusa.io`]}},blockExplorers:{default:{name:`Ultra EVM Testnet Explorer`,url:`https://evmexplorer.testnet.ultra.io`}},testnet:!0}),_P=L({id:1231,name:`Ultron Mainnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-rpc.net`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://ulxscan.com`}},testnet:!1}),vP=L({id:1230,name:`Ultron Testnet`,nativeCurrency:{name:`ULX`,symbol:`ULX`,decimals:18},rpcUrls:{default:{http:[`https://ultron-dev.io`]}},blockExplorers:{default:{name:`Ultron Scan`,url:`https://explorer.ultron-dev.io`}},testnet:!0}),yP=1,bP=L({...R,id:130,name:`Unichain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://mainnet.unichain.org/`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://uniscan.xyz`,apiUrl:`https://api.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[yP]:{address:`0x2F12d621a16e2d3285929C9996f478508951dFe4`}},portal:{[yP]:{address:`0x0bd48f6B86a26D3a217d0Fa6FfE2B491B956A7a2`}},l1StandardBridge:{[yP]:{address:`0x81014F44b0a345033bB2b3B21C7a1A308B35fEeA`}}},sourceId:yP}),xP=11155111,SP=L({...R,id:1301,name:`Unichain Sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},blockTime:1e3,rpcUrls:{default:{http:[`https://sepolia.unichain.org`]}},blockExplorers:{default:{name:`Uniscan`,url:`https://sepolia.uniscan.xyz`,apiUrl:`https://api-sepolia.uniscan.xyz/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},portal:{[xP]:{address:`0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD`}},l1StandardBridge:{[xP]:{address:`0xea58fcA6849d79EAd1f26608855c2D6407d54Ce2`}},disputeGameFactory:{[xP]:{address:`0xeff73e5aa3B9AEC32c659Aa3E00444d20a84394b`}}},testnet:!0,sourceId:xP}),CP=L({id:8880,name:`Unique Mainnet`,nativeCurrency:{decimals:18,name:`UNQ`,symbol:`UNQ`},rpcUrls:{default:{http:[`https://rpc.unique.network`]}},blockExplorers:{default:{name:`Unique Subscan`,url:`https://unique.subscan.io/`}}}),wP=L({id:8882,name:`Opal Testnet`,nativeCurrency:{decimals:18,name:`OPL`,symbol:`OPL`},rpcUrls:{default:{http:[`https://rpc-opal.unique.network`]}},blockExplorers:{default:{name:`Opal Subscan`,url:`https://opal.subscan.io/`}},testnet:!0}),TP=L({id:8881,name:`Quartz Mainnet`,nativeCurrency:{decimals:18,name:`QTZ`,symbol:`QTZ`},rpcUrls:{default:{http:[`https://rpc-quartz.unique.network`]}},blockExplorers:{default:{name:`Quartz Subscan`,url:`https://quartz.subscan.io/`}}}),EP=L({id:18233,name:`Unreal`,nativeCurrency:{name:`reETH`,decimals:18,symbol:`reETH`},rpcUrls:{default:{http:[`https://rpc.unreal-orbit.gelato.digital`]}},blockExplorers:{default:{name:`Unreal Explorer`,url:`https://unreal.blockscout.com`,apiUrl:`https://unreal.blockscout.com/api/v2`}},testnet:!0,contracts:{multicall3:{address:`0x8b6B0e60D8CD84898Ea8b981065A12F876eA5677`,blockCreated:1745}}}),DP=L({id:1480,name:`Vana`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.vana.org/`]}},blockExplorers:{default:{name:`Vana Block Explorer`,url:`https://vanascan.io`,apiUrl:`https://vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:716763}}}),OP=L({id:14800,name:`Vana Moksha Testnet`,blockTime:6e3,nativeCurrency:{decimals:18,name:`Vana`,symbol:`VANA`},rpcUrls:{default:{http:[`https://rpc.moksha.vana.org`]}},blockExplorers:{default:{name:`Vana Moksha Testnet`,url:`https://moksha.vanascan.io`,apiUrl:`https://moksha.vanascan.io/api`}},contracts:{multicall3:{address:`0xD8d2dFca27E8797fd779F8547166A2d3B29d360E`,blockCreated:732283}},testnet:!0}),kP=L({id:2040,name:`Vanar Mainnet`,nativeCurrency:{name:`VANRY`,symbol:`VANRY`,decimals:18},rpcUrls:{default:{http:[`https://rpc.vanarchain.com`]}},blockExplorers:{default:{name:`Vanar Mainnet Explorer`,url:`https://explorer.vanarchain.com/`}},testnet:!1}),AP=L({id:100009,name:`Vechain`,nativeCurrency:{name:`VeChain`,symbol:`VET`,decimals:18},rpcUrls:{default:{http:[`https://mainnet.vechain.org`]}},blockExplorers:{default:{name:`Vechain Explorer`,url:`https://explore.vechain.org`},vechainStats:{name:`Vechain Stats`,url:`https://vechainstats.com`}}}),jP=L({id:106,name:`Velas EVM Mainnet`,nativeCurrency:{name:`VLX`,symbol:`VLX`,decimals:18},rpcUrls:{default:{http:[`https://evmexplorer.velas.com/rpc`]}},blockExplorers:{default:{name:`Velas Explorer`,url:`https://evmexplorer.velas.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:55883577}},testnet:!1}),MP=L({id:88,name:`Viction`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://vicscan.xyz`}},testnet:!1}),NP=L({id:89,name:`Viction Testnet`,nativeCurrency:{name:`Viction`,symbol:`VIC`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet.viction.xyz`]}},blockExplorers:{default:{name:`VIC Scan`,url:`https://testnet.vicscan.xyz`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:12170179}},testnet:!0}),PP=L({id:888888,name:`Vision`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org`}},testnet:!1}),FP=L({id:666666,name:`Vision Testnet`,nativeCurrency:{name:`VISION`,symbol:`VS`,decimals:18},rpcUrls:{default:{http:[`https://vpioneer.infragrid.v.network/ethereum/compatible`]}},blockExplorers:{default:{name:`Vision Scan`,url:`https://visionscan.org/?chain=vpioneer`}},testnet:!0}),IP=L({id:888,name:`Wanchain`,nativeCurrency:{name:`WANCHAIN`,symbol:`WAN`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:56891`,`https://gwan2-ssl.wandevs.org`]}},blockExplorers:{default:{name:`WanScan`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0xcDF6A1566e78EB4594c86Fe73Fcdc82429e97fbB`,blockCreated:25312390}}}),LP=L({id:999,name:`Wanchain Testnet`,nativeCurrency:{name:`WANCHAIN`,symbol:`WANt`,decimals:18},rpcUrls:{default:{http:[`https://gwan-ssl.wandevs.org:46891`]}},blockExplorers:{default:{name:`WanScanTest`,url:`https://wanscan.org`}},contracts:{multicall3:{address:`0x11c89bF4496c39FB80535Ffb4c92715839CC5324`,blockCreated:24743448}},testnet:!0}),RP=L({id:9496,name:`WeaveVM Alphanet`,nativeCurrency:{name:`Testnet WeaveVM`,symbol:`tWVM`,decimals:18},rpcUrls:{default:{http:[`https://testnet-rpc.wvm.dev`]}},blockExplorers:{default:{name:`WeaveVM Alphanet Explorer`,url:`https://explorer.wvm.dev`}},testnet:!0}),zP=L({id:1111,name:`WEMIX`,network:`wemix-mainnet`,nativeCurrency:{name:`WEMIX`,symbol:`WEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://explorer.wemix.com`}}}),BP=L({id:1112,name:`WEMIX Testnet`,network:`wemix-testnet`,nativeCurrency:{name:`WEMIX`,symbol:`tWEMIX`,decimals:18},rpcUrls:{default:{http:[`https://api.test.wemix.com`]}},blockExplorers:{default:{name:`wemixExplorer`,url:`https://testnet.wemixscan.com`,apiUrl:`https://testnet.wemixscan.com/api`}},testnet:!0}),VP=L({id:420420421,name:`Westend Asset Hub`,nativeCurrency:{decimals:18,name:`Westies`,symbol:`WND`},rpcUrls:{default:{http:[`https://westend-asset-hub-eth-rpc.polkadot.io`]}},blockExplorers:{default:{name:`subscan`,url:`https://westend-asset-hub-eth-explorer.parity.io`}},testnet:!0}),HP=L({testnet:!1,name:`Whitechain`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://explorer.whitechain.io`}},id:1875,rpcUrls:{default:{http:[`https://rpc.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:25212237}}}),UP=L({testnet:!0,name:`Whitechain Testnet`,blockExplorers:{default:{name:`Whitechain Explorer`,url:`https://testnet.whitechain.io`}},id:2625,rpcUrls:{default:{http:[`https://rpc-testnet.whitechain.io`]}},nativeCurrency:{decimals:18,name:`WhiteBIT Coin`,symbol:`WBT`}}),WP=L({id:42070,name:`WMC Testnet`,nativeCurrency:{name:`WMTx`,symbol:`WMTx`,decimals:18},rpcUrls:{default:{http:[`https://rpc-testnet-base.worldmobile.net`]}},blockExplorers:{default:{name:`WMC Explorer`,url:`https://explorer2-base-testnet.worldmobile.net`}},testnet:!0}),GP=1,KP=L({...R,id:480,name:`World Chain`,network:`worldchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-mainnet.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan`,url:`https://worldscan.org`,apiUrl:`https://api.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-mainnet.explorer.alchemy.com`,apiUrl:`https://worldchain-mainnet.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[GP]:{address:`0x069c4c579671f8c120b1327a73217D01Ea2EC5ea`}},l2OutputOracle:{[GP]:{address:`0x19A6d1E9034596196295CF148509796978343c5D`}},portal:{[GP]:{address:`0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C`}},l1StandardBridge:{[GP]:{address:`0x470458C91978D2d929704489Ad730DC3E3001113`}}},testnet:!1,sourceId:GP}),qP=11155111,JP=L({...R,id:4801,name:`World Chain Sepolia`,network:`worldchain-sepolia`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://worldchain-sepolia.g.alchemy.com/public`]}},blockExplorers:{default:{name:`Worldscan Sepolia`,url:`https://sepolia.worldscan.org`,apiUrl:`https://api-sepolia.worldscan.org/api`},blockscout:{name:`Blockscout`,url:`https://worldchain-sepolia.explorer.alchemy.com`,apiUrl:`https://worldchain-sepolia.explorer.alchemy.com/api`}},contracts:{...R.contracts,multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:0},disputeGameFactory:{[qP]:{address:`0x8Ec1111f67Dad6b6A93B3F42DfBC92D81c98449A`}},l2OutputOracle:{[qP]:{address:`0xc8886f8BAb6Eaeb215aDB5f1c686BF699248300e`}},portal:{[qP]:{address:`0xFf6EBa109271fe6d4237EeeD4bAb1dD9A77dD1A4`}},l1StandardBridge:{[qP]:{address:`0xd7DF54b3989855eb66497301a4aAEc33Dbb3F8DE`}}},testnet:!0,sourceId:qP}),YP=L({id:103,name:`WorldLand Mainnet`,nativeCurrency:{decimals:18,name:`WLC`,symbol:`WLC`},rpcUrls:{default:{http:[`https://seoul.worldland.foundation`]}},blockExplorers:{default:{name:`WorldLand Scan`,url:`https://scan.worldland.foundation`}},testnet:!1}),XP=L({id:660279,name:`Xai Mainnet`,nativeCurrency:{name:`Xai`,symbol:`XAI`,decimals:18},rpcUrls:{default:{http:[`https://xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://explorer.xai-chain.net`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:222549}},testnet:!1}),ZP=L({id:37714555429,name:`Xai Testnet`,nativeCurrency:{name:`sXai`,symbol:`sXAI`,decimals:18},rpcUrls:{default:{http:[`https://testnet-v2.xai-chain.net/rpc`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://testnet-explorer-v2.xai-chain.net`}},testnet:!0}),QP=L({id:50,name:`XDC Network`,nativeCurrency:{decimals:18,name:`XDC`,symbol:`XDC`},rpcUrls:{default:{http:[`https://rpc.xdcrpc.com`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://xdcscan.com`}},contracts:{multicall3:{address:`0x0B1795ccA8E4eC4df02346a082df54D437F8D9aF`,blockCreated:75884020}}}),$P=L({id:51,name:`Apothem Network`,nativeCurrency:{decimals:18,name:`TXDC`,symbol:`TXDC`},rpcUrls:{default:{http:[`https://erpc.apothem.network`]}},blockExplorers:{default:{name:`XDCScan`,url:`https://testnet.xdcscan.com`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:59765389}}}),eF=L({id:1643,name:`XGR Mainnet`,nativeCurrency:{name:`XGR`,symbol:`XGR`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xgr.network`]}},blockExplorers:{default:{name:`XGR Explorer`,url:`https://explorer.xgr.network`}}}),tF=L({id:196,name:`X Layer Mainnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://xlayerrpc.okx.com`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer`,apiUrl:`https://www.oklink.com/api/v5/explorer/xlayer/api`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:47416}}}),nF=L({id:1952,name:`X1 Testnet`,nativeCurrency:{decimals:18,name:`OKB`,symbol:`OKB`},rpcUrls:{default:{http:[`https://xlayertestrpc.okx.com`]}},blockExplorers:{default:{name:`OKLink`,url:`https://www.oklink.com/xlayer-test`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:624344}},testnet:!0}),rF=L({id:3721,name:`Xone Chain Mainnet`,nativeCurrency:{decimals:18,name:`XOC`,symbol:`XOC`},rpcUrls:{default:{http:[`https://rpc.xone.org`]}},blockExplorers:{default:{name:`Xone Mainnet Explorer`,url:`https://xonescan.com`,apiUrl:`http://api.xonescan.com/api`}},testnet:!1}),iF=L({id:33772211,name:`Xone Chain Testnet`,nativeCurrency:{decimals:18,name:`XOC`,symbol:`XOC`},rpcUrls:{default:{http:[`https://rpc-testnet.xone.org`,`https://rpc-testnet.xone.plus`,`https://rpc-testnet.knight.center`]}},blockExplorers:{default:{name:`Xone Testnet Explorer`,url:`https://testnet.xonescan.com`,apiUrl:`http://api.testnet.xonescan.com/api`}},testnet:!0}),aF=L({id:20250217,name:`Xphere Mainnet`,nativeCurrency:{decimals:18,name:`XP`,symbol:`XP`},rpcUrls:{default:{http:[`https://en-bkk.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xp.tamsa.io`}},testnet:!1}),oF=L({id:1998991,name:`Xphere Testnet`,nativeCurrency:{decimals:18,name:`XPT`,symbol:`XPT`},rpcUrls:{default:{http:[`http://testnet.x-phere.com`]}},blockExplorers:{default:{name:`Xphere Tamsa Explorer`,url:`https://xpt.tamsa.io`}},testnet:!0}),sF=L({id:37,name:`CONX Chain`,nativeCurrency:{decimals:18,name:`XPLA`,symbol:`XPLA`},rpcUrls:{default:{http:[`https://dimension-evm-rpc.xpla.dev`]}},blockExplorers:{default:{name:`CONX Explorer`,url:`https://explorer.conx.xyz`}},testnet:!1}),cF=L({id:273,name:`XR One`,nativeCurrency:{decimals:18,name:`XR1`,symbol:`XR1`},rpcUrls:{default:{http:[`https://xr1.calderachain.xyz/http`],webSocket:[`wss://xr1.calderachain.xyz/ws`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr1.calderaexplorer.xyz`}},testnet:!1}),lF=L({id:144e4,name:`XRPL EVM`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xrplevm.org`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.xrplevm.org`,apiUrl:`https://explorer.xrplevm.org/api/v2`}},testnet:!1}),uF=L({id:1440002,name:`XRPL EVM Devnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.xrplevm.org/`]},public:{http:[`https://rpc.xrplevm.org/`]}},blockExplorers:{default:{name:`XRPLEVM Devnet Explorer`,url:`https://explorer.xrplevm.org/`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:15237286}},testnet:!0}),dF=L({id:1449e3,name:`XRPL EVM Testnet`,nativeCurrency:{name:`XRP`,symbol:`XRP`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.xrplevm.org`]}},blockExplorers:{default:{name:`blockscout`,url:`https://explorer.testnet.xrplevm.org`,apiUrl:`https://explorer.testnet.xrplevm.org/api/v2`}},contracts:{multicall3:{address:`0x82Cc144D7d0AD4B1c27cb41420e82b82Ad6e9B31`,blockCreated:492302}},testnet:!0}),fF=L({id:2730,name:`XR Sepolia`,nativeCurrency:{decimals:18,name:`tXR`,symbol:`tXR`},rpcUrls:{default:{http:[`https://xr-sepolia-testnet.rpc.caldera.xyz/http`]}},blockExplorers:{default:{name:`Blockscout`,url:`https://xr-sepolia-testnet.explorer.caldera.xyz`}},testnet:!0}),pF=L({id:50005,name:`Yooldo Verse`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Explorer`,url:`https://explorer.yooldo-verse.xyz`}}}),mF=L({id:50006,name:`Yooldo Verse Testnet`,nativeCurrency:{name:`OAS`,symbol:`OAS`,decimals:18},rpcUrls:{default:{http:[`https://rpc.testnet.yooldo-verse.xyz`]}},blockExplorers:{default:{name:`Yooldo Verse Testnet Explorer`,url:`https://explorer.testnet.yooldo-verse.xyz`}},testnet:!0}),hF=L({id:8408,name:`ZenChain Testnet`,nativeCurrency:{decimals:18,name:`ZTC`,symbol:`ZTC`},rpcUrls:{default:{http:[`https://zenchain-testnet.api.onfinality.io/public`],webSocket:[`wss://zenchain-testnet.api.onfinality.io/public-ws`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:230019}},blockExplorers:{default:{name:`Zentrace`,url:`https://zentrace.io`}},testnet:!0}),gF=L({id:383414847825,name:`Zeniq Mainnet`,nativeCurrency:{name:`ZENIQ`,symbol:`ZENIQ`,decimals:18},rpcUrls:{default:{http:[`https://api.zeniq.network`]}},blockExplorers:{default:{name:`Zeniq Explorer`,url:`https://zeniqscan.com`}},testnet:!1}),_F=L({id:543210,name:`Zero Network`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zerion.io/v1/zero`]}},blockExplorers:{default:{name:`Zero Network Explorer`,url:`https://explorer.zero.network`}},testnet:!1}),vF=L({id:7e3,name:`ZetaChain`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`ZETA`},rpcUrls:{default:{http:[`https://zetachain-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:1632781}},blockExplorers:{default:{name:`ZetaScan`,url:`https://zetascan.com`}},testnet:!1}),yF=L({id:7001,name:`ZetaChain Athens Testnet`,nativeCurrency:{decimals:18,name:`Zeta`,symbol:`aZETA`},rpcUrls:{default:{http:[`https://zetachain-athens-evm.blockpi.network/v1/rpc/public`]}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:2715217}},blockExplorers:{default:{name:`ZetaScan`,url:`https://testnet.zetascan.com`}},testnet:!0}),bF=L({id:1337803,name:`Zhejiang`,nativeCurrency:{name:`Zhejiang Ether`,symbol:`ZhejETH`,decimals:18},rpcUrls:{default:{http:[`https://rpc.zhejiang.ethpandaops.io`]}},blockExplorers:{default:{name:`Beaconchain`,url:`https://zhejiang.beaconcha.in`}},testnet:!0}),xF=L({id:32769,name:`Zilliqa`,network:`zilliqa`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.zilliqa.com`}},testnet:!1}),SF=L({id:33101,name:`Zilliqa Testnet`,network:`zilliqa-testnet`,nativeCurrency:{name:`Zilliqa`,symbol:`ZIL`,decimals:18},rpcUrls:{default:{http:[`https://dev-api.zilliqa.com`]}},blockExplorers:{default:{name:`Ethernal`,url:`https://evmx.testnet.zilliqa.com`}},testnet:!0}),CF=1,wF=L({...R,id:48900,name:`Zircuit Mainnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.zircuit.com`]}},blockExplorers:{default:{name:`Zircuit Explorer`,url:`https://explorer.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[CF]:{address:`0x92Ef6Af472b39F1b363da45E35530c24619245A4`}},portal:{[CF]:{address:`0x17bfAfA932d2e23Bd9B909Fd5B4D2e2a27043fb1`}},l1StandardBridge:{[CF]:{address:`0x386B76D9cA5F5Fb150B6BFB35CF5379B22B26dd8`}}},testnet:!1}),TF=11155111,EF=L({...R,id:48898,name:`Zircuit Garfield Testnet`,nativeCurrency:{name:`ETH`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://garfield-testnet.zircuit.com/`]}},blockExplorers:{default:{name:`Zircuit Garfield Testnet Explorer`,url:`https://explorer.garfield-testnet.zircuit.com`}},contracts:{multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`},l2OutputOracle:{[TF]:{address:`0xd69D3AC5CA686cCF94b258291772bc520FEAf211`}},portal:{[TF]:{address:`0x4E21A71Ac3F7607Da5c06153A17B1DD20E702c21`}},l1StandardBridge:{[TF]:{address:`0x87a7E2bCA9E35BA49282E832a28A6023904460D8`}}},testnet:!0}),DF=L({id:42766,name:`ZKFair Mainnet`,network:`zkfair-mainnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://scan.zkfair.io`,apiUrl:`https://scan.zkfair.io/api`}},contracts:{multicall3:{address:`0xca11bde05977b3631167028862be2a173976ca11`,blockCreated:6090959}},testnet:!1}),OF=L({id:43851,name:`ZKFair Testnet`,network:`zkfair-testnet`,nativeCurrency:{decimals:18,name:`USD Coin`,symbol:`USDC`},rpcUrls:{default:{http:[`https://testnet-rpc.zkfair.io`]}},blockExplorers:{default:{name:`zkFair Explorer`,url:`https://testnet-scan.zkfair.io`}},testnet:!0}),kF=L({id:810180,name:`zkLink Nova`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://explorer.zklink.io`}}}),AF=L({id:810181,name:`zkLink Nova Sepolia Testnet`,nativeCurrency:{decimals:18,name:`ETH`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zklink.io`]}},blockExplorers:{default:{name:`zkLink Nova Block Explorer`,url:`https://sepolia.explorer.zklink.io`}}}),jF=L({...xy,blockTime:200,id:324,name:`ZKsync Era`,network:`zksync-era`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://mainnet.era.zksync.io`],webSocket:[`wss://mainnet.era.zksync.io/ws`]}},blockExplorers:{default:{name:`ZKsync Explorer`,url:`https://explorer.zksync.io/`,apiUrl:`https://block-explorer-api.mainnet.zksync.io/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`,blockCreated:3908235},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:45659388}}}),MF=L({...xy,id:260,name:`ZKsync InMemory Node`,network:`zksync-in-memory-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:8011`]}},testnet:!0}),NF=L({...xy,id:272,name:`ZKsync CLI Local Custom Hyperchain`,nativeCurrency:{name:`BAT`,symbol:`BAT`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15200`],webSocket:[`ws://localhost:15201`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),PF=L({...xy,id:270,name:`ZKsync CLI Local Hyperchain`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15100`],webSocket:[`ws://localhost:15101`]}},blockExplorers:{default:{name:`ZKsync explorer`,url:`http://localhost:15005/`,apiUrl:`http://localhost:15005/api`}},testnet:!0}),FF=L({id:9,name:`ZKsync CLI Local Hyperchain L1`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:15045`]}},blockExplorers:{default:{name:`Blockscout`,url:`http://localhost:15001/`,apiUrl:`http://localhost:15001/api/v2`}},testnet:!0}),IF=L({...xy,id:270,name:`ZKsync CLI Local Node`,network:`zksync-cli-local-node`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`http://localhost:3050`]}},testnet:!0}),LF=L({...xy,blockTime:200,id:300,name:`ZKsync Sepolia Testnet`,network:`zksync-sepolia-testnet`,nativeCurrency:{name:`Ether`,symbol:`ETH`,decimals:18},rpcUrls:{default:{http:[`https://sepolia.era.zksync.dev`],webSocket:[`wss://sepolia.era.zksync.dev/ws`]}},blockExplorers:{default:{name:`ZKsync Explorer`,url:`https://sepolia.explorer.zksync.io/`,blockExplorerApi:`https://block-explorer-api.sepolia.zksync.dev/api`}},contracts:{multicall3:{address:`0xF9cda624FBC7e059355ce98a31693d299FACd963`},erc6492Verifier:{address:`0xfB688330379976DA81eB64Fe4BF50d7401763B9C`,blockCreated:3855712}},testnet:!0}),RF=L({id:375,name:`zkXPLA Mainnet`,network:`zkxpla`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zkxpla.io`]}},blockExplorers:{default:{name:`zkXPLA Mainnet Explorer`,url:`https://explorer.zkxpla.io`,apiUrl:`https://explorer.zkxpla.io/api`}},testnet:!1}),zF=L({id:475,name:`zkXPLA Testnet`,network:`zkxpla-testnet`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet-rpc.zkxpla.io`]}},blockExplorers:{default:{name:`zkXPLA Testnet Explorer`,url:`https://testnet-explorer.zkxpla.io`,apiUrl:`https://testnet-explorer.zkxpla.io/api`}},testnet:!0}),BF=1,VF=L({...R,id:7777777,name:`Zora`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[`https://rpc.zora.energy`],webSocket:[`wss://rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://explorer.zora.energy`,apiUrl:`https://explorer.zora.energy/api`}},contracts:{...R.contracts,disputeGameFactory:{[BF]:{address:`0xB0F15106fa1e473Ddb39790f197275BC979Aa37e`}},l2OutputOracle:{[BF]:{address:`0x9E6204F750cD866b299594e2aC9eA824E2e5f95c`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:5882},portal:{[BF]:{address:`0x1a0ad011913A150f69f6A19DF447A0CfD9551054`}},l1StandardBridge:{[BF]:{address:`0x3e2Ea9B92B7E48A52296fD261dc26fd995284631`}}},sourceId:BF}),HF=11155111,UF=L({...R,id:999999999,name:`Zora Sepolia`,network:`zora-sepolia`,nativeCurrency:{decimals:18,name:`Zora Sepolia`,symbol:`ETH`},rpcUrls:{default:{http:[`https://sepolia.rpc.zora.energy`],webSocket:[`wss://sepolia.rpc.zora.energy`]}},blockExplorers:{default:{name:`Zora Sepolia Explorer`,url:`https://sepolia.explorer.zora.energy/`,apiUrl:`https://sepolia.explorer.zora.energy/api`}},contracts:{...R.contracts,l2OutputOracle:{[HF]:{address:`0x2615B481Bd3E5A1C0C7Ca3Da1bdc663E8615Ade9`}},multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:83160},portal:{[HF]:{address:`0xeffE2C6cA9Ab797D418f0D91eA60807713f3536f`}},l1StandardBridge:{[HF]:{address:`0x5376f1D543dcbB5BD416c56C189e4cB7399fCcCB`}}},sourceId:HF,testnet:!0}),WF=5,GF=L({...R,id:999,name:`Zora Goerli Testnet`,nativeCurrency:{decimals:18,name:`Zora Goerli`,symbol:`ETH`},rpcUrls:{default:{http:[`https://testnet.rpc.zora.energy`],webSocket:[`wss://testnet.rpc.zora.energy`]}},blockExplorers:{default:{name:`Explorer`,url:`https://testnet.explorer.zora.energy`,apiUrl:`https://testnet.explorer.zora.energy/api`}},contracts:{...R.contracts,multicall3:{address:`0xcA11bde05977b3631167028862bE2a173976CA11`,blockCreated:189123},portal:{[WF]:{address:`0xDb9F51790365e7dc196e7D072728df39Be958ACe`}}},sourceId:WF,testnet:!0}),KF=c({abey:()=>ay,abstract:()=>Sy,abstractTestnet:()=>Cy,acala:()=>wy,acria:()=>Ty,adf:()=>Ey,adi:()=>Dy,agungTestnet:()=>Oy,aioz:()=>ky,alephZero:()=>Ay,alephZeroTestnet:()=>jy,alienx:()=>My,alienxHalTestnet:()=>Ny,alpenTestnet:()=>Py,ancient8:()=>Uy,ancient8Sepolia:()=>Gy,anvil:()=>Ky,apeChain:()=>qy,apexTestnet:()=>Jy,apollo:()=>Yy,arbitrum:()=>Xy,arbitrumGoerli:()=>Zy,arbitrumNova:()=>Qy,arbitrumSepolia:()=>$y,arcTestnet:()=>eb,arenaz:()=>tb,areonNetwork:()=>nb,areonNetworkTestnet:()=>rb,areum:()=>ib,artelaTestnet:()=>ab,arthera:()=>ob,artheraTestnet:()=>sb,assetChain:()=>cb,assetChainTestnet:()=>lb,astar:()=>ub,astarZkEVM:()=>db,astarZkyoto:()=>fb,atletaOlympia:()=>pb,aurora:()=>mb,auroraTestnet:()=>hb,auroria:()=>gb,autheoTestnet:()=>_b,avalanche:()=>vb,avalancheFuji:()=>yb,b3:()=>bb,b3Sepolia:()=>xb,bahamut:()=>Sb,base:()=>wb,baseGoerli:()=>Ob,basePreconf:()=>Tb,baseSepolia:()=>Ab,baseSepoliaPreconf:()=>jb,basecampTestnet:()=>Eb,beam:()=>Mb,beamTestnet:()=>Nb,bearNetworkChainMainnet:()=>Pb,bearNetworkChainTestnet:()=>Fb,berachain:()=>Ib,berachainBepolia:()=>Lb,berachainTestnet:()=>Rb,berachainTestnetbArtio:()=>zb,bevmMainnet:()=>Bb,bifrost:()=>Vb,birdlayer:()=>Hb,bitTorrent:()=>Yb,bitTorrentTestnet:()=>Xb,bitgert:()=>Ub,bitkub:()=>Wb,bitkubTestnet:()=>Gb,bitlayer:()=>Kb,bitlayerTestnet:()=>qb,bitrock:()=>Jb,blast:()=>Qb,blastSepolia:()=>$b,bob:()=>tx,bobSepolia:()=>ax,boba:()=>nx,bobaSepolia:()=>rx,boolBetaMainnet:()=>ox,botanix:()=>sx,botanixTestnet:()=>cx,bounceBit:()=>lx,bounceBitTestnet:()=>ux,bronos:()=>dx,bronosTestnet:()=>fx,bsc:()=>px,bscGreenfield:()=>mx,bscTestnet:()=>hx,bsquared:()=>gx,bsquaredTestnet:()=>_x,btr:()=>vx,btrTestnet:()=>yx,bxn:()=>bx,bxnTestnet:()=>xx,cannon:()=>Sx,canto:()=>Cx,celo:()=>Rx,celoAlfajores:()=>Bx,celoSepolia:()=>Hx,chang:()=>Ux,chiliz:()=>Wx,chips:()=>Gx,citrea:()=>Kx,citreaTestnet:()=>qx,classic:()=>Jx,codex:()=>Xx,codexTestnet:()=>Qx,coinbit:()=>$x,coinex:()=>eS,confluxESpace:()=>tS,confluxESpaceTestnet:()=>nS,coreDao:()=>rS,coreTestnet1:()=>iS,coreTestnet2:()=>aS,corn:()=>oS,cornTestnet:()=>sS,cpchain:()=>cS,crab:()=>lS,creatorTestnet:()=>uS,creditCoin3Devnet:()=>dS,creditCoin3Mainnet:()=>fS,creditCoin3Testnet:()=>pS,cronos:()=>mS,cronosTestnet:()=>hS,cronoszkEVM:()=>gS,cronoszkEVMTestnet:()=>_S,crossbell:()=>vS,crossfi:()=>yS,curtis:()=>bS,cyber:()=>xS,cyberTestnet:()=>SS,dailyNetwork:()=>CS,dailyNetworkTestnet:()=>wS,darwinia:()=>TS,datahavenTestnet:()=>ES,dbkchain:()=>DS,dchain:()=>OS,dchainTestnet:()=>kS,defichainEvm:()=>AS,defichainEvmTestnet:()=>jS,degen:()=>MS,dfk:()=>NS,diode:()=>PS,disChain:()=>FS,dodochainTestnet:()=>IS,dogechain:()=>LS,domaTestnet:()=>RS,donatuz:()=>zS,dosChain:()=>BS,dosChainTestnet:()=>VS,dreyerxMainnet:()=>HS,dreyerxTestnet:()=>US,dustboyIoT:()=>WS,dymension:()=>GS,edexa:()=>KS,edexaTestnet:()=>qS,edgeless:()=>JS,edgelessTestnet:()=>YS,edgeware:()=>XS,edgewareTestnet:()=>ZS,eduChain:()=>QS,eduChainTestnet:()=>$S,elastos:()=>eC,elastosTestnet:()=>tC,electroneum:()=>nC,electroneumTestnet:()=>rC,elysiumTestnet:()=>iC,energy:()=>aC,eni:()=>oC,eniTestnet:()=>sC,enuls:()=>cC,eon:()=>lC,eos:()=>uC,eosTestnet:()=>dC,eteria:()=>fC,etherlink:()=>pC,etherlinkShadownetTestnet:()=>mC,etherlinkTestnet:()=>hC,ethernity:()=>gC,etp:()=>_C,evmos:()=>vC,evmosTestnet:()=>yC,excelonMainnet:()=>bC,expanse:()=>xC,exsat:()=>SC,exsatTestnet:()=>CC,fantom:()=>wC,fantomSonicTestnet:()=>TC,fantomTestnet:()=>EC,fibo:()=>DC,filecoin:()=>OC,filecoinCalibration:()=>kC,filecoinHyperspace:()=>AC,fireChain:()=>iy,flame:()=>jC,flare:()=>MC,flareTestnet:()=>NC,flowMainnet:()=>PC,flowPreviewnet:()=>FC,flowTestnet:()=>IC,fluence:()=>LC,fluenceStage:()=>RC,fluenceTestnet:()=>zC,fluentDevnet:()=>BC,fluentTestnet:()=>VC,form:()=>UC,formTestnet:()=>KC,forma:()=>WC,formicarium:()=>lD,forta:()=>qC,foundry:()=>JC,fraxtal:()=>XC,fraxtalTestnet:()=>QC,funkiMainnet:()=>ew,funkiSepolia:()=>nw,fuse:()=>rw,fuseSparknet:()=>iw,fusion:()=>aw,fusionTestnet:()=>ow,garnet:()=>cw,gatechain:()=>lw,geist:()=>uw,genesys:()=>dw,giwaSepolia:()=>pw,giwaSepoliaPreconf:()=>mw,glideL1Protocol:()=>hw,glideL2Protocol:()=>gw,gnosis:()=>_w,gnosisChiado:()=>vw,goChain:()=>xw,goat:()=>yw,gobi:()=>bw,godwoken:()=>Sw,goerli:()=>Cw,graphite:()=>ww,graphiteTestnet:()=>Tw,gravity:()=>Ew,gunz:()=>Dw,guruNetwork:()=>Ow,guruTestnet:()=>kw,ham:()=>Aw,happychainTestnet:()=>jw,haqqMainnet:()=>Mw,haqqTestedge2:()=>Nw,hardhat:()=>Pw,harmonyOne:()=>Fw,hashkey:()=>Iw,hashkeyTestnet:()=>Lw,haustTestnet:()=>Rw,hedera:()=>zw,hederaPreviewnet:()=>Bw,hederaTestnet:()=>Vw,hela:()=>Hw,heliosTestnet:()=>Uw,hemi:()=>Ww,hemiSepolia:()=>Gw,henesys:()=>Kw,holesky:()=>qw,hoodi:()=>Jw,horizenTestnet:()=>Yw,hpb:()=>Xw,hpp:()=>Zw,hppSepolia:()=>Qw,huddle01Mainnet:()=>$w,huddle01Testnet:()=>eT,humanity:()=>tT,humanityTestnet:()=>nT,humanode:()=>rT,humanodeTestnet5:()=>iT,hychain:()=>aT,hychainTestnet:()=>oT,hyperEvm:()=>sT,hyperliquid:()=>sT,hyperliquidEvmTestnet:()=>cT,iSunCoin:()=>ET,icbNetwork:()=>lT,idchain:()=>uT,immutableZkEvm:()=>dT,immutableZkEvmTestnet:()=>fT,inEVM:()=>pT,initVerse:()=>mT,initVerseGenesis:()=>hT,injective:()=>gT,injectiveTestnet:()=>_T,ink:()=>yT,inkSepolia:()=>xT,iota:()=>ST,iotaTestnet:()=>CT,iotex:()=>wT,iotexTestnet:()=>TT,jasmyChain:()=>DT,jasmyChainTestnet:()=>OT,jbc:()=>kT,jbcTestnet:()=>AT,jocMainnet:()=>jT,jocTestnet:()=>MT,jovay:()=>NT,jovaySepolia:()=>PT,juneo:()=>FT,juneoBCH1Chain:()=>IT,juneoDAI1Chain:()=>LT,juneoDOGE1Chain:()=>RT,juneoEUR1Chain:()=>zT,juneoGLD1Chain:()=>BT,juneoLINK1Chain:()=>VT,juneoLTC1Chain:()=>HT,juneoSGD1Chain:()=>WT,juneoSocotraTestnet:()=>GT,juneoUSD1Chain:()=>KT,juneoUSDT1Chain:()=>qT,juneomBTC1Chain:()=>UT,kaia:()=>JT,kairos:()=>YT,kakarotSepolia:()=>XT,kakarotStarknetSepolia:()=>ZT,kardiaChain:()=>QT,karura:()=>$T,katana:()=>eE,kava:()=>tE,kavaTestnet:()=>nE,kcc:()=>rE,kii:()=>iE,kiiTestnetOro:()=>aE,kinto:()=>oE,klaytn:()=>sE,klaytnBaobab:()=>cE,koi:()=>lE,kroma:()=>uE,kromaSepolia:()=>dE,krown:()=>fE,l3x:()=>pE,l3xTestnet:()=>mE,lavita:()=>hE,lens:()=>gE,lensTestnet:()=>_E,lestnet:()=>vE,lightlinkPegasus:()=>yE,lightlinkPhoenix:()=>bE,linea:()=>wE,lineaGoerli:()=>TE,lineaSepolia:()=>EE,lineaTestnet:()=>DE,lisk:()=>kE,liskSepolia:()=>jE,loadAlphanet:()=>ME,localhost:()=>NE,loop:()=>PE,lukso:()=>FE,luksoTestnet:()=>IE,lumiaMainnet:()=>LE,lumiaTestnet:()=>RE,lumoz:()=>zE,lumozTestnet:()=>BE,luxeports:()=>VE,lycan:()=>HE,lyra:()=>UE,mainnet:()=>WE,mandala:()=>GE,manta:()=>KE,mantaSepoliaTestnet:()=>qE,mantaTestnet:()=>JE,mantle:()=>YE,mantleSepoliaTestnet:()=>XE,mantleTestnet:()=>ZE,mantraDuKongEVMTestnet:()=>QE,mantraEVM:()=>$E,mapProtocol:()=>eD,matchain:()=>tD,matchainTestnet:()=>nD,mchVerse:()=>rD,megaeth:()=>iD,megaethTestnet:()=>aD,mekong:()=>oD,meld:()=>sD,memecore:()=>cD,merlin:()=>uD,merlinErigonTestnet:()=>dD,metachain:()=>fD,metachainIstanbul:()=>pD,metadium:()=>mD,metalL2:()=>gD,meter:()=>_D,meterTestnet:()=>vD,metis:()=>yD,metisGoerli:()=>bD,metisSepolia:()=>xD,mev:()=>SD,mevTestnet:()=>CD,mint:()=>wD,mintSepoliaTestnet:()=>TD,mitosisTestnet:()=>ED,mode:()=>OD,modeTestnet:()=>AD,monad:()=>jD,monadTestnet:()=>MD,moonbaseAlpha:()=>ND,moonbeam:()=>PD,moonbeamDev:()=>FD,moonriver:()=>ID,morph:()=>LD,morphHolesky:()=>RD,morphSepolia:()=>zD,nahmii:()=>BD,nautilus:()=>VD,near:()=>HD,nearTestnet:()=>UD,neonDevnet:()=>WD,neonMainnet:()=>GD,neoxMainnet:()=>KD,neoxT4:()=>qD,newton:()=>JD,nexi:()=>YD,nexilix:()=>XD,nibiru:()=>ZD,nitrographTestnet:()=>QD,nomina:()=>$D,oasisTestnet:()=>eO,oasys:()=>tO,odysseyTestnet:()=>nO,okc:()=>rO,omax:()=>iO,omni:()=>aO,omniOmega:()=>oO,oneWorld:()=>sO,oortMainnetDev:()=>cO,opBNB:()=>uO,opBNBTestnet:()=>fO,openledger:()=>pO,optimism:()=>hO,optimismGoerli:()=>_O,optimismSepolia:()=>yO,optopia:()=>bO,optopiaTestnet:()=>xO,orderly:()=>SO,orderlySepolia:()=>CO,otimDevnet:()=>wO,palm:()=>TO,palmTestnet:()=>EO,paseoPassetHub:()=>DO,peaq:()=>OO,pgn:()=>AO,pgnTestnet:()=>MO,phoenix:()=>NO,planq:()=>PO,plasma:()=>FO,plasmaDevnet:()=>IO,plasmaTestnet:()=>LO,playfiAlbireo:()=>RO,plinga:()=>zO,plume:()=>BO,plumeDevnet:()=>VO,plumeMainnet:()=>HO,plumeSepolia:()=>UO,plumeTestnet:()=>WO,polterTestnet:()=>GO,polygon:()=>KO,polygonAmoy:()=>qO,polygonMumbai:()=>JO,polygonZkEvm:()=>YO,polygonZkEvmCardona:()=>XO,polygonZkEvmTestnet:()=>ZO,polynomial:()=>QO,polynomialSepolia:()=>$O,potos:()=>ek,potosTestnet:()=>tk,premiumBlockTestnet:()=>nk,pulsechain:()=>rk,pulsechainV4:()=>ik,pumpfiTestnet:()=>ak,pyrope:()=>sk,qMainnet:()=>lk,qTestnet:()=>uk,ql1:()=>ck,quai:()=>dk,quaiTestnet:()=>fk,reactiveTestnet:()=>pk,real:()=>mk,redbellyMainnet:()=>hk,redbellyTestnet:()=>gk,reddio:()=>_k,reddioSepolia:()=>vk,redstone:()=>bk,rei:()=>xk,reyaNetwork:()=>Sk,rise:()=>Ck,riseTestnet:()=>wk,rivalz:()=>Tk,rollux:()=>Ek,rolluxTestnet:()=>Dk,ronin:()=>Ok,root:()=>kk,rootPorcini:()=>Ak,rootstock:()=>jk,rootstockTestnet:()=>Mk,rss3:()=>Pk,rss3Sepolia:()=>Ik,saakuru:()=>Lk,saga:()=>Rk,saigon:()=>zk,sanko:()=>Bk,sapphire:()=>Vk,sapphireTestnet:()=>Hk,satoshiVM:()=>Uk,satoshiVMTestnet:()=>Wk,scroll:()=>Gk,scrollSepolia:()=>Kk,sei:()=>qk,seiTestnet:()=>Yk,seismicDevnet:()=>Jk,sepolia:()=>Xk,shape:()=>Qk,shapeSepolia:()=>eA,shardeum:()=>tA,shardeumSphinx:()=>nA,shibarium:()=>rA,shibariumTestnet:()=>iA,shiden:()=>aA,shimmer:()=>oA,shimmerTestnet:()=>sA,sidraChain:()=>cA,silentData:()=>lA,silicon:()=>uA,siliconSepolia:()=>dA,sixProtocol:()=>fA,skaleBase:()=>TA,skaleBaseSepoliaTestnet:()=>EA,skaleBlockBrawlers:()=>pA,skaleCalypso:()=>mA,skaleCalypsoTestnet:()=>hA,skaleCryptoBlades:()=>gA,skaleCryptoColosseum:()=>_A,skaleEuropa:()=>vA,skaleEuropaTestnet:()=>yA,skaleExorde:()=>bA,skaleHumanProtocol:()=>xA,skaleNebula:()=>SA,skaleNebulaTestnet:()=>CA,skaleRazor:()=>wA,skaleTitan:()=>DA,skaleTitanTestnet:()=>OA,sketchpad:()=>kA,snax:()=>jA,snaxTestnet:()=>NA,somnia:()=>PA,somniaTestnet:()=>FA,soneium:()=>LA,soneiumMinato:()=>zA,songbird:()=>BA,songbirdTestnet:()=>VA,sonic:()=>HA,sonicBlazeTestnet:()=>UA,sonicTestnet:()=>WA,sophon:()=>GA,sophonTestnet:()=>KA,sova:()=>qA,sovaSepolia:()=>JA,spicy:()=>YA,stable:()=>XA,stableTestnet:()=>ZA,statusNetworkSepolia:()=>QA,statusSepolia:()=>QA,step:()=>$A,story:()=>ej,storyAeneid:()=>tj,storyOdyssey:()=>nj,storyTestnet:()=>rj,stratis:()=>ij,subtensorEvm:()=>aj,superlumio:()=>oj,superposition:()=>sj,superseed:()=>lj,superseedSepolia:()=>dj,surgeTestnet:()=>fj,swan:()=>pj,swanProximaTestnet:()=>mj,swanSaturnTestnet:()=>hj,swellchain:()=>gj,swellchainTestnet:()=>_j,swissdlt:()=>vj,syscoin:()=>yj,syscoinTestnet:()=>bj,tac:()=>xj,tacSPB:()=>Sj,taiko:()=>Cj,taikoHekla:()=>wj,taikoHoodi:()=>Tj,taikoJolnir:()=>Ej,taikoKatla:()=>Dj,taikoTestnetSepolia:()=>Oj,taraxa:()=>kj,taraxaTestnet:()=>Aj,teaSepolia:()=>jj,telcoinTestnet:()=>Mj,telos:()=>Nj,telosTestnet:()=>Pj,tempo:()=>JN,tempoAndantino:()=>YN,tempoDevnet:()=>XN,tempoLocalnet:()=>ZN,tempoModerato:()=>QN,tempoTestnet:()=>YN,tenet:()=>$N,ternoa:()=>eP,thaiChain:()=>tP,that:()=>nP,theta:()=>rP,thetaTestnet:()=>iP,thunderCore:()=>aP,thunderTestnet:()=>oP,tiktrixTestnet:()=>sP,tomb:()=>cP,treasure:()=>lP,treasureTopaz:()=>uP,tron:()=>dP,tronNile:()=>fP,tronShasta:()=>pP,ubiq:()=>mP,ultra:()=>hP,ultraTestnet:()=>gP,ultron:()=>_P,ultronTestnet:()=>vP,unichain:()=>bP,unichainSepolia:()=>SP,unique:()=>CP,uniqueOpal:()=>wP,uniqueQuartz:()=>TP,unreal:()=>EP,vana:()=>DP,vanaMoksha:()=>OP,vanar:()=>kP,vechain:()=>AP,velas:()=>jP,viction:()=>MP,victionTestnet:()=>NP,vision:()=>PP,visionTestnet:()=>FP,wanchain:()=>IP,wanchainTestnet:()=>LP,weaveVMAlphanet:()=>RP,wemix:()=>zP,wemixTestnet:()=>BP,westendAssetHub:()=>VP,whitechain:()=>HP,whitechainTestnet:()=>UP,wmcTestnet:()=>WP,worldLand:()=>YP,worldchain:()=>KP,worldchainSepolia:()=>JP,x1Testnet:()=>nF,xLayer:()=>tF,xLayerTestnet:()=>nF,xai:()=>XP,xaiTestnet:()=>ZP,xdc:()=>QP,xdcTestnet:()=>$P,xgr:()=>eF,xoneMainnet:()=>rF,xoneTestnet:()=>iF,xphereMainnet:()=>aF,xphereTestnet:()=>oF,xpla:()=>sF,xrOne:()=>cF,xrSepolia:()=>fF,xrplevm:()=>lF,xrplevmDevnet:()=>uF,xrplevmTestnet:()=>dF,yooldoVerse:()=>pF,yooldoVerseTestnet:()=>mF,zenchainTestnet:()=>hF,zeniq:()=>gF,zeroG:()=>ey,zeroGGalileoTestnet:()=>ty,zeroGMainnet:()=>ny,zeroGTestnet:()=>ry,zeroNetwork:()=>_F,zetachain:()=>vF,zetachainAthensTestnet:()=>yF,zhejiang:()=>bF,zilliqa:()=>xF,zilliqaTestnet:()=>SF,zircuit:()=>wF,zircuitGarfieldTestnet:()=>EF,zkFair:()=>DF,zkFairTestnet:()=>OF,zkLinkNova:()=>kF,zkLinkNovaSepoliaTestnet:()=>AF,zkSync:()=>jF,zkSyncInMemoryNode:()=>MF,zkSyncLocalNode:()=>IF,zkSyncSepoliaTestnet:()=>LF,zkXPLA:()=>RF,zkXPLATestnet:()=>zF,zksync:()=>jF,zksyncInMemoryNode:()=>MF,zksyncLocalCustomHyperchain:()=>NF,zksyncLocalHyperchain:()=>PF,zksyncLocalHyperchainL1:()=>FF,zksyncLocalNode:()=>IF,zksyncSepoliaTestnet:()=>LF,zora:()=>VF,zoraSepolia:()=>UF,zoraTestnet:()=>GF}),qF=[wb,...Object.values(c({arbitrum:()=>Xy,arbitrumSepolia:()=>$y,base:()=>wb,baseSepolia:()=>Ab,berachain:()=>Ib,berachainBepolia:()=>Lb,bsc:()=>px,celo:()=>Rx,gnosis:()=>_w,hoodi:()=>Jw,katana:()=>eE,mainnet:()=>WE,optimism:()=>hO,optimismSepolia:()=>yO,polygon:()=>KO,sepolia:()=>Xk})).filter(e=>e&&e.id!==wb.id)],JF=Ky;({...JF}),{...JF};var YF=`#__bigint`;function XF(e,t){return JSON.parse(e,(e,n)=>{let r=n;return typeof r==`string`&&r.endsWith(YF)?BigInt(r.slice(0,-9)):typeof t==`function`?t(e,r):r})}function ZF(e,t,n){return JSON.stringify(e,(e,n)=>typeof t==`function`?t(e,n):typeof n==`bigint`?n.toString()+YF:n,n)}var QF=u(s(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=`~`;function i(){}Object.create&&(i.prototype=Object.create(null),new i().__proto__||(r=!1));function a(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function o(e,t,n,i,o){if(typeof n!=`function`)throw TypeError(`The listener must be a function`);var s=new a(n,i||e,o),c=r?r+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],s]:e._events[c].push(s):(e._events[c]=s,e._eventsCount++),e}function s(e,t){--e._eventsCount===0?e._events=new i:delete e._events[t]}function c(){this._events=new i,this._eventsCount=0}c.prototype.eventNames=function(){var e=[],t,i;if(this._eventsCount===0)return e;for(i in t=this._events)n.call(t,i)&&e.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e},c.prototype.listeners=function(e){var t=r?r+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,a=n.length,o=Array(a);i{if(n.cause instanceof e){if(n.cause.details)return n.cause.details;if(n.cause.shortMessage)return n.cause.shortMessage}return n.cause&&`details`in n.cause&&typeof n.cause.details==`string`?n.cause.details:n.cause?.message?n.cause.message:n.details})(),i=n.cause instanceof e&&n.cause.docsPath||n.docsPath,a=n.docsOrigin??e.prototype.docsOrigin,o=`${a}${i??``}`,s=!!(n.version??e.prototype.showVersion),c=n.version??e.prototype.version,l=[t||`An error occurred.`,...n.metaMessages?[``,...n.metaMessages]:[],...r||i||s?[``,r?`Details: ${r}`:void 0,i?`See: ${o}`:void 0,s?`Version: ${c}`:void 0]:[]].filter(e=>typeof e==`string`).join(` -`);super(l,n.cause?{cause:n.cause}:void 0),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docs`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsOrigin`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`docsPath`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`shortMessage`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`showVersion`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`version`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`BaseError`}),this.cause=n.cause,this.details=r,this.docs=o,this.docsOrigin=a,this.docsPath=i,this.shortMessage=t,this.showVersion=s,this.version=c}walk(e){return tI(this,e)}};Object.defineProperty(z,`defaultStaticOptions`,{enumerable:!0,configurable:!0,writable:!0,value:{docsOrigin:`https://oxlib.sh`,showVersion:!1,version:`ox@${eI()}`}}),z.setStaticOptions(z.defaultStaticOptions);function tI(e,t){return t?.(e)?e:e&&typeof e==`object`&&`cause`in e&&e.cause?tI(e.cause,t):t?null:e}function nI(e,t={}){let{raw:n=!1}=t,r=e;if(n)return e;if(r.error)throw rI(r.error);return r.result}function rI(e){let t=e;if(t instanceof Error&&!(`code`in t))return new hI({cause:t,data:t,message:t.message,stack:t.stack});let{code:n}=t;return n===hI.code?new hI(t):n===aI.code?new aI(t):n===mI.code?new mI(t):n===fI.code?new fI(t):n===uI.code?new uI(t):n===pI.code?new pI(t):n===lI.code?new lI(t):n===gI.code?new gI(t):n===oI.code?new oI(t):n===sI.code?new sI(t):n===cI.code?new cI(t):n===dI.code?new dI(t):new hI({cause:t instanceof Error?t:void 0,data:t,message:t.message,stack:t instanceof Error?t.stack:void 0})}var iI=class extends Error{constructor(e){let{cause:t,code:n,message:r,data:i,stack:a}=e;super(r,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.BaseError`}),Object.defineProperty(this,`cause`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`stack`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`data`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=t,this.code=n,this.data=i,this.stack=a??``}},aI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Missing or invalid parameters.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidInputError`})}};Object.defineProperty(aI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32e3});var oI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Requested resource not found.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ResourceNotFoundError`})}};Object.defineProperty(oI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32001});var sI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Requested resource not available.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ResourceUnavailableError`})}};Object.defineProperty(sI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32002});var cI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Transaction creation failed.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.TransactionRejectedError`})}};Object.defineProperty(cI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32003});var lI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Method is not implemented.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.MethodNotSupportedError`})}};Object.defineProperty(lI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32004});var uI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Rate limit exceeded.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.LimitExceededError`})}};Object.defineProperty(uI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32005});var dI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`JSON-RPC version not supported.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.VersionNotSupportedError`})}};Object.defineProperty(dI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32006});var fI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Input is not a valid JSON-RPC request.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidRequestError`})}};Object.defineProperty(fI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32600});var pI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Method does not exist.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.MethodNotFoundError`})}};Object.defineProperty(pI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32601});var mI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Invalid method parameters.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InvalidParamsError`})}};Object.defineProperty(mI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32602});var hI=class e extends iI{constructor(t={}){super({cause:t.cause,code:e.code,data:t.data,message:t.message??`Internal JSON-RPC error.`,stack:t.stack}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.InternalError`})}};Object.defineProperty(hI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32603});var gI=class e extends iI{constructor(t={}){super({code:e.code,data:t.data,message:t.message??`Failed to parse JSON-RPC response.`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`RpcResponse.ParseError`})}};Object.defineProperty(gI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:-32700});var _I=class extends Error{constructor(e,t){super(t),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`ProviderRpcError`}),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,`details`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=e,this.details=t}},vI=class extends _I{constructor({message:e=`The user rejected the request.`}={}){super(4001,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UserRejectedRequestError`})}};Object.defineProperty(vI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4001});var yI=class extends _I{constructor({message:e=`The requested method and/or account has not been authorized by the user.`}={}){super(4100,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnauthorizedError`})}};Object.defineProperty(yI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4100});var bI=class extends _I{constructor({message:e=`The provider does not support the requested method.`}={}){super(4200,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedMethodError`})}};Object.defineProperty(bI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4200});var xI=class extends _I{constructor({message:e=`The provider is disconnected from all chains.`}={}){super(4900,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.DisconnectedError`})}};Object.defineProperty(xI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4900});var SI=class extends _I{constructor({message:e=`The provider is not connected to the requested chain.`}={}){super(4901,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.ChainDisconnectedError`})}};Object.defineProperty(SI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4901});var CI=class extends _I{constructor({message:e=`An error occurred when attempting to switch chain.`}={}){super(4902,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.SwitchChainError`})}};Object.defineProperty(CI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:4902});var wI=class extends _I{constructor({message:e=`This Wallet does not support a capability that was not marked as optional.`}={}){super(5700,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedNonOptionalCapabilityError`})}};Object.defineProperty(wI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5700});var TI=class extends _I{constructor({message:e=`This Wallet does not support the requested chain ID.`}={}){super(5710,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnsupportedChainIdError`})}};Object.defineProperty(TI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5710});var EI=class extends _I{constructor({message:e=`There is already a bundle submitted with this ID.`}={}){super(5720,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.DuplicateIdError`})}};Object.defineProperty(EI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5720});var DI=class extends _I{constructor({message:e=`This bundle id is unknown / has not been submitted.`}={}){super(5730,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.UnknownBundleIdError`})}};Object.defineProperty(DI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5730});var OI=class extends _I{constructor({message:e=`The call bundle is too large for the Wallet to process.`}={}){super(5740,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.BundleTooLargeError`})}};Object.defineProperty(OI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5740});var kI=class extends _I{constructor({message:e=`The Wallet can support atomicity after an upgrade, but the user rejected the upgrade.`}={}){super(5750,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.AtomicReadyWalletRejectedUpgradeError`})}};Object.defineProperty(kI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5750});var AI=class extends _I{constructor({message:e=`The wallet does not support atomic execution but the request requires it.`}={}){super(5760,e),Object.defineProperty(this,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.AtomicityNotSupportedError`})}};Object.defineProperty(AI,`code`,{enumerable:!0,configurable:!0,writable:!0,value:5760});function jI(){let e=new QF.default;return{get eventNames(){return e.eventNames.bind(e)},get listenerCount(){return e.listenerCount.bind(e)},get listeners(){return e.listeners.bind(e)},addListener:e.addListener.bind(e),emit:e.emit.bind(e),off:e.off.bind(e),on:e.on.bind(e),once:e.once.bind(e),removeAllListeners:e.removeAllListeners.bind(e),removeListener:e.removeListener.bind(e)}}function MI(e,t={}){if(!e)throw new PI;return{...e,async request(t){try{let n=await e.request(t);return n&&typeof n==`object`&&`jsonrpc`in n?nI(n):n}catch(e){throw NI(e)}}}}function NI(e){let t=rI(e);if(t instanceof hI){if(!t.data)return t;let{code:e}=t.data;if(e===xI.code)return new xI(t);if(e===SI.code)return new SI(t);if(e===vI.code)return new vI(t);if(e===yI.code)return new yI(t);if(e===bI.code)return new bI(t);if(e===CI.code)return new CI(t);if(e===kI.code)return new kI(t);if(e===AI.code)return new AI(t);if(e===OI.code)return new OI(t);if(e===DI.code)return new DI(t);if(e===EI.code)return new EI(t);if(e===TI.code)return new TI(t);if(e===wI.code)return new wI(t)}return t}var PI=class extends z{constructor(){super("`provider` is undefined."),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Provider.IsUndefinedError`})}};function FI(e,t){if(SL(e)>t)throw new OL({givenSize:SL(e),maxSize:t})}function II(e,t){if(typeof t==`number`&&t>0&&t>SL(e)-1)throw new kL({offset:t,position:`start`,size:SL(e)})}function LI(e,t,n){if(typeof t==`number`&&typeof n==`number`&&SL(e)!==n-t)throw new kL({offset:n,position:`end`,size:SL(e)})}var RI={zero:48,nine:57,A:65,F:70,a:97,f:102};function zI(e){if(e>=RI.zero&&e<=RI.nine)return e-RI.zero;if(e>=RI.A&&e<=RI.F)return e-(RI.A-10);if(e>=RI.a&&e<=RI.f)return e-(RI.a-10)}function BI(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;if(e.length>r)throw new AL({size:e.length,targetSize:r,type:`Bytes`});let i=new Uint8Array(r);for(let t=0;tt)throw new fL({givenSize:iL(e),maxSize:t})}function HI(e,t){if(typeof t==`number`&&t>0&&t>iL(e)-1)throw new pL({offset:t,position:`start`,size:iL(e)})}function UI(e,t,n){if(typeof t==`number`&&typeof n==`number`&&iL(e)!==n-t)throw new pL({offset:n,position:`end`,size:iL(e)})}function WI(e,t={}){let{dir:n,size:r=32}=t;if(r===0)return e;let i=e.replace(`0x`,``);if(i.length>r*2)throw new mL({size:Math.ceil(i.length/2),targetSize:r,type:`Hex`});return`0x${i[n===`right`?`padEnd`:`padStart`](r*2,`0`)}`}function GI(e,t={}){let{dir:n=`left`}=t,r=e.replace(`0x`,``),i=0;for(let e=0;et.toString(16).padStart(2,`0`));function JI(e,t={}){let{strict:n=!1}=t;if(!e||typeof e!=`string`)throw new uL(e);if(n&&!/^0x[0-9a-fA-F]*$/.test(e)||!e.startsWith(`0x`))throw new dL(e)}function YI(...e){return`0x${e.reduce((e,t)=>e+t.replace(`0x`,``),``)}`}function XI(e){return e instanceof Uint8Array?QI(e):Array.isArray(e)?QI(new Uint8Array(e)):e}function ZI(e,t={}){let n=`0x${Number(e)}`;return typeof t.size==`number`?(VI(n,t.size),tL(n,t.size)):n}function QI(e,t={}){let n=``;for(let t=0;ta||i>1n?r:r-a-1n}function sL(e,t={}){let{signed:n,size:r}=t;return Number(!n&&!r?e:oL(e,t))}function cL(e,t={}){let{strict:n=!1}=t;try{return JI(e,{strict:n}),!0}catch{return!1}}var lL=class extends z{constructor({max:e,min:t,signed:n,size:r,value:i}){super(`Number \`${i}\` is not in safe${r?` ${r*8}-bit`:``}${n?` signed`:` unsigned`} integer range ${e?`(\`${t}\` to \`${e}\`)`:`(above \`${t}\`)`}`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.IntegerOutOfRangeError`})}},uL=class extends z{constructor(e){super(`Value \`${typeof e==`object`?ZF(e):e}\` of type \`${typeof e}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexTypeError`})}},dL=class extends z{constructor(e){super(`Value \`${e}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.InvalidHexValueError`})}},fL=class extends z{constructor({givenSize:e,maxSize:t}){super(`Size cannot exceed \`${t}\` bytes. Given size: \`${e}\` bytes.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeOverflowError`})}},pL=class extends z{constructor({offset:e,position:t,size:n}){super(`Slice ${t===`start`?`starting`:`ending`} at offset \`${e}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SliceOffsetOutOfBoundsError`})}},mL=class extends z{constructor({size:e,targetSize:t,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${e}\`) exceeds padding size (\`${t}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Hex.SizeExceedsPaddingSizeError`})}},hL=new TextEncoder;function gL(e){if(!(e instanceof Uint8Array)&&(!e||typeof e!=`object`||!(`BYTES_PER_ELEMENT`in e)||e.BYTES_PER_ELEMENT!==1||e.constructor.name!==`Uint8Array`))throw new DL(e)}function _L(e){return e instanceof Uint8Array?e:typeof e==`string`?yL(e):vL(e)}function vL(e){return e instanceof Uint8Array?e:new Uint8Array(e)}function yL(e,t={}){let{size:n}=t,r=e;n&&(VI(e,n),r=nL(e,n));let i=r.slice(2);i.length%2&&(i=`0${i}`);let a=i.length/2,o=new Uint8Array(a);for(let e=0,t=0;ethis.maxSize){let e=this.keys().next().value;e&&this.delete(e)}return this}}(8192)}.checksum;ti();function ML(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=$r(_L(e));return n===`Bytes`?r:QI(r)}function NL(e,t={}){let{as:n=typeof e==`string`?`Hex`:`Bytes`}=t,r=yd(_L(e));return n===`Bytes`?r:QI(r)}function PL(e,t={}){let{compressed:n}=t,{prefix:r,x:i,y:a}=e;if(n===!1||typeof i==`bigint`&&typeof a==`bigint`){if(r!==4)throw new BL({prefix:r,cause:new HL});return}if(n===!0||typeof i==`bigint`&&a===void 0){if(r!==3&&r!==2)throw new BL({prefix:r,cause:new VL});return}throw new zL({publicKey:e})}function FL(e){let t=(()=>{if(cL(e))return LL(e);if(EL(e))return IL(e);let{prefix:t,x:n,y:r}=e;return typeof n==`bigint`&&typeof r==`bigint`?{prefix:t??4,x:n,y:r}:{prefix:t,x:n}})();return PL(t),t}function IL(e){return LL(QI(e))}function LL(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new UL({publicKey:e});return e.length===130?{prefix:4,x:BigInt(rL(e,0,32)),y:BigInt(rL(e,32,64))}:e.length===132?{prefix:Number(rL(e,0,1)),x:BigInt(rL(e,1,33)),y:BigInt(rL(e,33,65))}:{prefix:Number(rL(e,0,1)),x:BigInt(rL(e,1,33))}}function RL(e,t={}){PL(e);let{prefix:n,x:r,y:i}=e,{includePrefix:a=!0}=t;return YI(a?$I(n,{size:1}):`0x`,$I(r,{size:32}),typeof i==`bigint`?$I(i,{size:32}):`0x`)}var zL=class extends z{constructor({publicKey:e}){super(`Value \`${ZF(e)}\` is not a valid public key.`,{metaMessages:[`Public key must contain:`,"- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidError`})}},BL=class extends z{constructor({prefix:e,cause:t}){super(`Prefix "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidPrefixError`})}},VL=class extends z{constructor(){super(`Prefix must be 2 or 3 for compressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidCompressedPrefixError`})}},HL=class extends z{constructor(){super(`Prefix must be 4 for uncompressed public keys.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidUncompressedPrefixError`})}},UL=class extends z{constructor({publicKey:e}){super(`Value \`${e}\` is an invalid public key size.`,{metaMessages:[`Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).`,`Received ${iL(XI(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`PublicKey.InvalidSerializedSizeError`})}},WL=/^0x[a-fA-F0-9]{40}$/;function GL(e,t={}){let{strict:n=!0}=t;if(!WL.test(e))throw new ZL({address:e,cause:new QL});if(n){if(e.toLowerCase()===e)return;if(KL(e)!==e)throw new ZL({address:e,cause:new $L})}}function KL(e){if(jL.has(e))return jL.get(e);GL(e,{strict:!1});let t=e.substring(2).toLowerCase(),n=ML(bL(t),{as:`Bytes`}),r=t.split(``);for(let e=0;e<40;e+=2)n[e>>1]>>4>=8&&r[e]&&(r[e]=r[e].toUpperCase()),(n[e>>1]&15)>=8&&r[e+1]&&(r[e+1]=r[e+1].toUpperCase());let i=`0x${r.join(``)}`;return jL.set(e,i),i}function qL(e,t={}){let{checksum:n=!1}=t;return GL(e),n?KL(e):e}function JL(e,t={}){return qL(`0x${ML(`0x${RL(e).slice(4)}`).substring(26)}`,t)}function YL(e,t){return GL(e,{strict:!1}),GL(t,{strict:!1}),e.toLowerCase()===t.toLowerCase()}function XL(e,t={}){let{strict:n=!0}=t??{};try{return GL(e,{strict:n}),!0}catch{return!1}}var ZL=class extends z{constructor({address:e,cause:t}){super(`Address "${e}" is invalid.`,{cause:t}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidAddressError`})}},QL=class extends z{constructor(){super(`Address is not a 20 byte (40 hexadecimal character) value.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidInputError`})}},$L=class extends z{constructor(){super(`Address does not match its checksum counterpart.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Address.InvalidChecksumError`})}},eR=!1;uu();function tR(e){let{privateKey:t}=e;return FL(Zl.ProjectivePoint.fromPrivateKey(XI(t).slice(2)))}function nR(e={}){let{as:t=`Hex`}=e,n=Zl.utils.randomPrivateKey();return t===`Hex`?QI(n):n}function rR(e){return JL(iR(e))}function iR(e){let{payload:t,signature:n}=e,{r,s:i,yParity:a}=n;return FL(new Zl.Signature(BigInt(r),BigInt(i)).addRecoveryBit(a).recoverPublicKey(XI(t).substring(2)))}function aR(e){let{extraEntropy:t=eR,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=Zl.sign(_L(r),_L(i),{extraEntropy:typeof t==`boolean`?t:XI(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}var oR=/^(.*)\[([0-9]*)\]$/,sR=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,cR=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;2n**(8n-1n)-1n,2n**(16n-1n)-1n,2n**(24n-1n)-1n,2n**(32n-1n)-1n,2n**(40n-1n)-1n,2n**(48n-1n)-1n,2n**(56n-1n)-1n,2n**(64n-1n)-1n,2n**(72n-1n)-1n,2n**(80n-1n)-1n,2n**(88n-1n)-1n,2n**(96n-1n)-1n,2n**(104n-1n)-1n,2n**(112n-1n)-1n,2n**(120n-1n)-1n,2n**(128n-1n)-1n,2n**(136n-1n)-1n,2n**(144n-1n)-1n,2n**(152n-1n)-1n,2n**(160n-1n)-1n,2n**(168n-1n)-1n,2n**(176n-1n)-1n,2n**(184n-1n)-1n,2n**(192n-1n)-1n,2n**(200n-1n)-1n,2n**(208n-1n)-1n,2n**(216n-1n)-1n,2n**(224n-1n)-1n,2n**(232n-1n)-1n,2n**(240n-1n)-1n,2n**(248n-1n)-1n,2n**(256n-1n)-1n,-(2n**(8n-1n)),-(2n**(16n-1n)),-(2n**(24n-1n)),-(2n**(32n-1n)),-(2n**(40n-1n)),-(2n**(48n-1n)),-(2n**(56n-1n)),-(2n**(64n-1n)),-(2n**(72n-1n)),-(2n**(80n-1n)),-(2n**(88n-1n)),-(2n**(96n-1n)),-(2n**(104n-1n)),-(2n**(112n-1n)),-(2n**(120n-1n)),-(2n**(128n-1n)),-(2n**(136n-1n)),-(2n**(144n-1n)),-(2n**(152n-1n)),-(2n**(160n-1n)),-(2n**(168n-1n)),-(2n**(176n-1n)),-(2n**(184n-1n)),-(2n**(192n-1n)),-(2n**(200n-1n)),-(2n**(208n-1n)),-(2n**(216n-1n)),-(2n**(224n-1n)),-(2n**(232n-1n)),-(2n**(240n-1n)),-(2n**(248n-1n)),-(2n**(256n-1n));var lR=2n**256n-1n;function uR(e,t={}){let{recovered:n}=t;if(e.r===void 0||e.s===void 0||n&&e.yParity===void 0)throw new SR({signature:e});if(e.r<0n||e.r>lR)throw new CR({value:e.r});if(e.s<0n||e.s>lR)throw new wR({value:e.s});if(typeof e.yParity==`number`&&e.yParity!==0&&e.yParity!==1)throw new TR({value:e.yParity})}function dR(e){return fR(QI(e))}function fR(e){if(e.length!==130&&e.length!==132)throw new xR({signature:e});let t=BigInt(rL(e,0,32)),n=BigInt(rL(e,32,64)),r=(()=>{let t=Number(`0x${e.slice(130)}`);if(!Number.isNaN(t))try{return yR(t)}catch{throw new TR({value:t})}})();return r===void 0?{r:t,s:n}:{r:t,s:n,yParity:r}}function pR(e){if(e.r!==void 0&&e.s!==void 0)return mR(e)}function mR(e){let t=typeof e==`string`?fR(e):e instanceof Uint8Array?dR(e):typeof e.r==`string`?gR(e):e.v?hR(e):{r:e.r,s:e.s,...e.yParity===void 0?{}:{yParity:e.yParity}};return uR(t),t}function hR(e){return{r:e.r,s:e.s,yParity:yR(e.v)}}function gR(e){let t=(()=>{let t=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof t==`number`&&typeof n!=`number`&&(n=yR(t)),typeof n!=`number`)throw new TR({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function _R(e){uR(e);let t=e.r,n=e.s;return YI($I(t,{size:32}),$I(n,{size:32}),typeof e.yParity==`number`?$I(bR(e.yParity),{size:1}):`0x`)}function vR(e){let{r:t,s:n,yParity:r}=e;return[r?`0x01`:`0x`,t===0n?`0x`:aL($I(t)),n===0n?`0x`:aL($I(n))]}function yR(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2==0?1:0;throw new ER({value:e})}function bR(e){if(e===0)return 27;if(e===1)return 28;throw new TR({value:e})}var xR=class extends z{constructor({signature:e}){super(`Value \`${e}\` is an invalid signature size.`,{metaMessages:[`Expected: 64 bytes or 65 bytes.`,`Received ${iL(XI(e))} bytes.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSerializedSizeError`})}},SR=class extends z{constructor({signature:e}){super(`Signature \`${ZF(e)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.MissingPropertiesError`})}},CR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidRError`})}},wR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidSError`})}},TR=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidYParityError`})}},ER=class extends z{constructor({value:e}){super(`Value \`${e}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Signature.InvalidVError`})}};function DR({checksumAddress:e,parameters:t,values:n}){let r=[];for(let i=0;i0?YI(t,e):t}}if(o)return{dynamic:!0,encoded:e}}return{dynamic:!1,encoded:YI(...s.map(({encoded:e})=>e))}}function MR(e,{type:t}){let[,n]=t.split(`bytes`),r=iL(e);if(!n){let t=e;return r%32!=0&&(t=nL(t,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:YI(tL($I(r,{size:32})),t)}}if(r!==Number.parseInt(n,10))throw new qR({expectedSize:Number.parseInt(n,10),value:e});return{dynamic:!1,encoded:nL(e)}}function NR(e){if(typeof e!=`boolean`)throw new z(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:tL(ZI(e))}}function PR(e,{signed:t,size:n}){if(typeof n==`number`){let r=2n**(BigInt(n)-(t?1n:0n))-1n,i=t?-r-1n:0n;if(e>r||ee))}}function LR(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}var RR={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:1/0,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new HR({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new VR({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new BR({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new BR({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let n=t??this.position;return this.assertPosition(n+e-1),this.bytes.subarray(n,n+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let n=this.inspectBytes(e);return this.position+=t??e,n},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===1/0)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function zR(e,{recursiveReadLimit:t=8192}={}){let n=Object.create(RR);return n.bytes=e,n.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),n.positionReadCount=new Map,n.recursiveReadLimit=t,n}var BR=class extends z{constructor({offset:e}){super(`Offset \`${e}\` cannot be negative.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.NegativeOffsetError`})}},VR=class extends z{constructor({length:e,position:t}){super(`Position \`${t}\` is out of bounds (\`0 < position < ${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.PositionOutOfBoundsError`})}},HR=class extends z{constructor({count:e,limit:t}){super(`Recursive read limit of \`${t}\` exceeded (recursive read count: \`${e}\`).`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Cursor.RecursiveReadLimitExceededError`})}};kt();function UR(e,t,n){let{checksumAddress:r=!1}=n??{};if(e.length!==t.length)throw new JR({expectedLength:e.length,givenLength:t.length});let i=kR(DR({checksumAddress:r,parameters:e,values:t}));return i.length===0?`0x`:i}function WR(e,t){if(e.length!==t.length)throw new JR({expectedLength:e.length,givenLength:t.length});let n=[];for(let r=0;r{for(let n of e){let{name:e,type:r}=n,o=t[e],s=r.match(cR);if(s&&(typeof o==`number`||typeof o==`bigint`)){let[,e,t]=s;$I(o,{signed:e===`int`,size:Number.parseInt(t??``,10)/8})}if(r===`address`&&typeof o==`string`&&!XL(o))throw new ZL({address:o,cause:new QL});let c=r.match(sR);if(c){let[,e]=c;if(e&&iL(o)!==Number.parseInt(e,10))throw new iz({expectedSize:Number.parseInt(e,10),givenSize:iL(o)})}let l=i[r];l&&(fz(r),a(l,o))}};if(i.EIP712Domain&&t){if(typeof t!=`object`)throw new az({domain:t});a(i.EIP712Domain,t)}if(r!==`EIP712Domain`)if(i[r])a(i[r],n);else throw new oz({primaryType:r,types:i})}function QR(e){let{domain:t={},message:n,primaryType:r}=e,i={EIP712Domain:ez(t),...e.types};ZR({domain:t,message:n,primaryType:r,types:i});let a=[`0x19`,`0x01`];return t&&a.push(nz({domain:t,types:i})),r!==`EIP712Domain`&&a.push(rz({data:n,primaryType:r,types:i})),YI(...a)}function $R(e){let{primaryType:t,types:n}=e,r=``,i=dz({primaryType:t,types:n});i.delete(t);let a=[t,...Array.from(i).sort()];for(let e of a)r+=`${e}(${(n[e]??[]).map(({name:e,type:t})=>`${t} ${e}`).join(`,`)})`;return r}function ez(e){return[typeof e?.name==`string`&&{name:`name`,type:`string`},e?.version&&{name:`version`,type:`string`},(typeof e?.chainId==`number`||typeof e?.chainId==`bigint`)&&{name:`chainId`,type:`uint256`},e?.verifyingContract&&{name:`verifyingContract`,type:`address`},e?.salt&&{name:`salt`,type:`bytes32`}].filter(Boolean)}function tz(e){return ML(QR(e))}function nz(e){let{domain:t,types:n}=e;return rz({data:t,primaryType:`EIP712Domain`,types:{...n,EIP712Domain:n?.EIP712Domain||ez(t)}})}function rz(e){let{data:t,primaryType:n,types:r}=e;return ML(cz({data:t,primaryType:n,types:r}))}var iz=class extends z{constructor({expectedSize:e,givenSize:t}){super(`Expected bytes${e}, got bytes${t}.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.BytesSizeMismatchError`})}},az=class extends z{constructor({domain:e}){super(`Invalid domain "${ZF(e)}".`,{metaMessages:[`Must be a valid EIP-712 domain.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidDomainError`})}},oz=class extends z{constructor({primaryType:e,types:t}){super(`Invalid primary type \`${e}\` must be one of \`${JSON.stringify(Object.keys(t))}\`.`,{metaMessages:["Check that the primary type is a key in `types`."]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidPrimaryTypeError`})}},sz=class extends z{constructor({type:e}){super(`Struct type "${e}" is invalid.`,{metaMessages:[`Struct type must not be a Solidity type.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`TypedData.InvalidStructTypeError`})}};function cz(e){let{data:t,primaryType:n,types:r}=e,i=[{type:`bytes32`}],a=[lz({primaryType:n,types:r})];for(let e of r[n]??[]){let[n,o]=uz({types:r,name:e.name,type:e.type,value:t[e.name]});i.push(n),a.push(o)}return UR(i,a)}function lz(e){let{primaryType:t,types:n}=e;return ML(eL($R({primaryType:t,types:n})))}function uz(e){let{types:t,name:n,type:r,value:i}=e;if(t[r]!==void 0)return[{type:`bytes32`},ML(cz({data:i,primaryType:r,types:t}))];if(r===`bytes`)return i=`0x${(i.length%2?`0`:``)+i.slice(2)}`,[{type:`bytes32`},ML(i,{as:`Hex`})];if(r===`string`)return[{type:`bytes32`},ML(bL(i),{as:`Hex`})];if(r.lastIndexOf(`]`)===r.length-1){let e=r.slice(0,r.lastIndexOf(`[`)),a=i.map(r=>uz({name:n,type:e,types:t,value:r}));return[{type:`bytes32`},ML(UR(a.map(([e])=>e),a.map(([,e])=>e)))]}return[{type:r},i]}function dz(e,t=new Set){let{primaryType:n,types:r}=e,i=n.match(/^\w*/u)?.[0];if(t.has(i)||r[i]===void 0)return t;t.add(i);for(let e of r[i])dz({primaryType:e.type,types:r},t);return t}function fz(e){if(e===`address`||e===`bool`||e===`string`||e.startsWith(`bytes`)||e.startsWith(`uint`)||e.startsWith(`int`))throw new sz({type:e})}gi(),Ei();function pz(e){if(typeof e==`string`){if(!Ci(e,{strict:!1}))throw new hi({address:e});return{address:e,type:`json-rpc`}}if(!Ci(e.address,{strict:!1}))throw new hi({address:e.address});return{address:e.address,nonceManager:e.nonceManager,sign:e.sign,signAuthorization:e.signAuthorization,signMessage:e.signMessage,signTransaction:e.signTransaction,signTypedData:e.signTypedData,source:`custom`,type:`local`}}function mz(e){let t=!0,n=``,r=0,i=``,a=!1;for(let o=0;ohz(Object.values(e)[n],t)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(r)?n===`number`||n===`bigint`:/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(r)?n===`string`||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(r)?Array.isArray(e)&&e.every(e=>hz(e,{...t,type:r.replace(/(\[[0-9]{0,}\])$/,``)})):!1}}function gz(e,t,n){for(let r in e){let i=e[r],a=t[r];if(i.type===`tuple`&&a.type===`tuple`&&`components`in i&&`components`in a)return gz(i.components,a.components,n[r]);let o=[i.type,a.type];if(o.includes(`address`)&&o.includes(`bytes20`)||(o.includes(`address`)&&o.includes(`string`)||o.includes(`address`)&&o.includes(`bytes`))&&XL(n[r],{strict:!1}))return o}}kt();function _z(e,t={}){let{prepare:n=!0}=t,r=Array.isArray(e)||typeof e==`string`?Tt(e):e;return{...r,...n?{hash:xz(r)}:{}}}function vz(e,t,n){let{args:r=[],prepare:i=!0}=n??{},a=cL(t,{strict:!1}),o=e.filter(e=>a?e.type===`function`||e.type===`error`?yz(e)===rL(t,0,4):e.type===`event`?xz(e)===t:!1:`name`in e&&e.name===t);if(o.length===0)throw new Cz({name:t});if(o.length===1)return{...o[0],...i?{hash:xz(o[0])}:{}};let s;for(let e of o)if(`inputs`in e){if(!r||r.length===0){if(!e.inputs||e.inputs.length===0)return{...e,...i?{hash:xz(e)}:{}};continue}if(e.inputs&&e.inputs.length!==0&&e.inputs.length===r.length&&r.every((t,n)=>{let r=`inputs`in e&&e.inputs[n];return r?hz(t,r):!1})){if(s&&`inputs`in s&&s.inputs){let t=gz(e.inputs,s.inputs,r);if(t)throw new Sz({abiItem:e,type:t[0]},{abiItem:s,type:t[1]})}s=e}}let c=(()=>{if(s)return s;let[e,...t]=o;return{...e,overloads:t}})();if(!c)throw new Cz({name:t});return{...c,...i?{hash:xz(c)}:{}}}function yz(...e){return rL(xz((()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})()),0,4)}function bz(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})();return mz(typeof t==`string`?t:ue(t))}function xz(...e){let t=(()=>{if(Array.isArray(e[0])){let[t,n]=e;return vz(t,n)}return e[0]})();return typeof t!=`string`&&`hash`in t&&t.hash?t.hash:ML(eL(bz(t)))}var Sz=class extends z{constructor(e,t){super(`Found ambiguous types in overloaded ABI Items.`,{metaMessages:[`\`${e.type}\` in \`${mz(ue(e.abiItem))}\`, and`,`\`${t.type}\` in \`${mz(ue(t.abiItem))}\``,``,`These types encode differently and cannot be distinguished at runtime.`,`Remove one of the ambiguous items in the ABI.`]}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.AmbiguityError`})}},Cz=class extends z{constructor({name:e,data:t,type:n=`item`}){let r=e?` with name "${e}"`:t?` with data "${t}"`:``;super(`ABI ${n}${r} not found.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`AbiItem.NotFoundError`})}};function wz(...e){let[t,n=[]]=(()=>{if(Array.isArray(e[0])){let[t,n,r]=e;return[Tz(t,n,{args:r}),r]}let[t,n]=e;return[t,n]})(),{overloads:r}=t,i=r?Tz([t,...r],t.name,{args:n}):t,a=Ez(i),o=n.length>0?UR(i.inputs,n):void 0;return o?YI(a,o):a}function Tz(e,t,n){let r=vz(e,t,n);if(r.type!==`function`)throw new Cz({name:t,type:`function`});return r}function Ez(e){return yz(e)}function Dz(e){let{privateKey:t}=e;return FL($j.ProjectivePoint.fromPrivateKey(typeof t==`string`?t.slice(2):QI(t).slice(2)))}function Oz(e={}){let{as:t=`Hex`}=e,n=$j.utils.randomPrivateKey();return t===`Hex`?QI(n):n}function kz(e){let{extraEntropy:t=eR,hash:n,payload:r,privateKey:i}=e,{r:a,s:o,recovery:s}=$j.sign(r instanceof Uint8Array?r:yL(r),i instanceof Uint8Array?i:yL(i),{extraEntropy:typeof t==`boolean`?t:XI(t).slice(2),lowS:!0,...n?{prehash:!0}:{}});return{r:a,s:o,yParity:s}}function Az(e,t=0){if(!/^(-?)([0-9]*)\.?([0-9]*)$/.test(e))throw new jz({value:e});let[n=``,r=`0`]=e.split(`.`),i=n.startsWith(`-`);if(i&&(n=n.slice(1)),r=r.replace(/(0+)$/,``),t===0)Math.round(Number(`.${r}`))===1&&(n=`${BigInt(n)+1n}`),r=``;else if(r.length>t){let[e,i,a]=[r.slice(0,t-1),r.slice(t-1,t),r.slice(t)],o=Math.round(Number(`${i}.${a}`));r=o>9?`${BigInt(e)+BigInt(1)}0`.padStart(e.length+1,`0`):`${e}${o}`,r.length>t&&(r=r.slice(1),n=`${BigInt(n)+1n}`),r=r.slice(0,t)}else r=r.padEnd(t,`0`);return BigInt(`${i?`-`:``}${n}${r}`)}var jz=class extends z{constructor({value:e}){super(`Value \`${e}\` is not a valid decimal number.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Value.InvalidDecimalNumberError`})}},Mz=new TextEncoder,Nz=new TextDecoder,Pz=Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[t,e.charCodeAt(0)])),Fz={...Object.fromEntries(Array.from(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`).map((e,t)=>[e.charCodeAt(0),t])),61:0,45:62,95:63};function Iz(e,t={}){let{pad:n=!0,url:r=!1}=t,i=new Uint8Array(Math.ceil(e.length/3)*4);for(let t=0,n=0;n>18],i[t+1]=Pz[r>>12&63],i[t+2]=Pz[r>>6&63],i[t+3]=Pz[r&63]}let a=e.length%3,o=Math.floor(e.length/3)*4+(a&&a+1),s=Nz.decode(new Uint8Array(i.buffer,0,o));return n&&a===1&&(s+=`==`),n&&a===2&&(s+=`=`),r&&(s=s.replaceAll(`+`,`-`).replaceAll(`/`,`_`)),s}function Lz(e,t={}){return Iz(yL(e),t)}function Rz(e){let t=e.replace(/=+$/,``),n=t.length,r=new Uint8Array(n+3);Mz.encodeInto(t+`===`,r);for(let e=0,n=0;e>16,r[n+1]=t>>8&255,r[n+2]=t&255}let i=(n>>2)*3+(n%4&&n%4-1);return new Uint8Array(r.buffer,0,i)}function zz(e){let t=e[4]===0?5:4,n=t+32,r=e[n+2]===0?n+3:n+2,i=BigInt(QI(e.slice(t,n))),a=BigInt(QI(e.slice(r)));return{r:i,s:a>Qj.CURVE.n/2n?Qj.CURVE.n-a:a}}async function Bz(e){try{let t=e.getPublicKey();if(!t)throw new Yz;let n=new Uint8Array(t),r=await crypto.subtle.importKey(`spki`,new Uint8Array(n),{name:`ECDSA`,namedCurve:`P-256`,hash:`SHA-256`},!0,[`verify`]);return FL(new Uint8Array(await crypto.subtle.exportKey(`raw`,r)))}catch(t){if(t.message!==`Permission denied to access object`)throw t;let n=new Uint8Array(e.attestationObject),r=e=>{let t=new Uint8Array([e,88,32]);for(let e=0;en[e+r]===t))return e+t.length;throw new Yz},i=r(33),a=r(34);return FL(new Uint8Array([4,...n.slice(i,i+32),...n.slice(a,a+32)]))}}var Vz=Uint8Array.from([105,171,180,181,160,222,75,198,42,42,32,31,141,37,186,233]);async function Hz(e){let{createFn:t=window.navigator.credentials.create.bind(window.navigator.credentials),...n}=e,r=Gz(n);try{let e=await t(r);if(!e)throw new Yz;let n=e.response,i=await Bz(n);return{id:e.id,publicKey:i,raw:e}}catch(e){throw new Yz({cause:e})}}function Uz(e={}){let{flag:t=5,rpId:n=window.location.hostname,signCount:r=0}=e;return YI(NL(eL(n)),$I(t,{size:1}),$I(r,{size:4}))}function Wz(e){let{challenge:t,crossOrigin:n=!1,extraClientData:r,origin:i=window.location.origin}=e;return JSON.stringify({type:`webauthn.get`,challenge:Lz(t,{url:!0,pad:!1}),origin:i,crossOrigin:n,...r})}function Gz(e){let{attestation:t=`none`,authenticatorSelection:n={residentKey:`preferred`,requireResidentKey:!1,userVerification:`required`},challenge:r=Vz,excludeCredentialIds:i,extensions:a,name:o,rp:s={id:window.location.hostname,name:window.document.title},user:c}=e,l=c?.name??o;return{publicKey:{attestation:t,authenticatorSelection:n,challenge:typeof r==`string`?yL(r):r,...i?{excludeCredentials:i?.map(e=>({id:Rz(e),type:`public-key`}))}:{},pubKeyCredParams:[{type:`public-key`,alg:-7}],...a&&{extensions:a},rp:s,user:{id:c?.id??ML(bL(l),{as:`Bytes`}),name:l,displayName:c?.displayName??l}}}}function Kz(e){let{credentialId:t,challenge:n,extensions:r,rpId:i=window.location.hostname,userVerification:a=`required`}=e;return{publicKey:{...t?{allowCredentials:Array.isArray(t)?t.map(e=>({id:Rz(e),type:`public-key`})):[{id:Rz(t),type:`public-key`}]}:{},challenge:yL(n),...r&&{extensions:r},rpId:i,userVerification:a}}}function qz(e){let{challenge:t,crossOrigin:n,extraClientData:r,flag:i,origin:a,rpId:o,signCount:s,userVerification:c=`required`}=e,l=Uz({flag:i,rpId:o,signCount:s}),u=Wz({challenge:t,crossOrigin:n,extraClientData:r,origin:a}),d=NL(eL(u));return{metadata:{authenticatorData:l,clientDataJSON:u,challengeIndex:u.indexOf(`"challenge"`),typeIndex:u.indexOf(`"type"`),userVerificationRequired:c===`required`},payload:YI(l,d)}}async function Jz(e){let{getFn:t=window.navigator.credentials.get.bind(window.navigator.credentials),...n}=e,r=Kz(n);try{let e=await t(r);if(!e)throw new Xz;let n=e.response,i=String.fromCharCode(...new Uint8Array(n.clientDataJSON)),a=i.indexOf(`"challenge"`),o=i.indexOf(`"type"`),s=zz(new Uint8Array(n.signature));return{metadata:{authenticatorData:QI(new Uint8Array(n.authenticatorData)),clientDataJSON:i,challengeIndex:a,typeIndex:o,userVerificationRequired:r.publicKey.userVerification===`required`},signature:s,raw:e}}catch(e){throw new Xz({cause:e})}}var Yz=class extends z{constructor({cause:e}={}){super(`Failed to create credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialCreationFailedError`})}},Xz=class extends z{constructor({cause:e}={}){super(`Failed to request credential.`,{cause:e}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`WebAuthnP256.CredentialRequestFailedError`})}};async function Zz(e={}){let{extractable:t=!1}=e,n=await globalThis.crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},t,[`sign`,`verify`]),r=await globalThis.crypto.subtle.exportKey(`raw`,n.publicKey),i=FL(new Uint8Array(r));return{privateKey:n.privateKey,publicKey:i}}async function Qz(e){let{payload:t,privateKey:n}=e,r=await globalThis.crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},n,_L(t)),i=vL(new Uint8Array(r)),a=wL(CL(i,0,32)),o=wL(CL(i,32,64));return o>Qj.CURVE.n/2n&&(o=Qj.CURVE.n-o),{r:a,s:o}}var $z=`0x32323232`,eB={p256:`p256`,secp256k1:`secp256k1`,webauthnp256:`webauthn-p256`},tB={admin:`admin`,normal:`session`},nB={0:`minute`,1:`hour`,2:`day`,3:`week`,4:`month`,5:`year`},rB={address:`secp256k1`,p256:`p256`,secp256k1:`secp256k1`,"webauthn-p256":`webauthnp256`},iB={admin:`admin`,session:`normal`},aB={address:2,p256:0,secp256k1:2,"webauthn-p256":1},oB={day:2,hour:1,minute:0,month:4,week:3,year:5};function sB(e={}){let t=Oz();return fB({...e,privateKey:t})}async function cB(e){let{createFn:t,label:n,rpId:r,userId:i}=e,a=await Hz({authenticatorSelection:{requireResidentKey:!0,residentKey:`required`,userVerification:`required`},createFn:t,extensions:{credProps:!0},rp:r?{id:r,name:r}:void 0,user:{displayName:n,id:new Uint8Array(i??bL(n)),name:n}});return mB({...e,credential:{id:a.id,publicKey:a.publicKey},id:i?TL(i):RL(a.publicKey,{includePrefix:!1})})}function lB(e={}){let t=Oz();return hB({...e,privateKey:t})}async function uB(e={}){let t=await Zz();return gB({...e,keyPair:t})}function dB(e,t={}){let{chainId:n=e.chainId}=t,{expiry:r=0,id:i,prehash:a=!1,role:o=`admin`,type:s}=e,c=(()=>{let t=e.publicKey;return t===`0x`?t:s===`secp256k1`||s===`address`?iL(t)===20||oL(rL(t,0,12))===0n?rL(t,-20):JL(LL(t)):t})();return{...e,chainId:n,expiry:r,hash:_B({publicKey:c,type:s}),id:(i??c).toLowerCase(),prehash:a,publicKey:c.toLowerCase(),role:o,type:s}}function fB(e){let{chainId:t,expiry:n,feeToken:r,permissions:i,privateKey:a,role:o}=e;return dB({chainId:t,expiry:n,feeToken:r,permissions:i,privateKey(){return a},publicKey:RL(Dz({privateKey:a}),{includePrefix:!1}),role:o,type:`p256`})}function pB(e,t){let{chainId:n}=t,{publicKey:r}=e,i=iL(r)===20||oL(rL(r,0,12))===0n,a={};for(let t of e.permissions)t.type===`call`&&(a.calls??=[],a.calls.push({signature:t.selector,to:t.to===`0x3232323232323232323232323232323232323232`?void 0:t.to})),t.type===`spend`&&(a.spend??=[],a.spend.push({limit:t.limit,period:t.period,token:t.token}));return dB({chainId:n,expiry:e.expiry,permissions:a,publicKey:e.publicKey,role:tB[e.role],type:i?`address`:eB[e.type]})}function mB(e){let{credential:t,id:n,rpId:r}=e,i=RL(t.publicKey,{includePrefix:!1});return dB({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,id:n,permissions:e.permissions,privateKey:{credential:t,rpId:r},publicKey:i,role:e.role,type:`webauthn-p256`})}function hB(e){let{privateKey:t}=e,n=RL(Dz({privateKey:t}),{includePrefix:!1});return dB({chainId:e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,permissions:e.permissions,privateKey:{privateKey(){return t}},publicKey:n,role:e.role,type:`webauthn-p256`})}function gB(e){let{chainId:t,expiry:n,feeToken:r,keyPair:i,permissions:a,role:o}=e,{privateKey:s}=i;return dB({chainId:t,expiry:n,feeToken:r,permissions:a,prehash:!0,privateKey:s,publicKey:RL(i.publicKey,{includePrefix:!1}),role:o,type:`p256`})}function _B(e){let{type:t}=e,n=vB(e.publicKey);return ML(UR([{type:`uint8`},{type:`bytes32`}],[aB[t],ML(n)]))}function vB(e){return iL(e)<32?tL(e,32):e}async function yB(e,t){let{address:n,storage:r,webAuthn:i,wrap:a=!0}=t,{privateKey:o,publicKey:s,type:c}=e;if(!o)throw Error(`Key does not have a private key to sign with. - -Key: -`+ZF(e,null,2));let l=n?tz({domain:{verifyingContract:n},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload,[u,d]=await(async()=>{if(c===`p256`){let{privateKey:t}=e;if(typeof t==`function`)return[_R(kz({payload:l,privateKey:t()})),!1];if(t instanceof CryptoKey)return[_R(await Qz({payload:l,privateKey:t})),!0]}if(c===`secp256k1`)return[_R(aR({payload:l,privateKey:o()})),!1];if(c===`webauthn-p256`){if(o.privateKey){let{payload:e,metadata:t}=qz({challenge:l,origin:`https://ithaca.xyz`,rpId:`ithaca.xyz`}),{r:n,s:r}=kz({hash:!0,payload:e,privateKey:o.privateKey()});return[CB({metadata:t,signature:{r:n,s:r}}),!1]}let{credential:t,rpId:n}=o,a=`porto.webauthnVerified.${e.hash}`,s=Date.now(),c=!0;if(r){let e=await r.getItem(a);c=!e||s-e>6e5}let{signature:{r:u,s:d},raw:f,metadata:p}=await Jz({challenge:l,credentialId:t.id,getFn:i?.getFn,rpId:n,userVerification:c?`required`:`preferred`}),m=f.response;if(!m?.userHandle)throw Error(`No user handle in response`,{cause:{response:m}});let h=TL(new Uint8Array(m.userHandle));if(e.id&&XL(e.id)&&!YL(e.id,h))throw Error(`supplied webauthn key "${e.id}" does not match signature webauthn key "${h}"`,{cause:{id:h,key:e}});return c&&r&&await r.setItem(a,s),[CB({metadata:p,signature:{r:u,s:d}}),!1]}throw Error(`Key type "${c}" is not supported.\n\nKey:\n`+ZF(e,null,2))})();return a?wB(u,{keyType:c,prehash:d,publicKey:s}):u}function bB(e,t={}){let{expiry:n=0,prehash:r=!1,publicKey:i,role:a=`admin`,type:o}=e,{feeTokens:s,orchestrator:c}=t,l=Object.entries(xB(e,{feeTokens:s})).map(([e,t])=>{if(e===`calls`)return t.map(({signature:e,to:t})=>({selector:e?cL(e)?e:Ez(e):$z,to:t??`0x3232323232323232323232323232323232323232`,type:`call`}));if(e!==`feeToken`){if(e===`spend`)return t.map(({limit:e,period:t,token:n})=>({limit:e,period:t,token:n,type:`spend`}));throw Error(`Invalid permission type "${e}".`)}}).flat().filter(Boolean);return e.role===`session`&&c&&l.push({selector:$z,to:c,type:`call`}),{expiry:n,permissions:l??[],prehash:r,publicKey:vB(i),role:iB[a],type:rB[o]}}function xB(e,t){let{permissions:n}=e,r=n?.calls?[...n.calls]:[],i=n?.spend?[...n.spend]:[],a=t.feeTokens?.filter(e=>e.feeToken);if(a&&a.length>0){let t=SB(e,{feeTokens:a});if(t){let e=-1,n=oB.year;for(let r=0;re.feeToken.symbol===t.symbol?!0:!e.feeToken.symbol||e.feeToken.symbol===`native`?t.address===dv:!1);if(!r)return;let i=Az(e.feeToken.limit,r.decimals);return{...r,value:i}}function CB(e){let{metadata:t,signature:n}=e;return UR(GR([`struct WebAuthnAuth { bytes authenticatorData; string clientDataJSON; uint256 challengeIndex; uint256 typeIndex; bytes32 r; bytes32 s; }`,`WebAuthnAuth auth`]),[{authenticatorData:t.authenticatorData,challengeIndex:BigInt(t.challengeIndex),clientDataJSON:t.clientDataJSON,r:$I(n.r,{size:32}),s:$I(n.s,{size:32}),typeIndex:BigInt(t.typeIndex)}])}function wB(e,t){let{keyType:n,prehash:r=!1,publicKey:i}=t,a=_B({publicKey:i,type:n});return WR([`bytes`,`bytes32`,`bool`],[e,a,r])}function TB(e){let t=typeof e==`string`?{address:e}:e,n=t.sign?`privateKey`:`porto`,{address:r,sign:i,signMessage:a,signTransaction:o,signTypedData:s,type:c}=pz({address:t.address,sign({hash:e}){if(n===`porto`)throw Error("`sign` not supported on porto accounts.");if(!t.sign)throw Error("`sign` not supported.");return t.sign({hash:e})},signMessage({message:e}){return this.sign({hash:Fh(e)})},signTransaction(){throw Error("`signTransaction` not supported on porto accounts.")},signTypedData(e){return this.sign({hash:Uh(e)})}});return{address:r,keys:t.keys??void 0,sign:i,signMessage:a,signTransaction:o,signTypedData:s,source:n,type:c}}function EB(e,t={}){let{keys:n}=t;return TB({address:JL(tR({privateKey:e})),keys:n,async sign({hash:t}){return _R(aR({payload:t,privateKey:e}))},source:`privateKey`})}function DB(e,t={}){let{key:n,role:r}=t;if(n!==null){if(typeof n==`object`)return n;if(e.keys&&e.keys.length>0)return typeof n==`number`?e.keys[n]:e.keys.find(e=>e.privateKey&&(!r||e.role===r))}}async function OB(e,t){let{storage:n,replaySafe:r=!0,wrap:i=!0,webAuthn:a}=t,o=DB(e,t),s=r?tz({domain:{verifyingContract:e.address},message:{digest:t.payload},primaryType:`ERC1271Sign`,types:{ERC1271Sign:[{name:`digest`,type:`bytes32`}]}}):t.payload,c=o?({hash:e})=>yB(o,{address:null,payload:e,storage:n,webAuthn:a,wrap:i}):e.source===`privateKey`?e.sign:void 0;if(!c)throw Error(`cannot find key to sign with.`);return await c({hash:s})}var kB=()=>`IntersectionObserver`in window&&`IntersectionObserverEntry`in window&&`intersectionRatio`in IntersectionObserverEntry.prototype&&`isVisible`in IntersectionObserverEntry.prototype;function AB(e={}){let{prefix:t=`[Porto]`}=e,n=new Set;return{error:MB(console.error,{prefix:t}),errorOnce:MB(console.error,{memo:n,prefix:t}),log:MB(console.log,{prefix:t}),logOnce:MB(console.log,{memo:n,prefix:t}),warn:MB(console.warn,{prefix:t}),warnOnce:MB(console.warn,{memo:n,prefix:t})}}var jB=AB();function MB(e,t={}){let{memo:n,prefix:r}=t;return(...t)=>{let i=t.join(` `);n?.has(i)||(n?.add(i),e(`${r} ${i}`))}}function NB(){let e=navigator.userAgent.toLowerCase();return e.includes(`safari`)&&!e.includes(`chrome`)}function PB(){let e=navigator.userAgent.toLowerCase();return(e.includes(`firefox`)||e.includes(`fxios`))&&!e.includes(`seamonkey`)}function FB(){return window.navigator?.userAgentData?.mobile?!0:navigator.maxTouchPoints>1||/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(navigator.userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(navigator.userAgent.slice(0,4))}function IB(){let e=()=>void 0,t=()=>void 0;return{promise:new Promise((n,r)=>{e=n,t=r}),reject:t,resolve:e}}function LB(e){if(Array.isArray(e))return e.map(LB);if(typeof e==`function`)return;if(typeof e!=`object`||!e)return e;if(Object.getPrototypeOf(e)!==Object.prototype)try{return structuredClone(e)}catch{return}let t={};for(let[n,r]of Object.entries(e))t[n]=LB(r);return t}function RB(e,t){let n=[],r=new Set;for(let i of e){let e=t(i);r.has(e)||(r.add(e),n.push(i))}return n}function zB(){return typeof globalThis<`u`&&`crypto`in globalThis?globalThis.crypto.randomUUID():crypto.randomUUID()}function BB(e,{enabled:t=!0,id:n}){if(!t||!n)return e();if(BB.cache.get(n))return BB.cache.get(n);let r=e().finally(()=>BB.cache.delete(n));return BB.cache.set(n,r),r}(function(e){e.cache=new Map})(BB||={});function VB(e){return e}function HB(e,t={}){let{targetOrigin:n}=t,r=new Map;return VB({destroy(){for(let t of r.values())e.removeEventListener(`message`,t)},on(t,i,a){function o(e){e.data.topic===t&&(a&&e.data.id!==a||n&&e.origin!==n||i(e.data.payload,e))}return e.addEventListener(`message`,o),r.set(t,o),()=>e.removeEventListener(`message`,o)},async send(t,r,i){let a=zB();return e.postMessage(LB({id:a,payload:r,topic:t}),i??n??`*`),{id:a,payload:r,topic:t}},async sendAsync(e,t,n){let{id:r}=await this.send(e,t,n);return new Promise(t=>this.on(e,t,r))}})}function UB(e){let{from:t,to:n,waitForReady:r=!1}=e,i=!1,a=IB();t.on(`ready`,a.resolve);let o=VB({destroy(){t.destroy(),n.destroy(),i&&a.reject()},on(e,n,r){return t.on(e,n,r)},async send(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.send(e,t)},async sendAsync(e,t){return i=!0,r&&await a.promise.finally(()=>i=!1),n.sendAsync(e,t)}});return{...o,ready(e){o.send(`ready`,e)},waitForReady(){return a.promise}}}var WB={local:`http://localhost:5175/dialog/`,prod:`https://id.porto.sh/dialog`,stg:`https://stg.id.porto.sh/dialog`};function GB(e){return e}function KB(e={}){let{skipProtocolCheck:t,skipUnsupported:n}=e,r=e=>!n&&NB()&&e?.some(e=>[`wallet_connect`,`eth_requestAccounts`].includes(e.method));return typeof window>`u`?JB():GB({name:`iframe`,setup(e){let{host:n,internal:i,theme:a,themeController:o}=e,{store:s}=i,c=qB().setup(e),l=!1,u=new URL(n),d=document.createElement(`dialog`);d.dataset.porto=``,d.setAttribute(`role`,`dialog`),d.setAttribute(`aria-closed`,`true`),d.setAttribute(`aria-label`,`Porto Wallet`),d.setAttribute(`hidden`,`until-found`),Object.assign(d.style,{background:`transparent`,border:`0`,outline:`0`,padding:`0`,position:`fixed`}),document.body.appendChild(d);let f=document.createElement(`iframe`);f.setAttribute(`data-testid`,`porto`);let p=[`payment`,`publickey-credentials-get ${u.origin}`,`publickey-credentials-create ${u.origin}`];PB()||p.push(`clipboard-write`),f.setAttribute(`allow`,p.join(`; `)),f.setAttribute(`tabindex`,`0`),f.setAttribute(`sandbox`,`allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox`),f.setAttribute(`src`,tV(n)),f.setAttribute(`title`,`Porto`),Object.assign(f.style,{backgroundColor:`transparent`,border:`0`,colorScheme:`light dark`,height:`100%`,left:`0`,position:`fixed`,top:`0`,width:`100%`});let m=document.createElement(`style`);m.innerHTML=` - dialog[data-porto]::backdrop { - background: transparent!important; - } - `,d.appendChild(m),d.appendChild(f);let h=UB({from:HB(window,{targetOrigin:u.origin}),to:HB(f.contentWindow,{targetOrigin:u.origin}),waitForReady:!0});o?._setup(h,!0);let g=window.matchMedia(`(max-width: 460px)`),_=()=>{h.send(`__internal`,{type:`resize`,width:g.matches?460:461})};g.addEventListener(`change`,_),h.on(`ready`,t=>{let n=e.internal.store.getState().chainIds.filter(e=>t.chainIds.includes(e));n.length===0&&(n=t.chainIds),s.setState(e=>({...e,chainIds:n})),h.send(`__internal`,{chainIds:n,mode:`iframe`,referrer:ZB(),theme:a,type:`init`}),_()}),h.on(`rpc-response`,e=>{r([e._request])&&(f.src=f.src),$B(s,e)}),h.on(`account`,e=>{eV(s,e)}),h.on(`__internal`,e=>{e.type===`switch`&&e.mode===`popup`&&(c.open(),c.syncRequests(s.getState().requestQueue))});let v=null,y=null,b=()=>QB(s),x=e=>{e.key===`Escape`&&QB(s)},S=new MutationObserver(e=>{for(let t of e){if(t.type!==`attributes`)continue;let e=t.attributeName;e&&e===`inert`&&d.removeAttribute(e)}});S.observe(d,{attributeOldValue:!0,attributes:!0});let ee=!1,C=()=>{ee&&(ee=!1,d.removeEventListener(`click`,b),document.removeEventListener(`keydown`,x),d.style.pointerEvents=`none`,y?.focus(),y=null,Object.assign(document.body.style,v??``),document.body.style.overflow=v?.overflow??``)},te=()=>{ee||(ee=!0,d.addEventListener(`click`,b),document.addEventListener(`keydown`,x),f.focus(),d.style.pointerEvents=`auto`,v=Object.assign({},document.body.style),document.body.style.overflow=`hidden`)},ne=!1,re=()=>{ne||(ne=!0,document.activeElement instanceof HTMLElement&&(y=document.activeElement),d.removeAttribute(`hidden`),d.removeAttribute(`aria-closed`),d.showModal())},ie=()=>{if(ne){ne=!1,d.setAttribute(`hidden`,`true`),d.setAttribute(`aria-closed`,`true`),d.close();for(let e of d.parentNode?Array.from(d.parentNode.children):[])e!==d&&e.hasAttribute(`inert`)&&e.removeAttribute(`inert`)}};return{close(){c.close(),l=!1,h.send(`__internal`,{mode:`iframe`,referrer:ZB(),type:`init`}),ie(),C()},destroy(){c.close(),l=!1,C(),ie(),c.destroy(),h.destroy(),d.remove(),S.disconnect(),g.removeEventListener(`change`,_)},open(){l||(l=!0,re(),te(),h.send(`__internal`,{mode:`iframe`,referrer:ZB(),type:`init`}))},async secure(){let{trustedHosts:e}=await h.waitForReady(),n=(()=>{if(t)return!0;let e=window.location.protocol.startsWith(`https`);return e||jB.warnOnce(`Detected insecure protocol (HTTP).`,`\n\nThe Porto iframe is not supported on HTTP origins (${window.location.origin})`,`due to lack of WebAuthn support.`,`See https://porto.sh/sdk#secure-origins-https for more information.`),e})(),r=kB(),i=!!e?.includes(window.location.hostname),a=!!(r||i);return a||jB.warnOnce([`Warning: Browser does not support IntersectionObserver v2 or host "${u.hostname}" is not trusted by Porto.`,`This may result in the dialog falling back to a popup.`,``,`Add "${u.hostname}" to the trusted hosts list to enable iframe dialog: https://github.com/ithacaxyz/porto/edit/main/src/trusted-hosts.ts`].join(` -`)),{frame:a,host:i,protocol:n}},async syncRequests(e){let{methodPolicies:t}=await h.waitForReady(),n=await this.secure(),i=e?.every(e=>t?.find(t=>t.method===e.request.method)?.modes?.headless===!0),a=r(e.map(e=>e.request));if(!i&&(a||!n.protocol||!n.frame))c.syncRequests(e);else{let n=e.some(e=>XB(e.request,{methodPolicies:t,targetOrigin:u.origin}));!l&&n&&this.open(),h.send(`rpc-requests`,e)}}}},supportsHeadless:!0})}function qB(e={}){if(typeof window>`u`)return JB();let{type:t=`auto`,size:n=YB}=e;return GB({name:`popup`,setup(e){let{host:r,internal:i,themeController:a}=e,{store:o}=i,s=new URL(r),c=null,l=t===`page`||t===`auto`&&FB()?`page`:`popup`;function u(){c&&QB(o)}let d=(()=>{let e=setInterval(()=>{c?.closed&&QB(o)},100);return()=>clearInterval(e)})(),f;return a?._setup(null,!0),{close(){c&&=(c.close(),null)},destroy(){this.close(),window.removeEventListener(`focus`,u),f?.destroy(),d()},open(){if(l===`popup`){let e=(window.innerWidth-n.width)/2+window.screenX,t=window.screenY+100;c=window.open(tV(r),`_blank`,`width=${n.width},height=${n.height},left=${e},top=${t}`)}else c=window.open(tV(r),`_blank`);if(!c)throw Error(`Failed to open popup`);f=UB({from:HB(window,{targetOrigin:s.origin}),to:HB(c,{targetOrigin:s.origin}),waitForReady:!0}),a?._setup(f,!1),f.send(`__internal`,{mode:l===`page`?`page`:`popup`,referrer:ZB(),theme:a?.getTheme()??e.theme,type:`init`}),f.on(`rpc-response`,e=>$B(o,e)),f.on(`account`,e=>{eV(o,e)}),window.removeEventListener(`focus`,u),window.addEventListener(`focus`,u)},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(e){e.some(e=>XB(e.request))&&((!c||c.closed)&&this.open(),c?.focus()),f?.send(`rpc-requests`,e)}}},supportsHeadless:!1})}function JB(){return GB({name:`noop`,setup(){return{close(){},destroy(){},open(){},async secure(){return{frame:!0,host:!0,protocol:!0}},async syncRequests(){}}},supportsHeadless:!0})}var YB={height:282,width:360};function XB(e,t={}){let{methodPolicies:n,targetOrigin:r}=t,i=n?.find(t=>t.method===e.method);return i&&i.modes?.headless?!!(typeof i.modes.headless==`object`&&i.modes.headless.sameOrigin&&r!==window.location.origin):!0}function ZB(){return{icon:(()=>{let e=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: dark)"]`)?.href,t=document.querySelector(`link[rel="icon"][media="(prefers-color-scheme: light)"]`)?.href??document.querySelector(`link[rel="icon"]`)?.href;return e&&t&&e!==t?{dark:e,light:t}:window.matchMedia(`(prefers-color-scheme: dark)`).matches?e:t})(),title:document.title}}function QB(e){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>({account:e.account,error:new vI,request:e.request,status:`error`}))}))}function $B(e,t){e.setState(e=>({...e,requestQueue:e.requestQueue.map(e=>e.request.id===t.id?t.error?{account:e.account,error:t.error,request:e.request,status:`error`}:{account:e.account,request:e.request,result:t.result,status:`success`}:e)}))}function eV(e,t){let{account:n}=t;e.setState(e=>({...e,accounts:[TB({address:n.address,keys:n.capabilities?.admins?.map(e=>dB(e))??[]})]}))}function tV(e){let t=new URL(e),n=new URLSearchParams(window.location.search);for(let[e,r]of n.entries())e.startsWith(`porto.`)&&t.searchParams.set(e.slice(6),r);return t.toString()}function nV(e){let t=new CustomEvent(`eip6963:announceProvider`,{detail:Object.freeze(e)});window.dispatchEvent(t);let n=()=>window.dispatchEvent(t);return window.addEventListener(`eip6963:requestProvider`,n),()=>window.removeEventListener(`eip6963:requestProvider`,n)}Object.freeze({status:`aborted`});function B(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;en?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var rV=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}},iV={};function aV(e){return e&&Object.assign(iV,e),iV}function oV(e,t){return typeof t==`bigint`?t.toString():t}function sV(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function cV(e){return e==null}function lV(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}var uV=Symbol(`evaluating`);function dV(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==uV)return r===void 0&&(r=uV,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}function fV(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function pV(...e){let t={};for(let n of e)Object.assign(t,Object.getOwnPropertyDescriptors(n));return Object.defineProperties({},t)}var mV=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function hV(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function gV(e){if(hV(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(hV(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}var _V=new Set([`string`,`number`,`bigint`,`boolean`,`symbol`,`undefined`]);function vV(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function yV(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function bV(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function xV(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function SV(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.pick() cannot be used on object schemas containing refinements`);return yV(e,pV(e._zod.def,{get shape(){let e={};for(let r in t){if(!(r in n.shape))throw Error(`Unrecognized key: "${r}"`);t[r]&&(e[r]=n.shape[r])}return fV(this,`shape`,e),e},checks:[]}))}function CV(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.omit() cannot be used on object schemas containing refinements`);return yV(e,pV(e._zod.def,{get shape(){let r={...e._zod.def.shape};for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return fV(this,`shape`,r),r},checks:[]}))}function wV(e,t,n){let r=t._zod.def.checks;if(r&&r.length>0)throw Error(`.partial() cannot be used on object schemas containing refinements`);return yV(t,pV(t._zod.def,{get shape(){let r=t._zod.def.shape,i={...r};if(n)for(let t in n){if(!(t in r))throw Error(`Unrecognized key: "${t}"`);n[t]&&(i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t])}else for(let t in r)i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t];return fV(this,`shape`,i),i},checks:[]}))}function TV(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function DV(e){return typeof e==`string`?e:e?.message}function OV(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=DV(e.inst?._zod.def?.error?.(e))??DV(t?.error?.(e))??DV(n.customError?.(e))??DV(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function kV(e){return Array.isArray(e)?`array`:typeof e==`string`?`string`:`unknown`}var AV=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,oV,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},jV=B(`$ZodError`,AV),MV=B(`$ZodError`,AV,{Parent:Error}),NV=e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new rV;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>OV(e,a,aV())));throw mV(t,i?.callee),t}return o.value},PV=NV(MV),FV=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>OV(e,a,aV())));throw mV(t,i?.callee),t}return o.value})(MV),IV=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new rV;return a.issues.length?{success:!1,error:new(e??jV)(a.issues.map(e=>OV(e,i,aV())))}:{success:!0,data:a.value}})(MV),LV=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>OV(e,i,aV())))}:{success:!0,data:a.value}})(MV),RV=(e=>(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return NV(e)(t,n,i)})(MV),zV=(e=>(t,n,r)=>NV(e)(t,n,r))(MV),BV=e=>{let t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\s\\S]*`;return RegExp(`^${t}$`)},VV=/^-?\d+n?$/,HV=/^-?\d+(?:\.\d+)?$/,UV=/^(?:true|false)$/i,WV=/^null$/i,GV=/^undefined$/i,KV=B(`$ZodCheck`,(e,t)=>{var n;e._zod??={},e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),qV={number:`number`,bigint:`bigint`,object:`date`},JV=B(`$ZodCheckGreaterThan`,(e,t)=>{KV.init(e,t);let n=qV[typeof t.value];e._zod.onattach.push(e=>{let n=e._zod.bag,r=(t.inclusive?n.minimum:n.exclusiveMinimum)??-1/0;t.value>r&&(t.inclusive?n.minimum=t.value:n.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:`too_small`,minimum:typeof t.value==`object`?t.value.getTime():t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),YV=B(`$ZodCheckMinLength`,(e,t)=>{var n;KV.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!cV(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag.minimum??-1/0;t.minimum>n&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{let r=n.value;if(r.length>=t.minimum)return;let i=kV(r);n.issues.push({origin:i,code:`too_small`,minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort})}}),XV=B(`$ZodCheckStringFormat`,(e,t)=>{var n,r;KV.init(e,t),e._zod.onattach.push(e=>{let n=e._zod.bag;n.format=t.format,t.pattern&&(n.patterns??=new Set,n.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:t.format,input:n.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),ZV=B(`$ZodCheckRegex`,(e,t)=>{XV.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:`string`,code:`invalid_format`,format:`regex`,input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),QV={major:4,minor:3,patch:6},$V=B(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=QV;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=TV(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new rV;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=TV(e,t))});else{if(e.issues.length===t)continue;r||=TV(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(TV(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new rV;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new rV;return o.then(e=>t(e,r,a))}return t(o,r,a)}}dV(e,`~standard`,()=>({validate:t=>{try{let n=IV(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return LV(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),eH=B(`$ZodString`,(e,t)=>{$V.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??BV(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),tH=B(`$ZodNumber`,(e,t)=>{$V.init(e,t),e._zod.pattern=e._zod.bag.pattern??HV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}let i=n.value;if(typeof i==`number`&&!Number.isNaN(i)&&Number.isFinite(i))return n;let a=typeof i==`number`?Number.isNaN(i)?`NaN`:Number.isFinite(i)?void 0:`Infinity`:void 0;return n.issues.push({expected:`number`,code:`invalid_type`,input:i,inst:e,...a?{received:a}:{}}),n}}),nH=B(`$ZodBoolean`,(e,t)=>{$V.init(e,t),e._zod.pattern=UV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=!!n.value}catch{}let i=n.value;return typeof i==`boolean`||n.issues.push({expected:`boolean`,code:`invalid_type`,input:i,inst:e}),n}}),rH=B(`$ZodBigInt`,(e,t)=>{$V.init(e,t),e._zod.pattern=VV,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=BigInt(n.value)}catch{}return typeof n.value==`bigint`||n.issues.push({expected:`bigint`,code:`invalid_type`,input:n.value,inst:e}),n}}),iH=B(`$ZodUndefined`,(e,t)=>{$V.init(e,t),e._zod.pattern=GV,e._zod.values=new Set([void 0]),e._zod.optin=`optional`,e._zod.optout=`optional`,e._zod.parse=(t,n)=>{let r=t.value;return r===void 0||t.issues.push({expected:`undefined`,code:`invalid_type`,input:r,inst:e}),t}}),aH=B(`$ZodNull`,(e,t)=>{$V.init(e,t),e._zod.pattern=WV,e._zod.values=new Set([null]),e._zod.parse=(t,n)=>{let r=t.value;return r===null||t.issues.push({expected:`null`,code:`invalid_type`,input:r,inst:e}),t}}),oH=B(`$ZodAny`,(e,t)=>{$V.init(e,t),e._zod.parse=e=>e}),sH=B(`$ZodUnknown`,(e,t)=>{$V.init(e,t),e._zod.parse=e=>e}),cH=B(`$ZodDate`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=new Date(n.value)}catch{}let i=n.value,a=i instanceof Date;return a&&!Number.isNaN(i.getTime())||n.issues.push({expected:`date`,code:`invalid_type`,input:i,...a?{received:`Invalid Date`}:{},inst:e}),n}});function lH(e,t,n){e.issues.length&&t.issues.push(...EV(n,e.issues)),t.value[n]=e.value}var uH=B(`$ZodArray`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;elH(t,n,e))):lH(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function dH(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...EV(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function fH(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key "${n}": expected a Zod schema`);let n=xV(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function pH(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>dH(e,n,i,t,u))):dH(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}var mH=B(`$ZodObject`,(e,t)=>{if($V.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=sV(()=>fH(t));dV(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=hV,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>dH(n,t,e,s,r))):dH(i,t,e,s,r)}return i?pH(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}});function hH(e,t,n,r){for(let n of e)if(n.issues.length===0)return t.value=n.value,t;let i=e.filter(e=>!TV(e));return i.length===1?(t.value=i[0].value,i[0]):(t.issues.push({code:`invalid_union`,input:t.value,inst:n,errors:e.map(e=>e.issues.map(e=>OV(e,r,aV())))}),t)}var gH=B(`$ZodUnion`,(e,t)=>{$V.init(e,t),dV(e._zod,`optin`,()=>t.options.some(e=>e._zod.optin===`optional`)?`optional`:void 0),dV(e._zod,`optout`,()=>t.options.some(e=>e._zod.optout===`optional`)?`optional`:void 0),dV(e._zod,`values`,()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),dV(e._zod,`pattern`,()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>lV(e.source)).join(`|`)})$`)}});let n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(i,a)=>{if(n)return r(i,a);let o=!1,s=[];for(let e of t.options){let t=e._zod.run({value:i.value,issues:[]},a);if(t instanceof Promise)s.push(t),o=!0;else{if(t.issues.length===0)return t;s.push(t)}}return o?Promise.all(s).then(t=>hH(t,i,e,a)):hH(s,i,e,a)}}),_H=B(`$ZodDiscriminatedUnion`,(e,t)=>{t.inclusive=!1,gH.init(e,t);let n=e._zod.parse;dV(e._zod,`propValues`,()=>{let e={};for(let n of t.options){let r=n._zod.propValues;if(!r||Object.keys(r).length===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`);for(let[t,n]of Object.entries(r)){e[t]||(e[t]=new Set);for(let r of n)e[t].add(r)}}return e});let r=sV(()=>{let e=t.options,n=new Map;for(let r of e){let e=r._zod.propValues?.[t.discriminator];if(!e||e.size===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`);for(let t of e){if(n.has(t))throw Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,r)}}return n});e._zod.parse=(i,a)=>{let o=i.value;if(!hV(o))return i.issues.push({code:`invalid_type`,expected:`object`,input:o,inst:e}),i;let s=r.value.get(o?.[t.discriminator]);return s?s._zod.run(i,a):t.unionFallback?n(i,a):(i.issues.push({code:`invalid_union`,errors:[],note:`No matching discriminator`,discriminator:t.discriminator,input:o,path:[t.discriminator],inst:e}),i)}}),vH=B(`$ZodTuple`,(e,t)=>{$V.init(e,t);let n=t.items;e._zod.parse=(r,i)=>{let a=r.value;if(!Array.isArray(a))return r.issues.push({input:a,inst:e,expected:`tuple`,code:`invalid_type`}),r;r.value=[];let o=[],s=[...n].reverse().findIndex(e=>e._zod.optin!==`optional`),c=s===-1?0:n.length-s;if(!t.rest){let t=a.length>n.length,i=a.length=a.length&&l>=c)continue;let t=e._zod.run({value:a[l],issues:[]},i);t instanceof Promise?o.push(t.then(e=>yH(e,r,l))):yH(t,r,l)}if(t.rest){let e=a.slice(n.length);for(let n of e){l++;let e=t.rest._zod.run({value:n,issues:[]},i);e instanceof Promise?o.push(e.then(e=>yH(e,r,l))):yH(e,r,l)}}return o.length?Promise.all(o).then(()=>r):r}});function yH(e,t,n){e.issues.length&&t.issues.push(...EV(n,e.issues)),t.value[n]=e.value}var bH=B(`$ZodRecord`,(e,t)=>{$V.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!gV(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...EV(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...EV(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&HV.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>OV(e,r,aV())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...EV(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...EV(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}}),xH=B(`$ZodLiteral`,(e,t)=>{if($V.init(e,t),t.values.length===0)throw Error(`Cannot create literal schema with no valid values`);let n=new Set(t.values);e._zod.values=n,e._zod.pattern=RegExp(`^(${t.values.map(e=>typeof e==`string`?vV(e):e?vV(e.toString()):String(e)).join(`|`)})$`),e._zod.parse=(r,i)=>{let a=r.value;return n.has(a)||r.issues.push({code:`invalid_value`,values:t.values,input:a,inst:e}),r}});function SH(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}var CH=B(`$ZodOptional`,(e,t)=>{$V.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,dV(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),dV(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${lV(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>SH(t,e.value)):SH(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}}),wH=B(`$ZodNullable`,(e,t)=>{$V.init(e,t),dV(e._zod,`optin`,()=>t.innerType._zod.optin),dV(e._zod,`optout`,()=>t.innerType._zod.optout),dV(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${lV(e.source)}|null)$`):void 0}),dV(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,n)=>e.value===null?e:t.innerType._zod.run(e,n)}),TH=B(`$ZodPipe`,(e,t)=>{$V.init(e,t),dV(e._zod,`values`,()=>t.in._zod.values),dV(e._zod,`optin`,()=>t.in._zod.optin),dV(e._zod,`optout`,()=>t.out._zod.optout),dV(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if(n.direction===`backward`){let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>EH(e,t.in,n)):EH(r,t.in,n)}let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>EH(e,t.out,n)):EH(r,t.out,n)}});function EH(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}var DH=B(`$ZodCodec`,(e,t)=>{$V.init(e,t),dV(e._zod,`values`,()=>t.in._zod.values),dV(e._zod,`optin`,()=>t.in._zod.optin),dV(e._zod,`optout`,()=>t.out._zod.optout),dV(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if((n.direction||`forward`)===`forward`){let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>OH(e,t,n)):OH(r,t,n)}else{let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>OH(e,t,n)):OH(r,t,n)}}});function OH(e,t,n){if(e.issues.length)return e.aborted=!0,e;if((n.direction||`forward`)===`forward`){let r=t.transform(e.value,e);return r instanceof Promise?r.then(r=>kH(e,r,t.out,n)):kH(e,r,t.out,n)}else{let r=t.reverseTransform(e.value,e);return r instanceof Promise?r.then(r=>kH(e,r,t.in,n)):kH(e,r,t.in,n)}}function kH(e,t,n,r){return e.issues.length?(e.aborted=!0,e):n._zod.run({value:t,issues:e.issues},r)}var AH=B(`$ZodReadonly`,(e,t)=>{$V.init(e,t),dV(e._zod,`propValues`,()=>t.innerType._zod.propValues),dV(e._zod,`values`,()=>t.innerType._zod.values),dV(e._zod,`optin`,()=>t.innerType?._zod?.optin),dV(e._zod,`optout`,()=>t.innerType?._zod?.optout),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(jH):jH(r)}});function jH(e){return e.value=Object.freeze(e.value),e}var MH=B(`$ZodTemplateLiteral`,(e,t)=>{$V.init(e,t);let n=[];for(let e of t.parts)if(typeof e==`object`&&e){if(!e._zod.pattern)throw Error(`Invalid template literal part, no pattern found: ${[...e._zod.traits].shift()}`);let t=e._zod.pattern instanceof RegExp?e._zod.pattern.source:e._zod.pattern;if(!t)throw Error(`Invalid template literal part: ${e._zod.traits}`);let r=t.startsWith(`^`)?1:0,i=t.endsWith(`$`)?t.length-1:t.length;n.push(t.slice(r,i))}else if(e===null||_V.has(typeof e))n.push(vV(`${e}`));else throw Error(`Invalid template literal part: ${e}`);e._zod.pattern=RegExp(`^${n.join(``)}$`),e._zod.parse=(n,r)=>typeof n.value==`string`?(e._zod.pattern.lastIndex=0,e._zod.pattern.test(n.value)||n.issues.push({input:n.value,inst:e,code:`invalid_format`,format:t.format??`template_literal`,pattern:e._zod.pattern.source}),n):(n.issues.push({input:n.value,inst:e,expected:`string`,code:`invalid_type`}),n)}),NH,PH=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function FH(){return new PH}(NH=globalThis).__zod_globalRegistry??(NH.__zod_globalRegistry=FH()),globalThis.__zod_globalRegistry;function IH(e,t){return new e({type:`string`,...bV(t)})}function LH(e,t){return new e({type:`number`,checks:[],...bV(t)})}function RH(e,t){return new e({type:`boolean`,...bV(t)})}function zH(e,t){return new e({type:`bigint`,...bV(t)})}function BH(e,t){return new e({type:`undefined`,...bV(t)})}function VH(e,t){return new e({type:`null`,...bV(t)})}function HH(e){return new e({type:`any`})}function UH(e){return new e({type:`unknown`})}function WH(e,t){return new e({type:`date`,...bV(t)})}function GH(e,t){return new JV({check:`greater_than`,...bV(t),value:e,inclusive:!0})}function KH(e,t){return new YV({check:`min_length`,...bV(t),minimum:e})}function qH(e,t){return new ZV({check:`string_format`,format:`regex`,...bV(t),pattern:e})}var JH=B(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);$V.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>PV(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>IV(e,t,n),e.parseAsync=async(t,n)=>FV(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>LV(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>yV(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),YH=B(`ZodMiniString`,(e,t)=>{eH.init(e,t),JH.init(e,t)});function V(e){return IH(YH,e)}var XH=B(`ZodMiniNumber`,(e,t)=>{tH.init(e,t),JH.init(e,t)});function H(e){return LH(XH,e)}var ZH=B(`ZodMiniBoolean`,(e,t)=>{nH.init(e,t),JH.init(e,t)});function QH(e){return RH(ZH,e)}var $H=B(`ZodMiniBigInt`,(e,t)=>{rH.init(e,t),JH.init(e,t)});function eU(e){return zH($H,e)}var tU=B(`ZodMiniUndefined`,(e,t)=>{iH.init(e,t),JH.init(e,t)});function nU(e){return BH(tU,e)}var rU=B(`ZodMiniNull`,(e,t)=>{aH.init(e,t),JH.init(e,t)});function iU(e){return VH(rU,e)}var aU=B(`ZodMiniAny`,(e,t)=>{oH.init(e,t),JH.init(e,t)});function oU(){return HH(aU)}var sU=B(`ZodMiniUnknown`,(e,t)=>{sH.init(e,t),JH.init(e,t)});function cU(){return UH(sU)}var lU=B(`ZodMiniDate`,(e,t)=>{cH.init(e,t),JH.init(e,t)});function uU(e){return WH(lU,e)}var dU=B(`ZodMiniArray`,(e,t)=>{uH.init(e,t),JH.init(e,t)});function U(e,t){return new dU({type:`array`,element:e,...bV(t)})}var fU=B(`ZodMiniObject`,(e,t)=>{mH.init(e,t),JH.init(e,t),dV(e,`shape`,()=>t.shape)});function W(e,t){return new fU({type:`object`,shape:e??{},...bV(t)})}function pU(e,t){return SV(e,t)}function mU(e,t){return CV(e,t)}function hU(e,t){return wV(CU,e,t)}var gU=B(`ZodMiniUnion`,(e,t)=>{gH.init(e,t),JH.init(e,t)});function G(e,t){return new gU({type:`union`,options:e,...bV(t)})}var _U=B(`ZodMiniDiscriminatedUnion`,(e,t)=>{_H.init(e,t),JH.init(e,t)});function vU(e,t,n){return new _U({type:`union`,options:t,discriminator:e,...bV(n)})}var yU=B(`ZodMiniTuple`,(e,t)=>{vH.init(e,t),JH.init(e,t)});function K(e,t,n){let r=t instanceof $V;return new yU({type:`tuple`,items:e,rest:r?t:null,...bV(r?n:t)})}var bU=B(`ZodMiniRecord`,(e,t)=>{bH.init(e,t),JH.init(e,t)});function xU(e,t,n){return new bU({type:`record`,keyType:e,valueType:t,...bV(n)})}var SU=B(`ZodMiniLiteral`,(e,t)=>{xH.init(e,t),JH.init(e,t)});function q(e,t){return new SU({type:`literal`,values:Array.isArray(e)?e:[e],...bV(t)})}var CU=B(`ZodMiniOptional`,(e,t)=>{CH.init(e,t),JH.init(e,t)});function J(e){return new CU({type:`optional`,innerType:e})}var wU=B(`ZodMiniNullable`,(e,t)=>{wH.init(e,t),JH.init(e,t)});function TU(e){return new wU({type:`nullable`,innerType:e})}function EU(e){return J(TU(e))}var DU=B(`ZodMiniPipe`,(e,t)=>{TH.init(e,t),JH.init(e,t)}),OU=B(`ZodMiniCodec`,(e,t)=>{DU.init(e,t),DH.init(e,t)});function kU(e,t,n){return new OU({type:`pipe`,in:e,out:t,transform:n.decode,reverseTransform:n.encode})}var AU=B(`ZodMiniReadonly`,(e,t)=>{AH.init(e,t),JH.init(e,t)});function Y(e){return new AU({type:`readonly`,innerType:e})}var jU=B(`ZodMiniTemplateLiteral`,(e,t)=>{MH.init(e,t),JH.init(e,t)});function MU(e,t){return new jU({type:`template_literal`,parts:e,...bV(t)})}function NU(e,t={}){return _z(e,t)}function PU(e,t,n){if(t===`Error`)return FU;if(t===`Panic`)return IU;if(cL(t,{strict:!1})){let e=rL(t,0,4);if(e===`0x08c379a0`)return FU;if(e===`0x4e487b71`)return IU}let r=vz(e,t,n);if(r.type!==`error`)throw new Cz({name:t,type:`error`});return r}var FU=NU({inputs:[{name:`message`,type:`string`}],name:`Error`,type:`error`}),IU=NU({inputs:[{name:`reason`,type:`uint8`}],name:`Panic`,type:`error`});D();var LU=class extends E{constructor(){super(`Function is not recognized.`,{metaMessages:[`This could be due to any of the following:`,` - The contract does not have the function,`,` - The address is not a contract.`],name:`FunctionSelectorNotRecognizedError`})}};function RU(e,t={}){return iv(e,t)}function zU(e){return ov(e)}Ha();function BU(e,t){let n=e.walk(e=>`data`in e);if(!n?.data)return e;if(n.data===zU(RU(`error FnSelectorNotRecognized()`)))return new LU;let r=null;for(let e of t.calls){let t=e;if(t.abi)try{if(!Va({abi:t.abi,data:n.data}))continue;r=t}catch{}}return r?ls(n,{abi:r.abi,address:r.to,args:r.args,functionName:r.functionName}):e}var X=()=>MU([`0x`,V()],{message:`Needs string in format ^0x[A-Fa-f0-9]{40}$.`}),Z=()=>MU([`0x`,V()],{message:`Needs string in format ^0x[A-Fa-f0-9]+$.`}),Q=()=>kU(Z(),H(),{decode:e=>sL(e),encode:e=>$I(e)}),VU=()=>kU(Z(),eU({message:`Required bigint`}),{decode:e=>oL(e),encode:e=>$I(e)});function HU(e){return G(e)}var UU=class extends z{constructor(){super(...arguments),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Schema.ValidationError`})}};function WU(e){let t=e,n=`Validation failed with ${t.issues.length} error${t.issues.length===1?``:`s`}:`;n+=` -`;for(let e of t.issues)e&&(n+=` -`,n+=GU(e));return new UU(n)}function GU(e,t=0){let n=KU(e.path),r=`- ${n?`${n}: `:``}`,i=` `.repeat(t+1),a=r;switch(e.code){case`invalid_type`:{let t=e.expected,n=e.input?qU(e):`undefined`;a+=`Expected ${t}. ${e.message===`Invalid input`?``:e.message}`,n!==`undefined`&&(a+=`but received ${n}`);break}case`too_big`:{let t=e.maximum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at most`:`less than`} ${t}`;break}case`too_small`:{let t=e.minimum,n=e.inclusive??!0;e.exact??!1?a+=`${e.origin} must be exactly ${t}`:a+=`${e.origin} must be ${n?`at least`:`greater than`} ${t}`;break}case`invalid_format`:switch(e.format){case`regex`:a+=`Must match pattern: ${e.pattern}`;break;case`starts_with`:a+=`Must start with "${e.prefix}"`;break;case`ends_with`:a+=`Must end with "${e.suffix}"`;break;case`includes`:a+=`Must include "${e.includes}"`;break;case`template_literal`:a+=`Must match pattern: ${e.pattern}`;break;default:a+=`Invalid ${e.format} format`}break;case`not_multiple_of`:a+=`Number must be a multiple of ${e.divisor}`;break;case`unrecognized_keys`:{let t=e.keys.map(e=>`"${e}"`).join(`, `);a+=`Unrecognized key${e.keys.length>1?`s`:``}: ${t}`;break}case`invalid_union`:{let n=e.errors&&e.errors.length>0;a+=`Invalid union value.`,n&&e.errors.forEach(e=>{e.length>0&&e.forEach(e=>{a+=` -`,a+=i,a+=GU(e,t+1)})});break}case`invalid_key`:a+=`Invalid ${e.origin} key`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` -`,a+=i,a+=GU(e,t+1)});break;case`invalid_element`:a+=`Invalid ${e.origin} element at key "${e.key}"`,e.issues&&e.issues.length>0&&e.issues.forEach(e=>{a+=` -`,a+=i,a+=GU(e,t+1)});break;case`invalid_value`:{let t=e.values.map(e=>JSON.stringify(e)).join(`, `);e.values.length>1?a+=`Expected one of: ${t}`:a+=`Expected ${t}`;break}case`custom`:a+=e.message||`Custom validation failed`;break;default:a+=e.message||`Validation failed`}return a}function KU(e){return e.length===0?``:"at `"+e.map((e,t)=>typeof e==`number`?`[${e}]`:typeof e==`symbol`?`[${e.toString()}]`:/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)&&t>0?`.${e}`:t===0&&/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e)?e:`["${e}"]`).join(``)+"`"}function qU(e){let t=e.input;if(t===void 0)return`undefined`;if(t===null)return`null`;let n=typeof t;return n===`object`?Array.isArray(t)?`array`:t instanceof Date?`date`:t instanceof Map?`map`:t instanceof Set?`set`:`object`:n}var JU=G([W({selector:Z(),to:X(),type:q(`call`)}),W({limit:VU(),period:G([q(`minute`),q(`hour`),q(`day`),q(`week`),q(`month`),q(`year`)]),token:J(G([X(),iU()])),type:q(`spend`)})]),YU=W({expiry:Q(),prehash:J(QH()),publicKey:Z(),role:G([q(`admin`),q(`normal`)]),type:G([q(`p256`),q(`secp256k1`),q(`webauthnp256`)])}),XU=W({...YU.shape,permissions:Y(U(JU))}),ZU;(function(e){e.AssetDiffAsset=G([W({address:J(G([X(),iU()])),decimals:J(G([H(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),name:J(G([V(),iU()])),symbol:V(),type:q(`erc20`),value:VU()}),W({address:J(G([X(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),name:J(G([V(),iU()])),symbol:V(),type:q(`erc721`),uri:V(),value:VU()}),W({address:iU(),decimals:J(G([H(),iU()])),direction:G([q(`incoming`),q(`outgoing`)]),fiat:J(W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})})),symbol:V(),type:iU(),value:VU()})]),e.Response=xU(Z(),Y(U(Y(K([X(),Y(U(e.AssetDiffAsset))])))))})(ZU||={});var QU;(function(e){e.Request=Y(U(XU)),e.Response=Y(U(W({...XU.shape,hash:Z()})))})(QU||={});var $U;(function(e){e.Response=xU(Z(),W({currency:V(),value:V()}))})($U||={});var eW;(function(e){e.Request=W({feePayer:J(X()),feeToken:J(X()),nonce:J(VU())})})(eW||={});var tW;(function(e){e.Request=Y(U(W({address:X(),value:VU()})))})(tW||={});var nW;(function(e){e.Request=Y(U(W({hash:Z()}))),e.Response=Y(U(W({hash:Z()})))})(nW||={});var rW=W({eoa:X(),executionData:Z(),nonce:Z(),signature:Z()}),iW=W({...rW.shape,chainId:Q()}),aW=G([W({combinedGas:VU(),encodedFundTransfers:Y(U(Z())),encodedPreCalls:Y(U(Z())),eoa:X(),executionData:Z(),expiry:VU(),funder:X(),funderSignature:Z(),isMultichain:QH(),nonce:VU(),payer:X(),paymentAmount:VU(),paymentMaxAmount:VU(),paymentRecipient:X(),paymentSignature:Z(),paymentToken:X(),settler:X(),settlerContext:Z(),signature:Z(),supportedAccountImplementation:X()}),W({combinedGas:VU(),encodedFundTransfers:Y(U(Z())),encodedPreCalls:Y(U(Z())),eoa:X(),executionData:Z(),expiry:VU(),funder:X(),funderSignature:Z(),isMultichain:QH(),nonce:VU(),payer:X(),paymentRecipient:X(),paymentSignature:Z(),paymentToken:X(),prePaymentAmount:VU(),prePaymentMaxAmount:VU(),settler:X(),settlerContext:Z(),signature:Z(),supportedAccountImplementation:X(),totalPaymentAmount:VU(),totalPaymentMaxAmount:VU()})]);VU();var oW=W({address:G([X(),iU()]),decimals:J(H()),deficit:VU(),fiat:J(W({currency:V(),value:V()})),name:J(V()),required:VU(),symbol:J(V())}),sW=W({additionalAuthorization:EU(W({address:X(),chainId:Q(),nonce:Q(),r:Z(),s:Z(),yParity:Q()})),assetDeficits:J(U(oW)),authorizationAddress:J(G([X(),iU()])),chainId:Q(),ethPrice:VU(),extraPayment:VU(),feeTokenDeficit:VU(),intent:aW,nativeFeeEstimate:W({maxFeePerGas:VU(),maxPriorityFeePerGas:VU()}),orchestrator:X(),paymentTokenDecimals:H(),txGas:VU()}),cW=W({...W({multiChainRoot:J(G([Z(),iU()])),quotes:Y(U(sW)).check(KH(1)),ttl:H()}).shape,hash:Z(),r:Z(),s:Z(),v:J(Z()),yParity:J(Z())}),lW=W({address:X(),decimals:H(),feeToken:J(QH()),interop:J(QH()),nativeRate:J(VU()),symbol:V(),uid:V()}),uW=V().check(qH(/^[A-Z0-9]+$/)),dW=W({address:X(),chainId:Q(),nonce:Q()}),fW=W({...dW.shape,r:Z(),s:Z(),yParity:Q()}),pW=W({data:J(Z()),to:X(),value:J(VU())}),mW;(function(e){e.Parameters=W({address:X(),secret:V()}),e.Request=W({method:q(`account_getOnrampContactInfo`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(V()),phone:J(V()),phoneVerifiedAt:J(H())})})(mW||={});var hW;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`account_onrampStatus`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(H()),phone:J(H())})})(hW||={});var gW;(function(e){e.Parameters=W({phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_resendVerifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(gW||={});var _W;(function(e){e.Parameters=W({email:V().check(qH(/^.*@.*$/)),walletAddress:X()}),e.Request=W({method:q(`account_setEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(_W||={});var vW;(function(e){e.Parameters=W({phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_setPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(vW||={});var yW;(function(e){e.Parameters=W({chainId:Q(),email:V(),signature:Z(),token:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(yW||={});var bW;(function(e){e.Parameters=W({code:V(),phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(bW||={});var xW;(function(e){e.Request=W({method:q(`health`),params:nU()}),e.Response=W({quoteSigner:X(),status:V(),version:V()})})(xW||={});var SW;(function(e){e.Parameters=W({address:X(),chainId:Q(),tokenAddress:X(),value:VU()}),e.Request=W({method:q(`wallet_addFaucetFunds`),params:Y(K([e.Parameters]))}),e.Response=W({message:J(V()),transactionHash:Z()})})(SW||={});var CW;(function(e){e.Parameters=W({chainId:Q(),id:Z()}),e.Request=W({method:q(`wallet_getAccounts`),params:Y(K([e.Parameters]))}),e.Response=Y(U(W({address:X(),keys:QU.Response})))})(CW||={});var wW;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`wallet_getAuthorization`),params:Y(K([e.Parameters]))}),e.Response=W({authorization:fW,data:Z(),to:X()})})(wW||={});var TW;(function(e){e.Request=W({method:q(`wallet_getCapabilities`),params:J(K([Y(U(H()))]))});let t=W({address:X(),version:J(G([V(),iU()]))});e.Response=xU(Z(),W({contracts:W({accountImplementation:t,accountProxy:t,legacyAccountImplementations:Y(U(t)),legacyOrchestrators:Y(U(G([W({orchestrator:t,simulator:t}),t]))),orchestrator:t,simulator:t}),fees:W({quoteConfig:W({constantRate:J(G([H(),iU()])),gas:J(W({intentBuffer:J(H()),txBuffer:J(H())})),rateTtl:H(),ttl:H()}),recipient:X(),tokens:Y(U(lW))})}))})(TW||={});var EW;(function(e){let t=G([q(`native`),q(`erc20`),q(`erc721`),V()]);e.Parameters=W({account:X(),assetFilter:J(xU(Z(),Y(U(W({address:G([X(),q(`native`)]),type:t}))))),assetTypeFilter:J(Y(U(t))),chainFilter:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getAssets`),params:Y(K([e.Parameters]))}),e.Price=W({currency:V(),value:kU(V(),H(),{decode:e=>Number(e),encode:e=>String(e)})}),e.Response=xU(V(),Y(U(HU([W({address:X(),balance:VU(),metadata:TU(W({decimals:H(),fiat:EU(e.Price),name:V(),symbol:V()})),type:q(`erc20`)}),W({address:TU(q(`native`)),balance:VU(),metadata:TU(W({decimals:H(),fiat:EU(e.Price),name:J(V()),symbol:J(V())})),type:q(`native`)})]))))})(EW||={});var DW;(function(e){e.Request=W({method:q(`wallet_getCallsStatus`),params:Y(K([Z()]))}),e.Response=W({id:V(),receipts:J(Y(U(W({blockHash:Z(),blockNumber:Q(),chainId:Q(),gasUsed:Q(),logs:Y(U(W({address:X(),data:Z(),topics:Y(U(Z()))}))),status:Z(),transactionHash:Z()})))),status:H()})})(DW||={});var OW;(function(e){e.Parameters=W({address:X(),index:J(H()),limit:H(),sort:G([q(`asc`),q(`desc`)])}),e.Request=W({method:q(`wallet_getCallsHistory`),params:Y(K([e.Parameters]))}),e.Transaction=W({chainId:Q(),transactionHash:Z()}),e.Capabilities=W({assetDiffs:J(ZU.Response),feeTotals:J($U.Response),quotes:J(Y(U(sW)))}),e.Entry=W({capabilities:e.Capabilities,id:Z(),index:H(),keyHash:Z(),status:H(),timestamp:H(),transactions:Y(U(e.Transaction))}),e.Response=Y(U(e.Entry))})(OW||={});var kW;(function(e){e.Parameters=W({address:X(),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getKeys`),params:Y(K([e.Parameters]))}),e.Response=xU(Z(),QU.Response)})(kW||={});var AW;(function(e){e.Capabilities=W({authorizeKeys:J(QU.Request),meta:eW.Request,preCall:J(QH()),preCalls:J(Y(U(rW))),requiredFunds:J(tW.Request),revokeKeys:J(nW.Request)}),e.ResponseCapabilities=W({assetDiffs:J(ZU.Response),authorizeKeys:EU(QU.Response),feePayerDigest:J(Z()),feeSignature:J(Z()),feeTotals:J($U.Response),revokeKeys:EU(nW.Response)}),e.Parameters=W({calls:Y(U(pW)),capabilities:e.Capabilities,chainId:Q(),from:J(X()),key:J(W({prehash:QH(),publicKey:Z(),type:YU.shape.type}))}),e.Request=W({method:q(`wallet_prepareCalls`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:e.ResponseCapabilities,context:W({preCall:J(hU(iW)),quote:J(hU(cW))}),digest:Z(),key:EU(W({prehash:QH(),publicKey:Z(),type:YU.shape.type})),signature:Z(),typedData:W({domain:G([W({chainId:G([Q(),H()]),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(AW||={});var jW;(function(e){e.Capabilities=W({authorizeKeys:QU.Request}),e.Parameters=W({address:X(),capabilities:e.Capabilities,chainId:J(H()),delegation:X()}),e.Request=W({method:q(`wallet_prepareUpgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:e.Capabilities,chainId:Q(),context:W({address:X(),authorization:dW,chainId:Q(),preCall:rW}),digests:W({auth:Z(),exec:Z()}),typedData:W({domain:G([W({chainId:G([Q(),H()]),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(jW||={});var MW;(function(e){e.Request=W({method:q(`wallet_feeTokens`),params:J(nU())}),e.Response=xU(Z(),Y(U(W({address:X(),decimals:H(),nativeRate:J(VU()),symbol:V()}))))})(MW||={});var NW;(function(e){e.Parameters=W({capabilities:J(W({feeSignature:J(Z())})),context:W({preCall:J(hU(iW)),quote:J(hU(cW))}),key:J(W({prehash:QH(),publicKey:Z(),type:YU.shape.type})),signature:Z()}),e.Request=W({method:q(`wallet_sendPreparedCalls`),params:Y(K([e.Parameters]))}),e.Response=W({id:Z()})})(NW||={});var PW;(function(e){e.Parameters=W({context:W({address:X(),authorization:dW,chainId:Q(),preCall:rW}),signatures:W({auth:Z(),exec:Z()})}),e.Request=W({method:q(`wallet_upgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=nU()})(PW||={});var FW;(function(e){e.Parameters=W({address:Z(),chainId:Q(),digest:Z(),signature:Z()}),e.Request=W({method:q(`wallet_verifySignature`),params:Y(K([e.Parameters]))}),e.Response=W({proof:EU(W({account:X(),initPreCall:EU(rW),keyHash:Z()})),valid:QH()})})(FW||={});async function IW(e,t){try{let n=`wallet_getAuthorization`,r=await Mm(()=>e.request({method:n,params:[RV(wW.Parameters,t)]}),{cacheKey:`${e.uid}.${n}.${t.address}`});return zV(wW.Response,r)}catch(e){throw $W(e),e}}async function LW(e,t={}){let n=(()=>{if(t.chainId)return[t.chainId];if(t.chainIds!==`all`)return t.chainIds?t.chainIds:[e.chain.id]})();try{let r=`wallet_getCapabilities`,i=await Mm(()=>e.request({method:r,params:n?[n]:void 0},{retryCount:0}),{cacheKey:`${e.uid}.${r}.${n?.join(`,`)}`}),a=t.raw?i:zV(TW.Response,i);return t.chainIds?a:Object.values(a)[0]}catch(e){throw $W(e),e}}async function RW(e,t){let{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a}=t;try{let t=await e.request({method:`wallet_getAssets`,params:[RV(EW.Parameters,{account:n,assetFilter:r,assetTypeFilter:i,chainFilter:a})]}),o=zV(EW.Response,t),s=Object.entries(o).reduce((e,[t,n])=>(e[sL(t)]=n,e),{}),c={};for(let e of Object.values(s))for(let t of e){let e=JSON.stringify(t.metadata);c[e]={...t,balance:t.balance+(c[e]?.balance??0n)}}return{...s,0:Object.values(c)}}catch(e){throw $W(e),e}}async function zW(e,t){let{id:n}=t;try{let t=await e.request({method:`wallet_getCallsStatus`,params:[n]});return zV(DW.Response,t)}catch(e){throw $W(e),e}}async function BW(e,t){try{let n=await e.request({method:`wallet_getCallsHistory`,params:[RV(OW.Parameters,t)]});return zV(OW.Response,n)}catch(e){throw $W(e),e}}async function VW(e,t){let{address:n,chainIds:r}=t;try{let t=await e.request({method:`wallet_getKeys`,params:[RV(kW.Parameters,{address:n,chainIds:r})]});return zV(kW.Response,t)}catch(e){throw $W(e),e}}async function HW(e){let t=`health`,n=await Mm(()=>e.request({method:t}),{cacheKey:`${e.uid}.${t}`});return zV(xW.Response,n)}async function UW(e,t){let{address:n,capabilities:r,chain:i=e.chain,key:a}=t,o=t.calls.map(e=>({data:e.abi?wz(Tz(e.abi,e.functionName),e.args):e.data??`0x`,to:e.to,value:e.value??0n}));try{let t=await e.request({method:`wallet_prepareCalls`,params:[RV(AW.Parameters,{calls:o,capabilities:{...r,meta:{...r?.meta}},chainId:i?.id,from:n,key:a?{prehash:a.prehash,publicKey:a.publicKey,type:a.type}:void 0})]},{retryCount:0});return Object.assign(zV(AW.Response,t),{_raw:t})}catch(e){throw $W(e),ZW(e,{calls:t.calls}),e}}async function WW(e,t){let{address:n,chain:r=e.chain,delegation:i,...a}=t;try{let t=await e.request({method:`wallet_prepareUpgradeAccount`,params:[RV(jW.Parameters,LB({address:n,capabilities:a,chainId:r?.id,delegation:i}))]},{retryCount:0});return zV(jW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function GW(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;try{let t=await e.request({method:`wallet_sendPreparedCalls`,params:[RV(NW.Parameters,{capabilities:n,context:{preCall:r.preCall,quote:r.quote},key:i?{prehash:i.prehash,publicKey:i.publicKey,type:i.type}:void 0,signature:a})]},{retryCount:0});return zV(NW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function KW(e,t){let{email:n,walletAddress:r}=t;try{let t=await e.request({method:`account_setEmail`,params:[RV(_W.Parameters,{email:n,walletAddress:r})]},{retryCount:0});return zV(_W.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function qW(e,t){let{context:n,signatures:r}=t;try{await e.request({method:`wallet_upgradeAccount`,params:[RV(PW.Parameters,{context:n,signatures:r})]},{retryCount:0})}catch(e){throw $W(e),ZW(e),e}}async function JW(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;try{let t=await e.request({method:`account_verifyEmail`,params:[RV(yW.Parameters,{chainId:n,email:r,signature:i,token:a,walletAddress:o})]},{retryCount:0});return zV(yW.Response,t)}catch(e){throw $W(e),ZW(e),e}}async function YW(e,t){let{signature:n}=t,{signature:r,capabilities:{feeSignature:i,...a},...o}=t.response,s=QW({capabilities:a,...o}),c=rR({payload:ML(eL(JSON.stringify(s))),signature:fR(n)}),{quoteSigner:l}=await HW(e);return c===l}async function XW(e,t){let{address:n,chain:r=e.chain,digest:i,signature:a}=t;try{async function t(){return{proof:null,valid:await vv(e,{address:n,hash:i,signature:a})}}let o=await(async()=>{let o=await e.request({method:`wallet_verifySignature`,params:[RV(FW.Parameters,{address:n,chainId:r?.id,digest:i,signature:a})]},{retryCount:0}).catch(t);return o.valid?o:t()})();return zV(FW.Response,o)}catch(e){throw $W(e),e}}function ZW(e,{calls:t}={}){if(!(e instanceof E))return;let n=e=>{try{if(e.name===`ContractFunctionExecutionError`){let t=e.cause.name===`ContractFunctionRevertedError`?e.cause.data:void 0;if(t)return PU([t.abiItem],t.errorName)}let t=e.walk(e=>!(e instanceof Error)&&e.code===3);if(!t)return;let{data:n,message:r}=t;return n===`0xd0d5039b`?NU(`error Unauthorized()`):{inputs:[],name:(r??n).split(`(`)[0],type:`error`}}catch{return}},r=BU(e,{calls:t??[]}),i=n(r);if(!(r===e&&!i))throw new eG(Object.assign(r,{abiError:i}))}function QW(e){if(typeof e==`object`&&e){if(Array.isArray(e))return e.map(QW);let t={};for(let n of Object.keys(e).sort())t[n]=QW(e[n]);return t}return e}function $W(e){if(e.name===`$ZodError`)throw WU(e)}var eG=class extends z{constructor(e){super(`An error occurred while executing calls.`,{cause:e,metaMessages:[e.abiError&&`Reason: `+e.abiError.name].filter(Boolean)}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Rpc.ExecutionError`}),Object.defineProperty(this,`abiError`,{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abiError=e.abiError}},tG={anvil:{http:`http://localhost:9119`},prod:{http:`https://rpc.porto.sh`},stg:{http:`https://stg-rpc.porto.sh`}};function nG(e){return t=>{let n=e.public(t),r=e.relay(t);return qv({key:nG.type,name:`Relay Proxy`,async request({method:e,params:t},i){return rG(e)?r.request({method:e,params:t},i):n.request({method:e,params:t},i)},type:nG.type})}}(function(e){e.type=`relayProxy`})(nG||={});function rG(e){return!!(e.startsWith(`wallet_`)||e.startsWith(`account_`)||e===`health`)}var iG=new Map;function aG(e,t={}){let{config:n,id:r,store:i}=e._internal,{chains:a,relay:o}=n,s=i.getState(),c=t.chainId??s.chainIds[0],l=a.find(e=>e.id===c);if(!l)throw Error([`Could not find a compatible Porto chain on the given chain configuration.`,``,`Provided chains: [${a.map(e=>`${e.name} (id: ${e.id})`).join(`, `)}]`,`Needed chain (id): ${c}`,`Please add this chain (id) to your chain configuration.`].join(` -`));let u=nG({public:n.transports[l.id]??Yv(l.rpcUrls.default.http.map(e=>$v(e))),relay:o}),d=[r,ZF(l)].filter(Boolean).join(`:`);if(iG.has(d))return iG.get(d);let f=rh({...t,chain:l,pollingInterval:1e3,transport:u});return iG.set(d,f),f}var oG=W({chainId:J(Q()),expiry:Q(),hash:Z(),id:Z(),prehash:J(QH()),publicKey:Z(),role:G([q(`admin`),q(`session`)]),type:G([q(`address`),q(`p256`),q(`secp256k1`),q(`webauthn-p256`)])}),sG=Y(U(HU([W({signature:V(),to:X()}),W({signature:V()}),W({to:X()})])).check(KH(1))),cG=W({limit:G([MU([H(),`.`,H()]),MU([H()])]).check(qH(/^\d+(\.\d+)?$/)),symbol:J(G([q(`native`),uW]))}),lG=W({addresses:Y(U(X()))}),uG=Y(U(W({limit:VU(),period:G([q(`minute`),q(`hour`),q(`day`),q(`week`),q(`month`),q(`year`)]),token:J(X())}))),dG=W({calls:J(sG),signatureVerification:J(lG),spend:J(uG)}),fG=W({...oG.shape,feeToken:J(TU(cG)),permissions:J(dG)}),pG=W({address:X(),chainId:J(Q()),expiry:H(),id:Z(),key:pU(oG,{publicKey:!0,type:!0}),permissions:W({calls:sG,signatureVerification:J(lG),spend:J(uG)})}),mG=W({address:J(X()),chainId:J(Q()),expiry:H().check(GH(1)),feeToken:TU(cG),key:J(pU(oG,{publicKey:!0,type:!0})),permissions:W({calls:sG,signatureVerification:J(lG),spend:J(uG)})}),hG=pG;function gG(e,t){let{chainId:n,expiry:r,permissions:i,id:a,publicKey:o,type:s}=e,{address:c}=t;return{address:c,chainId:n,expiry:r,id:a,key:{publicKey:o,type:s},permissions:i??{}}}function _G(e){let{chainId:t,expiry:n,key:r}=e;return dB({chainId:t,expiry:n,permissions:e.permissions??{},publicKey:r.publicKey,role:`session`,type:r.type})}var vG;(function(e){e.GetCapabilitiesResponse=W({status:G([q(`supported`),q(`unsupported`)])})})(vG||={});var yG;(function(e){e.Request=G([QH(),W({chainId:J(Q()),label:J(V())})])})(yG||={});var bG;(function(e){e.Request=HU([W({chainId:J(H()),domain:J(V()),expirationTime:J(uU()),issuedAt:J(uU()),nonce:V(),notBefore:J(uU()),requestId:J(V()),resources:J(Y(U(V()))),scheme:J(V()),statement:J(V()),uri:J(V()),version:J(q(`1`))}),W({authUrl:G([V(),W({logout:V(),nonce:V(),verify:V()})]),chainId:J(Q()),domain:J(V()),expirationTime:J(uU()),issuedAt:J(uU()),notBefore:J(uU()),requestId:J(V()),resources:J(Y(U(V()))),scheme:J(V()),statement:J(V()),uri:J(V()),version:J(q(`1`))})]),e.Response=W({message:V(),signature:Z(),token:J(V())})})(bG||={});var xG;(function(e){e.GetCapabilitiesResponse=W({supported:QH(),tokens:Y(U(lW))}),e.Request=G([uW,X()])})(xG||={});var SG;(function(e){e.Request=mG})(SG||={});var CG;(function(e){e.GetCapabilitiesResponse=W({supported:QH()})})(CG||={});var wG;(function(e){e.GetCapabilitiesResponse=W({supported:QH()}),e.Request=W({id:J(G([Z(),iU()]))}),e.Response=Y(U(pG))})(wG||={});var TG;(function(e){e.Request=Y(U(W({context:cU(),signature:Z()}))),e.Response=e.Request})(TG||={});var EG;(function(e){e.Request=V()})(EG||={});var DG;(function(e){e.GetCapabilitiesResponse=W({supported:QH(),tokens:Y(U(lW))}),e.Request=Y(U(HU([W({address:X(),value:VU()}),W({symbol:uW,value:G([MU([H(),`.`,H()]),MU([H()])]).check(qH(/^\d+(\.\d+)?$/))})])))})(DG||={});var OG=W({...pU(oG,{id:!0,publicKey:!0,type:!0}).shape,credentialId:J(V()),privateKey:J(oU())}),kG;(function(e){e.Parameters=W({address:X(),secret:V()}),e.Request=W({method:q(`account_getOnrampContactInfo`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(V()),phone:J(V()),phoneVerifiedAt:J(H())})})(kG||={});var AG;(function(e){e.Parameters=W({address:X()}),e.Request=W({method:q(`account_onrampStatus`),params:Y(K([e.Parameters]))}),e.Response=W({email:J(H()),phone:J(H())})})(AG||={});var jG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_resendVerifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(jG||={});var MG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_setEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(MG||={});var NG;(function(e){e.Parameters=W({email:V(),walletAddress:X()}),e.Request=W({method:q(`account_setPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(NG||={});var PG;(function(e){e.Parameters=W({chainId:Q(),email:V(),token:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyEmail`),params:Y(K([e.Parameters]))}),e.Response=iU()})(PG||={});var FG;(function(e){e.Parameters=W({code:V(),phone:V(),walletAddress:X()}),e.Request=W({method:q(`account_verifyPhone`),params:Y(K([e.Parameters]))}),e.Response=iU()})(FG||={});var IG;(function(e){e.Parameters=W({address:J(X()),chainId:J(Q()),token:J(X()),value:J(V())}),e.Request=W({method:q(`wallet_addFunds`),params:Y(K([e.Parameters]))}),e.Response=W({id:Z()})})(IG||={});var LG;(function(e){e.Request=W({method:q(`eth_accounts`),params:J(cU())}),e.Response=Y(U(X()))})(LG||={});var RG;(function(e){e.Request=W({method:q(`eth_chainId`),params:J(cU())}),e.Response=Z()})(RG||={});var zG;(function(e){e.Request=W({method:q(`eth_requestAccounts`),params:J(cU())}),e.Response=Y(U(X()))})(zG||={});var BG;(function(e){e.Request=W({method:q(`eth_sendTransaction`),params:Y(K([W({capabilities:J(W({feeToken:J(xG.Request),merchantUrl:J(EG.Request),preCalls:J(TG.Request)})),chainId:J(Q()),data:J(Z()),from:J(X()),to:X(),value:J(VU())})]))}),e.Response=Z()})(BG||={});var VG;(function(e){e.Request=W({method:q(`eth_signTypedData_v4`),params:Y(K([X(),V()]))}),e.Response=Z()})(VG||={});var HG;(function(e){e.Parameters=W({address:J(X()),chainId:J(Q())}),e.Request=W({method:q(`wallet_getAdmins`),params:J(Y(K([e.Parameters])))}),e.Key=OG,e.Response=W({address:X(),chainId:Q(),keys:Y(U(e.Key))})})(HG||={});var UG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),chainId:J(Q()),key:pU(oG,{publicKey:!0,type:!0})}),e.Request=W({method:q(`wallet_grantAdmin`),params:Y(K([e.Parameters]))}),e.Response=W({address:X(),chainId:Q(),key:HG.Key})})(UG||={});var WG;(function(e){e.Parameters=mG,e.Request=W({method:q(`wallet_grantPermissions`),params:Y(K([e.Parameters]))}),e.ResponseCapabilities=W({preCalls:J(TG.Response)}),e.Response=W({...pG.shape,capabilities:J(oU())})})(WG||={});var GG;(function(e){e.Parameters=W({address:J(X())}),e.Request=W({method:q(`wallet_getAccountVersion`),params:J(Y(K([e.Parameters])))}),e.Response=W({current:V(),latest:V()})})(GG||={});var KG;(function(e){e.Parameters=W({address:J(X()),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getPermissions`),params:J(Y(K([e.Parameters])))}),e.Response=wG.Response})(KG||={});var qG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),chainId:J(Q()),id:Z()}),e.Request=W({method:q(`wallet_revokeAdmin`),params:Y(K([e.Parameters]))}),e.Response=void 0})(qG||={});var JG;(function(e){e.Capabilities=W({feeToken:J(xG.Request)}),e.Parameters=W({address:J(X()),capabilities:J(e.Capabilities),id:Z()}),e.Request=W({method:q(`wallet_revokePermissions`),params:Y(K([e.Parameters]))}),e.Response=void 0})(JG||={});var YG;(function(e){e.Request=W({method:q(`wallet_switchEthereumChain`),params:Y(K([W({chainId:Z()})]))})})(YG||={});var XG;(function(e){e.Parameters=W({context:cU(),signatures:W({auth:Z(),exec:Z()})}),e.Request=W({method:q(`wallet_upgradeAccount`),params:Y(K([e.Parameters]))}),e.ResponseCapabilities=W({admins:J(Y(U(HG.Key))),permissions:J(wG.Response)}),e.Response=W({address:X(),capabilities:J(e.ResponseCapabilities)})})(XG||={});var ZG;(function(e){e.Request=W({method:q(`personal_sign`),params:Y(K([Z(),X()]))}),e.Response=Z()})(ZG||={});var QG;(function(e){e.Request=W({method:q(`porto_ping`),params:J(nU())}),e.Response=q(`pong`)})(QG||={});var $G;(function(e){e.Capabilities=W({createAccount:J(yG.Request),email:J(QH()),grantAdmins:J(Y(U(pU(oG,{publicKey:!0,type:!0})))),grantPermissions:J(SG.Request),preCalls:J(TG.Request),selectAccount:J(G([QH(),W({address:X(),key:J(W({credentialId:J(V()),publicKey:Z()}))})])),signInWithEthereum:J(bG.Request)}),e.Parameters=W({capabilities:J(e.Capabilities),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_connect`),params:J(Y(K([e.Parameters])))}),e.ResponseCapabilities=W({admins:J(Y(U(W({...pU(oG,{id:!0,publicKey:!0,type:!0}).shape,credentialId:J(V())})))),permissions:J(wG.Response),preCalls:J(TG.Response),signInWithEthereum:J(bG.Response)}),e.Response=W({accounts:Y(U(W({address:X(),capabilities:J(e.ResponseCapabilities)}))),chainIds:Y(U(Q()))})})($G||={});var eK;(function(e){e.Request=W({method:q(`wallet_disconnect`),params:J(cU())}),e.Response=void 0})(eK||={});var tK;(function(e){e.Parameters=EW.Parameters,e.Request=EW.Request,e.Response=EW.Response})(tK||={});var nK;(function(e){e.Request=W({method:q(`wallet_getCallsStatus`),params:K([Z()])}),e.Response=W({atomic:QH(),chainId:Q(),id:V(),receipts:J(Y(U(W({blockHash:Z(),blockNumber:Z(),gasUsed:Z(),logs:Y(U(W({address:X(),data:Z(),topics:Y(U(Z()))}))),status:Z(),transactionHash:Z()})))),status:H(),version:V()})})(nK||={});var rK;(function(e){e.Parameters=OW.Parameters,e.Request=OW.Request,e.Transaction=OW.Transaction,e.Capabilities=OW.Capabilities,e.Entry=OW.Entry,e.Response=OW.Response})(rK||={});var iK;(function(e){e.Request=W({method:q(`wallet_getCapabilities`),params:J(G([Y(K([G([Z(),nU()])])),Y(K([G([Z(),nU()]),Y(U(Q()))]))]))}),e.Response=xU(Z(),W({atomic:vG.GetCapabilitiesResponse,feeToken:xG.GetCapabilitiesResponse,merchant:CG.GetCapabilitiesResponse,permissions:wG.GetCapabilitiesResponse,requiredFunds:DG.GetCapabilitiesResponse}))})(iK||={});var aK;(function(e){e.Parameters=W({address:X(),chainIds:J(Y(U(Q())))}),e.Request=W({method:q(`wallet_getKeys`),params:Y(K([e.Parameters]))}),e.Response=Y(U(fG))})(aK||={});var oK;(function(e){e.Capabilities=W({feeToken:J(xG.Request),merchantUrl:J(EG.Request),permissions:J(wG.Request),preCalls:J(TG.Request),requiredFunds:J(DG.Request)}),e.Parameters=W({calls:Y(U(W({data:J(Z()),to:X(),value:J(VU())}))),capabilities:J(e.Capabilities),chainId:J(Q()),from:J(X()),key:J(pU(oG,{prehash:!0,publicKey:!0,type:!0})),version:J(V())}),e.Request=W({method:q(`wallet_prepareCalls`),params:Y(K([e.Parameters]))}),e.Response=W({capabilities:J(W({...AW.ResponseCapabilities.shape,quote:J(cW)})),chainId:Z(),context:W({account:W({address:X()}),calls:e.Parameters.shape.calls,nonce:VU(),quote:J(hU(cW))}),digest:Z(),key:pU(oG,{prehash:!0,publicKey:!0,type:!0}),typedData:W({domain:G([W({chainId:Q(),name:V(),verifyingContract:X(),version:V()}),W({})]),message:xU(V(),cU()),primaryType:V(),types:xU(V(),cU())})})})(oK||={});var sK;(function(e){e.Capabilities=W({...$G.Capabilities.shape,label:J(V())}),e.Parameters=W({address:X(),capabilities:J(e.Capabilities),chainId:J(Q())}),e.Request=W({method:q(`wallet_prepareUpgradeAccount`),params:Y(K([e.Parameters]))}),e.Response=W({context:cU(),digests:W({auth:Z(),exec:Z()})})})(sK||={});var cK;(function(e){e.Capabilities=oK.Capabilities,e.Request=W({method:q(`wallet_sendCalls`),params:Y(K([mU(oK.Parameters,{key:!0})]))}),e.Response=W({id:Z()})})(cK||={});var lK;(function(e){e.Parameters=W({capabilities:oK.Response.shape.capabilities,chainId:Z(),context:oK.Response.shape.context,key:oK.Response.shape.key,signature:Z()}),e.Request=W({method:q(`wallet_sendPreparedCalls`),params:Y(K([e.Parameters]))}),e.Response=Y(U(W({capabilities:J(xU(V(),cU())),id:Z()})))})(lK||={});var uK;(function(e){e.Parameters=W({address:X(),chainId:J(Q()),digest:Z(),signature:Z()}),e.Request=W({method:q(`wallet_verifySignature`),params:Y(K([e.Parameters]))}),e.Response=W({address:X(),chainId:Q(),proof:J(cU()),valid:QH()})})(uK||={});var dK=vU(`method`,[PG.Request,IG.Request,LG.Request,RG.Request,zG.Request,BG.Request,VG.Request,GG.Request,HG.Request,KG.Request,UG.Request,WG.Request,sK.Request,qG.Request,JG.Request,XG.Request,ZG.Request,QG.Request,$G.Request,eK.Request,tK.Request,nK.Request,rK.Request,iK.Request,aK.Request,oK.Request,cK.Request,lK.Request,YG.Request,uK.Request]);function fK(e,t){let n=IV(e,t);if(n.error){let e=n.error.issues.at(0);throw e?.code===`invalid_union`&&e.note===`No matching discriminator`?new lI:new mI(WU(n.error))}return{...t,_decoded:n.data}}async function pK(e){e.persist.hasHydrated()||await new Promise(t=>{e.persist.onFinishHydration(()=>t(!0)),setTimeout(()=>t(!0),100)})}function mK(e){if(e)return e.startsWith(`/`)?`${window.location.origin}${e}`:e}function hK(e){let{config:t,getMode:n,id:r,store:i}=e,{announceProvider:a}=t;function o(e={}){let a=s(),o=e.request??fK(dK,{method:`wallet_getCapabilities`,params:e.chainIds?[void 0,e.chainIds]:void 0});return Mm(()=>n().actions.getCapabilities({chainIds:e.chainIds,internal:{client:a,config:t,request:o,store:i}}),{cacheKey:`getCapabilities.${r}.${e.chainIds?.join(`,`)}`})}function s(t){let n=typeof t==`string`?sL(t):t;return aG({_internal:e},{chainId:n})}let c=new Map,l=[],u=jI(),d=MI({...u,async request(e){return await pK(i),BB(async()=>{let r;try{r=fK(dK,e)}catch(t){let n=t;if(!(n instanceof lI))throw n;if(e.method.startsWith(`wallet_`))throw new bI;return s().request(e)}let a=i.getState();switch(r.method){case`account_verifyEmail`:{if(a.accounts.length===0)throw new xI;let[e]=r._decoded.params,{chainId:o,email:c,token:l,walletAddress:u}=e,d=s(o);if(o&&o!==d.chain.id)throw new SI;let f=u?a.accounts.find(e=>YL(e.address,u)):a.accounts[0];if(!f)throw new yI;return await n().actions.verifyEmail({account:f,chainId:o,email:c,internal:{client:d,config:t,request:r,store:i},token:l,walletAddress:u})}case`wallet_addFunds`:{let{address:e,value:o,token:c}=r.params[0]??{},l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0],d=s(),f=await n().actions.addFunds({address:l?.address,internal:{client:d,config:t,request:r,store:i},token:c,value:o});return u.emit(`message`,{data:null,type:`assetsChanged`}),f}case`eth_accounts`:if(a.accounts.length===0)throw new xI;return a.accounts.map(yK);case`eth_chainId`:return $I(a.chainIds[0]);case`eth_requestAccounts`:{if(a.accounts.length>0&&c.get(`eth_requestAccounts`))return a.accounts.map(yK);let e=s(),{accounts:o}=await n().actions.loadAccounts({internal:{client:e,config:t,request:r,store:i}});return i.setState(e=>({...e,accounts:o})),u.emit(`connect`,{chainId:$I(e.chain.id)}),c.set(`eth_requestAccounts`,!0),setTimeout(()=>c.delete(`eth_requestAccounts`),1e3),o.map(yK)}case`eth_sendTransaction`:{let[{capabilities:e,chainId:o,data:c=`0x`,from:l,to:u,value:d}]=r._decoded.params,f=s(o);if(o&&o!==f.chain.id)throw new SI;let p=l?a.accounts.find(e=>YL(e.address,l)):a.accounts[0];if(l&&!p)throw new yI;let{id:m}=await n().actions.sendCalls({account:p,asTxHash:!0,calls:[{data:c,to:u,value:d}],chainId:f.chain.id,internal:{client:f,config:t,request:r,store:i},merchantUrl:mK(t.merchantUrl??e?.merchantUrl)});return m}case`eth_signTypedData_v4`:{if(a.accounts.length===0)throw new xI;let[e,o]=r._decoded.params,c=a.accounts.find(t=>YL(t.address,e));if(!c)throw new yI;let l=s();return await n().actions.signTypedData({account:c,data:o,internal:{client:l,config:t,request:r,store:i}})}case`wallet_grantAdmin`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,chainId:c,key:l}]=r._decoded.params??[{}],d=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!d)throw new yI;let f=s(c);if(_K([...d.keys??[]])?.some(e=>e.publicKey?.toLowerCase()===l.publicKey.toLowerCase()))throw new mI({message:`Key already granted as admin.`});let{key:p}=await n().actions.grantAdmin({account:d,feeToken:o?.feeToken,internal:{client:f,config:t,request:r,store:i},key:l});i.setState(e=>{let t=e.accounts.findIndex(e=>d?YL(e.address,d.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],p]}:e)}});let m=_K([...d.keys??[],p]);return u.emit(`message`,{data:null,type:`adminsChanged`}),RV(UG.Response,{address:d.address,chainId:f.chain.id,key:m.at(-1)})}case`wallet_grantPermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainId:o,...c}]=r._decoded.params??[{}],l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s(o),{key:f}=await n().actions.grantPermissions({account:l,internal:{client:d,config:t,request:r,store:i},permissions:c});return i.setState(e=>{let t=e.accounts.findIndex(e=>l?YL(e.address,l.address):!0);return t===-1?e:{...e,accounts:e.accounts.map((e,n)=>n===t?{...e,keys:[...e.keys??[],f]}:e)}}),u.emit(`message`,{data:null,type:`permissionsChanged`}),RV(WG.Response,{...gG(f,{address:l.address})})}case`wallet_getAdmins`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainId:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!c)throw new yI;let l=s(o),u=_K(await n().actions.getKeys({account:c,internal:{client:l,config:t,request:r,store:i}}));return RV(HG.Response,{address:c.address,chainId:l.chain.id,keys:u})}case`wallet_prepareUpgradeAccount`:{let[{address:e,capabilities:a,chainId:o}]=r._decoded.params??[{}],{email:c,label:u,grantPermissions:d}=a??{},f=s(o),{context:p,digests:m}=await n().actions.prepareUpgradeAccount({address:e,email:c,internal:{client:f,config:t,request:r,store:i},label:u,permissions:d});return l.push(p.account),{context:p,digests:m}}case`wallet_getAccountVersion`:{if(a.accounts.length===0)throw new xI;let[{address:e}]=r._decoded.params??[{}],o=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!o)throw new yI;let c=s(),{current:l,latest:u}=await n().actions.getAccountVersion({address:o.address,internal:{client:c,config:t,request:r,store:i}});return{current:l,latest:u}}case`wallet_getKeys`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=a.accounts.find(t=>YL(t.address,e));if(!c)throw new yI;let l=s(),u=await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}});return RV(aK.Response,u)}case`wallet_getPermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,chainIds:o}]=r._decoded.params??[{}],c=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!c)throw new yI;let l=s();return vK(await n().actions.getKeys({account:c,chainIds:o,internal:{client:l,config:t,request:r,store:i}}),{address:c.address})}case`wallet_revokeAdmin`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s();await n().actions.revokeAdmin({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>YL(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`adminsChanged`});return}case`wallet_revokePermissions`:{if(a.accounts.length===0)throw new xI;let[{address:e,capabilities:o,id:c}]=r._decoded.params,l=e?a.accounts.find(t=>YL(t.address,e)):a.accounts[0];if(!l)throw new yI;let d=s();await n().actions.revokePermissions({account:l,feeToken:o?.feeToken,id:c,internal:{client:d,config:t,request:r,store:i}});let f=l.keys?.filter(e=>e.id.toLowerCase()!==c.toLowerCase());i.setState(e=>({...e,accounts:e.accounts.map(e=>YL(e.address,l.address)?{...e,keys:f}:e)})),u.emit(`message`,{data:null,type:`permissionsChanged`});return}case`wallet_upgradeAccount`:{let[{context:e,signatures:a}]=r._decoded.params??[{}],o=s(),c=l.find(t=>YL(t.address,e.account.address));if(!c)throw new yI;let{account:d}=await n().actions.upgradeAccount({account:c,context:e,internal:{client:o,config:t,request:r,store:i},signatures:a}),f=_K(d.keys??[]),p=vK(d.keys??[],{address:d.address});return i.setState(e=>({...e,accounts:[d]})),u.emit(`connect`,{chainId:$I(o.chain.id)}),{address:d.address,capabilities:{admins:f,...p.length>0?{permissions:p}:{}}}}case`porto_ping`:return`pong`;case`personal_sign`:{if(a.accounts.length===0)throw new xI;let[e,o]=r._decoded.params,c=a.accounts.find(e=>YL(e.address,o));if(!c)throw new yI;let l=s();return await n().actions.signPersonalMessage({account:c,data:e,internal:{client:l,config:t,request:r,store:i}})}case`wallet_connect`:{let[{capabilities:e,chainIds:o}]=r._decoded.params??[{}],c=s(o?.[0]),l=c.chain.id,{createAccount:d,email:f,grantAdmins:p,grantPermissions:m,selectAccount:h,signInWithEthereum:g}=e??{},_={client:c,config:t,request:r,store:i},{accounts:v}=await(async()=>{if(f||d){let{label:e=void 0}=typeof d==`object`?d:{},{account:t}=await n().actions.createAccount({admins:p,email:f,internal:_,label:e,permissions:m,signInWithEthereum:g});return{accounts:[t]}}let e=a.accounts[0],{address:t,key:r}=(()=>{if(h)return typeof h==`object`?h:{address:void 0,key:void 0};for(let t of e?.keys??[])if(t.type===`webauthn-p256`&&t.role===`admin`)return{address:e?.address,key:{credentialId:t.credentialId??t.privateKey?.credential?.id,publicKey:t.publicKey}};return{address:void 0,key:void 0}})(),i={internal:_,permissions:m,signInWithEthereum:g};try{return await n().actions.loadAccounts({address:t,key:r,...i})}catch(e){if(e instanceof vI)throw e;if(t&&r)return await n().actions.loadAccounts(i);throw e}})();i.setState(e=>({...e,accounts:v}));let y=[l,...i.getState().chainIds.filter(e=>e!==l)];return u.emit(`connect`,{chainId:$I(y[0])}),{accounts:v.map(e=>({address:yK(e),capabilities:{admins:e.keys?_K(e.keys):[],permissions:e.keys?vK(e.keys,{address:e.address}):[],...e.signInWithEthereum&&{signInWithEthereum:e.signInWithEthereum}}})),chainIds:y.map(e=>$I(e))}}case`wallet_disconnect`:{let e=s();await n().actions.disconnect?.({internal:{client:e,config:t,request:r,store:i}}),i.setState(e=>({...e,accounts:[]})),u.emit(`disconnect`,new xI);return}case`wallet_getAssets`:{let[e]=r._decoded.params??[],{account:a,chainFilter:o,assetFilter:c,assetTypeFilter:l}=e,u=s(),d=await n().actions.getAssets({account:a,assetFilter:c,assetTypeFilter:l,chainFilter:o,internal:{client:u,config:t,request:r,store:i}}),f=Object.entries(d).reduce((e,[t,n])=>(e[$I(Number(t))]=n,e),{});return RV(tK.Response,f)}case`wallet_getCallsStatus`:{let[e]=r._decoded.params??[],a=s();return await n().actions.getCallsStatus({id:e,internal:{client:a,config:t,request:r,store:i}})}case`wallet_getCallsHistory`:{let[e]=r._decoded.params??[],a=s(),o=await n().actions.getCallsHistory({...e,internal:{client:a,config:t,request:r,store:i}});return RV(rK.Response,o)}case`wallet_getCapabilities`:{let[e,t]=r.params??[];return await o({chainIds:t,request:r})}case`wallet_prepareCalls`:{let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,key:u,from:d}=e,f=s(l),p=d??a.accounts[0];if(!p)throw new yI;if(l&&l!==f.chain.id)throw new SI;let{digest:m,...h}=await n().actions.prepareCalls({account:TB(p),calls:o,feeToken:c?.feeToken,internal:{client:f,config:t,request:r,store:i},key:u,merchantUrl:mK(t.merchantUrl??c?.merchantUrl),requiredFunds:c?.requiredFunds});return RV(oK.Response,{capabilities:h.capabilities,chainId:$I(h.chainId??f.chain.id),context:{...h.context,account:{address:h.account.address},calls:h.context.calls??[],nonce:h.context.nonce??0n},digest:m,key:h.key,typedData:h.typedData})}case`wallet_sendPreparedCalls`:{let[e]=r._decoded.params,{chainId:a,context:o,key:c,signature:l}=e,{account:u}=e.context,d=s(a);if(a&&sL(a)!==d.chain.id)throw new SI;return[{id:await n().actions.sendPreparedCalls({account:TB(u),context:o,internal:{client:d,config:t,request:r,store:i},key:c,signature:l})}]}case`wallet_sendCalls`:{let[e]=r._decoded.params,{calls:o,capabilities:c,chainId:l,from:u}=e,d=s(l);if(l&&l!==d.chain.id)throw new SI;let f=u?a.accounts.find(e=>YL(e.address,u)):a.accounts[0];if(u&&!f)throw new yI;let{id:p}=await n().actions.sendCalls({account:f,calls:o,chainId:d.chain.id,feeToken:c?.feeToken,internal:{client:d,config:t,request:r,store:i},merchantUrl:mK(t.merchantUrl??c?.merchantUrl),permissionsId:c?.permissions?.id,requiredFunds:c?.requiredFunds});return{id:p}}case`wallet_switchEthereumChain`:{let[e]=r._decoded.params,{chainId:a}=e,o=sL(a);if(!t.chains.find(e=>e.id===o))throw new TI;let c=s(a);await n().actions.switchChain?.({chainId:c.chain.id,internal:{client:c,config:t,request:r,store:i}}),i.setState(e=>({...e,chainIds:[o,...e.chainIds.filter(e=>e!==o)]}));return}case`wallet_verifySignature`:{let[e]=r._decoded.params,{address:t,chainId:n,digest:i,signature:a}=e,o=s(n);return{...await XW(o,{address:t,digest:i,signature:a}),address:t,chainId:$I(o.chain.id)}}}},{enabled:[`eth_accounts`,`eth_chainId`,`eth_requestAccounts`,`wallet_getAssets`,`wallet_getCapabilities`,`wallet_getKeys`,`wallet_getPermissions`,`wallet_getAccountVersion`,`wallet_connect`].includes(e.method),id:ZF(e)})}});function f(){let e=()=>{},t=()=>{};pK(i).then(()=>{o().catch(()=>{}),e(),e=i.subscribe(e=>e.accounts,e=>{u.emit(`accountsChanged`,e.map(yK))},{equalityFn:(e,t)=>e.every((e,n)=>e.address===t[n]?.address)}),t(),t=i.subscribe(e=>e.chainIds[0],(e,t)=>{e!==t&&u.emit(`chainChanged`,$I(e))})});let n=gK(d,a);return()=>{e(),t(),n()}}let p=f();return Object.assign(d,{_internal:{destroy:p}})}function gK(e,t){if(!t||typeof window>`u`||!window.dispatchEvent)return()=>{};let{icon:n=`data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDIyIiBoZWlnaHQ9IjQyMiIgdmlld0JveD0iMCAwIDQyMiA0MjIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MjIiIGhlaWdodD0iNDIyIiBmaWxsPSJibGFjayIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDBfMV8xNSkiPgo8cGF0aCBkPSJNODEgMjg2LjM2NkM4MSAyODAuODkzIDg1LjQ1MDUgMjc2LjQ1NSA5MC45NDA0IDI3Ni40NTVIMzI5LjUxMUMzMzUuMDAxIDI3Ni40NTUgMzM5LjQ1MiAyODAuODkzIDMzOS40NTIgMjg2LjM2NlYzMDYuMTg4QzMzOS40NTIgMzExLjY2MiAzMzUuMDAxIDMxNi4wOTkgMzI5LjUxMSAzMTYuMDk5SDkwLjk0MDRDODUuNDUwNSAzMTYuMDk5IDgxIDMxMS42NjIgODEgMzA2LjE4OFYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAyMzQuODI4Qzg1LjQ1MDUgMjM0LjgyOCA4MSAyMzkuMjY2IDgxIDI0NC43MzlWMjcxLjUzMUM4My44NDMyIDI2OS42MzMgODcuMjYyMiAyNjguNTI2IDkwLjk0MDQgMjY4LjUyNkgzMjkuNTExQzMzMy4xODggMjY4LjUyNiAzMzYuNjA4IDI2OS42MzMgMzM5LjQ1MiAyNzEuNTMxVjI0NC43MzlDMzM5LjQ1MiAyMzkuMjY2IDMzNS4wMDEgMjM0LjgyOCAzMjkuNTExIDIzNC44MjhIOTAuOTQwNFpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgwLjg5MyAzMzUuMDAxIDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTlDODEgMzExLjY2NCA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2NCAzMzkuNDUyIDMwNi4xOVYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAxOTMuMjAxQzg1LjQ1MDUgMTkzLjIwMSA4MSAxOTcuNjM4IDgxIDIwMy4xMTJWMjI5LjkwM0M4My44NDMyIDIyOC4wMDYgODcuMjYyMiAyMjYuODk5IDkwLjk0MDQgMjI2Ljg5OUgzMjkuNTExQzMzMy4xODggMjI2Ljg5OSAzMzYuNjA4IDIyOC4wMDYgMzM5LjQ1MiAyMjkuOTAzVjIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNFpNMzM5LjQ1MiAyNDQuNzM5QzMzOS40NTIgMjM5LjI2NSAzMzUuMDAxIDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNDODEuMjE3NSAyNzEuMzg1IDgxLjQzODMgMjcxLjI0NSA4MS42NjI0IDI3MS4xMDlDODMuODMyNSAyNjkuNzk0IDg2LjMwNTQgMjY4LjkyNyA4OC45NTIzIDI2OC42MzVDODkuNjA1MSAyNjguNTYzIDkwLjI2ODQgMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMzAuMTgzIDI2OC41MjYgMzMwLjg0NiAyNjguNTYzIDMzMS40OTggMjY4LjYzNUMzMzQuNDE5IDI2OC45NTcgMzM3LjEyOCAyNjkuOTggMzM5LjQ1MiAyNzEuNTNWMjQ0LjczOVpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgxLjAyMSAzMzUuMjA2IDI3Ni42NjMgMzI5Ljg5MyAyNzYuNDYyQzMyOS43NjcgMjc2LjQ1NyAzMjkuNjQgMjc2LjQ1NSAzMjkuNTExIDI3Ni40NTVIOTAuOTQwNEM4NS40NTA1IDI3Ni40NTUgODEgMjgwLjg5MyA4MSAyODYuMzY2VjMwNi4xODhDODEgMzExLjY2MiA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2MiAzMzkuNDUyIDMwNi4xODhWMjg2LjM2NloiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNSIvPgo8cGF0aCBvcGFjaXR5PSIwLjMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTguMDE0NiAxMDRDODguNjE3NyAxMDQgODEgMTExLjU5NSA4MSAxMjAuOTY1VjE4OC4yNzZDODMuODQzMiAxODYuMzc5IDg3LjI2MjIgMTg1LjI3MiA5MC45NDA0IDE4NS4yNzJIMzI5LjUxMUMzMzMuMTg4IDE4NS4yNzIgMzM2LjYwOCAxODYuMzc5IDMzOS40NTIgMTg4LjI3NlYxMjAuOTY1QzMzOS40NTIgMTExLjU5NSAzMzEuODMzIDEwNCAzMjIuNDM3IDEwNEg5OC4wMTQ2Wk0zMzkuNDUyIDIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNEM4NS40NTA1IDE5My4yMDEgODEgMTk3LjYzOCA4MSAyMDMuMTEyVjIyOS45MDNDODEuMjE3NSAyMjkuNzU4IDgxLjQzODMgMjI5LjYxOCA4MS42NjI0IDIyOS40ODJDODMuODMyNSAyMjguMTY3IDg2LjMwNTQgMjI3LjMgODguOTUyMyAyMjcuMDA4Qzg5LjYwNTEgMjI2LjkzNiA5MC4yNjg0IDIyNi44OTkgOTAuOTQwNCAyMjYuODk5SDMyOS41MTFDMzMwLjE4MyAyMjYuODk5IDMzMC44NDYgMjI2LjkzNiAzMzEuNDk4IDIyNy4wMDhDMzM0LjQxOSAyMjcuMzMgMzM3LjEyOCAyMjguMzUyIDMzOS40NTIgMjI5LjkwM1YyMDMuMTEyWk0zMzkuNDUyIDI0NC43MzlDMzM5LjQ1MiAyMzkuMzkzIDMzNS4yMDYgMjM1LjAzNiAzMjkuODkzIDIzNC44MzVDMzI5Ljc2NyAyMzQuODMgMzI5LjY0IDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNMODEuMDcwNyAyNzEuNDgzQzgxLjI2NTMgMjcxLjM1NSA4MS40NjI1IDI3MS4yMyA4MS42NjI0IDI3MS4xMDlDODEuOTA4MyAyNzAuOTYgODIuMTU4MSAyNzAuODE3IDgyLjQxMTcgMjcwLjY3OUM4NC4zOTUzIDI2OS42MDUgODYuNjA1NCAyNjguODk0IDg4Ljk1MjMgMjY4LjYzNUM4OS4wMDUyIDI2OC42MjkgODkuMDU4IDI2OC42MjQgODkuMTExIDI2OC42MThDODkuNzEyNSAyNjguNTU3IDkwLjMyMjggMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMjkuNzM4IDI2OC41MjYgMzI5Ljk2NSAyNjguNTMgMzMwLjE5MiAyNjguNTM5QzMzMC42MzEgMjY4LjU1NSAzMzEuMDY3IDI2OC41ODcgMzMxLjQ5OCAyNjguNjM1QzMzNC40MTkgMjY4Ljk1NyAzMzcuMTI4IDI2OS45OCAzMzkuNDUyIDI3MS41M1YyNDQuNzM5Wk0zMzkuNDUyIDI4Ni4zNjZDMzM5LjQ1MiAyODEuMDIxIDMzNS4yMDYgMjc2LjY2MyAzMjkuODkzIDI3Ni40NjJMMzI5Ljg2NSAyNzYuNDYxQzMyOS43NDggMjc2LjQ1NyAzMjkuNjI5IDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTg4QzgxIDMxMS42NjIgODUuNDUwNSAzMTYuMTAxIDkwLjk0MDQgMzE2LjEwMUgzMjkuNTExQzMzNS4wMDEgMzE2LjEwMSAzMzkuNDUyIDMxMS42NjIgMzM5LjQ1MiAzMDYuMTg4VjI4Ni4zNjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjY5Ljg2OCAxMzEuNzUyQzI2OS44NjggMTI2LjI3OCAyNzQuMzE4IDEyMS44NCAyNzkuODA4IDEyMS44NEgzMTEuNjE4QzMxNy4xMDggMTIxLjg0IDMyMS41NTggMTI2LjI3OCAzMjEuNTU4IDEzMS43NTJWMTYxLjQ4NUMzMjEuNTU4IDE2Ni45NTkgMzE3LjEwOCAxNzEuMzk2IDMxMS42MTggMTcxLjM5NkgyNzkuODA4QzI3NC4zMTggMTcxLjM5NiAyNjkuODY4IDE2Ni45NTkgMjY5Ljg2OCAxNjEuNDg1VjEzMS43NTJaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzFfMTUiPgo8cmVjdCB3aWR0aD0iMjU5IiBoZWlnaHQ9IjIxMyIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDgxIDEwNCkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K`,name:r=`Porto`,rdns:i=`xyz.ithaca.porto`}=typeof t==`object`?t:{};return nV({info:{icon:n,name:r,rdns:i,uuid:zB()},provider:e})}function _K(e){return e.map(e=>{if(e.role===`admin`)try{return RV(HG.Key,{id:e.id??e.publicKey,publicKey:e.publicKey,type:e.type,...e.type===`webauthn-p256`?{credentialId:e.privateKey?.credential?.id,privateKey:{credential:{id:e.privateKey?.credential?.id},rpId:e.privateKey?.rpId}}:{}})}catch{return}}).filter(Boolean)}function vK(e,{address:t}){return e.map(e=>{if(e.chainId&&e.role===`session`&&!(e.expiry>0&&e.expiry()=>{})}}async function xK(e){let{account:t,calls:n,permissionsId:r}=e;if(r!==void 0){if(r===null)return;let e=t.keys?.find(e=>e.publicKey===r&&e.privateKey);if(!e)throw Error(`permission (id: ${r}) does not exist.`);return e}let i=t.keys?.find(e=>!e.privateKey||e.role!==`session`||e.expirye.permissions?.calls?.some(e=>{if(e.to&&e.to!==t.to)return!1;if(e.signature){if(!t.data)return!1;let n=rL(t.data,0,4);if(cL(e.signature))return e.signature===n;if(yz(e.signature)!==n)return!1}return!0}))),a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);return i??a}function SK(e={}){let t=e.id??0;return{prepare(e){return CK({id:t++,...e})},get id(){return t}}}function CK(e){return{...e,jsonrpc:`2.0`}}function wK(){return null}var TK=mG;function EK(e){let{expiry:t,feeToken:n,permissions:r,publicKey:i,type:a}=e;return{expiry:t,feeToken:n??null,key:{publicKey:i,type:a},permissions:r??{}}}async function DK(e,t={}){if(!e)return;let n={chainId:t.chainId??e.chainId,expiry:e.expiry??0,feeToken:e.feeToken,permissions:xB(e,{feeTokens:t.feeTokens}),role:`session`};if(e?.key)return dB({...n,publicKey:e.key.publicKey,type:e.key.type??`secp256k1`});if(typeof globalThis.crypto?.subtle?.generateKey==`function`)try{return await uB(n)}catch(e){if(!OK(e))throw e}return sB(n)}function OK(e){if(!(e instanceof Error))return!1;let t=e.message?.toLowerCase()??``;return e.name===`TypeError`||e.name===`ReferenceError`||t.includes(`subtle`)||t.includes(`generatekey`)}var kK=/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/,AK=/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:[0-9]{1,5})?$/,jK=/^localhost(:[0-9]{1,5})?$/,MK=/^[a-zA-Z0-9]{8,}$/,NK=/^([a-zA-Z][a-zA-Z0-9+-.]*)$/,PK=/^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/,FK=/(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/;function IK(e){let{chainId:t,domain:n,expirationTime:r,issuedAt:i=new Date,nonce:a,notBefore:o,requestId:s,resources:c,scheme:l,uri:u,version:d}=e;{if(t!==Math.floor(t))throw new BK({field:`chainId`,metaMessages:[`- Chain ID must be a EIP-155 chain ID.`,`- See https://eips.ethereum.org/EIPS/eip-155`,``,`Provided value: ${t}`]});if(!(kK.test(n)||AK.test(n)||jK.test(n)))throw new BK({field:`domain`,metaMessages:[`- Domain must be an RFC 3986 authority.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${n}`]});if(!MK.test(a))throw new BK({field:`nonce`,metaMessages:[`- Nonce must be at least 8 characters.`,`- Nonce must be alphanumeric.`,``,`Provided value: ${a}`]});if(!LK(u))throw new BK({field:`uri`,metaMessages:[`- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${u}`]});if(d!==`1`)throw new BK({field:`version`,metaMessages:[`- Version must be '1'.`,``,`Provided value: ${d}`]});if(l&&!NK.test(l))throw new BK({field:`scheme`,metaMessages:[`- Scheme must be an RFC 3986 URI scheme.`,`- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1`,``,`Provided value: ${l}`]});let r=e.statement;if(r?.includes(` -`))throw new BK({field:`statement`,metaMessages:[`- Statement must not include '\\n'.`,``,`Provided value: ${r}`]})}let f=qL(e.address,{checksum:!0}),p=`${l?`${l}://${n}`:n} wants you to sign in with your Ethereum account:\n${f}\n\n${e.statement?`${e.statement}\n`:``}`,m=`URI: ${u}\nVersion: ${d}\nChain ID: ${t}\nNonce: ${a}\nIssued At: ${i.toISOString()}`;if(r&&(m+=`\nExpiration Time: ${r.toISOString()}`),o&&(m+=`\nNot Before: ${o.toISOString()}`),s&&(m+=`\nRequest ID: ${s}`),c){let e=` -Resources:`;for(let t of c){if(!LK(t))throw new BK({field:`resources`,metaMessages:[`- Every resource must be a RFC 3986 URI.`,`- See https://www.rfc-editor.org/rfc/rfc3986`,``,`Provided value: ${t}`]});e+=`\n- ${t}`}m+=e}return`${p}\n${m}`}function LK(e){if(/[^a-z0-9:/?#[\]@!$&'()*+,;=.\-_~%]/i.test(e)||/%[^0-9a-f]/i.test(e)||/%[0-9a-f](:?[^0-9a-f]|$)/i.test(e))return!1;let t=RK(e),n=t[1],r=t[2],i=t[3],a=t[4],o=t[5];if(!(n?.length&&i&&i.length>=0))return!1;if(r?.length){if(!(i.length===0||/^\//.test(i)))return!1}else if(/^\/\//.test(i))return!1;if(!/^[a-z][a-z0-9+\-.]*$/.test(n.toLowerCase()))return!1;let s=``;return s+=`${n}:`,r?.length&&(s+=`//${r}`),s+=i,a?.length&&(s+=`?${a}`),o?.length&&(s+=`#${o}`),s}function RK(e){return e.match(/(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/)}function zK(e){let{scheme:t,statement:n,...r}=e.match(PK)?.groups??{},{chainId:i,expirationTime:a,issuedAt:o,notBefore:s,requestId:c,...l}=e.match(FK)?.groups??{},u=e.split(`Resources:`)[1]?.split(` -- `).slice(1);return{...r,...l,...i?{chainId:Number(i)}:{},...a?{expirationTime:new Date(a)}:{},...o?{issuedAt:new Date(o)}:{},...s?{notBefore:new Date(s)}:{},...c?{requestId:c}:{},...u?{resources:u}:{},...t?{scheme:t}:{},...n?{statement:n}:{}}}var BK=class extends z{constructor(e){let{field:t,metaMessages:n}=e;super(`Invalid Sign-In with Ethereum message field "${t}".`,{metaMessages:n}),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`Siwe.InvalidMessageFieldError`})}};async function VK(e){let{address:t,authUrl:n,message:r,signature:i,publicKey:a}=e,{chainId:o}=zK(r);return await fetch(n.verify,{body:JSON.stringify({address:t,chainId:o,message:r,signature:i,walletAddress:t,...a&&{publicKey:a}}),credentials:`include`,headers:{"Content-Type":`application/json`},method:`POST`}).then(e=>e.json())}async function HK(e,t,n){let{chainId:r=e.chain?.id,domain:i,uri:a,resources:o,version:s=`1`}=t,{address:c}=n,l=t.authUrl?UK(t.authUrl):void 0;if(!r)throw Error("`chainId` is required.");if(!i)throw Error("`domain` is required.");if(!t.nonce&&!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");if(!a)throw Error("`uri` is required.");let u=await(async()=>{if(t.nonce)return t.nonce;if(!l?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");let e=await(await fetch(l.nonce,{body:JSON.stringify({address:c,chainId:r,walletAddress:c}),headers:{"Content-Type":`application/json`},method:`POST`})).json().catch(()=>void 0);if(!e?.nonce)throw Error("`nonce` or `authUrl.nonce` is required.");return e.nonce})();return IK({...t,address:n.address,chainId:r,domain:i,nonce:u,resources:o,uri:a,version:s})}function UK(e,t=``){if(!e)return;let n=(()=>{if(typeof e==`string`){let t=e.replace(/\/$/,``);return{logout:t+`/logout`,nonce:t+`/nonce`,verify:t+`/verify`}}return e})();return{logout:WK(n.logout,t),nonce:WK(n.nonce,t),verify:WK(n.verify,t)}}function WK(e,t){return!t||!e.startsWith(`/`)?e:t+e}function GK(e){let t=XI(e);return YI(`0x19`,eL(`Ethereum Signed Message: -`+iL(t)),t)}function KK(e){return ML(GK(e))}function qK(e,t){let{as:n}=t,r=YK(e),i=zR(new Uint8Array(r.length));return r.encode(i),n===`Hex`?QI(i.bytes):i.bytes}function JK(e,t={}){let{as:n=`Hex`}=t;return qK(e,{as:n})}function YK(e){return Array.isArray(e)?XK(e.map(e=>YK(e))):ZK(e)}function XK(e){let t=e.reduce((e,t)=>e+t.length,0),n=QK(t);return{length:t<=55?1+t:1+n+t,encode(r){t<=55?r.pushByte(192+t):(r.pushByte(247+n),n===1?r.pushUint8(t):n===2?r.pushUint16(t):n===3?r.pushUint24(t):r.pushUint32(t));for(let{encode:t}of e)t(r)}}}function ZK(e){let t=typeof e==`string`?yL(e):e,n=QK(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+n+t.length,encode(e){t.length===1&&t[0]<128?e.pushBytes(t):t.length<=55?(e.pushByte(128+t.length),e.pushBytes(t)):(e.pushByte(183+n),n===1?e.pushUint8(t.length):n===2?e.pushUint16(t.length):n===3?e.pushUint24(t.length):e.pushUint32(t.length),e.pushBytes(t))}}}function QK(e){if(e<=255)return 1;if(e<=65535)return 2;if(e<=16777215)return 3;if(e<=4294967295)return 4;throw new z(`Length is too large.`)}function $K(e){return eq(e,{presign:!0})}function eq(e,t={}){let{presign:n}=t;return ML(YI(`0x05`,JK(tq(n?{address:e.address,chainId:e.chainId,nonce:e.nonce}:e))))}function tq(e){let{address:t,chainId:n,nonce:r}=e,i=pR(e);return[n?$I(n):`0x`,t,r?$I(r):`0x`,...i?vR(i):[]]}async function nq(e,t){let{account:n=e.account}=t,r=n?TB(n):void 0;if(!r)throw Error(`account is required.`);let{domain:{name:i,version:a}}=await oh(e,{address:r.address});if(!e.chain)throw Error(`client.chain is required`);return{chainId:e.chain.id,name:i,verifyingContract:r.address,version:a}}async function rq(e,t){let{account:n=e.account,chainIds:r}=t,i=n?TB(n):void 0;if(!i)throw Error(`account is required.`);let a=await VW(e,{address:i.address,chainIds:r});return Object.entries(a).flatMap(([e,t])=>t.map(t=>pB(t,{chainId:Number(e)})))}async function iq(e,t){let{account:n=e.account,calls:r,chain:i=e.chain,feePayer:a,merchantUrl:o,nonce:s,preCalls:c,requiredFunds:l,revokeKeys:u}=t,d=n?TB(n):void 0,f=t.key??(d?DB(d,{role:`admin`}):void 0),p=t.authorizeKeys?.some(e=>e.role===`session`),{contracts:m,fees:{tokens:h}}=await LW(e,{chainId:i?.id}),g=p?m.orchestrator.address:void 0,_=(t.authorizeKeys??[]).map(e=>bB(e,{feeTokens:h,orchestrator:g})),v=t.feeToken?t.feeToken:f?.permissions?.spend?.[0]?.token,y=typeof c==`boolean`?c:!1,b=typeof c==`object`?c.map(({context:e,signature:t})=>({...e.preCall,signature:t})):void 0,x={address:d?.address,calls:r??[],capabilities:{authorizeKeys:_,meta:{feePayer:a,feeToken:v,nonce:s},preCall:y,preCalls:b,requiredFunds:l,revokeKeys:u?.map(e=>({hash:e.hash}))},chain:i,key:f?bB(f,{feeTokens:h}):void 0},S=await(async()=>o?await UW(rh({chain:e.chain,transport:$v(o)}),x).catch(t=>(console.error(t),UW(e,x))):await UW(e,x))(),{capabilities:ee,context:C,digest:te,signature:ne,typedData:re}=S;if(o&&!await YW(e,{response:S._raw,signature:ne}))throw Error(`cannot verify integrity of \`wallet_prepareCalls\` response from ${o}`);return{capabilities:{...ee,quote:C.quote},context:C,digest:te,key:f,typedData:re}}async function aq(e,t){let{address:n,authorizeKeys:r,chain:i=e.chain}=t;if(!i)throw Error(`chain is required.`);let{contracts:a,fees:{tokens:o}}=await LW(e,{chainId:i.id}),s=t.delegation??a.accountProxy.address,c=r.some(e=>e.role===`session`)?a.orchestrator.address:void 0,{capabilities:l,chainId:u,context:d,digests:f,typedData:p}=await WW(e,{address:n,authorizeKeys:r.map(e=>{let t=e.role===`session`?e.permissions:{};return bB({...e,permissions:t},{feeTokens:o,orchestrator:c})}),chain:i,delegation:s}),m=TB({address:n,keys:r});return{capabilities:l,chainId:u,context:{...d,account:m},digests:f,typedData:p}}async function oq(e,t){let{account:n=e.account,chain:r=e.chain,webAuthn:i}=t;if(!r)throw Error("`chain` is required.");let a=n?TB(n):void 0;if(!a)throw Error("`account` is required.");let o=t.key??DB(a,t);if(!o&&!a.sign)throw Error("`key` or `account` with `sign` is required");let s=await Promise.all((t.preCalls??[]).map(async n=>{if(n.signature)return n;let{authorizeKeys:o,key:s,calls:c,revokeKeys:l}=n,{context:u,digest:d}=await iq(e,{account:a,authorizeKeys:o,calls:c,chain:r,feeToken:t.feeToken,key:s,preCalls:!0,revokeKeys:l});return{context:u,signature:await yB(s,{address:null,payload:d,webAuthn:i})}})),{capabilities:c,context:l,digest:u}=await iq(e,{...t,account:a,chain:r,key:o,preCalls:s}),d=await(async()=>o?await yB(o,{address:null,payload:u,webAuthn:i,wrap:!1}):await a.sign({hash:u}))();return await sq(e,{capabilities:c.feeSignature?{feeSignature:c.feeSignature}:void 0,context:l,key:o,signature:d})}async function sq(e,t){let{capabilities:n,context:r,key:i,signature:a}=t;return await GW(e,{capabilities:n,context:r,key:i?bB(i):void 0,signature:a})}async function cq(e,t){let{email:n,walletAddress:r}=t;return await KW(e,{email:n,walletAddress:r})}async function lq(e,t){if(t.account){let{account:n}=t,r=[...n.keys??[],...t.authorizeKeys??[]].filter((e,t,n)=>n.findIndex(t=>t.id===e.id)===t),{digests:i,...a}=await aq(e,{...t,address:n.address,authorizeKeys:r}),o={auth:await n.sign({hash:i.auth}),exec:await n.sign({hash:i.exec})};return await lq(e,{...a,signatures:o})}let{context:n,signatures:r}=t,i=TB(n.account);return await qW(e,{context:n,signatures:r}),i}async function uq(e,t){let{chainId:n,email:r,signature:i,token:a,walletAddress:o}=t;return await JW(e,{chainId:n,email:r,signature:i,token:a,walletAddress:o})}var dq=`0x8010801080108010801080108010801080108010801080108010801080108010`,fq=GR(`(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data`);function pq(e){if(typeof e==`string`){if(rL(e,-32)!==`0x8010801080108010801080108010801080108010801080108010801080108010`)throw new hq(e)}else uR(e.authorization)}function mq(e){let{data:t,signature:n}=e;pq(e);let r=rR({payload:$K(e.authorization),signature:mR(e.authorization)}),i=UR(fq,[{...e.authorization,delegation:e.authorization.address,chainId:BigInt(e.authorization.chainId)},e.to??r,t??`0x`]);return YI(n,i,$I(iL(i),{size:32}),dq)}var hq=class extends z{constructor(e){super(`Value \`${e}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,`name`,{enumerable:!0,configurable:!0,writable:!0,value:`SignatureErc8010.InvalidWrappedSignatureError`})}};async function gq(e,t){let{address:n}=t,{authorization:r,data:i,to:a}=await IW(e,{address:n});return mq({authorization:{...r,nonce:BigInt(r.nonce),r:BigInt(r.r),s:BigInt(r.s)},data:i,signature:t.signature,to:a})}function _q(e,t){let{tokens:n}=t,r=n.filter(e=>e.interop);return e.map(e=>{if(e.address)return e;let t=r.find(t=>t.symbol===e.symbol);if(!t)throw Error(`interop token not found: ${e.symbol}`);return{address:t.address,value:Az(e.value,t.decimals)}})}async function vq(e,t){let{chain:n=e.chain}=t??{};return await LW(e,{chainId:n?.id}).then(e=>e.fees.tokens)}async function yq(e,t){let{addressOrSymbol:n}=t;return(await vq(e,t)).find(yq.predicate(n))}(function(e){function t(e){return t=>e?XL(e)?YL(t.address,e):e===`native`?t.address===dv:e===t.symbol:!1}e.predicate=t})(yq||={});async function bq(e,t){let{chain:n=e.chain,store:r}=t??{},i=r?.getState()??{},a=t?.addressOrSymbol??i.feeToken;return(await vq(e,{chain:n}).then(e=>e.filter(e=>e.feeToken)))?.find(e=>a?a===`native`&&e.address===`0x0000000000000000000000000000000000000000`||XL(a)&&YL(e.address,a)?!0:a===e.symbol:!1)}function xq(e={}){let t=e,{mock:n,multichain:r=!0,webAuthn:i}=t,a,o,s=(()=>{if(t.keystoreHost!==`self`&&!(typeof window<`u`&&window.location?.hostname===`localhost`))return t.keystoreHost})();return bK({actions:{async addFunds(){throw new bI},async createAccount(e){let{admins:t,email:r,eoa:o,label:c,permissions:l,internal:u,signInWithEthereum:d}=e,{client:f}=u,p=o??EB(nR()),m=await vq(f),h=n?lB():await cB({createFn:i?.createFn,label:c||`${p.address.slice(0,8)}\u2026${p.address.slice(-6)}`,rpId:s,userId:_L(p.address)}),g=await DK(l,{chainId:f.chain.id,feeTokens:m}),_=t?.map(e=>dB(e)),v=await lq(f,{account:p,authorizeKeys:[h,..._??[],...g?[g]:[]]});a=p.address,r&&c&&await cq(f,{email:c,walletAddress:v.address});let y=await(async()=>{if(!d)return;let e=await HK(f,d,{address:v.address}),t=await OB(p,{payload:KK(eL(e))});return{message:e,signature:await gq(f,{address:v.address,signature:t})}})();return{account:{...v,signInWithEthereum:y}}},async getAccountVersion(e){let{address:t,internal:n}=e,{client:r}=n,{contracts:i}=await LW(r),{accountImplementation:a}=i,o=await nq(r,{account:TB(a)}).then(e=>e.version),s=await nq(r,{account:t}).then(e=>e.version).catch(()=>o);if(!s||!o)throw Error(`version not found.`);return{current:s,latest:o}},async getAssets(e){let{account:t,chainFilter:n,assetFilter:r,assetTypeFilter:i,internal:a}=e,{client:o}=a;return await RW(o,{account:t,assetFilter:r,assetTypeFilter:i,chainFilter:n})},async getCallsHistory(e){let{internal:t,...n}=e,{client:r}=t;return await BW(r,n)},async getCallsStatus(e){let{id:t,internal:n}=e,{client:r}=n,i=await zW(r,{id:t});return{atomic:!0,chainId:$I(r.chain.id),id:t,receipts:i.receipts?.map(e=>({blockHash:e.blockHash,blockNumber:$I(e.blockNumber),gasUsed:$I(e.gasUsed),logs:e.logs,status:e.status,transactionHash:e.transactionHash})),status:i.status,version:`1.0`}},async getCapabilities(e){let{chainIds:t,internal:n}=e,{client:i}=n,a={atomic:{status:`supported`},atomicBatch:{supported:!0},feeToken:{supported:!0,tokens:[]},merchant:{supported:!0},permissions:{supported:!0},requiredFunds:{supported:!!r,tokens:[]}},o=await LW(i,{chainIds:t?t.map(e=>sL(e)):`all`,raw:!0});return Object.entries(o).reduce((e,[t,n])=>({...e,[t]:{...a,...n,feeToken:{supported:!0,tokens:n.fees.tokens},requiredFunds:{supported:!!r,tokens:r?n.fees.tokens.filter(e=>e.interop):[]}}}),{})},async getKeys(e){let{account:t,chainIds:n,internal:r}=e,{client:i}=r;return RB([...await rq(i,{account:t,chainIds:n}),...t.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{account:t,internal:n}=e,{client:r}=n,a=dB(e.key,{chainId:r.chain.id}),o=await bq(r,{addressOrSymbol:e.feeToken,store:n.store}),{id:s}=await oq(r,{account:t,authorizeKeys:[a],feeToken:o?.address,webAuthn:i});return await Zm(r,{id:s,pollingInterval:500}),{key:a}},async grantPermissions(e){let{account:t,internal:n,permissions:r}=e,{client:i}=n,a=await vq(i),o=await DK(r,{chainId:i.chain.id,feeTokens:a});if(!o)throw Error(`key to authorize not found.`);let s=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!s)throw Error(`admin key not found.`);let{context:c,digest:l}=await iq(i,{account:t,authorizeKeys:[o],key:s,preCalls:!0});return await sq(i,{context:c,key:s,signature:await yB(s,{address:null,payload:l})}),{key:o}},async loadAccounts(e){let{internal:t,permissions:r,signInWithEthereum:o}=e,{client:c}=t,l=await vq(c),u=await DK(r,{chainId:c.chain.id,feeTokens:l}),{digest:d,digestType:f,message:p}=await(async()=>{if(o&&e.address){let t=await HK(c,o,{address:e.address});return{context:void 0,digest:KK(eL(t)),digestType:`siwe`,message:t}}return{context:void 0,digest:`0x`,message:void 0}})(),{address:m,credentialId:h,webAuthnSignature:g}=await(async()=>{if(n){if(!a)throw Error(`address_internal not found.`);return{address:a,credentialId:void 0}}if(e.address&&e.key)return{address:e.address,credentialId:e.key.credentialId};let t=await Jz({challenge:d,getFn:i?.getFn,rpId:s}),r=t.raw.response;return{address:TL(new Uint8Array(r.userHandle)),credentialId:t.raw.id,webAuthnSignature:t}})(),_=TB({address:m,keys:[...await rq(c,{account:m,chainIds:[c.chain.id]}),...u?[u]:[]].map((e,t)=>t===0&&e.type===`webauthn-p256`?mB({...e,credential:{id:h,publicKey:LL(e.publicKey)},id:m,rpId:s}):e)}),v=DB(_,{role:`admin`}),y=await(async()=>{if(d!==`0x`)return g?wB(CB(g),{keyType:`webauthn-p256`,publicKey:v.publicKey}):await yB(v,{address:_.address,payload:d})})();if(u){let{context:e,digest:t}=await iq(c,{account:_,authorizeKeys:[u],preCalls:!0});await sq(c,{context:e,key:v,signature:await yB(v,{address:null,payload:t})})}let b=await(async()=>{if(o){if(f===`siwe`&&p&&y)return{message:p,signature:await gq(c,{address:_.address,signature:y})};{let e=await HK(c,o,{address:_.address}),t=await OB(_,{payload:KK(eL(e)),role:`admin`});return{message:e,signature:await gq(c,{address:_.address,signature:t})}}}})();return{accounts:[{..._,signInWithEthereum:b}]}},async prepareCalls(e){let{account:t,calls:n,internal:i,merchantUrl:a}=e,{client:o}=i,s=e.key??await xK({account:t,calls:n});if(!s)throw Error(`cannot find authorized key to sign with.`);let[c,l]=await Promise.all([vq(o),bq(o,{addressOrSymbol:e.feeToken,store:i.store})]),u=_q(e.requiredFunds??[],{tokens:c}),{capabilities:d,context:f,digest:p,typedData:m}=await iq(o,{account:t,calls:n,feeToken:l?.address,key:s,merchantUrl:a,requiredFunds:r?u:void 0}),h=f.quote?.quotes??[],g=h[h.length-1];return{account:t,capabilities:{...d,quote:f.quote},chainId:o.chain.id,context:{...f,account:t,calls:n,nonce:g?.intent.nonce},digest:p,key:s,typedData:m}},async prepareUpgradeAccount(e){let{address:t,email:r,label:a,internal:c,permissions:l}=e,{client:u}=c,[d,f]=await Promise.all([vq(u),bq(u,{store:c.store})]),p=n?lB():await cB({createFn:i?.createFn,label:a||`${t.slice(0,8)}\u2026${t.slice(-6)}`,rpId:s,userId:_L(t)}),m=await DK(l,{chainId:u.chain.id,feeTokens:d}),{context:h,digests:g}=await aq(u,{address:t,authorizeKeys:[p,...m?[m]:[]],feeToken:f?.address});return r&&(o=a),{context:h,digests:g}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);try{let{id:n}=await oq(a,{account:t,feeToken:(await bq(a,{addressOrSymbol:e.feeToken,store:r.store}))?.address,revokeKeys:[o],webAuthn:i});await Zm(a,{id:n})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke admins.`);try{let{id:n}=await oq(a,{account:t,feeToken:(await bq(a,{addressOrSymbol:e.feeToken,store:r.store}))?.address,revokeKeys:[o],webAuthn:i});await Zm(a,{id:n})}catch(e){let t=e;if(t.name===`Rpc.ExecutionError`&&t.abiError?.name===`KeyDoesNotExist`)return;throw e}}},async sendCalls(e){let{account:t,asTxHash:n,calls:a,chainId:o,internal:s,merchantUrl:c}=e,{client:l}=s;if(!t)throw Error(`account required for relay mode`);let u=await xK({account:t,calls:a,permissionsId:e.permissionsId}),[d,f]=await Promise.all([vq(l),bq(l,{addressOrSymbol:e.feeToken,store:s.store})]),p=_q(e.requiredFunds??[],{tokens:d}),m=await oq(l,{account:t,calls:a,feeToken:f?.address,key:u,merchantUrl:c,requiredFunds:r?p:void 0,webAuthn:i,...o?{chain:{id:o}}:{}});if(n){let{id:e,receipts:t,status:n}=await Zm(l,{id:m.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new DI({message:`Call bundle with id: `+e+` not found.`}):new cI({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return m},async sendPreparedCalls(e){let{context:t,key:n,internal:r,signature:i}=e,{client:a}=r,{id:o}=await sq(a,{context:t,key:n,signature:i});return o},async signPersonalMessage(e){let{account:t,data:n,internal:r}=e,{client:a}=r,o=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!o)throw Error(`cannot find admin key to sign with.`);let s=await OB(t,{key:o,payload:KK(n),webAuthn:i});return gq(a,{address:t.address,signature:s})},async signTypedData(e){let{account:t,internal:n}=e,{client:r}=n,a=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!a)throw Error(`cannot find admin key to sign with.`);let o=XF(e.data),s=o.domain?.name===`Orchestrator`,c=await OB(t,{key:a,payload:tz(o),replaySafe:!s,webAuthn:i});return s?c:gq(r,{address:t.address,signature:c})},async upgradeAccount(e){let{account:t,context:n,internal:r,signatures:i}=e,{client:a}=r;return await lq(a,{context:n,signatures:i}),o&&await cq(a,{email:o,walletAddress:t.address}),{account:t}},async verifyEmail(e){let{account:t,chainId:n,email:r,token:a,internal:o,walletAddress:s}=e,{client:c}=o,l=t.keys?.find(e=>e.role===`admin`&&e.privateKey);if(!l)throw Error(`cannot find admin key to sign with.`);return await uq(c,{chainId:n,email:r,signature:await OB(t,{key:l,payload:ML(eL(`${r}${a}`)),webAuthn:i}),token:a,walletAddress:s})}},config:e,name:`rpc`})}function Sq(e={}){let{fallback:t=xq(),host:n=WB.prod,renderer:r=KB(),theme:i,themeController:a}=e,o=new Set,s=SK();function c(e,t){return new Promise((n,r)=>{let i=a=>{let s=a.find(t=>t.request.id===e);if(!s&&a.length===0){o.delete(i),r(new vI);return}s&&(s.status!==`success`&&s.status!==`error`||(o.delete(i),s.status===`success`?n(s.result):r(NI(s.error)),t.setState(t=>({...t,requestQueue:t.requestQueue.filter(t=>t.request.id!==e)}))))};o.add(i)})}function l(e){return MI({async request(t){let n=s.prepare(t);return e.setState(e=>{let t=e.accounts[0],r=t?.keys?.find(e=>e.role===`admin`&&e.type===`webauthn-p256`);return{...e,requestQueue:[...e.requestQueue,{account:t?{address:t.address,key:r?{credentialId:r?.credentialId,publicKey:r.publicKey}:void 0}:void 0,request:n,status:`pending`}]}}),c(n.id,e)}},{schema:wK()})}return bK({actions:{async addFunds(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_addFunds`)throw Error(`Cannot add funds for method: `+n.method);return await l(r).request(n)},async createAccount(e){let{internal:t}=e,{client:n,config:r,request:i,store:a}=t,{storage:o}=r,s=l(a);return{account:await(async()=>{if(i.method===`wallet_connect`){let[{capabilities:e,chainIds:t}]=i._decoded.params??[{}],a=wq(e?.signInWithEthereum?.authUrl??r.authUrl,{storage:o}),c=i.params?.[0]?.capabilities?.signInWithEthereum,l=await DK(e?.grantPermissions,{chainId:n.chain.id}),u=l?RV(TK,EK(l)):void 0,{accounts:d}=await s.request({...i,params:[{capabilities:{...i.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:a||c?{...c,authUrl:a}:void 0},chainIds:t?.map(e=>$I(e))}]}),[f]=d;if(!f)throw Error(`no account found.`);let p=f.capabilities?.admins?.map(e=>dB(e,{chainId:n.chain.id})).filter(Boolean),m=f.capabilities?.permissions?.map(e=>{try{let t=_G(zV(hG,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),h=await(async()=>{if(!f.capabilities?.signInWithEthereum)return;let{message:e,signature:t}=f.capabilities.signInWithEthereum;if(!a)return{message:e,signature:t};let{token:n}=await VK({address:f.address,authUrl:a,message:e,publicKey:f.capabilities?.admins?.[0]?.publicKey,signature:t});return{message:e,signature:t,token:n}})();return{...TB({address:f.address,keys:[...p??[],...m??[]]}),signInWithEthereum:h}}throw Error(`Account creation not supported on method: ${i.method}`)})()}},async disconnect(e){let{internal:t}=e,{config:n}=t,{storage:r}=n,i=await r.getItem(`porto.authUrl`)||void 0,a=wq(n.authUrl??i,{storage:r});a&&await fetch(a.logout,{credentials:`include`,method:`POST`}).catch(()=>{})},async getAccountVersion(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAccountVersion`)throw Error(`Cannot get version for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getAccountVersion(e)},async getAssets(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getAssets`)throw Error(`Cannot get assets for method: `+a.method);if(!r.supportsHeadless)return t.actions.getAssets(e);let o=await l(i).request(a);return zV(tK.Response,o)},async getCallsHistory(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCallsHistory`)throw Error(`Cannot get history for method: `+a.method);if(!r.supportsHeadless)return t.actions.getCallsHistory(e);let o=await l(i).request(a);return zV(rK.Response,o)},async getCallsStatus(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCallsStatus`)throw Error(`Cannot get status for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getCallsStatus(e)},async getCapabilities(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_getCapabilities`)throw Error(`Cannot get capabilities for method: `+a.method);return r.supportsHeadless?await l(i).request(a):t.actions.getCapabilities(e)},async getKeys(e){let{account:n,chainIds:i,internal:a}=e,{store:o}=a;return RB([...await(async()=>{if(!r.supportsHeadless)return t.actions.getKeys(e);let a=await l(o).request({method:`wallet_getKeys`,params:[RV(aK.Parameters,{address:n.address,chainIds:i})]});return zV(aK.Response,a)})(),...n.keys??[]],e=>e.publicKey)},async grantAdmin(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`wallet_grantAdmin`)throw Error(`Cannot authorize admin for method: `+n.method);let[i]=n._decoded.params,a=dB(i.key);if(!a)throw Error(`no key found.`);let o=await Cq(t,e);return await l(r).request({method:`wallet_grantAdmin`,params:[{...n.params?.[0],capabilities:{...n.params?.[0]?.capabilities,feeToken:o}}]}),{key:a}},async grantPermissions(e){let{internal:t}=e,{client:n,request:r,store:i}=t;if(r.method!==`wallet_grantPermissions`)throw Error(`Cannot grant permissions for method: `+r.method);let[{address:a,...o}]=r._decoded.params,s=await DK(o,{chainId:n.chain.id});if(!s)throw Error(`no key found.`);let c=RV(TK,EK(s));return await l(i).request({method:`wallet_grantPermissions`,params:[c]}),{key:s}},async loadAccounts(e){let{internal:t}=e,{client:n,config:r,store:i}=t,{storage:a}=r,o=l(i),s=t.request;if(s.method!==`wallet_connect`&&s.method!==`eth_requestAccounts`)throw Error(`Cannot load accounts for method: `+s.method);return{accounts:await(async()=>{let[e]=s._decoded.params??[],{capabilities:t}=e??{},i=wq(t?.signInWithEthereum?.authUrl??r.authUrl,{storage:a}),c=s.params?.[0]?.capabilities?.signInWithEthereum,l=await DK(t?.grantPermissions,{chainId:n.chain.id}),u=l?RV(TK,EK(l)):void 0,{accounts:d}=await o.request({method:`wallet_connect`,params:[{...s.params?.[0],capabilities:{...s.params?.[0]?.capabilities,grantPermissions:u,signInWithEthereum:i||c?{...c,authUrl:i}:void 0}}]});return Promise.all(d.map(async e=>{let t=e.capabilities?.admins?.map(e=>dB(e)).filter(Boolean),n=e.capabilities?.permissions?.map(e=>{try{let t=_G(zV(hG,e));return t.id===l?.id?{...t,...l,permissions:t.permissions}:t}catch{return}}).filter(Boolean),r=await(async()=>{if(!e.capabilities?.signInWithEthereum)return;let{message:t,signature:n}=e.capabilities.signInWithEthereum;if(!i)return{message:t,signature:n};let{token:r}=await VK({address:e.address,authUrl:i,message:t,publicKey:e.capabilities?.admins?.[0]?.publicKey,signature:n});return{message:t,signature:n,token:r}})();return{...TB({address:e.address,keys:[...t??[],...n??[]]}),signInWithEthereum:r}}))})()}},async prepareCalls(e){let{account:n,internal:i}=e,{store:a,request:o}=i;if(o.method!==`wallet_prepareCalls`)throw Error(`Cannot prepare calls for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareCalls(e);let s=await Cq(i,e),c=l(a),u=zV(oK.Response,await c.request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,feeToken:s}}]}));return{account:n,chainId:Number(u.chainId),context:u.context,digest:u.digest,key:u.key,typedData:u.typedData}},async prepareUpgradeAccount(e){let{internal:n}=e,{client:i,store:a,request:o}=n;if(o.method!==`wallet_prepareUpgradeAccount`)throw Error(`Cannot prepare upgrade for method: `+o.method);if(!r.supportsHeadless)return t.actions.prepareUpgradeAccount(e);let[{capabilities:s}]=o._decoded.params??[{}],c=await DK(s?.grantPermissions,{chainId:i.chain.id}),u=c?RV(TK,EK(c)):void 0,{context:d,digests:f}=await l(a).request({...o,params:[{...o.params?.[0],capabilities:{...o.params?.[0]?.capabilities,grantPermissions:u}}]}),p=d.account.keys?.map(e=>e.id===c?.id?{...e,...c}:e);return{context:{...d,account:{...d.account,keys:p}},digests:f}},async revokeAdmin(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokeAdmin`)throw Error(`Cannot revoke admin for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(!o)return;if(o.type===`webauthn-p256`&&t.keys?.filter(e=>e.type===`webauthn-p256`).length===1)throw Error(`revoke the only WebAuthn key left.`);let s=await Cq(r,e);return await l(i).request({...a,params:[{...a.params?.[0],capabilities:{...a.params?.[0]?.capabilities,feeToken:s}}]})},async revokePermissions(e){let{account:t,id:n,internal:r}=e,{store:i,request:a}=r;if(a.method!==`wallet_revokePermissions`)throw Error(`Cannot revoke permissions for method: `+a.method);let o=t.keys?.find(e=>e.id===n);if(o){if(o.role===`admin`)throw Error(`cannot revoke permissions.`);return await l(i).request(a)}},async sendCalls(e){let{account:n,asTxHash:i,calls:a,chainId:o,internal:s,merchantUrl:c,requiredFunds:u}=e,{client:d,store:f,request:p}=s,m=l(f),h=await Cq(s,e),g=n?await xK({account:n,calls:a,permissionsId:e.permissionsId}):void 0;if(g?.role===`session`&&n){if(!r.supportsHeadless)return t.actions.sendCalls(e);try{let e=await m.request(RV(oK.Request,{method:`wallet_prepareCalls`,params:[{calls:a,capabilities:{...p._decoded.method===`wallet_sendCalls`?p._decoded.params?.[0]?.capabilities:void 0,feeToken:h,merchantUrl:c,requiredFunds:u},chainId:o,from:n.address,key:g}]})),t=e.capabilities?.quote?.quotes??[];if(t.some((e,n)=>n===t.length-1&&t.length>1?!1:oL(e.feeTokenDeficit)>0n))throw Error(`insufficient funds`);let r=await yB(g,{address:null,payload:e.digest,wrap:!1}),s=(await m.request({method:`wallet_sendPreparedCalls`,params:[{...e,signature:r}]}))[0];if(!s)throw Error(`id not found`);if(i){let{id:e,receipts:t,status:n}=await Zm(d,{id:s.id,pollingInterval:500});if(!t?.[0])throw n===`success`?new DI({message:`Call bundle with id: `+e+` not found.`}):new cI({message:`Transaction failed under call bundle id: `+e+`.`});return{id:t[0].transactionHash}}return s}catch{}}if(p.method===`eth_sendTransaction`)return{id:await m.request({...p,params:[{...p.params?.[0],capabilities:{feeToken:h,merchantUrl:c},...o?{chainId:$I(o)}:{}}]})};if(p.method===`wallet_sendCalls`)return await m.request({method:`wallet_sendCalls`,params:[{...p.params?.[0],capabilities:{...p.params?.[0]?.capabilities,feeToken:h,merchantUrl:c},...o?{chainId:$I(o)}:{}}]});throw Error(`Cannot execute for method: `+p.method)},async sendPreparedCalls(e){let{internal:n}=e,{store:i,request:a}=n;if(a.method!==`wallet_sendPreparedCalls`)throw Error(`Cannot send prepared calls for method: `+a.method);if(!r.supportsHeadless)return t.actions.sendPreparedCalls(e);let o=(await l(i).request(a))[0]?.id;if(!o)throw Error(`id not found`);return o},async signPersonalMessage(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`personal_sign`)throw Error(`Cannot sign personal message for method: `+r.method);return await l(n).request(r)},async signTypedData(e){let{internal:t}=e,{store:n,request:r}=t;if(r.method!==`eth_signTypedData_v4`)throw Error(`Cannot sign typed data for method: `+r.method);return await l(n).request(r)},async switchChain(e){let{internal:t}=e,{store:n,request:i}=t;if(i.method!==`wallet_switchEthereumChain`)throw Error(`Cannot switch chain for method: `+i.method);if(r.supportsHeadless)return await l(n).request(i)},async upgradeAccount(e){let{account:t,internal:n}=e,{store:r,request:i}=n;if(i.method!==`wallet_upgradeAccount`)throw Error(`Cannot upgrade account for method: `+i.method);return await l(r).request(i),{account:t}},async verifyEmail(e){let{internal:t}=e,{request:n,store:r}=t;if(n.method!==`account_verifyEmail`)throw Error(`Cannot verify email for method: `+n.method);return await l(r).request(n)}},config:e,name:`dialog`,setup(e){let{internal:t}=e,{store:s}=t,c=r.setup({host:n,internal:t,theme:i,themeController:a}),l=s.subscribe(e=>e.requestQueue,e=>{for(let t of o)t(e);let t=e.map(e=>e.status===`pending`?e:void 0).filter(Boolean);c.syncRequests(t).catch(()=>{}),t.length===0&&c.close()});return()=>{l(),c.destroy()}}})}async function Cq(e,t){let{config:{feeToken:n}}=e,{feeToken:r}=t??{};return r??n}function wq(e,{storage:t}){if(!e)return;let n=UK(e,typeof window<`u`?window.location.origin:void 0);return n&&t.setItem(`porto.authUrl`,n),n}var Tq=e=>!!e.dispatchFromDevtools&&typeof e.dispatch==`function`,Eq=new Map,Dq=e=>{let t=Eq.get(e);return t?Object.fromEntries(Object.entries(t.stores).map(([e,t])=>[e,t.getState()])):{}},Oq=(e,t,n)=>{if(e===void 0)return{type:`untracked`,connection:t.connect(n)};let r=Eq.get(n.name);if(r)return{type:`tracked`,store:e,...r};let i={connection:t.connect(n),stores:{}};return Eq.set(n.name,i),{type:`tracked`,store:e,...i}},kq=(e,t)=>{if(t===void 0)return;let n=Eq.get(e);n&&(delete n.stores[t],Object.keys(n.stores).length===0&&Eq.delete(e))},Aq=e=>{if(!e)return;let t=e.split(` -`),n=t.findIndex(e=>e.includes(`api.setState`));if(n<0)return;let r=t[n+1]?.trim()||``;return/.+ (.+) .+/.exec(r)?.[1]},jq=(e,t={})=>(n,r,i)=>{let{enabled:a,anonymousActionType:o,store:s,...c}=t,l;try{l=(a??!1)&&window.__REDUX_DEVTOOLS_EXTENSION__}catch{}if(!l)return e(n,r,i);let{connection:u,...d}=Oq(s,l,c),f=!0;i.setState=((e,t,a)=>{let l=n(e,t);if(!f)return l;let d=a===void 0?{type:o||Aq(Error().stack)||`anonymous`}:typeof a==`string`?{type:a}:a;return s===void 0?(u?.send(d,r()),l):(u?.send({...d,type:`${s}/${d.type}`},{...Dq(c.name),[s]:i.getState()}),l)}),i.devtools={cleanup:()=>{u&&typeof u.unsubscribe==`function`&&u.unsubscribe(),kq(c.name,s)}};let p=(...e)=>{let t=f;f=!1,n(...e),f=t},m=e(i.setState,r,i);if(d.type===`untracked`?u?.init(m):(d.stores[d.store]=i,u?.init(Object.fromEntries(Object.entries(d.stores).map(([e,t])=>[e,e===d.store?m:t.getState()])))),Tq(i)){let e=i.dispatch;i.dispatch=(...t)=>{e(...t)}}return u.subscribe(e=>{switch(e.type){case`ACTION`:if(typeof e.payload!=`string`){console.error(`[zustand devtools middleware] Unsupported action format`);return}return Mq(e.payload,e=>{if(e.type===`__setState`){if(s===void 0){p(e.state);return}Object.keys(e.state).length!==1&&console.error(` - [zustand devtools middleware] Unsupported __setState action format. - When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(), - and value of this only key should be a state object. Example: { "type": "__setState", "state": { "abc123Store": { "foo": "bar" } } } - `);let t=e.state[s];if(t==null)return;JSON.stringify(i.getState())!==JSON.stringify(t)&&p(t);return}Tq(i)&&i.dispatch(e)});case`DISPATCH`:switch(e.payload.type){case`RESET`:return p(m),s===void 0?u?.init(i.getState()):u?.init(Dq(c.name));case`COMMIT`:if(s===void 0){u?.init(i.getState());return}return u?.init(Dq(c.name));case`ROLLBACK`:return Mq(e.state,e=>{if(s===void 0){p(e),u?.init(i.getState());return}p(e[s]),u?.init(Dq(c.name))});case`JUMP_TO_STATE`:case`JUMP_TO_ACTION`:return Mq(e.state,e=>{if(s===void 0){p(e);return}JSON.stringify(i.getState())!==JSON.stringify(e[s])&&p(e[s])});case`IMPORT_STATE`:{let{nextLiftedState:t}=e.payload,n=t.computedStates.slice(-1)[0]?.state;if(!n)return;p(s===void 0?n:n[s]),u?.send(null,t);return}case`PAUSE_RECORDING`:return f=!f}return}}),m},Mq=(e,t)=>{let n;try{n=JSON.parse(e)}catch(e){console.error(`[zustand devtools middleware] Could not parse the received json`,e)}n!==void 0&&t(n)},Nq=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function Pq(e,t){let n;try{n=e()}catch{return}return{getItem:e=>{let r=e=>e===null?null:JSON.parse(e,t?.reviver),i=n.getItem(e)??null;return i instanceof Promise?i.then(r):r(i)},setItem:(e,r)=>n.setItem(e,JSON.stringify(r,t?.replacer)),removeItem:e=>n.removeItem(e)}}var Fq=e=>t=>{try{let n=e(t);return n instanceof Promise?n:{then(e){return Fq(e)(n)},catch(e){return this}}}catch(e){return{then(e){return this},catch(t){return Fq(t)(e)}}}},Iq=(e,t)=>(n,r,i)=>{let a={storage:Pq(()=>window.localStorage),partialize:e=>e,version:0,merge:(e,t)=>({...t,...e}),...t},o=!1,s=0,c=new Set,l=new Set,u=a.storage;if(!u)return e((...e)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...e)},r,i);let d=()=>{let e=a.partialize({...r()});return u.setItem(a.name,{state:e,version:a.version})},f=i.setState;i.setState=(e,t)=>(f(e,t),d());let p=e((...e)=>(n(...e),d()),r,i);i.getInitialState=()=>p;let m,h=()=>{if(!u)return;let e=++s;o=!1,c.forEach(e=>e(r()??p));let t=a.onRehydrateStorage?.call(a,r()??p)||void 0;return Fq(u.getItem.bind(u))(a.name).then(e=>{if(e)if(typeof e.version==`number`&&e.version!==a.version){if(a.migrate){let t=a.migrate(e.state,e.version);return t instanceof Promise?t.then(e=>[!0,e]):[!0,t]}console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`)}else return[!1,e.state];return[!1,void 0]}).then(t=>{if(e!==s)return;let[i,o]=t;if(m=a.merge(o,r()??p),n(m,!0),i)return d()}).then(()=>{e===s&&(t?.(r(),void 0),m=r(),o=!0,l.forEach(e=>e(m)))}).catch(n=>{e===s&&t?.(void 0,n)})};return i.persist={setOptions:e=>{a={...a,...e},e.storage&&(u=e.storage)},clearStorage:()=>{u?.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>h(),hasHydrated:()=>o,onHydrate:e=>(c.add(e),()=>{c.delete(e)}),onFinishHydration:e=>(l.add(e),()=>{l.delete(e)})},a.skipHydration||h(),m||p},Lq=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Rq=(e=>e?Lq(e):Lq);function zq(e){return new Promise((t,n)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>n(e.error)})}function Bq(e,t){let n,r=()=>{if(n)return n;let r=indexedDB.open(e);return r.onupgradeneeded=()=>r.result.createObjectStore(t),n=zq(r),n.then(e=>{e.onclose=()=>n=void 0},()=>{}),n};return(e,n)=>r().then(r=>n(r.transaction(t,e).objectStore(t)))}var Vq;function Hq(){return Vq||=Bq(`keyval-store`,`keyval`),Vq}function Uq(e,t=Hq()){return t(`readonly`,t=>zq(t.get(e)))}function Wq(e,t,n=Hq()){return n(`readwrite`,n=>(n.put(t,e),zq(n.transaction)))}function Gq(e,t=Hq()){return t(`readwrite`,t=>(t.delete(e),zq(t.transaction)))}function Kq(e){return e}function qq(){let e=typeof indexedDB<`u`?Bq(`porto`,`store`):void 0;return Kq({async getItem(t){let n=await Uq(t,e);return n===null?null:n},async removeItem(t){await Gq(t,e)},async setItem(t,n){await Wq(t,LB(n),e)},sizeLimit:1024*1024*50})}function Jq(){let e=new Map;return Kq({getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)},sizeLimit:1/0})}var Yq=typeof window<`u`&&typeof document<`u`,Xq={announceProvider:!0,chains:qF,mode:Yq?Sq({host:WB.prod}):xq(),relay:$v(tG.prod.http),storage:Yq&&typeof indexedDB<`u`?qq():Jq(),storageKey:`porto.store`};function Zq(e={}){let t=e.chains??Xq.chains,n=Object.fromEntries(t.map(t=>[t.id,e.transports?.[t.id]??$v()])),r={announceProvider:e.announceProvider??Xq.announceProvider,authUrl:e.authUrl,chains:t,feeToken:e.feeToken,merchantUrl:e.merchantUrl,mode:e.mode??Xq.mode,relay:e.relay??Xq.relay,storage:e.storage??Xq.storage,storageKey:e.storageKey??Xq.storageKey,transports:n},i=Rq(jq(Nq(Iq(e=>({accounts:[],chainIds:r.chains.map(e=>e.id),feeToken:r.feeToken,requestQueue:[]}),{merge(e,t){let n=e,i=r.chains.find(e=>e.id===n.chainIds[0])?.id??r.chains[0].id,a=[i,...r.chains.map(e=>e.id).filter(e=>e!==i)];return{...t,...n,chainIds:a}},name:r.storageKey,partialize:e=>({accounts:e.accounts.map(e=>LB(e)),chainIds:e.chainIds}),storage:r.storage,version:5})))),a=r.mode,o={config:r,getMode(){return a},id:zB(),setMode(e){return c?.(),a=e,c=e.setup({internal:o}),c},store:i},s=hK(o),c=a===null?()=>{}:a.setup({internal:o});return{_internal:o,config:r,destroy(){c(),s._internal.destroy()},provider:s}}var Qq=`http://127.0.0.1:9545`,$q=Object.freeze(Object.values(KF)),eJ=e=>$q.find(t=>t.id===e),tJ=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`)return;let t=e.trim();if(/^0x[0-9a-fA-F]+$/.test(t)){let e=Number.parseInt(t,16);return Number.isNaN(e)?void 0:e}if(/^\d+$/.test(t)){let e=Number.parseInt(t,10);return Number.isNaN(e)?void 0:e}},nJ=(e,t,n)=>{let r=tJ(e);if(t(r),r!=null){let e=eJ(r);e||={id:r,name:`Chain ${r}`,network:`chain-${r}`,nativeCurrency:{decimals:18,name:`Ether`,symbol:`ETH`},rpcUrls:{default:{http:[]},public:{http:[]}}},n(e)}else n(void 0)},rJ=async(e,t=`GET`,n)=>{let r={"Content-Type":`application/json`},i=typeof window<`u`?window.__SESSION_TOKEN__:void 0;i&&(r[`X-Session-Token`]=i);let a=await fetch(`${Qq}${e}`,{method:t,headers:r,body:n===void 0?void 0:JSON.stringify(n)});if(!a.ok)throw Error(`API request failed: ${a.status} ${a.statusText}`);try{return await a.json()}catch{throw Error(`Invalid JSON response`)}},iJ=e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t,2),aJ=e=>{if(e==null)return iJ(e);if(typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`){let t=e;try{let n=JSON.parse(t.message);return iJ({...e,message:n})}catch{return iJ(e)}}return iJ(e)},oJ=e=>!!e&&e.status===`ok`,sJ=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=s(((e,t)=>{t.exports=sJ()}))();function cJ(){(0,y.useEffect)(()=>{window.__PORTO__||(window.__PORTO__=Zq())},[]);let[e,t]=(0,y.useState)([]),[n,r]=(0,y.useState)(!1),[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(null),[c,l]=(0,y.useState)(null),u=e.find(e=>e.info.uuid===c)??null,[d,f]=(0,y.useState)(),[p,m]=(0,y.useState)(),[h,g]=(0,y.useState)(),[_,v]=(0,y.useState)(null),[b,x]=(0,y.useState)(null),[S,ee]=(0,y.useState)(null),C=(0,y.useRef)(null),te=(0,y.useRef)(null),ne=(0,y.useRef)(null),re=async()=>{if(!(!u||n)){f((await u.provider.request({method:`eth_requestAccounts`}))?.[0]??void 0);try{nJ(await u.provider.request({method:`eth_chainId`}),m,g)}catch{m(void 0),g(void 0)}}},ie=async()=>{if(!(!d||p==null)){try{await rJ(`/api/connection`,`POST`,[d,p])}catch{return}r(!0)}},ae=async()=>{if(!u||!o)return;let{id:e,signType:t,request:n}=o,r=n.address,i=n.message;try{let n;switch(t){case`PersonalSign`:n=await u.provider.request({method:`personal_sign`,params:[i,r]});break;case`SignTypedDataV4`:n=await u.provider.request({method:`eth_signTypedData_v4`,params:[r,i]});break;default:throw Error(`Unsupported signType: ${t}`)}await rJ(`/api/signing/response`,`POST`,{id:e,signature:n,error:null}),ee(n),s(null)}catch(t){let n=typeof t==`object`&&t&&`message`in t&&typeof t.message==`string`?t.message:String(t);try{await rJ(`/api/signing/response`,`POST`,{id:e,signature:null,error:n})}catch{}ee(null),s(null)}},oe=async()=>{if(!u||!i?.request)return;let e=Kv({transport:Jv(u.provider),chain:h});try{let{from:t,input:n,to:r,...a}=i.request,o=await e.sendTransaction({...a,account:t||(await e.getAddresses())[0],...n?{data:n}:{},...r?{to:r}:{},chain:h});x(o),await rJ(`/api/transaction/response`,`POST`,{id:i.id,hash:o,error:null}),v(await wv(e,{hash:o}))}catch(e){let t=typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?e.message:String(e);console.error(`send failed:`,t);try{await rJ(`/api/transaction/response`,`POST`,{id:i.id,hash:null,error:t})}catch{}}},se=(0,y.useCallback)(()=>{C.current&&=(window.clearInterval(C.current),null),te.current&&=(window.clearInterval(te.current),null),a(null),s(null),x(null),v(null),f(void 0),m(void 0),g(void 0),r(!1),rJ(`/api/connection`,`POST`,null)},[]);return(0,y.useEffect)(()=>{ne.current&&c&&ne.current!==c&&se(),ne.current=c},[c,se]),(0,y.useEffect)(()=>{e.length===1&&!u&&l(e[0].info.uuid)},[e,u]),(0,y.useEffect)(()=>{let e=e=>{let{info:n,provider:r}=e.detail;t(e=>e.some(e=>e.info.uuid===n.uuid)?e:[...e,{info:n,provider:r}])};return window.addEventListener(`eip6963:announceProvider`,e),window.dispatchEvent(new Event(`eip6963:requestProvider`)),()=>window.removeEventListener(`eip6963:announceProvider`,e)},[]),(0,y.useEffect)(()=>{if(!u)return;let e=e=>{n||f(e[0]??void 0)},t=e=>{n||nJ(e,m,g)};return u.provider.on?.(`accountsChanged`,e),u.provider.on?.(`chainChanged`,t),()=>{u.provider.removeListener?.(`accountsChanged`,e),u.provider.removeListener?.(`chainChanged`,t)}},[u,n]),(0,y.useEffect)(()=>{if(!n||i||o)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await rJ(`/api/transaction/request`);oJ(n)&&(window.clearInterval(t),e&&a(n.data))}catch{}},1e3);return C.current=t,()=>{e=!1,window.clearInterval(t),C.current===t&&(C.current=null)}},[n,i,o]),(0,y.useEffect)(()=>{if(!n||o||i)return;let e=!0,t=window.setInterval(async()=>{if(e)try{let n=await rJ(`/api/signing/request`);oJ(n)&&(window.clearInterval(t),e&&s(n.data))}catch{}},1e3);return te.current=t,()=>{e=!1,window.clearInterval(t),te.current===t&&(te.current=null)}},[n,o,i]),(0,$.jsx)(`div`,{className:`wrapper`,children:(0,$.jsxs)(`div`,{className:`container`,children:[(0,$.jsx)(`div`,{className:`notice`,children:`Browser wallet is still in early development. Use with caution!`}),(0,$.jsx)(`img`,{className:`banner`,src:`banner.png`,alt:`Foundry Browser Wallet`}),e.length>1&&(0,$.jsx)(`div`,{className:`wallet-selector`,children:(0,$.jsx)(`label`,{children:(0,$.jsxs)(`select`,{value:c??``,onChange:e=>l(e.target.value||null),disabled:n,children:[(0,$.jsx)(`option`,{value:``,disabled:!0,children:`Select wallet…`}),e.map(({info:e})=>(0,$.jsxs)(`option`,{value:e.uuid,children:[e.name,` (`,e.rdns,`)`]},e.uuid))]})})}),e.length===0&&(0,$.jsx)(`p`,{children:`No wallets found.`}),u&&!d&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-connect`,onClick:re,disabled:n,children:`Connect Wallet`}),u&&d&&!n&&(0,$.jsx)(`button`,{type:`button`,className:`wallet-confirm`,onClick:ie,disabled:!d||p==null,children:`Confirm Connection`}),u&&d&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Connected`}),(0,$.jsx)(`pre`,{className:`box`,children:`\ -account: ${d} -chain: ${h?`${h.name} (${p})`:p??`unknown`} -rpc: ${h?.rpcUrls?.default?.http?.[0]??h?.rpcUrls?.public?.http?.[0]??`unknown`}`})]}),u&&d&&n&&!i&&!o&&!b&&!S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction To Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:`No pending transaction or signing request`})})]}),u&&d&&n&&!b&&i&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction to Sign & Send`}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:oe,children:`Sign & Send`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:iJ(i.request)})})]}),u&&d&&n&&!i&&o&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Message / Data to Sign`}),(0,$.jsx)(`button`,{type:`button`,className:`wallet-send`,onClick:ae,children:`Sign`}),(0,$.jsx)(`div`,{className:`box`,children:(0,$.jsx)(`pre`,{children:aJ(o.request)})})]}),u&&d&&b&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Transaction Hash`}),(0,$.jsx)(`pre`,{className:`box`,children:b}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Receipt`}),(0,$.jsx)(`pre`,{className:`box`,children:_?iJ(_):`Waiting for receipt...`})]})]}),u&&d&&n&&S&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`div`,{className:`section-title`,children:`Signature Result`}),(0,$.jsx)(`pre`,{className:`box`,children:S})]})]})})}var lJ=document.getElementById(`root`);if(lJ)(0,v.createRoot)(lJ).render((0,$.jsx)(y.StrictMode,{children:(0,$.jsx)(cJ,{})}));else throw Error(`Root element with id "root" not found`); \ No newline at end of file diff --git a/crates/wallets/src/wallet_browser/app/assets/styles.css b/crates/wallets/src/wallet_browser/app/assets/styles.css deleted file mode 100644 index 3e00c2bb172d8..0000000000000 --- a/crates/wallets/src/wallet_browser/app/assets/styles.css +++ /dev/null @@ -1,2 +0,0 @@ -*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;color:inherit;letter-spacing:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html,body,#root{color:#f8f8f8;background-color:#13151b;width:100%;height:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}button{color:#f8f8f8;cursor:pointer;background-color:#3a3f51;border:1px solid #e1e4e8;border-radius:4px;padding:8px 12px}button:hover{background-color:#50566e}select{color:#f8f8f8;cursor:pointer;background-color:#1e2026;border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px}option{color:#f8f8f8;background-color:#1e2026}pre{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere}.wrapper{flex-direction:column;justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex}.container{background-color:#3b3b3b;border-radius:8px;flex-direction:column;align-items:flex-start;max-width:600px;padding:16px;display:flex}.notice{color:#333;text-align:center;background-color:#fc0;border-radius:8px;width:100%;margin-bottom:16px;padding:8px;font-size:13px;font-weight:700}.banner{border-radius:8px;width:100%;height:auto}.wallet-selector,.wallet-connect,.wallet-send,.wallet-confirm{align-self:center;margin-top:16px}.wallet-send{margin-bottom:16px}.title,.section-title{color:#f8f8f8}.title{margin-bottom:24px;font-size:36px}.section-title{margin-bottom:16px;font-size:24px}.box{border:1px solid #e1e4e8;border-radius:8px;margin-bottom:16px;padding:8px 12px;font-size:13px} -/*$vite$:1*/ \ No newline at end of file diff --git a/crates/wallets/src/wallet_browser/app/mod.rs b/crates/wallets/src/wallet_browser/app/mod.rs deleted file mode 100644 index 6b1adcdbe47c9..0000000000000 --- a/crates/wallets/src/wallet_browser/app/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) mod contents { - pub const INDEX_HTML: &str = include_str!("assets/index.html"); - pub const STYLES_CSS: &str = include_str!("assets/styles.css"); - pub const MAIN_JS: &str = include_str!("assets/main.js"); - pub const BANNER_PNG: &[u8] = include_bytes!("assets/banner.png"); - pub const LOGO_PNG: &[u8] = include_bytes!("assets/logo.png"); -} diff --git a/crates/wallets/src/wallet_browser/error.rs b/crates/wallets/src/wallet_browser/error.rs deleted file mode 100644 index ace0f1c1cf3f5..0000000000000 --- a/crates/wallets/src/wallet_browser/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use alloy_signer::Error as SignerError; - -#[derive(Debug, thiserror::Error)] -pub enum BrowserWalletError { - #[error("{operation} request timed out")] - Timeout { operation: &'static str }, - - #[error("{operation} rejected: {reason}")] - Rejected { operation: &'static str, reason: String }, - - #[error("Wallet not connected")] - NotConnected, - - #[error("Server error: {0}")] - ServerError(String), -} - -impl From for SignerError { - fn from(err: BrowserWalletError) -> Self { - Self::other(err) - } -} - -impl From for BrowserWalletError { - fn from(err: SignerError) -> Self { - Self::ServerError(err.to_string()) - } -} diff --git a/crates/wallets/src/wallet_browser/handlers.rs b/crates/wallets/src/wallet_browser/handlers.rs deleted file mode 100644 index 4b210dca60d7f..0000000000000 --- a/crates/wallets/src/wallet_browser/handlers.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::sync::Arc; - -use alloy_network::Network; -use axum::{ - Json, - extract::State, - http::{ - HeaderMap, HeaderValue, - header::{CACHE_CONTROL, CONTENT_TYPE, EXPIRES, PRAGMA}, - }, - response::Html, -}; - -use crate::wallet_browser::{ - app::contents, - state::BrowserWalletState, - types::{ - BrowserApiResponse, BrowserSignRequest, BrowserSignResponse, BrowserTransactionRequest, - BrowserTransactionResponse, Connection, - }, -}; - -/// Serve index.html -pub(crate) async fn serve_index() -> impl axum::response::IntoResponse { - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/html; charset=utf-8")); - headers.insert( - CACHE_CONTROL, - HeaderValue::from_static("no-store, no-cache, must-revalidate, max-age=0"), - ); - headers.insert(PRAGMA, HeaderValue::from_static("no-cache")); - headers.insert(EXPIRES, HeaderValue::from_static("0")); - (headers, Html(contents::INDEX_HTML)) -} - -/// Serve styles.css -pub(crate) async fn serve_css() -> impl axum::response::IntoResponse { - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/css; charset=utf-8")); - headers.insert( - CACHE_CONTROL, - HeaderValue::from_static("no-store, no-cache, must-revalidate, max-age=0"), - ); - headers.insert(PRAGMA, HeaderValue::from_static("no-cache")); - headers.insert(EXPIRES, HeaderValue::from_static("0")); - (headers, contents::STYLES_CSS) -} - -/// Serve main.js with injected session token. -pub(crate) async fn serve_js( - State(state): State>>, -) -> impl axum::response::IntoResponse { - let token = state.session_token(); - let js = format!("window.__SESSION_TOKEN__ = \"{}\";\n{}", token, contents::MAIN_JS); - - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/javascript; charset=utf-8")); - headers.insert( - CACHE_CONTROL, - HeaderValue::from_static("no-store, no-cache, must-revalidate, max-age=0"), - ); - headers.insert(PRAGMA, HeaderValue::from_static("no-cache")); - headers.insert(EXPIRES, HeaderValue::from_static("0")); - (headers, js) -} - -/// Serve banner.png -pub(crate) async fn serve_banner_png() -> impl axum::response::IntoResponse { - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png")); - headers.insert(CACHE_CONTROL, HeaderValue::from_static("public, max-age=31536000, immutable")); - (headers, contents::BANNER_PNG) -} - -/// Serve logo.png -pub(crate) async fn serve_logo_png() -> impl axum::response::IntoResponse { - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png")); - headers.insert(CACHE_CONTROL, HeaderValue::from_static("public, max-age=31536000, immutable")); - (headers, contents::LOGO_PNG) -} - -/// Get the next pending transaction request. -/// Route: GET /api/transaction/request -pub(crate) async fn get_next_transaction_request( - State(state): State>>, -) -> Json>> { - match state.read_next_transaction_request().await { - Some(tx) => Json(BrowserApiResponse::with_data(tx)), - None => Json(BrowserApiResponse::error("No pending transaction request")), - } -} - -/// Post a transaction response (signed or error). -/// Route: POST /api/transaction/response -pub(crate) async fn post_transaction_response( - State(state): State>>, - Json(body): Json, -) -> Json { - // Ensure that the transaction request exists. - if !state.has_transaction_request(&body.id).await { - return Json(BrowserApiResponse::error("Unknown transaction id")); - } - - // Ensure that exactly one of hash or error is provided. - match (&body.hash, &body.error) { - (None, None) => { - return Json(BrowserApiResponse::error("Either hash or error must be provided")); - } - (Some(_), Some(_)) => { - return Json(BrowserApiResponse::error("Only one of hash or error can be provided")); - } - _ => {} - } - - // Validate transaction hash if provided. - if let Some(hash) = &body.hash { - // Check for all-zero hash - if hash.is_zero() { - return Json(BrowserApiResponse::error("Invalid (zero) transaction hash")); - } - - // Sanity check: ensure the hash is exactly 32 bytes - if hash.as_slice().len() != 32 { - return Json(BrowserApiResponse::error( - "Malformed transaction hash (expected 32 bytes)", - )); - } - } - - state.add_transaction_response(body).await; - - Json(BrowserApiResponse::ok()) -} - -/// Get the next pending signing request. -/// Route: GET /api/signing/request -pub(crate) async fn get_next_signing_request( - State(state): State>>, -) -> Json> { - match state.read_next_signing_request().await { - Some(req) => Json(BrowserApiResponse::with_data(req)), - None => Json(BrowserApiResponse::error("No pending signing request")), - } -} - -/// Post a signing response (signature or error). -/// Route: POST /api/signing/response -pub(crate) async fn post_signing_response( - State(state): State>>, - Json(body): Json, -) -> Json { - // Ensure that the signing request exists. - if !state.has_signing_request(&body.id).await { - return Json(BrowserApiResponse::error("Unknown signing request id")); - } - - // Ensure that exactly one of signature or error is provided. - match (&body.signature, &body.error) { - (None, None) => { - return Json(BrowserApiResponse::error("Either signature or error must be provided")); - } - (Some(_), Some(_)) => { - return Json(BrowserApiResponse::error( - "Only one of signature or error can be provided", - )); - } - _ => {} - } - - state.add_signing_response(body).await; - - Json(BrowserApiResponse::ok()) -} - -/// Get current connection information. -/// Route: GET /api/connection -pub(crate) async fn get_connection_info( - State(state): State>>, -) -> Json>> { - let connection = state.get_connection().await; - - Json(BrowserApiResponse::with_data(connection)) -} - -/// Post connection update (connect or disconnect). -/// Route: POST /api/connection -pub(crate) async fn post_connection_update( - State(state): State>>, - Json(body): Json>, -) -> Json { - state.set_connection(body).await; - - Json(BrowserApiResponse::ok()) -} diff --git a/crates/wallets/src/wallet_browser/mod.rs b/crates/wallets/src/wallet_browser/mod.rs deleted file mode 100644 index dc7df0a01b107..0000000000000 --- a/crates/wallets/src/wallet_browser/mod.rs +++ /dev/null @@ -1,976 +0,0 @@ -pub mod error; -pub mod opts; -pub mod server; -pub mod signer; -pub mod state; - -mod app; -mod handlers; -mod queue; -mod router; -mod types; - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use alloy_network::{Ethereum, Network, TransactionBuilder}; - use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256, address}; - use axum::http::{HeaderMap, HeaderValue}; - use tokio::task::JoinHandle; - use uuid::Uuid; - - use crate::wallet_browser::{ - error::BrowserWalletError, - server::BrowserWalletServer, - types::{ - BrowserApiResponse, BrowserSignRequest, BrowserSignResponse, BrowserTransactionRequest, - BrowserTransactionResponse, Connection, SignRequest, SignType, - }, - }; - - const ALICE: Address = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); - const BOB: Address = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); - - const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1); - const DEFAULT_DEVELOPMENT: bool = false; - - #[tokio::test] - async fn test_setup_server() { - let mut server = create_server::(); - let client = client_with_token(&server); - - // Check initial state - assert!(!server.is_connected().await); - assert!(!server.open_browser()); - assert!(server.timeout() == DEFAULT_TIMEOUT); - - // Start server - server.start().await.unwrap(); - - // Check that the transaction request queue is empty - check_transaction_request_queue_empty(&client, &server).await; - - // Stop server - server.stop().await.unwrap(); - } - - #[tokio::test] - async fn test_connect_disconnect_wallet() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - - // Check that the transaction request queue is empty - check_transaction_request_queue_empty(&client, &server).await; - - // Connect Alice's wallet - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Check connection state - let Connection { address, chain_id } = - server.get_connection().await.expect("expected an active wallet connection"); - assert_eq!(address, ALICE); - assert_eq!(chain_id, 1); - - // Disconnect wallet - disconnect_wallet(&client, &server).await; - - // Check disconnected state - assert!(!server.is_connected().await); - - // Connect Bob's wallet - connect_wallet(&client, &server, Connection::new(BOB, 42)).await; - - // Check connection state - let Connection { address, chain_id } = - server.get_connection().await.expect("expected an active wallet connection"); - assert_eq!(address, BOB); - assert_eq!(chain_id, 42); - - // Stop server - server.stop().await.unwrap(); - } - - #[tokio::test] - async fn test_switch_wallet() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - - // Connect Alice, assert connected - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - let Connection { address, chain_id } = - server.get_connection().await.expect("expected an active wallet connection"); - assert_eq!(address, ALICE); - assert_eq!(chain_id, 1); - - // Connect Bob, assert switched - connect_wallet(&client, &server, Connection::new(BOB, 42)).await; - let Connection { address, chain_id } = - server.get_connection().await.expect("expected an active wallet connection"); - assert_eq!(address, BOB); - assert_eq!(chain_id, 42); - - server.stop().await.unwrap(); - } - - #[tokio::test] - async fn test_transaction_response_both_hash_and_error_rejected() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Enqueue a tx - let (tx_request_id, tx_request) = create_browser_transaction_request(); - let _handle = wait_for_transaction_signing(&server, tx_request).await; - check_transaction_request_content(&client, &server, tx_request_id).await; - - // Wallet posts both hash and error -> should be rejected - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id, - hash: Some(TxHash::random()), - error: Some("should not have both".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Only one of hash or error can be provided"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_transaction_response_neither_hash_nor_error_rejected() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (tx_request_id, tx_request) = create_browser_transaction_request(); - let _handle = wait_for_transaction_signing(&server, tx_request).await; - check_transaction_request_content(&client, &server, tx_request_id).await; - - // Neither hash nor error -> rejected - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { id: tx_request_id, hash: None, error: None }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Either hash or error must be provided"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_transaction_response_zero_hash_rejected() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (tx_request_id, tx_request) = create_browser_transaction_request(); - let _handle = wait_for_transaction_signing(&server, tx_request).await; - check_transaction_request_content(&client, &server, tx_request_id).await; - - // Zero hash -> rejected - let zero = TxHash::new([0u8; 32]); - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { id: tx_request_id, hash: Some(zero), error: None }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - // Message text per your handler; adjust if you use a different string. - assert!( - message.contains("Invalid") || message.contains("Malformed"), - "unexpected message: {message}" - ); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_send_transaction_client_accept() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (tx_request_id, tx_request) = create_browser_transaction_request(); - let handle = wait_for_transaction_signing(&server, tx_request).await; - check_transaction_request_content(&client, &server, tx_request_id).await; - - // Simulate the wallet accepting and signing the tx - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id, - hash: Some(TxHash::random()), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // The join handle should now return the tx hash - let res = handle.await.expect("task panicked"); - match res { - Ok(hash) => { - assert!(hash != TxHash::new([0; 32])); - } - other => panic!("expected success, got {other:?}"), - } - } - - #[tokio::test] - async fn test_send_transaction_client_not_requested() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Create a random transaction response without a matching request - let tx_request_id = Uuid::new_v4(); - - // Simulate the wallet sending a response for an unknown request - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id, - hash: Some(TxHash::random()), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // Assert that no transaction without a matching request is accepted - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Unknown transaction id"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_send_transaction_invalid_response_format() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Simulate the wallet sending a response with an invalid UUID - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .body( - r#"{ - "id": "invalid-uuid", - "hash": "invalid-hash", - "error": null - }"#, - ) - .header("Content-Type", "application/json") - .send() - .await - .unwrap(); - - // The server should respond with a 422 Unprocessable Entity status - assert_eq!(resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY); - } - - #[tokio::test] - async fn test_send_transaction_client_reject() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Create a browser transaction request - let (tx_request_id, tx_request) = create_browser_transaction_request(); - - // Spawn the transaction signing flow in the background - let handle = wait_for_transaction_signing(&server, tx_request).await; - - // Check transaction request - check_transaction_request_content(&client, &server, tx_request_id).await; - - // Simulate the wallet rejecting the tx - let resp = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id, - hash: None, - error: Some("User rejected the transaction".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // The join handle should now return a rejection error - let res = handle.await.expect("task panicked"); - match res { - Err(BrowserWalletError::Rejected { operation, reason }) => { - assert_eq!(operation, "Transaction"); - assert_eq!(reason, "User rejected the transaction"); - } - other => panic!("expected rejection, got {other:?}"), - } - } - - #[tokio::test] - async fn test_send_multiple_transaction_requests() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Create multiple browser transaction requests - let (tx_request_id1, tx_request1) = create_browser_transaction_request(); - let (tx_request_id2, tx_request2) = create_different_browser_transaction_request(); - - // Spawn signing flows for both transactions concurrently - let handle1 = wait_for_transaction_signing(&server, tx_request1.clone()).await; - let handle2 = wait_for_transaction_signing(&server, tx_request2.clone()).await; - - // Check first transaction request - { - let resp = client - .get(format!("http://localhost:{}/api/transaction/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_tx) = resp - .json::>>() - .await - .unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending transaction"); - }; - - assert_eq!( - pending_tx.id, tx_request_id1, - "expected the first transaction to be at the front of the queue" - ); - assert_eq!(pending_tx.request.from, tx_request1.request.from); - assert_eq!(pending_tx.request.to, tx_request1.request.to); - assert_eq!(pending_tx.request.value, tx_request1.request.value); - } - - // Simulate the wallet accepting and signing the first transaction - let resp1 = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id1, - hash: Some(TxHash::random()), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp1.status(), reqwest::StatusCode::OK); - - let res1 = handle1.await.expect("first signing flow panicked"); - match res1 { - Ok(hash) => assert!(!hash.is_zero(), "first tx hash should not be zero"), - other => panic!("expected success, got {other:?}"), - } - - // Check second transaction request - { - let resp = client - .get(format!("http://localhost:{}/api/transaction/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_tx) = resp - .json::>>() - .await - .unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending transaction"); - }; - - assert_eq!( - pending_tx.id, tx_request_id2, - "expected the second transaction to be pending after the first one completed" - ); - assert_eq!(pending_tx.request.from, tx_request2.request.from); - assert_eq!(pending_tx.request.to, tx_request2.request.to); - assert_eq!(pending_tx.request.value, tx_request2.request.value); - } - - // Simulate the wallet rejecting the second transaction - let resp2 = client - .post(format!("http://localhost:{}/api/transaction/response", server.port())) - .json(&BrowserTransactionResponse { - id: tx_request_id2, - hash: None, - error: Some("User rejected the transaction".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp2.status(), reqwest::StatusCode::OK); - - let res2 = handle2.await.expect("second signing flow panicked"); - match res2 { - Err(BrowserWalletError::Rejected { operation, reason }) => { - assert_eq!(operation, "Transaction"); - assert_eq!(reason, "User rejected the transaction"); - } - other => panic!("expected BrowserWalletError::Rejected, got {other:?}"), - } - - check_transaction_request_queue_empty(&client, &server).await; - - server.stop().await.unwrap(); - } - - #[tokio::test] - async fn test_send_sign_response_both_signature_and_error_rejected() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (sign_request_id, sign_request) = create_browser_sign_request(); - let _handle = wait_for_message_signing(&server, sign_request).await; - check_sign_request_content(&client, &server, sign_request_id).await; - - // Both signature and error -> should be rejected - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id, - signature: Some(Bytes::from("Hello World")), - error: Some("Should not have both".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Only one of signature or error can be provided"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_send_sign_response_neither_hash_nor_error_rejected() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (sign_request_id, sign_request) = create_browser_sign_request(); - let _handle = wait_for_message_signing(&server, sign_request).await; - check_sign_request_content(&client, &server, sign_request_id).await; - - // Neither signature nor error -> rejected - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { id: sign_request_id, signature: None, error: None }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Either signature or error must be provided"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_send_sign_client_accept() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (sign_request_id, sign_request) = create_browser_sign_request(); - let handle = wait_for_message_signing(&server, sign_request).await; - check_sign_request_content(&client, &server, sign_request_id).await; - - // Simulate the wallet accepting and signing the message - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id, - signature: Some(Bytes::from("FakeSignature")), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // The join handle should now return the signature - let res = handle.await.expect("task panicked"); - match res { - Ok(signature) => { - assert_eq!(signature, Bytes::from("FakeSignature")); - } - other => panic!("expected success, got {other:?}"), - } - } - - #[tokio::test] - async fn test_send_sign_client_not_requested() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Create a random signing response without a matching request - let sign_request_id = Uuid::new_v4(); - - // Simulate the wallet sending a response for an unknown request - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id, - signature: Some(Bytes::from("FakeSignature")), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // Assert that no signing response without a matching request is accepted - let api: BrowserApiResponse<()> = resp.json().await.unwrap(); - match api { - BrowserApiResponse::Error { message } => { - assert_eq!(message, "Unknown signing request id"); - } - _ => panic!("expected error response"), - } - } - - #[tokio::test] - async fn test_send_sign_invalid_response_format() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Simulate the wallet sending a response with an invalid UUID - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .body( - r#"{ - "id": "invalid-uuid", - "signature": "invalid-signature", - "error": null - }"#, - ) - .header("Content-Type", "application/json") - .send() - .await - .unwrap(); - - // The server should respond with a 422 Unprocessable Entity status - assert_eq!(resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY); - } - - #[tokio::test] - async fn test_send_sign_client_reject() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - let (sign_request_id, sign_request) = create_browser_sign_request(); - let handle = wait_for_message_signing(&server, sign_request).await; - check_sign_request_content(&client, &server, sign_request_id).await; - - // Simulate the wallet rejecting the signing request - let resp = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id, - signature: None, - error: Some("User rejected the signing request".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - - // The join handle should now return a rejection error - let res = handle.await.expect("task panicked"); - match res { - Err(BrowserWalletError::Rejected { operation, reason }) => { - assert_eq!(operation, "Signing"); - assert_eq!(reason, "User rejected the signing request"); - } - other => panic!("expected rejection, got {other:?}"), - } - } - - #[tokio::test] - async fn test_send_multiple_sign_requests() { - let mut server = create_server::(); - let client = client_with_token(&server); - server.start().await.unwrap(); - connect_wallet(&client, &server, Connection::new(ALICE, 1)).await; - - // Create multiple browser sign requests - let (sign_request_id1, sign_request1) = create_browser_sign_request(); - let (sign_request_id2, sign_request2) = create_different_browser_sign_request(); - - // Spawn signing flows for both sign requests concurrently - let handle1 = wait_for_message_signing(&server, sign_request1.clone()).await; - let handle2 = wait_for_message_signing(&server, sign_request2.clone()).await; - - // Check first sign request - { - let resp = client - .get(format!("http://localhost:{}/api/signing/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_sign) = - resp.json::>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending sign request"); - }; - - assert_eq!(pending_sign.id, sign_request_id1); - assert_eq!(pending_sign.sign_type, sign_request1.sign_type); - assert_eq!(pending_sign.request.address, sign_request1.request.address); - assert_eq!(pending_sign.request.message, sign_request1.request.message); - } - - // Simulate the wallet accepting and signing the first sign request - let resp1 = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id1, - signature: Some(Bytes::from("Signature1")), - error: None, - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp1.status(), reqwest::StatusCode::OK); - - let res1 = handle1.await.expect("first signing flow panicked"); - match res1 { - Ok(signature) => assert_eq!(signature, Bytes::from("Signature1")), - other => panic!("expected success, got {other:?}"), - } - - // Check second sign request - { - let resp = client - .get(format!("http://localhost:{}/api/signing/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_sign) = - resp.json::>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending sign request"); - }; - - assert_eq!(pending_sign.id, sign_request_id2,); - assert_eq!(pending_sign.sign_type, sign_request2.sign_type); - assert_eq!(pending_sign.request.address, sign_request2.request.address); - assert_eq!(pending_sign.request.message, sign_request2.request.message); - } - - // Simulate the wallet rejecting the second sign request - let resp2 = client - .post(format!("http://localhost:{}/api/signing/response", server.port())) - .json(&BrowserSignResponse { - id: sign_request_id2, - signature: None, - error: Some("User rejected the signing request".into()), - }) - .send() - .await - .unwrap() - .error_for_status() - .unwrap(); - assert_eq!(resp2.status(), reqwest::StatusCode::OK); - - let res2 = handle2.await.expect("second signing flow panicked"); - match res2 { - Err(BrowserWalletError::Rejected { operation, reason }) => { - assert_eq!(operation, "Signing"); - assert_eq!(reason, "User rejected the signing request"); - } - other => panic!("expected BrowserWalletError::Rejected, got {other:?}"), - } - - check_sign_request_queue_empty(&client, &server).await; - - server.stop().await.unwrap(); - } - - /// Helper to create a default browser wallet server. - fn create_server() -> BrowserWalletServer { - BrowserWalletServer::new(0, false, DEFAULT_TIMEOUT, DEFAULT_DEVELOPMENT) - } - - /// Helper to create a reqwest client with the session token header. - fn client_with_token(server: &BrowserWalletServer) -> reqwest::Client { - let mut headers = HeaderMap::new(); - headers.insert("X-Session-Token", HeaderValue::from_str(server.session_token()).unwrap()); - reqwest::Client::builder().default_headers(headers).build().unwrap() - } - - /// Helper to connect a wallet to the server. - async fn connect_wallet( - client: &reqwest::Client, - server: &BrowserWalletServer, - connection: Connection, - ) { - let resp = client - .post(format!("http://localhost:{}/api/connection", server.port())) - .json(&connection) - .send(); - assert!(resp.await.is_ok()); - } - - /// Helper to disconnect a wallet from the server. - async fn disconnect_wallet( - client: &reqwest::Client, - server: &BrowserWalletServer, - ) { - let resp = client - .post(format!("http://localhost:{}/api/connection", server.port())) - .json(&Option::::None) - .send(); - assert!(resp.await.is_ok()); - } - - /// Spawn the transaction signing flow in the background and return the join handle. - async fn wait_for_transaction_signing( - server: &BrowserWalletServer, - tx_request: BrowserTransactionRequest, - ) -> JoinHandle> { - // Spawn the signing flow in the background - let browser_server = server.clone(); - let join_handle = - tokio::spawn(async move { browser_server.request_transaction(tx_request).await }); - tokio::task::yield_now().await; - tokio::time::sleep(Duration::from_millis(100)).await; - - join_handle - } - - /// Spawn the message signing flow in the background and return the join handle. - async fn wait_for_message_signing( - server: &BrowserWalletServer, - sign_request: BrowserSignRequest, - ) -> JoinHandle> { - // Spawn the signing flow in the background - let browser_server = server.clone(); - let join_handle = - tokio::spawn(async move { browser_server.request_signing(sign_request).await }); - tokio::task::yield_now().await; - tokio::time::sleep(Duration::from_millis(100)).await; - - join_handle - } - - /// Create a simple browser transaction request. - fn create_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) { - let id = Uuid::new_v4(); - let request = N::TransactionRequest::default() - .with_from(ALICE) - .with_to(BOB) - .with_value(U256::from(1000)); - let tx = BrowserTransactionRequest { id, request }; - (id, tx) - } - - /// Create a different browser transaction request (from the first one). - fn create_different_browser_transaction_request() - -> (Uuid, BrowserTransactionRequest) { - let id = Uuid::new_v4(); - let request = N::TransactionRequest::default() - .with_from(BOB) - .with_to(ALICE) - .with_value(U256::from(2000)); - let tx = BrowserTransactionRequest { id, request }; - (id, tx) - } - - /// Create a simple browser sign request. - fn create_browser_sign_request() -> (Uuid, BrowserSignRequest) { - let id = Uuid::new_v4(); - let req = BrowserSignRequest { - id, - sign_type: SignType::PersonalSign, - request: SignRequest { message: "Hello, world!".into(), address: ALICE }, - }; - (id, req) - } - - /// Create a different browser sign request (from the first one). - fn create_different_browser_sign_request() -> (Uuid, BrowserSignRequest) { - let id = Uuid::new_v4(); - let req = BrowserSignRequest { - id, - sign_type: SignType::SignTypedDataV4, - request: SignRequest { message: "Different message".into(), address: BOB }, - }; - (id, req) - } - - /// Check that the transaction request queue is empty, if not panic. - async fn check_transaction_request_queue_empty( - client: &reqwest::Client, - server: &BrowserWalletServer, - ) { - let resp = client - .get(format!("http://localhost:{}/api/transaction/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Error { message } = - resp.json::>>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Error (no pending transaction), but got Ok"); - }; - - assert_eq!(message, "No pending transaction request"); - } - - /// Check that the transaction request matches the expected request ID and fields. - async fn check_transaction_request_content( - client: &reqwest::Client, - server: &BrowserWalletServer, - tx_request_id: Uuid, - ) { - let resp = client - .get(format!("http://localhost:{}/api/transaction/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_tx) = - resp.json::>>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending transaction"); - }; - - assert_eq!(pending_tx.id, tx_request_id); - assert_eq!(pending_tx.request.from(), Some(ALICE)); - assert_eq!(pending_tx.request.kind(), Some(TxKind::Call(BOB))); - assert_eq!(pending_tx.request.value(), Some(U256::from(1000))); - } - - /// Check that the sign request queue is empty, if not panic. - async fn check_sign_request_queue_empty( - client: &reqwest::Client, - server: &BrowserWalletServer, - ) { - let resp = client - .get(format!("http://localhost:{}/api/signing/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Error { message } = - resp.json::>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Error (no pending signing request), but got Ok"); - }; - - assert_eq!(message, "No pending signing request"); - } - - /// Check that the sign request matches the expected request ID and fields. - async fn check_sign_request_content( - client: &reqwest::Client, - server: &BrowserWalletServer, - sign_request_id: Uuid, - ) { - let resp = client - .get(format!("http://localhost:{}/api/signing/request", server.port())) - .send() - .await - .unwrap(); - - let BrowserApiResponse::Ok(pending_req) = - resp.json::>().await.unwrap() - else { - panic!("expected BrowserApiResponse::Ok with a pending signing request"); - }; - - assert_eq!(pending_req.id, sign_request_id); - assert_eq!(pending_req.sign_type, SignType::PersonalSign); - assert_eq!(pending_req.request.address, ALICE); - assert_eq!(pending_req.request.message, "Hello, world!"); - } -} diff --git a/crates/wallets/src/wallet_browser/opts.rs b/crates/wallets/src/wallet_browser/opts.rs deleted file mode 100644 index b8a30727115a5..0000000000000 --- a/crates/wallets/src/wallet_browser/opts.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::time::Duration; - -use alloy_network::Network; -use clap::Parser; -use eyre::Result; -use serde::Serialize; - -use crate::wallet_browser::signer::BrowserSigner; - -/// Browser wallet options -#[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options - browser wallet")] -pub struct BrowserWalletOpts { - /// Use a browser wallet. - #[arg(long)] - pub browser: bool, - - /// Port for the browser wallet server. - #[arg(long, value_name = "PORT", default_value = "9545", requires = "browser")] - pub browser_port: u16, - - /// Whether to open the browser for wallet connection. - #[arg(long, default_value_t = false, requires = "browser")] - pub browser_disable_open: bool, - - /// Enable development mode for the browser wallet. - /// This relaxes certain security features for local development. - /// - /// **WARNING**: This should only be used in a development environment. - #[arg(long, hide = true)] - pub browser_development: bool, -} - -impl BrowserWalletOpts { - pub async fn run(&self) -> Result>> { - Ok(if self.browser { - Some( - BrowserSigner::new( - self.browser_port, - !self.browser_disable_open, - Duration::from_secs(300), - self.browser_development, - ) - .await?, - ) - } else { - None - }) - } -} diff --git a/crates/wallets/src/wallet_browser/queue.rs b/crates/wallets/src/wallet_browser/queue.rs deleted file mode 100644 index 5df44a09e9fde..0000000000000 --- a/crates/wallets/src/wallet_browser/queue.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::collections::{HashMap, VecDeque}; - -use alloy_network::Network; -use uuid::Uuid; - -use crate::wallet_browser::types::{BrowserSignRequest, BrowserTransactionRequest}; - -#[derive(Debug)] -pub(crate) struct RequestQueue { - /// Pending requests from CLI to browser - requests: VecDeque, - /// Responses from browser indexed by request ID - responses: HashMap, -} - -impl Default for RequestQueue { - fn default() -> Self { - Self::new() - } -} - -impl RequestQueue { - /// Create a new request queue. - pub fn new() -> Self { - Self { requests: VecDeque::new(), responses: HashMap::new() } - } - - /// Add a new request to the queue. - pub fn add_request(&mut self, request: Req) { - self.requests.push_back(request); - } - - /// Check if the queue contains any pending requests matching the given ID. - pub fn has_request(&self, id: &Uuid) -> bool - where - Req: HasId, - { - self.requests.iter().any(|r| r.id() == id) - } - - /// Read the next request from the queue without removing it. - pub fn read_request(&self) -> Option<&Req> { - self.requests.front() - } - - /// Remove a request by its ID. - pub fn remove_request(&mut self, id: &Uuid) -> Option - where - Req: HasId, - { - if let Some(pos) = self.requests.iter().position(|r| r.id() == id) { - self.requests.remove(pos) - } else { - None - } - } - - /// Add a response to the queue. - pub fn add_response(&mut self, id: Uuid, response: Res) { - self.responses.insert(id, response); - } - - /// Get a response by its ID, removing it from the queue. - pub fn get_response(&mut self, id: &Uuid) -> Option { - self.responses.remove(id) - } -} - -pub(crate) trait HasId { - fn id(&self) -> &Uuid; -} - -impl HasId for BrowserTransactionRequest { - fn id(&self) -> &Uuid { - &self.id - } -} - -impl HasId for BrowserSignRequest { - fn id(&self) -> &Uuid { - &self.id - } -} diff --git a/crates/wallets/src/wallet_browser/router.rs b/crates/wallets/src/wallet_browser/router.rs deleted file mode 100644 index cb29e9122a5d8..0000000000000 --- a/crates/wallets/src/wallet_browser/router.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::sync::Arc; - -use alloy_network::Network; -use axum::{ - Router, - extract::{Request, State}, - http::{HeaderValue, Method, StatusCode, header}, - middleware::{self, Next}, - response::Response, - routing::{get, post}, -}; -use tower::ServiceBuilder; -use tower_http::{cors::CorsLayer, set_header::SetResponseHeaderLayer}; - -use crate::wallet_browser::{handlers, state::BrowserWalletState}; - -pub async fn build_router(state: Arc>, port: u16) -> Router { - let api = Router::new() - .route("/transaction/request", get(handlers::get_next_transaction_request)) - .route("/transaction/response", post(handlers::post_transaction_response)) - .route("/signing/request", get(handlers::get_next_signing_request)) - .route("/signing/response", post(handlers::post_signing_response)) - .route("/connection", get(handlers::get_connection_info)) - .route("/connection", post(handlers::post_connection_update)) - .route_layer(middleware::from_fn_with_state(state.clone(), require_session_token)) - .with_state(state.clone()); - - let mut origins = vec![format!("http://127.0.0.1:{port}").parse().unwrap()]; - - // Allow default port of 5173 in development mode. - if state.is_development() { - origins.push("https://localhost:5173".to_string().parse().unwrap()); - } - - let security_headers = ServiceBuilder::new() - .layer(SetResponseHeaderLayer::if_not_present( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static(concat!( - "default-src 'none'; ", - "object-src 'none'; ", - "base-uri 'none'; ", - "frame-ancestors 'none'; ", - "img-src 'self'; ", - "font-src 'none'; ", - "connect-src 'self' https: http: wss: ws:;", - "style-src 'self'; ", - "script-src 'self'; ", - "form-action 'none'; ", - "worker-src 'none'; ", - "frame-src https://id.porto.sh;" - )), - )) - .layer(SetResponseHeaderLayer::if_not_present( - header::REFERRER_POLICY, - HeaderValue::from_static("no-referrer"), - )) - .layer(SetResponseHeaderLayer::if_not_present( - header::X_CONTENT_TYPE_OPTIONS, - HeaderValue::from_static("nosniff"), - )) - .layer( - CorsLayer::new() - .allow_origin(origins) - .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) - .allow_headers([header::CONTENT_TYPE]) - .allow_credentials(false), - ); - - Router::new() - .route("/", get(handlers::serve_index)) - .route("/styles.css", get(handlers::serve_css)) - .route("/main.js", get(handlers::serve_js)) - .route("/banner.png", get(handlers::serve_banner_png)) - .route("/logo.png", get(handlers::serve_logo_png)) - .nest("/api", api) - .layer(security_headers) - .with_state(state) -} - -async fn require_session_token( - State(state): State>>, - req: Request, - next: Next, -) -> Result { - if req.method() == Method::OPTIONS { - return Ok(next.run(req).await); - } - - // In development mode, skip session token check. - if state.is_development() { - return Ok(next.run(req).await); - } - - let expected = state.session_token(); - let provided = req - .headers() - .get("X-Session-Token") - .and_then(|v| v.to_str().ok()) - .ok_or(StatusCode::FORBIDDEN)?; - - if provided != expected { - return Err(StatusCode::FORBIDDEN); - } - - Ok(next.run(req).await) -} diff --git a/crates/wallets/src/wallet_browser/server.rs b/crates/wallets/src/wallet_browser/server.rs deleted file mode 100644 index 0361416640c3a..0000000000000 --- a/crates/wallets/src/wallet_browser/server.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::{ - net::SocketAddr, - sync::Arc, - time::{Duration, Instant}, -}; - -use alloy_dyn_abi::TypedData; -use alloy_network::Network; -use alloy_primitives::{Address, Bytes, TxHash}; -use tokio::{ - net::TcpListener, - sync::{Mutex, oneshot}, -}; -use uuid::Uuid; - -use crate::wallet_browser::{ - error::BrowserWalletError, - router::build_router, - state::BrowserWalletState, - types::{ - BrowserSignRequest, BrowserSignTypedDataRequest, BrowserTransactionRequest, Connection, - SignRequest, SignType, - }, -}; - -/// Browser wallet server. -#[derive(Debug, Clone)] -pub struct BrowserWalletServer { - port: u16, - state: Arc>, - shutdown_tx: Option>>>>, - open_browser: bool, - timeout: Duration, -} - -impl BrowserWalletServer { - /// Create a new browser wallet server. - pub fn new(port: u16, open_browser: bool, timeout: Duration, development: bool) -> Self { - Self { - port, - state: Arc::new(BrowserWalletState::new(Uuid::new_v4().to_string(), development)), - shutdown_tx: None, - open_browser, - timeout, - } - } - - /// Start the server and open browser. - pub async fn start(&mut self) -> Result<(), BrowserWalletError> { - let router = build_router(self.state.clone(), self.port).await; - - let addr = SocketAddr::from(([127, 0, 0, 1], self.port)); - let listener = TcpListener::bind(addr) - .await - .map_err(|e| BrowserWalletError::ServerError(e.to_string()))?; - self.port = listener.local_addr().unwrap().port(); - - let (shutdown_tx, shutdown_rx) = oneshot::channel(); - self.shutdown_tx = Some(Arc::new(Mutex::new(Some(shutdown_tx)))); - - tokio::spawn(async move { - let server = axum::serve(listener, router); - let _ = server - .with_graceful_shutdown(async { - let _ = shutdown_rx.await; - }) - .await; - }); - - if self.open_browser { - webbrowser::open(&format!("http://127.0.0.1:{}", self.port)).map_err(|e| { - BrowserWalletError::ServerError(format!("Failed to open browser: {e}")) - })?; - } - - Ok(()) - } - - /// Stop the server. - pub async fn stop(&mut self) -> Result<(), BrowserWalletError> { - if let Some(shutdown_arc) = self.shutdown_tx.take() - && let Some(tx) = shutdown_arc.lock().await.take() - { - let _ = tx.send(()); - } - Ok(()) - } - - /// Get the server port. - pub const fn port(&self) -> u16 { - self.port - } - - /// Check if the browser should be opened. - pub const fn open_browser(&self) -> bool { - self.open_browser - } - - /// Get the timeout duration. - pub const fn timeout(&self) -> Duration { - self.timeout - } - - /// Get the session token. - pub fn session_token(&self) -> &str { - self.state.session_token() - } - - /// Check if a wallet is connected. - pub async fn is_connected(&self) -> bool { - self.state.is_connected().await - } - - /// Get current wallet connection. - pub async fn get_connection(&self) -> Option { - self.state.get_connection().await - } - - /// Request a transaction to be signed and sent via the browser wallet. - pub async fn request_transaction( - &self, - request: BrowserTransactionRequest, - ) -> Result { - if !self.is_connected().await { - return Err(BrowserWalletError::NotConnected); - } - - let tx_id = request.id; - - self.state.add_transaction_request(request).await; - - let start = Instant::now(); - - loop { - if let Some(response) = self.state.get_transaction_response(&tx_id).await { - if let Some(hash) = response.hash { - return Ok(hash); - } else if let Some(error) = response.error { - return Err(BrowserWalletError::Rejected { - operation: "Transaction", - reason: error, - }); - } - return Err(BrowserWalletError::ServerError( - "Transaction response missing both hash and error".to_string(), - )); - } - - if start.elapsed() > self.timeout { - self.state.remove_transaction_request(&tx_id).await; - return Err(BrowserWalletError::Timeout { operation: "Transaction" }); - } - - tokio::time::sleep(Duration::from_millis(100)).await; - } - } - - /// Request a message to be signed via the browser wallet. - pub async fn request_signing( - &self, - request: BrowserSignRequest, - ) -> Result { - if !self.is_connected().await { - return Err(BrowserWalletError::NotConnected); - } - - let tx_id = request.id; - - self.state.add_signing_request(request).await; - - let start = Instant::now(); - - loop { - if let Some(response) = self.state.get_signing_response(&tx_id).await { - if let Some(signature) = response.signature { - return Ok(signature); - } else if let Some(error) = response.error { - return Err(BrowserWalletError::Rejected { - operation: "Signing", - reason: error, - }); - } - return Err(BrowserWalletError::ServerError( - "Signing response missing both signature and error".to_string(), - )); - } - - if start.elapsed() > self.timeout { - self.state.remove_signing_request(&tx_id).await; - return Err(BrowserWalletError::Timeout { operation: "Signing" }); - } - - tokio::time::sleep(Duration::from_millis(100)).await; - } - } - - /// Request EIP-712 typed data signing via the browser wallet. - pub async fn request_typed_data_signing( - &self, - address: Address, - typed_data: TypedData, - ) -> Result { - let request = BrowserSignTypedDataRequest { id: Uuid::new_v4(), address, typed_data }; - - let sign_request = BrowserSignRequest { - id: request.id, - sign_type: SignType::SignTypedDataV4, - request: SignRequest { - message: serde_json::to_string(&request.typed_data).map_err(|e| { - BrowserWalletError::ServerError(format!("Failed to serialize typed data: {e}")) - })?, - address: request.address, - }, - }; - - self.request_signing(sign_request).await - } -} diff --git a/crates/wallets/src/wallet_browser/signer.rs b/crates/wallets/src/wallet_browser/signer.rs deleted file mode 100644 index 55f3bad5a4394..0000000000000 --- a/crates/wallets/src/wallet_browser/signer.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; - -use alloy_network::{Network, TransactionBuilder}; -use alloy_primitives::{Address, B256, ChainId}; -use alloy_signer::Result; -use tokio::sync::Mutex; -use uuid::Uuid; - -use crate::wallet_browser::{ - server::BrowserWalletServer, - types::{BrowserTransactionRequest, Connection}, -}; - -#[derive(Clone, Debug)] -pub struct BrowserSigner { - server: Arc>>, - address: Address, - chain_id: ChainId, -} - -impl BrowserSigner { - pub async fn new( - port: u16, - open_browser: bool, - timeout: Duration, - development: bool, - ) -> Result { - let mut server = BrowserWalletServer::new(port, open_browser, timeout, development); - - server.start().await.map_err(alloy_signer::Error::other)?; - - let _ = sh_warn!("Browser wallet is still in early development. Use with caution!"); - let _ = sh_println!("Opening browser for wallet connection..."); - let _ = sh_println!("Waiting for wallet connection..."); - - let start = Instant::now(); - - loop { - if let Some(Connection { address, chain_id }) = server.get_connection().await { - let _ = sh_println!("Wallet connected: {}", address); - let _ = sh_println!("Chain ID: {}", chain_id); - - return Ok(Self { server: Arc::new(Mutex::new(server)), address, chain_id }); - } - - if start.elapsed() > timeout { - return Err(alloy_signer::Error::other("Wallet connection timeout")); - } - - tokio::time::sleep(Duration::from_secs(1)).await; - } - } - - /// Send a transaction through the browser wallet. - pub async fn send_transaction_via_browser( - &self, - tx_request: N::TransactionRequest, - ) -> Result { - if let Some(from) = tx_request.from() - && from != self.address - { - return Err(alloy_signer::Error::other( - "Transaction `from` address does not match connected wallet address", - )); - } - - if let Some(chain_id) = tx_request.chain_id() - && chain_id != self.chain_id - { - return Err(alloy_signer::Error::other( - "Transaction `chainId` does not match connected wallet chain ID", - )); - } - - let request = BrowserTransactionRequest { id: Uuid::new_v4(), request: tx_request }; - - let server = self.server.lock().await; - let tx_hash = - server.request_transaction(request).await.map_err(alloy_signer::Error::other)?; - - tokio::time::sleep(Duration::from_millis(500)).await; - - Ok(tx_hash) - } - - pub const fn address(&self) -> Address { - self.address - } -} - -impl Drop for BrowserSigner { - fn drop(&mut self) { - let server = self.server.clone(); - - tokio::spawn(async move { - let mut server = server.lock().await; - let _ = server.stop().await; - }); - } -} diff --git a/crates/wallets/src/wallet_browser/state.rs b/crates/wallets/src/wallet_browser/state.rs deleted file mode 100644 index c7c9859ebdcf4..0000000000000 --- a/crates/wallets/src/wallet_browser/state.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::sync::Arc; - -use alloy_network::Network; -use tokio::sync::{Mutex, RwLock}; -use uuid::Uuid; - -use crate::wallet_browser::{ - queue::RequestQueue, - types::{ - BrowserSignRequest, BrowserSignResponse, BrowserTransactionRequest, - BrowserTransactionResponse, Connection, - }, -}; - -#[derive(Debug, Clone)] -pub(crate) struct BrowserWalletState { - /// Current information about the wallet connection. - connection: Arc>>, - /// Request/response queue for transactions. - transactions: - Arc, BrowserTransactionResponse>>>, - /// Request/response queue for signings. - signings: Arc>>, - /// Unique session token for the wallet browser instance. - /// The CSP on the served page prevents this token from being loaded by other origins. - session_token: String, - /// If true, the server is running in development mode. - /// This relaxes certain security restrictions for local development. - /// - /// **WARNING**: This should only be used in a development environment. - development: bool, -} - -impl BrowserWalletState { - /// Create a new browser wallet state. - pub fn new(session_token: String, development: bool) -> Self { - Self { - connection: Arc::new(RwLock::new(None)), - transactions: Arc::new(Mutex::new(RequestQueue::new())), - signings: Arc::new(Mutex::new(RequestQueue::new())), - session_token, - development, - } - } - - /// Get the session token. - pub fn session_token(&self) -> &str { - &self.session_token - } - - /// Check if in development mode. - /// This relaxes certain security restrictions for local development. - /// - /// **WARNING**: This should only be used in a development environment. - pub const fn is_development(&self) -> bool { - self.development - } - - /// Check if wallet is connected. - pub async fn is_connected(&self) -> bool { - self.connection.read().await.is_some() - } - - /// Get current connection information. - pub async fn get_connection(&self) -> Option { - *self.connection.read().await - } - - /// Set connection information. - pub async fn set_connection(&self, connection: Option) { - *self.connection.write().await = connection; - } - - /// Add a transaction request. - pub async fn add_transaction_request(&self, request: BrowserTransactionRequest) { - self.transactions.lock().await.add_request(request); - } - - /// Check if a transaction request exists. - pub async fn has_transaction_request(&self, id: &Uuid) -> bool { - self.transactions.lock().await.has_request(id) - } - - /// Read the next transaction request. - pub async fn read_next_transaction_request(&self) -> Option> { - self.transactions.lock().await.read_request().cloned() - } - - // Remove a transaction request. - pub async fn remove_transaction_request(&self, id: &Uuid) { - self.transactions.lock().await.remove_request(id); - } - - /// Add transaction response. - pub async fn add_transaction_response(&self, response: BrowserTransactionResponse) { - let id = response.id; - let mut transactions = self.transactions.lock().await; - transactions.add_response(id, response); - transactions.remove_request(&id); - } - - /// Get transaction response, removing it from the queue. - pub async fn get_transaction_response(&self, id: &Uuid) -> Option { - self.transactions.lock().await.get_response(id) - } - - /// Add a signing request. - pub async fn add_signing_request(&self, request: BrowserSignRequest) { - self.signings.lock().await.add_request(request); - } - - /// Check if a signing request exists. - pub async fn has_signing_request(&self, id: &Uuid) -> bool { - self.signings.lock().await.has_request(id) - } - - /// Read the next signing request. - pub async fn read_next_signing_request(&self) -> Option { - self.signings.lock().await.read_request().cloned() - } - - /// Remove a signing request. - pub async fn remove_signing_request(&self, id: &Uuid) { - self.signings.lock().await.remove_request(id); - } - - /// Add signing response. - pub async fn add_signing_response(&self, response: BrowserSignResponse) { - let id = response.id; - let mut signings = self.signings.lock().await; - signings.add_response(id, response); - signings.remove_request(&id); - } - - /// Get signing response, removing it from the queue. - pub async fn get_signing_response(&self, id: &Uuid) -> Option { - self.signings.lock().await.get_response(id) - } -} diff --git a/crates/wallets/src/wallet_browser/types.rs b/crates/wallets/src/wallet_browser/types.rs deleted file mode 100644 index 3e37498bb1e19..0000000000000 --- a/crates/wallets/src/wallet_browser/types.rs +++ /dev/null @@ -1,127 +0,0 @@ -use alloy_dyn_abi::TypedData; -use alloy_network::Network; -use alloy_primitives::{Address, Bytes, ChainId, TxHash}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -/// Response format for API endpoints. -/// - `Ok(T)` serializes as: {"status":"ok","data": ...} -/// - `Ok(())` serializes as: {"status":"ok"} (no data key) -/// - `Error { message }` as: {"status":"error","message":"..."} -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "status", content = "data", rename_all = "lowercase")] -pub(crate) enum BrowserApiResponse { - Ok(T), - Error { message: String }, -} - -impl BrowserApiResponse<()> { - /// Create a successful response with no data. - pub const fn ok() -> Self { - Self::Ok(()) - } -} - -impl BrowserApiResponse { - /// Create a successful response with the given data. - pub const fn with_data(data: T) -> Self { - Self::Ok(data) - } - - /// Create an error response with the given message. - pub fn error(msg: impl Into) -> Self { - Self::Error { message: msg.into() } - } -} - -/// Represents a transaction request sent to the browser wallet. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct BrowserTransactionRequest { - /// The unique identifier for the transaction. - pub id: Uuid, - /// The transaction request details. - pub request: N::TransactionRequest, -} - -/// Represents a transaction response sent from the browser wallet. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub(crate) struct BrowserTransactionResponse { - /// The unique identifier for the transaction, must match the request ID sent earlier. - pub id: Uuid, - /// The transaction hash if the transaction was successful. - pub hash: Option, - /// The error message if the transaction failed. - pub error: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub enum SignType { - /// Standard personal sign: `eth_sign` / `personal_sign` - PersonalSign, - /// EIP-712 typed data sign: `eth_signTypedData_v4` - SignTypedDataV4, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SignRequest { - /// The message to be signed. - pub message: String, - /// The address that should sign the message. - pub address: Address, -} - -/// Represents a signing request sent to the browser wallet. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct BrowserSignRequest { - /// The unique identifier for the signing request. - pub id: Uuid, - /// The type of signing operation. - pub sign_type: SignType, - /// The sign request details. - pub request: SignRequest, -} - -/// Represents a typed data signing request sent to the browser wallet. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct BrowserSignTypedDataRequest { - /// The unique identifier for the signing request. - pub id: Uuid, - /// The address that should sign the typed data. - pub address: Address, - /// The typed data to be signed. - pub typed_data: TypedData, -} - -/// Represents a signing response sent from the browser wallet. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub(crate) struct BrowserSignResponse { - /// The unique identifier for the signing request, must match the request ID sent earlier. - pub id: Uuid, - /// The signature if the signing was successful. - pub signature: Option, - /// The error message if the signing failed. - pub error: Option, -} - -/// Represents an active connection to a browser wallet. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct Connection { - /// The address of the connected wallet. - pub address: Address, - /// The chain ID of the connected wallet. - pub chain_id: ChainId, -} - -impl Connection { - /// Create a new connection instance. - pub const fn new(address: Address, chain_id: ChainId) -> Self { - Self { address, chain_id } - } -} diff --git a/crates/wallets/src/wallet_multi/mod.rs b/crates/wallets/src/wallet_multi/mod.rs deleted file mode 100644 index a6d85581e19f0..0000000000000 --- a/crates/wallets/src/wallet_multi/mod.rs +++ /dev/null @@ -1,621 +0,0 @@ -use crate::{ - BrowserWalletOpts, - signer::{PendingSigner, WalletSigner}, - utils, - wallet_browser::signer::BrowserSigner, -}; -use alloy_network::Network; -use alloy_primitives::map::AddressHashMap; -use alloy_signer::Signer; -use clap::Parser; -use derive_builder::Builder; -use eyre::Result; -use foundry_config::Config; -use serde::Serialize; -use std::path::PathBuf; - -/// Container for multiple wallets. -#[derive(Debug, Default)] -pub struct MultiWallet { - /// Vector of wallets that require an action to be unlocked. - /// Those are lazily unlocked on the first access of the signers. - pending_signers: Vec, - /// Contains unlocked signers. - signers: AddressHashMap, -} - -impl MultiWallet { - pub fn new(pending_signers: Vec, signers: Vec) -> Self { - let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); - Self { pending_signers, signers } - } - - fn maybe_unlock_pending(&mut self) -> Result<()> { - for pending in self.pending_signers.drain(..) { - let signer = pending.unlock()?; - self.signers.insert(signer.address(), signer); - } - Ok(()) - } - - pub fn signers(&mut self) -> Result<&AddressHashMap> { - self.maybe_unlock_pending()?; - Ok(&self.signers) - } - - pub fn into_signers(mut self) -> Result> { - self.maybe_unlock_pending()?; - Ok(self.signers) - } - - pub fn add_signer(&mut self, signer: WalletSigner) { - self.signers.insert(signer.address(), signer); - } -} - -/// A macro that initializes multiple wallets -/// -/// Should be used with a [`MultiWallet`] instance -macro_rules! create_hw_wallets { - ($self:ident, $create_signer:expr, $signers:ident) => { - let mut $signers = vec![]; - - if let Some(hd_paths) = &$self.hd_paths { - for path in hd_paths { - let hw = $create_signer(Some(path), 0).await?; - $signers.push(hw); - } - } - - if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { - for index in mnemonic_indexes { - let hw = $create_signer(None, *index).await?; - $signers.push(hw); - } - } - - if $signers.is_empty() { - let hw = $create_signer(None, 0).await?; - $signers.push(hw); - } - }; -} - -/// The wallet options can either be: -/// 1. Ledger -/// 2. Trezor -/// 3. Mnemonics (via file path) -/// 4. Keystores (via file path) -/// 5. Private Keys (cleartext in CLI) -/// 6. Private Keys (interactively via secure prompt) -/// 7. AWS KMS -/// 8. Turnkey -#[derive(Builder, Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct MultiWalletOpts { - /// Open an interactive prompt to enter your private key. - /// - /// Takes a value for the number of keys to enter. - #[arg(long, help_heading = "Wallet options - raw", default_value = "0", value_name = "NUM")] - pub interactives: u32, - - /// Open an interactive prompt to enter your private key. - #[arg(long, short, help_heading = "Wallet options - raw", conflicts_with = "interactives")] - pub interactive: bool, - - /// Use the provided private keys. - #[arg(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] - #[builder(default = "None")] - pub private_keys: Option>, - - /// Use the provided private key. - #[arg( - long, - help_heading = "Wallet options - raw", - conflicts_with = "private_keys", - value_name = "RAW_PRIVATE_KEY" - )] - #[builder(default = "None")] - pub private_key: Option, - - /// Use the mnemonic phrases of mnemonic files at the specified paths. - #[arg(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] - #[builder(default = "None")] - pub mnemonics: Option>, - - /// Use a BIP39 passphrases for the mnemonic. - #[arg(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] - #[builder(default = "None")] - pub mnemonic_passphrases: Option>, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[arg( - long = "mnemonic-derivation-paths", - alias = "hd-paths", - help_heading = "Wallet options - raw", - value_name = "PATH" - )] - #[builder(default = "None")] - pub hd_paths: Option>, - - /// Use the private key from the given mnemonic index. - /// - /// Can be used with --mnemonics, --ledger, --aws and --trezor. - #[arg( - long, - conflicts_with = "hd_paths", - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "INDEXES" - )] - pub mnemonic_indexes: Option>, - - /// Use the keystore by its filename in the given folder. - #[arg( - long = "keystore", - visible_alias = "keystores", - help_heading = "Wallet options - keystore", - value_name = "PATHS", - env = "ETH_KEYSTORE" - )] - #[builder(default = "None")] - pub keystore_paths: Option>, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename. - #[arg( - long = "account", - visible_alias = "accounts", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAMES", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_paths" - )] - #[builder(default = "None")] - pub keystore_account_names: Option>, - - /// The keystore password. - /// - /// Used with --keystore. - #[arg( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PASSWORDS" - )] - #[builder(default = "None")] - pub keystore_passwords: Option>, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[arg( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PATHS", - env = "ETH_PASSWORD" - )] - #[builder(default = "None")] - pub keystore_password_files: Option>, - - /// Use a Ledger hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - /// - /// Ensure either one of AWS_KMS_KEY_IDS (comma-separated) or AWS_KMS_KEY_ID environment - /// variables are set. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] - pub aws: bool, - - /// Use Google Cloud Key Management Service. - /// - /// Ensure the following environment variables are set: GCP_PROJECT_ID, GCP_LOCATION, - /// GCP_KEY_RING, GCP_KEY_NAME, GCP_KEY_VERSION. - /// - /// See: - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] - pub gcp: bool, - - /// Use Turnkey. - /// - /// Ensure the following environment variables are set: TURNKEY_API_PRIVATE_KEY, - /// TURNKEY_ORGANIZATION_ID, TURNKEY_ADDRESS. - /// - /// See: - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))] - pub turnkey: bool, - - /// Browser wallet options - #[command(flatten)] - pub browser: BrowserWalletOpts, -} - -impl MultiWalletOpts { - /// Returns [MultiWallet] container configured with provided options. - pub async fn get_multi_wallet(&self) -> Result { - let mut pending = Vec::new(); - let mut signers: Vec = Vec::new(); - - if let Some(ledgers) = self.ledgers().await? { - signers.extend(ledgers); - } - if let Some(trezors) = self.trezors().await? { - signers.extend(trezors); - } - if let Some(aws_signers) = self.aws_signers().await? { - signers.extend(aws_signers); - } - if let Some(gcp_signer) = self.gcp_signers().await? { - signers.extend(gcp_signer); - } - if let Some(turnkey_signers) = self.turnkey_signers()? { - signers.extend(turnkey_signers); - } - if let Some((pending_keystores, unlocked)) = self.keystores()? { - pending.extend(pending_keystores); - signers.extend(unlocked); - } - if let Some(pks) = self.private_keys()? { - signers.extend(pks); - } - if let Some(mnemonics) = self.mnemonics()? { - signers.extend(mnemonics); - } - if self.interactive { - pending.push(PendingSigner::Interactive); - } - if self.interactives > 0 { - pending.extend(std::iter::repeat_n( - PendingSigner::Interactive, - self.interactives as usize, - )); - } - - Ok(MultiWallet::new(pending, signers)) - } - - pub fn private_keys(&self) -> Result>> { - let mut pks = vec![]; - if let Some(private_key) = &self.private_key { - pks.push(private_key); - } - if let Some(private_keys) = &self.private_keys { - for pk in private_keys { - pks.push(pk); - } - } - if pks.is_empty() { - Ok(None) - } else { - let wallets = pks - .into_iter() - .map(|pk| utils::create_private_key_signer(pk)) - .collect::>>()?; - Ok(Some(wallets)) - } - } - - fn keystore_paths(&self) -> Result>> { - if let Some(keystore_paths) = &self.keystore_paths { - return Ok(Some(keystore_paths.iter().map(PathBuf::from).collect())); - } - if let Some(keystore_account_names) = &self.keystore_account_names { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - return Ok(Some( - keystore_account_names - .iter() - .map(|keystore_name| default_keystore_dir.join(keystore_name)) - .collect(), - )); - } - Ok(None) - } - - /// Returns all wallets read from the provided keystores arguments - /// - /// Returns `Ok(None)` if no keystore provided. - pub fn keystores(&self) -> Result, Vec)>> { - if let Some(keystore_paths) = self.keystore_paths()? { - let mut pending = Vec::new(); - let mut signers = Vec::new(); - - let mut passwords_iter = - self.keystore_passwords.iter().flat_map(|passwords| passwords.iter()); - - let mut password_files_iter = self - .keystore_password_files - .iter() - .flat_map(|password_files| password_files.iter()); - - for path in &keystore_paths { - let (maybe_signer, maybe_pending) = utils::create_keystore_signer( - path, - passwords_iter.next().map(|password| password.as_str()), - password_files_iter.next().map(|password_file| password_file.as_str()), - )?; - if let Some(pending_signer) = maybe_pending { - pending.push(pending_signer); - } else if let Some(signer) = maybe_signer { - signers.push(signer); - } - } - return Ok(Some((pending, signers))); - } - Ok(None) - } - - pub fn mnemonics(&self) -> Result>> { - if let Some(ref mnemonics) = self.mnemonics { - let mut wallets = vec![]; - - let mut hd_paths_iter = - self.hd_paths.iter().flat_map(|paths| paths.iter().map(String::as_str)); - - let mut passphrases_iter = self - .mnemonic_passphrases - .iter() - .flat_map(|passphrases| passphrases.iter().map(String::as_str)); - - let mut indexes_iter = - self.mnemonic_indexes.iter().flat_map(|indexes| indexes.iter().copied()); - - for mnemonic in mnemonics { - let wallet = utils::create_mnemonic_signer( - mnemonic, - passphrases_iter.next(), - hd_paths_iter.next(), - indexes_iter.next().unwrap_or(0), - )?; - wallets.push(wallet); - } - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn ledgers(&self) -> Result>> { - if self.ledger { - let mut args = self.clone(); - - if let Some(paths) = &args.hd_paths { - if paths.len() > 1 { - eyre::bail!("Ledger only supports one signer."); - } - args.mnemonic_indexes = None; - } - - create_hw_wallets!(args, utils::create_ledger_signer, wallets); - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn trezors(&self) -> Result>> { - if self.trezor { - let mut args = self.clone(); - - if args.hd_paths.is_some() { - args.mnemonic_indexes = None; - } - - create_hw_wallets!(args, utils::create_trezor_signer, wallets); - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn aws_signers(&self) -> Result>> { - #[cfg(feature = "aws-kms")] - if self.aws { - let mut wallets = vec![]; - let aws_keys = std::env::var("AWS_KMS_KEY_IDS") - .or(std::env::var("AWS_KMS_KEY_ID"))? - .split(',') - .map(|k| k.to_string()) - .collect::>(); - - for key in aws_keys { - let aws_signer = WalletSigner::from_aws(key).await?; - wallets.push(aws_signer) - } - - return Ok(Some(wallets)); - } - - Ok(None) - } - - /// Returns a list of GCP signers if the GCP flag is set. - /// - /// The GCP signers are created from the following environment variables: - /// - GCP_PROJECT_ID: The GCP project ID. e.g. `my-project-123456`. - /// - GCP_LOCATION: The GCP location. e.g. `us-central1`. - /// - GCP_KEY_RING: The GCP key ring name. e.g. `my-key-ring`. - /// - GCP_KEY_NAME: The GCP key name. e.g. `my-key`. - /// - GCP_KEY_VERSION: The GCP key version. e.g. `1`. - /// - /// For more information on GCP KMS, see the [official documentation](https://cloud.google.com/kms/docs). - pub async fn gcp_signers(&self) -> Result>> { - #[cfg(feature = "gcp-kms")] - if self.gcp { - let mut wallets = vec![]; - - let project_id = std::env::var("GCP_PROJECT_ID")?; - let location = std::env::var("GCP_LOCATION")?; - let key_ring = std::env::var("GCP_KEY_RING")?; - let key_name = std::env::var("GCP_KEY_NAME")?; - let key_version = std::env::var("GCP_KEY_VERSION")?; - - let gcp_signer = WalletSigner::from_gcp( - project_id, - location, - key_ring, - key_name, - key_version.parse()?, - ) - .await?; - wallets.push(gcp_signer); - - return Ok(Some(wallets)); - } - - Ok(None) - } - - pub fn turnkey_signers(&self) -> Result>> { - #[cfg(feature = "turnkey")] - if self.turnkey { - let api_private_key = std::env::var("TURNKEY_API_PRIVATE_KEY")?; - let organization_id = std::env::var("TURNKEY_ORGANIZATION_ID")?; - let address = std::env::var("TURNKEY_ADDRESS")?.parse()?; - - let signer = WalletSigner::from_turnkey(api_private_key, organization_id, address)?; - return Ok(Some(vec![signer])); - } - - Ok(None) - } - - /// Returns the Turnkey address if `--turnkey` flag is set and `TURNKEY_ADDRESS` is available. - pub fn turnkey_address(&self) -> Option { - #[cfg(feature = "turnkey")] - if self.turnkey { - return std::env::var("TURNKEY_ADDRESS").ok().and_then(|addr| addr.parse().ok()); - } - - None - } - - /// Launches and returns the Browser signer if `--browser` flag is set - pub async fn browser_signer(&self) -> Result>> { - self.browser.run().await - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::address; - use std::path::Path; - - #[test] - fn parse_keystore_args() { - let args: MultiWalletOpts = - MultiWalletOpts::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); - assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); - - unsafe { - std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); - } - let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); - assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); - - unsafe { - std::env::remove_var("ETH_KEYSTORE"); - } - } - - #[test] - fn parse_keystore_password_file() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - - let keystore_password_file = keystore.join("password-ec554").into_os_string(); - - let args: MultiWalletOpts = MultiWalletOpts::parse_from([ - "foundry-cli", - "--keystores", - keystore_file.to_str().unwrap(), - "--password-file", - keystore_password_file.to_str().unwrap(), - ]); - assert_eq!( - args.keystore_password_files, - Some(vec![keystore_password_file.to_str().unwrap().to_string()]) - ); - - let (_, unlocked) = args.keystores().unwrap().unwrap(); - assert_eq!(unlocked.len(), 1); - assert_eq!(unlocked[0].address(), address!("0xec554aeafe75601aaab43bd4621a22284db566c2")); - } - - // https://github.com/foundry-rs/foundry/issues/12916 - #[test] - #[cfg(feature = "turnkey")] - fn turnkey_address_returns_address_when_flag_set() { - let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli", "--turnkey"]); - assert!(args.turnkey); - - unsafe { - std::env::set_var("TURNKEY_ADDRESS", "0x1234567890123456789012345678901234567890"); - } - - let addr = args.turnkey_address(); - assert_eq!(addr, Some(address!("0x1234567890123456789012345678901234567890"))); - - unsafe { - std::env::remove_var("TURNKEY_ADDRESS"); - } - } - - #[test] - fn turnkey_address_returns_none_when_flag_not_set() { - let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); - assert!(!args.turnkey); - - unsafe { - std::env::set_var("TURNKEY_ADDRESS", "0x1234567890123456789012345678901234567890"); - } - - let addr = args.turnkey_address(); - assert_eq!(addr, None); - - unsafe { - std::env::remove_var("TURNKEY_ADDRESS"); - } - } - - // https://github.com/foundry-rs/foundry/issues/5179 - #[test] - fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { - let wallet_options = vec![ - ("ledger", "--mnemonic-indexes", 1), - ("trezor", "--mnemonic-indexes", 2), - ("aws", "--mnemonic-indexes", 10), - ("turnkey", "--mnemonic-indexes", 11), - ]; - - for test_case in wallet_options { - let args: MultiWalletOpts = MultiWalletOpts::parse_from([ - "foundry-cli", - &format!("--{}", test_case.0), - test_case.1, - &test_case.2.to_string(), - ]); - - match test_case.0 { - "ledger" => assert!(args.ledger), - "trezor" => assert!(args.trezor), - "aws" => assert!(args.aws), - "turnkey" => assert!(args.turnkey), - _ => panic!("Should have matched one of the previous wallet options"), - } - - assert_eq!( - args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], - test_case.2 - ) - } - } -} diff --git a/crates/wallets/src/wallet_raw/mod.rs b/crates/wallets/src/wallet_raw/mod.rs deleted file mode 100644 index c13076dafaf51..0000000000000 --- a/crates/wallets/src/wallet_raw/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::{PendingSigner, WalletSigner, utils}; -use clap::Parser; -use eyre::Result; -use serde::Serialize; - -/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. -/// The raw wallet options can either be: -/// 1. Private Key (cleartext in CLI) -/// 2. Private Key (interactively via secure prompt) -/// 3. Mnemonic (via file path) -#[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options - raw", about = None, long_about = None)] -pub struct RawWalletOpts { - /// Open an interactive prompt to enter your private key. - #[arg(long, short)] - pub interactive: bool, - - /// Use the provided private key. - #[arg(long, value_name = "RAW_PRIVATE_KEY")] - pub private_key: Option, - - /// Use the mnemonic phrase of mnemonic file at the specified path. - #[arg(long, alias = "mnemonic-path")] - pub mnemonic: Option, - - /// Use a BIP39 passphrase for the mnemonic. - #[arg(long, value_name = "PASSPHRASE")] - pub mnemonic_passphrase: Option, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[arg(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] - pub hd_path: Option, - - /// Use the private key from the given mnemonic index. - /// - /// Used with --mnemonic-path. - #[arg(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] - pub mnemonic_index: u32, -} - -impl RawWalletOpts { - /// Returns signer configured by provided parameters. - pub fn signer(&self) -> Result> { - if self.interactive { - return Ok(Some(PendingSigner::Interactive.unlock()?)); - } - if let Some(private_key) = &self.private_key { - return Ok(Some(utils::create_private_key_signer(private_key)?)); - } - if let Some(mnemonic) = &self.mnemonic { - return Ok(Some(utils::create_mnemonic_signer( - mnemonic, - self.mnemonic_passphrase.as_deref(), - self.hd_path.as_deref(), - self.mnemonic_index, - )?)); - } - Ok(None) - } -} diff --git a/deny.toml b/deny.toml index 15f839ae72189..b9dae10bfabf3 100644 --- a/deny.toml +++ b/deny.toml @@ -102,6 +102,7 @@ allow-git = [ "https://github.com/alloy-rs/evm", "https://github.com/foundry-rs/compilers", "https://github.com/foundry-rs/foundry-fork-db", + "https://github.com/foundry-rs/foundry-core", "https://github.com/foundry-rs/optimism", "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/paradigmxyz/solar", diff --git a/doc/doc-filelist.js b/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/doc/doc-script.js b/doc/doc-script.js new file mode 100644 index 0000000000000..7fa122605e7cb --- /dev/null +++ b/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +var sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/doc/doc-style.css b/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/flake.lock b/flake.lock index 3b3ca69bf658b..4fa81efa24f11 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1775891769, - "narHash": "sha256-EOfVlTKw2n8w1uhfh46GS4hEGnQ7oWrIWQfIY6utIkI=", + "lastModified": 1776497206, + "narHash": "sha256-Em+RSdFnwyyKPGUBFtQYtVjm+1UvIc9gOR91Y22zlzg=", "owner": "nix-community", "repo": "fenix", - "rev": "6fbc54dde15aee725bdc7aae5e478849685d5f56", + "rev": "df2295365fb081fe0745449762a771290782c22d", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1775888245, - "narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=", + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "13043924aaa7375ce482ebe2494338e058282925", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1775843361, - "narHash": "sha256-j53ZgyDvmYf3Sjh1IPvvTjqa614qUfVQSzj59+MpzkY=", + "lastModified": 1776441750, + "narHash": "sha256-1rVfG+mj8R4ze+lSYCa4iAv7FzrB03Cprtxmd1MfZak=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "9eb97ea96d8400e8957ddd56702e962614296583", + "rev": "251df518d73abb5c5d573c4d5d266a3edae9ca5a", "type": "github" }, "original": { diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** From 54113b1a460e5dc4ce50113690b375bb0057b7a8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:58:26 +0000 Subject: [PATCH 309/391] fix(cheatcodes): read broadcasts with the active network #437 (#440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry (#14347) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry When an access key is refreshed but not yet provisioned on-chain, the first charge payment strips key_authorization (assuming provisioned), the server rejects with verification-failed, and the retry must obtain a fresh 402 challenge — the server consumes challenge IDs on first use, so reusing the original challenge would fail again. Amp-Thread-ID: https://ampcode.com/threads/T-019d8bde-8cf7-778c-9c9c-727632e62bd4 Co-authored-by: Amp * refactor(mpp): extract select_challenge helper, improve retry robustness - Extract shared select_challenge() for consistent 402 diagnostics on both initial and verification-failed retry paths - Don't restore key_provisioned(true) on non-402 fresh probe responses to avoid bad state on transient server errors - Add rollback_pending() to key-not-provisioned retry to prevent dirty local state from producing wrong credential shapes Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019d9619-6c24-75af-b1e8-7d02d77e0561 --------- Co-authored-by: Amp * refactor(evm): remove useless Eth/Tempo EVM wrapper structs (#14350) * chore(mpp): add built-in rpc url mapping (#14353) add built-in rpc url mapping * fix(evm): skip isolation for CREATE2 factory redirect calls (#14360) * fix(evm): preserve CREATE2 redirect state across isolated transactions (#14363) * feat(init): add `SignatureVerifier` to tempo example (#14351) * feat(init): add `SignatureVerifier` to tempo example * chore: also call `recover()` * style: typo --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(config): validate `optimizer_runs` does not exceed u32::MAX (#14354) * fix(config): validate `optimizer_runs` does not exceed u32::MAX * fix clippy * ci: remove softprops/action-gh-release dependabot ignore (#14364) The draft-finalize race that caused `Too many retries` in matrix jobs (v2.5.0) was fixed in v2.5.2. We're already on v2.6.1, so the ignore rule is no longer needed. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): mine TIP20 virtual address master salt (#14365) * chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 (#14367) * chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#14368) * chore(deps): bump taiki-e/install-action from 2.75.0 to 2.75.5 (#14369) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.75.0 to 2.75.5. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cf39a74df4a72510be4e5b63348d61067f11e64a...a2352fc6ce487f030a3aa709482d57823eadfb37) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.75.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump anstream from 0.6.21 to 1.0.0 (#14329) Bumps [anstream](https://github.com/rust-cli/anstyle) from 0.6.21 to 1.0.0. - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0) --- updated-dependencies: - dependency-name: anstream dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * Update flake.lock (#14375) Co-authored-by: github-actions[bot] * fix(ci): handle stale branches in bump-tempo workflow (#14377) * fix(config): surface cleanup failures as warnings instead of hard errors (#14379) * fix(config): surface cleanup failures as warnings instead of hard errors Cache cleanup is best-effort by design. Instead of silently swallowing errors or making them fatal, return warnings from Config and emit them via sh_warn! at the CLI layer. All cleanup steps now run even if some fail. Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * fix: address CI - collapse nested ifs, remove disallowed eprintln Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * chore(tempo): bump rev + use extension traits (#14378) * feat(tempo): bump tempo rev and use TempoProviderExt::is_hardfork_active Bumps tempo crates from 6f4f5cc to bb08bb9 (latest main) and replaces the local is_hardfork_active helper with the new trait method on TempoProviderExt, which queries the tempo_forkSchedule RPC directly. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: propagate is_hardfork_active error instead of unwrap_or Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * refactor: use TempoAddressExt in tempo_labels and fee token parsing Replace is_tip20_prefix() with addr.is_tip20() in the TempoLabels inspector and use Address::TIP20_PREFIX instead of hardcoded hex bytes in token_id_to_address. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: enable reth-codec feature on tempo-primitives and fix rustfmt The rev bump split tempo crates into two copies (6f4f5cc for mpp-rs, bb08bb9 for foundry). The bb08bb9 copy lost the reth-codec feature that was previously unified from other workspace members. Explicitly enable it to restore Compact derives on TempoTxEnvelope/TempoHeader. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * chore: remove cargo-update workflow (#14382) Dependabot is now configured for weekly cargo updates, making this workflow redundant. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): return error for eth_getLogs with unknown blockHash instead of empty (#14371) Co-authored-by: Claude Opus 4.7 (1M context) * refactor(cast): cleanup `cast send` (#14385) * refactor(cast): cleanup `cast send` * docs: clarify `TempoNetwork` requirement * style: clippy * fix: review feedback * fix(anvil): refetch full fork blocks with missing tx cache (#14384) * docs: correct Tempo TIP-1022 documentation URL (#14387) * feat(verify): clearer error when `ETHERSCAN_API_KEY` selects etherscan (#14383) * feat: make asm-keccak a default feature in all binaries (#14389) * chore(wallets): move `wallets` crate to `foundry-core` (#14348) * fix(cheatcodes): read broadcasts with the active network (#14388) --------- Signed-off-by: dependabot[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: Louis Peter Sitoe Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: o-az From 6c5e62d9212aba839d8e1662defefd8377a3adbf Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:48:14 +0700 Subject: [PATCH 310/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..ca543dbc1fe83 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} From ae901be8c6e93bc3ab4095999b0e49168d8f85f2 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:48:32 +0700 Subject: [PATCH 311/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index ca543dbc1fe83..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; From f29d0aa1016c29ce5e9221a246dc2d41c1ced0c7 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:00:45 +0000 Subject: [PATCH 312/391] Wagmi (e604566) (#412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update docker.yml * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * doc-script.js (#438) * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <… * fix(cheatcodes): read broadcasts with the active network #437 (#440) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry (#14347) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry When an access key is refreshed but not yet provisioned on-chain, the first charge payment strips key_authorization (assuming provisioned), the server rejects with verification-failed, and the retry must obtain a fresh 402 challenge — the server consumes challenge IDs on first use, so reusing the original challenge would fail again. Amp-Thread-ID: https://ampcode.com/threads/T-019d8bde-8cf7-778c-9c9c-727632e62bd4 Co-authored-by: Amp * refactor(mpp): extract select_challenge helper, improve retry robustness - Extract shared select_challenge() for consistent 402 diagnostics on both initial and verification-failed retry paths - Don't restore key_provisioned(true) on non-402 fresh probe responses to avoid bad state on transient server errors - Add rollback_pending() to key-not-provisioned retry to prevent dirty local state from producing wrong credential shapes Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019d9619-6c24-75af-b1e8-7d02d77e0561 --------- Co-authored-by: Amp * refactor(evm): remove useless Eth/Tempo EVM wrapper structs (#14350) * chore(mpp): add built-in rpc url mapping (#14353) add built-in rpc url mapping * fix(evm): skip isolation for CREATE2 factory redirect calls (#14360) * fix(evm): preserve CREATE2 redirect state across isolated transactions (#14363) * feat(init): add `SignatureVerifier` to tempo example (#14351) * feat(init): add `SignatureVerifier` to tempo example * chore: also call `recover()` * style: typo --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(config): validate `optimizer_runs` does not exceed u32::MAX (#14354) * fix(config): validate `optimizer_runs` does not exceed u32::MAX * fix clippy * ci: remove softprops/action-gh-release dependabot ignore (#14364) The draft-finalize race that caused `Too many retries` in matrix jobs (v2.5.0) was fixed in v2.5.2. We're already on v2.6.1, so the ignore rule is no longer needed. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): mine TIP20 virtual address master salt (#14365) * chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 (#14367) * chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#14368) * chore(deps): bump taiki-e/install-action from 2.75.0 to 2.75.5 (#14369) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.75.0 to 2.75.5. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cf39a74df4a72510be4e5b63348d61067f11e64a...a2352fc6ce487f030a3aa709482d57823eadfb37) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.75.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump anstream from 0.6.21 to 1.0.0 (#14329) Bumps [anstream](https://github.com/rust-cli/anstyle) from 0.6.21 to 1.0.0. - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0) --- updated-dependencies: - dependency-name: anstream dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * Update flake.lock (#14375) Co-authored-by: github-actions[bot] * fix(ci): handle stale branches in bump-tempo workflow (#14377) * fix(config): surface cleanup failures as warnings instead of hard errors (#14379) * fix(config): surface cleanup failures as warnings instead of hard errors Cache cleanup is best-effort by design. Instead of silently swallowing errors or making them fatal, return warnings from Config and emit them via sh_warn! at the CLI layer. All cleanup steps now run even if some fail. Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * fix: address CI - collapse nested ifs, remove disallowed eprintln Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * chore(tempo): bump rev + use extension traits (#14378) * feat(tempo): bump tempo rev and use TempoProviderExt::is_hardfork_active Bumps tempo crates from 6f4f5cc to bb08bb9 (latest main) and replaces the local is_hardfork_active helper with the new trait method on TempoProviderExt, which queries the tempo_forkSchedule RPC directly. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: propagate is_hardfork_active error instead of unwrap_or Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * refactor: use TempoAddressExt in tempo_labels and fee token parsing Replace is_tip20_prefix() with addr.is_tip20() in the TempoLabels inspector and use Address::TIP20_PREFIX instead of hardcoded hex bytes in token_id_to_address. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: enable reth-codec feature on tempo-primitives and fix rustfmt The rev bump split tempo crates into two copies (6f4f5cc for mpp-rs, bb08bb9 for foundry). The bb08bb9 copy lost the reth-codec feature that was previously unified from other workspace members. Explicitly enable it to restore Compact derives on TempoTxEnvelope/TempoHeader. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * chore: remove cargo-update workflow (#14382) Dependabot is now configured for weekly cargo updates, making this workflow redundant. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): return error for eth_getLogs with unknown blockHash instead of empty (#14371) Co-authored-by: Claude Opus 4.7 (1M context) * refactor(cast): cleanup `cast send` (#14385) * refactor(cast): cleanup `cast send` * docs: clarify `TempoNetwork` requirement * style: clippy * fix: review feedback * fix(anvil): refetch full fork blocks with missing tx cache (#14384) * docs: correct Tempo TIP-1022 documentation URL (#14387) * feat(verify): clearer error when `ETHERSCAN_API_KEY` selects etherscan (#14383) * feat: make asm-keccak a default feature in all binaries (#14389) * chore(wallets): move `wallets` crate to `foundry-core` (#14348) * fix(cheatcodes): read broadcasts with the active network (#14388) --------- Signed-off-by: dependabot[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: Louis Peter Sitoe Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: o-az --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: cui Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az --- .circleci/ci_v1.yml | 31 ------- .circleci/config.yml | 31 ------- crates/doc/src/writer/as_doc.rs | 39 +++++--- crates/lint/src/sol/gas/keccak.rs | 98 +++----------------- crates/script-sequence/src/sequence.rs | 85 ++++++----------- crates/test-utils/src/script.rs | 15 +-- crates/verify/src/etherscan/standard_json.rs | 11 ++- 7 files changed, 86 insertions(+), 224 deletions(-) delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/config.yml diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d5d401c51893c..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 1502322ac87f4..e3358b2f437dd 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,12 +1,11 @@ use crate::{ - CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, - GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, - document::{DocumentContent, read_context}, - helpers::function_signature, + document::{read_context, DocumentContent}, parser::ParseSource, - solang_ext::SafeUnwrap, writer::BufWriter, + CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput, + CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID, }; +use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; use solang_parser::pt::{Base, FunctionDefinition, VariableAttribute}; use std::path::Path; @@ -63,7 +62,7 @@ impl AsDoc for CommentsRef<'_> { // Write dev tags let devs = self.include_tag(CommentTag::Dev); for d in devs.iter() { - writer.write_dev_content(&d.value)?; + writer.write_italic(&d.value)?; writer.writeln()?; } @@ -88,7 +87,7 @@ impl AsDoc for CommentsRef<'_> { impl AsDoc for Base { fn as_doc(&self) -> AsDocResult { - Ok(self.name.identifiers.iter().map(|ident| ident.name.clone()).join(".")) + Ok(self.name.identifiers.iter().map(|ident| ident.name.to_owned()).join(".")) } } @@ -107,7 +106,16 @@ impl AsDoc for Document { for item in items { let func = item.as_function().unwrap(); - let heading = function_signature(func).replace(',', ", "); + let mut heading = item.source.ident(); + if !func.params.is_empty() { + heading.push_str(&format!( + "({})", + func.params + .iter() + .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default()) + .join(", ") + )); + } writer.write_heading(&heading)?; writer.write_section(&item.comments, &item.code)?; } @@ -253,7 +261,15 @@ impl AsDoc for Document { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code)?; + + let filtered_comments: Comments = (*comments) + .iter() + .filter(|c| c.tag != CommentTag::Custom("variant".to_string())) + .cloned() + .collect::>() + .into(); + + writer.write_section(&filtered_comments, code)?; writer.try_write_variant_table(item, comments) })?; } @@ -319,10 +335,9 @@ impl Document { comments: &Comments, code: &str, ) -> Result<(), std::fmt::Error> { - let func_sign = function_signature(func); - let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.clone()); + let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); let comments = - comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc)); + comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); // Write function name writer.write_heading(&func_name)?; diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index b6a18f292a91b..7316f4c4239b7 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,103 +1,27 @@ use super::AsmKeccak256; use crate::{ - linter::{LateLintPass, LintContext}, + declare_forge_lint, + linter::EarlyLintPass, sol::{Severity, SolLint}, }; -use solar::{ - ast::{self as ast, Span}, - interface::kw, - sema::hir::{self}, -}; +use solar_ast::{Expr, ExprKind}; +use solar_interface::kw; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "use of inefficient hashing mechanism; consider using inline assembly" + "hash using inline assembly to save gas" ); -impl<'hir> LateLintPass<'hir> for AsmKeccak256 { - fn check_stmt( - &mut self, - ctx: &LintContext, - hir: &'hir hir::Hir<'hir>, - stmt: &'hir hir::Stmt<'hir>, - ) { - let check_expr_and_emit_lint = - |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { - if let Some(hash_arg) = extract_keccak256_arg(expr) { - self.emit_lint( - ctx, - hir, - stmt.span, - expr, - hash_arg, - AsmContext { _assign: assign, _is_return: is_return }, - ); - } - }; - - match stmt.kind { - hir::StmtKind::DeclSingle(var_id) => { - let var = hir.variable(var_id); - if let Some(init) = var.initializer { - // Constants should be optimized by the compiler, so no gas savings apply. - if !matches!(var.mutability, Some(hir::VarMut::Constant)) { - check_expr_and_emit_lint(init, var.name, false); - } +impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { + fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(expr, _) = &expr.kind { + if let ExprKind::Ident(ident) = &expr.kind { + if ident.name == kw::Keccak256 { + ctx.emit(&ASM_KECCAK256, expr.span); } } - // Expressions that don't (directly) assign to a variable - hir::StmtKind::Expr(expr) - | hir::StmtKind::Emit(expr) - | hir::StmtKind::Revert(expr) - | hir::StmtKind::DeclMulti(_, expr) - | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), - hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), - _ => (), } } } - -impl AsmKeccak256 { - /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. - fn emit_lint( - &self, - ctx: &LintContext, - _hir: &hir::Hir<'_>, - _stmt_span: Span, - call: &hir::Expr<'_>, - _hash: &hir::Expr<'_>, - _asm_ctx: AsmContext, - ) { - ctx.emit(&ASM_KECCAK256, call.span); - } -} - -/// If the expression is a call to `keccak256` with one argument, returns that argument. -fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { - let hir::ExprKind::Call( - callee, - hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, - .., - ) = &expr.kind - else { - return None; - }; - - let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { - matches!(builtin.name(), kw::Keccak256) - } else { - return None; - }; - - (is_keccak && args.len() == 1).then(|| &args[0]) -} - -// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- - -#[derive(Debug, Clone, Copy)] -struct AsmContext { - _assign: Option, - _is_return: bool, -} diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index fb0c01eb4fd1e..37c59fc47f17c 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -1,6 +1,7 @@ use crate::transaction::TransactionWithMetadata; -use alloy_network::{Network, ReceiptResponse}; +use alloy_network::ReceiptResponse; use alloy_primitives::{TxHash, hex, map::HashMap}; +use alloy_rpc_types_eth::TransactionReceipt; use eyre::{ContextCompat, Result, WrapErr}; use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, fs, shell}; use foundry_compilers::ArtifactId; @@ -11,7 +12,7 @@ use std::{ path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; - +# pub const DRY_RUN_DIR: &str = "dry-run"; #[derive(Clone, Serialize, Deserialize)] @@ -19,29 +20,13 @@ pub struct NestedValue { pub internal_type: String, pub value: String, } - -/// Sensitive values from the transactions in a script sequence -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveTransactionMetadata { - pub rpc: String, -} - -/// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveScriptSequence { - pub transactions: VecDeque, -} - +# /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound( - serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", - deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" -))] -pub struct ScriptSequence { - pub transactions: VecDeque>, - pub receipts: Vec, +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct ScriptSequence { + pub transactions: VecDeque, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] @@ -54,24 +39,20 @@ pub struct ScriptSequence { pub commit: Option, } -impl Default for ScriptSequence { - fn default() -> Self { - Self { - transactions: Default::default(), - receipts: Default::default(), - libraries: Default::default(), - pending: Default::default(), - paths: Default::default(), - returns: Default::default(), - timestamp: Default::default(), - chain: Default::default(), - commit: Default::default(), - } - } +/// Sensitive values from the transactions in a script sequence +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveTransactionMetadata { + pub rpc: String, +} + +/// Sensitive info from the script sequence which is saved into the cache folder +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveScriptSequence { + pub transactions: VecDeque, } -impl From<&ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &ScriptSequence) -> Self { +impl From<&ScriptSequence> for SensitiveScriptSequence { + fn from(sequence: &ScriptSequence) -> Self { Self { transactions: sequence .transactions @@ -82,7 +63,7 @@ impl From<&ScriptSequence> for SensitiveScriptSequence { } } -impl ScriptSequence { +impl ScriptSequence { /// Loads The sequence for the corresponding json file pub fn load( config: &Config, @@ -90,10 +71,7 @@ impl ScriptSequence { target: &ArtifactId, chain_id: u64, dry_run: bool, - ) -> Result - where - N::TxEnvelope: for<'d> Deserialize<'d>, - { + ) -> Result { let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = fs::read_json_file(&path) @@ -114,10 +92,7 @@ impl ScriptSequence { /// Saves the transactions as file if it's a standalone deployment. /// `save_ts` should be set to true for checkpoint updates, which might happen many times and /// could result in us saving many identical files. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> - where - N::TxEnvelope: Serialize, - { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { self.sort_receipts(); if self.transactions.is_empty() { @@ -166,10 +141,10 @@ impl ScriptSequence { Ok(()) } - pub fn add_receipt(&mut self, receipt: N::ReceiptResponse) { + pub fn add_receipt(&mut self, receipt: TransactionReceipt) { self.receipts.push(receipt); } - + # /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { self.receipts.sort_by_key(|r| (r.block_number(), r.transaction_index())); @@ -185,7 +160,7 @@ impl ScriptSequence { pub fn remove_pending(&mut self, tx_hash: TxHash) { self.pending.retain(|element| element != &tx_hash); } - + # /// Gets paths in the formats /// `./broadcast/[contract_filename]/[chain_id]/[sig]-latest.json` and /// `./cache/[contract_filename]/[chain_id]/[sig]-latest.json`. @@ -196,8 +171,8 @@ impl ScriptSequence { chain_id: u64, dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = config.broadcast.clone(); - let mut cache = config.cache_path.clone(); + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); let mut common = PathBuf::new(); let target_fname = target.source.file_name().wrap_err("No filename.")?; @@ -229,7 +204,7 @@ impl ScriptSequence { } /// Returns the list of the transactions without the metadata. - pub fn transactions(&self) -> impl Iterator> { + pub fn transactions(&self) -> impl Iterator { self.transactions.iter().map(|tx| tx.tx()) } @@ -240,7 +215,7 @@ impl ScriptSequence { .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } - +# /// Converts the `sig` argument into the corresponding file path. /// /// This accepts either the signature of the function or the raw calldata. diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index c1a6cb53bdbff..07413fef5495c 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -247,11 +247,12 @@ impl ScriptTester { trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - assert!( - !(!stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str())), - "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", - expected.as_str() - ); + if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { + panic!( + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); + } self } @@ -299,7 +300,7 @@ pub enum ScriptOutcome { } impl ScriptOutcome { - pub const fn as_str(&self) -> &'static str { + pub fn as_str(&self) -> &'static str { match self { Self::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", Self::OkSimulation => "SIMULATION COMPLETE. To broadcast these", @@ -323,7 +324,7 @@ impl ScriptOutcome { } } - pub const fn is_err(&self) -> bool { + pub fn is_err(&self) -> bool { match self { Self::OkNoEndpoint | Self::OkSimulation diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 848d5808fd77b..bb0549b9ad19e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -2,6 +2,8 @@ use super::{EtherscanSourceProvider, VerifyArgs}; use crate::{provider::VerificationContext, verify::ContractLanguage}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; +use foundry_compilers::artifacts::{Source, vyper::VyperInput}; +use std::path::Path; #[derive(Debug)] pub struct EtherscanStandardJsonSource; @@ -24,7 +26,14 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { serde_json::to_string(&input).wrap_err("Failed to parse standard json input")? } ContractLanguage::Vyper => { - let input = context.get_vyper_standard_json_input()?; + let path = Path::new(&context.target_path); + let sources = Source::read_all_from(path, &["vy", "vyi"])?; + let input = VyperInput::new( + sources, + context.compiler_settings.vyper.clone(), + &context.compiler_version, + ); + serde_json::to_string(&input).wrap_err("Failed to parse vyper json input")? } }; From 4efa9689307401de63d634a3032ee435c43d282c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:20:00 +0700 Subject: [PATCH 313/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cheatcodes/src/fs.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 5e762ec62a035..3a63c5631c297 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1129,6 +1129,14 @@ mod tests { assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); assert!(latest.success); + let temp_root = env::temp_dir().canonicalize().unwrap(); + let root_parent = root.parent().and_then(|p| p.canonicalize().ok()).unwrap(); + let root_name_ok = root + .file_name() + .and_then(|n| n.to_str()) + .map(|n| n.starts_with("foundry-cheatcodes-")) + .unwrap_or(false); + assert!(root_parent == temp_root && root_name_ok, "refusing to delete unexpected path"); stdfs::remove_dir_all(root).unwrap(); } } From 48b8782dd113ffdacc7d7bfc4fd0606269b8db4a Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:20:27 +0700 Subject: [PATCH 314/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 36d8e609cb36f..b695c94b8ab6c 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -118,17 +118,28 @@ impl ScriptTester { fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); + let canonical_from_dir = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); + + // Canonicalize and confine entry path before any filesystem operation on it. + let canonical_file = match file.canonicalize() { + Ok(path) => path, + Err(_) => continue, + }; + if !canonical_file.starts_with(&canonical_from_dir) { + continue; + } + // Only operate on regular files to avoid following symlinks or directories - let metadata = fs::symlink_metadata(&file)?; + let metadata = fs::symlink_metadata(&canonical_file)?; let ftype = metadata.file_type(); if !ftype.is_file() { continue; } - let name = match file.file_name() { + let name = match canonical_file.file_name() { Some(name) => name, None => continue, }; @@ -138,15 +149,8 @@ impl ScriptTester { // Skip invalid (potentially dangerous) file names continue; } - // Verify canonicalized file is in from_dir to avoid symlink traversal - if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { - continue; - } - } else { - continue; - } - fs::copy(&file, to_dir.join(name))?; + + fs::copy(&canonical_file, to_dir.join(name))?; } Ok(()) } From 9f6402dea78dde8f396812aa2b5c008b1ca769d1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:21:10 +0700 Subject: [PATCH 315/391] Update crates/cast/src/cmd/miner.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cast/src/cmd/miner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cast/src/cmd/miner.rs b/crates/cast/src/cmd/miner.rs index 346efb9a503a0..3b901235258ae 100644 --- a/crates/cast/src/cmd/miner.rs +++ b/crates/cast/src/cmd/miner.rs @@ -23,8 +23,8 @@ where handles.push(std::thread::spawn(move || { #[repr(C)] - struct B256Aligned(B256, [usize; 0]); - +#[repr(C, align(8))] +struct B256Aligned(B256); let mut salt = B256Aligned(salt, []); // SAFETY: `B256` is aligned to `usize`. let salt_word = unsafe { From a35104a60dc4da43d5735e0102b9ae3ef49a4a72 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:21:46 +0700 Subject: [PATCH 316/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/config/src/lib.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index c0f8249f37dfd..b0594a23355b3 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1314,19 +1314,42 @@ impl Config { } // Remove last test run failures file. - if let Err(err) = fs::remove_file(&self.test_failures_file) - && err.kind() != io::ErrorKind::NotFound - { + let test_failures_path = if self.test_failures_file.is_absolute() { + self.test_failures_file.clone() + } else { + project.root().join(&self.test_failures_file) + }; + if test_failures_path.starts_with(project.root()) { + if let Err(err) = fs::remove_file(&test_failures_path) + && err.kind() != io::ErrorKind::NotFound + { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + test_failures_path.display() + )); + } + } else { warnings.push(format!( - "failed to remove test failures file {}: {err}", - self.test_failures_file.display() + "skipping removal of test failures file outside project root: {}", + test_failures_path.display() )); } // Remove fuzz and invariant cache directories. let mut remove_test_dir = |test_dir: &Option| { if let Some(test_dir) = test_dir { - let path = project.root().join(test_dir); + let path = if test_dir.is_absolute() { + test_dir.clone() + } else { + project.root().join(test_dir) + }; + if !path.starts_with(project.root()) { + warnings.push(format!( + "skipping removal of test cache directory outside project root: {}", + path.display() + )); + return; + } if let Err(err) = fs::remove_dir_all(&path) && err.kind() != io::ErrorKind::NotFound { From fc3a2aade0fc5ba551daa137b7984e0510a7ab89 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:37:22 +0000 Subject: [PATCH 317/391] Remove duplicate logic in TxSigner::address() implementations (#447) Co-authored-by: Aganis From 4916e016fbaae0bd4a0f4347379bc7da10661039 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:40:27 +0000 Subject: [PATCH 318/391] fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#448) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: googleworkspace-bot From cc8a7ec2661048dc231f273d63ffb0f279ea3e69 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:31:07 +0700 Subject: [PATCH 319/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From 16480aa5b98fb5435b0f2ce900705cea7028e481 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:31:32 +0700 Subject: [PATCH 320/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From 3820bfc1f104645770ba92c8c1e649f29b650a5f Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:32:21 +0700 Subject: [PATCH 321/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..fc395c963375a 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; From 0ba669014b63104795ea18b0ea09a5a41dd7df84 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:32:48 +0700 Subject: [PATCH 322/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index fc395c963375a..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} From 23d9618adad1f01c2aad71173c1668814e3640c2 Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Wed, 22 Apr 2026 06:14:13 +0700 Subject: [PATCH 323/391] Update Docker.yml --- .github/workflows/Docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 5a2330e7d5d62..7b85ca2ae00c8 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,7 +4,7 @@ on: push: tags: ["*"] branches: - - "main" + - "master" pull_request: branches: ["**"] From 218c62a1afa02aaa69eb3a2fa3601525c9944a44 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:38:36 +0700 Subject: [PATCH 324/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From f8b0f1684a91f9eeace13c20fb4f8ac81c452025 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:38:57 +0700 Subject: [PATCH 325/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..fc395c963375a 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; From aa64de4071b5f39da194da17a80db2fbb8837bb4 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:39:15 +0700 Subject: [PATCH 326/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index fc395c963375a..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} From bcf6d774b149610d761c3b048cf9ae68d0695a29 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:27:14 +0000 Subject: [PATCH 327/391] feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp --- crates/anvil/src/cmd.rs | 125 +++++++++++++- crates/anvil/src/config.rs | 68 ++++++-- crates/anvil/src/eth/api.rs | 12 +- crates/anvil/src/eth/backend/db.rs | 4 +- crates/anvil/src/eth/backend/fork.rs | 62 ++++--- crates/anvil/src/eth/backend/mem/fork_db.rs | 4 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 2 +- crates/anvil/src/eth/backend/mem/mod.rs | 12 +- crates/anvil/tests/it/fork.rs | 58 +++++++ crates/common/src/provider/mod.rs | 128 ++++++++++++++- crates/config/src/endpoints.rs | 155 ++++++++++++++++-- crates/config/src/lib.rs | 4 + crates/evm/core/src/fork/database.rs | 2 +- 13 files changed, 565 insertions(+), 71 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 68bd83a1e6e89..fb75529b82908 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -227,6 +227,18 @@ impl NodeArgs { let compute_units_per_second = if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second }; + // Validate that secondary fork URLs don't have conflicting block number suffixes + if self.evm.fork_url.len() > 1 { + for fork in &self.evm.fork_url[1..] { + if fork.block.is_some() { + eyre::bail!( + "Block number suffixes (@block) on secondary --fork-url values are not supported. \ + Use --fork-block-number to set the fork block for all endpoints." + ); + } + } + } + let hardfork = match &self.hardfork { Some(hf) => { if self.evm.networks.is_optimism() { @@ -260,7 +272,7 @@ impl NodeArgs { _ => self .evm .fork_url - .as_ref() + .first() .and_then(|f| f.block) .map(|num| ForkChoice::Block(num as i128)), }) @@ -270,7 +282,7 @@ impl NodeArgs { .fork_request_retries(self.evm.fork_request_retries) .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) - .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) + .with_fork_urls(self.evm.fork_url.into_iter().map(|f| f.url).collect()) .with_base_fee(self.evm.block_base_fee_per_gas) .disable_min_priority_fee(self.evm.disable_min_priority_fee) .with_no_storage_caching(self.evm.no_storage_caching) @@ -426,6 +438,10 @@ pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. + /// + /// Multiple `--fork-url` flags can be provided to distribute requests across endpoints + /// using round-robin load balancing. On failure, the retry layer rotates to the next + /// endpoint. #[arg( long, short, @@ -433,7 +449,7 @@ pub struct AnvilEvmArgs { value_name = "URL", help_heading = "Fork config" )] - pub fork_url: Option, + pub fork_url: Vec, /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// @@ -630,13 +646,45 @@ pub struct AnvilEvmArgs { /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section /// of the project configuration file. /// Does nothing if the fork-url is not a configured alias. +/// +/// When an alias maps to an `RpcEndpoint` with multiple `endpoints`, all URLs are expanded +/// into additional `--fork-url` entries for multi-endpoint load balancing. impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { - if let Some(fork_url) = &self.fork_url - && let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) - && let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) - { - self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); + if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { + let mut resolved_urls = Vec::new(); + for fork_url in &self.fork_url { + let mut endpoints = config.rpc_endpoints.clone().resolved(); + if let Some(endpoint) = endpoints.remove(&fork_url.url) { + // Alias matched — expand all URLs from the endpoint config + match endpoint.all_urls() { + Ok(urls) => { + for (i, url) in urls.into_iter().enumerate() { + resolved_urls.push(ForkUrl { + url, + // Only the first URL inherits the block suffix + block: if i == 0 { fork_url.block } else { None }, + }); + } + } + Err(e) => { + warn!(target: "node", alias=%fork_url.url, %e, "could not resolve all endpoints, using primary endpoint only"); + if let Ok(url) = endpoint.url() { + resolved_urls.push(ForkUrl { url, block: fork_url.block }); + } else { + resolved_urls.push(fork_url.clone()); + } + } + } + } else if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { + // Try mesc or other resolution + resolved_urls.push(ForkUrl { url: url.to_string(), block: fork_url.block }); + } else { + // Not an alias — keep as-is + resolved_urls.push(fork_url.clone()); + } + } + self.fork_url = resolved_urls; } } } @@ -965,4 +1013,65 @@ mod tests { ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); } + + #[test] + fn can_parse_multiple_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545", + "--fork-url", + "http://localhost:8546", + "--fork-url", + "http://localhost:8547", + ]); + assert_eq!(args.evm.fork_url.len(), 3); + assert_eq!(args.evm.fork_url[0].url, "http://localhost:8545"); + assert_eq!(args.evm.fork_url[1].url, "http://localhost:8546"); + assert_eq!(args.evm.fork_url[2].url, "http://localhost:8547"); + + // Block suffix on first URL should work + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546", + ]); + assert_eq!(args.evm.fork_url[0].block, Some(1000000)); + assert_eq!(args.evm.fork_url[1].block, None); + } + + #[test] + fn rejects_block_suffix_on_secondary_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546@2000000", + ]); + let result = args.into_node_config(); + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains("Block number suffixes"), + "should reject block suffix on secondary fork URL" + ); + } + + #[test] + fn fork_dependent_args_require_fork_url() { + // All these args have `requires = "fork_url"` — they should fail without --fork-url + let cases = [ + vec!["anvil", "--fork-header", "X-Api-Key: test"], + vec!["anvil", "--timeout", "5000"], + vec!["anvil", "--retries", "3"], + vec!["anvil", "--fork-block-number", "100"], + vec!["anvil", "--fork-retry-backoff", "500"], + ]; + for args in &cases { + let result = NodeArgs::try_parse_from(args); + assert!(result.is_err(), "expected error when using {:?} without --fork-url", args[1]); + } + } } diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index c9b63f08cb2f5..23cd6e61bc076 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -134,11 +134,13 @@ pub struct NodeConfig { pub port: u16, /// maximum number of transactions in a block pub max_transactions: usize, - /// url of the rpc server that should be used for any rpc calls - pub eth_rpc_url: Option, + /// Fork URLs for RPC calls. The first entry is the primary endpoint. + /// When multiple URLs are provided, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// pins the block number or transaction hash for the state fork pub fork_choice: Option, - /// headers to use with `eth_rpc_url` + /// headers to use with fork RPC endpoints pub fork_headers: Vec, /// specifies chain id for cache to skip fetching from remote in offline-start mode pub fork_chain_id: Option, @@ -268,12 +270,19 @@ Block number: {} Block hash: {:?} Chain ID: {} "#, - fork.eth_rpc_url(), + fork.eth_rpc_url().as_deref().unwrap_or("none"), fork.block_number(), fork.block_hash(), fork.chain_id() ); + if self.fork_urls.len() > 1 { + let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len()); + for (i, url) in self.fork_urls.iter().enumerate() { + let _ = writeln!(s, " ({i}) {url}"); + } + } + if let Some(tx_hash) = fork.transaction_hash() { let _ = writeln!(s, "Transaction hash: {tx_hash}"); } @@ -393,7 +402,7 @@ Genesis Number json!({ "available_accounts": available_accounts, "private_keys": private_keys, - "endpoint": fork.eth_rpc_url(), + "endpoint": fork.eth_rpc_url().unwrap_or_default(), "block_number": fork.block_number(), "block_hash": fork.block_hash(), "chain_id": fork.chain_id(), @@ -466,7 +475,7 @@ impl Default for NodeConfig { mixed_mining: false, port: NODE_PORT, max_transactions: 1_000, - eth_rpc_url: None, + fork_urls: vec![], fork_choice: None, account_generator: None, base_fee: None, @@ -855,10 +864,19 @@ impl NodeConfig { self } - /// Sets the `eth_rpc_url` to use when forking + /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience). #[must_use] pub fn with_eth_rpc_url>(mut self, eth_rpc_url: Option) -> Self { - self.eth_rpc_url = eth_rpc_url.map(Into::into); + if let Some(url) = eth_rpc_url { + self.fork_urls = vec![url.into()]; + } + self + } + + /// Sets the fork URLs for load-balanced multi-endpoint forking. + #[must_use] + pub fn with_fork_urls(mut self, fork_urls: Vec) -> Self { + self.fork_urls = fork_urls; self } @@ -891,7 +909,7 @@ impl NodeConfig { self } - /// Sets the `fork_headers` to use with `eth_rpc_url` + /// Sets the `fork_headers` to use with fork RPC endpoints #[must_use] pub fn with_fork_headers(mut self, headers: Vec) -> Self { self.fork_headers = headers; @@ -1017,7 +1035,7 @@ impl NodeConfig { /// /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { - if self.no_storage_caching || self.eth_rpc_url.is_none() { + if self.no_storage_caching || self.fork_urls.is_empty() { return None; } let chain_id = self.get_chain_id(); @@ -1145,7 +1163,7 @@ impl NodeConfig { ); let (db, fork): (Arc>>, Option) = - if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { + if let Some(eth_rpc_url) = self.fork_urls.first().cloned() { self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await? } else { (Arc::new(TokioRwLock::new(Box::::default())), None) @@ -1208,7 +1226,7 @@ impl NodeConfig { // Writes the default create2 deployer to the backend, // if the option is not disabled and we are not forking. - if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { + if !self.disable_default_create2_deployer && self.fork_urls.is_empty() { backend .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await @@ -1248,6 +1266,10 @@ impl NodeConfig { fees: &FeeManager, ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); + + // Always bootstrap with the primary URL only to avoid race conditions + // where discovery calls (get_chain_id, find_latest_fork_block, get_block) + // hit different endpoints that may be at different chain tips. let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) @@ -1409,6 +1431,25 @@ latest block number: {latest_block}" BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) }; + // After bootstrap, rebuild the provider with round-robin if multiple URLs are + // configured. This ensures bootstrap used only the primary endpoint for consistency, + // while ongoing requests are distributed across all endpoints. + let provider = if self.fork_urls.len() > 1 { + debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider"); + Arc::new( + ProviderBuilder::new(ð_rpc_url) + .timeout(self.fork_request_timeout) + .initial_backoff(self.fork_retry_backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .max_retry(self.fork_request_retries) + .headers(self.fork_headers.clone()) + .build_fallback(self.fork_urls.clone()) + .wrap_err("failed to establish round-robin provider to fork urls")?, + ) + } else { + provider + }; + // This will spawn the background thread that will use the provider to fetch // blockchain data from the other client let backend = SharedBackend::spawn_backend( @@ -1419,7 +1460,7 @@ latest block number: {latest_block}" .await; let config = ClientForkConfig { - eth_rpc_url, + fork_urls: self.fork_urls.clone(), block_number: fork_block_number, block_hash, transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()), @@ -1432,6 +1473,7 @@ latest block number: {latest_block}" retries: self.fork_request_retries, backoff: self.fork_retry_backoff, compute_units_per_second: self.compute_units_per_second, + headers: self.fork_headers.clone(), total_difficulty: block.header.total_difficulty.unwrap_or_default(), blob_gas_used: block.header.blob_gas_used().map(|g| g as u128), blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price, diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index f48a2d96cc12c..0a0fd97d06556 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -416,7 +416,7 @@ impl EthApi { let config = fork.config.read(); NodeForkConfig { - fork_url: Some(config.eth_rpc_url.clone()), + fork_url: config.eth_rpc_url().map(|s| s.to_string()), fork_block_number: Some(config.block_number), fork_retry_backoff: Some(config.backoff.as_millis()), } @@ -527,7 +527,7 @@ impl EthApi { /// Sets the backend rpc url /// /// Handler for ETH RPC call: `anvil_setRpcUrl` - pub fn anvil_set_rpc_url(&self, url: String) -> Result<()> { + pub async fn anvil_set_rpc_url(&self, url: String) -> Result<()> { node_info!("anvil_setRpcUrl"); if let Some(fork) = self.backend.get_fork() { let mut config = fork.config.write(); @@ -543,9 +543,11 @@ impl EthApi { )?, // .interval(interval), ); config.provider = new_provider; - trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); - config.eth_rpc_url = url; + trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url().unwrap_or("none"), url); + config.fork_urls = vec![url.clone()]; } + // Keep node_config in sync so anvil_reset(None) uses the updated URL + self.backend.node_config.write().await.fork_urls = vec![url]; Ok(()) } @@ -1791,7 +1793,7 @@ impl EthApi { EthRequest::EvmMineDetailed(mine) => { self.evm_mine_detailed(mine.and_then(|p| p.params)).await.to_rpc_result() } - EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).to_rpc_result(), + EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).await.to_rpc_result(), EthRequest::EthSendUnsignedTransaction(tx) => { self.eth_send_unsigned_transaction(*tx).await.to_rpc_result() } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index de9ad434252f3..d1c842d12d108 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -84,7 +84,7 @@ where /// Helper trait to reset the DB if it's forked pub trait MaybeForkedDatabase { - fn maybe_reset(&mut self, _url: Option, block_number: BlockId) -> Result<(), String>; + fn maybe_reset(&mut self, _urls: Vec, block_number: BlockId) -> Result<(), String>; fn maybe_flush_cache(&self) -> Result<(), String>; @@ -375,7 +375,7 @@ impl + Debug> MaybeFullDatabase for CacheD } impl> MaybeForkedDatabase for CacheDB { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index b1bff4e66cd21..bc87fe2fc2052 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -97,8 +97,8 @@ impl ClientFork { self.config.read().block_hash } - pub fn eth_rpc_url(&self) -> String { - self.config.read().eth_rpc_url.clone() + pub fn eth_rpc_url(&self) -> Option { + self.config.read().eth_rpc_url().map(|s| s.to_string()) } pub fn chain_id(&self) -> u64 { @@ -269,7 +269,7 @@ impl ClientFork { /// Reset the fork to a fresh forked state, and optionally update the fork config pub async fn reset( &self, - url: Option, + urls: Vec, block_number: impl Into, ) -> Result<(), BlockchainError> { let block_number = block_number.into(); @@ -277,12 +277,12 @@ impl ClientFork { self.database .write() .await - .maybe_reset(url.clone(), block_number) + .maybe_reset(urls.clone(), block_number) .map_err(BlockchainError::Internal)?; } - if let Some(url) = url { - self.config.write().update_url(url)?; + if !urls.is_empty() { + self.config.write().update_urls(urls)?; let override_chain_id = self.config.read().override_chain_id; let chain_id = if let Some(chain_id) = override_chain_id { chain_id @@ -629,7 +629,10 @@ impl ClientFork { /// Contains all fork metadata #[derive(Clone, Debug)] pub struct ClientForkConfig { - pub eth_rpc_url: String, + /// All fork URLs. The first entry is the primary endpoint. + /// When multiple URLs are present, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// The block number of the forked block pub block_number: u64, /// The hash of the forked block @@ -655,6 +658,8 @@ pub struct ClientForkConfig { pub backoff: Duration, /// available CUPS pub compute_units_per_second: u64, + /// Headers to include with RPC requests + pub headers: Vec, /// total difficulty of the chain until this block pub total_difficulty: U256, /// Transactions to force include in the forked chain @@ -662,27 +667,40 @@ pub struct ClientForkConfig { } impl ClientForkConfig { - /// Updates the provider URL + /// Returns the primary RPC URL (first entry in `fork_urls`). + pub fn eth_rpc_url(&self) -> Option<&str> { + self.fork_urls.first().map(|s| s.as_str()) + } + + /// Updates the provider URLs /// /// # Errors /// /// This will fail if no new provider could be established (erroneous URL) - fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { - // let interval = self.provider.get_interval(); - self.provider = Arc::new( - ProviderBuilder::::new(url.as_str()) - .timeout(self.timeout) - // .timeout_retry(self.retries) - .max_retry(self.retries) - .initial_backoff(self.backoff.as_millis() as u64) - .compute_units_per_second(self.compute_units_per_second) - .build() - .map_err(|e| BlockchainError::InvalidUrl(format!("{url}: {e}")))?, /* .interval(interval), */ - ); - trace!(target: "fork", "Updated rpc url {}", url); - self.eth_rpc_url = url; + fn update_urls(&mut self, urls: Vec) -> Result<(), BlockchainError> { + let primary = urls.first().ok_or_else(|| { + BlockchainError::InvalidUrl("at least one fork URL required".to_string()) + })?; + + let builder = ProviderBuilder::::new(primary.as_str()) + .timeout(self.timeout) + .max_retry(self.retries) + .initial_backoff(self.backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .headers(self.headers.clone()); + + self.provider = Arc::new(if urls.len() > 1 { + builder + .build_fallback(urls.clone()) + .map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + } else { + builder.build().map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + }); + trace!(target: "fork", "Updated fork urls: {:?}", urls); + self.fork_urls = urls; Ok(()) } + /// Updates the block forked off `(block number, block hash, timestamp)` pub fn update_block( &mut self, diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 308cdd2e4334c..497e8ac8e4428 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -156,8 +156,8 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { } impl MaybeForkedDatabase for ForkedDatabase { - fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { - self.reset(url, block_number) + fn maybe_reset(&mut self, urls: Vec, block_number: BlockId) -> Result<(), String> { + self.reset(urls, block_number) } fn maybe_flush_cache(&self) -> Result<(), String> { diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 766805ee7ae9d..1d06ad03cae2f 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -128,7 +128,7 @@ impl MaybeFullDatabase for MemDb { } impl MaybeForkedDatabase for MemDb { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index ed928ef25213d..b538086fe6f10 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -246,7 +246,7 @@ pub struct Backend { prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, - node_config: Arc>, + pub(crate) node_config: Arc>, /// Slots in an epoch slots_in_an_epoch: u64, /// Precompiles to inject to the EVM. @@ -2079,6 +2079,7 @@ impl Backend { // we want to force the correct base fee for the next block during // `setup_fork_db_config` node_config.base_fee.take(); + node_config.fork_urls = vec![eth_rpc_url.clone()]; node_config.setup_fork_db_config(eth_rpc_url, &mut evm_env, &self.fees).await? }; @@ -2101,7 +2102,9 @@ impl Backend { let block_number = forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); // reset the fork entirely and reapply the genesis config - fork.reset(forking.json_rpc_url.clone(), block_number).await?; + let reset_urls = + forking.json_rpc_url.as_ref().map(|url| vec![url.clone()]).unwrap_or_default(); + fork.reset(reset_urls, block_number).await?; let fork_block_number = fork.block_number(); let fork_block = fork .block_by_number(fork_block_number) @@ -2115,7 +2118,8 @@ impl Backend { // If rpc url is unspecified, then update the fork with the new block number and // existing rpc url, this updates the cache path { - let maybe_fork_url = { self.node_config.read().await.eth_rpc_url.clone() }; + let maybe_fork_url = + { self.node_config.read().await.fork_urls.first().cloned() }; if let Some(fork_url) = maybe_fork_url { self.reset_block_number(fork_url, fork_block_number).await?; } @@ -2229,6 +2233,8 @@ impl Backend { ) -> Result<(), BlockchainError> { let mut node_config = self.node_config.write().await; node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128)); + // Update fork_urls so setup_fork_db_config uses the correct URL set + node_config.fork_urls = vec![fork_url.clone()]; let mut evm_env = self.evm_env.read().clone(); let (forked_db, client_fork_config) = diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index e802d8a19be7b..37d3f6f4a1dc4 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -2011,3 +2011,61 @@ async fn test_config_with_osaka_hardfork_with_precompile_factory() { &expected_system_contracts, ); } + +// Regression tests: verify that `anvil_setRpcUrl` and `anvil_reset` keep +// `ClientForkConfig.fork_urls` in sync so that subsequent resets don't +// silently revert to stale URLs. + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_rpc_url_syncs_fork_config() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Verify initial fork URL + let fork = api.backend.get_fork().unwrap(); + assert_eq!(fork.config.read().fork_urls, vec![origin_url.clone()]); + + // Spawn a second origin to use as the new URL + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Set RPC URL via the API + api.anvil_set_rpc_url(new_url.clone()).await.unwrap(); + + // Verify ClientForkConfig is updated + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should be updated after anvil_setRpcUrl" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_reset_with_url_updates_fork_urls() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Spawn a second origin + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Reset fork with a new URL + api.anvil_reset(Some(Forking { json_rpc_url: Some(new_url.clone()), block_number: None })) + .await + .unwrap(); + + // Verify the fork config uses the new URL, not the old one + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should reflect the new URL after anvil_reset" + ); +} diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 3b7432cd67c55..1620342989a29 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -9,6 +9,7 @@ use crate::{ provider::{curl_transport::CurlTransport, runtime_transport::RuntimeTransportBuilder}, }; use alloy_chains::NamedChain; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_network::{Network, NetworkWallet}; use alloy_provider::{ Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, @@ -16,7 +17,9 @@ use alloy_provider::{ network::{AnyNetwork, EthereumWallet}, }; use alloy_rpc_client::ClientBuilder; -use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; +use alloy_transport::{ + TransportError, TransportFut, layers::RetryBackoffLayer, utils::guess_local_url, +}; use eyre::{Result, WrapErr}; use foundry_config::Config; use reqwest::Url; @@ -25,8 +28,14 @@ use std::{ net::SocketAddr, path::{Path, PathBuf}, str::FromStr, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + task::{Context, Poll}, time::Duration, }; +use tower::Service; use url::ParseError; /// The assumed block time for unknown chains. @@ -75,6 +84,56 @@ pub fn try_get_http_provider(builder: impl AsRef) -> Result ProviderBuilder::new(builder.as_ref()).build() } +/// A round-robin transport that distributes requests across multiple transports. +/// +/// Each request is sent to exactly one transport, rotating through the list. +/// Failover on error is handled by the retry layer above this service. +#[derive(Clone)] +pub struct RoundRobinService { + transports: Arc>, + next: Arc, +} + +impl RoundRobinService { + /// Creates a new round-robin service from a non-empty list of transports. + /// + /// # Panics + /// + /// Panics if `transports` is empty. + pub fn new(transports: Vec) -> Self { + assert!(!transports.is_empty(), "RoundRobinService requires at least one transport"); + Self { transports: Arc::new(transports), next: Arc::new(AtomicUsize::new(0)) } + } +} + +impl Service for RoundRobinService +where + S: Service< + RequestPacket, + Response = ResponsePacket, + Error = TransportError, + Future = TransportFut<'static>, + > + Clone + + Send + + Sync + + 'static, +{ + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + let transports = self.transports.clone(); + let idx = self.next.fetch_add(1, Ordering::Relaxed) % transports.len(); + let mut transport = transports[idx].clone(); + transport.call(req) + } +} + /// Helper type to construct a `RetryProvider` /// /// This builder is generic over the network type `N`, defaulting to `AnyNetwork`. @@ -368,6 +427,73 @@ impl ProviderBuilder { } impl ProviderBuilder { + /// Constructs a `RetryProvider` backed by multiple URLs using round-robin load balancing. + /// + /// Each request is sent to exactly one transport, rotating through the list via + /// [`RoundRobinService`]. There is no health scoring or endpoint deprioritization. + /// On failure, the `RetryBackoffLayer` retries the request, which naturally hits + /// the next transport in the rotation. + pub fn build_fallback(self, urls: Vec) -> Result> { + let Self { + chain, + max_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + accept_invalid_certs, + no_proxy, + curl_mode, + .. + } = self; + + eyre::ensure!(!urls.is_empty(), "at least one fork URL is required"); + eyre::ensure!(!curl_mode, "curl mode is not supported with multiple fork URLs"); + + // Build a RuntimeTransport for each URL, using the same URL normalization + // as ProviderBuilder::new() (handles localhost:port, raw socket addrs, IPC paths) + let mut parsed_urls = Vec::with_capacity(urls.len()); + let transports: Vec<_> = urls + .iter() + .map(|url_str| { + let builder = Self::new(url_str); + let url = builder.url?; + parsed_urls.push(url.clone()); + Ok(RuntimeTransportBuilder::new(url) + .with_timeout(timeout) + .with_headers(headers.clone()) + .with_jwt(jwt.clone()) + .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) + .build()) + }) + .collect::>>()?; + + let round_robin = RoundRobinService::new(transports); + + let retry_layer = + RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + // Use normalized/parsed URLs for local detection, consistent with build() + let is_local = parsed_urls.iter().all(|url| guess_local_url(url.as_str())); + let client = ClientBuilder::default().layer(retry_layer).transport(round_robin, is_local); + + if !is_local { + client.set_poll_interval( + chain + .average_blocktime_hint() + .map(|hint| hint.min(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME)) + .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) + .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), + ); + } + + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); + + Ok(provider) + } + /// Constructs the `RetryProvider` with a wallet. pub fn build_with_wallet + Clone>( self, diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 64ee7e05fc75f..60a15206bc51a 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -277,6 +277,11 @@ pub struct RpcEndpoint { /// endpoint url or env pub endpoint: RpcEndpointUrl, + /// Additional fallback endpoints for load-balanced multi-endpoint forking. + /// When set, requests are distributed across all endpoints (primary + extra) + /// with automatic failover. + pub extra_endpoints: Vec, + /// Token to be used as authentication pub auth: Option, @@ -293,6 +298,7 @@ impl RpcEndpoint { pub fn resolve(self) -> ResolvedRpcEndpoint { ResolvedRpcEndpoint { endpoint: self.endpoint.resolve(), + extra_endpoints: self.extra_endpoints.into_iter().map(|e| e.resolve()).collect(), auth: self.auth.map(|auth| auth.resolve()), config: self.config, } @@ -301,7 +307,7 @@ impl RpcEndpoint { impl fmt::Display for RpcEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { endpoint, auth, config } = self; + let Self { endpoint, auth, config, .. } = self; write!(f, "{endpoint}")?; write!(f, "{config}")?; if let Some(auth) = auth { @@ -316,16 +322,24 @@ impl Serialize for RpcEndpoint { where S: Serializer, { - if self.config.retries.is_none() - && self.config.retry_backoff.is_none() - && self.config.compute_units_per_second.is_none() - && self.auth.is_none() - { - // serialize as endpoint if there's no additional config + let has_config = self.config.retries.is_some() + || self.config.retry_backoff.is_some() + || self.config.compute_units_per_second.is_some() + || self.auth.is_some(); + + if !has_config && self.extra_endpoints.is_empty() { + // serialize as plain endpoint string if there's no additional config self.endpoint.serialize(serializer) } else { - let mut map = serializer.serialize_map(Some(5))?; - map.serialize_entry("endpoint", &self.endpoint)?; + let mut map = serializer.serialize_map(None)?; + if self.extra_endpoints.is_empty() { + map.serialize_entry("endpoint", &self.endpoint)?; + } else { + // Serialize all endpoints as an array under "endpoints" + let all: Vec<&RpcEndpointUrl> = + std::iter::once(&self.endpoint).chain(&self.extra_endpoints).collect(); + map.serialize_entry("endpoints", &all)?; + } map.serialize_entry("retries", &self.config.retries)?; map.serialize_entry("retry_backoff", &self.config.retry_backoff)?; map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?; @@ -348,10 +362,13 @@ impl<'de> Deserialize<'de> for RpcEndpoint { }); } + // Support both single "endpoint" and array "endpoints" for backwards compatibility #[derive(Deserialize)] struct RpcEndpointConfigInner { #[serde(alias = "url")] - endpoint: RpcEndpointUrl, + endpoint: Option, + /// Array of endpoint URLs for multi-endpoint load balancing + endpoints: Option>, retries: Option, retry_backoff: Option, compute_units_per_second: Option, @@ -360,14 +377,43 @@ impl<'de> Deserialize<'de> for RpcEndpoint { let RpcEndpointConfigInner { endpoint, + endpoints, retries, retry_backoff, compute_units_per_second, auth, } = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + let (primary, extra) = match (endpoint, endpoints) { + // Single endpoint: endpoint = "..." + (Some(ep), None) => (ep, vec![]), + // Array of endpoints: endpoints = ["...", "..."] + (None, Some(mut eps)) => { + if eps.is_empty() { + return Err(serde::de::Error::custom( + "endpoints array must contain at least one URL", + )); + } + let primary = eps.remove(0); + (primary, eps) + } + // Both provided — error + (Some(_), Some(_)) => { + return Err(serde::de::Error::custom( + "cannot specify both `endpoint` and `endpoints`", + )); + } + // Neither provided — error + (None, None) => { + return Err(serde::de::Error::custom( + "must specify either `endpoint` or `endpoints`", + )); + } + }; + Ok(Self { - endpoint, + endpoint: primary, + extra_endpoints: extra, auth, config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second }, }) @@ -384,6 +430,7 @@ impl Default for RpcEndpoint { fn default() -> Self { Self { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig::default(), auth: None, } @@ -394,21 +441,38 @@ impl Default for RpcEndpoint { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResolvedRpcEndpoint { pub endpoint: Result, + /// Additional resolved endpoints for multi-endpoint load balancing. + pub extra_endpoints: Vec>, pub auth: Option>, pub config: RpcEndpointConfig, } impl ResolvedRpcEndpoint { - /// Returns the url this type holds, see [`RpcEndpoint::resolve`] + /// Returns the primary url this type holds, see [`RpcEndpoint::resolve`] pub fn url(&self) -> Result { self.endpoint.clone() } + /// Returns all resolved URLs (primary + extra) for multi-endpoint configurations. + /// Returns an empty vec if no extra endpoints are configured. + pub fn all_urls(&self) -> Result, UnresolvedEnvVarError> { + let primary = self.endpoint.clone()?; + if self.extra_endpoints.is_empty() { + return Ok(vec![primary]); + } + let mut urls = vec![primary]; + for ep in &self.extra_endpoints { + urls.push(ep.clone()?); + } + Ok(urls) + } + // Returns true if all environment variables are resolved successfully pub fn is_unresolved(&self) -> bool { let endpoint_err = self.endpoint.is_err(); + let extra_err = self.extra_endpoints.iter().any(|e| e.is_err()); let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false); - endpoint_err || auth_err + endpoint_err || extra_err || auth_err } // Attempts to resolve unresolved environment variables into a new instance @@ -419,6 +483,11 @@ impl ResolvedRpcEndpoint { if let Err(err) = self.endpoint { self.endpoint = err.try_resolve() } + for ep in &mut self.extra_endpoints { + if let Err(err) = std::mem::replace(ep, Ok(String::new())) { + *ep = err.try_resolve(); + } + } if let Some(Err(err)) = self.auth { self.auth = Some(err.try_resolve()) } @@ -483,6 +552,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(5), retry_backoff: Some(250), @@ -498,6 +568,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: None, retry_backoff: None, @@ -507,4 +578,62 @@ mod tests { } ); } + + #[test] + fn serde_rpc_config_multi_endpoints() { + // Array of endpoints via "endpoints" key + let s = r#"{ + "endpoints": ["https://rpc1.example.com", "https://rpc2.example.com", "https://rpc3.example.com"], + "retries": 5, + "retry_backoff": 1000 + }"#; + let config: RpcEndpoint = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpoint { + endpoint: RpcEndpointUrl::Url("https://rpc1.example.com".to_string()), + extra_endpoints: vec![ + RpcEndpointUrl::Url("https://rpc2.example.com".to_string()), + RpcEndpointUrl::Url("https://rpc3.example.com".to_string()), + ], + config: RpcEndpointConfig { + retries: Some(5), + retry_backoff: Some(1000), + compute_units_per_second: None, + }, + auth: None, + } + ); + + // Resolved URLs + let resolved = config.resolve(); + let all_urls = resolved.all_urls().unwrap(); + assert_eq!( + all_urls, + vec![ + "https://rpc1.example.com".to_string(), + "https://rpc2.example.com".to_string(), + "https://rpc3.example.com".to_string(), + ] + ); + } + + #[test] + fn serde_rpc_config_rejects_both_endpoint_and_endpoints() { + let s = r#"{ + "endpoint": "https://rpc1.example.com", + "endpoints": ["https://rpc2.example.com"] + }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("cannot specify both")); + } + + #[test] + fn serde_rpc_config_rejects_empty_endpoints() { + let s = r#"{ "endpoints": [] }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("at least one URL")); + } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index c0f8249f37dfd..f148432c42bed 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -3681,6 +3681,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3706,6 +3707,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3753,6 +3755,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3779,6 +3782,7 @@ mod tests { endpoint: RpcEndpointUrl::Url( "https://eth-mainnet.alchemyapi.io/v2/123455".to_string() ), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 6f539d356e5a9..aefa0e2ee9741 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -71,7 +71,7 @@ impl ForkedDatabase { /// Reset the fork to a fresh forked state, and optionally update the fork config pub fn reset( &mut self, - _url: Option, + _urls: Vec, block_number: impl Into, ) -> Result<(), String> { self.backend.set_pinned_block(block_number).map_err(|err| err.to_string())?; From 0203943fd04808b5ee7ac58e7288c64cf0c34755 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:38:52 +0700 Subject: [PATCH 328/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From d41b319ba8f3dc5a66d39c488db79d2fd3f8a4a3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:39:21 +0700 Subject: [PATCH 329/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From bec42f81b6a03cb6cddc2547f5e27d4e67eab687 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:39:39 +0700 Subject: [PATCH 330/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..fc395c963375a 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; From 88c60a66dbce3327e4cd4fd793bee056c1096267 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:39:58 +0700 Subject: [PATCH 331/391] Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/lint/src/linter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index fc395c963375a..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} From c679ca726e9c519a8ee466a43885fb8e25ffb0b5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:34:13 +0000 Subject: [PATCH 332/391] Add docs, sidebar assets and Foundry deps (#459) * Add docs, sidebar assets and Foundry deps Add generated documentation assets (doc-script.js, doc-style.css, doc-filelist.js) under both doc/ and counter/doc/ to provide a collapsible sidebar/tree UI. Add counter/.gas-snapshot test gas output and update counter/.gitignore to ignore Soldeer dependencies. Update counter/foundry.toml to include a dependencies section and forge-std, and add remappings.txt plus soldeer.lock to pin the forge-std dependency. * Update counter/doc/doc-script.js Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- counter/.gas-snapshot | 2 + counter/.gitignore | 4 + counter/doc/doc-filelist.js | 1 + counter/doc/doc-script.js | 228 ++++++++++++++++++++ counter/doc/doc-style.css | 403 ++++++++++++++++++++++++++++++++++++ counter/foundry.toml | 5 +- counter/remappings.txt | 1 + counter/soldeer.lock | 6 + 8 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 counter/.gas-snapshot create mode 100644 counter/doc/doc-filelist.js create mode 100644 counter/doc/doc-script.js create mode 100644 counter/doc/doc-style.css create mode 100644 counter/remappings.txt create mode 100644 counter/soldeer.lock diff --git a/counter/.gas-snapshot b/counter/.gas-snapshot new file mode 100644 index 0000000000000..ef525c09384e6 --- /dev/null +++ b/counter/.gas-snapshot @@ -0,0 +1,2 @@ +CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 30177, ~: 32354) +CounterTest:test_Increment() (gas: 31851) \ No newline at end of file diff --git a/counter/.gitignore b/counter/.gitignore index 85198aaa55b84..052b88bb6516b 100644 --- a/counter/.gitignore +++ b/counter/.gitignore @@ -12,3 +12,7 @@ docs/ # Dotenv file .env + + +# Soldeer +/dependencies diff --git a/counter/doc/doc-filelist.js b/counter/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/counter/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/counter/doc/doc-script.js b/counter/doc/doc-script.js new file mode 100644 index 0000000000000..62eeda3efc308 --- /dev/null +++ b/counter/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +let sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/counter/doc/doc-style.css b/counter/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/counter/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/counter/foundry.toml b/counter/foundry.toml index 25b918f9c9a96..c27b8ed21ba0b 100644 --- a/counter/foundry.toml +++ b/counter/foundry.toml @@ -1,6 +1,9 @@ [profile.default] src = "src" out = "out" -libs = ["lib"] +libs = ["lib", "dependencies"] + +[dependencies] +forge-std = "1.15.0" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/remappings.txt b/counter/remappings.txt new file mode 100644 index 0000000000000..6c93bbb0c4b12 --- /dev/null +++ b/counter/remappings.txt @@ -0,0 +1 @@ +forge-std-1.15.0/=dependencies/forge-std-1.15.0/ diff --git a/counter/soldeer.lock b/counter/soldeer.lock new file mode 100644 index 0000000000000..af6c8601cd7ba --- /dev/null +++ b/counter/soldeer.lock @@ -0,0 +1,6 @@ +[[dependencies]] +name = "forge-std" +version = "1.15.0" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_15_0_27-02-2026_08:26:17_forge-std-1.15.zip" +checksum = "40d9b3b3d786eec4cd05fb9d818616015cbe7b8866643a9f0854495c938588c4" +integrity = "92accf4f7850eb9f5832f0ea77d633d36ebe993efc6d6c9f32369d31befc8a75" From 3c69d9eb222f13bbc96249f4a0aeff87d9a4b494 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:59:01 +0000 Subject: [PATCH 333/391] feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp From 3d516b69d912c32ea5615d21fd9375c53e7264d2 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:37:57 +0000 Subject: [PATCH 334/391] foundry-rs#14280 (#462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c332240fea036..9ce8f1bc25bd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0 + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 shellcheck: runs-on: depot-ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ad324e7ef9c1..e8c2b4b7d2700 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -279,7 +279,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -294,7 +294,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9ccb850dbc5c..93c57d29ec1dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,7 +97,7 @@ jobs: run: pip --version && pip install vyper==0.4.3 - name: Foundry test cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/.foundry/cache From 1f430c22a2f8785ca33b231215584af3fa5fc2fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:39:30 +0000 Subject: [PATCH 335/391] chore(deps): bump strum from 0.27.2 to 0.28.0 Bumps [strum](https://github.com/Peternator7/strum) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/Peternator7/strum/releases) - [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md) - [Commits](https://github.com/Peternator7/strum/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: strum dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 79 ++++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfc0874cf1fd3..65a696a8be26d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,7 +111,7 @@ dependencies = [ "alloy-rlp", "num_enum", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -717,7 +717,7 @@ dependencies = [ "jsonwebtoken", "rand 0.8.5", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -1194,7 +1194,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3021,7 +3021,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3174,7 +3174,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3958,7 +3958,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4630,7 +4630,7 @@ dependencies = [ "solar-compiler", "soldeer-commands", "soldeer-core", - "strum", + "strum 0.28.0", "svm-rs", "tempfile", "tempo-alloy", @@ -4964,7 +4964,7 @@ dependencies = [ "serde_json", "solar-compiler", "strsim", - "strum", + "strum 0.28.0", "tempfile", "tempo-primitives", "tikv-jemallocator", @@ -6588,7 +6588,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7514,7 +7514,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7735,7 +7735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "parking_lot", "stable_deref_trait", ] @@ -8813,7 +8813,7 @@ dependencies = [ "itertools 0.14.0", "kasuari", "lru", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -8845,7 +8845,7 @@ dependencies = [ "itertools 0.14.0", "line-clipping", "ratatui-core", - "strum", + "strum 0.27.2", "time", "unicode-segmentation", "unicode-width 0.2.2", @@ -9398,7 +9398,7 @@ dependencies = [ "modular-bitfield", "reth-codecs 0.3.0", "serde", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "tracing", ] @@ -9489,7 +9489,7 @@ dependencies = [ "fixed-map", "reth-stages-types", "serde", - "strum", + "strum 0.27.2", "tracing", ] @@ -10048,7 +10048,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10107,7 +10107,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10810,7 +10810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10826,7 +10826,7 @@ dependencies = [ "solar-data-structures", "solar-interface", "solar-macros", - "strum", + "strum 0.27.2", ] [[package]] @@ -10850,7 +10850,7 @@ version = "0.1.8" source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "colorchoice", - "strum", + "strum 0.27.2", ] [[package]] @@ -10947,7 +10947,7 @@ dependencies = [ "solar-interface", "solar-macros", "solar-parse", - "strum", + "strum 0.27.2", "thread_local", "tracing", ] @@ -11104,7 +11104,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -11119,6 +11128,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -11337,7 +11358,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11542,7 +11563,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11552,7 +11573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -12769,7 +12790,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -12919,7 +12940,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 33ba7f64fab10..7cc434c28e278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -483,7 +483,7 @@ serde_json = { version = "1.0", features = ["arbitrary_precision"] } similar-asserts = "1.7" soldeer-commands = "=0.10.0" soldeer-core = { version = "=0.10.1", features = ["serde"] } -strum = "0.27" +strum = "0.28" tempfile = "3.23" tokio = "1" toml = "0.9" From 04d520f5db9889fd779246e9f9cb8846ccfa0ed6 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:30:38 +0000 Subject: [PATCH 336/391] chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (foundry-rs#14398 (#463) * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> --- .github/scripts/tempo-mpp.sh | 18 +- .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yml | 6 +- .github/workflows/crate-checks.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/npm.yml | 11 +- .github/workflows/release.yml | 6 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- Cargo.lock | 99 ++++-- Cargo.toml | 6 +- Makefile | 41 ++- crates/cast/Cargo.toml | 2 +- crates/cast/src/call_spec.rs | 10 +- crates/cast/src/cmd/erc20.rs | 103 +++++- crates/cli/Cargo.toml | 2 +- crates/cli/src/utils/mod.rs | 14 +- crates/common/Cargo.toml | 1 + crates/common/fmt/Cargo.toml | 1 + crates/common/fmt/src/console.rs | 107 ++++++ crates/common/fmt/src/lib.rs | 2 +- crates/common/src/provider/mpp/persist.rs | 410 +++++++++------------- crates/common/src/provider/mpp/session.rs | 38 +- crates/evm/abi/src/Console.json | 2 +- crates/evm/abi/src/console.py | 8 +- crates/evm/evm/src/inspectors/logs.rs | 4 +- crates/forge/Cargo.toml | 2 +- crates/forge/src/cmd/create.rs | 127 +++++-- crates/macros/src/console_fmt.rs | 46 ++- crates/script/Cargo.toml | 2 +- npm/bun.lock | 15 +- npm/scripts/publish.mjs | 10 - npm/tsconfig.json | 1 - 34 files changed, 714 insertions(+), 394 deletions(-) diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh index 1a98b1ed9c494..0d34c4a3b5fa5 100755 --- a/.github/scripts/tempo-mpp.sh +++ b/.github/scripts/tempo-mpp.sh @@ -110,7 +110,7 @@ BLOCK2=$("$CAST" block-number --rpc-url "$RPC_MPP") AFTER2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') SPENT2=$((BEFORE2 - AFTER2)) echo "Block: $BLOCK2" -echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/foundry/channels.json)" +echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/channels.db)" # 6. forge script via MPP echo "" @@ -129,9 +129,9 @@ contract MppCheck is Script { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" script "$TMPDIR/script/Mpp.s.sol" --rpc-url "$RPC_MPP" --root "$TMPDIR" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 7. forge test with vm.createSelectFork via MPP @@ -149,15 +149,15 @@ contract MppForkTest is Test { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" test --match-test test_fork_via_mpp --root "$TMPDIR" -vvv -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 8. anvil fork via MPP echo "" echo "=== 8. anvil --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$ANVIL" --fork-url "$RPC_MPP" --port 8555 --silent & ANVIL_PID=$! for _ in $(seq 1 30); do @@ -167,15 +167,15 @@ done echo "chain-id: $("$CAST" chain-id --rpc-url http://localhost:8555)" kill $ANVIL_PID 2>/dev/null wait $ANVIL_PID 2>/dev/null -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 9. chisel fork via MPP echo "" echo "=== 9. chisel --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo 'block.number' | "$CHISEL" --fork-url "$RPC_MPP" 2>&1 | grep -E "Decimal|Type" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" echo "" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 96af1f4ab9a1e..8465874854551 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -61,7 +61,7 @@ jobs: printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" - name: Build benchmark binary - run: cargo build --release --bin foundry-bench + run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c332240fea036..53d009de4c442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo test --workspace --doc + - run: cargo test --workspace --doc --locked typos: runs-on: depot-ubuntu-latest @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0 + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 shellcheck: runs-on: depot-ubuntu-latest @@ -92,7 +92,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --all-features --locked env: RUSTFLAGS: -Dwarnings diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index 781d63759045e..b2eb6261efd63 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -34,7 +34,7 @@ jobs: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo hack check + - run: cargo hack check --locked issue: name: Open an issue diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a8a3dcbb8c7d3..288d9d127592f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: cargo doc --workspace --all-features --no-deps --document-private-items --locked env: RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - name: Setup Pages diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 007ef5f4286aa..4ef7c4ac04dfc 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -31,6 +31,7 @@ jobs: contents: read actions: read id-token: write + environment: release name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: @@ -151,9 +152,6 @@ jobs: id: release-version working-directory: ./npm env: - PROVENANCE: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | @@ -224,8 +222,6 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail @@ -282,12 +278,11 @@ jobs: contents: read actions: read id-token: write + environment: release needs: publish-arch name: Publish Meta Package runs-on: ubuntu-latest env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} steps: - name: Checkout @@ -323,6 +318,4 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - NPM_TOKEN: ${{ env.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ad324e7ef9c1..52b33787de3f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,7 +179,7 @@ jobs: shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $RUST_PROFILE --bins + flags=(--locked --target $TARGET --profile $RUST_PROFILE --bins --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. @@ -279,7 +279,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -294,7 +294,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index af4415b1e8ba1..c0e4fcb5f3263 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -50,7 +50,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --no-fail-fast + run: cargo nextest run --locked --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. issue: diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index afd91d3d72c05..37c3edc9b916a 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -54,7 +54,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast + run: cargo nextest run --locked --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. issue-isolate: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9ccb850dbc5c..7c79cd266f152 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,7 +97,7 @@ jobs: run: pip --version && pip install vyper==0.4.3 - name: Foundry test cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/.foundry/cache @@ -121,4 +121,4 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run ${{ matrix.flags }} + run: cargo nextest run --locked ${{ matrix.flags }} diff --git a/Cargo.lock b/Cargo.lock index d15f7c092e642..5a102a3276c63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,7 +1194,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2558,7 +2558,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -3021,7 +3021,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3174,7 +3174,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3958,7 +3958,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4376,6 +4376,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fast-float2" version = "0.2.3" @@ -5009,6 +5021,7 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-wallets", "itertools 0.14.0", "jiff", "mpp", @@ -5048,6 +5061,7 @@ dependencies = [ "alloy-rpc-types", "alloy-serde 2.0.0", "chrono", + "comfy-table", "eyre", "foundry-macros", "op-alloy-consensus", @@ -5544,7 +5558,7 @@ dependencies = [ [[package]] name = "foundry-wallets" version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry-core?rev=7f401c1397af90a0a94ef7424a48bbf3dc0248cc#7f401c1397af90a0a94ef7424a48bbf3dc0248cc" +source = "git+https://github.com/foundry-rs/foundry-core?rev=d6c92dfcb41be26fbc1de21221a5bfc6bdd93245#d6c92dfcb41be26fbc1de21221a5bfc6bdd93245" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5568,6 +5582,7 @@ dependencies = [ "eth-keystore", "eyre", "rpassword", + "rusqlite", "serde", "serde_json", "tempo-primitives", @@ -5970,6 +5985,15 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -6565,7 +6589,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6920,6 +6944,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -7480,7 +7515,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7701,7 +7736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.17.0", + "hashbrown 0.16.1", "parking_lot", "stable_deref_trait", ] @@ -8464,7 +8499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8633,7 +8668,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9930,6 +9965,20 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rusqlite" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -10000,7 +10049,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10059,7 +10108,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10070,9 +10119,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -10762,7 +10811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10830,7 +10879,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10865,7 +10914,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11289,7 +11338,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -11494,7 +11543,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -11504,7 +11553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -12721,7 +12770,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -12871,7 +12920,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 70d6eba6a6aec..20fba04e5f329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -357,7 +357,7 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## foundry-core -foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "7f401c1397af90a0a94ef7424a48bbf3dc0248cc", default-features = false } +foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "d6c92dfcb41be26fbc1de21221a5bfc6bdd93245", default-features = false } ## alloy alloy-consensus = { version = "2.0.0", default-features = false } @@ -384,7 +384,6 @@ alloy-signer-local = { version = "2.0.0", default-features = false } alloy-signer-trezor = { version = "2.0.0", default-features = false } alloy-signer-turnkey = { version = "2.0.0", default-features = false } alloy-transport = { version = "2.0.0", default-features = false } -alloy-transport-http = { version = "2.0.0", default-features = false } alloy-transport-ipc = { version = "2.0.0", default-features = false } alloy-transport-ws = { version = "2.0.0", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } @@ -622,3 +621,6 @@ solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/sola solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } + +[workspace.metadata.cargo-shear] +ignored = ["idna_adapter", "cast", "chisel", "forge", "alloy-contract"] diff --git a/Makefile b/Makefile index fe768f19abf43..1bf20f4eeebd1 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ help: ## Display this help. .PHONY: build build: ## Build the project. - cargo build --features "$(FEATURES)" --profile "$(PROFILE)" + cargo build --locked --features "$(FEATURES)" --profile "$(PROFILE)" .PHONY: build-docker build-docker: ## Build the docker image. @@ -47,17 +47,17 @@ build-docker: ## Build the docker image. ## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). .PHONY: test-coverage test-coverage: - cargo +nightly llvm-cov --no-report nextest -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ - cargo +nightly llvm-cov --no-report --doc && \ + cargo +nightly llvm-cov --no-report nextest --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ + cargo +nightly llvm-cov --no-report --doc --locked && \ cargo +nightly llvm-cov report --doctests --open .PHONY: test-unit test-unit: ## Run unit tests. - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' + cargo nextest run --workspace --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .PHONY: test-doc test-doc: ## Run doc tests. - cargo test --doc --workspace + cargo test --doc --workspace --locked .PHONY: test test: ## Run all tests. @@ -77,6 +77,7 @@ lint-clippy: ## Run clippy on the codebase. --workspace \ --all-targets \ --all-features \ + --locked \ -- -D warnings .PHONY: lint-clippy-fix @@ -85,6 +86,7 @@ lint-clippy-fix: ## Run clippy on the codebase and fix warnings. --workspace \ --all-targets \ --all-features \ + --locked \ --fix \ --allow-dirty \ --allow-staged \ @@ -104,21 +106,46 @@ lint: ## Run all linters. $(MAKE) lint-clippy && \ $(MAKE) lint-typos +##@ Documentation + +.PHONY: doc +doc: ## Build the documentation. + RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options --show-type-layout --generate-link-to-definition" \ + cargo +nightly doc \ + --workspace \ + --all-features \ + --document-private-items \ + --no-deps \ + --locked + ##@ Other +.PHONY: lock +lock: ## Update the Cargo.lock file with the current dependencies. + cargo fetch + .PHONY: clean clean: ## Clean the project. cargo clean .PHONY: deny deny: ## Perform a `cargo` deny check. - cargo deny --all-features check all + cargo deny --locked --all-features check all + +.PHONY: check +check: ## Run a feature check on all crates and binaries. + cargo hack check --locked --feature-powerset --depth 1 + +.PHONY: shear +shear: ## Run `cargo shear` to check for unused dependencies. + cargo shear --locked .PHONY: pr pr: ## Run all checks and tests. $(MAKE) deny && \ $(MAKE) lint && \ - $(MAKE) test + $(MAKE) test && \ + $(MAKE) doc # dprint formatting commands .PHONY: dprint-fmt diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dbd89ce88827d..56164c8f6367a 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -28,7 +28,7 @@ foundry-compilers.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } forge-fmt.workspace = true alloy-chains.workspace = true diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs index b39e024f38af5..0047b56d25f03 100644 --- a/crates/cast/src/call_spec.rs +++ b/crates/cast/src/call_spec.rs @@ -145,8 +145,8 @@ impl FromStr for CallSpec { /// Parse a value string that can be in ether notation (e.g., "0.1ether") or raw wei. fn parse_ether_or_wei(s: &str) -> Result { // Use alloy's DynSolType coercion which handles "1ether", "1gwei", "1000" etc. - if s.starts_with("0x") { - U256::from_str_radix(s, 16).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) + if s.starts_with("0x") || s.starts_with("0X") { + U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s) .wrap_err_with(|| format!("Invalid value '{s}'"))? @@ -180,6 +180,12 @@ mod tests { assert!(spec.sig.is_none()); } + #[test] + fn test_parse_hex_value() { + assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16)); + } + #[test] fn test_parse_with_sig() { let spec = CallSpec::parse( diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index e629b8eca0d5a..1d27b7a23dcd5 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -3,13 +3,13 @@ use std::{str::FromStr, time::Duration}; use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, format_uint_exp, - tx::{SendTxOpts, TxParams}, + tx::{CastTxSender, SendTxOpts, TxParams}, }; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network}; -use alloy_primitives::U256; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256}; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; @@ -28,6 +28,7 @@ use foundry_common::{ pub use foundry_config::{Chain, utils::*}; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::PATH_USD_ADDRESS; sol! { #[sol(rpc)] @@ -281,6 +282,34 @@ impl Erc20Subcommand { } } + const fn uses_browser_send(&self) -> bool { + match self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => send_tx.browser.browser, + _ => false, + } + } + + async fn should_use_tempo_network( + &self, + tempo_access_key: &Option, + ) -> eyre::Result { + if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some() + { + return Ok(true); + } + + if self.uses_browser_send() { + let config = self.rpc_opts().load_config()?; + return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo()); + } + + Ok(false) + } + pub async fn run(self) -> eyre::Result<()> { // Resolve the signer once for state-changing variants. let (signer, tempo_access_key) = match &self { @@ -299,8 +328,7 @@ impl Erc20Subcommand { _ => (None, None), }; - let is_tempo = self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) - || tempo_access_key.is_some(); + let is_tempo = self.should_use_tempo_network(&tempo_access_key).await?; if is_tempo { self.run_generic::(signer, tempo_access_key).await @@ -356,6 +384,28 @@ impl Erc20Subcommand { timeout, ) .await? + } else if let Some(browser) = $send_tx.browser.run::().await? { + let $provider = ProviderBuilder::::from_config(&config)?.build()?; + if let Some(interval) = $send_tx.poll_interval { + $provider.client().set_poll_interval(Duration::from_secs(interval)); + } + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + let chain = get_chain(config.chain, &$provider).await?; + $tx_opts.apply::(&mut tx, chain.is_legacy()); + if chain.is_tempo() && tx.fee_token().is_none() { + tx.set_fee_token(PATH_USD_ADDRESS); + } + fill_tx(&$provider, &mut tx, browser.address(), chain).await?; + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&$provider) + .print_tx_result( + tx_hash, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? } else { let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); let $provider = build_provider_with_signer::(&$send_tx, signer)?; @@ -503,3 +553,46 @@ impl Erc20Subcommand { Ok(()) } } + +/// Fills from, chain_id, nonce, fees, and gas limit on a transaction request for the browser +/// wallet path. Mirrors the filling logic in the shared tx builder but operates on a +/// pre-built transaction request from the sol! macro rather than through the builder pipeline. +/// Only fills fields that haven't already been set by the user. +async fn fill_tx>( + provider: &P, + tx: &mut N::TransactionRequest, + from: Address, + chain: Chain, +) -> eyre::Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + tx.set_from(from); + tx.set_chain_id(chain.id()); + + if tx.nonce().is_none() { + tx.set_nonce(provider.get_transaction_count(from).await?); + } + + let legacy = chain.is_legacy(); + + if legacy { + if tx.gas_price().is_none() { + tx.set_gas_price(provider.get_gas_price().await?); + } + } else if tx.max_fee_per_gas().is_none() || tx.max_priority_fee_per_gas().is_none() { + let estimate = provider.estimate_eip1559_fees().await?; + if tx.max_fee_per_gas().is_none() { + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } + if tx.max_priority_fee_per_gas().is_none() { + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + } + + if tx.gas_limit().is_none() { + tx.set_gas_limit(provider.estimate_gas(tx.clone()).await?); + } + + Ok(()) +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 165154da88f63..bcbe837e824e3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,7 +19,7 @@ foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tempo-primitives.workspace = true diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f1c6c435217b7..be566288ef87a 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -129,8 +129,8 @@ where /// If the string represents an untagged amount (e.g. "100") then /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { - Ok(if value.starts_with("0x") { - U256::from_str_radix(value, 16)? + Ok(if value.starts_with("0x") || value.starts_with("0X") { + U256::from_str(value)? } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? .as_uint() @@ -844,6 +844,16 @@ mod tests { assert!(!p.is_sol_test()); } + #[test] + fn parse_ether_value_accepts_hex_prefixed_wei() { + assert_eq!(parse_ether_value("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0X10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0x12").unwrap(), U256::from(0x12)); + assert_eq!(parse_ether_value("0xff").unwrap(), U256::from(0xff)); + assert_eq!(parse_ether_value("100").unwrap(), U256::from(100)); + assert_eq!(parse_ether_value("1ether").unwrap(), U256::from(1000000000000000000u128)); + } + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 706527093cc8e..6697aff36e4f8 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -83,6 +83,7 @@ flate2.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true mpp.workspace = true +foundry-wallets.workspace = true [build-dependencies] chrono.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 78759848fc4bd..2c8e16bccdcc6 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -30,6 +30,7 @@ serde_json.workspace = true chrono.workspace = true revm.workspace = true yansi.workspace = true +comfy-table.workspace = true # Tempo tempo-alloy.workspace = true diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index d751ddba245ba..8b66335445046 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -1,5 +1,6 @@ use super::UIfmt; use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use comfy_table::{Table, TableComponent, presets::UTF8_FULL}; use std::fmt::{self, Write}; /// A piece is a portion of the format string which represents the next part to emit. @@ -407,6 +408,36 @@ fn format_spec<'a>( } } +pub fn console_table_format( + keys: Option<&[&dyn ConsoleFmt]>, + values: &[&dyn ConsoleFmt], +) -> String { + let keys_strings: Vec = match keys { + Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(), + None => (0..values.len()).map(|i| i.to_string()).collect(), + }; + let values_strings: Vec = values.iter().map(|v| v.fmt(FormatSpec::String)).collect(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL); + table.set_style(TableComponent::VerticalLines, '│'); + table.set_style(TableComponent::HeaderLines, '─'); + table.set_style(TableComponent::MiddleHeaderIntersections, '┼'); + table.set_style(TableComponent::LeftHeaderIntersection, '├'); + table.set_style(TableComponent::RightHeaderIntersection, '┤'); + table.set_header(vec!["(index)", "Values"]); + table.remove_style(TableComponent::HorizontalLines); + table.remove_style(TableComponent::MiddleIntersections); + table.remove_style(TableComponent::LeftBorderIntersections); + table.remove_style(TableComponent::RightBorderIntersections); + for i in 0..keys_strings.len().max(values_strings.len()) { + let key = keys_strings.get(i).map(String::as_str).unwrap_or(""); + let value = values_strings.get(i).map(String::as_str).unwrap_or(""); + table.add_row(vec![key, value]); + } + table.to_string() +} + #[cfg(test)] mod tests { use super::*; @@ -610,4 +641,80 @@ mod tests { let call = Logs::Log1(log1); assert_eq!(call.fmt(Default::default()), "foo 42 bar"); } + + #[test] + fn test_console_table_format() { + // auto-indexed, uint256 values + let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)]; + assert_eq!( + console_table_format(None, values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ 0 │ 100 │\n\ + │ 1 │ 200 │\n\ + │ 2 │ 300 │\n\ + └─────────┴────────┘" + ); + + // string keys, uint256 values + // key col expands to fit "charlie123" and value col expands to fit "20000000000000000" + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")]; + let values: &[&dyn ConsoleFmt] = &[ + &U256::from(1), + &U256::from_str("20000000000000000").unwrap(), + &U256::from_str("30000000000").unwrap(), + ]; + assert_eq!( + console_table_format(Some(keys), values), + "┌────────────┬───────────────────┐\n\ + │ (index) │ Values │\n\ + ├────────────┼───────────────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 20000000000000000 │\n\ + │ charlie123 │ 30000000000 │\n\ + └────────────┴───────────────────┘" + ); + + // empty table + assert_eq!( + console_table_format(None, &[]), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + └─────────┴────────┘" + ); + + // more keys than values + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie")]; + let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ charlie │ │\n\ + └─────────┴────────┘" + ); + + // more values than keys + let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")]; + let values: &[&dyn ConsoleFmt] = + &[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ │ 3 │\n\ + │ │ 4 │\n\ + └─────────┴────────┘" + ); + } } diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 89297a8e2c6b5..45ed47263ce32 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod console; -pub use console::{ConsoleFmt, FormatSpec, console_format}; +pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format}; mod dynamic; pub use dynamic::{ diff --git a/crates/common/src/provider/mpp/persist.rs b/crates/common/src/provider/mpp/persist.rs index 6d6781ab4c77b..803c7270e041e 100644 --- a/crates/common/src/provider/mpp/persist.rs +++ b/crates/common/src/provider/mpp/persist.rs @@ -1,243 +1,241 @@ //! Persistent channel storage for MPP sessions. //! -//! Stores open payment channel state in a JSON file at -//! `$TEMPO_HOME/foundry/channels.json` (default: `~/.tempo/foundry/channels.json`). +//! Stores open payment channel state in a SQLite database at +//! `$TEMPO_HOME/channels.db` (default: `~/.tempo/channels.db`). //! This allows channel reuse across process invocations, avoiding the cost of //! opening a new on-chain channel for every `cast` / `forge` command. use alloy_primitives::{Address, B256}; +use foundry_wallets::{Channel, ChannelDb}; use mpp::client::channel_ops::ChannelEntry; -use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - path::PathBuf, + sync::OnceLock, time::{SystemTime, UNIX_EPOCH}, }; use tracing::{debug, warn}; use crate::tempo::tempo_home; -/// Relative path from Tempo home to the Foundry channels file. -const CHANNELS_PATH: &str = "foundry/channels.json"; +/// Process-wide database handle. +fn global_db() -> Option<&'static ChannelDb> { + static DB: OnceLock> = OnceLock::new(); + DB.get_or_init(|| { + let path = tempo_home()?.join("channels.db"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Some(old) = + tempo_home().map(|h| h.join("foundry/channels.json")).filter(|p| p.exists()) + { + warn!( + ?old, + "found old channels.json — this file is no longer used; channels will be re-opened" + ); + } -/// Current schema version. -const SCHEMA_VERSION: u64 = 2; + match ChannelDb::open(&path) { + Ok(db) => { + debug!(?path, "opened channel database"); + Some(db) + } + Err(e) => { + warn!(?path, %e, "failed to open channel database"); + None + } + } + }) + .as_ref() +} -/// On-disk representation of the channel store. -#[derive(Debug, Serialize, Deserialize)] -struct ChannelStore { - version: u64, - #[serde(default)] - channels: HashMap, +/// Reconstruct the composite HashMap key from a persisted `Channel`. +/// +/// Mirrors `SessionProvider::channel_key()` in session.rs. +fn channel_key_from_persisted(ch: &Channel) -> String { + let origin_hash = &alloy_primitives::keccak256(ch.origin.as_bytes()).to_string()[..18]; + format!( + "{}:{}:{}:{}:{}:{}:{}", + origin_hash, + ch.chain_id, + ch.payer, + ch.authorized_signer, + ch.payee, + ch.token, + ch.escrow_contract + ) + .to_lowercase() } -impl Default for ChannelStore { - fn default() -> Self { - Self { version: SCHEMA_VERSION, channels: HashMap::new() } +/// Whether a channel can still be used (active and not fully spent). +fn is_usable(ch: &Channel) -> bool { + if ch.state != "active" { + return false; } + let cumulative: u128 = ch.cumulative_amount.parse().unwrap_or(u128::MAX); + let deposit: u128 = ch.deposit.parse().unwrap_or(0); + cumulative < deposit } -/// A persisted channel entry. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PersistedChannel { - pub channel_id: String, - pub salt: String, - pub escrow_contract: String, - pub chain_id: u64, - pub cumulative_amount: String, - pub deposit: String, - pub status: String, - pub origin: String, - pub created_at: u64, - pub last_used_at: u64, +/// Convert a persisted `Channel` to a `ChannelEntry`. +pub fn to_channel_entry(ch: &Channel) -> Option { + let channel_id: B256 = ch.channel_id.parse().ok()?; + let salt: B256 = ch.salt.parse().ok()?; + let escrow_contract: Address = ch.escrow_contract.parse().ok()?; + let cumulative_amount: u128 = ch.cumulative_amount.parse().ok()?; + + Some(ChannelEntry { + channel_id, + salt, + cumulative_amount, + escrow_contract, + chain_id: ch.chain_id as u64, + opened: ch.state == "active", + }) } -impl PersistedChannel { - /// Convert to an mpp `ChannelEntry` for use in the session provider. - pub fn to_channel_entry(&self) -> Option { - let channel_id: B256 = self.channel_id.parse().ok()?; - let salt: B256 = self.salt.parse().ok()?; - let escrow_contract: Address = self.escrow_contract.parse().ok()?; - let cumulative_amount: u128 = self.cumulative_amount.parse().ok()?; - - Some(ChannelEntry { - channel_id, - salt, - cumulative_amount, - escrow_contract, - chain_id: self.chain_id, - opened: self.status == "active", - }) - } - - /// Create from a `ChannelEntry` with metadata. - pub fn from_channel_entry(entry: &ChannelEntry, deposit: u128, origin: &str) -> Self { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); - - Self { - channel_id: entry.channel_id.to_string(), - salt: entry.salt.to_string(), - escrow_contract: entry.escrow_contract.to_string(), - chain_id: entry.chain_id, - cumulative_amount: entry.cumulative_amount.to_string(), - deposit: deposit.to_string(), - status: if entry.opened { "active" } else { "closed" }.to_string(), - origin: origin.to_string(), - created_at: now, - last_used_at: now, - } - } - - /// Whether this channel can still be used (active and not fully spent). - fn is_usable(&self) -> bool { - if self.status != "active" { - return false; - } - let cumulative: u128 = self.cumulative_amount.parse().unwrap_or(u128::MAX); - let deposit: u128 = self.deposit.parse().unwrap_or(0); - cumulative < deposit +/// Create a `Channel` from a `ChannelEntry` with metadata. +pub fn from_channel_entry( + entry: &ChannelEntry, + deposit: u128, + origin: &str, + payer: &Address, + payee: &Address, + token: &Address, + authorized_signer: &Address, +) -> Channel { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + Channel { + channel_id: entry.channel_id.to_string(), + version: 1, + origin: origin.to_string(), + request_url: String::new(), + chain_id: entry.chain_id as i64, + escrow_contract: entry.escrow_contract.to_string(), + token: token.to_string(), + payee: payee.to_string(), + payer: payer.to_string(), + authorized_signer: authorized_signer.to_string(), + salt: entry.salt.to_string(), + deposit: deposit.to_string(), + cumulative_amount: entry.cumulative_amount.to_string(), + challenge_echo: String::new(), + state: if entry.opened { "active" } else { "closed" }.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: now, + last_used_at: now, } } -/// Returns the path to the channels file. -fn channels_path() -> Option { - tempo_home().map(|home| home.join(CHANNELS_PATH)) -} - -/// Load channels from disk, evicting spent/inactive entries. -pub fn load_channels() -> HashMap { - let Some(path) = channels_path().filter(|p| p.exists()) else { +/// Load channels from database, evicting spent/inactive entries. +pub fn load_channels() -> HashMap { + let Some(db) = global_db() else { return HashMap::new(); }; - let Ok(contents) = std::fs::read_to_string(&path).inspect_err(|e| { - warn!(?path, %e, "failed to read channels file"); - }) else { - return HashMap::new(); - }; - - let Ok(store) = serde_json::from_str::(&contents).inspect_err(|e| { - warn!(?path, %e, "failed to parse channels file, starting fresh"); - }) else { - return HashMap::new(); + let channels = match db.load() { + Ok(channels) => channels, + Err(e) => { + warn!(%e, "failed to load channels from database"); + return HashMap::new(); + } }; - if store.version != SCHEMA_VERSION { - warn!( - version = store.version, - expected = SCHEMA_VERSION, - "channels file version mismatch, starting fresh" - ); - return HashMap::new(); - } - - // Evict spent/inactive entries - let usable: HashMap = - store.channels.into_iter().filter(|(_, ch)| ch.is_usable()).collect(); + let usable: HashMap = channels + .into_iter() + .filter(is_usable) + .map(|ch| { + let key = channel_key_from_persisted(&ch); + (key, ch) + }) + .collect(); debug!(count = usable.len(), "loaded persisted MPP channels"); usable } -/// Save channels to disk. -pub fn save_channels(channels: &HashMap) { - let Some(path) = channels_path() else { +/// Save channels to database. +pub fn save_channels(channels: &HashMap) { + let Some(db) = global_db() else { return; }; - if let Some(parent) = path.parent() - && let Err(e) = std::fs::create_dir_all(parent) - { - warn!(?path, %e, "failed to create channels directory"); - return; + for ch in channels.values() { + if let Err(e) = db.upsert(ch) { + warn!(%e, channel_id = %ch.channel_id, "failed to save channel"); + } } + debug!(count = channels.len(), "saved MPP channels"); +} - let store = ChannelStore { version: SCHEMA_VERSION, channels: channels.clone() }; - - match serde_json::to_string_pretty(&store) { - Ok(json) => { - if let Err(e) = std::fs::write(&path, json) { - warn!(?path, %e, "failed to write channels file"); - } else { - debug!(?path, count = channels.len(), "saved MPP channels"); - } - } - Err(e) => warn!(%e, "failed to serialize channels"), +/// Delete a channel from the database by its channel ID. +pub fn delete_channel_from_db(channel_id: &str) { + let Some(db) = global_db() else { + return; + }; + if let Err(e) = db.delete(channel_id) { + warn!(%e, channel_id, "failed to delete channel from database"); } } /// Look up a usable persisted channel by key. -pub fn find_channel( - channels: &HashMap, - key: &str, -) -> Option { - channels.get(key).filter(|ch| ch.is_usable()).and_then(|ch| ch.to_channel_entry()) +pub fn find_channel(channels: &HashMap, key: &str) -> Option { + channels.get(key).filter(|ch| is_usable(ch)).and_then(to_channel_entry) } -/// Insert or update a channel entry in memory only (no disk write). -/// -/// Use [`upsert_channel`] when you want to persist immediately, or call -/// [`save_channels`] separately after this. +/// Insert or update a channel entry in memory only (no DB write). pub fn upsert_channel_in_memory( - channels: &mut HashMap, + channels: &mut HashMap, key: &str, entry: &ChannelEntry, - deposit: u128, - origin: &str, ) { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; if let Some(existing) = channels.get_mut(key) { existing.cumulative_amount = entry.cumulative_amount.to_string(); existing.last_used_at = now; - existing.status = if entry.opened { "active" } else { "closed" }.to_string(); + existing.state = if entry.opened { "active" } else { "closed" }.to_string(); } else { - channels - .insert(key.to_string(), PersistedChannel::from_channel_entry(entry, deposit, origin)); + warn!(key, "upsert_channel_in_memory called for unknown channel"); } } -/// Insert or update a channel entry and save to disk. -/// -/// When updating an existing entry, `deposit` is ignored (preserved from the -/// original open). When inserting a new entry, `deposit` is recorded. -pub fn upsert_channel( - channels: &mut HashMap, - key: &str, - entry: &ChannelEntry, - deposit: u128, - origin: &str, -) { - upsert_channel_in_memory(channels, key, entry, deposit, origin); - save_channels(channels); -} - #[cfg(test)] mod tests { use super::*; - fn test_channel(status: &str, cumulative: &str, deposit: &str) -> PersistedChannel { - PersistedChannel { + fn test_channel(state: &str, cumulative: &str, deposit: &str) -> Channel { + Channel { channel_id: format!("0x{}", "ab".repeat(32)), - salt: format!("0x{}", "cd".repeat(32)), - escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + version: 1, + origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + request_url: String::new(), chain_id: 42431, - cumulative_amount: cumulative.to_string(), + escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + token: "0x20c0000000000000000000000000000000000000".to_string(), + payee: "0x3333333333333333333333333333333333333333".to_string(), + payer: "0x1111111111111111111111111111111111111111".to_string(), + authorized_signer: "0x1111111111111111111111111111111111111111".to_string(), + salt: format!("0x{}", "cd".repeat(32)), deposit: deposit.to_string(), - status: status.to_string(), - origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + cumulative_amount: cumulative.to_string(), + challenge_echo: String::new(), + state: state.to_string(), + close_requested_at: 0, + grace_ready_at: 0, created_at: 1000, last_used_at: 1000, } } #[test] - fn is_usable() { - assert!(test_channel("active", "5000", "100000").is_usable()); - assert!(!test_channel("active", "100000", "100000").is_usable()); - assert!(!test_channel("active", "200000", "100000").is_usable()); - assert!(!test_channel("closed", "0", "100000").is_usable()); - assert!(!test_channel("closing", "0", "100000").is_usable()); + fn usable() { + assert!(is_usable(&test_channel("active", "5000", "100000"))); + assert!(!is_usable(&test_channel("active", "100000", "100000"))); + assert!(!is_usable(&test_channel("active", "200000", "100000"))); + assert!(!is_usable(&test_channel("closed", "0", "100000"))); + assert!(!is_usable(&test_channel("closing", "0", "100000"))); } #[test] @@ -251,8 +249,12 @@ mod tests { opened: true, }; - let persisted = PersistedChannel::from_channel_entry(&entry, 100_000, "https://rpc.test"); - let restored = persisted.to_channel_entry().expect("should parse back"); + let payer = Address::random(); + let payee = Address::random(); + let token = Address::random(); + let persisted = + from_channel_entry(&entry, 100_000, "https://rpc.test", &payer, &payee, &token, &payer); + let restored = to_channel_entry(&persisted).expect("should parse back"); assert_eq!(restored.channel_id, entry.channel_id); assert_eq!(restored.salt, entry.salt); @@ -262,46 +264,6 @@ mod tests { assert!(restored.opened); } - #[test] - fn load_evicts_and_handles_edge_cases() { - let dir = tempfile::tempdir().unwrap(); - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - - let store = ChannelStore { - version: SCHEMA_VERSION, - channels: HashMap::from([ - ("active".into(), test_channel("active", "1000", "100000")), - ("spent".into(), test_channel("active", "100000", "100000")), - ("closed".into(), test_channel("closed", "0", "100000")), - ]), - }; - let json = serde_json::to_string(&store).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), &json).unwrap(); - - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - let loaded = load_channels(); - unsafe { std::env::remove_var("TEMPO_HOME") }; - - assert_eq!(loaded.len(), 1); - assert!(loaded.contains_key("active")); - } - - #[test] - fn load_missing_and_wrong_version() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - assert!(load_channels().is_empty()); - - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), r#"{"version": 999, "channels": {}}"#) - .unwrap(); - assert!(load_channels().is_empty()); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } - #[test] fn find_channel_filters_unusable() { let mut channels = HashMap::new(); @@ -312,34 +274,4 @@ mod tests { assert!(find_channel(&channels, "spent").is_none()); assert!(find_channel(&channels, "missing").is_none()); } - - #[test] - fn upsert_inserts_and_updates() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - - let mut channels = HashMap::new(); - let entry = ChannelEntry { - channel_id: B256::random(), - salt: B256::random(), - cumulative_amount: 1000, - escrow_contract: Address::random(), - chain_id: 42431, - opened: true, - }; - - upsert_channel(&mut channels, "key1", &entry, 100_000, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "1000"); - assert_eq!(channels["key1"].deposit, "100000"); - let created_at = channels["key1"].created_at; - - let mut updated = entry.clone(); - updated.cumulative_amount = 5000; - upsert_channel(&mut channels, "key1", &updated, 0, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "5000"); - assert_eq!(channels["key1"].deposit, "100000"); - assert_eq!(channels["key1"].created_at, created_at); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } } diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 5996c933218d2..334166b844613 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -6,8 +6,9 @@ //! `eth_getTransactionCount`. This avoids the chicken-and-egg problem when //! the RPC endpoint is itself 402-gated. -use super::persist::{self, PersistedChannel}; +use super::persist; use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use foundry_wallets::Channel; use mpp::{ client::{ PaymentProvider, @@ -42,7 +43,7 @@ static GLOBAL_CHANNELS: OnceLock>> = O /// /// Using a single map ensures saves from different origins don't clobber /// each other's state. -static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); +static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); /// Tracks uncommitted channel state from the most recent payment. /// @@ -86,7 +87,7 @@ pub struct SessionProvider { default_deposit: Option, channels: Arc>>, key_provisioned: Arc>, - persisted: Arc>>, + persisted: Arc>>, /// Tracks uncommitted open/top-up state for deferred persistence. pending: Arc>>, /// Chain ID from the key entry in `keys.toml` that was used to initialize @@ -128,10 +129,10 @@ impl SessionProvider { map.entry(origin.clone()) .or_insert_with(|| { // Hydrate only channels belonging to this origin. - let mut channels = HashMap::new(); + let mut channels: HashMap = HashMap::new(); for (key, ch) in persisted.lock().unwrap().iter() { if ch.origin == origin - && let Some(entry) = ch.to_channel_entry() + && let Some(entry) = persist::to_channel_entry(ch) { channels.insert(key.clone(), entry); } @@ -209,16 +210,16 @@ impl SessionProvider { // Lock order: channels → persisted (consistent with pay_session) let mut channels = self.channels.lock().unwrap(); let mut persisted = self.persisted.lock().unwrap(); - let keys_to_remove: Vec = persisted + let keys_to_remove: Vec<(String, String)> = persisted .iter() .filter(|(_, ch)| ch.origin == *origin) - .map(|(k, _)| k.clone()) + .map(|(k, ch): (&String, &Channel)| (k.clone(), ch.channel_id.clone())) .collect(); - for key in &keys_to_remove { + for (key, channel_id) in &keys_to_remove { channels.remove(key); persisted.remove(key); + persist::delete_channel_from_db(channel_id); } - persist::save_channels(&persisted); } /// Mark whether the access key has been provisioned on-chain. @@ -685,13 +686,7 @@ impl SessionProvider { // confirms acceptance. let updated_entry = ChannelEntry { cumulative_amount: new_cumulative, ..entry }; let mut persisted = self.persisted.lock().unwrap(); - persist::upsert_channel_in_memory( - &mut persisted, - &key, - &updated_entry, - 0, - &self.origin, - ); + persist::upsert_channel_in_memory(&mut persisted, &key, &updated_entry); drop(persisted); // Track the voucher so we can roll back cumulative_amount @@ -727,9 +722,18 @@ impl SessionProvider { // Update in-memory state but defer disk persistence until server confirms. self.channels.lock().unwrap().insert(key.clone(), entry.clone()); + let authorized_signer = self.authorized_signer.unwrap_or(payer); self.persisted.lock().unwrap().insert( key.clone(), - persist::PersistedChannel::from_channel_entry(&entry, deposit, &self.origin), + persist::from_channel_entry( + &entry, + deposit, + &self.origin, + &payer, + &payee, + ¤cy, + &authorized_signer, + ), ); *self.pending.lock().unwrap() = Some(PendingAction::Open { key }); Ok(build_credential(challenge, payload, chain_id, payer)) diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json index 54e6d46dff349..f275f471087ee 100644 --- a/crates/evm/abi/src/Console.json +++ b/crates/evm/abi/src/Console.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index 2e28fece21e42..d0b32ff410941 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -16,12 +16,15 @@ def main(): # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() sig_strings = re.findall( - r'"(log.*?)"', + r'"((?:log|table).*?)"', console_sol, ) raw_sigs = [s.strip().strip('"') for s in sig_strings] sigs = [ - s.replace("string", "string memory").replace("bytes)", "bytes memory)") + re.sub(r"(\w+\[\])", r"\1 memory", s) + .replace("string,", "string memory,") + .replace("string)", "string memory)") + .replace("bytes)", "bytes memory)") for s in raw_sigs ] sigs = list(set(sigs)) @@ -38,6 +41,7 @@ def main(): ) combined = json.loads(r.stdout.strip()) abi = combined["contracts"][":HardhatConsole"]["abi"] + open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 26acbe56a97cb..c591b9426bf9e 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -50,7 +50,9 @@ impl LogCollector { fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.push_msg(&decoded.fmt(Default::default())); + for line in decoded.fmt(Default::default()).lines() { + self.push_msg(line); + } Ok(()) } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 9a7a237855b97..ecdc13ded67cf 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -51,7 +51,7 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } alloy-chains.workspace = true alloy-consensus.workspace = true diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 122020571ad24..a48d2a9350c70 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -9,7 +9,7 @@ use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as Alloy use alloy_signer::{Signature, Signer}; use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; +use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, @@ -33,10 +33,12 @@ use foundry_config::{ }, merge_impl_figment_convert, }; -use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use foundry_wallets::{ + BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner, +}; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; -use tempo_alloy::TempoNetwork; +use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN}; merge_impl_figment_convert!(CreateArgs, build, eth); @@ -101,14 +103,29 @@ pub struct CreateArgs { #[command(flatten)] retry: RetryArgs, + + /// Browser wallet options + #[command(flatten)] + browser: BrowserWalletOpts, } impl CreateArgs { /// Executes the command to create a contract - pub async fn run(self) -> Result<()> { + pub async fn run(mut self) -> Result<()> { let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?; - if tempo_access_key.is_some() || self.tx.tempo.is_tempo() { + // Resolve chain early so we can dispatch to the correct network type. + if self.chain_id().is_none() { + let config = self.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let chain_id = provider.get_chain_id().await?; + self.eth.etherscan.chain = Some(chain_id.into()); + } + + if tempo_access_key.is_some() + || self.tx.tempo.is_tempo() + || self.chain_id().is_some_and(|c| c.is_tempo()) + { self.run_generic::(signer, tempo_access_key).await } else { self.run_generic::(signer, None).await @@ -185,17 +202,29 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chain_id().await? - }; - // Whether to broadcast the transaction or not let dry_run = !self.broadcast; - if self.unlocked { + // Launch browser signer if `--browser` flag is set + let browser = self.browser.run::().await?; + + if let Some(browser) = browser { + // Deploy with browser wallet + let deployer_address = browser.address(); + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + None, + Some(browser), + ) + .await + } else if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( @@ -203,12 +232,12 @@ impl CreateArgs { bin, params, provider, - chain_id, sender, config.transaction_timeout, id, dry_run, None, + None, ) .await } else if let Some(ak) = access_key { @@ -223,12 +252,12 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer_address, config.transaction_timeout, id, dry_run, Some((signer, ak)), + None, ) .await } else { @@ -246,20 +275,20 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer, config.transaction_timeout, id, dry_run, None, + None, ) .await } } - /// Returns the provided chain id, if any. - fn chain_id(&self) -> Option { - self.eth.etherscan.chain.map(|chain| chain.id()) + /// Returns the resolved chain, if any. + const fn chain_id(&self) -> Option { + self.eth.etherscan.chain } /// Ensures the verify command can be executed. @@ -271,7 +300,6 @@ impl CreateArgs { async fn verify_preflight_check( &self, constructor_args: Option, - chain: u64, id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, @@ -287,7 +315,7 @@ impl CreateArgs { num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), - chain: Some(chain.into()), + chain: self.chain_id(), }, rpc: Default::default(), flatten: false, @@ -311,7 +339,7 @@ impl CreateArgs { // ETHERSCAN_API_KEY value set. let config = verify.load_config()?; verify.etherscan.key = - config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key); let context = verify.resolve_context().await?; @@ -327,17 +355,19 @@ impl CreateArgs { bin: BytecodeObject, args: Vec, provider: P, - chain: u64, deployer_address: Address, timeout: u64, id: ArtifactId, dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, + browser_signer: Option>, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, N::ReceiptResponse: serde::Serialize, { + let chain = self.chain_id().context("chain ID not resolved")?; + let bin = bin.into_bytes().unwrap_or_default(); if bin.is_empty() { eyre::bail!("no bytecode found in bin object for {}", self.contract.name) @@ -349,22 +379,31 @@ impl CreateArgs { let is_args_empty = args.is_empty(); let mut deployer = - factory.deploy_tokens(args.clone(), self.tx.tempo.fee_token).context("failed to deploy contract").map_err(|e| { + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { e } })?; - let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + let is_legacy = self.tx.legacy || chain.is_legacy(); deployer.tx.set_from(deployer_address); - deployer.tx.set_chain_id(chain); + deployer.tx.set_chain_id(chain.id()); // `to` field must be set explicitly, cannot be None. if deployer.tx.to().is_none() { deployer.tx.set_create(); } + // If Tempo chain fee token must be set + if chain.is_tempo() { + if let Some(fee_token) = self.tx.tempo.fee_token { + deployer.tx.set_fee_token(fee_token); + } else { + deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); + } + } + // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); @@ -422,7 +461,7 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + self.verify_preflight_check(constructor_args.clone(), &id).await?; } if dry_run { @@ -452,7 +491,31 @@ impl CreateArgs { } // Deploy the actual contract - let (deployed_contract, receipt) = if let Some((signer, ak)) = tempo_keychain { + let (deployed_contract, receipt) = if let Some(browser) = browser_signer { + // Browser wallet signs and sends the transaction + let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?; + + // Wait for the transaction to be confirmed, then fetch the receipt. + provider + .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash)) + .await? + .await?; + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?; + + if !receipt.status() { + eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}"); + } + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else if let Some((signer, ak)) = tempo_keychain { // Tempo keychain mode: sign with access key provisioning and send raw let raw_tx = deployer .tx @@ -518,7 +581,7 @@ impl CreateArgs { no_auto_detect: false, use_solc: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) }, rpc: Default::default(), flatten: false, force: false, @@ -681,7 +744,6 @@ impl + Clone> DeploymentTxFactory { pub fn deploy_tokens( self, params: Vec, - fee_token: Option
, ) -> Result, ContractDeploymentError> where N::TransactionRequest: FoundryTransactionBuilder, @@ -703,9 +765,6 @@ impl + Clone> DeploymentTxFactory { // create the tx object. Since we're deploying a contract, `to` is `None` let mut tx = N::TransactionRequest::default(); tx.set_input(data); - if let Some(fee_token) = fee_token { - tx.set_fee_token(fee_token); - } Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } @@ -763,7 +822,7 @@ mod tests { "--chain-id", "9999", ]); - assert_eq!(args.chain_id(), Some(9999)); + assert_eq!(args.chain_id().map(|c| c.id()), Some(9999)); } #[test] diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 0c95841d1e653..245ba6e5b78e2 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -7,7 +7,7 @@ use syn::{ pub fn console_fmt(input: &DeriveInput) -> TokenStream { let name = &input.ident; let tokens = match &input.data { - Data::Struct(s) => derive_struct(s), + Data::Struct(s) => derive_struct(s, name), Data::Enum(e) => derive_enum(e), Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; @@ -18,8 +18,8 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { } } -fn derive_struct(s: &DataStruct) -> TokenStream { - let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); +fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream { + let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new())); quote! { fn fmt(&self, _spec: FormatSpec) -> String { #imp @@ -27,7 +27,7 @@ fn derive_struct(s: &DataStruct) -> TokenStream { } } -fn impl_struct(s: &DataStruct) -> Option { +fn impl_struct(s: &DataStruct, name: &Ident) -> Option { if s.fields.is_empty() { return None; } @@ -36,13 +36,49 @@ fn impl_struct(s: &DataStruct) -> Option { return None; } + let members = s.fields.members().collect::>(); let fields = s.fields.iter().collect::>(); + + // Detect table call structs: name must start with "table" (from the ABI function name) and + // all fields must be Vec types (Solidity arrays). Both conditions together prevent + // accidental table rendering for unrelated structs that happen to have Vec fields. + let is_table = name.to_string().starts_with("table") + && !fields.is_empty() + && fields.iter().all(|f| match &f.ty { + Type::Path(path) => path.path.segments.last().is_some_and(|seg| seg.ident == "Vec"), + _ => false, + }); + if is_table { + let member_ref = |m: &Member| match m { + Member::Named(ident) => quote!(&self.#ident), + Member::Unnamed(idx) => quote!(&self.#idx), + }; + let imp = if members.len() == 1 { + let vals = member_ref(&members[0]); + quote! { + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(None, &values) + } + } else { + let keys = member_ref(&members[0]); + let vals = member_ref(&members[1]); + quote! { + let keys: ::std::vec::Vec<&dyn ConsoleFmt> = + (#keys).iter().map(|v| v as &dyn ConsoleFmt).collect(); + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(Some(&keys), &values) + } + }; + return Some(imp); + } + let first_ty = match &fields.first().unwrap().ty { Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), _ => String::new(), }; - let members = s.fields.members().collect::>(); let args: Punctuated = members .into_iter() .map(|member| match member { diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 65b75b8b39bb1..332420d0d1c42 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -23,7 +23,7 @@ foundry-evm-networks.workspace = true alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } foundry-linking.workspace = true forge-script-sequence.workspace = true diff --git a/npm/bun.lock b/npm/bun.lock index e8ef087be9d50..d9e8fed5ce479 100644 --- a/npm/bun.lock +++ b/npm/bun.lock @@ -1,12 +1,13 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3", + "typescript": "^6.0.2", }, }, }, @@ -35,7 +36,7 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], - "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], @@ -45,8 +46,12 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "bun-types/@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } } diff --git a/npm/scripts/publish.mjs b/npm/scripts/publish.mjs index 0f582c8ebc555..deee29e46c92f 100644 --- a/npm/scripts/publish.mjs +++ b/npm/scripts/publish.mjs @@ -7,17 +7,12 @@ import { colors } from '#const.mjs' const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' -const NPM_TOKEN = Bun.env.NPM_TOKEN -if (!NPM_TOKEN) throw new Error('NPM_TOKEN is required') - main().catch(error => { console.error(error) process.exit(1) }) async function main() { - const npmToken = Bun.env.NPM_TOKEN - if (!npmToken) throw new Error('NPM_TOKEN is required') const inputPath = Bun.argv[2] if (!inputPath) throw new Error('Package path is required') @@ -114,11 +109,6 @@ async function setPackageVersion(packagePath, version) { console.info(colors.green, 'Setting package version:', version) const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` .cwd(packagePath) - .env({ - ...Bun.env, - ...process.env, - NPM_TOKEN - }) .quiet() .nothrow() diff --git a/npm/tsconfig.json b/npm/tsconfig.json index ca8e3c92f2daa..a7a0093d7ee56 100644 --- a/npm/tsconfig.json +++ b/npm/tsconfig.json @@ -2,7 +2,6 @@ "schema": "https://json.schemastore.org/tsconfig.json", "compilerOptions": { "strict": true, - "baseUrl": ".", "noEmit": true, "allowJs": true, "checkJs": true, From d0f3cff79cce76d6e152521924ba26042cbd8eed Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:34:42 +0000 Subject: [PATCH 337/391] fix(npm): remove deprecated baseUrl compiler option (foundry-rs#14413) (#467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> --- .github/scripts/tempo-mpp.sh | 18 +- .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/crate-checks.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/npm.yml | 11 +- .github/workflows/release.yml | 2 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 2 +- Cargo.lock | 99 ++++-- Cargo.toml | 6 +- Makefile | 41 ++- crates/cast/Cargo.toml | 2 +- crates/cast/src/call_spec.rs | 10 +- crates/cast/src/cmd/erc20.rs | 103 +++++- crates/cli/Cargo.toml | 2 +- crates/cli/src/utils/mod.rs | 14 +- crates/common/Cargo.toml | 1 + crates/common/fmt/Cargo.toml | 1 + crates/common/fmt/src/console.rs | 107 ++++++ crates/common/fmt/src/lib.rs | 2 +- crates/common/src/provider/mpp/persist.rs | 410 +++++++++------------- crates/common/src/provider/mpp/session.rs | 38 +- crates/evm/abi/src/Console.json | 2 +- crates/evm/abi/src/console.py | 8 +- crates/evm/evm/src/inspectors/logs.rs | 4 +- crates/forge/Cargo.toml | 2 +- crates/forge/src/cmd/create.rs | 127 +++++-- crates/macros/src/console_fmt.rs | 46 ++- crates/script/Cargo.toml | 2 +- npm/bun.lock | 15 +- npm/scripts/publish.mjs | 10 - npm/tsconfig.json | 1 - 34 files changed, 710 insertions(+), 390 deletions(-) diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh index 1a98b1ed9c494..0d34c4a3b5fa5 100755 --- a/.github/scripts/tempo-mpp.sh +++ b/.github/scripts/tempo-mpp.sh @@ -110,7 +110,7 @@ BLOCK2=$("$CAST" block-number --rpc-url "$RPC_MPP") AFTER2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') SPENT2=$((BEFORE2 - AFTER2)) echo "Block: $BLOCK2" -echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/foundry/channels.json)" +echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/channels.db)" # 6. forge script via MPP echo "" @@ -129,9 +129,9 @@ contract MppCheck is Script { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" script "$TMPDIR/script/Mpp.s.sol" --rpc-url "$RPC_MPP" --root "$TMPDIR" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 7. forge test with vm.createSelectFork via MPP @@ -149,15 +149,15 @@ contract MppForkTest is Test { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" test --match-test test_fork_via_mpp --root "$TMPDIR" -vvv -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 8. anvil fork via MPP echo "" echo "=== 8. anvil --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$ANVIL" --fork-url "$RPC_MPP" --port 8555 --silent & ANVIL_PID=$! for _ in $(seq 1 30); do @@ -167,15 +167,15 @@ done echo "chain-id: $("$CAST" chain-id --rpc-url http://localhost:8555)" kill $ANVIL_PID 2>/dev/null wait $ANVIL_PID 2>/dev/null -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 9. chisel fork via MPP echo "" echo "=== 9. chisel --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo 'block.number' | "$CHISEL" --fork-url "$RPC_MPP" 2>&1 | grep -E "Decimal|Type" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" echo "" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 96af1f4ab9a1e..8465874854551 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -61,7 +61,7 @@ jobs: printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" - name: Build benchmark binary - run: cargo build --release --bin foundry-bench + run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ce8f1bc25bd9..53d009de4c442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo test --workspace --doc + - run: cargo test --workspace --doc --locked typos: runs-on: depot-ubuntu-latest @@ -92,7 +92,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --all-features --locked env: RUSTFLAGS: -Dwarnings diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index 781d63759045e..b2eb6261efd63 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -34,7 +34,7 @@ jobs: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo hack check + - run: cargo hack check --locked issue: name: Open an issue diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a8a3dcbb8c7d3..288d9d127592f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: cargo doc --workspace --all-features --no-deps --document-private-items --locked env: RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - name: Setup Pages diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 9b9b798435c90..fdc5e5716c577 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -31,6 +31,7 @@ jobs: contents: read actions: read id-token: write + environment: release name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: @@ -151,9 +152,6 @@ jobs: id: release-version working-directory: ./npm env: - PROVENANCE: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | @@ -224,8 +222,6 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail @@ -245,12 +241,11 @@ jobs: contents: read actions: read id-token: write + environment: release needs: publish-arch name: Publish Meta Package runs-on: ubuntu-latest env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} steps: - name: Checkout @@ -286,6 +281,4 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - NPM_TOKEN: ${{ env.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e8c2b4b7d2700..52b33787de3f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,7 +179,7 @@ jobs: shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $RUST_PROFILE --bins + flags=(--locked --target $TARGET --profile $RUST_PROFILE --bins --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index af4415b1e8ba1..c0e4fcb5f3263 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -50,7 +50,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --no-fail-fast + run: cargo nextest run --locked --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. issue: diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index afd91d3d72c05..37c3edc9b916a 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -54,7 +54,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast + run: cargo nextest run --locked --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. issue-isolate: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93c57d29ec1dd..7c79cd266f152 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,4 +121,4 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run ${{ matrix.flags }} + run: cargo nextest run --locked ${{ matrix.flags }} diff --git a/Cargo.lock b/Cargo.lock index d15f7c092e642..5a102a3276c63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,7 +1194,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2558,7 +2558,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -3021,7 +3021,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3174,7 +3174,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3958,7 +3958,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4376,6 +4376,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fast-float2" version = "0.2.3" @@ -5009,6 +5021,7 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-wallets", "itertools 0.14.0", "jiff", "mpp", @@ -5048,6 +5061,7 @@ dependencies = [ "alloy-rpc-types", "alloy-serde 2.0.0", "chrono", + "comfy-table", "eyre", "foundry-macros", "op-alloy-consensus", @@ -5544,7 +5558,7 @@ dependencies = [ [[package]] name = "foundry-wallets" version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry-core?rev=7f401c1397af90a0a94ef7424a48bbf3dc0248cc#7f401c1397af90a0a94ef7424a48bbf3dc0248cc" +source = "git+https://github.com/foundry-rs/foundry-core?rev=d6c92dfcb41be26fbc1de21221a5bfc6bdd93245#d6c92dfcb41be26fbc1de21221a5bfc6bdd93245" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5568,6 +5582,7 @@ dependencies = [ "eth-keystore", "eyre", "rpassword", + "rusqlite", "serde", "serde_json", "tempo-primitives", @@ -5970,6 +5985,15 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -6565,7 +6589,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6920,6 +6944,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -7480,7 +7515,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7701,7 +7736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.17.0", + "hashbrown 0.16.1", "parking_lot", "stable_deref_trait", ] @@ -8464,7 +8499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8633,7 +8668,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9930,6 +9965,20 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rusqlite" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -10000,7 +10049,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10059,7 +10108,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10070,9 +10119,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -10762,7 +10811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10830,7 +10879,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10865,7 +10914,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11289,7 +11338,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -11494,7 +11543,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -11504,7 +11553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -12721,7 +12770,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -12871,7 +12920,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f902786070d81..9ca49c1bfc48a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -355,7 +355,7 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## foundry-core -foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "7f401c1397af90a0a94ef7424a48bbf3dc0248cc", default-features = false } +foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "d6c92dfcb41be26fbc1de21221a5bfc6bdd93245", default-features = false } ## alloy alloy-consensus = { version = "2.0.0", default-features = false } @@ -382,7 +382,6 @@ alloy-signer-local = { version = "2.0.0", default-features = false } alloy-signer-trezor = { version = "2.0.0", default-features = false } alloy-signer-turnkey = { version = "2.0.0", default-features = false } alloy-transport = { version = "2.0.0", default-features = false } -alloy-transport-http = { version = "2.0.0", default-features = false } alloy-transport-ipc = { version = "2.0.0", default-features = false } alloy-transport-ws = { version = "2.0.0", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } @@ -620,3 +619,6 @@ solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/sola solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } + +[workspace.metadata.cargo-shear] +ignored = ["idna_adapter", "cast", "chisel", "forge", "alloy-contract"] diff --git a/Makefile b/Makefile index fe768f19abf43..1bf20f4eeebd1 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ help: ## Display this help. .PHONY: build build: ## Build the project. - cargo build --features "$(FEATURES)" --profile "$(PROFILE)" + cargo build --locked --features "$(FEATURES)" --profile "$(PROFILE)" .PHONY: build-docker build-docker: ## Build the docker image. @@ -47,17 +47,17 @@ build-docker: ## Build the docker image. ## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). .PHONY: test-coverage test-coverage: - cargo +nightly llvm-cov --no-report nextest -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ - cargo +nightly llvm-cov --no-report --doc && \ + cargo +nightly llvm-cov --no-report nextest --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ + cargo +nightly llvm-cov --no-report --doc --locked && \ cargo +nightly llvm-cov report --doctests --open .PHONY: test-unit test-unit: ## Run unit tests. - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' + cargo nextest run --workspace --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .PHONY: test-doc test-doc: ## Run doc tests. - cargo test --doc --workspace + cargo test --doc --workspace --locked .PHONY: test test: ## Run all tests. @@ -77,6 +77,7 @@ lint-clippy: ## Run clippy on the codebase. --workspace \ --all-targets \ --all-features \ + --locked \ -- -D warnings .PHONY: lint-clippy-fix @@ -85,6 +86,7 @@ lint-clippy-fix: ## Run clippy on the codebase and fix warnings. --workspace \ --all-targets \ --all-features \ + --locked \ --fix \ --allow-dirty \ --allow-staged \ @@ -104,21 +106,46 @@ lint: ## Run all linters. $(MAKE) lint-clippy && \ $(MAKE) lint-typos +##@ Documentation + +.PHONY: doc +doc: ## Build the documentation. + RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options --show-type-layout --generate-link-to-definition" \ + cargo +nightly doc \ + --workspace \ + --all-features \ + --document-private-items \ + --no-deps \ + --locked + ##@ Other +.PHONY: lock +lock: ## Update the Cargo.lock file with the current dependencies. + cargo fetch + .PHONY: clean clean: ## Clean the project. cargo clean .PHONY: deny deny: ## Perform a `cargo` deny check. - cargo deny --all-features check all + cargo deny --locked --all-features check all + +.PHONY: check +check: ## Run a feature check on all crates and binaries. + cargo hack check --locked --feature-powerset --depth 1 + +.PHONY: shear +shear: ## Run `cargo shear` to check for unused dependencies. + cargo shear --locked .PHONY: pr pr: ## Run all checks and tests. $(MAKE) deny && \ $(MAKE) lint && \ - $(MAKE) test + $(MAKE) test && \ + $(MAKE) doc # dprint formatting commands .PHONY: dprint-fmt diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dbd89ce88827d..56164c8f6367a 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -28,7 +28,7 @@ foundry-compilers.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } forge-fmt.workspace = true alloy-chains.workspace = true diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs index b39e024f38af5..0047b56d25f03 100644 --- a/crates/cast/src/call_spec.rs +++ b/crates/cast/src/call_spec.rs @@ -145,8 +145,8 @@ impl FromStr for CallSpec { /// Parse a value string that can be in ether notation (e.g., "0.1ether") or raw wei. fn parse_ether_or_wei(s: &str) -> Result { // Use alloy's DynSolType coercion which handles "1ether", "1gwei", "1000" etc. - if s.starts_with("0x") { - U256::from_str_radix(s, 16).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) + if s.starts_with("0x") || s.starts_with("0X") { + U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s) .wrap_err_with(|| format!("Invalid value '{s}'"))? @@ -180,6 +180,12 @@ mod tests { assert!(spec.sig.is_none()); } + #[test] + fn test_parse_hex_value() { + assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16)); + } + #[test] fn test_parse_with_sig() { let spec = CallSpec::parse( diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index e629b8eca0d5a..1d27b7a23dcd5 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -3,13 +3,13 @@ use std::{str::FromStr, time::Duration}; use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, format_uint_exp, - tx::{SendTxOpts, TxParams}, + tx::{CastTxSender, SendTxOpts, TxParams}, }; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network}; -use alloy_primitives::U256; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256}; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; @@ -28,6 +28,7 @@ use foundry_common::{ pub use foundry_config::{Chain, utils::*}; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::PATH_USD_ADDRESS; sol! { #[sol(rpc)] @@ -281,6 +282,34 @@ impl Erc20Subcommand { } } + const fn uses_browser_send(&self) -> bool { + match self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => send_tx.browser.browser, + _ => false, + } + } + + async fn should_use_tempo_network( + &self, + tempo_access_key: &Option, + ) -> eyre::Result { + if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some() + { + return Ok(true); + } + + if self.uses_browser_send() { + let config = self.rpc_opts().load_config()?; + return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo()); + } + + Ok(false) + } + pub async fn run(self) -> eyre::Result<()> { // Resolve the signer once for state-changing variants. let (signer, tempo_access_key) = match &self { @@ -299,8 +328,7 @@ impl Erc20Subcommand { _ => (None, None), }; - let is_tempo = self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) - || tempo_access_key.is_some(); + let is_tempo = self.should_use_tempo_network(&tempo_access_key).await?; if is_tempo { self.run_generic::(signer, tempo_access_key).await @@ -356,6 +384,28 @@ impl Erc20Subcommand { timeout, ) .await? + } else if let Some(browser) = $send_tx.browser.run::().await? { + let $provider = ProviderBuilder::::from_config(&config)?.build()?; + if let Some(interval) = $send_tx.poll_interval { + $provider.client().set_poll_interval(Duration::from_secs(interval)); + } + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + let chain = get_chain(config.chain, &$provider).await?; + $tx_opts.apply::(&mut tx, chain.is_legacy()); + if chain.is_tempo() && tx.fee_token().is_none() { + tx.set_fee_token(PATH_USD_ADDRESS); + } + fill_tx(&$provider, &mut tx, browser.address(), chain).await?; + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&$provider) + .print_tx_result( + tx_hash, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? } else { let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); let $provider = build_provider_with_signer::(&$send_tx, signer)?; @@ -503,3 +553,46 @@ impl Erc20Subcommand { Ok(()) } } + +/// Fills from, chain_id, nonce, fees, and gas limit on a transaction request for the browser +/// wallet path. Mirrors the filling logic in the shared tx builder but operates on a +/// pre-built transaction request from the sol! macro rather than through the builder pipeline. +/// Only fills fields that haven't already been set by the user. +async fn fill_tx>( + provider: &P, + tx: &mut N::TransactionRequest, + from: Address, + chain: Chain, +) -> eyre::Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + tx.set_from(from); + tx.set_chain_id(chain.id()); + + if tx.nonce().is_none() { + tx.set_nonce(provider.get_transaction_count(from).await?); + } + + let legacy = chain.is_legacy(); + + if legacy { + if tx.gas_price().is_none() { + tx.set_gas_price(provider.get_gas_price().await?); + } + } else if tx.max_fee_per_gas().is_none() || tx.max_priority_fee_per_gas().is_none() { + let estimate = provider.estimate_eip1559_fees().await?; + if tx.max_fee_per_gas().is_none() { + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } + if tx.max_priority_fee_per_gas().is_none() { + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + } + + if tx.gas_limit().is_none() { + tx.set_gas_limit(provider.estimate_gas(tx.clone()).await?); + } + + Ok(()) +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 165154da88f63..bcbe837e824e3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,7 +19,7 @@ foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tempo-primitives.workspace = true diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f1c6c435217b7..be566288ef87a 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -129,8 +129,8 @@ where /// If the string represents an untagged amount (e.g. "100") then /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { - Ok(if value.starts_with("0x") { - U256::from_str_radix(value, 16)? + Ok(if value.starts_with("0x") || value.starts_with("0X") { + U256::from_str(value)? } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? .as_uint() @@ -844,6 +844,16 @@ mod tests { assert!(!p.is_sol_test()); } + #[test] + fn parse_ether_value_accepts_hex_prefixed_wei() { + assert_eq!(parse_ether_value("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0X10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0x12").unwrap(), U256::from(0x12)); + assert_eq!(parse_ether_value("0xff").unwrap(), U256::from(0xff)); + assert_eq!(parse_ether_value("100").unwrap(), U256::from(100)); + assert_eq!(parse_ether_value("1ether").unwrap(), U256::from(1000000000000000000u128)); + } + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ac7a8c12eae71..04c2a06a3fa90 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -82,6 +82,7 @@ flate2.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true mpp.workspace = true +foundry-wallets.workspace = true [build-dependencies] chrono.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 78759848fc4bd..2c8e16bccdcc6 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -30,6 +30,7 @@ serde_json.workspace = true chrono.workspace = true revm.workspace = true yansi.workspace = true +comfy-table.workspace = true # Tempo tempo-alloy.workspace = true diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index d751ddba245ba..8b66335445046 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -1,5 +1,6 @@ use super::UIfmt; use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use comfy_table::{Table, TableComponent, presets::UTF8_FULL}; use std::fmt::{self, Write}; /// A piece is a portion of the format string which represents the next part to emit. @@ -407,6 +408,36 @@ fn format_spec<'a>( } } +pub fn console_table_format( + keys: Option<&[&dyn ConsoleFmt]>, + values: &[&dyn ConsoleFmt], +) -> String { + let keys_strings: Vec = match keys { + Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(), + None => (0..values.len()).map(|i| i.to_string()).collect(), + }; + let values_strings: Vec = values.iter().map(|v| v.fmt(FormatSpec::String)).collect(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL); + table.set_style(TableComponent::VerticalLines, '│'); + table.set_style(TableComponent::HeaderLines, '─'); + table.set_style(TableComponent::MiddleHeaderIntersections, '┼'); + table.set_style(TableComponent::LeftHeaderIntersection, '├'); + table.set_style(TableComponent::RightHeaderIntersection, '┤'); + table.set_header(vec!["(index)", "Values"]); + table.remove_style(TableComponent::HorizontalLines); + table.remove_style(TableComponent::MiddleIntersections); + table.remove_style(TableComponent::LeftBorderIntersections); + table.remove_style(TableComponent::RightBorderIntersections); + for i in 0..keys_strings.len().max(values_strings.len()) { + let key = keys_strings.get(i).map(String::as_str).unwrap_or(""); + let value = values_strings.get(i).map(String::as_str).unwrap_or(""); + table.add_row(vec![key, value]); + } + table.to_string() +} + #[cfg(test)] mod tests { use super::*; @@ -610,4 +641,80 @@ mod tests { let call = Logs::Log1(log1); assert_eq!(call.fmt(Default::default()), "foo 42 bar"); } + + #[test] + fn test_console_table_format() { + // auto-indexed, uint256 values + let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)]; + assert_eq!( + console_table_format(None, values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ 0 │ 100 │\n\ + │ 1 │ 200 │\n\ + │ 2 │ 300 │\n\ + └─────────┴────────┘" + ); + + // string keys, uint256 values + // key col expands to fit "charlie123" and value col expands to fit "20000000000000000" + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")]; + let values: &[&dyn ConsoleFmt] = &[ + &U256::from(1), + &U256::from_str("20000000000000000").unwrap(), + &U256::from_str("30000000000").unwrap(), + ]; + assert_eq!( + console_table_format(Some(keys), values), + "┌────────────┬───────────────────┐\n\ + │ (index) │ Values │\n\ + ├────────────┼───────────────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 20000000000000000 │\n\ + │ charlie123 │ 30000000000 │\n\ + └────────────┴───────────────────┘" + ); + + // empty table + assert_eq!( + console_table_format(None, &[]), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + └─────────┴────────┘" + ); + + // more keys than values + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie")]; + let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ charlie │ │\n\ + └─────────┴────────┘" + ); + + // more values than keys + let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")]; + let values: &[&dyn ConsoleFmt] = + &[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ │ 3 │\n\ + │ │ 4 │\n\ + └─────────┴────────┘" + ); + } } diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 89297a8e2c6b5..45ed47263ce32 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod console; -pub use console::{ConsoleFmt, FormatSpec, console_format}; +pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format}; mod dynamic; pub use dynamic::{ diff --git a/crates/common/src/provider/mpp/persist.rs b/crates/common/src/provider/mpp/persist.rs index 6d6781ab4c77b..803c7270e041e 100644 --- a/crates/common/src/provider/mpp/persist.rs +++ b/crates/common/src/provider/mpp/persist.rs @@ -1,243 +1,241 @@ //! Persistent channel storage for MPP sessions. //! -//! Stores open payment channel state in a JSON file at -//! `$TEMPO_HOME/foundry/channels.json` (default: `~/.tempo/foundry/channels.json`). +//! Stores open payment channel state in a SQLite database at +//! `$TEMPO_HOME/channels.db` (default: `~/.tempo/channels.db`). //! This allows channel reuse across process invocations, avoiding the cost of //! opening a new on-chain channel for every `cast` / `forge` command. use alloy_primitives::{Address, B256}; +use foundry_wallets::{Channel, ChannelDb}; use mpp::client::channel_ops::ChannelEntry; -use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - path::PathBuf, + sync::OnceLock, time::{SystemTime, UNIX_EPOCH}, }; use tracing::{debug, warn}; use crate::tempo::tempo_home; -/// Relative path from Tempo home to the Foundry channels file. -const CHANNELS_PATH: &str = "foundry/channels.json"; +/// Process-wide database handle. +fn global_db() -> Option<&'static ChannelDb> { + static DB: OnceLock> = OnceLock::new(); + DB.get_or_init(|| { + let path = tempo_home()?.join("channels.db"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Some(old) = + tempo_home().map(|h| h.join("foundry/channels.json")).filter(|p| p.exists()) + { + warn!( + ?old, + "found old channels.json — this file is no longer used; channels will be re-opened" + ); + } -/// Current schema version. -const SCHEMA_VERSION: u64 = 2; + match ChannelDb::open(&path) { + Ok(db) => { + debug!(?path, "opened channel database"); + Some(db) + } + Err(e) => { + warn!(?path, %e, "failed to open channel database"); + None + } + } + }) + .as_ref() +} -/// On-disk representation of the channel store. -#[derive(Debug, Serialize, Deserialize)] -struct ChannelStore { - version: u64, - #[serde(default)] - channels: HashMap, +/// Reconstruct the composite HashMap key from a persisted `Channel`. +/// +/// Mirrors `SessionProvider::channel_key()` in session.rs. +fn channel_key_from_persisted(ch: &Channel) -> String { + let origin_hash = &alloy_primitives::keccak256(ch.origin.as_bytes()).to_string()[..18]; + format!( + "{}:{}:{}:{}:{}:{}:{}", + origin_hash, + ch.chain_id, + ch.payer, + ch.authorized_signer, + ch.payee, + ch.token, + ch.escrow_contract + ) + .to_lowercase() } -impl Default for ChannelStore { - fn default() -> Self { - Self { version: SCHEMA_VERSION, channels: HashMap::new() } +/// Whether a channel can still be used (active and not fully spent). +fn is_usable(ch: &Channel) -> bool { + if ch.state != "active" { + return false; } + let cumulative: u128 = ch.cumulative_amount.parse().unwrap_or(u128::MAX); + let deposit: u128 = ch.deposit.parse().unwrap_or(0); + cumulative < deposit } -/// A persisted channel entry. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PersistedChannel { - pub channel_id: String, - pub salt: String, - pub escrow_contract: String, - pub chain_id: u64, - pub cumulative_amount: String, - pub deposit: String, - pub status: String, - pub origin: String, - pub created_at: u64, - pub last_used_at: u64, +/// Convert a persisted `Channel` to a `ChannelEntry`. +pub fn to_channel_entry(ch: &Channel) -> Option { + let channel_id: B256 = ch.channel_id.parse().ok()?; + let salt: B256 = ch.salt.parse().ok()?; + let escrow_contract: Address = ch.escrow_contract.parse().ok()?; + let cumulative_amount: u128 = ch.cumulative_amount.parse().ok()?; + + Some(ChannelEntry { + channel_id, + salt, + cumulative_amount, + escrow_contract, + chain_id: ch.chain_id as u64, + opened: ch.state == "active", + }) } -impl PersistedChannel { - /// Convert to an mpp `ChannelEntry` for use in the session provider. - pub fn to_channel_entry(&self) -> Option { - let channel_id: B256 = self.channel_id.parse().ok()?; - let salt: B256 = self.salt.parse().ok()?; - let escrow_contract: Address = self.escrow_contract.parse().ok()?; - let cumulative_amount: u128 = self.cumulative_amount.parse().ok()?; - - Some(ChannelEntry { - channel_id, - salt, - cumulative_amount, - escrow_contract, - chain_id: self.chain_id, - opened: self.status == "active", - }) - } - - /// Create from a `ChannelEntry` with metadata. - pub fn from_channel_entry(entry: &ChannelEntry, deposit: u128, origin: &str) -> Self { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); - - Self { - channel_id: entry.channel_id.to_string(), - salt: entry.salt.to_string(), - escrow_contract: entry.escrow_contract.to_string(), - chain_id: entry.chain_id, - cumulative_amount: entry.cumulative_amount.to_string(), - deposit: deposit.to_string(), - status: if entry.opened { "active" } else { "closed" }.to_string(), - origin: origin.to_string(), - created_at: now, - last_used_at: now, - } - } - - /// Whether this channel can still be used (active and not fully spent). - fn is_usable(&self) -> bool { - if self.status != "active" { - return false; - } - let cumulative: u128 = self.cumulative_amount.parse().unwrap_or(u128::MAX); - let deposit: u128 = self.deposit.parse().unwrap_or(0); - cumulative < deposit +/// Create a `Channel` from a `ChannelEntry` with metadata. +pub fn from_channel_entry( + entry: &ChannelEntry, + deposit: u128, + origin: &str, + payer: &Address, + payee: &Address, + token: &Address, + authorized_signer: &Address, +) -> Channel { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + Channel { + channel_id: entry.channel_id.to_string(), + version: 1, + origin: origin.to_string(), + request_url: String::new(), + chain_id: entry.chain_id as i64, + escrow_contract: entry.escrow_contract.to_string(), + token: token.to_string(), + payee: payee.to_string(), + payer: payer.to_string(), + authorized_signer: authorized_signer.to_string(), + salt: entry.salt.to_string(), + deposit: deposit.to_string(), + cumulative_amount: entry.cumulative_amount.to_string(), + challenge_echo: String::new(), + state: if entry.opened { "active" } else { "closed" }.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: now, + last_used_at: now, } } -/// Returns the path to the channels file. -fn channels_path() -> Option { - tempo_home().map(|home| home.join(CHANNELS_PATH)) -} - -/// Load channels from disk, evicting spent/inactive entries. -pub fn load_channels() -> HashMap { - let Some(path) = channels_path().filter(|p| p.exists()) else { +/// Load channels from database, evicting spent/inactive entries. +pub fn load_channels() -> HashMap { + let Some(db) = global_db() else { return HashMap::new(); }; - let Ok(contents) = std::fs::read_to_string(&path).inspect_err(|e| { - warn!(?path, %e, "failed to read channels file"); - }) else { - return HashMap::new(); - }; - - let Ok(store) = serde_json::from_str::(&contents).inspect_err(|e| { - warn!(?path, %e, "failed to parse channels file, starting fresh"); - }) else { - return HashMap::new(); + let channels = match db.load() { + Ok(channels) => channels, + Err(e) => { + warn!(%e, "failed to load channels from database"); + return HashMap::new(); + } }; - if store.version != SCHEMA_VERSION { - warn!( - version = store.version, - expected = SCHEMA_VERSION, - "channels file version mismatch, starting fresh" - ); - return HashMap::new(); - } - - // Evict spent/inactive entries - let usable: HashMap = - store.channels.into_iter().filter(|(_, ch)| ch.is_usable()).collect(); + let usable: HashMap = channels + .into_iter() + .filter(is_usable) + .map(|ch| { + let key = channel_key_from_persisted(&ch); + (key, ch) + }) + .collect(); debug!(count = usable.len(), "loaded persisted MPP channels"); usable } -/// Save channels to disk. -pub fn save_channels(channels: &HashMap) { - let Some(path) = channels_path() else { +/// Save channels to database. +pub fn save_channels(channels: &HashMap) { + let Some(db) = global_db() else { return; }; - if let Some(parent) = path.parent() - && let Err(e) = std::fs::create_dir_all(parent) - { - warn!(?path, %e, "failed to create channels directory"); - return; + for ch in channels.values() { + if let Err(e) = db.upsert(ch) { + warn!(%e, channel_id = %ch.channel_id, "failed to save channel"); + } } + debug!(count = channels.len(), "saved MPP channels"); +} - let store = ChannelStore { version: SCHEMA_VERSION, channels: channels.clone() }; - - match serde_json::to_string_pretty(&store) { - Ok(json) => { - if let Err(e) = std::fs::write(&path, json) { - warn!(?path, %e, "failed to write channels file"); - } else { - debug!(?path, count = channels.len(), "saved MPP channels"); - } - } - Err(e) => warn!(%e, "failed to serialize channels"), +/// Delete a channel from the database by its channel ID. +pub fn delete_channel_from_db(channel_id: &str) { + let Some(db) = global_db() else { + return; + }; + if let Err(e) = db.delete(channel_id) { + warn!(%e, channel_id, "failed to delete channel from database"); } } /// Look up a usable persisted channel by key. -pub fn find_channel( - channels: &HashMap, - key: &str, -) -> Option { - channels.get(key).filter(|ch| ch.is_usable()).and_then(|ch| ch.to_channel_entry()) +pub fn find_channel(channels: &HashMap, key: &str) -> Option { + channels.get(key).filter(|ch| is_usable(ch)).and_then(to_channel_entry) } -/// Insert or update a channel entry in memory only (no disk write). -/// -/// Use [`upsert_channel`] when you want to persist immediately, or call -/// [`save_channels`] separately after this. +/// Insert or update a channel entry in memory only (no DB write). pub fn upsert_channel_in_memory( - channels: &mut HashMap, + channels: &mut HashMap, key: &str, entry: &ChannelEntry, - deposit: u128, - origin: &str, ) { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; if let Some(existing) = channels.get_mut(key) { existing.cumulative_amount = entry.cumulative_amount.to_string(); existing.last_used_at = now; - existing.status = if entry.opened { "active" } else { "closed" }.to_string(); + existing.state = if entry.opened { "active" } else { "closed" }.to_string(); } else { - channels - .insert(key.to_string(), PersistedChannel::from_channel_entry(entry, deposit, origin)); + warn!(key, "upsert_channel_in_memory called for unknown channel"); } } -/// Insert or update a channel entry and save to disk. -/// -/// When updating an existing entry, `deposit` is ignored (preserved from the -/// original open). When inserting a new entry, `deposit` is recorded. -pub fn upsert_channel( - channels: &mut HashMap, - key: &str, - entry: &ChannelEntry, - deposit: u128, - origin: &str, -) { - upsert_channel_in_memory(channels, key, entry, deposit, origin); - save_channels(channels); -} - #[cfg(test)] mod tests { use super::*; - fn test_channel(status: &str, cumulative: &str, deposit: &str) -> PersistedChannel { - PersistedChannel { + fn test_channel(state: &str, cumulative: &str, deposit: &str) -> Channel { + Channel { channel_id: format!("0x{}", "ab".repeat(32)), - salt: format!("0x{}", "cd".repeat(32)), - escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + version: 1, + origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + request_url: String::new(), chain_id: 42431, - cumulative_amount: cumulative.to_string(), + escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + token: "0x20c0000000000000000000000000000000000000".to_string(), + payee: "0x3333333333333333333333333333333333333333".to_string(), + payer: "0x1111111111111111111111111111111111111111".to_string(), + authorized_signer: "0x1111111111111111111111111111111111111111".to_string(), + salt: format!("0x{}", "cd".repeat(32)), deposit: deposit.to_string(), - status: status.to_string(), - origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + cumulative_amount: cumulative.to_string(), + challenge_echo: String::new(), + state: state.to_string(), + close_requested_at: 0, + grace_ready_at: 0, created_at: 1000, last_used_at: 1000, } } #[test] - fn is_usable() { - assert!(test_channel("active", "5000", "100000").is_usable()); - assert!(!test_channel("active", "100000", "100000").is_usable()); - assert!(!test_channel("active", "200000", "100000").is_usable()); - assert!(!test_channel("closed", "0", "100000").is_usable()); - assert!(!test_channel("closing", "0", "100000").is_usable()); + fn usable() { + assert!(is_usable(&test_channel("active", "5000", "100000"))); + assert!(!is_usable(&test_channel("active", "100000", "100000"))); + assert!(!is_usable(&test_channel("active", "200000", "100000"))); + assert!(!is_usable(&test_channel("closed", "0", "100000"))); + assert!(!is_usable(&test_channel("closing", "0", "100000"))); } #[test] @@ -251,8 +249,12 @@ mod tests { opened: true, }; - let persisted = PersistedChannel::from_channel_entry(&entry, 100_000, "https://rpc.test"); - let restored = persisted.to_channel_entry().expect("should parse back"); + let payer = Address::random(); + let payee = Address::random(); + let token = Address::random(); + let persisted = + from_channel_entry(&entry, 100_000, "https://rpc.test", &payer, &payee, &token, &payer); + let restored = to_channel_entry(&persisted).expect("should parse back"); assert_eq!(restored.channel_id, entry.channel_id); assert_eq!(restored.salt, entry.salt); @@ -262,46 +264,6 @@ mod tests { assert!(restored.opened); } - #[test] - fn load_evicts_and_handles_edge_cases() { - let dir = tempfile::tempdir().unwrap(); - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - - let store = ChannelStore { - version: SCHEMA_VERSION, - channels: HashMap::from([ - ("active".into(), test_channel("active", "1000", "100000")), - ("spent".into(), test_channel("active", "100000", "100000")), - ("closed".into(), test_channel("closed", "0", "100000")), - ]), - }; - let json = serde_json::to_string(&store).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), &json).unwrap(); - - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - let loaded = load_channels(); - unsafe { std::env::remove_var("TEMPO_HOME") }; - - assert_eq!(loaded.len(), 1); - assert!(loaded.contains_key("active")); - } - - #[test] - fn load_missing_and_wrong_version() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - assert!(load_channels().is_empty()); - - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), r#"{"version": 999, "channels": {}}"#) - .unwrap(); - assert!(load_channels().is_empty()); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } - #[test] fn find_channel_filters_unusable() { let mut channels = HashMap::new(); @@ -312,34 +274,4 @@ mod tests { assert!(find_channel(&channels, "spent").is_none()); assert!(find_channel(&channels, "missing").is_none()); } - - #[test] - fn upsert_inserts_and_updates() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - - let mut channels = HashMap::new(); - let entry = ChannelEntry { - channel_id: B256::random(), - salt: B256::random(), - cumulative_amount: 1000, - escrow_contract: Address::random(), - chain_id: 42431, - opened: true, - }; - - upsert_channel(&mut channels, "key1", &entry, 100_000, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "1000"); - assert_eq!(channels["key1"].deposit, "100000"); - let created_at = channels["key1"].created_at; - - let mut updated = entry.clone(); - updated.cumulative_amount = 5000; - upsert_channel(&mut channels, "key1", &updated, 0, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "5000"); - assert_eq!(channels["key1"].deposit, "100000"); - assert_eq!(channels["key1"].created_at, created_at); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } } diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 5996c933218d2..334166b844613 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -6,8 +6,9 @@ //! `eth_getTransactionCount`. This avoids the chicken-and-egg problem when //! the RPC endpoint is itself 402-gated. -use super::persist::{self, PersistedChannel}; +use super::persist; use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use foundry_wallets::Channel; use mpp::{ client::{ PaymentProvider, @@ -42,7 +43,7 @@ static GLOBAL_CHANNELS: OnceLock>> = O /// /// Using a single map ensures saves from different origins don't clobber /// each other's state. -static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); +static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); /// Tracks uncommitted channel state from the most recent payment. /// @@ -86,7 +87,7 @@ pub struct SessionProvider { default_deposit: Option, channels: Arc>>, key_provisioned: Arc>, - persisted: Arc>>, + persisted: Arc>>, /// Tracks uncommitted open/top-up state for deferred persistence. pending: Arc>>, /// Chain ID from the key entry in `keys.toml` that was used to initialize @@ -128,10 +129,10 @@ impl SessionProvider { map.entry(origin.clone()) .or_insert_with(|| { // Hydrate only channels belonging to this origin. - let mut channels = HashMap::new(); + let mut channels: HashMap = HashMap::new(); for (key, ch) in persisted.lock().unwrap().iter() { if ch.origin == origin - && let Some(entry) = ch.to_channel_entry() + && let Some(entry) = persist::to_channel_entry(ch) { channels.insert(key.clone(), entry); } @@ -209,16 +210,16 @@ impl SessionProvider { // Lock order: channels → persisted (consistent with pay_session) let mut channels = self.channels.lock().unwrap(); let mut persisted = self.persisted.lock().unwrap(); - let keys_to_remove: Vec = persisted + let keys_to_remove: Vec<(String, String)> = persisted .iter() .filter(|(_, ch)| ch.origin == *origin) - .map(|(k, _)| k.clone()) + .map(|(k, ch): (&String, &Channel)| (k.clone(), ch.channel_id.clone())) .collect(); - for key in &keys_to_remove { + for (key, channel_id) in &keys_to_remove { channels.remove(key); persisted.remove(key); + persist::delete_channel_from_db(channel_id); } - persist::save_channels(&persisted); } /// Mark whether the access key has been provisioned on-chain. @@ -685,13 +686,7 @@ impl SessionProvider { // confirms acceptance. let updated_entry = ChannelEntry { cumulative_amount: new_cumulative, ..entry }; let mut persisted = self.persisted.lock().unwrap(); - persist::upsert_channel_in_memory( - &mut persisted, - &key, - &updated_entry, - 0, - &self.origin, - ); + persist::upsert_channel_in_memory(&mut persisted, &key, &updated_entry); drop(persisted); // Track the voucher so we can roll back cumulative_amount @@ -727,9 +722,18 @@ impl SessionProvider { // Update in-memory state but defer disk persistence until server confirms. self.channels.lock().unwrap().insert(key.clone(), entry.clone()); + let authorized_signer = self.authorized_signer.unwrap_or(payer); self.persisted.lock().unwrap().insert( key.clone(), - persist::PersistedChannel::from_channel_entry(&entry, deposit, &self.origin), + persist::from_channel_entry( + &entry, + deposit, + &self.origin, + &payer, + &payee, + ¤cy, + &authorized_signer, + ), ); *self.pending.lock().unwrap() = Some(PendingAction::Open { key }); Ok(build_credential(challenge, payload, chain_id, payer)) diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json index 54e6d46dff349..f275f471087ee 100644 --- a/crates/evm/abi/src/Console.json +++ b/crates/evm/abi/src/Console.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index 2e28fece21e42..d0b32ff410941 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -16,12 +16,15 @@ def main(): # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() sig_strings = re.findall( - r'"(log.*?)"', + r'"((?:log|table).*?)"', console_sol, ) raw_sigs = [s.strip().strip('"') for s in sig_strings] sigs = [ - s.replace("string", "string memory").replace("bytes)", "bytes memory)") + re.sub(r"(\w+\[\])", r"\1 memory", s) + .replace("string,", "string memory,") + .replace("string)", "string memory)") + .replace("bytes)", "bytes memory)") for s in raw_sigs ] sigs = list(set(sigs)) @@ -38,6 +41,7 @@ def main(): ) combined = json.loads(r.stdout.strip()) abi = combined["contracts"][":HardhatConsole"]["abi"] + open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 26acbe56a97cb..c591b9426bf9e 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -50,7 +50,9 @@ impl LogCollector { fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.push_msg(&decoded.fmt(Default::default())); + for line in decoded.fmt(Default::default()).lines() { + self.push_msg(line); + } Ok(()) } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 9a7a237855b97..ecdc13ded67cf 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -51,7 +51,7 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } alloy-chains.workspace = true alloy-consensus.workspace = true diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 122020571ad24..a48d2a9350c70 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -9,7 +9,7 @@ use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as Alloy use alloy_signer::{Signature, Signer}; use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; +use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, @@ -33,10 +33,12 @@ use foundry_config::{ }, merge_impl_figment_convert, }; -use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use foundry_wallets::{ + BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner, +}; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; -use tempo_alloy::TempoNetwork; +use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN}; merge_impl_figment_convert!(CreateArgs, build, eth); @@ -101,14 +103,29 @@ pub struct CreateArgs { #[command(flatten)] retry: RetryArgs, + + /// Browser wallet options + #[command(flatten)] + browser: BrowserWalletOpts, } impl CreateArgs { /// Executes the command to create a contract - pub async fn run(self) -> Result<()> { + pub async fn run(mut self) -> Result<()> { let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?; - if tempo_access_key.is_some() || self.tx.tempo.is_tempo() { + // Resolve chain early so we can dispatch to the correct network type. + if self.chain_id().is_none() { + let config = self.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let chain_id = provider.get_chain_id().await?; + self.eth.etherscan.chain = Some(chain_id.into()); + } + + if tempo_access_key.is_some() + || self.tx.tempo.is_tempo() + || self.chain_id().is_some_and(|c| c.is_tempo()) + { self.run_generic::(signer, tempo_access_key).await } else { self.run_generic::(signer, None).await @@ -185,17 +202,29 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chain_id().await? - }; - // Whether to broadcast the transaction or not let dry_run = !self.broadcast; - if self.unlocked { + // Launch browser signer if `--browser` flag is set + let browser = self.browser.run::().await?; + + if let Some(browser) = browser { + // Deploy with browser wallet + let deployer_address = browser.address(); + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + None, + Some(browser), + ) + .await + } else if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( @@ -203,12 +232,12 @@ impl CreateArgs { bin, params, provider, - chain_id, sender, config.transaction_timeout, id, dry_run, None, + None, ) .await } else if let Some(ak) = access_key { @@ -223,12 +252,12 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer_address, config.transaction_timeout, id, dry_run, Some((signer, ak)), + None, ) .await } else { @@ -246,20 +275,20 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer, config.transaction_timeout, id, dry_run, None, + None, ) .await } } - /// Returns the provided chain id, if any. - fn chain_id(&self) -> Option { - self.eth.etherscan.chain.map(|chain| chain.id()) + /// Returns the resolved chain, if any. + const fn chain_id(&self) -> Option { + self.eth.etherscan.chain } /// Ensures the verify command can be executed. @@ -271,7 +300,6 @@ impl CreateArgs { async fn verify_preflight_check( &self, constructor_args: Option, - chain: u64, id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, @@ -287,7 +315,7 @@ impl CreateArgs { num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), - chain: Some(chain.into()), + chain: self.chain_id(), }, rpc: Default::default(), flatten: false, @@ -311,7 +339,7 @@ impl CreateArgs { // ETHERSCAN_API_KEY value set. let config = verify.load_config()?; verify.etherscan.key = - config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key); let context = verify.resolve_context().await?; @@ -327,17 +355,19 @@ impl CreateArgs { bin: BytecodeObject, args: Vec, provider: P, - chain: u64, deployer_address: Address, timeout: u64, id: ArtifactId, dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, + browser_signer: Option>, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, N::ReceiptResponse: serde::Serialize, { + let chain = self.chain_id().context("chain ID not resolved")?; + let bin = bin.into_bytes().unwrap_or_default(); if bin.is_empty() { eyre::bail!("no bytecode found in bin object for {}", self.contract.name) @@ -349,22 +379,31 @@ impl CreateArgs { let is_args_empty = args.is_empty(); let mut deployer = - factory.deploy_tokens(args.clone(), self.tx.tempo.fee_token).context("failed to deploy contract").map_err(|e| { + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { e } })?; - let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + let is_legacy = self.tx.legacy || chain.is_legacy(); deployer.tx.set_from(deployer_address); - deployer.tx.set_chain_id(chain); + deployer.tx.set_chain_id(chain.id()); // `to` field must be set explicitly, cannot be None. if deployer.tx.to().is_none() { deployer.tx.set_create(); } + // If Tempo chain fee token must be set + if chain.is_tempo() { + if let Some(fee_token) = self.tx.tempo.fee_token { + deployer.tx.set_fee_token(fee_token); + } else { + deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); + } + } + // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); @@ -422,7 +461,7 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + self.verify_preflight_check(constructor_args.clone(), &id).await?; } if dry_run { @@ -452,7 +491,31 @@ impl CreateArgs { } // Deploy the actual contract - let (deployed_contract, receipt) = if let Some((signer, ak)) = tempo_keychain { + let (deployed_contract, receipt) = if let Some(browser) = browser_signer { + // Browser wallet signs and sends the transaction + let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?; + + // Wait for the transaction to be confirmed, then fetch the receipt. + provider + .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash)) + .await? + .await?; + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?; + + if !receipt.status() { + eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}"); + } + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else if let Some((signer, ak)) = tempo_keychain { // Tempo keychain mode: sign with access key provisioning and send raw let raw_tx = deployer .tx @@ -518,7 +581,7 @@ impl CreateArgs { no_auto_detect: false, use_solc: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) }, rpc: Default::default(), flatten: false, force: false, @@ -681,7 +744,6 @@ impl + Clone> DeploymentTxFactory { pub fn deploy_tokens( self, params: Vec, - fee_token: Option
, ) -> Result, ContractDeploymentError> where N::TransactionRequest: FoundryTransactionBuilder, @@ -703,9 +765,6 @@ impl + Clone> DeploymentTxFactory { // create the tx object. Since we're deploying a contract, `to` is `None` let mut tx = N::TransactionRequest::default(); tx.set_input(data); - if let Some(fee_token) = fee_token { - tx.set_fee_token(fee_token); - } Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } @@ -763,7 +822,7 @@ mod tests { "--chain-id", "9999", ]); - assert_eq!(args.chain_id(), Some(9999)); + assert_eq!(args.chain_id().map(|c| c.id()), Some(9999)); } #[test] diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 0c95841d1e653..245ba6e5b78e2 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -7,7 +7,7 @@ use syn::{ pub fn console_fmt(input: &DeriveInput) -> TokenStream { let name = &input.ident; let tokens = match &input.data { - Data::Struct(s) => derive_struct(s), + Data::Struct(s) => derive_struct(s, name), Data::Enum(e) => derive_enum(e), Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; @@ -18,8 +18,8 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { } } -fn derive_struct(s: &DataStruct) -> TokenStream { - let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); +fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream { + let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new())); quote! { fn fmt(&self, _spec: FormatSpec) -> String { #imp @@ -27,7 +27,7 @@ fn derive_struct(s: &DataStruct) -> TokenStream { } } -fn impl_struct(s: &DataStruct) -> Option { +fn impl_struct(s: &DataStruct, name: &Ident) -> Option { if s.fields.is_empty() { return None; } @@ -36,13 +36,49 @@ fn impl_struct(s: &DataStruct) -> Option { return None; } + let members = s.fields.members().collect::>(); let fields = s.fields.iter().collect::>(); + + // Detect table call structs: name must start with "table" (from the ABI function name) and + // all fields must be Vec types (Solidity arrays). Both conditions together prevent + // accidental table rendering for unrelated structs that happen to have Vec fields. + let is_table = name.to_string().starts_with("table") + && !fields.is_empty() + && fields.iter().all(|f| match &f.ty { + Type::Path(path) => path.path.segments.last().is_some_and(|seg| seg.ident == "Vec"), + _ => false, + }); + if is_table { + let member_ref = |m: &Member| match m { + Member::Named(ident) => quote!(&self.#ident), + Member::Unnamed(idx) => quote!(&self.#idx), + }; + let imp = if members.len() == 1 { + let vals = member_ref(&members[0]); + quote! { + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(None, &values) + } + } else { + let keys = member_ref(&members[0]); + let vals = member_ref(&members[1]); + quote! { + let keys: ::std::vec::Vec<&dyn ConsoleFmt> = + (#keys).iter().map(|v| v as &dyn ConsoleFmt).collect(); + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(Some(&keys), &values) + } + }; + return Some(imp); + } + let first_ty = match &fields.first().unwrap().ty { Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), _ => String::new(), }; - let members = s.fields.members().collect::>(); let args: Punctuated = members .into_iter() .map(|member| match member { diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 4146e07d2248b..acdcbbdbf95ea 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -23,7 +23,7 @@ foundry-evm-networks.workspace = true alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } foundry-linking.workspace = true forge-script-sequence.workspace = true diff --git a/npm/bun.lock b/npm/bun.lock index e8ef087be9d50..d9e8fed5ce479 100644 --- a/npm/bun.lock +++ b/npm/bun.lock @@ -1,12 +1,13 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3", + "typescript": "^6.0.2", }, }, }, @@ -35,7 +36,7 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], - "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], @@ -45,8 +46,12 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "bun-types/@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } } diff --git a/npm/scripts/publish.mjs b/npm/scripts/publish.mjs index 0f582c8ebc555..deee29e46c92f 100644 --- a/npm/scripts/publish.mjs +++ b/npm/scripts/publish.mjs @@ -7,17 +7,12 @@ import { colors } from '#const.mjs' const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' -const NPM_TOKEN = Bun.env.NPM_TOKEN -if (!NPM_TOKEN) throw new Error('NPM_TOKEN is required') - main().catch(error => { console.error(error) process.exit(1) }) async function main() { - const npmToken = Bun.env.NPM_TOKEN - if (!npmToken) throw new Error('NPM_TOKEN is required') const inputPath = Bun.argv[2] if (!inputPath) throw new Error('Package path is required') @@ -114,11 +109,6 @@ async function setPackageVersion(packagePath, version) { console.info(colors.green, 'Setting package version:', version) const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` .cwd(packagePath) - .env({ - ...Bun.env, - ...process.env, - NPM_TOKEN - }) .quiet() .nothrow() diff --git a/npm/tsconfig.json b/npm/tsconfig.json index ca8e3c92f2daa..a7a0093d7ee56 100644 --- a/npm/tsconfig.json +++ b/npm/tsconfig.json @@ -2,7 +2,6 @@ "schema": "https://json.schemastore.org/tsconfig.json", "compilerOptions": { "strict": true, - "baseUrl": ".", "noEmit": true, "allowJs": true, "checkJs": true, From 3299d9f18eb681d3017006092eb147e09f365ac2 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:37:39 +0700 Subject: [PATCH 338/391] Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/anvil/src/cmd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index fb75529b82908..edababc83aa7f 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -654,8 +654,8 @@ impl AnvilEvmArgs { if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { let mut resolved_urls = Vec::new(); for fork_url in &self.fork_url { - let mut endpoints = config.rpc_endpoints.clone().resolved(); - if let Some(endpoint) = endpoints.remove(&fork_url.url) { + if let Some(endpoint) = config.rpc_endpoints.get(&fork_url.url) { + let endpoint = endpoint.clone().resolve(); // Alias matched — expand all URLs from the endpoint config match endpoint.all_urls() { Ok(urls) => { From abda75815d62bd6bc320704d63c581ee5f828048 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:54:51 +0700 Subject: [PATCH 339/391] chore(deps): bump docker/metadata-action from 5 to 6 (#415) Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/Docker.yml | 2 +- .github/workflows/docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 5978701aade87..53b7d1ed0ac7c 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -35,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5978701aade87..53b7d1ed0ac7c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | From eea1bf7634e812a29568fd9c607254ad1bf34537 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:57:41 +0700 Subject: [PATCH 340/391] chore(deps): bump actions/configure-pages from 5 to 6 (#428) Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 5 to 6. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 0ba82305f82b2..6026a64f84d28 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -32,7 +32,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: From 5d0847a77f040c0f8e1710be493b67c201021b5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:59:55 +0700 Subject: [PATCH 341/391] chore(deps): bump actions/upload-pages-artifact from 3 to 5 (#430) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 6026a64f84d28..fcfbbbbec7948 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Pages uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: # Upload entire repository path: '.' From ac77b5cf9aea007bafa37ea71695961d5ee4ea4f Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:04:46 +0000 Subject: [PATCH 342/391] Potential fix for code scanning alert no. 108: Artifact poisoning (#431) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/npm.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 4ef7c4ac04dfc..938e85c9330be 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -113,6 +113,22 @@ jobs: with: persist-credentials: false + - name: Validate Trusted Artifact Source + run: | + set -euo pipefail + + if [[ "${{ github.event_name }}" == "workflow_run" ]]; then + [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]] || { echo "ERROR: Upstream workflow_run did not succeed."; exit 1; } + [[ "${{ github.event.workflow_run.head_repository.full_name }}" == "${{ github.repository }}" ]] || { echo "ERROR: Upstream run repository mismatch."; exit 1; } + [[ "${{ github.event.workflow_run.event }}" != "pull_request" ]] || { echo "ERROR: Refusing artifacts from pull_request-triggered upstream runs."; exit 1; } + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + [[ -n "${{ inputs.run_id }}" ]] || { echo "ERROR: inputs.run_id is required for workflow_dispatch."; exit 1; } + [[ "${{ inputs.run_id }}" =~ ^[0-9]+$ ]] || { echo "ERROR: inputs.run_id must be numeric."; exit 1; } + else + echo "ERROR: Unsupported event: ${{ github.event_name }}" + exit 1 + fi + - name: Set Isolated Artifact Directory id: paths run: | From 412f874110561d46c32743479f954bf346f8686b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:21:08 +0000 Subject: [PATCH 343/391] 7 bug release workflow failed (#470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#345) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 110: Uncontrolled data used in path expression (#347) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 102: Artifact poisoning (#351) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md (#350) * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 109: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * Potential fix for code scanning alert no. 102: Artifact poisoning (#354) * Potential fix for code scanning alert no. 102: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#357) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * refactor(common): make ProviderBuilder generic over Network #13250 (#361) * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg --------- Signed-off-by: dependabot[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Matt D Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Tim Beiko Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#373) * Potential fix for code scanning alert no. 108: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Forge/master (#376) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar Co-authored-by: googleworkspace-bot * chore(deps): bump the cargo group across 1 directory with 9 updates (#377) Bumps the cargo group with 3 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing), [keccak](https://github.com/RustCrypto/sponges) and [slab](https://github.com/tokio-rs/slab). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `time` from 0.3.41 to 0.3.47 - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.41...v0.3.47) Updates `alloy-dyn-abi` from 1.3.0 to 1.5.7 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.3.0...v1.5.7) Updates `bytes` from 1.10.1 to 1.11.1 - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.1...v1.11.1) Updates `keccak` from 0.1.5 to 0.1.6 - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) Updates `lru` from 0.12.5 to 0.16.3 - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.3) Updates `protobuf` from 3.3.0 to 3.7.2 Updates `ruint` from 1.15.0 to 1.17.2 - [Release notes](https://github.com/recmo/uint/releases) - [Changelog](https://github.com/recmo/uint/blob/main/CHANGELOG.md) - [Commits](https://github.com/recmo/uint/compare/v1.15.0...v1.17.2) Updates `slab` from 0.4.10 to 0.4.12 - [Release notes](https://github.com/tokio-rs/slab/releases) - [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/slab/compare/v0.4.10...v0.4.12) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: time dependency-version: 0.3.47 dependency-type: direct:production dependency-group: cargo - dependency-name: alloy-dyn-abi dependency-version: 1.5.7 dependency-type: direct:production dependency-group: cargo - dependency-name: bytes dependency-version: 1.11.1 dependency-type: direct:production dependency-group: cargo - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect dependency-group: cargo - dependency-name: lru dependency-version: 0.16.3 dependency-type: indirect dependency-group: cargo - dependency-name: protobuf dependency-version: 3.7.2 dependency-type: indirect dependency-group: cargo - dependency-name: ruint dependency-version: 1.17.2 dependency-type: indirect dependency-group: cargo - dependency-name: slab dependency-version: 0.4.12 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create static.yml (#381) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Hardhat project (#389) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot * Change branch trigger from 'main' to 'master' (#403) https://github.com/Dargon789/hardhat-project/issues/2073 https://github.com/Dargon789/hardhat-project/pull/2078/commits/a98e69d5803ec208947f592c91749f248e2dd07c Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revise Foundry benchmark results and system info (#407) Updated benchmark results for Foundry versions and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revise Foundry benchmark results and system info (#408) Updated benchmark results for Foundry versions v1.3.6 and v1.4.0-rc1, including new test data and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "benches\LATEST.md (#350)" (#409) This reverts commit 71a26eea6cce80052b3a82da9aa7c9aa0d987b09. * Update Docker.yml * chore(deps): bump DeterminateSystems/update-flake-lock (#419) Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from e80a657d7603606be0c69b117cfdc240f1e6af88 to ff43f160ef7014ae1a1fd85699fb6a44f436135b. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/e80a657d7603606be0c69b117cfdc240f1e6af88...ff43f160ef7014ae1a1fd85699fb6a44f436135b) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: ff43f160ef7014ae1a1fd85699fb6a44f436135b dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/config.yml (#420) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Docker.yml * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (foundry-rs#14398 (#464) * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp * chore: bump alloy 2.0.1 (#14417) --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: googleworkspace-bot Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> --- .cargo/config.toml | 6 +- .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 ++ .deps/remix-tests/remix_tests.sol | 225 ++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 39 ++ .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/scripts/tempo-mpp.sh | 18 +- .github/workflows/Docker.yml | 62 +++ .github/workflows/apisec-scan.yml | 29 ++ .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yml | 6 +- .github/workflows/codeql.yml | 92 ++++ .github/workflows/crate-checks.yml | 2 +- .github/workflows/deploy.yml | 27 ++ .github/workflows/docker.yml | 62 +++ .github/workflows/docs.yml | 2 +- .github/workflows/google.yml | 117 +++++ .github/workflows/nix.yml | 2 +- .github/workflows/npm.yml | 48 +- .github/workflows/release.yml | 6 +- .github/workflows/snyk-container.yml | 55 +++ .github/workflows/static.yml | 43 ++ .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- .gitmodules | 6 + Cargo.lock | 363 +++++++++------- Cargo.toml | 60 +-- Makefile | 41 +- benches/src/lib.rs | 16 +- counter/.github/workflows/test.yml | 43 ++ counter/.gitignore | 14 + counter/README.md | 66 +++ counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 + counter/src/Counter.sol | 14 + counter/test/Counter.t.sol | 24 + crates/cast/Cargo.toml | 2 +- crates/cast/src/args.rs | 2 +- crates/cast/src/call_spec.rs | 10 +- crates/cast/src/cmd/erc20.rs | 103 ++++- crates/cast/tests/cli/main.rs | 28 ++ .../cheatcodes/assets/cheatcodes.schema.json | 2 +- crates/cheatcodes/spec/src/lib.rs | 4 +- crates/cheatcodes/src/fs.rs | 11 +- crates/cli/Cargo.toml | 2 +- crates/cli/src/utils/mod.rs | 14 +- crates/cli/src/utils/suggestions.rs | 3 +- crates/common/Cargo.toml | 1 + crates/common/fmt/Cargo.toml | 1 + crates/common/fmt/src/console.rs | 107 +++++ crates/common/fmt/src/lib.rs | 2 +- crates/common/src/contracts.rs | 2 +- crates/common/src/provider/mpp/persist.rs | 410 ++++++++---------- crates/common/src/provider/mpp/session.rs | 38 +- crates/config/Cargo.toml | 4 + crates/config/assets/config.schema.json | 6 + crates/config/spec/Cargo.toml | 28 ++ crates/config/spec/src/lib.rs | 64 +++ crates/config/src/lib.rs | 33 +- crates/doc/src/parser/comment.rs | 6 + crates/doc/src/writer/buf_writer.rs | 20 +- crates/evm/abi/src/Console.json | 2 +- crates/evm/abi/src/console.py | 8 +- crates/evm/evm/src/inspectors/logs.rs | 4 +- crates/forge/Cargo.toml | 3 +- crates/forge/src/cmd/config.rs | 2 +- crates/forge/src/cmd/create.rs | 127 ++++-- crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 ++++++ crates/macros/src/console_fmt.rs | 46 +- crates/script/Cargo.toml | 2 +- crates/test-utils/src/script.rs | 29 +- crates/test-utils/src/util.rs | 104 ++++- npm/bun.lock | 15 +- npm/scripts/publish.mjs | 10 - npm/scripts/stage-from-artifact.mjs | 28 +- npm/src/const.mjs | 30 +- npm/tsconfig.json | 1 - 82 files changed, 2442 insertions(+), 603 deletions(-) create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .github/workflows/static.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/config/assets/config.schema.json create mode 100644 crates/config/spec/Cargo.toml create mode 100644 crates/config/spec/src/lib.rs create mode 100644 crates/lint/src/linter.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 1ca035a75d78c..ca844fb33e15b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,12 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" bless-lints = "test -p forge --test ui -- --bless" +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" + # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..53a505774ac88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh index 1a98b1ed9c494..0d34c4a3b5fa5 100755 --- a/.github/scripts/tempo-mpp.sh +++ b/.github/scripts/tempo-mpp.sh @@ -110,7 +110,7 @@ BLOCK2=$("$CAST" block-number --rpc-url "$RPC_MPP") AFTER2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') SPENT2=$((BEFORE2 - AFTER2)) echo "Block: $BLOCK2" -echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/foundry/channels.json)" +echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/channels.db)" # 6. forge script via MPP echo "" @@ -129,9 +129,9 @@ contract MppCheck is Script { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" script "$TMPDIR/script/Mpp.s.sol" --rpc-url "$RPC_MPP" --root "$TMPDIR" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 7. forge test with vm.createSelectFork via MPP @@ -149,15 +149,15 @@ contract MppForkTest is Test { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" test --match-test test_fork_via_mpp --root "$TMPDIR" -vvv -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 8. anvil fork via MPP echo "" echo "=== 8. anvil --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$ANVIL" --fork-url "$RPC_MPP" --port 8555 --silent & ANVIL_PID=$! for _ in $(seq 1 30); do @@ -167,15 +167,15 @@ done echo "chain-id: $("$CAST" chain-id --rpc-url http://localhost:8555)" kill $ANVIL_PID 2>/dev/null wait $ANVIL_PID 2>/dev/null -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 9. chisel fork via MPP echo "" echo "=== 9. chisel --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo 'block.number' | "$CHISEL" --fork-url "$RPC_MPP" 2>&1 | grep -E "Decimal|Type" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" echo "" diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 96af1f4ab9a1e..8465874854551 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -61,7 +61,7 @@ jobs: printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" - name: Build benchmark binary - run: cargo build --release --bin foundry-bench + run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c332240fea036..53d009de4c442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo test --workspace --doc + - run: cargo test --workspace --doc --locked typos: runs-on: depot-ubuntu-latest @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0 + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 shellcheck: runs-on: depot-ubuntu-latest @@ -92,7 +92,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --all-features --locked env: RUSTFLAGS: -Dwarnings diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index 781d63759045e..b2eb6261efd63 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -34,7 +34,7 @@ jobs: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo hack check + - run: cargo hack check --locked issue: name: Open an issue diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a8a3dcbb8c7d3..288d9d127592f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: cargo doc --workspace --all-features --no-deps --document-private-items --locked env: RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - name: Setup Pages diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0a18c99a41f82..f6e068eeec62a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: DeterminateSystems/update-flake-lock@e80a657d7603606be0c69b117cfdc240f1e6af88 # main + - uses: DeterminateSystems/update-flake-lock@ff43f160ef7014ae1a1fd85699fb6a44f436135b # main with: pr-title: "Update flake.lock" pr-labels: | diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 9b9b798435c90..4ef7c4ac04dfc 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -31,6 +31,7 @@ jobs: contents: read actions: read id-token: write + environment: release name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: @@ -151,9 +152,6 @@ jobs: id: release-version working-directory: ./npm env: - PROVENANCE: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | @@ -224,8 +222,6 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail @@ -233,9 +229,46 @@ jobs: PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' + # Basic validation of matrix-derived values to avoid path manipulation + case "$TOOL" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid TOOL value: $TOOL" >&2; exit 1 ;; + esac + case "$PLATFORM" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid PLATFORM value: $PLATFORM" >&2; exit 1 ;; + esac + case "$ARCH" in + (*[!a-zA-Z0-9_-]*|'') echo "ERROR: Invalid ARCH value: $ARCH" >&2; exit 1 ;; + esac + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + echo "Preparing to publish package from: $PACKAGE_DIR" + + if [[ ! -d "$PACKAGE_DIR" ]]; then + echo "ERROR: Package directory does not exist: $PACKAGE_DIR" >&2 + exit 1 + fi + + # Resolve to an absolute path and ensure it stays within ./@foundry-rs + ABS_PACKAGE_DIR="$(realpath "$PACKAGE_DIR")" + ABS_EXPECTED_ROOT="$(realpath "./@foundry-rs")" + case "$ABS_PACKAGE_DIR" in + "$ABS_EXPECTED_ROOT"/*) ;; + *) + echo "ERROR: Resolved package directory is outside expected root:" >&2 + echo " ABS_PACKAGE_DIR=$ABS_PACKAGE_DIR" >&2 + echo " ABS_EXPECTED_ROOT=$ABS_EXPECTED_ROOT" >&2 + exit 1 + ;; + esac + ls -la "$PACKAGE_DIR" + # Minimal sanity check: require a package.json before publishing + if [[ ! -f "$PACKAGE_DIR/package.json" ]]; then + echo "ERROR: package.json not found in $PACKAGE_DIR; refusing to publish." >&2 + exit 1 + fi + bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" @@ -245,12 +278,11 @@ jobs: contents: read actions: read id-token: write + environment: release needs: publish-arch name: Publish Meta Package runs-on: ubuntu-latest env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} steps: - name: Checkout @@ -286,6 +318,4 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - NPM_TOKEN: ${{ env.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ad324e7ef9c1..52b33787de3f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,7 +179,7 @@ jobs: shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $RUST_PROFILE --bins + flags=(--locked --target $TARGET --profile $RUST_PROFILE --bins --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. @@ -279,7 +279,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -294,7 +294,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..0ba82305f82b2 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index af4415b1e8ba1..c0e4fcb5f3263 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -50,7 +50,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --no-fail-fast + run: cargo nextest run --locked --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. issue: diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index afd91d3d72c05..37c3edc9b916a 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -54,7 +54,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast + run: cargo nextest run --locked --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. issue-isolate: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9ccb850dbc5c..7c79cd266f152 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,7 +97,7 @@ jobs: run: pip --version && pip install vyper==0.4.3 - name: Foundry test cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/.foundry/cache @@ -121,4 +121,4 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run ${{ matrix.flags }} + run: cargo nextest run --locked ${{ matrix.flags }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index d15f7c092e642..b3741acb52d7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,14 +84,14 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -116,14 +116,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbe4e5e9107bf6854e7550b666ca654ff2027eabf8153913e2e31ac4b089779" +checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -143,23 +143,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88fc7bbfb98cf5605a35aadf0ba43a7d9f1608d6f220d05e4fbd5144d3b0b625" +checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c16fa30b623e40a5b216da00f3b61870f5cbe863b59816ac1ecc2489515a40" +checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9252074d8b7028bae6844827f51ed8f8a036367604dce9d30d16cedfcc53577" +checksum = "250ba1168b8a049185a68c4dfa7f2a6a4046bd26fcc8c68632caeb216a5e12dc" dependencies = [ "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", ] @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb4919fa34b268842f434bfafa9c09136ab7b1a87ce0dd40a61befa35b5408c" +checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -310,7 +310,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "auto_impl", "borsh", "c-kzg", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbf60d85481b12fcc06e29fbe14584fdc5743a34ba7719037f210decd020761" +checksum = "34a8c1330ad33c95b5958573bca9a1ad0b419a51d76bb4c521556fbba8539b8d" dependencies = [ "alloy-contract", "alloy-primitives", @@ -344,7 +344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f107d0e588e5d25fcf2db216390445d5804b875a22a419407ad0389b925bb4d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -359,13 +359,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e111e22c1a2133e9ebfd9051ea0eaf63559594d2f50d43cbc6762fbb95fc3c2" +checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "borsh", "serde", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b6af6f374c1eeef8ab8dc26232cd440db167322a4207a3debd3d1ee565ca47" +checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -415,19 +415,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a3f5a7f3678b71d33fcc45b714fab8928dbc647d5aff2145e72032d5c849bb" +checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-sol-types", "async-trait", @@ -441,14 +441,14 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb50dc1fb0e0b2c8748d5bee1aa7acdd18f9e036311bc93a71d97be624030317" +checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] @@ -458,7 +458,7 @@ version = "0.30.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -513,13 +513,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ba5468f78c8893be2d68a7f2fda61753336e5653f006af19781001b5f99e6c" +checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffcefb5d3391a320eadb95d398e4135f8cc35c7bf29a6bdb357eadcfc5ee5638" +checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222fd4efff0fb9a25184684742c44fe9fa9a16c4ab5bf97583e71c86598ef8f0" +checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974df1e56405c27cb8242381f45d8b212ba9df5006046ccf704764a2a4634366" +checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -640,44 +640,44 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06bc10b0dca4f5bfc3cd30ed46eab5d651b5bb2cd300d683bdcdf5d2bfe6e82c" +checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949c0f16a94ae33cdb1139b8dbf9e34d7f26ebfe97962e2a4d620b5f65f48fe4" +checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8f7fa8ca056bb797a368aeed329e6ace6b62ee4271432ac36ab8ae87a5e60d" +checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301249e3c9e43661cfd7ebbb4746a00af6ce1ef58b5c968451882cd60438417d" +checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -702,15 +702,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59bc947935732cae5b072753e5e034c0b70a8b031c2839f45e2659ba07df9ae" +checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -722,17 +722,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc280a41931bd419af86e9e859dd9726b73313aaa2e479b33c0e344f4b892ddb" +checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -743,13 +743,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede0458c51bef23620aa6bd01a0b4f608be7bcb61d98e91b8530208ae545f3c2" +checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", "thiserror 2.0.18", @@ -757,13 +757,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f4df183248b57f3e0b99054b1b6786769d3fdff6d01a702234068140c8ba76" +checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] @@ -780,9 +780,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4848831ff994c88b1c32b7df9c4c1c3eedea4b535bde5eb3c421ef0bdc5ac052" +checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" dependencies = [ "alloy-primitives", "serde", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b8ad9890b212e224291024b1aecfeef72127d27a2f6eebc5e347c40275c4bf" +checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -808,9 +808,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a57d1e72b1f9b11e5e71ebdab0569cb02277a462bbea6793fcaebfcd794ae9" +checksum = "a19d1985804e9a46d3b1b4f0654a68210b44007ffdaddd0e0a35d2b8db6cc1f0" dependencies = [ "alloy-consensus", "alloy-network", @@ -827,9 +827,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b27f20b5298b76a5a3b7cdbe6bdb184ab1ebd6e120e00dad748867673f5c90" +checksum = "34ce972f2ade53477e8e9336773a417f731fc7c02f41b9cd3b8a2a273e06363e" dependencies = [ "alloy-consensus", "alloy-network", @@ -845,9 +845,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c7acc40ffbfd37d4113eb619863099f3235d78d044006a1eecb94d8b0b2f1a" +checksum = "943c0105e0294b34cd06417129fadc591aed464d06f0614a7e998e585d27fbb1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -865,9 +865,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67d2372aada343130d41e249b59a3cef29b1678dcd3fd80f1c2c4d6b5318f2" +checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" dependencies = [ "alloy-consensus", "alloy-network", @@ -885,9 +885,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a09a865ae9e1f05478429ef0d935b16467f35c6e0b02cb10f23f66a3b33fc3" +checksum = "efe910cd3f56f7e4b26b8b7330b11c11c81286eaa8aa9fa6157e767a95e0f310" dependencies = [ "alloy-consensus", "alloy-network", @@ -902,9 +902,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb8218544ab635281f1be180a1cfd9b5d549db686faa7e85b3b2c10969819e" +checksum = "2296519a2342608afbd9baddd9d8df9f487604c3f1c2703b27bdd259f6018ea3" dependencies = [ "alloy-consensus", "alloy-network", @@ -991,9 +991,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b7b755e64ae6b5de0d762ed2c780e072167ea5e542076a559e00314352a0bf" +checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1014,9 +1014,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29980e69119444ed26b75e7ee5bed2043870f904a64318297e55800db686564" +checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -1030,9 +1030,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b27802653330740c88c28394cdaf1d55190b15b48ef9b99a946f37c9cdb19fa" +checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1050,9 +1050,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b71dc951db66795cfb52eef835f64cf15163bc93b656e061b457ce5ebff370" +checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1085,9 +1085,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8228b9236479ff16b03041b64b86c2bd4e53da1caa45d59b5868cd1571131e" +checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1229,7 +1229,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1241,7 +1241,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-beacon", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1302,12 +1302,12 @@ dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip5792", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "bytes", "foundry-common", "foundry-evm", @@ -2558,7 +2558,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -2730,7 +2730,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-ens", "alloy-evm", "alloy-hardforks", @@ -2742,7 +2742,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -3021,7 +3021,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3174,7 +3174,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3958,7 +3958,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4376,6 +4376,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fast-float2" version = "0.2.3" @@ -4693,7 +4705,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-json-abi", "alloy-network", @@ -4916,7 +4928,7 @@ version = "1.6.0" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-ens", "alloy-json-abi", "alloy-network", @@ -4978,7 +4990,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -5009,6 +5021,7 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-wallets", "itertools 0.14.0", "jiff", "mpp", @@ -5046,8 +5059,9 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "chrono", + "comfy-table", "eyre", "foundry-macros", "op-alloy-consensus", @@ -5285,7 +5299,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-sol-types", "anvil", "auto_impl", @@ -5381,7 +5395,7 @@ name = "foundry-evm-networks" version = "1.6.0" dependencies = [ "alloy-chains", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -5485,7 +5499,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5544,7 +5558,7 @@ dependencies = [ [[package]] name = "foundry-wallets" version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry-core?rev=7f401c1397af90a0a94ef7424a48bbf3dc0248cc#7f401c1397af90a0a94ef7424a48bbf3dc0248cc" +source = "git+https://github.com/foundry-rs/foundry-core?rev=d6c92dfcb41be26fbc1de21221a5bfc6bdd93245#d6c92dfcb41be26fbc1de21221a5bfc6bdd93245" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5568,6 +5582,7 @@ dependencies = [ "eth-keystore", "eyre", "rpassword", + "rusqlite", "serde", "serde_json", "tempo-primitives", @@ -5970,6 +5985,15 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -6565,7 +6589,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6920,6 +6944,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -7480,7 +7515,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7724,12 +7759,12 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "bytes", "derive_more", "reth-codecs 0.2.0", @@ -7778,12 +7813,12 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "op-alloy-consensus", "reth-rpc-traits 0.2.0", @@ -7798,11 +7833,11 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -8464,7 +8499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8633,7 +8668,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9033,7 +9068,7 @@ source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed4 dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9053,7 +9088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a29541038ab108b2e9d527c66b565e717e252e4eef6675377fc21f9ba587f792" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9072,7 +9107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a79b3247ae4fbb1d4d35ce83a11fc596428a4c6ea836c98a75a55340192578a4" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9125,7 +9160,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9161,7 +9196,7 @@ name = "reth-db-models" version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "bytes", "modular-bitfield", @@ -9176,7 +9211,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9205,7 +9240,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs 0.3.0", @@ -9219,7 +9254,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "auto_impl", @@ -9241,7 +9276,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -9274,7 +9309,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -9307,7 +9342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96ffdb2ce0cdcd814d39428dc1e9b660c85d85e0b75eb465a1ed0943a48c7bd" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9332,7 +9367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc759fd87c3f65440e5d3bfa3107fe8a13a61a6807cd485c62c49d63c7bf6717" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9465,7 +9500,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9487,7 +9522,7 @@ name = "reth-storage-errors" version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9509,7 +9544,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "arrayvec", "bytes", @@ -9930,6 +9965,20 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rusqlite" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -10000,7 +10049,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10059,7 +10108,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10070,9 +10119,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -10830,7 +10879,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10865,7 +10914,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11289,7 +11338,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11299,12 +11348,12 @@ source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe0 dependencies = [ "alloy-consensus", "alloy-contract", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer-local", "alloy-sol-types", "alloy-transport", @@ -11324,7 +11373,7 @@ name = "tempo-chainspec" version = "1.5.3" source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-hardforks", @@ -11429,12 +11478,12 @@ version = "1.6.0" source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "aws-lc-rs", "base64 0.22.1", "derive_more", @@ -11494,7 +11543,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11504,7 +11553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12721,7 +12770,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12871,7 +12920,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f902786070d81..7388b62a8acfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/cli/", "crates/common/", "crates/config/", + "crates/config/spec/", "crates/debugger/", "crates/doc/", "crates/evm/core/", @@ -326,6 +327,7 @@ foundry-cli-markdown = { path = "crates/cli-markdown" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } +foundry-config-spec = { path = "crates/config/spec" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } foundry-evm-abi = { path = "crates/evm/abi" } @@ -355,36 +357,35 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## foundry-core -foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "7f401c1397af90a0a94ef7424a48bbf3dc0248cc", default-features = false } +foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "d6c92dfcb41be26fbc1de21221a5bfc6bdd93245", default-features = false } ## alloy -alloy-consensus = { version = "2.0.0", default-features = false } -alloy-contract = { version = "2.0.0", default-features = false } -alloy-eips = { version = "2.0.0", default-features = false } -alloy-eip5792 = { version = "2.0.0", default-features = false } -alloy-ens = { version = "2.0.0", default-features = false } -alloy-genesis = { version = "2.0.0", default-features = false } -alloy-json-rpc = { version = "2.0.0", default-features = false } -alloy-network = { version = "2.0.0", default-features = false } -alloy-provider = { version = "2.0.0", default-features = false } -alloy-pubsub = { version = "2.0.0", default-features = false } -alloy-rpc-client = { version = "2.0.0", default-features = false } -alloy-rpc-types = { version = "2.0.0", default-features = true } -alloy-rpc-types-beacon = { version = "2.0.0", default-features = true } -alloy-rpc-types-engine = { version = "2.0.0", default-features = false } -alloy-rpc-types-eth = { version = "2.0.0", default-features = false } -alloy-serde = { version = "2.0.0", default-features = false } -alloy-signer = { version = "2.0.0", default-features = false } -alloy-signer-aws = { version = "2.0.0", default-features = false } -alloy-signer-gcp = { version = "2.0.0", default-features = false } -alloy-signer-ledger = { version = "2.0.0", default-features = false } -alloy-signer-local = { version = "2.0.0", default-features = false } -alloy-signer-trezor = { version = "2.0.0", default-features = false } -alloy-signer-turnkey = { version = "2.0.0", default-features = false } -alloy-transport = { version = "2.0.0", default-features = false } -alloy-transport-http = { version = "2.0.0", default-features = false } -alloy-transport-ipc = { version = "2.0.0", default-features = false } -alloy-transport-ws = { version = "2.0.0", default-features = false } +alloy-consensus = { version = "2.0.1", default-features = false } +alloy-contract = { version = "2.0.1", default-features = false } +alloy-eips = { version = "2.0.1", default-features = false } +alloy-eip5792 = { version = "2.0.1", default-features = false } +alloy-ens = { version = "2.0.1", default-features = false } +alloy-genesis = { version = "2.0.1", default-features = false } +alloy-json-rpc = { version = "2.0.1", default-features = false } +alloy-network = { version = "2.0.1", default-features = false } +alloy-provider = { version = "2.0.1", default-features = false } +alloy-pubsub = { version = "2.0.1", default-features = false } +alloy-rpc-client = { version = "2.0.1", default-features = false } +alloy-rpc-types = { version = "2.0.1", default-features = true } +alloy-rpc-types-beacon = { version = "2.0.1", default-features = true } +alloy-rpc-types-engine = { version = "2.0.1", default-features = false } +alloy-rpc-types-eth = { version = "2.0.1", default-features = false } +alloy-serde = { version = "2.0.1", default-features = false } +alloy-signer = { version = "2.0.1", default-features = false } +alloy-signer-aws = { version = "2.0.1", default-features = false } +alloy-signer-gcp = { version = "2.0.1", default-features = false } +alloy-signer-ledger = { version = "2.0.1", default-features = false } +alloy-signer-local = { version = "2.0.1", default-features = false } +alloy-signer-trezor = { version = "2.0.1", default-features = false } +alloy-signer-turnkey = { version = "2.0.1", default-features = false } +alloy-transport = { version = "2.0.1", default-features = false } +alloy-transport-ipc = { version = "2.0.1", default-features = false } +alloy-transport-ws = { version = "2.0.1", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } alloy-op-hardforks = { version = "0.4.7", default-features = false } @@ -620,3 +621,6 @@ solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/sola solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } + +[workspace.metadata.cargo-shear] +ignored = ["idna_adapter", "cast", "chisel", "forge", "alloy-contract"] diff --git a/Makefile b/Makefile index fe768f19abf43..1bf20f4eeebd1 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ help: ## Display this help. .PHONY: build build: ## Build the project. - cargo build --features "$(FEATURES)" --profile "$(PROFILE)" + cargo build --locked --features "$(FEATURES)" --profile "$(PROFILE)" .PHONY: build-docker build-docker: ## Build the docker image. @@ -47,17 +47,17 @@ build-docker: ## Build the docker image. ## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). .PHONY: test-coverage test-coverage: - cargo +nightly llvm-cov --no-report nextest -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ - cargo +nightly llvm-cov --no-report --doc && \ + cargo +nightly llvm-cov --no-report nextest --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ + cargo +nightly llvm-cov --no-report --doc --locked && \ cargo +nightly llvm-cov report --doctests --open .PHONY: test-unit test-unit: ## Run unit tests. - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' + cargo nextest run --workspace --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .PHONY: test-doc test-doc: ## Run doc tests. - cargo test --doc --workspace + cargo test --doc --workspace --locked .PHONY: test test: ## Run all tests. @@ -77,6 +77,7 @@ lint-clippy: ## Run clippy on the codebase. --workspace \ --all-targets \ --all-features \ + --locked \ -- -D warnings .PHONY: lint-clippy-fix @@ -85,6 +86,7 @@ lint-clippy-fix: ## Run clippy on the codebase and fix warnings. --workspace \ --all-targets \ --all-features \ + --locked \ --fix \ --allow-dirty \ --allow-staged \ @@ -104,21 +106,46 @@ lint: ## Run all linters. $(MAKE) lint-clippy && \ $(MAKE) lint-typos +##@ Documentation + +.PHONY: doc +doc: ## Build the documentation. + RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options --show-type-layout --generate-link-to-definition" \ + cargo +nightly doc \ + --workspace \ + --all-features \ + --document-private-items \ + --no-deps \ + --locked + ##@ Other +.PHONY: lock +lock: ## Update the Cargo.lock file with the current dependencies. + cargo fetch + .PHONY: clean clean: ## Clean the project. cargo clean .PHONY: deny deny: ## Perform a `cargo` deny check. - cargo deny --all-features check all + cargo deny --locked --all-features check all + +.PHONY: check +check: ## Run a feature check on all crates and binaries. + cargo hack check --locked --feature-powerset --depth 1 + +.PHONY: shear +shear: ## Run `cargo shear` to check for unused dependencies. + cargo shear --locked .PHONY: pr pr: ## Run all checks and tests. $(MAKE) deny && \ $(MAKE) lint && \ - $(MAKE) test + $(MAKE) test && \ + $(MAKE) doc # dprint formatting commands .PHONY: dprint-fmt diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ca429168c2095..ab3bec614e3cd 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -130,10 +130,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dbd89ce88827d..56164c8f6367a 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -28,7 +28,7 @@ foundry-compilers.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } forge-fmt.workspace = true alloy-chains.workspace = true diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index d36e74b53b6d1..94a435c08559b 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -801,7 +801,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; + sh_println!("{decoded_tx}")?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs index b39e024f38af5..0047b56d25f03 100644 --- a/crates/cast/src/call_spec.rs +++ b/crates/cast/src/call_spec.rs @@ -145,8 +145,8 @@ impl FromStr for CallSpec { /// Parse a value string that can be in ether notation (e.g., "0.1ether") or raw wei. fn parse_ether_or_wei(s: &str) -> Result { // Use alloy's DynSolType coercion which handles "1ether", "1gwei", "1000" etc. - if s.starts_with("0x") { - U256::from_str_radix(s, 16).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) + if s.starts_with("0x") || s.starts_with("0X") { + U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s) .wrap_err_with(|| format!("Invalid value '{s}'"))? @@ -180,6 +180,12 @@ mod tests { assert!(spec.sig.is_none()); } + #[test] + fn test_parse_hex_value() { + assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16)); + } + #[test] fn test_parse_with_sig() { let spec = CallSpec::parse( diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index e629b8eca0d5a..1d27b7a23dcd5 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -3,13 +3,13 @@ use std::{str::FromStr, time::Duration}; use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, format_uint_exp, - tx::{SendTxOpts, TxParams}, + tx::{CastTxSender, SendTxOpts, TxParams}, }; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network}; -use alloy_primitives::U256; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256}; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; @@ -28,6 +28,7 @@ use foundry_common::{ pub use foundry_config::{Chain, utils::*}; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::PATH_USD_ADDRESS; sol! { #[sol(rpc)] @@ -281,6 +282,34 @@ impl Erc20Subcommand { } } + const fn uses_browser_send(&self) -> bool { + match self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => send_tx.browser.browser, + _ => false, + } + } + + async fn should_use_tempo_network( + &self, + tempo_access_key: &Option, + ) -> eyre::Result { + if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some() + { + return Ok(true); + } + + if self.uses_browser_send() { + let config = self.rpc_opts().load_config()?; + return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo()); + } + + Ok(false) + } + pub async fn run(self) -> eyre::Result<()> { // Resolve the signer once for state-changing variants. let (signer, tempo_access_key) = match &self { @@ -299,8 +328,7 @@ impl Erc20Subcommand { _ => (None, None), }; - let is_tempo = self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) - || tempo_access_key.is_some(); + let is_tempo = self.should_use_tempo_network(&tempo_access_key).await?; if is_tempo { self.run_generic::(signer, tempo_access_key).await @@ -356,6 +384,28 @@ impl Erc20Subcommand { timeout, ) .await? + } else if let Some(browser) = $send_tx.browser.run::().await? { + let $provider = ProviderBuilder::::from_config(&config)?.build()?; + if let Some(interval) = $send_tx.poll_interval { + $provider.client().set_poll_interval(Duration::from_secs(interval)); + } + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + let chain = get_chain(config.chain, &$provider).await?; + $tx_opts.apply::(&mut tx, chain.is_legacy()); + if chain.is_tempo() && tx.fee_token().is_none() { + tx.set_fee_token(PATH_USD_ADDRESS); + } + fill_tx(&$provider, &mut tx, browser.address(), chain).await?; + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&$provider) + .print_tx_result( + tx_hash, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? } else { let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); let $provider = build_provider_with_signer::(&$send_tx, signer)?; @@ -503,3 +553,46 @@ impl Erc20Subcommand { Ok(()) } } + +/// Fills from, chain_id, nonce, fees, and gas limit on a transaction request for the browser +/// wallet path. Mirrors the filling logic in the shared tx builder but operates on a +/// pre-built transaction request from the sol! macro rather than through the builder pipeline. +/// Only fills fields that haven't already been set by the user. +async fn fill_tx>( + provider: &P, + tx: &mut N::TransactionRequest, + from: Address, + chain: Chain, +) -> eyre::Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + tx.set_from(from); + tx.set_chain_id(chain.id()); + + if tx.nonce().is_none() { + tx.set_nonce(provider.get_transaction_count(from).await?); + } + + let legacy = chain.is_legacy(); + + if legacy { + if tx.gas_price().is_none() { + tx.set_gas_price(provider.get_gas_price().await?); + } + } else if tx.max_fee_per_gas().is_none() || tx.max_priority_fee_per_gas().is_none() { + let estimate = provider.estimate_eip1559_fees().await?; + if tx.max_fee_per_gas().is_none() { + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } + if tx.max_priority_fee_per_gas().is_none() { + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + } + + if tx.gas_limit().is_none() { + tx.set_gas_limit(provider.estimate_gas(tx.clone()).await?); + } + + Ok(()) +} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 0df824f3588da..7d3b913b8780c 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3106,6 +3106,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index c98cfb69357bd..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 29e968aeb5bc6..202b590769857 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -178,7 +178,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 5e762ec62a035..20ba969abbff1 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1129,6 +1129,15 @@ mod tests { assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); assert!(latest.success); - stdfs::remove_dir_all(root).unwrap(); + if root.exists() { + let root_canon = stdfs::canonicalize(&root).unwrap(); + let temp_canon = stdfs::canonicalize(env::temp_dir()).unwrap(); + assert!( + root_canon.starts_with(&temp_canon), + "refusing to remove non-temp test directory: {}", + root_canon.display() + ); + stdfs::remove_dir_all(&root).unwrap(); + } } } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 165154da88f63..bcbe837e824e3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,7 +19,7 @@ foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tempo-primitives.workspace = true diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f1c6c435217b7..be566288ef87a 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -129,8 +129,8 @@ where /// If the string represents an untagged amount (e.g. "100") then /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { - Ok(if value.starts_with("0x") { - U256::from_str_radix(value, 16)? + Ok(if value.starts_with("0x") || value.starts_with("0X") { + U256::from_str(value)? } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? .as_uint() @@ -844,6 +844,16 @@ mod tests { assert!(!p.is_sol_test()); } + #[test] + fn parse_ether_value_accepts_hex_prefixed_wei() { + assert_eq!(parse_ether_value("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0X10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0x12").unwrap(), U256::from(0x12)); + assert_eq!(parse_ether_value("0xff").unwrap(), U256::from(0xff)); + assert_eq!(parse_ether_value("100").unwrap(), U256::from(100)); + assert_eq!(parse_ether_value("1ether").unwrap(), U256::from(1000000000000000000u128)); + } + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..8f6d7f3cde092 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -16,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ac7a8c12eae71..04c2a06a3fa90 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -82,6 +82,7 @@ flate2.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true mpp.workspace = true +foundry-wallets.workspace = true [build-dependencies] chrono.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 78759848fc4bd..2c8e16bccdcc6 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -30,6 +30,7 @@ serde_json.workspace = true chrono.workspace = true revm.workspace = true yansi.workspace = true +comfy-table.workspace = true # Tempo tempo-alloy.workspace = true diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index d751ddba245ba..8b66335445046 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -1,5 +1,6 @@ use super::UIfmt; use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use comfy_table::{Table, TableComponent, presets::UTF8_FULL}; use std::fmt::{self, Write}; /// A piece is a portion of the format string which represents the next part to emit. @@ -407,6 +408,36 @@ fn format_spec<'a>( } } +pub fn console_table_format( + keys: Option<&[&dyn ConsoleFmt]>, + values: &[&dyn ConsoleFmt], +) -> String { + let keys_strings: Vec = match keys { + Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(), + None => (0..values.len()).map(|i| i.to_string()).collect(), + }; + let values_strings: Vec = values.iter().map(|v| v.fmt(FormatSpec::String)).collect(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL); + table.set_style(TableComponent::VerticalLines, '│'); + table.set_style(TableComponent::HeaderLines, '─'); + table.set_style(TableComponent::MiddleHeaderIntersections, '┼'); + table.set_style(TableComponent::LeftHeaderIntersection, '├'); + table.set_style(TableComponent::RightHeaderIntersection, '┤'); + table.set_header(vec!["(index)", "Values"]); + table.remove_style(TableComponent::HorizontalLines); + table.remove_style(TableComponent::MiddleIntersections); + table.remove_style(TableComponent::LeftBorderIntersections); + table.remove_style(TableComponent::RightBorderIntersections); + for i in 0..keys_strings.len().max(values_strings.len()) { + let key = keys_strings.get(i).map(String::as_str).unwrap_or(""); + let value = values_strings.get(i).map(String::as_str).unwrap_or(""); + table.add_row(vec![key, value]); + } + table.to_string() +} + #[cfg(test)] mod tests { use super::*; @@ -610,4 +641,80 @@ mod tests { let call = Logs::Log1(log1); assert_eq!(call.fmt(Default::default()), "foo 42 bar"); } + + #[test] + fn test_console_table_format() { + // auto-indexed, uint256 values + let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)]; + assert_eq!( + console_table_format(None, values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ 0 │ 100 │\n\ + │ 1 │ 200 │\n\ + │ 2 │ 300 │\n\ + └─────────┴────────┘" + ); + + // string keys, uint256 values + // key col expands to fit "charlie123" and value col expands to fit "20000000000000000" + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")]; + let values: &[&dyn ConsoleFmt] = &[ + &U256::from(1), + &U256::from_str("20000000000000000").unwrap(), + &U256::from_str("30000000000").unwrap(), + ]; + assert_eq!( + console_table_format(Some(keys), values), + "┌────────────┬───────────────────┐\n\ + │ (index) │ Values │\n\ + ├────────────┼───────────────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 20000000000000000 │\n\ + │ charlie123 │ 30000000000 │\n\ + └────────────┴───────────────────┘" + ); + + // empty table + assert_eq!( + console_table_format(None, &[]), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + └─────────┴────────┘" + ); + + // more keys than values + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie")]; + let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ charlie │ │\n\ + └─────────┴────────┘" + ); + + // more values than keys + let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")]; + let values: &[&dyn ConsoleFmt] = + &[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ │ 3 │\n\ + │ │ 4 │\n\ + └─────────┴────────┘" + ); + } } diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 89297a8e2c6b5..45ed47263ce32 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod console; -pub use console::{ConsoleFmt, FormatSpec, console_format}; +pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format}; mod dynamic; pub use dynamic::{ diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 895b16b3b4532..e9f203ebf7539 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) .map(|(_, data)| data) } diff --git a/crates/common/src/provider/mpp/persist.rs b/crates/common/src/provider/mpp/persist.rs index 6d6781ab4c77b..803c7270e041e 100644 --- a/crates/common/src/provider/mpp/persist.rs +++ b/crates/common/src/provider/mpp/persist.rs @@ -1,243 +1,241 @@ //! Persistent channel storage for MPP sessions. //! -//! Stores open payment channel state in a JSON file at -//! `$TEMPO_HOME/foundry/channels.json` (default: `~/.tempo/foundry/channels.json`). +//! Stores open payment channel state in a SQLite database at +//! `$TEMPO_HOME/channels.db` (default: `~/.tempo/channels.db`). //! This allows channel reuse across process invocations, avoiding the cost of //! opening a new on-chain channel for every `cast` / `forge` command. use alloy_primitives::{Address, B256}; +use foundry_wallets::{Channel, ChannelDb}; use mpp::client::channel_ops::ChannelEntry; -use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - path::PathBuf, + sync::OnceLock, time::{SystemTime, UNIX_EPOCH}, }; use tracing::{debug, warn}; use crate::tempo::tempo_home; -/// Relative path from Tempo home to the Foundry channels file. -const CHANNELS_PATH: &str = "foundry/channels.json"; +/// Process-wide database handle. +fn global_db() -> Option<&'static ChannelDb> { + static DB: OnceLock> = OnceLock::new(); + DB.get_or_init(|| { + let path = tempo_home()?.join("channels.db"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Some(old) = + tempo_home().map(|h| h.join("foundry/channels.json")).filter(|p| p.exists()) + { + warn!( + ?old, + "found old channels.json — this file is no longer used; channels will be re-opened" + ); + } -/// Current schema version. -const SCHEMA_VERSION: u64 = 2; + match ChannelDb::open(&path) { + Ok(db) => { + debug!(?path, "opened channel database"); + Some(db) + } + Err(e) => { + warn!(?path, %e, "failed to open channel database"); + None + } + } + }) + .as_ref() +} -/// On-disk representation of the channel store. -#[derive(Debug, Serialize, Deserialize)] -struct ChannelStore { - version: u64, - #[serde(default)] - channels: HashMap, +/// Reconstruct the composite HashMap key from a persisted `Channel`. +/// +/// Mirrors `SessionProvider::channel_key()` in session.rs. +fn channel_key_from_persisted(ch: &Channel) -> String { + let origin_hash = &alloy_primitives::keccak256(ch.origin.as_bytes()).to_string()[..18]; + format!( + "{}:{}:{}:{}:{}:{}:{}", + origin_hash, + ch.chain_id, + ch.payer, + ch.authorized_signer, + ch.payee, + ch.token, + ch.escrow_contract + ) + .to_lowercase() } -impl Default for ChannelStore { - fn default() -> Self { - Self { version: SCHEMA_VERSION, channels: HashMap::new() } +/// Whether a channel can still be used (active and not fully spent). +fn is_usable(ch: &Channel) -> bool { + if ch.state != "active" { + return false; } + let cumulative: u128 = ch.cumulative_amount.parse().unwrap_or(u128::MAX); + let deposit: u128 = ch.deposit.parse().unwrap_or(0); + cumulative < deposit } -/// A persisted channel entry. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PersistedChannel { - pub channel_id: String, - pub salt: String, - pub escrow_contract: String, - pub chain_id: u64, - pub cumulative_amount: String, - pub deposit: String, - pub status: String, - pub origin: String, - pub created_at: u64, - pub last_used_at: u64, +/// Convert a persisted `Channel` to a `ChannelEntry`. +pub fn to_channel_entry(ch: &Channel) -> Option { + let channel_id: B256 = ch.channel_id.parse().ok()?; + let salt: B256 = ch.salt.parse().ok()?; + let escrow_contract: Address = ch.escrow_contract.parse().ok()?; + let cumulative_amount: u128 = ch.cumulative_amount.parse().ok()?; + + Some(ChannelEntry { + channel_id, + salt, + cumulative_amount, + escrow_contract, + chain_id: ch.chain_id as u64, + opened: ch.state == "active", + }) } -impl PersistedChannel { - /// Convert to an mpp `ChannelEntry` for use in the session provider. - pub fn to_channel_entry(&self) -> Option { - let channel_id: B256 = self.channel_id.parse().ok()?; - let salt: B256 = self.salt.parse().ok()?; - let escrow_contract: Address = self.escrow_contract.parse().ok()?; - let cumulative_amount: u128 = self.cumulative_amount.parse().ok()?; - - Some(ChannelEntry { - channel_id, - salt, - cumulative_amount, - escrow_contract, - chain_id: self.chain_id, - opened: self.status == "active", - }) - } - - /// Create from a `ChannelEntry` with metadata. - pub fn from_channel_entry(entry: &ChannelEntry, deposit: u128, origin: &str) -> Self { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); - - Self { - channel_id: entry.channel_id.to_string(), - salt: entry.salt.to_string(), - escrow_contract: entry.escrow_contract.to_string(), - chain_id: entry.chain_id, - cumulative_amount: entry.cumulative_amount.to_string(), - deposit: deposit.to_string(), - status: if entry.opened { "active" } else { "closed" }.to_string(), - origin: origin.to_string(), - created_at: now, - last_used_at: now, - } - } - - /// Whether this channel can still be used (active and not fully spent). - fn is_usable(&self) -> bool { - if self.status != "active" { - return false; - } - let cumulative: u128 = self.cumulative_amount.parse().unwrap_or(u128::MAX); - let deposit: u128 = self.deposit.parse().unwrap_or(0); - cumulative < deposit +/// Create a `Channel` from a `ChannelEntry` with metadata. +pub fn from_channel_entry( + entry: &ChannelEntry, + deposit: u128, + origin: &str, + payer: &Address, + payee: &Address, + token: &Address, + authorized_signer: &Address, +) -> Channel { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + Channel { + channel_id: entry.channel_id.to_string(), + version: 1, + origin: origin.to_string(), + request_url: String::new(), + chain_id: entry.chain_id as i64, + escrow_contract: entry.escrow_contract.to_string(), + token: token.to_string(), + payee: payee.to_string(), + payer: payer.to_string(), + authorized_signer: authorized_signer.to_string(), + salt: entry.salt.to_string(), + deposit: deposit.to_string(), + cumulative_amount: entry.cumulative_amount.to_string(), + challenge_echo: String::new(), + state: if entry.opened { "active" } else { "closed" }.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: now, + last_used_at: now, } } -/// Returns the path to the channels file. -fn channels_path() -> Option { - tempo_home().map(|home| home.join(CHANNELS_PATH)) -} - -/// Load channels from disk, evicting spent/inactive entries. -pub fn load_channels() -> HashMap { - let Some(path) = channels_path().filter(|p| p.exists()) else { +/// Load channels from database, evicting spent/inactive entries. +pub fn load_channels() -> HashMap { + let Some(db) = global_db() else { return HashMap::new(); }; - let Ok(contents) = std::fs::read_to_string(&path).inspect_err(|e| { - warn!(?path, %e, "failed to read channels file"); - }) else { - return HashMap::new(); - }; - - let Ok(store) = serde_json::from_str::(&contents).inspect_err(|e| { - warn!(?path, %e, "failed to parse channels file, starting fresh"); - }) else { - return HashMap::new(); + let channels = match db.load() { + Ok(channels) => channels, + Err(e) => { + warn!(%e, "failed to load channels from database"); + return HashMap::new(); + } }; - if store.version != SCHEMA_VERSION { - warn!( - version = store.version, - expected = SCHEMA_VERSION, - "channels file version mismatch, starting fresh" - ); - return HashMap::new(); - } - - // Evict spent/inactive entries - let usable: HashMap = - store.channels.into_iter().filter(|(_, ch)| ch.is_usable()).collect(); + let usable: HashMap = channels + .into_iter() + .filter(is_usable) + .map(|ch| { + let key = channel_key_from_persisted(&ch); + (key, ch) + }) + .collect(); debug!(count = usable.len(), "loaded persisted MPP channels"); usable } -/// Save channels to disk. -pub fn save_channels(channels: &HashMap) { - let Some(path) = channels_path() else { +/// Save channels to database. +pub fn save_channels(channels: &HashMap) { + let Some(db) = global_db() else { return; }; - if let Some(parent) = path.parent() - && let Err(e) = std::fs::create_dir_all(parent) - { - warn!(?path, %e, "failed to create channels directory"); - return; + for ch in channels.values() { + if let Err(e) = db.upsert(ch) { + warn!(%e, channel_id = %ch.channel_id, "failed to save channel"); + } } + debug!(count = channels.len(), "saved MPP channels"); +} - let store = ChannelStore { version: SCHEMA_VERSION, channels: channels.clone() }; - - match serde_json::to_string_pretty(&store) { - Ok(json) => { - if let Err(e) = std::fs::write(&path, json) { - warn!(?path, %e, "failed to write channels file"); - } else { - debug!(?path, count = channels.len(), "saved MPP channels"); - } - } - Err(e) => warn!(%e, "failed to serialize channels"), +/// Delete a channel from the database by its channel ID. +pub fn delete_channel_from_db(channel_id: &str) { + let Some(db) = global_db() else { + return; + }; + if let Err(e) = db.delete(channel_id) { + warn!(%e, channel_id, "failed to delete channel from database"); } } /// Look up a usable persisted channel by key. -pub fn find_channel( - channels: &HashMap, - key: &str, -) -> Option { - channels.get(key).filter(|ch| ch.is_usable()).and_then(|ch| ch.to_channel_entry()) +pub fn find_channel(channels: &HashMap, key: &str) -> Option { + channels.get(key).filter(|ch| is_usable(ch)).and_then(to_channel_entry) } -/// Insert or update a channel entry in memory only (no disk write). -/// -/// Use [`upsert_channel`] when you want to persist immediately, or call -/// [`save_channels`] separately after this. +/// Insert or update a channel entry in memory only (no DB write). pub fn upsert_channel_in_memory( - channels: &mut HashMap, + channels: &mut HashMap, key: &str, entry: &ChannelEntry, - deposit: u128, - origin: &str, ) { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; if let Some(existing) = channels.get_mut(key) { existing.cumulative_amount = entry.cumulative_amount.to_string(); existing.last_used_at = now; - existing.status = if entry.opened { "active" } else { "closed" }.to_string(); + existing.state = if entry.opened { "active" } else { "closed" }.to_string(); } else { - channels - .insert(key.to_string(), PersistedChannel::from_channel_entry(entry, deposit, origin)); + warn!(key, "upsert_channel_in_memory called for unknown channel"); } } -/// Insert or update a channel entry and save to disk. -/// -/// When updating an existing entry, `deposit` is ignored (preserved from the -/// original open). When inserting a new entry, `deposit` is recorded. -pub fn upsert_channel( - channels: &mut HashMap, - key: &str, - entry: &ChannelEntry, - deposit: u128, - origin: &str, -) { - upsert_channel_in_memory(channels, key, entry, deposit, origin); - save_channels(channels); -} - #[cfg(test)] mod tests { use super::*; - fn test_channel(status: &str, cumulative: &str, deposit: &str) -> PersistedChannel { - PersistedChannel { + fn test_channel(state: &str, cumulative: &str, deposit: &str) -> Channel { + Channel { channel_id: format!("0x{}", "ab".repeat(32)), - salt: format!("0x{}", "cd".repeat(32)), - escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + version: 1, + origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + request_url: String::new(), chain_id: 42431, - cumulative_amount: cumulative.to_string(), + escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + token: "0x20c0000000000000000000000000000000000000".to_string(), + payee: "0x3333333333333333333333333333333333333333".to_string(), + payer: "0x1111111111111111111111111111111111111111".to_string(), + authorized_signer: "0x1111111111111111111111111111111111111111".to_string(), + salt: format!("0x{}", "cd".repeat(32)), deposit: deposit.to_string(), - status: status.to_string(), - origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + cumulative_amount: cumulative.to_string(), + challenge_echo: String::new(), + state: state.to_string(), + close_requested_at: 0, + grace_ready_at: 0, created_at: 1000, last_used_at: 1000, } } #[test] - fn is_usable() { - assert!(test_channel("active", "5000", "100000").is_usable()); - assert!(!test_channel("active", "100000", "100000").is_usable()); - assert!(!test_channel("active", "200000", "100000").is_usable()); - assert!(!test_channel("closed", "0", "100000").is_usable()); - assert!(!test_channel("closing", "0", "100000").is_usable()); + fn usable() { + assert!(is_usable(&test_channel("active", "5000", "100000"))); + assert!(!is_usable(&test_channel("active", "100000", "100000"))); + assert!(!is_usable(&test_channel("active", "200000", "100000"))); + assert!(!is_usable(&test_channel("closed", "0", "100000"))); + assert!(!is_usable(&test_channel("closing", "0", "100000"))); } #[test] @@ -251,8 +249,12 @@ mod tests { opened: true, }; - let persisted = PersistedChannel::from_channel_entry(&entry, 100_000, "https://rpc.test"); - let restored = persisted.to_channel_entry().expect("should parse back"); + let payer = Address::random(); + let payee = Address::random(); + let token = Address::random(); + let persisted = + from_channel_entry(&entry, 100_000, "https://rpc.test", &payer, &payee, &token, &payer); + let restored = to_channel_entry(&persisted).expect("should parse back"); assert_eq!(restored.channel_id, entry.channel_id); assert_eq!(restored.salt, entry.salt); @@ -262,46 +264,6 @@ mod tests { assert!(restored.opened); } - #[test] - fn load_evicts_and_handles_edge_cases() { - let dir = tempfile::tempdir().unwrap(); - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - - let store = ChannelStore { - version: SCHEMA_VERSION, - channels: HashMap::from([ - ("active".into(), test_channel("active", "1000", "100000")), - ("spent".into(), test_channel("active", "100000", "100000")), - ("closed".into(), test_channel("closed", "0", "100000")), - ]), - }; - let json = serde_json::to_string(&store).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), &json).unwrap(); - - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - let loaded = load_channels(); - unsafe { std::env::remove_var("TEMPO_HOME") }; - - assert_eq!(loaded.len(), 1); - assert!(loaded.contains_key("active")); - } - - #[test] - fn load_missing_and_wrong_version() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - assert!(load_channels().is_empty()); - - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), r#"{"version": 999, "channels": {}}"#) - .unwrap(); - assert!(load_channels().is_empty()); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } - #[test] fn find_channel_filters_unusable() { let mut channels = HashMap::new(); @@ -312,34 +274,4 @@ mod tests { assert!(find_channel(&channels, "spent").is_none()); assert!(find_channel(&channels, "missing").is_none()); } - - #[test] - fn upsert_inserts_and_updates() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - - let mut channels = HashMap::new(); - let entry = ChannelEntry { - channel_id: B256::random(), - salt: B256::random(), - cumulative_amount: 1000, - escrow_contract: Address::random(), - chain_id: 42431, - opened: true, - }; - - upsert_channel(&mut channels, "key1", &entry, 100_000, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "1000"); - assert_eq!(channels["key1"].deposit, "100000"); - let created_at = channels["key1"].created_at; - - let mut updated = entry.clone(); - updated.cumulative_amount = 5000; - upsert_channel(&mut channels, "key1", &updated, 0, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "5000"); - assert_eq!(channels["key1"].deposit, "100000"); - assert_eq!(channels["key1"].created_at, created_at); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } } diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 5996c933218d2..334166b844613 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -6,8 +6,9 @@ //! `eth_getTransactionCount`. This avoids the chicken-and-egg problem when //! the RPC endpoint is itself 402-gated. -use super::persist::{self, PersistedChannel}; +use super::persist; use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use foundry_wallets::Channel; use mpp::{ client::{ PaymentProvider, @@ -42,7 +43,7 @@ static GLOBAL_CHANNELS: OnceLock>> = O /// /// Using a single map ensures saves from different origins don't clobber /// each other's state. -static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); +static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); /// Tracks uncommitted channel state from the most recent payment. /// @@ -86,7 +87,7 @@ pub struct SessionProvider { default_deposit: Option, channels: Arc>>, key_provisioned: Arc>, - persisted: Arc>>, + persisted: Arc>>, /// Tracks uncommitted open/top-up state for deferred persistence. pending: Arc>>, /// Chain ID from the key entry in `keys.toml` that was used to initialize @@ -128,10 +129,10 @@ impl SessionProvider { map.entry(origin.clone()) .or_insert_with(|| { // Hydrate only channels belonging to this origin. - let mut channels = HashMap::new(); + let mut channels: HashMap = HashMap::new(); for (key, ch) in persisted.lock().unwrap().iter() { if ch.origin == origin - && let Some(entry) = ch.to_channel_entry() + && let Some(entry) = persist::to_channel_entry(ch) { channels.insert(key.clone(), entry); } @@ -209,16 +210,16 @@ impl SessionProvider { // Lock order: channels → persisted (consistent with pay_session) let mut channels = self.channels.lock().unwrap(); let mut persisted = self.persisted.lock().unwrap(); - let keys_to_remove: Vec = persisted + let keys_to_remove: Vec<(String, String)> = persisted .iter() .filter(|(_, ch)| ch.origin == *origin) - .map(|(k, _)| k.clone()) + .map(|(k, ch): (&String, &Channel)| (k.clone(), ch.channel_id.clone())) .collect(); - for key in &keys_to_remove { + for (key, channel_id) in &keys_to_remove { channels.remove(key); persisted.remove(key); + persist::delete_channel_from_db(channel_id); } - persist::save_channels(&persisted); } /// Mark whether the access key has been provisioned on-chain. @@ -685,13 +686,7 @@ impl SessionProvider { // confirms acceptance. let updated_entry = ChannelEntry { cumulative_amount: new_cumulative, ..entry }; let mut persisted = self.persisted.lock().unwrap(); - persist::upsert_channel_in_memory( - &mut persisted, - &key, - &updated_entry, - 0, - &self.origin, - ); + persist::upsert_channel_in_memory(&mut persisted, &key, &updated_entry); drop(persisted); // Track the voucher so we can roll back cumulative_amount @@ -727,9 +722,18 @@ impl SessionProvider { // Update in-memory state but defer disk persistence until server confirms. self.channels.lock().unwrap().insert(key.clone(), entry.clone()); + let authorized_signer = self.authorized_signer.unwrap_or(payer); self.persisted.lock().unwrap().insert( key.clone(), - persist::PersistedChannel::from_channel_entry(&entry, deposit, &self.origin), + persist::from_channel_entry( + &entry, + deposit, + &self.origin, + &payer, + &payee, + ¤cy, + &authorized_signer, + ), ); *self.pending.lock().unwrap() = Some(PendingAction::Open { key }); Ok(build_credential(challenge, payload, chain_id, payer)) diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 39c281e81be25..4c6978e0d9750 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -49,6 +49,9 @@ walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" @@ -60,3 +63,4 @@ tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..00a381eaf66ff --- /dev/null +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..89d40cf5606ce --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..5a362e963d956 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,64 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use foundry_config::Config; +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if let Ok(old_contents) = fs::read_to_string(file) + && normalize_newlines(&old_contents) == normalize_newlines(contents) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f148432c42bed..b1b889031c690 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1314,13 +1314,32 @@ impl Config { } // Remove last test run failures file. - if let Err(err) = fs::remove_file(&self.test_failures_file) - && err.kind() != io::ErrorKind::NotFound - { - warnings.push(format!( - "failed to remove test failures file {}: {err}", - self.test_failures_file.display() - )); + let test_failures_path = if self.test_failures_file.is_absolute() { + self.test_failures_file.clone() + } else { + project.root().join(&self.test_failures_file) + }; + let root_canon = dunce::canonicalize(project.root()).unwrap_or_else(|_| project.root().to_path_buf()); + let validated_test_failures_path = test_failures_path + .parent() + .and_then(|parent| dunce::canonicalize(parent).ok().map(|p| (p, test_failures_path.file_name()))) + .and_then(|(parent, file_name)| file_name.map(|name| parent.join(name))); + + match validated_test_failures_path { + Some(path) if path.starts_with(&root_canon) => { + if let Err(err) = fs::remove_file(&path) && err.kind() != io::ErrorKind::NotFound { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + path.display() + )); + } + } + _ => { + warnings.push(format!( + "skipped removing test failures file outside project root: {}", + test_failures_path.display() + )); + } } // Remove fuzz and invariant cache directories. diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 3e915b1b7f479..3d4c57fe5dc3e 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -151,6 +151,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 8081de7dac660..085664f85fa81 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,4 +1,4 @@ -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike}; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; use solang_parser::pt::{ EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration, @@ -89,17 +89,6 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes dev content to the buffer, handling markdown lists properly. - /// If the content contains markdown lists, it formats them correctly. - /// Otherwise, it writes the content in italics. - pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { - for line in text.lines() { - writeln!(self.buf, "{line}")?; - } - - Ok(()) - } - /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -202,7 +191,8 @@ impl BufWriter { params: &EnumDefinition, comments: &Comments, ) -> fmt::Result { - let comments = comments.include_tags(&[CommentTag::Param]); + let comments = + comments.include_tags(&[CommentTag::Param, CommentTag::Custom("variant".to_string())]); // There is nothing to write. if comments.is_empty() { @@ -215,7 +205,7 @@ impl BufWriter { self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; - for value in ¶ms.values { + for value in params.values.iter() { let param_name = value.as_ref().map(|v| v.name.clone()); let comment = param_name.as_ref().and_then(|name| { @@ -223,7 +213,7 @@ impl BufWriter { }); let row = [ - Markdown::Code(¶m_name.unwrap_or("".to_string())).as_doc()?, + Markdown::Code(param_name.as_deref().unwrap_or("")).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json index 54e6d46dff349..f275f471087ee 100644 --- a/crates/evm/abi/src/Console.json +++ b/crates/evm/abi/src/Console.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index 2e28fece21e42..d0b32ff410941 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -16,12 +16,15 @@ def main(): # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() sig_strings = re.findall( - r'"(log.*?)"', + r'"((?:log|table).*?)"', console_sol, ) raw_sigs = [s.strip().strip('"') for s in sig_strings] sigs = [ - s.replace("string", "string memory").replace("bytes)", "bytes memory)") + re.sub(r"(\w+\[\])", r"\1 memory", s) + .replace("string,", "string memory,") + .replace("string)", "string memory)") + .replace("bytes)", "bytes memory)") for s in raw_sigs ] sigs = list(set(sigs)) @@ -38,6 +41,7 @@ def main(): ) combined = json.loads(r.stdout.strip()) abi = combined["contracts"][":HardhatConsole"]["abi"] + open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 26acbe56a97cb..c591b9426bf9e 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -50,7 +50,9 @@ impl LogCollector { fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.push_msg(&decoded.fmt(Default::default())); + for line in decoded.fmt(Default::default()).lines() { + self.push_msg(line); + } Ok(()) } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 540c5209aa981..ecdc13ded67cf 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -51,7 +51,7 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } alloy-chains.workspace = true alloy-consensus.workspace = true @@ -62,6 +62,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index 49716146c456b..fac66727c9d99 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -53,8 +53,8 @@ impl ConfigArgs { } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 122020571ad24..a48d2a9350c70 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -9,7 +9,7 @@ use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as Alloy use alloy_signer::{Signature, Signer}; use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; +use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, @@ -33,10 +33,12 @@ use foundry_config::{ }, merge_impl_figment_convert, }; -use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use foundry_wallets::{ + BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner, +}; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; -use tempo_alloy::TempoNetwork; +use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN}; merge_impl_figment_convert!(CreateArgs, build, eth); @@ -101,14 +103,29 @@ pub struct CreateArgs { #[command(flatten)] retry: RetryArgs, + + /// Browser wallet options + #[command(flatten)] + browser: BrowserWalletOpts, } impl CreateArgs { /// Executes the command to create a contract - pub async fn run(self) -> Result<()> { + pub async fn run(mut self) -> Result<()> { let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?; - if tempo_access_key.is_some() || self.tx.tempo.is_tempo() { + // Resolve chain early so we can dispatch to the correct network type. + if self.chain_id().is_none() { + let config = self.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let chain_id = provider.get_chain_id().await?; + self.eth.etherscan.chain = Some(chain_id.into()); + } + + if tempo_access_key.is_some() + || self.tx.tempo.is_tempo() + || self.chain_id().is_some_and(|c| c.is_tempo()) + { self.run_generic::(signer, tempo_access_key).await } else { self.run_generic::(signer, None).await @@ -185,17 +202,29 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chain_id().await? - }; - // Whether to broadcast the transaction or not let dry_run = !self.broadcast; - if self.unlocked { + // Launch browser signer if `--browser` flag is set + let browser = self.browser.run::().await?; + + if let Some(browser) = browser { + // Deploy with browser wallet + let deployer_address = browser.address(); + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + None, + Some(browser), + ) + .await + } else if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( @@ -203,12 +232,12 @@ impl CreateArgs { bin, params, provider, - chain_id, sender, config.transaction_timeout, id, dry_run, None, + None, ) .await } else if let Some(ak) = access_key { @@ -223,12 +252,12 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer_address, config.transaction_timeout, id, dry_run, Some((signer, ak)), + None, ) .await } else { @@ -246,20 +275,20 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer, config.transaction_timeout, id, dry_run, None, + None, ) .await } } - /// Returns the provided chain id, if any. - fn chain_id(&self) -> Option { - self.eth.etherscan.chain.map(|chain| chain.id()) + /// Returns the resolved chain, if any. + const fn chain_id(&self) -> Option { + self.eth.etherscan.chain } /// Ensures the verify command can be executed. @@ -271,7 +300,6 @@ impl CreateArgs { async fn verify_preflight_check( &self, constructor_args: Option, - chain: u64, id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, @@ -287,7 +315,7 @@ impl CreateArgs { num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), - chain: Some(chain.into()), + chain: self.chain_id(), }, rpc: Default::default(), flatten: false, @@ -311,7 +339,7 @@ impl CreateArgs { // ETHERSCAN_API_KEY value set. let config = verify.load_config()?; verify.etherscan.key = - config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key); let context = verify.resolve_context().await?; @@ -327,17 +355,19 @@ impl CreateArgs { bin: BytecodeObject, args: Vec, provider: P, - chain: u64, deployer_address: Address, timeout: u64, id: ArtifactId, dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, + browser_signer: Option>, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, N::ReceiptResponse: serde::Serialize, { + let chain = self.chain_id().context("chain ID not resolved")?; + let bin = bin.into_bytes().unwrap_or_default(); if bin.is_empty() { eyre::bail!("no bytecode found in bin object for {}", self.contract.name) @@ -349,22 +379,31 @@ impl CreateArgs { let is_args_empty = args.is_empty(); let mut deployer = - factory.deploy_tokens(args.clone(), self.tx.tempo.fee_token).context("failed to deploy contract").map_err(|e| { + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { e } })?; - let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + let is_legacy = self.tx.legacy || chain.is_legacy(); deployer.tx.set_from(deployer_address); - deployer.tx.set_chain_id(chain); + deployer.tx.set_chain_id(chain.id()); // `to` field must be set explicitly, cannot be None. if deployer.tx.to().is_none() { deployer.tx.set_create(); } + // If Tempo chain fee token must be set + if chain.is_tempo() { + if let Some(fee_token) = self.tx.tempo.fee_token { + deployer.tx.set_fee_token(fee_token); + } else { + deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); + } + } + // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); @@ -422,7 +461,7 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + self.verify_preflight_check(constructor_args.clone(), &id).await?; } if dry_run { @@ -452,7 +491,31 @@ impl CreateArgs { } // Deploy the actual contract - let (deployed_contract, receipt) = if let Some((signer, ak)) = tempo_keychain { + let (deployed_contract, receipt) = if let Some(browser) = browser_signer { + // Browser wallet signs and sends the transaction + let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?; + + // Wait for the transaction to be confirmed, then fetch the receipt. + provider + .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash)) + .await? + .await?; + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?; + + if !receipt.status() { + eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}"); + } + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else if let Some((signer, ak)) = tempo_keychain { // Tempo keychain mode: sign with access key provisioning and send raw let raw_tx = deployer .tx @@ -518,7 +581,7 @@ impl CreateArgs { no_auto_detect: false, use_solc: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) }, rpc: Default::default(), flatten: false, force: false, @@ -681,7 +744,6 @@ impl + Clone> DeploymentTxFactory { pub fn deploy_tokens( self, params: Vec, - fee_token: Option
, ) -> Result, ContractDeploymentError> where N::TransactionRequest: FoundryTransactionBuilder, @@ -703,9 +765,6 @@ impl + Clone> DeploymentTxFactory { // create the tx object. Since we're deploying a contract, `to` is `None` let mut tx = N::TransactionRequest::default(); tx.set_input(data); - if let Some(fee_token) = fee_token { - tx.set_fee_token(fee_token); - } Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } @@ -763,7 +822,7 @@ mod tests { "--chain-id", "9999", ]); - assert_eq!(args.chain_id(), Some(9999)); + assert_eq!(args.chain_id().map(|c| c.id()), Some(9999)); } #[test] diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 0c95841d1e653..245ba6e5b78e2 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -7,7 +7,7 @@ use syn::{ pub fn console_fmt(input: &DeriveInput) -> TokenStream { let name = &input.ident; let tokens = match &input.data { - Data::Struct(s) => derive_struct(s), + Data::Struct(s) => derive_struct(s, name), Data::Enum(e) => derive_enum(e), Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; @@ -18,8 +18,8 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { } } -fn derive_struct(s: &DataStruct) -> TokenStream { - let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); +fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream { + let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new())); quote! { fn fmt(&self, _spec: FormatSpec) -> String { #imp @@ -27,7 +27,7 @@ fn derive_struct(s: &DataStruct) -> TokenStream { } } -fn impl_struct(s: &DataStruct) -> Option { +fn impl_struct(s: &DataStruct, name: &Ident) -> Option { if s.fields.is_empty() { return None; } @@ -36,13 +36,49 @@ fn impl_struct(s: &DataStruct) -> Option { return None; } + let members = s.fields.members().collect::>(); let fields = s.fields.iter().collect::>(); + + // Detect table call structs: name must start with "table" (from the ABI function name) and + // all fields must be Vec types (Solidity arrays). Both conditions together prevent + // accidental table rendering for unrelated structs that happen to have Vec fields. + let is_table = name.to_string().starts_with("table") + && !fields.is_empty() + && fields.iter().all(|f| match &f.ty { + Type::Path(path) => path.path.segments.last().is_some_and(|seg| seg.ident == "Vec"), + _ => false, + }); + if is_table { + let member_ref = |m: &Member| match m { + Member::Named(ident) => quote!(&self.#ident), + Member::Unnamed(idx) => quote!(&self.#idx), + }; + let imp = if members.len() == 1 { + let vals = member_ref(&members[0]); + quote! { + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(None, &values) + } + } else { + let keys = member_ref(&members[0]); + let vals = member_ref(&members[1]); + quote! { + let keys: ::std::vec::Vec<&dyn ConsoleFmt> = + (#keys).iter().map(|v| v as &dyn ConsoleFmt).collect(); + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(Some(&keys), &values) + } + }; + return Some(imp); + } + let first_ty = match &fields.first().unwrap().ty { Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), _ => String::new(), }; - let members = s.fields.members().collect::>(); let args: Punctuated = members .into_iter() .map(|member| match member { diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 4146e07d2248b..acdcbbdbf95ea 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -23,7 +23,7 @@ foundry-evm-networks.workspace = true alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } foundry-linking.workspace = true forge-script-sequence.workspace = true diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 06f83886d0fd2..c1a6cb53bdbff 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index d22e12736d832..a6df9cda5e976 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -4,7 +4,7 @@ use std::{ env, fs::{self, File}, io::{Read, Seek, Write}, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, process::Command, sync::LazyLock, }; @@ -231,15 +231,59 @@ pub fn read_string(path: impl AsRef) -> String { /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + // Canonicalize the source once and treat it as the base directory for all traversal. + let base = src.canonicalize()?; + // Canonicalize the destination once and treat it as the base directory for all writes. + let dst_base = dst.canonicalize()?; + copy_dir_filtered_inner(src, dst, &base, &dst_base, true) } -fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { +fn copy_dir_filtered_inner( + src: &Path, + dst: &Path, + base_src: &Path, + base_dst: &Path, + is_root: bool, +) -> std::io::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + // Ensure that any path we operate on stays within the original source base directory. + let canonical_src_path = src_path.canonicalize()?; + if !canonical_src_path.starts_with(base_src) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + let relative_src_path = canonical_src_path.strip_prefix(base_src).map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "failed to derive relative path within source base directory", + ) + })?; + for component in relative_src_path.components() { + match component { + Component::Normal(name) => { + let name = name.to_string_lossy(); + if name.contains("..") || name.contains('/') || name.contains('\\') { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "invalid path component in source entry", + )); + } + } + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to access path outside of allowed source base directory", + )); + } + } + } + let dst_path = base_dst.join(relative_src_path); if ty.is_dir() { // Skip build artifact directories at the root level @@ -249,10 +293,58 @@ fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Re { continue; } + // Ensure that the destination directory stays within the allowed destination base. + let canonical_dst_dir = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // Directory does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_dir.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to create directory outside of allowed destination base directory", + )); + } fs::create_dir_all(&dst_path)?; - copy_dir_filtered_inner(&src_path, &dst_path, false)?; + copy_dir_filtered_inner(&src_path, &dst_path, base_src, base_dst, false)?; } else { - fs::copy(&src_path, &dst_path)?; + // Ensure that the destination file path stays within the allowed destination base. + let canonical_dst_path = dst_path.canonicalize().or_else(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + // File does not exist yet; ensure its parent is within base_dst. + if let Some(parent) = dst_path.parent() { + let parent_canonical = parent.canonicalize()?; + if !parent_canonical.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + } + Ok(dst_path.clone()) + } else { + Err(err) + } + })?; + if !canonical_dst_path.starts_with(base_dst) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "attempted to write file outside of allowed destination base directory", + )); + } + fs::copy(&canonical_src_path, &canonical_dst_path)?; } } Ok(()) diff --git a/npm/bun.lock b/npm/bun.lock index e8ef087be9d50..d9e8fed5ce479 100644 --- a/npm/bun.lock +++ b/npm/bun.lock @@ -1,12 +1,13 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3", + "typescript": "^6.0.2", }, }, }, @@ -35,7 +36,7 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], - "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], @@ -45,8 +46,12 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "bun-types/@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } } diff --git a/npm/scripts/publish.mjs b/npm/scripts/publish.mjs index 0f582c8ebc555..deee29e46c92f 100644 --- a/npm/scripts/publish.mjs +++ b/npm/scripts/publish.mjs @@ -7,17 +7,12 @@ import { colors } from '#const.mjs' const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' -const NPM_TOKEN = Bun.env.NPM_TOKEN -if (!NPM_TOKEN) throw new Error('NPM_TOKEN is required') - main().catch(error => { console.error(error) process.exit(1) }) async function main() { - const npmToken = Bun.env.NPM_TOKEN - if (!npmToken) throw new Error('NPM_TOKEN is required') const inputPath = Bun.argv[2] if (!inputPath) throw new Error('Package path is required') @@ -114,11 +109,6 @@ async function setPackageVersion(packagePath, version) { console.info(colors.green, 'Setting package version:', version) const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` .cwd(packagePath) - .env({ - ...Bun.env, - ...process.env, - NPM_TOKEN - }) .quiet() .nothrow() diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** diff --git a/npm/tsconfig.json b/npm/tsconfig.json index ca8e3c92f2daa..a7a0093d7ee56 100644 --- a/npm/tsconfig.json +++ b/npm/tsconfig.json @@ -2,7 +2,6 @@ "schema": "https://json.schemastore.org/tsconfig.json", "compilerOptions": { "strict": true, - "baseUrl": ".", "noEmit": true, "allowJs": true, "checkJs": true, From a40377f84edb43264e330a82ef9303d90b48de99 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:28:23 +0700 Subject: [PATCH 344/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From b300dc7fb82e5c662c3fcd9ea5ec2d96a8a015df Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:24:38 +0000 Subject: [PATCH 345/391] Potential fix for code scanning alert no. 19 (#471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 156: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create apisec-scan.yml (#404) * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Cr… * Delete .circleci directory (#423) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert 350 dargon789/gamefi (#424) * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production depende… * Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/config/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#412) * Update docker.yml * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * doc-script.js (#438) * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI p… * Remove duplicate logic in TxSigner::address() implementations (#447) Co-authored-by: Aganis * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#448) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: googleworkspace-bot * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Add docs, sidebar assets and Foundry deps (#459) * Add docs, sidebar assets and Foundry deps Add generated documentation assets (doc-script.js, doc-style.css, doc-filelist.js) under both doc/ and counter/doc/ to provide a collapsible sidebar/tree UI. Add counter/.gas-snapshot test gas output and update counter/.gitignore to ignore Soldeer dependencies. Update counter/foundry.toml to include a dependencies section and forge-std, and add remappings.txt plus soldeer.lock to pin the forge-std dependency. * Update counter/doc/doc-script.js Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (foundry-rs#14398 (#463) * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: bump alloy 2.0.1 (#14417) * chore(deps): bump docker/metadata-action from 5 to 6 (#415) Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/configure-pages from 5 to 6 (#428) Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 5 to 6. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/upload-pages-artifact from 3 to 5 (#430) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#431) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix(wallets): add gas limit margin to handle WebAuthn/P256 signing (#14416) fix(wallets): add gas limit margin to handle WebAuthn/P256 signing --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar Co-authored-by: googleworkspace-bot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: cui Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az Co-authored-by: steven Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> --- .circleci/ci_v1.yml | 31 -- .circleci/config.yml | 31 -- .github/scripts/tempo-mpp.sh | 18 +- .github/workflows/Docker.yml | 3 +- .github/workflows/benchmarks.yml | 2 +- .github/workflows/ci.yml | 6 +- .github/workflows/crate-checks.yml | 2 +- .github/workflows/docker.yml | 3 +- .github/workflows/docs.yml | 2 +- .github/workflows/nix.yml | 2 +- .github/workflows/npm.yml | 27 +- .github/workflows/release.yml | 6 +- .github/workflows/static.yml | 4 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- Cargo.lock | 363 +++++++++------- Cargo.toml | 58 +-- Makefile | 41 +- benches/src/lib.rs | 1 + counter/.gas-snapshot | 2 + counter/.gitignore | 4 + counter/doc/doc-filelist.js | 1 + counter/doc/doc-script.js | 228 ++++++++++ counter/doc/doc-style.css | 403 +++++++++++++++++ counter/foundry.toml | 5 +- counter/remappings.txt | 1 + counter/soldeer.lock | 6 + crates/anvil/src/cmd.rs | 125 +++++- crates/anvil/src/config.rs | 299 ++++++++----- crates/anvil/src/eth/api.rs | 12 +- crates/anvil/src/eth/backend/db.rs | 4 +- crates/anvil/src/eth/backend/fork.rs | 62 ++- crates/anvil/src/eth/backend/mem/fork_db.rs | 4 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 2 +- crates/anvil/src/eth/backend/mem/mod.rs | 12 +- crates/anvil/tests/it/fork.rs | 58 +++ crates/cast/Cargo.toml | 2 +- crates/cast/src/call_spec.rs | 10 +- crates/cast/src/cmd/erc20.rs | 113 ++++- crates/cast/src/cmd/send.rs | 14 +- crates/cast/src/tx.rs | 7 + crates/cli/Cargo.toml | 2 +- crates/cli/src/utils/mod.rs | 14 +- crates/cli/src/utils/suggestions.rs | 2 +- crates/common/Cargo.toml | 1 + crates/common/fmt/Cargo.toml | 1 + crates/common/fmt/src/console.rs | 107 +++++ crates/common/fmt/src/lib.rs | 2 +- crates/common/src/contracts.rs | 2 +- crates/common/src/provider/mod.rs | 128 +++++- crates/common/src/provider/mpp/persist.rs | 410 ++++++++---------- crates/common/src/provider/mpp/session.rs | 38 +- crates/common/src/tempo/mod.rs | 11 + crates/config/spec/src/lib.rs | 3 +- crates/config/src/endpoints.rs | 155 ++++++- crates/config/src/lib.rs | 4 + crates/doc/src/parser/comment.rs | 6 + crates/evm/abi/src/Console.json | 2 +- crates/evm/abi/src/console.py | 8 +- crates/evm/core/src/fork/database.rs | 2 +- crates/evm/evm/src/inspectors/logs.rs | 4 +- crates/forge/Cargo.toml | 2 +- crates/forge/src/cmd/create.rs | 139 ++++-- crates/lint/src/linter.rs | 4 +- crates/lint/src/sol/gas/keccak.rs | 2 +- crates/macros/src/console_fmt.rs | 46 +- crates/script/Cargo.toml | 2 +- crates/script/src/simulate.rs | 86 ++-- crates/test-utils/src/script.rs | 16 +- npm/bun.lock | 15 +- npm/scripts/publish.mjs | 10 - npm/tsconfig.json | 1 - 73 files changed, 2364 insertions(+), 843 deletions(-) delete mode 100644 .circleci/ci_v1.yml delete mode 100644 .circleci/config.yml create mode 100644 counter/.gas-snapshot create mode 100644 counter/doc/doc-filelist.js create mode 100644 counter/doc/doc-script.js create mode 100644 counter/doc/doc-style.css create mode 100644 counter/remappings.txt create mode 100644 counter/soldeer.lock diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml deleted file mode 100644 index 82c6de5b42b73..0000000000000 --- a/.circleci/ci_v1.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: 2.1 - -jobs: - build-and-test: - docker: - - image: cimg/rust:1.89.0 - steps: - - checkout - - restore_cache: - keys: - - v1-cargo-{{ checksum "Cargo.lock" }} - - v1-cargo- - - run: - name: "Check formatting" - command: cargo fmt -- --check - - run: - name: "Run tests" - command: cargo test - - save_cache: - key: v1-cargo-{{ checksum "Cargo.lock" }} - paths: - - "~/.cargo/bin" - - "~/.cargo/registry/index" - - "~/.cargo/registry/cache" - - "~/.cargo/git/db" - - "target" - -workflows: - ci: - jobs: - - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d5d401c51893c..0000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference -version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs -jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job - docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows -workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. - jobs: - - say-hello diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh index 1a98b1ed9c494..0d34c4a3b5fa5 100755 --- a/.github/scripts/tempo-mpp.sh +++ b/.github/scripts/tempo-mpp.sh @@ -110,7 +110,7 @@ BLOCK2=$("$CAST" block-number --rpc-url "$RPC_MPP") AFTER2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') SPENT2=$((BEFORE2 - AFTER2)) echo "Block: $BLOCK2" -echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/foundry/channels.json)" +echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/channels.db)" # 6. forge script via MPP echo "" @@ -129,9 +129,9 @@ contract MppCheck is Script { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" script "$TMPDIR/script/Mpp.s.sol" --rpc-url "$RPC_MPP" --root "$TMPDIR" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 7. forge test with vm.createSelectFork via MPP @@ -149,15 +149,15 @@ contract MppForkTest is Test { } } SOL -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$FORGE" test --match-test test_fork_via_mpp --root "$TMPDIR" -vvv -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 8. anvil fork via MPP echo "" echo "=== 8. anvil --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") "$ANVIL" --fork-url "$RPC_MPP" --port 8555 --silent & ANVIL_PID=$! for _ in $(seq 1 30); do @@ -167,15 +167,15 @@ done echo "chain-id: $("$CAST" chain-id --rpc-url http://localhost:8555)" kill $ANVIL_PID 2>/dev/null wait $ANVIL_PID 2>/dev/null -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" # 9. chisel fork via MPP echo "" echo "=== 9. chisel --fork-url (via MPP) ===" -VCNT_BEFORE=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo 'block.number' | "$CHISEL" --fork-url "$RPC_MPP" 2>&1 | grep -E "Decimal|Type" -VCNT_AFTER=$(grep cumulative_amount ~/.tempo/foundry/channels.json | awk -F'"' '{print $4}') +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" echo "" diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 5a2330e7d5d62..53b7d1ed0ac7c 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,7 +4,6 @@ on: push: tags: ["*"] branches: - - "main" pull_request: branches: ["**"] @@ -36,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 96af1f4ab9a1e..8465874854551 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -61,7 +61,7 @@ jobs: printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" - name: Build benchmark binary - run: cargo build --release --bin foundry-bench + run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c332240fea036..53d009de4c442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo test --workspace --doc + - run: cargo test --workspace --doc --locked typos: runs-on: depot-ubuntu-latest @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0 + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 shellcheck: runs-on: depot-ubuntu-latest @@ -92,7 +92,7 @@ jobs: - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --all-features --locked env: RUSTFLAGS: -Dwarnings diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index 781d63759045e..b2eb6261efd63 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -34,7 +34,7 @@ jobs: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - run: cargo hack check + - run: cargo hack check --locked issue: name: Open an issue diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5a2330e7d5d62..53b7d1ed0ac7c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,6 @@ on: push: tags: ["*"] branches: - - "main" pull_request: branches: ["**"] @@ -36,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a8a3dcbb8c7d3..288d9d127592f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: cargo doc --workspace --all-features --no-deps --document-private-items --locked env: RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - name: Setup Pages diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0a18c99a41f82..f6e068eeec62a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: DeterminateSystems/update-flake-lock@e80a657d7603606be0c69b117cfdc240f1e6af88 # main + - uses: DeterminateSystems/update-flake-lock@ff43f160ef7014ae1a1fd85699fb6a44f436135b # main with: pr-title: "Update flake.lock" pr-labels: | diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 007ef5f4286aa..938e85c9330be 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -31,6 +31,7 @@ jobs: contents: read actions: read id-token: write + environment: release name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: @@ -112,6 +113,22 @@ jobs: with: persist-credentials: false + - name: Validate Trusted Artifact Source + run: | + set -euo pipefail + + if [[ "${{ github.event_name }}" == "workflow_run" ]]; then + [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]] || { echo "ERROR: Upstream workflow_run did not succeed."; exit 1; } + [[ "${{ github.event.workflow_run.head_repository.full_name }}" == "${{ github.repository }}" ]] || { echo "ERROR: Upstream run repository mismatch."; exit 1; } + [[ "${{ github.event.workflow_run.event }}" != "pull_request" ]] || { echo "ERROR: Refusing artifacts from pull_request-triggered upstream runs."; exit 1; } + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + [[ -n "${{ inputs.run_id }}" ]] || { echo "ERROR: inputs.run_id is required for workflow_dispatch."; exit 1; } + [[ "${{ inputs.run_id }}" =~ ^[0-9]+$ ]] || { echo "ERROR: inputs.run_id must be numeric."; exit 1; } + else + echo "ERROR: Unsupported event: ${{ github.event_name }}" + exit 1 + fi + - name: Set Isolated Artifact Directory id: paths run: | @@ -151,9 +168,6 @@ jobs: id: release-version working-directory: ./npm env: - PROVENANCE: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | @@ -224,8 +238,6 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail @@ -282,12 +294,11 @@ jobs: contents: read actions: read id-token: write + environment: release needs: publish-arch name: Publish Meta Package runs-on: ubuntu-latest env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} steps: - name: Checkout @@ -323,6 +334,4 @@ jobs: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - NPM_TOKEN: ${{ env.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ad324e7ef9c1..52b33787de3f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,7 +179,7 @@ jobs: shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $RUST_PROFILE --bins + flags=(--locked --target $TARGET --profile $RUST_PROFILE --bins --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. @@ -279,7 +279,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -294,7 +294,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 0ba82305f82b2..fcfbbbbec7948 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -32,9 +32,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: # Upload entire repository path: '.' diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index af4415b1e8ba1..c0e4fcb5f3263 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -50,7 +50,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --no-fail-fast + run: cargo nextest run --locked --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. issue: diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index afd91d3d72c05..37c3edc9b916a 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -54,7 +54,7 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast + run: cargo nextest run --locked --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. issue-isolate: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9ccb850dbc5c..7c79cd266f152 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,7 +97,7 @@ jobs: run: pip --version && pip install vyper==0.4.3 - name: Foundry test cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/.foundry/cache @@ -121,4 +121,4 @@ jobs: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} - run: cargo nextest run ${{ matrix.flags }} + run: cargo nextest run --locked ${{ matrix.flags }} diff --git a/Cargo.lock b/Cargo.lock index d15f7c092e642..b3741acb52d7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,14 +84,14 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -116,14 +116,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbe4e5e9107bf6854e7550b666ca654ff2027eabf8153913e2e31ac4b089779" +checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -143,23 +143,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88fc7bbfb98cf5605a35aadf0ba43a7d9f1608d6f220d05e4fbd5144d3b0b625" +checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c16fa30b623e40a5b216da00f3b61870f5cbe863b59816ac1ecc2489515a40" +checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9252074d8b7028bae6844827f51ed8f8a036367604dce9d30d16cedfcc53577" +checksum = "250ba1168b8a049185a68c4dfa7f2a6a4046bd26fcc8c68632caeb216a5e12dc" dependencies = [ "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", ] @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb4919fa34b268842f434bfafa9c09136ab7b1a87ce0dd40a61befa35b5408c" +checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -310,7 +310,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "auto_impl", "borsh", "c-kzg", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbf60d85481b12fcc06e29fbe14584fdc5743a34ba7719037f210decd020761" +checksum = "34a8c1330ad33c95b5958573bca9a1ad0b419a51d76bb4c521556fbba8539b8d" dependencies = [ "alloy-contract", "alloy-primitives", @@ -344,7 +344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f107d0e588e5d25fcf2db216390445d5804b875a22a419407ad0389b925bb4d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -359,13 +359,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e111e22c1a2133e9ebfd9051ea0eaf63559594d2f50d43cbc6762fbb95fc3c2" +checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "borsh", "serde", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b6af6f374c1eeef8ab8dc26232cd440db167322a4207a3debd3d1ee565ca47" +checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -415,19 +415,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a3f5a7f3678b71d33fcc45b714fab8928dbc647d5aff2145e72032d5c849bb" +checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-sol-types", "async-trait", @@ -441,14 +441,14 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb50dc1fb0e0b2c8748d5bee1aa7acdd18f9e036311bc93a71d97be624030317" +checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] @@ -458,7 +458,7 @@ version = "0.30.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -513,13 +513,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ba5468f78c8893be2d68a7f2fda61753336e5653f006af19781001b5f99e6c" +checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffcefb5d3391a320eadb95d398e4135f8cc35c7bf29a6bdb357eadcfc5ee5638" +checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222fd4efff0fb9a25184684742c44fe9fa9a16c4ab5bf97583e71c86598ef8f0" +checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974df1e56405c27cb8242381f45d8b212ba9df5006046ccf704764a2a4634366" +checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -640,44 +640,44 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06bc10b0dca4f5bfc3cd30ed46eab5d651b5bb2cd300d683bdcdf5d2bfe6e82c" +checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949c0f16a94ae33cdb1139b8dbf9e34d7f26ebfe97962e2a4d620b5f65f48fe4" +checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8f7fa8ca056bb797a368aeed329e6ace6b62ee4271432ac36ab8ae87a5e60d" +checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301249e3c9e43661cfd7ebbb4746a00af6ce1ef58b5c968451882cd60438417d" +checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -702,15 +702,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59bc947935732cae5b072753e5e034c0b70a8b031c2839f45e2659ba07df9ae" +checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -722,17 +722,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc280a41931bd419af86e9e859dd9726b73313aaa2e479b33c0e344f4b892ddb" +checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -743,13 +743,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede0458c51bef23620aa6bd01a0b4f608be7bcb61d98e91b8530208ae545f3c2" +checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", "serde_json", "thiserror 2.0.18", @@ -757,13 +757,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f4df183248b57f3e0b99054b1b6786769d3fdff6d01a702234068140c8ba76" +checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "serde", ] @@ -780,9 +780,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4848831ff994c88b1c32b7df9c4c1c3eedea4b535bde5eb3c421ef0bdc5ac052" +checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" dependencies = [ "alloy-primitives", "serde", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b8ad9890b212e224291024b1aecfeef72127d27a2f6eebc5e347c40275c4bf" +checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -808,9 +808,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a57d1e72b1f9b11e5e71ebdab0569cb02277a462bbea6793fcaebfcd794ae9" +checksum = "a19d1985804e9a46d3b1b4f0654a68210b44007ffdaddd0e0a35d2b8db6cc1f0" dependencies = [ "alloy-consensus", "alloy-network", @@ -827,9 +827,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b27f20b5298b76a5a3b7cdbe6bdb184ab1ebd6e120e00dad748867673f5c90" +checksum = "34ce972f2ade53477e8e9336773a417f731fc7c02f41b9cd3b8a2a273e06363e" dependencies = [ "alloy-consensus", "alloy-network", @@ -845,9 +845,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c7acc40ffbfd37d4113eb619863099f3235d78d044006a1eecb94d8b0b2f1a" +checksum = "943c0105e0294b34cd06417129fadc591aed464d06f0614a7e998e585d27fbb1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -865,9 +865,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67d2372aada343130d41e249b59a3cef29b1678dcd3fd80f1c2c4d6b5318f2" +checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" dependencies = [ "alloy-consensus", "alloy-network", @@ -885,9 +885,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a09a865ae9e1f05478429ef0d935b16467f35c6e0b02cb10f23f66a3b33fc3" +checksum = "efe910cd3f56f7e4b26b8b7330b11c11c81286eaa8aa9fa6157e767a95e0f310" dependencies = [ "alloy-consensus", "alloy-network", @@ -902,9 +902,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb8218544ab635281f1be180a1cfd9b5d549db686faa7e85b3b2c10969819e" +checksum = "2296519a2342608afbd9baddd9d8df9f487604c3f1c2703b27bdd259f6018ea3" dependencies = [ "alloy-consensus", "alloy-network", @@ -991,9 +991,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b7b755e64ae6b5de0d762ed2c780e072167ea5e542076a559e00314352a0bf" +checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1014,9 +1014,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29980e69119444ed26b75e7ee5bed2043870f904a64318297e55800db686564" +checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -1030,9 +1030,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b27802653330740c88c28394cdaf1d55190b15b48ef9b99a946f37c9cdb19fa" +checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1050,9 +1050,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b71dc951db66795cfb52eef835f64cf15163bc93b656e061b457ce5ebff370" +checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1085,9 +1085,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8228b9236479ff16b03041b64b86c2bd4e53da1caa45d59b5868cd1571131e" +checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1229,7 +1229,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1241,7 +1241,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-beacon", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1302,12 +1302,12 @@ dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip5792", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "bytes", "foundry-common", "foundry-evm", @@ -2558,7 +2558,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -2730,7 +2730,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-ens", "alloy-evm", "alloy-hardforks", @@ -2742,7 +2742,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -3021,7 +3021,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3174,7 +3174,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3958,7 +3958,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4376,6 +4376,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fast-float2" version = "0.2.3" @@ -4693,7 +4705,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-json-abi", "alloy-network", @@ -4916,7 +4928,7 @@ version = "1.6.0" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-ens", "alloy-json-abi", "alloy-network", @@ -4978,7 +4990,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -5009,6 +5021,7 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-wallets", "itertools 0.14.0", "jiff", "mpp", @@ -5046,8 +5059,9 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "chrono", + "comfy-table", "eyre", "foundry-macros", "op-alloy-consensus", @@ -5285,7 +5299,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-sol-types", "anvil", "auto_impl", @@ -5381,7 +5395,7 @@ name = "foundry-evm-networks" version = "1.6.0" dependencies = [ "alloy-chains", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -5485,7 +5499,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5544,7 +5558,7 @@ dependencies = [ [[package]] name = "foundry-wallets" version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry-core?rev=7f401c1397af90a0a94ef7424a48bbf3dc0248cc#7f401c1397af90a0a94ef7424a48bbf3dc0248cc" +source = "git+https://github.com/foundry-rs/foundry-core?rev=d6c92dfcb41be26fbc1de21221a5bfc6bdd93245#d6c92dfcb41be26fbc1de21221a5bfc6bdd93245" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5568,6 +5582,7 @@ dependencies = [ "eth-keystore", "eyre", "rpassword", + "rusqlite", "serde", "serde_json", "tempo-primitives", @@ -5970,6 +5985,15 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -6565,7 +6589,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6920,6 +6944,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -7480,7 +7515,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7724,12 +7759,12 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "bytes", "derive_more", "reth-codecs 0.2.0", @@ -7778,12 +7813,12 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "op-alloy-consensus", "reth-rpc-traits 0.2.0", @@ -7798,11 +7833,11 @@ version = "0.24.0" source = "git+https://github.com/foundry-rs/optimism?branch=develop#0dc3c00bd1b3b2976f53503ba92fe84c602deca6" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -8464,7 +8499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8633,7 +8668,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9033,7 +9068,7 @@ source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed4 dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9053,7 +9088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a29541038ab108b2e9d527c66b565e717e252e4eef6675377fc21f9ba587f792" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9072,7 +9107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a79b3247ae4fbb1d4d35ce83a11fc596428a4c6ea836c98a75a55340192578a4" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9125,7 +9160,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9161,7 +9196,7 @@ name = "reth-db-models" version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "bytes", "modular-bitfield", @@ -9176,7 +9211,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9205,7 +9240,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs 0.3.0", @@ -9219,7 +9254,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "auto_impl", @@ -9241,7 +9276,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -9274,7 +9309,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -9307,7 +9342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96ffdb2ce0cdcd814d39428dc1e9b660c85d85e0b75eb465a1ed0943a48c7bd" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9332,7 +9367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc759fd87c3f65440e5d3bfa3107fe8a13a61a6807cd485c62c49d63c7bf6717" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9465,7 +9500,7 @@ version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9487,7 +9522,7 @@ name = "reth-storage-errors" version = "2.0.0" source = "git+https://github.com/paradigmxyz/reth?rev=0b33057#0b33057414c8815ed499e0aa837ae52d4d366150" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9509,7 +9544,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-trie", "arrayvec", "bytes", @@ -9930,6 +9965,20 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rusqlite" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -10000,7 +10049,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10059,7 +10108,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10070,9 +10119,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -10830,7 +10879,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10865,7 +10914,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11289,7 +11338,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11299,12 +11348,12 @@ source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe0 dependencies = [ "alloy-consensus", "alloy-contract", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "alloy-signer-local", "alloy-sol-types", "alloy-transport", @@ -11324,7 +11373,7 @@ name = "tempo-chainspec" version = "1.5.3" source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-evm", "alloy-genesis", "alloy-hardforks", @@ -11429,12 +11478,12 @@ version = "1.6.0" source = "git+https://github.com/tempoxyz/tempo?rev=bb08bb9#bb08bb905b6ee13fafe046aa9531aea2cdf60651" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips 2.0.1", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde 2.0.1", "aws-lc-rs", "base64 0.22.1", "derive_more", @@ -11494,7 +11543,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11504,7 +11553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12721,7 +12770,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12871,7 +12920,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 70d6eba6a6aec..7388b62a8acfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -357,36 +357,35 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## foundry-core -foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "7f401c1397af90a0a94ef7424a48bbf3dc0248cc", default-features = false } +foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "d6c92dfcb41be26fbc1de21221a5bfc6bdd93245", default-features = false } ## alloy -alloy-consensus = { version = "2.0.0", default-features = false } -alloy-contract = { version = "2.0.0", default-features = false } -alloy-eips = { version = "2.0.0", default-features = false } -alloy-eip5792 = { version = "2.0.0", default-features = false } -alloy-ens = { version = "2.0.0", default-features = false } -alloy-genesis = { version = "2.0.0", default-features = false } -alloy-json-rpc = { version = "2.0.0", default-features = false } -alloy-network = { version = "2.0.0", default-features = false } -alloy-provider = { version = "2.0.0", default-features = false } -alloy-pubsub = { version = "2.0.0", default-features = false } -alloy-rpc-client = { version = "2.0.0", default-features = false } -alloy-rpc-types = { version = "2.0.0", default-features = true } -alloy-rpc-types-beacon = { version = "2.0.0", default-features = true } -alloy-rpc-types-engine = { version = "2.0.0", default-features = false } -alloy-rpc-types-eth = { version = "2.0.0", default-features = false } -alloy-serde = { version = "2.0.0", default-features = false } -alloy-signer = { version = "2.0.0", default-features = false } -alloy-signer-aws = { version = "2.0.0", default-features = false } -alloy-signer-gcp = { version = "2.0.0", default-features = false } -alloy-signer-ledger = { version = "2.0.0", default-features = false } -alloy-signer-local = { version = "2.0.0", default-features = false } -alloy-signer-trezor = { version = "2.0.0", default-features = false } -alloy-signer-turnkey = { version = "2.0.0", default-features = false } -alloy-transport = { version = "2.0.0", default-features = false } -alloy-transport-http = { version = "2.0.0", default-features = false } -alloy-transport-ipc = { version = "2.0.0", default-features = false } -alloy-transport-ws = { version = "2.0.0", default-features = false } +alloy-consensus = { version = "2.0.1", default-features = false } +alloy-contract = { version = "2.0.1", default-features = false } +alloy-eips = { version = "2.0.1", default-features = false } +alloy-eip5792 = { version = "2.0.1", default-features = false } +alloy-ens = { version = "2.0.1", default-features = false } +alloy-genesis = { version = "2.0.1", default-features = false } +alloy-json-rpc = { version = "2.0.1", default-features = false } +alloy-network = { version = "2.0.1", default-features = false } +alloy-provider = { version = "2.0.1", default-features = false } +alloy-pubsub = { version = "2.0.1", default-features = false } +alloy-rpc-client = { version = "2.0.1", default-features = false } +alloy-rpc-types = { version = "2.0.1", default-features = true } +alloy-rpc-types-beacon = { version = "2.0.1", default-features = true } +alloy-rpc-types-engine = { version = "2.0.1", default-features = false } +alloy-rpc-types-eth = { version = "2.0.1", default-features = false } +alloy-serde = { version = "2.0.1", default-features = false } +alloy-signer = { version = "2.0.1", default-features = false } +alloy-signer-aws = { version = "2.0.1", default-features = false } +alloy-signer-gcp = { version = "2.0.1", default-features = false } +alloy-signer-ledger = { version = "2.0.1", default-features = false } +alloy-signer-local = { version = "2.0.1", default-features = false } +alloy-signer-trezor = { version = "2.0.1", default-features = false } +alloy-signer-turnkey = { version = "2.0.1", default-features = false } +alloy-transport = { version = "2.0.1", default-features = false } +alloy-transport-ipc = { version = "2.0.1", default-features = false } +alloy-transport-ws = { version = "2.0.1", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } alloy-op-hardforks = { version = "0.4.7", default-features = false } @@ -622,3 +621,6 @@ solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/sola solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } + +[workspace.metadata.cargo-shear] +ignored = ["idna_adapter", "cast", "chisel", "forge", "alloy-contract"] diff --git a/Makefile b/Makefile index fe768f19abf43..1bf20f4eeebd1 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ help: ## Display this help. .PHONY: build build: ## Build the project. - cargo build --features "$(FEATURES)" --profile "$(PROFILE)" + cargo build --locked --features "$(FEATURES)" --profile "$(PROFILE)" .PHONY: build-docker build-docker: ## Build the docker image. @@ -47,17 +47,17 @@ build-docker: ## Build the docker image. ## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). .PHONY: test-coverage test-coverage: - cargo +nightly llvm-cov --no-report nextest -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ - cargo +nightly llvm-cov --no-report --doc && \ + cargo +nightly llvm-cov --no-report nextest --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ + cargo +nightly llvm-cov --no-report --doc --locked && \ cargo +nightly llvm-cov report --doctests --open .PHONY: test-unit test-unit: ## Run unit tests. - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' + cargo nextest run --workspace --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .PHONY: test-doc test-doc: ## Run doc tests. - cargo test --doc --workspace + cargo test --doc --workspace --locked .PHONY: test test: ## Run all tests. @@ -77,6 +77,7 @@ lint-clippy: ## Run clippy on the codebase. --workspace \ --all-targets \ --all-features \ + --locked \ -- -D warnings .PHONY: lint-clippy-fix @@ -85,6 +86,7 @@ lint-clippy-fix: ## Run clippy on the codebase and fix warnings. --workspace \ --all-targets \ --all-features \ + --locked \ --fix \ --allow-dirty \ --allow-staged \ @@ -104,21 +106,46 @@ lint: ## Run all linters. $(MAKE) lint-clippy && \ $(MAKE) lint-typos +##@ Documentation + +.PHONY: doc +doc: ## Build the documentation. + RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options --show-type-layout --generate-link-to-definition" \ + cargo +nightly doc \ + --workspace \ + --all-features \ + --document-private-items \ + --no-deps \ + --locked + ##@ Other +.PHONY: lock +lock: ## Update the Cargo.lock file with the current dependencies. + cargo fetch + .PHONY: clean clean: ## Clean the project. cargo clean .PHONY: deny deny: ## Perform a `cargo` deny check. - cargo deny --all-features check all + cargo deny --locked --all-features check all + +.PHONY: check +check: ## Run a feature check on all crates and binaries. + cargo hack check --locked --feature-powerset --depth 1 + +.PHONY: shear +shear: ## Run `cargo shear` to check for unused dependencies. + cargo shear --locked .PHONY: pr pr: ## Run all checks and tests. $(MAKE) deny && \ $(MAKE) lint && \ - $(MAKE) test + $(MAKE) test && \ + $(MAKE) doc # dprint formatting commands .PHONY: dprint-fmt diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ab3bec614e3cd..516d73531007a 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -127,6 +127,7 @@ impl BenchmarkProject { let root = root_path.to_str().unwrap(); // Remove all files in the directory + let root_path = root_path.canonicalize()?; for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); diff --git a/counter/.gas-snapshot b/counter/.gas-snapshot new file mode 100644 index 0000000000000..ef525c09384e6 --- /dev/null +++ b/counter/.gas-snapshot @@ -0,0 +1,2 @@ +CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 30177, ~: 32354) +CounterTest:test_Increment() (gas: 31851) \ No newline at end of file diff --git a/counter/.gitignore b/counter/.gitignore index 85198aaa55b84..052b88bb6516b 100644 --- a/counter/.gitignore +++ b/counter/.gitignore @@ -12,3 +12,7 @@ docs/ # Dotenv file .env + + +# Soldeer +/dependencies diff --git a/counter/doc/doc-filelist.js b/counter/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/counter/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/counter/doc/doc-script.js b/counter/doc/doc-script.js new file mode 100644 index 0000000000000..62eeda3efc308 --- /dev/null +++ b/counter/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +let sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/counter/doc/doc-style.css b/counter/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/counter/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/counter/foundry.toml b/counter/foundry.toml index 25b918f9c9a96..c27b8ed21ba0b 100644 --- a/counter/foundry.toml +++ b/counter/foundry.toml @@ -1,6 +1,9 @@ [profile.default] src = "src" out = "out" -libs = ["lib"] +libs = ["lib", "dependencies"] + +[dependencies] +forge-std = "1.15.0" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/remappings.txt b/counter/remappings.txt new file mode 100644 index 0000000000000..6c93bbb0c4b12 --- /dev/null +++ b/counter/remappings.txt @@ -0,0 +1 @@ +forge-std-1.15.0/=dependencies/forge-std-1.15.0/ diff --git a/counter/soldeer.lock b/counter/soldeer.lock new file mode 100644 index 0000000000000..af6c8601cd7ba --- /dev/null +++ b/counter/soldeer.lock @@ -0,0 +1,6 @@ +[[dependencies]] +name = "forge-std" +version = "1.15.0" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_15_0_27-02-2026_08:26:17_forge-std-1.15.zip" +checksum = "40d9b3b3d786eec4cd05fb9d818616015cbe7b8866643a9f0854495c938588c4" +integrity = "92accf4f7850eb9f5832f0ea77d633d36ebe993efc6d6c9f32369d31befc8a75" diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 9dac717e1f8a2..07c345b4ce81a 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -224,6 +224,18 @@ impl NodeArgs { let compute_units_per_second = if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second }; + // Validate that secondary fork URLs don't have conflicting block number suffixes + if self.evm.fork_url.len() > 1 { + for fork in &self.evm.fork_url[1..] { + if fork.block.is_some() { + eyre::bail!( + "Block number suffixes (@block) on secondary --fork-url values are not supported. \ + Use --fork-block-number to set the fork block for all endpoints." + ); + } + } + } + let hardfork = match &self.hardfork { Some(hf) => { if self.evm.networks.is_optimism() { @@ -255,7 +267,7 @@ impl NodeArgs { _ => self .evm .fork_url - .as_ref() + .first() .and_then(|f| f.block) .map(|num| ForkChoice::Block(num as i128)), }) @@ -265,7 +277,7 @@ impl NodeArgs { .fork_request_retries(self.evm.fork_request_retries) .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) - .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) + .with_fork_urls(self.evm.fork_url.into_iter().map(|f| f.url).collect()) .with_base_fee(self.evm.block_base_fee_per_gas) .disable_min_priority_fee(self.evm.disable_min_priority_fee) .with_no_storage_caching(self.evm.no_storage_caching) @@ -421,6 +433,10 @@ pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. + /// + /// Multiple `--fork-url` flags can be provided to distribute requests across endpoints + /// using round-robin load balancing. On failure, the retry layer rotates to the next + /// endpoint. #[arg( long, short, @@ -428,7 +444,7 @@ pub struct AnvilEvmArgs { value_name = "URL", help_heading = "Fork config" )] - pub fork_url: Option, + pub fork_url: Vec, /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// @@ -625,13 +641,45 @@ pub struct AnvilEvmArgs { /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section /// of the project configuration file. /// Does nothing if the fork-url is not a configured alias. +/// +/// When an alias maps to an `RpcEndpoint` with multiple `endpoints`, all URLs are expanded +/// into additional `--fork-url` entries for multi-endpoint load balancing. impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { - if let Some(fork_url) = &self.fork_url - && let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) - && let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) - { - self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); + if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { + let mut resolved_urls = Vec::new(); + for fork_url in &self.fork_url { + let mut endpoints = config.rpc_endpoints.clone().resolved(); + if let Some(endpoint) = endpoints.remove(&fork_url.url) { + // Alias matched — expand all URLs from the endpoint config + match endpoint.all_urls() { + Ok(urls) => { + for (i, url) in urls.into_iter().enumerate() { + resolved_urls.push(ForkUrl { + url, + // Only the first URL inherits the block suffix + block: if i == 0 { fork_url.block } else { None }, + }); + } + } + Err(e) => { + warn!(target: "node", alias=%fork_url.url, %e, "could not resolve all endpoints, using primary endpoint only"); + if let Ok(url) = endpoint.url() { + resolved_urls.push(ForkUrl { url, block: fork_url.block }); + } else { + resolved_urls.push(fork_url.clone()); + } + } + } + } else if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { + // Try mesc or other resolution + resolved_urls.push(ForkUrl { url: url.to_string(), block: fork_url.block }); + } else { + // Not an alias — keep as-is + resolved_urls.push(fork_url.clone()); + } + } + self.fork_url = resolved_urls; } } } @@ -960,4 +1008,65 @@ mod tests { ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); } + + #[test] + fn can_parse_multiple_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545", + "--fork-url", + "http://localhost:8546", + "--fork-url", + "http://localhost:8547", + ]); + assert_eq!(args.evm.fork_url.len(), 3); + assert_eq!(args.evm.fork_url[0].url, "http://localhost:8545"); + assert_eq!(args.evm.fork_url[1].url, "http://localhost:8546"); + assert_eq!(args.evm.fork_url[2].url, "http://localhost:8547"); + + // Block suffix on first URL should work + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546", + ]); + assert_eq!(args.evm.fork_url[0].block, Some(1000000)); + assert_eq!(args.evm.fork_url[1].block, None); + } + + #[test] + fn rejects_block_suffix_on_secondary_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546@2000000", + ]); + let result = args.into_node_config(); + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains("Block number suffixes"), + "should reject block suffix on secondary fork URL" + ); + } + + #[test] + fn fork_dependent_args_require_fork_url() { + // All these args have `requires = "fork_url"` — they should fail without --fork-url + let cases = [ + vec!["anvil", "--fork-header", "X-Api-Key: test"], + vec!["anvil", "--timeout", "5000"], + vec!["anvil", "--retries", "3"], + vec!["anvil", "--fork-block-number", "100"], + vec!["anvil", "--fork-retry-backoff", "500"], + ]; + for args in &cases { + let result = NodeArgs::try_parse_from(args); + assert!(result.is_err(), "expected error when using {:?} without --fork-url", args[1]); + } + } } diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 56a5e4ddcfa81..23cd6e61bc076 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -3,7 +3,6 @@ use crate::{ eth::{ backend::{ db::{Db, SerializableState}, - env::Env, fork::{ClientFork, ClientForkConfig}, genesis::GenesisConfig, mem::fork_db::ForkedDatabase, @@ -38,19 +37,18 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{ - FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag, - spec_id_from_ethereum_hardfork, + hardfork::{FoundryHardfork, OpHardfork}, + utils::{ + apply_chain_and_block_specific_env_changes, block_env_from_header, + get_blob_base_fee_update_fraction, }, - utils::{apply_chain_and_block_specific_env_changes, get_blob_base_fee_update_fraction}, }; -use foundry_primitives::FoundryNetwork; +use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; -use op_revm::OpTransaction; use parking_lot::RwLock; use rand_08::thread_rng; use revm::{ - context::{BlockEnv, CfgEnv, TxEnv}, + context::{BlockEnv, CfgEnv}, context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId, }; @@ -62,6 +60,7 @@ use std::{ sync::Arc, time::Duration, }; +use tempo_chainspec::hardfork::TempoHardfork; use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; @@ -135,11 +134,13 @@ pub struct NodeConfig { pub port: u16, /// maximum number of transactions in a block pub max_transactions: usize, - /// url of the rpc server that should be used for any rpc calls - pub eth_rpc_url: Option, + /// Fork URLs for RPC calls. The first entry is the primary endpoint. + /// When multiple URLs are provided, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// pins the block number or transaction hash for the state fork pub fork_choice: Option, - /// headers to use with `eth_rpc_url` + /// headers to use with fork RPC endpoints pub fork_headers: Vec, /// specifies chain id for cache to skip fetching from remote in offline-start mode pub fork_chain_id: Option, @@ -269,12 +270,19 @@ Block number: {} Block hash: {:?} Chain ID: {} "#, - fork.eth_rpc_url(), + fork.eth_rpc_url().as_deref().unwrap_or("none"), fork.block_number(), fork.block_hash(), fork.chain_id() ); + if self.fork_urls.len() > 1 { + let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len()); + for (i, url) in self.fork_urls.iter().enumerate() { + let _ = writeln!(s, " ({i}) {url}"); + } + } + if let Some(tx_hash) = fork.transaction_hash() { let _ = writeln!(s, "Transaction hash: {tx_hash}"); } @@ -394,7 +402,7 @@ Genesis Number json!({ "available_accounts": available_accounts, "private_keys": private_keys, - "endpoint": fork.eth_rpc_url(), + "endpoint": fork.eth_rpc_url().unwrap_or_default(), "block_number": fork.block_number(), "block_hash": fork.block_hash(), "chain_id": fork.chain_id(), @@ -425,6 +433,12 @@ impl NodeConfig { Self { enable_tracing: true, port: 0, silent: true, ..Default::default() } } + /// Returns a test config with Tempo network enabled. + #[doc(hidden)] + pub fn test_tempo() -> Self { + Self { networks: NetworkConfigs::with_tempo(), ..Self::test() } + } + /// Returns a new config which does not initialize any accounts on node startup. pub fn empty_state() -> Self { Self { @@ -461,7 +475,7 @@ impl Default for NodeConfig { mixed_mining: false, port: NODE_PORT, max_transactions: 1_000, - eth_rpc_url: None, + fork_urls: vec![], fork_choice: None, account_generator: None, base_fee: None, @@ -506,20 +520,35 @@ impl Default for NodeConfig { impl NodeConfig { /// Returns the memory limit of the node #[must_use] - pub fn with_memory_limit(mut self, mems_value: Option) -> Self { + pub const fn with_memory_limit(mut self, mems_value: Option) -> Self { self.memory_limit = mems_value; self } - /// Returns the base fee to use + + /// Returns the base fee to use. + /// + /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+). pub fn get_base_fee(&self) -> u64 { + let default = if self.networks.is_tempo() { + TempoHardfork::from(self.get_hardfork()).base_fee() + } else { + INITIAL_BASE_FEE + }; self.base_fee .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) - .unwrap_or(INITIAL_BASE_FEE) + .unwrap_or(default) } - /// Returns the base fee to use + /// Returns the gas price to use. + /// + /// In Tempo mode, defaults to the hardfork-specific base fee. pub fn get_gas_price(&self) -> u128 { - self.gas_price.unwrap_or(INITIAL_GAS_PRICE) + let default = if self.networks.is_tempo() { + TempoHardfork::from(self.get_hardfork()).base_fee() as u128 + } else { + INITIAL_GAS_PRICE + }; + self.gas_price.unwrap_or(default) } pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { @@ -551,18 +580,21 @@ impl NodeConfig { if self.networks.is_optimism() { return OpHardfork::default().into(); } + if self.networks.is_tempo() { + return TempoHardfork::default().into(); + } EthereumHardfork::default().into() } /// Sets a custom code size limit #[must_use] - pub fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { + pub const fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { self.code_size_limit = code_size_limit; self } /// Disables code size limit #[must_use] - pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { + pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { if disable_code_size_limit { self.code_size_limit = Some(usize::MAX); } @@ -613,7 +645,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub const fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } @@ -622,7 +654,7 @@ impl NodeConfig { /// /// If set to `true` block gas limit will not be enforced #[must_use] - pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { + pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { self.disable_block_gas_limit = disable_block_gas_limit; self } @@ -631,14 +663,14 @@ impl NodeConfig { /// /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) #[must_use] - pub fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { + pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { self.enable_tx_gas_limit = enable_tx_gas_limit; self } /// Sets the gas price #[must_use] - pub fn with_gas_price(mut self, gas_price: Option) -> Self { + pub const fn with_gas_price(mut self, gas_price: Option) -> Self { self.gas_price = gas_price; self } @@ -662,7 +694,7 @@ impl NodeConfig { /// Sets the max number of transactions in a block #[must_use] - pub fn with_max_transactions(mut self, max_transactions: Option) -> Self { + pub const fn with_max_transactions(mut self, max_transactions: Option) -> Self { if let Some(max_transactions) = max_transactions { self.max_transactions = max_transactions; } @@ -681,14 +713,14 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee(mut self, base_fee: Option) -> Self { + pub const fn with_base_fee(mut self, base_fee: Option) -> Self { self.base_fee = base_fee; self } /// Disable the enforcement of a minimum suggested priority fee #[must_use] - pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { self.disable_min_priority_fee = disable_min_priority_fee; self } @@ -734,7 +766,7 @@ impl NodeConfig { /// Sets the hardfork #[must_use] - pub fn with_hardfork(mut self, hardfork: Option) -> Self { + pub const fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } @@ -788,21 +820,21 @@ impl NodeConfig { /// If set to `true` auto mining will be disabled #[must_use] - pub fn with_no_mining(mut self, no_mining: bool) -> Self { + pub const fn with_no_mining(mut self, no_mining: bool) -> Self { self.no_mining = no_mining; self } /// Sets the slots in an epoch #[must_use] - pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { self.slots_in_an_epoch = slots_in_an_epoch; self } /// Sets the port to use #[must_use] - pub fn with_port(mut self, port: u16) -> Self { + pub const fn with_port(mut self, port: u16) -> Self { self.port = port; self } @@ -827,15 +859,24 @@ impl NodeConfig { } #[must_use] - pub fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { + pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { self.no_storage_caching = no_storage_caching; self } - /// Sets the `eth_rpc_url` to use when forking + /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience). #[must_use] pub fn with_eth_rpc_url>(mut self, eth_rpc_url: Option) -> Self { - self.eth_rpc_url = eth_rpc_url.map(Into::into); + if let Some(url) = eth_rpc_url { + self.fork_urls = vec![url.into()]; + } + self + } + + /// Sets the fork URLs for load-balanced multi-endpoint forking. + #[must_use] + pub fn with_fork_urls(mut self, fork_urls: Vec) -> Self { + self.fork_urls = fork_urls; self } @@ -863,12 +904,12 @@ impl NodeConfig { /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { + pub const fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id; self } - /// Sets the `fork_headers` to use with `eth_rpc_url` + /// Sets the `fork_headers` to use with fork RPC endpoints #[must_use] pub fn with_fork_headers(mut self, headers: Vec) -> Self { self.fork_headers = headers; @@ -877,7 +918,7 @@ impl NodeConfig { /// Sets the `fork_request_timeout` to use for requests #[must_use] - pub fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { + pub const fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { if let Some(fork_request_timeout) = fork_request_timeout { self.fork_request_timeout = fork_request_timeout; } @@ -886,7 +927,7 @@ impl NodeConfig { /// Sets the `fork_request_retries` to use for spurious networks #[must_use] - pub fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { + pub const fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { if let Some(fork_request_retries) = fork_request_retries { self.fork_request_retries = fork_request_retries; } @@ -895,7 +936,7 @@ impl NodeConfig { /// Sets the initial `fork_retry_backoff` for rate limits #[must_use] - pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { + pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { if let Some(fork_retry_backoff) = fork_retry_backoff { self.fork_retry_backoff = fork_retry_backoff; } @@ -906,7 +947,10 @@ impl NodeConfig { /// /// See also, #[must_use] - pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { + pub const fn fork_compute_units_per_second( + mut self, + compute_units_per_second: Option, + ) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { self.compute_units_per_second = compute_units_per_second; } @@ -915,35 +959,35 @@ impl NodeConfig { /// Sets whether to enable tracing #[must_use] - pub fn with_tracing(mut self, enable_tracing: bool) -> Self { + pub const fn with_tracing(mut self, enable_tracing: bool) -> Self { self.enable_tracing = enable_tracing; self } /// Sets whether to enable steps tracing #[must_use] - pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { + pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { self.enable_steps_tracing = enable_steps_tracing; self } /// Sets whether to print `console.log` invocations to stdout. #[must_use] - pub fn with_print_logs(mut self, print_logs: bool) -> Self { + pub const fn with_print_logs(mut self, print_logs: bool) -> Self { self.print_logs = print_logs; self } /// Sets whether to print traces to stdout. #[must_use] - pub fn with_print_traces(mut self, print_traces: bool) -> Self { + pub const fn with_print_traces(mut self, print_traces: bool) -> Self { self.print_traces = print_traces; self } /// Sets whether to enable autoImpersonate #[must_use] - pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { + pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { self.enable_auto_impersonate = enable_auto_impersonate; self } @@ -962,7 +1006,7 @@ impl NodeConfig { } #[must_use] - pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { + pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { self.transaction_order = transaction_order; self } @@ -991,7 +1035,7 @@ impl NodeConfig { /// /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { - if self.no_storage_caching || self.eth_rpc_url.is_none() { + if self.no_storage_caching || self.fork_urls.is_empty() { return None; } let chain_id = self.get_chain_id(); @@ -1001,14 +1045,14 @@ impl NodeConfig { /// Sets whether to disable the default create2 deployer #[must_use] - pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { self.disable_default_create2_deployer = yes; self } /// Sets whether to disable pool balance checks #[must_use] - pub fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { + pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { self.disable_pool_balance_checks = yes; self } @@ -1022,19 +1066,33 @@ impl NodeConfig { /// Enable features for provided networks. #[must_use] - pub fn with_networks(mut self, networks: NetworkConfigs) -> Self { + pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self { self.networks = networks; self } + /// Enable Tempo network features. + #[must_use] + pub fn with_tempo(mut self) -> Self { + self.networks = NetworkConfigs::with_tempo(); + self + } + + /// Enable Optimism network features. + #[must_use] + pub fn with_optimism(mut self) -> Self { + self.networks = NetworkConfigs::with_optimism(); + self + } + /// Makes the node silent to not emit anything on stdout #[must_use] - pub fn silent(self) -> Self { + pub const fn silent(self) -> Self { self.set_silent(true) } #[must_use] - pub fn set_silent(mut self, silent: bool) -> Self { + pub const fn set_silent(mut self, silent: bool) -> Self { self.silent = silent; self } @@ -1053,7 +1111,13 @@ impl NodeConfig { /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> Result> { + pub(crate) async fn setup(&mut self) -> Result> + where + N: alloy_network::Network< + TxEnvelope = foundry_primitives::FoundryTxEnvelope, + ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope, + >, + { // configure the revm environment let mut cfg = CfgEnv::default(); @@ -1076,20 +1140,13 @@ impl NodeConfig { } let spec_id = cfg.spec; - let mut env = Env::new( - EvmEnv::new( - cfg, - BlockEnv { - gas_limit: self.gas_limit(), - basefee: self.get_base_fee(), - ..Default::default() - }, - ), - OpTransaction { - base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, + let mut evm_env = EvmEnv::new( + cfg, + BlockEnv { + gas_limit: self.gas_limit(), + basefee: self.get_base_fee(), ..Default::default() }, - self.networks, ); let base_fee_params: BaseFeeParams = @@ -1106,8 +1163,8 @@ impl NodeConfig { ); let (db, fork): (Arc>>, Option) = - if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { - self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? + if let Some(eth_rpc_url) = self.fork_urls.first().cloned() { + self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await? } else { (Arc::new(TokioRwLock::new(Box::::default())), None) }; @@ -1117,16 +1174,16 @@ impl NodeConfig { // --chain-id flag gets precedence over the genesis.json chain id // if self.chain_id.is_none() { - env.evm_env.cfg_env.chain_id = genesis.config.chain_id; + evm_env.cfg_env.chain_id = genesis.config.chain_id; } - env.evm_env.block_env.timestamp = U256::from(genesis.timestamp); + evm_env.block_env.timestamp = U256::from(genesis.timestamp); if let Some(base_fee) = genesis.base_fee_per_gas { - env.evm_env.block_env.basefee = base_fee.try_into()?; + evm_env.block_env.basefee = base_fee.try_into()?; } if let Some(number) = genesis.number { - env.evm_env.block_env.number = U256::from(number); + evm_env.block_env.number = U256::from(number); } - env.evm_env.block_env.beneficiary = genesis.coinbase; + evm_env.block_env.beneficiary = genesis.coinbase; } let genesis = GenesisConfig { @@ -1149,7 +1206,8 @@ impl NodeConfig { // only memory based backend for now let backend = mem::Backend::with_genesis( db, - Arc::new(RwLock::new(env)), + Arc::new(RwLock::new(evm_env)), + self.networks, genesis, fees, Arc::new(RwLock::new(fork)), @@ -1168,17 +1226,13 @@ impl NodeConfig { // Writes the default create2 deployer to the backend, // if the option is not disabled and we are not forking. - if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { + if !self.disable_default_create2_deployer && self.fork_urls.is_empty() { backend .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await .wrap_err("failed to create default create2 deployer")?; } - if let Some(state) = self.init_state.clone() { - backend.load_state(state).await.wrap_err("failed to load init state")?; - } - Ok(backend) } @@ -1191,10 +1245,10 @@ impl NodeConfig { pub async fn setup_fork_db( &mut self, eth_rpc_url: String, - env: &mut Env, + evm_env: &mut EvmEnv, fees: &FeeManager, ) -> Result<(Arc>>, Option)> { - let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; + let (db, config) = self.setup_fork_db_config(eth_rpc_url, evm_env, fees).await?; let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); let fork = ClientFork::new(config, Arc::clone(&db)); Ok((db, Some(fork))) @@ -1208,10 +1262,14 @@ impl NodeConfig { pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, - env: &mut Env, + evm_env: &mut EvmEnv, fees: &FeeManager, ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); + + // Always bootstrap with the primary URL only to avoid race conditions + // where discovery calls (get_chain_id, find_latest_fork_block, get_block) + // hit different endpoints that may be at different chain tips. let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) @@ -1233,16 +1291,8 @@ impl NodeConfig { let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { - // Auto-adjust hardfork if not specified, but only if we're forking mainnet. let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; - if alloy_chains::NamedChain::Mainnet == chain_id { - let hardfork: EthereumHardfork = - ethereum_hardfork_from_block_tag(fork_block_number); - - env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); - self.hardfork = Some(FoundryHardfork::Ethereum(hardfork)); - } Some(U256::from(chain_id)) } else { None @@ -1284,17 +1334,12 @@ latest block number: {latest_block}" let gas_limit = self.fork_gas_limit(&block); self.gas_limit = Some(gas_limit); - env.evm_env.block_env = BlockEnv { - number: U256::from(fork_block_number), - timestamp: U256::from(block.header.timestamp()), - difficulty: block.header.difficulty(), - // ensures prevrandao is set - prevrandao: Some(block.header.mix_hash().unwrap_or_default()), + evm_env.block_env = BlockEnv { gas_limit, // Keep previous `coinbase` and `basefee` value - beneficiary: env.evm_env.block_env.beneficiary, - basefee: env.evm_env.block_env.basefee, - ..Default::default() + beneficiary: evm_env.block_env.beneficiary, + basefee: evm_env.block_env.basefee, + ..block_env_from_header(&block.header) }; // Determine chain_id early so we can use it consistently @@ -1309,17 +1354,25 @@ latest block number: {latest_block}" // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); - env.evm_env.cfg_env.chain_id = chain_id; - env.tx.base.chain_id = chain_id.into(); + evm_env.cfg_env.chain_id = chain_id; chain_id }; + // Auto-detect hardfork from chain activation data if not explicitly set. + if self.hardfork.is_none() + && let Some(hardfork) = + FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp()) + { + evm_env.cfg_env.spec = SpecId::from(hardfork); + self.hardfork = Some(hardfork); + } + // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() && let Some(base_fee) = block.header.base_fee_per_gas() { self.base_fee = Some(base_fee); - env.evm_env.block_env.basefee = base_fee; + evm_env.block_env.basefee = base_fee; // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = fees.get_next_block_base_fee_per_gas( @@ -1338,7 +1391,7 @@ latest block number: {latest_block}" // Derive blob params using the fork block timestamp regardless of explicit base fee. let blob_params = get_blob_params(chain_id, block.header.timestamp()); - env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( + evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( blob_excess_gas, blob_params.update_fraction as u64, )); @@ -1365,19 +1418,38 @@ latest block number: {latest_block}" let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id - apply_chain_and_block_specific_env_changes::( - &mut env.evm_env, + apply_chain_and_block_specific_env_changes::( + evm_env, &block, self.networks, ); - let meta = BlockchainDbMeta::new(env.evm_env.block_env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) } else { BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) }; + // After bootstrap, rebuild the provider with round-robin if multiple URLs are + // configured. This ensures bootstrap used only the primary endpoint for consistency, + // while ongoing requests are distributed across all endpoints. + let provider = if self.fork_urls.len() > 1 { + debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider"); + Arc::new( + ProviderBuilder::new(ð_rpc_url) + .timeout(self.fork_request_timeout) + .initial_backoff(self.fork_retry_backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .max_retry(self.fork_request_retries) + .headers(self.fork_headers.clone()) + .build_fallback(self.fork_urls.clone()) + .wrap_err("failed to establish round-robin provider to fork urls")?, + ) + } else { + provider + }; + // This will spawn the background thread that will use the provider to fetch // blockchain data from the other client let backend = SharedBackend::spawn_backend( @@ -1388,7 +1460,7 @@ latest block number: {latest_block}" .await; let config = ClientForkConfig { - eth_rpc_url, + fork_urls: self.fork_urls.clone(), block_number: fork_block_number, block_hash, transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()), @@ -1401,9 +1473,10 @@ latest block number: {latest_block}" retries: self.fork_request_retries, backoff: self.fork_retry_backoff, compute_units_per_second: self.compute_units_per_second, + headers: self.fork_headers.clone(), total_difficulty: block.header.total_difficulty.unwrap_or_default(), blob_gas_used: block.header.blob_gas_used().map(|g| g as u128), - blob_excess_gas_and_price: env.evm_env.block_env.blob_excess_gas_and_price, + blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price, force_transactions, }; @@ -1451,7 +1524,7 @@ latest block number: {latest_block}" async fn derive_block_and_transactions( fork_choice: &ForkChoice, provider: &Arc, -) -> eyre::Result<(BlockNumber, Option>)> { +) -> eyre::Result<(BlockNumber, Option>>)> { match fork_choice { ForkChoice::Block(block_number) => { let block_number = *block_number; @@ -1513,7 +1586,7 @@ pub enum ForkChoice { impl ForkChoice { /// Returns the block number to fork from - pub fn block_number(&self) -> Option { + pub const fn block_number(&self) -> Option { match self { Self::Block(block_number) => Some(*block_number), Self::Transaction(_) => None, @@ -1521,7 +1594,7 @@ impl ForkChoice { } /// Returns the transaction hash to fork from - pub fn transaction_hash(&self) -> Option { + pub const fn transaction_hash(&self) -> Option { match self { Self::Block(_) => None, Self::Transaction(transaction_hash) => Some(*transaction_hash), @@ -1551,12 +1624,12 @@ pub struct PruneStateHistoryConfig { impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported - pub fn is_state_history_supported(&self) -> bool { + pub const fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } /// Returns true if this setting was enabled. - pub fn is_config_enabled(&self) -> bool { + pub const fn is_config_enabled(&self) -> bool { self.enabled } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index f48a2d96cc12c..0a0fd97d06556 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -416,7 +416,7 @@ impl EthApi { let config = fork.config.read(); NodeForkConfig { - fork_url: Some(config.eth_rpc_url.clone()), + fork_url: config.eth_rpc_url().map(|s| s.to_string()), fork_block_number: Some(config.block_number), fork_retry_backoff: Some(config.backoff.as_millis()), } @@ -527,7 +527,7 @@ impl EthApi { /// Sets the backend rpc url /// /// Handler for ETH RPC call: `anvil_setRpcUrl` - pub fn anvil_set_rpc_url(&self, url: String) -> Result<()> { + pub async fn anvil_set_rpc_url(&self, url: String) -> Result<()> { node_info!("anvil_setRpcUrl"); if let Some(fork) = self.backend.get_fork() { let mut config = fork.config.write(); @@ -543,9 +543,11 @@ impl EthApi { )?, // .interval(interval), ); config.provider = new_provider; - trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); - config.eth_rpc_url = url; + trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url().unwrap_or("none"), url); + config.fork_urls = vec![url.clone()]; } + // Keep node_config in sync so anvil_reset(None) uses the updated URL + self.backend.node_config.write().await.fork_urls = vec![url]; Ok(()) } @@ -1791,7 +1793,7 @@ impl EthApi { EthRequest::EvmMineDetailed(mine) => { self.evm_mine_detailed(mine.and_then(|p| p.params)).await.to_rpc_result() } - EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).to_rpc_result(), + EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).await.to_rpc_result(), EthRequest::EthSendUnsignedTransaction(tx) => { self.eth_send_unsigned_transaction(*tx).await.to_rpc_result() } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 14691862a225e..71764468fab99 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -83,7 +83,7 @@ where /// Helper trait to reset the DB if it's forked pub trait MaybeForkedDatabase { - fn maybe_reset(&mut self, _url: Option, block_number: BlockId) -> Result<(), String>; + fn maybe_reset(&mut self, _urls: Vec, block_number: BlockId) -> Result<(), String>; fn maybe_flush_cache(&self) -> Result<(), String>; @@ -301,7 +301,7 @@ impl + Debug> MaybeFullDatabase for CacheD } impl> MaybeForkedDatabase for CacheDB { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index b1bff4e66cd21..bc87fe2fc2052 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -97,8 +97,8 @@ impl ClientFork { self.config.read().block_hash } - pub fn eth_rpc_url(&self) -> String { - self.config.read().eth_rpc_url.clone() + pub fn eth_rpc_url(&self) -> Option { + self.config.read().eth_rpc_url().map(|s| s.to_string()) } pub fn chain_id(&self) -> u64 { @@ -269,7 +269,7 @@ impl ClientFork { /// Reset the fork to a fresh forked state, and optionally update the fork config pub async fn reset( &self, - url: Option, + urls: Vec, block_number: impl Into, ) -> Result<(), BlockchainError> { let block_number = block_number.into(); @@ -277,12 +277,12 @@ impl ClientFork { self.database .write() .await - .maybe_reset(url.clone(), block_number) + .maybe_reset(urls.clone(), block_number) .map_err(BlockchainError::Internal)?; } - if let Some(url) = url { - self.config.write().update_url(url)?; + if !urls.is_empty() { + self.config.write().update_urls(urls)?; let override_chain_id = self.config.read().override_chain_id; let chain_id = if let Some(chain_id) = override_chain_id { chain_id @@ -629,7 +629,10 @@ impl ClientFork { /// Contains all fork metadata #[derive(Clone, Debug)] pub struct ClientForkConfig { - pub eth_rpc_url: String, + /// All fork URLs. The first entry is the primary endpoint. + /// When multiple URLs are present, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// The block number of the forked block pub block_number: u64, /// The hash of the forked block @@ -655,6 +658,8 @@ pub struct ClientForkConfig { pub backoff: Duration, /// available CUPS pub compute_units_per_second: u64, + /// Headers to include with RPC requests + pub headers: Vec, /// total difficulty of the chain until this block pub total_difficulty: U256, /// Transactions to force include in the forked chain @@ -662,27 +667,40 @@ pub struct ClientForkConfig { } impl ClientForkConfig { - /// Updates the provider URL + /// Returns the primary RPC URL (first entry in `fork_urls`). + pub fn eth_rpc_url(&self) -> Option<&str> { + self.fork_urls.first().map(|s| s.as_str()) + } + + /// Updates the provider URLs /// /// # Errors /// /// This will fail if no new provider could be established (erroneous URL) - fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { - // let interval = self.provider.get_interval(); - self.provider = Arc::new( - ProviderBuilder::::new(url.as_str()) - .timeout(self.timeout) - // .timeout_retry(self.retries) - .max_retry(self.retries) - .initial_backoff(self.backoff.as_millis() as u64) - .compute_units_per_second(self.compute_units_per_second) - .build() - .map_err(|e| BlockchainError::InvalidUrl(format!("{url}: {e}")))?, /* .interval(interval), */ - ); - trace!(target: "fork", "Updated rpc url {}", url); - self.eth_rpc_url = url; + fn update_urls(&mut self, urls: Vec) -> Result<(), BlockchainError> { + let primary = urls.first().ok_or_else(|| { + BlockchainError::InvalidUrl("at least one fork URL required".to_string()) + })?; + + let builder = ProviderBuilder::::new(primary.as_str()) + .timeout(self.timeout) + .max_retry(self.retries) + .initial_backoff(self.backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .headers(self.headers.clone()); + + self.provider = Arc::new(if urls.len() > 1 { + builder + .build_fallback(urls.clone()) + .map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + } else { + builder.build().map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + }); + trace!(target: "fork", "Updated fork urls: {:?}", urls); + self.fork_urls = urls; Ok(()) } + /// Updates the block forked off `(block number, block hash, timestamp)` pub fn update_block( &mut self, diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 308cdd2e4334c..497e8ac8e4428 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -156,8 +156,8 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { } impl MaybeForkedDatabase for ForkedDatabase { - fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { - self.reset(url, block_number) + fn maybe_reset(&mut self, urls: Vec, block_number: BlockId) -> Result<(), String> { + self.reset(urls, block_number) } fn maybe_flush_cache(&self) -> Result<(), String> { diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 766805ee7ae9d..1d06ad03cae2f 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -128,7 +128,7 @@ impl MaybeFullDatabase for MemDb { } impl MaybeForkedDatabase for MemDb { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index ed928ef25213d..b538086fe6f10 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -246,7 +246,7 @@ pub struct Backend { prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, - node_config: Arc>, + pub(crate) node_config: Arc>, /// Slots in an epoch slots_in_an_epoch: u64, /// Precompiles to inject to the EVM. @@ -2079,6 +2079,7 @@ impl Backend { // we want to force the correct base fee for the next block during // `setup_fork_db_config` node_config.base_fee.take(); + node_config.fork_urls = vec![eth_rpc_url.clone()]; node_config.setup_fork_db_config(eth_rpc_url, &mut evm_env, &self.fees).await? }; @@ -2101,7 +2102,9 @@ impl Backend { let block_number = forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); // reset the fork entirely and reapply the genesis config - fork.reset(forking.json_rpc_url.clone(), block_number).await?; + let reset_urls = + forking.json_rpc_url.as_ref().map(|url| vec![url.clone()]).unwrap_or_default(); + fork.reset(reset_urls, block_number).await?; let fork_block_number = fork.block_number(); let fork_block = fork .block_by_number(fork_block_number) @@ -2115,7 +2118,8 @@ impl Backend { // If rpc url is unspecified, then update the fork with the new block number and // existing rpc url, this updates the cache path { - let maybe_fork_url = { self.node_config.read().await.eth_rpc_url.clone() }; + let maybe_fork_url = + { self.node_config.read().await.fork_urls.first().cloned() }; if let Some(fork_url) = maybe_fork_url { self.reset_block_number(fork_url, fork_block_number).await?; } @@ -2229,6 +2233,8 @@ impl Backend { ) -> Result<(), BlockchainError> { let mut node_config = self.node_config.write().await; node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128)); + // Update fork_urls so setup_fork_db_config uses the correct URL set + node_config.fork_urls = vec![fork_url.clone()]; let mut evm_env = self.evm_env.read().clone(); let (forked_db, client_fork_config) = diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index e802d8a19be7b..37d3f6f4a1dc4 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -2011,3 +2011,61 @@ async fn test_config_with_osaka_hardfork_with_precompile_factory() { &expected_system_contracts, ); } + +// Regression tests: verify that `anvil_setRpcUrl` and `anvil_reset` keep +// `ClientForkConfig.fork_urls` in sync so that subsequent resets don't +// silently revert to stale URLs. + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_rpc_url_syncs_fork_config() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Verify initial fork URL + let fork = api.backend.get_fork().unwrap(); + assert_eq!(fork.config.read().fork_urls, vec![origin_url.clone()]); + + // Spawn a second origin to use as the new URL + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Set RPC URL via the API + api.anvil_set_rpc_url(new_url.clone()).await.unwrap(); + + // Verify ClientForkConfig is updated + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should be updated after anvil_setRpcUrl" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_reset_with_url_updates_fork_urls() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Spawn a second origin + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Reset fork with a new URL + api.anvil_reset(Some(Forking { json_rpc_url: Some(new_url.clone()), block_number: None })) + .await + .unwrap(); + + // Verify the fork config uses the new URL, not the old one + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should reflect the new URL after anvil_reset" + ); +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dbd89ce88827d..56164c8f6367a 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -28,7 +28,7 @@ foundry-compilers.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } forge-fmt.workspace = true alloy-chains.workspace = true diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs index b39e024f38af5..0047b56d25f03 100644 --- a/crates/cast/src/call_spec.rs +++ b/crates/cast/src/call_spec.rs @@ -145,8 +145,8 @@ impl FromStr for CallSpec { /// Parse a value string that can be in ether notation (e.g., "0.1ether") or raw wei. fn parse_ether_or_wei(s: &str) -> Result { // Use alloy's DynSolType coercion which handles "1ether", "1gwei", "1000" etc. - if s.starts_with("0x") { - U256::from_str_radix(s, 16).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) + if s.starts_with("0x") || s.starts_with("0X") { + U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s) .wrap_err_with(|| format!("Invalid value '{s}'"))? @@ -180,6 +180,12 @@ mod tests { assert!(spec.sig.is_none()); } + #[test] + fn test_parse_hex_value() { + assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16)); + } + #[test] fn test_parse_with_sig() { let spec = CallSpec::parse( diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index e629b8eca0d5a..27e33ec03e7e7 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -3,13 +3,13 @@ use std::{str::FromStr, time::Duration}; use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, format_uint_exp, - tx::{SendTxOpts, TxParams}, + tx::{CastTxSender, SendTxOpts, TxParams}, }; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network}; -use alloy_primitives::U256; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256}; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; @@ -23,11 +23,13 @@ use foundry_common::{ fmt::{UIfmt, UIfmtReceiptExt}, provider::{ProviderBuilder, RetryProviderWithSigner}, shell, + tempo::TEMPO_BROWSER_GAS_BUFFER, }; #[doc(hidden)] pub use foundry_config::{Chain, utils::*}; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::PATH_USD_ADDRESS; sol! { #[sol(rpc)] @@ -281,6 +283,34 @@ impl Erc20Subcommand { } } + const fn uses_browser_send(&self) -> bool { + match self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => send_tx.browser.browser, + _ => false, + } + } + + async fn should_use_tempo_network( + &self, + tempo_access_key: &Option, + ) -> eyre::Result { + if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some() + { + return Ok(true); + } + + if self.uses_browser_send() { + let config = self.rpc_opts().load_config()?; + return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo()); + } + + Ok(false) + } + pub async fn run(self) -> eyre::Result<()> { // Resolve the signer once for state-changing variants. let (signer, tempo_access_key) = match &self { @@ -299,8 +329,7 @@ impl Erc20Subcommand { _ => (None, None), }; - let is_tempo = self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) - || tempo_access_key.is_some(); + let is_tempo = self.should_use_tempo_network(&tempo_access_key).await?; if is_tempo { self.run_generic::(signer, tempo_access_key).await @@ -356,6 +385,28 @@ impl Erc20Subcommand { timeout, ) .await? + } else if let Some(browser) = $send_tx.browser.run::().await? { + let $provider = ProviderBuilder::::from_config(&config)?.build()?; + if let Some(interval) = $send_tx.poll_interval { + $provider.client().set_poll_interval(Duration::from_secs(interval)); + } + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + let chain = get_chain(config.chain, &$provider).await?; + $tx_opts.apply::(&mut tx, chain.is_legacy()); + if chain.is_tempo() && tx.fee_token().is_none() { + tx.set_fee_token(PATH_USD_ADDRESS); + } + fill_tx(&$provider, &mut tx, browser.address(), chain).await?; + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&$provider) + .print_tx_result( + tx_hash, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? } else { let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); let $provider = build_provider_with_signer::(&$send_tx, signer)?; @@ -503,3 +554,55 @@ impl Erc20Subcommand { Ok(()) } } + +/// Fills from, chain_id, nonce, fees, and gas limit on a transaction request for the browser +/// wallet path. Mirrors the filling logic in the shared tx builder but operates on a +/// pre-built transaction request from the sol! macro rather than through the builder pipeline. +/// Only fills fields that haven't already been set by the user. +async fn fill_tx>( + provider: &P, + tx: &mut N::TransactionRequest, + from: Address, + chain: Chain, +) -> eyre::Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + tx.set_from(from); + tx.set_chain_id(chain.id()); + + if tx.nonce().is_none() { + tx.set_nonce(provider.get_transaction_count(from).await?); + } + + let legacy = chain.is_legacy(); + + if legacy { + if tx.gas_price().is_none() { + tx.set_gas_price(provider.get_gas_price().await?); + } + } else if tx.max_fee_per_gas().is_none() || tx.max_priority_fee_per_gas().is_none() { + let estimate = provider.estimate_eip1559_fees().await?; + if tx.max_fee_per_gas().is_none() { + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } + if tx.max_priority_fee_per_gas().is_none() { + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + } + + if tx.gas_limit().is_none() { + let mut estimated = provider.estimate_gas(tx.clone()).await?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if chain.is_tempo() { + estimated += TEMPO_BROWSER_GAS_BUFFER; + } + + tx.set_gas_limit(estimated); + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 08c535bef8a18..2d6e248cc7a73 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -13,6 +13,7 @@ use foundry_common::{ FoundryTransactionBuilder, fmt::{UIfmt, UIfmtReceiptExt}, provider::ProviderBuilder, + tempo::TEMPO_BROWSER_GAS_BUFFER, }; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; use tempo_alloy::TempoNetwork; @@ -258,7 +259,18 @@ impl SendTxArgs { // Case 2: // Browser wallet signs and sends the transaction in one step. } else if let Some(browser) = browser { - let (tx_request, _) = builder.build(browser.address()).await?; + let chain = builder.chain(); + let (mut tx_request, _) = builder.build(browser.address()).await?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if chain.is_tempo() + && let Some(gas) = tx_request.gas_limit() + { + tx_request.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + let tx_hash = browser.send_transaction_via_browser(tx_request).await?; let cast = CastTxSender::new(&provider); diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 131c96e6f6c17..184289a399450 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -398,6 +398,13 @@ pub struct CastTxBuilder { state: S, } +impl CastTxBuilder { + /// Returns the resolved chain for this builder. + pub const fn chain(&self) -> Chain { + self.chain + } +} + impl> CastTxBuilder where N::TransactionRequest: FoundryTransactionBuilder, diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 165154da88f63..bcbe837e824e3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,7 +19,7 @@ foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tempo-primitives.workspace = true diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f1c6c435217b7..be566288ef87a 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -129,8 +129,8 @@ where /// If the string represents an untagged amount (e.g. "100") then /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { - Ok(if value.starts_with("0x") { - U256::from_str_radix(value, 16)? + Ok(if value.starts_with("0x") || value.starts_with("0X") { + U256::from_str(value)? } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? .as_uint() @@ -844,6 +844,16 @@ mod tests { assert!(!p.is_sol_test()); } + #[test] + fn parse_ether_value_accepts_hex_prefixed_wei() { + assert_eq!(parse_ether_value("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0X10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0x12").unwrap(), U256::from(0x12)); + assert_eq!(parse_ether_value("0xff").unwrap(), U256::from(0xff)); + assert_eq!(parse_ether_value("100").unwrap(), U256::from(100)); + assert_eq!(parse_ether_value("1ether").unwrap(), U256::from(1000000000000000000u128)); + } + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 706527093cc8e..6697aff36e4f8 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -83,6 +83,7 @@ flate2.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true mpp.workspace = true +foundry-wallets.workspace = true [build-dependencies] chrono.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 78759848fc4bd..2c8e16bccdcc6 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -30,6 +30,7 @@ serde_json.workspace = true chrono.workspace = true revm.workspace = true yansi.workspace = true +comfy-table.workspace = true # Tempo tempo-alloy.workspace = true diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index d751ddba245ba..8b66335445046 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -1,5 +1,6 @@ use super::UIfmt; use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use comfy_table::{Table, TableComponent, presets::UTF8_FULL}; use std::fmt::{self, Write}; /// A piece is a portion of the format string which represents the next part to emit. @@ -407,6 +408,36 @@ fn format_spec<'a>( } } +pub fn console_table_format( + keys: Option<&[&dyn ConsoleFmt]>, + values: &[&dyn ConsoleFmt], +) -> String { + let keys_strings: Vec = match keys { + Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(), + None => (0..values.len()).map(|i| i.to_string()).collect(), + }; + let values_strings: Vec = values.iter().map(|v| v.fmt(FormatSpec::String)).collect(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL); + table.set_style(TableComponent::VerticalLines, '│'); + table.set_style(TableComponent::HeaderLines, '─'); + table.set_style(TableComponent::MiddleHeaderIntersections, '┼'); + table.set_style(TableComponent::LeftHeaderIntersection, '├'); + table.set_style(TableComponent::RightHeaderIntersection, '┤'); + table.set_header(vec!["(index)", "Values"]); + table.remove_style(TableComponent::HorizontalLines); + table.remove_style(TableComponent::MiddleIntersections); + table.remove_style(TableComponent::LeftBorderIntersections); + table.remove_style(TableComponent::RightBorderIntersections); + for i in 0..keys_strings.len().max(values_strings.len()) { + let key = keys_strings.get(i).map(String::as_str).unwrap_or(""); + let value = values_strings.get(i).map(String::as_str).unwrap_or(""); + table.add_row(vec![key, value]); + } + table.to_string() +} + #[cfg(test)] mod tests { use super::*; @@ -610,4 +641,80 @@ mod tests { let call = Logs::Log1(log1); assert_eq!(call.fmt(Default::default()), "foo 42 bar"); } + + #[test] + fn test_console_table_format() { + // auto-indexed, uint256 values + let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)]; + assert_eq!( + console_table_format(None, values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ 0 │ 100 │\n\ + │ 1 │ 200 │\n\ + │ 2 │ 300 │\n\ + └─────────┴────────┘" + ); + + // string keys, uint256 values + // key col expands to fit "charlie123" and value col expands to fit "20000000000000000" + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")]; + let values: &[&dyn ConsoleFmt] = &[ + &U256::from(1), + &U256::from_str("20000000000000000").unwrap(), + &U256::from_str("30000000000").unwrap(), + ]; + assert_eq!( + console_table_format(Some(keys), values), + "┌────────────┬───────────────────┐\n\ + │ (index) │ Values │\n\ + ├────────────┼───────────────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 20000000000000000 │\n\ + │ charlie123 │ 30000000000 │\n\ + └────────────┴───────────────────┘" + ); + + // empty table + assert_eq!( + console_table_format(None, &[]), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + └─────────┴────────┘" + ); + + // more keys than values + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie")]; + let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ charlie │ │\n\ + └─────────┴────────┘" + ); + + // more values than keys + let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")]; + let values: &[&dyn ConsoleFmt] = + &[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ │ 3 │\n\ + │ │ 4 │\n\ + └─────────┴────────┘" + ); + } } diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 89297a8e2c6b5..45ed47263ce32 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod console; -pub use console::{ConsoleFmt, FormatSpec, console_format}; +pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format}; mod dynamic; pub use dynamic::{ diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 3b7432cd67c55..1620342989a29 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -9,6 +9,7 @@ use crate::{ provider::{curl_transport::CurlTransport, runtime_transport::RuntimeTransportBuilder}, }; use alloy_chains::NamedChain; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_network::{Network, NetworkWallet}; use alloy_provider::{ Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, @@ -16,7 +17,9 @@ use alloy_provider::{ network::{AnyNetwork, EthereumWallet}, }; use alloy_rpc_client::ClientBuilder; -use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; +use alloy_transport::{ + TransportError, TransportFut, layers::RetryBackoffLayer, utils::guess_local_url, +}; use eyre::{Result, WrapErr}; use foundry_config::Config; use reqwest::Url; @@ -25,8 +28,14 @@ use std::{ net::SocketAddr, path::{Path, PathBuf}, str::FromStr, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + task::{Context, Poll}, time::Duration, }; +use tower::Service; use url::ParseError; /// The assumed block time for unknown chains. @@ -75,6 +84,56 @@ pub fn try_get_http_provider(builder: impl AsRef) -> Result ProviderBuilder::new(builder.as_ref()).build() } +/// A round-robin transport that distributes requests across multiple transports. +/// +/// Each request is sent to exactly one transport, rotating through the list. +/// Failover on error is handled by the retry layer above this service. +#[derive(Clone)] +pub struct RoundRobinService { + transports: Arc>, + next: Arc, +} + +impl RoundRobinService { + /// Creates a new round-robin service from a non-empty list of transports. + /// + /// # Panics + /// + /// Panics if `transports` is empty. + pub fn new(transports: Vec) -> Self { + assert!(!transports.is_empty(), "RoundRobinService requires at least one transport"); + Self { transports: Arc::new(transports), next: Arc::new(AtomicUsize::new(0)) } + } +} + +impl Service for RoundRobinService +where + S: Service< + RequestPacket, + Response = ResponsePacket, + Error = TransportError, + Future = TransportFut<'static>, + > + Clone + + Send + + Sync + + 'static, +{ + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + let transports = self.transports.clone(); + let idx = self.next.fetch_add(1, Ordering::Relaxed) % transports.len(); + let mut transport = transports[idx].clone(); + transport.call(req) + } +} + /// Helper type to construct a `RetryProvider` /// /// This builder is generic over the network type `N`, defaulting to `AnyNetwork`. @@ -368,6 +427,73 @@ impl ProviderBuilder { } impl ProviderBuilder { + /// Constructs a `RetryProvider` backed by multiple URLs using round-robin load balancing. + /// + /// Each request is sent to exactly one transport, rotating through the list via + /// [`RoundRobinService`]. There is no health scoring or endpoint deprioritization. + /// On failure, the `RetryBackoffLayer` retries the request, which naturally hits + /// the next transport in the rotation. + pub fn build_fallback(self, urls: Vec) -> Result> { + let Self { + chain, + max_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + accept_invalid_certs, + no_proxy, + curl_mode, + .. + } = self; + + eyre::ensure!(!urls.is_empty(), "at least one fork URL is required"); + eyre::ensure!(!curl_mode, "curl mode is not supported with multiple fork URLs"); + + // Build a RuntimeTransport for each URL, using the same URL normalization + // as ProviderBuilder::new() (handles localhost:port, raw socket addrs, IPC paths) + let mut parsed_urls = Vec::with_capacity(urls.len()); + let transports: Vec<_> = urls + .iter() + .map(|url_str| { + let builder = Self::new(url_str); + let url = builder.url?; + parsed_urls.push(url.clone()); + Ok(RuntimeTransportBuilder::new(url) + .with_timeout(timeout) + .with_headers(headers.clone()) + .with_jwt(jwt.clone()) + .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) + .build()) + }) + .collect::>>()?; + + let round_robin = RoundRobinService::new(transports); + + let retry_layer = + RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + // Use normalized/parsed URLs for local detection, consistent with build() + let is_local = parsed_urls.iter().all(|url| guess_local_url(url.as_str())); + let client = ClientBuilder::default().layer(retry_layer).transport(round_robin, is_local); + + if !is_local { + client.set_poll_interval( + chain + .average_blocktime_hint() + .map(|hint| hint.min(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME)) + .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) + .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), + ); + } + + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); + + Ok(provider) + } + /// Constructs the `RetryProvider` with a wallet. pub fn build_with_wallet + Clone>( self, diff --git a/crates/common/src/provider/mpp/persist.rs b/crates/common/src/provider/mpp/persist.rs index 6d6781ab4c77b..803c7270e041e 100644 --- a/crates/common/src/provider/mpp/persist.rs +++ b/crates/common/src/provider/mpp/persist.rs @@ -1,243 +1,241 @@ //! Persistent channel storage for MPP sessions. //! -//! Stores open payment channel state in a JSON file at -//! `$TEMPO_HOME/foundry/channels.json` (default: `~/.tempo/foundry/channels.json`). +//! Stores open payment channel state in a SQLite database at +//! `$TEMPO_HOME/channels.db` (default: `~/.tempo/channels.db`). //! This allows channel reuse across process invocations, avoiding the cost of //! opening a new on-chain channel for every `cast` / `forge` command. use alloy_primitives::{Address, B256}; +use foundry_wallets::{Channel, ChannelDb}; use mpp::client::channel_ops::ChannelEntry; -use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - path::PathBuf, + sync::OnceLock, time::{SystemTime, UNIX_EPOCH}, }; use tracing::{debug, warn}; use crate::tempo::tempo_home; -/// Relative path from Tempo home to the Foundry channels file. -const CHANNELS_PATH: &str = "foundry/channels.json"; +/// Process-wide database handle. +fn global_db() -> Option<&'static ChannelDb> { + static DB: OnceLock> = OnceLock::new(); + DB.get_or_init(|| { + let path = tempo_home()?.join("channels.db"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Some(old) = + tempo_home().map(|h| h.join("foundry/channels.json")).filter(|p| p.exists()) + { + warn!( + ?old, + "found old channels.json — this file is no longer used; channels will be re-opened" + ); + } -/// Current schema version. -const SCHEMA_VERSION: u64 = 2; + match ChannelDb::open(&path) { + Ok(db) => { + debug!(?path, "opened channel database"); + Some(db) + } + Err(e) => { + warn!(?path, %e, "failed to open channel database"); + None + } + } + }) + .as_ref() +} -/// On-disk representation of the channel store. -#[derive(Debug, Serialize, Deserialize)] -struct ChannelStore { - version: u64, - #[serde(default)] - channels: HashMap, +/// Reconstruct the composite HashMap key from a persisted `Channel`. +/// +/// Mirrors `SessionProvider::channel_key()` in session.rs. +fn channel_key_from_persisted(ch: &Channel) -> String { + let origin_hash = &alloy_primitives::keccak256(ch.origin.as_bytes()).to_string()[..18]; + format!( + "{}:{}:{}:{}:{}:{}:{}", + origin_hash, + ch.chain_id, + ch.payer, + ch.authorized_signer, + ch.payee, + ch.token, + ch.escrow_contract + ) + .to_lowercase() } -impl Default for ChannelStore { - fn default() -> Self { - Self { version: SCHEMA_VERSION, channels: HashMap::new() } +/// Whether a channel can still be used (active and not fully spent). +fn is_usable(ch: &Channel) -> bool { + if ch.state != "active" { + return false; } + let cumulative: u128 = ch.cumulative_amount.parse().unwrap_or(u128::MAX); + let deposit: u128 = ch.deposit.parse().unwrap_or(0); + cumulative < deposit } -/// A persisted channel entry. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PersistedChannel { - pub channel_id: String, - pub salt: String, - pub escrow_contract: String, - pub chain_id: u64, - pub cumulative_amount: String, - pub deposit: String, - pub status: String, - pub origin: String, - pub created_at: u64, - pub last_used_at: u64, +/// Convert a persisted `Channel` to a `ChannelEntry`. +pub fn to_channel_entry(ch: &Channel) -> Option { + let channel_id: B256 = ch.channel_id.parse().ok()?; + let salt: B256 = ch.salt.parse().ok()?; + let escrow_contract: Address = ch.escrow_contract.parse().ok()?; + let cumulative_amount: u128 = ch.cumulative_amount.parse().ok()?; + + Some(ChannelEntry { + channel_id, + salt, + cumulative_amount, + escrow_contract, + chain_id: ch.chain_id as u64, + opened: ch.state == "active", + }) } -impl PersistedChannel { - /// Convert to an mpp `ChannelEntry` for use in the session provider. - pub fn to_channel_entry(&self) -> Option { - let channel_id: B256 = self.channel_id.parse().ok()?; - let salt: B256 = self.salt.parse().ok()?; - let escrow_contract: Address = self.escrow_contract.parse().ok()?; - let cumulative_amount: u128 = self.cumulative_amount.parse().ok()?; - - Some(ChannelEntry { - channel_id, - salt, - cumulative_amount, - escrow_contract, - chain_id: self.chain_id, - opened: self.status == "active", - }) - } - - /// Create from a `ChannelEntry` with metadata. - pub fn from_channel_entry(entry: &ChannelEntry, deposit: u128, origin: &str) -> Self { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); - - Self { - channel_id: entry.channel_id.to_string(), - salt: entry.salt.to_string(), - escrow_contract: entry.escrow_contract.to_string(), - chain_id: entry.chain_id, - cumulative_amount: entry.cumulative_amount.to_string(), - deposit: deposit.to_string(), - status: if entry.opened { "active" } else { "closed" }.to_string(), - origin: origin.to_string(), - created_at: now, - last_used_at: now, - } - } - - /// Whether this channel can still be used (active and not fully spent). - fn is_usable(&self) -> bool { - if self.status != "active" { - return false; - } - let cumulative: u128 = self.cumulative_amount.parse().unwrap_or(u128::MAX); - let deposit: u128 = self.deposit.parse().unwrap_or(0); - cumulative < deposit +/// Create a `Channel` from a `ChannelEntry` with metadata. +pub fn from_channel_entry( + entry: &ChannelEntry, + deposit: u128, + origin: &str, + payer: &Address, + payee: &Address, + token: &Address, + authorized_signer: &Address, +) -> Channel { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + Channel { + channel_id: entry.channel_id.to_string(), + version: 1, + origin: origin.to_string(), + request_url: String::new(), + chain_id: entry.chain_id as i64, + escrow_contract: entry.escrow_contract.to_string(), + token: token.to_string(), + payee: payee.to_string(), + payer: payer.to_string(), + authorized_signer: authorized_signer.to_string(), + salt: entry.salt.to_string(), + deposit: deposit.to_string(), + cumulative_amount: entry.cumulative_amount.to_string(), + challenge_echo: String::new(), + state: if entry.opened { "active" } else { "closed" }.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: now, + last_used_at: now, } } -/// Returns the path to the channels file. -fn channels_path() -> Option { - tempo_home().map(|home| home.join(CHANNELS_PATH)) -} - -/// Load channels from disk, evicting spent/inactive entries. -pub fn load_channels() -> HashMap { - let Some(path) = channels_path().filter(|p| p.exists()) else { +/// Load channels from database, evicting spent/inactive entries. +pub fn load_channels() -> HashMap { + let Some(db) = global_db() else { return HashMap::new(); }; - let Ok(contents) = std::fs::read_to_string(&path).inspect_err(|e| { - warn!(?path, %e, "failed to read channels file"); - }) else { - return HashMap::new(); - }; - - let Ok(store) = serde_json::from_str::(&contents).inspect_err(|e| { - warn!(?path, %e, "failed to parse channels file, starting fresh"); - }) else { - return HashMap::new(); + let channels = match db.load() { + Ok(channels) => channels, + Err(e) => { + warn!(%e, "failed to load channels from database"); + return HashMap::new(); + } }; - if store.version != SCHEMA_VERSION { - warn!( - version = store.version, - expected = SCHEMA_VERSION, - "channels file version mismatch, starting fresh" - ); - return HashMap::new(); - } - - // Evict spent/inactive entries - let usable: HashMap = - store.channels.into_iter().filter(|(_, ch)| ch.is_usable()).collect(); + let usable: HashMap = channels + .into_iter() + .filter(is_usable) + .map(|ch| { + let key = channel_key_from_persisted(&ch); + (key, ch) + }) + .collect(); debug!(count = usable.len(), "loaded persisted MPP channels"); usable } -/// Save channels to disk. -pub fn save_channels(channels: &HashMap) { - let Some(path) = channels_path() else { +/// Save channels to database. +pub fn save_channels(channels: &HashMap) { + let Some(db) = global_db() else { return; }; - if let Some(parent) = path.parent() - && let Err(e) = std::fs::create_dir_all(parent) - { - warn!(?path, %e, "failed to create channels directory"); - return; + for ch in channels.values() { + if let Err(e) = db.upsert(ch) { + warn!(%e, channel_id = %ch.channel_id, "failed to save channel"); + } } + debug!(count = channels.len(), "saved MPP channels"); +} - let store = ChannelStore { version: SCHEMA_VERSION, channels: channels.clone() }; - - match serde_json::to_string_pretty(&store) { - Ok(json) => { - if let Err(e) = std::fs::write(&path, json) { - warn!(?path, %e, "failed to write channels file"); - } else { - debug!(?path, count = channels.len(), "saved MPP channels"); - } - } - Err(e) => warn!(%e, "failed to serialize channels"), +/// Delete a channel from the database by its channel ID. +pub fn delete_channel_from_db(channel_id: &str) { + let Some(db) = global_db() else { + return; + }; + if let Err(e) = db.delete(channel_id) { + warn!(%e, channel_id, "failed to delete channel from database"); } } /// Look up a usable persisted channel by key. -pub fn find_channel( - channels: &HashMap, - key: &str, -) -> Option { - channels.get(key).filter(|ch| ch.is_usable()).and_then(|ch| ch.to_channel_entry()) +pub fn find_channel(channels: &HashMap, key: &str) -> Option { + channels.get(key).filter(|ch| is_usable(ch)).and_then(to_channel_entry) } -/// Insert or update a channel entry in memory only (no disk write). -/// -/// Use [`upsert_channel`] when you want to persist immediately, or call -/// [`save_channels`] separately after this. +/// Insert or update a channel entry in memory only (no DB write). pub fn upsert_channel_in_memory( - channels: &mut HashMap, + channels: &mut HashMap, key: &str, entry: &ChannelEntry, - deposit: u128, - origin: &str, ) { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; if let Some(existing) = channels.get_mut(key) { existing.cumulative_amount = entry.cumulative_amount.to_string(); existing.last_used_at = now; - existing.status = if entry.opened { "active" } else { "closed" }.to_string(); + existing.state = if entry.opened { "active" } else { "closed" }.to_string(); } else { - channels - .insert(key.to_string(), PersistedChannel::from_channel_entry(entry, deposit, origin)); + warn!(key, "upsert_channel_in_memory called for unknown channel"); } } -/// Insert or update a channel entry and save to disk. -/// -/// When updating an existing entry, `deposit` is ignored (preserved from the -/// original open). When inserting a new entry, `deposit` is recorded. -pub fn upsert_channel( - channels: &mut HashMap, - key: &str, - entry: &ChannelEntry, - deposit: u128, - origin: &str, -) { - upsert_channel_in_memory(channels, key, entry, deposit, origin); - save_channels(channels); -} - #[cfg(test)] mod tests { use super::*; - fn test_channel(status: &str, cumulative: &str, deposit: &str) -> PersistedChannel { - PersistedChannel { + fn test_channel(state: &str, cumulative: &str, deposit: &str) -> Channel { + Channel { channel_id: format!("0x{}", "ab".repeat(32)), - salt: format!("0x{}", "cd".repeat(32)), - escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + version: 1, + origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + request_url: String::new(), chain_id: 42431, - cumulative_amount: cumulative.to_string(), + escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + token: "0x20c0000000000000000000000000000000000000".to_string(), + payee: "0x3333333333333333333333333333333333333333".to_string(), + payer: "0x1111111111111111111111111111111111111111".to_string(), + authorized_signer: "0x1111111111111111111111111111111111111111".to_string(), + salt: format!("0x{}", "cd".repeat(32)), deposit: deposit.to_string(), - status: status.to_string(), - origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + cumulative_amount: cumulative.to_string(), + challenge_echo: String::new(), + state: state.to_string(), + close_requested_at: 0, + grace_ready_at: 0, created_at: 1000, last_used_at: 1000, } } #[test] - fn is_usable() { - assert!(test_channel("active", "5000", "100000").is_usable()); - assert!(!test_channel("active", "100000", "100000").is_usable()); - assert!(!test_channel("active", "200000", "100000").is_usable()); - assert!(!test_channel("closed", "0", "100000").is_usable()); - assert!(!test_channel("closing", "0", "100000").is_usable()); + fn usable() { + assert!(is_usable(&test_channel("active", "5000", "100000"))); + assert!(!is_usable(&test_channel("active", "100000", "100000"))); + assert!(!is_usable(&test_channel("active", "200000", "100000"))); + assert!(!is_usable(&test_channel("closed", "0", "100000"))); + assert!(!is_usable(&test_channel("closing", "0", "100000"))); } #[test] @@ -251,8 +249,12 @@ mod tests { opened: true, }; - let persisted = PersistedChannel::from_channel_entry(&entry, 100_000, "https://rpc.test"); - let restored = persisted.to_channel_entry().expect("should parse back"); + let payer = Address::random(); + let payee = Address::random(); + let token = Address::random(); + let persisted = + from_channel_entry(&entry, 100_000, "https://rpc.test", &payer, &payee, &token, &payer); + let restored = to_channel_entry(&persisted).expect("should parse back"); assert_eq!(restored.channel_id, entry.channel_id); assert_eq!(restored.salt, entry.salt); @@ -262,46 +264,6 @@ mod tests { assert!(restored.opened); } - #[test] - fn load_evicts_and_handles_edge_cases() { - let dir = tempfile::tempdir().unwrap(); - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - - let store = ChannelStore { - version: SCHEMA_VERSION, - channels: HashMap::from([ - ("active".into(), test_channel("active", "1000", "100000")), - ("spent".into(), test_channel("active", "100000", "100000")), - ("closed".into(), test_channel("closed", "0", "100000")), - ]), - }; - let json = serde_json::to_string(&store).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), &json).unwrap(); - - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - let loaded = load_channels(); - unsafe { std::env::remove_var("TEMPO_HOME") }; - - assert_eq!(loaded.len(), 1); - assert!(loaded.contains_key("active")); - } - - #[test] - fn load_missing_and_wrong_version() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - assert!(load_channels().is_empty()); - - let foundry_dir = dir.path().join("foundry"); - std::fs::create_dir_all(&foundry_dir).unwrap(); - std::fs::write(foundry_dir.join("channels.json"), r#"{"version": 999, "channels": {}}"#) - .unwrap(); - assert!(load_channels().is_empty()); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } - #[test] fn find_channel_filters_unusable() { let mut channels = HashMap::new(); @@ -312,34 +274,4 @@ mod tests { assert!(find_channel(&channels, "spent").is_none()); assert!(find_channel(&channels, "missing").is_none()); } - - #[test] - fn upsert_inserts_and_updates() { - let dir = tempfile::tempdir().unwrap(); - unsafe { std::env::set_var("TEMPO_HOME", dir.path()) }; - - let mut channels = HashMap::new(); - let entry = ChannelEntry { - channel_id: B256::random(), - salt: B256::random(), - cumulative_amount: 1000, - escrow_contract: Address::random(), - chain_id: 42431, - opened: true, - }; - - upsert_channel(&mut channels, "key1", &entry, 100_000, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "1000"); - assert_eq!(channels["key1"].deposit, "100000"); - let created_at = channels["key1"].created_at; - - let mut updated = entry.clone(); - updated.cumulative_amount = 5000; - upsert_channel(&mut channels, "key1", &updated, 0, "https://rpc.test"); - assert_eq!(channels["key1"].cumulative_amount, "5000"); - assert_eq!(channels["key1"].deposit, "100000"); - assert_eq!(channels["key1"].created_at, created_at); - - unsafe { std::env::remove_var("TEMPO_HOME") }; - } } diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 5996c933218d2..334166b844613 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -6,8 +6,9 @@ //! `eth_getTransactionCount`. This avoids the chicken-and-egg problem when //! the RPC endpoint is itself 402-gated. -use super::persist::{self, PersistedChannel}; +use super::persist; use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use foundry_wallets::Channel; use mpp::{ client::{ PaymentProvider, @@ -42,7 +43,7 @@ static GLOBAL_CHANNELS: OnceLock>> = O /// /// Using a single map ensures saves from different origins don't clobber /// each other's state. -static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); +static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); /// Tracks uncommitted channel state from the most recent payment. /// @@ -86,7 +87,7 @@ pub struct SessionProvider { default_deposit: Option, channels: Arc>>, key_provisioned: Arc>, - persisted: Arc>>, + persisted: Arc>>, /// Tracks uncommitted open/top-up state for deferred persistence. pending: Arc>>, /// Chain ID from the key entry in `keys.toml` that was used to initialize @@ -128,10 +129,10 @@ impl SessionProvider { map.entry(origin.clone()) .or_insert_with(|| { // Hydrate only channels belonging to this origin. - let mut channels = HashMap::new(); + let mut channels: HashMap = HashMap::new(); for (key, ch) in persisted.lock().unwrap().iter() { if ch.origin == origin - && let Some(entry) = ch.to_channel_entry() + && let Some(entry) = persist::to_channel_entry(ch) { channels.insert(key.clone(), entry); } @@ -209,16 +210,16 @@ impl SessionProvider { // Lock order: channels → persisted (consistent with pay_session) let mut channels = self.channels.lock().unwrap(); let mut persisted = self.persisted.lock().unwrap(); - let keys_to_remove: Vec = persisted + let keys_to_remove: Vec<(String, String)> = persisted .iter() .filter(|(_, ch)| ch.origin == *origin) - .map(|(k, _)| k.clone()) + .map(|(k, ch): (&String, &Channel)| (k.clone(), ch.channel_id.clone())) .collect(); - for key in &keys_to_remove { + for (key, channel_id) in &keys_to_remove { channels.remove(key); persisted.remove(key); + persist::delete_channel_from_db(channel_id); } - persist::save_channels(&persisted); } /// Mark whether the access key has been provisioned on-chain. @@ -685,13 +686,7 @@ impl SessionProvider { // confirms acceptance. let updated_entry = ChannelEntry { cumulative_amount: new_cumulative, ..entry }; let mut persisted = self.persisted.lock().unwrap(); - persist::upsert_channel_in_memory( - &mut persisted, - &key, - &updated_entry, - 0, - &self.origin, - ); + persist::upsert_channel_in_memory(&mut persisted, &key, &updated_entry); drop(persisted); // Track the voucher so we can roll back cumulative_amount @@ -727,9 +722,18 @@ impl SessionProvider { // Update in-memory state but defer disk persistence until server confirms. self.channels.lock().unwrap().insert(key.clone(), entry.clone()); + let authorized_signer = self.authorized_signer.unwrap_or(payer); self.persisted.lock().unwrap().insert( key.clone(), - persist::PersistedChannel::from_channel_entry(&entry, deposit, &self.origin), + persist::from_channel_entry( + &entry, + deposit, + &self.origin, + &payer, + &payee, + ¤cy, + &authorized_signer, + ), ); *self.pending.lock().unwrap() = Some(PendingAction::Open { key }); Ok(build_credential(challenge, payload, chain_id, payer)) diff --git a/crates/common/src/tempo/mod.rs b/crates/common/src/tempo/mod.rs index d6c6340cc4d12..ef7be1b8e189e 100644 --- a/crates/common/src/tempo/mod.rs +++ b/crates/common/src/tempo/mod.rs @@ -2,3 +2,14 @@ mod keystore; pub use keystore::*; + +/// Conservative gas buffer for browser wallet transactions on Tempo chains. +/// +/// Browser wallets may sign with P256 or WebAuthn instead of secp256k1, which costs more gas +/// for signature verification. Since we can't determine the signature type before signing, +/// we add the worst-case (WebAuthn) overhead: +/// - P256: +5,000 gas (P256 precompile cost minus ecrecover savings) +/// - WebAuthn: ~6,500 gas (P256 cost + calldata for webauthn_data) +/// +/// See +pub const TEMPO_BROWSER_GAS_BUFFER: u64 = 7_000; diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs index 5a362e963d956..77a3bb7ab11c2 100644 --- a/crates/config/spec/src/lib.rs +++ b/crates/config/spec/src/lib.rs @@ -40,8 +40,7 @@ mod tests { /// Checks that the `file` has the specified `contents`. If that is not the /// case, updates the file and then fails the test. fn ensure_file_contents(file: &Path, contents: &str) { - if let Ok(old_contents) = fs::read_to_string(file) - && normalize_newlines(&old_contents) == normalize_newlines(contents) + if fs::read_to_string(file).map(|old| normalize_newlines(&old) == normalize_newlines(contents)).unwrap_or(false) { // File is already up to date. return; diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 64ee7e05fc75f..60a15206bc51a 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -277,6 +277,11 @@ pub struct RpcEndpoint { /// endpoint url or env pub endpoint: RpcEndpointUrl, + /// Additional fallback endpoints for load-balanced multi-endpoint forking. + /// When set, requests are distributed across all endpoints (primary + extra) + /// with automatic failover. + pub extra_endpoints: Vec, + /// Token to be used as authentication pub auth: Option, @@ -293,6 +298,7 @@ impl RpcEndpoint { pub fn resolve(self) -> ResolvedRpcEndpoint { ResolvedRpcEndpoint { endpoint: self.endpoint.resolve(), + extra_endpoints: self.extra_endpoints.into_iter().map(|e| e.resolve()).collect(), auth: self.auth.map(|auth| auth.resolve()), config: self.config, } @@ -301,7 +307,7 @@ impl RpcEndpoint { impl fmt::Display for RpcEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { endpoint, auth, config } = self; + let Self { endpoint, auth, config, .. } = self; write!(f, "{endpoint}")?; write!(f, "{config}")?; if let Some(auth) = auth { @@ -316,16 +322,24 @@ impl Serialize for RpcEndpoint { where S: Serializer, { - if self.config.retries.is_none() - && self.config.retry_backoff.is_none() - && self.config.compute_units_per_second.is_none() - && self.auth.is_none() - { - // serialize as endpoint if there's no additional config + let has_config = self.config.retries.is_some() + || self.config.retry_backoff.is_some() + || self.config.compute_units_per_second.is_some() + || self.auth.is_some(); + + if !has_config && self.extra_endpoints.is_empty() { + // serialize as plain endpoint string if there's no additional config self.endpoint.serialize(serializer) } else { - let mut map = serializer.serialize_map(Some(5))?; - map.serialize_entry("endpoint", &self.endpoint)?; + let mut map = serializer.serialize_map(None)?; + if self.extra_endpoints.is_empty() { + map.serialize_entry("endpoint", &self.endpoint)?; + } else { + // Serialize all endpoints as an array under "endpoints" + let all: Vec<&RpcEndpointUrl> = + std::iter::once(&self.endpoint).chain(&self.extra_endpoints).collect(); + map.serialize_entry("endpoints", &all)?; + } map.serialize_entry("retries", &self.config.retries)?; map.serialize_entry("retry_backoff", &self.config.retry_backoff)?; map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?; @@ -348,10 +362,13 @@ impl<'de> Deserialize<'de> for RpcEndpoint { }); } + // Support both single "endpoint" and array "endpoints" for backwards compatibility #[derive(Deserialize)] struct RpcEndpointConfigInner { #[serde(alias = "url")] - endpoint: RpcEndpointUrl, + endpoint: Option, + /// Array of endpoint URLs for multi-endpoint load balancing + endpoints: Option>, retries: Option, retry_backoff: Option, compute_units_per_second: Option, @@ -360,14 +377,43 @@ impl<'de> Deserialize<'de> for RpcEndpoint { let RpcEndpointConfigInner { endpoint, + endpoints, retries, retry_backoff, compute_units_per_second, auth, } = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + let (primary, extra) = match (endpoint, endpoints) { + // Single endpoint: endpoint = "..." + (Some(ep), None) => (ep, vec![]), + // Array of endpoints: endpoints = ["...", "..."] + (None, Some(mut eps)) => { + if eps.is_empty() { + return Err(serde::de::Error::custom( + "endpoints array must contain at least one URL", + )); + } + let primary = eps.remove(0); + (primary, eps) + } + // Both provided — error + (Some(_), Some(_)) => { + return Err(serde::de::Error::custom( + "cannot specify both `endpoint` and `endpoints`", + )); + } + // Neither provided — error + (None, None) => { + return Err(serde::de::Error::custom( + "must specify either `endpoint` or `endpoints`", + )); + } + }; + Ok(Self { - endpoint, + endpoint: primary, + extra_endpoints: extra, auth, config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second }, }) @@ -384,6 +430,7 @@ impl Default for RpcEndpoint { fn default() -> Self { Self { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig::default(), auth: None, } @@ -394,21 +441,38 @@ impl Default for RpcEndpoint { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResolvedRpcEndpoint { pub endpoint: Result, + /// Additional resolved endpoints for multi-endpoint load balancing. + pub extra_endpoints: Vec>, pub auth: Option>, pub config: RpcEndpointConfig, } impl ResolvedRpcEndpoint { - /// Returns the url this type holds, see [`RpcEndpoint::resolve`] + /// Returns the primary url this type holds, see [`RpcEndpoint::resolve`] pub fn url(&self) -> Result { self.endpoint.clone() } + /// Returns all resolved URLs (primary + extra) for multi-endpoint configurations. + /// Returns an empty vec if no extra endpoints are configured. + pub fn all_urls(&self) -> Result, UnresolvedEnvVarError> { + let primary = self.endpoint.clone()?; + if self.extra_endpoints.is_empty() { + return Ok(vec![primary]); + } + let mut urls = vec![primary]; + for ep in &self.extra_endpoints { + urls.push(ep.clone()?); + } + Ok(urls) + } + // Returns true if all environment variables are resolved successfully pub fn is_unresolved(&self) -> bool { let endpoint_err = self.endpoint.is_err(); + let extra_err = self.extra_endpoints.iter().any(|e| e.is_err()); let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false); - endpoint_err || auth_err + endpoint_err || extra_err || auth_err } // Attempts to resolve unresolved environment variables into a new instance @@ -419,6 +483,11 @@ impl ResolvedRpcEndpoint { if let Err(err) = self.endpoint { self.endpoint = err.try_resolve() } + for ep in &mut self.extra_endpoints { + if let Err(err) = std::mem::replace(ep, Ok(String::new())) { + *ep = err.try_resolve(); + } + } if let Some(Err(err)) = self.auth { self.auth = Some(err.try_resolve()) } @@ -483,6 +552,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(5), retry_backoff: Some(250), @@ -498,6 +568,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: None, retry_backoff: None, @@ -507,4 +578,62 @@ mod tests { } ); } + + #[test] + fn serde_rpc_config_multi_endpoints() { + // Array of endpoints via "endpoints" key + let s = r#"{ + "endpoints": ["https://rpc1.example.com", "https://rpc2.example.com", "https://rpc3.example.com"], + "retries": 5, + "retry_backoff": 1000 + }"#; + let config: RpcEndpoint = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpoint { + endpoint: RpcEndpointUrl::Url("https://rpc1.example.com".to_string()), + extra_endpoints: vec![ + RpcEndpointUrl::Url("https://rpc2.example.com".to_string()), + RpcEndpointUrl::Url("https://rpc3.example.com".to_string()), + ], + config: RpcEndpointConfig { + retries: Some(5), + retry_backoff: Some(1000), + compute_units_per_second: None, + }, + auth: None, + } + ); + + // Resolved URLs + let resolved = config.resolve(); + let all_urls = resolved.all_urls().unwrap(); + assert_eq!( + all_urls, + vec![ + "https://rpc1.example.com".to_string(), + "https://rpc2.example.com".to_string(), + "https://rpc3.example.com".to_string(), + ] + ); + } + + #[test] + fn serde_rpc_config_rejects_both_endpoint_and_endpoints() { + let s = r#"{ + "endpoint": "https://rpc1.example.com", + "endpoints": ["https://rpc2.example.com"] + }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("cannot specify both")); + } + + #[test] + fn serde_rpc_config_rejects_empty_endpoints() { + let s = r#"{ "endpoints": [] }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("at least one URL")); + } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index c0f8249f37dfd..f148432c42bed 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -3681,6 +3681,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3706,6 +3707,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3753,6 +3755,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3779,6 +3782,7 @@ mod tests { endpoint: RpcEndpointUrl::Url( "https://eth-mainnet.alchemyapi.io/v2/123455".to_string() ), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 3d4c57fe5dc3e..ee0175a5e21b1 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -157,6 +157,12 @@ impl From> for Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json index 54e6d46dff349..f275f471087ee 100644 --- a/crates/evm/abi/src/Console.json +++ b/crates/evm/abi/src/Console.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index 2e28fece21e42..d0b32ff410941 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -16,12 +16,15 @@ def main(): # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() sig_strings = re.findall( - r'"(log.*?)"', + r'"((?:log|table).*?)"', console_sol, ) raw_sigs = [s.strip().strip('"') for s in sig_strings] sigs = [ - s.replace("string", "string memory").replace("bytes)", "bytes memory)") + re.sub(r"(\w+\[\])", r"\1 memory", s) + .replace("string,", "string memory,") + .replace("string)", "string memory)") + .replace("bytes)", "bytes memory)") for s in raw_sigs ] sigs = list(set(sigs)) @@ -38,6 +41,7 @@ def main(): ) combined = json.loads(r.stdout.strip()) abi = combined["contracts"][":HardhatConsole"]["abi"] + open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 6f539d356e5a9..aefa0e2ee9741 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -71,7 +71,7 @@ impl ForkedDatabase { /// Reset the fork to a fresh forked state, and optionally update the fork config pub fn reset( &mut self, - _url: Option, + _urls: Vec, block_number: impl Into, ) -> Result<(), String> { self.backend.set_pinned_block(block_number).map_err(|err| err.to_string())?; diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 26acbe56a97cb..c591b9426bf9e 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -50,7 +50,9 @@ impl LogCollector { fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.push_msg(&decoded.fmt(Default::default())); + for line in decoded.fmt(Default::default()).lines() { + self.push_msg(line); + } Ok(()) } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 9a7a237855b97..ecdc13ded67cf 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -51,7 +51,7 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } alloy-chains.workspace = true alloy-consensus.workspace = true diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 122020571ad24..765bb64f95fdd 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -9,7 +9,7 @@ use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as Alloy use alloy_signer::{Signature, Signer}; use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; +use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, @@ -21,6 +21,7 @@ use foundry_common::{ fmt::parse_tokens, provider::ProviderBuilder, shell, + tempo::TEMPO_BROWSER_GAS_BUFFER, }; use foundry_compilers::{ ArtifactId, artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, @@ -33,10 +34,12 @@ use foundry_config::{ }, merge_impl_figment_convert, }; -use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use foundry_wallets::{ + BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner, +}; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; -use tempo_alloy::TempoNetwork; +use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN}; merge_impl_figment_convert!(CreateArgs, build, eth); @@ -101,14 +104,29 @@ pub struct CreateArgs { #[command(flatten)] retry: RetryArgs, + + /// Browser wallet options + #[command(flatten)] + browser: BrowserWalletOpts, } impl CreateArgs { /// Executes the command to create a contract - pub async fn run(self) -> Result<()> { + pub async fn run(mut self) -> Result<()> { let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?; - if tempo_access_key.is_some() || self.tx.tempo.is_tempo() { + // Resolve chain early so we can dispatch to the correct network type. + if self.chain_id().is_none() { + let config = self.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let chain_id = provider.get_chain_id().await?; + self.eth.etherscan.chain = Some(chain_id.into()); + } + + if tempo_access_key.is_some() + || self.tx.tempo.is_tempo() + || self.chain_id().is_some_and(|c| c.is_tempo()) + { self.run_generic::(signer, tempo_access_key).await } else { self.run_generic::(signer, None).await @@ -185,17 +203,29 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chain_id().await? - }; - // Whether to broadcast the transaction or not let dry_run = !self.broadcast; - if self.unlocked { + // Launch browser signer if `--browser` flag is set + let browser = self.browser.run::().await?; + + if let Some(browser) = browser { + // Deploy with browser wallet + let deployer_address = browser.address(); + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + None, + Some(browser), + ) + .await + } else if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( @@ -203,12 +233,12 @@ impl CreateArgs { bin, params, provider, - chain_id, sender, config.transaction_timeout, id, dry_run, None, + None, ) .await } else if let Some(ak) = access_key { @@ -223,12 +253,12 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer_address, config.transaction_timeout, id, dry_run, Some((signer, ak)), + None, ) .await } else { @@ -246,20 +276,20 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer, config.transaction_timeout, id, dry_run, None, + None, ) .await } } - /// Returns the provided chain id, if any. - fn chain_id(&self) -> Option { - self.eth.etherscan.chain.map(|chain| chain.id()) + /// Returns the resolved chain, if any. + const fn chain_id(&self) -> Option { + self.eth.etherscan.chain } /// Ensures the verify command can be executed. @@ -271,7 +301,6 @@ impl CreateArgs { async fn verify_preflight_check( &self, constructor_args: Option, - chain: u64, id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, @@ -287,7 +316,7 @@ impl CreateArgs { num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), - chain: Some(chain.into()), + chain: self.chain_id(), }, rpc: Default::default(), flatten: false, @@ -311,7 +340,7 @@ impl CreateArgs { // ETHERSCAN_API_KEY value set. let config = verify.load_config()?; verify.etherscan.key = - config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key); let context = verify.resolve_context().await?; @@ -327,17 +356,19 @@ impl CreateArgs { bin: BytecodeObject, args: Vec, provider: P, - chain: u64, deployer_address: Address, timeout: u64, id: ArtifactId, dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, + browser_signer: Option>, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, N::ReceiptResponse: serde::Serialize, { + let chain = self.chain_id().context("chain ID not resolved")?; + let bin = bin.into_bytes().unwrap_or_default(); if bin.is_empty() { eyre::bail!("no bytecode found in bin object for {}", self.contract.name) @@ -349,22 +380,31 @@ impl CreateArgs { let is_args_empty = args.is_empty(); let mut deployer = - factory.deploy_tokens(args.clone(), self.tx.tempo.fee_token).context("failed to deploy contract").map_err(|e| { + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { e } })?; - let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + let is_legacy = self.tx.legacy || chain.is_legacy(); deployer.tx.set_from(deployer_address); - deployer.tx.set_chain_id(chain); + deployer.tx.set_chain_id(chain.id()); // `to` field must be set explicitly, cannot be None. if deployer.tx.to().is_none() { deployer.tx.set_create(); } + // If Tempo chain fee token must be set + if chain.is_tempo() { + if let Some(fee_token) = self.tx.tempo.fee_token { + deployer.tx.set_fee_token(fee_token); + } else { + deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); + } + } + // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); @@ -394,7 +434,16 @@ impl CreateArgs { } if self.tx.gas_limit.is_none() { - deployer.tx.set_gas_limit(provider.estimate_gas(deployer.tx.clone()).await?); + let mut estimated = provider.estimate_gas(deployer.tx.clone()).await?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if browser_signer.is_some() && chain.is_tempo() { + estimated += TEMPO_BROWSER_GAS_BUFFER; + } + + deployer.tx.set_gas_limit(estimated); } if is_legacy { @@ -422,7 +471,7 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + self.verify_preflight_check(constructor_args.clone(), &id).await?; } if dry_run { @@ -452,7 +501,31 @@ impl CreateArgs { } // Deploy the actual contract - let (deployed_contract, receipt) = if let Some((signer, ak)) = tempo_keychain { + let (deployed_contract, receipt) = if let Some(browser) = browser_signer { + // Browser wallet signs and sends the transaction + let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?; + + // Wait for the transaction to be confirmed, then fetch the receipt. + provider + .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash)) + .await? + .await?; + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?; + + if !receipt.status() { + eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}"); + } + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else if let Some((signer, ak)) = tempo_keychain { // Tempo keychain mode: sign with access key provisioning and send raw let raw_tx = deployer .tx @@ -518,7 +591,7 @@ impl CreateArgs { no_auto_detect: false, use_solc: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) }, rpc: Default::default(), flatten: false, force: false, @@ -681,7 +754,6 @@ impl + Clone> DeploymentTxFactory { pub fn deploy_tokens( self, params: Vec, - fee_token: Option
, ) -> Result, ContractDeploymentError> where N::TransactionRequest: FoundryTransactionBuilder, @@ -703,9 +775,6 @@ impl + Clone> DeploymentTxFactory { // create the tx object. Since we're deploying a contract, `to` is `None` let mut tx = N::TransactionRequest::default(); tx.set_input(data); - if let Some(fee_token) = fee_token { - tx.set_fee_token(fee_token); - } Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } @@ -763,7 +832,7 @@ mod tests { "--chain-id", "9999", ]); - assert_eq!(args.chain_id(), Some(9999)); + assert_eq!(args.chain_id().map(|c| c.id()), Some(9999)); } #[test] diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index b6a18f292a91b..cb942510bbb49 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -91,7 +91,7 @@ fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir: return None; }; - (is_keccak && args.len() == 1).then(|| &args[0]) + if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } } // -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 0c95841d1e653..245ba6e5b78e2 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -7,7 +7,7 @@ use syn::{ pub fn console_fmt(input: &DeriveInput) -> TokenStream { let name = &input.ident; let tokens = match &input.data { - Data::Struct(s) => derive_struct(s), + Data::Struct(s) => derive_struct(s, name), Data::Enum(e) => derive_enum(e), Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; @@ -18,8 +18,8 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { } } -fn derive_struct(s: &DataStruct) -> TokenStream { - let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); +fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream { + let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new())); quote! { fn fmt(&self, _spec: FormatSpec) -> String { #imp @@ -27,7 +27,7 @@ fn derive_struct(s: &DataStruct) -> TokenStream { } } -fn impl_struct(s: &DataStruct) -> Option { +fn impl_struct(s: &DataStruct, name: &Ident) -> Option { if s.fields.is_empty() { return None; } @@ -36,13 +36,49 @@ fn impl_struct(s: &DataStruct) -> Option { return None; } + let members = s.fields.members().collect::>(); let fields = s.fields.iter().collect::>(); + + // Detect table call structs: name must start with "table" (from the ABI function name) and + // all fields must be Vec types (Solidity arrays). Both conditions together prevent + // accidental table rendering for unrelated structs that happen to have Vec fields. + let is_table = name.to_string().starts_with("table") + && !fields.is_empty() + && fields.iter().all(|f| match &f.ty { + Type::Path(path) => path.path.segments.last().is_some_and(|seg| seg.ident == "Vec"), + _ => false, + }); + if is_table { + let member_ref = |m: &Member| match m { + Member::Named(ident) => quote!(&self.#ident), + Member::Unnamed(idx) => quote!(&self.#idx), + }; + let imp = if members.len() == 1 { + let vals = member_ref(&members[0]); + quote! { + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(None, &values) + } + } else { + let keys = member_ref(&members[0]); + let vals = member_ref(&members[1]); + quote! { + let keys: ::std::vec::Vec<&dyn ConsoleFmt> = + (#keys).iter().map(|v| v as &dyn ConsoleFmt).collect(); + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(Some(&keys), &values) + } + }; + return Some(imp); + } + let first_ty = match &fields.first().unwrap().ty { Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), _ => String::new(), }; - let members = s.fields.members().collect::>(); let args: Punctuated = members .into_iter() .map(|member| match member { diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 65b75b8b39bb1..332420d0d1c42 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -23,7 +23,7 @@ foundry-evm-networks.workspace = true alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } foundry-linking.workspace = true forge-script-sequence.workspace = true diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 05834ed8bb036..5c5155aa1678b 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,15 +9,21 @@ use crate::{ execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, }; +use alloy_chains::NamedChain; +use alloy_evm::revm::context::Block; use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; +use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{ContractData, shell}; -use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; +use foundry_evm::{ + core::{FoundryBlock, evm::FoundryEvmNetwork}, + traces::{decode_trace_arena, render_trace_arena}, +}; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -31,23 +37,24 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState { +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result { + pub async fn fill_metadata(self) -> Result> { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -64,7 +71,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if let Some(TxKind::Call(_)) = to { + if to.is_some() { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -88,6 +95,7 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -100,8 +108,8 @@ impl PreSimulationState { /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque, - ) -> Result> { + transactions: VecDeque>, + ) -> Result>> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -121,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; + let to = tx.to(); let result = runner .simulate( tx.from() @@ -139,7 +147,8 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.env_mut().evm_env.block_env.number += U256::from(1); + let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); + runner.executor.evm_env_mut().block_env.set_number(block_number); } let is_noop_tx = if let Some(to) = to { @@ -226,12 +235,12 @@ impl PreSimulationState { } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result> { + async fn build_runners(&self) -> Result)>> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { let n = rpcs.len(); - let s = if n != 1 { "s" } else { "" }; + let s = if n == 1 { "" } else { "s" }; sh_println!("\n## Setting up {n} EVM{s}.")?; } @@ -248,22 +257,23 @@ impl PreSimulationState { /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque, + pub transactions: VecDeque>, } -impl FilledTransactionsState { +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result { + pub async fn bundle(mut self) -> Result> { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -274,7 +284,7 @@ impl FilledTransactionsState { // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::default(); + let mut manager = ProvidersManager::::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a @@ -282,7 +292,7 @@ impl FilledTransactionsState { let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable(); while let Some(mut tx) = txes_iter.next() { - let tx_rpc = tx.rpc.to_owned(); + let tx_rpc = tx.rpc.clone(); let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; if let Some(tx) = tx.tx_mut().as_unsigned_mut() { @@ -297,7 +307,7 @@ impl FilledTransactionsState { // only estimate gas for unsigned transactions if let Some(tx) = tx.as_unsigned_mut() { trace!("estimating with different gas calculation"); - let gas = tx.gas.expect("gas is set by simulation."); + let gas = tx.gas_limit().expect("gas is set by simulation."); // We are trying to show the user an estimation of the total gas usage. // @@ -351,6 +361,12 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); + // Get the native token symbol for the chain using NamedChain + let token_symbol = NamedChain::try_from(provider_info.chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); + // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -368,15 +384,7 @@ impl FilledTransactionsState { .unwrap_or_else(|_| "[Could not calculate]".to_string()); let estimated_amount = estimated_amount_raw.trim_end_matches('0'); - if !shell::is_json() { - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; - sh_println!("\n==========================")?; - } else { + if shell::is_json() { sh_println!( "{}", serde_json::json!({ @@ -384,8 +392,17 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, + "token_symbol": token_symbol, }) )?; + } else { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\n==========================")?; } } } @@ -406,6 +423,7 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -416,14 +434,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque, - ) -> Result { + transactions: VecDeque>, + ) -> Result> { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::get_paths( + Some(ScriptSequence::::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index c1a6cb53bdbff..8ee2228ebfe1d 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -120,6 +120,7 @@ impl ScriptTester { let from_dir = testdata.join("utils"); let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; + let from_dir = from_dir.canonicalize()?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); // Only operate on regular files to avoid following symlinks or directories @@ -247,11 +248,12 @@ impl ScriptTester { trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - assert!( - !(!stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str())), - "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", - expected.as_str() - ); + if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { + panic!( + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); + } self } @@ -299,7 +301,7 @@ pub enum ScriptOutcome { } impl ScriptOutcome { - pub const fn as_str(&self) -> &'static str { + pub fn as_str(&self) -> &'static str { match self { Self::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", Self::OkSimulation => "SIMULATION COMPLETE. To broadcast these", @@ -323,7 +325,7 @@ impl ScriptOutcome { } } - pub const fn is_err(&self) -> bool { + pub fn is_err(&self) -> bool { match self { Self::OkNoEndpoint | Self::OkSimulation diff --git a/npm/bun.lock b/npm/bun.lock index e8ef087be9d50..d9e8fed5ce479 100644 --- a/npm/bun.lock +++ b/npm/bun.lock @@ -1,12 +1,13 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^24.9.1", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3", + "typescript": "^6.0.2", }, }, }, @@ -35,7 +36,7 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], - "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], @@ -45,8 +46,12 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "bun-types/@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } } diff --git a/npm/scripts/publish.mjs b/npm/scripts/publish.mjs index 0f582c8ebc555..deee29e46c92f 100644 --- a/npm/scripts/publish.mjs +++ b/npm/scripts/publish.mjs @@ -7,17 +7,12 @@ import { colors } from '#const.mjs' const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' -const NPM_TOKEN = Bun.env.NPM_TOKEN -if (!NPM_TOKEN) throw new Error('NPM_TOKEN is required') - main().catch(error => { console.error(error) process.exit(1) }) async function main() { - const npmToken = Bun.env.NPM_TOKEN - if (!npmToken) throw new Error('NPM_TOKEN is required') const inputPath = Bun.argv[2] if (!inputPath) throw new Error('Package path is required') @@ -114,11 +109,6 @@ async function setPackageVersion(packagePath, version) { console.info(colors.green, 'Setting package version:', version) const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` .cwd(packagePath) - .env({ - ...Bun.env, - ...process.env, - NPM_TOKEN - }) .quiet() .nothrow() diff --git a/npm/tsconfig.json b/npm/tsconfig.json index ca8e3c92f2daa..a7a0093d7ee56 100644 --- a/npm/tsconfig.json +++ b/npm/tsconfig.json @@ -2,7 +2,6 @@ "schema": "https://json.schemastore.org/tsconfig.json", "compilerOptions": { "strict": true, - "baseUrl": ".", "noEmit": true, "allowJs": true, "checkJs": true, From 4adcd2918acf2f668c0bd5cb074105e8e6eeab01 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 05:45:49 +0700 Subject: [PATCH 346/391] Update crates/doc/src/parser/comment.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/doc/src/parser/comment.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index ee0175a5e21b1..3d4c57fe5dc3e 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -157,12 +157,6 @@ impl From> for Comments { } } -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value) - } -} - /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); From cd92feeb94bc6bb08f6e6816ec9e5f2e68835806 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 05:46:19 +0700 Subject: [PATCH 347/391] Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/evm/traces/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 5e8c452c8695a..a9c149ef10bcc 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -372,7 +372,7 @@ impl TraceMode { 3..=4 => std::cmp::max(self, Self::Call), // Enable step recording for backtraces when verbosity is 5 or higher. // We need to ensure we're recording JUMP AND JUMPDEST steps. - _ => std::cmp::min(self, Self::Steps), + _ => std::cmp::max(self, Self::Steps), } } From eca1d88662817bd1ae34e78ab4f053c527457920 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:10:08 +0700 Subject: [PATCH 348/391] Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/anvil/src/cmd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 07c345b4ce81a..2215e914c2e36 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -648,8 +648,8 @@ impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { let mut resolved_urls = Vec::new(); + let mut endpoints = config.rpc_endpoints.clone().resolved(); for fork_url in &self.fork_url { - let mut endpoints = config.rpc_endpoints.clone().resolved(); if let Some(endpoint) = endpoints.remove(&fork_url.url) { // Alias matched — expand all URLs from the endpoint config match endpoint.all_urls() { From 99b827a469f77a9c4d1382af097861c021f48b4c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:11:29 +0700 Subject: [PATCH 349/391] Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/script/src/simulate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 5c5155aa1678b..26c1bd7ed9c04 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -71,7 +71,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if to.is_some() { + if let Some(alloy_primitives::TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, From cafb30dbdd8eaed95efda81567bd6ca301c15265 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:12:10 +0700 Subject: [PATCH 350/391] Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/script/src/simulate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 26c1bd7ed9c04..4ee9c3fb53cb6 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -129,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = tx.to(); + let to = if let Some(alloy_primitives::TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() From c3b65e25a99c29d132e307b92c77a5b160da7a14 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:12:42 +0700 Subject: [PATCH 351/391] Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cast/src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 94a435c08559b..d36e74b53b6d1 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -801,7 +801,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{decoded_tx}")?; + sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; From 926c4d1b81a10eed9bf26b42b7cdde6a47d916e1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:38:07 +0700 Subject: [PATCH 352/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/tempo/keystore.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index 18edf39be59bd..eb1e5649092b1 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -6,7 +6,7 @@ use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; use serde::Deserialize; -use std::path::PathBuf; +use std::path::{Component, Path, PathBuf}; /// Environment variable for an ephemeral Tempo private key. pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; @@ -99,9 +99,23 @@ pub struct KeysFile { /// Resolve the Tempo home directory. /// /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. +fn is_safe_tempo_home_override(path: &Path) -> bool { + !path + .components() + .any(|component| matches!(component, Component::ParentDir)) +} + pub fn tempo_home() -> Option { if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { - return Some(PathBuf::from(home)); + let candidate = PathBuf::from(home); + if is_safe_tempo_home_override(&candidate) { + return Some(candidate); + } + tracing::warn!( + env = TEMPO_HOME_ENV, + ?candidate, + "ignoring unsafe TEMPO_HOME override containing parent directory components" + ); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) } From cc0ef96672cd98a241491ed8905f60fda1c67cae Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:04:31 +0700 Subject: [PATCH 353/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From 88ba719044302ba69c2698f7b3b4776af007ad56 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:05:04 +0700 Subject: [PATCH 354/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From b51d8a0e39bdcfc449345559e5ab649eebb68b43 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:09:58 +0000 Subject: [PATCH 355/391] Foundry/master test ux (#478) * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> From 0a7e77a9886823c39fa08ae45a16eac4827feaa9 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:13:33 +0700 Subject: [PATCH 356/391] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/tempo/keystore.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index 18edf39be59bd..0c1ce2d1a3c48 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -101,11 +101,29 @@ pub struct KeysFile { /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. pub fn tempo_home() -> Option { if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { - return Some(PathBuf::from(home)); + if is_safe_home_override(&home) { + return Some(PathBuf::from(home)); + } + tracing::warn!( + env = TEMPO_HOME_ENV, + "ignoring unsafe TEMPO_HOME override; falling back to default" + ); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) } +/// Validates a Tempo home override from environment. +/// +/// Accepts only a single relative path component (no separators, no `..`, +/// and not absolute) to prevent path traversal / arbitrary path targeting. +fn is_safe_home_override(home: &str) -> bool { + if home.trim().is_empty() || home.contains("..") || home.contains('/') || home.contains('\\') { + return false; + } + let p = std::path::Path::new(home); + p.components().count() == 1 && !p.is_absolute() +} + /// Returns the path to the Tempo wallet keys file. pub fn tempo_keys_path() -> Option { tempo_home().map(|home| home.join(WALLET_KEYS_PATH)) From d6dd9906dc87572a212c47aea30dba4eda8875d3 Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Thu, 23 Apr 2026 08:26:00 +0700 Subject: [PATCH 357/391] Remove CircleCI; update workflows, code, deps Delete CircleCI deployment config (.circleci/ci_deploy.yml). Update GitHub workflow files (.github/workflows/Docker.yml and docker.yml) to trigger on pushes to master and pin docker/metadata-action to v5 (was v6). Adjust Rust output in crates/cast/src/args.rs to print decoded_tx via its Display implementation instead of serializing with serde_json::to_string_pretty. Bump npm dev deps in npm/package.json: @types/node to ^25.5.2 and typescript to ^6.0.2. Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com> Co-Authored-By: Dargon789 <64915515+dargon789@users.noreply.github.com> Signed-off-by: googleworkspace-bot --- .circleci/ci_deploy.yml | 34 ---------------------------------- .github/workflows/Docker.yml | 3 ++- .github/workflows/docker.yml | 3 ++- crates/cast/src/args.rs | 2 +- npm/package.json | 4 ++-- 5 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 53b7d1ed0ac7c..7b85ca2ae00c8 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,6 +4,7 @@ on: push: tags: ["*"] branches: + - "master" pull_request: branches: ["**"] @@ -35,7 +36,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 53b7d1ed0ac7c..7b85ca2ae00c8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,7 @@ on: push: tags: ["*"] branches: + - "master" pull_request: branches: ["**"] @@ -35,7 +36,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index d36e74b53b6d1..94a435c08559b 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -801,7 +801,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; + sh_println!("{decoded_tx}")?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; diff --git a/npm/package.json b/npm/package.json index d596a7930b609..bac357038a1ea 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,9 +8,9 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^25.0.2", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3" + "typescript": "^6.0.2" }, "license": "MIT OR Apache-2.0", "$schema": "https://json.schemastore.org/package.json" From a95aaa53601f51ddd6e55586c89e9eb54878b5bb Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:36:03 +0000 Subject: [PATCH 358/391] Dargon789/gamefi (#480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revise Foundry benchmark results and system info (#407) Updated benchmark results for Foundry versions and system information. https://github.com/foundry-rs/foundry/commit/1c4d334e95bc1cad3a390cc735d0f3ec59eea07f Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#413) * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration… * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * doc-script.js (#438) * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <… * fix(cheatcodes): read broadcasts with the active network #437 (#440) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry (#14347) * fix(mpp): fetch fresh challenge on verification-failed key provisioning retry When an access key is refreshed but not yet provisioned on-chain, the first charge payment strips key_authorization (assuming provisioned), the server rejects with verification-failed, and the retry must obtain a fresh 402 challenge — the server consumes challenge IDs on first use, so reusing the original challenge would fail again. Amp-Thread-ID: https://ampcode.com/threads/T-019d8bde-8cf7-778c-9c9c-727632e62bd4 Co-authored-by: Amp * refactor(mpp): extract select_challenge helper, improve retry robustness - Extract shared select_challenge() for consistent 402 diagnostics on both initial and verification-failed retry paths - Don't restore key_provisioned(true) on non-402 fresh probe responses to avoid bad state on transient server errors - Add rollback_pending() to key-not-provisioned retry to prevent dirty local state from producing wrong credential shapes Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019d9619-6c24-75af-b1e8-7d02d77e0561 --------- Co-authored-by: Amp * refactor(evm): remove useless Eth/Tempo EVM wrapper structs (#14350) * chore(mpp): add built-in rpc url mapping (#14353) add built-in rpc url mapping * fix(evm): skip isolation for CREATE2 factory redirect calls (#14360) * fix(evm): preserve CREATE2 redirect state across isolated transactions (#14363) * feat(init): add `SignatureVerifier` to tempo example (#14351) * feat(init): add `SignatureVerifier` to tempo example * chore: also call `recover()` * style: typo --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(config): validate `optimizer_runs` does not exceed u32::MAX (#14354) * fix(config): validate `optimizer_runs` does not exceed u32::MAX * fix clippy * ci: remove softprops/action-gh-release dependabot ignore (#14364) The draft-finalize race that caused `Too many retries` in matrix jobs (v2.5.0) was fixed in v2.5.2. We're already on v2.6.1, so the ignore rule is no longer needed. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): mine TIP20 virtual address master salt (#14365) * chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 (#14367) * chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#14368) * chore(deps): bump taiki-e/install-action from 2.75.0 to 2.75.5 (#14369) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.75.0 to 2.75.5. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cf39a74df4a72510be4e5b63348d61067f11e64a...a2352fc6ce487f030a3aa709482d57823eadfb37) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.75.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump anstream from 0.6.21 to 1.0.0 (#14329) Bumps [anstream](https://github.com/rust-cli/anstyle) from 0.6.21 to 1.0.0. - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0) --- updated-dependencies: - dependency-name: anstream dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * Update flake.lock (#14375) Co-authored-by: github-actions[bot] * fix(ci): handle stale branches in bump-tempo workflow (#14377) * fix(config): surface cleanup failures as warnings instead of hard errors (#14379) * fix(config): surface cleanup failures as warnings instead of hard errors Cache cleanup is best-effort by design. Instead of silently swallowing errors or making them fatal, return warnings from Config and emit them via sh_warn! at the CLI layer. All cleanup steps now run even if some fail. Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * fix: address CI - collapse nested ifs, remove disallowed eprintln Amp-Thread-ID: https://ampcode.com/threads/T-019da9b2-d739-753a-a978-71ec793678a5 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> * chore(tempo): bump rev + use extension traits (#14378) * feat(tempo): bump tempo rev and use TempoProviderExt::is_hardfork_active Bumps tempo crates from 6f4f5cc to bb08bb9 (latest main) and replaces the local is_hardfork_active helper with the new trait method on TempoProviderExt, which queries the tempo_forkSchedule RPC directly. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: propagate is_hardfork_active error instead of unwrap_or Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * refactor: use TempoAddressExt in tempo_labels and fee token parsing Replace is_tip20_prefix() with addr.is_tip20() in the TempoLabels inspector and use Address::TIP20_PREFIX instead of hardcoded hex bytes in token_id_to_address. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 * fix: enable reth-codec feature on tempo-primitives and fix rustfmt The rev bump split tempo crates into two copies (6f4f5cc for mpp-rs, bb08bb9 for foundry). The bb08bb9 copy lost the reth-codec feature that was previously unified from other workspace members. Explicitly enable it to restore Compact derives on TempoTxEnvelope/TempoHeader. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019da9d4-456b-75e5-a0e5-ae9287a7f5c7 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * chore: remove cargo-update workflow (#14382) Dependabot is now configured for weekly cargo updates, making this workflow redundant. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): return error for eth_getLogs with unknown blockHash instead of empty (#14371) Co-authored-by: Claude Opus 4.7 (1M context) * refactor(cast): cleanup `cast send` (#14385) * refactor(cast): cleanup `cast send` * docs: clarify `TempoNetwork` requirement * style: clippy * fix: review feedback * fix(anvil): refetch full fork blocks with missing tx cache (#14384) * docs: correct Tempo TIP-1022 documentation URL (#14387) * feat(verify): clearer error when `ETHERSCAN_API_KEY` selects etherscan (#14383) * feat: make asm-keccak a default feature in all binaries (#14389) * chore(wallets): move `wallets` crate to `foundry-core` (#14348) * fix(cheatcodes): read broadcasts with the active network (#14388) --------- Signed-off-by: dependabot[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: Louis Peter Sitoe Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: o-az * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/miner.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Docker.yml * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#457) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#460) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * foundry-rs#14280 (#462) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(npm): remove deprecated baseUrl compiler option (foundry-rs#14413) (#467) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * 7 bug release workflow failed (#470) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fi… * Potential fix for code scanning alert no. 19 (#471) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 156: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create apisec-scan.yml (#404) * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bo… * Update crates/doc/src/parser/comment.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove CircleCI; update workflows, code, deps Delete CircleCI deployment config (.circleci/ci_deploy.yml). Update GitHub workflow files (.github/workflows/Docker.yml and docker.yml) to trigger on pushes to master and pin docker/metadata-action to v5 (was v6). Adjust Rust output in crates/cast/src/args.rs to print decoded_tx via its Display implementation instead of serializing with serde_json::to_string_pretty. Bump npm dev deps in npm/package.json: @types/node to ^25.5.2 and typescript to ^6.0.2. Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com> Co-Authored-By: Dargon789 <64915515+dargon789@users.noreply.github.com> Signed-off-by: googleworkspace-bot --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: googleworkspace-bot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: cui Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: Arsh Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az Co-authored-by: steven Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .github/scripts/bump-tempo.sh | 0 .github/scripts/tempo-check.sh | 0 .github/scripts/tempo-deploy.sh | 0 .github/scripts/tempo-mpp.sh | 0 .github/scripts/tempo-wallet.sh | 0 .github/workflows/Docker.yml | 3 ++- .github/workflows/docker.yml | 3 ++- crates/anvil/src/cmd.rs | 2 +- crates/cast/src/args.rs | 2 +- crates/cast/src/cmd/miner.rs | 4 ++-- crates/cheatcodes/src/fs.rs | 11 +++++++++- crates/common/src/tempo/keystore.rs | 28 +++++++++++------------- crates/config/src/lib.rs | 33 +++++++++++++++++++++++------ crates/doc/src/parser/comment.rs | 12 ----------- crates/evm/traces/src/lib.rs | 2 +- crates/forge/Cargo.toml | 1 - crates/script/src/simulate.rs | 4 ++-- crates/test-utils/src/script.rs | 26 +++++++++++++---------- npm/package.json | 4 ++-- 19 files changed, 76 insertions(+), 59 deletions(-) mode change 100755 => 100644 .github/scripts/bump-tempo.sh mode change 100755 => 100644 .github/scripts/tempo-check.sh mode change 100755 => 100644 .github/scripts/tempo-deploy.sh mode change 100755 => 100644 .github/scripts/tempo-mpp.sh mode change 100755 => 100644 .github/scripts/tempo-wallet.sh diff --git a/.github/scripts/bump-tempo.sh b/.github/scripts/bump-tempo.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-deploy.sh b/.github/scripts/tempo-deploy.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-wallet.sh b/.github/scripts/tempo-wallet.sh old mode 100755 new mode 100644 diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 53b7d1ed0ac7c..7b85ca2ae00c8 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,6 +4,7 @@ on: push: tags: ["*"] branches: + - "master" pull_request: branches: ["**"] @@ -35,7 +36,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 53b7d1ed0ac7c..7b85ca2ae00c8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,7 @@ on: push: tags: ["*"] branches: + - "master" pull_request: branches: ["**"] @@ -35,7 +36,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 07c345b4ce81a..2215e914c2e36 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -648,8 +648,8 @@ impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { let mut resolved_urls = Vec::new(); + let mut endpoints = config.rpc_endpoints.clone().resolved(); for fork_url in &self.fork_url { - let mut endpoints = config.rpc_endpoints.clone().resolved(); if let Some(endpoint) = endpoints.remove(&fork_url.url) { // Alias matched — expand all URLs from the endpoint config match endpoint.all_urls() { diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index d36e74b53b6d1..94a435c08559b 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -801,7 +801,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; + sh_println!("{decoded_tx}")?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; diff --git a/crates/cast/src/cmd/miner.rs b/crates/cast/src/cmd/miner.rs index 346efb9a503a0..3b901235258ae 100644 --- a/crates/cast/src/cmd/miner.rs +++ b/crates/cast/src/cmd/miner.rs @@ -23,8 +23,8 @@ where handles.push(std::thread::spawn(move || { #[repr(C)] - struct B256Aligned(B256, [usize; 0]); - +#[repr(C, align(8))] +struct B256Aligned(B256); let mut salt = B256Aligned(salt, []); // SAFETY: `B256` is aligned to `usize`. let salt_word = unsafe { diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 5e762ec62a035..20ba969abbff1 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1129,6 +1129,15 @@ mod tests { assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); assert!(latest.success); - stdfs::remove_dir_all(root).unwrap(); + if root.exists() { + let root_canon = stdfs::canonicalize(&root).unwrap(); + let temp_canon = stdfs::canonicalize(env::temp_dir()).unwrap(); + assert!( + root_canon.starts_with(&temp_canon), + "refusing to remove non-temp test directory: {}", + root_canon.display() + ); + stdfs::remove_dir_all(&root).unwrap(); + } } } diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index 0c1ce2d1a3c48..eb1e5649092b1 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -6,7 +6,7 @@ use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; use serde::Deserialize; -use std::path::PathBuf; +use std::path::{Component, Path, PathBuf}; /// Environment variable for an ephemeral Tempo private key. pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; @@ -99,31 +99,27 @@ pub struct KeysFile { /// Resolve the Tempo home directory. /// /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. +fn is_safe_tempo_home_override(path: &Path) -> bool { + !path + .components() + .any(|component| matches!(component, Component::ParentDir)) +} + pub fn tempo_home() -> Option { if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { - if is_safe_home_override(&home) { - return Some(PathBuf::from(home)); + let candidate = PathBuf::from(home); + if is_safe_tempo_home_override(&candidate) { + return Some(candidate); } tracing::warn!( env = TEMPO_HOME_ENV, - "ignoring unsafe TEMPO_HOME override; falling back to default" + ?candidate, + "ignoring unsafe TEMPO_HOME override containing parent directory components" ); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) } -/// Validates a Tempo home override from environment. -/// -/// Accepts only a single relative path component (no separators, no `..`, -/// and not absolute) to prevent path traversal / arbitrary path targeting. -fn is_safe_home_override(home: &str) -> bool { - if home.trim().is_empty() || home.contains("..") || home.contains('/') || home.contains('\\') { - return false; - } - let p = std::path::Path::new(home); - p.components().count() == 1 && !p.is_absolute() -} - /// Returns the path to the Tempo wallet keys file. pub fn tempo_keys_path() -> Option { tempo_home().map(|home| home.join(WALLET_KEYS_PATH)) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f148432c42bed..b1b889031c690 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1314,13 +1314,32 @@ impl Config { } // Remove last test run failures file. - if let Err(err) = fs::remove_file(&self.test_failures_file) - && err.kind() != io::ErrorKind::NotFound - { - warnings.push(format!( - "failed to remove test failures file {}: {err}", - self.test_failures_file.display() - )); + let test_failures_path = if self.test_failures_file.is_absolute() { + self.test_failures_file.clone() + } else { + project.root().join(&self.test_failures_file) + }; + let root_canon = dunce::canonicalize(project.root()).unwrap_or_else(|_| project.root().to_path_buf()); + let validated_test_failures_path = test_failures_path + .parent() + .and_then(|parent| dunce::canonicalize(parent).ok().map(|p| (p, test_failures_path.file_name()))) + .and_then(|(parent, file_name)| file_name.map(|name| parent.join(name))); + + match validated_test_failures_path { + Some(path) if path.starts_with(&root_canon) => { + if let Err(err) = fs::remove_file(&path) && err.kind() != io::ErrorKind::NotFound { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + path.display() + )); + } + } + _ => { + warnings.push(format!( + "skipped removing test failures file outside project root: {}", + test_failures_path.display() + )); + } } // Remove fuzz and invariant cache directories. diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index ee0175a5e21b1..3e915b1b7f479 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -151,18 +151,6 @@ impl From> for Comments { } } -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value) - } -} - -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value) - } -} - /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 5e8c452c8695a..a9c149ef10bcc 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -372,7 +372,7 @@ impl TraceMode { 3..=4 => std::cmp::max(self, Self::Call), // Enable step recording for backtraces when verbosity is 5 or higher. // We need to ensure we're recording JUMP AND JUMPDEST steps. - _ => std::cmp::min(self, Self::Steps), + _ => std::cmp::max(self, Self::Steps), } } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index ecdc13ded67cf..064834d248d5f 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,7 +62,6 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true -alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 5c5155aa1678b..4ee9c3fb53cb6 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -71,7 +71,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if to.is_some() { + if let Some(alloy_primitives::TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -129,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = tx.to(); + let to = if let Some(alloy_primitives::TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 8ee2228ebfe1d..4d2e031f05639 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -118,18 +118,29 @@ impl ScriptTester { fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); + let canonical_from_dir = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; let from_dir = from_dir.canonicalize()?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); + + // Canonicalize and confine entry path before any filesystem operation on it. + let canonical_file = match file.canonicalize() { + Ok(path) => path, + Err(_) => continue, + }; + if !canonical_file.starts_with(&canonical_from_dir) { + continue; + } + // Only operate on regular files to avoid following symlinks or directories - let metadata = fs::symlink_metadata(&file)?; + let metadata = fs::symlink_metadata(&canonical_file)?; let ftype = metadata.file_type(); if !ftype.is_file() { continue; } - let name = match file.file_name() { + let name = match canonical_file.file_name() { Some(name) => name, None => continue, }; @@ -139,15 +150,8 @@ impl ScriptTester { // Skip invalid (potentially dangerous) file names continue; } - // Verify canonicalized file is in from_dir to avoid symlink traversal - if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { - continue; - } - } else { - continue; - } - fs::copy(&file, to_dir.join(name))?; + + fs::copy(&canonical_file, to_dir.join(name))?; } Ok(()) } diff --git a/npm/package.json b/npm/package.json index d596a7930b609..bac357038a1ea 100644 --- a/npm/package.json +++ b/npm/package.json @@ -8,9 +8,9 @@ }, "dependencies": { "@types/bun": "^1.3.1", - "@types/node": "^25.0.2", + "@types/node": "^25.5.2", "bun": "^1.3.1", - "typescript": "^5.9.3" + "typescript": "^6.0.2" }, "license": "MIT OR Apache-2.0", "$schema": "https://json.schemastore.org/package.json" From aa0525c797b05dad1c610e16a6cdbbadba54fd17 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:22:58 +0700 Subject: [PATCH 359/391] Update benches/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ab3bec614e3cd..c1d84e8d188f2 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -136,7 +136,7 @@ impl BenchmarkProject { Err(_) => continue, // Skip if unable to canonicalize }; // Ensure canonicalized path stays strictly within root_path (TempProject root) - if !canon.starts_with(&root_path) { + if !canon.starts_with(root_path.canonicalize().unwrap_or_else(|_| root_path.clone())) { sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); continue; } From dd87c0c845219a39e15929cf84b582d26a36203b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:23:14 +0700 Subject: [PATCH 360/391] Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/cli/src/utils/suggestions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } From 29c11e58bc85427fed1a6fecafb47732bfdbbfff Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:23:30 +0700 Subject: [PATCH 361/391] Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/common/src/contracts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } From a5ad063abbbeda29314a8be0d3828b58292dc7ef Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:23:47 +0700 Subject: [PATCH 362/391] Update crates/test-utils/src/script.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/test-utils/src/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 4e318b7fc5569..f0f8ef94ed978 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -131,7 +131,7 @@ impl ScriptTester { } // Optionally verify canonicalized file is in from_dir to avoid symlink traversal if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { + if !canonical_file.starts_with(from_dir.canonicalize().unwrap_or_else(|_| from_dir.clone())) { continue; } } From a2b7742c76055fbde74716f437e7a37382e53f23 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 24 Apr 2026 06:38:14 +0000 Subject: [PATCH 363/391] Main (#486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#413) * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration… * Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/miner.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#457) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#460) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * foundry-rs#14280 (#462) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(npm): remove deprecated baseUrl compiler option (foundry-rs#14413) (#467) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * 7 bug release workflow failed (#470) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fi… * Potential fix for code scanning alert no. 19 (#471) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 156: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create apisec-scan.yml (#404) * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bo… * Update crates/doc/src/parser/comment.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: googleworkspace-bot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: cui Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: Arsh Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: steven Co-authored-by: zerosnacks Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ++++++++++++ .github/workflows/Docker.yml | 3 +- .github/workflows/docker.yml | 3 +- .github/workflows/static.yml | 4 +- counter/.gitignore | 4 ++ counter/foundry.toml | 5 +- crates/cli/src/utils/suggestions.rs | 2 +- crates/common/src/contracts.rs | 2 +- crates/config/spec/src/lib.rs | 3 +- crates/doc/src/parser/comment.rs | 6 -- crates/evm/traces/src/lib.rs | 2 +- crates/forge/Cargo.toml | 1 - crates/lint/src/linter.rs | 4 +- crates/script/src/simulate.rs | 86 +++++++++++++++++------------ crates/test-utils/src/script.rs | 42 ++++++++------ 15 files changed, 128 insertions(+), 73 deletions(-) create mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index 5a2330e7d5d62..53b7d1ed0ac7c 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -4,7 +4,6 @@ on: push: tags: ["*"] branches: - - "main" pull_request: branches: ["**"] @@ -36,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5a2330e7d5d62..53b7d1ed0ac7c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,6 @@ on: push: tags: ["*"] branches: - - "main" pull_request: branches: ["**"] @@ -36,7 +35,7 @@ jobs: # Extract metadata (tags, labels) for Docker - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 0ba82305f82b2..fcfbbbbec7948 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -32,9 +32,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: # Upload entire repository path: '.' diff --git a/counter/.gitignore b/counter/.gitignore index 85198aaa55b84..052b88bb6516b 100644 --- a/counter/.gitignore +++ b/counter/.gitignore @@ -12,3 +12,7 @@ docs/ # Dotenv file .env + + +# Soldeer +/dependencies diff --git a/counter/foundry.toml b/counter/foundry.toml index 25b918f9c9a96..c27b8ed21ba0b 100644 --- a/counter/foundry.toml +++ b/counter/foundry.toml @@ -1,6 +1,9 @@ [profile.default] src = "src" out = "out" -libs = ["lib"] +libs = ["lib", "dependencies"] + +[dependencies] +forge-std = "1.15.0" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index 8f6d7f3cde092..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -17,7 +17,7 @@ where .map(|pv| (strsim::jaro_winkler(v, pv.as_ref()), pv.as_ref().to_owned())) .filter(|(similarity, _)| *similarity > 0.8) .collect(); - candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.sort_by(|a, b| a.0.total_cmp(&b.0)); candidates.into_iter().map(|(_, pv)| pv).collect() } diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e9f203ebf7539..895b16b3b4532 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -239,7 +239,7 @@ impl ContractsByArtifact { None } }) - .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2)) .map(|(_, data)| data) } diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs index 5a362e963d956..77a3bb7ab11c2 100644 --- a/crates/config/spec/src/lib.rs +++ b/crates/config/spec/src/lib.rs @@ -40,8 +40,7 @@ mod tests { /// Checks that the `file` has the specified `contents`. If that is not the /// case, updates the file and then fails the test. fn ensure_file_contents(file: &Path, contents: &str) { - if let Ok(old_contents) = fs::read_to_string(file) - && normalize_newlines(&old_contents) == normalize_newlines(contents) + if fs::read_to_string(file).map(|old| normalize_newlines(&old) == normalize_newlines(contents)).unwrap_or(false) { // File is already up to date. return; diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 3d4c57fe5dc3e..3e915b1b7f479 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -151,12 +151,6 @@ impl From> for Comments { } } -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value) - } -} - /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 5e8c452c8695a..a9c149ef10bcc 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -372,7 +372,7 @@ impl TraceMode { 3..=4 => std::cmp::max(self, Self::Call), // Enable step recording for backtraces when verbosity is 5 or higher. // We need to ensure we're recording JUMP AND JUMPDEST steps. - _ => std::cmp::min(self, Self::Steps), + _ => std::cmp::max(self, Self::Steps), } } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index ecdc13ded67cf..064834d248d5f 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,7 +62,6 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true -alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs index 2c11e0222a286..0865147167243 100644 --- a/crates/lint/src/linter.rs +++ b/crates/lint/src/linter.rs @@ -19,7 +19,7 @@ use std::{ops::ControlFlow, path::PathBuf}; /// /// # Required Methods /// -/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. pub trait Linter: Send + Sync + Clone { type Language: Language; type Lint: Lint; @@ -60,7 +60,7 @@ impl<'s> LintContext<'s> { } /// Trait for lints that operate directly on the AST. -/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. pub trait EarlyLintPass<'ast>: Send + Sync { fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 05834ed8bb036..4ee9c3fb53cb6 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -9,15 +9,21 @@ use crate::{ execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, }; +use alloy_chains::NamedChain; +use alloy_evm::revm::context::Block; use alloy_network::TransactionBuilder; -use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units}; +use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{ContractData, shell}; -use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; +use foundry_evm::{ + core::{FoundryBlock, evm::FoundryEvmNetwork}, + traces::{decode_trace_arena, render_trace_arena}, +}; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -31,23 +37,24 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState { +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result { + pub async fn fill_metadata(self) -> Result> { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -64,7 +71,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if let Some(TxKind::Call(_)) = to { + if let Some(alloy_primitives::TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -88,6 +95,7 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -100,8 +108,8 @@ impl PreSimulationState { /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque, - ) -> Result> { + transactions: VecDeque>, + ) -> Result>> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -121,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; + let to = if let Some(alloy_primitives::TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() @@ -139,7 +147,8 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.env_mut().evm_env.block_env.number += U256::from(1); + let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); + runner.executor.evm_env_mut().block_env.set_number(block_number); } let is_noop_tx = if let Some(to) = to { @@ -226,12 +235,12 @@ impl PreSimulationState { } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result> { + async fn build_runners(&self) -> Result)>> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { let n = rpcs.len(); - let s = if n != 1 { "s" } else { "" }; + let s = if n == 1 { "" } else { "s" }; sh_println!("\n## Setting up {n} EVM{s}.")?; } @@ -248,22 +257,23 @@ impl PreSimulationState { /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque, + pub transactions: VecDeque>, } -impl FilledTransactionsState { +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result { + pub async fn bundle(mut self) -> Result> { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -274,7 +284,7 @@ impl FilledTransactionsState { // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::default(); + let mut manager = ProvidersManager::::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a @@ -282,7 +292,7 @@ impl FilledTransactionsState { let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable(); while let Some(mut tx) = txes_iter.next() { - let tx_rpc = tx.rpc.to_owned(); + let tx_rpc = tx.rpc.clone(); let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; if let Some(tx) = tx.tx_mut().as_unsigned_mut() { @@ -297,7 +307,7 @@ impl FilledTransactionsState { // only estimate gas for unsigned transactions if let Some(tx) = tx.as_unsigned_mut() { trace!("estimating with different gas calculation"); - let gas = tx.gas.expect("gas is set by simulation."); + let gas = tx.gas_limit().expect("gas is set by simulation."); // We are trying to show the user an estimation of the total gas usage. // @@ -351,6 +361,12 @@ impl FilledTransactionsState { for (rpc, total_gas) in total_gas_per_rpc { let provider_info = manager.get(&rpc).expect("provider is set."); + // Get the native token symbol for the chain using NamedChain + let token_symbol = NamedChain::try_from(provider_info.chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); + // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { @@ -368,15 +384,7 @@ impl FilledTransactionsState { .unwrap_or_else(|_| "[Could not calculate]".to_string()); let estimated_amount = estimated_amount_raw.trim_end_matches('0'); - if !shell::is_json() { - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; - sh_println!("\n==========================")?; - } else { + if shell::is_json() { sh_println!( "{}", serde_json::json!({ @@ -384,8 +392,17 @@ impl FilledTransactionsState { "estimated_gas_price": estimated_gas_price, "estimated_total_gas_used": total_gas, "estimated_amount_required": estimated_amount, + "token_symbol": token_symbol, }) )?; + } else { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\n==========================")?; } } } @@ -406,6 +423,7 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -416,14 +434,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque, - ) -> Result { + transactions: VecDeque>, + ) -> Result> { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::get_paths( + Some(ScriptSequence::::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index c1a6cb53bdbff..4d2e031f05639 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -118,17 +118,29 @@ impl ScriptTester { fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); let from_dir = testdata.join("utils"); + let canonical_from_dir = from_dir.canonicalize()?; let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; + let from_dir = from_dir.canonicalize()?; for entry in fs::read_dir(&from_dir)? { let file = entry?.path(); + + // Canonicalize and confine entry path before any filesystem operation on it. + let canonical_file = match file.canonicalize() { + Ok(path) => path, + Err(_) => continue, + }; + if !canonical_file.starts_with(&canonical_from_dir) { + continue; + } + // Only operate on regular files to avoid following symlinks or directories - let metadata = fs::symlink_metadata(&file)?; + let metadata = fs::symlink_metadata(&canonical_file)?; let ftype = metadata.file_type(); if !ftype.is_file() { continue; } - let name = match file.file_name() { + let name = match canonical_file.file_name() { Some(name) => name, None => continue, }; @@ -138,15 +150,8 @@ impl ScriptTester { // Skip invalid (potentially dangerous) file names continue; } - // Verify canonicalized file is in from_dir to avoid symlink traversal - if let Ok(canonical_file) = file.canonicalize() { - if !canonical_file.starts_with(&from_dir) { - continue; - } - } else { - continue; - } - fs::copy(&file, to_dir.join(name))?; + + fs::copy(&canonical_file, to_dir.join(name))?; } Ok(()) } @@ -247,11 +252,12 @@ impl ScriptTester { trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - assert!( - !(!stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str())), - "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", - expected.as_str() - ); + if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { + panic!( + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); + } self } @@ -299,7 +305,7 @@ pub enum ScriptOutcome { } impl ScriptOutcome { - pub const fn as_str(&self) -> &'static str { + pub fn as_str(&self) -> &'static str { match self { Self::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", Self::OkSimulation => "SIMULATION COMPLETE. To broadcast these", @@ -323,7 +329,7 @@ impl ScriptOutcome { } } - pub const fn is_err(&self) -> bool { + pub fn is_err(&self) -> bool { match self { Self::OkNoEndpoint | Self::OkSimulation From 044ef43b5d568210b85a953e00a2ce71125e0072 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 07:15:21 +0000 Subject: [PATCH 364/391] Main (#493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#413) * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration… * Delete .circleci directory (#422) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/miner.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/lint/src/linter.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#457) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) (#460) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * foundry-rs#14280 (#462) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(npm): remove deprecated baseUrl compiler option (foundry-rs#14413) (#467) * feat(anvil): support multiple fork URLs with round-robin load balancing (#14280) * feat(anvil): support multiple fork URLs with fallback Allow `--fork-url` to be specified multiple times to distribute RPC requests across endpoints using Alloy's FallbackService. Endpoints are scored by latency and success rate; unhealthy endpoints (429s, timeouts) are automatically deprioritized. Uses active_transport_count=1 for sequential best-endpoint routing. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * fix: fmt * feat(config): support multi-endpoint `endpoints` array in foundry.toml Add `endpoints` key to `[rpc_endpoints]` config as a backwards-compatible alternative to `endpoint`. When an alias with multiple endpoints is used as `--fork-url`, all URLs are expanded for multi-endpoint forking. Example: [rpc_endpoints] mainnet = { endpoints = ["https://rpc1.example.com", "https://rpc2.example.com"], retries = 5 } Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * test: add guardrail tests and handle curl_mode in build_fallback - Add test confirming `requires = "fork_url"` guards still fire with Vec - Bail on curl_mode in build_fallback (incompatible with multi-endpoint) - Clean up redundant extra_endpoints default in From impl Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d85e6-b08f-748c-8f05-17ebee8e17f8 * refactor(anvil): consolidate eth_rpc_url into fork_urls Remove duplicate `eth_rpc_url` field from `NodeConfig` and `ClientForkConfig`. The primary URL is now always `fork_urls[0]`, eliminating the invariant that `eth_rpc_url == fork_urls[0]`. - `ClientForkConfig::eth_rpc_url()` is now an accessor returning `&str` - `NodeConfig::with_eth_rpc_url()` kept as convenience, wraps into `fork_urls` - Checks for forking enabled use `fork_urls.is_empty()` instead of `eth_rpc_url.is_none()` Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): simplify update_url to replace all fork_urls When resetting the fork via anvil_reset or anvil_setRpcUrl, the intent is to switch the fork target entirely — not swap one endpoint in a load-balanced pool. Replace the whole fork_urls vec with the single new URL instead of mutating fork_urls[0] in-place. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): rename update_url to update_urls taking Vec Accepts a Vec so the reset/update path can reconstruct a fallback provider when given multiple URLs, instead of always collapsing to a single endpoint. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor(anvil): update ClientFork::reset to accept Vec Change reset(url: Option) to reset(urls: Vec) so the reset path can preserve multi-endpoint fallback. The underlying MaybeForkedDatabase::maybe_reset still takes Option since it ignores the url anyway (marked TODO upstream). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * refactor: use Vec throughout reset path Commit to Vec in MaybeForkedDatabase::maybe_reset, ForkedDatabase::reset, and ClientFork::reset. The Option → Vec conversion now happens only at the RPC boundary (Forking.json_rpc_url in reset_fork). Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * fix: fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d8b67-c7cc-764f-bb5e-74b6e8fa6858 * round-robin for load-balance * comments * fix(anvil): sync fork_urls after anvil_setRpcUrl and anvil_reset Fix two bugs where node_config.fork_urls could get out of sync: 1. reset_block_number() now updates node_config.fork_urls before calling setup_fork_db_config(), preventing stale multi-URL lists from persisting after anvil_reset with a new URL. 2. anvil_setRpcUrl now also updates node_config.fork_urls, so subsequent anvil_reset(None) uses the correct URL instead of reverting to the original startup URL. Added regression tests for both scenarios. Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp * chore: fmt Amp-Thread-ID: https://ampcode.com/threads/T-019db0c0-6038-7428-8483-365402ff31a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * 7 bug release workflow failed (#470) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fi… * Potential fix for code scanning alert no. 19 (#471) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * deprecate forge config --basic * fix * start adding schema generation for config * naive implementation, restore basic config - relevant for use in forge init * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 156: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create apisec-scan.yml (#404) * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bo… * Update crates/doc/src/parser/comment.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/anvil/src/cmd.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/args.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: googleworkspace-bot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: cui Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: Arsh Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: steven Co-authored-by: zerosnacks Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: Louis Peter Sitoe Co-authored-by: o-az Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> From c4c131e4a5122543c9c8ee891432c54b4bbac4a5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 07:26:50 +0000 Subject: [PATCH 365/391] Add CircleCI configuration for testing workflow (#494) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/config.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..2ef62819f07dc --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,22 @@ +version: 2.1 + +jobs: + test: + docker: + - image: ghcr.io/foundry-rs/foundry:latest + steps: + - checkout + - run: + name: Install submodules + command: git submodule update --init --recursive + - run: + name: Build + command: forge build + - run: + name: Test + command: forge test -vvv + +workflows: + main: + jobs: + - test From fb01017d511bbe31f8b7a746a752d406c8efa48d Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:31:34 +0700 Subject: [PATCH 366/391] Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/script/src/simulate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 4ee9c3fb53cb6..43b529a8c3e98 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -363,10 +363,9 @@ impl FilledTransactionsState { // Get the native token symbol for the chain using NamedChain let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() + let token_symbol = alloy_chains::Chain::from_id(provider_info.chain) .native_currency_symbol() .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { From a4b89428bae66a26c17706dc527db7a36513c944 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 07:37:53 +0000 Subject: [PATCH 367/391] Update crates/script/src/simulate.rs (#488) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- Cargo.lock | 60 ++++++++++++++++++++++++----- crates/cast/src/args.rs | 6 +-- crates/evm/evm/src/executors/mod.rs | 33 ++++++++++++++-- crates/script/src/execute.rs | 13 ++++++- crates/script/src/runner.rs | 8 +++- crates/script/src/simulate.rs | 3 +- 6 files changed, 100 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60349fa6ca760..71d56653d7a20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85805c194576017df6c11057504e1d60b36f3913f8e365945486931f6ee81e40" +checksum = "a547705d5c1b42575a0542bae2ba45bc62a6154be86611afaef1c0ab5c38598e" dependencies = [ "alloy-consensus", "alloy-contract", @@ -2579,7 +2579,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.23.0", + "darling 0.20.11", "ident_case", "prettyplease", "proc-macro2", @@ -2785,6 +2785,7 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-evm-networks", "foundry-test-utils", "foundry-wallets", "futures", @@ -4985,7 +4986,6 @@ dependencies = [ "dotenvy", "dunce", "eyre", - "foundry-block-explorers", "foundry-cli-markdown", "foundry-common", "foundry-compilers", @@ -5063,6 +5063,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-wallets", + "futures", "itertools 0.14.0", "jiff", "mpp", @@ -5073,6 +5074,7 @@ dependencies = [ "regex", "reqwest 0.13.2", "revm", + "rustls", "semver 1.0.28", "serde", "serde_json", @@ -5082,6 +5084,7 @@ dependencies = [ "tempo-primitives", "thiserror 2.0.18", "tokio", + "tokio-tungstenite 0.28.0", "toml", "tower", "tracing", @@ -5442,6 +5445,7 @@ dependencies = [ "foundry-evm-hardforks", "revm", "serde", + "serde_json", ] [[package]] @@ -5598,7 +5602,8 @@ dependencies = [ [[package]] name = "foundry-wallets" version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry-core?rev=d6c92dfcb41be26fbc1de21221a5bfc6bdd93245#d6c92dfcb41be26fbc1de21221a5bfc6bdd93245" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f2cc1ff7ea002addf41bd9c5785cd34f6a7b9658998c50035cc860ffebc3a9" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -7428,13 +7433,17 @@ dependencies = [ [[package]] name = "mpp" -version = "0.9.3" -source = "git+https://github.com/tempoxyz/mpp-rs?rev=310c9a1f3fe485fa9c7a877fd4da6b7a6ab34815#310c9a1f3fe485fa9c7a877fd4da6b7a6ab34815" +version = "0.10.0" +source = "git+https://github.com/tempoxyz/mpp-rs?rev=554d20112eb014bd223d54de7f152ca59b2aa4fd#554d20112eb014bd223d54de7f152ca59b2aa4fd" dependencies = [ "alloy", + "async-stream", "base64 0.22.1", + "futures-core", + "futures-util", "hex", "hmac", + "http 1.4.0", "rand 0.9.4", "reqwest 0.12.28", "serde", @@ -7445,6 +7454,8 @@ dependencies = [ "tempo-primitives", "thiserror 2.0.18", "time", + "tokio", + "tokio-tungstenite 0.26.2", "uuid 1.23.1", ] @@ -8562,7 +8573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -10833,7 +10844,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.14.0", + "itertools 0.12.1", "itoa", "normalize-path", "once_map", @@ -10868,7 +10879,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.14.0", + "itertools 0.12.1", "memchr", "num-bigint", "num-rational", @@ -11734,6 +11745,18 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -12105,6 +12128,23 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.4", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.28.0" diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index d36e74b53b6d1..6dc2ed6acf430 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -15,10 +15,7 @@ use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::{Result, WrapErr}; -use foundry_cli::{ - opts::NetworkVariant, - utils::{self, LoadConfig}, -}; +use foundry_cli::utils::{self, LoadConfig}; use foundry_common::{ abi::{get_error, get_event}, fmt::{format_tokens, format_uint_exp, serialize_value_as_json}, @@ -31,6 +28,7 @@ use foundry_common::{ }, shell, stdin, }; +use foundry_evm_networks::NetworkVariant; use op_alloy_network::Optimism; use std::time::Instant; use tempo_alloy::TempoNetwork; diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 2d712dcf5a48d..3ffa92b459905 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -207,9 +207,9 @@ impl Executor { self.evm_env.cfg_env.spec } - /// Sets the EVM spec. - pub const fn set_spec_id(&mut self, spec_id: SpecFor) { - self.evm_env.cfg_env.spec = spec_id; + /// Sets the EVM spec and updates spec-dependent gas parameters. + pub fn set_spec_id(&mut self, spec_id: SpecFor) { + self.evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id); } /// Returns the gas limit for calls and deployments. @@ -1316,6 +1316,7 @@ impl EarlyExit { mod tests { use super::*; use foundry_evm_core::constants::MAGIC_SKIP; + use revm::{context::Cfg, primitives::hardfork::SpecId}; #[test] fn cheatcode_skip_payload_is_classified_as_skip() { @@ -1352,4 +1353,30 @@ mod tests { let err = raw.into_evm_error(None); assert!(matches!(err, EvmError::Execution(_))); } + + #[test] + fn set_spec_id_updates_spec_dependent_cfg_state() { + let backend = Backend::::spawn(None).unwrap(); + let mut executor = ExecutorBuilder::default().build( + EvmEnvFor::::default(), + TxEnvFor::::default(), + backend, + ); + + executor.evm_env_mut().cfg_env.set_spec_and_mainnet_gas_params(SpecId::HOMESTEAD); + assert_eq!( + executor.evm_env().cfg_env.gas_params(), + &revm::context_interface::cfg::GasParams::new_spec(SpecId::HOMESTEAD), + ); + assert!(!executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled()); + + executor.set_spec_id(SpecId::AMSTERDAM); + + assert_eq!(executor.spec_id(), SpecId::AMSTERDAM); + assert_eq!( + executor.evm_env().cfg_env.gas_params(), + &revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM), + ); + assert!(executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled()); + } } diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 162d061462b66..627bcfffd3802 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -168,6 +168,7 @@ impl PreExecutionState { setup_result.traces.extend(script_result.traces); setup_result.labeled_addresses.extend(script_result.labeled_addresses); setup_result.returned = script_result.returned; + setup_result.exit_reason = script_result.exit_reason; setup_result.breakpoints = script_result.breakpoints; match (&mut setup_result.transactions, script_result.transactions) { @@ -422,7 +423,11 @@ impl PreSimulationState { if !self.execution_result.success { return Err(eyre::eyre!( "script failed: {}", - &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + &self + .execution_artifacts + .decoder + .revert_decoder + .decode(&result.returned[..], result.exit_reason) )); } @@ -505,7 +510,11 @@ impl PreSimulationState { if !result.success { return Err(eyre::eyre!( "script failed: {}", - &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + &self + .execution_artifacts + .decoder + .revert_decoder + .decode(&result.returned[..], result.exit_reason) )); } diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index de80b1bf98c47..e2404d60ce2b9 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -275,7 +275,7 @@ impl ScriptRunner { value.unwrap_or(U256::ZERO), None, ); - let (address, RawCallResult { gas_used, logs, traces, .. }) = match res { + let (address, RawCallResult { gas_used, logs, traces, exit_reason, .. }) = match res { Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { let ExecutionErr { raw, reason } = *err; @@ -294,6 +294,7 @@ impl ScriptRunner { traces: traces .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), + exit_reason, address: Some(address), ..Default::default() }) @@ -348,7 +349,9 @@ impl ScriptRunner { } } - let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res; + let RawCallResult { + result, reverted, logs, traces, labels, transactions, exit_reason, .. + } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -365,6 +368,7 @@ impl ScriptRunner { .unwrap_or_default(), labeled_addresses: labels, transactions, + exit_reason, address: None, breakpoints, }) diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 4ee9c3fb53cb6..43b529a8c3e98 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -363,10 +363,9 @@ impl FilledTransactionsState { // Get the native token symbol for the chain using NamedChain let token_symbol = NamedChain::try_from(provider_info.chain) - .unwrap_or_default() + let token_symbol = alloy_chains::Chain::from_id(provider_info.chain) .native_currency_symbol() .unwrap_or("ETH"); - // We don't store it in the transactions, since we want the most updated value. // Right before broadcasting. let per_gas = if let Some(gas_price) = self.args.with_gas_price { From 2d27168115d731f32f9de1097f28be85acd7d8dc Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 07:57:20 +0000 Subject: [PATCH 368/391] Refine construction of Vyper compiler input by cloning only the required compiler settings field instead of the entire verification context. (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp * chore: bump alloy 2.0.1 (#14417) * fix(wallets): add gas limit margin to handle WebAuthn/P256 signing (#14416) fix(wallets): add gas limit margin to handle WebAuthn/P256 signing * fix(evm): sync gas params when updating executor spec (#14420) * fix(evm): sync gas params when updating executor spec * fix: make clippy happy --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add incorrect ERC721 interface lint (#14412) * fix(common): enable `foundry-wallets` browser/tempo feats (#14421) * chore(tests): bump forge-std version (#14422) chore: bump forge-std version used for tests Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil-server): distinguish empty and notification batches (#14405) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(anvil): add debug_traceBlockByHash and debug_traceBlockByNumber (#14391) * feat(anvil): add `debug_traceBlockByHash` and `debug_traceBlockByNumber` RPC endpoints Implement the two missing geth debug_ block tracing endpoints that trace all transactions in a block at once, returning per-tx results. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: fmt --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore(foundryup): remove tempo fork support (#14324) * chore: remove tempo fork support Tempo is now fully upstream in the main Foundry repo, so the separate network handling is no longer needed. * Apply suggestion from @zerosnacks Update Foundryup version * warn when foundryup --network is ignored * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(lint): add missing visit methods to LateLintVisitor (#14276) * fix(lint): add missing visit methods to LateLintVisitor * fix(lint): dispatch deprecated nested late lint hooks * fix(lint): use by-value nested late lint hooks * test(lint): cover late visitor hooks --------- Co-authored-by: figtracer * fix: remove `network: tempo` from Tempo template (#14424) remove network: tempo from template * feat(anvil): RPC methods for tempo's `TipFeeManager` in `anvil_*` namespace (#14414) * feat(anvil): RPC methods for tempo's `TipFeeManager` in`anvil_*` namespace * feat(anvil): add tests + mint TIP20 tokens to admin before adding FeeAMM liquidity --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): separate queued txs in txpool RPC responses (#14425) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` (#14426) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` * chore: clean-ups + tests * fix: after fig's review * fix: skip serialization if `network` is `None` * chore: pin to foundry-wallets release (#14429) pin to foundry-wallets release * fix(config): respect custom Etherscan URL in cast/forge commands (#14319) * fix(config): respect custom Etherscan URL in cast/forge commands Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * fix lint * fix clippy * chore: update Cargo.lock Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 * fix: update Cargo.lock from upstream base Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * feat(common): add MPP WebSocket transport (#14404) * feat(common): add MPP WebSocket transport * docs * use alloy's wsbakcend::from_socket * use main repo * remove patches * bump rustls-webpki * fix: only use `MppWsConnect` when MPP key is available * fix: install default rustls crypto provider * fix: clean imports * fix: MPP known host check * fix: MPP only if known endpoint && key available --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(script): preserve exit reason in failed revert decode (#13985) * fix(script): preserve exit reason in failed revert decode * test(script): cover exit reason output in script failures * fix(script): initialize exit_reason in ScriptResult default --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(anvil): fix flaky test_increase_time_by_zero test (#14430) Co-authored-by: Amp * feat(lint): add incorrect ERC20 interface lint (#14428) * feat(lint): add incorrect ERC20 interface lint * test(lint): cover direct-name IERC20 case * fix(lint): revert IncorrectERC20Interface test to use empty IERC20 base Expanding IERC20 with full function signatures caused solc compilation failures (Error 4822: Overriding function return types differ) because IERC20Incorrect inherits from IERC20 and overrides with incompatible return types. Revert to the empty-base pattern used by IncorrectERC721Interface. Amp-Thread-ID: https://ampcode.com/threads/T-019dbb72-89c7-740d-b6cf-b87a1509d3e3 Co-authored-by: Amp * test: clarify incorrect ERC20 interface fixtures * test: align ERC20 fixture with ERC721 --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(cheatcodes): reject nested debug trace recording (#14423) * fix(cheatcodes): reject nested debug trace recording * test(cheatcodes): restore RecordDebugTrace formatting * chore: bump revm v38 (#14436) * deps: bump tempo to TIP-1016 (rev 2e6e9d1) Patches revm to bluealloy/revm@a1a1824 and op-revm to foundry-rs/op-revm@66388e6 for the InitialAndFloorGas handler change. Patches reth-core to paradigmxyz/reth-core@6b12498 for the Bytecode::new_analyzed unsafe change. Adds gas_limit() and state_gas_used() to AnvilStorageProvider to satisfy the updated PrecompileStorageProvider trait. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: resolve CI failures from patched revm - Replace deprecated OpCode::new_unchecked with new_or_unknown - Add gas_refunded field to PrecompileOutput struct literals - Allow foundry-rs/op-revm and paradigmxyz/reth-core git sources in deny.toml Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: align revm/op-revm/tempo revs with tip1016 branch Updates patch revisions to match tempo/tip1016 HEAD: - revm: a1a1824 → ea8d1f5 - op-revm: 66388e6 → 780b812 - tempo: 2e6e9d1 → 4092dfe Fixes CreateInputs::new reservoir parameter for the new revm. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: pin tempo to tip1016 HEAD (807b57c) Tempo's [patch.crates-io] now includes all revm sub-crates, preventing the invariant workflow's patch propagation from clobbering foundry's patches. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * bump to later commit in https://github.com/tempoxyz/tempo/pull/2684/commits * chore: bump revm 37→38, tempo crates to tip1016 head - revm 37.0.0 → 38.0.0 (crates.io release, no more git patches) - revm-inspectors 0.38.1 → 0.39.0 - alloy-evm 0.32.0 → 0.33.1 - tempo crates rev f873f0e → c095527 (tip1016 head) - foundry-fork-db, op-revm, foundry-rs/optimism bumped via branches - fix OpCode::new_or_unknown → new_unchecked (API change in revm 38) - add missing Evm::cfg_env impl (new trait method in alloy-evm 0.33.1) Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * bump alloy-evm dep to 0.33.2 * chore: bump revm v38 using upstream OP * fix: deny.toml * fix(deps): merge conflict rustls * style: drop cmnt Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: restore cargo-shear --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Federico Gimenez Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: group dependabot updates by ecosystem (#14438) Group minor and patch version updates per ecosystem into single PRs to reduce PR noise. Major version bumps still get individual PRs for careful review. Before: up to 15 PRs/week (5 per ecosystem) After: up to 6 PRs/week (1 grouped + 1 major per ecosystem, worst case) Amp-Thread-ID: https://ampcode.com/threads/T-019dbe78-a376-7248-a549-6ddbe2c960d4 Co-authored-by: George Niculae Co-authored-by: Amp * refactor(evm): remove useless OP EVM wrapper (#14440) Co-authored-by: Copilot * chore(deps): bump the actions-weekly group with 4 updates (#14441) Bumps the actions-weekly group with 4 updates: [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request), [github/codeql-action](https://github.com/github/codeql-action), [taiki-e/install-action](https://github.com/taiki-e/install-action) and [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action). Updates `peter-evans/create-pull-request` from 8.1.0 to 8.1.1 - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/c0f553fe549906ede9cf27b5156039d195d2ece0...5f6978faf089d4d20b00c7766989d076bb2fc7f1) Updates `github/codeql-action` from 4.35.1 to 4.35.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) Updates `taiki-e/install-action` from 2.75.16 to 2.75.17 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/a2352fc6ce487f030a3aa709482d57823eadfb37...58e862542551f667fa44c8a2a4a1d64ad477c96a) Updates `taiki-e/cache-cargo-install-action` from 3.0.5 to 3.0.6 - [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases) - [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/a8b9ecf8e0c0ea09d7481cfc583a5203ecd585b5...f9eed3e4680f27610dc6d8c67be1b88593f7dade) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/install-action dependency-version: 2.75.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/cache-cargo-install-action dependency-version: 3.0.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(tests): update gas report snapshot after gas params sync fix (#14439) The deployment cost for CounterWithFallback changed from 132471 to 132459 after #14420 started properly syncing gas parameters when updating the executor spec via set_spec_id. Amp-Thread-ID: https://ampcode.com/threads/T-019dbea7-a331-7269-80a8-b935a7bdaa49 Co-authored-by: Amp * feat: log broken invariant as soon as it is found (#14433) * feat: log broken invariant as soon as it is found * fix * nest failure counts inside metrics * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0, foundry-fork-db to 0.26.0 (#14443) * chore: bump foundry-compilers to 0.20.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf04-adac-755f-b685-5fa64d792acb Co-authored-by: Amp * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: bump foundry-fork-db to 0.26.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: enable zstd feature for foundry-fork-db Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d --------- Co-authored-by: Amp * chore: remove unmaintained devcontainer (#14449) deprecate unmaintained devcontainer * chore: update security contact email (#14450) update link * feat(lint): add block-timestamp lint (#14431) * feat: add ignored_error_codes_from config option (#13841) * chore(deny): remove deprecated repos from whitelist (#14455) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: MD Islam Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: mk0walsk Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Arsh Co-authored-by: Perico Perica Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Federico Gimenez Co-authored-by: George Niculae Co-authored-by: Copilot Co-authored-by: googleworkspace-bot From 5dbd7b227f598a9f5b3678fc6b9bab46089e03ab Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:04:37 +0700 Subject: [PATCH 369/391] Update crates/anvil/server/src/handler.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/anvil/server/src/handler.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/anvil/server/src/handler.rs b/crates/anvil/server/src/handler.rs index 250c486986240..95659d9eefbf6 100644 --- a/crates/anvil/server/src/handler.rs +++ b/crates/anvil/server/src/handler.rs @@ -49,7 +49,9 @@ pub async fn handle_request( Request::Single(call) => handle_call(call, handler).await.map(Response::Single), Request::Batch(calls) => { if calls.is_empty() { - return Some(Response::error(RpcError::invalid_request())); + return Some(Response::Batch(vec![anvil_rpc::response::RpcResponse::from( + RpcError::invalid_request(), + )])); } future::join_all(calls.into_iter().map(move |call| handle_call(call, handler.clone()))) .map(responses_as_batch) From c2f5430a5119d813cfb11ff80d1dcea501cccf83 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 25 Apr 2026 09:11:43 +0000 Subject: [PATCH 370/391] Create static.yml (#472) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot From 1ffa06d70d0930bfff962b653682b93caabc7a09 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:22:36 +0700 Subject: [PATCH 371/391] Delete .circleci/ci_deploy.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail From 47e2bcc418d7209e5679fea3925c68537956349e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:23:40 +0700 Subject: [PATCH 372/391] Delete .circleci/ci_deploy.yml (#496) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .circleci/ci_deploy.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .circleci/ci_deploy.yml diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml deleted file mode 100644 index 0c8ae5507187d..0000000000000 --- a/.circleci/ci_deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2.1 - -jobs: - say-hello: - docker: - - image: cimg/base:current - - steps: - - checkout - - run: - name: "Say hello" - command: "echo Hello, World!" - -workflows: - say-hello-workflow: - jobs: - - say-hello - -- run: - name: Plan a deploy - command: | - circleci run release plan \ - --environment-name="" \ - --component-name="" \ - --target-version="" -# Your job here doing the actual deployment -- run: - name: Update a deploy to SUCCESS - command: circleci run release update --status=SUCCESS - when: on_success -- run: - name: Update planned deploy to FAILED - command: circleci run release update --status=FAILED - when: on_fail From 5e16eb3fc44328c4a0b5b8ff3e0dec0e37d81d7c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 28 Apr 2026 06:22:07 +0700 Subject: [PATCH 373/391] fix(forge): adjust gas assertion CounterWithFallback (foundry-rs#14465 ) (#498) * chore(deps): bump rui314/setup-mold from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444 (#14442) chore(deps): bump rui314/setup-mold Bumps [rui314/setup-mold](https://github.com/rui314/setup-mold) from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444. - [Commits](https://github.com/rui314/setup-mold/compare/725a8794d15fc7563f59595bd9556495c0564878...9c9c13bf4c3f1adef0cc596abc155580bcb04444) --- updated-dependencies: - dependency-name: rui314/setup-mold dependency-version: 9c9c13bf4c3f1adef0cc596abc155580bcb04444 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update flake.lock (#14458) Co-authored-by: github-actions[bot] * fix(forge): adjust gas assertion `CounterWithFallback` (#14465) * chore: update latest benchmarks (#14467) * ci: split MPP e2e into its own workflow (#14468) * ci: split MPP e2e into its own workflow Move the MPP e2e step from ci-tempo.yml into a standalone ci-mpp.yml workflow so transient HTTP 402 failures from the MPP RPC do not block the Tempo CI workflow. Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename sanity-check job to tempo-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename mpp-e2e job to mpp-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp --------- Co-authored-by: Amp * Improve GH actions (#14473) * fix(benches): add repos + extra args support to prevent blocking errors (#14470) * fix(benches): add repos + extra args support to prevent blocking errors * fix(ci): set `inputs.repos` default to empty * fix: remove `--verbose` flags * fix: exclude `uniswap/v4-core` `TickMathTestTest` --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> --- .github/workflows/benchmarks.yml | 50 ++++++++++++--- .github/workflows/ci-mpp.yml | 55 ++++++++++++++++ .github/workflows/ci-tempo.yml | 17 +---- .github/workflows/ci.yml | 6 +- .github/workflows/crate-checks.yml | 2 +- .github/workflows/docker-publish.yml | 1 + .github/workflows/docs.yml | 5 +- .github/workflows/release.yml | 7 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 2 +- benches/LATEST.md | 79 ++++++++++++----------- benches/src/lib.rs | 96 +++++++++++++++++----------- benches/src/main.rs | 12 +++- crates/forge/tests/cli/cmd.rs | 2 +- flake.lock | 18 +++--- 16 files changed, 226 insertions(+), 130 deletions(-) create mode 100644 .github/workflows/ci-mpp.yml diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 8465874854551..9b7f834c420c7 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -15,15 +15,44 @@ on: type: string default: "stable,nightly" repos: - description: "Comma-separated list of repos to benchmark (e.g., ithacaxyz/account:main,Vectorized/solady)" + description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev][ ] (e.g. vectorized/solady:v0.1.26 --nmc BrokenTest). Leave empty to use the per-benchmark default repo lists." required: false type: string - default: "ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22" + default: "" env: - ITHACAXYZ_ACCOUNT: "ithacaxyz/account:v0.3.2" - VECTORIZED_SOLADY: "Vectorized/solady:v0.1.22" - DEFAULT_REPOS: "ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22" + # Repos to benchmark per step. Each comma-separated entry has the form + # org/repo[:rev][ ] + # where anything after the first whitespace is appended to every benchmark + # command for that repo (use this to skip a broken test contract via e.g. + # `--nmc BrokenTest`, so a single failing test does not fail the whole CI). + TEST_REPOS: >- + ithacaxyz/account:v0.5.7, + vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test', + aave/aave-v4:v0.5.11, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', + sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting + + ISOLATE_TEST_REPOS: >- + ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest, + vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test', + aave/aave-v4:v0.5.11, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', + sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting + + BUILD_REPOS: >- + ithacaxyz/account:v0.5.7, + vectorized/solady:v0.1.26, + aave/aave-v4:v0.5.11, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, + sparkdotfi/spark-psm:v1.0.0 + + COVERAGE_REPOS: >- + ithacaxyz/account:v0.5.7, + aave/aave-v4:v0.5.11, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, + sparkdotfi/spark-psm:v1.0.0 + RUSTC_WRAPPER: "sccache" jobs: @@ -48,7 +77,7 @@ jobs: with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 @@ -78,7 +107,7 @@ jobs: env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} - REPOS: ${{ github.event.inputs.repos || env.DEFAULT_REPOS }} + REPOS: ${{ github.event.inputs.repos || env.TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ @@ -90,7 +119,7 @@ jobs: env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} - REPOS: ${{ github.event.inputs.repos || env.VECTORIZED_SOLADY }} + REPOS: ${{ github.event.inputs.repos || env.ISOLATE_TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ @@ -102,7 +131,7 @@ jobs: env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} - REPOS: ${{ github.event.inputs.repos || env.DEFAULT_REPOS }} + REPOS: ${{ github.event.inputs.repos || env.BUILD_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ @@ -114,10 +143,11 @@ jobs: env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + REPOS: ${{ github.event.inputs.repos || env.COVERAGE_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ - --repos ${{ env.ITHACAXYZ_ACCOUNT }} \ + --repos "$REPOS" \ --benchmarks forge_coverage \ --output-file forge_coverage_bench.md diff --git a/.github/workflows/ci-mpp.yml b/.github/workflows/ci-mpp.yml new file mode 100644 index 0000000000000..00ec35f8f8aba --- /dev/null +++ b/.github/workflows/ci-mpp.yml @@ -0,0 +1,55 @@ +name: CI MPP + +permissions: {} + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" + +jobs: + mpp-check: + runs-on: depot-ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + steps: + # Checkout the repository + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + + # Build and install binaries + - name: Build and install Foundry binaries + run: | + cargo build --profile dev --locked -p forge -p cast -p anvil -p chisel + echo "${{ github.workspace }}/target/debug" >> "$GITHUB_PATH" + + - name: Run MPP e2e test + env: + TEMPO_KEYS_TOML_B64: ${{ secrets.TEMPO_KEYS_TOML_B64 }} + MPP_API_KEY: ${{ secrets.MPP_API_KEY }} + MPP_DEPOSIT: "1000000" + run: | + if [ -z "${TEMPO_KEYS_TOML_B64:-}" ]; then + echo "::warning::TEMPO_KEYS_TOML_B64 secret not set, skipping MPP e2e" + exit 0 + fi + mkdir -p ~/.tempo/wallet + echo "$TEMPO_KEYS_TOML_B64" | tr -d '[:space:]' | base64 -d > ~/.tempo/wallet/keys.toml + ./.github/scripts/tempo-mpp.sh "$(which cast | xargs dirname)" diff --git a/.github/workflows/ci-tempo.yml b/.github/workflows/ci-tempo.yml index 942f595a34977..b4bc98391e72d 100644 --- a/.github/workflows/ci-tempo.yml +++ b/.github/workflows/ci-tempo.yml @@ -36,7 +36,7 @@ env: RUSTC_WRAPPER: "sccache" jobs: - sanity-check: + tempo-check: runs-on: depot-ubuntu-latest timeout-minutes: 60 permissions: @@ -59,21 +59,6 @@ jobs: cargo build --profile dev --locked -p forge -p cast -p anvil -p chisel echo "${{ github.workspace }}/target/debug" >> "$GITHUB_PATH" - - name: Run MPP e2e test - if: github.event_name == 'push' || github.event_name == 'pull_request' - env: - TEMPO_KEYS_TOML_B64: ${{ secrets.TEMPO_KEYS_TOML_B64 }} - MPP_API_KEY: ${{ secrets.MPP_API_KEY }} - MPP_DEPOSIT: "1000000" - run: | - if [ -z "${TEMPO_KEYS_TOML_B64:-}" ]; then - echo "::warning::TEMPO_KEYS_TOML_B64 secret not set, skipping MPP e2e" - exit 0 - fi - mkdir -p ~/.tempo/wallet - echo "$TEMPO_KEYS_TOML_B64" | tr -d '[:space:]' | base64 -d > ~/.tempo/wallet/keys.toml - ./.github/scripts/tempo-mpp.sh "$(which cast | xargs dirname)" - # TODO(upstream): re-enable when flaky devnet faucet is fixed # - name: Run Tempo check on devnet # if: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d5564f94a7e2..9eb90a76cdbfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo test --workspace --doc --locked @@ -89,7 +89,7 @@ jobs: with: toolchain: nightly components: clippy - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo clippy --workspace --all-targets --all-features --locked @@ -123,7 +123,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: forge fmt diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index ce2988f3e0260..eb865bddc10e3 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -28,7 +28,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 with: tool: cargo-hack diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3372e816328b7..5b7cf1631fea8 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -108,3 +108,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64 push: true + no-cache: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 288d9d127592f..45d00708394f5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,6 @@ concurrency: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full - RUSTC_WRAPPER: "sccache" jobs: docs: @@ -30,9 +29,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: nightly - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - name: Build documentation run: cargo doc --workspace --all-features --no-deps --document-private-items --locked env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 351d8a334362d..9cb1cf51dda24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,12 +148,7 @@ jobs: with: toolchain: stable targets: ${{ matrix.target }} - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - if: ${{ contains(matrix.runner, 'depot') }} - - run: printf 'RUSTC_WRAPPER=sccache\n' >> "$GITHUB_ENV" - if: ${{ contains(matrix.runner, 'depot') }} + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - name: Apple M1 setup if: matrix.target == 'aarch64-apple-darwin' diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index 7e3fa21d64b9a..d6244f826887e 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -32,7 +32,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 with: tool: nextest diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 4446cdbba8ce6..141e3a049a73b 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -36,7 +36,7 @@ jobs: - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 with: tool: nextest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80a32068a21e3..eaa46c8e79f7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,7 +72,7 @@ jobs: with: toolchain: stable target: ${{ matrix.target }} - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 with: tool: nextest diff --git a/benches/LATEST.md b/benches/LATEST.md index 7ea1049a2ac41..238a691229389 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,6 +1,6 @@ # Foundry Benchmark Results -**Date**: 2025-10-02 12:14:23 +**Date**: 2026-04-24 23:10:24 ## Repositories Tested @@ -8,66 +8,67 @@ 2. [Vectorized/solady](https://github.com/Vectorized/solady) 3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) 4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +5. [aave/aave-v4](https://github.com/aave/aave-v4) ## Foundry Versions -- **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) -- **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **nightly**: forge Version: 1.6.0-nightly (a249f5c 2026-04-24) ## Forge Test -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 3.17 s | 2.94 s | -| solady | 2.28 s | 2.10 s | -| Uniswap-v4-core | 7.27 s | 6.13 s | -| sparkdotfi-spark-psm | 43.04 s | 44.08 s | +| Repository | v1.5.1 | nightly | +| -------------------- | -------- | -------- | +| vectorized-solady | 1.46 s | 1.38 s | +| aave-aave-v4 | 4m 14.2s | 3m 29.1s | ## Forge Fuzz Test -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------ | ---------- | -| ithacaxyz-account | 3.18 s | 3.02 s | -| solady | 2.39 s | 2.24 s | -| Uniswap-v4-core | 6.84 s | 6.20 s | -| sparkdotfi-spark-psm | 3.07 s | 2.72 s | +| Repository | v1.5.1 | nightly | +| -------------------- | --------- | -------- | +| ithacaxyz-account | 2.81 s | 1.59 s | +| vectorized-solady | 1.40 s | 1.34 s | +| Uniswap-v4-core | 3.01 s | 2.87 s | +| sparkdotfi-spark-psm | 2.04 s | 1.87 s | +| aave-aave-v4 | 3m 46.0s | 3m 17.3s | ## Forge Test (Isolated) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| solady | 2.26 s | 2.41 s | -| Uniswap-v4-core | 7.22 s | 7.71 s | -| sparkdotfi-spark-psm | 45.53 s | 50.49 s | +| Repository | v1.5.1 | nightly | +| -------------------- | -------- | -------- | +| Uniswap-v4-core | 3.50 s | 3.48 s. | +| aave-aave-v4 | 4m 14.0s | 3m 53.4s | ## Forge Build (No Cache) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 9.16 s | 9.08 s | -| solady | 14.62 s | 14.69 s | -| Uniswap-v4-core | 2m 3.8s | 2m 5.3s | -| sparkdotfi-spark-psm | 13.17 s | 13.14 s | +| Repository | v1.5.1 | nightly | +| -------------------- | -------- | -------- | +| ithacaxyz-account | 26.06 s | 26.61 s | +| vectorized-solady | 14.20 s | 14.26 s | +| Uniswap-v4-core | 2m 1.3s | 2m 5.0s | +| sparkdotfi-spark-psm | 15.16 s | 15.30 s | +| aave-aave-v4 | 3m 37.0s | 3m 35.1s | ## Forge Build (With Cache) -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | ------- | ---------- | -| ithacaxyz-account | 0.156 s | 0.113 s | -| solady | 0.089 s | 0.094 s | -| Uniswap-v4-core | 0.133 s | 0.127 s | -| sparkdotfi-spark-psm | 0.173 s | 0.131 s | +| Repository | v1.5.1 | nightly | +| -------------------- | ------- | ------- | +| ithacaxyz-account | 0.167 s | 0.201 s | +| vectorized-solady | 0.099 s | 0.098 s | +| Uniswap-v4-core | 0.139 s | 0.140 s | +| sparkdotfi-spark-psm | 0.168 s | 0.173 s | +| aave-aave-v4 | 0.370 s | 0.357 s | ## Forge Coverage -| Repository | v1.3.6 | v1.4.0-rc1 | -| -------------------- | -------- | ---------- | -| ithacaxyz-account | 14.91 s | 13.34 s | -| Uniswap-v4-core | 1m 34.8s | 1m 30.3s | -| sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | +| Repository | v1.5.1 | nightly | +| -------------------- | --------- | ---------- | +| Uniswap-v4-core | 1m 13.9s | 1m 10.3s | +| sparkdotfi-spark-psm | 2m 54.7s | 2m 50.0s | +| aave-aave-v4 | 11m 20.8s | 10m 58.7s | ## System Information - **OS**: macos -- **CPU**: 8 -- **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) +- **CPU**: 12 +- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) \ No newline at end of file diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ab3bec614e3cd..7ed8807cbf0f5 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -24,46 +24,53 @@ pub struct RepoConfig { pub org: String, pub repo: String, pub rev: String, + /// Optional extra arguments appended to every benchmark command for this + /// repo (e.g. `--nmc BrokenTest` to skip a broken test contract). + pub extra_args: Option, } impl FromStr for RepoConfig { type Err = eyre::Error; + /// Parse a repo spec of the form `org/repo[:rev][ ]`. + /// + /// Anything after the first whitespace is treated as extra arguments + /// appended to every benchmark command for this repo. fn from_str(spec: &str) -> Result { - // Split by ':' first to separate repo path from optional rev - let parts: Vec<&str> = spec.splitn(2, ':').collect(); - let repo_path = parts[0]; - let custom_rev = parts.get(1).copied(); - - // Now split the repo path by '/' - let path_parts: Vec<&str> = repo_path.split('/').collect(); - if path_parts.len() != 2 { - eyre::bail!("Invalid repo format '{}'. Expected 'org/repo' or 'org/repo:rev'", spec); - } - - let org = path_parts[0]; - let repo = path_parts[1]; + let spec = spec.trim(); + // Anything after the first whitespace is per-repo extra args. + let (head, extra_args) = match spec.split_once(char::is_whitespace) { + Some((head, rest)) => (head, Some(rest.trim().to_string())), + None => (spec, None), + }; - // Try to find this repo in BENCHMARK_REPOS to get the full config - let existing_config = BENCHMARK_REPOS.iter().find(|r| r.org == org && r.repo == repo); + let (repo_path, custom_rev) = match head.split_once(':') { + Some((path, rev)) => (path, Some(rev)), + None => (head, None), + }; - let config = if let Some(existing) = existing_config { - // Use existing config but allow custom rev to override - let mut config = existing.clone(); - if let Some(rev) = custom_rev { - config.rev = rev.to_string(); - } - config - } else { - // Create new config with custom rev or default - // Name should follow the format: org-repo (with hyphen) - Self { + let (org, repo) = repo_path.split_once('/').ok_or_else(|| { + eyre::eyre!("Invalid repo format '{spec}'. Expected 'org/repo' or 'org/repo:rev'") + })?; + + // Inherit defaults from BENCHMARK_REPOS when available, otherwise build + // a fresh config. Custom rev / extra args always override. + let mut config = BENCHMARK_REPOS + .iter() + .find(|r| r.org == org && r.repo == repo) + .cloned() + .unwrap_or_else(|| Self { name: format!("{org}-{repo}"), org: org.to_string(), repo: repo.to_string(), - rev: custom_rev.unwrap_or("main").to_string(), - } - }; + rev: "main".to_string(), + extra_args: None, + }); + + if let Some(rev) = custom_rev { + config.rev = rev.to_string(); + } + config.extra_args = extra_args; let _ = sh_println!("Parsed repo spec '{spec}' -> {config:?}"); Ok(config) @@ -78,12 +85,14 @@ pub fn default_benchmark_repos() -> Vec { org: "ithacaxyz".to_string(), repo: "account".to_string(), rev: "main".to_string(), + extra_args: None, }, RepoConfig { name: "solady".to_string(), org: "Vectorized".to_string(), repo: "solady".to_string(), rev: "main".to_string(), + extra_args: None, }, ] } @@ -113,6 +122,8 @@ pub struct BenchmarkProject { pub name: String, pub temp_project: TempProject, pub root_path: PathBuf, + /// Optional extra arguments appended to every benchmark command. + pub extra_args: Option, } impl BenchmarkProject { @@ -169,7 +180,20 @@ impl BenchmarkProject { Self::install_npm_dependencies(&root_path)?; sh_println!(" ✅ Project {} setup complete at {}", config.name, root); - Ok(Self { name: config.name.clone(), root_path, temp_project }) + Ok(Self { + name: config.name.clone(), + root_path, + temp_project, + extra_args: config.extra_args.clone(), + }) + } + + /// Append `self.extra_args` to a benchmark shell command, if any. + fn cmd(&self, base: &str) -> String { + match self.extra_args.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + Some(extra) => format!("{base} {extra}"), + None => base.to_string(), + } } /// Install npm dependencies if package.json exists @@ -296,7 +320,7 @@ impl BenchmarkProject { self.hyperfine( "forge_test", version, - "forge test", + &self.cmd("forge test"), runs, Some("forge build"), None, @@ -315,7 +339,7 @@ impl BenchmarkProject { self.hyperfine( "forge_build_with_cache", version, - "FOUNDRY_LINT_LINT_ON_BUILD=false forge build", + &self.cmd("FOUNDRY_LINT_LINT_ON_BUILD=false forge build"), runs, None, Some("forge build"), @@ -335,7 +359,7 @@ impl BenchmarkProject { self.hyperfine( "forge_build_no_cache", version, - "FOUNDRY_LINT_LINT_ON_BUILD=false forge build", + &self.cmd("FOUNDRY_LINT_LINT_ON_BUILD=false forge build"), runs, Some("forge clean"), None, @@ -355,7 +379,7 @@ impl BenchmarkProject { self.hyperfine( "forge_fuzz_test", version, - r#"forge test --match-test "test[^(]*\([^)]+\)""#, + &self.cmd(r#"forge test --match-test "test[^(]*\([^)]+\)""#), runs, Some("forge build"), None, @@ -376,7 +400,7 @@ impl BenchmarkProject { self.hyperfine( "forge_coverage", version, - "forge coverage --ir-minimum", + &self.cmd("forge coverage --ir-minimum"), runs, None, None, @@ -396,7 +420,7 @@ impl BenchmarkProject { self.hyperfine( "forge_isolate_test", version, - "forge test --isolate", + &self.cmd("forge test --isolate"), runs, Some("forge build"), None, diff --git a/benches/src/main.rs b/benches/src/main.rs index f3361bf30b6e6..60e815cecb0ec 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -48,8 +48,16 @@ struct Cli { #[clap(long, value_delimiter = ',')] benchmarks: Option>, - /// Run only on specific repositories (comma-separated in org/repo[:rev] format: - /// ithacaxyz/account,Vectorized/solady:main,foundry-rs/foundry:v1.0.0) + /// Comma-separated list of repositories to benchmark. + /// + /// Each entry has the form `org/repo[:rev][ ]`. Anything + /// after the first whitespace is appended to every benchmark command for + /// that repo (handy to skip a broken test contract via e.g. + /// `--nmc BrokenTest`). + /// + /// Examples: + /// `ithacaxyz/account:v0.5.7` + /// `vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest'` #[clap(long, value_delimiter = ',')] repos: Option>, } diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 13bf5cfdbacce..d3f1503faf569 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -3016,7 +3016,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", "deployment": { - "gas": 132471, + "gas": 132459, "size": 396 }, "functions": { diff --git a/flake.lock b/flake.lock index 4fa81efa24f11..27f426f491da6 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1776497206, - "narHash": "sha256-Em+RSdFnwyyKPGUBFtQYtVjm+1UvIc9gOR91Y22zlzg=", + "lastModified": 1777102577, + "narHash": "sha256-ycoy9svZOQgyInu/lwO7IEQtlP5liqYhEcF9m9hPRbM=", "owner": "nix-community", "repo": "fenix", - "rev": "df2295365fb081fe0745449762a771290782c22d", + "rev": "f37403486c59376cd285f9685a8ef8ff25c09a3c", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776329215, - "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", + "lastModified": 1776949667, + "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b86751bc4085f48661017fa226dee99fab6c651b", + "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1776441750, - "narHash": "sha256-1rVfG+mj8R4ze+lSYCa4iAv7FzrB03Cprtxmd1MfZak=", + "lastModified": 1776800521, + "narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "251df518d73abb5c5d573c4d5d266a3edae9ca5a", + "rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e", "type": "github" }, "original": { From 28430c4ab494cb40fb4f31bb686869d6b6c06a2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 12:44:42 +0700 Subject: [PATCH 374/391] chore(deps): bump the cargo-weekly group with 5 updates (#507) Bumps the cargo-weekly group with 5 updates: | Package | From | To | | --- | --- | --- | | [mimalloc](https://github.com/purpleprotocol/mimalloc_rust) | `0.1.48` | `0.1.50` | | [rustls](https://github.com/rustls/rustls) | `0.23.38` | `0.23.39` | | [jiff](https://github.com/BurntSushi/jiff) | `0.2.23` | `0.2.24` | | [idna_adapter](https://github.com/hsivonen/idna_adapter) | `1.1.0` | `1.2.1` | | [interprocess](https://github.com/kotauskas/interprocess) | `2.4.0` | `2.4.2` | Updates `mimalloc` from 0.1.48 to 0.1.50 - [Release notes](https://github.com/purpleprotocol/mimalloc_rust/releases) - [Commits](https://github.com/purpleprotocol/mimalloc_rust/compare/v0.1.48...v0.1.50) Updates `rustls` from 0.23.38 to 0.23.39 - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.38...v/0.23.39) Updates `jiff` from 0.2.23 to 0.2.24 - [Release notes](https://github.com/BurntSushi/jiff/releases) - [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.23...jiff-static-0.2.24) Updates `idna_adapter` from 1.1.0 to 1.2.1 - [Commits](https://github.com/hsivonen/idna_adapter/compare/v1.1.0...v1.2.1) Updates `interprocess` from 2.4.0 to 2.4.2 - [Release notes](https://github.com/kotauskas/interprocess/releases) - [Commits](https://github.com/kotauskas/interprocess/compare/2.4.0...2.4.2) --- updated-dependencies: - dependency-name: mimalloc dependency-version: 0.1.50 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: rustls dependency-version: 0.23.39 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: jiff dependency-version: 0.2.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: idna_adapter dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-weekly - dependency-name: interprocess dependency-version: 2.4.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d113b82b696ed..253ed6afdf6fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -537,7 +537,7 @@ snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } # Use unicode-rs which has a smaller binary size than the default ICU4X as the IDNA backend, used # by the `url` crate. # See the `idna_adapter` README.md for more details: https://docs.rs/crate/idna_adapter/latest -idna_adapter = "=1.1.0" +idna_adapter = "=1.2.1" [patch.crates-io] # https://github.com/rust-cli/rexpect/pull/106 From 6c44631ab8e2dfa35ccf2b7a26f1eb70a038eeaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 15:47:16 +0700 Subject: [PATCH 375/391] chore(deps): bump strum from 0.27.2 to 0.28.0 (#509) Bumps [strum](https://github.com/Peternator7/strum) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/Peternator7/strum/releases) - [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md) - [Commits](https://github.com/Peternator7/strum/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: strum dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> From b8e3a0c2d38faa684c0a8b9cce4dd5504eb28149 Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Sat, 2 May 2026 15:56:21 +0700 Subject: [PATCH 376/391] gas-snapshot --- counter/.gas-snapshot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/counter/.gas-snapshot b/counter/.gas-snapshot index ef525c09384e6..797ceebb2f595 100644 --- a/counter/.gas-snapshot +++ b/counter/.gas-snapshot @@ -1,2 +1,2 @@ -CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 30177, ~: 32354) +CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 30410, ~: 32354) CounterTest:test_Increment() (gas: 31851) \ No newline at end of file From 432d718616712d3586c067e773eeef120e222dd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 16:25:58 +0700 Subject: [PATCH 377/391] chore(deps): bump similar-asserts from 1.7.0 to 2.0.0 (#508) Bumps [similar-asserts](https://github.com/mitsuhiko/similar-asserts) from 1.7.0 to 2.0.0. - [Changelog](https://github.com/mitsuhiko/similar-asserts/blob/main/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/similar-asserts/compare/1.7.0...2.0.0) --- updated-dependencies: - dependency-name: similar-asserts dependency-version: 2.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 +++++++++++++------- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65a696a8be26d..7b52c5f580fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4625,7 +4625,7 @@ dependencies = [ "semver 1.0.28", "serde", "serde_json", - "similar", + "similar 2.7.0", "similar-asserts", "solar-compiler", "soldeer-commands", @@ -4678,7 +4678,7 @@ dependencies = [ "foundry-config", "foundry-test-utils", "itertools 0.14.0", - "similar", + "similar 2.7.0", "snapbox", "solar-compiler", "toml", @@ -10708,6 +10708,12 @@ name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "similar" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" dependencies = [ "bstr", "unicode-segmentation", @@ -10715,12 +10721,12 @@ dependencies = [ [[package]] name = "similar-asserts" -version = "1.7.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +checksum = "997e6ca38e97437973fc9f7f50a50d1274cacd874341a4960fea90067291038c" dependencies = [ - "console 0.15.11", - "similar", + "console 0.16.3", + "similar 3.1.0", ] [[package]] @@ -10790,7 +10796,7 @@ dependencies = [ "regex", "serde", "serde_json", - "similar", + "similar 2.7.0", "snapbox-macros", ] diff --git a/Cargo.toml b/Cargo.toml index c93dc44c47884..469d5bea793e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -481,7 +481,7 @@ rustls = "0.23" semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } -similar-asserts = "1.7" +similar-asserts = "2.0" soldeer-commands = "=0.10.0" soldeer-core = { version = "=0.10.1", features = ["serde"] } strum = "0.28" From 5a4d2893e23eb711512ca89ccef6e978bd7d5a6b Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 01:00:41 +0700 Subject: [PATCH 378/391] ci: sign release archives, docker images, and publish SBOMs (#520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * anvil: unify Tempo nonce markers across send RPCs (#14536) Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz * fix(forge): `flaky_gas_report_fallback_with_calldata` deployment cost (#14545) * chore(lint): add missing lints to README (#14551) * chore(bench): update `benchmark.sh` (#14548) Co-authored-by: Matthias Seitz * chore(clippy): fix for_kv_map and useless_borrows_in_formatting (#14554) * chore(clippy): fix for_kv_map and useless_borrows_in_formatting Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp * chore(clippy): drop redundant borrows in cheatcodes assert formatters Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp --------- Co-authored-by: Amp * fix(ci): use `PATH_USD` fallback fee token in Mail templates (#14546) * chore(deps): bump the actions-weekly group with 3 updates (#14497) * refactor(chisel): migrate to solar (#14532) * feat(lint): add too-many-digits lint (#14549) * feat: feature-gate optimism deps in common-fmt, common, cast (#14539) * feat(forge): support per-test network selection via inline config (#14530) * feat(cli): `--tempo.expires` retry-safe mode (TIP-1009 expiring nonces) (#14521) * fix(forge): `per_test_network_routing` match undeterministic order (#14557) output * chore(ci): run tempo mainnet and testnet checks before devnet (#14556) * Update flake.lock (#14553) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/f374034' (2026-04-25) → 'github:nix-community/fenix/74c1591' (2026-05-02) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/8954b66' (2026-04-21) → 'github:rust-lang/rust-analyzer/64cdaeb' (2026-05-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/01fbdee' (2026-04-23) → 'github:NixOS/nixpkgs/c6d6588' (2026-05-01) Co-authored-by: github-actions[bot] * chore(bench): update benchmark results (#14552) * fix(forge): ignore ETH_RPC_URL for test forking (#14555) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(cast): add Tempo keychain policy commands (#14531) * feat(cast): add tempo keychain policy commands * fix(cast): address keychain policy review * fix(cli): fix jsonwebtoken panic (#14562) `cast` panicked with this message coming from jsonwebtoken: ``` Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. See the documentation of the CryptoProvider type for more information. ``` This seemingly was introduced with the bump of jsonwebtoken to 10. Now it requires you to pick one backend used by default controlled by the compile time cargo features or call `CryptoProvider::install_default()` at the beginning. I realized that probably it would be better to just select the feature and I picked `aws_lc_rs` as it seems to be increasingly a default and we already are using the C toolchain. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(cli): tidy ETH_RPC_URL handling and add forge regression test (#14559) Follow-up to #14555: - Drop the redundant flashbots branch in RpcOpts::dict; self.url(None) already returns FLASHBOTS_URL when --flashbots is set, so the subsequent overwrite was dead code. - Inline the resolve_rpc_url helper back into RpcCommonOpts::url; it was only called from one place and added unneeded surface area. - Restore the doc comment on RpcCommonOpts and document why ETH_RPC_URL is intentionally not a clap env on the shared field (so EvmArgs cannot inherit it). - Add an integration test that runs forge config with ETH_RPC_URL set in the environment and asserts that eth_rpc_url stays None, directly exercising the regression scenario from #14538. Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: zerosnacks Co-authored-by: Amp * feat(cast): open Tempo wallet fund flow for MPP failures (#14505) * feat(cast): open Tempo wallet fund flow for MPP failures * ci(tempo): skip network checks without rpc secrets * Revert "ci(tempo): skip network checks without rpc secrets" This reverts commit f8dd70163f850b854888fd1c962174e1663284f4. * fix(common): address mpp funding review --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * ci: sign release archives, docker images, and publish SBOMs (#14563) - release.yml: emit per-archive sha256 + SPDX SBOM (Syft), cosign keyless sign-blob of the archive, and use actions/attest@v4 for both build provenance and SBOM attestations. Upload all artifacts to the draft release. - docker-publish.yml: enable BuildKit SBOM, capture the build digest, cosign keyless sign each pushed tag, and publish a Sigstore-signed SLSA provenance attestation via actions/attest with push-to-registry. - SECURITY.md: document how external users verify archives and the docker image (gh attestation, cosign, plain sha256, buildx imagetools). - README.md: link to the new verification section. * perf(common): short-circuit `find_by_name_or_identifier` instead of `collect` (#14514) * feat(foundryup): retry GitHub API fetches on transient errors (#14566) GitHub api.github.com occasionally returns transient 403s on certain VMs (per-IP rate limiting / WAF hiccups), causing foundryup to fail to resolve the latest stable / nightly release tag, e.g.: foundryup: fetching latest nightly releases from foundry-rs/foundry... Error: curl: (56) The requested URL returned error: 403 foundryup: failed to fetch releases from GitHub API Add curl/wget retry logic to the `fetch` helper (used exclusively for GitHub API releases endpoints): - curl: --retry 5 --retry-delay 2 --retry-max-time 60, plus --retry-all-errors when supported (curl 7.71+, feature-detected so older curl does not hard-fail). --retry-all-errors is required to retry HTTP 403, which is not in curl's default retryable set. - wget fallback: --tries=5 --waitretry=2 --retry-on-http-error=403,408,429,5xx. `fetch` now buffers to a temp file before emitting to stdout, since curl's --retry-all-errors is unsafe with piped consumers (mid-stream retries can duplicate bytes). Existing callers pipe into awk/grep. Tunable via FOUNDRYUP_MAX_RETRIES (default 5). `download` (binary tarballs, attestations, manpages) is intentionally left unchanged — those rarely fail and changing them affects the attestation existence check semantics. Bumps installer version 1.8.1 -> 1.8.2. Amp-Thread-ID: https://ampcode.com/threads/T-019df2f5-9b97-717a-b959-cf7cbc7ca3bb Co-authored-by: Amp * feat(lint): project-wide passes + pragma-inconsistent (#14543) * feat(lint): project-wide passes + pragma-inconsistent * rm hashset, msg * test(lint): exhaustive pragma-inconsistent coverage + clearer testdata names (#14561) * test(lint): exhaustive coverage for pragma-inconsistent Follow-up to #14543 expanding test coverage for the cross-file `pragma-inconsistent` lint across the syntax variants users encounter in real Solidity projects. Multi-file scenarios (added as `forgetest!` cases in `crates/forge/tests/cli/lint.rs`, since they cannot be expressed in a single `.sol` testdata file): - Negative (must NOT warn): - all files use the same exact pragma (`0.8.20`) - all files use the same caret pragma (`^0.8.20`) - single file in the project - Positive (must warn): - duplicates among a conflict -- two identical files plus one different pragma still emits three warnings - Mixed: - file without an explicit pragma uses the test-utils default (`add_raw_source` is used to bypass the auto-injected pragma) Source bodies are pulled out into module-level `const` raw strings so rustfmt does not collapse the inline `\n`-escaped strings into wide horizontal blobs. Single-file scenarios (added as `.sol` files under `crates/lint/testdata/` in the existing `//~NOTE:` annotation style): - `PragmaInconsistentCaretVsTilde.sol`: `^0.8.20` vs `~0.8.20` - `PragmaInconsistentRangeVsExact.sol`: `>=0.8.0 <0.9.0` vs `0.8.20` -- range satisfies exact but lint is intentionally string-based, matching SLITHER-W1078 - `PragmaInconsistentOrVsExact.sol`: `0.8.20 || 0.8.21` vs `0.8.20` - `PragmaInconsistentThreeDistinct.sol`: `>=0.8.0`, `^0.8.0`, `~0.8.0` -- verifies the `others` list contains every other variant * test(lint): rename pragma-inconsistent testdata to describe the case under test The two testdata files added in #14543 were named `PragmaInconsistent.sol` and `PragmaInconsistent2.sol`, which made them look like duplicates. They actually exercise distinct edge cases of the same string-based detection: - `PragmaInconsistentCaretAboveExact.sol` (was `PragmaInconsistent.sol`): caret range whose lower bound is strictly below the exact version (`^0.8.0` + `0.8.18`). - `PragmaInconsistentCaretMatchesExact.sol` (was `PragmaInconsistent2.sol`): caret range whose lower bound equals the exact version (`^0.8.20` + `0.8.20`) -- the looks-the-same-but-still-distinct case that guards SLITHER-W1078 parity (no semver intersection). Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: Amp --------- Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * refactor(script): reuse shared Tempo CLI opts (#14558) * deps: bump tempo to 6bf9903 (T6 hardfork) + fix alloy-evm 0.34 compat (#14567) * deps: bump tempo to 6bf9903 (T6 hardfork) Bumps tempo crates to 6bf9903d, adding the T6 hardfork variant to TempoHardfork. Without this, cast's tempo_forkSchedule lookup parses the chain's reported active fork ("T6") into TempoHardfork::FromStr, fails because T6 was unknown to the enum, and silently returns is_hardfork_active(T3) = false. That made 'cast keychain auth' fall back to the legacy authorizeKey selector and revert with LegacyAuthorizeKeySelectorChanged on any T6 chain. Also bumps alloy-evm to 0.34 and the optimism git pin to develop (e3b59e7) so alloy-op-evm picks up an EvmFactory impl built against alloy-evm 0.34. Removes the now-unused paradigmxyz/reth-core [patch] entries. No source changes; lockfile churn is transitive only. * fix: adapt AnvilBlockExecutor to alloy-evm 0.34.0 breaking changes - Add Send + 'static bounds to TxResult impl for AnvilTxResult - Change commit_transaction return type from Result to GasOutput - Remove .expect() on commit_transaction call site Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 * style: rustfmt commit_transaction signature Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 --------- Co-authored-by: Centaur AI * docs: add forge lint rule docs (#14571) * feat(forge): add fuzz run selection (#14522) * feat(forge): add fuzz run selection * fix(fuzz): make metadata builder const * test(fuzz): cover generated seed replay * fix(forge): persist fuzz worker for run replay * fix(evm): satisfy clippy in fuzz replay * fix(fuzz): reuse fuzz run metadata * forge(lint/docs): validate deployed forge lint docs (#14573) test: validate deployed forge lint docs * feat: gate foundry-primitives behind optimism feature (#14572) * fix(ci): increase permissions for the enhanced attestation writing (#14584) * increase permissions for artifact writing * apply write permissions to release-docker * feat(hardforks, networks): gate optimism behind cargo feature (#14581) * fix(forge): encode Tempo creates as AA calls (#14585) * feat(anvil): gate optimism behind cargo feature (#14577) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): introduce `vaddr` cmd for TIP-1022 (#14508) * feat(cast): introduce `vaddr` cmd for tip-1022 * fix: doc * chore: touch-ups * add tests * chore: move tests to tempo ci * feat: add vaddr watch test * feat: count 0 hadling, add `no_register` flag * fix: remove sweep subcommand * fix: make clippy happy * feat(bench): nightly regression tracking workflow (#14586) * fix(cli): fix release version strings for immutable tags, bump to 1.7.1 (#14496) * Fix release version metadata for immutable tags Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Derive nightly release suffix from commit SHA Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * bump to v1.7.1 * avoid appending whole sha hash, not necessary, handle version cmp correctly. after v1.7.1 release we need to bump to v1.7.2 for nightlies following it to compare correctly * Make foundryVersionCmp tolerate new version format and add tests - Strip both pre-release ('-nightly', '-dev') and build metadata ('+..') from SEMVER_VERSION before comparison so the cheatcode keeps working for tagged releases (which have no '-' separator). - Extract strip_semver_metadata helper and add Rust unit tests covering all SEMVER_VERSION shapes, version_cmp ordering, and parse_version rejection of pre-release/build/garbage input. - Extend the Solidity test suite for vm.getFoundryVersion()/foundryVersionCmp/foundryVersionAtLeast: validate MAJOR.MINOR.PATCH parseability, build profile value, cmp/atLeast invariant, and error paths for invalid user-supplied versions. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * fix(test): drop view from solidity tests using assert helpers and fix fmt - assertTrue/assertEq aren't view, so testGetFoundryVersionBuildProfile and testFoundryVersionCmpAndAtLeastAreConsistent can't be view either. - Collapse the buildType assertion onto one line to satisfy forge fmt. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * test(version): assert build profile is non-empty instead of debug|release The dist profile (used for distributed release binaries) is also valid; just require non-empty so any future profile works. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * Normalize nightly- to nightly in release_version Ensures tarball and Docker nightly artifacts produce the same version string. The commit identifier is already included in the SemVer build metadata (after `+`), so collapsing `nightly-` to `nightly` avoids duplicating the SHA in the pre-release tag. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019df79e-d4c9-707c-85eb-2efbf59160b3 --------- Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` (#14007) * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` * test(evm): cover `ForkDbStateSnapshot::storage_ref` snapshot lookup * fix(cast): consistent `--json` output for `keychain` subcommands (#14590) - `keychain rl`: wrap remaining limit in `{"remaining":"..."}` object instead of emitting a bare JSON string - `keychain policy add-call`: emit `{"status":"already_present","target":"..."}` when the rule already exists, instead of plain text - `send_keychain_tx`: wrap sponsor hash in `{"sponsor_hash":"0x..."}` object when --tempo.print-sponsor-hash is used with --json Add CLI tests covering the rl and sponsor-hash JSON output shapes. * feat(tempo): add sponsored transaction plumbing (#14560) * feat(tempo): add sponsored transaction plumbing * addressing mablr comments * fix tempo sponsor signer future layout * preserve json output for tempo sponsor preview * fix(cast): `--json` output support for `vaddr` (#14591) * feat(tempo): add named nonce lanes (#14527) * fix(cheatcodes): transfer value for payable mock calls (#14547) * test: updated tests * fix: execute value transfer * test: improve * imp: review item * test: vm.prank test * imp: moved mocked-call handling after prank application --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add inline-assembly lint (#14575) * feat(lint): add inline-assembly lint * lint(inline-assembly): also recognize `/// @solidity memory-safe-assembly` NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-019df4b6-1b76-734c-9a9b-29db9fb7d461 Co-authored-by: Amp --------- Co-authored-by: Amp * refactor(script): remove `ScriptConfig::{fee_token,expires_at}` in favour of `TempoOpts` (#14594) * feat(evm-core): gate optimism behind cargo feature (#14593) * fix(cli): resolve Tempo expires once (#14595) fix(cli): resolve tempo expires once * feat(cli): gate optimism behind cargo feature (#14596) * fix(anvil): classify EVM halts as transaction rejections (#14592) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat: drop optimism deps under no-default-features (#14600) * fix(forge): `--fuzz-seed` parameter is not effective in `forge coverage` (#14610) fix --fuzz-seed not effective in forge coverage * fix(foundryup): mirror tag resolution for install & use (#14611) * fix(foundryup): mirror tag resolution for install & use * fix(foundryup): mirror semver version normalization in `use` `install` auto-prepends `v` to bare semver versions (e.g. `1.7.0` -> `v1.7.0`) so the on-disk directory is always `v`-prefixed. `use` was doing a literal lookup, so `foundryup -u 1.7.0` failed even though `foundryup -i 1.7.0` had succeeded. Broaden the channel `case` in `use()` to also match bare semver inputs (`MAJOR.MINOR.PATCH[-prerelease]`) so they go through the same `resolve_version_and_tag` normalizer. The pattern is intentionally tighter than `install`'s `[[:digit:]]*` so locally-built versions whose names happen to start with a digit are still looked up literally. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp * chore(foundryup): clarify tag-resolution log and error messages Distinguish the GitHub API tag-resolution phase from the actual binary download by consistently referring to "release tag(s)" in the `resolve_version_and_tag` helper's `say` and `err` messages. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(ci): keep no-default builds free of op deps (#14612) * feat: cast unauthorized flow → wallet.tempo access-key authorization (#14517) * feat: cast unauthorized flow → wallet.tempo access-key authorization Amp-Thread-ID: https://ampcode.com/threads/T-019df174-9538-713b-b8c9-5001b1ad4719 Co-authored-by: Amp * fmt * feat(cast): replace TEMPO_NO_BROWSER env with flag * revert token addresses --------- Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * docs(expect-emit): clarify next-call semantics and warn about caught-revert leak (#14620) docs(cheatcodes): clarify expectEmit next-call semantics and caught-revert leak expectEmit is a 'next call' assertion. If the call immediately after expectEmit reverts and the revert is swallowed by the caller (low-level call or try/catch), the unmatched expectation can leak forward and be satisfied by a later unrelated emission, silently turning a broken test green. Document the constraint on the natspec for both no-arg and topic-checking overloads, and regenerate cheatcodes.json. Refs: https://github.com/foundry-rs/foundry/issues/14618 Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames (#14615) * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames The reverter address argument to `vm.expectRevert` was silently ignored when the innermost reverting frame was a CREATE (top-level or nested), because create_end never populated `expected_revert.reverted_by`. Mirror call_end's logic in create_end: when the outcome reverts and a reverter address is expected, record outcome.address (revm guarantees this is Some(would-be address) whenever the constructor executed). Adds positive regression tests for top-level and nested-CREATE reverts, and a negative regression test asserting wrong-reverter now fails. Co-authored-by: Amp * improve coverage * add Derek's suggested test cases * fix: forge fmt for ExpectRevert.t.sol Amp-Thread-ID: https://ampcode.com/threads/T-019dfdc5-5414-70b6-9f49-cb5797a37a29 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): keep plain Tempo broadcasts non-AA (#14616) * fix(script): don't force Tempo AA fee_token from --network tempo alone Plain --network tempo (or any selection that just sets the network to Tempo) does not by itself imply a Tempo AA / type 0x76 transaction. Defaulting tempo.common.fee_token to PATH_USD_ADDRESS solely from evm_opts.networks.is_tempo() caused every unsigned broadcast tx to flow through TempoOpts::apply, which set fee_token on the request and promoted it to the Tempo AA tx envelope. Signers that only know how to sign ordinary Ethereum transactions (e.g. the Ledger Ethereum app) then rejected the transaction with 'received an unexpected empty response'. Gate the default on an actual Tempo AA opt-in: - --batch (Tempo batch txs are themselves AA and need a fee token), or - any explicit --tempo.* flag (sponsor, expiring nonce, nonce key/lane, ...) which already forces an AA tx and benefits from a default fee token. Explicit --tempo.fee-token continues to win over the default in all cases, and non-Tempo networks never default the fee token. Add unit tests for each scenario. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't force eth_estimateGas on plain Tempo broadcasts Plain --network tempo produces an ordinary EIP-1559/legacy transaction (see tempo-alloy::TempoTransactionRequest::output_tx_type), so the local simulation gas estimate is sufficient. Forcing RPC re-estimation in this case can surface node-side errors such as 'gas required exceeds allowance (0)' (Geth-style balance/gasPrice cap from eth_estimateGas) on flows that previously worked, including Ledger-signed broadcasts that just got unblocked from the type 0x76 regression. Match tempo-foundry's behaviour: only force eth_estimateGas on Tempo when the user has actually opted into Tempo AA semantics (--batch or any explicit --tempo.* flag). Extract the gating into needs_tempo_aa_rpc_estimate(...) and add focused unit tests mirroring the fee-token gating tests. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't re-estimate plain Tempo chain broadcasts --------- Co-authored-by: Amp * fix(cheatcodes): preserve reverts with `expectEmit` (#14619) * test: added regression test * fix: re-order revert handling * refactor: simplify * lint: fmt * polish: tighten comment, extend test with revert reason and custom error Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * feat(lint): add tx-origin detector (#14589) * feat(lint): add tx-origin detector * test(lint): address tx-origin review feedback * fix: ui bless * fix(lint): cover tx-origin index and ternary predicates * test(lint): bless tx-origin snapshot --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * refactor(tempo): prepare batch access key txs w/ helper (#14597) fix(tempo): prepare batch access key txs before estimation * fix(anvil): respect non-zero genesis block in Otterscan APIs (#14490) fix(anvil): respect non-zero genesis block in Otterscan APIs The three Otterscan address-history endpoints (`ots_searchTransactionsBefore`/`After`, `ots_getTransactionBySenderAndNonce`) hardcoded `unwrap_or(1)` / `unwrap_or_default()` as the lower bound of their block scan, which breaks when `genesis_block_number` is non-zero (e.g. `genesis.json` `number: 73`). Expose `Backend::genesis_number()` and fall back to `genesis_number() + 1` in non-fork mode, mirroring the existing post-fork `f.block_number() + 1` convention. --------- Co-authored-by: Isagi Yates Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: figtracer Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Sergei Shulepov Co-authored-by: zerosnacks Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cui Co-authored-by: Centaur AI Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> Co-authored-by: Mikhail Mikheev <16622558+mmv08@users.noreply.github.com> Co-authored-by: lazymio Co-authored-by: Emma Jamieson-Hoare Co-authored-by: VIkions <99107287+vikions@users.noreply.github.com> Co-authored-by: Aïssata --- .github/scripts/commit-and-read-benchmarks.sh | 114 -- .github/scripts/commit-benchmark-results.sh | 75 + .github/scripts/compare-nightly.sh | 56 + .github/scripts/read-benchmark-results.sh | 37 + .github/scripts/tempo-check.sh | 86 +- .github/workflows/benchmarks-nightly.yml | 217 +++ .github/workflows/benchmarks.yml | 73 +- .github/workflows/ci-tempo.yml | 44 +- .github/workflows/ci.yml | 17 + .github/workflows/crate-checks.yml | 2 +- .github/workflows/docker-publish.yml | 30 + .github/workflows/nix.yml | 4 +- .github/workflows/npm.yml | 4 +- .github/workflows/release.yml | 78 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- Cargo.lock | 1011 +++++------ Cargo.toml | 98 +- README.md | 2 + SECURITY.md | 109 ++ benches/LATEST.md | 134 +- benches/src/main.rs | 40 +- benches/src/results.rs | 19 + benchmark.sh | 60 +- crates/anvil/Cargo.toml | 34 +- crates/anvil/core/Cargo.toml | 10 +- crates/anvil/src/cmd.rs | 27 +- crates/anvil/src/config.rs | 6 +- crates/anvil/src/eth/api.rs | 44 +- crates/anvil/src/eth/backend/executor.rs | 23 +- crates/anvil/src/eth/backend/mem/mod.rs | 267 ++- crates/anvil/src/eth/backend/mem/optimism.rs | 61 + .../anvil/src/eth/{error.rs => error/mod.rs} | 69 +- crates/anvil/src/eth/error/optimism.rs | 62 + crates/anvil/src/eth/otterscan/api.rs | 19 +- crates/anvil/src/eth/pool/transactions.rs | 4 +- crates/anvil/src/eth/sign.rs | 8 +- crates/anvil/src/{evm.rs => evm/mod.rs} | 84 +- crates/anvil/src/evm/optimism.rs | 87 + crates/anvil/src/lib.rs | 3 + crates/anvil/tests/it/main.rs | 1 + crates/anvil/tests/it/revert.rs | 50 + crates/cast/Cargo.toml | 17 +- crates/cast/src/args.rs | 7 + crates/cast/src/cmd/batch_mktx.rs | 27 +- crates/cast/src/cmd/batch_send.rs | 32 +- crates/cast/src/cmd/call.rs | 25 +- crates/cast/src/cmd/keychain.rs | 1022 ++++++++++- crates/cast/src/cmd/mktx.rs | 44 +- crates/cast/src/cmd/mod.rs | 3 + crates/cast/src/cmd/run.rs | 17 +- crates/cast/src/cmd/send.rs | 72 +- crates/cast/src/cmd/tempo.rs | 45 + crates/cast/src/cmd/tip20/mine.rs | 23 +- crates/cast/src/cmd/tip20/mod.rs | 2 +- crates/cast/src/cmd/vaddr/create.rs | 181 ++ crates/cast/src/cmd/vaddr/mod.rs | 131 ++ crates/cast/src/cmd/vaddr/resolve.rs | 52 + crates/cast/src/cmd/vaddr/watch.rs | 108 ++ crates/cast/src/cmd/wallet/mod.rs | 13 +- crates/cast/src/lib.rs | 4 +- crates/cast/src/opts.rs | 25 +- crates/cast/src/tempo.rs | 3 + crates/cast/src/tx.rs | 30 +- crates/cast/tests/cli/keychain.rs | 76 + crates/cast/tests/cli/main.rs | 119 ++ crates/cheatcodes/Cargo.toml | 10 + crates/cheatcodes/assets/cheatcodes.json | 4 +- crates/cheatcodes/spec/src/vm.rs | 6 + crates/cheatcodes/src/inspector.rs | 145 +- crates/cheatcodes/src/test/assert.rs | 4 +- crates/cheatcodes/src/version.rs | 67 +- crates/chisel/Cargo.toml | 8 +- crates/chisel/src/executor.rs | 1617 ++++++++--------- crates/chisel/src/source.rs | 561 ++---- crates/chisel/tests/it/repl/mod.rs | 20 + crates/cli/Cargo.toml | 7 + crates/cli/src/opts/evm.rs | 11 + crates/cli/src/opts/rpc.rs | 56 +- crates/cli/src/opts/rpc_common.rs | 7 +- crates/cli/src/opts/tempo.rs | 320 +++- crates/cli/src/utils/tempo.rs | 193 +- crates/common/Cargo.toml | 20 +- crates/common/build.rs | 20 +- crates/common/fmt/Cargo.toml | 8 +- crates/common/fmt/src/ui.rs | 8 + crates/common/src/contracts.rs | 14 +- crates/common/src/provider/mpp/keys.rs | 73 +- crates/common/src/provider/mpp/session.rs | 10 + crates/common/src/provider/mpp/transport.rs | 922 +++++++++- crates/common/src/provider/mpp/ws.rs | 4 + .../common/src/provider/runtime_transport.rs | 6 +- crates/common/src/tempo/auth.rs | 494 +++++ crates/common/src/tempo/keystore.rs | 147 +- crates/common/src/tempo/mod.rs | 186 ++ crates/common/src/transactions/builder.rs | 57 +- crates/common/src/transactions/receipt.rs | 2 + crates/config/src/fuzz.rs | 6 + crates/config/src/inline/mod.rs | 39 + crates/debugger/Cargo.toml | 8 + crates/doc/Cargo.toml | 4 + crates/doc/src/writer/as_doc.rs | 4 +- crates/evm/core/Cargo.toml | 24 +- crates/evm/core/src/decode.rs | 4 +- crates/evm/core/src/env.rs | 605 +++--- crates/evm/core/src/evm/mod.rs | 21 +- crates/evm/core/src/evm/op.rs | 22 +- crates/evm/core/src/fork/database.rs | 53 +- crates/evm/core/src/lib.rs | 3 + crates/evm/core/src/opts.rs | 7 +- crates/evm/coverage/Cargo.toml | 4 + crates/evm/evm/Cargo.toml | 13 + crates/evm/evm/src/executors/fuzz/mod.rs | 125 +- crates/evm/evm/src/executors/invariant/mod.rs | 2 +- crates/evm/fuzz/Cargo.toml | 9 + crates/evm/fuzz/src/lib.rs | 35 +- crates/evm/hardforks/Cargo.toml | 8 +- crates/evm/hardforks/src/lib.rs | 74 +- crates/evm/networks/Cargo.toml | 8 +- crates/evm/networks/src/lib.rs | 238 ++- crates/evm/networks/src/optimism.rs | 25 + crates/evm/traces/Cargo.toml | 4 + crates/fmt/Cargo.toml | 4 + crates/fmt/src/state/mod.rs | 2 +- crates/forge/Cargo.toml | 14 +- crates/forge/assets/tempo/MailTemplate.s.sol | 2 +- crates/forge/assets/tempo/MailTemplate.t.sol | 2 +- crates/forge/src/cmd/coverage.rs | 7 +- crates/forge/src/cmd/create.rs | 43 +- crates/forge/src/cmd/snapshot.rs | 7 +- crates/forge/src/cmd/test/mod.rs | 226 ++- crates/forge/src/cmd/test/summary.rs | 4 +- crates/forge/src/gas_report.rs | 2 +- crates/forge/src/multi_runner.rs | 32 + crates/forge/src/runner.rs | 24 +- crates/forge/tests/cli/cmd.rs | 4 +- crates/forge/tests/cli/config.rs | 28 + crates/forge/tests/cli/failure_assertions.rs | 7 +- crates/forge/tests/cli/inline_config.rs | 104 ++ crates/forge/tests/cli/lint.rs | 289 ++- crates/forge/tests/cli/lint/geiger.rs | 10 +- crates/forge/tests/cli/script.rs | 2 +- crates/forge/tests/cli/test_cmd/fuzz.rs | 145 ++ crates/forge/tests/cli/test_cmd/repros.rs | 60 + .../tests/fixtures/ExpectRevertFailures.t.sol | 57 + crates/lint/Cargo.toml | 4 + crates/lint/README.md | 8 + crates/lint/docs/README.md | 52 + crates/lint/docs/_template.md | 28 + crates/lint/docs/asm-keccak256.md | 42 + crates/lint/docs/block-timestamp.md | 44 + crates/lint/docs/boolean-cst.md | 37 + crates/lint/docs/boolean-equal.md | 34 + crates/lint/docs/could-be-immutable.md | 42 + crates/lint/docs/custom-errors.md | 45 + crates/lint/docs/divide-before-multiply.md | 32 + crates/lint/docs/erc20-unchecked-transfer.md | 43 + crates/lint/docs/incorrect-erc20-interface.md | 42 + .../lint/docs/incorrect-erc721-interface.md | 48 + crates/lint/docs/incorrect-shift.md | 37 + crates/lint/docs/inline-assembly.md | 69 + crates/lint/docs/interface-file-naming.md | 31 + crates/lint/docs/interface-naming.md | 31 + crates/lint/docs/missing-zero-check.md | 39 + crates/lint/docs/mixed-case-function.md | 32 + crates/lint/docs/mixed-case-variable.md | 36 + crates/lint/docs/multi-contract-file.md | 37 + crates/lint/docs/named-struct-fields.md | 31 + crates/lint/docs/pascal-case-struct.md | 31 + crates/lint/docs/pragma-inconsistent.md | 41 + crates/lint/docs/rtlo.md | 32 + .../lint/docs/screaming-snake-case-const.md | 30 + .../docs/screaming-snake-case-immutable.md | 31 + crates/lint/docs/too-many-digits.md | 32 + crates/lint/docs/tx-origin.md | 34 + crates/lint/docs/unaliased-plain-import.md | 34 + crates/lint/docs/unchecked-call.md | 34 + crates/lint/docs/unsafe-cheatcode.md | 35 + crates/lint/docs/unsafe-typecast.md | 40 + crates/lint/docs/unused-import.md | 40 + crates/lint/docs/unused-state-variables.md | 39 + crates/lint/docs/unwrapped-modifier-logic.md | 51 + crates/lint/src/linter/mod.rs | 2 + crates/lint/src/linter/project.rs | 92 + crates/lint/src/sol/info/inline_assembly.rs | 71 + crates/lint/src/sol/info/mod.rs | 12 + crates/lint/src/sol/info/pragma_directive.rs | 71 + crates/lint/src/sol/info/too_many_digits.rs | 50 + crates/lint/src/sol/macros.rs | 42 +- crates/lint/src/sol/med/mod.rs | 4 + crates/lint/src/sol/med/tx_origin.rs | 101 + crates/lint/src/sol/mod.rs | 133 +- crates/lint/testdata/BlockTimestamp.stderr | 24 +- crates/lint/testdata/BooleanCst.stderr | 10 +- crates/lint/testdata/BooleanEqual.stderr | 14 +- crates/lint/testdata/CouldBeImmutable.stderr | 14 +- crates/lint/testdata/CustomErrors.stderr | 10 +- .../lint/testdata/DivideBeforeMultiply.stderr | 12 +- crates/lint/testdata/Imports.stderr | 26 +- .../testdata/IncorrectERC20Interface.stderr | 30 +- .../testdata/IncorrectERC721Interface.stderr | 38 +- crates/lint/testdata/IncorrectShift.stderr | 10 +- crates/lint/testdata/InlineAssembly.sol | 110 ++ crates/lint/testdata/InlineAssembly.stderr | 96 + crates/lint/testdata/Keccak256.sol | 1 + crates/lint/testdata/Keccak256.stderr | 36 +- crates/lint/testdata/MissingZeroCheck.stderr | 46 +- crates/lint/testdata/MixedCase.stderr | 38 +- crates/lint/testdata/MultiContractFile.stderr | 10 +- .../MultiContractFile_InterfaceLibrary.stderr | 6 +- crates/lint/testdata/NamedStructFields.stderr | 2 +- .../PragmaInconsistentCaretAboveExact.sol | 7 + .../PragmaInconsistentCaretAboveExact.stderr | 16 + .../PragmaInconsistentCaretMatchesExact.sol | 7 + ...PragmaInconsistentCaretMatchesExact.stderr | 16 + .../PragmaInconsistentCaretVsTilde.sol | 7 + .../PragmaInconsistentCaretVsTilde.stderr | 16 + .../testdata/PragmaInconsistentOrVsExact.sol | 7 + .../PragmaInconsistentOrVsExact.stderr | 16 + .../PragmaInconsistentRangeVsExact.sol | 7 + .../PragmaInconsistentRangeVsExact.stderr | 16 + .../PragmaInconsistentThreeDistinct.sol | 8 + .../PragmaInconsistentThreeDistinct.stderr | 24 + crates/lint/testdata/Rtlo.stderr | 48 +- crates/lint/testdata/RtloCommentsOnly.stderr | 8 +- .../lint/testdata/ScreamingSnakeCase.stderr | 16 +- crates/lint/testdata/StructPascalCase.stderr | 12 +- crates/lint/testdata/TooManyDigits.sol | 73 + crates/lint/testdata/TooManyDigits.stderr | 72 + crates/lint/testdata/TxOrigin.sol | 65 + crates/lint/testdata/TxOrigin.stderr | 72 + crates/lint/testdata/UncheckedCall.stderr | 16 +- .../testdata/UncheckedTransferERC20.stderr | 22 +- crates/lint/testdata/UnsafeCheatcodes.stderr | 26 +- crates/lint/testdata/UnsafeTypecast.stderr | 330 ++-- .../lint/testdata/UnusedStateVariables.stderr | 10 +- .../testdata/UnwrappedModifierLogic.stderr | 22 +- crates/primitives/Cargo.toml | 17 +- crates/primitives/src/network/mod.rs | 10 +- crates/primitives/src/network/optimism.rs | 47 + crates/primitives/src/network/receipt.rs | 40 +- crates/primitives/src/transaction/envelope.rs | 281 +-- crates/primitives/src/transaction/mod.rs | 6 +- crates/primitives/src/transaction/optimism.rs | 300 +++ crates/primitives/src/transaction/receipt.rs | 114 +- crates/primitives/src/transaction/request.rs | 110 +- crates/script-sequence/Cargo.toml | 4 + crates/script/Cargo.toml | 12 + crates/script/src/broadcast.rs | 143 +- crates/script/src/lib.rs | 131 +- crates/script/src/runner.rs | 10 +- crates/script/src/verify.rs | 2 +- crates/sol-macro-gen/Cargo.toml | 4 + crates/test-utils/Cargo.toml | 4 + crates/verify/Cargo.toml | 9 + docs/dev/lintrules.md | 2 + flake.lock | 18 +- foundryup/README.md | 4 +- foundryup/foundryup | 135 +- testdata/default/cheats/ExpectRevert.t.sol | 85 + .../default/cheats/GetFoundryVersion.t.sol | 51 + testdata/default/cheats/MockCall.t.sol | 41 +- testdata/default/cheats/MockCalls.t.sol | 4 + 264 files changed, 13431 insertions(+), 4233 deletions(-) delete mode 100755 .github/scripts/commit-and-read-benchmarks.sh create mode 100755 .github/scripts/commit-benchmark-results.sh create mode 100755 .github/scripts/compare-nightly.sh create mode 100755 .github/scripts/read-benchmark-results.sh create mode 100644 .github/workflows/benchmarks-nightly.yml create mode 100644 crates/anvil/src/eth/backend/mem/optimism.rs rename crates/anvil/src/eth/{error.rs => error/mod.rs} (91%) create mode 100644 crates/anvil/src/eth/error/optimism.rs rename crates/anvil/src/{evm.rs => evm/mod.rs} (64%) create mode 100644 crates/anvil/src/evm/optimism.rs create mode 100644 crates/cast/src/cmd/tempo.rs create mode 100644 crates/cast/src/cmd/vaddr/create.rs create mode 100644 crates/cast/src/cmd/vaddr/mod.rs create mode 100644 crates/cast/src/cmd/vaddr/resolve.rs create mode 100644 crates/cast/src/cmd/vaddr/watch.rs create mode 100644 crates/cast/src/tempo.rs create mode 100644 crates/cast/tests/cli/keychain.rs create mode 100644 crates/common/src/tempo/auth.rs create mode 100644 crates/evm/networks/src/optimism.rs create mode 100644 crates/lint/docs/README.md create mode 100644 crates/lint/docs/_template.md create mode 100644 crates/lint/docs/asm-keccak256.md create mode 100644 crates/lint/docs/block-timestamp.md create mode 100644 crates/lint/docs/boolean-cst.md create mode 100644 crates/lint/docs/boolean-equal.md create mode 100644 crates/lint/docs/could-be-immutable.md create mode 100644 crates/lint/docs/custom-errors.md create mode 100644 crates/lint/docs/divide-before-multiply.md create mode 100644 crates/lint/docs/erc20-unchecked-transfer.md create mode 100644 crates/lint/docs/incorrect-erc20-interface.md create mode 100644 crates/lint/docs/incorrect-erc721-interface.md create mode 100644 crates/lint/docs/incorrect-shift.md create mode 100644 crates/lint/docs/inline-assembly.md create mode 100644 crates/lint/docs/interface-file-naming.md create mode 100644 crates/lint/docs/interface-naming.md create mode 100644 crates/lint/docs/missing-zero-check.md create mode 100644 crates/lint/docs/mixed-case-function.md create mode 100644 crates/lint/docs/mixed-case-variable.md create mode 100644 crates/lint/docs/multi-contract-file.md create mode 100644 crates/lint/docs/named-struct-fields.md create mode 100644 crates/lint/docs/pascal-case-struct.md create mode 100644 crates/lint/docs/pragma-inconsistent.md create mode 100644 crates/lint/docs/rtlo.md create mode 100644 crates/lint/docs/screaming-snake-case-const.md create mode 100644 crates/lint/docs/screaming-snake-case-immutable.md create mode 100644 crates/lint/docs/too-many-digits.md create mode 100644 crates/lint/docs/tx-origin.md create mode 100644 crates/lint/docs/unaliased-plain-import.md create mode 100644 crates/lint/docs/unchecked-call.md create mode 100644 crates/lint/docs/unsafe-cheatcode.md create mode 100644 crates/lint/docs/unsafe-typecast.md create mode 100644 crates/lint/docs/unused-import.md create mode 100644 crates/lint/docs/unused-state-variables.md create mode 100644 crates/lint/docs/unwrapped-modifier-logic.md create mode 100644 crates/lint/src/linter/project.rs create mode 100644 crates/lint/src/sol/info/inline_assembly.rs create mode 100644 crates/lint/src/sol/info/pragma_directive.rs create mode 100644 crates/lint/src/sol/info/too_many_digits.rs create mode 100644 crates/lint/src/sol/med/tx_origin.rs create mode 100644 crates/lint/testdata/InlineAssembly.sol create mode 100644 crates/lint/testdata/InlineAssembly.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.sol create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr create mode 100644 crates/lint/testdata/TooManyDigits.sol create mode 100644 crates/lint/testdata/TooManyDigits.stderr create mode 100644 crates/lint/testdata/TxOrigin.sol create mode 100644 crates/lint/testdata/TxOrigin.stderr create mode 100644 crates/primitives/src/network/optimism.rs create mode 100644 crates/primitives/src/transaction/optimism.rs diff --git a/.github/scripts/commit-and-read-benchmarks.sh b/.github/scripts/commit-and-read-benchmarks.sh deleted file mode 100755 index 358b53a73155a..0000000000000 --- a/.github/scripts/commit-and-read-benchmarks.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Script to commit benchmark results and read them for GitHub Actions output -# Usage: ./commit-and-read-benchmarks.sh - -OUTPUT_DIR="${1:-benches}" -GITHUB_EVENT_NAME="${2:-pull_request}" -GITHUB_REPOSITORY="${3:-}" - -# Global variable for branch name -BRANCH_NAME="" - -# Function to commit benchmark results -commit_results() { - echo "Configuring git..." - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # For PR runs, fetch and checkout the PR branch to ensure we're up to date - if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" - git fetch origin "$GITHUB_HEAD_REF" - git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" - fi - - echo "Adding benchmark file..." - git add "$OUTPUT_DIR/LATEST.md" - - if git diff --staged --quiet; then - echo "No changes to commit" - else - echo "Committing benchmark results..." - git commit -m "chore(\`benches\`): update benchmark results - -🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) - -Co-Authored-By: github-actions " - - echo "Pushing to repository..." - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - # For manual runs, we're on a new branch - git push origin "$BRANCH_NAME" - elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - # For PR runs, push to the PR branch - if [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Pushing to PR branch: $GITHUB_HEAD_REF" - git push origin "$GITHUB_HEAD_REF" - else - echo "Error: GITHUB_HEAD_REF not set for pull_request event" - exit 1 - fi - else - # This workflow should only run on workflow_dispatch or pull_request - echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" - echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" - exit 1 - fi - echo "Successfully pushed benchmark results" - fi -} - -# Function to read benchmark results and output for GitHub Actions -read_results() { - if [ -f "$OUTPUT_DIR/LATEST.md" ]; then - echo "Reading benchmark results..." - - # Output full results - { - echo 'results<> "$GITHUB_OUTPUT" - - # Format results for PR comment - echo "Formatting results for PR comment..." - FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") - - { - echo 'pr_comment<> "$GITHUB_OUTPUT" - - echo "Successfully read and formatted benchmark results" - else - echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" - fi -} - -# Main execution -echo "Starting benchmark results processing..." - -# Create new branch for manual runs -if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - echo "Manual workflow run detected, creating new branch..." - BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$BRANCH_NAME" - echo "Created branch: $BRANCH_NAME" - - # Output branch name for later use - echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" -fi - -# Always commit benchmark results -echo "Committing benchmark results..." -commit_results - -# Always read results for output -read_results - -echo "Benchmark results processing complete" \ No newline at end of file diff --git a/.github/scripts/commit-benchmark-results.sh b/.github/scripts/commit-benchmark-results.sh new file mode 100755 index 0000000000000..f7dba8980fd64 --- /dev/null +++ b/.github/scripts/commit-benchmark-results.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +# Script to commit and push benchmark results. +# +# This script is intended to run from the lightweight `publish-results` job, +# which checks out the repo with credentials and only operates on the +# trusted artifact produced by the benchmark job. Keeping the write-scoped +# token away from the bench job (which runs untrusted third-party builds) +# limits the blast radius of a compromised dependency. +# +# Usage: ./commit-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" +GITHUB_EVENT_NAME="${2:-workflow_dispatch}" +GITHUB_REPOSITORY="${3:-}" + +if [ ! -f "$OUTPUT_DIR/LATEST.md" ]; then + echo "Error: $OUTPUT_DIR/LATEST.md not found, nothing to commit" + exit 1 +fi + +echo "Configuring git..." +git config --local user.email "action@github.com" +git config --local user.name "GitHub Action" + +# Decide which branch to commit to based on the event. +BRANCH_NAME="" +case "$GITHUB_EVENT_NAME" in + workflow_dispatch) + echo "Manual workflow run detected, creating new branch..." + BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH_NAME" + echo "Created branch: $BRANCH_NAME" + ;; + pull_request) + if [ -z "${GITHUB_HEAD_REF:-}" ]; then + echo "Error: GITHUB_HEAD_REF not set for pull_request event" + exit 1 + fi + echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" + git fetch origin "$GITHUB_HEAD_REF" + git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" + BRANCH_NAME="$GITHUB_HEAD_REF" + ;; + *) + echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" + echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" + exit 1 + ;; +esac + +# Always emit the branch name so downstream steps (e.g. PR creation) can use it. +echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + +echo "Adding benchmark file..." +git add "$OUTPUT_DIR/LATEST.md" + +if git diff --staged --quiet; then + echo "No changes to commit" + echo "committed=false" >> "$GITHUB_OUTPUT" + exit 0 +fi + +echo "Committing benchmark results..." +git commit -m "chore(\`benches\`): update benchmark results + +🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) + +Co-Authored-By: github-actions " + +echo "Pushing to repository..." +git push origin "$BRANCH_NAME" +echo "Successfully pushed benchmark results to $BRANCH_NAME" +echo "committed=true" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh new file mode 100755 index 0000000000000..674cc0fe01754 --- /dev/null +++ b/.github/scripts/compare-nightly.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Compare two nightly benchmark JSON summaries and report regressions. +# +# Usage: compare-nightly.sh [warn_pct] [fail_pct] +# Exits 0 if no regressions, 1 if any metric exceeds fail_pct. +# Exits 0 gracefully when prev.json is missing (first run / gap > 7 days). +set -euo pipefail + +PREV_JSON="${1:-}" +TODAY_JSON="${2:-}" +WARN="${3:-1}" +FAIL="${4:-3}" + +PREV_JSON="$PREV_JSON" TODAY_JSON="$TODAY_JSON" WARN="$WARN" FAIL="$FAIL" \ +python3 - <<'EOF' +import json, os, sys + +warn = float(os.environ["WARN"]) +fail = float(os.environ["FAIL"]) + +prev_path = os.environ.get("PREV_JSON", "") +prev = json.load(open(prev_path)) if prev_path and os.path.isfile(prev_path) else {} +with open(os.environ["TODAY_JSON"]) as f: + today = json.load(f) + +print("## Nightly Benchmark Regression Report\n") +print("| Benchmark | Previous | Today | Δ | Status |") +print("|-----------|----------|-------|---|--------|") + +has_regression = False +all_keys = sorted(prev.keys() | today.keys()) +for key in all_keys: + t = today.get(key) + p = prev.get(key) + if t is None: + print(f"| `{key}` | {p:.5f}s | N/A | — | ⚠️ Missing |") + has_regression = True + continue + if p is None: + print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") + continue + delta = (t - p) / p * 100 + if delta >= fail: + status = "🔴 Regression" + has_regression = True + elif delta >= warn: + status = "🟡 Warning" + elif delta <= -warn: + status = "🟢 Improvement" + else: + status = "➡️ OK" + sign = "+" if delta > 0 else "" + print(f"| `{key}` | {p}s | {t}s | {sign}{delta:.1f}% | {status} |") + +sys.exit(1 if has_regression else 0) +EOF diff --git a/.github/scripts/read-benchmark-results.sh b/.github/scripts/read-benchmark-results.sh new file mode 100755 index 0000000000000..548611a7d204a --- /dev/null +++ b/.github/scripts/read-benchmark-results.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +# Script to read benchmark results and emit them as GitHub Actions outputs. +# This script performs no git operations — it only reads the combined +# benchmark file and writes outputs for the workflow to consume. +# +# Usage: ./read-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" + +echo "Reading benchmark results from $OUTPUT_DIR..." + +if [ -f "$OUTPUT_DIR/LATEST.md" ]; then + # Output full results + { + echo 'results<> "$GITHUB_OUTPUT" + + # Format results for PR comment + echo "Formatting results for PR comment..." + FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") + + { + echo 'pr_comment<> "$GITHUB_OUTPUT" + + echo "Successfully read and formatted benchmark results" +else + echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" +fi diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh index b730c466bde55..3caea992cfe7e 100755 --- a/.github/scripts/tempo-check.sh +++ b/.github/scripts/tempo-check.sh @@ -445,7 +445,7 @@ echo -e "\n=== CAST SEND WITH SPONSOR (--tempo.sponsor-signature) ===" # Test sponsored transactions using pre-signed signature. # Step 1: Get the fee_payer_signature_hash using --tempo.print-sponsor-hash # Step 2: Sign it with the sponsor's private key -# Step 3: Send with --tempo.sponsor-signature +# Step 3: Send with --tempo.sponsor and --tempo.sponsor-signature # Step 1: Get the hash that the sponsor needs to sign FEE_PAYER_HASH=$(cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ @@ -460,7 +460,7 @@ printf "Sponsor signature: %s\n" "$SPONSOR_SIG" # Step 3: Send the sponsored transaction with the signature RECEIPT=$(cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" \ - --tempo.sponsor-signature "$SPONSOR_SIG" --json) + --tempo.sponsor "$SPONSOR_ADDR" --tempo.sponsor-signature "$SPONSOR_SIG" --json) # Verify the fee_payer in the receipt matches the sponsor address RECEIPT_FEE_PAYER=$(echo "$RECEIPT" | jq -r '.feePayer // .fee_payer // empty') @@ -897,3 +897,85 @@ check_has_code "Nonce" "0x4e4F4E4345000000000000000000000000000000" check_has_code "AccountKeychain" "0xaAAAaaAA00000000000000000000000000000000" echo -e "\n=== CHISEL FORK TESTS COMPLETE ===" + +# --- cast virtual-address (TIP-1022) tests --- + +echo -e "\n=== CAST VIRTUAL-ADDRESS: SETUP MASTER WALLET ===" +vaddr_master_json="$(cast wallet new --json)" +VADDR_MASTER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_master_json")" +VADDR_MASTER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_master_json")" +printf "Master address: %s\n" "$VADDR_MASTER_ADDR" +fund_and_wait "$VADDR_MASTER_ADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: CREATE (mine + register) ===" +# Use the `vaddr` alias to also exercise it. +VADDR_CREATE_OUT=$(cast vaddr create \ +--owner "$VADDR_MASTER_ADDR" \ +--private-key "$VADDR_MASTER_PK" \ +--rpc-url "$ETH_RPC_URL") +echo "$VADDR_CREATE_OUT" +VADDR=$(echo "$VADDR_CREATE_OUT" | sed -n 's/^ tag=0x000000000000 \(0x[a-fA-F0-9]\{40\}\).*/\1/p' | head -1) +if [[ -z "$VADDR" ]]; then +echo "ERROR: failed to parse virtual address from create output" +exit 1 +fi +echo "Virtual address: $VADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: RESOLVE ===" +VADDR_RESOLVE_OUT=$(cast virtual-address resolve "$VADDR" --rpc-url "$ETH_RPC_URL") +echo "$VADDR_RESOLVE_OUT" +RESOLVED_MASTER=$(echo "$VADDR_RESOLVE_OUT" | sed -n 's/^Master address: \(0x[a-fA-F0-9]\{40\}\).*/\1/p') +RESOLVED_LOWER=$(echo "$RESOLVED_MASTER" | tr '[:upper:]' '[:lower:]') +EXPECTED_LOWER=$(echo "$VADDR_MASTER_ADDR" | tr '[:upper:]' '[:lower:]') +if [[ "$RESOLVED_LOWER" != "$EXPECTED_LOWER" ]]; then +echo "ERROR: resolve returned master $RESOLVED_MASTER, expected $VADDR_MASTER_ADDR" +exit 1 +fi +echo "OK: resolve returned the registered master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: AUTO-FORWARD TO MASTER ===" +# Create a separate sender, fund it, and transfer the fee token to the +# virtual address. The protocol must auto-forward to the master wallet. +vaddr_sender_json="$(cast wallet new --json)" +VADDR_SENDER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_sender_json")" +VADDR_SENDER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_sender_json")" +fund_and_wait "$VADDR_SENDER_ADDR" + +BAL_BEFORE=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance before: $BAL_BEFORE" + +# Capture the current block before the transfer so `cast vaddr watch` can +# replay the Transfer log via --from-block. +BLOCK_BEFORE_TRANSFER=$(cast block-number --rpc-url "$ETH_RPC_URL") + +AMOUNT=1000000 +cast send "$FEE_TOKEN" 'transfer(address,uint256)' "$VADDR" "$AMOUNT" \ +--rpc-url "$ETH_RPC_URL" --private-key "$VADDR_SENDER_PK" + +BAL_AFTER=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance after: $BAL_AFTER" + +EXPECTED=$((BAL_BEFORE + AMOUNT)) +if [[ "$BAL_AFTER" != "$EXPECTED" ]]; then +echo "ERROR: master balance grew by $((BAL_AFTER - BAL_BEFORE)), expected $AMOUNT" +exit 1 +fi +echo "OK: transfer to virtual address auto-forwarded to master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: WATCH ===" +# Tail incoming TIP-20 transfers to the virtual address. `cast vaddr watch` +# polls indefinitely, so we cap it with `timeout`; the historical fetch via +# --from-block emits the prior Transfer log immediately at startup. +WATCH_OUT=$(timeout 5 cast vaddr watch "$VADDR" \ + --token "$FEE_TOKEN" \ + --from-block "$BLOCK_BEFORE_TRANSFER" \ + --rpc-url "$ETH_RPC_URL" 2>&1 || true) +echo "$WATCH_OUT" + +EXPECTED_PATTERN="token=$FEE_TOKEN from=$VADDR_SENDER_ADDR amount=$AMOUNT" +echo "Expected pattern: $EXPECTED_PATTERN" +if ! echo "$WATCH_OUT" | grep -iqF "$EXPECTED_PATTERN"; then + echo "ERROR: cast vaddr watch output did not contain expected '$EXPECTED_PATTERN'" + exit 1 +fi +echo "OK: cast vaddr watch reported correct token/from/amount" diff --git a/.github/workflows/benchmarks-nightly.yml b/.github/workflows/benchmarks-nightly.yml new file mode 100644 index 0000000000000..8569f52ce3b93 --- /dev/null +++ b/.github/workflows/benchmarks-nightly.yml @@ -0,0 +1,217 @@ +name: Nightly Benchmarks (AAVE v4) + +permissions: {} + +on: + schedule: + - cron: "0 2 * * *" # 2am UTC nightly + workflow_dispatch: # allow manual triggering for testing + +env: + AAVE_V4_REPO: "aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" + RUSTC_WRAPPER: "sccache" + +jobs: + run-benchmarks: + name: Run Nightly Benchmarks + runs-on: depot-ubuntu-24.04-32 + permissions: + contents: read + actions: read # needed to download artifacts from previous runs + outputs: + has_regression: ${{ steps.compare.outputs.has_regression }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + + - name: Setup Foundry + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + ./.github/scripts/setup-foundryup.sh + printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" + + - name: Build benchmark binary + run: cargo build --locked --release --bin foundry-bench + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + - name: Install hyperfine + run: | + curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ + rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu + + - name: Download previous benchmark results + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p prev-results + PREV_RUN_ID=$(gh run list \ + --workflow=benchmarks-nightly.yml \ + --status=success \ + --limit=1 \ + --json databaseId \ + -q '.[0].databaseId // empty' 2>/dev/null || true) + if [[ -n "$PREV_RUN_ID" ]]; then + echo "Downloading results from previous run $PREV_RUN_ID" + gh run download "$PREV_RUN_ID" \ + --name nightly-bench-results \ + --dir prev-results/ || true + else + echo "No previous successful run found, skipping download." + fi + + - name: Run forge test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_test \ + --json-output "nightly-${DATE}-forge_test.json" \ + --verbose + + - name: Run forge fuzz test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_fuzz_test \ + --json-output "nightly-${DATE}-forge_fuzz_test.json" \ + --verbose + + - name: Run forge isolate test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_isolate_test \ + --json-output "nightly-${DATE}-forge_isolate_test.json" \ + --verbose + + - name: Run forge build benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --json-output "nightly-${DATE}-forge_build.json" \ + --verbose + + - name: Run forge coverage benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_coverage \ + --json-output "nightly-${DATE}-forge_coverage.json" \ + --verbose + + - name: Merge benchmark JSON results + run: | + DATE=$(date -u +%Y-%m-%d) + shopt -s nullglob + parts=( benches/nightly-${DATE}-*.json ) + if [[ ${#parts[@]} -eq 0 ]]; then + echo "No benchmark results produced — all steps failed." + exit 1 + fi + jq -s 'add' "${parts[@]}" > "benches/nightly-${DATE}.json" + echo "Merged ${#parts[@]} result file(s) into nightly-${DATE}.json" + + - name: Compare with previous results + id: compare + run: | + DATE=$(date -u +%Y-%m-%d) + PREV_JSON=$(ls prev-results/nightly-*.json 2>/dev/null | head -1 || true) + TODAY_JSON="benches/nightly-${DATE}.json" + if ./.github/scripts/compare-nightly.sh "$PREV_JSON" "$TODAY_JSON" > regression.md 2>&1; then + echo "has_regression=false" >> "$GITHUB_OUTPUT" + else + echo "has_regression=true" >> "$GITHUB_OUTPUT" + fi + cat regression.md + + - name: Upload benchmark results + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: nightly-bench-results + retention-days: 7 + path: | + benches/nightly-*.json + regression.md + + report-regression: + name: Report Regression + needs: run-benchmarks + if: needs.run-benchmarks.outputs.has_regression == 'true' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Download benchmark results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: nightly-bench-results + path: results/ + + - name: Open regression issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + DATE=$(date -u +%Y-%m-%d) + BODY="$(cat results/regression.md) + + --- + + **Run**: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + **Date**: ${DATE} + **Repo benchmarked**: \`aave/aave-v4\` (pinned commit) + **Threshold**: 🔴 >=3% regression, 🟡 >=1% warning" + + gh issue create \ + --title "[Nightly Regression] ${DATE}" \ + --body "$BODY" \ + --label "regression" \ + --repo "$GH_REPO" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5d4767ad3b554..a136703abc294 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -10,17 +10,17 @@ on: required: false type: string versions: - description: "Comma-separated list of Foundry versions to benchmark (e.g., stable,nightly,v1.0.0)" + description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'v1.5.1,v1.7.0')" required: false type: string - default: "stable,nightly" repos: - description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev][ ] (e.g. vectorized/solady:v0.1.26 --nmc BrokenTest). Leave empty to use the per-benchmark default repo lists." + description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev] (e.g. aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35). Leave empty to use the per-benchmark default repo lists." required: false type: string - default: "" env: + DEFAULT_VERSIONS: "v1.5.1,v1.7.0" + # Repos to benchmark per step. Each comma-separated entry has the form # org/repo[:rev][ ] # where anything after the first whitespace is appended to every benchmark @@ -29,27 +29,23 @@ env: TEST_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting ISOLATE_TEST_REPOS: >- ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest, - vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, + vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest', uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting BUILD_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 COVERAGE_REPOS: >- ithacaxyz/account:v0.5.7, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 @@ -60,7 +56,7 @@ jobs: name: Run All Benchmarks runs-on: depot-ubuntu-24.04-32 permissions: - contents: write + contents: read steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -93,7 +89,7 @@ jobs: run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" @@ -106,59 +102,61 @@ jobs: - name: Run forge test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_test,forge_fuzz_test \ - --output-file forge_test_bench.md + --output-file forge_test_bench.md \ + --verbose - name: Run forge isolate test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.ISOLATE_TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_isolate_test \ - --output-file forge_isolate_test_bench.md + --output-file forge_isolate_test_bench.md \ + --verbose - name: Run forge build benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.BUILD_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_build_no_cache,forge_build_with_cache \ - --output-file forge_build_bench.md + --output-file forge_build_bench.md \ + --verbose - name: Run forge coverage benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.COVERAGE_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_coverage \ - --output-file forge_coverage_bench.md + --output-file forge_coverage_bench.md \ + --verbose - name: Combine benchmark results run: ./.github/scripts/combine-benchmarks.sh benches - - name: Commit and read benchmark results + - name: Read benchmark results id: benchmark_results - env: - GITHUB_HEAD_REF: ${{ github.head_ref }} - run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" + run: ./.github/scripts/read-benchmark-results.sh benches - name: Upload benchmark results as artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -172,21 +170,21 @@ jobs: benches/LATEST.md outputs: - branch_name: ${{ steps.benchmark_results.outputs.branch_name }} pr_comment: ${{ steps.benchmark_results.outputs.pr_comment }} publish-results: name: Publish Results needs: run-benchmarks runs-on: ubuntu-latest + # All git writes happen here, on a clean ubuntu-latest runner that has + # never executed third-party benchmark code. permissions: contents: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false + # persist-credentials defaults to true so we can push. - name: Download benchmark results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -194,19 +192,22 @@ jobs: name: benchmark-results path: benches/ - - name: Push branch for manual runs - if: github.event_name == 'workflow_dispatch' - run: | - git push origin "${{ needs.run-benchmarks.outputs.branch_name }}" - echo "Pushed branch: ${{ needs.run-benchmarks.outputs.branch_name }}" + - name: Commit benchmark results + id: commit_results + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: ./.github/scripts/commit-benchmark-results.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Create PR for manual runs - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && steps.commit_results.outputs.committed == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BRANCH_NAME: ${{ steps.commit_results.outputs.branch_name }} + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | - const branchName = '${{ needs.run-benchmarks.outputs.branch_name }}'; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const branchName = process.env.BRANCH_NAME; + const prComment = process.env.PR_COMMENT; // Create the pull request const { data: pr } = await github.rest.pulls.create({ @@ -231,10 +232,12 @@ jobs: - name: Comment on PR if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const prComment = process.env.PR_COMMENT; const comment = `${prComment} diff --git a/.github/workflows/ci-tempo.yml b/.github/workflows/ci-tempo.yml index ad4c424b7e8b2..1ef4c760f324e 100644 --- a/.github/workflows/ci-tempo.yml +++ b/.github/workflows/ci-tempo.yml @@ -69,14 +69,16 @@ jobs: run: | cargo test --locked -p foundry-common --lib tempo::tests::test_fork_schedule_parses_configured_rpcs -- --exact --nocapture - - name: Run Tempo check on devnet + - name: Run Tempo check on mainnet if: | - github.event_name == 'push' || - github.event_name == 'pull_request' || - github.event.inputs.network == 'devnet' || + github.event_name == 'schedule' || + github.event.inputs.network == 'mainnet' || github.event.inputs.network == 'all' env: - ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} + TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" + VERIFIER_URL: ${{ secrets.VERIFIER_URL }} + PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} run: | if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then @@ -86,16 +88,6 @@ jobs: ./.github/scripts/tempo-deploy.sh fi - - name: Run Tempo wallet tests on devnet - if: | - github.event_name == 'push' || - github.event_name == 'pull_request' || - github.event.inputs.network == 'devnet' || - github.event.inputs.network == 'all' - env: - ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} - run: ./.github/scripts/tempo-wallet.sh - - name: Run Tempo check on testnet if: | github.event_name == 'schedule' || @@ -113,16 +105,14 @@ jobs: ./.github/scripts/tempo-deploy.sh fi - - name: Run Tempo check on mainnet + - name: Run Tempo check on devnet if: | - github.event_name == 'schedule' || - github.event.inputs.network == 'mainnet' || + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || github.event.inputs.network == 'all' env: - ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} - TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" - VERIFIER_URL: ${{ secrets.VERIFIER_URL }} - PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} run: | if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then @@ -132,6 +122,16 @@ jobs: ./.github/scripts/tempo-deploy.sh fi + - name: Run Tempo wallet tests on devnet + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + run: ./.github/scripts/tempo-wallet.sh + # If the nightly run fails, this will create an issue to signal so. issue: name: Open an issue diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb90a76cdbfd..a434028fdd18c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,23 @@ jobs: - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo test --workspace --doc --locked + no-default-features: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo build --workspace --no-default-features --locked + typos: runs-on: depot-ubuntu-latest timeout-minutes: 30 diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index eb865bddc10e3..f0d460da6fbb1 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -29,7 +29,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b97a99d5310a4..6120735657ee6 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -32,6 +32,8 @@ jobs: name: build and push runs-on: depot-ubuntu-latest permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -92,6 +94,7 @@ jobs: uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 - name: Build and push Foundry image + id: build uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 with: build-args: | @@ -106,3 +109,30 @@ jobs: platforms: linux/amd64,linux/arm64 push: true no-cache: true + sbom: true + provenance: mode=max + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign image with cosign (keyless) + env: + DOCKER_TAGS: ${{ steps.docker_tagging.outputs.docker_tags }} + DIGEST: ${{ steps.build.outputs.digest }} + shell: bash + run: | + set -euo pipefail + IFS=',' read -r -a tags <<< "$DOCKER_TAGS" + for tag in "${tags[@]}"; do + # Strip any tag suffix and pin to immutable digest, then sign. + ref="${tag%%:*}@${DIGEST}" + printf 'Signing %s\n' "$ref" + cosign sign --yes "$ref" + done + + - name: Image build provenance attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0a18c99a41f82..8528b71f299a9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index fdc5e5716c577..323059e99e6b6 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -143,7 +143,7 @@ jobs: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -259,7 +259,7 @@ jobs: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682c9214284f6..38fa791fb655f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2 + uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2.1 with: configuration: "./.github/changelog.json" fromTag: ${{ steps.release_info.outputs.from_tag || '' }} @@ -117,6 +117,8 @@ jobs: needs: prepare uses: ./.github/workflows/docker-publish.yml permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -129,9 +131,10 @@ jobs: # way, GitHub's immutable-releases setting seals the release at publish. release: permissions: - id-token: write - contents: write attestations: write + artifact-metadata: write + contents: write + id-token: write name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 @@ -264,6 +267,38 @@ jobs: printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" fi printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" + printf "foundry_sbom=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.spdx.json" >> "$GITHUB_OUTPUT" + printf "foundry_checksum=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sha256" >> "$GITHUB_OUTPUT" + printf "foundry_signature=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sigstore.json" >> "$GITHUB_OUTPUT" + + - name: Generate archive checksum + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + shell: bash + run: | + set -euo pipefail + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + else + shasum -a 256 "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + fi + cat "$FOUNDRY_CHECKSUM" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + + - name: Generate SBOM (SPDX) + env: + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash + run: | + set -euo pipefail + syft scan dir:. \ + --source-name foundry \ + --source-version "$VERSION_NAME" \ + -o spdx-json="$FOUNDRY_SBOM" - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -292,15 +327,37 @@ jobs: tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - - name: Binaries attestation + - name: Binaries and archive provenance attestation id: attestation - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: | ${{ env.anvil_bin_path }} ${{ env.cast_bin_path }} ${{ env.chisel_bin_path }} ${{ env.forge_bin_path }} + ${{ steps.artifacts.outputs.file_name }} + + - name: Archive SBOM attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: ${{ steps.artifacts.outputs.file_name }} + sbom-path: ${{ steps.artifacts.outputs.foundry_sbom }} + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign archive with cosign (keyless) + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} + shell: bash + run: | + set -euo pipefail + cosign sign-blob \ + --yes \ + --bundle "$FOUNDRY_SIGNATURE" \ + "$FILE_NAME" - name: Record attestation URL env: @@ -321,11 +378,20 @@ jobs: TAG_NAME: ${{ needs.prepare.outputs.tag_name }} FILE_NAME: ${{ steps.artifacts.outputs.file_name }} FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} FOUNDRY_MAN: ${{ steps.man.outputs.foundry_man }} shell: bash run: | set -euo pipefail - files=("$FILE_NAME" "$FOUNDRY_ATTESTATION") + files=( + "$FILE_NAME" + "$FOUNDRY_ATTESTATION" + "$FOUNDRY_SBOM" + "$FOUNDRY_CHECKSUM" + "$FOUNDRY_SIGNATURE" + ) if [[ -n "${FOUNDRY_MAN:-}" ]]; then files+=("$FOUNDRY_MAN") fi diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index d6244f826887e..9caa254f05c10 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 141e3a049a73b..6763f1f80bde3 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,7 +37,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaa46c8e79f7c..daa4822b6e395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,14 +73,14 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest # External tests dependencies - name: Setup Node.js if: contains(matrix.name, 'external') - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 - name: Install Bun diff --git a/Cargo.lock b/Cargo.lock index 781b2c7f02b50..9db38f1967f4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,21 +77,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a547705d5c1b42575a0542bae2ba45bc62a6154be86611afaef1c0ab5c38598e" +checksum = "d8010fc7e9e8643ef4e758cdccf3eef26734594aedf88a9d5ed35e51837d42ef" dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -116,14 +116,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -143,23 +143,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" +checksum = "de3df0aadc569a8b277808a7d0ad0e421180654ea36a3c59e9ed2bb968c9a1cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250ba1168b8a049185a68c4dfa7f2a6a4046bd26fcc8c68632caeb216a5e12dc" +checksum = "1ceb16e7fe5a95825305f218ccd356665f848831f94ce2bbf55339bf5d21e88a" dependencies = [ "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] @@ -265,13 +265,14 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "once_cell", "serde", ] @@ -300,9 +301,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -310,7 +311,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "auto_impl", "borsh", "c-kzg", @@ -325,9 +326,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a8c1330ad33c95b5958573bca9a1ad0b419a51d76bb4c521556fbba8539b8d" +checksum = "d5638cbbffb318d440fdb009de019090d8d117dae40de9d10cdb29891ea59eb9" dependencies = [ "alloy-contract", "alloy-primitives", @@ -339,12 +340,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.33.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc4b83cb672156663e6094d098beb509965b7fe684bb3d6e44bb9ca2e9ae714" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -359,13 +360,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "borsh", "serde", @@ -400,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -415,19 +416,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", @@ -441,24 +442,24 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-op-evm" version = "0.31.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -472,7 +473,7 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" version = "0.4.7" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -513,13 +514,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -547,7 +548,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "thiserror 2.0.18", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -603,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -616,7 +617,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "tokio", @@ -629,9 +630,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -640,44 +641,44 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -689,9 +690,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -702,15 +703,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -722,17 +723,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -743,13 +744,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", "thiserror 2.0.18", @@ -757,13 +758,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] @@ -780,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "serde", @@ -791,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -808,9 +809,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a57d1e72b1f9b11e5e71ebdab0569cb02277a462bbea6793fcaebfcd794ae9" +checksum = "1258987fbc82716b5153ec7bb95a8a295e7640871b8f03d8ec7c4000dc80c215" dependencies = [ "alloy-consensus", "alloy-network", @@ -827,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b27f20b5298b76a5a3b7cdbe6bdb184ab1ebd6e120e00dad748867673f5c90" +checksum = "7ffc2a49bca5b73c6964711b57452f6c36a6bcb7f845ab7e9ad05b5a828d0161" dependencies = [ "alloy-consensus", "alloy-network", @@ -845,9 +846,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c7acc40ffbfd37d4113eb619863099f3235d78d044006a1eecb94d8b0b2f1a" +checksum = "94e11ddaddfb98c1ddce737dc440225565b0ae0987ac9ad5e59a85db5904878c" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -865,9 +866,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -885,9 +886,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a09a865ae9e1f05478429ef0d935b16467f35c6e0b02cb10f23f66a3b33fc3" +checksum = "44eb341d0013784da6a39e5bbdc11b95d6744993b12a1c3fd55df795a850dd42" dependencies = [ "alloy-consensus", "alloy-network", @@ -902,9 +903,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb8218544ab635281f1be180a1cfd9b5d549db686faa7e85b3b2c10969819e" +checksum = "82ff16b4166fb90bbe79bd1e49244824fb3cadc6b8cd11e9c8a002c1f8c07492" dependencies = [ "alloy-consensus", "alloy-network", @@ -991,9 +992,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1014,14 +1015,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -1030,9 +1031,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1050,9 +1051,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1085,9 +1086,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1223,13 +1224,13 @@ dependencies = [ [[package]] name = "anvil" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1241,7 +1242,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-beacon", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1276,7 +1277,7 @@ dependencies = [ "parking_lot", "rand 0.8.6", "rand 0.9.4", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -1297,17 +1298,17 @@ dependencies = [ [[package]] name = "anvil-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip5792", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "foundry-common", "foundry-evm", @@ -1321,7 +1322,7 @@ dependencies = [ [[package]] name = "anvil-rpc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "serde", "serde_json", @@ -1329,7 +1330,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.6.0" +version = "1.7.1" dependencies = [ "anvil-rpc", "async-trait", @@ -1669,20 +1670,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ascii-canvas" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" -dependencies = [ - "term", -] - [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1955,9 +1947,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.101.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -2172,9 +2164,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.14" +version = "1.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -2361,9 +2353,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -2579,7 +2571,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -2745,13 +2737,13 @@ dependencies = [ [[package]] name = "cast" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-evm", "alloy-hardforks", @@ -2763,7 +2755,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -2822,9 +2814,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -2832,12 +2824,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -2876,7 +2862,7 @@ dependencies = [ [[package]] name = "chisel" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2890,10 +2876,9 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-solang-parser", "foundry-test-utils", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "rexpect", "rustyline", "semver 1.0.28", @@ -2997,9 +2982,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" dependencies = [ "clap", ] @@ -3042,7 +3027,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3195,7 +3180,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3364,9 +3349,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", @@ -3375,9 +3360,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concurrent-queue" @@ -3425,9 +3410,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -3538,9 +3523,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc-fast" @@ -3821,9 +3806,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" @@ -3973,9 +3958,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.1", @@ -3999,7 +3984,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4187,15 +4172,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "ena" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -4304,7 +4280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4502,15 +4478,15 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "figment2" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4380ce44915a6227efbb61e3885bc1c8e99fb9820f5db612abfac2c5cfc46871" +checksum = "87d63dee16df12076c7770919713c0b92f4e1c85eac828dc2ade0b6c998f016b" dependencies = [ "atomic", "parking_lot", "serde", "tempfile", - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.11+spec-1.1.0", "uncased", "version_check", ] @@ -4607,7 +4583,7 @@ checksum = "932dcfbd51320af5f27f1ba02d2e567dec332cac7d2c221ba45d8e767264c4dc" [[package]] name = "forge" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4661,7 +4637,7 @@ dependencies = [ "rand 0.9.4", "rayon", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4689,7 +4665,7 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "derive_more", @@ -4711,7 +4687,7 @@ dependencies = [ [[package]] name = "forge-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "foundry-common", "foundry-config", @@ -4725,7 +4701,7 @@ dependencies = [ [[package]] name = "forge-lint" -version = "1.6.0" +version = "1.7.1" dependencies = [ "eyre", "foundry-common", @@ -4739,12 +4715,12 @@ dependencies = [ [[package]] name = "forge-script" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-json-abi", "alloy-network", @@ -4787,7 +4763,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-network", "alloy-primitives", @@ -4803,7 +4779,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -4818,7 +4794,7 @@ dependencies = [ [[package]] name = "forge-verify" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4841,7 +4817,7 @@ dependencies = [ "futures", "itertools 0.14.0", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4864,7 +4840,7 @@ dependencies = [ [[package]] name = "foundry-bench" -version = "1.6.0" +version = "1.7.1" dependencies = [ "chrono", "clap", @@ -4889,7 +4865,7 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -4899,7 +4875,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4952,7 +4928,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes-spec" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-types", "foundry-macros", @@ -4963,11 +4939,11 @@ dependencies = [ [[package]] name = "foundry-cli" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-json-abi", "alloy-network", @@ -5007,6 +4983,7 @@ dependencies = [ "tempo-primitives", "tikv-jemallocator", "tokio", + "toml", "tracing", "tracing-subscriber 0.3.23", "tracing-tracy", @@ -5015,7 +4992,7 @@ dependencies = [ [[package]] name = "foundry-cli-markdown" -version = "1.6.0" +version = "1.7.1" dependencies = [ "clap", "pretty_assertions", @@ -5023,12 +5000,12 @@ dependencies = [ [[package]] name = "foundry-common" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -5040,6 +5017,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "alloy-transport", "alloy-transport-ipc", @@ -5047,6 +5025,7 @@ dependencies = [ "anstream 0.6.21", "anstyle", "axum", + "base64 0.22.1", "chrono", "ciborium", "clap", @@ -5064,18 +5043,20 @@ dependencies = [ "futures", "itertools 0.14.0", "jiff", + "k256", "mpp", "num-format", "op-alloy-network", "op-alloy-rpc-types", "path-slash", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "rustls", "semver 1.0.28", "serde", "serde_json", + "sha2 0.10.9", "solar-compiler", "tempfile", "tempo-alloy", @@ -5094,14 +5075,14 @@ dependencies = [ [[package]] name = "foundry-common-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "chrono", "comfy-table", "eyre", @@ -5217,7 +5198,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5257,7 +5238,7 @@ dependencies = [ [[package]] name = "foundry-debugger" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "crossterm", @@ -5275,7 +5256,7 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5312,7 +5293,7 @@ dependencies = [ [[package]] name = "foundry-evm-abi" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -5324,7 +5305,7 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -5339,7 +5320,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "anvil", "auto_impl", @@ -5376,7 +5357,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "eyre", @@ -5392,7 +5373,7 @@ dependencies = [ [[package]] name = "foundry-evm-fuzz" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5417,7 +5398,7 @@ dependencies = [ [[package]] name = "foundry-evm-hardforks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -5432,10 +5413,10 @@ dependencies = [ [[package]] name = "foundry-evm-networks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -5448,11 +5429,11 @@ dependencies = [ [[package]] name = "foundry-evm-sancov" -version = "1.6.0" +version = "1.7.1" [[package]] name = "foundry-evm-traces" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5470,7 +5451,7 @@ dependencies = [ "itertools 0.14.0", "memchr", "rayon", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -5509,7 +5490,7 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "foundry-compilers", @@ -5520,7 +5501,7 @@ dependencies = [ [[package]] name = "foundry-macros" -version = "1.6.0" +version = "1.7.1" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -5530,7 +5511,7 @@ dependencies = [ [[package]] name = "foundry-primitives" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-evm", @@ -5541,7 +5522,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5555,23 +5536,9 @@ dependencies = [ "tempo-revm", ] -[[package]] -name = "foundry-solang-parser" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9645e75b89f977423690f3b4bfd8d84825e5fdabd7803cbce6d4a2c4d54972b4" -dependencies = [ - "itertools 0.14.0", - "lalrpop", - "lalrpop-util", - "phf 0.11.3", - "thiserror 2.0.18", - "unicode-xid", -] - [[package]] name = "foundry-test-utils" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5586,7 +5553,7 @@ dependencies = [ "parking_lot", "rand 0.9.4", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "snapbox", "svm-rs", @@ -5814,7 +5781,7 @@ dependencies = [ "once_cell", "prost 0.14.3", "prost-types 0.14.3", - "reqwest 0.13.2", + "reqwest 0.13.3", "secret-vault-value", "serde", "serde_json", @@ -6188,9 +6155,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" dependencies = [ "typenum", ] @@ -6582,9 +6549,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -6592,7 +6559,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6641,7 +6608,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6694,9 +6661,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -6707,31 +6674,15 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -6741,7 +6692,7 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", "thiserror 2.0.18", @@ -6762,15 +6713,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -6802,9 +6744,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -6841,6 +6783,7 @@ version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", "getrandom 0.2.17", "js-sys", @@ -6923,45 +6866,14 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] -[[package]] -name = "lalrpop" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.14.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "sha3", - "string_cache 0.8.9", - "term", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" -dependencies = [ - "regex-automata", - "rustversion", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -6982,9 +6894,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -6994,12 +6906,11 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.44" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" dependencies = [ "cc", - "libc", ] [[package]] @@ -7299,12 +7210,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -7331,9 +7242,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" dependencies = [ "libmimalloc-sys", ] @@ -7588,7 +7499,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7809,7 +7720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "parking_lot", "stable_deref_trait", ] @@ -7817,7 +7728,7 @@ dependencies = [ [[package]] name = "op-alloy" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "op-alloy-consensus", "op-alloy-network", @@ -7829,15 +7740,15 @@ dependencies = [ [[package]] name = "op-alloy-consensus" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "derive_more", "reth-codecs", @@ -7856,7 +7767,7 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", "alloy-network", @@ -7869,7 +7780,7 @@ dependencies = [ [[package]] name = "op-alloy-provider" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-network", "alloy-primitives", @@ -7883,15 +7794,15 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "op-alloy-consensus", "reth-rpc-traits", @@ -7903,15 +7814,14 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.1", - "derive_more", + "alloy-serde 2.0.4", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", @@ -7924,7 +7834,7 @@ dependencies = [ [[package]] name = "op-revm" version = "19.0.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "auto_impl", "revm", @@ -8142,16 +8052,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap 2.14.0", -] - [[package]] name = "pharos" version = "0.5.3" @@ -8168,7 +8068,6 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -8178,7 +8077,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", + "phf_macros", "phf_shared 0.13.1", "serde", ] @@ -8223,19 +8122,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "phf_macros" version = "0.13.1" @@ -8571,7 +8457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8740,7 +8626,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9091,9 +8977,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -9135,12 +9021,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9155,11 +9041,12 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9173,8 +9060,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -9183,8 +9071,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9196,11 +9084,11 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9209,8 +9097,8 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9233,10 +9121,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "bytes", "modular-bitfield", @@ -9247,11 +9135,11 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9263,8 +9151,8 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -9276,11 +9164,11 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs", @@ -9290,11 +9178,11 @@ dependencies = [ [[package]] name = "reth-evm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "auto_impl", @@ -9312,11 +9200,11 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -9332,8 +9220,8 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-evm", "alloy-primitives", @@ -9345,11 +9233,11 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -9364,8 +9252,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9377,11 +9265,12 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9405,8 +9294,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9420,8 +9309,8 @@ dependencies = [ [[package]] name = "reth-revm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9433,8 +9322,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-evm", @@ -9453,9 +9342,9 @@ dependencies = [ [[package]] name = "reth-rpc-traits" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b766da61ec7c46596386b4bc88d9b57d1939d3da2bc9e927567a8a23650e5ce9" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" dependencies = [ "alloy-consensus", "alloy-network", @@ -9468,8 +9357,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "bytes", @@ -9481,8 +9370,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9495,11 +9384,11 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9518,10 +9407,10 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9536,14 +9425,14 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "arrayvec", "bytes", @@ -9559,8 +9448,9 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] @@ -9843,9 +9733,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -9853,20 +9743,20 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.4.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "5ac5b223d9738ef56e0b98305410be40fa0941bf6036c56f1506751e43552d64" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327b72899159dfae8060c51a1f6aebe955245bcd9cc4997eed0f623caea022e4" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", @@ -9874,9 +9764,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "arbitrary", @@ -10001,14 +9891,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -10034,9 +9924,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -10044,13 +9934,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni 0.21.1", + "jni", "log", "once_cell", "rustls", @@ -10060,7 +9950,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10478,9 +10368,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ "base64 0.22.1", "chrono", @@ -10497,9 +10387,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -10560,14 +10450,14 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -10701,9 +10591,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -10842,7 +10732,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10877,7 +10767,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11011,18 +10901,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -11181,7 +11059,7 @@ checksum = "4572dd9845e37ca0293acb5fe591a7f61b51f1b7b62d3dc6fb8e99e2664f3755" dependencies = [ "const-hex", "dirs", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -11301,22 +11179,22 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tempo-alloy" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-contract", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer-local", "alloy-sol-types", "alloy-transport", @@ -11334,9 +11212,9 @@ dependencies = [ [[package]] name = "tempo-chainspec" version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-hardforks", @@ -11353,7 +11231,7 @@ dependencies = [ [[package]] name = "tempo-consensus" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11371,7 +11249,7 @@ dependencies = [ [[package]] name = "tempo-contracts" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-contract", "alloy-primitives", @@ -11382,7 +11260,7 @@ dependencies = [ [[package]] name = "tempo-evm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11409,7 +11287,7 @@ dependencies = [ [[package]] name = "tempo-precompiles" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "alloy-evm", @@ -11429,7 +11307,7 @@ dependencies = [ [[package]] name = "tempo-precompiles-macros" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "proc-macro2", @@ -11440,15 +11318,15 @@ dependencies = [ [[package]] name = "tempo-primitives" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "aws-lc-rs", "base64 0.22.1", "derive_more", @@ -11471,7 +11349,7 @@ dependencies = [ [[package]] name = "tempo-revm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11502,15 +11380,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "term" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "terminal_size" version = "0.4.4" @@ -11518,7 +11387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11552,9 +11421,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" [[package]] name = "thiserror" @@ -11695,9 +11564,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -11864,9 +11733,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap 2.14.0", + "serde_core", + "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -11875,7 +11746,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -12220,9 +12091,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -12506,9 +12377,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174a690eb3293a5666442b0738d080df9ea6b9e03782bbe78875c89ff914a77c" +checksum = "2d7cb4a83971db3f6ae36f0aa41eaf5985d2e2b469581fa755c132f9c2a1ec89" dependencies = [ "anyhow", "bon", @@ -12519,9 +12390,9 @@ dependencies = [ [[package]] name = "vergen-gitcl" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f628f4acc90a5c1a8136495eaf5f9ef94e03c174d6fb2e6de691bc58fc721ee" +checksum = "bba14c9676943b2899cea2ed7ea194b89b3d13564a3c93a61882a978b123a41c" dependencies = [ "anyhow", "bon", @@ -12533,9 +12404,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390d0442b660baedd7a6f60d2af01bd8967e0d7fe69cd15e13c82c811d82b709" +checksum = "fb684e6d170ef15a9b3c20561779a50ba8c806f8acdaff47c0a2c5c4c6cadd43" dependencies = [ "anyhow", "bon", @@ -12600,11 +12471,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -12613,14 +12484,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -12631,9 +12502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -12641,9 +12512,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12651,9 +12522,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -12664,9 +12535,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -12764,7 +12635,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12804,9 +12675,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -12824,13 +12695,13 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", - "string_cache 0.9.0", + "string_cache", "string_cache_codegen", ] @@ -12841,7 +12712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ "core-foundation 0.10.1", - "jni 0.22.4", + "jni", "log", "ndk-context", "objc2", @@ -12914,7 +12785,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -13035,15 +12906,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -13080,21 +12942,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -13137,12 +12984,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -13155,12 +12996,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -13173,12 +13008,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -13203,12 +13032,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -13221,12 +13044,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -13239,12 +13056,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -13257,12 +13068,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -13286,9 +13091,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -13302,6 +13107,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index c412dad16366c..4db027dd400cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ members = [ resolver = "2" [workspace.package] -version = "1.6.0" +version = "1.7.1" edition = "2024" rust-version = "1.89" authors = ["Foundry Contributors"] @@ -174,9 +174,6 @@ foundry-compilers.opt-level = 3 serde_json.opt-level = 3 serde.opt-level = 3 -foundry-solang-parser.opt-level = 3 -lalrpop-util.opt-level = 3 - solar-compiler.opt-level = 3 solar-ast.opt-level = 3 solar-data-structures.opt-level = 3 @@ -307,40 +304,40 @@ crossterm.opt-level = "s" alloy-json-abi.opt-level = "s" [workspace.dependencies] -anvil = { path = "crates/anvil" } -cast = { path = "crates/cast" } -chisel = { path = "crates/chisel" } -forge = { path = "crates/forge" } - -forge-doc = { path = "crates/doc" } -forge-fmt = { path = "crates/fmt" } -forge-lint = { path = "crates/lint" } -forge-verify = { path = "crates/verify" } -forge-script = { path = "crates/script" } -forge-sol-macro-gen = { path = "crates/sol-macro-gen" } -forge-script-sequence = { path = "crates/script-sequence" } -foundry-cheatcodes = { path = "crates/cheatcodes" } +anvil = { path = "crates/anvil", default-features = false } +cast = { path = "crates/cast", default-features = false } +chisel = { path = "crates/chisel", default-features = false } +forge = { path = "crates/forge", default-features = false } + +forge-doc = { path = "crates/doc", default-features = false } +forge-fmt = { path = "crates/fmt", default-features = false } +forge-lint = { path = "crates/lint", default-features = false } +forge-verify = { path = "crates/verify", default-features = false } +forge-script = { path = "crates/script", default-features = false } +forge-sol-macro-gen = { path = "crates/sol-macro-gen", default-features = false } +forge-script-sequence = { path = "crates/script-sequence", default-features = false } +foundry-cheatcodes = { path = "crates/cheatcodes", default-features = false } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } -foundry-cli = { path = "crates/cli" } +foundry-cli = { path = "crates/cli", default-features = false } foundry-cli-markdown = { path = "crates/cli-markdown" } -foundry-common = { path = "crates/common" } -foundry-common-fmt = { path = "crates/common/fmt" } +foundry-common = { path = "crates/common", default-features = false } +foundry-common-fmt = { path = "crates/common/fmt", default-features = false } foundry-config = { path = "crates/config" } -foundry-debugger = { path = "crates/debugger" } -foundry-evm = { path = "crates/evm/evm" } +foundry-debugger = { path = "crates/debugger", default-features = false } +foundry-evm = { path = "crates/evm/evm", default-features = false } foundry-evm-abi = { path = "crates/evm/abi" } -foundry-evm-core = { path = "crates/evm/core" } -foundry-evm-coverage = { path = "crates/evm/coverage" } -foundry-evm-hardforks = { path = "crates/evm/hardforks" } -foundry-evm-networks = { path = "crates/evm/networks" } -foundry-evm-fuzz = { path = "crates/evm/fuzz" } +foundry-evm-core = { path = "crates/evm/core", default-features = false } +foundry-evm-coverage = { path = "crates/evm/coverage", default-features = false } +foundry-evm-hardforks = { path = "crates/evm/hardforks", default-features = false } +foundry-evm-networks = { path = "crates/evm/networks", default-features = false } +foundry-evm-fuzz = { path = "crates/evm/fuzz", default-features = false } foundry-evm-sancov = { path = "crates/evm/sancov" } -foundry-evm-traces = { path = "crates/evm/traces" } +foundry-evm-traces = { path = "crates/evm/traces", default-features = false } foundry-macros = { path = "crates/macros" } -foundry-test-utils = { path = "crates/test-utils" } +foundry-test-utils = { path = "crates/test-utils", default-features = false } foundry-wallets = { version = "0.1.0", default-features = false } foundry-linking = { path = "crates/linking" } -foundry-primitives = { path = "crates/primitives" } +foundry-primitives = { path = "crates/primitives", default-features = false } # solc & compilation utilities foundry-block-explorers = { version = "0.23.0", default-features = false } @@ -349,7 +346,6 @@ foundry-compilers = { version = "0.20.0", default-features = false, features = [ "svm-solc", ] } foundry-fork-db = { version = "0.26.0", features = ["zstd"] } -solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } solar = { package = "solar-compiler", version = "=0.1.8", default-features = false } svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ "rustls", @@ -410,7 +406,7 @@ op-alloy-rpc-types = "0.24.0" op-alloy-flz = "0.13.1" ## alloy-evm -alloy-evm = "0.33.2" +alloy-evm = "0.34.0" alloy-op-evm = "0.31.0" # revm @@ -515,17 +511,17 @@ mpp = { git = "https://github.com/tempoxyz/mpp-rs", rev = "554d20112eb014bd223d5 "reqwest-rustls-tls", "ws", ] } -tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false, features = [ +tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ "serde", ] } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false, features = [ +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ "serde", ] } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } ## Pinned dependencies. Enabled for the workspace in crates/test-utils. @@ -589,27 +585,21 @@ rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6 ## alloy-evm # alloy-evm = { git = "https://github.com/paradigmxyz/evm.git", rev = "04d8e4a" } -## reth-core -reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-codecs = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-codecs-derive = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } - ## op-revm / op-alloy / alloy-op-evm -op-revm = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-network = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-rpc-types = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -alloy-op-hardforks = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } +op-revm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-network = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-rpc-types = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-hardforks = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } ## foundry-fork-db # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-core", rev = "2f90eb86d4549fa15a8cc2d99bfc1039bc083977" } ## tempo — unify crates.io versions (pulled by mpp) with git rev -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } # solar solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } diff --git a/README.md b/README.md index c9f0a45c57b0a..90cd1d8a7865e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ foundryup See the [installation guide](https://getfoundry.sh/getting-started/installation) for more details. +To verify a downloaded release archive or container image, see [Verifying Releases](./SECURITY.md#verifying-releases). + ## Getting Started Initialize a new project, build and test: diff --git a/SECURITY.md b/SECURITY.md index d84327cc18e91..6296066db5e73 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,3 +3,112 @@ ## Reporting a Vulnerability Contact [security@tempo.xyz](mailto:security@tempo.xyz). + +## Verifying Releases + +Every official Foundry release ships with multiple, independent integrity +artifacts. All signing is keyless via [Sigstore](https://www.sigstore.dev/) — +no Foundry-managed key material is involved, and every signature is recorded +in the public [Rekor](https://docs.sigstore.dev/logging/overview/) transparency +log. The signing identity is the GitHub Actions OIDC token of this repository's +`release.yml` / `docker-publish.yml` workflows. + +### Per-release artifacts + +For each `foundry___.{tar.gz,zip}` archive on the +[releases page](https://github.com/foundry-rs/foundry/releases), the same +release also publishes: + +| Suffix | Purpose | +| --- | --- | +| `.sha256` | SHA-256 checksum of the archive (`sha256sum` format) | +| `.sigstore.json` | Cosign keyless signature bundle (cert + signature + Rekor proof) over the archive | +| `.spdx.json` | SPDX 2.3 SBOM of the source workspace used for the build | +| `.attestation.txt` | URL of the GitHub artifact-attestation summary | + +In addition, GitHub stores SLSA build-provenance and SBOM attestations against +the archive's digest; these are queryable via `gh attestation` without +downloading anything else. + +### Verifying an archive + +Pick whichever toolchain you have available — they verify the same signatures. + +#### Option 1: GitHub CLI (simplest) + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry +``` + +This computes the file's digest, fetches the matching attestation from GitHub, +and verifies the Sigstore signature plus the SLSA provenance predicate. Add +`--signer-workflow foundry-rs/foundry/.github/workflows/release.yml` to also +require the workflow identity. + +To verify the SBOM attestation specifically: + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry \ + --predicate-type 'https://spdx.dev/Document/v2.3' +``` + +#### Option 2: Cosign (offline-friendly) + +Download the archive and its `.sigstore.json` bundle from the release page, +then: + +```bash +cosign verify-blob \ + --bundle foundry_v1.4.0_linux_amd64.sigstore.json \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/release\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ + foundry_v1.4.0_linux_amd64.tar.gz +``` + +For nightly builds the certificate identity points at `refs/heads/master` +instead of a tag; the regex above matches both. + +#### Option 3: Plain checksum (integrity only) + +```bash +sha256sum -c foundry_v1.4.0_linux_amd64.sha256 # GNU coreutils +shasum -a 256 -c foundry_v1.4.0_linux_amd64.sha256 # macOS +``` + +This proves the bytes match what was uploaded, but says nothing about who +uploaded them. Combine with one of the verifications above for end-to-end +trust. + +### Verifying the Docker image + +Container signatures and attestations are pushed as OCI referrers to GHCR, so +no separate files need to be downloaded. + +```bash +# Cosign keyless signature on the image +cosign verify ghcr.io/foundry-rs/foundry:v1.4.0 \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' + +# SLSA build-provenance attestation +gh attestation verify oci://ghcr.io/foundry-rs/foundry:v1.4.0 \ + --repo foundry-rs/foundry + +# Inspect the buildx-attached SBOM and provenance +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .SBOM }}' +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .Provenance }}' +``` + +To pin to an immutable digest (recommended for reproducible deployments): + +```bash +docker pull ghcr.io/foundry-rs/foundry:v1.4.0 +DIGEST=$(docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 --format '{{ .Manifest.Digest }}') +cosign verify "ghcr.io/foundry-rs/foundry@${DIGEST}" \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` diff --git a/benches/LATEST.md b/benches/LATEST.md index 238a691229389..cb75f8d68780b 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,74 +1,108 @@ -# Foundry Benchmark Results +# 📊 Foundry Benchmark Results -**Date**: 2026-04-24 23:10:24 +**Generated at**: 2026-05-02 21:53:46 UTC -## Repositories Tested +## Forge Test + +### Repositories Tested 1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) 4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) -5. [aave/aave-v4](https://github.com/aave/aave-v4) - -## Foundry Versions +### Foundry Versions - **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) -- **nightly**: forge Version: 1.6.0-nightly (a249f5c 2026-04-24) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) -## Forge Test - -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| vectorized-solady | 1.46 s | 1.38 s | -| aave-aave-v4 | 4m 14.2s | 3m 29.1s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.78 s | 0.965 s | +| vectorized-solady | 0.995 s | 0.645 s | +| uniswap-v4-core | 5.97 s | 1.51 s | +| sparkdotfi-spark-psm | 19.98 s | 10.20 s | ## Forge Fuzz Test -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | -------- | -| ithacaxyz-account | 2.81 s | 1.59 s | -| vectorized-solady | 1.40 s | 1.34 s | -| Uniswap-v4-core | 3.01 s | 2.87 s | -| sparkdotfi-spark-psm | 2.04 s | 1.87 s | -| aave-aave-v4 | 3m 46.0s | 3m 17.3s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.54 s | 0.923 s | +| vectorized-solady | 0.929 s | 0.617 s | +| uniswap-v4-core | 6.44 s | 1.40 s | +| sparkdotfi-spark-psm | 2.25 s | 2.03 s | ## Forge Test (Isolated) -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| Uniswap-v4-core | 3.50 s | 3.48 s. | -| aave-aave-v4 | 4m 14.0s | 3m 53.4s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 3.05 s | 1.02 s | +| vectorized-solady | 0.871 s | 0.741 s | +| uniswap-v4-core | 6.81 s | 1.68 s | +| sparkdotfi-spark-psm | 21.96 s | 11.26 s | + +## Forge Build -## Forge Build (No Cache) +### Repositories Tested -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| ithacaxyz-account | 26.06 s | 26.61 s | -| vectorized-solady | 14.20 s | 14.26 s | -| Uniswap-v4-core | 2m 1.3s | 2m 5.0s | -| sparkdotfi-spark-psm | 15.16 s | 15.30 s | -| aave-aave-v4 | 3m 37.0s | 3m 35.1s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +### No Cache + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 34.58 s | 33.29 s | +| vectorized-solady | 14.40 s | 14.41 s | +| uniswap-v4-core | 2m 17.6s | 2m 17.7s | +| sparkdotfi-spark-psm | 12.62 s | 12.61 s | -## Forge Build (With Cache) +### With Cache -| Repository | v1.5.1 | nightly | -| -------------------- | ------- | ------- | -| ithacaxyz-account | 0.167 s | 0.201 s | -| vectorized-solady | 0.099 s | 0.098 s | -| Uniswap-v4-core | 0.139 s | 0.140 s | -| sparkdotfi-spark-psm | 0.168 s | 0.173 s | -| aave-aave-v4 | 0.370 s | 0.357 s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 0.083 s | 0.089 s | +| vectorized-solady | 0.062 s | 0.064 s | +| uniswap-v4-core | 0.071 s | 0.074 s | +| sparkdotfi-spark-psm | 0.066 s | 0.068 s | ## Forge Coverage -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | ---------- | -| Uniswap-v4-core | 1m 13.9s | 1m 10.3s | -| sparkdotfi-spark-psm | 2m 54.7s | 2m 50.0s | -| aave-aave-v4 | 11m 20.8s | 10m 58.7s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [uniswap/v4-core](https://github.com/uniswap/v4-core) +3. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 29.35 s | 18.69 s | +| uniswap-v4-core | 1m 26.8s | 1m 4.1s | +| sparkdotfi-spark-psm | 2m 1.6s | 1m 28.4s | ## System Information -- **OS**: macos -- **CPU**: 12 -- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) \ No newline at end of file + +- **OS**: linux +- **CPU**: 32 +- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) diff --git a/benches/src/main.rs b/benches/src/main.rs index 60e815cecb0ec..8d7134b1c25bc 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -39,9 +39,15 @@ struct Cli { #[clap(long, default_value = ".")] output_dir: PathBuf, - /// Name of the output file (default: LATEST.md) - #[clap(long, default_value = "LATEST.md")] - output_file: String, + /// Name of the output file. Defaults to LATEST.md unless --json-output is set + /// without this flag, in which case no Markdown is written. + #[clap(long)] + output_file: Option, + + /// Filename for a flat JSON summary (benchmark/repo -> mean_seconds). + /// Resolved relative to --output-dir. Used by the nightly regression comparison script. + #[clap(long)] + json_output: Option, /// Run only specific benchmarks (comma-separated: /// forge_test,forge_build_no_cache,forge_build_with_cache,forge_fuzz_test,forge_coverage) @@ -216,12 +222,28 @@ fn main() -> Result<()> { } } - // Generate markdown report - sh_println!("📝 Generating report..."); - let markdown = results.generate_markdown(&versions, &repos); - let output_path = cli.output_dir.join(cli.output_file); - fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; - sh_println!("✅ Report written to: {}", output_path.display()); + // Write Markdown report unless --json-output is set without an explicit --output-file. + let md_filename = match cli.output_file { + Some(f) => Some(f), + None if cli.json_output.is_none() => Some("LATEST.md".to_string()), + None => None, + }; + if let Some(filename) = md_filename { + sh_println!("📝 Generating report..."); + let markdown = results.generate_markdown(&versions, &repos); + let output_path = cli.output_dir.join(filename); + fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; + sh_println!("✅ Report written to: {}", output_path.display()); + } + + if let Some(json_filename) = cli.json_output { + let summary = results.generate_json_summary(&versions); + let json = + serde_json::to_string_pretty(&summary).wrap_err("Failed to serialize JSON summary")?; + let json_path = cli.output_dir.join(json_filename); + fs::write(&json_path, json).wrap_err("Failed to write JSON summary")?; + sh_println!("✅ JSON summary written to: {}", json_path.display()); + } Ok(()) } diff --git a/benches/src/results.rs b/benches/src/results.rs index 447e8ed2766b4..e7d57250fc9a1 100644 --- a/benches/src/results.rs +++ b/benches/src/results.rs @@ -66,6 +66,25 @@ impl BenchmarkResults { self.version_details.insert(version.to_string(), details); } + /// Generate a flat JSON summary mapping `"benchmark/repo" -> mean_seconds`. + /// + /// Used by the nightly regression comparison script. + pub fn generate_json_summary(&self, versions: &[String]) -> HashMap { + let mut summary = HashMap::new(); + for (benchmark_name, version_data) in &self.data { + for version in versions { + if let Some(repo_data) = version_data.get(version) { + for (repo_name, result) in repo_data { + let key = format!("{benchmark_name}/{repo_name}"); + let rounded = (result.mean * 10_000.0).round() / 10_000.0; + summary.insert(key, rounded); + } + } + } + } + summary + } + pub fn generate_markdown(&self, versions: &[String], repos: &[RepoConfig]) -> String { let mut output = String::new(); diff --git a/benchmark.sh b/benchmark.sh index 2faffa93dfa1d..ac6159099069b 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,36 +1,52 @@ #!/bin/bash -versions="v1.3.6,v1.4.0-rc1" +versions="v1.5.1,v1.7.0" # Repositories -export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" -export SOLADY_REPO="Vectorized/solady:v0.1.22" -export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" -export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" +ITHACA_ACCOUNT="ithacaxyz/account:v0.5.7" +SOLADY_REPO="vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test'" +AAVE_V4="aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" +UNISWAP_V4_CORE="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc TickMathTestTest" +SPARK_PSM="sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting" -# Benches -echo "===========FORGE TEST AND BUILD BENCHMARKS===========" +SOLADY_ISOLATE="vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest'" +ITHACA_ISOLATE="ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_test,forge_fuzz_test,forge_build_no_cache,forge_build_with_cache \ - --output-dir ./benches/results \ - --output-file TEST_BUILD.md +SOLADY_BUILD="vectorized/solady:v0.1.26" +UNISWAP_BUILD="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75" +SPARK_PSM_BUILD="sparkdotfi/spark-psm:v1.0.0" -echo "===========FORGE COVERAGE BENCHMARKS===========" +# Benches +echo "===========FORGE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_coverage \ - --output-dir ./benches/results \ - --output-file COVERAGE.md +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_REPO,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ + --benchmarks forge_test,forge_fuzz_test \ + --output-dir ./benches \ + --output-file forge_test_bench.md echo "===========FORGE ISOLATE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ISOLATE,$SOLADY_ISOLATE,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ --benchmarks forge_isolate_test \ - --output-dir ./benches/results \ - --output-file ISOLATE_TEST.md + --output-dir ./benches \ + --output-file forge_isolate_test_bench.md + +echo "===========FORGE BUILD BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_BUILD,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --output-dir ./benches \ + --output-file forge_build_bench.md + +echo "===========FORGE COVERAGE BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_coverage \ + --output-dir ./benches \ + --output-file forge_coverage_bench.md echo "===========BENCHMARKS COMPLETED===========" diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index b664266450d07..d6404b21e2e8e 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -20,15 +20,15 @@ required-features = ["cli"] [dependencies] # foundry internal -anvil-core = { path = "core" } +anvil-core = { path = "core", default-features = false } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } -foundry-cli.workspace = true +foundry-cli = { workspace = true, optional = true } foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } tempo-chainspec.workspace = true tempo-primitives.workspace = true tempo-precompiles.workspace = true @@ -37,7 +37,7 @@ tempo-revm.workspace = true # alloy alloy-evm = { workspace = true, features = ["call-util"] } -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } @@ -63,7 +63,8 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde"] } +op-alloy-consensus = { workspace = true, features = ["serde"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } # revm revm = { workspace = true, features = [ @@ -73,7 +74,7 @@ revm = { workspace = true, features = [ "c-kzg", ] } revm-inspectors.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } # axum related axum.workspace = true @@ -120,17 +121,28 @@ reqwest.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } -op-alloy-rpc-types.workspace = true tempo-alloy.workspace = true [features] -default = ["cli", "jemalloc", "asm-keccak"] +default = ["cli", "jemalloc", "asm-keccak", "optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "anvil-core/optimism", + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli?/optimism", +] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] -jemalloc = ["foundry-cli/jemalloc"] -mimalloc = ["foundry-cli/mimalloc"] -tracy-allocator = ["foundry-cli/tracy-allocator"] +jemalloc = ["foundry-cli?/jemalloc"] +mimalloc = ["foundry-cli?/mimalloc"] +tracy-allocator = ["foundry-cli?/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ + "dep:foundry-cli", "clap", "clap_complete", "dep:fdlimit", diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index cf4b952ecfaa3..8456413a78b1f 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } revm = { workspace = true, default-features = false, features = [ "std", "serde", @@ -39,3 +39,11 @@ bytes.workspace = true # misc rand.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index fb75529b82908..4fd2aabb82a8b 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -12,7 +12,9 @@ use clap::Parser; use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; -use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; +#[cfg(feature = "optimism")] +use foundry_evm::hardfork::OpHardfork; +use foundry_evm::hardfork::{EthereumHardfork, FoundryHardfork}; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::FoundryReceiptEnvelope; use futures::FutureExt; @@ -240,15 +242,7 @@ impl NodeArgs { } let hardfork = match &self.hardfork { - Some(hf) => { - if self.evm.networks.is_optimism() { - Some(OpHardfork::from_str(hf)?.into()) - } else if self.evm.networks.is_tempo() { - Some(TempoHardfork::from_str(hf)?.into()) - } else { - Some(EthereumHardfork::from_str(hf)?.into()) - } - } + Some(hf) => Some(parse_hardfork(hf, &self.evm.networks)?), None => None, }; @@ -849,6 +843,19 @@ impl FromStr for ForkUrl { } } +/// Parses a hardfork string against the active network configuration. +fn parse_hardfork(hf: &str, networks: &NetworkConfigs) -> eyre::Result { + #[cfg(feature = "optimism")] + if networks.is_optimism() { + return Ok(OpHardfork::from_str(hf)?.into()); + } + if networks.is_tempo() { + Ok(TempoHardfork::from_str(hf)?.into()) + } else { + Ok(EthereumHardfork::from_str(hf)?.into()) + } +} + /// Clap's value parser for genesis. Loads a genesis.json file. fn read_genesis_file(path: &str) -> Result { foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 23cd6e61bc076..7c91590c071fa 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -37,7 +37,7 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{FoundryHardfork, OpHardfork}, + hardfork::FoundryHardfork, utils::{ apply_chain_and_block_specific_env_changes, block_env_from_header, get_blob_base_fee_update_fraction, @@ -577,8 +577,9 @@ impl NodeConfig { if let Some(hardfork) = self.hardfork { return hardfork; } + #[cfg(feature = "optimism")] if self.networks.is_optimism() { - return OpHardfork::default().into(); + return foundry_evm::hardforks::OpHardfork::default().into(); } if self.networks.is_tempo() { return TempoHardfork::default().into(); @@ -1079,6 +1080,7 @@ impl NodeConfig { } /// Enable Optimism network features. + #[cfg(feature = "optimism")] #[must_use] pub fn with_optimism(mut self) -> Self { self.networks = NetworkConfigs::with_optimism(); diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 78cce938318c1..4900c18eebd82 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1882,6 +1882,7 @@ impl EthApi { fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { match typed_tx { + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { @@ -2210,9 +2211,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; - debug_assert!(requires != provides); + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -2288,11 +2293,10 @@ impl EthApi { let priority = self.transaction_priority(&pending_transaction.transaction); // Tempo txs use a 2D nonce system — no sequential ordering by account nonce. - let (requires, provides) = if let FoundryTxEnvelope::Tempo(aa_tx) = - pending_transaction.transaction.as_ref() - && !aa_tx.tx().nonce_key.is_zero() + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) { - (vec![], vec![pending_transaction.hash().to_vec()]) + (requires, provides) } else { let on_chain_nonce = self.backend.current_nonce(from).await?; let nonce = pending_transaction.transaction.nonce(); @@ -3192,8 +3196,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -3549,6 +3558,7 @@ impl EthApi { requires: Vec, provides: Vec, ) -> Result { + debug_assert!(requires != provides); let from = *pending_transaction.sender(); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = @@ -3565,7 +3575,9 @@ impl EthApi { FoundryTxEnvelope::Eip1559(_) => self.backend.ensure_eip1559_active(), FoundryTxEnvelope::Eip4844(_) => self.backend.ensure_eip4844_active(), FoundryTxEnvelope::Eip7702(_) => self.backend.ensure_eip7702_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => self.backend.ensure_op_deposits_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => Err(BlockchainError::InvalidTransactionRequest( "not implemented for post-exec tx".to_string(), )), @@ -3634,6 +3646,20 @@ fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> V if on_chain_nonce <= prev_nonce { vec![to_marker(prev_nonce, from)] } else { Vec::new() } } +fn tempo_parallel_nonce_markers( + pending_transaction: &PendingTransaction, +) -> Option<(Vec, Vec)> { + // Tempo txs with non-zero nonce_key use a 2D nonce system and should not + // be sequenced by account nonce markers. + if let FoundryTxEnvelope::Tempo(aa_tx) = pending_transaction.transaction.as_ref() + && !aa_tx.tx().nonce_key.is_zero() + { + Some((vec![], vec![pending_transaction.hash().to_vec()])) + } else { + None + } +} + fn convert_transact_out(out: &Option) -> Bytes { match out { None => Default::default(), diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 614f409c3eb65..c41937055fdd4 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -67,9 +67,11 @@ impl ReceiptBuilder for FoundryReceiptBuilder { FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { unreachable!("deposit receipts are built in commit_transaction") } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt), FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), } @@ -85,7 +87,7 @@ pub struct AnvilTxResult { pub sender: Address, } -impl TxResult for AnvilTxResult { +impl TxResult for AnvilTxResult { type HaltReason = H; fn result(&self) -> &ResultAndState { @@ -217,12 +219,10 @@ where }) } - fn commit_transaction( - &mut self, - output: Self::Result, - ) -> Result { + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { let AnvilTxResult { inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] sender, } = output; @@ -237,6 +237,7 @@ where self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); } + #[cfg(feature = "optimism")] let receipt = if tx_type == FoundryTxType::Deposit { let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); let receipt = alloy_consensus::Receipt { @@ -262,11 +263,19 @@ where cumulative_gas_used: self.gas_used, }) }; + #[cfg(not(feature = "optimism"))] + let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }); self.receipts.push(receipt); self.evm.db_mut().commit(state); - Ok(GasOutput::new(gas_used)) + GasOutput::new(gas_used) } fn finish( @@ -429,7 +438,7 @@ where let exec_result = result.result().result.clone(); let gas_used = result.result().result.tx_gas_used(); - executor.commit_transaction(result).expect("commit failed"); + executor.commit_transaction(result); let traces = executor.evm_mut().inspector_mut().finish_transaction(inspector_config); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index f404031445611..1c8dfffab9d95 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -57,6 +57,7 @@ use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction, }; +#[cfg(feature = "optimism")] use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; use alloy_primitives::{ Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, @@ -108,20 +109,60 @@ use foundry_evm::{ }, }; use foundry_evm_networks::NetworkConfigs; +#[cfg(feature = "optimism")] +use foundry_primitives::get_deposit_tx_parts; use foundry_primitives::{ FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, - FoundryTxReceipt, get_deposit_tx_parts, + FoundryTxReceipt, }; use futures::channel::mpsc::{UnboundedSender, unbounded}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait}; -use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +#[cfg(feature = "optimism")] +use op_revm::{OpSpecId, OpTransaction, transaction::deposit::DepositTransactionParts}; + +/// Side-channel container for OP-specific deposit info produced by +/// [`Backend::build_call_env`] and consumed by the OP transact path. +/// +/// When the `optimism` feature is enabled, this is an alias for +/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized +/// stand-in so the eth/tempo dispatch chain still type-checks. +#[cfg(feature = "optimism")] +type OpCallDepositInfo = DepositTransactionParts; +#[cfg(not(feature = "optimism"))] +#[derive(Default, Clone, Debug)] +struct OpCallDepositInfo; + +/// Marker trait that abstracts over the per-network inspector trait bounds +/// required by the in-memory backend. The OP bound is only included when the +/// `optimism` feature is enabled. +#[cfg(feature = "optimism")] +pub trait BackendInspector: + Inspector> + Inspector> + Inspector> +{ +} +#[cfg(feature = "optimism")] +impl BackendInspector for T where + T: Inspector> + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +pub trait BackendInspector: + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +impl BackendInspector for T where + T: Inspector> + Inspector> +{ +} use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ DatabaseCommit, Inspector, context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, - result::{EVMError, ExecutionResult, HaltReason, Output, ResultAndState}, + result::{ExecutionResult, HaltReason, Output, ResultAndState}, }, database::{CacheDB, DbAccount, WrapDatabaseRef}, interpreter::InstructionResult, @@ -157,6 +198,8 @@ pub mod cache; pub mod fork_db; pub mod in_memory_db; pub mod inspector; +#[cfg(feature = "optimism")] +pub mod optimism; pub mod state; pub mod storage; @@ -419,6 +462,11 @@ impl Backend { self.genesis.timestamp } + /// Returns the configured genesis block number. + pub const fn genesis_number(&self) -> u64 { + self.genesis.number + } + /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { Ok(self.get_account(address).await?.balance) @@ -490,12 +538,21 @@ impl Backend { } /// Returns true if op-stack deposits are active - pub fn is_optimism(&self) -> bool { + #[cfg(feature = "optimism")] + pub const fn is_optimism(&self) -> bool { self.networks.is_optimism() } + /// Returns true if op-stack deposits are active. + /// + /// Always `false` when built without the `optimism` feature. + #[cfg(not(feature = "optimism"))] + pub const fn is_optimism(&self) -> bool { + false + } + /// Returns true if Tempo network mode is active - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.networks.is_tempo() } @@ -589,7 +646,8 @@ impl Backend { } /// Returns an error if op-stack deposits are not active - pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { + #[cfg(feature = "optimism")] + pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { return Ok(()); } @@ -597,7 +655,7 @@ impl Backend { } /// Returns an error if Tempo transactions are not active - pub fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { + pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { if self.is_tempo() { return Ok(()); } @@ -1139,58 +1197,53 @@ impl Backend { db: &'db DB, evm_env: &EvmEnv, inspector: &mut I, - tx_env: OpTransaction, + tx_env: TxEnv, + op_deposit: OpCallDepositInfo, ) -> Result, BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_env = EvmEnv::new( - evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), - evm_env.block_env.clone(), - ); - let mut evm = OpEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - op_env, - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { - EVMError::Database(db) => EVMError::Database(db), - EVMError::Header(h) => EVMError::Header(h), - EVMError::Custom(s) => EVMError::Custom(s), - EVMError::CustomAny(err) => EVMError::CustomAny(err), - EVMError::Transaction(t) => EVMError::Transaction(t), - })?; - Ok(ResultAndState { - result: result.result.map_haltreason(|h| match h { - OpHaltReason::Base(eth) => eth, - _ => HaltReason::PrecompileError, - }), - state: result.state, - }) - } else if self.is_tempo() { - self.transact_tempo_with_inspector_ref( - db, - evm_env, - inspector, - TempoTxEnv::from(tx_env.base), - ) + let op_tx = OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() }; + return self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx); + } + // `op_deposit` only matters on the OP path; eth/tempo ignore it. + let _ = op_deposit; + if self.is_tempo() { + self.transact_tempo_with_inspector_ref(db, evm_env, inspector, TempoTxEnv::from(tx_env)) } else { - let mut evm = EthEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - evm_env.clone(), - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - Ok(evm.transact(tx_env.base)?) + self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env) } } + /// Eth path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an Ethereum EVM, injects precompiles, and transacts with a + /// plain [`TxEnv`]. + fn transact_eth_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TxEnv, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let mut evm = EthEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + evm_env.clone(), + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + Ok(evm.transact(tx_env)?) + } + /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`]. fn transact_envelope_with_inspector_ref<'db, I, DB>( @@ -1203,9 +1256,7 @@ impl Backend { ) -> Result<(ResultAndState, TxEnv), BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { if tx.is_tempo() { @@ -1213,14 +1264,21 @@ impl Backend { FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); let base = tx_env.inner.clone(); let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) - } else { - let tx_env: OpTransaction = + return Ok((result, base)); + } + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_tx: OpTransaction = FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); - let base = tx_env.base.clone(); - let result = self.transact_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) + let base = op_tx.base.clone(); + let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?; + return Ok((result, base)); } + let tx_env: TxEnv = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = tx_env.clone(); + let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?; + Ok((result, base)) } /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`]. @@ -1298,6 +1356,7 @@ impl Backend { }}; } + #[cfg(feature = "optimism")] if self.is_optimism() { let op_env = EvmEnv::new( evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), @@ -1305,8 +1364,10 @@ impl Backend { ); let mut evm = OpEvmFactory::::default().create_evm_with_inspector(db, op_env, inspector); - run!(evm) - } else if self.is_tempo() { + return run!(evm); + } + + if self.is_tempo() { let hardfork = TempoHardfork::from(self.hardfork); let tempo_env = EvmEnv::new( evm_env @@ -1338,7 +1399,7 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> (EvmEnv, OpTransaction) { + ) -> (EvmEnv, TxEnv, OpCallDepositInfo) { let tx_type = request.minimal_tx_type() as u8; let WithOtherFields:: { @@ -1391,7 +1452,7 @@ impl Backend { let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); - let mut base = TxEnv { + let mut tx_env = TxEnv { caller, gas_limit, gas_price, @@ -1413,11 +1474,10 @@ impl Backend { blob_hashes, ..Default::default() }; - base.set_signed_authorization(authorization_list.unwrap_or_default()); - let mut tx_env = OpTransaction { base, ..Default::default() }; + tx_env.set_signed_authorization(authorization_list.unwrap_or_default()); if let Some(nonce) = nonce { - tx_env.base.nonce = nonce; + tx_env.nonce = nonce; } if evm_env.block_env.basefee == 0 { @@ -1427,13 +1487,22 @@ impl Backend { } // Deposit transaction? (only valid when op-stack deposits are active) - if self.ensure_op_deposits_active().is_ok() + #[cfg(feature = "optimism")] + let op_deposit = if self.ensure_op_deposits_active().is_ok() && let Ok(deposit) = get_deposit_tx_parts(&other) { - tx_env.deposit = deposit; - } + deposit + } else { + OpCallDepositInfo::default() + }; + #[cfg(not(feature = "optimism"))] + let op_deposit = { + // `other` carries OP-only deposit fields; consumed only when feature is enabled. + let _ = &other; + OpCallDepositInfo::default() + }; - (evm_env, tx_env) + (evm_env, tx_env, op_deposit) } pub fn call_with_state( @@ -1467,13 +1536,13 @@ impl Backend { (fee_token, nonce_key, valid_before, valid_after) }); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state } = if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides { use tempo_primitives::transaction::Call; - let base = tx_env.base; + let base = tx_env; let mut tempo_tx = TempoTxEnv::from(base.clone()); tempo_tx.fee_token = fee_token; @@ -1495,7 +1564,13 @@ impl Backend { } self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)? } else { - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)? + self.transact_with_inspector_ref( + state, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )? }; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -1518,9 +1593,9 @@ impl Backend { let mut inspector = AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?; + self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env, op_deposit)?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) @@ -2912,7 +2987,7 @@ where TracingInspectorConfig::from_geth_call_config(&call_config), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let ResultAndState { result, state: _ } = self .transact_with_inspector_ref( @@ -2920,6 +2995,7 @@ where &evm_env, &mut inspector, tx_env, + op_deposit, )?; inspector.print_logs(); @@ -2945,13 +3021,14 @@ where ), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env, + op_deposit, )?; Ok(inspector @@ -2973,22 +3050,22 @@ where } #[cfg(feature = "js-tracer")] GethDebugTracerType::JsTracer(code) => { - use alloy_evm::IntoTxEnv; let config = tracer_config.into_json(); let mut inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block.clone()); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env.clone(), + op_deposit, )?; let res = inspector - .json_result(result, &OpTx(tx_env).into_tx_env(), &block, &cache_db) + .json_result(result, &tx_env, &block, &cache_db) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(GethTrace::JS(res)) @@ -3001,9 +3078,14 @@ where .build_inspector() .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block); - let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); + let ResultAndState { result, state: _ } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -3187,10 +3269,7 @@ where f: F, ) -> Result where - for<'a> I: Inspector>>>> - + Inspector>>>> - + Inspector>>>> - + 'a, + for<'a> I: BackendInspector>>> + 'a, for<'a> F: FnOnce(ResultAndState, CacheDB>, I, TxEnv, EvmEnv) -> T, { @@ -3965,7 +4044,7 @@ impl Backend { )? .or_zero_fees(); - let (mut evm_env, tx_env) = self.build_call_env( + let (mut evm_env, tx_env, op_deposit) = self.build_call_env( WithOtherFields::new(request.clone()), fee_details, block_env.clone(), @@ -3986,8 +4065,13 @@ impl Backend { inspector = inspector.with_transfers(); } trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); - let ResultAndState { result, state } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let ResultAndState { result, state } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; trace!(target: "backend", ?result, ?request, "simulate call"); inspector.print_logs(); @@ -4359,7 +4443,10 @@ where } // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system) + #[cfg(feature = "optimism")] let is_deposit_tx = pending.transaction.as_ref().is_deposit(); + #[cfg(not(feature = "optimism"))] + let is_deposit_tx = false; let is_tempo_tx = pending.transaction.as_ref().is_tempo(); let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx { @@ -4475,6 +4562,7 @@ where ); let value = tx.value(); match tx.as_ref() { + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(deposit_tx) => { // Deposit transactions // https://specs.optimism.io/protocol/deposits.html#execution @@ -4538,6 +4626,7 @@ pub fn transaction_build( info: Option, base_fee: Option, ) -> AnyRpcTransaction { + #[cfg(feature = "optimism")] if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() { let dep_tx = deposit_tx; diff --git a/crates/anvil/src/eth/backend/mem/optimism.rs b/crates/anvil/src/eth/backend/mem/optimism.rs new file mode 100644 index 0000000000000..e9a94cc254fb7 --- /dev/null +++ b/crates/anvil/src/eth/backend/mem/optimism.rs @@ -0,0 +1,61 @@ +//! Optimism-specific transact helpers for the in-memory backend. + +use super::Backend; +use crate::eth::error::BlockchainError; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use alloy_network::Network; +use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; +use foundry_evm::backend::DatabaseError; +use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +use revm::{ + DatabaseRef, Inspector, + context::{ + TxEnv, + result::{EVMError, HaltReason, ResultAndState}, + }, + database_interface::WrapDatabaseRef, +}; + +impl Backend { + /// Optimism path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an OP EVM, injects precompiles, transacts, and maps the + /// OP-specific halt reason back to the shared [`HaltReason`]. + pub(super) fn transact_op_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: OpTransaction, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let op_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), + evm_env.block_env.clone(), + ); + let mut evm = OpEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + op_env, + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::CustomAny(err) => EVMError::CustomAny(err), + EVMError::Transaction(t) => EVMError::Transaction(t), + })?; + Ok(ResultAndState { + result: result.result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }), + state: result.state, + }) + } +} diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error/mod.rs similarity index 91% rename from crates/anvil/src/eth/error.rs rename to crates/anvil/src/eth/error/mod.rs index 3b2ada43d7731..482df681b184a 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error/mod.rs @@ -12,7 +12,6 @@ use anvil_rpc::{ response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; -use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, @@ -21,6 +20,9 @@ use serde::Serialize; use tempo_revm::TempoInvalidTransaction; use tokio::time::Duration; +#[cfg(feature = "optimism")] +mod optimism; + pub(crate) type Result = std::result::Result; #[derive(Debug, thiserror::Error)] @@ -163,51 +165,6 @@ where } } -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => match err { - OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), - OpTransactionError::DepositSystemTxPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::HaltedDepositPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), - }, - EVMError::Header(err) => match err { - InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, - InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, - }, - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => { - let op_err: OpTransactionError = err.0; - EVMError::::Transaction(op_err).into() - } - EVMError::Header(err) => EVMError::::Header(err).into(), - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - impl From> for BlockchainError where T: Into, @@ -451,16 +408,6 @@ impl From for InvalidTransactionError { } } -impl From for InvalidTransactionError { - fn from(value: OpTransactionError) -> Self { - match value { - OpTransactionError::Base(err) => err.into(), - OpTransactionError::DepositSystemTxPostRegolith - | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, - OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, - } - } -} /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -577,9 +524,13 @@ impl ToRpcResponseResult for Result { err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), } } - err @ BlockchainError::EvmError(_) => { - RpcError::internal_error_with(err.to_string()) - } + err @ BlockchainError::EvmError(_) => RpcError { + // VM halts are execution failures, not JSON-RPC server faults. REVERT has a + // dedicated code/data path above; other halts, such as invalid opcode, do not. + code: ErrorCode::TransactionRejected, + message: err.to_string().into(), + data: None, + }, err @ BlockchainError::EvmOverrideError(_) => { RpcError::invalid_params(err.to_string()) } diff --git a/crates/anvil/src/eth/error/optimism.rs b/crates/anvil/src/eth/error/optimism.rs new file mode 100644 index 0000000000000..1207fde30a72a --- /dev/null +++ b/crates/anvil/src/eth/error/optimism.rs @@ -0,0 +1,62 @@ +//! Optimism-specific error conversions for [`BlockchainError`] and +//! [`InvalidTransactionError`]. + +use super::{BlockchainError, InvalidTransactionError}; +use op_revm::OpTransactionError; +use revm::context_interface::result::{EVMError, InvalidHeader}; + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => { + let op_err: OpTransactionError = err.0; + EVMError::::Transaction(op_err).into() + } + EVMError::Header(err) => EVMError::::Header(err).into(), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From for InvalidTransactionError { + fn from(value: OpTransactionError) -> Self { + match value { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::DepositSystemTxPostRegolith + | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, + } + } +} diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 4da86933020ae..2ccf65ba721f5 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -155,9 +155,12 @@ impl EthApi { let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block - // considering only post-fork + // considering only post-fork (or post-genesis in non-fork mode) let from = if block_number == 0 { best } else { block_number - 1 }; - let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let to = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let first_page = from >= best; let mut last_page = false; @@ -198,8 +201,11 @@ impl EthApi { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); - // we go from the first post-fork block, up to the tip - let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + // we go from the first post-fork (or post-genesis) block, up to the tip + let first_block = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; @@ -248,7 +254,10 @@ impl EthApi { ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); - let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); + let from = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let to = self.backend.best_number(); for n in (from..=to).rev() { diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index df65822e1eab3..5280987483dd7 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -123,10 +123,10 @@ impl PoolTransaction { impl fmt::Debug for PoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; + write!(fmt, "hash: {:?}, ", self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; write!(fmt, "provides: [{}], ", hex_fmt_many(self.provides.iter()))?; - write!(fmt, "raw tx: {:?}", &self.pending_transaction)?; + write!(fmt, "raw tx: {:?}", self.pending_transaction)?; write!(fmt, "}}")?; Ok(()) } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 3fdf6192c4537..d1736c3093056 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,5 +1,7 @@ use crate::eth::error::BlockchainError; -use alloy_consensus::{Sealed, SignableTransaction}; +#[cfg(feature = "optimism")] +use alloy_consensus::Sealed; +use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::{Network, TxSignerSync}; use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; @@ -130,9 +132,11 @@ impl Signer for DevSigner { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip4844(t.into_signed(sig)) } + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be signed") } @@ -156,7 +160,9 @@ pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)), FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be impersonated") } diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm/mod.rs similarity index 64% rename from crates/anvil/src/evm.rs rename to crates/anvil/src/evm/mod.rs index d1b40ba56ebbd..85e43d371b097 100644 --- a/crates/anvil/src/evm.rs +++ b/crates/anvil/src/evm/mod.rs @@ -2,6 +2,9 @@ use alloy_evm::precompiles::DynPrecompile; use alloy_primitives::Address; use std::fmt::Debug; +#[cfg(feature = "optimism")] +mod optimism; + /// Object-safe trait that enables injecting extra precompiles when using /// `anvil` as a library. pub trait PrecompileFactory: Send + Sync + Unpin + Debug { @@ -15,14 +18,12 @@ mod tests { use crate::PrecompileFactory; use alloy_evm::{ - EthEvm, Evm, EvmEnv, EvmFactory, + EthEvm, Evm, eth::EthEvmContext, precompiles::{DynPrecompile, PrecompilesMap}, }; - use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; - use alloy_primitives::{Address, Bytes, TxKind, U256, address}; + use alloy_primitives::{Address, Bytes, TxKind, address}; use itertools::Itertools; - use op_revm::{OpSpecId, OpTransaction}; use revm::{ Journal, context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, @@ -35,20 +36,19 @@ mod tests { }; // A precompile activated in the `Prague` spec (BLS12-381 G2 map). - const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); + pub(super) const ETH_PRAGUE_PRECOMPILE: Address = + address!("0x0000000000000000000000000000000000000011"); // A precompile activated in the `Osaka` spec (EIP-7951). const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A precompile activated in the `Isthmus` spec. - const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A custom precompile address and payload for testing. - const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); - const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; + pub(super) const PRECOMPILE_ADDR: Address = + address!("0x0000000000000000000000000000000000000071"); + pub(super) const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; #[derive(Debug)] - struct CustomPrecompileFactory; + pub(super) struct CustomPrecompileFactory; impl PrecompileFactory for CustomPrecompileFactory { fn precompiles(&self) -> Vec<(Address, DynPrecompile)> { @@ -109,34 +109,6 @@ mod tests { (tx_env, eth_evm) } - /// Creates a new OP EVM instance. - fn create_op_evm( - _spec: SpecId, - op_spec: OpSpecId, - ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { - let tx = OpTx(OpTransaction:: { - base: TxEnv { - kind: TxKind::Call(PRECOMPILE_ADDR), - data: PAYLOAD.into(), - ..Default::default() - }, - ..Default::default() - }); - - let mut evm = OpEvmFactory::::default().create_evm_with_inspector( - EmptyDB::default(), - EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), - NoOpInspector, - ); - - if op_spec == OpSpecId::ISTHMUS { - evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); - evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); - } - - (tx, evm) - } - #[test] fn build_eth_evm_with_extra_precompiles_osaka_spec() { let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA); @@ -187,38 +159,4 @@ mod tests { assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } - - #[test] - fn build_op_evm_with_extra_precompiles_isthmus_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); - - assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_op_evm_with_extra_precompiles_bedrock_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); - - assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } } diff --git a/crates/anvil/src/evm/optimism.rs b/crates/anvil/src/evm/optimism.rs new file mode 100644 index 0000000000000..526375fec31ea --- /dev/null +++ b/crates/anvil/src/evm/optimism.rs @@ -0,0 +1,87 @@ +//! Optimism-specific EVM helpers. + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use super::super::tests::{ + CustomPrecompileFactory, ETH_PRAGUE_PRECOMPILE, PAYLOAD, PRECOMPILE_ADDR, + }; + use crate::PrecompileFactory; + use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; + use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; + use alloy_primitives::{Address, TxKind, U256, address}; + use itertools::Itertools; + use op_revm::{OpSpecId, OpTransaction}; + use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + inspector::NoOpInspector, + primitives::hardfork::SpecId, + }; + + // A precompile activated in the `Isthmus` spec. + const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + /// Creates a new OP EVM instance. + fn create_op_evm( + _spec: SpecId, + op_spec: OpSpecId, + ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { + let tx = OpTx(OpTransaction:: { + base: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + ..Default::default() + }); + + let mut evm = OpEvmFactory::::default().create_evm_with_inspector( + EmptyDB::default(), + EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), + NoOpInspector, + ); + + if op_spec == OpSpecId::ISTHMUS { + evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); + evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); + } + + (tx, evm) + } + + #[test] + fn build_op_evm_with_extra_precompiles_isthmus_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); + + assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_bedrock_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); + + assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } +} diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 26e587e8b5123..a661e9c765b26 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::{ error::{NodeError, NodeResult}, eth::{ diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index c4879e36d5240..216476c69eeb8 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -11,6 +11,7 @@ mod gas; mod genesis; mod ipc; mod logs; +#[cfg(feature = "optimism")] mod optimism; mod otterscan; mod proof; diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index ab85fc89abf80..a15454fa5593e 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -28,6 +28,38 @@ async fn test_deploy_reverting() { assert!(!receipt.inner.inner.status()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_invalid_opcode_rpc_error_code() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + // Deploy a contract whose runtime bytecode is the invalid opcode 0xfe. + let code = bytes!("60fe60005360016000f3"); + let tx = TransactionRequest::default().from(sender).with_deploy_code(code); + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract = receipt.contract_address.unwrap(); + + for (method, params) in [ + ("eth_call", serde_json::json!([{ "from": sender, "to": contract }, "latest"])), + ("eth_estimateGas", serde_json::json!([{ "from": sender, "to": contract }])), + ] { + let error = rpc_error(&handle.http_endpoint(), method, params).await; + assert_eq!(error["code"], serde_json::json!(-32003), "{error}"); + assert!(error.get("data").is_none(), "{error}"); + + let message = error["message"].as_str().unwrap(); + assert!(message.contains("EVM error InvalidFEOpcode"), "{error}"); + assert!(!message.contains("execution reverted"), "{error}"); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { sol!( @@ -124,3 +156,21 @@ async fn test_solc_revert_custom_errors() { let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); } + +async fn rpc_error(endpoint: &str, method: &str, params: serde_json::Value) -> serde_json::Value { + let response = reqwest::Client::new() + .post(endpoint) + .json(&serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": method, + "params": params, + })) + .send() + .await + .unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let body = response.json::().await.unwrap(); + body.get("error").cloned().unwrap_or_else(|| panic!("expected JSON-RPC error, got {body}")) +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dc5be77a244da..03be26a631a11 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -61,9 +61,9 @@ tempo-contracts.workspace = true tempo-primitives.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-flz.workspace = true -op-alloy-network.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-flz = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } chrono.workspace = true eyre.workspace = true @@ -100,7 +100,7 @@ anvil.workspace = true foundry-test-utils.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -109,3 +109,12 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "dep:op-alloy-flz", + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "foundry-common/optimism", + "foundry-evm-networks/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 6dc2ed6acf430..23fda25c40faa 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -29,6 +29,7 @@ use foundry_common::{ shell, stdin, }; use foundry_evm_networks::NetworkVariant; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; use std::time::Instant; use tempo_alloy::TempoNetwork; @@ -351,6 +352,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let output = if raw || fields.contains(&"raw".into()) { match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -569,6 +571,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); let output = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -791,6 +794,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::DecodeTransaction { tx, network } => { let tx = stdin::unwrap_line(tx)?; let decoded_tx = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { SimpleCast::decode_raw_transaction::(&tx)? } @@ -809,6 +813,9 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::Erc20Token { command } => command.run().await?, CastSubcommand::Tip20Token { command } => command.run().await?, CastSubcommand::Keychain { command } => command.run().await?, + CastSubcommand::Tempo { command } => command.run().await?, + CastSubcommand::VirtualAddress { command } => command.run().await?, + #[cfg(feature = "optimism")] CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index ae5c9668f522f..e25798086d512 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -9,7 +9,7 @@ use crate::{ }; use alloy_consensus::SignableTransaction; use alloy_eips::eip2718::Encodable2718; -use alloy_network::{EthereumWallet, NetworkTransactionBuilder}; +use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder}; use alloy_primitives::Address; use alloy_provider::Provider; use alloy_signer::Signer; @@ -17,7 +17,7 @@ use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use tempo_alloy::TempoNetwork; @@ -53,7 +53,7 @@ pub struct BatchMakeTxArgs { impl BatchMakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { calls, tx, eth, raw_unsigned, ethsign } = self; + let Self { calls, mut tx, eth, raw_unsigned, ethsign } = self; let has_nonce = tx.nonce.is_some(); if calls.is_empty() { @@ -63,6 +63,10 @@ impl BatchMakeTxArgs { let config = eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + // Resolve signer to detect keychain mode let (signer, tempo_access_key) = eth.wallet.maybe_signer().await?; @@ -92,14 +96,14 @@ impl BatchMakeTxArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as raw unsigned. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Set calls on the transaction builder.tx.calls = tempo_calls; @@ -117,6 +121,7 @@ impl BatchMakeTxArgs { let from = eth.wallet.from.unwrap_or(Address::ZERO); let (tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -125,6 +130,7 @@ impl BatchMakeTxArgs { if ethsign { let (tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; return Ok(()); @@ -137,7 +143,9 @@ impl BatchMakeTxArgs { }; let signed_tx = if let Some(ref access_key) = tempo_access_key { - let (tx, _) = tx_builder.build(access_key.wallet_address).await?; + let (tx, _) = + tx_builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = tx .sign_with_access_key( &provider, @@ -151,6 +159,7 @@ impl BatchMakeTxArgs { } else { tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?; let (tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let envelope = tx.build(&EthereumWallet::new(signer)).await?; alloy_primitives::hex::encode(envelope.encoded_2718()) }; diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index ec7254b08e2f5..33128ae897898 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -9,14 +9,14 @@ use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, tx::{self, CastTxBuilder, SendTxOpts}, }; -use alloy_network::EthereumWallet; +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::TransactionOpts, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::provider::ProviderBuilder; use std::time::Duration; @@ -50,7 +50,7 @@ pub struct BatchSendArgs { impl BatchSendArgs { pub async fn run(self) -> Result<()> { - let Self { calls, send_tx, tx, unlocked } = self; + let Self { calls, send_tx, mut tx, unlocked } = self; if calls.is_empty() { return Err(eyre!("No calls specified. Use --call to specify at least one call.")); @@ -59,6 +59,10 @@ impl BatchSendArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -93,14 +97,14 @@ impl BatchSendArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as unlocked. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Access the inner tx and set calls builder.tx.calls = tempo_calls; @@ -116,6 +120,7 @@ impl BatchSendArgs { if unlocked { let (tx, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; cast_send( provider, tx, @@ -132,7 +137,12 @@ impl BatchSendArgs { }; if let Some(ref access_key) = tempo_access_key { - let (tx_request, _) = builder.build(access_key.wallet_address).await?; + let (tx_request, _) = + builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; cast_send_with_access_key( &provider, tx_request, @@ -146,6 +156,10 @@ impl BatchSendArgs { } else { tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?; let (tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() .wallet(wallet) diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 3637f166a53df..63ea17f707e03 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -33,10 +33,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, }, executors::TracingExecutor, opts::EvmOpts, @@ -222,18 +224,19 @@ impl CallArgs { return self.run_curl().await; } if self.tx.tempo.is_tempo() { - self.run_with_network::().await - } else { - let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); - let mut evm_opts = figment.extract::()?; - evm_opts.infer_network_from_fork().await; + return self.run_with_network::().await; + } - if evm_opts.networks.is_optimism() { - self.run_with_network::().await - } else { - self.run_with_network::().await - } + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + evm_opts.infer_network_from_fork().await; + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_network::().await; } + + self.run_with_network::().await } pub async fn run_with_network(self) -> Result<()> diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index 8b7d80786dfad..897a01c39202a 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -1,9 +1,12 @@ use alloy_ens::NameOrAddress; -use alloy_network::EthereumWallet; +use std::time::Duration; + +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, U256, hex, keccak256}; -use alloy_provider::ProviderBuilder as AlloyProviderBuilder; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use alloy_sol_types::SolCall; +use alloy_transport::TransportError; use chrono::DateTime; use clap::Parser; use eyre::Result; @@ -12,11 +15,16 @@ use foundry_cli::{ utils::LoadConfig, }; use foundry_common::{ + FoundryTransactionBuilder, provider::ProviderBuilder, - shell, - tempo::{self, KeyType, KeysFile, WalletType, read_tempo_keys_file, tempo_keys_path}, + sh_warn, shell, + tempo::{ + self, KeyType, KeysFile, TEMPO_BROWSER_GAS_BUFFER, WalletType, read_tempo_keys_file, + tempo_keys_path, + }, }; use foundry_evm::hardfork::TempoHardfork; +use serde::Deserialize; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_contracts::precompiles::{ ACCOUNT_KEYCHAIN_ADDRESS, IAccountKeychain, @@ -24,13 +32,16 @@ use tempo_contracts::precompiles::{ CallScope, KeyInfo, KeyRestrictions, LegacyTokenLimit, SelectorRule, SignatureType, TokenLimit, }, + ITIP20, PATH_USD_ADDRESS, account_keychain::{authorizeKeyCall, legacyAuthorizeKeyCall}, }; use yansi::Paint; +use foundry_cli::utils::{maybe_print_resolved_lane, resolve_lane}; + use crate::{ - cmd::send::{cast_send, cast_send_with_access_key}, - tx::{CastTxBuilder, SendTxOpts}, + cmd::send::cast_send, + tx::{CastTxBuilder, CastTxSender, SendTxOpts}, }; /// Tempo keychain management commands. @@ -62,6 +73,19 @@ pub enum KeychainSubcommand { rpc: RpcOpts, }, + /// Inspect an access key policy using the local key registry and on-chain state. + Inspect { + /// The key address to inspect. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + #[command(flatten)] + rpc: RpcOpts, + }, + /// Authorize a new key on-chain via the AccountKeychain precompile. #[command(visible_alias = "auth")] Authorize { @@ -183,8 +207,92 @@ pub enum KeychainSubcommand { #[command(flatten)] send_tx: SendTxOpts, }, + + /// Read or edit TIP-1011 access-key permissions. + Policy { + #[command(subcommand)] + command: KeychainPolicySubcommand, + }, +} + +/// Higher-level access-key policy editing commands. +#[derive(Debug, Parser)] +pub enum KeychainPolicySubcommand { + /// Add or widen an allowed call rule for a target contract. + AddCall { + /// The key address to update. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + /// Target contract address. + #[arg(long)] + target: Address, + + /// Function selector, full signature, or known TIP-20 shorthand. + #[arg(long, value_parser = parse_selector_arg)] + selector: SelectorArg, + + /// Optional recipient/spender restrictions for selector calls. + #[arg(long, value_delimiter = ',')] + recipients: Vec
, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Update a token spending limit amount for a key. + SetLimit { + /// The key address to update. + key_address: Address, + + /// Token address, numeric TIP-20 token id, or PathUSD. + #[arg(long, value_parser = parse_policy_token)] + token: Address, + + /// New raw token-denominated limit. + #[arg(long)] + amount: U256, + + /// Limit period such as 7d, 24h, or 3600s. + /// + /// The current AccountKeychain update entrypoint cannot change periods, so non-zero + /// values are rejected. + #[arg(long, value_parser = parse_period)] + period: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove all allowed-call rules for a target contract. + RemoveTarget { + /// The key address to update. + key_address: Address, + + /// Target contract address to remove. + #[arg(long)] + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, } +#[derive(Debug, Clone, Copy)] +pub struct SelectorArg([u8; 4]); + fn parse_signature_type(s: &str) -> Result { match s.to_lowercase().as_str() { "secp256k1" => Ok(SignatureType::Secp256k1), @@ -203,6 +311,15 @@ const fn signature_type_name(t: &SignatureType) -> &'static str { } } +const fn signature_type_label(t: &SignatureType) -> &'static str { + match t { + SignatureType::Secp256k1 => "Secp256k1", + SignatureType::P256 => "P256", + SignatureType::WebAuthn => "WebAuthn", + _ => "unknown", + } +} + const fn key_type_name(t: &KeyType) -> &'static str { match t { KeyType::Secp256k1 => "secp256k1", @@ -211,6 +328,14 @@ const fn key_type_name(t: &KeyType) -> &'static str { } } +const fn key_type_label(t: &KeyType) -> &'static str { + match t { + KeyType::Secp256k1 => "Secp256k1", + KeyType::P256 => "P256", + KeyType::WebAuthn => "WebAuthn", + } +} + const fn wallet_type_name(t: &WalletType) -> &'static str { match t { WalletType::Local => "local", @@ -332,6 +457,48 @@ fn parse_selector_bytes(s: &str) -> Result<[u8; 4], String> { } } +fn parse_selector_arg(s: &str) -> Result { + parse_selector_bytes(s).map(SelectorArg) +} + +fn parse_policy_token(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "pathusd" | "path_usd" | "path-usd" | "usd" => Ok(PATH_USD_ADDRESS), + _ => foundry_cli::utils::parse_fee_token_address(s).map_err(|e| e.to_string()), + } +} + +fn parse_period(s: &str) -> Result { + let s = s.trim(); + if s.is_empty() { + return Err("period cannot be empty".to_string()); + } + + let split = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len()); + if split == 0 { + return Err(format!( + "invalid period '{s}': expected a number followed by s, m, h, d, or w" + )); + } + + let value: u64 = + s[..split].parse().map_err(|e| format!("invalid period value '{}': {e}", &s[..split]))?; + let multiplier = match &s[split..].to_ascii_lowercase()[..] { + "" | "s" => 1, + "m" => 60, + "h" => 60 * 60, + "d" => 24 * 60 * 60, + "w" => 7 * 24 * 60 * 60, + unit => { + return Err(format!( + "invalid period unit '{unit}' in '{s}' (expected s, m, h, d, or w)" + )); + } + }; + + value.checked_mul(multiplier).ok_or_else(|| format!("period '{s}' is too large")) +} + /// Represents a single scope entry in JSON format for `--scopes`. #[derive(serde::Deserialize)] struct JsonCallScope { @@ -402,6 +569,9 @@ impl KeychainSubcommand { Self::Check { wallet_address, key_address, rpc } => { run_check(wallet_address, key_address, rpc).await } + Self::Inspect { key_address, root_account, rpc } => { + run_inspect(key_address, root_account, rpc).await + } Self::Authorize { key_address, key_type, @@ -443,6 +613,40 @@ impl KeychainSubcommand { Self::RemoveScope { key_address, target, tx, send_tx } => { run_remove_scope(key_address, target, tx, send_tx).await } + Self::Policy { command } => command.run().await, + } + } +} + +impl KeychainPolicySubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::AddCall { + key_address, + root_account, + target, + selector, + recipients, + tx, + send_tx, + } => { + run_policy_add_call( + key_address, + root_account, + target, + selector.0, + recipients, + tx, + send_tx, + ) + .await + } + Self::SetLimit { key_address, token, amount, period, tx, send_tx } => { + run_policy_set_limit(key_address, token, amount, period, tx, send_tx).await + } + Self::RemoveTarget { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await + } } } } @@ -500,6 +704,143 @@ fn run_show(wallet_address: Address) -> Result<()> { Ok(()) } +#[derive(Debug, Clone)] +struct LocalLimitMetadata { + token: Address, + amount: String, +} + +#[derive(Debug, Clone)] +struct KeyMetadata { + root_account: Address, + key_type: Option, + limits: Vec, +} + +#[derive(Debug, Clone)] +struct InspectedLimit { + token: Address, + configured_amount: Option, + remaining: U256, + period_end: Option, +} + +#[derive(Debug, Clone)] +enum AllowedCallsView { + Unsupported, + Unrestricted, + Scoped(Vec), +} + +/// `cast keychain inspect ` — inspect on-chain key policy. +async fn run_inspect( + key_address: Address, + root_account: Option
, + rpc: RpcOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let info: KeyInfo = provider.get_keychain_key(metadata.root_account, key_address).await?; + let provisioned = info.keyId != Address::ZERO; + let is_t3 = is_tempo_hardfork_active(&provider, TempoHardfork::T3).await?; + + let mut limits = Vec::new(); + if info.enforceLimits { + for local_limit in &metadata.limits { + let (remaining, period_end) = if is_t3 { + let limit = provider + .get_keychain_remaining_limit_with_period( + metadata.root_account, + key_address, + local_limit.token, + ) + .await?; + (limit.remaining, Some(limit.periodEnd)) + } else { + let remaining = provider + .account_keychain() + .getRemainingLimit(metadata.root_account, key_address, local_limit.token) + .call() + .await?; + (remaining, None) + }; + + limits.push(InspectedLimit { + token: local_limit.token, + configured_amount: Some(local_limit.amount.clone()), + remaining, + period_end, + }); + } + } + + let allowed_calls = if is_t3 { + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + if allowed.isScoped { + AllowedCallsView::Scoped(allowed.scopes) + } else { + AllowedCallsView::Unrestricted + } + } else { + AllowedCallsView::Unsupported + }; + + if shell::is_json() { + let key_type = if provisioned { + signature_type_name(&info.signatureType).to_string() + } else { + metadata + .key_type + .map(|key_type| key_type_name(&key_type).to_string()) + .unwrap_or_else(|| "unknown".to_string()) + }; + let json = serde_json::json!({ + "root_account": metadata.root_account.to_string(), + "key_id": key_address.to_string(), + "provisioned": provisioned, + "type": key_type, + "expiry": provisioned.then_some(info.expiry), + "expiry_human": provisioned.then(|| format_expiry_for_inspect(info.expiry)), + "enforce_limits": info.enforceLimits, + "is_revoked": info.isRevoked, + "limits": limits.iter().map(inspected_limit_to_json).collect::>(), + "allowed_calls": allowed_calls_to_json(&allowed_calls), + }); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + let key_type = if provisioned { + signature_type_label(&info.signatureType) + } else { + metadata.key_type.map(|key_type| key_type_label(&key_type)).unwrap_or("unknown") + }; + + sh_println!("Root account: {}", metadata.root_account)?; + sh_println!("Key id: {key_address}")?; + sh_println!("Type: {key_type}")?; + + if info.isRevoked { + sh_println!("Status: revoked")?; + } else if !provisioned { + sh_println!("Status: not provisioned")?; + } else { + sh_println!("Status: active")?; + sh_println!("Expiry: {}", format_expiry_for_inspect(info.expiry))?; + } + + print_inspected_limits(info.enforceLimits, &limits)?; + print_allowed_calls(&allowed_calls)?; + + Ok(()) +} + /// `cast keychain check` / `cast keychain info` — query on-chain key status. async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) -> Result<()> { let config = rpc.load_config()?; @@ -584,7 +925,7 @@ async fn run_authorize( let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let calldata = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let calldata = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { // T3+ authorizeKey(address,SignatureType,KeyRestrictions) let restrictions = KeyRestrictions { expiry, @@ -634,7 +975,7 @@ async fn run_remaining_limit( let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let remaining: U256 = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let remaining: U256 = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { provider.get_keychain_remaining_limit(wallet_address, key_address, token).await? } else { // Pre-T3: use the legacy getRemainingLimit(address,address,address) @@ -646,7 +987,7 @@ async fn run_remaining_limit( }; if shell::is_json() { - sh_println!("{}", serde_json::to_string(&remaining.to_string())?)?; + sh_println!("{}", serde_json::json!({ "remaining": remaining.to_string() }))?; } else { sh_println!("{remaining}")?; } @@ -695,6 +1036,88 @@ async fn run_remove_scope( send_keychain_tx(calldata, tx_opts, &send_tx).await } +/// `cast keychain policy add-call` — merge a selector rule into a target scope. +async fn run_policy_add_call( + key_address: Address, + root_account: Option
, + target: Address, + selector: [u8; 4], + recipients: Vec
, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if !is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + eyre::bail!("allowed-call policy editing requires the Tempo T3 hardfork"); + } + + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + + let new_rule = SelectorRule { selector: selector.into(), recipients }; + let existing_target = allowed + .isScoped + .then(|| allowed.scopes.into_iter().find(|scope| scope.target == target)) + .flatten(); + + let (target_scope, changed) = match existing_target { + Some(mut scope) => { + if scope.selectorRules.is_empty() { + sh_warn!( + "Allowed calls for {} already allow any selector; leaving wildcard scope unchanged", + address_label_with_address(target) + )?; + } + let changed = add_selector_rule_to_scope(&mut scope, new_rule); + (scope, changed) + } + None => (CallScope { target, selectorRules: vec![new_rule] }, true), + }; + + if !changed { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ "status": "already_present", "target": target.to_string() }) + )?; + } else { + sh_println!("Allowed call already present for {}", address_label_with_address(target))?; + } + return Ok(()); + } + + let calldata = + IAccountKeychain::setAllowedCallsCall { keyId: key_address, scopes: vec![target_scope] } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain policy set-limit` — update a spending limit amount. +async fn run_policy_set_limit( + key_address: Address, + token: Address, + amount: U256, + period: Option, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + if period.is_some_and(|period| period != 0) { + eyre::bail!( + "--period is not supported by the current AccountKeychain updateSpendingLimit \ + precompile; periods can only be set when authorizing a key" + ); + } + + // updateSpendingLimit authorizes against msg.sender; the root account is not part of calldata. + run_update_limit(key_address, token, amount, tx_opts, send_tx).await +} + /// Shared helper to send a keychain precompile transaction. async fn send_keychain_tx( calldata: Vec, @@ -702,16 +1125,22 @@ async fn send_keychain_tx( send_tx: &SendTxOpts, ) -> Result<()> { let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let print_sponsor_hash = tx_opts.tempo.print_sponsor_hash; + let tempo_sponsor = + if print_sponsor_hash { None } else { tx_opts.tempo.sponsor_config().await? }; let config = send_tx.eth.load_config()?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); let provider = ProviderBuilder::::from_config(&config)?.build()?; - // Inject key_id for correct gas estimation with keychain signature overhead. - if let Some(ref ak) = tempo_access_key { - tx_opts.tempo.key_id = Some(ak.key_address); + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx_opts.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx_opts.tempo, &config.root)?; + let builder = CastTxBuilder::new(&provider, tx_opts, &config) .await? .with_to(Some(NameOrAddress::Address(ACCOUNT_KEYCHAIN_ADDRESS))) @@ -719,27 +1148,70 @@ async fn send_keychain_tx( .with_code_sig_and_args(None, Some(hex::encode_prefixed(&calldata)), vec![]) .await?; - if let Some(ref ak) = tempo_access_key { - let signer = - signer.as_ref().ok_or_else(|| eyre::eyre!("signer required for access key"))?; - let (tx, _) = builder.build(ak.wallet_address).await?; - cast_send_with_access_key( - &provider, - tx, - signer, - ak, - send_tx.cast_async, - send_tx.confirmations, - timeout, - ) - .await?; + // Keychain management calls are authorized by the root account. Access keys can use their + // permissions, but cannot mutate their own key policy. + let browser = send_tx.browser.run::().await?; + + if print_sponsor_hash { + let from = if let Some(ref browser) = browser { + browser.address() + } else { + signer + .as_ref() + .ok_or_else(|| { + eyre::eyre!( + "--tempo.print-sponsor-hash requires a root account signer, such as \ + --browser, --private-key, or --keystore" + ) + })? + .address() + }; + + let (tx, _) = builder.build(from).await?; + let hash = tx + .compute_sponsor_hash(from) + .ok_or_else(|| eyre::eyre!("This network does not support sponsored transactions"))?; + if shell::is_json() { + sh_println!("{}", serde_json::json!({ "sponsor_hash": format!("{hash:?}") }))?; + } else { + sh_println!("{hash:?}")?; + } + return Ok(()); + } + + if let Some(browser) = browser { + let chain = builder.chain(); + let (mut tx, _) = builder.build(browser.address()).await?; + if chain.is_tempo() + && let Some(gas) = tx.gas_limit() + { + tx.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, browser.address()).await?; + } + + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&provider) + .print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout) + .await?; + } else if tempo_access_key.is_some() { + eyre::bail!( + "keychain policy changes must be signed by the root account; the selected `--from` \ + resolved to a Tempo access key. Use `--browser` for passkey roots, or pass a root \ + account signer with `--private-key`, `--keystore`, Ledger, Trezor, AWS, GCP, or Turnkey." + ); } else { let signer = match signer { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (mut tx, _) = builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() @@ -753,6 +1225,361 @@ async fn send_keychain_tx( Ok(()) } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct AnvilNodeInfo { + hard_fork: Option, + network: Option, +} + +async fn is_tempo_hardfork_active

(provider: &P, hardfork: TempoHardfork) -> Result +where + P: Provider, +{ + match provider.is_hardfork_active(hardfork).await { + Ok(active) => Ok(active), + Err(err) if is_rpc_method_not_found(&err) => { + match anvil_tempo_hardfork_active(provider, hardfork).await { + Ok(Some(active)) => Ok(active), + _ => Err(err.into()), + } + } + Err(err) => Err(err.into()), + } +} + +async fn anvil_tempo_hardfork_active

( + provider: &P, + hardfork: TempoHardfork, +) -> Result, TransportError> +where + P: Provider, +{ + let info = provider.raw_request::<_, AnvilNodeInfo>("anvil_nodeInfo".into(), ()).await?; + Ok(active_from_anvil_node_info(&info, hardfork)) +} + +fn active_from_anvil_node_info(info: &AnvilNodeInfo, hardfork: TempoHardfork) -> Option { + (info.network.as_deref() == Some("tempo")).then(|| { + info.hard_fork + .as_deref() + .and_then(|active_hardfork| active_hardfork.parse::().ok()) + .is_some_and(|active_hardfork| active_hardfork >= hardfork) + }) +} + +fn is_rpc_method_not_found(err: &TransportError) -> bool { + err.as_error_resp().is_some_and(|payload| payload.code == -32601) +} + +fn resolve_key_metadata( + key_address: Address, + root_account: Option

, +) -> Result { + let keys_file = read_tempo_keys_file(); + + if let Some(root_account) = root_account { + if let Some(keys_file) = keys_file.as_ref() + && let Some(entry) = keys_file.keys.iter().find(|entry| { + entry.wallet_address == root_account + && key_entry_effective_key(entry) == key_address + }) + { + return Ok(key_metadata_from_entry(entry)); + } + + return Ok(KeyMetadata { root_account, key_type: None, limits: Vec::new() }); + } + + let Some(keys_file) = keys_file.as_ref() else { + eyre::bail!( + "key {key_address} was not found because the local keys file could not be read at {}; pass --root-account", + tempo_keys_path_display() + ); + }; + + let matches: Vec<_> = keys_file + .keys + .iter() + .filter(|entry| key_entry_effective_key(entry) == key_address) + .collect(); + + if matches.is_empty() { + eyre::bail!( + "key {key_address} was not found in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let root_account = matches[0].wallet_address; + if matches.iter().any(|entry| entry.wallet_address != root_account) { + eyre::bail!( + "key {key_address} matches multiple root accounts in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let entry = + matches.iter().copied().find(|entry| !entry.limits.is_empty()).unwrap_or(matches[0]); + Ok(key_metadata_from_entry(entry)) +} + +fn key_entry_effective_key(entry: &tempo::KeyEntry) -> Address { + entry.key_address.unwrap_or(entry.wallet_address) +} + +fn key_metadata_from_entry(entry: &tempo::KeyEntry) -> KeyMetadata { + KeyMetadata { + root_account: entry.wallet_address, + key_type: Some(entry.key_type), + limits: entry + .limits + .iter() + .map(|limit| LocalLimitMetadata { token: limit.currency, amount: limit.limit.clone() }) + .collect(), + } +} + +fn tempo_keys_path_display() -> String { + tempo_keys_path() + .map(|path| path.display().to_string()) + .unwrap_or_else(|| "(unknown)".to_string()) +} + +fn add_selector_rule_to_scope(scope: &mut CallScope, rule: SelectorRule) -> bool { + if scope.selectorRules.is_empty() { + return false; + } + + let Some(existing_rule) = + scope.selectorRules.iter_mut().find(|existing| existing.selector == rule.selector) + else { + scope.selectorRules.push(rule); + return true; + }; + + if existing_rule.recipients.is_empty() { + return false; + } + + if rule.recipients.is_empty() { + existing_rule.recipients = Vec::new(); + return true; + } + + let mut changed = false; + for recipient in rule.recipients { + if !existing_rule.recipients.contains(&recipient) { + existing_rule.recipients.push(recipient); + changed = true; + } + } + changed +} + +fn inspected_limit_to_json(limit: &InspectedLimit) -> serde_json::Value { + serde_json::json!({ + "token": limit.token.to_string(), + "token_label": address_label(limit.token), + "configured_amount": limit.configured_amount.as_deref(), + "remaining": limit.remaining.to_string(), + "period_end": limit.period_end, + "period_end_human": limit.period_end.and_then(|period_end| { + (period_end != 0).then(|| format_period_end(period_end)) + }), + }) +} + +fn allowed_calls_to_json(allowed_calls: &AllowedCallsView) -> serde_json::Value { + match allowed_calls { + AllowedCallsView::Unsupported => serde_json::json!({ + "mode": "unsupported", + "scopes": [], + }), + AllowedCallsView::Unrestricted => serde_json::json!({ + "mode": "any", + "scopes": [], + }), + AllowedCallsView::Scoped(scopes) => serde_json::json!({ + "mode": if scopes.is_empty() { "none" } else { "scoped" }, + "scopes": scopes.iter().map(call_scope_to_json).collect::>(), + }), + } +} + +fn call_scope_to_json(scope: &CallScope) -> serde_json::Value { + serde_json::json!({ + "target": scope.target.to_string(), + "target_label": address_label(scope.target), + "selectors": scope.selectorRules.iter().map(selector_rule_to_json).collect::>(), + }) +} + +fn selector_rule_to_json(rule: &SelectorRule) -> serde_json::Value { + serde_json::json!({ + "selector": selector_hex(&rule.selector.0), + "signature": selector_signature(&rule.selector.0), + "recipients": rule.recipients.iter().map(ToString::to_string).collect::>(), + }) +} + +fn print_inspected_limits(enforce_limits: bool, limits: &[InspectedLimit]) -> Result<()> { + if !enforce_limits { + sh_println!("Limits: none")?; + return Ok(()); + } + + sh_println!("Limits:")?; + if limits.is_empty() { + sh_println!(" enforced, but no local limit metadata was found")?; + return Ok(()); + } + + for limit in limits { + let configured = limit.configured_amount.as_deref().unwrap_or("unknown"); + let period = limit + .period_end + .and_then(|period_end| { + (period_end != 0).then(|| format!(" ({})", format_period_end(period_end))) + }) + .unwrap_or_default(); + sh_println!( + " {}: {} / {} remaining{}", + address_label(limit.token), + limit.remaining, + configured, + period + )?; + } + + Ok(()) +} + +fn print_allowed_calls(allowed_calls: &AllowedCallsView) -> Result<()> { + match allowed_calls { + AllowedCallsView::Unsupported => sh_println!("Allowed calls: unsupported before T3")?, + AllowedCallsView::Unrestricted => sh_println!("Allowed calls: any")?, + AllowedCallsView::Scoped(scopes) if scopes.is_empty() => { + sh_println!("Allowed calls: none")?; + } + AllowedCallsView::Scoped(scopes) => { + sh_println!("Allowed calls:")?; + for scope in scopes { + sh_println!(" {}:", address_label_with_address(scope.target))?; + if scope.selectorRules.is_empty() { + sh_println!(" any selector")?; + continue; + } + + for rule in &scope.selectorRules { + sh_println!( + " {} -> {}", + format_selector(&rule.selector.0), + format_recipients(&rule.recipients) + )?; + } + } + } + } + + Ok(()) +} + +fn address_label(address: Address) -> String { + if address == PATH_USD_ADDRESS { "PathUSD".to_string() } else { address.to_string() } +} + +fn address_label_with_address(address: Address) -> String { + if address == PATH_USD_ADDRESS { format!("PathUSD ({address})") } else { address.to_string() } +} + +fn format_selector(selector: &[u8; 4]) -> String { + selector_signature(selector).map(str::to_string).unwrap_or_else(|| selector_hex(selector)) +} + +fn selector_signature(selector: &[u8; 4]) -> Option<&'static str> { + if selector == &ITIP20::transferCall::SELECTOR { + Some("transfer(address,uint256)") + } else if selector == &ITIP20::approveCall::SELECTOR { + Some("approve(address,uint256)") + } else if selector == &ITIP20::transferFromCall::SELECTOR { + Some("transferFrom(address,address,uint256)") + } else if selector == &ITIP20::transferWithMemoCall::SELECTOR { + Some("transferWithMemo(address,uint256,bytes32)") + } else if selector == &ITIP20::transferFromWithMemoCall::SELECTOR { + Some("transferFromWithMemo(address,address,uint256,bytes32)") + } else if selector == &ITIP20::mintCall::SELECTOR { + Some("mint(address,uint256)") + } else if selector == &ITIP20::burnCall::SELECTOR { + Some("burn(uint256)") + } else { + None + } +} + +fn selector_hex(selector: &[u8; 4]) -> String { + hex::encode_prefixed(selector) +} + +fn format_recipients(recipients: &[Address]) -> String { + if recipients.is_empty() { + return "any recipient".to_string(); + } + + let recipients = recipients.iter().map(ToString::to_string).collect::>().join(", "); + format!("recipients [{recipients}]") +} + +fn format_expiry_for_inspect(expiry: u64) -> String { + if expiry == u64::MAX { + return "never".to_string(); + } + + format!("{} ({})", format_timestamp_iso(expiry), format_relative_timestamp(expiry)) +} + +fn format_period_end(period_end: u64) -> String { + format!("period resets {}", format_relative_timestamp(period_end)) +} + +fn format_timestamp_iso(timestamp: u64) -> String { + DateTime::from_timestamp(timestamp as i64, 0) + .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) + .unwrap_or_else(|| timestamp.to_string()) +} + +fn format_relative_timestamp(timestamp: u64) -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + if timestamp == now { + "now".to_string() + } else if timestamp > now { + format!("in {}", format_duration_words(timestamp - now)) + } else { + format!("{} ago", format_duration_words(now - timestamp)) + } +} + +fn format_duration_words(seconds: u64) -> String { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + const DAY: u64 = 24 * HOUR; + + if seconds >= DAY { + let days = seconds / DAY; + if days == 1 { "1 day".to_string() } else { format!("{days} days") } + } else if seconds >= HOUR { + format!("{}h", seconds / HOUR) + } else if seconds >= MINUTE { + format!("{}m", seconds / MINUTE) + } else { + format!("{seconds}s") + } +} + fn format_expiry(expiry: u64) -> String { if expiry == u64::MAX { return "never".to_string(); @@ -842,6 +1669,7 @@ fn key_entry_to_json(entry: &tempo::KeyEntry) -> serde_json::Value { #[cfg(test)] mod tests { use super::*; + use alloy_json_rpc::ErrorPayload; use std::str::FromStr; #[test] @@ -967,4 +1795,144 @@ mod tests { let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":[{"selector":"transfer","recipients":[],"bogus":true}]}]"#; assert!(parse_scopes_json(json).is_err()); } + + #[test] + fn test_parse_policy_token_path_usd() { + assert_eq!(parse_policy_token("PathUSD").unwrap(), PATH_USD_ADDRESS); + assert_eq!(parse_policy_token("path-usd").unwrap(), PATH_USD_ADDRESS); + } + + #[test] + fn test_parse_period_units() { + assert_eq!(parse_period("0").unwrap(), 0); + assert_eq!(parse_period("30s").unwrap(), 30); + assert_eq!(parse_period("5m").unwrap(), 300); + assert_eq!(parse_period("2h").unwrap(), 7200); + assert_eq!(parse_period("7d").unwrap(), 604800); + assert_eq!(parse_period("2w").unwrap(), 1209600); + assert!(parse_period("1mo").is_err()); + } + + #[test] + fn test_add_selector_rule_merges_recipients() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let second = Address::from_str("0x2222222222222222222222222222222222222222").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![second], + }, + ); + + assert!(changed); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].recipients, vec![first, second]); + } + + #[test] + fn test_add_selector_rule_empty_recipients_widens_to_any() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(changed); + assert!(scope.selectorRules[0].recipients.is_empty()); + } + + #[test] + fn test_add_selector_rule_target_wildcard_is_unchanged() { + let mut scope = CallScope { target: PATH_USD_ADDRESS, selectorRules: vec![] }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(!changed); + assert!(scope.selectorRules.is_empty()); + } + + #[test] + fn test_policy_set_limit_parses() { + let key = "0x1111111111111111111111111111111111111111"; + + let command = KeychainSubcommand::try_parse_from([ + "keychain", + "policy", + "set-limit", + key, + "--token", + "PathUSD", + "--amount", + "123", + ]) + .unwrap(); + + match command { + KeychainSubcommand::Policy { + command: + KeychainPolicySubcommand::SetLimit { key_address, token, amount, period, .. }, + } => { + assert_eq!(key_address, Address::from_str(key).unwrap()); + assert_eq!(token, PATH_USD_ADDRESS); + assert_eq!(amount, U256::from(123)); + assert_eq!(period, None); + } + other => panic!("unexpected command: {other:?}"), + } + } + + #[test] + fn test_active_from_anvil_node_info_requires_tempo_network() { + let tempo_t3 = + AnvilNodeInfo { network: Some("tempo".to_string()), hard_fork: Some("T3".to_string()) }; + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T2), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T3), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T4), Some(false)); + + let ethereum_t3 = AnvilNodeInfo { + network: Some("ethereum".to_string()), + hard_fork: Some("T3".to_string()), + }; + assert_eq!(active_from_anvil_node_info(ðereum_t3, TempoHardfork::T3), None); + } + + #[test] + fn test_rpc_method_not_found_detection() { + let method_missing: TransportError = + TransportError::ErrorResp(ErrorPayload::method_not_found()); + assert!(is_rpc_method_not_found(&method_missing)); + + let internal_error: TransportError = + TransportError::ErrorResp(ErrorPayload::internal_error()); + assert!(!is_rpc_method_not_found(&internal_error)); + + let transport_error = alloy_transport::TransportErrorKind::backend_gone(); + assert!(!is_rpc_method_not_found(&transport_error)); + } } diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index 8aaf6e97a1827..67178cd093d7b 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -2,7 +2,9 @@ use crate::tx::{self, CastTxBuilder}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network, NetworkTransactionBuilder}; +use alloy_network::{ + Ethereum, EthereumWallet, Network, NetworkTransactionBuilder, TransactionBuilder, +}; use alloy_primitives::{Address, hex}; use alloy_provider::Provider; use alloy_signer::{Signature, Signer}; @@ -10,7 +12,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::LoadConfig, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use std::{path::PathBuf, str::FromStr}; @@ -94,9 +96,13 @@ impl MakeTxArgs { N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; + let Self { to, mut sig, mut args, command, mut tx, path, eth, raw_unsigned, ethsign } = + self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -117,6 +123,11 @@ impl MakeTxArgs { let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + // Must happen before `tx.clone()` so the cloned tx carries the resolved nonce_key. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) .await? .with_to(to) @@ -139,6 +150,10 @@ impl MakeTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + if raw_unsigned { // Build unsigned raw tx // Check if nonce is provided when --from is not specified @@ -148,11 +163,20 @@ impl MakeTxArgs { "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" ); } + if tempo_sponsor.is_some() && eth.wallet.from.is_none() { + eyre::bail!( + "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender" + ); + } // Use zero address as placeholder for unsigned transactions let from = eth.wallet.from.unwrap_or(Address::ZERO); - let (tx, _) = tx_builder.build(from).await?; + let (mut tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -162,7 +186,11 @@ impl MakeTxArgs { if ethsign { // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has // unlocked accounts. - let (tx, _) = tx_builder.build(config.sender).await?; + let (mut tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, config.sender).await?; + } let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; @@ -176,7 +204,11 @@ impl MakeTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = tx_builder.build(&signer).await?; + let (mut tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let tx = tx.build(&EthereumWallet::new(signer)).await?; diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 6a9d11f5dc61d..0b1b26615694a 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -15,6 +15,7 @@ pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; +#[cfg(feature = "optimism")] pub mod da_estimate; pub mod erc20; pub mod estimate; @@ -28,7 +29,9 @@ pub mod rpc; pub mod run; pub mod send; pub mod storage; +pub mod tempo; pub mod tip20; pub mod trace; pub mod txpool; +pub mod vaddr; pub mod wallet; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 84699d6cd6956..7e52a9e265f25 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -29,10 +29,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock as _, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, }, executors::{EvmError, Executor, TracingExecutor}, hardforks::FoundryHardfork, @@ -123,12 +125,15 @@ impl RunArgs { evm_opts.infer_network_from_fork().await; if evm_opts.networks.is_tempo() { - self.run_with_evm::().await - } else if evm_opts.networks.is_optimism() { - self.run_with_evm::().await - } else { - self.run_with_evm::().await + return self.run_with_evm::().await; + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_evm::().await; } + + self.run_with_evm::().await } async fn run_with_evm(self) -> Result<()> { diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 2d6e248cc7a73..421aae1f2153e 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -8,7 +8,10 @@ use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::{Signature, Signer}; use clap::Parser; use eyre::{Result, eyre}; -use foundry_cli::{opts::TransactionOpts, utils::LoadConfig}; +use foundry_cli::{ + opts::TransactionOpts, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; use foundry_common::{ FoundryTransactionBuilder, fmt::{UIfmt, UIfmtReceiptExt}, @@ -119,7 +122,9 @@ impl SendTxArgs { self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; - let sponsor_signature = tx.tempo.sponsor_signature; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -183,6 +188,8 @@ impl SendTxArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -202,13 +209,19 @@ impl SendTxArgs { // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. if print_sponsor_hash { - // Use the pre-resolved signer to derive the actual sender address, since the - // sponsor hash commits to the sender. - let signer = pre_resolved_signer.as_ref().ok_or_else(|| { - eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") - })?; - let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (tx, from) = if let Some(ref ak) = access_key { + let (tx, _) = builder.build_with_access_key(ak.wallet_address, ak).await?; + (tx, ak.wallet_address) + } else { + // Use the pre-resolved signer to derive the actual sender address, since the + // sponsor hash commits to the sender. + let signer = pre_resolved_signer.as_ref().ok_or_else(|| { + eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") + })?; + let from = signer.address(); + let (tx, _) = builder.build(from).await?; + (tx, from) + }; let hash = tx .compute_sponsor_hash(from) .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?; @@ -216,6 +229,10 @@ impl SendTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); // Launch browser signer if `--browser` flag is set @@ -245,11 +262,18 @@ impl SendTxArgs { } } - let (tx, _) = builder.build(config.sender).await?; + let (mut tx_request, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, config.sender).await?; + } cast_send( provider, - tx, + tx_request, send_tx.cast_async, send_tx.sync, send_tx.confirmations, @@ -261,6 +285,10 @@ impl SendTxArgs { } else if let Some(browser) = browser { let chain = builder.chain(); let (mut tx_request, _) = builder.build(browser.address()).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which // costs more gas for signature verification on Tempo chains. Add a @@ -270,6 +298,9 @@ impl SendTxArgs { { tx_request.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, browser.address()).await?; + } let tx_hash = browser.send_transaction_via_browser(tx_request).await?; @@ -283,7 +314,14 @@ impl SendTxArgs { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; - let (tx_request, _) = builder.build(ak.wallet_address).await?; + let (mut tx_request, _) = builder.build_with_access_key(ak.wallet_address, &ak).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, ak.wallet_address).await?; + } cast_send_with_access_key( &provider, tx_request, @@ -308,11 +346,13 @@ impl SendTxArgs { tx::validate_from_address(send_tx.eth.wallet.from, from)?; let (mut tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; - // Apply sponsor signature after gas estimation so the estimate is - // consistent with what `--tempo.print-sponsor-hash` computes. - if let Some(sig) = sponsor_signature { - tx_request.set_fee_payer_signature(sig); + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, from).await?; } let wallet = EthereumWallet::from(signer); diff --git a/crates/cast/src/cmd/tempo.rs b/crates/cast/src/cmd/tempo.rs new file mode 100644 index 0000000000000..6744e6b73c039 --- /dev/null +++ b/crates/cast/src/cmd/tempo.rs @@ -0,0 +1,45 @@ +use clap::Parser; +use eyre::Result; +use foundry_common::tempo::{EnsureAccessKeyConfig, ensure_access_key}; + +/// Tempo wallet integration commands. +#[derive(Debug, Parser)] +pub enum TempoSubcommand { + /// Authorize a new access key against your Tempo wallet via wallet.tempo. + /// + /// Persists the key to `$TEMPO_HOME/wallet/keys.toml` (default + /// `~/.tempo/wallet/keys.toml`). Also runs automatically on a 402 from a + /// Tempo RPC when no local key is configured. + /// + /// Env: `TEMPO_HOME`, `TEMPO_CLI_AUTH_URL` (override auth service). + Login { + /// Chain ID to authorize the key for. Defaults to Tempo mainnet (4217). + #[arg(long, default_value_t = 4217)] + chain_id: u64, + + /// Print the authorization URL to stderr instead of opening a browser. + #[arg(long)] + no_browser: bool, + }, +} + +impl TempoSubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::Login { chain_id, no_browser } => { + let mut cfg = EnsureAccessKeyConfig::from_env(chain_id); + if no_browser { + cfg.no_browser = true; + } + let outcome = ensure_access_key(cfg).await?; + let _ = foundry_common::sh_println!( + "Authorized key {} for wallet {} on chain {}", + outcome.key_address, + outcome.wallet_address, + outcome.chain_id, + ); + Ok(()) + } + } + } +} diff --git a/crates/cast/src/cmd/tip20/mine.rs b/crates/cast/src/cmd/tip20/mine.rs index a5f9062482a01..0367450a19b06 100644 --- a/crates/cast/src/cmd/tip20/mine.rs +++ b/crates/cast/src/cmd/tip20/mine.rs @@ -20,11 +20,11 @@ use tempo_primitives::{MasterId, TempoAddressExt, UserTag}; const POW_BYTES: usize = 4; -pub(super) struct Output { - pub(super) salt: B256, - pub(super) registration_hash: B256, - pub(super) master_id: MasterId, - pub(super) zero_tag_virtual_address: Address, +pub(crate) struct Output { + pub(crate) salt: B256, + pub(crate) registration_hash: B256, + pub(crate) master_id: MasterId, + pub(crate) zero_tag_virtual_address: Address, } pub(super) fn run( @@ -127,7 +127,12 @@ pub(super) async fn register( Ok(()) } -fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Result { +pub(crate) fn mine( + master: Address, + salt: B256, + n_threads: usize, + pow_bytes: usize, +) -> Result { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); @@ -144,7 +149,7 @@ fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Resu .ok_or_else(|| eyre::eyre!("virtual master mining failed: all threads panicked")) } -fn derive(master: Address, salt: B256) -> Output { +pub(crate) fn derive(master: Address, salt: B256) -> Output { let registration_hash = registration_hash(master, salt); let master_id = MasterId::from_slice(®istration_hash[4..8]); let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); @@ -152,14 +157,14 @@ fn derive(master: Address, salt: B256) -> Output { Output { salt, registration_hash, master_id, zero_tag_virtual_address } } -fn registration_hash(master: Address, salt: B256) -> B256 { +pub(crate) fn registration_hash(master: Address, salt: B256) -> B256 { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); packed[20..].copy_from_slice(salt.as_slice()); keccak256(packed) } -fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { +pub(crate) fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { registration_hash[..pow_bytes].iter().all(|byte| *byte == 0) } diff --git a/crates/cast/src/cmd/tip20/mod.rs b/crates/cast/src/cmd/tip20/mod.rs index e3c39b2c9bb18..edb4c3b7a57b3 100644 --- a/crates/cast/src/cmd/tip20/mod.rs +++ b/crates/cast/src/cmd/tip20/mod.rs @@ -6,7 +6,7 @@ use std::str::FromStr; mod create; pub(crate) use create::iso4217_warning_message; -mod mine; +pub(crate) mod mine; /// TIP-20 token operations (Tempo). #[derive(Debug, Parser, Clone)] diff --git a/crates/cast/src/cmd/vaddr/create.rs b/crates/cast/src/cmd/vaddr/create.rs new file mode 100644 index 0000000000000..0563b09601323 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/create.rs @@ -0,0 +1,181 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + tip20::mine, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::{provider::ProviderBuilder, shell}; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use serde_json::json; +use std::time::Instant; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + owner: Address, + salt: Option, + tag: u64, + count: u32, + threads: Option, + seed: Option, + no_random: bool, + no_register: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + if count == 0 { + // no virtual addresses to compute + return Ok(()); + } + + if !owner.is_valid_master() { + eyre::bail!( + "invalid owner address {owner}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + let output = if let Some(salt) = salt { + let output = mine::derive(owner, salt); + if !mine::has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + output + } else { + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut start_salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut start_salt[..]); + } + + if !shell::is_json() { + sh_println!("Mining TIP-1022 salt for {owner} with {n_threads} threads...")?; + } + let timer = Instant::now(); + let output = mine::mine(owner, start_salt, n_threads, POW_BYTES)?; + if !shell::is_json() { + sh_println!("Found salt in {:?}", timer.elapsed())?; + } + output + }; + + const MAX_USER_TAG: u64 = 0x0000_FFFF_FFFF_FFFF; + let mut virtual_addresses = Vec::with_capacity(count as usize); + for i in 0..count { + let tag_value = tag + .checked_add(i as u64) + .filter(|&t| t <= MAX_USER_TAG) + .ok_or_else(|| eyre::eyre!("tag overflow: tag + count exceeds the 6-byte user tag range (max {MAX_USER_TAG:#x})"))?; + let raw = tag_value.to_be_bytes(); + let user_tag = UserTag::new(raw[2..].try_into().expect("slice is 6 bytes")); + let vaddr = Address::new_virtual(output.master_id, user_tag); + virtual_addresses.push((user_tag, vaddr)); + } + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "salt": format!("{}", output.salt), + "registration_hash": format!("{}", output.registration_hash), + "master_id": format!("{}", output.master_id), + "virtual_addresses": virtual_addresses.iter().map(|(tag, addr)| json!({ + "tag": format!("{tag}"), + "address": format!("{addr}"), + })).collect::>(), + }))? + )?; + } else { + sh_println!( + "Salt: {} +Registration hash: {} +Master ID: {}", + output.salt, + output.registration_hash, + output.master_id, + )?; + sh_println!("\nVirtual addresses:")?; + for (tag, vaddr) in &virtual_addresses { + sh_println!(" tag={tag} {vaddr}")?; + } + } + + if no_register { + return Ok(()); + } + + register(owner, output.salt, send_tx, tx_opts).await +} + +async fn register( + owner: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!("cast vaddr create requires a signer (for example --private-key or --from)") + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != owner { + eyre::bail!( + "signer mismatch: salt is for {owner}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt})...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/mod.rs b/crates/cast/src/cmd/vaddr/mod.rs new file mode 100644 index 0000000000000..446d1e7ece5e2 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/mod.rs @@ -0,0 +1,131 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use foundry_cli::opts::RpcOpts; + +mod create; +mod resolve; +mod watch; + +/// TIP-1022 virtual address registry operations (Tempo). +/// +/// Virtual addresses are deterministic 20-byte aliases (masterId || VIRTUAL_MAGIC || userTag) +/// that auto-forward TIP-20 deposits to a registered master wallet at the protocol level, +/// with no on-chain sweep transaction required. +/// +/// See: +#[derive(Debug, Parser, Clone)] +pub enum VaddrSubcommand { + /// Mine a TIP-1022 proof-of-work salt, register as a virtual address master, and print + /// derived virtual addresses for the given owner. + #[command(visible_alias = "c")] + Create { + /// The master (owner) address that will control all virtual addresses under this + /// registration. Must not be the zero address, a virtual address, or a TIP-20 token. + #[arg(long, value_name = "ADDRESS")] + owner: Address, + + /// Use this salt directly instead of mining one. Must satisfy the 32-bit PoW requirement. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Starting user tag for the derived virtual address output (hex-encoded 6 bytes). + #[arg(long, default_value = "0", value_name = "U64")] + tag: u64, + + /// Number of virtual addresses to derive and print. + #[arg(long, default_value = "1", value_name = "N")] + count: u32, + + /// Number of threads to use for mining. Defaults to number of logical cores. + #[arg(long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// Seed for the random number generator used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Start salt search from zero instead of a random value. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Mine and print the salt and derived virtual addresses without submitting the + /// registerVirtualMaster transaction. + #[arg(long)] + no_register: bool, + + #[command(flatten)] + send_tx: Box, + + #[command(flatten)] + tx: Box, + }, + + /// Resolve a virtual address to its registered master and decode its components. + #[command(visible_alias = "r")] + Resolve { + /// The virtual address to resolve. + #[arg(value_name = "ADDRESS")] + addr: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Watch (tail) incoming TIP-20 transfers to a virtual address. + #[command(visible_alias = "w")] + Watch { + /// The virtual address to monitor. + #[arg(value_name = "ADDRESS")] + addr: Address, + + /// Filter on a specific TIP-20 token address. Watches all tokens if omitted. + #[arg(long, value_name = "ADDRESS")] + token: Option
, + + /// Block number to start from. Defaults to the current latest block. + #[arg(long, value_name = "BLOCK")] + from_block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, +} + +impl VaddrSubcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + send_tx, + tx, + } => { + create::run( + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + *send_tx, + *tx, + ) + .await? + } + Self::Resolve { addr, rpc } => resolve::run(addr, rpc).await?, + Self::Watch { addr, token, from_block, rpc } => { + watch::run(addr, token, from_block, rpc).await? + } + } + Ok(()) + } +} diff --git a/crates/cast/src/cmd/vaddr/resolve.rs b/crates/cast/src/cmd/vaddr/resolve.rs new file mode 100644 index 0000000000000..96936f4fe4713 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/resolve.rs @@ -0,0 +1,52 @@ +use alloy_primitives::{Address, hex}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; + +pub(super) async fn run(addr: Address, rpc: RpcOpts) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let registry = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider); + + let decode_builder = registry.decodeVirtualAddress(addr); + let resolve_builder = registry.resolveVirtualAddress(addr); + let (decoded, master) = tokio::try_join!(decode_builder.call(), resolve_builder.call())?; + + if !decoded.isVirtual { + sh_println!("{addr} is not a virtual address")?; + return Ok(()); + } + + let master_id = decoded.masterId; + let user_tag = decoded.userTag; + let master: Address = master; + + if shell::is_json() { + let master_address = if master.is_zero() { None } else { Some(format!("{master}")) }; + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "address": format!("{addr}"), + "master_id": format!("0x{}", hex::encode(master_id)), + "user_tag": format!("0x{}", hex::encode(user_tag)), + "master_address": master_address, + }))? + )?; + } else { + sh_println!("Virtual address: {addr}")?; + sh_println!("Master ID: 0x{}", hex::encode(master_id))?; + sh_println!("User tag: 0x{}", hex::encode(user_tag))?; + if master.is_zero() { + sh_println!("Master address: (unregistered)")?; + } else { + sh_println!("Master address: {master}")?; + } + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/watch.rs b/crates/cast/src/cmd/vaddr/watch.rs new file mode 100644 index 0000000000000..dc159d5e37c8e --- /dev/null +++ b/crates/cast/src/cmd/vaddr/watch.rs @@ -0,0 +1,108 @@ +use alloy_primitives::{Address, B256, keccak256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use std::sync::LazyLock; +use tempo_alloy::TempoNetwork; +use tempo_primitives::TempoAddressExt; + +static TRANSFER_TOPIC: LazyLock = + LazyLock::new(|| keccak256(b"Transfer(address,address,uint256)")); + +pub(super) async fn run( + addr: Address, + token: Option
, + from_block: Option, + rpc: RpcOpts, +) -> Result<()> { + if !addr.is_virtual() { + eyre::bail!("{addr} is not a virtual address"); + } + + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Transfer(address indexed from, address indexed to, uint256 value) + // topic[0] = event sig, topic[1] = from, topic[2] = to + let to_topic: B256 = { + let mut buf = [0u8; 32]; + buf[12..].copy_from_slice(addr.as_slice()); + buf.into() + }; + + let start = from_block.map(BlockNumberOrTag::Number).unwrap_or(BlockNumberOrTag::Latest); + + let mut filter = + Filter::new().event_signature(*TRANSFER_TOPIC).topic2(to_topic).from_block(start); + + if let Some(tok) = token { + filter = filter.address(tok); + } + + if !shell::is_json() { + sh_println!("Watching transfers to {addr}... (Ctrl-C to stop)")?; + } + + // Fetch logs from the requested start block (historical when from_block is set) + let logs = provider.get_logs(&filter).await?; + for log in &logs { + print_transfer_log(log)?; + } + + // Poll for new logs + let mut last_block = provider.get_block_number().await?; + loop { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let current = provider.get_block_number().await?; + if current > last_block { + let poll_filter = filter.clone().from_block(last_block + 1).to_block(current); + let new_logs = provider.get_logs(&poll_filter).await?; + for log in &new_logs { + print_transfer_log(log)?; + } + last_block = current; + } + } +} + +fn print_transfer_log(log: &alloy_rpc_types::Log) -> Result<()> { + let block = log.block_number.unwrap_or(0); + let tx = log.transaction_hash.unwrap_or_default(); + let token = log.address(); + + // Decode topics: topic[1]=from, topic[2]=to + let from = log.topics().get(1).map(|t| { + let mut addr = [0u8; 20]; + addr.copy_from_slice(&t[12..]); + Address::from(addr) + }); + + // Decode amount from data + let amount = if log.data().data.len() >= 32 { + alloy_primitives::U256::from_be_slice(&log.data().data[..32]) + } else { + alloy_primitives::U256::ZERO + }; + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string(&json!({ + "block": block, + "tx": format!("{tx}"), + "token": format!("{token}"), + "from": from.map(|a| format!("{a}")).unwrap_or_default(), + "amount": amount.to_string(), + }))? + )?; + } else { + sh_println!( + "block={block} tx={tx} token={token} from={} amount={amount}", + from.map(|a| a.to_string()).unwrap_or_default(), + )?; + } + Ok(()) +} diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 8e0dd8dd3ed8c..b2378d8bfdc58 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -779,8 +779,7 @@ flag to set your key via: )?; let address = wallet.address(); let success_message = format!( - "`{}` keystore was saved successfully. Address: {:?}", - &account_name, address, + "`{account_name}` keystore was saved successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } @@ -815,7 +814,7 @@ flag to set your key via: format!("Failed to remove keystore file at {}", keystore_path.display()) })?; - let success_message = format!("`{}` keystore was removed successfully.", &name); + let success_message = format!("`{name}` keystore was removed successfully."); sh_println!("{}", success_message.green())?; } Self::PrivateKey { @@ -886,8 +885,7 @@ flag to set your key via: let private_key = B256::from_slice(&wallet.credential().to_bytes()); - let success_message = - format!("{}'s private key is: {}", &account_name, private_key); + let success_message = format!("{account_name}'s private key is: {private_key}"); sh_println!("{}", success_message.green())?; } @@ -945,10 +943,9 @@ flag to set your key via: Some(&account_name), )?; + let address = wallet.address(); let success_message = format!( - "Password for keystore `{}` was changed successfully. Address: {:?}", - &account_name, - wallet.address(), + "Password for keystore `{account_name}` was changed successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ce5572acebc13..2b1b03486bf04 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -40,6 +40,7 @@ use foundry_common::{ use foundry_config::Chain; use foundry_evm::core::bytecode::InstIter; use futures::{FutureExt, StreamExt, future::Either}; +#[cfg(feature = "optimism")] use op_alloy_consensus as _; use rayon::prelude::*; @@ -60,6 +61,7 @@ pub use foundry_evm::*; pub mod args; pub mod cmd; pub mod opts; +pub mod tempo; pub mod base; pub mod call_spec; @@ -246,7 +248,7 @@ impl + Clone + Unpin, N: Network> Cast { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", &al.address.to_checksum(None))); + s.push(format!("- address: {}", al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index effc081e8072a..763eb132ddb5c 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,11 +1,13 @@ +#[cfg(feature = "optimism")] +use crate::cmd::da_estimate::DAEstimateArgs; use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, batch_mktx::BatchMakeTxArgs, batch_send::BatchSendArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, - da_estimate::DAEstimateArgs, erc20::Erc20Subcommand, estimate::EstimateArgs, - find_block::FindBlockArgs, interface::InterfaceArgs, keychain::KeychainSubcommand, - logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, - storage::StorageArgs, tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, + erc20::Erc20Subcommand, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, keychain::KeychainSubcommand, logs::LogsArgs, mktx::MakeTxArgs, + rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, tempo::TempoSubcommand, + tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, vaddr::VaddrSubcommand, wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; @@ -1163,6 +1165,7 @@ pub enum CastSubcommand { command: TxPoolSubcommands, }, /// Estimates the data availability size of a given opstack block. + #[cfg(feature = "optimism")] #[command(name = "da-estimate")] DAEstimate(DAEstimateArgs), @@ -1186,6 +1189,20 @@ pub enum CastSubcommand { #[command(subcommand)] command: KeychainSubcommand, }, + + /// Tempo wallet integration (login, etc.). + Tempo { + #[command(subcommand)] + command: TempoSubcommand, + }, + + /// TIP-1022 virtual address registry operations (Tempo). + #[command(visible_alias = "vaddr")] + VirtualAddress { + #[command(subcommand)] + command: VaddrSubcommand, + }, + #[command(name = "trace")] Trace(TraceArgs), } diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs new file mode 100644 index 0000000000000..737c33f5b70de --- /dev/null +++ b/crates/cast/src/tempo.rs @@ -0,0 +1,3 @@ +//! Tempo transaction helpers used by Cast-facing commands. + +pub use foundry_common::tempo::{TempoSponsor, TempoSponsorPreview, resolve_tempo_sponsor_signer}; diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 96f5fc5137575..b58136f4ae9de 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -20,7 +20,7 @@ use foundry_common::{ get_pretty_receipt_w_reason_attr, shell, }; use foundry_config::{Chain, Config}; -use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner}; +use foundry_wallets::{BrowserWalletOpts, TempoAccessKeyConfig, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration}; @@ -535,13 +535,29 @@ where sender: impl Into>, ) -> Result<(N::TransactionRequest, Option)> { let fill = self.fill; - self._build(sender, fill).await + self._build(sender, fill, None).await + } + + /// Builds a transaction that will be signed by a Tempo access key. + /// + /// The access-key id is set before gas estimation. If the access key needs on-chain + /// provisioning, its authorization is embedded before access-list/gas estimation and before + /// any sponsor digest can be computed. + pub async fn build_with_access_key( + mut self, + sender: impl Into>, + access_key: &TempoAccessKeyConfig, + ) -> Result<(N::TransactionRequest, Option)> { + self.tx.set_key_id(access_key.key_address); + let fill = self.fill; + self._build(sender, fill, Some(access_key)).await } async fn _build( mut self, sender: impl Into>, fill: bool, + access_key: Option<&TempoAccessKeyConfig>, ) -> Result<(N::TransactionRequest, Option)> { // prepare let sender = sender.into(); @@ -555,6 +571,16 @@ where // resolve let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; self.resolve_auth(&sender, tx_nonce).await?; + if let Some(access_key) = access_key { + self.tx + .prepare_access_key_authorization( + &self.provider, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + } self.resolve_access_list().await?; // fill diff --git a/crates/cast/tests/cli/keychain.rs b/crates/cast/tests/cli/keychain.rs new file mode 100644 index 0000000000000..88e9e16983cc5 --- /dev/null +++ b/crates/cast/tests/cli/keychain.rs @@ -0,0 +1,76 @@ +//! CLI tests for `cast keychain` subcommands. + +use anvil::NodeConfig; +use foundry_test_utils::util::OutputExt; + +/// Anvil test accounts (standard mnemonic). +mod accounts { + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + pub const TOKEN: &str = "0x20C000000000000000000000b9537d11c60E8b50"; // PathUSD +} + +// `cast keychain rl --json` must emit `{"remaining":""}`, not a bare string. +casttest!(keychain_rl_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "rl", + accounts::ADDR1, + accounts::ADDR2, + accounts::TOKEN, + "--rpc-url", + &rpc, + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain rl --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + assert!( + parsed.get("remaining").is_some(), + "expected 'remaining' key in JSON output, got: {output}" + ); + // Must not be a bare string (old bug: `"0"`) + assert!(!parsed.is_string(), "JSON output must not be a bare string, got: {output}"); +}); + +// `cast keychain authorize --tempo.print-sponsor-hash --json` must emit +// `{"sponsor_hash":"0x..."}`, not a raw hex string. +casttest!(keychain_authorize_sponsor_hash_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "authorize", + accounts::ADDR2, // key to authorize + "--private-key", + accounts::PK1, + "--rpc-url", + &rpc, + "--tempo.print-sponsor-hash", + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain authorize --tempo.print-sponsor-hash --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + let hash = parsed + .get("sponsor_hash") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("expected 'sponsor_hash' key in JSON output, got: {output}")); + assert!(hash.starts_with("0x"), "sponsor_hash should be 0x-prefixed, got: {hash}"); + assert_eq!(hash.len(), 66, "sponsor_hash should be 32-byte hex (66 chars), got: {hash}"); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index da33a34d849db..2f744efe4d4f0 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,6 +1,7 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_eips::Decodable2718; use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{B256, Bytes, U256, address, b256, hex}; @@ -20,11 +21,13 @@ use foundry_test_utils::{ }; use serde_json::json; use std::{fs, path::Path, str::FromStr}; +use tempo_primitives::TempoTxEnvelope; #[macro_use] extern crate foundry_test_utils; mod erc20; +mod keychain; mod selectors; casttest!(print_short_version, |_prj, cmd| { @@ -2055,6 +2058,55 @@ casttest!(mktx_ethsign, async |_prj, cmd| { ]]); }); +// tests that `cast mktx --tempo.lane ` resolves the lane against a `tempo.lanes.toml` file at +// the project root, sets the corresponding `nonce_key` on the produced Tempo AA transaction. +casttest!(mktx_tempo_lane_resolves_nonce_key, |prj, cmd| { + // Write a shared lanes file at the project root. + let lanes_path = prj.root().join("tempo.lanes.toml"); + fs::write(&lanes_path, "deploy = 1\nops = 2\npayments = 42\n").unwrap(); + + let output = cmd + .current_dir(prj.root()) + .args([ + "mktx", + "--tempo.lane", + "payments", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]) + .assert_success() + .get_output() + .clone(); + + // The resolved-lane breadcrumb is printed to stderr so it doesn't pollute stdout + // (which carries the raw signed transaction). + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("lane: payments (nonce_key=42, nonce=0)"), + "expected lane breadcrumb on stderr, got: {stderr}", + ); + + // Decode the produced signed Tempo AA transaction and verify it carries the + // resolved 2D nonce key. + let stdout = String::from_utf8_lossy(&output.stdout); + let raw_hex = stdout.trim().trim_start_matches("0x"); + let raw = hex::decode(raw_hex).expect("decode hex output"); + let envelope = TempoTxEnvelope::decode_2718(&mut raw.as_slice()).expect("decode tempo tx"); + assert!(envelope.is_aa(), "expected Tempo AA transaction, got: {envelope:?}"); + assert_eq!(envelope.nonce_key(), Some(U256::from(42_u64))); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -4024,6 +4076,7 @@ Warning: Contract code is empty }); // +#[cfg(feature = "optimism")] casttest!(tx_raw_opstack_deposit, |_prj, cmd| { cmd.args([ "tx", @@ -5020,6 +5073,7 @@ casttest!(cast_decode_tx_network_flag_short_and_long_equivalent, |_prj, cmd| { // Test that `--network optimism` and `-n optimism` produce identical output for decode-tx. // Uses a known OP-stack deposit transaction (same tx as tx_raw_opstack_deposit test). +#[cfg(feature = "optimism")] casttest!(cast_decode_tx_network_optimism_short_and_long_equivalent, |_prj, cmd| { let tx = "0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; @@ -5076,3 +5130,68 @@ casttest!(run_evm_version_updates_gas_params, |_prj, cmd| { "expected Spurious Dragon gas (177241), got: {sd_output}" ); }); + +// Tests for `cast vaddr` JSON output +casttest!(vaddr_create_json_output, |_prj, cmd| { + // Use a pre-computed salt that satisfies the 4-byte PoW requirement for this owner. + // Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 + // Owner: 0x1234567890123456789012345678901234567890 + let out = cmd + .args([ + "--json", + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + "--count", + "2", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let v: serde_json::Value = serde_json::from_str(out.trim()).expect("valid JSON"); + assert_eq!(v["salt"], "0x0000000000000000000000000000000000000000000000003ee0a78d00000000"); + assert_eq!( + v["registration_hash"], + "0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396" + ); + assert_eq!(v["master_id"], "0x2f51c0c4"); + let addrs = v["virtual_addresses"].as_array().expect("array"); + assert_eq!(addrs.len(), 2); + assert_eq!(addrs[0]["tag"], "0x000000000000"); + assert_eq!( + addrs[0]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000000" + ); + assert_eq!(addrs[1]["tag"], "0x000000000001"); + assert_eq!( + addrs[1]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000001" + ); +}); + +casttest!(vaddr_create_plain_output, |_prj, cmd| { + cmd.args([ + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + ]) + .assert_success() + .stdout_eq(str![[r#" +Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 +Registration hash: 0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396 +Master ID: 0x2f51c0c4 + +Virtual addresses: + tag=0x000000000000 [..] + +"#]]); +}); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 659fec7f1a333..0eab12331be04 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -68,3 +68,13 @@ tracing.workspace = true walkdir.workspace = true proptest.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", + "forge-script-sequence/optimism", +] diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 94974301df8ac..01de77b9c95fd 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5447,7 +5447,7 @@ { "func": { "id": "expectEmit_0", - "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", @@ -5487,7 +5487,7 @@ { "func": { "id": "expectEmit_2", - "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit() external;", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 7c2e0741704b3..12cfd19017770 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1082,6 +1082,9 @@ interface Vm { /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; @@ -1093,6 +1096,9 @@ interface Vm { /// Prepare an expected log with all topic and data checks enabled. /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit() external; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index b22f76714dd0f..27545c1b6cd33 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -856,42 +856,6 @@ impl Cheatcodes { } } - // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { - let ctx = MockCallDataContext { - calldata: call.input.bytes(ecx), - value: call.transfer_value(), - }; - - if let Some(return_data_queue) = match mocks.get_mut(&ctx) { - Some(queue) => Some(queue), - None => mocks - .iter_mut() - .find(|(mock, _)| { - call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) - && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) - }) - .map(|(_, v)| v), - } && let Some(return_data) = if return_data_queue.len() == 1 { - // If the mocked calls stack has a single element in it, don't empty it - return_data_queue.front().map(|x| x.to_owned()) - } else { - // Else, we pop the front element - return_data_queue.pop_front() - } { - return Some(CallOutcome { - result: InterpreterResult { - result: return_data.ret_type, - output: return_data.data, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - was_precompile_called: true, - precompile_call_logs: vec![], - }); - } - } - // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` @@ -932,6 +896,72 @@ impl Cheatcodes { } } + // Handle mocked calls + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { + let ctx = MockCallDataContext { + calldata: call.input.bytes(ecx), + value: call.transfer_value(), + }; + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) + && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned()) + { + if let Some(value) = call.transfer_value() { + let checkpoint = ecx.journal_mut().checkpoint(); + match ecx.journal_mut().transfer_loaded( + call.transfer_from(), + call.transfer_to(), + value, + ) { + None => { + if return_data.ret_type.is_ok() { + ecx.journal_mut().checkpoint_commit(); + } else { + ecx.journal_mut().checkpoint_revert(checkpoint); + } + } + Some(err) => { + ecx.journal_mut().checkpoint_revert(checkpoint); + return Some(CallOutcome { + result: InterpreterResult { + result: err.into(), + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], + }); + } + } + } + + // If the mocked calls stack has a single element in it, don't empty it + if return_data_queue.len() > 1 { + return_data_queue.pop_front(); + } + + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], + }); + } + } + // Apply EIP-2930 access list self.apply_accesslist(ecx); @@ -1497,6 +1527,21 @@ impl Inspector> for Cheatcode } } + // this will ensure we don't have false positives when trying to diagnose reverts in fork + // mode + let diag = self.fork_revert_diagnostic.take(); + + // If the call already reverted, preserve that primary failure and skip post-call + // expect* validation so it cannot overwrite the original revert. + if outcome.result.is_revert() { + // if there's a revert and a previous call was diagnosed as fork related revert then we + // can return a better error here + if let Some(err) = diag { + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + } + return; + } + // At the end of the call, // we need to check if we've found all the emits. // We know we've found all the expected emits in the right order @@ -1574,19 +1619,6 @@ impl Inspector> for Cheatcode self.expected_emits.clear() } - // this will ensure we don't have false positives when trying to diagnose reverts in fork - // mode - let diag = self.fork_revert_diagnostic.take(); - - // if there's a revert and a previous call was diagnosed as fork related revert then we can - // return a better error here - if outcome.result.is_revert() - && let Some(err) = diag - { - outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return; - } - // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist if let TxKind::Call(test_contract) = ecx.tx().kind() { @@ -1867,10 +1899,23 @@ impl Inspector> for Cheatcode } // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert + if let Some(expected_revert) = &mut self.expected_revert && curr_depth <= expected_revert.depth && matches!(expected_revert.kind, ExpectedRevertKind::Default) { + // Mirror the logic in `call_end`: when an expected reverter address is set + // and we don't yet have one (or we're matching multiple reverts), record the + // would-be deployed address as the reverter. revm guarantees `outcome.address` + // is `Some(_)` whenever the constructor actually ran (including the revert + // case); it is only `None` for pre-frame rejection (depth/balance/nonce), + // for which a reverter address is meaningless. + if outcome.result.is_revert() + && expected_revert.reverter.is_some() + && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + && let Some(addr) = outcome.address + { + expected_revert.reverted_by = Some(addr); + } let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match revert_handlers::handle_expect_revert( false, diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 608f20f0c4e32..12d625768a0c9 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -164,7 +164,7 @@ impl EqRelAssertionError { format_units_uint(&f.left, decimals), format_units_uint(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } @@ -179,7 +179,7 @@ impl EqRelAssertionError { format_units_int(&f.left, decimals), format_units_int(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs index fb722c2814baa..2b8f81518a621 100644 --- a/crates/cheatcodes/src/version.rs +++ b/crates/cheatcodes/src/version.rs @@ -20,7 +20,14 @@ impl Cheatcode for foundryVersionAtLeastCall { } fn foundry_version_cmp(version: &str) -> Result { - version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) + version_cmp(strip_semver_metadata(SEMVER_VERSION), version) +} + +/// Strips pre-release (e.g. `-nightly`, `-dev`) and build metadata +/// (e.g. `+..`) from a version string +/// so we compare on `MAJOR.MINOR.PATCH` only. +fn strip_semver_metadata(version: &str) -> &str { + version.split(['-', '+']).next().unwrap() } fn version_cmp(version_a: &str, version_b: &str) -> Result { @@ -42,3 +49,61 @@ fn parse_version(version: &str) -> Result { } Ok(version) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strips_build_metadata_only() { + // Tagged release: `1.7.1+..` + assert_eq!(strip_semver_metadata("1.7.1+abc1234567.1737036656.release"), "1.7.1"); + } + + #[test] + fn strips_pre_release_and_build_metadata() { + // Nightly: `1.7.1-nightly+..` + assert_eq!(strip_semver_metadata("1.7.1-nightly+abc1234567.1737036656.release"), "1.7.1"); + // Dev: `1.7.1-dev+..` + assert_eq!(strip_semver_metadata("1.7.1-dev+abc1234567.1737036656.debug"), "1.7.1"); + } + + #[test] + fn strips_plain_version() { + assert_eq!(strip_semver_metadata("1.7.1"), "1.7.1"); + } + + #[test] + fn version_cmp_orders_correctly() { + assert_eq!(version_cmp("1.7.1", "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp("1.7.1", "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "1.7.2").unwrap(), Ordering::Less); + assert_eq!(version_cmp("1.7.1", "0.0.1").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "99.0.0").unwrap(), Ordering::Less); + } + + #[test] + fn parse_version_rejects_pre_release_and_build_metadata() { + // User-supplied versions must be plain `MAJOR.MINOR.PATCH`. + assert!(parse_version("1.7.1-nightly").is_err()); + assert!(parse_version("1.7.1+abc").is_err()); + assert!(parse_version("not-a-version").is_err()); + assert!(parse_version("1.7.1").is_ok()); + } + + #[test] + fn cmp_works_against_full_semver_version_strings() { + // Simulate comparing each shape of `SEMVER_VERSION` against a user-supplied version. + for current in [ + "1.7.1+abc1234567.1737036656.release", + "1.7.1-nightly+abc1234567.1737036656.release", + "1.7.1-dev+abc1234567.1737036656.debug", + "1.7.1", + ] { + let stripped = strip_semver_metadata(current); + assert_eq!(version_cmp(stripped, "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp(stripped, "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp(stripped, "1.7.2").unwrap(), Ordering::Less); + } + } +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index a9396b4208886..bb673c9219e10 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -49,7 +49,6 @@ itertools.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true time = { version = "0.3", features = ["formatting"] } yansi.workspace = true tracing.workspace = true @@ -64,8 +63,13 @@ foundry-test-utils.workspace = true rexpect = "0.6" [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index da2c7f4caff02..2ec057b8167c0 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -2,10 +2,7 @@ //! //! This module contains the execution logic for the [SessionSource]. -use crate::{ - prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}, - source::IntermediateOutput, -}; +use crate::prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_json_abi::EventParam; use alloy_primitives::{Address, B256, U256, hex}; @@ -15,7 +12,17 @@ use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, inspectors::CheatsConfig, traces::TraceMode, }; -use solang_parser::pt; +use solar::{ + ast::{BinOpKind, ElementaryType, FunctionKind, LitKind, StateMutability, StrKind, UnOpKind}, + interface::Symbol, + sema::{ + hir::{ + ContractId, Event, Expr, ExprKind, Function, ItemId, Res, StmtKind, Type as HirType, + TypeKind, Visibility, + }, + ty::{Gcx, Ty, TyKind}, + }, +}; use std::ops::ControlFlow; use yansi::Paint; @@ -86,8 +93,10 @@ impl SessionSource { if let Some(err) = err { let output = source_without_inspector.build()?; - let formatted_event = - output.enter(|output| output.get_event(input).map(format_event_definition)); + let formatted_event = output.enter(|output| { + let gcx = output.gcx(); + output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid))) + }); if let Some(formatted_event) = formatted_event { return Ok((ControlFlow::Break(()), Some(formatted_event?))); } @@ -122,30 +131,37 @@ impl SessionSource { // which was wrapped in `abi.encode`. let generated_output = source.build()?; - // If the expression is a variable declaration within the REPL contract, use its type; - // otherwise, attempt to infer the type. - let contract_expr = generated_output - .intermediate - .repl_contract_expressions - .get(input) - .or_else(|| source.infer_inner_expr_type()); + // Inside the compiler closure, infer the DynSolType of the inspected expression and + // determine whether the REPL should continue. + let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> { + let gcx = out.gcx(); - // If the current action is a function call, we get its return type - // otherwise it returns None - let function_call_return_type = - Type::get_function_return_type(contract_expr, &generated_output.intermediate); + // Try direct lookup of `input` as a named variable in the REPL contract. + if let Some(direct_ty) = lookup_named_variable_type(gcx, input) { + return Some((false, direct_ty)); + } - let (contract_expr, ty) = if let Some(function_call_return_type) = function_call_return_type - { - (function_call_return_type.0, function_call_return_type.1) - } else { - match contract_expr.and_then(|e| { - Type::ethabi(e, Some(&generated_output.intermediate)).map(|ty| (e, ty)) - }) { - Some(res) => res, - // this type was denied for inspection, continue - None => return Ok((ControlFlow::Continue(()), None)), + // Otherwise, find the appended `bytes memory inspectoor = abi.encode();` + // and pull out the first call argument. + let block = out.run_func_body(); + let last = block.last()?; + let StmtKind::DeclSingle(vid) = last.kind else { return None }; + let var = gcx.hir.variable(vid); + let init = var.initializer?; + let ExprKind::Call(_callee, args, _) = &init.kind else { return None }; + let inner_expr = args.exprs().next()?; + + // If the call is `func()` returning a single value, prefer the function return type. + if let Some(ty) = get_function_return_type(gcx, inner_expr) { + return Some((should_continue(inner_expr), ty)); } + + let ty = expr_to_dyn(gcx, inner_expr, true)?; + Some((should_continue(inner_expr), ty)) + }); + + let Some((cont, ty)) = res_ty else { + return Ok((ControlFlow::Continue(()), None)); }; // the file compiled correctly, thus the last stack item must be the memory offset of @@ -162,42 +178,10 @@ impl SessionSource { eyre::bail!("Failed to inspect last expression: could not retrieve data from memory") }; let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?; - let c = if should_continue(contract_expr) { - ControlFlow::Continue(()) - } else { - ControlFlow::Break(()) - }; + let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }; Ok((c, Some(format_token(token)))) } - /// Gracefully attempts to extract the type of the expression within the `abi.encode(...)` - /// call inserted by the inspect function. - /// - /// ### Takes - /// - /// A reference to a [SessionSource] - /// - /// ### Returns - /// - /// Optionally, a [Type] - fn infer_inner_expr_type(&self) -> Option<&pt::Expression> { - let out = self.build().ok()?; - let run = out.run_func_body().ok()?.last(); - match run { - Some(pt::Statement::VariableDefinition( - _, - _, - Some(pt::Expression::FunctionCall(_, _, args)), - )) => { - // We can safely unwrap the first expression because this function - // will only be called on a session source that has just had an - // `inspectoor` variable appended to it. - Some(args.first().unwrap()) - } - _ => None, - } - } - async fn build_runner(&mut self, final_pc: usize) -> Result { let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?; @@ -241,6 +225,51 @@ impl SessionSource { } } +/// Looks up `name` as a named variable in the REPL contract (state variables or run() locals) +/// and returns its type as a [`DynSolType`]. +/// +/// Only top-level statements of `run()` are scanned. Variables declared inside nested blocks +/// (`if`, `for`, `while`, `unchecked`, etc.) are not visible here; the caller falls back to +/// the `inspectoor`-based path for those cases. +fn lookup_named_variable_type(gcx: Gcx<'_>, name: &str) -> Option { + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + + // State variables. + for vid in repl.variables() { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + + // Locals declared in run(). + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + for stmt in body.stmts { + match stmt.kind { + StmtKind::DeclSingle(vid) => { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + StmtKind::DeclMulti(vids, _) => { + for vid in vids.iter().flatten() { + let var = hir.variable(*vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item((*vid).into())); + } + } + } + _ => {} + } + } + None +} + /// Formats a value into an inspection message // TODO: Verbosity option fn format_token(token: DynSolValue) -> String { @@ -343,49 +372,37 @@ fn format_token(token: DynSolValue) -> String { } } -/// Formats a [pt::EventDefinition] into an inspection message -/// -/// ### Takes -/// -/// An borrowed [pt::EventDefinition] -/// -/// ### Returns -/// -/// A formatted [pt::EventDefinition] for use in inspection output. +/// Formats an [`Event`] into an inspection message. // TODO: Verbosity option -fn format_event_definition(event_definition: &pt::EventDefinition) -> Result { - let event_name = event_definition.name.as_ref().expect("Event has a name").to_string(); - let inputs = event_definition - .fields +fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result { + let event_name = event.name.as_str().to_string(); + let inputs = event + .parameters .iter() - .map(|param| { - let name = param - .name - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "".to_string()); - let kind = Type::from_expression(¶m.ty) - .and_then(Type::into_builtin) + .map(|&pid| { + let var = gcx.hir.variable(pid); + let name = + var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "".into()); + let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into())) .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?; Ok(EventParam { name, ty: kind.to_string(), components: vec![], - indexed: param.indexed, + indexed: var.indexed, internal_type: None, }) }) .collect::>>()?; - let event = - alloy_json_abi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; + let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous }; Ok(format!( "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}", "event".red(), SolidityHelper::new().highlight(&format!( "{}({})", - &event.name, - &event + event.name, + event .inputs .iter() .map(|param| format!( @@ -395,7 +412,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result>() @@ -411,844 +428,724 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result), - - /// (type, length) - FixedArray(Box, usize), +/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection. +/// +/// `lookup` controls whether user-defined type names are resolved via the HIR. +fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + match &expr.kind { + // Elementary type expression: `uint256`, `address`, etc. + ExprKind::Type(ty) => hir_ty_to_dyn(gcx, ty), + + // `type(T)`: only meaningful as the lhs of a member access. + ExprKind::TypeCall(_) => None, + + // Literals. + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Address(_) => Some(DynSolType::Address), + LitKind::Bool(_) => Some(DynSolType::Bool), + LitKind::Str(kind, _, _) => match kind { + StrKind::Hex => Some(DynSolType::Bytes), + StrKind::Str | StrKind::Unicode => Some(DynSolType::String), + }, + LitKind::Number(_) | LitKind::Rational(_) => Some(DynSolType::Uint(256)), + LitKind::Err(_) => None, + }, + + // Resolved identifier: `foo`. + ExprKind::Ident(reses) => { + let res = reses.first()?; + match *res { + Res::Item(ItemId::Variable(vid)) => { + solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) + } + Res::Item(ItemId::Struct(sid)) => { + // Struct reference used as a constructor produces a tuple of field types. + Some(DynSolType::Tuple( + gcx.struct_field_types(sid) + .iter() + .filter_map(|&t| solar_ty_to_dyn(gcx, t)) + .collect(), + )) + } + // Other items and builtins: handled by enclosing Call/Member expressions. + _ => None, + } + } - /// (type, index) - ArrayIndex(Box, Option), + // Index/access: `arr[i]`, `MyType[]`, `MyType[N]`. + ExprKind::Index(base, idx) => { + let base_ty = expr_to_dyn(gcx, base, lookup)?; + let num = + idx.and_then(|e| parse_number_literal(e)).and_then(|n| usize::try_from(n).ok()); + match &base.kind { + // Type-level indexing builds an array type expression. + ExprKind::Type(_) | ExprKind::TypeCall(_) => { + if let Some(n) = num { + Some(DynSolType::FixedArray(Box::new(base_ty), n)) + } else { + Some(DynSolType::Array(Box::new(base_ty))) + } + } + // Runtime indexing returns the element type. + _ => match base_ty { + DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => Some(*inner), + DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_) => { + Some(DynSolType::FixedBytes(1)) + } + other => Some(other), + }, + } + } - /// (types) - Tuple(Vec>), + // Slice: same type as the base. + ExprKind::Slice(base, _, _) => expr_to_dyn(gcx, base, lookup), - /// (name, params, returns) - Function(Box, Vec>, Vec>), + // Array literal `[a, b, c]`. + ExprKind::Array(values) => values + .first() + .and_then(|e| expr_to_dyn(gcx, e, lookup)) + .map(|ty| DynSolType::FixedArray(Box::new(ty), values.len())), - /// (lhs, rhs) - Access(Box, String), + // Tuple expression `(a, b, c)`. + ExprKind::Tuple(items) => Some(DynSolType::Tuple( + items.iter().filter_map(|opt| opt.and_then(|e| expr_to_dyn(gcx, e, lookup))).collect(), + )), - /// (types) - Custom(Vec), -} + // Member access `lhs.member`. + ExprKind::Member(_, _) => resolve_member(gcx, expr, lookup), -impl Type { - /// Convert a [pt::Expression] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Expression] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_expression(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::Type(_, ty) => Self::from_type(ty), - - pt::Expression::Variable(ident) => Some(Self::Custom(vec![ident.name.clone()])), - - // array - pt::Expression::ArraySubscript(_, expr, num) => { - // if num is Some then this is either an index operation (arr[]) - // or a FixedArray statement (new uint256[]) - Self::from_expression(expr).and_then(|ty| { - let boxed = Box::new(ty); - let num = num.as_deref().and_then(parse_number_literal).and_then(|n| { - usize::try_from(n).ok() - }); - match expr.as_ref() { - // statement - pt::Expression::Type(_, _) => { - if let Some(num) = num { - Some(Self::FixedArray(boxed, num)) - } else { - Some(Self::Array(boxed)) - } - } - // index - pt::Expression::Variable(_) => { - Some(Self::ArrayIndex(boxed, num)) - } - _ => None - } - }) - } - pt::Expression::ArrayLiteral(_, values) => { - values.first().and_then(Self::from_expression).map(|ty| { - Self::FixedArray(Box::new(ty), values.len()) - }) - } + // Function/constructor call. + ExprKind::Call(_, _, _) => resolve_call(gcx, expr, lookup), - // tuple - pt::Expression::List(_, params) => Some(Self::Tuple(map_parameters(params))), + // `new T`: produces a value of type T. + ExprKind::New(ty) => hir_ty_to_dyn(gcx, ty), - // . - pt::Expression::MemberAccess(_, lhs, rhs) => { - Self::from_expression(lhs).map(|lhs| { - Self::Access(Box::new(lhs), rhs.name.clone()) - }) - } + // `payable(addr)`. + ExprKind::Payable(_) => Some(DynSolType::Address), - // - pt::Expression::Parenthesis(_, inner) | // () - pt::Expression::New(_, inner) | // new - pt::Expression::UnaryPlus(_, inner) | // + - // ops - pt::Expression::BitwiseNot(_, inner) | // ~ - pt::Expression::ArraySlice(_, inner, _, _) | // [*start*:*end*] - // assign ops - pt::Expression::PreDecrement(_, inner) | // -- - pt::Expression::PostDecrement(_, inner) | // -- - pt::Expression::PreIncrement(_, inner) | // ++ - pt::Expression::PostIncrement(_, inner) | // ++ - pt::Expression::Assign(_, inner, _) | // = ... - pt::Expression::AssignAdd(_, inner, _) | // += ... - pt::Expression::AssignSubtract(_, inner, _) | // -= ... - pt::Expression::AssignMultiply(_, inner, _) | // *= ... - pt::Expression::AssignDivide(_, inner, _) | // /= ... - pt::Expression::AssignModulo(_, inner, _) | // %= ... - pt::Expression::AssignAnd(_, inner, _) | // &= ... - pt::Expression::AssignOr(_, inner, _) | // |= ... - pt::Expression::AssignXor(_, inner, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, inner, _) | // <<= ... - pt::Expression::AssignShiftRight(_, inner, _) // >>= ... - => Self::from_expression(inner), - - // *condition* ? : - pt::Expression::ConditionalOperator(_, _, if_true, if_false) => { - Self::from_expression(if_true).or_else(|| Self::from_expression(if_false)) - } + // Ternary: prefer truthy branch's type, fall back to else branch. + ExprKind::Ternary(_, t, e) => { + expr_to_dyn(gcx, t, lookup).or_else(|| expr_to_dyn(gcx, e, lookup)) + } - // address - pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), - pt::Expression::HexNumberLiteral(_, s, _) => { - match s.parse::
() { - Ok(addr) if *s == addr.to_checksum(None) => { - Some(Self::Builtin(DynSolType::Address)) + // Delete has no return type. + ExprKind::Delete(_) => None, + + // Unary operations. + ExprKind::Unary(op, inner) => match op.kind { + UnOpKind::Neg => expr_to_dyn(gcx, inner, lookup).map(|ty| match ty { + DynSolType::Uint(n) => DynSolType::Int(n), + DynSolType::Int(n) => DynSolType::Uint(n), + x => x, + }), + UnOpKind::Not => Some(DynSolType::Bool), + UnOpKind::BitNot + | UnOpKind::PreInc + | UnOpKind::PreDec + | UnOpKind::PostInc + | UnOpKind::PostDec => expr_to_dyn(gcx, inner, lookup), + }, + + // Binary operations. + ExprKind::Binary(lhs, op, rhs) => match op.kind { + BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Eq + | BinOpKind::Ne + | BinOpKind::And + | BinOpKind::Or => Some(DynSolType::Bool), + BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul | BinOpKind::Div => { + match (expr_to_dyn(gcx, lhs, false), expr_to_dyn(gcx, rhs, false)) { + (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) + | (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { + Some(DynSolType::Int(256)) } - _ => Some(Self::Builtin(DynSolType::Uint(256))), + _ => Some(DynSolType::Uint(256)), } } + BinOpKind::Rem + | BinOpKind::Pow + | BinOpKind::BitAnd + | BinOpKind::BitOr + | BinOpKind::BitXor + | BinOpKind::Shl + | BinOpKind::Shr + | BinOpKind::Sar => Some(DynSolType::Uint(256)), + }, + + // Assignments: type of the lhs. + ExprKind::Assign(lhs, _, _) => expr_to_dyn(gcx, lhs, lookup), + + ExprKind::Err(_) => None, + } +} - // uint and int - // invert - pt::Expression::Negate(_, inner) => Self::from_expression(inner).map(Self::invert_int), - - // int if either operand is int - // TODO: will need an update for Solidity v0.8.18 user defined operators: - // https://github.com/ethereum/solidity/issues/13718#issuecomment-1341058649 - pt::Expression::Add(_, lhs, rhs) | - pt::Expression::Subtract(_, lhs, rhs) | - pt::Expression::Multiply(_, lhs, rhs) | - pt::Expression::Divide(_, lhs, rhs) => { - match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) | -(Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { - Some(Self::Builtin(DynSolType::Int(256))) - } - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) - } +/// Converts a [`HirType`] to a [`DynSolType`]. +fn hir_ty_to_dyn(gcx: Gcx<'_>, ty: &HirType<'_>) -> Option { + match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + TypeKind::Array(arr) => { + let elem = hir_ty_to_dyn(gcx, &arr.element)?; + if let Some(size) = arr.size { + let n = parse_number_literal(size).and_then(|n| usize::try_from(n).ok()); + if let Some(n) = n { + Some(DynSolType::FixedArray(Box::new(elem), n)) + } else { + Some(DynSolType::Array(Box::new(elem))) } + } else { + Some(DynSolType::Array(Box::new(elem))) } - - // always assume uint - pt::Expression::Modulo(_, _, _) | - pt::Expression::Power(_, _, _) | - pt::Expression::BitwiseOr(_, _, _) | - pt::Expression::BitwiseAnd(_, _, _) | - pt::Expression::BitwiseXor(_, _, _) | - pt::Expression::ShiftRight(_, _, _) | - pt::Expression::ShiftLeft(_, _, _) | - pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(DynSolType::Uint(256))), - - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(_, _, _, _, _) => { - Some(Self::Builtin(DynSolType::Uint(256))) + } + TypeKind::Function(f) => match f.returns.len() { + 0 => None, + 1 => { + let var = gcx.hir.variable(f.returns[0]); + hir_ty_to_dyn(gcx, &var.ty) } + _ => Some(DynSolType::Tuple( + f.returns + .iter() + .filter_map(|&pid| hir_ty_to_dyn(gcx, &gcx.hir.variable(pid).ty)) + .collect(), + )), + }, + TypeKind::Mapping(m) => hir_ty_to_dyn(gcx, &m.value), + TypeKind::Custom(item) => solar_ty_to_dyn(gcx, gcx.type_of_item(*item)), + TypeKind::Err(_) => None, + } +} - // bool - pt::Expression::BoolLiteral(_, _) | - pt::Expression::And(_, _, _) | - pt::Expression::Or(_, _, _) | - pt::Expression::Equal(_, _, _) | - pt::Expression::NotEqual(_, _, _) | - pt::Expression::Less(_, _, _) | - pt::Expression::LessEqual(_, _, _) | - pt::Expression::More(_, _, _) | - pt::Expression::MoreEqual(_, _, _) | - pt::Expression::Not(_, _) => Some(Self::Builtin(DynSolType::Bool)), - - // string - pt::Expression::StringLiteral(_) => Some(Self::Builtin(DynSolType::String)), - - // bytes - pt::Expression::HexLiteral(_) => Some(Self::Builtin(DynSolType::Bytes)), - - // function - pt::Expression::FunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(Self::from_expression).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } - pt::Expression::NamedFunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(|arg| Self::from_expression(&arg.expr)).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } +/// Resolves a member-access expression (`lhs.member`) to its [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Member`. +fn resolve_member(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Member(lhs, ident) = &expr.kind else { return None }; + let member = ident.name; + + // `type(T).member` — type introspection. + if let ExprKind::TypeCall(ty) = &lhs.kind { + return match member.as_str() { + "name" => Some(DynSolType::String), + "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), + "interfaceId" => Some(DynSolType::FixedBytes(4)), + // Only valid for integer types; custom types (enums) fall back to Uint(256). + "min" | "max" => match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + _ => Some(DynSolType::Uint(256)), + }, + _ => None, + }; + } - // explicitly None - pt::Expression::Delete(_, _) | pt::Expression::FunctionCallBlock(_, _, _) => None, - } + // Built-in namespace identifier: `block.timestamp`, `msg.sender`, `abi.encode`, etc. + if let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + && let Some(ty) = builtin_member(b.name().as_str(), member.as_str()) + { + return Some(ty); } - /// Convert a [pt::Type] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Type] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_type(ty: &pt::Type) -> Option { - let ty = match ty { - pt::Type::Address | pt::Type::AddressPayable | pt::Type::Payable => { - Self::Builtin(DynSolType::Address) - } - pt::Type::Bool => Self::Builtin(DynSolType::Bool), - pt::Type::String => Self::Builtin(DynSolType::String), - pt::Type::Int(size) => Self::Builtin(DynSolType::Int(*size as usize)), - pt::Type::Uint(size) => Self::Builtin(DynSolType::Uint(*size as usize)), - pt::Type::Bytes(size) => Self::Builtin(DynSolType::FixedBytes(*size as usize)), - pt::Type::DynamicBytes => Self::Builtin(DynSolType::Bytes), - pt::Type::Mapping { value, .. } => Self::from_expression(value)?, - pt::Type::Function { params, returns, .. } => { - let params = map_parameters(params); - let returns = returns - .as_ref() - .map(|(returns, _)| map_parameters(returns)) - .unwrap_or_default(); - Self::Function( - Box::new(Self::Custom(vec!["__fn_type__".to_string()])), - params, - returns, - ) - } - // TODO: Rational numbers - pt::Type::Rational => return None, + // Elementary type used as a namespace: `address.balance`, `bytes.concat`, etc. + if let ExprKind::Type(ty) = &lhs.kind + && let TypeKind::Elementary(et) = &ty.kind + { + return match et { + ElementaryType::Address(_) => match member.as_str() { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + ElementaryType::Bytes => match member.as_str() { + "concat" => Some(DynSolType::Bytes), + _ => None, + }, + ElementaryType::String => match member.as_str() { + "concat" => Some(DynSolType::String), + _ => None, + }, + _ => None, }; - Some(ty) } - /// Handle special expressions like [global variables](https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables) - /// - /// See: - fn map_special(self) -> Self { - if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { - return self; - } + // Members on a resolved DynSolType (`.length`, `.pop`, `.selector`, `.address`). + if let Some(lhs_ty) = expr_to_dyn(gcx, lhs, lookup) + && let Some(ty) = dyn_member(&lhs_ty, member.as_str()) + { + return Some(ty); + } - let mut types = Vec::with_capacity(5); - let mut args = None; - self.recurse(&mut types, &mut args); + // HIR lookup for user-defined type members. + if lookup && let Some(mut chain) = expr_name_chain(gcx, lhs) { + chain.insert(0, member); + return infer_custom_type(gcx, &mut chain, None).ok().flatten(); + } - let len = types.len(); - if len == 0 { - return self; - } + None +} + +/// Returns the type of `builtin_ns.member` for built-in global namespaces. +fn builtin_member(builtin: &str, member: &str) -> Option { + match builtin { + "block" => match member { + "coinbase" => Some(DynSolType::Address), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | "chainid" + | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), + _ => None, + }, + "msg" => match member { + "sender" => Some(DynSolType::Address), + "gas" | "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), + _ => None, + }, + "tx" => match member { + "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), + _ => None, + }, + "address" => match member { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + _ => None, + } +} + +/// Returns the type of `ty.member` for a known [`DynSolType`]. +fn dyn_member(ty: &DynSolType, member: &str) -> Option { + match member { + "length" => match ty { + DynSolType::Array(_) + | DynSolType::FixedArray(_, _) + | DynSolType::Bytes + | DynSolType::String + | DynSolType::FixedBytes(_) => Some(DynSolType::Uint(256)), + _ => None, + }, + "pop" => match ty { + DynSolType::Array(inner) => Some(*inner.clone()), + _ => None, + }, + // Address members. + "balance" => match ty { + DynSolType::Address => Some(DynSolType::Uint(256)), + _ => None, + }, + "code" => match ty { + DynSolType::Address => Some(DynSolType::Bytes), + _ => None, + }, + "codehash" => match ty { + DynSolType::Address => Some(DynSolType::FixedBytes(32)), + _ => None, + }, + "send" => match ty { + DynSolType::Address => Some(DynSolType::Bool), + _ => None, + }, + // External function members. + "selector" => Some(DynSolType::FixedBytes(4)), + "address" => Some(DynSolType::Address), + _ => None, + } +} - // Type members, like array, bytes etc - #[expect(clippy::single_match)] - #[allow(clippy::collapsible_match)] - match &self { - Self::Access(inner, access) => { - if let Some(ty) = inner.as_ref().clone().try_as_ethabi(None) { - // Array / bytes members - let ty = Self::Builtin(ty); - match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { - return Self::Builtin(DynSolType::Uint(256)); +/// Resolves a call expression to its return [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Call`. +fn resolve_call(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Call(callee, args, _named) = &expr.kind else { return None }; + + // Type cast: `uint256(x)`, `address(y)`, etc. + if let ExprKind::Type(ty) = &callee.kind { + return hir_ty_to_dyn(gcx, ty); + } + + // Member call: `ns.method(...)`. + if let ExprKind::Member(lhs, method) = &callee.kind + && let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + { + match b.name().as_str() { + "abi" => { + return match method.as_str() { + "decode" => { + let last = args.exprs().last()?; + match expr_to_dyn(gcx, last, false)? { + DynSolType::Tuple(tys) => Some(DynSolType::Tuple(tys)), + ty => Some(DynSolType::Tuple(vec![ty])), } - "pop" if ty.is_dynamic_array() => return ty, - _ => {} } - } + s if s.starts_with("encode") => Some(DynSolType::Bytes), + _ => None, + }; } + "string" if method.as_str() == "concat" => return Some(DynSolType::String), + "bytes" if method.as_str() == "concat" => return Some(DynSolType::Bytes), _ => {} } + } - let this = { - let name = types.last().unwrap().as_str(); - match len { - 0 => unreachable!(), - 1 => match name { + // Simple identifier call: built-in global functions and HIR function calls. + if let ExprKind::Ident(reses) = &callee.kind { + match reses.first() { + Some(Res::Builtin(b)) => { + return match b.name().as_str() { "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)), "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)), "ripemd160" => Some(DynSolType::FixedBytes(20)), "ecrecover" => Some(DynSolType::Address), _ => None, - }, - 2 => { - let access = types.first().unwrap().as_str(); - match name { - "block" => match access { - "coinbase" => Some(DynSolType::Address), - "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" - | "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), - _ => None, - }, - "msg" => match access { - "sender" => Some(DynSolType::Address), - "gas" => Some(DynSolType::Uint(256)), - "value" => Some(DynSolType::Uint(256)), - "data" => Some(DynSolType::Bytes), - "sig" => Some(DynSolType::FixedBytes(4)), - _ => None, - }, - "tx" => match access { - "origin" => Some(DynSolType::Address), - "gasprice" => Some(DynSolType::Uint(256)), - _ => None, - }, - "abi" => match access { - "decode" => { - // args = Some([Bytes(_), Tuple(args)]) - // unwrapping is safe because this is first compiled by solc so - // it is guaranteed to be a valid call - let mut args = args.unwrap(); - let last = args.pop().unwrap(); - match last { - Some(ty) => { - return match ty { - Self::Tuple(_) => ty, - ty => Self::Tuple(vec![Some(ty)]), - }; - } - None => None, - } - } - s if s.starts_with("encode") => Some(DynSolType::Bytes), - _ => None, - }, - "address" => match access { - "balance" => Some(DynSolType::Uint(256)), - "code" => Some(DynSolType::Bytes), - "codehash" => Some(DynSolType::FixedBytes(32)), - "send" => Some(DynSolType::Bool), - _ => None, - }, - "type" => match access { - "name" => Some(DynSolType::String), - "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), - "interfaceId" => Some(DynSolType::FixedBytes(4)), - "min" | "max" => Some( - // Either a builtin or an enum - (|| args?.pop()??.into_builtin())() - .unwrap_or(DynSolType::Uint(256)), - ), - _ => None, - }, - "string" => match access { - "concat" => Some(DynSolType::String), - _ => None, - }, - "bytes" => match access { - "concat" => Some(DynSolType::Bytes), - _ => None, - }, - _ => None, - } - } - _ => None, - } - }; - - this.map(Self::Builtin).unwrap_or_else(|| match types.last().unwrap().as_str() { - "this" | "super" => Self::Custom(types), - _ => match self { - Self::Custom(_) | Self::Access(_, _) => Self::Custom(types), - Self::Function(_, _, _) => self, - _ => unreachable!(), - }, - }) - } - - /// Recurses over itself, appending all the idents and function arguments in the order that they - /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { - match self { - Self::Builtin(ty) => types.push(ty.to_string()), - Self::Custom(tys) => types.extend(tys.clone()), - Self::Access(expr, name) => { - types.push(name.clone()); - expr.recurse(types, args); + }; } - Self::Function(fn_name, fn_args, _fn_ret) => { - if args.is_none() && !fn_args.is_empty() { - *args = Some(fn_args.clone()); + Some(Res::Item(ItemId::Function(fid))) if lookup => { + let func = gcx.hir.function(*fid); + if !matches!(func.state_mutability, StateMutability::View | StateMutability::Pure) { + return None; } - fn_name.recurse(types, args); + let ret_id = *func.returns.first()?; + return solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())); } _ => {} } } - /// Infers a custom type's true type by recursing up the parse tree - /// - /// ### Takes - /// - A reference to the [IntermediateOutput] - /// - An array of custom types generated by the `MemberAccess` arm of [Self::from_expression] - /// - An optional contract name. This should always be `None` when this function is first - /// called. - /// - /// ### Returns - /// - /// If successful, an `Ok(Some(DynSolType))` variant. - /// If gracefully failed, an `Ok(None)` variant. - /// If failed, an `Err(e)` variant. - fn infer_custom_type( - intermediate: &IntermediateOutput, - custom_type: &mut Vec, - contract_name: Option, - ) -> Result> { - if let Some("this" | "super") = custom_type.last().map(String::as_str) { - custom_type.pop(); + // Fall back to the callee's resolved type. + expr_to_dyn(gcx, callee, lookup) +} + +/// Extracts a name chain from a member-access expression tree for HIR lookup. +/// +/// The chain is ordered outermost-first so `a.b.c` produces `["c", "b", "a"]` with the root +/// identifier at the back. This matches the convention expected by [`infer_custom_type`]. +fn expr_name_chain(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option> { + match &expr.kind { + ExprKind::Ident(reses) => { + let res = reses.first()?; + let name = match *res { + Res::Item(ItemId::Variable(vid)) => gcx.hir.variable(vid).name?.name, + Res::Item(ItemId::Function(fid)) => gcx.hir.function(fid).name?.name, + Res::Item(ItemId::Contract(cid)) => gcx.hir.contract(cid).name.name, + Res::Builtin(b) => b.name(), + _ => return None, + }; + Some(vec![name]) } - if custom_type.is_empty() { - return Ok(None); + ExprKind::Member(lhs, ident) => { + let mut chain = expr_name_chain(gcx, lhs)?; + chain.insert(0, ident.name); + Some(chain) } + _ => None, + } +} - // If a contract exists with the given name, check its definitions for a match. - // Otherwise look in the `run` - if let Some(contract_name) = contract_name { - let intermediate_contract = intermediate - .intermediate_contracts - .get(&contract_name) - .ok_or_else(|| eyre::eyre!("Could not find intermediate contract!"))?; - - let cur_type = custom_type.last().unwrap(); - if let Some(func) = intermediate_contract.function_definitions.get(cur_type) { - // Check if the custom type is a function pointer member access - if let res @ Some(_) = func_members(func, custom_type) { - return Ok(res); - } - - // Because tuple types cannot be passed to `abi.encode`, we will only be - // receiving functions that have 0 or 1 return parameters here. - if func.returns.is_empty() { - eyre::bail!( - "This call expression does not return any values to inspect. Insert as statement." - ) - } +/// Infers a custom type's true type by recursing through the HIR. +/// +/// `custom_type` is a name chain ordered outermost-first (root at back). This is mutated during +/// resolution. `contract_id` narrows the search to a specific contract scope. +fn infer_custom_type( + gcx: Gcx<'_>, + custom_type: &mut Vec, + contract_id: Option, +) -> Result> { + if let Some(last) = custom_type.last() + && (last.as_str() == "this" || last.as_str() == "super") + { + custom_type.pop(); + } + if custom_type.is_empty() { + return Ok(None); + } - // Empty return types check is done above - let (_, param) = func.returns.first().unwrap(); - // Return type should always be present - let return_ty = ¶m.as_ref().unwrap().ty; - - // If the return type is a variable (not a type expression), re-enter the recursion - // on the same contract for a variable / struct search. It could be a contract, - // struct, array, etc. - if let pt::Expression::Variable(ident) = return_ty { - custom_type.push(ident.name.clone()); - return Self::infer_custom_type(intermediate, custom_type, Some(contract_name)); - } + if let Some(cid) = contract_id { + let hir = &gcx.hir; + let contract = hir.contract(cid); - // Check if our final function call alters the state. If it does, we bail so that it - // will be inserted normally without inspecting. If the state mutability was not - // expressly set, the function is inferred to alter state. - if let Some(pt::FunctionAttribute::Mutability(_mut)) = func - .attributes - .iter() - .find(|attr| matches!(attr, pt::FunctionAttribute::Mutability(_))) - { - if let pt::Mutability::Payable(_) = _mut { - eyre::bail!("This function mutates state. Insert as a statement.") - } - } else { - eyre::bail!("This function mutates state. Insert as a statement.") - } + let cur_name = *custom_type.last().unwrap(); + let cur = cur_name.as_str(); - Ok(Self::ethabi(return_ty, Some(intermediate))) - } else if let Some(var) = intermediate_contract.variable_definitions.get(cur_type) { - Self::infer_var_expr(&var.ty, Some(intermediate), custom_type) - } else if let Some(strukt) = intermediate_contract.struct_definitions.get(cur_type) { - let inner_types = strukt - .fields - .iter() - .map(|var| { - Self::ethabi(&var.ty, Some(intermediate)) - .ok_or_else(|| eyre::eyre!("Struct `{cur_type}` has invalid fields")) - }) - .collect::>>()?; - Ok(Some(DynSolType::Tuple(inner_types))) - } else { - eyre::bail!( - "Could not find any definition in contract \"{contract_name}\" for type: {custom_type:?}" - ) - } - } else { - // Check if the custom type is a variable or function within the REPL contract before - // anything. If it is, we can stop here. - if let Ok(res) = Self::infer_custom_type(intermediate, custom_type, Some("REPL".into())) - { + // Function? + if let Some(fid) = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + let func = hir.function(fid); + if let res @ Some(_) = func_members(func, custom_type) { return Ok(res); } - // Check if the first element of the custom type is a known contract. If it is, begin - // our recursion on that contract's definitions. - let name = custom_type.last().unwrap(); - let contract = intermediate.intermediate_contracts.get(name); - if contract.is_some() { - let contract_name = custom_type.pop(); - return Self::infer_custom_type(intermediate, custom_type, contract_name); + if func.returns.is_empty() { + eyre::bail!( + "This call expression does not return any values to inspect. Insert as statement." + ) } - // See [`Type::infer_var_expr`] - let name = custom_type.last().unwrap(); - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - return Self::infer_var_expr(expr, Some(intermediate), custom_type); + let sm = func.state_mutability; + if !matches!(sm, StateMutability::View | StateMutability::Pure) { + eyre::bail!("This function mutates state. Insert as a statement.") } - // The first element of our custom type was neither a variable or a function within the - // REPL contract, move on to globally available types gracefully. - Ok(None) + let ret_id = func.returns[0]; + let ret_var = hir.variable(ret_id); + return Ok(solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) + .or_else(|| hir_ty_to_dyn(gcx, &ret_var.ty))); } - } - /// Infers the type from a variable's type - fn infer_var_expr( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - custom_type: &mut Vec, - ) -> Result> { - // Resolve local (in `run` function) or global (in the `REPL` or other contract) variable - let res = match &expr { - // Custom variable handling - pt::Expression::Variable(ident) => { - let name = &ident.name; - - if let Some(intermediate) = intermediate { - // expression in `run` - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - Self::infer_var_expr(expr, Some(intermediate), custom_type) - } else if intermediate.intermediate_contracts.contains_key(name) { - if custom_type.len() > 1 { - // There is still some recursing left to do: jump into the contract. - custom_type.pop(); - Self::infer_custom_type(intermediate, custom_type, Some(name.clone())) - } else { - // We have no types left to recurse: return the address of the contract. - Ok(Some(DynSolType::Address)) - } - } else { - Err(eyre::eyre!("Could not infer variable type")) - } - } else { - Ok(None) - } - } - other_expr => Ok(Self::ethabi(other_expr, intermediate)), - }; - // re-run everything with the resolved variable in case we're accessing a builtin member - // for example array or bytes length etc - match res { - Ok(Some(ty)) => { - let box_ty = Box::new(Self::Builtin(ty.clone())); - let access = Self::Access(box_ty, custom_type.drain(..).next().unwrap_or_default()); - if let Some(mapped) = access.map_special().try_as_ethabi(intermediate) { - Ok(Some(mapped)) - } else { - Ok(Some(ty)) + // Variable? + if let Some(vid) = contract + .variables() + .find(|&v| hir.variable(v).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + if let Some(ty) = solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) { + custom_type.pop(); + if custom_type.is_empty() { + return Ok(Some(ty)); } + let next_member = custom_type.drain(..).next().unwrap_or(Symbol::DUMMY); + return Ok(dyn_member(&ty, next_member.as_str()).or(Some(ty))); } - res => res, - } - } - - /// Attempt to convert this type into a [DynSolType] - /// - /// ### Takes - /// An immutable reference to an [IntermediateOutput] - /// - /// ### Returns - /// Optionally, a [DynSolType] - fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - Self::Tuple(types) => Some(DynSolType::Tuple(types_to_parameters(types, intermediate))), - Self::Array(inner) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::Array(Box::new(inner))), - }, - Self::FixedArray(inner, size) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::FixedArray(Box::new(inner), size)), - }, - ty @ Self::ArrayIndex(_, _) => ty.into_array_index(intermediate), - Self::Function(ty, _, _) => ty.try_as_ethabi(intermediate), - // should have been mapped to `Custom` in previous steps - Self::Access(_, _) => None, - Self::Custom(mut types) => { - // Cover any local non-state-modifying function call expressions - intermediate.and_then(|intermediate| { - Self::infer_custom_type(intermediate, &mut types, None).ok().flatten() - }) - } + let var = hir.variable(vid); + return infer_var_ty(gcx, &var.ty, custom_type); } - } - - /// Equivalent to `Type::from_expression` + `Type::map_special` + `Type::try_as_ethabi` - fn ethabi( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - ) -> Option { - Self::from_expression(expr) - .map(Self::map_special) - .and_then(|ty| ty.try_as_ethabi(intermediate)) - } - /// Get the return type of a function call expression. - fn get_function_return_type<'a>( - contract_expr: Option<&'a pt::Expression>, - intermediate: &IntermediateOutput, - ) -> Option<(&'a pt::Expression, DynSolType)> { - let function_call = match contract_expr? { - pt::Expression::FunctionCall(_, function_call, _) => function_call, - _ => return None, - }; - let (contract_name, function_name) = match function_call.as_ref() { - pt::Expression::MemberAccess(_, contract_name, function_name) => { - (contract_name, function_name) + // Struct? + if let Some(sid) = contract.items.iter().find_map(|i| { + if let ItemId::Struct(sid) = i + && hir.strukt(*sid).name.as_str() == cur + { + Some(*sid) + } else { + None } - _ => return None, - }; - let contract_name = match contract_name.as_ref() { - pt::Expression::Variable(contract_name) => contract_name.to_owned(), - _ => return None, - }; - - let pt::Expression::Variable(contract_name) = - intermediate.repl_contract_expressions.get(&contract_name.name)? - else { - return None; - }; - - let contract = intermediate - .intermediate_contracts - .get(&contract_name.name)? - .function_definitions - .get(&function_name.name)?; - let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) - } - - /// Inverts Int to Uint and vice-versa. - fn invert_int(self) -> Self { - match self { - Self::Builtin(DynSolType::Uint(n)) => Self::Builtin(DynSolType::Int(n)), - Self::Builtin(DynSolType::Int(n)) => Self::Builtin(DynSolType::Uint(n)), - x => x, + }) { + let inner = gcx + .struct_field_types(sid) + .iter() + .map(|&t| { + solar_ty_to_dyn(gcx, t) + .ok_or_else(|| eyre::eyre!("Struct `{cur}` has invalid fields")) + }) + .collect::>>()?; + return Ok(Some(DynSolType::Tuple(inner))); } - } - /// Returns the `DynSolType` contained by `Type::Builtin` - #[inline] - fn into_builtin(self) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - _ => None, - } + eyre::bail!( + "Could not find any definition in contract \"{}\" for type: {custom_type:?}", + contract.name.as_str() + ) } - /// Returns the resulting `DynSolType` of indexing self - fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { - match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner) | DynSolType::FixedArray(inner, _)) => { - Some(*inner) - } - Some(DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_)) => { - Some(DynSolType::FixedBytes(1)) - } - ty => ty, - } - } - _ => None, - } + let repl_id = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == "REPL").then_some(cid)); + if let Some(repl_id) = repl_id + && let Ok(res) = infer_custom_type(gcx, custom_type, Some(repl_id)) + { + return Ok(res); } - /// Returns whether this type is dynamic - #[inline] - const fn is_dynamic(&self) -> bool { - match self { - // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are - // not dynamic, nor are tuples of non-dynamic types. - Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, - Self::Array(_) => true, - _ => false, - } + let last_name = *custom_type.last().unwrap(); + let last = last_name.as_str(); + let contract_match = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == last).then_some(cid)); + if let Some(cid) = contract_match { + custom_type.pop(); + return infer_custom_type(gcx, custom_type, Some(cid)); } - /// Returns whether this type is an array - #[inline] - const fn is_array(&self) -> bool { - matches!( - self, - Self::Array(_) - | Self::FixedArray(_, _) - | Self::Builtin(DynSolType::Array(_) | DynSolType::FixedArray(_, _)) - ) - } + Ok(None) +} - /// Returns whether this type is a dynamic array (can call push, pop) - #[inline] - const fn is_dynamic_array(&self) -> bool { - matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) +/// Infers the type from a variable's HIR type, optionally accessing a named member. +fn infer_var_ty( + gcx: Gcx<'_>, + ty: &HirType<'_>, + custom_type: &mut Vec, +) -> Result> { + let Some(ty) = hir_ty_to_dyn(gcx, ty) else { return Ok(None) }; + let next_member = custom_type.drain(..).next(); + if let Some(m) = next_member { + Ok(dyn_member(&ty, m.as_str()).or(Some(ty))) + } else { + Ok(Some(ty)) } +} - const fn is_fixed_bytes(&self) -> bool { - matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) - } +/// Get the return type of a contract method call `receiver.method()`. +fn get_function_return_type(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option { + let ExprKind::Call(callee, _, _) = &expr.kind else { return None }; + let ExprKind::Member(obj, fn_ident) = &callee.kind else { return None }; + let ExprKind::Ident(reses) = &obj.kind else { return None }; + let res = reses.first()?; + let var_id = match res { + Res::Item(ItemId::Variable(vid)) => *vid, + _ => return None, + }; + let var_ty = gcx.type_of_item(var_id.into()).peel_refs(); + let cid = match var_ty.kind { + TyKind::Contract(cid) => cid, + _ => return None, + }; + + let hir = &gcx.hir; + let contract = hir.contract(cid); + let fid = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some(fn_ident.as_str()))?; + let func = hir.function(fid); + let ret_id = *func.returns.first()?; + solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) } -/// Returns Some if the custom type is a function member access +/// Returns Some if the custom type is a function member access. /// /// Ref: #[inline] -fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { - if !matches!(func.ty, pt::FunctionTy::Function) { +fn func_members(func: &Function<'_>, custom_type: &[Symbol]) -> Option { + if !matches!(func.kind, FunctionKind::Function) { return None; } - - let vis = func.attributes.iter().find_map(|attr| match attr { - pt::FunctionAttribute::Visibility(vis) => Some(vis), - _ => None, - }); - match vis { - Some(pt::Visibility::External(_) | pt::Visibility::Public(_)) => { - match custom_type.first().unwrap().as_str() { - "address" => Some(DynSolType::Address), - "selector" => Some(DynSolType::FixedBytes(4)), - _ => None, - } - } + if !matches!(func.visibility, Visibility::External | Visibility::Public) { + return None; + } + match custom_type.first().unwrap().as_str() { + "address" => Some(DynSolType::Address), + "selector" => Some(DynSolType::FixedBytes(4)), _ => None, } } -/// Whether execution should continue after inspecting this expression +/// Whether execution should continue after inspecting this expression. #[inline] -fn should_continue(expr: &pt::Expression) -> bool { - match expr { - // assignments - pt::Expression::PreDecrement(_, _) | // -- - pt::Expression::PostDecrement(_, _) | // -- - pt::Expression::PreIncrement(_, _) | // ++ - pt::Expression::PostIncrement(_, _) | // ++ - pt::Expression::Assign(_, _, _) | // = ... - pt::Expression::AssignAdd(_, _, _) | // += ... - pt::Expression::AssignSubtract(_, _, _) | // -= ... - pt::Expression::AssignMultiply(_, _, _) | // *= ... - pt::Expression::AssignDivide(_, _, _) | // /= ... - pt::Expression::AssignModulo(_, _, _) | // %= ... - pt::Expression::AssignAnd(_, _, _) | // &= ... - pt::Expression::AssignOr(_, _, _) | // |= ... - pt::Expression::AssignXor(_, _, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, _, _) | // <<= ... - pt::Expression::AssignShiftRight(_, _, _) // >>= ... - => { - true - } - +fn should_continue(expr: &Expr<'_>) -> bool { + match &expr.kind { + // assignments and compound assignments + ExprKind::Assign(_, _, _) => true, + // ++/-- pre/post operations + ExprKind::Unary(op, _) => matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ), // Array.pop() - pt::Expression::FunctionCall(_, lhs, _) => { - match lhs.as_ref() { - pt::Expression::MemberAccess(_, _inner, access) => access.name == "pop", - _ => false - } - } - - _ => false + ExprKind::Call(callee, _, _) => match &callee.kind { + ExprKind::Member(_, ident) => ident.as_str() == "pop", + _ => false, + }, + _ => false, } } -fn map_parameters(params: &[(pt::Loc, Option)]) -> Vec> { - params - .iter() - .map(|(_, param)| param.as_ref().and_then(|param| Type::from_expression(¶m.ty))) - .collect() +/// Parses an [`Expr`] number/hex literal into a `U256`. Returns `None` if the expression +/// is not a numeric literal. +/// +/// SubDenominations are already applied to numeric literals in solar's HIR. +const fn parse_number_literal(expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Number(n) => Some(*n), + _ => None, + }, + _ => None, + } } -fn types_to_parameters( - types: Vec>, - intermediate: Option<&IntermediateOutput>, -) -> Vec { - types.into_iter().filter_map(|ty| ty.and_then(|ty| ty.try_as_ethabi(intermediate))).collect() +/// Maps a solar [`ElementaryType`] to a [`DynSolType`]. +const fn elementary_to_dyn(et: ElementaryType) -> Option { + Some(match et { + ElementaryType::Address(_) => DynSolType::Address, + ElementaryType::Bool => DynSolType::Bool, + ElementaryType::String => DynSolType::String, + ElementaryType::Bytes => DynSolType::Bytes, + ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize), + ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize), + ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize), + // Fixed-point numbers are not yet representable as DynSolType. + ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None, + }) } -fn parse_number_literal(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::NumberLiteral(_, num, exp, unit) => { - let num = num.parse::().unwrap_or(U256::ZERO); - let exp = exp.parse().unwrap_or(0u32); - if exp > 77 { - None +/// Maps a solar [`Ty`] to a [`DynSolType`]. +fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option { + match ty.kind { + TyKind::Elementary(et) => elementary_to_dyn(et), + TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Array(elem, n) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + let size: usize = n.try_into().ok()?; + Some(DynSolType::FixedArray(Box::new(inner), size)) + } + TyKind::DynArray(elem) | TyKind::Slice(elem) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + Some(DynSolType::Array(Box::new(inner))) + } + TyKind::Tuple(tys) => { + Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect())) + } + TyKind::Mapping(_, _) => None, + TyKind::Struct(sid) => Some(DynSolType::Tuple( + gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + TyKind::Enum(_) => Some(DynSolType::Uint(8)), + TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Contract(_) => Some(DynSolType::Address), + // For a function-pointer type we return the ABI type of what the call *produces*, not a + // representation of the pointer itself. This is intentional: chisel inspects values, so + // the interesting type is the returned value. A zero-return function pointer has no + // inspectable value, so we return `None`. + TyKind::FnPtr(f) => match f.returns.len() { + 0 => None, + 1 => solar_ty_to_dyn(gcx, f.returns[0]), + _ => Some(DynSolType::Tuple( + f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + }, + TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::IntLiteral(neg, size) => { + let bits = (size.bits() as usize).max(8); + // Round up to the nearest multiple of 8 bits, capped at 256. + let bits = bits.div_ceil(8) * 8; + let bits = bits.min(256); + if neg { + Some(DynSolType::Int(bits.max(8))) } else { - let exp = U256::from(10usize.pow(exp)); - let unit_mul = unit_multiplier(unit).ok()?; - Some(num * exp * unit_mul) + Some(DynSolType::Uint(bits.max(8))) } } - pt::Expression::HexNumberLiteral(_, num, unit) => { - let unit_mul = unit_multiplier(unit).ok()?; - num.parse::().map(|num| num * unit_mul).ok() + TyKind::StringLiteral(valid_utf8, _) => { + if valid_utf8 { + Some(DynSolType::String) + } else { + Some(DynSolType::Bytes) + } } - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(..) => None, + TyKind::Module(_) + | TyKind::BuiltinModule(_) + | TyKind::Error(_, _) + | TyKind::Event(_, _) + | TyKind::Err(_) => None, _ => None, } } -#[inline] -fn unit_multiplier(unit: &Option) -> Result { - if let Some(unit) = unit { - let mul = match unit.name.as_str() { - "seconds" => 1, - "minutes" => 60, - "hours" => 60 * 60, - "days" => 60 * 60 * 24, - "weeks" => 60 * 60 * 24 * 7, - "wei" => 1, - "gwei" => 10_usize.pow(9), - "ether" => 10_usize.pow(18), - other => eyre::bail!("unknown unit: {other}"), - }; - Ok(U256::from(mul)) - } else { - Ok(U256::from(1)) - } -} - #[cfg(test)] mod tests { use super::*; use foundry_compilers::{error::SolcError, solc::Solc}; + use solar::sema::Compiler; use std::sync::Mutex; #[test] @@ -1558,46 +1455,66 @@ mod tests { DynSolType::FixedArray(Box::new(ty), len) } - fn parse(s: &mut SessionSource, input: &str, clear: bool) -> IntermediateOutput { + /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without + /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in + /// the run() body. + /// + /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so that + /// inputs which are syntactically valid but semantically rejected by solc (e.g. + /// `abi.decode(bytes, (uint8[13]))` or `a[0:3]` on a memory array) can still exercise the + /// HIR-based type-inference engine. + fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { if clear { s.clear(); } + // Always declare a sample enum so `Enum1` is available for `type(Enum1)` tests. *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; let input = format!("{};", input.trim_end().trim_end_matches(';')); - let (mut _s, _) = s.clone_with_new_line(input).unwrap(); - *s = _s.clone(); - let s = &mut _s; - - if let Err(e) = s.parse() { - let source = s.to_repl_source(); - panic!("{e}\n\ncould not parse input:\n{source}") - } - s.generate_intermediate_output().expect("could not generate intermediate output") - } - - fn expr(stmts: &[pt::Statement]) -> pt::Expression { - match stmts.last().expect("no statements") { - pt::Statement::Expression(_, e) => e.clone(), - s => panic!("Not an expression: {s:?}"), - } - } - - fn get_type( - s: &mut SessionSource, - input: &str, - clear: bool, - ) -> (Option, IntermediateOutput) { - let intermediate = parse(s, input, clear); - let run_func_body = intermediate.run_func_body().expect("no run func body"); - let expr = expr(run_func_body); - (Type::from_expression(&expr).map(Type::map_special), intermediate) - } + let (new_source, _) = s.clone_with_new_line(input).unwrap(); + *s = new_source.clone(); + + let src = new_source.to_repl_source(); + let sess = + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(sess); + + compiler.enter_mut(|c| -> Option { + // Stage 1: parse + lower (mutable access required). + let lowered = { + let mut pcx = c.parse(); + let file = c + .sess() + .source_map() + .new_source_file( + std::path::PathBuf::from(new_source.file_name.clone()), + src.clone(), + ) + .ok()?; + pcx.add_file(file); + pcx.parse(); + matches!(c.lower_asts(), Ok(ControlFlow::Continue(()))) + }; + if !lowered { + return None; + } - fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { - let (ty, intermediate) = get_type(s, input, clear); - ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) + // Stage 2: walk HIR (immutable access). + let gcx = c.gcx(); + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + let last = body.last()?; + let expr = match last.kind { + StmtKind::Expr(e) => e, + _ => return None, + }; + expr_to_dyn(gcx, expr, true) + }) } fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs index 8133de364c1da..90c0bad874622 100644 --- a/crates/chisel/src/source.rs +++ b/crates/chisel/src/source.rs @@ -5,7 +5,6 @@ //! execution helpers. use eyre::Result; -use foundry_common::fs; use foundry_compilers::{ Artifact, ProjectCompileOutput, artifacts::{ConfigurableContractArtifact, Source, Sources}, @@ -16,9 +15,16 @@ use foundry_config::{Config, SolcReq}; use foundry_evm::{backend::Backend, core::bytecode::InstIter, opts::EvmOpts}; use semver::Version; use serde::{Deserialize, Serialize}; -use solang_parser::pt::{self, CodeLocation}; -use solar::interface::diagnostics::EmittedDiagnostics; -use std::{cell::OnceCell, collections::HashMap, fmt, path::PathBuf}; +use solar::{ + ast::{ItemKind, StmtKind as AstStmtKind, yul}, + interface::{Span, diagnostics::EmittedDiagnostics}, + sema::{ + CompilerRef, + hir::{Block, Contract, EventId, ItemId, Stmt, StmtKind as HirStmtKind}, + ty::Gcx, + }, +}; +use std::{cell::OnceCell, fmt}; use walkdir::WalkDir; /// The minimum Solidity version of the `Vm` interface. @@ -30,41 +36,8 @@ static VM_SOURCE: &str = include_str!("../../../testdata/utils/Vm.sol"); /// [`SessionSource`] build output. pub struct GeneratedOutput { output: ProjectCompileOutput, - pub(crate) intermediate: IntermediateOutput, -} - -pub struct GeneratedOutputRef<'a> { - output: &'a ProjectCompileOutput, - // compiler: &'b solar::sema::CompilerRef<'c>, - pub(crate) intermediate: &'a IntermediateOutput, -} - -/// Intermediate output for the compiled [SessionSource] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct IntermediateOutput { - /// All expressions within the REPL contract's run function and top level scope. - pub repl_contract_expressions: HashMap, - /// Intermediate contracts - pub intermediate_contracts: IntermediateContracts, -} - -/// A refined intermediate parse tree for a contract that enables easy lookups -/// of definitions. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct IntermediateContract { - /// All function definitions within the contract - pub function_definitions: HashMap>, - /// All event definitions within the contract - pub event_definitions: HashMap>, - /// All struct definitions within the contract - pub struct_definitions: HashMap>, - /// All variable definitions within the top level scope of the contract - pub variable_definitions: HashMap>, } -/// A defined type for a map of contract names to [IntermediateContract]s -type IntermediateContracts = HashMap; - impl fmt::Debug for GeneratedOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GeneratedOutput").finish_non_exhaustive() @@ -72,158 +45,25 @@ impl fmt::Debug for GeneratedOutput { } impl GeneratedOutput { - pub fn enter(&self, f: impl FnOnce(GeneratedOutputRef<'_>) -> T + Send) -> T { - // TODO(dani): once intermediate is removed - // self.output - // .parser() - // .solc() - // .compiler() - // .enter(|compiler| f(GeneratedOutputRef { output: &self.output, compiler })) - f(GeneratedOutputRef { output: &self.output, intermediate: &self.intermediate }) - } -} - -impl GeneratedOutputRef<'_> { - pub fn repl_contract(&self) -> Option<&ConfigurableContractArtifact> { - self.output.find_first("REPL") - } -} - -impl std::ops::Deref for GeneratedOutput { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - &self.intermediate - } -} -impl std::ops::Deref for GeneratedOutputRef<'_> { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - self.intermediate + /// Enters the solar compiler context, providing access to the HIR and `Gcx`. + pub fn enter( + &self, + f: impl for<'a, 'b, 'gcx> FnOnce(GeneratedOutputRef<'a, 'b, 'gcx>) -> R + Send, + ) -> R { + self.output + .parser() + .solc() + .compiler() + .enter(|c| f(GeneratedOutputRef { output: &self.output, compiler: c })) } } -impl IntermediateOutput { - pub fn get_event(&self, input: &str) -> Option<&pt::EventDefinition> { - self.intermediate_contracts - .get("REPL") - .and_then(|contract| contract.event_definitions.get(input).map(std::ops::Deref::deref)) - } - - pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { - let deployed_bytecode = contract - .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - let deployed_bytecode_bytes = deployed_bytecode - .bytes() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - - let run_func_statements = self.run_func_body()?; - - // Record loc of first yul block return statement (if any). - // This is used to decide which is the final statement within the `run()` method. - // see . - let last_yul_return = run_func_statements.iter().find_map(|statement| { - if let pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } = statement - && let Some(statement) = block.statements.last() - && let pt::YulStatement::FunctionCall(yul_call) = statement - && yul_call.id.name == "return" - { - return Some(statement.loc()); - } - None - }); - - // Find the last statement within the "run()" method and get the program - // counter via the source map. - let Some(final_statement) = run_func_statements.last() else { return Ok(None) }; - - // If the final statement is some type of block (assembly, unchecked, or regular), - // we need to find the final statement within that block. Otherwise, default to - // the source loc of the final statement of the `run()` function's block. - // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. - let mut source_loc = match final_statement { - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(statement) = last_statement { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the asm block. Because we use saturating sub to get the second - // to last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - pt::Statement::Block { loc: _, unchecked: _, statements } => { - if let Some(statement) = statements.last() { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - _ => final_statement.loc(), - }; - - // Consider yul return statement as final statement (if it's loc is lower) . - if let Some(yul_return) = last_yul_return - && yul_return.end() < source_loc.start() - { - source_loc = yul_return; - } - - // Map the source location of the final statement of the `run()` function to its - // corresponding runtime program counter - let final_pc = { - let offset = source_loc.start() as u32; - let length = (source_loc.end() - source_loc.start()) as u32; - trace!(%offset, %length, "find pc"); - contract - .get_source_map_deployed() - .unwrap() - .unwrap() - .into_iter() - .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) - .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, pc)| pc) - .max() - }; - trace!(?final_pc); - Ok(final_pc) - } - - pub fn run_func_body(&self) -> Result<&Vec> { - match self - .intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find REPL intermediate contract!"))? - .function_definitions - .get("run") - .ok_or_else(|| eyre::eyre!("Could not find run function definition in REPL contract!"))? - .body - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find run function body!"))? - { - pt::Statement::Block { statements, .. } => Ok(statements), - _ => eyre::bail!("Could not find statements within run function body!"), - } - } +/// A scoped reference to a [`GeneratedOutput`] together with an entered solar compiler. +pub struct GeneratedOutputRef<'a, 'b, 'gcx> { + output: &'a ProjectCompileOutput, + pub(crate) compiler: &'b CompilerRef<'gcx>, } -// TODO(dani): further migration blocked on upstream work -#[cfg(false)] impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { pub fn gcx(&self) -> Gcx<'gcx> { self.compiler.gcx() @@ -233,8 +73,35 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { self.output.find_first("REPL") } - pub fn get_event(&self, input: &str) -> Option { - self.gcx().hir.events_enumerated().find(|(_, e)| e.name.as_str() == input).map(|(id, _)| id) + /// Looks up the REPL contract in the HIR. + pub fn repl_contract_hir(&self) -> Option<&'gcx Contract<'gcx>> { + self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + } + + /// Returns the body block of the REPL `run()` function. + pub fn run_func_body(&self) -> Block<'gcx> { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); + let f = c + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) + .expect("`run()` function not found in REPL contract"); + hir.function(f).body.expect("`run()` function does not have a body") + } + + /// Returns the [`EventId`] of an event named `input` in the REPL contract, if any. + pub fn get_event(&self, input: &str) -> Option { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir()?; + c.items.iter().find_map(|id| { + if let ItemId::Event(eid) = id + && hir.event(*eid).name.as_str() == input + { + Some(*eid) + } else { + None + } + }) } pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { @@ -251,52 +118,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Record loc of first yul block return statement (if any). // This is used to decide which is the final statement within the `run()` method. // see . - let last_yul_return_span: Option = run_body.iter().find_map(|stmt| { - // TODO(dani): Yul is not yet lowered to HIR. - let _ = stmt; - /* - if let hir::StmtKind::Assembly { block, .. } = stmt { - if let Some(stmt) = block.last() { - if let pt::YulStatement::FunctionCall(yul_call) = stmt { - if yul_call.id.name == "return" { - return Some(stmt.loc()) - } - } - } - } - */ - None - }); + // + // Yul is not yet lowered to HIR (assembly statements appear as `StmtKind::Err`), + // so we walk the AST of the REPL source to find a top-level `return(...)` call + // inside any `assembly { ... }` block in `run()`. + let last_yul_return_span: Option = self.first_yul_return_span(); // Find the last statement within the "run()" method and get the program // counter via the source map. let Some(last_stmt) = run_body.last() else { return Ok(None) }; - // If the final statement is some type of block (assembly, unchecked, or regular), + // If the final statement is some type of block (unchecked or regular), // we need to find the final statement within that block. Otherwise, default to // the source loc of the final statement of the `run()` function's block. // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. + // Inline assembly blocks (lowered to `StmtKind::Err` in HIR in the pinned solar + // version) are handled separately via `trailing_assembly_last_stmt_span`, which + // walks the AST to recover the last meaningful Yul statement. let source_stmt = match &last_stmt.kind { - // TODO(dani): Yul is not yet lowered to HIR. - /* - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(stmt) = last_statement { - stmt - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - &run_body[run_body.len().saturating_sub(2)] - } - } - */ - hir::StmtKind::UncheckedBlock(stmts) | hir::StmtKind::Block(stmts) => { + HirStmtKind::UncheckedBlock(stmts) | HirStmtKind::Block(stmts) => { if let Some(stmt) = stmts.last() { stmt } else { @@ -308,9 +148,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { } _ => last_stmt, }; - let mut source_span = self.stmt_span_without_semicolon(source_stmt); + // If the trailing statement is an assembly block, prefer the last meaningful + // (non-`let`) Yul statement's span as the source location for `final_pc`. + // See . + // + // Two guards are required: + // 1. `StmtKind::Err`, assembly lowers to an error node in the current pinned solar + // version; this ensures we don't apply the AST fallback to properly-lowered stmts. + // 2. `trailing_assembly_last_stmt_span` returning `Some`, verifies via the AST that the + // failing HIR node actually corresponds to an assembly block (not some other lowering + // failure), and supplies the concrete span to use. + let mut source_span = if matches!(last_stmt.kind, HirStmtKind::Err(_)) + && let Some(span) = self.trailing_assembly_last_stmt_span() + { + span + } else { + self.stmt_span_without_semicolon(source_stmt) + }; - // Consider yul return statement as final statement (if it's loc is lower) . + // Consider yul return statement as final statement (if it's loc is lower). if let Some(yul_return_span) = last_yul_return_span && yul_return_span.hi() < source_span.lo() { @@ -319,26 +175,32 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Map the source location of the final statement of the `run()` function to its // corresponding runtime program counter - let (_sf, range) = self.compiler.sess().source_map().span_to_source(source_span).unwrap(); - dbg!(source_span, &range, &_sf.src[range.clone()]); + let result = self + .compiler + .sess() + .source_map() + .span_to_source(source_span) + .map_err(|e| eyre::eyre!("failed to resolve span: {e:?}"))?; + let range = result.data; let offset = range.start as u32; let length = range.len() as u32; - let final_pc = deployed_bytecode - .source_map() + trace!(%offset, %length, "find pc"); + let final_pc = contract + .get_source_map_deployed() .ok_or_else(|| eyre::eyre!("No source map found for `REPL` contract"))?? .into_iter() - .zip(InstructionIter::new(deployed_bytecode_bytes)) + .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, i)| i.pc) - .max() - .unwrap_or_default(); - Ok(Some(final_pc)) + .map(|(_, pc)| pc) + .max(); + trace!(?final_pc); + Ok(final_pc) } /// Statements' ranges in the solc source map do not include the semicolon. - fn stmt_span_without_semicolon(&self, stmt: &hir::Stmt<'_>) -> Span { + fn stmt_span_without_semicolon(&self, stmt: &Stmt<'_>) -> Span { match stmt.kind { - hir::StmtKind::DeclSingle(id) => { + HirStmtKind::DeclSingle(id) => { let decl = self.gcx().hir.variable(id); if let Some(expr) = decl.initializer { stmt.span.with_hi(expr.span.hi()) @@ -346,23 +208,65 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { stmt.span } } - hir::StmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), - hir::StmtKind::Expr(expr) => expr.span, + HirStmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), + HirStmtKind::Expr(expr) => expr.span, _ => stmt.span, } } - fn run_func_body(&self) -> hir::Block<'_> { - let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); - let f = c - .functions() - .find(|&f| self.gcx().hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) - .expect("`run()` function not found in REPL contract"); - self.gcx().hir.function(f).body.expect("`run()` function does not have a body") + /// Returns the AST `run()` body of the REPL contract, if any. + /// + /// Yul/assembly is not yet lowered to HIR in the pinned solar version, so we + /// keep around the AST to be able to inspect inline assembly blocks. + fn repl_run_ast_body(&self) -> Option<&'gcx solar::ast::Block<'gcx>> { + let contract = self.repl_contract_hir()?; + let source = self.gcx().sources.get(contract.source)?; + let ast = source.ast.as_ref()?; + + let contract_ast = ast.items.iter().find_map(|i| match &i.kind { + ItemKind::Contract(c) if c.name.as_str() == "REPL" => Some(c), + _ => None, + })?; + contract_ast.body.iter().find_map(|i| match &i.kind { + ItemKind::Function(f) if f.header.name.is_some_and(|n| n.as_str() == "run") => { + f.body.as_ref() + } + _ => None, + }) } - fn repl_contract_hir(&self) -> Option<&hir::Contract<'_>> { - self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + /// Returns the span of the first top-level `return(...)` call inside any + /// `assembly { ... }` block in the REPL `run()` function, if any. + fn first_yul_return_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + for stmt in run_body.stmts.iter() { + let AstStmtKind::Assembly(asm) = &stmt.kind else { continue }; + for ystmt in asm.block.stmts.iter() { + if let yul::StmtKind::Expr(e) = &ystmt.kind + && let yul::ExprKind::Call(call) = &e.kind + && call.name.as_str() == "return" + { + return Some(ystmt.span); + } + } + } + None + } + + /// If the last statement of the REPL `run()` function is an `assembly { ... }` block, + /// returns the span of its last non-`let` (i.e. non-VarDecl) Yul statement. + /// + /// This mirrors the legacy behavior used to pick a meaningful end-of-function PC when + /// the trailing statement is inline assembly. + fn trailing_assembly_last_stmt_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + let AstStmtKind::Assembly(asm) = &run_body.stmts.last()?.kind else { return None }; + asm.block + .stmts + .iter() + .rev() + .find(|s| !matches!(s.kind, yul::StmtKind::VarDecl(_, _))) + .map(|s| s.span) } } @@ -584,8 +488,7 @@ impl SessionSource { return Ok(output); } let output = self.compile()?; - let intermediate = self.generate_intermediate_output()?; - let output = GeneratedOutput { output, intermediate }; + let output = GeneratedOutput { output }; Ok(self.output.get_or_init(|| output)) } @@ -602,12 +505,11 @@ impl SessionSource { eyre::bail!("{output}"); } - // TODO(dani): re-enable - if cfg!(false) { - output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { - let _ = c.lower_asts(); - }); - } + // Drive HIR lowering and analysis so that subsequent `enter` queries can use them. + output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { + let _ = c.lower_asts(); + let _ = c.analysis(); + }); Ok(output) } @@ -632,53 +534,6 @@ impl SessionSource { sources } - /// Generate intermediate contracts for all contract definitions in the compilation source. - /// - /// ### Returns - /// - /// Optionally, a map of contract names to a vec of [IntermediateContract]s. - pub fn generate_intermediate_contracts(&self) -> Result> { - let mut res_map = HashMap::default(); - let parsed_map = self.get_sources(); - for source in parsed_map.values() { - Self::get_intermediate_contract(&source.content, &mut res_map); - } - Ok(res_map) - } - - /// Generate intermediate output for the REPL contract - pub fn generate_intermediate_output(&self) -> Result { - // Parse generate intermediate contracts - let intermediate_contracts = self.generate_intermediate_contracts()?; - - // Construct variable definitions - let variable_definitions = intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find intermediate REPL contract!"))? - .variable_definitions - .clone() - .into_iter() - .map(|(k, v)| (k, v.ty)) - .collect::>(); - // Construct intermediate output - let mut intermediate_output = IntermediateOutput { - repl_contract_expressions: variable_definitions, - intermediate_contracts, - }; - - // Add all statements within the run function to the repl_contract_expressions map - for (key, val) in intermediate_output - .run_func_body()? - .clone() - .iter() - .flat_map(Self::get_statement_definitions) - { - intermediate_output.repl_contract_expressions.insert(key, val); - } - - Ok(intermediate_output) - } - /// Construct the REPL source. pub fn to_repl_source(&self) -> String { let Self { @@ -741,108 +596,6 @@ contract {contract_name} {{ }); sess.dcx.emitted_errors().unwrap() } - - /// Gets the [IntermediateContract] for a Solidity source string and inserts it into the - /// passed `res_map`. In addition, recurses on any imported files as well. - /// - /// ### Takes - /// - `content` - A Solidity source string - /// - `res_map` - A mutable reference to a map of contract names to [IntermediateContract]s - pub fn get_intermediate_contract( - content: &str, - res_map: &mut HashMap, - ) { - if let Ok((pt::SourceUnit(source_unit_parts), _)) = solang_parser::parse(content, 0) { - let func_defs = source_unit_parts - .into_iter() - .filter_map(|sup| match sup { - pt::SourceUnitPart::ImportDirective(i) => match i { - pt::Import::Plain(s, _) - | pt::Import::Rename(s, _, _) - | pt::Import::GlobalSymbol(s, _, _) => { - let s = match s { - pt::ImportPath::Filename(s) => s.string, - pt::ImportPath::Path(p) => p.to_string(), - }; - let path = PathBuf::from(s); - - match fs::read_to_string(path) { - Ok(source) => { - Self::get_intermediate_contract(&source, res_map); - None - } - Err(_) => None, - } - } - }, - pt::SourceUnitPart::ContractDefinition(cd) => { - let mut intermediate = IntermediateContract::default(); - - cd.parts.into_iter().for_each(|part| match part { - pt::ContractPart::FunctionDefinition(def) => { - // Only match normal function definitions here. - if matches!(def.ty, pt::FunctionTy::Function) { - intermediate - .function_definitions - .insert(def.name.clone().unwrap().name, def); - } - } - pt::ContractPart::EventDefinition(def) => { - let event_name = def.name.as_ref().unwrap().name.clone(); - intermediate.event_definitions.insert(event_name, def); - } - pt::ContractPart::StructDefinition(def) => { - let struct_name = def.name.as_ref().unwrap().name.clone(); - intermediate.struct_definitions.insert(struct_name, def); - } - pt::ContractPart::VariableDefinition(def) => { - let var_name = def.name.as_ref().unwrap().name.clone(); - intermediate.variable_definitions.insert(var_name, def); - } - _ => {} - }); - Some((cd.name.as_ref().unwrap().name.clone(), intermediate)) - } - _ => None, - }) - .collect::>(); - res_map.extend(func_defs); - } - } - - /// Helper to deconstruct a statement - /// - /// ### Takes - /// - /// A reference to a [pt::Statement] - /// - /// ### Returns - /// - /// A vector containing tuples of the inner expressions' names, types, and storage locations. - pub fn get_statement_definitions(statement: &pt::Statement) -> Vec<(String, pt::Expression)> { - match statement { - pt::Statement::VariableDefinition(_, def, _) => { - vec![(def.name.as_ref().unwrap().name.clone(), def.ty.clone())] - } - pt::Statement::Expression(_, pt::Expression::Assign(_, left, _)) => { - if let pt::Expression::List(_, list) = left.as_ref() { - list.iter() - .filter_map(|(_, param)| { - param.as_ref().and_then(|param| { - param - .name - .as_ref() - .map(|name| (name.name.clone(), param.ty.clone())) - }) - }) - .collect() - } else { - Vec::default() - } - } - _ => Vec::default(), - } - } } /// A Parse Tree Fragment diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs index 704d30405eed9..338b7d2043809 100644 --- a/crates/chisel/tests/it/repl/mod.rs +++ b/crates/chisel/tests/it/repl/mod.rs @@ -153,6 +153,26 @@ assembly { repl.expect("[0x00:0x20]"); }); +// Assembly as the final statement with a return — exercises the path where both +// `first_yul_return_span` and `trailing_assembly_last_stmt_span` resolve to the same `return(...)` +// span (no subsequent Solidity statement after the assembly block). +repl_test!(assembly_return_final, |repl| { + repl.sendln("uint x = 0xbeef;"); + repl.sendln("assembly { mstore(0x0, sload(0)) return(0x0, 0x20) }"); + repl.sendln("!md"); + repl.expect("[0x00:0x20]"); +}); + +// Assembly block without a `return(...)` call as an intermediate statement, exercises +// `first_yul_return_span` returning `None` while a subsequent Solidity statement is still evaluated +// correctly. +repl_test!(assembly_no_return_intermediate, |repl| { + repl.sendln("uint x = 1;"); + repl.sendln("assembly { x := add(x, 1) }"); + repl.sendln("x"); + repl.expect("Decimal: 2"); +}); + // Issue #5051, #8978: Test EVM version normalization. repl_test!(flaky_evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { repl.sendln("uint x;\nx"); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 37340f5f4cc3c..606b8291819e4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -52,6 +52,7 @@ rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true serde.workspace = true +toml.workspace = true strsim = "0.11" strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } @@ -70,7 +71,13 @@ tempfile.workspace = true tikv-jemallocator = { workspace = true, optional = true } [features] +default = ["optimism"] tracy = ["dep:tracing-tracy"] tracy-allocator = ["tracy"] jemalloc = ["dep:tikv-jemallocator"] mimalloc = ["dep:mimalloc"] +optimism = [ + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 87f14e2039606..4fc437c7232f8 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -307,6 +307,17 @@ mod tests { assert_eq!(val, &Value::from(1000u64)); } + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + use clap::CommandFactory; + + let command = EvmArgs::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + #[test] fn can_parse_chain_id() { let args = EvmArgs { diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 8c37860446683..f846da5002354 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -66,8 +66,20 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + self.url_with_env(config, std::env::var("ETH_RPC_URL").ok()) + } + + fn url_with_env<'a>( + &'a self, + config: Option<&'a Config>, + env_url: Option, + ) -> Result>> { if self.flashbots { Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else if let Some(url) = self.common.rpc_url.as_deref() { + Ok(Some(Cow::Borrowed(url))) + } else if let Some(url) = env_url { + Ok(Some(Cow::Owned(url))) } else { self.common.url(config) } @@ -85,8 +97,10 @@ impl RpcOpts { pub fn dict(&self) -> Dict { let mut dict = self.common.dict(); - if self.flashbots { - dict.insert("eth_rpc_url".into(), FLASHBOTS_URL.into()); + // `self.url(None)` already accounts for `flashbots` and the `ETH_RPC_URL` env var, + // so a single insert here covers both. + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); } if let Ok(Some(jwt)) = self.jwt(None) { dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); @@ -199,6 +213,7 @@ impl figment::Provider for EthereumOpts { #[cfg(test)] mod tests { use super::*; + use clap::CommandFactory; #[test] fn parse_etherscan_opts() { @@ -223,4 +238,41 @@ mod tests { let id: u64 = chain_id.deserialize().expect("chain_id should deserialize as u64"); assert_eq!(id, 9745); } + + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + let command = RpcOpts::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + + #[test] + fn rpc_url_resolves_eth_rpc_url_env() { + let args = RpcOpts::default(); + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8545"); + } + + #[test] + fn explicit_rpc_url_takes_precedence_over_eth_rpc_url_env() { + let args = RpcOpts { + common: RpcCommonOpts { + rpc_url: Some("http://127.0.0.1:8546".to_string()), + ..Default::default() + }, + ..Default::default() + }; + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8546"); + } } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs index 05b98582fa88f..6a5fe5ed4e9e4 100644 --- a/crates/cli/src/opts/rpc_common.rs +++ b/crates/cli/src/opts/rpc_common.rs @@ -17,10 +17,15 @@ use std::borrow::Cow; /// This struct holds fields that both [`super::RpcOpts`] (cast) and /// [`super::EvmArgs`] (forge/script) need, eliminating duplication and /// making the two structs composable. +/// +/// Note: `ETH_RPC_URL` is intentionally **not** bound here as a clap env +/// fallback; otherwise it would be inherited by `EvmArgs` and silently +/// fork all `forge test` runs. Cast resolves `ETH_RPC_URL` explicitly +/// at the call site (see [`super::RpcOpts::url`]). #[derive(Clone, Debug, Default, Serialize, Parser)] pub struct RpcCommonOpts { /// The RPC endpoint. - #[arg(short, long, visible_alias = "fork-url", env = "ETH_RPC_URL")] + #[arg(short, long, visible_alias = "fork-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub rpc_url: Option, diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs index 8c2a12e661e18..88119f163b325 100644 --- a/crates/cli/src/opts/tempo.rs +++ b/crates/cli/src/opts/tempo.rs @@ -1,16 +1,26 @@ use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, ruint::aliases::U256}; -use alloy_signer::Signature; +use alloy_signer::{Signature, Signer}; use clap::Parser; -use foundry_common::FoundryTransactionBuilder; -use std::{num::NonZeroU64, str::FromStr}; +use eyre::Result; +use foundry_common::{ + FoundryTransactionBuilder, + tempo::{TempoSponsor, resolve_tempo_sponsor_signer}, +}; +use std::{ + num::NonZeroU64, + path::PathBuf, + str::FromStr, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; use crate::utils::parse_fee_token_address; -/// CLI options for Tempo transactions. +/// CLI options common to Tempo transactions across commands. #[derive(Clone, Debug, Default, Parser)] #[command(next_help_heading = "Tempo")] -pub struct TempoOpts { +pub struct TempoCommonOpts { /// Fee token address for Tempo transactions. /// /// When set, builds a Tempo (type 0x76) transaction that pays gas fees @@ -21,6 +31,40 @@ pub struct TempoOpts { #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] pub fee_token: Option
, + /// Opt into TIP-1009 expiring-nonce mode with a validity window. + /// + /// Convenience flag that combines `--tempo.expiring-nonce` with a relative + /// `--tempo.valid-before`. Sets nonce_key = U256::MAX, nonce = 0, and valid_before = now + + /// seconds. + /// + /// Maximum value is 30 seconds. The transaction must be mined before the deadline or it + /// becomes permanently invalid, giving safe retry semantics: retries produce a fresh tx hash + /// and the old tx can never land late. + #[arg(long = "tempo.expires", value_name = "SECONDS", value_parser = parse_expires_seconds)] + pub expires: Option, +} + +impl TempoCommonOpts { + /// Returns `true` if any Tempo-specific option is set. + pub const fn is_tempo(&self) -> bool { + self.fee_token.is_some() || self.expires.is_some() + } + + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + let secs = self.expires?; + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Some(now.as_secs() + secs) + } +} + +/// CLI options for Tempo transactions. +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Tempo")] +pub struct TempoOpts { + #[command(flatten)] + pub common: TempoCommonOpts, + /// Nonce key for Tempo parallelizable nonces. /// /// When set, builds a Tempo (type 0x76) transaction with the specified nonce key, @@ -28,21 +72,69 @@ pub struct TempoOpts { /// to be executed in parallel. If not set, the protocol nonce key (0) will be used. /// /// For more information see . - #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY")] + #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY", conflicts_with = "lane")] pub nonce_key: Option, + /// Named nonce lane for Tempo parallelizable nonces. + /// + /// Resolves a friendly lane name (e.g. `deploy`, `payments`) to a `nonce_key` via a + /// shared lanes file (default: `tempo.lanes.toml` at the project root). The lanes file + /// is a TOML map of `name = ` entries, e.g.: + /// + /// ```toml + /// deploy = 1 + /// ops = 2 + /// payments = 3 + /// ``` + /// + /// Mutually exclusive with `--tempo.nonce-key`. + #[arg(long = "tempo.lane", value_name = "NAME")] + pub lane: Option, + + /// Path to the Tempo lanes file used by `--tempo.lane`. + /// + /// Defaults to `tempo.lanes.toml` at the project root. + #[arg(long = "tempo.lanes-file", value_name = "PATH")] + pub lanes_file: Option, + + /// Sponsor (fee payer) address for Tempo sponsored transactions. + #[arg(long = "tempo.sponsor", value_name = "ADDRESS")] + pub sponsor: Option
, + + /// Sign Tempo sponsor digests in-band with the given signer URI. + /// + /// Supported forms include `env://VAR`, `keystore://PATH`, `account://NAME`, + /// `ledger://`, `trezor://`, `aws://`, `gcp://`, `turnkey://`, and + /// `private-key://KEY`. + #[arg( + long = "tempo.sponsor-signer", + value_name = "SIGNER", + requires = "sponsor", + conflicts_with = "sponsor_sig" + )] + pub sponsor_signer: Option, + /// Sponsor (fee payer) signature for Tempo sponsored transactions. /// /// The sponsor signs the `fee_payer_signature_hash` to commit to paying gas fees /// on behalf of the sender. Provide as a hex-encoded signature. - #[arg(long = "tempo.sponsor-signature", value_parser = parse_signature)] - pub sponsor_signature: Option, + #[arg( + long = "tempo.sponsor-sig", + alias = "tempo.sponsor-signature", + value_parser = parse_signature, + requires = "sponsor", + conflicts_with = "sponsor_signer" + )] + pub sponsor_sig: Option, /// Print the sponsor signature hash and exit. /// /// Computes the `fee_payer_signature_hash` for the transaction so that a sponsor /// knows what hash to sign. The transaction is not sent. - #[arg(long = "tempo.print-sponsor-hash")] + #[arg( + long = "tempo.print-sponsor-hash", + conflicts_with_all = &["sponsor", "sponsor_signer", "sponsor_sig"] + )] pub print_sponsor_hash: bool, /// Access key ID for Tempo Keychain signature transactions. @@ -56,14 +148,14 @@ pub struct TempoOpts { /// /// Sets nonce to 0 and nonce_key to U256::MAX, enabling time-bounded transaction /// validity via `--tempo.valid-before` and `--tempo.valid-after`. - #[arg(long = "tempo.expiring-nonce", requires = "valid_before")] + #[arg(long = "tempo.expiring-nonce", requires = "valid_before", conflicts_with = "expires")] pub expiring_nonce: bool, /// Upper bound timestamp for Tempo expiring nonce transactions. /// /// The transaction is only valid before this unix timestamp. /// Requires `--tempo.expiring-nonce`. - #[arg(long = "tempo.valid-before")] + #[arg(long = "tempo.valid-before", conflicts_with = "expires")] pub valid_before: Option, /// Lower bound timestamp for Tempo expiring nonce transactions. @@ -77,9 +169,12 @@ pub struct TempoOpts { impl TempoOpts { /// Returns `true` if any Tempo-specific option is set. pub const fn is_tempo(&self) -> bool { - self.fee_token.is_some() + self.common.is_tempo() || self.nonce_key.is_some() - || self.sponsor_signature.is_some() + || self.lane.is_some() + || self.sponsor.is_some() + || self.sponsor_signer.is_some() + || self.sponsor_sig.is_some() || self.print_sponsor_hash || self.key_id.is_some() || self.expiring_nonce @@ -87,6 +182,58 @@ impl TempoOpts { || self.valid_after.is_some() } + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + self.common.expires_at() + } + + /// Resolves `--tempo.expires` into concrete expiring-nonce fields. + /// + /// This computes the relative deadline once so later calls to [`Self::apply`] reuse the same + /// `valid_before` timestamp instead of deriving a fresh one. + pub fn resolve_expires(&mut self) -> Option { + let ts = self.expires_at()?; + self.expiring_nonce = true; + self.valid_before = Some(ts); + self.common.expires = None; + Some(ts) + } + + /// Returns `true` if a sponsor signature should be attached before submission. + pub const fn has_sponsor_submission(&self) -> bool { + self.sponsor.is_some() || self.sponsor_signer.is_some() || self.sponsor_sig.is_some() + } + + /// Resolves sponsor CLI options into a reusable sponsor config for transaction submission. + pub async fn sponsor_config(&self) -> Result> { + let Some(sponsor) = self.sponsor else { + return Ok(None); + }; + + let signer = if let Some(spec) = &self.sponsor_signer { + Some(Arc::new(Box::pin(resolve_tempo_sponsor_signer(spec)).await?)) + } else { + None + }; + + if let Some(signer) = &signer { + let signer_address = signer.address(); + if signer_address != sponsor { + eyre::bail!( + "Tempo sponsor signer address {signer_address} does not match --tempo.sponsor {sponsor}" + ); + } + } + + if signer.is_none() && self.sponsor_sig.is_none() { + eyre::bail!( + "--tempo.sponsor requires either --tempo.sponsor-signer or --tempo.sponsor-sig" + ); + } + + Ok(Some(TempoSponsor::new(sponsor, signer, self.sponsor_sig))) + } + /// Applies Tempo-specific options to a transaction request. /// /// All setters are no-ops for non-Tempo networks, so this is safe to call unconditionally. @@ -94,8 +241,9 @@ impl TempoOpts { where N::TransactionRequest: FoundryTransactionBuilder, { - // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX - if self.expiring_nonce { + // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX. + // --tempo.expires is a convenience alias that also sets valid_before = now + duration. + if self.expiring_nonce || self.common.expires.is_some() { tx.set_nonce(0); tx.set_nonce_key(U256::MAX); } else { @@ -107,11 +255,14 @@ impl TempoOpts { } } - if let Some(fee_token) = self.fee_token { + if let Some(fee_token) = self.common.fee_token { tx.set_fee_token(fee_token); } - if let Some(valid_before) = self.valid_before + // --tempo.expires sets valid_before relative to now; --tempo.valid-before takes a raw + // unix timestamp. The two flags are mutually exclusive (enforced by clap). + let effective_valid_before = self.expires_at().or(self.valid_before); + if let Some(valid_before) = effective_valid_before && let Some(v) = NonZeroU64::new(valid_before) { tx.set_valid_before(v); @@ -131,8 +282,7 @@ impl TempoOpts { // gas estimation so that `--tempo.print-sponsor-hash` and // `--tempo.sponsor-signature` produce identical gas estimates. Callers // should call `set_fee_payer_signature` on the built tx request. - if (self.sponsor_signature.is_some() || self.print_sponsor_hash) && tx.nonce_key().is_none() - { + if (self.has_sponsor_submission() || self.print_sponsor_hash) && tx.nonce_key().is_none() { tx.set_nonce_key(U256::ZERO); } } @@ -142,11 +292,83 @@ fn parse_signature(s: &str) -> Result { Signature::from_str(s).map_err(|e| format!("invalid signature: {e}")) } +/// Parses a seconds value for `--tempo.expires`, capped at the protocol maximum of 30 seconds. +fn parse_expires_seconds(s: &str) -> Result { + let secs: u64 = s + .parse() + .map_err(|_| format!("invalid value '{s}': expected an integer number of seconds"))?; + if secs > 30 { + return Err(format!("expires must be at most 30 seconds (got {secs})")); + } + Ok(secs) +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::address; + #[test] + fn parses_lane_arg() { + let opts = TempoOpts::try_parse_from(["", "--tempo.lane", "deploy"]).unwrap(); + assert_eq!(opts.lane.as_deref(), Some("deploy")); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn lane_conflicts_with_nonce_key() { + let err = + TempoOpts::try_parse_from(["", "--tempo.lane", "deploy", "--tempo.nonce-key", "1"]) + .unwrap_err(); + assert!( + err.to_string().contains("cannot be used with"), + "expected clap conflict error, got: {err}", + ); + } + + #[test] + fn parse_expires_flag() { + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "30"]).unwrap(); + assert_eq!(opts.common.expires, Some(30)); + + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + assert_eq!(opts.common.expires, Some(10)); + + // exceeds 30s maximum + assert!(TempoOpts::try_parse_from(["", "--tempo.expires", "31"]).is_err()); + + // conflicts with --tempo.expiring-nonce + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.expires", + "30", + "--tempo.expiring-nonce", + "--tempo.valid-before", + "999" + ]) + .is_err() + ); + } + + #[test] + fn resolve_expires_materializes_valid_before() { + let before = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + let mut opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + + let resolved = opts.resolve_expires().unwrap(); + let after = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + + assert!(resolved >= before + 10); + assert!(resolved <= after + 10); + assert!(opts.expiring_nonce); + assert_eq!(opts.valid_before, Some(resolved)); + assert_eq!(opts.common.expires, None); + assert_eq!(opts.expires_at(), None); + } + #[test] fn parse_fee_token_id() { let opts = TempoOpts::try_parse_from([ @@ -155,13 +377,69 @@ mod tests { "0x20C0000000000000000000000000000000000002", ]) .unwrap(); - assert_eq!(opts.fee_token, Some(address!("0x20C0000000000000000000000000000000000002")),); + assert_eq!( + opts.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000002")), + ); // AlphaUSD token ID is 1u64 let opts_with_id = TempoOpts::try_parse_from(["", "--tempo.fee-token", "1"]).unwrap(); assert_eq!( - opts_with_id.fee_token, + opts_with_id.common.fee_token, Some(address!("0x20C0000000000000000000000000000000000001")), ); } + + #[test] + fn parse_sponsor_signer() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert_eq!(opts.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + assert!(opts.sponsor_sig.is_none()); + assert!(opts.is_tempo()); + assert!(opts.has_sponsor_submission()); + } + + #[test] + fn sponsor_signer_requires_sponsor() { + assert!( + TempoOpts::try_parse_from(["", "--tempo.sponsor-signer", "env://SPONSOR"]).is_err() + ); + } + + #[test] + fn parse_sponsor_signature_alias() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signature", + "0x0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert!(opts.sponsor_sig.is_some()); + } + + #[test] + fn print_sponsor_hash_conflicts_with_sponsor_submission() { + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.print-sponsor-hash", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + ]) + .is_err() + ); + } } diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs index 647f52d316a6c..4b5715b9ebe08 100644 --- a/crates/cli/src/utils/tempo.rs +++ b/crates/cli/src/utils/tempo.rs @@ -1,8 +1,44 @@ -use std::str::FromStr; +//! Tempo utilities: fee token parsing and named nonce lanes (2D nonces). +//! +//! A "lane" is a friendly alias for a Tempo `nonce_key` (a [`U256`]). Lanes are defined in a +//! shared TOML file (default `tempo.lanes.toml` at the project root) so a team can reserve +//! independent sequential nonce streams for parallel scripts without coordinating on raw +//! `U256` selectors. +//! +//! Example `tempo.lanes.toml`: +//! +//! ```toml +//! deploy = 1 +//! ops = 2 +//! payments = 3 +//! ``` +//! +//! ```bash +//! cast erc20 transfer ... --tempo.lane payments +//! ``` -use alloy_primitives::Address; +use crate::opts::TempoOpts; +use alloy_primitives::{Address, U256}; +use eyre::{Result, eyre}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, +}; use tempo_primitives::TempoAddressExt; +/// Default name of the lanes file at the project root. +pub const DEFAULT_LANES_FILE: &str = "tempo.lanes.toml"; + +/// Result of resolving a `--tempo.lane ` argument against a lanes file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResolvedLane { + /// The lane name as provided on the CLI. + pub name: String, + /// The `nonce_key` the lane resolved to. + pub nonce_key: U256, +} + /// Parses a fee token address. pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ Address::from_str(address_or_id).or_else(|_| Ok(token_id_to_address(address_or_id.parse()?))) @@ -14,3 +50,156 @@ fn token_id_to_address(token_id: u64) -> Address { address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); Address::from(address_bytes) } + +/// Loads a TOML lanes file from `path`. +/// +/// Each top-level key is a lane name, and the value is the `nonce_key` (an integer or a +/// decimal/hex string parsed as [`U256`]). +pub fn load_lanes(path: &Path) -> Result> { + let contents = std::fs::read_to_string(path) + .map_err(|e| eyre!("failed to read tempo lanes file {}: {}", path.display(), e))?; + parse_lanes(&contents) + .map_err(|e| eyre!("failed to parse tempo lanes file {}: {}", path.display(), e)) +} + +fn parse_lanes(contents: &str) -> Result> { + let raw: BTreeMap = toml::from_str(contents)?; + let mut out = BTreeMap::new(); + for (name, value) in raw { + let nonce_key = match value { + toml::Value::Integer(n) => { + if n < 0 { + return Err(eyre!("invalid nonce_key for lane '{name}': must be non-negative")); + } + U256::from(n as u64) + } + toml::Value::String(s) => U256::from_str(s.trim()) + .map_err(|e| eyre!("invalid nonce_key for lane '{name}': {e}"))?, + other => { + return Err(eyre!( + "invalid nonce_key for lane '{name}': expected integer or string, got {}", + other.type_str(), + )); + } + }; + out.insert(name, nonce_key); + } + Ok(out) +} + +/// Resolves `opts.lane` against a lanes file and writes the resulting `nonce_key` to +/// `opts.nonce_key`. Returns the resolved lane (or `None` if no `--tempo.lane` was set). +/// +/// `root` is the project root used to locate the default lanes file +/// (`/tempo.lanes.toml`) when `--tempo.lanes-file` was not provided. +pub fn resolve_lane(opts: &mut TempoOpts, root: &Path) -> Result> { + let Some(lane_name) = opts.lane.clone() else { return Ok(None) }; + + let path: PathBuf = opts.lanes_file.clone().unwrap_or_else(|| root.join(DEFAULT_LANES_FILE)); + + if !path.exists() { + return Err(eyre!( + "tempo lanes file not found at {}\n\ + create it with `name = ` entries, e.g.:\n \ + deploy = 1\n \ + ops = 2\n \ + payments = 3", + path.display(), + )); + } + + let lanes = load_lanes(&path)?; + + let nonce_key = lanes.get(&lane_name).copied().ok_or_else(|| { + let mut known: Vec<&str> = lanes.keys().map(String::as_str).collect(); + known.sort_unstable(); + eyre!( + "lane '{lane_name}' not found in {} (known lanes: {})", + path.display(), + if known.is_empty() { "".to_string() } else { known.join(", ") }, + ) + })?; + + opts.nonce_key = Some(nonce_key); + Ok(Some(ResolvedLane { name: lane_name, nonce_key })) +} + +/// Prints `lane: (nonce_key=, nonce=)` to stderr (so it doesn't pollute +/// stdout for commands like `cast mktx` whose stdout is meant to be piped), giving +/// visibility into which 2D nonce lane was used. +pub fn maybe_print_resolved_lane(resolved: Option<&ResolvedLane>, nonce: u64) -> Result<()> { + if let Some(lane) = resolved { + sh_eprintln!("lane: {} (nonce_key={}, nonce={})", lane.name, lane.nonce_key, nonce)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_int_and_string_lane_values() { + let toml = r#" +deploy = 1 +ops = 2 +payments = "3" +big = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +"#; + let lanes = parse_lanes(toml).unwrap(); + assert_eq!(lanes.get("deploy"), Some(&U256::from(1u64))); + assert_eq!(lanes.get("ops"), Some(&U256::from(2u64))); + assert_eq!(lanes.get("payments"), Some(&U256::from(3u64))); + assert_eq!(lanes.get("big"), Some(&U256::MAX)); + } + + #[test] + fn parse_lanes_rejects_invalid_string() { + let toml = "broken = \"not-a-number\""; + let err = parse_lanes(toml).unwrap_err(); + assert!(err.to_string().contains("invalid nonce_key for lane 'broken'")); + } + + #[test] + fn resolve_lane_sets_nonce_key_and_returns_resolved() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 7\npayments = 42\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let resolved = resolve_lane(&mut opts, dir.path()).unwrap().unwrap(); + assert_eq!(resolved.name, "payments"); + assert_eq!(resolved.nonce_key, U256::from(42u64)); + assert_eq!(opts.nonce_key, Some(U256::from(42u64))); + } + + #[test] + fn resolve_lane_returns_none_when_no_lane() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts::default(); + let resolved = resolve_lane(&mut opts, dir.path()).unwrap(); + assert!(resolved.is_none()); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn resolve_lane_errors_when_file_missing() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts { lane: Some("deploy".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + assert!(err.to_string().contains("tempo lanes file not found")); + } + + #[test] + fn resolve_lane_errors_when_lane_unknown() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 1\nops = 2\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("lane 'payments' not found")); + assert!(msg.contains("deploy, ops")); + } +} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 7fd94c07242e8..a66a0fef2fe0a 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -34,7 +34,7 @@ alloy-signer.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } -alloy-rpc-types-engine = { workspace = true, features = ["jwt"] } +alloy-rpc-types-engine = { workspace = true, features = ["jwt-aws-lc-rs"] } alloy-sol-types.workspace = true alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true @@ -43,8 +43,8 @@ alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true -op-alloy-network.workspace = true -op-alloy-rpc-types.workspace = true +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } revm.workspace = true @@ -86,6 +86,10 @@ mpp.workspace = true foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tokio-tungstenite.workspace = true futures.workspace = true +alloy-signer-local.workspace = true +base64.workspace = true +sha2 = "0.10" +tempfile.workspace = true [build-dependencies] chrono.workspace = true @@ -95,4 +99,12 @@ vergen = { workspace = true, features = ["build", "emit_and_set"] } foundry-evm-hardforks.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } axum = { workspace = true } -tempfile.workspace = true +k256 = { workspace = true } + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "foundry-common-fmt/optimism", +] diff --git a/crates/common/build.rs b/crates/common/build.rs index d89e23be850f4..9afa01b5757ef 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -15,16 +15,13 @@ fn main() -> Result<(), Box> { let sha_short = &sha[..10]; let tag_name = try_env_var("TAG_NAME").unwrap_or_else(|| String::from("dev")); - let is_nightly = tag_name.contains("nightly"); - let version_suffix = if is_nightly { "nightly" } else { &tag_name }; + let version = release_version(&env_var("CARGO_PKG_VERSION"), &tag_name); + let is_nightly = tag_name.starts_with("nightly"); if is_nightly { println!("cargo:rustc-env=FOUNDRY_IS_NIGHTLY_VERSION=true"); } - let pkg_version = env_var("CARGO_PKG_VERSION"); - let version = format!("{pkg_version}-{version_suffix}"); - // `PROFILE` captures only release or debug. Get the actual name from the out directory. let out_dir = PathBuf::from(env_var("OUT_DIR")); let profile = out_dir.components().rev().nth(3).unwrap().as_os_str().to_str().unwrap(); @@ -87,6 +84,19 @@ fn env_var(name: &str) -> String { try_env_var(name).unwrap() } +fn release_version(pkg_version: &str, tag_name: &str) -> String { + if let Some(version) = tag_name.strip_prefix('v') { + return version.to_owned(); + } + + // Normalize `nightly-` to `nightly` so tarball and Docker nightly + // artifacts produce the same version string. The commit identifier is + // already included in the SemVer build metadata (after `+`). + let normalized = if tag_name.starts_with("nightly-") { "nightly" } else { tag_name }; + + format!("{pkg_version}-{normalized}") +} + fn try_env_var(name: &str) -> Option { println!("cargo:rerun-if-env-changed={name}"); std::env::var(name).ok() diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 2c8e16bccdcc6..179c71048da5b 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -20,10 +20,10 @@ eyre.workspace = true # ui alloy-consensus.workspace = true -op-alloy-consensus.workspace = true +op-alloy-consensus = { workspace = true, optional = true } alloy-network.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } -op-alloy-rpc-types.workspace = true +op-alloy-rpc-types = { workspace = true, optional = true } alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true @@ -38,3 +38,7 @@ tempo-alloy.workspace = true [dev-dependencies] foundry-macros.workspace = true similar-asserts.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:op-alloy-consensus", "dep:op-alloy-rpc-types"] diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index e883810dcda34..2087a85236154 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -18,6 +18,7 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; @@ -448,6 +449,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxDeposit { fn pretty(&self) -> String { format!( @@ -472,6 +474,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxPostExec { fn pretty(&self) -> String { format!( @@ -606,6 +609,7 @@ type {:#x} } } +#[cfg(feature = "optimism")] impl UIfmt for OpTxEnvelope { fn pretty(&self) -> String { match self { @@ -651,6 +655,7 @@ effectiveGasPrice {} } } +#[cfg(feature = "optimism")] impl UIfmt for op_alloy_rpc_types::Transaction { fn pretty(&self) -> String { format!( @@ -786,6 +791,7 @@ impl UIfmtSignatureExt for AnyTxEnvelope { } } +#[cfg(feature = "optimism")] impl UIfmtSignatureExt for OpTxEnvelope { fn signature_pretty(&self) -> Option<(String, String, String)> { self.signature().map(|sig| { @@ -1135,6 +1141,7 @@ mod tests { assert_eq!(b.pretty(), b32.pretty()); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx() { let s = r#" @@ -1186,6 +1193,7 @@ yParity 1 ); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx_through_any() { let s = r#" diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 895b16b3b4532..95c7d4083f34e 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -383,16 +383,14 @@ impl ContractsByArtifact { &self, id: &str, ) -> Result>> { - let contracts = self - .iter() - .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) - .collect::>(); - - if contracts.len() > 1 { + let mut iter = + self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id); + let first = iter.next(); + if first.is_some() && iter.next().is_some() { eyre::bail!("{id} has more than one implementation."); } - Ok(contracts.first().copied()) + Ok(first) } /// Finds abi by name or source path @@ -411,7 +409,7 @@ impl ContractsByArtifact { let mut funcs = BTreeMap::new(); let mut events = BTreeMap::new(); let mut errors_abi = JsonAbi::new(); - for (_name, contract) in self.iter() { + for contract in self.values() { for func in contract.abi.functions() { funcs.insert(func.selector(), func.clone()); } diff --git a/crates/common/src/provider/mpp/keys.rs b/crates/common/src/provider/mpp/keys.rs index 65640c48ab841..fa0fc80ed3d03 100644 --- a/crates/common/src/provider/mpp/keys.rs +++ b/crates/common/src/provider/mpp/keys.rs @@ -7,6 +7,7 @@ use crate::tempo::{TEMPO_PRIVATE_KEY_ENV, WalletType, read_tempo_keys_file}; use alloy_primitives::Address; +use std::env; use tracing::debug; /// Options for MPP key discovery filtering. @@ -55,7 +56,7 @@ pub fn discover_mpp_key() -> Option { /// target chain and the required currency. pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 1. Check TEMPO_PRIVATE_KEY env var (no keychain metadata available) - if let Ok(key) = std::env::var(TEMPO_PRIVATE_KEY_ENV) { + if let Ok(key) = env::var(TEMPO_PRIVATE_KEY_ENV) { let key = key.trim().to_string(); if !key.is_empty() { debug!("using MPP key from {TEMPO_PRIVATE_KEY_ENV} env var"); @@ -73,11 +74,17 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 2. Read $TEMPO_HOME/wallet/keys.toml (default: ~/.tempo/wallet/keys.toml) let keys_file = read_tempo_keys_file()?; + // `expiry == 0` means "no expiry" on the wire. + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + // Pick primary key using the same deterministic order as // `Keystore::primary_key()` in tempo-common: // passkey > first entry with inline key > first entry // Only entries with a usable inline key can provide a signing key. - // Filter by chain_id and currency when provided. + // Filter by chain_id, currency, and freshness when provided. let candidates: Vec<_> = keys_file .keys .iter() @@ -86,6 +93,7 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { opts.currency .is_none_or(|cur| k.limits.is_empty() || k.limits.iter().any(|l| l.currency == cur)) }) + .filter(|k| k.expiry.is_none_or(|e| e == 0 || e > now)) .collect(); let primary = candidates @@ -135,6 +143,7 @@ mod tests { #[test] fn discover_from_tempo_home_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( r#" @@ -160,6 +169,7 @@ chain_id = 4217 #[test] fn discover_env_var_takes_priority_over_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let file_key = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; let env_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( @@ -187,6 +197,7 @@ key = "{file_key}" #[test] fn discover_returns_none_when_no_keys() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let (dir, _) = setup_keys_toml(""); unsafe { @@ -202,6 +213,7 @@ key = "{file_key}" #[test] fn discover_skips_entries_without_inline_key() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; let toml_content = format!( r#" @@ -344,6 +356,7 @@ key = "0xthe_key" #[test] fn discover_filters_by_chain_id() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let mainnet_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let testnet_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let toml_content = format!( @@ -416,6 +429,62 @@ chain_id = 4217 unsafe { std::env::remove_var("TEMPO_HOME") }; } + #[test] + fn discover_filters_expired_entries() { + // Expired entries must not be selected, so the next 402 re-triggers + // the device-code flow instead of returning a stale key. + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let expired_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let fresh_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let toml_content = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{fresh_key}" +chain_id = 4217 +expiry = 0 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + // Even though the expired entry comes first, discovery skips it. + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, fresh_key); + + // With only the expired entry present, discovery returns None so the + // 402 path can run `ensure_access_key` again. + let only_expired = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 +"# + ); + let (dir2, _) = setup_keys_toml(&only_expired); + unsafe { std::env::set_var("TEMPO_HOME", dir2.path()) }; + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert!(config.is_none(), "expired-only keys.toml must not yield a usable key"); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + #[test] fn parse_keys_toml_unknown_fields_ignored() { let toml_str = r#" diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 334166b844613..c3e87f8cf42b5 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -175,6 +175,16 @@ impl SessionProvider { self } + /// Address that funds payments for this provider. + pub fn funding_wallet_address(&self) -> Address { + self.signing_mode.from_address(self.signer.address()) + } + + /// Chain ID from the selected wallet key, when known. + pub const fn key_chain_id(&self) -> Option { + self.key_chain_id + } + /// Set the chain ID and currencies from the key entry used to initialize /// this provider. Used to reject challenges for incompatible chains/currencies. /// When `chain_id` is `None` (e.g. env var key), chain filtering is skipped. diff --git a/crates/common/src/provider/mpp/transport.rs b/crates/common/src/provider/mpp/transport.rs index 67354dc2bd60d..9e3b16dedd59e 100644 --- a/crates/common/src/provider/mpp/transport.rs +++ b/crates/common/src/provider/mpp/transport.rs @@ -4,6 +4,7 @@ //! handling via the MPP protocol. When the RPC endpoint returns a 402 response, //! this transport automatically pays the challenge and retries the request. +use alloy_chains::Chain; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_transport::{TransportError, TransportErrorKind, TransportFut, TransportResult}; use mpp::{ @@ -16,12 +17,17 @@ use mpp::{ use reqwest::{StatusCode, header::HeaderMap}; use std::{ collections::HashMap, - fmt, - sync::{Mutex, OnceLock}, + env, fmt, io, + io::IsTerminal, + process::{Command, Stdio}, + sync::{ + Arc, LazyLock, Mutex, + atomic::{AtomicBool, Ordering}, + }, task, time::Duration, }; -use tokio::sync::OwnedMutexGuard; +use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard}; use tower::Service; use tracing::{Instrument, debug, debug_span, trace}; use url::Url; @@ -39,7 +45,27 @@ const MPP_RETRY_TIMEOUT: Duration = Duration::from_secs(120); /// Resolve the deposit amount from `MPP_DEPOSIT` env var or the default. fn default_deposit() -> u128 { - std::env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) + env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct FundingContext { + wallet_address: Option, + token: Option, + chain_id: Option, +} + +impl FundingContext { + fn token_line(&self) -> String { + self.token + .as_ref() + .map(|token| format!("Requested payment token: {token}\n\n")) + .unwrap_or_default() + } + + fn network(&self) -> Option { + self.chain_id.filter(|chain| chain.is_tempo()).map(|chain| chain.to_string()) + } } fn format_http_diagnostics(headers: &HeaderMap) -> String { @@ -60,12 +86,173 @@ fn format_http_diagnostics(headers: &HeaderMap) -> String { } } +fn tempo_wallet_fund_help(ctx: &FundingContext) -> String { + let mut command = "tempo wallet fund".to_string(); + if let Some(address) = ctx.wallet_address { + command.push_str(&format!(" --address {address}")); + } + if let Some(network) = ctx.network() { + command.push_str(&format!(" --network {network}")); + } + + let mut no_browser = command.clone(); + no_browser.push_str(" --no-browser"); + + format!( + "\n\nTempo wallet payment could not be funded for this paid RPC request.\n\n{}\ + Fund the wallet, then rerun the command:\n {command}\n\n\ + If this CLI is running on a remote or headless host, use:\n {no_browser}", + ctx.token_line() + ) +} + +/// Decide whether the interactive `tempo wallet fund` flow may be launched. +/// +/// Policy (library-safe): +/// - never run inside CI +/// - never run unless both stdin and stderr are real terminals +/// - `FOUNDRY_MPP_NO_AUTO_FUND` is honored as an opt-out; it must not bypass CI/TTY guards in +/// shared transport code that may be embedded inside long-running RPC daemons. +fn interactive_tempo_fund_allowed( + no_auto_fund: Option<&str>, + in_ci: bool, + stdin_is_terminal: bool, + stderr_is_terminal: bool, +) -> bool { + if no_auto_fund.is_some_and(|v| { + !(v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off")) + }) { + return false; + } + + if in_ci { + return false; + } + + stdin_is_terminal && stderr_is_terminal +} + +fn can_run_interactive_tempo_fund() -> bool { + if cfg!(test) { + return false; + } + + interactive_tempo_fund_allowed( + std::env::var("FOUNDRY_MPP_NO_AUTO_FUND").ok().as_deref(), + std::env::var_os("CI").is_some(), + std::io::stdin().is_terminal(), + std::io::stderr().is_terminal(), + ) +} + +fn tempo_bin() -> String { + std::env::var("TEMPO_BIN").unwrap_or_else(|_| "tempo".to_string()) +} + +async fn run_interactive_tempo_fund(ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + + let tempo = tempo_bin(); + let mut args = vec!["wallet".to_string(), "fund".to_string()]; + if let Some(address) = ctx.wallet_address { + args.push("--address".to_string()); + args.push(address.to_string()); + } + if let Some(network) = ctx.network() { + args.push("--network".to_string()); + args.push(network); + } + + tracing::warn!( + token = ?ctx.token, + chain_id = ?ctx.chain_id, + "MPP payment could not be funded; opening `tempo wallet fund`" + ); + + let status = tokio::task::spawn_blocking(move || { + Command::new(tempo) + .args(args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + }) + .await + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to join tempo wallet fund process: {e}" + ))) + })? + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to run `tempo wallet fund`: {e}{}", + tempo_wallet_fund_help(ctx) + ))) + })?; + + if status.success() { + Ok(true) + } else { + Err(TransportErrorKind::custom(std::io::Error::other(format!( + "`tempo wallet fund` exited with status {status}{}", + tempo_wallet_fund_help(ctx) + )))) + } +} + +/// Single-attempt guard around [`run_interactive_tempo_fund`]. +/// +/// Ensures that for one logical request we launch `tempo wallet fund` at most +/// once, regardless of how many recovery paths (`do_request`, `pay_and_retry`, +/// `handle_response_or_retry_after_fund`, ...) attempt it. +async fn maybe_auto_fund(used: &AtomicBool, ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + if used.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Ok(false); + } + run_interactive_tempo_fund(ctx).await +} + +/// Returns true iff a 402 response carries a structured insufficient-balance +/// problem (RFC 9457 `PaymentErrorDetails`). +/// +/// We deliberately do **not** match on free-text body content or on generic +/// `verification-failed` problem types, as those have many non-funding causes +/// (bad signature, replay, expired challenge, clock skew, key provisioning, +/// malformed auth, ...). +fn should_suggest_tempo_fund(status: StatusCode, body: &[u8]) -> bool { + if status != StatusCode::PAYMENT_REQUIRED { + return false; + } + let Ok(problem) = serde_json::from_slice::(body) else { + return false; + }; + problem.problem_type.ends_with("/insufficient-balance") +} + +fn format_mpp_payment_failure( + error: impl fmt::Display, + ctx: &FundingContext, + suggest_fund: bool, +) -> String { + let message = error.to_string(); + if suggest_fund { + format!("MPP payment failed: {message}{}", tempo_wallet_fund_help(ctx)) + } else { + format!("MPP payment failed: {message}") + } +} + /// Process-wide payment serialization locks, keyed by origin URL. /// /// Created eagerly so the lock exists before the first provider init, /// preventing concurrent first-402 races. -static GLOBAL_PAY_LOCKS: OnceLock>>>> = - OnceLock::new(); +static GLOBAL_PAY_LOCKS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); /// Production transport: lazily discovers MPP keys from the Tempo wallet on /// first 402 response. @@ -75,24 +262,21 @@ pub type LazyMppHttpTransport = MppHttpTransport; /// Tempo wallet configuration on first use. #[derive(Clone, Debug)] pub struct LazySessionProvider { - inner: std::sync::Arc>>, + inner: Arc>>, /// Eagerly-created, process-wide payment serialization lock for this origin. - pay_lock: std::sync::Arc>, + pay_lock: Arc>, origin: String, } impl LazySessionProvider { pub(super) fn new(origin: String) -> Self { - let pay_lock = { - let global = GLOBAL_PAY_LOCKS.get_or_init(|| Mutex::new(HashMap::new())); - global - .lock() - .unwrap() - .entry(origin.clone()) - .or_insert_with(|| std::sync::Arc::new(tokio::sync::Mutex::new(()))) - .clone() - }; - Self { inner: std::sync::Arc::new(Mutex::new(None)), pay_lock, origin } + let pay_lock = GLOBAL_PAY_LOCKS + .lock() + .unwrap() + .entry(origin.clone()) + .or_insert_with(|| Arc::new(AsyncMutex::new(()))) + .clone(); + Self { inner: Arc::new(Mutex::new(None)), pay_lock, origin } } fn set_key_provisioned(&self, provisioned: bool) { @@ -125,6 +309,14 @@ impl LazySessionProvider { } } + /// Drop the cached `SessionProvider` so the next `get_or_init` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry, so a long-lived transport doesn't keep paying with + /// the superseded key. + fn invalidate(&self) { + *self.inner.lock().unwrap() = None; + } + pub(super) fn get_or_init(&self, opts: DiscoverOptions) -> TransportResult { let mut guard = self.inner.lock().unwrap(); if let Some(ref provider) = *guard { @@ -132,18 +324,20 @@ impl LazySessionProvider { } let config = discover_mpp_config(opts).ok_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "RPC endpoint returned HTTP 402 Payment Required. \ This endpoint requires payment via the Machine Payments Protocol (MPP).\n\n\ - To configure MPP, install the Tempo wallet CLI and create a key:\n\ - \n curl -sSL https://tempo.xyz/install.sh | bash\ - \n tempo wallet login\ + Authorize an access key against your Tempo wallet:\n\ + \n cast tempo login\ + \n\nIn headless environments, pass `--no-browser` to print the authorization \ + URL instead of launching a browser:\n\ + \n cast tempo login --no-browser\ \n\nSee https://docs.tempo.xyz for more information.", )) })?; let signer: mpp::PrivateKeySigner = config.key.parse().map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("invalid MPP key: {e}"))) + TransportErrorKind::custom(io::Error::other(format!("invalid MPP key: {e}"))) })?; let signing_mode = if let Some(wallet) = config.wallet_address { @@ -152,7 +346,7 @@ impl LazySessionProvider { .as_ref() .map(|hex_str| { crate::tempo::decode_key_authorization(hex_str).map(Box::new).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "invalid MPP key_authorization: {e}" ))) }) @@ -223,6 +417,17 @@ where P::Provider: Send + Sync + 'static, { async fn do_request(self, req: RequestPacket) -> TransportResult { + // Per-request guard: launch `tempo wallet fund` at most once for one + // logical request, regardless of how many recovery paths attempt it. + let auto_fund_used = AtomicBool::new(false); + self.do_request_inner(req, &auto_fund_used).await + } + + async fn do_request_inner( + self, + req: RequestPacket, + auto_fund_used: &AtomicBool, + ) -> TransportResult { let body = serde_json::to_vec(&req).map_err(TransportErrorKind::custom)?; let headers = req.headers(); @@ -246,15 +451,53 @@ where // held until the retry response is fully handled. let _pay_guard = self.provider.lock_pay().await; - let (resolved, challenge) = Self::select_challenge(&resp, &self.provider)?; + // No local key for any offered challenge → run device-code flow, + // invalidate the cached provider, and fetch a fresh 402 (the original + // may have expired during the browser/passkey flow). + let (resolved, challenge) = + if let Some(chain_id) = tempo_chain_needing_auth(&self.url, &resp) { + debug!(chain_id, "launching wallet.tempo authorization"); + let cfg = crate::tempo::EnsureAccessKeyConfig::from_env(chain_id); + crate::tempo::ensure_access_key(cfg).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "tempo access key authorization failed: {e}" + ))) + })?; + self.provider.invalidate_cached_provider(); + self.fetch_fresh_challenge(&headers, &body).await? + } else { + Self::select_challenge(&resp, &self.provider)? + }; + let funding_ctx = self.provider.funding_context(&challenge); debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP 402 challenge, paying"); - let credential = resolved.pay(&challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let credential = match resolved.pay(&challenge).await { + Ok(credential) => credential, + Err(e) => { + // Only the explicit `InsufficientBalance` variant is treated as + // a fundable error. Any other failure must surface unchanged so + // we don't mask payment/protocol issues behind a fund prompt. + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + resolved.pay(&challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); TransportErrorKind::custom(std::io::Error::other(format!( "failed to format MPP credential: {e}" ))) @@ -286,9 +529,20 @@ where self.provider.commit_topup_and_track_voucher(); let resolved = self.provider.resolve()?; - let voucher_resp = self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(voucher_resp).await; + let voucher_resp = + self.pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used).await?; + + // Route the voucher response through the funding-aware handler so + // a final 402 here also gets the fund retry / contextual help. + let result = self + .handle_response_or_retry_after_fund( + voucher_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -304,7 +558,7 @@ where self.provider.rollback_pending(); self.provider.clear_channels(); - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "MPP channel not found on server (410 Gone). \ The server may have restarted or the channel was closed externally.\n\ Local channel state has been cleared. Re-run to open a new channel.", @@ -333,10 +587,19 @@ where debug!("MPP voucher stale, retrying with fresh voucher"); let resolved = self.provider.resolve()?; if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - let final_resp = - self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.flush_pending(); } else { @@ -372,10 +635,19 @@ where let (resolved, fresh_challenge) = self.fetch_fresh_challenge(&headers, &body).await?; - let final_resp = - self.pay_and_retry(&fresh_challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -386,9 +658,40 @@ where } self.provider.rollback_pending(); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) + && maybe_auto_fund(auto_fund_used, &funding_ctx).await? + { + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + let mut error_text = format!("{retry_text}{diagnostics}"); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) { + error_text.push_str(&tempo_wallet_fund_help(&funding_ctx)); + } return Err(TransportErrorKind::http_error( StatusCode::PAYMENT_REQUIRED.as_u16(), - format!("{retry_text}{diagnostics}"), + error_text, )); } @@ -409,15 +712,32 @@ where provider: &P::Provider, headers: &reqwest::header::HeaderMap, body: &[u8], + auto_fund_used: &AtomicBool, ) -> TransportResult { - let credential = provider.pay(challenge).await.map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let funding_ctx = self.provider.funding_context(challenge); + let credential = match provider.pay(challenge).await { + Ok(credential) => credential, + Err(e) => { + self.provider.rollback_pending(); + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + provider.pay(challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "failed to format MPP credential: {e}" ))) })?; @@ -437,6 +757,41 @@ where }) } + async fn handle_response_or_retry_after_fund( + &self, + resp: reqwest::Response, + headers: &reqwest::header::HeaderMap, + body: &[u8], + funding_ctx: &FundingContext, + auto_fund_used: &AtomicBool, + ) -> TransportResult { + if resp.status() != StatusCode::PAYMENT_REQUIRED { + return Self::handle_response_with_funding(resp, Some(funding_ctx)).await; + } + + let diagnostics = format_http_diagnostics(resp.headers()); + let status = resp.status(); + let resp_body = resp.bytes().await.map_err(TransportErrorKind::custom)?; + + if should_suggest_tempo_fund(status, &resp_body) + && maybe_auto_fund(auto_fund_used, funding_ctx).await? + { + self.provider.rollback_pending(); + + let (resolved, fresh_challenge) = self.fetch_fresh_challenge(headers, body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, headers, body, auto_fund_used) + .await?; + return Self::handle_response_with_funding(final_resp, Some(funding_ctx)).await; + } + + let mut error_text = format!("{}{diagnostics}", String::from_utf8_lossy(&resp_body)); + if should_suggest_tempo_fund(status, &resp_body) { + error_text.push_str(&tempo_wallet_fund_help(funding_ctx)); + } + Err(TransportErrorKind::http_error(status.as_u16(), error_text)) + } + /// Fetch a fresh 402 challenge from the server (unauthenticated request). /// /// Returns `Ok(Some((provider, challenge)))` if the server returns a 402 @@ -462,7 +817,7 @@ where // Non-402 → return whatever the server sent (could be success or error). let result = Self::handle_response(fresh_resp).await; return Err(result.err().unwrap_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "unexpected success on unauthenticated fresh probe", )) })); @@ -477,25 +832,14 @@ where resp: &reqwest::Response, provider: &P, ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { - let www_auth_values: Vec<&str> = resp - .headers() - .get_all(WWW_AUTHENTICATE_HEADER) - .iter() - .filter_map(|v| v.to_str().ok()) - .collect(); - - if www_auth_values.is_empty() { - return Err(TransportErrorKind::custom(std::io::Error::other(format!( + let challenges = parse_challenges(resp); + if challenges.is_empty() && resp.headers().get(WWW_AUTHENTICATE_HEADER).is_none() { + return Err(TransportErrorKind::custom(io::Error::other(format!( "402 response missing WWW-Authenticate header{}", format_http_diagnostics(resp.headers()) )))); } - let challenges: Vec<_> = parse_www_authenticate_all(www_auth_values) - .into_iter() - .filter_map(|r| r.ok()) - .collect(); - let mut last_resolve_err: Option = None; let resolved_pair = challenges.iter().find_map(|c| { let (chain_id, currency) = extract_challenge_chain_and_currency(c); @@ -515,7 +859,7 @@ where } let offered: Vec<_> = challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "no supported MPP challenge; server offered [{}]", offered.join(", "), ))) @@ -523,6 +867,17 @@ where } async fn handle_response(resp: reqwest::Response) -> TransportResult { + Self::handle_response_with_funding(resp, None).await + } + + /// Like [`Self::handle_response`] but, when an unsuccessful 402 looks like a + /// fundable error, appends actionable `tempo wallet fund` help that uses + /// the per-request `FundingContext` (so the suggested command includes + /// `--address` and `--network` when known). + async fn handle_response_with_funding( + resp: reqwest::Response, + funding_ctx: Option<&FundingContext>, + ) -> TransportResult { let status = resp.status(); debug!(%status, "received response from MPP transport"); let diagnostics = format_http_diagnostics(resp.headers()); @@ -536,10 +891,19 @@ where } if !status.is_success() { - return Err(TransportErrorKind::http_error( - status.as_u16(), - format!("{}{diagnostics}", String::from_utf8_lossy(&body)), - )); + let mut body_text = format!("{}{diagnostics}", String::from_utf8_lossy(&body)); + if should_suggest_tempo_fund(status, &body) { + let default_ctx; + let ctx = match funding_ctx { + Some(c) => c, + None => { + default_ctx = FundingContext::default(); + &default_ctx + } + }; + body_text.push_str(&tempo_wallet_fund_help(ctx)); + } + return Err(TransportErrorKind::http_error(status.as_u16(), body_text)); } serde_json::from_slice(&body) @@ -547,6 +911,57 @@ where } } +/// Returns `Some(chain_id)` when a 402 response should trigger the +/// `wallet.tempo.xyz` device-code authorization flow. +/// +/// Conditions: known Tempo endpoint, interactive (TTY, not `CI`), and no +/// offered Tempo challenge resolves against a local key on `(chain, currency)`. +/// The picked chain matches the first unresolved challenge — same iteration +/// order [`MppHttpTransport::select_challenge`] uses. +fn tempo_chain_needing_auth(url: &Url, resp: &reqwest::Response) -> Option { + if !io::stderr().is_terminal() || env::var_os("CI").is_some() { + return None; + } + pick_chain_needing_auth(url, &parse_challenges(resp)) +} + +/// Extract all parseable MPP challenges from a 402 response's `WWW-Authenticate` headers. +fn parse_challenges(resp: &reqwest::Response) -> Vec { + let values: Vec<&str> = resp + .headers() + .get_all(WWW_AUTHENTICATE_HEADER) + .iter() + .filter_map(|v| v.to_str().ok()) + .collect(); + parse_www_authenticate_all(values).into_iter().filter_map(|r| r.ok()).collect() +} + +/// Inner logic of [`tempo_chain_needing_auth`], factored out for testing. +fn pick_chain_needing_auth( + url: &Url, + challenges: &[mpp::protocol::core::PaymentChallenge], +) -> Option { + if !crate::tempo::is_known_tempo_endpoint(url) { + return None; + } + + let tempo_challenges: Vec<_> = + challenges.iter().filter(|c| c.method.as_str() == "tempo").collect(); + + // If any challenge already resolves with a local key, no auth needed. + let any_resolvable = tempo_challenges.iter().any(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + super::keys::discover_mpp_config(super::keys::DiscoverOptions { chain_id, currency }) + .is_some() + }); + if any_resolvable { + return None; + } + + tempo_challenges.iter().find_map(|c| extract_challenge_chain_and_currency(c).0) +} + /// Extract `(chainId, currency)` from a parsed MPP challenge. pub(super) fn extract_challenge_chain_and_currency( c: &mpp::protocol::core::PaymentChallenge, @@ -576,10 +991,28 @@ pub(crate) trait ResolveProvider { fn flush_pending(&self) {} fn rollback_pending(&self) {} fn commit_topup_and_track_voucher(&self) {} + /// Drop any cached payment provider so the next `resolve_for` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry. + fn invalidate_cached_provider(&self) {} + fn funding_wallet_address(&self) -> Option { + None + } + fn funding_chain_id(&self) -> Option { + None + } + fn funding_context(&self, challenge: &mpp::protocol::core::PaymentChallenge) -> FundingContext { + let (challenge_chain_id, token) = extract_challenge_chain_and_currency(challenge); + FundingContext { + wallet_address: self.funding_wallet_address(), + token, + chain_id: challenge_chain_id.or_else(|| self.funding_chain_id()).map(Chain::from_id), + } + } /// Acquire the payment serialization lock. The returned guard must be held /// across the entire 402 → pay → retry → response cycle to prevent /// concurrent channel opens and colliding expiring-nonce transactions. - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn lock_pay(&self) -> impl Future>> + Send { async { None } } } @@ -599,7 +1032,7 @@ impl ResolveProvider for LazySessionProvider { // regardless of opts. Re-check that the provider's key is compatible // with this challenge's chain/currency. if !provider.matches_challenge(opts.chain_id, opts.currency) { - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "cached provider does not match challenge chain/currency", ))); } @@ -623,7 +1056,16 @@ impl ResolveProvider for LazySessionProvider { fn commit_topup_and_track_voucher(&self) { Self::commit_topup_and_track_voucher(self) } - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn invalidate_cached_provider(&self) { + Self::invalidate(self) + } + fn funding_wallet_address(&self) -> Option { + self.inner.lock().unwrap().as_ref().map(|p| p.funding_wallet_address()) + } + fn funding_chain_id(&self) -> Option { + self.inner.lock().unwrap().as_ref().and_then(|p| p.key_chain_id()) + } + fn lock_pay(&self) -> impl Future>> + Send { let lock = self.pay_lock.clone(); async move { Some(lock.lock_owned().await) } } @@ -685,7 +1127,7 @@ mod tests { fn pay( &self, challenge: &PaymentChallenge, - ) -> impl std::future::Future> + Send { + ) -> impl Future> + Send { let echo = challenge.to_echo(); async move { Ok(PaymentCredential::with_source( @@ -697,6 +1139,21 @@ mod tests { } } + #[derive(Clone, Debug)] + struct InsufficientBalanceProvider; + + impl PaymentProvider for InsufficientBalanceProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + async fn pay(&self, _challenge: &PaymentChallenge) -> Result { + Err(MppError::InsufficientBalance(Some( + "wallet has 0 pathUSD but needs 100000".to_string(), + ))) + } + } + fn test_challenge() -> (PaymentChallenge, String) { let request = Base64UrlJson::from_value(&serde_json::json!({ "amount": "1000", @@ -853,8 +1310,238 @@ mod tests { handle.abort(); } + #[tokio::test] + async fn test_mpp_transport_payment_failure_suggests_tempo_wallet_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move || { + let www_auth = www_auth.clone(); + async move { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required", + ) + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + InsufficientBalanceProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_retry_402_insufficient_balance_suggests_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail( + "Insufficient pathUSD balance: have 0, need 100000", + ), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("InsufficientBalanceError"), "got: {msg}"); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + /// Generic `verification-failed` has many non-funding causes (bad signature, + /// replay, expired challenge, clock skew, ...). The transport must surface + /// the original error verbatim and must NOT add a "fund your wallet" hint. + #[tokio::test] + async fn test_mpp_transport_final_402_verification_failed_does_not_suggest_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Verification Failed"), "got: {msg}"); + assert!( + !msg.contains("Tempo wallet payment could not be funded"), + "verification-failed must not be classified as fundable; got: {msg}" + ); + + handle.abort(); + } + + // --- Classifier unit tests -------------------------------------------- + + #[test] + fn classifier_only_triggers_on_explicit_insufficient_balance_problem() { + // explicit insufficient-balance → true + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail("Insufficient pathUSD balance"), + ) + .unwrap(); + assert!(should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_verification_failed() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_unrelated_text_with_balance_words() { + // Free-text 402 body that just happens to mention the word "balance" + // must NOT trigger the fund suggestion (no structured problem details). + let body = + b"402 Payment Required: server could not balance ledger entries; insufficient inputs."; + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, body)); + } + + #[test] + fn classifier_does_not_trigger_outside_402() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_detail("Insufficient balance"), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::INTERNAL_SERVER_ERROR, &body)); + assert!(!should_suggest_tempo_fund(StatusCode::OK, &body)); + } + + #[test] + fn fund_help_includes_address_and_network_for_known_chain() { + let ctx = FundingContext { + wallet_address: Some("0x000000000000000000000000000000000000dEaD".parse().unwrap()), + token: Some("0x20c0".to_string()), + chain_id: Some(Chain::from_id(42431)), + }; + let help = tempo_wallet_fund_help(&ctx); + assert!(help.contains("--address 0x"), "missing --address: {help}"); + assert!(help.contains("--network tempo-moderato"), "missing --network: {help}"); + assert!(help.contains("--no-browser"), "missing --no-browser: {help}"); + assert!(help.contains("Requested payment token: 0x20c0"), "missing token: {help}"); + + let mainnet = FundingContext { chain_id: Some(Chain::from_id(4217)), ..ctx }; + let help2 = tempo_wallet_fund_help(&mainnet); + assert!(help2.contains("--network tempo"), "missing tempo network: {help2}"); + } + + #[test] + fn auto_fund_policy_blocks_in_ci_and_non_tty() { + assert!(!interactive_tempo_fund_allowed(Some("1"), true, true, true), "must not run in CI"); + assert!( + interactive_tempo_fund_allowed(Some("0"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=0 must not disable" + ); + assert!( + interactive_tempo_fund_allowed(Some("false"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=false must not disable" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, false, true), + "stdin must be a terminal" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, true, false), + "stderr must be a terminal" + ); + assert!(!interactive_tempo_fund_allowed(Some("1"), false, true, true)); + assert!(!interactive_tempo_fund_allowed(Some("true"), false, true, true)); + assert!(interactive_tempo_fund_allowed(None, false, true, true)); + } + #[tokio::test] async fn test_plain_http_402_shows_mpp_setup_instructions() { + let _g = crate::tempo::test_env_mutex().lock().await; let (_, www_auth) = test_challenge(); let app = axum::Router::new().route( @@ -920,6 +1607,32 @@ mod tests { ); } + /// `invalidate_cached_provider` clears the cache so the next + /// `get_or_init` re-runs discovery — the path `do_request` takes after + /// `ensure_access_key` writes a fresh `keys.toml` entry. + #[tokio::test] + async fn lazy_session_provider_invalidate_clears_cache() { + let _g = crate::tempo::test_env_mutex().lock().await; + // TEMPO_PRIVATE_KEY lets discovery succeed without a keys.toml. + let key_hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + unsafe { + std::env::set_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV, key_hex); + std::env::remove_var(crate::tempo::TEMPO_HOME_ENV); + } + + let lazy = LazySessionProvider::new("https://rpc.example.com".into()); + let _ = lazy.get_or_init(Default::default()).expect("discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected provider to be cached"); + + ResolveProvider::invalidate_cached_provider(&lazy); + assert!(lazy.inner.lock().unwrap().is_none(), "expected cache to be cleared"); + + let _ = lazy.get_or_init(Default::default()).expect("re-discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected re-init to repopulate cache"); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV) }; + } + #[test] fn challenge_chain_and_currency_extraction() { let extract = |headers: Vec<&str>| -> Vec<(Option, Option)> { @@ -955,4 +1668,73 @@ mod tests { ); assert_eq!(extract(vec![&no_details]), vec![(None, Some("0x20c0".into()))]); } + + /// Auth must trigger when a key matches the chain but not the currency. + #[test] + fn pick_chain_needing_auth_currency_aware() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let dir = tempfile::tempdir().unwrap(); + let wallet = dir.path().join("wallet"); + std::fs::create_dir_all(&wallet).unwrap(); + std::fs::write( + wallet.join("keys.toml"), + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +chain_id = 4217 + +[[keys.limits]] +currency = "0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +limit = "1000" +"#, + ) + .unwrap(); + unsafe { + std::env::set_var(crate::tempo::TEMPO_HOME_ENV, dir.path()); + std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV); + } + + let url = Url::parse("https://rpc.mpp.tempo.xyz").unwrap(); + let mk = |currency: &str| -> PaymentChallenge { + PaymentChallenge { + id: "x".into(), + realm: "api".into(), + method: MethodName::new("tempo"), + intent: IntentName::new("charge"), + request: Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1", + "currency": currency, + "recipient": "0xabc", + "methodDetails": { "chainId": 4217 } + })) + .unwrap(), + expires: None, + description: None, + digest: None, + opaque: None, + } + }; + + // Currency mismatch → auth needed. + let mismatched = mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + assert_eq!(pick_chain_needing_auth(&url, &[mismatched]), Some(4217)); + + // Currency match → no auth. + let matched = mk("0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert_eq!(pick_chain_needing_auth(&url, &[matched]), None); + + // Non-Tempo host → never triggers, even without a key. + let stripe_url = Url::parse("https://api.stripe.com").unwrap(); + assert_eq!( + pick_chain_needing_auth( + &stripe_url, + &[mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")] + ), + None, + ); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_HOME_ENV) }; + } } diff --git a/crates/common/src/provider/mpp/ws.rs b/crates/common/src/provider/mpp/ws.rs index f631d0b08a2fc..69aef7d4f4cbc 100644 --- a/crates/common/src/provider/mpp/ws.rs +++ b/crates/common/src/provider/mpp/ws.rs @@ -378,6 +378,8 @@ mod tests { /// MPP server sends challenge → client pays → server sends receipt. #[tokio::test] async fn test_ws_mpp_challenge_credential_receipt() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); @@ -452,6 +454,8 @@ mod tests { /// MPP server sends challenge, client pays, server closes → rollback. #[tokio::test] async fn test_ws_mpp_rollback_on_post_pay_close() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 7db1ebd1b3f91..f59a2efa75b8e 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -36,7 +36,11 @@ fn is_known_mpp_endpoint(url: &Url) -> bool { /// Only meant to be used internally by [RuntimeTransport]. #[derive(Clone, Debug)] pub enum InnerTransport { - /// HTTP transport with lazy MPP 402 handling + /// HTTP transport with lazy MPP 402 handling. + /// + /// For known Tempo endpoints, the MPP layer additionally runs the + /// `wallet.tempo.xyz` device-code flow on a 402 when no local access key + /// is configured (see [`crate::tempo::ensure_access_key`]). Http(LazyMppHttpTransport), /// WebSocket transport Ws(PubSubFrontend), diff --git a/crates/common/src/tempo/auth.rs b/crates/common/src/tempo/auth.rs new file mode 100644 index 0000000000000..d79306cfb74f2 --- /dev/null +++ b/crates/common/src/tempo/auth.rs @@ -0,0 +1,494 @@ +//! Tempo wallet device-code authorization flow. +//! +//! Implements the CLI side of the tempoxyz/accounts `cli-auth` device-code +//! protocol: generates a local secp256k1 access key, creates a PKCE-protected +//! device code, opens `wallet.tempo.xyz/cli-auth?code=` in the browser, +//! polls until the user authorizes the key on their passkey wallet, and writes +//! the resulting `keyAuthorization` to `~/.tempo/wallet/keys.toml`. + +use crate::tempo::{ + KeyEntry, KeyType, StoredTokenLimit, WalletType, decode_key_authorization, upsert_key_entry, +}; +use alloy_primitives::{Address, B256, hex}; +use alloy_signer_local::PrivateKeySigner; +use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +#[cfg(any(unix, windows))] +use std::process::Command; +use std::{ + env, + sync::LazyLock, + time::{Duration, Instant}, +}; +use tempo_primitives::transaction::{SignatureType, SignedKeyAuthorization}; +use tokio::sync::Mutex; + +/// Default device-code service URL (production wallet.tempo.xyz). +const DEFAULT_CLI_AUTH_URL: &str = "https://wallet.tempo.xyz/cli-auth"; + +/// Returns `true` if `url`'s host is `tempo.xyz` or a subdomain of it. +pub(crate) fn is_known_tempo_endpoint(url: &url::Url) -> bool { + url.host_str().is_some_and(|host| host == "tempo.xyz" || host.ends_with(".tempo.xyz")) +} + +/// Env var to override the device-code service URL (for tests / staging). +const TEMPO_CLI_AUTH_URL_ENV: &str = "TEMPO_CLI_AUTH_URL"; + +const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2); +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); + +/// Per-process serialization of concurrent `ensure_access_key` calls. +/// +/// Prevents two `cast` invocations in the same process from racing two browser +/// popups for the same chain. +static AUTH_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Configuration for [`ensure_access_key`]. +#[derive(Clone, Debug)] +pub struct EnsureAccessKeyConfig { + /// Chain ID the access key is being authorized for. + pub chain_id: u64, + /// Device-code service base URL. Defaults to [`DEFAULT_CLI_AUTH_URL`]. + pub(crate) service_url: String, + /// Poll interval. + pub(crate) poll_interval: Duration, + /// Total timeout for the authorization flow. + pub(crate) timeout: Duration, + /// If `true`, print the authorization URL to stderr instead of opening a + /// browser. + pub no_browser: bool, +} + +impl EnsureAccessKeyConfig { + /// Build a config from the environment for the given chain. + /// + /// `no_browser` defaults to `true` under `CI`; callers (e.g. `cast tempo + /// login --no-browser`) may override it. + pub fn from_env(chain_id: u64) -> Self { + Self { + chain_id, + service_url: env::var(TEMPO_CLI_AUTH_URL_ENV) + .unwrap_or_else(|_| DEFAULT_CLI_AUTH_URL.to_string()), + poll_interval: DEFAULT_POLL_INTERVAL, + timeout: DEFAULT_TIMEOUT, + no_browser: env::var_os("CI").is_some(), + } + } +} + +/// Open `url` via the OS default browser handler. On platforms without a known +/// opener, this is a no-op (the URL is still printed by [`ensure_access_key`]). +fn open_browser(_url: &str) { + #[cfg(target_os = "macos")] + let _ = Command::new("open").arg(_url).spawn(); + #[cfg(target_os = "windows")] + let _ = Command::new("cmd").args(["/c", "start", "", _url]).spawn(); + #[cfg(all(unix, not(target_os = "macos")))] + let _ = Command::new("xdg-open").arg(_url).spawn(); +} + +/// Result of [`ensure_access_key`]. +#[derive(Debug, Clone)] +pub struct AccessKeyOutcome { + pub wallet_address: Address, + pub key_address: Address, + pub chain_id: u64, +} + +/// Run the device-code flow, persist the resulting key to `keys.toml`, and +/// return the new entry's identifying fields. +pub async fn ensure_access_key(cfg: EnsureAccessKeyConfig) -> Result { + let _guard = AUTH_LOCK.lock().await; + + let signer = PrivateKeySigner::random(); + let key_address = signer.address(); + // The server requires uncompressed SEC1 (65-byte `0x04 || X || Y`); the + // default `to_sec1_bytes()` would emit the compressed 33-byte form. + let pub_key_hex = format!( + "0x{}", + hex::encode(signer.credential().verifying_key().to_encoded_point(false).as_bytes()), + ); + + let code_verifier = random_code_verifier(); + let client = reqwest::Client::builder().timeout(Duration::from_secs(30)).build()?; + let service = cfg.service_url.trim_end_matches('/'); + + let create_req = CreateCodeRequest { + chain_id: cfg.chain_id, + code_challenge: sha256_b64url(&code_verifier), + key_type: "secp256k1", + pub_key: pub_key_hex, + }; + let code = create_code_with_retry(&client, service, &create_req, cfg.timeout).await?; + + let browser_url = format!("{service}?code={code}"); + if cfg.no_browser { + let _ = crate::sh_eprintln!("Open this URL to authorize: {browser_url}"); + } else { + let _ = crate::sh_eprintln!( + "Opening wallet.tempo to authorize an access key…\n {browser_url}" + ); + open_browser(&browser_url); + } + + let poll = PollRequest { code_verifier }; + let started = Instant::now(); + loop { + // Retry transient network/5xx/429 failures within `cfg.timeout`. + let send_res = client.post(format!("{service}/poll/{code}")).json(&poll).send().await; + + let resp = match send_res { + Ok(r) => r, + Err(e) if is_transient_error(&e) && started.elapsed() < cfg.timeout => { + tracing::debug!(error = %e, "transient error polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + Err(e) => return Err(e.into()), + }; + + let status = resp.status(); + if !status.is_success() { + if is_transient_status(status) && started.elapsed() < cfg.timeout { + tracing::debug!(%status, "transient HTTP status polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code poll failed ({status}): {body}"); + } + + let body: PollResponse = resp.json().await?; + match body { + PollResponse::Pending => { + if started.elapsed() > cfg.timeout { + eyre::bail!("timed out waiting for wallet authorization (code {code})"); + } + tokio::time::sleep(cfg.poll_interval).await; + } + PollResponse::Expired => { + eyre::bail!("device code {code} expired before authorization"); + } + PollResponse::Authorized { account_address, key_authorization } => { + let hex_str = key_authorization.ok_or_else(|| { + eyre::eyre!("wallet authorized response missing key_authorization") + })?; + let signed: SignedKeyAuthorization = decode_key_authorization(&hex_str)?; + // Reject mismatches before persisting — an unusable keys.toml + // entry would silently break the next 402 retry. + if signed.authorization.key_id != key_address { + eyre::bail!( + "wallet authorized key {} but the locally generated key is {}", + signed.authorization.key_id, + key_address, + ); + } + if signed.authorization.chain_id != cfg.chain_id { + eyre::bail!( + "wallet authorized chain {} but {} was requested", + signed.authorization.chain_id, + cfg.chain_id, + ); + } + if signed.authorization.key_type != SignatureType::Secp256k1 { + eyre::bail!( + "wallet returned keyType {:?} but secp256k1 was requested", + signed.authorization.key_type, + ); + } + let chain_id = signed.authorization.chain_id; + let key_authorization = + if hex_str.starts_with("0x") { hex_str } else { format!("0x{hex_str}") }; + let entry = KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: account_address, + chain_id, + key_type: match signed.authorization.key_type { + SignatureType::P256 => KeyType::P256, + SignatureType::WebAuthn => KeyType::WebAuthn, + _ => KeyType::Secp256k1, + }, + key_address: Some(key_address), + key: Some(format!("0x{}", hex::encode(signer.to_bytes()))), + key_authorization: Some(key_authorization), + expiry: signed.authorization.expiry.map(|n| n.get()), + limits: signed + .authorization + .limits + .unwrap_or_default() + .into_iter() + .map(|l| StoredTokenLimit { currency: l.token, limit: l.limit.to_string() }) + .collect(), + }; + upsert_key_entry(entry)?; + return Ok(AccessKeyOutcome { + wallet_address: account_address, + key_address, + chain_id, + }); + } + } + } +} + +fn is_transient_error(err: &reqwest::Error) -> bool { + err.is_timeout() || err.is_connect() || err.is_request() +} + +fn is_transient_status(status: reqwest::StatusCode) -> bool { + status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS +} + +/// POST `/code` with exponential backoff on transient errors, bounded by `timeout`. +async fn create_code_with_retry( + client: &reqwest::Client, + service: &str, + req: &CreateCodeRequest, + timeout: Duration, +) -> Result { + let started = Instant::now(); + let mut backoff = Duration::from_millis(500); + loop { + let send_res = client.post(format!("{service}/code")).json(req).send().await; + + match send_res { + Ok(resp) => { + let status = resp.status(); + if status.is_success() { + let CreateCodeResponse { code } = resp.json().await?; + return Ok(code); + } + if is_transient_status(status) && started.elapsed() < timeout { + tracing::debug!(%status, "transient HTTP status creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code create failed ({status}): {body}"); + } + Err(e) if is_transient_error(&e) && started.elapsed() < timeout => { + tracing::debug!(error = %e, "transient error creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + } + Err(e) => return Err(e.into()), + } + } +} + +fn random_code_verifier() -> String { + let bytes = B256::random(); + URL_SAFE_NO_PAD.encode(bytes.as_slice()) +} + +fn sha256_b64url(input: &str) -> String { + let digest = Sha256::digest(input.as_bytes()); + URL_SAFE_NO_PAD.encode(digest) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CreateCodeRequest { + /// `0x`-hex per the SDK schema (server accepts hex string or bigint, not a plain JSON number). + #[serde(serialize_with = "serialize_u64_hex")] + chain_id: u64, + code_challenge: String, + key_type: &'static str, + pub_key: String, +} + +fn serialize_u64_hex(v: &u64, s: S) -> std::result::Result { + s.serialize_str(&format!("0x{v:x}")) +} + +#[derive(Deserialize)] +struct CreateCodeResponse { + code: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PollRequest { + code_verifier: String, +} + +/// Matches `tempoxyz/wallet` poll response shape. +#[derive(Deserialize)] +#[serde(tag = "status", rename_all = "lowercase")] +enum PollResponse { + Pending, + Expired, + Authorized { + account_address: Address, + #[serde(default)] + key_authorization: Option, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tempo::{TEMPO_HOME_ENV, read_tempo_keys_file, test_env_mutex}; + use axum::{Json, Router, extract::State, routing::post}; + use std::sync::{Arc, Mutex}; + + #[test] + fn pkce_challenge_matches_sdk_format() { + // Vector from RFC 7636 §4.2. + let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + let challenge = sha256_b64url(verifier); + assert_eq!(challenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); + } + + /// Recover the EOA from a SEC1-encoded public key (compressed or + /// uncompressed). + fn address_from_sec1_hex(s: &str) -> Address { + let stripped = s.strip_prefix("0x").unwrap_or(s); + let bytes = hex::decode(stripped).expect("valid hex"); + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes).expect("valid SEC1 pubkey"); + Address::from_public_key(&vk) + } + + #[derive(Clone)] + struct MockState { + wallet: Arc>>, + /// Derived from the `pubKey` posted to `/code` so `/poll` can echo + /// back a matching `keyId`, like a real wallet would. + key_id: Arc>>, + /// Chain ID the mock `/poll` returns in `keyAuthorization`. + poll_chain_id: u64, + } + + async fn create_code_handler( + State(state): State, + Json(body): Json, + ) -> Json { + // Sanity: required fields present and chainId is a 0x-hex string, + // matching the SDK wire format the live server enforces. + let pub_key = body + .get("pubKey") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("pubKey missing: {body}")); + assert!(body.get("codeChallenge").is_some(), "codeChallenge missing: {body}"); + let chain_id = body.get("chainId").unwrap_or_else(|| panic!("chainId missing: {body}")); + let chain_str = chain_id + .as_str() + .unwrap_or_else(|| panic!("chainId must be string, got {chain_id}: {body}")); + assert!(chain_str.starts_with("0x"), "chainId must be 0x-hex, got {chain_str}"); + let wallet: Address = "0x0000000000000000000000000000000000000042".parse().unwrap(); + *state.wallet.lock().unwrap() = Some(wallet); + *state.key_id.lock().unwrap() = Some(address_from_sec1_hex(pub_key)); + Json(serde_json::json!({ "code": "ABCDEFGH" })) + } + + /// Build the RLP-hex `SignedKeyAuthorization` blob the live server returns + /// in the `key_authorization` field. + fn signed_key_auth_hex(chain_id: u64, key_id: Address, expiry: u64) -> String { + use alloy_rlp::Encodable; + use tempo_primitives::transaction::{KeyAuthorization, PrimitiveSignature}; + let auth = KeyAuthorization::unrestricted(chain_id, SignatureType::Secp256k1, key_id) + .with_expiry(expiry); + let sig: PrimitiveSignature = serde_json::from_value(serde_json::json!({ + "type": "secp256k1", "r": "0x0", "s": "0x0", "yParity": 0 + })) + .unwrap(); + let signed = auth.into_signed(sig); + let mut buf = Vec::new(); + signed.encode(&mut buf); + format!("0x{}", hex::encode(buf)) + } + + async fn poll_handler(State(state): State) -> Json { + let wallet = state.wallet.lock().unwrap().expect("create_code must be called first"); + let key_id = state.key_id.lock().unwrap().expect("create_code must be called first"); + Json(serde_json::json!({ + "status": "authorized", + "account_address": wallet, + "key_authorization": signed_key_auth_hex(state.poll_chain_id, key_id, 9_999_999_999), + })) + } + + /// Spawn a mock wallet.tempo server whose `/poll` echoes `poll_chain_id`. + async fn spawn_mock_wallet(poll_chain_id: u64) -> (String, tokio::task::JoinHandle<()>) { + let app = Router::new() + .route("/code", post(create_code_handler)) + .route("/poll/{code}", post(poll_handler)) + .with_state(MockState { + wallet: Arc::default(), + key_id: Arc::default(), + poll_chain_id, + }); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + (format!("http://{addr}"), handle) + } + + fn test_cfg(service_url: String) -> EnsureAccessKeyConfig { + EnsureAccessKeyConfig { + chain_id: 4217, + service_url, + poll_interval: Duration::from_millis(10), + timeout: Duration::from_secs(2), + no_browser: true, + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_happy_path_writes_keys_toml() { + // SAFETY: serialized with other tests that mutate TEMPO_HOME. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(4217).await; + let outcome = ensure_access_key(test_cfg(service_url)).await.unwrap(); + + let expected_wallet: Address = + "0x0000000000000000000000000000000000000042".parse().unwrap(); + assert_eq!(outcome.chain_id, 4217); + assert_eq!(outcome.wallet_address, expected_wallet); + + let file = read_tempo_keys_file().expect("keys.toml written"); + assert_eq!(file.keys.len(), 1); + let entry = &file.keys[0]; + assert_eq!(entry.wallet_address, outcome.wallet_address); + assert_eq!(entry.key_address, Some(outcome.key_address)); + assert_eq!(entry.chain_id, 4217); + assert_eq!(entry.expiry, Some(9_999_999_999)); + let decoded: tempo_primitives::transaction::SignedKeyAuthorization = + crate::tempo::decode_key_authorization(entry.key_authorization.as_deref().unwrap()) + .expect("RLP roundtrip"); + assert_eq!(decoded.authorization.chain_id, 4217); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_rejects_wrong_chain_id() { + // Wallet returns chain 99999 but client requested 4217 → must reject + // and persist nothing, else discovery would later fail to find a key + // for the requested chain. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(99999).await; + let err = ensure_access_key(test_cfg(service_url)).await.unwrap_err(); + assert!( + err.to_string().contains("wallet authorized chain 99999 but 4217 was requested"), + "expected chain mismatch error, got: {err}" + ); + assert!(read_tempo_keys_file().is_none_or(|f| f.keys.is_empty())); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } +} diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index 18edf39be59bd..b4f9527d1b106 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -5,8 +5,8 @@ use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; -use serde::Deserialize; -use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use std::{env, fs, io::Write, path::PathBuf}; /// Environment variable for an ephemeral Tempo private key. pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; @@ -21,7 +21,7 @@ pub const DEFAULT_TEMPO_HOME: &str = ".tempo"; pub const WALLET_KEYS_PATH: &str = "wallet/keys.toml"; /// Wallet type matching `tempo-common`'s `WalletType` enum. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum WalletType { #[default] @@ -30,7 +30,7 @@ pub enum WalletType { } /// Cryptographic key type. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum KeyType { #[default] @@ -40,7 +40,7 @@ pub enum KeyType { } /// Per-token spending limit stored in `keys.toml`. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct StoredTokenLimit { pub currency: Address, pub limit: String, @@ -50,7 +50,7 @@ pub struct StoredTokenLimit { /// /// Mirrors the fields from `tempo-common::keys::model::KeyEntry`. /// Unknown fields are ignored by serde. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct KeyEntry { /// Wallet type: "local" or "passkey". #[serde(default)] @@ -65,20 +65,20 @@ pub struct KeyEntry { #[serde(default)] pub key_type: KeyType, /// Key address (the EOA derived from the private key). - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key_address: Option
, /// Key private key, stored inline in keys.toml. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key: Option, /// RLP-encoded signed key authorization (hex string). /// Used in keychain mode to atomically provision the access key on-chain. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key_authorization: Option, /// Expiry timestamp. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub expiry: Option, /// Per-token spending limits. - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub limits: Vec, } @@ -90,17 +90,27 @@ impl KeyEntry { } /// The top-level structure of `keys.toml`. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct KeysFile { #[serde(default)] pub keys: Vec, } +/// Process-wide mutex used by tests that mutate `TEMPO_HOME`. +/// +/// Returns a [`tokio::sync::Mutex`] so async tests can hold it across `.await` +/// points without tripping `clippy::await_holding_lock`. +#[cfg(test)] +pub(crate) fn test_env_mutex() -> &'static tokio::sync::Mutex<()> { + static M: std::sync::OnceLock> = std::sync::OnceLock::new(); + M.get_or_init(|| tokio::sync::Mutex::new(())) +} + /// Resolve the Tempo home directory. /// /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. pub fn tempo_home() -> Option { - if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { + if let Ok(home) = env::var(TEMPO_HOME_ENV) { return Some(PathBuf::from(home)); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) @@ -122,7 +132,7 @@ pub fn read_tempo_keys_file() -> Option { return None; } - let contents = match std::fs::read_to_string(&keys_path) { + let contents = match fs::read_to_string(&keys_path) { Ok(c) => c, Err(e) => { tracing::warn!(?keys_path, %e, "failed to read tempo keys file"); @@ -148,3 +158,112 @@ pub fn decode_key_authorization(hex_str: &str) -> eyre::Result let auth = T::decode(&mut bytes.as_slice())?; Ok(auth) } + +/// Atomically upsert a [`KeyEntry`] into `keys.toml`. +/// +/// Replaces any existing entry for the same `(wallet_address, chain_id)`. +/// Each Tempo wallet has at most one active access key per chain, so a fresh +/// login always supersedes the previous entry regardless of the new key +/// address. Creates the file (and parent directories) if missing. Writes via +/// temp file + rename so a crash mid-write cannot corrupt the file. +pub(crate) fn upsert_key_entry(entry: KeyEntry) -> eyre::Result<()> { + let path = tempo_keys_path().ok_or_else(|| eyre::eyre!("could not resolve tempo home"))?; + let dir = path.parent().ok_or_else(|| eyre::eyre!("invalid keys path: {}", path.display()))?; + fs::create_dir_all(dir)?; + + let mut file = read_tempo_keys_file().unwrap_or_default(); + file.keys + .retain(|k| !(k.wallet_address == entry.wallet_address && k.chain_id == entry.chain_id)); + file.keys.push(entry); + + let body = toml::to_string_pretty(&file)?; + let contents = format!( + "# Tempo wallet keys — managed by Foundry / Tempo CLI.\n# Do not edit manually.\n\n{body}" + ); + + let mut tmp = tempfile::NamedTempFile::new_in(dir)?; + tmp.write_all(contents.as_bytes())?; + tmp.flush()?; + tmp.persist(&path).map_err(|e| eyre::eyre!("failed to persist keys.toml: {e}"))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + fn with_tempo_home(f: F) { + let tmp = tempfile::tempdir().unwrap(); + // SAFETY: process-global env access is serialized via the shared mutex. + let _g = test_env_mutex().blocking_lock(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + f(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[test] + fn upsert_replaces_matching_entry_atomically() { + with_tempo_home(|| { + let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); + let key = Address::from_str("0x0000000000000000000000000000000000000abc").unwrap(); + + let mk = |expiry: u64| KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: wallet, + chain_id: 4217, + key_type: KeyType::Secp256k1, + key_address: Some(key), + key: Some("0xdead".to_string()), + key_authorization: Some("0xbeef".to_string()), + expiry: Some(expiry), + limits: vec![], + }; + + upsert_key_entry(mk(100)).unwrap(); + upsert_key_entry(mk(200)).unwrap(); + + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 1); + assert_eq!(file.keys[0].expiry, Some(200)); + + // Different chain_id => separate entry. + let mut other = mk(300); + other.chain_id = 42431; + upsert_key_entry(other).unwrap(); + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 2); + }); + } + + #[test] + fn upsert_replaces_when_key_address_changes() { + // Re-login produces a fresh random key address; the new entry must + // supersede the old one for the same (wallet, chain), not coexist. + with_tempo_home(|| { + let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); + let old_key = Address::from_str("0x000000000000000000000000000000000000aaaa").unwrap(); + let new_key = Address::from_str("0x000000000000000000000000000000000000bbbb").unwrap(); + + let mk = |key_addr: Address| KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: wallet, + chain_id: 4217, + key_type: KeyType::Secp256k1, + key_address: Some(key_addr), + key: Some("0xdead".to_string()), + key_authorization: Some("0xbeef".to_string()), + expiry: Some(100), + limits: vec![], + }; + + upsert_key_entry(mk(old_key)).unwrap(); + upsert_key_entry(mk(new_key)).unwrap(); + + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 1, "old entry must be replaced, not duplicated"); + assert_eq!(file.keys[0].key_address, Some(new_key)); + }); + } +} diff --git a/crates/common/src/tempo/mod.rs b/crates/common/src/tempo/mod.rs index ec51dc607b5ab..ef8d0212bd453 100644 --- a/crates/common/src/tempo/mod.rs +++ b/crates/common/src/tempo/mod.rs @@ -1,8 +1,24 @@ //! Tempo network utilities. +pub mod auth; + +use crate::FoundryTransactionBuilder; +use alloy_network::Network; +use alloy_primitives::{Address, B256, Signature}; +use alloy_signer::Signer; +use eyre::{Context, Result}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use std::sync::Arc; + mod keystore; + +pub(crate) use auth::is_known_tempo_endpoint; +pub use auth::{AccessKeyOutcome, EnsureAccessKeyConfig, ensure_access_key}; pub use keystore::*; +#[cfg(test)] +pub(crate) use keystore::test_env_mutex; + #[cfg(test)] mod tests; @@ -16,3 +32,173 @@ mod tests; /// /// See pub const TEMPO_BROWSER_GAS_BUFFER: u64 = 7_000; + +/// Gas sponsor configuration for Tempo fee-payer signatures. +#[derive(Clone, Debug)] +pub struct TempoSponsor { + sponsor: Address, + signer: Option>, + signature: Option, +} + +impl TempoSponsor { + pub const fn new( + sponsor: Address, + signer: Option>, + signature: Option, + ) -> Self { + Self { sponsor, signer, signature } + } + + pub const fn sponsor(&self) -> Address { + self.sponsor + } + + pub async fn attach_and_print( + &self, + tx: &mut N::TransactionRequest, + sender: Address, + ) -> Result + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if self.sponsor == sender { + eyre::bail!( + "invalid Tempo sponsorship: sponsor {} must not equal transaction sender", + self.sponsor + ); + } + + let digest = tx.compute_sponsor_hash(sender).ok_or_else(|| { + eyre::eyre!( + "failed to compute Tempo sponsor digest; make sure this is a complete Tempo AA transaction" + ) + })?; + + let preview = TempoSponsorPreview { + sponsor: self.sponsor, + fee_token: tx.fee_token(), + valid_before: tx.valid_before().map(|v| v.get()), + valid_after: tx.valid_after().map(|v| v.get()), + digest, + }; + preview.print()?; + + let signature = if let Some(signature) = self.signature { + signature + } else if let Some(signer) = &self.signer { + signer.sign_hash(&digest).await.context("failed to sign Tempo sponsor digest")? + } else { + eyre::bail!("missing Tempo sponsor signature or signer") + }; + + let recovered = signature + .recover_address_from_prehash(&digest) + .context("failed to recover Tempo sponsor signature")?; + if recovered != self.sponsor { + eyre::bail!("Tempo sponsor signature recovered {recovered}, expected {}", self.sponsor); + } + if recovered == sender { + eyre::bail!( + "invalid Tempo sponsorship: recovered fee payer {recovered} must not equal transaction sender" + ); + } + + tx.set_fee_payer_signature(signature); + Ok(preview) + } +} + +/// User-visible sponsor digest metadata for a single outgoing Tempo transaction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TempoSponsorPreview { + pub sponsor: Address, + pub fee_token: Option
, + pub valid_before: Option, + pub valid_after: Option, + pub digest: B256, +} + +impl TempoSponsorPreview { + pub fn print(&self) -> Result<()> { + crate::sh_eprintln!("Tempo sponsor: {}", self.sponsor)?; + crate::sh_eprintln!( + "Tempo fee token: {}", + self.fee_token.map_or_else(|| "network default".to_string(), |addr| addr.to_string()) + )?; + crate::sh_eprintln!( + "Tempo validity: after {}, before {}", + self.valid_after.map_or_else(|| "none".to_string(), |v| v.to_string()), + self.valid_before.map_or_else(|| "none".to_string(), |v| v.to_string()) + )?; + crate::sh_eprintln!("Tempo sponsor digest: {:?}", self.digest)?; + Ok(()) + } +} + +/// Resolves a `--tempo.sponsor-signer` URI into a Foundry wallet signer. +pub async fn resolve_tempo_sponsor_signer(spec: &str) -> Result { + let spec = spec.trim(); + let (scheme, value) = spec + .split_once("://") + .map(|(scheme, value)| (scheme.to_ascii_lowercase(), value)) + .unwrap_or_else(|| (spec.to_ascii_lowercase(), "")); + + match scheme.as_str() { + "env" => { + if value.is_empty() { + eyre::bail!("env:// sponsor signer requires an environment variable name"); + } + let private_key = std::env::var(value) + .wrap_err_with(|| format!("{value} environment variable is required"))?; + foundry_wallets::utils::create_private_key_signer(&private_key) + } + "private-key" => { + if value.is_empty() { + eyre::bail!("private-key:// sponsor signer requires a private key"); + } + foundry_wallets::utils::create_private_key_signer(value) + } + "keystore" => { + if value.is_empty() { + eyre::bail!("keystore:// sponsor signer requires a keystore path"); + } + WalletOpts { keystore_path: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "account" => { + if value.is_empty() { + eyre::bail!("account:// sponsor signer requires an account name"); + } + WalletOpts { keystore_account_name: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "ledger" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { ledger: true, raw, ..Default::default() }.signer().await + } + "trezor" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { trezor: true, raw, ..Default::default() }.signer().await + } + "aws" => WalletOpts { aws: true, ..Default::default() }.signer().await, + "gcp" => WalletOpts { gcp: true, ..Default::default() }.signer().await, + "turnkey" => WalletOpts { turnkey: true, ..Default::default() }.signer().await, + "browser" => { + eyre::bail!( + "browser:// sponsor signing is not supported by the current browser wallet API; use --tempo.sponsor-sig or another sponsor signer" + ) + } + _ => eyre::bail!( + "unsupported Tempo sponsor signer `{spec}`; expected env://VAR, keystore://PATH, account://NAME, ledger://, trezor://, aws://, gcp://, turnkey://, or private-key://KEY" + ), + } +} diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index de03cf3adc73e..aa4c971680d00 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -9,7 +9,9 @@ use alloy_primitives::{Address, B256, Signature, TxKind, U256}; use alloy_provider::Provider; use alloy_signer::Signer; use eyre::Result; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionRequest; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_primitives::{ @@ -244,6 +246,24 @@ pub trait FoundryTransactionBuilder: NetworkTransactionBuilder { /// on-chain as part of this transaction. fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} + /// Embeds key authorization before gas estimation/signing if the access key is not yet + /// provisioned on-chain. + /// + /// This mirrors the mutation performed by [`Self::sign_with_access_key`], but makes the final + /// transaction body available before fee-payer sponsor digests are computed. + fn prepare_access_key_authorization<'a>( + &'a mut self, + _provider: &'a impl Provider, + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + async { Ok(()) } + } + /// Converts a CREATE transaction into an AA-compatible call entry. /// /// Tempo AA transactions use a `calls` list instead of `to`+`input`. Must be @@ -355,6 +375,7 @@ impl FoundryTransactionBuilder for ::Transact } } +#[cfg(feature = "optimism")] impl FoundryTransactionBuilder for OpTransactionRequest { fn reset_gas_limit(&mut self) { self.as_mut().gas = None; @@ -439,6 +460,35 @@ impl FoundryTransactionBuilder for ::Tran self.key_authorization = Some(key_authorization); } + fn prepare_access_key_authorization<'a>( + &'a mut self, + provider: &'a impl Provider, + wallet_address: Address, + key_address: Address, + key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + let auth = key_authorization.cloned(); + + async move { + if let Some(auth) = auth { + let is_provisioned = provider + .get_keychain_key(wallet_address, key_address) + .await + .map(|info| info.keyId != Address::ZERO) + .unwrap_or(false); + + if !is_provisioned { + self.set_key_authorization(auth); + } + } + + Ok(()) + } + } + fn convert_create_to_call(&mut self) { if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) { let input = self.inner.input.input().cloned().unwrap_or_default(); @@ -473,7 +523,12 @@ impl FoundryTransactionBuilder for ::Tran let is_provisioned = provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false); - if !is_provisioned { + if !is_provisioned && self.key_authorization.is_none() { + if self.fee_payer_signature.is_some() { + eyre::bail!( + "cannot add Tempo key authorization after fee payer signature was attached" + ); + } self.set_key_authorization(auth); } } diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs index 9ca6cb02b10ee..c2e34419248c4 100644 --- a/crates/common/src/transactions/receipt.rs +++ b/crates/common/src/transactions/receipt.rs @@ -7,6 +7,7 @@ use alloy_provider::{ use alloy_rpc_types::{BlockId, TransactionReceipt}; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionReceipt; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionReceipt; @@ -23,6 +24,7 @@ impl FoundryReceiptResponse for TransactionReceipt { } } +#[cfg(feature = "optimism")] impl FoundryReceiptResponse for OpTransactionReceipt { fn set_contract_address(&mut self, contract_address: Address) { self.inner.contract_address = Some(contract_address); diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index bab59137b0130..8f63718e086cd 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -10,6 +10,10 @@ use std::path::PathBuf; pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, + /// Optional 1-based fuzz run to execute. + pub run: Option, + /// Optional fuzz worker ID to pair with `run`. + pub worker: Option, /// Fails the fuzzed test if a revert occurs. pub fail_on_revert: bool, /// The maximum number of test case rejections allowed, @@ -37,6 +41,8 @@ impl Default for FuzzConfig { fn default() -> Self { Self { runs: 256, + run: None, + worker: None, fail_on_revert: true, max_test_rejects: 65536, seed: None, diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 270df14a6c291..000cefc26737a 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use crate::Config; use alloy_primitives::map::HashMap; use figment::{ @@ -5,6 +7,7 @@ use figment::{ value::{Dict, Map, Value}, }; use foundry_compilers::ProjectCompileOutput; +use foundry_evm_networks::NetworkVariant; use itertools::Itertools; mod natspec; @@ -123,6 +126,42 @@ impl InlineConfig { self.get_function(contract, function).is_some_and(|map| !map.is_empty()) } + /// Returns the configured [`NetworkVariant`] for a given test, checking function-level first + /// then contract-level. Returns `None` if no network annotation is present. + pub fn network_for( + &self, + profile: &Profile, + contract: &str, + function: &str, + ) -> Option { + let data = self.provide(contract, function).data().ok()?; + let dict = data.get(profile).or_else(|| data.get(&Profile::Default))?; + if let Some(Value::Dict(_, networks)) = dict.get("networks") + && let Some(Value::String(_, s)) = networks.get("network") + { + return s.parse().ok(); + } + None + } + + /// Returns all distinct [`NetworkVariant`]s referenced in any inline config annotation. + /// + /// This is used to determine whether a multi-network test pass is needed. + pub fn referenced_override_networks(&self, profile: &Profile) -> Vec { + let mut seen = BTreeSet::new(); + for (contract, function) in self.fn_level.keys() { + if let Some(v) = self.network_for(profile, contract, function) { + seen.insert(v); + } + } + for contract in self.contract_level.keys() { + if let Some(v) = self.network_for(profile, contract, "") { + seen.insert(v); + } + } + seen.into_iter().collect() + } + fn get_contract(&self, contract: &str) -> Option<&DataMap> { self.contract_level.get(contract) } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 3c8cad85bae10..cc3dabd32d4bf 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -29,3 +29,11 @@ ratatui = { version = "0.30", default-features = false, features = [ revm.workspace = true tracing.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 809e15b077c37..814beab402729 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -32,3 +32,7 @@ thiserror.workspace = true toml.workspace = true tracing.workspace = true regex.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index b8fd3760cd850..888f6269623a5 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -72,8 +72,8 @@ impl AsDoc for CommentsRef<'_> { writer.writeln_raw(format!( "{}{}: {}", if customs.len() == 1 { "" } else { "- " }, - &c.tag, - &c.value + c.tag, + c.value ))?; writer.writeln()?; } diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 03d569c17f500..801e813026a39 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -36,7 +36,7 @@ alloy-primitives = { workspace = true, features = [ alloy-provider.workspace = true alloy-network.workspace = true alloy-consensus.workspace = true -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-rpc-types = { workspace = true, features = ["anvil"] } alloy-sol-types.workspace = true alloy-rlp.workspace = true @@ -54,9 +54,10 @@ revm = { workspace = true, features = [ "blst", ] } revm-inspectors.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-network.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } tempo-revm.workspace = true tempo-alloy.workspace = true tempo-contracts.workspace = true @@ -77,7 +78,18 @@ url.workspace = true [dev-dependencies] alloy-serde.workspace = true -op-alloy-consensus.workspace = true -op-alloy-rpc-types.workspace = true anvil.workspace = true foundry-test-utils.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "foundry-common/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", +] diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 0cfd56a44219c..b836023a968b7 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -223,8 +223,8 @@ fn trimmed_hex(s: &[u8]) -> String { } else { format!( "{}…{} ({} bytes)", - &hex::encode(&s[..n / 2]), - &hex::encode(&s[s.len() - n / 2..]), + hex::encode(&s[..n / 2]), + hex::encode(&s[s.len() - n / 2..]), s.len(), ) } diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 132b986f55e7f..bfc45b6b5d773 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -4,13 +4,9 @@ use alloy_consensus::Typed2718; pub use alloy_evm::EvmEnv; use alloy_evm::FromRecoveredTx; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, U256}; -use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; -use op_revm::{ - OpTransaction, - transaction::{OpTxTr, deposit::DEPOSIT_TRANSACTION_TYPE}, -}; +#[cfg(feature = "optimism")] +use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; use revm::{ Context, Database, Journal, context::{Block, BlockEnv, Cfg, CfgEnv, Transaction, TxEnv}, @@ -236,9 +232,16 @@ pub trait FoundryTransaction: Transaction { /// Sets whether the transaction is a system transaction fn set_system_transaction(&mut self, _is_system_transaction: bool) {} - /// Returns `true` if transaction is of type [`DEPOSIT_TRANSACTION_TYPE`]. + /// Returns `true` if transaction is an Optimism deposit transaction. fn is_deposit(&self) -> bool { - self.tx_type() == DEPOSIT_TRANSACTION_TYPE + #[cfg(feature = "optimism")] + { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE + } + #[cfg(not(feature = "optimism"))] + { + false + } } // Tempo methods @@ -320,188 +323,6 @@ impl FoundryTransaction for TxEnv { } } -impl FoundryTransaction for OpTransaction { - fn set_tx_type(&mut self, tx_type: u8) { - self.base.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.base.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.base.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.base.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.base.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.base.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.base.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.base.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.base.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.base.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.base.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.base.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - OpTxTr::enveloped_tx(self) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.enveloped_tx = Some(bytes); - } - - fn source_hash(&self) -> Option { - OpTxTr::source_hash(self) - } - - fn set_source_hash(&mut self, source_hash: B256) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.source_hash = source_hash; - } - } - - fn mint(&self) -> Option { - OpTxTr::mint(self) - } - - fn set_mint(&mut self, mint: u128) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.mint = Some(mint); - } - } - - fn is_system_transaction(&self) -> bool { - OpTxTr::is_system_transaction(self) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.is_system_transaction = is_system_transaction; - } - } -} - -impl FoundryTransaction for OpTx { - fn set_tx_type(&mut self, tx_type: u8) { - self.0.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.0.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.0.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.0.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.0.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.0.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.0.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.0.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.0.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.0.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.0.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.0.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - FoundryTransaction::enveloped_tx(&self.0) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.0.set_enveloped_tx(bytes); - } - - fn source_hash(&self) -> Option { - FoundryTransaction::source_hash(&self.0) - } - - fn set_source_hash(&mut self, source_hash: B256) { - self.0.set_source_hash(source_hash); - } - - fn mint(&self) -> Option { - FoundryTransaction::mint(&self.0) - } - - fn set_mint(&mut self, mint: u128) { - self.0.set_mint(mint); - } - - fn is_system_transaction(&self) -> bool { - FoundryTransaction::is_system_transaction(&self.0) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - self.0.set_system_transaction(is_system_transaction); - } -} - impl FoundryTransaction for TempoTxEnv { fn set_tx_type(&mut self, tx_type: u8) { self.inner.set_tx_type(tx_type); @@ -687,32 +508,6 @@ impl FromAnyRpcTransaction for TxEnv { } } -impl FromAnyRpcTransaction for OpTx { - fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { - if let Some(envelope) = tx.as_envelope() { - return Ok(Self(OpTransaction:: { - base: TxEnv::from_recovered_tx(envelope, tx.from()), - enveloped_tx: None, - deposit: Default::default(), - })); - } - - // Handle OP deposit transactions from `Unknown` envelope variant. - if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner - && unknown.ty() == DEPOSIT_TX_TYPE_ID - { - let mut fields = unknown.inner.fields.clone(); - fields.insert("from".to_string(), serde_json::to_value(tx.from())?); - let deposit_tx: TxDeposit = fields - .deserialize_into() - .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; - return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); - } - - eyre::bail!("cannot convert unknown transaction type to OpTransaction") - } -} - impl FromAnyRpcTransaction for TempoTxEnv { fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { use alloy_consensus::Transaction as _; @@ -747,6 +542,222 @@ impl FromAnyRpcTransaction for TempoTxEnv { } } +#[cfg(feature = "optimism")] +mod optimism { + use super::*; + use alloy_op_evm::OpTx; + use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; + use op_revm::{OpTransaction, transaction::OpTxTr}; + + impl FoundryTransaction for OpTransaction { + fn set_tx_type(&mut self, tx_type: u8) { + self.base.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.base.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.base.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.base.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.base.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.base.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.base.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.base.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.base.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + OpTxTr::enveloped_tx(self) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.enveloped_tx = Some(bytes); + } + + fn source_hash(&self) -> Option { + OpTxTr::source_hash(self) + } + + fn set_source_hash(&mut self, source_hash: B256) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.source_hash = source_hash; + } + } + + fn mint(&self) -> Option { + OpTxTr::mint(self) + } + + fn set_mint(&mut self, mint: u128) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.mint = Some(mint); + } + } + + fn is_system_transaction(&self) -> bool { + OpTxTr::is_system_transaction(self) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.is_system_transaction = is_system_transaction; + } + } + } + + impl FoundryTransaction for OpTx { + fn set_tx_type(&mut self, tx_type: u8) { + self.0.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.0.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.0.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.0.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.0.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.0.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.0.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.0.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.0.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.0.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.0.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + FoundryTransaction::enveloped_tx(&self.0) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.0.set_enveloped_tx(bytes); + } + + fn source_hash(&self) -> Option { + FoundryTransaction::source_hash(&self.0) + } + + fn set_source_hash(&mut self, source_hash: B256) { + self.0.set_source_hash(source_hash); + } + + fn mint(&self) -> Option { + FoundryTransaction::mint(&self.0) + } + + fn set_mint(&mut self, mint: u128) { + self.0.set_mint(mint); + } + + fn is_system_transaction(&self) -> bool { + FoundryTransaction::is_system_transaction(&self.0) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + self.0.set_system_transaction(is_system_transaction); + } + } + + impl FromAnyRpcTransaction for OpTx { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + return Ok(Self(OpTransaction:: { + base: TxEnv::from_recovered_tx(envelope, tx.from()), + enveloped_tx: None, + deposit: Default::default(), + })); + } + + // Handle OP deposit transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == DEPOSIT_TX_TYPE_ID + { + let mut fields = unknown.inner.fields.clone(); + fields.insert("from".to_string(), serde_json::to_value(tx.from())?); + let deposit_tx: TxDeposit = fields + .deserialize_into() + .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; + return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); + } + + eyre::bail!("cannot convert unknown transaction type to OpTransaction") + } + } +} + #[cfg(test)] mod tests { use std::num::NonZeroU64; @@ -755,14 +766,10 @@ mod tests { use alloy_consensus::{Sealed, Signed, TxEip1559, transaction::Recovered}; use alloy_evm::{EthEvmFactory, EvmFactory}; use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; - use alloy_op_evm::OpEvmFactory; use alloy_primitives::Signature; use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo}; use alloy_serde::WithOtherFields; use foundry_evm_hardforks::TempoHardfork; - use op_alloy_consensus::{OpTxEnvelope, transaction::OpTransactionInfo}; - use op_alloy_rpc_types::Transaction as OpRpcTransaction; - use op_revm::OpSpecId; use revm::database::EmptyDB; use tempo_alloy::primitives::{ AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, @@ -793,30 +800,6 @@ mod tests { evm.ctx_mut().set_evm(evm_env); } - #[test] - fn op_evm_foundry_context_ext_implementation() { - let mut evm = - OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); - - // Test EVM Context Block mutation - evm.ctx_mut().block_mut().set_number(U256::from(123)); - assert_eq!(evm.ctx().block().number(), U256::from(123)); - - // Test EVM Context Tx mutation - evm.ctx_mut().tx_mut().set_nonce(99); - assert_eq!(evm.ctx().tx().nonce(), 99); - - // Test EVM Context Cfg mutation - evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; - assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); - - // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env - let tx_env = evm.ctx().tx_clone(); - evm.ctx_mut().set_tx(tx_env); - let evm_env = evm.ctx().evm_clone(); - evm.ctx_mut().set_evm(evm_env); - } - #[test] fn tempo_evm_foundry_context_ext_implementation() { let mut evm = TempoEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default()); @@ -874,23 +857,6 @@ mod tests { assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); } - #[test] - fn from_any_rpc_transaction_for_op() { - let from = Address::random(); - let signed_tx = make_signed_eip1559(); - - // Build the eth TxEnv to compare against op base - let rpc_tx = RpcTransaction::from_transaction( - Recovered::new_unchecked(signed_tx.into(), from), - TransactionInfo::default(), - ); - let any_tx = >::from(rpc_tx); - let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base, expected_base); - } - #[test] fn from_any_rpc_transaction_unknown_envelope_errors() { let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { @@ -915,39 +881,6 @@ mod tests { assert!(result.to_string().contains("unknown transaction type")); } - #[test] - fn from_any_rpc_transaction_for_op_deposit() { - let from = Address::random(); - let source_hash = B256::random(); - let deposit = TxDeposit { - source_hash, - from, - to: TxKind::Call(Address::with_last_byte(0xCC)), - mint: 1111, - value: U256::from(200), - gas_limit: 21000, - is_system_transaction: true, - input: Default::default(), - }; - - // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as AnyRpcTransaction. - let op_rpc_tx = OpRpcTransaction::from_transaction( - Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), - OpTransactionInfo::default(), - ); - let json = serde_json::to_value(&op_rpc_tx).unwrap(); - let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base.caller, from); - assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); - assert_eq!(op_tx_env.base.value, U256::from(200)); - assert_eq!(op_tx_env.base.gas_limit, 21000); - assert_eq!(op_tx_env.deposit.source_hash, source_hash); - assert_eq!(op_tx_env.deposit.mint, Some(1111)); - assert!(op_tx_env.deposit.is_system_transaction); - } - #[test] fn from_any_rpc_transaction_for_tempo_eth_envelope() { let from = Address::random(); @@ -1004,4 +937,88 @@ mod tests { assert_eq!(tx_env.inner.chain_id, Some(42431)); assert_eq!(tx_env.fee_token, fee_token); } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + use alloy_op_evm::{OpEvmFactory, OpTx}; + use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo}; + use op_alloy_rpc_types::Transaction as OpRpcTransaction; + use op_revm::OpSpecId; + + #[test] + fn op_evm_foundry_context_ext_implementation() { + let mut evm = + OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; + assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + #[test] + fn from_any_rpc_transaction_for_op() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base, expected_base); + } + + #[test] + fn from_any_rpc_transaction_for_op_deposit() { + let from = Address::random(); + let source_hash = B256::random(); + let deposit = TxDeposit { + source_hash, + from, + to: TxKind::Call(Address::with_last_byte(0xCC)), + mint: 1111, + value: U256::from(200), + gas_limit: 21000, + is_system_transaction: true, + input: Default::default(), + }; + + // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let op_rpc_tx = OpRpcTransaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), + OpTransactionInfo::default(), + ); + let json = serde_json::to_value(&op_rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base.caller, from); + assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); + assert_eq!(op_tx_env.base.value, U256::from(200)); + assert_eq!(op_tx_env.base.gas_limit, 21000); + assert_eq!(op_tx_env.deposit.source_hash, source_hash); + assert_eq!(op_tx_env.deposit.mint, Some(1111)); + assert!(op_tx_env.deposit.is_system_transaction); + } + } } diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs index 708226be003a2..fc9e9e7d2810f 100644 --- a/crates/evm/core/src/evm/mod.rs +++ b/crates/evm/core/src/evm/mod.rs @@ -10,14 +10,11 @@ use alloy_evm::{ EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap, }; use alloy_network::{Ethereum, Network}; -use alloy_op_evm::OpEvmFactory; use alloy_primitives::{Address, Signature, U256}; use alloy_rlp::Decodable; use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; -use op_alloy_network::Optimism; -use op_revm::OpHaltReason; use revm::{ Database, context::{ @@ -36,10 +33,12 @@ use tempo_evm::evm::TempoEvmFactory; use tempo_revm::TempoHaltReason; pub mod eth; +#[cfg(feature = "optimism")] pub mod op; pub mod tempo; pub use eth::*; +#[cfg(feature = "optimism")] pub use op::*; pub use tempo::*; @@ -75,13 +74,6 @@ impl FoundryEvmNetwork for TempoEvmNetwork { type EvmFactory = TempoEvmFactory; } -#[derive(Clone, Copy, Debug, Default)] -pub struct OpEvmNetwork; -impl FoundryEvmNetwork for OpEvmNetwork { - type Network = Optimism; - type EvmFactory = OpEvmFactory; -} - /// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. pub type EvmFactoryFor = ::EvmFactory; pub type FoundryContextFor<'db, FEN> = @@ -249,15 +241,6 @@ impl IntoInstructionResult for HaltReason { } } -impl IntoInstructionResult for OpHaltReason { - fn into_instruction_result(self) -> InstructionResult { - match self { - Self::Base(eth) => eth.into(), - Self::FailedDeposit => InstructionResult::Stop, - } - } -} - impl IntoInstructionResult for TempoHaltReason { fn into_instruction_result(self) -> InstructionResult { match self { diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs index cb8bf272d9a05..efb74ad3abf50 100644 --- a/crates/evm/core/src/evm/op.rs +++ b/crates/evm/core/src/evm/op.rs @@ -1,6 +1,7 @@ use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; use alloy_op_evm::{OpEvm, OpEvmContext, OpEvmFactory, OpTx}; use foundry_fork_db::DatabaseError; +use op_alloy_network::Optimism; use op_revm::{OpEvm as RevmEvm, OpHaltReason, OpSpecId, OpTransactionError, handler::OpHandler}; use revm::{ context::{ @@ -10,16 +11,33 @@ use revm::{ handler::{EthFrame, EvmTr, FrameResult, Handler, instructions::EthInstructions}, inspector::InspectorHandler, interpreter::{ - FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + FrameInput, InstructionResult, SharedMemory, interpreter::EthInterpreter, + interpreter_action::FrameInit, }, }; use crate::{ FoundryContextExt, FoundryInspectorExt, backend::{DatabaseExt, JournaledState}, - evm::{FoundryEvmFactory, NestedEvm}, + evm::{FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm}, }; +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = OpEvmFactory; +} + +impl IntoInstructionResult for OpHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Base(eth) => eth.into(), + Self::FailedDeposit => InstructionResult::Stop, + } + } +} + type OpEvmHandler<'db, I> = OpHandler, EVMError, EthFrame>; diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index aefa0e2ee9741..2284823047ca6 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -212,13 +212,18 @@ pub struct ForkDbStateSnapshot { } impl ForkDbStateSnapshot { - fn get_storage(&self, address: Address, index: U256) -> Option { - self.local - .cache - .accounts - .get(&address) - .and_then(|account| account.storage.get(&index)) - .copied() + /// Lookup storage in `state_snapshot`, then fall back to the backend (remote RPC). + fn storage_from_snapshot_or_backend( + &self, + address: Address, + index: U256, + ) -> Result { + // Check state_snapshot.storage first (data fetched by SharedBackend / disk cache). + if let Some(val) = self.state_snapshot.storage.get(&address).and_then(|s| s.get(&index)) { + return Ok(*val); + } + // Fall back to the underlying backend (SharedBackend → remote RPC). + DatabaseRef::storage_ref(&self.local, address, index) } } @@ -250,15 +255,9 @@ impl DatabaseRef for ForkDbStateSnapshot { match self.local.cache.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), - }, - }, - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), + None => self.storage_from_snapshot_or_backend(address, index), }, + None => self.storage_from_snapshot_or_backend(address, index), } } @@ -303,4 +302,28 @@ mod tests { assert!(loaded.is_some()); assert_eq!(loaded.unwrap(), info); } + + /// Verifies that `ForkDbStateSnapshot::storage_ref` reads from `state_snapshot.storage` + /// when the slot is missing from `local.cache.accounts`. Without this lookup the call + /// would fall through to the backend and return the unrelated remote value. + #[tokio::test(flavor = "multi_thread")] + async fn fork_db_state_snapshot_reads_storage_from_snapshot() { + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); + let provider = get_http_provider(rpc.clone()); + let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc); + let db = BlockchainDb::new(meta, None); + let backend = SharedBackend::spawn_backend(Arc::new(provider), db, None).await; + + let address = Address::random(); + let slot = U256::from(42u64); + let expected = U256::from(0xdeadbeefu64); + + let mut state_snapshot = StateSnapshot::default(); + state_snapshot.storage.entry(address).or_default().insert(slot, expected); + + let snapshot = ForkDbStateSnapshot { local: CacheDB::new(backend), state_snapshot }; + + let got = DatabaseRef::storage_ref(&snapshot, address, slot).unwrap(); + assert_eq!(got, expected); + } } diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 1b2201a9b8b84..c2edbb9dfd33b 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -5,6 +5,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::constants::DEFAULT_CREATE2_DEPLOYER; use alloy_primitives::{Address, map::HashMap}; use auto_impl::auto_impl; diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 82efd4a1aaaaa..ab68eb08821e8 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -137,8 +137,12 @@ impl EvmOpts { /// [`NetworkConfigs::with_chain_id`] to auto-enable the correct network /// (e.g. Tempo, OP Stack) based on the chain ID. pub async fn infer_network_from_fork(&mut self) { + #[cfg(feature = "optimism")] + let already_op = self.networks.is_optimism(); + #[cfg(not(feature = "optimism"))] + let already_op = false; if !self.networks.is_tempo() - && !self.networks.is_optimism() + && !already_op && let Some(ref fork_url) = self.fork_url && let Ok(provider) = self.fork_provider_with_url::(fork_url) && let Ok(chain_id) = provider.get_chain_id().await @@ -474,6 +478,7 @@ mod tests { // Plain anvil (chain id 31337) without tempo flag -> Ethereum (no network flags set). assert!(!evm_opts.networks.is_tempo()); + #[cfg(feature = "optimism")] assert!(!evm_opts.networks.is_optimism()); assert!(!evm_opts.networks.is_celo()); assert_eq!(evm_opts.networks, NetworkConfigs::default()); diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index d2d7b077ee9f0..758604564726e 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -25,3 +25,7 @@ semver.workspace = true tracing.workspace = true rayon.workspace = true solar.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 70bce50a89882..5dbf07c7a356c 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -61,3 +61,16 @@ serde.workspace = true uuid.workspace = true rayon.workspace = true tokio.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm-core/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 33152b73dda3c..1932a834ab397 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -17,7 +17,7 @@ use foundry_evm_core::{ use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ BaseCounterExample, BasicTxDetails, CallDetails, CounterExample, FuzzCase, FuzzError, - FuzzFixtures, FuzzTestResult, + FuzzFixtures, FuzzRunMetadata, FuzzTestResult, strategies::{EvmFuzzState, fuzz_calldata, fuzz_calldata_from_state}, }; use foundry_evm_traces::SparsedTraceArena; @@ -71,6 +71,8 @@ struct WorkerState { runs: u32, /// Failure reason if this worker failed failure: Option, + /// Fuzz run metadata that produced the failure. + failure_run: Option, /// Last run timestamp in milliseconds /// /// Used to identify which worker ran last and collect its traces and call breakpoints @@ -93,6 +95,7 @@ impl WorkerState { deprecated_cheatcodes: HashMap::default(), runs: 0, failure: None, + failure_run: None, last_run_timestamp: 0, failed_corpus_replays: 0, } @@ -196,8 +199,14 @@ impl FuzzedExecutor { config: FuzzConfig, persisted_failure: Option, ) -> Self { - let max_workers = - if config.runs == 0 { 0 } else { Ord::max(1, config.runs / MIN_RUNS_PER_WORKER) }; + let run_limit = if config.run.is_some() { 1 } else { config.runs }; + let max_workers = if run_limit == 0 { + 0 + } else if config.run.is_some() { + 1 + } else { + Ord::max(1, run_limit / MIN_RUNS_PER_WORKER) + }; let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } @@ -221,8 +230,9 @@ impl FuzzedExecutor { ) -> Result { let shared_state = SharedFuzzState::new(state, self.config.timeout, early_exit.clone()); - debug!(n = self.num_workers, "spawning workers"); - let workers = (0..self.num_workers) + let worker_ids = self.worker_ids(); + debug!(n = worker_ids.len(), "spawning workers"); + let workers = worker_ids .into_par_iter() .map(|worker_id| { let _guard = tokio_handle.enter(); @@ -364,8 +374,14 @@ impl FuzzedExecutor { } else { vec![] }; + let fuzz = failed_worker.failure_run.unwrap_or_default(); result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + BaseCounterExample::from_fuzz_call(calldata, args, call.traces) + .with_fuzz_metadata(FuzzRunMetadata::new( + fuzz.seed.or(self.config.seed), + fuzz.run, + fuzz.worker, + )), )); } Some(TestCaseError::Reject(reason)) => { @@ -453,16 +469,7 @@ impl FuzzedExecutor { runner_config.cases = worker_runs; let mut runner = if let Some(seed) = self.config.seed { - // For deterministic parallel fuzzing, derive a unique seed for each worker - let worker_seed = if worker_id == 0 { - // Master worker uses the provided seed as is. - seed - } else { - // Derive a worker-specific seed using keccak256(seed || worker_id) - let seed_data = - [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); - U256::from_be_bytes(keccak256(seed_data).0) - }; + let worker_seed = Self::fuzz_worker_seed(seed, worker_id); trace!(target: "forge::test", ?worker_seed, "deterministic seed for worker {worker_id}"); let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &worker_seed.to_be_bytes::<32>()); TestRunner::new_with_rng(runner_config, rng) @@ -470,11 +477,25 @@ impl FuzzedExecutor { TestRunner::new(runner_config) }; - let mut persisted_failure = self.persisted_failure.as_ref().filter(|_| worker_id == 0); + if let Some(target_run) = self.config.run { + for _ in 1..target_run { + if let Err(err) = corpus.new_input(&mut runner, &shared_state.state, func) { + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id + ))); + shared_state.try_claim_failure(worker_id); + return Ok(worker); + } + } + } + + let mut persisted_failure = + self.persisted_failure.as_ref().filter(|_| worker_id == 0 && self.config.run.is_none()); // Offset to stagger corpus syncs across workers; so that workers don't sync at the same // time. - let sync_offset = worker_id as u32 * 100; + let sync_offset = (worker_id as u32).saturating_mul(100); let sync_threshold = SYNC_INTERVAL + sync_offset; let mut runs_since_sync = sync_threshold; // Always sync at the start. let mut last_metrics_report = Instant::now(); @@ -483,11 +504,27 @@ impl FuzzedExecutor { // 2. Worker hasn't reached its specific run limit 'stop: while shared_state.should_continue() && worker.runs < worker_runs { // If counterexample recorded, replay it first, without incrementing runs. - let input = if worker_id == 0 + let (input, fuzz_run) = if worker_id == 0 && let Some(failure) = persisted_failure.take() && failure.calldata.get(..4).is_some_and(|selector| func.selector() == selector) { - failure.calldata.clone() + let seed = failure.fuzz.seed.or(self.config.seed); + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() + && let Some(seed) = seed + { + let run = failure.fuzz.run.unwrap_or(1); + let worker = failure.fuzz.worker.unwrap_or(worker_id as u32) as usize; + cheats.set_seed(Self::fuzz_run_seed(seed, worker, run)); + } + + ( + failure.calldata.clone(), + Some(FuzzRunMetadata::new( + seed, + failure.fuzz.run, + Some(failure.fuzz.worker.unwrap_or(worker_id as u32)), + )), + ) } else { runs_since_sync += 1; if runs_since_sync >= sync_threshold { @@ -503,13 +540,14 @@ impl FuzzedExecutor { runs_since_sync = 0; } + let fuzz_run = self.config.run.unwrap_or(worker.runs + 1); if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() && let Some(seed) = self.config.seed { - cheats.set_seed(seed.wrapping_add(U256::from(worker.runs))); + cheats.set_seed(Self::fuzz_run_seed(seed, worker_id, fuzz_run)); } - match corpus.new_input(&mut runner, &shared_state.state, func) { + let input = match corpus.new_input(&mut runner, &shared_state.state, func) { Ok(input) => input, Err(err) => { worker.failure = Some(TestCaseError::fail(format!( @@ -519,13 +557,24 @@ impl FuzzedExecutor { shared_state.try_claim_failure(worker_id); break 'stop; } - } + }; + + ( + input, + Some(FuzzRunMetadata::new( + self.config.seed, + Some(fuzz_run), + Some(worker_id as u32), + )), + ) }; let mut inc_runs = || { let total_runs = shared_state.increment_runs(); debug_assert!( - shared_state.timer.is_enabled() || total_runs <= self.config.runs, + shared_state.timer.is_enabled() + || total_runs + <= if self.config.run.is_some() { 1 } else { self.config.runs }, "worker runs were not distributed correctly" ); worker.runs += 1; @@ -595,6 +644,7 @@ impl FuzzedExecutor { .. }) => { inc_runs(); + worker.failure_run = fuzz_run; // Only classify magic skip payloads when the revert originates from the // cheatcode address. @@ -656,7 +706,7 @@ impl FuzzedExecutor { /// Determines the number of runs per worker. const fn runs_per_worker(&self, worker_id: usize) -> u32 { let worker_id = worker_id as u32; - let total_runs = self.config.runs; + let total_runs = if self.config.run.is_some() { 1 } else { self.config.runs }; let n = self.num_workers as u32; let runs = total_runs / n; let remainder = total_runs % n; @@ -664,4 +714,29 @@ impl FuzzedExecutor { // assuming `worker_id` is in `0..n`. if worker_id < remainder { runs + 1 } else { runs } } + + /// Returns the worker IDs to execute. + fn worker_ids(&self) -> Vec { + if self.config.run.is_some() { + vec![self.config.worker.unwrap_or(0) as usize] + } else { + (0..self.num_workers).collect() + } + } + + /// Derives the deterministic RNG seed for a fuzz worker. + fn fuzz_worker_seed(seed: U256, worker_id: usize) -> U256 { + if worker_id == 0 { + seed + } else { + let worker_id = worker_id as u32; + let seed_data = [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); + U256::from_be_bytes(keccak256(seed_data).0) + } + } + + /// Derives the deterministic RNG seed for cheatcode randomness in a worker-local run. + fn fuzz_run_seed(seed: U256, worker_id: usize, run: u32) -> U256 { + Self::fuzz_worker_seed(seed, worker_id).wrapping_add(U256::from(run.saturating_sub(1))) + } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 27d8e6a0ed588..e02cdbc393ee6 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -736,7 +736,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { if !msg.is_empty() { msg.push_str(", "); } - msg.push_str(&format!("{}", &corpus_manager.metrics)); + msg.push_str(&format!("{}", corpus_manager.metrics)); } progress.set_message(msg); } diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 62e4e80a73674..5629b17d936da 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -50,3 +50,12 @@ rand.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 44d71fb6deee3..9c3e7d179c7f6 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -33,6 +33,27 @@ pub use strategies::LiteralMaps; mod inspector; pub use inspector::Fuzzer; +/// Metadata needed to reproduce a fuzz run. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct FuzzRunMetadata { + /// Seed used for the worker's input stream. + #[serde(default, rename = "fuzz_seed", skip_serializing_if = "Option::is_none")] + pub seed: Option, + /// 1-based run inside the worker's input stream. + #[serde(default, rename = "fuzz_run", skip_serializing_if = "Option::is_none")] + pub run: Option, + /// Worker that generated the input stream. + #[serde(default, rename = "fuzz_worker", skip_serializing_if = "Option::is_none")] + pub worker: Option, +} + +impl FuzzRunMetadata { + /// Creates metadata for reproducing a fuzz run. + pub const fn new(seed: Option, run: Option, worker: Option) -> Self { + Self { seed, run, worker } + } +} + /// Details of a transaction generated by fuzz strategy for fuzzing a target. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BasicTxDetails { @@ -102,6 +123,9 @@ pub struct BaseCounterExample { /// Whether to display sequence as solidity. #[serde(skip)] pub show_solidity: bool, + /// Fuzz metadata needed to reproduce this counterexample. + #[serde(flatten)] + pub fuzz: FuzzRunMetadata, } impl BaseCounterExample { @@ -137,6 +161,7 @@ impl BaseCounterExample { ), traces, show_solidity, + fuzz: FuzzRunMetadata::default(), }; } } @@ -154,6 +179,7 @@ impl BaseCounterExample { raw_args: None, traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } @@ -176,8 +202,15 @@ impl BaseCounterExample { raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()), traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } + + /// Sets fuzz metadata for reproducing this counterexample. + pub const fn with_fuzz_metadata(mut self, fuzz: FuzzRunMetadata) -> Self { + self.fuzz = fuzz; + self + } } impl fmt::Display for BaseCounterExample { @@ -229,7 +262,7 @@ impl fmt::Display for BaseCounterExample { if let Some(sig) = &self.signature { write!(f, "calldata={sig}")? } else { - write!(f, "calldata={}", &self.calldata)? + write!(f, "calldata={}", self.calldata)? } if let Some(args) = &self.args { diff --git a/crates/evm/hardforks/Cargo.toml b/crates/evm/hardforks/Cargo.toml index 9bf318028487a..68f6fb23bab07 100644 --- a/crates/evm/hardforks/Cargo.toml +++ b/crates/evm/hardforks/Cargo.toml @@ -16,10 +16,14 @@ workspace = true [dependencies] alloy-chains.workspace = true alloy-hardforks = { workspace = true, features = ["serde"] } -alloy-op-hardforks = { workspace = true, features = ["serde"] } +alloy-op-hardforks = { workspace = true, features = ["serde"], optional = true } alloy-rpc-types.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } revm.workspace = true serde = { workspace = true, features = ["derive"] } tempo-chainspec.workspace = true foundry-compilers.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "dep:op-revm"] diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs index 8a29ebb7af4ec..a8e0d51738263 100644 --- a/crates/evm/hardforks/src/lib.rs +++ b/crates/evm/hardforks/src/lib.rs @@ -8,11 +8,13 @@ use std::str::FromStr; use alloy_chains::Chain; use alloy_rpc_types::BlockNumberOrTag; use foundry_compilers::artifacts::EvmVersion; +#[cfg(feature = "optimism")] use op_revm::OpSpecId; use revm::primitives::hardfork::SpecId; use serde::{Deserialize, Serialize}; pub use alloy_hardforks::EthereumHardfork; +#[cfg(feature = "optimism")] pub use alloy_op_hardforks::OpHardfork; pub use tempo_chainspec::hardfork::TempoHardfork; @@ -20,6 +22,7 @@ pub use tempo_chainspec::hardfork::TempoHardfork; #[serde(into = "String")] pub enum FoundryHardfork { Ethereum(EthereumHardfork), + #[cfg(feature = "optimism")] Optimism(OpHardfork), Tempo(TempoHardfork), } @@ -28,6 +31,7 @@ impl From for String { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(h) => format!("optimism:{h}"), FoundryHardfork::Tempo(h) => format!("tempo:{h}"), } @@ -64,6 +68,7 @@ impl FromStr for FoundryHardfork { .map(Self::Ethereum) .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")), + #[cfg(feature = "optimism")] "op" | "optimism" => OpHardfork::from_str(&fork) .map(Self::Optimism) .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")), @@ -83,6 +88,7 @@ impl FoundryHardfork { Self::Ethereum(h) } + #[cfg(feature = "optimism")] pub const fn optimism(h: OpHardfork) -> Self { Self::Optimism(h) } @@ -95,6 +101,7 @@ impl FoundryHardfork { pub fn name(&self) -> String { match self { Self::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] Self::Optimism(h) => format!("{h}"), Self::Tempo(h) => format!("{h}"), } @@ -106,6 +113,7 @@ impl FoundryHardfork { pub const fn namespace(&self) -> Option<&'static str> { match self { Self::Ethereum(_) => None, + #[cfg(feature = "optimism")] Self::Optimism(_) => Some("optimism"), Self::Tempo(_) => Some("tempo"), } @@ -119,6 +127,7 @@ impl FoundryHardfork { if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Ethereum(fork)); } + #[cfg(feature = "optimism")] if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Optimism(fork)); } @@ -143,12 +152,14 @@ impl From for EthereumHardfork { } } +#[cfg(feature = "optimism")] impl From for FoundryHardfork { fn from(value: OpHardfork) -> Self { Self::Optimism(value) } } +#[cfg(feature = "optimism")] impl From for OpHardfork { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -177,12 +188,14 @@ impl From for SpecId { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), FoundryHardfork::Tempo(hardfork) => hardfork.into(), } } } +#[cfg(feature = "optimism")] impl From for OpSpecId { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -223,6 +236,7 @@ pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { } /// Map an `OptimismHardfork` enum into its corresponding `OpSpecId`. +#[cfg(feature = "optimism")] pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { match hardfork { OpHardfork::Bedrock => OpSpecId::BEDROCK, @@ -265,6 +279,7 @@ impl FromEvmVersion for SpecId { } } +#[cfg(feature = "optimism")] impl FromEvmVersion for OpSpecId { fn from_evm_version(version: EvmVersion) -> Self { match version { @@ -324,16 +339,6 @@ mod tests { assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA); } - #[test] - fn test_optimism_spec_id_mapping() { - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - - // Test latest hardforks - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); - } - #[test] fn test_tempo_spec_id_mapping() { assert_eq!(SpecId::from(TempoHardfork::Genesis), SpecId::OSAKA); @@ -371,25 +376,40 @@ mod tests { } #[test] - fn test_from_chain_and_timestamp_op_mainnet() { - let op_chain_id = 10; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); + fn test_from_chain_and_timestamp_unknown_chain() { + assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); } - #[test] - fn test_from_chain_and_timestamp_base() { - let base_chain_id = 8453; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); - } + #[cfg(feature = "optimism")] + mod optimism { + use super::*; - #[test] - fn test_from_chain_and_timestamp_unknown_chain() { - assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); + #[test] + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); + + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } + + #[test] + fn test_from_chain_and_timestamp_op_mainnet() { + let op_chain_id = 10; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_base() { + let base_chain_id = 8453; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } } } diff --git a/crates/evm/networks/Cargo.toml b/crates/evm/networks/Cargo.toml index a63ed34ba61cf..00c9abf0f90f7 100644 --- a/crates/evm/networks/Cargo.toml +++ b/crates/evm/networks/Cargo.toml @@ -19,7 +19,7 @@ foundry-evm-hardforks.workspace = true alloy-chains.workspace = true alloy-eips.workspace = true alloy-evm.workspace = true -alloy-op-hardforks.workspace = true +alloy-op-hardforks = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = [ "serde", "getrandom", @@ -43,4 +43,8 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } serde.workspace = true [dev-dependencies] -serde_json.workspace = true \ No newline at end of file +serde_json.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "foundry-evm-hardforks/optimism"] diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs index 303b9ca8b7a13..384cee5a7bed3 100644 --- a/crates/evm/networks/src/lib.rs +++ b/crates/evm/networks/src/lib.rs @@ -11,7 +11,6 @@ use alloy_chains::{ }; use alloy_eips::eip1559::BaseFeeParams; use alloy_evm::precompiles::PrecompilesMap; -use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; use alloy_primitives::{Address, ChainId, map::AddressHashMap}; use clap::Parser; use foundry_evm_hardforks::FoundryHardfork; @@ -20,20 +19,52 @@ use std::collections::BTreeMap; pub mod celo; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)] +#[cfg(feature = "optimism")] +mod optimism; + +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + clap::ValueEnum, +)] #[serde(rename_all = "lowercase")] #[clap(rename_all = "lowercase")] pub enum NetworkVariant { #[default] Ethereum, + #[cfg(feature = "optimism")] Optimism, Tempo, } +impl std::str::FromStr for NetworkVariant { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ethereum" => Ok(Self::Ethereum), + #[cfg(feature = "optimism")] + "optimism" => Ok(Self::Optimism), + "tempo" => Ok(Self::Tempo), + _ => Err(format!("unknown network variant: {s}")), + } + } +} + impl NetworkVariant { pub const fn name(&self) -> &'static str { match self { Self::Ethereum => "ethereum", + #[cfg(feature = "optimism")] Self::Optimism => "optimism", Self::Tempo => "tempo", } @@ -50,32 +81,37 @@ impl From for NetworkVariant { fn from(chain_id: ChainId) -> Self { let chain = Chain::from_id(chain_id); if chain.is_tempo() { - Self::Tempo - } else if chain.is_optimism() { - Self::Optimism - } else { - Self::Ethereum + return Self::Tempo; + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::Optimism; } + Self::Ethereum } } #[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq)] pub struct NetworkConfigs { /// Enable a specific network family. - #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "optimism", "tempo"])] + #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] #[serde(default)] - network: Option, + pub(crate) network: Option, /// Enable Celo network features. - #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "optimism", "tempo"])] + #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] celo: bool, /// Enable Optimism network features (deprecated: use --network optimism). + #[cfg(feature = "optimism")] #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])] // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the // canonical form is `network = "optimism"`. #[serde(default)] - optimism: bool, + pub(crate) optimism: bool, /// Enable Tempo network features (deprecated: use --network tempo). - #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "optimism"])] + #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the // canonical form is `network = "tempo"`. #[serde(default)] @@ -102,10 +138,6 @@ impl Serialize for NetworkConfigs { } impl NetworkConfigs { - pub fn with_optimism() -> Self { - Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } - } - pub fn with_celo() -> Self { Self { celo: true, ..Default::default() } } @@ -114,11 +146,7 @@ impl NetworkConfigs { Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() } } - pub fn is_optimism(&self) -> bool { - matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) - } - - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { matches!(self.resolved_network(), Some(NetworkVariant::Tempo)) } @@ -127,14 +155,18 @@ impl NetworkConfigs { } /// Returns the resolved network variant, folding legacy flags. - fn resolved_network(&self) -> Option { - self.network.or(if self.optimism { - Some(NetworkVariant::Optimism) - } else if self.tempo { - Some(NetworkVariant::Tempo) - } else { - None - }) + const fn resolved_network(&self) -> Option { + if let Some(n) = self.network { + return Some(n); + } + #[cfg(feature = "optimism")] + if self.optimism { + return Some(NetworkVariant::Optimism); + } + if self.tempo { + return Some(NetworkVariant::Tempo); + } + None } /// Returns the name of the currently active non-Ethereum network, or `None` for plain Ethereum. @@ -150,16 +182,12 @@ impl NetworkConfigs { /// For Optimism networks, returns Canyon parameters if the Canyon hardfork is active /// at the given timestamp, otherwise returns pre-Canyon parameters. pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_hardforks = OpChainHardforks::op_mainnet(); - if op_hardforks.is_canyon_active_at_timestamp(timestamp) { - BaseFeeParams::optimism_canyon() - } else { - BaseFeeParams::optimism() - } - } else { - BaseFeeParams::ethereum() + return self.op_base_fee_params(timestamp); } + let _ = timestamp; + BaseFeeParams::ethereum() } pub fn bypass_prevrandao(&self, chain_id: u64) -> bool { @@ -174,21 +202,23 @@ impl NetworkConfigs { pub fn with_chain_id(self, chain_id: u64) -> Self { let chain = Chain::from_id(chain_id); - if self.resolved_network().is_none() { - if chain.is_tempo() { - Self::with_tempo() - } else if chain.is_optimism() { - Self::with_optimism() + if self.resolved_network().is_some() { + return if !self.celo + && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + { + Self::with_celo() } else { self - } - } else if !self.celo - && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) - { - Self::with_celo() - } else { - self + }; + } + if chain.is_tempo() { + return Self::with_tempo(); + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::with_optimism(); } + self } /// Validates `hardfork` against the current `NetworkConfigs` and, if consistent, returns an @@ -208,6 +238,7 @@ impl NetworkConfigs { let network = match hardfork { FoundryHardfork::Ethereum(_) => self, FoundryHardfork::Tempo(_) => Self::with_tempo(), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(_) => Self::with_optimism(), }; @@ -243,6 +274,21 @@ impl NetworkConfigs { } } +impl From for NetworkConfigs { + fn from(network: NetworkVariant) -> Self { + match network { + NetworkVariant::Ethereum => Self::default(), + NetworkVariant::Tempo => { + Self { network: Some(network), tempo: true, ..Default::default() } + } + #[cfg(feature = "optimism")] + NetworkVariant::Optimism => { + Self { network: Some(network), optimism: true, ..Default::default() } + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -254,17 +300,6 @@ mod tests { let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() }; let via_old = NetworkConfigs { tempo: true, ..Default::default() }; assert_eq!(via_new.is_tempo(), via_old.is_tempo()); - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.active_network_name(), via_old.active_network_name()); - } - - #[test] - fn new_optimism_flag_equivalent_to_legacy() { - let via_new = - NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; - let via_old = NetworkConfigs { optimism: true, ..Default::default() }; - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.is_tempo(), via_old.is_tempo()); assert_eq!(via_new.active_network_name(), via_old.active_network_name()); } @@ -276,31 +311,11 @@ mod tests { assert_eq!(cfg.active_network_name(), Some("tempo")); } - #[test] - fn active_network_name_optimism() { - let cfg = NetworkConfigs::with_optimism(); - assert_eq!(cfg.active_network_name(), Some("optimism")); - } - #[test] fn active_network_name_default_is_none() { assert_eq!(NetworkConfigs::default().active_network_name(), None); } - // --- new flag takes precedence over legacy flag --- - - #[test] - fn new_flag_wins_over_legacy_when_both_set() { - // --network optimism --tempo: network field wins - let cfg = NetworkConfigs { - network: Some(NetworkVariant::Optimism), - tempo: true, - ..Default::default() - }; - assert!(cfg.is_optimism()); - assert!(!cfg.is_tempo()); - } - // --- Serde round-trip --- #[test] @@ -309,16 +324,6 @@ mod tests { let json = serde_json::to_string(&original).unwrap(); let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); assert!(restored.is_tempo()); - assert!(!restored.is_optimism()); - } - - #[test] - fn serde_roundtrip_optimism() { - let original = NetworkConfigs::with_optimism(); - let json = serde_json::to_string(&original).unwrap(); - let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); - assert!(restored.is_optimism()); - assert!(!restored.is_tempo()); } #[test] @@ -345,8 +350,55 @@ mod tests { let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#; let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap(); assert!(cfg_tempo.is_tempo()); - let json_optimism = r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; - let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); - assert!(cfg_optimism.is_optimism()); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + + #[test] + fn new_optimism_flag_equivalent_to_legacy() { + let via_new = + NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; + let via_old = NetworkConfigs { optimism: true, ..Default::default() }; + assert_eq!(via_new.is_optimism(), via_old.is_optimism()); + assert_eq!(via_new.is_tempo(), via_old.is_tempo()); + assert_eq!(via_new.active_network_name(), via_old.active_network_name()); + } + + #[test] + fn active_network_name_optimism() { + let cfg = NetworkConfigs::with_optimism(); + assert_eq!(cfg.active_network_name(), Some("optimism")); + } + + #[test] + fn new_flag_wins_over_legacy_when_both_set() { + // --network optimism --tempo: network field wins + let cfg = NetworkConfigs { + network: Some(NetworkVariant::Optimism), + tempo: true, + ..Default::default() + }; + assert!(cfg.is_optimism()); + assert!(!cfg.is_tempo()); + } + + #[test] + fn serde_roundtrip_optimism() { + let original = NetworkConfigs::with_optimism(); + let json = serde_json::to_string(&original).unwrap(); + let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); + assert!(restored.is_optimism()); + assert!(!restored.is_tempo()); + } + + #[test] + fn serde_optimism_field_deserialized() { + let json_optimism = + r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; + let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); + assert!(cfg_optimism.is_optimism()); + } } } diff --git a/crates/evm/networks/src/optimism.rs b/crates/evm/networks/src/optimism.rs new file mode 100644 index 0000000000000..5fffa38a333c7 --- /dev/null +++ b/crates/evm/networks/src/optimism.rs @@ -0,0 +1,25 @@ +//! Optimism-specific extensions for [`NetworkConfigs`] and related helpers. + +use crate::{NetworkConfigs, NetworkVariant}; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; + +impl NetworkConfigs { + pub fn with_optimism() -> Self { + Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } + } + + pub const fn is_optimism(&self) -> bool { + matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) + } + + /// Optimism-specific base fee parameters, picking Canyon vs pre-Canyon based on `timestamp`. + pub(crate) fn op_base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + let op_hardforks = OpChainHardforks::op_mainnet(); + if op_hardforks.is_canyon_active_at_timestamp(timestamp) { + BaseFeeParams::optimism_canyon() + } else { + BaseFeeParams::optimism() + } + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 90d2db724cebc..73d64d3ab5d07 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -50,3 +50,7 @@ tempfile.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true yansi.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index bad5c577bc69e..b6f11772620f9 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -26,3 +26,7 @@ foundry-test-utils.workspace = true toml.workspace = true snapbox.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 89a9bf152c8c2..4b986017b71dd 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -711,7 +711,7 @@ impl<'sess> State<'sess, '_> { // Merge the lines and let the wrapper handle breaking if needed let merged_line = format!( "{current_line} {next_content}", - next_content = &next_line[prefix.len()..].trim_start() + next_content = next_line[prefix.len()..].trim_start() ); result.push(merged_line); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 064834d248d5f..667da6b442ca1 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -117,7 +117,7 @@ tempfile.workspace = true alloy-signer-local.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -126,3 +126,15 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cli/optimism", + "forge-script/optimism", + "forge-verify/optimism", + "forge-doc/optimism", + "forge-fmt/optimism", + "forge-lint/optimism", + "forge-sol-macro-gen/optimism", +] diff --git a/crates/forge/assets/tempo/MailTemplate.s.sol b/crates/forge/assets/tempo/MailTemplate.s.sol index 27512efe4d5ec..45006f7cd0e06 100644 --- a/crates/forge/assets/tempo/MailTemplate.s.sol +++ b/crates/forge/assets/tempo/MailTemplate.s.sol @@ -14,7 +14,7 @@ contract MailScript is Script { function run(string memory salt) public { vm.startBroadcast(); - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); ITIP20 token = ITIP20( diff --git a/crates/forge/assets/tempo/MailTemplate.t.sol b/crates/forge/assets/tempo/MailTemplate.t.sol index b1749db5df0bf..19760303860a1 100644 --- a/crates/forge/assets/tempo/MailTemplate.t.sol +++ b/crates/forge/assets/tempo/MailTemplate.t.sol @@ -17,7 +17,7 @@ contract MailTest is Test { address public constant BOB = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); function setUp() public virtual { - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); token = ITIP20( diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index ea034bce87185..b8ce2a9b945b1 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -87,8 +87,11 @@ impl CoverageArgs { config = self.load_config()?; } - // Set fuzz seed so coverage reports are deterministic - config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so coverage reports are deterministic, + // but allow the user to override it via `--fuzz-seed` or `[fuzz] seed` in config. + if config.fuzz.seed.is_none() { + config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let (paths, mut output) = { let (project, output) = self.build(&config)?; diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 765bb64f95fdd..4f638b6edcdf8 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -13,7 +13,10 @@ use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, - utils::{LoadConfig, find_contract_artifacts, read_constructor_args_file}, + utils::{ + LoadConfig, ResolvedLane, find_contract_artifacts, maybe_print_resolved_lane, + read_constructor_args_file, resolve_lane, + }, }; use foundry_common::{ FoundryTransactionBuilder, @@ -203,6 +206,11 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `self.tx.tempo.nonce_key` from the lane. + // Must happen before `self.deploy(...)` so `TempoOpts::apply` picks up the nonce_key. + let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?; + // Whether to broadcast the transaction or not let dry_run = !self.broadcast; @@ -223,6 +231,7 @@ impl CreateArgs { dry_run, None, Some(browser), + resolved_lane, ) .await } else if self.unlocked { @@ -239,6 +248,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } else if let Some(ak) = access_key { @@ -259,6 +269,7 @@ impl CreateArgs { dry_run, Some((signer, ak)), None, + resolved_lane, ) .await } else { @@ -282,6 +293,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } @@ -362,6 +374,7 @@ impl CreateArgs { dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, browser_signer: Option>, + resolved_lane: Option, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, @@ -398,7 +411,7 @@ impl CreateArgs { // If Tempo chain fee token must be set if chain.is_tempo() { - if let Some(fee_token) = self.tx.tempo.fee_token { + if let Some(fee_token) = self.tx.tempo.common.fee_token { deployer.tx.set_fee_token(fee_token); } else { deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); @@ -408,15 +421,18 @@ impl CreateArgs { // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); - // For keychain mode, set key_id and nonce_key before gas estimation. // Convert the CREATE into an AA-compatible call entry since Tempo AA // transactions use a `calls` list instead of `to`+`input`. + if chain.is_tempo() { + deployer.tx.convert_create_to_call(); + } + + // For keychain mode, set key_id and nonce_key before gas estimation. if let Some((_, ref ak)) = tempo_keychain { deployer.tx.set_key_id(ak.key_address); if deployer.tx.nonce_key().is_none() { deployer.tx.set_nonce_key(U256::ZERO); } - deployer.tx.convert_create_to_call(); } // Fetch defaults from provider for values not specified by user. @@ -424,6 +440,20 @@ impl CreateArgs { deployer.tx.set_nonce(provider.get_transaction_count(deployer_address).await?); } + maybe_print_resolved_lane(resolved_lane.as_ref(), deployer.tx.nonce().unwrap_or_default())?; + + if let Some((_, ref ak)) = tempo_keychain { + deployer + .tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } + // set access list if specified if let Some(access_list) = match self.tx.access_list { None => None, @@ -500,6 +530,11 @@ impl CreateArgs { return Ok(()); } + let tempo_sponsor = self.tx.tempo.sponsor_config().await?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut deployer.tx, deployer_address).await?; + } + // Deploy the actual contract let (deployed_contract, receipt) = if let Some(browser) = browser_signer { // Browser wallet signs and sends the transaction diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index c8dc2ba72aae1..7c6fb51ce3266 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -99,8 +99,11 @@ impl GasSnapshotArgs { } pub async fn run(mut self) -> Result<()> { - // Set fuzz seed so gas snapshots are deterministic - self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so gas snapshots are deterministic, + // but allow the user to override it via `--fuzz-seed`. + if self.test.fuzz_seed.is_none() { + self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let outcome = self.test.compile_and_run().await?; outcome.ensure_ok(false)?; diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index 94376dc5238cd..dd8f3afd56197 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -3,7 +3,7 @@ use crate::{ MultiContractRunner, MultiContractRunnerBuilder, decode::decode_console_logs, gas_report::GasReport, - multi_runner::matches_artifact, + multi_runner::{MultiNetworkConfig, matches_artifact}, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ CallTraceDecoderBuilder, InternalTraceMode, TraceKind, @@ -31,7 +31,7 @@ use foundry_compilers::{ utils::source_files_iter, }; use foundry_config::{ - Config, figment, + Config, InlineConfig, figment, figment::{ Metadata, Profile, Provider, value::{Dict, Map}, @@ -39,10 +39,11 @@ use foundry_config::{ filter::GlobMatcher, }; use foundry_debugger::Debugger; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::evm::{ - BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, SpecFor, TempoEvmNetwork, - TxEnvFor, + BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor, }, opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, @@ -169,6 +170,14 @@ pub struct TestArgs { #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, + /// Run only the fuzz case at the given 1-based run index. + #[arg(long, env = "FOUNDRY_FUZZ_RUN", value_name = "RUN")] + pub fuzz_run: Option, + + /// Run the fuzz case from the given worker. Requires `--fuzz-run`. + #[arg(long, env = "FOUNDRY_FUZZ_WORKER", value_name = "WORKER", requires = "fuzz_run")] + pub fuzz_worker: Option, + /// Timeout for each fuzz run in seconds. #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] pub fuzz_timeout: Option, @@ -301,6 +310,10 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, coverage: bool, ) -> Result { + if config.fuzz.run == Some(0) { + bail!("`fuzz.run` must be greater than 0"); + } + // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { evm_opts.isolate = true; @@ -342,40 +355,80 @@ impl TestArgs { // Auto-detect network from fork chain ID when not explicitly configured. evm_opts.infer_network_from_fork().await; - // Dispatch based on network type. - let (libraries, mut outcome) = if evm_opts.networks.is_tempo() { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - } else if evm_opts.networks.is_optimism() { - self.build_and_run_tests::( + // Parse inline config early to detect per-test network annotations. + let inline_config = InlineConfig::new_parsed(output, &config)?; + let override_networks = inline_config.referenced_override_networks(&config.profile); + + let (libraries, mut outcome) = if override_networks.is_empty() { + // Single-pass: no per-test network overrides, use global network setting. + self.dispatch_network( + &evm_opts, config, - evm_opts, + evm_opts.clone(), output, filter, coverage, should_debug, decode_internal, + MultiNetworkConfig::default(), ) .await? } else { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? + // Multi-pass: run each distinct network separately and merge results. + let all_override_networks = override_networks.clone(); + let multi_pass_timer = Instant::now(); + + // Default pass: global network, runs tests without an explicit network annotation. + let (libraries, mut outcome) = self + .dispatch_network( + &evm_opts, + config.clone(), + evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: None, + }, + ) + .await?; + + // Override passes: one per annotated network. + for &network in &override_networks { + let mut pass_evm_opts = evm_opts.clone(); + pass_evm_opts.networks = network.into(); + let (_, pass_outcome) = self + .dispatch_network( + &pass_evm_opts, + config.clone(), + pass_evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: Some(network), + }, + ) + .await?; + merge_outcomes(&mut outcome, pass_outcome); + } + + // Print the merged summary (per-pass summaries are suppressed in `run_tests_inner`). + if !self.summary && !shell::is_json() { + sh_println!("{}", outcome.summary(multi_pass_timer.elapsed()))?; + } + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; + } + + (libraries, outcome) }; if should_draw { @@ -461,6 +514,7 @@ impl TestArgs { coverage: bool, should_debug: bool, decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, ) -> eyre::Result<(Libraries, TestOutcome)> { let verbosity = evm_opts.verbosity; let (evm_env, tx_env, fork_block) = @@ -476,6 +530,7 @@ impl TestArgs { .enable_isolation(evm_opts.isolate) .fail_fast(self.fail_fast) .set_coverage(coverage) + .with_multi_network(multi_network) .build::(output, evm_env, tx_env, evm_opts)?; let libraries = runner.libraries.clone(); @@ -483,6 +538,62 @@ impl TestArgs { Ok((libraries, outcome)) } + /// Dispatches `build_and_run_tests` to the correct network type based on `evm_opts.networks`. + #[allow(clippy::too_many_arguments)] + async fn dispatch_network( + &self, + dispatch_opts: &EvmOpts, + config: Config, + evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + should_debug: bool, + decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, + ) -> eyre::Result<(Libraries, TestOutcome)> { + if dispatch_opts.networks.is_tempo() { + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } else { + #[cfg(feature = "optimism")] + if dispatch_opts.networks.is_optimism() { + return self + .build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await; + } + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } + } + /// Run all tests that matches the filter predicate from a test runner async fn run_tests_inner( &self, @@ -586,6 +697,11 @@ impl TestArgs { let libraries = runner.libraries.clone(); + // Capture multi-pass state before moving `runner` into the spawn task. + // In multi-pass mode the per-pass summary is suppressed; the merged summary is + // printed once by the caller after all passes complete. + let is_multi_pass = !runner.tcfg.multi_network.all_override_networks.is_empty(); + // Run tests in a streaming fashion. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); @@ -643,6 +759,13 @@ impl TestArgs { let tests = &mut suite_result.test_results; let has_tests = !tests.is_empty(); + // In multi-pass (per-test network override) mode, skip suites that contributed no + // tests to this pass so we don't emit a stray blank line in the suite header or + // pollute the outcome with empty entries. + if is_multi_pass && !has_tests && suite_result.warnings.is_empty() { + continue; + } + // Clear the addresses and labels from previous test. decoder.clear_addresses(); @@ -903,17 +1026,17 @@ impl TestArgs { if let Some(gas_report) = gas_report { let finalized = gas_report.finalize(); - sh_println!("{}", &finalized)?; + sh_println!("{finalized}")?; outcome.gas_report = Some(finalized); } - if !self.summary && !shell::is_json() { + if !is_multi_pass && !self.summary && !shell::is_json() { sh_println!("{}", outcome.summary(duration))?; } - if self.summary && !outcome.results.is_empty() { + if !is_multi_pass && self.summary && !outcome.results.is_empty() { let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); - sh_println!("{}", &summary_report)?; + sh_println!("{summary_report}")?; } // Reattach the task. @@ -980,6 +1103,12 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_run) = self.fuzz_run { + fuzz_dict.insert("run".to_string(), fuzz_run.into()); + } + if let Some(fuzz_worker) = self.fuzz_worker { + fuzz_dict.insert("worker".to_string(), fuzz_worker.into()); + } if let Some(fuzz_timeout) = self.fuzz_timeout { fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); } @@ -1023,6 +1152,29 @@ fn list( Ok(TestOutcome::empty(Some(runner.known_contracts), false)) } +/// Merges `other` into `base` by extending suite results. +/// +/// For suites that appear in both, test results are combined (function-level pass routing ensures +/// each function appears in exactly one pass, so there are no key conflicts in practice). +fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { + for (suite_id, other_suite) in other.results { + match base.results.entry(suite_id) { + std::collections::btree_map::Entry::Vacant(e) => { + e.insert(other_suite); + } + std::collections::btree_map::Entry::Occupied(mut e) => { + let base_suite = e.get_mut(); + base_suite.test_results.extend(other_suite.test_results); + base_suite.warnings.extend(other_suite.warnings); + base_suite.duration = base_suite.duration.max(other_suite.duration); + } + } + } + if let Some(decoder) = other.last_run_decoder { + base.last_run_decoder = Some(decoder); + } +} + /// Load persisted filter (with last test run failures) from file. fn last_run_failures(config: &Config) -> Option { match fs::read_to_string(&config.test_failures_file) { @@ -1131,6 +1283,14 @@ mod tests { assert!(args.fuzz_seed.is_some()); } + #[test] + fn fuzz_run() { + let args: TestArgs = + TestArgs::parse_from(["foundry-cli", "--fuzz-run", "10", "--fuzz-worker", "2"]); + assert_eq!(args.fuzz_run, Some(10)); + assert_eq!(args.fuzz_worker, Some(2)); + } + #[test] fn extract_chain() { let test = |arg: &str, expected: Chain| { diff --git a/crates/forge/src/cmd/test/summary.rs b/crates/forge/src/cmd/test/summary.rs index f8a72272af53c..a0123e896d0bf 100644 --- a/crates/forge/src/cmd/test/summary.rs +++ b/crates/forge/src/cmd/test/summary.rs @@ -25,9 +25,9 @@ impl TestSummaryReport { impl Display for TestSummaryReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "{}", self.format_json_output(&self.is_detailed, &self.outcome))?; } else { - writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "\n{}", self.format_table_output(&self.is_detailed, &self.outcome))?; } Ok(()) } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 6c93dc03b28b5..58b11d98874ed 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -146,7 +146,7 @@ impl GasReport { impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output())?; + writeln!(f, "{}", self.format_json_output())?; } else { for (name, contract) in &self.contracts { if contract.functions.is_empty() { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 88bbc6156c812..675f0c3e6c99c 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -27,6 +27,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; +use foundry_evm_networks::NetworkVariant; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; @@ -280,6 +281,25 @@ impl MultiContractRunner { } } +/// Tracks network assignment across a multi-network test run. +/// +/// When inline config specifies different networks for different tests, the runner performs one +/// pass per distinct network. This struct encodes which pass we're in so each `ContractRunner` +/// can skip tests that belong to a different pass. +/// +/// Default (empty `all_override_networks`, `None` pass) = single-pass mode, every test runs. +#[derive(Clone, Debug, Default)] +pub struct MultiNetworkConfig { + /// All networks explicitly referenced in inline config annotations across the whole suite. + /// Empty means single-pass mode (no per-test network overrides present). + pub all_override_networks: Vec, + /// The network this pass is responsible for. + /// `None` = default pass: runs tests *without* an explicit network annotation (or annotated + /// with a network not in `all_override_networks`). + /// `Some(v)` = override pass: runs only tests annotated with exactly `v`. + pub pass_network: Option, +} + /// Configuration for the test runner. /// /// This is modified after instantiation through inline config. @@ -311,6 +331,9 @@ pub struct TestRunnerConfig { pub isolation: bool, /// Whether to exit early on test failure or if test run interrupted. pub early_exit: EarlyExit, + + /// Multi-network pass configuration. Default = single-pass mode. + pub multi_network: MultiNetworkConfig, } impl TestRunnerConfig { @@ -423,6 +446,8 @@ pub struct MultiContractRunnerBuilder { pub isolation: bool, /// Whether to exit early on test failure. pub fail_fast: bool, + /// Multi-network pass configuration. + pub multi_network: MultiNetworkConfig, } impl MultiContractRunnerBuilder { @@ -437,6 +462,7 @@ impl MultiContractRunnerBuilder { isolation: Default::default(), decode_internal: Default::default(), fail_fast: false, + multi_network: Default::default(), } } @@ -470,6 +496,11 @@ impl MultiContractRunnerBuilder { self } + pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self { + self.multi_network = multi_network; + self + } + pub const fn fail_fast(mut self, fail_fast: bool) -> Self { self.fail_fast = fail_fast; self @@ -594,6 +625,7 @@ impl MultiContractRunnerBuilder { inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, early_exit: EarlyExit::new(self.fail_fast), + multi_network: self.multi_network, config: self.config, }, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index d924c416759a2..7feaf35254636 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -109,6 +109,25 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { } } + /// Returns `true` if `func` should run in the current multi-network pass. + /// + /// In single-pass mode (no inline network overrides) every function passes. + /// In multi-pass mode: + /// - Default pass (`pass_network = None`): includes functions *without* an override annotation. + /// - Override pass (`pass_network = Some(v)`): includes only functions annotated with `v`. + fn function_matches_network_pass(&self, func: &Function) -> bool { + let multi = &self.mcr.tcfg.multi_network; + if multi.all_override_networks.is_empty() { + return true; + } + let profile = &self.tcfg.config.profile; + let func_network = self.mcr.inline_config.network_for(profile, self.name, &func.name); + match &multi.pass_network { + None => func_network.is_none_or(|n| !multi.all_override_networks.contains(&n)), + Some(target) => func_network.as_ref() == Some(target), + } + } + /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { @@ -380,6 +399,7 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { .abi .functions() .filter(|func| filter.matches_test_function(func)) + .filter(|func| self.function_matches_network_pass(func)) .collect::>(); debug!( "Found {} test functions out of {} in {:?}", @@ -826,7 +846,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { ); if let Some(ref progress) = progress { - progress.set_prefix(format!("{}\n{warn}\n", &func.name)); + progress.set_prefix(format!("{}\n{warn}\n", func.name)); } else { let _ = sh_warn!("{warn}"); } @@ -1052,7 +1072,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { self.cr.name, &func.name, fuzz_config.timeout, - fuzz_config.runs, + if fuzz_config.run.is_some() { 1 } else { fuzz_config.runs }, ); let state = self.build_fuzz_state(false); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 6e0acebc67225..5d8378c50c9ac 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -3000,7 +3000,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132459 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| @@ -3023,7 +3023,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", "deployment": { - "gas": 132459, + "gas": 132471, "size": 396 }, "functions": { diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 0eeb3757982e9..3eebca475a781 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -577,6 +577,32 @@ forgetest_init!(can_get_evm_opts, |prj, _cmd| { } }); +// Regression test for : +// the bare `ETH_RPC_URL` env var must NOT cause `forge` commands to set +// `eth_rpc_url` (which would silently fork all `forge test` runs). +// Only `--rpc-url`, `foundry.toml`, the `FOUNDRY_ETH_RPC_URL` env var, or +// cheatcodes should configure forking. +forgetest_init!(eth_rpc_url_env_does_not_set_fork_url, |prj, _cmd| { + prj.initialize_default_contracts(); + let url = "http://127.0.0.1:8545"; + + let mut cmd = prj.forge_bin(); + cmd.arg("config") + .arg("--root") + .arg(prj.root()) + .arg("--json") + .env("ETH_RPC_URL", url) + // Make sure the figment-style env var is not set in the test environment. + .env_remove("FOUNDRY_ETH_RPC_URL"); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let config: Config = serde_json::from_str(stdout.as_ref()).unwrap(); + assert_eq!( + config.eth_rpc_url, None, + "bare ETH_RPC_URL must not propagate to forge config (regression #14538)" + ); +}); + // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { prj.initialize_default_contracts(); @@ -1269,6 +1295,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "show_progress": false, "fuzz": { "runs": 256, + "run": null, + "worker": null, "fail_on_revert": true, "max_test_rejects": 65536, "seed": null, diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index 48a17c723b261..77d5a5e84cfbb 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -70,8 +70,13 @@ Suite result: FAILED. 0 passed; 7 failed; 0 skipped; [ELAPSED] .stdout_eq( r#"No files changed, compilation skipped ... +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != [..]] testShouldFailExpectRevertNestedCreateInnerAddress() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterNestedCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterTopLevelCreate() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailExpectRevertsNotOnImmediateNextCall() ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#, ); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index 04fb2369d83b0..ba01767d58b26 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -425,3 +425,107 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// Checks that tests annotated with `forge-config: default.networks.network` run on the correct +// EVM network, and that unannotated tests run on the globally configured network. +// +// Each test makes a real call to the Tempo `TipFeeManager` precompile at +// `0xfeec000000000000000000000000000000000000` (a Tempo-only contract that exists on the +// Moderato testnet and is auto-injected by the in-memory Tempo EVM): +// +// * The default-network test asserts the precompile has no code (it does not exist on Ethereum). +// * The Tempo-network test asserts the precompile has code and `userTokens(address)` returns the +// unset zero-address sentinel, proving the Tempo network was actually selected for that test and +// the Tempo genesis state was loaded. +forgetest!(per_test_network_routing, |prj, cmd| { + prj.add_test( + "inline.sol", + r#" + address constant TIP_FEE_MANAGER = 0xfeEC000000000000000000000000000000000000; + + contract DefaultNetwork { + // No annotation -> runs on the globally selected network (Ethereum by default). + // The Tempo FeeManager precompile must NOT exist here. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + } + + contract TempoNetwork { + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + // Sentinel bytecode (0xef) is injected at every Tempo precompile address. + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + // Call a Tempo-only method: `userTokens(address)` returns the user's preferred + // fee token, or the zero address when none is set. + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + + // Mixed contract: one function annotated with Tempo, one unannotated (runs on Ethereum). + contract MixedNetwork { + // No annotation -> runs on Ethereum; precompile must be absent. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + "#, + ); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 3 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index fd69907be2f09..8420e24eb3df7 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,4 +1,7 @@ -use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; +use forge_lint::{ + linter::Lint, + sol::{self, SolLint}, +}; use foundry_config::{ DenyLevel, LintSeverity, LinterConfig, SolidityErrorCode, lint::LintSpecificConfig, }; @@ -203,7 +206,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -230,7 +233,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -610,7 +613,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -637,7 +640,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -665,7 +668,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 13 │ uint256 result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift "# @@ -694,7 +697,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]).stdout_eq(str![[r#" @@ -855,7 +858,7 @@ note[unused-import]: unused imports should be removed 8 │ import { _PascalCaseInfo } from "./ContractWithLints.sol"; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import "#]]); @@ -887,7 +890,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 6 │ uint256 public CounterB_Fail_Lint; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterBFailLint` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable "#]]); @@ -992,7 +995,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { ], "children": [ { - "message": "https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic", + "message": "https://getfoundry.sh/forge/linting/unwrapped-modifier-logic", "code": null, "level": "help", "spans": [], @@ -1048,7 +1051,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { "rendered": null } ], - "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" + "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" } "#]], ); @@ -1129,46 +1132,46 @@ Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. #[tokio::test] async fn ensure_lint_rule_docs() { - const FOUNDRY_BOOK_LINT_PAGE_URL: &str = "https://book.getfoundry.sh/forge/linting"; - - // Fetch the content of the lint reference - let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { - Ok(resp) => { - assert!( - resp.status().is_success(), - "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", - status = resp.status() - ); - match resp.text().await { - Ok(text) => text, - Err(e) => { - panic!("Failed to read response text: {e}"); - } + let client = reqwest::Client::new(); + let mut failures = Vec::new(); + + for lint in registered_lints() { + let url = lint.help(); + let response = match client.get(url).send().await { + Ok(response) => response, + Err(err) => { + failures.push(format!("{} ({url}) could not be fetched: {err}", lint.id())); + continue; } + }; + + if !response.status().is_success() { + failures.push(format!("{} ({url}) returned HTTP {}", lint.id(), response.status())); + continue; } - Err(e) => { - panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); - } - }; - // Ensure no missing lints - let mut missing_lints = Vec::new(); - for lint in REGISTERED_LINTS { + let content = match response.text().await { + Ok(content) => content.to_lowercase(), + Err(err) => { + failures + .push(format!("{} ({url}) response body could not be read: {err}", lint.id())); + continue; + } + }; + let selector = lint.id().to_lowercase(); let selector_with_space = selector.replace('-', " "); - if !content.to_lowercase().contains(&selector) - && !content.to_lowercase().contains(&selector_with_space) - { - missing_lints.push(lint.id()); + if !content.contains(&selector) && !content.contains(&selector_with_space) { + failures.push(format!("{} ({url}) did not mention the lint id", lint.id())); } } - if !missing_lints.is_empty() { + if !failures.is_empty() { let mut msg = String::from( - "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + "Foundry Book lint validation failed. The following lint pages are missing or invalid:\n", ); - for lint in missing_lints { - msg.push_str(&format!(" - {lint}\n")); + for failure in failures { + msg.push_str(&format!(" - {failure}\n")); } msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); panic!("{msg}"); @@ -1177,11 +1180,21 @@ async fn ensure_lint_rule_docs() { #[test] fn ensure_no_privileged_lint_id() { - for lint in REGISTERED_LINTS { + for lint in registered_lints() { assert_ne!(lint.id(), "all", "lint-id 'all' is reserved. Please use a different id"); } } +fn registered_lints() -> impl Iterator { + sol::high::REGISTERED_LINTS + .iter() + .chain(sol::med::REGISTERED_LINTS) + .chain(sol::low::REGISTERED_LINTS) + .chain(sol::info::REGISTERED_LINTS) + .chain(sol::gas::REGISTERED_LINTS) + .chain(sol::codesize::REGISTERED_LINTS) +} + // forgetest!(dependency_warnings_do_not_affect_lint_exit_code, |prj, cmd| { // Library with code that triggers a solc warning (unused local variable) @@ -1265,3 +1278,195 @@ contract OldContract { "# ]]); }); + +const PRAGMA_INCONSISTENT_ALPHA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Alpha {} +"#; + +const PRAGMA_INCONSISTENT_BETA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract Beta {} +"#; + +forgetest!(pragma_inconsistent_cross_file, |prj, cmd| { + prj.add_source("Alpha", PRAGMA_INCONSISTENT_ALPHA); + prj.add_source("Beta", PRAGMA_INCONSISTENT_BETA); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +const PRAGMA_EXACT_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract A {} +"#; + +const PRAGMA_EXACT_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract B {} +"#; + +const PRAGMA_EXACT_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract C {} +"#; + +const PRAGMA_CARET_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract A {} +"#; + +const PRAGMA_CARET_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract B {} +"#; + +const PRAGMA_CARET_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract C {} +"#; + +const NO_PRAGMA_C: &str = r#" +// SPDX-License-Identifier: MIT + +contract C {} +"#; + +// Multiple files all using the exact same pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_exact_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_EXACT_C); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Multiple files all using the exact same caret pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_caret_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + prj.add_source("B", PRAGMA_CARET_B); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// A single file in the project cannot conflict with itself. +forgetest!(pragma_inconsistent_single_file_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Even files that share a requirement still emit when ANY other variant exists. +// Two files with `0.8.20` plus one file with `^0.8.20` => 3 emits total. +forgetest!(pragma_inconsistent_duplicates_among_conflict, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_CARET_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +// Files without a `pragma solidity` directive must not affect the conflict computation. +// Note: `add_raw_source` is used here to bypass the helper that would otherwise inject a default +// `pragma solidity =;` for files that omit one. +forgetest!(pragma_inconsistent_files_without_pragma, |prj, cmd| { + prj.add_raw_source("A", PRAGMA_EXACT_A); + prj.add_raw_source("B", PRAGMA_CARET_B); + // C has no pragma at all; should be ignored by the cross-file check. + prj.add_raw_source("C", NO_PRAGMA_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); diff --git a/crates/forge/tests/cli/lint/geiger.rs b/crates/forge/tests/cli/lint/geiger.rs index faecfb212fb90..202866e83e35f 100644 --- a/crates/forge/tests/cli/lint/geiger.rs +++ b/crates/forge/tests/cli/lint/geiger.rs @@ -21,7 +21,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ bytes memory stuff = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:10:20 @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 10 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:11:20 @@ -100,7 +100,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 11 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 3 linter note(s) ... diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 242a0ebb4267f..031d80f0cf071 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -3241,7 +3241,7 @@ contract CounterScript is Script { error: the following required arguments were not provided: --broadcast -Usage: [..] script --broadcast --verify --rpc-url [ARGS]... +Usage: [..] script --broadcast --verify --rpc-url [ARGS]... For more information, try '--help'. diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index fefefb30d9b15..454b014a6e1bc 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -1,4 +1,5 @@ use alloy_primitives::U256; +use foundry_evm::fuzz::BaseCounterExample; use foundry_test_utils::{TestCommand, forgetest_init, str}; use regex::Regex; @@ -845,6 +846,8 @@ forgetest_init!(test_fuzz_random_uint_varies_across_runs, |prj, cmd| { prj.add_test( "RandomFuzzTest.t.sol", r#" +pragma solidity >=0.8.0; + import {Test} from "forge-std/Test.sol"; contract RandomFuzzTest is Test { @@ -868,3 +871,145 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) ... "#]]); }); + +forgetest_init!(test_fuzz_run_replays_random_uint_failure, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + function testFuzz_randomUint_shouldFail(uint256) public { + uint256 rand = vm.randomUint(0, 4); + assertTrue(rand != 0, "hit value 0"); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: hit value 0; counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + + cmd.args(["test", "--fuzz-seed", "1", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + assert_eq!(persisted_failure.fuzz.seed, Some(U256::from(1))); + assert_eq!(persisted_failure.fuzz.worker, Some(0)); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + cmd.forge_fuse() + .args([ + "test", + "--fuzz-seed", + "1", + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure().stdout_eq(expected_output); +}); + +forgetest_init!(test_fuzz_rerun_replays_random_uint_failure_without_seed, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + error Random(uint256 value); + + function testFuzz_randomUint_shouldFail(uint256) public { + revert Random(vm.randomUint()); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: Random([..]); counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]; + + let assert = cmd + .args(["test", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + let reason = random_failure_reason(&stdout); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + let fuzz_seed = format!("{:#x}", persisted_failure.fuzz.seed.unwrap()); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + let assert = cmd + .forge_fuse() + .args([ + "test", + "--fuzz-seed", + &fuzz_seed, + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd + .forge_fuse() + .args(["test", "--rerun", "-j1"]) + .assert_failure() + .stdout_eq(expected_output); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); +}); + +fn random_failure_reason(stdout: &str) -> String { + Regex::new(r"\[FAIL: (Random\([^)]+\))") + .unwrap() + .captures(stdout) + .unwrap_or_else(|| panic!("{stdout}"))[1] + .to_string() +} diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 32bfe6a98a9fd..3803385b496ab 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -783,6 +783,66 @@ ParserError: Source "Missing.sol" not found: File not found. Searched the follow "#]]); }); +// https://github.com/foundry-rs/foundry/issues/10463 +forgetest_init!(issue_10463, |prj, cmd| { + prj.add_test( + "Issue10463.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue10463Test is Test { + event Foo(); + + error CustomError(uint256 code); + + function revertingBefore(bool shouldRevert) external { + if (shouldRevert) revert(); + emit Foo(); + } + + function revertingWithReason() external pure { + revert("revert reason"); + } + + function revertingWithCustomError() external pure { + revert CustomError(42); + } + + function testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() public { + vm.expectEmit(); + emit Foo(); + + this.revertingBefore(true); + } + + function testExpectEmitPreservesRevertReason() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithReason(); + } + + function testExpectEmitPreservesCustomError() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithCustomError(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue10463.t.sol:Issue10463Test +[FAIL: CustomError(42)] testExpectEmitPreservesCustomError() ([GAS]) +[FAIL: revert reason] testExpectEmitPreservesRevertReason() ([GAS]) +[FAIL: EvmError: Revert] testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() ([GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + // https://github.com/foundry-rs/foundry/issues/12803 // Test gas underflow prevention on Cancun (no EIP-7702 gas floor) forgetest_init!(issue_12803_cancun, |prj, cmd| { diff --git a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol index 7e482c6673155..838183a1b0b5e 100644 --- a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol +++ b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol @@ -233,6 +233,63 @@ contract ExpectRevertWithReverterFailureTest is DSTest { aContract.doNotRevert(); aContract.callAndRevert(); } + + // + // Regression: must fail because 0xdead is not the actual reverter when a + // top-level CREATE constructor reverts directly. + function testShouldFailExpectRevertWrongReverterTopLevelCreate() public { + vm.expectRevert(address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // even when an exact-bytes pattern is also supplied for a top-level CREATE. + function testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() public { + vm.expectRevert(abi.encodePacked("Reverted by DContract"), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // for `expectPartialRevert(bytes4, address)` against a top-level CREATE. + function testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() public { + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail when the innermost reverting frame is a nested + // CREATE and the reverter address argument does not match the would-be + // deployed address of the failed deployment. + function testShouldFailExpectRevertWrongReverterNestedCreate() public { + vm.expectRevert(address(0xdead)); + new NestedDContractCreator(); + } + + // + // Regression: documents the intended semantics for nested CREATEs — the + // matched reverter is the *outer* would-be-deployed address (the contract + // whose deployment failed), NOT the innermost reverting CREATE's address. + // Supplying the inner address must fail. + function testShouldFailExpectRevertNestedCreateInnerAddress() public { + // Outer = NestedDContractCreator at this contract's next nonce. + // Inner = DContract created from inside the outer constructor (deployer + // is the outer, nonce 1). + address outer = + vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + address inner = vm.computeCreateAddress(outer, 1); + vm.expectRevert(inner); + new NestedDContractCreator(); + } +} + +// Used by `testShouldFailExpectRevertWrongReverterNestedCreate`: a contract whose +// constructor directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } } contract ExpectRevertCountFailureTest is DSTest { diff --git a/crates/lint/Cargo.toml b/crates/lint/Cargo.toml index 87864721432d9..589a0a5069e37 100644 --- a/crates/lint/Cargo.toml +++ b/crates/lint/Cargo.toml @@ -24,3 +24,7 @@ eyre.workspace = true heck.workspace = true rayon.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/lint/README.md b/crates/lint/README.md index e7c6471555da3..9d5dad0c0272e 100644 --- a/crates/lint/README.md +++ b/crates/lint/README.md @@ -17,11 +17,14 @@ It helps enforce best practices and improve code quality within Foundry projects - `divide-before-multiply`: Warns against performing division before multiplication in the same expression, which can cause precision loss. - `incorrect-erc20-interface`: Flags ERC20 interfaces and implementations with non-compliant function signatures. - `incorrect-erc721-interface`: Flags ERC721 interfaces and implementations with non-compliant function signatures. + - `tx-origin`: Flags use of `tx.origin` in authorization-like predicates. - `unsafe-typecast`: Typecasts that can truncate values should be checked. - **Low Severity:** - `block-timestamp`: Warns when `block.timestamp` is used in a comparison, as it may be manipulated by validators. + - `missing-zero-check`: Address parameter is used in a state write or value transfer without a zero-address check. - **Informational / Style Guide:** - `boolean-equal`: Boolean comparisons to constants should be simplified. + - `too-many-digits`: Numeric literals with 5+ consecutive zeros are error-prone. - `pascal-case-struct`: Flags for struct names not adhering to `PascalCase`. - `mixed-case-function`: Flags for function names not adhering to `mixedCase`. - `mixed-case-variable`: Flags for mutable variable names not adhering to `mixedCase`. @@ -31,10 +34,15 @@ It helps enforce best practices and improve code quality within Foundry projects - `unaliased-plain-import`: Use named imports `{A, B}` or alias `import ".." as X`. - `named-struct-fields`: Prefer initializing structs with named fields. - `unsafe-cheatcode`: Usage of unsafe cheatcodes that can perform dangerous operations. + - `multi-contract-file`: Prefer having only one contract, interface, or library per file. + - `interface-file-naming`: Interface file names should be prefixed with `I`. + - `interface-naming`: Interface names should be prefixed with `I`. + - `pragma-inconsistent`: Flags projects whose source files declare different Solidity pragma version requirements. - **Gas Optimizations:** - `asm-keccak256`: Recommends using inline assembly for `keccak256` for potential gas savings. - `could-be-immutable`: Recommends declaring constructor-only state variables as `immutable`. - `custom-errors`: Recommends using custom errors instead of strings and plain reverts for potential gas savings. + - `unused-state-variables`: State variables that are never used should be removed. - **Code Size:** - `unwrapped-modifier-logic`: Recommends wrapping modifier logic to reduce contract code size. diff --git a/crates/lint/docs/README.md b/crates/lint/docs/README.md new file mode 100644 index 0000000000000..5eb4110a92e57 --- /dev/null +++ b/crates/lint/docs/README.md @@ -0,0 +1,52 @@ +# Forge lint documentation + +This directory contains one markdown file per registered `forge-lint` rule. Each file is referenced +by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the +[Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. + +## Adding a new lint + +When you add a new lint with `declare_forge_lint!`, you **must** also add a documentation file at +`crates/lint/docs/.md`. The presence of the file is enforced by the +`registered_lints_have_docs` unit test in [`crates/lint/src/sol/mod.rs`](../src/sol/mod.rs). + +Use [`_template.md`](./_template.md) as a starting point. + +## File structure + +Each lint doc file should follow this structure: + +```markdown +# + +**Severity**: `` +**ID**: `` + +A one-paragraph description of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` + +## Configuration + +Document any inline-config or `foundry.toml` options that affect this lint, if any. +``` diff --git a/crates/lint/docs/_template.md b/crates/lint/docs/_template.md new file mode 100644 index 0000000000000..41c735a0ba579 --- /dev/null +++ b/crates/lint/docs/_template.md @@ -0,0 +1,28 @@ +# + +**Severity**: `` +**ID**: `` + +One-paragraph summary of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` diff --git a/crates/lint/docs/asm-keccak256.md b/crates/lint/docs/asm-keccak256.md new file mode 100644 index 0000000000000..4678cfe9f8d12 --- /dev/null +++ b/crates/lint/docs/asm-keccak256.md @@ -0,0 +1,42 @@ +# Inefficient keccak256 call + +**Severity**: `Gas` +**ID**: `asm-keccak256` + +Flags calls to the high-level `keccak256(...)` builtin that can be cheaply rewritten with inline +assembly. + +## What it does + +Reports `keccak256(arg)` calls and (when possible) emits a fix suggestion that uses inline +assembly to compute the hash directly, avoiding the overhead of the high-level call. + +## Why is this bad? + +The high-level `keccak256` call performs additional memory management and ABI encoding compared +to a direct `keccak256(ptr, len)` opcode invocation. In hot paths the difference is visible in +gas reports. + +## Example + +### Bad + +```solidity +bytes32 h = keccak256(abi.encodePacked(a, b)); +``` + +### Good + +```solidity +bytes32 h; +assembly ("memory-safe") { + let m := mload(0x40) + mstore(m, a) + mstore(add(m, 0x20), b) + h := keccak256(m, 0x40) +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/block-timestamp.md b/crates/lint/docs/block-timestamp.md new file mode 100644 index 0000000000000..a51b55ff5d8cc --- /dev/null +++ b/crates/lint/docs/block-timestamp.md @@ -0,0 +1,44 @@ +# Use of block.timestamp in comparisons + +**Severity**: `Low` +**ID**: `block-timestamp` + +Flags use of `block.timestamp` as an operand of a comparison, where its value can be slightly +manipulated by the block proposer. + +## What it does + +Reports any comparison expression (`<`, `<=`, `>`, `>=`, `==`, `!=`) that directly or +transitively reads `block.timestamp`. + +## Why is this bad? + +Block proposers can adjust `block.timestamp` within a small window (a few seconds). This is +usually harmless, but for short-window logic — auctions ending, randomness, time-locked +withdrawals — a few seconds of manipulation can be enough for an attacker to capture value. + +Using `block.timestamp` for general scheduling (hours/days) is fine; what's risky is fine-grained +timing and treating timestamps as a source of randomness. + +## Example + +### Bad + +```solidity +function settle() external { + require(block.timestamp >= auctionEnd, "auction ongoing"); + // ... +} +``` + +### Good + +```solidity +// Prefer block numbers for tight windows, or accept a clearly large grace period. +require(block.number >= endBlock, "auction ongoing"); +``` + +## Notes + +This lint is intentionally conservative: not every flagged comparison is exploitable. Review +each occurrence in context. diff --git a/crates/lint/docs/boolean-cst.md b/crates/lint/docs/boolean-cst.md new file mode 100644 index 0000000000000..f5c65dfec2789 --- /dev/null +++ b/crates/lint/docs/boolean-cst.md @@ -0,0 +1,37 @@ +# Misuse of a boolean constant + +**Severity**: `Med` +**ID**: `boolean-cst` + +Flags expressions where a boolean constant (`true`/`false`) is used as a control-flow condition +or operand of a boolean operator, which usually indicates dead code or a leftover debug toggle. + +## What it does + +Reports `if (true)`, `if (false)`, `while (true)` outside of intentional infinite loops, and +boolean operators (`&&`, `||`) where one side is a literal `true`/`false`. + +## Why is this bad? + +A literal boolean as a condition makes the surrounding branch dead, hides logic errors, or +preserves a forgotten debug shortcut that bypasses real checks. + +## Example + +### Bad + +```solidity +if (true) { // always taken + doSomething(); +} +require(condition && true, "unreachable"); // 'true' is redundant +``` + +### Good + +```solidity +if (condition) { + doSomething(); +} +require(condition, "..."); +``` diff --git a/crates/lint/docs/boolean-equal.md b/crates/lint/docs/boolean-equal.md new file mode 100644 index 0000000000000..9397003b039b4 --- /dev/null +++ b/crates/lint/docs/boolean-equal.md @@ -0,0 +1,34 @@ +# Boolean comparison to a constant + +**Severity**: `Info` +**ID**: `boolean-equal` + +Flags expressions of the form `x == true`, `x == false`, `x != true`, `x != false`, which can be +simplified. + +## What it does + +Reports any equality comparison between a boolean expression and a literal `true` or `false`. + +## Why is this bad? + +Comparing a boolean to a boolean literal is redundant and harms readability. Use the boolean +expression directly (or its negation). + +## Example + +### Bad + +```solidity +if (paused == true) revert(); +if (paused == false) doSomething(); +require(ok != false, "fail"); +``` + +### Good + +```solidity +if (paused) revert(); +if (!paused) doSomething(); +require(ok, "fail"); +``` diff --git a/crates/lint/docs/could-be-immutable.md b/crates/lint/docs/could-be-immutable.md new file mode 100644 index 0000000000000..bda1de6379955 --- /dev/null +++ b/crates/lint/docs/could-be-immutable.md @@ -0,0 +1,42 @@ +# State variable could be immutable + +**Severity**: `Gas` +**ID**: `could-be-immutable` + +Flags state variables that are assigned only in the constructor and never written to afterward — +making them eligible to be declared `immutable`. + +## What it does + +Reports each non-`constant`, non-`immutable` state variable whose only writes occur in the +constructor (or in initialization at declaration time). + +## Why is this bad? + +`immutable` state variables are stored in the deployed bytecode rather than in storage, eliminating +an `SLOAD` per access and saving substantial gas across the contract's lifetime. Declaring such +variables `immutable` also expresses intent and prevents future writes. + +## Example + +### Bad + +```solidity +contract C { + address owner; + constructor() { owner = msg.sender; } +} +``` + +### Good + +```solidity +contract C { + address immutable OWNER; + constructor() { OWNER = msg.sender; } +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/custom-errors.md b/crates/lint/docs/custom-errors.md new file mode 100644 index 0000000000000..9e01e01d593e9 --- /dev/null +++ b/crates/lint/docs/custom-errors.md @@ -0,0 +1,45 @@ +# Prefer custom errors over revert strings + +**Severity**: `Gas` +**ID**: `custom-errors` + +Flags `require(cond, "message")`, `revert("message")`, and `revert()` calls; suggests replacing +them with a `revert CustomError(...)`. + +## What it does + +Reports `require` calls whose second argument is a string literal, and `revert(...)` calls that +are either bare or have a string-literal argument. + +## Why is this bad? + +Custom errors: +- cost less gas than encoding/decoding a string, +- can carry typed parameters for richer diagnostics, +- shrink contract bytecode (string constants live in code). + +Solidity 0.8.4+ supports custom errors natively. + +## Example + +### Bad + +```solidity +require(amount > 0, "amount must be > 0"); +revert("not authorized"); +revert(); +``` + +### Good + +```solidity +error AmountZero(); +error NotAuthorized(); + +if (amount == 0) revert AmountZero(); +if (!authorized) revert NotAuthorized(); +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/divide-before-multiply.md b/crates/lint/docs/divide-before-multiply.md new file mode 100644 index 0000000000000..f082bef19a1bd --- /dev/null +++ b/crates/lint/docs/divide-before-multiply.md @@ -0,0 +1,32 @@ +# Divide before multiply + +**Severity**: `Med` +**ID**: `divide-before-multiply` + +Flags arithmetic expressions where division is performed before multiplication, which can cause +unintended precision loss in integer arithmetic. + +## What it does + +Warns on expressions of the form `(a / b) * c` (or equivalent shapes), where the integer division +truncates before the result is multiplied. + +## Why is this bad? + +Solidity's integer division truncates toward zero. Performing `(a / b) * c` discards the remainder +of `a / b` before scaling, while `(a * c) / b` preserves precision. This pattern frequently +manifests as fee/share/yield miscalculations. + +## Example + +### Bad + +```solidity +uint256 share = (amount / total) * weight; // truncates first, then scales +``` + +### Good + +```solidity +uint256 share = (amount * weight) / total; // preserves precision +``` diff --git a/crates/lint/docs/erc20-unchecked-transfer.md b/crates/lint/docs/erc20-unchecked-transfer.md new file mode 100644 index 0000000000000..d7d053e020cca --- /dev/null +++ b/crates/lint/docs/erc20-unchecked-transfer.md @@ -0,0 +1,43 @@ +# Unchecked ERC20 transfer return value + +**Severity**: `High` +**ID**: `erc20-unchecked-transfer` + +Flags calls to ERC20 `transfer` and `transferFrom` where the boolean return value is ignored. + +## What it does + +Warns when a function with the same signature as +`transfer(address,uint256)` or `transferFrom(address,address,uint256)` and a `bool` return type is +invoked but the result is not checked. + +## Why is this bad? + +The ERC20 spec allows tokens to signal failure by returning `false` instead of reverting. Ignoring +the return value lets a "failed" transfer go unnoticed, allowing accounting to drift and creating +common DeFi exploits. Use a wrapper such as OpenZeppelin's `SafeERC20` or check the boolean +explicitly. + +## Example + +### Bad + +```solidity +token.transfer(to, amount); +token.transferFrom(from, to, amount); +``` + +### Good + +```solidity +require(token.transfer(to, amount), "transfer failed"); +require(token.transferFrom(from, to, amount), "transferFrom failed"); + +// or use SafeERC20 +SafeERC20.safeTransfer(token, to, amount); +``` + +## Notes + +This lint can produce false positives when the callee does not strictly conform to the ERC20 +interface (e.g. tokens that revert on failure rather than returning `false`). diff --git a/crates/lint/docs/incorrect-erc20-interface.md b/crates/lint/docs/incorrect-erc20-interface.md new file mode 100644 index 0000000000000..65fb8313c205f --- /dev/null +++ b/crates/lint/docs/incorrect-erc20-interface.md @@ -0,0 +1,42 @@ +# Incorrect ERC20 interface + +**Severity**: `Med` +**ID**: `incorrect-erc20-interface` + +Flags interfaces or contracts whose function signatures match an ERC20 method by name and +parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC20 method +(`totalSupply`, `balanceOf`, `transfer`, `transferFrom`, `approve`, `allowance`), the lint checks +that the return type matches the spec. A mismatch is reported. + +## Why is this bad? + +Tokens that diverge from the ERC20 spec break composability with the wider ecosystem (DEXes, +lending protocols, multisigs) and are a common source of integration bugs and exploits. + +## Example + +### Bad + +```solidity +interface IBadERC20 { + function balanceOf(address) external view returns (bool); // should be uint256 + function transfer(address, uint256) external; // should return bool +} +``` + +### Good + +```solidity +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-erc721-interface.md b/crates/lint/docs/incorrect-erc721-interface.md new file mode 100644 index 0000000000000..4803afdde7cc1 --- /dev/null +++ b/crates/lint/docs/incorrect-erc721-interface.md @@ -0,0 +1,48 @@ +# Incorrect ERC721 interface + +**Severity**: `Med` +**ID**: `incorrect-erc721-interface` + +Flags interfaces or contracts whose function signatures match an ERC721 (or ERC165) method by +name and parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC721/ERC165 method +(`balanceOf`, `ownerOf`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, +`getApproved`, `isApprovedForAll`, `supportsInterface`), the lint checks that the return type +matches the spec. A mismatch is reported. + +## Why is this bad? + +Non-conforming NFT contracts break marketplaces, indexers, and any protocol that relies on the +ERC721 spec. A wrong return type often compiles and deploys silently but causes integration +failures at runtime. + +## Example + +### Bad + +```solidity +interface IBadERC721 { + function balanceOf(address) external view returns (bool); // should be uint256 + function ownerOf(uint256) external view returns (bool); // should be address + function supportsInterface(bytes4) external view returns (uint256); // should be bool +} +``` + +### Good + +```solidity +interface IERC721 { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function transferFrom(address from, address to, uint256 tokenId) external; + function approve(address to, uint256 tokenId) external; + function setApprovalForAll(address operator, bool approved) external; + function getApproved(uint256 tokenId) external view returns (address); + function isApprovedForAll(address owner, address operator) external view returns (bool); + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-shift.md b/crates/lint/docs/incorrect-shift.md new file mode 100644 index 0000000000000..a9a70c7f93128 --- /dev/null +++ b/crates/lint/docs/incorrect-shift.md @@ -0,0 +1,37 @@ +# Incorrect shift order + +**Severity**: `High` +**ID**: `incorrect-shift` + +Flags shift operations where a literal appears on the left and a non-literal on the right, which +is almost always the wrong operand order. + +## What it does + +Warns when the left-hand operand of `<<` or `>>` is a numeric literal and the right-hand operand +is a non-literal expression (e.g. a variable, function call, or composite expression). + +## Why is this bad? + +Shift expressions like `2 << x` are usually a typo for `x << 2`. In the former, the *value being +shifted* is a tiny constant and the *shift amount* is dynamic — almost never the intended +behavior, and a known source of bugs in production contracts. + +## Example + +### Bad + +```solidity +result = 2 << stateValue; // shift amount comes from state +result = 8 >> localValue; // shift amount comes from a local +result = 16 << (stateValue + 1); // shift amount is a dynamic expression +``` + +### Good + +```solidity +result = stateValue << 2; +result = localValue >> 3; +result = stateValue << localShiftAmount; +result = 1 << 8; // both literals — fine +``` diff --git a/crates/lint/docs/inline-assembly.md b/crates/lint/docs/inline-assembly.md new file mode 100644 index 0000000000000..bba61148b84c5 --- /dev/null +++ b/crates/lint/docs/inline-assembly.md @@ -0,0 +1,69 @@ +# Inline assembly + +**Severity**: `Info` +**ID**: `inline-assembly` + +Flags every `assembly { ... }` block. Inline assembly bypasses many of Solidity's safety +features (type checks, overflow checks, memory layout invariants) and is a common source of +high-impact bugs, so each occurrence should be reviewed deliberately. + +## What it does + +Reports every inline assembly statement, including blocks declared with the `"evmasm"` dialect +and/or the `("memory-safe")` flag. Blocks declared as memory-safe — either via the modern +`("memory-safe")` flag or the legacy `/// @solidity memory-safe-assembly` NatSpec marker — are +still reported, but with a softer message acknowledging the developer attestation: review +focuses on business logic and side effects rather than memory layout. + +## Why is this bad? + +Assembly skips Solidity's compile-time checks and many of its runtime guarantees. Mistakes +inside an `assembly` block can corrupt memory, break the free memory pointer, leak storage, +escalate privileges via `delegatecall`, or destroy the contract via `selfdestruct`. Even when +required for gas or features unavailable in high-level Solidity, assembly should be small, +documented, and reviewed. + +## When inline assembly is reasonable + +Some idioms are widely used and generally safe: + +- Reading transaction/chain context: `chainid()`, `gas()`, `returndatasize()`. +- Probing code: `codesize()`, `extcodesize(addr)`, `extcodehash(addr)`. +- Reading the free memory pointer: `mload(0x40)`. +- Cheap hashing of a known memory layout, when paired with `("memory-safe")`. + +If you must use assembly: + +1. Keep the block minimal and well-commented. +2. Add the `("memory-safe")` flag when the block does not violate Solidity's memory model, so + the optimizer (and reviewers) can rely on it. The legacy + `/// @solidity memory-safe-assembly` NatSpec marker on the line directly above the block is + also recognized for compatibility with older codebases. +3. Suppress the lint locally to mark the block as audited: + ```solidity + // forge-lint: disable-next-line(inline-assembly) + assembly ("memory-safe") { /* reviewed: ... */ } + ``` + +## Example + +### Bad + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory) { + assembly { + let ok := call(gas(), target, 0, add(data.offset, 0), data.length, 0, 0) + // ... + } +} +``` + +### Good + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory result) { + bool ok; + (ok, result) = target.call(data); + require(ok, "call failed"); +} +``` diff --git a/crates/lint/docs/interface-file-naming.md b/crates/lint/docs/interface-file-naming.md new file mode 100644 index 0000000000000..ff72a0c175e8e --- /dev/null +++ b/crates/lint/docs/interface-file-naming.md @@ -0,0 +1,31 @@ +# Interface file naming + +**Severity**: `Info` +**ID**: `interface-file-naming` + +Flags Solidity files whose only top-level declaration is an interface but whose filename is not +prefixed with `I`. + +## What it does + +Reports interface-only files whose path basename does not start with `I` (e.g. `IERC20.sol`). + +## Why is this bad? + +Prefixing interface filenames with `I` is the prevailing convention in the Solidity ecosystem. +Following it makes import paths predictable and lets reviewers tell at a glance whether they are +looking at an interface or an implementation. + +## Example + +### Bad + +```text +contracts/Token.sol // file contains only `interface Token { ... }` +``` + +### Good + +```text +contracts/IToken.sol // file contains only `interface IToken { ... }` +``` diff --git a/crates/lint/docs/interface-naming.md b/crates/lint/docs/interface-naming.md new file mode 100644 index 0000000000000..5c6b12b946091 --- /dev/null +++ b/crates/lint/docs/interface-naming.md @@ -0,0 +1,31 @@ +# Interface name should be prefixed with 'I' + +**Severity**: `Info` +**ID**: `interface-naming` + +Flags `interface` declarations whose names are not prefixed with `I`. + +## What it does + +Reports `interface Foo` where `Foo` does not start with `I` (e.g. `IFoo`). + +## Why is this bad? + +Prefixing interfaces with `I` is the prevailing convention in Solidity codebases (`IERC20`, +`IERC721`, `IUniswapV2Pair`, ...). Following it makes the role of each type unambiguous at use +sites and aligns with the matching +[`interface-file-naming`](https://getfoundry.sh/forge/linting/interface-file-naming) lint. + +## Example + +### Bad + +```solidity +interface ERC20 { /* ... */ } +``` + +### Good + +```solidity +interface IERC20 { /* ... */ } +``` diff --git a/crates/lint/docs/missing-zero-check.md b/crates/lint/docs/missing-zero-check.md new file mode 100644 index 0000000000000..7eab1f3a00117 --- /dev/null +++ b/crates/lint/docs/missing-zero-check.md @@ -0,0 +1,39 @@ +# Missing zero-address check + +**Severity**: `Low` +**ID**: `missing-zero-check` + +Flags entry-point functions and constructors where an `address` parameter flows into a state write +or value transfer without a zero-address guard. + +## What it does + +Performs a taint analysis from each `address` parameter of an externally callable, state-mutating +function (or constructor) and reports a parameter that reaches a sink (state write, `transfer`, +`call{value: ...}`, etc.) without first being compared against `address(0)` in an `if`/`require`/ +`assert` predicate. + +## Why is this bad? + +Forgetting a zero-address check is a common source of value loss: tokens become permanently +unrecoverable, ownership is renounced unintentionally, or upgrades are bricked. Adding an explicit +guard is cheap and removes an entire class of operational mistakes. + +## Example + +### Bad + +```solidity +function setOwner(address newOwner) external onlyOwner { + owner = newOwner; // no zero-address check +} +``` + +### Good + +```solidity +function setOwner(address newOwner) external onlyOwner { + require(newOwner != address(0), "zero address"); + owner = newOwner; +} +``` diff --git a/crates/lint/docs/mixed-case-function.md b/crates/lint/docs/mixed-case-function.md new file mode 100644 index 0000000000000..9997dcb5691c7 --- /dev/null +++ b/crates/lint/docs/mixed-case-function.md @@ -0,0 +1,32 @@ +# Function names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-function` + +Flags function names that do not follow `mixedCase`. + +## What it does + +Reports functions whose names contain underscores, start with an uppercase letter, or otherwise +deviate from `mixedCase`. Test functions starting with `test`, `invariant_`, or `statefulFuzz` +and user-defined patterns (e.g. `ERC20`) are exempted. + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for function names. Consistent style makes call +sites uniform, helps editor tooling, and reduces friction in code review. + +## Example + +### Bad + +```solidity +function get_balance() external view returns (uint256); +function GetBalance() external view returns (uint256); +``` + +### Good + +```solidity +function getBalance() external view returns (uint256); +``` diff --git a/crates/lint/docs/mixed-case-variable.md b/crates/lint/docs/mixed-case-variable.md new file mode 100644 index 0000000000000..3341e1a0c48ad --- /dev/null +++ b/crates/lint/docs/mixed-case-variable.md @@ -0,0 +1,36 @@ +# Mutable variable names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-variable` + +Flags mutable variable names (locals, parameters, mutable state) that do not follow `mixedCase`. + +## What it does + +Reports mutable variable identifiers that contain underscores, start with an uppercase letter, +or otherwise deviate from `mixedCase`. + +`constant` and `immutable` state variables are not flagged by this lint — see +[`screaming-snake-case-const`](https://getfoundry.sh/forge/linting/screaming-snake-case-const) and +[`screaming-snake-case-immutable`](https://getfoundry.sh/forge/linting/screaming-snake-case-immutable). + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for mutable variables. Consistent style makes +code easier to scan and review. + +## Example + +### Bad + +```solidity +uint256 public total_supply; +address Owner; +``` + +### Good + +```solidity +uint256 public totalSupply; +address owner; +``` diff --git a/crates/lint/docs/multi-contract-file.md b/crates/lint/docs/multi-contract-file.md new file mode 100644 index 0000000000000..beabc827e4ea6 --- /dev/null +++ b/crates/lint/docs/multi-contract-file.md @@ -0,0 +1,37 @@ +# Multiple contracts in one file + +**Severity**: `Info` +**ID**: `multi-contract-file` + +Flags source files that declare more than one top-level contract, interface, or library. + +## What it does + +Reports each top-level `contract`, `interface`, or `library` definition (after the first) in a +file that contains more than one such declaration. + +## Why is this bad? + +Keeping one contract per file improves discoverability (`grep`, IDE jump-to-file), simplifies +import paths, and avoids unintentional bytecode bloat from artifacts that bundle unrelated +contracts. + +## Example + +### Bad + +```solidity +// File: Token.sol +contract TokenA { /* ... */ } +contract TokenB { /* ... */ } +``` + +### Good + +```solidity +// File: TokenA.sol +contract TokenA { /* ... */ } + +// File: TokenB.sol +contract TokenB { /* ... */ } +``` diff --git a/crates/lint/docs/named-struct-fields.md b/crates/lint/docs/named-struct-fields.md new file mode 100644 index 0000000000000..45713e2555ddc --- /dev/null +++ b/crates/lint/docs/named-struct-fields.md @@ -0,0 +1,31 @@ +# Prefer named struct fields + +**Severity**: `Info` +**ID**: `named-struct-fields` + +Flags struct construction expressions that pass fields positionally instead of by name. + +## What it does + +Reports `Struct(a, b, c)` style struct construction; suggests `Struct({ field1: a, field2: b, +field3: c })` instead. + +## Why is this bad? + +Positional struct construction is fragile: adding or reordering fields silently changes the +meaning of every existing call site. Named-field construction is self-documenting and resilient +to struct changes. + +## Example + +### Bad + +```solidity +User memory u = User(addr, 100, true); +``` + +### Good + +```solidity +User memory u = User({ wallet: addr, balance: 100, active: true }); +``` diff --git a/crates/lint/docs/pascal-case-struct.md b/crates/lint/docs/pascal-case-struct.md new file mode 100644 index 0000000000000..02a243bd56bf4 --- /dev/null +++ b/crates/lint/docs/pascal-case-struct.md @@ -0,0 +1,31 @@ +# Struct names should use PascalCase + +**Severity**: `Info` +**ID**: `pascal-case-struct` + +Flags struct definitions whose names do not follow `PascalCase`. + +## What it does + +Reports any `struct` whose identifier does not match the `PascalCase` convention. + +## Why is this bad? + +The Solidity style guide recommends `PascalCase` for type-like names (contracts, structs, +enums, libraries). Consistent casing makes code easier to scan and integrates with editor +features and external tooling. + +## Example + +### Bad + +```solidity +struct user_info { uint256 balance; } +struct USERINFO { uint256 balance; } +``` + +### Good + +```solidity +struct UserInfo { uint256 balance; } +``` diff --git a/crates/lint/docs/pragma-inconsistent.md b/crates/lint/docs/pragma-inconsistent.md new file mode 100644 index 0000000000000..095f45783773d --- /dev/null +++ b/crates/lint/docs/pragma-inconsistent.md @@ -0,0 +1,41 @@ +# Inconsistent pragma directives + +**Severity**: `Info` +**ID**: `pragma-inconsistent` + +Flags projects whose source files declare incompatible or differently-shaped Solidity version +pragmas. + +## What it does + +Inspects every `pragma solidity ...;` directive across all input source files and reports when +their version requirements are inconsistent (different exact versions, mixed caret/tilde/range +shapes, etc.). + +## Why is this bad? + +A project compiled under multiple Solidity versions can subtly change behavior between files +(e.g. checked arithmetic, default visibility, ABI encoding). Aligning pragmas across the project +removes a hidden source of integration bugs and makes upgrades coordinated. + +## Example + +### Bad + +```solidity +// A.sol +pragma solidity 0.8.18; + +// B.sol +pragma solidity ^0.8.20; + +// C.sol +pragma solidity >=0.7.0 <0.9.0; +``` + +### Good + +```solidity +// All files +pragma solidity 0.8.20; +``` diff --git a/crates/lint/docs/rtlo.md b/crates/lint/docs/rtlo.md new file mode 100644 index 0000000000000..58ce648752c6f --- /dev/null +++ b/crates/lint/docs/rtlo.md @@ -0,0 +1,32 @@ +# Right-to-left override character + +**Severity**: `High` +**ID**: `rtlo` + +Flags the presence of Unicode bidirectional override characters in source code, which can be used +to hide malicious behavior ("Trojan Source", [CVE-2021-42574](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574)). + +## What it does + +Detects the right-to-left override codepoint (`U+202E`) and other bidirectional control characters +embedded in identifiers, strings, and comments. + +## Why is this bad? + +These characters render source code in a different visual order than how the compiler reads it, +allowing an attacker to make malicious code look benign on review. Solidity contracts are public +and frequently audited visually; this attack vector must not be ignored. + +## Example + +### Bad + +```solidity +// transfer(victim‮, attacker)/* // U+202E hidden between args +``` + +### Good + +```solidity +// Avoid bidirectional override characters in code and comments. +``` diff --git a/crates/lint/docs/screaming-snake-case-const.md b/crates/lint/docs/screaming-snake-case-const.md new file mode 100644 index 0000000000000..72a16c5875fae --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-const.md @@ -0,0 +1,30 @@ +# Constants should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-const` + +Flags `constant` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `constant` whose identifier deviates from `SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for constants so they stand out from +mutable state and immutables at call sites. + +## Example + +### Bad + +```solidity +uint256 constant maxSupply = 1_000_000; +uint256 constant Max_Supply = 1_000_000; +``` + +### Good + +```solidity +uint256 constant MAX_SUPPLY = 1_000_000; +``` diff --git a/crates/lint/docs/screaming-snake-case-immutable.md b/crates/lint/docs/screaming-snake-case-immutable.md new file mode 100644 index 0000000000000..cee5590e16d27 --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-immutable.md @@ -0,0 +1,31 @@ +# Immutables should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-immutable` + +Flags `immutable` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `immutable` whose identifier deviates from +`SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for `immutable` variables so they +visually align with `constant` ones and stand out from mutable state at call sites. + +## Example + +### Bad + +```solidity +address immutable owner; +address immutable Owner; +``` + +### Good + +```solidity +address immutable OWNER; +``` diff --git a/crates/lint/docs/too-many-digits.md b/crates/lint/docs/too-many-digits.md new file mode 100644 index 0000000000000..5decb67bec9c3 --- /dev/null +++ b/crates/lint/docs/too-many-digits.md @@ -0,0 +1,32 @@ +# Numeric literal with too many digits + +**Severity**: `Info` +**ID**: `too-many-digits` + +Flags numeric literals containing five or more consecutive zeros, which are easy to misread. + +## What it does + +Reports decimal numeric literals that contain a run of 5 or more `0` characters. + +## Why is this bad? + +Long sequences of zeros are difficult to count visually, and an off-by-one zero is a common bug +(e.g. funding `1_000_000` instead of `10_000_000`). Use scientific notation, sub-denominations, or +underscore separators to make the magnitude obvious. + +## Example + +### Bad + +```solidity +uint256 amount = 1000000000000000000; +``` + +### Good + +```solidity +uint256 amount = 1e18; +uint256 amount2 = 1 ether; +uint256 amount3 = 1_000_000_000_000_000_000; +``` diff --git a/crates/lint/docs/tx-origin.md b/crates/lint/docs/tx-origin.md new file mode 100644 index 0000000000000..26877cf9c0116 --- /dev/null +++ b/crates/lint/docs/tx-origin.md @@ -0,0 +1,34 @@ +# Use of tx.origin for authorization + +**Severity**: `Med` +**ID**: `tx-origin` + +Flags use of `tx.origin` inside authorization-like predicates such as `require`, `assert`, `if`, +`while`, and `for` conditions. + +## What it does + +Reports `tx.origin` reads when they are used as part of a guard condition. Plain reads outside of +guard predicates are not reported. + +## Why is this bad? + +`tx.origin` is the original externally owned account that started the whole transaction, not the +immediate caller. If authorization checks rely on `tx.origin`, a malicious contract can call the +protected contract while the legitimate owner is the transaction origin. + +Use `msg.sender` for authorization checks instead. + +## Example + +### Bad + +```solidity +require(tx.origin == owner, "not owner"); +``` + +### Good + +```solidity +require(msg.sender == owner, "not owner"); +``` diff --git a/crates/lint/docs/unaliased-plain-import.md b/crates/lint/docs/unaliased-plain-import.md new file mode 100644 index 0000000000000..be8c5120028d6 --- /dev/null +++ b/crates/lint/docs/unaliased-plain-import.md @@ -0,0 +1,34 @@ +# Unaliased plain import + +**Severity**: `Info` +**ID**: `unaliased-plain-import` + +Flags `import "path";` statements that pull in every top-level symbol from another file without +an alias. + +## What it does + +Reports plain imports of the form `import "path";`. Suggests using either named imports +(`import { A, B } from "path"`) or an aliased import (`import "path" as X`). + +## Why is this bad? + +Plain imports pollute the importing file's namespace and make the source of each symbol +non-obvious. Named or aliased imports make the dependency surface explicit and reduce the chance +of accidental name collisions. + +## Example + +### Bad + +```solidity +import "./Lib.sol"; +``` + +### Good + +```solidity +import { Foo, Bar } from "./Lib.sol"; +// or +import "./Lib.sol" as Lib; +``` diff --git a/crates/lint/docs/unchecked-call.md b/crates/lint/docs/unchecked-call.md new file mode 100644 index 0000000000000..9a0a4143a0e0e --- /dev/null +++ b/crates/lint/docs/unchecked-call.md @@ -0,0 +1,34 @@ +# Unchecked low-level call + +**Severity**: `High` +**ID**: `unchecked-call` + +Flags low-level calls (`call`, `delegatecall`, `staticcall`, `callcode`) whose `success` return +value is ignored. + +## What it does + +Warns when the boolean returned by a low-level call is discarded — either because the return value +is not assigned or because only the `bytes memory` payload is used. + +## Why is this bad? + +Low-level calls do **not** revert when the callee fails; they silently return `false`. Ignoring +the success flag means a failed call is indistinguishable from a successful one, leading to bugs +where state is updated on the assumption that an external interaction succeeded. + +## Example + +### Bad + +```solidity +target.call(data); // success ignored +(, bytes memory ret) = target.call(data); // only payload kept +``` + +### Good + +```solidity +(bool ok, ) = target.call(data); +require(ok, "call failed"); +``` diff --git a/crates/lint/docs/unsafe-cheatcode.md b/crates/lint/docs/unsafe-cheatcode.md new file mode 100644 index 0000000000000..0aef657b0b7be --- /dev/null +++ b/crates/lint/docs/unsafe-cheatcode.md @@ -0,0 +1,35 @@ +# Usage of unsafe cheatcodes + +**Severity**: `Info` +**ID**: `unsafe-cheatcode` + +Flags use of Foundry cheatcodes that perform dangerous side effects (filesystem access, network +activity, environment variable reads, etc.) so they cannot slip into production code unnoticed. + +## What it does + +Reports calls to cheatcodes whose effects extend beyond the EVM sandbox or that bypass typical +test invariants. The flagged set follows the cheatcode's +[`Safety::Unsafe`](https://book.getfoundry.sh/cheatcodes) classification. + +## Why is this bad? + +Unsafe cheatcodes can read/write files, hit the network, or fork external state. They are +appropriate in tests with explicit intent but should not be added without review, and must +never end up in shipped contract code. + +## Example + +### Bad + +```solidity +vm.writeFile("./out.txt", data); // unsafe — writes to host filesystem +vm.envString("PRIVATE_KEY"); // unsafe — reads host environment +``` + +### Good + +```solidity +// Use safe cheatcodes (vm.expectRevert, vm.prank, vm.warp, ...) and explicit +// inputs/fixtures instead of pulling state from the host environment. +``` diff --git a/crates/lint/docs/unsafe-typecast.md b/crates/lint/docs/unsafe-typecast.md new file mode 100644 index 0000000000000..89d493eec3c3f --- /dev/null +++ b/crates/lint/docs/unsafe-typecast.md @@ -0,0 +1,40 @@ +# Unsafe typecast + +**Severity**: `Med` +**ID**: `unsafe-typecast` + +Flags explicit numeric typecasts that can silently truncate or alter the value. + +## What it does + +Reports casts where the source value's type is wider than the target type +(e.g. `uint256 → uint128`, `int256 → uint128`), unless the cast is preceded by a check that +guarantees the value fits in the target. + +## Why is this bad? + +Solidity does **not** revert on narrowing casts; it silently keeps the lowest bits, which can +cause severe accounting bugs (e.g. amount overflows, wrong fees, broken invariants). Use a checked +cast helper such as OpenZeppelin's `SafeCast` whenever the source value is not provably bounded. + +## Example + +### Bad + +```solidity +function setAmount(uint256 amount) external { + smallAmount = uint128(amount); // silent truncation if amount >= 2**128 +} +``` + +### Good + +```solidity +function setAmount(uint256 amount) external { + require(amount <= type(uint128).max, "overflow"); + smallAmount = uint128(amount); +} + +// or +smallAmount = SafeCast.toUint128(amount); +``` diff --git a/crates/lint/docs/unused-import.md b/crates/lint/docs/unused-import.md new file mode 100644 index 0000000000000..08f2545a36587 --- /dev/null +++ b/crates/lint/docs/unused-import.md @@ -0,0 +1,40 @@ +# Unused import + +**Severity**: `Info` +**ID**: `unused-import` + +Flags imported symbols (or whole import statements) whose imported names are not referenced +anywhere in the source unit. + +## What it does + +Reports `import "..."`, `import "..." as X`, and `import { A, B } from "..."` statements where one +or more imported names are never used. Symbols brought in via `import * as X` are tracked through +`X.member` accesses. + +## Why is this bad? + +Unused imports add noise, slow down compilation, can cause name collisions, and frequently +indicate dead code or stale refactors. + +## Example + +### Bad + +```solidity +import { A, B } from "./Lib.sol"; // B is never used + +contract C { + A internal a; +} +``` + +### Good + +```solidity +import { A } from "./Lib.sol"; + +contract C { + A internal a; +} +``` diff --git a/crates/lint/docs/unused-state-variables.md b/crates/lint/docs/unused-state-variables.md new file mode 100644 index 0000000000000..758c6e58b911b --- /dev/null +++ b/crates/lint/docs/unused-state-variables.md @@ -0,0 +1,39 @@ +# Unused state variable + +**Severity**: `Gas` +**ID**: `unused-state-variables` + +Flags state variables that are declared but never read or written anywhere in the contract or its +descendants. + +## What it does + +Reports each state variable that has no read or write site across the project. + +## Why is this bad? + +Unused state variables waste storage slots, inflate deployment cost, and are a strong signal of +dead or stale code that should be removed. + +## Example + +### Bad + +```solidity +contract C { + uint256 unused; // never read or written + uint256 public total; // used elsewhere +} +``` + +### Good + +```solidity +contract C { + uint256 public total; +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/unwrapped-modifier-logic.md b/crates/lint/docs/unwrapped-modifier-logic.md new file mode 100644 index 0000000000000..985c79962af07 --- /dev/null +++ b/crates/lint/docs/unwrapped-modifier-logic.md @@ -0,0 +1,51 @@ +# Unwrapped modifier logic + +**Severity**: `CodeSize` +**ID**: `unwrapped-modifier-logic` + +Flags modifiers whose body contains non-trivial logic that should be moved into a helper function +to reduce contract code size. + +## What it does + +Reports modifiers whose body contains statements other than a single placeholder, simple builtin +calls (`require`/`assert`), or a single library function call. Modifiers that use inline assembly +are exempted. + +## Why is this bad? + +Solidity inlines a modifier's body at every call site, so any non-trivial logic is duplicated +across all functions that use the modifier. Wrapping the logic in an internal function and calling +it from the modifier keeps the bytecode small while preserving behavior. + +## Example + +### Bad + +```solidity +modifier onlyAuth() { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; + _; +} +``` + +### Good + +```solidity +modifier onlyAuth() { + _checkAuth(); + _; +} + +function _checkAuth() internal { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; +} +``` + +## Notes + +This is a `CodeSize`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/src/linter/mod.rs b/crates/lint/src/linter/mod.rs index 0a5b4a40a5118..3e38a02726605 100644 --- a/crates/lint/src/linter/mod.rs +++ b/crates/lint/src/linter/mod.rs @@ -1,8 +1,10 @@ mod early; mod late; +mod project; pub use early::{EarlyLintPass, EarlyLintVisitor}; pub use late::{LateLintPass, LateLintVisitor}; +pub use project::{ProjectLintEmitter, ProjectLintPass, ProjectSource}; use foundry_common::comments::inline_config::InlineConfig; use foundry_compilers::Language; diff --git a/crates/lint/src/linter/project.rs b/crates/lint/src/linter/project.rs new file mode 100644 index 0000000000000..38fc1ad1ba59f --- /dev/null +++ b/crates/lint/src/linter/project.rs @@ -0,0 +1,92 @@ +use super::{Lint, LintContext, LinterConfig}; +use foundry_common::comments::inline_config::InlineConfig; +use foundry_config::lint::LintSpecificConfig; +use solar::{ + ast, + interface::{Session, Span, diagnostics::DiagMsg, source_map::SourceFile}, +}; +use std::{path::PathBuf, sync::Arc}; + +/// A single source unit visible to a project-wide lint pass, pre-loaded with its inline config so +/// emits respect `// forge-lint: disable-*` markers without rebuilding it per emit. +pub struct ProjectSource<'ast> { + pub path: PathBuf, + pub file: Arc, + pub ast: &'ast ast::SourceUnit<'ast>, + pub inline_config: InlineConfig>, +} + +/// Trait for lints that need to inspect every input source at once (e.g. cross-file checks). +/// +/// `check_project` runs once after all per-file [`super::EarlyLintPass`] / +/// [`super::LateLintPass`] passes have completed. +pub trait ProjectLintPass<'ast>: Send + Sync { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]); +} + +/// Helper passed to [`ProjectLintPass::check_project`] for emitting diagnostics against a specific +/// source. +pub struct ProjectLintEmitter<'s, 'c> { + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, +} + +impl<'s, 'c> ProjectLintEmitter<'s, 'c> { + pub const fn new( + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, + ) -> Self { + Self { sess, with_description, with_json_emitter, lint_specific, active_lints } + } + + /// Returns `true` if the given lint id is enabled for this run. Project passes that perform + /// expensive analysis should guard their work behind this check. + pub fn is_lint_enabled(&self, id: &'static str) -> bool { + self.active_lints.contains(&id) + } + + /// Emits a diagnostic with the lint's default description as the message. + pub fn emit<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + ) where + 'c: 'a, + { + self.build_ctx(source).emit(lint, span); + } + + /// Emits a diagnostic with a caller-provided message. + pub fn emit_with_msg<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + msg: impl Into, + ) where + 'c: 'a, + { + self.build_ctx(source).emit_with_msg(lint, span, msg); + } + + fn build_ctx<'a, 'ast>(&'a self, source: &'a ProjectSource<'ast>) -> LintContext<'s, 'a> + where + 'c: 'a, + { + LintContext::new( + self.sess, + self.with_description, + self.with_json_emitter, + LinterConfig { inline: &source.inline_config, lint_specific: self.lint_specific }, + self.active_lints.clone(), + Some(source.file.clone()), + ) + } +} diff --git a/crates/lint/src/sol/info/inline_assembly.rs b/crates/lint/src/sol/info/inline_assembly.rs new file mode 100644 index 0000000000000..1111129dada34 --- /dev/null +++ b/crates/lint/src/sol/info/inline_assembly.rs @@ -0,0 +1,71 @@ +use super::InlineAssembly; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Stmt, StmtKind}, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + INLINE_ASSEMBLY, + Severity::Info, + "inline-assembly", + "usage of inline assembly; assembly bypasses Solidity safety features and should be reviewed" +); + +const ASSEMBLY_KW_LEN: u32 = 8; +const NATSPEC_MEMORY_SAFE_MARKER: &str = "@solidity memory-safe-assembly"; + +impl<'ast> EarlyLintPass<'ast> for InlineAssembly { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + let StmtKind::Assembly(asm) = &stmt.kind else { return }; + + let kw_span = assembly_keyword_span(stmt.span); + + let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe") + || has_memory_safe_natspec(ctx, stmt.span.lo()); + + let msg = if memory_safe { + "inline assembly (declared memory-safe); review business logic and side effects" + } else { + "inline assembly used; review for memory safety and side effects" + }; + + ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg); + } +} + +/// Narrows a span to the leading `assembly` keyword to keep diagnostics readable. +fn assembly_keyword_span(span: Span) -> Span { + span.with_hi(span.lo() + BytePos(ASSEMBLY_KW_LEN)) +} + +/// Returns `true` when the lines immediately preceding `stmt_lo` form a `///` NatSpec block +/// containing `@solidity memory-safe-assembly`. +fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool { + let Some(source_file) = ctx.source_file() else { return false }; + let src = source_file.src.as_str(); + let start_pos = source_file.start_pos.to_u32(); + let lo_abs = stmt_lo.to_u32(); + if lo_abs < start_pos { + return false; + } + let offset = (lo_abs - start_pos) as usize; + if offset > src.len() { + return false; + } + + for line in src[..offset].lines().rev() { + let trimmed = line.trim_start(); + if trimmed.is_empty() { + continue; + } + let Some(rest) = trimmed.strip_prefix("///") else { return false }; + if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) { + return true; + } + } + false +} diff --git a/crates/lint/src/sol/info/mod.rs b/crates/lint/src/sol/info/mod.rs index c7800a417bafc..913c5d2ea9da3 100644 --- a/crates/lint/src/sol/info/mod.rs +++ b/crates/lint/src/sol/info/mod.rs @@ -30,6 +30,15 @@ use multi_contract_file::MULTI_CONTRACT_FILE; mod interface_naming; use interface_naming::{INTERFACE_FILE_NAMING, INTERFACE_NAMING}; +mod too_many_digits; +use too_many_digits::TOO_MANY_DIGITS; + +mod pragma_directive; +use pragma_directive::PRAGMA_INCONSISTENT; + +mod inline_assembly; +use inline_assembly::INLINE_ASSEMBLY; + register_lints!( (BooleanCst, early, (BOOLEAN_CST)), (BooleanEqual, early, (BOOLEAN_EQUAL)), @@ -42,4 +51,7 @@ register_lints!( (UnsafeCheatcodes, early, (UNSAFE_CHEATCODE_USAGE)), (MultiContractFile, early, (MULTI_CONTRACT_FILE)), (InterfaceFileNaming, early, (INTERFACE_FILE_NAMING, INTERFACE_NAMING)), + (TooManyDigits, early, (TOO_MANY_DIGITS)), + (PragmaDirective, project, (PRAGMA_INCONSISTENT)), + (InlineAssembly, early, (INLINE_ASSEMBLY)), ); diff --git a/crates/lint/src/sol/info/pragma_directive.rs b/crates/lint/src/sol/info/pragma_directive.rs new file mode 100644 index 0000000000000..b66b6bcff6ade --- /dev/null +++ b/crates/lint/src/sol/info/pragma_directive.rs @@ -0,0 +1,71 @@ +use crate::{ + linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource}, + sol::{Severity, SolLint, info::PragmaDirective}, +}; +use solar::{ast, interface::Span}; + +declare_forge_lint!( + PRAGMA_INCONSISTENT, + Severity::Info, + "pragma-inconsistent", + "inconsistent Solidity pragma version requirements across the project" +); + +impl<'ast> ProjectLintPass<'ast> for PragmaDirective { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) { + if !ctx.is_lint_enabled(PRAGMA_INCONSISTENT.id()) { + return; + } + + // Collect every `pragma solidity` directive across input sources, with its rendered + // version-requirement string for grouping. Stores source index to avoid lifetime + // invariance issues with `&ProjectSource<'ast>`. + let mut entries: Vec<(usize, Span, String)> = Vec::new(); + for (idx, source) in sources.iter().enumerate() { + for (span, req) in solidity_pragmas(source.ast) { + entries.push((idx, span, req.to_string())); + } + } + + // Stable order for snapshots and JSON output. + entries.sort_by(|a, b| { + sources[a.0].path.cmp(&sources[b.0].path).then(a.1.lo().cmp(&b.1.lo())) + }); + + // Build the distinct list once and bail if all sources agree. + let mut distinct: Vec<&str> = entries.iter().map(|(_, _, s)| s.as_str()).collect(); + distinct.sort_unstable(); + distinct.dedup(); + if distinct.len() < 2 { + return; + } + + for (idx, span, req_str) in &entries { + let others = distinct + .iter() + .filter(|v| **v != req_str.as_str()) + .copied() + .collect::>() + .join(", "); + let msg = format!( + "'pragma solidity {req_str};' conflicts with other version requirements in the project: {others}" + ); + ctx.emit_with_msg(&sources[*idx], &PRAGMA_INCONSISTENT, *span, msg); + } + } +} + +/// Yields every top-level `pragma solidity ...;` directive in `unit`. +fn solidity_pragmas<'ast>( + unit: &'ast ast::SourceUnit<'ast>, +) -> impl Iterator)> + 'ast { + unit.items.iter().filter_map(|item| match &item.kind { + ast::ItemKind::Pragma(p) => match &p.tokens { + ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => { + Some((item.span, req)) + } + _ => None, + }, + _ => None, + }) +} diff --git a/crates/lint/src/sol/info/too_many_digits.rs b/crates/lint/src/sol/info/too_many_digits.rs new file mode 100644 index 0000000000000..3ba9e8abba2de --- /dev/null +++ b/crates/lint/src/sol/info/too_many_digits.rs @@ -0,0 +1,50 @@ +use super::TooManyDigits; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{Expr, ExprKind, LitKind}; + +declare_forge_lint!( + TOO_MANY_DIGITS, + Severity::Info, + "too-many-digits", + "numeric literal with many digits is error-prone; \ + use scientific notation, sub-denominations, or underscore separators" +); + +impl<'ast> EarlyLintPass<'ast> for TooManyDigits { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + let ExprKind::Lit(lit, sub_denom) = &expr.kind else { return }; + + // Only plain integer literals. `LitKind::Address` (40-hex-digit address) is a + // distinct variant and is therefore skipped automatically. + if !matches!(lit.kind, LitKind::Number(_)) { + return; + } + + // Skip literals with a sub-denomination, e.g. `1000000 gwei`, `5 minutes`. + if sub_denom.is_some() { + return; + } + + let s = lit.symbol.as_str(); + + // Skip hex literals — long zero runs in hex are usually intentional (masks, + // selectors, bit patterns) and there is no scientific-notation alternative. + if s.starts_with("0x") || s.starts_with("0X") { + return; + } + + // Skip if the user already used scientific notation (`1e18`). + if s.contains('e') || s.contains('E') { + return; + } + + // 5+ consecutive zeros in the literal as written. Underscores are + // preserved, so `1_000_000` passes while `1_000000` is flagged. + if s.contains("00000") { + ctx.emit(&TOO_MANY_DIGITS, lit.span); + } + } +} diff --git a/crates/lint/src/sol/macros.rs b/crates/lint/src/sol/macros.rs index 00d764770374a..8540ab8b95b8f 100644 --- a/crates/lint/src/sol/macros.rs +++ b/crates/lint/src/sol/macros.rs @@ -9,9 +9,11 @@ /// - `$desc`: A short description of the lint. /// /// # Note -/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by -/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the -/// book, the existence of the lint rule's help section is validated with a unit test. +/// Each lint must have a corresponding markdown documentation file at +/// `crates/lint/docs/.md`. The `help` URL is auto-generated by the macro and points to +/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/`). To +/// ensure that new lint rules have their corresponding docs, the existence of every registered +/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`). #[macro_export] macro_rules! declare_forge_lint { ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { @@ -20,7 +22,7 @@ macro_rules! declare_forge_lint { id: $str_id, severity: $severity, description: $desc, - help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id), + help: concat!("https://getfoundry.sh/forge/linting/", $str_id), }; }; } @@ -53,6 +55,7 @@ macro_rules! register_lints { register_lints!(@early_impl $pass_id, $pass_type); register_lints!(@late_impl $pass_id, $pass_type); + register_lints!(@project_impl $pass_id, $pass_type); } )* }; @@ -89,10 +92,22 @@ macro_rules! register_lints { .flatten() .collect() } + + pub fn create_project_lint_passes<'ast>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@project_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() + } }; // --- HELPERS ------------------------------------------------------------ (@early_impl $_pass_id:ident, late) => {}; + (@early_impl $_pass_id:ident, project) => {}; (@early_impl $pass_id:ident, $other:ident) => { pub fn as_early_lint_pass<'a>() -> Box> { Box::new(Self::default()) @@ -100,22 +115,41 @@ macro_rules! register_lints { }; (@late_impl $_pass_id:ident, early) => {}; + (@late_impl $_pass_id:ident, project) => {}; (@late_impl $pass_id:ident, $other:ident) => { pub fn as_late_lint_pass<'hir>() -> Box> { Box::new(Self::default()) } }; + (@project_impl $_pass_id:ident, early) => {}; + (@project_impl $_pass_id:ident, late) => {}; + (@project_impl $_pass_id:ident, both) => {}; + (@project_impl $pass_id:ident, $other:ident) => { + pub fn as_project_lint_pass<'ast>() -> Box> { + Box::new(Self::default()) + } + }; + (@early_create $_pass_id:ident, late) => { None }; + (@early_create $_pass_id:ident, project) => { None }; (@early_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_early_lint_pass(), $pass_id::LINTS)) }; (@late_create $_pass_id:ident, early) => { None }; + (@late_create $_pass_id:ident, project) => { None }; (@late_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_late_lint_pass(), $pass_id::LINTS)) }; + (@project_create $_pass_id:ident, early) => { None }; + (@project_create $_pass_id:ident, late) => { None }; + (@project_create $_pass_id:ident, both) => { None }; + (@project_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_project_lint_pass(), $pass_id::LINTS)) + }; + // --- ENTRY POINT --------------------------------------------------------- ( $($tokens:tt)* ) => { register_lints! { @declare_structs $($tokens)* } diff --git a/crates/lint/src/sol/med/mod.rs b/crates/lint/src/sol/med/mod.rs index ba7a09b0e9bac..2673ba23d3252 100644 --- a/crates/lint/src/sol/med/mod.rs +++ b/crates/lint/src/sol/med/mod.rs @@ -9,6 +9,9 @@ use incorrect_erc20_interface::INCORRECT_ERC20_INTERFACE; mod incorrect_erc721_interface; use incorrect_erc721_interface::INCORRECT_ERC721_INTERFACE; +mod tx_origin; +use tx_origin::TX_ORIGIN; + mod unsafe_typecast; use unsafe_typecast::UNSAFE_TYPECAST; @@ -16,5 +19,6 @@ register_lints!( (DivideBeforeMultiply, early, (DIVIDE_BEFORE_MULTIPLY)), (IncorrectERC20Interface, late, (INCORRECT_ERC20_INTERFACE)), (IncorrectERC721Interface, late, (INCORRECT_ERC721_INTERFACE)), + (TxOrigin, early, (TX_ORIGIN)), (UnsafeTypecast, late, (UNSAFE_TYPECAST)) ); diff --git a/crates/lint/src/sol/med/tx_origin.rs b/crates/lint/src/sol/med/tx_origin.rs new file mode 100644 index 0000000000000..00ff5f939ebfb --- /dev/null +++ b/crates/lint/src/sol/med/tx_origin.rs @@ -0,0 +1,101 @@ +use super::TxOrigin; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Expr, ExprKind, IndexKind, Stmt, StmtKind}, + interface::SpannedOption, +}; + +declare_forge_lint!( + TX_ORIGIN, + Severity::Med, + "tx-origin", + "`tx.origin` should not be used for authorization" +); + +impl<'ast> EarlyLintPass<'ast> for TxOrigin { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::While(cond, _) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::For { cond: Some(cond), .. } => { + emit_if_contains_tx_origin(ctx, cond); + } + _ => {} + } + } + + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(callee, args) = &expr.kind + && is_require_or_assert_call(callee) + && let Some(cond) = args.exprs().next() + { + emit_if_contains_tx_origin(ctx, cond); + } + } +} + +fn emit_if_contains_tx_origin(ctx: &LintContext, expr: &Expr<'_>) { + if contains_tx_origin(expr) { + ctx.emit(&TX_ORIGIN, expr.span); + } +} + +fn contains_tx_origin(expr: &Expr<'_>) -> bool { + if is_tx_origin(expr) { + return true; + } + match &expr.kind { + ExprKind::Unary(_, inner) => contains_tx_origin(inner), + ExprKind::Binary(lhs, _, rhs) => contains_tx_origin(lhs) || contains_tx_origin(rhs), + ExprKind::Index(base, index) => { + contains_tx_origin(base) + || match index { + IndexKind::Index(Some(index)) => contains_tx_origin(index), + IndexKind::Range(start, end) => { + start.as_ref().is_some_and(|start| contains_tx_origin(start)) + || end.as_ref().is_some_and(|end| contains_tx_origin(end)) + } + _ => false, + } + } + ExprKind::Tuple(elems) => elems.iter().any(|elem| { + if let SpannedOption::Some(inner) = elem.as_ref() { + contains_tx_origin(inner) + } else { + false + } + }), + ExprKind::Call(callee, args) => { + contains_tx_origin(callee) || args.exprs().any(contains_tx_origin) + } + ExprKind::Ternary(cond, then_expr, else_expr) => { + contains_tx_origin(cond) + || contains_tx_origin(then_expr) + || contains_tx_origin(else_expr) + } + _ => false, + } +} + +fn is_tx_origin(expr: &Expr<'_>) -> bool { + matches!( + &expr.kind, + ExprKind::Member(base, member) + if member.as_str() == "origin" + && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "tx") + ) +} + +fn is_require_or_assert_call(callee: &Expr<'_>) -> bool { + matches!( + &callee.kind, + ExprKind::Ident(ident) if matches!(ident.as_str(), "require" | "assert") + ) +} diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index 1f7c515585fb4..7ae073f2ea20b 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -1,6 +1,6 @@ use crate::linter::{ EarlyLintPass, EarlyLintVisitor, LateLintPass, LateLintVisitor, Lint, LintContext, Linter, - LinterConfig, + LinterConfig, ProjectLintEmitter, ProjectLintPass, ProjectSource, }; use foundry_common::{ comments::{ @@ -179,6 +179,62 @@ impl<'a> SolidityLinter<'a> { Ok(()) } + /// Runs all enabled project-wide lint passes against the given input sources. + fn process_project<'gcx>(&self, gcx: Gcx<'gcx>, input: &[PathBuf]) { + // Gather enabled project passes from every severity bucket. + let mut passes_and_lints: Vec<(Box>, &'static [SolLint])> = + Vec::new(); + passes_and_lints.extend(high::create_project_lint_passes()); + passes_and_lints.extend(med::create_project_lint_passes()); + passes_and_lints.extend(low::create_project_lint_passes()); + passes_and_lints.extend(info::create_project_lint_passes()); + passes_and_lints.extend(gas::create_project_lint_passes()); + passes_and_lints.extend(codesize::create_project_lint_passes()); + + let (mut passes, lint_ids): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + if !included.is_empty() { + passes.push(pass); + ids.extend(included); + } + (passes, ids) + }); + + if passes.is_empty() { + return; + } + + // Pre-load every input source with its inline config, in input order. + let sources: Vec> = input + .iter() + .filter_map(|path| { + let path = self.path_config.root.join(path); + let (_, source) = gcx.get_ast_source(&path)?; + let ast = source.ast.as_ref()?; + let comments = + Comments::new(&source.file, gcx.sess.source_map(), false, false, None); + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + Some(ProjectSource { path, file: source.file.clone(), ast, inline_config }) + }) + .collect(); + + let emitter = ProjectLintEmitter::new( + gcx.sess, + self.with_description, + self.with_json_emitter, + self.lint_specific, + lint_ids, + ); + for pass in &mut passes { + pass.check_project(&emitter, &sources); + } + } + fn process_source_hir<'gcx>( &self, gcx: Gcx<'gcx>, @@ -314,6 +370,9 @@ impl<'a> Linter for SolidityLinter<'a> { ); }); + // Project-wide lints, run once after all per-file passes. + self.process_project(gcx, input); + convert_solar_errors(compiler.dcx()) })?; @@ -453,3 +512,75 @@ impl<'a> TryFrom<&'a str> for SolLint { Err(SolLintError::InvalidId(value.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Every registered lint must have a markdown documentation file at + /// `crates/lint/docs/.md`. This test enforces that contract so that the `help` URL + /// generated by `declare_forge_lint!` always resolves to real documentation. + /// + /// When this test fails, add a new file at `crates/lint/docs/.md` describing the + /// lint. See [`crates/lint/docs/_template.md`](../../docs/_template.md) for the expected + /// structure. + #[test] + fn registered_lints_have_docs() { + let docs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs"); + assert!(docs_dir.is_dir(), "missing docs directory at {}", docs_dir.display()); + + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + let mut missing: Vec<&'static str> = Vec::new(); + let mut empty: Vec<&'static str> = Vec::new(); + for lint in &all_lints { + let path = docs_dir.join(format!("{}.md", lint.id())); + match std::fs::read_to_string(&path) { + Ok(content) => { + // Basic sanity: file should be non-trivial and reference the lint id. + if content.trim().is_empty() || !content.contains(lint.id()) { + empty.push(lint.id()); + } + } + Err(_) => missing.push(lint.id()), + } + } + + assert!( + missing.is_empty(), + "the following registered lints are missing a docs file at \ + `crates/lint/docs/.md`: {missing:?}\n\ + See `crates/lint/docs/_template.md` for the expected structure." + ); + assert!( + empty.is_empty(), + "the following lint docs files are empty or do not reference the lint id: {empty:?}" + ); + } + + /// The auto-generated `help` URL must point at the canonical Foundry docs site so that the + /// link printed in diagnostics resolves correctly. + #[test] + fn registered_lints_have_canonical_help_url() { + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + for lint in all_lints { + let expected = format!("https://getfoundry.sh/forge/linting/{}", lint.id()); + assert_eq!(lint.help(), expected, "lint `{}` has a non-canonical help URL", lint.id()); + } + } +} diff --git a/crates/lint/testdata/BlockTimestamp.stderr b/crates/lint/testdata/BlockTimestamp.stderr index 016f8fa2bdb2d..62ab588ae7340 100644 --- a/crates/lint/testdata/BlockTimestamp.stderr +++ b/crates/lint/testdata/BlockTimestamp.stderr @@ -4,7 +4,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -12,7 +12,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp == 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -20,7 +20,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp != 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -28,7 +28,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp <= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -36,7 +36,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp >= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -44,7 +44,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp < deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -52,7 +52,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return deadline > block.timestamp; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -60,7 +60,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp + 1 > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -68,7 +68,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return (block.timestamp / 3600) == 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -76,7 +76,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ require(block.timestamp > deadline); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -84,7 +84,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ if (block.timestamp > deadline) { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -92,5 +92,5 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return foo(block.timestamp) > 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp diff --git a/crates/lint/testdata/BooleanCst.stderr b/crates/lint/testdata/BooleanCst.stderr index 53b89fcb11735..75fdb0b57cea7 100644 --- a/crates/lint/testdata/BooleanCst.stderr +++ b/crates/lint/testdata/BooleanCst.stderr @@ -4,7 +4,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (false) {} │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -12,7 +12,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag || true) {} │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -20,7 +20,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag ? true : false) {} │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -28,7 +28,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag ? true : false) {} │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -36,5 +36,5 @@ warning[boolean-cst]: misuse of a boolean constant LL │ return assigned && false; │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst diff --git a/crates/lint/testdata/BooleanEqual.stderr b/crates/lint/testdata/BooleanEqual.stderr index 11749698f5714..590a85b806fcf 100644 --- a/crates/lint/testdata/BooleanEqual.stderr +++ b/crates/lint/testdata/BooleanEqual.stderr @@ -4,7 +4,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (enabled == true) {} │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -12,7 +12,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (paused == false) {} │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `!paused` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -20,7 +20,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (true != ready) {} │ ━━━━━━━━━━━━━ help: consider simplifying to: `!ready` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -28,7 +28,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ while (done != false) { │ ━━━━━━━━━━━━━ help: consider simplifying to: `done` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -36,7 +36,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ for (; enabled == true && paused != false;) { │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -44,7 +44,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ for (; enabled == true && paused != false;) { │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `paused` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -52,5 +52,5 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ return enabled == true; │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal diff --git a/crates/lint/testdata/CouldBeImmutable.stderr b/crates/lint/testdata/CouldBeImmutable.stderr index 2858b2311cd95..170682baf89d3 100644 --- a/crates/lint/testdata/CouldBeImmutable.stderr +++ b/crates/lint/testdata/CouldBeImmutable.stderr @@ -4,7 +4,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address public owner; │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -12,7 +12,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address public deployer = msg.sender; │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -20,7 +20,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 private configured; │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -28,7 +28,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -36,7 +36,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ CouldBeImmutable private peer; │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -44,7 +44,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 internal inheritedBase; │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -52,5 +52,5 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 internal baseConfigured; │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable diff --git a/crates/lint/testdata/CustomErrors.stderr b/crates/lint/testdata/CustomErrors.stderr index 66b3c11bc183c..286a649aee269 100644 --- a/crates/lint/testdata/CustomErrors.stderr +++ b/crates/lint/testdata/CustomErrors.stderr @@ -4,7 +4,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ require(a > 0, "Value must be greater than zero"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -12,7 +12,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ … require(a >= 0 && a <= 100 || b == 50, "Complex condition should be linted"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -20,7 +20,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert("Something went wrong"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -28,7 +28,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(""); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -36,5 +36,5 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index c0e5ef78e2e1c..95022f65db874 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -4,7 +4,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -12,7 +12,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 / 2) * 3) * 4; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -20,7 +20,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 * 2) / 3) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -28,7 +28,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2 / 3) * 4; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -36,7 +36,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / (2 + 3)) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -44,5 +44,5 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ 1 / ((2 / 3) * 3); │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply diff --git a/crates/lint/testdata/Imports.stderr b/crates/lint/testdata/Imports.stderr index 8fa9800b27ded..1031f4f6f8ca0 100644 --- a/crates/lint/testdata/Imports.stderr +++ b/crates/lint/testdata/Imports.stderr @@ -4,7 +4,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsSomeFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." as X' ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -12,7 +12,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsAnotherFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -20,7 +20,7 @@ note[unused-import]: unused imports should be removed LL │ symbol2 as notUsed, │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -28,7 +28,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol, │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -36,7 +36,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol2, │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -44,7 +44,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbolWrongTag, │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -52,7 +52,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed, │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -60,7 +60,7 @@ note[unused-import]: unused imports should be removed LL │ IContractNotUsed │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -68,7 +68,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed3 │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -76,7 +76,7 @@ note[unused-import]: unused imports should be removed LL │ CONSTANT_1 │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -84,7 +84,7 @@ note[unused-import]: unused imports should be removed LL │ YetAnotherType │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -92,7 +92,7 @@ note[unused-import]: unused imports should be removed LL │ import "./auxiliary/ImportsAnotherFile2.sol" as AnotherFile2; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -100,5 +100,5 @@ note[unused-import]: unused imports should be removed LL │ import * as OtherUtils from "./auxiliary/ImportsUtils2.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import diff --git a/crates/lint/testdata/IncorrectERC20Interface.stderr b/crates/lint/testdata/IncorrectERC20Interface.stderr index 3bb60ecce8320..33e2f1ca27d22 100644 --- a/crates/lint/testdata/IncorrectERC20Interface.stderr +++ b/crates/lint/testdata/IncorrectERC20Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 {} │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Incorrect is IERC20 { │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Correct is IERC20 { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20NamedCorrect { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC20 { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transferFrom(address from, address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function allowance(address owner, address spender) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function balanceOf(address account) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -116,5 +116,5 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function totalSupply() external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface diff --git a/crates/lint/testdata/IncorrectERC721Interface.stderr b/crates/lint/testdata/IncorrectERC721Interface.stderr index a88db93e39b10..2e68084c1cec1 100644 --- a/crates/lint/testdata/IncorrectERC721Interface.stderr +++ b/crates/lint/testdata/IncorrectERC721Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721 {} │ ━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Incorrect is IERC721 { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Correct is IERC721 { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721NamedCorrect { │ ━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC721 { │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function transferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -116,7 +116,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function approve(address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -124,7 +124,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function setApprovalForAll(address operator, bool approved) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -132,7 +132,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function getApproved(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -140,7 +140,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function isApprovedForAll(address owner, address operator) external view returns (address); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -148,5 +148,5 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function supportsInterface(bytes4 interfaceId) external view returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface diff --git a/crates/lint/testdata/IncorrectShift.stderr b/crates/lint/testdata/IncorrectShift.stderr index bce84c98df432..dfff32db897bb 100644 --- a/crates/lint/testdata/IncorrectShift.stderr +++ b/crates/lint/testdata/IncorrectShift.stderr @@ -4,7 +4,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 2 << stateValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -12,7 +12,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -20,7 +20,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 16 << (stateValue + 1); │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -28,7 +28,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 32 >> getAmount(); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -36,5 +36,5 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ … result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift diff --git a/crates/lint/testdata/InlineAssembly.sol b/crates/lint/testdata/InlineAssembly.sol new file mode 100644 index 0000000000000..05917ea22784c --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.sol @@ -0,0 +1,110 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract InlineAssembly { + function bare() public view returns (uint256 id) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + id := chainid() + } + } + + function withMemorySafe() public view returns (uint256 size) { + assembly ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + size := extcodesize(address()) + } + } + + function withDialectAndMemorySafe() public view returns (uint256 ptr) { + assembly "evmasm" ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + ptr := mload(0x40) + } + } + + function withNatspecMemorySafe() public view returns (uint256 v) { + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := chainid() + } + } + + function withNatspecMemorySafeAndOtherDocs() public view returns (uint256 v) { + /// @notice does a thing + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := gas() + } + } + + function plainCommentDoesNotCount() public view returns (uint256 v) { + // solidity memory-safe-assembly + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := chainid() + } + } + + function nestedInControlFlow(bool flag) public view returns (uint256 v) { + if (flag) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := gas() + } + } + + for (uint256 i = 0; i < 1; ++i) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInUnchecked(uint256 x) public pure returns (uint256 v) { + unchecked { + v = x + 1; + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInTryCatch() public returns (uint256 v) { + try this.bare() returns (uint256) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 1 + } + } catch { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 2 + } + } + } + + function suppressed() public view returns (uint256 id) { + // forge-lint: disable-next-line(inline-assembly) + assembly { + id := chainid() + } + } + + modifier guarded() { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + if iszero(caller()) { revert(0, 0) } + } + _; + } + + function suppressedRegion() public view returns (uint256 a, uint256 b) { + // forge-lint: disable-start(inline-assembly) + assembly { + a := chainid() + } + assembly ("memory-safe") { + b := gas() + } + // forge-lint: disable-end(inline-assembly) + } + + function noAssembly() public pure returns (uint256) { + return 42; + } +} diff --git a/crates/lint/testdata/InlineAssembly.stderr b/crates/lint/testdata/InlineAssembly.stderr new file mode 100644 index 0000000000000..12f8bcbacd14e --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.stderr @@ -0,0 +1,96 @@ +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly "evmasm" ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + diff --git a/crates/lint/testdata/Keccak256.sol b/crates/lint/testdata/Keccak256.sol index 41f856336b5b3..2457aed96d601 100644 --- a/crates/lint/testdata/Keccak256.sol +++ b/crates/lint/testdata/Keccak256.sol @@ -52,6 +52,7 @@ contract AsmKeccak256 { function assemblyHash(uint256 a, uint256 b) public pure returns (bytes32) { //optimized + // forge-lint: disable-next-line(inline-assembly) assembly { mstore(0x00, a) mstore(0x20, b) diff --git a/crates/lint/testdata/Keccak256.stderr b/crates/lint/testdata/Keccak256.stderr index 4203d950d9cba..a81e429e389a1 100644 --- a/crates/lint/testdata/Keccak256.stderr +++ b/crates/lint/testdata/Keccak256.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `mixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Another_MixedCase = 2; │ ━━━━━━━━━━━━━━━━━ help: consider using: `anotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 YetAnother_MixedCase = 3; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `yetAnotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract AsmKeccak256 { │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract OtherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -60,7 +60,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract YetAnotherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -68,7 +68,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 hash = keccak256(abi.encodePacked(a, b, bytes32(bytes20(c)))); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -76,7 +76,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 afterDisabledBlock = keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -84,7 +84,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromCalldata = keccak256(z); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -92,7 +92,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromMemory = keccak256(y); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -100,7 +100,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 lintWithoutFix = keccak256(abi.encodePacked(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -108,7 +108,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -116,7 +116,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 Enabled_MixedCase_Variable; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -124,7 +124,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesNotUseScratchSpace = keccak256(abi.encode(x, y, x, y, x, y)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -132,7 +132,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesUseScratchSpace = keccak256(abi.encode(x)); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -140,5 +140,5 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(doesUseScratchSpace, doesNotUseScratchSpace)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 diff --git a/crates/lint/testdata/MissingZeroCheck.stderr b/crates/lint/testdata/MissingZeroCheck.stderr index b55a902547fcf..81a9179e79c94 100644 --- a/crates/lint/testdata/MissingZeroCheck.stderr +++ b/crates/lint/testdata/MissingZeroCheck.stderr @@ -4,7 +4,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwner(address newOwner) external { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -12,7 +12,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ constructor(address initialOwner) { │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -20,7 +20,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function pay(address payable to) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -28,7 +28,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function lowLevel(address payable to, bytes calldata data) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -36,7 +36,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function withUselessModifier(address a) external doesNothing(a) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -44,7 +44,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaAlias(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -52,7 +52,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaReassign(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -60,7 +60,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaCast(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -68,7 +68,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payViaAlias(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -76,7 +76,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function mixedParams(address a, address b) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -84,7 +84,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function bothSinks(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -92,7 +92,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function ternaryAlias(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -100,7 +100,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payableWrap(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -108,7 +108,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function modifierWithExpr(address a) external nonZero(addrIdentity(a)) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -116,7 +116,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function delegateCallSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -124,7 +124,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkStmt(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -132,7 +132,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkDecl(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -140,7 +140,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function multiHopTaint(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -148,7 +148,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardAfterSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -156,7 +156,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardOnOneBranch(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -164,7 +164,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInForLoop(address a, uint256 n) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -172,7 +172,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInWhileLoop(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -180,5 +180,5 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInTryClause(address a, address payable target) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check diff --git a/crates/lint/testdata/MixedCase.stderr b/crates/lint/testdata/MixedCase.stderr index d290af5cdb5a8..2db30559ba5a6 100644 --- a/crates/lint/testdata/MixedCase.stderr +++ b/crates/lint/testdata/MixedCase.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Variablemixedcase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variablemixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VARIABLE_MIXED_CASE; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VariableMixedCase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 testVAL; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TestVal; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -44,7 +44,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TESTVAL; │ ━━━━━━━ help: consider using: `testval` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -52,7 +52,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function Functionmixedcase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionmixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -60,7 +60,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FUNCTION_MIXED_CASE() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -68,7 +68,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FunctionMixedCase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -76,7 +76,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function function_mixed_case() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -84,7 +84,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantBalance_MixedCase_Enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantBalanceMixedCaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -92,7 +92,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantbalance_mixedcase_enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantbalanceMixedcaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -100,7 +100,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function ERC20_DoSomething() public {} // invalid because of the underscore │ ━━━━━━━━━━━━━━━━━ help: consider using: `erc20DoSomething` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -108,7 +108,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_PARAMS(address addr) external view returns (uint256) {} │ ━━━━━━━━━━ help: consider using: `hasParams` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -116,7 +116,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_NO_RETURN() external view {} │ ━━━━━━━━━━━━━ help: consider using: `hasNoReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -124,7 +124,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_MORE_THAN_ONE_RETURN() external view returns (uint256, uint256) {} │ ━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `hasMoreThanOneReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -132,7 +132,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function NOT_ELEMENTARY_RETURN() external view returns (uint256[] memory) {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `notElementaryReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -140,7 +140,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -148,5 +148,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract MixedCaseTest { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile.stderr b/crates/lint/testdata/MultiContractFile.stderr index c6e4e32a2df55..e25f3d72ad01a 100644 --- a/crates/lint/testdata/MultiContractFile.stderr +++ b/crates/lint/testdata/MultiContractFile.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract A {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract B {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -36,5 +36,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr index 41fc439ea7d1b..1912f16863712 100644 --- a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr +++ b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -20,5 +20,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/NamedStructFields.stderr b/crates/lint/testdata/NamedStructFields.stderr index 6ee2160791cd2..cfb35637176bd 100644 --- a/crates/lint/testdata/NamedStructFields.stderr +++ b/crates/lint/testdata/NamedStructFields.stderr @@ -4,5 +4,5 @@ note[named-struct-fields]: prefer initializing structs with named fields LL │ Person memory person = Person("Alice", 25, address(0)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using named fields: `Person({ name: "Alice", age: 25, wallet: address(0) })` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#named-struct-fields + ╰ help: https://getfoundry.sh/forge/linting/named-struct-fields diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol new file mode 100644 index 0000000000000..bfc993baab794 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 +pragma solidity 0.8.18; //~NOTE: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr new file mode 100644 index 0000000000000..c2c967dee792f --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.18; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol new file mode 100644 index 0000000000000..75bc17988accc --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr new file mode 100644 index 0000000000000..f60361718ba9b --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol new file mode 100644 index 0000000000000..37b06040c33a6 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 +pragma solidity ~0.8.20; //~NOTE: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr new file mode 100644 index 0000000000000..6c46f2478208d --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ~0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.sol b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol new file mode 100644 index 0000000000000..f85a477cc8744 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20 || 0.8.21; //~NOTE: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr new file mode 100644 index 0000000000000..acf6bd7c2d6e0 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20 || 0.8.21; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol new file mode 100644 index 0000000000000..d8fcb7a0eb4b1 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; //~NOTE: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr new file mode 100644 index 0000000000000..5ac221b924c9a --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0 <0.9.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol new file mode 100644 index 0000000000000..fe208e15efb63 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol @@ -0,0 +1,8 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; //~NOTE: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 +pragma solidity ~0.8.0; //~NOTE: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr new file mode 100644 index 0000000000000..e1e5ad7333fb2 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr @@ -0,0 +1,24 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ~0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/Rtlo.stderr b/crates/lint/testdata/Rtlo.stderr index 2c2b53df646e1..93f5bb191532f 100644 --- a/crates/lint/testdata/Rtlo.stderr +++ b/crates/lint/testdata/Rtlo.stderr @@ -4,7 +4,7 @@ warning[rtlo]: U+202A (Left-to-Right Embedding) detected LL │ string public lre = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -12,7 +12,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public lre = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202B (Right-to-Left Embedding) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -20,7 +20,7 @@ warning[rtlo]: U+202B (Right-to-Left Embedding) detected LL │ string public rle = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -28,7 +28,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public rle = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202A (Left-to-Right Embedding) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -36,7 +36,7 @@ warning[rtlo]: U+202A (Left-to-Right Embedding) detected LL │ string public pdf = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -44,7 +44,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public pdf = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202D (Left-to-Right Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -52,7 +52,7 @@ warning[rtlo]: U+202D (Left-to-Right Override) detected LL │ string public lro = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -60,7 +60,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public lro = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -68,7 +68,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ string public rlo = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -76,7 +76,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public rlo = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2066 (Left-to-Right Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -84,7 +84,7 @@ warning[rtlo]: U+2066 (Left-to-Right Isolate) detected LL │ string public lri = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -92,7 +92,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public lri = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2067 (Right-to-Left Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -100,7 +100,7 @@ warning[rtlo]: U+2067 (Right-to-Left Isolate) detected LL │ string public rli = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -108,7 +108,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public rli = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2068 (First Strong Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -116,7 +116,7 @@ warning[rtlo]: U+2068 (First Strong Isolate) detected LL │ string public fsi = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -124,7 +124,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public fsi = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2066 (Left-to-Right Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -132,7 +132,7 @@ warning[rtlo]: U+2066 (Left-to-Right Isolate) detected LL │ string public pdi = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -140,7 +140,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public pdi = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -148,7 +148,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ /* hidden� /* text � */ uint256 inBlockComment; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -156,7 +156,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ /* hidden� /* text � */ uint256 inBlockComment; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -164,7 +164,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ // sneaky� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -172,7 +172,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ // sneaky� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+200E (Left-to-Right Mark) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -180,7 +180,7 @@ warning[rtlo]: U+200E (Left-to-Right Mark) detected LL │ string public marks = unicode"left‎right‏end"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+200F (Right-to-Left Mark) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -188,5 +188,5 @@ warning[rtlo]: U+200F (Right-to-Left Mark) detected LL │ string public marks = unicode"left‎right‏end"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo diff --git a/crates/lint/testdata/RtloCommentsOnly.stderr b/crates/lint/testdata/RtloCommentsOnly.stderr index 88a354432867e..5a7ec9ee6e69d 100644 --- a/crates/lint/testdata/RtloCommentsOnly.stderr +++ b/crates/lint/testdata/RtloCommentsOnly.stderr @@ -4,7 +4,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ // hidden� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -12,7 +12,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ // hidden� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -20,7 +20,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ /* block� comment � end */ │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -28,5 +28,5 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ /* block� comment � end */ │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo diff --git a/crates/lint/testdata/ScreamingSnakeCase.stderr b/crates/lint/testdata/ScreamingSnakeCase.stderr index a740506ed74d8..36305bb268d9a 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.stderr +++ b/crates/lint/testdata/ScreamingSnakeCase.stderr @@ -4,7 +4,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -12,7 +12,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screaming_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -20,7 +20,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant ScreamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -28,7 +28,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant SCREAMING_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -36,7 +36,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -44,7 +44,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screaming_snake_case0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -52,7 +52,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable ScreamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -60,5 +60,5 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable SCREAMING_snake_case_0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE_0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable diff --git a/crates/lint/testdata/StructPascalCase.stderr b/crates/lint/testdata/StructPascalCase.stderr index 1c7bfa13ba84b..255c1c4d5d74b 100644 --- a/crates/lint/testdata/StructPascalCase.stderr +++ b/crates/lint/testdata/StructPascalCase.stderr @@ -4,7 +4,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct _PascalCase { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -12,7 +12,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalCase { │ ━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -20,7 +20,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalcase { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -28,7 +28,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascal_case { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -36,7 +36,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCAL_CASE { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -44,5 +44,5 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCALCASE { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct diff --git a/crates/lint/testdata/TooManyDigits.sol b/crates/lint/testdata/TooManyDigits.sol new file mode 100644 index 0000000000000..a56ad67fe379e --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.sol @@ -0,0 +1,73 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TooManyDigits { + // SHOULD FAIL: plain decimal integer literals with 5+ consecutive zeros. + + uint256 stateA = 1000000000000000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + uint256 stateB = 100000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + function asReturn() public pure returns (uint256) { + return 10000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asComparison(uint256 x) public pure returns (bool) { + return x == 1000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArg(address to) public { + _send(to, 50000000000); //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArraySize() public pure { + uint256[100000] memory _arr; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + // Zero-run in the middle (not just trailing). + uint256 middleZeros = 123000007; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscores that don't actually break up the zero run. + uint256 badGrouping = 1_000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscore right after a single digit, leaving a 5-zero group. + uint256 badGrouping2 = 1_00000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // SHOULD PASS: + + // Boundary: 4 consecutive zeros (one short of the threshold). + uint256 fourZeros = 10000; + + // Uppercase scientific notation. + uint256 sciUpper = 1E18; + + // Scientific notation. + uint256 sci = 1e18; + + // Underscore-separated digit groups. + uint256 grouped = 1_000_000_000_000_000_000; + + // Sub-denominations. + uint256 oneEther = 1 ether; + uint256 oneGwei = 1 gwei; + uint256 fiveMin = 5 minutes; + + // Address literal (distinct AST kind, not flagged). + address adr = 0x1234567890123456789012345678901234567890; + + // Hex literal — intentional zero patterns (mask / padded value). + bytes32 mask = 0x0000000000000000000000000000000000000000000000000000000000000001; + uint256 hexNum = 0x100000; + + // Small literals (< 5 consecutive zeros). + uint256 small1 = 100; + uint256 small2 = 9999; + uint256 small3 = 1234; + uint256 spread = 101010; + + // Boolean literal. + bool flag = true; + + function _send(address, uint256) internal pure {} +} diff --git a/crates/lint/testdata/TooManyDigits.stderr b/crates/lint/testdata/TooManyDigits.stderr new file mode 100644 index 0000000000000..7e21a530776c2 --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.stderr @@ -0,0 +1,72 @@ +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateA = 1000000000000000000; + │ ━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateB = 100000; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return 10000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return x == 1000000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … _send(to, 50000000000); + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … uint256[100000] memory _arr; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 middleZeros = 123000007; + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping = 1_000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping2 = 1_00000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + diff --git a/crates/lint/testdata/TxOrigin.sol b/crates/lint/testdata/TxOrigin.sol new file mode 100644 index 0000000000000..9728a7e528e5b --- /dev/null +++ b/crates/lint/testdata/TxOrigin.sol @@ -0,0 +1,65 @@ +//@compile-flags: --only-lint tx-origin +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TxOrigin { + address public owner; + mapping(address => bool) public allowed; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(tx.origin == owner, "not owner"); //~WARN: `tx.origin` should not be used for authorization + _; + } + + function guardedByIf() external view { + if (tx.origin != owner) { //~WARN: `tx.origin` should not be used for authorization + revert("not owner"); + } + } + + function guardedByPredicate() external view { + assert(isOwner(tx.origin)); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByWhile() external view { + while (tx.origin == owner) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByFor() external view { + for (; tx.origin == owner;) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByDoWhile() external view { + do { + } while (tx.origin == owner); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByMapping() external view { + require(allowed[tx.origin], "not allowed"); //~WARN: `tx.origin` should not be used for authorization + require(allowed[tx.origin] == true, "not allowed"); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByTernary() external view { + require(tx.origin == owner ? true : false, "not owner"); //~WARN: `tx.origin` should not be used for authorization + } + + function readForLogging() external view returns (address) { + return tx.origin; + } + + function explicitSenderCheck() external view { + require(msg.sender == owner, "not owner"); + } + + function isOwner(address account) internal view returns (bool) { + return account == owner; + } +} diff --git a/crates/lint/testdata/TxOrigin.stderr b/crates/lint/testdata/TxOrigin.stderr new file mode 100644 index 0000000000000..7c2e70225b76d --- /dev/null +++ b/crates/lint/testdata/TxOrigin.stderr @@ -0,0 +1,72 @@ +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner, "not owner"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ if (tx.origin != owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ assert(isOwner(tx.origin)); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ while (tx.origin == owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ for (; tx.origin == owner;) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ } while (tx.origin == owner); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin], "not allowed"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin] == true, "not allowed"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner ? true : false, "not owner"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + diff --git a/crates/lint/testdata/UncheckedCall.stderr b/crates/lint/testdata/UncheckedCall.stderr index afb8ade4ea89b..8a8a9fa9b5e17 100644 --- a/crates/lint/testdata/UncheckedCall.stderr +++ b/crates/lint/testdata/UncheckedCall.stderr @@ -4,7 +4,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call(data); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -12,7 +12,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call{value: value}(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -20,7 +20,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.delegatecall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -28,7 +28,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.staticcall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -36,7 +36,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target1.call(""); │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -44,7 +44,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target2.delegatecall(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -52,7 +52,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, bytes memory data) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -60,5 +60,5 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, existingData) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call diff --git a/crates/lint/testdata/UncheckedTransferERC20.stderr b/crates/lint/testdata/UncheckedTransferERC20.stderr index 733d22ce610d1..2c2caa69e7215 100644 --- a/crates/lint/testdata/UncheckedTransferERC20.stderr +++ b/crates/lint/testdata/UncheckedTransferERC20.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Wrapper { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransfer { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library Currency { │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransferUsingCurrencyLib { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -44,7 +44,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ IERC20(address(token)).transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -52,7 +52,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -60,7 +60,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -68,7 +68,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -76,7 +76,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -84,5 +84,5 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer diff --git a/crates/lint/testdata/UnsafeCheatcodes.stderr b/crates/lint/testdata/UnsafeCheatcodes.stderr index e66a4d72c70de..5b8b429942e80 100644 --- a/crates/lint/testdata/UnsafeCheatcodes.stderr +++ b/crates/lint/testdata/UnsafeCheatcodes.stderr @@ -4,7 +4,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -12,7 +12,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -20,7 +20,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readLine("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -28,7 +28,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeFile("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -36,7 +36,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeLine("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -44,7 +44,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.removeFile("test.txt"); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.closeFile("test.txt"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -60,7 +60,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -68,7 +68,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.deriveKey("mnemonic", 0); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -76,7 +76,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ bytes memory result = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(new string[](1)); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -100,5 +100,5 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode diff --git a/crates/lint/testdata/UnsafeTypecast.stderr b/crates/lint/testdata/UnsafeTypecast.stderr index b3e0334d63d43..d909b90973e00 100644 --- a/crates/lint/testdata/UnsafeTypecast.stderr +++ b/crates/lint/testdata/UnsafeTypecast.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UnsafeTypecast { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract Repros { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -26,7 +26,7 @@ LL │ uint248 b = uint248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -40,7 +40,7 @@ LL │ uint240 c = uint240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -54,7 +54,7 @@ LL │ uint232 d = uint232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -68,7 +68,7 @@ LL │ uint224 e = uint224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -82,7 +82,7 @@ LL │ uint216 f = uint216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -96,7 +96,7 @@ LL │ uint208 g = uint208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -110,7 +110,7 @@ LL │ uint200 h = uint200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -124,7 +124,7 @@ LL │ uint192 i = uint192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -138,7 +138,7 @@ LL │ uint184 j = uint184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -152,7 +152,7 @@ LL │ uint176 k = uint176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -166,7 +166,7 @@ LL │ uint168 l = uint168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -180,7 +180,7 @@ LL │ uint160 m = uint160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -194,7 +194,7 @@ LL │ uint152 n = uint152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -208,7 +208,7 @@ LL │ uint144 o = uint144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -222,7 +222,7 @@ LL │ uint136 p = uint136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -236,7 +236,7 @@ LL │ uint128 q = uint128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -250,7 +250,7 @@ LL │ uint120 r = uint120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -264,7 +264,7 @@ LL │ uint112 s = uint112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -278,7 +278,7 @@ LL │ uint104 t = uint104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -292,7 +292,7 @@ LL │ uint96 u = uint96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -306,7 +306,7 @@ LL │ uint88 v = uint88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -320,7 +320,7 @@ LL │ uint80 w = uint80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -334,7 +334,7 @@ LL │ uint72 x = uint72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -348,7 +348,7 @@ LL │ uint64 y = uint64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -362,7 +362,7 @@ LL │ uint56 z = uint56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -376,7 +376,7 @@ LL │ uint48 A = uint48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -390,7 +390,7 @@ LL │ uint40 B = uint40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -404,7 +404,7 @@ LL │ uint32 C = uint32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -418,7 +418,7 @@ LL │ uint24 D = uint24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -432,7 +432,7 @@ LL │ uint16 E = uint16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -446,7 +446,7 @@ LL │ uint8 F = uint8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -460,7 +460,7 @@ LL │ int248 b = int248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -474,7 +474,7 @@ LL │ int240 c = int240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -488,7 +488,7 @@ LL │ int232 d = int232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -502,7 +502,7 @@ LL │ int224 e = int224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -516,7 +516,7 @@ LL │ int216 f = int216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -530,7 +530,7 @@ LL │ int208 g = int208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -544,7 +544,7 @@ LL │ int200 h = int200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -558,7 +558,7 @@ LL │ int192 i = int192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -572,7 +572,7 @@ LL │ int184 j = int184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -586,7 +586,7 @@ LL │ int176 k = int176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -600,7 +600,7 @@ LL │ int168 l = int168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -614,7 +614,7 @@ LL │ int160 m = int160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -628,7 +628,7 @@ LL │ int152 n = int152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -642,7 +642,7 @@ LL │ int144 o = int144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -656,7 +656,7 @@ LL │ int136 p = int136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -670,7 +670,7 @@ LL │ int128 q = int128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -684,7 +684,7 @@ LL │ int120 r = int120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -698,7 +698,7 @@ LL │ int112 s = int112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -712,7 +712,7 @@ LL │ int104 t = int104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -726,7 +726,7 @@ LL │ int96 u = int96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -740,7 +740,7 @@ LL │ int88 v = int88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -754,7 +754,7 @@ LL │ int80 w = int80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -768,7 +768,7 @@ LL │ int72 x = int72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -782,7 +782,7 @@ LL │ int64 y = int64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -796,7 +796,7 @@ LL │ int56 z = int56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -810,7 +810,7 @@ LL │ int48 A = int48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -824,7 +824,7 @@ LL │ int40 B = int40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -838,7 +838,7 @@ LL │ int32 C = int32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -852,7 +852,7 @@ LL │ int24 D = int24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -866,7 +866,7 @@ LL │ int16 E = int16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -880,7 +880,7 @@ LL │ int8 F = int8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -894,7 +894,7 @@ LL │ bytes31 b = bytes31(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -908,7 +908,7 @@ LL │ bytes30 c = bytes30(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -922,7 +922,7 @@ LL │ bytes29 d = bytes29(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -936,7 +936,7 @@ LL │ bytes28 e = bytes28(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -950,7 +950,7 @@ LL │ bytes27 f = bytes27(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -964,7 +964,7 @@ LL │ bytes26 g = bytes26(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -978,7 +978,7 @@ LL │ bytes25 h = bytes25(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -992,7 +992,7 @@ LL │ bytes24 i = bytes24(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1006,7 +1006,7 @@ LL │ bytes23 j = bytes23(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1020,7 +1020,7 @@ LL │ bytes22 k = bytes22(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1034,7 +1034,7 @@ LL │ bytes21 l = bytes21(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1048,7 +1048,7 @@ LL │ bytes20 m = bytes20(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1062,7 +1062,7 @@ LL │ bytes19 n = bytes19(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1076,7 +1076,7 @@ LL │ bytes18 o = bytes18(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1090,7 +1090,7 @@ LL │ bytes17 p = bytes17(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1104,7 +1104,7 @@ LL │ bytes16 q = bytes16(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1118,7 +1118,7 @@ LL │ bytes15 r = bytes15(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1132,7 +1132,7 @@ LL │ bytes14 s = bytes14(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1146,7 +1146,7 @@ LL │ bytes13 t = bytes13(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1160,7 +1160,7 @@ LL │ bytes12 u = bytes12(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1174,7 +1174,7 @@ LL │ bytes11 v = bytes11(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1188,7 +1188,7 @@ LL │ bytes10 w = bytes10(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1202,7 +1202,7 @@ LL │ bytes9 x = bytes9(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1216,7 +1216,7 @@ LL │ bytes8 y = bytes8(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1230,7 +1230,7 @@ LL │ bytes7 z = bytes7(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1244,7 +1244,7 @@ LL │ bytes6 A = bytes6(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1258,7 +1258,7 @@ LL │ bytes5 B = bytes5(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1272,7 +1272,7 @@ LL │ bytes4 C = bytes4(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1286,7 +1286,7 @@ LL │ bytes3 D = bytes3(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1300,7 +1300,7 @@ LL │ bytes2 E = bytes2(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1314,7 +1314,7 @@ LL │ bytes1 F = bytes1(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1328,7 +1328,7 @@ LL │ int256 b = int256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1342,7 +1342,7 @@ LL │ int248 d = int248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1356,7 +1356,7 @@ LL │ int240 f = int240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1370,7 +1370,7 @@ LL │ int232 h = int232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1384,7 +1384,7 @@ LL │ int224 j = int224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1398,7 +1398,7 @@ LL │ int216 l = int216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1412,7 +1412,7 @@ LL │ int208 n = int208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1426,7 +1426,7 @@ LL │ int200 p = int200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1440,7 +1440,7 @@ LL │ int192 r = int192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1454,7 +1454,7 @@ LL │ int184 t = int184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1468,7 +1468,7 @@ LL │ int176 v = int176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1482,7 +1482,7 @@ LL │ int168 x = int168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1496,7 +1496,7 @@ LL │ int160 z = int160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1510,7 +1510,7 @@ LL │ int152 B = int152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1524,7 +1524,7 @@ LL │ int144 D = int144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1538,7 +1538,7 @@ LL │ int136 F = int136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1552,7 +1552,7 @@ LL │ int128 H = int128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1566,7 +1566,7 @@ LL │ int120 J = int120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1580,7 +1580,7 @@ LL │ int112 L = int112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1594,7 +1594,7 @@ LL │ int104 N = int104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1608,7 +1608,7 @@ LL │ int96 P = int96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1622,7 +1622,7 @@ LL │ int88 R = int88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1636,7 +1636,7 @@ LL │ int80 T = int80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1650,7 +1650,7 @@ LL │ int72 V = int72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1664,7 +1664,7 @@ LL │ int64 X = int64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1678,7 +1678,7 @@ LL │ int56 Z = int56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1692,7 +1692,7 @@ LL │ int48 BB = int48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1706,7 +1706,7 @@ LL │ int40 DD = int40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1720,7 +1720,7 @@ LL │ int32 FF = int32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1734,7 +1734,7 @@ LL │ int24 HH = int24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1748,7 +1748,7 @@ LL │ int16 JJ = int16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1762,7 +1762,7 @@ LL │ int8 LL = int8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1776,7 +1776,7 @@ LL │ uint256 b = uint256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1790,7 +1790,7 @@ LL │ uint248 d = uint248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1804,7 +1804,7 @@ LL │ uint240 f = uint240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1818,7 +1818,7 @@ LL │ uint232 h = uint232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1832,7 +1832,7 @@ LL │ uint224 j = uint224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1846,7 +1846,7 @@ LL │ uint216 l = uint216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1860,7 +1860,7 @@ LL │ uint208 n = uint208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1874,7 +1874,7 @@ LL │ uint200 p = uint200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1888,7 +1888,7 @@ LL │ uint192 r = uint192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1902,7 +1902,7 @@ LL │ uint184 t = uint184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1916,7 +1916,7 @@ LL │ uint176 v = uint176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1930,7 +1930,7 @@ LL │ uint168 x = uint168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1944,7 +1944,7 @@ LL │ uint160 z = uint160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1958,7 +1958,7 @@ LL │ uint152 B = uint152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1972,7 +1972,7 @@ LL │ uint144 D = uint144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1986,7 +1986,7 @@ LL │ uint136 F = uint136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2000,7 +2000,7 @@ LL │ uint128 H = uint128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2014,7 +2014,7 @@ LL │ uint120 J = uint120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2028,7 +2028,7 @@ LL │ uint112 L = uint112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2042,7 +2042,7 @@ LL │ uint104 N = uint104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2056,7 +2056,7 @@ LL │ uint96 P = uint96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2070,7 +2070,7 @@ LL │ uint88 R = uint88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2084,7 +2084,7 @@ LL │ uint80 T = uint80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2098,7 +2098,7 @@ LL │ uint72 V = uint72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2112,7 +2112,7 @@ LL │ uint64 X = uint64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2126,7 +2126,7 @@ LL │ uint56 Z = uint56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2140,7 +2140,7 @@ LL │ uint48 BB = uint48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2154,7 +2154,7 @@ LL │ uint40 DD = uint40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2168,7 +2168,7 @@ LL │ uint32 FF = uint32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2182,7 +2182,7 @@ LL │ uint24 HH = uint24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2196,7 +2196,7 @@ LL │ uint16 JJ = uint16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2210,7 +2210,7 @@ LL │ uint8 LL = uint8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2224,7 +2224,7 @@ LL │ bytes32 dataSlice = bytes32(data); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2238,7 +2238,7 @@ LL │ bytes32 strSlice = bytes32(bytes(str)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2252,7 +2252,7 @@ LL │ uint128 aPlusB = uint128(int128(uint128(a)) + b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2266,7 +2266,7 @@ LL │ uint64 unsafe = uint64(aPlusB); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2280,7 +2280,7 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2294,5 +2294,5 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast diff --git a/crates/lint/testdata/UnusedStateVariables.stderr b/crates/lint/testdata/UnusedStateVariables.stderr index ecc9e87efc105..92a0082bb8293 100644 --- a/crates/lint/testdata/UnusedStateVariables.stderr +++ b/crates/lint/testdata/UnusedStateVariables.stderr @@ -4,7 +4,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address usedInBoth; │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -12,7 +12,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 unused; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -20,7 +20,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 unused; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -28,7 +28,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 firstUnused; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -36,5 +36,5 @@ note[unused-state-variables]: state variable is never used LL │ uint256 secondUnused; │ ━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr index 5e1bf754e60e4..dc5514c2d5e98 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -9,7 +9,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforePlaceholder() { @@ -35,7 +35,7 @@ LL │ ┃ checkInternal(msg.sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleAfterPlaceholder() { @@ -62,7 +62,7 @@ LL │ ┃ checkPublic(sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforeAfterPlaceholder(address sender) { @@ -91,7 +91,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwner() { @@ -113,7 +113,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRole(bytes32 role) { @@ -135,7 +135,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrOpenRole(bytes32 role) { @@ -157,7 +157,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrAdmin(bytes32 role, address admin) { @@ -180,7 +180,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier assign(address sender) { @@ -204,7 +204,7 @@ LL │ ┃ sender; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier uncheckedBlock(address sender) { @@ -228,7 +228,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier emitEvent(address sender) { @@ -250,7 +250,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwnerContract(address sender) { diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 9970616900e6b..15363a6a40bb0 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -23,10 +23,10 @@ alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"] } -op-alloy-rpc-types.workspace = true -alloy-op-evm.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +alloy-op-evm = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } revm.workspace = true serde_json.workspace = true serde = { version = "1.0", features = ["derive"] } @@ -34,3 +34,12 @@ derive_more.workspace = true tempo-primitives.workspace = true tempo-alloy.workspace = true tempo-revm.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", +] diff --git a/crates/primitives/src/network/mod.rs b/crates/primitives/src/network/mod.rs index 7000b580b6a73..0b840185b0383 100644 --- a/crates/primitives/src/network/mod.rs +++ b/crates/primitives/src/network/mod.rs @@ -1,12 +1,20 @@ use alloy_network::Network; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; use alloy_provider::fillers::{ BlobGasFiller, ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, }; +#[cfg(feature = "optimism")] +pub use optimism::FoundryTransactionResponse; pub use receipt::*; +/// Default JSON-RPC transaction response when the `optimism` feature is disabled. +#[cfg(not(feature = "optimism"))] +pub type FoundryTransactionResponse = alloy_rpc_types_eth::Transaction; + /// Foundry network type. /// /// This network type supports Foundry-specific transaction types, including @@ -36,7 +44,7 @@ impl Network for FoundryNetwork { type TransactionRequest = crate::FoundryTransactionRequest; - type TransactionResponse = op_alloy_rpc_types::Transaction; + type TransactionResponse = FoundryTransactionResponse; type ReceiptResponse = crate::FoundryTxReceipt; diff --git a/crates/primitives/src/network/optimism.rs b/crates/primitives/src/network/optimism.rs new file mode 100644 index 0000000000000..aff30a755663f --- /dev/null +++ b/crates/primitives/src/network/optimism.rs @@ -0,0 +1,47 @@ +//! OP-stack-specific helpers and type aliases used by [`super::FoundryNetwork`] and +//! [`super::FoundryTxReceipt`]. + +use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_primitives::U64; +use alloy_rpc_types::Log; +use alloy_serde::OtherFields; +use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; + +use crate::FoundryReceiptEnvelope; + +/// JSON-RPC transaction response type used by [`super::FoundryNetwork`]. +pub type FoundryTransactionResponse = op_alloy_rpc_types::Transaction; + +/// Build a [`FoundryReceiptEnvelope::Deposit`] from a `ReceiptWithBloom` plus the OP +/// deposit-specific fields decoded from the [`OtherFields`] of an `AnyTransactionReceipt`. +pub(super) fn build_deposit_receipt_envelope( + receipt_with_bloom: ReceiptWithBloom>, + other: &OtherFields, +) -> FoundryReceiptEnvelope { + // These fields may not be present in all receipts, so missing/invalid values are None. + let deposit_nonce = other + .get_deserialized::("depositNonce") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + let deposit_receipt_version = other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + + FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: Receipt { + status: alloy_consensus::Eip658Value::Eip658(receipt_with_bloom.status()), + cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), + logs: receipt_with_bloom.receipt.logs, + }, + deposit_nonce, + deposit_receipt_version, + }, + logs_bloom: receipt_with_bloom.logs_bloom, + }) +} diff --git a/crates/primitives/src/network/receipt.rs b/crates/primitives/src/network/receipt.rs index b727b4c39b345..6b01f9eaa9ee9 100644 --- a/crates/primitives/src/network/receipt.rs +++ b/crates/primitives/src/network/receipt.rs @@ -1,13 +1,13 @@ -use alloy_consensus::{Receipt, TxReceipt}; use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt, ReceiptResponse}; -use alloy_primitives::{Address, B256, BlockHash, TxHash, U64}; +use alloy_primitives::{Address, B256, BlockHash, TxHash}; use alloy_rpc_types::{ConversionError, Log, TransactionReceipt}; use alloy_serde::WithOtherFields; use derive_more::AsRef; -use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; use serde::{Deserialize, Serialize}; use tempo_primitives::TEMPO_TX_TYPE_ID; +#[cfg(feature = "optimism")] +use super::optimism::build_deposit_receipt_envelope; use crate::FoundryReceiptEnvelope; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AsRef)] @@ -144,38 +144,8 @@ impl TryFrom for FoundryTxReceipt { 0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), 0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), TEMPO_TX_TYPE_ID => FoundryReceiptEnvelope::Tempo(receipt_with_bloom), - 0x7E => { - // Construct the deposit receipt, extracting optional deposit fields - // These fields may not be present in all receipts, so missing/invalid - // values are None - let deposit_nonce = other - .get_deserialized::("depositNonce") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - let deposit_receipt_version = other - .get_deserialized::("depositReceiptVersion") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - - FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { - receipt: OpDepositReceipt { - inner: Receipt { - status: alloy_consensus::Eip658Value::Eip658( - receipt_with_bloom.status(), - ), - cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), - logs: receipt_with_bloom.receipt.logs, - }, - deposit_nonce, - deposit_receipt_version, - }, - logs_bloom: receipt_with_bloom.logs_bloom, - }) - } + #[cfg(feature = "optimism")] + 0x7E => build_deposit_receipt_envelope(receipt_with_bloom, &other), _ => { let tx_type = r#type; return Err(ConversionError::Custom(format!( diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index ab4aafcc294c0..0a009a1931f06 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -1,6 +1,7 @@ +#[cfg(feature = "optimism")] +use alloy_consensus::{Sealed, Transaction as _}; use alloy_consensus::{ - Sealed, Signed, Transaction as _, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, - TxLegacy, TxType, Typed2718, + Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType, Typed2718, crypto::RecoveryError, transaction::{ SignerRecoverable, TxEip7702, TxHashRef, @@ -9,14 +10,10 @@ use alloy_consensus::{ }; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, TxHash}; use alloy_rpc_types::ConversionError; -use op_alloy_consensus::{ - DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait, POST_EXEC_TX_TYPE_ID, TxDeposit, - TxPostExec, -}; -use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec}; use revm::context::TxEnv; use tempo_primitives::{AASigned, TempoTransaction}; use tempo_revm::TempoTxEnv; @@ -57,9 +54,11 @@ pub enum FoundryTxEnvelope { /// OP stack deposit transaction. /// /// See . + #[cfg(feature = "optimism")] #[envelope(ty = 126)] Deposit(Sealed), /// OP stack post-execution synthetic transaction. + #[cfg(feature = "optimism")] #[envelope(ty = 0x7D)] PostExec(Sealed), /// Tempo transaction type. @@ -80,7 +79,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)), Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)), Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)), + #[cfg(feature = "optimism")] Self::Deposit(_) => Err(self), + #[cfg(feature = "optimism")] Self::PostExec(_) => Err(self), Self::Tempo(_) => Err(self), } @@ -109,7 +110,9 @@ impl FoundryTxEnvelope { Self::Eip1559(t) => *t.hash(), Self::Eip4844(t) => *t.hash(), Self::Eip7702(t) => *t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.tx_hash(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.tx_hash(), Self::Tempo(t) => *t.hash(), } @@ -128,7 +131,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => tx.recover_signer()?, Self::Eip4844(tx) => tx.recover_signer()?, Self::Eip7702(tx) => tx.recover_signer()?, + #[cfg(feature = "optimism")] Self::Deposit(tx) => tx.from, + #[cfg(feature = "optimism")] Self::PostExec(tx) => tx.inner().signer_address(), Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?, }) @@ -143,7 +148,9 @@ impl TxHashRef for FoundryTxEnvelope { Self::Eip1559(t) => t.hash(), Self::Eip4844(t) => t.hash(), Self::Eip7702(t) => t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.hash_ref(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.hash_ref(), Self::Tempo(t) => t.hash(), } @@ -160,23 +167,6 @@ impl SignerRecoverable for FoundryTxEnvelope { } } -impl OpTransactionTrait for FoundryTxEnvelope { - fn is_deposit(&self) -> bool { - matches!(self, Self::Deposit(_)) - } - - fn as_deposit(&self) -> Option<&Sealed> { - match self { - Self::Deposit(tx) => Some(tx), - _ => None, - } - } - - fn as_post_exec(&self) -> Option<&Sealed> { - if let Self::PostExec(tx) = self { Some(tx) } else { None } - } -} - impl TryFrom for TxEnvelope { type Error = FoundryTxEnvelope; @@ -197,19 +187,6 @@ impl From for FoundryTxEnvelope { } } -impl From for FoundryTxEnvelope { - fn from(tx: op_alloy_consensus::OpTxEnvelope) -> Self { - match tx { - op_alloy_consensus::OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), - op_alloy_consensus::OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), - op_alloy_consensus::OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), - op_alloy_consensus::OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), - op_alloy_consensus::OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), - op_alloy_consensus::OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), - } - } -} - impl From for FoundryTxEnvelope { fn from(tx: tempo_primitives::TempoTxEnvelope) -> Self { match tx { @@ -236,33 +213,50 @@ impl TryFrom for FoundryTxEnvelope { TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)), TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)), }, - AnyTxEnvelope::Unknown(mut tx) => { - // Try to convert to deposit transaction - if tx.ty() == DEPOSIT_TX_TYPE_ID { - tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); - let deposit_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize deposit tx: {e}" - )) - })?; - - return Ok(Self::Deposit(Sealed::new(deposit_tx))); + AnyTxEnvelope::Unknown(tx) => { + #[cfg(feature = "optimism")] + { + let mut tx = tx; + let _ = from; + // Try to convert to deposit transaction + if tx.ty() == DEPOSIT_TX_TYPE_ID { + tx.inner + .fields + .insert("from".to_string(), serde_json::to_value(from).unwrap()); + let deposit_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize deposit tx: {e}" + )) + })?; + + return Ok(Self::Deposit(Sealed::new(deposit_tx))); + } + + if tx.ty() == POST_EXEC_TX_TYPE_ID { + let post_exec_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize post-exec tx: {e}" + )) + })?; + + return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + } + + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - if tx.ty() == POST_EXEC_TX_TYPE_ID { - let post_exec_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize post-exec tx: {e}" - )) - })?; - - return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + #[cfg(not(feature = "optimism"))] + { + let _ = from; + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - let tx_type = tx.ty(); - Err(ConversionError::Custom(format!("Unknown transaction type: 0x{tx_type:02X}"))) } } } @@ -276,6 +270,7 @@ impl FromRecoveredTx for TxEnv { FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -288,6 +283,7 @@ impl FromRecoveredTx for TxEnv { ..Default::default() } } + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -303,63 +299,6 @@ impl FromRecoveredTx for TxEnv { } } -impl FromRecoveredTx for OpTransaction { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: None, deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl FromTxWithEncoded for TxEnv { fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { Self::from_recovered_tx(tx, sender) @@ -384,7 +323,9 @@ impl FromRecoveredTx for TempoTxEnv { FoundryTxEnvelope::Eip7702(signed_tx) => { Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) } + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => unreachable!("Post-exec tx in Tempo context"), FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller), } @@ -397,75 +338,6 @@ impl FromTxWithEncoded for TempoTxEnv { } } -impl FromRecoveredTx for OpTx { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - Self(OpTransaction::::from_recovered_tx(tx, caller)) - } -} - -impl FromTxWithEncoded for OpTx { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) - } -} - -impl FromTxWithEncoded for OpTransaction { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: Some(encoded), deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl std::fmt::Display for FoundryTxType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -474,7 +346,9 @@ impl std::fmt::Display for FoundryTxType { Self::Eip1559 => write!(f, "eip1559"), Self::Eip4844 => write!(f, "eip4844"), Self::Eip7702 => write!(f, "eip7702"), + #[cfg(feature = "optimism")] Self::Deposit => write!(f, "deposit"), + #[cfg(feature = "optimism")] Self::PostExec => write!(f, "post-exec"), Self::Tempo => write!(f, "tempo"), } @@ -501,7 +375,9 @@ impl From for FoundryTypedTx { FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()), FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()), FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => Self::PostExec(sealed_tx.into_inner()), FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()), } @@ -609,28 +485,6 @@ mod tests { assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); } - #[test] - fn test_decode_encode_deposit_tx() { - // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" - .parse::() - .unwrap(); - - // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let raw_tx = alloy_primitives::hex::decode( - "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", - ) - .unwrap(); - let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); - - let mut encoded = Vec::new(); - dep_tx.encode_2718(&mut encoded); - - assert_eq!(raw_tx, encoded); - - assert_eq!(tx_hash, dep_tx.hash()); - } - #[test] fn can_recover_sender_not_normalized() { let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); @@ -707,11 +561,6 @@ mod tests { assert_eq!(tx_env.caller, sender); assert_eq!(tx_env.gas_limit, 0x5208); assert_eq!(tx_env.gas_price, 1); - - // Test OpTransaction conversion via FromRecoveredTx trait - let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); - assert_eq!(op_tx.base.caller, sender); - assert_eq!(op_tx.base.gas_limit, 0x5208); } // Test vector from Tempo testnet: diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 7dccf3e30752f..18f39c437bfbc 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,7 +1,11 @@ mod envelope; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; mod request; pub use envelope::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; +#[cfg(feature = "optimism")] +pub use optimism::get_deposit_tx_parts; pub use receipt::FoundryReceiptEnvelope; -pub use request::{FoundryTransactionRequest, get_deposit_tx_parts}; +pub use request::FoundryTransactionRequest; diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs new file mode 100644 index 0000000000000..5952b5d6a9020 --- /dev/null +++ b/crates/primitives/src/transaction/optimism.rs @@ -0,0 +1,300 @@ +//! OP-stack-specific impls for [`FoundryTxEnvelope`] and [`FoundryTransactionRequest`]. + +use alloy_consensus::{Sealed, Transaction as _, Typed2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_op_evm::OpTx; +use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_serde::OtherFields; +use op_alloy_consensus::{ + OpDepositReceipt, OpDepositReceiptWithBloom, OpTransaction as OpTransactionTrait, OpTxEnvelope, + TxDeposit, TxPostExec, +}; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +use revm::context::TxEnv; + +use super::{FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope}; + +impl OpTransactionTrait for FoundryTxEnvelope { + fn is_deposit(&self) -> bool { + matches!(self, Self::Deposit(_)) + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + Self::Deposit(tx) => Some(tx), + _ => None, + } + } + + fn as_post_exec(&self) -> Option<&Sealed> { + if let Self::PostExec(tx) = self { Some(tx) } else { None } + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: OpTxEnvelope) -> Self { + match tx { + OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), + OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), + OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), + } + } +} + +impl FromRecoveredTx for OpTransaction { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: None, deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + Self(OpTransaction::::from_recovered_tx(tx, caller)) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) + } +} + +impl FromTxWithEncoded for OpTransaction { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: Some(encoded), deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: op_alloy_rpc_types::Transaction) -> Self { + tx.inner.into_inner().into() + } +} + +/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields. +pub fn get_deposit_tx_parts( + other: &OtherFields, +) -> Result> { + let mut missing = Vec::new(); + let source_hash = + other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("sourceHash"); + Default::default() + }, + ); + let mint = other + .get_deserialized::("mint") + .transpose() + .unwrap_or_else(|_| { + missing.push("mint"); + Default::default() + }) + .map(|value| value.to::()); + let is_system_transaction = + other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("isSystemTx"); + Default::default() + }, + ); + if missing.is_empty() { + Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) + } else { + Err(missing) + } +} + +/// OP-stack-specific accessors on [`FoundryReceiptEnvelope`]. +impl FoundryReceiptEnvelope { + /// Return the receipt's deposit_nonce if it is a deposit receipt. + pub fn deposit_nonce(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_nonce) + } + + /// Return the receipt's deposit version if it is a deposit receipt. + pub fn deposit_receipt_version(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { + match self { + Self::Deposit(t) => Some(t), + _ => None, + } + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { + match self { + Self::Deposit(t) => Some(&t.receipt), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use alloy_network::eip2718::Encodable2718; + use alloy_primitives::TxHash; + use alloy_rlp::Decodable; + + use super::*; + + #[test] + fn test_from_recovered_tx_legacy_op() { + use alloy_consensus::transaction::SignerRecoverable; + + let tx = r#" + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gas": "0x5208", + "gasPrice": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x1", + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "v": "0x1b", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + let sender = typed_tx.recover_signer().unwrap(); + + // Test OpTransaction conversion via FromRecoveredTx trait + let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); + assert_eq!(op_tx.base.caller, sender); + assert_eq!(op_tx.base.gas_limit, 0x5208); + } + + #[test] + fn test_decode_encode_deposit_tx() { + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); + + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + let mut encoded = Vec::new(); + dep_tx.encode_2718(&mut encoded); + + assert_eq!(raw_tx, encoded); + + assert_eq!(tx_hash, dep_tx.hash()); + } +} diff --git a/crates/primitives/src/transaction/receipt.rs b/crates/primitives/src/transaction/receipt.rs index fe209fc72c907..78bbcd1efa2fb 100644 --- a/crates/primitives/src/transaction/receipt.rs +++ b/crates/primitives/src/transaction/receipt.rs @@ -8,6 +8,7 @@ use alloy_network::eip2718::{ use alloy_primitives::{Bloom, Log, TxHash, logs_bloom}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, bytes}; use alloy_rpc_types::{BlockNumHash, trace::otterscan::OtsReceipt}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{ DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, POST_EXEC_TX_TYPE_ID, }; @@ -29,8 +30,10 @@ pub enum FoundryReceiptEnvelope { Eip4844(ReceiptWithBloom>), #[serde(rename = "0x4", alias = "0x04")] Eip7702(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7D", alias = "0x7d")] PostExec(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7E", alias = "0x7e")] Deposit(OpDepositReceiptWithBloom), #[serde(rename = "0x76")] @@ -44,7 +47,8 @@ impl FoundryReceiptEnvelope { cumulative_gas_used: u64, logs: impl IntoIterator, tx_type: FoundryTxType, - deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_receipt_version: Option, ) -> Self { let logs = logs.into_iter().collect::>(); @@ -67,9 +71,11 @@ impl FoundryReceiptEnvelope { FoundryTxType::Eip7702 => { Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => { Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { let inner = OpDepositReceiptWithBloom { receipt: OpDepositReceipt { @@ -112,13 +118,18 @@ impl FoundryReceiptEnvelope { removed: false, }) .collect::>(); + #[cfg(feature = "optimism")] + let (deposit_nonce, deposit_receipt_version) = + (self.deposit_nonce(), self.deposit_receipt_version()); + #[cfg(not(feature = "optimism"))] + let (deposit_nonce, deposit_receipt_version) = (None, None); FoundryReceiptEnvelope::::from_parts( self.status(), self.cumulative_gas_used(), logs, self.tx_type(), - self.deposit_nonce(), - self.deposit_receipt_version(), + deposit_nonce, + deposit_receipt_version, ) } } @@ -132,7 +143,9 @@ impl FoundryReceiptEnvelope { Self::Eip1559(_) => FoundryTxType::Eip1559, Self::Eip4844(_) => FoundryTxType::Eip4844, Self::Eip7702(_) => FoundryTxType::Eip7702, + #[cfg(feature = "optimism")] Self::PostExec(_) => FoundryTxType::PostExec, + #[cfg(feature = "optimism")] Self::Deposit(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -158,8 +171,12 @@ impl FoundryReceiptEnvelope { Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)), Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)), Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)), + #[cfg(feature = "optimism")] Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)), - Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(r.map_receipt(|r| r.map_logs(f))), + #[cfg(feature = "optimism")] + Self::Deposit(r) => FoundryReceiptEnvelope::Deposit( + r.map_receipt(|r: OpDepositReceipt| r.map_logs(f)), + ), Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)), } } @@ -182,38 +199,14 @@ impl FoundryReceiptEnvelope { Self::Eip1559(t) => &t.logs_bloom, Self::Eip4844(t) => &t.logs_bloom, Self::Eip7702(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::PostExec(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.logs_bloom, Self::Tempo(t) => &t.logs_bloom, } } - /// Return the receipt's deposit_nonce if it is a deposit receipt. - pub fn deposit_nonce(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_nonce) - } - - /// Return the receipt's deposit version if it is a deposit receipt. - pub fn deposit_receipt_version(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { - match self { - Self::Deposit(t) => Some(t), - _ => None, - } - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { - match self { - Self::Deposit(t) => Some(&t.receipt), - _ => None, - } - } - /// Consumes the type and returns the underlying [`Receipt`]. pub fn into_receipt(self) -> Receipt { match self { @@ -222,8 +215,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => t.receipt.into_inner(), } } @@ -236,8 +231,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => &t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => &t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.receipt.inner, } } @@ -287,7 +284,9 @@ impl Encodable for FoundryReceiptEnvelope { Self::Eip1559(r) => r.length() + 1, Self::Eip4844(r) => r.length() + 1, Self::Eip7702(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::PostExec(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::Deposit(r) => r.length() + 1, Self::Tempo(r) => r.length() + 1, _ => unreachable!("receipt already matched"), @@ -314,11 +313,13 @@ impl Encodable for FoundryReceiptEnvelope { EIP7702_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::PostExec(r) => { Header { list: true, payload_length: payload_len }.encode(out); POST_EXEC_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::Deposit(r) => { Header { list: true, payload_length: payload_len }.encode(out); DEPOSIT_TX_TYPE_ID.encode(out); @@ -371,18 +372,23 @@ impl Decodable for FoundryReceiptEnvelope { buf.advance(1); ::decode(buf) .map(FoundryReceiptEnvelope::Eip7702) - } else if receipt_type == POST_EXEC_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::PostExec) - } else if receipt_type == DEPOSIT_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::Deposit) } else if receipt_type == TEMPO_TX_TYPE_ID { buf.advance(1); ::decode(buf).map(FoundryReceiptEnvelope::Tempo) } else { + #[cfg(feature = "optimism")] + { + if receipt_type == POST_EXEC_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::PostExec); + } + if receipt_type == DEPOSIT_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::Deposit); + } + } Err(alloy_rlp::Error::Custom("invalid receipt type")) } } @@ -404,7 +410,9 @@ impl Typed2718 for FoundryReceiptEnvelope { Self::Eip1559(_) => EIP1559_TX_TYPE_ID, Self::Eip4844(_) => EIP4844_TX_TYPE_ID, Self::Eip7702(_) => EIP7702_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::PostExec(_) => POST_EXEC_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, Self::Tempo(_) => TEMPO_TX_TYPE_ID, } @@ -419,7 +427,9 @@ impl Encodable2718 for FoundryReceiptEnvelope { Self::Eip1559(r) => 1 + r.length(), Self::Eip4844(r) => 1 + r.length(), Self::Eip7702(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::PostExec(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::Deposit(r) => 1 + r.length(), Self::Tempo(r) => 1 + r.length(), } @@ -435,8 +445,10 @@ impl Encodable2718 for FoundryReceiptEnvelope { | Self::Eip1559(r) | Self::Eip4844(r) | Self::Eip7702(r) - | Self::PostExec(r) | Self::Tempo(r) => r.encode(out), + #[cfg(feature = "optimism")] + Self::PostExec(r) => r.encode(out), + #[cfg(feature = "optimism")] Self::Deposit(r) => r.encode(out), } } @@ -444,15 +456,18 @@ impl Encodable2718 for FoundryReceiptEnvelope { impl Decodable2718 for FoundryReceiptEnvelope { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == DEPOSIT_TX_TYPE_ID { - return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + #[cfg(feature = "optimism")] + { + if ty == DEPOSIT_TX_TYPE_ID { + return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + } + if ty == POST_EXEC_TX_TYPE_ID { + return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); + } } if ty == TEMPO_TX_TYPE_ID { return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?)); } - if ty == POST_EXEC_TX_TYPE_ID { - return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); - } match ReceiptEnvelope::typed_decode(ty, buf)? { ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)), ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)), @@ -646,8 +661,11 @@ mod tests { assert!(receipt.status()); assert_eq!(receipt.cumulative_gas_used(), 100000); assert!(receipt.logs().is_empty()); - assert!(receipt.deposit_nonce().is_none()); - assert!(receipt.deposit_receipt_version().is_none()); + #[cfg(feature = "optimism")] + { + assert!(receipt.deposit_nonce().is_none()); + assert!(receipt.deposit_receipt_version().is_none()); + } } #[test] diff --git a/crates/primitives/src/transaction/request.rs b/crates/primitives/src/transaction/request.rs index 2c4dbae8fdcd4..8ae31efbd5cb1 100644 --- a/crates/primitives/src/transaction/request.rs +++ b/crates/primitives/src/transaction/request.rs @@ -3,15 +3,19 @@ use alloy_network::{ BuildResult, NetworkTransactionBuilder, NetworkWallet, TransactionBuilder, TransactionBuilder4844, TransactionBuilderError, }; -use alloy_primitives::{Address, B256, ChainId, TxKind, U256}; +use alloy_primitives::{Address, ChainId, TxKind, U256}; use alloy_rpc_types::{AccessList, TransactionInputKind, TransactionRequest}; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit}; +#[cfg(feature = "optimism")] use op_revm::transaction::deposit::DepositTransactionParts; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionRequest; use tempo_primitives::{TEMPO_TX_TYPE_ID, TempoTxType}; +#[cfg(feature = "optimism")] +use super::optimism::get_deposit_tx_parts; use super::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; use crate::FoundryNetwork; @@ -28,6 +32,7 @@ use crate::FoundryNetwork; #[derive(Clone, Debug, PartialEq, Eq)] pub enum FoundryTransactionRequest { Ethereum(TransactionRequest), + #[cfg(feature = "optimism")] Op(WithOtherFields), Tempo(Box), } @@ -44,6 +49,7 @@ impl FoundryTransactionRequest { pub fn into_inner(self) -> TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx.inner, Self::Tempo(tx) => tx.inner, } @@ -55,6 +61,7 @@ impl FoundryTransactionRequest { /// # Returns /// - Ok(deposit_tx_parts) if all necessary keys are present to build a deposit transaction. /// - Err(missing) if some keys are missing to build a deposit transaction. + #[cfg(feature = "optimism")] pub fn get_deposit_tx_parts(&self) -> Result> { match self { Self::Op(tx) => get_deposit_tx_parts(&tx.other), @@ -69,9 +76,11 @@ impl FoundryTransactionRequest { pub fn preferred_type(&self) -> FoundryTxType { match self { Self::Ethereum(tx) => tx.preferred_type().into(), + #[cfg(feature = "optimism")] Self::Op(tx) if tx.inner.transaction_type == Some(POST_EXEC_TX_TYPE_ID) => { FoundryTxType::PostExec } + #[cfg(feature = "optimism")] Self::Op(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -95,6 +104,7 @@ impl FoundryTransactionRequest { /// Check if all necessary keys are present to build a Deposit transaction, returning a list of /// keys that are missing. + #[cfg(feature = "optimism")] pub fn complete_deposit(&self) -> Result<(), Vec<&'static str>> { self.get_deposit_tx_parts().map(|_| ()) } @@ -123,7 +133,9 @@ impl FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } { @@ -138,9 +150,10 @@ impl FoundryTransactionRequest { /// Converts the request into a `FoundryTypedTx`, handling all Ethereum and OP-stack transaction /// types. pub fn build_typed_tx(self) -> Result { + #[cfg(feature = "optimism")] if let Ok(deposit_tx_parts) = self.get_deposit_tx_parts() { // Build deposit transaction - Ok(FoundryTypedTx::Deposit(TxDeposit { + return Ok(FoundryTypedTx::Deposit(TxDeposit { from: self.from().unwrap_or_default(), source_hash: deposit_tx_parts.source_hash, to: self.kind().unwrap_or_default(), @@ -149,8 +162,9 @@ impl FoundryTransactionRequest { gas_limit: self.gas_limit().unwrap_or_default(), is_system_transaction: deposit_tx_parts.is_system_transaction, input: self.input().cloned().unwrap_or_default(), - })) - } else if self.complete_tempo().is_ok() + })); + } + if self.complete_tempo().is_ok() && let Self::Tempo(tx_req) = self { // Build Tempo transaction @@ -192,6 +206,7 @@ impl Serialize for FoundryTransactionRequest { { match self { Self::Ethereum(tx) => tx.serialize(serializer), + #[cfg(feature = "optimism")] Self::Op(tx) => tx.serialize(serializer), Self::Tempo(tx) => tx.serialize(serializer), } @@ -211,6 +226,7 @@ impl AsRef for FoundryTransactionRequest { fn as_ref(&self) -> &TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_ref(), } @@ -221,6 +237,7 @@ impl AsMut for FoundryTransactionRequest { fn as_mut(&mut self) -> &mut TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_mut(), } @@ -244,15 +261,16 @@ impl From> for FoundryTransactionRequest { { tempo_tx_req.set_nonce_key(nonce_key); } - Self::Tempo(Box::new(tempo_tx_req)) - } else if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) + return Self::Tempo(Box::new(tempo_tx_req)); + } + #[cfg(feature = "optimism")] + if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) || tx.transaction_type == Some(POST_EXEC_TX_TYPE_ID) || get_deposit_tx_parts(&tx.other).is_ok() { - Self::Op(tx) - } else { - Self::Ethereum(tx.into_inner()) + return Self::Op(tx); } + Self::Ethereum(tx.into_inner()) } } @@ -264,6 +282,7 @@ impl From for FoundryTransactionRequest { FoundryTypedTx::Eip1559(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip4844(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip7702(tx) => Self::Ethereum(Into::::into(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => { let other = OtherFields::from_iter([ ("sourceHash", tx.source_hash.to_string().into()), @@ -272,6 +291,7 @@ impl From for FoundryTransactionRequest { ]); WithOtherFields { inner: Into::::into(tx), other }.into() } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(tx) => WithOtherFields { inner: Into::::into(tx), other: OtherFields::default(), @@ -307,8 +327,9 @@ impl From for FoundryTransactionRequest { } } -impl From> for FoundryTransactionRequest { - fn from(tx: op_alloy_rpc_types::Transaction) -> Self { +#[cfg(not(feature = "optimism"))] +impl From> for FoundryTransactionRequest { + fn from(tx: alloy_rpc_types_eth::Transaction) -> Self { tx.inner.into_inner().into() } } @@ -437,7 +458,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.as_ref().complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } @@ -448,9 +471,14 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { } fn can_build(&self) -> bool { - self.as_ref().can_build() - || self.complete_deposit().is_ok() - || self.complete_tempo().is_ok() + if self.as_ref().can_build() || self.complete_tempo().is_ok() { + return true; + } + #[cfg(feature = "optimism")] + if self.complete_deposit().is_ok() { + return true; + } + false } fn output_tx_type(&self) -> FoundryTxType { @@ -465,7 +493,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit().ok(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => self.complete_type(pref).ok(), FoundryTxType::Tempo => self.complete_tempo().ok(), }?; @@ -479,11 +509,21 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { let inner = self.as_mut(); inner.transaction_type = Some(preferred_type as u8); inner.gas.is_none().then(|| inner.set_gas_limit(Default::default())); - if !matches!(preferred_type, FoundryTxType::Deposit | FoundryTxType::Tempo) { + let is_deposit = { + #[cfg(feature = "optimism")] + { + preferred_type == FoundryTxType::Deposit + } + #[cfg(not(feature = "optimism"))] + { + false + } + }; + if !is_deposit && preferred_type != FoundryTxType::Tempo { inner.trim_conflicting_keys(); inner.populate_blob_hashes(); } - if preferred_type != FoundryTxType::Deposit { + if !is_deposit { inner.nonce.is_none().then(|| inner.set_nonce(Default::default())); } if matches!(preferred_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { @@ -548,42 +588,10 @@ impl TransactionBuilder4844 for FoundryTransactionRequest { } } -/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields -pub fn get_deposit_tx_parts( - other: &OtherFields, -) -> Result> { - let mut missing = Vec::new(); - let source_hash = - other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("sourceHash"); - Default::default() - }, - ); - let mint = other - .get_deserialized::("mint") - .transpose() - .unwrap_or_else(|_| { - missing.push("mint"); - Default::default() - }) - .map(|value| value.to::()); - let is_system_transaction = - other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("isSystemTx"); - Default::default() - }, - ); - if missing.is_empty() { - Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) - } else { - Err(missing) - } -} - #[cfg(test)] mod tests { + use alloy_primitives::B256; + use super::*; fn default_tx_req() -> TransactionRequest { @@ -618,6 +626,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_routing_op_by_deposit_fields() { let tx = default_tx_req(); let mut other = OtherFields::default(); @@ -669,6 +678,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_serialization_op() { let tx = default_tx_req(); let mut other = OtherFields::default(); diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index 7f112ce1bbda8..ce94945fb27cf 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -27,3 +27,7 @@ revm-inspectors.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index acdcbbdbf95ea..6de71571ac7eb 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -62,3 +62,15 @@ tempo-primitives.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-cli/optimism", + "forge-script-sequence/optimism", + "forge-verify/optimism", +] diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 5ac7e4c669ade..f1bda2ccdcf55 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,8 +1,8 @@ -use std::{cmp::Ordering, sync::Arc, time::Duration}; +use std::{cmp::Ordering, num::NonZeroU64, sync::Arc, time::Duration}; use crate::{ - ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, - sequence::ScriptSequenceKind, verify::BroadcastedState, + ScriptArgs, ScriptConfig, build::LinkedBuildData, needs_script_rpc_estimate, + progress::ScriptProgress, sequence::ScriptSequenceKind, verify::BroadcastedState, }; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::{SignableTransaction, Signed}; @@ -21,11 +21,12 @@ use alloy_signer::Signature; use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; -use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; +use foundry_cli::utils::has_batch_support; use foundry_common::{ FoundryTransactionBuilder, TransactionMaybeSigned, provider::{ProviderBuilder, try_get_http_provider}, shell, + tempo::TempoSponsor, }; use foundry_config::Config; use foundry_evm::core::evm::{FoundryEvmNetwork, TempoEvmNetwork}; @@ -100,7 +101,17 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result<()> { + let access_key_authorization = match self { + Self::AccessKey(_, _, access_key) => Some(( + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.clone(), + )), + _ => None, + }; + if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) @@ -137,11 +148,28 @@ where } } + if let Some((wallet_address, key_address, key_authorization)) = + access_key_authorization.as_ref() + { + tx.prepare_access_key_authorization( + provider, + *wallet_address, + *key_address, + key_authorization.as_ref(), + ) + .await?; + } + // Chains which use `eth_estimateGas` are being sent sequentially and require their // gas to be re-estimated right before broadcasting. if !is_fixed_gas_limit && estimate_via_rpc { estimate_gas(tx, provider, estimate_multiplier).await?; } + + if let Some(sponsor) = tempo_sponsor { + let from = tx.from().expect("no sender"); + sponsor.attach_and_print::(tx, from).await?; + } } Ok(()) @@ -211,6 +239,7 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result { self.prepare( &provider, @@ -218,6 +247,7 @@ where is_fixed_gas_limit, estimate_via_rpc, estimate_multiplier, + tempo_sponsor, ) .await?; @@ -387,6 +417,27 @@ impl BundledState { SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?.map(Arc::new); + if tempo_sponsor.is_some() && self.script_config.tempo.sponsor_sig.is_some() { + let remaining = self + .sequence + .sequences() + .iter() + .map(|sequence| { + sequence + .transactions() + .skip(sequence.receipts.len()) + .filter(|tx| tx.is_unsigned()) + .count() + }) + .sum::(); + if remaining > 1 { + eyre::bail!( + "--tempo.sponsor-sig can only sponsor one remaining script transaction; use --tempo.sponsor-signer for multi-transaction scripts" + ); + } + } + let progress = ScriptProgress::default(); for i in 0..self.sequence.sequences().len() { @@ -464,6 +515,11 @@ impl BundledState { let kind = match tx_with_metadata.tx().clone() { TransactionMaybeSigned::Signed { tx, .. } => { + if tempo_sponsor.is_some() { + eyre::bail!( + "cannot attach Tempo sponsor signature to an already signed script transaction" + ); + } SendTransactionKind::Signed(tx) } TransactionMaybeSigned::Unsigned(mut tx) => { @@ -487,6 +543,8 @@ impl BundledState { tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } + self.script_config.tempo.apply::(&mut tx, None); + send_kind.for_sender(&from, tx)? } }; @@ -495,9 +553,13 @@ impl BundledState { }) .collect::>>()?; - let estimate_via_rpc = has_different_gas_calc(sequence.chain) - || self.script_config.evm_opts.networks.is_tempo() - || self.args.skip_simulation; + let estimate_via_rpc = needs_script_rpc_estimate( + sequence.chain, + self.script_config.evm_opts.networks.is_tempo(), + self.script_config.batch, + &self.script_config.tempo, + self.args.skip_simulation, + ); // We only wait for a transaction receipt before sending the next transaction, if // there is more than one signer. There would be no way of assuring @@ -525,6 +587,7 @@ impl BundledState { let pending_transactions = batch.iter().map(|(kind, is_fixed_gas_limit)| { let provider = provider.clone(); + let tempo_sponsor = tempo_sponsor.clone(); async move { let res = kind .clone() @@ -534,22 +597,36 @@ impl BundledState { *is_fixed_gas_limit, estimate_via_rpc, self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), ) .await; - (res, kind, 0, None) + (res, kind, *is_fixed_gas_limit, 0, None) } .boxed() }); let mut buffer = pending_transactions.collect::>(); - 'send: while let Some((res, kind, attempt, original_res)) = - buffer.next().await + 'send: while let Some(( + res, + kind, + is_fixed_gas_limit, + attempt, + original_res, + )) = buffer.next().await { - if res.is_err() && attempt <= 3 { + if res.is_err() + && self.script_config.tempo.sponsor_sig.is_some() + && attempt == 0 + { + debug!( + "not retrying transaction because --tempo.sponsor-sig is a static signature" + ); + } else if res.is_err() && attempt <= 3 { // Try to resubmit the transaction let provider = provider.clone(); let progress = seq_progress.inner.clone(); + let tempo_sponsor = tempo_sponsor.clone(); buffer.push(Box::pin(async move { debug!(err=?res, ?attempt, "retrying transaction "); let attempt = attempt + 1; @@ -557,8 +634,24 @@ impl BundledState { "retrying transaction {res:?} (attempt {attempt})" )); tokio::time::sleep(Duration::from_millis(1000 * attempt)).await; - let r = kind.clone().send(provider).await; - (r, kind, attempt, original_res.or(Some(res))) + let r = kind + .clone() + .prepare_and_send( + provider, + sequential_broadcast, + is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), + ) + .await; + ( + r, + kind, + is_fixed_gas_limit, + attempt, + original_res.or(Some(res)), + ) })); continue 'send; @@ -675,6 +768,7 @@ impl BundledState { let sequence = self.sequence.sequences_mut().get_mut(0).unwrap(); let provider = Arc::new(ProviderBuilder::::new(sequence.rpc_url()).build()?); + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?; // Collect sender addresses - batch mode requires single sender let senders: AddressHashSet = sequence @@ -794,16 +888,35 @@ impl BundledState { max_priority_fee_per_gas: Some(max_priority_fee_per_gas), ..Default::default() }, - fee_token: self.script_config.fee_token, + fee_token: self.script_config.tempo.common.fee_token, calls: calls.clone(), + nonce_key: self.script_config.tempo.expiring_nonce.then_some(U256::MAX), + valid_before: self.script_config.tempo.valid_before.and_then(NonZeroU64::new), ..Default::default() }; + self.script_config.tempo.apply::(&mut batch_tx, None); + + if let BatchSigner::TempoKeychain(_, ak) = &batch_signer { + batch_tx.key_id = Some(ak.key_address); + batch_tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } // Estimate gas for the batch transaction estimate_gas(&mut batch_tx, provider.as_ref(), self.args.gas_estimate_multiplier).await?; sh_println!("Estimated gas: {}", batch_tx.inner.gas.unwrap_or(0))?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut batch_tx, sender).await?; + } + // Sign and send let tx_hash = match batch_signer { BatchSigner::Wallet(wallet) => { @@ -816,8 +929,6 @@ impl BundledState { *pending.tx_hash() } BatchSigner::TempoKeychain(signer, access_key) => { - batch_tx.key_id = Some(access_key.key_address); - let raw_tx = batch_tx .sign_with_access_key( provider.as_ref(), diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index f5e4d46de0344..ea906c9b872e1 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -28,8 +28,8 @@ use eyre::{ContextCompat, Result}; use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; use foundry_cli::{ - opts::{BuildOpts, EvmArgs, GlobalArgs}, - utils::{LoadConfig, parse_fee_token_address}, + opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts}, + utils::{LoadConfig, has_different_gas_calc}, }; use foundry_common::{ CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, @@ -44,11 +44,13 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ backend::Backend, core::{ Breakpoints, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, tempo::PATH_USD_ADDRESS, }, executors::ExecutorBuilder, @@ -140,9 +142,9 @@ pub struct ScriptArgs { #[arg(long, requires = "batch", default_value = "100")] pub batch_size: usize, - /// Tempo fee token address for paying transaction fees. - #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] - pub fee_token: Option
, + /// Tempo transaction options. + #[command(flatten)] + pub tempo: TempoOpts, /// Skips on-chain simulation. #[arg(long)] @@ -246,13 +248,43 @@ pub struct ScriptArgs { pub retry: RetryArgs, } +const fn should_default_tempo_fee_token( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + // Plain `--network tempo` should stay an ordinary transaction; only Tempo AA opts get defaults. + is_tempo_network && tempo.common.fee_token.is_none() && (batch || tempo.is_tempo()) +} + +const fn needs_tempo_aa_rpc_estimate( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + is_tempo_network && (batch || tempo.is_tempo()) +} + +pub(crate) fn needs_script_rpc_estimate( + chain_id: u64, + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, + skip_simulation: bool, +) -> bool { + // Tempo AA needs RPC estimation; plain Tempo scripts can use the local simulation result. + (has_different_gas_calc(chain_id) && !is_tempo_network) + || needs_tempo_aa_rpc_estimate(is_tempo_network, batch, tempo) + || skip_simulation +} + impl ScriptArgs { /// Loads config, resolves evm_opts (including network inference from fork), and returns them. async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - if self.fee_token.is_some() { - // If fee token is set directly select tempo + if self.tempo.is_tempo() { + // If fee token or expiry is set directly select tempo evm_opts.networks = NetworkConfigs::with_tempo(); } else { // Auto-detect network from fork chain ID when not explicitly configured. @@ -285,13 +317,14 @@ impl ScriptArgs { } } - let fee_token = if evm_opts.networks.is_tempo() && self.fee_token.is_none() { - Some(PATH_USD_ADDRESS) - } else { - self.fee_token - }; + let mut tempo = self.tempo.clone(); + tempo.resolve_expires(); + + if should_default_tempo_fee_token(evm_opts.networks.is_tempo(), self.batch, &tempo) { + tempo.common.fee_token = Some(PATH_USD_ADDRESS); + } - let script_config = ScriptConfig::new(config, evm_opts, self.batch, fee_token).await?; + let script_config = ScriptConfig::new(config, evm_opts, self.batch, tempo).await?; Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } @@ -320,12 +353,15 @@ impl ScriptArgs { if broadcasted.args.verify { broadcasted.verify().await?; } - Ok(()) - } else if evm_opts.networks.is_optimism() { - self.run_generic_script::(config, evm_opts).await - } else { - self.run_generic_script::(config, evm_opts).await + return Ok(()); } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_generic_script::(config, evm_opts).await; + } + + self.run_generic_script::(config, evm_opts).await } /// Prepares the bundled state (compile, simulate, bundle) and returns it @@ -708,8 +744,8 @@ pub struct ScriptConfig { pub backends: HashMap>, /// Whether to batch all broadcast transactions into a single Tempo batch transaction. pub batch: bool, - /// Tempo fee token address for paying transaction fees. - pub fee_token: Option
, + /// Tempo transaction options applied to broadcast transactions. + pub tempo: TempoOpts, } impl ScriptConfig { @@ -717,7 +753,7 @@ impl ScriptConfig { config: Config, evm_opts: EvmOpts, batch: bool, - fee_token: Option
, + tempo: TempoOpts, ) -> Result { let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? @@ -726,7 +762,7 @@ impl ScriptConfig { 1 }; - Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, fee_token }) + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, tempo }) } pub async fn update_sender(&mut self, sender: Address) -> Result<()> { @@ -802,7 +838,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), - self.fee_token, + self.tempo.common.fee_token, ) .into(), ) @@ -813,7 +849,7 @@ impl ScriptConfig { // Propagate fee token to the transaction environment so that internal EVM calls // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. - tx_env.set_fee_token(self.fee_token); + tx_env.set_fee_token(self.tempo.common.fee_token); Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } @@ -823,6 +859,7 @@ impl ScriptConfig { mod tests { use super::*; use alloy_network::Ethereum; + use alloy_primitives::address; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -834,6 +871,50 @@ mod tests { assert_eq!(args.sig, sig); } + #[test] + fn can_parse_shared_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.fee-token", + "1", + "--tempo.expires", + "10", + ]); + + assert_eq!( + args.tempo.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")) + ); + assert_eq!(args.tempo.common.expires, Some(10)); + } + + #[test] + fn can_parse_sponsor_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]); + + assert_eq!( + args.tempo.sponsor, + Some(address!("0x1111111111111111111111111111111111111111")) + ); + assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + } + + #[test] + fn can_parse_full_tempo_opts() { + let args = + ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]); + + assert_eq!(args.tempo.nonce_key, Some(U256::from(1))); + } + #[test] fn can_parse_unlocked() { let args = ScriptArgs::parse_from([ diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index e2404d60ce2b9..b085f8eaf4545 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -6,7 +6,7 @@ use alloy_network::TransactionBuilder; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; -use foundry_common::{FoundryTransactionBuilder, TransactionMaybeSigned}; +use foundry_common::TransactionMaybeSigned; use foundry_config::Config; use foundry_evm::{ constants::CALLER, @@ -84,9 +84,7 @@ impl ScriptRunner { .with_input(code.clone()) .with_nonce(sender_nonce + library_transactions.len() as u64); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), @@ -122,9 +120,7 @@ impl ScriptRunner { .with_nonce(sender_nonce + library_transactions.len() as u64) .with_to(create2_deployer); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index ef10a1ce94082..fe1e9345fa23c 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -129,7 +129,7 @@ impl VerifyBundle { path: Some(artifact.source.to_string_lossy().to_string()), name: artifact .name - .strip_suffix(&format!(".{}", &artifact.profile)) + .strip_suffix(&format!(".{}", artifact.profile)) .unwrap_or_else(|| &artifact.name) .to_string(), }; diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index 69ea952d4d040..d3ad56a96cdd2 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -27,3 +27,7 @@ prettyplease.workspace = true eyre.workspace = true heck.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index d29f6358e93d2..8dc652bcb32bd 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -44,3 +44,7 @@ idna_adapter.workspace = true [dev-dependencies] tokio.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 65a202911509f..e3372a2494f34 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -48,3 +48,12 @@ url.workspace = true tokio = { workspace = true, features = ["macros"] } foundry-test-utils.workspace = true tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-cli/optimism", +] diff --git a/docs/dev/lintrules.md b/docs/dev/lintrules.md index 6f5dbbd850784..969d7effe142f 100644 --- a/docs/dev/lintrules.md +++ b/docs/dev/lintrules.md @@ -60,6 +60,8 @@ Next, choose whether you want an [early or late lint pass](#choosing-between-ear - Implement the appropriate trait logic (`EarlyLintPass` or `LateLintPass`) for your lint. Do it in a new file within the relevant severity module (e.g., `src/sol/med/my_new_lint.rs`). +- Add a markdown documentation file for the lint at `crates/lint/docs/.md`. The file is referenced by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the [Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. Use [`crates/lint/docs/_template.md`](../../crates/lint/docs/_template.md) as a starting point. The presence of this file is enforced by the `registered_lints_have_docs` unit test in `crates/lint/src/sol/mod.rs`. + ### Choosing Between Early and Late Passes - **Use `EarlyLintPass`** for: diff --git a/flake.lock b/flake.lock index 27f426f491da6..343a79c0cda3d 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1777102577, - "narHash": "sha256-ycoy9svZOQgyInu/lwO7IEQtlP5liqYhEcF9m9hPRbM=", + "lastModified": 1777708550, + "narHash": "sha256-Qif3UXT0l5OQq8H9pRWt4/ia4gF48MWK2oHKL8uVx8U=", "owner": "nix-community", "repo": "fenix", - "rev": "f37403486c59376cd285f9685a8ef8ff25c09a3c", + "rev": "74c1591efaff494756b8d35ebe357c6c2bbdca96", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776949667, - "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=", + "lastModified": 1777641297, + "narHash": "sha256-WNGcmeOZ8Tr9dq6ztCspYbzWFswr2mPebM9LpsfGxPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30", + "rev": "c6d65881c5624c9cae5ea6cedef24699b0c0a4c0", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1776800521, - "narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=", + "lastModified": 1777639980, + "narHash": "sha256-6d7Hdurvbjc5uwJuc0YiK7rZBGj6Gs3uzfBFcTs+xCc=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e", + "rev": "64cdaeb06f69b6b769a492edd88b022ae88e8ca2", "type": "github" }, "original": { diff --git a/foundryup/README.md b/foundryup/README.md index 29e91378929cc..2cf61f8725227 100644 --- a/foundryup/README.md +++ b/foundryup/README.md @@ -30,10 +30,10 @@ To install the latest **nightly** version: foundryup --install nightly ``` -To install a specific version (e.g. `v1.6.0`): +To install a specific version (e.g. `v1.7.0`): ```sh -foundryup --install v1.6.0 +foundryup --install v1.7.0 ``` To **list** all **versions** installed: diff --git a/foundryup/foundryup b/foundryup/foundryup index 7576501b4619e..5fb086a75f8c1 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -3,7 +3,7 @@ set -eo pipefail # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -FOUNDRYUP_INSTALLER_VERSION="1.8.1" +FOUNDRYUP_INSTALLER_VERSION="1.8.3" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} @@ -15,6 +15,13 @@ FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" FOUNDRYUP_JOBS="" FOUNDRYUP_IGNORE_VERIFICATION=false +# Retry/backoff settings used for `fetch` (GitHub API calls). +# Recovers from transient HTTP 403/429/5xx responses returned by +# api.github.com under heavy load or per-IP rate limiting. +FOUNDRYUP_MAX_RETRIES=5 +FOUNDRYUP_RETRY_DELAY=2 +FOUNDRYUP_RETRY_MAX_TIME=60 + BINS=(forge cast anvil chisel) HASH_NAMES=() HASH_VALUES=() @@ -111,51 +118,7 @@ main() { # Install by downloading binaries if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-latest} - - # Normalize versions (handle channels, versions without v prefix) - if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then - # Resolve to the latest release (non-prerelease) via the GitHub API - say "fetching latest release from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' - /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } - ') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a latest release for ${FOUNDRYUP_REPO}" - fi - say "resolved release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" - elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then - # Resolve to the latest nightly (prerelease) release via the GitHub API. - # The GitHub API does not guarantee that releases are returned in - # chronological order, so we collect all matching nightlies along with - # their `published_at` timestamps and sort them ourselves. - say "fetching latest nightly releases from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' - /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } - /"published_at"[[:space:]]*:[[:space:]]*"/ { - pub=$0 - gsub(/.*"published_at"[[:space:]]*:[[:space:]]*"/, "", pub) - gsub(/".*/, "", pub) - if (tag ~ /^nightly-/) print pub "\t" tag - tag="" - } - ' | sort -r | awk -F '\t' 'NR==1 { print $2 }') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a nightly release for ${FOUNDRYUP_REPO}" - fi - say "resolved nightly release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then - # Specific nightly tag (e.g. nightly-abc123...) - FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then - # Add v prefix - FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - else - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - fi + resolve_version_and_tag say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})" @@ -179,7 +142,7 @@ main() { tmp_dir="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" tmp="$tmp_dir/attestation.txt" ensure download "$ATTESTATION_URL" "$tmp" - + # Read the first line of the attestation file to get the artifact link. # The first line should contain the link to the attestation artifact. attestation_artifact_link="$(head -n1 "$tmp" | tr -d '\r')" @@ -292,7 +255,7 @@ main() { else say 'skipping manpage download: missing "tar"' fi - + if [ "$FOUNDRYUP_IGNORE_VERIFICATION" = true ]; then say "skipped SHA verification for downloaded binaries due to --force flag" else @@ -505,6 +468,18 @@ list() { use() { [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" + + # If the requested version is a channel (`latest`, `stable`, `nightly`) or a bare semver + # version (e.g. `1.7.0`, `1.6.0-rc1`), resolve it to the immutable tag directory created by + # `--install` (channels hit the GitHub API; semver versions get a `v` prefix). + # Falls back to the literal value for locally-built versions (branches, PRs, commits, custom names). + case "$FOUNDRYUP_VERSION" in + latest|stable|nightly|[0-9]*.[0-9]*.[0-9]*) + resolve_version_and_tag + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + ;; + esac + FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" if [ -d "$FOUNDRY_VERSION_DIR" ]; then @@ -683,12 +658,70 @@ ensure() { if ! "$@"; then err "command failed: $*"; fi } -# Silently fetches $1 to stdout +# Normalizes `FOUNDRYUP_VERSION` and resolves it to a concrete release tag, +# populating `FOUNDRYUP_TAG`. Handles the `latest`/`stable`/`nightly` channels +# (looked up via the GitHub API). +resolve_version_and_tag() { + FOUNDRYUP_REPO=${FOUNDRYUP_REPO:-foundry-rs/foundry} + if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then + # Resolve to the latest release (non-prerelease) via the GitHub API. + say "fetching latest release tag from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' + /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } + ') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a latest release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then + # Resolve to the latest nightly (prerelease) release via the GitHub API. + # The GitHub API does not guarantee that releases are returned in + # chronological order, so we collect all matching nightlies along with + # their `published_at` timestamps and sort them ourselves. + say "fetching latest nightly release tags from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' + /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } + /"published_at"[[:space:]]*:[[:space:]]*"/ { + pub=$0 + gsub(/.*"published_at"[[:space:]]*:[[:space:]]*"/, "", pub) + gsub(/".*/, "", pub) + if (tag ~ /^nightly-/) print pub "\t" tag + tag="" + } + ' | sort -r | awk -F '\t' 'NR==1 { print $2 }') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a nightly release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved nightly release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then + # Specific nightly tag (e.g. nightly-abc123...) + FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then + # Add v prefix + FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + else + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + fi +} + +# Silently fetches $1 to stdout. fetch() { if check_cmd curl; then - curl -fsSL "$1" + curl -fsSL \ + --retry "$FOUNDRYUP_MAX_RETRIES" \ + --retry-delay "$FOUNDRYUP_RETRY_DELAY" \ + --retry-max-time "$FOUNDRYUP_RETRY_MAX_TIME" \ + --retry-all-errors \ + "$1" else - wget -qO- "$1" + wget --tries="$FOUNDRYUP_MAX_RETRIES" \ + --waitretry="$FOUNDRYUP_RETRY_DELAY" \ + --retry-on-http-error=403,408,429,500,502,503,504 \ + -qO- "$1" fi } diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 839d97962aa94..ae0c8ed844f5d 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -305,6 +305,91 @@ contract ExpectRevertWithReverterTest is Test { vm.expectRevert(address(cContract)); aContract.createDContractThroughCContract(); } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // reverts directly, the reverter address argument must be enforced (it used to + // be silently ignored). The matched reverter is the would-be-deployed address. + function testExpectRevertsWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new DContract(); + + expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(abi.encodePacked("Reverted by DContract"), expected); + new DContract(); + } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // synchronously creates another contract that reverts (i.e. innermost frame is + // a CREATE), the matched reverter is the outer would-be-deployed address (the + // contract whose deployment failed). + function testExpectRevertsWithReverterNestedCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new NestedDContractCreator(); + } + + // + // Regression: `expectPartialRevert(bytes4, address)` overload must enforce + // the reverter address argument when matching a top-level CREATE revert. + function testExpectPartialRevertWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + // `Reverted by DContract` triggers Solidity's `Error(string)` selector. + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), expected); + new DContract(); + } + + // + // Regression: `expectRevert(bytes4, address)` (exact 4-byte selector + reverter) + // overload must enforce the reverter address argument for a top-level CREATE. + function testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(DCustomErrorContract.CustomError.selector, expected); + new DCustomErrorContract(); + } + + // + // Regression: `expectRevert(address, uint64)` count-bearing overload must + // exercise the `count > 1` branch in `create_end`. Use CREATE2 with the same + // salt so both deploys would resolve to the same would-be address (each + // constructor reverts so no contract is ever actually placed there). + function testExpectRevertsWithReverterCountTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0x42)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected, 2); + new DContract{salt: salt}(); + new DContract{salt: salt}(); + } + + // + // Regression: CREATE2 deploys must also enforce the reverter address argument. + function testExpectRevertsWithReverterTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0xC0FFEE)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected); + new DContract{salt: salt}(); + } +} + +// Used by `testExpectRevertsWithReverterNestedCreate`: a contract whose constructor +// directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } +} + +// Used by `testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate`: constructor +// reverts with a parameter-less custom error so the full revert data is exactly the +// 4-byte selector. +contract DCustomErrorContract { + error CustomError(); + + constructor() { + revert CustomError(); + } } contract ExpectRevertCount is Test { diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index 6139b8b6b6a5e..f01b7cdd7d213 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -84,4 +84,55 @@ contract GetFoundryVersionTest is Test { // Should return true for past versions assertTrue(vm.foundryVersionAtLeast("0.2.0")); } + + /// Returns the `MAJOR.MINOR.PATCH` prefix of `vm.getFoundryVersion()`, + /// stripping any pre-release suffix (`-nightly`, `-dev`, …) and the + /// `+..` build metadata. + function _semverPrefix() internal view returns (string memory) { + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + string[] memory dashSplit = vm.split(plusSplit[0], "-"); + return dashSplit[0]; + } + + function testGetFoundryVersionMajorMinorPatchIsParseable() public view { + // The MAJOR.MINOR.PATCH prefix must always be three numeric components, + // regardless of build kind (tagged release / nightly / dev). + string[] memory parts = vm.split(_semverPrefix(), "."); + require(parts.length == 3, "Invalid semver prefix: expected MAJOR.MINOR.PATCH"); + // Each component must parse as a uint (this reverts on garbage). + vm.parseUint(parts[0]); + vm.parseUint(parts[1]); + vm.parseUint(parts[2]); + } + + function testGetFoundryVersionBuildProfile() public view { + // The build profile must be present and non-empty (e.g. "debug", "release", "dist", …). + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + string[] memory metadataComponents = vm.split(plusSplit[1], "."); + require(bytes(metadataComponents[2]).length > 0, "Build profile is empty"); + } + + function testFoundryVersionCmpAndAtLeastAreConsistent() public { + // `foundryVersionAtLeast(v)` must equal `foundryVersionCmp(v) >= 0` for any input. + string[3] memory probes = ["0.0.1", _semverPrefix(), "99.0.0"]; + for (uint256 i = 0; i < probes.length; i++) { + assertEq(vm.foundryVersionAtLeast(probes[i]), vm.foundryVersionCmp(probes[i]) >= 0); + } + } + + function testFoundryVersionCmpRejectsPreRelease() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0-nightly"); + } + + function testFoundryVersionCmpRejectsBuildMetadata() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0+abc1234567.1700000000.release"); + } + + function testFoundryVersionCmpRejectsInvalidVersion() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("not-a-version"); + } } diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index e2ac74d6f70fa..d8019ab4f6ee8 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -158,6 +158,35 @@ contract MockCallTest is Test { assertEq(mock.pay{value: 50}(1), 100); } + function testMockCallWithValueTransfersBalance() public { + Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + assertEq(address(mock).balance, 0); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + } + + function testMockCallWithValueTransfersPrankedSenderBalance() public { + Mock mock = new Mock(); + address sender = address(0xBEEF); + uint256 value = 10; + vm.deal(address(this), 0); + vm.deal(sender, value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + vm.prank(sender); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + assertEq(sender.balance, 0); + } + function testMockCallWithValueCalldataPrecedence() public { Mock mock = new Mock(); @@ -279,17 +308,25 @@ contract MockCallRevertTest is Test { function testMockCallRevertWithValue() public { Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); - vm.mockCallRevert(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); + vm.mockCallRevert(address(mock), value, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - try mock.pay{value: 10}(1) { + uint256 initSenderBalance = address(this).balance; + uint256 initTargetBalance = address(mock).balance; + + try mock.pay{value: value}(1) { revert(); } catch (bytes memory err) { require(keccak256(err) == keccak256(ERROR_MESSAGE)); } + + assertEq(address(this).balance, initSenderBalance); + assertEq(address(mock).balance, initTargetBalance); } function testMockCallResetsMockCallRevert() public { diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol index e0f5eef151db6..777543f28e361 100644 --- a/testdata/default/cheats/MockCalls.t.sol +++ b/testdata/default/cheats/MockCalls.t.sol @@ -28,13 +28,17 @@ contract MockCallsTest is Test { mocks[0] = abi.encode(2 ether); mocks[1] = abi.encode(1 ether); mocks[2] = abi.encode(6.423 ether); + vm.deal(address(this), 3 ether); vm.mockCalls(mockErc20, 1 ether, data, mocks); (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret1, (uint256)), 2 ether); + assertEq(mockErc20.balance, 1 ether); (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret2, (uint256)), 1 ether); + assertEq(mockErc20.balance, 2 ether); (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + assertEq(mockErc20.balance, 3 ether); } function testMockCalls() public { From 53c39b4bd4832c5539b28900a9c63df4006e7cc7 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 01:06:45 +0700 Subject: [PATCH 379/391] ci: sign release archives, docker images, and publish SBOMs (#519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * anvil: unify Tempo nonce markers across send RPCs (#14536) Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz * fix(forge): `flaky_gas_report_fallback_with_calldata` deployment cost (#14545) * chore(lint): add missing lints to README (#14551) * chore(bench): update `benchmark.sh` (#14548) Co-authored-by: Matthias Seitz * chore(clippy): fix for_kv_map and useless_borrows_in_formatting (#14554) * chore(clippy): fix for_kv_map and useless_borrows_in_formatting Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp * chore(clippy): drop redundant borrows in cheatcodes assert formatters Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp --------- Co-authored-by: Amp * fix(ci): use `PATH_USD` fallback fee token in Mail templates (#14546) * chore(deps): bump the actions-weekly group with 3 updates (#14497) * refactor(chisel): migrate to solar (#14532) * feat(lint): add too-many-digits lint (#14549) * feat: feature-gate optimism deps in common-fmt, common, cast (#14539) * feat(forge): support per-test network selection via inline config (#14530) * feat(cli): `--tempo.expires` retry-safe mode (TIP-1009 expiring nonces) (#14521) * fix(forge): `per_test_network_routing` match undeterministic order (#14557) output * chore(ci): run tempo mainnet and testnet checks before devnet (#14556) * Update flake.lock (#14553) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/f374034' (2026-04-25) → 'github:nix-community/fenix/74c1591' (2026-05-02) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/8954b66' (2026-04-21) → 'github:rust-lang/rust-analyzer/64cdaeb' (2026-05-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/01fbdee' (2026-04-23) → 'github:NixOS/nixpkgs/c6d6588' (2026-05-01) Co-authored-by: github-actions[bot] * chore(bench): update benchmark results (#14552) * fix(forge): ignore ETH_RPC_URL for test forking (#14555) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(cast): add Tempo keychain policy commands (#14531) * feat(cast): add tempo keychain policy commands * fix(cast): address keychain policy review * fix(cli): fix jsonwebtoken panic (#14562) `cast` panicked with this message coming from jsonwebtoken: ``` Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. See the documentation of the CryptoProvider type for more information. ``` This seemingly was introduced with the bump of jsonwebtoken to 10. Now it requires you to pick one backend used by default controlled by the compile time cargo features or call `CryptoProvider::install_default()` at the beginning. I realized that probably it would be better to just select the feature and I picked `aws_lc_rs` as it seems to be increasingly a default and we already are using the C toolchain. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(cli): tidy ETH_RPC_URL handling and add forge regression test (#14559) Follow-up to #14555: - Drop the redundant flashbots branch in RpcOpts::dict; self.url(None) already returns FLASHBOTS_URL when --flashbots is set, so the subsequent overwrite was dead code. - Inline the resolve_rpc_url helper back into RpcCommonOpts::url; it was only called from one place and added unneeded surface area. - Restore the doc comment on RpcCommonOpts and document why ETH_RPC_URL is intentionally not a clap env on the shared field (so EvmArgs cannot inherit it). - Add an integration test that runs forge config with ETH_RPC_URL set in the environment and asserts that eth_rpc_url stays None, directly exercising the regression scenario from #14538. Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: zerosnacks Co-authored-by: Amp * feat(cast): open Tempo wallet fund flow for MPP failures (#14505) * feat(cast): open Tempo wallet fund flow for MPP failures * ci(tempo): skip network checks without rpc secrets * Revert "ci(tempo): skip network checks without rpc secrets" This reverts commit f8dd70163f850b854888fd1c962174e1663284f4. * fix(common): address mpp funding review --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * ci: sign release archives, docker images, and publish SBOMs (#14563) - release.yml: emit per-archive sha256 + SPDX SBOM (Syft), cosign keyless sign-blob of the archive, and use actions/attest@v4 for both build provenance and SBOM attestations. Upload all artifacts to the draft release. - docker-publish.yml: enable BuildKit SBOM, capture the build digest, cosign keyless sign each pushed tag, and publish a Sigstore-signed SLSA provenance attestation via actions/attest with push-to-registry. - SECURITY.md: document how external users verify archives and the docker image (gh attestation, cosign, plain sha256, buildx imagetools). - README.md: link to the new verification section. * perf(common): short-circuit `find_by_name_or_identifier` instead of `collect` (#14514) * feat(foundryup): retry GitHub API fetches on transient errors (#14566) GitHub api.github.com occasionally returns transient 403s on certain VMs (per-IP rate limiting / WAF hiccups), causing foundryup to fail to resolve the latest stable / nightly release tag, e.g.: foundryup: fetching latest nightly releases from foundry-rs/foundry... Error: curl: (56) The requested URL returned error: 403 foundryup: failed to fetch releases from GitHub API Add curl/wget retry logic to the `fetch` helper (used exclusively for GitHub API releases endpoints): - curl: --retry 5 --retry-delay 2 --retry-max-time 60, plus --retry-all-errors when supported (curl 7.71+, feature-detected so older curl does not hard-fail). --retry-all-errors is required to retry HTTP 403, which is not in curl's default retryable set. - wget fallback: --tries=5 --waitretry=2 --retry-on-http-error=403,408,429,5xx. `fetch` now buffers to a temp file before emitting to stdout, since curl's --retry-all-errors is unsafe with piped consumers (mid-stream retries can duplicate bytes). Existing callers pipe into awk/grep. Tunable via FOUNDRYUP_MAX_RETRIES (default 5). `download` (binary tarballs, attestations, manpages) is intentionally left unchanged — those rarely fail and changing them affects the attestation existence check semantics. Bumps installer version 1.8.1 -> 1.8.2. Amp-Thread-ID: https://ampcode.com/threads/T-019df2f5-9b97-717a-b959-cf7cbc7ca3bb Co-authored-by: Amp * feat(lint): project-wide passes + pragma-inconsistent (#14543) * feat(lint): project-wide passes + pragma-inconsistent * rm hashset, msg * test(lint): exhaustive pragma-inconsistent coverage + clearer testdata names (#14561) * test(lint): exhaustive coverage for pragma-inconsistent Follow-up to #14543 expanding test coverage for the cross-file `pragma-inconsistent` lint across the syntax variants users encounter in real Solidity projects. Multi-file scenarios (added as `forgetest!` cases in `crates/forge/tests/cli/lint.rs`, since they cannot be expressed in a single `.sol` testdata file): - Negative (must NOT warn): - all files use the same exact pragma (`0.8.20`) - all files use the same caret pragma (`^0.8.20`) - single file in the project - Positive (must warn): - duplicates among a conflict -- two identical files plus one different pragma still emits three warnings - Mixed: - file without an explicit pragma uses the test-utils default (`add_raw_source` is used to bypass the auto-injected pragma) Source bodies are pulled out into module-level `const` raw strings so rustfmt does not collapse the inline `\n`-escaped strings into wide horizontal blobs. Single-file scenarios (added as `.sol` files under `crates/lint/testdata/` in the existing `//~NOTE:` annotation style): - `PragmaInconsistentCaretVsTilde.sol`: `^0.8.20` vs `~0.8.20` - `PragmaInconsistentRangeVsExact.sol`: `>=0.8.0 <0.9.0` vs `0.8.20` -- range satisfies exact but lint is intentionally string-based, matching SLITHER-W1078 - `PragmaInconsistentOrVsExact.sol`: `0.8.20 || 0.8.21` vs `0.8.20` - `PragmaInconsistentThreeDistinct.sol`: `>=0.8.0`, `^0.8.0`, `~0.8.0` -- verifies the `others` list contains every other variant * test(lint): rename pragma-inconsistent testdata to describe the case under test The two testdata files added in #14543 were named `PragmaInconsistent.sol` and `PragmaInconsistent2.sol`, which made them look like duplicates. They actually exercise distinct edge cases of the same string-based detection: - `PragmaInconsistentCaretAboveExact.sol` (was `PragmaInconsistent.sol`): caret range whose lower bound is strictly below the exact version (`^0.8.0` + `0.8.18`). - `PragmaInconsistentCaretMatchesExact.sol` (was `PragmaInconsistent2.sol`): caret range whose lower bound equals the exact version (`^0.8.20` + `0.8.20`) -- the looks-the-same-but-still-distinct case that guards SLITHER-W1078 parity (no semver intersection). Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: Amp --------- Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * refactor(script): reuse shared Tempo CLI opts (#14558) * deps: bump tempo to 6bf9903 (T6 hardfork) + fix alloy-evm 0.34 compat (#14567) * deps: bump tempo to 6bf9903 (T6 hardfork) Bumps tempo crates to 6bf9903d, adding the T6 hardfork variant to TempoHardfork. Without this, cast's tempo_forkSchedule lookup parses the chain's reported active fork ("T6") into TempoHardfork::FromStr, fails because T6 was unknown to the enum, and silently returns is_hardfork_active(T3) = false. That made 'cast keychain auth' fall back to the legacy authorizeKey selector and revert with LegacyAuthorizeKeySelectorChanged on any T6 chain. Also bumps alloy-evm to 0.34 and the optimism git pin to develop (e3b59e7) so alloy-op-evm picks up an EvmFactory impl built against alloy-evm 0.34. Removes the now-unused paradigmxyz/reth-core [patch] entries. No source changes; lockfile churn is transitive only. * fix: adapt AnvilBlockExecutor to alloy-evm 0.34.0 breaking changes - Add Send + 'static bounds to TxResult impl for AnvilTxResult - Change commit_transaction return type from Result to GasOutput - Remove .expect() on commit_transaction call site Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 * style: rustfmt commit_transaction signature Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 --------- Co-authored-by: Centaur AI * docs: add forge lint rule docs (#14571) * feat(forge): add fuzz run selection (#14522) * feat(forge): add fuzz run selection * fix(fuzz): make metadata builder const * test(fuzz): cover generated seed replay * fix(forge): persist fuzz worker for run replay * fix(evm): satisfy clippy in fuzz replay * fix(fuzz): reuse fuzz run metadata * forge(lint/docs): validate deployed forge lint docs (#14573) test: validate deployed forge lint docs * feat: gate foundry-primitives behind optimism feature (#14572) * fix(ci): increase permissions for the enhanced attestation writing (#14584) * increase permissions for artifact writing * apply write permissions to release-docker * feat(hardforks, networks): gate optimism behind cargo feature (#14581) * fix(forge): encode Tempo creates as AA calls (#14585) * feat(anvil): gate optimism behind cargo feature (#14577) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): introduce `vaddr` cmd for TIP-1022 (#14508) * feat(cast): introduce `vaddr` cmd for tip-1022 * fix: doc * chore: touch-ups * add tests * chore: move tests to tempo ci * feat: add vaddr watch test * feat: count 0 hadling, add `no_register` flag * fix: remove sweep subcommand * fix: make clippy happy * feat(bench): nightly regression tracking workflow (#14586) * fix(cli): fix release version strings for immutable tags, bump to 1.7.1 (#14496) * Fix release version metadata for immutable tags Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Derive nightly release suffix from commit SHA Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * bump to v1.7.1 * avoid appending whole sha hash, not necessary, handle version cmp correctly. after v1.7.1 release we need to bump to v1.7.2 for nightlies following it to compare correctly * Make foundryVersionCmp tolerate new version format and add tests - Strip both pre-release ('-nightly', '-dev') and build metadata ('+..') from SEMVER_VERSION before comparison so the cheatcode keeps working for tagged releases (which have no '-' separator). - Extract strip_semver_metadata helper and add Rust unit tests covering all SEMVER_VERSION shapes, version_cmp ordering, and parse_version rejection of pre-release/build/garbage input. - Extend the Solidity test suite for vm.getFoundryVersion()/foundryVersionCmp/foundryVersionAtLeast: validate MAJOR.MINOR.PATCH parseability, build profile value, cmp/atLeast invariant, and error paths for invalid user-supplied versions. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * fix(test): drop view from solidity tests using assert helpers and fix fmt - assertTrue/assertEq aren't view, so testGetFoundryVersionBuildProfile and testFoundryVersionCmpAndAtLeastAreConsistent can't be view either. - Collapse the buildType assertion onto one line to satisfy forge fmt. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * test(version): assert build profile is non-empty instead of debug|release The dist profile (used for distributed release binaries) is also valid; just require non-empty so any future profile works. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * Normalize nightly- to nightly in release_version Ensures tarball and Docker nightly artifacts produce the same version string. The commit identifier is already included in the SemVer build metadata (after `+`), so collapsing `nightly-` to `nightly` avoids duplicating the SHA in the pre-release tag. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019df79e-d4c9-707c-85eb-2efbf59160b3 --------- Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` (#14007) * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` * test(evm): cover `ForkDbStateSnapshot::storage_ref` snapshot lookup * fix(cast): consistent `--json` output for `keychain` subcommands (#14590) - `keychain rl`: wrap remaining limit in `{"remaining":"..."}` object instead of emitting a bare JSON string - `keychain policy add-call`: emit `{"status":"already_present","target":"..."}` when the rule already exists, instead of plain text - `send_keychain_tx`: wrap sponsor hash in `{"sponsor_hash":"0x..."}` object when --tempo.print-sponsor-hash is used with --json Add CLI tests covering the rl and sponsor-hash JSON output shapes. * feat(tempo): add sponsored transaction plumbing (#14560) * feat(tempo): add sponsored transaction plumbing * addressing mablr comments * fix tempo sponsor signer future layout * preserve json output for tempo sponsor preview * fix(cast): `--json` output support for `vaddr` (#14591) * feat(tempo): add named nonce lanes (#14527) * fix(cheatcodes): transfer value for payable mock calls (#14547) * test: updated tests * fix: execute value transfer * test: improve * imp: review item * test: vm.prank test * imp: moved mocked-call handling after prank application --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add inline-assembly lint (#14575) * feat(lint): add inline-assembly lint * lint(inline-assembly): also recognize `/// @solidity memory-safe-assembly` NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-019df4b6-1b76-734c-9a9b-29db9fb7d461 Co-authored-by: Amp --------- Co-authored-by: Amp * refactor(script): remove `ScriptConfig::{fee_token,expires_at}` in favour of `TempoOpts` (#14594) * feat(evm-core): gate optimism behind cargo feature (#14593) * fix(cli): resolve Tempo expires once (#14595) fix(cli): resolve tempo expires once * feat(cli): gate optimism behind cargo feature (#14596) * fix(anvil): classify EVM halts as transaction rejections (#14592) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat: drop optimism deps under no-default-features (#14600) * fix(forge): `--fuzz-seed` parameter is not effective in `forge coverage` (#14610) fix --fuzz-seed not effective in forge coverage * fix(foundryup): mirror tag resolution for install & use (#14611) * fix(foundryup): mirror tag resolution for install & use * fix(foundryup): mirror semver version normalization in `use` `install` auto-prepends `v` to bare semver versions (e.g. `1.7.0` -> `v1.7.0`) so the on-disk directory is always `v`-prefixed. `use` was doing a literal lookup, so `foundryup -u 1.7.0` failed even though `foundryup -i 1.7.0` had succeeded. Broaden the channel `case` in `use()` to also match bare semver inputs (`MAJOR.MINOR.PATCH[-prerelease]`) so they go through the same `resolve_version_and_tag` normalizer. The pattern is intentionally tighter than `install`'s `[[:digit:]]*` so locally-built versions whose names happen to start with a digit are still looked up literally. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp * chore(foundryup): clarify tag-resolution log and error messages Distinguish the GitHub API tag-resolution phase from the actual binary download by consistently referring to "release tag(s)" in the `resolve_version_and_tag` helper's `say` and `err` messages. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(ci): keep no-default builds free of op deps (#14612) * feat: cast unauthorized flow → wallet.tempo access-key authorization (#14517) * feat: cast unauthorized flow → wallet.tempo access-key authorization Amp-Thread-ID: https://ampcode.com/threads/T-019df174-9538-713b-b8c9-5001b1ad4719 Co-authored-by: Amp * fmt * feat(cast): replace TEMPO_NO_BROWSER env with flag * revert token addresses --------- Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * docs(expect-emit): clarify next-call semantics and warn about caught-revert leak (#14620) docs(cheatcodes): clarify expectEmit next-call semantics and caught-revert leak expectEmit is a 'next call' assertion. If the call immediately after expectEmit reverts and the revert is swallowed by the caller (low-level call or try/catch), the unmatched expectation can leak forward and be satisfied by a later unrelated emission, silently turning a broken test green. Document the constraint on the natspec for both no-arg and topic-checking overloads, and regenerate cheatcodes.json. Refs: https://github.com/foundry-rs/foundry/issues/14618 Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames (#14615) * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames The reverter address argument to `vm.expectRevert` was silently ignored when the innermost reverting frame was a CREATE (top-level or nested), because create_end never populated `expected_revert.reverted_by`. Mirror call_end's logic in create_end: when the outcome reverts and a reverter address is expected, record outcome.address (revm guarantees this is Some(would-be address) whenever the constructor executed). Adds positive regression tests for top-level and nested-CREATE reverts, and a negative regression test asserting wrong-reverter now fails. Co-authored-by: Amp * improve coverage * add Derek's suggested test cases * fix: forge fmt for ExpectRevert.t.sol Amp-Thread-ID: https://ampcode.com/threads/T-019dfdc5-5414-70b6-9f49-cb5797a37a29 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): keep plain Tempo broadcasts non-AA (#14616) * fix(script): don't force Tempo AA fee_token from --network tempo alone Plain --network tempo (or any selection that just sets the network to Tempo) does not by itself imply a Tempo AA / type 0x76 transaction. Defaulting tempo.common.fee_token to PATH_USD_ADDRESS solely from evm_opts.networks.is_tempo() caused every unsigned broadcast tx to flow through TempoOpts::apply, which set fee_token on the request and promoted it to the Tempo AA tx envelope. Signers that only know how to sign ordinary Ethereum transactions (e.g. the Ledger Ethereum app) then rejected the transaction with 'received an unexpected empty response'. Gate the default on an actual Tempo AA opt-in: - --batch (Tempo batch txs are themselves AA and need a fee token), or - any explicit --tempo.* flag (sponsor, expiring nonce, nonce key/lane, ...) which already forces an AA tx and benefits from a default fee token. Explicit --tempo.fee-token continues to win over the default in all cases, and non-Tempo networks never default the fee token. Add unit tests for each scenario. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't force eth_estimateGas on plain Tempo broadcasts Plain --network tempo produces an ordinary EIP-1559/legacy transaction (see tempo-alloy::TempoTransactionRequest::output_tx_type), so the local simulation gas estimate is sufficient. Forcing RPC re-estimation in this case can surface node-side errors such as 'gas required exceeds allowance (0)' (Geth-style balance/gasPrice cap from eth_estimateGas) on flows that previously worked, including Ledger-signed broadcasts that just got unblocked from the type 0x76 regression. Match tempo-foundry's behaviour: only force eth_estimateGas on Tempo when the user has actually opted into Tempo AA semantics (--batch or any explicit --tempo.* flag). Extract the gating into needs_tempo_aa_rpc_estimate(...) and add focused unit tests mirroring the fee-token gating tests. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't re-estimate plain Tempo chain broadcasts --------- Co-authored-by: Amp * fix(cheatcodes): preserve reverts with `expectEmit` (#14619) * test: added regression test * fix: re-order revert handling * refactor: simplify * lint: fmt * polish: tighten comment, extend test with revert reason and custom error Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * feat(lint): add tx-origin detector (#14589) * feat(lint): add tx-origin detector * test(lint): address tx-origin review feedback * fix: ui bless * fix(lint): cover tx-origin index and ternary predicates * test(lint): bless tx-origin snapshot --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * refactor(tempo): prepare batch access key txs w/ helper (#14597) fix(tempo): prepare batch access key txs before estimation * fix(anvil): respect non-zero genesis block in Otterscan APIs (#14490) fix(anvil): respect non-zero genesis block in Otterscan APIs The three Otterscan address-history endpoints (`ots_searchTransactionsBefore`/`After`, `ots_getTransactionBySenderAndNonce`) hardcoded `unwrap_or(1)` / `unwrap_or_default()` as the lower bound of their block scan, which breaks when `genesis_block_number` is non-zero (e.g. `genesis.json` `number: 73`). Expose `Backend::genesis_number()` and fall back to `genesis_number() + 1` in non-fork mode, mirroring the existing post-fork `f.block_number() + 1` convention. --------- Co-authored-by: Isagi Yates Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: figtracer Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Sergei Shulepov Co-authored-by: zerosnacks Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cui Co-authored-by: Centaur AI Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> Co-authored-by: Mikhail Mikheev <16622558+mmv08@users.noreply.github.com> Co-authored-by: lazymio Co-authored-by: Emma Jamieson-Hoare Co-authored-by: VIkions <99107287+vikions@users.noreply.github.com> Co-authored-by: Aïssata --- .github/scripts/commit-and-read-benchmarks.sh | 114 -- .github/scripts/commit-benchmark-results.sh | 75 + .github/scripts/compare-nightly.sh | 56 + .github/scripts/read-benchmark-results.sh | 37 + .github/scripts/tempo-check.sh | 86 +- .github/workflows/benchmarks-nightly.yml | 217 +++ .github/workflows/benchmarks.yml | 73 +- .github/workflows/ci-tempo.yml | 44 +- .github/workflows/ci.yml | 17 + .github/workflows/crate-checks.yml | 2 +- .github/workflows/docker-publish.yml | 30 + .github/workflows/nix.yml | 4 +- .github/workflows/npm.yml | 4 +- .github/workflows/release.yml | 78 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- Cargo.lock | 1011 +++++------ Cargo.toml | 98 +- README.md | 2 + SECURITY.md | 109 ++ benches/LATEST.md | 134 +- benches/src/main.rs | 40 +- benches/src/results.rs | 19 + benchmark.sh | 60 +- crates/anvil/Cargo.toml | 34 +- crates/anvil/core/Cargo.toml | 10 +- crates/anvil/src/cmd.rs | 27 +- crates/anvil/src/config.rs | 6 +- crates/anvil/src/eth/api.rs | 44 +- crates/anvil/src/eth/backend/executor.rs | 23 +- crates/anvil/src/eth/backend/mem/mod.rs | 267 ++- crates/anvil/src/eth/backend/mem/optimism.rs | 61 + .../anvil/src/eth/{error.rs => error/mod.rs} | 69 +- crates/anvil/src/eth/error/optimism.rs | 62 + crates/anvil/src/eth/otterscan/api.rs | 19 +- crates/anvil/src/eth/pool/transactions.rs | 4 +- crates/anvil/src/eth/sign.rs | 8 +- crates/anvil/src/{evm.rs => evm/mod.rs} | 84 +- crates/anvil/src/evm/optimism.rs | 87 + crates/anvil/src/lib.rs | 3 + crates/anvil/tests/it/main.rs | 1 + crates/anvil/tests/it/revert.rs | 50 + crates/cast/Cargo.toml | 17 +- crates/cast/src/args.rs | 7 + crates/cast/src/cmd/batch_mktx.rs | 27 +- crates/cast/src/cmd/batch_send.rs | 32 +- crates/cast/src/cmd/call.rs | 25 +- crates/cast/src/cmd/keychain.rs | 1022 ++++++++++- crates/cast/src/cmd/mktx.rs | 44 +- crates/cast/src/cmd/mod.rs | 3 + crates/cast/src/cmd/run.rs | 17 +- crates/cast/src/cmd/send.rs | 72 +- crates/cast/src/cmd/tempo.rs | 45 + crates/cast/src/cmd/tip20/mine.rs | 23 +- crates/cast/src/cmd/tip20/mod.rs | 2 +- crates/cast/src/cmd/vaddr/create.rs | 181 ++ crates/cast/src/cmd/vaddr/mod.rs | 131 ++ crates/cast/src/cmd/vaddr/resolve.rs | 52 + crates/cast/src/cmd/vaddr/watch.rs | 108 ++ crates/cast/src/cmd/wallet/mod.rs | 13 +- crates/cast/src/lib.rs | 4 +- crates/cast/src/opts.rs | 25 +- crates/cast/src/tempo.rs | 3 + crates/cast/src/tx.rs | 30 +- crates/cast/tests/cli/keychain.rs | 76 + crates/cast/tests/cli/main.rs | 119 ++ crates/cheatcodes/Cargo.toml | 10 + crates/cheatcodes/assets/cheatcodes.json | 4 +- crates/cheatcodes/spec/src/vm.rs | 6 + crates/cheatcodes/src/inspector.rs | 145 +- crates/cheatcodes/src/test/assert.rs | 4 +- crates/cheatcodes/src/version.rs | 67 +- crates/chisel/Cargo.toml | 8 +- crates/chisel/src/executor.rs | 1617 ++++++++--------- crates/chisel/src/source.rs | 561 ++---- crates/chisel/tests/it/repl/mod.rs | 20 + crates/cli/Cargo.toml | 7 + crates/cli/src/opts/evm.rs | 11 + crates/cli/src/opts/rpc.rs | 56 +- crates/cli/src/opts/rpc_common.rs | 7 +- crates/cli/src/opts/tempo.rs | 320 +++- crates/cli/src/utils/tempo.rs | 193 +- crates/common/Cargo.toml | 20 +- crates/common/build.rs | 20 +- crates/common/fmt/Cargo.toml | 8 +- crates/common/fmt/src/ui.rs | 8 + crates/common/src/contracts.rs | 14 +- crates/common/src/provider/mpp/keys.rs | 73 +- crates/common/src/provider/mpp/session.rs | 10 + crates/common/src/provider/mpp/transport.rs | 922 +++++++++- crates/common/src/provider/mpp/ws.rs | 4 + .../common/src/provider/runtime_transport.rs | 6 +- crates/common/src/tempo/auth.rs | 494 +++++ crates/common/src/tempo/keystore.rs | 147 +- crates/common/src/tempo/mod.rs | 186 ++ crates/common/src/transactions/builder.rs | 57 +- crates/common/src/transactions/receipt.rs | 2 + crates/config/src/fuzz.rs | 6 + crates/config/src/inline/mod.rs | 39 + crates/debugger/Cargo.toml | 8 + crates/doc/Cargo.toml | 4 + crates/doc/src/writer/as_doc.rs | 4 +- crates/evm/core/Cargo.toml | 24 +- crates/evm/core/src/decode.rs | 4 +- crates/evm/core/src/env.rs | 605 +++--- crates/evm/core/src/evm/mod.rs | 21 +- crates/evm/core/src/evm/op.rs | 22 +- crates/evm/core/src/fork/database.rs | 53 +- crates/evm/core/src/lib.rs | 3 + crates/evm/core/src/opts.rs | 7 +- crates/evm/coverage/Cargo.toml | 4 + crates/evm/evm/Cargo.toml | 13 + crates/evm/evm/src/executors/fuzz/mod.rs | 125 +- crates/evm/evm/src/executors/invariant/mod.rs | 2 +- crates/evm/fuzz/Cargo.toml | 9 + crates/evm/fuzz/src/lib.rs | 35 +- crates/evm/hardforks/Cargo.toml | 8 +- crates/evm/hardforks/src/lib.rs | 74 +- crates/evm/networks/Cargo.toml | 8 +- crates/evm/networks/src/lib.rs | 238 ++- crates/evm/networks/src/optimism.rs | 25 + crates/evm/traces/Cargo.toml | 4 + crates/fmt/Cargo.toml | 4 + crates/fmt/src/state/mod.rs | 2 +- crates/forge/Cargo.toml | 14 +- crates/forge/assets/tempo/MailTemplate.s.sol | 2 +- crates/forge/assets/tempo/MailTemplate.t.sol | 2 +- crates/forge/src/cmd/coverage.rs | 7 +- crates/forge/src/cmd/create.rs | 43 +- crates/forge/src/cmd/snapshot.rs | 7 +- crates/forge/src/cmd/test/mod.rs | 226 ++- crates/forge/src/cmd/test/summary.rs | 4 +- crates/forge/src/gas_report.rs | 2 +- crates/forge/src/multi_runner.rs | 32 + crates/forge/src/runner.rs | 24 +- crates/forge/tests/cli/cmd.rs | 4 +- crates/forge/tests/cli/config.rs | 28 + crates/forge/tests/cli/failure_assertions.rs | 7 +- crates/forge/tests/cli/inline_config.rs | 104 ++ crates/forge/tests/cli/lint.rs | 289 ++- crates/forge/tests/cli/lint/geiger.rs | 10 +- crates/forge/tests/cli/script.rs | 2 +- crates/forge/tests/cli/test_cmd/fuzz.rs | 145 ++ crates/forge/tests/cli/test_cmd/repros.rs | 60 + .../tests/fixtures/ExpectRevertFailures.t.sol | 57 + crates/lint/Cargo.toml | 4 + crates/lint/README.md | 8 + crates/lint/docs/README.md | 52 + crates/lint/docs/_template.md | 28 + crates/lint/docs/asm-keccak256.md | 42 + crates/lint/docs/block-timestamp.md | 44 + crates/lint/docs/boolean-cst.md | 37 + crates/lint/docs/boolean-equal.md | 34 + crates/lint/docs/could-be-immutable.md | 42 + crates/lint/docs/custom-errors.md | 45 + crates/lint/docs/divide-before-multiply.md | 32 + crates/lint/docs/erc20-unchecked-transfer.md | 43 + crates/lint/docs/incorrect-erc20-interface.md | 42 + .../lint/docs/incorrect-erc721-interface.md | 48 + crates/lint/docs/incorrect-shift.md | 37 + crates/lint/docs/inline-assembly.md | 69 + crates/lint/docs/interface-file-naming.md | 31 + crates/lint/docs/interface-naming.md | 31 + crates/lint/docs/missing-zero-check.md | 39 + crates/lint/docs/mixed-case-function.md | 32 + crates/lint/docs/mixed-case-variable.md | 36 + crates/lint/docs/multi-contract-file.md | 37 + crates/lint/docs/named-struct-fields.md | 31 + crates/lint/docs/pascal-case-struct.md | 31 + crates/lint/docs/pragma-inconsistent.md | 41 + crates/lint/docs/rtlo.md | 32 + .../lint/docs/screaming-snake-case-const.md | 30 + .../docs/screaming-snake-case-immutable.md | 31 + crates/lint/docs/too-many-digits.md | 32 + crates/lint/docs/tx-origin.md | 34 + crates/lint/docs/unaliased-plain-import.md | 34 + crates/lint/docs/unchecked-call.md | 34 + crates/lint/docs/unsafe-cheatcode.md | 35 + crates/lint/docs/unsafe-typecast.md | 40 + crates/lint/docs/unused-import.md | 40 + crates/lint/docs/unused-state-variables.md | 39 + crates/lint/docs/unwrapped-modifier-logic.md | 51 + crates/lint/src/linter/mod.rs | 2 + crates/lint/src/linter/project.rs | 92 + crates/lint/src/sol/info/inline_assembly.rs | 71 + crates/lint/src/sol/info/mod.rs | 12 + crates/lint/src/sol/info/pragma_directive.rs | 71 + crates/lint/src/sol/info/too_many_digits.rs | 50 + crates/lint/src/sol/macros.rs | 42 +- crates/lint/src/sol/med/mod.rs | 4 + crates/lint/src/sol/med/tx_origin.rs | 101 + crates/lint/src/sol/mod.rs | 133 +- crates/lint/testdata/BlockTimestamp.stderr | 24 +- crates/lint/testdata/BooleanCst.stderr | 10 +- crates/lint/testdata/BooleanEqual.stderr | 14 +- crates/lint/testdata/CouldBeImmutable.stderr | 14 +- crates/lint/testdata/CustomErrors.stderr | 10 +- .../lint/testdata/DivideBeforeMultiply.stderr | 12 +- crates/lint/testdata/Imports.stderr | 26 +- .../testdata/IncorrectERC20Interface.stderr | 30 +- .../testdata/IncorrectERC721Interface.stderr | 38 +- crates/lint/testdata/IncorrectShift.stderr | 10 +- crates/lint/testdata/InlineAssembly.sol | 110 ++ crates/lint/testdata/InlineAssembly.stderr | 96 + crates/lint/testdata/Keccak256.sol | 1 + crates/lint/testdata/Keccak256.stderr | 36 +- crates/lint/testdata/MissingZeroCheck.stderr | 46 +- crates/lint/testdata/MixedCase.stderr | 38 +- crates/lint/testdata/MultiContractFile.stderr | 10 +- .../MultiContractFile_InterfaceLibrary.stderr | 6 +- crates/lint/testdata/NamedStructFields.stderr | 2 +- .../PragmaInconsistentCaretAboveExact.sol | 7 + .../PragmaInconsistentCaretAboveExact.stderr | 16 + .../PragmaInconsistentCaretMatchesExact.sol | 7 + ...PragmaInconsistentCaretMatchesExact.stderr | 16 + .../PragmaInconsistentCaretVsTilde.sol | 7 + .../PragmaInconsistentCaretVsTilde.stderr | 16 + .../testdata/PragmaInconsistentOrVsExact.sol | 7 + .../PragmaInconsistentOrVsExact.stderr | 16 + .../PragmaInconsistentRangeVsExact.sol | 7 + .../PragmaInconsistentRangeVsExact.stderr | 16 + .../PragmaInconsistentThreeDistinct.sol | 8 + .../PragmaInconsistentThreeDistinct.stderr | 24 + crates/lint/testdata/Rtlo.stderr | 48 +- crates/lint/testdata/RtloCommentsOnly.stderr | 8 +- .../lint/testdata/ScreamingSnakeCase.stderr | 16 +- crates/lint/testdata/StructPascalCase.stderr | 12 +- crates/lint/testdata/TooManyDigits.sol | 73 + crates/lint/testdata/TooManyDigits.stderr | 72 + crates/lint/testdata/TxOrigin.sol | 65 + crates/lint/testdata/TxOrigin.stderr | 72 + crates/lint/testdata/UncheckedCall.stderr | 16 +- .../testdata/UncheckedTransferERC20.stderr | 22 +- crates/lint/testdata/UnsafeCheatcodes.stderr | 26 +- crates/lint/testdata/UnsafeTypecast.stderr | 330 ++-- .../lint/testdata/UnusedStateVariables.stderr | 10 +- .../testdata/UnwrappedModifierLogic.stderr | 22 +- crates/primitives/Cargo.toml | 17 +- crates/primitives/src/network/mod.rs | 10 +- crates/primitives/src/network/optimism.rs | 47 + crates/primitives/src/network/receipt.rs | 40 +- crates/primitives/src/transaction/envelope.rs | 281 +-- crates/primitives/src/transaction/mod.rs | 6 +- crates/primitives/src/transaction/optimism.rs | 300 +++ crates/primitives/src/transaction/receipt.rs | 114 +- crates/primitives/src/transaction/request.rs | 110 +- crates/script-sequence/Cargo.toml | 4 + crates/script/Cargo.toml | 12 + crates/script/src/broadcast.rs | 143 +- crates/script/src/lib.rs | 131 +- crates/script/src/runner.rs | 10 +- crates/script/src/verify.rs | 2 +- crates/sol-macro-gen/Cargo.toml | 4 + crates/test-utils/Cargo.toml | 4 + crates/verify/Cargo.toml | 9 + docs/dev/lintrules.md | 2 + flake.lock | 18 +- foundryup/README.md | 4 +- foundryup/foundryup | 135 +- testdata/default/cheats/ExpectRevert.t.sol | 85 + .../default/cheats/GetFoundryVersion.t.sol | 51 + testdata/default/cheats/MockCall.t.sol | 41 +- testdata/default/cheats/MockCalls.t.sol | 4 + 264 files changed, 13431 insertions(+), 4233 deletions(-) delete mode 100755 .github/scripts/commit-and-read-benchmarks.sh create mode 100755 .github/scripts/commit-benchmark-results.sh create mode 100755 .github/scripts/compare-nightly.sh create mode 100755 .github/scripts/read-benchmark-results.sh create mode 100644 .github/workflows/benchmarks-nightly.yml create mode 100644 crates/anvil/src/eth/backend/mem/optimism.rs rename crates/anvil/src/eth/{error.rs => error/mod.rs} (91%) create mode 100644 crates/anvil/src/eth/error/optimism.rs rename crates/anvil/src/{evm.rs => evm/mod.rs} (64%) create mode 100644 crates/anvil/src/evm/optimism.rs create mode 100644 crates/cast/src/cmd/tempo.rs create mode 100644 crates/cast/src/cmd/vaddr/create.rs create mode 100644 crates/cast/src/cmd/vaddr/mod.rs create mode 100644 crates/cast/src/cmd/vaddr/resolve.rs create mode 100644 crates/cast/src/cmd/vaddr/watch.rs create mode 100644 crates/cast/src/tempo.rs create mode 100644 crates/cast/tests/cli/keychain.rs create mode 100644 crates/common/src/tempo/auth.rs create mode 100644 crates/evm/networks/src/optimism.rs create mode 100644 crates/lint/docs/README.md create mode 100644 crates/lint/docs/_template.md create mode 100644 crates/lint/docs/asm-keccak256.md create mode 100644 crates/lint/docs/block-timestamp.md create mode 100644 crates/lint/docs/boolean-cst.md create mode 100644 crates/lint/docs/boolean-equal.md create mode 100644 crates/lint/docs/could-be-immutable.md create mode 100644 crates/lint/docs/custom-errors.md create mode 100644 crates/lint/docs/divide-before-multiply.md create mode 100644 crates/lint/docs/erc20-unchecked-transfer.md create mode 100644 crates/lint/docs/incorrect-erc20-interface.md create mode 100644 crates/lint/docs/incorrect-erc721-interface.md create mode 100644 crates/lint/docs/incorrect-shift.md create mode 100644 crates/lint/docs/inline-assembly.md create mode 100644 crates/lint/docs/interface-file-naming.md create mode 100644 crates/lint/docs/interface-naming.md create mode 100644 crates/lint/docs/missing-zero-check.md create mode 100644 crates/lint/docs/mixed-case-function.md create mode 100644 crates/lint/docs/mixed-case-variable.md create mode 100644 crates/lint/docs/multi-contract-file.md create mode 100644 crates/lint/docs/named-struct-fields.md create mode 100644 crates/lint/docs/pascal-case-struct.md create mode 100644 crates/lint/docs/pragma-inconsistent.md create mode 100644 crates/lint/docs/rtlo.md create mode 100644 crates/lint/docs/screaming-snake-case-const.md create mode 100644 crates/lint/docs/screaming-snake-case-immutable.md create mode 100644 crates/lint/docs/too-many-digits.md create mode 100644 crates/lint/docs/tx-origin.md create mode 100644 crates/lint/docs/unaliased-plain-import.md create mode 100644 crates/lint/docs/unchecked-call.md create mode 100644 crates/lint/docs/unsafe-cheatcode.md create mode 100644 crates/lint/docs/unsafe-typecast.md create mode 100644 crates/lint/docs/unused-import.md create mode 100644 crates/lint/docs/unused-state-variables.md create mode 100644 crates/lint/docs/unwrapped-modifier-logic.md create mode 100644 crates/lint/src/linter/project.rs create mode 100644 crates/lint/src/sol/info/inline_assembly.rs create mode 100644 crates/lint/src/sol/info/pragma_directive.rs create mode 100644 crates/lint/src/sol/info/too_many_digits.rs create mode 100644 crates/lint/src/sol/med/tx_origin.rs create mode 100644 crates/lint/testdata/InlineAssembly.sol create mode 100644 crates/lint/testdata/InlineAssembly.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.sol create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr create mode 100644 crates/lint/testdata/TooManyDigits.sol create mode 100644 crates/lint/testdata/TooManyDigits.stderr create mode 100644 crates/lint/testdata/TxOrigin.sol create mode 100644 crates/lint/testdata/TxOrigin.stderr create mode 100644 crates/primitives/src/network/optimism.rs create mode 100644 crates/primitives/src/transaction/optimism.rs diff --git a/.github/scripts/commit-and-read-benchmarks.sh b/.github/scripts/commit-and-read-benchmarks.sh deleted file mode 100755 index 358b53a73155a..0000000000000 --- a/.github/scripts/commit-and-read-benchmarks.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Script to commit benchmark results and read them for GitHub Actions output -# Usage: ./commit-and-read-benchmarks.sh - -OUTPUT_DIR="${1:-benches}" -GITHUB_EVENT_NAME="${2:-pull_request}" -GITHUB_REPOSITORY="${3:-}" - -# Global variable for branch name -BRANCH_NAME="" - -# Function to commit benchmark results -commit_results() { - echo "Configuring git..." - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # For PR runs, fetch and checkout the PR branch to ensure we're up to date - if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" - git fetch origin "$GITHUB_HEAD_REF" - git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" - fi - - echo "Adding benchmark file..." - git add "$OUTPUT_DIR/LATEST.md" - - if git diff --staged --quiet; then - echo "No changes to commit" - else - echo "Committing benchmark results..." - git commit -m "chore(\`benches\`): update benchmark results - -🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) - -Co-Authored-By: github-actions " - - echo "Pushing to repository..." - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - # For manual runs, we're on a new branch - git push origin "$BRANCH_NAME" - elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - # For PR runs, push to the PR branch - if [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Pushing to PR branch: $GITHUB_HEAD_REF" - git push origin "$GITHUB_HEAD_REF" - else - echo "Error: GITHUB_HEAD_REF not set for pull_request event" - exit 1 - fi - else - # This workflow should only run on workflow_dispatch or pull_request - echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" - echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" - exit 1 - fi - echo "Successfully pushed benchmark results" - fi -} - -# Function to read benchmark results and output for GitHub Actions -read_results() { - if [ -f "$OUTPUT_DIR/LATEST.md" ]; then - echo "Reading benchmark results..." - - # Output full results - { - echo 'results<> "$GITHUB_OUTPUT" - - # Format results for PR comment - echo "Formatting results for PR comment..." - FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") - - { - echo 'pr_comment<> "$GITHUB_OUTPUT" - - echo "Successfully read and formatted benchmark results" - else - echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" - fi -} - -# Main execution -echo "Starting benchmark results processing..." - -# Create new branch for manual runs -if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - echo "Manual workflow run detected, creating new branch..." - BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$BRANCH_NAME" - echo "Created branch: $BRANCH_NAME" - - # Output branch name for later use - echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" -fi - -# Always commit benchmark results -echo "Committing benchmark results..." -commit_results - -# Always read results for output -read_results - -echo "Benchmark results processing complete" \ No newline at end of file diff --git a/.github/scripts/commit-benchmark-results.sh b/.github/scripts/commit-benchmark-results.sh new file mode 100755 index 0000000000000..f7dba8980fd64 --- /dev/null +++ b/.github/scripts/commit-benchmark-results.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +# Script to commit and push benchmark results. +# +# This script is intended to run from the lightweight `publish-results` job, +# which checks out the repo with credentials and only operates on the +# trusted artifact produced by the benchmark job. Keeping the write-scoped +# token away from the bench job (which runs untrusted third-party builds) +# limits the blast radius of a compromised dependency. +# +# Usage: ./commit-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" +GITHUB_EVENT_NAME="${2:-workflow_dispatch}" +GITHUB_REPOSITORY="${3:-}" + +if [ ! -f "$OUTPUT_DIR/LATEST.md" ]; then + echo "Error: $OUTPUT_DIR/LATEST.md not found, nothing to commit" + exit 1 +fi + +echo "Configuring git..." +git config --local user.email "action@github.com" +git config --local user.name "GitHub Action" + +# Decide which branch to commit to based on the event. +BRANCH_NAME="" +case "$GITHUB_EVENT_NAME" in + workflow_dispatch) + echo "Manual workflow run detected, creating new branch..." + BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH_NAME" + echo "Created branch: $BRANCH_NAME" + ;; + pull_request) + if [ -z "${GITHUB_HEAD_REF:-}" ]; then + echo "Error: GITHUB_HEAD_REF not set for pull_request event" + exit 1 + fi + echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" + git fetch origin "$GITHUB_HEAD_REF" + git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" + BRANCH_NAME="$GITHUB_HEAD_REF" + ;; + *) + echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" + echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" + exit 1 + ;; +esac + +# Always emit the branch name so downstream steps (e.g. PR creation) can use it. +echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + +echo "Adding benchmark file..." +git add "$OUTPUT_DIR/LATEST.md" + +if git diff --staged --quiet; then + echo "No changes to commit" + echo "committed=false" >> "$GITHUB_OUTPUT" + exit 0 +fi + +echo "Committing benchmark results..." +git commit -m "chore(\`benches\`): update benchmark results + +🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) + +Co-Authored-By: github-actions " + +echo "Pushing to repository..." +git push origin "$BRANCH_NAME" +echo "Successfully pushed benchmark results to $BRANCH_NAME" +echo "committed=true" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh new file mode 100755 index 0000000000000..674cc0fe01754 --- /dev/null +++ b/.github/scripts/compare-nightly.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Compare two nightly benchmark JSON summaries and report regressions. +# +# Usage: compare-nightly.sh [warn_pct] [fail_pct] +# Exits 0 if no regressions, 1 if any metric exceeds fail_pct. +# Exits 0 gracefully when prev.json is missing (first run / gap > 7 days). +set -euo pipefail + +PREV_JSON="${1:-}" +TODAY_JSON="${2:-}" +WARN="${3:-1}" +FAIL="${4:-3}" + +PREV_JSON="$PREV_JSON" TODAY_JSON="$TODAY_JSON" WARN="$WARN" FAIL="$FAIL" \ +python3 - <<'EOF' +import json, os, sys + +warn = float(os.environ["WARN"]) +fail = float(os.environ["FAIL"]) + +prev_path = os.environ.get("PREV_JSON", "") +prev = json.load(open(prev_path)) if prev_path and os.path.isfile(prev_path) else {} +with open(os.environ["TODAY_JSON"]) as f: + today = json.load(f) + +print("## Nightly Benchmark Regression Report\n") +print("| Benchmark | Previous | Today | Δ | Status |") +print("|-----------|----------|-------|---|--------|") + +has_regression = False +all_keys = sorted(prev.keys() | today.keys()) +for key in all_keys: + t = today.get(key) + p = prev.get(key) + if t is None: + print(f"| `{key}` | {p:.5f}s | N/A | — | ⚠️ Missing |") + has_regression = True + continue + if p is None: + print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") + continue + delta = (t - p) / p * 100 + if delta >= fail: + status = "🔴 Regression" + has_regression = True + elif delta >= warn: + status = "🟡 Warning" + elif delta <= -warn: + status = "🟢 Improvement" + else: + status = "➡️ OK" + sign = "+" if delta > 0 else "" + print(f"| `{key}` | {p}s | {t}s | {sign}{delta:.1f}% | {status} |") + +sys.exit(1 if has_regression else 0) +EOF diff --git a/.github/scripts/read-benchmark-results.sh b/.github/scripts/read-benchmark-results.sh new file mode 100755 index 0000000000000..548611a7d204a --- /dev/null +++ b/.github/scripts/read-benchmark-results.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +# Script to read benchmark results and emit them as GitHub Actions outputs. +# This script performs no git operations — it only reads the combined +# benchmark file and writes outputs for the workflow to consume. +# +# Usage: ./read-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" + +echo "Reading benchmark results from $OUTPUT_DIR..." + +if [ -f "$OUTPUT_DIR/LATEST.md" ]; then + # Output full results + { + echo 'results<> "$GITHUB_OUTPUT" + + # Format results for PR comment + echo "Formatting results for PR comment..." + FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") + + { + echo 'pr_comment<> "$GITHUB_OUTPUT" + + echo "Successfully read and formatted benchmark results" +else + echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" +fi diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh index b730c466bde55..3caea992cfe7e 100755 --- a/.github/scripts/tempo-check.sh +++ b/.github/scripts/tempo-check.sh @@ -445,7 +445,7 @@ echo -e "\n=== CAST SEND WITH SPONSOR (--tempo.sponsor-signature) ===" # Test sponsored transactions using pre-signed signature. # Step 1: Get the fee_payer_signature_hash using --tempo.print-sponsor-hash # Step 2: Sign it with the sponsor's private key -# Step 3: Send with --tempo.sponsor-signature +# Step 3: Send with --tempo.sponsor and --tempo.sponsor-signature # Step 1: Get the hash that the sponsor needs to sign FEE_PAYER_HASH=$(cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ @@ -460,7 +460,7 @@ printf "Sponsor signature: %s\n" "$SPONSOR_SIG" # Step 3: Send the sponsored transaction with the signature RECEIPT=$(cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" \ - --tempo.sponsor-signature "$SPONSOR_SIG" --json) + --tempo.sponsor "$SPONSOR_ADDR" --tempo.sponsor-signature "$SPONSOR_SIG" --json) # Verify the fee_payer in the receipt matches the sponsor address RECEIPT_FEE_PAYER=$(echo "$RECEIPT" | jq -r '.feePayer // .fee_payer // empty') @@ -897,3 +897,85 @@ check_has_code "Nonce" "0x4e4F4E4345000000000000000000000000000000" check_has_code "AccountKeychain" "0xaAAAaaAA00000000000000000000000000000000" echo -e "\n=== CHISEL FORK TESTS COMPLETE ===" + +# --- cast virtual-address (TIP-1022) tests --- + +echo -e "\n=== CAST VIRTUAL-ADDRESS: SETUP MASTER WALLET ===" +vaddr_master_json="$(cast wallet new --json)" +VADDR_MASTER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_master_json")" +VADDR_MASTER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_master_json")" +printf "Master address: %s\n" "$VADDR_MASTER_ADDR" +fund_and_wait "$VADDR_MASTER_ADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: CREATE (mine + register) ===" +# Use the `vaddr` alias to also exercise it. +VADDR_CREATE_OUT=$(cast vaddr create \ +--owner "$VADDR_MASTER_ADDR" \ +--private-key "$VADDR_MASTER_PK" \ +--rpc-url "$ETH_RPC_URL") +echo "$VADDR_CREATE_OUT" +VADDR=$(echo "$VADDR_CREATE_OUT" | sed -n 's/^ tag=0x000000000000 \(0x[a-fA-F0-9]\{40\}\).*/\1/p' | head -1) +if [[ -z "$VADDR" ]]; then +echo "ERROR: failed to parse virtual address from create output" +exit 1 +fi +echo "Virtual address: $VADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: RESOLVE ===" +VADDR_RESOLVE_OUT=$(cast virtual-address resolve "$VADDR" --rpc-url "$ETH_RPC_URL") +echo "$VADDR_RESOLVE_OUT" +RESOLVED_MASTER=$(echo "$VADDR_RESOLVE_OUT" | sed -n 's/^Master address: \(0x[a-fA-F0-9]\{40\}\).*/\1/p') +RESOLVED_LOWER=$(echo "$RESOLVED_MASTER" | tr '[:upper:]' '[:lower:]') +EXPECTED_LOWER=$(echo "$VADDR_MASTER_ADDR" | tr '[:upper:]' '[:lower:]') +if [[ "$RESOLVED_LOWER" != "$EXPECTED_LOWER" ]]; then +echo "ERROR: resolve returned master $RESOLVED_MASTER, expected $VADDR_MASTER_ADDR" +exit 1 +fi +echo "OK: resolve returned the registered master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: AUTO-FORWARD TO MASTER ===" +# Create a separate sender, fund it, and transfer the fee token to the +# virtual address. The protocol must auto-forward to the master wallet. +vaddr_sender_json="$(cast wallet new --json)" +VADDR_SENDER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_sender_json")" +VADDR_SENDER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_sender_json")" +fund_and_wait "$VADDR_SENDER_ADDR" + +BAL_BEFORE=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance before: $BAL_BEFORE" + +# Capture the current block before the transfer so `cast vaddr watch` can +# replay the Transfer log via --from-block. +BLOCK_BEFORE_TRANSFER=$(cast block-number --rpc-url "$ETH_RPC_URL") + +AMOUNT=1000000 +cast send "$FEE_TOKEN" 'transfer(address,uint256)' "$VADDR" "$AMOUNT" \ +--rpc-url "$ETH_RPC_URL" --private-key "$VADDR_SENDER_PK" + +BAL_AFTER=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance after: $BAL_AFTER" + +EXPECTED=$((BAL_BEFORE + AMOUNT)) +if [[ "$BAL_AFTER" != "$EXPECTED" ]]; then +echo "ERROR: master balance grew by $((BAL_AFTER - BAL_BEFORE)), expected $AMOUNT" +exit 1 +fi +echo "OK: transfer to virtual address auto-forwarded to master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: WATCH ===" +# Tail incoming TIP-20 transfers to the virtual address. `cast vaddr watch` +# polls indefinitely, so we cap it with `timeout`; the historical fetch via +# --from-block emits the prior Transfer log immediately at startup. +WATCH_OUT=$(timeout 5 cast vaddr watch "$VADDR" \ + --token "$FEE_TOKEN" \ + --from-block "$BLOCK_BEFORE_TRANSFER" \ + --rpc-url "$ETH_RPC_URL" 2>&1 || true) +echo "$WATCH_OUT" + +EXPECTED_PATTERN="token=$FEE_TOKEN from=$VADDR_SENDER_ADDR amount=$AMOUNT" +echo "Expected pattern: $EXPECTED_PATTERN" +if ! echo "$WATCH_OUT" | grep -iqF "$EXPECTED_PATTERN"; then + echo "ERROR: cast vaddr watch output did not contain expected '$EXPECTED_PATTERN'" + exit 1 +fi +echo "OK: cast vaddr watch reported correct token/from/amount" diff --git a/.github/workflows/benchmarks-nightly.yml b/.github/workflows/benchmarks-nightly.yml new file mode 100644 index 0000000000000..8569f52ce3b93 --- /dev/null +++ b/.github/workflows/benchmarks-nightly.yml @@ -0,0 +1,217 @@ +name: Nightly Benchmarks (AAVE v4) + +permissions: {} + +on: + schedule: + - cron: "0 2 * * *" # 2am UTC nightly + workflow_dispatch: # allow manual triggering for testing + +env: + AAVE_V4_REPO: "aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" + RUSTC_WRAPPER: "sccache" + +jobs: + run-benchmarks: + name: Run Nightly Benchmarks + runs-on: depot-ubuntu-24.04-32 + permissions: + contents: read + actions: read # needed to download artifacts from previous runs + outputs: + has_regression: ${{ steps.compare.outputs.has_regression }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + + - name: Setup Foundry + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + ./.github/scripts/setup-foundryup.sh + printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" + + - name: Build benchmark binary + run: cargo build --locked --release --bin foundry-bench + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + - name: Install hyperfine + run: | + curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ + rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu + + - name: Download previous benchmark results + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p prev-results + PREV_RUN_ID=$(gh run list \ + --workflow=benchmarks-nightly.yml \ + --status=success \ + --limit=1 \ + --json databaseId \ + -q '.[0].databaseId // empty' 2>/dev/null || true) + if [[ -n "$PREV_RUN_ID" ]]; then + echo "Downloading results from previous run $PREV_RUN_ID" + gh run download "$PREV_RUN_ID" \ + --name nightly-bench-results \ + --dir prev-results/ || true + else + echo "No previous successful run found, skipping download." + fi + + - name: Run forge test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_test \ + --json-output "nightly-${DATE}-forge_test.json" \ + --verbose + + - name: Run forge fuzz test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_fuzz_test \ + --json-output "nightly-${DATE}-forge_fuzz_test.json" \ + --verbose + + - name: Run forge isolate test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_isolate_test \ + --json-output "nightly-${DATE}-forge_isolate_test.json" \ + --verbose + + - name: Run forge build benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --json-output "nightly-${DATE}-forge_build.json" \ + --verbose + + - name: Run forge coverage benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_coverage \ + --json-output "nightly-${DATE}-forge_coverage.json" \ + --verbose + + - name: Merge benchmark JSON results + run: | + DATE=$(date -u +%Y-%m-%d) + shopt -s nullglob + parts=( benches/nightly-${DATE}-*.json ) + if [[ ${#parts[@]} -eq 0 ]]; then + echo "No benchmark results produced — all steps failed." + exit 1 + fi + jq -s 'add' "${parts[@]}" > "benches/nightly-${DATE}.json" + echo "Merged ${#parts[@]} result file(s) into nightly-${DATE}.json" + + - name: Compare with previous results + id: compare + run: | + DATE=$(date -u +%Y-%m-%d) + PREV_JSON=$(ls prev-results/nightly-*.json 2>/dev/null | head -1 || true) + TODAY_JSON="benches/nightly-${DATE}.json" + if ./.github/scripts/compare-nightly.sh "$PREV_JSON" "$TODAY_JSON" > regression.md 2>&1; then + echo "has_regression=false" >> "$GITHUB_OUTPUT" + else + echo "has_regression=true" >> "$GITHUB_OUTPUT" + fi + cat regression.md + + - name: Upload benchmark results + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: nightly-bench-results + retention-days: 7 + path: | + benches/nightly-*.json + regression.md + + report-regression: + name: Report Regression + needs: run-benchmarks + if: needs.run-benchmarks.outputs.has_regression == 'true' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Download benchmark results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: nightly-bench-results + path: results/ + + - name: Open regression issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + DATE=$(date -u +%Y-%m-%d) + BODY="$(cat results/regression.md) + + --- + + **Run**: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + **Date**: ${DATE} + **Repo benchmarked**: \`aave/aave-v4\` (pinned commit) + **Threshold**: 🔴 >=3% regression, 🟡 >=1% warning" + + gh issue create \ + --title "[Nightly Regression] ${DATE}" \ + --body "$BODY" \ + --label "regression" \ + --repo "$GH_REPO" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5d4767ad3b554..a136703abc294 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -10,17 +10,17 @@ on: required: false type: string versions: - description: "Comma-separated list of Foundry versions to benchmark (e.g., stable,nightly,v1.0.0)" + description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'v1.5.1,v1.7.0')" required: false type: string - default: "stable,nightly" repos: - description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev][ ] (e.g. vectorized/solady:v0.1.26 --nmc BrokenTest). Leave empty to use the per-benchmark default repo lists." + description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev] (e.g. aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35). Leave empty to use the per-benchmark default repo lists." required: false type: string - default: "" env: + DEFAULT_VERSIONS: "v1.5.1,v1.7.0" + # Repos to benchmark per step. Each comma-separated entry has the form # org/repo[:rev][ ] # where anything after the first whitespace is appended to every benchmark @@ -29,27 +29,23 @@ env: TEST_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting ISOLATE_TEST_REPOS: >- ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest, - vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, + vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest', uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting BUILD_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 COVERAGE_REPOS: >- ithacaxyz/account:v0.5.7, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 @@ -60,7 +56,7 @@ jobs: name: Run All Benchmarks runs-on: depot-ubuntu-24.04-32 permissions: - contents: write + contents: read steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -93,7 +89,7 @@ jobs: run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" @@ -106,59 +102,61 @@ jobs: - name: Run forge test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_test,forge_fuzz_test \ - --output-file forge_test_bench.md + --output-file forge_test_bench.md \ + --verbose - name: Run forge isolate test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.ISOLATE_TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_isolate_test \ - --output-file forge_isolate_test_bench.md + --output-file forge_isolate_test_bench.md \ + --verbose - name: Run forge build benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.BUILD_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_build_no_cache,forge_build_with_cache \ - --output-file forge_build_bench.md + --output-file forge_build_bench.md \ + --verbose - name: Run forge coverage benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.COVERAGE_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_coverage \ - --output-file forge_coverage_bench.md + --output-file forge_coverage_bench.md \ + --verbose - name: Combine benchmark results run: ./.github/scripts/combine-benchmarks.sh benches - - name: Commit and read benchmark results + - name: Read benchmark results id: benchmark_results - env: - GITHUB_HEAD_REF: ${{ github.head_ref }} - run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" + run: ./.github/scripts/read-benchmark-results.sh benches - name: Upload benchmark results as artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -172,21 +170,21 @@ jobs: benches/LATEST.md outputs: - branch_name: ${{ steps.benchmark_results.outputs.branch_name }} pr_comment: ${{ steps.benchmark_results.outputs.pr_comment }} publish-results: name: Publish Results needs: run-benchmarks runs-on: ubuntu-latest + # All git writes happen here, on a clean ubuntu-latest runner that has + # never executed third-party benchmark code. permissions: contents: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false + # persist-credentials defaults to true so we can push. - name: Download benchmark results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -194,19 +192,22 @@ jobs: name: benchmark-results path: benches/ - - name: Push branch for manual runs - if: github.event_name == 'workflow_dispatch' - run: | - git push origin "${{ needs.run-benchmarks.outputs.branch_name }}" - echo "Pushed branch: ${{ needs.run-benchmarks.outputs.branch_name }}" + - name: Commit benchmark results + id: commit_results + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: ./.github/scripts/commit-benchmark-results.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Create PR for manual runs - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && steps.commit_results.outputs.committed == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BRANCH_NAME: ${{ steps.commit_results.outputs.branch_name }} + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | - const branchName = '${{ needs.run-benchmarks.outputs.branch_name }}'; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const branchName = process.env.BRANCH_NAME; + const prComment = process.env.PR_COMMENT; // Create the pull request const { data: pr } = await github.rest.pulls.create({ @@ -231,10 +232,12 @@ jobs: - name: Comment on PR if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const prComment = process.env.PR_COMMENT; const comment = `${prComment} diff --git a/.github/workflows/ci-tempo.yml b/.github/workflows/ci-tempo.yml index ad4c424b7e8b2..1ef4c760f324e 100644 --- a/.github/workflows/ci-tempo.yml +++ b/.github/workflows/ci-tempo.yml @@ -69,14 +69,16 @@ jobs: run: | cargo test --locked -p foundry-common --lib tempo::tests::test_fork_schedule_parses_configured_rpcs -- --exact --nocapture - - name: Run Tempo check on devnet + - name: Run Tempo check on mainnet if: | - github.event_name == 'push' || - github.event_name == 'pull_request' || - github.event.inputs.network == 'devnet' || + github.event_name == 'schedule' || + github.event.inputs.network == 'mainnet' || github.event.inputs.network == 'all' env: - ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} + TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" + VERIFIER_URL: ${{ secrets.VERIFIER_URL }} + PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} run: | if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then @@ -86,16 +88,6 @@ jobs: ./.github/scripts/tempo-deploy.sh fi - - name: Run Tempo wallet tests on devnet - if: | - github.event_name == 'push' || - github.event_name == 'pull_request' || - github.event.inputs.network == 'devnet' || - github.event.inputs.network == 'all' - env: - ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} - run: ./.github/scripts/tempo-wallet.sh - - name: Run Tempo check on testnet if: | github.event_name == 'schedule' || @@ -113,16 +105,14 @@ jobs: ./.github/scripts/tempo-deploy.sh fi - - name: Run Tempo check on mainnet + - name: Run Tempo check on devnet if: | - github.event_name == 'schedule' || - github.event.inputs.network == 'mainnet' || + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || github.event.inputs.network == 'all' env: - ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} - TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" - VERIFIER_URL: ${{ secrets.VERIFIER_URL }} - PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} run: | if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then @@ -132,6 +122,16 @@ jobs: ./.github/scripts/tempo-deploy.sh fi + - name: Run Tempo wallet tests on devnet + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + run: ./.github/scripts/tempo-wallet.sh + # If the nightly run fails, this will create an issue to signal so. issue: name: Open an issue diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb90a76cdbfd..a434028fdd18c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,23 @@ jobs: - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo test --workspace --doc --locked + no-default-features: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo build --workspace --no-default-features --locked + typos: runs-on: depot-ubuntu-latest timeout-minutes: 30 diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index eb865bddc10e3..f0d460da6fbb1 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -29,7 +29,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b97a99d5310a4..6120735657ee6 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -32,6 +32,8 @@ jobs: name: build and push runs-on: depot-ubuntu-latest permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -92,6 +94,7 @@ jobs: uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 - name: Build and push Foundry image + id: build uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 with: build-args: | @@ -106,3 +109,30 @@ jobs: platforms: linux/amd64,linux/arm64 push: true no-cache: true + sbom: true + provenance: mode=max + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign image with cosign (keyless) + env: + DOCKER_TAGS: ${{ steps.docker_tagging.outputs.docker_tags }} + DIGEST: ${{ steps.build.outputs.digest }} + shell: bash + run: | + set -euo pipefail + IFS=',' read -r -a tags <<< "$DOCKER_TAGS" + for tag in "${tags[@]}"; do + # Strip any tag suffix and pin to immutable digest, then sign. + ref="${tag%%:*}@${DIGEST}" + printf 'Signing %s\n' "$ref" + cosign sign --yes "$ref" + done + + - name: Image build provenance attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0a18c99a41f82..8528b71f299a9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index fdc5e5716c577..323059e99e6b6 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -143,7 +143,7 @@ jobs: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -259,7 +259,7 @@ jobs: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682c9214284f6..38fa791fb655f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2 + uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2.1 with: configuration: "./.github/changelog.json" fromTag: ${{ steps.release_info.outputs.from_tag || '' }} @@ -117,6 +117,8 @@ jobs: needs: prepare uses: ./.github/workflows/docker-publish.yml permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -129,9 +131,10 @@ jobs: # way, GitHub's immutable-releases setting seals the release at publish. release: permissions: - id-token: write - contents: write attestations: write + artifact-metadata: write + contents: write + id-token: write name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 @@ -264,6 +267,38 @@ jobs: printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" fi printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" + printf "foundry_sbom=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.spdx.json" >> "$GITHUB_OUTPUT" + printf "foundry_checksum=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sha256" >> "$GITHUB_OUTPUT" + printf "foundry_signature=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sigstore.json" >> "$GITHUB_OUTPUT" + + - name: Generate archive checksum + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + shell: bash + run: | + set -euo pipefail + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + else + shasum -a 256 "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + fi + cat "$FOUNDRY_CHECKSUM" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + + - name: Generate SBOM (SPDX) + env: + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash + run: | + set -euo pipefail + syft scan dir:. \ + --source-name foundry \ + --source-version "$VERSION_NAME" \ + -o spdx-json="$FOUNDRY_SBOM" - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -292,15 +327,37 @@ jobs: tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - - name: Binaries attestation + - name: Binaries and archive provenance attestation id: attestation - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: | ${{ env.anvil_bin_path }} ${{ env.cast_bin_path }} ${{ env.chisel_bin_path }} ${{ env.forge_bin_path }} + ${{ steps.artifacts.outputs.file_name }} + + - name: Archive SBOM attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: ${{ steps.artifacts.outputs.file_name }} + sbom-path: ${{ steps.artifacts.outputs.foundry_sbom }} + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign archive with cosign (keyless) + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} + shell: bash + run: | + set -euo pipefail + cosign sign-blob \ + --yes \ + --bundle "$FOUNDRY_SIGNATURE" \ + "$FILE_NAME" - name: Record attestation URL env: @@ -321,11 +378,20 @@ jobs: TAG_NAME: ${{ needs.prepare.outputs.tag_name }} FILE_NAME: ${{ steps.artifacts.outputs.file_name }} FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} FOUNDRY_MAN: ${{ steps.man.outputs.foundry_man }} shell: bash run: | set -euo pipefail - files=("$FILE_NAME" "$FOUNDRY_ATTESTATION") + files=( + "$FILE_NAME" + "$FOUNDRY_ATTESTATION" + "$FOUNDRY_SBOM" + "$FOUNDRY_CHECKSUM" + "$FOUNDRY_SIGNATURE" + ) if [[ -n "${FOUNDRY_MAN:-}" ]]; then files+=("$FOUNDRY_MAN") fi diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index d6244f826887e..9caa254f05c10 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 141e3a049a73b..6763f1f80bde3 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,7 +37,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaa46c8e79f7c..daa4822b6e395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,14 +73,14 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest # External tests dependencies - name: Setup Node.js if: contains(matrix.name, 'external') - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 - name: Install Bun diff --git a/Cargo.lock b/Cargo.lock index 781b2c7f02b50..9db38f1967f4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,21 +77,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a547705d5c1b42575a0542bae2ba45bc62a6154be86611afaef1c0ab5c38598e" +checksum = "d8010fc7e9e8643ef4e758cdccf3eef26734594aedf88a9d5ed35e51837d42ef" dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -116,14 +116,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -143,23 +143,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" +checksum = "de3df0aadc569a8b277808a7d0ad0e421180654ea36a3c59e9ed2bb968c9a1cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250ba1168b8a049185a68c4dfa7f2a6a4046bd26fcc8c68632caeb216a5e12dc" +checksum = "1ceb16e7fe5a95825305f218ccd356665f848831f94ce2bbf55339bf5d21e88a" dependencies = [ "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] @@ -265,13 +265,14 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "once_cell", "serde", ] @@ -300,9 +301,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -310,7 +311,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "auto_impl", "borsh", "c-kzg", @@ -325,9 +326,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a8c1330ad33c95b5958573bca9a1ad0b419a51d76bb4c521556fbba8539b8d" +checksum = "d5638cbbffb318d440fdb009de019090d8d117dae40de9d10cdb29891ea59eb9" dependencies = [ "alloy-contract", "alloy-primitives", @@ -339,12 +340,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.33.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc4b83cb672156663e6094d098beb509965b7fe684bb3d6e44bb9ca2e9ae714" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -359,13 +360,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "borsh", "serde", @@ -400,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -415,19 +416,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", @@ -441,24 +442,24 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-op-evm" version = "0.31.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -472,7 +473,7 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" version = "0.4.7" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -513,13 +514,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -547,7 +548,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "thiserror 2.0.18", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -603,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -616,7 +617,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "tokio", @@ -629,9 +630,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -640,44 +641,44 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -689,9 +690,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -702,15 +703,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -722,17 +723,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -743,13 +744,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", "thiserror 2.0.18", @@ -757,13 +758,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] @@ -780,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "serde", @@ -791,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -808,9 +809,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a57d1e72b1f9b11e5e71ebdab0569cb02277a462bbea6793fcaebfcd794ae9" +checksum = "1258987fbc82716b5153ec7bb95a8a295e7640871b8f03d8ec7c4000dc80c215" dependencies = [ "alloy-consensus", "alloy-network", @@ -827,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b27f20b5298b76a5a3b7cdbe6bdb184ab1ebd6e120e00dad748867673f5c90" +checksum = "7ffc2a49bca5b73c6964711b57452f6c36a6bcb7f845ab7e9ad05b5a828d0161" dependencies = [ "alloy-consensus", "alloy-network", @@ -845,9 +846,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c7acc40ffbfd37d4113eb619863099f3235d78d044006a1eecb94d8b0b2f1a" +checksum = "94e11ddaddfb98c1ddce737dc440225565b0ae0987ac9ad5e59a85db5904878c" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -865,9 +866,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -885,9 +886,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a09a865ae9e1f05478429ef0d935b16467f35c6e0b02cb10f23f66a3b33fc3" +checksum = "44eb341d0013784da6a39e5bbdc11b95d6744993b12a1c3fd55df795a850dd42" dependencies = [ "alloy-consensus", "alloy-network", @@ -902,9 +903,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb8218544ab635281f1be180a1cfd9b5d549db686faa7e85b3b2c10969819e" +checksum = "82ff16b4166fb90bbe79bd1e49244824fb3cadc6b8cd11e9c8a002c1f8c07492" dependencies = [ "alloy-consensus", "alloy-network", @@ -991,9 +992,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1014,14 +1015,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -1030,9 +1031,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1050,9 +1051,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1085,9 +1086,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1223,13 +1224,13 @@ dependencies = [ [[package]] name = "anvil" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1241,7 +1242,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-beacon", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1276,7 +1277,7 @@ dependencies = [ "parking_lot", "rand 0.8.6", "rand 0.9.4", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -1297,17 +1298,17 @@ dependencies = [ [[package]] name = "anvil-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip5792", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "foundry-common", "foundry-evm", @@ -1321,7 +1322,7 @@ dependencies = [ [[package]] name = "anvil-rpc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "serde", "serde_json", @@ -1329,7 +1330,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.6.0" +version = "1.7.1" dependencies = [ "anvil-rpc", "async-trait", @@ -1669,20 +1670,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ascii-canvas" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" -dependencies = [ - "term", -] - [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1955,9 +1947,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.101.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -2172,9 +2164,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.14" +version = "1.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -2361,9 +2353,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -2579,7 +2571,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -2745,13 +2737,13 @@ dependencies = [ [[package]] name = "cast" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-evm", "alloy-hardforks", @@ -2763,7 +2755,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -2822,9 +2814,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -2832,12 +2824,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -2876,7 +2862,7 @@ dependencies = [ [[package]] name = "chisel" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2890,10 +2876,9 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-solang-parser", "foundry-test-utils", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "rexpect", "rustyline", "semver 1.0.28", @@ -2997,9 +2982,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" dependencies = [ "clap", ] @@ -3042,7 +3027,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3195,7 +3180,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3364,9 +3349,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", @@ -3375,9 +3360,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concurrent-queue" @@ -3425,9 +3410,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -3538,9 +3523,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc-fast" @@ -3821,9 +3806,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" @@ -3973,9 +3958,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.1", @@ -3999,7 +3984,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4187,15 +4172,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "ena" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -4304,7 +4280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4502,15 +4478,15 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "figment2" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4380ce44915a6227efbb61e3885bc1c8e99fb9820f5db612abfac2c5cfc46871" +checksum = "87d63dee16df12076c7770919713c0b92f4e1c85eac828dc2ade0b6c998f016b" dependencies = [ "atomic", "parking_lot", "serde", "tempfile", - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.11+spec-1.1.0", "uncased", "version_check", ] @@ -4607,7 +4583,7 @@ checksum = "932dcfbd51320af5f27f1ba02d2e567dec332cac7d2c221ba45d8e767264c4dc" [[package]] name = "forge" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4661,7 +4637,7 @@ dependencies = [ "rand 0.9.4", "rayon", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4689,7 +4665,7 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "derive_more", @@ -4711,7 +4687,7 @@ dependencies = [ [[package]] name = "forge-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "foundry-common", "foundry-config", @@ -4725,7 +4701,7 @@ dependencies = [ [[package]] name = "forge-lint" -version = "1.6.0" +version = "1.7.1" dependencies = [ "eyre", "foundry-common", @@ -4739,12 +4715,12 @@ dependencies = [ [[package]] name = "forge-script" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-json-abi", "alloy-network", @@ -4787,7 +4763,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-network", "alloy-primitives", @@ -4803,7 +4779,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -4818,7 +4794,7 @@ dependencies = [ [[package]] name = "forge-verify" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4841,7 +4817,7 @@ dependencies = [ "futures", "itertools 0.14.0", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4864,7 +4840,7 @@ dependencies = [ [[package]] name = "foundry-bench" -version = "1.6.0" +version = "1.7.1" dependencies = [ "chrono", "clap", @@ -4889,7 +4865,7 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -4899,7 +4875,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4952,7 +4928,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes-spec" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-types", "foundry-macros", @@ -4963,11 +4939,11 @@ dependencies = [ [[package]] name = "foundry-cli" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-json-abi", "alloy-network", @@ -5007,6 +4983,7 @@ dependencies = [ "tempo-primitives", "tikv-jemallocator", "tokio", + "toml", "tracing", "tracing-subscriber 0.3.23", "tracing-tracy", @@ -5015,7 +4992,7 @@ dependencies = [ [[package]] name = "foundry-cli-markdown" -version = "1.6.0" +version = "1.7.1" dependencies = [ "clap", "pretty_assertions", @@ -5023,12 +5000,12 @@ dependencies = [ [[package]] name = "foundry-common" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -5040,6 +5017,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "alloy-transport", "alloy-transport-ipc", @@ -5047,6 +5025,7 @@ dependencies = [ "anstream 0.6.21", "anstyle", "axum", + "base64 0.22.1", "chrono", "ciborium", "clap", @@ -5064,18 +5043,20 @@ dependencies = [ "futures", "itertools 0.14.0", "jiff", + "k256", "mpp", "num-format", "op-alloy-network", "op-alloy-rpc-types", "path-slash", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "rustls", "semver 1.0.28", "serde", "serde_json", + "sha2 0.10.9", "solar-compiler", "tempfile", "tempo-alloy", @@ -5094,14 +5075,14 @@ dependencies = [ [[package]] name = "foundry-common-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "chrono", "comfy-table", "eyre", @@ -5217,7 +5198,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5257,7 +5238,7 @@ dependencies = [ [[package]] name = "foundry-debugger" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "crossterm", @@ -5275,7 +5256,7 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5312,7 +5293,7 @@ dependencies = [ [[package]] name = "foundry-evm-abi" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -5324,7 +5305,7 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -5339,7 +5320,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "anvil", "auto_impl", @@ -5376,7 +5357,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "eyre", @@ -5392,7 +5373,7 @@ dependencies = [ [[package]] name = "foundry-evm-fuzz" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5417,7 +5398,7 @@ dependencies = [ [[package]] name = "foundry-evm-hardforks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -5432,10 +5413,10 @@ dependencies = [ [[package]] name = "foundry-evm-networks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -5448,11 +5429,11 @@ dependencies = [ [[package]] name = "foundry-evm-sancov" -version = "1.6.0" +version = "1.7.1" [[package]] name = "foundry-evm-traces" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5470,7 +5451,7 @@ dependencies = [ "itertools 0.14.0", "memchr", "rayon", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -5509,7 +5490,7 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "foundry-compilers", @@ -5520,7 +5501,7 @@ dependencies = [ [[package]] name = "foundry-macros" -version = "1.6.0" +version = "1.7.1" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -5530,7 +5511,7 @@ dependencies = [ [[package]] name = "foundry-primitives" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-evm", @@ -5541,7 +5522,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5555,23 +5536,9 @@ dependencies = [ "tempo-revm", ] -[[package]] -name = "foundry-solang-parser" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9645e75b89f977423690f3b4bfd8d84825e5fdabd7803cbce6d4a2c4d54972b4" -dependencies = [ - "itertools 0.14.0", - "lalrpop", - "lalrpop-util", - "phf 0.11.3", - "thiserror 2.0.18", - "unicode-xid", -] - [[package]] name = "foundry-test-utils" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5586,7 +5553,7 @@ dependencies = [ "parking_lot", "rand 0.9.4", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "snapbox", "svm-rs", @@ -5814,7 +5781,7 @@ dependencies = [ "once_cell", "prost 0.14.3", "prost-types 0.14.3", - "reqwest 0.13.2", + "reqwest 0.13.3", "secret-vault-value", "serde", "serde_json", @@ -6188,9 +6155,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" dependencies = [ "typenum", ] @@ -6582,9 +6549,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -6592,7 +6559,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6641,7 +6608,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6694,9 +6661,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -6707,31 +6674,15 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -6741,7 +6692,7 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", "thiserror 2.0.18", @@ -6762,15 +6713,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -6802,9 +6744,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -6841,6 +6783,7 @@ version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", "getrandom 0.2.17", "js-sys", @@ -6923,45 +6866,14 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] -[[package]] -name = "lalrpop" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.14.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "sha3", - "string_cache 0.8.9", - "term", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" -dependencies = [ - "regex-automata", - "rustversion", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -6982,9 +6894,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -6994,12 +6906,11 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.44" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" dependencies = [ "cc", - "libc", ] [[package]] @@ -7299,12 +7210,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -7331,9 +7242,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" dependencies = [ "libmimalloc-sys", ] @@ -7588,7 +7499,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7809,7 +7720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "parking_lot", "stable_deref_trait", ] @@ -7817,7 +7728,7 @@ dependencies = [ [[package]] name = "op-alloy" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "op-alloy-consensus", "op-alloy-network", @@ -7829,15 +7740,15 @@ dependencies = [ [[package]] name = "op-alloy-consensus" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "derive_more", "reth-codecs", @@ -7856,7 +7767,7 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", "alloy-network", @@ -7869,7 +7780,7 @@ dependencies = [ [[package]] name = "op-alloy-provider" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-network", "alloy-primitives", @@ -7883,15 +7794,15 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "op-alloy-consensus", "reth-rpc-traits", @@ -7903,15 +7814,14 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.1", - "derive_more", + "alloy-serde 2.0.4", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", @@ -7924,7 +7834,7 @@ dependencies = [ [[package]] name = "op-revm" version = "19.0.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "auto_impl", "revm", @@ -8142,16 +8052,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap 2.14.0", -] - [[package]] name = "pharos" version = "0.5.3" @@ -8168,7 +8068,6 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -8178,7 +8077,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", + "phf_macros", "phf_shared 0.13.1", "serde", ] @@ -8223,19 +8122,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "phf_macros" version = "0.13.1" @@ -8571,7 +8457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8740,7 +8626,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -9091,9 +8977,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -9135,12 +9021,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9155,11 +9041,12 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9173,8 +9060,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -9183,8 +9071,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9196,11 +9084,11 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9209,8 +9097,8 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9233,10 +9121,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "bytes", "modular-bitfield", @@ -9247,11 +9135,11 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9263,8 +9151,8 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -9276,11 +9164,11 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs", @@ -9290,11 +9178,11 @@ dependencies = [ [[package]] name = "reth-evm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "auto_impl", @@ -9312,11 +9200,11 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -9332,8 +9220,8 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-evm", "alloy-primitives", @@ -9345,11 +9233,11 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -9364,8 +9252,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9377,11 +9265,12 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9405,8 +9294,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9420,8 +9309,8 @@ dependencies = [ [[package]] name = "reth-revm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9433,8 +9322,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-evm", @@ -9453,9 +9342,9 @@ dependencies = [ [[package]] name = "reth-rpc-traits" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b766da61ec7c46596386b4bc88d9b57d1939d3da2bc9e927567a8a23650e5ce9" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" dependencies = [ "alloy-consensus", "alloy-network", @@ -9468,8 +9357,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "bytes", @@ -9481,8 +9370,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9495,11 +9384,11 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9518,10 +9407,10 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9536,14 +9425,14 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "arrayvec", "bytes", @@ -9559,8 +9448,9 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] @@ -9843,9 +9733,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -9853,20 +9743,20 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.4.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "5ac5b223d9738ef56e0b98305410be40fa0941bf6036c56f1506751e43552d64" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327b72899159dfae8060c51a1f6aebe955245bcd9cc4997eed0f623caea022e4" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", @@ -9874,9 +9764,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "arbitrary", @@ -10001,14 +9891,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -10034,9 +9924,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -10044,13 +9934,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni 0.21.1", + "jni", "log", "once_cell", "rustls", @@ -10060,7 +9950,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10478,9 +10368,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ "base64 0.22.1", "chrono", @@ -10497,9 +10387,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -10560,14 +10450,14 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -10701,9 +10591,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -10842,7 +10732,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10877,7 +10767,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11011,18 +10901,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -11181,7 +11059,7 @@ checksum = "4572dd9845e37ca0293acb5fe591a7f61b51f1b7b62d3dc6fb8e99e2664f3755" dependencies = [ "const-hex", "dirs", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -11301,22 +11179,22 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tempo-alloy" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-contract", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer-local", "alloy-sol-types", "alloy-transport", @@ -11334,9 +11212,9 @@ dependencies = [ [[package]] name = "tempo-chainspec" version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-hardforks", @@ -11353,7 +11231,7 @@ dependencies = [ [[package]] name = "tempo-consensus" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11371,7 +11249,7 @@ dependencies = [ [[package]] name = "tempo-contracts" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-contract", "alloy-primitives", @@ -11382,7 +11260,7 @@ dependencies = [ [[package]] name = "tempo-evm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11409,7 +11287,7 @@ dependencies = [ [[package]] name = "tempo-precompiles" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "alloy-evm", @@ -11429,7 +11307,7 @@ dependencies = [ [[package]] name = "tempo-precompiles-macros" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "proc-macro2", @@ -11440,15 +11318,15 @@ dependencies = [ [[package]] name = "tempo-primitives" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "aws-lc-rs", "base64 0.22.1", "derive_more", @@ -11471,7 +11349,7 @@ dependencies = [ [[package]] name = "tempo-revm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11502,15 +11380,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "term" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "terminal_size" version = "0.4.4" @@ -11518,7 +11387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11552,9 +11421,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" [[package]] name = "thiserror" @@ -11695,9 +11564,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -11864,9 +11733,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap 2.14.0", + "serde_core", + "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -11875,7 +11746,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -12220,9 +12091,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -12506,9 +12377,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174a690eb3293a5666442b0738d080df9ea6b9e03782bbe78875c89ff914a77c" +checksum = "2d7cb4a83971db3f6ae36f0aa41eaf5985d2e2b469581fa755c132f9c2a1ec89" dependencies = [ "anyhow", "bon", @@ -12519,9 +12390,9 @@ dependencies = [ [[package]] name = "vergen-gitcl" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f628f4acc90a5c1a8136495eaf5f9ef94e03c174d6fb2e6de691bc58fc721ee" +checksum = "bba14c9676943b2899cea2ed7ea194b89b3d13564a3c93a61882a978b123a41c" dependencies = [ "anyhow", "bon", @@ -12533,9 +12404,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390d0442b660baedd7a6f60d2af01bd8967e0d7fe69cd15e13c82c811d82b709" +checksum = "fb684e6d170ef15a9b3c20561779a50ba8c806f8acdaff47c0a2c5c4c6cadd43" dependencies = [ "anyhow", "bon", @@ -12600,11 +12471,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -12613,14 +12484,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -12631,9 +12502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -12641,9 +12512,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12651,9 +12522,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -12664,9 +12535,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -12764,7 +12635,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12804,9 +12675,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -12824,13 +12695,13 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", - "string_cache 0.9.0", + "string_cache", "string_cache_codegen", ] @@ -12841,7 +12712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ "core-foundation 0.10.1", - "jni 0.22.4", + "jni", "log", "ndk-context", "objc2", @@ -12914,7 +12785,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -13035,15 +12906,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -13080,21 +12942,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -13137,12 +12984,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -13155,12 +12996,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -13173,12 +13008,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -13203,12 +13032,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -13221,12 +13044,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -13239,12 +13056,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -13257,12 +13068,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -13286,9 +13091,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -13302,6 +13107,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index c412dad16366c..4db027dd400cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ members = [ resolver = "2" [workspace.package] -version = "1.6.0" +version = "1.7.1" edition = "2024" rust-version = "1.89" authors = ["Foundry Contributors"] @@ -174,9 +174,6 @@ foundry-compilers.opt-level = 3 serde_json.opt-level = 3 serde.opt-level = 3 -foundry-solang-parser.opt-level = 3 -lalrpop-util.opt-level = 3 - solar-compiler.opt-level = 3 solar-ast.opt-level = 3 solar-data-structures.opt-level = 3 @@ -307,40 +304,40 @@ crossterm.opt-level = "s" alloy-json-abi.opt-level = "s" [workspace.dependencies] -anvil = { path = "crates/anvil" } -cast = { path = "crates/cast" } -chisel = { path = "crates/chisel" } -forge = { path = "crates/forge" } - -forge-doc = { path = "crates/doc" } -forge-fmt = { path = "crates/fmt" } -forge-lint = { path = "crates/lint" } -forge-verify = { path = "crates/verify" } -forge-script = { path = "crates/script" } -forge-sol-macro-gen = { path = "crates/sol-macro-gen" } -forge-script-sequence = { path = "crates/script-sequence" } -foundry-cheatcodes = { path = "crates/cheatcodes" } +anvil = { path = "crates/anvil", default-features = false } +cast = { path = "crates/cast", default-features = false } +chisel = { path = "crates/chisel", default-features = false } +forge = { path = "crates/forge", default-features = false } + +forge-doc = { path = "crates/doc", default-features = false } +forge-fmt = { path = "crates/fmt", default-features = false } +forge-lint = { path = "crates/lint", default-features = false } +forge-verify = { path = "crates/verify", default-features = false } +forge-script = { path = "crates/script", default-features = false } +forge-sol-macro-gen = { path = "crates/sol-macro-gen", default-features = false } +forge-script-sequence = { path = "crates/script-sequence", default-features = false } +foundry-cheatcodes = { path = "crates/cheatcodes", default-features = false } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } -foundry-cli = { path = "crates/cli" } +foundry-cli = { path = "crates/cli", default-features = false } foundry-cli-markdown = { path = "crates/cli-markdown" } -foundry-common = { path = "crates/common" } -foundry-common-fmt = { path = "crates/common/fmt" } +foundry-common = { path = "crates/common", default-features = false } +foundry-common-fmt = { path = "crates/common/fmt", default-features = false } foundry-config = { path = "crates/config" } -foundry-debugger = { path = "crates/debugger" } -foundry-evm = { path = "crates/evm/evm" } +foundry-debugger = { path = "crates/debugger", default-features = false } +foundry-evm = { path = "crates/evm/evm", default-features = false } foundry-evm-abi = { path = "crates/evm/abi" } -foundry-evm-core = { path = "crates/evm/core" } -foundry-evm-coverage = { path = "crates/evm/coverage" } -foundry-evm-hardforks = { path = "crates/evm/hardforks" } -foundry-evm-networks = { path = "crates/evm/networks" } -foundry-evm-fuzz = { path = "crates/evm/fuzz" } +foundry-evm-core = { path = "crates/evm/core", default-features = false } +foundry-evm-coverage = { path = "crates/evm/coverage", default-features = false } +foundry-evm-hardforks = { path = "crates/evm/hardforks", default-features = false } +foundry-evm-networks = { path = "crates/evm/networks", default-features = false } +foundry-evm-fuzz = { path = "crates/evm/fuzz", default-features = false } foundry-evm-sancov = { path = "crates/evm/sancov" } -foundry-evm-traces = { path = "crates/evm/traces" } +foundry-evm-traces = { path = "crates/evm/traces", default-features = false } foundry-macros = { path = "crates/macros" } -foundry-test-utils = { path = "crates/test-utils" } +foundry-test-utils = { path = "crates/test-utils", default-features = false } foundry-wallets = { version = "0.1.0", default-features = false } foundry-linking = { path = "crates/linking" } -foundry-primitives = { path = "crates/primitives" } +foundry-primitives = { path = "crates/primitives", default-features = false } # solc & compilation utilities foundry-block-explorers = { version = "0.23.0", default-features = false } @@ -349,7 +346,6 @@ foundry-compilers = { version = "0.20.0", default-features = false, features = [ "svm-solc", ] } foundry-fork-db = { version = "0.26.0", features = ["zstd"] } -solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } solar = { package = "solar-compiler", version = "=0.1.8", default-features = false } svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ "rustls", @@ -410,7 +406,7 @@ op-alloy-rpc-types = "0.24.0" op-alloy-flz = "0.13.1" ## alloy-evm -alloy-evm = "0.33.2" +alloy-evm = "0.34.0" alloy-op-evm = "0.31.0" # revm @@ -515,17 +511,17 @@ mpp = { git = "https://github.com/tempoxyz/mpp-rs", rev = "554d20112eb014bd223d5 "reqwest-rustls-tls", "ws", ] } -tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false, features = [ +tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ "serde", ] } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false } -tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a", default-features = false, features = [ +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ "serde", ] } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } ## Pinned dependencies. Enabled for the workspace in crates/test-utils. @@ -589,27 +585,21 @@ rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6 ## alloy-evm # alloy-evm = { git = "https://github.com/paradigmxyz/evm.git", rev = "04d8e4a" } -## reth-core -reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-codecs = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-codecs-derive = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } -reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth-core", rev = "6b12498" } - ## op-revm / op-alloy / alloy-op-evm -op-revm = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-network = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -op-alloy-rpc-types = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } -alloy-op-hardforks = { git = "https://github.com/ethereum-optimism/optimism", rev = "42f5117c2e7de0614cd3b96f274d0a3078f9701c" } +op-revm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-network = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-rpc-types = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-hardforks = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } ## foundry-fork-db # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-core", rev = "2f90eb86d4549fa15a8cc2d99bfc1039bc083977" } ## tempo — unify crates.io versions (pulled by mpp) with git rev -tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } -tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "8bd4d01d37e3cc324030baacbce2da0862d7735a" } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } # solar solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } diff --git a/README.md b/README.md index c9f0a45c57b0a..90cd1d8a7865e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ foundryup See the [installation guide](https://getfoundry.sh/getting-started/installation) for more details. +To verify a downloaded release archive or container image, see [Verifying Releases](./SECURITY.md#verifying-releases). + ## Getting Started Initialize a new project, build and test: diff --git a/SECURITY.md b/SECURITY.md index d84327cc18e91..6296066db5e73 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,3 +3,112 @@ ## Reporting a Vulnerability Contact [security@tempo.xyz](mailto:security@tempo.xyz). + +## Verifying Releases + +Every official Foundry release ships with multiple, independent integrity +artifacts. All signing is keyless via [Sigstore](https://www.sigstore.dev/) — +no Foundry-managed key material is involved, and every signature is recorded +in the public [Rekor](https://docs.sigstore.dev/logging/overview/) transparency +log. The signing identity is the GitHub Actions OIDC token of this repository's +`release.yml` / `docker-publish.yml` workflows. + +### Per-release artifacts + +For each `foundry___.{tar.gz,zip}` archive on the +[releases page](https://github.com/foundry-rs/foundry/releases), the same +release also publishes: + +| Suffix | Purpose | +| --- | --- | +| `.sha256` | SHA-256 checksum of the archive (`sha256sum` format) | +| `.sigstore.json` | Cosign keyless signature bundle (cert + signature + Rekor proof) over the archive | +| `.spdx.json` | SPDX 2.3 SBOM of the source workspace used for the build | +| `.attestation.txt` | URL of the GitHub artifact-attestation summary | + +In addition, GitHub stores SLSA build-provenance and SBOM attestations against +the archive's digest; these are queryable via `gh attestation` without +downloading anything else. + +### Verifying an archive + +Pick whichever toolchain you have available — they verify the same signatures. + +#### Option 1: GitHub CLI (simplest) + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry +``` + +This computes the file's digest, fetches the matching attestation from GitHub, +and verifies the Sigstore signature plus the SLSA provenance predicate. Add +`--signer-workflow foundry-rs/foundry/.github/workflows/release.yml` to also +require the workflow identity. + +To verify the SBOM attestation specifically: + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry \ + --predicate-type 'https://spdx.dev/Document/v2.3' +``` + +#### Option 2: Cosign (offline-friendly) + +Download the archive and its `.sigstore.json` bundle from the release page, +then: + +```bash +cosign verify-blob \ + --bundle foundry_v1.4.0_linux_amd64.sigstore.json \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/release\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ + foundry_v1.4.0_linux_amd64.tar.gz +``` + +For nightly builds the certificate identity points at `refs/heads/master` +instead of a tag; the regex above matches both. + +#### Option 3: Plain checksum (integrity only) + +```bash +sha256sum -c foundry_v1.4.0_linux_amd64.sha256 # GNU coreutils +shasum -a 256 -c foundry_v1.4.0_linux_amd64.sha256 # macOS +``` + +This proves the bytes match what was uploaded, but says nothing about who +uploaded them. Combine with one of the verifications above for end-to-end +trust. + +### Verifying the Docker image + +Container signatures and attestations are pushed as OCI referrers to GHCR, so +no separate files need to be downloaded. + +```bash +# Cosign keyless signature on the image +cosign verify ghcr.io/foundry-rs/foundry:v1.4.0 \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' + +# SLSA build-provenance attestation +gh attestation verify oci://ghcr.io/foundry-rs/foundry:v1.4.0 \ + --repo foundry-rs/foundry + +# Inspect the buildx-attached SBOM and provenance +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .SBOM }}' +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .Provenance }}' +``` + +To pin to an immutable digest (recommended for reproducible deployments): + +```bash +docker pull ghcr.io/foundry-rs/foundry:v1.4.0 +DIGEST=$(docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 --format '{{ .Manifest.Digest }}') +cosign verify "ghcr.io/foundry-rs/foundry@${DIGEST}" \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` diff --git a/benches/LATEST.md b/benches/LATEST.md index 238a691229389..cb75f8d68780b 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,74 +1,108 @@ -# Foundry Benchmark Results +# 📊 Foundry Benchmark Results -**Date**: 2026-04-24 23:10:24 +**Generated at**: 2026-05-02 21:53:46 UTC -## Repositories Tested +## Forge Test + +### Repositories Tested 1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) 4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) -5. [aave/aave-v4](https://github.com/aave/aave-v4) - -## Foundry Versions +### Foundry Versions - **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) -- **nightly**: forge Version: 1.6.0-nightly (a249f5c 2026-04-24) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) -## Forge Test - -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| vectorized-solady | 1.46 s | 1.38 s | -| aave-aave-v4 | 4m 14.2s | 3m 29.1s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.78 s | 0.965 s | +| vectorized-solady | 0.995 s | 0.645 s | +| uniswap-v4-core | 5.97 s | 1.51 s | +| sparkdotfi-spark-psm | 19.98 s | 10.20 s | ## Forge Fuzz Test -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | -------- | -| ithacaxyz-account | 2.81 s | 1.59 s | -| vectorized-solady | 1.40 s | 1.34 s | -| Uniswap-v4-core | 3.01 s | 2.87 s | -| sparkdotfi-spark-psm | 2.04 s | 1.87 s | -| aave-aave-v4 | 3m 46.0s | 3m 17.3s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.54 s | 0.923 s | +| vectorized-solady | 0.929 s | 0.617 s | +| uniswap-v4-core | 6.44 s | 1.40 s | +| sparkdotfi-spark-psm | 2.25 s | 2.03 s | ## Forge Test (Isolated) -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| Uniswap-v4-core | 3.50 s | 3.48 s. | -| aave-aave-v4 | 4m 14.0s | 3m 53.4s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 3.05 s | 1.02 s | +| vectorized-solady | 0.871 s | 0.741 s | +| uniswap-v4-core | 6.81 s | 1.68 s | +| sparkdotfi-spark-psm | 21.96 s | 11.26 s | + +## Forge Build -## Forge Build (No Cache) +### Repositories Tested -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| ithacaxyz-account | 26.06 s | 26.61 s | -| vectorized-solady | 14.20 s | 14.26 s | -| Uniswap-v4-core | 2m 1.3s | 2m 5.0s | -| sparkdotfi-spark-psm | 15.16 s | 15.30 s | -| aave-aave-v4 | 3m 37.0s | 3m 35.1s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +### No Cache + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 34.58 s | 33.29 s | +| vectorized-solady | 14.40 s | 14.41 s | +| uniswap-v4-core | 2m 17.6s | 2m 17.7s | +| sparkdotfi-spark-psm | 12.62 s | 12.61 s | -## Forge Build (With Cache) +### With Cache -| Repository | v1.5.1 | nightly | -| -------------------- | ------- | ------- | -| ithacaxyz-account | 0.167 s | 0.201 s | -| vectorized-solady | 0.099 s | 0.098 s | -| Uniswap-v4-core | 0.139 s | 0.140 s | -| sparkdotfi-spark-psm | 0.168 s | 0.173 s | -| aave-aave-v4 | 0.370 s | 0.357 s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 0.083 s | 0.089 s | +| vectorized-solady | 0.062 s | 0.064 s | +| uniswap-v4-core | 0.071 s | 0.074 s | +| sparkdotfi-spark-psm | 0.066 s | 0.068 s | ## Forge Coverage -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | ---------- | -| Uniswap-v4-core | 1m 13.9s | 1m 10.3s | -| sparkdotfi-spark-psm | 2m 54.7s | 2m 50.0s | -| aave-aave-v4 | 11m 20.8s | 10m 58.7s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [uniswap/v4-core](https://github.com/uniswap/v4-core) +3. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 29.35 s | 18.69 s | +| uniswap-v4-core | 1m 26.8s | 1m 4.1s | +| sparkdotfi-spark-psm | 2m 1.6s | 1m 28.4s | ## System Information -- **OS**: macos -- **CPU**: 12 -- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) \ No newline at end of file + +- **OS**: linux +- **CPU**: 32 +- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) diff --git a/benches/src/main.rs b/benches/src/main.rs index 60e815cecb0ec..8d7134b1c25bc 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -39,9 +39,15 @@ struct Cli { #[clap(long, default_value = ".")] output_dir: PathBuf, - /// Name of the output file (default: LATEST.md) - #[clap(long, default_value = "LATEST.md")] - output_file: String, + /// Name of the output file. Defaults to LATEST.md unless --json-output is set + /// without this flag, in which case no Markdown is written. + #[clap(long)] + output_file: Option, + + /// Filename for a flat JSON summary (benchmark/repo -> mean_seconds). + /// Resolved relative to --output-dir. Used by the nightly regression comparison script. + #[clap(long)] + json_output: Option, /// Run only specific benchmarks (comma-separated: /// forge_test,forge_build_no_cache,forge_build_with_cache,forge_fuzz_test,forge_coverage) @@ -216,12 +222,28 @@ fn main() -> Result<()> { } } - // Generate markdown report - sh_println!("📝 Generating report..."); - let markdown = results.generate_markdown(&versions, &repos); - let output_path = cli.output_dir.join(cli.output_file); - fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; - sh_println!("✅ Report written to: {}", output_path.display()); + // Write Markdown report unless --json-output is set without an explicit --output-file. + let md_filename = match cli.output_file { + Some(f) => Some(f), + None if cli.json_output.is_none() => Some("LATEST.md".to_string()), + None => None, + }; + if let Some(filename) = md_filename { + sh_println!("📝 Generating report..."); + let markdown = results.generate_markdown(&versions, &repos); + let output_path = cli.output_dir.join(filename); + fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; + sh_println!("✅ Report written to: {}", output_path.display()); + } + + if let Some(json_filename) = cli.json_output { + let summary = results.generate_json_summary(&versions); + let json = + serde_json::to_string_pretty(&summary).wrap_err("Failed to serialize JSON summary")?; + let json_path = cli.output_dir.join(json_filename); + fs::write(&json_path, json).wrap_err("Failed to write JSON summary")?; + sh_println!("✅ JSON summary written to: {}", json_path.display()); + } Ok(()) } diff --git a/benches/src/results.rs b/benches/src/results.rs index 447e8ed2766b4..e7d57250fc9a1 100644 --- a/benches/src/results.rs +++ b/benches/src/results.rs @@ -66,6 +66,25 @@ impl BenchmarkResults { self.version_details.insert(version.to_string(), details); } + /// Generate a flat JSON summary mapping `"benchmark/repo" -> mean_seconds`. + /// + /// Used by the nightly regression comparison script. + pub fn generate_json_summary(&self, versions: &[String]) -> HashMap { + let mut summary = HashMap::new(); + for (benchmark_name, version_data) in &self.data { + for version in versions { + if let Some(repo_data) = version_data.get(version) { + for (repo_name, result) in repo_data { + let key = format!("{benchmark_name}/{repo_name}"); + let rounded = (result.mean * 10_000.0).round() / 10_000.0; + summary.insert(key, rounded); + } + } + } + } + summary + } + pub fn generate_markdown(&self, versions: &[String], repos: &[RepoConfig]) -> String { let mut output = String::new(); diff --git a/benchmark.sh b/benchmark.sh index 2faffa93dfa1d..ac6159099069b 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,36 +1,52 @@ #!/bin/bash -versions="v1.3.6,v1.4.0-rc1" +versions="v1.5.1,v1.7.0" # Repositories -export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" -export SOLADY_REPO="Vectorized/solady:v0.1.22" -export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" -export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" +ITHACA_ACCOUNT="ithacaxyz/account:v0.5.7" +SOLADY_REPO="vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test'" +AAVE_V4="aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" +UNISWAP_V4_CORE="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc TickMathTestTest" +SPARK_PSM="sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting" -# Benches -echo "===========FORGE TEST AND BUILD BENCHMARKS===========" +SOLADY_ISOLATE="vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest'" +ITHACA_ISOLATE="ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_test,forge_fuzz_test,forge_build_no_cache,forge_build_with_cache \ - --output-dir ./benches/results \ - --output-file TEST_BUILD.md +SOLADY_BUILD="vectorized/solady:v0.1.26" +UNISWAP_BUILD="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75" +SPARK_PSM_BUILD="sparkdotfi/spark-psm:v1.0.0" -echo "===========FORGE COVERAGE BENCHMARKS===========" +# Benches +echo "===========FORGE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_coverage \ - --output-dir ./benches/results \ - --output-file COVERAGE.md +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_REPO,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ + --benchmarks forge_test,forge_fuzz_test \ + --output-dir ./benches \ + --output-file forge_test_bench.md echo "===========FORGE ISOLATE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ISOLATE,$SOLADY_ISOLATE,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ --benchmarks forge_isolate_test \ - --output-dir ./benches/results \ - --output-file ISOLATE_TEST.md + --output-dir ./benches \ + --output-file forge_isolate_test_bench.md + +echo "===========FORGE BUILD BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_BUILD,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --output-dir ./benches \ + --output-file forge_build_bench.md + +echo "===========FORGE COVERAGE BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_coverage \ + --output-dir ./benches \ + --output-file forge_coverage_bench.md echo "===========BENCHMARKS COMPLETED===========" diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index b664266450d07..d6404b21e2e8e 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -20,15 +20,15 @@ required-features = ["cli"] [dependencies] # foundry internal -anvil-core = { path = "core" } +anvil-core = { path = "core", default-features = false } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } -foundry-cli.workspace = true +foundry-cli = { workspace = true, optional = true } foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } tempo-chainspec.workspace = true tempo-primitives.workspace = true tempo-precompiles.workspace = true @@ -37,7 +37,7 @@ tempo-revm.workspace = true # alloy alloy-evm = { workspace = true, features = ["call-util"] } -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } @@ -63,7 +63,8 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde"] } +op-alloy-consensus = { workspace = true, features = ["serde"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } # revm revm = { workspace = true, features = [ @@ -73,7 +74,7 @@ revm = { workspace = true, features = [ "c-kzg", ] } revm-inspectors.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } # axum related axum.workspace = true @@ -120,17 +121,28 @@ reqwest.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } -op-alloy-rpc-types.workspace = true tempo-alloy.workspace = true [features] -default = ["cli", "jemalloc", "asm-keccak"] +default = ["cli", "jemalloc", "asm-keccak", "optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "anvil-core/optimism", + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli?/optimism", +] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] -jemalloc = ["foundry-cli/jemalloc"] -mimalloc = ["foundry-cli/mimalloc"] -tracy-allocator = ["foundry-cli/tracy-allocator"] +jemalloc = ["foundry-cli?/jemalloc"] +mimalloc = ["foundry-cli?/mimalloc"] +tracy-allocator = ["foundry-cli?/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ + "dep:foundry-cli", "clap", "clap_complete", "dep:fdlimit", diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index cf4b952ecfaa3..8456413a78b1f 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } revm = { workspace = true, default-features = false, features = [ "std", "serde", @@ -39,3 +39,11 @@ bytes.workspace = true # misc rand.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index fb75529b82908..4fd2aabb82a8b 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -12,7 +12,9 @@ use clap::Parser; use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; -use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; +#[cfg(feature = "optimism")] +use foundry_evm::hardfork::OpHardfork; +use foundry_evm::hardfork::{EthereumHardfork, FoundryHardfork}; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::FoundryReceiptEnvelope; use futures::FutureExt; @@ -240,15 +242,7 @@ impl NodeArgs { } let hardfork = match &self.hardfork { - Some(hf) => { - if self.evm.networks.is_optimism() { - Some(OpHardfork::from_str(hf)?.into()) - } else if self.evm.networks.is_tempo() { - Some(TempoHardfork::from_str(hf)?.into()) - } else { - Some(EthereumHardfork::from_str(hf)?.into()) - } - } + Some(hf) => Some(parse_hardfork(hf, &self.evm.networks)?), None => None, }; @@ -849,6 +843,19 @@ impl FromStr for ForkUrl { } } +/// Parses a hardfork string against the active network configuration. +fn parse_hardfork(hf: &str, networks: &NetworkConfigs) -> eyre::Result { + #[cfg(feature = "optimism")] + if networks.is_optimism() { + return Ok(OpHardfork::from_str(hf)?.into()); + } + if networks.is_tempo() { + Ok(TempoHardfork::from_str(hf)?.into()) + } else { + Ok(EthereumHardfork::from_str(hf)?.into()) + } +} + /// Clap's value parser for genesis. Loads a genesis.json file. fn read_genesis_file(path: &str) -> Result { foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 23cd6e61bc076..7c91590c071fa 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -37,7 +37,7 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{FoundryHardfork, OpHardfork}, + hardfork::FoundryHardfork, utils::{ apply_chain_and_block_specific_env_changes, block_env_from_header, get_blob_base_fee_update_fraction, @@ -577,8 +577,9 @@ impl NodeConfig { if let Some(hardfork) = self.hardfork { return hardfork; } + #[cfg(feature = "optimism")] if self.networks.is_optimism() { - return OpHardfork::default().into(); + return foundry_evm::hardforks::OpHardfork::default().into(); } if self.networks.is_tempo() { return TempoHardfork::default().into(); @@ -1079,6 +1080,7 @@ impl NodeConfig { } /// Enable Optimism network features. + #[cfg(feature = "optimism")] #[must_use] pub fn with_optimism(mut self) -> Self { self.networks = NetworkConfigs::with_optimism(); diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 78cce938318c1..4900c18eebd82 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1882,6 +1882,7 @@ impl EthApi { fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { match typed_tx { + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { @@ -2210,9 +2211,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; - debug_assert!(requires != provides); + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -2288,11 +2293,10 @@ impl EthApi { let priority = self.transaction_priority(&pending_transaction.transaction); // Tempo txs use a 2D nonce system — no sequential ordering by account nonce. - let (requires, provides) = if let FoundryTxEnvelope::Tempo(aa_tx) = - pending_transaction.transaction.as_ref() - && !aa_tx.tx().nonce_key.is_zero() + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) { - (vec![], vec![pending_transaction.hash().to_vec()]) + (requires, provides) } else { let on_chain_nonce = self.backend.current_nonce(from).await?; let nonce = pending_transaction.transaction.nonce(); @@ -3192,8 +3196,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -3549,6 +3558,7 @@ impl EthApi { requires: Vec, provides: Vec, ) -> Result { + debug_assert!(requires != provides); let from = *pending_transaction.sender(); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = @@ -3565,7 +3575,9 @@ impl EthApi { FoundryTxEnvelope::Eip1559(_) => self.backend.ensure_eip1559_active(), FoundryTxEnvelope::Eip4844(_) => self.backend.ensure_eip4844_active(), FoundryTxEnvelope::Eip7702(_) => self.backend.ensure_eip7702_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => self.backend.ensure_op_deposits_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => Err(BlockchainError::InvalidTransactionRequest( "not implemented for post-exec tx".to_string(), )), @@ -3634,6 +3646,20 @@ fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> V if on_chain_nonce <= prev_nonce { vec![to_marker(prev_nonce, from)] } else { Vec::new() } } +fn tempo_parallel_nonce_markers( + pending_transaction: &PendingTransaction, +) -> Option<(Vec, Vec)> { + // Tempo txs with non-zero nonce_key use a 2D nonce system and should not + // be sequenced by account nonce markers. + if let FoundryTxEnvelope::Tempo(aa_tx) = pending_transaction.transaction.as_ref() + && !aa_tx.tx().nonce_key.is_zero() + { + Some((vec![], vec![pending_transaction.hash().to_vec()])) + } else { + None + } +} + fn convert_transact_out(out: &Option) -> Bytes { match out { None => Default::default(), diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 614f409c3eb65..c41937055fdd4 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -67,9 +67,11 @@ impl ReceiptBuilder for FoundryReceiptBuilder { FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { unreachable!("deposit receipts are built in commit_transaction") } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt), FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), } @@ -85,7 +87,7 @@ pub struct AnvilTxResult { pub sender: Address, } -impl TxResult for AnvilTxResult { +impl TxResult for AnvilTxResult { type HaltReason = H; fn result(&self) -> &ResultAndState { @@ -217,12 +219,10 @@ where }) } - fn commit_transaction( - &mut self, - output: Self::Result, - ) -> Result { + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { let AnvilTxResult { inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] sender, } = output; @@ -237,6 +237,7 @@ where self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); } + #[cfg(feature = "optimism")] let receipt = if tx_type == FoundryTxType::Deposit { let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); let receipt = alloy_consensus::Receipt { @@ -262,11 +263,19 @@ where cumulative_gas_used: self.gas_used, }) }; + #[cfg(not(feature = "optimism"))] + let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }); self.receipts.push(receipt); self.evm.db_mut().commit(state); - Ok(GasOutput::new(gas_used)) + GasOutput::new(gas_used) } fn finish( @@ -429,7 +438,7 @@ where let exec_result = result.result().result.clone(); let gas_used = result.result().result.tx_gas_used(); - executor.commit_transaction(result).expect("commit failed"); + executor.commit_transaction(result); let traces = executor.evm_mut().inspector_mut().finish_transaction(inspector_config); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index f404031445611..1c8dfffab9d95 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -57,6 +57,7 @@ use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction, }; +#[cfg(feature = "optimism")] use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; use alloy_primitives::{ Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, @@ -108,20 +109,60 @@ use foundry_evm::{ }, }; use foundry_evm_networks::NetworkConfigs; +#[cfg(feature = "optimism")] +use foundry_primitives::get_deposit_tx_parts; use foundry_primitives::{ FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, - FoundryTxReceipt, get_deposit_tx_parts, + FoundryTxReceipt, }; use futures::channel::mpsc::{UnboundedSender, unbounded}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait}; -use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +#[cfg(feature = "optimism")] +use op_revm::{OpSpecId, OpTransaction, transaction::deposit::DepositTransactionParts}; + +/// Side-channel container for OP-specific deposit info produced by +/// [`Backend::build_call_env`] and consumed by the OP transact path. +/// +/// When the `optimism` feature is enabled, this is an alias for +/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized +/// stand-in so the eth/tempo dispatch chain still type-checks. +#[cfg(feature = "optimism")] +type OpCallDepositInfo = DepositTransactionParts; +#[cfg(not(feature = "optimism"))] +#[derive(Default, Clone, Debug)] +struct OpCallDepositInfo; + +/// Marker trait that abstracts over the per-network inspector trait bounds +/// required by the in-memory backend. The OP bound is only included when the +/// `optimism` feature is enabled. +#[cfg(feature = "optimism")] +pub trait BackendInspector: + Inspector> + Inspector> + Inspector> +{ +} +#[cfg(feature = "optimism")] +impl BackendInspector for T where + T: Inspector> + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +pub trait BackendInspector: + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +impl BackendInspector for T where + T: Inspector> + Inspector> +{ +} use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ DatabaseCommit, Inspector, context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, - result::{EVMError, ExecutionResult, HaltReason, Output, ResultAndState}, + result::{ExecutionResult, HaltReason, Output, ResultAndState}, }, database::{CacheDB, DbAccount, WrapDatabaseRef}, interpreter::InstructionResult, @@ -157,6 +198,8 @@ pub mod cache; pub mod fork_db; pub mod in_memory_db; pub mod inspector; +#[cfg(feature = "optimism")] +pub mod optimism; pub mod state; pub mod storage; @@ -419,6 +462,11 @@ impl Backend { self.genesis.timestamp } + /// Returns the configured genesis block number. + pub const fn genesis_number(&self) -> u64 { + self.genesis.number + } + /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { Ok(self.get_account(address).await?.balance) @@ -490,12 +538,21 @@ impl Backend { } /// Returns true if op-stack deposits are active - pub fn is_optimism(&self) -> bool { + #[cfg(feature = "optimism")] + pub const fn is_optimism(&self) -> bool { self.networks.is_optimism() } + /// Returns true if op-stack deposits are active. + /// + /// Always `false` when built without the `optimism` feature. + #[cfg(not(feature = "optimism"))] + pub const fn is_optimism(&self) -> bool { + false + } + /// Returns true if Tempo network mode is active - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.networks.is_tempo() } @@ -589,7 +646,8 @@ impl Backend { } /// Returns an error if op-stack deposits are not active - pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { + #[cfg(feature = "optimism")] + pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { return Ok(()); } @@ -597,7 +655,7 @@ impl Backend { } /// Returns an error if Tempo transactions are not active - pub fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { + pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { if self.is_tempo() { return Ok(()); } @@ -1139,58 +1197,53 @@ impl Backend { db: &'db DB, evm_env: &EvmEnv, inspector: &mut I, - tx_env: OpTransaction, + tx_env: TxEnv, + op_deposit: OpCallDepositInfo, ) -> Result, BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_env = EvmEnv::new( - evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), - evm_env.block_env.clone(), - ); - let mut evm = OpEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - op_env, - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { - EVMError::Database(db) => EVMError::Database(db), - EVMError::Header(h) => EVMError::Header(h), - EVMError::Custom(s) => EVMError::Custom(s), - EVMError::CustomAny(err) => EVMError::CustomAny(err), - EVMError::Transaction(t) => EVMError::Transaction(t), - })?; - Ok(ResultAndState { - result: result.result.map_haltreason(|h| match h { - OpHaltReason::Base(eth) => eth, - _ => HaltReason::PrecompileError, - }), - state: result.state, - }) - } else if self.is_tempo() { - self.transact_tempo_with_inspector_ref( - db, - evm_env, - inspector, - TempoTxEnv::from(tx_env.base), - ) + let op_tx = OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() }; + return self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx); + } + // `op_deposit` only matters on the OP path; eth/tempo ignore it. + let _ = op_deposit; + if self.is_tempo() { + self.transact_tempo_with_inspector_ref(db, evm_env, inspector, TempoTxEnv::from(tx_env)) } else { - let mut evm = EthEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - evm_env.clone(), - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - Ok(evm.transact(tx_env.base)?) + self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env) } } + /// Eth path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an Ethereum EVM, injects precompiles, and transacts with a + /// plain [`TxEnv`]. + fn transact_eth_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TxEnv, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let mut evm = EthEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + evm_env.clone(), + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + Ok(evm.transact(tx_env)?) + } + /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`]. fn transact_envelope_with_inspector_ref<'db, I, DB>( @@ -1203,9 +1256,7 @@ impl Backend { ) -> Result<(ResultAndState, TxEnv), BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { if tx.is_tempo() { @@ -1213,14 +1264,21 @@ impl Backend { FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); let base = tx_env.inner.clone(); let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) - } else { - let tx_env: OpTransaction = + return Ok((result, base)); + } + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_tx: OpTransaction = FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); - let base = tx_env.base.clone(); - let result = self.transact_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) + let base = op_tx.base.clone(); + let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?; + return Ok((result, base)); } + let tx_env: TxEnv = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = tx_env.clone(); + let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?; + Ok((result, base)) } /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`]. @@ -1298,6 +1356,7 @@ impl Backend { }}; } + #[cfg(feature = "optimism")] if self.is_optimism() { let op_env = EvmEnv::new( evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), @@ -1305,8 +1364,10 @@ impl Backend { ); let mut evm = OpEvmFactory::::default().create_evm_with_inspector(db, op_env, inspector); - run!(evm) - } else if self.is_tempo() { + return run!(evm); + } + + if self.is_tempo() { let hardfork = TempoHardfork::from(self.hardfork); let tempo_env = EvmEnv::new( evm_env @@ -1338,7 +1399,7 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> (EvmEnv, OpTransaction) { + ) -> (EvmEnv, TxEnv, OpCallDepositInfo) { let tx_type = request.minimal_tx_type() as u8; let WithOtherFields:: { @@ -1391,7 +1452,7 @@ impl Backend { let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); - let mut base = TxEnv { + let mut tx_env = TxEnv { caller, gas_limit, gas_price, @@ -1413,11 +1474,10 @@ impl Backend { blob_hashes, ..Default::default() }; - base.set_signed_authorization(authorization_list.unwrap_or_default()); - let mut tx_env = OpTransaction { base, ..Default::default() }; + tx_env.set_signed_authorization(authorization_list.unwrap_or_default()); if let Some(nonce) = nonce { - tx_env.base.nonce = nonce; + tx_env.nonce = nonce; } if evm_env.block_env.basefee == 0 { @@ -1427,13 +1487,22 @@ impl Backend { } // Deposit transaction? (only valid when op-stack deposits are active) - if self.ensure_op_deposits_active().is_ok() + #[cfg(feature = "optimism")] + let op_deposit = if self.ensure_op_deposits_active().is_ok() && let Ok(deposit) = get_deposit_tx_parts(&other) { - tx_env.deposit = deposit; - } + deposit + } else { + OpCallDepositInfo::default() + }; + #[cfg(not(feature = "optimism"))] + let op_deposit = { + // `other` carries OP-only deposit fields; consumed only when feature is enabled. + let _ = &other; + OpCallDepositInfo::default() + }; - (evm_env, tx_env) + (evm_env, tx_env, op_deposit) } pub fn call_with_state( @@ -1467,13 +1536,13 @@ impl Backend { (fee_token, nonce_key, valid_before, valid_after) }); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state } = if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides { use tempo_primitives::transaction::Call; - let base = tx_env.base; + let base = tx_env; let mut tempo_tx = TempoTxEnv::from(base.clone()); tempo_tx.fee_token = fee_token; @@ -1495,7 +1564,13 @@ impl Backend { } self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)? } else { - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)? + self.transact_with_inspector_ref( + state, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )? }; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -1518,9 +1593,9 @@ impl Backend { let mut inspector = AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?; + self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env, op_deposit)?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) @@ -2912,7 +2987,7 @@ where TracingInspectorConfig::from_geth_call_config(&call_config), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let ResultAndState { result, state: _ } = self .transact_with_inspector_ref( @@ -2920,6 +2995,7 @@ where &evm_env, &mut inspector, tx_env, + op_deposit, )?; inspector.print_logs(); @@ -2945,13 +3021,14 @@ where ), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env, + op_deposit, )?; Ok(inspector @@ -2973,22 +3050,22 @@ where } #[cfg(feature = "js-tracer")] GethDebugTracerType::JsTracer(code) => { - use alloy_evm::IntoTxEnv; let config = tracer_config.into_json(); let mut inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block.clone()); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env.clone(), + op_deposit, )?; let res = inspector - .json_result(result, &OpTx(tx_env).into_tx_env(), &block, &cache_db) + .json_result(result, &tx_env, &block, &cache_db) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(GethTrace::JS(res)) @@ -3001,9 +3078,14 @@ where .build_inspector() .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block); - let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); + let ResultAndState { result, state: _ } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -3187,10 +3269,7 @@ where f: F, ) -> Result where - for<'a> I: Inspector>>>> - + Inspector>>>> - + Inspector>>>> - + 'a, + for<'a> I: BackendInspector>>> + 'a, for<'a> F: FnOnce(ResultAndState, CacheDB>, I, TxEnv, EvmEnv) -> T, { @@ -3965,7 +4044,7 @@ impl Backend { )? .or_zero_fees(); - let (mut evm_env, tx_env) = self.build_call_env( + let (mut evm_env, tx_env, op_deposit) = self.build_call_env( WithOtherFields::new(request.clone()), fee_details, block_env.clone(), @@ -3986,8 +4065,13 @@ impl Backend { inspector = inspector.with_transfers(); } trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); - let ResultAndState { result, state } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let ResultAndState { result, state } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; trace!(target: "backend", ?result, ?request, "simulate call"); inspector.print_logs(); @@ -4359,7 +4443,10 @@ where } // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system) + #[cfg(feature = "optimism")] let is_deposit_tx = pending.transaction.as_ref().is_deposit(); + #[cfg(not(feature = "optimism"))] + let is_deposit_tx = false; let is_tempo_tx = pending.transaction.as_ref().is_tempo(); let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx { @@ -4475,6 +4562,7 @@ where ); let value = tx.value(); match tx.as_ref() { + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(deposit_tx) => { // Deposit transactions // https://specs.optimism.io/protocol/deposits.html#execution @@ -4538,6 +4626,7 @@ pub fn transaction_build( info: Option, base_fee: Option, ) -> AnyRpcTransaction { + #[cfg(feature = "optimism")] if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() { let dep_tx = deposit_tx; diff --git a/crates/anvil/src/eth/backend/mem/optimism.rs b/crates/anvil/src/eth/backend/mem/optimism.rs new file mode 100644 index 0000000000000..e9a94cc254fb7 --- /dev/null +++ b/crates/anvil/src/eth/backend/mem/optimism.rs @@ -0,0 +1,61 @@ +//! Optimism-specific transact helpers for the in-memory backend. + +use super::Backend; +use crate::eth::error::BlockchainError; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use alloy_network::Network; +use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; +use foundry_evm::backend::DatabaseError; +use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +use revm::{ + DatabaseRef, Inspector, + context::{ + TxEnv, + result::{EVMError, HaltReason, ResultAndState}, + }, + database_interface::WrapDatabaseRef, +}; + +impl Backend { + /// Optimism path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an OP EVM, injects precompiles, transacts, and maps the + /// OP-specific halt reason back to the shared [`HaltReason`]. + pub(super) fn transact_op_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: OpTransaction, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let op_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), + evm_env.block_env.clone(), + ); + let mut evm = OpEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + op_env, + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::CustomAny(err) => EVMError::CustomAny(err), + EVMError::Transaction(t) => EVMError::Transaction(t), + })?; + Ok(ResultAndState { + result: result.result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }), + state: result.state, + }) + } +} diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error/mod.rs similarity index 91% rename from crates/anvil/src/eth/error.rs rename to crates/anvil/src/eth/error/mod.rs index 3b2ada43d7731..482df681b184a 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error/mod.rs @@ -12,7 +12,6 @@ use anvil_rpc::{ response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; -use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, @@ -21,6 +20,9 @@ use serde::Serialize; use tempo_revm::TempoInvalidTransaction; use tokio::time::Duration; +#[cfg(feature = "optimism")] +mod optimism; + pub(crate) type Result = std::result::Result; #[derive(Debug, thiserror::Error)] @@ -163,51 +165,6 @@ where } } -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => match err { - OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), - OpTransactionError::DepositSystemTxPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::HaltedDepositPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), - }, - EVMError::Header(err) => match err { - InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, - InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, - }, - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => { - let op_err: OpTransactionError = err.0; - EVMError::::Transaction(op_err).into() - } - EVMError::Header(err) => EVMError::::Header(err).into(), - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - impl From> for BlockchainError where T: Into, @@ -451,16 +408,6 @@ impl From for InvalidTransactionError { } } -impl From for InvalidTransactionError { - fn from(value: OpTransactionError) -> Self { - match value { - OpTransactionError::Base(err) => err.into(), - OpTransactionError::DepositSystemTxPostRegolith - | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, - OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, - } - } -} /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -577,9 +524,13 @@ impl ToRpcResponseResult for Result { err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), } } - err @ BlockchainError::EvmError(_) => { - RpcError::internal_error_with(err.to_string()) - } + err @ BlockchainError::EvmError(_) => RpcError { + // VM halts are execution failures, not JSON-RPC server faults. REVERT has a + // dedicated code/data path above; other halts, such as invalid opcode, do not. + code: ErrorCode::TransactionRejected, + message: err.to_string().into(), + data: None, + }, err @ BlockchainError::EvmOverrideError(_) => { RpcError::invalid_params(err.to_string()) } diff --git a/crates/anvil/src/eth/error/optimism.rs b/crates/anvil/src/eth/error/optimism.rs new file mode 100644 index 0000000000000..1207fde30a72a --- /dev/null +++ b/crates/anvil/src/eth/error/optimism.rs @@ -0,0 +1,62 @@ +//! Optimism-specific error conversions for [`BlockchainError`] and +//! [`InvalidTransactionError`]. + +use super::{BlockchainError, InvalidTransactionError}; +use op_revm::OpTransactionError; +use revm::context_interface::result::{EVMError, InvalidHeader}; + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => { + let op_err: OpTransactionError = err.0; + EVMError::::Transaction(op_err).into() + } + EVMError::Header(err) => EVMError::::Header(err).into(), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From for InvalidTransactionError { + fn from(value: OpTransactionError) -> Self { + match value { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::DepositSystemTxPostRegolith + | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, + } + } +} diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 4da86933020ae..2ccf65ba721f5 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -155,9 +155,12 @@ impl EthApi { let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block - // considering only post-fork + // considering only post-fork (or post-genesis in non-fork mode) let from = if block_number == 0 { best } else { block_number - 1 }; - let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let to = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let first_page = from >= best; let mut last_page = false; @@ -198,8 +201,11 @@ impl EthApi { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); - // we go from the first post-fork block, up to the tip - let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + // we go from the first post-fork (or post-genesis) block, up to the tip + let first_block = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; @@ -248,7 +254,10 @@ impl EthApi { ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); - let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); + let from = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let to = self.backend.best_number(); for n in (from..=to).rev() { diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index df65822e1eab3..5280987483dd7 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -123,10 +123,10 @@ impl PoolTransaction { impl fmt::Debug for PoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; + write!(fmt, "hash: {:?}, ", self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; write!(fmt, "provides: [{}], ", hex_fmt_many(self.provides.iter()))?; - write!(fmt, "raw tx: {:?}", &self.pending_transaction)?; + write!(fmt, "raw tx: {:?}", self.pending_transaction)?; write!(fmt, "}}")?; Ok(()) } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 3fdf6192c4537..d1736c3093056 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,5 +1,7 @@ use crate::eth::error::BlockchainError; -use alloy_consensus::{Sealed, SignableTransaction}; +#[cfg(feature = "optimism")] +use alloy_consensus::Sealed; +use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::{Network, TxSignerSync}; use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; @@ -130,9 +132,11 @@ impl Signer for DevSigner { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip4844(t.into_signed(sig)) } + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be signed") } @@ -156,7 +160,9 @@ pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)), FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be impersonated") } diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm/mod.rs similarity index 64% rename from crates/anvil/src/evm.rs rename to crates/anvil/src/evm/mod.rs index d1b40ba56ebbd..85e43d371b097 100644 --- a/crates/anvil/src/evm.rs +++ b/crates/anvil/src/evm/mod.rs @@ -2,6 +2,9 @@ use alloy_evm::precompiles::DynPrecompile; use alloy_primitives::Address; use std::fmt::Debug; +#[cfg(feature = "optimism")] +mod optimism; + /// Object-safe trait that enables injecting extra precompiles when using /// `anvil` as a library. pub trait PrecompileFactory: Send + Sync + Unpin + Debug { @@ -15,14 +18,12 @@ mod tests { use crate::PrecompileFactory; use alloy_evm::{ - EthEvm, Evm, EvmEnv, EvmFactory, + EthEvm, Evm, eth::EthEvmContext, precompiles::{DynPrecompile, PrecompilesMap}, }; - use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; - use alloy_primitives::{Address, Bytes, TxKind, U256, address}; + use alloy_primitives::{Address, Bytes, TxKind, address}; use itertools::Itertools; - use op_revm::{OpSpecId, OpTransaction}; use revm::{ Journal, context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, @@ -35,20 +36,19 @@ mod tests { }; // A precompile activated in the `Prague` spec (BLS12-381 G2 map). - const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); + pub(super) const ETH_PRAGUE_PRECOMPILE: Address = + address!("0x0000000000000000000000000000000000000011"); // A precompile activated in the `Osaka` spec (EIP-7951). const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A precompile activated in the `Isthmus` spec. - const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A custom precompile address and payload for testing. - const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); - const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; + pub(super) const PRECOMPILE_ADDR: Address = + address!("0x0000000000000000000000000000000000000071"); + pub(super) const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; #[derive(Debug)] - struct CustomPrecompileFactory; + pub(super) struct CustomPrecompileFactory; impl PrecompileFactory for CustomPrecompileFactory { fn precompiles(&self) -> Vec<(Address, DynPrecompile)> { @@ -109,34 +109,6 @@ mod tests { (tx_env, eth_evm) } - /// Creates a new OP EVM instance. - fn create_op_evm( - _spec: SpecId, - op_spec: OpSpecId, - ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { - let tx = OpTx(OpTransaction:: { - base: TxEnv { - kind: TxKind::Call(PRECOMPILE_ADDR), - data: PAYLOAD.into(), - ..Default::default() - }, - ..Default::default() - }); - - let mut evm = OpEvmFactory::::default().create_evm_with_inspector( - EmptyDB::default(), - EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), - NoOpInspector, - ); - - if op_spec == OpSpecId::ISTHMUS { - evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); - evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); - } - - (tx, evm) - } - #[test] fn build_eth_evm_with_extra_precompiles_osaka_spec() { let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA); @@ -187,38 +159,4 @@ mod tests { assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } - - #[test] - fn build_op_evm_with_extra_precompiles_isthmus_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); - - assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_op_evm_with_extra_precompiles_bedrock_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); - - assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } } diff --git a/crates/anvil/src/evm/optimism.rs b/crates/anvil/src/evm/optimism.rs new file mode 100644 index 0000000000000..526375fec31ea --- /dev/null +++ b/crates/anvil/src/evm/optimism.rs @@ -0,0 +1,87 @@ +//! Optimism-specific EVM helpers. + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use super::super::tests::{ + CustomPrecompileFactory, ETH_PRAGUE_PRECOMPILE, PAYLOAD, PRECOMPILE_ADDR, + }; + use crate::PrecompileFactory; + use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; + use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; + use alloy_primitives::{Address, TxKind, U256, address}; + use itertools::Itertools; + use op_revm::{OpSpecId, OpTransaction}; + use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + inspector::NoOpInspector, + primitives::hardfork::SpecId, + }; + + // A precompile activated in the `Isthmus` spec. + const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + /// Creates a new OP EVM instance. + fn create_op_evm( + _spec: SpecId, + op_spec: OpSpecId, + ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { + let tx = OpTx(OpTransaction:: { + base: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + ..Default::default() + }); + + let mut evm = OpEvmFactory::::default().create_evm_with_inspector( + EmptyDB::default(), + EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), + NoOpInspector, + ); + + if op_spec == OpSpecId::ISTHMUS { + evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); + evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); + } + + (tx, evm) + } + + #[test] + fn build_op_evm_with_extra_precompiles_isthmus_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); + + assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_bedrock_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); + + assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } +} diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 26e587e8b5123..a661e9c765b26 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::{ error::{NodeError, NodeResult}, eth::{ diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index c4879e36d5240..216476c69eeb8 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -11,6 +11,7 @@ mod gas; mod genesis; mod ipc; mod logs; +#[cfg(feature = "optimism")] mod optimism; mod otterscan; mod proof; diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index ab85fc89abf80..a15454fa5593e 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -28,6 +28,38 @@ async fn test_deploy_reverting() { assert!(!receipt.inner.inner.status()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_invalid_opcode_rpc_error_code() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + // Deploy a contract whose runtime bytecode is the invalid opcode 0xfe. + let code = bytes!("60fe60005360016000f3"); + let tx = TransactionRequest::default().from(sender).with_deploy_code(code); + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract = receipt.contract_address.unwrap(); + + for (method, params) in [ + ("eth_call", serde_json::json!([{ "from": sender, "to": contract }, "latest"])), + ("eth_estimateGas", serde_json::json!([{ "from": sender, "to": contract }])), + ] { + let error = rpc_error(&handle.http_endpoint(), method, params).await; + assert_eq!(error["code"], serde_json::json!(-32003), "{error}"); + assert!(error.get("data").is_none(), "{error}"); + + let message = error["message"].as_str().unwrap(); + assert!(message.contains("EVM error InvalidFEOpcode"), "{error}"); + assert!(!message.contains("execution reverted"), "{error}"); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { sol!( @@ -124,3 +156,21 @@ async fn test_solc_revert_custom_errors() { let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); } + +async fn rpc_error(endpoint: &str, method: &str, params: serde_json::Value) -> serde_json::Value { + let response = reqwest::Client::new() + .post(endpoint) + .json(&serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": method, + "params": params, + })) + .send() + .await + .unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let body = response.json::().await.unwrap(); + body.get("error").cloned().unwrap_or_else(|| panic!("expected JSON-RPC error, got {body}")) +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dc5be77a244da..03be26a631a11 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -61,9 +61,9 @@ tempo-contracts.workspace = true tempo-primitives.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-flz.workspace = true -op-alloy-network.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-flz = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } chrono.workspace = true eyre.workspace = true @@ -100,7 +100,7 @@ anvil.workspace = true foundry-test-utils.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -109,3 +109,12 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "dep:op-alloy-flz", + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "foundry-common/optimism", + "foundry-evm-networks/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 6dc2ed6acf430..23fda25c40faa 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -29,6 +29,7 @@ use foundry_common::{ shell, stdin, }; use foundry_evm_networks::NetworkVariant; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; use std::time::Instant; use tempo_alloy::TempoNetwork; @@ -351,6 +352,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let output = if raw || fields.contains(&"raw".into()) { match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -569,6 +571,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); let output = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -791,6 +794,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::DecodeTransaction { tx, network } => { let tx = stdin::unwrap_line(tx)?; let decoded_tx = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { SimpleCast::decode_raw_transaction::(&tx)? } @@ -809,6 +813,9 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::Erc20Token { command } => command.run().await?, CastSubcommand::Tip20Token { command } => command.run().await?, CastSubcommand::Keychain { command } => command.run().await?, + CastSubcommand::Tempo { command } => command.run().await?, + CastSubcommand::VirtualAddress { command } => command.run().await?, + #[cfg(feature = "optimism")] CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index ae5c9668f522f..e25798086d512 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -9,7 +9,7 @@ use crate::{ }; use alloy_consensus::SignableTransaction; use alloy_eips::eip2718::Encodable2718; -use alloy_network::{EthereumWallet, NetworkTransactionBuilder}; +use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder}; use alloy_primitives::Address; use alloy_provider::Provider; use alloy_signer::Signer; @@ -17,7 +17,7 @@ use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use tempo_alloy::TempoNetwork; @@ -53,7 +53,7 @@ pub struct BatchMakeTxArgs { impl BatchMakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { calls, tx, eth, raw_unsigned, ethsign } = self; + let Self { calls, mut tx, eth, raw_unsigned, ethsign } = self; let has_nonce = tx.nonce.is_some(); if calls.is_empty() { @@ -63,6 +63,10 @@ impl BatchMakeTxArgs { let config = eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + // Resolve signer to detect keychain mode let (signer, tempo_access_key) = eth.wallet.maybe_signer().await?; @@ -92,14 +96,14 @@ impl BatchMakeTxArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as raw unsigned. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Set calls on the transaction builder.tx.calls = tempo_calls; @@ -117,6 +121,7 @@ impl BatchMakeTxArgs { let from = eth.wallet.from.unwrap_or(Address::ZERO); let (tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -125,6 +130,7 @@ impl BatchMakeTxArgs { if ethsign { let (tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; return Ok(()); @@ -137,7 +143,9 @@ impl BatchMakeTxArgs { }; let signed_tx = if let Some(ref access_key) = tempo_access_key { - let (tx, _) = tx_builder.build(access_key.wallet_address).await?; + let (tx, _) = + tx_builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = tx .sign_with_access_key( &provider, @@ -151,6 +159,7 @@ impl BatchMakeTxArgs { } else { tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?; let (tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let envelope = tx.build(&EthereumWallet::new(signer)).await?; alloy_primitives::hex::encode(envelope.encoded_2718()) }; diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index ec7254b08e2f5..33128ae897898 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -9,14 +9,14 @@ use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, tx::{self, CastTxBuilder, SendTxOpts}, }; -use alloy_network::EthereumWallet; +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::TransactionOpts, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::provider::ProviderBuilder; use std::time::Duration; @@ -50,7 +50,7 @@ pub struct BatchSendArgs { impl BatchSendArgs { pub async fn run(self) -> Result<()> { - let Self { calls, send_tx, tx, unlocked } = self; + let Self { calls, send_tx, mut tx, unlocked } = self; if calls.is_empty() { return Err(eyre!("No calls specified. Use --call to specify at least one call.")); @@ -59,6 +59,10 @@ impl BatchSendArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -93,14 +97,14 @@ impl BatchSendArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as unlocked. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Access the inner tx and set calls builder.tx.calls = tempo_calls; @@ -116,6 +120,7 @@ impl BatchSendArgs { if unlocked { let (tx, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; cast_send( provider, tx, @@ -132,7 +137,12 @@ impl BatchSendArgs { }; if let Some(ref access_key) = tempo_access_key { - let (tx_request, _) = builder.build(access_key.wallet_address).await?; + let (tx_request, _) = + builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; cast_send_with_access_key( &provider, tx_request, @@ -146,6 +156,10 @@ impl BatchSendArgs { } else { tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?; let (tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() .wallet(wallet) diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 3637f166a53df..63ea17f707e03 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -33,10 +33,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, }, executors::TracingExecutor, opts::EvmOpts, @@ -222,18 +224,19 @@ impl CallArgs { return self.run_curl().await; } if self.tx.tempo.is_tempo() { - self.run_with_network::().await - } else { - let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); - let mut evm_opts = figment.extract::()?; - evm_opts.infer_network_from_fork().await; + return self.run_with_network::().await; + } - if evm_opts.networks.is_optimism() { - self.run_with_network::().await - } else { - self.run_with_network::().await - } + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + evm_opts.infer_network_from_fork().await; + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_network::().await; } + + self.run_with_network::().await } pub async fn run_with_network(self) -> Result<()> diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index 8b7d80786dfad..897a01c39202a 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -1,9 +1,12 @@ use alloy_ens::NameOrAddress; -use alloy_network::EthereumWallet; +use std::time::Duration; + +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, U256, hex, keccak256}; -use alloy_provider::ProviderBuilder as AlloyProviderBuilder; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use alloy_sol_types::SolCall; +use alloy_transport::TransportError; use chrono::DateTime; use clap::Parser; use eyre::Result; @@ -12,11 +15,16 @@ use foundry_cli::{ utils::LoadConfig, }; use foundry_common::{ + FoundryTransactionBuilder, provider::ProviderBuilder, - shell, - tempo::{self, KeyType, KeysFile, WalletType, read_tempo_keys_file, tempo_keys_path}, + sh_warn, shell, + tempo::{ + self, KeyType, KeysFile, TEMPO_BROWSER_GAS_BUFFER, WalletType, read_tempo_keys_file, + tempo_keys_path, + }, }; use foundry_evm::hardfork::TempoHardfork; +use serde::Deserialize; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_contracts::precompiles::{ ACCOUNT_KEYCHAIN_ADDRESS, IAccountKeychain, @@ -24,13 +32,16 @@ use tempo_contracts::precompiles::{ CallScope, KeyInfo, KeyRestrictions, LegacyTokenLimit, SelectorRule, SignatureType, TokenLimit, }, + ITIP20, PATH_USD_ADDRESS, account_keychain::{authorizeKeyCall, legacyAuthorizeKeyCall}, }; use yansi::Paint; +use foundry_cli::utils::{maybe_print_resolved_lane, resolve_lane}; + use crate::{ - cmd::send::{cast_send, cast_send_with_access_key}, - tx::{CastTxBuilder, SendTxOpts}, + cmd::send::cast_send, + tx::{CastTxBuilder, CastTxSender, SendTxOpts}, }; /// Tempo keychain management commands. @@ -62,6 +73,19 @@ pub enum KeychainSubcommand { rpc: RpcOpts, }, + /// Inspect an access key policy using the local key registry and on-chain state. + Inspect { + /// The key address to inspect. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + #[command(flatten)] + rpc: RpcOpts, + }, + /// Authorize a new key on-chain via the AccountKeychain precompile. #[command(visible_alias = "auth")] Authorize { @@ -183,8 +207,92 @@ pub enum KeychainSubcommand { #[command(flatten)] send_tx: SendTxOpts, }, + + /// Read or edit TIP-1011 access-key permissions. + Policy { + #[command(subcommand)] + command: KeychainPolicySubcommand, + }, +} + +/// Higher-level access-key policy editing commands. +#[derive(Debug, Parser)] +pub enum KeychainPolicySubcommand { + /// Add or widen an allowed call rule for a target contract. + AddCall { + /// The key address to update. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + /// Target contract address. + #[arg(long)] + target: Address, + + /// Function selector, full signature, or known TIP-20 shorthand. + #[arg(long, value_parser = parse_selector_arg)] + selector: SelectorArg, + + /// Optional recipient/spender restrictions for selector calls. + #[arg(long, value_delimiter = ',')] + recipients: Vec
, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Update a token spending limit amount for a key. + SetLimit { + /// The key address to update. + key_address: Address, + + /// Token address, numeric TIP-20 token id, or PathUSD. + #[arg(long, value_parser = parse_policy_token)] + token: Address, + + /// New raw token-denominated limit. + #[arg(long)] + amount: U256, + + /// Limit period such as 7d, 24h, or 3600s. + /// + /// The current AccountKeychain update entrypoint cannot change periods, so non-zero + /// values are rejected. + #[arg(long, value_parser = parse_period)] + period: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove all allowed-call rules for a target contract. + RemoveTarget { + /// The key address to update. + key_address: Address, + + /// Target contract address to remove. + #[arg(long)] + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, } +#[derive(Debug, Clone, Copy)] +pub struct SelectorArg([u8; 4]); + fn parse_signature_type(s: &str) -> Result { match s.to_lowercase().as_str() { "secp256k1" => Ok(SignatureType::Secp256k1), @@ -203,6 +311,15 @@ const fn signature_type_name(t: &SignatureType) -> &'static str { } } +const fn signature_type_label(t: &SignatureType) -> &'static str { + match t { + SignatureType::Secp256k1 => "Secp256k1", + SignatureType::P256 => "P256", + SignatureType::WebAuthn => "WebAuthn", + _ => "unknown", + } +} + const fn key_type_name(t: &KeyType) -> &'static str { match t { KeyType::Secp256k1 => "secp256k1", @@ -211,6 +328,14 @@ const fn key_type_name(t: &KeyType) -> &'static str { } } +const fn key_type_label(t: &KeyType) -> &'static str { + match t { + KeyType::Secp256k1 => "Secp256k1", + KeyType::P256 => "P256", + KeyType::WebAuthn => "WebAuthn", + } +} + const fn wallet_type_name(t: &WalletType) -> &'static str { match t { WalletType::Local => "local", @@ -332,6 +457,48 @@ fn parse_selector_bytes(s: &str) -> Result<[u8; 4], String> { } } +fn parse_selector_arg(s: &str) -> Result { + parse_selector_bytes(s).map(SelectorArg) +} + +fn parse_policy_token(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "pathusd" | "path_usd" | "path-usd" | "usd" => Ok(PATH_USD_ADDRESS), + _ => foundry_cli::utils::parse_fee_token_address(s).map_err(|e| e.to_string()), + } +} + +fn parse_period(s: &str) -> Result { + let s = s.trim(); + if s.is_empty() { + return Err("period cannot be empty".to_string()); + } + + let split = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len()); + if split == 0 { + return Err(format!( + "invalid period '{s}': expected a number followed by s, m, h, d, or w" + )); + } + + let value: u64 = + s[..split].parse().map_err(|e| format!("invalid period value '{}': {e}", &s[..split]))?; + let multiplier = match &s[split..].to_ascii_lowercase()[..] { + "" | "s" => 1, + "m" => 60, + "h" => 60 * 60, + "d" => 24 * 60 * 60, + "w" => 7 * 24 * 60 * 60, + unit => { + return Err(format!( + "invalid period unit '{unit}' in '{s}' (expected s, m, h, d, or w)" + )); + } + }; + + value.checked_mul(multiplier).ok_or_else(|| format!("period '{s}' is too large")) +} + /// Represents a single scope entry in JSON format for `--scopes`. #[derive(serde::Deserialize)] struct JsonCallScope { @@ -402,6 +569,9 @@ impl KeychainSubcommand { Self::Check { wallet_address, key_address, rpc } => { run_check(wallet_address, key_address, rpc).await } + Self::Inspect { key_address, root_account, rpc } => { + run_inspect(key_address, root_account, rpc).await + } Self::Authorize { key_address, key_type, @@ -443,6 +613,40 @@ impl KeychainSubcommand { Self::RemoveScope { key_address, target, tx, send_tx } => { run_remove_scope(key_address, target, tx, send_tx).await } + Self::Policy { command } => command.run().await, + } + } +} + +impl KeychainPolicySubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::AddCall { + key_address, + root_account, + target, + selector, + recipients, + tx, + send_tx, + } => { + run_policy_add_call( + key_address, + root_account, + target, + selector.0, + recipients, + tx, + send_tx, + ) + .await + } + Self::SetLimit { key_address, token, amount, period, tx, send_tx } => { + run_policy_set_limit(key_address, token, amount, period, tx, send_tx).await + } + Self::RemoveTarget { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await + } } } } @@ -500,6 +704,143 @@ fn run_show(wallet_address: Address) -> Result<()> { Ok(()) } +#[derive(Debug, Clone)] +struct LocalLimitMetadata { + token: Address, + amount: String, +} + +#[derive(Debug, Clone)] +struct KeyMetadata { + root_account: Address, + key_type: Option, + limits: Vec, +} + +#[derive(Debug, Clone)] +struct InspectedLimit { + token: Address, + configured_amount: Option, + remaining: U256, + period_end: Option, +} + +#[derive(Debug, Clone)] +enum AllowedCallsView { + Unsupported, + Unrestricted, + Scoped(Vec), +} + +/// `cast keychain inspect ` — inspect on-chain key policy. +async fn run_inspect( + key_address: Address, + root_account: Option
, + rpc: RpcOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let info: KeyInfo = provider.get_keychain_key(metadata.root_account, key_address).await?; + let provisioned = info.keyId != Address::ZERO; + let is_t3 = is_tempo_hardfork_active(&provider, TempoHardfork::T3).await?; + + let mut limits = Vec::new(); + if info.enforceLimits { + for local_limit in &metadata.limits { + let (remaining, period_end) = if is_t3 { + let limit = provider + .get_keychain_remaining_limit_with_period( + metadata.root_account, + key_address, + local_limit.token, + ) + .await?; + (limit.remaining, Some(limit.periodEnd)) + } else { + let remaining = provider + .account_keychain() + .getRemainingLimit(metadata.root_account, key_address, local_limit.token) + .call() + .await?; + (remaining, None) + }; + + limits.push(InspectedLimit { + token: local_limit.token, + configured_amount: Some(local_limit.amount.clone()), + remaining, + period_end, + }); + } + } + + let allowed_calls = if is_t3 { + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + if allowed.isScoped { + AllowedCallsView::Scoped(allowed.scopes) + } else { + AllowedCallsView::Unrestricted + } + } else { + AllowedCallsView::Unsupported + }; + + if shell::is_json() { + let key_type = if provisioned { + signature_type_name(&info.signatureType).to_string() + } else { + metadata + .key_type + .map(|key_type| key_type_name(&key_type).to_string()) + .unwrap_or_else(|| "unknown".to_string()) + }; + let json = serde_json::json!({ + "root_account": metadata.root_account.to_string(), + "key_id": key_address.to_string(), + "provisioned": provisioned, + "type": key_type, + "expiry": provisioned.then_some(info.expiry), + "expiry_human": provisioned.then(|| format_expiry_for_inspect(info.expiry)), + "enforce_limits": info.enforceLimits, + "is_revoked": info.isRevoked, + "limits": limits.iter().map(inspected_limit_to_json).collect::>(), + "allowed_calls": allowed_calls_to_json(&allowed_calls), + }); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + let key_type = if provisioned { + signature_type_label(&info.signatureType) + } else { + metadata.key_type.map(|key_type| key_type_label(&key_type)).unwrap_or("unknown") + }; + + sh_println!("Root account: {}", metadata.root_account)?; + sh_println!("Key id: {key_address}")?; + sh_println!("Type: {key_type}")?; + + if info.isRevoked { + sh_println!("Status: revoked")?; + } else if !provisioned { + sh_println!("Status: not provisioned")?; + } else { + sh_println!("Status: active")?; + sh_println!("Expiry: {}", format_expiry_for_inspect(info.expiry))?; + } + + print_inspected_limits(info.enforceLimits, &limits)?; + print_allowed_calls(&allowed_calls)?; + + Ok(()) +} + /// `cast keychain check` / `cast keychain info` — query on-chain key status. async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) -> Result<()> { let config = rpc.load_config()?; @@ -584,7 +925,7 @@ async fn run_authorize( let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let calldata = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let calldata = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { // T3+ authorizeKey(address,SignatureType,KeyRestrictions) let restrictions = KeyRestrictions { expiry, @@ -634,7 +975,7 @@ async fn run_remaining_limit( let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let remaining: U256 = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let remaining: U256 = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { provider.get_keychain_remaining_limit(wallet_address, key_address, token).await? } else { // Pre-T3: use the legacy getRemainingLimit(address,address,address) @@ -646,7 +987,7 @@ async fn run_remaining_limit( }; if shell::is_json() { - sh_println!("{}", serde_json::to_string(&remaining.to_string())?)?; + sh_println!("{}", serde_json::json!({ "remaining": remaining.to_string() }))?; } else { sh_println!("{remaining}")?; } @@ -695,6 +1036,88 @@ async fn run_remove_scope( send_keychain_tx(calldata, tx_opts, &send_tx).await } +/// `cast keychain policy add-call` — merge a selector rule into a target scope. +async fn run_policy_add_call( + key_address: Address, + root_account: Option
, + target: Address, + selector: [u8; 4], + recipients: Vec
, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if !is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + eyre::bail!("allowed-call policy editing requires the Tempo T3 hardfork"); + } + + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + + let new_rule = SelectorRule { selector: selector.into(), recipients }; + let existing_target = allowed + .isScoped + .then(|| allowed.scopes.into_iter().find(|scope| scope.target == target)) + .flatten(); + + let (target_scope, changed) = match existing_target { + Some(mut scope) => { + if scope.selectorRules.is_empty() { + sh_warn!( + "Allowed calls for {} already allow any selector; leaving wildcard scope unchanged", + address_label_with_address(target) + )?; + } + let changed = add_selector_rule_to_scope(&mut scope, new_rule); + (scope, changed) + } + None => (CallScope { target, selectorRules: vec![new_rule] }, true), + }; + + if !changed { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ "status": "already_present", "target": target.to_string() }) + )?; + } else { + sh_println!("Allowed call already present for {}", address_label_with_address(target))?; + } + return Ok(()); + } + + let calldata = + IAccountKeychain::setAllowedCallsCall { keyId: key_address, scopes: vec![target_scope] } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain policy set-limit` — update a spending limit amount. +async fn run_policy_set_limit( + key_address: Address, + token: Address, + amount: U256, + period: Option, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + if period.is_some_and(|period| period != 0) { + eyre::bail!( + "--period is not supported by the current AccountKeychain updateSpendingLimit \ + precompile; periods can only be set when authorizing a key" + ); + } + + // updateSpendingLimit authorizes against msg.sender; the root account is not part of calldata. + run_update_limit(key_address, token, amount, tx_opts, send_tx).await +} + /// Shared helper to send a keychain precompile transaction. async fn send_keychain_tx( calldata: Vec, @@ -702,16 +1125,22 @@ async fn send_keychain_tx( send_tx: &SendTxOpts, ) -> Result<()> { let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let print_sponsor_hash = tx_opts.tempo.print_sponsor_hash; + let tempo_sponsor = + if print_sponsor_hash { None } else { tx_opts.tempo.sponsor_config().await? }; let config = send_tx.eth.load_config()?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); let provider = ProviderBuilder::::from_config(&config)?.build()?; - // Inject key_id for correct gas estimation with keychain signature overhead. - if let Some(ref ak) = tempo_access_key { - tx_opts.tempo.key_id = Some(ak.key_address); + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx_opts.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx_opts.tempo, &config.root)?; + let builder = CastTxBuilder::new(&provider, tx_opts, &config) .await? .with_to(Some(NameOrAddress::Address(ACCOUNT_KEYCHAIN_ADDRESS))) @@ -719,27 +1148,70 @@ async fn send_keychain_tx( .with_code_sig_and_args(None, Some(hex::encode_prefixed(&calldata)), vec![]) .await?; - if let Some(ref ak) = tempo_access_key { - let signer = - signer.as_ref().ok_or_else(|| eyre::eyre!("signer required for access key"))?; - let (tx, _) = builder.build(ak.wallet_address).await?; - cast_send_with_access_key( - &provider, - tx, - signer, - ak, - send_tx.cast_async, - send_tx.confirmations, - timeout, - ) - .await?; + // Keychain management calls are authorized by the root account. Access keys can use their + // permissions, but cannot mutate their own key policy. + let browser = send_tx.browser.run::().await?; + + if print_sponsor_hash { + let from = if let Some(ref browser) = browser { + browser.address() + } else { + signer + .as_ref() + .ok_or_else(|| { + eyre::eyre!( + "--tempo.print-sponsor-hash requires a root account signer, such as \ + --browser, --private-key, or --keystore" + ) + })? + .address() + }; + + let (tx, _) = builder.build(from).await?; + let hash = tx + .compute_sponsor_hash(from) + .ok_or_else(|| eyre::eyre!("This network does not support sponsored transactions"))?; + if shell::is_json() { + sh_println!("{}", serde_json::json!({ "sponsor_hash": format!("{hash:?}") }))?; + } else { + sh_println!("{hash:?}")?; + } + return Ok(()); + } + + if let Some(browser) = browser { + let chain = builder.chain(); + let (mut tx, _) = builder.build(browser.address()).await?; + if chain.is_tempo() + && let Some(gas) = tx.gas_limit() + { + tx.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, browser.address()).await?; + } + + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&provider) + .print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout) + .await?; + } else if tempo_access_key.is_some() { + eyre::bail!( + "keychain policy changes must be signed by the root account; the selected `--from` \ + resolved to a Tempo access key. Use `--browser` for passkey roots, or pass a root \ + account signer with `--private-key`, `--keystore`, Ledger, Trezor, AWS, GCP, or Turnkey." + ); } else { let signer = match signer { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (mut tx, _) = builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() @@ -753,6 +1225,361 @@ async fn send_keychain_tx( Ok(()) } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct AnvilNodeInfo { + hard_fork: Option, + network: Option, +} + +async fn is_tempo_hardfork_active

(provider: &P, hardfork: TempoHardfork) -> Result +where + P: Provider, +{ + match provider.is_hardfork_active(hardfork).await { + Ok(active) => Ok(active), + Err(err) if is_rpc_method_not_found(&err) => { + match anvil_tempo_hardfork_active(provider, hardfork).await { + Ok(Some(active)) => Ok(active), + _ => Err(err.into()), + } + } + Err(err) => Err(err.into()), + } +} + +async fn anvil_tempo_hardfork_active

( + provider: &P, + hardfork: TempoHardfork, +) -> Result, TransportError> +where + P: Provider, +{ + let info = provider.raw_request::<_, AnvilNodeInfo>("anvil_nodeInfo".into(), ()).await?; + Ok(active_from_anvil_node_info(&info, hardfork)) +} + +fn active_from_anvil_node_info(info: &AnvilNodeInfo, hardfork: TempoHardfork) -> Option { + (info.network.as_deref() == Some("tempo")).then(|| { + info.hard_fork + .as_deref() + .and_then(|active_hardfork| active_hardfork.parse::().ok()) + .is_some_and(|active_hardfork| active_hardfork >= hardfork) + }) +} + +fn is_rpc_method_not_found(err: &TransportError) -> bool { + err.as_error_resp().is_some_and(|payload| payload.code == -32601) +} + +fn resolve_key_metadata( + key_address: Address, + root_account: Option

, +) -> Result { + let keys_file = read_tempo_keys_file(); + + if let Some(root_account) = root_account { + if let Some(keys_file) = keys_file.as_ref() + && let Some(entry) = keys_file.keys.iter().find(|entry| { + entry.wallet_address == root_account + && key_entry_effective_key(entry) == key_address + }) + { + return Ok(key_metadata_from_entry(entry)); + } + + return Ok(KeyMetadata { root_account, key_type: None, limits: Vec::new() }); + } + + let Some(keys_file) = keys_file.as_ref() else { + eyre::bail!( + "key {key_address} was not found because the local keys file could not be read at {}; pass --root-account", + tempo_keys_path_display() + ); + }; + + let matches: Vec<_> = keys_file + .keys + .iter() + .filter(|entry| key_entry_effective_key(entry) == key_address) + .collect(); + + if matches.is_empty() { + eyre::bail!( + "key {key_address} was not found in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let root_account = matches[0].wallet_address; + if matches.iter().any(|entry| entry.wallet_address != root_account) { + eyre::bail!( + "key {key_address} matches multiple root accounts in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let entry = + matches.iter().copied().find(|entry| !entry.limits.is_empty()).unwrap_or(matches[0]); + Ok(key_metadata_from_entry(entry)) +} + +fn key_entry_effective_key(entry: &tempo::KeyEntry) -> Address { + entry.key_address.unwrap_or(entry.wallet_address) +} + +fn key_metadata_from_entry(entry: &tempo::KeyEntry) -> KeyMetadata { + KeyMetadata { + root_account: entry.wallet_address, + key_type: Some(entry.key_type), + limits: entry + .limits + .iter() + .map(|limit| LocalLimitMetadata { token: limit.currency, amount: limit.limit.clone() }) + .collect(), + } +} + +fn tempo_keys_path_display() -> String { + tempo_keys_path() + .map(|path| path.display().to_string()) + .unwrap_or_else(|| "(unknown)".to_string()) +} + +fn add_selector_rule_to_scope(scope: &mut CallScope, rule: SelectorRule) -> bool { + if scope.selectorRules.is_empty() { + return false; + } + + let Some(existing_rule) = + scope.selectorRules.iter_mut().find(|existing| existing.selector == rule.selector) + else { + scope.selectorRules.push(rule); + return true; + }; + + if existing_rule.recipients.is_empty() { + return false; + } + + if rule.recipients.is_empty() { + existing_rule.recipients = Vec::new(); + return true; + } + + let mut changed = false; + for recipient in rule.recipients { + if !existing_rule.recipients.contains(&recipient) { + existing_rule.recipients.push(recipient); + changed = true; + } + } + changed +} + +fn inspected_limit_to_json(limit: &InspectedLimit) -> serde_json::Value { + serde_json::json!({ + "token": limit.token.to_string(), + "token_label": address_label(limit.token), + "configured_amount": limit.configured_amount.as_deref(), + "remaining": limit.remaining.to_string(), + "period_end": limit.period_end, + "period_end_human": limit.period_end.and_then(|period_end| { + (period_end != 0).then(|| format_period_end(period_end)) + }), + }) +} + +fn allowed_calls_to_json(allowed_calls: &AllowedCallsView) -> serde_json::Value { + match allowed_calls { + AllowedCallsView::Unsupported => serde_json::json!({ + "mode": "unsupported", + "scopes": [], + }), + AllowedCallsView::Unrestricted => serde_json::json!({ + "mode": "any", + "scopes": [], + }), + AllowedCallsView::Scoped(scopes) => serde_json::json!({ + "mode": if scopes.is_empty() { "none" } else { "scoped" }, + "scopes": scopes.iter().map(call_scope_to_json).collect::>(), + }), + } +} + +fn call_scope_to_json(scope: &CallScope) -> serde_json::Value { + serde_json::json!({ + "target": scope.target.to_string(), + "target_label": address_label(scope.target), + "selectors": scope.selectorRules.iter().map(selector_rule_to_json).collect::>(), + }) +} + +fn selector_rule_to_json(rule: &SelectorRule) -> serde_json::Value { + serde_json::json!({ + "selector": selector_hex(&rule.selector.0), + "signature": selector_signature(&rule.selector.0), + "recipients": rule.recipients.iter().map(ToString::to_string).collect::>(), + }) +} + +fn print_inspected_limits(enforce_limits: bool, limits: &[InspectedLimit]) -> Result<()> { + if !enforce_limits { + sh_println!("Limits: none")?; + return Ok(()); + } + + sh_println!("Limits:")?; + if limits.is_empty() { + sh_println!(" enforced, but no local limit metadata was found")?; + return Ok(()); + } + + for limit in limits { + let configured = limit.configured_amount.as_deref().unwrap_or("unknown"); + let period = limit + .period_end + .and_then(|period_end| { + (period_end != 0).then(|| format!(" ({})", format_period_end(period_end))) + }) + .unwrap_or_default(); + sh_println!( + " {}: {} / {} remaining{}", + address_label(limit.token), + limit.remaining, + configured, + period + )?; + } + + Ok(()) +} + +fn print_allowed_calls(allowed_calls: &AllowedCallsView) -> Result<()> { + match allowed_calls { + AllowedCallsView::Unsupported => sh_println!("Allowed calls: unsupported before T3")?, + AllowedCallsView::Unrestricted => sh_println!("Allowed calls: any")?, + AllowedCallsView::Scoped(scopes) if scopes.is_empty() => { + sh_println!("Allowed calls: none")?; + } + AllowedCallsView::Scoped(scopes) => { + sh_println!("Allowed calls:")?; + for scope in scopes { + sh_println!(" {}:", address_label_with_address(scope.target))?; + if scope.selectorRules.is_empty() { + sh_println!(" any selector")?; + continue; + } + + for rule in &scope.selectorRules { + sh_println!( + " {} -> {}", + format_selector(&rule.selector.0), + format_recipients(&rule.recipients) + )?; + } + } + } + } + + Ok(()) +} + +fn address_label(address: Address) -> String { + if address == PATH_USD_ADDRESS { "PathUSD".to_string() } else { address.to_string() } +} + +fn address_label_with_address(address: Address) -> String { + if address == PATH_USD_ADDRESS { format!("PathUSD ({address})") } else { address.to_string() } +} + +fn format_selector(selector: &[u8; 4]) -> String { + selector_signature(selector).map(str::to_string).unwrap_or_else(|| selector_hex(selector)) +} + +fn selector_signature(selector: &[u8; 4]) -> Option<&'static str> { + if selector == &ITIP20::transferCall::SELECTOR { + Some("transfer(address,uint256)") + } else if selector == &ITIP20::approveCall::SELECTOR { + Some("approve(address,uint256)") + } else if selector == &ITIP20::transferFromCall::SELECTOR { + Some("transferFrom(address,address,uint256)") + } else if selector == &ITIP20::transferWithMemoCall::SELECTOR { + Some("transferWithMemo(address,uint256,bytes32)") + } else if selector == &ITIP20::transferFromWithMemoCall::SELECTOR { + Some("transferFromWithMemo(address,address,uint256,bytes32)") + } else if selector == &ITIP20::mintCall::SELECTOR { + Some("mint(address,uint256)") + } else if selector == &ITIP20::burnCall::SELECTOR { + Some("burn(uint256)") + } else { + None + } +} + +fn selector_hex(selector: &[u8; 4]) -> String { + hex::encode_prefixed(selector) +} + +fn format_recipients(recipients: &[Address]) -> String { + if recipients.is_empty() { + return "any recipient".to_string(); + } + + let recipients = recipients.iter().map(ToString::to_string).collect::>().join(", "); + format!("recipients [{recipients}]") +} + +fn format_expiry_for_inspect(expiry: u64) -> String { + if expiry == u64::MAX { + return "never".to_string(); + } + + format!("{} ({})", format_timestamp_iso(expiry), format_relative_timestamp(expiry)) +} + +fn format_period_end(period_end: u64) -> String { + format!("period resets {}", format_relative_timestamp(period_end)) +} + +fn format_timestamp_iso(timestamp: u64) -> String { + DateTime::from_timestamp(timestamp as i64, 0) + .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) + .unwrap_or_else(|| timestamp.to_string()) +} + +fn format_relative_timestamp(timestamp: u64) -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + if timestamp == now { + "now".to_string() + } else if timestamp > now { + format!("in {}", format_duration_words(timestamp - now)) + } else { + format!("{} ago", format_duration_words(now - timestamp)) + } +} + +fn format_duration_words(seconds: u64) -> String { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + const DAY: u64 = 24 * HOUR; + + if seconds >= DAY { + let days = seconds / DAY; + if days == 1 { "1 day".to_string() } else { format!("{days} days") } + } else if seconds >= HOUR { + format!("{}h", seconds / HOUR) + } else if seconds >= MINUTE { + format!("{}m", seconds / MINUTE) + } else { + format!("{seconds}s") + } +} + fn format_expiry(expiry: u64) -> String { if expiry == u64::MAX { return "never".to_string(); @@ -842,6 +1669,7 @@ fn key_entry_to_json(entry: &tempo::KeyEntry) -> serde_json::Value { #[cfg(test)] mod tests { use super::*; + use alloy_json_rpc::ErrorPayload; use std::str::FromStr; #[test] @@ -967,4 +1795,144 @@ mod tests { let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":[{"selector":"transfer","recipients":[],"bogus":true}]}]"#; assert!(parse_scopes_json(json).is_err()); } + + #[test] + fn test_parse_policy_token_path_usd() { + assert_eq!(parse_policy_token("PathUSD").unwrap(), PATH_USD_ADDRESS); + assert_eq!(parse_policy_token("path-usd").unwrap(), PATH_USD_ADDRESS); + } + + #[test] + fn test_parse_period_units() { + assert_eq!(parse_period("0").unwrap(), 0); + assert_eq!(parse_period("30s").unwrap(), 30); + assert_eq!(parse_period("5m").unwrap(), 300); + assert_eq!(parse_period("2h").unwrap(), 7200); + assert_eq!(parse_period("7d").unwrap(), 604800); + assert_eq!(parse_period("2w").unwrap(), 1209600); + assert!(parse_period("1mo").is_err()); + } + + #[test] + fn test_add_selector_rule_merges_recipients() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let second = Address::from_str("0x2222222222222222222222222222222222222222").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![second], + }, + ); + + assert!(changed); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].recipients, vec![first, second]); + } + + #[test] + fn test_add_selector_rule_empty_recipients_widens_to_any() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(changed); + assert!(scope.selectorRules[0].recipients.is_empty()); + } + + #[test] + fn test_add_selector_rule_target_wildcard_is_unchanged() { + let mut scope = CallScope { target: PATH_USD_ADDRESS, selectorRules: vec![] }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(!changed); + assert!(scope.selectorRules.is_empty()); + } + + #[test] + fn test_policy_set_limit_parses() { + let key = "0x1111111111111111111111111111111111111111"; + + let command = KeychainSubcommand::try_parse_from([ + "keychain", + "policy", + "set-limit", + key, + "--token", + "PathUSD", + "--amount", + "123", + ]) + .unwrap(); + + match command { + KeychainSubcommand::Policy { + command: + KeychainPolicySubcommand::SetLimit { key_address, token, amount, period, .. }, + } => { + assert_eq!(key_address, Address::from_str(key).unwrap()); + assert_eq!(token, PATH_USD_ADDRESS); + assert_eq!(amount, U256::from(123)); + assert_eq!(period, None); + } + other => panic!("unexpected command: {other:?}"), + } + } + + #[test] + fn test_active_from_anvil_node_info_requires_tempo_network() { + let tempo_t3 = + AnvilNodeInfo { network: Some("tempo".to_string()), hard_fork: Some("T3".to_string()) }; + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T2), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T3), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T4), Some(false)); + + let ethereum_t3 = AnvilNodeInfo { + network: Some("ethereum".to_string()), + hard_fork: Some("T3".to_string()), + }; + assert_eq!(active_from_anvil_node_info(ðereum_t3, TempoHardfork::T3), None); + } + + #[test] + fn test_rpc_method_not_found_detection() { + let method_missing: TransportError = + TransportError::ErrorResp(ErrorPayload::method_not_found()); + assert!(is_rpc_method_not_found(&method_missing)); + + let internal_error: TransportError = + TransportError::ErrorResp(ErrorPayload::internal_error()); + assert!(!is_rpc_method_not_found(&internal_error)); + + let transport_error = alloy_transport::TransportErrorKind::backend_gone(); + assert!(!is_rpc_method_not_found(&transport_error)); + } } diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index 8aaf6e97a1827..67178cd093d7b 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -2,7 +2,9 @@ use crate::tx::{self, CastTxBuilder}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network, NetworkTransactionBuilder}; +use alloy_network::{ + Ethereum, EthereumWallet, Network, NetworkTransactionBuilder, TransactionBuilder, +}; use alloy_primitives::{Address, hex}; use alloy_provider::Provider; use alloy_signer::{Signature, Signer}; @@ -10,7 +12,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::LoadConfig, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use std::{path::PathBuf, str::FromStr}; @@ -94,9 +96,13 @@ impl MakeTxArgs { N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; + let Self { to, mut sig, mut args, command, mut tx, path, eth, raw_unsigned, ethsign } = + self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -117,6 +123,11 @@ impl MakeTxArgs { let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + // Must happen before `tx.clone()` so the cloned tx carries the resolved nonce_key. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) .await? .with_to(to) @@ -139,6 +150,10 @@ impl MakeTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + if raw_unsigned { // Build unsigned raw tx // Check if nonce is provided when --from is not specified @@ -148,11 +163,20 @@ impl MakeTxArgs { "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" ); } + if tempo_sponsor.is_some() && eth.wallet.from.is_none() { + eyre::bail!( + "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender" + ); + } // Use zero address as placeholder for unsigned transactions let from = eth.wallet.from.unwrap_or(Address::ZERO); - let (tx, _) = tx_builder.build(from).await?; + let (mut tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -162,7 +186,11 @@ impl MakeTxArgs { if ethsign { // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has // unlocked accounts. - let (tx, _) = tx_builder.build(config.sender).await?; + let (mut tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, config.sender).await?; + } let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; @@ -176,7 +204,11 @@ impl MakeTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = tx_builder.build(&signer).await?; + let (mut tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let tx = tx.build(&EthereumWallet::new(signer)).await?; diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 6a9d11f5dc61d..0b1b26615694a 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -15,6 +15,7 @@ pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; +#[cfg(feature = "optimism")] pub mod da_estimate; pub mod erc20; pub mod estimate; @@ -28,7 +29,9 @@ pub mod rpc; pub mod run; pub mod send; pub mod storage; +pub mod tempo; pub mod tip20; pub mod trace; pub mod txpool; +pub mod vaddr; pub mod wallet; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 84699d6cd6956..7e52a9e265f25 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -29,10 +29,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock as _, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, }, executors::{EvmError, Executor, TracingExecutor}, hardforks::FoundryHardfork, @@ -123,12 +125,15 @@ impl RunArgs { evm_opts.infer_network_from_fork().await; if evm_opts.networks.is_tempo() { - self.run_with_evm::().await - } else if evm_opts.networks.is_optimism() { - self.run_with_evm::().await - } else { - self.run_with_evm::().await + return self.run_with_evm::().await; + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_evm::().await; } + + self.run_with_evm::().await } async fn run_with_evm(self) -> Result<()> { diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 2d6e248cc7a73..421aae1f2153e 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -8,7 +8,10 @@ use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::{Signature, Signer}; use clap::Parser; use eyre::{Result, eyre}; -use foundry_cli::{opts::TransactionOpts, utils::LoadConfig}; +use foundry_cli::{ + opts::TransactionOpts, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; use foundry_common::{ FoundryTransactionBuilder, fmt::{UIfmt, UIfmtReceiptExt}, @@ -119,7 +122,9 @@ impl SendTxArgs { self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; - let sponsor_signature = tx.tempo.sponsor_signature; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -183,6 +188,8 @@ impl SendTxArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -202,13 +209,19 @@ impl SendTxArgs { // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. if print_sponsor_hash { - // Use the pre-resolved signer to derive the actual sender address, since the - // sponsor hash commits to the sender. - let signer = pre_resolved_signer.as_ref().ok_or_else(|| { - eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") - })?; - let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (tx, from) = if let Some(ref ak) = access_key { + let (tx, _) = builder.build_with_access_key(ak.wallet_address, ak).await?; + (tx, ak.wallet_address) + } else { + // Use the pre-resolved signer to derive the actual sender address, since the + // sponsor hash commits to the sender. + let signer = pre_resolved_signer.as_ref().ok_or_else(|| { + eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") + })?; + let from = signer.address(); + let (tx, _) = builder.build(from).await?; + (tx, from) + }; let hash = tx .compute_sponsor_hash(from) .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?; @@ -216,6 +229,10 @@ impl SendTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); // Launch browser signer if `--browser` flag is set @@ -245,11 +262,18 @@ impl SendTxArgs { } } - let (tx, _) = builder.build(config.sender).await?; + let (mut tx_request, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, config.sender).await?; + } cast_send( provider, - tx, + tx_request, send_tx.cast_async, send_tx.sync, send_tx.confirmations, @@ -261,6 +285,10 @@ impl SendTxArgs { } else if let Some(browser) = browser { let chain = builder.chain(); let (mut tx_request, _) = builder.build(browser.address()).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which // costs more gas for signature verification on Tempo chains. Add a @@ -270,6 +298,9 @@ impl SendTxArgs { { tx_request.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, browser.address()).await?; + } let tx_hash = browser.send_transaction_via_browser(tx_request).await?; @@ -283,7 +314,14 @@ impl SendTxArgs { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; - let (tx_request, _) = builder.build(ak.wallet_address).await?; + let (mut tx_request, _) = builder.build_with_access_key(ak.wallet_address, &ak).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, ak.wallet_address).await?; + } cast_send_with_access_key( &provider, tx_request, @@ -308,11 +346,13 @@ impl SendTxArgs { tx::validate_from_address(send_tx.eth.wallet.from, from)?; let (mut tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; - // Apply sponsor signature after gas estimation so the estimate is - // consistent with what `--tempo.print-sponsor-hash` computes. - if let Some(sig) = sponsor_signature { - tx_request.set_fee_payer_signature(sig); + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, from).await?; } let wallet = EthereumWallet::from(signer); diff --git a/crates/cast/src/cmd/tempo.rs b/crates/cast/src/cmd/tempo.rs new file mode 100644 index 0000000000000..6744e6b73c039 --- /dev/null +++ b/crates/cast/src/cmd/tempo.rs @@ -0,0 +1,45 @@ +use clap::Parser; +use eyre::Result; +use foundry_common::tempo::{EnsureAccessKeyConfig, ensure_access_key}; + +/// Tempo wallet integration commands. +#[derive(Debug, Parser)] +pub enum TempoSubcommand { + /// Authorize a new access key against your Tempo wallet via wallet.tempo. + /// + /// Persists the key to `$TEMPO_HOME/wallet/keys.toml` (default + /// `~/.tempo/wallet/keys.toml`). Also runs automatically on a 402 from a + /// Tempo RPC when no local key is configured. + /// + /// Env: `TEMPO_HOME`, `TEMPO_CLI_AUTH_URL` (override auth service). + Login { + /// Chain ID to authorize the key for. Defaults to Tempo mainnet (4217). + #[arg(long, default_value_t = 4217)] + chain_id: u64, + + /// Print the authorization URL to stderr instead of opening a browser. + #[arg(long)] + no_browser: bool, + }, +} + +impl TempoSubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::Login { chain_id, no_browser } => { + let mut cfg = EnsureAccessKeyConfig::from_env(chain_id); + if no_browser { + cfg.no_browser = true; + } + let outcome = ensure_access_key(cfg).await?; + let _ = foundry_common::sh_println!( + "Authorized key {} for wallet {} on chain {}", + outcome.key_address, + outcome.wallet_address, + outcome.chain_id, + ); + Ok(()) + } + } + } +} diff --git a/crates/cast/src/cmd/tip20/mine.rs b/crates/cast/src/cmd/tip20/mine.rs index a5f9062482a01..0367450a19b06 100644 --- a/crates/cast/src/cmd/tip20/mine.rs +++ b/crates/cast/src/cmd/tip20/mine.rs @@ -20,11 +20,11 @@ use tempo_primitives::{MasterId, TempoAddressExt, UserTag}; const POW_BYTES: usize = 4; -pub(super) struct Output { - pub(super) salt: B256, - pub(super) registration_hash: B256, - pub(super) master_id: MasterId, - pub(super) zero_tag_virtual_address: Address, +pub(crate) struct Output { + pub(crate) salt: B256, + pub(crate) registration_hash: B256, + pub(crate) master_id: MasterId, + pub(crate) zero_tag_virtual_address: Address, } pub(super) fn run( @@ -127,7 +127,12 @@ pub(super) async fn register( Ok(()) } -fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Result { +pub(crate) fn mine( + master: Address, + salt: B256, + n_threads: usize, + pow_bytes: usize, +) -> Result { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); @@ -144,7 +149,7 @@ fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Resu .ok_or_else(|| eyre::eyre!("virtual master mining failed: all threads panicked")) } -fn derive(master: Address, salt: B256) -> Output { +pub(crate) fn derive(master: Address, salt: B256) -> Output { let registration_hash = registration_hash(master, salt); let master_id = MasterId::from_slice(®istration_hash[4..8]); let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); @@ -152,14 +157,14 @@ fn derive(master: Address, salt: B256) -> Output { Output { salt, registration_hash, master_id, zero_tag_virtual_address } } -fn registration_hash(master: Address, salt: B256) -> B256 { +pub(crate) fn registration_hash(master: Address, salt: B256) -> B256 { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); packed[20..].copy_from_slice(salt.as_slice()); keccak256(packed) } -fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { +pub(crate) fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { registration_hash[..pow_bytes].iter().all(|byte| *byte == 0) } diff --git a/crates/cast/src/cmd/tip20/mod.rs b/crates/cast/src/cmd/tip20/mod.rs index e3c39b2c9bb18..edb4c3b7a57b3 100644 --- a/crates/cast/src/cmd/tip20/mod.rs +++ b/crates/cast/src/cmd/tip20/mod.rs @@ -6,7 +6,7 @@ use std::str::FromStr; mod create; pub(crate) use create::iso4217_warning_message; -mod mine; +pub(crate) mod mine; /// TIP-20 token operations (Tempo). #[derive(Debug, Parser, Clone)] diff --git a/crates/cast/src/cmd/vaddr/create.rs b/crates/cast/src/cmd/vaddr/create.rs new file mode 100644 index 0000000000000..0563b09601323 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/create.rs @@ -0,0 +1,181 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + tip20::mine, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::{provider::ProviderBuilder, shell}; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use serde_json::json; +use std::time::Instant; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + owner: Address, + salt: Option, + tag: u64, + count: u32, + threads: Option, + seed: Option, + no_random: bool, + no_register: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + if count == 0 { + // no virtual addresses to compute + return Ok(()); + } + + if !owner.is_valid_master() { + eyre::bail!( + "invalid owner address {owner}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + let output = if let Some(salt) = salt { + let output = mine::derive(owner, salt); + if !mine::has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + output + } else { + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut start_salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut start_salt[..]); + } + + if !shell::is_json() { + sh_println!("Mining TIP-1022 salt for {owner} with {n_threads} threads...")?; + } + let timer = Instant::now(); + let output = mine::mine(owner, start_salt, n_threads, POW_BYTES)?; + if !shell::is_json() { + sh_println!("Found salt in {:?}", timer.elapsed())?; + } + output + }; + + const MAX_USER_TAG: u64 = 0x0000_FFFF_FFFF_FFFF; + let mut virtual_addresses = Vec::with_capacity(count as usize); + for i in 0..count { + let tag_value = tag + .checked_add(i as u64) + .filter(|&t| t <= MAX_USER_TAG) + .ok_or_else(|| eyre::eyre!("tag overflow: tag + count exceeds the 6-byte user tag range (max {MAX_USER_TAG:#x})"))?; + let raw = tag_value.to_be_bytes(); + let user_tag = UserTag::new(raw[2..].try_into().expect("slice is 6 bytes")); + let vaddr = Address::new_virtual(output.master_id, user_tag); + virtual_addresses.push((user_tag, vaddr)); + } + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "salt": format!("{}", output.salt), + "registration_hash": format!("{}", output.registration_hash), + "master_id": format!("{}", output.master_id), + "virtual_addresses": virtual_addresses.iter().map(|(tag, addr)| json!({ + "tag": format!("{tag}"), + "address": format!("{addr}"), + })).collect::>(), + }))? + )?; + } else { + sh_println!( + "Salt: {} +Registration hash: {} +Master ID: {}", + output.salt, + output.registration_hash, + output.master_id, + )?; + sh_println!("\nVirtual addresses:")?; + for (tag, vaddr) in &virtual_addresses { + sh_println!(" tag={tag} {vaddr}")?; + } + } + + if no_register { + return Ok(()); + } + + register(owner, output.salt, send_tx, tx_opts).await +} + +async fn register( + owner: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!("cast vaddr create requires a signer (for example --private-key or --from)") + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != owner { + eyre::bail!( + "signer mismatch: salt is for {owner}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt})...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/mod.rs b/crates/cast/src/cmd/vaddr/mod.rs new file mode 100644 index 0000000000000..446d1e7ece5e2 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/mod.rs @@ -0,0 +1,131 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use foundry_cli::opts::RpcOpts; + +mod create; +mod resolve; +mod watch; + +/// TIP-1022 virtual address registry operations (Tempo). +/// +/// Virtual addresses are deterministic 20-byte aliases (masterId || VIRTUAL_MAGIC || userTag) +/// that auto-forward TIP-20 deposits to a registered master wallet at the protocol level, +/// with no on-chain sweep transaction required. +/// +/// See: +#[derive(Debug, Parser, Clone)] +pub enum VaddrSubcommand { + /// Mine a TIP-1022 proof-of-work salt, register as a virtual address master, and print + /// derived virtual addresses for the given owner. + #[command(visible_alias = "c")] + Create { + /// The master (owner) address that will control all virtual addresses under this + /// registration. Must not be the zero address, a virtual address, or a TIP-20 token. + #[arg(long, value_name = "ADDRESS")] + owner: Address, + + /// Use this salt directly instead of mining one. Must satisfy the 32-bit PoW requirement. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Starting user tag for the derived virtual address output (hex-encoded 6 bytes). + #[arg(long, default_value = "0", value_name = "U64")] + tag: u64, + + /// Number of virtual addresses to derive and print. + #[arg(long, default_value = "1", value_name = "N")] + count: u32, + + /// Number of threads to use for mining. Defaults to number of logical cores. + #[arg(long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// Seed for the random number generator used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Start salt search from zero instead of a random value. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Mine and print the salt and derived virtual addresses without submitting the + /// registerVirtualMaster transaction. + #[arg(long)] + no_register: bool, + + #[command(flatten)] + send_tx: Box, + + #[command(flatten)] + tx: Box, + }, + + /// Resolve a virtual address to its registered master and decode its components. + #[command(visible_alias = "r")] + Resolve { + /// The virtual address to resolve. + #[arg(value_name = "ADDRESS")] + addr: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Watch (tail) incoming TIP-20 transfers to a virtual address. + #[command(visible_alias = "w")] + Watch { + /// The virtual address to monitor. + #[arg(value_name = "ADDRESS")] + addr: Address, + + /// Filter on a specific TIP-20 token address. Watches all tokens if omitted. + #[arg(long, value_name = "ADDRESS")] + token: Option
, + + /// Block number to start from. Defaults to the current latest block. + #[arg(long, value_name = "BLOCK")] + from_block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, +} + +impl VaddrSubcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + send_tx, + tx, + } => { + create::run( + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + *send_tx, + *tx, + ) + .await? + } + Self::Resolve { addr, rpc } => resolve::run(addr, rpc).await?, + Self::Watch { addr, token, from_block, rpc } => { + watch::run(addr, token, from_block, rpc).await? + } + } + Ok(()) + } +} diff --git a/crates/cast/src/cmd/vaddr/resolve.rs b/crates/cast/src/cmd/vaddr/resolve.rs new file mode 100644 index 0000000000000..96936f4fe4713 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/resolve.rs @@ -0,0 +1,52 @@ +use alloy_primitives::{Address, hex}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; + +pub(super) async fn run(addr: Address, rpc: RpcOpts) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let registry = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider); + + let decode_builder = registry.decodeVirtualAddress(addr); + let resolve_builder = registry.resolveVirtualAddress(addr); + let (decoded, master) = tokio::try_join!(decode_builder.call(), resolve_builder.call())?; + + if !decoded.isVirtual { + sh_println!("{addr} is not a virtual address")?; + return Ok(()); + } + + let master_id = decoded.masterId; + let user_tag = decoded.userTag; + let master: Address = master; + + if shell::is_json() { + let master_address = if master.is_zero() { None } else { Some(format!("{master}")) }; + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "address": format!("{addr}"), + "master_id": format!("0x{}", hex::encode(master_id)), + "user_tag": format!("0x{}", hex::encode(user_tag)), + "master_address": master_address, + }))? + )?; + } else { + sh_println!("Virtual address: {addr}")?; + sh_println!("Master ID: 0x{}", hex::encode(master_id))?; + sh_println!("User tag: 0x{}", hex::encode(user_tag))?; + if master.is_zero() { + sh_println!("Master address: (unregistered)")?; + } else { + sh_println!("Master address: {master}")?; + } + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/watch.rs b/crates/cast/src/cmd/vaddr/watch.rs new file mode 100644 index 0000000000000..dc159d5e37c8e --- /dev/null +++ b/crates/cast/src/cmd/vaddr/watch.rs @@ -0,0 +1,108 @@ +use alloy_primitives::{Address, B256, keccak256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use std::sync::LazyLock; +use tempo_alloy::TempoNetwork; +use tempo_primitives::TempoAddressExt; + +static TRANSFER_TOPIC: LazyLock = + LazyLock::new(|| keccak256(b"Transfer(address,address,uint256)")); + +pub(super) async fn run( + addr: Address, + token: Option
, + from_block: Option, + rpc: RpcOpts, +) -> Result<()> { + if !addr.is_virtual() { + eyre::bail!("{addr} is not a virtual address"); + } + + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Transfer(address indexed from, address indexed to, uint256 value) + // topic[0] = event sig, topic[1] = from, topic[2] = to + let to_topic: B256 = { + let mut buf = [0u8; 32]; + buf[12..].copy_from_slice(addr.as_slice()); + buf.into() + }; + + let start = from_block.map(BlockNumberOrTag::Number).unwrap_or(BlockNumberOrTag::Latest); + + let mut filter = + Filter::new().event_signature(*TRANSFER_TOPIC).topic2(to_topic).from_block(start); + + if let Some(tok) = token { + filter = filter.address(tok); + } + + if !shell::is_json() { + sh_println!("Watching transfers to {addr}... (Ctrl-C to stop)")?; + } + + // Fetch logs from the requested start block (historical when from_block is set) + let logs = provider.get_logs(&filter).await?; + for log in &logs { + print_transfer_log(log)?; + } + + // Poll for new logs + let mut last_block = provider.get_block_number().await?; + loop { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let current = provider.get_block_number().await?; + if current > last_block { + let poll_filter = filter.clone().from_block(last_block + 1).to_block(current); + let new_logs = provider.get_logs(&poll_filter).await?; + for log in &new_logs { + print_transfer_log(log)?; + } + last_block = current; + } + } +} + +fn print_transfer_log(log: &alloy_rpc_types::Log) -> Result<()> { + let block = log.block_number.unwrap_or(0); + let tx = log.transaction_hash.unwrap_or_default(); + let token = log.address(); + + // Decode topics: topic[1]=from, topic[2]=to + let from = log.topics().get(1).map(|t| { + let mut addr = [0u8; 20]; + addr.copy_from_slice(&t[12..]); + Address::from(addr) + }); + + // Decode amount from data + let amount = if log.data().data.len() >= 32 { + alloy_primitives::U256::from_be_slice(&log.data().data[..32]) + } else { + alloy_primitives::U256::ZERO + }; + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string(&json!({ + "block": block, + "tx": format!("{tx}"), + "token": format!("{token}"), + "from": from.map(|a| format!("{a}")).unwrap_or_default(), + "amount": amount.to_string(), + }))? + )?; + } else { + sh_println!( + "block={block} tx={tx} token={token} from={} amount={amount}", + from.map(|a| a.to_string()).unwrap_or_default(), + )?; + } + Ok(()) +} diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 8e0dd8dd3ed8c..b2378d8bfdc58 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -779,8 +779,7 @@ flag to set your key via: )?; let address = wallet.address(); let success_message = format!( - "`{}` keystore was saved successfully. Address: {:?}", - &account_name, address, + "`{account_name}` keystore was saved successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } @@ -815,7 +814,7 @@ flag to set your key via: format!("Failed to remove keystore file at {}", keystore_path.display()) })?; - let success_message = format!("`{}` keystore was removed successfully.", &name); + let success_message = format!("`{name}` keystore was removed successfully."); sh_println!("{}", success_message.green())?; } Self::PrivateKey { @@ -886,8 +885,7 @@ flag to set your key via: let private_key = B256::from_slice(&wallet.credential().to_bytes()); - let success_message = - format!("{}'s private key is: {}", &account_name, private_key); + let success_message = format!("{account_name}'s private key is: {private_key}"); sh_println!("{}", success_message.green())?; } @@ -945,10 +943,9 @@ flag to set your key via: Some(&account_name), )?; + let address = wallet.address(); let success_message = format!( - "Password for keystore `{}` was changed successfully. Address: {:?}", - &account_name, - wallet.address(), + "Password for keystore `{account_name}` was changed successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ce5572acebc13..2b1b03486bf04 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -40,6 +40,7 @@ use foundry_common::{ use foundry_config::Chain; use foundry_evm::core::bytecode::InstIter; use futures::{FutureExt, StreamExt, future::Either}; +#[cfg(feature = "optimism")] use op_alloy_consensus as _; use rayon::prelude::*; @@ -60,6 +61,7 @@ pub use foundry_evm::*; pub mod args; pub mod cmd; pub mod opts; +pub mod tempo; pub mod base; pub mod call_spec; @@ -246,7 +248,7 @@ impl + Clone + Unpin, N: Network> Cast { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", &al.address.to_checksum(None))); + s.push(format!("- address: {}", al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index effc081e8072a..763eb132ddb5c 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,11 +1,13 @@ +#[cfg(feature = "optimism")] +use crate::cmd::da_estimate::DAEstimateArgs; use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, batch_mktx::BatchMakeTxArgs, batch_send::BatchSendArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, - da_estimate::DAEstimateArgs, erc20::Erc20Subcommand, estimate::EstimateArgs, - find_block::FindBlockArgs, interface::InterfaceArgs, keychain::KeychainSubcommand, - logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, - storage::StorageArgs, tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, + erc20::Erc20Subcommand, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, keychain::KeychainSubcommand, logs::LogsArgs, mktx::MakeTxArgs, + rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, tempo::TempoSubcommand, + tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, vaddr::VaddrSubcommand, wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; @@ -1163,6 +1165,7 @@ pub enum CastSubcommand { command: TxPoolSubcommands, }, /// Estimates the data availability size of a given opstack block. + #[cfg(feature = "optimism")] #[command(name = "da-estimate")] DAEstimate(DAEstimateArgs), @@ -1186,6 +1189,20 @@ pub enum CastSubcommand { #[command(subcommand)] command: KeychainSubcommand, }, + + /// Tempo wallet integration (login, etc.). + Tempo { + #[command(subcommand)] + command: TempoSubcommand, + }, + + /// TIP-1022 virtual address registry operations (Tempo). + #[command(visible_alias = "vaddr")] + VirtualAddress { + #[command(subcommand)] + command: VaddrSubcommand, + }, + #[command(name = "trace")] Trace(TraceArgs), } diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs new file mode 100644 index 0000000000000..737c33f5b70de --- /dev/null +++ b/crates/cast/src/tempo.rs @@ -0,0 +1,3 @@ +//! Tempo transaction helpers used by Cast-facing commands. + +pub use foundry_common::tempo::{TempoSponsor, TempoSponsorPreview, resolve_tempo_sponsor_signer}; diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 96f5fc5137575..b58136f4ae9de 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -20,7 +20,7 @@ use foundry_common::{ get_pretty_receipt_w_reason_attr, shell, }; use foundry_config::{Chain, Config}; -use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner}; +use foundry_wallets::{BrowserWalletOpts, TempoAccessKeyConfig, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration}; @@ -535,13 +535,29 @@ where sender: impl Into>, ) -> Result<(N::TransactionRequest, Option)> { let fill = self.fill; - self._build(sender, fill).await + self._build(sender, fill, None).await + } + + /// Builds a transaction that will be signed by a Tempo access key. + /// + /// The access-key id is set before gas estimation. If the access key needs on-chain + /// provisioning, its authorization is embedded before access-list/gas estimation and before + /// any sponsor digest can be computed. + pub async fn build_with_access_key( + mut self, + sender: impl Into>, + access_key: &TempoAccessKeyConfig, + ) -> Result<(N::TransactionRequest, Option)> { + self.tx.set_key_id(access_key.key_address); + let fill = self.fill; + self._build(sender, fill, Some(access_key)).await } async fn _build( mut self, sender: impl Into>, fill: bool, + access_key: Option<&TempoAccessKeyConfig>, ) -> Result<(N::TransactionRequest, Option)> { // prepare let sender = sender.into(); @@ -555,6 +571,16 @@ where // resolve let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; self.resolve_auth(&sender, tx_nonce).await?; + if let Some(access_key) = access_key { + self.tx + .prepare_access_key_authorization( + &self.provider, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + } self.resolve_access_list().await?; // fill diff --git a/crates/cast/tests/cli/keychain.rs b/crates/cast/tests/cli/keychain.rs new file mode 100644 index 0000000000000..88e9e16983cc5 --- /dev/null +++ b/crates/cast/tests/cli/keychain.rs @@ -0,0 +1,76 @@ +//! CLI tests for `cast keychain` subcommands. + +use anvil::NodeConfig; +use foundry_test_utils::util::OutputExt; + +/// Anvil test accounts (standard mnemonic). +mod accounts { + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + pub const TOKEN: &str = "0x20C000000000000000000000b9537d11c60E8b50"; // PathUSD +} + +// `cast keychain rl --json` must emit `{"remaining":""}`, not a bare string. +casttest!(keychain_rl_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "rl", + accounts::ADDR1, + accounts::ADDR2, + accounts::TOKEN, + "--rpc-url", + &rpc, + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain rl --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + assert!( + parsed.get("remaining").is_some(), + "expected 'remaining' key in JSON output, got: {output}" + ); + // Must not be a bare string (old bug: `"0"`) + assert!(!parsed.is_string(), "JSON output must not be a bare string, got: {output}"); +}); + +// `cast keychain authorize --tempo.print-sponsor-hash --json` must emit +// `{"sponsor_hash":"0x..."}`, not a raw hex string. +casttest!(keychain_authorize_sponsor_hash_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "authorize", + accounts::ADDR2, // key to authorize + "--private-key", + accounts::PK1, + "--rpc-url", + &rpc, + "--tempo.print-sponsor-hash", + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain authorize --tempo.print-sponsor-hash --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + let hash = parsed + .get("sponsor_hash") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("expected 'sponsor_hash' key in JSON output, got: {output}")); + assert!(hash.starts_with("0x"), "sponsor_hash should be 0x-prefixed, got: {hash}"); + assert_eq!(hash.len(), 66, "sponsor_hash should be 32-byte hex (66 chars), got: {hash}"); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index da33a34d849db..2f744efe4d4f0 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,6 +1,7 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_eips::Decodable2718; use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{B256, Bytes, U256, address, b256, hex}; @@ -20,11 +21,13 @@ use foundry_test_utils::{ }; use serde_json::json; use std::{fs, path::Path, str::FromStr}; +use tempo_primitives::TempoTxEnvelope; #[macro_use] extern crate foundry_test_utils; mod erc20; +mod keychain; mod selectors; casttest!(print_short_version, |_prj, cmd| { @@ -2055,6 +2058,55 @@ casttest!(mktx_ethsign, async |_prj, cmd| { ]]); }); +// tests that `cast mktx --tempo.lane ` resolves the lane against a `tempo.lanes.toml` file at +// the project root, sets the corresponding `nonce_key` on the produced Tempo AA transaction. +casttest!(mktx_tempo_lane_resolves_nonce_key, |prj, cmd| { + // Write a shared lanes file at the project root. + let lanes_path = prj.root().join("tempo.lanes.toml"); + fs::write(&lanes_path, "deploy = 1\nops = 2\npayments = 42\n").unwrap(); + + let output = cmd + .current_dir(prj.root()) + .args([ + "mktx", + "--tempo.lane", + "payments", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]) + .assert_success() + .get_output() + .clone(); + + // The resolved-lane breadcrumb is printed to stderr so it doesn't pollute stdout + // (which carries the raw signed transaction). + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("lane: payments (nonce_key=42, nonce=0)"), + "expected lane breadcrumb on stderr, got: {stderr}", + ); + + // Decode the produced signed Tempo AA transaction and verify it carries the + // resolved 2D nonce key. + let stdout = String::from_utf8_lossy(&output.stdout); + let raw_hex = stdout.trim().trim_start_matches("0x"); + let raw = hex::decode(raw_hex).expect("decode hex output"); + let envelope = TempoTxEnvelope::decode_2718(&mut raw.as_slice()).expect("decode tempo tx"); + assert!(envelope.is_aa(), "expected Tempo AA transaction, got: {envelope:?}"); + assert_eq!(envelope.nonce_key(), Some(U256::from(42_u64))); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -4024,6 +4076,7 @@ Warning: Contract code is empty }); // +#[cfg(feature = "optimism")] casttest!(tx_raw_opstack_deposit, |_prj, cmd| { cmd.args([ "tx", @@ -5020,6 +5073,7 @@ casttest!(cast_decode_tx_network_flag_short_and_long_equivalent, |_prj, cmd| { // Test that `--network optimism` and `-n optimism` produce identical output for decode-tx. // Uses a known OP-stack deposit transaction (same tx as tx_raw_opstack_deposit test). +#[cfg(feature = "optimism")] casttest!(cast_decode_tx_network_optimism_short_and_long_equivalent, |_prj, cmd| { let tx = "0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; @@ -5076,3 +5130,68 @@ casttest!(run_evm_version_updates_gas_params, |_prj, cmd| { "expected Spurious Dragon gas (177241), got: {sd_output}" ); }); + +// Tests for `cast vaddr` JSON output +casttest!(vaddr_create_json_output, |_prj, cmd| { + // Use a pre-computed salt that satisfies the 4-byte PoW requirement for this owner. + // Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 + // Owner: 0x1234567890123456789012345678901234567890 + let out = cmd + .args([ + "--json", + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + "--count", + "2", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let v: serde_json::Value = serde_json::from_str(out.trim()).expect("valid JSON"); + assert_eq!(v["salt"], "0x0000000000000000000000000000000000000000000000003ee0a78d00000000"); + assert_eq!( + v["registration_hash"], + "0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396" + ); + assert_eq!(v["master_id"], "0x2f51c0c4"); + let addrs = v["virtual_addresses"].as_array().expect("array"); + assert_eq!(addrs.len(), 2); + assert_eq!(addrs[0]["tag"], "0x000000000000"); + assert_eq!( + addrs[0]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000000" + ); + assert_eq!(addrs[1]["tag"], "0x000000000001"); + assert_eq!( + addrs[1]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000001" + ); +}); + +casttest!(vaddr_create_plain_output, |_prj, cmd| { + cmd.args([ + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + ]) + .assert_success() + .stdout_eq(str![[r#" +Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 +Registration hash: 0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396 +Master ID: 0x2f51c0c4 + +Virtual addresses: + tag=0x000000000000 [..] + +"#]]); +}); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 659fec7f1a333..0eab12331be04 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -68,3 +68,13 @@ tracing.workspace = true walkdir.workspace = true proptest.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", + "forge-script-sequence/optimism", +] diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 94974301df8ac..01de77b9c95fd 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5447,7 +5447,7 @@ { "func": { "id": "expectEmit_0", - "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", @@ -5487,7 +5487,7 @@ { "func": { "id": "expectEmit_2", - "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit() external;", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 7c2e0741704b3..12cfd19017770 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1082,6 +1082,9 @@ interface Vm { /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; @@ -1093,6 +1096,9 @@ interface Vm { /// Prepare an expected log with all topic and data checks enabled. /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit() external; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index b22f76714dd0f..27545c1b6cd33 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -856,42 +856,6 @@ impl Cheatcodes { } } - // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { - let ctx = MockCallDataContext { - calldata: call.input.bytes(ecx), - value: call.transfer_value(), - }; - - if let Some(return_data_queue) = match mocks.get_mut(&ctx) { - Some(queue) => Some(queue), - None => mocks - .iter_mut() - .find(|(mock, _)| { - call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) - && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) - }) - .map(|(_, v)| v), - } && let Some(return_data) = if return_data_queue.len() == 1 { - // If the mocked calls stack has a single element in it, don't empty it - return_data_queue.front().map(|x| x.to_owned()) - } else { - // Else, we pop the front element - return_data_queue.pop_front() - } { - return Some(CallOutcome { - result: InterpreterResult { - result: return_data.ret_type, - output: return_data.data, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - was_precompile_called: true, - precompile_call_logs: vec![], - }); - } - } - // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` @@ -932,6 +896,72 @@ impl Cheatcodes { } } + // Handle mocked calls + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { + let ctx = MockCallDataContext { + calldata: call.input.bytes(ecx), + value: call.transfer_value(), + }; + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) + && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned()) + { + if let Some(value) = call.transfer_value() { + let checkpoint = ecx.journal_mut().checkpoint(); + match ecx.journal_mut().transfer_loaded( + call.transfer_from(), + call.transfer_to(), + value, + ) { + None => { + if return_data.ret_type.is_ok() { + ecx.journal_mut().checkpoint_commit(); + } else { + ecx.journal_mut().checkpoint_revert(checkpoint); + } + } + Some(err) => { + ecx.journal_mut().checkpoint_revert(checkpoint); + return Some(CallOutcome { + result: InterpreterResult { + result: err.into(), + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], + }); + } + } + } + + // If the mocked calls stack has a single element in it, don't empty it + if return_data_queue.len() > 1 { + return_data_queue.pop_front(); + } + + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], + }); + } + } + // Apply EIP-2930 access list self.apply_accesslist(ecx); @@ -1497,6 +1527,21 @@ impl Inspector> for Cheatcode } } + // this will ensure we don't have false positives when trying to diagnose reverts in fork + // mode + let diag = self.fork_revert_diagnostic.take(); + + // If the call already reverted, preserve that primary failure and skip post-call + // expect* validation so it cannot overwrite the original revert. + if outcome.result.is_revert() { + // if there's a revert and a previous call was diagnosed as fork related revert then we + // can return a better error here + if let Some(err) = diag { + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + } + return; + } + // At the end of the call, // we need to check if we've found all the emits. // We know we've found all the expected emits in the right order @@ -1574,19 +1619,6 @@ impl Inspector> for Cheatcode self.expected_emits.clear() } - // this will ensure we don't have false positives when trying to diagnose reverts in fork - // mode - let diag = self.fork_revert_diagnostic.take(); - - // if there's a revert and a previous call was diagnosed as fork related revert then we can - // return a better error here - if outcome.result.is_revert() - && let Some(err) = diag - { - outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return; - } - // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist if let TxKind::Call(test_contract) = ecx.tx().kind() { @@ -1867,10 +1899,23 @@ impl Inspector> for Cheatcode } // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert + if let Some(expected_revert) = &mut self.expected_revert && curr_depth <= expected_revert.depth && matches!(expected_revert.kind, ExpectedRevertKind::Default) { + // Mirror the logic in `call_end`: when an expected reverter address is set + // and we don't yet have one (or we're matching multiple reverts), record the + // would-be deployed address as the reverter. revm guarantees `outcome.address` + // is `Some(_)` whenever the constructor actually ran (including the revert + // case); it is only `None` for pre-frame rejection (depth/balance/nonce), + // for which a reverter address is meaningless. + if outcome.result.is_revert() + && expected_revert.reverter.is_some() + && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + && let Some(addr) = outcome.address + { + expected_revert.reverted_by = Some(addr); + } let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match revert_handlers::handle_expect_revert( false, diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 608f20f0c4e32..12d625768a0c9 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -164,7 +164,7 @@ impl EqRelAssertionError { format_units_uint(&f.left, decimals), format_units_uint(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } @@ -179,7 +179,7 @@ impl EqRelAssertionError { format_units_int(&f.left, decimals), format_units_int(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs index fb722c2814baa..2b8f81518a621 100644 --- a/crates/cheatcodes/src/version.rs +++ b/crates/cheatcodes/src/version.rs @@ -20,7 +20,14 @@ impl Cheatcode for foundryVersionAtLeastCall { } fn foundry_version_cmp(version: &str) -> Result { - version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) + version_cmp(strip_semver_metadata(SEMVER_VERSION), version) +} + +/// Strips pre-release (e.g. `-nightly`, `-dev`) and build metadata +/// (e.g. `+..`) from a version string +/// so we compare on `MAJOR.MINOR.PATCH` only. +fn strip_semver_metadata(version: &str) -> &str { + version.split(['-', '+']).next().unwrap() } fn version_cmp(version_a: &str, version_b: &str) -> Result { @@ -42,3 +49,61 @@ fn parse_version(version: &str) -> Result { } Ok(version) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strips_build_metadata_only() { + // Tagged release: `1.7.1+..` + assert_eq!(strip_semver_metadata("1.7.1+abc1234567.1737036656.release"), "1.7.1"); + } + + #[test] + fn strips_pre_release_and_build_metadata() { + // Nightly: `1.7.1-nightly+..` + assert_eq!(strip_semver_metadata("1.7.1-nightly+abc1234567.1737036656.release"), "1.7.1"); + // Dev: `1.7.1-dev+..` + assert_eq!(strip_semver_metadata("1.7.1-dev+abc1234567.1737036656.debug"), "1.7.1"); + } + + #[test] + fn strips_plain_version() { + assert_eq!(strip_semver_metadata("1.7.1"), "1.7.1"); + } + + #[test] + fn version_cmp_orders_correctly() { + assert_eq!(version_cmp("1.7.1", "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp("1.7.1", "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "1.7.2").unwrap(), Ordering::Less); + assert_eq!(version_cmp("1.7.1", "0.0.1").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "99.0.0").unwrap(), Ordering::Less); + } + + #[test] + fn parse_version_rejects_pre_release_and_build_metadata() { + // User-supplied versions must be plain `MAJOR.MINOR.PATCH`. + assert!(parse_version("1.7.1-nightly").is_err()); + assert!(parse_version("1.7.1+abc").is_err()); + assert!(parse_version("not-a-version").is_err()); + assert!(parse_version("1.7.1").is_ok()); + } + + #[test] + fn cmp_works_against_full_semver_version_strings() { + // Simulate comparing each shape of `SEMVER_VERSION` against a user-supplied version. + for current in [ + "1.7.1+abc1234567.1737036656.release", + "1.7.1-nightly+abc1234567.1737036656.release", + "1.7.1-dev+abc1234567.1737036656.debug", + "1.7.1", + ] { + let stripped = strip_semver_metadata(current); + assert_eq!(version_cmp(stripped, "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp(stripped, "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp(stripped, "1.7.2").unwrap(), Ordering::Less); + } + } +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index a9396b4208886..bb673c9219e10 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -49,7 +49,6 @@ itertools.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true time = { version = "0.3", features = ["formatting"] } yansi.workspace = true tracing.workspace = true @@ -64,8 +63,13 @@ foundry-test-utils.workspace = true rexpect = "0.6" [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index da2c7f4caff02..2ec057b8167c0 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -2,10 +2,7 @@ //! //! This module contains the execution logic for the [SessionSource]. -use crate::{ - prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}, - source::IntermediateOutput, -}; +use crate::prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_json_abi::EventParam; use alloy_primitives::{Address, B256, U256, hex}; @@ -15,7 +12,17 @@ use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, inspectors::CheatsConfig, traces::TraceMode, }; -use solang_parser::pt; +use solar::{ + ast::{BinOpKind, ElementaryType, FunctionKind, LitKind, StateMutability, StrKind, UnOpKind}, + interface::Symbol, + sema::{ + hir::{ + ContractId, Event, Expr, ExprKind, Function, ItemId, Res, StmtKind, Type as HirType, + TypeKind, Visibility, + }, + ty::{Gcx, Ty, TyKind}, + }, +}; use std::ops::ControlFlow; use yansi::Paint; @@ -86,8 +93,10 @@ impl SessionSource { if let Some(err) = err { let output = source_without_inspector.build()?; - let formatted_event = - output.enter(|output| output.get_event(input).map(format_event_definition)); + let formatted_event = output.enter(|output| { + let gcx = output.gcx(); + output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid))) + }); if let Some(formatted_event) = formatted_event { return Ok((ControlFlow::Break(()), Some(formatted_event?))); } @@ -122,30 +131,37 @@ impl SessionSource { // which was wrapped in `abi.encode`. let generated_output = source.build()?; - // If the expression is a variable declaration within the REPL contract, use its type; - // otherwise, attempt to infer the type. - let contract_expr = generated_output - .intermediate - .repl_contract_expressions - .get(input) - .or_else(|| source.infer_inner_expr_type()); + // Inside the compiler closure, infer the DynSolType of the inspected expression and + // determine whether the REPL should continue. + let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> { + let gcx = out.gcx(); - // If the current action is a function call, we get its return type - // otherwise it returns None - let function_call_return_type = - Type::get_function_return_type(contract_expr, &generated_output.intermediate); + // Try direct lookup of `input` as a named variable in the REPL contract. + if let Some(direct_ty) = lookup_named_variable_type(gcx, input) { + return Some((false, direct_ty)); + } - let (contract_expr, ty) = if let Some(function_call_return_type) = function_call_return_type - { - (function_call_return_type.0, function_call_return_type.1) - } else { - match contract_expr.and_then(|e| { - Type::ethabi(e, Some(&generated_output.intermediate)).map(|ty| (e, ty)) - }) { - Some(res) => res, - // this type was denied for inspection, continue - None => return Ok((ControlFlow::Continue(()), None)), + // Otherwise, find the appended `bytes memory inspectoor = abi.encode();` + // and pull out the first call argument. + let block = out.run_func_body(); + let last = block.last()?; + let StmtKind::DeclSingle(vid) = last.kind else { return None }; + let var = gcx.hir.variable(vid); + let init = var.initializer?; + let ExprKind::Call(_callee, args, _) = &init.kind else { return None }; + let inner_expr = args.exprs().next()?; + + // If the call is `func()` returning a single value, prefer the function return type. + if let Some(ty) = get_function_return_type(gcx, inner_expr) { + return Some((should_continue(inner_expr), ty)); } + + let ty = expr_to_dyn(gcx, inner_expr, true)?; + Some((should_continue(inner_expr), ty)) + }); + + let Some((cont, ty)) = res_ty else { + return Ok((ControlFlow::Continue(()), None)); }; // the file compiled correctly, thus the last stack item must be the memory offset of @@ -162,42 +178,10 @@ impl SessionSource { eyre::bail!("Failed to inspect last expression: could not retrieve data from memory") }; let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?; - let c = if should_continue(contract_expr) { - ControlFlow::Continue(()) - } else { - ControlFlow::Break(()) - }; + let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }; Ok((c, Some(format_token(token)))) } - /// Gracefully attempts to extract the type of the expression within the `abi.encode(...)` - /// call inserted by the inspect function. - /// - /// ### Takes - /// - /// A reference to a [SessionSource] - /// - /// ### Returns - /// - /// Optionally, a [Type] - fn infer_inner_expr_type(&self) -> Option<&pt::Expression> { - let out = self.build().ok()?; - let run = out.run_func_body().ok()?.last(); - match run { - Some(pt::Statement::VariableDefinition( - _, - _, - Some(pt::Expression::FunctionCall(_, _, args)), - )) => { - // We can safely unwrap the first expression because this function - // will only be called on a session source that has just had an - // `inspectoor` variable appended to it. - Some(args.first().unwrap()) - } - _ => None, - } - } - async fn build_runner(&mut self, final_pc: usize) -> Result { let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?; @@ -241,6 +225,51 @@ impl SessionSource { } } +/// Looks up `name` as a named variable in the REPL contract (state variables or run() locals) +/// and returns its type as a [`DynSolType`]. +/// +/// Only top-level statements of `run()` are scanned. Variables declared inside nested blocks +/// (`if`, `for`, `while`, `unchecked`, etc.) are not visible here; the caller falls back to +/// the `inspectoor`-based path for those cases. +fn lookup_named_variable_type(gcx: Gcx<'_>, name: &str) -> Option { + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + + // State variables. + for vid in repl.variables() { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + + // Locals declared in run(). + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + for stmt in body.stmts { + match stmt.kind { + StmtKind::DeclSingle(vid) => { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + StmtKind::DeclMulti(vids, _) => { + for vid in vids.iter().flatten() { + let var = hir.variable(*vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item((*vid).into())); + } + } + } + _ => {} + } + } + None +} + /// Formats a value into an inspection message // TODO: Verbosity option fn format_token(token: DynSolValue) -> String { @@ -343,49 +372,37 @@ fn format_token(token: DynSolValue) -> String { } } -/// Formats a [pt::EventDefinition] into an inspection message -/// -/// ### Takes -/// -/// An borrowed [pt::EventDefinition] -/// -/// ### Returns -/// -/// A formatted [pt::EventDefinition] for use in inspection output. +/// Formats an [`Event`] into an inspection message. // TODO: Verbosity option -fn format_event_definition(event_definition: &pt::EventDefinition) -> Result { - let event_name = event_definition.name.as_ref().expect("Event has a name").to_string(); - let inputs = event_definition - .fields +fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result { + let event_name = event.name.as_str().to_string(); + let inputs = event + .parameters .iter() - .map(|param| { - let name = param - .name - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "".to_string()); - let kind = Type::from_expression(¶m.ty) - .and_then(Type::into_builtin) + .map(|&pid| { + let var = gcx.hir.variable(pid); + let name = + var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "".into()); + let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into())) .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?; Ok(EventParam { name, ty: kind.to_string(), components: vec![], - indexed: param.indexed, + indexed: var.indexed, internal_type: None, }) }) .collect::>>()?; - let event = - alloy_json_abi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; + let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous }; Ok(format!( "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}", "event".red(), SolidityHelper::new().highlight(&format!( "{}({})", - &event.name, - &event + event.name, + event .inputs .iter() .map(|param| format!( @@ -395,7 +412,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result>() @@ -411,844 +428,724 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result), - - /// (type, length) - FixedArray(Box, usize), +/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection. +/// +/// `lookup` controls whether user-defined type names are resolved via the HIR. +fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + match &expr.kind { + // Elementary type expression: `uint256`, `address`, etc. + ExprKind::Type(ty) => hir_ty_to_dyn(gcx, ty), + + // `type(T)`: only meaningful as the lhs of a member access. + ExprKind::TypeCall(_) => None, + + // Literals. + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Address(_) => Some(DynSolType::Address), + LitKind::Bool(_) => Some(DynSolType::Bool), + LitKind::Str(kind, _, _) => match kind { + StrKind::Hex => Some(DynSolType::Bytes), + StrKind::Str | StrKind::Unicode => Some(DynSolType::String), + }, + LitKind::Number(_) | LitKind::Rational(_) => Some(DynSolType::Uint(256)), + LitKind::Err(_) => None, + }, + + // Resolved identifier: `foo`. + ExprKind::Ident(reses) => { + let res = reses.first()?; + match *res { + Res::Item(ItemId::Variable(vid)) => { + solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) + } + Res::Item(ItemId::Struct(sid)) => { + // Struct reference used as a constructor produces a tuple of field types. + Some(DynSolType::Tuple( + gcx.struct_field_types(sid) + .iter() + .filter_map(|&t| solar_ty_to_dyn(gcx, t)) + .collect(), + )) + } + // Other items and builtins: handled by enclosing Call/Member expressions. + _ => None, + } + } - /// (type, index) - ArrayIndex(Box, Option), + // Index/access: `arr[i]`, `MyType[]`, `MyType[N]`. + ExprKind::Index(base, idx) => { + let base_ty = expr_to_dyn(gcx, base, lookup)?; + let num = + idx.and_then(|e| parse_number_literal(e)).and_then(|n| usize::try_from(n).ok()); + match &base.kind { + // Type-level indexing builds an array type expression. + ExprKind::Type(_) | ExprKind::TypeCall(_) => { + if let Some(n) = num { + Some(DynSolType::FixedArray(Box::new(base_ty), n)) + } else { + Some(DynSolType::Array(Box::new(base_ty))) + } + } + // Runtime indexing returns the element type. + _ => match base_ty { + DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => Some(*inner), + DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_) => { + Some(DynSolType::FixedBytes(1)) + } + other => Some(other), + }, + } + } - /// (types) - Tuple(Vec>), + // Slice: same type as the base. + ExprKind::Slice(base, _, _) => expr_to_dyn(gcx, base, lookup), - /// (name, params, returns) - Function(Box, Vec>, Vec>), + // Array literal `[a, b, c]`. + ExprKind::Array(values) => values + .first() + .and_then(|e| expr_to_dyn(gcx, e, lookup)) + .map(|ty| DynSolType::FixedArray(Box::new(ty), values.len())), - /// (lhs, rhs) - Access(Box, String), + // Tuple expression `(a, b, c)`. + ExprKind::Tuple(items) => Some(DynSolType::Tuple( + items.iter().filter_map(|opt| opt.and_then(|e| expr_to_dyn(gcx, e, lookup))).collect(), + )), - /// (types) - Custom(Vec), -} + // Member access `lhs.member`. + ExprKind::Member(_, _) => resolve_member(gcx, expr, lookup), -impl Type { - /// Convert a [pt::Expression] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Expression] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_expression(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::Type(_, ty) => Self::from_type(ty), - - pt::Expression::Variable(ident) => Some(Self::Custom(vec![ident.name.clone()])), - - // array - pt::Expression::ArraySubscript(_, expr, num) => { - // if num is Some then this is either an index operation (arr[]) - // or a FixedArray statement (new uint256[]) - Self::from_expression(expr).and_then(|ty| { - let boxed = Box::new(ty); - let num = num.as_deref().and_then(parse_number_literal).and_then(|n| { - usize::try_from(n).ok() - }); - match expr.as_ref() { - // statement - pt::Expression::Type(_, _) => { - if let Some(num) = num { - Some(Self::FixedArray(boxed, num)) - } else { - Some(Self::Array(boxed)) - } - } - // index - pt::Expression::Variable(_) => { - Some(Self::ArrayIndex(boxed, num)) - } - _ => None - } - }) - } - pt::Expression::ArrayLiteral(_, values) => { - values.first().and_then(Self::from_expression).map(|ty| { - Self::FixedArray(Box::new(ty), values.len()) - }) - } + // Function/constructor call. + ExprKind::Call(_, _, _) => resolve_call(gcx, expr, lookup), - // tuple - pt::Expression::List(_, params) => Some(Self::Tuple(map_parameters(params))), + // `new T`: produces a value of type T. + ExprKind::New(ty) => hir_ty_to_dyn(gcx, ty), - // . - pt::Expression::MemberAccess(_, lhs, rhs) => { - Self::from_expression(lhs).map(|lhs| { - Self::Access(Box::new(lhs), rhs.name.clone()) - }) - } + // `payable(addr)`. + ExprKind::Payable(_) => Some(DynSolType::Address), - // - pt::Expression::Parenthesis(_, inner) | // () - pt::Expression::New(_, inner) | // new - pt::Expression::UnaryPlus(_, inner) | // + - // ops - pt::Expression::BitwiseNot(_, inner) | // ~ - pt::Expression::ArraySlice(_, inner, _, _) | // [*start*:*end*] - // assign ops - pt::Expression::PreDecrement(_, inner) | // -- - pt::Expression::PostDecrement(_, inner) | // -- - pt::Expression::PreIncrement(_, inner) | // ++ - pt::Expression::PostIncrement(_, inner) | // ++ - pt::Expression::Assign(_, inner, _) | // = ... - pt::Expression::AssignAdd(_, inner, _) | // += ... - pt::Expression::AssignSubtract(_, inner, _) | // -= ... - pt::Expression::AssignMultiply(_, inner, _) | // *= ... - pt::Expression::AssignDivide(_, inner, _) | // /= ... - pt::Expression::AssignModulo(_, inner, _) | // %= ... - pt::Expression::AssignAnd(_, inner, _) | // &= ... - pt::Expression::AssignOr(_, inner, _) | // |= ... - pt::Expression::AssignXor(_, inner, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, inner, _) | // <<= ... - pt::Expression::AssignShiftRight(_, inner, _) // >>= ... - => Self::from_expression(inner), - - // *condition* ? : - pt::Expression::ConditionalOperator(_, _, if_true, if_false) => { - Self::from_expression(if_true).or_else(|| Self::from_expression(if_false)) - } + // Ternary: prefer truthy branch's type, fall back to else branch. + ExprKind::Ternary(_, t, e) => { + expr_to_dyn(gcx, t, lookup).or_else(|| expr_to_dyn(gcx, e, lookup)) + } - // address - pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), - pt::Expression::HexNumberLiteral(_, s, _) => { - match s.parse::
() { - Ok(addr) if *s == addr.to_checksum(None) => { - Some(Self::Builtin(DynSolType::Address)) + // Delete has no return type. + ExprKind::Delete(_) => None, + + // Unary operations. + ExprKind::Unary(op, inner) => match op.kind { + UnOpKind::Neg => expr_to_dyn(gcx, inner, lookup).map(|ty| match ty { + DynSolType::Uint(n) => DynSolType::Int(n), + DynSolType::Int(n) => DynSolType::Uint(n), + x => x, + }), + UnOpKind::Not => Some(DynSolType::Bool), + UnOpKind::BitNot + | UnOpKind::PreInc + | UnOpKind::PreDec + | UnOpKind::PostInc + | UnOpKind::PostDec => expr_to_dyn(gcx, inner, lookup), + }, + + // Binary operations. + ExprKind::Binary(lhs, op, rhs) => match op.kind { + BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Eq + | BinOpKind::Ne + | BinOpKind::And + | BinOpKind::Or => Some(DynSolType::Bool), + BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul | BinOpKind::Div => { + match (expr_to_dyn(gcx, lhs, false), expr_to_dyn(gcx, rhs, false)) { + (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) + | (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { + Some(DynSolType::Int(256)) } - _ => Some(Self::Builtin(DynSolType::Uint(256))), + _ => Some(DynSolType::Uint(256)), } } + BinOpKind::Rem + | BinOpKind::Pow + | BinOpKind::BitAnd + | BinOpKind::BitOr + | BinOpKind::BitXor + | BinOpKind::Shl + | BinOpKind::Shr + | BinOpKind::Sar => Some(DynSolType::Uint(256)), + }, + + // Assignments: type of the lhs. + ExprKind::Assign(lhs, _, _) => expr_to_dyn(gcx, lhs, lookup), + + ExprKind::Err(_) => None, + } +} - // uint and int - // invert - pt::Expression::Negate(_, inner) => Self::from_expression(inner).map(Self::invert_int), - - // int if either operand is int - // TODO: will need an update for Solidity v0.8.18 user defined operators: - // https://github.com/ethereum/solidity/issues/13718#issuecomment-1341058649 - pt::Expression::Add(_, lhs, rhs) | - pt::Expression::Subtract(_, lhs, rhs) | - pt::Expression::Multiply(_, lhs, rhs) | - pt::Expression::Divide(_, lhs, rhs) => { - match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) | -(Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { - Some(Self::Builtin(DynSolType::Int(256))) - } - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) - } +/// Converts a [`HirType`] to a [`DynSolType`]. +fn hir_ty_to_dyn(gcx: Gcx<'_>, ty: &HirType<'_>) -> Option { + match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + TypeKind::Array(arr) => { + let elem = hir_ty_to_dyn(gcx, &arr.element)?; + if let Some(size) = arr.size { + let n = parse_number_literal(size).and_then(|n| usize::try_from(n).ok()); + if let Some(n) = n { + Some(DynSolType::FixedArray(Box::new(elem), n)) + } else { + Some(DynSolType::Array(Box::new(elem))) } + } else { + Some(DynSolType::Array(Box::new(elem))) } - - // always assume uint - pt::Expression::Modulo(_, _, _) | - pt::Expression::Power(_, _, _) | - pt::Expression::BitwiseOr(_, _, _) | - pt::Expression::BitwiseAnd(_, _, _) | - pt::Expression::BitwiseXor(_, _, _) | - pt::Expression::ShiftRight(_, _, _) | - pt::Expression::ShiftLeft(_, _, _) | - pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(DynSolType::Uint(256))), - - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(_, _, _, _, _) => { - Some(Self::Builtin(DynSolType::Uint(256))) + } + TypeKind::Function(f) => match f.returns.len() { + 0 => None, + 1 => { + let var = gcx.hir.variable(f.returns[0]); + hir_ty_to_dyn(gcx, &var.ty) } + _ => Some(DynSolType::Tuple( + f.returns + .iter() + .filter_map(|&pid| hir_ty_to_dyn(gcx, &gcx.hir.variable(pid).ty)) + .collect(), + )), + }, + TypeKind::Mapping(m) => hir_ty_to_dyn(gcx, &m.value), + TypeKind::Custom(item) => solar_ty_to_dyn(gcx, gcx.type_of_item(*item)), + TypeKind::Err(_) => None, + } +} - // bool - pt::Expression::BoolLiteral(_, _) | - pt::Expression::And(_, _, _) | - pt::Expression::Or(_, _, _) | - pt::Expression::Equal(_, _, _) | - pt::Expression::NotEqual(_, _, _) | - pt::Expression::Less(_, _, _) | - pt::Expression::LessEqual(_, _, _) | - pt::Expression::More(_, _, _) | - pt::Expression::MoreEqual(_, _, _) | - pt::Expression::Not(_, _) => Some(Self::Builtin(DynSolType::Bool)), - - // string - pt::Expression::StringLiteral(_) => Some(Self::Builtin(DynSolType::String)), - - // bytes - pt::Expression::HexLiteral(_) => Some(Self::Builtin(DynSolType::Bytes)), - - // function - pt::Expression::FunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(Self::from_expression).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } - pt::Expression::NamedFunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(|arg| Self::from_expression(&arg.expr)).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } +/// Resolves a member-access expression (`lhs.member`) to its [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Member`. +fn resolve_member(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Member(lhs, ident) = &expr.kind else { return None }; + let member = ident.name; + + // `type(T).member` — type introspection. + if let ExprKind::TypeCall(ty) = &lhs.kind { + return match member.as_str() { + "name" => Some(DynSolType::String), + "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), + "interfaceId" => Some(DynSolType::FixedBytes(4)), + // Only valid for integer types; custom types (enums) fall back to Uint(256). + "min" | "max" => match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + _ => Some(DynSolType::Uint(256)), + }, + _ => None, + }; + } - // explicitly None - pt::Expression::Delete(_, _) | pt::Expression::FunctionCallBlock(_, _, _) => None, - } + // Built-in namespace identifier: `block.timestamp`, `msg.sender`, `abi.encode`, etc. + if let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + && let Some(ty) = builtin_member(b.name().as_str(), member.as_str()) + { + return Some(ty); } - /// Convert a [pt::Type] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Type] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_type(ty: &pt::Type) -> Option { - let ty = match ty { - pt::Type::Address | pt::Type::AddressPayable | pt::Type::Payable => { - Self::Builtin(DynSolType::Address) - } - pt::Type::Bool => Self::Builtin(DynSolType::Bool), - pt::Type::String => Self::Builtin(DynSolType::String), - pt::Type::Int(size) => Self::Builtin(DynSolType::Int(*size as usize)), - pt::Type::Uint(size) => Self::Builtin(DynSolType::Uint(*size as usize)), - pt::Type::Bytes(size) => Self::Builtin(DynSolType::FixedBytes(*size as usize)), - pt::Type::DynamicBytes => Self::Builtin(DynSolType::Bytes), - pt::Type::Mapping { value, .. } => Self::from_expression(value)?, - pt::Type::Function { params, returns, .. } => { - let params = map_parameters(params); - let returns = returns - .as_ref() - .map(|(returns, _)| map_parameters(returns)) - .unwrap_or_default(); - Self::Function( - Box::new(Self::Custom(vec!["__fn_type__".to_string()])), - params, - returns, - ) - } - // TODO: Rational numbers - pt::Type::Rational => return None, + // Elementary type used as a namespace: `address.balance`, `bytes.concat`, etc. + if let ExprKind::Type(ty) = &lhs.kind + && let TypeKind::Elementary(et) = &ty.kind + { + return match et { + ElementaryType::Address(_) => match member.as_str() { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + ElementaryType::Bytes => match member.as_str() { + "concat" => Some(DynSolType::Bytes), + _ => None, + }, + ElementaryType::String => match member.as_str() { + "concat" => Some(DynSolType::String), + _ => None, + }, + _ => None, }; - Some(ty) } - /// Handle special expressions like [global variables](https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables) - /// - /// See: - fn map_special(self) -> Self { - if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { - return self; - } + // Members on a resolved DynSolType (`.length`, `.pop`, `.selector`, `.address`). + if let Some(lhs_ty) = expr_to_dyn(gcx, lhs, lookup) + && let Some(ty) = dyn_member(&lhs_ty, member.as_str()) + { + return Some(ty); + } - let mut types = Vec::with_capacity(5); - let mut args = None; - self.recurse(&mut types, &mut args); + // HIR lookup for user-defined type members. + if lookup && let Some(mut chain) = expr_name_chain(gcx, lhs) { + chain.insert(0, member); + return infer_custom_type(gcx, &mut chain, None).ok().flatten(); + } - let len = types.len(); - if len == 0 { - return self; - } + None +} + +/// Returns the type of `builtin_ns.member` for built-in global namespaces. +fn builtin_member(builtin: &str, member: &str) -> Option { + match builtin { + "block" => match member { + "coinbase" => Some(DynSolType::Address), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | "chainid" + | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), + _ => None, + }, + "msg" => match member { + "sender" => Some(DynSolType::Address), + "gas" | "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), + _ => None, + }, + "tx" => match member { + "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), + _ => None, + }, + "address" => match member { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + _ => None, + } +} + +/// Returns the type of `ty.member` for a known [`DynSolType`]. +fn dyn_member(ty: &DynSolType, member: &str) -> Option { + match member { + "length" => match ty { + DynSolType::Array(_) + | DynSolType::FixedArray(_, _) + | DynSolType::Bytes + | DynSolType::String + | DynSolType::FixedBytes(_) => Some(DynSolType::Uint(256)), + _ => None, + }, + "pop" => match ty { + DynSolType::Array(inner) => Some(*inner.clone()), + _ => None, + }, + // Address members. + "balance" => match ty { + DynSolType::Address => Some(DynSolType::Uint(256)), + _ => None, + }, + "code" => match ty { + DynSolType::Address => Some(DynSolType::Bytes), + _ => None, + }, + "codehash" => match ty { + DynSolType::Address => Some(DynSolType::FixedBytes(32)), + _ => None, + }, + "send" => match ty { + DynSolType::Address => Some(DynSolType::Bool), + _ => None, + }, + // External function members. + "selector" => Some(DynSolType::FixedBytes(4)), + "address" => Some(DynSolType::Address), + _ => None, + } +} - // Type members, like array, bytes etc - #[expect(clippy::single_match)] - #[allow(clippy::collapsible_match)] - match &self { - Self::Access(inner, access) => { - if let Some(ty) = inner.as_ref().clone().try_as_ethabi(None) { - // Array / bytes members - let ty = Self::Builtin(ty); - match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { - return Self::Builtin(DynSolType::Uint(256)); +/// Resolves a call expression to its return [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Call`. +fn resolve_call(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Call(callee, args, _named) = &expr.kind else { return None }; + + // Type cast: `uint256(x)`, `address(y)`, etc. + if let ExprKind::Type(ty) = &callee.kind { + return hir_ty_to_dyn(gcx, ty); + } + + // Member call: `ns.method(...)`. + if let ExprKind::Member(lhs, method) = &callee.kind + && let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + { + match b.name().as_str() { + "abi" => { + return match method.as_str() { + "decode" => { + let last = args.exprs().last()?; + match expr_to_dyn(gcx, last, false)? { + DynSolType::Tuple(tys) => Some(DynSolType::Tuple(tys)), + ty => Some(DynSolType::Tuple(vec![ty])), } - "pop" if ty.is_dynamic_array() => return ty, - _ => {} } - } + s if s.starts_with("encode") => Some(DynSolType::Bytes), + _ => None, + }; } + "string" if method.as_str() == "concat" => return Some(DynSolType::String), + "bytes" if method.as_str() == "concat" => return Some(DynSolType::Bytes), _ => {} } + } - let this = { - let name = types.last().unwrap().as_str(); - match len { - 0 => unreachable!(), - 1 => match name { + // Simple identifier call: built-in global functions and HIR function calls. + if let ExprKind::Ident(reses) = &callee.kind { + match reses.first() { + Some(Res::Builtin(b)) => { + return match b.name().as_str() { "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)), "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)), "ripemd160" => Some(DynSolType::FixedBytes(20)), "ecrecover" => Some(DynSolType::Address), _ => None, - }, - 2 => { - let access = types.first().unwrap().as_str(); - match name { - "block" => match access { - "coinbase" => Some(DynSolType::Address), - "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" - | "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), - _ => None, - }, - "msg" => match access { - "sender" => Some(DynSolType::Address), - "gas" => Some(DynSolType::Uint(256)), - "value" => Some(DynSolType::Uint(256)), - "data" => Some(DynSolType::Bytes), - "sig" => Some(DynSolType::FixedBytes(4)), - _ => None, - }, - "tx" => match access { - "origin" => Some(DynSolType::Address), - "gasprice" => Some(DynSolType::Uint(256)), - _ => None, - }, - "abi" => match access { - "decode" => { - // args = Some([Bytes(_), Tuple(args)]) - // unwrapping is safe because this is first compiled by solc so - // it is guaranteed to be a valid call - let mut args = args.unwrap(); - let last = args.pop().unwrap(); - match last { - Some(ty) => { - return match ty { - Self::Tuple(_) => ty, - ty => Self::Tuple(vec![Some(ty)]), - }; - } - None => None, - } - } - s if s.starts_with("encode") => Some(DynSolType::Bytes), - _ => None, - }, - "address" => match access { - "balance" => Some(DynSolType::Uint(256)), - "code" => Some(DynSolType::Bytes), - "codehash" => Some(DynSolType::FixedBytes(32)), - "send" => Some(DynSolType::Bool), - _ => None, - }, - "type" => match access { - "name" => Some(DynSolType::String), - "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), - "interfaceId" => Some(DynSolType::FixedBytes(4)), - "min" | "max" => Some( - // Either a builtin or an enum - (|| args?.pop()??.into_builtin())() - .unwrap_or(DynSolType::Uint(256)), - ), - _ => None, - }, - "string" => match access { - "concat" => Some(DynSolType::String), - _ => None, - }, - "bytes" => match access { - "concat" => Some(DynSolType::Bytes), - _ => None, - }, - _ => None, - } - } - _ => None, - } - }; - - this.map(Self::Builtin).unwrap_or_else(|| match types.last().unwrap().as_str() { - "this" | "super" => Self::Custom(types), - _ => match self { - Self::Custom(_) | Self::Access(_, _) => Self::Custom(types), - Self::Function(_, _, _) => self, - _ => unreachable!(), - }, - }) - } - - /// Recurses over itself, appending all the idents and function arguments in the order that they - /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { - match self { - Self::Builtin(ty) => types.push(ty.to_string()), - Self::Custom(tys) => types.extend(tys.clone()), - Self::Access(expr, name) => { - types.push(name.clone()); - expr.recurse(types, args); + }; } - Self::Function(fn_name, fn_args, _fn_ret) => { - if args.is_none() && !fn_args.is_empty() { - *args = Some(fn_args.clone()); + Some(Res::Item(ItemId::Function(fid))) if lookup => { + let func = gcx.hir.function(*fid); + if !matches!(func.state_mutability, StateMutability::View | StateMutability::Pure) { + return None; } - fn_name.recurse(types, args); + let ret_id = *func.returns.first()?; + return solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())); } _ => {} } } - /// Infers a custom type's true type by recursing up the parse tree - /// - /// ### Takes - /// - A reference to the [IntermediateOutput] - /// - An array of custom types generated by the `MemberAccess` arm of [Self::from_expression] - /// - An optional contract name. This should always be `None` when this function is first - /// called. - /// - /// ### Returns - /// - /// If successful, an `Ok(Some(DynSolType))` variant. - /// If gracefully failed, an `Ok(None)` variant. - /// If failed, an `Err(e)` variant. - fn infer_custom_type( - intermediate: &IntermediateOutput, - custom_type: &mut Vec, - contract_name: Option, - ) -> Result> { - if let Some("this" | "super") = custom_type.last().map(String::as_str) { - custom_type.pop(); + // Fall back to the callee's resolved type. + expr_to_dyn(gcx, callee, lookup) +} + +/// Extracts a name chain from a member-access expression tree for HIR lookup. +/// +/// The chain is ordered outermost-first so `a.b.c` produces `["c", "b", "a"]` with the root +/// identifier at the back. This matches the convention expected by [`infer_custom_type`]. +fn expr_name_chain(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option> { + match &expr.kind { + ExprKind::Ident(reses) => { + let res = reses.first()?; + let name = match *res { + Res::Item(ItemId::Variable(vid)) => gcx.hir.variable(vid).name?.name, + Res::Item(ItemId::Function(fid)) => gcx.hir.function(fid).name?.name, + Res::Item(ItemId::Contract(cid)) => gcx.hir.contract(cid).name.name, + Res::Builtin(b) => b.name(), + _ => return None, + }; + Some(vec![name]) } - if custom_type.is_empty() { - return Ok(None); + ExprKind::Member(lhs, ident) => { + let mut chain = expr_name_chain(gcx, lhs)?; + chain.insert(0, ident.name); + Some(chain) } + _ => None, + } +} - // If a contract exists with the given name, check its definitions for a match. - // Otherwise look in the `run` - if let Some(contract_name) = contract_name { - let intermediate_contract = intermediate - .intermediate_contracts - .get(&contract_name) - .ok_or_else(|| eyre::eyre!("Could not find intermediate contract!"))?; - - let cur_type = custom_type.last().unwrap(); - if let Some(func) = intermediate_contract.function_definitions.get(cur_type) { - // Check if the custom type is a function pointer member access - if let res @ Some(_) = func_members(func, custom_type) { - return Ok(res); - } - - // Because tuple types cannot be passed to `abi.encode`, we will only be - // receiving functions that have 0 or 1 return parameters here. - if func.returns.is_empty() { - eyre::bail!( - "This call expression does not return any values to inspect. Insert as statement." - ) - } +/// Infers a custom type's true type by recursing through the HIR. +/// +/// `custom_type` is a name chain ordered outermost-first (root at back). This is mutated during +/// resolution. `contract_id` narrows the search to a specific contract scope. +fn infer_custom_type( + gcx: Gcx<'_>, + custom_type: &mut Vec, + contract_id: Option, +) -> Result> { + if let Some(last) = custom_type.last() + && (last.as_str() == "this" || last.as_str() == "super") + { + custom_type.pop(); + } + if custom_type.is_empty() { + return Ok(None); + } - // Empty return types check is done above - let (_, param) = func.returns.first().unwrap(); - // Return type should always be present - let return_ty = ¶m.as_ref().unwrap().ty; - - // If the return type is a variable (not a type expression), re-enter the recursion - // on the same contract for a variable / struct search. It could be a contract, - // struct, array, etc. - if let pt::Expression::Variable(ident) = return_ty { - custom_type.push(ident.name.clone()); - return Self::infer_custom_type(intermediate, custom_type, Some(contract_name)); - } + if let Some(cid) = contract_id { + let hir = &gcx.hir; + let contract = hir.contract(cid); - // Check if our final function call alters the state. If it does, we bail so that it - // will be inserted normally without inspecting. If the state mutability was not - // expressly set, the function is inferred to alter state. - if let Some(pt::FunctionAttribute::Mutability(_mut)) = func - .attributes - .iter() - .find(|attr| matches!(attr, pt::FunctionAttribute::Mutability(_))) - { - if let pt::Mutability::Payable(_) = _mut { - eyre::bail!("This function mutates state. Insert as a statement.") - } - } else { - eyre::bail!("This function mutates state. Insert as a statement.") - } + let cur_name = *custom_type.last().unwrap(); + let cur = cur_name.as_str(); - Ok(Self::ethabi(return_ty, Some(intermediate))) - } else if let Some(var) = intermediate_contract.variable_definitions.get(cur_type) { - Self::infer_var_expr(&var.ty, Some(intermediate), custom_type) - } else if let Some(strukt) = intermediate_contract.struct_definitions.get(cur_type) { - let inner_types = strukt - .fields - .iter() - .map(|var| { - Self::ethabi(&var.ty, Some(intermediate)) - .ok_or_else(|| eyre::eyre!("Struct `{cur_type}` has invalid fields")) - }) - .collect::>>()?; - Ok(Some(DynSolType::Tuple(inner_types))) - } else { - eyre::bail!( - "Could not find any definition in contract \"{contract_name}\" for type: {custom_type:?}" - ) - } - } else { - // Check if the custom type is a variable or function within the REPL contract before - // anything. If it is, we can stop here. - if let Ok(res) = Self::infer_custom_type(intermediate, custom_type, Some("REPL".into())) - { + // Function? + if let Some(fid) = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + let func = hir.function(fid); + if let res @ Some(_) = func_members(func, custom_type) { return Ok(res); } - // Check if the first element of the custom type is a known contract. If it is, begin - // our recursion on that contract's definitions. - let name = custom_type.last().unwrap(); - let contract = intermediate.intermediate_contracts.get(name); - if contract.is_some() { - let contract_name = custom_type.pop(); - return Self::infer_custom_type(intermediate, custom_type, contract_name); + if func.returns.is_empty() { + eyre::bail!( + "This call expression does not return any values to inspect. Insert as statement." + ) } - // See [`Type::infer_var_expr`] - let name = custom_type.last().unwrap(); - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - return Self::infer_var_expr(expr, Some(intermediate), custom_type); + let sm = func.state_mutability; + if !matches!(sm, StateMutability::View | StateMutability::Pure) { + eyre::bail!("This function mutates state. Insert as a statement.") } - // The first element of our custom type was neither a variable or a function within the - // REPL contract, move on to globally available types gracefully. - Ok(None) + let ret_id = func.returns[0]; + let ret_var = hir.variable(ret_id); + return Ok(solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) + .or_else(|| hir_ty_to_dyn(gcx, &ret_var.ty))); } - } - /// Infers the type from a variable's type - fn infer_var_expr( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - custom_type: &mut Vec, - ) -> Result> { - // Resolve local (in `run` function) or global (in the `REPL` or other contract) variable - let res = match &expr { - // Custom variable handling - pt::Expression::Variable(ident) => { - let name = &ident.name; - - if let Some(intermediate) = intermediate { - // expression in `run` - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - Self::infer_var_expr(expr, Some(intermediate), custom_type) - } else if intermediate.intermediate_contracts.contains_key(name) { - if custom_type.len() > 1 { - // There is still some recursing left to do: jump into the contract. - custom_type.pop(); - Self::infer_custom_type(intermediate, custom_type, Some(name.clone())) - } else { - // We have no types left to recurse: return the address of the contract. - Ok(Some(DynSolType::Address)) - } - } else { - Err(eyre::eyre!("Could not infer variable type")) - } - } else { - Ok(None) - } - } - other_expr => Ok(Self::ethabi(other_expr, intermediate)), - }; - // re-run everything with the resolved variable in case we're accessing a builtin member - // for example array or bytes length etc - match res { - Ok(Some(ty)) => { - let box_ty = Box::new(Self::Builtin(ty.clone())); - let access = Self::Access(box_ty, custom_type.drain(..).next().unwrap_or_default()); - if let Some(mapped) = access.map_special().try_as_ethabi(intermediate) { - Ok(Some(mapped)) - } else { - Ok(Some(ty)) + // Variable? + if let Some(vid) = contract + .variables() + .find(|&v| hir.variable(v).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + if let Some(ty) = solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) { + custom_type.pop(); + if custom_type.is_empty() { + return Ok(Some(ty)); } + let next_member = custom_type.drain(..).next().unwrap_or(Symbol::DUMMY); + return Ok(dyn_member(&ty, next_member.as_str()).or(Some(ty))); } - res => res, - } - } - - /// Attempt to convert this type into a [DynSolType] - /// - /// ### Takes - /// An immutable reference to an [IntermediateOutput] - /// - /// ### Returns - /// Optionally, a [DynSolType] - fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - Self::Tuple(types) => Some(DynSolType::Tuple(types_to_parameters(types, intermediate))), - Self::Array(inner) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::Array(Box::new(inner))), - }, - Self::FixedArray(inner, size) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::FixedArray(Box::new(inner), size)), - }, - ty @ Self::ArrayIndex(_, _) => ty.into_array_index(intermediate), - Self::Function(ty, _, _) => ty.try_as_ethabi(intermediate), - // should have been mapped to `Custom` in previous steps - Self::Access(_, _) => None, - Self::Custom(mut types) => { - // Cover any local non-state-modifying function call expressions - intermediate.and_then(|intermediate| { - Self::infer_custom_type(intermediate, &mut types, None).ok().flatten() - }) - } + let var = hir.variable(vid); + return infer_var_ty(gcx, &var.ty, custom_type); } - } - - /// Equivalent to `Type::from_expression` + `Type::map_special` + `Type::try_as_ethabi` - fn ethabi( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - ) -> Option { - Self::from_expression(expr) - .map(Self::map_special) - .and_then(|ty| ty.try_as_ethabi(intermediate)) - } - /// Get the return type of a function call expression. - fn get_function_return_type<'a>( - contract_expr: Option<&'a pt::Expression>, - intermediate: &IntermediateOutput, - ) -> Option<(&'a pt::Expression, DynSolType)> { - let function_call = match contract_expr? { - pt::Expression::FunctionCall(_, function_call, _) => function_call, - _ => return None, - }; - let (contract_name, function_name) = match function_call.as_ref() { - pt::Expression::MemberAccess(_, contract_name, function_name) => { - (contract_name, function_name) + // Struct? + if let Some(sid) = contract.items.iter().find_map(|i| { + if let ItemId::Struct(sid) = i + && hir.strukt(*sid).name.as_str() == cur + { + Some(*sid) + } else { + None } - _ => return None, - }; - let contract_name = match contract_name.as_ref() { - pt::Expression::Variable(contract_name) => contract_name.to_owned(), - _ => return None, - }; - - let pt::Expression::Variable(contract_name) = - intermediate.repl_contract_expressions.get(&contract_name.name)? - else { - return None; - }; - - let contract = intermediate - .intermediate_contracts - .get(&contract_name.name)? - .function_definitions - .get(&function_name.name)?; - let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) - } - - /// Inverts Int to Uint and vice-versa. - fn invert_int(self) -> Self { - match self { - Self::Builtin(DynSolType::Uint(n)) => Self::Builtin(DynSolType::Int(n)), - Self::Builtin(DynSolType::Int(n)) => Self::Builtin(DynSolType::Uint(n)), - x => x, + }) { + let inner = gcx + .struct_field_types(sid) + .iter() + .map(|&t| { + solar_ty_to_dyn(gcx, t) + .ok_or_else(|| eyre::eyre!("Struct `{cur}` has invalid fields")) + }) + .collect::>>()?; + return Ok(Some(DynSolType::Tuple(inner))); } - } - /// Returns the `DynSolType` contained by `Type::Builtin` - #[inline] - fn into_builtin(self) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - _ => None, - } + eyre::bail!( + "Could not find any definition in contract \"{}\" for type: {custom_type:?}", + contract.name.as_str() + ) } - /// Returns the resulting `DynSolType` of indexing self - fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { - match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner) | DynSolType::FixedArray(inner, _)) => { - Some(*inner) - } - Some(DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_)) => { - Some(DynSolType::FixedBytes(1)) - } - ty => ty, - } - } - _ => None, - } + let repl_id = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == "REPL").then_some(cid)); + if let Some(repl_id) = repl_id + && let Ok(res) = infer_custom_type(gcx, custom_type, Some(repl_id)) + { + return Ok(res); } - /// Returns whether this type is dynamic - #[inline] - const fn is_dynamic(&self) -> bool { - match self { - // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are - // not dynamic, nor are tuples of non-dynamic types. - Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, - Self::Array(_) => true, - _ => false, - } + let last_name = *custom_type.last().unwrap(); + let last = last_name.as_str(); + let contract_match = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == last).then_some(cid)); + if let Some(cid) = contract_match { + custom_type.pop(); + return infer_custom_type(gcx, custom_type, Some(cid)); } - /// Returns whether this type is an array - #[inline] - const fn is_array(&self) -> bool { - matches!( - self, - Self::Array(_) - | Self::FixedArray(_, _) - | Self::Builtin(DynSolType::Array(_) | DynSolType::FixedArray(_, _)) - ) - } + Ok(None) +} - /// Returns whether this type is a dynamic array (can call push, pop) - #[inline] - const fn is_dynamic_array(&self) -> bool { - matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) +/// Infers the type from a variable's HIR type, optionally accessing a named member. +fn infer_var_ty( + gcx: Gcx<'_>, + ty: &HirType<'_>, + custom_type: &mut Vec, +) -> Result> { + let Some(ty) = hir_ty_to_dyn(gcx, ty) else { return Ok(None) }; + let next_member = custom_type.drain(..).next(); + if let Some(m) = next_member { + Ok(dyn_member(&ty, m.as_str()).or(Some(ty))) + } else { + Ok(Some(ty)) } +} - const fn is_fixed_bytes(&self) -> bool { - matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) - } +/// Get the return type of a contract method call `receiver.method()`. +fn get_function_return_type(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option { + let ExprKind::Call(callee, _, _) = &expr.kind else { return None }; + let ExprKind::Member(obj, fn_ident) = &callee.kind else { return None }; + let ExprKind::Ident(reses) = &obj.kind else { return None }; + let res = reses.first()?; + let var_id = match res { + Res::Item(ItemId::Variable(vid)) => *vid, + _ => return None, + }; + let var_ty = gcx.type_of_item(var_id.into()).peel_refs(); + let cid = match var_ty.kind { + TyKind::Contract(cid) => cid, + _ => return None, + }; + + let hir = &gcx.hir; + let contract = hir.contract(cid); + let fid = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some(fn_ident.as_str()))?; + let func = hir.function(fid); + let ret_id = *func.returns.first()?; + solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) } -/// Returns Some if the custom type is a function member access +/// Returns Some if the custom type is a function member access. /// /// Ref: #[inline] -fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { - if !matches!(func.ty, pt::FunctionTy::Function) { +fn func_members(func: &Function<'_>, custom_type: &[Symbol]) -> Option { + if !matches!(func.kind, FunctionKind::Function) { return None; } - - let vis = func.attributes.iter().find_map(|attr| match attr { - pt::FunctionAttribute::Visibility(vis) => Some(vis), - _ => None, - }); - match vis { - Some(pt::Visibility::External(_) | pt::Visibility::Public(_)) => { - match custom_type.first().unwrap().as_str() { - "address" => Some(DynSolType::Address), - "selector" => Some(DynSolType::FixedBytes(4)), - _ => None, - } - } + if !matches!(func.visibility, Visibility::External | Visibility::Public) { + return None; + } + match custom_type.first().unwrap().as_str() { + "address" => Some(DynSolType::Address), + "selector" => Some(DynSolType::FixedBytes(4)), _ => None, } } -/// Whether execution should continue after inspecting this expression +/// Whether execution should continue after inspecting this expression. #[inline] -fn should_continue(expr: &pt::Expression) -> bool { - match expr { - // assignments - pt::Expression::PreDecrement(_, _) | // -- - pt::Expression::PostDecrement(_, _) | // -- - pt::Expression::PreIncrement(_, _) | // ++ - pt::Expression::PostIncrement(_, _) | // ++ - pt::Expression::Assign(_, _, _) | // = ... - pt::Expression::AssignAdd(_, _, _) | // += ... - pt::Expression::AssignSubtract(_, _, _) | // -= ... - pt::Expression::AssignMultiply(_, _, _) | // *= ... - pt::Expression::AssignDivide(_, _, _) | // /= ... - pt::Expression::AssignModulo(_, _, _) | // %= ... - pt::Expression::AssignAnd(_, _, _) | // &= ... - pt::Expression::AssignOr(_, _, _) | // |= ... - pt::Expression::AssignXor(_, _, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, _, _) | // <<= ... - pt::Expression::AssignShiftRight(_, _, _) // >>= ... - => { - true - } - +fn should_continue(expr: &Expr<'_>) -> bool { + match &expr.kind { + // assignments and compound assignments + ExprKind::Assign(_, _, _) => true, + // ++/-- pre/post operations + ExprKind::Unary(op, _) => matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ), // Array.pop() - pt::Expression::FunctionCall(_, lhs, _) => { - match lhs.as_ref() { - pt::Expression::MemberAccess(_, _inner, access) => access.name == "pop", - _ => false - } - } - - _ => false + ExprKind::Call(callee, _, _) => match &callee.kind { + ExprKind::Member(_, ident) => ident.as_str() == "pop", + _ => false, + }, + _ => false, } } -fn map_parameters(params: &[(pt::Loc, Option)]) -> Vec> { - params - .iter() - .map(|(_, param)| param.as_ref().and_then(|param| Type::from_expression(¶m.ty))) - .collect() +/// Parses an [`Expr`] number/hex literal into a `U256`. Returns `None` if the expression +/// is not a numeric literal. +/// +/// SubDenominations are already applied to numeric literals in solar's HIR. +const fn parse_number_literal(expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Number(n) => Some(*n), + _ => None, + }, + _ => None, + } } -fn types_to_parameters( - types: Vec>, - intermediate: Option<&IntermediateOutput>, -) -> Vec { - types.into_iter().filter_map(|ty| ty.and_then(|ty| ty.try_as_ethabi(intermediate))).collect() +/// Maps a solar [`ElementaryType`] to a [`DynSolType`]. +const fn elementary_to_dyn(et: ElementaryType) -> Option { + Some(match et { + ElementaryType::Address(_) => DynSolType::Address, + ElementaryType::Bool => DynSolType::Bool, + ElementaryType::String => DynSolType::String, + ElementaryType::Bytes => DynSolType::Bytes, + ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize), + ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize), + ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize), + // Fixed-point numbers are not yet representable as DynSolType. + ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None, + }) } -fn parse_number_literal(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::NumberLiteral(_, num, exp, unit) => { - let num = num.parse::().unwrap_or(U256::ZERO); - let exp = exp.parse().unwrap_or(0u32); - if exp > 77 { - None +/// Maps a solar [`Ty`] to a [`DynSolType`]. +fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option { + match ty.kind { + TyKind::Elementary(et) => elementary_to_dyn(et), + TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Array(elem, n) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + let size: usize = n.try_into().ok()?; + Some(DynSolType::FixedArray(Box::new(inner), size)) + } + TyKind::DynArray(elem) | TyKind::Slice(elem) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + Some(DynSolType::Array(Box::new(inner))) + } + TyKind::Tuple(tys) => { + Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect())) + } + TyKind::Mapping(_, _) => None, + TyKind::Struct(sid) => Some(DynSolType::Tuple( + gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + TyKind::Enum(_) => Some(DynSolType::Uint(8)), + TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Contract(_) => Some(DynSolType::Address), + // For a function-pointer type we return the ABI type of what the call *produces*, not a + // representation of the pointer itself. This is intentional: chisel inspects values, so + // the interesting type is the returned value. A zero-return function pointer has no + // inspectable value, so we return `None`. + TyKind::FnPtr(f) => match f.returns.len() { + 0 => None, + 1 => solar_ty_to_dyn(gcx, f.returns[0]), + _ => Some(DynSolType::Tuple( + f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + }, + TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::IntLiteral(neg, size) => { + let bits = (size.bits() as usize).max(8); + // Round up to the nearest multiple of 8 bits, capped at 256. + let bits = bits.div_ceil(8) * 8; + let bits = bits.min(256); + if neg { + Some(DynSolType::Int(bits.max(8))) } else { - let exp = U256::from(10usize.pow(exp)); - let unit_mul = unit_multiplier(unit).ok()?; - Some(num * exp * unit_mul) + Some(DynSolType::Uint(bits.max(8))) } } - pt::Expression::HexNumberLiteral(_, num, unit) => { - let unit_mul = unit_multiplier(unit).ok()?; - num.parse::().map(|num| num * unit_mul).ok() + TyKind::StringLiteral(valid_utf8, _) => { + if valid_utf8 { + Some(DynSolType::String) + } else { + Some(DynSolType::Bytes) + } } - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(..) => None, + TyKind::Module(_) + | TyKind::BuiltinModule(_) + | TyKind::Error(_, _) + | TyKind::Event(_, _) + | TyKind::Err(_) => None, _ => None, } } -#[inline] -fn unit_multiplier(unit: &Option) -> Result { - if let Some(unit) = unit { - let mul = match unit.name.as_str() { - "seconds" => 1, - "minutes" => 60, - "hours" => 60 * 60, - "days" => 60 * 60 * 24, - "weeks" => 60 * 60 * 24 * 7, - "wei" => 1, - "gwei" => 10_usize.pow(9), - "ether" => 10_usize.pow(18), - other => eyre::bail!("unknown unit: {other}"), - }; - Ok(U256::from(mul)) - } else { - Ok(U256::from(1)) - } -} - #[cfg(test)] mod tests { use super::*; use foundry_compilers::{error::SolcError, solc::Solc}; + use solar::sema::Compiler; use std::sync::Mutex; #[test] @@ -1558,46 +1455,66 @@ mod tests { DynSolType::FixedArray(Box::new(ty), len) } - fn parse(s: &mut SessionSource, input: &str, clear: bool) -> IntermediateOutput { + /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without + /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in + /// the run() body. + /// + /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so that + /// inputs which are syntactically valid but semantically rejected by solc (e.g. + /// `abi.decode(bytes, (uint8[13]))` or `a[0:3]` on a memory array) can still exercise the + /// HIR-based type-inference engine. + fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { if clear { s.clear(); } + // Always declare a sample enum so `Enum1` is available for `type(Enum1)` tests. *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; let input = format!("{};", input.trim_end().trim_end_matches(';')); - let (mut _s, _) = s.clone_with_new_line(input).unwrap(); - *s = _s.clone(); - let s = &mut _s; - - if let Err(e) = s.parse() { - let source = s.to_repl_source(); - panic!("{e}\n\ncould not parse input:\n{source}") - } - s.generate_intermediate_output().expect("could not generate intermediate output") - } - - fn expr(stmts: &[pt::Statement]) -> pt::Expression { - match stmts.last().expect("no statements") { - pt::Statement::Expression(_, e) => e.clone(), - s => panic!("Not an expression: {s:?}"), - } - } - - fn get_type( - s: &mut SessionSource, - input: &str, - clear: bool, - ) -> (Option, IntermediateOutput) { - let intermediate = parse(s, input, clear); - let run_func_body = intermediate.run_func_body().expect("no run func body"); - let expr = expr(run_func_body); - (Type::from_expression(&expr).map(Type::map_special), intermediate) - } + let (new_source, _) = s.clone_with_new_line(input).unwrap(); + *s = new_source.clone(); + + let src = new_source.to_repl_source(); + let sess = + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(sess); + + compiler.enter_mut(|c| -> Option { + // Stage 1: parse + lower (mutable access required). + let lowered = { + let mut pcx = c.parse(); + let file = c + .sess() + .source_map() + .new_source_file( + std::path::PathBuf::from(new_source.file_name.clone()), + src.clone(), + ) + .ok()?; + pcx.add_file(file); + pcx.parse(); + matches!(c.lower_asts(), Ok(ControlFlow::Continue(()))) + }; + if !lowered { + return None; + } - fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { - let (ty, intermediate) = get_type(s, input, clear); - ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) + // Stage 2: walk HIR (immutable access). + let gcx = c.gcx(); + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + let last = body.last()?; + let expr = match last.kind { + StmtKind::Expr(e) => e, + _ => return None, + }; + expr_to_dyn(gcx, expr, true) + }) } fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs index 8133de364c1da..90c0bad874622 100644 --- a/crates/chisel/src/source.rs +++ b/crates/chisel/src/source.rs @@ -5,7 +5,6 @@ //! execution helpers. use eyre::Result; -use foundry_common::fs; use foundry_compilers::{ Artifact, ProjectCompileOutput, artifacts::{ConfigurableContractArtifact, Source, Sources}, @@ -16,9 +15,16 @@ use foundry_config::{Config, SolcReq}; use foundry_evm::{backend::Backend, core::bytecode::InstIter, opts::EvmOpts}; use semver::Version; use serde::{Deserialize, Serialize}; -use solang_parser::pt::{self, CodeLocation}; -use solar::interface::diagnostics::EmittedDiagnostics; -use std::{cell::OnceCell, collections::HashMap, fmt, path::PathBuf}; +use solar::{ + ast::{ItemKind, StmtKind as AstStmtKind, yul}, + interface::{Span, diagnostics::EmittedDiagnostics}, + sema::{ + CompilerRef, + hir::{Block, Contract, EventId, ItemId, Stmt, StmtKind as HirStmtKind}, + ty::Gcx, + }, +}; +use std::{cell::OnceCell, fmt}; use walkdir::WalkDir; /// The minimum Solidity version of the `Vm` interface. @@ -30,41 +36,8 @@ static VM_SOURCE: &str = include_str!("../../../testdata/utils/Vm.sol"); /// [`SessionSource`] build output. pub struct GeneratedOutput { output: ProjectCompileOutput, - pub(crate) intermediate: IntermediateOutput, -} - -pub struct GeneratedOutputRef<'a> { - output: &'a ProjectCompileOutput, - // compiler: &'b solar::sema::CompilerRef<'c>, - pub(crate) intermediate: &'a IntermediateOutput, -} - -/// Intermediate output for the compiled [SessionSource] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct IntermediateOutput { - /// All expressions within the REPL contract's run function and top level scope. - pub repl_contract_expressions: HashMap, - /// Intermediate contracts - pub intermediate_contracts: IntermediateContracts, -} - -/// A refined intermediate parse tree for a contract that enables easy lookups -/// of definitions. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct IntermediateContract { - /// All function definitions within the contract - pub function_definitions: HashMap>, - /// All event definitions within the contract - pub event_definitions: HashMap>, - /// All struct definitions within the contract - pub struct_definitions: HashMap>, - /// All variable definitions within the top level scope of the contract - pub variable_definitions: HashMap>, } -/// A defined type for a map of contract names to [IntermediateContract]s -type IntermediateContracts = HashMap; - impl fmt::Debug for GeneratedOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GeneratedOutput").finish_non_exhaustive() @@ -72,158 +45,25 @@ impl fmt::Debug for GeneratedOutput { } impl GeneratedOutput { - pub fn enter(&self, f: impl FnOnce(GeneratedOutputRef<'_>) -> T + Send) -> T { - // TODO(dani): once intermediate is removed - // self.output - // .parser() - // .solc() - // .compiler() - // .enter(|compiler| f(GeneratedOutputRef { output: &self.output, compiler })) - f(GeneratedOutputRef { output: &self.output, intermediate: &self.intermediate }) - } -} - -impl GeneratedOutputRef<'_> { - pub fn repl_contract(&self) -> Option<&ConfigurableContractArtifact> { - self.output.find_first("REPL") - } -} - -impl std::ops::Deref for GeneratedOutput { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - &self.intermediate - } -} -impl std::ops::Deref for GeneratedOutputRef<'_> { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - self.intermediate + /// Enters the solar compiler context, providing access to the HIR and `Gcx`. + pub fn enter( + &self, + f: impl for<'a, 'b, 'gcx> FnOnce(GeneratedOutputRef<'a, 'b, 'gcx>) -> R + Send, + ) -> R { + self.output + .parser() + .solc() + .compiler() + .enter(|c| f(GeneratedOutputRef { output: &self.output, compiler: c })) } } -impl IntermediateOutput { - pub fn get_event(&self, input: &str) -> Option<&pt::EventDefinition> { - self.intermediate_contracts - .get("REPL") - .and_then(|contract| contract.event_definitions.get(input).map(std::ops::Deref::deref)) - } - - pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { - let deployed_bytecode = contract - .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - let deployed_bytecode_bytes = deployed_bytecode - .bytes() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - - let run_func_statements = self.run_func_body()?; - - // Record loc of first yul block return statement (if any). - // This is used to decide which is the final statement within the `run()` method. - // see . - let last_yul_return = run_func_statements.iter().find_map(|statement| { - if let pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } = statement - && let Some(statement) = block.statements.last() - && let pt::YulStatement::FunctionCall(yul_call) = statement - && yul_call.id.name == "return" - { - return Some(statement.loc()); - } - None - }); - - // Find the last statement within the "run()" method and get the program - // counter via the source map. - let Some(final_statement) = run_func_statements.last() else { return Ok(None) }; - - // If the final statement is some type of block (assembly, unchecked, or regular), - // we need to find the final statement within that block. Otherwise, default to - // the source loc of the final statement of the `run()` function's block. - // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. - let mut source_loc = match final_statement { - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(statement) = last_statement { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the asm block. Because we use saturating sub to get the second - // to last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - pt::Statement::Block { loc: _, unchecked: _, statements } => { - if let Some(statement) = statements.last() { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - _ => final_statement.loc(), - }; - - // Consider yul return statement as final statement (if it's loc is lower) . - if let Some(yul_return) = last_yul_return - && yul_return.end() < source_loc.start() - { - source_loc = yul_return; - } - - // Map the source location of the final statement of the `run()` function to its - // corresponding runtime program counter - let final_pc = { - let offset = source_loc.start() as u32; - let length = (source_loc.end() - source_loc.start()) as u32; - trace!(%offset, %length, "find pc"); - contract - .get_source_map_deployed() - .unwrap() - .unwrap() - .into_iter() - .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) - .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, pc)| pc) - .max() - }; - trace!(?final_pc); - Ok(final_pc) - } - - pub fn run_func_body(&self) -> Result<&Vec> { - match self - .intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find REPL intermediate contract!"))? - .function_definitions - .get("run") - .ok_or_else(|| eyre::eyre!("Could not find run function definition in REPL contract!"))? - .body - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find run function body!"))? - { - pt::Statement::Block { statements, .. } => Ok(statements), - _ => eyre::bail!("Could not find statements within run function body!"), - } - } +/// A scoped reference to a [`GeneratedOutput`] together with an entered solar compiler. +pub struct GeneratedOutputRef<'a, 'b, 'gcx> { + output: &'a ProjectCompileOutput, + pub(crate) compiler: &'b CompilerRef<'gcx>, } -// TODO(dani): further migration blocked on upstream work -#[cfg(false)] impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { pub fn gcx(&self) -> Gcx<'gcx> { self.compiler.gcx() @@ -233,8 +73,35 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { self.output.find_first("REPL") } - pub fn get_event(&self, input: &str) -> Option { - self.gcx().hir.events_enumerated().find(|(_, e)| e.name.as_str() == input).map(|(id, _)| id) + /// Looks up the REPL contract in the HIR. + pub fn repl_contract_hir(&self) -> Option<&'gcx Contract<'gcx>> { + self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + } + + /// Returns the body block of the REPL `run()` function. + pub fn run_func_body(&self) -> Block<'gcx> { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); + let f = c + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) + .expect("`run()` function not found in REPL contract"); + hir.function(f).body.expect("`run()` function does not have a body") + } + + /// Returns the [`EventId`] of an event named `input` in the REPL contract, if any. + pub fn get_event(&self, input: &str) -> Option { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir()?; + c.items.iter().find_map(|id| { + if let ItemId::Event(eid) = id + && hir.event(*eid).name.as_str() == input + { + Some(*eid) + } else { + None + } + }) } pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { @@ -251,52 +118,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Record loc of first yul block return statement (if any). // This is used to decide which is the final statement within the `run()` method. // see . - let last_yul_return_span: Option = run_body.iter().find_map(|stmt| { - // TODO(dani): Yul is not yet lowered to HIR. - let _ = stmt; - /* - if let hir::StmtKind::Assembly { block, .. } = stmt { - if let Some(stmt) = block.last() { - if let pt::YulStatement::FunctionCall(yul_call) = stmt { - if yul_call.id.name == "return" { - return Some(stmt.loc()) - } - } - } - } - */ - None - }); + // + // Yul is not yet lowered to HIR (assembly statements appear as `StmtKind::Err`), + // so we walk the AST of the REPL source to find a top-level `return(...)` call + // inside any `assembly { ... }` block in `run()`. + let last_yul_return_span: Option = self.first_yul_return_span(); // Find the last statement within the "run()" method and get the program // counter via the source map. let Some(last_stmt) = run_body.last() else { return Ok(None) }; - // If the final statement is some type of block (assembly, unchecked, or regular), + // If the final statement is some type of block (unchecked or regular), // we need to find the final statement within that block. Otherwise, default to // the source loc of the final statement of the `run()` function's block. // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. + // Inline assembly blocks (lowered to `StmtKind::Err` in HIR in the pinned solar + // version) are handled separately via `trailing_assembly_last_stmt_span`, which + // walks the AST to recover the last meaningful Yul statement. let source_stmt = match &last_stmt.kind { - // TODO(dani): Yul is not yet lowered to HIR. - /* - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(stmt) = last_statement { - stmt - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - &run_body[run_body.len().saturating_sub(2)] - } - } - */ - hir::StmtKind::UncheckedBlock(stmts) | hir::StmtKind::Block(stmts) => { + HirStmtKind::UncheckedBlock(stmts) | HirStmtKind::Block(stmts) => { if let Some(stmt) = stmts.last() { stmt } else { @@ -308,9 +148,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { } _ => last_stmt, }; - let mut source_span = self.stmt_span_without_semicolon(source_stmt); + // If the trailing statement is an assembly block, prefer the last meaningful + // (non-`let`) Yul statement's span as the source location for `final_pc`. + // See . + // + // Two guards are required: + // 1. `StmtKind::Err`, assembly lowers to an error node in the current pinned solar + // version; this ensures we don't apply the AST fallback to properly-lowered stmts. + // 2. `trailing_assembly_last_stmt_span` returning `Some`, verifies via the AST that the + // failing HIR node actually corresponds to an assembly block (not some other lowering + // failure), and supplies the concrete span to use. + let mut source_span = if matches!(last_stmt.kind, HirStmtKind::Err(_)) + && let Some(span) = self.trailing_assembly_last_stmt_span() + { + span + } else { + self.stmt_span_without_semicolon(source_stmt) + }; - // Consider yul return statement as final statement (if it's loc is lower) . + // Consider yul return statement as final statement (if it's loc is lower). if let Some(yul_return_span) = last_yul_return_span && yul_return_span.hi() < source_span.lo() { @@ -319,26 +175,32 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Map the source location of the final statement of the `run()` function to its // corresponding runtime program counter - let (_sf, range) = self.compiler.sess().source_map().span_to_source(source_span).unwrap(); - dbg!(source_span, &range, &_sf.src[range.clone()]); + let result = self + .compiler + .sess() + .source_map() + .span_to_source(source_span) + .map_err(|e| eyre::eyre!("failed to resolve span: {e:?}"))?; + let range = result.data; let offset = range.start as u32; let length = range.len() as u32; - let final_pc = deployed_bytecode - .source_map() + trace!(%offset, %length, "find pc"); + let final_pc = contract + .get_source_map_deployed() .ok_or_else(|| eyre::eyre!("No source map found for `REPL` contract"))?? .into_iter() - .zip(InstructionIter::new(deployed_bytecode_bytes)) + .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, i)| i.pc) - .max() - .unwrap_or_default(); - Ok(Some(final_pc)) + .map(|(_, pc)| pc) + .max(); + trace!(?final_pc); + Ok(final_pc) } /// Statements' ranges in the solc source map do not include the semicolon. - fn stmt_span_without_semicolon(&self, stmt: &hir::Stmt<'_>) -> Span { + fn stmt_span_without_semicolon(&self, stmt: &Stmt<'_>) -> Span { match stmt.kind { - hir::StmtKind::DeclSingle(id) => { + HirStmtKind::DeclSingle(id) => { let decl = self.gcx().hir.variable(id); if let Some(expr) = decl.initializer { stmt.span.with_hi(expr.span.hi()) @@ -346,23 +208,65 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { stmt.span } } - hir::StmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), - hir::StmtKind::Expr(expr) => expr.span, + HirStmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), + HirStmtKind::Expr(expr) => expr.span, _ => stmt.span, } } - fn run_func_body(&self) -> hir::Block<'_> { - let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); - let f = c - .functions() - .find(|&f| self.gcx().hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) - .expect("`run()` function not found in REPL contract"); - self.gcx().hir.function(f).body.expect("`run()` function does not have a body") + /// Returns the AST `run()` body of the REPL contract, if any. + /// + /// Yul/assembly is not yet lowered to HIR in the pinned solar version, so we + /// keep around the AST to be able to inspect inline assembly blocks. + fn repl_run_ast_body(&self) -> Option<&'gcx solar::ast::Block<'gcx>> { + let contract = self.repl_contract_hir()?; + let source = self.gcx().sources.get(contract.source)?; + let ast = source.ast.as_ref()?; + + let contract_ast = ast.items.iter().find_map(|i| match &i.kind { + ItemKind::Contract(c) if c.name.as_str() == "REPL" => Some(c), + _ => None, + })?; + contract_ast.body.iter().find_map(|i| match &i.kind { + ItemKind::Function(f) if f.header.name.is_some_and(|n| n.as_str() == "run") => { + f.body.as_ref() + } + _ => None, + }) } - fn repl_contract_hir(&self) -> Option<&hir::Contract<'_>> { - self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + /// Returns the span of the first top-level `return(...)` call inside any + /// `assembly { ... }` block in the REPL `run()` function, if any. + fn first_yul_return_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + for stmt in run_body.stmts.iter() { + let AstStmtKind::Assembly(asm) = &stmt.kind else { continue }; + for ystmt in asm.block.stmts.iter() { + if let yul::StmtKind::Expr(e) = &ystmt.kind + && let yul::ExprKind::Call(call) = &e.kind + && call.name.as_str() == "return" + { + return Some(ystmt.span); + } + } + } + None + } + + /// If the last statement of the REPL `run()` function is an `assembly { ... }` block, + /// returns the span of its last non-`let` (i.e. non-VarDecl) Yul statement. + /// + /// This mirrors the legacy behavior used to pick a meaningful end-of-function PC when + /// the trailing statement is inline assembly. + fn trailing_assembly_last_stmt_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + let AstStmtKind::Assembly(asm) = &run_body.stmts.last()?.kind else { return None }; + asm.block + .stmts + .iter() + .rev() + .find(|s| !matches!(s.kind, yul::StmtKind::VarDecl(_, _))) + .map(|s| s.span) } } @@ -584,8 +488,7 @@ impl SessionSource { return Ok(output); } let output = self.compile()?; - let intermediate = self.generate_intermediate_output()?; - let output = GeneratedOutput { output, intermediate }; + let output = GeneratedOutput { output }; Ok(self.output.get_or_init(|| output)) } @@ -602,12 +505,11 @@ impl SessionSource { eyre::bail!("{output}"); } - // TODO(dani): re-enable - if cfg!(false) { - output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { - let _ = c.lower_asts(); - }); - } + // Drive HIR lowering and analysis so that subsequent `enter` queries can use them. + output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { + let _ = c.lower_asts(); + let _ = c.analysis(); + }); Ok(output) } @@ -632,53 +534,6 @@ impl SessionSource { sources } - /// Generate intermediate contracts for all contract definitions in the compilation source. - /// - /// ### Returns - /// - /// Optionally, a map of contract names to a vec of [IntermediateContract]s. - pub fn generate_intermediate_contracts(&self) -> Result> { - let mut res_map = HashMap::default(); - let parsed_map = self.get_sources(); - for source in parsed_map.values() { - Self::get_intermediate_contract(&source.content, &mut res_map); - } - Ok(res_map) - } - - /// Generate intermediate output for the REPL contract - pub fn generate_intermediate_output(&self) -> Result { - // Parse generate intermediate contracts - let intermediate_contracts = self.generate_intermediate_contracts()?; - - // Construct variable definitions - let variable_definitions = intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find intermediate REPL contract!"))? - .variable_definitions - .clone() - .into_iter() - .map(|(k, v)| (k, v.ty)) - .collect::>(); - // Construct intermediate output - let mut intermediate_output = IntermediateOutput { - repl_contract_expressions: variable_definitions, - intermediate_contracts, - }; - - // Add all statements within the run function to the repl_contract_expressions map - for (key, val) in intermediate_output - .run_func_body()? - .clone() - .iter() - .flat_map(Self::get_statement_definitions) - { - intermediate_output.repl_contract_expressions.insert(key, val); - } - - Ok(intermediate_output) - } - /// Construct the REPL source. pub fn to_repl_source(&self) -> String { let Self { @@ -741,108 +596,6 @@ contract {contract_name} {{ }); sess.dcx.emitted_errors().unwrap() } - - /// Gets the [IntermediateContract] for a Solidity source string and inserts it into the - /// passed `res_map`. In addition, recurses on any imported files as well. - /// - /// ### Takes - /// - `content` - A Solidity source string - /// - `res_map` - A mutable reference to a map of contract names to [IntermediateContract]s - pub fn get_intermediate_contract( - content: &str, - res_map: &mut HashMap, - ) { - if let Ok((pt::SourceUnit(source_unit_parts), _)) = solang_parser::parse(content, 0) { - let func_defs = source_unit_parts - .into_iter() - .filter_map(|sup| match sup { - pt::SourceUnitPart::ImportDirective(i) => match i { - pt::Import::Plain(s, _) - | pt::Import::Rename(s, _, _) - | pt::Import::GlobalSymbol(s, _, _) => { - let s = match s { - pt::ImportPath::Filename(s) => s.string, - pt::ImportPath::Path(p) => p.to_string(), - }; - let path = PathBuf::from(s); - - match fs::read_to_string(path) { - Ok(source) => { - Self::get_intermediate_contract(&source, res_map); - None - } - Err(_) => None, - } - } - }, - pt::SourceUnitPart::ContractDefinition(cd) => { - let mut intermediate = IntermediateContract::default(); - - cd.parts.into_iter().for_each(|part| match part { - pt::ContractPart::FunctionDefinition(def) => { - // Only match normal function definitions here. - if matches!(def.ty, pt::FunctionTy::Function) { - intermediate - .function_definitions - .insert(def.name.clone().unwrap().name, def); - } - } - pt::ContractPart::EventDefinition(def) => { - let event_name = def.name.as_ref().unwrap().name.clone(); - intermediate.event_definitions.insert(event_name, def); - } - pt::ContractPart::StructDefinition(def) => { - let struct_name = def.name.as_ref().unwrap().name.clone(); - intermediate.struct_definitions.insert(struct_name, def); - } - pt::ContractPart::VariableDefinition(def) => { - let var_name = def.name.as_ref().unwrap().name.clone(); - intermediate.variable_definitions.insert(var_name, def); - } - _ => {} - }); - Some((cd.name.as_ref().unwrap().name.clone(), intermediate)) - } - _ => None, - }) - .collect::>(); - res_map.extend(func_defs); - } - } - - /// Helper to deconstruct a statement - /// - /// ### Takes - /// - /// A reference to a [pt::Statement] - /// - /// ### Returns - /// - /// A vector containing tuples of the inner expressions' names, types, and storage locations. - pub fn get_statement_definitions(statement: &pt::Statement) -> Vec<(String, pt::Expression)> { - match statement { - pt::Statement::VariableDefinition(_, def, _) => { - vec![(def.name.as_ref().unwrap().name.clone(), def.ty.clone())] - } - pt::Statement::Expression(_, pt::Expression::Assign(_, left, _)) => { - if let pt::Expression::List(_, list) = left.as_ref() { - list.iter() - .filter_map(|(_, param)| { - param.as_ref().and_then(|param| { - param - .name - .as_ref() - .map(|name| (name.name.clone(), param.ty.clone())) - }) - }) - .collect() - } else { - Vec::default() - } - } - _ => Vec::default(), - } - } } /// A Parse Tree Fragment diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs index 704d30405eed9..338b7d2043809 100644 --- a/crates/chisel/tests/it/repl/mod.rs +++ b/crates/chisel/tests/it/repl/mod.rs @@ -153,6 +153,26 @@ assembly { repl.expect("[0x00:0x20]"); }); +// Assembly as the final statement with a return — exercises the path where both +// `first_yul_return_span` and `trailing_assembly_last_stmt_span` resolve to the same `return(...)` +// span (no subsequent Solidity statement after the assembly block). +repl_test!(assembly_return_final, |repl| { + repl.sendln("uint x = 0xbeef;"); + repl.sendln("assembly { mstore(0x0, sload(0)) return(0x0, 0x20) }"); + repl.sendln("!md"); + repl.expect("[0x00:0x20]"); +}); + +// Assembly block without a `return(...)` call as an intermediate statement, exercises +// `first_yul_return_span` returning `None` while a subsequent Solidity statement is still evaluated +// correctly. +repl_test!(assembly_no_return_intermediate, |repl| { + repl.sendln("uint x = 1;"); + repl.sendln("assembly { x := add(x, 1) }"); + repl.sendln("x"); + repl.expect("Decimal: 2"); +}); + // Issue #5051, #8978: Test EVM version normalization. repl_test!(flaky_evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { repl.sendln("uint x;\nx"); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 37340f5f4cc3c..606b8291819e4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -52,6 +52,7 @@ rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true serde.workspace = true +toml.workspace = true strsim = "0.11" strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } @@ -70,7 +71,13 @@ tempfile.workspace = true tikv-jemallocator = { workspace = true, optional = true } [features] +default = ["optimism"] tracy = ["dep:tracing-tracy"] tracy-allocator = ["tracy"] jemalloc = ["dep:tikv-jemallocator"] mimalloc = ["dep:mimalloc"] +optimism = [ + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 87f14e2039606..4fc437c7232f8 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -307,6 +307,17 @@ mod tests { assert_eq!(val, &Value::from(1000u64)); } + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + use clap::CommandFactory; + + let command = EvmArgs::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + #[test] fn can_parse_chain_id() { let args = EvmArgs { diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 8c37860446683..f846da5002354 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -66,8 +66,20 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + self.url_with_env(config, std::env::var("ETH_RPC_URL").ok()) + } + + fn url_with_env<'a>( + &'a self, + config: Option<&'a Config>, + env_url: Option, + ) -> Result>> { if self.flashbots { Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else if let Some(url) = self.common.rpc_url.as_deref() { + Ok(Some(Cow::Borrowed(url))) + } else if let Some(url) = env_url { + Ok(Some(Cow::Owned(url))) } else { self.common.url(config) } @@ -85,8 +97,10 @@ impl RpcOpts { pub fn dict(&self) -> Dict { let mut dict = self.common.dict(); - if self.flashbots { - dict.insert("eth_rpc_url".into(), FLASHBOTS_URL.into()); + // `self.url(None)` already accounts for `flashbots` and the `ETH_RPC_URL` env var, + // so a single insert here covers both. + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); } if let Ok(Some(jwt)) = self.jwt(None) { dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); @@ -199,6 +213,7 @@ impl figment::Provider for EthereumOpts { #[cfg(test)] mod tests { use super::*; + use clap::CommandFactory; #[test] fn parse_etherscan_opts() { @@ -223,4 +238,41 @@ mod tests { let id: u64 = chain_id.deserialize().expect("chain_id should deserialize as u64"); assert_eq!(id, 9745); } + + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + let command = RpcOpts::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + + #[test] + fn rpc_url_resolves_eth_rpc_url_env() { + let args = RpcOpts::default(); + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8545"); + } + + #[test] + fn explicit_rpc_url_takes_precedence_over_eth_rpc_url_env() { + let args = RpcOpts { + common: RpcCommonOpts { + rpc_url: Some("http://127.0.0.1:8546".to_string()), + ..Default::default() + }, + ..Default::default() + }; + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8546"); + } } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs index 05b98582fa88f..6a5fe5ed4e9e4 100644 --- a/crates/cli/src/opts/rpc_common.rs +++ b/crates/cli/src/opts/rpc_common.rs @@ -17,10 +17,15 @@ use std::borrow::Cow; /// This struct holds fields that both [`super::RpcOpts`] (cast) and /// [`super::EvmArgs`] (forge/script) need, eliminating duplication and /// making the two structs composable. +/// +/// Note: `ETH_RPC_URL` is intentionally **not** bound here as a clap env +/// fallback; otherwise it would be inherited by `EvmArgs` and silently +/// fork all `forge test` runs. Cast resolves `ETH_RPC_URL` explicitly +/// at the call site (see [`super::RpcOpts::url`]). #[derive(Clone, Debug, Default, Serialize, Parser)] pub struct RpcCommonOpts { /// The RPC endpoint. - #[arg(short, long, visible_alias = "fork-url", env = "ETH_RPC_URL")] + #[arg(short, long, visible_alias = "fork-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub rpc_url: Option, diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs index 8c2a12e661e18..88119f163b325 100644 --- a/crates/cli/src/opts/tempo.rs +++ b/crates/cli/src/opts/tempo.rs @@ -1,16 +1,26 @@ use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, ruint::aliases::U256}; -use alloy_signer::Signature; +use alloy_signer::{Signature, Signer}; use clap::Parser; -use foundry_common::FoundryTransactionBuilder; -use std::{num::NonZeroU64, str::FromStr}; +use eyre::Result; +use foundry_common::{ + FoundryTransactionBuilder, + tempo::{TempoSponsor, resolve_tempo_sponsor_signer}, +}; +use std::{ + num::NonZeroU64, + path::PathBuf, + str::FromStr, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; use crate::utils::parse_fee_token_address; -/// CLI options for Tempo transactions. +/// CLI options common to Tempo transactions across commands. #[derive(Clone, Debug, Default, Parser)] #[command(next_help_heading = "Tempo")] -pub struct TempoOpts { +pub struct TempoCommonOpts { /// Fee token address for Tempo transactions. /// /// When set, builds a Tempo (type 0x76) transaction that pays gas fees @@ -21,6 +31,40 @@ pub struct TempoOpts { #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] pub fee_token: Option
, + /// Opt into TIP-1009 expiring-nonce mode with a validity window. + /// + /// Convenience flag that combines `--tempo.expiring-nonce` with a relative + /// `--tempo.valid-before`. Sets nonce_key = U256::MAX, nonce = 0, and valid_before = now + + /// seconds. + /// + /// Maximum value is 30 seconds. The transaction must be mined before the deadline or it + /// becomes permanently invalid, giving safe retry semantics: retries produce a fresh tx hash + /// and the old tx can never land late. + #[arg(long = "tempo.expires", value_name = "SECONDS", value_parser = parse_expires_seconds)] + pub expires: Option, +} + +impl TempoCommonOpts { + /// Returns `true` if any Tempo-specific option is set. + pub const fn is_tempo(&self) -> bool { + self.fee_token.is_some() || self.expires.is_some() + } + + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + let secs = self.expires?; + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Some(now.as_secs() + secs) + } +} + +/// CLI options for Tempo transactions. +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Tempo")] +pub struct TempoOpts { + #[command(flatten)] + pub common: TempoCommonOpts, + /// Nonce key for Tempo parallelizable nonces. /// /// When set, builds a Tempo (type 0x76) transaction with the specified nonce key, @@ -28,21 +72,69 @@ pub struct TempoOpts { /// to be executed in parallel. If not set, the protocol nonce key (0) will be used. /// /// For more information see . - #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY")] + #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY", conflicts_with = "lane")] pub nonce_key: Option, + /// Named nonce lane for Tempo parallelizable nonces. + /// + /// Resolves a friendly lane name (e.g. `deploy`, `payments`) to a `nonce_key` via a + /// shared lanes file (default: `tempo.lanes.toml` at the project root). The lanes file + /// is a TOML map of `name = ` entries, e.g.: + /// + /// ```toml + /// deploy = 1 + /// ops = 2 + /// payments = 3 + /// ``` + /// + /// Mutually exclusive with `--tempo.nonce-key`. + #[arg(long = "tempo.lane", value_name = "NAME")] + pub lane: Option, + + /// Path to the Tempo lanes file used by `--tempo.lane`. + /// + /// Defaults to `tempo.lanes.toml` at the project root. + #[arg(long = "tempo.lanes-file", value_name = "PATH")] + pub lanes_file: Option, + + /// Sponsor (fee payer) address for Tempo sponsored transactions. + #[arg(long = "tempo.sponsor", value_name = "ADDRESS")] + pub sponsor: Option
, + + /// Sign Tempo sponsor digests in-band with the given signer URI. + /// + /// Supported forms include `env://VAR`, `keystore://PATH`, `account://NAME`, + /// `ledger://`, `trezor://`, `aws://`, `gcp://`, `turnkey://`, and + /// `private-key://KEY`. + #[arg( + long = "tempo.sponsor-signer", + value_name = "SIGNER", + requires = "sponsor", + conflicts_with = "sponsor_sig" + )] + pub sponsor_signer: Option, + /// Sponsor (fee payer) signature for Tempo sponsored transactions. /// /// The sponsor signs the `fee_payer_signature_hash` to commit to paying gas fees /// on behalf of the sender. Provide as a hex-encoded signature. - #[arg(long = "tempo.sponsor-signature", value_parser = parse_signature)] - pub sponsor_signature: Option, + #[arg( + long = "tempo.sponsor-sig", + alias = "tempo.sponsor-signature", + value_parser = parse_signature, + requires = "sponsor", + conflicts_with = "sponsor_signer" + )] + pub sponsor_sig: Option, /// Print the sponsor signature hash and exit. /// /// Computes the `fee_payer_signature_hash` for the transaction so that a sponsor /// knows what hash to sign. The transaction is not sent. - #[arg(long = "tempo.print-sponsor-hash")] + #[arg( + long = "tempo.print-sponsor-hash", + conflicts_with_all = &["sponsor", "sponsor_signer", "sponsor_sig"] + )] pub print_sponsor_hash: bool, /// Access key ID for Tempo Keychain signature transactions. @@ -56,14 +148,14 @@ pub struct TempoOpts { /// /// Sets nonce to 0 and nonce_key to U256::MAX, enabling time-bounded transaction /// validity via `--tempo.valid-before` and `--tempo.valid-after`. - #[arg(long = "tempo.expiring-nonce", requires = "valid_before")] + #[arg(long = "tempo.expiring-nonce", requires = "valid_before", conflicts_with = "expires")] pub expiring_nonce: bool, /// Upper bound timestamp for Tempo expiring nonce transactions. /// /// The transaction is only valid before this unix timestamp. /// Requires `--tempo.expiring-nonce`. - #[arg(long = "tempo.valid-before")] + #[arg(long = "tempo.valid-before", conflicts_with = "expires")] pub valid_before: Option, /// Lower bound timestamp for Tempo expiring nonce transactions. @@ -77,9 +169,12 @@ pub struct TempoOpts { impl TempoOpts { /// Returns `true` if any Tempo-specific option is set. pub const fn is_tempo(&self) -> bool { - self.fee_token.is_some() + self.common.is_tempo() || self.nonce_key.is_some() - || self.sponsor_signature.is_some() + || self.lane.is_some() + || self.sponsor.is_some() + || self.sponsor_signer.is_some() + || self.sponsor_sig.is_some() || self.print_sponsor_hash || self.key_id.is_some() || self.expiring_nonce @@ -87,6 +182,58 @@ impl TempoOpts { || self.valid_after.is_some() } + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + self.common.expires_at() + } + + /// Resolves `--tempo.expires` into concrete expiring-nonce fields. + /// + /// This computes the relative deadline once so later calls to [`Self::apply`] reuse the same + /// `valid_before` timestamp instead of deriving a fresh one. + pub fn resolve_expires(&mut self) -> Option { + let ts = self.expires_at()?; + self.expiring_nonce = true; + self.valid_before = Some(ts); + self.common.expires = None; + Some(ts) + } + + /// Returns `true` if a sponsor signature should be attached before submission. + pub const fn has_sponsor_submission(&self) -> bool { + self.sponsor.is_some() || self.sponsor_signer.is_some() || self.sponsor_sig.is_some() + } + + /// Resolves sponsor CLI options into a reusable sponsor config for transaction submission. + pub async fn sponsor_config(&self) -> Result> { + let Some(sponsor) = self.sponsor else { + return Ok(None); + }; + + let signer = if let Some(spec) = &self.sponsor_signer { + Some(Arc::new(Box::pin(resolve_tempo_sponsor_signer(spec)).await?)) + } else { + None + }; + + if let Some(signer) = &signer { + let signer_address = signer.address(); + if signer_address != sponsor { + eyre::bail!( + "Tempo sponsor signer address {signer_address} does not match --tempo.sponsor {sponsor}" + ); + } + } + + if signer.is_none() && self.sponsor_sig.is_none() { + eyre::bail!( + "--tempo.sponsor requires either --tempo.sponsor-signer or --tempo.sponsor-sig" + ); + } + + Ok(Some(TempoSponsor::new(sponsor, signer, self.sponsor_sig))) + } + /// Applies Tempo-specific options to a transaction request. /// /// All setters are no-ops for non-Tempo networks, so this is safe to call unconditionally. @@ -94,8 +241,9 @@ impl TempoOpts { where N::TransactionRequest: FoundryTransactionBuilder, { - // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX - if self.expiring_nonce { + // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX. + // --tempo.expires is a convenience alias that also sets valid_before = now + duration. + if self.expiring_nonce || self.common.expires.is_some() { tx.set_nonce(0); tx.set_nonce_key(U256::MAX); } else { @@ -107,11 +255,14 @@ impl TempoOpts { } } - if let Some(fee_token) = self.fee_token { + if let Some(fee_token) = self.common.fee_token { tx.set_fee_token(fee_token); } - if let Some(valid_before) = self.valid_before + // --tempo.expires sets valid_before relative to now; --tempo.valid-before takes a raw + // unix timestamp. The two flags are mutually exclusive (enforced by clap). + let effective_valid_before = self.expires_at().or(self.valid_before); + if let Some(valid_before) = effective_valid_before && let Some(v) = NonZeroU64::new(valid_before) { tx.set_valid_before(v); @@ -131,8 +282,7 @@ impl TempoOpts { // gas estimation so that `--tempo.print-sponsor-hash` and // `--tempo.sponsor-signature` produce identical gas estimates. Callers // should call `set_fee_payer_signature` on the built tx request. - if (self.sponsor_signature.is_some() || self.print_sponsor_hash) && tx.nonce_key().is_none() - { + if (self.has_sponsor_submission() || self.print_sponsor_hash) && tx.nonce_key().is_none() { tx.set_nonce_key(U256::ZERO); } } @@ -142,11 +292,83 @@ fn parse_signature(s: &str) -> Result { Signature::from_str(s).map_err(|e| format!("invalid signature: {e}")) } +/// Parses a seconds value for `--tempo.expires`, capped at the protocol maximum of 30 seconds. +fn parse_expires_seconds(s: &str) -> Result { + let secs: u64 = s + .parse() + .map_err(|_| format!("invalid value '{s}': expected an integer number of seconds"))?; + if secs > 30 { + return Err(format!("expires must be at most 30 seconds (got {secs})")); + } + Ok(secs) +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::address; + #[test] + fn parses_lane_arg() { + let opts = TempoOpts::try_parse_from(["", "--tempo.lane", "deploy"]).unwrap(); + assert_eq!(opts.lane.as_deref(), Some("deploy")); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn lane_conflicts_with_nonce_key() { + let err = + TempoOpts::try_parse_from(["", "--tempo.lane", "deploy", "--tempo.nonce-key", "1"]) + .unwrap_err(); + assert!( + err.to_string().contains("cannot be used with"), + "expected clap conflict error, got: {err}", + ); + } + + #[test] + fn parse_expires_flag() { + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "30"]).unwrap(); + assert_eq!(opts.common.expires, Some(30)); + + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + assert_eq!(opts.common.expires, Some(10)); + + // exceeds 30s maximum + assert!(TempoOpts::try_parse_from(["", "--tempo.expires", "31"]).is_err()); + + // conflicts with --tempo.expiring-nonce + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.expires", + "30", + "--tempo.expiring-nonce", + "--tempo.valid-before", + "999" + ]) + .is_err() + ); + } + + #[test] + fn resolve_expires_materializes_valid_before() { + let before = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + let mut opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + + let resolved = opts.resolve_expires().unwrap(); + let after = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + + assert!(resolved >= before + 10); + assert!(resolved <= after + 10); + assert!(opts.expiring_nonce); + assert_eq!(opts.valid_before, Some(resolved)); + assert_eq!(opts.common.expires, None); + assert_eq!(opts.expires_at(), None); + } + #[test] fn parse_fee_token_id() { let opts = TempoOpts::try_parse_from([ @@ -155,13 +377,69 @@ mod tests { "0x20C0000000000000000000000000000000000002", ]) .unwrap(); - assert_eq!(opts.fee_token, Some(address!("0x20C0000000000000000000000000000000000002")),); + assert_eq!( + opts.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000002")), + ); // AlphaUSD token ID is 1u64 let opts_with_id = TempoOpts::try_parse_from(["", "--tempo.fee-token", "1"]).unwrap(); assert_eq!( - opts_with_id.fee_token, + opts_with_id.common.fee_token, Some(address!("0x20C0000000000000000000000000000000000001")), ); } + + #[test] + fn parse_sponsor_signer() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert_eq!(opts.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + assert!(opts.sponsor_sig.is_none()); + assert!(opts.is_tempo()); + assert!(opts.has_sponsor_submission()); + } + + #[test] + fn sponsor_signer_requires_sponsor() { + assert!( + TempoOpts::try_parse_from(["", "--tempo.sponsor-signer", "env://SPONSOR"]).is_err() + ); + } + + #[test] + fn parse_sponsor_signature_alias() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signature", + "0x0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert!(opts.sponsor_sig.is_some()); + } + + #[test] + fn print_sponsor_hash_conflicts_with_sponsor_submission() { + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.print-sponsor-hash", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + ]) + .is_err() + ); + } } diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs index 647f52d316a6c..4b5715b9ebe08 100644 --- a/crates/cli/src/utils/tempo.rs +++ b/crates/cli/src/utils/tempo.rs @@ -1,8 +1,44 @@ -use std::str::FromStr; +//! Tempo utilities: fee token parsing and named nonce lanes (2D nonces). +//! +//! A "lane" is a friendly alias for a Tempo `nonce_key` (a [`U256`]). Lanes are defined in a +//! shared TOML file (default `tempo.lanes.toml` at the project root) so a team can reserve +//! independent sequential nonce streams for parallel scripts without coordinating on raw +//! `U256` selectors. +//! +//! Example `tempo.lanes.toml`: +//! +//! ```toml +//! deploy = 1 +//! ops = 2 +//! payments = 3 +//! ``` +//! +//! ```bash +//! cast erc20 transfer ... --tempo.lane payments +//! ``` -use alloy_primitives::Address; +use crate::opts::TempoOpts; +use alloy_primitives::{Address, U256}; +use eyre::{Result, eyre}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, +}; use tempo_primitives::TempoAddressExt; +/// Default name of the lanes file at the project root. +pub const DEFAULT_LANES_FILE: &str = "tempo.lanes.toml"; + +/// Result of resolving a `--tempo.lane ` argument against a lanes file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResolvedLane { + /// The lane name as provided on the CLI. + pub name: String, + /// The `nonce_key` the lane resolved to. + pub nonce_key: U256, +} + /// Parses a fee token address. pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ Address::from_str(address_or_id).or_else(|_| Ok(token_id_to_address(address_or_id.parse()?))) @@ -14,3 +50,156 @@ fn token_id_to_address(token_id: u64) -> Address { address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); Address::from(address_bytes) } + +/// Loads a TOML lanes file from `path`. +/// +/// Each top-level key is a lane name, and the value is the `nonce_key` (an integer or a +/// decimal/hex string parsed as [`U256`]). +pub fn load_lanes(path: &Path) -> Result> { + let contents = std::fs::read_to_string(path) + .map_err(|e| eyre!("failed to read tempo lanes file {}: {}", path.display(), e))?; + parse_lanes(&contents) + .map_err(|e| eyre!("failed to parse tempo lanes file {}: {}", path.display(), e)) +} + +fn parse_lanes(contents: &str) -> Result> { + let raw: BTreeMap = toml::from_str(contents)?; + let mut out = BTreeMap::new(); + for (name, value) in raw { + let nonce_key = match value { + toml::Value::Integer(n) => { + if n < 0 { + return Err(eyre!("invalid nonce_key for lane '{name}': must be non-negative")); + } + U256::from(n as u64) + } + toml::Value::String(s) => U256::from_str(s.trim()) + .map_err(|e| eyre!("invalid nonce_key for lane '{name}': {e}"))?, + other => { + return Err(eyre!( + "invalid nonce_key for lane '{name}': expected integer or string, got {}", + other.type_str(), + )); + } + }; + out.insert(name, nonce_key); + } + Ok(out) +} + +/// Resolves `opts.lane` against a lanes file and writes the resulting `nonce_key` to +/// `opts.nonce_key`. Returns the resolved lane (or `None` if no `--tempo.lane` was set). +/// +/// `root` is the project root used to locate the default lanes file +/// (`/tempo.lanes.toml`) when `--tempo.lanes-file` was not provided. +pub fn resolve_lane(opts: &mut TempoOpts, root: &Path) -> Result> { + let Some(lane_name) = opts.lane.clone() else { return Ok(None) }; + + let path: PathBuf = opts.lanes_file.clone().unwrap_or_else(|| root.join(DEFAULT_LANES_FILE)); + + if !path.exists() { + return Err(eyre!( + "tempo lanes file not found at {}\n\ + create it with `name = ` entries, e.g.:\n \ + deploy = 1\n \ + ops = 2\n \ + payments = 3", + path.display(), + )); + } + + let lanes = load_lanes(&path)?; + + let nonce_key = lanes.get(&lane_name).copied().ok_or_else(|| { + let mut known: Vec<&str> = lanes.keys().map(String::as_str).collect(); + known.sort_unstable(); + eyre!( + "lane '{lane_name}' not found in {} (known lanes: {})", + path.display(), + if known.is_empty() { "".to_string() } else { known.join(", ") }, + ) + })?; + + opts.nonce_key = Some(nonce_key); + Ok(Some(ResolvedLane { name: lane_name, nonce_key })) +} + +/// Prints `lane: (nonce_key=, nonce=)` to stderr (so it doesn't pollute +/// stdout for commands like `cast mktx` whose stdout is meant to be piped), giving +/// visibility into which 2D nonce lane was used. +pub fn maybe_print_resolved_lane(resolved: Option<&ResolvedLane>, nonce: u64) -> Result<()> { + if let Some(lane) = resolved { + sh_eprintln!("lane: {} (nonce_key={}, nonce={})", lane.name, lane.nonce_key, nonce)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_int_and_string_lane_values() { + let toml = r#" +deploy = 1 +ops = 2 +payments = "3" +big = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +"#; + let lanes = parse_lanes(toml).unwrap(); + assert_eq!(lanes.get("deploy"), Some(&U256::from(1u64))); + assert_eq!(lanes.get("ops"), Some(&U256::from(2u64))); + assert_eq!(lanes.get("payments"), Some(&U256::from(3u64))); + assert_eq!(lanes.get("big"), Some(&U256::MAX)); + } + + #[test] + fn parse_lanes_rejects_invalid_string() { + let toml = "broken = \"not-a-number\""; + let err = parse_lanes(toml).unwrap_err(); + assert!(err.to_string().contains("invalid nonce_key for lane 'broken'")); + } + + #[test] + fn resolve_lane_sets_nonce_key_and_returns_resolved() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 7\npayments = 42\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let resolved = resolve_lane(&mut opts, dir.path()).unwrap().unwrap(); + assert_eq!(resolved.name, "payments"); + assert_eq!(resolved.nonce_key, U256::from(42u64)); + assert_eq!(opts.nonce_key, Some(U256::from(42u64))); + } + + #[test] + fn resolve_lane_returns_none_when_no_lane() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts::default(); + let resolved = resolve_lane(&mut opts, dir.path()).unwrap(); + assert!(resolved.is_none()); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn resolve_lane_errors_when_file_missing() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts { lane: Some("deploy".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + assert!(err.to_string().contains("tempo lanes file not found")); + } + + #[test] + fn resolve_lane_errors_when_lane_unknown() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 1\nops = 2\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("lane 'payments' not found")); + assert!(msg.contains("deploy, ops")); + } +} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 7fd94c07242e8..a66a0fef2fe0a 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -34,7 +34,7 @@ alloy-signer.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } -alloy-rpc-types-engine = { workspace = true, features = ["jwt"] } +alloy-rpc-types-engine = { workspace = true, features = ["jwt-aws-lc-rs"] } alloy-sol-types.workspace = true alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true @@ -43,8 +43,8 @@ alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true -op-alloy-network.workspace = true -op-alloy-rpc-types.workspace = true +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } revm.workspace = true @@ -86,6 +86,10 @@ mpp.workspace = true foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tokio-tungstenite.workspace = true futures.workspace = true +alloy-signer-local.workspace = true +base64.workspace = true +sha2 = "0.10" +tempfile.workspace = true [build-dependencies] chrono.workspace = true @@ -95,4 +99,12 @@ vergen = { workspace = true, features = ["build", "emit_and_set"] } foundry-evm-hardforks.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } axum = { workspace = true } -tempfile.workspace = true +k256 = { workspace = true } + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "foundry-common-fmt/optimism", +] diff --git a/crates/common/build.rs b/crates/common/build.rs index d89e23be850f4..9afa01b5757ef 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -15,16 +15,13 @@ fn main() -> Result<(), Box> { let sha_short = &sha[..10]; let tag_name = try_env_var("TAG_NAME").unwrap_or_else(|| String::from("dev")); - let is_nightly = tag_name.contains("nightly"); - let version_suffix = if is_nightly { "nightly" } else { &tag_name }; + let version = release_version(&env_var("CARGO_PKG_VERSION"), &tag_name); + let is_nightly = tag_name.starts_with("nightly"); if is_nightly { println!("cargo:rustc-env=FOUNDRY_IS_NIGHTLY_VERSION=true"); } - let pkg_version = env_var("CARGO_PKG_VERSION"); - let version = format!("{pkg_version}-{version_suffix}"); - // `PROFILE` captures only release or debug. Get the actual name from the out directory. let out_dir = PathBuf::from(env_var("OUT_DIR")); let profile = out_dir.components().rev().nth(3).unwrap().as_os_str().to_str().unwrap(); @@ -87,6 +84,19 @@ fn env_var(name: &str) -> String { try_env_var(name).unwrap() } +fn release_version(pkg_version: &str, tag_name: &str) -> String { + if let Some(version) = tag_name.strip_prefix('v') { + return version.to_owned(); + } + + // Normalize `nightly-` to `nightly` so tarball and Docker nightly + // artifacts produce the same version string. The commit identifier is + // already included in the SemVer build metadata (after `+`). + let normalized = if tag_name.starts_with("nightly-") { "nightly" } else { tag_name }; + + format!("{pkg_version}-{normalized}") +} + fn try_env_var(name: &str) -> Option { println!("cargo:rerun-if-env-changed={name}"); std::env::var(name).ok() diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 2c8e16bccdcc6..179c71048da5b 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -20,10 +20,10 @@ eyre.workspace = true # ui alloy-consensus.workspace = true -op-alloy-consensus.workspace = true +op-alloy-consensus = { workspace = true, optional = true } alloy-network.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } -op-alloy-rpc-types.workspace = true +op-alloy-rpc-types = { workspace = true, optional = true } alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true @@ -38,3 +38,7 @@ tempo-alloy.workspace = true [dev-dependencies] foundry-macros.workspace = true similar-asserts.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:op-alloy-consensus", "dep:op-alloy-rpc-types"] diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index e883810dcda34..2087a85236154 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -18,6 +18,7 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; @@ -448,6 +449,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxDeposit { fn pretty(&self) -> String { format!( @@ -472,6 +474,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxPostExec { fn pretty(&self) -> String { format!( @@ -606,6 +609,7 @@ type {:#x} } } +#[cfg(feature = "optimism")] impl UIfmt for OpTxEnvelope { fn pretty(&self) -> String { match self { @@ -651,6 +655,7 @@ effectiveGasPrice {} } } +#[cfg(feature = "optimism")] impl UIfmt for op_alloy_rpc_types::Transaction { fn pretty(&self) -> String { format!( @@ -786,6 +791,7 @@ impl UIfmtSignatureExt for AnyTxEnvelope { } } +#[cfg(feature = "optimism")] impl UIfmtSignatureExt for OpTxEnvelope { fn signature_pretty(&self) -> Option<(String, String, String)> { self.signature().map(|sig| { @@ -1135,6 +1141,7 @@ mod tests { assert_eq!(b.pretty(), b32.pretty()); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx() { let s = r#" @@ -1186,6 +1193,7 @@ yParity 1 ); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx_through_any() { let s = r#" diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 895b16b3b4532..95c7d4083f34e 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -383,16 +383,14 @@ impl ContractsByArtifact { &self, id: &str, ) -> Result>> { - let contracts = self - .iter() - .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) - .collect::>(); - - if contracts.len() > 1 { + let mut iter = + self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id); + let first = iter.next(); + if first.is_some() && iter.next().is_some() { eyre::bail!("{id} has more than one implementation."); } - Ok(contracts.first().copied()) + Ok(first) } /// Finds abi by name or source path @@ -411,7 +409,7 @@ impl ContractsByArtifact { let mut funcs = BTreeMap::new(); let mut events = BTreeMap::new(); let mut errors_abi = JsonAbi::new(); - for (_name, contract) in self.iter() { + for contract in self.values() { for func in contract.abi.functions() { funcs.insert(func.selector(), func.clone()); } diff --git a/crates/common/src/provider/mpp/keys.rs b/crates/common/src/provider/mpp/keys.rs index 65640c48ab841..fa0fc80ed3d03 100644 --- a/crates/common/src/provider/mpp/keys.rs +++ b/crates/common/src/provider/mpp/keys.rs @@ -7,6 +7,7 @@ use crate::tempo::{TEMPO_PRIVATE_KEY_ENV, WalletType, read_tempo_keys_file}; use alloy_primitives::Address; +use std::env; use tracing::debug; /// Options for MPP key discovery filtering. @@ -55,7 +56,7 @@ pub fn discover_mpp_key() -> Option { /// target chain and the required currency. pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 1. Check TEMPO_PRIVATE_KEY env var (no keychain metadata available) - if let Ok(key) = std::env::var(TEMPO_PRIVATE_KEY_ENV) { + if let Ok(key) = env::var(TEMPO_PRIVATE_KEY_ENV) { let key = key.trim().to_string(); if !key.is_empty() { debug!("using MPP key from {TEMPO_PRIVATE_KEY_ENV} env var"); @@ -73,11 +74,17 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 2. Read $TEMPO_HOME/wallet/keys.toml (default: ~/.tempo/wallet/keys.toml) let keys_file = read_tempo_keys_file()?; + // `expiry == 0` means "no expiry" on the wire. + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + // Pick primary key using the same deterministic order as // `Keystore::primary_key()` in tempo-common: // passkey > first entry with inline key > first entry // Only entries with a usable inline key can provide a signing key. - // Filter by chain_id and currency when provided. + // Filter by chain_id, currency, and freshness when provided. let candidates: Vec<_> = keys_file .keys .iter() @@ -86,6 +93,7 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { opts.currency .is_none_or(|cur| k.limits.is_empty() || k.limits.iter().any(|l| l.currency == cur)) }) + .filter(|k| k.expiry.is_none_or(|e| e == 0 || e > now)) .collect(); let primary = candidates @@ -135,6 +143,7 @@ mod tests { #[test] fn discover_from_tempo_home_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( r#" @@ -160,6 +169,7 @@ chain_id = 4217 #[test] fn discover_env_var_takes_priority_over_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let file_key = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; let env_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( @@ -187,6 +197,7 @@ key = "{file_key}" #[test] fn discover_returns_none_when_no_keys() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let (dir, _) = setup_keys_toml(""); unsafe { @@ -202,6 +213,7 @@ key = "{file_key}" #[test] fn discover_skips_entries_without_inline_key() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; let toml_content = format!( r#" @@ -344,6 +356,7 @@ key = "0xthe_key" #[test] fn discover_filters_by_chain_id() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let mainnet_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let testnet_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let toml_content = format!( @@ -416,6 +429,62 @@ chain_id = 4217 unsafe { std::env::remove_var("TEMPO_HOME") }; } + #[test] + fn discover_filters_expired_entries() { + // Expired entries must not be selected, so the next 402 re-triggers + // the device-code flow instead of returning a stale key. + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let expired_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let fresh_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let toml_content = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{fresh_key}" +chain_id = 4217 +expiry = 0 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + // Even though the expired entry comes first, discovery skips it. + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, fresh_key); + + // With only the expired entry present, discovery returns None so the + // 402 path can run `ensure_access_key` again. + let only_expired = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 +"# + ); + let (dir2, _) = setup_keys_toml(&only_expired); + unsafe { std::env::set_var("TEMPO_HOME", dir2.path()) }; + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert!(config.is_none(), "expired-only keys.toml must not yield a usable key"); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + #[test] fn parse_keys_toml_unknown_fields_ignored() { let toml_str = r#" diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 334166b844613..c3e87f8cf42b5 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -175,6 +175,16 @@ impl SessionProvider { self } + /// Address that funds payments for this provider. + pub fn funding_wallet_address(&self) -> Address { + self.signing_mode.from_address(self.signer.address()) + } + + /// Chain ID from the selected wallet key, when known. + pub const fn key_chain_id(&self) -> Option { + self.key_chain_id + } + /// Set the chain ID and currencies from the key entry used to initialize /// this provider. Used to reject challenges for incompatible chains/currencies. /// When `chain_id` is `None` (e.g. env var key), chain filtering is skipped. diff --git a/crates/common/src/provider/mpp/transport.rs b/crates/common/src/provider/mpp/transport.rs index 67354dc2bd60d..9e3b16dedd59e 100644 --- a/crates/common/src/provider/mpp/transport.rs +++ b/crates/common/src/provider/mpp/transport.rs @@ -4,6 +4,7 @@ //! handling via the MPP protocol. When the RPC endpoint returns a 402 response, //! this transport automatically pays the challenge and retries the request. +use alloy_chains::Chain; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_transport::{TransportError, TransportErrorKind, TransportFut, TransportResult}; use mpp::{ @@ -16,12 +17,17 @@ use mpp::{ use reqwest::{StatusCode, header::HeaderMap}; use std::{ collections::HashMap, - fmt, - sync::{Mutex, OnceLock}, + env, fmt, io, + io::IsTerminal, + process::{Command, Stdio}, + sync::{ + Arc, LazyLock, Mutex, + atomic::{AtomicBool, Ordering}, + }, task, time::Duration, }; -use tokio::sync::OwnedMutexGuard; +use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard}; use tower::Service; use tracing::{Instrument, debug, debug_span, trace}; use url::Url; @@ -39,7 +45,27 @@ const MPP_RETRY_TIMEOUT: Duration = Duration::from_secs(120); /// Resolve the deposit amount from `MPP_DEPOSIT` env var or the default. fn default_deposit() -> u128 { - std::env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) + env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct FundingContext { + wallet_address: Option, + token: Option, + chain_id: Option, +} + +impl FundingContext { + fn token_line(&self) -> String { + self.token + .as_ref() + .map(|token| format!("Requested payment token: {token}\n\n")) + .unwrap_or_default() + } + + fn network(&self) -> Option { + self.chain_id.filter(|chain| chain.is_tempo()).map(|chain| chain.to_string()) + } } fn format_http_diagnostics(headers: &HeaderMap) -> String { @@ -60,12 +86,173 @@ fn format_http_diagnostics(headers: &HeaderMap) -> String { } } +fn tempo_wallet_fund_help(ctx: &FundingContext) -> String { + let mut command = "tempo wallet fund".to_string(); + if let Some(address) = ctx.wallet_address { + command.push_str(&format!(" --address {address}")); + } + if let Some(network) = ctx.network() { + command.push_str(&format!(" --network {network}")); + } + + let mut no_browser = command.clone(); + no_browser.push_str(" --no-browser"); + + format!( + "\n\nTempo wallet payment could not be funded for this paid RPC request.\n\n{}\ + Fund the wallet, then rerun the command:\n {command}\n\n\ + If this CLI is running on a remote or headless host, use:\n {no_browser}", + ctx.token_line() + ) +} + +/// Decide whether the interactive `tempo wallet fund` flow may be launched. +/// +/// Policy (library-safe): +/// - never run inside CI +/// - never run unless both stdin and stderr are real terminals +/// - `FOUNDRY_MPP_NO_AUTO_FUND` is honored as an opt-out; it must not bypass CI/TTY guards in +/// shared transport code that may be embedded inside long-running RPC daemons. +fn interactive_tempo_fund_allowed( + no_auto_fund: Option<&str>, + in_ci: bool, + stdin_is_terminal: bool, + stderr_is_terminal: bool, +) -> bool { + if no_auto_fund.is_some_and(|v| { + !(v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off")) + }) { + return false; + } + + if in_ci { + return false; + } + + stdin_is_terminal && stderr_is_terminal +} + +fn can_run_interactive_tempo_fund() -> bool { + if cfg!(test) { + return false; + } + + interactive_tempo_fund_allowed( + std::env::var("FOUNDRY_MPP_NO_AUTO_FUND").ok().as_deref(), + std::env::var_os("CI").is_some(), + std::io::stdin().is_terminal(), + std::io::stderr().is_terminal(), + ) +} + +fn tempo_bin() -> String { + std::env::var("TEMPO_BIN").unwrap_or_else(|_| "tempo".to_string()) +} + +async fn run_interactive_tempo_fund(ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + + let tempo = tempo_bin(); + let mut args = vec!["wallet".to_string(), "fund".to_string()]; + if let Some(address) = ctx.wallet_address { + args.push("--address".to_string()); + args.push(address.to_string()); + } + if let Some(network) = ctx.network() { + args.push("--network".to_string()); + args.push(network); + } + + tracing::warn!( + token = ?ctx.token, + chain_id = ?ctx.chain_id, + "MPP payment could not be funded; opening `tempo wallet fund`" + ); + + let status = tokio::task::spawn_blocking(move || { + Command::new(tempo) + .args(args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + }) + .await + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to join tempo wallet fund process: {e}" + ))) + })? + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to run `tempo wallet fund`: {e}{}", + tempo_wallet_fund_help(ctx) + ))) + })?; + + if status.success() { + Ok(true) + } else { + Err(TransportErrorKind::custom(std::io::Error::other(format!( + "`tempo wallet fund` exited with status {status}{}", + tempo_wallet_fund_help(ctx) + )))) + } +} + +/// Single-attempt guard around [`run_interactive_tempo_fund`]. +/// +/// Ensures that for one logical request we launch `tempo wallet fund` at most +/// once, regardless of how many recovery paths (`do_request`, `pay_and_retry`, +/// `handle_response_or_retry_after_fund`, ...) attempt it. +async fn maybe_auto_fund(used: &AtomicBool, ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + if used.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Ok(false); + } + run_interactive_tempo_fund(ctx).await +} + +/// Returns true iff a 402 response carries a structured insufficient-balance +/// problem (RFC 9457 `PaymentErrorDetails`). +/// +/// We deliberately do **not** match on free-text body content or on generic +/// `verification-failed` problem types, as those have many non-funding causes +/// (bad signature, replay, expired challenge, clock skew, key provisioning, +/// malformed auth, ...). +fn should_suggest_tempo_fund(status: StatusCode, body: &[u8]) -> bool { + if status != StatusCode::PAYMENT_REQUIRED { + return false; + } + let Ok(problem) = serde_json::from_slice::(body) else { + return false; + }; + problem.problem_type.ends_with("/insufficient-balance") +} + +fn format_mpp_payment_failure( + error: impl fmt::Display, + ctx: &FundingContext, + suggest_fund: bool, +) -> String { + let message = error.to_string(); + if suggest_fund { + format!("MPP payment failed: {message}{}", tempo_wallet_fund_help(ctx)) + } else { + format!("MPP payment failed: {message}") + } +} + /// Process-wide payment serialization locks, keyed by origin URL. /// /// Created eagerly so the lock exists before the first provider init, /// preventing concurrent first-402 races. -static GLOBAL_PAY_LOCKS: OnceLock>>>> = - OnceLock::new(); +static GLOBAL_PAY_LOCKS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); /// Production transport: lazily discovers MPP keys from the Tempo wallet on /// first 402 response. @@ -75,24 +262,21 @@ pub type LazyMppHttpTransport = MppHttpTransport; /// Tempo wallet configuration on first use. #[derive(Clone, Debug)] pub struct LazySessionProvider { - inner: std::sync::Arc>>, + inner: Arc>>, /// Eagerly-created, process-wide payment serialization lock for this origin. - pay_lock: std::sync::Arc>, + pay_lock: Arc>, origin: String, } impl LazySessionProvider { pub(super) fn new(origin: String) -> Self { - let pay_lock = { - let global = GLOBAL_PAY_LOCKS.get_or_init(|| Mutex::new(HashMap::new())); - global - .lock() - .unwrap() - .entry(origin.clone()) - .or_insert_with(|| std::sync::Arc::new(tokio::sync::Mutex::new(()))) - .clone() - }; - Self { inner: std::sync::Arc::new(Mutex::new(None)), pay_lock, origin } + let pay_lock = GLOBAL_PAY_LOCKS + .lock() + .unwrap() + .entry(origin.clone()) + .or_insert_with(|| Arc::new(AsyncMutex::new(()))) + .clone(); + Self { inner: Arc::new(Mutex::new(None)), pay_lock, origin } } fn set_key_provisioned(&self, provisioned: bool) { @@ -125,6 +309,14 @@ impl LazySessionProvider { } } + /// Drop the cached `SessionProvider` so the next `get_or_init` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry, so a long-lived transport doesn't keep paying with + /// the superseded key. + fn invalidate(&self) { + *self.inner.lock().unwrap() = None; + } + pub(super) fn get_or_init(&self, opts: DiscoverOptions) -> TransportResult { let mut guard = self.inner.lock().unwrap(); if let Some(ref provider) = *guard { @@ -132,18 +324,20 @@ impl LazySessionProvider { } let config = discover_mpp_config(opts).ok_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "RPC endpoint returned HTTP 402 Payment Required. \ This endpoint requires payment via the Machine Payments Protocol (MPP).\n\n\ - To configure MPP, install the Tempo wallet CLI and create a key:\n\ - \n curl -sSL https://tempo.xyz/install.sh | bash\ - \n tempo wallet login\ + Authorize an access key against your Tempo wallet:\n\ + \n cast tempo login\ + \n\nIn headless environments, pass `--no-browser` to print the authorization \ + URL instead of launching a browser:\n\ + \n cast tempo login --no-browser\ \n\nSee https://docs.tempo.xyz for more information.", )) })?; let signer: mpp::PrivateKeySigner = config.key.parse().map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("invalid MPP key: {e}"))) + TransportErrorKind::custom(io::Error::other(format!("invalid MPP key: {e}"))) })?; let signing_mode = if let Some(wallet) = config.wallet_address { @@ -152,7 +346,7 @@ impl LazySessionProvider { .as_ref() .map(|hex_str| { crate::tempo::decode_key_authorization(hex_str).map(Box::new).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "invalid MPP key_authorization: {e}" ))) }) @@ -223,6 +417,17 @@ where P::Provider: Send + Sync + 'static, { async fn do_request(self, req: RequestPacket) -> TransportResult { + // Per-request guard: launch `tempo wallet fund` at most once for one + // logical request, regardless of how many recovery paths attempt it. + let auto_fund_used = AtomicBool::new(false); + self.do_request_inner(req, &auto_fund_used).await + } + + async fn do_request_inner( + self, + req: RequestPacket, + auto_fund_used: &AtomicBool, + ) -> TransportResult { let body = serde_json::to_vec(&req).map_err(TransportErrorKind::custom)?; let headers = req.headers(); @@ -246,15 +451,53 @@ where // held until the retry response is fully handled. let _pay_guard = self.provider.lock_pay().await; - let (resolved, challenge) = Self::select_challenge(&resp, &self.provider)?; + // No local key for any offered challenge → run device-code flow, + // invalidate the cached provider, and fetch a fresh 402 (the original + // may have expired during the browser/passkey flow). + let (resolved, challenge) = + if let Some(chain_id) = tempo_chain_needing_auth(&self.url, &resp) { + debug!(chain_id, "launching wallet.tempo authorization"); + let cfg = crate::tempo::EnsureAccessKeyConfig::from_env(chain_id); + crate::tempo::ensure_access_key(cfg).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "tempo access key authorization failed: {e}" + ))) + })?; + self.provider.invalidate_cached_provider(); + self.fetch_fresh_challenge(&headers, &body).await? + } else { + Self::select_challenge(&resp, &self.provider)? + }; + let funding_ctx = self.provider.funding_context(&challenge); debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP 402 challenge, paying"); - let credential = resolved.pay(&challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let credential = match resolved.pay(&challenge).await { + Ok(credential) => credential, + Err(e) => { + // Only the explicit `InsufficientBalance` variant is treated as + // a fundable error. Any other failure must surface unchanged so + // we don't mask payment/protocol issues behind a fund prompt. + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + resolved.pay(&challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); TransportErrorKind::custom(std::io::Error::other(format!( "failed to format MPP credential: {e}" ))) @@ -286,9 +529,20 @@ where self.provider.commit_topup_and_track_voucher(); let resolved = self.provider.resolve()?; - let voucher_resp = self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(voucher_resp).await; + let voucher_resp = + self.pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used).await?; + + // Route the voucher response through the funding-aware handler so + // a final 402 here also gets the fund retry / contextual help. + let result = self + .handle_response_or_retry_after_fund( + voucher_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -304,7 +558,7 @@ where self.provider.rollback_pending(); self.provider.clear_channels(); - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "MPP channel not found on server (410 Gone). \ The server may have restarted or the channel was closed externally.\n\ Local channel state has been cleared. Re-run to open a new channel.", @@ -333,10 +587,19 @@ where debug!("MPP voucher stale, retrying with fresh voucher"); let resolved = self.provider.resolve()?; if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - let final_resp = - self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.flush_pending(); } else { @@ -372,10 +635,19 @@ where let (resolved, fresh_challenge) = self.fetch_fresh_challenge(&headers, &body).await?; - let final_resp = - self.pay_and_retry(&fresh_challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -386,9 +658,40 @@ where } self.provider.rollback_pending(); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) + && maybe_auto_fund(auto_fund_used, &funding_ctx).await? + { + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + let mut error_text = format!("{retry_text}{diagnostics}"); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) { + error_text.push_str(&tempo_wallet_fund_help(&funding_ctx)); + } return Err(TransportErrorKind::http_error( StatusCode::PAYMENT_REQUIRED.as_u16(), - format!("{retry_text}{diagnostics}"), + error_text, )); } @@ -409,15 +712,32 @@ where provider: &P::Provider, headers: &reqwest::header::HeaderMap, body: &[u8], + auto_fund_used: &AtomicBool, ) -> TransportResult { - let credential = provider.pay(challenge).await.map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let funding_ctx = self.provider.funding_context(challenge); + let credential = match provider.pay(challenge).await { + Ok(credential) => credential, + Err(e) => { + self.provider.rollback_pending(); + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + provider.pay(challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "failed to format MPP credential: {e}" ))) })?; @@ -437,6 +757,41 @@ where }) } + async fn handle_response_or_retry_after_fund( + &self, + resp: reqwest::Response, + headers: &reqwest::header::HeaderMap, + body: &[u8], + funding_ctx: &FundingContext, + auto_fund_used: &AtomicBool, + ) -> TransportResult { + if resp.status() != StatusCode::PAYMENT_REQUIRED { + return Self::handle_response_with_funding(resp, Some(funding_ctx)).await; + } + + let diagnostics = format_http_diagnostics(resp.headers()); + let status = resp.status(); + let resp_body = resp.bytes().await.map_err(TransportErrorKind::custom)?; + + if should_suggest_tempo_fund(status, &resp_body) + && maybe_auto_fund(auto_fund_used, funding_ctx).await? + { + self.provider.rollback_pending(); + + let (resolved, fresh_challenge) = self.fetch_fresh_challenge(headers, body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, headers, body, auto_fund_used) + .await?; + return Self::handle_response_with_funding(final_resp, Some(funding_ctx)).await; + } + + let mut error_text = format!("{}{diagnostics}", String::from_utf8_lossy(&resp_body)); + if should_suggest_tempo_fund(status, &resp_body) { + error_text.push_str(&tempo_wallet_fund_help(funding_ctx)); + } + Err(TransportErrorKind::http_error(status.as_u16(), error_text)) + } + /// Fetch a fresh 402 challenge from the server (unauthenticated request). /// /// Returns `Ok(Some((provider, challenge)))` if the server returns a 402 @@ -462,7 +817,7 @@ where // Non-402 → return whatever the server sent (could be success or error). let result = Self::handle_response(fresh_resp).await; return Err(result.err().unwrap_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "unexpected success on unauthenticated fresh probe", )) })); @@ -477,25 +832,14 @@ where resp: &reqwest::Response, provider: &P, ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { - let www_auth_values: Vec<&str> = resp - .headers() - .get_all(WWW_AUTHENTICATE_HEADER) - .iter() - .filter_map(|v| v.to_str().ok()) - .collect(); - - if www_auth_values.is_empty() { - return Err(TransportErrorKind::custom(std::io::Error::other(format!( + let challenges = parse_challenges(resp); + if challenges.is_empty() && resp.headers().get(WWW_AUTHENTICATE_HEADER).is_none() { + return Err(TransportErrorKind::custom(io::Error::other(format!( "402 response missing WWW-Authenticate header{}", format_http_diagnostics(resp.headers()) )))); } - let challenges: Vec<_> = parse_www_authenticate_all(www_auth_values) - .into_iter() - .filter_map(|r| r.ok()) - .collect(); - let mut last_resolve_err: Option = None; let resolved_pair = challenges.iter().find_map(|c| { let (chain_id, currency) = extract_challenge_chain_and_currency(c); @@ -515,7 +859,7 @@ where } let offered: Vec<_> = challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "no supported MPP challenge; server offered [{}]", offered.join(", "), ))) @@ -523,6 +867,17 @@ where } async fn handle_response(resp: reqwest::Response) -> TransportResult { + Self::handle_response_with_funding(resp, None).await + } + + /// Like [`Self::handle_response`] but, when an unsuccessful 402 looks like a + /// fundable error, appends actionable `tempo wallet fund` help that uses + /// the per-request `FundingContext` (so the suggested command includes + /// `--address` and `--network` when known). + async fn handle_response_with_funding( + resp: reqwest::Response, + funding_ctx: Option<&FundingContext>, + ) -> TransportResult { let status = resp.status(); debug!(%status, "received response from MPP transport"); let diagnostics = format_http_diagnostics(resp.headers()); @@ -536,10 +891,19 @@ where } if !status.is_success() { - return Err(TransportErrorKind::http_error( - status.as_u16(), - format!("{}{diagnostics}", String::from_utf8_lossy(&body)), - )); + let mut body_text = format!("{}{diagnostics}", String::from_utf8_lossy(&body)); + if should_suggest_tempo_fund(status, &body) { + let default_ctx; + let ctx = match funding_ctx { + Some(c) => c, + None => { + default_ctx = FundingContext::default(); + &default_ctx + } + }; + body_text.push_str(&tempo_wallet_fund_help(ctx)); + } + return Err(TransportErrorKind::http_error(status.as_u16(), body_text)); } serde_json::from_slice(&body) @@ -547,6 +911,57 @@ where } } +/// Returns `Some(chain_id)` when a 402 response should trigger the +/// `wallet.tempo.xyz` device-code authorization flow. +/// +/// Conditions: known Tempo endpoint, interactive (TTY, not `CI`), and no +/// offered Tempo challenge resolves against a local key on `(chain, currency)`. +/// The picked chain matches the first unresolved challenge — same iteration +/// order [`MppHttpTransport::select_challenge`] uses. +fn tempo_chain_needing_auth(url: &Url, resp: &reqwest::Response) -> Option { + if !io::stderr().is_terminal() || env::var_os("CI").is_some() { + return None; + } + pick_chain_needing_auth(url, &parse_challenges(resp)) +} + +/// Extract all parseable MPP challenges from a 402 response's `WWW-Authenticate` headers. +fn parse_challenges(resp: &reqwest::Response) -> Vec { + let values: Vec<&str> = resp + .headers() + .get_all(WWW_AUTHENTICATE_HEADER) + .iter() + .filter_map(|v| v.to_str().ok()) + .collect(); + parse_www_authenticate_all(values).into_iter().filter_map(|r| r.ok()).collect() +} + +/// Inner logic of [`tempo_chain_needing_auth`], factored out for testing. +fn pick_chain_needing_auth( + url: &Url, + challenges: &[mpp::protocol::core::PaymentChallenge], +) -> Option { + if !crate::tempo::is_known_tempo_endpoint(url) { + return None; + } + + let tempo_challenges: Vec<_> = + challenges.iter().filter(|c| c.method.as_str() == "tempo").collect(); + + // If any challenge already resolves with a local key, no auth needed. + let any_resolvable = tempo_challenges.iter().any(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + super::keys::discover_mpp_config(super::keys::DiscoverOptions { chain_id, currency }) + .is_some() + }); + if any_resolvable { + return None; + } + + tempo_challenges.iter().find_map(|c| extract_challenge_chain_and_currency(c).0) +} + /// Extract `(chainId, currency)` from a parsed MPP challenge. pub(super) fn extract_challenge_chain_and_currency( c: &mpp::protocol::core::PaymentChallenge, @@ -576,10 +991,28 @@ pub(crate) trait ResolveProvider { fn flush_pending(&self) {} fn rollback_pending(&self) {} fn commit_topup_and_track_voucher(&self) {} + /// Drop any cached payment provider so the next `resolve_for` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry. + fn invalidate_cached_provider(&self) {} + fn funding_wallet_address(&self) -> Option { + None + } + fn funding_chain_id(&self) -> Option { + None + } + fn funding_context(&self, challenge: &mpp::protocol::core::PaymentChallenge) -> FundingContext { + let (challenge_chain_id, token) = extract_challenge_chain_and_currency(challenge); + FundingContext { + wallet_address: self.funding_wallet_address(), + token, + chain_id: challenge_chain_id.or_else(|| self.funding_chain_id()).map(Chain::from_id), + } + } /// Acquire the payment serialization lock. The returned guard must be held /// across the entire 402 → pay → retry → response cycle to prevent /// concurrent channel opens and colliding expiring-nonce transactions. - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn lock_pay(&self) -> impl Future>> + Send { async { None } } } @@ -599,7 +1032,7 @@ impl ResolveProvider for LazySessionProvider { // regardless of opts. Re-check that the provider's key is compatible // with this challenge's chain/currency. if !provider.matches_challenge(opts.chain_id, opts.currency) { - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "cached provider does not match challenge chain/currency", ))); } @@ -623,7 +1056,16 @@ impl ResolveProvider for LazySessionProvider { fn commit_topup_and_track_voucher(&self) { Self::commit_topup_and_track_voucher(self) } - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn invalidate_cached_provider(&self) { + Self::invalidate(self) + } + fn funding_wallet_address(&self) -> Option { + self.inner.lock().unwrap().as_ref().map(|p| p.funding_wallet_address()) + } + fn funding_chain_id(&self) -> Option { + self.inner.lock().unwrap().as_ref().and_then(|p| p.key_chain_id()) + } + fn lock_pay(&self) -> impl Future>> + Send { let lock = self.pay_lock.clone(); async move { Some(lock.lock_owned().await) } } @@ -685,7 +1127,7 @@ mod tests { fn pay( &self, challenge: &PaymentChallenge, - ) -> impl std::future::Future> + Send { + ) -> impl Future> + Send { let echo = challenge.to_echo(); async move { Ok(PaymentCredential::with_source( @@ -697,6 +1139,21 @@ mod tests { } } + #[derive(Clone, Debug)] + struct InsufficientBalanceProvider; + + impl PaymentProvider for InsufficientBalanceProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + async fn pay(&self, _challenge: &PaymentChallenge) -> Result { + Err(MppError::InsufficientBalance(Some( + "wallet has 0 pathUSD but needs 100000".to_string(), + ))) + } + } + fn test_challenge() -> (PaymentChallenge, String) { let request = Base64UrlJson::from_value(&serde_json::json!({ "amount": "1000", @@ -853,8 +1310,238 @@ mod tests { handle.abort(); } + #[tokio::test] + async fn test_mpp_transport_payment_failure_suggests_tempo_wallet_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move || { + let www_auth = www_auth.clone(); + async move { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required", + ) + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + InsufficientBalanceProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_retry_402_insufficient_balance_suggests_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail( + "Insufficient pathUSD balance: have 0, need 100000", + ), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("InsufficientBalanceError"), "got: {msg}"); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + /// Generic `verification-failed` has many non-funding causes (bad signature, + /// replay, expired challenge, clock skew, ...). The transport must surface + /// the original error verbatim and must NOT add a "fund your wallet" hint. + #[tokio::test] + async fn test_mpp_transport_final_402_verification_failed_does_not_suggest_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Verification Failed"), "got: {msg}"); + assert!( + !msg.contains("Tempo wallet payment could not be funded"), + "verification-failed must not be classified as fundable; got: {msg}" + ); + + handle.abort(); + } + + // --- Classifier unit tests -------------------------------------------- + + #[test] + fn classifier_only_triggers_on_explicit_insufficient_balance_problem() { + // explicit insufficient-balance → true + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail("Insufficient pathUSD balance"), + ) + .unwrap(); + assert!(should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_verification_failed() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_unrelated_text_with_balance_words() { + // Free-text 402 body that just happens to mention the word "balance" + // must NOT trigger the fund suggestion (no structured problem details). + let body = + b"402 Payment Required: server could not balance ledger entries; insufficient inputs."; + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, body)); + } + + #[test] + fn classifier_does_not_trigger_outside_402() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_detail("Insufficient balance"), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::INTERNAL_SERVER_ERROR, &body)); + assert!(!should_suggest_tempo_fund(StatusCode::OK, &body)); + } + + #[test] + fn fund_help_includes_address_and_network_for_known_chain() { + let ctx = FundingContext { + wallet_address: Some("0x000000000000000000000000000000000000dEaD".parse().unwrap()), + token: Some("0x20c0".to_string()), + chain_id: Some(Chain::from_id(42431)), + }; + let help = tempo_wallet_fund_help(&ctx); + assert!(help.contains("--address 0x"), "missing --address: {help}"); + assert!(help.contains("--network tempo-moderato"), "missing --network: {help}"); + assert!(help.contains("--no-browser"), "missing --no-browser: {help}"); + assert!(help.contains("Requested payment token: 0x20c0"), "missing token: {help}"); + + let mainnet = FundingContext { chain_id: Some(Chain::from_id(4217)), ..ctx }; + let help2 = tempo_wallet_fund_help(&mainnet); + assert!(help2.contains("--network tempo"), "missing tempo network: {help2}"); + } + + #[test] + fn auto_fund_policy_blocks_in_ci_and_non_tty() { + assert!(!interactive_tempo_fund_allowed(Some("1"), true, true, true), "must not run in CI"); + assert!( + interactive_tempo_fund_allowed(Some("0"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=0 must not disable" + ); + assert!( + interactive_tempo_fund_allowed(Some("false"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=false must not disable" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, false, true), + "stdin must be a terminal" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, true, false), + "stderr must be a terminal" + ); + assert!(!interactive_tempo_fund_allowed(Some("1"), false, true, true)); + assert!(!interactive_tempo_fund_allowed(Some("true"), false, true, true)); + assert!(interactive_tempo_fund_allowed(None, false, true, true)); + } + #[tokio::test] async fn test_plain_http_402_shows_mpp_setup_instructions() { + let _g = crate::tempo::test_env_mutex().lock().await; let (_, www_auth) = test_challenge(); let app = axum::Router::new().route( @@ -920,6 +1607,32 @@ mod tests { ); } + /// `invalidate_cached_provider` clears the cache so the next + /// `get_or_init` re-runs discovery — the path `do_request` takes after + /// `ensure_access_key` writes a fresh `keys.toml` entry. + #[tokio::test] + async fn lazy_session_provider_invalidate_clears_cache() { + let _g = crate::tempo::test_env_mutex().lock().await; + // TEMPO_PRIVATE_KEY lets discovery succeed without a keys.toml. + let key_hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + unsafe { + std::env::set_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV, key_hex); + std::env::remove_var(crate::tempo::TEMPO_HOME_ENV); + } + + let lazy = LazySessionProvider::new("https://rpc.example.com".into()); + let _ = lazy.get_or_init(Default::default()).expect("discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected provider to be cached"); + + ResolveProvider::invalidate_cached_provider(&lazy); + assert!(lazy.inner.lock().unwrap().is_none(), "expected cache to be cleared"); + + let _ = lazy.get_or_init(Default::default()).expect("re-discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected re-init to repopulate cache"); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV) }; + } + #[test] fn challenge_chain_and_currency_extraction() { let extract = |headers: Vec<&str>| -> Vec<(Option, Option)> { @@ -955,4 +1668,73 @@ mod tests { ); assert_eq!(extract(vec![&no_details]), vec![(None, Some("0x20c0".into()))]); } + + /// Auth must trigger when a key matches the chain but not the currency. + #[test] + fn pick_chain_needing_auth_currency_aware() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let dir = tempfile::tempdir().unwrap(); + let wallet = dir.path().join("wallet"); + std::fs::create_dir_all(&wallet).unwrap(); + std::fs::write( + wallet.join("keys.toml"), + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +chain_id = 4217 + +[[keys.limits]] +currency = "0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +limit = "1000" +"#, + ) + .unwrap(); + unsafe { + std::env::set_var(crate::tempo::TEMPO_HOME_ENV, dir.path()); + std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV); + } + + let url = Url::parse("https://rpc.mpp.tempo.xyz").unwrap(); + let mk = |currency: &str| -> PaymentChallenge { + PaymentChallenge { + id: "x".into(), + realm: "api".into(), + method: MethodName::new("tempo"), + intent: IntentName::new("charge"), + request: Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1", + "currency": currency, + "recipient": "0xabc", + "methodDetails": { "chainId": 4217 } + })) + .unwrap(), + expires: None, + description: None, + digest: None, + opaque: None, + } + }; + + // Currency mismatch → auth needed. + let mismatched = mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + assert_eq!(pick_chain_needing_auth(&url, &[mismatched]), Some(4217)); + + // Currency match → no auth. + let matched = mk("0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert_eq!(pick_chain_needing_auth(&url, &[matched]), None); + + // Non-Tempo host → never triggers, even without a key. + let stripe_url = Url::parse("https://api.stripe.com").unwrap(); + assert_eq!( + pick_chain_needing_auth( + &stripe_url, + &[mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")] + ), + None, + ); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_HOME_ENV) }; + } } diff --git a/crates/common/src/provider/mpp/ws.rs b/crates/common/src/provider/mpp/ws.rs index f631d0b08a2fc..69aef7d4f4cbc 100644 --- a/crates/common/src/provider/mpp/ws.rs +++ b/crates/common/src/provider/mpp/ws.rs @@ -378,6 +378,8 @@ mod tests { /// MPP server sends challenge → client pays → server sends receipt. #[tokio::test] async fn test_ws_mpp_challenge_credential_receipt() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); @@ -452,6 +454,8 @@ mod tests { /// MPP server sends challenge, client pays, server closes → rollback. #[tokio::test] async fn test_ws_mpp_rollback_on_post_pay_close() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 7db1ebd1b3f91..f59a2efa75b8e 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -36,7 +36,11 @@ fn is_known_mpp_endpoint(url: &Url) -> bool { /// Only meant to be used internally by [RuntimeTransport]. #[derive(Clone, Debug)] pub enum InnerTransport { - /// HTTP transport with lazy MPP 402 handling + /// HTTP transport with lazy MPP 402 handling. + /// + /// For known Tempo endpoints, the MPP layer additionally runs the + /// `wallet.tempo.xyz` device-code flow on a 402 when no local access key + /// is configured (see [`crate::tempo::ensure_access_key`]). Http(LazyMppHttpTransport), /// WebSocket transport Ws(PubSubFrontend), diff --git a/crates/common/src/tempo/auth.rs b/crates/common/src/tempo/auth.rs new file mode 100644 index 0000000000000..d79306cfb74f2 --- /dev/null +++ b/crates/common/src/tempo/auth.rs @@ -0,0 +1,494 @@ +//! Tempo wallet device-code authorization flow. +//! +//! Implements the CLI side of the tempoxyz/accounts `cli-auth` device-code +//! protocol: generates a local secp256k1 access key, creates a PKCE-protected +//! device code, opens `wallet.tempo.xyz/cli-auth?code=` in the browser, +//! polls until the user authorizes the key on their passkey wallet, and writes +//! the resulting `keyAuthorization` to `~/.tempo/wallet/keys.toml`. + +use crate::tempo::{ + KeyEntry, KeyType, StoredTokenLimit, WalletType, decode_key_authorization, upsert_key_entry, +}; +use alloy_primitives::{Address, B256, hex}; +use alloy_signer_local::PrivateKeySigner; +use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +#[cfg(any(unix, windows))] +use std::process::Command; +use std::{ + env, + sync::LazyLock, + time::{Duration, Instant}, +}; +use tempo_primitives::transaction::{SignatureType, SignedKeyAuthorization}; +use tokio::sync::Mutex; + +/// Default device-code service URL (production wallet.tempo.xyz). +const DEFAULT_CLI_AUTH_URL: &str = "https://wallet.tempo.xyz/cli-auth"; + +/// Returns `true` if `url`'s host is `tempo.xyz` or a subdomain of it. +pub(crate) fn is_known_tempo_endpoint(url: &url::Url) -> bool { + url.host_str().is_some_and(|host| host == "tempo.xyz" || host.ends_with(".tempo.xyz")) +} + +/// Env var to override the device-code service URL (for tests / staging). +const TEMPO_CLI_AUTH_URL_ENV: &str = "TEMPO_CLI_AUTH_URL"; + +const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2); +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); + +/// Per-process serialization of concurrent `ensure_access_key` calls. +/// +/// Prevents two `cast` invocations in the same process from racing two browser +/// popups for the same chain. +static AUTH_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Configuration for [`ensure_access_key`]. +#[derive(Clone, Debug)] +pub struct EnsureAccessKeyConfig { + /// Chain ID the access key is being authorized for. + pub chain_id: u64, + /// Device-code service base URL. Defaults to [`DEFAULT_CLI_AUTH_URL`]. + pub(crate) service_url: String, + /// Poll interval. + pub(crate) poll_interval: Duration, + /// Total timeout for the authorization flow. + pub(crate) timeout: Duration, + /// If `true`, print the authorization URL to stderr instead of opening a + /// browser. + pub no_browser: bool, +} + +impl EnsureAccessKeyConfig { + /// Build a config from the environment for the given chain. + /// + /// `no_browser` defaults to `true` under `CI`; callers (e.g. `cast tempo + /// login --no-browser`) may override it. + pub fn from_env(chain_id: u64) -> Self { + Self { + chain_id, + service_url: env::var(TEMPO_CLI_AUTH_URL_ENV) + .unwrap_or_else(|_| DEFAULT_CLI_AUTH_URL.to_string()), + poll_interval: DEFAULT_POLL_INTERVAL, + timeout: DEFAULT_TIMEOUT, + no_browser: env::var_os("CI").is_some(), + } + } +} + +/// Open `url` via the OS default browser handler. On platforms without a known +/// opener, this is a no-op (the URL is still printed by [`ensure_access_key`]). +fn open_browser(_url: &str) { + #[cfg(target_os = "macos")] + let _ = Command::new("open").arg(_url).spawn(); + #[cfg(target_os = "windows")] + let _ = Command::new("cmd").args(["/c", "start", "", _url]).spawn(); + #[cfg(all(unix, not(target_os = "macos")))] + let _ = Command::new("xdg-open").arg(_url).spawn(); +} + +/// Result of [`ensure_access_key`]. +#[derive(Debug, Clone)] +pub struct AccessKeyOutcome { + pub wallet_address: Address, + pub key_address: Address, + pub chain_id: u64, +} + +/// Run the device-code flow, persist the resulting key to `keys.toml`, and +/// return the new entry's identifying fields. +pub async fn ensure_access_key(cfg: EnsureAccessKeyConfig) -> Result { + let _guard = AUTH_LOCK.lock().await; + + let signer = PrivateKeySigner::random(); + let key_address = signer.address(); + // The server requires uncompressed SEC1 (65-byte `0x04 || X || Y`); the + // default `to_sec1_bytes()` would emit the compressed 33-byte form. + let pub_key_hex = format!( + "0x{}", + hex::encode(signer.credential().verifying_key().to_encoded_point(false).as_bytes()), + ); + + let code_verifier = random_code_verifier(); + let client = reqwest::Client::builder().timeout(Duration::from_secs(30)).build()?; + let service = cfg.service_url.trim_end_matches('/'); + + let create_req = CreateCodeRequest { + chain_id: cfg.chain_id, + code_challenge: sha256_b64url(&code_verifier), + key_type: "secp256k1", + pub_key: pub_key_hex, + }; + let code = create_code_with_retry(&client, service, &create_req, cfg.timeout).await?; + + let browser_url = format!("{service}?code={code}"); + if cfg.no_browser { + let _ = crate::sh_eprintln!("Open this URL to authorize: {browser_url}"); + } else { + let _ = crate::sh_eprintln!( + "Opening wallet.tempo to authorize an access key…\n {browser_url}" + ); + open_browser(&browser_url); + } + + let poll = PollRequest { code_verifier }; + let started = Instant::now(); + loop { + // Retry transient network/5xx/429 failures within `cfg.timeout`. + let send_res = client.post(format!("{service}/poll/{code}")).json(&poll).send().await; + + let resp = match send_res { + Ok(r) => r, + Err(e) if is_transient_error(&e) && started.elapsed() < cfg.timeout => { + tracing::debug!(error = %e, "transient error polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + Err(e) => return Err(e.into()), + }; + + let status = resp.status(); + if !status.is_success() { + if is_transient_status(status) && started.elapsed() < cfg.timeout { + tracing::debug!(%status, "transient HTTP status polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code poll failed ({status}): {body}"); + } + + let body: PollResponse = resp.json().await?; + match body { + PollResponse::Pending => { + if started.elapsed() > cfg.timeout { + eyre::bail!("timed out waiting for wallet authorization (code {code})"); + } + tokio::time::sleep(cfg.poll_interval).await; + } + PollResponse::Expired => { + eyre::bail!("device code {code} expired before authorization"); + } + PollResponse::Authorized { account_address, key_authorization } => { + let hex_str = key_authorization.ok_or_else(|| { + eyre::eyre!("wallet authorized response missing key_authorization") + })?; + let signed: SignedKeyAuthorization = decode_key_authorization(&hex_str)?; + // Reject mismatches before persisting — an unusable keys.toml + // entry would silently break the next 402 retry. + if signed.authorization.key_id != key_address { + eyre::bail!( + "wallet authorized key {} but the locally generated key is {}", + signed.authorization.key_id, + key_address, + ); + } + if signed.authorization.chain_id != cfg.chain_id { + eyre::bail!( + "wallet authorized chain {} but {} was requested", + signed.authorization.chain_id, + cfg.chain_id, + ); + } + if signed.authorization.key_type != SignatureType::Secp256k1 { + eyre::bail!( + "wallet returned keyType {:?} but secp256k1 was requested", + signed.authorization.key_type, + ); + } + let chain_id = signed.authorization.chain_id; + let key_authorization = + if hex_str.starts_with("0x") { hex_str } else { format!("0x{hex_str}") }; + let entry = KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: account_address, + chain_id, + key_type: match signed.authorization.key_type { + SignatureType::P256 => KeyType::P256, + SignatureType::WebAuthn => KeyType::WebAuthn, + _ => KeyType::Secp256k1, + }, + key_address: Some(key_address), + key: Some(format!("0x{}", hex::encode(signer.to_bytes()))), + key_authorization: Some(key_authorization), + expiry: signed.authorization.expiry.map(|n| n.get()), + limits: signed + .authorization + .limits + .unwrap_or_default() + .into_iter() + .map(|l| StoredTokenLimit { currency: l.token, limit: l.limit.to_string() }) + .collect(), + }; + upsert_key_entry(entry)?; + return Ok(AccessKeyOutcome { + wallet_address: account_address, + key_address, + chain_id, + }); + } + } + } +} + +fn is_transient_error(err: &reqwest::Error) -> bool { + err.is_timeout() || err.is_connect() || err.is_request() +} + +fn is_transient_status(status: reqwest::StatusCode) -> bool { + status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS +} + +/// POST `/code` with exponential backoff on transient errors, bounded by `timeout`. +async fn create_code_with_retry( + client: &reqwest::Client, + service: &str, + req: &CreateCodeRequest, + timeout: Duration, +) -> Result { + let started = Instant::now(); + let mut backoff = Duration::from_millis(500); + loop { + let send_res = client.post(format!("{service}/code")).json(req).send().await; + + match send_res { + Ok(resp) => { + let status = resp.status(); + if status.is_success() { + let CreateCodeResponse { code } = resp.json().await?; + return Ok(code); + } + if is_transient_status(status) && started.elapsed() < timeout { + tracing::debug!(%status, "transient HTTP status creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code create failed ({status}): {body}"); + } + Err(e) if is_transient_error(&e) && started.elapsed() < timeout => { + tracing::debug!(error = %e, "transient error creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + } + Err(e) => return Err(e.into()), + } + } +} + +fn random_code_verifier() -> String { + let bytes = B256::random(); + URL_SAFE_NO_PAD.encode(bytes.as_slice()) +} + +fn sha256_b64url(input: &str) -> String { + let digest = Sha256::digest(input.as_bytes()); + URL_SAFE_NO_PAD.encode(digest) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CreateCodeRequest { + /// `0x`-hex per the SDK schema (server accepts hex string or bigint, not a plain JSON number). + #[serde(serialize_with = "serialize_u64_hex")] + chain_id: u64, + code_challenge: String, + key_type: &'static str, + pub_key: String, +} + +fn serialize_u64_hex(v: &u64, s: S) -> std::result::Result { + s.serialize_str(&format!("0x{v:x}")) +} + +#[derive(Deserialize)] +struct CreateCodeResponse { + code: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PollRequest { + code_verifier: String, +} + +/// Matches `tempoxyz/wallet` poll response shape. +#[derive(Deserialize)] +#[serde(tag = "status", rename_all = "lowercase")] +enum PollResponse { + Pending, + Expired, + Authorized { + account_address: Address, + #[serde(default)] + key_authorization: Option, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tempo::{TEMPO_HOME_ENV, read_tempo_keys_file, test_env_mutex}; + use axum::{Json, Router, extract::State, routing::post}; + use std::sync::{Arc, Mutex}; + + #[test] + fn pkce_challenge_matches_sdk_format() { + // Vector from RFC 7636 §4.2. + let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + let challenge = sha256_b64url(verifier); + assert_eq!(challenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); + } + + /// Recover the EOA from a SEC1-encoded public key (compressed or + /// uncompressed). + fn address_from_sec1_hex(s: &str) -> Address { + let stripped = s.strip_prefix("0x").unwrap_or(s); + let bytes = hex::decode(stripped).expect("valid hex"); + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes).expect("valid SEC1 pubkey"); + Address::from_public_key(&vk) + } + + #[derive(Clone)] + struct MockState { + wallet: Arc>>, + /// Derived from the `pubKey` posted to `/code` so `/poll` can echo + /// back a matching `keyId`, like a real wallet would. + key_id: Arc>>, + /// Chain ID the mock `/poll` returns in `keyAuthorization`. + poll_chain_id: u64, + } + + async fn create_code_handler( + State(state): State, + Json(body): Json, + ) -> Json { + // Sanity: required fields present and chainId is a 0x-hex string, + // matching the SDK wire format the live server enforces. + let pub_key = body + .get("pubKey") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("pubKey missing: {body}")); + assert!(body.get("codeChallenge").is_some(), "codeChallenge missing: {body}"); + let chain_id = body.get("chainId").unwrap_or_else(|| panic!("chainId missing: {body}")); + let chain_str = chain_id + .as_str() + .unwrap_or_else(|| panic!("chainId must be string, got {chain_id}: {body}")); + assert!(chain_str.starts_with("0x"), "chainId must be 0x-hex, got {chain_str}"); + let wallet: Address = "0x0000000000000000000000000000000000000042".parse().unwrap(); + *state.wallet.lock().unwrap() = Some(wallet); + *state.key_id.lock().unwrap() = Some(address_from_sec1_hex(pub_key)); + Json(serde_json::json!({ "code": "ABCDEFGH" })) + } + + /// Build the RLP-hex `SignedKeyAuthorization` blob the live server returns + /// in the `key_authorization` field. + fn signed_key_auth_hex(chain_id: u64, key_id: Address, expiry: u64) -> String { + use alloy_rlp::Encodable; + use tempo_primitives::transaction::{KeyAuthorization, PrimitiveSignature}; + let auth = KeyAuthorization::unrestricted(chain_id, SignatureType::Secp256k1, key_id) + .with_expiry(expiry); + let sig: PrimitiveSignature = serde_json::from_value(serde_json::json!({ + "type": "secp256k1", "r": "0x0", "s": "0x0", "yParity": 0 + })) + .unwrap(); + let signed = auth.into_signed(sig); + let mut buf = Vec::new(); + signed.encode(&mut buf); + format!("0x{}", hex::encode(buf)) + } + + async fn poll_handler(State(state): State) -> Json { + let wallet = state.wallet.lock().unwrap().expect("create_code must be called first"); + let key_id = state.key_id.lock().unwrap().expect("create_code must be called first"); + Json(serde_json::json!({ + "status": "authorized", + "account_address": wallet, + "key_authorization": signed_key_auth_hex(state.poll_chain_id, key_id, 9_999_999_999), + })) + } + + /// Spawn a mock wallet.tempo server whose `/poll` echoes `poll_chain_id`. + async fn spawn_mock_wallet(poll_chain_id: u64) -> (String, tokio::task::JoinHandle<()>) { + let app = Router::new() + .route("/code", post(create_code_handler)) + .route("/poll/{code}", post(poll_handler)) + .with_state(MockState { + wallet: Arc::default(), + key_id: Arc::default(), + poll_chain_id, + }); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + (format!("http://{addr}"), handle) + } + + fn test_cfg(service_url: String) -> EnsureAccessKeyConfig { + EnsureAccessKeyConfig { + chain_id: 4217, + service_url, + poll_interval: Duration::from_millis(10), + timeout: Duration::from_secs(2), + no_browser: true, + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_happy_path_writes_keys_toml() { + // SAFETY: serialized with other tests that mutate TEMPO_HOME. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(4217).await; + let outcome = ensure_access_key(test_cfg(service_url)).await.unwrap(); + + let expected_wallet: Address = + "0x0000000000000000000000000000000000000042".parse().unwrap(); + assert_eq!(outcome.chain_id, 4217); + assert_eq!(outcome.wallet_address, expected_wallet); + + let file = read_tempo_keys_file().expect("keys.toml written"); + assert_eq!(file.keys.len(), 1); + let entry = &file.keys[0]; + assert_eq!(entry.wallet_address, outcome.wallet_address); + assert_eq!(entry.key_address, Some(outcome.key_address)); + assert_eq!(entry.chain_id, 4217); + assert_eq!(entry.expiry, Some(9_999_999_999)); + let decoded: tempo_primitives::transaction::SignedKeyAuthorization = + crate::tempo::decode_key_authorization(entry.key_authorization.as_deref().unwrap()) + .expect("RLP roundtrip"); + assert_eq!(decoded.authorization.chain_id, 4217); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_rejects_wrong_chain_id() { + // Wallet returns chain 99999 but client requested 4217 → must reject + // and persist nothing, else discovery would later fail to find a key + // for the requested chain. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(99999).await; + let err = ensure_access_key(test_cfg(service_url)).await.unwrap_err(); + assert!( + err.to_string().contains("wallet authorized chain 99999 but 4217 was requested"), + "expected chain mismatch error, got: {err}" + ); + assert!(read_tempo_keys_file().is_none_or(|f| f.keys.is_empty())); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } +} diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index 18edf39be59bd..b4f9527d1b106 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -5,8 +5,8 @@ use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; -use serde::Deserialize; -use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use std::{env, fs, io::Write, path::PathBuf}; /// Environment variable for an ephemeral Tempo private key. pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; @@ -21,7 +21,7 @@ pub const DEFAULT_TEMPO_HOME: &str = ".tempo"; pub const WALLET_KEYS_PATH: &str = "wallet/keys.toml"; /// Wallet type matching `tempo-common`'s `WalletType` enum. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum WalletType { #[default] @@ -30,7 +30,7 @@ pub enum WalletType { } /// Cryptographic key type. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum KeyType { #[default] @@ -40,7 +40,7 @@ pub enum KeyType { } /// Per-token spending limit stored in `keys.toml`. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct StoredTokenLimit { pub currency: Address, pub limit: String, @@ -50,7 +50,7 @@ pub struct StoredTokenLimit { /// /// Mirrors the fields from `tempo-common::keys::model::KeyEntry`. /// Unknown fields are ignored by serde. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct KeyEntry { /// Wallet type: "local" or "passkey". #[serde(default)] @@ -65,20 +65,20 @@ pub struct KeyEntry { #[serde(default)] pub key_type: KeyType, /// Key address (the EOA derived from the private key). - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key_address: Option
, /// Key private key, stored inline in keys.toml. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key: Option, /// RLP-encoded signed key authorization (hex string). /// Used in keychain mode to atomically provision the access key on-chain. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub key_authorization: Option, /// Expiry timestamp. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub expiry: Option, /// Per-token spending limits. - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub limits: Vec, } @@ -90,17 +90,27 @@ impl KeyEntry { } /// The top-level structure of `keys.toml`. -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct KeysFile { #[serde(default)] pub keys: Vec, } +/// Process-wide mutex used by tests that mutate `TEMPO_HOME`. +/// +/// Returns a [`tokio::sync::Mutex`] so async tests can hold it across `.await` +/// points without tripping `clippy::await_holding_lock`. +#[cfg(test)] +pub(crate) fn test_env_mutex() -> &'static tokio::sync::Mutex<()> { + static M: std::sync::OnceLock> = std::sync::OnceLock::new(); + M.get_or_init(|| tokio::sync::Mutex::new(())) +} + /// Resolve the Tempo home directory. /// /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. pub fn tempo_home() -> Option { - if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { + if let Ok(home) = env::var(TEMPO_HOME_ENV) { return Some(PathBuf::from(home)); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) @@ -122,7 +132,7 @@ pub fn read_tempo_keys_file() -> Option { return None; } - let contents = match std::fs::read_to_string(&keys_path) { + let contents = match fs::read_to_string(&keys_path) { Ok(c) => c, Err(e) => { tracing::warn!(?keys_path, %e, "failed to read tempo keys file"); @@ -148,3 +158,112 @@ pub fn decode_key_authorization(hex_str: &str) -> eyre::Result let auth = T::decode(&mut bytes.as_slice())?; Ok(auth) } + +/// Atomically upsert a [`KeyEntry`] into `keys.toml`. +/// +/// Replaces any existing entry for the same `(wallet_address, chain_id)`. +/// Each Tempo wallet has at most one active access key per chain, so a fresh +/// login always supersedes the previous entry regardless of the new key +/// address. Creates the file (and parent directories) if missing. Writes via +/// temp file + rename so a crash mid-write cannot corrupt the file. +pub(crate) fn upsert_key_entry(entry: KeyEntry) -> eyre::Result<()> { + let path = tempo_keys_path().ok_or_else(|| eyre::eyre!("could not resolve tempo home"))?; + let dir = path.parent().ok_or_else(|| eyre::eyre!("invalid keys path: {}", path.display()))?; + fs::create_dir_all(dir)?; + + let mut file = read_tempo_keys_file().unwrap_or_default(); + file.keys + .retain(|k| !(k.wallet_address == entry.wallet_address && k.chain_id == entry.chain_id)); + file.keys.push(entry); + + let body = toml::to_string_pretty(&file)?; + let contents = format!( + "# Tempo wallet keys — managed by Foundry / Tempo CLI.\n# Do not edit manually.\n\n{body}" + ); + + let mut tmp = tempfile::NamedTempFile::new_in(dir)?; + tmp.write_all(contents.as_bytes())?; + tmp.flush()?; + tmp.persist(&path).map_err(|e| eyre::eyre!("failed to persist keys.toml: {e}"))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + fn with_tempo_home(f: F) { + let tmp = tempfile::tempdir().unwrap(); + // SAFETY: process-global env access is serialized via the shared mutex. + let _g = test_env_mutex().blocking_lock(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + f(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[test] + fn upsert_replaces_matching_entry_atomically() { + with_tempo_home(|| { + let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); + let key = Address::from_str("0x0000000000000000000000000000000000000abc").unwrap(); + + let mk = |expiry: u64| KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: wallet, + chain_id: 4217, + key_type: KeyType::Secp256k1, + key_address: Some(key), + key: Some("0xdead".to_string()), + key_authorization: Some("0xbeef".to_string()), + expiry: Some(expiry), + limits: vec![], + }; + + upsert_key_entry(mk(100)).unwrap(); + upsert_key_entry(mk(200)).unwrap(); + + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 1); + assert_eq!(file.keys[0].expiry, Some(200)); + + // Different chain_id => separate entry. + let mut other = mk(300); + other.chain_id = 42431; + upsert_key_entry(other).unwrap(); + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 2); + }); + } + + #[test] + fn upsert_replaces_when_key_address_changes() { + // Re-login produces a fresh random key address; the new entry must + // supersede the old one for the same (wallet, chain), not coexist. + with_tempo_home(|| { + let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); + let old_key = Address::from_str("0x000000000000000000000000000000000000aaaa").unwrap(); + let new_key = Address::from_str("0x000000000000000000000000000000000000bbbb").unwrap(); + + let mk = |key_addr: Address| KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: wallet, + chain_id: 4217, + key_type: KeyType::Secp256k1, + key_address: Some(key_addr), + key: Some("0xdead".to_string()), + key_authorization: Some("0xbeef".to_string()), + expiry: Some(100), + limits: vec![], + }; + + upsert_key_entry(mk(old_key)).unwrap(); + upsert_key_entry(mk(new_key)).unwrap(); + + let file = read_tempo_keys_file().unwrap(); + assert_eq!(file.keys.len(), 1, "old entry must be replaced, not duplicated"); + assert_eq!(file.keys[0].key_address, Some(new_key)); + }); + } +} diff --git a/crates/common/src/tempo/mod.rs b/crates/common/src/tempo/mod.rs index ec51dc607b5ab..ef8d0212bd453 100644 --- a/crates/common/src/tempo/mod.rs +++ b/crates/common/src/tempo/mod.rs @@ -1,8 +1,24 @@ //! Tempo network utilities. +pub mod auth; + +use crate::FoundryTransactionBuilder; +use alloy_network::Network; +use alloy_primitives::{Address, B256, Signature}; +use alloy_signer::Signer; +use eyre::{Context, Result}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use std::sync::Arc; + mod keystore; + +pub(crate) use auth::is_known_tempo_endpoint; +pub use auth::{AccessKeyOutcome, EnsureAccessKeyConfig, ensure_access_key}; pub use keystore::*; +#[cfg(test)] +pub(crate) use keystore::test_env_mutex; + #[cfg(test)] mod tests; @@ -16,3 +32,173 @@ mod tests; /// /// See pub const TEMPO_BROWSER_GAS_BUFFER: u64 = 7_000; + +/// Gas sponsor configuration for Tempo fee-payer signatures. +#[derive(Clone, Debug)] +pub struct TempoSponsor { + sponsor: Address, + signer: Option>, + signature: Option, +} + +impl TempoSponsor { + pub const fn new( + sponsor: Address, + signer: Option>, + signature: Option, + ) -> Self { + Self { sponsor, signer, signature } + } + + pub const fn sponsor(&self) -> Address { + self.sponsor + } + + pub async fn attach_and_print( + &self, + tx: &mut N::TransactionRequest, + sender: Address, + ) -> Result + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if self.sponsor == sender { + eyre::bail!( + "invalid Tempo sponsorship: sponsor {} must not equal transaction sender", + self.sponsor + ); + } + + let digest = tx.compute_sponsor_hash(sender).ok_or_else(|| { + eyre::eyre!( + "failed to compute Tempo sponsor digest; make sure this is a complete Tempo AA transaction" + ) + })?; + + let preview = TempoSponsorPreview { + sponsor: self.sponsor, + fee_token: tx.fee_token(), + valid_before: tx.valid_before().map(|v| v.get()), + valid_after: tx.valid_after().map(|v| v.get()), + digest, + }; + preview.print()?; + + let signature = if let Some(signature) = self.signature { + signature + } else if let Some(signer) = &self.signer { + signer.sign_hash(&digest).await.context("failed to sign Tempo sponsor digest")? + } else { + eyre::bail!("missing Tempo sponsor signature or signer") + }; + + let recovered = signature + .recover_address_from_prehash(&digest) + .context("failed to recover Tempo sponsor signature")?; + if recovered != self.sponsor { + eyre::bail!("Tempo sponsor signature recovered {recovered}, expected {}", self.sponsor); + } + if recovered == sender { + eyre::bail!( + "invalid Tempo sponsorship: recovered fee payer {recovered} must not equal transaction sender" + ); + } + + tx.set_fee_payer_signature(signature); + Ok(preview) + } +} + +/// User-visible sponsor digest metadata for a single outgoing Tempo transaction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TempoSponsorPreview { + pub sponsor: Address, + pub fee_token: Option
, + pub valid_before: Option, + pub valid_after: Option, + pub digest: B256, +} + +impl TempoSponsorPreview { + pub fn print(&self) -> Result<()> { + crate::sh_eprintln!("Tempo sponsor: {}", self.sponsor)?; + crate::sh_eprintln!( + "Tempo fee token: {}", + self.fee_token.map_or_else(|| "network default".to_string(), |addr| addr.to_string()) + )?; + crate::sh_eprintln!( + "Tempo validity: after {}, before {}", + self.valid_after.map_or_else(|| "none".to_string(), |v| v.to_string()), + self.valid_before.map_or_else(|| "none".to_string(), |v| v.to_string()) + )?; + crate::sh_eprintln!("Tempo sponsor digest: {:?}", self.digest)?; + Ok(()) + } +} + +/// Resolves a `--tempo.sponsor-signer` URI into a Foundry wallet signer. +pub async fn resolve_tempo_sponsor_signer(spec: &str) -> Result { + let spec = spec.trim(); + let (scheme, value) = spec + .split_once("://") + .map(|(scheme, value)| (scheme.to_ascii_lowercase(), value)) + .unwrap_or_else(|| (spec.to_ascii_lowercase(), "")); + + match scheme.as_str() { + "env" => { + if value.is_empty() { + eyre::bail!("env:// sponsor signer requires an environment variable name"); + } + let private_key = std::env::var(value) + .wrap_err_with(|| format!("{value} environment variable is required"))?; + foundry_wallets::utils::create_private_key_signer(&private_key) + } + "private-key" => { + if value.is_empty() { + eyre::bail!("private-key:// sponsor signer requires a private key"); + } + foundry_wallets::utils::create_private_key_signer(value) + } + "keystore" => { + if value.is_empty() { + eyre::bail!("keystore:// sponsor signer requires a keystore path"); + } + WalletOpts { keystore_path: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "account" => { + if value.is_empty() { + eyre::bail!("account:// sponsor signer requires an account name"); + } + WalletOpts { keystore_account_name: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "ledger" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { ledger: true, raw, ..Default::default() }.signer().await + } + "trezor" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { trezor: true, raw, ..Default::default() }.signer().await + } + "aws" => WalletOpts { aws: true, ..Default::default() }.signer().await, + "gcp" => WalletOpts { gcp: true, ..Default::default() }.signer().await, + "turnkey" => WalletOpts { turnkey: true, ..Default::default() }.signer().await, + "browser" => { + eyre::bail!( + "browser:// sponsor signing is not supported by the current browser wallet API; use --tempo.sponsor-sig or another sponsor signer" + ) + } + _ => eyre::bail!( + "unsupported Tempo sponsor signer `{spec}`; expected env://VAR, keystore://PATH, account://NAME, ledger://, trezor://, aws://, gcp://, turnkey://, or private-key://KEY" + ), + } +} diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index de03cf3adc73e..aa4c971680d00 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -9,7 +9,9 @@ use alloy_primitives::{Address, B256, Signature, TxKind, U256}; use alloy_provider::Provider; use alloy_signer::Signer; use eyre::Result; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionRequest; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_primitives::{ @@ -244,6 +246,24 @@ pub trait FoundryTransactionBuilder: NetworkTransactionBuilder { /// on-chain as part of this transaction. fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} + /// Embeds key authorization before gas estimation/signing if the access key is not yet + /// provisioned on-chain. + /// + /// This mirrors the mutation performed by [`Self::sign_with_access_key`], but makes the final + /// transaction body available before fee-payer sponsor digests are computed. + fn prepare_access_key_authorization<'a>( + &'a mut self, + _provider: &'a impl Provider, + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + async { Ok(()) } + } + /// Converts a CREATE transaction into an AA-compatible call entry. /// /// Tempo AA transactions use a `calls` list instead of `to`+`input`. Must be @@ -355,6 +375,7 @@ impl FoundryTransactionBuilder for ::Transact } } +#[cfg(feature = "optimism")] impl FoundryTransactionBuilder for OpTransactionRequest { fn reset_gas_limit(&mut self) { self.as_mut().gas = None; @@ -439,6 +460,35 @@ impl FoundryTransactionBuilder for ::Tran self.key_authorization = Some(key_authorization); } + fn prepare_access_key_authorization<'a>( + &'a mut self, + provider: &'a impl Provider, + wallet_address: Address, + key_address: Address, + key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + let auth = key_authorization.cloned(); + + async move { + if let Some(auth) = auth { + let is_provisioned = provider + .get_keychain_key(wallet_address, key_address) + .await + .map(|info| info.keyId != Address::ZERO) + .unwrap_or(false); + + if !is_provisioned { + self.set_key_authorization(auth); + } + } + + Ok(()) + } + } + fn convert_create_to_call(&mut self) { if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) { let input = self.inner.input.input().cloned().unwrap_or_default(); @@ -473,7 +523,12 @@ impl FoundryTransactionBuilder for ::Tran let is_provisioned = provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false); - if !is_provisioned { + if !is_provisioned && self.key_authorization.is_none() { + if self.fee_payer_signature.is_some() { + eyre::bail!( + "cannot add Tempo key authorization after fee payer signature was attached" + ); + } self.set_key_authorization(auth); } } diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs index 9ca6cb02b10ee..c2e34419248c4 100644 --- a/crates/common/src/transactions/receipt.rs +++ b/crates/common/src/transactions/receipt.rs @@ -7,6 +7,7 @@ use alloy_provider::{ use alloy_rpc_types::{BlockId, TransactionReceipt}; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionReceipt; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionReceipt; @@ -23,6 +24,7 @@ impl FoundryReceiptResponse for TransactionReceipt { } } +#[cfg(feature = "optimism")] impl FoundryReceiptResponse for OpTransactionReceipt { fn set_contract_address(&mut self, contract_address: Address) { self.inner.contract_address = Some(contract_address); diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index bab59137b0130..8f63718e086cd 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -10,6 +10,10 @@ use std::path::PathBuf; pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, + /// Optional 1-based fuzz run to execute. + pub run: Option, + /// Optional fuzz worker ID to pair with `run`. + pub worker: Option, /// Fails the fuzzed test if a revert occurs. pub fail_on_revert: bool, /// The maximum number of test case rejections allowed, @@ -37,6 +41,8 @@ impl Default for FuzzConfig { fn default() -> Self { Self { runs: 256, + run: None, + worker: None, fail_on_revert: true, max_test_rejects: 65536, seed: None, diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 270df14a6c291..000cefc26737a 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use crate::Config; use alloy_primitives::map::HashMap; use figment::{ @@ -5,6 +7,7 @@ use figment::{ value::{Dict, Map, Value}, }; use foundry_compilers::ProjectCompileOutput; +use foundry_evm_networks::NetworkVariant; use itertools::Itertools; mod natspec; @@ -123,6 +126,42 @@ impl InlineConfig { self.get_function(contract, function).is_some_and(|map| !map.is_empty()) } + /// Returns the configured [`NetworkVariant`] for a given test, checking function-level first + /// then contract-level. Returns `None` if no network annotation is present. + pub fn network_for( + &self, + profile: &Profile, + contract: &str, + function: &str, + ) -> Option { + let data = self.provide(contract, function).data().ok()?; + let dict = data.get(profile).or_else(|| data.get(&Profile::Default))?; + if let Some(Value::Dict(_, networks)) = dict.get("networks") + && let Some(Value::String(_, s)) = networks.get("network") + { + return s.parse().ok(); + } + None + } + + /// Returns all distinct [`NetworkVariant`]s referenced in any inline config annotation. + /// + /// This is used to determine whether a multi-network test pass is needed. + pub fn referenced_override_networks(&self, profile: &Profile) -> Vec { + let mut seen = BTreeSet::new(); + for (contract, function) in self.fn_level.keys() { + if let Some(v) = self.network_for(profile, contract, function) { + seen.insert(v); + } + } + for contract in self.contract_level.keys() { + if let Some(v) = self.network_for(profile, contract, "") { + seen.insert(v); + } + } + seen.into_iter().collect() + } + fn get_contract(&self, contract: &str) -> Option<&DataMap> { self.contract_level.get(contract) } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 3c8cad85bae10..cc3dabd32d4bf 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -29,3 +29,11 @@ ratatui = { version = "0.30", default-features = false, features = [ revm.workspace = true tracing.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 809e15b077c37..814beab402729 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -32,3 +32,7 @@ thiserror.workspace = true toml.workspace = true tracing.workspace = true regex.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index b8fd3760cd850..888f6269623a5 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -72,8 +72,8 @@ impl AsDoc for CommentsRef<'_> { writer.writeln_raw(format!( "{}{}: {}", if customs.len() == 1 { "" } else { "- " }, - &c.tag, - &c.value + c.tag, + c.value ))?; writer.writeln()?; } diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 03d569c17f500..801e813026a39 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -36,7 +36,7 @@ alloy-primitives = { workspace = true, features = [ alloy-provider.workspace = true alloy-network.workspace = true alloy-consensus.workspace = true -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-rpc-types = { workspace = true, features = ["anvil"] } alloy-sol-types.workspace = true alloy-rlp.workspace = true @@ -54,9 +54,10 @@ revm = { workspace = true, features = [ "blst", ] } revm-inspectors.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-network.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } tempo-revm.workspace = true tempo-alloy.workspace = true tempo-contracts.workspace = true @@ -77,7 +78,18 @@ url.workspace = true [dev-dependencies] alloy-serde.workspace = true -op-alloy-consensus.workspace = true -op-alloy-rpc-types.workspace = true anvil.workspace = true foundry-test-utils.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "foundry-common/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", +] diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 0cfd56a44219c..b836023a968b7 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -223,8 +223,8 @@ fn trimmed_hex(s: &[u8]) -> String { } else { format!( "{}…{} ({} bytes)", - &hex::encode(&s[..n / 2]), - &hex::encode(&s[s.len() - n / 2..]), + hex::encode(&s[..n / 2]), + hex::encode(&s[s.len() - n / 2..]), s.len(), ) } diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 132b986f55e7f..bfc45b6b5d773 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -4,13 +4,9 @@ use alloy_consensus::Typed2718; pub use alloy_evm::EvmEnv; use alloy_evm::FromRecoveredTx; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, U256}; -use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; -use op_revm::{ - OpTransaction, - transaction::{OpTxTr, deposit::DEPOSIT_TRANSACTION_TYPE}, -}; +#[cfg(feature = "optimism")] +use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; use revm::{ Context, Database, Journal, context::{Block, BlockEnv, Cfg, CfgEnv, Transaction, TxEnv}, @@ -236,9 +232,16 @@ pub trait FoundryTransaction: Transaction { /// Sets whether the transaction is a system transaction fn set_system_transaction(&mut self, _is_system_transaction: bool) {} - /// Returns `true` if transaction is of type [`DEPOSIT_TRANSACTION_TYPE`]. + /// Returns `true` if transaction is an Optimism deposit transaction. fn is_deposit(&self) -> bool { - self.tx_type() == DEPOSIT_TRANSACTION_TYPE + #[cfg(feature = "optimism")] + { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE + } + #[cfg(not(feature = "optimism"))] + { + false + } } // Tempo methods @@ -320,188 +323,6 @@ impl FoundryTransaction for TxEnv { } } -impl FoundryTransaction for OpTransaction { - fn set_tx_type(&mut self, tx_type: u8) { - self.base.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.base.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.base.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.base.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.base.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.base.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.base.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.base.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.base.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.base.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.base.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.base.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - OpTxTr::enveloped_tx(self) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.enveloped_tx = Some(bytes); - } - - fn source_hash(&self) -> Option { - OpTxTr::source_hash(self) - } - - fn set_source_hash(&mut self, source_hash: B256) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.source_hash = source_hash; - } - } - - fn mint(&self) -> Option { - OpTxTr::mint(self) - } - - fn set_mint(&mut self, mint: u128) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.mint = Some(mint); - } - } - - fn is_system_transaction(&self) -> bool { - OpTxTr::is_system_transaction(self) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.is_system_transaction = is_system_transaction; - } - } -} - -impl FoundryTransaction for OpTx { - fn set_tx_type(&mut self, tx_type: u8) { - self.0.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.0.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.0.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.0.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.0.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.0.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.0.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.0.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.0.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.0.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.0.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.0.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - FoundryTransaction::enveloped_tx(&self.0) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.0.set_enveloped_tx(bytes); - } - - fn source_hash(&self) -> Option { - FoundryTransaction::source_hash(&self.0) - } - - fn set_source_hash(&mut self, source_hash: B256) { - self.0.set_source_hash(source_hash); - } - - fn mint(&self) -> Option { - FoundryTransaction::mint(&self.0) - } - - fn set_mint(&mut self, mint: u128) { - self.0.set_mint(mint); - } - - fn is_system_transaction(&self) -> bool { - FoundryTransaction::is_system_transaction(&self.0) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - self.0.set_system_transaction(is_system_transaction); - } -} - impl FoundryTransaction for TempoTxEnv { fn set_tx_type(&mut self, tx_type: u8) { self.inner.set_tx_type(tx_type); @@ -687,32 +508,6 @@ impl FromAnyRpcTransaction for TxEnv { } } -impl FromAnyRpcTransaction for OpTx { - fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { - if let Some(envelope) = tx.as_envelope() { - return Ok(Self(OpTransaction:: { - base: TxEnv::from_recovered_tx(envelope, tx.from()), - enveloped_tx: None, - deposit: Default::default(), - })); - } - - // Handle OP deposit transactions from `Unknown` envelope variant. - if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner - && unknown.ty() == DEPOSIT_TX_TYPE_ID - { - let mut fields = unknown.inner.fields.clone(); - fields.insert("from".to_string(), serde_json::to_value(tx.from())?); - let deposit_tx: TxDeposit = fields - .deserialize_into() - .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; - return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); - } - - eyre::bail!("cannot convert unknown transaction type to OpTransaction") - } -} - impl FromAnyRpcTransaction for TempoTxEnv { fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { use alloy_consensus::Transaction as _; @@ -747,6 +542,222 @@ impl FromAnyRpcTransaction for TempoTxEnv { } } +#[cfg(feature = "optimism")] +mod optimism { + use super::*; + use alloy_op_evm::OpTx; + use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; + use op_revm::{OpTransaction, transaction::OpTxTr}; + + impl FoundryTransaction for OpTransaction { + fn set_tx_type(&mut self, tx_type: u8) { + self.base.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.base.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.base.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.base.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.base.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.base.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.base.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.base.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.base.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + OpTxTr::enveloped_tx(self) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.enveloped_tx = Some(bytes); + } + + fn source_hash(&self) -> Option { + OpTxTr::source_hash(self) + } + + fn set_source_hash(&mut self, source_hash: B256) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.source_hash = source_hash; + } + } + + fn mint(&self) -> Option { + OpTxTr::mint(self) + } + + fn set_mint(&mut self, mint: u128) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.mint = Some(mint); + } + } + + fn is_system_transaction(&self) -> bool { + OpTxTr::is_system_transaction(self) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.is_system_transaction = is_system_transaction; + } + } + } + + impl FoundryTransaction for OpTx { + fn set_tx_type(&mut self, tx_type: u8) { + self.0.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.0.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.0.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.0.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.0.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.0.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.0.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.0.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.0.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.0.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.0.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + FoundryTransaction::enveloped_tx(&self.0) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.0.set_enveloped_tx(bytes); + } + + fn source_hash(&self) -> Option { + FoundryTransaction::source_hash(&self.0) + } + + fn set_source_hash(&mut self, source_hash: B256) { + self.0.set_source_hash(source_hash); + } + + fn mint(&self) -> Option { + FoundryTransaction::mint(&self.0) + } + + fn set_mint(&mut self, mint: u128) { + self.0.set_mint(mint); + } + + fn is_system_transaction(&self) -> bool { + FoundryTransaction::is_system_transaction(&self.0) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + self.0.set_system_transaction(is_system_transaction); + } + } + + impl FromAnyRpcTransaction for OpTx { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + return Ok(Self(OpTransaction:: { + base: TxEnv::from_recovered_tx(envelope, tx.from()), + enveloped_tx: None, + deposit: Default::default(), + })); + } + + // Handle OP deposit transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == DEPOSIT_TX_TYPE_ID + { + let mut fields = unknown.inner.fields.clone(); + fields.insert("from".to_string(), serde_json::to_value(tx.from())?); + let deposit_tx: TxDeposit = fields + .deserialize_into() + .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; + return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); + } + + eyre::bail!("cannot convert unknown transaction type to OpTransaction") + } + } +} + #[cfg(test)] mod tests { use std::num::NonZeroU64; @@ -755,14 +766,10 @@ mod tests { use alloy_consensus::{Sealed, Signed, TxEip1559, transaction::Recovered}; use alloy_evm::{EthEvmFactory, EvmFactory}; use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; - use alloy_op_evm::OpEvmFactory; use alloy_primitives::Signature; use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo}; use alloy_serde::WithOtherFields; use foundry_evm_hardforks::TempoHardfork; - use op_alloy_consensus::{OpTxEnvelope, transaction::OpTransactionInfo}; - use op_alloy_rpc_types::Transaction as OpRpcTransaction; - use op_revm::OpSpecId; use revm::database::EmptyDB; use tempo_alloy::primitives::{ AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, @@ -793,30 +800,6 @@ mod tests { evm.ctx_mut().set_evm(evm_env); } - #[test] - fn op_evm_foundry_context_ext_implementation() { - let mut evm = - OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); - - // Test EVM Context Block mutation - evm.ctx_mut().block_mut().set_number(U256::from(123)); - assert_eq!(evm.ctx().block().number(), U256::from(123)); - - // Test EVM Context Tx mutation - evm.ctx_mut().tx_mut().set_nonce(99); - assert_eq!(evm.ctx().tx().nonce(), 99); - - // Test EVM Context Cfg mutation - evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; - assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); - - // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env - let tx_env = evm.ctx().tx_clone(); - evm.ctx_mut().set_tx(tx_env); - let evm_env = evm.ctx().evm_clone(); - evm.ctx_mut().set_evm(evm_env); - } - #[test] fn tempo_evm_foundry_context_ext_implementation() { let mut evm = TempoEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default()); @@ -874,23 +857,6 @@ mod tests { assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); } - #[test] - fn from_any_rpc_transaction_for_op() { - let from = Address::random(); - let signed_tx = make_signed_eip1559(); - - // Build the eth TxEnv to compare against op base - let rpc_tx = RpcTransaction::from_transaction( - Recovered::new_unchecked(signed_tx.into(), from), - TransactionInfo::default(), - ); - let any_tx = >::from(rpc_tx); - let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base, expected_base); - } - #[test] fn from_any_rpc_transaction_unknown_envelope_errors() { let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { @@ -915,39 +881,6 @@ mod tests { assert!(result.to_string().contains("unknown transaction type")); } - #[test] - fn from_any_rpc_transaction_for_op_deposit() { - let from = Address::random(); - let source_hash = B256::random(); - let deposit = TxDeposit { - source_hash, - from, - to: TxKind::Call(Address::with_last_byte(0xCC)), - mint: 1111, - value: U256::from(200), - gas_limit: 21000, - is_system_transaction: true, - input: Default::default(), - }; - - // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as AnyRpcTransaction. - let op_rpc_tx = OpRpcTransaction::from_transaction( - Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), - OpTransactionInfo::default(), - ); - let json = serde_json::to_value(&op_rpc_tx).unwrap(); - let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base.caller, from); - assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); - assert_eq!(op_tx_env.base.value, U256::from(200)); - assert_eq!(op_tx_env.base.gas_limit, 21000); - assert_eq!(op_tx_env.deposit.source_hash, source_hash); - assert_eq!(op_tx_env.deposit.mint, Some(1111)); - assert!(op_tx_env.deposit.is_system_transaction); - } - #[test] fn from_any_rpc_transaction_for_tempo_eth_envelope() { let from = Address::random(); @@ -1004,4 +937,88 @@ mod tests { assert_eq!(tx_env.inner.chain_id, Some(42431)); assert_eq!(tx_env.fee_token, fee_token); } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + use alloy_op_evm::{OpEvmFactory, OpTx}; + use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo}; + use op_alloy_rpc_types::Transaction as OpRpcTransaction; + use op_revm::OpSpecId; + + #[test] + fn op_evm_foundry_context_ext_implementation() { + let mut evm = + OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; + assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + #[test] + fn from_any_rpc_transaction_for_op() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base, expected_base); + } + + #[test] + fn from_any_rpc_transaction_for_op_deposit() { + let from = Address::random(); + let source_hash = B256::random(); + let deposit = TxDeposit { + source_hash, + from, + to: TxKind::Call(Address::with_last_byte(0xCC)), + mint: 1111, + value: U256::from(200), + gas_limit: 21000, + is_system_transaction: true, + input: Default::default(), + }; + + // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let op_rpc_tx = OpRpcTransaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), + OpTransactionInfo::default(), + ); + let json = serde_json::to_value(&op_rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base.caller, from); + assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); + assert_eq!(op_tx_env.base.value, U256::from(200)); + assert_eq!(op_tx_env.base.gas_limit, 21000); + assert_eq!(op_tx_env.deposit.source_hash, source_hash); + assert_eq!(op_tx_env.deposit.mint, Some(1111)); + assert!(op_tx_env.deposit.is_system_transaction); + } + } } diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs index 708226be003a2..fc9e9e7d2810f 100644 --- a/crates/evm/core/src/evm/mod.rs +++ b/crates/evm/core/src/evm/mod.rs @@ -10,14 +10,11 @@ use alloy_evm::{ EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap, }; use alloy_network::{Ethereum, Network}; -use alloy_op_evm::OpEvmFactory; use alloy_primitives::{Address, Signature, U256}; use alloy_rlp::Decodable; use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; -use op_alloy_network::Optimism; -use op_revm::OpHaltReason; use revm::{ Database, context::{ @@ -36,10 +33,12 @@ use tempo_evm::evm::TempoEvmFactory; use tempo_revm::TempoHaltReason; pub mod eth; +#[cfg(feature = "optimism")] pub mod op; pub mod tempo; pub use eth::*; +#[cfg(feature = "optimism")] pub use op::*; pub use tempo::*; @@ -75,13 +74,6 @@ impl FoundryEvmNetwork for TempoEvmNetwork { type EvmFactory = TempoEvmFactory; } -#[derive(Clone, Copy, Debug, Default)] -pub struct OpEvmNetwork; -impl FoundryEvmNetwork for OpEvmNetwork { - type Network = Optimism; - type EvmFactory = OpEvmFactory; -} - /// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. pub type EvmFactoryFor = ::EvmFactory; pub type FoundryContextFor<'db, FEN> = @@ -249,15 +241,6 @@ impl IntoInstructionResult for HaltReason { } } -impl IntoInstructionResult for OpHaltReason { - fn into_instruction_result(self) -> InstructionResult { - match self { - Self::Base(eth) => eth.into(), - Self::FailedDeposit => InstructionResult::Stop, - } - } -} - impl IntoInstructionResult for TempoHaltReason { fn into_instruction_result(self) -> InstructionResult { match self { diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs index cb8bf272d9a05..efb74ad3abf50 100644 --- a/crates/evm/core/src/evm/op.rs +++ b/crates/evm/core/src/evm/op.rs @@ -1,6 +1,7 @@ use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; use alloy_op_evm::{OpEvm, OpEvmContext, OpEvmFactory, OpTx}; use foundry_fork_db::DatabaseError; +use op_alloy_network::Optimism; use op_revm::{OpEvm as RevmEvm, OpHaltReason, OpSpecId, OpTransactionError, handler::OpHandler}; use revm::{ context::{ @@ -10,16 +11,33 @@ use revm::{ handler::{EthFrame, EvmTr, FrameResult, Handler, instructions::EthInstructions}, inspector::InspectorHandler, interpreter::{ - FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + FrameInput, InstructionResult, SharedMemory, interpreter::EthInterpreter, + interpreter_action::FrameInit, }, }; use crate::{ FoundryContextExt, FoundryInspectorExt, backend::{DatabaseExt, JournaledState}, - evm::{FoundryEvmFactory, NestedEvm}, + evm::{FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm}, }; +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = OpEvmFactory; +} + +impl IntoInstructionResult for OpHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Base(eth) => eth.into(), + Self::FailedDeposit => InstructionResult::Stop, + } + } +} + type OpEvmHandler<'db, I> = OpHandler, EVMError, EthFrame>; diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index aefa0e2ee9741..2284823047ca6 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -212,13 +212,18 @@ pub struct ForkDbStateSnapshot { } impl ForkDbStateSnapshot { - fn get_storage(&self, address: Address, index: U256) -> Option { - self.local - .cache - .accounts - .get(&address) - .and_then(|account| account.storage.get(&index)) - .copied() + /// Lookup storage in `state_snapshot`, then fall back to the backend (remote RPC). + fn storage_from_snapshot_or_backend( + &self, + address: Address, + index: U256, + ) -> Result { + // Check state_snapshot.storage first (data fetched by SharedBackend / disk cache). + if let Some(val) = self.state_snapshot.storage.get(&address).and_then(|s| s.get(&index)) { + return Ok(*val); + } + // Fall back to the underlying backend (SharedBackend → remote RPC). + DatabaseRef::storage_ref(&self.local, address, index) } } @@ -250,15 +255,9 @@ impl DatabaseRef for ForkDbStateSnapshot { match self.local.cache.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), - }, - }, - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), + None => self.storage_from_snapshot_or_backend(address, index), }, + None => self.storage_from_snapshot_or_backend(address, index), } } @@ -303,4 +302,28 @@ mod tests { assert!(loaded.is_some()); assert_eq!(loaded.unwrap(), info); } + + /// Verifies that `ForkDbStateSnapshot::storage_ref` reads from `state_snapshot.storage` + /// when the slot is missing from `local.cache.accounts`. Without this lookup the call + /// would fall through to the backend and return the unrelated remote value. + #[tokio::test(flavor = "multi_thread")] + async fn fork_db_state_snapshot_reads_storage_from_snapshot() { + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); + let provider = get_http_provider(rpc.clone()); + let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc); + let db = BlockchainDb::new(meta, None); + let backend = SharedBackend::spawn_backend(Arc::new(provider), db, None).await; + + let address = Address::random(); + let slot = U256::from(42u64); + let expected = U256::from(0xdeadbeefu64); + + let mut state_snapshot = StateSnapshot::default(); + state_snapshot.storage.entry(address).or_default().insert(slot, expected); + + let snapshot = ForkDbStateSnapshot { local: CacheDB::new(backend), state_snapshot }; + + let got = DatabaseRef::storage_ref(&snapshot, address, slot).unwrap(); + assert_eq!(got, expected); + } } diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 1b2201a9b8b84..c2edbb9dfd33b 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -5,6 +5,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::constants::DEFAULT_CREATE2_DEPLOYER; use alloy_primitives::{Address, map::HashMap}; use auto_impl::auto_impl; diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 82efd4a1aaaaa..ab68eb08821e8 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -137,8 +137,12 @@ impl EvmOpts { /// [`NetworkConfigs::with_chain_id`] to auto-enable the correct network /// (e.g. Tempo, OP Stack) based on the chain ID. pub async fn infer_network_from_fork(&mut self) { + #[cfg(feature = "optimism")] + let already_op = self.networks.is_optimism(); + #[cfg(not(feature = "optimism"))] + let already_op = false; if !self.networks.is_tempo() - && !self.networks.is_optimism() + && !already_op && let Some(ref fork_url) = self.fork_url && let Ok(provider) = self.fork_provider_with_url::(fork_url) && let Ok(chain_id) = provider.get_chain_id().await @@ -474,6 +478,7 @@ mod tests { // Plain anvil (chain id 31337) without tempo flag -> Ethereum (no network flags set). assert!(!evm_opts.networks.is_tempo()); + #[cfg(feature = "optimism")] assert!(!evm_opts.networks.is_optimism()); assert!(!evm_opts.networks.is_celo()); assert_eq!(evm_opts.networks, NetworkConfigs::default()); diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index d2d7b077ee9f0..758604564726e 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -25,3 +25,7 @@ semver.workspace = true tracing.workspace = true rayon.workspace = true solar.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 70bce50a89882..5dbf07c7a356c 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -61,3 +61,16 @@ serde.workspace = true uuid.workspace = true rayon.workspace = true tokio.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm-core/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 33152b73dda3c..1932a834ab397 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -17,7 +17,7 @@ use foundry_evm_core::{ use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ BaseCounterExample, BasicTxDetails, CallDetails, CounterExample, FuzzCase, FuzzError, - FuzzFixtures, FuzzTestResult, + FuzzFixtures, FuzzRunMetadata, FuzzTestResult, strategies::{EvmFuzzState, fuzz_calldata, fuzz_calldata_from_state}, }; use foundry_evm_traces::SparsedTraceArena; @@ -71,6 +71,8 @@ struct WorkerState { runs: u32, /// Failure reason if this worker failed failure: Option, + /// Fuzz run metadata that produced the failure. + failure_run: Option, /// Last run timestamp in milliseconds /// /// Used to identify which worker ran last and collect its traces and call breakpoints @@ -93,6 +95,7 @@ impl WorkerState { deprecated_cheatcodes: HashMap::default(), runs: 0, failure: None, + failure_run: None, last_run_timestamp: 0, failed_corpus_replays: 0, } @@ -196,8 +199,14 @@ impl FuzzedExecutor { config: FuzzConfig, persisted_failure: Option, ) -> Self { - let max_workers = - if config.runs == 0 { 0 } else { Ord::max(1, config.runs / MIN_RUNS_PER_WORKER) }; + let run_limit = if config.run.is_some() { 1 } else { config.runs }; + let max_workers = if run_limit == 0 { + 0 + } else if config.run.is_some() { + 1 + } else { + Ord::max(1, run_limit / MIN_RUNS_PER_WORKER) + }; let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } @@ -221,8 +230,9 @@ impl FuzzedExecutor { ) -> Result { let shared_state = SharedFuzzState::new(state, self.config.timeout, early_exit.clone()); - debug!(n = self.num_workers, "spawning workers"); - let workers = (0..self.num_workers) + let worker_ids = self.worker_ids(); + debug!(n = worker_ids.len(), "spawning workers"); + let workers = worker_ids .into_par_iter() .map(|worker_id| { let _guard = tokio_handle.enter(); @@ -364,8 +374,14 @@ impl FuzzedExecutor { } else { vec![] }; + let fuzz = failed_worker.failure_run.unwrap_or_default(); result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + BaseCounterExample::from_fuzz_call(calldata, args, call.traces) + .with_fuzz_metadata(FuzzRunMetadata::new( + fuzz.seed.or(self.config.seed), + fuzz.run, + fuzz.worker, + )), )); } Some(TestCaseError::Reject(reason)) => { @@ -453,16 +469,7 @@ impl FuzzedExecutor { runner_config.cases = worker_runs; let mut runner = if let Some(seed) = self.config.seed { - // For deterministic parallel fuzzing, derive a unique seed for each worker - let worker_seed = if worker_id == 0 { - // Master worker uses the provided seed as is. - seed - } else { - // Derive a worker-specific seed using keccak256(seed || worker_id) - let seed_data = - [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); - U256::from_be_bytes(keccak256(seed_data).0) - }; + let worker_seed = Self::fuzz_worker_seed(seed, worker_id); trace!(target: "forge::test", ?worker_seed, "deterministic seed for worker {worker_id}"); let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &worker_seed.to_be_bytes::<32>()); TestRunner::new_with_rng(runner_config, rng) @@ -470,11 +477,25 @@ impl FuzzedExecutor { TestRunner::new(runner_config) }; - let mut persisted_failure = self.persisted_failure.as_ref().filter(|_| worker_id == 0); + if let Some(target_run) = self.config.run { + for _ in 1..target_run { + if let Err(err) = corpus.new_input(&mut runner, &shared_state.state, func) { + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id + ))); + shared_state.try_claim_failure(worker_id); + return Ok(worker); + } + } + } + + let mut persisted_failure = + self.persisted_failure.as_ref().filter(|_| worker_id == 0 && self.config.run.is_none()); // Offset to stagger corpus syncs across workers; so that workers don't sync at the same // time. - let sync_offset = worker_id as u32 * 100; + let sync_offset = (worker_id as u32).saturating_mul(100); let sync_threshold = SYNC_INTERVAL + sync_offset; let mut runs_since_sync = sync_threshold; // Always sync at the start. let mut last_metrics_report = Instant::now(); @@ -483,11 +504,27 @@ impl FuzzedExecutor { // 2. Worker hasn't reached its specific run limit 'stop: while shared_state.should_continue() && worker.runs < worker_runs { // If counterexample recorded, replay it first, without incrementing runs. - let input = if worker_id == 0 + let (input, fuzz_run) = if worker_id == 0 && let Some(failure) = persisted_failure.take() && failure.calldata.get(..4).is_some_and(|selector| func.selector() == selector) { - failure.calldata.clone() + let seed = failure.fuzz.seed.or(self.config.seed); + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() + && let Some(seed) = seed + { + let run = failure.fuzz.run.unwrap_or(1); + let worker = failure.fuzz.worker.unwrap_or(worker_id as u32) as usize; + cheats.set_seed(Self::fuzz_run_seed(seed, worker, run)); + } + + ( + failure.calldata.clone(), + Some(FuzzRunMetadata::new( + seed, + failure.fuzz.run, + Some(failure.fuzz.worker.unwrap_or(worker_id as u32)), + )), + ) } else { runs_since_sync += 1; if runs_since_sync >= sync_threshold { @@ -503,13 +540,14 @@ impl FuzzedExecutor { runs_since_sync = 0; } + let fuzz_run = self.config.run.unwrap_or(worker.runs + 1); if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() && let Some(seed) = self.config.seed { - cheats.set_seed(seed.wrapping_add(U256::from(worker.runs))); + cheats.set_seed(Self::fuzz_run_seed(seed, worker_id, fuzz_run)); } - match corpus.new_input(&mut runner, &shared_state.state, func) { + let input = match corpus.new_input(&mut runner, &shared_state.state, func) { Ok(input) => input, Err(err) => { worker.failure = Some(TestCaseError::fail(format!( @@ -519,13 +557,24 @@ impl FuzzedExecutor { shared_state.try_claim_failure(worker_id); break 'stop; } - } + }; + + ( + input, + Some(FuzzRunMetadata::new( + self.config.seed, + Some(fuzz_run), + Some(worker_id as u32), + )), + ) }; let mut inc_runs = || { let total_runs = shared_state.increment_runs(); debug_assert!( - shared_state.timer.is_enabled() || total_runs <= self.config.runs, + shared_state.timer.is_enabled() + || total_runs + <= if self.config.run.is_some() { 1 } else { self.config.runs }, "worker runs were not distributed correctly" ); worker.runs += 1; @@ -595,6 +644,7 @@ impl FuzzedExecutor { .. }) => { inc_runs(); + worker.failure_run = fuzz_run; // Only classify magic skip payloads when the revert originates from the // cheatcode address. @@ -656,7 +706,7 @@ impl FuzzedExecutor { /// Determines the number of runs per worker. const fn runs_per_worker(&self, worker_id: usize) -> u32 { let worker_id = worker_id as u32; - let total_runs = self.config.runs; + let total_runs = if self.config.run.is_some() { 1 } else { self.config.runs }; let n = self.num_workers as u32; let runs = total_runs / n; let remainder = total_runs % n; @@ -664,4 +714,29 @@ impl FuzzedExecutor { // assuming `worker_id` is in `0..n`. if worker_id < remainder { runs + 1 } else { runs } } + + /// Returns the worker IDs to execute. + fn worker_ids(&self) -> Vec { + if self.config.run.is_some() { + vec![self.config.worker.unwrap_or(0) as usize] + } else { + (0..self.num_workers).collect() + } + } + + /// Derives the deterministic RNG seed for a fuzz worker. + fn fuzz_worker_seed(seed: U256, worker_id: usize) -> U256 { + if worker_id == 0 { + seed + } else { + let worker_id = worker_id as u32; + let seed_data = [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); + U256::from_be_bytes(keccak256(seed_data).0) + } + } + + /// Derives the deterministic RNG seed for cheatcode randomness in a worker-local run. + fn fuzz_run_seed(seed: U256, worker_id: usize, run: u32) -> U256 { + Self::fuzz_worker_seed(seed, worker_id).wrapping_add(U256::from(run.saturating_sub(1))) + } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 27d8e6a0ed588..e02cdbc393ee6 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -736,7 +736,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { if !msg.is_empty() { msg.push_str(", "); } - msg.push_str(&format!("{}", &corpus_manager.metrics)); + msg.push_str(&format!("{}", corpus_manager.metrics)); } progress.set_message(msg); } diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 62e4e80a73674..5629b17d936da 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -50,3 +50,12 @@ rand.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 44d71fb6deee3..9c3e7d179c7f6 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -33,6 +33,27 @@ pub use strategies::LiteralMaps; mod inspector; pub use inspector::Fuzzer; +/// Metadata needed to reproduce a fuzz run. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct FuzzRunMetadata { + /// Seed used for the worker's input stream. + #[serde(default, rename = "fuzz_seed", skip_serializing_if = "Option::is_none")] + pub seed: Option, + /// 1-based run inside the worker's input stream. + #[serde(default, rename = "fuzz_run", skip_serializing_if = "Option::is_none")] + pub run: Option, + /// Worker that generated the input stream. + #[serde(default, rename = "fuzz_worker", skip_serializing_if = "Option::is_none")] + pub worker: Option, +} + +impl FuzzRunMetadata { + /// Creates metadata for reproducing a fuzz run. + pub const fn new(seed: Option, run: Option, worker: Option) -> Self { + Self { seed, run, worker } + } +} + /// Details of a transaction generated by fuzz strategy for fuzzing a target. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BasicTxDetails { @@ -102,6 +123,9 @@ pub struct BaseCounterExample { /// Whether to display sequence as solidity. #[serde(skip)] pub show_solidity: bool, + /// Fuzz metadata needed to reproduce this counterexample. + #[serde(flatten)] + pub fuzz: FuzzRunMetadata, } impl BaseCounterExample { @@ -137,6 +161,7 @@ impl BaseCounterExample { ), traces, show_solidity, + fuzz: FuzzRunMetadata::default(), }; } } @@ -154,6 +179,7 @@ impl BaseCounterExample { raw_args: None, traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } @@ -176,8 +202,15 @@ impl BaseCounterExample { raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()), traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } + + /// Sets fuzz metadata for reproducing this counterexample. + pub const fn with_fuzz_metadata(mut self, fuzz: FuzzRunMetadata) -> Self { + self.fuzz = fuzz; + self + } } impl fmt::Display for BaseCounterExample { @@ -229,7 +262,7 @@ impl fmt::Display for BaseCounterExample { if let Some(sig) = &self.signature { write!(f, "calldata={sig}")? } else { - write!(f, "calldata={}", &self.calldata)? + write!(f, "calldata={}", self.calldata)? } if let Some(args) = &self.args { diff --git a/crates/evm/hardforks/Cargo.toml b/crates/evm/hardforks/Cargo.toml index 9bf318028487a..68f6fb23bab07 100644 --- a/crates/evm/hardforks/Cargo.toml +++ b/crates/evm/hardforks/Cargo.toml @@ -16,10 +16,14 @@ workspace = true [dependencies] alloy-chains.workspace = true alloy-hardforks = { workspace = true, features = ["serde"] } -alloy-op-hardforks = { workspace = true, features = ["serde"] } +alloy-op-hardforks = { workspace = true, features = ["serde"], optional = true } alloy-rpc-types.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } revm.workspace = true serde = { workspace = true, features = ["derive"] } tempo-chainspec.workspace = true foundry-compilers.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "dep:op-revm"] diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs index 8a29ebb7af4ec..a8e0d51738263 100644 --- a/crates/evm/hardforks/src/lib.rs +++ b/crates/evm/hardforks/src/lib.rs @@ -8,11 +8,13 @@ use std::str::FromStr; use alloy_chains::Chain; use alloy_rpc_types::BlockNumberOrTag; use foundry_compilers::artifacts::EvmVersion; +#[cfg(feature = "optimism")] use op_revm::OpSpecId; use revm::primitives::hardfork::SpecId; use serde::{Deserialize, Serialize}; pub use alloy_hardforks::EthereumHardfork; +#[cfg(feature = "optimism")] pub use alloy_op_hardforks::OpHardfork; pub use tempo_chainspec::hardfork::TempoHardfork; @@ -20,6 +22,7 @@ pub use tempo_chainspec::hardfork::TempoHardfork; #[serde(into = "String")] pub enum FoundryHardfork { Ethereum(EthereumHardfork), + #[cfg(feature = "optimism")] Optimism(OpHardfork), Tempo(TempoHardfork), } @@ -28,6 +31,7 @@ impl From for String { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(h) => format!("optimism:{h}"), FoundryHardfork::Tempo(h) => format!("tempo:{h}"), } @@ -64,6 +68,7 @@ impl FromStr for FoundryHardfork { .map(Self::Ethereum) .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")), + #[cfg(feature = "optimism")] "op" | "optimism" => OpHardfork::from_str(&fork) .map(Self::Optimism) .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")), @@ -83,6 +88,7 @@ impl FoundryHardfork { Self::Ethereum(h) } + #[cfg(feature = "optimism")] pub const fn optimism(h: OpHardfork) -> Self { Self::Optimism(h) } @@ -95,6 +101,7 @@ impl FoundryHardfork { pub fn name(&self) -> String { match self { Self::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] Self::Optimism(h) => format!("{h}"), Self::Tempo(h) => format!("{h}"), } @@ -106,6 +113,7 @@ impl FoundryHardfork { pub const fn namespace(&self) -> Option<&'static str> { match self { Self::Ethereum(_) => None, + #[cfg(feature = "optimism")] Self::Optimism(_) => Some("optimism"), Self::Tempo(_) => Some("tempo"), } @@ -119,6 +127,7 @@ impl FoundryHardfork { if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Ethereum(fork)); } + #[cfg(feature = "optimism")] if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Optimism(fork)); } @@ -143,12 +152,14 @@ impl From for EthereumHardfork { } } +#[cfg(feature = "optimism")] impl From for FoundryHardfork { fn from(value: OpHardfork) -> Self { Self::Optimism(value) } } +#[cfg(feature = "optimism")] impl From for OpHardfork { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -177,12 +188,14 @@ impl From for SpecId { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), FoundryHardfork::Tempo(hardfork) => hardfork.into(), } } } +#[cfg(feature = "optimism")] impl From for OpSpecId { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -223,6 +236,7 @@ pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { } /// Map an `OptimismHardfork` enum into its corresponding `OpSpecId`. +#[cfg(feature = "optimism")] pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { match hardfork { OpHardfork::Bedrock => OpSpecId::BEDROCK, @@ -265,6 +279,7 @@ impl FromEvmVersion for SpecId { } } +#[cfg(feature = "optimism")] impl FromEvmVersion for OpSpecId { fn from_evm_version(version: EvmVersion) -> Self { match version { @@ -324,16 +339,6 @@ mod tests { assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA); } - #[test] - fn test_optimism_spec_id_mapping() { - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - - // Test latest hardforks - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); - } - #[test] fn test_tempo_spec_id_mapping() { assert_eq!(SpecId::from(TempoHardfork::Genesis), SpecId::OSAKA); @@ -371,25 +376,40 @@ mod tests { } #[test] - fn test_from_chain_and_timestamp_op_mainnet() { - let op_chain_id = 10; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); + fn test_from_chain_and_timestamp_unknown_chain() { + assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); } - #[test] - fn test_from_chain_and_timestamp_base() { - let base_chain_id = 8453; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); - } + #[cfg(feature = "optimism")] + mod optimism { + use super::*; - #[test] - fn test_from_chain_and_timestamp_unknown_chain() { - assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); + #[test] + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); + + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } + + #[test] + fn test_from_chain_and_timestamp_op_mainnet() { + let op_chain_id = 10; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_base() { + let base_chain_id = 8453; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } } } diff --git a/crates/evm/networks/Cargo.toml b/crates/evm/networks/Cargo.toml index a63ed34ba61cf..00c9abf0f90f7 100644 --- a/crates/evm/networks/Cargo.toml +++ b/crates/evm/networks/Cargo.toml @@ -19,7 +19,7 @@ foundry-evm-hardforks.workspace = true alloy-chains.workspace = true alloy-eips.workspace = true alloy-evm.workspace = true -alloy-op-hardforks.workspace = true +alloy-op-hardforks = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = [ "serde", "getrandom", @@ -43,4 +43,8 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } serde.workspace = true [dev-dependencies] -serde_json.workspace = true \ No newline at end of file +serde_json.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "foundry-evm-hardforks/optimism"] diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs index 303b9ca8b7a13..384cee5a7bed3 100644 --- a/crates/evm/networks/src/lib.rs +++ b/crates/evm/networks/src/lib.rs @@ -11,7 +11,6 @@ use alloy_chains::{ }; use alloy_eips::eip1559::BaseFeeParams; use alloy_evm::precompiles::PrecompilesMap; -use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; use alloy_primitives::{Address, ChainId, map::AddressHashMap}; use clap::Parser; use foundry_evm_hardforks::FoundryHardfork; @@ -20,20 +19,52 @@ use std::collections::BTreeMap; pub mod celo; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)] +#[cfg(feature = "optimism")] +mod optimism; + +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + clap::ValueEnum, +)] #[serde(rename_all = "lowercase")] #[clap(rename_all = "lowercase")] pub enum NetworkVariant { #[default] Ethereum, + #[cfg(feature = "optimism")] Optimism, Tempo, } +impl std::str::FromStr for NetworkVariant { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ethereum" => Ok(Self::Ethereum), + #[cfg(feature = "optimism")] + "optimism" => Ok(Self::Optimism), + "tempo" => Ok(Self::Tempo), + _ => Err(format!("unknown network variant: {s}")), + } + } +} + impl NetworkVariant { pub const fn name(&self) -> &'static str { match self { Self::Ethereum => "ethereum", + #[cfg(feature = "optimism")] Self::Optimism => "optimism", Self::Tempo => "tempo", } @@ -50,32 +81,37 @@ impl From for NetworkVariant { fn from(chain_id: ChainId) -> Self { let chain = Chain::from_id(chain_id); if chain.is_tempo() { - Self::Tempo - } else if chain.is_optimism() { - Self::Optimism - } else { - Self::Ethereum + return Self::Tempo; + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::Optimism; } + Self::Ethereum } } #[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq)] pub struct NetworkConfigs { /// Enable a specific network family. - #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "optimism", "tempo"])] + #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] #[serde(default)] - network: Option, + pub(crate) network: Option, /// Enable Celo network features. - #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "optimism", "tempo"])] + #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] celo: bool, /// Enable Optimism network features (deprecated: use --network optimism). + #[cfg(feature = "optimism")] #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])] // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the // canonical form is `network = "optimism"`. #[serde(default)] - optimism: bool, + pub(crate) optimism: bool, /// Enable Tempo network features (deprecated: use --network tempo). - #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "optimism"])] + #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the // canonical form is `network = "tempo"`. #[serde(default)] @@ -102,10 +138,6 @@ impl Serialize for NetworkConfigs { } impl NetworkConfigs { - pub fn with_optimism() -> Self { - Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } - } - pub fn with_celo() -> Self { Self { celo: true, ..Default::default() } } @@ -114,11 +146,7 @@ impl NetworkConfigs { Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() } } - pub fn is_optimism(&self) -> bool { - matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) - } - - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { matches!(self.resolved_network(), Some(NetworkVariant::Tempo)) } @@ -127,14 +155,18 @@ impl NetworkConfigs { } /// Returns the resolved network variant, folding legacy flags. - fn resolved_network(&self) -> Option { - self.network.or(if self.optimism { - Some(NetworkVariant::Optimism) - } else if self.tempo { - Some(NetworkVariant::Tempo) - } else { - None - }) + const fn resolved_network(&self) -> Option { + if let Some(n) = self.network { + return Some(n); + } + #[cfg(feature = "optimism")] + if self.optimism { + return Some(NetworkVariant::Optimism); + } + if self.tempo { + return Some(NetworkVariant::Tempo); + } + None } /// Returns the name of the currently active non-Ethereum network, or `None` for plain Ethereum. @@ -150,16 +182,12 @@ impl NetworkConfigs { /// For Optimism networks, returns Canyon parameters if the Canyon hardfork is active /// at the given timestamp, otherwise returns pre-Canyon parameters. pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_hardforks = OpChainHardforks::op_mainnet(); - if op_hardforks.is_canyon_active_at_timestamp(timestamp) { - BaseFeeParams::optimism_canyon() - } else { - BaseFeeParams::optimism() - } - } else { - BaseFeeParams::ethereum() + return self.op_base_fee_params(timestamp); } + let _ = timestamp; + BaseFeeParams::ethereum() } pub fn bypass_prevrandao(&self, chain_id: u64) -> bool { @@ -174,21 +202,23 @@ impl NetworkConfigs { pub fn with_chain_id(self, chain_id: u64) -> Self { let chain = Chain::from_id(chain_id); - if self.resolved_network().is_none() { - if chain.is_tempo() { - Self::with_tempo() - } else if chain.is_optimism() { - Self::with_optimism() + if self.resolved_network().is_some() { + return if !self.celo + && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + { + Self::with_celo() } else { self - } - } else if !self.celo - && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) - { - Self::with_celo() - } else { - self + }; + } + if chain.is_tempo() { + return Self::with_tempo(); + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::with_optimism(); } + self } /// Validates `hardfork` against the current `NetworkConfigs` and, if consistent, returns an @@ -208,6 +238,7 @@ impl NetworkConfigs { let network = match hardfork { FoundryHardfork::Ethereum(_) => self, FoundryHardfork::Tempo(_) => Self::with_tempo(), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(_) => Self::with_optimism(), }; @@ -243,6 +274,21 @@ impl NetworkConfigs { } } +impl From for NetworkConfigs { + fn from(network: NetworkVariant) -> Self { + match network { + NetworkVariant::Ethereum => Self::default(), + NetworkVariant::Tempo => { + Self { network: Some(network), tempo: true, ..Default::default() } + } + #[cfg(feature = "optimism")] + NetworkVariant::Optimism => { + Self { network: Some(network), optimism: true, ..Default::default() } + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -254,17 +300,6 @@ mod tests { let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() }; let via_old = NetworkConfigs { tempo: true, ..Default::default() }; assert_eq!(via_new.is_tempo(), via_old.is_tempo()); - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.active_network_name(), via_old.active_network_name()); - } - - #[test] - fn new_optimism_flag_equivalent_to_legacy() { - let via_new = - NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; - let via_old = NetworkConfigs { optimism: true, ..Default::default() }; - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.is_tempo(), via_old.is_tempo()); assert_eq!(via_new.active_network_name(), via_old.active_network_name()); } @@ -276,31 +311,11 @@ mod tests { assert_eq!(cfg.active_network_name(), Some("tempo")); } - #[test] - fn active_network_name_optimism() { - let cfg = NetworkConfigs::with_optimism(); - assert_eq!(cfg.active_network_name(), Some("optimism")); - } - #[test] fn active_network_name_default_is_none() { assert_eq!(NetworkConfigs::default().active_network_name(), None); } - // --- new flag takes precedence over legacy flag --- - - #[test] - fn new_flag_wins_over_legacy_when_both_set() { - // --network optimism --tempo: network field wins - let cfg = NetworkConfigs { - network: Some(NetworkVariant::Optimism), - tempo: true, - ..Default::default() - }; - assert!(cfg.is_optimism()); - assert!(!cfg.is_tempo()); - } - // --- Serde round-trip --- #[test] @@ -309,16 +324,6 @@ mod tests { let json = serde_json::to_string(&original).unwrap(); let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); assert!(restored.is_tempo()); - assert!(!restored.is_optimism()); - } - - #[test] - fn serde_roundtrip_optimism() { - let original = NetworkConfigs::with_optimism(); - let json = serde_json::to_string(&original).unwrap(); - let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); - assert!(restored.is_optimism()); - assert!(!restored.is_tempo()); } #[test] @@ -345,8 +350,55 @@ mod tests { let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#; let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap(); assert!(cfg_tempo.is_tempo()); - let json_optimism = r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; - let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); - assert!(cfg_optimism.is_optimism()); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + + #[test] + fn new_optimism_flag_equivalent_to_legacy() { + let via_new = + NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; + let via_old = NetworkConfigs { optimism: true, ..Default::default() }; + assert_eq!(via_new.is_optimism(), via_old.is_optimism()); + assert_eq!(via_new.is_tempo(), via_old.is_tempo()); + assert_eq!(via_new.active_network_name(), via_old.active_network_name()); + } + + #[test] + fn active_network_name_optimism() { + let cfg = NetworkConfigs::with_optimism(); + assert_eq!(cfg.active_network_name(), Some("optimism")); + } + + #[test] + fn new_flag_wins_over_legacy_when_both_set() { + // --network optimism --tempo: network field wins + let cfg = NetworkConfigs { + network: Some(NetworkVariant::Optimism), + tempo: true, + ..Default::default() + }; + assert!(cfg.is_optimism()); + assert!(!cfg.is_tempo()); + } + + #[test] + fn serde_roundtrip_optimism() { + let original = NetworkConfigs::with_optimism(); + let json = serde_json::to_string(&original).unwrap(); + let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); + assert!(restored.is_optimism()); + assert!(!restored.is_tempo()); + } + + #[test] + fn serde_optimism_field_deserialized() { + let json_optimism = + r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; + let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); + assert!(cfg_optimism.is_optimism()); + } } } diff --git a/crates/evm/networks/src/optimism.rs b/crates/evm/networks/src/optimism.rs new file mode 100644 index 0000000000000..5fffa38a333c7 --- /dev/null +++ b/crates/evm/networks/src/optimism.rs @@ -0,0 +1,25 @@ +//! Optimism-specific extensions for [`NetworkConfigs`] and related helpers. + +use crate::{NetworkConfigs, NetworkVariant}; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; + +impl NetworkConfigs { + pub fn with_optimism() -> Self { + Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } + } + + pub const fn is_optimism(&self) -> bool { + matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) + } + + /// Optimism-specific base fee parameters, picking Canyon vs pre-Canyon based on `timestamp`. + pub(crate) fn op_base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + let op_hardforks = OpChainHardforks::op_mainnet(); + if op_hardforks.is_canyon_active_at_timestamp(timestamp) { + BaseFeeParams::optimism_canyon() + } else { + BaseFeeParams::optimism() + } + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 90d2db724cebc..73d64d3ab5d07 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -50,3 +50,7 @@ tempfile.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true yansi.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index bad5c577bc69e..b6f11772620f9 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -26,3 +26,7 @@ foundry-test-utils.workspace = true toml.workspace = true snapbox.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 89a9bf152c8c2..4b986017b71dd 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -711,7 +711,7 @@ impl<'sess> State<'sess, '_> { // Merge the lines and let the wrapper handle breaking if needed let merged_line = format!( "{current_line} {next_content}", - next_content = &next_line[prefix.len()..].trim_start() + next_content = next_line[prefix.len()..].trim_start() ); result.push(merged_line); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 064834d248d5f..667da6b442ca1 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -117,7 +117,7 @@ tempfile.workspace = true alloy-signer-local.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -126,3 +126,15 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cli/optimism", + "forge-script/optimism", + "forge-verify/optimism", + "forge-doc/optimism", + "forge-fmt/optimism", + "forge-lint/optimism", + "forge-sol-macro-gen/optimism", +] diff --git a/crates/forge/assets/tempo/MailTemplate.s.sol b/crates/forge/assets/tempo/MailTemplate.s.sol index 27512efe4d5ec..45006f7cd0e06 100644 --- a/crates/forge/assets/tempo/MailTemplate.s.sol +++ b/crates/forge/assets/tempo/MailTemplate.s.sol @@ -14,7 +14,7 @@ contract MailScript is Script { function run(string memory salt) public { vm.startBroadcast(); - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); ITIP20 token = ITIP20( diff --git a/crates/forge/assets/tempo/MailTemplate.t.sol b/crates/forge/assets/tempo/MailTemplate.t.sol index b1749db5df0bf..19760303860a1 100644 --- a/crates/forge/assets/tempo/MailTemplate.t.sol +++ b/crates/forge/assets/tempo/MailTemplate.t.sol @@ -17,7 +17,7 @@ contract MailTest is Test { address public constant BOB = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); function setUp() public virtual { - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); token = ITIP20( diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index ea034bce87185..b8ce2a9b945b1 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -87,8 +87,11 @@ impl CoverageArgs { config = self.load_config()?; } - // Set fuzz seed so coverage reports are deterministic - config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so coverage reports are deterministic, + // but allow the user to override it via `--fuzz-seed` or `[fuzz] seed` in config. + if config.fuzz.seed.is_none() { + config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let (paths, mut output) = { let (project, output) = self.build(&config)?; diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 765bb64f95fdd..4f638b6edcdf8 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -13,7 +13,10 @@ use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, - utils::{LoadConfig, find_contract_artifacts, read_constructor_args_file}, + utils::{ + LoadConfig, ResolvedLane, find_contract_artifacts, maybe_print_resolved_lane, + read_constructor_args_file, resolve_lane, + }, }; use foundry_common::{ FoundryTransactionBuilder, @@ -203,6 +206,11 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `self.tx.tempo.nonce_key` from the lane. + // Must happen before `self.deploy(...)` so `TempoOpts::apply` picks up the nonce_key. + let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?; + // Whether to broadcast the transaction or not let dry_run = !self.broadcast; @@ -223,6 +231,7 @@ impl CreateArgs { dry_run, None, Some(browser), + resolved_lane, ) .await } else if self.unlocked { @@ -239,6 +248,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } else if let Some(ak) = access_key { @@ -259,6 +269,7 @@ impl CreateArgs { dry_run, Some((signer, ak)), None, + resolved_lane, ) .await } else { @@ -282,6 +293,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } @@ -362,6 +374,7 @@ impl CreateArgs { dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, browser_signer: Option>, + resolved_lane: Option, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, @@ -398,7 +411,7 @@ impl CreateArgs { // If Tempo chain fee token must be set if chain.is_tempo() { - if let Some(fee_token) = self.tx.tempo.fee_token { + if let Some(fee_token) = self.tx.tempo.common.fee_token { deployer.tx.set_fee_token(fee_token); } else { deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); @@ -408,15 +421,18 @@ impl CreateArgs { // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); - // For keychain mode, set key_id and nonce_key before gas estimation. // Convert the CREATE into an AA-compatible call entry since Tempo AA // transactions use a `calls` list instead of `to`+`input`. + if chain.is_tempo() { + deployer.tx.convert_create_to_call(); + } + + // For keychain mode, set key_id and nonce_key before gas estimation. if let Some((_, ref ak)) = tempo_keychain { deployer.tx.set_key_id(ak.key_address); if deployer.tx.nonce_key().is_none() { deployer.tx.set_nonce_key(U256::ZERO); } - deployer.tx.convert_create_to_call(); } // Fetch defaults from provider for values not specified by user. @@ -424,6 +440,20 @@ impl CreateArgs { deployer.tx.set_nonce(provider.get_transaction_count(deployer_address).await?); } + maybe_print_resolved_lane(resolved_lane.as_ref(), deployer.tx.nonce().unwrap_or_default())?; + + if let Some((_, ref ak)) = tempo_keychain { + deployer + .tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } + // set access list if specified if let Some(access_list) = match self.tx.access_list { None => None, @@ -500,6 +530,11 @@ impl CreateArgs { return Ok(()); } + let tempo_sponsor = self.tx.tempo.sponsor_config().await?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut deployer.tx, deployer_address).await?; + } + // Deploy the actual contract let (deployed_contract, receipt) = if let Some(browser) = browser_signer { // Browser wallet signs and sends the transaction diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index c8dc2ba72aae1..7c6fb51ce3266 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -99,8 +99,11 @@ impl GasSnapshotArgs { } pub async fn run(mut self) -> Result<()> { - // Set fuzz seed so gas snapshots are deterministic - self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so gas snapshots are deterministic, + // but allow the user to override it via `--fuzz-seed`. + if self.test.fuzz_seed.is_none() { + self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let outcome = self.test.compile_and_run().await?; outcome.ensure_ok(false)?; diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index 94376dc5238cd..dd8f3afd56197 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -3,7 +3,7 @@ use crate::{ MultiContractRunner, MultiContractRunnerBuilder, decode::decode_console_logs, gas_report::GasReport, - multi_runner::matches_artifact, + multi_runner::{MultiNetworkConfig, matches_artifact}, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ CallTraceDecoderBuilder, InternalTraceMode, TraceKind, @@ -31,7 +31,7 @@ use foundry_compilers::{ utils::source_files_iter, }; use foundry_config::{ - Config, figment, + Config, InlineConfig, figment, figment::{ Metadata, Profile, Provider, value::{Dict, Map}, @@ -39,10 +39,11 @@ use foundry_config::{ filter::GlobMatcher, }; use foundry_debugger::Debugger; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::evm::{ - BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, SpecFor, TempoEvmNetwork, - TxEnvFor, + BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor, }, opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, @@ -169,6 +170,14 @@ pub struct TestArgs { #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, + /// Run only the fuzz case at the given 1-based run index. + #[arg(long, env = "FOUNDRY_FUZZ_RUN", value_name = "RUN")] + pub fuzz_run: Option, + + /// Run the fuzz case from the given worker. Requires `--fuzz-run`. + #[arg(long, env = "FOUNDRY_FUZZ_WORKER", value_name = "WORKER", requires = "fuzz_run")] + pub fuzz_worker: Option, + /// Timeout for each fuzz run in seconds. #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] pub fuzz_timeout: Option, @@ -301,6 +310,10 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, coverage: bool, ) -> Result { + if config.fuzz.run == Some(0) { + bail!("`fuzz.run` must be greater than 0"); + } + // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { evm_opts.isolate = true; @@ -342,40 +355,80 @@ impl TestArgs { // Auto-detect network from fork chain ID when not explicitly configured. evm_opts.infer_network_from_fork().await; - // Dispatch based on network type. - let (libraries, mut outcome) = if evm_opts.networks.is_tempo() { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - } else if evm_opts.networks.is_optimism() { - self.build_and_run_tests::( + // Parse inline config early to detect per-test network annotations. + let inline_config = InlineConfig::new_parsed(output, &config)?; + let override_networks = inline_config.referenced_override_networks(&config.profile); + + let (libraries, mut outcome) = if override_networks.is_empty() { + // Single-pass: no per-test network overrides, use global network setting. + self.dispatch_network( + &evm_opts, config, - evm_opts, + evm_opts.clone(), output, filter, coverage, should_debug, decode_internal, + MultiNetworkConfig::default(), ) .await? } else { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? + // Multi-pass: run each distinct network separately and merge results. + let all_override_networks = override_networks.clone(); + let multi_pass_timer = Instant::now(); + + // Default pass: global network, runs tests without an explicit network annotation. + let (libraries, mut outcome) = self + .dispatch_network( + &evm_opts, + config.clone(), + evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: None, + }, + ) + .await?; + + // Override passes: one per annotated network. + for &network in &override_networks { + let mut pass_evm_opts = evm_opts.clone(); + pass_evm_opts.networks = network.into(); + let (_, pass_outcome) = self + .dispatch_network( + &pass_evm_opts, + config.clone(), + pass_evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: Some(network), + }, + ) + .await?; + merge_outcomes(&mut outcome, pass_outcome); + } + + // Print the merged summary (per-pass summaries are suppressed in `run_tests_inner`). + if !self.summary && !shell::is_json() { + sh_println!("{}", outcome.summary(multi_pass_timer.elapsed()))?; + } + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; + } + + (libraries, outcome) }; if should_draw { @@ -461,6 +514,7 @@ impl TestArgs { coverage: bool, should_debug: bool, decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, ) -> eyre::Result<(Libraries, TestOutcome)> { let verbosity = evm_opts.verbosity; let (evm_env, tx_env, fork_block) = @@ -476,6 +530,7 @@ impl TestArgs { .enable_isolation(evm_opts.isolate) .fail_fast(self.fail_fast) .set_coverage(coverage) + .with_multi_network(multi_network) .build::(output, evm_env, tx_env, evm_opts)?; let libraries = runner.libraries.clone(); @@ -483,6 +538,62 @@ impl TestArgs { Ok((libraries, outcome)) } + /// Dispatches `build_and_run_tests` to the correct network type based on `evm_opts.networks`. + #[allow(clippy::too_many_arguments)] + async fn dispatch_network( + &self, + dispatch_opts: &EvmOpts, + config: Config, + evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + should_debug: bool, + decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, + ) -> eyre::Result<(Libraries, TestOutcome)> { + if dispatch_opts.networks.is_tempo() { + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } else { + #[cfg(feature = "optimism")] + if dispatch_opts.networks.is_optimism() { + return self + .build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await; + } + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } + } + /// Run all tests that matches the filter predicate from a test runner async fn run_tests_inner( &self, @@ -586,6 +697,11 @@ impl TestArgs { let libraries = runner.libraries.clone(); + // Capture multi-pass state before moving `runner` into the spawn task. + // In multi-pass mode the per-pass summary is suppressed; the merged summary is + // printed once by the caller after all passes complete. + let is_multi_pass = !runner.tcfg.multi_network.all_override_networks.is_empty(); + // Run tests in a streaming fashion. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); @@ -643,6 +759,13 @@ impl TestArgs { let tests = &mut suite_result.test_results; let has_tests = !tests.is_empty(); + // In multi-pass (per-test network override) mode, skip suites that contributed no + // tests to this pass so we don't emit a stray blank line in the suite header or + // pollute the outcome with empty entries. + if is_multi_pass && !has_tests && suite_result.warnings.is_empty() { + continue; + } + // Clear the addresses and labels from previous test. decoder.clear_addresses(); @@ -903,17 +1026,17 @@ impl TestArgs { if let Some(gas_report) = gas_report { let finalized = gas_report.finalize(); - sh_println!("{}", &finalized)?; + sh_println!("{finalized}")?; outcome.gas_report = Some(finalized); } - if !self.summary && !shell::is_json() { + if !is_multi_pass && !self.summary && !shell::is_json() { sh_println!("{}", outcome.summary(duration))?; } - if self.summary && !outcome.results.is_empty() { + if !is_multi_pass && self.summary && !outcome.results.is_empty() { let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); - sh_println!("{}", &summary_report)?; + sh_println!("{summary_report}")?; } // Reattach the task. @@ -980,6 +1103,12 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_run) = self.fuzz_run { + fuzz_dict.insert("run".to_string(), fuzz_run.into()); + } + if let Some(fuzz_worker) = self.fuzz_worker { + fuzz_dict.insert("worker".to_string(), fuzz_worker.into()); + } if let Some(fuzz_timeout) = self.fuzz_timeout { fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); } @@ -1023,6 +1152,29 @@ fn list( Ok(TestOutcome::empty(Some(runner.known_contracts), false)) } +/// Merges `other` into `base` by extending suite results. +/// +/// For suites that appear in both, test results are combined (function-level pass routing ensures +/// each function appears in exactly one pass, so there are no key conflicts in practice). +fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { + for (suite_id, other_suite) in other.results { + match base.results.entry(suite_id) { + std::collections::btree_map::Entry::Vacant(e) => { + e.insert(other_suite); + } + std::collections::btree_map::Entry::Occupied(mut e) => { + let base_suite = e.get_mut(); + base_suite.test_results.extend(other_suite.test_results); + base_suite.warnings.extend(other_suite.warnings); + base_suite.duration = base_suite.duration.max(other_suite.duration); + } + } + } + if let Some(decoder) = other.last_run_decoder { + base.last_run_decoder = Some(decoder); + } +} + /// Load persisted filter (with last test run failures) from file. fn last_run_failures(config: &Config) -> Option { match fs::read_to_string(&config.test_failures_file) { @@ -1131,6 +1283,14 @@ mod tests { assert!(args.fuzz_seed.is_some()); } + #[test] + fn fuzz_run() { + let args: TestArgs = + TestArgs::parse_from(["foundry-cli", "--fuzz-run", "10", "--fuzz-worker", "2"]); + assert_eq!(args.fuzz_run, Some(10)); + assert_eq!(args.fuzz_worker, Some(2)); + } + #[test] fn extract_chain() { let test = |arg: &str, expected: Chain| { diff --git a/crates/forge/src/cmd/test/summary.rs b/crates/forge/src/cmd/test/summary.rs index f8a72272af53c..a0123e896d0bf 100644 --- a/crates/forge/src/cmd/test/summary.rs +++ b/crates/forge/src/cmd/test/summary.rs @@ -25,9 +25,9 @@ impl TestSummaryReport { impl Display for TestSummaryReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "{}", self.format_json_output(&self.is_detailed, &self.outcome))?; } else { - writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "\n{}", self.format_table_output(&self.is_detailed, &self.outcome))?; } Ok(()) } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 6c93dc03b28b5..58b11d98874ed 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -146,7 +146,7 @@ impl GasReport { impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output())?; + writeln!(f, "{}", self.format_json_output())?; } else { for (name, contract) in &self.contracts { if contract.functions.is_empty() { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 88bbc6156c812..675f0c3e6c99c 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -27,6 +27,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; +use foundry_evm_networks::NetworkVariant; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; @@ -280,6 +281,25 @@ impl MultiContractRunner { } } +/// Tracks network assignment across a multi-network test run. +/// +/// When inline config specifies different networks for different tests, the runner performs one +/// pass per distinct network. This struct encodes which pass we're in so each `ContractRunner` +/// can skip tests that belong to a different pass. +/// +/// Default (empty `all_override_networks`, `None` pass) = single-pass mode, every test runs. +#[derive(Clone, Debug, Default)] +pub struct MultiNetworkConfig { + /// All networks explicitly referenced in inline config annotations across the whole suite. + /// Empty means single-pass mode (no per-test network overrides present). + pub all_override_networks: Vec, + /// The network this pass is responsible for. + /// `None` = default pass: runs tests *without* an explicit network annotation (or annotated + /// with a network not in `all_override_networks`). + /// `Some(v)` = override pass: runs only tests annotated with exactly `v`. + pub pass_network: Option, +} + /// Configuration for the test runner. /// /// This is modified after instantiation through inline config. @@ -311,6 +331,9 @@ pub struct TestRunnerConfig { pub isolation: bool, /// Whether to exit early on test failure or if test run interrupted. pub early_exit: EarlyExit, + + /// Multi-network pass configuration. Default = single-pass mode. + pub multi_network: MultiNetworkConfig, } impl TestRunnerConfig { @@ -423,6 +446,8 @@ pub struct MultiContractRunnerBuilder { pub isolation: bool, /// Whether to exit early on test failure. pub fail_fast: bool, + /// Multi-network pass configuration. + pub multi_network: MultiNetworkConfig, } impl MultiContractRunnerBuilder { @@ -437,6 +462,7 @@ impl MultiContractRunnerBuilder { isolation: Default::default(), decode_internal: Default::default(), fail_fast: false, + multi_network: Default::default(), } } @@ -470,6 +496,11 @@ impl MultiContractRunnerBuilder { self } + pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self { + self.multi_network = multi_network; + self + } + pub const fn fail_fast(mut self, fail_fast: bool) -> Self { self.fail_fast = fail_fast; self @@ -594,6 +625,7 @@ impl MultiContractRunnerBuilder { inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, early_exit: EarlyExit::new(self.fail_fast), + multi_network: self.multi_network, config: self.config, }, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index d924c416759a2..7feaf35254636 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -109,6 +109,25 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { } } + /// Returns `true` if `func` should run in the current multi-network pass. + /// + /// In single-pass mode (no inline network overrides) every function passes. + /// In multi-pass mode: + /// - Default pass (`pass_network = None`): includes functions *without* an override annotation. + /// - Override pass (`pass_network = Some(v)`): includes only functions annotated with `v`. + fn function_matches_network_pass(&self, func: &Function) -> bool { + let multi = &self.mcr.tcfg.multi_network; + if multi.all_override_networks.is_empty() { + return true; + } + let profile = &self.tcfg.config.profile; + let func_network = self.mcr.inline_config.network_for(profile, self.name, &func.name); + match &multi.pass_network { + None => func_network.is_none_or(|n| !multi.all_override_networks.contains(&n)), + Some(target) => func_network.as_ref() == Some(target), + } + } + /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { @@ -380,6 +399,7 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { .abi .functions() .filter(|func| filter.matches_test_function(func)) + .filter(|func| self.function_matches_network_pass(func)) .collect::>(); debug!( "Found {} test functions out of {} in {:?}", @@ -826,7 +846,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { ); if let Some(ref progress) = progress { - progress.set_prefix(format!("{}\n{warn}\n", &func.name)); + progress.set_prefix(format!("{}\n{warn}\n", func.name)); } else { let _ = sh_warn!("{warn}"); } @@ -1052,7 +1072,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { self.cr.name, &func.name, fuzz_config.timeout, - fuzz_config.runs, + if fuzz_config.run.is_some() { 1 } else { fuzz_config.runs }, ); let state = self.build_fuzz_state(false); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 6e0acebc67225..5d8378c50c9ac 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -3000,7 +3000,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132459 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| @@ -3023,7 +3023,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", "deployment": { - "gas": 132459, + "gas": 132471, "size": 396 }, "functions": { diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 0eeb3757982e9..3eebca475a781 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -577,6 +577,32 @@ forgetest_init!(can_get_evm_opts, |prj, _cmd| { } }); +// Regression test for : +// the bare `ETH_RPC_URL` env var must NOT cause `forge` commands to set +// `eth_rpc_url` (which would silently fork all `forge test` runs). +// Only `--rpc-url`, `foundry.toml`, the `FOUNDRY_ETH_RPC_URL` env var, or +// cheatcodes should configure forking. +forgetest_init!(eth_rpc_url_env_does_not_set_fork_url, |prj, _cmd| { + prj.initialize_default_contracts(); + let url = "http://127.0.0.1:8545"; + + let mut cmd = prj.forge_bin(); + cmd.arg("config") + .arg("--root") + .arg(prj.root()) + .arg("--json") + .env("ETH_RPC_URL", url) + // Make sure the figment-style env var is not set in the test environment. + .env_remove("FOUNDRY_ETH_RPC_URL"); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let config: Config = serde_json::from_str(stdout.as_ref()).unwrap(); + assert_eq!( + config.eth_rpc_url, None, + "bare ETH_RPC_URL must not propagate to forge config (regression #14538)" + ); +}); + // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { prj.initialize_default_contracts(); @@ -1269,6 +1295,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "show_progress": false, "fuzz": { "runs": 256, + "run": null, + "worker": null, "fail_on_revert": true, "max_test_rejects": 65536, "seed": null, diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index 48a17c723b261..77d5a5e84cfbb 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -70,8 +70,13 @@ Suite result: FAILED. 0 passed; 7 failed; 0 skipped; [ELAPSED] .stdout_eq( r#"No files changed, compilation skipped ... +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != [..]] testShouldFailExpectRevertNestedCreateInnerAddress() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterNestedCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterTopLevelCreate() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailExpectRevertsNotOnImmediateNextCall() ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#, ); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index 04fb2369d83b0..ba01767d58b26 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -425,3 +425,107 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// Checks that tests annotated with `forge-config: default.networks.network` run on the correct +// EVM network, and that unannotated tests run on the globally configured network. +// +// Each test makes a real call to the Tempo `TipFeeManager` precompile at +// `0xfeec000000000000000000000000000000000000` (a Tempo-only contract that exists on the +// Moderato testnet and is auto-injected by the in-memory Tempo EVM): +// +// * The default-network test asserts the precompile has no code (it does not exist on Ethereum). +// * The Tempo-network test asserts the precompile has code and `userTokens(address)` returns the +// unset zero-address sentinel, proving the Tempo network was actually selected for that test and +// the Tempo genesis state was loaded. +forgetest!(per_test_network_routing, |prj, cmd| { + prj.add_test( + "inline.sol", + r#" + address constant TIP_FEE_MANAGER = 0xfeEC000000000000000000000000000000000000; + + contract DefaultNetwork { + // No annotation -> runs on the globally selected network (Ethereum by default). + // The Tempo FeeManager precompile must NOT exist here. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + } + + contract TempoNetwork { + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + // Sentinel bytecode (0xef) is injected at every Tempo precompile address. + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + // Call a Tempo-only method: `userTokens(address)` returns the user's preferred + // fee token, or the zero address when none is set. + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + + // Mixed contract: one function annotated with Tempo, one unannotated (runs on Ethereum). + contract MixedNetwork { + // No annotation -> runs on Ethereum; precompile must be absent. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + "#, + ); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 3 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index fd69907be2f09..8420e24eb3df7 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,4 +1,7 @@ -use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; +use forge_lint::{ + linter::Lint, + sol::{self, SolLint}, +}; use foundry_config::{ DenyLevel, LintSeverity, LinterConfig, SolidityErrorCode, lint::LintSpecificConfig, }; @@ -203,7 +206,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -230,7 +233,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -610,7 +613,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -637,7 +640,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -665,7 +668,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 13 │ uint256 result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift "# @@ -694,7 +697,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]).stdout_eq(str![[r#" @@ -855,7 +858,7 @@ note[unused-import]: unused imports should be removed 8 │ import { _PascalCaseInfo } from "./ContractWithLints.sol"; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import "#]]); @@ -887,7 +890,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 6 │ uint256 public CounterB_Fail_Lint; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterBFailLint` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable "#]]); @@ -992,7 +995,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { ], "children": [ { - "message": "https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic", + "message": "https://getfoundry.sh/forge/linting/unwrapped-modifier-logic", "code": null, "level": "help", "spans": [], @@ -1048,7 +1051,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { "rendered": null } ], - "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" + "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" } "#]], ); @@ -1129,46 +1132,46 @@ Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. #[tokio::test] async fn ensure_lint_rule_docs() { - const FOUNDRY_BOOK_LINT_PAGE_URL: &str = "https://book.getfoundry.sh/forge/linting"; - - // Fetch the content of the lint reference - let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { - Ok(resp) => { - assert!( - resp.status().is_success(), - "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", - status = resp.status() - ); - match resp.text().await { - Ok(text) => text, - Err(e) => { - panic!("Failed to read response text: {e}"); - } + let client = reqwest::Client::new(); + let mut failures = Vec::new(); + + for lint in registered_lints() { + let url = lint.help(); + let response = match client.get(url).send().await { + Ok(response) => response, + Err(err) => { + failures.push(format!("{} ({url}) could not be fetched: {err}", lint.id())); + continue; } + }; + + if !response.status().is_success() { + failures.push(format!("{} ({url}) returned HTTP {}", lint.id(), response.status())); + continue; } - Err(e) => { - panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); - } - }; - // Ensure no missing lints - let mut missing_lints = Vec::new(); - for lint in REGISTERED_LINTS { + let content = match response.text().await { + Ok(content) => content.to_lowercase(), + Err(err) => { + failures + .push(format!("{} ({url}) response body could not be read: {err}", lint.id())); + continue; + } + }; + let selector = lint.id().to_lowercase(); let selector_with_space = selector.replace('-', " "); - if !content.to_lowercase().contains(&selector) - && !content.to_lowercase().contains(&selector_with_space) - { - missing_lints.push(lint.id()); + if !content.contains(&selector) && !content.contains(&selector_with_space) { + failures.push(format!("{} ({url}) did not mention the lint id", lint.id())); } } - if !missing_lints.is_empty() { + if !failures.is_empty() { let mut msg = String::from( - "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + "Foundry Book lint validation failed. The following lint pages are missing or invalid:\n", ); - for lint in missing_lints { - msg.push_str(&format!(" - {lint}\n")); + for failure in failures { + msg.push_str(&format!(" - {failure}\n")); } msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); panic!("{msg}"); @@ -1177,11 +1180,21 @@ async fn ensure_lint_rule_docs() { #[test] fn ensure_no_privileged_lint_id() { - for lint in REGISTERED_LINTS { + for lint in registered_lints() { assert_ne!(lint.id(), "all", "lint-id 'all' is reserved. Please use a different id"); } } +fn registered_lints() -> impl Iterator { + sol::high::REGISTERED_LINTS + .iter() + .chain(sol::med::REGISTERED_LINTS) + .chain(sol::low::REGISTERED_LINTS) + .chain(sol::info::REGISTERED_LINTS) + .chain(sol::gas::REGISTERED_LINTS) + .chain(sol::codesize::REGISTERED_LINTS) +} + // forgetest!(dependency_warnings_do_not_affect_lint_exit_code, |prj, cmd| { // Library with code that triggers a solc warning (unused local variable) @@ -1265,3 +1278,195 @@ contract OldContract { "# ]]); }); + +const PRAGMA_INCONSISTENT_ALPHA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Alpha {} +"#; + +const PRAGMA_INCONSISTENT_BETA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract Beta {} +"#; + +forgetest!(pragma_inconsistent_cross_file, |prj, cmd| { + prj.add_source("Alpha", PRAGMA_INCONSISTENT_ALPHA); + prj.add_source("Beta", PRAGMA_INCONSISTENT_BETA); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +const PRAGMA_EXACT_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract A {} +"#; + +const PRAGMA_EXACT_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract B {} +"#; + +const PRAGMA_EXACT_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract C {} +"#; + +const PRAGMA_CARET_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract A {} +"#; + +const PRAGMA_CARET_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract B {} +"#; + +const PRAGMA_CARET_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract C {} +"#; + +const NO_PRAGMA_C: &str = r#" +// SPDX-License-Identifier: MIT + +contract C {} +"#; + +// Multiple files all using the exact same pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_exact_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_EXACT_C); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Multiple files all using the exact same caret pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_caret_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + prj.add_source("B", PRAGMA_CARET_B); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// A single file in the project cannot conflict with itself. +forgetest!(pragma_inconsistent_single_file_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Even files that share a requirement still emit when ANY other variant exists. +// Two files with `0.8.20` plus one file with `^0.8.20` => 3 emits total. +forgetest!(pragma_inconsistent_duplicates_among_conflict, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_CARET_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +// Files without a `pragma solidity` directive must not affect the conflict computation. +// Note: `add_raw_source` is used here to bypass the helper that would otherwise inject a default +// `pragma solidity =;` for files that omit one. +forgetest!(pragma_inconsistent_files_without_pragma, |prj, cmd| { + prj.add_raw_source("A", PRAGMA_EXACT_A); + prj.add_raw_source("B", PRAGMA_CARET_B); + // C has no pragma at all; should be ignored by the cross-file check. + prj.add_raw_source("C", NO_PRAGMA_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); diff --git a/crates/forge/tests/cli/lint/geiger.rs b/crates/forge/tests/cli/lint/geiger.rs index faecfb212fb90..202866e83e35f 100644 --- a/crates/forge/tests/cli/lint/geiger.rs +++ b/crates/forge/tests/cli/lint/geiger.rs @@ -21,7 +21,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ bytes memory stuff = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:10:20 @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 10 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:11:20 @@ -100,7 +100,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 11 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 3 linter note(s) ... diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 242a0ebb4267f..031d80f0cf071 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -3241,7 +3241,7 @@ contract CounterScript is Script { error: the following required arguments were not provided: --broadcast -Usage: [..] script --broadcast --verify --rpc-url [ARGS]... +Usage: [..] script --broadcast --verify --rpc-url [ARGS]... For more information, try '--help'. diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index fefefb30d9b15..454b014a6e1bc 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -1,4 +1,5 @@ use alloy_primitives::U256; +use foundry_evm::fuzz::BaseCounterExample; use foundry_test_utils::{TestCommand, forgetest_init, str}; use regex::Regex; @@ -845,6 +846,8 @@ forgetest_init!(test_fuzz_random_uint_varies_across_runs, |prj, cmd| { prj.add_test( "RandomFuzzTest.t.sol", r#" +pragma solidity >=0.8.0; + import {Test} from "forge-std/Test.sol"; contract RandomFuzzTest is Test { @@ -868,3 +871,145 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) ... "#]]); }); + +forgetest_init!(test_fuzz_run_replays_random_uint_failure, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + function testFuzz_randomUint_shouldFail(uint256) public { + uint256 rand = vm.randomUint(0, 4); + assertTrue(rand != 0, "hit value 0"); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: hit value 0; counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + + cmd.args(["test", "--fuzz-seed", "1", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + assert_eq!(persisted_failure.fuzz.seed, Some(U256::from(1))); + assert_eq!(persisted_failure.fuzz.worker, Some(0)); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + cmd.forge_fuse() + .args([ + "test", + "--fuzz-seed", + "1", + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure().stdout_eq(expected_output); +}); + +forgetest_init!(test_fuzz_rerun_replays_random_uint_failure_without_seed, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + error Random(uint256 value); + + function testFuzz_randomUint_shouldFail(uint256) public { + revert Random(vm.randomUint()); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: Random([..]); counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]; + + let assert = cmd + .args(["test", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + let reason = random_failure_reason(&stdout); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + let fuzz_seed = format!("{:#x}", persisted_failure.fuzz.seed.unwrap()); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + let assert = cmd + .forge_fuse() + .args([ + "test", + "--fuzz-seed", + &fuzz_seed, + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd + .forge_fuse() + .args(["test", "--rerun", "-j1"]) + .assert_failure() + .stdout_eq(expected_output); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); +}); + +fn random_failure_reason(stdout: &str) -> String { + Regex::new(r"\[FAIL: (Random\([^)]+\))") + .unwrap() + .captures(stdout) + .unwrap_or_else(|| panic!("{stdout}"))[1] + .to_string() +} diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 32bfe6a98a9fd..3803385b496ab 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -783,6 +783,66 @@ ParserError: Source "Missing.sol" not found: File not found. Searched the follow "#]]); }); +// https://github.com/foundry-rs/foundry/issues/10463 +forgetest_init!(issue_10463, |prj, cmd| { + prj.add_test( + "Issue10463.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue10463Test is Test { + event Foo(); + + error CustomError(uint256 code); + + function revertingBefore(bool shouldRevert) external { + if (shouldRevert) revert(); + emit Foo(); + } + + function revertingWithReason() external pure { + revert("revert reason"); + } + + function revertingWithCustomError() external pure { + revert CustomError(42); + } + + function testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() public { + vm.expectEmit(); + emit Foo(); + + this.revertingBefore(true); + } + + function testExpectEmitPreservesRevertReason() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithReason(); + } + + function testExpectEmitPreservesCustomError() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithCustomError(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue10463.t.sol:Issue10463Test +[FAIL: CustomError(42)] testExpectEmitPreservesCustomError() ([GAS]) +[FAIL: revert reason] testExpectEmitPreservesRevertReason() ([GAS]) +[FAIL: EvmError: Revert] testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() ([GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + // https://github.com/foundry-rs/foundry/issues/12803 // Test gas underflow prevention on Cancun (no EIP-7702 gas floor) forgetest_init!(issue_12803_cancun, |prj, cmd| { diff --git a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol index 7e482c6673155..838183a1b0b5e 100644 --- a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol +++ b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol @@ -233,6 +233,63 @@ contract ExpectRevertWithReverterFailureTest is DSTest { aContract.doNotRevert(); aContract.callAndRevert(); } + + // + // Regression: must fail because 0xdead is not the actual reverter when a + // top-level CREATE constructor reverts directly. + function testShouldFailExpectRevertWrongReverterTopLevelCreate() public { + vm.expectRevert(address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // even when an exact-bytes pattern is also supplied for a top-level CREATE. + function testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() public { + vm.expectRevert(abi.encodePacked("Reverted by DContract"), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // for `expectPartialRevert(bytes4, address)` against a top-level CREATE. + function testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() public { + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail when the innermost reverting frame is a nested + // CREATE and the reverter address argument does not match the would-be + // deployed address of the failed deployment. + function testShouldFailExpectRevertWrongReverterNestedCreate() public { + vm.expectRevert(address(0xdead)); + new NestedDContractCreator(); + } + + // + // Regression: documents the intended semantics for nested CREATEs — the + // matched reverter is the *outer* would-be-deployed address (the contract + // whose deployment failed), NOT the innermost reverting CREATE's address. + // Supplying the inner address must fail. + function testShouldFailExpectRevertNestedCreateInnerAddress() public { + // Outer = NestedDContractCreator at this contract's next nonce. + // Inner = DContract created from inside the outer constructor (deployer + // is the outer, nonce 1). + address outer = + vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + address inner = vm.computeCreateAddress(outer, 1); + vm.expectRevert(inner); + new NestedDContractCreator(); + } +} + +// Used by `testShouldFailExpectRevertWrongReverterNestedCreate`: a contract whose +// constructor directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } } contract ExpectRevertCountFailureTest is DSTest { diff --git a/crates/lint/Cargo.toml b/crates/lint/Cargo.toml index 87864721432d9..589a0a5069e37 100644 --- a/crates/lint/Cargo.toml +++ b/crates/lint/Cargo.toml @@ -24,3 +24,7 @@ eyre.workspace = true heck.workspace = true rayon.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/lint/README.md b/crates/lint/README.md index e7c6471555da3..9d5dad0c0272e 100644 --- a/crates/lint/README.md +++ b/crates/lint/README.md @@ -17,11 +17,14 @@ It helps enforce best practices and improve code quality within Foundry projects - `divide-before-multiply`: Warns against performing division before multiplication in the same expression, which can cause precision loss. - `incorrect-erc20-interface`: Flags ERC20 interfaces and implementations with non-compliant function signatures. - `incorrect-erc721-interface`: Flags ERC721 interfaces and implementations with non-compliant function signatures. + - `tx-origin`: Flags use of `tx.origin` in authorization-like predicates. - `unsafe-typecast`: Typecasts that can truncate values should be checked. - **Low Severity:** - `block-timestamp`: Warns when `block.timestamp` is used in a comparison, as it may be manipulated by validators. + - `missing-zero-check`: Address parameter is used in a state write or value transfer without a zero-address check. - **Informational / Style Guide:** - `boolean-equal`: Boolean comparisons to constants should be simplified. + - `too-many-digits`: Numeric literals with 5+ consecutive zeros are error-prone. - `pascal-case-struct`: Flags for struct names not adhering to `PascalCase`. - `mixed-case-function`: Flags for function names not adhering to `mixedCase`. - `mixed-case-variable`: Flags for mutable variable names not adhering to `mixedCase`. @@ -31,10 +34,15 @@ It helps enforce best practices and improve code quality within Foundry projects - `unaliased-plain-import`: Use named imports `{A, B}` or alias `import ".." as X`. - `named-struct-fields`: Prefer initializing structs with named fields. - `unsafe-cheatcode`: Usage of unsafe cheatcodes that can perform dangerous operations. + - `multi-contract-file`: Prefer having only one contract, interface, or library per file. + - `interface-file-naming`: Interface file names should be prefixed with `I`. + - `interface-naming`: Interface names should be prefixed with `I`. + - `pragma-inconsistent`: Flags projects whose source files declare different Solidity pragma version requirements. - **Gas Optimizations:** - `asm-keccak256`: Recommends using inline assembly for `keccak256` for potential gas savings. - `could-be-immutable`: Recommends declaring constructor-only state variables as `immutable`. - `custom-errors`: Recommends using custom errors instead of strings and plain reverts for potential gas savings. + - `unused-state-variables`: State variables that are never used should be removed. - **Code Size:** - `unwrapped-modifier-logic`: Recommends wrapping modifier logic to reduce contract code size. diff --git a/crates/lint/docs/README.md b/crates/lint/docs/README.md new file mode 100644 index 0000000000000..5eb4110a92e57 --- /dev/null +++ b/crates/lint/docs/README.md @@ -0,0 +1,52 @@ +# Forge lint documentation + +This directory contains one markdown file per registered `forge-lint` rule. Each file is referenced +by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the +[Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. + +## Adding a new lint + +When you add a new lint with `declare_forge_lint!`, you **must** also add a documentation file at +`crates/lint/docs/.md`. The presence of the file is enforced by the +`registered_lints_have_docs` unit test in [`crates/lint/src/sol/mod.rs`](../src/sol/mod.rs). + +Use [`_template.md`](./_template.md) as a starting point. + +## File structure + +Each lint doc file should follow this structure: + +```markdown +# + +**Severity**: `` +**ID**: `` + +A one-paragraph description of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` + +## Configuration + +Document any inline-config or `foundry.toml` options that affect this lint, if any. +``` diff --git a/crates/lint/docs/_template.md b/crates/lint/docs/_template.md new file mode 100644 index 0000000000000..41c735a0ba579 --- /dev/null +++ b/crates/lint/docs/_template.md @@ -0,0 +1,28 @@ +# + +**Severity**: `` +**ID**: `` + +One-paragraph summary of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` diff --git a/crates/lint/docs/asm-keccak256.md b/crates/lint/docs/asm-keccak256.md new file mode 100644 index 0000000000000..4678cfe9f8d12 --- /dev/null +++ b/crates/lint/docs/asm-keccak256.md @@ -0,0 +1,42 @@ +# Inefficient keccak256 call + +**Severity**: `Gas` +**ID**: `asm-keccak256` + +Flags calls to the high-level `keccak256(...)` builtin that can be cheaply rewritten with inline +assembly. + +## What it does + +Reports `keccak256(arg)` calls and (when possible) emits a fix suggestion that uses inline +assembly to compute the hash directly, avoiding the overhead of the high-level call. + +## Why is this bad? + +The high-level `keccak256` call performs additional memory management and ABI encoding compared +to a direct `keccak256(ptr, len)` opcode invocation. In hot paths the difference is visible in +gas reports. + +## Example + +### Bad + +```solidity +bytes32 h = keccak256(abi.encodePacked(a, b)); +``` + +### Good + +```solidity +bytes32 h; +assembly ("memory-safe") { + let m := mload(0x40) + mstore(m, a) + mstore(add(m, 0x20), b) + h := keccak256(m, 0x40) +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/block-timestamp.md b/crates/lint/docs/block-timestamp.md new file mode 100644 index 0000000000000..a51b55ff5d8cc --- /dev/null +++ b/crates/lint/docs/block-timestamp.md @@ -0,0 +1,44 @@ +# Use of block.timestamp in comparisons + +**Severity**: `Low` +**ID**: `block-timestamp` + +Flags use of `block.timestamp` as an operand of a comparison, where its value can be slightly +manipulated by the block proposer. + +## What it does + +Reports any comparison expression (`<`, `<=`, `>`, `>=`, `==`, `!=`) that directly or +transitively reads `block.timestamp`. + +## Why is this bad? + +Block proposers can adjust `block.timestamp` within a small window (a few seconds). This is +usually harmless, but for short-window logic — auctions ending, randomness, time-locked +withdrawals — a few seconds of manipulation can be enough for an attacker to capture value. + +Using `block.timestamp` for general scheduling (hours/days) is fine; what's risky is fine-grained +timing and treating timestamps as a source of randomness. + +## Example + +### Bad + +```solidity +function settle() external { + require(block.timestamp >= auctionEnd, "auction ongoing"); + // ... +} +``` + +### Good + +```solidity +// Prefer block numbers for tight windows, or accept a clearly large grace period. +require(block.number >= endBlock, "auction ongoing"); +``` + +## Notes + +This lint is intentionally conservative: not every flagged comparison is exploitable. Review +each occurrence in context. diff --git a/crates/lint/docs/boolean-cst.md b/crates/lint/docs/boolean-cst.md new file mode 100644 index 0000000000000..f5c65dfec2789 --- /dev/null +++ b/crates/lint/docs/boolean-cst.md @@ -0,0 +1,37 @@ +# Misuse of a boolean constant + +**Severity**: `Med` +**ID**: `boolean-cst` + +Flags expressions where a boolean constant (`true`/`false`) is used as a control-flow condition +or operand of a boolean operator, which usually indicates dead code or a leftover debug toggle. + +## What it does + +Reports `if (true)`, `if (false)`, `while (true)` outside of intentional infinite loops, and +boolean operators (`&&`, `||`) where one side is a literal `true`/`false`. + +## Why is this bad? + +A literal boolean as a condition makes the surrounding branch dead, hides logic errors, or +preserves a forgotten debug shortcut that bypasses real checks. + +## Example + +### Bad + +```solidity +if (true) { // always taken + doSomething(); +} +require(condition && true, "unreachable"); // 'true' is redundant +``` + +### Good + +```solidity +if (condition) { + doSomething(); +} +require(condition, "..."); +``` diff --git a/crates/lint/docs/boolean-equal.md b/crates/lint/docs/boolean-equal.md new file mode 100644 index 0000000000000..9397003b039b4 --- /dev/null +++ b/crates/lint/docs/boolean-equal.md @@ -0,0 +1,34 @@ +# Boolean comparison to a constant + +**Severity**: `Info` +**ID**: `boolean-equal` + +Flags expressions of the form `x == true`, `x == false`, `x != true`, `x != false`, which can be +simplified. + +## What it does + +Reports any equality comparison between a boolean expression and a literal `true` or `false`. + +## Why is this bad? + +Comparing a boolean to a boolean literal is redundant and harms readability. Use the boolean +expression directly (or its negation). + +## Example + +### Bad + +```solidity +if (paused == true) revert(); +if (paused == false) doSomething(); +require(ok != false, "fail"); +``` + +### Good + +```solidity +if (paused) revert(); +if (!paused) doSomething(); +require(ok, "fail"); +``` diff --git a/crates/lint/docs/could-be-immutable.md b/crates/lint/docs/could-be-immutable.md new file mode 100644 index 0000000000000..bda1de6379955 --- /dev/null +++ b/crates/lint/docs/could-be-immutable.md @@ -0,0 +1,42 @@ +# State variable could be immutable + +**Severity**: `Gas` +**ID**: `could-be-immutable` + +Flags state variables that are assigned only in the constructor and never written to afterward — +making them eligible to be declared `immutable`. + +## What it does + +Reports each non-`constant`, non-`immutable` state variable whose only writes occur in the +constructor (or in initialization at declaration time). + +## Why is this bad? + +`immutable` state variables are stored in the deployed bytecode rather than in storage, eliminating +an `SLOAD` per access and saving substantial gas across the contract's lifetime. Declaring such +variables `immutable` also expresses intent and prevents future writes. + +## Example + +### Bad + +```solidity +contract C { + address owner; + constructor() { owner = msg.sender; } +} +``` + +### Good + +```solidity +contract C { + address immutable OWNER; + constructor() { OWNER = msg.sender; } +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/custom-errors.md b/crates/lint/docs/custom-errors.md new file mode 100644 index 0000000000000..9e01e01d593e9 --- /dev/null +++ b/crates/lint/docs/custom-errors.md @@ -0,0 +1,45 @@ +# Prefer custom errors over revert strings + +**Severity**: `Gas` +**ID**: `custom-errors` + +Flags `require(cond, "message")`, `revert("message")`, and `revert()` calls; suggests replacing +them with a `revert CustomError(...)`. + +## What it does + +Reports `require` calls whose second argument is a string literal, and `revert(...)` calls that +are either bare or have a string-literal argument. + +## Why is this bad? + +Custom errors: +- cost less gas than encoding/decoding a string, +- can carry typed parameters for richer diagnostics, +- shrink contract bytecode (string constants live in code). + +Solidity 0.8.4+ supports custom errors natively. + +## Example + +### Bad + +```solidity +require(amount > 0, "amount must be > 0"); +revert("not authorized"); +revert(); +``` + +### Good + +```solidity +error AmountZero(); +error NotAuthorized(); + +if (amount == 0) revert AmountZero(); +if (!authorized) revert NotAuthorized(); +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/divide-before-multiply.md b/crates/lint/docs/divide-before-multiply.md new file mode 100644 index 0000000000000..f082bef19a1bd --- /dev/null +++ b/crates/lint/docs/divide-before-multiply.md @@ -0,0 +1,32 @@ +# Divide before multiply + +**Severity**: `Med` +**ID**: `divide-before-multiply` + +Flags arithmetic expressions where division is performed before multiplication, which can cause +unintended precision loss in integer arithmetic. + +## What it does + +Warns on expressions of the form `(a / b) * c` (or equivalent shapes), where the integer division +truncates before the result is multiplied. + +## Why is this bad? + +Solidity's integer division truncates toward zero. Performing `(a / b) * c` discards the remainder +of `a / b` before scaling, while `(a * c) / b` preserves precision. This pattern frequently +manifests as fee/share/yield miscalculations. + +## Example + +### Bad + +```solidity +uint256 share = (amount / total) * weight; // truncates first, then scales +``` + +### Good + +```solidity +uint256 share = (amount * weight) / total; // preserves precision +``` diff --git a/crates/lint/docs/erc20-unchecked-transfer.md b/crates/lint/docs/erc20-unchecked-transfer.md new file mode 100644 index 0000000000000..d7d053e020cca --- /dev/null +++ b/crates/lint/docs/erc20-unchecked-transfer.md @@ -0,0 +1,43 @@ +# Unchecked ERC20 transfer return value + +**Severity**: `High` +**ID**: `erc20-unchecked-transfer` + +Flags calls to ERC20 `transfer` and `transferFrom` where the boolean return value is ignored. + +## What it does + +Warns when a function with the same signature as +`transfer(address,uint256)` or `transferFrom(address,address,uint256)` and a `bool` return type is +invoked but the result is not checked. + +## Why is this bad? + +The ERC20 spec allows tokens to signal failure by returning `false` instead of reverting. Ignoring +the return value lets a "failed" transfer go unnoticed, allowing accounting to drift and creating +common DeFi exploits. Use a wrapper such as OpenZeppelin's `SafeERC20` or check the boolean +explicitly. + +## Example + +### Bad + +```solidity +token.transfer(to, amount); +token.transferFrom(from, to, amount); +``` + +### Good + +```solidity +require(token.transfer(to, amount), "transfer failed"); +require(token.transferFrom(from, to, amount), "transferFrom failed"); + +// or use SafeERC20 +SafeERC20.safeTransfer(token, to, amount); +``` + +## Notes + +This lint can produce false positives when the callee does not strictly conform to the ERC20 +interface (e.g. tokens that revert on failure rather than returning `false`). diff --git a/crates/lint/docs/incorrect-erc20-interface.md b/crates/lint/docs/incorrect-erc20-interface.md new file mode 100644 index 0000000000000..65fb8313c205f --- /dev/null +++ b/crates/lint/docs/incorrect-erc20-interface.md @@ -0,0 +1,42 @@ +# Incorrect ERC20 interface + +**Severity**: `Med` +**ID**: `incorrect-erc20-interface` + +Flags interfaces or contracts whose function signatures match an ERC20 method by name and +parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC20 method +(`totalSupply`, `balanceOf`, `transfer`, `transferFrom`, `approve`, `allowance`), the lint checks +that the return type matches the spec. A mismatch is reported. + +## Why is this bad? + +Tokens that diverge from the ERC20 spec break composability with the wider ecosystem (DEXes, +lending protocols, multisigs) and are a common source of integration bugs and exploits. + +## Example + +### Bad + +```solidity +interface IBadERC20 { + function balanceOf(address) external view returns (bool); // should be uint256 + function transfer(address, uint256) external; // should return bool +} +``` + +### Good + +```solidity +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-erc721-interface.md b/crates/lint/docs/incorrect-erc721-interface.md new file mode 100644 index 0000000000000..4803afdde7cc1 --- /dev/null +++ b/crates/lint/docs/incorrect-erc721-interface.md @@ -0,0 +1,48 @@ +# Incorrect ERC721 interface + +**Severity**: `Med` +**ID**: `incorrect-erc721-interface` + +Flags interfaces or contracts whose function signatures match an ERC721 (or ERC165) method by +name and parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC721/ERC165 method +(`balanceOf`, `ownerOf`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, +`getApproved`, `isApprovedForAll`, `supportsInterface`), the lint checks that the return type +matches the spec. A mismatch is reported. + +## Why is this bad? + +Non-conforming NFT contracts break marketplaces, indexers, and any protocol that relies on the +ERC721 spec. A wrong return type often compiles and deploys silently but causes integration +failures at runtime. + +## Example + +### Bad + +```solidity +interface IBadERC721 { + function balanceOf(address) external view returns (bool); // should be uint256 + function ownerOf(uint256) external view returns (bool); // should be address + function supportsInterface(bytes4) external view returns (uint256); // should be bool +} +``` + +### Good + +```solidity +interface IERC721 { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function transferFrom(address from, address to, uint256 tokenId) external; + function approve(address to, uint256 tokenId) external; + function setApprovalForAll(address operator, bool approved) external; + function getApproved(uint256 tokenId) external view returns (address); + function isApprovedForAll(address owner, address operator) external view returns (bool); + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-shift.md b/crates/lint/docs/incorrect-shift.md new file mode 100644 index 0000000000000..a9a70c7f93128 --- /dev/null +++ b/crates/lint/docs/incorrect-shift.md @@ -0,0 +1,37 @@ +# Incorrect shift order + +**Severity**: `High` +**ID**: `incorrect-shift` + +Flags shift operations where a literal appears on the left and a non-literal on the right, which +is almost always the wrong operand order. + +## What it does + +Warns when the left-hand operand of `<<` or `>>` is a numeric literal and the right-hand operand +is a non-literal expression (e.g. a variable, function call, or composite expression). + +## Why is this bad? + +Shift expressions like `2 << x` are usually a typo for `x << 2`. In the former, the *value being +shifted* is a tiny constant and the *shift amount* is dynamic — almost never the intended +behavior, and a known source of bugs in production contracts. + +## Example + +### Bad + +```solidity +result = 2 << stateValue; // shift amount comes from state +result = 8 >> localValue; // shift amount comes from a local +result = 16 << (stateValue + 1); // shift amount is a dynamic expression +``` + +### Good + +```solidity +result = stateValue << 2; +result = localValue >> 3; +result = stateValue << localShiftAmount; +result = 1 << 8; // both literals — fine +``` diff --git a/crates/lint/docs/inline-assembly.md b/crates/lint/docs/inline-assembly.md new file mode 100644 index 0000000000000..bba61148b84c5 --- /dev/null +++ b/crates/lint/docs/inline-assembly.md @@ -0,0 +1,69 @@ +# Inline assembly + +**Severity**: `Info` +**ID**: `inline-assembly` + +Flags every `assembly { ... }` block. Inline assembly bypasses many of Solidity's safety +features (type checks, overflow checks, memory layout invariants) and is a common source of +high-impact bugs, so each occurrence should be reviewed deliberately. + +## What it does + +Reports every inline assembly statement, including blocks declared with the `"evmasm"` dialect +and/or the `("memory-safe")` flag. Blocks declared as memory-safe — either via the modern +`("memory-safe")` flag or the legacy `/// @solidity memory-safe-assembly` NatSpec marker — are +still reported, but with a softer message acknowledging the developer attestation: review +focuses on business logic and side effects rather than memory layout. + +## Why is this bad? + +Assembly skips Solidity's compile-time checks and many of its runtime guarantees. Mistakes +inside an `assembly` block can corrupt memory, break the free memory pointer, leak storage, +escalate privileges via `delegatecall`, or destroy the contract via `selfdestruct`. Even when +required for gas or features unavailable in high-level Solidity, assembly should be small, +documented, and reviewed. + +## When inline assembly is reasonable + +Some idioms are widely used and generally safe: + +- Reading transaction/chain context: `chainid()`, `gas()`, `returndatasize()`. +- Probing code: `codesize()`, `extcodesize(addr)`, `extcodehash(addr)`. +- Reading the free memory pointer: `mload(0x40)`. +- Cheap hashing of a known memory layout, when paired with `("memory-safe")`. + +If you must use assembly: + +1. Keep the block minimal and well-commented. +2. Add the `("memory-safe")` flag when the block does not violate Solidity's memory model, so + the optimizer (and reviewers) can rely on it. The legacy + `/// @solidity memory-safe-assembly` NatSpec marker on the line directly above the block is + also recognized for compatibility with older codebases. +3. Suppress the lint locally to mark the block as audited: + ```solidity + // forge-lint: disable-next-line(inline-assembly) + assembly ("memory-safe") { /* reviewed: ... */ } + ``` + +## Example + +### Bad + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory) { + assembly { + let ok := call(gas(), target, 0, add(data.offset, 0), data.length, 0, 0) + // ... + } +} +``` + +### Good + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory result) { + bool ok; + (ok, result) = target.call(data); + require(ok, "call failed"); +} +``` diff --git a/crates/lint/docs/interface-file-naming.md b/crates/lint/docs/interface-file-naming.md new file mode 100644 index 0000000000000..ff72a0c175e8e --- /dev/null +++ b/crates/lint/docs/interface-file-naming.md @@ -0,0 +1,31 @@ +# Interface file naming + +**Severity**: `Info` +**ID**: `interface-file-naming` + +Flags Solidity files whose only top-level declaration is an interface but whose filename is not +prefixed with `I`. + +## What it does + +Reports interface-only files whose path basename does not start with `I` (e.g. `IERC20.sol`). + +## Why is this bad? + +Prefixing interface filenames with `I` is the prevailing convention in the Solidity ecosystem. +Following it makes import paths predictable and lets reviewers tell at a glance whether they are +looking at an interface or an implementation. + +## Example + +### Bad + +```text +contracts/Token.sol // file contains only `interface Token { ... }` +``` + +### Good + +```text +contracts/IToken.sol // file contains only `interface IToken { ... }` +``` diff --git a/crates/lint/docs/interface-naming.md b/crates/lint/docs/interface-naming.md new file mode 100644 index 0000000000000..5c6b12b946091 --- /dev/null +++ b/crates/lint/docs/interface-naming.md @@ -0,0 +1,31 @@ +# Interface name should be prefixed with 'I' + +**Severity**: `Info` +**ID**: `interface-naming` + +Flags `interface` declarations whose names are not prefixed with `I`. + +## What it does + +Reports `interface Foo` where `Foo` does not start with `I` (e.g. `IFoo`). + +## Why is this bad? + +Prefixing interfaces with `I` is the prevailing convention in Solidity codebases (`IERC20`, +`IERC721`, `IUniswapV2Pair`, ...). Following it makes the role of each type unambiguous at use +sites and aligns with the matching +[`interface-file-naming`](https://getfoundry.sh/forge/linting/interface-file-naming) lint. + +## Example + +### Bad + +```solidity +interface ERC20 { /* ... */ } +``` + +### Good + +```solidity +interface IERC20 { /* ... */ } +``` diff --git a/crates/lint/docs/missing-zero-check.md b/crates/lint/docs/missing-zero-check.md new file mode 100644 index 0000000000000..7eab1f3a00117 --- /dev/null +++ b/crates/lint/docs/missing-zero-check.md @@ -0,0 +1,39 @@ +# Missing zero-address check + +**Severity**: `Low` +**ID**: `missing-zero-check` + +Flags entry-point functions and constructors where an `address` parameter flows into a state write +or value transfer without a zero-address guard. + +## What it does + +Performs a taint analysis from each `address` parameter of an externally callable, state-mutating +function (or constructor) and reports a parameter that reaches a sink (state write, `transfer`, +`call{value: ...}`, etc.) without first being compared against `address(0)` in an `if`/`require`/ +`assert` predicate. + +## Why is this bad? + +Forgetting a zero-address check is a common source of value loss: tokens become permanently +unrecoverable, ownership is renounced unintentionally, or upgrades are bricked. Adding an explicit +guard is cheap and removes an entire class of operational mistakes. + +## Example + +### Bad + +```solidity +function setOwner(address newOwner) external onlyOwner { + owner = newOwner; // no zero-address check +} +``` + +### Good + +```solidity +function setOwner(address newOwner) external onlyOwner { + require(newOwner != address(0), "zero address"); + owner = newOwner; +} +``` diff --git a/crates/lint/docs/mixed-case-function.md b/crates/lint/docs/mixed-case-function.md new file mode 100644 index 0000000000000..9997dcb5691c7 --- /dev/null +++ b/crates/lint/docs/mixed-case-function.md @@ -0,0 +1,32 @@ +# Function names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-function` + +Flags function names that do not follow `mixedCase`. + +## What it does + +Reports functions whose names contain underscores, start with an uppercase letter, or otherwise +deviate from `mixedCase`. Test functions starting with `test`, `invariant_`, or `statefulFuzz` +and user-defined patterns (e.g. `ERC20`) are exempted. + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for function names. Consistent style makes call +sites uniform, helps editor tooling, and reduces friction in code review. + +## Example + +### Bad + +```solidity +function get_balance() external view returns (uint256); +function GetBalance() external view returns (uint256); +``` + +### Good + +```solidity +function getBalance() external view returns (uint256); +``` diff --git a/crates/lint/docs/mixed-case-variable.md b/crates/lint/docs/mixed-case-variable.md new file mode 100644 index 0000000000000..3341e1a0c48ad --- /dev/null +++ b/crates/lint/docs/mixed-case-variable.md @@ -0,0 +1,36 @@ +# Mutable variable names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-variable` + +Flags mutable variable names (locals, parameters, mutable state) that do not follow `mixedCase`. + +## What it does + +Reports mutable variable identifiers that contain underscores, start with an uppercase letter, +or otherwise deviate from `mixedCase`. + +`constant` and `immutable` state variables are not flagged by this lint — see +[`screaming-snake-case-const`](https://getfoundry.sh/forge/linting/screaming-snake-case-const) and +[`screaming-snake-case-immutable`](https://getfoundry.sh/forge/linting/screaming-snake-case-immutable). + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for mutable variables. Consistent style makes +code easier to scan and review. + +## Example + +### Bad + +```solidity +uint256 public total_supply; +address Owner; +``` + +### Good + +```solidity +uint256 public totalSupply; +address owner; +``` diff --git a/crates/lint/docs/multi-contract-file.md b/crates/lint/docs/multi-contract-file.md new file mode 100644 index 0000000000000..beabc827e4ea6 --- /dev/null +++ b/crates/lint/docs/multi-contract-file.md @@ -0,0 +1,37 @@ +# Multiple contracts in one file + +**Severity**: `Info` +**ID**: `multi-contract-file` + +Flags source files that declare more than one top-level contract, interface, or library. + +## What it does + +Reports each top-level `contract`, `interface`, or `library` definition (after the first) in a +file that contains more than one such declaration. + +## Why is this bad? + +Keeping one contract per file improves discoverability (`grep`, IDE jump-to-file), simplifies +import paths, and avoids unintentional bytecode bloat from artifacts that bundle unrelated +contracts. + +## Example + +### Bad + +```solidity +// File: Token.sol +contract TokenA { /* ... */ } +contract TokenB { /* ... */ } +``` + +### Good + +```solidity +// File: TokenA.sol +contract TokenA { /* ... */ } + +// File: TokenB.sol +contract TokenB { /* ... */ } +``` diff --git a/crates/lint/docs/named-struct-fields.md b/crates/lint/docs/named-struct-fields.md new file mode 100644 index 0000000000000..45713e2555ddc --- /dev/null +++ b/crates/lint/docs/named-struct-fields.md @@ -0,0 +1,31 @@ +# Prefer named struct fields + +**Severity**: `Info` +**ID**: `named-struct-fields` + +Flags struct construction expressions that pass fields positionally instead of by name. + +## What it does + +Reports `Struct(a, b, c)` style struct construction; suggests `Struct({ field1: a, field2: b, +field3: c })` instead. + +## Why is this bad? + +Positional struct construction is fragile: adding or reordering fields silently changes the +meaning of every existing call site. Named-field construction is self-documenting and resilient +to struct changes. + +## Example + +### Bad + +```solidity +User memory u = User(addr, 100, true); +``` + +### Good + +```solidity +User memory u = User({ wallet: addr, balance: 100, active: true }); +``` diff --git a/crates/lint/docs/pascal-case-struct.md b/crates/lint/docs/pascal-case-struct.md new file mode 100644 index 0000000000000..02a243bd56bf4 --- /dev/null +++ b/crates/lint/docs/pascal-case-struct.md @@ -0,0 +1,31 @@ +# Struct names should use PascalCase + +**Severity**: `Info` +**ID**: `pascal-case-struct` + +Flags struct definitions whose names do not follow `PascalCase`. + +## What it does + +Reports any `struct` whose identifier does not match the `PascalCase` convention. + +## Why is this bad? + +The Solidity style guide recommends `PascalCase` for type-like names (contracts, structs, +enums, libraries). Consistent casing makes code easier to scan and integrates with editor +features and external tooling. + +## Example + +### Bad + +```solidity +struct user_info { uint256 balance; } +struct USERINFO { uint256 balance; } +``` + +### Good + +```solidity +struct UserInfo { uint256 balance; } +``` diff --git a/crates/lint/docs/pragma-inconsistent.md b/crates/lint/docs/pragma-inconsistent.md new file mode 100644 index 0000000000000..095f45783773d --- /dev/null +++ b/crates/lint/docs/pragma-inconsistent.md @@ -0,0 +1,41 @@ +# Inconsistent pragma directives + +**Severity**: `Info` +**ID**: `pragma-inconsistent` + +Flags projects whose source files declare incompatible or differently-shaped Solidity version +pragmas. + +## What it does + +Inspects every `pragma solidity ...;` directive across all input source files and reports when +their version requirements are inconsistent (different exact versions, mixed caret/tilde/range +shapes, etc.). + +## Why is this bad? + +A project compiled under multiple Solidity versions can subtly change behavior between files +(e.g. checked arithmetic, default visibility, ABI encoding). Aligning pragmas across the project +removes a hidden source of integration bugs and makes upgrades coordinated. + +## Example + +### Bad + +```solidity +// A.sol +pragma solidity 0.8.18; + +// B.sol +pragma solidity ^0.8.20; + +// C.sol +pragma solidity >=0.7.0 <0.9.0; +``` + +### Good + +```solidity +// All files +pragma solidity 0.8.20; +``` diff --git a/crates/lint/docs/rtlo.md b/crates/lint/docs/rtlo.md new file mode 100644 index 0000000000000..58ce648752c6f --- /dev/null +++ b/crates/lint/docs/rtlo.md @@ -0,0 +1,32 @@ +# Right-to-left override character + +**Severity**: `High` +**ID**: `rtlo` + +Flags the presence of Unicode bidirectional override characters in source code, which can be used +to hide malicious behavior ("Trojan Source", [CVE-2021-42574](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574)). + +## What it does + +Detects the right-to-left override codepoint (`U+202E`) and other bidirectional control characters +embedded in identifiers, strings, and comments. + +## Why is this bad? + +These characters render source code in a different visual order than how the compiler reads it, +allowing an attacker to make malicious code look benign on review. Solidity contracts are public +and frequently audited visually; this attack vector must not be ignored. + +## Example + +### Bad + +```solidity +// transfer(victim‮, attacker)/* // U+202E hidden between args +``` + +### Good + +```solidity +// Avoid bidirectional override characters in code and comments. +``` diff --git a/crates/lint/docs/screaming-snake-case-const.md b/crates/lint/docs/screaming-snake-case-const.md new file mode 100644 index 0000000000000..72a16c5875fae --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-const.md @@ -0,0 +1,30 @@ +# Constants should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-const` + +Flags `constant` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `constant` whose identifier deviates from `SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for constants so they stand out from +mutable state and immutables at call sites. + +## Example + +### Bad + +```solidity +uint256 constant maxSupply = 1_000_000; +uint256 constant Max_Supply = 1_000_000; +``` + +### Good + +```solidity +uint256 constant MAX_SUPPLY = 1_000_000; +``` diff --git a/crates/lint/docs/screaming-snake-case-immutable.md b/crates/lint/docs/screaming-snake-case-immutable.md new file mode 100644 index 0000000000000..cee5590e16d27 --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-immutable.md @@ -0,0 +1,31 @@ +# Immutables should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-immutable` + +Flags `immutable` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `immutable` whose identifier deviates from +`SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for `immutable` variables so they +visually align with `constant` ones and stand out from mutable state at call sites. + +## Example + +### Bad + +```solidity +address immutable owner; +address immutable Owner; +``` + +### Good + +```solidity +address immutable OWNER; +``` diff --git a/crates/lint/docs/too-many-digits.md b/crates/lint/docs/too-many-digits.md new file mode 100644 index 0000000000000..5decb67bec9c3 --- /dev/null +++ b/crates/lint/docs/too-many-digits.md @@ -0,0 +1,32 @@ +# Numeric literal with too many digits + +**Severity**: `Info` +**ID**: `too-many-digits` + +Flags numeric literals containing five or more consecutive zeros, which are easy to misread. + +## What it does + +Reports decimal numeric literals that contain a run of 5 or more `0` characters. + +## Why is this bad? + +Long sequences of zeros are difficult to count visually, and an off-by-one zero is a common bug +(e.g. funding `1_000_000` instead of `10_000_000`). Use scientific notation, sub-denominations, or +underscore separators to make the magnitude obvious. + +## Example + +### Bad + +```solidity +uint256 amount = 1000000000000000000; +``` + +### Good + +```solidity +uint256 amount = 1e18; +uint256 amount2 = 1 ether; +uint256 amount3 = 1_000_000_000_000_000_000; +``` diff --git a/crates/lint/docs/tx-origin.md b/crates/lint/docs/tx-origin.md new file mode 100644 index 0000000000000..26877cf9c0116 --- /dev/null +++ b/crates/lint/docs/tx-origin.md @@ -0,0 +1,34 @@ +# Use of tx.origin for authorization + +**Severity**: `Med` +**ID**: `tx-origin` + +Flags use of `tx.origin` inside authorization-like predicates such as `require`, `assert`, `if`, +`while`, and `for` conditions. + +## What it does + +Reports `tx.origin` reads when they are used as part of a guard condition. Plain reads outside of +guard predicates are not reported. + +## Why is this bad? + +`tx.origin` is the original externally owned account that started the whole transaction, not the +immediate caller. If authorization checks rely on `tx.origin`, a malicious contract can call the +protected contract while the legitimate owner is the transaction origin. + +Use `msg.sender` for authorization checks instead. + +## Example + +### Bad + +```solidity +require(tx.origin == owner, "not owner"); +``` + +### Good + +```solidity +require(msg.sender == owner, "not owner"); +``` diff --git a/crates/lint/docs/unaliased-plain-import.md b/crates/lint/docs/unaliased-plain-import.md new file mode 100644 index 0000000000000..be8c5120028d6 --- /dev/null +++ b/crates/lint/docs/unaliased-plain-import.md @@ -0,0 +1,34 @@ +# Unaliased plain import + +**Severity**: `Info` +**ID**: `unaliased-plain-import` + +Flags `import "path";` statements that pull in every top-level symbol from another file without +an alias. + +## What it does + +Reports plain imports of the form `import "path";`. Suggests using either named imports +(`import { A, B } from "path"`) or an aliased import (`import "path" as X`). + +## Why is this bad? + +Plain imports pollute the importing file's namespace and make the source of each symbol +non-obvious. Named or aliased imports make the dependency surface explicit and reduce the chance +of accidental name collisions. + +## Example + +### Bad + +```solidity +import "./Lib.sol"; +``` + +### Good + +```solidity +import { Foo, Bar } from "./Lib.sol"; +// or +import "./Lib.sol" as Lib; +``` diff --git a/crates/lint/docs/unchecked-call.md b/crates/lint/docs/unchecked-call.md new file mode 100644 index 0000000000000..9a0a4143a0e0e --- /dev/null +++ b/crates/lint/docs/unchecked-call.md @@ -0,0 +1,34 @@ +# Unchecked low-level call + +**Severity**: `High` +**ID**: `unchecked-call` + +Flags low-level calls (`call`, `delegatecall`, `staticcall`, `callcode`) whose `success` return +value is ignored. + +## What it does + +Warns when the boolean returned by a low-level call is discarded — either because the return value +is not assigned or because only the `bytes memory` payload is used. + +## Why is this bad? + +Low-level calls do **not** revert when the callee fails; they silently return `false`. Ignoring +the success flag means a failed call is indistinguishable from a successful one, leading to bugs +where state is updated on the assumption that an external interaction succeeded. + +## Example + +### Bad + +```solidity +target.call(data); // success ignored +(, bytes memory ret) = target.call(data); // only payload kept +``` + +### Good + +```solidity +(bool ok, ) = target.call(data); +require(ok, "call failed"); +``` diff --git a/crates/lint/docs/unsafe-cheatcode.md b/crates/lint/docs/unsafe-cheatcode.md new file mode 100644 index 0000000000000..0aef657b0b7be --- /dev/null +++ b/crates/lint/docs/unsafe-cheatcode.md @@ -0,0 +1,35 @@ +# Usage of unsafe cheatcodes + +**Severity**: `Info` +**ID**: `unsafe-cheatcode` + +Flags use of Foundry cheatcodes that perform dangerous side effects (filesystem access, network +activity, environment variable reads, etc.) so they cannot slip into production code unnoticed. + +## What it does + +Reports calls to cheatcodes whose effects extend beyond the EVM sandbox or that bypass typical +test invariants. The flagged set follows the cheatcode's +[`Safety::Unsafe`](https://book.getfoundry.sh/cheatcodes) classification. + +## Why is this bad? + +Unsafe cheatcodes can read/write files, hit the network, or fork external state. They are +appropriate in tests with explicit intent but should not be added without review, and must +never end up in shipped contract code. + +## Example + +### Bad + +```solidity +vm.writeFile("./out.txt", data); // unsafe — writes to host filesystem +vm.envString("PRIVATE_KEY"); // unsafe — reads host environment +``` + +### Good + +```solidity +// Use safe cheatcodes (vm.expectRevert, vm.prank, vm.warp, ...) and explicit +// inputs/fixtures instead of pulling state from the host environment. +``` diff --git a/crates/lint/docs/unsafe-typecast.md b/crates/lint/docs/unsafe-typecast.md new file mode 100644 index 0000000000000..89d493eec3c3f --- /dev/null +++ b/crates/lint/docs/unsafe-typecast.md @@ -0,0 +1,40 @@ +# Unsafe typecast + +**Severity**: `Med` +**ID**: `unsafe-typecast` + +Flags explicit numeric typecasts that can silently truncate or alter the value. + +## What it does + +Reports casts where the source value's type is wider than the target type +(e.g. `uint256 → uint128`, `int256 → uint128`), unless the cast is preceded by a check that +guarantees the value fits in the target. + +## Why is this bad? + +Solidity does **not** revert on narrowing casts; it silently keeps the lowest bits, which can +cause severe accounting bugs (e.g. amount overflows, wrong fees, broken invariants). Use a checked +cast helper such as OpenZeppelin's `SafeCast` whenever the source value is not provably bounded. + +## Example + +### Bad + +```solidity +function setAmount(uint256 amount) external { + smallAmount = uint128(amount); // silent truncation if amount >= 2**128 +} +``` + +### Good + +```solidity +function setAmount(uint256 amount) external { + require(amount <= type(uint128).max, "overflow"); + smallAmount = uint128(amount); +} + +// or +smallAmount = SafeCast.toUint128(amount); +``` diff --git a/crates/lint/docs/unused-import.md b/crates/lint/docs/unused-import.md new file mode 100644 index 0000000000000..08f2545a36587 --- /dev/null +++ b/crates/lint/docs/unused-import.md @@ -0,0 +1,40 @@ +# Unused import + +**Severity**: `Info` +**ID**: `unused-import` + +Flags imported symbols (or whole import statements) whose imported names are not referenced +anywhere in the source unit. + +## What it does + +Reports `import "..."`, `import "..." as X`, and `import { A, B } from "..."` statements where one +or more imported names are never used. Symbols brought in via `import * as X` are tracked through +`X.member` accesses. + +## Why is this bad? + +Unused imports add noise, slow down compilation, can cause name collisions, and frequently +indicate dead code or stale refactors. + +## Example + +### Bad + +```solidity +import { A, B } from "./Lib.sol"; // B is never used + +contract C { + A internal a; +} +``` + +### Good + +```solidity +import { A } from "./Lib.sol"; + +contract C { + A internal a; +} +``` diff --git a/crates/lint/docs/unused-state-variables.md b/crates/lint/docs/unused-state-variables.md new file mode 100644 index 0000000000000..758c6e58b911b --- /dev/null +++ b/crates/lint/docs/unused-state-variables.md @@ -0,0 +1,39 @@ +# Unused state variable + +**Severity**: `Gas` +**ID**: `unused-state-variables` + +Flags state variables that are declared but never read or written anywhere in the contract or its +descendants. + +## What it does + +Reports each state variable that has no read or write site across the project. + +## Why is this bad? + +Unused state variables waste storage slots, inflate deployment cost, and are a strong signal of +dead or stale code that should be removed. + +## Example + +### Bad + +```solidity +contract C { + uint256 unused; // never read or written + uint256 public total; // used elsewhere +} +``` + +### Good + +```solidity +contract C { + uint256 public total; +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/unwrapped-modifier-logic.md b/crates/lint/docs/unwrapped-modifier-logic.md new file mode 100644 index 0000000000000..985c79962af07 --- /dev/null +++ b/crates/lint/docs/unwrapped-modifier-logic.md @@ -0,0 +1,51 @@ +# Unwrapped modifier logic + +**Severity**: `CodeSize` +**ID**: `unwrapped-modifier-logic` + +Flags modifiers whose body contains non-trivial logic that should be moved into a helper function +to reduce contract code size. + +## What it does + +Reports modifiers whose body contains statements other than a single placeholder, simple builtin +calls (`require`/`assert`), or a single library function call. Modifiers that use inline assembly +are exempted. + +## Why is this bad? + +Solidity inlines a modifier's body at every call site, so any non-trivial logic is duplicated +across all functions that use the modifier. Wrapping the logic in an internal function and calling +it from the modifier keeps the bytecode small while preserving behavior. + +## Example + +### Bad + +```solidity +modifier onlyAuth() { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; + _; +} +``` + +### Good + +```solidity +modifier onlyAuth() { + _checkAuth(); + _; +} + +function _checkAuth() internal { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; +} +``` + +## Notes + +This is a `CodeSize`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/src/linter/mod.rs b/crates/lint/src/linter/mod.rs index 0a5b4a40a5118..3e38a02726605 100644 --- a/crates/lint/src/linter/mod.rs +++ b/crates/lint/src/linter/mod.rs @@ -1,8 +1,10 @@ mod early; mod late; +mod project; pub use early::{EarlyLintPass, EarlyLintVisitor}; pub use late::{LateLintPass, LateLintVisitor}; +pub use project::{ProjectLintEmitter, ProjectLintPass, ProjectSource}; use foundry_common::comments::inline_config::InlineConfig; use foundry_compilers::Language; diff --git a/crates/lint/src/linter/project.rs b/crates/lint/src/linter/project.rs new file mode 100644 index 0000000000000..38fc1ad1ba59f --- /dev/null +++ b/crates/lint/src/linter/project.rs @@ -0,0 +1,92 @@ +use super::{Lint, LintContext, LinterConfig}; +use foundry_common::comments::inline_config::InlineConfig; +use foundry_config::lint::LintSpecificConfig; +use solar::{ + ast, + interface::{Session, Span, diagnostics::DiagMsg, source_map::SourceFile}, +}; +use std::{path::PathBuf, sync::Arc}; + +/// A single source unit visible to a project-wide lint pass, pre-loaded with its inline config so +/// emits respect `// forge-lint: disable-*` markers without rebuilding it per emit. +pub struct ProjectSource<'ast> { + pub path: PathBuf, + pub file: Arc, + pub ast: &'ast ast::SourceUnit<'ast>, + pub inline_config: InlineConfig>, +} + +/// Trait for lints that need to inspect every input source at once (e.g. cross-file checks). +/// +/// `check_project` runs once after all per-file [`super::EarlyLintPass`] / +/// [`super::LateLintPass`] passes have completed. +pub trait ProjectLintPass<'ast>: Send + Sync { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]); +} + +/// Helper passed to [`ProjectLintPass::check_project`] for emitting diagnostics against a specific +/// source. +pub struct ProjectLintEmitter<'s, 'c> { + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, +} + +impl<'s, 'c> ProjectLintEmitter<'s, 'c> { + pub const fn new( + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, + ) -> Self { + Self { sess, with_description, with_json_emitter, lint_specific, active_lints } + } + + /// Returns `true` if the given lint id is enabled for this run. Project passes that perform + /// expensive analysis should guard their work behind this check. + pub fn is_lint_enabled(&self, id: &'static str) -> bool { + self.active_lints.contains(&id) + } + + /// Emits a diagnostic with the lint's default description as the message. + pub fn emit<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + ) where + 'c: 'a, + { + self.build_ctx(source).emit(lint, span); + } + + /// Emits a diagnostic with a caller-provided message. + pub fn emit_with_msg<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + msg: impl Into, + ) where + 'c: 'a, + { + self.build_ctx(source).emit_with_msg(lint, span, msg); + } + + fn build_ctx<'a, 'ast>(&'a self, source: &'a ProjectSource<'ast>) -> LintContext<'s, 'a> + where + 'c: 'a, + { + LintContext::new( + self.sess, + self.with_description, + self.with_json_emitter, + LinterConfig { inline: &source.inline_config, lint_specific: self.lint_specific }, + self.active_lints.clone(), + Some(source.file.clone()), + ) + } +} diff --git a/crates/lint/src/sol/info/inline_assembly.rs b/crates/lint/src/sol/info/inline_assembly.rs new file mode 100644 index 0000000000000..1111129dada34 --- /dev/null +++ b/crates/lint/src/sol/info/inline_assembly.rs @@ -0,0 +1,71 @@ +use super::InlineAssembly; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Stmt, StmtKind}, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + INLINE_ASSEMBLY, + Severity::Info, + "inline-assembly", + "usage of inline assembly; assembly bypasses Solidity safety features and should be reviewed" +); + +const ASSEMBLY_KW_LEN: u32 = 8; +const NATSPEC_MEMORY_SAFE_MARKER: &str = "@solidity memory-safe-assembly"; + +impl<'ast> EarlyLintPass<'ast> for InlineAssembly { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + let StmtKind::Assembly(asm) = &stmt.kind else { return }; + + let kw_span = assembly_keyword_span(stmt.span); + + let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe") + || has_memory_safe_natspec(ctx, stmt.span.lo()); + + let msg = if memory_safe { + "inline assembly (declared memory-safe); review business logic and side effects" + } else { + "inline assembly used; review for memory safety and side effects" + }; + + ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg); + } +} + +/// Narrows a span to the leading `assembly` keyword to keep diagnostics readable. +fn assembly_keyword_span(span: Span) -> Span { + span.with_hi(span.lo() + BytePos(ASSEMBLY_KW_LEN)) +} + +/// Returns `true` when the lines immediately preceding `stmt_lo` form a `///` NatSpec block +/// containing `@solidity memory-safe-assembly`. +fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool { + let Some(source_file) = ctx.source_file() else { return false }; + let src = source_file.src.as_str(); + let start_pos = source_file.start_pos.to_u32(); + let lo_abs = stmt_lo.to_u32(); + if lo_abs < start_pos { + return false; + } + let offset = (lo_abs - start_pos) as usize; + if offset > src.len() { + return false; + } + + for line in src[..offset].lines().rev() { + let trimmed = line.trim_start(); + if trimmed.is_empty() { + continue; + } + let Some(rest) = trimmed.strip_prefix("///") else { return false }; + if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) { + return true; + } + } + false +} diff --git a/crates/lint/src/sol/info/mod.rs b/crates/lint/src/sol/info/mod.rs index c7800a417bafc..913c5d2ea9da3 100644 --- a/crates/lint/src/sol/info/mod.rs +++ b/crates/lint/src/sol/info/mod.rs @@ -30,6 +30,15 @@ use multi_contract_file::MULTI_CONTRACT_FILE; mod interface_naming; use interface_naming::{INTERFACE_FILE_NAMING, INTERFACE_NAMING}; +mod too_many_digits; +use too_many_digits::TOO_MANY_DIGITS; + +mod pragma_directive; +use pragma_directive::PRAGMA_INCONSISTENT; + +mod inline_assembly; +use inline_assembly::INLINE_ASSEMBLY; + register_lints!( (BooleanCst, early, (BOOLEAN_CST)), (BooleanEqual, early, (BOOLEAN_EQUAL)), @@ -42,4 +51,7 @@ register_lints!( (UnsafeCheatcodes, early, (UNSAFE_CHEATCODE_USAGE)), (MultiContractFile, early, (MULTI_CONTRACT_FILE)), (InterfaceFileNaming, early, (INTERFACE_FILE_NAMING, INTERFACE_NAMING)), + (TooManyDigits, early, (TOO_MANY_DIGITS)), + (PragmaDirective, project, (PRAGMA_INCONSISTENT)), + (InlineAssembly, early, (INLINE_ASSEMBLY)), ); diff --git a/crates/lint/src/sol/info/pragma_directive.rs b/crates/lint/src/sol/info/pragma_directive.rs new file mode 100644 index 0000000000000..b66b6bcff6ade --- /dev/null +++ b/crates/lint/src/sol/info/pragma_directive.rs @@ -0,0 +1,71 @@ +use crate::{ + linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource}, + sol::{Severity, SolLint, info::PragmaDirective}, +}; +use solar::{ast, interface::Span}; + +declare_forge_lint!( + PRAGMA_INCONSISTENT, + Severity::Info, + "pragma-inconsistent", + "inconsistent Solidity pragma version requirements across the project" +); + +impl<'ast> ProjectLintPass<'ast> for PragmaDirective { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) { + if !ctx.is_lint_enabled(PRAGMA_INCONSISTENT.id()) { + return; + } + + // Collect every `pragma solidity` directive across input sources, with its rendered + // version-requirement string for grouping. Stores source index to avoid lifetime + // invariance issues with `&ProjectSource<'ast>`. + let mut entries: Vec<(usize, Span, String)> = Vec::new(); + for (idx, source) in sources.iter().enumerate() { + for (span, req) in solidity_pragmas(source.ast) { + entries.push((idx, span, req.to_string())); + } + } + + // Stable order for snapshots and JSON output. + entries.sort_by(|a, b| { + sources[a.0].path.cmp(&sources[b.0].path).then(a.1.lo().cmp(&b.1.lo())) + }); + + // Build the distinct list once and bail if all sources agree. + let mut distinct: Vec<&str> = entries.iter().map(|(_, _, s)| s.as_str()).collect(); + distinct.sort_unstable(); + distinct.dedup(); + if distinct.len() < 2 { + return; + } + + for (idx, span, req_str) in &entries { + let others = distinct + .iter() + .filter(|v| **v != req_str.as_str()) + .copied() + .collect::>() + .join(", "); + let msg = format!( + "'pragma solidity {req_str};' conflicts with other version requirements in the project: {others}" + ); + ctx.emit_with_msg(&sources[*idx], &PRAGMA_INCONSISTENT, *span, msg); + } + } +} + +/// Yields every top-level `pragma solidity ...;` directive in `unit`. +fn solidity_pragmas<'ast>( + unit: &'ast ast::SourceUnit<'ast>, +) -> impl Iterator)> + 'ast { + unit.items.iter().filter_map(|item| match &item.kind { + ast::ItemKind::Pragma(p) => match &p.tokens { + ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => { + Some((item.span, req)) + } + _ => None, + }, + _ => None, + }) +} diff --git a/crates/lint/src/sol/info/too_many_digits.rs b/crates/lint/src/sol/info/too_many_digits.rs new file mode 100644 index 0000000000000..3ba9e8abba2de --- /dev/null +++ b/crates/lint/src/sol/info/too_many_digits.rs @@ -0,0 +1,50 @@ +use super::TooManyDigits; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{Expr, ExprKind, LitKind}; + +declare_forge_lint!( + TOO_MANY_DIGITS, + Severity::Info, + "too-many-digits", + "numeric literal with many digits is error-prone; \ + use scientific notation, sub-denominations, or underscore separators" +); + +impl<'ast> EarlyLintPass<'ast> for TooManyDigits { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + let ExprKind::Lit(lit, sub_denom) = &expr.kind else { return }; + + // Only plain integer literals. `LitKind::Address` (40-hex-digit address) is a + // distinct variant and is therefore skipped automatically. + if !matches!(lit.kind, LitKind::Number(_)) { + return; + } + + // Skip literals with a sub-denomination, e.g. `1000000 gwei`, `5 minutes`. + if sub_denom.is_some() { + return; + } + + let s = lit.symbol.as_str(); + + // Skip hex literals — long zero runs in hex are usually intentional (masks, + // selectors, bit patterns) and there is no scientific-notation alternative. + if s.starts_with("0x") || s.starts_with("0X") { + return; + } + + // Skip if the user already used scientific notation (`1e18`). + if s.contains('e') || s.contains('E') { + return; + } + + // 5+ consecutive zeros in the literal as written. Underscores are + // preserved, so `1_000_000` passes while `1_000000` is flagged. + if s.contains("00000") { + ctx.emit(&TOO_MANY_DIGITS, lit.span); + } + } +} diff --git a/crates/lint/src/sol/macros.rs b/crates/lint/src/sol/macros.rs index 00d764770374a..8540ab8b95b8f 100644 --- a/crates/lint/src/sol/macros.rs +++ b/crates/lint/src/sol/macros.rs @@ -9,9 +9,11 @@ /// - `$desc`: A short description of the lint. /// /// # Note -/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by -/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the -/// book, the existence of the lint rule's help section is validated with a unit test. +/// Each lint must have a corresponding markdown documentation file at +/// `crates/lint/docs/.md`. The `help` URL is auto-generated by the macro and points to +/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/`). To +/// ensure that new lint rules have their corresponding docs, the existence of every registered +/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`). #[macro_export] macro_rules! declare_forge_lint { ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { @@ -20,7 +22,7 @@ macro_rules! declare_forge_lint { id: $str_id, severity: $severity, description: $desc, - help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id), + help: concat!("https://getfoundry.sh/forge/linting/", $str_id), }; }; } @@ -53,6 +55,7 @@ macro_rules! register_lints { register_lints!(@early_impl $pass_id, $pass_type); register_lints!(@late_impl $pass_id, $pass_type); + register_lints!(@project_impl $pass_id, $pass_type); } )* }; @@ -89,10 +92,22 @@ macro_rules! register_lints { .flatten() .collect() } + + pub fn create_project_lint_passes<'ast>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@project_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() + } }; // --- HELPERS ------------------------------------------------------------ (@early_impl $_pass_id:ident, late) => {}; + (@early_impl $_pass_id:ident, project) => {}; (@early_impl $pass_id:ident, $other:ident) => { pub fn as_early_lint_pass<'a>() -> Box> { Box::new(Self::default()) @@ -100,22 +115,41 @@ macro_rules! register_lints { }; (@late_impl $_pass_id:ident, early) => {}; + (@late_impl $_pass_id:ident, project) => {}; (@late_impl $pass_id:ident, $other:ident) => { pub fn as_late_lint_pass<'hir>() -> Box> { Box::new(Self::default()) } }; + (@project_impl $_pass_id:ident, early) => {}; + (@project_impl $_pass_id:ident, late) => {}; + (@project_impl $_pass_id:ident, both) => {}; + (@project_impl $pass_id:ident, $other:ident) => { + pub fn as_project_lint_pass<'ast>() -> Box> { + Box::new(Self::default()) + } + }; + (@early_create $_pass_id:ident, late) => { None }; + (@early_create $_pass_id:ident, project) => { None }; (@early_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_early_lint_pass(), $pass_id::LINTS)) }; (@late_create $_pass_id:ident, early) => { None }; + (@late_create $_pass_id:ident, project) => { None }; (@late_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_late_lint_pass(), $pass_id::LINTS)) }; + (@project_create $_pass_id:ident, early) => { None }; + (@project_create $_pass_id:ident, late) => { None }; + (@project_create $_pass_id:ident, both) => { None }; + (@project_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_project_lint_pass(), $pass_id::LINTS)) + }; + // --- ENTRY POINT --------------------------------------------------------- ( $($tokens:tt)* ) => { register_lints! { @declare_structs $($tokens)* } diff --git a/crates/lint/src/sol/med/mod.rs b/crates/lint/src/sol/med/mod.rs index ba7a09b0e9bac..2673ba23d3252 100644 --- a/crates/lint/src/sol/med/mod.rs +++ b/crates/lint/src/sol/med/mod.rs @@ -9,6 +9,9 @@ use incorrect_erc20_interface::INCORRECT_ERC20_INTERFACE; mod incorrect_erc721_interface; use incorrect_erc721_interface::INCORRECT_ERC721_INTERFACE; +mod tx_origin; +use tx_origin::TX_ORIGIN; + mod unsafe_typecast; use unsafe_typecast::UNSAFE_TYPECAST; @@ -16,5 +19,6 @@ register_lints!( (DivideBeforeMultiply, early, (DIVIDE_BEFORE_MULTIPLY)), (IncorrectERC20Interface, late, (INCORRECT_ERC20_INTERFACE)), (IncorrectERC721Interface, late, (INCORRECT_ERC721_INTERFACE)), + (TxOrigin, early, (TX_ORIGIN)), (UnsafeTypecast, late, (UNSAFE_TYPECAST)) ); diff --git a/crates/lint/src/sol/med/tx_origin.rs b/crates/lint/src/sol/med/tx_origin.rs new file mode 100644 index 0000000000000..00ff5f939ebfb --- /dev/null +++ b/crates/lint/src/sol/med/tx_origin.rs @@ -0,0 +1,101 @@ +use super::TxOrigin; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Expr, ExprKind, IndexKind, Stmt, StmtKind}, + interface::SpannedOption, +}; + +declare_forge_lint!( + TX_ORIGIN, + Severity::Med, + "tx-origin", + "`tx.origin` should not be used for authorization" +); + +impl<'ast> EarlyLintPass<'ast> for TxOrigin { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::While(cond, _) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::For { cond: Some(cond), .. } => { + emit_if_contains_tx_origin(ctx, cond); + } + _ => {} + } + } + + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(callee, args) = &expr.kind + && is_require_or_assert_call(callee) + && let Some(cond) = args.exprs().next() + { + emit_if_contains_tx_origin(ctx, cond); + } + } +} + +fn emit_if_contains_tx_origin(ctx: &LintContext, expr: &Expr<'_>) { + if contains_tx_origin(expr) { + ctx.emit(&TX_ORIGIN, expr.span); + } +} + +fn contains_tx_origin(expr: &Expr<'_>) -> bool { + if is_tx_origin(expr) { + return true; + } + match &expr.kind { + ExprKind::Unary(_, inner) => contains_tx_origin(inner), + ExprKind::Binary(lhs, _, rhs) => contains_tx_origin(lhs) || contains_tx_origin(rhs), + ExprKind::Index(base, index) => { + contains_tx_origin(base) + || match index { + IndexKind::Index(Some(index)) => contains_tx_origin(index), + IndexKind::Range(start, end) => { + start.as_ref().is_some_and(|start| contains_tx_origin(start)) + || end.as_ref().is_some_and(|end| contains_tx_origin(end)) + } + _ => false, + } + } + ExprKind::Tuple(elems) => elems.iter().any(|elem| { + if let SpannedOption::Some(inner) = elem.as_ref() { + contains_tx_origin(inner) + } else { + false + } + }), + ExprKind::Call(callee, args) => { + contains_tx_origin(callee) || args.exprs().any(contains_tx_origin) + } + ExprKind::Ternary(cond, then_expr, else_expr) => { + contains_tx_origin(cond) + || contains_tx_origin(then_expr) + || contains_tx_origin(else_expr) + } + _ => false, + } +} + +fn is_tx_origin(expr: &Expr<'_>) -> bool { + matches!( + &expr.kind, + ExprKind::Member(base, member) + if member.as_str() == "origin" + && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "tx") + ) +} + +fn is_require_or_assert_call(callee: &Expr<'_>) -> bool { + matches!( + &callee.kind, + ExprKind::Ident(ident) if matches!(ident.as_str(), "require" | "assert") + ) +} diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index 1f7c515585fb4..7ae073f2ea20b 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -1,6 +1,6 @@ use crate::linter::{ EarlyLintPass, EarlyLintVisitor, LateLintPass, LateLintVisitor, Lint, LintContext, Linter, - LinterConfig, + LinterConfig, ProjectLintEmitter, ProjectLintPass, ProjectSource, }; use foundry_common::{ comments::{ @@ -179,6 +179,62 @@ impl<'a> SolidityLinter<'a> { Ok(()) } + /// Runs all enabled project-wide lint passes against the given input sources. + fn process_project<'gcx>(&self, gcx: Gcx<'gcx>, input: &[PathBuf]) { + // Gather enabled project passes from every severity bucket. + let mut passes_and_lints: Vec<(Box>, &'static [SolLint])> = + Vec::new(); + passes_and_lints.extend(high::create_project_lint_passes()); + passes_and_lints.extend(med::create_project_lint_passes()); + passes_and_lints.extend(low::create_project_lint_passes()); + passes_and_lints.extend(info::create_project_lint_passes()); + passes_and_lints.extend(gas::create_project_lint_passes()); + passes_and_lints.extend(codesize::create_project_lint_passes()); + + let (mut passes, lint_ids): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + if !included.is_empty() { + passes.push(pass); + ids.extend(included); + } + (passes, ids) + }); + + if passes.is_empty() { + return; + } + + // Pre-load every input source with its inline config, in input order. + let sources: Vec> = input + .iter() + .filter_map(|path| { + let path = self.path_config.root.join(path); + let (_, source) = gcx.get_ast_source(&path)?; + let ast = source.ast.as_ref()?; + let comments = + Comments::new(&source.file, gcx.sess.source_map(), false, false, None); + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + Some(ProjectSource { path, file: source.file.clone(), ast, inline_config }) + }) + .collect(); + + let emitter = ProjectLintEmitter::new( + gcx.sess, + self.with_description, + self.with_json_emitter, + self.lint_specific, + lint_ids, + ); + for pass in &mut passes { + pass.check_project(&emitter, &sources); + } + } + fn process_source_hir<'gcx>( &self, gcx: Gcx<'gcx>, @@ -314,6 +370,9 @@ impl<'a> Linter for SolidityLinter<'a> { ); }); + // Project-wide lints, run once after all per-file passes. + self.process_project(gcx, input); + convert_solar_errors(compiler.dcx()) })?; @@ -453,3 +512,75 @@ impl<'a> TryFrom<&'a str> for SolLint { Err(SolLintError::InvalidId(value.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Every registered lint must have a markdown documentation file at + /// `crates/lint/docs/.md`. This test enforces that contract so that the `help` URL + /// generated by `declare_forge_lint!` always resolves to real documentation. + /// + /// When this test fails, add a new file at `crates/lint/docs/.md` describing the + /// lint. See [`crates/lint/docs/_template.md`](../../docs/_template.md) for the expected + /// structure. + #[test] + fn registered_lints_have_docs() { + let docs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs"); + assert!(docs_dir.is_dir(), "missing docs directory at {}", docs_dir.display()); + + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + let mut missing: Vec<&'static str> = Vec::new(); + let mut empty: Vec<&'static str> = Vec::new(); + for lint in &all_lints { + let path = docs_dir.join(format!("{}.md", lint.id())); + match std::fs::read_to_string(&path) { + Ok(content) => { + // Basic sanity: file should be non-trivial and reference the lint id. + if content.trim().is_empty() || !content.contains(lint.id()) { + empty.push(lint.id()); + } + } + Err(_) => missing.push(lint.id()), + } + } + + assert!( + missing.is_empty(), + "the following registered lints are missing a docs file at \ + `crates/lint/docs/.md`: {missing:?}\n\ + See `crates/lint/docs/_template.md` for the expected structure." + ); + assert!( + empty.is_empty(), + "the following lint docs files are empty or do not reference the lint id: {empty:?}" + ); + } + + /// The auto-generated `help` URL must point at the canonical Foundry docs site so that the + /// link printed in diagnostics resolves correctly. + #[test] + fn registered_lints_have_canonical_help_url() { + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + for lint in all_lints { + let expected = format!("https://getfoundry.sh/forge/linting/{}", lint.id()); + assert_eq!(lint.help(), expected, "lint `{}` has a non-canonical help URL", lint.id()); + } + } +} diff --git a/crates/lint/testdata/BlockTimestamp.stderr b/crates/lint/testdata/BlockTimestamp.stderr index 016f8fa2bdb2d..62ab588ae7340 100644 --- a/crates/lint/testdata/BlockTimestamp.stderr +++ b/crates/lint/testdata/BlockTimestamp.stderr @@ -4,7 +4,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -12,7 +12,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp == 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -20,7 +20,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp != 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -28,7 +28,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp <= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -36,7 +36,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp >= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -44,7 +44,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp < deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -52,7 +52,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return deadline > block.timestamp; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -60,7 +60,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp + 1 > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -68,7 +68,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return (block.timestamp / 3600) == 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -76,7 +76,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ require(block.timestamp > deadline); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -84,7 +84,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ if (block.timestamp > deadline) { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -92,5 +92,5 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return foo(block.timestamp) > 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp diff --git a/crates/lint/testdata/BooleanCst.stderr b/crates/lint/testdata/BooleanCst.stderr index 53b89fcb11735..75fdb0b57cea7 100644 --- a/crates/lint/testdata/BooleanCst.stderr +++ b/crates/lint/testdata/BooleanCst.stderr @@ -4,7 +4,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (false) {} │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -12,7 +12,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag || true) {} │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -20,7 +20,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag ? true : false) {} │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -28,7 +28,7 @@ warning[boolean-cst]: misuse of a boolean constant LL │ if (flag ? true : false) {} │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst warning[boolean-cst]: misuse of a boolean constant ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC @@ -36,5 +36,5 @@ warning[boolean-cst]: misuse of a boolean constant LL │ return assigned && false; │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-cst + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst diff --git a/crates/lint/testdata/BooleanEqual.stderr b/crates/lint/testdata/BooleanEqual.stderr index 11749698f5714..590a85b806fcf 100644 --- a/crates/lint/testdata/BooleanEqual.stderr +++ b/crates/lint/testdata/BooleanEqual.stderr @@ -4,7 +4,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (enabled == true) {} │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -12,7 +12,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (paused == false) {} │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `!paused` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -20,7 +20,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ if (true != ready) {} │ ━━━━━━━━━━━━━ help: consider simplifying to: `!ready` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -28,7 +28,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ while (done != false) { │ ━━━━━━━━━━━━━ help: consider simplifying to: `done` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -36,7 +36,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ for (; enabled == true && paused != false;) { │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -44,7 +44,7 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ for (; enabled == true && paused != false;) { │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `paused` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal note[boolean-equal]: boolean comparisons to constants should be simplified ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC @@ -52,5 +52,5 @@ note[boolean-equal]: boolean comparisons to constants should be simplified LL │ return enabled == true; │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#boolean-equal + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal diff --git a/crates/lint/testdata/CouldBeImmutable.stderr b/crates/lint/testdata/CouldBeImmutable.stderr index 2858b2311cd95..170682baf89d3 100644 --- a/crates/lint/testdata/CouldBeImmutable.stderr +++ b/crates/lint/testdata/CouldBeImmutable.stderr @@ -4,7 +4,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address public owner; │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -12,7 +12,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address public deployer = msg.sender; │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -20,7 +20,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 private configured; │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -28,7 +28,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -36,7 +36,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ CouldBeImmutable private peer; │ ━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -44,7 +44,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 internal inheritedBase; │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[could-be-immutable]: state variable could be declared immutable ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC @@ -52,5 +52,5 @@ note[could-be-immutable]: state variable could be declared immutable LL │ uint256 internal baseConfigured; │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable diff --git a/crates/lint/testdata/CustomErrors.stderr b/crates/lint/testdata/CustomErrors.stderr index 66b3c11bc183c..286a649aee269 100644 --- a/crates/lint/testdata/CustomErrors.stderr +++ b/crates/lint/testdata/CustomErrors.stderr @@ -4,7 +4,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ require(a > 0, "Value must be greater than zero"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -12,7 +12,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ … require(a >= 0 && a <= 100 || b == 50, "Complex condition should be linted"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -20,7 +20,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert("Something went wrong"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -28,7 +28,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(""); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -36,5 +36,5 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index c0e5ef78e2e1c..95022f65db874 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -4,7 +4,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -12,7 +12,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 / 2) * 3) * 4; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -20,7 +20,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 * 2) / 3) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -28,7 +28,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2 / 3) * 4; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -36,7 +36,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / (2 + 3)) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -44,5 +44,5 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ 1 / ((2 / 3) * 3); │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply diff --git a/crates/lint/testdata/Imports.stderr b/crates/lint/testdata/Imports.stderr index 8fa9800b27ded..1031f4f6f8ca0 100644 --- a/crates/lint/testdata/Imports.stderr +++ b/crates/lint/testdata/Imports.stderr @@ -4,7 +4,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsSomeFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." as X' ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -12,7 +12,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsAnotherFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -20,7 +20,7 @@ note[unused-import]: unused imports should be removed LL │ symbol2 as notUsed, │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -28,7 +28,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol, │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -36,7 +36,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol2, │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -44,7 +44,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbolWrongTag, │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -52,7 +52,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed, │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -60,7 +60,7 @@ note[unused-import]: unused imports should be removed LL │ IContractNotUsed │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -68,7 +68,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed3 │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -76,7 +76,7 @@ note[unused-import]: unused imports should be removed LL │ CONSTANT_1 │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -84,7 +84,7 @@ note[unused-import]: unused imports should be removed LL │ YetAnotherType │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -92,7 +92,7 @@ note[unused-import]: unused imports should be removed LL │ import "./auxiliary/ImportsAnotherFile2.sol" as AnotherFile2; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -100,5 +100,5 @@ note[unused-import]: unused imports should be removed LL │ import * as OtherUtils from "./auxiliary/ImportsUtils2.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import diff --git a/crates/lint/testdata/IncorrectERC20Interface.stderr b/crates/lint/testdata/IncorrectERC20Interface.stderr index 3bb60ecce8320..33e2f1ca27d22 100644 --- a/crates/lint/testdata/IncorrectERC20Interface.stderr +++ b/crates/lint/testdata/IncorrectERC20Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 {} │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Incorrect is IERC20 { │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Correct is IERC20 { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20NamedCorrect { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC20 { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transferFrom(address from, address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function allowance(address owner, address spender) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function balanceOf(address account) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -116,5 +116,5 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function totalSupply() external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface diff --git a/crates/lint/testdata/IncorrectERC721Interface.stderr b/crates/lint/testdata/IncorrectERC721Interface.stderr index a88db93e39b10..2e68084c1cec1 100644 --- a/crates/lint/testdata/IncorrectERC721Interface.stderr +++ b/crates/lint/testdata/IncorrectERC721Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721 {} │ ━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Incorrect is IERC721 { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Correct is IERC721 { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721NamedCorrect { │ ━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC721 { │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function transferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -116,7 +116,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function approve(address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -124,7 +124,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function setApprovalForAll(address operator, bool approved) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -132,7 +132,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function getApproved(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -140,7 +140,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function isApprovedForAll(address owner, address operator) external view returns (address); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -148,5 +148,5 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function supportsInterface(bytes4 interfaceId) external view returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface diff --git a/crates/lint/testdata/IncorrectShift.stderr b/crates/lint/testdata/IncorrectShift.stderr index bce84c98df432..dfff32db897bb 100644 --- a/crates/lint/testdata/IncorrectShift.stderr +++ b/crates/lint/testdata/IncorrectShift.stderr @@ -4,7 +4,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 2 << stateValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -12,7 +12,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -20,7 +20,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 16 << (stateValue + 1); │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -28,7 +28,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 32 >> getAmount(); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -36,5 +36,5 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ … result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift diff --git a/crates/lint/testdata/InlineAssembly.sol b/crates/lint/testdata/InlineAssembly.sol new file mode 100644 index 0000000000000..05917ea22784c --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.sol @@ -0,0 +1,110 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract InlineAssembly { + function bare() public view returns (uint256 id) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + id := chainid() + } + } + + function withMemorySafe() public view returns (uint256 size) { + assembly ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + size := extcodesize(address()) + } + } + + function withDialectAndMemorySafe() public view returns (uint256 ptr) { + assembly "evmasm" ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + ptr := mload(0x40) + } + } + + function withNatspecMemorySafe() public view returns (uint256 v) { + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := chainid() + } + } + + function withNatspecMemorySafeAndOtherDocs() public view returns (uint256 v) { + /// @notice does a thing + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := gas() + } + } + + function plainCommentDoesNotCount() public view returns (uint256 v) { + // solidity memory-safe-assembly + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := chainid() + } + } + + function nestedInControlFlow(bool flag) public view returns (uint256 v) { + if (flag) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := gas() + } + } + + for (uint256 i = 0; i < 1; ++i) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInUnchecked(uint256 x) public pure returns (uint256 v) { + unchecked { + v = x + 1; + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInTryCatch() public returns (uint256 v) { + try this.bare() returns (uint256) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 1 + } + } catch { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 2 + } + } + } + + function suppressed() public view returns (uint256 id) { + // forge-lint: disable-next-line(inline-assembly) + assembly { + id := chainid() + } + } + + modifier guarded() { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + if iszero(caller()) { revert(0, 0) } + } + _; + } + + function suppressedRegion() public view returns (uint256 a, uint256 b) { + // forge-lint: disable-start(inline-assembly) + assembly { + a := chainid() + } + assembly ("memory-safe") { + b := gas() + } + // forge-lint: disable-end(inline-assembly) + } + + function noAssembly() public pure returns (uint256) { + return 42; + } +} diff --git a/crates/lint/testdata/InlineAssembly.stderr b/crates/lint/testdata/InlineAssembly.stderr new file mode 100644 index 0000000000000..12f8bcbacd14e --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.stderr @@ -0,0 +1,96 @@ +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly "evmasm" ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + diff --git a/crates/lint/testdata/Keccak256.sol b/crates/lint/testdata/Keccak256.sol index 41f856336b5b3..2457aed96d601 100644 --- a/crates/lint/testdata/Keccak256.sol +++ b/crates/lint/testdata/Keccak256.sol @@ -52,6 +52,7 @@ contract AsmKeccak256 { function assemblyHash(uint256 a, uint256 b) public pure returns (bytes32) { //optimized + // forge-lint: disable-next-line(inline-assembly) assembly { mstore(0x00, a) mstore(0x20, b) diff --git a/crates/lint/testdata/Keccak256.stderr b/crates/lint/testdata/Keccak256.stderr index 4203d950d9cba..a81e429e389a1 100644 --- a/crates/lint/testdata/Keccak256.stderr +++ b/crates/lint/testdata/Keccak256.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `mixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Another_MixedCase = 2; │ ━━━━━━━━━━━━━━━━━ help: consider using: `anotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 YetAnother_MixedCase = 3; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `yetAnotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract AsmKeccak256 { │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract OtherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -60,7 +60,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract YetAnotherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -68,7 +68,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 hash = keccak256(abi.encodePacked(a, b, bytes32(bytes20(c)))); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -76,7 +76,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 afterDisabledBlock = keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -84,7 +84,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromCalldata = keccak256(z); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -92,7 +92,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromMemory = keccak256(y); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -100,7 +100,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 lintWithoutFix = keccak256(abi.encodePacked(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -108,7 +108,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -116,7 +116,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 Enabled_MixedCase_Variable; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -124,7 +124,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesNotUseScratchSpace = keccak256(abi.encode(x, y, x, y, x, y)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -132,7 +132,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesUseScratchSpace = keccak256(abi.encode(x)); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -140,5 +140,5 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(doesUseScratchSpace, doesNotUseScratchSpace)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 diff --git a/crates/lint/testdata/MissingZeroCheck.stderr b/crates/lint/testdata/MissingZeroCheck.stderr index b55a902547fcf..81a9179e79c94 100644 --- a/crates/lint/testdata/MissingZeroCheck.stderr +++ b/crates/lint/testdata/MissingZeroCheck.stderr @@ -4,7 +4,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwner(address newOwner) external { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -12,7 +12,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ constructor(address initialOwner) { │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -20,7 +20,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function pay(address payable to) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -28,7 +28,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function lowLevel(address payable to, bytes calldata data) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -36,7 +36,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function withUselessModifier(address a) external doesNothing(a) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -44,7 +44,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaAlias(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -52,7 +52,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaReassign(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -60,7 +60,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaCast(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -68,7 +68,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payViaAlias(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -76,7 +76,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function mixedParams(address a, address b) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -84,7 +84,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function bothSinks(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -92,7 +92,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function ternaryAlias(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -100,7 +100,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payableWrap(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -108,7 +108,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function modifierWithExpr(address a) external nonZero(addrIdentity(a)) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -116,7 +116,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function delegateCallSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -124,7 +124,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkStmt(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -132,7 +132,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkDecl(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -140,7 +140,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function multiHopTaint(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -148,7 +148,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardAfterSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -156,7 +156,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardOnOneBranch(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -164,7 +164,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInForLoop(address a, uint256 n) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -172,7 +172,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInWhileLoop(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -180,5 +180,5 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInTryClause(address a, address payable target) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check diff --git a/crates/lint/testdata/MixedCase.stderr b/crates/lint/testdata/MixedCase.stderr index d290af5cdb5a8..2db30559ba5a6 100644 --- a/crates/lint/testdata/MixedCase.stderr +++ b/crates/lint/testdata/MixedCase.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Variablemixedcase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variablemixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VARIABLE_MIXED_CASE; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VariableMixedCase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 testVAL; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TestVal; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -44,7 +44,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TESTVAL; │ ━━━━━━━ help: consider using: `testval` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -52,7 +52,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function Functionmixedcase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionmixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -60,7 +60,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FUNCTION_MIXED_CASE() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -68,7 +68,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FunctionMixedCase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -76,7 +76,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function function_mixed_case() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -84,7 +84,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantBalance_MixedCase_Enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantBalanceMixedCaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -92,7 +92,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantbalance_mixedcase_enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantbalanceMixedcaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -100,7 +100,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function ERC20_DoSomething() public {} // invalid because of the underscore │ ━━━━━━━━━━━━━━━━━ help: consider using: `erc20DoSomething` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -108,7 +108,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_PARAMS(address addr) external view returns (uint256) {} │ ━━━━━━━━━━ help: consider using: `hasParams` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -116,7 +116,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_NO_RETURN() external view {} │ ━━━━━━━━━━━━━ help: consider using: `hasNoReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -124,7 +124,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_MORE_THAN_ONE_RETURN() external view returns (uint256, uint256) {} │ ━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `hasMoreThanOneReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -132,7 +132,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function NOT_ELEMENTARY_RETURN() external view returns (uint256[] memory) {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `notElementaryReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -140,7 +140,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -148,5 +148,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract MixedCaseTest { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile.stderr b/crates/lint/testdata/MultiContractFile.stderr index c6e4e32a2df55..e25f3d72ad01a 100644 --- a/crates/lint/testdata/MultiContractFile.stderr +++ b/crates/lint/testdata/MultiContractFile.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract A {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract B {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -36,5 +36,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr index 41fc439ea7d1b..1912f16863712 100644 --- a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr +++ b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -20,5 +20,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/NamedStructFields.stderr b/crates/lint/testdata/NamedStructFields.stderr index 6ee2160791cd2..cfb35637176bd 100644 --- a/crates/lint/testdata/NamedStructFields.stderr +++ b/crates/lint/testdata/NamedStructFields.stderr @@ -4,5 +4,5 @@ note[named-struct-fields]: prefer initializing structs with named fields LL │ Person memory person = Person("Alice", 25, address(0)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using named fields: `Person({ name: "Alice", age: 25, wallet: address(0) })` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#named-struct-fields + ╰ help: https://getfoundry.sh/forge/linting/named-struct-fields diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol new file mode 100644 index 0000000000000..bfc993baab794 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 +pragma solidity 0.8.18; //~NOTE: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr new file mode 100644 index 0000000000000..c2c967dee792f --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.18; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol new file mode 100644 index 0000000000000..75bc17988accc --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr new file mode 100644 index 0000000000000..f60361718ba9b --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol new file mode 100644 index 0000000000000..37b06040c33a6 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 +pragma solidity ~0.8.20; //~NOTE: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr new file mode 100644 index 0000000000000..6c46f2478208d --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ~0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.sol b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol new file mode 100644 index 0000000000000..f85a477cc8744 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20 || 0.8.21; //~NOTE: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr new file mode 100644 index 0000000000000..acf6bd7c2d6e0 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20 || 0.8.21; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol new file mode 100644 index 0000000000000..d8fcb7a0eb4b1 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; //~NOTE: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr new file mode 100644 index 0000000000000..5ac221b924c9a --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0 <0.9.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol new file mode 100644 index 0000000000000..fe208e15efb63 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol @@ -0,0 +1,8 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; //~NOTE: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 +pragma solidity ~0.8.0; //~NOTE: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr new file mode 100644 index 0000000000000..e1e5ad7333fb2 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr @@ -0,0 +1,24 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ~0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/Rtlo.stderr b/crates/lint/testdata/Rtlo.stderr index 2c2b53df646e1..93f5bb191532f 100644 --- a/crates/lint/testdata/Rtlo.stderr +++ b/crates/lint/testdata/Rtlo.stderr @@ -4,7 +4,7 @@ warning[rtlo]: U+202A (Left-to-Right Embedding) detected LL │ string public lre = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -12,7 +12,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public lre = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202B (Right-to-Left Embedding) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -20,7 +20,7 @@ warning[rtlo]: U+202B (Right-to-Left Embedding) detected LL │ string public rle = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -28,7 +28,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public rle = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202A (Left-to-Right Embedding) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -36,7 +36,7 @@ warning[rtlo]: U+202A (Left-to-Right Embedding) detected LL │ string public pdf = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -44,7 +44,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public pdf = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202D (Left-to-Right Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -52,7 +52,7 @@ warning[rtlo]: U+202D (Left-to-Right Override) detected LL │ string public lro = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -60,7 +60,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public lro = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -68,7 +68,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ string public rlo = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -76,7 +76,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ string public rlo = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2066 (Left-to-Right Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -84,7 +84,7 @@ warning[rtlo]: U+2066 (Left-to-Right Isolate) detected LL │ string public lri = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -92,7 +92,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public lri = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2067 (Right-to-Left Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -100,7 +100,7 @@ warning[rtlo]: U+2067 (Right-to-Left Isolate) detected LL │ string public rli = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -108,7 +108,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public rli = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2068 (First Strong Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -116,7 +116,7 @@ warning[rtlo]: U+2068 (First Strong Isolate) detected LL │ string public fsi = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -124,7 +124,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public fsi = unicode"�_�"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2066 (Left-to-Right Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -132,7 +132,7 @@ warning[rtlo]: U+2066 (Left-to-Right Isolate) detected LL │ string public pdi = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+2069 (Pop Directional Isolate) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -140,7 +140,7 @@ warning[rtlo]: U+2069 (Pop Directional Isolate) detected LL │ string public pdi = unicode"��"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -148,7 +148,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ /* hidden� /* text � */ uint256 inBlockComment; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -156,7 +156,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ /* hidden� /* text � */ uint256 inBlockComment; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -164,7 +164,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ // sneaky� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -172,7 +172,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ // sneaky� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+200E (Left-to-Right Mark) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -180,7 +180,7 @@ warning[rtlo]: U+200E (Left-to-Right Mark) detected LL │ string public marks = unicode"left‎right‏end"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+200F (Right-to-Left Mark) detected ╭▸ ROOT/testdata/Rtlo.sol:LL:CC @@ -188,5 +188,5 @@ warning[rtlo]: U+200F (Right-to-Left Mark) detected LL │ string public marks = unicode"left‎right‏end"; │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo diff --git a/crates/lint/testdata/RtloCommentsOnly.stderr b/crates/lint/testdata/RtloCommentsOnly.stderr index 88a354432867e..5a7ec9ee6e69d 100644 --- a/crates/lint/testdata/RtloCommentsOnly.stderr +++ b/crates/lint/testdata/RtloCommentsOnly.stderr @@ -4,7 +4,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ // hidden� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -12,7 +12,7 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ // hidden� payload � trailing │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202E (Right-to-Left Override) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -20,7 +20,7 @@ warning[rtlo]: U+202E (Right-to-Left Override) detected LL │ /* block� comment � end */ │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo warning[rtlo]: U+202C (Pop Directional Formatting) detected ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC @@ -28,5 +28,5 @@ warning[rtlo]: U+202C (Pop Directional Formatting) detected LL │ /* block� comment � end */ │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#rtlo + ╰ help: https://getfoundry.sh/forge/linting/rtlo diff --git a/crates/lint/testdata/ScreamingSnakeCase.stderr b/crates/lint/testdata/ScreamingSnakeCase.stderr index a740506ed74d8..36305bb268d9a 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.stderr +++ b/crates/lint/testdata/ScreamingSnakeCase.stderr @@ -4,7 +4,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -12,7 +12,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screaming_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -20,7 +20,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant ScreamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -28,7 +28,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant SCREAMING_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -36,7 +36,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -44,7 +44,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screaming_snake_case0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -52,7 +52,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable ScreamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -60,5 +60,5 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable SCREAMING_snake_case_0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE_0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable diff --git a/crates/lint/testdata/StructPascalCase.stderr b/crates/lint/testdata/StructPascalCase.stderr index 1c7bfa13ba84b..255c1c4d5d74b 100644 --- a/crates/lint/testdata/StructPascalCase.stderr +++ b/crates/lint/testdata/StructPascalCase.stderr @@ -4,7 +4,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct _PascalCase { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -12,7 +12,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalCase { │ ━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -20,7 +20,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalcase { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -28,7 +28,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascal_case { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -36,7 +36,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCAL_CASE { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -44,5 +44,5 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCALCASE { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct diff --git a/crates/lint/testdata/TooManyDigits.sol b/crates/lint/testdata/TooManyDigits.sol new file mode 100644 index 0000000000000..a56ad67fe379e --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.sol @@ -0,0 +1,73 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TooManyDigits { + // SHOULD FAIL: plain decimal integer literals with 5+ consecutive zeros. + + uint256 stateA = 1000000000000000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + uint256 stateB = 100000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + function asReturn() public pure returns (uint256) { + return 10000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asComparison(uint256 x) public pure returns (bool) { + return x == 1000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArg(address to) public { + _send(to, 50000000000); //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArraySize() public pure { + uint256[100000] memory _arr; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + // Zero-run in the middle (not just trailing). + uint256 middleZeros = 123000007; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscores that don't actually break up the zero run. + uint256 badGrouping = 1_000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscore right after a single digit, leaving a 5-zero group. + uint256 badGrouping2 = 1_00000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // SHOULD PASS: + + // Boundary: 4 consecutive zeros (one short of the threshold). + uint256 fourZeros = 10000; + + // Uppercase scientific notation. + uint256 sciUpper = 1E18; + + // Scientific notation. + uint256 sci = 1e18; + + // Underscore-separated digit groups. + uint256 grouped = 1_000_000_000_000_000_000; + + // Sub-denominations. + uint256 oneEther = 1 ether; + uint256 oneGwei = 1 gwei; + uint256 fiveMin = 5 minutes; + + // Address literal (distinct AST kind, not flagged). + address adr = 0x1234567890123456789012345678901234567890; + + // Hex literal — intentional zero patterns (mask / padded value). + bytes32 mask = 0x0000000000000000000000000000000000000000000000000000000000000001; + uint256 hexNum = 0x100000; + + // Small literals (< 5 consecutive zeros). + uint256 small1 = 100; + uint256 small2 = 9999; + uint256 small3 = 1234; + uint256 spread = 101010; + + // Boolean literal. + bool flag = true; + + function _send(address, uint256) internal pure {} +} diff --git a/crates/lint/testdata/TooManyDigits.stderr b/crates/lint/testdata/TooManyDigits.stderr new file mode 100644 index 0000000000000..7e21a530776c2 --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.stderr @@ -0,0 +1,72 @@ +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateA = 1000000000000000000; + │ ━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateB = 100000; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return 10000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return x == 1000000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … _send(to, 50000000000); + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … uint256[100000] memory _arr; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 middleZeros = 123000007; + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping = 1_000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping2 = 1_00000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + diff --git a/crates/lint/testdata/TxOrigin.sol b/crates/lint/testdata/TxOrigin.sol new file mode 100644 index 0000000000000..9728a7e528e5b --- /dev/null +++ b/crates/lint/testdata/TxOrigin.sol @@ -0,0 +1,65 @@ +//@compile-flags: --only-lint tx-origin +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TxOrigin { + address public owner; + mapping(address => bool) public allowed; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(tx.origin == owner, "not owner"); //~WARN: `tx.origin` should not be used for authorization + _; + } + + function guardedByIf() external view { + if (tx.origin != owner) { //~WARN: `tx.origin` should not be used for authorization + revert("not owner"); + } + } + + function guardedByPredicate() external view { + assert(isOwner(tx.origin)); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByWhile() external view { + while (tx.origin == owner) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByFor() external view { + for (; tx.origin == owner;) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByDoWhile() external view { + do { + } while (tx.origin == owner); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByMapping() external view { + require(allowed[tx.origin], "not allowed"); //~WARN: `tx.origin` should not be used for authorization + require(allowed[tx.origin] == true, "not allowed"); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByTernary() external view { + require(tx.origin == owner ? true : false, "not owner"); //~WARN: `tx.origin` should not be used for authorization + } + + function readForLogging() external view returns (address) { + return tx.origin; + } + + function explicitSenderCheck() external view { + require(msg.sender == owner, "not owner"); + } + + function isOwner(address account) internal view returns (bool) { + return account == owner; + } +} diff --git a/crates/lint/testdata/TxOrigin.stderr b/crates/lint/testdata/TxOrigin.stderr new file mode 100644 index 0000000000000..7c2e70225b76d --- /dev/null +++ b/crates/lint/testdata/TxOrigin.stderr @@ -0,0 +1,72 @@ +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner, "not owner"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ if (tx.origin != owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ assert(isOwner(tx.origin)); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ while (tx.origin == owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ for (; tx.origin == owner;) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ } while (tx.origin == owner); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin], "not allowed"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin] == true, "not allowed"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner ? true : false, "not owner"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + diff --git a/crates/lint/testdata/UncheckedCall.stderr b/crates/lint/testdata/UncheckedCall.stderr index afb8ade4ea89b..8a8a9fa9b5e17 100644 --- a/crates/lint/testdata/UncheckedCall.stderr +++ b/crates/lint/testdata/UncheckedCall.stderr @@ -4,7 +4,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call(data); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -12,7 +12,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call{value: value}(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -20,7 +20,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.delegatecall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -28,7 +28,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.staticcall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -36,7 +36,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target1.call(""); │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -44,7 +44,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target2.delegatecall(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -52,7 +52,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, bytes memory data) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -60,5 +60,5 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, existingData) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call diff --git a/crates/lint/testdata/UncheckedTransferERC20.stderr b/crates/lint/testdata/UncheckedTransferERC20.stderr index 733d22ce610d1..2c2caa69e7215 100644 --- a/crates/lint/testdata/UncheckedTransferERC20.stderr +++ b/crates/lint/testdata/UncheckedTransferERC20.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Wrapper { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransfer { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library Currency { │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransferUsingCurrencyLib { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -44,7 +44,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ IERC20(address(token)).transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -52,7 +52,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -60,7 +60,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -68,7 +68,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -76,7 +76,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -84,5 +84,5 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer diff --git a/crates/lint/testdata/UnsafeCheatcodes.stderr b/crates/lint/testdata/UnsafeCheatcodes.stderr index e66a4d72c70de..5b8b429942e80 100644 --- a/crates/lint/testdata/UnsafeCheatcodes.stderr +++ b/crates/lint/testdata/UnsafeCheatcodes.stderr @@ -4,7 +4,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -12,7 +12,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -20,7 +20,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readLine("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -28,7 +28,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeFile("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -36,7 +36,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeLine("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -44,7 +44,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.removeFile("test.txt"); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.closeFile("test.txt"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -60,7 +60,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -68,7 +68,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.deriveKey("mnemonic", 0); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -76,7 +76,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ bytes memory result = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(new string[](1)); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -100,5 +100,5 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode diff --git a/crates/lint/testdata/UnsafeTypecast.stderr b/crates/lint/testdata/UnsafeTypecast.stderr index b3e0334d63d43..d909b90973e00 100644 --- a/crates/lint/testdata/UnsafeTypecast.stderr +++ b/crates/lint/testdata/UnsafeTypecast.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UnsafeTypecast { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract Repros { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -26,7 +26,7 @@ LL │ uint248 b = uint248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -40,7 +40,7 @@ LL │ uint240 c = uint240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -54,7 +54,7 @@ LL │ uint232 d = uint232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -68,7 +68,7 @@ LL │ uint224 e = uint224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -82,7 +82,7 @@ LL │ uint216 f = uint216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -96,7 +96,7 @@ LL │ uint208 g = uint208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -110,7 +110,7 @@ LL │ uint200 h = uint200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -124,7 +124,7 @@ LL │ uint192 i = uint192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -138,7 +138,7 @@ LL │ uint184 j = uint184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -152,7 +152,7 @@ LL │ uint176 k = uint176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -166,7 +166,7 @@ LL │ uint168 l = uint168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -180,7 +180,7 @@ LL │ uint160 m = uint160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -194,7 +194,7 @@ LL │ uint152 n = uint152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -208,7 +208,7 @@ LL │ uint144 o = uint144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -222,7 +222,7 @@ LL │ uint136 p = uint136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -236,7 +236,7 @@ LL │ uint128 q = uint128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -250,7 +250,7 @@ LL │ uint120 r = uint120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -264,7 +264,7 @@ LL │ uint112 s = uint112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -278,7 +278,7 @@ LL │ uint104 t = uint104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -292,7 +292,7 @@ LL │ uint96 u = uint96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -306,7 +306,7 @@ LL │ uint88 v = uint88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -320,7 +320,7 @@ LL │ uint80 w = uint80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -334,7 +334,7 @@ LL │ uint72 x = uint72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -348,7 +348,7 @@ LL │ uint64 y = uint64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -362,7 +362,7 @@ LL │ uint56 z = uint56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -376,7 +376,7 @@ LL │ uint48 A = uint48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -390,7 +390,7 @@ LL │ uint40 B = uint40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -404,7 +404,7 @@ LL │ uint32 C = uint32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -418,7 +418,7 @@ LL │ uint24 D = uint24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -432,7 +432,7 @@ LL │ uint16 E = uint16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -446,7 +446,7 @@ LL │ uint8 F = uint8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -460,7 +460,7 @@ LL │ int248 b = int248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -474,7 +474,7 @@ LL │ int240 c = int240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -488,7 +488,7 @@ LL │ int232 d = int232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -502,7 +502,7 @@ LL │ int224 e = int224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -516,7 +516,7 @@ LL │ int216 f = int216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -530,7 +530,7 @@ LL │ int208 g = int208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -544,7 +544,7 @@ LL │ int200 h = int200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -558,7 +558,7 @@ LL │ int192 i = int192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -572,7 +572,7 @@ LL │ int184 j = int184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -586,7 +586,7 @@ LL │ int176 k = int176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -600,7 +600,7 @@ LL │ int168 l = int168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -614,7 +614,7 @@ LL │ int160 m = int160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -628,7 +628,7 @@ LL │ int152 n = int152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -642,7 +642,7 @@ LL │ int144 o = int144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -656,7 +656,7 @@ LL │ int136 p = int136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -670,7 +670,7 @@ LL │ int128 q = int128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -684,7 +684,7 @@ LL │ int120 r = int120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -698,7 +698,7 @@ LL │ int112 s = int112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -712,7 +712,7 @@ LL │ int104 t = int104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -726,7 +726,7 @@ LL │ int96 u = int96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -740,7 +740,7 @@ LL │ int88 v = int88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -754,7 +754,7 @@ LL │ int80 w = int80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -768,7 +768,7 @@ LL │ int72 x = int72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -782,7 +782,7 @@ LL │ int64 y = int64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -796,7 +796,7 @@ LL │ int56 z = int56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -810,7 +810,7 @@ LL │ int48 A = int48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -824,7 +824,7 @@ LL │ int40 B = int40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -838,7 +838,7 @@ LL │ int32 C = int32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -852,7 +852,7 @@ LL │ int24 D = int24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -866,7 +866,7 @@ LL │ int16 E = int16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -880,7 +880,7 @@ LL │ int8 F = int8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -894,7 +894,7 @@ LL │ bytes31 b = bytes31(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -908,7 +908,7 @@ LL │ bytes30 c = bytes30(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -922,7 +922,7 @@ LL │ bytes29 d = bytes29(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -936,7 +936,7 @@ LL │ bytes28 e = bytes28(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -950,7 +950,7 @@ LL │ bytes27 f = bytes27(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -964,7 +964,7 @@ LL │ bytes26 g = bytes26(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -978,7 +978,7 @@ LL │ bytes25 h = bytes25(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -992,7 +992,7 @@ LL │ bytes24 i = bytes24(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1006,7 +1006,7 @@ LL │ bytes23 j = bytes23(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1020,7 +1020,7 @@ LL │ bytes22 k = bytes22(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1034,7 +1034,7 @@ LL │ bytes21 l = bytes21(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1048,7 +1048,7 @@ LL │ bytes20 m = bytes20(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1062,7 +1062,7 @@ LL │ bytes19 n = bytes19(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1076,7 +1076,7 @@ LL │ bytes18 o = bytes18(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1090,7 +1090,7 @@ LL │ bytes17 p = bytes17(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1104,7 +1104,7 @@ LL │ bytes16 q = bytes16(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1118,7 +1118,7 @@ LL │ bytes15 r = bytes15(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1132,7 +1132,7 @@ LL │ bytes14 s = bytes14(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1146,7 +1146,7 @@ LL │ bytes13 t = bytes13(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1160,7 +1160,7 @@ LL │ bytes12 u = bytes12(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1174,7 +1174,7 @@ LL │ bytes11 v = bytes11(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1188,7 +1188,7 @@ LL │ bytes10 w = bytes10(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1202,7 +1202,7 @@ LL │ bytes9 x = bytes9(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1216,7 +1216,7 @@ LL │ bytes8 y = bytes8(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1230,7 +1230,7 @@ LL │ bytes7 z = bytes7(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1244,7 +1244,7 @@ LL │ bytes6 A = bytes6(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1258,7 +1258,7 @@ LL │ bytes5 B = bytes5(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1272,7 +1272,7 @@ LL │ bytes4 C = bytes4(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1286,7 +1286,7 @@ LL │ bytes3 D = bytes3(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1300,7 +1300,7 @@ LL │ bytes2 E = bytes2(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1314,7 +1314,7 @@ LL │ bytes1 F = bytes1(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1328,7 +1328,7 @@ LL │ int256 b = int256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1342,7 +1342,7 @@ LL │ int248 d = int248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1356,7 +1356,7 @@ LL │ int240 f = int240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1370,7 +1370,7 @@ LL │ int232 h = int232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1384,7 +1384,7 @@ LL │ int224 j = int224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1398,7 +1398,7 @@ LL │ int216 l = int216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1412,7 +1412,7 @@ LL │ int208 n = int208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1426,7 +1426,7 @@ LL │ int200 p = int200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1440,7 +1440,7 @@ LL │ int192 r = int192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1454,7 +1454,7 @@ LL │ int184 t = int184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1468,7 +1468,7 @@ LL │ int176 v = int176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1482,7 +1482,7 @@ LL │ int168 x = int168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1496,7 +1496,7 @@ LL │ int160 z = int160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1510,7 +1510,7 @@ LL │ int152 B = int152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1524,7 +1524,7 @@ LL │ int144 D = int144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1538,7 +1538,7 @@ LL │ int136 F = int136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1552,7 +1552,7 @@ LL │ int128 H = int128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1566,7 +1566,7 @@ LL │ int120 J = int120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1580,7 +1580,7 @@ LL │ int112 L = int112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1594,7 +1594,7 @@ LL │ int104 N = int104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1608,7 +1608,7 @@ LL │ int96 P = int96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1622,7 +1622,7 @@ LL │ int88 R = int88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1636,7 +1636,7 @@ LL │ int80 T = int80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1650,7 +1650,7 @@ LL │ int72 V = int72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1664,7 +1664,7 @@ LL │ int64 X = int64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1678,7 +1678,7 @@ LL │ int56 Z = int56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1692,7 +1692,7 @@ LL │ int48 BB = int48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1706,7 +1706,7 @@ LL │ int40 DD = int40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1720,7 +1720,7 @@ LL │ int32 FF = int32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1734,7 +1734,7 @@ LL │ int24 HH = int24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1748,7 +1748,7 @@ LL │ int16 JJ = int16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1762,7 +1762,7 @@ LL │ int8 LL = int8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1776,7 +1776,7 @@ LL │ uint256 b = uint256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1790,7 +1790,7 @@ LL │ uint248 d = uint248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1804,7 +1804,7 @@ LL │ uint240 f = uint240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1818,7 +1818,7 @@ LL │ uint232 h = uint232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1832,7 +1832,7 @@ LL │ uint224 j = uint224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1846,7 +1846,7 @@ LL │ uint216 l = uint216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1860,7 +1860,7 @@ LL │ uint208 n = uint208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1874,7 +1874,7 @@ LL │ uint200 p = uint200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1888,7 +1888,7 @@ LL │ uint192 r = uint192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1902,7 +1902,7 @@ LL │ uint184 t = uint184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1916,7 +1916,7 @@ LL │ uint176 v = uint176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1930,7 +1930,7 @@ LL │ uint168 x = uint168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1944,7 +1944,7 @@ LL │ uint160 z = uint160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1958,7 +1958,7 @@ LL │ uint152 B = uint152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1972,7 +1972,7 @@ LL │ uint144 D = uint144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1986,7 +1986,7 @@ LL │ uint136 F = uint136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2000,7 +2000,7 @@ LL │ uint128 H = uint128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2014,7 +2014,7 @@ LL │ uint120 J = uint120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2028,7 +2028,7 @@ LL │ uint112 L = uint112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2042,7 +2042,7 @@ LL │ uint104 N = uint104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2056,7 +2056,7 @@ LL │ uint96 P = uint96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2070,7 +2070,7 @@ LL │ uint88 R = uint88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2084,7 +2084,7 @@ LL │ uint80 T = uint80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2098,7 +2098,7 @@ LL │ uint72 V = uint72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2112,7 +2112,7 @@ LL │ uint64 X = uint64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2126,7 +2126,7 @@ LL │ uint56 Z = uint56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2140,7 +2140,7 @@ LL │ uint48 BB = uint48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2154,7 +2154,7 @@ LL │ uint40 DD = uint40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2168,7 +2168,7 @@ LL │ uint32 FF = uint32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2182,7 +2182,7 @@ LL │ uint24 HH = uint24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2196,7 +2196,7 @@ LL │ uint16 JJ = uint16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2210,7 +2210,7 @@ LL │ uint8 LL = uint8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2224,7 +2224,7 @@ LL │ bytes32 dataSlice = bytes32(data); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2238,7 +2238,7 @@ LL │ bytes32 strSlice = bytes32(bytes(str)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2252,7 +2252,7 @@ LL │ uint128 aPlusB = uint128(int128(uint128(a)) + b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2266,7 +2266,7 @@ LL │ uint64 unsafe = uint64(aPlusB); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2280,7 +2280,7 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2294,5 +2294,5 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast diff --git a/crates/lint/testdata/UnusedStateVariables.stderr b/crates/lint/testdata/UnusedStateVariables.stderr index ecc9e87efc105..92a0082bb8293 100644 --- a/crates/lint/testdata/UnusedStateVariables.stderr +++ b/crates/lint/testdata/UnusedStateVariables.stderr @@ -4,7 +4,7 @@ note[could-be-immutable]: state variable could be declared immutable LL │ address usedInBoth; │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#could-be-immutable + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -12,7 +12,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 unused; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -20,7 +20,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 unused; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -28,7 +28,7 @@ note[unused-state-variables]: state variable is never used LL │ uint256 firstUnused; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[unused-state-variables]: state variable is never used ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC @@ -36,5 +36,5 @@ note[unused-state-variables]: state variable is never used LL │ uint256 secondUnused; │ ━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-state-variables + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr index 5e1bf754e60e4..dc5514c2d5e98 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -9,7 +9,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforePlaceholder() { @@ -35,7 +35,7 @@ LL │ ┃ checkInternal(msg.sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleAfterPlaceholder() { @@ -62,7 +62,7 @@ LL │ ┃ checkPublic(sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforeAfterPlaceholder(address sender) { @@ -91,7 +91,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwner() { @@ -113,7 +113,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRole(bytes32 role) { @@ -135,7 +135,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrOpenRole(bytes32 role) { @@ -157,7 +157,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrAdmin(bytes32 role, address admin) { @@ -180,7 +180,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier assign(address sender) { @@ -204,7 +204,7 @@ LL │ ┃ sender; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier uncheckedBlock(address sender) { @@ -228,7 +228,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier emitEvent(address sender) { @@ -250,7 +250,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwnerContract(address sender) { diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 9970616900e6b..15363a6a40bb0 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -23,10 +23,10 @@ alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"] } -op-alloy-rpc-types.workspace = true -alloy-op-evm.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +alloy-op-evm = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } revm.workspace = true serde_json.workspace = true serde = { version = "1.0", features = ["derive"] } @@ -34,3 +34,12 @@ derive_more.workspace = true tempo-primitives.workspace = true tempo-alloy.workspace = true tempo-revm.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", +] diff --git a/crates/primitives/src/network/mod.rs b/crates/primitives/src/network/mod.rs index 7000b580b6a73..0b840185b0383 100644 --- a/crates/primitives/src/network/mod.rs +++ b/crates/primitives/src/network/mod.rs @@ -1,12 +1,20 @@ use alloy_network::Network; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; use alloy_provider::fillers::{ BlobGasFiller, ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, }; +#[cfg(feature = "optimism")] +pub use optimism::FoundryTransactionResponse; pub use receipt::*; +/// Default JSON-RPC transaction response when the `optimism` feature is disabled. +#[cfg(not(feature = "optimism"))] +pub type FoundryTransactionResponse = alloy_rpc_types_eth::Transaction; + /// Foundry network type. /// /// This network type supports Foundry-specific transaction types, including @@ -36,7 +44,7 @@ impl Network for FoundryNetwork { type TransactionRequest = crate::FoundryTransactionRequest; - type TransactionResponse = op_alloy_rpc_types::Transaction; + type TransactionResponse = FoundryTransactionResponse; type ReceiptResponse = crate::FoundryTxReceipt; diff --git a/crates/primitives/src/network/optimism.rs b/crates/primitives/src/network/optimism.rs new file mode 100644 index 0000000000000..aff30a755663f --- /dev/null +++ b/crates/primitives/src/network/optimism.rs @@ -0,0 +1,47 @@ +//! OP-stack-specific helpers and type aliases used by [`super::FoundryNetwork`] and +//! [`super::FoundryTxReceipt`]. + +use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_primitives::U64; +use alloy_rpc_types::Log; +use alloy_serde::OtherFields; +use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; + +use crate::FoundryReceiptEnvelope; + +/// JSON-RPC transaction response type used by [`super::FoundryNetwork`]. +pub type FoundryTransactionResponse = op_alloy_rpc_types::Transaction; + +/// Build a [`FoundryReceiptEnvelope::Deposit`] from a `ReceiptWithBloom` plus the OP +/// deposit-specific fields decoded from the [`OtherFields`] of an `AnyTransactionReceipt`. +pub(super) fn build_deposit_receipt_envelope( + receipt_with_bloom: ReceiptWithBloom>, + other: &OtherFields, +) -> FoundryReceiptEnvelope { + // These fields may not be present in all receipts, so missing/invalid values are None. + let deposit_nonce = other + .get_deserialized::("depositNonce") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + let deposit_receipt_version = other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + + FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: Receipt { + status: alloy_consensus::Eip658Value::Eip658(receipt_with_bloom.status()), + cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), + logs: receipt_with_bloom.receipt.logs, + }, + deposit_nonce, + deposit_receipt_version, + }, + logs_bloom: receipt_with_bloom.logs_bloom, + }) +} diff --git a/crates/primitives/src/network/receipt.rs b/crates/primitives/src/network/receipt.rs index b727b4c39b345..6b01f9eaa9ee9 100644 --- a/crates/primitives/src/network/receipt.rs +++ b/crates/primitives/src/network/receipt.rs @@ -1,13 +1,13 @@ -use alloy_consensus::{Receipt, TxReceipt}; use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt, ReceiptResponse}; -use alloy_primitives::{Address, B256, BlockHash, TxHash, U64}; +use alloy_primitives::{Address, B256, BlockHash, TxHash}; use alloy_rpc_types::{ConversionError, Log, TransactionReceipt}; use alloy_serde::WithOtherFields; use derive_more::AsRef; -use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; use serde::{Deserialize, Serialize}; use tempo_primitives::TEMPO_TX_TYPE_ID; +#[cfg(feature = "optimism")] +use super::optimism::build_deposit_receipt_envelope; use crate::FoundryReceiptEnvelope; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AsRef)] @@ -144,38 +144,8 @@ impl TryFrom for FoundryTxReceipt { 0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), 0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), TEMPO_TX_TYPE_ID => FoundryReceiptEnvelope::Tempo(receipt_with_bloom), - 0x7E => { - // Construct the deposit receipt, extracting optional deposit fields - // These fields may not be present in all receipts, so missing/invalid - // values are None - let deposit_nonce = other - .get_deserialized::("depositNonce") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - let deposit_receipt_version = other - .get_deserialized::("depositReceiptVersion") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - - FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { - receipt: OpDepositReceipt { - inner: Receipt { - status: alloy_consensus::Eip658Value::Eip658( - receipt_with_bloom.status(), - ), - cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), - logs: receipt_with_bloom.receipt.logs, - }, - deposit_nonce, - deposit_receipt_version, - }, - logs_bloom: receipt_with_bloom.logs_bloom, - }) - } + #[cfg(feature = "optimism")] + 0x7E => build_deposit_receipt_envelope(receipt_with_bloom, &other), _ => { let tx_type = r#type; return Err(ConversionError::Custom(format!( diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index ab4aafcc294c0..0a009a1931f06 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -1,6 +1,7 @@ +#[cfg(feature = "optimism")] +use alloy_consensus::{Sealed, Transaction as _}; use alloy_consensus::{ - Sealed, Signed, Transaction as _, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, - TxLegacy, TxType, Typed2718, + Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType, Typed2718, crypto::RecoveryError, transaction::{ SignerRecoverable, TxEip7702, TxHashRef, @@ -9,14 +10,10 @@ use alloy_consensus::{ }; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, TxHash}; use alloy_rpc_types::ConversionError; -use op_alloy_consensus::{ - DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait, POST_EXEC_TX_TYPE_ID, TxDeposit, - TxPostExec, -}; -use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec}; use revm::context::TxEnv; use tempo_primitives::{AASigned, TempoTransaction}; use tempo_revm::TempoTxEnv; @@ -57,9 +54,11 @@ pub enum FoundryTxEnvelope { /// OP stack deposit transaction. /// /// See . + #[cfg(feature = "optimism")] #[envelope(ty = 126)] Deposit(Sealed), /// OP stack post-execution synthetic transaction. + #[cfg(feature = "optimism")] #[envelope(ty = 0x7D)] PostExec(Sealed), /// Tempo transaction type. @@ -80,7 +79,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)), Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)), Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)), + #[cfg(feature = "optimism")] Self::Deposit(_) => Err(self), + #[cfg(feature = "optimism")] Self::PostExec(_) => Err(self), Self::Tempo(_) => Err(self), } @@ -109,7 +110,9 @@ impl FoundryTxEnvelope { Self::Eip1559(t) => *t.hash(), Self::Eip4844(t) => *t.hash(), Self::Eip7702(t) => *t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.tx_hash(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.tx_hash(), Self::Tempo(t) => *t.hash(), } @@ -128,7 +131,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => tx.recover_signer()?, Self::Eip4844(tx) => tx.recover_signer()?, Self::Eip7702(tx) => tx.recover_signer()?, + #[cfg(feature = "optimism")] Self::Deposit(tx) => tx.from, + #[cfg(feature = "optimism")] Self::PostExec(tx) => tx.inner().signer_address(), Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?, }) @@ -143,7 +148,9 @@ impl TxHashRef for FoundryTxEnvelope { Self::Eip1559(t) => t.hash(), Self::Eip4844(t) => t.hash(), Self::Eip7702(t) => t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.hash_ref(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.hash_ref(), Self::Tempo(t) => t.hash(), } @@ -160,23 +167,6 @@ impl SignerRecoverable for FoundryTxEnvelope { } } -impl OpTransactionTrait for FoundryTxEnvelope { - fn is_deposit(&self) -> bool { - matches!(self, Self::Deposit(_)) - } - - fn as_deposit(&self) -> Option<&Sealed> { - match self { - Self::Deposit(tx) => Some(tx), - _ => None, - } - } - - fn as_post_exec(&self) -> Option<&Sealed> { - if let Self::PostExec(tx) = self { Some(tx) } else { None } - } -} - impl TryFrom for TxEnvelope { type Error = FoundryTxEnvelope; @@ -197,19 +187,6 @@ impl From for FoundryTxEnvelope { } } -impl From for FoundryTxEnvelope { - fn from(tx: op_alloy_consensus::OpTxEnvelope) -> Self { - match tx { - op_alloy_consensus::OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), - op_alloy_consensus::OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), - op_alloy_consensus::OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), - op_alloy_consensus::OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), - op_alloy_consensus::OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), - op_alloy_consensus::OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), - } - } -} - impl From for FoundryTxEnvelope { fn from(tx: tempo_primitives::TempoTxEnvelope) -> Self { match tx { @@ -236,33 +213,50 @@ impl TryFrom for FoundryTxEnvelope { TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)), TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)), }, - AnyTxEnvelope::Unknown(mut tx) => { - // Try to convert to deposit transaction - if tx.ty() == DEPOSIT_TX_TYPE_ID { - tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); - let deposit_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize deposit tx: {e}" - )) - })?; - - return Ok(Self::Deposit(Sealed::new(deposit_tx))); + AnyTxEnvelope::Unknown(tx) => { + #[cfg(feature = "optimism")] + { + let mut tx = tx; + let _ = from; + // Try to convert to deposit transaction + if tx.ty() == DEPOSIT_TX_TYPE_ID { + tx.inner + .fields + .insert("from".to_string(), serde_json::to_value(from).unwrap()); + let deposit_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize deposit tx: {e}" + )) + })?; + + return Ok(Self::Deposit(Sealed::new(deposit_tx))); + } + + if tx.ty() == POST_EXEC_TX_TYPE_ID { + let post_exec_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize post-exec tx: {e}" + )) + })?; + + return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + } + + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - if tx.ty() == POST_EXEC_TX_TYPE_ID { - let post_exec_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize post-exec tx: {e}" - )) - })?; - - return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + #[cfg(not(feature = "optimism"))] + { + let _ = from; + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - let tx_type = tx.ty(); - Err(ConversionError::Custom(format!("Unknown transaction type: 0x{tx_type:02X}"))) } } } @@ -276,6 +270,7 @@ impl FromRecoveredTx for TxEnv { FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -288,6 +283,7 @@ impl FromRecoveredTx for TxEnv { ..Default::default() } } + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -303,63 +299,6 @@ impl FromRecoveredTx for TxEnv { } } -impl FromRecoveredTx for OpTransaction { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: None, deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl FromTxWithEncoded for TxEnv { fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { Self::from_recovered_tx(tx, sender) @@ -384,7 +323,9 @@ impl FromRecoveredTx for TempoTxEnv { FoundryTxEnvelope::Eip7702(signed_tx) => { Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) } + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => unreachable!("Post-exec tx in Tempo context"), FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller), } @@ -397,75 +338,6 @@ impl FromTxWithEncoded for TempoTxEnv { } } -impl FromRecoveredTx for OpTx { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - Self(OpTransaction::::from_recovered_tx(tx, caller)) - } -} - -impl FromTxWithEncoded for OpTx { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) - } -} - -impl FromTxWithEncoded for OpTransaction { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: Some(encoded), deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl std::fmt::Display for FoundryTxType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -474,7 +346,9 @@ impl std::fmt::Display for FoundryTxType { Self::Eip1559 => write!(f, "eip1559"), Self::Eip4844 => write!(f, "eip4844"), Self::Eip7702 => write!(f, "eip7702"), + #[cfg(feature = "optimism")] Self::Deposit => write!(f, "deposit"), + #[cfg(feature = "optimism")] Self::PostExec => write!(f, "post-exec"), Self::Tempo => write!(f, "tempo"), } @@ -501,7 +375,9 @@ impl From for FoundryTypedTx { FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()), FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()), FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => Self::PostExec(sealed_tx.into_inner()), FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()), } @@ -609,28 +485,6 @@ mod tests { assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); } - #[test] - fn test_decode_encode_deposit_tx() { - // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" - .parse::() - .unwrap(); - - // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let raw_tx = alloy_primitives::hex::decode( - "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", - ) - .unwrap(); - let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); - - let mut encoded = Vec::new(); - dep_tx.encode_2718(&mut encoded); - - assert_eq!(raw_tx, encoded); - - assert_eq!(tx_hash, dep_tx.hash()); - } - #[test] fn can_recover_sender_not_normalized() { let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); @@ -707,11 +561,6 @@ mod tests { assert_eq!(tx_env.caller, sender); assert_eq!(tx_env.gas_limit, 0x5208); assert_eq!(tx_env.gas_price, 1); - - // Test OpTransaction conversion via FromRecoveredTx trait - let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); - assert_eq!(op_tx.base.caller, sender); - assert_eq!(op_tx.base.gas_limit, 0x5208); } // Test vector from Tempo testnet: diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 7dccf3e30752f..18f39c437bfbc 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,7 +1,11 @@ mod envelope; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; mod request; pub use envelope::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; +#[cfg(feature = "optimism")] +pub use optimism::get_deposit_tx_parts; pub use receipt::FoundryReceiptEnvelope; -pub use request::{FoundryTransactionRequest, get_deposit_tx_parts}; +pub use request::FoundryTransactionRequest; diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs new file mode 100644 index 0000000000000..5952b5d6a9020 --- /dev/null +++ b/crates/primitives/src/transaction/optimism.rs @@ -0,0 +1,300 @@ +//! OP-stack-specific impls for [`FoundryTxEnvelope`] and [`FoundryTransactionRequest`]. + +use alloy_consensus::{Sealed, Transaction as _, Typed2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_op_evm::OpTx; +use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_serde::OtherFields; +use op_alloy_consensus::{ + OpDepositReceipt, OpDepositReceiptWithBloom, OpTransaction as OpTransactionTrait, OpTxEnvelope, + TxDeposit, TxPostExec, +}; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +use revm::context::TxEnv; + +use super::{FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope}; + +impl OpTransactionTrait for FoundryTxEnvelope { + fn is_deposit(&self) -> bool { + matches!(self, Self::Deposit(_)) + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + Self::Deposit(tx) => Some(tx), + _ => None, + } + } + + fn as_post_exec(&self) -> Option<&Sealed> { + if let Self::PostExec(tx) = self { Some(tx) } else { None } + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: OpTxEnvelope) -> Self { + match tx { + OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), + OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), + OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), + } + } +} + +impl FromRecoveredTx for OpTransaction { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: None, deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + Self(OpTransaction::::from_recovered_tx(tx, caller)) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) + } +} + +impl FromTxWithEncoded for OpTransaction { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: Some(encoded), deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: op_alloy_rpc_types::Transaction) -> Self { + tx.inner.into_inner().into() + } +} + +/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields. +pub fn get_deposit_tx_parts( + other: &OtherFields, +) -> Result> { + let mut missing = Vec::new(); + let source_hash = + other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("sourceHash"); + Default::default() + }, + ); + let mint = other + .get_deserialized::("mint") + .transpose() + .unwrap_or_else(|_| { + missing.push("mint"); + Default::default() + }) + .map(|value| value.to::()); + let is_system_transaction = + other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("isSystemTx"); + Default::default() + }, + ); + if missing.is_empty() { + Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) + } else { + Err(missing) + } +} + +/// OP-stack-specific accessors on [`FoundryReceiptEnvelope`]. +impl FoundryReceiptEnvelope { + /// Return the receipt's deposit_nonce if it is a deposit receipt. + pub fn deposit_nonce(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_nonce) + } + + /// Return the receipt's deposit version if it is a deposit receipt. + pub fn deposit_receipt_version(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { + match self { + Self::Deposit(t) => Some(t), + _ => None, + } + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { + match self { + Self::Deposit(t) => Some(&t.receipt), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use alloy_network::eip2718::Encodable2718; + use alloy_primitives::TxHash; + use alloy_rlp::Decodable; + + use super::*; + + #[test] + fn test_from_recovered_tx_legacy_op() { + use alloy_consensus::transaction::SignerRecoverable; + + let tx = r#" + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gas": "0x5208", + "gasPrice": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x1", + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "v": "0x1b", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + let sender = typed_tx.recover_signer().unwrap(); + + // Test OpTransaction conversion via FromRecoveredTx trait + let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); + assert_eq!(op_tx.base.caller, sender); + assert_eq!(op_tx.base.gas_limit, 0x5208); + } + + #[test] + fn test_decode_encode_deposit_tx() { + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); + + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + let mut encoded = Vec::new(); + dep_tx.encode_2718(&mut encoded); + + assert_eq!(raw_tx, encoded); + + assert_eq!(tx_hash, dep_tx.hash()); + } +} diff --git a/crates/primitives/src/transaction/receipt.rs b/crates/primitives/src/transaction/receipt.rs index fe209fc72c907..78bbcd1efa2fb 100644 --- a/crates/primitives/src/transaction/receipt.rs +++ b/crates/primitives/src/transaction/receipt.rs @@ -8,6 +8,7 @@ use alloy_network::eip2718::{ use alloy_primitives::{Bloom, Log, TxHash, logs_bloom}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, bytes}; use alloy_rpc_types::{BlockNumHash, trace::otterscan::OtsReceipt}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{ DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, POST_EXEC_TX_TYPE_ID, }; @@ -29,8 +30,10 @@ pub enum FoundryReceiptEnvelope { Eip4844(ReceiptWithBloom>), #[serde(rename = "0x4", alias = "0x04")] Eip7702(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7D", alias = "0x7d")] PostExec(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7E", alias = "0x7e")] Deposit(OpDepositReceiptWithBloom), #[serde(rename = "0x76")] @@ -44,7 +47,8 @@ impl FoundryReceiptEnvelope { cumulative_gas_used: u64, logs: impl IntoIterator, tx_type: FoundryTxType, - deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_receipt_version: Option, ) -> Self { let logs = logs.into_iter().collect::>(); @@ -67,9 +71,11 @@ impl FoundryReceiptEnvelope { FoundryTxType::Eip7702 => { Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => { Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { let inner = OpDepositReceiptWithBloom { receipt: OpDepositReceipt { @@ -112,13 +118,18 @@ impl FoundryReceiptEnvelope { removed: false, }) .collect::>(); + #[cfg(feature = "optimism")] + let (deposit_nonce, deposit_receipt_version) = + (self.deposit_nonce(), self.deposit_receipt_version()); + #[cfg(not(feature = "optimism"))] + let (deposit_nonce, deposit_receipt_version) = (None, None); FoundryReceiptEnvelope::::from_parts( self.status(), self.cumulative_gas_used(), logs, self.tx_type(), - self.deposit_nonce(), - self.deposit_receipt_version(), + deposit_nonce, + deposit_receipt_version, ) } } @@ -132,7 +143,9 @@ impl FoundryReceiptEnvelope { Self::Eip1559(_) => FoundryTxType::Eip1559, Self::Eip4844(_) => FoundryTxType::Eip4844, Self::Eip7702(_) => FoundryTxType::Eip7702, + #[cfg(feature = "optimism")] Self::PostExec(_) => FoundryTxType::PostExec, + #[cfg(feature = "optimism")] Self::Deposit(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -158,8 +171,12 @@ impl FoundryReceiptEnvelope { Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)), Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)), Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)), + #[cfg(feature = "optimism")] Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)), - Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(r.map_receipt(|r| r.map_logs(f))), + #[cfg(feature = "optimism")] + Self::Deposit(r) => FoundryReceiptEnvelope::Deposit( + r.map_receipt(|r: OpDepositReceipt| r.map_logs(f)), + ), Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)), } } @@ -182,38 +199,14 @@ impl FoundryReceiptEnvelope { Self::Eip1559(t) => &t.logs_bloom, Self::Eip4844(t) => &t.logs_bloom, Self::Eip7702(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::PostExec(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.logs_bloom, Self::Tempo(t) => &t.logs_bloom, } } - /// Return the receipt's deposit_nonce if it is a deposit receipt. - pub fn deposit_nonce(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_nonce) - } - - /// Return the receipt's deposit version if it is a deposit receipt. - pub fn deposit_receipt_version(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { - match self { - Self::Deposit(t) => Some(t), - _ => None, - } - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { - match self { - Self::Deposit(t) => Some(&t.receipt), - _ => None, - } - } - /// Consumes the type and returns the underlying [`Receipt`]. pub fn into_receipt(self) -> Receipt { match self { @@ -222,8 +215,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => t.receipt.into_inner(), } } @@ -236,8 +231,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => &t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => &t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.receipt.inner, } } @@ -287,7 +284,9 @@ impl Encodable for FoundryReceiptEnvelope { Self::Eip1559(r) => r.length() + 1, Self::Eip4844(r) => r.length() + 1, Self::Eip7702(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::PostExec(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::Deposit(r) => r.length() + 1, Self::Tempo(r) => r.length() + 1, _ => unreachable!("receipt already matched"), @@ -314,11 +313,13 @@ impl Encodable for FoundryReceiptEnvelope { EIP7702_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::PostExec(r) => { Header { list: true, payload_length: payload_len }.encode(out); POST_EXEC_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::Deposit(r) => { Header { list: true, payload_length: payload_len }.encode(out); DEPOSIT_TX_TYPE_ID.encode(out); @@ -371,18 +372,23 @@ impl Decodable for FoundryReceiptEnvelope { buf.advance(1); ::decode(buf) .map(FoundryReceiptEnvelope::Eip7702) - } else if receipt_type == POST_EXEC_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::PostExec) - } else if receipt_type == DEPOSIT_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::Deposit) } else if receipt_type == TEMPO_TX_TYPE_ID { buf.advance(1); ::decode(buf).map(FoundryReceiptEnvelope::Tempo) } else { + #[cfg(feature = "optimism")] + { + if receipt_type == POST_EXEC_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::PostExec); + } + if receipt_type == DEPOSIT_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::Deposit); + } + } Err(alloy_rlp::Error::Custom("invalid receipt type")) } } @@ -404,7 +410,9 @@ impl Typed2718 for FoundryReceiptEnvelope { Self::Eip1559(_) => EIP1559_TX_TYPE_ID, Self::Eip4844(_) => EIP4844_TX_TYPE_ID, Self::Eip7702(_) => EIP7702_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::PostExec(_) => POST_EXEC_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, Self::Tempo(_) => TEMPO_TX_TYPE_ID, } @@ -419,7 +427,9 @@ impl Encodable2718 for FoundryReceiptEnvelope { Self::Eip1559(r) => 1 + r.length(), Self::Eip4844(r) => 1 + r.length(), Self::Eip7702(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::PostExec(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::Deposit(r) => 1 + r.length(), Self::Tempo(r) => 1 + r.length(), } @@ -435,8 +445,10 @@ impl Encodable2718 for FoundryReceiptEnvelope { | Self::Eip1559(r) | Self::Eip4844(r) | Self::Eip7702(r) - | Self::PostExec(r) | Self::Tempo(r) => r.encode(out), + #[cfg(feature = "optimism")] + Self::PostExec(r) => r.encode(out), + #[cfg(feature = "optimism")] Self::Deposit(r) => r.encode(out), } } @@ -444,15 +456,18 @@ impl Encodable2718 for FoundryReceiptEnvelope { impl Decodable2718 for FoundryReceiptEnvelope { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == DEPOSIT_TX_TYPE_ID { - return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + #[cfg(feature = "optimism")] + { + if ty == DEPOSIT_TX_TYPE_ID { + return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + } + if ty == POST_EXEC_TX_TYPE_ID { + return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); + } } if ty == TEMPO_TX_TYPE_ID { return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?)); } - if ty == POST_EXEC_TX_TYPE_ID { - return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); - } match ReceiptEnvelope::typed_decode(ty, buf)? { ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)), ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)), @@ -646,8 +661,11 @@ mod tests { assert!(receipt.status()); assert_eq!(receipt.cumulative_gas_used(), 100000); assert!(receipt.logs().is_empty()); - assert!(receipt.deposit_nonce().is_none()); - assert!(receipt.deposit_receipt_version().is_none()); + #[cfg(feature = "optimism")] + { + assert!(receipt.deposit_nonce().is_none()); + assert!(receipt.deposit_receipt_version().is_none()); + } } #[test] diff --git a/crates/primitives/src/transaction/request.rs b/crates/primitives/src/transaction/request.rs index 2c4dbae8fdcd4..8ae31efbd5cb1 100644 --- a/crates/primitives/src/transaction/request.rs +++ b/crates/primitives/src/transaction/request.rs @@ -3,15 +3,19 @@ use alloy_network::{ BuildResult, NetworkTransactionBuilder, NetworkWallet, TransactionBuilder, TransactionBuilder4844, TransactionBuilderError, }; -use alloy_primitives::{Address, B256, ChainId, TxKind, U256}; +use alloy_primitives::{Address, ChainId, TxKind, U256}; use alloy_rpc_types::{AccessList, TransactionInputKind, TransactionRequest}; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit}; +#[cfg(feature = "optimism")] use op_revm::transaction::deposit::DepositTransactionParts; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionRequest; use tempo_primitives::{TEMPO_TX_TYPE_ID, TempoTxType}; +#[cfg(feature = "optimism")] +use super::optimism::get_deposit_tx_parts; use super::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; use crate::FoundryNetwork; @@ -28,6 +32,7 @@ use crate::FoundryNetwork; #[derive(Clone, Debug, PartialEq, Eq)] pub enum FoundryTransactionRequest { Ethereum(TransactionRequest), + #[cfg(feature = "optimism")] Op(WithOtherFields), Tempo(Box), } @@ -44,6 +49,7 @@ impl FoundryTransactionRequest { pub fn into_inner(self) -> TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx.inner, Self::Tempo(tx) => tx.inner, } @@ -55,6 +61,7 @@ impl FoundryTransactionRequest { /// # Returns /// - Ok(deposit_tx_parts) if all necessary keys are present to build a deposit transaction. /// - Err(missing) if some keys are missing to build a deposit transaction. + #[cfg(feature = "optimism")] pub fn get_deposit_tx_parts(&self) -> Result> { match self { Self::Op(tx) => get_deposit_tx_parts(&tx.other), @@ -69,9 +76,11 @@ impl FoundryTransactionRequest { pub fn preferred_type(&self) -> FoundryTxType { match self { Self::Ethereum(tx) => tx.preferred_type().into(), + #[cfg(feature = "optimism")] Self::Op(tx) if tx.inner.transaction_type == Some(POST_EXEC_TX_TYPE_ID) => { FoundryTxType::PostExec } + #[cfg(feature = "optimism")] Self::Op(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -95,6 +104,7 @@ impl FoundryTransactionRequest { /// Check if all necessary keys are present to build a Deposit transaction, returning a list of /// keys that are missing. + #[cfg(feature = "optimism")] pub fn complete_deposit(&self) -> Result<(), Vec<&'static str>> { self.get_deposit_tx_parts().map(|_| ()) } @@ -123,7 +133,9 @@ impl FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } { @@ -138,9 +150,10 @@ impl FoundryTransactionRequest { /// Converts the request into a `FoundryTypedTx`, handling all Ethereum and OP-stack transaction /// types. pub fn build_typed_tx(self) -> Result { + #[cfg(feature = "optimism")] if let Ok(deposit_tx_parts) = self.get_deposit_tx_parts() { // Build deposit transaction - Ok(FoundryTypedTx::Deposit(TxDeposit { + return Ok(FoundryTypedTx::Deposit(TxDeposit { from: self.from().unwrap_or_default(), source_hash: deposit_tx_parts.source_hash, to: self.kind().unwrap_or_default(), @@ -149,8 +162,9 @@ impl FoundryTransactionRequest { gas_limit: self.gas_limit().unwrap_or_default(), is_system_transaction: deposit_tx_parts.is_system_transaction, input: self.input().cloned().unwrap_or_default(), - })) - } else if self.complete_tempo().is_ok() + })); + } + if self.complete_tempo().is_ok() && let Self::Tempo(tx_req) = self { // Build Tempo transaction @@ -192,6 +206,7 @@ impl Serialize for FoundryTransactionRequest { { match self { Self::Ethereum(tx) => tx.serialize(serializer), + #[cfg(feature = "optimism")] Self::Op(tx) => tx.serialize(serializer), Self::Tempo(tx) => tx.serialize(serializer), } @@ -211,6 +226,7 @@ impl AsRef for FoundryTransactionRequest { fn as_ref(&self) -> &TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_ref(), } @@ -221,6 +237,7 @@ impl AsMut for FoundryTransactionRequest { fn as_mut(&mut self) -> &mut TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_mut(), } @@ -244,15 +261,16 @@ impl From> for FoundryTransactionRequest { { tempo_tx_req.set_nonce_key(nonce_key); } - Self::Tempo(Box::new(tempo_tx_req)) - } else if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) + return Self::Tempo(Box::new(tempo_tx_req)); + } + #[cfg(feature = "optimism")] + if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) || tx.transaction_type == Some(POST_EXEC_TX_TYPE_ID) || get_deposit_tx_parts(&tx.other).is_ok() { - Self::Op(tx) - } else { - Self::Ethereum(tx.into_inner()) + return Self::Op(tx); } + Self::Ethereum(tx.into_inner()) } } @@ -264,6 +282,7 @@ impl From for FoundryTransactionRequest { FoundryTypedTx::Eip1559(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip4844(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip7702(tx) => Self::Ethereum(Into::::into(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => { let other = OtherFields::from_iter([ ("sourceHash", tx.source_hash.to_string().into()), @@ -272,6 +291,7 @@ impl From for FoundryTransactionRequest { ]); WithOtherFields { inner: Into::::into(tx), other }.into() } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(tx) => WithOtherFields { inner: Into::::into(tx), other: OtherFields::default(), @@ -307,8 +327,9 @@ impl From for FoundryTransactionRequest { } } -impl From> for FoundryTransactionRequest { - fn from(tx: op_alloy_rpc_types::Transaction) -> Self { +#[cfg(not(feature = "optimism"))] +impl From> for FoundryTransactionRequest { + fn from(tx: alloy_rpc_types_eth::Transaction) -> Self { tx.inner.into_inner().into() } } @@ -437,7 +458,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.as_ref().complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } @@ -448,9 +471,14 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { } fn can_build(&self) -> bool { - self.as_ref().can_build() - || self.complete_deposit().is_ok() - || self.complete_tempo().is_ok() + if self.as_ref().can_build() || self.complete_tempo().is_ok() { + return true; + } + #[cfg(feature = "optimism")] + if self.complete_deposit().is_ok() { + return true; + } + false } fn output_tx_type(&self) -> FoundryTxType { @@ -465,7 +493,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit().ok(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => self.complete_type(pref).ok(), FoundryTxType::Tempo => self.complete_tempo().ok(), }?; @@ -479,11 +509,21 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { let inner = self.as_mut(); inner.transaction_type = Some(preferred_type as u8); inner.gas.is_none().then(|| inner.set_gas_limit(Default::default())); - if !matches!(preferred_type, FoundryTxType::Deposit | FoundryTxType::Tempo) { + let is_deposit = { + #[cfg(feature = "optimism")] + { + preferred_type == FoundryTxType::Deposit + } + #[cfg(not(feature = "optimism"))] + { + false + } + }; + if !is_deposit && preferred_type != FoundryTxType::Tempo { inner.trim_conflicting_keys(); inner.populate_blob_hashes(); } - if preferred_type != FoundryTxType::Deposit { + if !is_deposit { inner.nonce.is_none().then(|| inner.set_nonce(Default::default())); } if matches!(preferred_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { @@ -548,42 +588,10 @@ impl TransactionBuilder4844 for FoundryTransactionRequest { } } -/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields -pub fn get_deposit_tx_parts( - other: &OtherFields, -) -> Result> { - let mut missing = Vec::new(); - let source_hash = - other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("sourceHash"); - Default::default() - }, - ); - let mint = other - .get_deserialized::("mint") - .transpose() - .unwrap_or_else(|_| { - missing.push("mint"); - Default::default() - }) - .map(|value| value.to::()); - let is_system_transaction = - other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("isSystemTx"); - Default::default() - }, - ); - if missing.is_empty() { - Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) - } else { - Err(missing) - } -} - #[cfg(test)] mod tests { + use alloy_primitives::B256; + use super::*; fn default_tx_req() -> TransactionRequest { @@ -618,6 +626,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_routing_op_by_deposit_fields() { let tx = default_tx_req(); let mut other = OtherFields::default(); @@ -669,6 +678,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_serialization_op() { let tx = default_tx_req(); let mut other = OtherFields::default(); diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index 7f112ce1bbda8..ce94945fb27cf 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -27,3 +27,7 @@ revm-inspectors.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index acdcbbdbf95ea..6de71571ac7eb 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -62,3 +62,15 @@ tempo-primitives.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-cli/optimism", + "forge-script-sequence/optimism", + "forge-verify/optimism", +] diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 5ac7e4c669ade..f1bda2ccdcf55 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,8 +1,8 @@ -use std::{cmp::Ordering, sync::Arc, time::Duration}; +use std::{cmp::Ordering, num::NonZeroU64, sync::Arc, time::Duration}; use crate::{ - ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, - sequence::ScriptSequenceKind, verify::BroadcastedState, + ScriptArgs, ScriptConfig, build::LinkedBuildData, needs_script_rpc_estimate, + progress::ScriptProgress, sequence::ScriptSequenceKind, verify::BroadcastedState, }; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::{SignableTransaction, Signed}; @@ -21,11 +21,12 @@ use alloy_signer::Signature; use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; -use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; +use foundry_cli::utils::has_batch_support; use foundry_common::{ FoundryTransactionBuilder, TransactionMaybeSigned, provider::{ProviderBuilder, try_get_http_provider}, shell, + tempo::TempoSponsor, }; use foundry_config::Config; use foundry_evm::core::evm::{FoundryEvmNetwork, TempoEvmNetwork}; @@ -100,7 +101,17 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result<()> { + let access_key_authorization = match self { + Self::AccessKey(_, _, access_key) => Some(( + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.clone(), + )), + _ => None, + }; + if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) @@ -137,11 +148,28 @@ where } } + if let Some((wallet_address, key_address, key_authorization)) = + access_key_authorization.as_ref() + { + tx.prepare_access_key_authorization( + provider, + *wallet_address, + *key_address, + key_authorization.as_ref(), + ) + .await?; + } + // Chains which use `eth_estimateGas` are being sent sequentially and require their // gas to be re-estimated right before broadcasting. if !is_fixed_gas_limit && estimate_via_rpc { estimate_gas(tx, provider, estimate_multiplier).await?; } + + if let Some(sponsor) = tempo_sponsor { + let from = tx.from().expect("no sender"); + sponsor.attach_and_print::(tx, from).await?; + } } Ok(()) @@ -211,6 +239,7 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result { self.prepare( &provider, @@ -218,6 +247,7 @@ where is_fixed_gas_limit, estimate_via_rpc, estimate_multiplier, + tempo_sponsor, ) .await?; @@ -387,6 +417,27 @@ impl BundledState { SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?.map(Arc::new); + if tempo_sponsor.is_some() && self.script_config.tempo.sponsor_sig.is_some() { + let remaining = self + .sequence + .sequences() + .iter() + .map(|sequence| { + sequence + .transactions() + .skip(sequence.receipts.len()) + .filter(|tx| tx.is_unsigned()) + .count() + }) + .sum::(); + if remaining > 1 { + eyre::bail!( + "--tempo.sponsor-sig can only sponsor one remaining script transaction; use --tempo.sponsor-signer for multi-transaction scripts" + ); + } + } + let progress = ScriptProgress::default(); for i in 0..self.sequence.sequences().len() { @@ -464,6 +515,11 @@ impl BundledState { let kind = match tx_with_metadata.tx().clone() { TransactionMaybeSigned::Signed { tx, .. } => { + if tempo_sponsor.is_some() { + eyre::bail!( + "cannot attach Tempo sponsor signature to an already signed script transaction" + ); + } SendTransactionKind::Signed(tx) } TransactionMaybeSigned::Unsigned(mut tx) => { @@ -487,6 +543,8 @@ impl BundledState { tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } + self.script_config.tempo.apply::(&mut tx, None); + send_kind.for_sender(&from, tx)? } }; @@ -495,9 +553,13 @@ impl BundledState { }) .collect::>>()?; - let estimate_via_rpc = has_different_gas_calc(sequence.chain) - || self.script_config.evm_opts.networks.is_tempo() - || self.args.skip_simulation; + let estimate_via_rpc = needs_script_rpc_estimate( + sequence.chain, + self.script_config.evm_opts.networks.is_tempo(), + self.script_config.batch, + &self.script_config.tempo, + self.args.skip_simulation, + ); // We only wait for a transaction receipt before sending the next transaction, if // there is more than one signer. There would be no way of assuring @@ -525,6 +587,7 @@ impl BundledState { let pending_transactions = batch.iter().map(|(kind, is_fixed_gas_limit)| { let provider = provider.clone(); + let tempo_sponsor = tempo_sponsor.clone(); async move { let res = kind .clone() @@ -534,22 +597,36 @@ impl BundledState { *is_fixed_gas_limit, estimate_via_rpc, self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), ) .await; - (res, kind, 0, None) + (res, kind, *is_fixed_gas_limit, 0, None) } .boxed() }); let mut buffer = pending_transactions.collect::>(); - 'send: while let Some((res, kind, attempt, original_res)) = - buffer.next().await + 'send: while let Some(( + res, + kind, + is_fixed_gas_limit, + attempt, + original_res, + )) = buffer.next().await { - if res.is_err() && attempt <= 3 { + if res.is_err() + && self.script_config.tempo.sponsor_sig.is_some() + && attempt == 0 + { + debug!( + "not retrying transaction because --tempo.sponsor-sig is a static signature" + ); + } else if res.is_err() && attempt <= 3 { // Try to resubmit the transaction let provider = provider.clone(); let progress = seq_progress.inner.clone(); + let tempo_sponsor = tempo_sponsor.clone(); buffer.push(Box::pin(async move { debug!(err=?res, ?attempt, "retrying transaction "); let attempt = attempt + 1; @@ -557,8 +634,24 @@ impl BundledState { "retrying transaction {res:?} (attempt {attempt})" )); tokio::time::sleep(Duration::from_millis(1000 * attempt)).await; - let r = kind.clone().send(provider).await; - (r, kind, attempt, original_res.or(Some(res))) + let r = kind + .clone() + .prepare_and_send( + provider, + sequential_broadcast, + is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), + ) + .await; + ( + r, + kind, + is_fixed_gas_limit, + attempt, + original_res.or(Some(res)), + ) })); continue 'send; @@ -675,6 +768,7 @@ impl BundledState { let sequence = self.sequence.sequences_mut().get_mut(0).unwrap(); let provider = Arc::new(ProviderBuilder::::new(sequence.rpc_url()).build()?); + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?; // Collect sender addresses - batch mode requires single sender let senders: AddressHashSet = sequence @@ -794,16 +888,35 @@ impl BundledState { max_priority_fee_per_gas: Some(max_priority_fee_per_gas), ..Default::default() }, - fee_token: self.script_config.fee_token, + fee_token: self.script_config.tempo.common.fee_token, calls: calls.clone(), + nonce_key: self.script_config.tempo.expiring_nonce.then_some(U256::MAX), + valid_before: self.script_config.tempo.valid_before.and_then(NonZeroU64::new), ..Default::default() }; + self.script_config.tempo.apply::(&mut batch_tx, None); + + if let BatchSigner::TempoKeychain(_, ak) = &batch_signer { + batch_tx.key_id = Some(ak.key_address); + batch_tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } // Estimate gas for the batch transaction estimate_gas(&mut batch_tx, provider.as_ref(), self.args.gas_estimate_multiplier).await?; sh_println!("Estimated gas: {}", batch_tx.inner.gas.unwrap_or(0))?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut batch_tx, sender).await?; + } + // Sign and send let tx_hash = match batch_signer { BatchSigner::Wallet(wallet) => { @@ -816,8 +929,6 @@ impl BundledState { *pending.tx_hash() } BatchSigner::TempoKeychain(signer, access_key) => { - batch_tx.key_id = Some(access_key.key_address); - let raw_tx = batch_tx .sign_with_access_key( provider.as_ref(), diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index f5e4d46de0344..ea906c9b872e1 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -28,8 +28,8 @@ use eyre::{ContextCompat, Result}; use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; use foundry_cli::{ - opts::{BuildOpts, EvmArgs, GlobalArgs}, - utils::{LoadConfig, parse_fee_token_address}, + opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts}, + utils::{LoadConfig, has_different_gas_calc}, }; use foundry_common::{ CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, @@ -44,11 +44,13 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ backend::Backend, core::{ Breakpoints, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, tempo::PATH_USD_ADDRESS, }, executors::ExecutorBuilder, @@ -140,9 +142,9 @@ pub struct ScriptArgs { #[arg(long, requires = "batch", default_value = "100")] pub batch_size: usize, - /// Tempo fee token address for paying transaction fees. - #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] - pub fee_token: Option
, + /// Tempo transaction options. + #[command(flatten)] + pub tempo: TempoOpts, /// Skips on-chain simulation. #[arg(long)] @@ -246,13 +248,43 @@ pub struct ScriptArgs { pub retry: RetryArgs, } +const fn should_default_tempo_fee_token( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + // Plain `--network tempo` should stay an ordinary transaction; only Tempo AA opts get defaults. + is_tempo_network && tempo.common.fee_token.is_none() && (batch || tempo.is_tempo()) +} + +const fn needs_tempo_aa_rpc_estimate( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + is_tempo_network && (batch || tempo.is_tempo()) +} + +pub(crate) fn needs_script_rpc_estimate( + chain_id: u64, + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, + skip_simulation: bool, +) -> bool { + // Tempo AA needs RPC estimation; plain Tempo scripts can use the local simulation result. + (has_different_gas_calc(chain_id) && !is_tempo_network) + || needs_tempo_aa_rpc_estimate(is_tempo_network, batch, tempo) + || skip_simulation +} + impl ScriptArgs { /// Loads config, resolves evm_opts (including network inference from fork), and returns them. async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - if self.fee_token.is_some() { - // If fee token is set directly select tempo + if self.tempo.is_tempo() { + // If fee token or expiry is set directly select tempo evm_opts.networks = NetworkConfigs::with_tempo(); } else { // Auto-detect network from fork chain ID when not explicitly configured. @@ -285,13 +317,14 @@ impl ScriptArgs { } } - let fee_token = if evm_opts.networks.is_tempo() && self.fee_token.is_none() { - Some(PATH_USD_ADDRESS) - } else { - self.fee_token - }; + let mut tempo = self.tempo.clone(); + tempo.resolve_expires(); + + if should_default_tempo_fee_token(evm_opts.networks.is_tempo(), self.batch, &tempo) { + tempo.common.fee_token = Some(PATH_USD_ADDRESS); + } - let script_config = ScriptConfig::new(config, evm_opts, self.batch, fee_token).await?; + let script_config = ScriptConfig::new(config, evm_opts, self.batch, tempo).await?; Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } @@ -320,12 +353,15 @@ impl ScriptArgs { if broadcasted.args.verify { broadcasted.verify().await?; } - Ok(()) - } else if evm_opts.networks.is_optimism() { - self.run_generic_script::(config, evm_opts).await - } else { - self.run_generic_script::(config, evm_opts).await + return Ok(()); } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_generic_script::(config, evm_opts).await; + } + + self.run_generic_script::(config, evm_opts).await } /// Prepares the bundled state (compile, simulate, bundle) and returns it @@ -708,8 +744,8 @@ pub struct ScriptConfig { pub backends: HashMap>, /// Whether to batch all broadcast transactions into a single Tempo batch transaction. pub batch: bool, - /// Tempo fee token address for paying transaction fees. - pub fee_token: Option
, + /// Tempo transaction options applied to broadcast transactions. + pub tempo: TempoOpts, } impl ScriptConfig { @@ -717,7 +753,7 @@ impl ScriptConfig { config: Config, evm_opts: EvmOpts, batch: bool, - fee_token: Option
, + tempo: TempoOpts, ) -> Result { let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? @@ -726,7 +762,7 @@ impl ScriptConfig { 1 }; - Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, fee_token }) + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, tempo }) } pub async fn update_sender(&mut self, sender: Address) -> Result<()> { @@ -802,7 +838,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), - self.fee_token, + self.tempo.common.fee_token, ) .into(), ) @@ -813,7 +849,7 @@ impl ScriptConfig { // Propagate fee token to the transaction environment so that internal EVM calls // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. - tx_env.set_fee_token(self.fee_token); + tx_env.set_fee_token(self.tempo.common.fee_token); Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } @@ -823,6 +859,7 @@ impl ScriptConfig { mod tests { use super::*; use alloy_network::Ethereum; + use alloy_primitives::address; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -834,6 +871,50 @@ mod tests { assert_eq!(args.sig, sig); } + #[test] + fn can_parse_shared_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.fee-token", + "1", + "--tempo.expires", + "10", + ]); + + assert_eq!( + args.tempo.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")) + ); + assert_eq!(args.tempo.common.expires, Some(10)); + } + + #[test] + fn can_parse_sponsor_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]); + + assert_eq!( + args.tempo.sponsor, + Some(address!("0x1111111111111111111111111111111111111111")) + ); + assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + } + + #[test] + fn can_parse_full_tempo_opts() { + let args = + ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]); + + assert_eq!(args.tempo.nonce_key, Some(U256::from(1))); + } + #[test] fn can_parse_unlocked() { let args = ScriptArgs::parse_from([ diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index e2404d60ce2b9..b085f8eaf4545 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -6,7 +6,7 @@ use alloy_network::TransactionBuilder; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; -use foundry_common::{FoundryTransactionBuilder, TransactionMaybeSigned}; +use foundry_common::TransactionMaybeSigned; use foundry_config::Config; use foundry_evm::{ constants::CALLER, @@ -84,9 +84,7 @@ impl ScriptRunner { .with_input(code.clone()) .with_nonce(sender_nonce + library_transactions.len() as u64); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), @@ -122,9 +120,7 @@ impl ScriptRunner { .with_nonce(sender_nonce + library_transactions.len() as u64) .with_to(create2_deployer); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index ef10a1ce94082..fe1e9345fa23c 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -129,7 +129,7 @@ impl VerifyBundle { path: Some(artifact.source.to_string_lossy().to_string()), name: artifact .name - .strip_suffix(&format!(".{}", &artifact.profile)) + .strip_suffix(&format!(".{}", artifact.profile)) .unwrap_or_else(|| &artifact.name) .to_string(), }; diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index 69ea952d4d040..d3ad56a96cdd2 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -27,3 +27,7 @@ prettyplease.workspace = true eyre.workspace = true heck.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index d29f6358e93d2..8dc652bcb32bd 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -44,3 +44,7 @@ idna_adapter.workspace = true [dev-dependencies] tokio.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 65a202911509f..e3372a2494f34 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -48,3 +48,12 @@ url.workspace = true tokio = { workspace = true, features = ["macros"] } foundry-test-utils.workspace = true tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-cli/optimism", +] diff --git a/docs/dev/lintrules.md b/docs/dev/lintrules.md index 6f5dbbd850784..969d7effe142f 100644 --- a/docs/dev/lintrules.md +++ b/docs/dev/lintrules.md @@ -60,6 +60,8 @@ Next, choose whether you want an [early or late lint pass](#choosing-between-ear - Implement the appropriate trait logic (`EarlyLintPass` or `LateLintPass`) for your lint. Do it in a new file within the relevant severity module (e.g., `src/sol/med/my_new_lint.rs`). +- Add a markdown documentation file for the lint at `crates/lint/docs/.md`. The file is referenced by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the [Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. Use [`crates/lint/docs/_template.md`](../../crates/lint/docs/_template.md) as a starting point. The presence of this file is enforced by the `registered_lints_have_docs` unit test in `crates/lint/src/sol/mod.rs`. + ### Choosing Between Early and Late Passes - **Use `EarlyLintPass`** for: diff --git a/flake.lock b/flake.lock index 27f426f491da6..343a79c0cda3d 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1777102577, - "narHash": "sha256-ycoy9svZOQgyInu/lwO7IEQtlP5liqYhEcF9m9hPRbM=", + "lastModified": 1777708550, + "narHash": "sha256-Qif3UXT0l5OQq8H9pRWt4/ia4gF48MWK2oHKL8uVx8U=", "owner": "nix-community", "repo": "fenix", - "rev": "f37403486c59376cd285f9685a8ef8ff25c09a3c", + "rev": "74c1591efaff494756b8d35ebe357c6c2bbdca96", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776949667, - "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=", + "lastModified": 1777641297, + "narHash": "sha256-WNGcmeOZ8Tr9dq6ztCspYbzWFswr2mPebM9LpsfGxPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30", + "rev": "c6d65881c5624c9cae5ea6cedef24699b0c0a4c0", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1776800521, - "narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=", + "lastModified": 1777639980, + "narHash": "sha256-6d7Hdurvbjc5uwJuc0YiK7rZBGj6Gs3uzfBFcTs+xCc=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e", + "rev": "64cdaeb06f69b6b769a492edd88b022ae88e8ca2", "type": "github" }, "original": { diff --git a/foundryup/README.md b/foundryup/README.md index 29e91378929cc..2cf61f8725227 100644 --- a/foundryup/README.md +++ b/foundryup/README.md @@ -30,10 +30,10 @@ To install the latest **nightly** version: foundryup --install nightly ``` -To install a specific version (e.g. `v1.6.0`): +To install a specific version (e.g. `v1.7.0`): ```sh -foundryup --install v1.6.0 +foundryup --install v1.7.0 ``` To **list** all **versions** installed: diff --git a/foundryup/foundryup b/foundryup/foundryup index 7576501b4619e..5fb086a75f8c1 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -3,7 +3,7 @@ set -eo pipefail # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -FOUNDRYUP_INSTALLER_VERSION="1.8.1" +FOUNDRYUP_INSTALLER_VERSION="1.8.3" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} @@ -15,6 +15,13 @@ FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" FOUNDRYUP_JOBS="" FOUNDRYUP_IGNORE_VERIFICATION=false +# Retry/backoff settings used for `fetch` (GitHub API calls). +# Recovers from transient HTTP 403/429/5xx responses returned by +# api.github.com under heavy load or per-IP rate limiting. +FOUNDRYUP_MAX_RETRIES=5 +FOUNDRYUP_RETRY_DELAY=2 +FOUNDRYUP_RETRY_MAX_TIME=60 + BINS=(forge cast anvil chisel) HASH_NAMES=() HASH_VALUES=() @@ -111,51 +118,7 @@ main() { # Install by downloading binaries if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-latest} - - # Normalize versions (handle channels, versions without v prefix) - if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then - # Resolve to the latest release (non-prerelease) via the GitHub API - say "fetching latest release from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' - /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } - ') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a latest release for ${FOUNDRYUP_REPO}" - fi - say "resolved release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" - elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then - # Resolve to the latest nightly (prerelease) release via the GitHub API. - # The GitHub API does not guarantee that releases are returned in - # chronological order, so we collect all matching nightlies along with - # their `published_at` timestamps and sort them ourselves. - say "fetching latest nightly releases from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' - /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } - /"published_at"[[:space:]]*:[[:space:]]*"/ { - pub=$0 - gsub(/.*"published_at"[[:space:]]*:[[:space:]]*"/, "", pub) - gsub(/".*/, "", pub) - if (tag ~ /^nightly-/) print pub "\t" tag - tag="" - } - ' | sort -r | awk -F '\t' 'NR==1 { print $2 }') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a nightly release for ${FOUNDRYUP_REPO}" - fi - say "resolved nightly release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then - # Specific nightly tag (e.g. nightly-abc123...) - FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then - # Add v prefix - FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - else - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - fi + resolve_version_and_tag say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})" @@ -179,7 +142,7 @@ main() { tmp_dir="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" tmp="$tmp_dir/attestation.txt" ensure download "$ATTESTATION_URL" "$tmp" - + # Read the first line of the attestation file to get the artifact link. # The first line should contain the link to the attestation artifact. attestation_artifact_link="$(head -n1 "$tmp" | tr -d '\r')" @@ -292,7 +255,7 @@ main() { else say 'skipping manpage download: missing "tar"' fi - + if [ "$FOUNDRYUP_IGNORE_VERIFICATION" = true ]; then say "skipped SHA verification for downloaded binaries due to --force flag" else @@ -505,6 +468,18 @@ list() { use() { [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" + + # If the requested version is a channel (`latest`, `stable`, `nightly`) or a bare semver + # version (e.g. `1.7.0`, `1.6.0-rc1`), resolve it to the immutable tag directory created by + # `--install` (channels hit the GitHub API; semver versions get a `v` prefix). + # Falls back to the literal value for locally-built versions (branches, PRs, commits, custom names). + case "$FOUNDRYUP_VERSION" in + latest|stable|nightly|[0-9]*.[0-9]*.[0-9]*) + resolve_version_and_tag + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + ;; + esac + FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" if [ -d "$FOUNDRY_VERSION_DIR" ]; then @@ -683,12 +658,70 @@ ensure() { if ! "$@"; then err "command failed: $*"; fi } -# Silently fetches $1 to stdout +# Normalizes `FOUNDRYUP_VERSION` and resolves it to a concrete release tag, +# populating `FOUNDRYUP_TAG`. Handles the `latest`/`stable`/`nightly` channels +# (looked up via the GitHub API). +resolve_version_and_tag() { + FOUNDRYUP_REPO=${FOUNDRYUP_REPO:-foundry-rs/foundry} + if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then + # Resolve to the latest release (non-prerelease) via the GitHub API. + say "fetching latest release tag from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' + /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } + ') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a latest release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then + # Resolve to the latest nightly (prerelease) release via the GitHub API. + # The GitHub API does not guarantee that releases are returned in + # chronological order, so we collect all matching nightlies along with + # their `published_at` timestamps and sort them ourselves. + say "fetching latest nightly release tags from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' + /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } + /"published_at"[[:space:]]*:[[:space:]]*"/ { + pub=$0 + gsub(/.*"published_at"[[:space:]]*:[[:space:]]*"/, "", pub) + gsub(/".*/, "", pub) + if (tag ~ /^nightly-/) print pub "\t" tag + tag="" + } + ' | sort -r | awk -F '\t' 'NR==1 { print $2 }') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a nightly release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved nightly release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then + # Specific nightly tag (e.g. nightly-abc123...) + FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then + # Add v prefix + FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + else + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + fi +} + +# Silently fetches $1 to stdout. fetch() { if check_cmd curl; then - curl -fsSL "$1" + curl -fsSL \ + --retry "$FOUNDRYUP_MAX_RETRIES" \ + --retry-delay "$FOUNDRYUP_RETRY_DELAY" \ + --retry-max-time "$FOUNDRYUP_RETRY_MAX_TIME" \ + --retry-all-errors \ + "$1" else - wget -qO- "$1" + wget --tries="$FOUNDRYUP_MAX_RETRIES" \ + --waitretry="$FOUNDRYUP_RETRY_DELAY" \ + --retry-on-http-error=403,408,429,500,502,503,504 \ + -qO- "$1" fi } diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 839d97962aa94..ae0c8ed844f5d 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -305,6 +305,91 @@ contract ExpectRevertWithReverterTest is Test { vm.expectRevert(address(cContract)); aContract.createDContractThroughCContract(); } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // reverts directly, the reverter address argument must be enforced (it used to + // be silently ignored). The matched reverter is the would-be-deployed address. + function testExpectRevertsWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new DContract(); + + expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(abi.encodePacked("Reverted by DContract"), expected); + new DContract(); + } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // synchronously creates another contract that reverts (i.e. innermost frame is + // a CREATE), the matched reverter is the outer would-be-deployed address (the + // contract whose deployment failed). + function testExpectRevertsWithReverterNestedCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new NestedDContractCreator(); + } + + // + // Regression: `expectPartialRevert(bytes4, address)` overload must enforce + // the reverter address argument when matching a top-level CREATE revert. + function testExpectPartialRevertWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + // `Reverted by DContract` triggers Solidity's `Error(string)` selector. + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), expected); + new DContract(); + } + + // + // Regression: `expectRevert(bytes4, address)` (exact 4-byte selector + reverter) + // overload must enforce the reverter address argument for a top-level CREATE. + function testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(DCustomErrorContract.CustomError.selector, expected); + new DCustomErrorContract(); + } + + // + // Regression: `expectRevert(address, uint64)` count-bearing overload must + // exercise the `count > 1` branch in `create_end`. Use CREATE2 with the same + // salt so both deploys would resolve to the same would-be address (each + // constructor reverts so no contract is ever actually placed there). + function testExpectRevertsWithReverterCountTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0x42)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected, 2); + new DContract{salt: salt}(); + new DContract{salt: salt}(); + } + + // + // Regression: CREATE2 deploys must also enforce the reverter address argument. + function testExpectRevertsWithReverterTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0xC0FFEE)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected); + new DContract{salt: salt}(); + } +} + +// Used by `testExpectRevertsWithReverterNestedCreate`: a contract whose constructor +// directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } +} + +// Used by `testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate`: constructor +// reverts with a parameter-less custom error so the full revert data is exactly the +// 4-byte selector. +contract DCustomErrorContract { + error CustomError(); + + constructor() { + revert CustomError(); + } } contract ExpectRevertCount is Test { diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index 6139b8b6b6a5e..f01b7cdd7d213 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -84,4 +84,55 @@ contract GetFoundryVersionTest is Test { // Should return true for past versions assertTrue(vm.foundryVersionAtLeast("0.2.0")); } + + /// Returns the `MAJOR.MINOR.PATCH` prefix of `vm.getFoundryVersion()`, + /// stripping any pre-release suffix (`-nightly`, `-dev`, …) and the + /// `+..` build metadata. + function _semverPrefix() internal view returns (string memory) { + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + string[] memory dashSplit = vm.split(plusSplit[0], "-"); + return dashSplit[0]; + } + + function testGetFoundryVersionMajorMinorPatchIsParseable() public view { + // The MAJOR.MINOR.PATCH prefix must always be three numeric components, + // regardless of build kind (tagged release / nightly / dev). + string[] memory parts = vm.split(_semverPrefix(), "."); + require(parts.length == 3, "Invalid semver prefix: expected MAJOR.MINOR.PATCH"); + // Each component must parse as a uint (this reverts on garbage). + vm.parseUint(parts[0]); + vm.parseUint(parts[1]); + vm.parseUint(parts[2]); + } + + function testGetFoundryVersionBuildProfile() public view { + // The build profile must be present and non-empty (e.g. "debug", "release", "dist", …). + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + string[] memory metadataComponents = vm.split(plusSplit[1], "."); + require(bytes(metadataComponents[2]).length > 0, "Build profile is empty"); + } + + function testFoundryVersionCmpAndAtLeastAreConsistent() public { + // `foundryVersionAtLeast(v)` must equal `foundryVersionCmp(v) >= 0` for any input. + string[3] memory probes = ["0.0.1", _semverPrefix(), "99.0.0"]; + for (uint256 i = 0; i < probes.length; i++) { + assertEq(vm.foundryVersionAtLeast(probes[i]), vm.foundryVersionCmp(probes[i]) >= 0); + } + } + + function testFoundryVersionCmpRejectsPreRelease() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0-nightly"); + } + + function testFoundryVersionCmpRejectsBuildMetadata() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0+abc1234567.1700000000.release"); + } + + function testFoundryVersionCmpRejectsInvalidVersion() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("not-a-version"); + } } diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index e2ac74d6f70fa..d8019ab4f6ee8 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -158,6 +158,35 @@ contract MockCallTest is Test { assertEq(mock.pay{value: 50}(1), 100); } + function testMockCallWithValueTransfersBalance() public { + Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + assertEq(address(mock).balance, 0); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + } + + function testMockCallWithValueTransfersPrankedSenderBalance() public { + Mock mock = new Mock(); + address sender = address(0xBEEF); + uint256 value = 10; + vm.deal(address(this), 0); + vm.deal(sender, value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + vm.prank(sender); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + assertEq(sender.balance, 0); + } + function testMockCallWithValueCalldataPrecedence() public { Mock mock = new Mock(); @@ -279,17 +308,25 @@ contract MockCallRevertTest is Test { function testMockCallRevertWithValue() public { Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); - vm.mockCallRevert(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); + vm.mockCallRevert(address(mock), value, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - try mock.pay{value: 10}(1) { + uint256 initSenderBalance = address(this).balance; + uint256 initTargetBalance = address(mock).balance; + + try mock.pay{value: value}(1) { revert(); } catch (bytes memory err) { require(keccak256(err) == keccak256(ERROR_MESSAGE)); } + + assertEq(address(this).balance, initSenderBalance); + assertEq(address(mock).balance, initTargetBalance); } function testMockCallResetsMockCallRevert() public { diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol index e0f5eef151db6..777543f28e361 100644 --- a/testdata/default/cheats/MockCalls.t.sol +++ b/testdata/default/cheats/MockCalls.t.sol @@ -28,13 +28,17 @@ contract MockCallsTest is Test { mocks[0] = abi.encode(2 ether); mocks[1] = abi.encode(1 ether); mocks[2] = abi.encode(6.423 ether); + vm.deal(address(this), 3 ether); vm.mockCalls(mockErc20, 1 ether, data, mocks); (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret1, (uint256)), 2 ether); + assertEq(mockErc20.balance, 1 ether); (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret2, (uint256)), 1 ether); + assertEq(mockErc20.balance, 2 ether); (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + assertEq(mockErc20.balance, 3 ether); } function testMockCalls() public { From b2931c358e7d33b11c12886b6c20808fbbe43f50 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 07:29:33 +0700 Subject: [PATCH 380/391] Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 53a505774ac88..edd3e4a15ddbc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,6 +25,8 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] + - Browser [e.g. Chrome, Safari] + - Version [e.g. 22] - Browser [e.g. Chrome, safari] - Version [e.g. 22] From fc617f4b1e82ecceb1f4850d373e8d8373b5f38e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 08:36:51 +0700 Subject: [PATCH 381/391] Tempo signer lookup and access key signing (#523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Forge/master (#311) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Update dev_stage.yml (#313) (#315) * Update dev_stage.yml (#313) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/dev_stage.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Foundry/main (#316) * chore(deps): bump the cargo group across 1 directory with 2 updates Bumps the cargo group with 2 updates in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing) and [ammonia](https://github.com/rust-ammonia/ammonia). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) Updates `ammonia` from 4.1.0 to 4.1.2 - [Release notes](https://github.com/rust-ammonia/ammonia/releases) - [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo - dependency-name: ammonia dependency-version: 4.1.2 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update crates/verify/src/provider.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/as_doc.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update as_doc.rs (#235) Tidy up formatting in as_doc.rs by removing extraneous blank lines in the Document::as_doc implementation Enhancements: Remove unnecessary blank line before initializing bases Remove unnecessary blank line before writing state variables Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: ignore RUSTSEC (#13011) * update deny for CI * Update more * chore(chisel): rm dead code (#13014) * chore(cli): rm dead code (#13015) * chore(cheatcodes): rm dead code (#13016) * chore(common): rm dead code (#13018) * chore(bench): rm dead code (#13017) * fix(forge): respect lint ignore config in solar compilation (#12978) Co-authored-by: tefyosL-sol * fix: deduplicate submodule status check logic (#13010) Update mod.rs * Foundry/ethereum ux fix tempo #296 (#319) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: Gengar * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 104: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 105: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: add Tempo transaction receipt type support in TryFrom conversion (#334) * fix: add Tempo transaction receipt type support in TryFrom conversion (#13047) Amp-Thread-ID: https://ampcode.com/threads/T-019bbf45-d7c8-75ed-8c05-bc1638d487ee Co-authored-by: Matthias Seitz Co-authored-by: Amp * feat(cheatcodes): add getRecordedLogsJson cheatcode (#13093) Adds a new cheatcode `getRecordedLogsJson` that returns recorded logs as a JSON string, similar to the existing `getStateDiffJson` pattern. This allows users to easily post-process recorded logs externally without needing to manually transform the Log[] array to JSON. JSON format: ```json [{"topics": ["0x..."], "data": "0x...", "emitter": "0x..."}] ``` Closes #12854 * feat: add Sourcify support to forge clone (#12900) * Integrate Sourcify API for contract cloning Added support for Sourcify API in `forge clone` command. * Add reqwest dependency with json feature * Remove unused import in clone.rs Removed unused import of BTreeMap. * Refactor EtherscanClient to ExplorerClient * Change sourcify module from private to public * Implement test for sourcify clone functionality Add test for cloning with sourcify source * Update clone.rs * Add url dependency to Cargo.toml * cargo fmt * Enhance Sourcify client with cached creation data Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields. * Improve error handling for contract data retrieval Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable. * Enhance contract_source_code with improved caching Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API. * Refactor creation_data handling in clone.rs Removed redundant creation_data initialization and caching. * Refactor response deserialization to use untagged enum * fix: use serde_json::Value for abi in Sourcify parsing The #[serde(untagged)] enum SourcifyContractResponse failed to deserialize because Box doesn't work with untagged enums. RawValue requires borrowing from the original JSON, but untagged enums buffer data during variant matching. Changes: - Change abi field from Box to serde_json::Value - Truncate response in error messages to avoid huge output * feat: add --sourcify-url option for custom Sourcify API endpoint * feat: imply --source sourcify when --sourcify-url is specified * feat: support full path in --sourcify-url When --sourcify-url contains v2/contract/chain, only append address and fields instead of building the full path again. --------- Co-authored-by: grandizzy * perf: add dist profile for smaller release binaries (#13097) * perf: add dist profile for smaller release binaries Add a new 'dist' Cargo profile optimized for distribution: - Fat LTO and codegen-units=1 for better optimization - Strip symbols for smaller binaries - opt-level="s" overrides for non-perf-critical dependencies Benchmarks on Solady test suite show dist is 8% faster than release while being 45% smaller (43MB vs 78MB). Update release workflows to use the dist profile instead of maxperf. * Apply suggestion from @DaniPopes --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore(deps): update figment to figment2 v0.11 (#13099) * chore(deps): update figment to figment2 v0.11 * rename * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY (#13094) * feat: add precompile decoding for Prague BLS12-381 and Osaka P256VERIFY * wip * wip * fix(traces): use raw byte decoding for P256VERIFY precompile P256VERIFY (RIP-7212) uses concatenated raw bytes, not ABI encoding: - Input: hash (32) + r (32) + s (32) + qx (32) + qy (32) = 160 bytes - Output: 32 bytes where 0x...01 means success * fix(traces): use raw byte decoding for all precompiles Precompiles use concatenated raw bytes, not ABI encoding: - ecrecover: hash (32) + v (32) + r (32) + s (32), returns address in last 20 bytes - sha256/ripemd160: raw input, raw 32-byte output (ripemd in last 20 bytes) - ecadd: x1/y1/x2/y2 (32 each), returns x/y (32 each) - ecmul: x1/y1/s (32 each), returns x/y (32 each) - ecpairing: returns 32-byte bool (1 = success) - bls12PairingCheck: returns 32-byte bool (1 = success) * fix(traces): restore ABI-based precompile decoding * fix * fix(anvil): use suggested priority fee by default (#13092) * fix(anvil): use suggested priority fee by default * test: fix anvil trace expectations --------- Co-authored-by: tefyosL-sol * chore: aggregate PRs (#13100) * chore: aggregate PRs This PR aggregates changes from the following PRs: - Closes #13032 by @\splinter012 - Closes #13059 by @\phrwlk * fmt * chore(evm): misleading error message in traces serialization (#13081) Co-authored-by: tefyosL-sol --------- Co-authored-by: Desant pivo Co-authored-by: Matthias Seitz Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: grandizzy Co-authored-by: onbjerg Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol * Potential fix for code scanning alert no. 103: Artifact poisoning (#336) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Create Docker.yml (#338) Build: Introduce a Docker GitHub Actions workflow that logs into Docker Hub, builds images with buildx, tags them based on branch, semver, and SHA, and pushes them on non-PR events while only loading them for pull requests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 108: Artifact poisoning (#345) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 110: Uncontrolled data used in path expression (#347) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 102: Artifact poisoning (#351) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * benches\LATEST.md (#350) * benches\LATEST.md * Update benches/LATEST.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 109: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#344) * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci.yml (#57) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#66) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#71) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#107) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update crates/doc/src/writer/buf_writer.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> … * Potential fix for code scanning alert no. 102: Artifact poisoning (#354) * Potential fix for code scanning alert no. 102: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/npm.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix(config): Respect user-configured etherscan URL over chain defaults (#13238) (#357) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 156: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Hardhat project (#378) * docs(config): complete foundry.toml configuration reference (#13198) * complete config * docs: mark optional fields in config README * use foundry book for full config * docs(config): point to Foundry Book for configuration reference * feat(invariant): add optimization mode for invariant testing (#13196) * feat(invariant): add optimization mode for invariant testing Adds optimization mode for invariant testing, similar to Echidna. This mode maximizes an `int256` return value from a function prefixed with `optimize_`. **Usage:** - Define an invariant function returning `int256` with `optimize_` prefix - Foundry will fuzz to find the sequence that maximizes this value - The best value and sequence are tracked and reported - Sequence shrinking is applied to find the minimal reproducing sequence **Example:** ```solidity function optimize_maxRoundingError() external view returns (int256) { return int256(pool.totalShares()) - int256(pool.expectedShares()); } ``` **Bug fix during implementation:** Fixed a bug in shrink logic where `vm.warp` and `vm.roll` values were not correctly accumulated when replaying shrunk sequences. This caused the "best" sequence to be non-reproducible because time/block values depended on removed calls. The fix: - Keeps reverted calls in sequence during optimization to preserve warp/roll - Accumulates warps/rolls from removed calls into kept calls during shrinking - Updates inspector's cheatcodes.block alongside executor.env Closes #12190 Amp-Thread-ID: https://ampcode.com/threads/T-019bea21-149c-728c-9556-850778b70ea3 Co-authored-by: Amp * refactor: isolate optimization shrinking logic from check mode - Keep shrink_sequence and check_sequence unchanged for regular invariant checks - Add shrink_sequence_value and check_sequence_value for optimization mode - Optimization mode handles warp/roll accumulation from removed calls - replay.rs dispatches to correct shrinking function based on target_value * refactor: cleanup shrink optimization code, extract helpers --------- Co-authored-by: Amp * fix: only classify setUp as test setup if it has no parameters (#13204) Contracts with `setUp(bytes memory)` (common in Gnosis Safe/Zodiac modules) were incorrectly classified as dev/test contracts and excluded from `forge build --sizes` output. Now `setUp` is only classified as a test `Setup` function when it has no parameters, matching Forge's actual test setup behavior. Fixes #11126 * chore: fix clippy lints (#13207) * feat(cast): add --flatten flag to cast interface (#13201) * feat(cast): add --all-in-one flag to cast interface Adds a new `--all-in-one` flag to `cast interface` that inlines inherited/library struct types directly into the generated interface. This addresses the issue where `cast interface` generates a separate `library` block for struct types that originate from inherited interfaces, making the generated interface less usable for some workflows. With `--all-in-one`, all types are consolidated into a single interface: ```solidity // Before (default): library IBase { struct TestStruct { address asset; } } interface Contract { function test(IBase.TestStruct memory) external; } // After (with --all-in-one): interface Contract { struct TestStruct { address asset; } function test(TestStruct memory) external; } ``` Uses alloy-json-abi's `ToSolConfig::one_contract(true)` option introduced in alloy-core 0.8.24. Closes #9960 * chore: cargo fmt * refactor: rename --all-in-one to --flatten per review * chore: fix rustfmt * Update flake.lock (#13212) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/edd5602' (2026-01-17) → 'github:nix-community/fenix/93523fa' (2026-01-24) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/adbff8b' (2026-01-15) → 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/be5afa0' (2026-01-16) → 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) Co-authored-by: github-actions[bot] * chore(deps): weekly `cargo update` (#13213) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 62 packages to latest compatible versions Updating alloy-chains v0.2.29 -> v0.2.30 Updating alloy-consensus v1.4.3 -> v1.5.2 Updating alloy-consensus-any v1.4.3 -> v1.5.2 Updating alloy-contract v1.4.3 -> v1.5.2 Updating alloy-eip5792 v1.4.3 -> v1.5.2 Updating alloy-eip7928 v0.3.0 -> v0.3.2 Updating alloy-eips v1.4.3 -> v1.5.2 Updating alloy-ens v1.4.3 -> v1.5.2 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-genesis v1.4.3 -> v1.5.2 Updating alloy-json-rpc v1.4.3 -> v1.5.2 Updating alloy-network v1.4.3 -> v1.5.2 Updating alloy-network-primitives v1.4.3 -> v1.5.2 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-provider v1.4.3 -> v1.5.2 Updating alloy-pubsub v1.4.3 -> v1.5.2 Updating alloy-rpc-client v1.4.3 -> v1.5.2 Updating alloy-rpc-types v1.4.3 -> v1.5.2 Updating alloy-rpc-types-anvil v1.4.3 -> v1.5.2 Updating alloy-rpc-types-any v1.4.3 -> v1.5.2 Updating alloy-rpc-types-beacon v1.4.3 -> v1.5.2 Updating alloy-rpc-types-debug v1.4.3 -> v1.5.2 Updating alloy-rpc-types-engine v1.4.3 -> v1.5.2 Updating alloy-rpc-types-eth v1.4.3 -> v1.5.2 Updating alloy-rpc-types-trace v1.4.3 -> v1.5.2 Updating alloy-rpc-types-txpool v1.4.3 -> v1.5.2 Updating alloy-serde v1.4.3 -> v1.5.2 Updating alloy-signer v1.4.3 -> v1.5.2 Updating alloy-signer-aws v1.4.3 -> v1.5.2 Updating alloy-signer-gcp v1.4.3 -> v1.5.2 Updating alloy-signer-ledger v1.4.3 -> v1.5.2 Updating alloy-signer-local v1.4.3 -> v1.5.2 Updating alloy-signer-trezor v1.4.3 -> v1.5.2 Updating alloy-signer-turnkey v1.4.3 -> v1.5.2 Updating alloy-transport v1.4.3 -> v1.5.2 Updating alloy-transport-http v1.4.3 -> v1.5.2 Updating alloy-transport-ipc v1.4.3 -> v1.5.2 Updating alloy-transport-ws v1.4.3 -> v1.5.2 Updating alloy-tx-macros v1.4.3 -> v1.5.2 Updating aws-lc-rs v1.15.3 -> v1.15.4 Updating aws-lc-sys v0.36.0 -> v0.37.0 Updating cc v1.2.53 -> v1.2.54 Updating clearscreen v4.0.2 -> v4.0.3 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating libm v0.2.15 -> v0.2.16 Unchanged matchit v0.8.4 (available: v0.8.6) Removing nix v0.29.0 Updating num-conv v0.1.0 -> v0.2.0 Updating opener v0.8.3 -> v0.8.4 Updating openssl-probe v0.2.0 -> v0.2.1 Updating proc-macro2 v1.0.105 -> v1.0.106 Updating process-wrap v8.2.1 -> v9.0.1 Updating quote v1.0.43 -> v1.0.44 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating socket2 v0.6.1 -> v0.6.2 Updating time v0.3.45 -> v0.3.46 Updating time-core v0.1.7 -> v0.1.8 Updating time-macros v0.2.25 -> v0.2.26 Updating uuid v1.19.0 -> v1.20.0 Updating watchexec-signals v5.0.0 -> v5.0.1 Updating watchexec-supervisor v5.0.1 -> v5.0.2 Updating web_atoms v0.2.1 -> v0.2.3 Updating windows v0.61.3 -> v0.62.2 Updating windows-collections v0.2.0 -> v0.3.2 Removing windows-core v0.61.2 Updating windows-future v0.2.1 -> v0.3.2 Removing windows-link v0.1.3 Updating windows-numerics v0.2.0 -> v0.3.1 Removing windows-result v0.3.4 Removing windows-strings v0.4.2 Updating windows-threading v0.1.0 -> v0.2.1 Updating zmij v1.0.15 -> v1.0.16 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * chore(anvil): clean up remaining dead code after Odyssey sunset (#13211) * fix(coverage): correct BRDA hit values for LCOV consistency (#13151) * fix(coverage): correct BRDA hit values for LCOV consistency Per the LCOV tracefile format specification, BRDA hit values should be: - "-" when the expression was never evaluated (line not executed) - "0" when the branch exists and was evaluated but never taken - "N" when the branch was taken N times Previously, we were outputting "-" for all branches with 0 hits, which caused genhtml to fail with "inconsistent" errors when a line was hit (DA shows hits > 0) but branches on that line showed "-". This fix tracks line hits in a first pass, then uses that information to determine whether to output "-" (line never hit) or "0" (line hit but branch not taken) for branches with 0 hits. Fixes foundry-rs/foundry#11548 * fix: clippy explicit_iter_loop warning * test(coverage): add brda_lcov_consistency test for BRDA hit values Verifies that BRDA outputs follow LCOV spec: - "0" when line was executed but branch not taken - "-" when line was never executed This catches the inconsistency that caused genhtml to fail. --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * feat(forge): add browser wallet support for `forge script` (#12952) * feat(script): add support for browser wallet * fix: browser wallet opts defaults * chore: bump foundry-browser-wallet v0.1.0 * ci: use shared cache mounts for parallel builds (#13231) perf(docker): use shared cache mounts for parallel builds Change cache mount sharing mode from `locked` to `shared` for cargo registry, git, and sccache directories. With `sharing=locked`, parallel builds must wait for exclusive access to each cache, causing builds to queue up even when compilation itself is fast. Both cargo and sccache handle concurrent access correctly, so `shared` is safe and allows parallel builds to proceed without blocking. Amp-Thread-ID: https://ampcode.com/threads/T-019bfc1d-3cee-70ca-9caa-01e33acdff46 Co-authored-by: Amp * fix(anvil): preserve withdrawals in SerializableBlock for state dump/load (#13227) * fix(anvil): preserve withdrawals in SerializableBlock for state dump/load * add: test * fix ci * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.1 to 3.15.2 (#13236) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.1 to 3.15.2. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/1d699fc25db3f9e079cd2f168ca007a4183389be...89ab342bd48ff7318caf8d39d6a330c7b1df8f2f) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.66.2 to 2.67.13 (#13235) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.66.2 to 2.67.13. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.66.2...710817a1645ef40daad5bcde7431ceccf6cc3528) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.13 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 (#13234) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/98357b18bf14b5342f975ff684046ec3b2a07725...c0f553fe549906ede9cf27b5156039d195d2ece0) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.42.1 to 1.42.2 (#13233) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.1 to 1.42.2. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/65120634e79d8374d1aa2f27e54baa0c364fff5a...a1d64977b4aa1709d6328d518aa753f4899352d8) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.42.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(docker): expose VERGEN_GIT_SHA to build environment (#13237) * docs(coverage): link to stack-too-deep guide in warnings (#13240) Update coverage warnings to point to the new Foundry Book guide instead of the GitHub issue, providing users with actionable techniques to resolve the error. Amp-Thread-ID: https://ampcode.com/threads/T-019bff7a-8502-72b7-af76-794f258e8c67 Co-authored-by: Amp * Polkadot/Kusama/PolkadotTestnet for RPC gas estimation (#12537) * adds polkadot testnet * adds polkadot testnet * bump alloy chains * fmt * Remove kluster * update rpc url * update rpc url * fmt * adds polkadot kusama * bump alloy chains * perf(anvil): avoid redundant block queries in ots_getBlockTransactions (#13243) * fix(anvil): use consistent chain_id fallback for blob params (#13241) * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * feat(primitives): introduce `NetworkWallet` impl for `EthereumWallet` (#13248) - Seamless support for eth/op/tempo txs - This aims to replace `WalletSigner`'s impl once `FoundryNetwork` will be used everywhere * refactor(common): make `ProviderBuilder` generic over `Network` (#13250) * refactor(common): make `ProviderBuilder` generic over `Network` - Updated `ProviderBuilder` helper to be generic, which will facilate `FoundryNetwork` rollout - Adjusted the instantiation of `ProviderBuilder` in various locations to use `AnyNetwork`. - Enhanced the `build` and `build_with_wallet` methods to accommodate the new generic structure. * fix: relax trait bound on `N::TransactionRequest` * fix: comment * fix: comment * fix(anvil): return error instead of empty vec for out-of-range log queries (#13251) * fix(config): respect user-configured etherscan URL over chain defaults (#13239) fix(config): Respect user-configured etherscan URL over chain defaults (#13238) * fix(config): respect user-configured etherscan URL over chain defaults * test(config): add tests for custom etherscan URL handling Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` (#13218) * fix(primitives): track both 4844/7594 sidecars presence in `FoundryTransactionRequest::build_typed_tx` * fix: after `TransactionBuilder4844` impl * feat(common): introduce generic `ProviderBuilder::from_config` method (#13268) - keep the existing helpers that erase generic to avoid breaking change * fix(cast): remove redundant chain() call in explorer_client (#13272) Co-authored-by: tefyosL-sol * fix(verify): respect user-configured etherscan URL over chain defaults (#13275) Co-authored-by: tefyosL-sol * Update flake.lock (#13279) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/93523fa' (2026-01-24) → 'github:nix-community/fenix/b2344f3' (2026-01-31) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/39018ac' (2026-01-23) → 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ab9fbbc' (2026-01-24) → 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) Co-authored-by: github-actions[bot] * fix(invariant): remove unused cloned calldata (#12893) * fix(invariant): prune calldata to bound memory usage in long runs * style: fix formatting in invariant executor * chore: remove vyper files from testdata to fix CI * chore: trigger ci update * fix(invariant): remove unused FuzzCase.calldata field to prevent OOM The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(debugger): display actual gas usage alongside refund counter (#13271) * feat(debugger): display actual gas usage alongside refund counter * fix(forge-test): fix flamegraph gas inaccuracy issues * reverse `--decode-internal` not default with `--flamechart` * make gas unsigned as `inferno` doesn't support anyway * replace revm-inspectors patch * fix clippy * update tests * update tests * fmt * fix(config): handle decimal string in U256 deserialization (#13284) * fix: adjust numerical cells in gas report to be right aligned (#12883) * Adjust Cells to be Right Aligned in Gas Report * fix test and fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): weekly `cargo update` (#13280) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 35 packages to latest compatible versions Updating alloy-dyn-abi v1.5.2 -> v1.5.4 Unchanged alloy-evm v0.26.3 (available: v0.27.0) Updating alloy-json-abi v1.5.2 -> v1.5.4 Unchanged alloy-op-evm v0.26.3 (available: v0.27.0) Updating alloy-primitives v1.5.2 -> v1.5.4 Updating alloy-sol-macro v1.5.2 -> v1.5.4 Updating alloy-sol-macro-expander v1.5.2 -> v1.5.4 Updating alloy-sol-macro-input v1.5.2 -> v1.5.4 Updating alloy-sol-type-parser v1.5.2 -> v1.5.4 Updating alloy-sol-types v1.5.2 -> v1.5.4 Updating annotate-snippets v0.12.10 -> v0.12.11 Updating aws-smithy-async v1.2.7 -> v1.2.10 Updating aws-smithy-http-client v1.1.5 -> v1.1.8 Updating aws-smithy-observability v0.2.0 -> v0.2.3 Updating aws-smithy-query v0.60.9 -> v0.60.12 Updating aws-smithy-runtime-api v1.10.0 -> v1.11.2 Updating aws-smithy-types v1.3.6 -> v1.4.2 Updating bytemuck v1.24.0 -> v1.25.0 Updating cc v1.2.54 -> v1.2.55 Updating clap v4.5.54 -> v4.5.56 Updating clap_builder v4.5.54 -> v4.5.56 Updating clap_derive v4.5.49 -> v4.5.55 Updating cliclack v0.3.7 -> v0.3.8 Updating find-msvc-tools v0.1.8 -> v0.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating iana-time-zone v0.1.64 -> v0.1.65 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating keccak-asm v0.1.4 -> v0.1.5 Unchanged matchit v0.8.4 (available: v0.8.6) Updating notify-types v2.0.0 -> v2.1.0 Updating portable-atomic v1.13.0 -> v1.13.1 Updating portable-atomic-util v0.2.4 -> v0.2.5 Unchanged rand v0.8.5 (available: v0.9.2) Unchanged reqwest v0.12.28 (available: v0.13.1) Updating revm-inspectors v0.34.0 -> v0.34.2 Updating sha3-asm v0.1.4 -> v0.1.5 Updating siphasher v1.0.1 -> v1.0.2 Updating slab v0.4.11 -> v0.4.12 Updating syn-solidity v1.5.2 -> v1.5.4 Removing tiny-keccak v2.0.2 Updating zerocopy v0.8.33 -> v0.8.37 Updating zerocopy-derive v0.8.33 -> v0.8.37 Updating zmij v1.0.16 -> v1.0.18 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * chore: update RPC URLs from ithaca.xyz to reth.rs (#13261) * chore: update RPC URLs from ithaca.xyz to reth.rs Co-authored-by: Tim Beiko Amp-Thread-ID: https://ampcode.com/threads/T-019c0a51-3f0a-76eb-ba4a-bfb6a697d9ba Co-authored-by: Amp * fix * fmt * Update rpc.rs * Update rpc.rs * test: update test --------- Co-authored-by: Tim Beiko Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg Co-authored-by: onbjerg * ci(bench): use depot runner for benchmarks (#13288) * feat(cli): cli markdown docs (#13291) * feat: add foundry-cli-markdown crate Add a new crate for generating Markdown documentation from clap CLIs. This is a fork of clap-markdown with the following enhancements: - Support for grouped options by help heading (PR #48) - Show environment variable names for arguments (PR #50) - Add version information to generated Markdown (PR #52) * feat(cli): add hidden --markdown-help flag Add a hidden --markdown-help flag to forge, cast, anvil, and chisel that prints CLI reference documentation as Markdown and exits. This uses the new foundry-cli-markdown crate to generate the output. * chore(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#13298) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.13 to 2.67.18 (#13297) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.13 to 2.67.18. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/710817a1645ef40daad5bcde7431ceccf6cc3528...650c5ca14212efbbf3e580844b04bdccf68dac31) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 (#13299) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 727cc5b0b19bc265bd5ef28fc66bccb284473b5d to 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/727cc5b0b19bc265bd5ef28fc66bccb284473b5d...5adeaaaf36f64df54f62adb34aa5fbfdb0109d34) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump mikepenz/release-changelog-builder-action from 6.0.1 to 6.1.0 (#13300) chore(deps): bump mikepenz/release-changelog-builder-action Bumps [mikepenz/release-changelog-builder-action](https://github.com/mikepenz/release-changelog-builder-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/mikepenz/release-changelog-builder-action/releases) - [Commits](https://github.com/mikepenz/release-changelog-builder-action/compare/439f79b5b5428107c7688c1d2b0e8bacc9b8792c...6faf020194b7c8853f9e55c4fd92e40b02122a04) --- updated-dependencies: - dependency-name: mikepenz/release-changelog-builder-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: fix typos CI (#13303) - Add consts to typos ignore list (valid Rust std::env::consts) - Fix LintCotext -> LintContext typos in linter docs * chore(deps): bump crate-ci/typos from 1.42.2 to 1.43.0 (#13296) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.43.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/a1d64977b4aa1709d6328d518aa753f4899352d8...93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * sec: bump to `bytes` `^1.11.1` for `RUSTSEC-2026-0007` (#13306) bump to 1.11.1 for patch: https://rustsec.org/advisories/RUSTSEC-2026-0007 * chore(anvil,cast): remove unnecessary `populate_blob_hashes()` (#13308) Both `set_blob_sidecar`/`set_blob_sidecar_7594` implement a call to `populate_blob_hashes()` * feat(forge): generate random fuzz seed if none provided (#13309) * feat(forge): generate random fuzz seed if none provided Generate a random seed for fuzz/invariant tests when no seed is explicitly configured. This ensures reproducibility by always having a seed available that can be passed via --fuzz-seed to reproduce test runs. * feat(forge): print fuzz seed on fuzz failure (#13310) * feat(forge): print fuzz seed on test failure When a fuzz or invariant test fails, print the seed used so users can reproduce the failure with --fuzz-seed. * test: update snapshots for fuzz seed output Add [SEED] redaction pattern to match 'Fuzz seed: 0x...' output. Update all test snapshots that have fuzz/invariant failures to include the new seed line. * fix: use [SEED] placeholder in issue_3055 test snapshot Amp-Thread-ID: https://ampcode.com/threads/T-019c26cb-9d21-74f9-9e49-7ea59885e827 Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp --------- Co-authored-by: Georgios Konstantopoulos Co-authored-by: Amp * feat(anvil): cache block timestamp in mined receipts (#13311) * fix: use shared `display_chain` helper in CLI error handler (#13314) * Update handler.rs * Update handler.rs * Add --enable-tx-gas-limit CLI flag for EIP-7825 support (#13307) add --enable-tx-gas-limit * fix(anvil): use consistent chain_id fallback in fork setup (#13276) Co-authored-by: tefyosL-sol * fix(test-utils): skip build artifacts when copying to temp workspace (#13266) * feat(lint): add common uppercase abbreviations to mixedCase exceptions (#13305) Co-authored-by: onbjerg * fix(fmt): correct indentation for closing brace in empty contracts with comments (#13319) Co-authored-by: onbjerg * feat(anvil): add `trace_replayBlockTransactions` endpoint for block txs tracing (#13098) Co-authored-by: onbjerg * fix(eip712): write diagnostics to stderr instead of stdout (#13293) Co-authored-by: onbjerg * foundryup: tempo now distributes all binaries (#13337) Amp-Thread-ID: https://ampcode.com/threads/T-019c2ea2-963a-744a-8b1d-57709bc295be Co-authored-by: Amp * fix(config): handle vyper section with skip_serializing_if fields (#13318) * fix(config): handle vyper section with skip_serializing_if fields The vyper config section uses skip_serializing_if = Option::is_none on all fields, causing the default serialization to produce an empty dict. This led to all vyper keys being flagged as unknown. Add explicit VYPER_KEYS constant and special-case the vyper section in collect_standalone_section_warnings to use these known keys instead of deriving them from the (empty) default serialization. Fixes #13316 * test(config): add regression tests for vyper config warnings Tests for #13316: - no_false_warnings_for_vyper_config_keys: valid vyper keys in standalone section - no_false_warnings_for_nested_vyper_config_keys: valid vyper keys in profile - warns_on_unknown_vyper_keys: unknown vyper keys should still warn Amp-Thread-ID: https://ampcode.com/threads/T-019c28b9-9c8c-76bf-96a5-ff5a504c0507 Co-authored-by: Amp * fix build issues * fix(config): handle nested vyper section with skip_serializing_if fields The VyperConfig struct uses skip_serializing_if on all Option fields, causing the default serialization to produce an empty dict. This caused false warnings for valid vyper keys like optimize, path, and experimental_codegen when used in profile nested sections like [profile.default.vyper]. Uses the existing VYPER_KEYS constant for nested vyper sections, matching how standalone [vyper] sections are already handled. Fixes #13316 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c297e-a282-7188-8f79-5080d3e451a9 --------- Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: broken config test, currently blocking CI (#13340) fix broken config * skip checksum hash in create2 mining when case-insensitive (#13331) * kip checksum hash in create2 mining * fix the clippy * fix: avoid setting FOUNDRY_PROFILE: ci in template workflows, profile does not exist (#13339) avoid encoding FOUNDRY_PROFILE: ci, profile does not exist * perf(evm): wrap Executor.backend in Arc for copy-on-write cloning (#13327) * perf(evm): wrap Executor.backend in Arc for copy-on-write cloning During parallel fuzzing, each worker clones the Executor. Previously this deep-cloned the entire Backend (CacheDB, JournaledState, state snapshots), which could be 10-50MB per clone with 16 workers = 160-800MB wasted memory. This change wraps Backend in Arc and uses Arc::make_mut() for copy-on-write semantics. When workers only read state, they share the same backend. When a worker mutates, it gets its own copy. Expected impact: - ~80% memory reduction for parallel fuzz runs - Faster executor clone (pointer copy instead of deep clone) - No behavioral change: mutations still get isolated copies Amp-Thread-ID: https://ampcode.com/threads/T-019c2af1-f00b-723a-a3c3-25cbd6f3e92b Co-authored-by: Amp * test: update config test expectations for new mixed_case_exceptions Fix test expectations after 1bd687f0d added new values (ID, URL, API, JSON, XML, HTML, HTTP, HTTPS) to lint.mixed_case_exceptions defaults. Amp-Thread-ID: https://ampcode.com/threads/T-019c2af1-f00b-723a-a3c3-25cbd6f3e92b Co-authored-by: Amp * Update config.rs * Update config.rs * fix: restore "URI" in config test JSON expectations Amp-Thread-ID: https://ampcode.com/threads/T-019c2f68-f9df-76bc-ba4c-94fbe1789c9c Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * chore(ci): update time crate (#13348) * test(cast): ignore flaky_run_celo_with_precompiles (Celo RPC no longer supports debug_traceTransaction) (#13347) * fix(test-utils): create destination directory in copy_dir_filtered (#13350) * chore(wallets): Remove `NetworkWallet` impl for `WalletSigner` (#13343) - superseeded by `EthereumWallet`'s one, which is integrated in `TransactionBuilder` flow * fix(anvil): return error when querying future block number in with_database_at (#13267) * return error when querying future block number * fix test --------- Co-authored-by: onbjerg Co-authored-by: Matthias Seitz * chore: remove stale `tiny-keccak` references (#13358) * chore: remove stale tiny-keccak profile override * chore: remove stale tiny-keccak deny exception * chore(script): typo (#13353) * perf(cheatcodes): loop invariant code motion by hand (#13357) * chore(anvil): remove unnecessary clone operations (#13330) * perf(linking): replace double hash mpa lookup contains_key + [] with single get (#13361) * fix(verify): correct Sourcify API URL construction for custom chains (#13360) Update verify.rs * chore(common): remove dead `with_spinner_reporter` function (#13366) * resolve absolute and relative paths on Windows (#13364) * fix: unittest failed (#13371) * perf(anvil): reuse storage root from prove_storage instead of recompu… (#13363) perf(anvil): reuse storage root from prove_storage instead of recomputing * chore(deps): weekly `cargo update` (#13384) Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 94 packages to latest compatible versions Updating alloy-consensus v1.5.2 -> v1.6.1 Updating alloy-consensus-any v1.5.2 -> v1.6.1 Updating alloy-contract v1.5.2 -> v1.6.1 Updating alloy-eip5792 v1.5.2 -> v1.6.1 Updating alloy-eips v1.5.2 -> v1.6.1 Updating alloy-ens v1.5.2 -> v1.6.1 Updating alloy-evm v0.26.3 -> v0.26.4 (available: v0.27.2) Updating alloy-genesis v1.5.2 -> v1.6.1 Updating alloy-json-rpc v1.5.2 -> v1.6.1 Updating alloy-network v1.5.2 -> v1.6.1 Updating alloy-network-primitives v1.5.2 -> v1.6.1 Updating alloy-op-evm v0.26.3 -> v0.26.4 (available: v0.27.2) Updating alloy-provider v1.5.2 -> v1.6.1 Updating alloy-pubsub v1.5.2 -> v1.6.1 Updating alloy-rlp v0.3.12 -> v0.3.13 Updating alloy-rlp-derive v0.3.12 -> v0.3.13 Updating alloy-rpc-client v1.5.2 -> v1.6.1 Updating alloy-rpc-types v1.5.2 -> v1.6.1 Updating alloy-rpc-types-anvil v1.5.2 -> v1.6.1 Updating alloy-rpc-types-any v1.5.2 -> v1.6.1 Updating alloy-rpc-types-beacon v1.5.2 -> v1.6.1 Updating alloy-rpc-types-debug v1.5.2 -> v1.6.1 Updating alloy-rpc-types-engine v1.5.2 -> v1.6.1 Updating alloy-rpc-types-eth v1.5.2 -> v1.6.1 Updating alloy-rpc-types-trace v1.5.2 -> v1.6.1 Updating alloy-rpc-types-txpool v1.5.2 -> v1.6.1 Updating alloy-serde v1.5.2 -> v1.6.1 Updating alloy-signer v1.5.2 -> v1.6.1 Updating alloy-signer-aws v1.5.2 -> v1.6.1 Updating alloy-signer-gcp v1.5.2 -> v1.6.1 Updating alloy-signer-ledger v1.5.2 -> v1.6.1 Updating alloy-signer-local v1.5.2 -> v1.6.1 Updating alloy-signer-trezor v1.5.2 -> v1.6.1 Updating alloy-signer-turnkey v1.5.2 -> v1.6.1 Updating alloy-transport v1.5.2 -> v1.6.1 Updating alloy-transport-http v1.5.2 -> v1.6.1 Updating alloy-transport-ipc v1.5.2 -> v1.6.1 Updating alloy-transport-ws v1.5.2 -> v1.6.1 Updating alloy-trie v0.9.3 -> v0.9.4 Updating alloy-tx-macros v1.5.2 -> v1.6.1 Updating anyhow v1.0.100 -> v1.0.101 Updating async-compression v0.4.37 -> v0.4.39 Updating aws-config v1.8.12 -> v1.8.13 Updating aws-runtime v1.5.18 -> v1.6.0 Updating aws-sdk-kms v1.98.0 -> v1.99.0 Updating aws-sdk-sso v1.92.0 -> v1.93.0 Updating aws-sdk-ssooidc v1.94.0 -> v1.95.0 Updating aws-sdk-sts v1.96.0 -> v1.97.0 Updating aws-sigv4 v1.3.7 -> v1.3.8 Updating aws-smithy-async v1.2.10 -> v1.2.11 Updating aws-smithy-http v0.62.6 -> v0.63.3 Updating aws-smithy-http-client v1.1.8 -> v1.1.9 Updating aws-smithy-json v0.61.9 -> v0.62.3 Updating aws-smithy-observability v0.2.3 -> v0.2.4 Updating aws-smithy-query v0.60.12 -> v0.60.13 Updating aws-smithy-runtime v1.9.8 -> v1.10.0 Updating aws-smithy-runtime-api v1.11.2 -> v1.11.3 Updating aws-smithy-types v1.4.2 -> v1.4.3 Updating clap v4.5.56 -> v4.5.57 Updating clap_builder v4.5.56 -> v4.5.57 Updating flate2 v1.1.8 -> v1.1.9 Unchanged generic-array v0.14.7 (available: v0.14.9) Updating hyper-util v0.1.19 -> v0.1.20 Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating interprocess v2.2.3 -> v2.3.1 Updating jiff v0.2.18 -> v0.2.19 Updating jiff-static v0.2.18 -> v0.2.19 Unchanged matchit v0.8.4 (available: v0.8.6) Updating memchr v2.7.6 -> v2.8.0 Updating nybbles v0.4.7 -> v0.4.8 Updating pest v2.8.5 -> v2.8.6 Updating pest_derive v2.8.5 -> v2.8.6 Updating pest_generator v2.8.5 -> v2.8.6 Updating pest_meta v2.8.5 -> v2.8.6 Updating proptest v1.9.0 -> v1.10.0 Unchanged rand v0.8.5 (available: v0.9.2) Updating rapidhash v4.2.1 -> v4.2.2 Updating regex v1.12.2 -> v1.12.3 Updating regex-automata v0.4.13 -> v0.4.14 Updating regex-lite v0.1.8 -> v0.1.9 Updating regex-syntax v0.8.8 -> v0.8.9 Unchanged reqwest v0.12.28 (available: v0.13.2) Updating schemars v1.2.0 -> v1.2.1 Updating schemars_derive v1.2.0 -> v1.2.1 Updating sval v2.16.0 -> v2.17.0 Updating sval_buffer v2.16.0 -> v2.17.0 Updating sval_dynamic v2.16.0 -> v2.17.0 Updating sval_fmt v2.16.0 -> v2.17.0 Updating sval_json v2.16.0 -> v2.17.0 Updating sval_nested v2.16.0 -> v2.17.0 Updating sval_ref v2.16.0 -> v2.17.0 Updating sval_serde v2.16.0 -> v2.17.0 Updating system-configuration v0.6.1 -> v0.7.0 Updating webbrowser v1.0.6 -> v1.1.0 Updating webpki-roots v1.0.5 -> v1.0.6 Updating zerocopy v0.8.37 -> v0.8.39 Updating zerocopy-derive v0.8.37 -> v0.8.39 Updating zlib-rs v0.5.5 -> v0.6.0 Updating zmij v1.0.18 -> v1.0.19 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * Update flake.lock (#13383) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/b2344f3' (2026-01-31) → 'github:nix-community/fenix/e1b28f6' (2026-02-07) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/eb05888' (2026-01-30) → 'github:rust-lang/rust-analyzer/d2a00da' (2026-02-05) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/6308c3b' (2026-01-30) → 'github:NixOS/nixpkgs/ae67888' (2026-02-06) Co-authored-by: github-actions[bot] * perf(verify): reuse transaction from earlier RPC call instead of fetching twice (#13391) * perf(verify): reuse transaction from earlier RPC call instead of fetching twice * fix ci * fix(cast): --json support for erc20 cmds (#12727) * refactor(anvil): using is_ok since it's more robust (#13377) * fix: may div by zero (#13369) * refactor(primitives): turn `FoundryTransactionRequest` into an enum (#13278) - Combines Eth's, Op's, and Tempo's transaction requests to inherit Op/Tempo tx building * perf: avoid checksum (#13374) * docs: slim readme (#13393) * fix: correct trace message in dynamic linking preprocessor (#13394) * perf(invariant): avoid cloning state changeset in fuzz runs (#13398) Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore(deps): bump depot/build-push-action from 1.16.2 to 1.17.0 (#13405) Bumps [depot/build-push-action](https://github.com/depot/build-push-action) from 1.16.2 to 1.17.0. - [Release notes](https://github.com/depot/build-push-action/releases) - [Commits](https://github.com/depot/build-push-action/compare/9785b135c3c76c33db102e45be96a25ab55cd507...5f3b3c2e5a00f0093de47f657aeaefcedff27d18) --- updated-dependencies: - dependency-name: depot/build-push-action dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.67.18 to 2.67.27 (#13406) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.18 to 2.67.27. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/650c5ca14212efbbf3e580844b04bdccf68dac31...1e67dedb5e3c590e1c9d9272ace46ef689da250d) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.67.27 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.0 to 1.43.4 (#13407) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.0 to 1.43.4. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/93cbdb2d23269548cf0db0f74d0bc6a09a3f0d5c...78bc6fb2c0d734235d57a2d6b9de923cc325ebdd) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci: use dedicated template for isolate flaky test failures (#13409) * chore(deps): bump depot/setup-action from 1.6.0 to 1.7.1 (#13408) * fix(primitives): `FoundryTransactionRequest` conversion w/ tempo variant (#13401) - Fix `TempoTransactionRequest` variant, as inner req was always set to default. - Added unit tests to assess `FoundryTransactionRequest` proper variant routing * return error instead of empty array when filter not found (#13415) * chore(config): remove unused enum accessor methods (#13414) * fix(cast): clean up temp dir in `cast storage` when etherscan cache is unavailable (#13418) * perf(primitives): avoid cloning receipts (#13396) * fix: constructor params and args check (#13375) * fix: correct path format in get_paths doc comment (#13388) * ci: replace merge_group with push on master (#13419) * ci(release): pin action-gh-release to v2.4.2 (#13420) v2.5.0 introduced a draft→finalize flow that races in matrix jobs, causing 'Too many retries' failures in the Create release step. See: https://github.com/softprops/action-gh-release/issues/704 Amp-Thread-ID: https://ampcode.com/threads/T-019c4c6d-c55a-752a-8b27-25413f485bed Co-authored-by: Amp * fix(anvil): handle disk cache write failures in state eviction (#13332) * feat(forge,chisel): realtime `console.log` (#13321) * fix(cast): remove duplicate receipt handling in Tempo transactions (#13378) * perf(traces): deduplicate addresses before external fetching (#13320) * fix: prevent panic on etherscan client creation failure in test command (#13395) * perf(config): skip redundant remapping detection in _with_root (#13389) * fix(common): remove trailing space in `state_root` match pattern (#13426) * chore(config): `curl` mode as config key (#13260) Co-authored-by: onbjerg * fix(config): normalize deny_warnings from env vars (#13434) * fix: correct dead condition in command error formatting (#13427) * add missing JSON output support for `erc20 decimals` (#13438) * fix(anvil): variable shadowing bug in ReadyTransactions::remove_with_markers (#13436) Update transactions.rs * Update flake.lock (#13448) * chore(deps): weekly `cargo update` (#13449) * feat(evm): `ForkDatabase`/`MultiFork` generic over `Network` (#13459) * feat(evm): `ForkDatabase`/`MultiFork` generic over `Network` bump `foundry-fork-db` * fix: typo * fix(cheatcodes): fix vm.expectRevert for direct precompile calls (#13460) Precompile calls don't create an interpreter frame, so `initialize_interp` never fires and `max_depth` never gets bumped beyond the cheatcode call depth. This causes the depth check in `handle_expect_revert` to fail with "call didn't revert at a lower depth than cheatcode call depth". Track `max_depth` in the `call` hook as well, accounting for the callee depth (`curr_depth + 1`). Amp-Thread-ID: https://ampcode.com/threads/T-019c63a2-2c36-7334-ab55-2931a174b59c Co-authored-by: Amp * fix(lint): remove unreachable macro arm in declare_forge_lint (#13452) * chore(flake): use nightly rustfmt (#13441) * chore(flake): use nightly rustfmt * chore(flake): update flake * feat: add `executeTransaction` cheatcode (#13437) feat: add executeTransaction cheatcode Port the executeTransaction cheatcode from tempoxyz/tempo-foundry. Executes RLP-encoded signed transactions in an isolated EVM context with full semantics (like --isolate mode). OP deposit and Tempo AA transactions return errors for now (marked with TODOs). * fix(forge): don't reset snapshot diff result on missing file (#13442) * fix(traces): check HTTP status before JSON parsing in Sourcify fetcher (#13446) * Update external.rs * chore: fmt * test: rm useless tests --------- Co-authored-by: Oliver Nordbjerg * feat(cheatcodes): add Ed25519 crypto cheatcodes (#13450) * feat(cheatcodes): add Ed25519 crypto cheatcodes Add four new cheatcodes for Ed25519 cryptography: - createEd25519Key(bytes32 salt) - deterministic key generation - publicKeyEd25519(bytes32 privateKey) - derive public key - signEd25519(namespace, message, privateKey) - sign with domain separation - verifyEd25519(signature, namespace, message, publicKey) - verify signatures Uses ed25519-consensus crate. Includes comprehensive unit tests for determinism, namespace separation, edge cases, and invalid inputs. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c5f04-a6ed-7015-9b4d-4464a35bc26c * chore: solidity test * test: fix assertions --------- Co-authored-by: Amp Co-authored-by: Oliver Nordbjerg * feat(lint): add missing visit methods to EarlyLintVisitor (#13454) Update early.rs * notify subscribers for txs promoted after block mining (#13464) * notify subscribers for txs promoted after block mining * refactor: extract notify_ready helper to deduplicate notification logic Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp --------- Co-authored-by: Matthias Seitz Co-authored-by: Amp * chore(deps): bump taiki-e/install-action from 2.67.27 to 2.68.0 (#13465) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.67.27 to 2.68.0. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/1e67dedb5e3c590e1c9d9272ace46ef689da250d...f8d25fb8a2df08dcd3cead89780d572767b8655f) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.68.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/cache-cargo-install-action from 3.0.1 to 3.0.2 (#13466) Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases) - [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/34ce5120836e5f9f1508d8713d7fdea0e8facd6f...2bfc3cedaf2ee5e7fa5d0ae034ccd5fb50cf8e1f) --- updated-dependencies: - dependency-name: taiki-e/cache-cargo-install-action dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.4 to 1.43.5 (#13467) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.4 to 1.43.5. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/78bc6fb2c0d734235d57a2d6b9de923cc325ebdd...57b11c6b7e54c402ccd9cda953f1072ec4f78e33) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.43.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/update-flake-lock from 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 to a135ea602656a8348c5c34887131dd9f7a28bd8c (#13468) chore(deps): bump DeterminateSystems/update-flake-lock Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 5adeaaaf36f64df54f62adb34aa5fbfdb0109d34 to a135ea602656a8348c5c34887131dd9f7a28bd8c. - [Release notes](https://github.com/determinatesystems/update-flake-lock/releases) - [Commits](https://github.com/determinatesystems/update-flake-lock/compare/5adeaaaf36f64df54f62adb34aa5fbfdb0109d34...a135ea602656a8348c5c34887131dd9f7a28bd8c) --- updated-dependencies: - dependency-name: DeterminateSystems/update-flake-lock dependency-version: a135ea602656a8348c5c34887131dd9f7a28bd8c dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.4.2 to 2.5.0 (#13469) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.2 to 2.5.0. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/5be0e66d93ac7ed76da52eca8bb058f665c3a5fe...a06a81a03ee405af7f2048a818ed3f03bbf83c7b) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * test: mark clone CLI tests as flaky (#13472) These tests hit real Etherscan/Sourcify APIs and fail intermittently due to rate limiting and network issues. They will now run in the nightly flaky test workflow with retries instead of blocking every PR. Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp * fix: bind TempDir guard in clone test to prevent premature cleanup (#13471) Amp-Thread-ID: https://ampcode.com/threads/T-019c6840-d225-723a-bf92-46e4e29c7ad1 Co-authored-by: Amp * prevent balance overflow in anvil_addBalance (#13457) Co-authored-by: Matthias Seitz * chore(tests): bump forge-std version (#13482) * move `sccache --show-stats` into build RUN to show actual stats (#13483) * fix(anvil): correct blob_gas_used_ratio calculation in fee history (#13491) Update fees.rs * fix(cheatcodes): make vm.executeTransaction work in isolation mode (#13475) * fix(test): exclude ExecuteTransactionTest from isolation mode vm.executeTransaction already performs its own isolated execution (fresh EVM, cloned state, state merging). When isolation mode is enabled, the inspector's transact_inner intercepts CALLs at depth==1 inside the cheatcode's inner EVM, causing double-isolation that results in 'transaction reverted: 0x'. Amp-Thread-ID: https://ampcode.com/threads/T-019c6ad3-d3f0-70d3-8d78-38ccd8444e9e Co-authored-by: Amp * fix(cheatcodes): make vm.executeTransaction work in isolation mode Two bugs prevented vm.executeTransaction from working with --isolate: 1. Double isolation: executeTransaction creates its own inner EVM at depth=1, but the isolation inspector also intercepts CALLs at depth=1, causing a nested transact_inner. Fix: add set_in_inner_context() to CheatcodesExecutor trait and set it before/after the inner EVM run, matching how transact_inner already handles this. 2. Corrupted cfg env: executeTransaction modified env.cfg (disabled nonce checks, set initcode size limit) but never restored it. Subsequent isolated calls then failed nonce validation (NonceTooHigh). Fix: restore env.cfg from the cached copy alongside env.tx and basefee. Amp-Thread-ID: https://ampcode.com/threads/T-019c6ad3-d3f0-70d3-8d78-38ccd8444e9e Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test-utils: remove unused `IS_TTY` helper (#13492) Update util.rs * chore: update LATEST_SOLC to 0.8.34 (#13489) Amp-Thread-ID: https://ampcode.com/threads/T-019c7441-de53-7338-86cb-6d84f755016a Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(anvil): add --max-transactions CLI flag (#13495) * remove unimplemented anvil_enableTraces endpoint (#13499) * feat(fmt): pretty printing for generic block/transaction responses (#13497) feat(fmt): pretty printing for generic block/transaction reponses * Refactor `locked_read_to_string` to reuse `locked_read (#13494) Update fs.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(script-sequence): extract duplicated filter logic (#13500) * chore(broadcast): cleanup avg gas price calculation (#13509) * chore(deps): weekly `cargo update` (#13513) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * feat(common): generic `TransactionReceiptWithRevertReason` + pprinting (#13503) * refactor: Use `fs::write_pretty_json_file` in `MultiChainSequence::save` (#13510) * Update flake.lock (#13511) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/d0555da' (2026-02-14) → 'github:nix-community/fenix/6d86ae5' (2026-02-21) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/bbc84d3' (2026-02-13) → 'github:rust-lang/rust-analyzer/46a214b' (2026-02-20) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2343bbb' (2026-02-11) → 'github:NixOS/nixpkgs/d1c15b7' (2026-02-16) Co-authored-by: github-actions[bot] * fix(sol-macro-gen): correct identifier check in write_mod_name (#13508) * chore(deps): bump DeterminateSystems/update-flake-lock from a135ea602656a8348c5c34887131dd9f7a28bd8c to 5909792a83875ddb5dd4b18734534a98a74a709c (#13524) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.68.0 to 2.68.8 (#13523) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.2 to 3.16.1 (#13522) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): prevent panic in `utc_from_secs` for out-of-range timestamps (#13520) * ci(release): revert action-gh-release to v2.4.2 (#13527) * ci: ignore softprops/action-gh-release in dependabot (#13528) * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.16.1 to 3.16.3 (#13529) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.16.1 to 3.16.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/681d8e8bfdb5d7af56f113ba2425b1fb00ec9edc...73327eb48f028efaaf5013656ba216ca3cdeca7b) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.16.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(primitives): add `FoundryTransactionBuilder` trait (#13512) Co-authored-by: figtracer <1gusredo@gmail.com> * fix(wallets): use turnkey_unsupported() instead of hardcoded error (#13535) * chore(deps): bump taiki-e/install-action from 2.68.8 to 2.68.9 (#13534) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.8 to 2.68.9. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/cfdb446e391c69574ebc316dfb7d7849ec12b940...7f491e26f71f4ec2e6902c7c95c73043f209ab79) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.68.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): preserve original error in update_url (#13531) Co-authored-by: Oliver Nordbjerg * fix: swap incorrect doc comments for archive RPC URL functions in rpc. (#13480) * Allow verifier-url for unknown Etherscan chains (#13079) Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: onbjerg * fix: remove unused `no_storage_caching()` method from `NodeConfig` (#13487) * fix: remove code duplication in `get_runtime_codes` (#13502) * forge: avoid repeated selector decoding in find command (#13516) * fix(cli): Git::is_repo_root always returns false (#13505) * fix(verify): remove unused functions from `VerificationContext` (#13481) Co-authored-by: onbjerg * add missing doc section keys to config validation (#13447) * fix(anvil): use EIP-2718 encoding for OP enveloped_tx (#13537) * fix: correct flag name in error message from --compiler-version to --… (#13539) * chore(ci): unblock ci, fix clippy lint (#13543) * add lint-fix * fix: resolve nightly clippy warnings - collapsible_match: collapse plain if into match guards, allow for if-let - iter_kv_map: use .values()/.keys() instead of .iter().flat_map(|(_, v)| v) - useless_conversion: remove unnecessary .into_iter() Amp-Thread-ID: https://ampcode.com/threads/T-019c9930-51be-760a-b2c7-9a029f851fee Co-authored-by: Amp * fix: add missing match arm for Occupied entry in remappings Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp * fix: revert incorrect .values() call on Vec in runner Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp * fix: resolve irrefutable let pattern warning in MultiForkHandler Amp-Thread-ID: https://ampcode.com/threads/T-019c99a5-39f3-72be-ad16-e7d041662ea9 Co-authored-by: Amp --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Amp * refactor(wallets): browser wallet generic `Network` (#13550) * fix(anvil): use individual tx gas instead of cumulative in fee history (#13552) * fix(anvil): clear tx pool on anvil_reset (#13544) * add EIP-3860 initcode size check in txpool validation (#13473) * fix: clarify Anvil storage caching builder naming (#13546) Co-authored-by: Amp Co-authored-by: Oliver Nordbjerg * refactor(evm): remove dead BackendError::Other variant (#13553) * fix(script): actually skip Vyper contract verification (#13484) Co-authored-by: onbjerg * feat(doc): Adding new 'Constants' section for constants and immutables (#13116) Co-authored-by: onbjerg Co-authored-by: Oliver Nordbjerg * fix(evm): avoid wrong CowBackend initialization in load_allocs and clone_account (#13554) Co-authored-by: Amp Co-authored-by: Matthias Seitz * chore: use `fs::write_pretty_json_file` in `ScriptSequence::save` (#13562) * fix(forge): apply --access-list in forge create (#13557) Co-authored-by: onbjerg * Update flake.lock (#13564) Co-authored-by: github-actions[bot] Co-authored-by: onbjerg * chore(deps): weekly `cargo update` (#13565) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * fix(cast): correct max_int boundary check for uint255 (#13568) * fix(fmt): correct total_difficulty attribute name (#13578) * chore(evm): use `AnyRpcTransaction` in `configure_tx_env`/`commit_transaction` (#13572) * feat(evm): add `AsEnvMut::set_env` (#13573) * fix(fmt): don't inline while/for/if blocks with multiple statements (#13566) * refactor(cheatcodes,evm): use ContextTr read accessors for env fields (#13582) Co-authored-by: Amp * chore(evm): simplify `CowBackend` init logic by using `Option` (#13584) * feat(anvil): add EIP-2935 blockhash histo… * Potential fix for code scanning alert no. 155: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 170: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * foundry-rs#13763 (#398) * fix: update EtherlinkTestnet -> EtherlinkShadownet for alloy-chains v0.2.31 (#13763) Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> * chore(evm): make `FoundryCfg` generic over `Spec` (#13757) * chore(deps): weekly `cargo update` (#13760) Updating git repository `https://github.com/alloy-rs/alloy` Updating git repository `https://github.com/alloy-rs/evm.git` Updating git repository `https://github.com/foundry-rs/optimism` Updating git submodule `https://github.com/flashbots/op-rbuilder` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/runtimeverification/kontrol-cheatcodes` Updating git submodule `https://github.com/ethereum-optimism/lib-keccak` Updating git submodule `https://github.com/dapphub/ds-test` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable` Updating git submodule `https://github.com/OpenZeppelin/openzeppelin-contracts` Updating git submodule `https://github.com/a16z/erc4626-tests.git` Updating git submodule `https://github.com/safe-global/safe-contracts` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/transmissions11/solmate` Updating git submodule `https://github.com/ethereum-optimism/superchain-registry` Updating git submodule `https://github.com/flashbots/rollup-boost` Updating git repository `https://github.com/foundry-rs/foundry-fork-db` Updating git repository `https://github.com/paradigmxyz/revm-inspectors.git` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git submodule `https://github.com/foundry-rs/forge-std` Updating git submodule `https://github.com/vectorized/solady` Updating git submodule `https://github.com/tempoxyz/tempo-std` Updating git repository `https://github.com/stevencartavia/reth` Locking 31 packages to latest compatible versions Updating alloy-chains v0.2.30 -> v0.2.31 Updating alloy-trie v0.9.4 -> v0.9.5 Adding anstream v1.0.0 Unchanged anstream v0.6.21 (available: v1.0.0) Updating anstyle v1.0.13 -> v1.0.14 Updating anstyle-lossy v1.1.4 -> v1.1.5 Adding anstyle-parse v1.0.0 Updating bon v3.9.0 -> v3.9.1 Updating bon-macros v3.9.0 -> v3.9.1 Updating c-kzg v2.1.6 -> v2.1.7 Updating cc v1.2.56 -> v1.2.57 Updating clap v4.5.60 -> v4.6.0 Updating clap_builder v4.5.60 -> v4.6.0 Updating clap_complete v4.5.66 -> v4.6.0 Updating clap_complete_nushell v4.5.10 -> v4.6.0 Updating clap_derive v4.5.55 -> v4.6.0 Updating clap_lex v1.0.0 -> v1.1.0 Updating colorchoice v1.0.4 -> v1.0.5 Updating console v0.16.2 -> v0.16.3 Removing darling v0.21.3 Removing darling_core v0.21.3 Removing darling_macro v0.21.3 Updating derive-where v1.6.0 -> v1.6.1 Updating evmole v0.8.2 -> v0.8.4 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating kasuari v0.4.11 -> v0.4.12 Unchanged matchit v0.8.4 (available: v0.8.6) Updating once_cell v1.21.3 -> v1.21.4 Unchanged op-revm v15.0.0 (available: v17.0.0) Updating portable-atomic-util v0.2.5 -> v0.2.6 Unchanged quick-junit v0.5.2 (available: v0.6.0) Unchanged rand v0.8.5 (available: v0.10.0) Unchanged rand v0.9.2 (available: v0.10.0) Unchanged revm v34.0.0 (available: v36.0.0) Updating schannel v0.1.28 -> v0.1.29 Updating serde_with v3.17.0 -> v3.18.0 Updating serde_with_macros v3.17.0 -> v3.18.0 Unchanged snapbox v0.6.24 (available: v1.1.0) Unchanged soldeer-commands v0.10.0 (available: v0.10.1) Unchanged soldeer-core v0.10.0 (available: v0.10.1) Unchanged strum v0.27.2 (available: v0.28.0) Updating tempfile v3.26.0 -> v3.27.0 Updating tinyvec v1.10.0 -> v1.11.0 Unchanged toml v0.9.12+spec-1.1.0 (available: v1.0.6+spec-1.1.0) Unchanged toml_edit v0.24.1+spec-1.1.0 (available: v0.25.4+spec-1.1.0) Updating tracing-subscriber v0.3.22 -> v0.3.23 Updating zerocopy v0.8.41 -> v0.8.42 Updating zerocopy-derive v0.8.41 -> v0.8.42 note: to see how you depend on a package, run `cargo tree --invert @` Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * feat(cheatcodes): bubble-up `Network` generic to `Wallets` (#13768) * feat(script): generic `TxStatus` receipt type (#13770) feat(script): generic `TxStatus` * chore(script): idiomatic `BroadcastReader::into_tx_receipts` (#13771) * refactor(evm): simplify `Backend::initialize` and `CowBackend::backend_mut` (#13755) `initialize` now takes `(SpecId, Address, TxKind)` instead of `&Env`, since those are the only fields it reads. This removes the need for `backend_mut` to clone `EvmEnv` — it only needs `&TxEnv` now, because the `SpecId` already comes from `self.spec_id`. Moves towards aligning with alloy-evm's BlockExecutor ownership model where `EvmEnv` is block-scoped and `TxEnv` is tx-scoped. * feat(cheatcodes): make `Cheatcodes` context-generic (#13767) * feat(cheatcodes): make `Cheatcodes` context-generic * fix: merge conflict --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): add `--network` flag to `cast tx` for network-specific raw encoding (#13745) * feat(cast): add --network flag to `cast tx` for multi-network raw encoding Replace the `FoundryNetwork`-based `transaction_raw` workaround with proper network selection via a new `--network` / `-n` CLI flag. This moves raw tx encoding back into `Cast::transaction` dispatching to the correct provider (Ethereum, Optimism, or Tempo) based on the flag. - Add `NetworkVariant` enum (Ethereum/Optimism/Tempo) in foundry-cli opts - use it w/ `--network` through CastSubcommand::Tx and provider construction - Add test for Tempo raw tx encoding (`tx_raw_tempo`) * fix: network num args * feat(cast): `block --raw` network selection (#13754) * rebase PR 13745 * feat(cast): `block` for multi-network raw encoding Co-authored-by: figtracer <1gusredo@gmail.com> * fix: doctest --------- Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make mined_receipts generic (#13761) * chore(evm): split `Executor::env` into `evm_env` and `tx_env` fields (#13773) Split the getters/setters accordingly, and update all call sites. * refactor(cheatcodes): `CheatcodesExecutor` generic (#13774) refactor(cheatcodes): make `CheatcodesExecutor` use generic env types from `ContextTr` Replace concrete `EvmEnv`/`TxEnv` in `with_fresh_nested_evm` with `EvmEnv<::Spec, CTX::Block>` and `CTX::Tx` to support non-Eth networks. Add `FoundryContextTr` trait as fully generic counterpart to the concrete `FoundryContextExt`. Remove unused `clone_to_cfg_env`/`apply_cfg_env` from `FoundryCfg`. * fix(anvil): flaky `test_trace_filter()` (#13764) fix * chore(cast): granular bounds on `Cast` (#13776) * refactor(evm): `FoundryContextExt` generic types (#13778) * fix(cheatcodes): create file in writeJson/writeToml 3-arg overload (#13777) Closes foundry-rs/foundry#13775 Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(anvil): make EthApi generic over `N: Network` (#13751) * refactor(anvil): make EthApi generic over N: Network * rm todo comments --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): move `CheatsCtxExt` trait to `foundry-evm-core` (#13781) Previously `CheatsCtxExt` was defined in `foundry-cheatcodes`. Move it to `foundry-evm-core`, and rename it to `EthCheatCtx` to make clear it pins the context to Ethereum-specific env types (`BlockEnv`/`TxEnv`/`CfgEnv`) as a temporary alias during the transition to fully generic EVM and cheatcodes. * refactor(evm): make `NestedEvm` trait generic with associated types (#13782) Add `Tx`, `Block`, and `Spec` associated types to `NestedEvm` so each network can specify its own environment types. The trait remains object-safe when used as `dyn NestedEvm`. Update `NestedEvmClosure` to pin the associated types to Eth types, and set the concrete types in the `FoundryEvm` implementation. * refactor(anvil): propagate `EthApi` to all holders (#13783) refactor(anvil): propagate EthApi to all holders * refactor(evm): rename `NestedEvmClosure` and move to `foundry-evm-core` (#13785) refactor(evm): rename `NestedEvmClosure` to `EthNestedEvmClosure` and move to `foundry-evm-core` * refactor(evm): remove `Env` abstraction from `Executor` impl (#13790) * refactor(anvil): remove redundant param (#13792) * refactor(cheatcodes): tighten verbose bounds to `EthCheatCtx` (#13791) * refactor(evm): remove `eth_*_mut()` from `FoundryContextExt` (#13789) * feat(script): generic `TransactionWithMetadata` + generic pprinting `TransactionMaybeSigned` (#13795) * refactor(evm): `DatabaseExt` generic over env types (#13797) refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * test(cast): mark flaky revert_reason_from and wildcard RPC-dependent tail (#13796) * fix(anvil): reject invalid versioned_hashes in beacon blobs endpoint (#13787) * fix(cheatcodes): prevent panic in expectRevert with empty bytes (#13769) * fix(cheatcodes): prevent panic in expectRevert with empty bytes When vm.expectRevert(bytes('')) catches a revert with non-empty data, decode_error in alloy-dyn-abi panics on the empty expected_reason (slice index out of range). Guard the decode_error call with a length check. Closes #13766 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: add regression test for expectRevert empty bytes panic Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test: fix snapshot for expectRevert empty bytes regression test Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): add `DB` associated type to `FoundryJournalExt` (#13799) * refactor(evm): make DatabaseExt generic over environment types Add generic parameters with defaults to DatabaseExt: `DatabaseExt` This makes the trait generic over EVM environment types while remaining fully backwards-compatible — all existing code uses the defaults. Non-Ethereum networks (e.g. Tempo) can now implement DatabaseExt with their own environment types. 9 method signatures updated: snapshot_state, revert_state, create_select_fork, create_select_fork_at_transaction, select_fork, roll_fork, roll_fork_to_transaction, transact, transact_from_tx. * refactor(evm): replace dyn DatabaseExt in FoundryJournalExt with associated type Replace `&mut dyn DatabaseExt` return type in `FoundryJournalExt` with an associated type `type DB: DatabaseExt`. This removes 3 uses of `dyn DatabaseExt` while remaining backwards-compatible — `&mut DB` auto-coerces to `&mut dyn DatabaseExt` at call sites that still need it. `FoundryJournalExt` is never used as a trait object itself, so adding the associated type has no object-safety impact. --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(anvil): add `AnvilBlockExecutor` and `FoundryReceiptBuilder` (#13788) feat(anvil): add AnvilBlockExecutor and FoundryReceiptBuilder * fix(anvil): swap param order in get_next_block_blob_excess_gas to match callers (#13740) * feat(script): `Network`-generic `ScriptSequence` (#13803) * fix(config): add symmetric serialization for FuzzDictionaryConfig usize fields (#13723) * chore(evm): remove `Env::new_with_spec_id()` method (#13806) * fix(install): clean up nested submodules when using --no-git (#13779) * fix(install): clean up nested submodules when using --no-git Fixes #13688 Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b * test: mark regression test as flaky_ instead of ignored Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * style: fix fmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cf6ab-6839-70a9-98a9-289974db717b --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(evm): use associated types in `with_cloned_context` (#13802) refactor(evm): use associated types in `with_cloned_context` closure signature * refactor(evm): propagate env types through `FoundryJournalExt` (#13808) * refactor(evm): simplify `FoundryCfg` to marker trait (#13810) * feat(anvil): add `AnvilBlockExecutorFactory` (#13811) * feat(script): `Network`-generic `ScriptSequenceKind` (#13809) * feature(evm): owned `Tx`/`Evm` getters and `Evm` setter for `FoundryContextExt` (#13812) * chore(evm): remove `Env::{clone_evm_and_tx,apply_evm_and_tx}` methods (#13813) * chore(evm): rename `InspectorExt` to `EthInspectorExt` (#13815) * refactor(evm): add `Fork::backend()` accessor (#13817) * refactor(evm): remove `Env` from `commit_transaction` and `replay_until` (#13816) * feat(script): generic `BundledState` impl (#13825) * chore: bump alloy chains (#13827) * chore(evm): remove `Env` abstraction (#13826) * chore(evm): remove `Env` abstraction completely * fix: nit Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(macros): use correct index for tuple struct fields in ConsoleFmt (#13829) * chore(evm): fix stale `Env` references in doc comments (#13828) The combined `Env` wrapper struct was removed in #13826. Update remaining doc comments that still reference it to use the current `EvmEnv`/`TxEnv` names instead. * refactor(anvil): wire `AnvilBlockExecutorFactory` into `Backend::mine_block` (#13814) refactor(anvil): wire AnvilBlockExecutorFactory into Backend::mine_block * feat(script): generic `ScriptTransactionBuilder` (#13830) Remove hardcoded `Ethereum` default from `TransactionWithMetadata`, making `ScriptTransactionBuilder`, `simulate_and_fill`, and broadcast reader calls `Network`-generic. Cheatcode broadcast helpers now explicitly use Ethereum. * fix(`foundryup`): bump foundryup version (#13832) bump foundryup version * fix(foundryup): tempo-foundry now ships all binaries (#13834) nit * chore(deps): bump taiki-e/install-action from 2.68.17 to 2.68.35 (#13821) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump oven-sh/setup-bun from 2.1.2 to 2.2.0 (#13819) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump Swatinem/rust-cache from 2.8.2 to 2.9.1 (#13818) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.43.5 to 1.44.0 (#13820) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): backend env helpers generic (#13836) Update `update_env_block` and `update_current_env_with_fork_env` to use generic `FoundryBlock`/`FoundryTransaction` bounds and `BlockHeader` trait methods via setters, replacing direct field access on `AnyRpcBlock`. * refactor(anvil): rm `TransactionExecutor`, mv revm callers to `AnvilBlockExecutorFactory` (#13835) refactor(anvil): rm , mv remai callers AnvilBlockExecutorFactory * refactor(script): extract `BrowserSigner` from `MultiWallet` (#13839) * refactor(anvil): mv `Backend` methods to generic impl, thread N through NodeConfig/spawn (#13840) * refactor(anvil): extract `block_env_from_header` utility (#13838) * chore(evm): clean-up `FoundryEvm` impl (#13844) * feat(cheatcodes): add `currentFilePath` cheatcode (#13735) * feat(cheatcodes): add `currentFilePath` cheatcode Add `vm.currentFilePath()` that returns the source file path of the currently running test or script contract, relative to the project root. This enables contracts to locate sibling files (calldata JSONs, markdown descriptions, configs) without hardcoding paths. A common pattern in proposal/script repos is overriding a `dirPath()` function with a hardcoded string — this cheatcode eliminates that boilerplate. Implementation leverages `CheatsConfig::running_artifact` which already tracks the entry-point `ArtifactId`. The `source` field is stripped of the project root prefix to return a consistent relative path. * fix: rustfmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfb9f-8fce-717d-b9de-fedd8ee7d555 Co-authored-by: Amp * fix: remove view from test functions, fix forge-fmt Amp-Thread-ID: https://ampcode.com/threads/T-019cfba7-4986-77c6-9630-574261e9d580 Co-authored-by: Amp --------- Co-authored-by: Alex Netto Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify nested Evm handling (#13846) refactor(evm): simplify nested EVM handling - Replace `to_env()` (returning cfg+block+tx tuple) with `to_evm_env()` returning only `EvmEnv` (cfg+block), since tx is not needed by callers - Remove unused `with_cloned_context` generic type parameter `R` - Drop `set_tx` call in `with_cloned_context` since nested tx modifications should not propagate back to the outer context - Remove `Evm` useless `db_mut`, `precompiles`, `precompiles_mut`, `inspector`, `inspector_mut` provideds methods reimplementations * refactor(cheatcodes): extract fork env helper to reduce duplication (#13848) Extract `fork_env_op` helper that handles the common pattern shared by all fork-switching cheatcodes: clone EVM/tx env → run db operation → write env back. Deduplicates 7 call sites (rollFork × 4, selectFork, createSelectFork × 2). * refactor(cheatcodes): `BroadcastableTransaction` network-agnostic (#13849) refactor(cheatcodes): make BroadcastableTransaction network-agnostic Remove the `Network` type parameter from `BroadcastableTransaction` by storing raw EVM data (from, to, value, input, nonce, gas) as primitive fields instead of wrapping `TransactionMaybeSigned`. This prevents the `Network` generic from leaking through `Cheatcodes`, `RawCallResult`, and `ScriptResult`. The conversion to network-specific types (`TransactionMaybeSigned`) now happens in the script layer (`simulate.rs`) at the point where transactions are handed off to `ScriptTransactionBuilder`. * feat(evm): generic `NestedEvmClosure` (#13850) refactor(evm): generalize NestedEvmClosure with generic type params Replace `EthNestedEvmClosure` (pinned to Eth types) with generic `NestedEvmClosure` to support non-Eth networks. * feat(evm): `FoundryContextExt` generic impl (#13857) * feat(evm): wire Inspector and DatabaseExt Context generics (#13856) * refactor(anvil): make `PendingTransaction` generic over tx type (#13854) * chore(cheatcodes): remove `Cheatcodes` context generic (#13861) * refactor(evm): delegate to alloy's `EthEvmFactory` in `new_evm_with_inspector` (#13860) * chore: add `id` attributes to issue templates (#13864) * chore: add `id` attributes to issue templates Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 * chore: add `id` attributes to required issue template fields only Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0b22-cf39-75b7-b3d7-9280780eecd5 --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` (#13863) * refactor(evm): merge `FoundryJournalExt` into `FoundryContextExt` * fix: failed merge * fix(traces): fix verbosity trace mode and unify verbosity handling (#13859) * fix(traces): use max instead of min for verbosity trace mode Add regression tests for TraceMode::with_verbosity to ensure verbosity 5 raises the trace mode to at least Steps rather than lowering it. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-Authored-By: James Niken <155266991+dizer-ti@users.noreply.github.com> * fix(traces): unify verbosity handling in TraceMode with_verbosity now raises to RecordStateDiff at verbosity 5, matching the documented behavior of -vvvvv (storage changes + backtraces). This removes the separate with_state_changes(verbosity() > 4) call in trace_mode() which used the global shell verbosity instead of the local evm_opts.verbosity — these two values can diverge when --gas-report or --flamegraph bumps evm_opts.verbosity to 3. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(forge): only show backtraces at verbosity 5 Backtraces require opcode-level step recording which is expensive. Gate backtrace display at verbosity >= 5 (-vvvvv) instead of >= 3, matching the documented behavior and the step recording threshold. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): comprehensive unit tests for TraceMode verbosity levels Tests every verbosity level (0-5) × every TraceMode variant: - verbosity 0-2: noop across all modes - verbosity 3-4: raises to Call, never downgrades - verbosity 5: raises to RecordStateDiff, never downgrades - into_config correctness at each level (record_steps, record_state_diff) - monotonicity invariant: with_verbosity never lowers any mode Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): add integration test for backtrace verbosity levels Runs the same failing test at verbosity 1, 3, 4, and 5 with snapshot assertions to verify backtraces only appear at -vvvvv. Removes the monotonicity unit test in favor of concrete integration coverage. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): keep backtraces at verbosity >= 3, add source locations at 5 Backtraces are useful even without source locations — they show contract/function names at -vvv/-vvvv. Source file locations are only added at -vvvvv when step recording is enabled. Updates integration test to assert all three behaviors: - -vvv: backtrace with function names only - -vvvv: same, plus setup traces - -vvvvv: backtrace with source file:line:col locations Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: fix rustfmt Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): handle compiler warnings in snapshot Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): add [staticcall] to snapshot for pure function Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * perf(traces): RecordStateDiff should not enable debug-level recording RecordStateDiff now behaves as Steps + state diff, not Debug + state diff. This avoids recording full stack snapshots (memcpy per opcode), memory snapshots, and unfiltered opcode recording at -vvvvv. Before: 50k opcodes → 50k step records with full stack copies (~16MB) After: 50k opcodes → ~500 step records (JUMP/JUMPDEST only), no copies Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * test(traces): assert Debug mode config is unchanged Locks in that Debug mode still enables full memory/stack snapshots, returndata, immediate bytes, and unfiltered opcode recording — ensuring the RecordStateDiff optimization doesn't affect the debugger or cheatcode recording paths. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(traces): state diff needs unfiltered opcodes for SLOAD/SSTORE record_state_diff captures storage changes in the step() callback, which only fires for recorded opcodes. With a JUMP/JUMPDEST filter, SSTORE steps are skipped and state diffs are lost. RecordStateDiff now disables the opcode filter (like before) but still skips memory/stack snapshots — saving the expensive per-opcode memcpy. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(test): update JSON fixture for RecordStateDiff config Stack and memory snapshots are no longer recorded at -vvvvv since RecordStateDiff now uses Steps-level config. These fields are null in the JSON output instead of populated. Full debugger-level data is still available via --debug. Co-Authored-By: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> * refactor(anvil): make Block generic over tx type (#13865) * refactor(evm): `FoundryContextExt` bound, use generic `Spec` in `EthCheatCtx` (#13866) * refactor(evm): use `TxEnv` directly in `DatabaseExt` instead of `TransactionRequest` (#13867) * chore(deps): weekly `cargo update` (#13878) Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> * Update flake.lock (#13877) Co-authored-by: github-actions[bot] * fix(deps): bump to Foundry browser wallet version 0.2.0 (#13890) bump to https://github.com/foundry-rs/foundry-browser-wallet/releases/tag/v0.2.0 * revert: "BroadcastableTransaction network-agnostic" (#13849) (#13891) * perf(anvil): remove redundant clone in create_access_list (#13887) * perf(evm): make `NestedEvm::to_evm_env` consuming to avoid useless clone (#13893) Change `to_evm_env(&self)` to `to_evm_env(self)` so the implementation can use `Evm::finish()` to destructure the EVM without an extra clone of `cfg_env` and `block_env`. Update `with_fresh_nested_evm` to return `EvmEnv` after consuming the EVM post-closure, so callers no longer need to capture the env inside a `NestedEvmClosure` (which only has `&mut dyn NestedEvm` and cannot call a consuming method). Fix the `sub_inner` / `sub_evm_env` ordering in `with_nested_evm` impls so the journal is cloned before `to_evm_env` consumes the EVM. * chore(common): consistency fix on `TransactionMaybeSigned::to()` (#13895) * feat(cheatcodes): Network/Evm generic `Cheatcodes` (#13894) * feat(cheatcodes): Network/Evm generic `Cheatcodes` * fix: tx create detection * fix(ci): adapt to `TransactionMaybeSigned::to()` return type change (#13896) * chore(cheatcodes): remove unused `cheats` param from `CheatcodesExecutor::console_log` (#13897) * refactor(evm): remove `tx_env` param from EVM constructor helper (#13903) Remove the `tx_env` parameter from `new_eth_evm_with_inspector`, `with_cloned_context`, and `with_fresh_nested_evm`. Instead of setting the transaction environment at EVM construction time (where it may become stale or be set redundantly), callers now pass the tx env directly to `evm.transact()` at execution time. This avoids some clones. * chore: remove dead code `paths_config` and `log_status` from inspector stack (#13905) chore: remove dead code in InspectorStackInner Remove two unused methods: - `InspectorStack::paths_config()`: zero callers across the codebase - `InspectorStackInner::log_status()`: zero callers across the codebase Also removes the now-unused `foundry_compilers::ProjectPathsConfig` import. * chore: remove no-op `spec_id` reassignment in `Executor::clone_with_backend` (#13906) chore: remove no-op spec_id reassignment in clone_with_backend After cloning `self.evm_env`, `evm_env.cfg_env.spec` already equals `self.spec_id()` (which simply returns `self.evm_env.cfg_env.spec`). The reassignment is a no-op. * feat(evm): `Backend` generic network (#13579) * feat(evm): `Backend` generic over `Network` * fix(evm): use `AnyNetwork` for fork env init to support non-standard chains When forking non-standard EVM chains (e.g. Moonbeam, Arbitrum), their block headers may lack standard Ethereum fields like `mixHash`. Using the generic `N`-typed provider (defaulting to `Ethereum`) caused deserialization failures for these chains. * feat(evm): add `TryAnyIntoTxEnv` trait for `AnyTxEnvelope` to `TxEnv` conversion Introduces TryAnyIntoTxEnv trait in foundry-evm-core to replace the TxEnv: FromRecoveredTx bound on Backend. This allows Backend to default to AnyNetwork: - TxEnvelope (Ethereum): delegates to FromRecoveredTx directly - AnyTxEnvelope: extracts inner TxEnvelope via as_envelope(), then delegates to FromRecoveredTx; returns error for unknown tx types Co-authored-by: Amp * chore: fix `cargo deny` failure --------- Co-authored-by: Amp * refactor(evm): `NestedEvm` based on revm's `Evm` instead of alloy-evm (#13908) * Add feature: `forge inspect linearization` to see a Solidity contract's linearized inheritance (#13704) * feat: Tempo wallet access key support for cast (#13909) * refactor(script-sequence): rename misleading `opcode` field to `call_kind` (#13907) * fix(fmt): swap valid_before/valid_after in TempoTransaction pretty print (#13910) * fix(fmt): prefer header total_difficulty for totalDifficulty (#13919) * feat(evm): `FoundryContextExt` impls for OP (#13925) * refactor(evm): simplify `NestedEvm` trait by removing `Block` and `Spec` (#13933) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits (#13922) * refactor(evm): make `FoundryInspectorExt` generic over `CTX` and rename traits Restructure the inspector extension traits for clarity and genericity: - Rename FoundryInspectorExt -> InspectorExt: the context-free base trait providing Foundry-specific inspector methods (console logging, network config) without any Inspector supertrait. - Rename EthInspectorExt -> FoundryInspectorExt: the combined trait that unifies Inspector + InspectorExt. Generic over any CTX that implements FoundryContextExt, rather than being hardcoded to specific BLOCK/TX/SPEC type parameters with for<'a> HRTB baked in. - Introduce EthEvmCtx<'db> type alias for the concrete Eth context (Context), keeping usage sites concise. This makes the trait hierarchy composable for multi-network support while keeping the Eth-specific default path ergonomic via the type alias during refactor. Co-authored-by: Amp * fix: use `EthEvmContext` instead of EthEvmCtx as default --------- Co-authored-by: Amp * fix(evm): restore `code_size_limit` config in `CfgEnv` (#13912) `cfg_env()` unconditionally set `limit_contract_code_size = Some(usize::MAX)`, ignoring the user's `code_size_limit` setting from foundry.toml. Use the configured value when present, falling back to `usize::MAX` (disabled). * perf(evm): remove unnecessary clones in do_call_end/do_create_end (#13913) perf(evm): remove unnecessary clones in `do_call_end`/`do_create_end` Both methods returned `CallOutcome`/`CreateOutcome` but all callers discarded the return value — the outcome is already mutated in-place via `&mut`. This removes the final `outcome.clone()` and changes the early-return in the `#[ret]` macro from `then_some(outcome.clone())` to `then(|| ())`, eliminating up to 2 clones per call/create end. * fix(wallets): browser wallet CLI help heading formatting (#13876) fix: browser wallet CLI help heading formatting - Set proper help_heading on BrowserWalletOpts to use 'Wallet options - browser wallet' consistently - Add next_help_heading to Erc20TxOpts to prevent transaction options from leaking into the browser wallet section Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(forge-script): Tempo access key support for forge script (#13917) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` (#13932) * refactor(anvil): encapsulate per-tx inspector lifecycle in `finish_transaction` Extract the repeated ~20-line drain-and-reset block from 3 locations in the block mining loop into `AnvilInspector::finish_transaction()`. The method prints traces/logs, drains the tracer into `Vec`, and reinstalls fresh tracer + log collector for the next transaction. Introduces `InspectorTxConfig` to carry the print/tracing settings, replacing direct field access into the inspector internals. * style: fix rustfmt * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification (#13924) * refactor(evm): generalize `TryAnyToTxEnv` trait over TxEnv + simplification The old trait converted `TxEnvelope` (the consensus-layer type) into `TxEnv`, requiring callers to separately pass the sender address. This was a leaky abstraction: the sender is already carried by the RPC transaction wrapper (TransactionResponse::from()), so callers had to extract it themselves before calling `try_into_tx_env`. The new trait `TryAnyToTxEnv` is generic over the output TxEnv type and takes the full RPC transaction as receiver, which means: - Implementations for `alloy_rpc_types::Transaction` and `AnyRpcTransaction` can call ToTxEnv / FromRecoveredTx internally without leaking address handling to callers. - A new impl for `op_alloy_rpc_types::Transaction` produces `OpTransaction`, enabling OP-stack fork replay without a separate code path. - `Backend` now constrains `N::TransactionResponse: TryAnyToTxEnv` instead of `N::TxEnvelope: TryAnyIntoTxEnv`, which is both more accurate and unlocks multi-network support. * fix: docs --------- Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(evm): simplify `replay_until` to use single `ForkDB` clone (#13931) * refactor: simplify replay_until to use single ForkDB clone Replace the per-transaction Backend + EVM creation in replay_until with a single cloned ForkDB and one persistent EthEvm instance. Before: for each tx in the block, commit_transaction would clone Fork + JournaledState, spawn a new Backend (including MultiFork), create a new FoundryEvm, transact, then apply_state_changeset. In a block with N preceding transactions, this meant N full clones. After: clone the fork's CacheDB once (SharedBackend is Arc-backed, so only the local cache layer is duplicated), create one EthEvm via EthEvmFactory, call transact_commit for each tx, then replace the fork's DB and refresh journaled states once at the end. Also: - Remove unused tx_env parameter from replay_until - Extract Fork::refresh_journaled_states helper to deduplicate the paired update_state calls in both replay_until and apply_state_changeset - Remove unused NoOpInspector import Amp-Thread-ID: https://ampcode.com/threads/T-019d2512-8074-72ac-92d8-e8f887911219 Co-authored-by: Amp * style: fix rustfmt * refactor: simplify tx collection loop and remove stale comments --------- Co-authored-by: Amp Co-authored-by: zerosnacks * fix(evm): use try_any_to_tx_env in replay_until (#13938) The merge of #13931 introduced a call to the old `try_into_tx_env` method which was renamed to `try_any_to_tx_env` in #13924. This fixes the docs CI build. Amp-Thread-ID: https://ampcode.com/threads/T-019d2952-9ec2-76f9-8f90-b7b3735d4ce3 Co-authored-by: Amp * chore(deps): bump taiki-e/install-action from 2.68.35 to 2.69.8 (#13915) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.35 to 2.69.8. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d...7bc99eee1f1b8902a125006cf790a1f4c8461e63) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.69.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.17.0 to 3.17.1 (#13914) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.17.0 to 3.17.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/131015bad844610e5e6300f8a143bf625d3e74f4...a18f73c54ca8525de051e73c31512a67f44df919) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.17.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(evm): simplify `FoundryContextExt` spec handling (#13935) - Introduced `FoundryContextExt::Spec` type for convenience - Removed complex bounds on `ContextTr::Cfg` using instead the new `Spec` type - Simplified accordingly the rest of the code Co-authored-by: zerosnacks Co-authored-by: Amp * refactor(anvil): remove some intermediate `Env` wrappers, pass `EvmEnv` directly (#13941) refactor(anvil): remove some intermediate Env wrapper, pass EvmEnv directly Replace usage of the `Env` struct wrapper with direct `EvmEnv` references in `new_eth_evm_with_inspector`, `validate_pool_transaction_for`, and `validate_for`. The optimism flag is now passed explicitly as `is_optimism: bool` instead of being extracted from `env.networks`. This eliminates unnecessary `Env::new(...)` construction in several hot paths (block mining, pending block simulation, tx replay) and simplifies the function signatures. * refactor(anvil): remove Env from storage, call env, and executor APIs (#13942) Continue the cleanup of the Env wrapper by passing EvmEnv directly in remaining call sites: - BlockchainStorage::new / Blockchain::new: drop the redundant spec_id parameter, derive it from evm_env.spec_id() instead - build_call_env: return (EvmEnv, OpTransaction) instead of Env, consumers destructure and use directly - new_eth_evm_with_inspector_ref: accept &EvmEnv instead of &Env, derive is_optimism from self.is_optimism() - build_tx_env_for_pending: take is_optimism: bool instead of NetworkConfigs + &EvmEnv - inspect_tx / replay_block_transactions_with_inspector: extract only evm_env from next_env(), avoid cloning the whole Env - api.rs: use spec_id() / chain_id() helpers instead of digging into env.evm_env.cfg_env fields directly - Various minor cleanups: inline single-use env write guards, use self.chain_id() / self.spec_id() helpers consistently --------- Signed-off-by: dependabot[bot] Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Nikki Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Edgar Richards Co-authored-by: Red Swan Co-authored-by: onbjerg Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Cleartext logging of sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/traces/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/anvil/src/eth/backend/executor.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: Avory Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Georgios Konstantopoulos Co-authored-by: zerosnacks Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: cui Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan --- .circleci/cargo.yml | 32 + .circleci/ci-web3-gamefi.yml | 26 + .circleci/ci.yml | 31 + .circleci/ci_cargo.yml | 37 + .circleci/ci_deploy.yml | 34 + .circleci/ci_v1.yml | 31 + .circleci/config.yml | 32 + .circleci/dev_stage.yml | 70 ++ .circleci/web3_defi_gamefi.yml | 26 + .codesandbox/tasks.json | 7 + .deps/remix-tests/remix_accounts.sol | 39 + .deps/remix-tests/remix_tests.sol | 225 +++++ .github/ISSUE_TEMPLATE/bug_report.md | 41 + .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/workflows/Docker.yml | 62 ++ .github/workflows/apisec-scan.yml | 29 + .github/workflows/codeql.yml | 92 +++ .github/workflows/deploy.yml | 27 + .github/workflows/docker.yml | 62 ++ .github/workflows/google.yml | 117 +++ .github/workflows/npm.yml | 30 + .github/workflows/snyk-container.yml | 55 ++ .gitmodules | 6 + benches/src/lib.rs | 16 +- counter/.github/workflows/test.yml | 43 + counter/.gitignore | 14 + counter/README.md | 66 ++ counter/foundry.toml | 6 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/script/Counter.s.sol | 19 + counter/src/Counter.sol | 14 + counter/test/Counter.t.sol | 24 + crates/cast/tests/cli/main.rs | 28 + crates/cli/src/utils/suggestions.rs | 1 + crates/common/Cargo.toml | 1 + crates/doc/src/parser/comment.rs | 6 + crates/evm/evm/Cargo.toml | 1 + crates/forge/Cargo.toml | 1 + crates/forge/tests/cli/test_optimizer.rs | 1 - crates/lint/src/linter.rs | 129 +++ crates/script/Cargo.toml | 1 + crates/test-utils/src/script.rs | 29 +- crates/test-utils/src/util.rs | 96 ++- crates/wallets/src/tempo.rs | 196 +++++ deny.toml | 7 +- npm/scripts/stage-from-artifact.mjs | 28 +- npm/src/const.mjs | 30 +- sleep.json | 955 ++++++++++++++++++++++ 50 files changed, 2829 insertions(+), 26 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_deploy.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/config.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .codesandbox/tasks.json create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/google.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .gitmodules create mode 100644 counter/.github/workflows/test.yml create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/script/Counter.s.sol create mode 100644 counter/src/Counter.sol create mode 100644 counter/test/Counter.t.sol create mode 100644 crates/lint/src/linter.rs create mode 100644 crates/wallets/src/tempo.rs create mode 100644 sleep.json diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..4168efef0971f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..edd3e4a15ddbc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, Safari] + - Version [e.g. 22] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..1295e430ca96a --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@6051de21ad50fbb1767bc93c11357a49082ad116' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 323059e99e6b6..e3a5b385a28a3 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -137,6 +137,36 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + - name: Validate Downloaded Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Validating artifacts in: $ARTIFACT_DIR" + + if [[ ! -d "$ARTIFACT_DIR" ]]; then + echo "ERROR: Artifact directory does not exist: $ARTIFACT_DIR" >&2 + exit 1 + fi + + if ! find "$ARTIFACT_DIR" -mindepth 1 -print -quit | grep -q .; then + echo "ERROR: Artifact directory is empty: $ARTIFACT_DIR" >&2 + exit 1 + fi + + # Reject files with suspicious paths (absolute paths or parent directory traversals) + # Use null-delimited paths to safely handle filenames with newlines or whitespace + while IFS= read -r -d '' path; do + rel="${path#"$ARTIFACT_DIR"/}" + if [[ "$rel" == /* ]] || [[ "$rel" == *".."* ]]; then + echo "ERROR: Suspicious artifact path detected: $rel" >&2 + exit 1 + fi + done < <(find "$ARTIFACT_DIR" -type f -print0) + + echo "Artifact validation completed successfully." + - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 50c7afae2ddec..7ed8807cbf0f5 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -141,10 +141,20 @@ impl BenchmarkProject { for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(&root_path) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 2f744efe4d4f0..9071614e3a7a9 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3158,6 +3158,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a66a0fef2fe0a..6921faabcb102 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,6 +17,7 @@ foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-primitives.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 42b91ccc366aa..e70f47e174a81 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -205,6 +205,12 @@ impl Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 5dbf07c7a356c..dd5138f532074 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -28,6 +28,7 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 667da6b442ca1..4e83eb168abca 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..2c11e0222a286 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a daignostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintCotext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 6de71571ac7eb..d243814c4b148 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -56,6 +56,7 @@ alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true thiserror.workspace = true +tempo-alloy.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 06f83886d0fd2..c1a6cb53bdbff 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(&from_dir) { + continue; + } + } else { + continue; + } + fs::copy(&file, to_dir.join(name))?; } Ok(()) } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 48489e43e34d9..fa065425b5281 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -3,12 +3,28 @@ use foundry_config::Config; use std::{ env, fs::{self, File}, - io::{Read, Seek, Write}, + io::{self, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, process::Command, sync::LazyLock, }; +/// Base directory under which all test utility filesystem operations are constrained. +/// Using a fixed directory under the system temp dir avoids trusting the current +/// working directory (which may be user-controlled) as a security boundary. +static TEST_UTIL_BASE: LazyLock = LazyLock::new(|| { + // Resolve the system temp directory to an absolute, canonical path where possible. + // If canonicalization fails for any reason, fall back to the raw temp_dir value. + let tmp = env::temp_dir(); + let mut base = tmp + .canonicalize() + .unwrap_or(tmp); + base.push("foundry_test_utils"); + // Ignore errors here; they will surface when the path is actually used. + let _ = fs::create_dir_all(&base); + base +}); + /// Directories to skip when copying project directories. /// These are build artifacts and runtime-generated files that should not be copied to temp /// workspaces. @@ -19,6 +35,9 @@ pub use crate::{ext::*, prj::*}; /// The commit of forge-std to use. pub const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); +/// Stores whether `stdout` is a tty / terminal. +pub static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); + /// Global default template path. Contains the global template project from which all other /// temp projects are initialized. See [`initialize()`] for more info. static TEMPLATE_PATH: LazyLock = @@ -146,7 +165,9 @@ pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { out = project.compile().unwrap(); test_debug!("compiled {}", lock_file_path.display()); - assert!(!out.has_compiler_errors(), "Compiled with errors:\n{out}"); + if out.has_compiler_errors() { + panic!("Compiled with errors:\n{out}"); + } if let Some(write) = &mut write { write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap(); @@ -174,7 +195,7 @@ pub fn get_vyper() -> Vyper { let path = VYPER.as_path(); let mut file = File::create(path).unwrap(); if let Err(e) = file.try_lock() { - if matches!(e, fs::TryLockError::WouldBlock) { + if let fs::TryLockError::WouldBlock = e { file.lock().unwrap(); assert!(path.exists()); return Vyper::new(path).unwrap(); @@ -230,22 +251,77 @@ pub fn read_string(path: impl AsRef) -> String { /// like `out/`, `cache/`, and `broadcast/` which are build artifacts that should not be /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { - fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + fs::create_dir_all(&dst)?; + copy_dir_filtered_inner(&src, &dst, true) +} + +/// Resolve a path against a safe base directory and ensure it does not escape that base. +/// +/// This guards against using uncontrolled paths that could traverse outside the intended +/// workspace (for example, via `..` components or absolute paths). +fn resolve_and_validate_under_base(path: &Path) -> io::Result { + // Use a fixed base directory for test utilities instead of the current working + // directory, which may be influenced by the environment. + let base = TEST_UTIL_BASE.clone(); + + // If `path` is absolute, interpret it relative to the base by stripping the + // root and joining the remaining components. This avoids treating arbitrary + // absolute paths as trustworthy. + let joined = if path.is_absolute() { + let relative_components = path.components().filter_map(|c| { + use std::path::Component; + match c { + Component::Normal(p) => Some(PathBuf::from(p)), + // Skip root and current-dir components; preserve parent-dir so that + // canonicalization below can detect and resolve them safely. + Component::RootDir | Component::CurDir => None, + Component::ParentDir => Some(PathBuf::from("..")), + Component::Prefix(_) => None, + } + }); + let mut rel = PathBuf::new(); + for c in relative_components { + rel.push(c); + } + base.join(rel) + } else { + base.join(path) + }; + + let canonical = joined.canonicalize()?; + if !canonical.starts_with(&base) { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "path escapes allowed base directory", + )); + } + + Ok(canonical) } fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { - for entry in fs::read_dir(src)? { + // Ensure that each recursion step operates on paths that are constrained to the + // configured base directory. This guarantees that any `src_path` passed to + // filesystem operations cannot escape the allowed workspace even if the initial + // input was influenced by the user. + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + for entry in fs::read_dir(&src)? { let entry = entry?; let ty = entry.file_type()?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + let name = entry.file_name(); + let src_path = src.join(&name); + let dst_path = dst.join(&name); if ty.is_dir() { // Skip build artifact directories at the root level if is_root - && let Some(name) = entry.file_name().to_str() - && SKIP_DIRS.contains(&name) + && let Some(name_str) = name.to_str() + && SKIP_DIRS.contains(&name_str) { continue; } diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs new file mode 100644 index 0000000000000..a86b568fdea2b --- /dev/null +++ b/crates/wallets/src/tempo.rs @@ -0,0 +1,196 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use alloy_signer::Signer; +use eyre::Result; +use std::path::PathBuf; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::transaction::{ + KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, +}; + +use crate::{WalletSigner, utils}; + +/// Wallet type: how this wallet was created. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// A single entry from Tempo's `keys.toml`. +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct KeyEntry { + #[serde(default)] + wallet_type: WalletType, + #[serde(default)] + wallet_address: Address, + #[serde(default)] + chain_id: u64, + #[serde(default)] + key_type: KeyType, + #[serde(default)] + key_address: Option
, + #[serde(default)] + key: Option, + #[serde(default)] + key_authorization: Option, + #[serde(default)] + expiry: Option, + #[serde(default)] + limits: Vec, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(serde::Deserialize)] +struct StoredTokenLimit { + #[allow(dead_code)] + currency: Address, + #[allow(dead_code)] + limit: String, +} + +/// The top-level structure of `~/.tempo/wallet/keys.toml`. +#[derive(serde::Deserialize)] +struct KeysFile { + #[serde(default)] + keys: Vec, +} + +/// Configuration for a Tempo access key (keychain mode). +/// +/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer +/// is an access key that signs on behalf of the root wallet. This struct carries the metadata +/// needed to construct the correct transaction. +#[derive(Debug, Clone)] +pub struct TempoAccessKeyConfig { + /// The root wallet address (the `from` address for transactions). + pub wallet_address: Address, + /// The access key's address (derived from the private key that actually signs). + pub key_address: Address, + /// Decoded key authorization for on-chain provisioning. + /// + /// When present, callers should check whether the key is already provisioned on-chain + /// (via the AccountKeychain precompile) before including this in a transaction. + pub key_authorization: Option, +} + +/// Result of looking up an address in Tempo's key store. +pub enum TempoLookup { + /// A direct (EOA) signer was found — `wallet_address == key_address`. + Direct(WalletSigner), + /// A keychain (access key) signer was found — `wallet_address != key_address`. + Keychain(WalletSigner, Box), + /// No matching entry was found. + NotFound, +} + +/// Returns the path to Tempo's keys file. +/// +/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. +fn keys_path() -> Option { + let base = std::env::var_os("TEMPO_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; + Some(base.join("wallet").join("keys.toml")) +} + +/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. +fn decode_key_authorization(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str)?; + let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; + Ok(auth) +} + +/// Looks up a signer for the given address in Tempo's `keys.toml`. +/// +/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, +/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, +/// or [`TempoLookup::NotFound`] if no entry matches. +pub fn lookup_signer(from: Address) -> Result { + let path = match keys_path() { + Some(p) if p.is_file() => p, + _ => return Ok(TempoLookup::NotFound), + }; + + let contents = std::fs::read_to_string(&path)?; + let file: KeysFile = toml::from_str(&contents)?; + + for entry in &file.keys { + if entry.wallet_address != from { + continue; + } + + let Some(key) = &entry.key else { + continue; + }; + + // Direct mode: wallet_address == key_address (or key_address is absent). + let is_direct = + entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let signer = utils::create_private_key_signer(key)?; + + if is_direct { + return Ok(TempoLookup::Direct(signer)); + } + + // Keychain mode: the key is an access key signing on behalf of wallet_address. + let key_authorization = + entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; + + let config = TempoAccessKeyConfig { + wallet_address: entry.wallet_address, + // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address + key_address: entry.key_address.unwrap(), + key_authorization, + }; + return Ok(TempoLookup::Keychain(signer, Box::new(config))); + } + + Ok(TempoLookup::NotFound) +} + +/// Signs a Tempo transaction request using an access key (keychain V2 mode). +/// +/// Bypasses the standard `EthereumWallet` signing path and instead: +/// 1. Builds the `TempoTransaction` from the request +/// 2. Computes the V2 keychain signing hash +/// 3. Signs with the access key +/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format +pub async fn sign_with_access_key( + tx_request: impl Into, + signer: &impl Signer, + wallet_address: Address, +) -> Result> { + let tx_request: TempoTransactionRequest = tx_request.into(); + let tempo_tx = tx_request + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + + Ok(buf) +} diff --git a/deny.toml b/deny.toml index 1a0e1e8e53005..0f891df4bfbfe 100644 --- a/deny.toml +++ b/deny.toml @@ -100,7 +100,10 @@ unknown-git = "deny" allow-git = [ "https://github.com/alloy-rs/alloy", "https://github.com/alloy-rs/evm", + "https://github.com/foundry-rs/compilers", + "https://github.com/foundry-rs/foundry-fork-db", "https://github.com/foundry-rs/foundry-core", + "https://github.com/foundry-rs/optimism", "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/paradigmxyz/solar", "https://github.com/bluealloy/revm", @@ -111,7 +114,5 @@ allow-git = [ "https://github.com/tempoxyz/mpp-rs", # Transitive dependency of Tempo "https://github.com/paradigmxyz/reth", - "https://github.com/paradigmxyz/reth-core", - # Temporary: upstream OP crates until release is published. - "https://github.com/ethereum-optimism/optimism", + "https://github.com/stevencartavia/reth", ] diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} From 1175872ab3ae417ede7dce3d6d4c1cc160a492be Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Thu, 7 May 2026 08:42:20 +0700 Subject: [PATCH 382/391] ci: sign release archives, docker images, and publish SBOMs --- .circleci/cargo.yml | 32 + .circleci/ci-web3-gamefi.yml | 26 + .circleci/ci.yml | 31 + .circleci/ci_cargo.yml | 37 + .circleci/ci_v1.yml | 31 + .circleci/dev_stage.yml | 70 + .circleci/web3_defi_gamefi.yml | 26 + .github/CODEOWNERS | 2 +- .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md | 10 + .github/scripts/commit-and-read-benchmarks.sh | 114 -- .github/scripts/commit-benchmark-results.sh | 75 + .github/scripts/compare-nightly.sh | 56 + .github/scripts/read-benchmark-results.sh | 37 + .github/scripts/tempo-check.sh | 86 +- .github/workflows/benchmarks-nightly.yml | 217 +++ .github/workflows/benchmarks.yml | 73 +- .github/workflows/ci-tempo.yml | 91 +- .github/workflows/ci.yml | 18 + .github/workflows/crate-checks.yml | 2 +- .github/workflows/docker-publish.yml | 30 + .github/workflows/nix.yml | 4 +- .github/workflows/npm.yml | 34 +- .github/workflows/release.yml | 78 +- .github/workflows/test-flaky.yml | 2 +- .github/workflows/test-isolate.yml | 2 +- .github/workflows/test.yml | 4 +- Cargo.lock | 1024 +++++------ README.md | 2 + SECURITY.md | 109 ++ benches/LATEST.md | 134 +- benches/src/main.rs | 40 +- benches/src/results.rs | 19 + benchmark.sh | 60 +- crates/anvil/Cargo.toml | 34 +- crates/anvil/core/Cargo.toml | 10 +- crates/anvil/src/cmd.rs | 27 +- crates/anvil/src/config.rs | 6 +- crates/anvil/src/eth/api.rs | 44 +- crates/anvil/src/eth/backend/executor.rs | 23 +- crates/anvil/src/eth/backend/mem/mod.rs | 267 ++- crates/anvil/src/eth/backend/mem/optimism.rs | 61 + .../anvil/src/eth/{error.rs => error/mod.rs} | 69 +- crates/anvil/src/eth/error/optimism.rs | 62 + crates/anvil/src/eth/otterscan/api.rs | 19 +- crates/anvil/src/eth/pool/transactions.rs | 4 +- crates/anvil/src/eth/sign.rs | 8 +- crates/anvil/src/{evm.rs => evm/mod.rs} | 84 +- crates/anvil/src/evm/optimism.rs | 87 + crates/anvil/src/lib.rs | 3 + crates/anvil/tests/it/main.rs | 2 + crates/anvil/tests/it/revert.rs | 50 + crates/cast/Cargo.toml | 17 +- crates/cast/src/args.rs | 7 + crates/cast/src/cmd/batch_mktx.rs | 27 +- crates/cast/src/cmd/batch_send.rs | 32 +- crates/cast/src/cmd/call.rs | 25 +- crates/cast/src/cmd/keychain.rs | 1022 ++++++++++- crates/cast/src/cmd/mktx.rs | 44 +- crates/cast/src/cmd/mod.rs | 3 + crates/cast/src/cmd/run.rs | 17 +- crates/cast/src/cmd/send.rs | 72 +- crates/cast/src/cmd/tempo.rs | 45 + crates/cast/src/cmd/tip20/mine.rs | 23 +- crates/cast/src/cmd/tip20/mod.rs | 2 +- crates/cast/src/cmd/vaddr/create.rs | 181 ++ crates/cast/src/cmd/vaddr/mod.rs | 131 ++ crates/cast/src/cmd/vaddr/resolve.rs | 52 + crates/cast/src/cmd/vaddr/watch.rs | 108 ++ crates/cast/src/cmd/wallet/mod.rs | 13 +- crates/cast/src/lib.rs | 4 +- crates/cast/src/opts.rs | 25 +- crates/cast/src/tx.rs | 30 +- crates/cast/tests/cli/keychain.rs | 76 + crates/cast/tests/cli/main.rs | 119 ++ crates/cheatcodes/Cargo.toml | 10 + crates/cheatcodes/assets/cheatcodes.json | 120 +- crates/cheatcodes/spec/src/vm.rs | 122 +- crates/cheatcodes/src/inspector.rs | 145 +- crates/cheatcodes/src/test/assert.rs | 14 +- crates/cheatcodes/src/version.rs | 67 +- crates/chisel/Cargo.toml | 9 +- crates/chisel/src/executor.rs | 1617 ++++++++--------- crates/chisel/src/source.rs | 562 ++---- crates/chisel/tests/it/repl/mod.rs | 20 + crates/cli/Cargo.toml | 7 + crates/cli/src/opts/evm.rs | 11 + crates/cli/src/opts/rpc.rs | 56 +- crates/cli/src/opts/rpc_common.rs | 7 +- crates/cli/src/opts/tempo.rs | 320 +++- crates/cli/src/utils/tempo.rs | 193 +- crates/common/Cargo.toml | 20 +- crates/common/build.rs | 20 +- crates/common/fmt/Cargo.toml | 8 +- crates/common/fmt/src/ui.rs | 8 + crates/common/src/contracts.rs | 14 +- crates/common/src/provider/mpp/keys.rs | 73 +- crates/common/src/provider/mpp/session.rs | 10 + crates/common/src/provider/mpp/transport.rs | 922 +++++++++- crates/common/src/provider/mpp/ws.rs | 4 + .../common/src/provider/runtime_transport.rs | 6 +- crates/common/src/tempo/auth.rs | 494 +++++ crates/common/src/tempo/mod.rs | 186 ++ crates/common/src/transactions/builder.rs | 57 +- crates/common/src/transactions/receipt.rs | 2 + crates/config/src/fuzz.rs | 6 + crates/config/src/inline/mod.rs | 39 + crates/config/src/lib.rs | 83 +- crates/config/src/providers/warnings.rs | 5 +- crates/debugger/Cargo.toml | 8 + crates/doc/Cargo.toml | 6 +- crates/doc/src/builder.rs | 40 +- crates/doc/src/helpers.rs | 91 - crates/doc/src/lib.rs | 7 +- crates/doc/src/parser/comment.rs | 74 +- crates/doc/src/parser/item.rs | 136 +- crates/doc/src/parser/mod.rs | 407 +++-- crates/doc/src/parser/source.rs | 172 ++ .../src/preprocessor/contract_inheritance.rs | 12 +- .../doc/src/preprocessor/infer_hyperlinks.rs | 17 +- crates/doc/src/preprocessor/inheritdoc.rs | 3 +- crates/doc/src/solang_ext/ast_eq.rs | 708 -------- crates/doc/src/solang_ext/loc.rs | 168 -- crates/doc/src/solang_ext/mod.rs | 31 - crates/doc/src/solang_ext/safe_unwrap.rs | 52 - crates/doc/src/solang_ext/visit.rs | 621 ------- crates/doc/src/writer/as_doc.rs | 69 +- crates/doc/src/writer/traits.rs | 51 +- crates/evm/core/Cargo.toml | 24 +- crates/evm/core/src/decode.rs | 4 +- crates/evm/core/src/env.rs | 608 ++++--- crates/evm/core/src/evm/mod.rs | 21 +- crates/evm/core/src/evm/op.rs | 22 +- crates/evm/core/src/fork/database.rs | 53 +- crates/evm/core/src/lib.rs | 3 + crates/evm/core/src/opts.rs | 7 +- crates/evm/coverage/Cargo.toml | 4 + crates/evm/evm/Cargo.toml | 13 + crates/evm/evm/src/executors/fuzz/mod.rs | 125 +- crates/evm/evm/src/executors/invariant/mod.rs | 2 +- crates/evm/fuzz/Cargo.toml | 9 + crates/evm/fuzz/src/lib.rs | 35 +- crates/evm/hardforks/Cargo.toml | 8 +- crates/evm/hardforks/src/lib.rs | 74 +- crates/evm/networks/Cargo.toml | 8 +- crates/evm/networks/src/lib.rs | 273 ++- crates/evm/networks/src/optimism.rs | 25 + crates/evm/traces/Cargo.toml | 4 + crates/fmt/Cargo.toml | 4 + crates/fmt/src/state/mod.rs | 2 +- .../bracket-spacing.fmt.sol | 6 + .../contract-new-lines.fmt.sol | 9 + .../fmt/testdata/ContractDefinition/fmt.sol | 9 + .../testdata/ContractDefinition/original.sol | 3 + crates/forge/Cargo.toml | 15 +- crates/forge/assets/tempo/MailTemplate.s.sol | 2 +- crates/forge/assets/tempo/MailTemplate.t.sol | 2 +- crates/forge/src/cmd/coverage.rs | 7 +- crates/forge/src/cmd/create.rs | 49 +- crates/forge/src/cmd/snapshot.rs | 7 +- crates/forge/src/cmd/test/mod.rs | 238 ++- crates/forge/src/cmd/test/summary.rs | 4 +- crates/forge/src/gas_report.rs | 2 +- crates/forge/src/multi_runner.rs | 32 + crates/forge/src/runner.rs | 24 +- crates/forge/tests/cli/cmd.rs | 127 +- crates/forge/tests/cli/config.rs | 31 +- crates/forge/tests/cli/ext_integration.rs | 14 +- crates/forge/tests/cli/failure_assertions.rs | 7 +- crates/forge/tests/cli/inline_config.rs | 104 ++ crates/forge/tests/cli/lint.rs | 289 ++- crates/forge/tests/cli/lint/geiger.rs | 10 +- crates/forge/tests/cli/script.rs | 2 +- crates/forge/tests/cli/test_cmd/fuzz.rs | 145 ++ .../tests/cli/test_cmd/invariant/common.rs | 2 +- .../forge/tests/cli/test_cmd/invariant/mod.rs | 12 +- crates/forge/tests/cli/test_cmd/repros.rs | 60 + .../tests/fixtures/ExpectRevertFailures.t.sol | 57 + crates/lint/Cargo.toml | 4 + crates/lint/README.md | 12 + crates/lint/docs/README.md | 52 + crates/lint/docs/_template.md | 28 + crates/lint/docs/asm-keccak256.md | 42 + crates/lint/docs/block-timestamp.md | 44 + crates/lint/docs/boolean-cst.md | 37 + crates/lint/docs/boolean-equal.md | 34 + crates/lint/docs/could-be-immutable.md | 42 + crates/lint/docs/custom-errors.md | 45 + crates/lint/docs/divide-before-multiply.md | 32 + crates/lint/docs/erc20-unchecked-transfer.md | 43 + crates/lint/docs/incorrect-erc20-interface.md | 42 + .../lint/docs/incorrect-erc721-interface.md | 48 + crates/lint/docs/incorrect-shift.md | 37 + crates/lint/docs/inline-assembly.md | 69 + crates/lint/docs/interface-file-naming.md | 31 + crates/lint/docs/interface-naming.md | 31 + crates/lint/docs/missing-zero-check.md | 39 + crates/lint/docs/mixed-case-function.md | 32 + crates/lint/docs/mixed-case-variable.md | 36 + crates/lint/docs/multi-contract-file.md | 37 + crates/lint/docs/named-struct-fields.md | 31 + crates/lint/docs/pascal-case-struct.md | 31 + crates/lint/docs/pragma-inconsistent.md | 41 + crates/lint/docs/rtlo.md | 32 + .../lint/docs/screaming-snake-case-const.md | 30 + .../docs/screaming-snake-case-immutable.md | 31 + crates/lint/docs/too-many-digits.md | 32 + crates/lint/docs/tx-origin.md | 34 + crates/lint/docs/unaliased-plain-import.md | 34 + crates/lint/docs/unchecked-call.md | 34 + crates/lint/docs/unsafe-cheatcode.md | 35 + crates/lint/docs/unsafe-typecast.md | 40 + crates/lint/docs/unused-import.md | 40 + crates/lint/docs/unused-state-variables.md | 39 + crates/lint/docs/unwrapped-modifier-logic.md | 51 + crates/lint/src/linter/late.rs | 1 + crates/lint/src/linter/mod.rs | 34 +- crates/lint/src/linter/project.rs | 92 + crates/lint/src/sol/gas/immutable.rs | 406 +++++ crates/lint/src/sol/gas/mod.rs | 11 +- .../src/sol/gas/unused_state_variables.rs | 90 + crates/lint/src/sol/high/mod.rs | 5 +- crates/lint/src/sol/high/rtlo.rs | 58 + crates/lint/src/sol/info/boolean_cst.rs | 116 ++ crates/lint/src/sol/info/boolean_equal.rs | 108 ++ crates/lint/src/sol/info/inline_assembly.rs | 71 + crates/lint/src/sol/info/mod.rs | 20 + crates/lint/src/sol/info/pragma_directive.rs | 71 + crates/lint/src/sol/info/too_many_digits.rs | 50 + crates/lint/src/sol/macros.rs | 42 +- crates/lint/src/sol/med/mod.rs | 4 + crates/lint/src/sol/med/tx_origin.rs | 101 + crates/lint/src/sol/mod.rs | 156 +- crates/lint/testdata/BlockTimestamp.stderr | 24 +- crates/lint/testdata/BooleanCst.sol | 25 + crates/lint/testdata/BooleanCst.stderr | 40 + crates/lint/testdata/BooleanEqual.sol | 24 + crates/lint/testdata/BooleanEqual.stderr | 56 + crates/lint/testdata/CouldBeImmutable.sol | 85 + crates/lint/testdata/CouldBeImmutable.stderr | 56 + crates/lint/testdata/CustomErrors.stderr | 10 +- .../lint/testdata/DivideBeforeMultiply.stderr | 12 +- crates/lint/testdata/Imports.stderr | 26 +- .../testdata/IncorrectERC20Interface.stderr | 30 +- .../testdata/IncorrectERC721Interface.stderr | 38 +- crates/lint/testdata/IncorrectShift.stderr | 10 +- crates/lint/testdata/InlineAssembly.sol | 110 ++ crates/lint/testdata/InlineAssembly.stderr | 96 + crates/lint/testdata/Keccak256.sol | 1 + crates/lint/testdata/Keccak256.stderr | 42 +- crates/lint/testdata/MissingZeroCheck.stderr | 46 +- crates/lint/testdata/MixedCase.stderr | 38 +- crates/lint/testdata/MultiContractFile.stderr | 10 +- .../MultiContractFile_InterfaceLibrary.stderr | 6 +- crates/lint/testdata/NamedStructFields.stderr | 2 +- .../PragmaInconsistentCaretAboveExact.sol | 7 + .../PragmaInconsistentCaretAboveExact.stderr | 16 + .../PragmaInconsistentCaretMatchesExact.sol | 7 + ...PragmaInconsistentCaretMatchesExact.stderr | 16 + .../PragmaInconsistentCaretVsTilde.sol | 7 + .../PragmaInconsistentCaretVsTilde.stderr | 16 + .../testdata/PragmaInconsistentOrVsExact.sol | 7 + .../PragmaInconsistentOrVsExact.stderr | 16 + .../PragmaInconsistentRangeVsExact.sol | 7 + .../PragmaInconsistentRangeVsExact.stderr | 16 + .../PragmaInconsistentThreeDistinct.sol | 8 + .../PragmaInconsistentThreeDistinct.stderr | 24 + crates/lint/testdata/Rtlo.sol | 81 + crates/lint/testdata/Rtlo.stderr | 192 ++ crates/lint/testdata/RtloCommentsOnly.sol | 15 + crates/lint/testdata/RtloCommentsOnly.stderr | 32 + .../lint/testdata/ScreamingSnakeCase.stderr | 16 +- crates/lint/testdata/StructPascalCase.stderr | 12 +- crates/lint/testdata/TooManyDigits.sol | 73 + crates/lint/testdata/TooManyDigits.stderr | 72 + crates/lint/testdata/TxOrigin.sol | 65 + crates/lint/testdata/TxOrigin.stderr | 72 + crates/lint/testdata/UncheckedCall.stderr | 16 +- .../testdata/UncheckedTransferERC20.stderr | 22 +- crates/lint/testdata/UnsafeCheatcodes.stderr | 26 +- crates/lint/testdata/UnsafeTypecast.stderr | 330 ++-- crates/lint/testdata/UnusedStateVariables.sol | 52 + .../lint/testdata/UnusedStateVariables.stderr | 40 + .../testdata/UnwrappedModifierLogic.stderr | 22 +- crates/primitives/Cargo.toml | 17 +- crates/primitives/src/network/mod.rs | 10 +- crates/primitives/src/network/optimism.rs | 47 + crates/primitives/src/network/receipt.rs | 40 +- crates/primitives/src/transaction/envelope.rs | 281 +-- crates/primitives/src/transaction/mod.rs | 6 +- crates/primitives/src/transaction/optimism.rs | 300 +++ crates/primitives/src/transaction/receipt.rs | 114 +- crates/primitives/src/transaction/request.rs | 110 +- crates/script-sequence/Cargo.toml | 4 + crates/script/Cargo.toml | 12 + crates/script/src/broadcast.rs | 143 +- crates/script/src/lib.rs | 131 +- crates/script/src/runner.rs | 10 +- crates/script/src/verify.rs | 2 +- crates/sol-macro-gen/Cargo.toml | 4 + crates/test-utils/Cargo.toml | 4 + crates/test-utils/src/util.rs | 2 +- crates/verify/Cargo.toml | 9 + crates/wallets/src/tempo.rs | 196 ++ deny.toml | 7 +- docs/dev/lintrules.md | 2 + flake.lock | 18 +- foundryup/README.md | 4 +- foundryup/foundryup | 127 +- sleep.json | 955 ++++++++++ testdata/default/cheats/ExpectRevert.t.sol | 85 + testdata/default/cheats/Fork2.t.sol | 1 + .../default/cheats/GetFoundryVersion.t.sol | 51 + testdata/default/cheats/MockCall.t.sol | 41 +- testdata/default/cheats/MockCalls.t.sol | 4 + testdata/forge-std-rev | 2 +- testdata/utils/Vm.sol | 116 +- 316 files changed, 17238 insertions(+), 6495 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md delete mode 100755 .github/scripts/commit-and-read-benchmarks.sh create mode 100644 .github/scripts/commit-benchmark-results.sh create mode 100644 .github/scripts/compare-nightly.sh create mode 100644 .github/scripts/read-benchmark-results.sh create mode 100644 .github/workflows/benchmarks-nightly.yml create mode 100644 crates/anvil/src/eth/backend/mem/optimism.rs rename crates/anvil/src/eth/{error.rs => error/mod.rs} (91%) create mode 100644 crates/anvil/src/eth/error/optimism.rs rename crates/anvil/src/{evm.rs => evm/mod.rs} (64%) create mode 100644 crates/anvil/src/evm/optimism.rs create mode 100644 crates/cast/src/cmd/tempo.rs create mode 100644 crates/cast/src/cmd/vaddr/create.rs create mode 100644 crates/cast/src/cmd/vaddr/mod.rs create mode 100644 crates/cast/src/cmd/vaddr/resolve.rs create mode 100644 crates/cast/src/cmd/vaddr/watch.rs create mode 100644 crates/cast/tests/cli/keychain.rs create mode 100644 crates/common/src/tempo/auth.rs create mode 100644 crates/doc/src/parser/source.rs delete mode 100644 crates/doc/src/solang_ext/ast_eq.rs delete mode 100644 crates/doc/src/solang_ext/loc.rs delete mode 100644 crates/doc/src/solang_ext/mod.rs delete mode 100644 crates/doc/src/solang_ext/safe_unwrap.rs delete mode 100644 crates/doc/src/solang_ext/visit.rs create mode 100644 crates/evm/networks/src/optimism.rs create mode 100644 crates/lint/docs/README.md create mode 100644 crates/lint/docs/_template.md create mode 100644 crates/lint/docs/asm-keccak256.md create mode 100644 crates/lint/docs/block-timestamp.md create mode 100644 crates/lint/docs/boolean-cst.md create mode 100644 crates/lint/docs/boolean-equal.md create mode 100644 crates/lint/docs/could-be-immutable.md create mode 100644 crates/lint/docs/custom-errors.md create mode 100644 crates/lint/docs/divide-before-multiply.md create mode 100644 crates/lint/docs/erc20-unchecked-transfer.md create mode 100644 crates/lint/docs/incorrect-erc20-interface.md create mode 100644 crates/lint/docs/incorrect-erc721-interface.md create mode 100644 crates/lint/docs/incorrect-shift.md create mode 100644 crates/lint/docs/inline-assembly.md create mode 100644 crates/lint/docs/interface-file-naming.md create mode 100644 crates/lint/docs/interface-naming.md create mode 100644 crates/lint/docs/missing-zero-check.md create mode 100644 crates/lint/docs/mixed-case-function.md create mode 100644 crates/lint/docs/mixed-case-variable.md create mode 100644 crates/lint/docs/multi-contract-file.md create mode 100644 crates/lint/docs/named-struct-fields.md create mode 100644 crates/lint/docs/pascal-case-struct.md create mode 100644 crates/lint/docs/pragma-inconsistent.md create mode 100644 crates/lint/docs/rtlo.md create mode 100644 crates/lint/docs/screaming-snake-case-const.md create mode 100644 crates/lint/docs/screaming-snake-case-immutable.md create mode 100644 crates/lint/docs/too-many-digits.md create mode 100644 crates/lint/docs/tx-origin.md create mode 100644 crates/lint/docs/unaliased-plain-import.md create mode 100644 crates/lint/docs/unchecked-call.md create mode 100644 crates/lint/docs/unsafe-cheatcode.md create mode 100644 crates/lint/docs/unsafe-typecast.md create mode 100644 crates/lint/docs/unused-import.md create mode 100644 crates/lint/docs/unused-state-variables.md create mode 100644 crates/lint/docs/unwrapped-modifier-logic.md create mode 100644 crates/lint/src/linter/project.rs create mode 100644 crates/lint/src/sol/gas/immutable.rs create mode 100644 crates/lint/src/sol/gas/unused_state_variables.rs create mode 100644 crates/lint/src/sol/high/rtlo.rs create mode 100644 crates/lint/src/sol/info/boolean_cst.rs create mode 100644 crates/lint/src/sol/info/boolean_equal.rs create mode 100644 crates/lint/src/sol/info/inline_assembly.rs create mode 100644 crates/lint/src/sol/info/pragma_directive.rs create mode 100644 crates/lint/src/sol/info/too_many_digits.rs create mode 100644 crates/lint/src/sol/med/tx_origin.rs create mode 100644 crates/lint/testdata/BooleanCst.sol create mode 100644 crates/lint/testdata/BooleanCst.stderr create mode 100644 crates/lint/testdata/BooleanEqual.sol create mode 100644 crates/lint/testdata/BooleanEqual.stderr create mode 100644 crates/lint/testdata/CouldBeImmutable.sol create mode 100644 crates/lint/testdata/CouldBeImmutable.stderr create mode 100644 crates/lint/testdata/InlineAssembly.sol create mode 100644 crates/lint/testdata/InlineAssembly.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.sol create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr create mode 100644 crates/lint/testdata/Rtlo.sol create mode 100644 crates/lint/testdata/Rtlo.stderr create mode 100644 crates/lint/testdata/RtloCommentsOnly.sol create mode 100644 crates/lint/testdata/RtloCommentsOnly.stderr create mode 100644 crates/lint/testdata/TooManyDigits.sol create mode 100644 crates/lint/testdata/TooManyDigits.stderr create mode 100644 crates/lint/testdata/TxOrigin.sol create mode 100644 crates/lint/testdata/TxOrigin.stderr create mode 100644 crates/lint/testdata/UnusedStateVariables.sol create mode 100644 crates/lint/testdata/UnusedStateVariables.stderr create mode 100644 crates/primitives/src/network/optimism.rs create mode 100644 crates/primitives/src/transaction/optimism.rs create mode 100644 crates/wallets/src/tempo.rs create mode 100644 sleep.json diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 83ff83e43c870..a6726847fd70b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @danipopes @mattsse @grandizzy @zerosnacks @onbjerg @0xrusowsky @mablr @figtracer @stevencartavia +* @danipopes @mattsse @grandizzy @zerosnacks @0xrusowsky @mablr @figtracer @stevencartavia diff --git a/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md b/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md new file mode 100644 index 0000000000000..8f72899c63611 --- /dev/null +++ b/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: "bug: tempo nightly workflow failed" +labels: P-high, T-bug +--- + +The nightly Tempo workflow (mainnet and testnet checks) has failed. This indicates a regression in Foundry's Tempo support or an issue with the Tempo mainnet/testnet endpoints. + +Check the [tempo nightly workflow page]({{ env.WORKFLOW_URL }}) for details. + +This issue was raised by the workflow at `.github/workflows/ci-tempo.yml`. diff --git a/.github/scripts/commit-and-read-benchmarks.sh b/.github/scripts/commit-and-read-benchmarks.sh deleted file mode 100755 index 358b53a73155a..0000000000000 --- a/.github/scripts/commit-and-read-benchmarks.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Script to commit benchmark results and read them for GitHub Actions output -# Usage: ./commit-and-read-benchmarks.sh - -OUTPUT_DIR="${1:-benches}" -GITHUB_EVENT_NAME="${2:-pull_request}" -GITHUB_REPOSITORY="${3:-}" - -# Global variable for branch name -BRANCH_NAME="" - -# Function to commit benchmark results -commit_results() { - echo "Configuring git..." - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # For PR runs, fetch and checkout the PR branch to ensure we're up to date - if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" - git fetch origin "$GITHUB_HEAD_REF" - git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" - fi - - echo "Adding benchmark file..." - git add "$OUTPUT_DIR/LATEST.md" - - if git diff --staged --quiet; then - echo "No changes to commit" - else - echo "Committing benchmark results..." - git commit -m "chore(\`benches\`): update benchmark results - -🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) - -Co-Authored-By: github-actions " - - echo "Pushing to repository..." - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - # For manual runs, we're on a new branch - git push origin "$BRANCH_NAME" - elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - # For PR runs, push to the PR branch - if [ -n "${GITHUB_HEAD_REF:-}" ]; then - echo "Pushing to PR branch: $GITHUB_HEAD_REF" - git push origin "$GITHUB_HEAD_REF" - else - echo "Error: GITHUB_HEAD_REF not set for pull_request event" - exit 1 - fi - else - # This workflow should only run on workflow_dispatch or pull_request - echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" - echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" - exit 1 - fi - echo "Successfully pushed benchmark results" - fi -} - -# Function to read benchmark results and output for GitHub Actions -read_results() { - if [ -f "$OUTPUT_DIR/LATEST.md" ]; then - echo "Reading benchmark results..." - - # Output full results - { - echo 'results<> "$GITHUB_OUTPUT" - - # Format results for PR comment - echo "Formatting results for PR comment..." - FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") - - { - echo 'pr_comment<> "$GITHUB_OUTPUT" - - echo "Successfully read and formatted benchmark results" - else - echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" - echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" - fi -} - -# Main execution -echo "Starting benchmark results processing..." - -# Create new branch for manual runs -if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - echo "Manual workflow run detected, creating new branch..." - BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$BRANCH_NAME" - echo "Created branch: $BRANCH_NAME" - - # Output branch name for later use - echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" -fi - -# Always commit benchmark results -echo "Committing benchmark results..." -commit_results - -# Always read results for output -read_results - -echo "Benchmark results processing complete" \ No newline at end of file diff --git a/.github/scripts/commit-benchmark-results.sh b/.github/scripts/commit-benchmark-results.sh new file mode 100644 index 0000000000000..f7dba8980fd64 --- /dev/null +++ b/.github/scripts/commit-benchmark-results.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +# Script to commit and push benchmark results. +# +# This script is intended to run from the lightweight `publish-results` job, +# which checks out the repo with credentials and only operates on the +# trusted artifact produced by the benchmark job. Keeping the write-scoped +# token away from the bench job (which runs untrusted third-party builds) +# limits the blast radius of a compromised dependency. +# +# Usage: ./commit-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" +GITHUB_EVENT_NAME="${2:-workflow_dispatch}" +GITHUB_REPOSITORY="${3:-}" + +if [ ! -f "$OUTPUT_DIR/LATEST.md" ]; then + echo "Error: $OUTPUT_DIR/LATEST.md not found, nothing to commit" + exit 1 +fi + +echo "Configuring git..." +git config --local user.email "action@github.com" +git config --local user.name "GitHub Action" + +# Decide which branch to commit to based on the event. +BRANCH_NAME="" +case "$GITHUB_EVENT_NAME" in + workflow_dispatch) + echo "Manual workflow run detected, creating new branch..." + BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH_NAME" + echo "Created branch: $BRANCH_NAME" + ;; + pull_request) + if [ -z "${GITHUB_HEAD_REF:-}" ]; then + echo "Error: GITHUB_HEAD_REF not set for pull_request event" + exit 1 + fi + echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" + git fetch origin "$GITHUB_HEAD_REF" + git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" + BRANCH_NAME="$GITHUB_HEAD_REF" + ;; + *) + echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" + echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" + exit 1 + ;; +esac + +# Always emit the branch name so downstream steps (e.g. PR creation) can use it. +echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + +echo "Adding benchmark file..." +git add "$OUTPUT_DIR/LATEST.md" + +if git diff --staged --quiet; then + echo "No changes to commit" + echo "committed=false" >> "$GITHUB_OUTPUT" + exit 0 +fi + +echo "Committing benchmark results..." +git commit -m "chore(\`benches\`): update benchmark results + +🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) + +Co-Authored-By: github-actions " + +echo "Pushing to repository..." +git push origin "$BRANCH_NAME" +echo "Successfully pushed benchmark results to $BRANCH_NAME" +echo "committed=true" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh new file mode 100644 index 0000000000000..674cc0fe01754 --- /dev/null +++ b/.github/scripts/compare-nightly.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Compare two nightly benchmark JSON summaries and report regressions. +# +# Usage: compare-nightly.sh [warn_pct] [fail_pct] +# Exits 0 if no regressions, 1 if any metric exceeds fail_pct. +# Exits 0 gracefully when prev.json is missing (first run / gap > 7 days). +set -euo pipefail + +PREV_JSON="${1:-}" +TODAY_JSON="${2:-}" +WARN="${3:-1}" +FAIL="${4:-3}" + +PREV_JSON="$PREV_JSON" TODAY_JSON="$TODAY_JSON" WARN="$WARN" FAIL="$FAIL" \ +python3 - <<'EOF' +import json, os, sys + +warn = float(os.environ["WARN"]) +fail = float(os.environ["FAIL"]) + +prev_path = os.environ.get("PREV_JSON", "") +prev = json.load(open(prev_path)) if prev_path and os.path.isfile(prev_path) else {} +with open(os.environ["TODAY_JSON"]) as f: + today = json.load(f) + +print("## Nightly Benchmark Regression Report\n") +print("| Benchmark | Previous | Today | Δ | Status |") +print("|-----------|----------|-------|---|--------|") + +has_regression = False +all_keys = sorted(prev.keys() | today.keys()) +for key in all_keys: + t = today.get(key) + p = prev.get(key) + if t is None: + print(f"| `{key}` | {p:.5f}s | N/A | — | ⚠️ Missing |") + has_regression = True + continue + if p is None: + print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") + continue + delta = (t - p) / p * 100 + if delta >= fail: + status = "🔴 Regression" + has_regression = True + elif delta >= warn: + status = "🟡 Warning" + elif delta <= -warn: + status = "🟢 Improvement" + else: + status = "➡️ OK" + sign = "+" if delta > 0 else "" + print(f"| `{key}` | {p}s | {t}s | {sign}{delta:.1f}% | {status} |") + +sys.exit(1 if has_regression else 0) +EOF diff --git a/.github/scripts/read-benchmark-results.sh b/.github/scripts/read-benchmark-results.sh new file mode 100644 index 0000000000000..548611a7d204a --- /dev/null +++ b/.github/scripts/read-benchmark-results.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +# Script to read benchmark results and emit them as GitHub Actions outputs. +# This script performs no git operations — it only reads the combined +# benchmark file and writes outputs for the workflow to consume. +# +# Usage: ./read-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" + +echo "Reading benchmark results from $OUTPUT_DIR..." + +if [ -f "$OUTPUT_DIR/LATEST.md" ]; then + # Output full results + { + echo 'results<> "$GITHUB_OUTPUT" + + # Format results for PR comment + echo "Formatting results for PR comment..." + FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") + + { + echo 'pr_comment<> "$GITHUB_OUTPUT" + + echo "Successfully read and formatted benchmark results" +else + echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" +fi diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh index b730c466bde55..3caea992cfe7e 100644 --- a/.github/scripts/tempo-check.sh +++ b/.github/scripts/tempo-check.sh @@ -445,7 +445,7 @@ echo -e "\n=== CAST SEND WITH SPONSOR (--tempo.sponsor-signature) ===" # Test sponsored transactions using pre-signed signature. # Step 1: Get the fee_payer_signature_hash using --tempo.print-sponsor-hash # Step 2: Sign it with the sponsor's private key -# Step 3: Send with --tempo.sponsor-signature +# Step 3: Send with --tempo.sponsor and --tempo.sponsor-signature # Step 1: Get the hash that the sponsor needs to sign FEE_PAYER_HASH=$(cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ @@ -460,7 +460,7 @@ printf "Sponsor signature: %s\n" "$SPONSOR_SIG" # Step 3: Send the sponsored transaction with the signature RECEIPT=$(cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" \ - --tempo.sponsor-signature "$SPONSOR_SIG" --json) + --tempo.sponsor "$SPONSOR_ADDR" --tempo.sponsor-signature "$SPONSOR_SIG" --json) # Verify the fee_payer in the receipt matches the sponsor address RECEIPT_FEE_PAYER=$(echo "$RECEIPT" | jq -r '.feePayer // .fee_payer // empty') @@ -897,3 +897,85 @@ check_has_code "Nonce" "0x4e4F4E4345000000000000000000000000000000" check_has_code "AccountKeychain" "0xaAAAaaAA00000000000000000000000000000000" echo -e "\n=== CHISEL FORK TESTS COMPLETE ===" + +# --- cast virtual-address (TIP-1022) tests --- + +echo -e "\n=== CAST VIRTUAL-ADDRESS: SETUP MASTER WALLET ===" +vaddr_master_json="$(cast wallet new --json)" +VADDR_MASTER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_master_json")" +VADDR_MASTER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_master_json")" +printf "Master address: %s\n" "$VADDR_MASTER_ADDR" +fund_and_wait "$VADDR_MASTER_ADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: CREATE (mine + register) ===" +# Use the `vaddr` alias to also exercise it. +VADDR_CREATE_OUT=$(cast vaddr create \ +--owner "$VADDR_MASTER_ADDR" \ +--private-key "$VADDR_MASTER_PK" \ +--rpc-url "$ETH_RPC_URL") +echo "$VADDR_CREATE_OUT" +VADDR=$(echo "$VADDR_CREATE_OUT" | sed -n 's/^ tag=0x000000000000 \(0x[a-fA-F0-9]\{40\}\).*/\1/p' | head -1) +if [[ -z "$VADDR" ]]; then +echo "ERROR: failed to parse virtual address from create output" +exit 1 +fi +echo "Virtual address: $VADDR" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: RESOLVE ===" +VADDR_RESOLVE_OUT=$(cast virtual-address resolve "$VADDR" --rpc-url "$ETH_RPC_URL") +echo "$VADDR_RESOLVE_OUT" +RESOLVED_MASTER=$(echo "$VADDR_RESOLVE_OUT" | sed -n 's/^Master address: \(0x[a-fA-F0-9]\{40\}\).*/\1/p') +RESOLVED_LOWER=$(echo "$RESOLVED_MASTER" | tr '[:upper:]' '[:lower:]') +EXPECTED_LOWER=$(echo "$VADDR_MASTER_ADDR" | tr '[:upper:]' '[:lower:]') +if [[ "$RESOLVED_LOWER" != "$EXPECTED_LOWER" ]]; then +echo "ERROR: resolve returned master $RESOLVED_MASTER, expected $VADDR_MASTER_ADDR" +exit 1 +fi +echo "OK: resolve returned the registered master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: AUTO-FORWARD TO MASTER ===" +# Create a separate sender, fund it, and transfer the fee token to the +# virtual address. The protocol must auto-forward to the master wallet. +vaddr_sender_json="$(cast wallet new --json)" +VADDR_SENDER_ADDR="$(jq -r '.[0].address' <<<"$vaddr_sender_json")" +VADDR_SENDER_PK="$(jq -r '.[0].private_key' <<<"$vaddr_sender_json")" +fund_and_wait "$VADDR_SENDER_ADDR" + +BAL_BEFORE=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance before: $BAL_BEFORE" + +# Capture the current block before the transfer so `cast vaddr watch` can +# replay the Transfer log via --from-block. +BLOCK_BEFORE_TRANSFER=$(cast block-number --rpc-url "$ETH_RPC_URL") + +AMOUNT=1000000 +cast send "$FEE_TOKEN" 'transfer(address,uint256)' "$VADDR" "$AMOUNT" \ +--rpc-url "$ETH_RPC_URL" --private-key "$VADDR_SENDER_PK" + +BAL_AFTER=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$VADDR_MASTER_ADDR" | awk '{print $1}') +echo "Master balance after: $BAL_AFTER" + +EXPECTED=$((BAL_BEFORE + AMOUNT)) +if [[ "$BAL_AFTER" != "$EXPECTED" ]]; then +echo "ERROR: master balance grew by $((BAL_AFTER - BAL_BEFORE)), expected $AMOUNT" +exit 1 +fi +echo "OK: transfer to virtual address auto-forwarded to master" + +echo -e "\n=== CAST VIRTUAL-ADDRESS: WATCH ===" +# Tail incoming TIP-20 transfers to the virtual address. `cast vaddr watch` +# polls indefinitely, so we cap it with `timeout`; the historical fetch via +# --from-block emits the prior Transfer log immediately at startup. +WATCH_OUT=$(timeout 5 cast vaddr watch "$VADDR" \ + --token "$FEE_TOKEN" \ + --from-block "$BLOCK_BEFORE_TRANSFER" \ + --rpc-url "$ETH_RPC_URL" 2>&1 || true) +echo "$WATCH_OUT" + +EXPECTED_PATTERN="token=$FEE_TOKEN from=$VADDR_SENDER_ADDR amount=$AMOUNT" +echo "Expected pattern: $EXPECTED_PATTERN" +if ! echo "$WATCH_OUT" | grep -iqF "$EXPECTED_PATTERN"; then + echo "ERROR: cast vaddr watch output did not contain expected '$EXPECTED_PATTERN'" + exit 1 +fi +echo "OK: cast vaddr watch reported correct token/from/amount" diff --git a/.github/workflows/benchmarks-nightly.yml b/.github/workflows/benchmarks-nightly.yml new file mode 100644 index 0000000000000..8569f52ce3b93 --- /dev/null +++ b/.github/workflows/benchmarks-nightly.yml @@ -0,0 +1,217 @@ +name: Nightly Benchmarks (AAVE v4) + +permissions: {} + +on: + schedule: + - cron: "0 2 * * *" # 2am UTC nightly + workflow_dispatch: # allow manual triggering for testing + +env: + AAVE_V4_REPO: "aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" + RUSTC_WRAPPER: "sccache" + +jobs: + run-benchmarks: + name: Run Nightly Benchmarks + runs-on: depot-ubuntu-24.04-32 + permissions: + contents: read + actions: read # needed to download artifacts from previous runs + outputs: + has_regression: ${{ steps.compare.outputs.has_regression }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + + - name: Setup Foundry + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + ./.github/scripts/setup-foundryup.sh + printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" + + - name: Build benchmark binary + run: cargo build --locked --release --bin foundry-bench + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + - name: Install hyperfine + run: | + curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ + rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu + + - name: Download previous benchmark results + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p prev-results + PREV_RUN_ID=$(gh run list \ + --workflow=benchmarks-nightly.yml \ + --status=success \ + --limit=1 \ + --json databaseId \ + -q '.[0].databaseId // empty' 2>/dev/null || true) + if [[ -n "$PREV_RUN_ID" ]]; then + echo "Downloading results from previous run $PREV_RUN_ID" + gh run download "$PREV_RUN_ID" \ + --name nightly-bench-results \ + --dir prev-results/ || true + else + echo "No previous successful run found, skipping download." + fi + + - name: Run forge test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_test \ + --json-output "nightly-${DATE}-forge_test.json" \ + --verbose + + - name: Run forge fuzz test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_fuzz_test \ + --json-output "nightly-${DATE}-forge_fuzz_test.json" \ + --verbose + + - name: Run forge isolate test benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_isolate_test \ + --json-output "nightly-${DATE}-forge_isolate_test.json" \ + --verbose + + - name: Run forge build benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --json-output "nightly-${DATE}-forge_build.json" \ + --verbose + + - name: Run forge coverage benchmarks + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_coverage \ + --json-output "nightly-${DATE}-forge_coverage.json" \ + --verbose + + - name: Merge benchmark JSON results + run: | + DATE=$(date -u +%Y-%m-%d) + shopt -s nullglob + parts=( benches/nightly-${DATE}-*.json ) + if [[ ${#parts[@]} -eq 0 ]]; then + echo "No benchmark results produced — all steps failed." + exit 1 + fi + jq -s 'add' "${parts[@]}" > "benches/nightly-${DATE}.json" + echo "Merged ${#parts[@]} result file(s) into nightly-${DATE}.json" + + - name: Compare with previous results + id: compare + run: | + DATE=$(date -u +%Y-%m-%d) + PREV_JSON=$(ls prev-results/nightly-*.json 2>/dev/null | head -1 || true) + TODAY_JSON="benches/nightly-${DATE}.json" + if ./.github/scripts/compare-nightly.sh "$PREV_JSON" "$TODAY_JSON" > regression.md 2>&1; then + echo "has_regression=false" >> "$GITHUB_OUTPUT" + else + echo "has_regression=true" >> "$GITHUB_OUTPUT" + fi + cat regression.md + + - name: Upload benchmark results + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: nightly-bench-results + retention-days: 7 + path: | + benches/nightly-*.json + regression.md + + report-regression: + name: Report Regression + needs: run-benchmarks + if: needs.run-benchmarks.outputs.has_regression == 'true' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Download benchmark results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: nightly-bench-results + path: results/ + + - name: Open regression issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + DATE=$(date -u +%Y-%m-%d) + BODY="$(cat results/regression.md) + + --- + + **Run**: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + **Date**: ${DATE} + **Repo benchmarked**: \`aave/aave-v4\` (pinned commit) + **Threshold**: 🔴 >=3% regression, 🟡 >=1% warning" + + gh issue create \ + --title "[Nightly Regression] ${DATE}" \ + --body "$BODY" \ + --label "regression" \ + --repo "$GH_REPO" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5d4767ad3b554..a136703abc294 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -10,17 +10,17 @@ on: required: false type: string versions: - description: "Comma-separated list of Foundry versions to benchmark (e.g., stable,nightly,v1.0.0)" + description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'v1.5.1,v1.7.0')" required: false type: string - default: "stable,nightly" repos: - description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev][ ] (e.g. vectorized/solady:v0.1.26 --nmc BrokenTest). Leave empty to use the per-benchmark default repo lists." + description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev] (e.g. aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35). Leave empty to use the per-benchmark default repo lists." required: false type: string - default: "" env: + DEFAULT_VERSIONS: "v1.5.1,v1.7.0" + # Repos to benchmark per step. Each comma-separated entry has the form # org/repo[:rev][ ] # where anything after the first whitespace is appended to every benchmark @@ -29,27 +29,23 @@ env: TEST_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting ISOLATE_TEST_REPOS: >- ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest, - vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test', - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, + vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest', uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting BUILD_REPOS: >- ithacaxyz/account:v0.5.7, vectorized/solady:v0.1.26, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 COVERAGE_REPOS: >- ithacaxyz/account:v0.5.7, - aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35, uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, sparkdotfi/spark-psm:v1.0.0 @@ -60,7 +56,7 @@ jobs: name: Run All Benchmarks runs-on: depot-ubuntu-24.04-32 permissions: - contents: write + contents: read steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -93,7 +89,7 @@ jobs: run: cargo build --locked --release --bin foundry-bench - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" @@ -106,59 +102,61 @@ jobs: - name: Run forge test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_test,forge_fuzz_test \ - --output-file forge_test_bench.md + --output-file forge_test_bench.md \ + --verbose - name: Run forge isolate test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.ISOLATE_TEST_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_isolate_test \ - --output-file forge_isolate_test_bench.md + --output-file forge_isolate_test_bench.md \ + --verbose - name: Run forge build benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.BUILD_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_build_no_cache,forge_build_with_cache \ - --output-file forge_build_bench.md + --output-file forge_build_bench.md \ + --verbose - name: Run forge coverage benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry - VERSIONS: ${{ github.event.inputs.versions || 'stable,nightly' }} + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} REPOS: ${{ github.event.inputs.repos || env.COVERAGE_REPOS }} run: | ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions "$VERSIONS" \ --repos "$REPOS" \ --benchmarks forge_coverage \ - --output-file forge_coverage_bench.md + --output-file forge_coverage_bench.md \ + --verbose - name: Combine benchmark results run: ./.github/scripts/combine-benchmarks.sh benches - - name: Commit and read benchmark results + - name: Read benchmark results id: benchmark_results - env: - GITHUB_HEAD_REF: ${{ github.head_ref }} - run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" + run: ./.github/scripts/read-benchmark-results.sh benches - name: Upload benchmark results as artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -172,21 +170,21 @@ jobs: benches/LATEST.md outputs: - branch_name: ${{ steps.benchmark_results.outputs.branch_name }} pr_comment: ${{ steps.benchmark_results.outputs.pr_comment }} publish-results: name: Publish Results needs: run-benchmarks runs-on: ubuntu-latest + # All git writes happen here, on a clean ubuntu-latest runner that has + # never executed third-party benchmark code. permissions: contents: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false + # persist-credentials defaults to true so we can push. - name: Download benchmark results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -194,19 +192,22 @@ jobs: name: benchmark-results path: benches/ - - name: Push branch for manual runs - if: github.event_name == 'workflow_dispatch' - run: | - git push origin "${{ needs.run-benchmarks.outputs.branch_name }}" - echo "Pushed branch: ${{ needs.run-benchmarks.outputs.branch_name }}" + - name: Commit benchmark results + id: commit_results + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: ./.github/scripts/commit-benchmark-results.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Create PR for manual runs - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && steps.commit_results.outputs.committed == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BRANCH_NAME: ${{ steps.commit_results.outputs.branch_name }} + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | - const branchName = '${{ needs.run-benchmarks.outputs.branch_name }}'; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const branchName = process.env.BRANCH_NAME; + const prComment = process.env.PR_COMMENT; // Create the pull request const { data: pr } = await github.rest.pulls.create({ @@ -231,10 +232,12 @@ jobs: - name: Comment on PR if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} with: script: | const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; - const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; + const prComment = process.env.PR_COMMENT; const comment = `${prComment} diff --git a/.github/workflows/ci-tempo.yml b/.github/workflows/ci-tempo.yml index b2be5a5312a10..1ef4c760f324e 100644 --- a/.github/workflows/ci-tempo.yml +++ b/.github/workflows/ci-tempo.yml @@ -6,6 +6,8 @@ on: push: branches: [master] pull_request: + schedule: + - cron: "0 2 * * *" # Run daily at 2 AM UTC (offset from other nightlies) workflow_dispatch: inputs: network: @@ -67,36 +69,28 @@ jobs: run: | cargo test --locked -p foundry-common --lib tempo::tests::test_fork_schedule_parses_configured_rpcs -- --exact --nocapture - # TODO: re-enable once devnet is up and stable - # - name: Run Tempo check on devnet - # if: | - # github.event_name == 'pull_request' || - # github.event.inputs.network == 'devnet' || - # github.event.inputs.network == 'all' - # env: - # ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} - # SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} - # run: | - # if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then - # ./.github/scripts/tempo-check.sh - # fi - # if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then - # ./.github/scripts/tempo-deploy.sh - # fi - - # TODO: re-enable once devnet is up and stable - # - name: Run Tempo wallet tests on devnet - # if: | - # github.event_name == 'pull_request' || - # github.event.inputs.network == 'devnet' || - # github.event.inputs.network == 'all' - # env: - # ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} - # run: ./.github/scripts/tempo-wallet.sh + - name: Run Tempo check on mainnet + if: | + github.event_name == 'schedule' || + github.event.inputs.network == 'mainnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} + TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" + VERIFIER_URL: ${{ secrets.VERIFIER_URL }} + PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} + SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} + run: | + if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-check.sh + fi + if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-deploy.sh + fi - name: Run Tempo check on testnet if: | - github.event_name == 'pull_request' || + github.event_name == 'schedule' || github.event.inputs.network == 'testnet' || github.event.inputs.network == 'all' env: @@ -111,15 +105,14 @@ jobs: ./.github/scripts/tempo-deploy.sh fi - - name: Run Tempo check on mainnet + - name: Run Tempo check on devnet if: | - github.event.inputs.network == 'mainnet' || + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || github.event.inputs.network == 'all' env: - ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} - TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" - VERIFIER_URL: ${{ secrets.VERIFIER_URL }} - PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} run: | if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then @@ -128,3 +121,35 @@ jobs: if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then ./.github/scripts/tempo-deploy.sh fi + + - name: Run Tempo wallet tests on devnet + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + run: ./.github/scripts/tempo-wallet.sh + + # If the nightly run fails, this will create an issue to signal so. + issue: + name: Open an issue + runs-on: ubuntu-latest + needs: [tempo-check] + if: failure() && github.event_name == 'schedule' + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb90a76cdbfd..9b63b3e01b4b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,23 @@ jobs: - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo test --workspace --doc --locked + no-default-features: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo build --workspace --no-default-features --locked + typos: runs-on: depot-ubuntu-latest timeout-minutes: 30 @@ -171,6 +188,7 @@ jobs: - test - docs - doctest + - no-default-features - typos - clippy - rustfmt diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml index eb865bddc10e3..f0d460da6fbb1 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -29,7 +29,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b97a99d5310a4..6120735657ee6 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -32,6 +32,8 @@ jobs: name: build and push runs-on: depot-ubuntu-latest permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -92,6 +94,7 @@ jobs: uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 - name: Build and push Foundry image + id: build uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 with: build-args: | @@ -106,3 +109,30 @@ jobs: platforms: linux/amd64,linux/arm64 push: true no-cache: true + sbom: true + provenance: mode=max + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign image with cosign (keyless) + env: + DOCKER_TAGS: ${{ steps.docker_tagging.outputs.docker_tags }} + DIGEST: ${{ steps.build.outputs.digest }} + shell: bash + run: | + set -euo pipefail + IFS=',' read -r -a tags <<< "$DOCKER_TAGS" + for tag in "${tags[@]}"; do + # Strip any tag suffix and pin to immutable digest, then sign. + ref="${tag%%:*}@${DIGEST}" + printf 'Signing %s\n' "$ref" + cosign sign --yes "$ref" + done + + - name: Image build provenance attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index f6e068eeec62a..258c86e9e72c2 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@32cb6a5ae30bb0dfc996fa7baf8bf1ed28442fa4 # v3.17.3 + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 2f990e2f9da92..7e849f31c34fa 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -153,13 +153,43 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + - name: Validate Downloaded Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Validating artifacts in: $ARTIFACT_DIR" + + if [[ ! -d "$ARTIFACT_DIR" ]]; then + echo "ERROR: Artifact directory does not exist: $ARTIFACT_DIR" >&2 + exit 1 + fi + + if ! find "$ARTIFACT_DIR" -mindepth 1 -print -quit | grep -q .; then + echo "ERROR: Artifact directory is empty: $ARTIFACT_DIR" >&2 + exit 1 + fi + + # Reject files with suspicious paths (absolute paths or parent directory traversals) + # Use null-delimited paths to safely handle filenames with newlines or whitespace + while IFS= read -r -d '' path; do + rel="${path#"$ARTIFACT_DIR"/}" + if [[ "$rel" == /* ]] || [[ "$rel" == *".."* ]]; then + echo "ERROR: Suspicious artifact path detected: $rel" >&2 + exit 1 + fi + done < <(find "$ARTIFACT_DIR" -type f -print0) + + echo "Artifact validation completed successfully." + - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -275,7 +305,7 @@ jobs: bun-version: latest - name: Setup Node (for npm publish auth) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682c9214284f6..38fa791fb655f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2 + uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2.1 with: configuration: "./.github/changelog.json" fromTag: ${{ steps.release_info.outputs.from_tag || '' }} @@ -117,6 +117,8 @@ jobs: needs: prepare uses: ./.github/workflows/docker-publish.yml permissions: + attestations: write + artifact-metadata: write contents: read id-token: write packages: write @@ -129,9 +131,10 @@ jobs: # way, GitHub's immutable-releases setting seals the release at publish. release: permissions: - id-token: write - contents: write attestations: write + artifact-metadata: write + contents: write + id-token: write name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 @@ -264,6 +267,38 @@ jobs: printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" fi printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" + printf "foundry_sbom=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.spdx.json" >> "$GITHUB_OUTPUT" + printf "foundry_checksum=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sha256" >> "$GITHUB_OUTPUT" + printf "foundry_signature=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sigstore.json" >> "$GITHUB_OUTPUT" + + - name: Generate archive checksum + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + shell: bash + run: | + set -euo pipefail + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + else + shasum -a 256 "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + fi + cat "$FOUNDRY_CHECKSUM" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + + - name: Generate SBOM (SPDX) + env: + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash + run: | + set -euo pipefail + syft scan dir:. \ + --source-name foundry \ + --source-version "$VERSION_NAME" \ + -o spdx-json="$FOUNDRY_SBOM" - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -292,15 +327,37 @@ jobs: tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - - name: Binaries attestation + - name: Binaries and archive provenance attestation id: attestation - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: | ${{ env.anvil_bin_path }} ${{ env.cast_bin_path }} ${{ env.chisel_bin_path }} ${{ env.forge_bin_path }} + ${{ steps.artifacts.outputs.file_name }} + + - name: Archive SBOM attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: ${{ steps.artifacts.outputs.file_name }} + sbom-path: ${{ steps.artifacts.outputs.foundry_sbom }} + + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign archive with cosign (keyless) + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} + shell: bash + run: | + set -euo pipefail + cosign sign-blob \ + --yes \ + --bundle "$FOUNDRY_SIGNATURE" \ + "$FILE_NAME" - name: Record attestation URL env: @@ -321,11 +378,20 @@ jobs: TAG_NAME: ${{ needs.prepare.outputs.tag_name }} FILE_NAME: ${{ steps.artifacts.outputs.file_name }} FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} FOUNDRY_MAN: ${{ steps.man.outputs.foundry_man }} shell: bash run: | set -euo pipefail - files=("$FILE_NAME" "$FOUNDRY_ATTESTATION") + files=( + "$FILE_NAME" + "$FOUNDRY_ATTESTATION" + "$FOUNDRY_SBOM" + "$FOUNDRY_CHECKSUM" + "$FOUNDRY_SIGNATURE" + ) if [[ -n "${FOUNDRY_MAN:-}" ]]; then files+=("$FOUNDRY_MAN") fi diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml index d6244f826887e..9caa254f05c10 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 141e3a049a73b..6763f1f80bde3 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -37,7 +37,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaa46c8e79f7c..daa4822b6e395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,14 +73,14 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@58e862542551f667fa44c8a2a4a1d64ad477c96a # v2.75.17 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: tool: nextest # External tests dependencies - name: Setup Node.js if: contains(matrix.name, 'external') - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 - name: Install Bun diff --git a/Cargo.lock b/Cargo.lock index 6996d21b881d9..9db38f1967f4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,21 +77,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a547705d5c1b42575a0542bae2ba45bc62a6154be86611afaef1c0ab5c38598e" +checksum = "d8010fc7e9e8643ef4e758cdccf3eef26734594aedf88a9d5ed35e51837d42ef" dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-network", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -116,14 +116,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c24c95e90c1608c2d91cff1b451d796474168d3310ccc8b7cd12502ca8169" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "alloy-tx-macros", "auto_impl", @@ -143,23 +143,23 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d211ad0ef468a70a7a829e49683ff59ad25f02b4ab3764344c4c2663329a52c" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-contract" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d55233ac14aa7fa6bcdcad45ba305e90c556065e0947cd9f243c4469e7c2d" +checksum = "de3df0aadc569a8b277808a7d0ad0e421180654ea36a3c59e9ed2bb968c9a1cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250ba1168b8a049185a68c4dfa7f2a6a4046bd26fcc8c68632caeb216a5e12dc" +checksum = "1ceb16e7fe5a95825305f218ccd356665f848831f94ce2bbf55339bf5d21e88a" dependencies = [ "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] @@ -265,13 +265,14 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "once_cell", "serde", ] @@ -300,9 +301,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae69eaa5096b47ffe97e6a5d6bde7e7fa2dec106af22a9315621d11039c3de3c" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -310,7 +311,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "auto_impl", "borsh", "c-kzg", @@ -325,9 +326,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a8c1330ad33c95b5958573bca9a1ad0b419a51d76bb4c521556fbba8539b8d" +checksum = "d5638cbbffb318d440fdb009de019090d8d117dae40de9d10cdb29891ea59eb9" dependencies = [ "alloy-contract", "alloy-primitives", @@ -339,12 +340,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.33.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc4b83cb672156663e6094d098beb509965b7fe684bb3d6e44bb9ca2e9ae714" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -359,13 +360,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39789db0b3f3bbef0e6549c87bc6842b73886ebabee1405b6941685b1cc34083" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "borsh", "serde", @@ -400,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662b525af73e86b2167dae923261c8edf440ba7e1426b30a8b993177bc214c02" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -415,19 +416,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c657c2d9751d3c7d94990554b231e5372c3c2e4bad842806280b6151a0d6a05d" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", @@ -441,24 +442,24 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e7c4bb0ebbd6d7406d2808968f43c0d5186c69c5e58cedcbee7380f4cd1fcf" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-op-evm" version = "0.31.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -472,7 +473,7 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" version = "0.4.7" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -513,13 +514,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fea0fc2628cdbc851aaa333124f9d8ab9f567ab8d4c20202819db13aa1a534" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -547,7 +548,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "thiserror 2.0.18", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7b42e514613c717887dc77bb58d35e845557ebd63a18c3f92a77094e4891f" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -603,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ee7b51752c68fb95f21705e402700750e692b1d21ccc294ac48fadc8655d53" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -616,7 +617,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde", "serde_json", "tokio", @@ -629,9 +630,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa76988f54105ad4398828e8aaf1a39b3f07f91fb79091529056689514ee8c2" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -640,44 +641,44 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d276bea4e92e4991269d31b9abd3e722eed2565b82036478a4416adb8dd4992" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1a9a3bda9be7f6515316eb792710532411878bbfc88934973f4b371376b00d" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5d68ddca890854fb78291cbde06115473ded00b2337d0f815e92c0c1f8003" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -689,9 +690,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea21739e232c221779741eba7e7b9bc19ad8ff777b72736647ae519f5c9f6f33" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -702,15 +703,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f05338cfb4ee5508ff76f01c88142cab8a4579db74b7d9432936c26e4f11374" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", @@ -722,17 +723,17 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda4ece0050154ab278241aeffade58916b04f38254832e8cb6e4671c6e72ed2" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "itertools 0.14.0", "serde", @@ -743,13 +744,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5905ac3663b0859d67b82d912acce20887d20682a0cadde79c8a763b133a515" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", "serde_json", "thiserror 2.0.18", @@ -757,13 +758,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fbf71892d4df9cae8d35dc96f15d522384bb93806205465e2c8c012b7f0a34" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "serde", ] @@ -780,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beaa5c581a67e2743d95b4849eb9cfeb90866429cdaa6d8f6b75eb988b2d0cd9" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "serde", @@ -791,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5da9ae50f9b48d7b4e2e5cde87175257be7e5e56909a7794720597c1d9806f6" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -808,9 +809,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a57d1e72b1f9b11e5e71ebdab0569cb02277a462bbea6793fcaebfcd794ae9" +checksum = "1258987fbc82716b5153ec7bb95a8a295e7640871b8f03d8ec7c4000dc80c215" dependencies = [ "alloy-consensus", "alloy-network", @@ -827,9 +828,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b27f20b5298b76a5a3b7cdbe6bdb184ab1ebd6e120e00dad748867673f5c90" +checksum = "7ffc2a49bca5b73c6964711b57452f6c36a6bcb7f845ab7e9ad05b5a828d0161" dependencies = [ "alloy-consensus", "alloy-network", @@ -845,9 +846,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c7acc40ffbfd37d4113eb619863099f3235d78d044006a1eecb94d8b0b2f1a" +checksum = "94e11ddaddfb98c1ddce737dc440225565b0ae0987ac9ad5e59a85db5904878c" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -865,9 +866,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b794002d57fd2f71b4c87298a41ca24dfc0f2cf6630d95106a477e451747ba" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -885,9 +886,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a09a865ae9e1f05478429ef0d935b16467f35c6e0b02cb10f23f66a3b33fc3" +checksum = "44eb341d0013784da6a39e5bbdc11b95d6744993b12a1c3fd55df795a850dd42" dependencies = [ "alloy-consensus", "alloy-network", @@ -902,9 +903,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb8218544ab635281f1be180a1cfd9b5d549db686faa7e85b3b2c10969819e" +checksum = "82ff16b4166fb90bbe79bd1e49244824fb3cadc6b8cd11e9c8a002c1f8c07492" dependencies = [ "alloy-consensus", "alloy-network", @@ -991,9 +992,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19dec9bfb59647254afdecbb5ddcddd7ba02edcd48ffa40510bddfbed0be1634" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1014,14 +1015,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2035f3c4d6bee20624da2dcf765d469b292398e48d766ffade61b0fcf8b4d45d" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -1030,9 +1031,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfad7aa9206fcb831ae401b6a1c893a402b8eed74f9c8ffbb7a7323afb0d9a4c" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1050,9 +1051,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aa8ff49386df3e008b73c7fb0a5479410e8493fdb86a8b916877a16e8aead9" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1085,9 +1086,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520337f3d3d063a7fe20f47aaa62d695e3dc0372b34f601560dee24e76988b9" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1194,7 +1195,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1218,18 +1219,18 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anvil" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-network", @@ -1241,7 +1242,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-beacon", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1276,7 +1277,7 @@ dependencies = [ "parking_lot", "rand 0.8.6", "rand 0.9.4", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -1297,17 +1298,17 @@ dependencies = [ [[package]] name = "anvil-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip5792", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "foundry-common", "foundry-evm", @@ -1321,7 +1322,7 @@ dependencies = [ [[package]] name = "anvil-rpc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "serde", "serde_json", @@ -1329,7 +1330,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.6.0" +version = "1.7.1" dependencies = [ "anvil-rpc", "async-trait", @@ -1669,20 +1670,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ascii-canvas" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" -dependencies = [ - "term", -] - [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1955,9 +1947,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.101.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -2172,9 +2164,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.14" +version = "1.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -2361,9 +2353,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -2579,7 +2571,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -2745,13 +2737,13 @@ dependencies = [ [[package]] name = "cast" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-evm", "alloy-hardforks", @@ -2763,7 +2755,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -2822,9 +2814,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -2832,12 +2824,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -2876,7 +2862,7 @@ dependencies = [ [[package]] name = "chisel" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2884,17 +2870,15 @@ dependencies = [ "clap", "dirs", "eyre", - "forge-doc", "forge-fmt", "foundry-cli", "foundry-common", "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-solang-parser", "foundry-test-utils", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.3", "rexpect", "rustyline", "semver 1.0.28", @@ -2998,9 +2982,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" dependencies = [ "clap", ] @@ -3043,7 +3027,7 @@ dependencies = [ "terminfo", "thiserror 2.0.18", "which", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3196,7 +3180,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3365,9 +3349,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", @@ -3376,9 +3360,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concurrent-queue" @@ -3426,9 +3410,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -3539,9 +3523,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc-fast" @@ -3822,9 +3806,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" @@ -3974,9 +3958,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.1", @@ -4000,7 +3984,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4188,15 +4172,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "ena" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -4305,7 +4280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4503,15 +4478,15 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "figment2" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4380ce44915a6227efbb61e3885bc1c8e99fb9820f5db612abfac2c5cfc46871" +checksum = "87d63dee16df12076c7770919713c0b92f4e1c85eac828dc2ade0b6c998f016b" dependencies = [ "atomic", "parking_lot", "serde", "tempfile", - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.11+spec-1.1.0", "uncased", "version_check", ] @@ -4608,7 +4583,7 @@ checksum = "932dcfbd51320af5f27f1ba02d2e567dec332cac7d2c221ba45d8e767264c4dc" [[package]] name = "forge" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4662,7 +4637,7 @@ dependencies = [ "rand 0.9.4", "rayon", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4690,16 +4665,14 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "derive_more", "eyre", - "forge-fmt", "foundry-common", "foundry-compilers", "foundry-config", - "foundry-solang-parser", "itertools 0.14.0", "mdbook-driver", "rayon", @@ -4714,7 +4687,7 @@ dependencies = [ [[package]] name = "forge-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "foundry-common", "foundry-config", @@ -4728,7 +4701,7 @@ dependencies = [ [[package]] name = "forge-lint" -version = "1.6.0" +version = "1.7.1" dependencies = [ "eyre", "foundry-common", @@ -4742,12 +4715,12 @@ dependencies = [ [[package]] name = "forge-script" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-json-abi", "alloy-network", @@ -4790,7 +4763,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-network", "alloy-primitives", @@ -4806,7 +4779,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -4821,7 +4794,7 @@ dependencies = [ [[package]] name = "forge-verify" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4844,7 +4817,7 @@ dependencies = [ "futures", "itertools 0.14.0", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "semver 1.0.28", "serde", @@ -4867,7 +4840,7 @@ dependencies = [ [[package]] name = "foundry-bench" -version = "1.6.0" +version = "1.7.1" dependencies = [ "chrono", "clap", @@ -4892,7 +4865,7 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -4902,7 +4875,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4955,7 +4928,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes-spec" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-sol-types", "foundry-macros", @@ -4966,11 +4939,11 @@ dependencies = [ [[package]] name = "foundry-cli" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-ens", "alloy-json-abi", "alloy-network", @@ -5010,6 +4983,7 @@ dependencies = [ "tempo-primitives", "tikv-jemallocator", "tokio", + "toml", "tracing", "tracing-subscriber 0.3.23", "tracing-tracy", @@ -5018,7 +4992,7 @@ dependencies = [ [[package]] name = "foundry-cli-markdown" -version = "1.6.0" +version = "1.7.1" dependencies = [ "clap", "pretty_assertions", @@ -5026,12 +5000,12 @@ dependencies = [ [[package]] name = "foundry-common" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-json-abi", "alloy-json-rpc", "alloy-network", @@ -5043,6 +5017,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "alloy-transport", "alloy-transport-ipc", @@ -5050,6 +5025,7 @@ dependencies = [ "anstream 0.6.21", "anstyle", "axum", + "base64 0.22.1", "chrono", "ciborium", "clap", @@ -5067,18 +5043,20 @@ dependencies = [ "futures", "itertools 0.14.0", "jiff", + "k256", "mpp", "num-format", "op-alloy-network", "op-alloy-rpc-types", "path-slash", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "rustls", "semver 1.0.28", "serde", "serde_json", + "sha2 0.10.9", "solar-compiler", "tempfile", "tempo-alloy", @@ -5097,14 +5075,14 @@ dependencies = [ [[package]] name = "foundry-common-fmt" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "chrono", "comfy-table", "eyre", @@ -5220,7 +5198,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5260,7 +5238,7 @@ dependencies = [ [[package]] name = "foundry-debugger" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "crossterm", @@ -5278,7 +5256,7 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5315,7 +5293,7 @@ dependencies = [ [[package]] name = "foundry-evm-abi" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -5327,7 +5305,7 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -5342,7 +5320,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-sol-types", "anvil", "auto_impl", @@ -5379,7 +5357,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "eyre", @@ -5395,7 +5373,7 @@ dependencies = [ [[package]] name = "foundry-evm-fuzz" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5420,7 +5398,7 @@ dependencies = [ [[package]] name = "foundry-evm-hardforks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -5435,10 +5413,10 @@ dependencies = [ [[package]] name = "foundry-evm-networks" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", @@ -5451,11 +5429,11 @@ dependencies = [ [[package]] name = "foundry-evm-sancov" -version = "1.6.0" +version = "1.7.1" [[package]] name = "foundry-evm-traces" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -5473,7 +5451,7 @@ dependencies = [ "itertools 0.14.0", "memchr", "rayon", - "reqwest 0.13.2", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", @@ -5512,7 +5490,7 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-primitives", "foundry-compilers", @@ -5523,7 +5501,7 @@ dependencies = [ [[package]] name = "foundry-macros" -version = "1.6.0" +version = "1.7.1" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -5533,7 +5511,7 @@ dependencies = [ [[package]] name = "foundry-primitives" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-consensus", "alloy-evm", @@ -5544,7 +5522,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer", "derive_more", "op-alloy-consensus", @@ -5558,23 +5536,9 @@ dependencies = [ "tempo-revm", ] -[[package]] -name = "foundry-solang-parser" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9645e75b89f977423690f3b4bfd8d84825e5fdabd7803cbce6d4a2c4d54972b4" -dependencies = [ - "itertools 0.14.0", - "lalrpop", - "lalrpop-util", - "phf 0.11.3", - "thiserror 2.0.18", - "unicode-xid", -] - [[package]] name = "foundry-test-utils" -version = "1.6.0" +version = "1.7.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -5589,7 +5553,7 @@ dependencies = [ "parking_lot", "rand 0.9.4", "regex", - "reqwest 0.13.2", + "reqwest 0.13.3", "serde_json", "snapbox", "svm-rs", @@ -5817,7 +5781,7 @@ dependencies = [ "once_cell", "prost 0.14.3", "prost-types 0.14.3", - "reqwest 0.13.2", + "reqwest 0.13.3", "secret-vault-value", "serde", "serde_json", @@ -6191,9 +6155,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" dependencies = [ "typenum", ] @@ -6585,9 +6549,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -6595,7 +6559,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6644,7 +6608,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6697,9 +6661,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -6710,31 +6674,15 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -6744,7 +6692,7 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", "thiserror 2.0.18", @@ -6765,15 +6713,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -6805,9 +6744,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -6844,6 +6783,7 @@ version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", "getrandom 0.2.17", "js-sys", @@ -6926,45 +6866,14 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] -[[package]] -name = "lalrpop" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.14.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "sha3", - "string_cache 0.8.9", - "term", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" -dependencies = [ - "regex-automata", - "rustversion", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -6985,9 +6894,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -6997,12 +6906,11 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.44" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" dependencies = [ "cc", - "libc", ] [[package]] @@ -7302,12 +7210,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -7334,9 +7242,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" dependencies = [ "libmimalloc-sys", ] @@ -7591,7 +7499,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7820,7 +7728,7 @@ dependencies = [ [[package]] name = "op-alloy" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "op-alloy-consensus", "op-alloy-network", @@ -7832,15 +7740,15 @@ dependencies = [ [[package]] name = "op-alloy-consensus" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "bytes", "derive_more", "reth-codecs", @@ -7859,7 +7767,7 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", "alloy-network", @@ -7872,7 +7780,7 @@ dependencies = [ [[package]] name = "op-alloy-provider" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-network", "alloy-primitives", @@ -7886,15 +7794,15 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "derive_more", "op-alloy-consensus", "reth-rpc-traits", @@ -7906,15 +7814,14 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" version = "0.24.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde 2.0.1", - "derive_more", + "alloy-serde 2.0.4", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", @@ -7927,7 +7834,7 @@ dependencies = [ [[package]] name = "op-revm" version = "19.0.0" -source = "git+https://github.com/ethereum-optimism/optimism?rev=42f5117c2e7de0614cd3b96f274d0a3078f9701c#42f5117c2e7de0614cd3b96f274d0a3078f9701c" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "auto_impl", "revm", @@ -8145,16 +8052,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap 2.14.0", -] - [[package]] name = "pharos" version = "0.5.3" @@ -8171,7 +8068,6 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -8181,7 +8077,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", + "phf_macros", "phf_shared 0.13.1", "serde", ] @@ -8226,19 +8122,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "phf_macros" version = "0.13.1" @@ -8574,7 +8457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -9094,9 +8977,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -9138,12 +9021,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9158,11 +9041,12 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -9176,8 +9060,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -9186,8 +9071,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9199,11 +9084,11 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9212,8 +9097,8 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9236,10 +9121,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "bytes", "modular-bitfield", @@ -9250,11 +9135,11 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -9266,8 +9151,8 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -9279,11 +9164,11 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs", @@ -9293,11 +9178,11 @@ dependencies = [ [[package]] name = "reth-evm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "auto_impl", @@ -9315,11 +9200,11 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -9335,8 +9220,8 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-evm", "alloy-primitives", @@ -9348,11 +9233,11 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -9367,8 +9252,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9380,11 +9265,12 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -9408,8 +9294,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9423,8 +9309,8 @@ dependencies = [ [[package]] name = "reth-revm" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9436,8 +9322,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-evm", @@ -9456,9 +9342,9 @@ dependencies = [ [[package]] name = "reth-rpc-traits" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b766da61ec7c46596386b4bc88d9b57d1939d3da2bc9e927567a8a23650e5ce9" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" dependencies = [ "alloy-consensus", "alloy-network", @@ -9471,8 +9357,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "bytes", @@ -9484,8 +9370,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-primitives", "derive_more", @@ -9498,11 +9384,11 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9521,10 +9407,10 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9539,14 +9425,14 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "2.1.0" -source = "git+https://github.com/paradigmxyz/reth?rev=7839f3d#7839f3d876b32842b059ca8171242b807ba1fc80" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-trie", "arrayvec", "bytes", @@ -9562,8 +9448,9 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "0.3.0" -source = "git+https://github.com/paradigmxyz/reth-core?rev=6b12498#6b12498871bc1b1d42c6dcf28968c271660de8c0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] @@ -9846,9 +9733,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -9856,20 +9743,20 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.4.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "5ac5b223d9738ef56e0b98305410be40fa0941bf6036c56f1506751e43552d64" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327b72899159dfae8060c51a1f6aebe955245bcd9cc4997eed0f623caea022e4" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", @@ -9877,9 +9764,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "arbitrary", @@ -10004,14 +9891,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -10037,9 +9924,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -10047,13 +9934,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni 0.21.1", + "jni", "log", "once_cell", "rustls", @@ -10063,7 +9950,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10481,9 +10368,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ "base64 0.22.1", "chrono", @@ -10500,9 +10387,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -10563,14 +10450,14 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -10704,9 +10591,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -10777,7 +10664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10845,7 +10732,7 @@ dependencies = [ "derive_more", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "normalize-path", "once_map", @@ -10880,7 +10767,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.11.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -11014,18 +10901,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -11178,13 +11053,13 @@ dependencies = [ [[package]] name = "svm-rs" -version = "0.5.24" +version = "0.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230df06b463c7251e4d1b39b1b3e6f25a9b3a42630179053a1e5f919e6e15534" +checksum = "4572dd9845e37ca0293acb5fe591a7f61b51f1b7b62d3dc6fb8e99e2664f3755" dependencies = [ "const-hex", "dirs", - "reqwest 0.13.2", + "reqwest 0.13.3", "semver 1.0.28", "serde", "serde_json", @@ -11197,9 +11072,9 @@ dependencies = [ [[package]] name = "svm-rs-builds" -version = "0.5.24" +version = "0.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b271921143e5b12947a526de464db02b00363919d582a7ea712374840f928328" +checksum = "74224f62f19c1309caa071de7c1c9c1ad1d7551d2f881af4046f3d71880c820a" dependencies = [ "const-hex", "semver 1.0.28", @@ -11304,22 +11179,22 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "tempo-alloy" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-contract", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "alloy-signer-local", "alloy-sol-types", "alloy-transport", @@ -11337,9 +11212,9 @@ dependencies = [ [[package]] name = "tempo-chainspec" version = "1.5.3" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-hardforks", @@ -11356,7 +11231,7 @@ dependencies = [ [[package]] name = "tempo-consensus" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11374,7 +11249,7 @@ dependencies = [ [[package]] name = "tempo-contracts" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-contract", "alloy-primitives", @@ -11385,7 +11260,7 @@ dependencies = [ [[package]] name = "tempo-evm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11412,7 +11287,7 @@ dependencies = [ [[package]] name = "tempo-precompiles" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "alloy-evm", @@ -11432,7 +11307,7 @@ dependencies = [ [[package]] name = "tempo-precompiles-macros" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy", "proc-macro2", @@ -11443,15 +11318,15 @@ dependencies = [ [[package]] name = "tempo-primitives" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.1", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.1", + "alloy-serde 2.0.4", "aws-lc-rs", "base64 0.22.1", "derive_more", @@ -11474,7 +11349,7 @@ dependencies = [ [[package]] name = "tempo-revm" version = "1.6.0" -source = "git+https://github.com/tempoxyz/tempo?rev=8bd4d01d37e3cc324030baacbce2da0862d7735a#8bd4d01d37e3cc324030baacbce2da0862d7735a" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11505,15 +11380,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "term" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" -dependencies = [ - "windows-sys 0.60.2", -] - [[package]] name = "terminal_size" version = "0.4.4" @@ -11521,7 +11387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11555,9 +11421,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" [[package]] name = "thiserror" @@ -11698,9 +11564,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -11867,9 +11733,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap 2.14.0", + "serde_core", + "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -11878,7 +11746,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -12223,9 +12091,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -12509,9 +12377,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174a690eb3293a5666442b0738d080df9ea6b9e03782bbe78875c89ff914a77c" +checksum = "2d7cb4a83971db3f6ae36f0aa41eaf5985d2e2b469581fa755c132f9c2a1ec89" dependencies = [ "anyhow", "bon", @@ -12522,9 +12390,9 @@ dependencies = [ [[package]] name = "vergen-gitcl" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f628f4acc90a5c1a8136495eaf5f9ef94e03c174d6fb2e6de691bc58fc721ee" +checksum = "bba14c9676943b2899cea2ed7ea194b89b3d13564a3c93a61882a978b123a41c" dependencies = [ "anyhow", "bon", @@ -12536,9 +12404,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "10.0.0-beta.6" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390d0442b660baedd7a6f60d2af01bd8967e0d7fe69cd15e13c82c811d82b709" +checksum = "fb684e6d170ef15a9b3c20561779a50ba8c806f8acdaff47c0a2c5c4c6cadd43" dependencies = [ "anyhow", "bon", @@ -12603,11 +12471,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -12616,14 +12484,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -12634,9 +12502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -12644,9 +12512,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12654,9 +12522,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -12667,9 +12535,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -12767,7 +12635,7 @@ dependencies = [ "watchexec-events", "watchexec-signals", "watchexec-supervisor", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -12807,9 +12675,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -12827,13 +12695,13 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", - "string_cache 0.9.0", + "string_cache", "string_cache_codegen", ] @@ -12844,7 +12712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ "core-foundation 0.10.1", - "jni 0.22.4", + "jni", "log", "ndk-context", "objc2", @@ -12917,7 +12785,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -13038,15 +12906,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -13083,21 +12942,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -13140,12 +12984,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -13158,12 +12996,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -13176,12 +13008,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -13206,12 +13032,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -13224,12 +13044,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -13242,12 +13056,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -13260,12 +13068,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -13289,9 +13091,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -13305,6 +13107,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/README.md b/README.md index c9f0a45c57b0a..90cd1d8a7865e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ foundryup See the [installation guide](https://getfoundry.sh/getting-started/installation) for more details. +To verify a downloaded release archive or container image, see [Verifying Releases](./SECURITY.md#verifying-releases). + ## Getting Started Initialize a new project, build and test: diff --git a/SECURITY.md b/SECURITY.md index d84327cc18e91..6296066db5e73 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,3 +3,112 @@ ## Reporting a Vulnerability Contact [security@tempo.xyz](mailto:security@tempo.xyz). + +## Verifying Releases + +Every official Foundry release ships with multiple, independent integrity +artifacts. All signing is keyless via [Sigstore](https://www.sigstore.dev/) — +no Foundry-managed key material is involved, and every signature is recorded +in the public [Rekor](https://docs.sigstore.dev/logging/overview/) transparency +log. The signing identity is the GitHub Actions OIDC token of this repository's +`release.yml` / `docker-publish.yml` workflows. + +### Per-release artifacts + +For each `foundry___.{tar.gz,zip}` archive on the +[releases page](https://github.com/foundry-rs/foundry/releases), the same +release also publishes: + +| Suffix | Purpose | +| --- | --- | +| `.sha256` | SHA-256 checksum of the archive (`sha256sum` format) | +| `.sigstore.json` | Cosign keyless signature bundle (cert + signature + Rekor proof) over the archive | +| `.spdx.json` | SPDX 2.3 SBOM of the source workspace used for the build | +| `.attestation.txt` | URL of the GitHub artifact-attestation summary | + +In addition, GitHub stores SLSA build-provenance and SBOM attestations against +the archive's digest; these are queryable via `gh attestation` without +downloading anything else. + +### Verifying an archive + +Pick whichever toolchain you have available — they verify the same signatures. + +#### Option 1: GitHub CLI (simplest) + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry +``` + +This computes the file's digest, fetches the matching attestation from GitHub, +and verifies the Sigstore signature plus the SLSA provenance predicate. Add +`--signer-workflow foundry-rs/foundry/.github/workflows/release.yml` to also +require the workflow identity. + +To verify the SBOM attestation specifically: + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry \ + --predicate-type 'https://spdx.dev/Document/v2.3' +``` + +#### Option 2: Cosign (offline-friendly) + +Download the archive and its `.sigstore.json` bundle from the release page, +then: + +```bash +cosign verify-blob \ + --bundle foundry_v1.4.0_linux_amd64.sigstore.json \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/release\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ + foundry_v1.4.0_linux_amd64.tar.gz +``` + +For nightly builds the certificate identity points at `refs/heads/master` +instead of a tag; the regex above matches both. + +#### Option 3: Plain checksum (integrity only) + +```bash +sha256sum -c foundry_v1.4.0_linux_amd64.sha256 # GNU coreutils +shasum -a 256 -c foundry_v1.4.0_linux_amd64.sha256 # macOS +``` + +This proves the bytes match what was uploaded, but says nothing about who +uploaded them. Combine with one of the verifications above for end-to-end +trust. + +### Verifying the Docker image + +Container signatures and attestations are pushed as OCI referrers to GHCR, so +no separate files need to be downloaded. + +```bash +# Cosign keyless signature on the image +cosign verify ghcr.io/foundry-rs/foundry:v1.4.0 \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' + +# SLSA build-provenance attestation +gh attestation verify oci://ghcr.io/foundry-rs/foundry:v1.4.0 \ + --repo foundry-rs/foundry + +# Inspect the buildx-attached SBOM and provenance +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .SBOM }}' +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .Provenance }}' +``` + +To pin to an immutable digest (recommended for reproducible deployments): + +```bash +docker pull ghcr.io/foundry-rs/foundry:v1.4.0 +DIGEST=$(docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 --format '{{ .Manifest.Digest }}') +cosign verify "ghcr.io/foundry-rs/foundry@${DIGEST}" \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` diff --git a/benches/LATEST.md b/benches/LATEST.md index 238a691229389..cb75f8d68780b 100644 --- a/benches/LATEST.md +++ b/benches/LATEST.md @@ -1,74 +1,108 @@ -# Foundry Benchmark Results +# 📊 Foundry Benchmark Results -**Date**: 2026-04-24 23:10:24 +**Generated at**: 2026-05-02 21:53:46 UTC -## Repositories Tested +## Forge Test + +### Repositories Tested 1. [ithacaxyz/account](https://github.com/ithacaxyz/account) -2. [Vectorized/solady](https://github.com/Vectorized/solady) -3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) 4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) -5. [aave/aave-v4](https://github.com/aave/aave-v4) - -## Foundry Versions +### Foundry Versions - **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) -- **nightly**: forge Version: 1.6.0-nightly (a249f5c 2026-04-24) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) -## Forge Test - -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| vectorized-solady | 1.46 s | 1.38 s | -| aave-aave-v4 | 4m 14.2s | 3m 29.1s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.78 s | 0.965 s | +| vectorized-solady | 0.995 s | 0.645 s | +| uniswap-v4-core | 5.97 s | 1.51 s | +| sparkdotfi-spark-psm | 19.98 s | 10.20 s | ## Forge Fuzz Test -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | -------- | -| ithacaxyz-account | 2.81 s | 1.59 s | -| vectorized-solady | 1.40 s | 1.34 s | -| Uniswap-v4-core | 3.01 s | 2.87 s | -| sparkdotfi-spark-psm | 2.04 s | 1.87 s | -| aave-aave-v4 | 3m 46.0s | 3m 17.3s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.54 s | 0.923 s | +| vectorized-solady | 0.929 s | 0.617 s | +| uniswap-v4-core | 6.44 s | 1.40 s | +| sparkdotfi-spark-psm | 2.25 s | 2.03 s | ## Forge Test (Isolated) -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| Uniswap-v4-core | 3.50 s | 3.48 s. | -| aave-aave-v4 | 4m 14.0s | 3m 53.4s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 3.05 s | 1.02 s | +| vectorized-solady | 0.871 s | 0.741 s | +| uniswap-v4-core | 6.81 s | 1.68 s | +| sparkdotfi-spark-psm | 21.96 s | 11.26 s | + +## Forge Build -## Forge Build (No Cache) +### Repositories Tested -| Repository | v1.5.1 | nightly | -| -------------------- | -------- | -------- | -| ithacaxyz-account | 26.06 s | 26.61 s | -| vectorized-solady | 14.20 s | 14.26 s | -| Uniswap-v4-core | 2m 1.3s | 2m 5.0s | -| sparkdotfi-spark-psm | 15.16 s | 15.30 s | -| aave-aave-v4 | 3m 37.0s | 3m 35.1s | +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +### No Cache + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 34.58 s | 33.29 s | +| vectorized-solady | 14.40 s | 14.41 s | +| uniswap-v4-core | 2m 17.6s | 2m 17.7s | +| sparkdotfi-spark-psm | 12.62 s | 12.61 s | -## Forge Build (With Cache) +### With Cache -| Repository | v1.5.1 | nightly | -| -------------------- | ------- | ------- | -| ithacaxyz-account | 0.167 s | 0.201 s | -| vectorized-solady | 0.099 s | 0.098 s | -| Uniswap-v4-core | 0.139 s | 0.140 s | -| sparkdotfi-spark-psm | 0.168 s | 0.173 s | -| aave-aave-v4 | 0.370 s | 0.357 s | +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 0.083 s | 0.089 s | +| vectorized-solady | 0.062 s | 0.064 s | +| uniswap-v4-core | 0.071 s | 0.074 s | +| sparkdotfi-spark-psm | 0.066 s | 0.068 s | ## Forge Coverage -| Repository | v1.5.1 | nightly | -| -------------------- | --------- | ---------- | -| Uniswap-v4-core | 1m 13.9s | 1m 10.3s | -| sparkdotfi-spark-psm | 2m 54.7s | 2m 50.0s | -| aave-aave-v4 | 11m 20.8s | 10m 58.7s | +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [uniswap/v4-core](https://github.com/uniswap/v4-core) +3. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 29.35 s | 18.69 s | +| uniswap-v4-core | 1m 26.8s | 1m 4.1s | +| sparkdotfi-spark-psm | 2m 1.6s | 1m 28.4s | ## System Information -- **OS**: macos -- **CPU**: 12 -- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) \ No newline at end of file + +- **OS**: linux +- **CPU**: 32 +- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) diff --git a/benches/src/main.rs b/benches/src/main.rs index 60e815cecb0ec..8d7134b1c25bc 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -39,9 +39,15 @@ struct Cli { #[clap(long, default_value = ".")] output_dir: PathBuf, - /// Name of the output file (default: LATEST.md) - #[clap(long, default_value = "LATEST.md")] - output_file: String, + /// Name of the output file. Defaults to LATEST.md unless --json-output is set + /// without this flag, in which case no Markdown is written. + #[clap(long)] + output_file: Option, + + /// Filename for a flat JSON summary (benchmark/repo -> mean_seconds). + /// Resolved relative to --output-dir. Used by the nightly regression comparison script. + #[clap(long)] + json_output: Option, /// Run only specific benchmarks (comma-separated: /// forge_test,forge_build_no_cache,forge_build_with_cache,forge_fuzz_test,forge_coverage) @@ -216,12 +222,28 @@ fn main() -> Result<()> { } } - // Generate markdown report - sh_println!("📝 Generating report..."); - let markdown = results.generate_markdown(&versions, &repos); - let output_path = cli.output_dir.join(cli.output_file); - fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; - sh_println!("✅ Report written to: {}", output_path.display()); + // Write Markdown report unless --json-output is set without an explicit --output-file. + let md_filename = match cli.output_file { + Some(f) => Some(f), + None if cli.json_output.is_none() => Some("LATEST.md".to_string()), + None => None, + }; + if let Some(filename) = md_filename { + sh_println!("📝 Generating report..."); + let markdown = results.generate_markdown(&versions, &repos); + let output_path = cli.output_dir.join(filename); + fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; + sh_println!("✅ Report written to: {}", output_path.display()); + } + + if let Some(json_filename) = cli.json_output { + let summary = results.generate_json_summary(&versions); + let json = + serde_json::to_string_pretty(&summary).wrap_err("Failed to serialize JSON summary")?; + let json_path = cli.output_dir.join(json_filename); + fs::write(&json_path, json).wrap_err("Failed to write JSON summary")?; + sh_println!("✅ JSON summary written to: {}", json_path.display()); + } Ok(()) } diff --git a/benches/src/results.rs b/benches/src/results.rs index 447e8ed2766b4..e7d57250fc9a1 100644 --- a/benches/src/results.rs +++ b/benches/src/results.rs @@ -66,6 +66,25 @@ impl BenchmarkResults { self.version_details.insert(version.to_string(), details); } + /// Generate a flat JSON summary mapping `"benchmark/repo" -> mean_seconds`. + /// + /// Used by the nightly regression comparison script. + pub fn generate_json_summary(&self, versions: &[String]) -> HashMap { + let mut summary = HashMap::new(); + for (benchmark_name, version_data) in &self.data { + for version in versions { + if let Some(repo_data) = version_data.get(version) { + for (repo_name, result) in repo_data { + let key = format!("{benchmark_name}/{repo_name}"); + let rounded = (result.mean * 10_000.0).round() / 10_000.0; + summary.insert(key, rounded); + } + } + } + } + summary + } + pub fn generate_markdown(&self, versions: &[String], repos: &[RepoConfig]) -> String { let mut output = String::new(); diff --git a/benchmark.sh b/benchmark.sh index 2faffa93dfa1d..ac6159099069b 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,36 +1,52 @@ #!/bin/bash -versions="v1.3.6,v1.4.0-rc1" +versions="v1.5.1,v1.7.0" # Repositories -export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" -export SOLADY_REPO="Vectorized/solady:v0.1.22" -export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" -export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" +ITHACA_ACCOUNT="ithacaxyz/account:v0.5.7" +SOLADY_REPO="vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test'" +AAVE_V4="aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" +UNISWAP_V4_CORE="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc TickMathTestTest" +SPARK_PSM="sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting" -# Benches -echo "===========FORGE TEST AND BUILD BENCHMARKS===========" +SOLADY_ISOLATE="vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest'" +ITHACA_ISOLATE="ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_test,forge_fuzz_test,forge_build_no_cache,forge_build_with_cache \ - --output-dir ./benches/results \ - --output-file TEST_BUILD.md +SOLADY_BUILD="vectorized/solady:v0.1.26" +UNISWAP_BUILD="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75" +SPARK_PSM_BUILD="sparkdotfi/spark-psm:v1.0.0" -echo "===========FORGE COVERAGE BENCHMARKS===========" +# Benches +echo "===========FORGE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM \ - --benchmarks forge_coverage \ - --output-dir ./benches/results \ - --output-file COVERAGE.md +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_REPO,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ + --benchmarks forge_test,forge_fuzz_test \ + --output-dir ./benches \ + --output-file forge_test_bench.md echo "===========FORGE ISOLATE TEST BENCHMARKS===========" -foundry-bench --versions $versions \ - --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ISOLATE,$SOLADY_ISOLATE,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ --benchmarks forge_isolate_test \ - --output-dir ./benches/results \ - --output-file ISOLATE_TEST.md + --output-dir ./benches \ + --output-file forge_isolate_test_bench.md + +echo "===========FORGE BUILD BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_BUILD,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --output-dir ./benches \ + --output-file forge_build_bench.md + +echo "===========FORGE COVERAGE BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_coverage \ + --output-dir ./benches \ + --output-file forge_coverage_bench.md echo "===========BENCHMARKS COMPLETED===========" diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index b664266450d07..d6404b21e2e8e 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -20,15 +20,15 @@ required-features = ["cli"] [dependencies] # foundry internal -anvil-core = { path = "core" } +anvil-core = { path = "core", default-features = false } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } -foundry-cli.workspace = true +foundry-cli = { workspace = true, optional = true } foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } tempo-chainspec.workspace = true tempo-primitives.workspace = true tempo-precompiles.workspace = true @@ -37,7 +37,7 @@ tempo-revm.workspace = true # alloy alloy-evm = { workspace = true, features = ["call-util"] } -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } @@ -63,7 +63,8 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde"] } +op-alloy-consensus = { workspace = true, features = ["serde"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } # revm revm = { workspace = true, features = [ @@ -73,7 +74,7 @@ revm = { workspace = true, features = [ "c-kzg", ] } revm-inspectors.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } # axum related axum.workspace = true @@ -120,17 +121,28 @@ reqwest.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } -op-alloy-rpc-types.workspace = true tempo-alloy.workspace = true [features] -default = ["cli", "jemalloc", "asm-keccak"] +default = ["cli", "jemalloc", "asm-keccak", "optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "anvil-core/optimism", + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli?/optimism", +] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] -jemalloc = ["foundry-cli/jemalloc"] -mimalloc = ["foundry-cli/mimalloc"] -tracy-allocator = ["foundry-cli/tracy-allocator"] +jemalloc = ["foundry-cli?/jemalloc"] +mimalloc = ["foundry-cli?/mimalloc"] +tracy-allocator = ["foundry-cli?/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ + "dep:foundry-cli", "clap", "clap_complete", "dep:fdlimit", diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index cf4b952ecfaa3..8456413a78b1f 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true -foundry-primitives.workspace = true +foundry-primitives = { workspace = true, default-features = false } revm = { workspace = true, default-features = false, features = [ "std", "serde", @@ -39,3 +39,11 @@ bytes.workspace = true # misc rand.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index fb75529b82908..4fd2aabb82a8b 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -12,7 +12,9 @@ use clap::Parser; use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; -use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; +#[cfg(feature = "optimism")] +use foundry_evm::hardfork::OpHardfork; +use foundry_evm::hardfork::{EthereumHardfork, FoundryHardfork}; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::FoundryReceiptEnvelope; use futures::FutureExt; @@ -240,15 +242,7 @@ impl NodeArgs { } let hardfork = match &self.hardfork { - Some(hf) => { - if self.evm.networks.is_optimism() { - Some(OpHardfork::from_str(hf)?.into()) - } else if self.evm.networks.is_tempo() { - Some(TempoHardfork::from_str(hf)?.into()) - } else { - Some(EthereumHardfork::from_str(hf)?.into()) - } - } + Some(hf) => Some(parse_hardfork(hf, &self.evm.networks)?), None => None, }; @@ -849,6 +843,19 @@ impl FromStr for ForkUrl { } } +/// Parses a hardfork string against the active network configuration. +fn parse_hardfork(hf: &str, networks: &NetworkConfigs) -> eyre::Result { + #[cfg(feature = "optimism")] + if networks.is_optimism() { + return Ok(OpHardfork::from_str(hf)?.into()); + } + if networks.is_tempo() { + Ok(TempoHardfork::from_str(hf)?.into()) + } else { + Ok(EthereumHardfork::from_str(hf)?.into()) + } +} + /// Clap's value parser for genesis. Loads a genesis.json file. fn read_genesis_file(path: &str) -> Result { foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 23cd6e61bc076..7c91590c071fa 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -37,7 +37,7 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - hardfork::{FoundryHardfork, OpHardfork}, + hardfork::FoundryHardfork, utils::{ apply_chain_and_block_specific_env_changes, block_env_from_header, get_blob_base_fee_update_fraction, @@ -577,8 +577,9 @@ impl NodeConfig { if let Some(hardfork) = self.hardfork { return hardfork; } + #[cfg(feature = "optimism")] if self.networks.is_optimism() { - return OpHardfork::default().into(); + return foundry_evm::hardforks::OpHardfork::default().into(); } if self.networks.is_tempo() { return TempoHardfork::default().into(); @@ -1079,6 +1080,7 @@ impl NodeConfig { } /// Enable Optimism network features. + #[cfg(feature = "optimism")] #[must_use] pub fn with_optimism(mut self) -> Self { self.networks = NetworkConfigs::with_optimism(); diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 78cce938318c1..4900c18eebd82 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1882,6 +1882,7 @@ impl EthApi { fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { match typed_tx { + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { @@ -2210,9 +2211,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; - debug_assert!(requires != provides); + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -2288,11 +2293,10 @@ impl EthApi { let priority = self.transaction_priority(&pending_transaction.transaction); // Tempo txs use a 2D nonce system — no sequential ordering by account nonce. - let (requires, provides) = if let FoundryTxEnvelope::Tempo(aa_tx) = - pending_transaction.transaction.as_ref() - && !aa_tx.tx().nonce_key.is_zero() + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) { - (vec![], vec![pending_transaction.hash().to_vec()]) + (requires, provides) } else { let on_chain_nonce = self.backend.current_nonce(from).await?; let nonce = pending_transaction.transaction.nonce(); @@ -3192,8 +3196,13 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -3549,6 +3558,7 @@ impl EthApi { requires: Vec, provides: Vec, ) -> Result { + debug_assert!(requires != provides); let from = *pending_transaction.sender(); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = @@ -3565,7 +3575,9 @@ impl EthApi { FoundryTxEnvelope::Eip1559(_) => self.backend.ensure_eip1559_active(), FoundryTxEnvelope::Eip4844(_) => self.backend.ensure_eip4844_active(), FoundryTxEnvelope::Eip7702(_) => self.backend.ensure_eip7702_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => self.backend.ensure_op_deposits_active(), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => Err(BlockchainError::InvalidTransactionRequest( "not implemented for post-exec tx".to_string(), )), @@ -3634,6 +3646,20 @@ fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> V if on_chain_nonce <= prev_nonce { vec![to_marker(prev_nonce, from)] } else { Vec::new() } } +fn tempo_parallel_nonce_markers( + pending_transaction: &PendingTransaction, +) -> Option<(Vec, Vec)> { + // Tempo txs with non-zero nonce_key use a 2D nonce system and should not + // be sequenced by account nonce markers. + if let FoundryTxEnvelope::Tempo(aa_tx) = pending_transaction.transaction.as_ref() + && !aa_tx.tx().nonce_key.is_zero() + { + Some((vec![], vec![pending_transaction.hash().to_vec()])) + } else { + None + } +} + fn convert_transact_out(out: &Option) -> Bytes { match out { None => Default::default(), diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 614f409c3eb65..c41937055fdd4 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -67,9 +67,11 @@ impl ReceiptBuilder for FoundryReceiptBuilder { FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { unreachable!("deposit receipts are built in commit_transaction") } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt), FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), } @@ -85,7 +87,7 @@ pub struct AnvilTxResult { pub sender: Address, } -impl TxResult for AnvilTxResult { +impl TxResult for AnvilTxResult { type HaltReason = H; fn result(&self) -> &ResultAndState { @@ -217,12 +219,10 @@ where }) } - fn commit_transaction( - &mut self, - output: Self::Result, - ) -> Result { + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { let AnvilTxResult { inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] sender, } = output; @@ -237,6 +237,7 @@ where self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); } + #[cfg(feature = "optimism")] let receipt = if tx_type == FoundryTxType::Deposit { let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); let receipt = alloy_consensus::Receipt { @@ -262,11 +263,19 @@ where cumulative_gas_used: self.gas_used, }) }; + #[cfg(not(feature = "optimism"))] + let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }); self.receipts.push(receipt); self.evm.db_mut().commit(state); - Ok(GasOutput::new(gas_used)) + GasOutput::new(gas_used) } fn finish( @@ -429,7 +438,7 @@ where let exec_result = result.result().result.clone(); let gas_used = result.result().result.tx_gas_used(); - executor.commit_transaction(result).expect("commit failed"); + executor.commit_transaction(result); let traces = executor.evm_mut().inspector_mut().finish_transaction(inspector_config); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index f404031445611..19f39f3d368de 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -57,6 +57,7 @@ use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction, }; +#[cfg(feature = "optimism")] use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; use alloy_primitives::{ Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, @@ -108,20 +109,60 @@ use foundry_evm::{ }, }; use foundry_evm_networks::NetworkConfigs; +#[cfg(feature = "optimism")] +use foundry_primitives::get_deposit_tx_parts; use foundry_primitives::{ FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, - FoundryTxReceipt, get_deposit_tx_parts, + FoundryTxReceipt, }; use futures::channel::mpsc::{UnboundedSender, unbounded}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait}; -use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +#[cfg(feature = "optimism")] +use op_revm::{OpSpecId, OpTransaction, transaction::deposit::DepositTransactionParts}; + +/// Side-channel container for OP-specific deposit info produced by +/// [`Backend::build_call_env`] and consumed by the OP transact path. +/// +/// When the `optimism` feature is enabled, this is an alias for +/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized +/// stand-in so the eth/tempo dispatch chain still type-checks. +#[cfg(feature = "optimism")] +type OpCallDepositInfo = DepositTransactionParts; +#[cfg(not(feature = "optimism"))] +#[derive(Default, Clone, Debug)] +struct OpCallDepositInfo; + +/// Marker trait that abstracts over the per-network inspector trait bounds +/// required by the in-memory backend. The OP bound is only included when the +/// `optimism` feature is enabled. +#[cfg(feature = "optimism")] +pub trait BackendInspector: + Inspector> + Inspector> + Inspector> +{ +} +#[cfg(feature = "optimism")] +impl BackendInspector for T where + T: Inspector> + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +pub trait BackendInspector: + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +impl BackendInspector for T where + T: Inspector> + Inspector> +{ +} use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ DatabaseCommit, Inspector, context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, - result::{EVMError, ExecutionResult, HaltReason, Output, ResultAndState}, + result::{ExecutionResult, HaltReason, Output, ResultAndState}, }, database::{CacheDB, DbAccount, WrapDatabaseRef}, interpreter::InstructionResult, @@ -157,6 +198,8 @@ pub mod cache; pub mod fork_db; pub mod in_memory_db; pub mod inspector; +#[cfg(feature = "optimism")] +pub mod optimism; pub mod state; pub mod storage; @@ -419,6 +462,11 @@ impl Backend { self.genesis.timestamp } + /// Returns the configured genesis block number. + pub const fn genesis_number(&self) -> u64 { + self.genesis.number + } + /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { Ok(self.get_account(address).await?.balance) @@ -490,12 +538,21 @@ impl Backend { } /// Returns true if op-stack deposits are active - pub fn is_optimism(&self) -> bool { + #[cfg(feature = "optimism")] + pub const fn is_optimism(&self) -> bool { self.networks.is_optimism() } + /// Returns true if op-stack deposits are active. + /// + /// Always `false` when built without the `optimism` feature. + #[cfg(not(feature = "optimism"))] + pub const fn is_optimism(&self) -> bool { + false + } + /// Returns true if Tempo network mode is active - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { self.networks.is_tempo() } @@ -589,7 +646,8 @@ impl Backend { } /// Returns an error if op-stack deposits are not active - pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { + #[cfg(feature = "optimism")] + pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { return Ok(()); } @@ -597,7 +655,7 @@ impl Backend { } /// Returns an error if Tempo transactions are not active - pub fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { + pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { if self.is_tempo() { return Ok(()); } @@ -1139,58 +1197,53 @@ impl Backend { db: &'db DB, evm_env: &EvmEnv, inspector: &mut I, - tx_env: OpTransaction, + tx_env: TxEnv, + op_deposit: OpCallDepositInfo, ) -> Result, BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_env = EvmEnv::new( - evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), - evm_env.block_env.clone(), - ); - let mut evm = OpEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - op_env, - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { - EVMError::Database(db) => EVMError::Database(db), - EVMError::Header(h) => EVMError::Header(h), - EVMError::Custom(s) => EVMError::Custom(s), - EVMError::CustomAny(err) => EVMError::CustomAny(err), - EVMError::Transaction(t) => EVMError::Transaction(t), - })?; - Ok(ResultAndState { - result: result.result.map_haltreason(|h| match h { - OpHaltReason::Base(eth) => eth, - _ => HaltReason::PrecompileError, - }), - state: result.state, - }) - } else if self.is_tempo() { - self.transact_tempo_with_inspector_ref( - db, - evm_env, - inspector, - TempoTxEnv::from(tx_env.base), - ) + let op_tx = OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() }; + return self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx); + } + // `op_deposit` only matters on the OP path; eth/tempo ignore it. + let _ = op_deposit; + if self.is_tempo() { + self.transact_tempo_with_inspector_ref(db, evm_env, inspector, TempoTxEnv::from(tx_env)) } else { - let mut evm = EthEvmFactory::default().create_evm_with_inspector( - WrapDatabaseRef(db), - evm_env.clone(), - inspector, - ); - self.inject_precompiles(evm.precompiles_mut()); - Ok(evm.transact(tx_env.base)?) + self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env) } } + /// Eth path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an Ethereum EVM, injects precompiles, and transacts with a + /// plain [`TxEnv`]. + fn transact_eth_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TxEnv, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let mut evm = EthEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + evm_env.clone(), + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + Ok(evm.transact(tx_env)?) + } + /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`]. fn transact_envelope_with_inspector_ref<'db, I, DB>( @@ -1203,9 +1256,7 @@ impl Backend { ) -> Result<(ResultAndState, TxEnv), BlockchainError> where DB: DatabaseRef + ?Sized, - I: Inspector>> - + Inspector>> - + Inspector>>, + I: BackendInspector>, WrapDatabaseRef<&'db DB>: Database, { if tx.is_tempo() { @@ -1213,14 +1264,21 @@ impl Backend { FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); let base = tx_env.inner.clone(); let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) - } else { - let tx_env: OpTransaction = + return Ok((result, base)); + } + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_tx: OpTransaction = FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); - let base = tx_env.base.clone(); - let result = self.transact_with_inspector_ref(db, evm_env, inspector, tx_env)?; - Ok((result, base)) + let base = op_tx.base.clone(); + let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?; + return Ok((result, base)); } + let tx_env: TxEnv = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = tx_env.clone(); + let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?; + Ok((result, base)) } /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`]. @@ -1298,6 +1356,7 @@ impl Backend { }}; } + #[cfg(feature = "optimism")] if self.is_optimism() { let op_env = EvmEnv::new( evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), @@ -1305,8 +1364,10 @@ impl Backend { ); let mut evm = OpEvmFactory::::default().create_evm_with_inspector(db, op_env, inspector); - run!(evm) - } else if self.is_tempo() { + return run!(evm); + } + + if self.is_tempo() { let hardfork = TempoHardfork::from(self.hardfork); let tempo_env = EvmEnv::new( evm_env @@ -1338,7 +1399,7 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> (EvmEnv, OpTransaction) { + ) -> (EvmEnv, TxEnv, OpCallDepositInfo) { let tx_type = request.minimal_tx_type() as u8; let WithOtherFields:: { @@ -1391,7 +1452,7 @@ impl Backend { let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); - let mut base = TxEnv { + let mut tx_env = TxEnv { caller, gas_limit, gas_price, @@ -1413,11 +1474,10 @@ impl Backend { blob_hashes, ..Default::default() }; - base.set_signed_authorization(authorization_list.unwrap_or_default()); - let mut tx_env = OpTransaction { base, ..Default::default() }; + tx_env.set_signed_authorization(authorization_list.unwrap_or_default()); if let Some(nonce) = nonce { - tx_env.base.nonce = nonce; + tx_env.nonce = nonce; } if evm_env.block_env.basefee == 0 { @@ -1427,13 +1487,22 @@ impl Backend { } // Deposit transaction? (only valid when op-stack deposits are active) - if self.ensure_op_deposits_active().is_ok() + #[cfg(feature = "optimism")] + let op_deposit = if self.ensure_op_deposits_active().is_ok() && let Ok(deposit) = get_deposit_tx_parts(&other) { - tx_env.deposit = deposit; - } + deposit + } else { + OpCallDepositInfo::default() + }; + #[cfg(not(feature = "optimism"))] + let op_deposit = { + // `other` carries OP-only deposit fields; consumed only when feature is enabled. + let _ = &other; + OpCallDepositInfo + }; - (evm_env, tx_env) + (evm_env, tx_env, op_deposit) } pub fn call_with_state( @@ -1467,13 +1536,13 @@ impl Backend { (fee_token, nonce_key, valid_before, valid_after) }); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state } = if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides { use tempo_primitives::transaction::Call; - let base = tx_env.base; + let base = tx_env; let mut tempo_tx = TempoTxEnv::from(base.clone()); tempo_tx.fee_token = fee_token; @@ -1495,7 +1564,13 @@ impl Backend { } self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)? } else { - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)? + self.transact_with_inspector_ref( + state, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )? }; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -1518,9 +1593,9 @@ impl Backend { let mut inspector = AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block_env); + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?; + self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env, op_deposit)?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) @@ -2912,7 +2987,7 @@ where TracingInspectorConfig::from_geth_call_config(&call_config), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let ResultAndState { result, state: _ } = self .transact_with_inspector_ref( @@ -2920,6 +2995,7 @@ where &evm_env, &mut inspector, tx_env, + op_deposit, )?; inspector.print_logs(); @@ -2945,13 +3021,14 @@ where ), ); - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env, + op_deposit, )?; Ok(inspector @@ -2973,22 +3050,22 @@ where } #[cfg(feature = "js-tracer")] GethDebugTracerType::JsTracer(code) => { - use alloy_evm::IntoTxEnv; let config = tracer_config.into_json(); let mut inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; - let (evm_env, tx_env) = + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block.clone()); let result = self.transact_with_inspector_ref( &cache_db, &evm_env, &mut inspector, tx_env.clone(), + op_deposit, )?; let res = inspector - .json_result(result, &OpTx(tx_env).into_tx_env(), &block, &cache_db) + .json_result(result, &tx_env, &block, &cache_db) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(GethTrace::JS(res)) @@ -3001,9 +3078,14 @@ where .build_inspector() .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); - let (evm_env, tx_env) = self.build_call_env(request, fee_details, block); - let ResultAndState { result, state: _ } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); + let ResultAndState { result, state: _ } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); @@ -3187,10 +3269,7 @@ where f: F, ) -> Result where - for<'a> I: Inspector>>>> - + Inspector>>>> - + Inspector>>>> - + 'a, + for<'a> I: BackendInspector>>> + 'a, for<'a> F: FnOnce(ResultAndState, CacheDB>, I, TxEnv, EvmEnv) -> T, { @@ -3965,7 +4044,7 @@ impl Backend { )? .or_zero_fees(); - let (mut evm_env, tx_env) = self.build_call_env( + let (mut evm_env, tx_env, op_deposit) = self.build_call_env( WithOtherFields::new(request.clone()), fee_details, block_env.clone(), @@ -3986,8 +4065,13 @@ impl Backend { inspector = inspector.with_transfers(); } trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); - let ResultAndState { result, state } = - self.transact_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?; + let ResultAndState { result, state } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; trace!(target: "backend", ?result, ?request, "simulate call"); inspector.print_logs(); @@ -4359,7 +4443,10 @@ where } // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system) + #[cfg(feature = "optimism")] let is_deposit_tx = pending.transaction.as_ref().is_deposit(); + #[cfg(not(feature = "optimism"))] + let is_deposit_tx = false; let is_tempo_tx = pending.transaction.as_ref().is_tempo(); let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx { @@ -4475,6 +4562,7 @@ where ); let value = tx.value(); match tx.as_ref() { + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(deposit_tx) => { // Deposit transactions // https://specs.optimism.io/protocol/deposits.html#execution @@ -4538,6 +4626,7 @@ pub fn transaction_build( info: Option, base_fee: Option, ) -> AnyRpcTransaction { + #[cfg(feature = "optimism")] if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() { let dep_tx = deposit_tx; diff --git a/crates/anvil/src/eth/backend/mem/optimism.rs b/crates/anvil/src/eth/backend/mem/optimism.rs new file mode 100644 index 0000000000000..e9a94cc254fb7 --- /dev/null +++ b/crates/anvil/src/eth/backend/mem/optimism.rs @@ -0,0 +1,61 @@ +//! Optimism-specific transact helpers for the in-memory backend. + +use super::Backend; +use crate::eth::error::BlockchainError; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use alloy_network::Network; +use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; +use foundry_evm::backend::DatabaseError; +use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +use revm::{ + DatabaseRef, Inspector, + context::{ + TxEnv, + result::{EVMError, HaltReason, ResultAndState}, + }, + database_interface::WrapDatabaseRef, +}; + +impl Backend { + /// Optimism path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an OP EVM, injects precompiles, transacts, and maps the + /// OP-specific halt reason back to the shared [`HaltReason`]. + pub(super) fn transact_op_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: OpTransaction, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let op_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), + evm_env.block_env.clone(), + ); + let mut evm = OpEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + op_env, + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::CustomAny(err) => EVMError::CustomAny(err), + EVMError::Transaction(t) => EVMError::Transaction(t), + })?; + Ok(ResultAndState { + result: result.result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }), + state: result.state, + }) + } +} diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error/mod.rs similarity index 91% rename from crates/anvil/src/eth/error.rs rename to crates/anvil/src/eth/error/mod.rs index 3b2ada43d7731..482df681b184a 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error/mod.rs @@ -12,7 +12,6 @@ use anvil_rpc::{ response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; -use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, @@ -21,6 +20,9 @@ use serde::Serialize; use tempo_revm::TempoInvalidTransaction; use tokio::time::Duration; +#[cfg(feature = "optimism")] +mod optimism; + pub(crate) type Result = std::result::Result; #[derive(Debug, thiserror::Error)] @@ -163,51 +165,6 @@ where } } -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => match err { - OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), - OpTransactionError::DepositSystemTxPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::HaltedDepositPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), - }, - EVMError::Header(err) => match err { - InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, - InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, - }, - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - -impl From> for BlockchainError -where - T: Into, -{ - fn from(err: EVMError) -> Self { - match err { - EVMError::Transaction(err) => { - let op_err: OpTransactionError = err.0; - EVMError::::Transaction(op_err).into() - } - EVMError::Header(err) => EVMError::::Header(err).into(), - EVMError::Database(err) => err.into(), - EVMError::Custom(err) => Self::Message(err), - EVMError::CustomAny(err) => Self::Message(err.to_string()), - } - } -} - impl From> for BlockchainError where T: Into, @@ -451,16 +408,6 @@ impl From for InvalidTransactionError { } } -impl From for InvalidTransactionError { - fn from(value: OpTransactionError) -> Self { - match value { - OpTransactionError::Base(err) => err.into(), - OpTransactionError::DepositSystemTxPostRegolith - | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, - OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, - } - } -} /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -577,9 +524,13 @@ impl ToRpcResponseResult for Result { err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), } } - err @ BlockchainError::EvmError(_) => { - RpcError::internal_error_with(err.to_string()) - } + err @ BlockchainError::EvmError(_) => RpcError { + // VM halts are execution failures, not JSON-RPC server faults. REVERT has a + // dedicated code/data path above; other halts, such as invalid opcode, do not. + code: ErrorCode::TransactionRejected, + message: err.to_string().into(), + data: None, + }, err @ BlockchainError::EvmOverrideError(_) => { RpcError::invalid_params(err.to_string()) } diff --git a/crates/anvil/src/eth/error/optimism.rs b/crates/anvil/src/eth/error/optimism.rs new file mode 100644 index 0000000000000..1207fde30a72a --- /dev/null +++ b/crates/anvil/src/eth/error/optimism.rs @@ -0,0 +1,62 @@ +//! Optimism-specific error conversions for [`BlockchainError`] and +//! [`InvalidTransactionError`]. + +use super::{BlockchainError, InvalidTransactionError}; +use op_revm::OpTransactionError; +use revm::context_interface::result::{EVMError, InvalidHeader}; + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => { + let op_err: OpTransactionError = err.0; + EVMError::::Transaction(op_err).into() + } + EVMError::Header(err) => EVMError::::Header(err).into(), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From for InvalidTransactionError { + fn from(value: OpTransactionError) -> Self { + match value { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::DepositSystemTxPostRegolith + | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, + } + } +} diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 4da86933020ae..2ccf65ba721f5 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -155,9 +155,12 @@ impl EthApi { let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block - // considering only post-fork + // considering only post-fork (or post-genesis in non-fork mode) let from = if block_number == 0 { best } else { block_number - 1 }; - let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let to = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let first_page = from >= best; let mut last_page = false; @@ -198,8 +201,11 @@ impl EthApi { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); - // we go from the first post-fork block, up to the tip - let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + // we go from the first post-fork (or post-genesis) block, up to the tip + let first_block = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; @@ -248,7 +254,10 @@ impl EthApi { ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); - let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); + let from = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let to = self.backend.best_number(); for n in (from..=to).rev() { diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index df65822e1eab3..5280987483dd7 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -123,10 +123,10 @@ impl PoolTransaction { impl fmt::Debug for PoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; + write!(fmt, "hash: {:?}, ", self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; write!(fmt, "provides: [{}], ", hex_fmt_many(self.provides.iter()))?; - write!(fmt, "raw tx: {:?}", &self.pending_transaction)?; + write!(fmt, "raw tx: {:?}", self.pending_transaction)?; write!(fmt, "}}")?; Ok(()) } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 3fdf6192c4537..d1736c3093056 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,5 +1,7 @@ use crate::eth::error::BlockchainError; -use alloy_consensus::{Sealed, SignableTransaction}; +#[cfg(feature = "optimism")] +use alloy_consensus::Sealed; +use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::{Network, TxSignerSync}; use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; @@ -130,9 +132,11 @@ impl Signer for DevSigner { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip4844(t.into_signed(sig)) } + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be signed") } @@ -156,7 +160,9 @@ pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)), FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(_) => { unreachable!("op post-exec txs should not be impersonated") } diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm/mod.rs similarity index 64% rename from crates/anvil/src/evm.rs rename to crates/anvil/src/evm/mod.rs index d1b40ba56ebbd..85e43d371b097 100644 --- a/crates/anvil/src/evm.rs +++ b/crates/anvil/src/evm/mod.rs @@ -2,6 +2,9 @@ use alloy_evm::precompiles::DynPrecompile; use alloy_primitives::Address; use std::fmt::Debug; +#[cfg(feature = "optimism")] +mod optimism; + /// Object-safe trait that enables injecting extra precompiles when using /// `anvil` as a library. pub trait PrecompileFactory: Send + Sync + Unpin + Debug { @@ -15,14 +18,12 @@ mod tests { use crate::PrecompileFactory; use alloy_evm::{ - EthEvm, Evm, EvmEnv, EvmFactory, + EthEvm, Evm, eth::EthEvmContext, precompiles::{DynPrecompile, PrecompilesMap}, }; - use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; - use alloy_primitives::{Address, Bytes, TxKind, U256, address}; + use alloy_primitives::{Address, Bytes, TxKind, address}; use itertools::Itertools; - use op_revm::{OpSpecId, OpTransaction}; use revm::{ Journal, context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, @@ -35,20 +36,19 @@ mod tests { }; // A precompile activated in the `Prague` spec (BLS12-381 G2 map). - const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); + pub(super) const ETH_PRAGUE_PRECOMPILE: Address = + address!("0x0000000000000000000000000000000000000011"); // A precompile activated in the `Osaka` spec (EIP-7951). const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A precompile activated in the `Isthmus` spec. - const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - // A custom precompile address and payload for testing. - const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); - const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; + pub(super) const PRECOMPILE_ADDR: Address = + address!("0x0000000000000000000000000000000000000071"); + pub(super) const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; #[derive(Debug)] - struct CustomPrecompileFactory; + pub(super) struct CustomPrecompileFactory; impl PrecompileFactory for CustomPrecompileFactory { fn precompiles(&self) -> Vec<(Address, DynPrecompile)> { @@ -109,34 +109,6 @@ mod tests { (tx_env, eth_evm) } - /// Creates a new OP EVM instance. - fn create_op_evm( - _spec: SpecId, - op_spec: OpSpecId, - ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { - let tx = OpTx(OpTransaction:: { - base: TxEnv { - kind: TxKind::Call(PRECOMPILE_ADDR), - data: PAYLOAD.into(), - ..Default::default() - }, - ..Default::default() - }); - - let mut evm = OpEvmFactory::::default().create_evm_with_inspector( - EmptyDB::default(), - EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), - NoOpInspector, - ); - - if op_spec == OpSpecId::ISTHMUS { - evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); - evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); - } - - (tx, evm) - } - #[test] fn build_eth_evm_with_extra_precompiles_osaka_spec() { let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA); @@ -187,38 +159,4 @@ mod tests { assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } - - #[test] - fn build_op_evm_with_extra_precompiles_isthmus_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); - - assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_op_evm_with_extra_precompiles_bedrock_spec() { - let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); - - assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = evm.transact(tx).unwrap(); - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } } diff --git a/crates/anvil/src/evm/optimism.rs b/crates/anvil/src/evm/optimism.rs new file mode 100644 index 0000000000000..526375fec31ea --- /dev/null +++ b/crates/anvil/src/evm/optimism.rs @@ -0,0 +1,87 @@ +//! Optimism-specific EVM helpers. + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use super::super::tests::{ + CustomPrecompileFactory, ETH_PRAGUE_PRECOMPILE, PAYLOAD, PRECOMPILE_ADDR, + }; + use crate::PrecompileFactory; + use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; + use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; + use alloy_primitives::{Address, TxKind, U256, address}; + use itertools::Itertools; + use op_revm::{OpSpecId, OpTransaction}; + use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + inspector::NoOpInspector, + primitives::hardfork::SpecId, + }; + + // A precompile activated in the `Isthmus` spec. + const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + /// Creates a new OP EVM instance. + fn create_op_evm( + _spec: SpecId, + op_spec: OpSpecId, + ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { + let tx = OpTx(OpTransaction:: { + base: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + ..Default::default() + }); + + let mut evm = OpEvmFactory::::default().create_evm_with_inspector( + EmptyDB::default(), + EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), + NoOpInspector, + ); + + if op_spec == OpSpecId::ISTHMUS { + evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); + evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); + } + + (tx, evm) + } + + #[test] + fn build_op_evm_with_extra_precompiles_isthmus_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); + + assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_bedrock_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); + + assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } +} diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 26e587e8b5123..a661e9c765b26 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::{ error::{NodeError, NodeResult}, eth::{ diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index c4879e36d5240..84cc9b861bbbf 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -11,6 +11,7 @@ mod gas; mod genesis; mod ipc; mod logs; +#[cfg(feature = "optimism")] mod optimism; mod otterscan; mod proof; @@ -18,6 +19,7 @@ mod pubsub; mod revert; mod sign; mod simulate; +#[cfg(feature = "cmd")] mod state; mod tempo; mod traces; diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index ab85fc89abf80..a15454fa5593e 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -28,6 +28,38 @@ async fn test_deploy_reverting() { assert!(!receipt.inner.inner.status()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_invalid_opcode_rpc_error_code() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + // Deploy a contract whose runtime bytecode is the invalid opcode 0xfe. + let code = bytes!("60fe60005360016000f3"); + let tx = TransactionRequest::default().from(sender).with_deploy_code(code); + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract = receipt.contract_address.unwrap(); + + for (method, params) in [ + ("eth_call", serde_json::json!([{ "from": sender, "to": contract }, "latest"])), + ("eth_estimateGas", serde_json::json!([{ "from": sender, "to": contract }])), + ] { + let error = rpc_error(&handle.http_endpoint(), method, params).await; + assert_eq!(error["code"], serde_json::json!(-32003), "{error}"); + assert!(error.get("data").is_none(), "{error}"); + + let message = error["message"].as_str().unwrap(); + assert!(message.contains("EVM error InvalidFEOpcode"), "{error}"); + assert!(!message.contains("execution reverted"), "{error}"); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { sol!( @@ -124,3 +156,21 @@ async fn test_solc_revert_custom_errors() { let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); } + +async fn rpc_error(endpoint: &str, method: &str, params: serde_json::Value) -> serde_json::Value { + let response = reqwest::Client::new() + .post(endpoint) + .json(&serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": method, + "params": params, + })) + .send() + .await + .unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let body = response.json::().await.unwrap(); + body.get("error").cloned().unwrap_or_else(|| panic!("expected JSON-RPC error, got {body}")) +} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index dc5be77a244da..03be26a631a11 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -61,9 +61,9 @@ tempo-contracts.workspace = true tempo-primitives.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-flz.workspace = true -op-alloy-network.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-flz = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } chrono.workspace = true eyre.workspace = true @@ -100,7 +100,7 @@ anvil.workspace = true foundry-test-utils.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -109,3 +109,12 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "dep:op-alloy-flz", + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "foundry-common/optimism", + "foundry-evm-networks/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 6dc2ed6acf430..23fda25c40faa 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -29,6 +29,7 @@ use foundry_common::{ shell, stdin, }; use foundry_evm_networks::NetworkVariant; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; use std::time::Instant; use tempo_alloy::TempoNetwork; @@ -351,6 +352,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let output = if raw || fields.contains(&"raw".into()) { match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -569,6 +571,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { // Can use either --raw or specify raw as a field let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); let output = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; @@ -791,6 +794,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::DecodeTransaction { tx, network } => { let tx = stdin::unwrap_line(tx)?; let decoded_tx = match network { + #[cfg(feature = "optimism")] Some(NetworkVariant::Optimism) => { SimpleCast::decode_raw_transaction::(&tx)? } @@ -809,6 +813,9 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::Erc20Token { command } => command.run().await?, CastSubcommand::Tip20Token { command } => command.run().await?, CastSubcommand::Keychain { command } => command.run().await?, + CastSubcommand::Tempo { command } => command.run().await?, + CastSubcommand::VirtualAddress { command } => command.run().await?, + #[cfg(feature = "optimism")] CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index ae5c9668f522f..e25798086d512 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -9,7 +9,7 @@ use crate::{ }; use alloy_consensus::SignableTransaction; use alloy_eips::eip2718::Encodable2718; -use alloy_network::{EthereumWallet, NetworkTransactionBuilder}; +use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder}; use alloy_primitives::Address; use alloy_provider::Provider; use alloy_signer::Signer; @@ -17,7 +17,7 @@ use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use tempo_alloy::TempoNetwork; @@ -53,7 +53,7 @@ pub struct BatchMakeTxArgs { impl BatchMakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { calls, tx, eth, raw_unsigned, ethsign } = self; + let Self { calls, mut tx, eth, raw_unsigned, ethsign } = self; let has_nonce = tx.nonce.is_some(); if calls.is_empty() { @@ -63,6 +63,10 @@ impl BatchMakeTxArgs { let config = eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + // Resolve signer to detect keychain mode let (signer, tempo_access_key) = eth.wallet.maybe_signer().await?; @@ -92,14 +96,14 @@ impl BatchMakeTxArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as raw unsigned. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Set calls on the transaction builder.tx.calls = tempo_calls; @@ -117,6 +121,7 @@ impl BatchMakeTxArgs { let from = eth.wallet.from.unwrap_or(Address::ZERO); let (tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -125,6 +130,7 @@ impl BatchMakeTxArgs { if ethsign { let (tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; return Ok(()); @@ -137,7 +143,9 @@ impl BatchMakeTxArgs { }; let signed_tx = if let Some(ref access_key) = tempo_access_key { - let (tx, _) = tx_builder.build(access_key.wallet_address).await?; + let (tx, _) = + tx_builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let raw_tx = tx .sign_with_access_key( &provider, @@ -151,6 +159,7 @@ impl BatchMakeTxArgs { } else { tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?; let (tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; let envelope = tx.build(&EthereumWallet::new(signer)).await?; alloy_primitives::hex::encode(envelope.encoded_2718()) }; diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index ec7254b08e2f5..33128ae897898 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -9,14 +9,14 @@ use crate::{ cmd::send::{cast_send, cast_send_with_access_key}, tx::{self, CastTxBuilder, SendTxOpts}, }; -use alloy_network::EthereumWallet; +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{ opts::TransactionOpts, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::provider::ProviderBuilder; use std::time::Duration; @@ -50,7 +50,7 @@ pub struct BatchSendArgs { impl BatchSendArgs { pub async fn run(self) -> Result<()> { - let Self { calls, send_tx, tx, unlocked } = self; + let Self { calls, send_tx, mut tx, unlocked } = self; if calls.is_empty() { return Err(eyre!("No calls specified. Use --call to specify at least one call.")); @@ -59,6 +59,10 @@ impl BatchSendArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -93,14 +97,14 @@ impl BatchSendArgs { sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; - // Build transaction request with calls - let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; - - // Set key_id for access key transactions + // Preserve key_id for modes that do not call build_with_access_key, such as unlocked. if let Some(ref access_key) = tempo_access_key { - builder.tx.set_key_id(access_key.key_address); + tx.tempo.key_id = Some(access_key.key_address); } + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + // Access the inner tx and set calls builder.tx.calls = tempo_calls; @@ -116,6 +120,7 @@ impl BatchSendArgs { if unlocked { let (tx, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; cast_send( provider, tx, @@ -132,7 +137,12 @@ impl BatchSendArgs { }; if let Some(ref access_key) = tempo_access_key { - let (tx_request, _) = builder.build(access_key.wallet_address).await?; + let (tx_request, _) = + builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; cast_send_with_access_key( &provider, tx_request, @@ -146,6 +156,10 @@ impl BatchSendArgs { } else { tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?; let (tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() .wallet(wallet) diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 3637f166a53df..63ea17f707e03 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -33,10 +33,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, }, executors::TracingExecutor, opts::EvmOpts, @@ -222,18 +224,19 @@ impl CallArgs { return self.run_curl().await; } if self.tx.tempo.is_tempo() { - self.run_with_network::().await - } else { - let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); - let mut evm_opts = figment.extract::()?; - evm_opts.infer_network_from_fork().await; + return self.run_with_network::().await; + } - if evm_opts.networks.is_optimism() { - self.run_with_network::().await - } else { - self.run_with_network::().await - } + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + evm_opts.infer_network_from_fork().await; + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_network::().await; } + + self.run_with_network::().await } pub async fn run_with_network(self) -> Result<()> diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs index 8b7d80786dfad..897a01c39202a 100644 --- a/crates/cast/src/cmd/keychain.rs +++ b/crates/cast/src/cmd/keychain.rs @@ -1,9 +1,12 @@ use alloy_ens::NameOrAddress; -use alloy_network::EthereumWallet; +use std::time::Duration; + +use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, U256, hex, keccak256}; -use alloy_provider::ProviderBuilder as AlloyProviderBuilder; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::Signer; use alloy_sol_types::SolCall; +use alloy_transport::TransportError; use chrono::DateTime; use clap::Parser; use eyre::Result; @@ -12,11 +15,16 @@ use foundry_cli::{ utils::LoadConfig, }; use foundry_common::{ + FoundryTransactionBuilder, provider::ProviderBuilder, - shell, - tempo::{self, KeyType, KeysFile, WalletType, read_tempo_keys_file, tempo_keys_path}, + sh_warn, shell, + tempo::{ + self, KeyType, KeysFile, TEMPO_BROWSER_GAS_BUFFER, WalletType, read_tempo_keys_file, + tempo_keys_path, + }, }; use foundry_evm::hardfork::TempoHardfork; +use serde::Deserialize; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_contracts::precompiles::{ ACCOUNT_KEYCHAIN_ADDRESS, IAccountKeychain, @@ -24,13 +32,16 @@ use tempo_contracts::precompiles::{ CallScope, KeyInfo, KeyRestrictions, LegacyTokenLimit, SelectorRule, SignatureType, TokenLimit, }, + ITIP20, PATH_USD_ADDRESS, account_keychain::{authorizeKeyCall, legacyAuthorizeKeyCall}, }; use yansi::Paint; +use foundry_cli::utils::{maybe_print_resolved_lane, resolve_lane}; + use crate::{ - cmd::send::{cast_send, cast_send_with_access_key}, - tx::{CastTxBuilder, SendTxOpts}, + cmd::send::cast_send, + tx::{CastTxBuilder, CastTxSender, SendTxOpts}, }; /// Tempo keychain management commands. @@ -62,6 +73,19 @@ pub enum KeychainSubcommand { rpc: RpcOpts, }, + /// Inspect an access key policy using the local key registry and on-chain state. + Inspect { + /// The key address to inspect. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + #[command(flatten)] + rpc: RpcOpts, + }, + /// Authorize a new key on-chain via the AccountKeychain precompile. #[command(visible_alias = "auth")] Authorize { @@ -183,8 +207,92 @@ pub enum KeychainSubcommand { #[command(flatten)] send_tx: SendTxOpts, }, + + /// Read or edit TIP-1011 access-key permissions. + Policy { + #[command(subcommand)] + command: KeychainPolicySubcommand, + }, +} + +/// Higher-level access-key policy editing commands. +#[derive(Debug, Parser)] +pub enum KeychainPolicySubcommand { + /// Add or widen an allowed call rule for a target contract. + AddCall { + /// The key address to update. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + /// Target contract address. + #[arg(long)] + target: Address, + + /// Function selector, full signature, or known TIP-20 shorthand. + #[arg(long, value_parser = parse_selector_arg)] + selector: SelectorArg, + + /// Optional recipient/spender restrictions for selector calls. + #[arg(long, value_delimiter = ',')] + recipients: Vec
, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Update a token spending limit amount for a key. + SetLimit { + /// The key address to update. + key_address: Address, + + /// Token address, numeric TIP-20 token id, or PathUSD. + #[arg(long, value_parser = parse_policy_token)] + token: Address, + + /// New raw token-denominated limit. + #[arg(long)] + amount: U256, + + /// Limit period such as 7d, 24h, or 3600s. + /// + /// The current AccountKeychain update entrypoint cannot change periods, so non-zero + /// values are rejected. + #[arg(long, value_parser = parse_period)] + period: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove all allowed-call rules for a target contract. + RemoveTarget { + /// The key address to update. + key_address: Address, + + /// Target contract address to remove. + #[arg(long)] + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, } +#[derive(Debug, Clone, Copy)] +pub struct SelectorArg([u8; 4]); + fn parse_signature_type(s: &str) -> Result { match s.to_lowercase().as_str() { "secp256k1" => Ok(SignatureType::Secp256k1), @@ -203,6 +311,15 @@ const fn signature_type_name(t: &SignatureType) -> &'static str { } } +const fn signature_type_label(t: &SignatureType) -> &'static str { + match t { + SignatureType::Secp256k1 => "Secp256k1", + SignatureType::P256 => "P256", + SignatureType::WebAuthn => "WebAuthn", + _ => "unknown", + } +} + const fn key_type_name(t: &KeyType) -> &'static str { match t { KeyType::Secp256k1 => "secp256k1", @@ -211,6 +328,14 @@ const fn key_type_name(t: &KeyType) -> &'static str { } } +const fn key_type_label(t: &KeyType) -> &'static str { + match t { + KeyType::Secp256k1 => "Secp256k1", + KeyType::P256 => "P256", + KeyType::WebAuthn => "WebAuthn", + } +} + const fn wallet_type_name(t: &WalletType) -> &'static str { match t { WalletType::Local => "local", @@ -332,6 +457,48 @@ fn parse_selector_bytes(s: &str) -> Result<[u8; 4], String> { } } +fn parse_selector_arg(s: &str) -> Result { + parse_selector_bytes(s).map(SelectorArg) +} + +fn parse_policy_token(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "pathusd" | "path_usd" | "path-usd" | "usd" => Ok(PATH_USD_ADDRESS), + _ => foundry_cli::utils::parse_fee_token_address(s).map_err(|e| e.to_string()), + } +} + +fn parse_period(s: &str) -> Result { + let s = s.trim(); + if s.is_empty() { + return Err("period cannot be empty".to_string()); + } + + let split = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len()); + if split == 0 { + return Err(format!( + "invalid period '{s}': expected a number followed by s, m, h, d, or w" + )); + } + + let value: u64 = + s[..split].parse().map_err(|e| format!("invalid period value '{}': {e}", &s[..split]))?; + let multiplier = match &s[split..].to_ascii_lowercase()[..] { + "" | "s" => 1, + "m" => 60, + "h" => 60 * 60, + "d" => 24 * 60 * 60, + "w" => 7 * 24 * 60 * 60, + unit => { + return Err(format!( + "invalid period unit '{unit}' in '{s}' (expected s, m, h, d, or w)" + )); + } + }; + + value.checked_mul(multiplier).ok_or_else(|| format!("period '{s}' is too large")) +} + /// Represents a single scope entry in JSON format for `--scopes`. #[derive(serde::Deserialize)] struct JsonCallScope { @@ -402,6 +569,9 @@ impl KeychainSubcommand { Self::Check { wallet_address, key_address, rpc } => { run_check(wallet_address, key_address, rpc).await } + Self::Inspect { key_address, root_account, rpc } => { + run_inspect(key_address, root_account, rpc).await + } Self::Authorize { key_address, key_type, @@ -443,6 +613,40 @@ impl KeychainSubcommand { Self::RemoveScope { key_address, target, tx, send_tx } => { run_remove_scope(key_address, target, tx, send_tx).await } + Self::Policy { command } => command.run().await, + } + } +} + +impl KeychainPolicySubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::AddCall { + key_address, + root_account, + target, + selector, + recipients, + tx, + send_tx, + } => { + run_policy_add_call( + key_address, + root_account, + target, + selector.0, + recipients, + tx, + send_tx, + ) + .await + } + Self::SetLimit { key_address, token, amount, period, tx, send_tx } => { + run_policy_set_limit(key_address, token, amount, period, tx, send_tx).await + } + Self::RemoveTarget { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await + } } } } @@ -500,6 +704,143 @@ fn run_show(wallet_address: Address) -> Result<()> { Ok(()) } +#[derive(Debug, Clone)] +struct LocalLimitMetadata { + token: Address, + amount: String, +} + +#[derive(Debug, Clone)] +struct KeyMetadata { + root_account: Address, + key_type: Option, + limits: Vec, +} + +#[derive(Debug, Clone)] +struct InspectedLimit { + token: Address, + configured_amount: Option, + remaining: U256, + period_end: Option, +} + +#[derive(Debug, Clone)] +enum AllowedCallsView { + Unsupported, + Unrestricted, + Scoped(Vec), +} + +/// `cast keychain inspect ` — inspect on-chain key policy. +async fn run_inspect( + key_address: Address, + root_account: Option
, + rpc: RpcOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let info: KeyInfo = provider.get_keychain_key(metadata.root_account, key_address).await?; + let provisioned = info.keyId != Address::ZERO; + let is_t3 = is_tempo_hardfork_active(&provider, TempoHardfork::T3).await?; + + let mut limits = Vec::new(); + if info.enforceLimits { + for local_limit in &metadata.limits { + let (remaining, period_end) = if is_t3 { + let limit = provider + .get_keychain_remaining_limit_with_period( + metadata.root_account, + key_address, + local_limit.token, + ) + .await?; + (limit.remaining, Some(limit.periodEnd)) + } else { + let remaining = provider + .account_keychain() + .getRemainingLimit(metadata.root_account, key_address, local_limit.token) + .call() + .await?; + (remaining, None) + }; + + limits.push(InspectedLimit { + token: local_limit.token, + configured_amount: Some(local_limit.amount.clone()), + remaining, + period_end, + }); + } + } + + let allowed_calls = if is_t3 { + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + if allowed.isScoped { + AllowedCallsView::Scoped(allowed.scopes) + } else { + AllowedCallsView::Unrestricted + } + } else { + AllowedCallsView::Unsupported + }; + + if shell::is_json() { + let key_type = if provisioned { + signature_type_name(&info.signatureType).to_string() + } else { + metadata + .key_type + .map(|key_type| key_type_name(&key_type).to_string()) + .unwrap_or_else(|| "unknown".to_string()) + }; + let json = serde_json::json!({ + "root_account": metadata.root_account.to_string(), + "key_id": key_address.to_string(), + "provisioned": provisioned, + "type": key_type, + "expiry": provisioned.then_some(info.expiry), + "expiry_human": provisioned.then(|| format_expiry_for_inspect(info.expiry)), + "enforce_limits": info.enforceLimits, + "is_revoked": info.isRevoked, + "limits": limits.iter().map(inspected_limit_to_json).collect::>(), + "allowed_calls": allowed_calls_to_json(&allowed_calls), + }); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + let key_type = if provisioned { + signature_type_label(&info.signatureType) + } else { + metadata.key_type.map(|key_type| key_type_label(&key_type)).unwrap_or("unknown") + }; + + sh_println!("Root account: {}", metadata.root_account)?; + sh_println!("Key id: {key_address}")?; + sh_println!("Type: {key_type}")?; + + if info.isRevoked { + sh_println!("Status: revoked")?; + } else if !provisioned { + sh_println!("Status: not provisioned")?; + } else { + sh_println!("Status: active")?; + sh_println!("Expiry: {}", format_expiry_for_inspect(info.expiry))?; + } + + print_inspected_limits(info.enforceLimits, &limits)?; + print_allowed_calls(&allowed_calls)?; + + Ok(()) +} + /// `cast keychain check` / `cast keychain info` — query on-chain key status. async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) -> Result<()> { let config = rpc.load_config()?; @@ -584,7 +925,7 @@ async fn run_authorize( let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let calldata = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let calldata = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { // T3+ authorizeKey(address,SignatureType,KeyRestrictions) let restrictions = KeyRestrictions { expiry, @@ -634,7 +975,7 @@ async fn run_remaining_limit( let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; - let remaining: U256 = if provider.is_hardfork_active(TempoHardfork::T3).await? { + let remaining: U256 = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { provider.get_keychain_remaining_limit(wallet_address, key_address, token).await? } else { // Pre-T3: use the legacy getRemainingLimit(address,address,address) @@ -646,7 +987,7 @@ async fn run_remaining_limit( }; if shell::is_json() { - sh_println!("{}", serde_json::to_string(&remaining.to_string())?)?; + sh_println!("{}", serde_json::json!({ "remaining": remaining.to_string() }))?; } else { sh_println!("{remaining}")?; } @@ -695,6 +1036,88 @@ async fn run_remove_scope( send_keychain_tx(calldata, tx_opts, &send_tx).await } +/// `cast keychain policy add-call` — merge a selector rule into a target scope. +async fn run_policy_add_call( + key_address: Address, + root_account: Option
, + target: Address, + selector: [u8; 4], + recipients: Vec
, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if !is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + eyre::bail!("allowed-call policy editing requires the Tempo T3 hardfork"); + } + + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + + let new_rule = SelectorRule { selector: selector.into(), recipients }; + let existing_target = allowed + .isScoped + .then(|| allowed.scopes.into_iter().find(|scope| scope.target == target)) + .flatten(); + + let (target_scope, changed) = match existing_target { + Some(mut scope) => { + if scope.selectorRules.is_empty() { + sh_warn!( + "Allowed calls for {} already allow any selector; leaving wildcard scope unchanged", + address_label_with_address(target) + )?; + } + let changed = add_selector_rule_to_scope(&mut scope, new_rule); + (scope, changed) + } + None => (CallScope { target, selectorRules: vec![new_rule] }, true), + }; + + if !changed { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ "status": "already_present", "target": target.to_string() }) + )?; + } else { + sh_println!("Allowed call already present for {}", address_label_with_address(target))?; + } + return Ok(()); + } + + let calldata = + IAccountKeychain::setAllowedCallsCall { keyId: key_address, scopes: vec![target_scope] } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain policy set-limit` — update a spending limit amount. +async fn run_policy_set_limit( + key_address: Address, + token: Address, + amount: U256, + period: Option, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + if period.is_some_and(|period| period != 0) { + eyre::bail!( + "--period is not supported by the current AccountKeychain updateSpendingLimit \ + precompile; periods can only be set when authorizing a key" + ); + } + + // updateSpendingLimit authorizes against msg.sender; the root account is not part of calldata. + run_update_limit(key_address, token, amount, tx_opts, send_tx).await +} + /// Shared helper to send a keychain precompile transaction. async fn send_keychain_tx( calldata: Vec, @@ -702,16 +1125,22 @@ async fn send_keychain_tx( send_tx: &SendTxOpts, ) -> Result<()> { let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let print_sponsor_hash = tx_opts.tempo.print_sponsor_hash; + let tempo_sponsor = + if print_sponsor_hash { None } else { tx_opts.tempo.sponsor_config().await? }; let config = send_tx.eth.load_config()?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); let provider = ProviderBuilder::::from_config(&config)?.build()?; - // Inject key_id for correct gas estimation with keychain signature overhead. - if let Some(ref ak) = tempo_access_key { - tx_opts.tempo.key_id = Some(ak.key_address); + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx_opts.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx_opts.tempo, &config.root)?; + let builder = CastTxBuilder::new(&provider, tx_opts, &config) .await? .with_to(Some(NameOrAddress::Address(ACCOUNT_KEYCHAIN_ADDRESS))) @@ -719,27 +1148,70 @@ async fn send_keychain_tx( .with_code_sig_and_args(None, Some(hex::encode_prefixed(&calldata)), vec![]) .await?; - if let Some(ref ak) = tempo_access_key { - let signer = - signer.as_ref().ok_or_else(|| eyre::eyre!("signer required for access key"))?; - let (tx, _) = builder.build(ak.wallet_address).await?; - cast_send_with_access_key( - &provider, - tx, - signer, - ak, - send_tx.cast_async, - send_tx.confirmations, - timeout, - ) - .await?; + // Keychain management calls are authorized by the root account. Access keys can use their + // permissions, but cannot mutate their own key policy. + let browser = send_tx.browser.run::().await?; + + if print_sponsor_hash { + let from = if let Some(ref browser) = browser { + browser.address() + } else { + signer + .as_ref() + .ok_or_else(|| { + eyre::eyre!( + "--tempo.print-sponsor-hash requires a root account signer, such as \ + --browser, --private-key, or --keystore" + ) + })? + .address() + }; + + let (tx, _) = builder.build(from).await?; + let hash = tx + .compute_sponsor_hash(from) + .ok_or_else(|| eyre::eyre!("This network does not support sponsored transactions"))?; + if shell::is_json() { + sh_println!("{}", serde_json::json!({ "sponsor_hash": format!("{hash:?}") }))?; + } else { + sh_println!("{hash:?}")?; + } + return Ok(()); + } + + if let Some(browser) = browser { + let chain = builder.chain(); + let (mut tx, _) = builder.build(browser.address()).await?; + if chain.is_tempo() + && let Some(gas) = tx.gas_limit() + { + tx.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, browser.address()).await?; + } + + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&provider) + .print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout) + .await?; + } else if tempo_access_key.is_some() { + eyre::bail!( + "keychain policy changes must be signed by the root account; the selected `--from` \ + resolved to a Tempo access key. Use `--browser` for passkey roots, or pass a root \ + account signer with `--private-key`, `--keystore`, Ledger, Trezor, AWS, GCP, or Turnkey." + ); } else { let signer = match signer { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (mut tx, _) = builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() @@ -753,6 +1225,361 @@ async fn send_keychain_tx( Ok(()) } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct AnvilNodeInfo { + hard_fork: Option, + network: Option, +} + +async fn is_tempo_hardfork_active

(provider: &P, hardfork: TempoHardfork) -> Result +where + P: Provider, +{ + match provider.is_hardfork_active(hardfork).await { + Ok(active) => Ok(active), + Err(err) if is_rpc_method_not_found(&err) => { + match anvil_tempo_hardfork_active(provider, hardfork).await { + Ok(Some(active)) => Ok(active), + _ => Err(err.into()), + } + } + Err(err) => Err(err.into()), + } +} + +async fn anvil_tempo_hardfork_active

( + provider: &P, + hardfork: TempoHardfork, +) -> Result, TransportError> +where + P: Provider, +{ + let info = provider.raw_request::<_, AnvilNodeInfo>("anvil_nodeInfo".into(), ()).await?; + Ok(active_from_anvil_node_info(&info, hardfork)) +} + +fn active_from_anvil_node_info(info: &AnvilNodeInfo, hardfork: TempoHardfork) -> Option { + (info.network.as_deref() == Some("tempo")).then(|| { + info.hard_fork + .as_deref() + .and_then(|active_hardfork| active_hardfork.parse::().ok()) + .is_some_and(|active_hardfork| active_hardfork >= hardfork) + }) +} + +fn is_rpc_method_not_found(err: &TransportError) -> bool { + err.as_error_resp().is_some_and(|payload| payload.code == -32601) +} + +fn resolve_key_metadata( + key_address: Address, + root_account: Option

, +) -> Result { + let keys_file = read_tempo_keys_file(); + + if let Some(root_account) = root_account { + if let Some(keys_file) = keys_file.as_ref() + && let Some(entry) = keys_file.keys.iter().find(|entry| { + entry.wallet_address == root_account + && key_entry_effective_key(entry) == key_address + }) + { + return Ok(key_metadata_from_entry(entry)); + } + + return Ok(KeyMetadata { root_account, key_type: None, limits: Vec::new() }); + } + + let Some(keys_file) = keys_file.as_ref() else { + eyre::bail!( + "key {key_address} was not found because the local keys file could not be read at {}; pass --root-account", + tempo_keys_path_display() + ); + }; + + let matches: Vec<_> = keys_file + .keys + .iter() + .filter(|entry| key_entry_effective_key(entry) == key_address) + .collect(); + + if matches.is_empty() { + eyre::bail!( + "key {key_address} was not found in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let root_account = matches[0].wallet_address; + if matches.iter().any(|entry| entry.wallet_address != root_account) { + eyre::bail!( + "key {key_address} matches multiple root accounts in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let entry = + matches.iter().copied().find(|entry| !entry.limits.is_empty()).unwrap_or(matches[0]); + Ok(key_metadata_from_entry(entry)) +} + +fn key_entry_effective_key(entry: &tempo::KeyEntry) -> Address { + entry.key_address.unwrap_or(entry.wallet_address) +} + +fn key_metadata_from_entry(entry: &tempo::KeyEntry) -> KeyMetadata { + KeyMetadata { + root_account: entry.wallet_address, + key_type: Some(entry.key_type), + limits: entry + .limits + .iter() + .map(|limit| LocalLimitMetadata { token: limit.currency, amount: limit.limit.clone() }) + .collect(), + } +} + +fn tempo_keys_path_display() -> String { + tempo_keys_path() + .map(|path| path.display().to_string()) + .unwrap_or_else(|| "(unknown)".to_string()) +} + +fn add_selector_rule_to_scope(scope: &mut CallScope, rule: SelectorRule) -> bool { + if scope.selectorRules.is_empty() { + return false; + } + + let Some(existing_rule) = + scope.selectorRules.iter_mut().find(|existing| existing.selector == rule.selector) + else { + scope.selectorRules.push(rule); + return true; + }; + + if existing_rule.recipients.is_empty() { + return false; + } + + if rule.recipients.is_empty() { + existing_rule.recipients = Vec::new(); + return true; + } + + let mut changed = false; + for recipient in rule.recipients { + if !existing_rule.recipients.contains(&recipient) { + existing_rule.recipients.push(recipient); + changed = true; + } + } + changed +} + +fn inspected_limit_to_json(limit: &InspectedLimit) -> serde_json::Value { + serde_json::json!({ + "token": limit.token.to_string(), + "token_label": address_label(limit.token), + "configured_amount": limit.configured_amount.as_deref(), + "remaining": limit.remaining.to_string(), + "period_end": limit.period_end, + "period_end_human": limit.period_end.and_then(|period_end| { + (period_end != 0).then(|| format_period_end(period_end)) + }), + }) +} + +fn allowed_calls_to_json(allowed_calls: &AllowedCallsView) -> serde_json::Value { + match allowed_calls { + AllowedCallsView::Unsupported => serde_json::json!({ + "mode": "unsupported", + "scopes": [], + }), + AllowedCallsView::Unrestricted => serde_json::json!({ + "mode": "any", + "scopes": [], + }), + AllowedCallsView::Scoped(scopes) => serde_json::json!({ + "mode": if scopes.is_empty() { "none" } else { "scoped" }, + "scopes": scopes.iter().map(call_scope_to_json).collect::>(), + }), + } +} + +fn call_scope_to_json(scope: &CallScope) -> serde_json::Value { + serde_json::json!({ + "target": scope.target.to_string(), + "target_label": address_label(scope.target), + "selectors": scope.selectorRules.iter().map(selector_rule_to_json).collect::>(), + }) +} + +fn selector_rule_to_json(rule: &SelectorRule) -> serde_json::Value { + serde_json::json!({ + "selector": selector_hex(&rule.selector.0), + "signature": selector_signature(&rule.selector.0), + "recipients": rule.recipients.iter().map(ToString::to_string).collect::>(), + }) +} + +fn print_inspected_limits(enforce_limits: bool, limits: &[InspectedLimit]) -> Result<()> { + if !enforce_limits { + sh_println!("Limits: none")?; + return Ok(()); + } + + sh_println!("Limits:")?; + if limits.is_empty() { + sh_println!(" enforced, but no local limit metadata was found")?; + return Ok(()); + } + + for limit in limits { + let configured = limit.configured_amount.as_deref().unwrap_or("unknown"); + let period = limit + .period_end + .and_then(|period_end| { + (period_end != 0).then(|| format!(" ({})", format_period_end(period_end))) + }) + .unwrap_or_default(); + sh_println!( + " {}: {} / {} remaining{}", + address_label(limit.token), + limit.remaining, + configured, + period + )?; + } + + Ok(()) +} + +fn print_allowed_calls(allowed_calls: &AllowedCallsView) -> Result<()> { + match allowed_calls { + AllowedCallsView::Unsupported => sh_println!("Allowed calls: unsupported before T3")?, + AllowedCallsView::Unrestricted => sh_println!("Allowed calls: any")?, + AllowedCallsView::Scoped(scopes) if scopes.is_empty() => { + sh_println!("Allowed calls: none")?; + } + AllowedCallsView::Scoped(scopes) => { + sh_println!("Allowed calls:")?; + for scope in scopes { + sh_println!(" {}:", address_label_with_address(scope.target))?; + if scope.selectorRules.is_empty() { + sh_println!(" any selector")?; + continue; + } + + for rule in &scope.selectorRules { + sh_println!( + " {} -> {}", + format_selector(&rule.selector.0), + format_recipients(&rule.recipients) + )?; + } + } + } + } + + Ok(()) +} + +fn address_label(address: Address) -> String { + if address == PATH_USD_ADDRESS { "PathUSD".to_string() } else { address.to_string() } +} + +fn address_label_with_address(address: Address) -> String { + if address == PATH_USD_ADDRESS { format!("PathUSD ({address})") } else { address.to_string() } +} + +fn format_selector(selector: &[u8; 4]) -> String { + selector_signature(selector).map(str::to_string).unwrap_or_else(|| selector_hex(selector)) +} + +fn selector_signature(selector: &[u8; 4]) -> Option<&'static str> { + if selector == &ITIP20::transferCall::SELECTOR { + Some("transfer(address,uint256)") + } else if selector == &ITIP20::approveCall::SELECTOR { + Some("approve(address,uint256)") + } else if selector == &ITIP20::transferFromCall::SELECTOR { + Some("transferFrom(address,address,uint256)") + } else if selector == &ITIP20::transferWithMemoCall::SELECTOR { + Some("transferWithMemo(address,uint256,bytes32)") + } else if selector == &ITIP20::transferFromWithMemoCall::SELECTOR { + Some("transferFromWithMemo(address,address,uint256,bytes32)") + } else if selector == &ITIP20::mintCall::SELECTOR { + Some("mint(address,uint256)") + } else if selector == &ITIP20::burnCall::SELECTOR { + Some("burn(uint256)") + } else { + None + } +} + +fn selector_hex(selector: &[u8; 4]) -> String { + hex::encode_prefixed(selector) +} + +fn format_recipients(recipients: &[Address]) -> String { + if recipients.is_empty() { + return "any recipient".to_string(); + } + + let recipients = recipients.iter().map(ToString::to_string).collect::>().join(", "); + format!("recipients [{recipients}]") +} + +fn format_expiry_for_inspect(expiry: u64) -> String { + if expiry == u64::MAX { + return "never".to_string(); + } + + format!("{} ({})", format_timestamp_iso(expiry), format_relative_timestamp(expiry)) +} + +fn format_period_end(period_end: u64) -> String { + format!("period resets {}", format_relative_timestamp(period_end)) +} + +fn format_timestamp_iso(timestamp: u64) -> String { + DateTime::from_timestamp(timestamp as i64, 0) + .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) + .unwrap_or_else(|| timestamp.to_string()) +} + +fn format_relative_timestamp(timestamp: u64) -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + if timestamp == now { + "now".to_string() + } else if timestamp > now { + format!("in {}", format_duration_words(timestamp - now)) + } else { + format!("{} ago", format_duration_words(now - timestamp)) + } +} + +fn format_duration_words(seconds: u64) -> String { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + const DAY: u64 = 24 * HOUR; + + if seconds >= DAY { + let days = seconds / DAY; + if days == 1 { "1 day".to_string() } else { format!("{days} days") } + } else if seconds >= HOUR { + format!("{}h", seconds / HOUR) + } else if seconds >= MINUTE { + format!("{}m", seconds / MINUTE) + } else { + format!("{seconds}s") + } +} + fn format_expiry(expiry: u64) -> String { if expiry == u64::MAX { return "never".to_string(); @@ -842,6 +1669,7 @@ fn key_entry_to_json(entry: &tempo::KeyEntry) -> serde_json::Value { #[cfg(test)] mod tests { use super::*; + use alloy_json_rpc::ErrorPayload; use std::str::FromStr; #[test] @@ -967,4 +1795,144 @@ mod tests { let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":[{"selector":"transfer","recipients":[],"bogus":true}]}]"#; assert!(parse_scopes_json(json).is_err()); } + + #[test] + fn test_parse_policy_token_path_usd() { + assert_eq!(parse_policy_token("PathUSD").unwrap(), PATH_USD_ADDRESS); + assert_eq!(parse_policy_token("path-usd").unwrap(), PATH_USD_ADDRESS); + } + + #[test] + fn test_parse_period_units() { + assert_eq!(parse_period("0").unwrap(), 0); + assert_eq!(parse_period("30s").unwrap(), 30); + assert_eq!(parse_period("5m").unwrap(), 300); + assert_eq!(parse_period("2h").unwrap(), 7200); + assert_eq!(parse_period("7d").unwrap(), 604800); + assert_eq!(parse_period("2w").unwrap(), 1209600); + assert!(parse_period("1mo").is_err()); + } + + #[test] + fn test_add_selector_rule_merges_recipients() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let second = Address::from_str("0x2222222222222222222222222222222222222222").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![second], + }, + ); + + assert!(changed); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].recipients, vec![first, second]); + } + + #[test] + fn test_add_selector_rule_empty_recipients_widens_to_any() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(changed); + assert!(scope.selectorRules[0].recipients.is_empty()); + } + + #[test] + fn test_add_selector_rule_target_wildcard_is_unchanged() { + let mut scope = CallScope { target: PATH_USD_ADDRESS, selectorRules: vec![] }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(!changed); + assert!(scope.selectorRules.is_empty()); + } + + #[test] + fn test_policy_set_limit_parses() { + let key = "0x1111111111111111111111111111111111111111"; + + let command = KeychainSubcommand::try_parse_from([ + "keychain", + "policy", + "set-limit", + key, + "--token", + "PathUSD", + "--amount", + "123", + ]) + .unwrap(); + + match command { + KeychainSubcommand::Policy { + command: + KeychainPolicySubcommand::SetLimit { key_address, token, amount, period, .. }, + } => { + assert_eq!(key_address, Address::from_str(key).unwrap()); + assert_eq!(token, PATH_USD_ADDRESS); + assert_eq!(amount, U256::from(123)); + assert_eq!(period, None); + } + other => panic!("unexpected command: {other:?}"), + } + } + + #[test] + fn test_active_from_anvil_node_info_requires_tempo_network() { + let tempo_t3 = + AnvilNodeInfo { network: Some("tempo".to_string()), hard_fork: Some("T3".to_string()) }; + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T2), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T3), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T4), Some(false)); + + let ethereum_t3 = AnvilNodeInfo { + network: Some("ethereum".to_string()), + hard_fork: Some("T3".to_string()), + }; + assert_eq!(active_from_anvil_node_info(ðereum_t3, TempoHardfork::T3), None); + } + + #[test] + fn test_rpc_method_not_found_detection() { + let method_missing: TransportError = + TransportError::ErrorResp(ErrorPayload::method_not_found()); + assert!(is_rpc_method_not_found(&method_missing)); + + let internal_error: TransportError = + TransportError::ErrorResp(ErrorPayload::internal_error()); + assert!(!is_rpc_method_not_found(&internal_error)); + + let transport_error = alloy_transport::TransportErrorKind::backend_gone(); + assert!(!is_rpc_method_not_found(&transport_error)); + } } diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index 8aaf6e97a1827..67178cd093d7b 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -2,7 +2,9 @@ use crate::tx::{self, CastTxBuilder}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{Ethereum, EthereumWallet, Network, NetworkTransactionBuilder}; +use alloy_network::{ + Ethereum, EthereumWallet, Network, NetworkTransactionBuilder, TransactionBuilder, +}; use alloy_primitives::{Address, hex}; use alloy_provider::Provider; use alloy_signer::{Signature, Signer}; @@ -10,7 +12,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::LoadConfig, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use std::{path::PathBuf, str::FromStr}; @@ -94,9 +96,13 @@ impl MakeTxArgs { N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; + let Self { to, mut sig, mut args, command, mut tx, path, eth, raw_unsigned, ethsign } = + self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -117,6 +123,11 @@ impl MakeTxArgs { let provider = ProviderBuilder::::from_config(&config)?.build()?; + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + // Must happen before `tx.clone()` so the cloned tx carries the resolved nonce_key. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) .await? .with_to(to) @@ -139,6 +150,10 @@ impl MakeTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + if raw_unsigned { // Build unsigned raw tx // Check if nonce is provided when --from is not specified @@ -148,11 +163,20 @@ impl MakeTxArgs { "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" ); } + if tempo_sponsor.is_some() && eth.wallet.from.is_none() { + eyre::bail!( + "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender" + ); + } // Use zero address as placeholder for unsigned transactions let from = eth.wallet.from.unwrap_or(Address::ZERO); - let (tx, _) = tx_builder.build(from).await?; + let (mut tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; @@ -162,7 +186,11 @@ impl MakeTxArgs { if ethsign { // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has // unlocked accounts. - let (tx, _) = tx_builder.build(config.sender).await?; + let (mut tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, config.sender).await?; + } let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; @@ -176,7 +204,11 @@ impl MakeTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = tx_builder.build(&signer).await?; + let (mut tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let tx = tx.build(&EthereumWallet::new(signer)).await?; diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 6a9d11f5dc61d..0b1b26615694a 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -15,6 +15,7 @@ pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; +#[cfg(feature = "optimism")] pub mod da_estimate; pub mod erc20; pub mod estimate; @@ -28,7 +29,9 @@ pub mod rpc; pub mod run; pub mod send; pub mod storage; +pub mod tempo; pub mod tip20; pub mod trace; pub mod txpool; +pub mod vaddr; pub mod wallet; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 84699d6cd6956..7e52a9e265f25 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -29,10 +29,12 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::{ FoundryBlock as _, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, }, executors::{EvmError, Executor, TracingExecutor}, hardforks::FoundryHardfork, @@ -123,12 +125,15 @@ impl RunArgs { evm_opts.infer_network_from_fork().await; if evm_opts.networks.is_tempo() { - self.run_with_evm::().await - } else if evm_opts.networks.is_optimism() { - self.run_with_evm::().await - } else { - self.run_with_evm::().await + return self.run_with_evm::().await; + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_evm::().await; } + + self.run_with_evm::().await } async fn run_with_evm(self) -> Result<()> { diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 2d6e248cc7a73..421aae1f2153e 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -8,7 +8,10 @@ use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::{Signature, Signer}; use clap::Parser; use eyre::{Result, eyre}; -use foundry_cli::{opts::TransactionOpts, utils::LoadConfig}; +use foundry_cli::{ + opts::TransactionOpts, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; use foundry_common::{ FoundryTransactionBuilder, fmt::{UIfmt, UIfmtReceiptExt}, @@ -119,7 +122,9 @@ impl SendTxArgs { self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; - let sponsor_signature = tx.tempo.sponsor_signature; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -183,6 +188,8 @@ impl SendTxArgs { let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } @@ -202,13 +209,19 @@ impl SendTxArgs { // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. if print_sponsor_hash { - // Use the pre-resolved signer to derive the actual sender address, since the - // sponsor hash commits to the sender. - let signer = pre_resolved_signer.as_ref().ok_or_else(|| { - eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") - })?; - let from = signer.address(); - let (tx, _) = builder.build(from).await?; + let (tx, from) = if let Some(ref ak) = access_key { + let (tx, _) = builder.build_with_access_key(ak.wallet_address, ak).await?; + (tx, ak.wallet_address) + } else { + // Use the pre-resolved signer to derive the actual sender address, since the + // sponsor hash commits to the sender. + let signer = pre_resolved_signer.as_ref().ok_or_else(|| { + eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") + })?; + let from = signer.address(); + let (tx, _) = builder.build(from).await?; + (tx, from) + }; let hash = tx .compute_sponsor_hash(from) .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?; @@ -216,6 +229,10 @@ impl SendTxArgs { return Ok(()); } + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); // Launch browser signer if `--browser` flag is set @@ -245,11 +262,18 @@ impl SendTxArgs { } } - let (tx, _) = builder.build(config.sender).await?; + let (mut tx_request, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, config.sender).await?; + } cast_send( provider, - tx, + tx_request, send_tx.cast_async, send_tx.sync, send_tx.confirmations, @@ -261,6 +285,10 @@ impl SendTxArgs { } else if let Some(browser) = browser { let chain = builder.chain(); let (mut tx_request, _) = builder.build(browser.address()).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which // costs more gas for signature verification on Tempo chains. Add a @@ -270,6 +298,9 @@ impl SendTxArgs { { tx_request.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, browser.address()).await?; + } let tx_hash = browser.send_transaction_via_browser(tx_request).await?; @@ -283,7 +314,14 @@ impl SendTxArgs { Some(s) => s, None => send_tx.eth.wallet.signer().await?, }; - let (tx_request, _) = builder.build(ak.wallet_address).await?; + let (mut tx_request, _) = builder.build_with_access_key(ak.wallet_address, &ak).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, ak.wallet_address).await?; + } cast_send_with_access_key( &provider, tx_request, @@ -308,11 +346,13 @@ impl SendTxArgs { tx::validate_from_address(send_tx.eth.wallet.from, from)?; let (mut tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; - // Apply sponsor signature after gas estimation so the estimate is - // consistent with what `--tempo.print-sponsor-hash` computes. - if let Some(sig) = sponsor_signature { - tx_request.set_fee_payer_signature(sig); + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, from).await?; } let wallet = EthereumWallet::from(signer); diff --git a/crates/cast/src/cmd/tempo.rs b/crates/cast/src/cmd/tempo.rs new file mode 100644 index 0000000000000..6744e6b73c039 --- /dev/null +++ b/crates/cast/src/cmd/tempo.rs @@ -0,0 +1,45 @@ +use clap::Parser; +use eyre::Result; +use foundry_common::tempo::{EnsureAccessKeyConfig, ensure_access_key}; + +/// Tempo wallet integration commands. +#[derive(Debug, Parser)] +pub enum TempoSubcommand { + /// Authorize a new access key against your Tempo wallet via wallet.tempo. + /// + /// Persists the key to `$TEMPO_HOME/wallet/keys.toml` (default + /// `~/.tempo/wallet/keys.toml`). Also runs automatically on a 402 from a + /// Tempo RPC when no local key is configured. + /// + /// Env: `TEMPO_HOME`, `TEMPO_CLI_AUTH_URL` (override auth service). + Login { + /// Chain ID to authorize the key for. Defaults to Tempo mainnet (4217). + #[arg(long, default_value_t = 4217)] + chain_id: u64, + + /// Print the authorization URL to stderr instead of opening a browser. + #[arg(long)] + no_browser: bool, + }, +} + +impl TempoSubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::Login { chain_id, no_browser } => { + let mut cfg = EnsureAccessKeyConfig::from_env(chain_id); + if no_browser { + cfg.no_browser = true; + } + let outcome = ensure_access_key(cfg).await?; + let _ = foundry_common::sh_println!( + "Authorized key {} for wallet {} on chain {}", + outcome.key_address, + outcome.wallet_address, + outcome.chain_id, + ); + Ok(()) + } + } + } +} diff --git a/crates/cast/src/cmd/tip20/mine.rs b/crates/cast/src/cmd/tip20/mine.rs index a5f9062482a01..0367450a19b06 100644 --- a/crates/cast/src/cmd/tip20/mine.rs +++ b/crates/cast/src/cmd/tip20/mine.rs @@ -20,11 +20,11 @@ use tempo_primitives::{MasterId, TempoAddressExt, UserTag}; const POW_BYTES: usize = 4; -pub(super) struct Output { - pub(super) salt: B256, - pub(super) registration_hash: B256, - pub(super) master_id: MasterId, - pub(super) zero_tag_virtual_address: Address, +pub(crate) struct Output { + pub(crate) salt: B256, + pub(crate) registration_hash: B256, + pub(crate) master_id: MasterId, + pub(crate) zero_tag_virtual_address: Address, } pub(super) fn run( @@ -127,7 +127,12 @@ pub(super) async fn register( Ok(()) } -fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Result { +pub(crate) fn mine( + master: Address, + salt: B256, + n_threads: usize, + pow_bytes: usize, +) -> Result { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); @@ -144,7 +149,7 @@ fn mine(master: Address, salt: B256, n_threads: usize, pow_bytes: usize) -> Resu .ok_or_else(|| eyre::eyre!("virtual master mining failed: all threads panicked")) } -fn derive(master: Address, salt: B256) -> Output { +pub(crate) fn derive(master: Address, salt: B256) -> Output { let registration_hash = registration_hash(master, salt); let master_id = MasterId::from_slice(®istration_hash[4..8]); let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); @@ -152,14 +157,14 @@ fn derive(master: Address, salt: B256) -> Output { Output { salt, registration_hash, master_id, zero_tag_virtual_address } } -fn registration_hash(master: Address, salt: B256) -> B256 { +pub(crate) fn registration_hash(master: Address, salt: B256) -> B256 { let mut packed = [0u8; 52]; packed[..20].copy_from_slice(master.as_slice()); packed[20..].copy_from_slice(salt.as_slice()); keccak256(packed) } -fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { +pub(crate) fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { registration_hash[..pow_bytes].iter().all(|byte| *byte == 0) } diff --git a/crates/cast/src/cmd/tip20/mod.rs b/crates/cast/src/cmd/tip20/mod.rs index e3c39b2c9bb18..edb4c3b7a57b3 100644 --- a/crates/cast/src/cmd/tip20/mod.rs +++ b/crates/cast/src/cmd/tip20/mod.rs @@ -6,7 +6,7 @@ use std::str::FromStr; mod create; pub(crate) use create::iso4217_warning_message; -mod mine; +pub(crate) mod mine; /// TIP-20 token operations (Tempo). #[derive(Debug, Parser, Clone)] diff --git a/crates/cast/src/cmd/vaddr/create.rs b/crates/cast/src/cmd/vaddr/create.rs new file mode 100644 index 0000000000000..0563b09601323 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/create.rs @@ -0,0 +1,181 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + tip20::mine, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::{provider::ProviderBuilder, shell}; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use serde_json::json; +use std::time::Instant; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + owner: Address, + salt: Option, + tag: u64, + count: u32, + threads: Option, + seed: Option, + no_random: bool, + no_register: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + if count == 0 { + // no virtual addresses to compute + return Ok(()); + } + + if !owner.is_valid_master() { + eyre::bail!( + "invalid owner address {owner}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + let output = if let Some(salt) = salt { + let output = mine::derive(owner, salt); + if !mine::has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + output + } else { + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut start_salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut start_salt[..]); + } + + if !shell::is_json() { + sh_println!("Mining TIP-1022 salt for {owner} with {n_threads} threads...")?; + } + let timer = Instant::now(); + let output = mine::mine(owner, start_salt, n_threads, POW_BYTES)?; + if !shell::is_json() { + sh_println!("Found salt in {:?}", timer.elapsed())?; + } + output + }; + + const MAX_USER_TAG: u64 = 0x0000_FFFF_FFFF_FFFF; + let mut virtual_addresses = Vec::with_capacity(count as usize); + for i in 0..count { + let tag_value = tag + .checked_add(i as u64) + .filter(|&t| t <= MAX_USER_TAG) + .ok_or_else(|| eyre::eyre!("tag overflow: tag + count exceeds the 6-byte user tag range (max {MAX_USER_TAG:#x})"))?; + let raw = tag_value.to_be_bytes(); + let user_tag = UserTag::new(raw[2..].try_into().expect("slice is 6 bytes")); + let vaddr = Address::new_virtual(output.master_id, user_tag); + virtual_addresses.push((user_tag, vaddr)); + } + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "salt": format!("{}", output.salt), + "registration_hash": format!("{}", output.registration_hash), + "master_id": format!("{}", output.master_id), + "virtual_addresses": virtual_addresses.iter().map(|(tag, addr)| json!({ + "tag": format!("{tag}"), + "address": format!("{addr}"), + })).collect::>(), + }))? + )?; + } else { + sh_println!( + "Salt: {} +Registration hash: {} +Master ID: {}", + output.salt, + output.registration_hash, + output.master_id, + )?; + sh_println!("\nVirtual addresses:")?; + for (tag, vaddr) in &virtual_addresses { + sh_println!(" tag={tag} {vaddr}")?; + } + } + + if no_register { + return Ok(()); + } + + register(owner, output.salt, send_tx, tx_opts).await +} + +async fn register( + owner: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!("cast vaddr create requires a signer (for example --private-key or --from)") + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != owner { + eyre::bail!( + "signer mismatch: salt is for {owner}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt})...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/mod.rs b/crates/cast/src/cmd/vaddr/mod.rs new file mode 100644 index 0000000000000..446d1e7ece5e2 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/mod.rs @@ -0,0 +1,131 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use foundry_cli::opts::RpcOpts; + +mod create; +mod resolve; +mod watch; + +/// TIP-1022 virtual address registry operations (Tempo). +/// +/// Virtual addresses are deterministic 20-byte aliases (masterId || VIRTUAL_MAGIC || userTag) +/// that auto-forward TIP-20 deposits to a registered master wallet at the protocol level, +/// with no on-chain sweep transaction required. +/// +/// See: +#[derive(Debug, Parser, Clone)] +pub enum VaddrSubcommand { + /// Mine a TIP-1022 proof-of-work salt, register as a virtual address master, and print + /// derived virtual addresses for the given owner. + #[command(visible_alias = "c")] + Create { + /// The master (owner) address that will control all virtual addresses under this + /// registration. Must not be the zero address, a virtual address, or a TIP-20 token. + #[arg(long, value_name = "ADDRESS")] + owner: Address, + + /// Use this salt directly instead of mining one. Must satisfy the 32-bit PoW requirement. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Starting user tag for the derived virtual address output (hex-encoded 6 bytes). + #[arg(long, default_value = "0", value_name = "U64")] + tag: u64, + + /// Number of virtual addresses to derive and print. + #[arg(long, default_value = "1", value_name = "N")] + count: u32, + + /// Number of threads to use for mining. Defaults to number of logical cores. + #[arg(long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// Seed for the random number generator used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Start salt search from zero instead of a random value. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Mine and print the salt and derived virtual addresses without submitting the + /// registerVirtualMaster transaction. + #[arg(long)] + no_register: bool, + + #[command(flatten)] + send_tx: Box, + + #[command(flatten)] + tx: Box, + }, + + /// Resolve a virtual address to its registered master and decode its components. + #[command(visible_alias = "r")] + Resolve { + /// The virtual address to resolve. + #[arg(value_name = "ADDRESS")] + addr: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Watch (tail) incoming TIP-20 transfers to a virtual address. + #[command(visible_alias = "w")] + Watch { + /// The virtual address to monitor. + #[arg(value_name = "ADDRESS")] + addr: Address, + + /// Filter on a specific TIP-20 token address. Watches all tokens if omitted. + #[arg(long, value_name = "ADDRESS")] + token: Option
, + + /// Block number to start from. Defaults to the current latest block. + #[arg(long, value_name = "BLOCK")] + from_block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, +} + +impl VaddrSubcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + send_tx, + tx, + } => { + create::run( + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + *send_tx, + *tx, + ) + .await? + } + Self::Resolve { addr, rpc } => resolve::run(addr, rpc).await?, + Self::Watch { addr, token, from_block, rpc } => { + watch::run(addr, token, from_block, rpc).await? + } + } + Ok(()) + } +} diff --git a/crates/cast/src/cmd/vaddr/resolve.rs b/crates/cast/src/cmd/vaddr/resolve.rs new file mode 100644 index 0000000000000..96936f4fe4713 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/resolve.rs @@ -0,0 +1,52 @@ +use alloy_primitives::{Address, hex}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; + +pub(super) async fn run(addr: Address, rpc: RpcOpts) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let registry = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider); + + let decode_builder = registry.decodeVirtualAddress(addr); + let resolve_builder = registry.resolveVirtualAddress(addr); + let (decoded, master) = tokio::try_join!(decode_builder.call(), resolve_builder.call())?; + + if !decoded.isVirtual { + sh_println!("{addr} is not a virtual address")?; + return Ok(()); + } + + let master_id = decoded.masterId; + let user_tag = decoded.userTag; + let master: Address = master; + + if shell::is_json() { + let master_address = if master.is_zero() { None } else { Some(format!("{master}")) }; + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "address": format!("{addr}"), + "master_id": format!("0x{}", hex::encode(master_id)), + "user_tag": format!("0x{}", hex::encode(user_tag)), + "master_address": master_address, + }))? + )?; + } else { + sh_println!("Virtual address: {addr}")?; + sh_println!("Master ID: 0x{}", hex::encode(master_id))?; + sh_println!("User tag: 0x{}", hex::encode(user_tag))?; + if master.is_zero() { + sh_println!("Master address: (unregistered)")?; + } else { + sh_println!("Master address: {master}")?; + } + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/watch.rs b/crates/cast/src/cmd/vaddr/watch.rs new file mode 100644 index 0000000000000..dc159d5e37c8e --- /dev/null +++ b/crates/cast/src/cmd/vaddr/watch.rs @@ -0,0 +1,108 @@ +use alloy_primitives::{Address, B256, keccak256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use std::sync::LazyLock; +use tempo_alloy::TempoNetwork; +use tempo_primitives::TempoAddressExt; + +static TRANSFER_TOPIC: LazyLock = + LazyLock::new(|| keccak256(b"Transfer(address,address,uint256)")); + +pub(super) async fn run( + addr: Address, + token: Option
, + from_block: Option, + rpc: RpcOpts, +) -> Result<()> { + if !addr.is_virtual() { + eyre::bail!("{addr} is not a virtual address"); + } + + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Transfer(address indexed from, address indexed to, uint256 value) + // topic[0] = event sig, topic[1] = from, topic[2] = to + let to_topic: B256 = { + let mut buf = [0u8; 32]; + buf[12..].copy_from_slice(addr.as_slice()); + buf.into() + }; + + let start = from_block.map(BlockNumberOrTag::Number).unwrap_or(BlockNumberOrTag::Latest); + + let mut filter = + Filter::new().event_signature(*TRANSFER_TOPIC).topic2(to_topic).from_block(start); + + if let Some(tok) = token { + filter = filter.address(tok); + } + + if !shell::is_json() { + sh_println!("Watching transfers to {addr}... (Ctrl-C to stop)")?; + } + + // Fetch logs from the requested start block (historical when from_block is set) + let logs = provider.get_logs(&filter).await?; + for log in &logs { + print_transfer_log(log)?; + } + + // Poll for new logs + let mut last_block = provider.get_block_number().await?; + loop { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let current = provider.get_block_number().await?; + if current > last_block { + let poll_filter = filter.clone().from_block(last_block + 1).to_block(current); + let new_logs = provider.get_logs(&poll_filter).await?; + for log in &new_logs { + print_transfer_log(log)?; + } + last_block = current; + } + } +} + +fn print_transfer_log(log: &alloy_rpc_types::Log) -> Result<()> { + let block = log.block_number.unwrap_or(0); + let tx = log.transaction_hash.unwrap_or_default(); + let token = log.address(); + + // Decode topics: topic[1]=from, topic[2]=to + let from = log.topics().get(1).map(|t| { + let mut addr = [0u8; 20]; + addr.copy_from_slice(&t[12..]); + Address::from(addr) + }); + + // Decode amount from data + let amount = if log.data().data.len() >= 32 { + alloy_primitives::U256::from_be_slice(&log.data().data[..32]) + } else { + alloy_primitives::U256::ZERO + }; + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string(&json!({ + "block": block, + "tx": format!("{tx}"), + "token": format!("{token}"), + "from": from.map(|a| format!("{a}")).unwrap_or_default(), + "amount": amount.to_string(), + }))? + )?; + } else { + sh_println!( + "block={block} tx={tx} token={token} from={} amount={amount}", + from.map(|a| a.to_string()).unwrap_or_default(), + )?; + } + Ok(()) +} diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 8e0dd8dd3ed8c..b2378d8bfdc58 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -779,8 +779,7 @@ flag to set your key via: )?; let address = wallet.address(); let success_message = format!( - "`{}` keystore was saved successfully. Address: {:?}", - &account_name, address, + "`{account_name}` keystore was saved successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } @@ -815,7 +814,7 @@ flag to set your key via: format!("Failed to remove keystore file at {}", keystore_path.display()) })?; - let success_message = format!("`{}` keystore was removed successfully.", &name); + let success_message = format!("`{name}` keystore was removed successfully."); sh_println!("{}", success_message.green())?; } Self::PrivateKey { @@ -886,8 +885,7 @@ flag to set your key via: let private_key = B256::from_slice(&wallet.credential().to_bytes()); - let success_message = - format!("{}'s private key is: {}", &account_name, private_key); + let success_message = format!("{account_name}'s private key is: {private_key}"); sh_println!("{}", success_message.green())?; } @@ -945,10 +943,9 @@ flag to set your key via: Some(&account_name), )?; + let address = wallet.address(); let success_message = format!( - "Password for keystore `{}` was changed successfully. Address: {:?}", - &account_name, - wallet.address(), + "Password for keystore `{account_name}` was changed successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ce5572acebc13..2b1b03486bf04 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -40,6 +40,7 @@ use foundry_common::{ use foundry_config::Chain; use foundry_evm::core::bytecode::InstIter; use futures::{FutureExt, StreamExt, future::Either}; +#[cfg(feature = "optimism")] use op_alloy_consensus as _; use rayon::prelude::*; @@ -60,6 +61,7 @@ pub use foundry_evm::*; pub mod args; pub mod cmd; pub mod opts; +pub mod tempo; pub mod base; pub mod call_spec; @@ -246,7 +248,7 @@ impl + Clone + Unpin, N: Network> Cast { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", &al.address.to_checksum(None))); + s.push(format!("- address: {}", al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index effc081e8072a..763eb132ddb5c 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,11 +1,13 @@ +#[cfg(feature = "optimism")] +use crate::cmd::da_estimate::DAEstimateArgs; use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, batch_mktx::BatchMakeTxArgs, batch_send::BatchSendArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, - da_estimate::DAEstimateArgs, erc20::Erc20Subcommand, estimate::EstimateArgs, - find_block::FindBlockArgs, interface::InterfaceArgs, keychain::KeychainSubcommand, - logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, - storage::StorageArgs, tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, + erc20::Erc20Subcommand, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, keychain::KeychainSubcommand, logs::LogsArgs, mktx::MakeTxArgs, + rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, tempo::TempoSubcommand, + tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, vaddr::VaddrSubcommand, wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; @@ -1163,6 +1165,7 @@ pub enum CastSubcommand { command: TxPoolSubcommands, }, /// Estimates the data availability size of a given opstack block. + #[cfg(feature = "optimism")] #[command(name = "da-estimate")] DAEstimate(DAEstimateArgs), @@ -1186,6 +1189,20 @@ pub enum CastSubcommand { #[command(subcommand)] command: KeychainSubcommand, }, + + /// Tempo wallet integration (login, etc.). + Tempo { + #[command(subcommand)] + command: TempoSubcommand, + }, + + /// TIP-1022 virtual address registry operations (Tempo). + #[command(visible_alias = "vaddr")] + VirtualAddress { + #[command(subcommand)] + command: VaddrSubcommand, + }, + #[command(name = "trace")] Trace(TraceArgs), } diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 96f5fc5137575..b58136f4ae9de 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -20,7 +20,7 @@ use foundry_common::{ get_pretty_receipt_w_reason_attr, shell, }; use foundry_config::{Chain, Config}; -use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner}; +use foundry_wallets::{BrowserWalletOpts, TempoAccessKeyConfig, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration}; @@ -535,13 +535,29 @@ where sender: impl Into>, ) -> Result<(N::TransactionRequest, Option)> { let fill = self.fill; - self._build(sender, fill).await + self._build(sender, fill, None).await + } + + /// Builds a transaction that will be signed by a Tempo access key. + /// + /// The access-key id is set before gas estimation. If the access key needs on-chain + /// provisioning, its authorization is embedded before access-list/gas estimation and before + /// any sponsor digest can be computed. + pub async fn build_with_access_key( + mut self, + sender: impl Into>, + access_key: &TempoAccessKeyConfig, + ) -> Result<(N::TransactionRequest, Option)> { + self.tx.set_key_id(access_key.key_address); + let fill = self.fill; + self._build(sender, fill, Some(access_key)).await } async fn _build( mut self, sender: impl Into>, fill: bool, + access_key: Option<&TempoAccessKeyConfig>, ) -> Result<(N::TransactionRequest, Option)> { // prepare let sender = sender.into(); @@ -555,6 +571,16 @@ where // resolve let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; self.resolve_auth(&sender, tx_nonce).await?; + if let Some(access_key) = access_key { + self.tx + .prepare_access_key_authorization( + &self.provider, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + } self.resolve_access_list().await?; // fill diff --git a/crates/cast/tests/cli/keychain.rs b/crates/cast/tests/cli/keychain.rs new file mode 100644 index 0000000000000..88e9e16983cc5 --- /dev/null +++ b/crates/cast/tests/cli/keychain.rs @@ -0,0 +1,76 @@ +//! CLI tests for `cast keychain` subcommands. + +use anvil::NodeConfig; +use foundry_test_utils::util::OutputExt; + +/// Anvil test accounts (standard mnemonic). +mod accounts { + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + pub const TOKEN: &str = "0x20C000000000000000000000b9537d11c60E8b50"; // PathUSD +} + +// `cast keychain rl --json` must emit `{"remaining":""}`, not a bare string. +casttest!(keychain_rl_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "rl", + accounts::ADDR1, + accounts::ADDR2, + accounts::TOKEN, + "--rpc-url", + &rpc, + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain rl --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + assert!( + parsed.get("remaining").is_some(), + "expected 'remaining' key in JSON output, got: {output}" + ); + // Must not be a bare string (old bug: `"0"`) + assert!(!parsed.is_string(), "JSON output must not be a bare string, got: {output}"); +}); + +// `cast keychain authorize --tempo.print-sponsor-hash --json` must emit +// `{"sponsor_hash":"0x..."}`, not a raw hex string. +casttest!(keychain_authorize_sponsor_hash_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "authorize", + accounts::ADDR2, // key to authorize + "--private-key", + accounts::PK1, + "--rpc-url", + &rpc, + "--tempo.print-sponsor-hash", + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain authorize --tempo.print-sponsor-hash --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + let hash = parsed + .get("sponsor_hash") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("expected 'sponsor_hash' key in JSON output, got: {output}")); + assert!(hash.starts_with("0x"), "sponsor_hash should be 0x-prefixed, got: {hash}"); + assert_eq!(hash.len(), 66, "sponsor_hash should be 32-byte hex (66 chars), got: {hash}"); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index c26baaca638da..9071614e3a7a9 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,6 +1,7 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_eips::Decodable2718; use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{B256, Bytes, U256, address, b256, hex}; @@ -20,11 +21,13 @@ use foundry_test_utils::{ }; use serde_json::json; use std::{fs, path::Path, str::FromStr}; +use tempo_primitives::TempoTxEnvelope; #[macro_use] extern crate foundry_test_utils; mod erc20; +mod keychain; mod selectors; casttest!(print_short_version, |_prj, cmd| { @@ -2055,6 +2058,55 @@ casttest!(mktx_ethsign, async |_prj, cmd| { ]]); }); +// tests that `cast mktx --tempo.lane ` resolves the lane against a `tempo.lanes.toml` file at +// the project root, sets the corresponding `nonce_key` on the produced Tempo AA transaction. +casttest!(mktx_tempo_lane_resolves_nonce_key, |prj, cmd| { + // Write a shared lanes file at the project root. + let lanes_path = prj.root().join("tempo.lanes.toml"); + fs::write(&lanes_path, "deploy = 1\nops = 2\npayments = 42\n").unwrap(); + + let output = cmd + .current_dir(prj.root()) + .args([ + "mktx", + "--tempo.lane", + "payments", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]) + .assert_success() + .get_output() + .clone(); + + // The resolved-lane breadcrumb is printed to stderr so it doesn't pollute stdout + // (which carries the raw signed transaction). + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("lane: payments (nonce_key=42, nonce=0)"), + "expected lane breadcrumb on stderr, got: {stderr}", + ); + + // Decode the produced signed Tempo AA transaction and verify it carries the + // resolved 2D nonce key. + let stdout = String::from_utf8_lossy(&output.stdout); + let raw_hex = stdout.trim().trim_start_matches("0x"); + let raw = hex::decode(raw_hex).expect("decode hex output"); + let envelope = TempoTxEnvelope::decode_2718(&mut raw.as_slice()).expect("decode tempo tx"); + assert!(envelope.is_aa(), "expected Tempo AA transaction, got: {envelope:?}"); + assert_eq!(envelope.nonce_key(), Some(U256::from(42_u64))); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -4052,6 +4104,7 @@ Warning: Contract code is empty }); // +#[cfg(feature = "optimism")] casttest!(tx_raw_opstack_deposit, |_prj, cmd| { cmd.args([ "tx", @@ -5048,6 +5101,7 @@ casttest!(cast_decode_tx_network_flag_short_and_long_equivalent, |_prj, cmd| { // Test that `--network optimism` and `-n optimism` produce identical output for decode-tx. // Uses a known OP-stack deposit transaction (same tx as tx_raw_opstack_deposit test). +#[cfg(feature = "optimism")] casttest!(cast_decode_tx_network_optimism_short_and_long_equivalent, |_prj, cmd| { let tx = "0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; @@ -5104,3 +5158,68 @@ casttest!(run_evm_version_updates_gas_params, |_prj, cmd| { "expected Spurious Dragon gas (177241), got: {sd_output}" ); }); + +// Tests for `cast vaddr` JSON output +casttest!(vaddr_create_json_output, |_prj, cmd| { + // Use a pre-computed salt that satisfies the 4-byte PoW requirement for this owner. + // Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 + // Owner: 0x1234567890123456789012345678901234567890 + let out = cmd + .args([ + "--json", + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + "--count", + "2", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let v: serde_json::Value = serde_json::from_str(out.trim()).expect("valid JSON"); + assert_eq!(v["salt"], "0x0000000000000000000000000000000000000000000000003ee0a78d00000000"); + assert_eq!( + v["registration_hash"], + "0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396" + ); + assert_eq!(v["master_id"], "0x2f51c0c4"); + let addrs = v["virtual_addresses"].as_array().expect("array"); + assert_eq!(addrs.len(), 2); + assert_eq!(addrs[0]["tag"], "0x000000000000"); + assert_eq!( + addrs[0]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000000" + ); + assert_eq!(addrs[1]["tag"], "0x000000000001"); + assert_eq!( + addrs[1]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000001" + ); +}); + +casttest!(vaddr_create_plain_output, |_prj, cmd| { + cmd.args([ + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + ]) + .assert_success() + .stdout_eq(str![[r#" +Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 +Registration hash: 0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396 +Master ID: 0x2f51c0c4 + +Virtual addresses: + tag=0x000000000000 [..] + +"#]]); +}); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 659fec7f1a333..0eab12331be04 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -68,3 +68,13 @@ tracing.workspace = true walkdir.workspace = true proptest.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", + "forge-script-sequence/optimism", +] diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 7d6cb9e481502..01de77b9c95fd 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -864,7 +864,7 @@ "func": { "id": "assertApproxEqAbsDecimal_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256,string)", @@ -904,7 +904,7 @@ "func": { "id": "assertApproxEqAbsDecimal_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256,string)", @@ -944,7 +944,7 @@ "func": { "id": "assertApproxEqAbs_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(uint256,uint256,uint256,string)", @@ -984,7 +984,7 @@ "func": { "id": "assertApproxEqAbs_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(int256,int256,uint256,string)", @@ -1024,7 +1024,7 @@ "func": { "id": "assertApproxEqRelDecimal_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256,string)", @@ -1064,7 +1064,7 @@ "func": { "id": "assertApproxEqRelDecimal_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256,string)", @@ -1104,7 +1104,7 @@ "func": { "id": "assertApproxEqRel_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(uint256,uint256,uint256,string)", @@ -1144,7 +1144,7 @@ "func": { "id": "assertApproxEqRel_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(int256,int256,uint256,string)", @@ -1184,7 +1184,7 @@ "func": { "id": "assertEqDecimal_1", "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(uint256,uint256,uint256,string)", @@ -1224,7 +1224,7 @@ "func": { "id": "assertEqDecimal_3", "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(int256,int256,uint256,string)", @@ -1264,7 +1264,7 @@ "func": { "id": "assertEq_1", "description": "Asserts that two `bool` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bool left, bool right, string calldata error) external pure;", + "declaration": "function assertEq(bool left, bool right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool,bool,string)", @@ -1304,7 +1304,7 @@ "func": { "id": "assertEq_11", "description": "Asserts that two `string` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(string calldata left, string calldata right, string calldata error) external pure;", + "declaration": "function assertEq(string calldata left, string calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string,string,string)", @@ -1344,7 +1344,7 @@ "func": { "id": "assertEq_13", "description": "Asserts that two `bytes` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes,bytes,string)", @@ -1384,7 +1384,7 @@ "func": { "id": "assertEq_15", "description": "Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool[],bool[],string)", @@ -1424,7 +1424,7 @@ "func": { "id": "assertEq_17", "description": "Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256[],uint256[],string)", @@ -1464,7 +1464,7 @@ "func": { "id": "assertEq_19", "description": "Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256[],int256[],string)", @@ -1524,7 +1524,7 @@ "func": { "id": "assertEq_21", "description": "Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address[],address[],string)", @@ -1564,7 +1564,7 @@ "func": { "id": "assertEq_23", "description": "Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32[],bytes32[],string)", @@ -1604,7 +1604,7 @@ "func": { "id": "assertEq_25", "description": "Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string[],string[],string)", @@ -1644,7 +1644,7 @@ "func": { "id": "assertEq_27", "description": "Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes[],bytes[],string)", @@ -1664,7 +1664,7 @@ "func": { "id": "assertEq_3", "description": "Asserts that two `uint256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertEq(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256,uint256,string)", @@ -1704,7 +1704,7 @@ "func": { "id": "assertEq_5", "description": "Asserts that two `int256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertEq(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256,int256,string)", @@ -1744,7 +1744,7 @@ "func": { "id": "assertEq_7", "description": "Asserts that two `address` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(address left, address right, string calldata error) external pure;", + "declaration": "function assertEq(address left, address right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address,address,string)", @@ -1784,7 +1784,7 @@ "func": { "id": "assertEq_9", "description": "Asserts that two `bytes32` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32,bytes32,string)", @@ -1824,7 +1824,7 @@ "func": { "id": "assertFalse_1", "description": "Asserts that the given condition is false and includes error message into revert string on failure.", - "declaration": "function assertFalse(bool condition, string calldata error) external pure;", + "declaration": "function assertFalse(bool condition, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertFalse(bool,string)", @@ -1864,7 +1864,7 @@ "func": { "id": "assertGeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(uint256,uint256,uint256,string)", @@ -1904,7 +1904,7 @@ "func": { "id": "assertGeDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(int256,int256,uint256,string)", @@ -1944,7 +1944,7 @@ "func": { "id": "assertGe_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGe(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertGe(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(uint256,uint256,string)", @@ -1984,7 +1984,7 @@ "func": { "id": "assertGe_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGe(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertGe(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(int256,int256,string)", @@ -2024,7 +2024,7 @@ "func": { "id": "assertGtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(uint256,uint256,uint256,string)", @@ -2064,7 +2064,7 @@ "func": { "id": "assertGtDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(int256,int256,uint256,string)", @@ -2104,7 +2104,7 @@ "func": { "id": "assertGt_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGt(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertGt(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(uint256,uint256,string)", @@ -2144,7 +2144,7 @@ "func": { "id": "assertGt_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGt(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertGt(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(int256,int256,string)", @@ -2184,7 +2184,7 @@ "func": { "id": "assertLeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(uint256,uint256,uint256,string)", @@ -2224,7 +2224,7 @@ "func": { "id": "assertLeDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(int256,int256,uint256,string)", @@ -2264,7 +2264,7 @@ "func": { "id": "assertLe_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLe(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertLe(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(uint256,uint256,string)", @@ -2304,7 +2304,7 @@ "func": { "id": "assertLe_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLe(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertLe(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(int256,int256,string)", @@ -2344,7 +2344,7 @@ "func": { "id": "assertLtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(uint256,uint256,uint256,string)", @@ -2384,7 +2384,7 @@ "func": { "id": "assertLtDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(int256,int256,uint256,string)", @@ -2424,7 +2424,7 @@ "func": { "id": "assertLt_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLt(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertLt(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(uint256,uint256,string)", @@ -2464,7 +2464,7 @@ "func": { "id": "assertLt_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLt(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertLt(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(int256,int256,string)", @@ -2504,7 +2504,7 @@ "func": { "id": "assertNotEqDecimal_1", "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(uint256,uint256,uint256,string)", @@ -2544,7 +2544,7 @@ "func": { "id": "assertNotEqDecimal_3", "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(int256,int256,uint256,string)", @@ -2584,7 +2584,7 @@ "func": { "id": "assertNotEq_1", "description": "Asserts that two `bool` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bool left, bool right, string calldata error) external pure;", + "declaration": "function assertNotEq(bool left, bool right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool,bool,string)", @@ -2624,7 +2624,7 @@ "func": { "id": "assertNotEq_11", "description": "Asserts that two `string` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string,string,string)", @@ -2664,7 +2664,7 @@ "func": { "id": "assertNotEq_13", "description": "Asserts that two `bytes` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes,bytes,string)", @@ -2704,7 +2704,7 @@ "func": { "id": "assertNotEq_15", "description": "Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool[],bool[],string)", @@ -2744,7 +2744,7 @@ "func": { "id": "assertNotEq_17", "description": "Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256[],uint256[],string)", @@ -2784,7 +2784,7 @@ "func": { "id": "assertNotEq_19", "description": "Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256[],int256[],string)", @@ -2844,7 +2844,7 @@ "func": { "id": "assertNotEq_21", "description": "Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address[],address[],string)", @@ -2884,7 +2884,7 @@ "func": { "id": "assertNotEq_23", "description": "Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32[],bytes32[],string)", @@ -2924,7 +2924,7 @@ "func": { "id": "assertNotEq_25", "description": "Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string[],string[],string)", @@ -2964,7 +2964,7 @@ "func": { "id": "assertNotEq_27", "description": "Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes[],bytes[],string)", @@ -2984,7 +2984,7 @@ "func": { "id": "assertNotEq_3", "description": "Asserts that two `uint256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256,uint256,string)", @@ -3024,7 +3024,7 @@ "func": { "id": "assertNotEq_5", "description": "Asserts that two `int256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertNotEq(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256,int256,string)", @@ -3064,7 +3064,7 @@ "func": { "id": "assertNotEq_7", "description": "Asserts that two `address` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(address left, address right, string calldata error) external pure;", + "declaration": "function assertNotEq(address left, address right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address,address,string)", @@ -3104,7 +3104,7 @@ "func": { "id": "assertNotEq_9", "description": "Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32,bytes32,string)", @@ -3144,7 +3144,7 @@ "func": { "id": "assertTrue_1", "description": "Asserts that the given condition is true and includes error message into revert string on failure.", - "declaration": "function assertTrue(bool condition, string calldata error) external pure;", + "declaration": "function assertTrue(bool condition, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertTrue(bool,string)", @@ -5447,7 +5447,7 @@ { "func": { "id": "expectEmit_0", - "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", @@ -5487,7 +5487,7 @@ { "func": { "id": "expectEmit_2", - "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit() external;", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 65f181f6b0e35..12cfd19017770 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1082,6 +1082,9 @@ interface Vm { /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; @@ -1093,6 +1096,9 @@ interface Vm { /// Prepare an expected log with all topic and data checks enabled. /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit() external; @@ -1243,7 +1249,7 @@ interface Vm { /// Asserts that the given condition is true and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertTrue(bool condition, string calldata error) external pure; + function assertTrue(bool condition, string calldata err) external pure; /// Asserts that the given condition is false. #[cheatcode(group = Testing, safety = Safe)] @@ -1251,7 +1257,7 @@ interface Vm { /// Asserts that the given condition is false and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertFalse(bool condition, string calldata error) external pure; + function assertFalse(bool condition, string calldata err) external pure; /// Asserts that two `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1259,7 +1265,7 @@ interface Vm { /// Asserts that two `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(bool left, bool right, string calldata err) external pure; /// Asserts that two `uint256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1267,7 +1273,7 @@ interface Vm { /// Asserts that two `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right, string calldata err) external pure; /// Asserts that two `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1275,7 +1281,7 @@ interface Vm { /// Asserts that two `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right, string calldata err) external pure; /// Asserts that two `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1283,7 +1289,7 @@ interface Vm { /// Asserts that two `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(address left, address right, string calldata error) external pure; + function assertEq(address left, address right, string calldata err) external pure; /// Asserts that two `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1291,7 +1297,7 @@ interface Vm { /// Asserts that two `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata err) external pure; /// Asserts that two `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1299,7 +1305,7 @@ interface Vm { /// Asserts that two `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right, string calldata err) external pure; /// Asserts that two `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1307,7 +1313,7 @@ interface Vm { /// Asserts that two `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure; /// Asserts that two arrays of `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1315,7 +1321,7 @@ interface Vm { /// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `uint256 values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1323,7 +1329,7 @@ interface Vm { /// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1331,7 +1337,7 @@ interface Vm { /// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1339,7 +1345,7 @@ interface Vm { /// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1347,7 +1353,7 @@ interface Vm { /// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1355,7 +1361,7 @@ interface Vm { /// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1363,7 +1369,7 @@ interface Vm { /// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1372,7 +1378,7 @@ interface Vm { /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1381,7 +1387,7 @@ interface Vm { /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1389,7 +1395,7 @@ interface Vm { /// Asserts that two `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(bool left, bool right, string calldata err) external pure; /// Asserts that two `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1397,7 +1403,7 @@ interface Vm { /// Asserts that two `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata err) external pure; /// Asserts that two `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1405,7 +1411,7 @@ interface Vm { /// Asserts that two `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right, string calldata err) external pure; /// Asserts that two `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1413,7 +1419,7 @@ interface Vm { /// Asserts that two `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(address left, address right, string calldata err) external pure; /// Asserts that two `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1421,7 +1427,7 @@ interface Vm { /// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure; /// Asserts that two `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1429,7 +1435,7 @@ interface Vm { /// Asserts that two `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata err) external pure; /// Asserts that two `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1437,7 +1443,7 @@ interface Vm { /// Asserts that two `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure; /// Asserts that two arrays of `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1445,7 +1451,7 @@ interface Vm { /// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1453,7 +1459,7 @@ interface Vm { /// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1461,7 +1467,7 @@ interface Vm { /// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1469,7 +1475,7 @@ interface Vm { /// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1477,7 +1483,7 @@ interface Vm { /// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1485,7 +1491,7 @@ interface Vm { /// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1493,7 +1499,7 @@ interface Vm { /// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1502,7 +1508,7 @@ interface Vm { /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1511,7 +1517,7 @@ interface Vm { /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1520,7 +1526,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1529,7 +1535,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. @@ -1539,7 +1545,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. @@ -1549,7 +1555,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1558,7 +1564,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1567,7 +1573,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. @@ -1577,7 +1583,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. @@ -1587,7 +1593,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1596,7 +1602,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1605,7 +1611,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. @@ -1615,7 +1621,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. @@ -1625,7 +1631,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1634,7 +1640,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1643,7 +1649,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. @@ -1653,7 +1659,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. @@ -1663,7 +1669,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] @@ -1672,7 +1678,7 @@ interface Vm { /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] @@ -1681,7 +1687,7 @@ interface Vm { /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. @@ -1696,7 +1702,7 @@ interface Vm { uint256 right, uint256 maxDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. @@ -1712,7 +1718,7 @@ interface Vm { int256 right, uint256 maxDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. @@ -1724,7 +1730,7 @@ interface Vm { /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% @@ -1735,7 +1741,7 @@ interface Vm { /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% @@ -1757,7 +1763,7 @@ interface Vm { uint256 right, uint256 maxPercentDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. @@ -1780,7 +1786,7 @@ interface Vm { int256 right, uint256 maxPercentDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Returns true if the current Foundry version is greater than or equal to the given version. diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index b22f76714dd0f..27545c1b6cd33 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -856,42 +856,6 @@ impl Cheatcodes { } } - // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { - let ctx = MockCallDataContext { - calldata: call.input.bytes(ecx), - value: call.transfer_value(), - }; - - if let Some(return_data_queue) = match mocks.get_mut(&ctx) { - Some(queue) => Some(queue), - None => mocks - .iter_mut() - .find(|(mock, _)| { - call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) - && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) - }) - .map(|(_, v)| v), - } && let Some(return_data) = if return_data_queue.len() == 1 { - // If the mocked calls stack has a single element in it, don't empty it - return_data_queue.front().map(|x| x.to_owned()) - } else { - // Else, we pop the front element - return_data_queue.pop_front() - } { - return Some(CallOutcome { - result: InterpreterResult { - result: return_data.ret_type, - output: return_data.data, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - was_precompile_called: true, - precompile_call_logs: vec![], - }); - } - } - // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` @@ -932,6 +896,72 @@ impl Cheatcodes { } } + // Handle mocked calls + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { + let ctx = MockCallDataContext { + calldata: call.input.bytes(ecx), + value: call.transfer_value(), + }; + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) + && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned()) + { + if let Some(value) = call.transfer_value() { + let checkpoint = ecx.journal_mut().checkpoint(); + match ecx.journal_mut().transfer_loaded( + call.transfer_from(), + call.transfer_to(), + value, + ) { + None => { + if return_data.ret_type.is_ok() { + ecx.journal_mut().checkpoint_commit(); + } else { + ecx.journal_mut().checkpoint_revert(checkpoint); + } + } + Some(err) => { + ecx.journal_mut().checkpoint_revert(checkpoint); + return Some(CallOutcome { + result: InterpreterResult { + result: err.into(), + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], + }); + } + } + } + + // If the mocked calls stack has a single element in it, don't empty it + if return_data_queue.len() > 1 { + return_data_queue.pop_front(); + } + + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], + }); + } + } + // Apply EIP-2930 access list self.apply_accesslist(ecx); @@ -1497,6 +1527,21 @@ impl Inspector> for Cheatcode } } + // this will ensure we don't have false positives when trying to diagnose reverts in fork + // mode + let diag = self.fork_revert_diagnostic.take(); + + // If the call already reverted, preserve that primary failure and skip post-call + // expect* validation so it cannot overwrite the original revert. + if outcome.result.is_revert() { + // if there's a revert and a previous call was diagnosed as fork related revert then we + // can return a better error here + if let Some(err) = diag { + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + } + return; + } + // At the end of the call, // we need to check if we've found all the emits. // We know we've found all the expected emits in the right order @@ -1574,19 +1619,6 @@ impl Inspector> for Cheatcode self.expected_emits.clear() } - // this will ensure we don't have false positives when trying to diagnose reverts in fork - // mode - let diag = self.fork_revert_diagnostic.take(); - - // if there's a revert and a previous call was diagnosed as fork related revert then we can - // return a better error here - if outcome.result.is_revert() - && let Some(err) = diag - { - outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return; - } - // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist if let TxKind::Call(test_contract) = ecx.tx().kind() { @@ -1867,10 +1899,23 @@ impl Inspector> for Cheatcode } // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert + if let Some(expected_revert) = &mut self.expected_revert && curr_depth <= expected_revert.depth && matches!(expected_revert.kind, ExpectedRevertKind::Default) { + // Mirror the logic in `call_end`: when an expected reverter address is set + // and we don't yet have one (or we're matching multiple reverts), record the + // would-be deployed address as the reverter. revm guarantees `outcome.address` + // is `Some(_)` whenever the constructor actually ran (including the revert + // case); it is only `None` for pre-frame rejection (depth/balance/nonce), + // for which a reverter address is meaningless. + if outcome.result.is_revert() + && expected_revert.reverter.is_some() + && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + && let Some(addr) = outcome.address + { + expected_revert.reverted_by = Some(addr); + } let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match revert_handlers::handle_expect_revert( false, diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 632ba8e04245b..12d625768a0c9 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -164,7 +164,7 @@ impl EqRelAssertionError { format_units_uint(&f.left, decimals), format_units_uint(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } @@ -179,7 +179,7 @@ impl EqRelAssertionError { format_units_int(&f.left, decimals), format_units_int(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } @@ -222,9 +222,9 @@ fn handle_assertion_result_mono( /// Implements [crate::Cheatcode] for pairs of cheatcodes. /// /// Accepts a list of pairs of cheatcodes, where the first cheatcode is the one that doesn't contain -/// a custom error message, and the second one contains it at `error` field. +/// a custom error message, and the second one contains it at `err` field. /// -/// Passed `args` are the common arguments for both cheatcode structs (excluding `error` field). +/// Passed `args` are the common arguments for both cheatcode structs (excluding `err` field). /// /// Macro also accepts an optional closure that formats the error returned by the assertion. macro_rules! impl_assertions { @@ -267,10 +267,12 @@ macro_rules! impl_assertions { ccx: &mut CheatsCtxt<'_, '_, FEN>, executor: &mut dyn CheatcodesExecutor, ) -> Result { - let Self { $($arg,)* error } = self; + let Self { $($arg,)* err } = self; match $body { Ok(()) => Ok(Default::default()), - Err(err) => handle_assertion_result(ccx, executor, err, $error_formatter, Some(error)) + Err(assertion_err) => { + handle_assertion_result(ccx, executor, assertion_err, $error_formatter, Some(err)) + } } } } diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs index fb722c2814baa..2b8f81518a621 100644 --- a/crates/cheatcodes/src/version.rs +++ b/crates/cheatcodes/src/version.rs @@ -20,7 +20,14 @@ impl Cheatcode for foundryVersionAtLeastCall { } fn foundry_version_cmp(version: &str) -> Result { - version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) + version_cmp(strip_semver_metadata(SEMVER_VERSION), version) +} + +/// Strips pre-release (e.g. `-nightly`, `-dev`) and build metadata +/// (e.g. `+..`) from a version string +/// so we compare on `MAJOR.MINOR.PATCH` only. +fn strip_semver_metadata(version: &str) -> &str { + version.split(['-', '+']).next().unwrap() } fn version_cmp(version_a: &str, version_b: &str) -> Result { @@ -42,3 +49,61 @@ fn parse_version(version: &str) -> Result { } Ok(version) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strips_build_metadata_only() { + // Tagged release: `1.7.1+..` + assert_eq!(strip_semver_metadata("1.7.1+abc1234567.1737036656.release"), "1.7.1"); + } + + #[test] + fn strips_pre_release_and_build_metadata() { + // Nightly: `1.7.1-nightly+..` + assert_eq!(strip_semver_metadata("1.7.1-nightly+abc1234567.1737036656.release"), "1.7.1"); + // Dev: `1.7.1-dev+..` + assert_eq!(strip_semver_metadata("1.7.1-dev+abc1234567.1737036656.debug"), "1.7.1"); + } + + #[test] + fn strips_plain_version() { + assert_eq!(strip_semver_metadata("1.7.1"), "1.7.1"); + } + + #[test] + fn version_cmp_orders_correctly() { + assert_eq!(version_cmp("1.7.1", "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp("1.7.1", "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "1.7.2").unwrap(), Ordering::Less); + assert_eq!(version_cmp("1.7.1", "0.0.1").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "99.0.0").unwrap(), Ordering::Less); + } + + #[test] + fn parse_version_rejects_pre_release_and_build_metadata() { + // User-supplied versions must be plain `MAJOR.MINOR.PATCH`. + assert!(parse_version("1.7.1-nightly").is_err()); + assert!(parse_version("1.7.1+abc").is_err()); + assert!(parse_version("not-a-version").is_err()); + assert!(parse_version("1.7.1").is_ok()); + } + + #[test] + fn cmp_works_against_full_semver_version_strings() { + // Simulate comparing each shape of `SEMVER_VERSION` against a user-supplied version. + for current in [ + "1.7.1+abc1234567.1737036656.release", + "1.7.1-nightly+abc1234567.1737036656.release", + "1.7.1-dev+abc1234567.1737036656.debug", + "1.7.1", + ] { + let stripped = strip_semver_metadata(current); + assert_eq!(version_cmp(stripped, "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp(stripped, "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp(stripped, "1.7.2").unwrap(), Ordering::Less); + } + } +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index e9a1936013272..bb673c9219e10 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -19,7 +19,6 @@ path = "bin/main.rs" [dependencies] # forge -forge-doc.workspace = true forge-fmt.workspace = true foundry-cli.workspace = true foundry-common.workspace = true @@ -50,7 +49,6 @@ itertools.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true time = { version = "0.3", features = ["formatting"] } yansi.workspace = true tracing.workspace = true @@ -65,8 +63,13 @@ foundry-test-utils.workspace = true rexpect = "0.6" [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index da2c7f4caff02..2ec057b8167c0 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -2,10 +2,7 @@ //! //! This module contains the execution logic for the [SessionSource]. -use crate::{ - prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}, - source::IntermediateOutput, -}; +use crate::prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_json_abi::EventParam; use alloy_primitives::{Address, B256, U256, hex}; @@ -15,7 +12,17 @@ use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, inspectors::CheatsConfig, traces::TraceMode, }; -use solang_parser::pt; +use solar::{ + ast::{BinOpKind, ElementaryType, FunctionKind, LitKind, StateMutability, StrKind, UnOpKind}, + interface::Symbol, + sema::{ + hir::{ + ContractId, Event, Expr, ExprKind, Function, ItemId, Res, StmtKind, Type as HirType, + TypeKind, Visibility, + }, + ty::{Gcx, Ty, TyKind}, + }, +}; use std::ops::ControlFlow; use yansi::Paint; @@ -86,8 +93,10 @@ impl SessionSource { if let Some(err) = err { let output = source_without_inspector.build()?; - let formatted_event = - output.enter(|output| output.get_event(input).map(format_event_definition)); + let formatted_event = output.enter(|output| { + let gcx = output.gcx(); + output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid))) + }); if let Some(formatted_event) = formatted_event { return Ok((ControlFlow::Break(()), Some(formatted_event?))); } @@ -122,30 +131,37 @@ impl SessionSource { // which was wrapped in `abi.encode`. let generated_output = source.build()?; - // If the expression is a variable declaration within the REPL contract, use its type; - // otherwise, attempt to infer the type. - let contract_expr = generated_output - .intermediate - .repl_contract_expressions - .get(input) - .or_else(|| source.infer_inner_expr_type()); + // Inside the compiler closure, infer the DynSolType of the inspected expression and + // determine whether the REPL should continue. + let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> { + let gcx = out.gcx(); - // If the current action is a function call, we get its return type - // otherwise it returns None - let function_call_return_type = - Type::get_function_return_type(contract_expr, &generated_output.intermediate); + // Try direct lookup of `input` as a named variable in the REPL contract. + if let Some(direct_ty) = lookup_named_variable_type(gcx, input) { + return Some((false, direct_ty)); + } - let (contract_expr, ty) = if let Some(function_call_return_type) = function_call_return_type - { - (function_call_return_type.0, function_call_return_type.1) - } else { - match contract_expr.and_then(|e| { - Type::ethabi(e, Some(&generated_output.intermediate)).map(|ty| (e, ty)) - }) { - Some(res) => res, - // this type was denied for inspection, continue - None => return Ok((ControlFlow::Continue(()), None)), + // Otherwise, find the appended `bytes memory inspectoor = abi.encode();` + // and pull out the first call argument. + let block = out.run_func_body(); + let last = block.last()?; + let StmtKind::DeclSingle(vid) = last.kind else { return None }; + let var = gcx.hir.variable(vid); + let init = var.initializer?; + let ExprKind::Call(_callee, args, _) = &init.kind else { return None }; + let inner_expr = args.exprs().next()?; + + // If the call is `func()` returning a single value, prefer the function return type. + if let Some(ty) = get_function_return_type(gcx, inner_expr) { + return Some((should_continue(inner_expr), ty)); } + + let ty = expr_to_dyn(gcx, inner_expr, true)?; + Some((should_continue(inner_expr), ty)) + }); + + let Some((cont, ty)) = res_ty else { + return Ok((ControlFlow::Continue(()), None)); }; // the file compiled correctly, thus the last stack item must be the memory offset of @@ -162,42 +178,10 @@ impl SessionSource { eyre::bail!("Failed to inspect last expression: could not retrieve data from memory") }; let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?; - let c = if should_continue(contract_expr) { - ControlFlow::Continue(()) - } else { - ControlFlow::Break(()) - }; + let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }; Ok((c, Some(format_token(token)))) } - /// Gracefully attempts to extract the type of the expression within the `abi.encode(...)` - /// call inserted by the inspect function. - /// - /// ### Takes - /// - /// A reference to a [SessionSource] - /// - /// ### Returns - /// - /// Optionally, a [Type] - fn infer_inner_expr_type(&self) -> Option<&pt::Expression> { - let out = self.build().ok()?; - let run = out.run_func_body().ok()?.last(); - match run { - Some(pt::Statement::VariableDefinition( - _, - _, - Some(pt::Expression::FunctionCall(_, _, args)), - )) => { - // We can safely unwrap the first expression because this function - // will only be called on a session source that has just had an - // `inspectoor` variable appended to it. - Some(args.first().unwrap()) - } - _ => None, - } - } - async fn build_runner(&mut self, final_pc: usize) -> Result { let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?; @@ -241,6 +225,51 @@ impl SessionSource { } } +/// Looks up `name` as a named variable in the REPL contract (state variables or run() locals) +/// and returns its type as a [`DynSolType`]. +/// +/// Only top-level statements of `run()` are scanned. Variables declared inside nested blocks +/// (`if`, `for`, `while`, `unchecked`, etc.) are not visible here; the caller falls back to +/// the `inspectoor`-based path for those cases. +fn lookup_named_variable_type(gcx: Gcx<'_>, name: &str) -> Option { + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + + // State variables. + for vid in repl.variables() { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + + // Locals declared in run(). + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + for stmt in body.stmts { + match stmt.kind { + StmtKind::DeclSingle(vid) => { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + StmtKind::DeclMulti(vids, _) => { + for vid in vids.iter().flatten() { + let var = hir.variable(*vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item((*vid).into())); + } + } + } + _ => {} + } + } + None +} + /// Formats a value into an inspection message // TODO: Verbosity option fn format_token(token: DynSolValue) -> String { @@ -343,49 +372,37 @@ fn format_token(token: DynSolValue) -> String { } } -/// Formats a [pt::EventDefinition] into an inspection message -/// -/// ### Takes -/// -/// An borrowed [pt::EventDefinition] -/// -/// ### Returns -/// -/// A formatted [pt::EventDefinition] for use in inspection output. +/// Formats an [`Event`] into an inspection message. // TODO: Verbosity option -fn format_event_definition(event_definition: &pt::EventDefinition) -> Result { - let event_name = event_definition.name.as_ref().expect("Event has a name").to_string(); - let inputs = event_definition - .fields +fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result { + let event_name = event.name.as_str().to_string(); + let inputs = event + .parameters .iter() - .map(|param| { - let name = param - .name - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "".to_string()); - let kind = Type::from_expression(¶m.ty) - .and_then(Type::into_builtin) + .map(|&pid| { + let var = gcx.hir.variable(pid); + let name = + var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "".into()); + let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into())) .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?; Ok(EventParam { name, ty: kind.to_string(), components: vec![], - indexed: param.indexed, + indexed: var.indexed, internal_type: None, }) }) .collect::>>()?; - let event = - alloy_json_abi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; + let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous }; Ok(format!( "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}", "event".red(), SolidityHelper::new().highlight(&format!( "{}({})", - &event.name, - &event + event.name, + event .inputs .iter() .map(|param| format!( @@ -395,7 +412,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result>() @@ -411,844 +428,724 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result), - - /// (type, length) - FixedArray(Box, usize), +/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection. +/// +/// `lookup` controls whether user-defined type names are resolved via the HIR. +fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + match &expr.kind { + // Elementary type expression: `uint256`, `address`, etc. + ExprKind::Type(ty) => hir_ty_to_dyn(gcx, ty), + + // `type(T)`: only meaningful as the lhs of a member access. + ExprKind::TypeCall(_) => None, + + // Literals. + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Address(_) => Some(DynSolType::Address), + LitKind::Bool(_) => Some(DynSolType::Bool), + LitKind::Str(kind, _, _) => match kind { + StrKind::Hex => Some(DynSolType::Bytes), + StrKind::Str | StrKind::Unicode => Some(DynSolType::String), + }, + LitKind::Number(_) | LitKind::Rational(_) => Some(DynSolType::Uint(256)), + LitKind::Err(_) => None, + }, + + // Resolved identifier: `foo`. + ExprKind::Ident(reses) => { + let res = reses.first()?; + match *res { + Res::Item(ItemId::Variable(vid)) => { + solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) + } + Res::Item(ItemId::Struct(sid)) => { + // Struct reference used as a constructor produces a tuple of field types. + Some(DynSolType::Tuple( + gcx.struct_field_types(sid) + .iter() + .filter_map(|&t| solar_ty_to_dyn(gcx, t)) + .collect(), + )) + } + // Other items and builtins: handled by enclosing Call/Member expressions. + _ => None, + } + } - /// (type, index) - ArrayIndex(Box, Option), + // Index/access: `arr[i]`, `MyType[]`, `MyType[N]`. + ExprKind::Index(base, idx) => { + let base_ty = expr_to_dyn(gcx, base, lookup)?; + let num = + idx.and_then(|e| parse_number_literal(e)).and_then(|n| usize::try_from(n).ok()); + match &base.kind { + // Type-level indexing builds an array type expression. + ExprKind::Type(_) | ExprKind::TypeCall(_) => { + if let Some(n) = num { + Some(DynSolType::FixedArray(Box::new(base_ty), n)) + } else { + Some(DynSolType::Array(Box::new(base_ty))) + } + } + // Runtime indexing returns the element type. + _ => match base_ty { + DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => Some(*inner), + DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_) => { + Some(DynSolType::FixedBytes(1)) + } + other => Some(other), + }, + } + } - /// (types) - Tuple(Vec>), + // Slice: same type as the base. + ExprKind::Slice(base, _, _) => expr_to_dyn(gcx, base, lookup), - /// (name, params, returns) - Function(Box, Vec>, Vec>), + // Array literal `[a, b, c]`. + ExprKind::Array(values) => values + .first() + .and_then(|e| expr_to_dyn(gcx, e, lookup)) + .map(|ty| DynSolType::FixedArray(Box::new(ty), values.len())), - /// (lhs, rhs) - Access(Box, String), + // Tuple expression `(a, b, c)`. + ExprKind::Tuple(items) => Some(DynSolType::Tuple( + items.iter().filter_map(|opt| opt.and_then(|e| expr_to_dyn(gcx, e, lookup))).collect(), + )), - /// (types) - Custom(Vec), -} + // Member access `lhs.member`. + ExprKind::Member(_, _) => resolve_member(gcx, expr, lookup), -impl Type { - /// Convert a [pt::Expression] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Expression] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_expression(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::Type(_, ty) => Self::from_type(ty), - - pt::Expression::Variable(ident) => Some(Self::Custom(vec![ident.name.clone()])), - - // array - pt::Expression::ArraySubscript(_, expr, num) => { - // if num is Some then this is either an index operation (arr[]) - // or a FixedArray statement (new uint256[]) - Self::from_expression(expr).and_then(|ty| { - let boxed = Box::new(ty); - let num = num.as_deref().and_then(parse_number_literal).and_then(|n| { - usize::try_from(n).ok() - }); - match expr.as_ref() { - // statement - pt::Expression::Type(_, _) => { - if let Some(num) = num { - Some(Self::FixedArray(boxed, num)) - } else { - Some(Self::Array(boxed)) - } - } - // index - pt::Expression::Variable(_) => { - Some(Self::ArrayIndex(boxed, num)) - } - _ => None - } - }) - } - pt::Expression::ArrayLiteral(_, values) => { - values.first().and_then(Self::from_expression).map(|ty| { - Self::FixedArray(Box::new(ty), values.len()) - }) - } + // Function/constructor call. + ExprKind::Call(_, _, _) => resolve_call(gcx, expr, lookup), - // tuple - pt::Expression::List(_, params) => Some(Self::Tuple(map_parameters(params))), + // `new T`: produces a value of type T. + ExprKind::New(ty) => hir_ty_to_dyn(gcx, ty), - // . - pt::Expression::MemberAccess(_, lhs, rhs) => { - Self::from_expression(lhs).map(|lhs| { - Self::Access(Box::new(lhs), rhs.name.clone()) - }) - } + // `payable(addr)`. + ExprKind::Payable(_) => Some(DynSolType::Address), - // - pt::Expression::Parenthesis(_, inner) | // () - pt::Expression::New(_, inner) | // new - pt::Expression::UnaryPlus(_, inner) | // + - // ops - pt::Expression::BitwiseNot(_, inner) | // ~ - pt::Expression::ArraySlice(_, inner, _, _) | // [*start*:*end*] - // assign ops - pt::Expression::PreDecrement(_, inner) | // -- - pt::Expression::PostDecrement(_, inner) | // -- - pt::Expression::PreIncrement(_, inner) | // ++ - pt::Expression::PostIncrement(_, inner) | // ++ - pt::Expression::Assign(_, inner, _) | // = ... - pt::Expression::AssignAdd(_, inner, _) | // += ... - pt::Expression::AssignSubtract(_, inner, _) | // -= ... - pt::Expression::AssignMultiply(_, inner, _) | // *= ... - pt::Expression::AssignDivide(_, inner, _) | // /= ... - pt::Expression::AssignModulo(_, inner, _) | // %= ... - pt::Expression::AssignAnd(_, inner, _) | // &= ... - pt::Expression::AssignOr(_, inner, _) | // |= ... - pt::Expression::AssignXor(_, inner, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, inner, _) | // <<= ... - pt::Expression::AssignShiftRight(_, inner, _) // >>= ... - => Self::from_expression(inner), - - // *condition* ? : - pt::Expression::ConditionalOperator(_, _, if_true, if_false) => { - Self::from_expression(if_true).or_else(|| Self::from_expression(if_false)) - } + // Ternary: prefer truthy branch's type, fall back to else branch. + ExprKind::Ternary(_, t, e) => { + expr_to_dyn(gcx, t, lookup).or_else(|| expr_to_dyn(gcx, e, lookup)) + } - // address - pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), - pt::Expression::HexNumberLiteral(_, s, _) => { - match s.parse::
() { - Ok(addr) if *s == addr.to_checksum(None) => { - Some(Self::Builtin(DynSolType::Address)) + // Delete has no return type. + ExprKind::Delete(_) => None, + + // Unary operations. + ExprKind::Unary(op, inner) => match op.kind { + UnOpKind::Neg => expr_to_dyn(gcx, inner, lookup).map(|ty| match ty { + DynSolType::Uint(n) => DynSolType::Int(n), + DynSolType::Int(n) => DynSolType::Uint(n), + x => x, + }), + UnOpKind::Not => Some(DynSolType::Bool), + UnOpKind::BitNot + | UnOpKind::PreInc + | UnOpKind::PreDec + | UnOpKind::PostInc + | UnOpKind::PostDec => expr_to_dyn(gcx, inner, lookup), + }, + + // Binary operations. + ExprKind::Binary(lhs, op, rhs) => match op.kind { + BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Eq + | BinOpKind::Ne + | BinOpKind::And + | BinOpKind::Or => Some(DynSolType::Bool), + BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul | BinOpKind::Div => { + match (expr_to_dyn(gcx, lhs, false), expr_to_dyn(gcx, rhs, false)) { + (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) + | (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { + Some(DynSolType::Int(256)) } - _ => Some(Self::Builtin(DynSolType::Uint(256))), + _ => Some(DynSolType::Uint(256)), } } + BinOpKind::Rem + | BinOpKind::Pow + | BinOpKind::BitAnd + | BinOpKind::BitOr + | BinOpKind::BitXor + | BinOpKind::Shl + | BinOpKind::Shr + | BinOpKind::Sar => Some(DynSolType::Uint(256)), + }, + + // Assignments: type of the lhs. + ExprKind::Assign(lhs, _, _) => expr_to_dyn(gcx, lhs, lookup), + + ExprKind::Err(_) => None, + } +} - // uint and int - // invert - pt::Expression::Negate(_, inner) => Self::from_expression(inner).map(Self::invert_int), - - // int if either operand is int - // TODO: will need an update for Solidity v0.8.18 user defined operators: - // https://github.com/ethereum/solidity/issues/13718#issuecomment-1341058649 - pt::Expression::Add(_, lhs, rhs) | - pt::Expression::Subtract(_, lhs, rhs) | - pt::Expression::Multiply(_, lhs, rhs) | - pt::Expression::Divide(_, lhs, rhs) => { - match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) | -(Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { - Some(Self::Builtin(DynSolType::Int(256))) - } - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) - } +/// Converts a [`HirType`] to a [`DynSolType`]. +fn hir_ty_to_dyn(gcx: Gcx<'_>, ty: &HirType<'_>) -> Option { + match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + TypeKind::Array(arr) => { + let elem = hir_ty_to_dyn(gcx, &arr.element)?; + if let Some(size) = arr.size { + let n = parse_number_literal(size).and_then(|n| usize::try_from(n).ok()); + if let Some(n) = n { + Some(DynSolType::FixedArray(Box::new(elem), n)) + } else { + Some(DynSolType::Array(Box::new(elem))) } + } else { + Some(DynSolType::Array(Box::new(elem))) } - - // always assume uint - pt::Expression::Modulo(_, _, _) | - pt::Expression::Power(_, _, _) | - pt::Expression::BitwiseOr(_, _, _) | - pt::Expression::BitwiseAnd(_, _, _) | - pt::Expression::BitwiseXor(_, _, _) | - pt::Expression::ShiftRight(_, _, _) | - pt::Expression::ShiftLeft(_, _, _) | - pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(DynSolType::Uint(256))), - - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(_, _, _, _, _) => { - Some(Self::Builtin(DynSolType::Uint(256))) + } + TypeKind::Function(f) => match f.returns.len() { + 0 => None, + 1 => { + let var = gcx.hir.variable(f.returns[0]); + hir_ty_to_dyn(gcx, &var.ty) } + _ => Some(DynSolType::Tuple( + f.returns + .iter() + .filter_map(|&pid| hir_ty_to_dyn(gcx, &gcx.hir.variable(pid).ty)) + .collect(), + )), + }, + TypeKind::Mapping(m) => hir_ty_to_dyn(gcx, &m.value), + TypeKind::Custom(item) => solar_ty_to_dyn(gcx, gcx.type_of_item(*item)), + TypeKind::Err(_) => None, + } +} - // bool - pt::Expression::BoolLiteral(_, _) | - pt::Expression::And(_, _, _) | - pt::Expression::Or(_, _, _) | - pt::Expression::Equal(_, _, _) | - pt::Expression::NotEqual(_, _, _) | - pt::Expression::Less(_, _, _) | - pt::Expression::LessEqual(_, _, _) | - pt::Expression::More(_, _, _) | - pt::Expression::MoreEqual(_, _, _) | - pt::Expression::Not(_, _) => Some(Self::Builtin(DynSolType::Bool)), - - // string - pt::Expression::StringLiteral(_) => Some(Self::Builtin(DynSolType::String)), - - // bytes - pt::Expression::HexLiteral(_) => Some(Self::Builtin(DynSolType::Bytes)), - - // function - pt::Expression::FunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(Self::from_expression).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } - pt::Expression::NamedFunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(|arg| Self::from_expression(&arg.expr)).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } +/// Resolves a member-access expression (`lhs.member`) to its [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Member`. +fn resolve_member(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Member(lhs, ident) = &expr.kind else { return None }; + let member = ident.name; + + // `type(T).member` — type introspection. + if let ExprKind::TypeCall(ty) = &lhs.kind { + return match member.as_str() { + "name" => Some(DynSolType::String), + "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), + "interfaceId" => Some(DynSolType::FixedBytes(4)), + // Only valid for integer types; custom types (enums) fall back to Uint(256). + "min" | "max" => match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + _ => Some(DynSolType::Uint(256)), + }, + _ => None, + }; + } - // explicitly None - pt::Expression::Delete(_, _) | pt::Expression::FunctionCallBlock(_, _, _) => None, - } + // Built-in namespace identifier: `block.timestamp`, `msg.sender`, `abi.encode`, etc. + if let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + && let Some(ty) = builtin_member(b.name().as_str(), member.as_str()) + { + return Some(ty); } - /// Convert a [pt::Type] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Type] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_type(ty: &pt::Type) -> Option { - let ty = match ty { - pt::Type::Address | pt::Type::AddressPayable | pt::Type::Payable => { - Self::Builtin(DynSolType::Address) - } - pt::Type::Bool => Self::Builtin(DynSolType::Bool), - pt::Type::String => Self::Builtin(DynSolType::String), - pt::Type::Int(size) => Self::Builtin(DynSolType::Int(*size as usize)), - pt::Type::Uint(size) => Self::Builtin(DynSolType::Uint(*size as usize)), - pt::Type::Bytes(size) => Self::Builtin(DynSolType::FixedBytes(*size as usize)), - pt::Type::DynamicBytes => Self::Builtin(DynSolType::Bytes), - pt::Type::Mapping { value, .. } => Self::from_expression(value)?, - pt::Type::Function { params, returns, .. } => { - let params = map_parameters(params); - let returns = returns - .as_ref() - .map(|(returns, _)| map_parameters(returns)) - .unwrap_or_default(); - Self::Function( - Box::new(Self::Custom(vec!["__fn_type__".to_string()])), - params, - returns, - ) - } - // TODO: Rational numbers - pt::Type::Rational => return None, + // Elementary type used as a namespace: `address.balance`, `bytes.concat`, etc. + if let ExprKind::Type(ty) = &lhs.kind + && let TypeKind::Elementary(et) = &ty.kind + { + return match et { + ElementaryType::Address(_) => match member.as_str() { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + ElementaryType::Bytes => match member.as_str() { + "concat" => Some(DynSolType::Bytes), + _ => None, + }, + ElementaryType::String => match member.as_str() { + "concat" => Some(DynSolType::String), + _ => None, + }, + _ => None, }; - Some(ty) } - /// Handle special expressions like [global variables](https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables) - /// - /// See: - fn map_special(self) -> Self { - if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { - return self; - } + // Members on a resolved DynSolType (`.length`, `.pop`, `.selector`, `.address`). + if let Some(lhs_ty) = expr_to_dyn(gcx, lhs, lookup) + && let Some(ty) = dyn_member(&lhs_ty, member.as_str()) + { + return Some(ty); + } - let mut types = Vec::with_capacity(5); - let mut args = None; - self.recurse(&mut types, &mut args); + // HIR lookup for user-defined type members. + if lookup && let Some(mut chain) = expr_name_chain(gcx, lhs) { + chain.insert(0, member); + return infer_custom_type(gcx, &mut chain, None).ok().flatten(); + } - let len = types.len(); - if len == 0 { - return self; - } + None +} + +/// Returns the type of `builtin_ns.member` for built-in global namespaces. +fn builtin_member(builtin: &str, member: &str) -> Option { + match builtin { + "block" => match member { + "coinbase" => Some(DynSolType::Address), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | "chainid" + | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), + _ => None, + }, + "msg" => match member { + "sender" => Some(DynSolType::Address), + "gas" | "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), + _ => None, + }, + "tx" => match member { + "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), + _ => None, + }, + "address" => match member { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + _ => None, + } +} + +/// Returns the type of `ty.member` for a known [`DynSolType`]. +fn dyn_member(ty: &DynSolType, member: &str) -> Option { + match member { + "length" => match ty { + DynSolType::Array(_) + | DynSolType::FixedArray(_, _) + | DynSolType::Bytes + | DynSolType::String + | DynSolType::FixedBytes(_) => Some(DynSolType::Uint(256)), + _ => None, + }, + "pop" => match ty { + DynSolType::Array(inner) => Some(*inner.clone()), + _ => None, + }, + // Address members. + "balance" => match ty { + DynSolType::Address => Some(DynSolType::Uint(256)), + _ => None, + }, + "code" => match ty { + DynSolType::Address => Some(DynSolType::Bytes), + _ => None, + }, + "codehash" => match ty { + DynSolType::Address => Some(DynSolType::FixedBytes(32)), + _ => None, + }, + "send" => match ty { + DynSolType::Address => Some(DynSolType::Bool), + _ => None, + }, + // External function members. + "selector" => Some(DynSolType::FixedBytes(4)), + "address" => Some(DynSolType::Address), + _ => None, + } +} - // Type members, like array, bytes etc - #[expect(clippy::single_match)] - #[allow(clippy::collapsible_match)] - match &self { - Self::Access(inner, access) => { - if let Some(ty) = inner.as_ref().clone().try_as_ethabi(None) { - // Array / bytes members - let ty = Self::Builtin(ty); - match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { - return Self::Builtin(DynSolType::Uint(256)); +/// Resolves a call expression to its return [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Call`. +fn resolve_call(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Call(callee, args, _named) = &expr.kind else { return None }; + + // Type cast: `uint256(x)`, `address(y)`, etc. + if let ExprKind::Type(ty) = &callee.kind { + return hir_ty_to_dyn(gcx, ty); + } + + // Member call: `ns.method(...)`. + if let ExprKind::Member(lhs, method) = &callee.kind + && let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + { + match b.name().as_str() { + "abi" => { + return match method.as_str() { + "decode" => { + let last = args.exprs().last()?; + match expr_to_dyn(gcx, last, false)? { + DynSolType::Tuple(tys) => Some(DynSolType::Tuple(tys)), + ty => Some(DynSolType::Tuple(vec![ty])), } - "pop" if ty.is_dynamic_array() => return ty, - _ => {} } - } + s if s.starts_with("encode") => Some(DynSolType::Bytes), + _ => None, + }; } + "string" if method.as_str() == "concat" => return Some(DynSolType::String), + "bytes" if method.as_str() == "concat" => return Some(DynSolType::Bytes), _ => {} } + } - let this = { - let name = types.last().unwrap().as_str(); - match len { - 0 => unreachable!(), - 1 => match name { + // Simple identifier call: built-in global functions and HIR function calls. + if let ExprKind::Ident(reses) = &callee.kind { + match reses.first() { + Some(Res::Builtin(b)) => { + return match b.name().as_str() { "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)), "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)), "ripemd160" => Some(DynSolType::FixedBytes(20)), "ecrecover" => Some(DynSolType::Address), _ => None, - }, - 2 => { - let access = types.first().unwrap().as_str(); - match name { - "block" => match access { - "coinbase" => Some(DynSolType::Address), - "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" - | "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), - _ => None, - }, - "msg" => match access { - "sender" => Some(DynSolType::Address), - "gas" => Some(DynSolType::Uint(256)), - "value" => Some(DynSolType::Uint(256)), - "data" => Some(DynSolType::Bytes), - "sig" => Some(DynSolType::FixedBytes(4)), - _ => None, - }, - "tx" => match access { - "origin" => Some(DynSolType::Address), - "gasprice" => Some(DynSolType::Uint(256)), - _ => None, - }, - "abi" => match access { - "decode" => { - // args = Some([Bytes(_), Tuple(args)]) - // unwrapping is safe because this is first compiled by solc so - // it is guaranteed to be a valid call - let mut args = args.unwrap(); - let last = args.pop().unwrap(); - match last { - Some(ty) => { - return match ty { - Self::Tuple(_) => ty, - ty => Self::Tuple(vec![Some(ty)]), - }; - } - None => None, - } - } - s if s.starts_with("encode") => Some(DynSolType::Bytes), - _ => None, - }, - "address" => match access { - "balance" => Some(DynSolType::Uint(256)), - "code" => Some(DynSolType::Bytes), - "codehash" => Some(DynSolType::FixedBytes(32)), - "send" => Some(DynSolType::Bool), - _ => None, - }, - "type" => match access { - "name" => Some(DynSolType::String), - "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), - "interfaceId" => Some(DynSolType::FixedBytes(4)), - "min" | "max" => Some( - // Either a builtin or an enum - (|| args?.pop()??.into_builtin())() - .unwrap_or(DynSolType::Uint(256)), - ), - _ => None, - }, - "string" => match access { - "concat" => Some(DynSolType::String), - _ => None, - }, - "bytes" => match access { - "concat" => Some(DynSolType::Bytes), - _ => None, - }, - _ => None, - } - } - _ => None, - } - }; - - this.map(Self::Builtin).unwrap_or_else(|| match types.last().unwrap().as_str() { - "this" | "super" => Self::Custom(types), - _ => match self { - Self::Custom(_) | Self::Access(_, _) => Self::Custom(types), - Self::Function(_, _, _) => self, - _ => unreachable!(), - }, - }) - } - - /// Recurses over itself, appending all the idents and function arguments in the order that they - /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { - match self { - Self::Builtin(ty) => types.push(ty.to_string()), - Self::Custom(tys) => types.extend(tys.clone()), - Self::Access(expr, name) => { - types.push(name.clone()); - expr.recurse(types, args); + }; } - Self::Function(fn_name, fn_args, _fn_ret) => { - if args.is_none() && !fn_args.is_empty() { - *args = Some(fn_args.clone()); + Some(Res::Item(ItemId::Function(fid))) if lookup => { + let func = gcx.hir.function(*fid); + if !matches!(func.state_mutability, StateMutability::View | StateMutability::Pure) { + return None; } - fn_name.recurse(types, args); + let ret_id = *func.returns.first()?; + return solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())); } _ => {} } } - /// Infers a custom type's true type by recursing up the parse tree - /// - /// ### Takes - /// - A reference to the [IntermediateOutput] - /// - An array of custom types generated by the `MemberAccess` arm of [Self::from_expression] - /// - An optional contract name. This should always be `None` when this function is first - /// called. - /// - /// ### Returns - /// - /// If successful, an `Ok(Some(DynSolType))` variant. - /// If gracefully failed, an `Ok(None)` variant. - /// If failed, an `Err(e)` variant. - fn infer_custom_type( - intermediate: &IntermediateOutput, - custom_type: &mut Vec, - contract_name: Option, - ) -> Result> { - if let Some("this" | "super") = custom_type.last().map(String::as_str) { - custom_type.pop(); + // Fall back to the callee's resolved type. + expr_to_dyn(gcx, callee, lookup) +} + +/// Extracts a name chain from a member-access expression tree for HIR lookup. +/// +/// The chain is ordered outermost-first so `a.b.c` produces `["c", "b", "a"]` with the root +/// identifier at the back. This matches the convention expected by [`infer_custom_type`]. +fn expr_name_chain(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option> { + match &expr.kind { + ExprKind::Ident(reses) => { + let res = reses.first()?; + let name = match *res { + Res::Item(ItemId::Variable(vid)) => gcx.hir.variable(vid).name?.name, + Res::Item(ItemId::Function(fid)) => gcx.hir.function(fid).name?.name, + Res::Item(ItemId::Contract(cid)) => gcx.hir.contract(cid).name.name, + Res::Builtin(b) => b.name(), + _ => return None, + }; + Some(vec![name]) } - if custom_type.is_empty() { - return Ok(None); + ExprKind::Member(lhs, ident) => { + let mut chain = expr_name_chain(gcx, lhs)?; + chain.insert(0, ident.name); + Some(chain) } + _ => None, + } +} - // If a contract exists with the given name, check its definitions for a match. - // Otherwise look in the `run` - if let Some(contract_name) = contract_name { - let intermediate_contract = intermediate - .intermediate_contracts - .get(&contract_name) - .ok_or_else(|| eyre::eyre!("Could not find intermediate contract!"))?; - - let cur_type = custom_type.last().unwrap(); - if let Some(func) = intermediate_contract.function_definitions.get(cur_type) { - // Check if the custom type is a function pointer member access - if let res @ Some(_) = func_members(func, custom_type) { - return Ok(res); - } - - // Because tuple types cannot be passed to `abi.encode`, we will only be - // receiving functions that have 0 or 1 return parameters here. - if func.returns.is_empty() { - eyre::bail!( - "This call expression does not return any values to inspect. Insert as statement." - ) - } +/// Infers a custom type's true type by recursing through the HIR. +/// +/// `custom_type` is a name chain ordered outermost-first (root at back). This is mutated during +/// resolution. `contract_id` narrows the search to a specific contract scope. +fn infer_custom_type( + gcx: Gcx<'_>, + custom_type: &mut Vec, + contract_id: Option, +) -> Result> { + if let Some(last) = custom_type.last() + && (last.as_str() == "this" || last.as_str() == "super") + { + custom_type.pop(); + } + if custom_type.is_empty() { + return Ok(None); + } - // Empty return types check is done above - let (_, param) = func.returns.first().unwrap(); - // Return type should always be present - let return_ty = ¶m.as_ref().unwrap().ty; - - // If the return type is a variable (not a type expression), re-enter the recursion - // on the same contract for a variable / struct search. It could be a contract, - // struct, array, etc. - if let pt::Expression::Variable(ident) = return_ty { - custom_type.push(ident.name.clone()); - return Self::infer_custom_type(intermediate, custom_type, Some(contract_name)); - } + if let Some(cid) = contract_id { + let hir = &gcx.hir; + let contract = hir.contract(cid); - // Check if our final function call alters the state. If it does, we bail so that it - // will be inserted normally without inspecting. If the state mutability was not - // expressly set, the function is inferred to alter state. - if let Some(pt::FunctionAttribute::Mutability(_mut)) = func - .attributes - .iter() - .find(|attr| matches!(attr, pt::FunctionAttribute::Mutability(_))) - { - if let pt::Mutability::Payable(_) = _mut { - eyre::bail!("This function mutates state. Insert as a statement.") - } - } else { - eyre::bail!("This function mutates state. Insert as a statement.") - } + let cur_name = *custom_type.last().unwrap(); + let cur = cur_name.as_str(); - Ok(Self::ethabi(return_ty, Some(intermediate))) - } else if let Some(var) = intermediate_contract.variable_definitions.get(cur_type) { - Self::infer_var_expr(&var.ty, Some(intermediate), custom_type) - } else if let Some(strukt) = intermediate_contract.struct_definitions.get(cur_type) { - let inner_types = strukt - .fields - .iter() - .map(|var| { - Self::ethabi(&var.ty, Some(intermediate)) - .ok_or_else(|| eyre::eyre!("Struct `{cur_type}` has invalid fields")) - }) - .collect::>>()?; - Ok(Some(DynSolType::Tuple(inner_types))) - } else { - eyre::bail!( - "Could not find any definition in contract \"{contract_name}\" for type: {custom_type:?}" - ) - } - } else { - // Check if the custom type is a variable or function within the REPL contract before - // anything. If it is, we can stop here. - if let Ok(res) = Self::infer_custom_type(intermediate, custom_type, Some("REPL".into())) - { + // Function? + if let Some(fid) = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + let func = hir.function(fid); + if let res @ Some(_) = func_members(func, custom_type) { return Ok(res); } - // Check if the first element of the custom type is a known contract. If it is, begin - // our recursion on that contract's definitions. - let name = custom_type.last().unwrap(); - let contract = intermediate.intermediate_contracts.get(name); - if contract.is_some() { - let contract_name = custom_type.pop(); - return Self::infer_custom_type(intermediate, custom_type, contract_name); + if func.returns.is_empty() { + eyre::bail!( + "This call expression does not return any values to inspect. Insert as statement." + ) } - // See [`Type::infer_var_expr`] - let name = custom_type.last().unwrap(); - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - return Self::infer_var_expr(expr, Some(intermediate), custom_type); + let sm = func.state_mutability; + if !matches!(sm, StateMutability::View | StateMutability::Pure) { + eyre::bail!("This function mutates state. Insert as a statement.") } - // The first element of our custom type was neither a variable or a function within the - // REPL contract, move on to globally available types gracefully. - Ok(None) + let ret_id = func.returns[0]; + let ret_var = hir.variable(ret_id); + return Ok(solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) + .or_else(|| hir_ty_to_dyn(gcx, &ret_var.ty))); } - } - /// Infers the type from a variable's type - fn infer_var_expr( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - custom_type: &mut Vec, - ) -> Result> { - // Resolve local (in `run` function) or global (in the `REPL` or other contract) variable - let res = match &expr { - // Custom variable handling - pt::Expression::Variable(ident) => { - let name = &ident.name; - - if let Some(intermediate) = intermediate { - // expression in `run` - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - Self::infer_var_expr(expr, Some(intermediate), custom_type) - } else if intermediate.intermediate_contracts.contains_key(name) { - if custom_type.len() > 1 { - // There is still some recursing left to do: jump into the contract. - custom_type.pop(); - Self::infer_custom_type(intermediate, custom_type, Some(name.clone())) - } else { - // We have no types left to recurse: return the address of the contract. - Ok(Some(DynSolType::Address)) - } - } else { - Err(eyre::eyre!("Could not infer variable type")) - } - } else { - Ok(None) - } - } - other_expr => Ok(Self::ethabi(other_expr, intermediate)), - }; - // re-run everything with the resolved variable in case we're accessing a builtin member - // for example array or bytes length etc - match res { - Ok(Some(ty)) => { - let box_ty = Box::new(Self::Builtin(ty.clone())); - let access = Self::Access(box_ty, custom_type.drain(..).next().unwrap_or_default()); - if let Some(mapped) = access.map_special().try_as_ethabi(intermediate) { - Ok(Some(mapped)) - } else { - Ok(Some(ty)) + // Variable? + if let Some(vid) = contract + .variables() + .find(|&v| hir.variable(v).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + if let Some(ty) = solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) { + custom_type.pop(); + if custom_type.is_empty() { + return Ok(Some(ty)); } + let next_member = custom_type.drain(..).next().unwrap_or(Symbol::DUMMY); + return Ok(dyn_member(&ty, next_member.as_str()).or(Some(ty))); } - res => res, - } - } - - /// Attempt to convert this type into a [DynSolType] - /// - /// ### Takes - /// An immutable reference to an [IntermediateOutput] - /// - /// ### Returns - /// Optionally, a [DynSolType] - fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - Self::Tuple(types) => Some(DynSolType::Tuple(types_to_parameters(types, intermediate))), - Self::Array(inner) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::Array(Box::new(inner))), - }, - Self::FixedArray(inner, size) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::FixedArray(Box::new(inner), size)), - }, - ty @ Self::ArrayIndex(_, _) => ty.into_array_index(intermediate), - Self::Function(ty, _, _) => ty.try_as_ethabi(intermediate), - // should have been mapped to `Custom` in previous steps - Self::Access(_, _) => None, - Self::Custom(mut types) => { - // Cover any local non-state-modifying function call expressions - intermediate.and_then(|intermediate| { - Self::infer_custom_type(intermediate, &mut types, None).ok().flatten() - }) - } + let var = hir.variable(vid); + return infer_var_ty(gcx, &var.ty, custom_type); } - } - - /// Equivalent to `Type::from_expression` + `Type::map_special` + `Type::try_as_ethabi` - fn ethabi( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - ) -> Option { - Self::from_expression(expr) - .map(Self::map_special) - .and_then(|ty| ty.try_as_ethabi(intermediate)) - } - /// Get the return type of a function call expression. - fn get_function_return_type<'a>( - contract_expr: Option<&'a pt::Expression>, - intermediate: &IntermediateOutput, - ) -> Option<(&'a pt::Expression, DynSolType)> { - let function_call = match contract_expr? { - pt::Expression::FunctionCall(_, function_call, _) => function_call, - _ => return None, - }; - let (contract_name, function_name) = match function_call.as_ref() { - pt::Expression::MemberAccess(_, contract_name, function_name) => { - (contract_name, function_name) + // Struct? + if let Some(sid) = contract.items.iter().find_map(|i| { + if let ItemId::Struct(sid) = i + && hir.strukt(*sid).name.as_str() == cur + { + Some(*sid) + } else { + None } - _ => return None, - }; - let contract_name = match contract_name.as_ref() { - pt::Expression::Variable(contract_name) => contract_name.to_owned(), - _ => return None, - }; - - let pt::Expression::Variable(contract_name) = - intermediate.repl_contract_expressions.get(&contract_name.name)? - else { - return None; - }; - - let contract = intermediate - .intermediate_contracts - .get(&contract_name.name)? - .function_definitions - .get(&function_name.name)?; - let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) - } - - /// Inverts Int to Uint and vice-versa. - fn invert_int(self) -> Self { - match self { - Self::Builtin(DynSolType::Uint(n)) => Self::Builtin(DynSolType::Int(n)), - Self::Builtin(DynSolType::Int(n)) => Self::Builtin(DynSolType::Uint(n)), - x => x, + }) { + let inner = gcx + .struct_field_types(sid) + .iter() + .map(|&t| { + solar_ty_to_dyn(gcx, t) + .ok_or_else(|| eyre::eyre!("Struct `{cur}` has invalid fields")) + }) + .collect::>>()?; + return Ok(Some(DynSolType::Tuple(inner))); } - } - /// Returns the `DynSolType` contained by `Type::Builtin` - #[inline] - fn into_builtin(self) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - _ => None, - } + eyre::bail!( + "Could not find any definition in contract \"{}\" for type: {custom_type:?}", + contract.name.as_str() + ) } - /// Returns the resulting `DynSolType` of indexing self - fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { - match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner) | DynSolType::FixedArray(inner, _)) => { - Some(*inner) - } - Some(DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_)) => { - Some(DynSolType::FixedBytes(1)) - } - ty => ty, - } - } - _ => None, - } + let repl_id = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == "REPL").then_some(cid)); + if let Some(repl_id) = repl_id + && let Ok(res) = infer_custom_type(gcx, custom_type, Some(repl_id)) + { + return Ok(res); } - /// Returns whether this type is dynamic - #[inline] - const fn is_dynamic(&self) -> bool { - match self { - // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are - // not dynamic, nor are tuples of non-dynamic types. - Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, - Self::Array(_) => true, - _ => false, - } + let last_name = *custom_type.last().unwrap(); + let last = last_name.as_str(); + let contract_match = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == last).then_some(cid)); + if let Some(cid) = contract_match { + custom_type.pop(); + return infer_custom_type(gcx, custom_type, Some(cid)); } - /// Returns whether this type is an array - #[inline] - const fn is_array(&self) -> bool { - matches!( - self, - Self::Array(_) - | Self::FixedArray(_, _) - | Self::Builtin(DynSolType::Array(_) | DynSolType::FixedArray(_, _)) - ) - } + Ok(None) +} - /// Returns whether this type is a dynamic array (can call push, pop) - #[inline] - const fn is_dynamic_array(&self) -> bool { - matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) +/// Infers the type from a variable's HIR type, optionally accessing a named member. +fn infer_var_ty( + gcx: Gcx<'_>, + ty: &HirType<'_>, + custom_type: &mut Vec, +) -> Result> { + let Some(ty) = hir_ty_to_dyn(gcx, ty) else { return Ok(None) }; + let next_member = custom_type.drain(..).next(); + if let Some(m) = next_member { + Ok(dyn_member(&ty, m.as_str()).or(Some(ty))) + } else { + Ok(Some(ty)) } +} - const fn is_fixed_bytes(&self) -> bool { - matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) - } +/// Get the return type of a contract method call `receiver.method()`. +fn get_function_return_type(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option { + let ExprKind::Call(callee, _, _) = &expr.kind else { return None }; + let ExprKind::Member(obj, fn_ident) = &callee.kind else { return None }; + let ExprKind::Ident(reses) = &obj.kind else { return None }; + let res = reses.first()?; + let var_id = match res { + Res::Item(ItemId::Variable(vid)) => *vid, + _ => return None, + }; + let var_ty = gcx.type_of_item(var_id.into()).peel_refs(); + let cid = match var_ty.kind { + TyKind::Contract(cid) => cid, + _ => return None, + }; + + let hir = &gcx.hir; + let contract = hir.contract(cid); + let fid = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some(fn_ident.as_str()))?; + let func = hir.function(fid); + let ret_id = *func.returns.first()?; + solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) } -/// Returns Some if the custom type is a function member access +/// Returns Some if the custom type is a function member access. /// /// Ref: #[inline] -fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { - if !matches!(func.ty, pt::FunctionTy::Function) { +fn func_members(func: &Function<'_>, custom_type: &[Symbol]) -> Option { + if !matches!(func.kind, FunctionKind::Function) { return None; } - - let vis = func.attributes.iter().find_map(|attr| match attr { - pt::FunctionAttribute::Visibility(vis) => Some(vis), - _ => None, - }); - match vis { - Some(pt::Visibility::External(_) | pt::Visibility::Public(_)) => { - match custom_type.first().unwrap().as_str() { - "address" => Some(DynSolType::Address), - "selector" => Some(DynSolType::FixedBytes(4)), - _ => None, - } - } + if !matches!(func.visibility, Visibility::External | Visibility::Public) { + return None; + } + match custom_type.first().unwrap().as_str() { + "address" => Some(DynSolType::Address), + "selector" => Some(DynSolType::FixedBytes(4)), _ => None, } } -/// Whether execution should continue after inspecting this expression +/// Whether execution should continue after inspecting this expression. #[inline] -fn should_continue(expr: &pt::Expression) -> bool { - match expr { - // assignments - pt::Expression::PreDecrement(_, _) | // -- - pt::Expression::PostDecrement(_, _) | // -- - pt::Expression::PreIncrement(_, _) | // ++ - pt::Expression::PostIncrement(_, _) | // ++ - pt::Expression::Assign(_, _, _) | // = ... - pt::Expression::AssignAdd(_, _, _) | // += ... - pt::Expression::AssignSubtract(_, _, _) | // -= ... - pt::Expression::AssignMultiply(_, _, _) | // *= ... - pt::Expression::AssignDivide(_, _, _) | // /= ... - pt::Expression::AssignModulo(_, _, _) | // %= ... - pt::Expression::AssignAnd(_, _, _) | // &= ... - pt::Expression::AssignOr(_, _, _) | // |= ... - pt::Expression::AssignXor(_, _, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, _, _) | // <<= ... - pt::Expression::AssignShiftRight(_, _, _) // >>= ... - => { - true - } - +fn should_continue(expr: &Expr<'_>) -> bool { + match &expr.kind { + // assignments and compound assignments + ExprKind::Assign(_, _, _) => true, + // ++/-- pre/post operations + ExprKind::Unary(op, _) => matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ), // Array.pop() - pt::Expression::FunctionCall(_, lhs, _) => { - match lhs.as_ref() { - pt::Expression::MemberAccess(_, _inner, access) => access.name == "pop", - _ => false - } - } - - _ => false + ExprKind::Call(callee, _, _) => match &callee.kind { + ExprKind::Member(_, ident) => ident.as_str() == "pop", + _ => false, + }, + _ => false, } } -fn map_parameters(params: &[(pt::Loc, Option)]) -> Vec> { - params - .iter() - .map(|(_, param)| param.as_ref().and_then(|param| Type::from_expression(¶m.ty))) - .collect() +/// Parses an [`Expr`] number/hex literal into a `U256`. Returns `None` if the expression +/// is not a numeric literal. +/// +/// SubDenominations are already applied to numeric literals in solar's HIR. +const fn parse_number_literal(expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Number(n) => Some(*n), + _ => None, + }, + _ => None, + } } -fn types_to_parameters( - types: Vec>, - intermediate: Option<&IntermediateOutput>, -) -> Vec { - types.into_iter().filter_map(|ty| ty.and_then(|ty| ty.try_as_ethabi(intermediate))).collect() +/// Maps a solar [`ElementaryType`] to a [`DynSolType`]. +const fn elementary_to_dyn(et: ElementaryType) -> Option { + Some(match et { + ElementaryType::Address(_) => DynSolType::Address, + ElementaryType::Bool => DynSolType::Bool, + ElementaryType::String => DynSolType::String, + ElementaryType::Bytes => DynSolType::Bytes, + ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize), + ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize), + ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize), + // Fixed-point numbers are not yet representable as DynSolType. + ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None, + }) } -fn parse_number_literal(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::NumberLiteral(_, num, exp, unit) => { - let num = num.parse::().unwrap_or(U256::ZERO); - let exp = exp.parse().unwrap_or(0u32); - if exp > 77 { - None +/// Maps a solar [`Ty`] to a [`DynSolType`]. +fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option { + match ty.kind { + TyKind::Elementary(et) => elementary_to_dyn(et), + TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Array(elem, n) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + let size: usize = n.try_into().ok()?; + Some(DynSolType::FixedArray(Box::new(inner), size)) + } + TyKind::DynArray(elem) | TyKind::Slice(elem) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + Some(DynSolType::Array(Box::new(inner))) + } + TyKind::Tuple(tys) => { + Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect())) + } + TyKind::Mapping(_, _) => None, + TyKind::Struct(sid) => Some(DynSolType::Tuple( + gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + TyKind::Enum(_) => Some(DynSolType::Uint(8)), + TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Contract(_) => Some(DynSolType::Address), + // For a function-pointer type we return the ABI type of what the call *produces*, not a + // representation of the pointer itself. This is intentional: chisel inspects values, so + // the interesting type is the returned value. A zero-return function pointer has no + // inspectable value, so we return `None`. + TyKind::FnPtr(f) => match f.returns.len() { + 0 => None, + 1 => solar_ty_to_dyn(gcx, f.returns[0]), + _ => Some(DynSolType::Tuple( + f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + }, + TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::IntLiteral(neg, size) => { + let bits = (size.bits() as usize).max(8); + // Round up to the nearest multiple of 8 bits, capped at 256. + let bits = bits.div_ceil(8) * 8; + let bits = bits.min(256); + if neg { + Some(DynSolType::Int(bits.max(8))) } else { - let exp = U256::from(10usize.pow(exp)); - let unit_mul = unit_multiplier(unit).ok()?; - Some(num * exp * unit_mul) + Some(DynSolType::Uint(bits.max(8))) } } - pt::Expression::HexNumberLiteral(_, num, unit) => { - let unit_mul = unit_multiplier(unit).ok()?; - num.parse::().map(|num| num * unit_mul).ok() + TyKind::StringLiteral(valid_utf8, _) => { + if valid_utf8 { + Some(DynSolType::String) + } else { + Some(DynSolType::Bytes) + } } - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(..) => None, + TyKind::Module(_) + | TyKind::BuiltinModule(_) + | TyKind::Error(_, _) + | TyKind::Event(_, _) + | TyKind::Err(_) => None, _ => None, } } -#[inline] -fn unit_multiplier(unit: &Option) -> Result { - if let Some(unit) = unit { - let mul = match unit.name.as_str() { - "seconds" => 1, - "minutes" => 60, - "hours" => 60 * 60, - "days" => 60 * 60 * 24, - "weeks" => 60 * 60 * 24 * 7, - "wei" => 1, - "gwei" => 10_usize.pow(9), - "ether" => 10_usize.pow(18), - other => eyre::bail!("unknown unit: {other}"), - }; - Ok(U256::from(mul)) - } else { - Ok(U256::from(1)) - } -} - #[cfg(test)] mod tests { use super::*; use foundry_compilers::{error::SolcError, solc::Solc}; + use solar::sema::Compiler; use std::sync::Mutex; #[test] @@ -1558,46 +1455,66 @@ mod tests { DynSolType::FixedArray(Box::new(ty), len) } - fn parse(s: &mut SessionSource, input: &str, clear: bool) -> IntermediateOutput { + /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without + /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in + /// the run() body. + /// + /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so that + /// inputs which are syntactically valid but semantically rejected by solc (e.g. + /// `abi.decode(bytes, (uint8[13]))` or `a[0:3]` on a memory array) can still exercise the + /// HIR-based type-inference engine. + fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { if clear { s.clear(); } + // Always declare a sample enum so `Enum1` is available for `type(Enum1)` tests. *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; let input = format!("{};", input.trim_end().trim_end_matches(';')); - let (mut _s, _) = s.clone_with_new_line(input).unwrap(); - *s = _s.clone(); - let s = &mut _s; - - if let Err(e) = s.parse() { - let source = s.to_repl_source(); - panic!("{e}\n\ncould not parse input:\n{source}") - } - s.generate_intermediate_output().expect("could not generate intermediate output") - } - - fn expr(stmts: &[pt::Statement]) -> pt::Expression { - match stmts.last().expect("no statements") { - pt::Statement::Expression(_, e) => e.clone(), - s => panic!("Not an expression: {s:?}"), - } - } - - fn get_type( - s: &mut SessionSource, - input: &str, - clear: bool, - ) -> (Option, IntermediateOutput) { - let intermediate = parse(s, input, clear); - let run_func_body = intermediate.run_func_body().expect("no run func body"); - let expr = expr(run_func_body); - (Type::from_expression(&expr).map(Type::map_special), intermediate) - } + let (new_source, _) = s.clone_with_new_line(input).unwrap(); + *s = new_source.clone(); + + let src = new_source.to_repl_source(); + let sess = + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(sess); + + compiler.enter_mut(|c| -> Option { + // Stage 1: parse + lower (mutable access required). + let lowered = { + let mut pcx = c.parse(); + let file = c + .sess() + .source_map() + .new_source_file( + std::path::PathBuf::from(new_source.file_name.clone()), + src.clone(), + ) + .ok()?; + pcx.add_file(file); + pcx.parse(); + matches!(c.lower_asts(), Ok(ControlFlow::Continue(()))) + }; + if !lowered { + return None; + } - fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { - let (ty, intermediate) = get_type(s, input, clear); - ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) + // Stage 2: walk HIR (immutable access). + let gcx = c.gcx(); + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + let last = body.last()?; + let expr = match last.kind { + StmtKind::Expr(e) => e, + _ => return None, + }; + expr_to_dyn(gcx, expr, true) + }) } fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs index 7eedb31923439..90c0bad874622 100644 --- a/crates/chisel/src/source.rs +++ b/crates/chisel/src/source.rs @@ -5,8 +5,6 @@ //! execution helpers. use eyre::Result; -use forge_doc::solang_ext::{CodeLocationExt, SafeUnwrap}; -use foundry_common::fs; use foundry_compilers::{ Artifact, ProjectCompileOutput, artifacts::{ConfigurableContractArtifact, Source, Sources}, @@ -17,9 +15,16 @@ use foundry_config::{Config, SolcReq}; use foundry_evm::{backend::Backend, core::bytecode::InstIter, opts::EvmOpts}; use semver::Version; use serde::{Deserialize, Serialize}; -use solang_parser::pt; -use solar::interface::diagnostics::EmittedDiagnostics; -use std::{cell::OnceCell, collections::HashMap, fmt, path::PathBuf}; +use solar::{ + ast::{ItemKind, StmtKind as AstStmtKind, yul}, + interface::{Span, diagnostics::EmittedDiagnostics}, + sema::{ + CompilerRef, + hir::{Block, Contract, EventId, ItemId, Stmt, StmtKind as HirStmtKind}, + ty::Gcx, + }, +}; +use std::{cell::OnceCell, fmt}; use walkdir::WalkDir; /// The minimum Solidity version of the `Vm` interface. @@ -31,41 +36,8 @@ static VM_SOURCE: &str = include_str!("../../../testdata/utils/Vm.sol"); /// [`SessionSource`] build output. pub struct GeneratedOutput { output: ProjectCompileOutput, - pub(crate) intermediate: IntermediateOutput, -} - -pub struct GeneratedOutputRef<'a> { - output: &'a ProjectCompileOutput, - // compiler: &'b solar::sema::CompilerRef<'c>, - pub(crate) intermediate: &'a IntermediateOutput, -} - -/// Intermediate output for the compiled [SessionSource] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct IntermediateOutput { - /// All expressions within the REPL contract's run function and top level scope. - pub repl_contract_expressions: HashMap, - /// Intermediate contracts - pub intermediate_contracts: IntermediateContracts, -} - -/// A refined intermediate parse tree for a contract that enables easy lookups -/// of definitions. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct IntermediateContract { - /// All function definitions within the contract - pub function_definitions: HashMap>, - /// All event definitions within the contract - pub event_definitions: HashMap>, - /// All struct definitions within the contract - pub struct_definitions: HashMap>, - /// All variable definitions within the top level scope of the contract - pub variable_definitions: HashMap>, } -/// A defined type for a map of contract names to [IntermediateContract]s -type IntermediateContracts = HashMap; - impl fmt::Debug for GeneratedOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GeneratedOutput").finish_non_exhaustive() @@ -73,158 +45,25 @@ impl fmt::Debug for GeneratedOutput { } impl GeneratedOutput { - pub fn enter(&self, f: impl FnOnce(GeneratedOutputRef<'_>) -> T + Send) -> T { - // TODO(dani): once intermediate is removed - // self.output - // .parser() - // .solc() - // .compiler() - // .enter(|compiler| f(GeneratedOutputRef { output: &self.output, compiler })) - f(GeneratedOutputRef { output: &self.output, intermediate: &self.intermediate }) - } -} - -impl GeneratedOutputRef<'_> { - pub fn repl_contract(&self) -> Option<&ConfigurableContractArtifact> { - self.output.find_first("REPL") - } -} - -impl std::ops::Deref for GeneratedOutput { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - &self.intermediate - } -} -impl std::ops::Deref for GeneratedOutputRef<'_> { - type Target = IntermediateOutput; - fn deref(&self) -> &Self::Target { - self.intermediate + /// Enters the solar compiler context, providing access to the HIR and `Gcx`. + pub fn enter( + &self, + f: impl for<'a, 'b, 'gcx> FnOnce(GeneratedOutputRef<'a, 'b, 'gcx>) -> R + Send, + ) -> R { + self.output + .parser() + .solc() + .compiler() + .enter(|c| f(GeneratedOutputRef { output: &self.output, compiler: c })) } } -impl IntermediateOutput { - pub fn get_event(&self, input: &str) -> Option<&pt::EventDefinition> { - self.intermediate_contracts - .get("REPL") - .and_then(|contract| contract.event_definitions.get(input).map(std::ops::Deref::deref)) - } - - pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { - let deployed_bytecode = contract - .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - let deployed_bytecode_bytes = deployed_bytecode - .bytes() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - - let run_func_statements = self.run_func_body()?; - - // Record loc of first yul block return statement (if any). - // This is used to decide which is the final statement within the `run()` method. - // see . - let last_yul_return = run_func_statements.iter().find_map(|statement| { - if let pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } = statement - && let Some(statement) = block.statements.last() - && let pt::YulStatement::FunctionCall(yul_call) = statement - && yul_call.id.name == "return" - { - return Some(statement.loc()); - } - None - }); - - // Find the last statement within the "run()" method and get the program - // counter via the source map. - let Some(final_statement) = run_func_statements.last() else { return Ok(None) }; - - // If the final statement is some type of block (assembly, unchecked, or regular), - // we need to find the final statement within that block. Otherwise, default to - // the source loc of the final statement of the `run()` function's block. - // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. - let mut source_loc = match final_statement { - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(statement) = last_statement { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the asm block. Because we use saturating sub to get the second - // to last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - pt::Statement::Block { loc: _, unchecked: _, statements } => { - if let Some(statement) = statements.last() { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - _ => final_statement.loc(), - }; - - // Consider yul return statement as final statement (if it's loc is lower) . - if let Some(yul_return) = last_yul_return - && yul_return.end() < source_loc.start() - { - source_loc = yul_return; - } - - // Map the source location of the final statement of the `run()` function to its - // corresponding runtime program counter - let final_pc = { - let offset = source_loc.start() as u32; - let length = (source_loc.end() - source_loc.start()) as u32; - trace!(%offset, %length, "find pc"); - contract - .get_source_map_deployed() - .unwrap() - .unwrap() - .into_iter() - .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) - .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, pc)| pc) - .max() - }; - trace!(?final_pc); - Ok(final_pc) - } - - pub fn run_func_body(&self) -> Result<&Vec> { - match self - .intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find REPL intermediate contract!"))? - .function_definitions - .get("run") - .ok_or_else(|| eyre::eyre!("Could not find run function definition in REPL contract!"))? - .body - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find run function body!"))? - { - pt::Statement::Block { statements, .. } => Ok(statements), - _ => eyre::bail!("Could not find statements within run function body!"), - } - } +/// A scoped reference to a [`GeneratedOutput`] together with an entered solar compiler. +pub struct GeneratedOutputRef<'a, 'b, 'gcx> { + output: &'a ProjectCompileOutput, + pub(crate) compiler: &'b CompilerRef<'gcx>, } -// TODO(dani): further migration blocked on upstream work -#[cfg(false)] impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { pub fn gcx(&self) -> Gcx<'gcx> { self.compiler.gcx() @@ -234,8 +73,35 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { self.output.find_first("REPL") } - pub fn get_event(&self, input: &str) -> Option { - self.gcx().hir.events_enumerated().find(|(_, e)| e.name.as_str() == input).map(|(id, _)| id) + /// Looks up the REPL contract in the HIR. + pub fn repl_contract_hir(&self) -> Option<&'gcx Contract<'gcx>> { + self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + } + + /// Returns the body block of the REPL `run()` function. + pub fn run_func_body(&self) -> Block<'gcx> { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); + let f = c + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) + .expect("`run()` function not found in REPL contract"); + hir.function(f).body.expect("`run()` function does not have a body") + } + + /// Returns the [`EventId`] of an event named `input` in the REPL contract, if any. + pub fn get_event(&self, input: &str) -> Option { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir()?; + c.items.iter().find_map(|id| { + if let ItemId::Event(eid) = id + && hir.event(*eid).name.as_str() == input + { + Some(*eid) + } else { + None + } + }) } pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { @@ -252,52 +118,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Record loc of first yul block return statement (if any). // This is used to decide which is the final statement within the `run()` method. // see . - let last_yul_return_span: Option = run_body.iter().find_map(|stmt| { - // TODO(dani): Yul is not yet lowered to HIR. - let _ = stmt; - /* - if let hir::StmtKind::Assembly { block, .. } = stmt { - if let Some(stmt) = block.last() { - if let pt::YulStatement::FunctionCall(yul_call) = stmt { - if yul_call.id.name == "return" { - return Some(stmt.loc()) - } - } - } - } - */ - None - }); + // + // Yul is not yet lowered to HIR (assembly statements appear as `StmtKind::Err`), + // so we walk the AST of the REPL source to find a top-level `return(...)` call + // inside any `assembly { ... }` block in `run()`. + let last_yul_return_span: Option = self.first_yul_return_span(); // Find the last statement within the "run()" method and get the program // counter via the source map. let Some(last_stmt) = run_body.last() else { return Ok(None) }; - // If the final statement is some type of block (assembly, unchecked, or regular), + // If the final statement is some type of block (unchecked or regular), // we need to find the final statement within that block. Otherwise, default to // the source loc of the final statement of the `run()` function's block. // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. + // Inline assembly blocks (lowered to `StmtKind::Err` in HIR in the pinned solar + // version) are handled separately via `trailing_assembly_last_stmt_span`, which + // walks the AST to recover the last meaningful Yul statement. let source_stmt = match &last_stmt.kind { - // TODO(dani): Yul is not yet lowered to HIR. - /* - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(stmt) = last_statement { - stmt - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - &run_body[run_body.len().saturating_sub(2)] - } - } - */ - hir::StmtKind::UncheckedBlock(stmts) | hir::StmtKind::Block(stmts) => { + HirStmtKind::UncheckedBlock(stmts) | HirStmtKind::Block(stmts) => { if let Some(stmt) = stmts.last() { stmt } else { @@ -309,9 +148,25 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { } _ => last_stmt, }; - let mut source_span = self.stmt_span_without_semicolon(source_stmt); + // If the trailing statement is an assembly block, prefer the last meaningful + // (non-`let`) Yul statement's span as the source location for `final_pc`. + // See . + // + // Two guards are required: + // 1. `StmtKind::Err`, assembly lowers to an error node in the current pinned solar + // version; this ensures we don't apply the AST fallback to properly-lowered stmts. + // 2. `trailing_assembly_last_stmt_span` returning `Some`, verifies via the AST that the + // failing HIR node actually corresponds to an assembly block (not some other lowering + // failure), and supplies the concrete span to use. + let mut source_span = if matches!(last_stmt.kind, HirStmtKind::Err(_)) + && let Some(span) = self.trailing_assembly_last_stmt_span() + { + span + } else { + self.stmt_span_without_semicolon(source_stmt) + }; - // Consider yul return statement as final statement (if it's loc is lower) . + // Consider yul return statement as final statement (if it's loc is lower). if let Some(yul_return_span) = last_yul_return_span && yul_return_span.hi() < source_span.lo() { @@ -320,26 +175,32 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { // Map the source location of the final statement of the `run()` function to its // corresponding runtime program counter - let (_sf, range) = self.compiler.sess().source_map().span_to_source(source_span).unwrap(); - dbg!(source_span, &range, &_sf.src[range.clone()]); + let result = self + .compiler + .sess() + .source_map() + .span_to_source(source_span) + .map_err(|e| eyre::eyre!("failed to resolve span: {e:?}"))?; + let range = result.data; let offset = range.start as u32; let length = range.len() as u32; - let final_pc = deployed_bytecode - .source_map() + trace!(%offset, %length, "find pc"); + let final_pc = contract + .get_source_map_deployed() .ok_or_else(|| eyre::eyre!("No source map found for `REPL` contract"))?? .into_iter() - .zip(InstructionIter::new(deployed_bytecode_bytes)) + .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, i)| i.pc) - .max() - .unwrap_or_default(); - Ok(Some(final_pc)) + .map(|(_, pc)| pc) + .max(); + trace!(?final_pc); + Ok(final_pc) } /// Statements' ranges in the solc source map do not include the semicolon. - fn stmt_span_without_semicolon(&self, stmt: &hir::Stmt<'_>) -> Span { + fn stmt_span_without_semicolon(&self, stmt: &Stmt<'_>) -> Span { match stmt.kind { - hir::StmtKind::DeclSingle(id) => { + HirStmtKind::DeclSingle(id) => { let decl = self.gcx().hir.variable(id); if let Some(expr) = decl.initializer { stmt.span.with_hi(expr.span.hi()) @@ -347,23 +208,65 @@ impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { stmt.span } } - hir::StmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), - hir::StmtKind::Expr(expr) => expr.span, + HirStmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), + HirStmtKind::Expr(expr) => expr.span, _ => stmt.span, } } - fn run_func_body(&self) -> hir::Block<'_> { - let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); - let f = c - .functions() - .find(|&f| self.gcx().hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) - .expect("`run()` function not found in REPL contract"); - self.gcx().hir.function(f).body.expect("`run()` function does not have a body") + /// Returns the AST `run()` body of the REPL contract, if any. + /// + /// Yul/assembly is not yet lowered to HIR in the pinned solar version, so we + /// keep around the AST to be able to inspect inline assembly blocks. + fn repl_run_ast_body(&self) -> Option<&'gcx solar::ast::Block<'gcx>> { + let contract = self.repl_contract_hir()?; + let source = self.gcx().sources.get(contract.source)?; + let ast = source.ast.as_ref()?; + + let contract_ast = ast.items.iter().find_map(|i| match &i.kind { + ItemKind::Contract(c) if c.name.as_str() == "REPL" => Some(c), + _ => None, + })?; + contract_ast.body.iter().find_map(|i| match &i.kind { + ItemKind::Function(f) if f.header.name.is_some_and(|n| n.as_str() == "run") => { + f.body.as_ref() + } + _ => None, + }) } - fn repl_contract_hir(&self) -> Option<&hir::Contract<'_>> { - self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + /// Returns the span of the first top-level `return(...)` call inside any + /// `assembly { ... }` block in the REPL `run()` function, if any. + fn first_yul_return_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + for stmt in run_body.stmts.iter() { + let AstStmtKind::Assembly(asm) = &stmt.kind else { continue }; + for ystmt in asm.block.stmts.iter() { + if let yul::StmtKind::Expr(e) = &ystmt.kind + && let yul::ExprKind::Call(call) = &e.kind + && call.name.as_str() == "return" + { + return Some(ystmt.span); + } + } + } + None + } + + /// If the last statement of the REPL `run()` function is an `assembly { ... }` block, + /// returns the span of its last non-`let` (i.e. non-VarDecl) Yul statement. + /// + /// This mirrors the legacy behavior used to pick a meaningful end-of-function PC when + /// the trailing statement is inline assembly. + fn trailing_assembly_last_stmt_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + let AstStmtKind::Assembly(asm) = &run_body.stmts.last()?.kind else { return None }; + asm.block + .stmts + .iter() + .rev() + .find(|s| !matches!(s.kind, yul::StmtKind::VarDecl(_, _))) + .map(|s| s.span) } } @@ -585,8 +488,7 @@ impl SessionSource { return Ok(output); } let output = self.compile()?; - let intermediate = self.generate_intermediate_output()?; - let output = GeneratedOutput { output, intermediate }; + let output = GeneratedOutput { output }; Ok(self.output.get_or_init(|| output)) } @@ -603,12 +505,11 @@ impl SessionSource { eyre::bail!("{output}"); } - // TODO(dani): re-enable - if cfg!(false) { - output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { - let _ = c.lower_asts(); - }); - } + // Drive HIR lowering and analysis so that subsequent `enter` queries can use them. + output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { + let _ = c.lower_asts(); + let _ = c.analysis(); + }); Ok(output) } @@ -633,53 +534,6 @@ impl SessionSource { sources } - /// Generate intermediate contracts for all contract definitions in the compilation source. - /// - /// ### Returns - /// - /// Optionally, a map of contract names to a vec of [IntermediateContract]s. - pub fn generate_intermediate_contracts(&self) -> Result> { - let mut res_map = HashMap::default(); - let parsed_map = self.get_sources(); - for source in parsed_map.values() { - Self::get_intermediate_contract(&source.content, &mut res_map); - } - Ok(res_map) - } - - /// Generate intermediate output for the REPL contract - pub fn generate_intermediate_output(&self) -> Result { - // Parse generate intermediate contracts - let intermediate_contracts = self.generate_intermediate_contracts()?; - - // Construct variable definitions - let variable_definitions = intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find intermediate REPL contract!"))? - .variable_definitions - .clone() - .into_iter() - .map(|(k, v)| (k, v.ty)) - .collect::>(); - // Construct intermediate output - let mut intermediate_output = IntermediateOutput { - repl_contract_expressions: variable_definitions, - intermediate_contracts, - }; - - // Add all statements within the run function to the repl_contract_expressions map - for (key, val) in intermediate_output - .run_func_body()? - .clone() - .iter() - .flat_map(Self::get_statement_definitions) - { - intermediate_output.repl_contract_expressions.insert(key, val); - } - - Ok(intermediate_output) - } - /// Construct the REPL source. pub fn to_repl_source(&self) -> String { let Self { @@ -742,108 +596,6 @@ contract {contract_name} {{ }); sess.dcx.emitted_errors().unwrap() } - - /// Gets the [IntermediateContract] for a Solidity source string and inserts it into the - /// passed `res_map`. In addition, recurses on any imported files as well. - /// - /// ### Takes - /// - `content` - A Solidity source string - /// - `res_map` - A mutable reference to a map of contract names to [IntermediateContract]s - pub fn get_intermediate_contract( - content: &str, - res_map: &mut HashMap, - ) { - if let Ok((pt::SourceUnit(source_unit_parts), _)) = solang_parser::parse(content, 0) { - let func_defs = source_unit_parts - .into_iter() - .filter_map(|sup| match sup { - pt::SourceUnitPart::ImportDirective(i) => match i { - pt::Import::Plain(s, _) - | pt::Import::Rename(s, _, _) - | pt::Import::GlobalSymbol(s, _, _) => { - let s = match s { - pt::ImportPath::Filename(s) => s.string, - pt::ImportPath::Path(p) => p.to_string(), - }; - let path = PathBuf::from(s); - - match fs::read_to_string(path) { - Ok(source) => { - Self::get_intermediate_contract(&source, res_map); - None - } - Err(_) => None, - } - } - }, - pt::SourceUnitPart::ContractDefinition(cd) => { - let mut intermediate = IntermediateContract::default(); - - cd.parts.into_iter().for_each(|part| match part { - pt::ContractPart::FunctionDefinition(def) => { - // Only match normal function definitions here. - if matches!(def.ty, pt::FunctionTy::Function) { - intermediate - .function_definitions - .insert(def.name.clone().unwrap().name, def); - } - } - pt::ContractPart::EventDefinition(def) => { - let event_name = def.name.safe_unwrap().name.clone(); - intermediate.event_definitions.insert(event_name, def); - } - pt::ContractPart::StructDefinition(def) => { - let struct_name = def.name.safe_unwrap().name.clone(); - intermediate.struct_definitions.insert(struct_name, def); - } - pt::ContractPart::VariableDefinition(def) => { - let var_name = def.name.safe_unwrap().name.clone(); - intermediate.variable_definitions.insert(var_name, def); - } - _ => {} - }); - Some((cd.name.safe_unwrap().name.clone(), intermediate)) - } - _ => None, - }) - .collect::>(); - res_map.extend(func_defs); - } - } - - /// Helper to deconstruct a statement - /// - /// ### Takes - /// - /// A reference to a [pt::Statement] - /// - /// ### Returns - /// - /// A vector containing tuples of the inner expressions' names, types, and storage locations. - pub fn get_statement_definitions(statement: &pt::Statement) -> Vec<(String, pt::Expression)> { - match statement { - pt::Statement::VariableDefinition(_, def, _) => { - vec![(def.name.safe_unwrap().name.clone(), def.ty.clone())] - } - pt::Statement::Expression(_, pt::Expression::Assign(_, left, _)) => { - if let pt::Expression::List(_, list) = left.as_ref() { - list.iter() - .filter_map(|(_, param)| { - param.as_ref().and_then(|param| { - param - .name - .as_ref() - .map(|name| (name.name.clone(), param.ty.clone())) - }) - }) - .collect() - } else { - Vec::default() - } - } - _ => Vec::default(), - } - } } /// A Parse Tree Fragment diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs index 704d30405eed9..338b7d2043809 100644 --- a/crates/chisel/tests/it/repl/mod.rs +++ b/crates/chisel/tests/it/repl/mod.rs @@ -153,6 +153,26 @@ assembly { repl.expect("[0x00:0x20]"); }); +// Assembly as the final statement with a return — exercises the path where both +// `first_yul_return_span` and `trailing_assembly_last_stmt_span` resolve to the same `return(...)` +// span (no subsequent Solidity statement after the assembly block). +repl_test!(assembly_return_final, |repl| { + repl.sendln("uint x = 0xbeef;"); + repl.sendln("assembly { mstore(0x0, sload(0)) return(0x0, 0x20) }"); + repl.sendln("!md"); + repl.expect("[0x00:0x20]"); +}); + +// Assembly block without a `return(...)` call as an intermediate statement, exercises +// `first_yul_return_span` returning `None` while a subsequent Solidity statement is still evaluated +// correctly. +repl_test!(assembly_no_return_intermediate, |repl| { + repl.sendln("uint x = 1;"); + repl.sendln("assembly { x := add(x, 1) }"); + repl.sendln("x"); + repl.expect("Decimal: 2"); +}); + // Issue #5051, #8978: Test EVM version normalization. repl_test!(flaky_evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { repl.sendln("uint x;\nx"); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 37340f5f4cc3c..606b8291819e4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -52,6 +52,7 @@ rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true serde.workspace = true +toml.workspace = true strsim = "0.11" strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } @@ -70,7 +71,13 @@ tempfile.workspace = true tikv-jemallocator = { workspace = true, optional = true } [features] +default = ["optimism"] tracy = ["dep:tracing-tracy"] tracy-allocator = ["tracy"] jemalloc = ["dep:tikv-jemallocator"] mimalloc = ["dep:mimalloc"] +optimism = [ + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 87f14e2039606..4fc437c7232f8 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -307,6 +307,17 @@ mod tests { assert_eq!(val, &Value::from(1000u64)); } + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + use clap::CommandFactory; + + let command = EvmArgs::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + #[test] fn can_parse_chain_id() { let args = EvmArgs { diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 8c37860446683..f846da5002354 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -66,8 +66,20 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + self.url_with_env(config, std::env::var("ETH_RPC_URL").ok()) + } + + fn url_with_env<'a>( + &'a self, + config: Option<&'a Config>, + env_url: Option, + ) -> Result>> { if self.flashbots { Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else if let Some(url) = self.common.rpc_url.as_deref() { + Ok(Some(Cow::Borrowed(url))) + } else if let Some(url) = env_url { + Ok(Some(Cow::Owned(url))) } else { self.common.url(config) } @@ -85,8 +97,10 @@ impl RpcOpts { pub fn dict(&self) -> Dict { let mut dict = self.common.dict(); - if self.flashbots { - dict.insert("eth_rpc_url".into(), FLASHBOTS_URL.into()); + // `self.url(None)` already accounts for `flashbots` and the `ETH_RPC_URL` env var, + // so a single insert here covers both. + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); } if let Ok(Some(jwt)) = self.jwt(None) { dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); @@ -199,6 +213,7 @@ impl figment::Provider for EthereumOpts { #[cfg(test)] mod tests { use super::*; + use clap::CommandFactory; #[test] fn parse_etherscan_opts() { @@ -223,4 +238,41 @@ mod tests { let id: u64 = chain_id.deserialize().expect("chain_id should deserialize as u64"); assert_eq!(id, 9745); } + + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + let command = RpcOpts::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + + #[test] + fn rpc_url_resolves_eth_rpc_url_env() { + let args = RpcOpts::default(); + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8545"); + } + + #[test] + fn explicit_rpc_url_takes_precedence_over_eth_rpc_url_env() { + let args = RpcOpts { + common: RpcCommonOpts { + rpc_url: Some("http://127.0.0.1:8546".to_string()), + ..Default::default() + }, + ..Default::default() + }; + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8546"); + } } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs index 05b98582fa88f..6a5fe5ed4e9e4 100644 --- a/crates/cli/src/opts/rpc_common.rs +++ b/crates/cli/src/opts/rpc_common.rs @@ -17,10 +17,15 @@ use std::borrow::Cow; /// This struct holds fields that both [`super::RpcOpts`] (cast) and /// [`super::EvmArgs`] (forge/script) need, eliminating duplication and /// making the two structs composable. +/// +/// Note: `ETH_RPC_URL` is intentionally **not** bound here as a clap env +/// fallback; otherwise it would be inherited by `EvmArgs` and silently +/// fork all `forge test` runs. Cast resolves `ETH_RPC_URL` explicitly +/// at the call site (see [`super::RpcOpts::url`]). #[derive(Clone, Debug, Default, Serialize, Parser)] pub struct RpcCommonOpts { /// The RPC endpoint. - #[arg(short, long, visible_alias = "fork-url", env = "ETH_RPC_URL")] + #[arg(short, long, visible_alias = "fork-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub rpc_url: Option, diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs index 8c2a12e661e18..88119f163b325 100644 --- a/crates/cli/src/opts/tempo.rs +++ b/crates/cli/src/opts/tempo.rs @@ -1,16 +1,26 @@ use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, ruint::aliases::U256}; -use alloy_signer::Signature; +use alloy_signer::{Signature, Signer}; use clap::Parser; -use foundry_common::FoundryTransactionBuilder; -use std::{num::NonZeroU64, str::FromStr}; +use eyre::Result; +use foundry_common::{ + FoundryTransactionBuilder, + tempo::{TempoSponsor, resolve_tempo_sponsor_signer}, +}; +use std::{ + num::NonZeroU64, + path::PathBuf, + str::FromStr, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; use crate::utils::parse_fee_token_address; -/// CLI options for Tempo transactions. +/// CLI options common to Tempo transactions across commands. #[derive(Clone, Debug, Default, Parser)] #[command(next_help_heading = "Tempo")] -pub struct TempoOpts { +pub struct TempoCommonOpts { /// Fee token address for Tempo transactions. /// /// When set, builds a Tempo (type 0x76) transaction that pays gas fees @@ -21,6 +31,40 @@ pub struct TempoOpts { #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] pub fee_token: Option
, + /// Opt into TIP-1009 expiring-nonce mode with a validity window. + /// + /// Convenience flag that combines `--tempo.expiring-nonce` with a relative + /// `--tempo.valid-before`. Sets nonce_key = U256::MAX, nonce = 0, and valid_before = now + + /// seconds. + /// + /// Maximum value is 30 seconds. The transaction must be mined before the deadline or it + /// becomes permanently invalid, giving safe retry semantics: retries produce a fresh tx hash + /// and the old tx can never land late. + #[arg(long = "tempo.expires", value_name = "SECONDS", value_parser = parse_expires_seconds)] + pub expires: Option, +} + +impl TempoCommonOpts { + /// Returns `true` if any Tempo-specific option is set. + pub const fn is_tempo(&self) -> bool { + self.fee_token.is_some() || self.expires.is_some() + } + + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + let secs = self.expires?; + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Some(now.as_secs() + secs) + } +} + +/// CLI options for Tempo transactions. +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Tempo")] +pub struct TempoOpts { + #[command(flatten)] + pub common: TempoCommonOpts, + /// Nonce key for Tempo parallelizable nonces. /// /// When set, builds a Tempo (type 0x76) transaction with the specified nonce key, @@ -28,21 +72,69 @@ pub struct TempoOpts { /// to be executed in parallel. If not set, the protocol nonce key (0) will be used. /// /// For more information see . - #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY")] + #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY", conflicts_with = "lane")] pub nonce_key: Option, + /// Named nonce lane for Tempo parallelizable nonces. + /// + /// Resolves a friendly lane name (e.g. `deploy`, `payments`) to a `nonce_key` via a + /// shared lanes file (default: `tempo.lanes.toml` at the project root). The lanes file + /// is a TOML map of `name = ` entries, e.g.: + /// + /// ```toml + /// deploy = 1 + /// ops = 2 + /// payments = 3 + /// ``` + /// + /// Mutually exclusive with `--tempo.nonce-key`. + #[arg(long = "tempo.lane", value_name = "NAME")] + pub lane: Option, + + /// Path to the Tempo lanes file used by `--tempo.lane`. + /// + /// Defaults to `tempo.lanes.toml` at the project root. + #[arg(long = "tempo.lanes-file", value_name = "PATH")] + pub lanes_file: Option, + + /// Sponsor (fee payer) address for Tempo sponsored transactions. + #[arg(long = "tempo.sponsor", value_name = "ADDRESS")] + pub sponsor: Option
, + + /// Sign Tempo sponsor digests in-band with the given signer URI. + /// + /// Supported forms include `env://VAR`, `keystore://PATH`, `account://NAME`, + /// `ledger://`, `trezor://`, `aws://`, `gcp://`, `turnkey://`, and + /// `private-key://KEY`. + #[arg( + long = "tempo.sponsor-signer", + value_name = "SIGNER", + requires = "sponsor", + conflicts_with = "sponsor_sig" + )] + pub sponsor_signer: Option, + /// Sponsor (fee payer) signature for Tempo sponsored transactions. /// /// The sponsor signs the `fee_payer_signature_hash` to commit to paying gas fees /// on behalf of the sender. Provide as a hex-encoded signature. - #[arg(long = "tempo.sponsor-signature", value_parser = parse_signature)] - pub sponsor_signature: Option, + #[arg( + long = "tempo.sponsor-sig", + alias = "tempo.sponsor-signature", + value_parser = parse_signature, + requires = "sponsor", + conflicts_with = "sponsor_signer" + )] + pub sponsor_sig: Option, /// Print the sponsor signature hash and exit. /// /// Computes the `fee_payer_signature_hash` for the transaction so that a sponsor /// knows what hash to sign. The transaction is not sent. - #[arg(long = "tempo.print-sponsor-hash")] + #[arg( + long = "tempo.print-sponsor-hash", + conflicts_with_all = &["sponsor", "sponsor_signer", "sponsor_sig"] + )] pub print_sponsor_hash: bool, /// Access key ID for Tempo Keychain signature transactions. @@ -56,14 +148,14 @@ pub struct TempoOpts { /// /// Sets nonce to 0 and nonce_key to U256::MAX, enabling time-bounded transaction /// validity via `--tempo.valid-before` and `--tempo.valid-after`. - #[arg(long = "tempo.expiring-nonce", requires = "valid_before")] + #[arg(long = "tempo.expiring-nonce", requires = "valid_before", conflicts_with = "expires")] pub expiring_nonce: bool, /// Upper bound timestamp for Tempo expiring nonce transactions. /// /// The transaction is only valid before this unix timestamp. /// Requires `--tempo.expiring-nonce`. - #[arg(long = "tempo.valid-before")] + #[arg(long = "tempo.valid-before", conflicts_with = "expires")] pub valid_before: Option, /// Lower bound timestamp for Tempo expiring nonce transactions. @@ -77,9 +169,12 @@ pub struct TempoOpts { impl TempoOpts { /// Returns `true` if any Tempo-specific option is set. pub const fn is_tempo(&self) -> bool { - self.fee_token.is_some() + self.common.is_tempo() || self.nonce_key.is_some() - || self.sponsor_signature.is_some() + || self.lane.is_some() + || self.sponsor.is_some() + || self.sponsor_signer.is_some() + || self.sponsor_sig.is_some() || self.print_sponsor_hash || self.key_id.is_some() || self.expiring_nonce @@ -87,6 +182,58 @@ impl TempoOpts { || self.valid_after.is_some() } + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + self.common.expires_at() + } + + /// Resolves `--tempo.expires` into concrete expiring-nonce fields. + /// + /// This computes the relative deadline once so later calls to [`Self::apply`] reuse the same + /// `valid_before` timestamp instead of deriving a fresh one. + pub fn resolve_expires(&mut self) -> Option { + let ts = self.expires_at()?; + self.expiring_nonce = true; + self.valid_before = Some(ts); + self.common.expires = None; + Some(ts) + } + + /// Returns `true` if a sponsor signature should be attached before submission. + pub const fn has_sponsor_submission(&self) -> bool { + self.sponsor.is_some() || self.sponsor_signer.is_some() || self.sponsor_sig.is_some() + } + + /// Resolves sponsor CLI options into a reusable sponsor config for transaction submission. + pub async fn sponsor_config(&self) -> Result> { + let Some(sponsor) = self.sponsor else { + return Ok(None); + }; + + let signer = if let Some(spec) = &self.sponsor_signer { + Some(Arc::new(Box::pin(resolve_tempo_sponsor_signer(spec)).await?)) + } else { + None + }; + + if let Some(signer) = &signer { + let signer_address = signer.address(); + if signer_address != sponsor { + eyre::bail!( + "Tempo sponsor signer address {signer_address} does not match --tempo.sponsor {sponsor}" + ); + } + } + + if signer.is_none() && self.sponsor_sig.is_none() { + eyre::bail!( + "--tempo.sponsor requires either --tempo.sponsor-signer or --tempo.sponsor-sig" + ); + } + + Ok(Some(TempoSponsor::new(sponsor, signer, self.sponsor_sig))) + } + /// Applies Tempo-specific options to a transaction request. /// /// All setters are no-ops for non-Tempo networks, so this is safe to call unconditionally. @@ -94,8 +241,9 @@ impl TempoOpts { where N::TransactionRequest: FoundryTransactionBuilder, { - // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX - if self.expiring_nonce { + // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX. + // --tempo.expires is a convenience alias that also sets valid_before = now + duration. + if self.expiring_nonce || self.common.expires.is_some() { tx.set_nonce(0); tx.set_nonce_key(U256::MAX); } else { @@ -107,11 +255,14 @@ impl TempoOpts { } } - if let Some(fee_token) = self.fee_token { + if let Some(fee_token) = self.common.fee_token { tx.set_fee_token(fee_token); } - if let Some(valid_before) = self.valid_before + // --tempo.expires sets valid_before relative to now; --tempo.valid-before takes a raw + // unix timestamp. The two flags are mutually exclusive (enforced by clap). + let effective_valid_before = self.expires_at().or(self.valid_before); + if let Some(valid_before) = effective_valid_before && let Some(v) = NonZeroU64::new(valid_before) { tx.set_valid_before(v); @@ -131,8 +282,7 @@ impl TempoOpts { // gas estimation so that `--tempo.print-sponsor-hash` and // `--tempo.sponsor-signature` produce identical gas estimates. Callers // should call `set_fee_payer_signature` on the built tx request. - if (self.sponsor_signature.is_some() || self.print_sponsor_hash) && tx.nonce_key().is_none() - { + if (self.has_sponsor_submission() || self.print_sponsor_hash) && tx.nonce_key().is_none() { tx.set_nonce_key(U256::ZERO); } } @@ -142,11 +292,83 @@ fn parse_signature(s: &str) -> Result { Signature::from_str(s).map_err(|e| format!("invalid signature: {e}")) } +/// Parses a seconds value for `--tempo.expires`, capped at the protocol maximum of 30 seconds. +fn parse_expires_seconds(s: &str) -> Result { + let secs: u64 = s + .parse() + .map_err(|_| format!("invalid value '{s}': expected an integer number of seconds"))?; + if secs > 30 { + return Err(format!("expires must be at most 30 seconds (got {secs})")); + } + Ok(secs) +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::address; + #[test] + fn parses_lane_arg() { + let opts = TempoOpts::try_parse_from(["", "--tempo.lane", "deploy"]).unwrap(); + assert_eq!(opts.lane.as_deref(), Some("deploy")); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn lane_conflicts_with_nonce_key() { + let err = + TempoOpts::try_parse_from(["", "--tempo.lane", "deploy", "--tempo.nonce-key", "1"]) + .unwrap_err(); + assert!( + err.to_string().contains("cannot be used with"), + "expected clap conflict error, got: {err}", + ); + } + + #[test] + fn parse_expires_flag() { + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "30"]).unwrap(); + assert_eq!(opts.common.expires, Some(30)); + + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + assert_eq!(opts.common.expires, Some(10)); + + // exceeds 30s maximum + assert!(TempoOpts::try_parse_from(["", "--tempo.expires", "31"]).is_err()); + + // conflicts with --tempo.expiring-nonce + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.expires", + "30", + "--tempo.expiring-nonce", + "--tempo.valid-before", + "999" + ]) + .is_err() + ); + } + + #[test] + fn resolve_expires_materializes_valid_before() { + let before = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + let mut opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + + let resolved = opts.resolve_expires().unwrap(); + let after = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + + assert!(resolved >= before + 10); + assert!(resolved <= after + 10); + assert!(opts.expiring_nonce); + assert_eq!(opts.valid_before, Some(resolved)); + assert_eq!(opts.common.expires, None); + assert_eq!(opts.expires_at(), None); + } + #[test] fn parse_fee_token_id() { let opts = TempoOpts::try_parse_from([ @@ -155,13 +377,69 @@ mod tests { "0x20C0000000000000000000000000000000000002", ]) .unwrap(); - assert_eq!(opts.fee_token, Some(address!("0x20C0000000000000000000000000000000000002")),); + assert_eq!( + opts.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000002")), + ); // AlphaUSD token ID is 1u64 let opts_with_id = TempoOpts::try_parse_from(["", "--tempo.fee-token", "1"]).unwrap(); assert_eq!( - opts_with_id.fee_token, + opts_with_id.common.fee_token, Some(address!("0x20C0000000000000000000000000000000000001")), ); } + + #[test] + fn parse_sponsor_signer() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert_eq!(opts.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + assert!(opts.sponsor_sig.is_none()); + assert!(opts.is_tempo()); + assert!(opts.has_sponsor_submission()); + } + + #[test] + fn sponsor_signer_requires_sponsor() { + assert!( + TempoOpts::try_parse_from(["", "--tempo.sponsor-signer", "env://SPONSOR"]).is_err() + ); + } + + #[test] + fn parse_sponsor_signature_alias() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signature", + "0x0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert!(opts.sponsor_sig.is_some()); + } + + #[test] + fn print_sponsor_hash_conflicts_with_sponsor_submission() { + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.print-sponsor-hash", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + ]) + .is_err() + ); + } } diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs index 647f52d316a6c..4b5715b9ebe08 100644 --- a/crates/cli/src/utils/tempo.rs +++ b/crates/cli/src/utils/tempo.rs @@ -1,8 +1,44 @@ -use std::str::FromStr; +//! Tempo utilities: fee token parsing and named nonce lanes (2D nonces). +//! +//! A "lane" is a friendly alias for a Tempo `nonce_key` (a [`U256`]). Lanes are defined in a +//! shared TOML file (default `tempo.lanes.toml` at the project root) so a team can reserve +//! independent sequential nonce streams for parallel scripts without coordinating on raw +//! `U256` selectors. +//! +//! Example `tempo.lanes.toml`: +//! +//! ```toml +//! deploy = 1 +//! ops = 2 +//! payments = 3 +//! ``` +//! +//! ```bash +//! cast erc20 transfer ... --tempo.lane payments +//! ``` -use alloy_primitives::Address; +use crate::opts::TempoOpts; +use alloy_primitives::{Address, U256}; +use eyre::{Result, eyre}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, +}; use tempo_primitives::TempoAddressExt; +/// Default name of the lanes file at the project root. +pub const DEFAULT_LANES_FILE: &str = "tempo.lanes.toml"; + +/// Result of resolving a `--tempo.lane ` argument against a lanes file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResolvedLane { + /// The lane name as provided on the CLI. + pub name: String, + /// The `nonce_key` the lane resolved to. + pub nonce_key: U256, +} + /// Parses a fee token address. pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result
{ Address::from_str(address_or_id).or_else(|_| Ok(token_id_to_address(address_or_id.parse()?))) @@ -14,3 +50,156 @@ fn token_id_to_address(token_id: u64) -> Address { address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); Address::from(address_bytes) } + +/// Loads a TOML lanes file from `path`. +/// +/// Each top-level key is a lane name, and the value is the `nonce_key` (an integer or a +/// decimal/hex string parsed as [`U256`]). +pub fn load_lanes(path: &Path) -> Result> { + let contents = std::fs::read_to_string(path) + .map_err(|e| eyre!("failed to read tempo lanes file {}: {}", path.display(), e))?; + parse_lanes(&contents) + .map_err(|e| eyre!("failed to parse tempo lanes file {}: {}", path.display(), e)) +} + +fn parse_lanes(contents: &str) -> Result> { + let raw: BTreeMap = toml::from_str(contents)?; + let mut out = BTreeMap::new(); + for (name, value) in raw { + let nonce_key = match value { + toml::Value::Integer(n) => { + if n < 0 { + return Err(eyre!("invalid nonce_key for lane '{name}': must be non-negative")); + } + U256::from(n as u64) + } + toml::Value::String(s) => U256::from_str(s.trim()) + .map_err(|e| eyre!("invalid nonce_key for lane '{name}': {e}"))?, + other => { + return Err(eyre!( + "invalid nonce_key for lane '{name}': expected integer or string, got {}", + other.type_str(), + )); + } + }; + out.insert(name, nonce_key); + } + Ok(out) +} + +/// Resolves `opts.lane` against a lanes file and writes the resulting `nonce_key` to +/// `opts.nonce_key`. Returns the resolved lane (or `None` if no `--tempo.lane` was set). +/// +/// `root` is the project root used to locate the default lanes file +/// (`/tempo.lanes.toml`) when `--tempo.lanes-file` was not provided. +pub fn resolve_lane(opts: &mut TempoOpts, root: &Path) -> Result> { + let Some(lane_name) = opts.lane.clone() else { return Ok(None) }; + + let path: PathBuf = opts.lanes_file.clone().unwrap_or_else(|| root.join(DEFAULT_LANES_FILE)); + + if !path.exists() { + return Err(eyre!( + "tempo lanes file not found at {}\n\ + create it with `name = ` entries, e.g.:\n \ + deploy = 1\n \ + ops = 2\n \ + payments = 3", + path.display(), + )); + } + + let lanes = load_lanes(&path)?; + + let nonce_key = lanes.get(&lane_name).copied().ok_or_else(|| { + let mut known: Vec<&str> = lanes.keys().map(String::as_str).collect(); + known.sort_unstable(); + eyre!( + "lane '{lane_name}' not found in {} (known lanes: {})", + path.display(), + if known.is_empty() { "".to_string() } else { known.join(", ") }, + ) + })?; + + opts.nonce_key = Some(nonce_key); + Ok(Some(ResolvedLane { name: lane_name, nonce_key })) +} + +/// Prints `lane: (nonce_key=, nonce=)` to stderr (so it doesn't pollute +/// stdout for commands like `cast mktx` whose stdout is meant to be piped), giving +/// visibility into which 2D nonce lane was used. +pub fn maybe_print_resolved_lane(resolved: Option<&ResolvedLane>, nonce: u64) -> Result<()> { + if let Some(lane) = resolved { + sh_eprintln!("lane: {} (nonce_key={}, nonce={})", lane.name, lane.nonce_key, nonce)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_int_and_string_lane_values() { + let toml = r#" +deploy = 1 +ops = 2 +payments = "3" +big = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +"#; + let lanes = parse_lanes(toml).unwrap(); + assert_eq!(lanes.get("deploy"), Some(&U256::from(1u64))); + assert_eq!(lanes.get("ops"), Some(&U256::from(2u64))); + assert_eq!(lanes.get("payments"), Some(&U256::from(3u64))); + assert_eq!(lanes.get("big"), Some(&U256::MAX)); + } + + #[test] + fn parse_lanes_rejects_invalid_string() { + let toml = "broken = \"not-a-number\""; + let err = parse_lanes(toml).unwrap_err(); + assert!(err.to_string().contains("invalid nonce_key for lane 'broken'")); + } + + #[test] + fn resolve_lane_sets_nonce_key_and_returns_resolved() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 7\npayments = 42\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let resolved = resolve_lane(&mut opts, dir.path()).unwrap().unwrap(); + assert_eq!(resolved.name, "payments"); + assert_eq!(resolved.nonce_key, U256::from(42u64)); + assert_eq!(opts.nonce_key, Some(U256::from(42u64))); + } + + #[test] + fn resolve_lane_returns_none_when_no_lane() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts::default(); + let resolved = resolve_lane(&mut opts, dir.path()).unwrap(); + assert!(resolved.is_none()); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn resolve_lane_errors_when_file_missing() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts { lane: Some("deploy".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + assert!(err.to_string().contains("tempo lanes file not found")); + } + + #[test] + fn resolve_lane_errors_when_lane_unknown() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 1\nops = 2\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("lane 'payments' not found")); + assert!(msg.contains("deploy, ops")); + } +} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 337c51f52d2c2..6921faabcb102 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -35,7 +35,7 @@ alloy-signer.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } -alloy-rpc-types-engine = { workspace = true, features = ["jwt"] } +alloy-rpc-types-engine = { workspace = true, features = ["jwt-aws-lc-rs"] } alloy-sol-types.workspace = true alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true @@ -44,8 +44,8 @@ alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true -op-alloy-network.workspace = true -op-alloy-rpc-types.workspace = true +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } revm.workspace = true @@ -87,6 +87,10 @@ mpp.workspace = true foundry-wallets = { workspace = true, features = ["browser", "tempo"] } tokio-tungstenite.workspace = true futures.workspace = true +alloy-signer-local.workspace = true +base64.workspace = true +sha2 = "0.10" +tempfile.workspace = true [build-dependencies] chrono.workspace = true @@ -96,4 +100,12 @@ vergen = { workspace = true, features = ["build", "emit_and_set"] } foundry-evm-hardforks.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } axum = { workspace = true } -tempfile.workspace = true +k256 = { workspace = true } + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "foundry-common-fmt/optimism", +] diff --git a/crates/common/build.rs b/crates/common/build.rs index d89e23be850f4..9afa01b5757ef 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -15,16 +15,13 @@ fn main() -> Result<(), Box> { let sha_short = &sha[..10]; let tag_name = try_env_var("TAG_NAME").unwrap_or_else(|| String::from("dev")); - let is_nightly = tag_name.contains("nightly"); - let version_suffix = if is_nightly { "nightly" } else { &tag_name }; + let version = release_version(&env_var("CARGO_PKG_VERSION"), &tag_name); + let is_nightly = tag_name.starts_with("nightly"); if is_nightly { println!("cargo:rustc-env=FOUNDRY_IS_NIGHTLY_VERSION=true"); } - let pkg_version = env_var("CARGO_PKG_VERSION"); - let version = format!("{pkg_version}-{version_suffix}"); - // `PROFILE` captures only release or debug. Get the actual name from the out directory. let out_dir = PathBuf::from(env_var("OUT_DIR")); let profile = out_dir.components().rev().nth(3).unwrap().as_os_str().to_str().unwrap(); @@ -87,6 +84,19 @@ fn env_var(name: &str) -> String { try_env_var(name).unwrap() } +fn release_version(pkg_version: &str, tag_name: &str) -> String { + if let Some(version) = tag_name.strip_prefix('v') { + return version.to_owned(); + } + + // Normalize `nightly-` to `nightly` so tarball and Docker nightly + // artifacts produce the same version string. The commit identifier is + // already included in the SemVer build metadata (after `+`). + let normalized = if tag_name.starts_with("nightly-") { "nightly" } else { tag_name }; + + format!("{pkg_version}-{normalized}") +} + fn try_env_var(name: &str) -> Option { println!("cargo:rerun-if-env-changed={name}"); std::env::var(name).ok() diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 2c8e16bccdcc6..179c71048da5b 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -20,10 +20,10 @@ eyre.workspace = true # ui alloy-consensus.workspace = true -op-alloy-consensus.workspace = true +op-alloy-consensus = { workspace = true, optional = true } alloy-network.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } -op-alloy-rpc-types.workspace = true +op-alloy-rpc-types = { workspace = true, optional = true } alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true @@ -38,3 +38,7 @@ tempo-alloy.workspace = true [dev-dependencies] foundry-macros.workspace = true similar-asserts.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:op-alloy-consensus", "dep:op-alloy-rpc-types"] diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index e883810dcda34..2087a85236154 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -18,6 +18,7 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; @@ -448,6 +449,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxDeposit { fn pretty(&self) -> String { format!( @@ -472,6 +474,7 @@ input {}", } } +#[cfg(feature = "optimism")] impl UIfmt for TxPostExec { fn pretty(&self) -> String { format!( @@ -606,6 +609,7 @@ type {:#x} } } +#[cfg(feature = "optimism")] impl UIfmt for OpTxEnvelope { fn pretty(&self) -> String { match self { @@ -651,6 +655,7 @@ effectiveGasPrice {} } } +#[cfg(feature = "optimism")] impl UIfmt for op_alloy_rpc_types::Transaction { fn pretty(&self) -> String { format!( @@ -786,6 +791,7 @@ impl UIfmtSignatureExt for AnyTxEnvelope { } } +#[cfg(feature = "optimism")] impl UIfmtSignatureExt for OpTxEnvelope { fn signature_pretty(&self) -> Option<(String, String, String)> { self.signature().map(|sig| { @@ -1135,6 +1141,7 @@ mod tests { assert_eq!(b.pretty(), b32.pretty()); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx() { let s = r#" @@ -1186,6 +1193,7 @@ yParity 1 ); } + #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx_through_any() { let s = r#" diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 895b16b3b4532..95c7d4083f34e 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -383,16 +383,14 @@ impl ContractsByArtifact { &self, id: &str, ) -> Result>> { - let contracts = self - .iter() - .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) - .collect::>(); - - if contracts.len() > 1 { + let mut iter = + self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id); + let first = iter.next(); + if first.is_some() && iter.next().is_some() { eyre::bail!("{id} has more than one implementation."); } - Ok(contracts.first().copied()) + Ok(first) } /// Finds abi by name or source path @@ -411,7 +409,7 @@ impl ContractsByArtifact { let mut funcs = BTreeMap::new(); let mut events = BTreeMap::new(); let mut errors_abi = JsonAbi::new(); - for (_name, contract) in self.iter() { + for contract in self.values() { for func in contract.abi.functions() { funcs.insert(func.selector(), func.clone()); } diff --git a/crates/common/src/provider/mpp/keys.rs b/crates/common/src/provider/mpp/keys.rs index 65640c48ab841..fa0fc80ed3d03 100644 --- a/crates/common/src/provider/mpp/keys.rs +++ b/crates/common/src/provider/mpp/keys.rs @@ -7,6 +7,7 @@ use crate::tempo::{TEMPO_PRIVATE_KEY_ENV, WalletType, read_tempo_keys_file}; use alloy_primitives::Address; +use std::env; use tracing::debug; /// Options for MPP key discovery filtering. @@ -55,7 +56,7 @@ pub fn discover_mpp_key() -> Option { /// target chain and the required currency. pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 1. Check TEMPO_PRIVATE_KEY env var (no keychain metadata available) - if let Ok(key) = std::env::var(TEMPO_PRIVATE_KEY_ENV) { + if let Ok(key) = env::var(TEMPO_PRIVATE_KEY_ENV) { let key = key.trim().to_string(); if !key.is_empty() { debug!("using MPP key from {TEMPO_PRIVATE_KEY_ENV} env var"); @@ -73,11 +74,17 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { // 2. Read $TEMPO_HOME/wallet/keys.toml (default: ~/.tempo/wallet/keys.toml) let keys_file = read_tempo_keys_file()?; + // `expiry == 0` means "no expiry" on the wire. + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + // Pick primary key using the same deterministic order as // `Keystore::primary_key()` in tempo-common: // passkey > first entry with inline key > first entry // Only entries with a usable inline key can provide a signing key. - // Filter by chain_id and currency when provided. + // Filter by chain_id, currency, and freshness when provided. let candidates: Vec<_> = keys_file .keys .iter() @@ -86,6 +93,7 @@ pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { opts.currency .is_none_or(|cur| k.limits.is_empty() || k.limits.iter().any(|l| l.currency == cur)) }) + .filter(|k| k.expiry.is_none_or(|e| e == 0 || e > now)) .collect(); let primary = candidates @@ -135,6 +143,7 @@ mod tests { #[test] fn discover_from_tempo_home_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( r#" @@ -160,6 +169,7 @@ chain_id = 4217 #[test] fn discover_env_var_takes_priority_over_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let file_key = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; let env_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let toml_content = format!( @@ -187,6 +197,7 @@ key = "{file_key}" #[test] fn discover_returns_none_when_no_keys() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let (dir, _) = setup_keys_toml(""); unsafe { @@ -202,6 +213,7 @@ key = "{file_key}" #[test] fn discover_skips_entries_without_inline_key() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; let toml_content = format!( r#" @@ -344,6 +356,7 @@ key = "0xthe_key" #[test] fn discover_filters_by_chain_id() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); let mainnet_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let testnet_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let toml_content = format!( @@ -416,6 +429,62 @@ chain_id = 4217 unsafe { std::env::remove_var("TEMPO_HOME") }; } + #[test] + fn discover_filters_expired_entries() { + // Expired entries must not be selected, so the next 402 re-triggers + // the device-code flow instead of returning a stale key. + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let expired_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let fresh_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let toml_content = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{fresh_key}" +chain_id = 4217 +expiry = 0 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + // Even though the expired entry comes first, discovery skips it. + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, fresh_key); + + // With only the expired entry present, discovery returns None so the + // 402 path can run `ensure_access_key` again. + let only_expired = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 +"# + ); + let (dir2, _) = setup_keys_toml(&only_expired); + unsafe { std::env::set_var("TEMPO_HOME", dir2.path()) }; + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert!(config.is_none(), "expired-only keys.toml must not yield a usable key"); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + #[test] fn parse_keys_toml_unknown_fields_ignored() { let toml_str = r#" diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs index 334166b844613..c3e87f8cf42b5 100644 --- a/crates/common/src/provider/mpp/session.rs +++ b/crates/common/src/provider/mpp/session.rs @@ -175,6 +175,16 @@ impl SessionProvider { self } + /// Address that funds payments for this provider. + pub fn funding_wallet_address(&self) -> Address { + self.signing_mode.from_address(self.signer.address()) + } + + /// Chain ID from the selected wallet key, when known. + pub const fn key_chain_id(&self) -> Option { + self.key_chain_id + } + /// Set the chain ID and currencies from the key entry used to initialize /// this provider. Used to reject challenges for incompatible chains/currencies. /// When `chain_id` is `None` (e.g. env var key), chain filtering is skipped. diff --git a/crates/common/src/provider/mpp/transport.rs b/crates/common/src/provider/mpp/transport.rs index 67354dc2bd60d..9e3b16dedd59e 100644 --- a/crates/common/src/provider/mpp/transport.rs +++ b/crates/common/src/provider/mpp/transport.rs @@ -4,6 +4,7 @@ //! handling via the MPP protocol. When the RPC endpoint returns a 402 response, //! this transport automatically pays the challenge and retries the request. +use alloy_chains::Chain; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_transport::{TransportError, TransportErrorKind, TransportFut, TransportResult}; use mpp::{ @@ -16,12 +17,17 @@ use mpp::{ use reqwest::{StatusCode, header::HeaderMap}; use std::{ collections::HashMap, - fmt, - sync::{Mutex, OnceLock}, + env, fmt, io, + io::IsTerminal, + process::{Command, Stdio}, + sync::{ + Arc, LazyLock, Mutex, + atomic::{AtomicBool, Ordering}, + }, task, time::Duration, }; -use tokio::sync::OwnedMutexGuard; +use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard}; use tower::Service; use tracing::{Instrument, debug, debug_span, trace}; use url::Url; @@ -39,7 +45,27 @@ const MPP_RETRY_TIMEOUT: Duration = Duration::from_secs(120); /// Resolve the deposit amount from `MPP_DEPOSIT` env var or the default. fn default_deposit() -> u128 { - std::env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) + env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct FundingContext { + wallet_address: Option, + token: Option, + chain_id: Option, +} + +impl FundingContext { + fn token_line(&self) -> String { + self.token + .as_ref() + .map(|token| format!("Requested payment token: {token}\n\n")) + .unwrap_or_default() + } + + fn network(&self) -> Option { + self.chain_id.filter(|chain| chain.is_tempo()).map(|chain| chain.to_string()) + } } fn format_http_diagnostics(headers: &HeaderMap) -> String { @@ -60,12 +86,173 @@ fn format_http_diagnostics(headers: &HeaderMap) -> String { } } +fn tempo_wallet_fund_help(ctx: &FundingContext) -> String { + let mut command = "tempo wallet fund".to_string(); + if let Some(address) = ctx.wallet_address { + command.push_str(&format!(" --address {address}")); + } + if let Some(network) = ctx.network() { + command.push_str(&format!(" --network {network}")); + } + + let mut no_browser = command.clone(); + no_browser.push_str(" --no-browser"); + + format!( + "\n\nTempo wallet payment could not be funded for this paid RPC request.\n\n{}\ + Fund the wallet, then rerun the command:\n {command}\n\n\ + If this CLI is running on a remote or headless host, use:\n {no_browser}", + ctx.token_line() + ) +} + +/// Decide whether the interactive `tempo wallet fund` flow may be launched. +/// +/// Policy (library-safe): +/// - never run inside CI +/// - never run unless both stdin and stderr are real terminals +/// - `FOUNDRY_MPP_NO_AUTO_FUND` is honored as an opt-out; it must not bypass CI/TTY guards in +/// shared transport code that may be embedded inside long-running RPC daemons. +fn interactive_tempo_fund_allowed( + no_auto_fund: Option<&str>, + in_ci: bool, + stdin_is_terminal: bool, + stderr_is_terminal: bool, +) -> bool { + if no_auto_fund.is_some_and(|v| { + !(v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off")) + }) { + return false; + } + + if in_ci { + return false; + } + + stdin_is_terminal && stderr_is_terminal +} + +fn can_run_interactive_tempo_fund() -> bool { + if cfg!(test) { + return false; + } + + interactive_tempo_fund_allowed( + std::env::var("FOUNDRY_MPP_NO_AUTO_FUND").ok().as_deref(), + std::env::var_os("CI").is_some(), + std::io::stdin().is_terminal(), + std::io::stderr().is_terminal(), + ) +} + +fn tempo_bin() -> String { + std::env::var("TEMPO_BIN").unwrap_or_else(|_| "tempo".to_string()) +} + +async fn run_interactive_tempo_fund(ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + + let tempo = tempo_bin(); + let mut args = vec!["wallet".to_string(), "fund".to_string()]; + if let Some(address) = ctx.wallet_address { + args.push("--address".to_string()); + args.push(address.to_string()); + } + if let Some(network) = ctx.network() { + args.push("--network".to_string()); + args.push(network); + } + + tracing::warn!( + token = ?ctx.token, + chain_id = ?ctx.chain_id, + "MPP payment could not be funded; opening `tempo wallet fund`" + ); + + let status = tokio::task::spawn_blocking(move || { + Command::new(tempo) + .args(args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + }) + .await + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to join tempo wallet fund process: {e}" + ))) + })? + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to run `tempo wallet fund`: {e}{}", + tempo_wallet_fund_help(ctx) + ))) + })?; + + if status.success() { + Ok(true) + } else { + Err(TransportErrorKind::custom(std::io::Error::other(format!( + "`tempo wallet fund` exited with status {status}{}", + tempo_wallet_fund_help(ctx) + )))) + } +} + +/// Single-attempt guard around [`run_interactive_tempo_fund`]. +/// +/// Ensures that for one logical request we launch `tempo wallet fund` at most +/// once, regardless of how many recovery paths (`do_request`, `pay_and_retry`, +/// `handle_response_or_retry_after_fund`, ...) attempt it. +async fn maybe_auto_fund(used: &AtomicBool, ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + if used.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Ok(false); + } + run_interactive_tempo_fund(ctx).await +} + +/// Returns true iff a 402 response carries a structured insufficient-balance +/// problem (RFC 9457 `PaymentErrorDetails`). +/// +/// We deliberately do **not** match on free-text body content or on generic +/// `verification-failed` problem types, as those have many non-funding causes +/// (bad signature, replay, expired challenge, clock skew, key provisioning, +/// malformed auth, ...). +fn should_suggest_tempo_fund(status: StatusCode, body: &[u8]) -> bool { + if status != StatusCode::PAYMENT_REQUIRED { + return false; + } + let Ok(problem) = serde_json::from_slice::(body) else { + return false; + }; + problem.problem_type.ends_with("/insufficient-balance") +} + +fn format_mpp_payment_failure( + error: impl fmt::Display, + ctx: &FundingContext, + suggest_fund: bool, +) -> String { + let message = error.to_string(); + if suggest_fund { + format!("MPP payment failed: {message}{}", tempo_wallet_fund_help(ctx)) + } else { + format!("MPP payment failed: {message}") + } +} + /// Process-wide payment serialization locks, keyed by origin URL. /// /// Created eagerly so the lock exists before the first provider init, /// preventing concurrent first-402 races. -static GLOBAL_PAY_LOCKS: OnceLock>>>> = - OnceLock::new(); +static GLOBAL_PAY_LOCKS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); /// Production transport: lazily discovers MPP keys from the Tempo wallet on /// first 402 response. @@ -75,24 +262,21 @@ pub type LazyMppHttpTransport = MppHttpTransport; /// Tempo wallet configuration on first use. #[derive(Clone, Debug)] pub struct LazySessionProvider { - inner: std::sync::Arc>>, + inner: Arc>>, /// Eagerly-created, process-wide payment serialization lock for this origin. - pay_lock: std::sync::Arc>, + pay_lock: Arc>, origin: String, } impl LazySessionProvider { pub(super) fn new(origin: String) -> Self { - let pay_lock = { - let global = GLOBAL_PAY_LOCKS.get_or_init(|| Mutex::new(HashMap::new())); - global - .lock() - .unwrap() - .entry(origin.clone()) - .or_insert_with(|| std::sync::Arc::new(tokio::sync::Mutex::new(()))) - .clone() - }; - Self { inner: std::sync::Arc::new(Mutex::new(None)), pay_lock, origin } + let pay_lock = GLOBAL_PAY_LOCKS + .lock() + .unwrap() + .entry(origin.clone()) + .or_insert_with(|| Arc::new(AsyncMutex::new(()))) + .clone(); + Self { inner: Arc::new(Mutex::new(None)), pay_lock, origin } } fn set_key_provisioned(&self, provisioned: bool) { @@ -125,6 +309,14 @@ impl LazySessionProvider { } } + /// Drop the cached `SessionProvider` so the next `get_or_init` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry, so a long-lived transport doesn't keep paying with + /// the superseded key. + fn invalidate(&self) { + *self.inner.lock().unwrap() = None; + } + pub(super) fn get_or_init(&self, opts: DiscoverOptions) -> TransportResult { let mut guard = self.inner.lock().unwrap(); if let Some(ref provider) = *guard { @@ -132,18 +324,20 @@ impl LazySessionProvider { } let config = discover_mpp_config(opts).ok_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "RPC endpoint returned HTTP 402 Payment Required. \ This endpoint requires payment via the Machine Payments Protocol (MPP).\n\n\ - To configure MPP, install the Tempo wallet CLI and create a key:\n\ - \n curl -sSL https://tempo.xyz/install.sh | bash\ - \n tempo wallet login\ + Authorize an access key against your Tempo wallet:\n\ + \n cast tempo login\ + \n\nIn headless environments, pass `--no-browser` to print the authorization \ + URL instead of launching a browser:\n\ + \n cast tempo login --no-browser\ \n\nSee https://docs.tempo.xyz for more information.", )) })?; let signer: mpp::PrivateKeySigner = config.key.parse().map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("invalid MPP key: {e}"))) + TransportErrorKind::custom(io::Error::other(format!("invalid MPP key: {e}"))) })?; let signing_mode = if let Some(wallet) = config.wallet_address { @@ -152,7 +346,7 @@ impl LazySessionProvider { .as_ref() .map(|hex_str| { crate::tempo::decode_key_authorization(hex_str).map(Box::new).map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "invalid MPP key_authorization: {e}" ))) }) @@ -223,6 +417,17 @@ where P::Provider: Send + Sync + 'static, { async fn do_request(self, req: RequestPacket) -> TransportResult { + // Per-request guard: launch `tempo wallet fund` at most once for one + // logical request, regardless of how many recovery paths attempt it. + let auto_fund_used = AtomicBool::new(false); + self.do_request_inner(req, &auto_fund_used).await + } + + async fn do_request_inner( + self, + req: RequestPacket, + auto_fund_used: &AtomicBool, + ) -> TransportResult { let body = serde_json::to_vec(&req).map_err(TransportErrorKind::custom)?; let headers = req.headers(); @@ -246,15 +451,53 @@ where // held until the retry response is fully handled. let _pay_guard = self.provider.lock_pay().await; - let (resolved, challenge) = Self::select_challenge(&resp, &self.provider)?; + // No local key for any offered challenge → run device-code flow, + // invalidate the cached provider, and fetch a fresh 402 (the original + // may have expired during the browser/passkey flow). + let (resolved, challenge) = + if let Some(chain_id) = tempo_chain_needing_auth(&self.url, &resp) { + debug!(chain_id, "launching wallet.tempo authorization"); + let cfg = crate::tempo::EnsureAccessKeyConfig::from_env(chain_id); + crate::tempo::ensure_access_key(cfg).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "tempo access key authorization failed: {e}" + ))) + })?; + self.provider.invalidate_cached_provider(); + self.fetch_fresh_challenge(&headers, &body).await? + } else { + Self::select_challenge(&resp, &self.provider)? + }; + let funding_ctx = self.provider.funding_context(&challenge); debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP 402 challenge, paying"); - let credential = resolved.pay(&challenge).await.map_err(|e| { - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let credential = match resolved.pay(&challenge).await { + Ok(credential) => credential, + Err(e) => { + // Only the explicit `InsufficientBalance` variant is treated as + // a fundable error. Any other failure must surface unchanged so + // we don't mask payment/protocol issues behind a fund prompt. + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + resolved.pay(&challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); TransportErrorKind::custom(std::io::Error::other(format!( "failed to format MPP credential: {e}" ))) @@ -286,9 +529,20 @@ where self.provider.commit_topup_and_track_voucher(); let resolved = self.provider.resolve()?; - let voucher_resp = self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(voucher_resp).await; + let voucher_resp = + self.pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used).await?; + + // Route the voucher response through the funding-aware handler so + // a final 402 here also gets the fund retry / contextual help. + let result = self + .handle_response_or_retry_after_fund( + voucher_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -304,7 +558,7 @@ where self.provider.rollback_pending(); self.provider.clear_channels(); - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "MPP channel not found on server (410 Gone). \ The server may have restarted or the channel was closed externally.\n\ Local channel state has been cleared. Re-run to open a new channel.", @@ -333,10 +587,19 @@ where debug!("MPP voucher stale, retrying with fresh voucher"); let resolved = self.provider.resolve()?; if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { - let final_resp = - self.pay_and_retry(&challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.flush_pending(); } else { @@ -372,10 +635,19 @@ where let (resolved, fresh_challenge) = self.fetch_fresh_challenge(&headers, &body).await?; - let final_resp = - self.pay_and_retry(&fresh_challenge, &resolved, &headers, &body).await?; - - let result = Self::handle_response(final_resp).await; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; if result.is_ok() { self.provider.set_key_provisioned(true); self.provider.flush_pending(); @@ -386,9 +658,40 @@ where } self.provider.rollback_pending(); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) + && maybe_auto_fund(auto_fund_used, &funding_ctx).await? + { + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + let mut error_text = format!("{retry_text}{diagnostics}"); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) { + error_text.push_str(&tempo_wallet_fund_help(&funding_ctx)); + } return Err(TransportErrorKind::http_error( StatusCode::PAYMENT_REQUIRED.as_u16(), - format!("{retry_text}{diagnostics}"), + error_text, )); } @@ -409,15 +712,32 @@ where provider: &P::Provider, headers: &reqwest::header::HeaderMap, body: &[u8], + auto_fund_used: &AtomicBool, ) -> TransportResult { - let credential = provider.pay(challenge).await.map_err(|e| { - self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!("MPP payment failed: {e}"))) - })?; + let funding_ctx = self.provider.funding_context(challenge); + let credential = match provider.pay(challenge).await { + Ok(credential) => credential, + Err(e) => { + self.provider.rollback_pending(); + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + provider.pay(challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; let auth_header = format_authorization(&credential).map_err(|e| { self.provider.rollback_pending(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "failed to format MPP credential: {e}" ))) })?; @@ -437,6 +757,41 @@ where }) } + async fn handle_response_or_retry_after_fund( + &self, + resp: reqwest::Response, + headers: &reqwest::header::HeaderMap, + body: &[u8], + funding_ctx: &FundingContext, + auto_fund_used: &AtomicBool, + ) -> TransportResult { + if resp.status() != StatusCode::PAYMENT_REQUIRED { + return Self::handle_response_with_funding(resp, Some(funding_ctx)).await; + } + + let diagnostics = format_http_diagnostics(resp.headers()); + let status = resp.status(); + let resp_body = resp.bytes().await.map_err(TransportErrorKind::custom)?; + + if should_suggest_tempo_fund(status, &resp_body) + && maybe_auto_fund(auto_fund_used, funding_ctx).await? + { + self.provider.rollback_pending(); + + let (resolved, fresh_challenge) = self.fetch_fresh_challenge(headers, body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, headers, body, auto_fund_used) + .await?; + return Self::handle_response_with_funding(final_resp, Some(funding_ctx)).await; + } + + let mut error_text = format!("{}{diagnostics}", String::from_utf8_lossy(&resp_body)); + if should_suggest_tempo_fund(status, &resp_body) { + error_text.push_str(&tempo_wallet_fund_help(funding_ctx)); + } + Err(TransportErrorKind::http_error(status.as_u16(), error_text)) + } + /// Fetch a fresh 402 challenge from the server (unauthenticated request). /// /// Returns `Ok(Some((provider, challenge)))` if the server returns a 402 @@ -462,7 +817,7 @@ where // Non-402 → return whatever the server sent (could be success or error). let result = Self::handle_response(fresh_resp).await; return Err(result.err().unwrap_or_else(|| { - TransportErrorKind::custom(std::io::Error::other( + TransportErrorKind::custom(io::Error::other( "unexpected success on unauthenticated fresh probe", )) })); @@ -477,25 +832,14 @@ where resp: &reqwest::Response, provider: &P, ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { - let www_auth_values: Vec<&str> = resp - .headers() - .get_all(WWW_AUTHENTICATE_HEADER) - .iter() - .filter_map(|v| v.to_str().ok()) - .collect(); - - if www_auth_values.is_empty() { - return Err(TransportErrorKind::custom(std::io::Error::other(format!( + let challenges = parse_challenges(resp); + if challenges.is_empty() && resp.headers().get(WWW_AUTHENTICATE_HEADER).is_none() { + return Err(TransportErrorKind::custom(io::Error::other(format!( "402 response missing WWW-Authenticate header{}", format_http_diagnostics(resp.headers()) )))); } - let challenges: Vec<_> = parse_www_authenticate_all(www_auth_values) - .into_iter() - .filter_map(|r| r.ok()) - .collect(); - let mut last_resolve_err: Option = None; let resolved_pair = challenges.iter().find_map(|c| { let (chain_id, currency) = extract_challenge_chain_and_currency(c); @@ -515,7 +859,7 @@ where } let offered: Vec<_> = challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); - TransportErrorKind::custom(std::io::Error::other(format!( + TransportErrorKind::custom(io::Error::other(format!( "no supported MPP challenge; server offered [{}]", offered.join(", "), ))) @@ -523,6 +867,17 @@ where } async fn handle_response(resp: reqwest::Response) -> TransportResult { + Self::handle_response_with_funding(resp, None).await + } + + /// Like [`Self::handle_response`] but, when an unsuccessful 402 looks like a + /// fundable error, appends actionable `tempo wallet fund` help that uses + /// the per-request `FundingContext` (so the suggested command includes + /// `--address` and `--network` when known). + async fn handle_response_with_funding( + resp: reqwest::Response, + funding_ctx: Option<&FundingContext>, + ) -> TransportResult { let status = resp.status(); debug!(%status, "received response from MPP transport"); let diagnostics = format_http_diagnostics(resp.headers()); @@ -536,10 +891,19 @@ where } if !status.is_success() { - return Err(TransportErrorKind::http_error( - status.as_u16(), - format!("{}{diagnostics}", String::from_utf8_lossy(&body)), - )); + let mut body_text = format!("{}{diagnostics}", String::from_utf8_lossy(&body)); + if should_suggest_tempo_fund(status, &body) { + let default_ctx; + let ctx = match funding_ctx { + Some(c) => c, + None => { + default_ctx = FundingContext::default(); + &default_ctx + } + }; + body_text.push_str(&tempo_wallet_fund_help(ctx)); + } + return Err(TransportErrorKind::http_error(status.as_u16(), body_text)); } serde_json::from_slice(&body) @@ -547,6 +911,57 @@ where } } +/// Returns `Some(chain_id)` when a 402 response should trigger the +/// `wallet.tempo.xyz` device-code authorization flow. +/// +/// Conditions: known Tempo endpoint, interactive (TTY, not `CI`), and no +/// offered Tempo challenge resolves against a local key on `(chain, currency)`. +/// The picked chain matches the first unresolved challenge — same iteration +/// order [`MppHttpTransport::select_challenge`] uses. +fn tempo_chain_needing_auth(url: &Url, resp: &reqwest::Response) -> Option { + if !io::stderr().is_terminal() || env::var_os("CI").is_some() { + return None; + } + pick_chain_needing_auth(url, &parse_challenges(resp)) +} + +/// Extract all parseable MPP challenges from a 402 response's `WWW-Authenticate` headers. +fn parse_challenges(resp: &reqwest::Response) -> Vec { + let values: Vec<&str> = resp + .headers() + .get_all(WWW_AUTHENTICATE_HEADER) + .iter() + .filter_map(|v| v.to_str().ok()) + .collect(); + parse_www_authenticate_all(values).into_iter().filter_map(|r| r.ok()).collect() +} + +/// Inner logic of [`tempo_chain_needing_auth`], factored out for testing. +fn pick_chain_needing_auth( + url: &Url, + challenges: &[mpp::protocol::core::PaymentChallenge], +) -> Option { + if !crate::tempo::is_known_tempo_endpoint(url) { + return None; + } + + let tempo_challenges: Vec<_> = + challenges.iter().filter(|c| c.method.as_str() == "tempo").collect(); + + // If any challenge already resolves with a local key, no auth needed. + let any_resolvable = tempo_challenges.iter().any(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + super::keys::discover_mpp_config(super::keys::DiscoverOptions { chain_id, currency }) + .is_some() + }); + if any_resolvable { + return None; + } + + tempo_challenges.iter().find_map(|c| extract_challenge_chain_and_currency(c).0) +} + /// Extract `(chainId, currency)` from a parsed MPP challenge. pub(super) fn extract_challenge_chain_and_currency( c: &mpp::protocol::core::PaymentChallenge, @@ -576,10 +991,28 @@ pub(crate) trait ResolveProvider { fn flush_pending(&self) {} fn rollback_pending(&self) {} fn commit_topup_and_track_voucher(&self) {} + /// Drop any cached payment provider so the next `resolve_for` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry. + fn invalidate_cached_provider(&self) {} + fn funding_wallet_address(&self) -> Option { + None + } + fn funding_chain_id(&self) -> Option { + None + } + fn funding_context(&self, challenge: &mpp::protocol::core::PaymentChallenge) -> FundingContext { + let (challenge_chain_id, token) = extract_challenge_chain_and_currency(challenge); + FundingContext { + wallet_address: self.funding_wallet_address(), + token, + chain_id: challenge_chain_id.or_else(|| self.funding_chain_id()).map(Chain::from_id), + } + } /// Acquire the payment serialization lock. The returned guard must be held /// across the entire 402 → pay → retry → response cycle to prevent /// concurrent channel opens and colliding expiring-nonce transactions. - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn lock_pay(&self) -> impl Future>> + Send { async { None } } } @@ -599,7 +1032,7 @@ impl ResolveProvider for LazySessionProvider { // regardless of opts. Re-check that the provider's key is compatible // with this challenge's chain/currency. if !provider.matches_challenge(opts.chain_id, opts.currency) { - return Err(TransportErrorKind::custom(std::io::Error::other( + return Err(TransportErrorKind::custom(io::Error::other( "cached provider does not match challenge chain/currency", ))); } @@ -623,7 +1056,16 @@ impl ResolveProvider for LazySessionProvider { fn commit_topup_and_track_voucher(&self) { Self::commit_topup_and_track_voucher(self) } - fn lock_pay(&self) -> impl std::future::Future>> + Send { + fn invalidate_cached_provider(&self) { + Self::invalidate(self) + } + fn funding_wallet_address(&self) -> Option { + self.inner.lock().unwrap().as_ref().map(|p| p.funding_wallet_address()) + } + fn funding_chain_id(&self) -> Option { + self.inner.lock().unwrap().as_ref().and_then(|p| p.key_chain_id()) + } + fn lock_pay(&self) -> impl Future>> + Send { let lock = self.pay_lock.clone(); async move { Some(lock.lock_owned().await) } } @@ -685,7 +1127,7 @@ mod tests { fn pay( &self, challenge: &PaymentChallenge, - ) -> impl std::future::Future> + Send { + ) -> impl Future> + Send { let echo = challenge.to_echo(); async move { Ok(PaymentCredential::with_source( @@ -697,6 +1139,21 @@ mod tests { } } + #[derive(Clone, Debug)] + struct InsufficientBalanceProvider; + + impl PaymentProvider for InsufficientBalanceProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + async fn pay(&self, _challenge: &PaymentChallenge) -> Result { + Err(MppError::InsufficientBalance(Some( + "wallet has 0 pathUSD but needs 100000".to_string(), + ))) + } + } + fn test_challenge() -> (PaymentChallenge, String) { let request = Base64UrlJson::from_value(&serde_json::json!({ "amount": "1000", @@ -853,8 +1310,238 @@ mod tests { handle.abort(); } + #[tokio::test] + async fn test_mpp_transport_payment_failure_suggests_tempo_wallet_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move || { + let www_auth = www_auth.clone(); + async move { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required", + ) + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + InsufficientBalanceProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_retry_402_insufficient_balance_suggests_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail( + "Insufficient pathUSD balance: have 0, need 100000", + ), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("InsufficientBalanceError"), "got: {msg}"); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + /// Generic `verification-failed` has many non-funding causes (bad signature, + /// replay, expired challenge, clock skew, ...). The transport must surface + /// the original error verbatim and must NOT add a "fund your wallet" hint. + #[tokio::test] + async fn test_mpp_transport_final_402_verification_failed_does_not_suggest_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Verification Failed"), "got: {msg}"); + assert!( + !msg.contains("Tempo wallet payment could not be funded"), + "verification-failed must not be classified as fundable; got: {msg}" + ); + + handle.abort(); + } + + // --- Classifier unit tests -------------------------------------------- + + #[test] + fn classifier_only_triggers_on_explicit_insufficient_balance_problem() { + // explicit insufficient-balance → true + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail("Insufficient pathUSD balance"), + ) + .unwrap(); + assert!(should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_verification_failed() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_unrelated_text_with_balance_words() { + // Free-text 402 body that just happens to mention the word "balance" + // must NOT trigger the fund suggestion (no structured problem details). + let body = + b"402 Payment Required: server could not balance ledger entries; insufficient inputs."; + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, body)); + } + + #[test] + fn classifier_does_not_trigger_outside_402() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_detail("Insufficient balance"), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::INTERNAL_SERVER_ERROR, &body)); + assert!(!should_suggest_tempo_fund(StatusCode::OK, &body)); + } + + #[test] + fn fund_help_includes_address_and_network_for_known_chain() { + let ctx = FundingContext { + wallet_address: Some("0x000000000000000000000000000000000000dEaD".parse().unwrap()), + token: Some("0x20c0".to_string()), + chain_id: Some(Chain::from_id(42431)), + }; + let help = tempo_wallet_fund_help(&ctx); + assert!(help.contains("--address 0x"), "missing --address: {help}"); + assert!(help.contains("--network tempo-moderato"), "missing --network: {help}"); + assert!(help.contains("--no-browser"), "missing --no-browser: {help}"); + assert!(help.contains("Requested payment token: 0x20c0"), "missing token: {help}"); + + let mainnet = FundingContext { chain_id: Some(Chain::from_id(4217)), ..ctx }; + let help2 = tempo_wallet_fund_help(&mainnet); + assert!(help2.contains("--network tempo"), "missing tempo network: {help2}"); + } + + #[test] + fn auto_fund_policy_blocks_in_ci_and_non_tty() { + assert!(!interactive_tempo_fund_allowed(Some("1"), true, true, true), "must not run in CI"); + assert!( + interactive_tempo_fund_allowed(Some("0"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=0 must not disable" + ); + assert!( + interactive_tempo_fund_allowed(Some("false"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=false must not disable" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, false, true), + "stdin must be a terminal" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, true, false), + "stderr must be a terminal" + ); + assert!(!interactive_tempo_fund_allowed(Some("1"), false, true, true)); + assert!(!interactive_tempo_fund_allowed(Some("true"), false, true, true)); + assert!(interactive_tempo_fund_allowed(None, false, true, true)); + } + #[tokio::test] async fn test_plain_http_402_shows_mpp_setup_instructions() { + let _g = crate::tempo::test_env_mutex().lock().await; let (_, www_auth) = test_challenge(); let app = axum::Router::new().route( @@ -920,6 +1607,32 @@ mod tests { ); } + /// `invalidate_cached_provider` clears the cache so the next + /// `get_or_init` re-runs discovery — the path `do_request` takes after + /// `ensure_access_key` writes a fresh `keys.toml` entry. + #[tokio::test] + async fn lazy_session_provider_invalidate_clears_cache() { + let _g = crate::tempo::test_env_mutex().lock().await; + // TEMPO_PRIVATE_KEY lets discovery succeed without a keys.toml. + let key_hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + unsafe { + std::env::set_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV, key_hex); + std::env::remove_var(crate::tempo::TEMPO_HOME_ENV); + } + + let lazy = LazySessionProvider::new("https://rpc.example.com".into()); + let _ = lazy.get_or_init(Default::default()).expect("discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected provider to be cached"); + + ResolveProvider::invalidate_cached_provider(&lazy); + assert!(lazy.inner.lock().unwrap().is_none(), "expected cache to be cleared"); + + let _ = lazy.get_or_init(Default::default()).expect("re-discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected re-init to repopulate cache"); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV) }; + } + #[test] fn challenge_chain_and_currency_extraction() { let extract = |headers: Vec<&str>| -> Vec<(Option, Option)> { @@ -955,4 +1668,73 @@ mod tests { ); assert_eq!(extract(vec![&no_details]), vec![(None, Some("0x20c0".into()))]); } + + /// Auth must trigger when a key matches the chain but not the currency. + #[test] + fn pick_chain_needing_auth_currency_aware() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let dir = tempfile::tempdir().unwrap(); + let wallet = dir.path().join("wallet"); + std::fs::create_dir_all(&wallet).unwrap(); + std::fs::write( + wallet.join("keys.toml"), + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +chain_id = 4217 + +[[keys.limits]] +currency = "0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +limit = "1000" +"#, + ) + .unwrap(); + unsafe { + std::env::set_var(crate::tempo::TEMPO_HOME_ENV, dir.path()); + std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV); + } + + let url = Url::parse("https://rpc.mpp.tempo.xyz").unwrap(); + let mk = |currency: &str| -> PaymentChallenge { + PaymentChallenge { + id: "x".into(), + realm: "api".into(), + method: MethodName::new("tempo"), + intent: IntentName::new("charge"), + request: Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1", + "currency": currency, + "recipient": "0xabc", + "methodDetails": { "chainId": 4217 } + })) + .unwrap(), + expires: None, + description: None, + digest: None, + opaque: None, + } + }; + + // Currency mismatch → auth needed. + let mismatched = mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + assert_eq!(pick_chain_needing_auth(&url, &[mismatched]), Some(4217)); + + // Currency match → no auth. + let matched = mk("0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert_eq!(pick_chain_needing_auth(&url, &[matched]), None); + + // Non-Tempo host → never triggers, even without a key. + let stripe_url = Url::parse("https://api.stripe.com").unwrap(); + assert_eq!( + pick_chain_needing_auth( + &stripe_url, + &[mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")] + ), + None, + ); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_HOME_ENV) }; + } } diff --git a/crates/common/src/provider/mpp/ws.rs b/crates/common/src/provider/mpp/ws.rs index f631d0b08a2fc..69aef7d4f4cbc 100644 --- a/crates/common/src/provider/mpp/ws.rs +++ b/crates/common/src/provider/mpp/ws.rs @@ -378,6 +378,8 @@ mod tests { /// MPP server sends challenge → client pays → server sends receipt. #[tokio::test] async fn test_ws_mpp_challenge_credential_receipt() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); @@ -452,6 +454,8 @@ mod tests { /// MPP server sends challenge, client pays, server closes → rollback. #[tokio::test] async fn test_ws_mpp_rollback_on_post_pay_close() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; let challenge = test_challenge(); let challenge_json = serde_json::to_value(&challenge).unwrap(); diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 7db1ebd1b3f91..f59a2efa75b8e 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -36,7 +36,11 @@ fn is_known_mpp_endpoint(url: &Url) -> bool { /// Only meant to be used internally by [RuntimeTransport]. #[derive(Clone, Debug)] pub enum InnerTransport { - /// HTTP transport with lazy MPP 402 handling + /// HTTP transport with lazy MPP 402 handling. + /// + /// For known Tempo endpoints, the MPP layer additionally runs the + /// `wallet.tempo.xyz` device-code flow on a 402 when no local access key + /// is configured (see [`crate::tempo::ensure_access_key`]). Http(LazyMppHttpTransport), /// WebSocket transport Ws(PubSubFrontend), diff --git a/crates/common/src/tempo/auth.rs b/crates/common/src/tempo/auth.rs new file mode 100644 index 0000000000000..d79306cfb74f2 --- /dev/null +++ b/crates/common/src/tempo/auth.rs @@ -0,0 +1,494 @@ +//! Tempo wallet device-code authorization flow. +//! +//! Implements the CLI side of the tempoxyz/accounts `cli-auth` device-code +//! protocol: generates a local secp256k1 access key, creates a PKCE-protected +//! device code, opens `wallet.tempo.xyz/cli-auth?code=` in the browser, +//! polls until the user authorizes the key on their passkey wallet, and writes +//! the resulting `keyAuthorization` to `~/.tempo/wallet/keys.toml`. + +use crate::tempo::{ + KeyEntry, KeyType, StoredTokenLimit, WalletType, decode_key_authorization, upsert_key_entry, +}; +use alloy_primitives::{Address, B256, hex}; +use alloy_signer_local::PrivateKeySigner; +use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +#[cfg(any(unix, windows))] +use std::process::Command; +use std::{ + env, + sync::LazyLock, + time::{Duration, Instant}, +}; +use tempo_primitives::transaction::{SignatureType, SignedKeyAuthorization}; +use tokio::sync::Mutex; + +/// Default device-code service URL (production wallet.tempo.xyz). +const DEFAULT_CLI_AUTH_URL: &str = "https://wallet.tempo.xyz/cli-auth"; + +/// Returns `true` if `url`'s host is `tempo.xyz` or a subdomain of it. +pub(crate) fn is_known_tempo_endpoint(url: &url::Url) -> bool { + url.host_str().is_some_and(|host| host == "tempo.xyz" || host.ends_with(".tempo.xyz")) +} + +/// Env var to override the device-code service URL (for tests / staging). +const TEMPO_CLI_AUTH_URL_ENV: &str = "TEMPO_CLI_AUTH_URL"; + +const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2); +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); + +/// Per-process serialization of concurrent `ensure_access_key` calls. +/// +/// Prevents two `cast` invocations in the same process from racing two browser +/// popups for the same chain. +static AUTH_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Configuration for [`ensure_access_key`]. +#[derive(Clone, Debug)] +pub struct EnsureAccessKeyConfig { + /// Chain ID the access key is being authorized for. + pub chain_id: u64, + /// Device-code service base URL. Defaults to [`DEFAULT_CLI_AUTH_URL`]. + pub(crate) service_url: String, + /// Poll interval. + pub(crate) poll_interval: Duration, + /// Total timeout for the authorization flow. + pub(crate) timeout: Duration, + /// If `true`, print the authorization URL to stderr instead of opening a + /// browser. + pub no_browser: bool, +} + +impl EnsureAccessKeyConfig { + /// Build a config from the environment for the given chain. + /// + /// `no_browser` defaults to `true` under `CI`; callers (e.g. `cast tempo + /// login --no-browser`) may override it. + pub fn from_env(chain_id: u64) -> Self { + Self { + chain_id, + service_url: env::var(TEMPO_CLI_AUTH_URL_ENV) + .unwrap_or_else(|_| DEFAULT_CLI_AUTH_URL.to_string()), + poll_interval: DEFAULT_POLL_INTERVAL, + timeout: DEFAULT_TIMEOUT, + no_browser: env::var_os("CI").is_some(), + } + } +} + +/// Open `url` via the OS default browser handler. On platforms without a known +/// opener, this is a no-op (the URL is still printed by [`ensure_access_key`]). +fn open_browser(_url: &str) { + #[cfg(target_os = "macos")] + let _ = Command::new("open").arg(_url).spawn(); + #[cfg(target_os = "windows")] + let _ = Command::new("cmd").args(["/c", "start", "", _url]).spawn(); + #[cfg(all(unix, not(target_os = "macos")))] + let _ = Command::new("xdg-open").arg(_url).spawn(); +} + +/// Result of [`ensure_access_key`]. +#[derive(Debug, Clone)] +pub struct AccessKeyOutcome { + pub wallet_address: Address, + pub key_address: Address, + pub chain_id: u64, +} + +/// Run the device-code flow, persist the resulting key to `keys.toml`, and +/// return the new entry's identifying fields. +pub async fn ensure_access_key(cfg: EnsureAccessKeyConfig) -> Result { + let _guard = AUTH_LOCK.lock().await; + + let signer = PrivateKeySigner::random(); + let key_address = signer.address(); + // The server requires uncompressed SEC1 (65-byte `0x04 || X || Y`); the + // default `to_sec1_bytes()` would emit the compressed 33-byte form. + let pub_key_hex = format!( + "0x{}", + hex::encode(signer.credential().verifying_key().to_encoded_point(false).as_bytes()), + ); + + let code_verifier = random_code_verifier(); + let client = reqwest::Client::builder().timeout(Duration::from_secs(30)).build()?; + let service = cfg.service_url.trim_end_matches('/'); + + let create_req = CreateCodeRequest { + chain_id: cfg.chain_id, + code_challenge: sha256_b64url(&code_verifier), + key_type: "secp256k1", + pub_key: pub_key_hex, + }; + let code = create_code_with_retry(&client, service, &create_req, cfg.timeout).await?; + + let browser_url = format!("{service}?code={code}"); + if cfg.no_browser { + let _ = crate::sh_eprintln!("Open this URL to authorize: {browser_url}"); + } else { + let _ = crate::sh_eprintln!( + "Opening wallet.tempo to authorize an access key…\n {browser_url}" + ); + open_browser(&browser_url); + } + + let poll = PollRequest { code_verifier }; + let started = Instant::now(); + loop { + // Retry transient network/5xx/429 failures within `cfg.timeout`. + let send_res = client.post(format!("{service}/poll/{code}")).json(&poll).send().await; + + let resp = match send_res { + Ok(r) => r, + Err(e) if is_transient_error(&e) && started.elapsed() < cfg.timeout => { + tracing::debug!(error = %e, "transient error polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + Err(e) => return Err(e.into()), + }; + + let status = resp.status(); + if !status.is_success() { + if is_transient_status(status) && started.elapsed() < cfg.timeout { + tracing::debug!(%status, "transient HTTP status polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code poll failed ({status}): {body}"); + } + + let body: PollResponse = resp.json().await?; + match body { + PollResponse::Pending => { + if started.elapsed() > cfg.timeout { + eyre::bail!("timed out waiting for wallet authorization (code {code})"); + } + tokio::time::sleep(cfg.poll_interval).await; + } + PollResponse::Expired => { + eyre::bail!("device code {code} expired before authorization"); + } + PollResponse::Authorized { account_address, key_authorization } => { + let hex_str = key_authorization.ok_or_else(|| { + eyre::eyre!("wallet authorized response missing key_authorization") + })?; + let signed: SignedKeyAuthorization = decode_key_authorization(&hex_str)?; + // Reject mismatches before persisting — an unusable keys.toml + // entry would silently break the next 402 retry. + if signed.authorization.key_id != key_address { + eyre::bail!( + "wallet authorized key {} but the locally generated key is {}", + signed.authorization.key_id, + key_address, + ); + } + if signed.authorization.chain_id != cfg.chain_id { + eyre::bail!( + "wallet authorized chain {} but {} was requested", + signed.authorization.chain_id, + cfg.chain_id, + ); + } + if signed.authorization.key_type != SignatureType::Secp256k1 { + eyre::bail!( + "wallet returned keyType {:?} but secp256k1 was requested", + signed.authorization.key_type, + ); + } + let chain_id = signed.authorization.chain_id; + let key_authorization = + if hex_str.starts_with("0x") { hex_str } else { format!("0x{hex_str}") }; + let entry = KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: account_address, + chain_id, + key_type: match signed.authorization.key_type { + SignatureType::P256 => KeyType::P256, + SignatureType::WebAuthn => KeyType::WebAuthn, + _ => KeyType::Secp256k1, + }, + key_address: Some(key_address), + key: Some(format!("0x{}", hex::encode(signer.to_bytes()))), + key_authorization: Some(key_authorization), + expiry: signed.authorization.expiry.map(|n| n.get()), + limits: signed + .authorization + .limits + .unwrap_or_default() + .into_iter() + .map(|l| StoredTokenLimit { currency: l.token, limit: l.limit.to_string() }) + .collect(), + }; + upsert_key_entry(entry)?; + return Ok(AccessKeyOutcome { + wallet_address: account_address, + key_address, + chain_id, + }); + } + } + } +} + +fn is_transient_error(err: &reqwest::Error) -> bool { + err.is_timeout() || err.is_connect() || err.is_request() +} + +fn is_transient_status(status: reqwest::StatusCode) -> bool { + status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS +} + +/// POST `/code` with exponential backoff on transient errors, bounded by `timeout`. +async fn create_code_with_retry( + client: &reqwest::Client, + service: &str, + req: &CreateCodeRequest, + timeout: Duration, +) -> Result { + let started = Instant::now(); + let mut backoff = Duration::from_millis(500); + loop { + let send_res = client.post(format!("{service}/code")).json(req).send().await; + + match send_res { + Ok(resp) => { + let status = resp.status(); + if status.is_success() { + let CreateCodeResponse { code } = resp.json().await?; + return Ok(code); + } + if is_transient_status(status) && started.elapsed() < timeout { + tracing::debug!(%status, "transient HTTP status creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code create failed ({status}): {body}"); + } + Err(e) if is_transient_error(&e) && started.elapsed() < timeout => { + tracing::debug!(error = %e, "transient error creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + } + Err(e) => return Err(e.into()), + } + } +} + +fn random_code_verifier() -> String { + let bytes = B256::random(); + URL_SAFE_NO_PAD.encode(bytes.as_slice()) +} + +fn sha256_b64url(input: &str) -> String { + let digest = Sha256::digest(input.as_bytes()); + URL_SAFE_NO_PAD.encode(digest) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CreateCodeRequest { + /// `0x`-hex per the SDK schema (server accepts hex string or bigint, not a plain JSON number). + #[serde(serialize_with = "serialize_u64_hex")] + chain_id: u64, + code_challenge: String, + key_type: &'static str, + pub_key: String, +} + +fn serialize_u64_hex(v: &u64, s: S) -> std::result::Result { + s.serialize_str(&format!("0x{v:x}")) +} + +#[derive(Deserialize)] +struct CreateCodeResponse { + code: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PollRequest { + code_verifier: String, +} + +/// Matches `tempoxyz/wallet` poll response shape. +#[derive(Deserialize)] +#[serde(tag = "status", rename_all = "lowercase")] +enum PollResponse { + Pending, + Expired, + Authorized { + account_address: Address, + #[serde(default)] + key_authorization: Option, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tempo::{TEMPO_HOME_ENV, read_tempo_keys_file, test_env_mutex}; + use axum::{Json, Router, extract::State, routing::post}; + use std::sync::{Arc, Mutex}; + + #[test] + fn pkce_challenge_matches_sdk_format() { + // Vector from RFC 7636 §4.2. + let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + let challenge = sha256_b64url(verifier); + assert_eq!(challenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); + } + + /// Recover the EOA from a SEC1-encoded public key (compressed or + /// uncompressed). + fn address_from_sec1_hex(s: &str) -> Address { + let stripped = s.strip_prefix("0x").unwrap_or(s); + let bytes = hex::decode(stripped).expect("valid hex"); + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes).expect("valid SEC1 pubkey"); + Address::from_public_key(&vk) + } + + #[derive(Clone)] + struct MockState { + wallet: Arc>>, + /// Derived from the `pubKey` posted to `/code` so `/poll` can echo + /// back a matching `keyId`, like a real wallet would. + key_id: Arc>>, + /// Chain ID the mock `/poll` returns in `keyAuthorization`. + poll_chain_id: u64, + } + + async fn create_code_handler( + State(state): State, + Json(body): Json, + ) -> Json { + // Sanity: required fields present and chainId is a 0x-hex string, + // matching the SDK wire format the live server enforces. + let pub_key = body + .get("pubKey") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("pubKey missing: {body}")); + assert!(body.get("codeChallenge").is_some(), "codeChallenge missing: {body}"); + let chain_id = body.get("chainId").unwrap_or_else(|| panic!("chainId missing: {body}")); + let chain_str = chain_id + .as_str() + .unwrap_or_else(|| panic!("chainId must be string, got {chain_id}: {body}")); + assert!(chain_str.starts_with("0x"), "chainId must be 0x-hex, got {chain_str}"); + let wallet: Address = "0x0000000000000000000000000000000000000042".parse().unwrap(); + *state.wallet.lock().unwrap() = Some(wallet); + *state.key_id.lock().unwrap() = Some(address_from_sec1_hex(pub_key)); + Json(serde_json::json!({ "code": "ABCDEFGH" })) + } + + /// Build the RLP-hex `SignedKeyAuthorization` blob the live server returns + /// in the `key_authorization` field. + fn signed_key_auth_hex(chain_id: u64, key_id: Address, expiry: u64) -> String { + use alloy_rlp::Encodable; + use tempo_primitives::transaction::{KeyAuthorization, PrimitiveSignature}; + let auth = KeyAuthorization::unrestricted(chain_id, SignatureType::Secp256k1, key_id) + .with_expiry(expiry); + let sig: PrimitiveSignature = serde_json::from_value(serde_json::json!({ + "type": "secp256k1", "r": "0x0", "s": "0x0", "yParity": 0 + })) + .unwrap(); + let signed = auth.into_signed(sig); + let mut buf = Vec::new(); + signed.encode(&mut buf); + format!("0x{}", hex::encode(buf)) + } + + async fn poll_handler(State(state): State) -> Json { + let wallet = state.wallet.lock().unwrap().expect("create_code must be called first"); + let key_id = state.key_id.lock().unwrap().expect("create_code must be called first"); + Json(serde_json::json!({ + "status": "authorized", + "account_address": wallet, + "key_authorization": signed_key_auth_hex(state.poll_chain_id, key_id, 9_999_999_999), + })) + } + + /// Spawn a mock wallet.tempo server whose `/poll` echoes `poll_chain_id`. + async fn spawn_mock_wallet(poll_chain_id: u64) -> (String, tokio::task::JoinHandle<()>) { + let app = Router::new() + .route("/code", post(create_code_handler)) + .route("/poll/{code}", post(poll_handler)) + .with_state(MockState { + wallet: Arc::default(), + key_id: Arc::default(), + poll_chain_id, + }); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + (format!("http://{addr}"), handle) + } + + fn test_cfg(service_url: String) -> EnsureAccessKeyConfig { + EnsureAccessKeyConfig { + chain_id: 4217, + service_url, + poll_interval: Duration::from_millis(10), + timeout: Duration::from_secs(2), + no_browser: true, + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_happy_path_writes_keys_toml() { + // SAFETY: serialized with other tests that mutate TEMPO_HOME. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(4217).await; + let outcome = ensure_access_key(test_cfg(service_url)).await.unwrap(); + + let expected_wallet: Address = + "0x0000000000000000000000000000000000000042".parse().unwrap(); + assert_eq!(outcome.chain_id, 4217); + assert_eq!(outcome.wallet_address, expected_wallet); + + let file = read_tempo_keys_file().expect("keys.toml written"); + assert_eq!(file.keys.len(), 1); + let entry = &file.keys[0]; + assert_eq!(entry.wallet_address, outcome.wallet_address); + assert_eq!(entry.key_address, Some(outcome.key_address)); + assert_eq!(entry.chain_id, 4217); + assert_eq!(entry.expiry, Some(9_999_999_999)); + let decoded: tempo_primitives::transaction::SignedKeyAuthorization = + crate::tempo::decode_key_authorization(entry.key_authorization.as_deref().unwrap()) + .expect("RLP roundtrip"); + assert_eq!(decoded.authorization.chain_id, 4217); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_rejects_wrong_chain_id() { + // Wallet returns chain 99999 but client requested 4217 → must reject + // and persist nothing, else discovery would later fail to find a key + // for the requested chain. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(99999).await; + let err = ensure_access_key(test_cfg(service_url)).await.unwrap_err(); + assert!( + err.to_string().contains("wallet authorized chain 99999 but 4217 was requested"), + "expected chain mismatch error, got: {err}" + ); + assert!(read_tempo_keys_file().is_none_or(|f| f.keys.is_empty())); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } +} diff --git a/crates/common/src/tempo/mod.rs b/crates/common/src/tempo/mod.rs index ec51dc607b5ab..ef8d0212bd453 100644 --- a/crates/common/src/tempo/mod.rs +++ b/crates/common/src/tempo/mod.rs @@ -1,8 +1,24 @@ //! Tempo network utilities. +pub mod auth; + +use crate::FoundryTransactionBuilder; +use alloy_network::Network; +use alloy_primitives::{Address, B256, Signature}; +use alloy_signer::Signer; +use eyre::{Context, Result}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use std::sync::Arc; + mod keystore; + +pub(crate) use auth::is_known_tempo_endpoint; +pub use auth::{AccessKeyOutcome, EnsureAccessKeyConfig, ensure_access_key}; pub use keystore::*; +#[cfg(test)] +pub(crate) use keystore::test_env_mutex; + #[cfg(test)] mod tests; @@ -16,3 +32,173 @@ mod tests; /// /// See pub const TEMPO_BROWSER_GAS_BUFFER: u64 = 7_000; + +/// Gas sponsor configuration for Tempo fee-payer signatures. +#[derive(Clone, Debug)] +pub struct TempoSponsor { + sponsor: Address, + signer: Option>, + signature: Option, +} + +impl TempoSponsor { + pub const fn new( + sponsor: Address, + signer: Option>, + signature: Option, + ) -> Self { + Self { sponsor, signer, signature } + } + + pub const fn sponsor(&self) -> Address { + self.sponsor + } + + pub async fn attach_and_print( + &self, + tx: &mut N::TransactionRequest, + sender: Address, + ) -> Result + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if self.sponsor == sender { + eyre::bail!( + "invalid Tempo sponsorship: sponsor {} must not equal transaction sender", + self.sponsor + ); + } + + let digest = tx.compute_sponsor_hash(sender).ok_or_else(|| { + eyre::eyre!( + "failed to compute Tempo sponsor digest; make sure this is a complete Tempo AA transaction" + ) + })?; + + let preview = TempoSponsorPreview { + sponsor: self.sponsor, + fee_token: tx.fee_token(), + valid_before: tx.valid_before().map(|v| v.get()), + valid_after: tx.valid_after().map(|v| v.get()), + digest, + }; + preview.print()?; + + let signature = if let Some(signature) = self.signature { + signature + } else if let Some(signer) = &self.signer { + signer.sign_hash(&digest).await.context("failed to sign Tempo sponsor digest")? + } else { + eyre::bail!("missing Tempo sponsor signature or signer") + }; + + let recovered = signature + .recover_address_from_prehash(&digest) + .context("failed to recover Tempo sponsor signature")?; + if recovered != self.sponsor { + eyre::bail!("Tempo sponsor signature recovered {recovered}, expected {}", self.sponsor); + } + if recovered == sender { + eyre::bail!( + "invalid Tempo sponsorship: recovered fee payer {recovered} must not equal transaction sender" + ); + } + + tx.set_fee_payer_signature(signature); + Ok(preview) + } +} + +/// User-visible sponsor digest metadata for a single outgoing Tempo transaction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TempoSponsorPreview { + pub sponsor: Address, + pub fee_token: Option
, + pub valid_before: Option, + pub valid_after: Option, + pub digest: B256, +} + +impl TempoSponsorPreview { + pub fn print(&self) -> Result<()> { + crate::sh_eprintln!("Tempo sponsor: {}", self.sponsor)?; + crate::sh_eprintln!( + "Tempo fee token: {}", + self.fee_token.map_or_else(|| "network default".to_string(), |addr| addr.to_string()) + )?; + crate::sh_eprintln!( + "Tempo validity: after {}, before {}", + self.valid_after.map_or_else(|| "none".to_string(), |v| v.to_string()), + self.valid_before.map_or_else(|| "none".to_string(), |v| v.to_string()) + )?; + crate::sh_eprintln!("Tempo sponsor digest: {:?}", self.digest)?; + Ok(()) + } +} + +/// Resolves a `--tempo.sponsor-signer` URI into a Foundry wallet signer. +pub async fn resolve_tempo_sponsor_signer(spec: &str) -> Result { + let spec = spec.trim(); + let (scheme, value) = spec + .split_once("://") + .map(|(scheme, value)| (scheme.to_ascii_lowercase(), value)) + .unwrap_or_else(|| (spec.to_ascii_lowercase(), "")); + + match scheme.as_str() { + "env" => { + if value.is_empty() { + eyre::bail!("env:// sponsor signer requires an environment variable name"); + } + let private_key = std::env::var(value) + .wrap_err_with(|| format!("{value} environment variable is required"))?; + foundry_wallets::utils::create_private_key_signer(&private_key) + } + "private-key" => { + if value.is_empty() { + eyre::bail!("private-key:// sponsor signer requires a private key"); + } + foundry_wallets::utils::create_private_key_signer(value) + } + "keystore" => { + if value.is_empty() { + eyre::bail!("keystore:// sponsor signer requires a keystore path"); + } + WalletOpts { keystore_path: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "account" => { + if value.is_empty() { + eyre::bail!("account:// sponsor signer requires an account name"); + } + WalletOpts { keystore_account_name: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "ledger" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { ledger: true, raw, ..Default::default() }.signer().await + } + "trezor" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { trezor: true, raw, ..Default::default() }.signer().await + } + "aws" => WalletOpts { aws: true, ..Default::default() }.signer().await, + "gcp" => WalletOpts { gcp: true, ..Default::default() }.signer().await, + "turnkey" => WalletOpts { turnkey: true, ..Default::default() }.signer().await, + "browser" => { + eyre::bail!( + "browser:// sponsor signing is not supported by the current browser wallet API; use --tempo.sponsor-sig or another sponsor signer" + ) + } + _ => eyre::bail!( + "unsupported Tempo sponsor signer `{spec}`; expected env://VAR, keystore://PATH, account://NAME, ledger://, trezor://, aws://, gcp://, turnkey://, or private-key://KEY" + ), + } +} diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index de03cf3adc73e..aa4c971680d00 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -9,7 +9,9 @@ use alloy_primitives::{Address, B256, Signature, TxKind, U256}; use alloy_provider::Provider; use alloy_signer::Signer; use eyre::Result; +#[cfg(feature = "optimism")] use op_alloy_network::Optimism; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionRequest; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; use tempo_primitives::{ @@ -244,6 +246,24 @@ pub trait FoundryTransactionBuilder: NetworkTransactionBuilder { /// on-chain as part of this transaction. fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} + /// Embeds key authorization before gas estimation/signing if the access key is not yet + /// provisioned on-chain. + /// + /// This mirrors the mutation performed by [`Self::sign_with_access_key`], but makes the final + /// transaction body available before fee-payer sponsor digests are computed. + fn prepare_access_key_authorization<'a>( + &'a mut self, + _provider: &'a impl Provider, + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + async { Ok(()) } + } + /// Converts a CREATE transaction into an AA-compatible call entry. /// /// Tempo AA transactions use a `calls` list instead of `to`+`input`. Must be @@ -355,6 +375,7 @@ impl FoundryTransactionBuilder for ::Transact } } +#[cfg(feature = "optimism")] impl FoundryTransactionBuilder for OpTransactionRequest { fn reset_gas_limit(&mut self) { self.as_mut().gas = None; @@ -439,6 +460,35 @@ impl FoundryTransactionBuilder for ::Tran self.key_authorization = Some(key_authorization); } + fn prepare_access_key_authorization<'a>( + &'a mut self, + provider: &'a impl Provider, + wallet_address: Address, + key_address: Address, + key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + let auth = key_authorization.cloned(); + + async move { + if let Some(auth) = auth { + let is_provisioned = provider + .get_keychain_key(wallet_address, key_address) + .await + .map(|info| info.keyId != Address::ZERO) + .unwrap_or(false); + + if !is_provisioned { + self.set_key_authorization(auth); + } + } + + Ok(()) + } + } + fn convert_create_to_call(&mut self) { if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) { let input = self.inner.input.input().cloned().unwrap_or_default(); @@ -473,7 +523,12 @@ impl FoundryTransactionBuilder for ::Tran let is_provisioned = provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false); - if !is_provisioned { + if !is_provisioned && self.key_authorization.is_none() { + if self.fee_payer_signature.is_some() { + eyre::bail!( + "cannot add Tempo key authorization after fee payer signature was attached" + ); + } self.set_key_authorization(auth); } } diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs index 9ca6cb02b10ee..c2e34419248c4 100644 --- a/crates/common/src/transactions/receipt.rs +++ b/crates/common/src/transactions/receipt.rs @@ -7,6 +7,7 @@ use alloy_provider::{ use alloy_rpc_types::{BlockId, TransactionReceipt}; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +#[cfg(feature = "optimism")] use op_alloy_rpc_types::OpTransactionReceipt; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionReceipt; @@ -23,6 +24,7 @@ impl FoundryReceiptResponse for TransactionReceipt { } } +#[cfg(feature = "optimism")] impl FoundryReceiptResponse for OpTransactionReceipt { fn set_contract_address(&mut self, contract_address: Address) { self.inner.contract_address = Some(contract_address); diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index bab59137b0130..8f63718e086cd 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -10,6 +10,10 @@ use std::path::PathBuf; pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, + /// Optional 1-based fuzz run to execute. + pub run: Option, + /// Optional fuzz worker ID to pair with `run`. + pub worker: Option, /// Fails the fuzzed test if a revert occurs. pub fail_on_revert: bool, /// The maximum number of test case rejections allowed, @@ -37,6 +41,8 @@ impl Default for FuzzConfig { fn default() -> Self { Self { runs: 256, + run: None, + worker: None, fail_on_revert: true, max_test_rejects: 65536, seed: None, diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 270df14a6c291..000cefc26737a 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use crate::Config; use alloy_primitives::map::HashMap; use figment::{ @@ -5,6 +7,7 @@ use figment::{ value::{Dict, Map, Value}, }; use foundry_compilers::ProjectCompileOutput; +use foundry_evm_networks::NetworkVariant; use itertools::Itertools; mod natspec; @@ -123,6 +126,42 @@ impl InlineConfig { self.get_function(contract, function).is_some_and(|map| !map.is_empty()) } + /// Returns the configured [`NetworkVariant`] for a given test, checking function-level first + /// then contract-level. Returns `None` if no network annotation is present. + pub fn network_for( + &self, + profile: &Profile, + contract: &str, + function: &str, + ) -> Option { + let data = self.provide(contract, function).data().ok()?; + let dict = data.get(profile).or_else(|| data.get(&Profile::Default))?; + if let Some(Value::Dict(_, networks)) = dict.get("networks") + && let Some(Value::String(_, s)) = networks.get("network") + { + return s.parse().ok(); + } + None + } + + /// Returns all distinct [`NetworkVariant`]s referenced in any inline config annotation. + /// + /// This is used to determine whether a multi-network test pass is needed. + pub fn referenced_override_networks(&self, profile: &Profile) -> Vec { + let mut seen = BTreeSet::new(); + for (contract, function) in self.fn_level.keys() { + if let Some(v) = self.network_for(profile, contract, function) { + seen.insert(v); + } + } + for contract in self.contract_level.keys() { + if let Some(v) = self.network_for(profile, contract, "") { + seen.insert(v); + } + } + seen.into_iter().collect() + } + fn get_contract(&self, contract: &str) -> Option<&DataMap> { self.contract_level.get(contract) } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 1f5c35775f1dc..71eb2a96d7152 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -2883,19 +2883,35 @@ impl BasicConfig { /// /// This serializes to a table with the name of the profile pub fn to_string_pretty(&self) -> Result { - let mut value = toml::Value::try_from(self)?; + let mut profile_body = toml::Value::try_from(self)?; if let Some(ref network) = self.network - && let toml::Value::Table(ref mut table) = value + && let toml::Value::Table(ref mut table) = profile_body { - table.insert(network.clone(), toml::Value::Boolean(true)); + table.insert("network".to_string(), toml::Value::String(network.clone())); + } + + let mut profile_section = toml::value::Table::new(); + profile_section.insert(self.profile.to_string(), profile_body); + + let mut document = toml::value::Table::new(); + document.insert("profile".to_string(), toml::Value::Table(profile_section)); + + if self.network.as_deref() == Some("tempo") { + let mut endpoints = toml::value::Table::new(); + endpoints.insert( + "tempo".to_string(), + toml::Value::String("https://rpc.tempo.xyz/".to_string()), + ); + endpoints.insert( + "moderato".to_string(), + toml::Value::String("https://rpc.moderato.tempo.xyz/".to_string()), + ); + document.insert("rpc_endpoints".to_string(), toml::Value::Table(endpoints)); } - let s = toml::to_string_pretty(&value)?; + + let body = toml::to_string_pretty(&toml::Value::Table(document))?; Ok(format!( - "\ -[profile.{}] -{s} -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", - self.profile + "{body}\n# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n" )) } } @@ -6700,6 +6716,55 @@ mod tests { }); } + #[test] + fn no_unknown_key_warning_for_network_field() { + // Regression test: `network` is a flattened `Option` field of `NetworkConfigs`. It must + // not trigger an unknown-key warning, regardless of whether it is set. + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + network = "tempo" + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + !cfg.warnings.iter().any( + |w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "network") + ), + "did not expect UnknownKey warning for `network`, got: {:?}", + cfg.warnings + ); + Ok(()) + }); + } + + #[test] + fn no_unknown_key_warning_for_legacy_tempo_alias() { + // Regression test: the legacy `tempo = true` alias must keep working without warnings. + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + tempo = true + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + !cfg.warnings + .iter() + .any(|w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "tempo")), + "did not expect UnknownKey warning for `tempo`, got: {:?}", + cfg.warnings + ); + Ok(()) + }); + } + #[test] fn fails_on_ambiguous_version_in_compilation_restrictions() { figment::Jail::expect_with(|jail| { diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs index 930066b29cf73..ff1d0b35def47 100644 --- a/crates/config/src/providers/warnings.rs +++ b/crates/config/src/providers/warnings.rs @@ -38,7 +38,10 @@ const DOC_KEYS: &[&str] = &["out", "title", "book", "homepage", "repository", "p const RESERVED_KEYS: &[&str] = &["extends"]; /// Keys kept for backward compatibility that should not trigger unknown key warnings. -const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version"]; +/// +/// `tempo` and `optimism` are legacy aliases for `network = "tempo"` / `network = "optimism"` — +/// still accepted on input but no longer serialized in the default config. +const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version", "tempo", "optimism"]; /// Generate warnings for unknown sections and deprecated keys pub struct WarningsProvider

{ diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 3c8cad85bae10..cc3dabd32d4bf 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -29,3 +29,11 @@ ratatui = { version = "0.30", default-features = false, features = [ revm.workspace = true tracing.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 77020a88fe3cd..814beab402729 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -14,7 +14,6 @@ repository.workspace = true workspace = true [dependencies] -forge-fmt.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true @@ -29,8 +28,11 @@ mdbook-driver = { version = "0.5", default-features = false, features = ["search rayon.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true thiserror.workspace = true toml.workspace = true tracing.workspace = true regex.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 7ecada9d33e72..ae456e5de7e2c 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -1,6 +1,6 @@ use crate::{ AsDoc, BufWriter, Document, ParseItem, ParseSource, Parser, Preprocessor, - document::DocumentContent, helpers::merge_toml_table, solang_ext::Visitable, + document::DocumentContent, helpers::merge_toml_table, }; use alloy_primitives::map::HashMap; use eyre::{Context, Result}; @@ -129,41 +129,27 @@ impl DocBuilder { let gcx = compiler.gcx(); let documents = combined_sources .par_iter() - .enumerate() - .map(|(i, (path, from_library))| { + .map(|(path, from_library)| { let path = *path; let from_library = *from_library; let mut files = vec![]; // Read and parse source file - if let Some((_, ast)) = gcx.get_ast_source(path) - && let Some(source) = - forge_fmt::format_ast(gcx, ast, self.fmt.clone().into()) + if let Some((_, ast_source)) = gcx.get_ast_source(path) + && let Some(source_unit) = ast_source.ast.as_ref() { - let (mut source_unit, comments) = match solang_parser::parse(&source, i) { - Ok(res) => res, - Err(err) => { - if from_library { - // Ignore failures for library files - return Ok(files); - } - return Err(eyre::eyre!( - "Failed to parse Solidity code for {}\nDebug info: {:?}", - path.display(), - err - )); - } - }; + // Solar uses a global SourceMap: span BytePos values are global + // offsets, not per-file offsets. Subtract file.start_pos so that + // span-based indexing into the per-file source string is correct. + let source = ast_source.file.src.to_string(); + let file_start = ast_source.file.start_pos.to_usize(); - // Visit the parse tree - let mut doc = Parser::new(comments, source, self.fmt.tab_width); - source_unit - .visit(&mut doc) - .map_err(|err| eyre::eyre!("Failed to parse source: {err}"))?; + // Walk the solar AST directly + let doc = Parser::new(source, file_start, self.fmt.tab_width); + let all_items = doc.parse(source_unit); // Split the parsed items on top-level constants and rest. - let (items, consts): (Vec, Vec) = doc - .items() + let (items, consts): (Vec, Vec) = all_items .into_iter() .partition(|item| !matches!(item.source, ParseSource::Variable(_))); diff --git a/crates/doc/src/helpers.rs b/crates/doc/src/helpers.rs index 77f10329a88b8..7b36e2e257b71 100644 --- a/crates/doc/src/helpers.rs +++ b/crates/doc/src/helpers.rs @@ -1,25 +1,5 @@ -use itertools::Itertools; -use solang_parser::pt::FunctionDefinition; use toml::{Value, value::Table}; -/// Generates a function signature with parameter types (e.g., "functionName(type1,type2)"). -/// Returns the function name without parameters if the function has no parameters. -pub fn function_signature(func: &FunctionDefinition) -> String { - let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.clone()); - if func.params.is_empty() { - return func_name; - } - - format!( - "{}({})", - func_name, - func.params - .iter() - .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default()) - .join(",") - ) -} - /// Merge original toml table with the override. pub(crate) fn merge_toml_table(table: &mut Table, override_table: Table) { for (key, override_value) in override_table { @@ -46,74 +26,3 @@ pub(crate) fn merge_toml_table(table: &mut Table, override_table: Table) { }; } } - -#[cfg(test)] -mod tests { - use super::*; - use solang_parser::{ - parse, - pt::{ContractPart, SourceUnit, SourceUnitPart}, - }; - - #[test] - fn test_function_signature_no_params() { - let (source_unit, _) = parse( - r#" - contract Test { - function foo() public {} - } - "#, - 0, - ) - .unwrap(); - - let func = extract_function(&source_unit); - assert_eq!(function_signature(func), "foo"); - } - - #[test] - fn test_function_signature_with_params() { - let (source_unit, _) = parse( - r#" - contract Test { - function transfer(address to, uint256 amount) public {} - } - "#, - 0, - ) - .unwrap(); - - let func = extract_function(&source_unit); - assert_eq!(function_signature(func), "transfer(address,uint256)"); - } - - #[test] - fn test_function_signature_constructor() { - let (source_unit, _) = parse( - r#" - contract Test { - constructor(address owner) {} - } - "#, - 0, - ) - .unwrap(); - - let func = extract_function(&source_unit); - assert_eq!(function_signature(func), "constructor(address)"); - } - - /// Helper to extract the first function from a parsed source unit - fn extract_function(source_unit: &SourceUnit) -> &FunctionDefinition { - for part in &source_unit.0 { - if let SourceUnitPart::ContractDefinition(contract) = part { - for part in &contract.parts { - if let ContractPart::FunctionDefinition(func) = part { - return func; - } - } - } - } - panic!("No function found in source unit"); - } -} diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 847d58dfb1d8b..e50598cff83ac 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -22,6 +22,10 @@ mod helpers; mod parser; pub use parser::{ Comment, CommentTag, Comments, CommentsRef, ParseItem, ParseSource, Parser, error, + source::{ + BaseInfo, ContractKind, ContractSource, EnumSource, ErrorSource, EventSource, + FunctionSource, ParamInfo, StructSource, TypeSource, VariableAttr, VariableSource, + }, }; mod preprocessor; @@ -31,6 +35,3 @@ mod writer; pub use writer::{AsDoc, AsDocResult, BufWriter, Markdown}; pub use mdbook_driver; - -// old formatter dependencies -pub mod solang_ext; diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 3e915b1b7f479..e70f47e174a81 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -1,6 +1,5 @@ use alloy_primitives::map::HashMap; use derive_more::{Deref, DerefMut, derive::Display}; -use solang_parser::doccomment::DocCommentTag; /// The natspec comment tag explaining the purpose of the comment. /// See: . @@ -70,10 +69,10 @@ impl Comment { Self { tag, value } } - /// Create new instance of [Comment] from [DocCommentTag] + /// Create new instance of [Comment] from a tag string and value, /// if it has a valid natspec tag. - pub fn from_doc_comment(value: DocCommentTag) -> Option { - CommentTag::from_str(&value.tag).map(|tag| Self { tag, value: value.value }) + pub fn from_tag_and_value(tag: &str, value: String) -> Option { + CommentTag::from_str(tag).map(|tag| Self { tag, value }) } /// Split the comment at first word. @@ -145,9 +144,70 @@ impl Comments { } } -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value.into_iter().filter_map(Comment::from_doc_comment).collect()) +impl Comments { + /// Parse natspec comments from raw doc comment lines. + /// + /// Each line should be the raw text content of a `///` or `/** */` doc comment + /// with the comment delimiters already stripped (as provided by solar's `DocComment::symbol`). + /// + /// Natspec tags start with `@` (e.g. `@notice`, `@dev`, `@param`). + /// Lines without a tag at the start are treated as continuations of the previous tag, + /// or as `@notice` if no previous tag exists. + pub fn from_doc_lines(lines: impl IntoIterator>) -> Self { + let mut comments = Vec::new(); + let mut current_tag: Option = None; + let mut current_value = String::new(); + + let flush = |tag: &Option, value: &str, out: &mut Vec| { + let value = value.trim(); + if value.is_empty() && tag.is_none() { + return; + } + let tag_str = tag.as_deref().unwrap_or("notice"); + // Filter out `@solidity` tags and empty tags + if tag_str.trim() == "solidity" || tag_str.trim().is_empty() { + return; + } + if let Some(c) = Comment::from_tag_and_value(tag_str, value.to_string()) { + out.push(c); + } + }; + + for raw_line in lines { + let raw = raw_line.as_ref(); + // For block comments, process each line individually + for line in raw.lines() { + let trimmed = line.trim().trim_start_matches('*').trim(); + + if let Some(rest) = trimmed.strip_prefix('@') { + // Flush previous + flush(¤t_tag, ¤t_value, &mut comments); + // Parse new tag + let (tag, value) = rest.split_once(char::is_whitespace).unwrap_or((rest, "")); + current_tag = Some(tag.to_string()); + current_value = value.trim().to_string(); + } else if !trimmed.is_empty() { + // Continuation of current tag + if current_value.is_empty() { + current_value = trimmed.to_string(); + } else { + current_value.push('\n'); + current_value.push_str(trimmed); + } + } + } + } + + // Flush last + flush(¤t_tag, ¤t_value, &mut comments); + + Self(comments) + } +} + +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) } } diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index b50c0ccae9f4e..cca36d47301c8 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -1,12 +1,12 @@ -use crate::{Comments, helpers::function_signature, solang_ext::SafeUnwrap}; -use solang_parser::pt::{ - ContractDefinition, ContractTy, EnumDefinition, ErrorDefinition, EventDefinition, - FunctionDefinition, StructDefinition, TypeDefinition, VariableDefinition, +use crate::Comments; + +pub use super::source::{ + ContractSource, EnumSource, ErrorSource, EventSource, FunctionSource, ParseSource, + StructSource, VariableSource, }; -use std::ops::Range; /// The parsed item. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct ParseItem { /// The parse tree source. pub source: ParseSource, @@ -18,12 +18,12 @@ pub struct ParseItem { pub code: String, } -/// Defines a method that filters [ParseItem]'s children and returns the source pt token of the +/// Defines a method that filters [ParseItem]'s children and returns the source data of the /// children matching the target variant as well as its comments. /// Returns [Option::None] if no children matching the variant are found. macro_rules! filter_children_fn { ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => { - /// Filter children items for [ParseSource::$variant] variants. + /// Filter children items for matching variants. $vis fn $name(&self) -> Option> { let items = self.children.iter().filter_map(|item| match item.source { ParseSource::$variant(ref inner) => Some((inner, &item.comments, &item.code)), @@ -39,12 +39,11 @@ macro_rules! filter_children_fn { }; } -/// Defines a method that returns [ParseSource] inner element if it matches -/// the variant +/// Defines a method that returns the inner element if it matches the variant. macro_rules! as_inner_source { ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => { - /// Return inner element if it matches $variant. - /// If the element doesn't match, returns [None] + /// Return inner element if it matches the variant. + /// If the element doesn't match, returns [None]. $vis fn $name(&self) -> Option<&$ret> { match self.source { ParseSource::$variant(ref inner) => Some(inner), @@ -77,38 +76,16 @@ impl ParseItem { self } - /// Set the source code of this [ParseItem]. - /// - /// The parameter should be the full source file where this parse item originated from. - pub fn with_code(mut self, source: &str, tab_width: usize) -> Self { - let mut code = source[self.source.range()].to_string(); - - // Special function case, add `;` at the end of definition. - if let ParseSource::Function(_) | ParseSource::Error(_) | ParseSource::Event(_) = - self.source - { - code.push(';'); - } - - // Remove extra indent from source lines. - let prefix = &" ".repeat(tab_width); - self.code = code - .lines() - .map(|line| line.strip_prefix(prefix).unwrap_or(line)) - .collect::>() - .join("\n"); + /// Set the code string for this [ParseItem]. + pub fn with_code(mut self, code: String) -> Self { + self.code = code; self } /// Format the item's filename. pub fn filename(&self) -> String { - let prefix = match self.source { - ParseSource::Contract(ref c) => match c.ty { - ContractTy::Contract(_) => "contract", - ContractTy::Abstract(_) => "abstract", - ContractTy::Interface(_) => "interface", - ContractTy::Library(_) => "library", - }, + let prefix = match &self.source { + ParseSource::Contract(c) => c.kind.as_str(), ParseSource::Function(_) => "function", ParseSource::Variable(_) => "variable", ParseSource::Event(_) => "event", @@ -121,77 +98,14 @@ impl ParseItem { format!("{prefix}.{ident}.md") } - filter_children_fn!(pub fn variables(&self, Variable) -> VariableDefinition); - filter_children_fn!(pub fn functions(&self, Function) -> FunctionDefinition); - filter_children_fn!(pub fn events(&self, Event) -> EventDefinition); - filter_children_fn!(pub fn errors(&self, Error) -> ErrorDefinition); - filter_children_fn!(pub fn structs(&self, Struct) -> StructDefinition); - filter_children_fn!(pub fn enums(&self, Enum) -> EnumDefinition); + filter_children_fn!(pub fn variables(&self, Variable) -> VariableSource); + filter_children_fn!(pub fn functions(&self, Function) -> FunctionSource); + filter_children_fn!(pub fn events(&self, Event) -> EventSource); + filter_children_fn!(pub fn errors(&self, Error) -> ErrorSource); + filter_children_fn!(pub fn structs(&self, Struct) -> StructSource); + filter_children_fn!(pub fn enums(&self, Enum) -> EnumSource); - as_inner_source!(pub fn as_contract(&self, Contract) -> ContractDefinition); - as_inner_source!(pub fn as_variable(&self, Variable) -> VariableDefinition); - as_inner_source!(pub fn as_function(&self, Function) -> FunctionDefinition); -} - -/// A wrapper type around pt token. -#[derive(Clone, Debug, PartialEq, Eq)] -#[allow(clippy::large_enum_variant)] -pub enum ParseSource { - /// Source contract definition. - Contract(Box), - /// Source function definition. - Function(FunctionDefinition), - /// Source variable definition. - Variable(VariableDefinition), - /// Source event definition. - Event(EventDefinition), - /// Source error definition. - Error(ErrorDefinition), - /// Source struct definition. - Struct(StructDefinition), - /// Source enum definition. - Enum(EnumDefinition), - /// Source type definition. - Type(TypeDefinition), -} - -impl ParseSource { - /// Get the identity of the source - pub fn ident(&self) -> String { - match self { - Self::Contract(contract) => contract.name.safe_unwrap().name.clone(), - Self::Variable(var) => var.name.safe_unwrap().name.clone(), - Self::Event(event) => event.name.safe_unwrap().name.clone(), - Self::Error(error) => error.name.safe_unwrap().name.clone(), - Self::Struct(structure) => structure.name.safe_unwrap().name.clone(), - Self::Enum(enumerable) => enumerable.name.safe_unwrap().name.clone(), - Self::Function(func) => { - func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.clone()) - } - Self::Type(ty) => ty.name.name.clone(), - } - } - - /// Get the signature of the source (for functions, includes parameter types) - pub fn signature(&self) -> String { - match self { - Self::Function(func) => function_signature(func), - _ => self.ident(), - } - } - - /// Get the range of this item in the source file. - pub fn range(&self) -> Range { - match self { - Self::Contract(contract) => contract.loc, - Self::Variable(var) => var.loc, - Self::Event(event) => event.loc, - Self::Error(error) => error.loc, - Self::Struct(structure) => structure.loc, - Self::Enum(enumerable) => enumerable.loc, - Self::Function(func) => func.loc_prototype, - Self::Type(ty) => ty.loc, - } - .range() - } + as_inner_source!(pub fn as_contract(&self, Contract) -> ContractSource); + as_inner_source!(pub fn as_variable(&self, Variable) -> VariableSource); + as_inner_source!(pub fn as_function(&self, Function) -> FunctionSource); } diff --git a/crates/doc/src/parser/mod.rs b/crates/doc/src/parser/mod.rs index 76da5d032382d..124d2aa3ed576 100644 --- a/crates/doc/src/parser/mod.rs +++ b/crates/doc/src/parser/mod.rs @@ -1,19 +1,12 @@ //! The parser module. -use crate::solang_ext::{Visitable, Visitor}; -use itertools::Itertools; -use solang_parser::{ - doccomment::{DocComment, parse_doccomments}, - pt::{ - Comment as SolangComment, EnumDefinition, ErrorDefinition, EventDefinition, - FunctionDefinition, Identifier, Loc, SourceUnit, SourceUnitPart, StructDefinition, - TypeDefinition, VariableDefinition, - }, -}; +use solar::parse::ast; /// Parser error. pub mod error; -use error::{ParserError, ParserResult}; + +/// Owned source types. +pub mod source; /// Parser item. mod item; @@ -23,206 +16,274 @@ pub use item::{ParseItem, ParseSource}; mod comment; pub use comment::{Comment, CommentTag, Comments, CommentsRef}; -/// The documentation parser. This type implements a [Visitor] trait. +use source::*; + +/// The documentation parser. /// -/// While walking the parse tree, [Parser] will collect relevant source items and corresponding -/// doc comments. The resulting [ParseItem]s can be accessed by calling [Parser::items]. -#[derive(Debug, Default)] +/// Walks the solar AST and extracts [`ParseItem`]s with owned source data and doc comments. +#[derive(Debug)] pub struct Parser { - /// Initial comments from solang parser. - comments: Vec, - /// Parser context. - context: ParserContext, /// Parsed results. items: Vec, - /// Source file. + /// The source code string of the file being parsed. source: String, + /// The global byte offset of this file's first byte in solar's source map. + /// + /// Solar uses a global `SourceMap` where each file's `BytePos` values start at + /// `file.start_pos` rather than 0. All span `lo`/`hi` values must be offset by this + /// amount before indexing into `self.source`. + file_start: usize, /// Tab width used to format code. tab_width: usize, } -/// [Parser] context. -#[derive(Debug, Default)] -struct ParserContext { - /// Current visited parent. - parent: Option, - /// Current start pointer for parsing doc comments. - doc_start_loc: usize, -} - impl Parser { /// Create a new instance of [Parser]. - pub fn new(comments: Vec, source: String, tab_width: usize) -> Self { - Self { comments, source, tab_width, ..Default::default() } + /// + /// `file_start` is `ast_source.file.start_pos.to_usize()` — the offset of the file's + /// first byte in solar's global source map. Pass `0` in tests where you parse directly. + pub const fn new(source: String, file_start: usize, tab_width: usize) -> Self { + Self { items: Vec::new(), source, file_start, tab_width } } - /// Return the parsed items. Consumes the parser. - pub fn items(self) -> Vec { + /// Parse a solar source unit and return the parsed items. + pub fn parse(mut self, source_unit: &ast::SourceUnit<'_>) -> Vec { + for item in source_unit.items.iter() { + if let Some(parsed) = self.parse_item(item) { + self.items.push(parsed); + } + } self.items } - /// Visit the children elements with parent context. - /// This function memoizes the previous parent, sets the context - /// to a new one and invokes a visit function. The context will be reset - /// to the previous parent at the end of the function. - fn with_parent( - &mut self, - mut parent: ParseItem, - mut visit: impl FnMut(&mut Self) -> ParserResult<()>, - ) -> ParserResult { - let curr = self.context.parent.take(); - self.context.parent = Some(parent); - visit(self)?; - parent = self.context.parent.take().unwrap(); - self.context.parent = curr; - Ok(parent) - } - - /// Adds a child element to the parent item if it exists. - /// Otherwise the element will be added to a top-level items collection. - /// Moves the doc comment pointer to the end location of the child element. - fn add_element_to_parent(&mut self, source: ParseSource, loc: Loc) -> ParserResult<()> { - let child = self.new_item(source, loc.start())?; - if let Some(parent) = self.context.parent.as_mut() { - parent.children.push(child); - } else { - self.items.push(child); + /// Parse a single solar AST item into a [ParseItem]. + fn parse_item(&self, item: &ast::Item<'_>) -> Option { + let docs = Self::parse_docs(&item.docs); + let span = item.span; + + match &item.kind { + ast::ItemKind::Contract(contract) => { + let source = self.parse_contract(contract); + let code = self.extract_code(span); + let mut parse_item = + ParseItem::new(ParseSource::Contract(source)).with_comments(docs); + + // Parse children + let mut children = Vec::new(); + for child in contract.body.iter() { + if let Some(parsed) = self.parse_item(child) { + children.push(parsed); + } + } + parse_item.children = children; + parse_item.code = code; + Some(parse_item) + } + ast::ItemKind::Function(func) => { + let source = self.parse_function(func); + let code = self.extract_prototype_code(func); + Some( + ParseItem::new(ParseSource::Function(source)) + .with_comments(docs) + .with_code(code), + ) + } + ast::ItemKind::Variable(var) => { + let source = self.parse_variable(var); + let code = self.extract_code(span); + Some( + ParseItem::new(ParseSource::Variable(source)) + .with_comments(docs) + .with_code(code), + ) + } + ast::ItemKind::Event(event) => { + let source = self.parse_event(event); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Event(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Error(err) => { + let source = self.parse_error(err); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Error(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Struct(strukt) => { + let source = self.parse_struct(strukt); + let code = self.extract_code(span); + Some( + ParseItem::new(ParseSource::Struct(source)).with_comments(docs).with_code(code), + ) + } + ast::ItemKind::Enum(enm) => { + let source = self.parse_enum(enm); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Enum(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Udvt(udvt) => { + let source = TypeSource { name: udvt.name.to_string() }; + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Type(source)).with_comments(docs).with_code(code)) + } + // Skip pragmas, imports, using directives + _ => None, } - self.context.doc_start_loc = loc.end(); - Ok(()) } - /// Create new [ParseItem] with comments and formatted code. - fn new_item(&mut self, source: ParseSource, loc_start: usize) -> ParserResult { - let docs = self.parse_docs(loc_start)?; - Ok(ParseItem::new(source).with_comments(docs).with_code(&self.source, self.tab_width)) + fn parse_contract(&self, contract: &ast::ItemContract<'_>) -> ContractSource { + let kind = match contract.kind { + ast::ContractKind::Contract => ContractKind::Contract, + ast::ContractKind::AbstractContract => ContractKind::Abstract, + ast::ContractKind::Interface => ContractKind::Interface, + ast::ContractKind::Library => ContractKind::Library, + }; + + let bases = contract + .bases + .iter() + .map(|base| { + let full_name = base.name.to_string(); + let ident = base.name.last().name.to_string(); + BaseInfo { name: full_name, ident } + }) + .collect(); + + ContractSource { name: contract.name.to_string(), kind, bases } } - /// Parse the doc comments from the current start location. - fn parse_docs(&mut self, end: usize) -> ParserResult { - self.parse_docs_range(self.context.doc_start_loc, end) + fn parse_function(&self, func: &ast::ItemFunction<'_>) -> FunctionSource { + let name = func.header.name.map(|n| n.to_string()); + let kind = func.kind.to_string(); + let params = self.parse_var_defs(&func.header.parameters); + let returns = + func.header.returns.as_deref().map(|r| self.parse_var_defs(r)).unwrap_or_default(); + FunctionSource { name, kind, params, returns } } - /// Parse doc comments from the within specified range. - fn parse_docs_range(&mut self, start: usize, end: usize) -> ParserResult { - let mut res = vec![]; - for comment in parse_doccomments(&self.comments, start, end) { - match comment { - DocComment::Line { comment } => res.push(comment), - DocComment::Block { comments } => res.extend(comments), + fn parse_variable(&self, var: &ast::VariableDefinition<'_>) -> VariableSource { + let name = var.name.map(|n| n.to_string()).unwrap_or_default(); + + let mut attrs = Vec::new(); + if let Some(m) = var.mutability { + match m { + ast::VarMut::Constant => attrs.push(VariableAttr::Constant), + ast::VarMut::Immutable => attrs.push(VariableAttr::Immutable), } } - // Filter out `@solidity` and empty tags - // See https://docs.soliditylang.org/en/v0.8.17/assembly.html#memory-safety - let res = res - .into_iter() - .filter(|c| c.tag.trim() != "solidity" && !c.tag.trim().is_empty()) - .collect_vec(); - Ok(res.into()) + VariableSource { name, attrs } } -} -impl Visitor for Parser { - type Error = ParserError; - - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> ParserResult<()> { - for source in &mut source_unit.0 { - match source { - SourceUnitPart::ContractDefinition(def) => { - // Create new contract parse item. - let contract = - self.new_item(ParseSource::Contract(def.clone()), def.loc.start())?; - - // Move the doc pointer to the contract location start. - self.context.doc_start_loc = def.loc.start(); - - // Parse child elements with current contract as parent - let contract = self.with_parent(contract, |doc| { - def.parts - .iter_mut() - .map(|d| d.visit(doc)) - .collect::>>()?; - Ok(()) - })?; - - // Move the doc pointer to the contract location end. - self.context.doc_start_loc = def.loc.end(); - - // Add contract to the parsed items. - self.items.push(contract); - } - SourceUnitPart::FunctionDefinition(func) => self.visit_function(func)?, - SourceUnitPart::EventDefinition(event) => self.visit_event(event)?, - SourceUnitPart::ErrorDefinition(error) => self.visit_error(error)?, - SourceUnitPart::StructDefinition(structure) => self.visit_struct(structure)?, - SourceUnitPart::EnumDefinition(enumerable) => self.visit_enum(enumerable)?, - SourceUnitPart::VariableDefinition(var) => self.visit_var_definition(var)?, - SourceUnitPart::TypeDefinition(ty) => self.visit_type_definition(ty)?, - _ => {} - }; - } + fn parse_event(&self, event: &ast::ItemEvent<'_>) -> EventSource { + let fields = self.parse_var_defs(&event.parameters); + EventSource { name: event.name.to_string(), fields } + } - Ok(()) + fn parse_error(&self, err: &ast::ItemError<'_>) -> ErrorSource { + let fields = self.parse_var_defs(&err.parameters); + ErrorSource { name: err.name.to_string(), fields } } - fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) + fn parse_struct(&self, strukt: &ast::ItemStruct<'_>) -> StructSource { + let fields = self.parse_var_defs(strukt.fields); + StructSource { name: strukt.name.to_string(), fields } } - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + /// Parse a list of variable definitions into [ParamInfo]. + fn parse_var_defs(&self, vars: &[ast::VariableDefinition<'_>]) -> Vec { + vars.iter() + .map(|v| ParamInfo { name: v.name.map(|n| n.to_string()), ty: self.type_string(&v.ty) }) + .collect() } - fn visit_function(&mut self, func: &mut FunctionDefinition) -> ParserResult<()> { - // If the function parameter doesn't have a name, try to set it with - // `@custom:name` tag if any was provided - let mut start_loc = func.loc.start(); - for (loc, param) in &mut func.params { - if let Some(param) = param - && param.name.is_none() - { - let docs = self.parse_docs_range(start_loc, loc.end())?; - let name_tag = docs.iter().find(|c| c.tag == CommentTag::Custom("name".to_owned())); - if let Some(name_tag) = name_tag - && let Some(name) = name_tag.value.trim().split(' ').next() - { - param.name = Some(Identifier { loc: Loc::Implicit, name: name.to_owned() }) - } - } - start_loc = loc.end(); + /// Extract the type as a string from the source code. + fn type_string(&self, ty: &ast::Type<'_>) -> String { + let lo = ty.span.lo().to_usize().saturating_sub(self.file_start); + let hi = ty.span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + self.source[lo..hi].to_string() + } else { + String::new() } + } - self.add_element_to_parent(ParseSource::Function(func.clone()), func.loc) + fn parse_enum(&self, enm: &ast::ItemEnum<'_>) -> EnumSource { + let variants = enm.variants.iter().map(|v| v.to_string()).collect(); + EnumSource { name: enm.name.to_string(), variants } } - fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) + /// Parse doc comments from solar's [ast::DocComments] into our [Comments] type. + fn parse_docs(docs: &ast::DocComments<'_>) -> Comments { + if docs.is_empty() { + return Comments::default(); + } + Comments::from_doc_lines(docs.iter().map(|d| d.symbol.as_str())) } - fn visit_event(&mut self, event: &mut EventDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Event(event.clone()), event.loc) + /// Extract a code snippet from the source for the given span. + fn extract_code(&self, span: ast::Span) -> String { + let lo = span.lo().to_usize().saturating_sub(self.file_start); + let hi = span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + let code = &self.source[lo..hi]; + self.dedent(code) + } else { + String::new() + } } - fn visit_error(&mut self, error: &mut ErrorDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Error(error.clone()), error.loc) + /// Extract only the function prototype (excluding the body) from the source. + fn extract_prototype_code(&self, func: &ast::ItemFunction<'_>) -> String { + let lo = func.header.span.lo().to_usize().saturating_sub(self.file_start); + let hi = func.header.span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + let mut code = self.source[lo..hi].to_string(); + code.push(';'); + self.dedent(&code) + } else { + String::new() + } } - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Type(def.clone()), def.loc) + /// Remove one level of indentation from code. + fn dedent(&self, code: &str) -> String { + let prefix = &" ".repeat(self.tab_width); + code.lines() + .map(|line| line.strip_prefix(prefix).unwrap_or(line)) + .collect::>() + .join("\n") } } #[cfg(test)] mod tests { use super::*; - use solang_parser::parse; fn parse_source(src: &str) -> Vec { - let (mut source, comments) = parse(src, 0).expect("failed to parse source"); - let mut doc = Parser::new(comments, src.to_owned(), 4); - source.visit(&mut doc).expect("failed to visit source"); - doc.items() + use solar::parse::{ + Parser as SolarParser, + ast::{Arena, interface}, + interface::Session, + }; + + let sess = + Session::builder().with_silent_emitter(Some("test parse failed".to_string())).build(); + + sess.enter(|| -> Vec { + let arena = Arena::new(); + let mut parser = SolarParser::from_source_code( + &sess, + &arena, + interface::source_map::FileName::Custom("test".to_string()), + src.to_string(), + ) + .expect("failed to create parser"); + + let source_unit = parser.parse_file().map_err(|e| e.emit()).expect("failed to parse"); + + // file_start=0: when parsing directly, solar's BytePos values start at 0. + let doc = Parser::new(src.to_string(), 0, 4); + doc.parse(&source_unit) + }) } macro_rules! test_single_unit { @@ -339,23 +400,39 @@ mod tests { assert!(matches!(fallback.source, ParseSource::Function(_))); } + #[test] + fn overloaded_function_signatures() { + let items = parse_source( + r" + interface IFoo { + function process(address addr) external; + function process(address[] calldata addrs) external; + function process(address addr, uint256 value) external; + } + ", + ); + assert_eq!(items.len(), 1); + let contract = items.first().unwrap(); + assert_eq!(contract.children.len(), 3); + let sigs: Vec = contract.children.iter().map(|ch| ch.source.signature()).collect(); + assert_eq!(sigs[0], "process(address)", "first overload"); + assert_eq!(sigs[1], "process(address[])", "second overload (array)"); + assert_eq!(sigs[2], "process(address,uint256)", "third overload"); + } + #[test] fn contract_with_doc_comments() { let items = parse_source( r" pragma solidity ^0.8.19; - /// @name Test - /// no tag - ///@notice Cool contract - /// @ dev This is not a dev tag + /// @notice Cool contract /** * @dev line one * line 2 */ contract Test { - /** my function - i like whitespace - */ + /// my function + /// i like whitespace function test() {} } ", diff --git a/crates/doc/src/parser/source.rs b/crates/doc/src/parser/source.rs new file mode 100644 index 0000000000000..5a2fbddf43d5a --- /dev/null +++ b/crates/doc/src/parser/source.rs @@ -0,0 +1,172 @@ +//! Owned source types extracted from the parse tree for documentation generation. +//! +//! These types hold only the data needed by the doc writer and preprocessors, +//! avoiding lifetime dependencies on the parser's arena-allocated AST. + +/// Information about a parameter, struct field, event/error parameter, etc. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParamInfo { + /// The parameter name, if any. + pub name: Option, + /// The type rendered as a string (e.g. `"uint256"`, `"address"`). + pub ty: String, +} + +/// A base contract reference (e.g. `is IERC721, Ownable`). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BaseInfo { + /// The full dotted name (e.g. `"IERC721"` or `"SomeLib.Base"`). + pub name: String, + /// The last identifier in the path, used for linking. + pub ident: String, +} + +/// The kind of a contract-like definition. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ContractKind { + Contract, + Abstract, + Interface, + Library, +} + +impl ContractKind { + /// Returns the lowercase keyword string. + pub const fn as_str(&self) -> &'static str { + match self { + Self::Contract => "contract", + Self::Abstract => "abstract", + Self::Interface => "interface", + Self::Library => "library", + } + } +} + +/// Owned contract definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContractSource { + pub name: String, + pub kind: ContractKind, + pub bases: Vec, +} + +/// Owned function definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FunctionSource { + /// The function name, or `None` for unnamed functions (fallback/receive). + pub name: Option, + /// The function kind as a display string (e.g. `"function"`, `"constructor"`, `"fallback"`). + pub kind: String, + /// Function parameters. + pub params: Vec, + /// Return parameters. + pub returns: Vec, +} + +impl FunctionSource { + /// Get the signature of the function, including parameter types. + pub fn signature(&self) -> String { + let name = self.name.as_deref().unwrap_or(&self.kind); + if self.params.is_empty() { + return name.to_string(); + } + format!( + "{}({})", + name, + self.params.iter().map(|p| p.ty.as_str()).collect::>().join(",") + ) + } +} + +/// Variable attribute relevant for doc generation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VariableAttr { + Constant, + Immutable, +} + +/// Owned variable definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VariableSource { + pub name: String, + pub attrs: Vec, +} + +/// Owned event definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EventSource { + pub name: String, + pub fields: Vec, +} + +/// Owned error definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ErrorSource { + pub name: String, + pub fields: Vec, +} + +/// Owned struct definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StructSource { + pub name: String, + pub fields: Vec, +} + +/// Owned enum definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EnumSource { + pub name: String, + pub variants: Vec, +} + +/// Owned type definition data (user-defined value types). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TypeSource { + pub name: String, +} + +/// A wrapper type around owned source data extracted from the parse tree. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ParseSource { + /// Source contract definition. + Contract(ContractSource), + /// Source function definition. + Function(FunctionSource), + /// Source variable definition. + Variable(VariableSource), + /// Source event definition. + Event(EventSource), + /// Source error definition. + Error(ErrorSource), + /// Source struct definition. + Struct(StructSource), + /// Source enum definition. + Enum(EnumSource), + /// Source type definition. + Type(TypeSource), +} + +impl ParseSource { + /// Get the identity of the source. + pub fn ident(&self) -> String { + match self { + Self::Contract(c) => c.name.clone(), + Self::Variable(v) => v.name.clone(), + Self::Event(e) => e.name.clone(), + Self::Error(e) => e.name.clone(), + Self::Struct(s) => s.name.clone(), + Self::Enum(e) => e.name.clone(), + Self::Function(f) => f.name.clone().unwrap_or_else(|| f.kind.clone()), + Self::Type(t) => t.name.clone(), + } + } + + /// Get the signature of the source (for functions, includes parameter types). + pub fn signature(&self) -> String { + match self { + Self::Function(f) => f.signature(), + _ => self.ident(), + } + } +} diff --git a/crates/doc/src/preprocessor/contract_inheritance.rs b/crates/doc/src/preprocessor/contract_inheritance.rs index 1c2159131b01a..5df27c12a4e72 100644 --- a/crates/doc/src/preprocessor/contract_inheritance.rs +++ b/crates/doc/src/preprocessor/contract_inheritance.rs @@ -1,7 +1,5 @@ use super::{Preprocessor, PreprocessorId}; -use crate::{ - Document, ParseSource, PreprocessorOutput, document::DocumentContent, solang_ext::SafeUnwrap, -}; +use crate::{Document, ParseSource, PreprocessorOutput, document::DocumentContent}; use alloy_primitives::map::HashMap; use std::path::PathBuf; @@ -11,7 +9,7 @@ pub const CONTRACT_INHERITANCE_ID: PreprocessorId = PreprocessorId("contract_inh /// The contract inheritance preprocessor. /// /// It matches the documents with inner [`ParseSource::Contract`](crate::ParseSource) elements, -/// iterates over their [Base](solang_parser::pt::Base)s and attempts +/// iterates over their base contracts and attempts /// to link them with the paths of the other contract documents. /// /// This preprocessor writes to [Document]'s context. @@ -34,8 +32,8 @@ impl Preprocessor for ContractInheritance { let mut links = HashMap::default(); // Attempt to match bases to other contracts - for base in &contract.base { - let base_ident = base.name.identifiers.last().unwrap().name.clone(); + for base in &contract.bases { + let base_ident = base.ident.clone(); if let Some(linked) = self.try_link_base(&base_ident, &documents) { links.insert(base_ident, linked); } @@ -60,7 +58,7 @@ impl ContractInheritance { } if let DocumentContent::Single(ref item) = candidate.content && let ParseSource::Contract(ref contract) = item.source - && base == contract.name.safe_unwrap().name + && base == contract.name { return Some(candidate.relative_output_path().to_path_buf()); } diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs index 925a7cc8a2259..6fdda0e40d290 100644 --- a/crates/doc/src/preprocessor/infer_hyperlinks.rs +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -1,5 +1,5 @@ use super::{Preprocessor, PreprocessorId}; -use crate::{Comments, Document, ParseItem, ParseSource, solang_ext::SafeUnwrap}; +use crate::{Comments, Document, ParseItem, ParseSource}; use regex::{Captures, Match, Regex}; use std::{ borrow::Cow, @@ -84,7 +84,7 @@ impl InferInlineHyperlinks { for item in items { match &item.source { ParseSource::Contract(contract) => { - let name = &contract.name.safe_unwrap().name; + let name = &contract.name; if name == link.identifier { if link.part.is_none() { return Some(InlineLinkTarget::borrowed( @@ -102,11 +102,8 @@ impl InferInlineHyperlinks { // have so we can match the correct one if let Some(id) = &fun.name { // Note: constructors don't have a name - if id.name == link.ref_name() { - return Some(InlineLinkTarget::borrowed( - &id.name, - target_path.to_path_buf(), - )); + if id == link.ref_name() { + return Some(InlineLinkTarget::borrowed(id, target_path.to_path_buf())); } } else if link.ref_name() == "constructor" { return Some(InlineLinkTarget::borrowed( @@ -117,7 +114,7 @@ impl InferInlineHyperlinks { } ParseSource::Variable(_) => {} ParseSource::Event(ev) => { - let ev_name = &ev.name.safe_unwrap().name; + let ev_name = &ev.name; if ev_name == link.ref_name() { return Some(InlineLinkTarget::borrowed( ev_name, @@ -126,7 +123,7 @@ impl InferInlineHyperlinks { } } ParseSource::Error(err) => { - let err_name = &err.name.safe_unwrap().name; + let err_name = &err.name; if err_name == link.ref_name() { return Some(InlineLinkTarget::borrowed( err_name, @@ -135,7 +132,7 @@ impl InferInlineHyperlinks { } } ParseSource::Struct(structdef) => { - let struct_name = &structdef.name.safe_unwrap().name; + let struct_name = &structdef.name; if struct_name == link.ref_name() { return Some(InlineLinkTarget::borrowed( struct_name, diff --git a/crates/doc/src/preprocessor/inheritdoc.rs b/crates/doc/src/preprocessor/inheritdoc.rs index cc18bdfbd83fb..15d190f83d49c 100644 --- a/crates/doc/src/preprocessor/inheritdoc.rs +++ b/crates/doc/src/preprocessor/inheritdoc.rs @@ -1,7 +1,6 @@ use super::{Preprocessor, PreprocessorId}; use crate::{ Comments, Document, ParseItem, ParseSource, PreprocessorOutput, document::DocumentContent, - solang_ext::SafeUnwrap, }; use alloy_primitives::map::HashMap; @@ -72,7 +71,7 @@ impl Inheritdoc { for candidate in documents { if let DocumentContent::Single(ref item) = candidate.content && let ParseSource::Contract(ref contract) = item.source - && base == contract.name.safe_unwrap().name + && base == contract.name { // Not matched for the contract because it's a noop // https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags diff --git a/crates/doc/src/solang_ext/ast_eq.rs b/crates/doc/src/solang_ext/ast_eq.rs deleted file mode 100644 index 1252ac4eb32bf..0000000000000 --- a/crates/doc/src/solang_ext/ast_eq.rs +++ /dev/null @@ -1,708 +0,0 @@ -use alloy_primitives::{Address, I256, U256}; -use solang_parser::pt::*; -use std::str::FromStr; - -/// Helper to convert a string number into a comparable one -fn to_num(string: &str) -> I256 { - if string.is_empty() { - return I256::ZERO; - } - string.replace('_', "").trim().parse().unwrap() -} - -/// Helper to convert the fractional part of a number into a comparable one. -/// This will reverse the number so that 0's can be ignored -fn to_num_reversed(string: &str) -> U256 { - if string.is_empty() { - return U256::from(0); - } - string.replace('_', "").trim().chars().rev().collect::().parse().unwrap() -} - -/// Helper to filter [ParameterList] to omit empty -/// parameters -fn filter_params(list: &ParameterList) -> ParameterList { - list.iter().filter(|(_, param)| param.is_some()).cloned().collect::>() -} - -/// Check if two ParseTrees are equal ignoring location information or ordering if ordering does -/// not matter -pub trait AstEq { - fn ast_eq(&self, other: &Self) -> bool; -} - -impl AstEq for Loc { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for IdentifierPath { - fn ast_eq(&self, other: &Self) -> bool { - self.identifiers.ast_eq(&other.identifiers) - } -} - -impl AstEq for SourceUnit { - fn ast_eq(&self, other: &Self) -> bool { - self.0.ast_eq(&other.0) - } -} - -impl AstEq for VariableDefinition { - fn ast_eq(&self, other: &Self) -> bool { - let sorted_attrs = |def: &Self| { - let mut attrs = def.attrs.clone(); - attrs.sort(); - attrs - }; - self.ty.ast_eq(&other.ty) - && self.name.ast_eq(&other.name) - && self.initializer.ast_eq(&other.initializer) - && sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for FunctionDefinition { - fn ast_eq(&self, other: &Self) -> bool { - // attributes - let sorted_attrs = |def: &Self| { - let mut attrs = def.attributes.clone(); - attrs.sort(); - attrs - }; - - // params - let left_params = filter_params(&self.params); - let right_params = filter_params(&other.params); - let left_returns = filter_params(&self.returns); - let right_returns = filter_params(&other.returns); - - self.ty.ast_eq(&other.ty) - && self.name.ast_eq(&other.name) - && left_params.ast_eq(&right_params) - && self.return_not_returns.ast_eq(&other.return_not_returns) - && left_returns.ast_eq(&right_returns) - && self.body.ast_eq(&other.body) - && sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for Base { - fn ast_eq(&self, other: &Self) -> bool { - self.name.ast_eq(&other.name) - && self.args.clone().unwrap_or_default().ast_eq(&other.args.clone().unwrap_or_default()) - } -} - -impl AstEq for Vec -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - if self.len() == other.len() { - self.iter().zip(other.iter()).all(|(left, right)| left.ast_eq(right)) - } else { - false - } - } -} - -impl AstEq for Option -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - match (self, other) { - (Some(left), Some(right)) => left.ast_eq(right), - (None, None) => true, - _ => false, - } - } -} - -impl AstEq for Box -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for () { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for &T -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for String { - fn ast_eq(&self, other: &Self) -> bool { - match (Address::from_str(self), Address::from_str(other)) { - (Ok(left), Ok(right)) => left == right, - _ => self == other, - } - } -} - -macro_rules! ast_eq_field { - (#[ast_eq_use($convert_func:ident)] $field:ident) => { - $convert_func($field) - }; - ($field:ident) => { - $field - }; -} - -macro_rules! gen_ast_eq_enum { - ($self:expr, $other:expr, $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - match $self { - $($name::$unit_variant => gen_ast_eq_enum!($other, $name, $unit_variant),)* - $($name::$tuple_variant($($tuple_field),*) => - gen_ast_eq_enum!($other, $name, $tuple_variant ($($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),*)),)* - $($name::$struct_variant { $($struct_field),* } => - gen_ast_eq_enum!($other, $name, $struct_variant {$($(#[ast_eq_use($struct_convert_func)])? $struct_field),*}),)* - } - }; - ($other:expr, $name:ident, $unit_variant:ident) => { - { - matches!($other, $name::$unit_variant) - } - }; - ($other:expr, $name:ident, $tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? ) ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - if let $name::$tuple_variant($($tuple_field),*) = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; - ($other:expr, $name:ident, $struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? } ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - if let $name::$struct_variant { $($struct_field),* } = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; -} - -macro_rules! wrap_in_box { - ($stmt:expr, $loc:expr) => { - if !matches!(**$stmt, Statement::Block { .. }) { - Box::new(Statement::Block { - loc: $loc, - unchecked: false, - statements: vec![*$stmt.clone()], - }) - } else { - $stmt.clone() - } - }; -} - -impl AstEq for Statement { - fn ast_eq(&self, other: &Self) -> bool { - match self { - Self::If(loc, expr, stmt1, stmt2) => { - #[expect(clippy::borrowed_box)] - let wrap_if = |stmt1: &Box, stmt2: &Option>| { - ( - wrap_in_box!(stmt1, *loc), - stmt2.as_ref().map(|stmt2| { - if matches!(**stmt2, Self::If(..)) { - stmt2.clone() - } else { - wrap_in_box!(stmt2, *loc) - } - }), - ) - }; - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let left = (loc, expr, &stmt1, &stmt2); - if let Self::If(loc, expr, stmt1, stmt2) = other { - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let right = (loc, expr, &stmt1, &stmt2); - left.ast_eq(&right) - } else { - false - } - } - Self::While(loc, expr, stmt1) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, expr, &stmt1); - if let Self::While(loc, expr, stmt1) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, expr, &stmt1); - left.ast_eq(&right) - } else { - false - } - } - Self::DoWhile(loc, stmt1, expr) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, &stmt1, expr); - if let Self::DoWhile(loc, stmt1, expr) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, &stmt1, expr); - left.ast_eq(&right) - } else { - false - } - } - Self::For(loc, stmt1, expr, stmt2, stmt3) => { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let left = (loc, stmt1, expr, stmt2, &stmt3); - if let Self::For(loc, stmt1, expr, stmt2, stmt3) = other { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let right = (loc, stmt1, expr, stmt2, &stmt3); - left.ast_eq(&right) - } else { - false - } - } - Self::Try(loc, expr, returns, catch) => { - let left_returns = - returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); - let left = (loc, expr, left_returns, catch); - if let Self::Try(loc, expr, returns, catch) = other { - let right_returns = - returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); - let right = (loc, expr, right_returns, catch); - left.ast_eq(&right) - } else { - false - } - } - _ => gen_ast_eq_enum!(self, other, Statement { - _ - Args(loc, args), - Expression(loc, expr), - VariableDefinition(loc, decl, expr), - Continue(loc, ), - Break(loc, ), - Return(loc, expr), - Revert(loc, expr, expr2), - RevertNamedArgs(loc, expr, args), - Emit(loc, expr), - // provide overridden variants regardless - If(loc, expr, stmt1, stmt2), - While(loc, expr, stmt1), - DoWhile(loc, stmt1, expr), - For(loc, stmt1, expr, stmt2, stmt3), - Try(loc, expr, params, clause), - Error(loc) - _ - Block { - loc, - unchecked, - statements, - }, - Assembly { - loc, - dialect, - block, - flags, - }, - }), - } - } -} - -macro_rules! derive_ast_eq { - ($name:ident) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - self == other - } - } - }; - (($($index:tt $gen:tt),*)) => { - impl < $( $gen ),* > AstEq for ($($gen,)*) where $($gen: AstEq),* { - fn ast_eq(&self, other: &Self) -> bool { - $( - if !self.$index.ast_eq(&other.$index) { - return false - } - )* - true - } - } - }; - (struct $name:ident { $($field:ident),* $(,)? }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - let $name { $($field),* } = self; - let left = ($($field),*); - let $name { $($field),* } = other; - let right = ($($field),*); - left.ast_eq(&right) - } - } - }; - (enum $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - gen_ast_eq_enum!(self, other, $name { - $($unit_variant),* - _ - $($tuple_variant ( $($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),* )),* - _ - $($struct_variant { $($(#[ast_eq_use($struct_convert_func)])? $struct_field),* }),* - }) - } - } - } -} - -derive_ast_eq! { (0 A) } -derive_ast_eq! { (0 A, 1 B) } -derive_ast_eq! { (0 A, 1 B, 2 C) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G) } -derive_ast_eq! { bool } -derive_ast_eq! { u8 } -derive_ast_eq! { u16 } -derive_ast_eq! { I256 } -derive_ast_eq! { U256 } -derive_ast_eq! { struct Identifier { loc, name } } -derive_ast_eq! { struct HexLiteral { loc, hex } } -derive_ast_eq! { struct StringLiteral { loc, unicode, string } } -derive_ast_eq! { struct Parameter { loc, annotation, ty, storage, name } } -derive_ast_eq! { struct NamedArgument { loc, name, expr } } -derive_ast_eq! { struct YulBlock { loc, statements } } -derive_ast_eq! { struct YulFunctionCall { loc, id, arguments } } -derive_ast_eq! { struct YulFunctionDefinition { loc, id, params, returns, body } } -derive_ast_eq! { struct YulSwitch { loc, condition, cases, default } } -derive_ast_eq! { struct YulFor { - loc, - init_block, - condition, - post_block, - execution_block, -}} -derive_ast_eq! { struct YulTypedIdentifier { loc, id, ty } } -derive_ast_eq! { struct VariableDeclaration { loc, ty, storage, name } } -derive_ast_eq! { struct Using { loc, list, ty, global } } -derive_ast_eq! { struct UsingFunction { loc, path, oper } } -derive_ast_eq! { struct TypeDefinition { loc, name, ty } } -derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, layout, parts } } -derive_ast_eq! { struct EventParameter { loc, ty, indexed, name } } -derive_ast_eq! { struct ErrorParameter { loc, ty, name } } -derive_ast_eq! { struct EventDefinition { loc, name, fields, anonymous } } -derive_ast_eq! { struct ErrorDefinition { loc, keyword, name, fields } } -derive_ast_eq! { struct StructDefinition { loc, name, fields } } -derive_ast_eq! { struct EnumDefinition { loc, name, values } } -derive_ast_eq! { struct Annotation { loc, id, value } } -derive_ast_eq! { enum PragmaDirective { - _ - Identifier(loc, id1, id2), - StringLiteral(loc, id, lit), - Version(loc, id, version), - _ -}} -derive_ast_eq! { enum UsingList { - Error, - _ - Library(expr), - Functions(exprs), - _ -}} -derive_ast_eq! { enum UserDefinedOperator { - BitwiseAnd, - BitwiseNot, - Negate, - BitwiseOr, - BitwiseXor, - Add, - Divide, - Modulo, - Multiply, - Subtract, - Equal, - More, - MoreEqual, - Less, - LessEqual, - NotEqual, - _ - _ -}} -derive_ast_eq! { enum Visibility { - _ - External(loc), - Public(loc), - Internal(loc), - Private(loc), - _ -}} -derive_ast_eq! { enum Mutability { - _ - Pure(loc), - View(loc), - Constant(loc), - Payable(loc), - _ -}} -derive_ast_eq! { enum FunctionAttribute { - _ - Mutability(muta), - Visibility(visi), - Virtual(loc), - Immutable(loc), - Override(loc, idents), - BaseOrModifier(loc, base), - Error(loc), - _ -}} -derive_ast_eq! { enum StorageLocation { - _ - Memory(loc), - Storage(loc), - Calldata(loc), - Transient(loc), - _ -}} -derive_ast_eq! { enum Type { - Address, - AddressPayable, - Payable, - Bool, - Rational, - DynamicBytes, - String, - _ - Int(int), - Uint(int), - Bytes(int), - _ - Mapping{ loc, key, key_name, value, value_name }, - Function { params, attributes, returns }, -}} -derive_ast_eq! { enum Expression { - _ - PostIncrement(loc, expr1), - PostDecrement(loc, expr1), - New(loc, expr1), - ArraySubscript(loc, expr1, expr2), - ArraySlice( - loc, - expr1, - expr2, - expr3, - ), - MemberAccess(loc, expr1, ident1), - FunctionCall(loc, expr1, exprs1), - FunctionCallBlock(loc, expr1, stmt), - NamedFunctionCall(loc, expr1, args), - Not(loc, expr1), - BitwiseNot(loc, expr1), - Delete(loc, expr1), - PreIncrement(loc, expr1), - PreDecrement(loc, expr1), - UnaryPlus(loc, expr1), - Negate(loc, expr1), - Power(loc, expr1, expr2), - Multiply(loc, expr1, expr2), - Divide(loc, expr1, expr2), - Modulo(loc, expr1, expr2), - Add(loc, expr1, expr2), - Subtract(loc, expr1, expr2), - ShiftLeft(loc, expr1, expr2), - ShiftRight(loc, expr1, expr2), - BitwiseAnd(loc, expr1, expr2), - BitwiseXor(loc, expr1, expr2), - BitwiseOr(loc, expr1, expr2), - Less(loc, expr1, expr2), - More(loc, expr1, expr2), - LessEqual(loc, expr1, expr2), - MoreEqual(loc, expr1, expr2), - Equal(loc, expr1, expr2), - NotEqual(loc, expr1, expr2), - And(loc, expr1, expr2), - Or(loc, expr1, expr2), - ConditionalOperator(loc, expr1, expr2, expr3), - Assign(loc, expr1, expr2), - AssignOr(loc, expr1, expr2), - AssignAnd(loc, expr1, expr2), - AssignXor(loc, expr1, expr2), - AssignShiftLeft(loc, expr1, expr2), - AssignShiftRight(loc, expr1, expr2), - AssignAdd(loc, expr1, expr2), - AssignSubtract(loc, expr1, expr2), - AssignMultiply(loc, expr1, expr2), - AssignDivide(loc, expr1, expr2), - AssignModulo(loc, expr1, expr2), - BoolLiteral(loc, bool1), - NumberLiteral(loc, #[ast_eq_use(to_num)] str1, #[ast_eq_use(to_num)] str2, unit), - RationalNumberLiteral( - loc, - #[ast_eq_use(to_num)] str1, - #[ast_eq_use(to_num_reversed)] str2, - #[ast_eq_use(to_num)] str3, - unit - ), - HexNumberLiteral(loc, str1, unit), - StringLiteral(strs1), - Type(loc, ty1), - HexLiteral(hexs1), - AddressLiteral(loc, str1), - Variable(ident1), - List(loc, params1), - ArrayLiteral(loc, exprs1), - Parenthesis(loc, expr) - _ -}} -derive_ast_eq! { enum CatchClause { - _ - Simple(param, ident, stmt), - Named(loc, ident, param, stmt), - _ -}} -derive_ast_eq! { enum YulStatement { - _ - Assign(loc, exprs, expr), - VariableDeclaration(loc, idents, expr), - If(loc, expr, block), - For(yul_for), - Switch(switch), - Leave(loc), - Break(loc), - Continue(loc), - Block(block), - FunctionDefinition(def), - FunctionCall(func), - Error(loc), - _ -}} -derive_ast_eq! { enum YulExpression { - _ - BoolLiteral(loc, boo, ident), - NumberLiteral(loc, string1, string2, ident), - HexNumberLiteral(loc, string, ident), - HexStringLiteral(hex, ident), - StringLiteral(string, ident), - Variable(ident), - FunctionCall(func), - SuffixAccess(loc, expr, ident), - _ -}} -derive_ast_eq! { enum YulSwitchOptions { - _ - Case(loc, expr, block), - Default(loc, block), - _ -}} -derive_ast_eq! { enum SourceUnitPart { - _ - ContractDefinition(def), - PragmaDirective(pragma), - ImportDirective(import), - EnumDefinition(def), - StructDefinition(def), - EventDefinition(def), - ErrorDefinition(def), - FunctionDefinition(def), - VariableDefinition(def), - TypeDefinition(def), - Using(using), - StraySemicolon(loc), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ImportPath { - _ - Filename(lit), - Path(path), - _ -}} -derive_ast_eq! { enum Import { - _ - Plain(string, loc), - GlobalSymbol(string, ident, loc), - Rename(string, idents, loc), - _ -}} -derive_ast_eq! { enum FunctionTy { - Constructor, - Function, - Fallback, - Receive, - Modifier, - _ - _ -}} -derive_ast_eq! { enum ContractPart { - _ - StructDefinition(def), - EventDefinition(def), - EnumDefinition(def), - ErrorDefinition(def), - VariableDefinition(def), - FunctionDefinition(def), - TypeDefinition(def), - StraySemicolon(loc), - Using(using), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ContractTy { - _ - Abstract(loc), - Contract(loc), - Interface(loc), - Library(loc), - _ -}} -derive_ast_eq! { enum VariableAttribute { - _ - Visibility(visi), - Constant(loc), - Immutable(loc), - Override(loc, idents), - StorageType(st), - StorageLocation(st), - _ -}} - -// Who cares -impl AstEq for StorageType { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for VersionComparator { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} diff --git a/crates/doc/src/solang_ext/loc.rs b/crates/doc/src/solang_ext/loc.rs deleted file mode 100644 index cec21d7714385..0000000000000 --- a/crates/doc/src/solang_ext/loc.rs +++ /dev/null @@ -1,168 +0,0 @@ -use solang_parser::pt; -use std::{borrow::Cow, rc::Rc, sync::Arc}; - -/// Returns the code location. -/// -/// Patched version of [`pt::CodeLocation`]: includes the block of a [`pt::FunctionDefinition`] in -/// its `loc`. -pub trait CodeLocationExt { - /// Returns the code location of `self`. - fn loc(&self) -> pt::Loc; -} - -impl CodeLocationExt for &T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for &mut T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Cow<'_, T> { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Box { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Rc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Arc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -// FunctionDefinition patch -impl CodeLocationExt for pt::FunctionDefinition { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - let mut loc = self.loc; - if let Some(ref body) = self.body { - loc.use_end_from(&pt::CodeLocation::loc(body)); - } - loc - } -} - -impl CodeLocationExt for pt::ContractPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::SourceUnitPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::ImportPath { - fn loc(&self) -> pt::Loc { - match self { - Self::Filename(s) => s.loc(), - Self::Path(i) => i.loc(), - } - } -} - -impl CodeLocationExt for pt::VersionComparator { - fn loc(&self) -> pt::Loc { - match self { - Self::Plain { loc, .. } - | Self::Operator { loc, .. } - | Self::Or { loc, .. } - | Self::Range { loc, .. } => *loc, - } - } -} - -macro_rules! impl_delegate { - ($($t:ty),+ $(,)?) => {$( - impl CodeLocationExt for $t { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - pt::CodeLocation::loc(self) - } - } - )+}; -} - -impl_delegate! { - pt::Annotation, - pt::Base, - pt::ContractDefinition, - pt::EnumDefinition, - pt::ErrorDefinition, - pt::ErrorParameter, - pt::EventDefinition, - pt::EventParameter, - pt::PragmaDirective, - // pt::FunctionDefinition, - pt::HexLiteral, - pt::Identifier, - pt::IdentifierPath, - pt::NamedArgument, - pt::Parameter, - // pt::SourceUnit, - pt::StringLiteral, - pt::StructDefinition, - pt::TypeDefinition, - pt::Using, - pt::UsingFunction, - pt::VariableDeclaration, - pt::VariableDefinition, - pt::YulBlock, - pt::YulFor, - pt::YulFunctionCall, - pt::YulFunctionDefinition, - pt::YulSwitch, - pt::YulTypedIdentifier, - - pt::CatchClause, - pt::Comment, - // pt::ContractPart, - pt::ContractTy, - pt::Expression, - pt::FunctionAttribute, - // pt::FunctionTy, - pt::Import, - pt::Loc, - pt::Mutability, - // pt::SourceUnitPart, - pt::Statement, - pt::StorageLocation, - // pt::Type, - // pt::UserDefinedOperator, - pt::UsingList, - pt::VariableAttribute, - // pt::Visibility, - pt::YulExpression, - pt::YulStatement, - pt::YulSwitchOptions, -} diff --git a/crates/doc/src/solang_ext/mod.rs b/crates/doc/src/solang_ext/mod.rs deleted file mode 100644 index d85dd1a5aace7..0000000000000 --- a/crates/doc/src/solang_ext/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Extension traits and modules to the [`solang_parser`] crate. - -/// Same as [`solang_parser::pt`], but with the patched `CodeLocation`. -pub mod pt { - #[doc(no_inline)] - pub use super::loc::CodeLocationExt as CodeLocation; - - #[doc(no_inline)] - pub use solang_parser::pt::{ - Annotation, Base, CatchClause, Comment, ContractDefinition, ContractPart, ContractTy, - EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, - Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, - IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, - Parameter, ParameterList, PragmaDirective, SourceUnit, SourceUnitPart, Statement, - StorageLocation, StringLiteral, StructDefinition, Type, TypeDefinition, - UserDefinedOperator, Using, UsingFunction, UsingList, VariableAttribute, - VariableDeclaration, VariableDefinition, Visibility, YulBlock, YulExpression, YulFor, - YulFunctionCall, YulFunctionDefinition, YulStatement, YulSwitch, YulSwitchOptions, - YulTypedIdentifier, - }; -} - -mod ast_eq; -mod loc; -mod safe_unwrap; -mod visit; - -pub use ast_eq::AstEq; -pub use loc::CodeLocationExt; -pub use safe_unwrap::SafeUnwrap; -pub use visit::{Visitable, Visitor}; diff --git a/crates/doc/src/solang_ext/safe_unwrap.rs b/crates/doc/src/solang_ext/safe_unwrap.rs deleted file mode 100644 index fe2810ad9705a..0000000000000 --- a/crates/doc/src/solang_ext/safe_unwrap.rs +++ /dev/null @@ -1,52 +0,0 @@ -use solang_parser::pt; - -/// Trait implemented to unwrap optional parse tree items initially introduced in -/// [hyperledger/solang#1068]. -/// -/// Note that the methods of this trait should only be used on parse tree items' fields, like -/// [pt::VariableDefinition] or [pt::EventDefinition], where the `name` field is `None` only when an -/// error occurred during parsing. -/// -/// [hyperledger/solang#1068]: https://github.com/hyperledger/solang/pull/1068 -pub trait SafeUnwrap { - /// See [SafeUnwrap]. - fn safe_unwrap(&self) -> &T; - - /// See [SafeUnwrap]. - fn safe_unwrap_mut(&mut self) -> &mut T; -} - -#[inline(never)] -#[cold] -#[track_caller] -fn invalid() -> ! { - panic!("invalid parse tree") -} - -macro_rules! impl_ { - ($($t:ty),+ $(,)?) => { - $( - impl SafeUnwrap<$t> for Option<$t> { - #[inline] - #[track_caller] - fn safe_unwrap(&self) -> &$t { - match *self { - Some(ref x) => x, - None => invalid(), - } - } - - #[inline] - #[track_caller] - fn safe_unwrap_mut(&mut self) -> &mut $t { - match *self { - Some(ref mut x) => x, - None => invalid(), - } - } - } - )+ - }; -} - -impl_!(pt::Identifier, pt::StringLiteral); diff --git a/crates/doc/src/solang_ext/visit.rs b/crates/doc/src/solang_ext/visit.rs deleted file mode 100644 index 80cb4f3dc1376..0000000000000 --- a/crates/doc/src/solang_ext/visit.rs +++ /dev/null @@ -1,621 +0,0 @@ -//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). - -use crate::solang_ext::{CodeLocationExt, pt::*}; - -/// A trait that is invoked while traversing the Solidity Parse Tree. -/// Each method of the [Visitor] trait is a hook that can be potentially overridden. -/// -/// Currently the main implementer of this trait is the [`Formatter`](crate::Formatter<'_>) struct. -pub trait Visitor { - type Error: std::error::Error; - - fn visit_source(&mut self, _loc: Loc) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_source_unit(&mut self, _source_unit: &mut SourceUnit) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_contract(&mut self, _contract: &mut ContractDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<(), Self::Error> { - self.visit_source(annotation.loc) - } - - fn visit_pragma(&mut self, pragma: &mut PragmaDirective) -> Result<(), Self::Error> { - self.visit_source(pragma.loc()) - } - - fn visit_import_plain( - &mut self, - _loc: Loc, - _import: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_global( - &mut self, - _loc: Loc, - _global: &mut ImportPath, - _alias: &mut Identifier, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_renames( - &mut self, - _loc: Loc, - _imports: &mut [(Identifier, Option)], - _from: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_enum(&mut self, _enum: &mut EnumDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_assembly( - &mut self, - loc: Loc, - _dialect: &mut Option, - _block: &mut YulBlock, - _flags: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_block( - &mut self, - loc: Loc, - _unchecked: bool, - _statements: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_args(&mut self, loc: Loc, _args: &mut Vec) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - /// Don't write semicolon at the end because expressions can appear as both - /// part of other node and a statement in the function body - fn visit_expr(&mut self, loc: Loc, _expr: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident(&mut self, loc: Loc, _ident: &mut Identifier) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { - self.visit_source(idents.loc) - } - - fn visit_emit(&mut self, loc: Loc, _event: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { - self.visit_source(var.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - _declaration: &mut VariableDeclaration, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon() - } - - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { - self.visit_source(var.loc) - } - - fn visit_return( - &mut self, - loc: Loc, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert_named_args( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_break(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_continue(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - #[expect(clippy::type_complexity)] - fn visit_try( - &mut self, - loc: Loc, - _expr: &mut Expression, - _returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - _clauses: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_if( - &mut self, - loc: Loc, - _cond: &mut Expression, - _if_branch: &mut Box, - _else_branch: &mut Option>, - _is_first_stmt: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_do_while( - &mut self, - loc: Loc, - _body: &mut Statement, - _cond: &mut Expression, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_while( - &mut self, - loc: Loc, - _cond: &mut Expression, - _body: &mut Statement, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_for( - &mut self, - loc: Loc, - _init: &mut Option>, - _cond: &mut Option>, - _update: &mut Option>, - _body: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(func.loc())?; - if func.body.is_none() { - self.visit_stray_semicolon()?; - } - - Ok(()) - } - - fn visit_function_attribute( - &mut self, - attribute: &mut FunctionAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_var_attribute( - &mut self, - attribute: &mut VariableAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_base(&mut self, base: &mut Base) -> Result<(), Self::Error> { - self.visit_source(base.loc) - } - - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { - self.visit_source(parameter.loc) - } - - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<(), Self::Error> { - self.visit_source(structure.loc)?; - - Ok(()) - } - - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<(), Self::Error> { - self.visit_source(event.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<(), Self::Error> { - self.visit_source(error.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { - self.visit_source(def.loc) - } - - fn visit_stray_semicolon(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_using(&mut self, using: &mut Using) -> Result<(), Self::Error> { - self.visit_source(using.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_yul_block( - &mut self, - loc: Loc, - _stmts: &mut Vec, - _attempt_single_line: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { - self.visit_source(expr.loc()) - } - - fn visit_yul_assignment( - &mut self, - loc: Loc, - _exprs: &mut Vec, - _expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocationExt, - { - self.visit_source(loc) - } - - fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_if( - &mut self, - loc: Loc, - _expr: &mut YulExpression, - _block: &mut YulBlock, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_var_declaration( - &mut self, - loc: Loc, - _idents: &mut Vec, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - self.visit_source(ident.loc) - } - - fn visit_parser_error(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } -} - -/// Visitable trait for [`solang_parser::pt`] types. -/// -/// All [`solang_parser::pt`] types, such as [Statement], should implement the [Visitable] trait -/// that accepts a trait [Visitor] implementation, which has various callback handles for Solidity -/// Parse Tree nodes. -/// -/// We want to take a `&mut self` to be able to implement some advanced features in the future such -/// as modifying the Parse Tree before formatting it. -pub trait Visitable { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor; -} - -impl Visitable for &mut T -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Option -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - if let Some(inner) = self.as_mut() { inner.visit(v) } else { Ok(()) } - } -} - -impl Visitable for Box -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Vec -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - for item in self.iter_mut() { - item.visit(v)?; - } - Ok(()) - } -} - -impl Visitable for SourceUnitPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::ContractDefinition(contract) => v.visit_contract(contract), - Self::PragmaDirective(pragma) => v.visit_pragma(pragma), - Self::ImportDirective(import) => import.visit(v), - Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), - Self::StructDefinition(structure) => v.visit_struct(structure), - Self::EventDefinition(event) => v.visit_event(event), - Self::ErrorDefinition(error) => v.visit_error(error), - Self::FunctionDefinition(function) => v.visit_function(function), - Self::VariableDefinition(variable) => v.visit_var_definition(variable), - Self::TypeDefinition(def) => v.visit_type_definition(def), - Self::StraySemicolon(_) => v.visit_stray_semicolon(), - Self::Using(using) => v.visit_using(using), - Self::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Import { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Plain(import, loc) => v.visit_import_plain(*loc, import), - Self::GlobalSymbol(global, import_as, loc) => { - v.visit_import_global(*loc, global, import_as) - } - Self::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), - } - } -} - -impl Visitable for ContractPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::StructDefinition(structure) => v.visit_struct(structure), - Self::EventDefinition(event) => v.visit_event(event), - Self::ErrorDefinition(error) => v.visit_error(error), - Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), - Self::VariableDefinition(variable) => v.visit_var_definition(variable), - Self::FunctionDefinition(function) => v.visit_function(function), - Self::TypeDefinition(def) => v.visit_type_definition(def), - Self::StraySemicolon(_) => v.visit_stray_semicolon(), - Self::Using(using) => v.visit_using(using), - Self::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Statement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Block { loc, unchecked, statements } => { - v.visit_block(*loc, *unchecked, statements) - } - Self::Assembly { loc, dialect, block, flags } => { - v.visit_assembly(*loc, dialect, block, flags) - } - Self::Args(loc, args) => v.visit_args(*loc, args), - Self::If(loc, cond, if_branch, else_branch) => { - v.visit_if(*loc, cond, if_branch, else_branch, true) - } - Self::While(loc, cond, body) => v.visit_while(*loc, cond, body), - Self::Expression(loc, expr) => { - v.visit_expr(*loc, expr)?; - v.visit_stray_semicolon() - } - Self::VariableDefinition(loc, declaration, expr) => { - v.visit_var_definition_stmt(*loc, declaration, expr) - } - Self::For(loc, init, cond, update, body) => v.visit_for(*loc, init, cond, update, body), - Self::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), - Self::Continue(loc) => v.visit_continue(*loc, true), - Self::Break(loc) => v.visit_break(*loc, true), - Self::Return(loc, expr) => v.visit_return(*loc, expr), - Self::Revert(loc, error, args) => v.visit_revert(*loc, error, args), - Self::RevertNamedArgs(loc, error, args) => v.visit_revert_named_args(*loc, error, args), - Self::Emit(loc, event) => v.visit_emit(*loc, event), - Self::Try(loc, expr, returns, clauses) => v.visit_try(*loc, expr, returns, clauses), - Self::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -impl Visitable for Loc { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_source(*self) - } -} - -impl Visitable for Expression { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_expr(self.loc(), self) - } -} - -impl Visitable for Identifier { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_ident(self.loc, self) - } -} - -impl Visitable for VariableDeclaration { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_var_declaration(self) - } -} - -impl Visitable for YulBlock { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_yul_block(self.loc, self.statements.as_mut(), false) - } -} - -impl Visitable for YulStatement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Assign(loc, exprs, expr) => v.visit_yul_assignment(*loc, exprs, &mut Some(expr)), - Self::Block(block) => v.visit_yul_block(block.loc, block.statements.as_mut(), false), - Self::Break(loc) => v.visit_break(*loc, false), - Self::Continue(loc) => v.visit_continue(*loc, false), - Self::For(stmt) => v.visit_yul_for(stmt), - Self::FunctionCall(stmt) => v.visit_yul_function_call(stmt), - Self::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), - Self::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), - Self::Leave(loc) => v.visit_yul_leave(*loc), - Self::Switch(stmt) => v.visit_yul_switch(stmt), - Self::VariableDeclaration(loc, idents, expr) => { - v.visit_yul_var_declaration(*loc, idents, expr) - } - Self::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -macro_rules! impl_visitable { - ($type:ty, $func:ident) => { - impl Visitable for $type { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.$func(self) - } - } - }; -} - -impl_visitable!(SourceUnit, visit_source_unit); -impl_visitable!(FunctionAttribute, visit_function_attribute); -impl_visitable!(VariableAttribute, visit_var_attribute); -impl_visitable!(Parameter, visit_parameter); -impl_visitable!(Base, visit_base); -impl_visitable!(EventParameter, visit_event_parameter); -impl_visitable!(ErrorParameter, visit_error_parameter); -impl_visitable!(IdentifierPath, visit_ident_path); -impl_visitable!(YulExpression, visit_yul_expr); -impl_visitable!(YulTypedIdentifier, visit_yul_typed_ident); diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 1502322ac87f4..888f6269623a5 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,14 +1,11 @@ use crate::{ - CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, - GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, + BaseInfo, CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, + FunctionSource, GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, VariableAttr, document::{DocumentContent, read_context}, - helpers::function_signature, parser::ParseSource, - solang_ext::SafeUnwrap, writer::BufWriter, }; use itertools::Itertools; -use solang_parser::pt::{Base, FunctionDefinition, VariableAttribute}; use std::path::Path; /// The result of [`AsDoc::as_doc`]. @@ -75,8 +72,8 @@ impl AsDoc for CommentsRef<'_> { writer.writeln_raw(format!( "{}{}: {}", if customs.len() == 1 { "" } else { "- " }, - &c.tag, - &c.value + c.tag, + c.value ))?; writer.writeln()?; } @@ -86,9 +83,9 @@ impl AsDoc for CommentsRef<'_> { } } -impl AsDoc for Base { +impl AsDoc for BaseInfo { fn as_doc(&self) -> AsDocResult { - Ok(self.name.identifiers.iter().map(|ident| ident.name.clone()).join(".")) + Ok(self.name.clone()) } } @@ -106,8 +103,7 @@ impl AsDoc for Document { } for item in items { - let func = item.as_function().unwrap(); - let heading = function_signature(func).replace(',', ", "); + let heading = item.source.signature().replace(',', ", "); writer.write_heading(&heading)?; writer.write_section(&item.comments, &item.code)?; } @@ -121,7 +117,7 @@ impl AsDoc for Document { for item in items { let var = item.as_variable().unwrap(); - writer.write_heading(&var.name.safe_unwrap().name)?; + writer.write_heading(&var.name)?; writer.write_section(&item.comments, &item.code)?; } } @@ -138,15 +134,15 @@ impl AsDoc for Document { match &item.source { ParseSource::Contract(contract) => { - if !contract.base.is_empty() { + if !contract.bases.is_empty() { writer.write_bold("Inherits:")?; let mut bases = vec![]; let linked = read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance); - for base in &contract.base { + for base in &contract.bases { let base_doc = base.as_doc()?; - let base_ident = &base.name.identifiers.last().unwrap().name; + let base_ident = &base.ident; let link = linked .as_ref() @@ -179,8 +175,7 @@ impl AsDoc for Document { item.attrs.iter().any(|attr| { matches!( attr, - VariableAttribute::Constant(_) - | VariableAttribute::Immutable(_) + VariableAttr::Constant | VariableAttr::Immutable ) }) }); @@ -189,11 +184,11 @@ impl AsDoc for Document { writer.write_subtitle("Constants")?; constants.into_iter().try_for_each(|(item, comments, code)| { let comments = comments.merge_inheritdoc( - &item.name.safe_unwrap().name, + &item.name, read_context!(self, INHERITDOC_ID, Inheritdoc), ); - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(&comments, code)?; writer.writeln() })?; @@ -203,11 +198,11 @@ impl AsDoc for Document { writer.write_subtitle("State Variables")?; state_vars.into_iter().try_for_each(|(item, comments, code)| { let comments = comments.merge_inheritdoc( - &item.name.safe_unwrap().name, + &item.name, read_context!(self, INHERITDOC_ID, Inheritdoc), ); - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(&comments, code)?; writer.writeln() })?; @@ -225,7 +220,7 @@ impl AsDoc for Document { if let Some(events) = item.events() { writer.write_subtitle("Events")?; events.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_events_table(&item.fields, comments) })?; @@ -234,7 +229,7 @@ impl AsDoc for Document { if let Some(errors) = item.errors() { writer.write_subtitle("Errors")?; errors.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_errors_table(&item.fields, comments) })?; @@ -243,7 +238,7 @@ impl AsDoc for Document { if let Some(structs) = item.structs() { writer.write_subtitle("Structs")?; structs.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_properties_table(&item.fields, comments) })?; @@ -252,7 +247,7 @@ impl AsDoc for Document { if let Some(enums) = item.enums() { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_variant_table(item, comments) })?; @@ -270,16 +265,16 @@ impl AsDoc for Document { writer.write_code(&item.code)?; // Write function parameter comments in a table - let params = - func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Param, ¶ms, &item.comments)?; + writer.try_write_param_table( + CommentTag::Param, + &func.params, + &item.comments, + )?; // Write function return parameter comments in a table - let returns = - func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); writer.try_write_param_table( CommentTag::Return, - &returns, + &func.returns, &item.comments, )?; @@ -315,12 +310,12 @@ impl Document { fn write_function( &self, writer: &mut BufWriter, - func: &FunctionDefinition, + func: &FunctionSource, comments: &Comments, code: &str, ) -> Result<(), std::fmt::Error> { - let func_sign = function_signature(func); - let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.clone()); + let func_name = func.name.as_deref().unwrap_or(&func.kind).to_string(); + let func_sign = func.signature(); let comments = comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc)); @@ -336,12 +331,10 @@ impl Document { writer.write_code(code)?; // Write function parameter comments in a table - let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?; + writer.try_write_param_table(CommentTag::Param, &func.params, &comments)?; // Write function return parameter comments in a table - let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Return, &returns, &comments)?; + writer.try_write_param_table(CommentTag::Return, &func.returns, &comments)?; writer.writeln()?; Ok(()) diff --git a/crates/doc/src/writer/traits.rs b/crates/doc/src/writer/traits.rs index 0b79718d5102f..7b0fbcc813a2b 100644 --- a/crates/doc/src/writer/traits.rs +++ b/crates/doc/src/writer/traits.rs @@ -1,58 +1,23 @@ //! Helper traits for writing documentation. -use solang_parser::pt::Expression; +use crate::ParamInfo; -/// Helper trait to abstract over a solang type that can be documented as parameter +/// Helper trait to abstract over a type that can be documented as a parameter. pub(crate) trait ParamLike { - /// Returns the type of the parameter. - fn ty(&self) -> &Expression; - /// Returns the type as a string. - fn type_name(&self) -> String { - self.ty().to_string() - } + fn type_name(&self) -> &str; /// Returns the identifier of the parameter. fn name(&self) -> Option<&str>; } -impl ParamLike for solang_parser::pt::Parameter { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::VariableDeclaration { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::EventParameter { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::ErrorParameter { - fn ty(&self) -> &Expression { +impl ParamLike for ParamInfo { + fn type_name(&self) -> &str { &self.ty } fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) + self.name.as_deref() } } @@ -60,8 +25,8 @@ impl ParamLike for &T where T: ParamLike, { - fn ty(&self) -> &Expression { - T::ty(*self) + fn type_name(&self) -> &str { + T::type_name(*self) } fn name(&self) -> Option<&str> { diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 03d569c17f500..801e813026a39 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -36,7 +36,7 @@ alloy-primitives = { workspace = true, features = [ alloy-provider.workspace = true alloy-network.workspace = true alloy-consensus.workspace = true -alloy-op-evm.workspace = true +alloy-op-evm = { workspace = true, optional = true } alloy-rpc-types = { workspace = true, features = ["anvil"] } alloy-sol-types.workspace = true alloy-rlp.workspace = true @@ -54,9 +54,10 @@ revm = { workspace = true, features = [ "blst", ] } revm-inspectors.workspace = true -op-alloy-consensus = { workspace = true, features = ["k256"] } -op-alloy-network.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } tempo-revm.workspace = true tempo-alloy.workspace = true tempo-contracts.workspace = true @@ -77,7 +78,18 @@ url.workspace = true [dev-dependencies] alloy-serde.workspace = true -op-alloy-consensus.workspace = true -op-alloy-rpc-types.workspace = true anvil.workspace = true foundry-test-utils.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "foundry-common/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", +] diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 0cfd56a44219c..b836023a968b7 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -223,8 +223,8 @@ fn trimmed_hex(s: &[u8]) -> String { } else { format!( "{}…{} ({} bytes)", - &hex::encode(&s[..n / 2]), - &hex::encode(&s[s.len() - n / 2..]), + hex::encode(&s[..n / 2]), + hex::encode(&s[s.len() - n / 2..]), s.len(), ) } diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index 132b986f55e7f..76429e4bc78e3 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -4,13 +4,9 @@ use alloy_consensus::Typed2718; pub use alloy_evm::EvmEnv; use alloy_evm::FromRecoveredTx; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, U256}; -use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; -use op_revm::{ - OpTransaction, - transaction::{OpTxTr, deposit::DEPOSIT_TRANSACTION_TYPE}, -}; +#[cfg(feature = "optimism")] +use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; use revm::{ Context, Database, Journal, context::{Block, BlockEnv, Cfg, CfgEnv, Transaction, TxEnv}, @@ -236,9 +232,16 @@ pub trait FoundryTransaction: Transaction { /// Sets whether the transaction is a system transaction fn set_system_transaction(&mut self, _is_system_transaction: bool) {} - /// Returns `true` if transaction is of type [`DEPOSIT_TRANSACTION_TYPE`]. + /// Returns `true` if transaction is an Optimism deposit transaction. fn is_deposit(&self) -> bool { - self.tx_type() == DEPOSIT_TRANSACTION_TYPE + #[cfg(feature = "optimism")] + { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE + } + #[cfg(not(feature = "optimism"))] + { + false + } } // Tempo methods @@ -320,188 +323,6 @@ impl FoundryTransaction for TxEnv { } } -impl FoundryTransaction for OpTransaction { - fn set_tx_type(&mut self, tx_type: u8) { - self.base.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.base.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.base.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.base.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.base.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.base.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.base.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.base.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.base.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.base.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.base.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.base.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - OpTxTr::enveloped_tx(self) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.enveloped_tx = Some(bytes); - } - - fn source_hash(&self) -> Option { - OpTxTr::source_hash(self) - } - - fn set_source_hash(&mut self, source_hash: B256) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.source_hash = source_hash; - } - } - - fn mint(&self) -> Option { - OpTxTr::mint(self) - } - - fn set_mint(&mut self, mint: u128) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.mint = Some(mint); - } - } - - fn is_system_transaction(&self) -> bool { - OpTxTr::is_system_transaction(self) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { - self.deposit.is_system_transaction = is_system_transaction; - } - } -} - -impl FoundryTransaction for OpTx { - fn set_tx_type(&mut self, tx_type: u8) { - self.0.set_tx_type(tx_type); - } - - fn set_caller(&mut self, caller: Address) { - self.0.set_caller(caller); - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.0.set_gas_limit(gas_limit); - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.0.set_gas_price(gas_price); - } - - fn set_kind(&mut self, kind: TxKind) { - self.0.set_kind(kind); - } - - fn set_value(&mut self, value: U256) { - self.0.set_value(value); - } - - fn set_data(&mut self, data: Bytes) { - self.0.set_data(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.0.set_nonce(nonce); - } - - fn set_chain_id(&mut self, chain_id: Option) { - self.0.set_chain_id(chain_id); - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.0.set_access_list(access_list); - } - - fn authorization_list_mut( - &mut self, - ) -> &mut Vec> { - self.0.authorization_list_mut() - } - - fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { - self.0.set_gas_priority_fee(gas_priority_fee); - } - - fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} - - fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} - - fn enveloped_tx(&self) -> Option<&Bytes> { - FoundryTransaction::enveloped_tx(&self.0) - } - - fn set_enveloped_tx(&mut self, bytes: Bytes) { - self.0.set_enveloped_tx(bytes); - } - - fn source_hash(&self) -> Option { - FoundryTransaction::source_hash(&self.0) - } - - fn set_source_hash(&mut self, source_hash: B256) { - self.0.set_source_hash(source_hash); - } - - fn mint(&self) -> Option { - FoundryTransaction::mint(&self.0) - } - - fn set_mint(&mut self, mint: u128) { - self.0.set_mint(mint); - } - - fn is_system_transaction(&self) -> bool { - FoundryTransaction::is_system_transaction(&self.0) - } - - fn set_system_transaction(&mut self, is_system_transaction: bool) { - self.0.set_system_transaction(is_system_transaction); - } -} - impl FoundryTransaction for TempoTxEnv { fn set_tx_type(&mut self, tx_type: u8) { self.inner.set_tx_type(tx_type); @@ -687,32 +508,6 @@ impl FromAnyRpcTransaction for TxEnv { } } -impl FromAnyRpcTransaction for OpTx { - fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { - if let Some(envelope) = tx.as_envelope() { - return Ok(Self(OpTransaction:: { - base: TxEnv::from_recovered_tx(envelope, tx.from()), - enveloped_tx: None, - deposit: Default::default(), - })); - } - - // Handle OP deposit transactions from `Unknown` envelope variant. - if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner - && unknown.ty() == DEPOSIT_TX_TYPE_ID - { - let mut fields = unknown.inner.fields.clone(); - fields.insert("from".to_string(), serde_json::to_value(tx.from())?); - let deposit_tx: TxDeposit = fields - .deserialize_into() - .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; - return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); - } - - eyre::bail!("cannot convert unknown transaction type to OpTransaction") - } -} - impl FromAnyRpcTransaction for TempoTxEnv { fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { use alloy_consensus::Transaction as _; @@ -747,22 +542,234 @@ impl FromAnyRpcTransaction for TempoTxEnv { } } +#[cfg(feature = "optimism")] +mod optimism { + use super::*; + use alloy_op_evm::OpTx; + use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; + use op_revm::{OpTransaction, transaction::OpTxTr}; + + impl FoundryTransaction for OpTransaction { + fn set_tx_type(&mut self, tx_type: u8) { + self.base.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.base.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.base.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.base.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.base.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.base.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.base.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.base.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.base.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + OpTxTr::enveloped_tx(self) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.enveloped_tx = Some(bytes); + } + + fn source_hash(&self) -> Option { + OpTxTr::source_hash(self) + } + + fn set_source_hash(&mut self, source_hash: B256) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.source_hash = source_hash; + } + } + + fn mint(&self) -> Option { + OpTxTr::mint(self) + } + + fn set_mint(&mut self, mint: u128) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.mint = Some(mint); + } + } + + fn is_system_transaction(&self) -> bool { + OpTxTr::is_system_transaction(self) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.is_system_transaction = is_system_transaction; + } + } + } + + impl FoundryTransaction for OpTx { + fn set_tx_type(&mut self, tx_type: u8) { + self.0.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.0.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.0.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.0.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.0.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.0.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.0.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.0.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.0.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.0.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.0.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + FoundryTransaction::enveloped_tx(&self.0) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.0.set_enveloped_tx(bytes); + } + + fn source_hash(&self) -> Option { + FoundryTransaction::source_hash(&self.0) + } + + fn set_source_hash(&mut self, source_hash: B256) { + self.0.set_source_hash(source_hash); + } + + fn mint(&self) -> Option { + FoundryTransaction::mint(&self.0) + } + + fn set_mint(&mut self, mint: u128) { + self.0.set_mint(mint); + } + + fn is_system_transaction(&self) -> bool { + FoundryTransaction::is_system_transaction(&self.0) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + self.0.set_system_transaction(is_system_transaction); + } + } + + impl FromAnyRpcTransaction for OpTx { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + return Ok(Self(OpTransaction:: { + base: TxEnv::from_recovered_tx(envelope, tx.from()), + enveloped_tx: None, + deposit: Default::default(), + })); + } + + // Handle OP deposit transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == DEPOSIT_TX_TYPE_ID + { + let mut fields = unknown.inner.fields.clone(); + fields.insert("from".to_string(), serde_json::to_value(tx.from())?); + let deposit_tx: TxDeposit = fields + .deserialize_into() + .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; + return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); + } + + eyre::bail!("cannot convert unknown transaction type to OpTransaction") + } + } +} + #[cfg(test)] mod tests { use std::num::NonZeroU64; use super::*; - use alloy_consensus::{Sealed, Signed, TxEip1559, transaction::Recovered}; + use alloy_consensus::{Signed, TxEip1559, transaction::Recovered}; use alloy_evm::{EthEvmFactory, EvmFactory}; use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; - use alloy_op_evm::OpEvmFactory; use alloy_primitives::Signature; use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo}; use alloy_serde::WithOtherFields; use foundry_evm_hardforks::TempoHardfork; - use op_alloy_consensus::{OpTxEnvelope, transaction::OpTransactionInfo}; - use op_alloy_rpc_types::Transaction as OpRpcTransaction; - use op_revm::OpSpecId; use revm::database::EmptyDB; use tempo_alloy::primitives::{ AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, @@ -793,30 +800,6 @@ mod tests { evm.ctx_mut().set_evm(evm_env); } - #[test] - fn op_evm_foundry_context_ext_implementation() { - let mut evm = - OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); - - // Test EVM Context Block mutation - evm.ctx_mut().block_mut().set_number(U256::from(123)); - assert_eq!(evm.ctx().block().number(), U256::from(123)); - - // Test EVM Context Tx mutation - evm.ctx_mut().tx_mut().set_nonce(99); - assert_eq!(evm.ctx().tx().nonce(), 99); - - // Test EVM Context Cfg mutation - evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; - assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); - - // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env - let tx_env = evm.ctx().tx_clone(); - evm.ctx_mut().set_tx(tx_env); - let evm_env = evm.ctx().evm_clone(); - evm.ctx_mut().set_evm(evm_env); - } - #[test] fn tempo_evm_foundry_context_ext_implementation() { let mut evm = TempoEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default()); @@ -874,23 +857,6 @@ mod tests { assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); } - #[test] - fn from_any_rpc_transaction_for_op() { - let from = Address::random(); - let signed_tx = make_signed_eip1559(); - - // Build the eth TxEnv to compare against op base - let rpc_tx = RpcTransaction::from_transaction( - Recovered::new_unchecked(signed_tx.into(), from), - TransactionInfo::default(), - ); - let any_tx = >::from(rpc_tx); - let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base, expected_base); - } - #[test] fn from_any_rpc_transaction_unknown_envelope_errors() { let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { @@ -915,39 +881,6 @@ mod tests { assert!(result.to_string().contains("unknown transaction type")); } - #[test] - fn from_any_rpc_transaction_for_op_deposit() { - let from = Address::random(); - let source_hash = B256::random(); - let deposit = TxDeposit { - source_hash, - from, - to: TxKind::Call(Address::with_last_byte(0xCC)), - mint: 1111, - value: U256::from(200), - gas_limit: 21000, - is_system_transaction: true, - input: Default::default(), - }; - - // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as AnyRpcTransaction. - let op_rpc_tx = OpRpcTransaction::from_transaction( - Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), - OpTransactionInfo::default(), - ); - let json = serde_json::to_value(&op_rpc_tx).unwrap(); - let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); - - let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); - assert_eq!(op_tx_env.base.caller, from); - assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); - assert_eq!(op_tx_env.base.value, U256::from(200)); - assert_eq!(op_tx_env.base.gas_limit, 21000); - assert_eq!(op_tx_env.deposit.source_hash, source_hash); - assert_eq!(op_tx_env.deposit.mint, Some(1111)); - assert!(op_tx_env.deposit.is_system_transaction); - } - #[test] fn from_any_rpc_transaction_for_tempo_eth_envelope() { let from = Address::random(); @@ -1004,4 +937,89 @@ mod tests { assert_eq!(tx_env.inner.chain_id, Some(42431)); assert_eq!(tx_env.fee_token, fee_token); } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + use alloy_consensus::Sealed; + use alloy_op_evm::{OpEvmFactory, OpTx}; + use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo}; + use op_alloy_rpc_types::Transaction as OpRpcTransaction; + use op_revm::OpSpecId; + + #[test] + fn op_evm_foundry_context_ext_implementation() { + let mut evm = + OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; + assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + #[test] + fn from_any_rpc_transaction_for_op() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base, expected_base); + } + + #[test] + fn from_any_rpc_transaction_for_op_deposit() { + let from = Address::random(); + let source_hash = B256::random(); + let deposit = TxDeposit { + source_hash, + from, + to: TxKind::Call(Address::with_last_byte(0xCC)), + mint: 1111, + value: U256::from(200), + gas_limit: 21000, + is_system_transaction: true, + input: Default::default(), + }; + + // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let op_rpc_tx = OpRpcTransaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), + OpTransactionInfo::default(), + ); + let json = serde_json::to_value(&op_rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base.caller, from); + assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); + assert_eq!(op_tx_env.base.value, U256::from(200)); + assert_eq!(op_tx_env.base.gas_limit, 21000); + assert_eq!(op_tx_env.deposit.source_hash, source_hash); + assert_eq!(op_tx_env.deposit.mint, Some(1111)); + assert!(op_tx_env.deposit.is_system_transaction); + } + } } diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs index 708226be003a2..fc9e9e7d2810f 100644 --- a/crates/evm/core/src/evm/mod.rs +++ b/crates/evm/core/src/evm/mod.rs @@ -10,14 +10,11 @@ use alloy_evm::{ EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap, }; use alloy_network::{Ethereum, Network}; -use alloy_op_evm::OpEvmFactory; use alloy_primitives::{Address, Signature, U256}; use alloy_rlp::Decodable; use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; -use op_alloy_network::Optimism; -use op_revm::OpHaltReason; use revm::{ Database, context::{ @@ -36,10 +33,12 @@ use tempo_evm::evm::TempoEvmFactory; use tempo_revm::TempoHaltReason; pub mod eth; +#[cfg(feature = "optimism")] pub mod op; pub mod tempo; pub use eth::*; +#[cfg(feature = "optimism")] pub use op::*; pub use tempo::*; @@ -75,13 +74,6 @@ impl FoundryEvmNetwork for TempoEvmNetwork { type EvmFactory = TempoEvmFactory; } -#[derive(Clone, Copy, Debug, Default)] -pub struct OpEvmNetwork; -impl FoundryEvmNetwork for OpEvmNetwork { - type Network = Optimism; - type EvmFactory = OpEvmFactory; -} - /// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. pub type EvmFactoryFor = ::EvmFactory; pub type FoundryContextFor<'db, FEN> = @@ -249,15 +241,6 @@ impl IntoInstructionResult for HaltReason { } } -impl IntoInstructionResult for OpHaltReason { - fn into_instruction_result(self) -> InstructionResult { - match self { - Self::Base(eth) => eth.into(), - Self::FailedDeposit => InstructionResult::Stop, - } - } -} - impl IntoInstructionResult for TempoHaltReason { fn into_instruction_result(self) -> InstructionResult { match self { diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs index cb8bf272d9a05..efb74ad3abf50 100644 --- a/crates/evm/core/src/evm/op.rs +++ b/crates/evm/core/src/evm/op.rs @@ -1,6 +1,7 @@ use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; use alloy_op_evm::{OpEvm, OpEvmContext, OpEvmFactory, OpTx}; use foundry_fork_db::DatabaseError; +use op_alloy_network::Optimism; use op_revm::{OpEvm as RevmEvm, OpHaltReason, OpSpecId, OpTransactionError, handler::OpHandler}; use revm::{ context::{ @@ -10,16 +11,33 @@ use revm::{ handler::{EthFrame, EvmTr, FrameResult, Handler, instructions::EthInstructions}, inspector::InspectorHandler, interpreter::{ - FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + FrameInput, InstructionResult, SharedMemory, interpreter::EthInterpreter, + interpreter_action::FrameInit, }, }; use crate::{ FoundryContextExt, FoundryInspectorExt, backend::{DatabaseExt, JournaledState}, - evm::{FoundryEvmFactory, NestedEvm}, + evm::{FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm}, }; +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = OpEvmFactory; +} + +impl IntoInstructionResult for OpHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Base(eth) => eth.into(), + Self::FailedDeposit => InstructionResult::Stop, + } + } +} + type OpEvmHandler<'db, I> = OpHandler, EVMError, EthFrame>; diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index aefa0e2ee9741..2284823047ca6 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -212,13 +212,18 @@ pub struct ForkDbStateSnapshot { } impl ForkDbStateSnapshot { - fn get_storage(&self, address: Address, index: U256) -> Option { - self.local - .cache - .accounts - .get(&address) - .and_then(|account| account.storage.get(&index)) - .copied() + /// Lookup storage in `state_snapshot`, then fall back to the backend (remote RPC). + fn storage_from_snapshot_or_backend( + &self, + address: Address, + index: U256, + ) -> Result { + // Check state_snapshot.storage first (data fetched by SharedBackend / disk cache). + if let Some(val) = self.state_snapshot.storage.get(&address).and_then(|s| s.get(&index)) { + return Ok(*val); + } + // Fall back to the underlying backend (SharedBackend → remote RPC). + DatabaseRef::storage_ref(&self.local, address, index) } } @@ -250,15 +255,9 @@ impl DatabaseRef for ForkDbStateSnapshot { match self.local.cache.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), - }, - }, - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), + None => self.storage_from_snapshot_or_backend(address, index), }, + None => self.storage_from_snapshot_or_backend(address, index), } } @@ -303,4 +302,28 @@ mod tests { assert!(loaded.is_some()); assert_eq!(loaded.unwrap(), info); } + + /// Verifies that `ForkDbStateSnapshot::storage_ref` reads from `state_snapshot.storage` + /// when the slot is missing from `local.cache.accounts`. Without this lookup the call + /// would fall through to the backend and return the unrelated remote value. + #[tokio::test(flavor = "multi_thread")] + async fn fork_db_state_snapshot_reads_storage_from_snapshot() { + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); + let provider = get_http_provider(rpc.clone()); + let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc); + let db = BlockchainDb::new(meta, None); + let backend = SharedBackend::spawn_backend(Arc::new(provider), db, None).await; + + let address = Address::random(); + let slot = U256::from(42u64); + let expected = U256::from(0xdeadbeefu64); + + let mut state_snapshot = StateSnapshot::default(); + state_snapshot.storage.entry(address).or_default().insert(slot, expected); + + let snapshot = ForkDbStateSnapshot { local: CacheDB::new(backend), state_snapshot }; + + let got = DatabaseRef::storage_ref(&snapshot, address, slot).unwrap(); + assert_eq!(got, expected); + } } diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 1b2201a9b8b84..c2edbb9dfd33b 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -5,6 +5,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; + use crate::constants::DEFAULT_CREATE2_DEPLOYER; use alloy_primitives::{Address, map::HashMap}; use auto_impl::auto_impl; diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 82efd4a1aaaaa..ab68eb08821e8 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -137,8 +137,12 @@ impl EvmOpts { /// [`NetworkConfigs::with_chain_id`] to auto-enable the correct network /// (e.g. Tempo, OP Stack) based on the chain ID. pub async fn infer_network_from_fork(&mut self) { + #[cfg(feature = "optimism")] + let already_op = self.networks.is_optimism(); + #[cfg(not(feature = "optimism"))] + let already_op = false; if !self.networks.is_tempo() - && !self.networks.is_optimism() + && !already_op && let Some(ref fork_url) = self.fork_url && let Ok(provider) = self.fork_provider_with_url::(fork_url) && let Ok(chain_id) = provider.get_chain_id().await @@ -474,6 +478,7 @@ mod tests { // Plain anvil (chain id 31337) without tempo flag -> Ethereum (no network flags set). assert!(!evm_opts.networks.is_tempo()); + #[cfg(feature = "optimism")] assert!(!evm_opts.networks.is_optimism()); assert!(!evm_opts.networks.is_celo()); assert_eq!(evm_opts.networks, NetworkConfigs::default()); diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index d2d7b077ee9f0..758604564726e 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -25,3 +25,7 @@ semver.workspace = true tracing.workspace = true rayon.workspace = true solar.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 6bf7163d6e023..dd5138f532074 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -62,3 +62,16 @@ serde.workspace = true uuid.workspace = true rayon.workspace = true tokio.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm-core/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 33152b73dda3c..1932a834ab397 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -17,7 +17,7 @@ use foundry_evm_core::{ use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ BaseCounterExample, BasicTxDetails, CallDetails, CounterExample, FuzzCase, FuzzError, - FuzzFixtures, FuzzTestResult, + FuzzFixtures, FuzzRunMetadata, FuzzTestResult, strategies::{EvmFuzzState, fuzz_calldata, fuzz_calldata_from_state}, }; use foundry_evm_traces::SparsedTraceArena; @@ -71,6 +71,8 @@ struct WorkerState { runs: u32, /// Failure reason if this worker failed failure: Option, + /// Fuzz run metadata that produced the failure. + failure_run: Option, /// Last run timestamp in milliseconds /// /// Used to identify which worker ran last and collect its traces and call breakpoints @@ -93,6 +95,7 @@ impl WorkerState { deprecated_cheatcodes: HashMap::default(), runs: 0, failure: None, + failure_run: None, last_run_timestamp: 0, failed_corpus_replays: 0, } @@ -196,8 +199,14 @@ impl FuzzedExecutor { config: FuzzConfig, persisted_failure: Option, ) -> Self { - let max_workers = - if config.runs == 0 { 0 } else { Ord::max(1, config.runs / MIN_RUNS_PER_WORKER) }; + let run_limit = if config.run.is_some() { 1 } else { config.runs }; + let max_workers = if run_limit == 0 { + 0 + } else if config.run.is_some() { + 1 + } else { + Ord::max(1, run_limit / MIN_RUNS_PER_WORKER) + }; let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } @@ -221,8 +230,9 @@ impl FuzzedExecutor { ) -> Result { let shared_state = SharedFuzzState::new(state, self.config.timeout, early_exit.clone()); - debug!(n = self.num_workers, "spawning workers"); - let workers = (0..self.num_workers) + let worker_ids = self.worker_ids(); + debug!(n = worker_ids.len(), "spawning workers"); + let workers = worker_ids .into_par_iter() .map(|worker_id| { let _guard = tokio_handle.enter(); @@ -364,8 +374,14 @@ impl FuzzedExecutor { } else { vec![] }; + let fuzz = failed_worker.failure_run.unwrap_or_default(); result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + BaseCounterExample::from_fuzz_call(calldata, args, call.traces) + .with_fuzz_metadata(FuzzRunMetadata::new( + fuzz.seed.or(self.config.seed), + fuzz.run, + fuzz.worker, + )), )); } Some(TestCaseError::Reject(reason)) => { @@ -453,16 +469,7 @@ impl FuzzedExecutor { runner_config.cases = worker_runs; let mut runner = if let Some(seed) = self.config.seed { - // For deterministic parallel fuzzing, derive a unique seed for each worker - let worker_seed = if worker_id == 0 { - // Master worker uses the provided seed as is. - seed - } else { - // Derive a worker-specific seed using keccak256(seed || worker_id) - let seed_data = - [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); - U256::from_be_bytes(keccak256(seed_data).0) - }; + let worker_seed = Self::fuzz_worker_seed(seed, worker_id); trace!(target: "forge::test", ?worker_seed, "deterministic seed for worker {worker_id}"); let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &worker_seed.to_be_bytes::<32>()); TestRunner::new_with_rng(runner_config, rng) @@ -470,11 +477,25 @@ impl FuzzedExecutor { TestRunner::new(runner_config) }; - let mut persisted_failure = self.persisted_failure.as_ref().filter(|_| worker_id == 0); + if let Some(target_run) = self.config.run { + for _ in 1..target_run { + if let Err(err) = corpus.new_input(&mut runner, &shared_state.state, func) { + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id + ))); + shared_state.try_claim_failure(worker_id); + return Ok(worker); + } + } + } + + let mut persisted_failure = + self.persisted_failure.as_ref().filter(|_| worker_id == 0 && self.config.run.is_none()); // Offset to stagger corpus syncs across workers; so that workers don't sync at the same // time. - let sync_offset = worker_id as u32 * 100; + let sync_offset = (worker_id as u32).saturating_mul(100); let sync_threshold = SYNC_INTERVAL + sync_offset; let mut runs_since_sync = sync_threshold; // Always sync at the start. let mut last_metrics_report = Instant::now(); @@ -483,11 +504,27 @@ impl FuzzedExecutor { // 2. Worker hasn't reached its specific run limit 'stop: while shared_state.should_continue() && worker.runs < worker_runs { // If counterexample recorded, replay it first, without incrementing runs. - let input = if worker_id == 0 + let (input, fuzz_run) = if worker_id == 0 && let Some(failure) = persisted_failure.take() && failure.calldata.get(..4).is_some_and(|selector| func.selector() == selector) { - failure.calldata.clone() + let seed = failure.fuzz.seed.or(self.config.seed); + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() + && let Some(seed) = seed + { + let run = failure.fuzz.run.unwrap_or(1); + let worker = failure.fuzz.worker.unwrap_or(worker_id as u32) as usize; + cheats.set_seed(Self::fuzz_run_seed(seed, worker, run)); + } + + ( + failure.calldata.clone(), + Some(FuzzRunMetadata::new( + seed, + failure.fuzz.run, + Some(failure.fuzz.worker.unwrap_or(worker_id as u32)), + )), + ) } else { runs_since_sync += 1; if runs_since_sync >= sync_threshold { @@ -503,13 +540,14 @@ impl FuzzedExecutor { runs_since_sync = 0; } + let fuzz_run = self.config.run.unwrap_or(worker.runs + 1); if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() && let Some(seed) = self.config.seed { - cheats.set_seed(seed.wrapping_add(U256::from(worker.runs))); + cheats.set_seed(Self::fuzz_run_seed(seed, worker_id, fuzz_run)); } - match corpus.new_input(&mut runner, &shared_state.state, func) { + let input = match corpus.new_input(&mut runner, &shared_state.state, func) { Ok(input) => input, Err(err) => { worker.failure = Some(TestCaseError::fail(format!( @@ -519,13 +557,24 @@ impl FuzzedExecutor { shared_state.try_claim_failure(worker_id); break 'stop; } - } + }; + + ( + input, + Some(FuzzRunMetadata::new( + self.config.seed, + Some(fuzz_run), + Some(worker_id as u32), + )), + ) }; let mut inc_runs = || { let total_runs = shared_state.increment_runs(); debug_assert!( - shared_state.timer.is_enabled() || total_runs <= self.config.runs, + shared_state.timer.is_enabled() + || total_runs + <= if self.config.run.is_some() { 1 } else { self.config.runs }, "worker runs were not distributed correctly" ); worker.runs += 1; @@ -595,6 +644,7 @@ impl FuzzedExecutor { .. }) => { inc_runs(); + worker.failure_run = fuzz_run; // Only classify magic skip payloads when the revert originates from the // cheatcode address. @@ -656,7 +706,7 @@ impl FuzzedExecutor { /// Determines the number of runs per worker. const fn runs_per_worker(&self, worker_id: usize) -> u32 { let worker_id = worker_id as u32; - let total_runs = self.config.runs; + let total_runs = if self.config.run.is_some() { 1 } else { self.config.runs }; let n = self.num_workers as u32; let runs = total_runs / n; let remainder = total_runs % n; @@ -664,4 +714,29 @@ impl FuzzedExecutor { // assuming `worker_id` is in `0..n`. if worker_id < remainder { runs + 1 } else { runs } } + + /// Returns the worker IDs to execute. + fn worker_ids(&self) -> Vec { + if self.config.run.is_some() { + vec![self.config.worker.unwrap_or(0) as usize] + } else { + (0..self.num_workers).collect() + } + } + + /// Derives the deterministic RNG seed for a fuzz worker. + fn fuzz_worker_seed(seed: U256, worker_id: usize) -> U256 { + if worker_id == 0 { + seed + } else { + let worker_id = worker_id as u32; + let seed_data = [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); + U256::from_be_bytes(keccak256(seed_data).0) + } + } + + /// Derives the deterministic RNG seed for cheatcode randomness in a worker-local run. + fn fuzz_run_seed(seed: U256, worker_id: usize, run: u32) -> U256 { + Self::fuzz_worker_seed(seed, worker_id).wrapping_add(U256::from(run.saturating_sub(1))) + } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 27d8e6a0ed588..e02cdbc393ee6 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -736,7 +736,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { if !msg.is_empty() { msg.push_str(", "); } - msg.push_str(&format!("{}", &corpus_manager.metrics)); + msg.push_str(&format!("{}", corpus_manager.metrics)); } progress.set_message(msg); } diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 62e4e80a73674..5629b17d936da 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -50,3 +50,12 @@ rand.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 44d71fb6deee3..9c3e7d179c7f6 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -33,6 +33,27 @@ pub use strategies::LiteralMaps; mod inspector; pub use inspector::Fuzzer; +/// Metadata needed to reproduce a fuzz run. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct FuzzRunMetadata { + /// Seed used for the worker's input stream. + #[serde(default, rename = "fuzz_seed", skip_serializing_if = "Option::is_none")] + pub seed: Option, + /// 1-based run inside the worker's input stream. + #[serde(default, rename = "fuzz_run", skip_serializing_if = "Option::is_none")] + pub run: Option, + /// Worker that generated the input stream. + #[serde(default, rename = "fuzz_worker", skip_serializing_if = "Option::is_none")] + pub worker: Option, +} + +impl FuzzRunMetadata { + /// Creates metadata for reproducing a fuzz run. + pub const fn new(seed: Option, run: Option, worker: Option) -> Self { + Self { seed, run, worker } + } +} + /// Details of a transaction generated by fuzz strategy for fuzzing a target. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BasicTxDetails { @@ -102,6 +123,9 @@ pub struct BaseCounterExample { /// Whether to display sequence as solidity. #[serde(skip)] pub show_solidity: bool, + /// Fuzz metadata needed to reproduce this counterexample. + #[serde(flatten)] + pub fuzz: FuzzRunMetadata, } impl BaseCounterExample { @@ -137,6 +161,7 @@ impl BaseCounterExample { ), traces, show_solidity, + fuzz: FuzzRunMetadata::default(), }; } } @@ -154,6 +179,7 @@ impl BaseCounterExample { raw_args: None, traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } @@ -176,8 +202,15 @@ impl BaseCounterExample { raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()), traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } + + /// Sets fuzz metadata for reproducing this counterexample. + pub const fn with_fuzz_metadata(mut self, fuzz: FuzzRunMetadata) -> Self { + self.fuzz = fuzz; + self + } } impl fmt::Display for BaseCounterExample { @@ -229,7 +262,7 @@ impl fmt::Display for BaseCounterExample { if let Some(sig) = &self.signature { write!(f, "calldata={sig}")? } else { - write!(f, "calldata={}", &self.calldata)? + write!(f, "calldata={}", self.calldata)? } if let Some(args) = &self.args { diff --git a/crates/evm/hardforks/Cargo.toml b/crates/evm/hardforks/Cargo.toml index 9bf318028487a..68f6fb23bab07 100644 --- a/crates/evm/hardforks/Cargo.toml +++ b/crates/evm/hardforks/Cargo.toml @@ -16,10 +16,14 @@ workspace = true [dependencies] alloy-chains.workspace = true alloy-hardforks = { workspace = true, features = ["serde"] } -alloy-op-hardforks = { workspace = true, features = ["serde"] } +alloy-op-hardforks = { workspace = true, features = ["serde"], optional = true } alloy-rpc-types.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } revm.workspace = true serde = { workspace = true, features = ["derive"] } tempo-chainspec.workspace = true foundry-compilers.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "dep:op-revm"] diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs index 8a29ebb7af4ec..a8e0d51738263 100644 --- a/crates/evm/hardforks/src/lib.rs +++ b/crates/evm/hardforks/src/lib.rs @@ -8,11 +8,13 @@ use std::str::FromStr; use alloy_chains::Chain; use alloy_rpc_types::BlockNumberOrTag; use foundry_compilers::artifacts::EvmVersion; +#[cfg(feature = "optimism")] use op_revm::OpSpecId; use revm::primitives::hardfork::SpecId; use serde::{Deserialize, Serialize}; pub use alloy_hardforks::EthereumHardfork; +#[cfg(feature = "optimism")] pub use alloy_op_hardforks::OpHardfork; pub use tempo_chainspec::hardfork::TempoHardfork; @@ -20,6 +22,7 @@ pub use tempo_chainspec::hardfork::TempoHardfork; #[serde(into = "String")] pub enum FoundryHardfork { Ethereum(EthereumHardfork), + #[cfg(feature = "optimism")] Optimism(OpHardfork), Tempo(TempoHardfork), } @@ -28,6 +31,7 @@ impl From for String { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(h) => format!("optimism:{h}"), FoundryHardfork::Tempo(h) => format!("tempo:{h}"), } @@ -64,6 +68,7 @@ impl FromStr for FoundryHardfork { .map(Self::Ethereum) .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")), + #[cfg(feature = "optimism")] "op" | "optimism" => OpHardfork::from_str(&fork) .map(Self::Optimism) .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")), @@ -83,6 +88,7 @@ impl FoundryHardfork { Self::Ethereum(h) } + #[cfg(feature = "optimism")] pub const fn optimism(h: OpHardfork) -> Self { Self::Optimism(h) } @@ -95,6 +101,7 @@ impl FoundryHardfork { pub fn name(&self) -> String { match self { Self::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] Self::Optimism(h) => format!("{h}"), Self::Tempo(h) => format!("{h}"), } @@ -106,6 +113,7 @@ impl FoundryHardfork { pub const fn namespace(&self) -> Option<&'static str> { match self { Self::Ethereum(_) => None, + #[cfg(feature = "optimism")] Self::Optimism(_) => Some("optimism"), Self::Tempo(_) => Some("tempo"), } @@ -119,6 +127,7 @@ impl FoundryHardfork { if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Ethereum(fork)); } + #[cfg(feature = "optimism")] if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) { return Some(Self::Optimism(fork)); } @@ -143,12 +152,14 @@ impl From for EthereumHardfork { } } +#[cfg(feature = "optimism")] impl From for FoundryHardfork { fn from(value: OpHardfork) -> Self { Self::Optimism(value) } } +#[cfg(feature = "optimism")] impl From for OpHardfork { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -177,12 +188,14 @@ impl From for SpecId { fn from(fork: FoundryHardfork) -> Self { match fork { FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), FoundryHardfork::Tempo(hardfork) => hardfork.into(), } } } +#[cfg(feature = "optimism")] impl From for OpSpecId { fn from(fork: FoundryHardfork) -> Self { match fork { @@ -223,6 +236,7 @@ pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { } /// Map an `OptimismHardfork` enum into its corresponding `OpSpecId`. +#[cfg(feature = "optimism")] pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { match hardfork { OpHardfork::Bedrock => OpSpecId::BEDROCK, @@ -265,6 +279,7 @@ impl FromEvmVersion for SpecId { } } +#[cfg(feature = "optimism")] impl FromEvmVersion for OpSpecId { fn from_evm_version(version: EvmVersion) -> Self { match version { @@ -324,16 +339,6 @@ mod tests { assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA); } - #[test] - fn test_optimism_spec_id_mapping() { - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - - // Test latest hardforks - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); - } - #[test] fn test_tempo_spec_id_mapping() { assert_eq!(SpecId::from(TempoHardfork::Genesis), SpecId::OSAKA); @@ -371,25 +376,40 @@ mod tests { } #[test] - fn test_from_chain_and_timestamp_op_mainnet() { - let op_chain_id = 10; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); + fn test_from_chain_and_timestamp_unknown_chain() { + assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); } - #[test] - fn test_from_chain_and_timestamp_base() { - let base_chain_id = 8453; - assert!(matches!( - FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), - Some(FoundryHardfork::Optimism(_)) - )); - } + #[cfg(feature = "optimism")] + mod optimism { + use super::*; - #[test] - fn test_from_chain_and_timestamp_unknown_chain() { - assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); + #[test] + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); + + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } + + #[test] + fn test_from_chain_and_timestamp_op_mainnet() { + let op_chain_id = 10; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_base() { + let base_chain_id = 8453; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } } } diff --git a/crates/evm/networks/Cargo.toml b/crates/evm/networks/Cargo.toml index a63ed34ba61cf..00c9abf0f90f7 100644 --- a/crates/evm/networks/Cargo.toml +++ b/crates/evm/networks/Cargo.toml @@ -19,7 +19,7 @@ foundry-evm-hardforks.workspace = true alloy-chains.workspace = true alloy-eips.workspace = true alloy-evm.workspace = true -alloy-op-hardforks.workspace = true +alloy-op-hardforks = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = [ "serde", "getrandom", @@ -43,4 +43,8 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } serde.workspace = true [dev-dependencies] -serde_json.workspace = true \ No newline at end of file +serde_json.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "foundry-evm-hardforks/optimism"] diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs index 4672deff353d0..384cee5a7bed3 100644 --- a/crates/evm/networks/src/lib.rs +++ b/crates/evm/networks/src/lib.rs @@ -11,7 +11,6 @@ use alloy_chains::{ }; use alloy_eips::eip1559::BaseFeeParams; use alloy_evm::precompiles::PrecompilesMap; -use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; use alloy_primitives::{Address, ChainId, map::AddressHashMap}; use clap::Parser; use foundry_evm_hardforks::FoundryHardfork; @@ -20,20 +19,52 @@ use std::collections::BTreeMap; pub mod celo; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)] +#[cfg(feature = "optimism")] +mod optimism; + +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + clap::ValueEnum, +)] #[serde(rename_all = "lowercase")] #[clap(rename_all = "lowercase")] pub enum NetworkVariant { #[default] Ethereum, + #[cfg(feature = "optimism")] Optimism, Tempo, } +impl std::str::FromStr for NetworkVariant { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ethereum" => Ok(Self::Ethereum), + #[cfg(feature = "optimism")] + "optimism" => Ok(Self::Optimism), + "tempo" => Ok(Self::Tempo), + _ => Err(format!("unknown network variant: {s}")), + } + } +} + impl NetworkVariant { pub const fn name(&self) -> &'static str { match self { Self::Ethereum => "ethereum", + #[cfg(feature = "optimism")] Self::Optimism => "optimism", Self::Tempo => "tempo", } @@ -50,31 +81,39 @@ impl From for NetworkVariant { fn from(chain_id: ChainId) -> Self { let chain = Chain::from_id(chain_id); if chain.is_tempo() { - Self::Tempo - } else if chain.is_optimism() { - Self::Optimism - } else { - Self::Ethereum + return Self::Tempo; + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::Optimism; } + Self::Ethereum } } -#[derive(Clone, Debug, Default, Parser, Serialize, Deserialize, Copy, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq)] pub struct NetworkConfigs { /// Enable a specific network family. - #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "optimism", "tempo"])] - #[serde(skip_serializing_if = "Option::is_none")] - network: Option, + #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] + #[serde(default)] + pub(crate) network: Option, /// Enable Celo network features. - #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "optimism", "tempo"])] + #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] celo: bool, /// Enable Optimism network features (deprecated: use --network optimism). + #[cfg(feature = "optimism")] #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])] - // Skipped from configs (forge) as there is no feature to be added yet. - #[serde(skip)] - optimism: bool, + // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the + // canonical form is `network = "optimism"`. + #[serde(default)] + pub(crate) optimism: bool, /// Enable Tempo network features (deprecated: use --network tempo). - #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "optimism"])] + #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] + // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the + // canonical form is `network = "tempo"`. #[serde(default)] tempo: bool, /// Whether to bypass prevrandao. @@ -83,11 +122,22 @@ pub struct NetworkConfigs { bypass_prevrandao: bool, } -impl NetworkConfigs { - pub fn with_optimism() -> Self { - Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } +// Custom `Serialize` impl: always emits the *resolved* network as the canonical +// `network = "..."` field, and never emits the legacy `tempo` / `optimism` aliases. This avoids +// confusing output like `network = "tempo"` next to `tempo = false`, and ensures `tempo = true` +// in foundry.toml round-trips as `network = "tempo"`. +impl Serialize for NetworkConfigs { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeStruct; + let mut s = serializer.serialize_struct("NetworkConfigs", 3)?; + s.serialize_field("network", &self.resolved_network())?; + s.serialize_field("celo", &self.celo)?; + s.serialize_field("bypass_prevrandao", &self.bypass_prevrandao)?; + s.end() } +} +impl NetworkConfigs { pub fn with_celo() -> Self { Self { celo: true, ..Default::default() } } @@ -96,11 +146,7 @@ impl NetworkConfigs { Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() } } - pub fn is_optimism(&self) -> bool { - matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) - } - - pub fn is_tempo(&self) -> bool { + pub const fn is_tempo(&self) -> bool { matches!(self.resolved_network(), Some(NetworkVariant::Tempo)) } @@ -109,14 +155,18 @@ impl NetworkConfigs { } /// Returns the resolved network variant, folding legacy flags. - fn resolved_network(&self) -> Option { - self.network.or(if self.optimism { - Some(NetworkVariant::Optimism) - } else if self.tempo { - Some(NetworkVariant::Tempo) - } else { - None - }) + const fn resolved_network(&self) -> Option { + if let Some(n) = self.network { + return Some(n); + } + #[cfg(feature = "optimism")] + if self.optimism { + return Some(NetworkVariant::Optimism); + } + if self.tempo { + return Some(NetworkVariant::Tempo); + } + None } /// Returns the name of the currently active non-Ethereum network, or `None` for plain Ethereum. @@ -132,16 +182,12 @@ impl NetworkConfigs { /// For Optimism networks, returns Canyon parameters if the Canyon hardfork is active /// at the given timestamp, otherwise returns pre-Canyon parameters. pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + #[cfg(feature = "optimism")] if self.is_optimism() { - let op_hardforks = OpChainHardforks::op_mainnet(); - if op_hardforks.is_canyon_active_at_timestamp(timestamp) { - BaseFeeParams::optimism_canyon() - } else { - BaseFeeParams::optimism() - } - } else { - BaseFeeParams::ethereum() + return self.op_base_fee_params(timestamp); } + let _ = timestamp; + BaseFeeParams::ethereum() } pub fn bypass_prevrandao(&self, chain_id: u64) -> bool { @@ -156,21 +202,23 @@ impl NetworkConfigs { pub fn with_chain_id(self, chain_id: u64) -> Self { let chain = Chain::from_id(chain_id); - if self.resolved_network().is_none() { - if chain.is_tempo() { - Self::with_tempo() - } else if chain.is_optimism() { - Self::with_optimism() + if self.resolved_network().is_some() { + return if !self.celo + && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + { + Self::with_celo() } else { self - } - } else if !self.celo - && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) - { - Self::with_celo() - } else { - self + }; + } + if chain.is_tempo() { + return Self::with_tempo(); + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::with_optimism(); } + self } /// Validates `hardfork` against the current `NetworkConfigs` and, if consistent, returns an @@ -190,6 +238,7 @@ impl NetworkConfigs { let network = match hardfork { FoundryHardfork::Ethereum(_) => self, FoundryHardfork::Tempo(_) => Self::with_tempo(), + #[cfg(feature = "optimism")] FoundryHardfork::Optimism(_) => Self::with_optimism(), }; @@ -225,6 +274,21 @@ impl NetworkConfigs { } } +impl From for NetworkConfigs { + fn from(network: NetworkVariant) -> Self { + match network { + NetworkVariant::Ethereum => Self::default(), + NetworkVariant::Tempo => { + Self { network: Some(network), tempo: true, ..Default::default() } + } + #[cfg(feature = "optimism")] + NetworkVariant::Optimism => { + Self { network: Some(network), optimism: true, ..Default::default() } + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -236,17 +300,6 @@ mod tests { let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() }; let via_old = NetworkConfigs { tempo: true, ..Default::default() }; assert_eq!(via_new.is_tempo(), via_old.is_tempo()); - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.active_network_name(), via_old.active_network_name()); - } - - #[test] - fn new_optimism_flag_equivalent_to_legacy() { - let via_new = - NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; - let via_old = NetworkConfigs { optimism: true, ..Default::default() }; - assert_eq!(via_new.is_optimism(), via_old.is_optimism()); - assert_eq!(via_new.is_tempo(), via_old.is_tempo()); assert_eq!(via_new.active_network_name(), via_old.active_network_name()); } @@ -258,31 +311,11 @@ mod tests { assert_eq!(cfg.active_network_name(), Some("tempo")); } - #[test] - fn active_network_name_optimism() { - let cfg = NetworkConfigs::with_optimism(); - assert_eq!(cfg.active_network_name(), Some("optimism")); - } - #[test] fn active_network_name_default_is_none() { assert_eq!(NetworkConfigs::default().active_network_name(), None); } - // --- new flag takes precedence over legacy flag --- - - #[test] - fn new_flag_wins_over_legacy_when_both_set() { - // --network optimism --tempo: network field wins - let cfg = NetworkConfigs { - network: Some(NetworkVariant::Optimism), - tempo: true, - ..Default::default() - }; - assert!(cfg.is_optimism()); - assert!(!cfg.is_tempo()); - } - // --- Serde round-trip --- #[test] @@ -291,16 +324,6 @@ mod tests { let json = serde_json::to_string(&original).unwrap(); let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); assert!(restored.is_tempo()); - assert!(!restored.is_optimism()); - } - - #[test] - fn serde_roundtrip_optimism() { - let original = NetworkConfigs::with_optimism(); - let json = serde_json::to_string(&original).unwrap(); - let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); - assert!(restored.is_optimism()); - assert!(!restored.is_tempo()); } #[test] @@ -311,13 +334,71 @@ mod tests { assert!(cfg.is_tempo()); } + #[test] + fn serde_serializes_legacy_alias_as_canonical_network() { + // Legacy `tempo = true` should serialize as the canonical `network = "tempo"`, + // and the legacy `tempo` / `optimism` keys must not appear in the output. + let cfg = NetworkConfigs { tempo: true, ..Default::default() }; + let json = serde_json::to_value(cfg).unwrap(); + assert_eq!(json["network"], serde_json::json!("tempo")); + assert!(json.get("tempo").is_none(), "legacy `tempo` key should not be serialized"); + assert!(json.get("optimism").is_none(), "legacy `optimism` key should not be serialized"); + } + #[test] fn serde_new_network_field_deserialized() { let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#; let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap(); assert!(cfg_tempo.is_tempo()); - let json_optimism = r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; - let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); - assert!(cfg_optimism.is_optimism()); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + + #[test] + fn new_optimism_flag_equivalent_to_legacy() { + let via_new = + NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; + let via_old = NetworkConfigs { optimism: true, ..Default::default() }; + assert_eq!(via_new.is_optimism(), via_old.is_optimism()); + assert_eq!(via_new.is_tempo(), via_old.is_tempo()); + assert_eq!(via_new.active_network_name(), via_old.active_network_name()); + } + + #[test] + fn active_network_name_optimism() { + let cfg = NetworkConfigs::with_optimism(); + assert_eq!(cfg.active_network_name(), Some("optimism")); + } + + #[test] + fn new_flag_wins_over_legacy_when_both_set() { + // --network optimism --tempo: network field wins + let cfg = NetworkConfigs { + network: Some(NetworkVariant::Optimism), + tempo: true, + ..Default::default() + }; + assert!(cfg.is_optimism()); + assert!(!cfg.is_tempo()); + } + + #[test] + fn serde_roundtrip_optimism() { + let original = NetworkConfigs::with_optimism(); + let json = serde_json::to_string(&original).unwrap(); + let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); + assert!(restored.is_optimism()); + assert!(!restored.is_tempo()); + } + + #[test] + fn serde_optimism_field_deserialized() { + let json_optimism = + r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; + let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); + assert!(cfg_optimism.is_optimism()); + } } } diff --git a/crates/evm/networks/src/optimism.rs b/crates/evm/networks/src/optimism.rs new file mode 100644 index 0000000000000..5fffa38a333c7 --- /dev/null +++ b/crates/evm/networks/src/optimism.rs @@ -0,0 +1,25 @@ +//! Optimism-specific extensions for [`NetworkConfigs`] and related helpers. + +use crate::{NetworkConfigs, NetworkVariant}; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; + +impl NetworkConfigs { + pub fn with_optimism() -> Self { + Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } + } + + pub const fn is_optimism(&self) -> bool { + matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) + } + + /// Optimism-specific base fee parameters, picking Canyon vs pre-Canyon based on `timestamp`. + pub(crate) fn op_base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + let op_hardforks = OpChainHardforks::op_mainnet(); + if op_hardforks.is_canyon_active_at_timestamp(timestamp) { + BaseFeeParams::optimism_canyon() + } else { + BaseFeeParams::optimism() + } + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 90d2db724cebc..73d64d3ab5d07 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -50,3 +50,7 @@ tempfile.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true yansi.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index bad5c577bc69e..b6f11772620f9 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -26,3 +26,7 @@ foundry-test-utils.workspace = true toml.workspace = true snapbox.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 89a9bf152c8c2..4b986017b71dd 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -711,7 +711,7 @@ impl<'sess> State<'sess, '_> { // Merge the lines and let the wrapper handle breaking if needed let merged_line = format!( "{current_line} {next_content}", - next_content = &next_line[prefix.len()..].trim_start() + next_content = next_line[prefix.len()..].trim_start() ); result.push(merged_line); diff --git a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol index 25074229db558..26809a9418cf0 100644 --- a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol @@ -51,3 +51,9 @@ contract AnotherContract is { } contract WithLayoutAndBase layout at 69 is Base { } + +contract ERC7201Short layout at erc7201("s") is Base { } + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base { } + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") is Base { } diff --git a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol index 990845d3d1349..9dca04b11e25c 100644 --- a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol @@ -62,3 +62,12 @@ contract AnotherContract is {} contract WithLayoutAndBase layout at 69 is Base {} + +contract ERC7201Short layout at erc7201("s") is Base {} + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") + is + Base +{} diff --git a/crates/fmt/testdata/ContractDefinition/fmt.sol b/crates/fmt/testdata/ContractDefinition/fmt.sol index 93cddcd2c6a20..afe196dbf3da9 100644 --- a/crates/fmt/testdata/ContractDefinition/fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/fmt.sol @@ -57,3 +57,12 @@ contract AnotherContract is {} contract WithLayoutAndBase layout at 69 is Base {} + +contract ERC7201Short layout at erc7201("s") is Base {} + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") + is + Base +{} diff --git a/crates/fmt/testdata/ContractDefinition/original.sol b/crates/fmt/testdata/ContractDefinition/original.sol index c0aa88a7d6d17..ed66f2c5728d2 100644 --- a/crates/fmt/testdata/ContractDefinition/original.sol +++ b/crates/fmt/testdata/ContractDefinition/original.sol @@ -50,3 +50,6 @@ contract AnotherContract is { } contract WithLayoutAndBase layout at 69 is Base {} +contract ERC7201Short layout at erc7201("s") is Base {} +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") is Base {} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 064834d248d5f..4e83eb168abca 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true @@ -117,7 +118,7 @@ tempfile.workspace = true alloy-signer-local.workspace = true [features] -default = ["jemalloc", "asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] @@ -126,3 +127,15 @@ aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cli/optimism", + "forge-script/optimism", + "forge-verify/optimism", + "forge-doc/optimism", + "forge-fmt/optimism", + "forge-lint/optimism", + "forge-sol-macro-gen/optimism", +] diff --git a/crates/forge/assets/tempo/MailTemplate.s.sol b/crates/forge/assets/tempo/MailTemplate.s.sol index 27512efe4d5ec..45006f7cd0e06 100644 --- a/crates/forge/assets/tempo/MailTemplate.s.sol +++ b/crates/forge/assets/tempo/MailTemplate.s.sol @@ -14,7 +14,7 @@ contract MailScript is Script { function run(string memory salt) public { vm.startBroadcast(); - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); ITIP20 token = ITIP20( diff --git a/crates/forge/assets/tempo/MailTemplate.t.sol b/crates/forge/assets/tempo/MailTemplate.t.sol index b1749db5df0bf..19760303860a1 100644 --- a/crates/forge/assets/tempo/MailTemplate.t.sol +++ b/crates/forge/assets/tempo/MailTemplate.t.sol @@ -17,7 +17,7 @@ contract MailTest is Test { address public constant BOB = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); function setUp() public virtual { - address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.ALPHA_USD_ADDRESS); + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); token = ITIP20( diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index ea034bce87185..b8ce2a9b945b1 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -87,8 +87,11 @@ impl CoverageArgs { config = self.load_config()?; } - // Set fuzz seed so coverage reports are deterministic - config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so coverage reports are deterministic, + // but allow the user to override it via `--fuzz-seed` or `[fuzz] seed` in config. + if config.fuzz.seed.is_none() { + config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let (paths, mut output) = { let (project, output) = self.build(&config)?; diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 765bb64f95fdd..623723317cdcb 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -13,7 +13,10 @@ use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, - utils::{LoadConfig, find_contract_artifacts, read_constructor_args_file}, + utils::{ + LoadConfig, ResolvedLane, find_contract_artifacts, maybe_print_resolved_lane, + read_constructor_args_file, resolve_lane, + }, }; use foundry_common::{ FoundryTransactionBuilder, @@ -193,6 +196,12 @@ impl CreateArgs { constructor_args.as_deref().unwrap_or(&self.constructor_args), )? } else { + if !self.constructor_args.is_empty() || self.constructor_args_path.is_some() { + sh_warn!( + "`{}` has no constructor; ignoring provided constructor arguments", + self.contract.name + )?; + } vec![] }; @@ -203,6 +212,11 @@ impl CreateArgs { self.tx.tempo.key_id = Some(ak.key_address); } + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `self.tx.tempo.nonce_key` from the lane. + // Must happen before `self.deploy(...)` so `TempoOpts::apply` picks up the nonce_key. + let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?; + // Whether to broadcast the transaction or not let dry_run = !self.broadcast; @@ -223,6 +237,7 @@ impl CreateArgs { dry_run, None, Some(browser), + resolved_lane, ) .await } else if self.unlocked { @@ -239,6 +254,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } else if let Some(ak) = access_key { @@ -259,6 +275,7 @@ impl CreateArgs { dry_run, Some((signer, ak)), None, + resolved_lane, ) .await } else { @@ -282,6 +299,7 @@ impl CreateArgs { dry_run, None, None, + resolved_lane, ) .await } @@ -362,6 +380,7 @@ impl CreateArgs { dry_run: bool, tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, browser_signer: Option>, + resolved_lane: Option, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, @@ -398,7 +417,7 @@ impl CreateArgs { // If Tempo chain fee token must be set if chain.is_tempo() { - if let Some(fee_token) = self.tx.tempo.fee_token { + if let Some(fee_token) = self.tx.tempo.common.fee_token { deployer.tx.set_fee_token(fee_token); } else { deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); @@ -408,15 +427,18 @@ impl CreateArgs { // Apply user-provided gas, fee, nonce, and Tempo options. self.tx.apply::(&mut deployer.tx, is_legacy); - // For keychain mode, set key_id and nonce_key before gas estimation. // Convert the CREATE into an AA-compatible call entry since Tempo AA // transactions use a `calls` list instead of `to`+`input`. + if chain.is_tempo() { + deployer.tx.convert_create_to_call(); + } + + // For keychain mode, set key_id and nonce_key before gas estimation. if let Some((_, ref ak)) = tempo_keychain { deployer.tx.set_key_id(ak.key_address); if deployer.tx.nonce_key().is_none() { deployer.tx.set_nonce_key(U256::ZERO); } - deployer.tx.convert_create_to_call(); } // Fetch defaults from provider for values not specified by user. @@ -424,6 +446,20 @@ impl CreateArgs { deployer.tx.set_nonce(provider.get_transaction_count(deployer_address).await?); } + maybe_print_resolved_lane(resolved_lane.as_ref(), deployer.tx.nonce().unwrap_or_default())?; + + if let Some((_, ref ak)) = tempo_keychain { + deployer + .tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } + // set access list if specified if let Some(access_list) = match self.tx.access_list { None => None, @@ -500,6 +536,11 @@ impl CreateArgs { return Ok(()); } + let tempo_sponsor = self.tx.tempo.sponsor_config().await?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut deployer.tx, deployer_address).await?; + } + // Deploy the actual contract let (deployed_contract, receipt) = if let Some(browser) = browser_signer { // Browser wallet signs and sends the transaction diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index c8dc2ba72aae1..7c6fb51ce3266 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -99,8 +99,11 @@ impl GasSnapshotArgs { } pub async fn run(mut self) -> Result<()> { - // Set fuzz seed so gas snapshots are deterministic - self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so gas snapshots are deterministic, + // but allow the user to override it via `--fuzz-seed`. + if self.test.fuzz_seed.is_none() { + self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } let outcome = self.test.compile_and_run().await?; outcome.ensure_ok(false)?; diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index da300c429e37e..dd8f3afd56197 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -3,7 +3,7 @@ use crate::{ MultiContractRunner, MultiContractRunnerBuilder, decode::decode_console_logs, gas_report::GasReport, - multi_runner::matches_artifact, + multi_runner::{MultiNetworkConfig, matches_artifact}, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ CallTraceDecoderBuilder, InternalTraceMode, TraceKind, @@ -31,7 +31,7 @@ use foundry_compilers::{ utils::source_files_iter, }; use foundry_config::{ - Config, figment, + Config, InlineConfig, figment, figment::{ Metadata, Profile, Provider, value::{Dict, Map}, @@ -39,10 +39,11 @@ use foundry_config::{ filter::GlobMatcher, }; use foundry_debugger::Debugger; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ core::evm::{ - BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, SpecFor, TempoEvmNetwork, - TxEnvFor, + BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor, }, opts::EvmOpts, traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, @@ -169,6 +170,14 @@ pub struct TestArgs { #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, + /// Run only the fuzz case at the given 1-based run index. + #[arg(long, env = "FOUNDRY_FUZZ_RUN", value_name = "RUN")] + pub fuzz_run: Option, + + /// Run the fuzz case from the given worker. Requires `--fuzz-run`. + #[arg(long, env = "FOUNDRY_FUZZ_WORKER", value_name = "WORKER", requires = "fuzz_run")] + pub fuzz_worker: Option, + /// Timeout for each fuzz run in seconds. #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] pub fuzz_timeout: Option, @@ -301,6 +310,10 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, coverage: bool, ) -> Result { + if config.fuzz.run == Some(0) { + bail!("`fuzz.run` must be greater than 0"); + } + // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { evm_opts.isolate = true; @@ -342,40 +355,80 @@ impl TestArgs { // Auto-detect network from fork chain ID when not explicitly configured. evm_opts.infer_network_from_fork().await; - // Dispatch based on network type. - let (libraries, mut outcome) = if evm_opts.networks.is_tempo() { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? - } else if evm_opts.networks.is_optimism() { - self.build_and_run_tests::( + // Parse inline config early to detect per-test network annotations. + let inline_config = InlineConfig::new_parsed(output, &config)?; + let override_networks = inline_config.referenced_override_networks(&config.profile); + + let (libraries, mut outcome) = if override_networks.is_empty() { + // Single-pass: no per-test network overrides, use global network setting. + self.dispatch_network( + &evm_opts, config, - evm_opts, + evm_opts.clone(), output, filter, coverage, should_debug, decode_internal, + MultiNetworkConfig::default(), ) .await? } else { - self.build_and_run_tests::( - config, - evm_opts, - output, - filter, - coverage, - should_debug, - decode_internal, - ) - .await? + // Multi-pass: run each distinct network separately and merge results. + let all_override_networks = override_networks.clone(); + let multi_pass_timer = Instant::now(); + + // Default pass: global network, runs tests without an explicit network annotation. + let (libraries, mut outcome) = self + .dispatch_network( + &evm_opts, + config.clone(), + evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: None, + }, + ) + .await?; + + // Override passes: one per annotated network. + for &network in &override_networks { + let mut pass_evm_opts = evm_opts.clone(); + pass_evm_opts.networks = network.into(); + let (_, pass_outcome) = self + .dispatch_network( + &pass_evm_opts, + config.clone(), + pass_evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: Some(network), + }, + ) + .await?; + merge_outcomes(&mut outcome, pass_outcome); + } + + // Print the merged summary (per-pass summaries are suppressed in `run_tests_inner`). + if !self.summary && !shell::is_json() { + sh_println!("{}", outcome.summary(multi_pass_timer.elapsed()))?; + } + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; + } + + (libraries, outcome) }; if should_draw { @@ -461,6 +514,7 @@ impl TestArgs { coverage: bool, should_debug: bool, decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, ) -> eyre::Result<(Libraries, TestOutcome)> { let verbosity = evm_opts.verbosity; let (evm_env, tx_env, fork_block) = @@ -476,6 +530,7 @@ impl TestArgs { .enable_isolation(evm_opts.isolate) .fail_fast(self.fail_fast) .set_coverage(coverage) + .with_multi_network(multi_network) .build::(output, evm_env, tx_env, evm_opts)?; let libraries = runner.libraries.clone(); @@ -483,6 +538,62 @@ impl TestArgs { Ok((libraries, outcome)) } + /// Dispatches `build_and_run_tests` to the correct network type based on `evm_opts.networks`. + #[allow(clippy::too_many_arguments)] + async fn dispatch_network( + &self, + dispatch_opts: &EvmOpts, + config: Config, + evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + should_debug: bool, + decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, + ) -> eyre::Result<(Libraries, TestOutcome)> { + if dispatch_opts.networks.is_tempo() { + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } else { + #[cfg(feature = "optimism")] + if dispatch_opts.networks.is_optimism() { + return self + .build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await; + } + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } + } + /// Run all tests that matches the filter predicate from a test runner async fn run_tests_inner( &self, @@ -586,6 +697,11 @@ impl TestArgs { let libraries = runner.libraries.clone(); + // Capture multi-pass state before moving `runner` into the spawn task. + // In multi-pass mode the per-pass summary is suppressed; the merged summary is + // printed once by the caller after all passes complete. + let is_multi_pass = !runner.tcfg.multi_network.all_override_networks.is_empty(); + // Run tests in a streaming fashion. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); @@ -643,6 +759,13 @@ impl TestArgs { let tests = &mut suite_result.test_results; let has_tests = !tests.is_empty(); + // In multi-pass (per-test network override) mode, skip suites that contributed no + // tests to this pass so we don't emit a stray blank line in the suite header or + // pollute the outcome with empty entries. + if is_multi_pass && !has_tests && suite_result.warnings.is_empty() { + continue; + } + // Clear the addresses and labels from previous test. decoder.clear_addresses(); @@ -812,9 +935,8 @@ impl TestArgs { // // Exiting early with code 1 if differences are found. if self.gas_snapshot_check.unwrap_or(config.gas_snapshot_check) { - let differences_found = gas_snapshots.clone().into_iter().fold( - false, - |mut found, (group, snapshots)| { + let differences_found = + gas_snapshots.iter().fold(false, |mut found, (group, snapshots)| { // If the snapshot file doesn't exist, we can't compare so we skip. if !&config.snapshots.join(format!("{group}.json")).exists() { return found; @@ -852,8 +974,7 @@ impl TestArgs { } found - }, - ); + }); if differences_found { sh_eprintln!()?; @@ -875,13 +996,13 @@ impl TestArgs { fs::create_dir_all(&config.snapshots)?; // Write gas snapshots to disk per group. - gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + for (group, snapshots) in &gas_snapshots { fs::write_pretty_json_file( &config.snapshots.join(format!("{group}.json")), &snapshots, ) .expect("Failed to write gas snapshots to disk"); - }); + } } } @@ -905,17 +1026,17 @@ impl TestArgs { if let Some(gas_report) = gas_report { let finalized = gas_report.finalize(); - sh_println!("{}", &finalized)?; + sh_println!("{finalized}")?; outcome.gas_report = Some(finalized); } - if !self.summary && !shell::is_json() { + if !is_multi_pass && !self.summary && !shell::is_json() { sh_println!("{}", outcome.summary(duration))?; } - if self.summary && !outcome.results.is_empty() { + if !is_multi_pass && self.summary && !outcome.results.is_empty() { let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); - sh_println!("{}", &summary_report)?; + sh_println!("{summary_report}")?; } // Reattach the task. @@ -982,6 +1103,12 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_run) = self.fuzz_run { + fuzz_dict.insert("run".to_string(), fuzz_run.into()); + } + if let Some(fuzz_worker) = self.fuzz_worker { + fuzz_dict.insert("worker".to_string(), fuzz_worker.into()); + } if let Some(fuzz_timeout) = self.fuzz_timeout { fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); } @@ -1025,6 +1152,29 @@ fn list( Ok(TestOutcome::empty(Some(runner.known_contracts), false)) } +/// Merges `other` into `base` by extending suite results. +/// +/// For suites that appear in both, test results are combined (function-level pass routing ensures +/// each function appears in exactly one pass, so there are no key conflicts in practice). +fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { + for (suite_id, other_suite) in other.results { + match base.results.entry(suite_id) { + std::collections::btree_map::Entry::Vacant(e) => { + e.insert(other_suite); + } + std::collections::btree_map::Entry::Occupied(mut e) => { + let base_suite = e.get_mut(); + base_suite.test_results.extend(other_suite.test_results); + base_suite.warnings.extend(other_suite.warnings); + base_suite.duration = base_suite.duration.max(other_suite.duration); + } + } + } + if let Some(decoder) = other.last_run_decoder { + base.last_run_decoder = Some(decoder); + } +} + /// Load persisted filter (with last test run failures) from file. fn last_run_failures(config: &Config) -> Option { match fs::read_to_string(&config.test_failures_file) { @@ -1133,6 +1283,14 @@ mod tests { assert!(args.fuzz_seed.is_some()); } + #[test] + fn fuzz_run() { + let args: TestArgs = + TestArgs::parse_from(["foundry-cli", "--fuzz-run", "10", "--fuzz-worker", "2"]); + assert_eq!(args.fuzz_run, Some(10)); + assert_eq!(args.fuzz_worker, Some(2)); + } + #[test] fn extract_chain() { let test = |arg: &str, expected: Chain| { diff --git a/crates/forge/src/cmd/test/summary.rs b/crates/forge/src/cmd/test/summary.rs index f8a72272af53c..a0123e896d0bf 100644 --- a/crates/forge/src/cmd/test/summary.rs +++ b/crates/forge/src/cmd/test/summary.rs @@ -25,9 +25,9 @@ impl TestSummaryReport { impl Display for TestSummaryReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "{}", self.format_json_output(&self.is_detailed, &self.outcome))?; } else { - writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; + writeln!(f, "\n{}", self.format_table_output(&self.is_detailed, &self.outcome))?; } Ok(()) } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 6c93dc03b28b5..58b11d98874ed 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -146,7 +146,7 @@ impl GasReport { impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { if shell::is_json() { - writeln!(f, "{}", &self.format_json_output())?; + writeln!(f, "{}", self.format_json_output())?; } else { for (name, contract) in &self.contracts { if contract.functions.is_empty() { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 88bbc6156c812..675f0c3e6c99c 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -27,6 +27,7 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; +use foundry_evm_networks::NetworkVariant; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; @@ -280,6 +281,25 @@ impl MultiContractRunner { } } +/// Tracks network assignment across a multi-network test run. +/// +/// When inline config specifies different networks for different tests, the runner performs one +/// pass per distinct network. This struct encodes which pass we're in so each `ContractRunner` +/// can skip tests that belong to a different pass. +/// +/// Default (empty `all_override_networks`, `None` pass) = single-pass mode, every test runs. +#[derive(Clone, Debug, Default)] +pub struct MultiNetworkConfig { + /// All networks explicitly referenced in inline config annotations across the whole suite. + /// Empty means single-pass mode (no per-test network overrides present). + pub all_override_networks: Vec, + /// The network this pass is responsible for. + /// `None` = default pass: runs tests *without* an explicit network annotation (or annotated + /// with a network not in `all_override_networks`). + /// `Some(v)` = override pass: runs only tests annotated with exactly `v`. + pub pass_network: Option, +} + /// Configuration for the test runner. /// /// This is modified after instantiation through inline config. @@ -311,6 +331,9 @@ pub struct TestRunnerConfig { pub isolation: bool, /// Whether to exit early on test failure or if test run interrupted. pub early_exit: EarlyExit, + + /// Multi-network pass configuration. Default = single-pass mode. + pub multi_network: MultiNetworkConfig, } impl TestRunnerConfig { @@ -423,6 +446,8 @@ pub struct MultiContractRunnerBuilder { pub isolation: bool, /// Whether to exit early on test failure. pub fail_fast: bool, + /// Multi-network pass configuration. + pub multi_network: MultiNetworkConfig, } impl MultiContractRunnerBuilder { @@ -437,6 +462,7 @@ impl MultiContractRunnerBuilder { isolation: Default::default(), decode_internal: Default::default(), fail_fast: false, + multi_network: Default::default(), } } @@ -470,6 +496,11 @@ impl MultiContractRunnerBuilder { self } + pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self { + self.multi_network = multi_network; + self + } + pub const fn fail_fast(mut self, fail_fast: bool) -> Self { self.fail_fast = fail_fast; self @@ -594,6 +625,7 @@ impl MultiContractRunnerBuilder { inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, early_exit: EarlyExit::new(self.fail_fast), + multi_network: self.multi_network, config: self.config, }, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index d924c416759a2..7feaf35254636 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -109,6 +109,25 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { } } + /// Returns `true` if `func` should run in the current multi-network pass. + /// + /// In single-pass mode (no inline network overrides) every function passes. + /// In multi-pass mode: + /// - Default pass (`pass_network = None`): includes functions *without* an override annotation. + /// - Override pass (`pass_network = Some(v)`): includes only functions annotated with `v`. + fn function_matches_network_pass(&self, func: &Function) -> bool { + let multi = &self.mcr.tcfg.multi_network; + if multi.all_override_networks.is_empty() { + return true; + } + let profile = &self.tcfg.config.profile; + let func_network = self.mcr.inline_config.network_for(profile, self.name, &func.name); + match &multi.pass_network { + None => func_network.is_none_or(|n| !multi.all_override_networks.contains(&n)), + Some(target) => func_network.as_ref() == Some(target), + } + } + /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { @@ -380,6 +399,7 @@ impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { .abi .functions() .filter(|func| filter.matches_test_function(func)) + .filter(|func| self.function_matches_network_pass(func)) .collect::>(); debug!( "Found {} test functions out of {} in {:?}", @@ -826,7 +846,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { ); if let Some(ref progress) = progress { - progress.set_prefix(format!("{}\n{warn}\n", &func.name)); + progress.set_prefix(format!("{}\n{warn}\n", func.name)); } else { let _ = sh_warn!("{warn}"); } @@ -1052,7 +1072,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { self.cr.name, &func.name, fuzz_config.timeout, - fuzz_config.runs, + if fuzz_config.run.is_some() { 1 } else { fuzz_config.runs }, ); let state = self.build_fuzz_state(false); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index d3f1503faf569..5d8378c50c9ac 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -916,11 +916,18 @@ Installing tempo-std in [..] (url: https://github.com/tempoxyz/tempo-std, tag: N assert!(prj.root().join("foundry.toml").exists()); - // Verify foundry.toml contains `tempo = true` so subsequent commands auto-detect the network. + // Verify foundry.toml contains `network = "tempo"` so subsequent commands auto-detect the + // network. let foundry_toml = std::fs::read_to_string(prj.root().join("foundry.toml")).unwrap(); assert!( - foundry_toml.contains("tempo = true"), - "foundry.toml should contain `tempo = true`, got:\n{foundry_toml}" + foundry_toml.contains("network = \"tempo\""), + "foundry.toml should contain `network = \"tempo\"`, got:\n{foundry_toml}" + ); + assert!( + foundry_toml.contains("[rpc_endpoints]") + && foundry_toml.contains("tempo = \"https://rpc.tempo.xyz/\"") + && foundry_toml.contains("moderato = \"https://rpc.moderato.tempo.xyz/\""), + "foundry.toml should contain tempo rpc_endpoints, got:\n{foundry_toml}" ); assert!(prj.root().join("lib/forge-std").exists()); @@ -1816,7 +1823,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -1830,7 +1837,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -1844,7 +1851,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -1863,7 +1870,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1879,7 +1886,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -1895,7 +1902,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1921,7 +1928,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -1935,7 +1942,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -1949,7 +1956,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -1968,7 +1975,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1984,7 +1991,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2000,7 +2007,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2026,7 +2033,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2040,7 +2047,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2054,7 +2061,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2073,7 +2080,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2089,7 +2096,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2105,7 +2112,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2134,7 +2141,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2148,7 +2155,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2162,7 +2169,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2181,7 +2188,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2197,7 +2204,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2213,7 +2220,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2246,7 +2253,7 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2265,7 +2272,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2293,7 +2300,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2312,7 +2319,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2340,7 +2347,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2359,7 +2366,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2395,7 +2402,7 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2409,7 +2416,7 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2428,7 +2435,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2444,7 +2451,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2476,7 +2483,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2490,7 +2497,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2509,7 +2516,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2525,7 +2532,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2565,7 +2572,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2579,7 +2586,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| @@ -2593,7 +2600,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| @@ -2622,7 +2629,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2638,7 +2645,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2654,7 +2661,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2993,7 +3000,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132459 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| @@ -3016,7 +3023,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", "deployment": { - "gas": 132459, + "gas": 132471, "size": 396 }, "functions": { @@ -3106,7 +3113,7 @@ contract NestedDeploy is Test { +============================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| 328961 | 1163 | | | | | +| 328949 | 1163 | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | | | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| @@ -3161,7 +3168,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/NestedDeployTest.sol:Parent", "deployment": { - "gas": 328961, + "gas": 328949, "size": 1163 }, "functions": { @@ -3918,7 +3925,7 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 156813 | 509 | | | | | +| 156801 | 509 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| @@ -3942,7 +3949,7 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { |-----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| setUp | 218890 | 218890 | 218890 | 218890 | 1 | |-----------------------------------------+-----------------+--------+--------+--------+---------| | test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | ╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -3960,7 +3967,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) | src/Counter.sol:Counter Contract | | | | | | |----------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 156813 | 509 | | | | | +| 156801 | 509 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | | increment | 43482 | 43482 | 43482 | 43482 | 1 | @@ -3973,7 +3980,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) | 1544498 | 7573 | | | | | | | | | | | | | Function Name | Min | Avg | Median | Max | # Calls | -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| setUp | 218890 | 218890 | 218890 | 218890 | 1 | | test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | @@ -3990,7 +3997,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "src/Counter.sol:Counter", "deployment": { - "gas": 156813, + "gas": 156801, "size": 509 }, "functions": { @@ -4026,10 +4033,10 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "functions": { "setUp()": { "calls": 1, - "min": 218902, - "mean": 218902, - "median": 218902, - "max": 218902 + "min": 218890, + "mean": 218890, + "median": 218890, + "max": 218890 }, "test_Increment()": { "calls": 1, diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index dca88ad1c2f63..3eebca475a781 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -112,7 +112,6 @@ create2_deployer = "0x4e59b44847b379578588920ca78fbf26c0b4956c" assertions_revert = true legacy_assertions = false celo = false -tempo = false bypass_prevrandao = false transaction_timeout = 120 additional_compiler_profiles = [] @@ -578,6 +577,32 @@ forgetest_init!(can_get_evm_opts, |prj, _cmd| { } }); +// Regression test for : +// the bare `ETH_RPC_URL` env var must NOT cause `forge` commands to set +// `eth_rpc_url` (which would silently fork all `forge test` runs). +// Only `--rpc-url`, `foundry.toml`, the `FOUNDRY_ETH_RPC_URL` env var, or +// cheatcodes should configure forking. +forgetest_init!(eth_rpc_url_env_does_not_set_fork_url, |prj, _cmd| { + prj.initialize_default_contracts(); + let url = "http://127.0.0.1:8545"; + + let mut cmd = prj.forge_bin(); + cmd.arg("config") + .arg("--root") + .arg(prj.root()) + .arg("--json") + .env("ETH_RPC_URL", url) + // Make sure the figment-style env var is not set in the test environment. + .env_remove("FOUNDRY_ETH_RPC_URL"); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let config: Config = serde_json::from_str(stdout.as_ref()).unwrap(); + assert_eq!( + config.eth_rpc_url, None, + "bare ETH_RPC_URL must not propagate to forge config (regression #14538)" + ); +}); + // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { prj.initialize_default_contracts(); @@ -1270,6 +1295,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "show_progress": false, "fuzz": { "runs": 256, + "run": null, + "worker": null, "fail_on_revert": true, "max_test_rejects": 65536, "seed": null, @@ -1437,8 +1464,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "soldeer": null, "assertions_revert": true, "legacy_assertions": false, + "network": null, "celo": false, - "tempo": false, "bypass_prevrandao": false, "transaction_timeout": 120, "additional_compiler_profiles": [], diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index fbd84739635c5..f792f5b4a7887 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -1,12 +1,12 @@ use foundry_test_utils::util::ExtTester; // Actively maintained tests -// Last updated: June 19th 2025 +// Last updated: April 29th 2026 // #[test] fn forge_std() { - ExtTester::new("foundry-rs", "forge-std", "b69e66b0ff79924d487d49bf7fb47c9ec326acba") + ExtTester::new("foundry-rs", "forge-std", "8987040ede9553cea20c95ad40d0455930f9c8e0") // Skip fork tests. .args(["--nmc", "Fork"]) .verbosity(2) @@ -17,7 +17,7 @@ fn forge_std() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn prb_math() { - ExtTester::new("PaulRBerg", "prb-math", "aad73cfc6cdc2c9b660199b5b1e9db391ea48640") + ExtTester::new("PaulRBerg", "prb-math", "82e5ed5561d0a1c43a3a59edbf4291c8de26479e") .install_command(&["bun", "install", "--prefer-offline"]) // Try npm if bun fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) @@ -40,7 +40,7 @@ fn prb_proxy() { #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn sablier_v2_core() { let mut tester = - ExtTester::new("sablier-labs", "v2-core", "d85521f5615f6c19612ff250ee89c57b9afa6aa2") + ExtTester::new("sablier-labs", "v2-core", "8b6823c019ff7556ac9ad24cbb5ac62821854d2f") // Skip fork tests. .args(["--nmc", "Fork"]) // Increase the gas limit: https://github.com/sablier-labs/v2-core/issues/956 @@ -65,7 +65,7 @@ fn sablier_v2_core() { #[test] fn solady() { let mut tester = - ExtTester::new("Vectorized", "solady", "cbcfe0009477aa329574f17e8db0a05703bb8bdd"); + ExtTester::new("Vectorized", "solady", "90db92ce173856605d24a554969f2c67cadbc7e9"); // This test expects the mover contract created via CREATE2 to be selfdestructed within the // same transaction. In isolation mode, each top-level call runs as a separate transaction @@ -82,7 +82,7 @@ fn solady() { #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] #[cfg(not(feature = "isolate-by-default"))] fn snekmate() { - ExtTester::new("pcaversaccio", "snekmate", "601031d244475b160a00f73053532528bf665cc3") + ExtTester::new("pcaversaccio", "snekmate", "1a54931129f2814cbbd7ddbafb4005707f8a5bf8") .install_command(&["pnpm", "install", "--prefer-offline"]) // Try npm if pnpm fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) @@ -92,7 +92,7 @@ fn snekmate() { // #[test] fn mds1_multicall3() { - ExtTester::new("mds1", "multicall", "5f90062160aedb7c807fadca469ac783a0557b57").run(); + ExtTester::new("mds1", "multicall", "b667d67ecfa5361a81e8f110234ce242613b0012").run(); } // Legacy tests diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index 48a17c723b261..77d5a5e84cfbb 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -70,8 +70,13 @@ Suite result: FAILED. 0 passed; 7 failed; 0 skipped; [ELAPSED] .stdout_eq( r#"No files changed, compilation skipped ... +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != [..]] testShouldFailExpectRevertNestedCreateInnerAddress() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterNestedCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterTopLevelCreate() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailExpectRevertsNotOnImmediateNextCall() ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#, ); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index 04fb2369d83b0..ba01767d58b26 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -425,3 +425,107 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// Checks that tests annotated with `forge-config: default.networks.network` run on the correct +// EVM network, and that unannotated tests run on the globally configured network. +// +// Each test makes a real call to the Tempo `TipFeeManager` precompile at +// `0xfeec000000000000000000000000000000000000` (a Tempo-only contract that exists on the +// Moderato testnet and is auto-injected by the in-memory Tempo EVM): +// +// * The default-network test asserts the precompile has no code (it does not exist on Ethereum). +// * The Tempo-network test asserts the precompile has code and `userTokens(address)` returns the +// unset zero-address sentinel, proving the Tempo network was actually selected for that test and +// the Tempo genesis state was loaded. +forgetest!(per_test_network_routing, |prj, cmd| { + prj.add_test( + "inline.sol", + r#" + address constant TIP_FEE_MANAGER = 0xfeEC000000000000000000000000000000000000; + + contract DefaultNetwork { + // No annotation -> runs on the globally selected network (Ethereum by default). + // The Tempo FeeManager precompile must NOT exist here. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + } + + contract TempoNetwork { + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + // Sentinel bytecode (0xef) is injected at every Tempo precompile address. + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + // Call a Tempo-only method: `userTokens(address)` returns the user's preferred + // fee token, or the zero address when none is set. + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + + // Mixed contract: one function annotated with Tempo, one unannotated (runs on Ethereum). + contract MixedNetwork { + // No annotation -> runs on Ethereum; precompile must be absent. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + "#, + ); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 3 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index fd69907be2f09..8420e24eb3df7 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,4 +1,7 @@ -use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; +use forge_lint::{ + linter::Lint, + sol::{self, SolLint}, +}; use foundry_config::{ DenyLevel, LintSeverity, LinterConfig, SolidityErrorCode, lint::LintSpecificConfig, }; @@ -203,7 +206,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -230,7 +233,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -610,7 +613,7 @@ note[mixed-case-function]: function names should use mixedCase 9 │ function functionMIXEDCaseInfo() public {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -637,7 +640,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); @@ -665,7 +668,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect 13 │ uint256 result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift "# @@ -694,7 +697,7 @@ warning[divide-before-multiply]: multiplication should occur before division to 16 │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]).stdout_eq(str![[r#" @@ -855,7 +858,7 @@ note[unused-import]: unused imports should be removed 8 │ import { _PascalCaseInfo } from "./ContractWithLints.sol"; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import "#]]); @@ -887,7 +890,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase 6 │ uint256 public CounterB_Fail_Lint; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterBFailLint` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable "#]]); @@ -992,7 +995,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { ], "children": [ { - "message": "https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic", + "message": "https://getfoundry.sh/forge/linting/unwrapped-modifier-logic", "code": null, "level": "help", "spans": [], @@ -1048,7 +1051,7 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { "rendered": null } ], - "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" + "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" } "#]], ); @@ -1129,46 +1132,46 @@ Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. #[tokio::test] async fn ensure_lint_rule_docs() { - const FOUNDRY_BOOK_LINT_PAGE_URL: &str = "https://book.getfoundry.sh/forge/linting"; - - // Fetch the content of the lint reference - let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { - Ok(resp) => { - assert!( - resp.status().is_success(), - "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", - status = resp.status() - ); - match resp.text().await { - Ok(text) => text, - Err(e) => { - panic!("Failed to read response text: {e}"); - } + let client = reqwest::Client::new(); + let mut failures = Vec::new(); + + for lint in registered_lints() { + let url = lint.help(); + let response = match client.get(url).send().await { + Ok(response) => response, + Err(err) => { + failures.push(format!("{} ({url}) could not be fetched: {err}", lint.id())); + continue; } + }; + + if !response.status().is_success() { + failures.push(format!("{} ({url}) returned HTTP {}", lint.id(), response.status())); + continue; } - Err(e) => { - panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); - } - }; - // Ensure no missing lints - let mut missing_lints = Vec::new(); - for lint in REGISTERED_LINTS { + let content = match response.text().await { + Ok(content) => content.to_lowercase(), + Err(err) => { + failures + .push(format!("{} ({url}) response body could not be read: {err}", lint.id())); + continue; + } + }; + let selector = lint.id().to_lowercase(); let selector_with_space = selector.replace('-', " "); - if !content.to_lowercase().contains(&selector) - && !content.to_lowercase().contains(&selector_with_space) - { - missing_lints.push(lint.id()); + if !content.contains(&selector) && !content.contains(&selector_with_space) { + failures.push(format!("{} ({url}) did not mention the lint id", lint.id())); } } - if !missing_lints.is_empty() { + if !failures.is_empty() { let mut msg = String::from( - "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + "Foundry Book lint validation failed. The following lint pages are missing or invalid:\n", ); - for lint in missing_lints { - msg.push_str(&format!(" - {lint}\n")); + for failure in failures { + msg.push_str(&format!(" - {failure}\n")); } msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); panic!("{msg}"); @@ -1177,11 +1180,21 @@ async fn ensure_lint_rule_docs() { #[test] fn ensure_no_privileged_lint_id() { - for lint in REGISTERED_LINTS { + for lint in registered_lints() { assert_ne!(lint.id(), "all", "lint-id 'all' is reserved. Please use a different id"); } } +fn registered_lints() -> impl Iterator { + sol::high::REGISTERED_LINTS + .iter() + .chain(sol::med::REGISTERED_LINTS) + .chain(sol::low::REGISTERED_LINTS) + .chain(sol::info::REGISTERED_LINTS) + .chain(sol::gas::REGISTERED_LINTS) + .chain(sol::codesize::REGISTERED_LINTS) +} + // forgetest!(dependency_warnings_do_not_affect_lint_exit_code, |prj, cmd| { // Library with code that triggers a solc warning (unused local variable) @@ -1265,3 +1278,195 @@ contract OldContract { "# ]]); }); + +const PRAGMA_INCONSISTENT_ALPHA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Alpha {} +"#; + +const PRAGMA_INCONSISTENT_BETA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract Beta {} +"#; + +forgetest!(pragma_inconsistent_cross_file, |prj, cmd| { + prj.add_source("Alpha", PRAGMA_INCONSISTENT_ALPHA); + prj.add_source("Beta", PRAGMA_INCONSISTENT_BETA); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +const PRAGMA_EXACT_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract A {} +"#; + +const PRAGMA_EXACT_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract B {} +"#; + +const PRAGMA_EXACT_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract C {} +"#; + +const PRAGMA_CARET_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract A {} +"#; + +const PRAGMA_CARET_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract B {} +"#; + +const PRAGMA_CARET_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract C {} +"#; + +const NO_PRAGMA_C: &str = r#" +// SPDX-License-Identifier: MIT + +contract C {} +"#; + +// Multiple files all using the exact same pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_exact_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_EXACT_C); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Multiple files all using the exact same caret pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_caret_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + prj.add_source("B", PRAGMA_CARET_B); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// A single file in the project cannot conflict with itself. +forgetest!(pragma_inconsistent_single_file_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Even files that share a requirement still emit when ANY other variant exists. +// Two files with `0.8.20` plus one file with `^0.8.20` => 3 emits total. +forgetest!(pragma_inconsistent_duplicates_among_conflict, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_CARET_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +// Files without a `pragma solidity` directive must not affect the conflict computation. +// Note: `add_raw_source` is used here to bypass the helper that would otherwise inject a default +// `pragma solidity =;` for files that omit one. +forgetest!(pragma_inconsistent_files_without_pragma, |prj, cmd| { + prj.add_raw_source("A", PRAGMA_EXACT_A); + prj.add_raw_source("B", PRAGMA_CARET_B); + // C has no pragma at all; should be ignored by the cross-file check. + prj.add_raw_source("C", NO_PRAGMA_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); diff --git a/crates/forge/tests/cli/lint/geiger.rs b/crates/forge/tests/cli/lint/geiger.rs index faecfb212fb90..202866e83e35f 100644 --- a/crates/forge/tests/cli/lint/geiger.rs +++ b/crates/forge/tests/cli/lint/geiger.rs @@ -21,7 +21,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ bytes memory stuff = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 1 linter note(s) ... @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 9 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:10:20 @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 10 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations [FILE]:11:20 @@ -100,7 +100,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op 11 │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode Error: aborting due to 3 linter note(s) ... diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 242a0ebb4267f..031d80f0cf071 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -3241,7 +3241,7 @@ contract CounterScript is Script { error: the following required arguments were not provided: --broadcast -Usage: [..] script --broadcast --verify --rpc-url [ARGS]... +Usage: [..] script --broadcast --verify --rpc-url [ARGS]... For more information, try '--help'. diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index fefefb30d9b15..454b014a6e1bc 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -1,4 +1,5 @@ use alloy_primitives::U256; +use foundry_evm::fuzz::BaseCounterExample; use foundry_test_utils::{TestCommand, forgetest_init, str}; use regex::Regex; @@ -845,6 +846,8 @@ forgetest_init!(test_fuzz_random_uint_varies_across_runs, |prj, cmd| { prj.add_test( "RandomFuzzTest.t.sol", r#" +pragma solidity >=0.8.0; + import {Test} from "forge-std/Test.sol"; contract RandomFuzzTest is Test { @@ -868,3 +871,145 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) ... "#]]); }); + +forgetest_init!(test_fuzz_run_replays_random_uint_failure, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + function testFuzz_randomUint_shouldFail(uint256) public { + uint256 rand = vm.randomUint(0, 4); + assertTrue(rand != 0, "hit value 0"); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: hit value 0; counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + + cmd.args(["test", "--fuzz-seed", "1", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + assert_eq!(persisted_failure.fuzz.seed, Some(U256::from(1))); + assert_eq!(persisted_failure.fuzz.worker, Some(0)); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + cmd.forge_fuse() + .args([ + "test", + "--fuzz-seed", + "1", + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure().stdout_eq(expected_output); +}); + +forgetest_init!(test_fuzz_rerun_replays_random_uint_failure_without_seed, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + error Random(uint256 value); + + function testFuzz_randomUint_shouldFail(uint256) public { + revert Random(vm.randomUint()); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: Random([..]); counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]; + + let assert = cmd + .args(["test", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + let reason = random_failure_reason(&stdout); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + let fuzz_seed = format!("{:#x}", persisted_failure.fuzz.seed.unwrap()); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + let assert = cmd + .forge_fuse() + .args([ + "test", + "--fuzz-seed", + &fuzz_seed, + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd + .forge_fuse() + .args(["test", "--rerun", "-j1"]) + .assert_failure() + .stdout_eq(expected_output); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); +}); + +fn random_failure_reason(stdout: &str) -> String { + Regex::new(r"\[FAIL: (Random\([^)]+\))") + .unwrap() + .captures(stdout) + .unwrap_or_else(|| panic!("{stdout}"))[1] + .to_string() +} diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs index fefd95def0412..107b387bb0ec5 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/common.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -2670,7 +2670,7 @@ contract InvariantWarp is Test { [FAIL: max time] [Sequence] (original: 3, shrunk: 1) vm.warp(block.timestamp + 656868); - vm.prank(0x00000000000000000000000000000000000012d2); + vm.prank(0x00000000000000000000000000000000000012d1); Warp(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); invariant_warp() (runs: 0, calls: 0, reverts: 2) ... diff --git a/crates/forge/tests/cli/test_cmd/invariant/mod.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs index 723b5bd789c8b..bbe65f2f2f2fa 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/mod.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -422,9 +422,9 @@ Failing tests: Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest [FAIL: invariant increment failure] [Sequence] (original: 3, shrunk: 3) - sender=0x00000000000000000000000000000000000014aD addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x0000000000000000000000000000000000001490 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] sender=0x8ef7F804bAd9183981A366EA618d9D47D3124649 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] - sender=0x00000000000000000000000000000000000016Ac addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] + sender=0x00000000000000000000000000000000000016C5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] invariant_increment() (runs: 0, calls: 0, reverts: 0) Encountered a total of 1 failing tests, 0 tests succeeded @@ -448,11 +448,11 @@ Failing tests: Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest [FAIL: invariant increment failure] [Sequence] (original: 3, shrunk: 3) - vm.prank(0x00000000000000000000000000000000000014aD); + vm.prank(0x0000000000000000000000000000000000001490); Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); vm.prank(0x8ef7F804bAd9183981A366EA618d9D47D3124649); Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); - vm.prank(0x00000000000000000000000000000000000016Ac); + vm.prank(0x00000000000000000000000000000000000016C5); Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(284406551521730736391345481857560031052359183671404042152984097777); invariant_increment() (runs: 0, calls: 0, reverts: 0) @@ -476,9 +476,9 @@ Failing tests: Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest [FAIL: invariant increment failure] [Sequence] (original: 3, shrunk: 3) - sender=0x00000000000000000000000000000000000014aD addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x0000000000000000000000000000000000001490 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] sender=0x8ef7F804bAd9183981A366EA618d9D47D3124649 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] - sender=0x00000000000000000000000000000000000016Ac addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] + sender=0x00000000000000000000000000000000000016C5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] invariant_increment() (runs: 1, calls: 1, reverts: 1) Encountered a total of 1 failing tests, 0 tests succeeded diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index 32bfe6a98a9fd..3803385b496ab 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -783,6 +783,66 @@ ParserError: Source "Missing.sol" not found: File not found. Searched the follow "#]]); }); +// https://github.com/foundry-rs/foundry/issues/10463 +forgetest_init!(issue_10463, |prj, cmd| { + prj.add_test( + "Issue10463.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue10463Test is Test { + event Foo(); + + error CustomError(uint256 code); + + function revertingBefore(bool shouldRevert) external { + if (shouldRevert) revert(); + emit Foo(); + } + + function revertingWithReason() external pure { + revert("revert reason"); + } + + function revertingWithCustomError() external pure { + revert CustomError(42); + } + + function testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() public { + vm.expectEmit(); + emit Foo(); + + this.revertingBefore(true); + } + + function testExpectEmitPreservesRevertReason() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithReason(); + } + + function testExpectEmitPreservesCustomError() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithCustomError(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue10463.t.sol:Issue10463Test +[FAIL: CustomError(42)] testExpectEmitPreservesCustomError() ([GAS]) +[FAIL: revert reason] testExpectEmitPreservesRevertReason() ([GAS]) +[FAIL: EvmError: Revert] testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() ([GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + // https://github.com/foundry-rs/foundry/issues/12803 // Test gas underflow prevention on Cancun (no EIP-7702 gas floor) forgetest_init!(issue_12803_cancun, |prj, cmd| { diff --git a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol index 7e482c6673155..838183a1b0b5e 100644 --- a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol +++ b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol @@ -233,6 +233,63 @@ contract ExpectRevertWithReverterFailureTest is DSTest { aContract.doNotRevert(); aContract.callAndRevert(); } + + // + // Regression: must fail because 0xdead is not the actual reverter when a + // top-level CREATE constructor reverts directly. + function testShouldFailExpectRevertWrongReverterTopLevelCreate() public { + vm.expectRevert(address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // even when an exact-bytes pattern is also supplied for a top-level CREATE. + function testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() public { + vm.expectRevert(abi.encodePacked("Reverted by DContract"), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // for `expectPartialRevert(bytes4, address)` against a top-level CREATE. + function testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() public { + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail when the innermost reverting frame is a nested + // CREATE and the reverter address argument does not match the would-be + // deployed address of the failed deployment. + function testShouldFailExpectRevertWrongReverterNestedCreate() public { + vm.expectRevert(address(0xdead)); + new NestedDContractCreator(); + } + + // + // Regression: documents the intended semantics for nested CREATEs — the + // matched reverter is the *outer* would-be-deployed address (the contract + // whose deployment failed), NOT the innermost reverting CREATE's address. + // Supplying the inner address must fail. + function testShouldFailExpectRevertNestedCreateInnerAddress() public { + // Outer = NestedDContractCreator at this contract's next nonce. + // Inner = DContract created from inside the outer constructor (deployer + // is the outer, nonce 1). + address outer = + vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + address inner = vm.computeCreateAddress(outer, 1); + vm.expectRevert(inner); + new NestedDContractCreator(); + } +} + +// Used by `testShouldFailExpectRevertWrongReverterNestedCreate`: a contract whose +// constructor directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } } contract ExpectRevertCountFailureTest is DSTest { diff --git a/crates/lint/Cargo.toml b/crates/lint/Cargo.toml index 87864721432d9..589a0a5069e37 100644 --- a/crates/lint/Cargo.toml +++ b/crates/lint/Cargo.toml @@ -24,3 +24,7 @@ eyre.workspace = true heck.workspace = true rayon.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/lint/README.md b/crates/lint/README.md index 9e3f394eba832..9d5dad0c0272e 100644 --- a/crates/lint/README.md +++ b/crates/lint/README.md @@ -11,14 +11,20 @@ It helps enforce best practices and improve code quality within Foundry projects - `incorrect-shift`: Warns against shift operations where operands might be in the wrong order. - `unchecked-call`: Low-level calls should check the success return value. - `erc20-unchecked-transfer`: ERC20 `transfer` and `transferFrom` calls should check the return value. + - `rtlo`: Flags Unicode bidirectional override characters ("Trojan Source", CVE-2021-42574) that can hide malicious code. - **Medium Severity:** + - `boolean-cst`: Flags misuse of boolean constants. - `divide-before-multiply`: Warns against performing division before multiplication in the same expression, which can cause precision loss. - `incorrect-erc20-interface`: Flags ERC20 interfaces and implementations with non-compliant function signatures. - `incorrect-erc721-interface`: Flags ERC721 interfaces and implementations with non-compliant function signatures. + - `tx-origin`: Flags use of `tx.origin` in authorization-like predicates. - `unsafe-typecast`: Typecasts that can truncate values should be checked. - **Low Severity:** - `block-timestamp`: Warns when `block.timestamp` is used in a comparison, as it may be manipulated by validators. + - `missing-zero-check`: Address parameter is used in a state write or value transfer without a zero-address check. - **Informational / Style Guide:** + - `boolean-equal`: Boolean comparisons to constants should be simplified. + - `too-many-digits`: Numeric literals with 5+ consecutive zeros are error-prone. - `pascal-case-struct`: Flags for struct names not adhering to `PascalCase`. - `mixed-case-function`: Flags for function names not adhering to `mixedCase`. - `mixed-case-variable`: Flags for mutable variable names not adhering to `mixedCase`. @@ -28,9 +34,15 @@ It helps enforce best practices and improve code quality within Foundry projects - `unaliased-plain-import`: Use named imports `{A, B}` or alias `import ".." as X`. - `named-struct-fields`: Prefer initializing structs with named fields. - `unsafe-cheatcode`: Usage of unsafe cheatcodes that can perform dangerous operations. + - `multi-contract-file`: Prefer having only one contract, interface, or library per file. + - `interface-file-naming`: Interface file names should be prefixed with `I`. + - `interface-naming`: Interface names should be prefixed with `I`. + - `pragma-inconsistent`: Flags projects whose source files declare different Solidity pragma version requirements. - **Gas Optimizations:** - `asm-keccak256`: Recommends using inline assembly for `keccak256` for potential gas savings. + - `could-be-immutable`: Recommends declaring constructor-only state variables as `immutable`. - `custom-errors`: Recommends using custom errors instead of strings and plain reverts for potential gas savings. + - `unused-state-variables`: State variables that are never used should be removed. - **Code Size:** - `unwrapped-modifier-logic`: Recommends wrapping modifier logic to reduce contract code size. diff --git a/crates/lint/docs/README.md b/crates/lint/docs/README.md new file mode 100644 index 0000000000000..5eb4110a92e57 --- /dev/null +++ b/crates/lint/docs/README.md @@ -0,0 +1,52 @@ +# Forge lint documentation + +This directory contains one markdown file per registered `forge-lint` rule. Each file is referenced +by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the +[Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. + +## Adding a new lint + +When you add a new lint with `declare_forge_lint!`, you **must** also add a documentation file at +`crates/lint/docs/.md`. The presence of the file is enforced by the +`registered_lints_have_docs` unit test in [`crates/lint/src/sol/mod.rs`](../src/sol/mod.rs). + +Use [`_template.md`](./_template.md) as a starting point. + +## File structure + +Each lint doc file should follow this structure: + +```markdown +# + +**Severity**: `` +**ID**: `` + +A one-paragraph description of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` + +## Configuration + +Document any inline-config or `foundry.toml` options that affect this lint, if any. +``` diff --git a/crates/lint/docs/_template.md b/crates/lint/docs/_template.md new file mode 100644 index 0000000000000..41c735a0ba579 --- /dev/null +++ b/crates/lint/docs/_template.md @@ -0,0 +1,28 @@ +# + +**Severity**: `` +**ID**: `` + +One-paragraph summary of what this lint detects and why it matters. + +## What it does + +Explain precisely what the lint flags. + +## Why is this bad? + +Explain the impact (security, correctness, gas, readability). + +## Example + +### Bad + +```solidity +// triggering example +``` + +### Good + +```solidity +// non-triggering, recommended example +``` diff --git a/crates/lint/docs/asm-keccak256.md b/crates/lint/docs/asm-keccak256.md new file mode 100644 index 0000000000000..4678cfe9f8d12 --- /dev/null +++ b/crates/lint/docs/asm-keccak256.md @@ -0,0 +1,42 @@ +# Inefficient keccak256 call + +**Severity**: `Gas` +**ID**: `asm-keccak256` + +Flags calls to the high-level `keccak256(...)` builtin that can be cheaply rewritten with inline +assembly. + +## What it does + +Reports `keccak256(arg)` calls and (when possible) emits a fix suggestion that uses inline +assembly to compute the hash directly, avoiding the overhead of the high-level call. + +## Why is this bad? + +The high-level `keccak256` call performs additional memory management and ABI encoding compared +to a direct `keccak256(ptr, len)` opcode invocation. In hot paths the difference is visible in +gas reports. + +## Example + +### Bad + +```solidity +bytes32 h = keccak256(abi.encodePacked(a, b)); +``` + +### Good + +```solidity +bytes32 h; +assembly ("memory-safe") { + let m := mload(0x40) + mstore(m, a) + mstore(add(m, 0x20), b) + h := keccak256(m, 0x40) +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/block-timestamp.md b/crates/lint/docs/block-timestamp.md new file mode 100644 index 0000000000000..a51b55ff5d8cc --- /dev/null +++ b/crates/lint/docs/block-timestamp.md @@ -0,0 +1,44 @@ +# Use of block.timestamp in comparisons + +**Severity**: `Low` +**ID**: `block-timestamp` + +Flags use of `block.timestamp` as an operand of a comparison, where its value can be slightly +manipulated by the block proposer. + +## What it does + +Reports any comparison expression (`<`, `<=`, `>`, `>=`, `==`, `!=`) that directly or +transitively reads `block.timestamp`. + +## Why is this bad? + +Block proposers can adjust `block.timestamp` within a small window (a few seconds). This is +usually harmless, but for short-window logic — auctions ending, randomness, time-locked +withdrawals — a few seconds of manipulation can be enough for an attacker to capture value. + +Using `block.timestamp` for general scheduling (hours/days) is fine; what's risky is fine-grained +timing and treating timestamps as a source of randomness. + +## Example + +### Bad + +```solidity +function settle() external { + require(block.timestamp >= auctionEnd, "auction ongoing"); + // ... +} +``` + +### Good + +```solidity +// Prefer block numbers for tight windows, or accept a clearly large grace period. +require(block.number >= endBlock, "auction ongoing"); +``` + +## Notes + +This lint is intentionally conservative: not every flagged comparison is exploitable. Review +each occurrence in context. diff --git a/crates/lint/docs/boolean-cst.md b/crates/lint/docs/boolean-cst.md new file mode 100644 index 0000000000000..f5c65dfec2789 --- /dev/null +++ b/crates/lint/docs/boolean-cst.md @@ -0,0 +1,37 @@ +# Misuse of a boolean constant + +**Severity**: `Med` +**ID**: `boolean-cst` + +Flags expressions where a boolean constant (`true`/`false`) is used as a control-flow condition +or operand of a boolean operator, which usually indicates dead code or a leftover debug toggle. + +## What it does + +Reports `if (true)`, `if (false)`, `while (true)` outside of intentional infinite loops, and +boolean operators (`&&`, `||`) where one side is a literal `true`/`false`. + +## Why is this bad? + +A literal boolean as a condition makes the surrounding branch dead, hides logic errors, or +preserves a forgotten debug shortcut that bypasses real checks. + +## Example + +### Bad + +```solidity +if (true) { // always taken + doSomething(); +} +require(condition && true, "unreachable"); // 'true' is redundant +``` + +### Good + +```solidity +if (condition) { + doSomething(); +} +require(condition, "..."); +``` diff --git a/crates/lint/docs/boolean-equal.md b/crates/lint/docs/boolean-equal.md new file mode 100644 index 0000000000000..9397003b039b4 --- /dev/null +++ b/crates/lint/docs/boolean-equal.md @@ -0,0 +1,34 @@ +# Boolean comparison to a constant + +**Severity**: `Info` +**ID**: `boolean-equal` + +Flags expressions of the form `x == true`, `x == false`, `x != true`, `x != false`, which can be +simplified. + +## What it does + +Reports any equality comparison between a boolean expression and a literal `true` or `false`. + +## Why is this bad? + +Comparing a boolean to a boolean literal is redundant and harms readability. Use the boolean +expression directly (or its negation). + +## Example + +### Bad + +```solidity +if (paused == true) revert(); +if (paused == false) doSomething(); +require(ok != false, "fail"); +``` + +### Good + +```solidity +if (paused) revert(); +if (!paused) doSomething(); +require(ok, "fail"); +``` diff --git a/crates/lint/docs/could-be-immutable.md b/crates/lint/docs/could-be-immutable.md new file mode 100644 index 0000000000000..bda1de6379955 --- /dev/null +++ b/crates/lint/docs/could-be-immutable.md @@ -0,0 +1,42 @@ +# State variable could be immutable + +**Severity**: `Gas` +**ID**: `could-be-immutable` + +Flags state variables that are assigned only in the constructor and never written to afterward — +making them eligible to be declared `immutable`. + +## What it does + +Reports each non-`constant`, non-`immutable` state variable whose only writes occur in the +constructor (or in initialization at declaration time). + +## Why is this bad? + +`immutable` state variables are stored in the deployed bytecode rather than in storage, eliminating +an `SLOAD` per access and saving substantial gas across the contract's lifetime. Declaring such +variables `immutable` also expresses intent and prevents future writes. + +## Example + +### Bad + +```solidity +contract C { + address owner; + constructor() { owner = msg.sender; } +} +``` + +### Good + +```solidity +contract C { + address immutable OWNER; + constructor() { OWNER = msg.sender; } +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/custom-errors.md b/crates/lint/docs/custom-errors.md new file mode 100644 index 0000000000000..9e01e01d593e9 --- /dev/null +++ b/crates/lint/docs/custom-errors.md @@ -0,0 +1,45 @@ +# Prefer custom errors over revert strings + +**Severity**: `Gas` +**ID**: `custom-errors` + +Flags `require(cond, "message")`, `revert("message")`, and `revert()` calls; suggests replacing +them with a `revert CustomError(...)`. + +## What it does + +Reports `require` calls whose second argument is a string literal, and `revert(...)` calls that +are either bare or have a string-literal argument. + +## Why is this bad? + +Custom errors: +- cost less gas than encoding/decoding a string, +- can carry typed parameters for richer diagnostics, +- shrink contract bytecode (string constants live in code). + +Solidity 0.8.4+ supports custom errors natively. + +## Example + +### Bad + +```solidity +require(amount > 0, "amount must be > 0"); +revert("not authorized"); +revert(); +``` + +### Good + +```solidity +error AmountZero(); +error NotAuthorized(); + +if (amount == 0) revert AmountZero(); +if (!authorized) revert NotAuthorized(); +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/divide-before-multiply.md b/crates/lint/docs/divide-before-multiply.md new file mode 100644 index 0000000000000..f082bef19a1bd --- /dev/null +++ b/crates/lint/docs/divide-before-multiply.md @@ -0,0 +1,32 @@ +# Divide before multiply + +**Severity**: `Med` +**ID**: `divide-before-multiply` + +Flags arithmetic expressions where division is performed before multiplication, which can cause +unintended precision loss in integer arithmetic. + +## What it does + +Warns on expressions of the form `(a / b) * c` (or equivalent shapes), where the integer division +truncates before the result is multiplied. + +## Why is this bad? + +Solidity's integer division truncates toward zero. Performing `(a / b) * c` discards the remainder +of `a / b` before scaling, while `(a * c) / b` preserves precision. This pattern frequently +manifests as fee/share/yield miscalculations. + +## Example + +### Bad + +```solidity +uint256 share = (amount / total) * weight; // truncates first, then scales +``` + +### Good + +```solidity +uint256 share = (amount * weight) / total; // preserves precision +``` diff --git a/crates/lint/docs/erc20-unchecked-transfer.md b/crates/lint/docs/erc20-unchecked-transfer.md new file mode 100644 index 0000000000000..d7d053e020cca --- /dev/null +++ b/crates/lint/docs/erc20-unchecked-transfer.md @@ -0,0 +1,43 @@ +# Unchecked ERC20 transfer return value + +**Severity**: `High` +**ID**: `erc20-unchecked-transfer` + +Flags calls to ERC20 `transfer` and `transferFrom` where the boolean return value is ignored. + +## What it does + +Warns when a function with the same signature as +`transfer(address,uint256)` or `transferFrom(address,address,uint256)` and a `bool` return type is +invoked but the result is not checked. + +## Why is this bad? + +The ERC20 spec allows tokens to signal failure by returning `false` instead of reverting. Ignoring +the return value lets a "failed" transfer go unnoticed, allowing accounting to drift and creating +common DeFi exploits. Use a wrapper such as OpenZeppelin's `SafeERC20` or check the boolean +explicitly. + +## Example + +### Bad + +```solidity +token.transfer(to, amount); +token.transferFrom(from, to, amount); +``` + +### Good + +```solidity +require(token.transfer(to, amount), "transfer failed"); +require(token.transferFrom(from, to, amount), "transferFrom failed"); + +// or use SafeERC20 +SafeERC20.safeTransfer(token, to, amount); +``` + +## Notes + +This lint can produce false positives when the callee does not strictly conform to the ERC20 +interface (e.g. tokens that revert on failure rather than returning `false`). diff --git a/crates/lint/docs/incorrect-erc20-interface.md b/crates/lint/docs/incorrect-erc20-interface.md new file mode 100644 index 0000000000000..65fb8313c205f --- /dev/null +++ b/crates/lint/docs/incorrect-erc20-interface.md @@ -0,0 +1,42 @@ +# Incorrect ERC20 interface + +**Severity**: `Med` +**ID**: `incorrect-erc20-interface` + +Flags interfaces or contracts whose function signatures match an ERC20 method by name and +parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC20 method +(`totalSupply`, `balanceOf`, `transfer`, `transferFrom`, `approve`, `allowance`), the lint checks +that the return type matches the spec. A mismatch is reported. + +## Why is this bad? + +Tokens that diverge from the ERC20 spec break composability with the wider ecosystem (DEXes, +lending protocols, multisigs) and are a common source of integration bugs and exploits. + +## Example + +### Bad + +```solidity +interface IBadERC20 { + function balanceOf(address) external view returns (bool); // should be uint256 + function transfer(address, uint256) external; // should return bool +} +``` + +### Good + +```solidity +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-erc721-interface.md b/crates/lint/docs/incorrect-erc721-interface.md new file mode 100644 index 0000000000000..4803afdde7cc1 --- /dev/null +++ b/crates/lint/docs/incorrect-erc721-interface.md @@ -0,0 +1,48 @@ +# Incorrect ERC721 interface + +**Severity**: `Med` +**ID**: `incorrect-erc721-interface` + +Flags interfaces or contracts whose function signatures match an ERC721 (or ERC165) method by +name and parameters but use the wrong return type. + +## What it does + +For each function whose name and parameter types match a canonical ERC721/ERC165 method +(`balanceOf`, `ownerOf`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, +`getApproved`, `isApprovedForAll`, `supportsInterface`), the lint checks that the return type +matches the spec. A mismatch is reported. + +## Why is this bad? + +Non-conforming NFT contracts break marketplaces, indexers, and any protocol that relies on the +ERC721 spec. A wrong return type often compiles and deploys silently but causes integration +failures at runtime. + +## Example + +### Bad + +```solidity +interface IBadERC721 { + function balanceOf(address) external view returns (bool); // should be uint256 + function ownerOf(uint256) external view returns (bool); // should be address + function supportsInterface(bytes4) external view returns (uint256); // should be bool +} +``` + +### Good + +```solidity +interface IERC721 { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function transferFrom(address from, address to, uint256 tokenId) external; + function approve(address to, uint256 tokenId) external; + function setApprovalForAll(address operator, bool approved) external; + function getApproved(uint256 tokenId) external view returns (address); + function isApprovedForAll(address owner, address operator) external view returns (bool); + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} +``` diff --git a/crates/lint/docs/incorrect-shift.md b/crates/lint/docs/incorrect-shift.md new file mode 100644 index 0000000000000..a9a70c7f93128 --- /dev/null +++ b/crates/lint/docs/incorrect-shift.md @@ -0,0 +1,37 @@ +# Incorrect shift order + +**Severity**: `High` +**ID**: `incorrect-shift` + +Flags shift operations where a literal appears on the left and a non-literal on the right, which +is almost always the wrong operand order. + +## What it does + +Warns when the left-hand operand of `<<` or `>>` is a numeric literal and the right-hand operand +is a non-literal expression (e.g. a variable, function call, or composite expression). + +## Why is this bad? + +Shift expressions like `2 << x` are usually a typo for `x << 2`. In the former, the *value being +shifted* is a tiny constant and the *shift amount* is dynamic — almost never the intended +behavior, and a known source of bugs in production contracts. + +## Example + +### Bad + +```solidity +result = 2 << stateValue; // shift amount comes from state +result = 8 >> localValue; // shift amount comes from a local +result = 16 << (stateValue + 1); // shift amount is a dynamic expression +``` + +### Good + +```solidity +result = stateValue << 2; +result = localValue >> 3; +result = stateValue << localShiftAmount; +result = 1 << 8; // both literals — fine +``` diff --git a/crates/lint/docs/inline-assembly.md b/crates/lint/docs/inline-assembly.md new file mode 100644 index 0000000000000..bba61148b84c5 --- /dev/null +++ b/crates/lint/docs/inline-assembly.md @@ -0,0 +1,69 @@ +# Inline assembly + +**Severity**: `Info` +**ID**: `inline-assembly` + +Flags every `assembly { ... }` block. Inline assembly bypasses many of Solidity's safety +features (type checks, overflow checks, memory layout invariants) and is a common source of +high-impact bugs, so each occurrence should be reviewed deliberately. + +## What it does + +Reports every inline assembly statement, including blocks declared with the `"evmasm"` dialect +and/or the `("memory-safe")` flag. Blocks declared as memory-safe — either via the modern +`("memory-safe")` flag or the legacy `/// @solidity memory-safe-assembly` NatSpec marker — are +still reported, but with a softer message acknowledging the developer attestation: review +focuses on business logic and side effects rather than memory layout. + +## Why is this bad? + +Assembly skips Solidity's compile-time checks and many of its runtime guarantees. Mistakes +inside an `assembly` block can corrupt memory, break the free memory pointer, leak storage, +escalate privileges via `delegatecall`, or destroy the contract via `selfdestruct`. Even when +required for gas or features unavailable in high-level Solidity, assembly should be small, +documented, and reviewed. + +## When inline assembly is reasonable + +Some idioms are widely used and generally safe: + +- Reading transaction/chain context: `chainid()`, `gas()`, `returndatasize()`. +- Probing code: `codesize()`, `extcodesize(addr)`, `extcodehash(addr)`. +- Reading the free memory pointer: `mload(0x40)`. +- Cheap hashing of a known memory layout, when paired with `("memory-safe")`. + +If you must use assembly: + +1. Keep the block minimal and well-commented. +2. Add the `("memory-safe")` flag when the block does not violate Solidity's memory model, so + the optimizer (and reviewers) can rely on it. The legacy + `/// @solidity memory-safe-assembly` NatSpec marker on the line directly above the block is + also recognized for compatibility with older codebases. +3. Suppress the lint locally to mark the block as audited: + ```solidity + // forge-lint: disable-next-line(inline-assembly) + assembly ("memory-safe") { /* reviewed: ... */ } + ``` + +## Example + +### Bad + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory) { + assembly { + let ok := call(gas(), target, 0, add(data.offset, 0), data.length, 0, 0) + // ... + } +} +``` + +### Good + +```solidity +function rawCall(address target, bytes calldata data) external returns (bytes memory result) { + bool ok; + (ok, result) = target.call(data); + require(ok, "call failed"); +} +``` diff --git a/crates/lint/docs/interface-file-naming.md b/crates/lint/docs/interface-file-naming.md new file mode 100644 index 0000000000000..ff72a0c175e8e --- /dev/null +++ b/crates/lint/docs/interface-file-naming.md @@ -0,0 +1,31 @@ +# Interface file naming + +**Severity**: `Info` +**ID**: `interface-file-naming` + +Flags Solidity files whose only top-level declaration is an interface but whose filename is not +prefixed with `I`. + +## What it does + +Reports interface-only files whose path basename does not start with `I` (e.g. `IERC20.sol`). + +## Why is this bad? + +Prefixing interface filenames with `I` is the prevailing convention in the Solidity ecosystem. +Following it makes import paths predictable and lets reviewers tell at a glance whether they are +looking at an interface or an implementation. + +## Example + +### Bad + +```text +contracts/Token.sol // file contains only `interface Token { ... }` +``` + +### Good + +```text +contracts/IToken.sol // file contains only `interface IToken { ... }` +``` diff --git a/crates/lint/docs/interface-naming.md b/crates/lint/docs/interface-naming.md new file mode 100644 index 0000000000000..5c6b12b946091 --- /dev/null +++ b/crates/lint/docs/interface-naming.md @@ -0,0 +1,31 @@ +# Interface name should be prefixed with 'I' + +**Severity**: `Info` +**ID**: `interface-naming` + +Flags `interface` declarations whose names are not prefixed with `I`. + +## What it does + +Reports `interface Foo` where `Foo` does not start with `I` (e.g. `IFoo`). + +## Why is this bad? + +Prefixing interfaces with `I` is the prevailing convention in Solidity codebases (`IERC20`, +`IERC721`, `IUniswapV2Pair`, ...). Following it makes the role of each type unambiguous at use +sites and aligns with the matching +[`interface-file-naming`](https://getfoundry.sh/forge/linting/interface-file-naming) lint. + +## Example + +### Bad + +```solidity +interface ERC20 { /* ... */ } +``` + +### Good + +```solidity +interface IERC20 { /* ... */ } +``` diff --git a/crates/lint/docs/missing-zero-check.md b/crates/lint/docs/missing-zero-check.md new file mode 100644 index 0000000000000..7eab1f3a00117 --- /dev/null +++ b/crates/lint/docs/missing-zero-check.md @@ -0,0 +1,39 @@ +# Missing zero-address check + +**Severity**: `Low` +**ID**: `missing-zero-check` + +Flags entry-point functions and constructors where an `address` parameter flows into a state write +or value transfer without a zero-address guard. + +## What it does + +Performs a taint analysis from each `address` parameter of an externally callable, state-mutating +function (or constructor) and reports a parameter that reaches a sink (state write, `transfer`, +`call{value: ...}`, etc.) without first being compared against `address(0)` in an `if`/`require`/ +`assert` predicate. + +## Why is this bad? + +Forgetting a zero-address check is a common source of value loss: tokens become permanently +unrecoverable, ownership is renounced unintentionally, or upgrades are bricked. Adding an explicit +guard is cheap and removes an entire class of operational mistakes. + +## Example + +### Bad + +```solidity +function setOwner(address newOwner) external onlyOwner { + owner = newOwner; // no zero-address check +} +``` + +### Good + +```solidity +function setOwner(address newOwner) external onlyOwner { + require(newOwner != address(0), "zero address"); + owner = newOwner; +} +``` diff --git a/crates/lint/docs/mixed-case-function.md b/crates/lint/docs/mixed-case-function.md new file mode 100644 index 0000000000000..9997dcb5691c7 --- /dev/null +++ b/crates/lint/docs/mixed-case-function.md @@ -0,0 +1,32 @@ +# Function names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-function` + +Flags function names that do not follow `mixedCase`. + +## What it does + +Reports functions whose names contain underscores, start with an uppercase letter, or otherwise +deviate from `mixedCase`. Test functions starting with `test`, `invariant_`, or `statefulFuzz` +and user-defined patterns (e.g. `ERC20`) are exempted. + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for function names. Consistent style makes call +sites uniform, helps editor tooling, and reduces friction in code review. + +## Example + +### Bad + +```solidity +function get_balance() external view returns (uint256); +function GetBalance() external view returns (uint256); +``` + +### Good + +```solidity +function getBalance() external view returns (uint256); +``` diff --git a/crates/lint/docs/mixed-case-variable.md b/crates/lint/docs/mixed-case-variable.md new file mode 100644 index 0000000000000..3341e1a0c48ad --- /dev/null +++ b/crates/lint/docs/mixed-case-variable.md @@ -0,0 +1,36 @@ +# Mutable variable names should use mixedCase + +**Severity**: `Info` +**ID**: `mixed-case-variable` + +Flags mutable variable names (locals, parameters, mutable state) that do not follow `mixedCase`. + +## What it does + +Reports mutable variable identifiers that contain underscores, start with an uppercase letter, +or otherwise deviate from `mixedCase`. + +`constant` and `immutable` state variables are not flagged by this lint — see +[`screaming-snake-case-const`](https://getfoundry.sh/forge/linting/screaming-snake-case-const) and +[`screaming-snake-case-immutable`](https://getfoundry.sh/forge/linting/screaming-snake-case-immutable). + +## Why is this bad? + +The Solidity style guide recommends `mixedCase` for mutable variables. Consistent style makes +code easier to scan and review. + +## Example + +### Bad + +```solidity +uint256 public total_supply; +address Owner; +``` + +### Good + +```solidity +uint256 public totalSupply; +address owner; +``` diff --git a/crates/lint/docs/multi-contract-file.md b/crates/lint/docs/multi-contract-file.md new file mode 100644 index 0000000000000..beabc827e4ea6 --- /dev/null +++ b/crates/lint/docs/multi-contract-file.md @@ -0,0 +1,37 @@ +# Multiple contracts in one file + +**Severity**: `Info` +**ID**: `multi-contract-file` + +Flags source files that declare more than one top-level contract, interface, or library. + +## What it does + +Reports each top-level `contract`, `interface`, or `library` definition (after the first) in a +file that contains more than one such declaration. + +## Why is this bad? + +Keeping one contract per file improves discoverability (`grep`, IDE jump-to-file), simplifies +import paths, and avoids unintentional bytecode bloat from artifacts that bundle unrelated +contracts. + +## Example + +### Bad + +```solidity +// File: Token.sol +contract TokenA { /* ... */ } +contract TokenB { /* ... */ } +``` + +### Good + +```solidity +// File: TokenA.sol +contract TokenA { /* ... */ } + +// File: TokenB.sol +contract TokenB { /* ... */ } +``` diff --git a/crates/lint/docs/named-struct-fields.md b/crates/lint/docs/named-struct-fields.md new file mode 100644 index 0000000000000..45713e2555ddc --- /dev/null +++ b/crates/lint/docs/named-struct-fields.md @@ -0,0 +1,31 @@ +# Prefer named struct fields + +**Severity**: `Info` +**ID**: `named-struct-fields` + +Flags struct construction expressions that pass fields positionally instead of by name. + +## What it does + +Reports `Struct(a, b, c)` style struct construction; suggests `Struct({ field1: a, field2: b, +field3: c })` instead. + +## Why is this bad? + +Positional struct construction is fragile: adding or reordering fields silently changes the +meaning of every existing call site. Named-field construction is self-documenting and resilient +to struct changes. + +## Example + +### Bad + +```solidity +User memory u = User(addr, 100, true); +``` + +### Good + +```solidity +User memory u = User({ wallet: addr, balance: 100, active: true }); +``` diff --git a/crates/lint/docs/pascal-case-struct.md b/crates/lint/docs/pascal-case-struct.md new file mode 100644 index 0000000000000..02a243bd56bf4 --- /dev/null +++ b/crates/lint/docs/pascal-case-struct.md @@ -0,0 +1,31 @@ +# Struct names should use PascalCase + +**Severity**: `Info` +**ID**: `pascal-case-struct` + +Flags struct definitions whose names do not follow `PascalCase`. + +## What it does + +Reports any `struct` whose identifier does not match the `PascalCase` convention. + +## Why is this bad? + +The Solidity style guide recommends `PascalCase` for type-like names (contracts, structs, +enums, libraries). Consistent casing makes code easier to scan and integrates with editor +features and external tooling. + +## Example + +### Bad + +```solidity +struct user_info { uint256 balance; } +struct USERINFO { uint256 balance; } +``` + +### Good + +```solidity +struct UserInfo { uint256 balance; } +``` diff --git a/crates/lint/docs/pragma-inconsistent.md b/crates/lint/docs/pragma-inconsistent.md new file mode 100644 index 0000000000000..095f45783773d --- /dev/null +++ b/crates/lint/docs/pragma-inconsistent.md @@ -0,0 +1,41 @@ +# Inconsistent pragma directives + +**Severity**: `Info` +**ID**: `pragma-inconsistent` + +Flags projects whose source files declare incompatible or differently-shaped Solidity version +pragmas. + +## What it does + +Inspects every `pragma solidity ...;` directive across all input source files and reports when +their version requirements are inconsistent (different exact versions, mixed caret/tilde/range +shapes, etc.). + +## Why is this bad? + +A project compiled under multiple Solidity versions can subtly change behavior between files +(e.g. checked arithmetic, default visibility, ABI encoding). Aligning pragmas across the project +removes a hidden source of integration bugs and makes upgrades coordinated. + +## Example + +### Bad + +```solidity +// A.sol +pragma solidity 0.8.18; + +// B.sol +pragma solidity ^0.8.20; + +// C.sol +pragma solidity >=0.7.0 <0.9.0; +``` + +### Good + +```solidity +// All files +pragma solidity 0.8.20; +``` diff --git a/crates/lint/docs/rtlo.md b/crates/lint/docs/rtlo.md new file mode 100644 index 0000000000000..58ce648752c6f --- /dev/null +++ b/crates/lint/docs/rtlo.md @@ -0,0 +1,32 @@ +# Right-to-left override character + +**Severity**: `High` +**ID**: `rtlo` + +Flags the presence of Unicode bidirectional override characters in source code, which can be used +to hide malicious behavior ("Trojan Source", [CVE-2021-42574](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574)). + +## What it does + +Detects the right-to-left override codepoint (`U+202E`) and other bidirectional control characters +embedded in identifiers, strings, and comments. + +## Why is this bad? + +These characters render source code in a different visual order than how the compiler reads it, +allowing an attacker to make malicious code look benign on review. Solidity contracts are public +and frequently audited visually; this attack vector must not be ignored. + +## Example + +### Bad + +```solidity +// transfer(victim‮, attacker)/* // U+202E hidden between args +``` + +### Good + +```solidity +// Avoid bidirectional override characters in code and comments. +``` diff --git a/crates/lint/docs/screaming-snake-case-const.md b/crates/lint/docs/screaming-snake-case-const.md new file mode 100644 index 0000000000000..72a16c5875fae --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-const.md @@ -0,0 +1,30 @@ +# Constants should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-const` + +Flags `constant` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `constant` whose identifier deviates from `SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for constants so they stand out from +mutable state and immutables at call sites. + +## Example + +### Bad + +```solidity +uint256 constant maxSupply = 1_000_000; +uint256 constant Max_Supply = 1_000_000; +``` + +### Good + +```solidity +uint256 constant MAX_SUPPLY = 1_000_000; +``` diff --git a/crates/lint/docs/screaming-snake-case-immutable.md b/crates/lint/docs/screaming-snake-case-immutable.md new file mode 100644 index 0000000000000..cee5590e16d27 --- /dev/null +++ b/crates/lint/docs/screaming-snake-case-immutable.md @@ -0,0 +1,31 @@ +# Immutables should use SCREAMING_SNAKE_CASE + +**Severity**: `Info` +**ID**: `screaming-snake-case-immutable` + +Flags `immutable` state variables whose names do not follow `SCREAMING_SNAKE_CASE`. + +## What it does + +Reports state variables declared `immutable` whose identifier deviates from +`SCREAMING_SNAKE_CASE`. + +## Why is this bad? + +The Solidity style guide recommends `SCREAMING_SNAKE_CASE` for `immutable` variables so they +visually align with `constant` ones and stand out from mutable state at call sites. + +## Example + +### Bad + +```solidity +address immutable owner; +address immutable Owner; +``` + +### Good + +```solidity +address immutable OWNER; +``` diff --git a/crates/lint/docs/too-many-digits.md b/crates/lint/docs/too-many-digits.md new file mode 100644 index 0000000000000..5decb67bec9c3 --- /dev/null +++ b/crates/lint/docs/too-many-digits.md @@ -0,0 +1,32 @@ +# Numeric literal with too many digits + +**Severity**: `Info` +**ID**: `too-many-digits` + +Flags numeric literals containing five or more consecutive zeros, which are easy to misread. + +## What it does + +Reports decimal numeric literals that contain a run of 5 or more `0` characters. + +## Why is this bad? + +Long sequences of zeros are difficult to count visually, and an off-by-one zero is a common bug +(e.g. funding `1_000_000` instead of `10_000_000`). Use scientific notation, sub-denominations, or +underscore separators to make the magnitude obvious. + +## Example + +### Bad + +```solidity +uint256 amount = 1000000000000000000; +``` + +### Good + +```solidity +uint256 amount = 1e18; +uint256 amount2 = 1 ether; +uint256 amount3 = 1_000_000_000_000_000_000; +``` diff --git a/crates/lint/docs/tx-origin.md b/crates/lint/docs/tx-origin.md new file mode 100644 index 0000000000000..26877cf9c0116 --- /dev/null +++ b/crates/lint/docs/tx-origin.md @@ -0,0 +1,34 @@ +# Use of tx.origin for authorization + +**Severity**: `Med` +**ID**: `tx-origin` + +Flags use of `tx.origin` inside authorization-like predicates such as `require`, `assert`, `if`, +`while`, and `for` conditions. + +## What it does + +Reports `tx.origin` reads when they are used as part of a guard condition. Plain reads outside of +guard predicates are not reported. + +## Why is this bad? + +`tx.origin` is the original externally owned account that started the whole transaction, not the +immediate caller. If authorization checks rely on `tx.origin`, a malicious contract can call the +protected contract while the legitimate owner is the transaction origin. + +Use `msg.sender` for authorization checks instead. + +## Example + +### Bad + +```solidity +require(tx.origin == owner, "not owner"); +``` + +### Good + +```solidity +require(msg.sender == owner, "not owner"); +``` diff --git a/crates/lint/docs/unaliased-plain-import.md b/crates/lint/docs/unaliased-plain-import.md new file mode 100644 index 0000000000000..be8c5120028d6 --- /dev/null +++ b/crates/lint/docs/unaliased-plain-import.md @@ -0,0 +1,34 @@ +# Unaliased plain import + +**Severity**: `Info` +**ID**: `unaliased-plain-import` + +Flags `import "path";` statements that pull in every top-level symbol from another file without +an alias. + +## What it does + +Reports plain imports of the form `import "path";`. Suggests using either named imports +(`import { A, B } from "path"`) or an aliased import (`import "path" as X`). + +## Why is this bad? + +Plain imports pollute the importing file's namespace and make the source of each symbol +non-obvious. Named or aliased imports make the dependency surface explicit and reduce the chance +of accidental name collisions. + +## Example + +### Bad + +```solidity +import "./Lib.sol"; +``` + +### Good + +```solidity +import { Foo, Bar } from "./Lib.sol"; +// or +import "./Lib.sol" as Lib; +``` diff --git a/crates/lint/docs/unchecked-call.md b/crates/lint/docs/unchecked-call.md new file mode 100644 index 0000000000000..9a0a4143a0e0e --- /dev/null +++ b/crates/lint/docs/unchecked-call.md @@ -0,0 +1,34 @@ +# Unchecked low-level call + +**Severity**: `High` +**ID**: `unchecked-call` + +Flags low-level calls (`call`, `delegatecall`, `staticcall`, `callcode`) whose `success` return +value is ignored. + +## What it does + +Warns when the boolean returned by a low-level call is discarded — either because the return value +is not assigned or because only the `bytes memory` payload is used. + +## Why is this bad? + +Low-level calls do **not** revert when the callee fails; they silently return `false`. Ignoring +the success flag means a failed call is indistinguishable from a successful one, leading to bugs +where state is updated on the assumption that an external interaction succeeded. + +## Example + +### Bad + +```solidity +target.call(data); // success ignored +(, bytes memory ret) = target.call(data); // only payload kept +``` + +### Good + +```solidity +(bool ok, ) = target.call(data); +require(ok, "call failed"); +``` diff --git a/crates/lint/docs/unsafe-cheatcode.md b/crates/lint/docs/unsafe-cheatcode.md new file mode 100644 index 0000000000000..0aef657b0b7be --- /dev/null +++ b/crates/lint/docs/unsafe-cheatcode.md @@ -0,0 +1,35 @@ +# Usage of unsafe cheatcodes + +**Severity**: `Info` +**ID**: `unsafe-cheatcode` + +Flags use of Foundry cheatcodes that perform dangerous side effects (filesystem access, network +activity, environment variable reads, etc.) so they cannot slip into production code unnoticed. + +## What it does + +Reports calls to cheatcodes whose effects extend beyond the EVM sandbox or that bypass typical +test invariants. The flagged set follows the cheatcode's +[`Safety::Unsafe`](https://book.getfoundry.sh/cheatcodes) classification. + +## Why is this bad? + +Unsafe cheatcodes can read/write files, hit the network, or fork external state. They are +appropriate in tests with explicit intent but should not be added without review, and must +never end up in shipped contract code. + +## Example + +### Bad + +```solidity +vm.writeFile("./out.txt", data); // unsafe — writes to host filesystem +vm.envString("PRIVATE_KEY"); // unsafe — reads host environment +``` + +### Good + +```solidity +// Use safe cheatcodes (vm.expectRevert, vm.prank, vm.warp, ...) and explicit +// inputs/fixtures instead of pulling state from the host environment. +``` diff --git a/crates/lint/docs/unsafe-typecast.md b/crates/lint/docs/unsafe-typecast.md new file mode 100644 index 0000000000000..89d493eec3c3f --- /dev/null +++ b/crates/lint/docs/unsafe-typecast.md @@ -0,0 +1,40 @@ +# Unsafe typecast + +**Severity**: `Med` +**ID**: `unsafe-typecast` + +Flags explicit numeric typecasts that can silently truncate or alter the value. + +## What it does + +Reports casts where the source value's type is wider than the target type +(e.g. `uint256 → uint128`, `int256 → uint128`), unless the cast is preceded by a check that +guarantees the value fits in the target. + +## Why is this bad? + +Solidity does **not** revert on narrowing casts; it silently keeps the lowest bits, which can +cause severe accounting bugs (e.g. amount overflows, wrong fees, broken invariants). Use a checked +cast helper such as OpenZeppelin's `SafeCast` whenever the source value is not provably bounded. + +## Example + +### Bad + +```solidity +function setAmount(uint256 amount) external { + smallAmount = uint128(amount); // silent truncation if amount >= 2**128 +} +``` + +### Good + +```solidity +function setAmount(uint256 amount) external { + require(amount <= type(uint128).max, "overflow"); + smallAmount = uint128(amount); +} + +// or +smallAmount = SafeCast.toUint128(amount); +``` diff --git a/crates/lint/docs/unused-import.md b/crates/lint/docs/unused-import.md new file mode 100644 index 0000000000000..08f2545a36587 --- /dev/null +++ b/crates/lint/docs/unused-import.md @@ -0,0 +1,40 @@ +# Unused import + +**Severity**: `Info` +**ID**: `unused-import` + +Flags imported symbols (or whole import statements) whose imported names are not referenced +anywhere in the source unit. + +## What it does + +Reports `import "..."`, `import "..." as X`, and `import { A, B } from "..."` statements where one +or more imported names are never used. Symbols brought in via `import * as X` are tracked through +`X.member` accesses. + +## Why is this bad? + +Unused imports add noise, slow down compilation, can cause name collisions, and frequently +indicate dead code or stale refactors. + +## Example + +### Bad + +```solidity +import { A, B } from "./Lib.sol"; // B is never used + +contract C { + A internal a; +} +``` + +### Good + +```solidity +import { A } from "./Lib.sol"; + +contract C { + A internal a; +} +``` diff --git a/crates/lint/docs/unused-state-variables.md b/crates/lint/docs/unused-state-variables.md new file mode 100644 index 0000000000000..758c6e58b911b --- /dev/null +++ b/crates/lint/docs/unused-state-variables.md @@ -0,0 +1,39 @@ +# Unused state variable + +**Severity**: `Gas` +**ID**: `unused-state-variables` + +Flags state variables that are declared but never read or written anywhere in the contract or its +descendants. + +## What it does + +Reports each state variable that has no read or write site across the project. + +## Why is this bad? + +Unused state variables waste storage slots, inflate deployment cost, and are a strong signal of +dead or stale code that should be removed. + +## Example + +### Bad + +```solidity +contract C { + uint256 unused; // never read or written + uint256 public total; // used elsewhere +} +``` + +### Good + +```solidity +contract C { + uint256 public total; +} +``` + +## Notes + +This is a `Gas`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/docs/unwrapped-modifier-logic.md b/crates/lint/docs/unwrapped-modifier-logic.md new file mode 100644 index 0000000000000..985c79962af07 --- /dev/null +++ b/crates/lint/docs/unwrapped-modifier-logic.md @@ -0,0 +1,51 @@ +# Unwrapped modifier logic + +**Severity**: `CodeSize` +**ID**: `unwrapped-modifier-logic` + +Flags modifiers whose body contains non-trivial logic that should be moved into a helper function +to reduce contract code size. + +## What it does + +Reports modifiers whose body contains statements other than a single placeholder, simple builtin +calls (`require`/`assert`), or a single library function call. Modifiers that use inline assembly +are exempted. + +## Why is this bad? + +Solidity inlines a modifier's body at every call site, so any non-trivial logic is duplicated +across all functions that use the modifier. Wrapping the logic in an internal function and calling +it from the modifier keeps the bytecode small while preserving behavior. + +## Example + +### Bad + +```solidity +modifier onlyAuth() { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; + _; +} +``` + +### Good + +```solidity +modifier onlyAuth() { + _checkAuth(); + _; +} + +function _checkAuth() internal { + if (!auth[msg.sender]) revert NotAuth(); + bytes32 nonce = keccak256(abi.encodePacked(msg.sender, block.number)); + seenNonce[nonce] = true; +} +``` + +## Notes + +This is a `CodeSize`-severity lint and is **not** applied to test or script files. diff --git a/crates/lint/src/linter/late.rs b/crates/lint/src/linter/late.rs index f7e97d00e7612..ffdc284cacc56 100644 --- a/crates/lint/src/linter/late.rs +++ b/crates/lint/src/linter/late.rs @@ -385,6 +385,7 @@ mod tests { false, LinterConfig { inline: &inline, lint_specific: &lint_specific }, Vec::new(), + None, ); let mut passes: Vec>> = vec![Box::new(RecordingPass { counts: counts.clone() })]; diff --git a/crates/lint/src/linter/mod.rs b/crates/lint/src/linter/mod.rs index fc140643eb68a..3e38a02726605 100644 --- a/crates/lint/src/linter/mod.rs +++ b/crates/lint/src/linter/mod.rs @@ -1,8 +1,10 @@ mod early; mod late; +mod project; pub use early::{EarlyLintPass, EarlyLintVisitor}; pub use late::{LateLintPass, LateLintVisitor}; +pub use project::{ProjectLintEmitter, ProjectLintPass, ProjectSource}; use foundry_common::comments::inline_config::InlineConfig; use foundry_compilers::Language; @@ -16,10 +18,11 @@ use solar::{ diagnostics::{ Applicability, DiagBuilder, DiagId, DiagMsg, MultiSpan, Style, SuggestionStyle, }, + source_map::SourceFile, }, sema::Compiler, }; -use std::path::PathBuf; +use std::{path::PathBuf, sync::Arc}; /// Trait representing a generic linter for analyzing and reporting issues in smart contract source /// code files. @@ -54,6 +57,8 @@ pub struct LintContext<'s, 'c> { with_json_emitter: bool, pub config: LinterConfig<'c>, active_lints: Vec<&'static str>, + /// The source file currently being linted, when known. + source_file: Option>, } pub struct LinterConfig<'s> { @@ -68,8 +73,9 @@ impl<'s, 'c> LintContext<'s, 'c> { with_json_emitter: bool, config: LinterConfig<'c>, active_lints: Vec<&'static str>, + source_file: Option>, ) -> Self { - Self { sess, with_description, with_json_emitter, config, active_lints } + Self { sess, with_description, with_json_emitter, config, active_lints, source_file } } fn add_help<'a>(&self, diag: DiagBuilder<'a, ()>, help: &'static str) -> DiagBuilder<'a, ()> { @@ -81,6 +87,11 @@ impl<'s, 'c> LintContext<'s, 'c> { self.sess } + /// Returns the source file currently being linted, if any. + pub const fn source_file(&self) -> Option<&Arc> { + self.source_file.as_ref() + } + // Helper method to check if a lint id is enabled. // // For performance reasons, some passes check several lints at once. Thus, this method is @@ -108,6 +119,25 @@ impl<'s, 'c> LintContext<'s, 'c> { diag.emit(); } + /// Emit a diagnostic with a caller-provided message instead of the lint's description. + /// + /// Useful when the message must vary per occurrence (e.g. embedding the offending + /// codepoint detected by the `rtlo` lint). + pub fn emit_with_msg(&self, lint: &'static L, span: Span, msg: impl Into) { + if self.config.inline.is_id_disabled(span, lint.id()) || !self.is_lint_enabled(lint.id()) { + return; + } + + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), msg.into()) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)); + + self.add_help(diag, lint.help()).emit(); + } + /// Emit a diagnostic with a code suggestion. /// /// If no span is provided for [`SuggestionKind::Fix`], it will use the lint's span. diff --git a/crates/lint/src/linter/project.rs b/crates/lint/src/linter/project.rs new file mode 100644 index 0000000000000..38fc1ad1ba59f --- /dev/null +++ b/crates/lint/src/linter/project.rs @@ -0,0 +1,92 @@ +use super::{Lint, LintContext, LinterConfig}; +use foundry_common::comments::inline_config::InlineConfig; +use foundry_config::lint::LintSpecificConfig; +use solar::{ + ast, + interface::{Session, Span, diagnostics::DiagMsg, source_map::SourceFile}, +}; +use std::{path::PathBuf, sync::Arc}; + +/// A single source unit visible to a project-wide lint pass, pre-loaded with its inline config so +/// emits respect `// forge-lint: disable-*` markers without rebuilding it per emit. +pub struct ProjectSource<'ast> { + pub path: PathBuf, + pub file: Arc, + pub ast: &'ast ast::SourceUnit<'ast>, + pub inline_config: InlineConfig>, +} + +/// Trait for lints that need to inspect every input source at once (e.g. cross-file checks). +/// +/// `check_project` runs once after all per-file [`super::EarlyLintPass`] / +/// [`super::LateLintPass`] passes have completed. +pub trait ProjectLintPass<'ast>: Send + Sync { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]); +} + +/// Helper passed to [`ProjectLintPass::check_project`] for emitting diagnostics against a specific +/// source. +pub struct ProjectLintEmitter<'s, 'c> { + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, +} + +impl<'s, 'c> ProjectLintEmitter<'s, 'c> { + pub const fn new( + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, + ) -> Self { + Self { sess, with_description, with_json_emitter, lint_specific, active_lints } + } + + /// Returns `true` if the given lint id is enabled for this run. Project passes that perform + /// expensive analysis should guard their work behind this check. + pub fn is_lint_enabled(&self, id: &'static str) -> bool { + self.active_lints.contains(&id) + } + + /// Emits a diagnostic with the lint's default description as the message. + pub fn emit<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + ) where + 'c: 'a, + { + self.build_ctx(source).emit(lint, span); + } + + /// Emits a diagnostic with a caller-provided message. + pub fn emit_with_msg<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + msg: impl Into, + ) where + 'c: 'a, + { + self.build_ctx(source).emit_with_msg(lint, span, msg); + } + + fn build_ctx<'a, 'ast>(&'a self, source: &'a ProjectSource<'ast>) -> LintContext<'s, 'a> + where + 'c: 'a, + { + LintContext::new( + self.sess, + self.with_description, + self.with_json_emitter, + LinterConfig { inline: &source.inline_config, lint_specific: self.lint_specific }, + self.active_lints.clone(), + Some(source.file.clone()), + ) + } +} diff --git a/crates/lint/src/sol/gas/immutable.rs b/crates/lint/src/sol/gas/immutable.rs new file mode 100644 index 0000000000000..5baba86996841 --- /dev/null +++ b/crates/lint/src/sol/gas/immutable.rs @@ -0,0 +1,406 @@ +use super::CouldBeImmutable; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{self, UnOpKind}, + interface::{kw, sym}, + sema::hir::{self, ExprKind, Res, StmtKind, TypeKind}, +}; +use std::collections::HashSet; + +declare_forge_lint!( + COULD_BE_IMMUTABLE, + Severity::Gas, + "could-be-immutable", + "state variable could be declared immutable" +); + +impl<'hir> LateLintPass<'hir> for CouldBeImmutable { + fn check_nested_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract_id: hir::ContractId, + ) { + let contract = hir.contract(contract_id); + if contract.kind == ast::ContractKind::Interface { + return; + } + if !is_most_derived_contract(hir, contract_id) { + return; + } + + let candidates: Vec<_> = contract + .linearized_bases + .iter() + .flat_map(|&contract_id| hir.contract(contract_id).variables()) + .filter(|&id| is_immutable_candidate_type(hir.variable(id))) + .collect(); + + if candidates.is_empty() { + return; + } + let candidate_set: HashSet<_> = candidates.iter().copied().collect(); + + if contract_contains_unlowered_stmt(hir, contract) { + return; + } + + let mut constructor_writes = HashSet::new(); + let mut runtime_writes = HashSet::new(); + + for &var_id in &candidates { + let var = hir.variable(var_id); + if var.initializer.is_some_and(|expr| !is_compile_time_constant(hir, expr)) { + constructor_writes.insert(var_id); + } + } + + for &contract_id in contract.linearized_bases { + for function_id in hir.contract(contract_id).all_functions() { + let function = hir.function(function_id); + if function.is_constructor() { + collect_modifier_writes( + hir, + function, + &candidate_set, + &mut constructor_writes, + &mut runtime_writes, + &mut HashSet::new(), + ); + + if let Some(body) = function.body { + collect_state_writes(hir, body, &candidate_set, &mut constructor_writes); + } + } else { + // Immutable variables can only be assigned inline or directly in constructor + // bodies, so writes hidden behind internal helpers are not valid candidates. + let mut modifier_argument_writes = HashSet::new(); + collect_modifier_writes( + hir, + function, + &candidate_set, + &mut modifier_argument_writes, + &mut runtime_writes, + &mut HashSet::new(), + ); + runtime_writes.extend(modifier_argument_writes); + + if let Some(body) = function.body { + collect_state_writes(hir, body, &candidate_set, &mut runtime_writes); + } + } + } + } + + for &var_id in &candidates { + if constructor_writes.contains(&var_id) && !runtime_writes.contains(&var_id) { + let var = hir.variable(var_id); + ctx.emit(&COULD_BE_IMMUTABLE, var.name.map_or(var.span, |name| name.span)); + } + } + } +} + +fn is_most_derived_contract(hir: &hir::Hir<'_>, contract_id: hir::ContractId) -> bool { + !hir.contracts() + .any(|contract| contract.linearized_bases.iter().skip(1).any(|&id| id == contract_id)) +} + +fn collect_modifier_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + function: &'hir hir::Function<'hir>, + candidates: &HashSet, + argument_writes: &mut HashSet, + body_writes: &mut HashSet, + visited_modifiers: &mut HashSet, +) { + for modifier in function.modifiers { + for expr in modifier.args.exprs() { + collect_expr_writes(expr, candidates, argument_writes); + } + + let Some(modifier_id) = modifier.id.as_function() else { continue }; + if !visited_modifiers.insert(modifier_id) { + continue; + } + + let modifier = hir.function(modifier_id); + let mut nested_argument_writes = HashSet::new(); + collect_modifier_writes( + hir, + modifier, + candidates, + &mut nested_argument_writes, + body_writes, + visited_modifiers, + ); + body_writes.extend(nested_argument_writes); + if let Some(body) = modifier.body { + collect_state_writes(hir, body, candidates, body_writes); + } + } +} + +fn is_immutable_candidate_type(var: &hir::Variable<'_>) -> bool { + var.is_state_variable() + && var.mutability.is_none() + && match var.ty.kind { + TypeKind::Elementary(ty) => ty.is_value_type(), + TypeKind::Custom(hir::ItemId::Contract(_)) => true, + _ => false, + } +} + +fn contract_contains_unlowered_stmt<'hir>( + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, +) -> bool { + contract.linearized_bases.iter().any(|&contract_id| { + hir.contract(contract_id).all_functions().any(|function_id| { + hir.function(function_id).body.is_some_and(|body| block_contains_unlowered_stmt(body)) + }) + }) +} + +fn block_contains_unlowered_stmt(block: hir::Block<'_>) -> bool { + block.stmts.iter().any(stmt_contains_unlowered_stmt) +} + +fn stmt_contains_unlowered_stmt(stmt: &hir::Stmt<'_>) -> bool { + match &stmt.kind { + StmtKind::Err(_) => true, + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => { + block_contains_unlowered_stmt(*block) + } + StmtKind::If(_, then_stmt, else_stmt) => { + stmt_contains_unlowered_stmt(then_stmt) + || else_stmt.is_some_and(stmt_contains_unlowered_stmt) + } + StmtKind::Try(stmt_try) => { + stmt_try.clauses.iter().any(|clause| block_contains_unlowered_stmt(clause.block)) + } + StmtKind::DeclSingle(_) + | StmtKind::DeclMulti(_, _) + | StmtKind::Emit(_) + | StmtKind::Revert(_) + | StmtKind::Return(_) + | StmtKind::Break + | StmtKind::Continue + | StmtKind::Expr(_) + | StmtKind::Placeholder => false, + } +} + +fn collect_state_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + block: hir::Block<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + for stmt in block.stmts { + collect_stmt_writes(hir, stmt, candidates, writes); + } +} + +fn collect_stmt_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + stmt: &'hir hir::Stmt<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &stmt.kind { + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => { + collect_state_writes(hir, *block, candidates, writes); + } + StmtKind::If(condition, then_stmt, else_stmt) => { + collect_expr_writes(condition, candidates, writes); + collect_stmt_writes(hir, then_stmt, candidates, writes); + if let Some(else_stmt) = else_stmt { + collect_stmt_writes(hir, else_stmt, candidates, writes); + } + } + StmtKind::Try(stmt_try) => { + collect_expr_writes(&stmt_try.expr, candidates, writes); + for clause in stmt_try.clauses { + collect_state_writes(hir, clause.block, candidates, writes); + } + } + StmtKind::DeclSingle(var_id) => { + if let Some(initializer) = hir.variable(*var_id).initializer { + collect_expr_writes(initializer, candidates, writes); + } + } + StmtKind::DeclMulti(_, expr) + | StmtKind::Emit(expr) + | StmtKind::Revert(expr) + | StmtKind::Return(Some(expr)) + | StmtKind::Expr(expr) => collect_expr_writes(expr, candidates, writes), + StmtKind::Return(None) + | StmtKind::Break + | StmtKind::Continue + | StmtKind::Placeholder + | StmtKind::Err(_) => {} + } +} + +fn collect_expr_writes<'hir>( + expr: &'hir hir::Expr<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &expr.kind { + ExprKind::Assign(lhs, _, rhs) => { + collect_lvalue_writes(lhs, candidates, writes); + collect_expr_writes(lhs, candidates, writes); + collect_expr_writes(rhs, candidates, writes); + } + ExprKind::Delete(inner) => { + collect_lvalue_writes(inner, candidates, writes); + collect_expr_writes(inner, candidates, writes); + } + ExprKind::Unary(op, inner) => { + if op.kind.has_side_effects() { + collect_lvalue_writes(inner, candidates, writes); + } + collect_expr_writes(inner, candidates, writes); + } + ExprKind::Array(exprs) => { + for expr in *exprs { + collect_expr_writes(expr, candidates, writes); + } + } + ExprKind::Binary(lhs, _, rhs) => { + collect_expr_writes(lhs, candidates, writes); + collect_expr_writes(rhs, candidates, writes); + } + ExprKind::Call(callee, args, named_args) => { + collect_expr_writes(callee, candidates, writes); + for expr in args.exprs() { + collect_expr_writes(expr, candidates, writes); + } + if let Some(named_args) = named_args { + for arg in *named_args { + collect_expr_writes(&arg.value, candidates, writes); + } + } + } + ExprKind::Index(base, index) => { + collect_expr_writes(base, candidates, writes); + if let Some(index) = index { + collect_expr_writes(index, candidates, writes); + } + } + ExprKind::Slice(base, start, end) => { + collect_expr_writes(base, candidates, writes); + if let Some(start) = start { + collect_expr_writes(start, candidates, writes); + } + if let Some(end) = end { + collect_expr_writes(end, candidates, writes); + } + } + ExprKind::Member(base, _) | ExprKind::Payable(base) => { + collect_expr_writes(base, candidates, writes); + } + ExprKind::Ternary(condition, then_expr, else_expr) => { + collect_expr_writes(condition, candidates, writes); + collect_expr_writes(then_expr, candidates, writes); + collect_expr_writes(else_expr, candidates, writes); + } + ExprKind::Tuple(exprs) => { + for expr in exprs.iter().flatten() { + collect_expr_writes(expr, candidates, writes); + } + } + ExprKind::Ident(_) + | ExprKind::Lit(_) + | ExprKind::New(_) + | ExprKind::TypeCall(_) + | ExprKind::Type(_) + | ExprKind::Err(_) => {} + } +} + +fn collect_lvalue_writes( + expr: &hir::Expr<'_>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &expr.peel_parens().kind { + ExprKind::Ident([Res::Item(hir::ItemId::Variable(id)), ..]) if candidates.contains(id) => { + writes.insert(*id); + } + ExprKind::Tuple(exprs) => { + for expr in exprs.iter().flatten() { + collect_lvalue_writes(expr, candidates, writes); + } + } + ExprKind::Index(base, _) + | ExprKind::Slice(base, _, _) + | ExprKind::Member(base, _) + | ExprKind::Payable(base) => collect_lvalue_writes(base, candidates, writes), + _ => {} + } +} + +fn is_compile_time_constant(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + ExprKind::Lit(_) | ExprKind::Type(_) | ExprKind::TypeCall(_) => true, + ExprKind::Ident(resolutions) => resolutions.iter().all(|res| match res { + Res::Item(hir::ItemId::Variable(var_id)) => hir.variable(*var_id).is_constant(), + _ => false, + }), + ExprKind::Unary(op, inner) => { + !matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ) && is_compile_time_constant(hir, inner) + } + ExprKind::Binary(lhs, _, rhs) => { + is_compile_time_constant(hir, lhs) && is_compile_time_constant(hir, rhs) + } + ExprKind::Call(callee, args, named_args) => { + is_allowed_constant_call(callee) + && args.exprs().all(|expr| is_compile_time_constant(hir, expr)) + && named_args.is_none_or(|args| { + args.iter().all(|arg| is_compile_time_constant(hir, &arg.value)) + }) + } + ExprKind::Ternary(condition, then_expr, else_expr) => { + is_compile_time_constant(hir, condition) + && is_compile_time_constant(hir, then_expr) + && is_compile_time_constant(hir, else_expr) + } + ExprKind::Tuple(exprs) => { + exprs.iter().flatten().all(|expr| is_compile_time_constant(hir, expr)) + } + ExprKind::Array(_) + | ExprKind::Assign(_, _, _) + | ExprKind::Delete(_) + | ExprKind::Index(_, _) + | ExprKind::Slice(_, _, _) + | ExprKind::Member(_, _) + | ExprKind::New(_) + | ExprKind::Payable(_) + | ExprKind::Err(_) => false, + } +} + +fn is_allowed_constant_call(callee: &hir::Expr<'_>) -> bool { + match &callee.kind { + ExprKind::Type(_) => true, + ExprKind::Ident([Res::Builtin(builtin), ..]) => { + let name = builtin.name(); + name == kw::Keccak256 + || name == kw::Addmod + || name == kw::Mulmod + || name == sym::sha256 + || name == sym::ripemd160 + || name == sym::ecrecover + } + _ => false, + } +} diff --git a/crates/lint/src/sol/gas/mod.rs b/crates/lint/src/sol/gas/mod.rs index 9a4c37f925cd5..2d5ce2a1becc1 100644 --- a/crates/lint/src/sol/gas/mod.rs +++ b/crates/lint/src/sol/gas/mod.rs @@ -1,8 +1,17 @@ use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod custom_errors; +mod immutable; mod keccak; +mod unused_state_variables; use custom_errors::CUSTOM_ERRORS; +use immutable::COULD_BE_IMMUTABLE; use keccak::ASM_KECCAK256; +use unused_state_variables::UNUSED_STATE_VARIABLES; -register_lints!((CustomErrors, early, (CUSTOM_ERRORS)), (AsmKeccak256, late, (ASM_KECCAK256))); +register_lints!( + (AsmKeccak256, late, (ASM_KECCAK256)), + (CustomErrors, early, (CUSTOM_ERRORS)), + (CouldBeImmutable, late, (COULD_BE_IMMUTABLE)), + (UnusedStateVariables, late, (UNUSED_STATE_VARIABLES)), +); diff --git a/crates/lint/src/sol/gas/unused_state_variables.rs b/crates/lint/src/sol/gas/unused_state_variables.rs new file mode 100644 index 0000000000000..78d32c196b20c --- /dev/null +++ b/crates/lint/src/sol/gas/unused_state_variables.rs @@ -0,0 +1,90 @@ +use super::UnusedStateVariables; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::ContractKind, + interface::data_structures::Never, + sema::hir::{self, Visit as _}, +}; +use std::{collections::HashSet, ops::ControlFlow}; + +declare_forge_lint!( + UNUSED_STATE_VARIABLES, + Severity::Gas, + "unused-state-variables", + "state variable is never used" +); + +impl<'hir> LateLintPass<'hir> for UnusedStateVariables { + fn check_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, + ) { + // Skip interfaces, they cannot have mutable state variables. + if contract.kind == ContractKind::Interface { + return; + } + + // Collect state variable IDs, skipping constants and immutables + // (those are handled by the compiler and don't occupy storage slots). + let state_vars: Vec = contract + .variables() + .filter(|&var_id| { + let var = hir.variable(var_id); + !var.is_constant() && !var.is_immutable() + }) + .collect(); + + if state_vars.is_empty() { + return; + } + + // Walk the full contract — functions (including modifier call args, parameters, returns, + // and bodies) and state variable initializers — to collect every variable referenced + // anywhere in this contract. + let mut collector = UsedVarCollector { hir, used: HashSet::new() }; + for func_id in contract.all_functions() { + let _ = collector.visit_nested_function(func_id); + } + // State variables can reference other state variables in their initializers. + for var_id in contract.variables() { + let _ = collector.visit_nested_var(var_id); + } + + // Report any state variable that was never referenced. + for var_id in state_vars { + if !collector.used.contains(&var_id) { + let var = hir.variable(var_id); + ctx.emit(&UNUSED_STATE_VARIABLES, var.span); + } + } + } +} + +struct UsedVarCollector<'hir> { + hir: &'hir hir::Hir<'hir>, + used: HashSet, +} + +impl<'hir> hir::Visit<'hir> for UsedVarCollector<'hir> { + type BreakValue = Never; + + fn hir(&self) -> &'hir hir::Hir<'hir> { + self.hir + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow { + if let hir::ExprKind::Ident(resolutions) = &expr.kind { + for res in *resolutions { + if let hir::Res::Item(hir::ItemId::Variable(var_id)) = res { + self.used.insert(*var_id); + } + } + } + self.walk_expr(expr) + } +} diff --git a/crates/lint/src/sol/high/mod.rs b/crates/lint/src/sol/high/mod.rs index de199e88ff33a..09658ac232461 100644 --- a/crates/lint/src/sol/high/mod.rs +++ b/crates/lint/src/sol/high/mod.rs @@ -1,13 +1,16 @@ use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod incorrect_shift; +mod rtlo; mod unchecked_calls; use incorrect_shift::INCORRECT_SHIFT; +use rtlo::RTLO; use unchecked_calls::{ERC20_UNCHECKED_TRANSFER, UNCHECKED_CALL}; register_lints!( (IncorrectShift, early, (INCORRECT_SHIFT)), (UncheckedCall, early, (UNCHECKED_CALL)), - (UncheckedTransferERC20, late, (ERC20_UNCHECKED_TRANSFER)) + (UncheckedTransferERC20, late, (ERC20_UNCHECKED_TRANSFER)), + (Rtlo, early, (RTLO)) ); diff --git a/crates/lint/src/sol/high/rtlo.rs b/crates/lint/src/sol/high/rtlo.rs new file mode 100644 index 0000000000000..c121ac758da32 --- /dev/null +++ b/crates/lint/src/sol/high/rtlo.rs @@ -0,0 +1,58 @@ +use super::Rtlo; +use crate::{ + linter::{EarlyLintPass, Lint, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + RTLO, + Severity::High, + "rtlo", + "unicode bidirectional override character can hide malicious code" +); + +impl<'ast> EarlyLintPass<'ast> for Rtlo { + fn check_full_source_unit( + &mut self, + ctx: &LintContext<'ast, '_>, + _unit: &'ast ast::SourceUnit<'ast>, + ) { + if !ctx.is_lint_enabled(RTLO.id()) { + return; + } + + // Scan the raw source so bidi chars in comments are also caught. + let Some(file) = ctx.source_file() else { return }; + + for (offset, ch) in file.src.char_indices() { + let Some(name) = bidi_char_name(ch) else { continue }; + + let lo = file.start_pos + BytePos::from_usize(offset); + let hi = lo + BytePos::from_usize(ch.len_utf8()); + let span = Span::new(lo, hi); + + ctx.emit_with_msg(&RTLO, span, format!("U+{:04X} ({name}) detected", ch as u32)); + } + } +} + +const fn bidi_char_name(ch: char) -> Option<&'static str> { + Some(match ch { + '\u{200E}' => "Left-to-Right Mark", + '\u{200F}' => "Right-to-Left Mark", + '\u{202A}' => "Left-to-Right Embedding", + '\u{202B}' => "Right-to-Left Embedding", + '\u{202C}' => "Pop Directional Formatting", + '\u{202D}' => "Left-to-Right Override", + '\u{202E}' => "Right-to-Left Override", + '\u{2066}' => "Left-to-Right Isolate", + '\u{2067}' => "Right-to-Left Isolate", + '\u{2068}' => "First Strong Isolate", + '\u{2069}' => "Pop Directional Isolate", + _ => return None, + }) +} diff --git a/crates/lint/src/sol/info/boolean_cst.rs b/crates/lint/src/sol/info/boolean_cst.rs new file mode 100644 index 0000000000000..50a7075338d3d --- /dev/null +++ b/crates/lint/src/sol/info/boolean_cst.rs @@ -0,0 +1,116 @@ +use super::BooleanCst; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind, Stmt, StmtKind, VariableDefinition}, + interface::SpannedOption, +}; + +declare_forge_lint!(BOOLEAN_CST, Severity::Med, "boolean-cst", "misuse of a boolean constant"); + +impl<'ast> EarlyLintPass<'ast> for BooleanCst { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + } + StmtKind::While(cond, _) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: true }); + } + StmtKind::For { cond: Some(cond), .. } => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + } + StmtKind::DeclMulti(_, expr) => check_allowed_bare_expr(ctx, expr), + StmtKind::Expr(expr) | StmtKind::Return(Some(expr)) => { + check_allowed_bare_expr(ctx, expr); + } + _ => {} + } + } + + fn check_variable_definition( + &mut self, + ctx: &LintContext, + var: &'ast VariableDefinition<'ast>, + ) { + if let Some(initializer) = &var.initializer { + check_allowed_bare_expr(ctx, initializer); + } + } +} + +#[derive(Clone, Copy)] +enum ExprContext { + Condition { allow_bare_true: bool }, + General, + AllowedBare, +} + +fn check_allowed_bare_expr(ctx: &LintContext, expr: &Expr<'_>) { + let context = + if bool_literal(expr).is_some() { ExprContext::AllowedBare } else { ExprContext::General }; + check_expr(ctx, expr, context); +} + +fn check_expr(ctx: &LintContext, expr: &Expr<'_>, context: ExprContext) { + if let Some(value) = bool_literal(expr) { + match context { + ExprContext::AllowedBare => {} + ExprContext::Condition { allow_bare_true: true } if value => {} + ExprContext::Condition { .. } | ExprContext::General => { + ctx.emit(&BOOLEAN_CST, expr.span); + } + } + return; + } + + match &expr.kind { + ExprKind::Assign(_, _, rhs) => check_allowed_bare_expr(ctx, rhs), + ExprKind::Binary(left, op, right) => check_binary_expr(ctx, left, *op, right), + ExprKind::Call(_, args) => { + for arg in args.exprs() { + check_allowed_bare_expr(ctx, arg); + } + } + ExprKind::Delete(expr) | ExprKind::Unary(_, expr) => { + check_expr(ctx, expr, ExprContext::General); + } + ExprKind::Ternary(cond, true_expr, false_expr) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + check_expr(ctx, true_expr, ExprContext::General); + check_expr(ctx, false_expr, ExprContext::General); + } + ExprKind::Tuple(exprs) => { + for opt_expr in exprs.iter() { + if let SpannedOption::Some(expr) = opt_expr.as_ref() { + check_expr(ctx, expr, ExprContext::General); + } + } + } + _ => {} + } +} + +fn check_binary_expr(ctx: &LintContext, left: &Expr<'_>, op: BinOp, right: &Expr<'_>) { + if matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne) + && (bool_literal(left).is_some() || bool_literal(right).is_some()) + { + return; + } + + check_expr(ctx, left, ExprContext::General); + check_expr(ctx, right, ExprContext::General); +} + +fn bool_literal(expr: &Expr<'_>) -> Option { + let expr = expr.peel_parens(); + if let ExprKind::Lit(lit, _) = &expr.kind + && let LitKind::Bool(value) = lit.kind + { + Some(value) + } else { + None + } +} diff --git a/crates/lint/src/sol/info/boolean_equal.rs b/crates/lint/src/sol/info/boolean_equal.rs new file mode 100644 index 0000000000000..89cd7ec136f75 --- /dev/null +++ b/crates/lint/src/sol/info/boolean_equal.rs @@ -0,0 +1,108 @@ +use super::BooleanEqual; +use crate::{ + linter::{EarlyLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind}, + interface::diagnostics::Applicability, +}; + +declare_forge_lint!( + BOOLEAN_EQUAL, + Severity::Info, + "boolean-equal", + "boolean comparisons to constants should be simplified" +); + +impl<'ast> EarlyLintPass<'ast> for BooleanEqual { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Binary( + left, + op @ BinOp { kind: BinOpKind::Eq | BinOpKind::Ne, .. }, + right, + ) = &expr.kind + { + match bool_comparison_suggestion(ctx, left, op.kind, right) { + BoolComparison::WithSuggestion(simplified) => { + ctx.emit_with_suggestion( + &BOOLEAN_EQUAL, + expr.span, + Suggestion::fix(simplified, Applicability::MachineApplicable) + .with_desc("consider simplifying to"), + ); + } + BoolComparison::WithoutSuggestion => ctx.emit(&BOOLEAN_EQUAL, expr.span), + BoolComparison::None => {} + } + } + } +} + +enum BoolComparison { + WithSuggestion(String), + WithoutSuggestion, + None, +} + +fn bool_comparison_suggestion( + ctx: &LintContext, + left: &Expr<'_>, + op: BinOpKind, + right: &Expr<'_>, +) -> BoolComparison { + let left_bool = bool_literal(left); + let right_bool = bool_literal(right); + + match (left_bool, right_bool) { + (Some(value), None) => simplify_expr(ctx, right, op, value), + (None, Some(value)) => simplify_expr(ctx, left, op, value), + (Some(_), Some(_)) => BoolComparison::WithoutSuggestion, + (None, None) => BoolComparison::None, + } +} + +fn bool_literal(expr: &Expr<'_>) -> Option { + let expr = expr.peel_parens(); + if let ExprKind::Lit(lit, _) = &expr.kind + && let LitKind::Bool(value) = lit.kind + { + Some(value) + } else { + None + } +} + +fn simplify_expr( + ctx: &LintContext, + expr: &Expr<'_>, + op: BinOpKind, + constant: bool, +) -> BoolComparison { + let Some(snippet) = ctx.span_to_snippet(expr.span) else { + return BoolComparison::WithoutSuggestion; + }; + + let simplified = match (op, constant) { + (BinOpKind::Eq, true) | (BinOpKind::Ne, false) => snippet, + (BinOpKind::Eq, false) | (BinOpKind::Ne, true) if can_negate_without_parens(expr) => { + format!("!{snippet}") + } + (BinOpKind::Eq, false) | (BinOpKind::Ne, true) => format!("!({snippet})"), + _ => return BoolComparison::None, + }; + + BoolComparison::WithSuggestion(simplified) +} + +fn can_negate_without_parens(expr: &Expr<'_>) -> bool { + matches!( + expr.peel_parens().kind, + ExprKind::Call(..) + | ExprKind::CallOptions(..) + | ExprKind::Ident(_) + | ExprKind::Index(..) + | ExprKind::Lit(..) + | ExprKind::Member(..) + ) +} diff --git a/crates/lint/src/sol/info/inline_assembly.rs b/crates/lint/src/sol/info/inline_assembly.rs new file mode 100644 index 0000000000000..1111129dada34 --- /dev/null +++ b/crates/lint/src/sol/info/inline_assembly.rs @@ -0,0 +1,71 @@ +use super::InlineAssembly; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Stmt, StmtKind}, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + INLINE_ASSEMBLY, + Severity::Info, + "inline-assembly", + "usage of inline assembly; assembly bypasses Solidity safety features and should be reviewed" +); + +const ASSEMBLY_KW_LEN: u32 = 8; +const NATSPEC_MEMORY_SAFE_MARKER: &str = "@solidity memory-safe-assembly"; + +impl<'ast> EarlyLintPass<'ast> for InlineAssembly { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + let StmtKind::Assembly(asm) = &stmt.kind else { return }; + + let kw_span = assembly_keyword_span(stmt.span); + + let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe") + || has_memory_safe_natspec(ctx, stmt.span.lo()); + + let msg = if memory_safe { + "inline assembly (declared memory-safe); review business logic and side effects" + } else { + "inline assembly used; review for memory safety and side effects" + }; + + ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg); + } +} + +/// Narrows a span to the leading `assembly` keyword to keep diagnostics readable. +fn assembly_keyword_span(span: Span) -> Span { + span.with_hi(span.lo() + BytePos(ASSEMBLY_KW_LEN)) +} + +/// Returns `true` when the lines immediately preceding `stmt_lo` form a `///` NatSpec block +/// containing `@solidity memory-safe-assembly`. +fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool { + let Some(source_file) = ctx.source_file() else { return false }; + let src = source_file.src.as_str(); + let start_pos = source_file.start_pos.to_u32(); + let lo_abs = stmt_lo.to_u32(); + if lo_abs < start_pos { + return false; + } + let offset = (lo_abs - start_pos) as usize; + if offset > src.len() { + return false; + } + + for line in src[..offset].lines().rev() { + let trimmed = line.trim_start(); + if trimmed.is_empty() { + continue; + } + let Some(rest) = trimmed.strip_prefix("///") else { return false }; + if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) { + return true; + } + } + false +} diff --git a/crates/lint/src/sol/info/mod.rs b/crates/lint/src/sol/info/mod.rs index 290583cdc965a..913c5d2ea9da3 100644 --- a/crates/lint/src/sol/info/mod.rs +++ b/crates/lint/src/sol/info/mod.rs @@ -3,6 +3,12 @@ use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod mixed_case; use mixed_case::{MIXED_CASE_FUNCTION, MIXED_CASE_VARIABLE}; +mod boolean_cst; +use boolean_cst::BOOLEAN_CST; + +mod boolean_equal; +use boolean_equal::BOOLEAN_EQUAL; + mod pascal_case; use pascal_case::PASCAL_CASE_STRUCT; @@ -24,7 +30,18 @@ use multi_contract_file::MULTI_CONTRACT_FILE; mod interface_naming; use interface_naming::{INTERFACE_FILE_NAMING, INTERFACE_NAMING}; +mod too_many_digits; +use too_many_digits::TOO_MANY_DIGITS; + +mod pragma_directive; +use pragma_directive::PRAGMA_INCONSISTENT; + +mod inline_assembly; +use inline_assembly::INLINE_ASSEMBLY; + register_lints!( + (BooleanCst, early, (BOOLEAN_CST)), + (BooleanEqual, early, (BOOLEAN_EQUAL)), (PascalCaseStruct, early, (PASCAL_CASE_STRUCT)), (MixedCaseVariable, early, (MIXED_CASE_VARIABLE)), (MixedCaseFunction, early, (MIXED_CASE_FUNCTION)), @@ -34,4 +51,7 @@ register_lints!( (UnsafeCheatcodes, early, (UNSAFE_CHEATCODE_USAGE)), (MultiContractFile, early, (MULTI_CONTRACT_FILE)), (InterfaceFileNaming, early, (INTERFACE_FILE_NAMING, INTERFACE_NAMING)), + (TooManyDigits, early, (TOO_MANY_DIGITS)), + (PragmaDirective, project, (PRAGMA_INCONSISTENT)), + (InlineAssembly, early, (INLINE_ASSEMBLY)), ); diff --git a/crates/lint/src/sol/info/pragma_directive.rs b/crates/lint/src/sol/info/pragma_directive.rs new file mode 100644 index 0000000000000..b66b6bcff6ade --- /dev/null +++ b/crates/lint/src/sol/info/pragma_directive.rs @@ -0,0 +1,71 @@ +use crate::{ + linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource}, + sol::{Severity, SolLint, info::PragmaDirective}, +}; +use solar::{ast, interface::Span}; + +declare_forge_lint!( + PRAGMA_INCONSISTENT, + Severity::Info, + "pragma-inconsistent", + "inconsistent Solidity pragma version requirements across the project" +); + +impl<'ast> ProjectLintPass<'ast> for PragmaDirective { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) { + if !ctx.is_lint_enabled(PRAGMA_INCONSISTENT.id()) { + return; + } + + // Collect every `pragma solidity` directive across input sources, with its rendered + // version-requirement string for grouping. Stores source index to avoid lifetime + // invariance issues with `&ProjectSource<'ast>`. + let mut entries: Vec<(usize, Span, String)> = Vec::new(); + for (idx, source) in sources.iter().enumerate() { + for (span, req) in solidity_pragmas(source.ast) { + entries.push((idx, span, req.to_string())); + } + } + + // Stable order for snapshots and JSON output. + entries.sort_by(|a, b| { + sources[a.0].path.cmp(&sources[b.0].path).then(a.1.lo().cmp(&b.1.lo())) + }); + + // Build the distinct list once and bail if all sources agree. + let mut distinct: Vec<&str> = entries.iter().map(|(_, _, s)| s.as_str()).collect(); + distinct.sort_unstable(); + distinct.dedup(); + if distinct.len() < 2 { + return; + } + + for (idx, span, req_str) in &entries { + let others = distinct + .iter() + .filter(|v| **v != req_str.as_str()) + .copied() + .collect::>() + .join(", "); + let msg = format!( + "'pragma solidity {req_str};' conflicts with other version requirements in the project: {others}" + ); + ctx.emit_with_msg(&sources[*idx], &PRAGMA_INCONSISTENT, *span, msg); + } + } +} + +/// Yields every top-level `pragma solidity ...;` directive in `unit`. +fn solidity_pragmas<'ast>( + unit: &'ast ast::SourceUnit<'ast>, +) -> impl Iterator)> + 'ast { + unit.items.iter().filter_map(|item| match &item.kind { + ast::ItemKind::Pragma(p) => match &p.tokens { + ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => { + Some((item.span, req)) + } + _ => None, + }, + _ => None, + }) +} diff --git a/crates/lint/src/sol/info/too_many_digits.rs b/crates/lint/src/sol/info/too_many_digits.rs new file mode 100644 index 0000000000000..3ba9e8abba2de --- /dev/null +++ b/crates/lint/src/sol/info/too_many_digits.rs @@ -0,0 +1,50 @@ +use super::TooManyDigits; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{Expr, ExprKind, LitKind}; + +declare_forge_lint!( + TOO_MANY_DIGITS, + Severity::Info, + "too-many-digits", + "numeric literal with many digits is error-prone; \ + use scientific notation, sub-denominations, or underscore separators" +); + +impl<'ast> EarlyLintPass<'ast> for TooManyDigits { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + let ExprKind::Lit(lit, sub_denom) = &expr.kind else { return }; + + // Only plain integer literals. `LitKind::Address` (40-hex-digit address) is a + // distinct variant and is therefore skipped automatically. + if !matches!(lit.kind, LitKind::Number(_)) { + return; + } + + // Skip literals with a sub-denomination, e.g. `1000000 gwei`, `5 minutes`. + if sub_denom.is_some() { + return; + } + + let s = lit.symbol.as_str(); + + // Skip hex literals — long zero runs in hex are usually intentional (masks, + // selectors, bit patterns) and there is no scientific-notation alternative. + if s.starts_with("0x") || s.starts_with("0X") { + return; + } + + // Skip if the user already used scientific notation (`1e18`). + if s.contains('e') || s.contains('E') { + return; + } + + // 5+ consecutive zeros in the literal as written. Underscores are + // preserved, so `1_000_000` passes while `1_000000` is flagged. + if s.contains("00000") { + ctx.emit(&TOO_MANY_DIGITS, lit.span); + } + } +} diff --git a/crates/lint/src/sol/macros.rs b/crates/lint/src/sol/macros.rs index 00d764770374a..8540ab8b95b8f 100644 --- a/crates/lint/src/sol/macros.rs +++ b/crates/lint/src/sol/macros.rs @@ -9,9 +9,11 @@ /// - `$desc`: A short description of the lint. /// /// # Note -/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by -/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the -/// book, the existence of the lint rule's help section is validated with a unit test. +/// Each lint must have a corresponding markdown documentation file at +/// `crates/lint/docs/.md`. The `help` URL is auto-generated by the macro and points to +/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/`). To +/// ensure that new lint rules have their corresponding docs, the existence of every registered +/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`). #[macro_export] macro_rules! declare_forge_lint { ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { @@ -20,7 +22,7 @@ macro_rules! declare_forge_lint { id: $str_id, severity: $severity, description: $desc, - help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id), + help: concat!("https://getfoundry.sh/forge/linting/", $str_id), }; }; } @@ -53,6 +55,7 @@ macro_rules! register_lints { register_lints!(@early_impl $pass_id, $pass_type); register_lints!(@late_impl $pass_id, $pass_type); + register_lints!(@project_impl $pass_id, $pass_type); } )* }; @@ -89,10 +92,22 @@ macro_rules! register_lints { .flatten() .collect() } + + pub fn create_project_lint_passes<'ast>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@project_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() + } }; // --- HELPERS ------------------------------------------------------------ (@early_impl $_pass_id:ident, late) => {}; + (@early_impl $_pass_id:ident, project) => {}; (@early_impl $pass_id:ident, $other:ident) => { pub fn as_early_lint_pass<'a>() -> Box> { Box::new(Self::default()) @@ -100,22 +115,41 @@ macro_rules! register_lints { }; (@late_impl $_pass_id:ident, early) => {}; + (@late_impl $_pass_id:ident, project) => {}; (@late_impl $pass_id:ident, $other:ident) => { pub fn as_late_lint_pass<'hir>() -> Box> { Box::new(Self::default()) } }; + (@project_impl $_pass_id:ident, early) => {}; + (@project_impl $_pass_id:ident, late) => {}; + (@project_impl $_pass_id:ident, both) => {}; + (@project_impl $pass_id:ident, $other:ident) => { + pub fn as_project_lint_pass<'ast>() -> Box> { + Box::new(Self::default()) + } + }; + (@early_create $_pass_id:ident, late) => { None }; + (@early_create $_pass_id:ident, project) => { None }; (@early_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_early_lint_pass(), $pass_id::LINTS)) }; (@late_create $_pass_id:ident, early) => { None }; + (@late_create $_pass_id:ident, project) => { None }; (@late_create $pass_id:ident, $_other:ident) => { Some(($pass_id::as_late_lint_pass(), $pass_id::LINTS)) }; + (@project_create $_pass_id:ident, early) => { None }; + (@project_create $_pass_id:ident, late) => { None }; + (@project_create $_pass_id:ident, both) => { None }; + (@project_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_project_lint_pass(), $pass_id::LINTS)) + }; + // --- ENTRY POINT --------------------------------------------------------- ( $($tokens:tt)* ) => { register_lints! { @declare_structs $($tokens)* } diff --git a/crates/lint/src/sol/med/mod.rs b/crates/lint/src/sol/med/mod.rs index ba7a09b0e9bac..2673ba23d3252 100644 --- a/crates/lint/src/sol/med/mod.rs +++ b/crates/lint/src/sol/med/mod.rs @@ -9,6 +9,9 @@ use incorrect_erc20_interface::INCORRECT_ERC20_INTERFACE; mod incorrect_erc721_interface; use incorrect_erc721_interface::INCORRECT_ERC721_INTERFACE; +mod tx_origin; +use tx_origin::TX_ORIGIN; + mod unsafe_typecast; use unsafe_typecast::UNSAFE_TYPECAST; @@ -16,5 +19,6 @@ register_lints!( (DivideBeforeMultiply, early, (DIVIDE_BEFORE_MULTIPLY)), (IncorrectERC20Interface, late, (INCORRECT_ERC20_INTERFACE)), (IncorrectERC721Interface, late, (INCORRECT_ERC721_INTERFACE)), + (TxOrigin, early, (TX_ORIGIN)), (UnsafeTypecast, late, (UNSAFE_TYPECAST)) ); diff --git a/crates/lint/src/sol/med/tx_origin.rs b/crates/lint/src/sol/med/tx_origin.rs new file mode 100644 index 0000000000000..00ff5f939ebfb --- /dev/null +++ b/crates/lint/src/sol/med/tx_origin.rs @@ -0,0 +1,101 @@ +use super::TxOrigin; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Expr, ExprKind, IndexKind, Stmt, StmtKind}, + interface::SpannedOption, +}; + +declare_forge_lint!( + TX_ORIGIN, + Severity::Med, + "tx-origin", + "`tx.origin` should not be used for authorization" +); + +impl<'ast> EarlyLintPass<'ast> for TxOrigin { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::While(cond, _) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::For { cond: Some(cond), .. } => { + emit_if_contains_tx_origin(ctx, cond); + } + _ => {} + } + } + + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(callee, args) = &expr.kind + && is_require_or_assert_call(callee) + && let Some(cond) = args.exprs().next() + { + emit_if_contains_tx_origin(ctx, cond); + } + } +} + +fn emit_if_contains_tx_origin(ctx: &LintContext, expr: &Expr<'_>) { + if contains_tx_origin(expr) { + ctx.emit(&TX_ORIGIN, expr.span); + } +} + +fn contains_tx_origin(expr: &Expr<'_>) -> bool { + if is_tx_origin(expr) { + return true; + } + match &expr.kind { + ExprKind::Unary(_, inner) => contains_tx_origin(inner), + ExprKind::Binary(lhs, _, rhs) => contains_tx_origin(lhs) || contains_tx_origin(rhs), + ExprKind::Index(base, index) => { + contains_tx_origin(base) + || match index { + IndexKind::Index(Some(index)) => contains_tx_origin(index), + IndexKind::Range(start, end) => { + start.as_ref().is_some_and(|start| contains_tx_origin(start)) + || end.as_ref().is_some_and(|end| contains_tx_origin(end)) + } + _ => false, + } + } + ExprKind::Tuple(elems) => elems.iter().any(|elem| { + if let SpannedOption::Some(inner) = elem.as_ref() { + contains_tx_origin(inner) + } else { + false + } + }), + ExprKind::Call(callee, args) => { + contains_tx_origin(callee) || args.exprs().any(contains_tx_origin) + } + ExprKind::Ternary(cond, then_expr, else_expr) => { + contains_tx_origin(cond) + || contains_tx_origin(then_expr) + || contains_tx_origin(else_expr) + } + _ => false, + } +} + +fn is_tx_origin(expr: &Expr<'_>) -> bool { + matches!( + &expr.kind, + ExprKind::Member(base, member) + if member.as_str() == "origin" + && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "tx") + ) +} + +fn is_require_or_assert_call(callee: &Expr<'_>) -> bool { + matches!( + &callee.kind, + ExprKind::Ident(ident) if matches!(ident.as_str(), "require" | "assert") + ) +} diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index 59de6a40df015..7ae073f2ea20b 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -1,6 +1,6 @@ use crate::linter::{ EarlyLintPass, EarlyLintVisitor, LateLintPass, LateLintVisitor, Lint, LintContext, Linter, - LinterConfig, + LinterConfig, ProjectLintEmitter, ProjectLintPass, ProjectSource, }; use foundry_common::{ comments::{ @@ -21,6 +21,7 @@ use solar::{ interface::{ Session, diagnostics::{self, HumanEmitter, JsonEmitter}, + source_map::SourceFile, }, sema::{ Compiler, Gcx, @@ -29,7 +30,7 @@ use solar::{ }; use std::{ path::{Path, PathBuf}, - sync::LazyLock, + sync::{Arc, LazyLock}, }; use thiserror::Error; @@ -130,6 +131,7 @@ impl<'a> SolidityLinter<'a> { ast: &'gcx ast::SourceUnit<'gcx>, path: &Path, inline_config: &InlineConfig>, + source_file: Option>, ) -> Result<(), diagnostics::ErrorGuaranteed> { // Declare all available passes and lints let mut passes_and_lints = Vec::new(); @@ -168,6 +170,7 @@ impl<'a> SolidityLinter<'a> { self.with_json_emitter, self.config(inline_config), lints, + source_file, ); let mut early_visitor = EarlyLintVisitor::new(&ctx, &mut passes); _ = early_visitor.visit_source_unit(ast); @@ -176,12 +179,69 @@ impl<'a> SolidityLinter<'a> { Ok(()) } + /// Runs all enabled project-wide lint passes against the given input sources. + fn process_project<'gcx>(&self, gcx: Gcx<'gcx>, input: &[PathBuf]) { + // Gather enabled project passes from every severity bucket. + let mut passes_and_lints: Vec<(Box>, &'static [SolLint])> = + Vec::new(); + passes_and_lints.extend(high::create_project_lint_passes()); + passes_and_lints.extend(med::create_project_lint_passes()); + passes_and_lints.extend(low::create_project_lint_passes()); + passes_and_lints.extend(info::create_project_lint_passes()); + passes_and_lints.extend(gas::create_project_lint_passes()); + passes_and_lints.extend(codesize::create_project_lint_passes()); + + let (mut passes, lint_ids): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + if !included.is_empty() { + passes.push(pass); + ids.extend(included); + } + (passes, ids) + }); + + if passes.is_empty() { + return; + } + + // Pre-load every input source with its inline config, in input order. + let sources: Vec> = input + .iter() + .filter_map(|path| { + let path = self.path_config.root.join(path); + let (_, source) = gcx.get_ast_source(&path)?; + let ast = source.ast.as_ref()?; + let comments = + Comments::new(&source.file, gcx.sess.source_map(), false, false, None); + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + Some(ProjectSource { path, file: source.file.clone(), ast, inline_config }) + }) + .collect(); + + let emitter = ProjectLintEmitter::new( + gcx.sess, + self.with_description, + self.with_json_emitter, + self.lint_specific, + lint_ids, + ); + for pass in &mut passes { + pass.check_project(&emitter, &sources); + } + } + fn process_source_hir<'gcx>( &self, gcx: Gcx<'gcx>, source_id: hir::SourceId, path: &Path, inline_config: &InlineConfig>, + source_file: Option>, ) -> Result<(), diagnostics::ErrorGuaranteed> { // Declare all available passes and lints let mut passes_and_lints = Vec::new(); @@ -220,6 +280,7 @@ impl<'a> SolidityLinter<'a> { self.with_json_emitter, self.config(inline_config), lints, + source_file, ); let mut late_visitor = LateLintVisitor::new(&ctx, &mut passes, &gcx.hir); @@ -288,15 +349,30 @@ impl<'a> Linter for SolidityLinter<'a> { let inline_config = parse_inline_config(gcx.sess, &comments, ast); // Early lints. - let _ = self.process_source_ast(gcx.sess, ast, path, &inline_config); + let _ = self.process_source_ast( + gcx.sess, + ast, + path, + &inline_config, + Some(file.clone()), + ); // Late lints. let Some((hir_source_id, _)) = gcx.get_hir_source(path) else { panic!("HIR source not found for {}", path.display()); }; - let _ = self.process_source_hir(gcx, hir_source_id, path, &inline_config); + let _ = self.process_source_hir( + gcx, + hir_source_id, + path, + &inline_config, + Some(file.clone()), + ); }); + // Project-wide lints, run once after all per-file passes. + self.process_project(gcx, input); + convert_solar_errors(compiler.dcx()) })?; @@ -436,3 +512,75 @@ impl<'a> TryFrom<&'a str> for SolLint { Err(SolLintError::InvalidId(value.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Every registered lint must have a markdown documentation file at + /// `crates/lint/docs/.md`. This test enforces that contract so that the `help` URL + /// generated by `declare_forge_lint!` always resolves to real documentation. + /// + /// When this test fails, add a new file at `crates/lint/docs/.md` describing the + /// lint. See [`crates/lint/docs/_template.md`](../../docs/_template.md) for the expected + /// structure. + #[test] + fn registered_lints_have_docs() { + let docs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs"); + assert!(docs_dir.is_dir(), "missing docs directory at {}", docs_dir.display()); + + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + let mut missing: Vec<&'static str> = Vec::new(); + let mut empty: Vec<&'static str> = Vec::new(); + for lint in &all_lints { + let path = docs_dir.join(format!("{}.md", lint.id())); + match std::fs::read_to_string(&path) { + Ok(content) => { + // Basic sanity: file should be non-trivial and reference the lint id. + if content.trim().is_empty() || !content.contains(lint.id()) { + empty.push(lint.id()); + } + } + Err(_) => missing.push(lint.id()), + } + } + + assert!( + missing.is_empty(), + "the following registered lints are missing a docs file at \ + `crates/lint/docs/.md`: {missing:?}\n\ + See `crates/lint/docs/_template.md` for the expected structure." + ); + assert!( + empty.is_empty(), + "the following lint docs files are empty or do not reference the lint id: {empty:?}" + ); + } + + /// The auto-generated `help` URL must point at the canonical Foundry docs site so that the + /// link printed in diagnostics resolves correctly. + #[test] + fn registered_lints_have_canonical_help_url() { + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + for lint in all_lints { + let expected = format!("https://getfoundry.sh/forge/linting/{}", lint.id()); + assert_eq!(lint.help(), expected, "lint `{}` has a non-canonical help URL", lint.id()); + } + } +} diff --git a/crates/lint/testdata/BlockTimestamp.stderr b/crates/lint/testdata/BlockTimestamp.stderr index 016f8fa2bdb2d..62ab588ae7340 100644 --- a/crates/lint/testdata/BlockTimestamp.stderr +++ b/crates/lint/testdata/BlockTimestamp.stderr @@ -4,7 +4,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -12,7 +12,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp == 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -20,7 +20,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp != 0; │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -28,7 +28,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp <= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -36,7 +36,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp >= deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -44,7 +44,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp < deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -52,7 +52,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return deadline > block.timestamp; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -60,7 +60,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return block.timestamp + 1 > deadline; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -68,7 +68,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return (block.timestamp / 3600) == 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -76,7 +76,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ require(block.timestamp > deadline); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -84,7 +84,7 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ if (block.timestamp > deadline) { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC @@ -92,5 +92,5 @@ warning[block-timestamp]: usage of `block.timestamp` in a comparison may be mani LL │ return foo(block.timestamp) > 0; │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#block-timestamp + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp diff --git a/crates/lint/testdata/BooleanCst.sol b/crates/lint/testdata/BooleanCst.sol new file mode 100644 index 0000000000000..2b7cdc32f6702 --- /dev/null +++ b/crates/lint/testdata/BooleanCst.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract BooleanCst { + function check(bool flag) public pure returns (bool) { + if (false) {} //~WARN: misuse of a boolean constant + if (flag || true) {} //~WARN: misuse of a boolean constant + if (flag ? true : false) {} + //~^WARN: misuse of a boolean constant + //~|WARN: misuse of a boolean constant + while (true) { + break; + } + + bool assigned = true; + return assigned && false; //~WARN: misuse of a boolean constant + } + + function allowedBareConstants(bool flag) public pure returns (bool) { + takesBool(true); + return true; + } + + function takesBool(bool value) internal pure {} +} diff --git a/crates/lint/testdata/BooleanCst.stderr b/crates/lint/testdata/BooleanCst.stderr new file mode 100644 index 0000000000000..75fdb0b57cea7 --- /dev/null +++ b/crates/lint/testdata/BooleanCst.stderr @@ -0,0 +1,40 @@ +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (false) {} + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag || true) {} + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag ? true : false) {} + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag ? true : false) {} + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ return assigned && false; + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + diff --git a/crates/lint/testdata/BooleanEqual.sol b/crates/lint/testdata/BooleanEqual.sol new file mode 100644 index 0000000000000..30171e9c797ac --- /dev/null +++ b/crates/lint/testdata/BooleanEqual.sol @@ -0,0 +1,24 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract BooleanEqual { + function check(bool enabled, bool paused, bool ready, bool done) public pure { + if (enabled == true) {} //~NOTE: boolean comparisons to constants should be simplified + if (paused == false) {} //~NOTE: boolean comparisons to constants should be simplified + if (true != ready) {} //~NOTE: boolean comparisons to constants should be simplified + while (done != false) { //~NOTE: boolean comparisons to constants should be simplified + break; + } + for (; enabled == true && paused != false;) { + //~^NOTE: boolean comparisons to constants should be simplified + //~|NOTE: boolean comparisons to constants should be simplified + break; + } + } + + function returnedComparison(bool enabled) public pure returns (bool) { + return enabled == true; //~NOTE: boolean comparisons to constants should be simplified + } +} diff --git a/crates/lint/testdata/BooleanEqual.stderr b/crates/lint/testdata/BooleanEqual.stderr new file mode 100644 index 0000000000000..590a85b806fcf --- /dev/null +++ b/crates/lint/testdata/BooleanEqual.stderr @@ -0,0 +1,56 @@ +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (enabled == true) {} + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (paused == false) {} + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `!paused` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (true != ready) {} + │ ━━━━━━━━━━━━━ help: consider simplifying to: `!ready` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ while (done != false) { + │ ━━━━━━━━━━━━━ help: consider simplifying to: `done` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ for (; enabled == true && paused != false;) { + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ for (; enabled == true && paused != false;) { + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `paused` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ return enabled == true; + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + diff --git a/crates/lint/testdata/CouldBeImmutable.sol b/crates/lint/testdata/CouldBeImmutable.sol new file mode 100644 index 0000000000000..cd7e9aea13d01 --- /dev/null +++ b/crates/lint/testdata/CouldBeImmutable.sol @@ -0,0 +1,85 @@ +//@compile-flags: --only-lint could-be-immutable + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +contract CouldBeImmutable { + uint256 public constant MAX = 10; + uint256 public immutable ALREADY_IMMUTABLE; + + address public owner; + address public deployer = msg.sender; + uint256 private configured; + bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); + CouldBeImmutable private peer; + + uint256 private mutableValue; + uint256 private assignedInInternal; + uint256 private compileTimeConstant = 1 + 2; + string private dynamicValue; + + constructor(uint256 configured_, CouldBeImmutable peer_, string memory value) { + ALREADY_IMMUTABLE = configured_; + owner = msg.sender; + configured = configured_; + peer = peer_; + mutableValue = 1; + setInternal(1); + dynamicValue = value; + } + + function setMutableValue(uint256 newValue) public { + mutableValue = newValue; + } + + function setInternal(uint256 newValue) internal { + assignedInInternal = newValue; + } +} + +contract BaseImmutableCandidate { + uint256 internal inheritedBase; +} + +contract DerivedImmutableCandidate is BaseImmutableCandidate { + constructor(uint256 value) { + inheritedBase = value; + } +} + +contract BaseConstructorImmutableCandidate { + uint256 internal baseConfigured; + + constructor(uint256 value) { + baseConfigured = value; + } +} + +contract DerivedConstructorImmutableCandidate is BaseConstructorImmutableCandidate { + constructor(uint256 value) BaseConstructorImmutableCandidate(value) {} +} + +contract ModifierBodyWrite { + uint256 private fromModifier; + + modifier initializesState() { + fromModifier = 1; + _; + } + + constructor() initializesState() {} +} + +contract AssemblyWrite { + uint256 private fromAssembly; + + constructor() { + fromAssembly = 1; + } + + function mutate() public { + assembly { + sstore(0, 2) + } + } +} diff --git a/crates/lint/testdata/CouldBeImmutable.stderr b/crates/lint/testdata/CouldBeImmutable.stderr new file mode 100644 index 0000000000000..170682baf89d3 --- /dev/null +++ b/crates/lint/testdata/CouldBeImmutable.stderr @@ -0,0 +1,56 @@ +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ address public owner; + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ address public deployer = msg.sender; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 private configured; + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ CouldBeImmutable private peer; + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 internal inheritedBase; + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 internal baseConfigured; + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + diff --git a/crates/lint/testdata/CustomErrors.stderr b/crates/lint/testdata/CustomErrors.stderr index 66b3c11bc183c..286a649aee269 100644 --- a/crates/lint/testdata/CustomErrors.stderr +++ b/crates/lint/testdata/CustomErrors.stderr @@ -4,7 +4,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ require(a > 0, "Value must be greater than zero"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -12,7 +12,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ … require(a >= 0 && a <= 100 || b == 50, "Complex condition should be linted"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -20,7 +20,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert("Something went wrong"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -28,7 +28,7 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(""); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors note[custom-errors]: prefer using custom errors on revert and require calls ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC @@ -36,5 +36,5 @@ note[custom-errors]: prefer using custom errors on revert and require calls LL │ revert(); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#custom-errors + ╰ help: https://getfoundry.sh/forge/linting/custom-errors diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index c0e5ef78e2e1c..95022f65db874 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -4,7 +4,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2) * 3; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -12,7 +12,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 / 2) * 3) * 4; │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -20,7 +20,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ ((1 * 2) / 3) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -28,7 +28,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / 2 / 3) * 4; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -36,7 +36,7 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ (1 / (2 + 3)) * 4; │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC @@ -44,5 +44,5 @@ warning[divide-before-multiply]: multiplication should occur before division to LL │ 1 / ((2 / 3) * 3); │ ━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply diff --git a/crates/lint/testdata/Imports.stderr b/crates/lint/testdata/Imports.stderr index 8fa9800b27ded..1031f4f6f8ca0 100644 --- a/crates/lint/testdata/Imports.stderr +++ b/crates/lint/testdata/Imports.stderr @@ -4,7 +4,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsSomeFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." as X' ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -12,7 +12,7 @@ note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." a LL │ import "./auxiliary/ImportsAnotherFile.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unaliased-plain-import + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -20,7 +20,7 @@ note[unused-import]: unused imports should be removed LL │ symbol2 as notUsed, │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -28,7 +28,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol, │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -36,7 +36,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbol2, │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -44,7 +44,7 @@ note[unused-import]: unused imports should be removed LL │ docSymbolWrongTag, │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -52,7 +52,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed, │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -60,7 +60,7 @@ note[unused-import]: unused imports should be removed LL │ IContractNotUsed │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -68,7 +68,7 @@ note[unused-import]: unused imports should be removed LL │ symbolNotUsed3 │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -76,7 +76,7 @@ note[unused-import]: unused imports should be removed LL │ CONSTANT_1 │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -84,7 +84,7 @@ note[unused-import]: unused imports should be removed LL │ YetAnotherType │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -92,7 +92,7 @@ note[unused-import]: unused imports should be removed LL │ import "./auxiliary/ImportsAnotherFile2.sol" as AnotherFile2; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import note[unused-import]: unused imports should be removed ╭▸ ROOT/testdata/Imports.sol:LL:CC @@ -100,5 +100,5 @@ note[unused-import]: unused imports should be removed LL │ import * as OtherUtils from "./auxiliary/ImportsUtils2.sol"; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unused-import + ╰ help: https://getfoundry.sh/forge/linting/unused-import diff --git a/crates/lint/testdata/IncorrectERC20Interface.stderr b/crates/lint/testdata/IncorrectERC20Interface.stderr index 3bb60ecce8320..33e2f1ca27d22 100644 --- a/crates/lint/testdata/IncorrectERC20Interface.stderr +++ b/crates/lint/testdata/IncorrectERC20Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 {} │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC20 { │ ━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Incorrect is IERC20 { │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Correct is IERC20 { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20NamedCorrect { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC20 { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transfer(address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function transferFrom(address from, address to, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function approve(address spender, uint256 value) external returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function allowance(address owner, address spender) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function balanceOf(address account) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface warning[incorrect-erc20-interface]: incorrect ERC20 function interface ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC @@ -116,5 +116,5 @@ warning[incorrect-erc20-interface]: incorrect ERC20 function interface LL │ function totalSupply() external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc20-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface diff --git a/crates/lint/testdata/IncorrectERC721Interface.stderr b/crates/lint/testdata/IncorrectERC721Interface.stderr index a88db93e39b10..2e68084c1cec1 100644 --- a/crates/lint/testdata/IncorrectERC721Interface.stderr +++ b/crates/lint/testdata/IncorrectERC721Interface.stderr @@ -4,7 +4,7 @@ note[interface-naming]: interface names should be prefixed with 'I' LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#interface-naming + ╰ help: https://getfoundry.sh/forge/linting/interface-naming note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721 {} │ ━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface ERC721 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Incorrect is IERC721 { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721Correct is IERC721 { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC721NamedCorrect { │ ━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface INotERC721 { │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -60,7 +60,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -68,7 +68,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -76,7 +76,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function balanceOf(address owner) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -84,7 +84,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function ownerOf(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -92,7 +92,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -100,7 +100,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function safeTransferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -108,7 +108,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function transferFrom(address from, address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -116,7 +116,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function approve(address to, uint256 tokenId) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -124,7 +124,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function setApprovalForAll(address operator, bool approved) external returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -132,7 +132,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function getApproved(uint256 tokenId) external view returns (bool); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -140,7 +140,7 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function isApprovedForAll(address owner, address operator) external view returns (address); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface warning[incorrect-erc721-interface]: incorrect ERC721 function interface ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC @@ -148,5 +148,5 @@ warning[incorrect-erc721-interface]: incorrect ERC721 function interface LL │ function supportsInterface(bytes4 interfaceId) external view returns (uint256); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-erc721-interface + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface diff --git a/crates/lint/testdata/IncorrectShift.stderr b/crates/lint/testdata/IncorrectShift.stderr index bce84c98df432..dfff32db897bb 100644 --- a/crates/lint/testdata/IncorrectShift.stderr +++ b/crates/lint/testdata/IncorrectShift.stderr @@ -4,7 +4,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 2 << stateValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -12,7 +12,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 8 >> localValue; │ ━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -20,7 +20,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 16 << (stateValue + 1); │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -28,7 +28,7 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ result = 32 >> getAmount(); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC @@ -36,5 +36,5 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect LL │ … result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift diff --git a/crates/lint/testdata/InlineAssembly.sol b/crates/lint/testdata/InlineAssembly.sol new file mode 100644 index 0000000000000..05917ea22784c --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.sol @@ -0,0 +1,110 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract InlineAssembly { + function bare() public view returns (uint256 id) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + id := chainid() + } + } + + function withMemorySafe() public view returns (uint256 size) { + assembly ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + size := extcodesize(address()) + } + } + + function withDialectAndMemorySafe() public view returns (uint256 ptr) { + assembly "evmasm" ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + ptr := mload(0x40) + } + } + + function withNatspecMemorySafe() public view returns (uint256 v) { + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := chainid() + } + } + + function withNatspecMemorySafeAndOtherDocs() public view returns (uint256 v) { + /// @notice does a thing + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := gas() + } + } + + function plainCommentDoesNotCount() public view returns (uint256 v) { + // solidity memory-safe-assembly + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := chainid() + } + } + + function nestedInControlFlow(bool flag) public view returns (uint256 v) { + if (flag) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := gas() + } + } + + for (uint256 i = 0; i < 1; ++i) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInUnchecked(uint256 x) public pure returns (uint256 v) { + unchecked { + v = x + 1; + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInTryCatch() public returns (uint256 v) { + try this.bare() returns (uint256) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 1 + } + } catch { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 2 + } + } + } + + function suppressed() public view returns (uint256 id) { + // forge-lint: disable-next-line(inline-assembly) + assembly { + id := chainid() + } + } + + modifier guarded() { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + if iszero(caller()) { revert(0, 0) } + } + _; + } + + function suppressedRegion() public view returns (uint256 a, uint256 b) { + // forge-lint: disable-start(inline-assembly) + assembly { + a := chainid() + } + assembly ("memory-safe") { + b := gas() + } + // forge-lint: disable-end(inline-assembly) + } + + function noAssembly() public pure returns (uint256) { + return 42; + } +} diff --git a/crates/lint/testdata/InlineAssembly.stderr b/crates/lint/testdata/InlineAssembly.stderr new file mode 100644 index 0000000000000..12f8bcbacd14e --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.stderr @@ -0,0 +1,96 @@ +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly "evmasm" ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + diff --git a/crates/lint/testdata/Keccak256.sol b/crates/lint/testdata/Keccak256.sol index 41f856336b5b3..2457aed96d601 100644 --- a/crates/lint/testdata/Keccak256.sol +++ b/crates/lint/testdata/Keccak256.sol @@ -52,6 +52,7 @@ contract AsmKeccak256 { function assemblyHash(uint256 a, uint256 b) public pure returns (bytes32) { //optimized + // forge-lint: disable-next-line(inline-assembly) assembly { mstore(0x00, a) mstore(0x20, b) diff --git a/crates/lint/testdata/Keccak256.stderr b/crates/lint/testdata/Keccak256.stderr index 05d1dcf5641a8..a81e429e389a1 100644 --- a/crates/lint/testdata/Keccak256.stderr +++ b/crates/lint/testdata/Keccak256.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `mixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Another_MixedCase = 2; │ ━━━━━━━━━━━━━━━━━ help: consider using: `anotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 YetAnother_MixedCase = 3; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `yetAnotherMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Enabled_MixedCase_Variable = 1; │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -44,7 +44,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract AsmKeccak256 { │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -52,7 +52,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract OtherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -60,7 +60,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract YetAnotherAsmKeccak256 { │ ━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -68,7 +68,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 hash = keccak256(abi.encodePacked(a, b, bytes32(bytes20(c)))); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -76,7 +76,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 afterDisabledBlock = keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -84,7 +84,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromCalldata = keccak256(z); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -92,7 +92,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 loadsFromMemory = keccak256(y); │ ━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -100,7 +100,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 lintWithoutFix = keccak256(abi.encodePacked(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -108,7 +108,15 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(a, b, c)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 Enabled_MixedCase_Variable; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -116,7 +124,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesNotUseScratchSpace = keccak256(abi.encode(x, y, x, y, x, y)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -124,7 +132,7 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ bytes32 doesUseScratchSpace = keccak256(abi.encode(x)); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly ╭▸ ROOT/testdata/Keccak256.sol:LL:CC @@ -132,5 +140,5 @@ note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline LL │ return keccak256(abi.encode(doesUseScratchSpace, doesNotUseScratchSpace)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 diff --git a/crates/lint/testdata/MissingZeroCheck.stderr b/crates/lint/testdata/MissingZeroCheck.stderr index b55a902547fcf..81a9179e79c94 100644 --- a/crates/lint/testdata/MissingZeroCheck.stderr +++ b/crates/lint/testdata/MissingZeroCheck.stderr @@ -4,7 +4,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwner(address newOwner) external { │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -12,7 +12,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ constructor(address initialOwner) { │ ━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -20,7 +20,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function pay(address payable to) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -28,7 +28,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function lowLevel(address payable to, bytes calldata data) external { │ ━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -36,7 +36,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function withUselessModifier(address a) external doesNothing(a) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -44,7 +44,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaAlias(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -52,7 +52,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaReassign(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -60,7 +60,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function setOwnerViaCast(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -68,7 +68,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payViaAlias(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -76,7 +76,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function mixedParams(address a, address b) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -84,7 +84,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function bothSinks(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -92,7 +92,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function ternaryAlias(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -100,7 +100,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function payableWrap(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -108,7 +108,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function modifierWithExpr(address a) external nonZero(addrIdentity(a)) { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -116,7 +116,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function delegateCallSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -124,7 +124,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkStmt(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -132,7 +132,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function sendSinkDecl(address payable a) external { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -140,7 +140,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function multiHopTaint(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -148,7 +148,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardAfterSink(address a) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -156,7 +156,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardOnOneBranch(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -164,7 +164,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInForLoop(address a, uint256 n) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -172,7 +172,7 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInWhileLoop(address a, bool flag) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC @@ -180,5 +180,5 @@ warning[missing-zero-check]: address parameter is used in a state write or value LL │ function guardInTryClause(address a, address payable target) external { │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#missing-zero-check + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check diff --git a/crates/lint/testdata/MixedCase.stderr b/crates/lint/testdata/MixedCase.stderr index d290af5cdb5a8..2db30559ba5a6 100644 --- a/crates/lint/testdata/MixedCase.stderr +++ b/crates/lint/testdata/MixedCase.stderr @@ -4,7 +4,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 Variablemixedcase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variablemixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -12,7 +12,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VARIABLE_MIXED_CASE; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -20,7 +20,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 VariableMixedCase; │ ━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -28,7 +28,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 testVAL; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -36,7 +36,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TestVal; │ ━━━━━━━ help: consider using: `testVal` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -44,7 +44,7 @@ note[mixed-case-variable]: mutable variables should use mixedCase LL │ uint256 TESTVAL; │ ━━━━━━━ help: consider using: `testval` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -52,7 +52,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function Functionmixedcase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionmixedcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -60,7 +60,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FUNCTION_MIXED_CASE() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -68,7 +68,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function FunctionMixedCase() public {} │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -76,7 +76,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function function_mixed_case() public {} │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -84,7 +84,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantBalance_MixedCase_Enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantBalanceMixedCaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -92,7 +92,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function invariantbalance_mixedcase_enabled() public {} │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantbalanceMixedcaseEnabled` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -100,7 +100,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function ERC20_DoSomething() public {} // invalid because of the underscore │ ━━━━━━━━━━━━━━━━━ help: consider using: `erc20DoSomething` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -108,7 +108,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_PARAMS(address addr) external view returns (uint256) {} │ ━━━━━━━━━━ help: consider using: `hasParams` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -116,7 +116,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_NO_RETURN() external view {} │ ━━━━━━━━━━━━━ help: consider using: `hasNoReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -124,7 +124,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function HAS_MORE_THAN_ONE_RETURN() external view returns (uint256, uint256) {} │ ━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `hasMoreThanOneReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -132,7 +132,7 @@ note[mixed-case-function]: function names should use mixedCase LL │ function NOT_ELEMENTARY_RETURN() external view returns (uint256[] memory) {} │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `notElementaryReturn` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -140,7 +140,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MixedCase.sol:LL:CC @@ -148,5 +148,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract MixedCaseTest { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile.stderr b/crates/lint/testdata/MultiContractFile.stderr index c6e4e32a2df55..e25f3d72ad01a 100644 --- a/crates/lint/testdata/MultiContractFile.stderr +++ b/crates/lint/testdata/MultiContractFile.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract A {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract B {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC @@ -36,5 +36,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L {} │ ━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr index 41fc439ea7d1b..1912f16863712 100644 --- a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr +++ b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface I1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library L1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC @@ -20,5 +20,5 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract C1 {} │ ━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/NamedStructFields.stderr b/crates/lint/testdata/NamedStructFields.stderr index 6ee2160791cd2..cfb35637176bd 100644 --- a/crates/lint/testdata/NamedStructFields.stderr +++ b/crates/lint/testdata/NamedStructFields.stderr @@ -4,5 +4,5 @@ note[named-struct-fields]: prefer initializing structs with named fields LL │ Person memory person = Person("Alice", 25, address(0)); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using named fields: `Person({ name: "Alice", age: 25, wallet: address(0) })` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#named-struct-fields + ╰ help: https://getfoundry.sh/forge/linting/named-struct-fields diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol new file mode 100644 index 0000000000000..bfc993baab794 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 +pragma solidity 0.8.18; //~NOTE: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr new file mode 100644 index 0000000000000..c2c967dee792f --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.18; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol new file mode 100644 index 0000000000000..75bc17988accc --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr new file mode 100644 index 0000000000000..f60361718ba9b --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol new file mode 100644 index 0000000000000..37b06040c33a6 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 +pragma solidity ~0.8.20; //~NOTE: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr new file mode 100644 index 0000000000000..6c46f2478208d --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ~0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.sol b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol new file mode 100644 index 0000000000000..f85a477cc8744 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20 || 0.8.21; //~NOTE: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr new file mode 100644 index 0000000000000..acf6bd7c2d6e0 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20 || 0.8.21; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol new file mode 100644 index 0000000000000..d8fcb7a0eb4b1 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; //~NOTE: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr new file mode 100644 index 0000000000000..5ac221b924c9a --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0 <0.9.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol new file mode 100644 index 0000000000000..fe208e15efb63 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol @@ -0,0 +1,8 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; //~NOTE: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 +pragma solidity ~0.8.0; //~NOTE: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr new file mode 100644 index 0000000000000..e1e5ad7333fb2 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr @@ -0,0 +1,24 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ~0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/Rtlo.sol b/crates/lint/testdata/Rtlo.sol new file mode 100644 index 0000000000000..82f7e68a41548 --- /dev/null +++ b/crates/lint/testdata/Rtlo.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Tests for the `rtlo` lint, which detects "Trojan Source" bidirectional +// formatting characters (CVE-2021-42574). These have no legitimate use in +// Solidity source and can be used to hide malicious code. +// +// Note: solc itself rejects unbalanced directional override markers (error +// 8936), so each test uses a balanced opening/closing pair. Our lint flags +// each occurrence individually regardless of balance. + +contract Rtlo { + // SHOULD FAIL: every codepoint in the Trojan-Source set is flagged. + // Each line below contains two bidi characters (an opener and its closer) + // and produces two diagnostics. + + string public lre = unicode"‪_‬"; + //~^WARN: U+202A (Left-to-Right Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public rle = unicode"‫_‬"; + //~^WARN: U+202B (Right-to-Left Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public pdf = unicode"‪‬"; + //~^WARN: U+202A (Left-to-Right Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public lro = unicode"‭_‬"; + //~^WARN: U+202D (Left-to-Right Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public rlo = unicode"‮_‬"; + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public lri = unicode"⁦_⁩"; + //~^WARN: U+2066 (Left-to-Right Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public rli = unicode"⁧_⁩"; + //~^WARN: U+2067 (Right-to-Left Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public fsi = unicode"⁨_⁩"; + //~^WARN: U+2068 (First Strong Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public pdi = unicode"⁦⁩"; + //~^WARN: U+2066 (Left-to-Right Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + // SHOULD FAIL: bidi controls inside a block comment are also detected. + /* hidden‮ /* text ‬ */ uint256 inBlockComment; + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + // SHOULD FAIL: bidi controls inside a line comment are also detected. The + // expectation markers must come on separate lines because the ui-test + // parser only treats the first comment on a line as a marker. + // sneaky‮ payload ‬ trailing + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + // SHOULD PASS: inline-config disable suppresses the diagnostic. + // forge-lint: disable-next-line(rtlo) + string public suppressedLine = unicode"‮_‬"; + + // forge-lint: disable-start(rtlo) + string public suppressedA = unicode"‮_‬"; + string public suppressedB = unicode"⁦_⁩"; + // forge-lint: disable-end(rtlo) + + // SHOULD PASS: plain ASCII source, no bidi controls. + string public clean = "no bidi here"; + + // SHOULD FAIL: LRM/RLM marks (U+200E/U+200F) are also flagged. + string public marks = unicode"left‎right‏end"; + //~^WARN: U+200E (Left-to-Right Mark) detected + //~|WARN: U+200F (Right-to-Left Mark) detected +} diff --git a/crates/lint/testdata/Rtlo.stderr b/crates/lint/testdata/Rtlo.stderr new file mode 100644 index 0000000000000..93f5bb191532f --- /dev/null +++ b/crates/lint/testdata/Rtlo.stderr @@ -0,0 +1,192 @@ +warning[rtlo]: U+202A (Left-to-Right Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lre = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lre = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202B (Right-to-Left Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rle = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rle = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202A (Left-to-Right Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdf = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdf = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202D (Left-to-Right Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lro = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lro = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rlo = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rlo = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2066 (Left-to-Right Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lri = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lri = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2067 (Right-to-Left Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rli = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rli = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2068 (First Strong Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public fsi = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public fsi = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2066 (Left-to-Right Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdi = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdi = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ /* hidden� /* text � */ uint256 inBlockComment; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ /* hidden� /* text � */ uint256 inBlockComment; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ // sneaky� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ // sneaky� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+200E (Left-to-Right Mark) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public marks = unicode"left‎right‏end"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+200F (Right-to-Left Mark) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public marks = unicode"left‎right‏end"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + diff --git a/crates/lint/testdata/RtloCommentsOnly.sol b/crates/lint/testdata/RtloCommentsOnly.sol new file mode 100644 index 0000000000000..82a1fbd7fc58e --- /dev/null +++ b/crates/lint/testdata/RtloCommentsOnly.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Bidi chars that only appear in comments (outside any item) must still be +// reported. + +// hidden‮ payload ‬ trailing +//~^WARN: U+202E (Right-to-Left Override) detected +//~|WARN: U+202C (Pop Directional Formatting) detected + +/* block‮ comment ‬ end */ +//~^WARN: U+202E (Right-to-Left Override) detected +//~|WARN: U+202C (Pop Directional Formatting) detected + +contract RtloCommentsOnly {} diff --git a/crates/lint/testdata/RtloCommentsOnly.stderr b/crates/lint/testdata/RtloCommentsOnly.stderr new file mode 100644 index 0000000000000..5a7ec9ee6e69d --- /dev/null +++ b/crates/lint/testdata/RtloCommentsOnly.stderr @@ -0,0 +1,32 @@ +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ // hidden� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ // hidden� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ /* block� comment � end */ + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ /* block� comment � end */ + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + diff --git a/crates/lint/testdata/ScreamingSnakeCase.stderr b/crates/lint/testdata/ScreamingSnakeCase.stderr index a740506ed74d8..36305bb268d9a 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.stderr +++ b/crates/lint/testdata/ScreamingSnakeCase.stderr @@ -4,7 +4,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -12,7 +12,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant screaming_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -20,7 +20,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant ScreamingSnakeCase = 0; │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -28,7 +28,7 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE LL │ uint256 constant SCREAMING_snake_case = 0; │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -36,7 +36,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -44,7 +44,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable screaming_snake_case0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -52,7 +52,7 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable ScreamingSnakeCase0 = 0; │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC @@ -60,5 +60,5 @@ note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE LL │ uint256 immutable SCREAMING_snake_case_0 = 0; │ ━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE_0` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable diff --git a/crates/lint/testdata/StructPascalCase.stderr b/crates/lint/testdata/StructPascalCase.stderr index 1c7bfa13ba84b..255c1c4d5d74b 100644 --- a/crates/lint/testdata/StructPascalCase.stderr +++ b/crates/lint/testdata/StructPascalCase.stderr @@ -4,7 +4,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct _PascalCase { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -12,7 +12,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalCase { │ ━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -20,7 +20,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascalcase { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -28,7 +28,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct pascal_case { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -36,7 +36,7 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCAL_CASE { │ ━━━━━━━━━━━ help: consider using: `PascalCase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC @@ -44,5 +44,5 @@ note[pascal-case-struct]: structs should use PascalCase LL │ struct PASCALCASE { │ ━━━━━━━━━━ help: consider using: `Pascalcase` │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct diff --git a/crates/lint/testdata/TooManyDigits.sol b/crates/lint/testdata/TooManyDigits.sol new file mode 100644 index 0000000000000..a56ad67fe379e --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.sol @@ -0,0 +1,73 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TooManyDigits { + // SHOULD FAIL: plain decimal integer literals with 5+ consecutive zeros. + + uint256 stateA = 1000000000000000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + uint256 stateB = 100000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + function asReturn() public pure returns (uint256) { + return 10000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asComparison(uint256 x) public pure returns (bool) { + return x == 1000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArg(address to) public { + _send(to, 50000000000); //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArraySize() public pure { + uint256[100000] memory _arr; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + // Zero-run in the middle (not just trailing). + uint256 middleZeros = 123000007; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscores that don't actually break up the zero run. + uint256 badGrouping = 1_000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscore right after a single digit, leaving a 5-zero group. + uint256 badGrouping2 = 1_00000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // SHOULD PASS: + + // Boundary: 4 consecutive zeros (one short of the threshold). + uint256 fourZeros = 10000; + + // Uppercase scientific notation. + uint256 sciUpper = 1E18; + + // Scientific notation. + uint256 sci = 1e18; + + // Underscore-separated digit groups. + uint256 grouped = 1_000_000_000_000_000_000; + + // Sub-denominations. + uint256 oneEther = 1 ether; + uint256 oneGwei = 1 gwei; + uint256 fiveMin = 5 minutes; + + // Address literal (distinct AST kind, not flagged). + address adr = 0x1234567890123456789012345678901234567890; + + // Hex literal — intentional zero patterns (mask / padded value). + bytes32 mask = 0x0000000000000000000000000000000000000000000000000000000000000001; + uint256 hexNum = 0x100000; + + // Small literals (< 5 consecutive zeros). + uint256 small1 = 100; + uint256 small2 = 9999; + uint256 small3 = 1234; + uint256 spread = 101010; + + // Boolean literal. + bool flag = true; + + function _send(address, uint256) internal pure {} +} diff --git a/crates/lint/testdata/TooManyDigits.stderr b/crates/lint/testdata/TooManyDigits.stderr new file mode 100644 index 0000000000000..7e21a530776c2 --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.stderr @@ -0,0 +1,72 @@ +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateA = 1000000000000000000; + │ ━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateB = 100000; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return 10000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return x == 1000000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … _send(to, 50000000000); + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … uint256[100000] memory _arr; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 middleZeros = 123000007; + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping = 1_000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping2 = 1_00000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + diff --git a/crates/lint/testdata/TxOrigin.sol b/crates/lint/testdata/TxOrigin.sol new file mode 100644 index 0000000000000..9728a7e528e5b --- /dev/null +++ b/crates/lint/testdata/TxOrigin.sol @@ -0,0 +1,65 @@ +//@compile-flags: --only-lint tx-origin +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TxOrigin { + address public owner; + mapping(address => bool) public allowed; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(tx.origin == owner, "not owner"); //~WARN: `tx.origin` should not be used for authorization + _; + } + + function guardedByIf() external view { + if (tx.origin != owner) { //~WARN: `tx.origin` should not be used for authorization + revert("not owner"); + } + } + + function guardedByPredicate() external view { + assert(isOwner(tx.origin)); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByWhile() external view { + while (tx.origin == owner) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByFor() external view { + for (; tx.origin == owner;) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByDoWhile() external view { + do { + } while (tx.origin == owner); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByMapping() external view { + require(allowed[tx.origin], "not allowed"); //~WARN: `tx.origin` should not be used for authorization + require(allowed[tx.origin] == true, "not allowed"); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByTernary() external view { + require(tx.origin == owner ? true : false, "not owner"); //~WARN: `tx.origin` should not be used for authorization + } + + function readForLogging() external view returns (address) { + return tx.origin; + } + + function explicitSenderCheck() external view { + require(msg.sender == owner, "not owner"); + } + + function isOwner(address account) internal view returns (bool) { + return account == owner; + } +} diff --git a/crates/lint/testdata/TxOrigin.stderr b/crates/lint/testdata/TxOrigin.stderr new file mode 100644 index 0000000000000..7c2e70225b76d --- /dev/null +++ b/crates/lint/testdata/TxOrigin.stderr @@ -0,0 +1,72 @@ +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner, "not owner"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ if (tx.origin != owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ assert(isOwner(tx.origin)); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ while (tx.origin == owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ for (; tx.origin == owner;) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ } while (tx.origin == owner); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin], "not allowed"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin] == true, "not allowed"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner ? true : false, "not owner"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + diff --git a/crates/lint/testdata/UncheckedCall.stderr b/crates/lint/testdata/UncheckedCall.stderr index afb8ade4ea89b..8a8a9fa9b5e17 100644 --- a/crates/lint/testdata/UncheckedCall.stderr +++ b/crates/lint/testdata/UncheckedCall.stderr @@ -4,7 +4,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call(data); │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -12,7 +12,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.call{value: value}(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -20,7 +20,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.delegatecall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -28,7 +28,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target.staticcall(data); │ ━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -36,7 +36,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target1.call(""); │ ━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -44,7 +44,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ target2.delegatecall(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -52,7 +52,7 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, bytes memory data) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call warning[unchecked-call]: Low-level calls should check the success return value ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC @@ -60,5 +60,5 @@ warning[unchecked-call]: Low-level calls should check the success return value LL │ (, existingData) = target.call(""); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unchecked-call + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call diff --git a/crates/lint/testdata/UncheckedTransferERC20.stderr b/crates/lint/testdata/UncheckedTransferERC20.stderr index 733d22ce610d1..2c2caa69e7215 100644 --- a/crates/lint/testdata/UncheckedTransferERC20.stderr +++ b/crates/lint/testdata/UncheckedTransferERC20.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20 { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ interface IERC20Wrapper { │ ━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -20,7 +20,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransfer { │ ━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -28,7 +28,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ library Currency { │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -36,7 +36,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UncheckedTransferUsingCurrencyLib { │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -44,7 +44,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ IERC20(address(token)).transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -52,7 +52,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -60,7 +60,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -68,7 +68,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transferFrom(from, to, amount); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -76,7 +76,7 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ … IERC20(address(token)).transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC @@ -84,5 +84,5 @@ warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls sho LL │ token.transfer(recipients[i], amounts[i]); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#erc20-unchecked-transfer + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer diff --git a/crates/lint/testdata/UnsafeCheatcodes.stderr b/crates/lint/testdata/UnsafeCheatcodes.stderr index e66a4d72c70de..5b8b429942e80 100644 --- a/crates/lint/testdata/UnsafeCheatcodes.stderr +++ b/crates/lint/testdata/UnsafeCheatcodes.stderr @@ -4,7 +4,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -12,7 +12,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -20,7 +20,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readLine("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -28,7 +28,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeFile("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -36,7 +36,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.writeLine("test.txt", "data"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -44,7 +44,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.removeFile("test.txt"); │ ━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -52,7 +52,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.closeFile("test.txt"); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -60,7 +60,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -68,7 +68,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.deriveKey("mnemonic", 0); │ ━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -76,7 +76,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ bytes memory result = vm.ffi(inputs); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -84,7 +84,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.ffi(new string[](1)); │ ━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -92,7 +92,7 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.setEnv("KEY", "value"); │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC @@ -100,5 +100,5 @@ note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous op LL │ vm.readFile("test.txt"); │ ━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-cheatcode + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode diff --git a/crates/lint/testdata/UnsafeTypecast.stderr b/crates/lint/testdata/UnsafeTypecast.stderr index b3e0334d63d43..d909b90973e00 100644 --- a/crates/lint/testdata/UnsafeTypecast.stderr +++ b/crates/lint/testdata/UnsafeTypecast.stderr @@ -4,7 +4,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract UnsafeTypecast { │ ━━━━━━━━━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file note[multi-contract-file]: prefer having only one contract, interface or library per file ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -12,7 +12,7 @@ note[multi-contract-file]: prefer having only one contract, interface or library LL │ contract Repros { │ ━━━━━━ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#multi-contract-file + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -26,7 +26,7 @@ LL │ uint248 b = uint248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -40,7 +40,7 @@ LL │ uint240 c = uint240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -54,7 +54,7 @@ LL │ uint232 d = uint232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -68,7 +68,7 @@ LL │ uint224 e = uint224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -82,7 +82,7 @@ LL │ uint216 f = uint216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -96,7 +96,7 @@ LL │ uint208 g = uint208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -110,7 +110,7 @@ LL │ uint200 h = uint200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -124,7 +124,7 @@ LL │ uint192 i = uint192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -138,7 +138,7 @@ LL │ uint184 j = uint184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -152,7 +152,7 @@ LL │ uint176 k = uint176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -166,7 +166,7 @@ LL │ uint168 l = uint168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -180,7 +180,7 @@ LL │ uint160 m = uint160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -194,7 +194,7 @@ LL │ uint152 n = uint152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -208,7 +208,7 @@ LL │ uint144 o = uint144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -222,7 +222,7 @@ LL │ uint136 p = uint136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -236,7 +236,7 @@ LL │ uint128 q = uint128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -250,7 +250,7 @@ LL │ uint120 r = uint120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -264,7 +264,7 @@ LL │ uint112 s = uint112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -278,7 +278,7 @@ LL │ uint104 t = uint104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -292,7 +292,7 @@ LL │ uint96 u = uint96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -306,7 +306,7 @@ LL │ uint88 v = uint88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -320,7 +320,7 @@ LL │ uint80 w = uint80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -334,7 +334,7 @@ LL │ uint72 x = uint72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -348,7 +348,7 @@ LL │ uint64 y = uint64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -362,7 +362,7 @@ LL │ uint56 z = uint56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -376,7 +376,7 @@ LL │ uint48 A = uint48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -390,7 +390,7 @@ LL │ uint40 B = uint40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -404,7 +404,7 @@ LL │ uint32 C = uint32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -418,7 +418,7 @@ LL │ uint24 D = uint24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -432,7 +432,7 @@ LL │ uint16 E = uint16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -446,7 +446,7 @@ LL │ uint8 F = uint8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -460,7 +460,7 @@ LL │ int248 b = int248(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -474,7 +474,7 @@ LL │ int240 c = int240(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -488,7 +488,7 @@ LL │ int232 d = int232(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -502,7 +502,7 @@ LL │ int224 e = int224(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -516,7 +516,7 @@ LL │ int216 f = int216(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -530,7 +530,7 @@ LL │ int208 g = int208(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -544,7 +544,7 @@ LL │ int200 h = int200(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -558,7 +558,7 @@ LL │ int192 i = int192(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -572,7 +572,7 @@ LL │ int184 j = int184(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -586,7 +586,7 @@ LL │ int176 k = int176(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -600,7 +600,7 @@ LL │ int168 l = int168(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -614,7 +614,7 @@ LL │ int160 m = int160(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -628,7 +628,7 @@ LL │ int152 n = int152(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -642,7 +642,7 @@ LL │ int144 o = int144(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -656,7 +656,7 @@ LL │ int136 p = int136(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -670,7 +670,7 @@ LL │ int128 q = int128(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -684,7 +684,7 @@ LL │ int120 r = int120(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -698,7 +698,7 @@ LL │ int112 s = int112(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -712,7 +712,7 @@ LL │ int104 t = int104(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -726,7 +726,7 @@ LL │ int96 u = int96(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -740,7 +740,7 @@ LL │ int88 v = int88(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -754,7 +754,7 @@ LL │ int80 w = int80(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -768,7 +768,7 @@ LL │ int72 x = int72(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -782,7 +782,7 @@ LL │ int64 y = int64(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -796,7 +796,7 @@ LL │ int56 z = int56(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -810,7 +810,7 @@ LL │ int48 A = int48(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -824,7 +824,7 @@ LL │ int40 B = int40(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -838,7 +838,7 @@ LL │ int32 C = int32(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -852,7 +852,7 @@ LL │ int24 D = int24(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -866,7 +866,7 @@ LL │ int16 E = int16(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -880,7 +880,7 @@ LL │ int8 F = int8(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -894,7 +894,7 @@ LL │ bytes31 b = bytes31(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -908,7 +908,7 @@ LL │ bytes30 c = bytes30(b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -922,7 +922,7 @@ LL │ bytes29 d = bytes29(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -936,7 +936,7 @@ LL │ bytes28 e = bytes28(d); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -950,7 +950,7 @@ LL │ bytes27 f = bytes27(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -964,7 +964,7 @@ LL │ bytes26 g = bytes26(f); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -978,7 +978,7 @@ LL │ bytes25 h = bytes25(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -992,7 +992,7 @@ LL │ bytes24 i = bytes24(h); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1006,7 +1006,7 @@ LL │ bytes23 j = bytes23(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1020,7 +1020,7 @@ LL │ bytes22 k = bytes22(j); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1034,7 +1034,7 @@ LL │ bytes21 l = bytes21(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1048,7 +1048,7 @@ LL │ bytes20 m = bytes20(l); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1062,7 +1062,7 @@ LL │ bytes19 n = bytes19(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1076,7 +1076,7 @@ LL │ bytes18 o = bytes18(n); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1090,7 +1090,7 @@ LL │ bytes17 p = bytes17(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1104,7 +1104,7 @@ LL │ bytes16 q = bytes16(p); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1118,7 +1118,7 @@ LL │ bytes15 r = bytes15(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1132,7 +1132,7 @@ LL │ bytes14 s = bytes14(r); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1146,7 +1146,7 @@ LL │ bytes13 t = bytes13(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1160,7 +1160,7 @@ LL │ bytes12 u = bytes12(t); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1174,7 +1174,7 @@ LL │ bytes11 v = bytes11(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1188,7 +1188,7 @@ LL │ bytes10 w = bytes10(v); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1202,7 +1202,7 @@ LL │ bytes9 x = bytes9(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1216,7 +1216,7 @@ LL │ bytes8 y = bytes8(x); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1230,7 +1230,7 @@ LL │ bytes7 z = bytes7(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1244,7 +1244,7 @@ LL │ bytes6 A = bytes6(z); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1258,7 +1258,7 @@ LL │ bytes5 B = bytes5(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1272,7 +1272,7 @@ LL │ bytes4 C = bytes4(B); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1286,7 +1286,7 @@ LL │ bytes3 D = bytes3(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1300,7 +1300,7 @@ LL │ bytes2 E = bytes2(D); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1314,7 +1314,7 @@ LL │ bytes1 F = bytes1(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1328,7 +1328,7 @@ LL │ int256 b = int256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1342,7 +1342,7 @@ LL │ int248 d = int248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1356,7 +1356,7 @@ LL │ int240 f = int240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1370,7 +1370,7 @@ LL │ int232 h = int232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1384,7 +1384,7 @@ LL │ int224 j = int224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1398,7 +1398,7 @@ LL │ int216 l = int216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1412,7 +1412,7 @@ LL │ int208 n = int208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1426,7 +1426,7 @@ LL │ int200 p = int200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1440,7 +1440,7 @@ LL │ int192 r = int192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1454,7 +1454,7 @@ LL │ int184 t = int184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1468,7 +1468,7 @@ LL │ int176 v = int176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1482,7 +1482,7 @@ LL │ int168 x = int168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1496,7 +1496,7 @@ LL │ int160 z = int160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1510,7 +1510,7 @@ LL │ int152 B = int152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1524,7 +1524,7 @@ LL │ int144 D = int144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1538,7 +1538,7 @@ LL │ int136 F = int136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1552,7 +1552,7 @@ LL │ int128 H = int128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1566,7 +1566,7 @@ LL │ int120 J = int120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1580,7 +1580,7 @@ LL │ int112 L = int112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1594,7 +1594,7 @@ LL │ int104 N = int104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1608,7 +1608,7 @@ LL │ int96 P = int96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1622,7 +1622,7 @@ LL │ int88 R = int88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1636,7 +1636,7 @@ LL │ int80 T = int80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1650,7 +1650,7 @@ LL │ int72 V = int72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1664,7 +1664,7 @@ LL │ int64 X = int64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1678,7 +1678,7 @@ LL │ int56 Z = int56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1692,7 +1692,7 @@ LL │ int48 BB = int48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1706,7 +1706,7 @@ LL │ int40 DD = int40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1720,7 +1720,7 @@ LL │ int32 FF = int32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1734,7 +1734,7 @@ LL │ int24 HH = int24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1748,7 +1748,7 @@ LL │ int16 JJ = int16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1762,7 +1762,7 @@ LL │ int8 LL = int8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1776,7 +1776,7 @@ LL │ uint256 b = uint256(a); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1790,7 +1790,7 @@ LL │ uint248 d = uint248(c); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1804,7 +1804,7 @@ LL │ uint240 f = uint240(e); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1818,7 +1818,7 @@ LL │ uint232 h = uint232(g); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1832,7 +1832,7 @@ LL │ uint224 j = uint224(i); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1846,7 +1846,7 @@ LL │ uint216 l = uint216(k); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1860,7 +1860,7 @@ LL │ uint208 n = uint208(m); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1874,7 +1874,7 @@ LL │ uint200 p = uint200(o); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1888,7 +1888,7 @@ LL │ uint192 r = uint192(q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1902,7 +1902,7 @@ LL │ uint184 t = uint184(s); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1916,7 +1916,7 @@ LL │ uint176 v = uint176(u); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1930,7 +1930,7 @@ LL │ uint168 x = uint168(w); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1944,7 +1944,7 @@ LL │ uint160 z = uint160(y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1958,7 +1958,7 @@ LL │ uint152 B = uint152(A); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1972,7 +1972,7 @@ LL │ uint144 D = uint144(C); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -1986,7 +1986,7 @@ LL │ uint136 F = uint136(E); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2000,7 +2000,7 @@ LL │ uint128 H = uint128(G); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2014,7 +2014,7 @@ LL │ uint120 J = uint120(I); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2028,7 +2028,7 @@ LL │ uint112 L = uint112(K); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2042,7 +2042,7 @@ LL │ uint104 N = uint104(M); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2056,7 +2056,7 @@ LL │ uint96 P = uint96(O); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2070,7 +2070,7 @@ LL │ uint88 R = uint88(Q); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2084,7 +2084,7 @@ LL │ uint80 T = uint80(S); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2098,7 +2098,7 @@ LL │ uint72 V = uint72(U); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2112,7 +2112,7 @@ LL │ uint64 X = uint64(W); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2126,7 +2126,7 @@ LL │ uint56 Z = uint56(Y); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2140,7 +2140,7 @@ LL │ uint48 BB = uint48(AA); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2154,7 +2154,7 @@ LL │ uint40 DD = uint40(CC); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2168,7 +2168,7 @@ LL │ uint32 FF = uint32(EE); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2182,7 +2182,7 @@ LL │ uint24 HH = uint24(GG); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2196,7 +2196,7 @@ LL │ uint16 JJ = uint16(II); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2210,7 +2210,7 @@ LL │ uint8 LL = uint8(KK); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2224,7 +2224,7 @@ LL │ bytes32 dataSlice = bytes32(data); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2238,7 +2238,7 @@ LL │ bytes32 strSlice = bytes32(bytes(str)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2252,7 +2252,7 @@ LL │ uint128 aPlusB = uint128(int128(uint128(a)) + b); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2266,7 +2266,7 @@ LL │ uint64 unsafe = uint64(aPlusB); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2280,7 +2280,7 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast warning[unsafe-typecast]: typecasts that can truncate values should be checked ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC @@ -2294,5 +2294,5 @@ LL │ return uint64(uint128(int128(uint128(a)) + b)); │ // forge-lint: disable-next-line(unsafe-typecast) │ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unsafe-typecast + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast diff --git a/crates/lint/testdata/UnusedStateVariables.sol b/crates/lint/testdata/UnusedStateVariables.sol new file mode 100644 index 0000000000000..f68650d2a8d01 --- /dev/null +++ b/crates/lint/testdata/UnusedStateVariables.sol @@ -0,0 +1,52 @@ +//@compile-flags: --severity gas + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract UnusedVars { + uint256 unused; //~NOTE: state variable is never used + uint256 usedInRead; + uint256 usedInWrite; + address usedInBoth; + uint256 constant CONST = 1; // skip constant + uint256 immutable IMMUT; // skip immutable + + constructor() { + usedInBoth = msg.sender; + } + + function read() external view returns (uint256) { + return usedInRead; + } + + function write(uint256 v) external { + usedInWrite = v; + } + + function both() external view returns (address) { + return usedInBoth; + } +} + +// State variables used only as modifier call arguments must not be flagged. +contract UsedInModifierArg { + uint256 limit; + uint256 unused; //~NOTE: state variable is never used + + modifier limitedBy(uint256 max) { + require(msg.value <= max); + _; + } + + function foo() external payable limitedBy(limit) {} +} + +contract MultiUnused { + uint256 firstUnused; //~NOTE: state variable is never used + uint256 secondUnused; //~NOTE: state variable is never used + uint256 usedVar; + + function use() external view returns (uint256) { + return usedVar; + } +} diff --git a/crates/lint/testdata/UnusedStateVariables.stderr b/crates/lint/testdata/UnusedStateVariables.stderr new file mode 100644 index 0000000000000..92a0082bb8293 --- /dev/null +++ b/crates/lint/testdata/UnusedStateVariables.stderr @@ -0,0 +1,40 @@ +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ address usedInBoth; + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 unused; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 unused; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 firstUnused; + │ ━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 secondUnused; + │ ━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr index 5e1bf754e60e4..dc5514c2d5e98 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -9,7 +9,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforePlaceholder() { @@ -35,7 +35,7 @@ LL │ ┃ checkInternal(msg.sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleAfterPlaceholder() { @@ -62,7 +62,7 @@ LL │ ┃ checkPublic(sender); LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier multipleBeforeAfterPlaceholder(address sender) { @@ -91,7 +91,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwner() { @@ -113,7 +113,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRole(bytes32 role) { @@ -135,7 +135,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrOpenRole(bytes32 role) { @@ -157,7 +157,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyRoleOrAdmin(bytes32 role, address admin) { @@ -180,7 +180,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier assign(address sender) { @@ -204,7 +204,7 @@ LL │ ┃ sender; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier uncheckedBlock(address sender) { @@ -228,7 +228,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier emitEvent(address sender) { @@ -250,7 +250,7 @@ LL │ ┃ _; LL │ ┃ } │ ┗━━━━━┛ │ - ╰ help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic help: wrap modifier logic to reduce code size ╭╴ LL ± modifier onlyOwnerContract(address sender) { diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 9970616900e6b..15363a6a40bb0 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -23,10 +23,10 @@ alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true alloy-signer.workspace = true alloy-evm.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"] } -op-alloy-rpc-types.workspace = true -alloy-op-evm.workspace = true -op-revm.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +alloy-op-evm = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } revm.workspace = true serde_json.workspace = true serde = { version = "1.0", features = ["derive"] } @@ -34,3 +34,12 @@ derive_more.workspace = true tempo-primitives.workspace = true tempo-alloy.workspace = true tempo-revm.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", +] diff --git a/crates/primitives/src/network/mod.rs b/crates/primitives/src/network/mod.rs index 7000b580b6a73..0b840185b0383 100644 --- a/crates/primitives/src/network/mod.rs +++ b/crates/primitives/src/network/mod.rs @@ -1,12 +1,20 @@ use alloy_network::Network; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; use alloy_provider::fillers::{ BlobGasFiller, ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, }; +#[cfg(feature = "optimism")] +pub use optimism::FoundryTransactionResponse; pub use receipt::*; +/// Default JSON-RPC transaction response when the `optimism` feature is disabled. +#[cfg(not(feature = "optimism"))] +pub type FoundryTransactionResponse = alloy_rpc_types_eth::Transaction; + /// Foundry network type. /// /// This network type supports Foundry-specific transaction types, including @@ -36,7 +44,7 @@ impl Network for FoundryNetwork { type TransactionRequest = crate::FoundryTransactionRequest; - type TransactionResponse = op_alloy_rpc_types::Transaction; + type TransactionResponse = FoundryTransactionResponse; type ReceiptResponse = crate::FoundryTxReceipt; diff --git a/crates/primitives/src/network/optimism.rs b/crates/primitives/src/network/optimism.rs new file mode 100644 index 0000000000000..aff30a755663f --- /dev/null +++ b/crates/primitives/src/network/optimism.rs @@ -0,0 +1,47 @@ +//! OP-stack-specific helpers and type aliases used by [`super::FoundryNetwork`] and +//! [`super::FoundryTxReceipt`]. + +use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_primitives::U64; +use alloy_rpc_types::Log; +use alloy_serde::OtherFields; +use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; + +use crate::FoundryReceiptEnvelope; + +/// JSON-RPC transaction response type used by [`super::FoundryNetwork`]. +pub type FoundryTransactionResponse = op_alloy_rpc_types::Transaction; + +/// Build a [`FoundryReceiptEnvelope::Deposit`] from a `ReceiptWithBloom` plus the OP +/// deposit-specific fields decoded from the [`OtherFields`] of an `AnyTransactionReceipt`. +pub(super) fn build_deposit_receipt_envelope( + receipt_with_bloom: ReceiptWithBloom>, + other: &OtherFields, +) -> FoundryReceiptEnvelope { + // These fields may not be present in all receipts, so missing/invalid values are None. + let deposit_nonce = other + .get_deserialized::("depositNonce") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + let deposit_receipt_version = other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + + FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: Receipt { + status: alloy_consensus::Eip658Value::Eip658(receipt_with_bloom.status()), + cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), + logs: receipt_with_bloom.receipt.logs, + }, + deposit_nonce, + deposit_receipt_version, + }, + logs_bloom: receipt_with_bloom.logs_bloom, + }) +} diff --git a/crates/primitives/src/network/receipt.rs b/crates/primitives/src/network/receipt.rs index b727b4c39b345..6b01f9eaa9ee9 100644 --- a/crates/primitives/src/network/receipt.rs +++ b/crates/primitives/src/network/receipt.rs @@ -1,13 +1,13 @@ -use alloy_consensus::{Receipt, TxReceipt}; use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt, ReceiptResponse}; -use alloy_primitives::{Address, B256, BlockHash, TxHash, U64}; +use alloy_primitives::{Address, B256, BlockHash, TxHash}; use alloy_rpc_types::{ConversionError, Log, TransactionReceipt}; use alloy_serde::WithOtherFields; use derive_more::AsRef; -use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; use serde::{Deserialize, Serialize}; use tempo_primitives::TEMPO_TX_TYPE_ID; +#[cfg(feature = "optimism")] +use super::optimism::build_deposit_receipt_envelope; use crate::FoundryReceiptEnvelope; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AsRef)] @@ -144,38 +144,8 @@ impl TryFrom for FoundryTxReceipt { 0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), 0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), TEMPO_TX_TYPE_ID => FoundryReceiptEnvelope::Tempo(receipt_with_bloom), - 0x7E => { - // Construct the deposit receipt, extracting optional deposit fields - // These fields may not be present in all receipts, so missing/invalid - // values are None - let deposit_nonce = other - .get_deserialized::("depositNonce") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - let deposit_receipt_version = other - .get_deserialized::("depositReceiptVersion") - .transpose() - .ok() - .flatten() - .map(|v| v.to::()); - - FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { - receipt: OpDepositReceipt { - inner: Receipt { - status: alloy_consensus::Eip658Value::Eip658( - receipt_with_bloom.status(), - ), - cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), - logs: receipt_with_bloom.receipt.logs, - }, - deposit_nonce, - deposit_receipt_version, - }, - logs_bloom: receipt_with_bloom.logs_bloom, - }) - } + #[cfg(feature = "optimism")] + 0x7E => build_deposit_receipt_envelope(receipt_with_bloom, &other), _ => { let tx_type = r#type; return Err(ConversionError::Custom(format!( diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index ab4aafcc294c0..0a009a1931f06 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -1,6 +1,7 @@ +#[cfg(feature = "optimism")] +use alloy_consensus::{Sealed, Transaction as _}; use alloy_consensus::{ - Sealed, Signed, Transaction as _, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, - TxLegacy, TxType, Typed2718, + Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType, Typed2718, crypto::RecoveryError, transaction::{ SignerRecoverable, TxEip7702, TxHashRef, @@ -9,14 +10,10 @@ use alloy_consensus::{ }; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; -use alloy_op_evm::OpTx; use alloy_primitives::{Address, B256, Bytes, TxHash}; use alloy_rpc_types::ConversionError; -use op_alloy_consensus::{ - DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait, POST_EXEC_TX_TYPE_ID, TxDeposit, - TxPostExec, -}; -use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec}; use revm::context::TxEnv; use tempo_primitives::{AASigned, TempoTransaction}; use tempo_revm::TempoTxEnv; @@ -57,9 +54,11 @@ pub enum FoundryTxEnvelope { /// OP stack deposit transaction. /// /// See . + #[cfg(feature = "optimism")] #[envelope(ty = 126)] Deposit(Sealed), /// OP stack post-execution synthetic transaction. + #[cfg(feature = "optimism")] #[envelope(ty = 0x7D)] PostExec(Sealed), /// Tempo transaction type. @@ -80,7 +79,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)), Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)), Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)), + #[cfg(feature = "optimism")] Self::Deposit(_) => Err(self), + #[cfg(feature = "optimism")] Self::PostExec(_) => Err(self), Self::Tempo(_) => Err(self), } @@ -109,7 +110,9 @@ impl FoundryTxEnvelope { Self::Eip1559(t) => *t.hash(), Self::Eip4844(t) => *t.hash(), Self::Eip7702(t) => *t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.tx_hash(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.tx_hash(), Self::Tempo(t) => *t.hash(), } @@ -128,7 +131,9 @@ impl FoundryTxEnvelope { Self::Eip1559(tx) => tx.recover_signer()?, Self::Eip4844(tx) => tx.recover_signer()?, Self::Eip7702(tx) => tx.recover_signer()?, + #[cfg(feature = "optimism")] Self::Deposit(tx) => tx.from, + #[cfg(feature = "optimism")] Self::PostExec(tx) => tx.inner().signer_address(), Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?, }) @@ -143,7 +148,9 @@ impl TxHashRef for FoundryTxEnvelope { Self::Eip1559(t) => t.hash(), Self::Eip4844(t) => t.hash(), Self::Eip7702(t) => t.hash(), + #[cfg(feature = "optimism")] Self::Deposit(t) => t.hash_ref(), + #[cfg(feature = "optimism")] Self::PostExec(t) => t.hash_ref(), Self::Tempo(t) => t.hash(), } @@ -160,23 +167,6 @@ impl SignerRecoverable for FoundryTxEnvelope { } } -impl OpTransactionTrait for FoundryTxEnvelope { - fn is_deposit(&self) -> bool { - matches!(self, Self::Deposit(_)) - } - - fn as_deposit(&self) -> Option<&Sealed> { - match self { - Self::Deposit(tx) => Some(tx), - _ => None, - } - } - - fn as_post_exec(&self) -> Option<&Sealed> { - if let Self::PostExec(tx) = self { Some(tx) } else { None } - } -} - impl TryFrom for TxEnvelope { type Error = FoundryTxEnvelope; @@ -197,19 +187,6 @@ impl From for FoundryTxEnvelope { } } -impl From for FoundryTxEnvelope { - fn from(tx: op_alloy_consensus::OpTxEnvelope) -> Self { - match tx { - op_alloy_consensus::OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), - op_alloy_consensus::OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), - op_alloy_consensus::OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), - op_alloy_consensus::OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), - op_alloy_consensus::OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), - op_alloy_consensus::OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), - } - } -} - impl From for FoundryTxEnvelope { fn from(tx: tempo_primitives::TempoTxEnvelope) -> Self { match tx { @@ -236,33 +213,50 @@ impl TryFrom for FoundryTxEnvelope { TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)), TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)), }, - AnyTxEnvelope::Unknown(mut tx) => { - // Try to convert to deposit transaction - if tx.ty() == DEPOSIT_TX_TYPE_ID { - tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); - let deposit_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize deposit tx: {e}" - )) - })?; - - return Ok(Self::Deposit(Sealed::new(deposit_tx))); + AnyTxEnvelope::Unknown(tx) => { + #[cfg(feature = "optimism")] + { + let mut tx = tx; + let _ = from; + // Try to convert to deposit transaction + if tx.ty() == DEPOSIT_TX_TYPE_ID { + tx.inner + .fields + .insert("from".to_string(), serde_json::to_value(from).unwrap()); + let deposit_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize deposit tx: {e}" + )) + })?; + + return Ok(Self::Deposit(Sealed::new(deposit_tx))); + } + + if tx.ty() == POST_EXEC_TX_TYPE_ID { + let post_exec_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize post-exec tx: {e}" + )) + })?; + + return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + } + + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - if tx.ty() == POST_EXEC_TX_TYPE_ID { - let post_exec_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize post-exec tx: {e}" - )) - })?; - - return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + #[cfg(not(feature = "optimism"))] + { + let _ = from; + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) } - - let tx_type = tx.ty(); - Err(ConversionError::Custom(format!("Unknown transaction type: 0x{tx_type:02X}"))) } } } @@ -276,6 +270,7 @@ impl FromRecoveredTx for TxEnv { FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller), FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -288,6 +283,7 @@ impl FromRecoveredTx for TxEnv { ..Default::default() } } + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => { let tx = sealed_tx.inner(); Self { @@ -303,63 +299,6 @@ impl FromRecoveredTx for TxEnv { } } -impl FromRecoveredTx for OpTransaction { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: None, deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: None, deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl FromTxWithEncoded for TxEnv { fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { Self::from_recovered_tx(tx, sender) @@ -384,7 +323,9 @@ impl FromRecoveredTx for TempoTxEnv { FoundryTxEnvelope::Eip7702(signed_tx) => { Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) } + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(_) => unreachable!("Post-exec tx in Tempo context"), FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller), } @@ -397,75 +338,6 @@ impl FromTxWithEncoded for TempoTxEnv { } } -impl FromRecoveredTx for OpTx { - fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { - Self(OpTransaction::::from_recovered_tx(tx, caller)) - } -} - -impl FromTxWithEncoded for OpTx { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) - } -} - -impl FromTxWithEncoded for OpTransaction { - fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { - match tx { - FoundryTxEnvelope::Legacy(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip2930(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip1559(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip4844(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Eip7702(signed_tx) => { - let base = TxEnv::from_recovered_tx(signed_tx, caller); - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Deposit(sealed_tx) => { - let deposit_tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: deposit_tx.ty(), - caller, - gas_limit: deposit_tx.gas_limit, - kind: deposit_tx.to, - value: deposit_tx.value, - data: deposit_tx.input.clone(), - ..Default::default() - }; - let deposit = DepositTransactionParts { - source_hash: deposit_tx.source_hash, - mint: Some(deposit_tx.mint), - is_system_transaction: deposit_tx.is_system_transaction, - }; - Self { base, enveloped_tx: Some(encoded), deposit } - } - FoundryTxEnvelope::PostExec(sealed_tx) => { - let tx = sealed_tx.inner(); - let base = TxEnv { - tx_type: tx.ty(), - caller, - kind: tx.kind(), - data: tx.input.clone(), - ..Default::default() - }; - Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } - } - FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), - } - } -} - impl std::fmt::Display for FoundryTxType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -474,7 +346,9 @@ impl std::fmt::Display for FoundryTxType { Self::Eip1559 => write!(f, "eip1559"), Self::Eip4844 => write!(f, "eip4844"), Self::Eip7702 => write!(f, "eip7702"), + #[cfg(feature = "optimism")] Self::Deposit => write!(f, "deposit"), + #[cfg(feature = "optimism")] Self::PostExec => write!(f, "post-exec"), Self::Tempo => write!(f, "tempo"), } @@ -501,7 +375,9 @@ impl From for FoundryTypedTx { FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()), FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()), FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()), + #[cfg(feature = "optimism")] FoundryTxEnvelope::PostExec(sealed_tx) => Self::PostExec(sealed_tx.into_inner()), FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()), } @@ -609,28 +485,6 @@ mod tests { assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); } - #[test] - fn test_decode_encode_deposit_tx() { - // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" - .parse::() - .unwrap(); - - // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let raw_tx = alloy_primitives::hex::decode( - "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", - ) - .unwrap(); - let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); - - let mut encoded = Vec::new(); - dep_tx.encode_2718(&mut encoded); - - assert_eq!(raw_tx, encoded); - - assert_eq!(tx_hash, dep_tx.hash()); - } - #[test] fn can_recover_sender_not_normalized() { let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); @@ -707,11 +561,6 @@ mod tests { assert_eq!(tx_env.caller, sender); assert_eq!(tx_env.gas_limit, 0x5208); assert_eq!(tx_env.gas_price, 1); - - // Test OpTransaction conversion via FromRecoveredTx trait - let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); - assert_eq!(op_tx.base.caller, sender); - assert_eq!(op_tx.base.gas_limit, 0x5208); } // Test vector from Tempo testnet: diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 7dccf3e30752f..18f39c437bfbc 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,7 +1,11 @@ mod envelope; +#[cfg(feature = "optimism")] +mod optimism; mod receipt; mod request; pub use envelope::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; +#[cfg(feature = "optimism")] +pub use optimism::get_deposit_tx_parts; pub use receipt::FoundryReceiptEnvelope; -pub use request::{FoundryTransactionRequest, get_deposit_tx_parts}; +pub use request::FoundryTransactionRequest; diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs new file mode 100644 index 0000000000000..5952b5d6a9020 --- /dev/null +++ b/crates/primitives/src/transaction/optimism.rs @@ -0,0 +1,300 @@ +//! OP-stack-specific impls for [`FoundryTxEnvelope`] and [`FoundryTransactionRequest`]. + +use alloy_consensus::{Sealed, Transaction as _, Typed2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_op_evm::OpTx; +use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_serde::OtherFields; +use op_alloy_consensus::{ + OpDepositReceipt, OpDepositReceiptWithBloom, OpTransaction as OpTransactionTrait, OpTxEnvelope, + TxDeposit, TxPostExec, +}; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +use revm::context::TxEnv; + +use super::{FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope}; + +impl OpTransactionTrait for FoundryTxEnvelope { + fn is_deposit(&self) -> bool { + matches!(self, Self::Deposit(_)) + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + Self::Deposit(tx) => Some(tx), + _ => None, + } + } + + fn as_post_exec(&self) -> Option<&Sealed> { + if let Self::PostExec(tx) = self { Some(tx) } else { None } + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: OpTxEnvelope) -> Self { + match tx { + OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), + OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), + OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), + } + } +} + +impl FromRecoveredTx for OpTransaction { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: None, deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + Self(OpTransaction::::from_recovered_tx(tx, caller)) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) + } +} + +impl FromTxWithEncoded for OpTransaction { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: Some(encoded), deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: op_alloy_rpc_types::Transaction) -> Self { + tx.inner.into_inner().into() + } +} + +/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields. +pub fn get_deposit_tx_parts( + other: &OtherFields, +) -> Result> { + let mut missing = Vec::new(); + let source_hash = + other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("sourceHash"); + Default::default() + }, + ); + let mint = other + .get_deserialized::("mint") + .transpose() + .unwrap_or_else(|_| { + missing.push("mint"); + Default::default() + }) + .map(|value| value.to::()); + let is_system_transaction = + other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("isSystemTx"); + Default::default() + }, + ); + if missing.is_empty() { + Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) + } else { + Err(missing) + } +} + +/// OP-stack-specific accessors on [`FoundryReceiptEnvelope`]. +impl FoundryReceiptEnvelope { + /// Return the receipt's deposit_nonce if it is a deposit receipt. + pub fn deposit_nonce(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_nonce) + } + + /// Return the receipt's deposit version if it is a deposit receipt. + pub fn deposit_receipt_version(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { + match self { + Self::Deposit(t) => Some(t), + _ => None, + } + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { + match self { + Self::Deposit(t) => Some(&t.receipt), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use alloy_network::eip2718::Encodable2718; + use alloy_primitives::TxHash; + use alloy_rlp::Decodable; + + use super::*; + + #[test] + fn test_from_recovered_tx_legacy_op() { + use alloy_consensus::transaction::SignerRecoverable; + + let tx = r#" + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gas": "0x5208", + "gasPrice": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x1", + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "v": "0x1b", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + let sender = typed_tx.recover_signer().unwrap(); + + // Test OpTransaction conversion via FromRecoveredTx trait + let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); + assert_eq!(op_tx.base.caller, sender); + assert_eq!(op_tx.base.gas_limit, 0x5208); + } + + #[test] + fn test_decode_encode_deposit_tx() { + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); + + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + let mut encoded = Vec::new(); + dep_tx.encode_2718(&mut encoded); + + assert_eq!(raw_tx, encoded); + + assert_eq!(tx_hash, dep_tx.hash()); + } +} diff --git a/crates/primitives/src/transaction/receipt.rs b/crates/primitives/src/transaction/receipt.rs index fe209fc72c907..78bbcd1efa2fb 100644 --- a/crates/primitives/src/transaction/receipt.rs +++ b/crates/primitives/src/transaction/receipt.rs @@ -8,6 +8,7 @@ use alloy_network::eip2718::{ use alloy_primitives::{Bloom, Log, TxHash, logs_bloom}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, bytes}; use alloy_rpc_types::{BlockNumHash, trace::otterscan::OtsReceipt}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{ DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, POST_EXEC_TX_TYPE_ID, }; @@ -29,8 +30,10 @@ pub enum FoundryReceiptEnvelope { Eip4844(ReceiptWithBloom>), #[serde(rename = "0x4", alias = "0x04")] Eip7702(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7D", alias = "0x7d")] PostExec(ReceiptWithBloom>), + #[cfg(feature = "optimism")] #[serde(rename = "0x7E", alias = "0x7e")] Deposit(OpDepositReceiptWithBloom), #[serde(rename = "0x76")] @@ -44,7 +47,8 @@ impl FoundryReceiptEnvelope { cumulative_gas_used: u64, logs: impl IntoIterator, tx_type: FoundryTxType, - deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_receipt_version: Option, ) -> Self { let logs = logs.into_iter().collect::>(); @@ -67,9 +71,11 @@ impl FoundryReceiptEnvelope { FoundryTxType::Eip7702 => { Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::PostExec => { Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + #[cfg(feature = "optimism")] FoundryTxType::Deposit => { let inner = OpDepositReceiptWithBloom { receipt: OpDepositReceipt { @@ -112,13 +118,18 @@ impl FoundryReceiptEnvelope { removed: false, }) .collect::>(); + #[cfg(feature = "optimism")] + let (deposit_nonce, deposit_receipt_version) = + (self.deposit_nonce(), self.deposit_receipt_version()); + #[cfg(not(feature = "optimism"))] + let (deposit_nonce, deposit_receipt_version) = (None, None); FoundryReceiptEnvelope::::from_parts( self.status(), self.cumulative_gas_used(), logs, self.tx_type(), - self.deposit_nonce(), - self.deposit_receipt_version(), + deposit_nonce, + deposit_receipt_version, ) } } @@ -132,7 +143,9 @@ impl FoundryReceiptEnvelope { Self::Eip1559(_) => FoundryTxType::Eip1559, Self::Eip4844(_) => FoundryTxType::Eip4844, Self::Eip7702(_) => FoundryTxType::Eip7702, + #[cfg(feature = "optimism")] Self::PostExec(_) => FoundryTxType::PostExec, + #[cfg(feature = "optimism")] Self::Deposit(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -158,8 +171,12 @@ impl FoundryReceiptEnvelope { Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)), Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)), Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)), + #[cfg(feature = "optimism")] Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)), - Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(r.map_receipt(|r| r.map_logs(f))), + #[cfg(feature = "optimism")] + Self::Deposit(r) => FoundryReceiptEnvelope::Deposit( + r.map_receipt(|r: OpDepositReceipt| r.map_logs(f)), + ), Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)), } } @@ -182,38 +199,14 @@ impl FoundryReceiptEnvelope { Self::Eip1559(t) => &t.logs_bloom, Self::Eip4844(t) => &t.logs_bloom, Self::Eip7702(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::PostExec(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.logs_bloom, Self::Tempo(t) => &t.logs_bloom, } } - /// Return the receipt's deposit_nonce if it is a deposit receipt. - pub fn deposit_nonce(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_nonce) - } - - /// Return the receipt's deposit version if it is a deposit receipt. - pub fn deposit_receipt_version(&self) -> Option { - self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { - match self { - Self::Deposit(t) => Some(t), - _ => None, - } - } - - /// Returns the deposit receipt if it is a deposit receipt. - pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { - match self { - Self::Deposit(t) => Some(&t.receipt), - _ => None, - } - } - /// Consumes the type and returns the underlying [`Receipt`]. pub fn into_receipt(self) -> Receipt { match self { @@ -222,8 +215,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => t.receipt.into_inner(), } } @@ -236,8 +231,10 @@ impl FoundryReceiptEnvelope { | Self::Eip1559(t) | Self::Eip4844(t) | Self::Eip7702(t) - | Self::PostExec(t) | Self::Tempo(t) => &t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => &t.receipt, + #[cfg(feature = "optimism")] Self::Deposit(t) => &t.receipt.inner, } } @@ -287,7 +284,9 @@ impl Encodable for FoundryReceiptEnvelope { Self::Eip1559(r) => r.length() + 1, Self::Eip4844(r) => r.length() + 1, Self::Eip7702(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::PostExec(r) => r.length() + 1, + #[cfg(feature = "optimism")] Self::Deposit(r) => r.length() + 1, Self::Tempo(r) => r.length() + 1, _ => unreachable!("receipt already matched"), @@ -314,11 +313,13 @@ impl Encodable for FoundryReceiptEnvelope { EIP7702_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::PostExec(r) => { Header { list: true, payload_length: payload_len }.encode(out); POST_EXEC_TX_TYPE_ID.encode(out); r.encode(out); } + #[cfg(feature = "optimism")] Self::Deposit(r) => { Header { list: true, payload_length: payload_len }.encode(out); DEPOSIT_TX_TYPE_ID.encode(out); @@ -371,18 +372,23 @@ impl Decodable for FoundryReceiptEnvelope { buf.advance(1); ::decode(buf) .map(FoundryReceiptEnvelope::Eip7702) - } else if receipt_type == POST_EXEC_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::PostExec) - } else if receipt_type == DEPOSIT_TX_TYPE_ID { - buf.advance(1); - ::decode(buf) - .map(FoundryReceiptEnvelope::Deposit) } else if receipt_type == TEMPO_TX_TYPE_ID { buf.advance(1); ::decode(buf).map(FoundryReceiptEnvelope::Tempo) } else { + #[cfg(feature = "optimism")] + { + if receipt_type == POST_EXEC_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::PostExec); + } + if receipt_type == DEPOSIT_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::Deposit); + } + } Err(alloy_rlp::Error::Custom("invalid receipt type")) } } @@ -404,7 +410,9 @@ impl Typed2718 for FoundryReceiptEnvelope { Self::Eip1559(_) => EIP1559_TX_TYPE_ID, Self::Eip4844(_) => EIP4844_TX_TYPE_ID, Self::Eip7702(_) => EIP7702_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::PostExec(_) => POST_EXEC_TX_TYPE_ID, + #[cfg(feature = "optimism")] Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, Self::Tempo(_) => TEMPO_TX_TYPE_ID, } @@ -419,7 +427,9 @@ impl Encodable2718 for FoundryReceiptEnvelope { Self::Eip1559(r) => 1 + r.length(), Self::Eip4844(r) => 1 + r.length(), Self::Eip7702(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::PostExec(r) => 1 + r.length(), + #[cfg(feature = "optimism")] Self::Deposit(r) => 1 + r.length(), Self::Tempo(r) => 1 + r.length(), } @@ -435,8 +445,10 @@ impl Encodable2718 for FoundryReceiptEnvelope { | Self::Eip1559(r) | Self::Eip4844(r) | Self::Eip7702(r) - | Self::PostExec(r) | Self::Tempo(r) => r.encode(out), + #[cfg(feature = "optimism")] + Self::PostExec(r) => r.encode(out), + #[cfg(feature = "optimism")] Self::Deposit(r) => r.encode(out), } } @@ -444,15 +456,18 @@ impl Encodable2718 for FoundryReceiptEnvelope { impl Decodable2718 for FoundryReceiptEnvelope { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == DEPOSIT_TX_TYPE_ID { - return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + #[cfg(feature = "optimism")] + { + if ty == DEPOSIT_TX_TYPE_ID { + return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + } + if ty == POST_EXEC_TX_TYPE_ID { + return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); + } } if ty == TEMPO_TX_TYPE_ID { return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?)); } - if ty == POST_EXEC_TX_TYPE_ID { - return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); - } match ReceiptEnvelope::typed_decode(ty, buf)? { ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)), ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)), @@ -646,8 +661,11 @@ mod tests { assert!(receipt.status()); assert_eq!(receipt.cumulative_gas_used(), 100000); assert!(receipt.logs().is_empty()); - assert!(receipt.deposit_nonce().is_none()); - assert!(receipt.deposit_receipt_version().is_none()); + #[cfg(feature = "optimism")] + { + assert!(receipt.deposit_nonce().is_none()); + assert!(receipt.deposit_receipt_version().is_none()); + } } #[test] diff --git a/crates/primitives/src/transaction/request.rs b/crates/primitives/src/transaction/request.rs index 2c4dbae8fdcd4..8ae31efbd5cb1 100644 --- a/crates/primitives/src/transaction/request.rs +++ b/crates/primitives/src/transaction/request.rs @@ -3,15 +3,19 @@ use alloy_network::{ BuildResult, NetworkTransactionBuilder, NetworkWallet, TransactionBuilder, TransactionBuilder4844, TransactionBuilderError, }; -use alloy_primitives::{Address, B256, ChainId, TxKind, U256}; +use alloy_primitives::{Address, ChainId, TxKind, U256}; use alloy_rpc_types::{AccessList, TransactionInputKind, TransactionRequest}; use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit}; +#[cfg(feature = "optimism")] use op_revm::transaction::deposit::DepositTransactionParts; use serde::{Deserialize, Serialize}; use tempo_alloy::rpc::TempoTransactionRequest; use tempo_primitives::{TEMPO_TX_TYPE_ID, TempoTxType}; +#[cfg(feature = "optimism")] +use super::optimism::get_deposit_tx_parts; use super::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; use crate::FoundryNetwork; @@ -28,6 +32,7 @@ use crate::FoundryNetwork; #[derive(Clone, Debug, PartialEq, Eq)] pub enum FoundryTransactionRequest { Ethereum(TransactionRequest), + #[cfg(feature = "optimism")] Op(WithOtherFields), Tempo(Box), } @@ -44,6 +49,7 @@ impl FoundryTransactionRequest { pub fn into_inner(self) -> TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx.inner, Self::Tempo(tx) => tx.inner, } @@ -55,6 +61,7 @@ impl FoundryTransactionRequest { /// # Returns /// - Ok(deposit_tx_parts) if all necessary keys are present to build a deposit transaction. /// - Err(missing) if some keys are missing to build a deposit transaction. + #[cfg(feature = "optimism")] pub fn get_deposit_tx_parts(&self) -> Result> { match self { Self::Op(tx) => get_deposit_tx_parts(&tx.other), @@ -69,9 +76,11 @@ impl FoundryTransactionRequest { pub fn preferred_type(&self) -> FoundryTxType { match self { Self::Ethereum(tx) => tx.preferred_type().into(), + #[cfg(feature = "optimism")] Self::Op(tx) if tx.inner.transaction_type == Some(POST_EXEC_TX_TYPE_ID) => { FoundryTxType::PostExec } + #[cfg(feature = "optimism")] Self::Op(_) => FoundryTxType::Deposit, Self::Tempo(_) => FoundryTxType::Tempo, } @@ -95,6 +104,7 @@ impl FoundryTransactionRequest { /// Check if all necessary keys are present to build a Deposit transaction, returning a list of /// keys that are missing. + #[cfg(feature = "optimism")] pub fn complete_deposit(&self) -> Result<(), Vec<&'static str>> { self.get_deposit_tx_parts().map(|_| ()) } @@ -123,7 +133,9 @@ impl FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } { @@ -138,9 +150,10 @@ impl FoundryTransactionRequest { /// Converts the request into a `FoundryTypedTx`, handling all Ethereum and OP-stack transaction /// types. pub fn build_typed_tx(self) -> Result { + #[cfg(feature = "optimism")] if let Ok(deposit_tx_parts) = self.get_deposit_tx_parts() { // Build deposit transaction - Ok(FoundryTypedTx::Deposit(TxDeposit { + return Ok(FoundryTypedTx::Deposit(TxDeposit { from: self.from().unwrap_or_default(), source_hash: deposit_tx_parts.source_hash, to: self.kind().unwrap_or_default(), @@ -149,8 +162,9 @@ impl FoundryTransactionRequest { gas_limit: self.gas_limit().unwrap_or_default(), is_system_transaction: deposit_tx_parts.is_system_transaction, input: self.input().cloned().unwrap_or_default(), - })) - } else if self.complete_tempo().is_ok() + })); + } + if self.complete_tempo().is_ok() && let Self::Tempo(tx_req) = self { // Build Tempo transaction @@ -192,6 +206,7 @@ impl Serialize for FoundryTransactionRequest { { match self { Self::Ethereum(tx) => tx.serialize(serializer), + #[cfg(feature = "optimism")] Self::Op(tx) => tx.serialize(serializer), Self::Tempo(tx) => tx.serialize(serializer), } @@ -211,6 +226,7 @@ impl AsRef for FoundryTransactionRequest { fn as_ref(&self) -> &TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_ref(), } @@ -221,6 +237,7 @@ impl AsMut for FoundryTransactionRequest { fn as_mut(&mut self) -> &mut TransactionRequest { match self { Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] Self::Op(tx) => tx, Self::Tempo(tx) => tx.as_mut(), } @@ -244,15 +261,16 @@ impl From> for FoundryTransactionRequest { { tempo_tx_req.set_nonce_key(nonce_key); } - Self::Tempo(Box::new(tempo_tx_req)) - } else if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) + return Self::Tempo(Box::new(tempo_tx_req)); + } + #[cfg(feature = "optimism")] + if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) || tx.transaction_type == Some(POST_EXEC_TX_TYPE_ID) || get_deposit_tx_parts(&tx.other).is_ok() { - Self::Op(tx) - } else { - Self::Ethereum(tx.into_inner()) + return Self::Op(tx); } + Self::Ethereum(tx.into_inner()) } } @@ -264,6 +282,7 @@ impl From for FoundryTransactionRequest { FoundryTypedTx::Eip1559(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip4844(tx) => Self::Ethereum(Into::::into(tx)), FoundryTypedTx::Eip7702(tx) => Self::Ethereum(Into::::into(tx)), + #[cfg(feature = "optimism")] FoundryTypedTx::Deposit(tx) => { let other = OtherFields::from_iter([ ("sourceHash", tx.source_hash.to_string().into()), @@ -272,6 +291,7 @@ impl From for FoundryTransactionRequest { ]); WithOtherFields { inner: Into::::into(tx), other }.into() } + #[cfg(feature = "optimism")] FoundryTypedTx::PostExec(tx) => WithOtherFields { inner: Into::::into(tx), other: OtherFields::default(), @@ -307,8 +327,9 @@ impl From for FoundryTransactionRequest { } } -impl From> for FoundryTransactionRequest { - fn from(tx: op_alloy_rpc_types::Transaction) -> Self { +#[cfg(not(feature = "optimism"))] +impl From> for FoundryTransactionRequest { + fn from(tx: alloy_rpc_types_eth::Transaction) -> Self { tx.inner.into_inner().into() } } @@ -437,7 +458,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559(), FoundryTxType::Eip4844 => self.as_ref().complete_4844(), FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), FoundryTxType::Tempo => self.complete_tempo(), } @@ -448,9 +471,14 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { } fn can_build(&self) -> bool { - self.as_ref().can_build() - || self.complete_deposit().is_ok() - || self.complete_tempo().is_ok() + if self.as_ref().can_build() || self.complete_tempo().is_ok() { + return true; + } + #[cfg(feature = "optimism")] + if self.complete_deposit().is_ok() { + return true; + } + false } fn output_tx_type(&self) -> FoundryTxType { @@ -465,7 +493,9 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), + #[cfg(feature = "optimism")] FoundryTxType::Deposit => self.complete_deposit().ok(), + #[cfg(feature = "optimism")] FoundryTxType::PostExec => self.complete_type(pref).ok(), FoundryTxType::Tempo => self.complete_tempo().ok(), }?; @@ -479,11 +509,21 @@ impl NetworkTransactionBuilder for FoundryTransactionRequest { let inner = self.as_mut(); inner.transaction_type = Some(preferred_type as u8); inner.gas.is_none().then(|| inner.set_gas_limit(Default::default())); - if !matches!(preferred_type, FoundryTxType::Deposit | FoundryTxType::Tempo) { + let is_deposit = { + #[cfg(feature = "optimism")] + { + preferred_type == FoundryTxType::Deposit + } + #[cfg(not(feature = "optimism"))] + { + false + } + }; + if !is_deposit && preferred_type != FoundryTxType::Tempo { inner.trim_conflicting_keys(); inner.populate_blob_hashes(); } - if preferred_type != FoundryTxType::Deposit { + if !is_deposit { inner.nonce.is_none().then(|| inner.set_nonce(Default::default())); } if matches!(preferred_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { @@ -548,42 +588,10 @@ impl TransactionBuilder4844 for FoundryTransactionRequest { } } -/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields -pub fn get_deposit_tx_parts( - other: &OtherFields, -) -> Result> { - let mut missing = Vec::new(); - let source_hash = - other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("sourceHash"); - Default::default() - }, - ); - let mint = other - .get_deserialized::("mint") - .transpose() - .unwrap_or_else(|_| { - missing.push("mint"); - Default::default() - }) - .map(|value| value.to::()); - let is_system_transaction = - other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( - || { - missing.push("isSystemTx"); - Default::default() - }, - ); - if missing.is_empty() { - Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) - } else { - Err(missing) - } -} - #[cfg(test)] mod tests { + use alloy_primitives::B256; + use super::*; fn default_tx_req() -> TransactionRequest { @@ -618,6 +626,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_routing_op_by_deposit_fields() { let tx = default_tx_req(); let mut other = OtherFields::default(); @@ -669,6 +678,7 @@ mod tests { } #[test] + #[cfg(feature = "optimism")] fn test_serialization_op() { let tx = default_tx_req(); let mut other = OtherFields::default(); diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index 7f112ce1bbda8..ce94945fb27cf 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -27,3 +27,7 @@ revm-inspectors.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 332420d0d1c42..d243814c4b148 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -63,3 +63,15 @@ tempo-primitives.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-cli/optimism", + "forge-script-sequence/optimism", + "forge-verify/optimism", +] diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 5ac7e4c669ade..f1bda2ccdcf55 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,8 +1,8 @@ -use std::{cmp::Ordering, sync::Arc, time::Duration}; +use std::{cmp::Ordering, num::NonZeroU64, sync::Arc, time::Duration}; use crate::{ - ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, - sequence::ScriptSequenceKind, verify::BroadcastedState, + ScriptArgs, ScriptConfig, build::LinkedBuildData, needs_script_rpc_estimate, + progress::ScriptProgress, sequence::ScriptSequenceKind, verify::BroadcastedState, }; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::{SignableTransaction, Signed}; @@ -21,11 +21,12 @@ use alloy_signer::Signature; use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; -use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; +use foundry_cli::utils::has_batch_support; use foundry_common::{ FoundryTransactionBuilder, TransactionMaybeSigned, provider::{ProviderBuilder, try_get_http_provider}, shell, + tempo::TempoSponsor, }; use foundry_config::Config; use foundry_evm::core::evm::{FoundryEvmNetwork, TempoEvmNetwork}; @@ -100,7 +101,17 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result<()> { + let access_key_authorization = match self { + Self::AccessKey(_, _, access_key) => Some(( + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.clone(), + )), + _ => None, + }; + if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) @@ -137,11 +148,28 @@ where } } + if let Some((wallet_address, key_address, key_authorization)) = + access_key_authorization.as_ref() + { + tx.prepare_access_key_authorization( + provider, + *wallet_address, + *key_address, + key_authorization.as_ref(), + ) + .await?; + } + // Chains which use `eth_estimateGas` are being sent sequentially and require their // gas to be re-estimated right before broadcasting. if !is_fixed_gas_limit && estimate_via_rpc { estimate_gas(tx, provider, estimate_multiplier).await?; } + + if let Some(sponsor) = tempo_sponsor { + let from = tx.from().expect("no sender"); + sponsor.attach_and_print::(tx, from).await?; + } } Ok(()) @@ -211,6 +239,7 @@ where is_fixed_gas_limit: bool, estimate_via_rpc: bool, estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, ) -> Result { self.prepare( &provider, @@ -218,6 +247,7 @@ where is_fixed_gas_limit, estimate_via_rpc, estimate_multiplier, + tempo_sponsor, ) .await?; @@ -387,6 +417,27 @@ impl BundledState { SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?.map(Arc::new); + if tempo_sponsor.is_some() && self.script_config.tempo.sponsor_sig.is_some() { + let remaining = self + .sequence + .sequences() + .iter() + .map(|sequence| { + sequence + .transactions() + .skip(sequence.receipts.len()) + .filter(|tx| tx.is_unsigned()) + .count() + }) + .sum::(); + if remaining > 1 { + eyre::bail!( + "--tempo.sponsor-sig can only sponsor one remaining script transaction; use --tempo.sponsor-signer for multi-transaction scripts" + ); + } + } + let progress = ScriptProgress::default(); for i in 0..self.sequence.sequences().len() { @@ -464,6 +515,11 @@ impl BundledState { let kind = match tx_with_metadata.tx().clone() { TransactionMaybeSigned::Signed { tx, .. } => { + if tempo_sponsor.is_some() { + eyre::bail!( + "cannot attach Tempo sponsor signature to an already signed script transaction" + ); + } SendTransactionKind::Signed(tx) } TransactionMaybeSigned::Unsigned(mut tx) => { @@ -487,6 +543,8 @@ impl BundledState { tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } + self.script_config.tempo.apply::(&mut tx, None); + send_kind.for_sender(&from, tx)? } }; @@ -495,9 +553,13 @@ impl BundledState { }) .collect::>>()?; - let estimate_via_rpc = has_different_gas_calc(sequence.chain) - || self.script_config.evm_opts.networks.is_tempo() - || self.args.skip_simulation; + let estimate_via_rpc = needs_script_rpc_estimate( + sequence.chain, + self.script_config.evm_opts.networks.is_tempo(), + self.script_config.batch, + &self.script_config.tempo, + self.args.skip_simulation, + ); // We only wait for a transaction receipt before sending the next transaction, if // there is more than one signer. There would be no way of assuring @@ -525,6 +587,7 @@ impl BundledState { let pending_transactions = batch.iter().map(|(kind, is_fixed_gas_limit)| { let provider = provider.clone(); + let tempo_sponsor = tempo_sponsor.clone(); async move { let res = kind .clone() @@ -534,22 +597,36 @@ impl BundledState { *is_fixed_gas_limit, estimate_via_rpc, self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), ) .await; - (res, kind, 0, None) + (res, kind, *is_fixed_gas_limit, 0, None) } .boxed() }); let mut buffer = pending_transactions.collect::>(); - 'send: while let Some((res, kind, attempt, original_res)) = - buffer.next().await + 'send: while let Some(( + res, + kind, + is_fixed_gas_limit, + attempt, + original_res, + )) = buffer.next().await { - if res.is_err() && attempt <= 3 { + if res.is_err() + && self.script_config.tempo.sponsor_sig.is_some() + && attempt == 0 + { + debug!( + "not retrying transaction because --tempo.sponsor-sig is a static signature" + ); + } else if res.is_err() && attempt <= 3 { // Try to resubmit the transaction let provider = provider.clone(); let progress = seq_progress.inner.clone(); + let tempo_sponsor = tempo_sponsor.clone(); buffer.push(Box::pin(async move { debug!(err=?res, ?attempt, "retrying transaction "); let attempt = attempt + 1; @@ -557,8 +634,24 @@ impl BundledState { "retrying transaction {res:?} (attempt {attempt})" )); tokio::time::sleep(Duration::from_millis(1000 * attempt)).await; - let r = kind.clone().send(provider).await; - (r, kind, attempt, original_res.or(Some(res))) + let r = kind + .clone() + .prepare_and_send( + provider, + sequential_broadcast, + is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), + ) + .await; + ( + r, + kind, + is_fixed_gas_limit, + attempt, + original_res.or(Some(res)), + ) })); continue 'send; @@ -675,6 +768,7 @@ impl BundledState { let sequence = self.sequence.sequences_mut().get_mut(0).unwrap(); let provider = Arc::new(ProviderBuilder::::new(sequence.rpc_url()).build()?); + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?; // Collect sender addresses - batch mode requires single sender let senders: AddressHashSet = sequence @@ -794,16 +888,35 @@ impl BundledState { max_priority_fee_per_gas: Some(max_priority_fee_per_gas), ..Default::default() }, - fee_token: self.script_config.fee_token, + fee_token: self.script_config.tempo.common.fee_token, calls: calls.clone(), + nonce_key: self.script_config.tempo.expiring_nonce.then_some(U256::MAX), + valid_before: self.script_config.tempo.valid_before.and_then(NonZeroU64::new), ..Default::default() }; + self.script_config.tempo.apply::(&mut batch_tx, None); + + if let BatchSigner::TempoKeychain(_, ak) = &batch_signer { + batch_tx.key_id = Some(ak.key_address); + batch_tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } // Estimate gas for the batch transaction estimate_gas(&mut batch_tx, provider.as_ref(), self.args.gas_estimate_multiplier).await?; sh_println!("Estimated gas: {}", batch_tx.inner.gas.unwrap_or(0))?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut batch_tx, sender).await?; + } + // Sign and send let tx_hash = match batch_signer { BatchSigner::Wallet(wallet) => { @@ -816,8 +929,6 @@ impl BundledState { *pending.tx_hash() } BatchSigner::TempoKeychain(signer, access_key) => { - batch_tx.key_id = Some(access_key.key_address); - let raw_tx = batch_tx .sign_with_access_key( provider.as_ref(), diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index f5e4d46de0344..ea906c9b872e1 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -28,8 +28,8 @@ use eyre::{ContextCompat, Result}; use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; use foundry_cli::{ - opts::{BuildOpts, EvmArgs, GlobalArgs}, - utils::{LoadConfig, parse_fee_token_address}, + opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts}, + utils::{LoadConfig, has_different_gas_calc}, }; use foundry_common::{ CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, @@ -44,11 +44,13 @@ use foundry_config::{ value::{Dict, Map}, }, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ backend::Backend, core::{ Breakpoints, FoundryTransaction, - evm::{EthEvmNetwork, FoundryEvmNetwork, OpEvmNetwork, TempoEvmNetwork, TxEnvFor}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, tempo::PATH_USD_ADDRESS, }, executors::ExecutorBuilder, @@ -140,9 +142,9 @@ pub struct ScriptArgs { #[arg(long, requires = "batch", default_value = "100")] pub batch_size: usize, - /// Tempo fee token address for paying transaction fees. - #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] - pub fee_token: Option

, + /// Tempo transaction options. + #[command(flatten)] + pub tempo: TempoOpts, /// Skips on-chain simulation. #[arg(long)] @@ -246,13 +248,43 @@ pub struct ScriptArgs { pub retry: RetryArgs, } +const fn should_default_tempo_fee_token( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + // Plain `--network tempo` should stay an ordinary transaction; only Tempo AA opts get defaults. + is_tempo_network && tempo.common.fee_token.is_none() && (batch || tempo.is_tempo()) +} + +const fn needs_tempo_aa_rpc_estimate( + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, +) -> bool { + is_tempo_network && (batch || tempo.is_tempo()) +} + +pub(crate) fn needs_script_rpc_estimate( + chain_id: u64, + is_tempo_network: bool, + batch: bool, + tempo: &TempoOpts, + skip_simulation: bool, +) -> bool { + // Tempo AA needs RPC estimation; plain Tempo scripts can use the local simulation result. + (has_different_gas_calc(chain_id) && !is_tempo_network) + || needs_tempo_aa_rpc_estimate(is_tempo_network, batch, tempo) + || skip_simulation +} + impl ScriptArgs { /// Loads config, resolves evm_opts (including network inference from fork), and returns them. async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { let (config, mut evm_opts) = self.load_config_and_evm_opts()?; - if self.fee_token.is_some() { - // If fee token is set directly select tempo + if self.tempo.is_tempo() { + // If fee token or expiry is set directly select tempo evm_opts.networks = NetworkConfigs::with_tempo(); } else { // Auto-detect network from fork chain ID when not explicitly configured. @@ -285,13 +317,14 @@ impl ScriptArgs { } } - let fee_token = if evm_opts.networks.is_tempo() && self.fee_token.is_none() { - Some(PATH_USD_ADDRESS) - } else { - self.fee_token - }; + let mut tempo = self.tempo.clone(); + tempo.resolve_expires(); + + if should_default_tempo_fee_token(evm_opts.networks.is_tempo(), self.batch, &tempo) { + tempo.common.fee_token = Some(PATH_USD_ADDRESS); + } - let script_config = ScriptConfig::new(config, evm_opts, self.batch, fee_token).await?; + let script_config = ScriptConfig::new(config, evm_opts, self.batch, tempo).await?; Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } @@ -320,12 +353,15 @@ impl ScriptArgs { if broadcasted.args.verify { broadcasted.verify().await?; } - Ok(()) - } else if evm_opts.networks.is_optimism() { - self.run_generic_script::(config, evm_opts).await - } else { - self.run_generic_script::(config, evm_opts).await + return Ok(()); } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_generic_script::(config, evm_opts).await; + } + + self.run_generic_script::(config, evm_opts).await } /// Prepares the bundled state (compile, simulate, bundle) and returns it @@ -708,8 +744,8 @@ pub struct ScriptConfig { pub backends: HashMap>, /// Whether to batch all broadcast transactions into a single Tempo batch transaction. pub batch: bool, - /// Tempo fee token address for paying transaction fees. - pub fee_token: Option
, + /// Tempo transaction options applied to broadcast transactions. + pub tempo: TempoOpts, } impl ScriptConfig { @@ -717,7 +753,7 @@ impl ScriptConfig { config: Config, evm_opts: EvmOpts, batch: bool, - fee_token: Option
, + tempo: TempoOpts, ) -> Result { let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? @@ -726,7 +762,7 @@ impl ScriptConfig { 1 }; - Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, fee_token }) + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, tempo }) } pub async fn update_sender(&mut self, sender: Address) -> Result<()> { @@ -802,7 +838,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), - self.fee_token, + self.tempo.common.fee_token, ) .into(), ) @@ -813,7 +849,7 @@ impl ScriptConfig { // Propagate fee token to the transaction environment so that internal EVM calls // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. - tx_env.set_fee_token(self.fee_token); + tx_env.set_fee_token(self.tempo.common.fee_token); Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } @@ -823,6 +859,7 @@ impl ScriptConfig { mod tests { use super::*; use alloy_network::Ethereum; + use alloy_primitives::address; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -834,6 +871,50 @@ mod tests { assert_eq!(args.sig, sig); } + #[test] + fn can_parse_shared_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.fee-token", + "1", + "--tempo.expires", + "10", + ]); + + assert_eq!( + args.tempo.common.fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")) + ); + assert_eq!(args.tempo.common.expires, Some(10)); + } + + #[test] + fn can_parse_sponsor_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]); + + assert_eq!( + args.tempo.sponsor, + Some(address!("0x1111111111111111111111111111111111111111")) + ); + assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + } + + #[test] + fn can_parse_full_tempo_opts() { + let args = + ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]); + + assert_eq!(args.tempo.nonce_key, Some(U256::from(1))); + } + #[test] fn can_parse_unlocked() { let args = ScriptArgs::parse_from([ diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index e2404d60ce2b9..b085f8eaf4545 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -6,7 +6,7 @@ use alloy_network::TransactionBuilder; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; -use foundry_common::{FoundryTransactionBuilder, TransactionMaybeSigned}; +use foundry_common::TransactionMaybeSigned; use foundry_config::Config; use foundry_evm::{ constants::CALLER, @@ -84,9 +84,7 @@ impl ScriptRunner { .with_input(code.clone()) .with_nonce(sender_nonce + library_transactions.len() as u64); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), @@ -122,9 +120,7 @@ impl ScriptRunner { .with_nonce(sender_nonce + library_transactions.len() as u64) .with_to(create2_deployer); - if let Some(fee_token) = script_config.fee_token { - tx_req.set_fee_token(fee_token); - } + script_config.tempo.apply::(&mut tx_req, None); library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index ef10a1ce94082..fe1e9345fa23c 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -129,7 +129,7 @@ impl VerifyBundle { path: Some(artifact.source.to_string_lossy().to_string()), name: artifact .name - .strip_suffix(&format!(".{}", &artifact.profile)) + .strip_suffix(&format!(".{}", artifact.profile)) .unwrap_or_else(|| &artifact.name) .to_string(), }; diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index 69ea952d4d040..d3ad56a96cdd2 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -27,3 +27,7 @@ prettyplease.workspace = true eyre.workspace = true heck.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index d29f6358e93d2..8dc652bcb32bd 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -44,3 +44,7 @@ idna_adapter.workspace = true [dev-dependencies] tokio.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 0a3fad720ef57..fa065425b5281 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -49,7 +49,7 @@ static TEMPLATE_LOCK: LazyLock = LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template.lock")); /// The default Solc version used when compiling tests. -pub const SOLC_VERSION: &str = "0.8.33"; +pub const SOLC_VERSION: &str = "0.8.35"; /// Another Solc version used when compiling tests. /// diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 65a202911509f..e3372a2494f34 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -48,3 +48,12 @@ url.workspace = true tokio = { workspace = true, features = ["macros"] } foundry-test-utils.workspace = true tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-cli/optimism", +] diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs new file mode 100644 index 0000000000000..a86b568fdea2b --- /dev/null +++ b/crates/wallets/src/tempo.rs @@ -0,0 +1,196 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use alloy_signer::Signer; +use eyre::Result; +use std::path::PathBuf; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::transaction::{ + KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, +}; + +use crate::{WalletSigner, utils}; + +/// Wallet type: how this wallet was created. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// A single entry from Tempo's `keys.toml`. +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct KeyEntry { + #[serde(default)] + wallet_type: WalletType, + #[serde(default)] + wallet_address: Address, + #[serde(default)] + chain_id: u64, + #[serde(default)] + key_type: KeyType, + #[serde(default)] + key_address: Option
, + #[serde(default)] + key: Option, + #[serde(default)] + key_authorization: Option, + #[serde(default)] + expiry: Option, + #[serde(default)] + limits: Vec, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(serde::Deserialize)] +struct StoredTokenLimit { + #[allow(dead_code)] + currency: Address, + #[allow(dead_code)] + limit: String, +} + +/// The top-level structure of `~/.tempo/wallet/keys.toml`. +#[derive(serde::Deserialize)] +struct KeysFile { + #[serde(default)] + keys: Vec, +} + +/// Configuration for a Tempo access key (keychain mode). +/// +/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer +/// is an access key that signs on behalf of the root wallet. This struct carries the metadata +/// needed to construct the correct transaction. +#[derive(Debug, Clone)] +pub struct TempoAccessKeyConfig { + /// The root wallet address (the `from` address for transactions). + pub wallet_address: Address, + /// The access key's address (derived from the private key that actually signs). + pub key_address: Address, + /// Decoded key authorization for on-chain provisioning. + /// + /// When present, callers should check whether the key is already provisioned on-chain + /// (via the AccountKeychain precompile) before including this in a transaction. + pub key_authorization: Option, +} + +/// Result of looking up an address in Tempo's key store. +pub enum TempoLookup { + /// A direct (EOA) signer was found — `wallet_address == key_address`. + Direct(WalletSigner), + /// A keychain (access key) signer was found — `wallet_address != key_address`. + Keychain(WalletSigner, Box), + /// No matching entry was found. + NotFound, +} + +/// Returns the path to Tempo's keys file. +/// +/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. +fn keys_path() -> Option { + let base = std::env::var_os("TEMPO_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; + Some(base.join("wallet").join("keys.toml")) +} + +/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. +fn decode_key_authorization(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str)?; + let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; + Ok(auth) +} + +/// Looks up a signer for the given address in Tempo's `keys.toml`. +/// +/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, +/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, +/// or [`TempoLookup::NotFound`] if no entry matches. +pub fn lookup_signer(from: Address) -> Result { + let path = match keys_path() { + Some(p) if p.is_file() => p, + _ => return Ok(TempoLookup::NotFound), + }; + + let contents = std::fs::read_to_string(&path)?; + let file: KeysFile = toml::from_str(&contents)?; + + for entry in &file.keys { + if entry.wallet_address != from { + continue; + } + + let Some(key) = &entry.key else { + continue; + }; + + // Direct mode: wallet_address == key_address (or key_address is absent). + let is_direct = + entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let signer = utils::create_private_key_signer(key)?; + + if is_direct { + return Ok(TempoLookup::Direct(signer)); + } + + // Keychain mode: the key is an access key signing on behalf of wallet_address. + let key_authorization = + entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; + + let config = TempoAccessKeyConfig { + wallet_address: entry.wallet_address, + // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address + key_address: entry.key_address.unwrap(), + key_authorization, + }; + return Ok(TempoLookup::Keychain(signer, Box::new(config))); + } + + Ok(TempoLookup::NotFound) +} + +/// Signs a Tempo transaction request using an access key (keychain V2 mode). +/// +/// Bypasses the standard `EthereumWallet` signing path and instead: +/// 1. Builds the `TempoTransaction` from the request +/// 2. Computes the V2 keychain signing hash +/// 3. Signs with the access key +/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format +pub async fn sign_with_access_key( + tx_request: impl Into, + signer: &impl Signer, + wallet_address: Address, +) -> Result> { + let tx_request: TempoTransactionRequest = tx_request.into(); + let tempo_tx = tx_request + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + + Ok(buf) +} diff --git a/deny.toml b/deny.toml index 1a0e1e8e53005..0f891df4bfbfe 100644 --- a/deny.toml +++ b/deny.toml @@ -100,7 +100,10 @@ unknown-git = "deny" allow-git = [ "https://github.com/alloy-rs/alloy", "https://github.com/alloy-rs/evm", + "https://github.com/foundry-rs/compilers", + "https://github.com/foundry-rs/foundry-fork-db", "https://github.com/foundry-rs/foundry-core", + "https://github.com/foundry-rs/optimism", "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/paradigmxyz/solar", "https://github.com/bluealloy/revm", @@ -111,7 +114,5 @@ allow-git = [ "https://github.com/tempoxyz/mpp-rs", # Transitive dependency of Tempo "https://github.com/paradigmxyz/reth", - "https://github.com/paradigmxyz/reth-core", - # Temporary: upstream OP crates until release is published. - "https://github.com/ethereum-optimism/optimism", + "https://github.com/stevencartavia/reth", ] diff --git a/docs/dev/lintrules.md b/docs/dev/lintrules.md index 6f5dbbd850784..969d7effe142f 100644 --- a/docs/dev/lintrules.md +++ b/docs/dev/lintrules.md @@ -60,6 +60,8 @@ Next, choose whether you want an [early or late lint pass](#choosing-between-ear - Implement the appropriate trait logic (`EarlyLintPass` or `LateLintPass`) for your lint. Do it in a new file within the relevant severity module (e.g., `src/sol/med/my_new_lint.rs`). +- Add a markdown documentation file for the lint at `crates/lint/docs/.md`. The file is referenced by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the [Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. Use [`crates/lint/docs/_template.md`](../../crates/lint/docs/_template.md) as a starting point. The presence of this file is enforced by the `registered_lints_have_docs` unit test in `crates/lint/src/sol/mod.rs`. + ### Choosing Between Early and Late Passes - **Use `EarlyLintPass`** for: diff --git a/flake.lock b/flake.lock index 27f426f491da6..343a79c0cda3d 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1777102577, - "narHash": "sha256-ycoy9svZOQgyInu/lwO7IEQtlP5liqYhEcF9m9hPRbM=", + "lastModified": 1777708550, + "narHash": "sha256-Qif3UXT0l5OQq8H9pRWt4/ia4gF48MWK2oHKL8uVx8U=", "owner": "nix-community", "repo": "fenix", - "rev": "f37403486c59376cd285f9685a8ef8ff25c09a3c", + "rev": "74c1591efaff494756b8d35ebe357c6c2bbdca96", "type": "github" }, "original": { @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776949667, - "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=", + "lastModified": 1777641297, + "narHash": "sha256-WNGcmeOZ8Tr9dq6ztCspYbzWFswr2mPebM9LpsfGxPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30", + "rev": "c6d65881c5624c9cae5ea6cedef24699b0c0a4c0", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1776800521, - "narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=", + "lastModified": 1777639980, + "narHash": "sha256-6d7Hdurvbjc5uwJuc0YiK7rZBGj6Gs3uzfBFcTs+xCc=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e", + "rev": "64cdaeb06f69b6b769a492edd88b022ae88e8ca2", "type": "github" }, "original": { diff --git a/foundryup/README.md b/foundryup/README.md index 29e91378929cc..2cf61f8725227 100644 --- a/foundryup/README.md +++ b/foundryup/README.md @@ -30,10 +30,10 @@ To install the latest **nightly** version: foundryup --install nightly ``` -To install a specific version (e.g. `v1.6.0`): +To install a specific version (e.g. `v1.7.0`): ```sh -foundryup --install v1.6.0 +foundryup --install v1.7.0 ``` To **list** all **versions** installed: diff --git a/foundryup/foundryup b/foundryup/foundryup index 9bbaea82e7e21..5fb086a75f8c1 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -3,7 +3,7 @@ set -eo pipefail # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -FOUNDRYUP_INSTALLER_VERSION="1.8.0" +FOUNDRYUP_INSTALLER_VERSION="1.8.3" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} @@ -15,6 +15,13 @@ FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" FOUNDRYUP_JOBS="" FOUNDRYUP_IGNORE_VERIFICATION=false +# Retry/backoff settings used for `fetch` (GitHub API calls). +# Recovers from transient HTTP 403/429/5xx responses returned by +# api.github.com under heavy load or per-IP rate limiting. +FOUNDRYUP_MAX_RETRIES=5 +FOUNDRYUP_RETRY_DELAY=2 +FOUNDRYUP_RETRY_MAX_TIME=60 + BINS=(forge cast anvil chisel) HASH_NAMES=() HASH_VALUES=() @@ -111,43 +118,7 @@ main() { # Install by downloading binaries if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-latest} - - # Normalize versions (handle channels, versions without v prefix) - if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then - # Resolve to the latest release (non-prerelease) via the GitHub API - say "fetching latest release from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' - /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } - ') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a latest release for ${FOUNDRYUP_REPO}" - fi - say "resolved release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" - elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then - # Resolve to the latest nightly (prerelease) release via the GitHub API - say "fetching latest nightly releases from ${FOUNDRYUP_REPO}..." - FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' - /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } - /"draft"[[:space:]]*:[[:space:]]*true/ { tag="" } - /"prerelease"[[:space:]]*:[[:space:]]*true/ && !found { if (tag ~ /^nightly-/) { print tag; found=1 } } - ') || err "failed to fetch releases from GitHub API" - if [ -z "$FOUNDRYUP_TAG" ]; then - err "could not find a nightly release for ${FOUNDRYUP_REPO}" - fi - say "resolved nightly release tag: ${FOUNDRYUP_TAG}" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then - # Specific nightly tag (e.g. nightly-abc123...) - FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then - # Add v prefix - FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - else - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - fi + resolve_version_and_tag say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})" @@ -171,7 +142,7 @@ main() { tmp_dir="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" tmp="$tmp_dir/attestation.txt" ensure download "$ATTESTATION_URL" "$tmp" - + # Read the first line of the attestation file to get the artifact link. # The first line should contain the link to the attestation artifact. attestation_artifact_link="$(head -n1 "$tmp" | tr -d '\r')" @@ -284,7 +255,7 @@ main() { else say 'skipping manpage download: missing "tar"' fi - + if [ "$FOUNDRYUP_IGNORE_VERIFICATION" = true ]; then say "skipped SHA verification for downloaded binaries due to --force flag" else @@ -497,6 +468,18 @@ list() { use() { [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" + + # If the requested version is a channel (`latest`, `stable`, `nightly`) or a bare semver + # version (e.g. `1.7.0`, `1.6.0-rc1`), resolve it to the immutable tag directory created by + # `--install` (channels hit the GitHub API; semver versions get a `v` prefix). + # Falls back to the literal value for locally-built versions (branches, PRs, commits, custom names). + case "$FOUNDRYUP_VERSION" in + latest|stable|nightly|[0-9]*.[0-9]*.[0-9]*) + resolve_version_and_tag + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + ;; + esac + FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" if [ -d "$FOUNDRY_VERSION_DIR" ]; then @@ -675,12 +658,70 @@ ensure() { if ! "$@"; then err "command failed: $*"; fi } -# Silently fetches $1 to stdout +# Normalizes `FOUNDRYUP_VERSION` and resolves it to a concrete release tag, +# populating `FOUNDRYUP_TAG`. Handles the `latest`/`stable`/`nightly` channels +# (looked up via the GitHub API). +resolve_version_and_tag() { + FOUNDRYUP_REPO=${FOUNDRYUP_REPO:-foundry-rs/foundry} + if [[ "$FOUNDRYUP_VERSION" == "latest" || "$FOUNDRYUP_VERSION" == "stable" ]]; then + # Resolve to the latest release (non-prerelease) via the GitHub API. + say "fetching latest release tag from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases/latest" | awk ' + /"tag_name"[[:space:]]*:/ && !found { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; found=1 } + ') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a latest release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + elif [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then + # Resolve to the latest nightly (prerelease) release via the GitHub API. + # The GitHub API does not guarantee that releases are returned in + # chronological order, so we collect all matching nightlies along with + # their `published_at` timestamps and sort them ourselves. + say "fetching latest nightly release tags from ${FOUNDRYUP_REPO}..." + FOUNDRYUP_TAG=$(fetch "https://api.github.com/repos/${FOUNDRYUP_REPO}/releases" | awk ' + /"tag_name"[[:space:]]*:/ { gsub(/.*"tag_name"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); tag=$0 } + /"published_at"[[:space:]]*:[[:space:]]*"/ { + pub=$0 + gsub(/.*"published_at"[[:space:]]*:[[:space:]]*"/, "", pub) + gsub(/".*/, "", pub) + if (tag ~ /^nightly-/) print pub "\t" tag + tag="" + } + ' | sort -r | awk -F '\t' 'NR==1 { print $2 }') || err "failed to fetch release tags from GitHub API" + if [ -z "$FOUNDRYUP_TAG" ]; then + err "could not find a nightly release tag for ${FOUNDRYUP_REPO}" + fi + say "resolved nightly release tag: ${FOUNDRYUP_TAG}" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" =~ ^nightly- ]]; then + # Specific nightly tag (e.g. nightly-abc123...) + FOUNDRYUP_TAG="$FOUNDRYUP_VERSION" + FOUNDRYUP_VERSION="nightly" + elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then + # Add v prefix + FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + else + FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" + fi +} + +# Silently fetches $1 to stdout. fetch() { if check_cmd curl; then - curl -fsSL "$1" + curl -fsSL \ + --retry "$FOUNDRYUP_MAX_RETRIES" \ + --retry-delay "$FOUNDRYUP_RETRY_DELAY" \ + --retry-max-time "$FOUNDRYUP_RETRY_MAX_TIME" \ + --retry-all-errors \ + "$1" else - wget -qO- "$1" + wget --tries="$FOUNDRYUP_MAX_RETRIES" \ + --waitretry="$FOUNDRYUP_RETRY_DELAY" \ + --retry-on-http-error=403,408,429,500,502,503,504 \ + -qO- "$1" fi } diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 839d97962aa94..ae0c8ed844f5d 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -305,6 +305,91 @@ contract ExpectRevertWithReverterTest is Test { vm.expectRevert(address(cContract)); aContract.createDContractThroughCContract(); } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // reverts directly, the reverter address argument must be enforced (it used to + // be silently ignored). The matched reverter is the would-be-deployed address. + function testExpectRevertsWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new DContract(); + + expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(abi.encodePacked("Reverted by DContract"), expected); + new DContract(); + } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // synchronously creates another contract that reverts (i.e. innermost frame is + // a CREATE), the matched reverter is the outer would-be-deployed address (the + // contract whose deployment failed). + function testExpectRevertsWithReverterNestedCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new NestedDContractCreator(); + } + + // + // Regression: `expectPartialRevert(bytes4, address)` overload must enforce + // the reverter address argument when matching a top-level CREATE revert. + function testExpectPartialRevertWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + // `Reverted by DContract` triggers Solidity's `Error(string)` selector. + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), expected); + new DContract(); + } + + // + // Regression: `expectRevert(bytes4, address)` (exact 4-byte selector + reverter) + // overload must enforce the reverter address argument for a top-level CREATE. + function testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(DCustomErrorContract.CustomError.selector, expected); + new DCustomErrorContract(); + } + + // + // Regression: `expectRevert(address, uint64)` count-bearing overload must + // exercise the `count > 1` branch in `create_end`. Use CREATE2 with the same + // salt so both deploys would resolve to the same would-be address (each + // constructor reverts so no contract is ever actually placed there). + function testExpectRevertsWithReverterCountTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0x42)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected, 2); + new DContract{salt: salt}(); + new DContract{salt: salt}(); + } + + // + // Regression: CREATE2 deploys must also enforce the reverter address argument. + function testExpectRevertsWithReverterTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0xC0FFEE)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected); + new DContract{salt: salt}(); + } +} + +// Used by `testExpectRevertsWithReverterNestedCreate`: a contract whose constructor +// directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } +} + +// Used by `testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate`: constructor +// reverts with a parameter-less custom error so the full revert data is exactly the +// 4-byte selector. +contract DCustomErrorContract { + error CustomError(); + + constructor() { + revert CustomError(); + } } contract ExpectRevertCount is Test { diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 0941e508483fd..d83c0480b7e72 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -325,6 +325,7 @@ contract ForkTest is Test { struct LegacyTransactionResult { bytes32 blockHash; bytes blockNumber; + bytes blockTimestamp; bytes chainId; address from; bytes gas; diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index 6139b8b6b6a5e..f01b7cdd7d213 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -84,4 +84,55 @@ contract GetFoundryVersionTest is Test { // Should return true for past versions assertTrue(vm.foundryVersionAtLeast("0.2.0")); } + + /// Returns the `MAJOR.MINOR.PATCH` prefix of `vm.getFoundryVersion()`, + /// stripping any pre-release suffix (`-nightly`, `-dev`, …) and the + /// `+..` build metadata. + function _semverPrefix() internal view returns (string memory) { + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + string[] memory dashSplit = vm.split(plusSplit[0], "-"); + return dashSplit[0]; + } + + function testGetFoundryVersionMajorMinorPatchIsParseable() public view { + // The MAJOR.MINOR.PATCH prefix must always be three numeric components, + // regardless of build kind (tagged release / nightly / dev). + string[] memory parts = vm.split(_semverPrefix(), "."); + require(parts.length == 3, "Invalid semver prefix: expected MAJOR.MINOR.PATCH"); + // Each component must parse as a uint (this reverts on garbage). + vm.parseUint(parts[0]); + vm.parseUint(parts[1]); + vm.parseUint(parts[2]); + } + + function testGetFoundryVersionBuildProfile() public view { + // The build profile must be present and non-empty (e.g. "debug", "release", "dist", …). + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + string[] memory metadataComponents = vm.split(plusSplit[1], "."); + require(bytes(metadataComponents[2]).length > 0, "Build profile is empty"); + } + + function testFoundryVersionCmpAndAtLeastAreConsistent() public { + // `foundryVersionAtLeast(v)` must equal `foundryVersionCmp(v) >= 0` for any input. + string[3] memory probes = ["0.0.1", _semverPrefix(), "99.0.0"]; + for (uint256 i = 0; i < probes.length; i++) { + assertEq(vm.foundryVersionAtLeast(probes[i]), vm.foundryVersionCmp(probes[i]) >= 0); + } + } + + function testFoundryVersionCmpRejectsPreRelease() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0-nightly"); + } + + function testFoundryVersionCmpRejectsBuildMetadata() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0+abc1234567.1700000000.release"); + } + + function testFoundryVersionCmpRejectsInvalidVersion() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("not-a-version"); + } } diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index e2ac74d6f70fa..d8019ab4f6ee8 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -158,6 +158,35 @@ contract MockCallTest is Test { assertEq(mock.pay{value: 50}(1), 100); } + function testMockCallWithValueTransfersBalance() public { + Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + assertEq(address(mock).balance, 0); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + } + + function testMockCallWithValueTransfersPrankedSenderBalance() public { + Mock mock = new Mock(); + address sender = address(0xBEEF); + uint256 value = 10; + vm.deal(address(this), 0); + vm.deal(sender, value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + vm.prank(sender); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + assertEq(sender.balance, 0); + } + function testMockCallWithValueCalldataPrecedence() public { Mock mock = new Mock(); @@ -279,17 +308,25 @@ contract MockCallRevertTest is Test { function testMockCallRevertWithValue() public { Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); - vm.mockCallRevert(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); + vm.mockCallRevert(address(mock), value, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - try mock.pay{value: 10}(1) { + uint256 initSenderBalance = address(this).balance; + uint256 initTargetBalance = address(mock).balance; + + try mock.pay{value: value}(1) { revert(); } catch (bytes memory err) { require(keccak256(err) == keccak256(ERROR_MESSAGE)); } + + assertEq(address(this).balance, initSenderBalance); + assertEq(address(mock).balance, initTargetBalance); } function testMockCallResetsMockCallRevert() public { diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol index e0f5eef151db6..777543f28e361 100644 --- a/testdata/default/cheats/MockCalls.t.sol +++ b/testdata/default/cheats/MockCalls.t.sol @@ -28,13 +28,17 @@ contract MockCallsTest is Test { mocks[0] = abi.encode(2 ether); mocks[1] = abi.encode(1 ether); mocks[2] = abi.encode(6.423 ether); + vm.deal(address(this), 3 ether); vm.mockCalls(mockErc20, 1 ether, data, mocks); (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret1, (uint256)), 2 ether); + assertEq(mockErc20.balance, 1 ether); (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret2, (uint256)), 1 ether); + assertEq(mockErc20.balance, 2 ether); (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + assertEq(mockErc20.balance, 3 ether); } function testMockCalls() public { diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev index b1716a0a12950..977c31eec5512 100644 --- a/testdata/forge-std-rev +++ b/testdata/forge-std-rev @@ -1 +1 @@ -8987040ede9553cea20c95ad40d0455930f9c8e0 \ No newline at end of file +620536fa5277db4e3fd46772d5cbc1ea0696fb43 \ No newline at end of file diff --git a/testdata/utils/Vm.sol b/testdata/utils/Vm.sol index d9f9b52821f52..e488a1820453e 100644 --- a/testdata/utils/Vm.sol +++ b/testdata/utils/Vm.sol @@ -36,121 +36,121 @@ interface Vm { function addr(uint256 privateKey) external pure returns (address keyAddr); function allowCheatcodes(address account) external; function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; - function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; - function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; - function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure; function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; - function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure; function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure; - function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure; - function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; - function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure; function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; - function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure; function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertEq(bool left, bool right) external pure; - function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(bool left, bool right, string calldata err) external pure; function assertEq(string calldata left, string calldata right) external pure; - function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right, string calldata err) external pure; function assertEq(bytes calldata left, bytes calldata right) external pure; - function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure; function assertEq(bool[] calldata left, bool[] calldata right) external pure; - function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; - function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; function assertEq(int256[] calldata left, int256[] calldata right) external pure; - function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; function assertEq(uint256 left, uint256 right) external pure; function assertEq(address[] calldata left, address[] calldata right) external pure; - function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure; function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; - function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; function assertEq(string[] calldata left, string[] calldata right) external pure; - function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure; function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; - function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; - function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; + function assertEq(uint256 left, uint256 right, string calldata err) external pure; function assertEq(int256 left, int256 right) external pure; - function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right, string calldata err) external pure; function assertEq(address left, address right) external pure; - function assertEq(address left, address right, string calldata error) external pure; + function assertEq(address left, address right, string calldata err) external pure; function assertEq(bytes32 left, bytes32 right) external pure; - function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata err) external pure; function assertFalse(bool condition) external pure; - function assertFalse(bool condition, string calldata error) external pure; + function assertFalse(bool condition, string calldata err) external pure; function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertGe(uint256 left, uint256 right) external pure; - function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(uint256 left, uint256 right, string calldata err) external pure; function assertGe(int256 left, int256 right) external pure; - function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right, string calldata err) external pure; function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertGt(uint256 left, uint256 right) external pure; - function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(uint256 left, uint256 right, string calldata err) external pure; function assertGt(int256 left, int256 right) external pure; - function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right, string calldata err) external pure; function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertLe(uint256 left, uint256 right) external pure; - function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(uint256 left, uint256 right, string calldata err) external pure; function assertLe(int256 left, int256 right) external pure; - function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right, string calldata err) external pure; function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertLt(uint256 left, uint256 right) external pure; - function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(uint256 left, uint256 right, string calldata err) external pure; function assertLt(int256 left, int256 right) external pure; - function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right, string calldata err) external pure; function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertNotEq(bool left, bool right) external pure; - function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(bool left, bool right, string calldata err) external pure; function assertNotEq(string calldata left, string calldata right) external pure; - function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata err) external pure; function assertNotEq(bytes calldata left, bytes calldata right) external pure; - function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure; function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; - function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; - function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; - function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; function assertNotEq(uint256 left, uint256 right) external pure; function assertNotEq(address[] calldata left, address[] calldata right) external pure; - function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure; function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; - function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; function assertNotEq(string[] calldata left, string[] calldata right) external pure; - function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure; function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; - function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; - function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata err) external pure; function assertNotEq(int256 left, int256 right) external pure; - function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right, string calldata err) external pure; function assertNotEq(address left, address right) external pure; - function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(address left, address right, string calldata err) external pure; function assertNotEq(bytes32 left, bytes32 right) external pure; - function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure; function assertTrue(bool condition) external pure; - function assertTrue(bool condition, string calldata error) external pure; + function assertTrue(bool condition, string calldata err) external pure; function assume(bool condition) external pure; function assumeNoRevert() external pure; function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; From de9cf905299ea383a5fe668a8d69e8440683c3a4 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 09:06:17 +0700 Subject: [PATCH 383/391] Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/scripts/compare-nightly.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh index 674cc0fe01754..5b87aa0618e4c 100644 --- a/.github/scripts/compare-nightly.sh +++ b/.github/scripts/compare-nightly.sh @@ -39,7 +39,7 @@ for key in all_keys: if p is None: print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") continue - delta = (t - p) / p * 100 + delta = (t - p) / p * 100 if p > 0 else 0 if delta >= fail: status = "🔴 Regression" has_regression = True From 2b8840d349f42a733fa89d544eea962d72731b35 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 09:06:33 +0700 Subject: [PATCH 384/391] Update crates/forge/src/cmd/test/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- crates/forge/src/cmd/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index dd8f3afd56197..938e9674c0cbe 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -1166,7 +1166,7 @@ fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { let base_suite = e.get_mut(); base_suite.test_results.extend(other_suite.test_results); base_suite.warnings.extend(other_suite.warnings); - base_suite.duration = base_suite.duration.max(other_suite.duration); + base_suite.duration += other_suite.duration; } } } From 397c9f63c791f3eb1ee91b229c24f1028d5855b5 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 10:24:37 +0700 Subject: [PATCH 385/391] dargon789/gamefi (#531) * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .github/scripts/compare-nightly.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) mode change 100644 => 100755 .github/scripts/compare-nightly.sh diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh old mode 100644 new mode 100755 index 5b87aa0618e4c..c2cec964cf52d --- a/.github/scripts/compare-nightly.sh +++ b/.github/scripts/compare-nightly.sh @@ -19,7 +19,10 @@ warn = float(os.environ["WARN"]) fail = float(os.environ["FAIL"]) prev_path = os.environ.get("PREV_JSON", "") -prev = json.load(open(prev_path)) if prev_path and os.path.isfile(prev_path) else {} +prev = {} +if prev_path and os.path.isfile(prev_path): + with open(prev_path) as f: + prev = json.load(f) with open(os.environ["TODAY_JSON"]) as f: today = json.load(f) @@ -39,7 +42,10 @@ for key in all_keys: if p is None: print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") continue - delta = (t - p) / p * 100 if p > 0 else 0 + if p == 0: + delta = float('inf') if t > 0 else 0.0 + else: + delta = (t - p) / p * 100 if delta >= fail: status = "🔴 Regression" has_regression = True From 746ad26a43e46d6e3d1b0ec73bcb1dd9585fb44e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 7 May 2026 10:48:10 +0700 Subject: [PATCH 386/391] Potential fix for pull request finding 'CodeQL / Artifact poisoning' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .github/workflows/npm.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 7e849f31c34fa..fadfbd5d907e2 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -3,12 +3,6 @@ name: npm permissions: {} on: - workflow_dispatch: - inputs: - run_id: - type: string - required: false - description: The run id of the release to publish workflow_run: types: [completed] workflows: [release] @@ -151,7 +145,7 @@ jobs: # Extract artifacts into an isolated temp directory, not the workspace path: ${{ steps.paths.outputs.artifact_dir }} github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + run-id: ${{ github.event.workflow_run.id }} - name: Validate Downloaded Artifacts env: From 4527b09ee5ed13d24621f5dbe42af8ac7bcd4194 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 8 May 2026 20:57:05 -0600 Subject: [PATCH 387/391] feat: improve forge build lint-failure UX --- crates/common/src/errors/mod.rs | 11 ++++- crates/forge/src/cmd/build.rs | 56 ++++++++++++++++++++--- crates/forge/tests/cli/lint.rs | 78 +++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/crates/common/src/errors/mod.rs b/crates/common/src/errors/mod.rs index f66a657aa6e9c..6a1b2bc28bf57 100644 --- a/crates/common/src/errors/mod.rs +++ b/crates/common/src/errors/mod.rs @@ -45,8 +45,15 @@ fn all_sources(err: &E) -> Vec { pub fn convert_solar_errors(dcx: &solar::interface::diagnostics::DiagCtxt) -> eyre::Result<()> { match dcx.emitted_errors() { Some(Ok(())) => Ok(()), - Some(Err(e)) if !e.is_empty() => eyre::bail!("solar run failed:\n\n{e}"), - _ if dcx.has_errors().is_err() => eyre::bail!("solar run failed"), + Some(Err(e)) if !e.is_empty() => eyre::bail!("solar reported errors:\n\n{e}"), + _ if dcx.has_errors().is_err() => { + // Non-buffer emitter: diagnostics already went to stderr; include the count. + let n = dcx.err_count(); + let plural = if n == 1 { "" } else { "s" }; + eyre::bail!( + "solar reported {n} error{plural}; see the diagnostic{plural} printed above" + ) + } _ => Ok(()), } } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 55eff1174853e..2aea7fd0830b6 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -1,6 +1,6 @@ use super::{install, watch::WatchArgs}; use clap::Parser; -use eyre::{Context, Result}; +use eyre::Result; use forge_lint::{linter::Linter, sol::SolidityLinter}; use foundry_cli::{ opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, @@ -61,6 +61,14 @@ pub struct BuildArgs { #[serde(skip)] pub ignore_eip_3860: bool, + /// Skip the post-build lint step for this invocation. + /// + /// Equivalent to setting `lint_on_build = false` under `[lint]` in foundry.toml, + /// but only for the current command. + #[arg(long, visible_alias = "skip-lint")] + #[serde(skip)] + pub no_lint: bool, + #[command(flatten)] #[serde(flatten)] pub build: BuildOpts, @@ -117,9 +125,16 @@ impl BuildArgs { } // Only run the `SolidityLinter` if lint on build and no compilation errors. - if config.lint.lint_on_build && !output.output().errors.iter().any(|e| e.is_error()) { - self.lint(&project, &config, self.paths.as_deref(), &mut output) - .wrap_err("Lint failed")?; + if !self.no_lint + && config.lint.lint_on_build + && !output.output().errors.iter().any(|e| e.is_error()) + && let Err(err) = self.lint(&project, &config, self.paths.as_deref(), &mut output) + { + emit_lint_failure_notice(); + return Err(err.wrap_err( + "post-build lint step failed; rerun with --no-lint or set \ + `lint_on_build = false` under `[lint]` in foundry.toml to bypass", + )); } Ok(output) @@ -188,8 +203,12 @@ impl BuildArgs { // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new // compiler, we should reuse the parser from the project output. + // + // Buffer emitter so parse-phase errors surface verbatim in `convert_solar_errors`. let mut compiler = solar::sema::Compiler::new( - solar::interface::Session::builder().with_stderr_emitter().build(), + solar::interface::Session::builder() + .with_buffer_emitter(Default::default()) + .build(), ); // Load the solar-compatible sources to the pcx before linting @@ -199,6 +218,18 @@ impl BuildArgs { pcx.set_resolve_imports(true); pcx.parse(); }); + + // Flush buffered parse-phase warnings; on error, `convert_solar_errors` surfaces + // them in the returned error instead, so skip to avoid duplicates. + if compiler.sess().dcx.has_errors().is_ok() + && let Some(diags) = compiler.sess().emitted_diagnostics() + { + let s = diags.to_string(); + if !s.is_empty() { + let _ = sh_eprint!("{s}"); + } + } + linter.lint(&input_files, config.deny, &mut compiler)?; } @@ -322,6 +353,21 @@ impl BuildArgs { } } +/// Notice shown on lint-on-build failure; printed separately so it survives single-line +/// cause-chain rendering. +const LINT_FAILURE_NOTICE: &str = "\ +note: post-build lint failed, but compilation succeeded. +bypass with `--no-lint` or set `lint_on_build = false` under `[lint]` in foundry.toml +docs: https://getfoundry.sh/forge/linting#disable-linting-on-build +"; + +fn emit_lint_failure_notice() { + if shell::is_json() { + return; + } + let _ = sh_eprintln!("\n{LINT_FAILURE_NOTICE}"); +} + // Make this args a `figment::Provider` so that it can be merged into the `Config` impl Provider for BuildArgs { fn metadata(&self) -> Metadata { diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index 8420e24eb3df7..dcf30e34827b5 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -837,6 +837,84 @@ Warning (2018): Function state mutability can be restricted to pure "#]]); }); +// Regression test: after switching the lint session to a buffer emitter, lint diagnostics +// produced during `forge build` must still stream to stderr (they are emitted through a +// separate emitter installed inside `SolidityLinter::lint`, not the session emitter). +forgetest!(build_emits_lint_diagnostics, |prj, cmd| { + prj.add_source("CounterAWithLints", COUNTER_A); + + prj.update_config(|config| { + config.lint.severity = vec![LintSeverity::Info]; + }); + + cmd.arg("build").assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase + [FILE]:6:20 + │ +6 │ uint256 public CounterA_Fail_Lint; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterAFailLint` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + + +"#]]); +}); + +forgetest!(build_no_lint_flag_skips_lint, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter with medium severity lints and ensure lint_on_build is enabled + // so the only thing skipping the lint step is the `--no-lint` flag. + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + cmd.args(["build", "--no-lint"]).assert_success().stderr_eq(str![[r#""#]]).stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: + | +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: + | +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"# + ]]); +}); + forgetest!(can_process_inline_config_regardless_of_input_order, |prj, cmd| { prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); From d2e6a1fd74ad3d3237fc7f1161bf71ae91fa255c Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 9 May 2026 18:59:25 +0700 Subject: [PATCH 388/391] vercel-wagmi (#535) Co-authored-by: googleworkspace-bot From 46b4eeca11ac8445ba17dc334f6b629d03a04d7e Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 9 May 2026 19:05:44 +0700 Subject: [PATCH 389/391] Master (#534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(tests): bump forge-std version (#14422) chore: bump forge-std version used for tests Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil-server): distinguish empty and notification batches (#14405) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(anvil): add debug_traceBlockByHash and debug_traceBlockByNumber (#14391) * feat(anvil): add `debug_traceBlockByHash` and `debug_traceBlockByNumber` RPC endpoints Implement the two missing geth debug_ block tracing endpoints that trace all transactions in a block at once, returning per-tx results. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: fmt --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore(foundryup): remove tempo fork support (#14324) * chore: remove tempo fork support Tempo is now fully upstream in the main Foundry repo, so the separate network handling is no longer needed. * Apply suggestion from @zerosnacks Update Foundryup version * warn when foundryup --network is ignored * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(lint): add missing visit methods to LateLintVisitor (#14276) * fix(lint): add missing visit methods to LateLintVisitor * fix(lint): dispatch deprecated nested late lint hooks * fix(lint): use by-value nested late lint hooks * test(lint): cover late visitor hooks --------- Co-authored-by: figtracer * fix: remove `network: tempo` from Tempo template (#14424) remove network: tempo from template * feat(anvil): RPC methods for tempo's `TipFeeManager` in `anvil_*` namespace (#14414) * feat(anvil): RPC methods for tempo's `TipFeeManager` in`anvil_*` namespace * feat(anvil): add tests + mint TIP20 tokens to admin before adding FeeAMM liquidity --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): separate queued txs in txpool RPC responses (#14425) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` (#14426) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` * chore: clean-ups + tests * fix: after fig's review * fix: skip serialization if `network` is `None` * chore: pin to foundry-wallets release (#14429) pin to foundry-wallets release * fix(config): respect custom Etherscan URL in cast/forge commands (#14319) * fix(config): respect custom Etherscan URL in cast/forge commands Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * fix lint * fix clippy * chore: update Cargo.lock Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 * fix: update Cargo.lock from upstream base Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * feat(common): add MPP WebSocket transport (#14404) * feat(common): add MPP WebSocket transport * docs * use alloy's wsbakcend::from_socket * use main repo * remove patches * bump rustls-webpki * fix: only use `MppWsConnect` when MPP key is available * fix: install default rustls crypto provider * fix: clean imports * fix: MPP known host check * fix: MPP only if known endpoint && key available --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(script): preserve exit reason in failed revert decode (#13985) * fix(script): preserve exit reason in failed revert decode * test(script): cover exit reason output in script failures * fix(script): initialize exit_reason in ScriptResult default --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(anvil): fix flaky test_increase_time_by_zero test (#14430) Co-authored-by: Amp * Update benches/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/test-utils/src/script.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Main (#486) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#413) * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI en… * feat(lint): add incorrect ERC20 interface lint (#14428) * feat(lint): add incorrect ERC20 interface lint * test(lint): cover direct-name IERC20 case * fix(lint): revert IncorrectERC20Interface test to use empty IERC20 base Expanding IERC20 with full function signatures caused solc compilation failures (Error 4822: Overriding function return types differ) because IERC20Incorrect inherits from IERC20 and overrides with incompatible return types. Revert to the empty-base pattern used by IncorrectERC721Interface. Amp-Thread-ID: https://ampcode.com/threads/T-019dbb72-89c7-740d-b6cf-b87a1509d3e3 Co-authored-by: Amp * test: clarify incorrect ERC20 interface fixtures * test: align ERC20 fixture with ERC721 --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(cheatcodes): reject nested debug trace recording (#14423) * fix(cheatcodes): reject nested debug trace recording * test(cheatcodes): restore RecordDebugTrace formatting * chore: bump revm v38 (#14436) * deps: bump tempo to TIP-1016 (rev 2e6e9d1) Patches revm to bluealloy/revm@a1a1824 and op-revm to foundry-rs/op-revm@66388e6 for the InitialAndFloorGas handler change. Patches reth-core to paradigmxyz/reth-core@6b12498 for the Bytecode::new_analyzed unsafe change. Adds gas_limit() and state_gas_used() to AnvilStorageProvider to satisfy the updated PrecompileStorageProvider trait. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: resolve CI failures from patched revm - Replace deprecated OpCode::new_unchecked with new_or_unknown - Add gas_refunded field to PrecompileOutput struct literals - Allow foundry-rs/op-revm and paradigmxyz/reth-core git sources in deny.toml Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: align revm/op-revm/tempo revs with tip1016 branch Updates patch revisions to match tempo/tip1016 HEAD: - revm: a1a1824 → ea8d1f5 - op-revm: 66388e6 → 780b812 - tempo: 2e6e9d1 → 4092dfe Fixes CreateInputs::new reservoir parameter for the new revm. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: pin tempo to tip1016 HEAD (807b57c) Tempo's [patch.crates-io] now includes all revm sub-crates, preventing the invariant workflow's patch propagation from clobbering foundry's patches. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * bump to later commit in https://github.com/tempoxyz/tempo/pull/2684/commits * chore: bump revm 37→38, tempo crates to tip1016 head - revm 37.0.0 → 38.0.0 (crates.io release, no more git patches) - revm-inspectors 0.38.1 → 0.39.0 - alloy-evm 0.32.0 → 0.33.1 - tempo crates rev f873f0e → c095527 (tip1016 head) - foundry-fork-db, op-revm, foundry-rs/optimism bumped via branches - fix OpCode::new_or_unknown → new_unchecked (API change in revm 38) - add missing Evm::cfg_env impl (new trait method in alloy-evm 0.33.1) Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * bump alloy-evm dep to 0.33.2 * chore: bump revm v38 using upstream OP * fix: deny.toml * fix(deps): merge conflict rustls * style: drop cmnt Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: restore cargo-shear --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Federico Gimenez Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: group dependabot updates by ecosystem (#14438) Group minor and patch version updates per ecosystem into single PRs to reduce PR noise. Major version bumps still get individual PRs for careful review. Before: up to 15 PRs/week (5 per ecosystem) After: up to 6 PRs/week (1 grouped + 1 major per ecosystem, worst case) Amp-Thread-ID: https://ampcode.com/threads/T-019dbe78-a376-7248-a549-6ddbe2c960d4 Co-authored-by: George Niculae Co-authored-by: Amp * refactor(evm): remove useless OP EVM wrapper (#14440) Co-authored-by: Copilot * chore(deps): bump the actions-weekly group with 4 updates (#14441) Bumps the actions-weekly group with 4 updates: [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request), [github/codeql-action](https://github.com/github/codeql-action), [taiki-e/install-action](https://github.com/taiki-e/install-action) and [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action). Updates `peter-evans/create-pull-request` from 8.1.0 to 8.1.1 - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/c0f553fe549906ede9cf27b5156039d195d2ece0...5f6978faf089d4d20b00c7766989d076bb2fc7f1) Updates `github/codeql-action` from 4.35.1 to 4.35.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) Updates `taiki-e/install-action` from 2.75.16 to 2.75.17 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/a2352fc6ce487f030a3aa709482d57823eadfb37...58e862542551f667fa44c8a2a4a1d64ad477c96a) Updates `taiki-e/cache-cargo-install-action` from 3.0.5 to 3.0.6 - [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases) - [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/a8b9ecf8e0c0ea09d7481cfc583a5203ecd585b5...f9eed3e4680f27610dc6d8c67be1b88593f7dade) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/install-action dependency-version: 2.75.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/cache-cargo-install-action dependency-version: 3.0.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(tests): update gas report snapshot after gas params sync fix (#14439) The deployment cost for CounterWithFallback changed from 132471 to 132459 after #14420 started properly syncing gas parameters when updating the executor spec via set_spec_id. Amp-Thread-ID: https://ampcode.com/threads/T-019dbea7-a331-7269-80a8-b935a7bdaa49 Co-authored-by: Amp * feat: log broken invariant as soon as it is found (#14433) * feat: log broken invariant as soon as it is found * fix * nest failure counts inside metrics * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0, foundry-fork-db to 0.26.0 (#14443) * chore: bump foundry-compilers to 0.20.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf04-adac-755f-b685-5fa64d792acb Co-authored-by: Amp * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: bump foundry-fork-db to 0.26.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: enable zstd feature for foundry-fork-db Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d --------- Co-authored-by: Amp * chore: remove unmaintained devcontainer (#14449) deprecate unmaintained devcontainer * chore: update security contact email (#14450) update link * feat(lint): add block-timestamp lint (#14431) * feat: add ignored_error_codes_from config option (#13841) * chore(deny): remove deprecated repos from whitelist (#14455) * Main (#493) * fix: compare sign github passkey (#132) * Create config.yml (#114) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#113) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action (#111) Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/dbda91f6efef3ee627f56175120aa9543687d830...762d7fdba79d046449732c729c1d3aaad021baa2) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump crate-ci/typos from 1.38.0 to 1.38.1 (#112) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/83157de2df0fa7c7ae20f73f9dbed44c41f2bb64...80c8a4945eec0f6d464eaf9e65ed98ef085283d1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 (#110) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.28 (#109) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.28. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/522492a8c115f1b6d4d318581f09638e9442547b...e7ef886cf8f69c25ecef6bbc2858a42e273496ec) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#115) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Foundry/master (#122) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename config.yml to ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to ci_v1.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/ci_v1.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename config.yml to ci_deploy.yml (#123) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create snyk-container.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci.yml to ci-say-hello.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.ym (#128) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory (#129) Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 1.4.1 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create cargo.yml (#74) (#130) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix typo in CircleCI config file name Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#181) CI Configuration Removal: The .circleci/cargo.yml file, which defined specific CircleCI jobs for building and testing Rust code, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#212) This pull request updates the CircleCI configuration by correcting a repository URL in a comment and fixing a job name by removing a trailing hyphen. These are good fixes. However, the job name web3-defi-game-project itself appears to be a leftover from another project and is not descriptive. I've added a comment suggesting a rename to improve maintainability. For the same reason, you might also consider renaming the workflow my-custom-workflow to something more descriptive of what it does. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-say-hello.yml (#320) bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#321) * Delete .circleci/ci-say-hello.yml bug Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#322) clean vercel block account api app next react Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#323) https://github.com/Dargon789/hardhat-project/commit/92a3e1c76ad0a29dcb545e1589d6ed3b48dd5c81 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update docker.yml * Revert "Delete .circleci/ci_deploy.yml (#322)" (#358) This reverts commit 87dd517cf50fef686c8ef39431d06b16d4744d88. * Potential fix for code scanning alert no. 132: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 154: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 172: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/evm/evm/src/executors/invariant/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cli/src/utils/suggestions.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/common/src/contracts.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Wagmi (e604566) (#413) * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI en… * Add CircleCI configuration for testing workflow (#494) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/script/src/simulate.rs (#488) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Refine construction of Vyper compiler input by cloning only the required compiler settings field instead of the entire verification context. (#455) * chore(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#14396) * chore(deps): bump actions/cache from 5.0.4 to 5.0.5 (#14397) * chore(deps): bump crate-ci/typos from 1.45.0 to 1.45.1 (#14398) * fix(cast): add browser wallet support for erc20 commands (#14395) * fix(cast): add browser wallet support for erc20 commands Add a browser wallet branch to the erc20_send! macro so that cast erc20 transfer/approve/mint/burn --browser works the same way cast send --browser does. The new path builds the transaction via the sol! IERC20 macro, applies user-provided tx params, then fills missing nonce, fees, and gas limit from the provider before handing the request to the browser wallet. Closes #13103 Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * fix(cast): default Tempo browser erc20 fee token * fix(cast): scope Tempo erc20 browser auto-detection * style(cast): format erc20 Tempo network helper --------- Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> * feat(forge): browser wallet support for `create` subcommand (#14394) * fix(deps): update rustls-webpki to fix RUSTSEC-2026-0104 (#14408) chore: update rustls-webpki to fix RUSTSEC-2026-0104 Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * fix: accept 0x-prefixed value inputs (#14406) * fix: accept 0x-prefixed value inputs * use U256::from_str for 0x-prefixed hex parsing, add 0X support and expand tests Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp --------- Co-authored-by: zerosnacks Co-authored-by: Amp * ci(npm): use OIDC trusted publishing, remove NPM_TOKEN (#14249) * chore: harden Makefile & CI (#14386) chore: harden Makefile, remove unused deps found w/ cargo shear Amp-Thread-ID: https://ampcode.com/threads/T-019daf55-1b0e-7756-8fff-4506e2e239c8 Co-authored-by: Amp * feat: use ChannelDb for channel persistence (#14355) * feat: use mpp-rs ChannelDb for channel persistence * use foundry-core * rm blank line * warn when old channels.json is found after SQLite migration * rename Channel --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat: support console.table (#14338) * feat(abi): add console.table variants to Console ABI * feat(fmt): implement console_table_format * feat(macros): extend ConsoleFmt derive for console.table * feat(inspectors): push msg per line in hardhat_log * chore: run cargo fmt * chore: add todo comment * refactor: use comfy-table for console_table_format Amp-Thread-ID: https://ampcode.com/threads/T-019db45a-2ee1-771d-8127-2052dc6df2a3 Co-authored-by: Amp * refactor(macros): detect table structs by field types instead of name Replace fragile name-based detection (`starts_with("table")`) with a structural check: table call structs have all fields of type `Vec` (Solidity arrays), while regular `log` calls never use array parameters. * refactor(macros): gate table detection on both name prefix and Vec fields Combine the name-based check (`starts_with("table")`) with the structural Vec field check so both must hold. This prevents accidental table rendering for unrelated structs that happen to have all-Vec fields. --------- Co-authored-by: zerosnacks Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump `foundry-wallets` dep (#14409) * chore: bump `foundry-wallets` dep + set `browser` & `tempo` features * chore: set features * fix(npm): add release environment for OIDC trusted publishing (#14411) Add `environment: release` to both publish-arch and publish-meta jobs to match the npm trusted publisher configuration on npmjs.com. Without the environment claim in the OIDC token, the registry rejects publishes with a misleading E404. Also refreshes bun.lock to fix frozen-lockfile failures. Amp-Thread-ID: https://ampcode.com/threads/T-019db4f4-ee94-735b-b75d-4f1bcbbb3041 Co-authored-by: Amp * fix(npm): remove deprecated `baseUrl` compiler option (#14413) Amp-Thread-ID: https://ampcode.com/threads/T-019db557-8d02-7723-bfd6-68c4107b9433 Co-authored-by: Amp * chore: bump alloy 2.0.1 (#14417) * fix(wallets): add gas limit margin to handle WebAuthn/P256 signing (#14416) fix(wallets): add gas limit margin to handle WebAuthn/P256 signing * fix(evm): sync gas params when updating executor spec (#14420) * fix(evm): sync gas params when updating executor spec * fix: make clippy happy --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add incorrect ERC721 interface lint (#14412) * fix(common): enable `foundry-wallets` browser/tempo feats (#14421) * chore(tests): bump forge-std version (#14422) chore: bump forge-std version used for tests Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil-server): distinguish empty and notification batches (#14405) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(anvil): add debug_traceBlockByHash and debug_traceBlockByNumber (#14391) * feat(anvil): add `debug_traceBlockByHash` and `debug_traceBlockByNumber` RPC endpoints Implement the two missing geth debug_ block tracing endpoints that trace all transactions in a block at once, returning per-tx results. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: fmt --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore(foundryup): remove tempo fork support (#14324) * chore: remove tempo fork support Tempo is now fully upstream in the main Foundry repo, so the separate network handling is no longer needed. * Apply suggestion from @zerosnacks Update Foundryup version * warn when foundryup --network is ignored * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Update foundryup/foundryup Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(lint): add missing visit methods to LateLintVisitor (#14276) * fix(lint): add missing visit methods to LateLintVisitor * fix(lint): dispatch deprecated nested late lint hooks * fix(lint): use by-value nested late lint hooks * test(lint): cover late visitor hooks --------- Co-authored-by: figtracer * fix: remove `network: tempo` from Tempo template (#14424) remove network: tempo from template * feat(anvil): RPC methods for tempo's `TipFeeManager` in `anvil_*` namespace (#14414) * feat(anvil): RPC methods for tempo's `TipFeeManager` in`anvil_*` namespace * feat(anvil): add tests + mint TIP20 tokens to admin before adding FeeAMM liquidity --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(anvil): separate queued txs in txpool RPC responses (#14425) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` (#14426) * refactor(cli): unify `NetworkVariant` with `NetworkConfigs` * chore: clean-ups + tests * fix: after fig's review * fix: skip serialization if `network` is `None` * chore: pin to foundry-wallets release (#14429) pin to foundry-wallets release * fix(config): respect custom Etherscan URL in cast/forge commands (#14319) * fix(config): respect custom Etherscan URL in cast/forge commands Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * fix lint * fix clippy * chore: update Cargo.lock Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 * fix: update Cargo.lock from upstream base Amp-Thread-ID: https://ampcode.com/threads/T-019db9f9-01dd-729a-9f7a-ed150aabf130 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: Gustavo Figueiredo * feat(common): add MPP WebSocket transport (#14404) * feat(common): add MPP WebSocket transport * docs * use alloy's wsbakcend::from_socket * use main repo * remove patches * bump rustls-webpki * fix: only use `MppWsConnect` when MPP key is available * fix: install default rustls crypto provider * fix: clean imports * fix: MPP known host check * fix: MPP only if known endpoint && key available --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(script): preserve exit reason in failed revert decode (#13985) * fix(script): preserve exit reason in failed revert decode * test(script): cover exit reason output in script failures * fix(script): initialize exit_reason in ScriptResult default --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(anvil): fix flaky test_increase_time_by_zero test (#14430) Co-authored-by: Amp * feat(lint): add incorrect ERC20 interface lint (#14428) * feat(lint): add incorrect ERC20 interface lint * test(lint): cover direct-name IERC20 case * fix(lint): revert IncorrectERC20Interface test to use empty IERC20 base Expanding IERC20 with full function signatures caused solc compilation failures (Error 4822: Overriding function return types differ) because IERC20Incorrect inherits from IERC20 and overrides with incompatible return types. Revert to the empty-base pattern used by IncorrectERC721Interface. Amp-Thread-ID: https://ampcode.com/threads/T-019dbb72-89c7-740d-b6cf-b87a1509d3e3 Co-authored-by: Amp * test: clarify incorrect ERC20 interface fixtures * test: align ERC20 fixture with ERC721 --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(cheatcodes): reject nested debug trace recording (#14423) * fix(cheatcodes): reject nested debug trace recording * test(cheatcodes): restore RecordDebugTrace formatting * chore: bump revm v38 (#14436) * deps: bump tempo to TIP-1016 (rev 2e6e9d1) Patches revm to bluealloy/revm@a1a1824 and op-revm to foundry-rs/op-revm@66388e6 for the InitialAndFloorGas handler change. Patches reth-core to paradigmxyz/reth-core@6b12498 for the Bytecode::new_analyzed unsafe change. Adds gas_limit() and state_gas_used() to AnvilStorageProvider to satisfy the updated PrecompileStorageProvider trait. Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: resolve CI failures from patched revm - Replace deprecated OpCode::new_unchecked with new_or_unknown - Add gas_refunded field to PrecompileOutput struct literals - Allow foundry-rs/op-revm and paradigmxyz/reth-core git sources in deny.toml Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: align revm/op-revm/tempo revs with tip1016 branch Updates patch revisions to match tempo/tip1016 HEAD: - revm: a1a1824 → ea8d1f5 - op-revm: 66388e6 → 780b812 - tempo: 2e6e9d1 → 4092dfe Fixes CreateInputs::new reservoir parameter for the new revm. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * fix: pin tempo to tip1016 HEAD (807b57c) Tempo's [patch.crates-io] now includes all revm sub-crates, preventing the invariant workflow's patch propagation from clobbering foundry's patches. Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d97c8-3033-770a-bd6c-714c2a8393d5 * bump to later commit in https://github.com/tempoxyz/tempo/pull/2684/commits * chore: bump revm 37→38, tempo crates to tip1016 head - revm 37.0.0 → 38.0.0 (crates.io release, no more git patches) - revm-inspectors 0.38.1 → 0.39.0 - alloy-evm 0.32.0 → 0.33.1 - tempo crates rev f873f0e → c095527 (tip1016 head) - foundry-fork-db, op-revm, foundry-rs/optimism bumped via branches - fix OpCode::new_or_unknown → new_unchecked (API change in revm 38) - add missing Evm::cfg_env impl (new trait method in alloy-evm 0.33.1) Co-Authored-By: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * bump alloy-evm dep to 0.33.2 * chore: bump revm v38 using upstream OP * fix: deny.toml * fix(deps): merge conflict rustls * style: drop cmnt Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix: restore cargo-shear --------- Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: zerosnacks Co-authored-by: Federico Gimenez Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: group dependabot updates by ecosystem (#14438) Group minor and patch version updates per ecosystem into single PRs to reduce PR noise. Major version bumps still get individual PRs for careful review. Before: up to 15 PRs/week (5 per ecosystem) After: up to 6 PRs/week (1 grouped + 1 major per ecosystem, worst case) Amp-Thread-ID: https://ampcode.com/threads/T-019dbe78-a376-7248-a549-6ddbe2c960d4 Co-authored-by: George Niculae Co-authored-by: Amp * refactor(evm): remove useless OP EVM wrapper (#14440) Co-authored-by: Copilot * chore(deps): bump the actions-weekly group with 4 updates (#14441) Bumps the actions-weekly group with 4 updates: [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request), [github/codeql-action](https://github.com/github/codeql-action), [taiki-e/install-action](https://github.com/taiki-e/install-action) and [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action). Updates `peter-evans/create-pull-request` from 8.1.0 to 8.1.1 - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/c0f553fe549906ede9cf27b5156039d195d2ece0...5f6978faf089d4d20b00c7766989d076bb2fc7f1) Updates `github/codeql-action` from 4.35.1 to 4.35.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) Updates `taiki-e/install-action` from 2.75.16 to 2.75.17 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/a2352fc6ce487f030a3aa709482d57823eadfb37...58e862542551f667fa44c8a2a4a1d64ad477c96a) Updates `taiki-e/cache-cargo-install-action` from 3.0.5 to 3.0.6 - [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases) - [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/a8b9ecf8e0c0ea09d7481cfc583a5203ecd585b5...f9eed3e4680f27610dc6d8c67be1b88593f7dade) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/install-action dependency-version: 2.75.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly - dependency-name: taiki-e/cache-cargo-install-action dependency-version: 3.0.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-weekly ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(tests): update gas report snapshot after gas params sync fix (#14439) The deployment cost for CounterWithFallback changed from 132471 to 132459 after #14420 started properly syncing gas parameters when updating the executor spec via set_spec_id. Amp-Thread-ID: https://ampcode.com/threads/T-019dbea7-a331-7269-80a8-b935a7bdaa49 Co-authored-by: Amp * feat: log broken invariant as soon as it is found (#14433) * feat: log broken invariant as soon as it is found * fix * nest failure counts inside metrics * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0, foundry-fork-db to 0.26.0 (#14443) * chore: bump foundry-compilers to 0.20.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf04-adac-755f-b685-5fa64d792acb Co-authored-by: Amp * chore: bump foundry-compilers to 0.20.0, foundry-block-explorers to 0.23.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: bump foundry-fork-db to 0.26.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d Co-authored-by: Amp * chore: enable zstd feature for foundry-fork-db Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019dbf01-f2de-711e-8edf-581a979f9f0d --------- Co-authored-by: Amp * chore: remove unmaintained devcontainer (#14449) deprecate unmaintained devcontainer * chore: update security contact email (#14450) update link * feat(lint): add block-timestamp lint (#14431) * feat: add ignored_error_codes_from config option (#13841) * chore(deny): remove deprecated repos from whitelist (#14455) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: figtracer Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: zerosnacks Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: MD Islam Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: mk0walsk Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Arsh Co-authored-by: Perico Perica Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Federico Gimenez Co-authored-by: George Niculae Co-authored-by: Copilot Co-authored-by: googleworkspace-bot * Update crates/anvil/server/src/handler.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create static.yml (#472) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot * chore(deps): bump rui314/setup-mold from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444 (#14442) chore(deps): bump rui314/setup-mold Bumps [rui314/setup-mold](https://github.com/rui314/setup-mold) from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444. - [Commits](https://github.com/rui314/setup-mold/compare/725a8794d15fc7563f59595bd9556495c0564878...9c9c13bf4c3f1adef0cc596abc155580bcb04444) --- updated-dependencies: - dependency-name: rui314/setup-mold dependency-version: 9c9c13bf4c3f1adef0cc596abc155580bcb04444 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update flake.lock (#14458) Co-authored-by: github-actions[bot] * fix(forge): adjust gas assertion `CounterWithFallback` (#14465) * Delete .circleci/ci_deploy.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#496) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore: update latest benchmarks (#14467) * ci: split MPP e2e into its own workflow (#14468) * ci: split MPP e2e into its own workflow Move the MPP e2e step from ci-tempo.yml into a standalone ci-mpp.yml workflow so transient HTTP 402 failures from the MPP RPC do not block the Tempo CI workflow. Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename sanity-check job to tempo-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename mpp-e2e job to mpp-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp --------- Co-authored-by: Amp * Improve GH actions (#14473) * fix(benches): add repos + extra args support to prevent blocking errors (#14470) * fix(benches): add repos + extra args support to prevent blocking errors * fix(ci): set `inputs.repos` default to empty * fix: remove `--verbose` flags * fix: exclude `uniswap/v4-core` `TickMathTestTest` * fix(forge): adjust gas assertion CounterWithFallback (foundry-rs#14465 ) (#498) * chore(deps): bump rui314/setup-mold from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444 (#14442) chore(deps): bump rui314/setup-mold Bumps [rui314/setup-mold](https://github.com/rui314/setup-mold) from 725a8794d15fc7563f59595bd9556495c0564878 to 9c9c13bf4c3f1adef0cc596abc155580bcb04444. - [Commits](https://github.com/rui314/setup-mold/compare/725a8794d15fc7563f59595bd9556495c0564878...9c9c13bf4c3f1adef0cc596abc155580bcb04444) --- updated-dependencies: - dependency-name: rui314/setup-mold dependency-version: 9c9c13bf4c3f1adef0cc596abc155580bcb04444 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update flake.lock (#14458) Co-authored-by: github-actions[bot] * fix(forge): adjust gas assertion `CounterWithFallback` (#14465) * chore: update latest benchmarks (#14467) * ci: split MPP e2e into its own workflow (#14468) * ci: split MPP e2e into its own workflow Move the MPP e2e step from ci-tempo.yml into a standalone ci-mpp.yml workflow so transient HTTP 402 failures from the MPP RPC do not block the Tempo CI workflow. Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename sanity-check job to tempo-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp * ci: rename mpp-e2e job to mpp-check Amp-Thread-ID: https://ampcode.com/threads/T-019dceb8-61e5-734f-b047-17665b4ea7d3 Co-authored-by: Amp --------- Co-authored-by: Amp * Improve GH actions (#14473) * fix(benches): add repos + extra args support to prevent blocking errors (#14470) * fix(benches): add repos + extra args support to prevent blocking errors * fix(ci): set `inputs.repos` default to empty * fix: remove `--verbose` flags * fix: exclude `uniswap/v4-core` `TickMathTestTest` --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore(cli): make `Git` impl more robust (#14452) * chore(cli): make `Git` impl more robust Co-authored-by: Copilot * fix: path --------- Co-authored-by: Copilot Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(ci): aave v4 pinned on latest (#14474) fix: aave v4 pinned on latest * ci: prefer direct-key credentials for MPP e2e (#14472) * ci: prefer direct-key MPP e2e credentials * ci: fail early when MPP wallet needs refill * ci: auto-fund MPP wallet when balance is low * mpp: include proxy request ids in HTTP errors * fix: address mpp ci fallout * fmt --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: steven * fix(evm): `<` to `<=` in `validate_uint_mutation` (#14459) * ci(tempo): run testnet on PRs (#14479) * ci(tempo): re-enable devnet steps and run testnet on PRs Amp-Thread-ID: https://ampcode.com/threads/T-019dd305-c099-7742-8682-0d22798eaac7 Co-authored-by: Amp * disable devnet again, down as part of migration * leave note --------- Co-authored-by: Amp * feat: immutable releases (#14445) * feat: immutable releases - Remove mutable tag triggers (stable, rc, rc-*) from release workflow; only v*.*.* semver tags trigger releases now - Remove mutable 'nightly' GitHub release overwrite; each nightly gets its own immutable nightly-{SHA} release only - Delete move-tag.js; stop force-moving the nightly tag to HEAD - Docker: stop tagging :nightly and :latest; use immutable :nightly-{short-sha} and :v*.*.* tags instead - foundryup: resolve 'latest' and 'nightly' channels via GitHub API to find the actual immutable release tags - foundryup: 'stable' is a silent alias for 'latest' - Simplify prune-prereleases.js filter (no mutable nightly to protect) - Bump foundryup installer version to 1.8.0 Amp-Thread-ID: https://ampcode.com/threads/T-019dbf9e-5115-77b9-a177-1af9d30af00c Co-authored-by: Amp * fix: address immutable release gaps - Docker: derive tags from inputs.tag_name directly, fixing workflow_call nightly mis-tagging - Remove release pruning entirely (immutable means permanent) - Delete prune-prereleases.js and cleanup job from release.yml - Remove force: true from create-tag.js (invalid for createRef) Amp-Thread-ID: https://ampcode.com/threads/T-019dbf9e-5115-77b9-a177-1af9d30af00c Co-authored-by: Amp * fix: use /releases/latest endpoint for stable channel resolution Avoids pagination issue where 50+ nightlies could push the latest stable release off the first page of results. Amp-Thread-ID: https://ampcode.com/threads/T-019dbf9e-5115-77b9-a177-1af9d30af00c Co-authored-by: Amp * fix: use /releases/latest for latest channel, per_page=10 for nightly Amp-Thread-ID: https://ampcode.com/threads/T-019dbf9e-5115-77b9-a177-1af9d30af00c Co-authored-by: Amp * fix: fail on unexpected tag creation errors, exclude drafts from nightly resolution Amp-Thread-ID: https://ampcode.com/threads/T-019dbf9e-5115-77b9-a177-1af9d30af00c Co-authored-by: Amp * refactor: remove local variables and temp file from resolve_latest_release Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * refactor: inline release resolution, add fetch helper, improve logging Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * fix: handle pipefail in fetch pipelines for cross-platform compatibility Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * fix: use previous stable tag as changelog fromTag for stable releases Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * fix(docker): keep mutable :latest and :nightly tags following Docker convention Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * fix: use previous nightly tag as changelog fromTag for nightly releases Amp-Thread-ID: https://ampcode.com/threads/T-019dce16-13eb-7218-a42c-ce91ed473f36 Co-authored-by: Amp * ci(release): adopt draft-first flow for GitHub immutable releases - prepare creates the GitHub release as a draft up-front (with --verify-tag) so all matrix jobs upload assets to an existing draft. - release matrix replaces softprops/action-gh-release with gh release upload --clobber against the draft. - nightly releases are auto-published by a new publish-nightly job once all assets have been uploaded; stable releases are left as drafts for a maintainer to publish manually. - prepare distinguishes existing draft (reuse) from already-published release (fail loudly), so a same-SHA nightly rerun after publication can't try to upload to an immutable release. Amp-Thread-ID: https://ampcode.com/threads/T-019dced2-6c4a-76fb-af86-79fd948a0157 Co-authored-by: Amp * remove limit for safety * fix(ci): use github context in publish-nightly job if condition env context is not available in job-level if expressions Amp-Thread-ID: https://ampcode.com/threads/T-019dd37e-7210-77da-bf0e-1d77ccecb19e Co-authored-by: Amp --------- Co-authored-by: Amp * Improve CI action (#14484) * fix(config): warn instead of erroring on unknown FOUNDRY_PROFILE (#14486) * fix(config): warn instead of erroring on unknown FOUNDRY_PROFILE When the selected profile (via `FOUNDRY_PROFILE` or otherwise) does not exist in `foundry.toml`, fall back to the default profile and emit a `Warning::UnknownProfile` instead of returning a hard error. This matches the lenient behaviour used for nested lib configs and keeps commands like `FOUNDRY_PROFILE=ci forge build` working when `[profile.ci]` happens to be missing. Amp-Thread-ID: https://ampcode.com/threads/T-019dd43e-e2d0-723e-bc03-03b0467dc68e Co-authored-by: Amp * fix fmt --------- Co-authored-by: Amp * fix(anvil): pre-check Tempo valid_after before execution (#14469) * feat(lint): add missing-zero-check (#14460) * feat(lint): add missing-zero-check * rm Staticcall, guard ordering * fix * handle guards in branches --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: bump Tempo deps + test `tempo_forkSchedule` (#14485) * chore: bump tempo and validate fork schedule Amp-Thread-ID: https://ampcode.com/threads/T-019dd36a-7d77-772b-8cba-13f2147c9e5c * test: cleanup + isolate on tempo mod * style: fmt * style: clippy * fix: clippy * Fix miner forced-tx deadlock (#14453) * chore(lint): move low-severity lints to low/ (#14500) * fix(ext): bump forge-std commit in external integration tests (#14504) bump forge commit * fix(foundryup): sort nightly releases chronologically before picking latest (#14507) * sort by datetime * bump version * feat(doc): migrate from solang to solar (#14447) * feat(doc): migrate from solang to solar Replace all solang_parser usage in forge-doc with solar AST. Use owned purpose-built structs (ParamInfo, ContractSource, FunctionSource, etc.) extracted at walk time instead of cloning arena-allocated solar AST nodes. - Add parser/source.rs with owned doc structs - Rewrite Parser to walk solar AST items directly - Parse doc comments from solar's DocComments (raw text) - Update builder to use solar AST from gcx, remove solang re-parse - Update writer and preprocessor layers for owned structs - Delete solang_ext/ module (Visitor, Visitable, AstEq, etc.) - Remove solang-parser dependency from forge-doc - Decouple chisel from forge-doc (inline CodeLocation/SafeUnwrap) Co-authored-by: Amp * fix: docs * fix: apply Steven's dedup suggestion * fix: apply fig's comment * fix: clippy --------- Co-authored-by: Amp * fix(cheatcodes): fix solc 0.8.35 error keyword warning (#14509) fix(cheatcodes): avoid solc error keyword warning * chore(tests): bump forge-std version (#14510) chore: bump forge-std version used for tests Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(ext): bump revs in external integration tests (#14506) * bump forge commit * update revs * bump date * Apply suggestion from @zerosnacks * feat(lint): detect boolean constant misuse (#14489) * feat(lint): detect boolean constant misuse * test(lint): move boolean lint coverage to ui tests * feat(lint): add could-be-immutable lint (#14495) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: bump compiler version to 0.8.35 for tests (#14524) * bump compiler version for tests * fix CI: update gas snapshots and bump svm-rs to 0.5.25 Amp-Thread-ID: https://ampcode.com/threads/T-019dddbd-7f28-72cc-965b-d5a23ecb37ce Co-authored-by: Amp * make fmt Amp-Thread-ID: https://ampcode.com/threads/T-019dddbd-7f28-72cc-965b-d5a23ecb37ce Co-authored-by: Amp * fix: add blockTimestamp to LegacyTransactionResult for testRpcTransactionByHash DRPC now returns a blockTimestamp field, which shifts ABI decoding offsets and breaks the test. Amp-Thread-ID: https://ampcode.com/threads/T-019dddbd-7f28-72cc-965b-d5a23ecb37ce Co-authored-by: Amp --------- Co-authored-by: Amp * chore: update codeowners (#14529) * feat(lint): add `unused-state-variable` (#14483) * feat(lint): add `unused-state-variable` * fix: iterate through full function + state vars --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(ci): re-enable Tempo devnet in CI, make Tempo testnet and mainnet a nightly check (#14501) * re-enable devnet * make testnet / mainnet nightly * feat(lint): add rtlo lint (#14499) * feat(lint): add rtlo lint * comments --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(forge): use network = "tempo" and add rpc_endpoints in tempo template (#14528) * feat(forge): use network = "tempo" and add rpc_endpoints in tempo template Amp-Thread-ID: https://ampcode.com/threads/T-019dddff-1855-752f-b136-bd0465462ac8 Co-authored-by: Amp * feat(forge): add eth_rpc_url = "tempo" to tempo template Amp-Thread-ID: https://ampcode.com/threads/T-019dddff-1855-752f-b136-bd0465462ac8 Co-authored-by: Amp * style: cargo fmt Amp-Thread-ID: https://ampcode.com/threads/T-019dddff-1855-752f-b136-bd0465462ac8 Co-authored-by: Amp * fix: drop eth_rpc_url from tempo template (would silently fork all tests) Amp-Thread-ID: https://ampcode.com/threads/T-019dddff-1855-752f-b136-bd0465462ac8 Co-authored-by: Amp * refactor: build tempo template foundry.toml in a single toml serialize Amp-Thread-ID: https://ampcode.com/threads/T-019dddff-1855-752f-b136-bd0465462ac8 Co-authored-by: Amp --------- Co-authored-by: Amp * ci(tempo): also run devnet checks on push to master (#14537) Devnet checks currently only run on `pull_request` (and via scheduled nightly / workflow_dispatch). Run them on `push` to master as well so that regressions which slip through (e.g. due to flaky CI being re-run-merged) are caught at merge time. Amp-Thread-ID: https://ampcode.com/threads/T-019ddec8-2f04-7255-b8bf-70c1ce2a996d Co-authored-by: Amp * fix(config): suppress spurious unknown-key warning for `network` (#14534) * fix(config): suppress spurious unknown-key warning for `network` The `network` field in `NetworkConfigs` had `skip_serializing_if = "Option::is_none"`, so it was missing from the serialized default `Config`. The warnings provider builds its allowed-keys set from that serialized default, so a valid `network = "tempo"` entry in foundry.toml triggered: Warning: Found unknown `network` config for profile `default` defined in foundry.toml. Replace `skip_serializing_if` with `#[serde(default)]` so `network` appears (as null) in the default config and is recognized as a known profile key. TOML output is unchanged (None is omitted by toml-rs); only the JSON snapshot in `test_default_config` needed updating. Amp-Thread-ID: https://ampcode.com/threads/T-019ddec8-2f04-7255-b8bf-70c1ce2a996d Co-authored-by: Amp * test(config): add regression tests for network/tempo unknown-key warnings Cover both the new `network = "tempo"` form and the legacy `tempo = true` alias to ensure neither triggers UnknownKey warnings. Amp-Thread-ID: https://ampcode.com/threads/T-019ddec8-2f04-7255-b8bf-70c1ce2a996d Co-authored-by: Amp * fix(config): canonicalize network field on serialization Previously, with `network = "tempo"` set, `forge config` would emit both: network = "tempo" tempo = false which is misleading — the legacy boolean alias contradicts the resolved network. Conversely, with `tempo = true` (legacy), the output omitted `network` entirely, hiding the actual resolved network. Make `NetworkConfigs` use a custom `Serialize` impl that: - Always emits the *resolved* network as the canonical `network = "..."` field. - Never emits the legacy `tempo` / `optimism` boolean aliases (they are still accepted as deserialize-only aliases for backward compatibility). - Continues to emit `celo` and `bypass_prevrandao` as before. This ensures consistent output regardless of input form: network = "tempo" (canonical) → network = "tempo" tempo = true (legacy) → network = "tempo" optimism = true (legacy) → network = "optimism" Both legacy keys are added to `BACKWARD_COMPATIBLE_KEYS` in the warnings provider so they don't trigger unknown-key warnings on input. Test snapshots updated accordingly. Amp-Thread-ID: https://ampcode.com/threads/T-019ddec8-2f04-7255-b8bf-70c1ce2a996d Co-authored-by: Amp * style: cargo fmt Amp-Thread-ID: https://ampcode.com/threads/T-019ddec8-2f04-7255-b8bf-70c1ce2a996d Co-authored-by: Amp --------- Co-authored-by: Amp * test(fmt): cover erc7201 layout formatting (#14519) * test(fmt): cover erc7201 layout formatting Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019ddcf6-6c0d-75bb-a172-e3f7df6d8c2d * test(fmt): cover over-limit erc7201 layout formatting Amp-Thread-ID: https://ampcode.com/threads/T-019ddcf6-6c0d-75bb-a172-e3f7df6d8c2d Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: Amp * optimization(forge): avoid unnecessary gas_snapshots clones in test runner (#13969) refactor(forge): avoid unnecessary gas_snapshots clones in test runner Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore(deps): bump the cargo-weekly group with 5 updates (#507) Bumps the cargo-weekly group with 5 updates: | Package | From | To | | --- | --- | --- | | [mimalloc](https://github.com/purpleprotocol/mimalloc_rust) | `0.1.48` | `0.1.50` | | [rustls](https://github.com/rustls/rustls) | `0.23.38` | `0.23.39` | | [jiff](https://github.com/BurntSushi/jiff) | `0.2.23` | `0.2.24` | | [idna_adapter](https://github.com/hsivonen/idna_adapter) | `1.1.0` | `1.2.1` | | [interprocess](https://github.com/kotauskas/interprocess) | `2.4.0` | `2.4.2` | Updates `mimalloc` from 0.1.48 to 0.1.50 - [Release notes](https://github.com/purpleprotocol/mimalloc_rust/releases) - [Commits](https://github.com/purpleprotocol/mimalloc_rust/compare/v0.1.48...v0.1.50) Updates `rustls` from 0.23.38 to 0.23.39 - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.38...v/0.23.39) Updates `jiff` from 0.2.23 to 0.2.24 - [Release notes](https://github.com/BurntSushi/jiff/releases) - [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.23...jiff-static-0.2.24) Updates `idna_adapter` from 1.1.0 to 1.2.1 - [Commits](https://github.com/hsivonen/idna_adapter/compare/v1.1.0...v1.2.1) Updates `interprocess` from 2.4.0 to 2.4.2 - [Release notes](https://github.com/kotauskas/interprocess/releases) - [Commits](https://github.com/kotauskas/interprocess/compare/2.4.0...2.4.2) --- updated-dependencies: - dependency-name: mimalloc dependency-version: 0.1.50 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: rustls dependency-version: 0.23.39 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: jiff dependency-version: 0.2.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly - dependency-name: idna_adapter dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-weekly - dependency-name: interprocess dependency-version: 2.4.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-weekly ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump strum from 0.27.2 to 0.28.0 (#509) Bumps [strum](https://github.com/Peternator7/strum) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/Peternator7/strum/releases) - [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md) - [Commits](https://github.com/Peternator7/strum/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: strum dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * gas-snapshot * chore(deps): bump similar-asserts from 1.7.0 to 2.0.0 (#508) Bumps [similar-asserts](https://github.com/mitsuhiko/similar-asserts) from 1.7.0 to 2.0.0. - [Changelog](https://github.com/mitsuhiko/similar-asserts/blob/main/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/similar-asserts/compare/1.7.0...2.0.0) --- updated-dependencies: - dependency-name: similar-asserts dependency-version: 2.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * anvil: unify Tempo nonce markers across send RPCs (#14536) Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz * fix(forge): `flaky_gas_report_fallback_with_calldata` deployment cost (#14545) * chore(lint): add missing lints to README (#14551) * chore(bench): update `benchmark.sh` (#14548) Co-authored-by: Matthias Seitz * chore(clippy): fix for_kv_map and useless_borrows_in_formatting (#14554) * chore(clippy): fix for_kv_map and useless_borrows_in_formatting Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp * chore(clippy): drop redundant borrows in cheatcodes assert formatters Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp --------- Co-authored-by: Amp * fix(ci): use `PATH_USD` fallback fee token in Mail templates (#14546) * chore(deps): bump the actions-weekly group with 3 updates (#14497) * refactor(chisel): migrate to solar (#14532) * feat(lint): add too-many-digits lint (#14549) * feat: feature-gate optimism deps in common-fmt, common, cast (#14539) * feat(forge): support per-test network selection via inline config (#14530) * feat(cli): `--tempo.expires` retry-safe mode (TIP-1009 expiring nonces) (#14521) * fix(forge): `per_test_network_routing` match undeterministic order (#14557) output * chore(ci): run tempo mainnet and testnet checks before devnet (#14556) * Update flake.lock (#14553) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/f374034' (2026-04-25) → 'github:nix-community/fenix/74c1591' (2026-05-02) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/8954b66' (2026-04-21) → 'github:rust-lang/rust-analyzer/64cdaeb' (2026-05-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/01fbdee' (2026-04-23) → 'github:NixOS/nixpkgs/c6d6588' (2026-05-01) Co-authored-by: github-actions[bot] * chore(bench): update benchmark results (#14552) * fix(forge): ignore ETH_RPC_URL for test forking (#14555) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(cast): add Tempo keychain policy commands (#14531) * feat(cast): add tempo keychain policy commands * fix(cast): address keychain policy review * fix(cli): fix jsonwebtoken panic (#14562) `cast` panicked with this message coming from jsonwebtoken: ``` Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. See the documentation of the CryptoProvider type for more information. ``` This seemingly was introduced with the bump of jsonwebtoken to 10. Now it requires you to pick one backend used by default controlled by the compile time cargo features or call `CryptoProvider::install_default()` at the beginning. I realized that probably it would be better to just select the feature and I picked `aws_lc_rs` as it seems to be increasingly a default and we already are using the C toolchain. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(cli): tidy ETH_RPC_URL handling and add forge regression test (#14559) Follow-up to #14555: - Drop the redundant flashbots branch in RpcOpts::dict; self.url(None) already returns FLASHBOTS_URL when --flashbots is set, so the subsequent overwrite was dead code. - Inline the resolve_rpc_url helper back into RpcCommonOpts::url; it was only called from one place and added unneeded surface area. - Restore the doc comment on RpcCommonOpts and document why ETH_RPC_URL is intentionally not a clap env on the shared field (so EvmArgs cannot inherit it). - Add an integration test that runs forge config with ETH_RPC_URL set in the environment and asserts that eth_rpc_url stays None, directly exercising the regression scenario from #14538. Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: zerosnacks Co-authored-by: Amp * feat(cast): open Tempo wallet fund flow for MPP failures (#14505) * feat(cast): open Tempo wallet fund flow for MPP failures * ci(tempo): skip network checks without rpc secrets * Revert "ci(tempo): skip network checks without rpc secrets" This reverts commit f8dd70163f850b854888fd1c962174e1663284f4. * fix(common): address mpp funding review --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * ci: sign release archives, docker images, and publish SBOMs (#14563) - release.yml: emit per-archive sha256 + SPDX SBOM (Syft), cosign keyless sign-blob of the archive, and use actions/attest@v4 for both build provenance and SBOM attestations. Upload all artifacts to the draft release. - docker-publish.yml: enable BuildKit SBOM, capture the build digest, cosign keyless sign each pushed tag, and publish a Sigstore-signed SLSA provenance attestation via actions/attest with push-to-registry. - SECURITY.md: document how external users verify archives and the docker image (gh attestation, cosign, plain sha256, buildx imagetools). - README.md: link to the new verification section. * perf(common): short-circuit `find_by_name_or_identifier` instead of `collect` (#14514) * feat(foundryup): retry GitHub API fetches on transient errors (#14566) GitHub api.github.com occasionally returns transient 403s on certain VMs (per-IP rate limiting / WAF hiccups), causing foundryup to fail to resolve the latest stable / nightly release tag, e.g.: foundryup: fetching latest nightly releases from foundry-rs/foundry... Error: curl: (56) The requested URL returned error: 403 foundryup: failed to fetch releases from GitHub API Add curl/wget retry logic to the `fetch` helper (used exclusively for GitHub API releases endpoints): - curl: --retry 5 --retry-delay 2 --retry-max-time 60, plus --retry-all-errors when supported (curl 7.71+, feature-detected so older curl does not hard-fail). --retry-all-errors is required to retry HTTP 403, which is not in curl's default retryable set. - wget fallback: --tries=5 --waitretry=2 --retry-on-http-error=403,408,429,5xx. `fetch` now buffers to a temp file before emitting to stdout, since curl's --retry-all-errors is unsafe with piped consumers (mid-stream retries can duplicate bytes). Existing callers pipe into awk/grep. Tunable via FOUNDRYUP_MAX_RETRIES (default 5). `download` (binary tarballs, attestations, manpages) is intentionally left unchanged — those rarely fail and changing them affects the attestation existence check semantics. Bumps installer version 1.8.1 -> 1.8.2. Amp-Thread-ID: https://ampcode.com/threads/T-019df2f5-9b97-717a-b959-cf7cbc7ca3bb Co-authored-by: Amp * feat(lint): project-wide passes + pragma-inconsistent (#14543) * feat(lint): project-wide passes + pragma-inconsistent * rm hashset, msg * test(lint): exhaustive pragma-inconsistent coverage + clearer testdata names (#14561) * test(lint): exhaustive coverage for pragma-inconsistent Follow-up to #14543 expanding test coverage for the cross-file `pragma-inconsistent` lint across the syntax variants users encounter in real Solidity projects. Multi-file scenarios (added as `forgetest!` cases in `crates/forge/tests/cli/lint.rs`, since they cannot be expressed in a single `.sol` testdata file): - Negative (must NOT warn): - all files use the same exact pragma (`0.8.20`) - all files use the same caret pragma (`^0.8.20`) - single file in the project - Positive (must warn): - duplicates among a conflict -- two identical files plus one different pragma still emits three warnings - Mixed: - file without an explicit pragma uses the test-utils default (`add_raw_source` is used to bypass the auto-injected pragma) Source bodies are pulled out into module-level `const` raw strings so rustfmt does not collapse the inline `\n`-escaped strings into wide horizontal blobs. Single-file scenarios (added as `.sol` files under `crates/lint/testdata/` in the existing `//~NOTE:` annotation style): - `PragmaInconsistentCaretVsTilde.sol`: `^0.8.20` vs `~0.8.20` - `PragmaInconsistentRangeVsExact.sol`: `>=0.8.0 <0.9.0` vs `0.8.20` -- range satisfies exact but lint is intentionally string-based, matching SLITHER-W1078 - `PragmaInconsistentOrVsExact.sol`: `0.8.20 || 0.8.21` vs `0.8.20` - `PragmaInconsistentThreeDistinct.sol`: `>=0.8.0`, `^0.8.0`, `~0.8.0` -- verifies the `others` list contains every other variant * test(lint): rename pragma-inconsistent testdata to describe the case under test The two testdata files added in #14543 were named `PragmaInconsistent.sol` and `PragmaInconsistent2.sol`, which made them look like duplicates. They actually exercise distinct edge cases of the same string-based detection: - `PragmaInconsistentCaretAboveExact.sol` (was `PragmaInconsistent.sol`): caret range whose lower bound is strictly below the exact version (`^0.8.0` + `0.8.18`). - `PragmaInconsistentCaretMatchesExact.sol` (was `PragmaInconsistent2.sol`): caret range whose lower bound equals the exact version (`^0.8.20` + `0.8.20`) -- the looks-the-same-but-still-distinct case that guards SLITHER-W1078 parity (no semver intersection). Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: Amp --------- Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * refactor(script): reuse shared Tempo CLI opts (#14558) * deps: bump tempo to 6bf9903 (T6 hardfork) + fix alloy-evm 0.34 compat (#14567) * deps: bump tempo to 6bf9903 (T6 hardfork) Bumps tempo crates to 6bf9903d, adding the T6 hardfork variant to TempoHardfork. Without this, cast's tempo_forkSchedule lookup parses the chain's reported active fork ("T6") into TempoHardfork::FromStr, fails because T6 was unknown to the enum, and silently returns is_hardfork_active(T3) = false. That made 'cast keychain auth' fall back to the legacy authorizeKey selector and revert with LegacyAuthorizeKeySelectorChanged on any T6 chain. Also bumps alloy-evm to 0.34 and the optimism git pin to develop (e3b59e7) so alloy-op-evm picks up an EvmFactory impl built against alloy-evm 0.34. Removes the now-unused paradigmxyz/reth-core [patch] entries. No source changes; lockfile churn is transitive only. * fix: adapt AnvilBlockExecutor to alloy-evm 0.34.0 breaking changes - Add Send + 'static bounds to TxResult impl for AnvilTxResult - Change commit_transaction return type from Result to GasOutput - Remove .expect() on commit_transaction call site Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 * style: rustfmt commit_transaction signature Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 --------- Co-authored-by: Centaur AI * docs: add forge lint rule docs (#14571) * feat(forge): add fuzz run selection (#14522) * feat(forge): add fuzz run selection * fix(fuzz): make metadata builder const * test(fuzz): cover generated seed replay * fix(forge): persist fuzz worker for run replay * fix(evm): satisfy clippy in fuzz replay * fix(fuzz): reuse fuzz run metadata * forge(lint/docs): validate deployed forge lint docs (#14573) test: validate deployed forge lint docs * feat: gate foundry-primitives behind optimism feature (#14572) * fix(ci): increase permissions for the enhanced attestation writing (#14584) * increase permissions for artifact writing * apply write permissions to release-docker * feat(hardforks, networks): gate optimism behind cargo feature (#14581) * fix(forge): encode Tempo creates as AA calls (#14585) * feat(anvil): gate optimism behind cargo feature (#14577) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): introduce `vaddr` cmd for TIP-1022 (#14508) * feat(cast): introduce `vaddr` cmd for tip-1022 * fix: doc * chore: touch-ups * add tests * chore: move tests to tempo ci * feat: add vaddr watch test * feat: count 0 hadling, add `no_register` flag * fix: remove sweep subcommand * fix: make clippy happy * feat(bench): nightly regression tracking workflow (#14586) * fix(cli): fix release version strings for immutable tags, bump to 1.7.1 (#14496) * Fix release version metadata for immutable tags Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Derive nightly release suffix from commit SHA Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * bump to v1.7.1 * avoid appending whole sha hash, not necessary, handle version cmp correctly. after v1.7.1 release we need to bump to v1.7.2 for nightlies following it to compare correctly * Make foundryVersionCmp tolerate new version format and add tests - Strip both pre-release ('-nightly', '-dev') and build metadata ('+..') from SEMVER_VERSION before comparison so the cheatcode keeps working for tagged releases (which have no '-' separator). - Extract strip_semver_metadata helper and add Rust unit tests covering all SEMVER_VERSION shapes, version_cmp ordering, and parse_version rejection of pre-release/build/garbage input. - Extend the Solidity test suite for vm.getFoundryVersion()/foundryVersionCmp/foundryVersionAtLeast: validate MAJOR.MINOR.PATCH parseability, build profile value, cmp/atLeast invariant, and error paths for invalid user-supplied versions. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * fix(test): drop view from solidity tests using assert helpers and fix fmt - assertTrue/assertEq aren't view, so testGetFoundryVersionBuildProfile and testFoundryVersionCmpAndAtLeastAreConsistent can't be view either. - Collapse the buildType assertion onto one line to satisfy forge fmt. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * test(version): assert build profile is non-empty instead of debug|release The dist profile (used for distributed release binaries) is also valid; just require non-empty so any future profile works. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * Normalize nightly- to nightly in release_version Ensures tarball and Docker nightly artifacts produce the same version string. The commit identifier is already included in the SemVer build metadata (after `+`), so collapsing `nightly-` to `nightly` avoids duplicating the SHA in the pre-release tag. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019df79e-d4c9-707c-85eb-2efbf59160b3 --------- Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` (#14007) * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` * test(evm): cover `ForkDbStateSnapshot::storage_ref` snapshot lookup * fix(cast): consistent `--json` output for `keychain` subcommands (#14590) - `keychain rl`: wrap remaining limit in `{"remaining":"..."}` object instead of emitting a bare JSON string - `keychain policy add-call`: emit `{"status":"already_present","target":"..."}` when the rule already exists, instead of plain text - `send_keychain_tx`: wrap sponsor hash in `{"sponsor_hash":"0x..."}` object when --tempo.print-sponsor-hash is used with --json Add CLI tests covering the rl and sponsor-hash JSON output shapes. * feat(tempo): add sponsored transaction plumbing (#14560) * feat(tempo): add sponsored transaction plumbing * addressing mablr comments * fix tempo sponsor signer future layout * preserve json output for tempo sponsor preview * fix(cast): `--json` output support for `vaddr` (#14591) * feat(tempo): add named nonce lanes (#14527) * fix(cheatcodes): transfer value for payable mock calls (#14547) * test: updated tests * fix: execute value transfer * test: improve * imp: review item * test: vm.prank test * imp: moved mocked-call handling after prank application --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add inline-assembly lint (#14575) * feat(lint): add inline-assembly lint * lint(inline-assembly): also recognize `/// @solidity memory-safe-assembly` NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-019df4b6-1b76-734c-9a9b-29db9fb7d461 Co-authored-by: Amp --------- Co-authored-by: Amp * refactor(script): remove `ScriptConfig::{fee_token,expires_at}` in favour of `TempoOpts` (#14594) * feat(evm-core): gate optimism behind cargo feature (#14593) * fix(cli): resolve Tempo expires once (#14595) fix(cli): resolve tempo expires once * feat(cli): gate optimism behind cargo feature (#14596) * fix(anvil): classify EVM halts as transaction rejections (#14592) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat: drop optimism deps under no-default-features (#14600) * fix(forge): `--fuzz-seed` parameter is not effective in `forge coverage` (#14610) fix --fuzz-seed not effective in forge coverage * fix(foundryup): mirror tag resolution for install & use (#14611) * fix(foundryup): mirror tag resolution for install & use * fix(foundryup): mirror semver version normalization in `use` `install` auto-prepends `v` to bare semver versions (e.g. `1.7.0` -> `v1.7.0`) so the on-disk directory is always `v`-prefixed. `use` was doing a literal lookup, so `foundryup -u 1.7.0` failed even though `foundryup -i 1.7.0` had succeeded. Broaden the channel `case` in `use()` to also match bare semver inputs (`MAJOR.MINOR.PATCH[-prerelease]`) so they go through the same `resolve_version_and_tag` normalizer. The pattern is intentionally tighter than `install`'s `[[:digit:]]*` so locally-built versions whose names happen to start with a digit are still looked up literally. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp * chore(foundryup): clarify tag-resolution log and error messages Distinguish the GitHub API tag-resolution phase from the actual binary download by consistently referring to "release tag(s)" in the `resolve_version_and_tag` helper's `say` and `err` messages. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(ci): keep no-default builds free of op deps (#14612) * feat: cast unauthorized flow → wallet.tempo access-key authorization (#14517) * feat: cast unauthorized flow → wallet.tempo access-key authorization Amp-Thread-ID: https://ampcode.com/threads/T-019df174-9538-713b-b8c9-5001b1ad4719 Co-authored-by: Amp * fmt * feat(cast): replace TEMPO_NO_BROWSER env with flag * revert token addresses --------- Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * docs(expect-emit): clarify next-call semantics and warn about caught-revert leak (#14620) docs(cheatcodes): clarify expectEmit next-call semantics and caught-revert leak expectEmit is a 'next call' assertion. If the call immediately after expectEmit reverts and the revert is swallowed by the caller (low-level call or try/catch), the unmatched expectation can leak forward and be satisfied by a later unrelated emission, silently turning a broken test green. Document the constraint on the natspec for both no-arg and topic-checking overloads, and regenerate cheatcodes.json. Refs: https://github.com/foundry-rs/foundry/issues/14618 Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames (#14615) * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames The reverter address argument to `vm.expectRevert` was silently ignored when the innermost reverting frame was a CREATE (top-level or nested), because create_end never populated `expected_revert.reverted_by`. Mirror call_end's logic in create_end: when the outcome reverts and a reverter address is expected, record outcome.address (revm guarantees this is Some(would-be address) whenever the constructor executed). Adds positive regression tests for top-level and nested-CREATE reverts, and a negative regression test asserting wrong-reverter now fails. Co-authored-by: Amp * improve coverage * add Derek's suggested test cases * fix: forge fmt for ExpectRevert.t.sol Amp-Thread-ID: https://ampcode.com/threads/T-019dfdc5-5414-70b6-9f49-cb5797a37a29 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): keep plain Tempo broadcasts non-AA (#14616) * fix(script): don't force Tempo AA fee_token from --network tempo alone Plain --network tempo (or any selection that just sets the network to Tempo) does not by itself imply a Tempo AA / type 0x76 transaction. Defaulting tempo.common.fee_token to PATH_USD_ADDRESS solely from evm_opts.networks.is_tempo() caused every unsigned broadcast tx to flow through TempoOpts::apply, which set fee_token on the request and promoted it to the Tempo AA tx envelope. Signers that only know how to sign ordinary Ethereum transactions (e.g. the Ledger Ethereum app) then rejected the transaction with 'received an unexpected empty response'. Gate the default on an actual Tempo AA opt-in: - --batch (Tempo batch txs are themselves AA and need a fee token), or - any explicit --tempo.* flag (sponsor, expiring nonce, nonce key/lane, ...) which already forces an AA tx and benefits from a default fee token. Explicit --tempo.fee-token continues to win over the default in all cases, and non-Tempo networks never default the fee token. Add unit tests for each scenario. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't force eth_estimateGas on plain Tempo broadcasts Plain --network tempo produces an ordinary EIP-1559/legacy transaction (see tempo-alloy::TempoTransactionRequest::output_tx_type), so the local simulation gas estimate is sufficient. Forcing RPC re-estimation in this case can surface node-side errors such as 'gas required exceeds allowance (0)' (Geth-style balance/gasPrice cap from eth_estimateGas) on flows that previously worked, including Ledger-signed broadcasts that just got unblocked from the type 0x76 regression. Match tempo-foundry's behaviour: only force eth_estimateGas on Tempo when the user has actually opted into Tempo AA semantics (--batch or any explicit --tempo.* flag). Extract the gating into needs_tempo_aa_rpc_estimate(...) and add focused unit tests mirroring the fee-token gating tests. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't re-estimate plain Tempo chain broadcasts --------- Co-authored-by: Amp * fix(cheatcodes): preserve reverts with `expectEmit` (#14619) * test: added regression test * fix: re-order revert handling * refactor: simplify * lint: fmt * polish: tighten comment, extend test with revert reason and custom error Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * feat(lint): add tx-origin detector (#14589) * feat(lint): add tx-origin detector * test(lint): address tx-origin review feedback * fix: ui bless * fix(lint): cover tx-origin index and ternary predicates * test(lint): bless tx-origin snapshot --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * refactor(tempo): prepare batch access key txs w/ helper (#14597) fix(tempo): prepare batch access key txs before estimation * fix(anvil): respect non-zero genesis block in Otterscan APIs (#14490) fix(anvil): respect non-zero genesis block in Otterscan APIs The three Otterscan address-history endpoints (`ots_searchTransactionsBefore`/`After`, `ots_getTransactionBySenderAndNonce`) hardcoded `unwrap_or(1)` / `unwrap_or_default()` as the lower bound of their block scan, which breaks when `genesis_block_number` is non-zero (e.g. `genesis.json` `number: 73`). Expose `Backend::genesis_number()` and fall back to `genesis_number() + 1` in non-fork mode, mirroring the existing post-fork `f.block_number() + 1` convention. * ci: sign release archives, docker images, and publish SBOMs (#520) * anvil: unify Tempo nonce markers across send RPCs (#14536) Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz * fix(forge): `flaky_gas_report_fallback_with_calldata` deployment cost (#14545) * chore(lint): add missing lints to README (#14551) * chore(bench): update `benchmark.sh` (#14548) Co-authored-by: Matthias Seitz * chore(clippy): fix for_kv_map and useless_borrows_in_formatting (#14554) * chore(clippy): fix for_kv_map and useless_borrows_in_formatting Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp * chore(clippy): drop redundant borrows in cheatcodes assert formatters Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp --------- Co-authored-by: Amp * fix(ci): use `PATH_USD` fallback fee token in Mail templates (#14546) * chore(deps): bump the actions-weekly group with 3 updates (#14497) * refactor(chisel): migrate to solar (#14532) * feat(lint): add too-many-digits lint (#14549) * feat: feature-gate optimism deps in common-fmt, common, cast (#14539) * feat(forge): support per-test network selection via inline config (#14530) * feat(cli): `--tempo.expires` retry-safe mode (TIP-1009 expiring nonces) (#14521) * fix(forge): `per_test_network_routing` match undeterministic order (#14557) output * chore(ci): run tempo mainnet and testnet checks before devnet (#14556) * Update flake.lock (#14553) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/f374034' (2026-04-25) → 'github:nix-community/fenix/74c1591' (2026-05-02) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/8954b66' (2026-04-21) → 'github:rust-lang/rust-analyzer/64cdaeb' (2026-05-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/01fbdee' (2026-04-23) → 'github:NixOS/nixpkgs/c6d6588' (2026-05-01) Co-authored-by: github-actions[bot] * chore(bench): update benchmark results (#14552) * fix(forge): ignore ETH_RPC_URL for test forking (#14555) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(cast): add Tempo keychain policy commands (#14531) * feat(cast): add tempo keychain policy commands * fix(cast): address keychain policy review * fix(cli): fix jsonwebtoken panic (#14562) `cast` panicked with this message coming from jsonwebtoken: ``` Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. See the documentation of the CryptoProvider type for more information. ``` This seemingly was introduced with the bump of jsonwebtoken to 10. Now it requires you to pick one backend used by default controlled by the compile time cargo features or call `CryptoProvider::install_default()` at the beginning. I realized that probably it would be better to just select the feature and I picked `aws_lc_rs` as it seems to be increasingly a default and we already are using the C toolchain. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(cli): tidy ETH_RPC_URL handling and add forge regression test (#14559) Follow-up to #14555: - Drop the redundant flashbots branch in RpcOpts::dict; self.url(None) already returns FLASHBOTS_URL when --flashbots is set, so the subsequent overwrite was dead code. - Inline the resolve_rpc_url helper back into RpcCommonOpts::url; it was only called from one place and added unneeded surface area. - Restore the doc comment on RpcCommonOpts and document why ETH_RPC_URL is intentionally not a clap env on the shared field (so EvmArgs cannot inherit it). - Add an integration test that runs forge config with ETH_RPC_URL set in the environment and asserts that eth_rpc_url stays None, directly exercising the regression scenario from #14538. Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: zerosnacks Co-authored-by: Amp * feat(cast): open Tempo wallet fund flow for MPP failures (#14505) * feat(cast): open Tempo wallet fund flow for MPP failures * ci(tempo): skip network checks without rpc secrets * Revert "ci(tempo): skip network checks without rpc secrets" This reverts commit f8dd70163f850b854888fd1c962174e1663284f4. * fix(common): address mpp funding review --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * ci: sign release archives, docker images, and publish SBOMs (#14563) - release.yml: emit per-archive sha256 + SPDX SBOM (Syft), cosign keyless sign-blob of the archive, and use actions/attest@v4 for both build provenance and SBOM attestations. Upload all artifacts to the draft release. - docker-publish.yml: enable BuildKit SBOM, capture the build digest, cosign keyless sign each pushed tag, and publish a Sigstore-signed SLSA provenance attestation via actions/attest with push-to-registry. - SECURITY.md: document how external users verify archives and the docker image (gh attestation, cosign, plain sha256, buildx imagetools). - README.md: link to the new verification section. * perf(common): short-circuit `find_by_name_or_identifier` instead of `collect` (#14514) * feat(foundryup): retry GitHub API fetches on transient errors (#14566) GitHub api.github.com occasionally returns transient 403s on certain VMs (per-IP rate limiting / WAF hiccups), causing foundryup to fail to resolve the latest stable / nightly release tag, e.g.: foundryup: fetching latest nightly releases from foundry-rs/foundry... Error: curl: (56) The requested URL returned error: 403 foundryup: failed to fetch releases from GitHub API Add curl/wget retry logic to the `fetch` helper (used exclusively for GitHub API releases endpoints): - curl: --retry 5 --retry-delay 2 --retry-max-time 60, plus --retry-all-errors when supported (curl 7.71+, feature-detected so older curl does not hard-fail). --retry-all-errors is required to retry HTTP 403, which is not in curl's default retryable set. - wget fallback: --tries=5 --waitretry=2 --retry-on-http-error=403,408,429,5xx. `fetch` now buffers to a temp file before emitting to stdout, since curl's --retry-all-errors is unsafe with piped consumers (mid-stream retries can duplicate bytes). Existing callers pipe into awk/grep. Tunable via FOUNDRYUP_MAX_RETRIES (default 5). `download` (binary tarballs, attestations, manpages) is intentionally left unchanged — those rarely fail and changing them affects the attestation existence check semantics. Bumps installer version 1.8.1 -> 1.8.2. Amp-Thread-ID: https://ampcode.com/threads/T-019df2f5-9b97-717a-b959-cf7cbc7ca3bb Co-authored-by: Amp * feat(lint): project-wide passes + pragma-inconsistent (#14543) * feat(lint): project-wide passes + pragma-inconsistent * rm hashset, msg * test(lint): exhaustive pragma-inconsistent coverage + clearer testdata names (#14561) * test(lint): exhaustive coverage for pragma-inconsistent Follow-up to #14543 expanding test coverage for the cross-file `pragma-inconsistent` lint across the syntax variants users encounter in real Solidity projects. Multi-file scenarios (added as `forgetest!` cases in `crates/forge/tests/cli/lint.rs`, since they cannot be expressed in a single `.sol` testdata file): - Negative (must NOT warn): - all files use the same exact pragma (`0.8.20`) - all files use the same caret pragma (`^0.8.20`) - single file in the project - Positive (must warn): - duplicates among a conflict -- two identical files plus one different pragma still emits three warnings - Mixed: - file without an explicit pragma uses the test-utils default (`add_raw_source` is used to bypass the auto-injected pragma) Source bodies are pulled out into module-level `const` raw strings so rustfmt does not collapse the inline `\n`-escaped strings into wide horizontal blobs. Single-file scenarios (added as `.sol` files under `crates/lint/testdata/` in the existing `//~NOTE:` annotation style): - `PragmaInconsistentCaretVsTilde.sol`: `^0.8.20` vs `~0.8.20` - `PragmaInconsistentRangeVsExact.sol`: `>=0.8.0 <0.9.0` vs `0.8.20` -- range satisfies exact but lint is intentionally string-based, matching SLITHER-W1078 - `PragmaInconsistentOrVsExact.sol`: `0.8.20 || 0.8.21` vs `0.8.20` - `PragmaInconsistentThreeDistinct.sol`: `>=0.8.0`, `^0.8.0`, `~0.8.0` -- verifies the `others` list contains every other variant * test(lint): rename pragma-inconsistent testdata to describe the case under test The two testdata files added in #14543 were named `PragmaInconsistent.sol` and `PragmaInconsistent2.sol`, which made them look like duplicates. They actually exercise distinct edge cases of the same string-based detection: - `PragmaInconsistentCaretAboveExact.sol` (was `PragmaInconsistent.sol`): caret range whose lower bound is strictly below the exact version (`^0.8.0` + `0.8.18`). - `PragmaInconsistentCaretMatchesExact.sol` (was `PragmaInconsistent2.sol`): caret range whose lower bound equals the exact version (`^0.8.20` + `0.8.20`) -- the looks-the-same-but-still-distinct case that guards SLITHER-W1078 parity (no semver intersection). Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: Amp --------- Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * refactor(script): reuse shared Tempo CLI opts (#14558) * deps: bump tempo to 6bf9903 (T6 hardfork) + fix alloy-evm 0.34 compat (#14567) * deps: bump tempo to 6bf9903 (T6 hardfork) Bumps tempo crates to 6bf9903d, adding the T6 hardfork variant to TempoHardfork. Without this, cast's tempo_forkSchedule lookup parses the chain's reported active fork ("T6") into TempoHardfork::FromStr, fails because T6 was unknown to the enum, and silently returns is_hardfork_active(T3) = false. That made 'cast keychain auth' fall back to the legacy authorizeKey selector and revert with LegacyAuthorizeKeySelectorChanged on any T6 chain. Also bumps alloy-evm to 0.34 and the optimism git pin to develop (e3b59e7) so alloy-op-evm picks up an EvmFactory impl built against alloy-evm 0.34. Removes the now-unused paradigmxyz/reth-core [patch] entries. No source changes; lockfile churn is transitive only. * fix: adapt AnvilBlockExecutor to alloy-evm 0.34.0 breaking changes - Add Send + 'static bounds to TxResult impl for AnvilTxResult - Change commit_transaction return type from Result to GasOutput - Remove .expect() on commit_transaction call site Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 * style: rustfmt commit_transaction signature Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 --------- Co-authored-by: Centaur AI * docs: add forge lint rule docs (#14571) * feat(forge): add fuzz run selection (#14522) * feat(forge): add fuzz run selection * fix(fuzz): make metadata builder const * test(fuzz): cover generated seed replay * fix(forge): persist fuzz worker for run replay * fix(evm): satisfy clippy in fuzz replay * fix(fuzz): reuse fuzz run metadata * forge(lint/docs): validate deployed forge lint docs (#14573) test: validate deployed forge lint docs * feat: gate foundry-primitives behind optimism feature (#14572) * fix(ci): increase permissions for the enhanced attestation writing (#14584) * increase permissions for artifact writing * apply write permissions to release-docker * feat(hardforks, networks): gate optimism behind cargo feature (#14581) * fix(forge): encode Tempo creates as AA calls (#14585) * feat(anvil): gate optimism behind cargo feature (#14577) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): introduce `vaddr` cmd for TIP-1022 (#14508) * feat(cast): introduce `vaddr` cmd for tip-1022 * fix: doc * chore: touch-ups * add tests * chore: move tests to tempo ci * feat: add vaddr watch test * feat: count 0 hadling, add `no_register` flag * fix: remove sweep subcommand * fix: make clippy happy * feat(bench): nightly regression tracking workflow (#14586) * fix(cli): fix release version strings for immutable tags, bump to 1.7.1 (#14496) * Fix release version metadata for immutable tags Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Derive nightly release suffix from commit SHA Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * bump to v1.7.1 * avoid appending whole sha hash, not necessary, handle version cmp correctly. after v1.7.1 release we need to bump to v1.7.2 for nightlies following it to compare correctly * Make foundryVersionCmp tolerate new version format and add tests - Strip both pre-release ('-nightly', '-dev') and build metadata ('+..') from SEMVER_VERSION before comparison so the cheatcode keeps working for tagged releases (which have no '-' separator). - Extract strip_semver_metadata helper and add Rust unit tests covering all SEMVER_VERSION shapes, version_cmp ordering, and parse_version rejection of pre-release/build/garbage input. - Extend the Solidity test suite for vm.getFoundryVersion()/foundryVersionCmp/foundryVersionAtLeast: validate MAJOR.MINOR.PATCH parseability, build profile value, cmp/atLeast invariant, and error paths for invalid user-supplied versions. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * fix(test): drop view from solidity tests using assert helpers and fix fmt - assertTrue/assertEq aren't view, so testGetFoundryVersionBuildProfile and testFoundryVersionCmpAndAtLeastAreConsistent can't be view either. - Collapse the buildType assertion onto one line to satisfy forge fmt. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * test(version): assert build profile is non-empty instead of debug|release The dist profile (used for distributed release binaries) is also valid; just require non-empty so any future profile works. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * Normalize nightly- to nightly in release_version Ensures tarball and Docker nightly artifacts produce the same version string. The commit identifier is already included in the SemVer build metadata (after `+`), so collapsing `nightly-` to `nightly` avoids duplicating the SHA in the pre-release tag. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019df79e-d4c9-707c-85eb-2efbf59160b3 --------- Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` (#14007) * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` * test(evm): cover `ForkDbStateSnapshot::storage_ref` snapshot lookup * fix(cast): consistent `--json` output for `keychain` subcommands (#14590) - `keychain rl`: wrap remaining limit in `{"remaining":"..."}` object instead of emitting a bare JSON string - `keychain policy add-call`: emit `{"status":"already_present","target":"..."}` when the rule already exists, instead of plain text - `send_keychain_tx`: wrap sponsor hash in `{"sponsor_hash":"0x..."}` object when --tempo.print-sponsor-hash is used with --json Add CLI tests covering the rl and sponsor-hash JSON output shapes. * feat(tempo): add sponsored transaction plumbing (#14560) * feat(tempo): add sponsored transaction plumbing * addressing mablr comments * fix tempo sponsor signer future layout * preserve json output for tempo sponsor preview * fix(cast): `--json` output support for `vaddr` (#14591) * feat(tempo): add named nonce lanes (#14527) * fix(cheatcodes): transfer value for payable mock calls (#14547) * test: updated tests * fix: execute value transfer * test: improve * imp: review item * test: vm.prank test * imp: moved mocked-call handling after prank application --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add inline-assembly lint (#14575) * feat(lint): add inline-assembly lint * lint(inline-assembly): also recognize `/// @solidity memory-safe-assembly` NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-019df4b6-1b76-734c-9a9b-29db9fb7d461 Co-authored-by: Amp --------- Co-authored-by: Amp * refactor(script): remove `ScriptConfig::{fee_token,expires_at}` in favour of `TempoOpts` (#14594) * feat(evm-core): gate optimism behind cargo feature (#14593) * fix(cli): resolve Tempo expires once (#14595) fix(cli): resolve tempo expires once * feat(cli): gate optimism behind cargo feature (#14596) * fix(anvil): classify EVM halts as transaction rejections (#14592) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat: drop optimism deps under no-default-features (#14600) * fix(forge): `--fuzz-seed` parameter is not effective in `forge coverage` (#14610) fix --fuzz-seed not effective in forge coverage * fix(foundryup): mirror tag resolution for install & use (#14611) * fix(foundryup): mirror tag resolution for install & use * fix(foundryup): mirror semver version normalization in `use` `install` auto-prepends `v` to bare semver versions (e.g. `1.7.0` -> `v1.7.0`) so the on-disk directory is always `v`-prefixed. `use` was doing a literal lookup, so `foundryup -u 1.7.0` failed even though `foundryup -i 1.7.0` had succeeded. Broaden the channel `case` in `use()` to also match bare semver inputs (`MAJOR.MINOR.PATCH[-prerelease]`) so they go through the same `resolve_version_and_tag` normalizer. The pattern is intentionally tighter than `install`'s `[[:digit:]]*` so locally-built versions whose names happen to start with a digit are still looked up literally. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp * chore(foundryup): clarify tag-resolution log and error messages Distinguish the GitHub API tag-resolution phase from the actual binary download by consistently referring to "release tag(s)" in the `resolve_version_and_tag` helper's `say` and `err` messages. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(ci): keep no-default builds free of op deps (#14612) * feat: cast unauthorized flow → wallet.tempo access-key authorization (#14517) * feat: cast unauthorized flow → wallet.tempo access-key authorization Amp-Thread-ID: https://ampcode.com/threads/T-019df174-9538-713b-b8c9-5001b1ad4719 Co-authored-by: Amp * fmt * feat(cast): replace TEMPO_NO_BROWSER env with flag * revert token addresses --------- Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * docs(expect-emit): clarify next-call semantics and warn about caught-revert leak (#14620) docs(cheatcodes): clarify expectEmit next-call semantics and caught-revert leak expectEmit is a 'next call' assertion. If the call immediately after expectEmit reverts and the revert is swallowed by the caller (low-level call or try/catch), the unmatched expectation can leak forward and be satisfied by a later unrelated emission, silently turning a broken test green. Document the constraint on the natspec for both no-arg and topic-checking overloads, and regenerate cheatcodes.json. Refs: https://github.com/foundry-rs/foundry/issues/14618 Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames (#14615) * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames The reverter address argument to `vm.expectRevert` was silently ignored when the innermost reverting frame was a CREATE (top-level or nested), because create_end never populated `expected_revert.reverted_by`. Mirror call_end's logic in create_end: when the outcome reverts and a reverter address is expected, record outcome.address (revm guarantees this is Some(would-be address) whenever the constructor executed). Adds positive regression tests for top-level and nested-CREATE reverts, and a negative regression test asserting wrong-reverter now fails. Co-authored-by: Amp * improve coverage * add Derek's suggested test cases * fix: forge fmt for ExpectRevert.t.sol Amp-Thread-ID: https://ampcode.com/threads/T-019dfdc5-5414-70b6-9f49-cb5797a37a29 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): keep plain Tempo broadcasts non-AA (#14616) * fix(script): don't force Tempo AA fee_token from --network tempo alone Plain --network tempo (or any selection that just sets the network to Tempo) does not by itself imply a Tempo AA / type 0x76 transaction. Defaulting tempo.common.fee_token to PATH_USD_ADDRESS solely from evm_opts.networks.is_tempo() caused every unsigned broadcast tx to flow through TempoOpts::apply, which set fee_token on the request and promoted it to the Tempo AA tx envelope. Signers that only know how to sign ordinary Ethereum transactions (e.g. the Ledger Ethereum app) then rejected the transaction with 'received an unexpected empty response'. Gate the default on an actual Tempo AA opt-in: - --batch (Tempo batch txs are themselves AA and need a fee token), or - any explicit --tempo.* flag (sponsor, expiring nonce, nonce key/lane, ...) which already forces an AA tx and benefits from a default fee token. Explicit --tempo.fee-token continues to win over the default in all cases, and non-Tempo networks never default the fee token. Add unit tests for each scenario. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't force eth_estimateGas on plain Tempo broadcasts Plain --network tempo produces an ordinary EIP-1559/legacy transaction (see tempo-alloy::TempoTransactionRequest::output_tx_type), so the local simulation gas estimate is sufficient. Forcing RPC re-estimation in this case can surface node-side errors such as 'gas required exceeds allowance (0)' (Geth-style balance/gasPrice cap from eth_estimateGas) on flows that previously worked, including Ledger-signed broadcasts that just got unblocked from the type 0x76 regression. Match tempo-foundry's behaviour: only force eth_estimateGas on Tempo when the user has actually opted into Tempo AA semantics (--batch or any explicit --tempo.* flag). Extract the gating into needs_tempo_aa_rpc_estimate(...) and add focused unit tests mirroring the fee-token gating tests. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't re-estimate plain Tempo chain broadcasts --------- Co-authored-by: Amp * fix(cheatcodes): preserve reverts with `expectEmit` (#14619) * test: added regression test * fix: re-order revert handling * refactor: simplify * lint: fmt * polish: tighten comment, extend test with revert reason and custom error Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * feat(lint): add tx-origin detector (#14589) * feat(lint): add tx-origin detector * test(lint): address tx-origin review feedback * fix: ui bless * fix(lint): cover tx-origin index and ternary predicates * test(lint): bless tx-origin snapshot --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * refactor(tempo): prepare batch access key txs w/ helper (#14597) fix(tempo): prepare batch access key txs before estimation * fix(anvil): respect non-zero genesis block in Otterscan APIs (#14490) fix(anvil): respect non-zero genesis block in Otterscan APIs The three Otterscan address-history endpoints (`ots_searchTransactionsBefore`/`After`, `ots_getTransactionBySenderAndNonce`) hardcoded `unwrap_or(1)` / `unwrap_or_default()` as the lower bound of their block scan, which breaks when `genesis_block_number` is non-zero (e.g. `genesis.json` `number: 73`). Expose `Backend::genesis_number()` and fall back to `genesis_number() + 1` in non-fork mode, mirroring the existing post-fork `f.block_number() + 1` convention. --------- Co-authored-by: Isagi Yates Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: figtracer Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Sergei Shulepov Co-authored-by: zerosnacks Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cui Co-authored-by: Centaur AI Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> Co-authored-by: Mikhail Mikheev <16622558+mmv08@users.noreply.github.com> Co-authored-by: lazymio Co-authored-by: Emma Jamieson-Hoare Co-authored-by: VIkions <99107287+vikions@users.noreply.github.com> Co-authored-by: Aïssata * ci: sign release archives, docker images, and publish SBOMs (#519) * anvil: unify Tempo nonce markers across send RPCs (#14536) Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz * fix(forge): `flaky_gas_report_fallback_with_calldata` deployment cost (#14545) * chore(lint): add missing lints to README (#14551) * chore(bench): update `benchmark.sh` (#14548) Co-authored-by: Matthias Seitz * chore(clippy): fix for_kv_map and useless_borrows_in_formatting (#14554) * chore(clippy): fix for_kv_map and useless_borrows_in_formatting Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp * chore(clippy): drop redundant borrows in cheatcodes assert formatters Amp-Thread-ID: https://ampcode.com/threads/T-019df0f9-62e7-74b8-bd5e-da2acce678fb Co-authored-by: Amp --------- Co-authored-by: Amp * fix(ci): use `PATH_USD` fallback fee token in Mail templates (#14546) * chore(deps): bump the actions-weekly group with 3 updates (#14497) * refactor(chisel): migrate to solar (#14532) * feat(lint): add too-many-digits lint (#14549) * feat: feature-gate optimism deps in common-fmt, common, cast (#14539) * feat(forge): support per-test network selection via inline config (#14530) * feat(cli): `--tempo.expires` retry-safe mode (TIP-1009 expiring nonces) (#14521) * fix(forge): `per_test_network_routing` match undeterministic order (#14557) output * chore(ci): run tempo mainnet and testnet checks before devnet (#14556) * Update flake.lock (#14553) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/f374034' (2026-04-25) → 'github:nix-community/fenix/74c1591' (2026-05-02) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/8954b66' (2026-04-21) → 'github:rust-lang/rust-analyzer/64cdaeb' (2026-05-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/01fbdee' (2026-04-23) → 'github:NixOS/nixpkgs/c6d6588' (2026-05-01) Co-authored-by: github-actions[bot] * chore(bench): update benchmark results (#14552) * fix(forge): ignore ETH_RPC_URL for test forking (#14555) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(cast): add Tempo keychain policy commands (#14531) * feat(cast): add tempo keychain policy commands * fix(cast): address keychain policy review * fix(cli): fix jsonwebtoken panic (#14562) `cast` panicked with this message coming from jsonwebtoken: ``` Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'rust_crypto' and 'aws_lc_rs' features is enabled. See the documentation of the CryptoProvider type for more information. ``` This seemingly was introduced with the bump of jsonwebtoken to 10. Now it requires you to pick one backend used by default controlled by the compile time cargo features or call `CryptoProvider::install_default()` at the beginning. I realized that probably it would be better to just select the feature and I picked `aws_lc_rs` as it seems to be increasingly a default and we already are using the C toolchain. Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(cli): tidy ETH_RPC_URL handling and add forge regression test (#14559) Follow-up to #14555: - Drop the redundant flashbots branch in RpcOpts::dict; self.url(None) already returns FLASHBOTS_URL when --flashbots is set, so the subsequent overwrite was dead code. - Inline the resolve_rpc_url helper back into RpcCommonOpts::url; it was only called from one place and added unneeded surface area. - Restore the doc comment on RpcCommonOpts and document why ETH_RPC_URL is intentionally not a clap env on the shared field (so EvmArgs cannot inherit it). - Add an integration test that runs forge config with ETH_RPC_URL set in the environment and asserts that eth_rpc_url stays None, directly exercising the regression scenario from #14538. Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: zerosnacks Co-authored-by: Amp * feat(cast): open Tempo wallet fund flow for MPP failures (#14505) * feat(cast): open Tempo wallet fund flow for MPP failures * ci(tempo): skip network checks without rpc secrets * Revert "ci(tempo): skip network checks without rpc secrets" This reverts commit f8dd70163f850b854888fd1c962174e1663284f4. * fix(common): address mpp funding review --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * ci: sign release archives, docker images, and publish SBOMs (#14563) - release.yml: emit per-archive sha256 + SPDX SBOM (Syft), cosign keyless sign-blob of the archive, and use actions/attest@v4 for both build provenance and SBOM attestations. Upload all artifacts to the draft release. - docker-publish.yml: enable BuildKit SBOM, capture the build digest, cosign keyless sign each pushed tag, and publish a Sigstore-signed SLSA provenance attestation via actions/attest with push-to-registry. - SECURITY.md: document how external users verify archives and the docker image (gh attestation, cosign, plain sha256, buildx imagetools). - README.md: link to the new verification section. * perf(common): short-circuit `find_by_name_or_identifier` instead of `collect` (#14514) * feat(foundryup): retry GitHub API fetches on transient errors (#14566) GitHub api.github.com occasionally returns transient 403s on certain VMs (per-IP rate limiting / WAF hiccups), causing foundryup to fail to resolve the latest stable / nightly release tag, e.g.: foundryup: fetching latest nightly releases from foundry-rs/foundry... Error: curl: (56) The requested URL returned error: 403 foundryup: failed to fetch releases from GitHub API Add curl/wget retry logic to the `fetch` helper (used exclusively for GitHub API releases endpoints): - curl: --retry 5 --retry-delay 2 --retry-max-time 60, plus --retry-all-errors when supported (curl 7.71+, feature-detected so older curl does not hard-fail). --retry-all-errors is required to retry HTTP 403, which is not in curl's default retryable set. - wget fallback: --tries=5 --waitretry=2 --retry-on-http-error=403,408,429,5xx. `fetch` now buffers to a temp file before emitting to stdout, since curl's --retry-all-errors is unsafe with piped consumers (mid-stream retries can duplicate bytes). Existing callers pipe into awk/grep. Tunable via FOUNDRYUP_MAX_RETRIES (default 5). `download` (binary tarballs, attestations, manpages) is intentionally left unchanged — those rarely fail and changing them affects the attestation existence check semantics. Bumps installer version 1.8.1 -> 1.8.2. Amp-Thread-ID: https://ampcode.com/threads/T-019df2f5-9b97-717a-b959-cf7cbc7ca3bb Co-authored-by: Amp * feat(lint): project-wide passes + pragma-inconsistent (#14543) * feat(lint): project-wide passes + pragma-inconsistent * rm hashset, msg * test(lint): exhaustive pragma-inconsistent coverage + clearer testdata names (#14561) * test(lint): exhaustive coverage for pragma-inconsistent Follow-up to #14543 expanding test coverage for the cross-file `pragma-inconsistent` lint across the syntax variants users encounter in real Solidity projects. Multi-file scenarios (added as `forgetest!` cases in `crates/forge/tests/cli/lint.rs`, since they cannot be expressed in a single `.sol` testdata file): - Negative (must NOT warn): - all files use the same exact pragma (`0.8.20`) - all files use the same caret pragma (`^0.8.20`) - single file in the project - Positive (must warn): - duplicates among a conflict -- two identical files plus one different pragma still emits three warnings - Mixed: - file without an explicit pragma uses the test-utils default (`add_raw_source` is used to bypass the auto-injected pragma) Source bodies are pulled out into module-level `const` raw strings so rustfmt does not collapse the inline `\n`-escaped strings into wide horizontal blobs. Single-file scenarios (added as `.sol` files under `crates/lint/testdata/` in the existing `//~NOTE:` annotation style): - `PragmaInconsistentCaretVsTilde.sol`: `^0.8.20` vs `~0.8.20` - `PragmaInconsistentRangeVsExact.sol`: `>=0.8.0 <0.9.0` vs `0.8.20` -- range satisfies exact but lint is intentionally string-based, matching SLITHER-W1078 - `PragmaInconsistentOrVsExact.sol`: `0.8.20 || 0.8.21` vs `0.8.20` - `PragmaInconsistentThreeDistinct.sol`: `>=0.8.0`, `^0.8.0`, `~0.8.0` -- verifies the `others` list contains every other variant * test(lint): rename pragma-inconsistent testdata to describe the case under test The two testdata files added in #14543 were named `PragmaInconsistent.sol` and `PragmaInconsistent2.sol`, which made them look like duplicates. They actually exercise distinct edge cases of the same string-based detection: - `PragmaInconsistentCaretAboveExact.sol` (was `PragmaInconsistent.sol`): caret range whose lower bound is strictly below the exact version (`^0.8.0` + `0.8.18`). - `PragmaInconsistentCaretMatchesExact.sol` (was `PragmaInconsistent2.sol`): caret range whose lower bound equals the exact version (`^0.8.20` + `0.8.20`) -- the looks-the-same-but-still-distinct case that guards SLITHER-W1078 parity (no semver intersection). Amp-Thread-ID: https://ampcode.com/threads/T-019df243-267f-7779-93e1-5d6686082444 Co-authored-by: Amp --------- Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * refactor(script): reuse shared Tempo CLI opts (#14558) * deps: bump tempo to 6bf9903 (T6 hardfork) + fix alloy-evm 0.34 compat (#14567) * deps: bump tempo to 6bf9903 (T6 hardfork) Bumps tempo crates to 6bf9903d, adding the T6 hardfork variant to TempoHardfork. Without this, cast's tempo_forkSchedule lookup parses the chain's reported active fork ("T6") into TempoHardfork::FromStr, fails because T6 was unknown to the enum, and silently returns is_hardfork_active(T3) = false. That made 'cast keychain auth' fall back to the legacy authorizeKey selector and revert with LegacyAuthorizeKeySelectorChanged on any T6 chain. Also bumps alloy-evm to 0.34 and the optimism git pin to develop (e3b59e7) so alloy-op-evm picks up an EvmFactory impl built against alloy-evm 0.34. Removes the now-unused paradigmxyz/reth-core [patch] entries. No source changes; lockfile churn is transitive only. * fix: adapt AnvilBlockExecutor to alloy-evm 0.34.0 breaking changes - Add Send + 'static bounds to TxResult impl for AnvilTxResult - Change commit_transaction return type from Result to GasOutput - Remove .expect() on commit_transaction call site Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 * style: rustfmt commit_transaction signature Amp-Thread-ID: https://ampcode.com/threads/T-019df322-c0f1-73e7-858c-5ca2d242ddb4 --------- Co-authored-by: Centaur AI * docs: add forge lint rule docs (#14571) * feat(forge): add fuzz run selection (#14522) * feat(forge): add fuzz run selection * fix(fuzz): make metadata builder const * test(fuzz): cover generated seed replay * fix(forge): persist fuzz worker for run replay * fix(evm): satisfy clippy in fuzz replay * fix(fuzz): reuse fuzz run metadata * forge(lint/docs): validate deployed forge lint docs (#14573) test: validate deployed forge lint docs * feat: gate foundry-primitives behind optimism feature (#14572) * fix(ci): increase permissions for the enhanced attestation writing (#14584) * increase permissions for artifact writing * apply write permissions to release-docker * feat(hardforks, networks): gate optimism behind cargo feature (#14581) * fix(forge): encode Tempo creates as AA calls (#14585) * feat(anvil): gate optimism behind cargo feature (#14577) Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * feat(cast): introduce `vaddr` cmd for TIP-1022 (#14508) * feat(cast): introduce `vaddr` cmd for tip-1022 * fix: doc * chore: touch-ups * add tests * chore: move tests to tempo ci * feat: add vaddr watch test * feat: count 0 hadling, add `no_register` flag * fix: remove sweep subcommand * fix: make clippy happy * feat(bench): nightly regression tracking workflow (#14586) * fix(cli): fix release version strings for immutable tags, bump to 1.7.1 (#14496) * Fix release version metadata for immutable tags Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Derive nightly release suffix from commit SHA Amp-Thread-ID: https://ampcode.com/threads/T-019dd617-b29f-7409-8523-9858a1504f17 Co-authored-by: Amp * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * Apply suggestion from @zerosnacks * bump to v1.7.1 * avoid appending whole sha hash, not necessary, handle version cmp correctly. after v1.7.1 release we need to bump to v1.7.2 for nightlies following it to compare correctly * Make foundryVersionCmp tolerate new version format and add tests - Strip both pre-release ('-nightly', '-dev') and build metadata ('+..') from SEMVER_VERSION before comparison so the cheatcode keeps working for tagged releases (which have no '-' separator). - Extract strip_semver_metadata helper and add Rust unit tests covering all SEMVER_VERSION shapes, version_cmp ordering, and parse_version rejection of pre-release/build/garbage input. - Extend the Solidity test suite for vm.getFoundryVersion()/foundryVersionCmp/foundryVersionAtLeast: validate MAJOR.MINOR.PATCH parseability, build profile value, cmp/atLeast invariant, and error paths for invalid user-supplied versions. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * fix(test): drop view from solidity tests using assert helpers and fix fmt - assertTrue/assertEq aren't view, so testGetFoundryVersionBuildProfile and testFoundryVersionCmpAndAtLeastAreConsistent can't be view either. - Collapse the buildType assertion onto one line to satisfy forge fmt. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * test(version): assert build profile is non-empty instead of debug|release The dist profile (used for distributed release binaries) is also valid; just require non-empty so any future profile works. Amp-Thread-ID: https://ampcode.com/threads/T-019dd971-fcb7-7149-9680-f0134130844c Co-authored-by: Amp * Normalize nightly- to nightly in release_version Ensures tarball and Docker nightly artifacts produce the same version string. The commit identifier is already included in the SemVer build metadata (after `+`), so collapsing `nightly-` to `nightly` avoids duplicating the SHA in the pre-release tag. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019df79e-d4c9-707c-85eb-2efbf59160b3 --------- Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: zerosnacks * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` (#14007) * fix(evm): query `state_snapshot.storage` in `ForkDbStateSnapshot::storage_ref` * test(evm): cover `ForkDbStateSnapshot::storage_ref` snapshot lookup * fix(cast): consistent `--json` output for `keychain` subcommands (#14590) - `keychain rl`: wrap remaining limit in `{"remaining":"..."}` object instead of emitting a bare JSON string - `keychain policy add-call`: emit `{"status":"already_present","target":"..."}` when the rule already exists, instead of plain text - `send_keychain_tx`: wrap sponsor hash in `{"sponsor_hash":"0x..."}` object when --tempo.print-sponsor-hash is used with --json Add CLI tests covering the rl and sponsor-hash JSON output shapes. * feat(tempo): add sponsored transaction plumbing (#14560) * feat(tempo): add sponsored transaction plumbing * addressing mablr comments * fix tempo sponsor signer future layout * preserve json output for tempo sponsor preview * fix(cast): `--json` output support for `vaddr` (#14591) * feat(tempo): add named nonce lanes (#14527) * fix(cheatcodes): transfer value for payable mock calls (#14547) * test: updated tests * fix: execute value transfer * test: improve * imp: review item * test: vm.prank test * imp: moved mocked-call handling after prank application --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat(lint): add inline-assembly lint (#14575) * feat(lint): add inline-assembly lint * lint(inline-assembly): also recognize `/// @solidity memory-safe-assembly` NatSpec Amp-Thread-ID: https://ampcode.com/threads/T-019df4b6-1b76-734c-9a9b-29db9fb7d461 Co-authored-by: Amp --------- Co-authored-by: Amp * refactor(script): remove `ScriptConfig::{fee_token,expires_at}` in favour of `TempoOpts` (#14594) * feat(evm-core): gate optimism behind cargo feature (#14593) * fix(cli): resolve Tempo expires once (#14595) fix(cli): resolve tempo expires once * feat(cli): gate optimism behind cargo feature (#14596) * fix(anvil): classify EVM halts as transaction rejections (#14592) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * feat: drop optimism deps under no-default-features (#14600) * fix(forge): `--fuzz-seed` parameter is not effective in `forge coverage` (#14610) fix --fuzz-seed not effective in forge coverage * fix(foundryup): mirror tag resolution for install & use (#14611) * fix(foundryup): mirror tag resolution for install & use * fix(foundryup): mirror semver version normalization in `use` `install` auto-prepends `v` to bare semver versions (e.g. `1.7.0` -> `v1.7.0`) so the on-disk directory is always `v`-prefixed. `use` was doing a literal lookup, so `foundryup -u 1.7.0` failed even though `foundryup -i 1.7.0` had succeeded. Broaden the channel `case` in `use()` to also match bare semver inputs (`MAJOR.MINOR.PATCH[-prerelease]`) so they go through the same `resolve_version_and_tag` normalizer. The pattern is intentionally tighter than `install`'s `[[:digit:]]*` so locally-built versions whose names happen to start with a digit are still looked up literally. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp * chore(foundryup): clarify tag-resolution log and error messages Distinguish the GitHub API tag-resolution phase from the actual binary download by consistently referring to "release tag(s)" in the `resolve_version_and_tag` helper's `say` and `err` messages. Amp-Thread-ID: https://ampcode.com/threads/T-019dfc78-8557-712b-9944-bbff9a4a3b76 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * fix(ci): keep no-default builds free of op deps (#14612) * feat: cast unauthorized flow → wallet.tempo access-key authorization (#14517) * feat: cast unauthorized flow → wallet.tempo access-key authorization Amp-Thread-ID: https://ampcode.com/threads/T-019df174-9538-713b-b8c9-5001b1ad4719 Co-authored-by: Amp * fmt * feat(cast): replace TEMPO_NO_BROWSER env with flag * revert token addresses --------- Co-authored-by: Amp Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * docs(expect-emit): clarify next-call semantics and warn about caught-revert leak (#14620) docs(cheatcodes): clarify expectEmit next-call semantics and caught-revert leak expectEmit is a 'next call' assertion. If the call immediately after expectEmit reverts and the revert is swallowed by the caller (low-level call or try/catch), the unmatched expectation can leak forward and be satisfied by a later unrelated emission, silently turning a broken test green. Document the constraint on the natspec for both no-arg and topic-checking overloads, and regenerate cheatcodes.json. Refs: https://github.com/foundry-rs/foundry/issues/14618 Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames (#14615) * fix(cheatcodes): enforce `expectRevert` reverter address for CREATE frames The reverter address argument to `vm.expectRevert` was silently ignored when the innermost reverting frame was a CREATE (top-level or nested), because create_end never populated `expected_revert.reverted_by`. Mirror call_end's logic in create_end: when the outcome reverts and a reverter address is expected, record outcome.address (revm guarantees this is Some(would-be address) whenever the constructor executed). Adds positive regression tests for top-level and nested-CREATE reverts, and a negative regression test asserting wrong-reverter now fails. Co-authored-by: Amp * improve coverage * add Derek's suggested test cases * fix: forge fmt for ExpectRevert.t.sol Amp-Thread-ID: https://ampcode.com/threads/T-019dfdc5-5414-70b6-9f49-cb5797a37a29 Co-authored-by: Amp --------- Co-authored-by: Amp Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(script): keep plain Tempo broadcasts non-AA (#14616) * fix(script): don't force Tempo AA fee_token from --network tempo alone Plain --network tempo (or any selection that just sets the network to Tempo) does not by itself imply a Tempo AA / type 0x76 transaction. Defaulting tempo.common.fee_token to PATH_USD_ADDRESS solely from evm_opts.networks.is_tempo() caused every unsigned broadcast tx to flow through TempoOpts::apply, which set fee_token on the request and promoted it to the Tempo AA tx envelope. Signers that only know how to sign ordinary Ethereum transactions (e.g. the Ledger Ethereum app) then rejected the transaction with 'received an unexpected empty response'. Gate the default on an actual Tempo AA opt-in: - --batch (Tempo batch txs are themselves AA and need a fee token), or - any explicit --tempo.* flag (sponsor, expiring nonce, nonce key/lane, ...) which already forces an AA tx and benefits from a default fee token. Explicit --tempo.fee-token continues to win over the default in all cases, and non-Tempo networks never default the fee token. Add unit tests for each scenario. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't force eth_estimateGas on plain Tempo broadcasts Plain --network tempo produces an ordinary EIP-1559/legacy transaction (see tempo-alloy::TempoTransactionRequest::output_tx_type), so the local simulation gas estimate is sufficient. Forcing RPC re-estimation in this case can surface node-side errors such as 'gas required exceeds allowance (0)' (Geth-style balance/gasPrice cap from eth_estimateGas) on flows that previously worked, including Ledger-signed broadcasts that just got unblocked from the type 0x76 regression. Match tempo-foundry's behaviour: only force eth_estimateGas on Tempo when the user has actually opted into Tempo AA semantics (--batch or any explicit --tempo.* flag). Extract the gating into needs_tempo_aa_rpc_estimate(...) and add focused unit tests mirroring the fee-token gating tests. Amp-Thread-ID: https://ampcode.com/threads/T-019dfd37-2354-712f-95b1-2584fd47ad5e Co-authored-by: Amp * fix(script): don't re-estimate plain Tempo chain broadcasts --------- Co-authored-by: Amp * fix(cheatcodes): preserve reverts with `expectEmit` (#14619) * test: added regression test * fix: re-order revert handling * refactor: simplify * lint: fmt * polish: tighten comment, extend test with revert reason and custom error Amp-Thread-ID: https://ampcode.com/threads/T-019dfd96-7a03-7249-8c10-af20ee2729f5 Co-authored-by: Amp --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Amp * feat(lint): add tx-origin detector (#14589) * feat(lint): add tx-origin detector * test(lint): address tx-origin review feedback * fix: ui bless * fix(lint): cover tx-origin index and ternary predicates * test(lint): bless tx-origin snapshot --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * refactor(tempo): prepare batch access key txs w/ helper (#14597) fix(tempo): prepare batch access key txs before estimation * fix(anvil): respect non-zero genesis block in Otterscan APIs (#14490) fix(anvil): respect non-zero genesis block in Otterscan APIs The three Otterscan address-history endpoints (`ots_searchTransactionsBefore`/`After`, `ots_getTransactionBySenderAndNonce`) hardcoded `unwrap_or(1)` / `unwrap_or_default()` as the lower bound of their block scan, which breaks when `genesis_block_number` is non-zero (e.g. `genesis.json` `number: 73`). Expose `Backend::genesis_number()` and fall back to `genesis_number() + 1` in non-fork mode, mirroring the existing post-fork `f.block_number() + 1` convention. --------- Co-authored-by: Isagi Yates Co-authored-by: Amp Co-authored-by: steven Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: figtracer Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Sergei Shulepov Co-authored-by: zerosnacks Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cui Co-authored-by: Centaur AI Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> Co-authored-by: Mikhail Mikheev <16622558+mmv08@users.noreply.github.com> Co-authored-by: lazymio Co-authored-by: Emma Jamieson-Hoare Co-authored-by: VIkions <99107287+vikions@users.noreply.github.com> Co-authored-by: Aïssata * fix(config): disable proxy detection in Etherscan client (#14454) * fix(config): disable proxy detection in Etherscan client * chore(config): use workspace reqwest version After rebase onto master, foundry-block-explorers 0.23 depends on reqwest 0.13. Aligning foundry-config's reqwest dependency with the workspace version (0.13) so the `reqwest::Client` passed to `with_client(..)` matches the type expected by the block-explorers builder. * fix(forge): warn when constructor args are ignored for constructor-less contracts (#14512) `forge create` previously dropped `--constructor-args` and `--constructor-args-path` silently when the target contract had no constructor, so users had no way to tell their values were never encoded. Emit `sh_warn!` in that branch, naming the contract, so the misuse surfaces at deploy time. * chore: update Cargo.lock (#14628) * Revert "fix(config): disable proxy detection in Etherscan client" (#14629) * Revert "fix(config): disable proxy detection in Etherscan client (#14454)" This reverts commit e37f525afbf0be3518f259ec6d5909b120b1cf46. * fix lockfile * fix: clean --no-default-features test build (#14622) * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Tempo signer lookup and access key signing (#523) * Fix formatting in cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation for on_fail condition in CI config Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix indentation in CircleCI configuration Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.21 to 2.62.31 (#139) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.21 to 2.62.31. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.21...0005e0116e92d8489d8d96fbff83f061c79ba95a) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github/codeql-action from 3 to 4 (#138) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump snyk/actions Bumps [snyk/actions](https://github.com/snyk/actions) from 14818c4695ecc4045f33c9cee9e795a788711ca4 to 9adf32b1121593767fc3c057af55b55db032dc04. - [Release notes](https://github.com/snyk/actions/releases) - [Commits](https://github.com/snyk/actions/compare/14818c4695ecc4045f33c9cee9e795a788711ca4...9adf32b1121593767fc3c057af55b55db032dc04) --- updated-dependencies: - dependency-name: snyk/actions dependency-version: 9adf32b1121593767fc3c057af55b55db032dc04 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update CircleCI config with comments and formatting Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update and rename ci-say-hello.yml to ci-web3-defi-gamefi.yml (#154) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci-web3-defi-gamefi.yml (#155) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_deploy.yml (#158) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/cargo.yml (#159) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.31 to 2.62.33 (#162) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.31 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/0005e0116e92d8489d8d96fbff83f061c79ba95a...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4 to 5 (#163) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge branch 'foundry-rs:master' (#164) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * feat(forge): add bypass prevrandao (#12125) * feat(forge): add bypass prevrandao * Update crates/evm/networks/src/lib.rs Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * changes after review: remove duped code --------- Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> * fix(fmt): filter libs when recursing (#12119) * fix(fmt): account for ternary operators when estimating size * fix(fmt): filter libs when recursing * style: clippy * test: wipe contracts before formatting * test: explicitly test ignore * fix(fmt): break try stmts in a fn header-like fashion (#12131) * chore(deps): bump softprops/action-gh-release from 2.3.4 to 2.4.1 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.4 to 2.4.1. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/62c96d0c4e8a889135c1f3a25910db8dbe0e85f7...6da8fa9354ddfdc4aeace5fc48d7f679b5214090) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#161) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/e7ef886cf8f69c25ecef6bbc2858a42e273496ec...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(anvil): always disable nonce check (foundry-rs#12144) (#165) * test: refactor testdata/ tests to be run in `forge test` (#12049) * test: run forge test on testdata/ * chore: refactor to use common Test contract * chore: disable testGasMeteringExternal, via-ir * test: rm unused repros * fix: paths * upd * fmt * fix more tests * test: turn testNonExistingContractRevert into expectRevert * fix some more paths * legacy assertions * compile paris with paris * fix: set configs for fs tests * fix remaining paths in cheats * restrict fs permissions * fix: set runtime evm_version too * fix vyper * fix: a couple of repros * fix: we have storage layouts * fix: 3223, 3674: set sender * reorder * feat: move repros expected failures to snapshots * feat: migrate remaining repros tests * feat: rm migrated files * skip testRevertIfGetUnlinked * move expected core/ failures * upd * move logs/ * move all forgetest tests from it/ to cli/ * fix fork test * move trace/ * tmp: move fuzz/invariant out of fuzz/ * move fuzz/ * forge fmt * wips * fix: both vyper and paris; set src/ * canon * lib log * logs * Revert "fix: set runtime evm_version too" This reverts commit 7ca544b10047f608d57c74fb3500a5fbe7e2650e. Contract-level inline config will set evm version for libraries too, which means we fail on deploying libraries that are compiled with newer evm version. * fix: set evm version where needed, per test function * test: reduce gas wastage * chore: clippy * invariant mod.rs * test: fix linking tests with new utils * redact_with * Revert "wips" This reverts commit ee2c17a3023ca7ce8e7effccf0ea0a0f28f6e510. * migrate invariant/target{,Abi} * migrate InvariantAfterInvariant.t.sol * migrate InvariantAssume.t.sol * migrate InvariantCalldataDictionary.t.sol, more test utils * migrate InvariantCustomError.t.sol * migrate InvariantExcludedSenders.t.sol * migrate InvariantFixtures.t.sol * migrate InvariantHandlerFailure.t.sol * interlude: forgot to use a new file * migrate InvariantInnerContract.t.sol * migrate InvariantPreserveState.t.sol * migrate InvariantReentrancy.t.sol * migrate InvariantRollFork.t.sol * migrate InvariantScrapeValues.t.sol * migrate InvariantSequenceNoReverts.t.sol * migrate InvariantShrinkBigSequence.t.sol * migrate InvariantShrinkFailOnRevert.t.sol * migrate InvariantShrinkWithAssert.t.sol * migrate InvariantTest1.t.sol * fix InvariantInnerContract.t.sol * update new Rlp test * com * better com * nuke tests/it * test: fix testdata paths in script tester * test: fix relative paths in test_cmd * test: redact more in issue_2851 * fix: copy testdata correctly * trace addrs * manual retry logic with --retry * fix nondeterministic output * debug: fs lock error context * test: fix project root for windows * test: skip project root test if unset * normalize both * typo * Revert "typo" This reverts commit 402bea105c6f38b82664b50ca854f95e456df795. * Revert "debug: fs lock error context" This reverts commit e5caeddd1e4cb457d7b24d7d7fdfdb370e2feabf. * fix * fix: locked_write_line for windows * chore: clippy * fmt * chore: speed up fuzzed_selected_targets * other way * fix nondeterministic output 2 * fix: disable persistence * test: revert old via-ir * ci: tweak cache key * do not run trace test when isolate --------- Co-authored-by: grandizzy * fix(anvil): always disable nonce check (#12144) * deps: bump deps (#12149) * deps: bump deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * minimum Cargo.lock --------- Co-authored-by: rplusq Co-authored-by: Claude Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: Claude * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#168) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#171) CI/CD Configuration Update: The CircleCI configuration file, cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring the CI pipeline utilizes a more recent Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.62.28 to 2.62.33 (#175) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.28 to 2.62.33. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/v2.62.28...e43a5023a747770bfcb71ae048541a681714b951) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.62.33 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Delete .circleci/cargo.yml (#179) I Configuration Removal: The .circleci/cargo.yml file, which defined CircleCI jobs for building and testing Rust projects, has been completely removed from the repository. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#182) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#183) Configuration File Cleanup: Removed an unnecessary blank line in the .circleci/config.yml file, improving its formatting and readability. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#187) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci_v1.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Rust Docker image version to 1.89.0 Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Create ci-web3-gamefi.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create codeql.yml (#208) * Update ci.yml (#209) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- https://github.com/apps/gemini-code-assist Code Review This pull request updates the Rust version in the CI from 1.88.0 to 1.89.0. While this is a good maintenance step, I've identified a potential improvement for your CI configuration. The project's Cargo.toml specifies a Minimum Supported Rust Version (MSRV) of 1.86, but the CI doesn't test against it. I've added a comment suggesting the addition of an MSRV check to prevent compatibility issues. * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry rs maste 1f4b36a (#214) * Create jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 58: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/docker-image.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "chore: fix isolate tests (#10344)" This reverts commit 70ded2b35f95ee9b4ee94f5e44961914d30a87f7. * Delete .github/workflows/jekyll.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update and rename docker-image.yml to docker.yml (#218) Streamline the Docker CI workflow by renaming the file and enhancing it with scheduled runs, Buildx multi-platform builds, metadata tagging, conditional pushes, and automated image signing with Cosign. CI: Rename and replace the legacy docker-image.yml workflow with docker.yml Add scheduled cron runs and triggers on pushes to master, semver tags, and PRs Configure Docker Buildx for multi-platform builds with cache Extract Docker metadata and conditionally push images to GHCR on non-PR events Install Cosign and sign published Docker images using ephemeral identity tokens Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker-image.yml (#224) CI: Introduce docker-image.yml GitHub Actions workflow to checkout code and build Docker image on ubuntu-latest Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update config.yml (#225) CI: Insert comment lines to delineate and structure sections in .circleci/config.yml for enhanced clarity Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update sequence.rs (#226) Enhancements: Add standalone # lines in sequence.rs to serve as hidden placeholders for rustdoc examples Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#227) * Update dependencies.yml Refactor the weekly dependencies workflow to inline cargo update steps, auto-generate commit messages and PR bodies with update logs, and use the create-pull-request action to open update PRs on a dedicated branch. Enhancements: Define environment variables for GitHub token, branch name, PR title, and PR body including cargo update logs Inline checkout, Rust toolchain setup, and cargo update command with log cleanup instead of relying on an external workflow Craft commit messages and PR bodies dynamically by capturing and formatting cargo update output Use peter-evans/create-pull-request to push Cargo.lock updates to a 'cargo-update' branch CI: Move permissions and GitHub token configuration into the job context Explicitly set the runner to ubuntu-latest and remove the top-level empty permissions block Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/dependencies.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update npm.yml (#228) CI: Add comment to the Publish Binary step indicating it runs automatically after a successful release workflow or can be triggered manually with a run_id Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update snyk-container.yml (#229) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.yml (#230) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update const.ts (#231) Code Formatting: Removed an extraneous blank line in npm/src/const.ts to improve code cleanliness and consistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Revert "Create web3_defi_gamefi.yml (#61)" (#233) This reverts commit 8575916b7675f246b54daf70cfddccb3f5b97fb0. * Create deploy.yml (#240) * Create deploy.yml CI: Add GitHub Actions workflow to build the Rust project, run tests, and build a Docker image on pushes to main/master Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 106: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#253) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.lock (#254) Chores: Regenerate Cargo.lock to update dependencies Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#255) * Create config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .circleci/config.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update config.yml (#256) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: upgrade tsdown from 0.15.12 to 0.16.1 Snyk has created this PR to upgrade tsdown from 0.15.12 to 0.16.1. See this package in npm: tsdown See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * Create google.yml (#266) CI: Introduce a Google Cloud deployment workflow that builds a Docker image, pushes it to Artifact Registry, and deploys it to a GKE cluster on pushes to the main branches. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.lock (#269) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update flake.nix (#270) Adjust Nix flake development shell configuration for better cross-platform support and simplify dependencies. Enhancements: Remove the dprint dependency from the Nix development shell. Add conditional AppKit framework linkage on Darwin systems in the Nix shell configuration. Drop custom hardeningDisable settings from the Nix development shell definition. https://github.com/apps/gemini-code-assist Code Review This pull request updates the Nix flake configuration to improve cross-platform support and simplify dependencies. The changes include removing dprint and hardeningDisable settings, and conditionally adding the AppKit framework for Darwin systems. While most changes are beneficial, removing dprint from the development shell dependencies while its configuration file remains could cause issues for contributors. I've added a comment regarding this potential inconsistency. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update Cargo.toml (#271) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update nextest.toml (#272) Adjust test runner configuration for nextest to better handle long-running and specific tests. Enhancements: Introduce a dedicated test group that limits chisel-serial tests to a single thread. Increase the default slow-test timeout period to reduce premature terminations for longer-running tests. Expand the slow-timeout override filter to include both ext_integration and can_test_forge_std tests. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dprint.json (#273) (https://github.com/apps/gemini-code-assist) Code Review This pull request updates the dprint.json configuration file. The changes correctly enable formatting for dprint.json itself by modifying the excludes list, update the JSON and Markdown dprint plugins to their latest versions, and add a final newline to the file for POSIX compliance. These are all good maintenance improvements. The changes have been reviewed and appear to be correct and beneficial. No issues were found. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/workflows/apisec-scan.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update counter/README.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Dependabot/cargo/cargo 38744a1864 (#282) * chore(deps): bump alloy-dyn-abi in the cargo group across 1 directory Bumps the cargo group with 1 update in the / directory: [alloy-dyn-abi](https://github.com/alloy-rs/core). Updates `alloy-dyn-abi` from 0.8.25 to 0.8.26 - [Release notes](https://github.com/alloy-rs/core/releases) - [Changelog](https://github.com/alloy-rs/core/blob/v0.8.26/CHANGELOG.md) - [Commits](https://github.com/alloy-rs/core/compare/v0.8.25...v0.8.26) --- updated-dependencies: - dependency-name: alloy-dyn-abi dependency-version: 0.8.26 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] * Update and rename ci.yml to cargo.yml (#268) Update CircleCI configuration to use a different Rust toolchain image and rename the workflow file. Build: Rename the CircleCI configuration file from ci.yml to cargo.yml. Change the CircleCI Docker image to use Rust 1.78.0 instead of 1.88.0. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Update config.yml (#283) Summary by Sourcery Update CircleCI pipeline to use a custom Docker executor and job tailored to the project instead of the example hello-world workflow. Enhancements: Introduce a reusable custom executor that pulls from the stable cimg/base Docker image with Docker Hub authentication. CI: Replace the sample say-hello job and workflow with a project-specific job and workflow wired to the new custom executor in .circleci/config.yml. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix: use network-specific BaseFeeParams for Optimism in Anvil * Dargon789 patch 1 (#285) * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#167) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/ci_v1.yml (#173) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#174) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci/config.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 83: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 93: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 76: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 80: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update cargo.yml (#210) https://github.com/apps/gemini-code-assist ------------------- Code Review This pull request downgrades the Rust version in the CI pipeline from 1.88.0 to 1.87.0. This is inconsistent with the project's declared Minimum Supported Rust Version (MSRV) of 1.89 in Cargo.toml. My review highlights this discrepancy and suggests aligning the CI's Rust version with the MSRV to ensure the project's compatibility guarantees are properly tested. --------------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Gengar * merge gh-master (#287) * Create config.yml (#236) Create .circleci/config.yml defining a version 2.1 pipeline with a docker-based "say-hello" job, checkout and echo steps, and a workflow to orchestrate it Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * Update crates/config/src/compilation.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Foundry/ethereum ux (#284) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Gamefi defi (#288) * chore: ignore RUSTSEC-2025-0137 (#12941) Co-authored-by: Claude * chore(deps): weekly `cargo update` (#12940) * chore(deps): weekly `cargo update` Updating git repository `https://github.com/rust-cli/rexpect` Updating git repository `https://github.com/paradigmxyz/solar.git` Skipping git submodule `https://github.com/argotorg/solidity.git` due to update strategy in .gitmodules Updating git repository `https://github.com/tempoxyz/tempo` Updating git repository `https://github.com/paradigmxyz/reth` Locking 71 packages to latest compatible versions Updating alloy-chains v0.2.23 -> v0.2.24 Updating alloy-consensus v1.1.3 -> v1.2.1 Updating alloy-consensus-any v1.1.3 -> v1.2.1 Updating alloy-contract v1.1.3 -> v1.2.1 Updating alloy-dyn-abi v1.5.1 -> v1.5.2 Updating alloy-eip5792 v1.1.3 -> v1.2.1 Updating alloy-eips v1.1.3 -> v1.2.1 Updating alloy-ens v1.1.3 -> v1.2.1 Updating alloy-genesis v1.1.3 -> v1.2.1 Updating alloy-json-abi v1.5.1 -> v1.5.2 Updating alloy-json-rpc v1.1.3 -> v1.2.1 Updating alloy-network v1.1.3 -> v1.2.1 Updating alloy-network-primitives v1.1.3 -> v1.2.1 Updating alloy-primitives v1.5.1 -> v1.5.2 Updating alloy-provider v1.1.3 -> v1.2.1 Updating alloy-pubsub v1.1.3 -> v1.2.1 Updating alloy-rpc-client v1.1.3 -> v1.2.1 Updating alloy-rpc-types v1.1.3 -> v1.2.1 Updating alloy-rpc-types-anvil v1.1.3 -> v1.2.1 Updating alloy-rpc-types-any v1.1.3 -> v1.2.1 Updating alloy-rpc-types-beacon v1.1.3 -> v1.2.1 Updating alloy-rpc-types-debug v1.1.3 -> v1.2.1 Updating alloy-rpc-types-engine v1.1.3 -> v1.2.1 Updating alloy-rpc-types-eth v1.1.3 -> v1.2.1 Updating alloy-rpc-types-trace v1.1.3 -> v1.2.1 Updating alloy-rpc-types-txpool v1.1.3 -> v1.2.1 Updating alloy-serde v1.1.3 -> v1.2.1 Updating alloy-signer v1.1.3 -> v1.2.1 Updating alloy-signer-aws v1.1.3 -> v1.2.1 Updating alloy-signer-gcp v1.1.3 -> v1.2.1 Updating alloy-signer-ledger v1.1.3 -> v1.2.1 Updating alloy-signer-local v1.1.3 -> v1.2.1 Updating alloy-signer-trezor v1.1.3 -> v1.2.1 Updating alloy-signer-turnkey v1.1.3 -> v1.2.1 Updating alloy-sol-macro v1.5.1 -> v1.5.2 Updating alloy-sol-macro-expander v1.5.1 -> v1.5.2 Updating alloy-sol-macro-input v1.5.1 -> v1.5.2 Updating alloy-sol-type-parser v1.5.1 -> v1.5.2 Updating alloy-sol-types v1.5.1 -> v1.5.2 Updating alloy-transport v1.1.3 -> v1.2.1 Updating alloy-transport-http v1.1.3 -> v1.2.1 Updating alloy-transport-ipc v1.1.3 -> v1.2.1 Updating alloy-transport-ws v1.1.3 -> v1.2.1 Updating alloy-trie v0.9.1 -> v0.9.2 Updating alloy-tx-macros v1.1.3 -> v1.2.1 Unchanged annotate-snippets v0.12.5 (available: v0.12.10) Unchanged anstyle-svg v0.1.11 (available: v0.1.12) Downgrading aws-smithy-runtime v1.9.6 -> v1.9.5 Updating axum-core v0.5.5 -> v0.5.6 Updating cc v1.2.50 -> v1.2.51 Updating derive_more v2.1.0 -> v2.1.1 Updating derive_more-impl v2.1.0 -> v2.1.1 Updating dtoa v1.0.10 -> v1.0.11 Updating find-msvc-tools v0.1.5 -> v0.1.6 Unchanged generic-array v0.14.7 (available: v0.14.9) Unchanged icu_collections v2.0.0 (available: v2.1.1) Unchanged icu_normalizer v2.0.1 (available: v2.1.1) Unchanged icu_normalizer_data v2.0.0 (available: v2.1.1) Unchanged icu_properties v2.0.2 (available: v2.1.2) Unchanged icu_properties_data v2.0.1 (available: v2.1.2) Unchanged idna_adapter v1.1.0 (available: v1.2.1) Updating itoa v1.0.15 -> v1.0.17 Updating jiff v0.2.16 -> v0.2.17 Updating jiff-static v0.2.16 -> v0.2.17 Updating libredox v0.1.11 -> v0.1.12 Updating libz-rs-sys v0.5.4 -> v0.5.5 Unchanged matchit v0.8.4 (available: v0.8.6) Unchanged mdbook v0.4.52 (available: v0.5.2) Updating portable-atomic v1.12.0 -> v1.13.0 Updating proc-macro2 v1.0.103 -> v1.0.104 Unchanged protobuf v3.3.0 (available: v3.7.2) Unchanged protobuf-support v3.3.0 (available: v3.7.2) Unchanged rand v0.8.5 (available: v0.9.2) Unchanged ratatui v0.29.0 (available: v0.30.0) Updating reqwest v0.12.26 -> v0.12.28 Updating ruint v1.17.0 -> v1.17.1 Updating rustix v1.1.2 -> v1.1.3 Updating ryu v1.0.21 -> v1.0.22 Updating schemars v1.1.0 -> v1.2.0 Updating schemars_derive v1.1.0 -> v1.2.0 Updating serde_json v1.0.145 -> v1.0.148 Updating signal-hook-registry v1.4.7 -> v1.4.8 Updating syn-solidity v1.5.1 -> v1.5.2 Updating tempfile v3.23.0 -> v3.24.0 Unchanged trezor-client v0.1.4 (available: v0.1.5) Unchanged unicode-width v0.2.0 (available: v0.2.2) Unchanged vergen v8.3.2 (available: v9.0.6) Updating zlib-rs v0.5.4 -> v0.5.5 Adding zmij v1.0.0 note: to see how you depend on a package, run `cargo tree --invert @` * touchups * touchups --------- Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: Matthias Seitz * Update flake.lock (#12939) flake.lock: Update Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/16642c5' (2025-12-20) → 'github:nix-community/fenix/3479aaf' (2025-12-27) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/ea1d299' (2025-12-18) → 'github:rust-lang/rust-analyzer/8c5a68e' (2025-12-26) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/7d853e5' (2025-12-19) → 'github:NixOS/nixpkgs/3edc4a3' (2025-12-27) Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * fix(chisel): uninitalized variables (#12937) * chore(deps): bump Swatinem/rust-cache from 2.8.1 to 2.8.2 (#12919) Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/f13886b937689c021905a6b90929199931d60db1...779680da715d629ac1d338a641029a2f4372abb5) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-version: 2.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore(deps): bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12918) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...98357b18bf14b5342f975ff684046ec3b2a07725) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * chore: sepolia rpc url (#12945) chore: sepolia rpc url private * chore(deps): bump crate-ci/typos from 1.40.0 to 1.40.1 (#12949) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/2d0ce569feab1f8752f1dde43cc2f2aa53236e06...1a319b54cc9e3b333fed6a5c88ba1a90324da514) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump DeterminateSystems/determinate-nix-action from 3.15.0 to 3.15.1 (#12950) chore(deps): bump DeterminateSystems/determinate-nix-action Bumps [DeterminateSystems/determinate-nix-action](https://github.com/determinatesystems/determinate-nix-action) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/determinatesystems/determinate-nix-action/releases) - [Commits](https://github.com/determinatesystems/determinate-nix-action/compare/95732e95d70db3ba1e0adc26a63c5e0375aba78c...1d699fc25db3f9e079cd2f168ca007a4183389be) --- updated-dependencies: - dependency-name: DeterminateSystems/determinate-nix-action dependency-version: 3.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump taiki-e/install-action from 2.65.1 to 2.65.7 (#12951) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.65.1 to 2.65.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/b9c5db3aef04caffaf95a1d03931de10fb2a140f...4c6723ec9c638cccae824b8957c5085b695c8085) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.65.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(config): err on unknown profile (#12946) * test: remove duplicate Issue2851 test (#12953) * chore(cheats): make sign(Wallet) pure (#12912) * chore(cheats): make sign(Wallet) pure * ignore --------- Co-authored-by: Matthias Seitz Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * fix(evm): use timestamp-based blob base fee calculation (#12959) * fix(evm): use timestamp-based blob base fee calculation * chore: use patch * Now BPO1 is default * bump to hardforks to 0.4.7 --------- Co-authored-by: Matthias Seitz * fix(config): reject bare versions in compilation restrictions (#12955) fmt Co-authored-by: tefyosL-sol * Revert "fix(config): err on unknown profile (#12946)" (#12964) This reverts commit 6ff4b52e2e572e93d0cd81591b1bd0e6ad9ed507. * fix(anvil): use B256 instead of TxHash for block hash parameters (#12961) Update mod.rs * Update crates/config/src/compilation.rs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Matthias Seitz Co-authored-by: Claude Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: Desant pivo Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Create ci-web3-gamefi.yml (#217) (#289) Introduce a basic CircleCI pipeline for the web3 GameFi project, providing a custom Docker executor and a stub job within a workflow. CI: Add CircleCI config file ci-web3-gamefi.yml with version 2.1 pipeline Define a custom executor using the cimg/base:stable Docker image with Docker Hub credentials Create a web3-defi-game-project- job and integrate it into a my-custom-workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Merge pull request #47 (#290) * Add .circleci/config.yml * Updated config.yml * Updated config.yml * Updated config.yml * Update test.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#46) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * chore(deps): bump revm to 24.0.0 (#10601) * feat: implement add_balance endpoint (#10636) * fix(bindings): ensure forge bind generates snake_case file names (#10622) * fix(bindings): ensure forge bind generates snake_case file names * refactor: use heck crate for snake_case conversion --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * chore: standardize lint help + validate docs existance (#10639) * feat(cast mktx): add support for "--ethsign" option (#10641) - Sign transactions using "eth_signTransaction" on local node with unlocked accounts. - Same TX building logic as in "cast send --unlocked". - Added a test case to validate the new functionality. * chore(wallets): improve error message for signer instantiation failure (#10646) chore(wallets): improve error message on signer instantiation failure * chore: replaced anvil hardforks with alloy hardforks (#10612) * chore: replaced anvil hardforks with alloy hardforks * fixes * fixes * fixes * removed redundant op and alloy hardforks enum * fixes * fixes * bumped alloy hardforks and kept default to prague and isthmus * bumped alloy-hardforks and fixes --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * fix(`anvil`): latest evm version should be prague (#10653) * fix(`anvil`): latest evm version should be prague * fix test * nit * chore(deps): bump tracing-subscriber (#51) Bumps the cargo group with 1 update in the / directory: [tracing-subscriber](https://github.com/tokio-rs/tracing). Updates `tracing-subscriber` from 0.3.19 to 0.3.20 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.19...tracing-subscriber-0.3.20) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.20 dependency-type: direct:production dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update test.yml (#52) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update docker-image.yml (#53) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create ci_cargo.yml (#59) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create web3_defi_gamefi.yml (#61) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 21: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#247) Improve readability of the GitHub Actions dependencies workflow by adjusting whitespace and adding blank lines CI: Add blank line before the workflow name declaration Insert blank line after the scheduled cron job entry Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update dependencies.yml (#248) CI: Remove extraneous blank line in .github/workflows/dependencies.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#249) CI: Remove dev branch from test workflow triggers Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update crates/evm/evm/src/executors/corpus.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Foundry/master test ux (#295) * Update ci.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yml (#211) This pull request updates the Rust version in the CircleCI workflow to 1.89.0. This is a good maintenance task to keep the CI environment up-to-date. I have one suggestion regarding the Docker image tag to potentially simplify future maintenance by automatically adopting patch releases. Overall, the change is correct and beneficial. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update test.yml (#250) CI: Include the 'main' branch in the push event triggers for the test workflow Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * fix(fmt): handle trailing coments between base contracts (#296) (#299) @0xrusowsky @Dargon789 fix(fmt): handle trailing coments between base contracts Revert 142 master (#296) * Create ci_cargo.yml (#72) * Create config.yml * Rename ci_cargo.yml to cargo.yml * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. * Fix cloning of compiler settings for Vyper input Replace context.clone().compiler_settings.vyper with context.compiler_settings.vyper.clone() to avoid unnecessary cloning of the entire VerificationContext. This reduces memory allocations when creating VyperInput instances. Applied to both etherscan and sourcify verification providers. * Remove duplicate logic in TxSigner::address() implementations --------- Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Aganis * Update CircleCI configuration for dev stage (#300) fix Automatic reruns provide a safety net for your CI/CD pipelines by automatically retrying failed steps and/or workflows. Automatic reruns help teams maintain productivity by reducing the need for manual intervention when steps and workflows fail due to temporary issues. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * EIP-4788 implementation * formatting * add beacon block root tests * Update crates/evm/evm/src/executors/trace.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/cast/src/cmd/run.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * feat: upgrade @types/node from 24.10.4 to 25.0.2 Snyk has created this PR to upgrade @types/node from 24.10.4 to 25.0.2. See this package in npm: @types/node See this project in Snyk: https://app.snyk.io/org/dargon789/project/8da85645-409e-46fa-bd46-9b58e7905fb8?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr * fix: `svm fails to download solc 0.8.33 on linux/arm64`, bump `svm-rs` (#13007) (#309) bump svm-rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Ethereumjs/master (#310) * Potential fix for code scanning alert no. 19: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 61: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Potential fix for code scanning alert no. 74: Artifact poisoning Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Create config.yml (#105) * Create cargo.yml (#106) Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Delete .github/workflows/docker-image.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Rename ci_cargo.yml to cargo.yml Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * fix(fmt): handle trailing coments between base contracts (#12127) * fix(fmt): account for ternary operators when estimating size * fix(fmt): handle comments between inherited base contracts * test: layout + base inheritance * Revert "fix(fmt): handle trailing coments between base contracts (#12127)" This reverts commit b8b5fbb83fa2436063cebc34ddf900abc972b11d. * Update cargo.yml (#172) CI/CD Configuration Update: The CircleCI configuration file, .circleci/cargo.yml, has been updated to use a newer version of the Rust Docker image. Rust Toolchain Version Bump: The cimg/rust Docker image version has been incremented from 1.88.0 to 1.89.0, ensuring builds and tests run with the latest stable Rust toolchain. Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> * Revert "Create cargo.yml (#106)" This reverts commit 251a2b4fce0c50e3426ffb2022d9abef5b948fa9. * Create cargo.yml (#213) https://github.com/apps/gemini-code-assist Code Review This pull request introduces a CircleCI workflow to automate formatting checks and tests. My review has identified two main issues in the configuration: redundant steps that would unnecessarily increase job execution time, and a mismatch between the Rust version in the CI environment and the one specified in the project's Cargo.toml. I've provided suggestions to fix these issues for a more efficient and consistent CI process. Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create docker.yml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Remove duplicate logic in TxSigner::address() implementations * fix(fmt): handle trailing coments between base contracts (#296) @0xrusowsky @Dargon789 fix(fmt): handle trailin… * ci: sign release archives, docker images, and publish SBOMs * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update crates/forge/src/cmd/test/mod.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * dargon789/gamefi (#531) * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update .github/scripts/compare-nightly.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: googleworkspace-bot Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Artifact poisoning' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * fix(evm): preserve `CREATE` address on revert in isolation mode (#14637) * ci: run test-isolate on push to master, move flaky isolate tests to test-flaky (#14638) * ci: run test-isolate on push to master, move isolated flaky tests to test-flaky Amp-Thread-ID: https://ampcode.com/threads/T-019e0177-aaab-74ec-a6bf-b5d330936b9a Co-authored-by: Amp * ci: drop issue-opening on test-isolate failures Amp-Thread-ID: https://ampcode.com/threads/T-019e0177-aaab-74ec-a6bf-b5d330936b9a Co-authored-by: Amp --------- Co-authored-by: Amp * fix(anvil): abort node tasks on handle drop (#14636) * fix(anvil): abort node tasks on handle drop * test(cast): keep anvil handle alive in erc20 tests * fix(ci): prev nightly benchmark results selection (#14639) * Revert "fix(script): keep plain Tempo broadcasts non-AA" (#14642) This reverts commit 24b023c9959ce4535e58851963308aab6d64f531. * fix(ci): remove `cast vaddr` tests from Tempo CI (#14644) * feat(lint): add `var-read-using-this` gas lint (#14607) * feat(lint): add var-read-using-this gas lint * move import --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * refactor(cli): merge `TempoCommonOpts` back to `TempoOpts` (#14647) * fix(Makefile): prevent backtick command substitution in lint-typos message (#14648) * feat(forge): add noop --no-commit flag to forge init for backwards compatibility (#14649) * feat(anvil): add --fund-accounts CLI flag (#14392) * feat(anvil): add `--fund-accounts` CLI flag Allow funding specific accounts with custom ETH balances on startup via `--fund-accounts 0xAddr:amount` (amount in ETH). Co-Authored-By: Claude Opus 4.6 (1M context) * refactor: use HashMap import instead of fully-qualified std::collections::HashMap Co-Authored-By: Claude Opus 4.6 (1M context) * fix: fmt --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * fix(anvil): saturate `mint` when parsing OP deposit txs (#14652) fix(anvil): saturate `mint` when parsing OP deposit txs `get_deposit_tx_parts` converted `mint` that is a `U256` to `u128` with `U256::to::()`, which panics on overflow * fix(config): gate etherscan no_proxy behind --no-proxy flag (#14630) Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Centaur AI Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> * chore: bump version to 1.7.2 (#14663) chore: bump version to 1.7.2 for next nightly prefix Amp-Thread-ID: https://ampcode.com/threads/T-019e06ef-8f01-72c9-8d57-eab53250ca7e Co-authored-by: Amp * feat(bench): compare stable vs nightly in the same run (#14671) Running benchmarks against yesterday's results is unreliable because different CI runners have varying hardware performance. Instead, run both stable and nightly on the same runner in the same workflow run and compare them directly, eliminating hardware variance. * test(cast): mark estimate_base_da as flaky (#14672) Amp-Thread-ID: https://ampcode.com/threads/T-019e0804-5bc5-76ac-bf65-e822925f3b63 Co-authored-by: Amp * fix(`anvil`): saturate U256 casts on user controlled RPC inputs (#14658) Swap `Uint::to::<_>()` for `saturating_to::<_>()`. Closes foundry-rs#14653, foundry-rs#14654, foundry-rs#14655, foundry-rs#14656, foundry-rs#14657 * fix(cast): infer evm opts with `--trace` (#14673) * feat: improve forge build lint-failure UX * fix(anvil): accept string values for u64 numeric RPC params (#14650) Co-authored-by: Matthias Seitz * vercel-wagmi (#535) Co-authored-by: googleworkspace-bot --------- Signed-off-by: googleworkspace-bot Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: AU_gdev_19 <64915515+Dargon789@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Co-authored-by: Louis Peter Sitoe Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: MD Islam Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: figtracer Co-authored-by: mk0walsk Co-authored-by: Suuuuuuperrrrr fred Co-authored-by: Amp Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Co-authored-by: Arsh Co-authored-by: googleworkspace-bot Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: grandizzy Co-authored-by: Rafael Quintero <32346241+rplusq@users.noreply.github.com> Co-authored-by: rplusq Co-authored-by: snyk-io[bot] <141718529+snyk-io[bot]@users.noreply.github.com> Co-authored-by: Gengar Co-authored-by: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Co-authored-by: cakevm Co-authored-by: Matthias Seitz Co-authored-by: Theodore Solis Co-authored-by: tefyosL-sol Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Desant pivo Co-authored-by: pistomat Co-authored-by: zark <77061323+zarkk01@users.noreply.github.com> Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Aganis Co-authored-by: tskoyo Co-authored-by: Matt D Co-authored-by: onbjerg Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Co-authored-by: Avory Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> Co-authored-by: cui Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Co-authored-by: cui <1579517+cuiweixie@users.noreply.github.com> Co-authored-by: steven Co-authored-by: zerosnacks Co-authored-by: John Chase <68833933+joohhnnn@users.noreply.github.com> Co-authored-by: Nuno David <74260683+ndavd@users.noreply.github.com> Co-authored-by: Yuya Maruyama <69783679+YuyaMaruyama21D4E@users.noreply.github.com> Co-authored-by: Yero~ Co-authored-by: Georgios Konstantopoulos Co-authored-by: Philippe Dumonet Co-authored-by: Vicze Osikata Co-authored-by: Mahmoud Lababidi Co-authored-by: Tim Beiko Co-authored-by: Oliver Nordbjerg Co-authored-by: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Himess <95512809+Himess@users.noreply.github.com> Co-authored-by: albertov19 <64150856+albertov19@users.noreply.github.com> Co-authored-by: Ninja Co-authored-by: Mark Fizer Co-authored-by: marukai67 Co-authored-by: sashass1315 Co-authored-by: 0xferrous <0xferrous@proton.me> Co-authored-by: radik878 Co-authored-by: MozirDmitriy Co-authored-by: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Co-authored-by: Tran Quang Loc Co-authored-by: James Niken <155266991+dizer-ti@users.noreply.github.com> Co-authored-by: 0xMars42 Co-authored-by: strmfos <155266597+strmfos@users.noreply.github.com> Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com> Co-authored-by: iPLAY888 <133153661+letmehateu@users.noreply.github.com> Co-authored-by: Valentin B. <703631+beeb@users.noreply.github.com> Co-authored-by: howy <132113803+howydev@users.noreply.github.com> Co-authored-by: Adrian Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com> Co-authored-by: fuder.eth Co-authored-by: figtracer <1gusredo@gmail.com> Co-authored-by: Tomass <155266802+zeroprooff@users.noreply.github.com> Co-authored-by: Nikki Co-authored-by: Mayank Sharma <82099885+mayanksharma-eth@users.noreply.github.com> Co-authored-by: Mayank Sharma <82099885+codersharma2001@users.noreply.github.com> Co-authored-by: Snezhkko Co-authored-by: anim001k <140460766+anim001k@users.noreply.github.com> Co-authored-by: Forostovec Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com> Co-authored-by: SocksNFlops <91764028+SocksNFlops@users.noreply.github.com> Co-authored-by: Amlandeep Bhadra Co-authored-by: Edgar Richards Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com> Co-authored-by: Alexandro T. Netto <56097505+alextnetto@users.noreply.github.com> Co-authored-by: Alex Netto Co-authored-by: Red Swan Co-authored-by: Louis Peter Sitoe <202908293+solanaXpeter@users.noreply.github.com> Co-authored-by: Santiago Palladino Co-authored-by: o-az Co-authored-by: Perico Perica Co-authored-by: Federico Gimenez Co-authored-by: George Niculae Co-authored-by: Copilot Co-authored-by: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Co-authored-by: Isagi Yates Co-authored-by: Amp Co-authored-by: Sergei Shulepov Co-authored-by: Centaur AI Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> Co-authored-by: Mikhail Mikheev <16622558+mmv08@users.noreply.github.com> Co-authored-by: lazymio Co-authored-by: Emma Jamieson-Hoare Co-authored-by: VIkions <99107287+vikions@users.noreply.github.com> Co-authored-by: Aïssata Co-authored-by: MD Islam Co-authored-by: Daniel Boye <83395536+DanielBoye@users.noreply.github.com> --- .cargo/config.toml | 7 +- .circleci/cargo.yml | 32 + .circleci/ci-web3-gamefi.yml | 26 + .circleci/ci.yml | 31 + .circleci/ci_cargo.yml | 37 + .circleci/ci_v1.yml | 31 + .circleci/config.yml | 37 +- .circleci/dev_stage.yml | 70 + .circleci/web3_defi_gamefi.yml | 26 + .codesandbox/tasks.json | 7 + .codespellrc | 3 - .config/nextest.toml | 17 +- .deps/remix-tests/remix_accounts.sol | 39 + .deps/remix-tests/remix_tests.sol | 225 + .devcontainer/Dockerfile.dev | 84 - .devcontainer/devcontainer.json | 49 - .git-blame-ignore-revs | 3 + .gitattributes | 7 + .github/CODEOWNERS | 2 +- .github/CRATE_CHECKS_FAILURE_TEMPLATE.md | 10 + .github/FLAKY_TEST_FAILURE_TEMPLATE.md | 10 + .../FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md | 10 + .github/ISSUE_TEMPLATE/BUG-FORM.yml | 109 +- .github/ISSUE_TEMPLATE/FEATURE-FORM.yml | 61 +- .github/ISSUE_TEMPLATE/bug_report.md | 41 + .github/ISSUE_TEMPLATE/custom.md | 10 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md | 10 + .github/dependabot.yml | 39 + .github/scripts/bump-tempo.sh | 145 + .github/scripts/combine-benchmarks.sh | 187 + .github/scripts/commit-benchmark-results.sh | 75 + .github/scripts/compare-nightly.sh | 62 + .github/scripts/contracts/BatchCounter.sol | 18 + .../contracts/BatchRevertTest.s.sol.template | 24 + .../contracts/BatchTest.s.sol.template | 25 + .../scripts/contracts/CounterWithRequire.sol | 15 + .github/scripts/contracts/DeployAndCall.s.sol | 24 + .github/scripts/create-tag.js | 8 +- .github/scripts/format-pr-comment.sh | 34 + .github/scripts/format.sh | 9 +- .github/scripts/matrices.py | 39 +- .github/scripts/move-tag.js | 15 - .github/scripts/prune-prereleases.js | 68 - .github/scripts/read-benchmark-results.sh | 37 + .github/scripts/setup-foundryup.sh | 54 + .github/scripts/shellcheck.sh | 24 + .github/scripts/tempo-check.sh | 899 ++ .github/scripts/tempo-deploy.sh | 85 + .github/scripts/tempo-mpp.sh | 207 + .github/scripts/tempo-wallet.sh | 95 + .github/workflows/Docker.yml | 62 + .github/workflows/apisec-scan.yml | 29 + .github/workflows/benchmarks-nightly.yml | 277 + .github/workflows/benchmarks.yml | 255 + .github/workflows/bump-forge-std.yml | 15 +- .github/workflows/bump-tempo.yml | 59 + .github/workflows/ci-mpp.yml | 60 + .github/workflows/ci-tempo.yml | 155 + .github/workflows/ci.yml | 203 + .github/workflows/codeql.yml | 92 + .github/workflows/crate-checks.yml | 58 + .github/workflows/dependencies.yml | 20 - .github/workflows/deploy.yml | 27 + .github/workflows/docker-publish.yml | 138 +- .github/workflows/docker.yml | 62 + .github/workflows/docs.yml | 60 + .github/workflows/google.yml | 117 + .github/workflows/nextest.yml | 98 - .github/workflows/nix.yml | 53 + .github/workflows/npm.yml | 324 + .github/workflows/release.yml | 333 +- .github/workflows/snyk-container.yml | 55 + .github/workflows/static.yml | 43 + .github/workflows/test-flaky.yml | 131 + .github/workflows/test-isolate.yml | 23 +- .github/workflows/test.yml | 220 +- .gitignore | 21 +- .gitmodules | 6 + CONTRIBUTING.md | 39 +- Cargo.lock | 7676 +++++++++----- Cargo.toml | 569 +- Dockerfile | 97 +- Dockerfile.cross | 28 - FUNDING.json | 2 +- Makefile | 163 +- README.md | 337 +- SECURITY.md | 111 +- benches/Cargo.toml | 31 + benches/LATEST.md | 108 + benches/README.md | 132 + benches/src/lib.rs | 531 + benches/src/main.rs | 270 + benches/src/results.rs | 280 + benchmark.sh | 52 + clippy.toml | 2 - counter/.gas-snapshot | 2 + .../.github/workflows/test.yml | 3 + counter/.gitignore | 18 + counter/README.md | 66 + counter/doc/doc-filelist.js | 1 + counter/doc/doc-script.js | 228 + counter/doc/doc-style.css | 403 + counter/foundry.toml | 9 + counter/lib/forge-std | 1 + counter/lib/openzeppelin-contracts | 1 + counter/remappings.txt | 1 + .../script/Counter.s.sol | 0 counter/soldeer.lock | 6 + .../src/Counter.sol | 0 .../test/Counter.t.sol | 0 crates/anvil/Cargo.toml | 59 +- crates/anvil/core/Cargo.toml | 14 +- crates/anvil/core/src/eth/block.rs | 158 +- crates/anvil/core/src/eth/mod.rs | 222 +- crates/anvil/core/src/eth/serde_helpers.rs | 53 +- crates/anvil/core/src/eth/subscription.rs | 6 +- crates/anvil/core/src/eth/transaction/mod.rs | 1695 +-- crates/anvil/core/src/eth/wallet.rs | 51 +- crates/anvil/core/src/lib.rs | 2 +- crates/anvil/rpc/src/error.rs | 4 +- crates/anvil/rpc/src/lib.rs | 2 +- crates/anvil/rpc/src/request.rs | 4 +- crates/anvil/rpc/src/response.rs | 9 +- crates/anvil/server/src/config.rs | 2 +- crates/anvil/server/src/handler.rs | 92 +- crates/anvil/server/src/ipc.rs | 9 +- crates/anvil/server/src/lib.rs | 8 +- crates/anvil/server/src/pubsub.rs | 99 +- crates/anvil/server/src/ws.rs | 6 +- crates/anvil/src/args.rs | 23 +- crates/anvil/src/cmd.rs | 294 +- crates/anvil/src/config.rs | 589 +- crates/anvil/src/{server => }/error.rs | 0 crates/anvil/src/eth/api.rs | 4390 ++++---- crates/anvil/src/eth/backend/cheats.rs | 108 +- crates/anvil/src/eth/backend/db.rs | 380 +- crates/anvil/src/eth/backend/env.rs | 30 - crates/anvil/src/eth/backend/executor.rs | 904 +- crates/anvil/src/eth/backend/fork.rs | 662 +- crates/anvil/src/eth/backend/genesis.rs | 2 + crates/anvil/src/eth/backend/info.rs | 40 +- crates/anvil/src/eth/backend/mem/cache.rs | 57 +- crates/anvil/src/eth/backend/mem/fork_db.rs | 47 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 14 +- crates/anvil/src/eth/backend/mem/inspector.rs | 122 +- crates/anvil/src/eth/backend/mem/mod.rs | 6085 +++++++---- crates/anvil/src/eth/backend/mem/optimism.rs | 61 + crates/anvil/src/eth/backend/mem/state.rs | 133 +- crates/anvil/src/eth/backend/mem/storage.rs | 392 +- crates/anvil/src/eth/backend/mod.rs | 2 +- crates/anvil/src/eth/backend/tempo.rs | 318 + crates/anvil/src/eth/backend/time.rs | 4 +- crates/anvil/src/eth/backend/validate.rs | 18 +- .../anvil/src/eth/{error.rs => error/mod.rs} | 203 +- crates/anvil/src/eth/error/optimism.rs | 62 + crates/anvil/src/eth/fees.rs | 230 +- crates/anvil/src/eth/miner.rs | 117 +- crates/anvil/src/eth/otterscan/api.rs | 155 +- crates/anvil/src/eth/pool/mod.rs | 336 +- crates/anvil/src/eth/pool/transactions.rs | 497 +- crates/anvil/src/eth/sign.rs | 132 +- crates/anvil/src/eth/util.rs | 56 +- crates/anvil/src/evm.rs | 280 - crates/anvil/src/evm/mod.rs | 162 + crates/anvil/src/evm/optimism.rs | 87 + crates/anvil/src/filter.rs | 127 +- crates/anvil/src/hardfork.rs | 124 - crates/anvil/src/lib.rs | 90 +- crates/anvil/src/logging.rs | 12 +- crates/anvil/src/opts.rs | 6 +- crates/anvil/src/pubsub.rs | 83 +- crates/anvil/src/server/beacon/error.rs | 132 + crates/anvil/src/server/beacon/handlers.rs | 162 + crates/anvil/src/server/beacon/mod.rs | 22 + crates/anvil/src/server/beacon/utils.rs | 39 + crates/anvil/src/server/mod.rs | 39 +- .../server/{handler.rs => rpc_handlers.rs} | 27 +- crates/anvil/src/service.rs | 81 +- crates/anvil/src/shutdown.rs | 3 +- crates/anvil/src/tasks/block_listener.rs | 17 +- crates/anvil/src/tasks/mod.rs | 61 +- .../test-data/state-dump-legacy-stress.json | 2 +- crates/anvil/test-data/state-dump-legacy.json | 2 +- crates/anvil/test-data/state-dump.json | 315 +- crates/anvil/tests/it/anvil.rs | 123 +- crates/anvil/tests/it/anvil_api.rs | 449 +- crates/anvil/tests/it/api.rs | 301 +- crates/anvil/tests/it/beacon_api.rs | 259 + crates/anvil/tests/it/eip2935.rs | 70 + crates/anvil/tests/it/eip4844.rs | 189 +- crates/anvil/tests/it/eip7702.rs | 80 +- crates/anvil/tests/it/fork.rs | 524 +- crates/anvil/tests/it/gas.rs | 56 +- crates/anvil/tests/it/genesis.rs | 2 +- crates/anvil/tests/it/ipc.rs | 4 +- crates/anvil/tests/it/logs.rs | 24 +- crates/anvil/tests/it/main.rs | 13 +- crates/anvil/tests/it/optimism.rs | 146 +- crates/anvil/tests/it/otterscan.rs | 10 +- crates/anvil/tests/it/proof.rs | 26 +- crates/anvil/tests/it/pubsub.rs | 2 +- crates/anvil/tests/it/revert.rs | 54 +- crates/anvil/tests/it/sign.rs | 7 +- crates/anvil/tests/it/simulate.rs | 6 +- crates/anvil/tests/it/state.rs | 502 +- crates/anvil/tests/it/tempo.rs | 2790 +++++ crates/anvil/tests/it/traces.rs | 527 +- crates/anvil/tests/it/transaction.rs | 257 +- crates/anvil/tests/it/txpool.rs | 138 +- crates/anvil/tests/it/utils.rs | 4 +- crates/anvil/tests/it/wsapi.rs | 2 +- crates/cast/Cargo.toml | 51 +- crates/cast/src/args.rs | 245 +- crates/cast/src/base.rs | 80 +- crates/cast/src/call_spec.rs | 219 + crates/cast/src/cmd/access_list.rs | 88 +- crates/cast/src/cmd/artifact.rs | 21 +- crates/cast/src/cmd/b2e_payload.rs | 110 + crates/cast/src/cmd/batch_mktx.rs | 171 + crates/cast/src/cmd/batch_send.rs | 182 + crates/cast/src/cmd/call.rs | 520 +- crates/cast/src/cmd/constructor_args.rs | 46 +- crates/cast/src/cmd/create2.rs | 92 +- crates/cast/src/cmd/creation_code.rs | 57 +- crates/cast/src/cmd/da_estimate.rs | 64 +- crates/cast/src/cmd/erc20.rs | 608 ++ crates/cast/src/cmd/estimate.rs | 52 +- crates/cast/src/cmd/interface.rs | 60 +- crates/cast/src/cmd/keychain.rs | 1938 ++++ crates/cast/src/cmd/logs.rs | 87 +- crates/cast/src/cmd/miner.rs | 52 + crates/cast/src/cmd/mktx.rs | 104 +- crates/cast/src/cmd/mod.rs | 11 + crates/cast/src/cmd/rpc.rs | 12 +- crates/cast/src/cmd/run.rs | 305 +- crates/cast/src/cmd/send.rs | 361 +- crates/cast/src/cmd/storage.rs | 176 +- crates/cast/src/cmd/tempo.rs | 45 + crates/cast/src/cmd/tip20/create.rs | 113 + crates/cast/src/cmd/tip20/mine.rs | 254 + crates/cast/src/cmd/tip20/mod.rs | 111 + crates/cast/src/cmd/trace.rs | 89 + crates/cast/src/cmd/txpool.rs | 12 +- crates/cast/src/cmd/vaddr/create.rs | 181 + crates/cast/src/cmd/vaddr/mod.rs | 131 + crates/cast/src/cmd/vaddr/resolve.rs | 52 + crates/cast/src/cmd/vaddr/watch.rs | 108 + crates/cast/src/cmd/wallet/list.rs | 53 +- crates/cast/src/cmd/wallet/mod.rs | 460 +- crates/cast/src/cmd/wallet/vanity.rs | 15 +- crates/cast/src/debug.rs | 101 + crates/cast/src/errors.rs | 5 +- crates/cast/src/lib.rs | 1356 ++- crates/cast/src/opts.rs | 173 +- crates/cast/src/rlp_converter.rs | 4 +- crates/cast/src/tempo.rs | 3 + crates/cast/src/tx.rs | 670 +- crates/cast/tests/cli/erc20.rs | 608 ++ crates/cast/tests/cli/keychain.rs | 76 + crates/cast/tests/cli/main.rs | 2826 ++++- crates/cast/tests/cli/selectors.rs | 65 +- crates/cast/tests/fixtures/TestToken.sol | 62 + .../fixtures/interface_inherited_struct.json | 1 + crates/cheatcodes/Cargo.toml | 19 +- crates/cheatcodes/README.md | 1 + crates/cheatcodes/assets/cheatcodes.json | 724 +- .../cheatcodes/assets/cheatcodes.schema.json | 311 +- crates/cheatcodes/spec/Cargo.toml | 2 +- crates/cheatcodes/spec/src/cheatcode.rs | 20 +- crates/cheatcodes/spec/src/function.rs | 2 - crates/cheatcodes/spec/src/lib.rs | 19 +- crates/cheatcodes/spec/src/vm.rs | 347 +- crates/cheatcodes/src/base64.rs | 25 +- crates/cheatcodes/src/config.rs | 514 +- crates/cheatcodes/src/crypto.rs | 410 +- crates/cheatcodes/src/env.rs | 90 +- crates/cheatcodes/src/error.rs | 35 +- crates/cheatcodes/src/evm.rs | 1113 +- crates/cheatcodes/src/evm/fork.rs | 267 +- crates/cheatcodes/src/evm/mapping.rs | 99 +- crates/cheatcodes/src/evm/mock.rs | 64 +- crates/cheatcodes/src/evm/prank.rs | 57 +- .../cheatcodes/src/evm/record_debug_step.rs | 77 +- crates/cheatcodes/src/fs.rs | 561 +- crates/cheatcodes/src/inspector.rs | 1840 ++-- crates/cheatcodes/src/inspector/analysis.rs | 168 + crates/cheatcodes/src/inspector/utils.rs | 96 +- crates/cheatcodes/src/json.rs | 689 +- crates/cheatcodes/src/lib.rs | 101 +- crates/cheatcodes/src/script.rs | 186 +- crates/cheatcodes/src/string.rs | 56 +- crates/cheatcodes/src/test.rs | 68 +- crates/cheatcodes/src/test/assert.rs | 405 +- crates/cheatcodes/src/test/assume.rs | 29 +- crates/cheatcodes/src/test/expect.rs | 587 +- crates/cheatcodes/src/test/revert_handlers.rs | 171 +- crates/cheatcodes/src/toml.rs | 130 +- crates/cheatcodes/src/utils.rs | 309 +- crates/cheatcodes/src/version.rs | 76 +- crates/chisel/Cargo.toml | 33 +- crates/chisel/src/args.rs | 222 +- crates/chisel/src/cmd.rs | 287 +- crates/chisel/src/dispatcher.rs | 1256 +-- crates/chisel/src/executor.rs | 1965 ++-- crates/chisel/src/history.rs | 11 - crates/chisel/src/lib.rs | 23 +- crates/chisel/src/opts.rs | 29 +- crates/chisel/src/runner.rs | 130 +- crates/chisel/src/session.rs | 27 +- crates/chisel/src/session_source.rs | 685 -- crates/chisel/src/solidity_helper.rs | 188 +- crates/chisel/src/source.rs | 613 ++ crates/chisel/tests/cache.rs | 212 - crates/chisel/tests/it/main.rs | 2 + crates/chisel/tests/it/repl/mod.rs | 296 + crates/chisel/tests/it/repl/session.rs | 121 + crates/cli-markdown/Cargo.toml | 20 + crates/cli-markdown/src/lib.rs | 582 + crates/cli/Cargo.toml | 30 +- crates/cli/src/clap.rs | 44 + crates/cli/src/handler.rs | 19 +- crates/cli/src/lib.rs | 6 +- crates/cli/src/opts/build/core.rs | 57 +- crates/cli/src/opts/build/mod.rs | 5 +- crates/cli/src/opts/build/paths.rs | 6 +- crates/cli/src/opts/build/utils.rs | 225 + crates/cli/src/opts/chain.rs | 2 +- crates/cli/src/opts/dependency.rs | 51 +- crates/{common/src => cli/src/opts}/evm.rs | 156 +- crates/cli/src/opts/global.rs | 66 +- crates/cli/src/opts/mod.rs | 6 + crates/cli/src/opts/rpc.rs | 141 +- crates/cli/src/opts/rpc_common.rs | 119 + crates/cli/src/opts/tempo.rs | 423 + crates/cli/src/opts/transaction.rs | 56 +- crates/cli/src/utils/abi.rs | 44 +- crates/cli/src/utils/allocator.rs | 3 +- crates/cli/src/utils/cmd.rs | 328 +- crates/cli/src/utils/default_directives.txt | 11 + crates/cli/src/utils/mod.rs | 480 +- crates/cli/src/utils/suggestions.rs | 1 + crates/cli/src/utils/tempo.rs | 205 + crates/common/Cargo.toml | 47 +- crates/common/build.rs | 103 +- crates/common/fmt/Cargo.toml | 11 + crates/common/fmt/src/console.rs | 141 +- crates/common/fmt/src/dynamic.rs | 146 +- crates/common/fmt/src/exp.rs | 5 +- crates/common/fmt/src/lib.rs | 14 +- crates/common/fmt/src/ui.rs | 1816 ++-- crates/common/src/abi.rs | 93 +- crates/common/src/calc.rs | 8 +- crates/common/src/comments/comment.rs | 70 + crates/common/src/comments/inline_config.rs | 449 + crates/common/src/comments/mod.rs | 463 + crates/common/src/compile.rs | 129 +- crates/common/src/constants.rs | 36 +- crates/common/src/contracts.rs | 298 +- crates/common/src/errors/artifacts.rs | 12 - crates/common/src/errors/fs.rs | 97 +- crates/common/src/errors/mod.rs | 20 +- crates/common/src/fs.rs | 102 +- crates/common/src/io/macros.rs | 9 +- crates/common/src/io/shell.rs | 115 +- crates/common/src/io/stdin.rs | 10 +- crates/common/src/iter.rs | 31 + crates/common/src/lib.rs | 12 +- crates/common/src/mapping_slots.rs | 71 + crates/common/src/preprocessor/data.rs | 29 +- crates/common/src/preprocessor/deps.rs | 331 +- crates/common/src/preprocessor/mod.rs | 138 +- crates/common/src/provider/curl_transport.rs | 203 + crates/common/src/provider/mod.rs | 323 +- crates/common/src/provider/mpp/keys.rs | 507 + crates/common/src/provider/mpp/mod.rs | 10 + crates/common/src/provider/mpp/persist.rs | 277 + crates/common/src/provider/mpp/session.rs | 935 ++ crates/common/src/provider/mpp/transport.rs | 1740 +++ crates/common/src/provider/mpp/ws.rs | 496 + .../common/src/provider/runtime_transport.rs | 139 +- crates/common/src/reports.rs | 19 - crates/common/src/retry.rs | 6 +- crates/common/src/selectors.rs | 42 +- crates/common/src/serde_helpers.rs | 149 +- crates/common/src/slot_identifier.rs | 985 ++ crates/common/src/tempo/auth.rs | 494 + crates/common/src/tempo/keystore.rs | 164 + crates/common/src/tempo/mod.rs | 204 + crates/common/src/tempo/tests.rs | 55 + crates/common/src/term.rs | 38 +- crates/common/src/traits.rs | 70 +- crates/common/src/transactions.rs | 294 - crates/common/src/transactions/broadcast.rs | 136 + crates/common/src/transactions/builder.rs | 553 + crates/common/src/transactions/mod.rs | 9 + crates/common/src/transactions/receipt.rs | 154 + crates/common/src/utils.rs | 69 +- crates/common/src/version.rs | 7 +- crates/config/Cargo.toml | 17 +- crates/config/README.md | 255 +- crates/config/assets/config.schema.json | 6 + crates/config/spec/Cargo.toml | 28 + crates/config/spec/src/lib.rs | 63 + crates/config/src/cache.rs | 8 +- crates/config/src/compilation.rs | 35 +- crates/config/src/doc.rs | 5 + crates/config/src/endpoints.rs | 197 +- crates/config/src/error.rs | 23 +- crates/config/src/etherscan.rs | 232 +- crates/config/src/extend.rs | 76 + crates/config/src/filter.rs | 19 +- crates/config/src/fix.rs | 32 +- crates/config/src/fmt.rs | 184 +- crates/config/src/fs_permissions.rs | 101 +- crates/config/src/fuzz.rs | 137 +- crates/config/src/inline/mod.rs | 47 +- crates/config/src/inline/natspec.rs | 395 +- crates/config/src/invariant.rs | 43 +- crates/config/src/lib.rs | 2994 +++++- crates/config/src/lint.rs | 147 +- crates/config/src/macros.rs | 3 +- crates/config/src/providers/ext.rs | 309 +- crates/config/src/providers/remappings.rs | 180 +- crates/config/src/providers/warnings.rs | 266 +- crates/config/src/soldeer.rs | 6 +- crates/config/src/utils.rs | 92 +- crates/config/src/warning.rs | 47 +- crates/debugger/Cargo.toml | 12 +- crates/debugger/src/builder.rs | 10 +- crates/debugger/src/debugger.rs | 6 +- crates/debugger/src/dump.rs | 2 +- crates/debugger/src/lib.rs | 2 +- crates/debugger/src/node.rs | 11 +- crates/debugger/src/op.rs | 78 +- crates/debugger/src/tui/context.rs | 57 +- crates/debugger/src/tui/draw.rs | 93 +- crates/debugger/src/tui/mod.rs | 64 +- crates/doc/Cargo.toml | 9 +- crates/doc/README.md | 4 +- crates/doc/src/builder.rs | 343 +- crates/doc/src/document.rs | 20 +- crates/doc/src/helpers.rs | 2 +- crates/doc/src/lib.rs | 10 +- crates/doc/src/parser/comment.rs | 110 +- crates/doc/src/parser/error.rs | 4 +- crates/doc/src/parser/item.rs | 128 +- crates/doc/src/parser/mod.rs | 422 +- crates/doc/src/parser/source.rs | 172 + .../src/preprocessor/contract_inheritance.rs | 43 +- crates/doc/src/preprocessor/git_source.rs | 2 +- .../doc/src/preprocessor/infer_hyperlinks.rs | 61 +- crates/doc/src/preprocessor/inheritdoc.rs | 26 +- crates/doc/src/preprocessor/mod.rs | 10 +- crates/doc/src/writer/as_doc.rs | 152 +- crates/doc/src/writer/buf_writer.rs | 75 +- crates/doc/src/writer/traits.rs | 51 +- crates/evm/abi/src/Console.json | 2 +- crates/evm/abi/src/console.py | 8 +- crates/evm/abi/src/lib.rs | 2 +- crates/evm/core/Cargo.toml | 37 +- crates/evm/core/src/backend/cow.rs | 178 +- crates/evm/core/src/backend/diagnostic.rs | 6 +- crates/evm/core/src/backend/error.rs | 3 +- crates/evm/core/src/backend/in_memory_db.rs | 12 +- crates/evm/core/src/backend/mod.rs | 792 +- crates/evm/core/src/backend/snapshot.rs | 22 +- crates/evm/core/src/buffer.rs | 28 +- crates/evm/core/src/bytecode.rs | 248 + crates/evm/core/src/constants.rs | 19 +- crates/evm/core/src/decode.rs | 76 +- crates/evm/core/src/either_evm.rs | 276 - crates/evm/core/src/env.rs | 1048 +- crates/evm/core/src/evm.rs | 387 - crates/evm/core/src/evm/eth.rs | 103 + crates/evm/core/src/evm/mod.rs | 251 + crates/evm/core/src/evm/op.rs | 136 + crates/evm/core/src/evm/tempo.rs | 167 + crates/evm/core/src/fork/database.rs | 108 +- crates/evm/core/src/fork/init.rs | 96 - crates/evm/core/src/fork/mod.rs | 6 - crates/evm/core/src/fork/multi.rs | 352 +- crates/evm/core/src/hardfork.rs | 1 + crates/evm/core/src/ic.rs | 119 +- crates/evm/core/src/lib.rs | 50 +- crates/evm/core/src/opcodes.rs | 25 - crates/evm/core/src/opts.rs | 495 +- crates/evm/core/src/precompiles.rs | 39 +- crates/evm/core/src/state_snapshot.rs | 8 +- crates/evm/core/src/tempo.rs | 197 + crates/evm/core/src/utils.rs | 212 +- crates/evm/coverage/Cargo.toml | 5 + crates/evm/coverage/src/analysis.rs | 928 +- crates/evm/coverage/src/anchors.rs | 46 +- crates/evm/coverage/src/inspector.rs | 47 +- crates/evm/coverage/src/lib.rs | 168 +- crates/evm/evm/Cargo.toml | 25 +- crates/evm/evm/src/executors/builder.rs | 73 +- crates/evm/evm/src/executors/corpus.rs | 1391 +++ crates/evm/evm/src/executors/fuzz/mod.rs | 837 +- crates/evm/evm/src/executors/fuzz/types.rs | 14 +- .../evm/evm/src/executors/invariant/error.rs | 36 +- crates/evm/evm/src/executors/invariant/mod.rs | 945 +- .../evm/evm/src/executors/invariant/replay.rs | 143 +- .../evm/evm/src/executors/invariant/result.rs | 277 +- .../evm/evm/src/executors/invariant/shrink.rs | 540 +- crates/evm/evm/src/executors/mod.rs | 820 +- crates/evm/evm/src/executors/sancov.rs | 70 + crates/evm/evm/src/executors/trace.rs | 107 +- crates/evm/evm/src/inspectors/chisel_state.rs | 29 +- .../evm/evm/src/inspectors/custom_printer.rs | 37 +- crates/evm/evm/src/inspectors/logs.rs | 87 +- crates/evm/evm/src/inspectors/mod.rs | 5 +- .../evm/src/inspectors/revert_diagnostic.rs | 119 +- crates/evm/evm/src/inspectors/script.rs | 40 +- crates/evm/evm/src/inspectors/stack.rs | 1134 +- crates/evm/evm/src/inspectors/tempo_labels.rs | 45 + crates/evm/evm/src/lib.rs | 7 +- crates/evm/fuzz/Cargo.toml | 11 + crates/evm/fuzz/src/inspector.rs | 121 +- .../evm/fuzz/src/invariant/call_override.rs | 36 +- crates/evm/fuzz/src/invariant/filters.rs | 10 +- crates/evm/fuzz/src/invariant/mod.rs | 224 +- crates/evm/fuzz/src/lib.rs | 200 +- crates/evm/fuzz/src/strategies/calldata.rs | 13 +- crates/evm/fuzz/src/strategies/int.rs | 43 +- crates/evm/fuzz/src/strategies/invariants.rs | 89 +- crates/evm/fuzz/src/strategies/literals.rs | 357 + crates/evm/fuzz/src/strategies/mod.rs | 11 +- crates/evm/fuzz/src/strategies/mutators.rs | 729 ++ crates/evm/fuzz/src/strategies/param.rs | 551 +- crates/evm/fuzz/src/strategies/state.rs | 343 +- crates/evm/fuzz/src/strategies/uint.rs | 41 +- crates/evm/hardforks/Cargo.toml | 29 + crates/evm/hardforks/src/lib.rs | 415 + crates/evm/networks/Cargo.toml | 50 + crates/evm/networks/README.md | 58 + crates/evm/networks/src/celo/mod.rs | 1 + crates/evm/networks/src/celo/transfer.rs | 124 + crates/evm/networks/src/lib.rs | 404 + crates/evm/networks/src/optimism.rs | 25 + crates/evm/sancov/Cargo.toml | 14 + crates/evm/sancov/README.md | 48 + crates/evm/sancov/src/lib.rs | 258 + crates/evm/traces/Cargo.toml | 22 +- crates/evm/traces/src/backtrace/mod.rs | 419 + crates/evm/traces/src/backtrace/source_map.rs | 177 + crates/evm/traces/src/debug/mod.rs | 60 +- crates/evm/traces/src/debug/sources.rs | 66 +- crates/evm/traces/src/decoder/mod.rs | 600 +- crates/evm/traces/src/decoder/precompiles.rs | 573 +- crates/evm/traces/src/folded_stack_trace.rs | 56 +- crates/evm/traces/src/identifier/etherscan.rs | 269 - crates/evm/traces/src/identifier/external.rs | 512 + crates/evm/traces/src/identifier/local.rs | 40 +- crates/evm/traces/src/identifier/mod.rs | 42 +- .../evm/traces/src/identifier/signatures.rs | 100 +- crates/evm/traces/src/lib.rs | 206 +- crates/fmt/Cargo.toml | 18 +- crates/fmt/README.md | 296 +- crates/fmt/src/buffer.rs | 442 - crates/fmt/src/chunk.rs | 60 - crates/fmt/src/comments.rs | 456 - crates/fmt/src/formatter.rs | 3902 ------- crates/fmt/src/helpers.rs | 135 - crates/fmt/src/inline_config.rs | 155 - crates/fmt/src/lib.rs | 305 +- crates/fmt/src/macros.rs | 125 - crates/fmt/src/pp/convenience.rs | 164 + crates/fmt/src/pp/helpers.rs | 46 + crates/fmt/src/pp/mod.rs | 488 + crates/fmt/src/pp/ring.rs | 95 + crates/fmt/src/solang_ext/ast_eq.rs | 708 -- crates/fmt/src/solang_ext/loc.rs | 168 - crates/fmt/src/solang_ext/mod.rs | 29 - crates/fmt/src/solang_ext/safe_unwrap.rs | 52 - crates/fmt/src/state/common.rs | 960 ++ crates/fmt/src/state/mod.rs | 1191 +++ crates/fmt/src/state/sol.rs | 3129 ++++++ crates/fmt/src/state/yul.rs | 317 + crates/fmt/src/string.rs | 181 - crates/fmt/src/visit.rs | 637 -- crates/fmt/testdata/ArrayExpressions/fmt.sol | 7 +- crates/fmt/testdata/BlockComments/tab.fmt.sol | 26 + .../BlockCommentsFunction/tab.fmt.sol | 21 + crates/fmt/testdata/CommentEmptyLine/fmt.sol | 5 + .../testdata/CommentEmptyLine/original.sol | 6 + .../testdata/ConstructorModifierStyle/fmt.sol | 4 +- .../ConstructorModifierStyle/original.sol | 4 +- .../bracket-spacing.fmt.sol | 24 +- .../contract-new-lines.fmt.sol | 21 + .../fmt/testdata/ContractDefinition/fmt.sol | 21 + .../testdata/ContractDefinition/original.sol | 17 +- crates/fmt/testdata/DocComments/block.fmt.sol | 117 + crates/fmt/testdata/DocComments/fmt.sol | 4 + crates/fmt/testdata/DocComments/line.fmt.sol | 87 + crates/fmt/testdata/DocComments/original.sol | 10 +- crates/fmt/testdata/DocComments/tab.fmt.sol | 105 + .../DocComments/wrap-comments.fmt.sol | 49 +- .../EmitStatement/120.compact.fmt.sol | 48 + crates/fmt/testdata/EmitStatement/120.fmt.sol | 60 + crates/fmt/testdata/EmitStatement/fmt.sol | 39 +- .../fmt/testdata/EmitStatement/original.sol | 26 +- .../EnumDefinition/bracket-spacing.fmt.sol | 2 +- crates/fmt/testdata/EnumVariants/fmt.sol | 4 +- crates/fmt/testdata/ForStatement/fmt.sol | 11 +- crates/fmt/testdata/ForStatement/original.sol | 10 +- .../bracket-spacing.fmt.sol | 20 +- .../FunctionCallArgsStatement/fmt.sol | 11 +- .../FunctionCallArgsStatement/original.sol | 15 +- .../FunctionDefinition/all-params.fmt.sol | 20 +- .../testdata/FunctionDefinition/all.fmt.sol | 20 +- .../fmt/testdata/FunctionDefinition/fmt.sol | 17 +- .../testdata/FunctionDefinition/original.sol | 12 +- .../override-spacing.fmt.sol | 17 +- ...ms-first.fmt.sol => params-always.fmt.sol} | 20 +- .../FunctionDefinition/params-multi.fmt.sol | 18 +- .../fmt.sol | 5 +- crates/fmt/testdata/FunctionType/fmt.sol | 14 +- crates/fmt/testdata/FunctionType/original.sol | 8 + .../testdata/IfStatement/block-multi.fmt.sol | 35 +- .../testdata/IfStatement/block-single.fmt.sol | 21 +- crates/fmt/testdata/IfStatement/fmt.sol | 21 +- crates/fmt/testdata/IfStatement/original.sol | 27 +- crates/fmt/testdata/IfStatement2/120.fmt.sol | 28 + crates/fmt/testdata/IfStatement2/fmt.sol | 33 + crates/fmt/testdata/IfStatement2/original.sol | 21 +- .../ImportDirective/bracket-spacing.fmt.sol | 9 +- crates/fmt/testdata/ImportDirective/fmt.sol | 9 +- .../namespace-import-prefer-glob.fmt.sol | 26 + .../namespace-import-prefer-plain.fmt.sol | 26 + .../namespace-import-preserve.fmt.sol | 26 + .../fmt/testdata/ImportDirective/original.sol | 7 +- .../ImportDirective/preserve-quote.fmt.sol | 9 +- .../ImportDirective/single-quote.fmt.sol | 9 +- .../single_line_import.fmt.sol | 24 + crates/fmt/testdata/InlineDisable/fmt.sol | 49 +- .../fmt/testdata/InlineDisable/original.sol | 54 +- crates/fmt/testdata/LiteralExpression/fmt.sol | 4 +- .../testdata/LiteralExpression/original.sol | 4 +- .../LiteralExpression/preserve-quote.fmt.sol | 4 +- .../LiteralExpression/single-quote.fmt.sol | 4 +- crates/fmt/testdata/MappingType/fmt.sol | 4 +- .../NamedFunctionCallExpression/fmt.sol | 15 +- .../NamedFunctionCallExpression/original.sol | 10 +- crates/fmt/testdata/NonKeywords/fmt.sol | 17 +- crates/fmt/testdata/NonKeywords/original.sol | 13 +- .../testdata/NumberLiteralUnderscore/fmt.sol | 25 +- .../NumberLiteralUnderscore/original.sol | 25 +- .../NumberLiteralUnderscore/preserve.fmt.sol | 25 +- .../NumberLiteralUnderscore/remove.fmt.sol | 25 +- .../NumberLiteralUnderscore/thousands.fmt.sol | 29 +- .../testdata/OperatorExpressions/120.fmt.sol | 89 + .../fmt/testdata/OperatorExpressions/fmt.sol | 62 + .../testdata/OperatorExpressions/original.sol | 53 +- .../OperatorExpressions/pow-no-space.fmt.sol | 106 + crates/fmt/testdata/Repros/fmt.sol | 290 +- crates/fmt/testdata/Repros/original.sol | 266 + crates/fmt/testdata/Repros/sorted.fmt.sol | 430 + crates/fmt/testdata/Repros/tab.fmt.sol | 430 + crates/fmt/testdata/ReprosCalls/110.fmt.sol | 166 + crates/fmt/testdata/ReprosCalls/120.fmt.sol | 152 + crates/fmt/testdata/ReprosCalls/80.fmt.sol | 226 + .../ReprosCalls/consistent.120.fmt.sol | 165 + crates/fmt/testdata/ReprosCalls/original.sol | 148 + .../ReprosFunctionDefs/all.120.fmt.sol | 17 + .../testdata/ReprosFunctionDefs/original.sol | 5 + crates/fmt/testdata/ReturnStatement/fmt.sol | 4 +- .../fmt/testdata/ReturnStatement/original.sol | 6 +- .../bracket-spacing.fmt.sol | 38 + .../testdata/RevertNamedArgsStatement/fmt.sol | 14 +- .../RevertNamedArgsStatement/original.sol | 8 +- crates/fmt/testdata/RevertStatement/fmt.sol | 13 +- .../fmt/testdata/RevertStatement/original.sol | 14 +- crates/fmt/testdata/SimpleComments/fmt.sol | 41 + .../fmt/testdata/SimpleComments/original.sol | 42 +- .../SimpleComments/wrap-comments.fmt.sol | 51 +- crates/fmt/testdata/SortedImports/fmt.sol | 8 +- .../fmt/testdata/SortedImports/original.sol | 8 +- .../StructDefinition/bracket-spacing.fmt.sol | 2 +- .../testdata/StructDefinition/original.sol | 1 + crates/fmt/testdata/StructFieldAccess/fmt.sol | 46 + .../testdata/StructFieldAccess/original.sol | 43 + crates/fmt/testdata/ThisExpression/fmt.sol | 6 +- crates/fmt/testdata/TrailingComma/fmt.sol | 4 - .../fmt/testdata/TrailingComma/original.sol | 8 +- crates/fmt/testdata/TryStatement/fmt.sol | 34 +- crates/fmt/testdata/TryStatement/original.sol | 36 +- crates/fmt/testdata/UnitExpression/fmt.sol | 10 +- .../fmt/testdata/UnitExpression/original.sol | 3 +- .../bracket-spacing.fmt.sol | 45 + .../fmt/testdata/VariableAssignment/fmt.sol | 45 + .../testdata/VariableAssignment/original.sol | 29 + .../fmt/testdata/VariableDefinition/fmt.sol | 20 +- .../testdata/VariableDefinition/original.sol | 16 +- .../override-spacing.fmt.sol | 20 +- .../WhileStatement/block-multi.fmt.sol | 8 +- .../WhileStatement/block-single.fmt.sol | 8 +- crates/fmt/testdata/WhileStatement/fmt.sol | 8 +- .../fmt/testdata/WhileStatement/original.sol | 2 + crates/fmt/testdata/Yul/fmt.sol | 76 +- crates/fmt/testdata/Yul/original.sol | 62 +- crates/fmt/testdata/YulStrings/fmt.sol | 9 +- crates/fmt/testdata/YulStrings/original.sol | 9 +- .../YulStrings/preserve-quote.fmt.sol | 9 +- .../testdata/YulStrings/single-quote.fmt.sol | 9 +- crates/fmt/tests/formatter.rs | 395 +- crates/forge/Cargo.toml | 60 +- crates/forge/assets/.gitignoreTemplate | 6 + crates/forge/assets/README.md | 8 +- .../assets/solidity/CounterTemplate.s.sol | 19 + .../forge/assets/solidity/CounterTemplate.sol | 14 + .../assets/solidity/CounterTemplate.t.sol | 24 + .../assets/solidity/workflowTemplate.yml | 35 + crates/forge/assets/tempo/MailTemplate.s.sol | 33 + crates/forge/assets/tempo/MailTemplate.sol | 77 + crates/forge/assets/tempo/MailTemplate.t.sol | 175 + crates/forge/assets/tempo/README.md | 57 + .../forge/assets/tempo/workflowTemplate.yml | 37 + .../forge/assets/vyper/CounterTemplate.s.sol | 19 + .../forge/assets/vyper/CounterTemplate.t.sol | 24 + crates/forge/assets/vyper/CounterTemplate.vy | 11 + .../forge/assets/vyper/ICounterTemplate.sol | 8 + .../forge/assets/vyper/workflowTemplate.yml | 46 + crates/forge/src/args.rs | 78 +- crates/forge/src/cmd/bind.rs | 24 +- crates/forge/src/cmd/bind_json.rs | 621 +- crates/forge/src/cmd/build.rs | 255 +- crates/forge/src/cmd/cache.rs | 29 +- crates/forge/src/cmd/clone.rs | 565 +- crates/forge/src/cmd/compiler.rs | 30 +- crates/forge/src/cmd/config.rs | 8 +- crates/forge/src/cmd/coverage.rs | 184 +- crates/forge/src/cmd/create.rs | 469 +- crates/forge/src/cmd/doc/mod.rs | 41 +- crates/forge/src/cmd/doc/server.rs | 19 +- crates/forge/src/cmd/eip712.rs | 369 +- crates/forge/src/cmd/flatten.rs | 23 +- crates/forge/src/cmd/fmt.rs | 223 +- crates/forge/src/cmd/geiger.rs | 160 +- crates/forge/src/cmd/generate/mod.rs | 2 + crates/forge/src/cmd/init.rs | 175 +- crates/forge/src/cmd/inspect.rs | 486 +- crates/forge/src/cmd/install.rs | 215 +- crates/forge/src/cmd/lint.rs | 91 +- crates/forge/src/cmd/remove.rs | 22 +- crates/forge/src/cmd/selectors.rs | 137 +- crates/forge/src/cmd/snapshot.rs | 205 +- crates/forge/src/cmd/soldeer.rs | 2 +- crates/forge/src/cmd/test/filter.rs | 34 +- crates/forge/src/cmd/test/mod.rs | 662 +- crates/forge/src/cmd/test/summary.rs | 38 +- crates/forge/src/cmd/tree.rs | 4 +- crates/forge/src/cmd/update.rs | 189 +- crates/forge/src/cmd/watch.rs | 69 +- crates/forge/src/coverage.rs | 103 +- crates/forge/src/gas_report.rs | 86 +- crates/forge/src/lib.rs | 10 +- crates/forge/src/lockfile.rs | 441 + crates/forge/src/multi_runner.rs | 293 +- crates/forge/src/opts.rs | 15 +- crates/forge/src/progress.rs | 28 +- crates/forge/src/result.rs | 386 +- crates/forge/src/runner.rs | 810 +- crates/forge/tests/cli/backtrace.rs | 688 ++ crates/forge/tests/cli/bind.rs | 26 + crates/forge/tests/cli/bind_json.rs | 9 +- crates/forge/tests/cli/build.rs | 223 +- crates/forge/tests/cli/cache.rs | 1 + crates/forge/tests/cli/cmd.rs | 1241 ++- crates/forge/tests/cli/compiler.rs | 100 +- crates/forge/tests/cli/config.rs | 801 +- crates/forge/tests/cli/context.rs | 12 +- crates/forge/tests/cli/coverage.rs | 655 +- crates/forge/tests/cli/create.rs | 77 +- crates/forge/tests/cli/debug.rs | 8 +- crates/forge/tests/cli/doc.rs | 283 +- crates/forge/tests/cli/eip712.rs | 831 +- crates/forge/tests/cli/ext_integration.rs | 30 +- crates/forge/tests/cli/failure_assertions.rs | 171 +- crates/forge/tests/cli/fmt.rs | 164 + crates/forge/tests/cli/fmt_integration.rs | 27 + crates/forge/tests/cli/geiger.rs | 92 - crates/forge/tests/cli/inline_config.rs | 166 +- crates/forge/tests/cli/install.rs | 704 ++ crates/forge/tests/cli/json.rs | 66 + crates/forge/tests/cli/lint.rs | 1506 ++- crates/forge/tests/cli/lint/geiger.rs | 108 + crates/forge/tests/cli/main.rs | 9 +- crates/forge/tests/cli/multi_script.rs | 2 +- crates/forge/tests/cli/precompiles.rs | 234 + crates/forge/tests/cli/script.rs | 1098 +- crates/forge/tests/cli/soldeer.rs | 55 + crates/forge/tests/cli/svm.rs | 13 +- crates/forge/tests/cli/test_cmd/core.rs | 139 + crates/forge/tests/cli/test_cmd/fuzz.rs | 1015 ++ .../tests/cli/test_cmd/invariant/common.rs | 2680 +++++ .../forge/tests/cli/test_cmd/invariant/mod.rs | 1319 +++ .../tests/cli/test_cmd/invariant/storage.rs | 32 +- .../tests/cli/test_cmd/invariant/target.rs | 861 ++ crates/forge/tests/cli/test_cmd/logs.rs | 896 ++ .../cli/{test_cmd.rs => test_cmd/mod.rs} | 1268 ++- crates/forge/tests/cli/test_cmd/repros.rs | 970 ++ crates/forge/tests/cli/test_cmd/spec.rs | 180 + crates/forge/tests/cli/test_cmd/table.rs | 231 + crates/forge/tests/cli/test_cmd/trace.rs | 576 + crates/forge/tests/cli/test_optimizer.rs | 427 +- crates/forge/tests/cli/utils.rs | 10 +- crates/forge/tests/cli/verify.rs | 81 +- crates/forge/tests/cli/verify_bytecode.rs | 189 +- crates/forge/tests/fixtures/CreateXScript.sol | 165 + .../tests/fixtures/ExpectEmitFailures.t.sol | 3 +- .../fixtures/ExpectEmitParamFailures.t.sol | 114 + .../tests/fixtures/ExpectEmitParamHarness.sol | 79 + .../tests/fixtures/ExpectRevertFailures.t.sol | 86 +- .../SimpleContractTestNonVerbose.json | 46 +- .../fixtures/SimpleContractTestVerbose.json | 9362 ++++++++++++++++- .../tests/fixtures/backtraces/Backtrace.t.sol | 94 + .../fixtures/backtraces/DelegateCall.sol | 35 + .../fixtures/backtraces/ForkBacktrace.t.sol | 38 + .../backtraces/ForkedERC20Wrapper.sol | 33 + .../backtraces/LibraryBacktrace.t.sol | 74 + .../fixtures/backtraces/LibraryConsumer.sol | 77 + .../backtraces/MultipleLibraryBacktrace.t.sol | 33 + .../backtraces/MultipleLibraryConsumer.sol | 39 + .../tests/fixtures/backtraces/NestedCalls.sol | 27 + .../fixtures/backtraces/SimpleRevert.sol | 23 + .../tests/fixtures/backtraces/StaticCall.sol | 33 + .../backtraces/libraries/ExternalMathLib.sol | 35 + .../backtraces/libraries/InternalMathLib.sol | 35 + .../libraries/MultipleLibraries.sol | 54 + .../forge/tests/fixtures/colored_traces.svg | 52 +- .../forge/tests/fixtures/invariant_traces.svg | 82 + crates/forge/tests/it/cheats.rs | 82 - crates/forge/tests/it/config.rs | 170 - crates/forge/tests/it/core.rs | 827 -- crates/forge/tests/it/fork.rs | 129 - crates/forge/tests/it/fs.rs | 23 - crates/forge/tests/it/fuzz.rs | 279 - crates/forge/tests/it/inline.rs | 70 - crates/forge/tests/it/invariant.rs | 1335 --- crates/forge/tests/it/main.rs | 13 - crates/forge/tests/it/repros.rs | 415 - crates/forge/tests/it/spec.rs | 11 - crates/forge/tests/it/test_helpers.rs | 367 - crates/forge/tests/it/vyper.rs | 10 - crates/forge/tests/rpc-cache-keyfile | 5 - crates/linking/Cargo.toml | 7 +- crates/linking/src/lib.rs | 378 +- crates/lint/Cargo.toml | 11 +- crates/lint/README.md | 85 +- crates/lint/docs/README.md | 52 + crates/lint/docs/_template.md | 28 + crates/lint/docs/asm-keccak256.md | 42 + crates/lint/docs/block-timestamp.md | 44 + crates/lint/docs/boolean-cst.md | 37 + crates/lint/docs/boolean-equal.md | 34 + crates/lint/docs/could-be-immutable.md | 42 + crates/lint/docs/custom-errors.md | 45 + crates/lint/docs/divide-before-multiply.md | 32 + crates/lint/docs/erc20-unchecked-transfer.md | 43 + crates/lint/docs/incorrect-erc20-interface.md | 42 + .../lint/docs/incorrect-erc721-interface.md | 48 + crates/lint/docs/incorrect-shift.md | 37 + crates/lint/docs/inline-assembly.md | 69 + crates/lint/docs/interface-file-naming.md | 31 + crates/lint/docs/interface-naming.md | 31 + crates/lint/docs/missing-zero-check.md | 39 + crates/lint/docs/mixed-case-function.md | 32 + crates/lint/docs/mixed-case-variable.md | 36 + crates/lint/docs/multi-contract-file.md | 37 + crates/lint/docs/named-struct-fields.md | 31 + crates/lint/docs/pascal-case-struct.md | 31 + crates/lint/docs/pragma-inconsistent.md | 41 + crates/lint/docs/rtlo.md | 32 + .../lint/docs/screaming-snake-case-const.md | 30 + .../docs/screaming-snake-case-immutable.md | 31 + crates/lint/docs/too-many-digits.md | 32 + crates/lint/docs/tx-origin.md | 34 + crates/lint/docs/unaliased-plain-import.md | 34 + crates/lint/docs/unchecked-call.md | 34 + crates/lint/docs/unsafe-cheatcode.md | 35 + crates/lint/docs/unsafe-typecast.md | 40 + crates/lint/docs/unused-import.md | 40 + crates/lint/docs/unused-state-variables.md | 39 + crates/lint/docs/unwrapped-modifier-logic.md | 51 + crates/lint/docs/var-read-using-this.md | 70 + crates/lint/src/lib.rs | 3 +- crates/lint/src/linter.rs | 4 +- crates/lint/src/linter/early.rs | 204 + crates/lint/src/linter/late.rs | 406 + crates/lint/src/linter/mod.rs | 307 + crates/lint/src/linter/project.rs | 92 + crates/lint/src/sol/codesize/mod.rs | 6 + .../sol/codesize/unwrapped_modifier_logic.rs | 181 + crates/lint/src/sol/gas/custom_errors.rs | 53 + crates/lint/src/sol/gas/immutable.rs | 406 + crates/lint/src/sol/gas/keccak.rs | 98 +- crates/lint/src/sol/gas/mod.rs | 21 +- .../src/sol/gas/unused_state_variables.rs | 90 + .../lint/src/sol/gas/var_read_using_this.rs | 336 + crates/lint/src/sol/high/incorrect_shift.rs | 12 +- crates/lint/src/sol/high/mod.rs | 17 +- crates/lint/src/sol/high/rtlo.rs | 58 + crates/lint/src/sol/high/unchecked_calls.rs | 199 + crates/lint/src/sol/info/boolean_cst.rs | 116 + crates/lint/src/sol/info/boolean_equal.rs | 108 + crates/lint/src/sol/info/imports.rs | 171 + crates/lint/src/sol/info/inline_assembly.rs | 71 + crates/lint/src/sol/info/interface_naming.rs | 62 + crates/lint/src/sol/info/mixed_case.rs | 126 +- crates/lint/src/sol/info/mod.rs | 53 +- .../lint/src/sol/info/multi_contract_file.rs | 44 + .../lint/src/sol/info/named_struct_fields.rs | 74 + crates/lint/src/sol/info/pascal_case.rs | 29 +- crates/lint/src/sol/info/pragma_directive.rs | 71 + .../lint/src/sol/info/screaming_snake_case.rs | 45 +- crates/lint/src/sol/info/too_many_digits.rs | 50 + crates/lint/src/sol/info/unsafe_cheatcodes.rs | 36 + crates/lint/src/sol/low/block_timestamp.rs | 70 + crates/lint/src/sol/low/missing_zero_check.rs | 443 + crates/lint/src/sol/low/mod.rs | 12 + crates/lint/src/sol/macros.rs | 142 +- crates/lint/src/sol/med/div_mul.rs | 18 +- .../src/sol/med/incorrect_erc20_interface.rs | 112 + .../src/sol/med/incorrect_erc721_interface.rs | 121 + crates/lint/src/sol/med/mod.rs | 25 +- crates/lint/src/sol/med/tx_origin.rs | 101 + crates/lint/src/sol/med/unsafe_typecast.rs | 172 + crates/lint/src/sol/mod.rs | 535 +- crates/lint/testdata/.gitignore | 2 + crates/lint/testdata/BlockTimestamp.sol | 84 + crates/lint/testdata/BlockTimestamp.stderr | 96 + crates/lint/testdata/BooleanCst.sol | 25 + crates/lint/testdata/BooleanCst.stderr | 40 + crates/lint/testdata/BooleanEqual.sol | 24 + crates/lint/testdata/BooleanEqual.stderr | 56 + crates/lint/testdata/CouldBeImmutable.sol | 85 + crates/lint/testdata/CouldBeImmutable.stderr | 56 + crates/lint/testdata/CustomErrors.sol | 42 + crates/lint/testdata/CustomErrors.stderr | 40 + crates/lint/testdata/DivideBeforeMultiply.sol | 3 + .../lint/testdata/DivideBeforeMultiply.stderr | 72 +- crates/lint/testdata/Imports.sol | 80 + crates/lint/testdata/Imports.stderr | 104 + .../lint/testdata/IncorrectERC20Interface.sol | 44 + .../testdata/IncorrectERC20Interface.stderr | 120 + .../testdata/IncorrectERC721Interface.sol | 52 + .../testdata/IncorrectERC721Interface.stderr | 152 + crates/lint/testdata/IncorrectShift.sol | 2 +- crates/lint/testdata/IncorrectShift.stderr | 60 +- crates/lint/testdata/InlineAssembly.sol | 110 + crates/lint/testdata/InlineAssembly.stderr | 96 + crates/lint/testdata/Keccak256.sol | 81 +- crates/lint/testdata/Keccak256.stderr | 158 +- crates/lint/testdata/MissingZeroCheck.sol | 243 + crates/lint/testdata/MissingZeroCheck.stderr | 184 + crates/lint/testdata/MixedCase.sol | 35 +- crates/lint/testdata/MixedCase.stderr | 200 +- crates/lint/testdata/MultiContractFile.sol | 14 + crates/lint/testdata/MultiContractFile.stderr | 40 + .../MultiContractFile_InterfaceLibrary.sol | 14 + .../MultiContractFile_InterfaceLibrary.stderr | 24 + crates/lint/testdata/NamedStructFields.sol | 24 + crates/lint/testdata/NamedStructFields.stderr | 8 + .../PragmaInconsistentCaretAboveExact.sol | 7 + .../PragmaInconsistentCaretAboveExact.stderr | 16 + .../PragmaInconsistentCaretMatchesExact.sol | 7 + ...PragmaInconsistentCaretMatchesExact.stderr | 16 + .../PragmaInconsistentCaretVsTilde.sol | 7 + .../PragmaInconsistentCaretVsTilde.stderr | 16 + .../testdata/PragmaInconsistentOrVsExact.sol | 7 + .../PragmaInconsistentOrVsExact.stderr | 16 + .../PragmaInconsistentRangeVsExact.sol | 7 + .../PragmaInconsistentRangeVsExact.stderr | 16 + .../PragmaInconsistentThreeDistinct.sol | 8 + .../PragmaInconsistentThreeDistinct.stderr | 24 + crates/lint/testdata/Rtlo.sol | 81 + crates/lint/testdata/Rtlo.stderr | 192 + crates/lint/testdata/RtloCommentsOnly.sol | 15 + crates/lint/testdata/RtloCommentsOnly.stderr | 32 + crates/lint/testdata/ScreamingSnakeCase.sol | 4 +- .../lint/testdata/ScreamingSnakeCase.stderr | 96 +- crates/lint/testdata/SoloInterfaces.sol | 15 + crates/lint/testdata/StructPascalCase.sol | 4 +- crates/lint/testdata/StructPascalCase.stderr | 72 +- crates/lint/testdata/TooManyDigits.sol | 73 + crates/lint/testdata/TooManyDigits.stderr | 72 + crates/lint/testdata/TxOrigin.sol | 65 + crates/lint/testdata/TxOrigin.stderr | 72 + crates/lint/testdata/UncheckedCall.sol | 91 + crates/lint/testdata/UncheckedCall.stderr | 64 + .../lint/testdata/UncheckedTransferERC20.sol | 127 + .../testdata/UncheckedTransferERC20.stderr | 88 + crates/lint/testdata/UnsafeCheatcodes.sol | 66 + crates/lint/testdata/UnsafeCheatcodes.stderr | 104 + crates/lint/testdata/UnsafeTypecast.sol | 460 + crates/lint/testdata/UnsafeTypecast.stderr | 2298 ++++ crates/lint/testdata/UnusedStateVariables.sol | 52 + .../lint/testdata/UnusedStateVariables.stderr | 40 + .../lint/testdata/UnwrappedModifierLogic.sol | 177 + .../testdata/UnwrappedModifierLogic.stderr | 265 + crates/lint/testdata/VarReadUsingThis.sol | 286 + crates/lint/testdata/VarReadUsingThis.stderr | 275 + .../testdata/auxiliary/ImportsAnotherFile.sol | 6 + .../auxiliary/ImportsAnotherFile2.sol | 6 + .../testdata/auxiliary/ImportsConstants.sol | 5 + .../lint/testdata/auxiliary/ImportsFile.sol | 39 + .../testdata/auxiliary/ImportsSomeFile.sol | 9 + .../testdata/auxiliary/ImportsSomeFile2.sol | 7 + .../lint/testdata/auxiliary/ImportsTypes.sol | 6 + .../lint/testdata/auxiliary/ImportsUtils.sol | 10 + .../lint/testdata/auxiliary/ImportsUtils2.sol | 10 + crates/lint/testdata/auxiliary/Test.sol | 27 + crates/macros/src/cheatcodes.rs | 64 +- crates/macros/src/console_fmt.rs | 57 +- crates/macros/src/lib.rs | 4 +- crates/primitives/Cargo.toml | 45 + crates/primitives/src/lib.rs | 6 + crates/primitives/src/network/mod.rs | 64 + crates/primitives/src/network/optimism.rs | 47 + crates/primitives/src/network/receipt.rs | 173 + crates/primitives/src/transaction/envelope.rs | 619 ++ crates/primitives/src/transaction/mod.rs | 11 + crates/primitives/src/transaction/optimism.rs | 300 + crates/primitives/src/transaction/receipt.rs | 690 ++ crates/primitives/src/transaction/request.rs | 711 ++ crates/script-sequence/Cargo.toml | 8 +- crates/script-sequence/src/lib.rs | 1 + crates/script-sequence/src/reader.rs | 87 +- crates/script-sequence/src/sequence.rs | 142 +- crates/script-sequence/src/transaction.rs | 40 +- crates/script/Cargo.toml | 27 +- crates/script/src/broadcast.rs | 862 +- crates/script/src/build.rs | 140 +- crates/script/src/execute.rs | 124 +- crates/script/src/lib.rs | 467 +- crates/script/src/multi_sequence.rs | 54 +- crates/script/src/progress.rs | 96 +- crates/script/src/providers.rs | 34 +- crates/script/src/receipts.rs | 254 +- crates/script/src/runner.rs | 130 +- crates/script/src/sequence.rs | 64 +- crates/script/src/simulate.rs | 115 +- crates/script/src/transaction.rs | 46 +- crates/script/src/verify.rs | 73 +- crates/sol-macro-gen/Cargo.toml | 5 +- crates/sol-macro-gen/src/lib.rs | 1 + crates/sol-macro-gen/src/sol_macro_gen.rs | 87 +- crates/test-utils/Cargo.toml | 17 +- crates/test-utils/src/etherscan.rs | 32 + crates/test-utils/src/ext.rs | 183 + crates/test-utils/src/fd_lock.rs | 6 + crates/test-utils/src/filter.rs | 28 +- crates/test-utils/src/lib.rs | 51 +- crates/test-utils/src/macros.rs | 49 +- crates/test-utils/src/prj.rs | 880 ++ crates/test-utils/src/rpc.rs | 264 +- crates/test-utils/src/script.rs | 100 +- crates/test-utils/src/ui_runner.rs | 22 +- crates/test-utils/src/util.rs | 1086 +- crates/verify/Cargo.toml | 20 +- crates/verify/src/bytecode.rs | 198 +- crates/verify/src/etherscan/flatten.rs | 34 +- crates/verify/src/etherscan/mod.rs | 173 +- crates/verify/src/etherscan/standard_json.rs | 50 +- crates/verify/src/lib.rs | 4 +- crates/verify/src/provider.rs | 145 +- crates/verify/src/retry.rs | 4 +- crates/verify/src/sourcify.rs | 381 +- crates/verify/src/types.rs | 9 - crates/verify/src/utils.rs | 268 +- crates/verify/src/verify.rs | 222 +- crates/wallets/Cargo.toml | 55 - crates/wallets/src/error.rs | 55 - crates/wallets/src/lib.rs | 21 - crates/wallets/src/multi_wallet.rs | 521 - crates/wallets/src/raw_wallet.rs | 62 - crates/wallets/src/tempo.rs | 196 + crates/wallets/src/utils.rs | 163 - crates/wallets/src/wallet.rs | 214 - crates/wallets/src/wallet_signer.rs | 284 - deny.toml | 26 +- doc/doc-filelist.js | 1 + doc/doc-script.js | 228 + doc/doc-style.css | 403 + docs/dev/README.md | 5 + docs/dev/cheatcodes.md | 14 +- docs/dev/debugging.md | 4 +- docs/dev/lintrules.md | 149 +- docs/dev/networks.md | 6 + docs/dev/scripting.md | 2 - dprint.json | 60 + flake.lock | 105 +- flake.nix | 92 +- foundryup/README.md | 34 +- foundryup/foundryup | 452 +- foundryup/install | 4 +- npm/.env.example | 14 + npm/.gitignore | 41 + npm/@foundry-rs/anvil/README.md | 52 + npm/@foundry-rs/anvil/package.json | 42 + npm/@foundry-rs/cast/README.md | 53 + npm/@foundry-rs/cast/package.json | 42 + npm/@foundry-rs/chisel/README.md | 52 + npm/@foundry-rs/chisel/package.json | 42 + npm/@foundry-rs/forge/README.md | 52 + npm/@foundry-rs/forge/package.json | 42 + npm/README.md | 31 + npm/bun.lock | 57 + npm/bunfig.toml | 12 + npm/env.d.ts | 46 + npm/package.json | 17 + npm/scripts/check.sh | 286 + npm/scripts/prepublish.mjs | 263 + npm/scripts/publish-meta.mjs | 140 + npm/scripts/publish.mjs | 150 + npm/scripts/stage-from-artifact.mjs | 214 + npm/src/bin.mjs | 152 + npm/src/const.mjs | 113 + npm/src/generate-package-json.mjs | 108 + npm/src/install.mjs | 363 + npm/tsconfig.json | 50 + rustfmt.toml | 11 +- sleep.json | 955 ++ testdata/default/cheats/AccessList.t.sol | 8 +- testdata/default/cheats/Addr.t.sol | 7 +- .../default/cheats/ArbitraryStorage.t.sol | 22 +- testdata/default/cheats/Assert.t.sol | 18 +- testdata/default/cheats/Assume.t.sol | 7 +- testdata/default/cheats/AssumeNoRevert.t.sol | 18 +- testdata/default/cheats/AttachBlob.t.sol | 6 +- .../default/cheats/AttachDelegation.t.sol | 37 +- testdata/default/cheats/Bank.t.sol | 7 +- testdata/default/cheats/Base64.t.sol | 22 +- testdata/default/cheats/BlobBaseFee.t.sol | 7 +- testdata/default/cheats/Blobhashes.t.sol | 7 +- testdata/default/cheats/Broadcast.t.sol | 60 +- .../cheats/BroadcastRawTransaction.t.sol | 13 +- testdata/default/cheats/ChainId.t.sol | 7 +- testdata/default/cheats/CloneAccount.t.sol | 7 +- testdata/default/cheats/Cool.t.sol | 6 +- testdata/default/cheats/CopyStorage.t.sol | 10 +- testdata/default/cheats/CurrentFilePath.t.sol | 21 + testdata/default/cheats/Deal.t.sol | 7 +- testdata/default/cheats/DeployCode.t.sol | 7 +- testdata/default/cheats/Derive.t.sol | 7 +- testdata/default/cheats/Ed25519.t.sol | 105 + testdata/default/cheats/EnsNamehash.t.sol | 7 +- testdata/default/cheats/Env.t.sol | 7 +- testdata/default/cheats/Etch.t.sol | 7 +- .../default/cheats/ExecuteTransaction.t.sol | 215 + testdata/default/cheats/ExpectCall.t.sol | 18 +- testdata/default/cheats/ExpectCreate.t.sol | 6 +- testdata/default/cheats/ExpectEmit.t.sol | 14 +- testdata/default/cheats/ExpectRevert.t.sol | 158 +- testdata/default/cheats/Fee.t.sol | 7 +- testdata/default/cheats/Ffi.t.sol | 7 +- testdata/default/cheats/Fork.t.sol | 6 +- testdata/default/cheats/Fork2.t.sol | 154 +- testdata/default/cheats/Fs.t.sol | 14 +- testdata/default/cheats/GasMetering.t.sol | 8 +- testdata/default/cheats/GetArtifactPath.t.sol | 12 +- .../default/cheats/GetBlockTimestamp.t.sol | 7 +- testdata/default/cheats/GetChain.t.sol | 24 +- testdata/default/cheats/GetCode.t.sol | 8 +- testdata/default/cheats/GetDeployedCode.t.sol | 7 +- .../default/cheats/GetFoundryVersion.t.sol | 58 +- testdata/default/cheats/GetLabel.t.sol | 7 +- testdata/default/cheats/GetNonce.t.sol | 7 +- .../default/cheats/GetRawBlockHeader.t.sol | 15 + testdata/default/cheats/GetStorageSlots.t.sol | 91 + testdata/default/cheats/Json.t.sol | 50 +- testdata/default/cheats/Label.t.sol | 7 +- testdata/default/cheats/Load.t.sol | 6 +- testdata/default/cheats/Mapping.t.sol | 7 +- testdata/default/cheats/MemSafety.t.sol | 9 +- testdata/default/cheats/MockCall.t.sol | 52 +- testdata/default/cheats/MockCalls.t.sol | 11 +- testdata/default/cheats/MockFunction.t.sol | 184 +- testdata/default/cheats/Nonce.t.sol | 8 +- testdata/default/cheats/Parse.t.sol | 7 +- testdata/default/cheats/Prank.t.sol | 18 +- testdata/default/cheats/Prevrandao.t.sol | 7 +- testdata/default/cheats/ProjectRoot.t.sol | 20 +- testdata/default/cheats/Prompt.t.sol | 23 +- testdata/default/cheats/RandomAddress.t.sol | 7 +- testdata/default/cheats/RandomBytes.t.sol | 7 +- .../default/cheats/RandomCheatcodes.t.sol | 11 +- testdata/default/cheats/RandomUint.t.sol | 7 +- testdata/default/cheats/ReadCallers.t.sol | 7 +- testdata/default/cheats/Record.t.sol | 7 +- .../cheats/RecordAccountAccesses.t.sol | 233 +- .../default/cheats/RecordDebugTrace.t.sol | 40 +- testdata/default/cheats/RecordLogs.t.sol | 41 +- testdata/default/cheats/Remember.t.sol | 7 +- testdata/default/cheats/ResetNonce.t.sol | 6 +- testdata/default/cheats/Rlp.t.sol | 101 + testdata/default/cheats/Roll.t.sol | 7 +- testdata/default/cheats/RpcUrls.t.sol | 9 +- testdata/default/cheats/Seed.t.sol | 89 + testdata/default/cheats/SetBlockhash.t.sol | 7 +- testdata/default/cheats/SetNonce.t.sol | 6 +- testdata/default/cheats/SetNonceUnsafe.t.sol | 6 +- testdata/default/cheats/Setup.t.sol | 6 +- testdata/default/cheats/Shuffle.t.sol | 58 + testdata/default/cheats/Sign.t.sol | 63 +- testdata/default/cheats/SignP256.t.sol | 7 +- testdata/default/cheats/Skip.t.sol | 7 +- testdata/default/cheats/Sleep.t.sol | 7 +- testdata/default/cheats/Sort.t.sol | 7 +- .../default/cheats/StateDiffBytesString.t.sol | 217 + .../default/cheats/StateDiffMappings.t.sol | 247 + .../cheats/StateDiffStorageLayout.t.sol | 344 + .../default/cheats/StateDiffStructTest.t.sol | 256 + testdata/default/cheats/StateSnapshots.t.sol | 11 +- .../default/cheats/StorageSlotState.t.sol | 7 +- testdata/default/cheats/Store.t.sol | 6 +- testdata/default/cheats/StringUtils.t.sol | 7 +- testdata/default/cheats/ToString.t.sol | 7 +- testdata/default/cheats/Toml.t.sol | 53 +- testdata/default/cheats/Travel.t.sol | 7 +- testdata/default/cheats/TryFfi.sol | 7 +- testdata/default/cheats/UnixTime.t.sol | 7 +- testdata/default/cheats/Wallet.t.sol | 7 +- testdata/default/cheats/Warp.t.sol | 19 +- testdata/default/cheats/dumpState.t.sol | 7 +- testdata/default/cheats/getBlockNumber.t.sol | 7 +- testdata/default/cheats/loadAllocs.t.sol | 6 +- .../default/core/BadSigAfterInvariant.t.sol | 4 +- .../default/core/ContractEnvironment.t.sol | 4 +- .../core/FailingTestAfterFailedSetup.t.sol | 18 - testdata/default/core/LegacyAssertions.t.sol | 24 - testdata/default/core/PaymentFailure.t.sol | 19 - testdata/default/core/Reverting.t.sol | 7 +- testdata/default/core/SetupConsistency.t.sol | 4 +- testdata/default/fork/DssExecLib.sol | 4 +- testdata/default/fork/ForkSame_1.t.sol | 6 +- testdata/default/fork/ForkSame_2.t.sol | 6 +- testdata/default/fork/LaunchFork.t.sol | 21 +- testdata/default/fs/Disabled.t.sol | 23 +- .../fs/{Default.t.sol => ReadOnly.sol} | 14 +- testdata/default/fuzz/Fuzz.t.sol | 31 - testdata/default/fuzz/FuzzCollection.t.sol | 70 - .../default/fuzz/FuzzFailurePersist.t.sol | 29 - testdata/default/fuzz/FuzzInt.t.sol | 58 - testdata/default/fuzz/FuzzPositive.t.sol | 18 - testdata/default/fuzz/FuzzUint.t.sol | 46 - .../common/InvariantAfterInvariant.t.sol | 55 - .../invariant/common/InvariantAssume.t.sol | 23 - .../common/InvariantCalldataDictionary.t.sol | 95 - .../common/InvariantCustomError.t.sol | 35 - .../common/InvariantExcludedSenders.t.sol | 22 - .../invariant/common/InvariantFixtures.t.sol | 77 - .../common/InvariantHandlerFailure.t.sol | 35 - .../common/InvariantInnerContract.t.sol | 50 - .../common/InvariantPreserveState.t.sol | 49 - .../common/InvariantReentrancy.t.sol | 55 - .../invariant/common/InvariantRollFork.t.sol | 50 - .../common/InvariantScrapeValues.t.sol | 69 - .../common/InvariantSequenceNoReverts.t.sol | 25 - .../common/InvariantShrinkBigSequence.t.sol | 31 - .../common/InvariantShrinkFailOnRevert.t.sol | 26 - .../common/InvariantShrinkWithAssert.t.sol | 32 - .../invariant/common/InvariantTest1.t.sol | 39 - .../invariant/target/ExcludeContracts.t.sol | 31 - .../invariant/target/ExcludeSelectors.t.sol | 41 - .../invariant/target/ExcludeSenders.t.sol | 45 - .../target/FuzzedTargetContracts.t.sol | 66 - .../invariant/target/TargetContracts.t.sol | 32 - .../invariant/target/TargetInterfaces.t.sol | 72 - .../invariant/target/TargetSelectors.t.sol | 41 - .../fuzz/invariant/target/TargetSenders.t.sol | 31 - .../targetAbi/ExcludeArtifacts.t.sol | 45 - .../targetAbi/TargetArtifactSelectors.t.sol | 42 - .../targetAbi/TargetArtifactSelectors2.t.sol | 72 - .../invariant/targetAbi/TargetArtifacts.t.sol | 44 - testdata/default/inline/FuzzInlineConf.t.sol | 6 +- .../default/inline/InvariantInlineConf.t.sol | 6 +- .../default/linking/duplicate/Duplicate.t.sol | 4 +- testdata/default/linking/nested/Nested.t.sol | 4 +- .../default/linking/samefile_union/Libs.sol | 14 + .../samefile_union/SameFileUnion.t.sol | 18 + testdata/default/linking/simple/Simple.t.sol | 4 +- testdata/default/logs/DebugLogs.t.sol | 105 - testdata/default/logs/HardhatLogs.t.sol | 238 - testdata/default/repros/Issue10302.t.sol | 7 +- testdata/default/repros/Issue10477.t.sol | 7 +- testdata/default/repros/Issue10527.t.sol | 12 +- testdata/default/repros/Issue10552.t.sol | 7 +- testdata/default/repros/Issue10586.t.sol | 11 +- testdata/default/repros/Issue10957.t.sol | 24 + testdata/default/repros/Issue11353.t.sol | 37 + testdata/default/repros/Issue11616.t.sol | 29 + testdata/default/repros/Issue12075.t.sol | 32 + testdata/default/repros/Issue14212.t.sol | 56 + testdata/default/repros/Issue2623.t.sol | 7 +- testdata/default/repros/Issue2629.t.sol | 7 +- testdata/default/repros/Issue2723.t.sol | 7 +- testdata/default/repros/Issue2851.t.sol | 28 - testdata/default/repros/Issue2898.t.sol | 7 +- testdata/default/repros/Issue2956.t.sol | 6 +- testdata/default/repros/Issue2984.t.sol | 6 +- testdata/default/repros/Issue3055.t.sol | 36 - testdata/default/repros/Issue3077.t.sol | 7 +- testdata/default/repros/Issue3110.t.sol | 7 +- testdata/default/repros/Issue3119.t.sol | 7 +- testdata/default/repros/Issue3189.t.sol | 32 - testdata/default/repros/Issue3190.t.sol | 8 +- testdata/default/repros/Issue3192.t.sol | 6 +- testdata/default/repros/Issue3220.t.sol | 6 +- testdata/default/repros/Issue3221.t.sol | 6 +- testdata/default/repros/Issue3223.t.sol | 7 +- testdata/default/repros/Issue3347.t.sol | 14 - testdata/default/repros/Issue3437.t.sol | 19 - testdata/default/repros/Issue3596.t.sol | 31 - testdata/default/repros/Issue3653.t.sol | 6 +- testdata/default/repros/Issue3661.t.sol | 4 +- testdata/default/repros/Issue3674.t.sol | 10 +- testdata/default/repros/Issue3685.t.sol | 7 +- testdata/default/repros/Issue3703.t.sol | 9 +- testdata/default/repros/Issue3708.t.sol | 6 +- testdata/default/repros/Issue3723.t.sol | 18 - testdata/default/repros/Issue3753.t.sol | 7 +- testdata/default/repros/Issue3792.t.sol | 4 +- testdata/default/repros/Issue4232.t.sol | 7 +- testdata/default/repros/Issue4402.t.sol | 7 +- testdata/default/repros/Issue4586.t.sol | 10 +- testdata/default/repros/Issue4630.t.sol | 7 +- testdata/default/repros/Issue4640.t.sol | 13 +- testdata/default/repros/Issue4832.t.sol | 17 - testdata/default/repros/Issue5038.t.sol | 7 +- testdata/default/repros/Issue5529.t.sol | 16 +- testdata/default/repros/Issue5739.t.sol | 6 +- testdata/default/repros/Issue5808.t.sol | 7 +- testdata/default/repros/Issue5929.t.sol | 7 +- testdata/default/repros/Issue5935.t.sol | 7 +- testdata/default/repros/Issue5948.t.sol | 7 +- testdata/default/repros/Issue6006.t.sol | 7 +- testdata/default/repros/Issue6032.t.sol | 7 +- testdata/default/repros/Issue6070.t.sol | 7 +- testdata/default/repros/Issue6115.t.sol | 4 +- testdata/default/repros/Issue6170.t.sol | 28 - testdata/default/repros/Issue6180.t.sol | 7 +- testdata/default/repros/Issue6293.t.sol | 10 +- testdata/default/repros/Issue6355.t.sol | 39 - testdata/default/repros/Issue6437.t.sol | 7 +- testdata/default/repros/Issue6501.t.sol | 14 - testdata/default/repros/Issue6538.t.sol | 7 +- testdata/default/repros/Issue6554.t.sol | 11 +- testdata/default/repros/Issue6616.t.sol | 7 +- testdata/default/repros/Issue6634.t.sol | 9 +- testdata/default/repros/Issue6643.t.sol | 6 +- testdata/default/repros/Issue6759.t.sol | 7 +- testdata/default/repros/Issue6966.t.sol | 4 +- testdata/default/repros/Issue7238.t.sol | 11 +- testdata/default/repros/Issue7457.t.sol | 7 +- testdata/default/repros/Issue7481.t.sol | 10 +- testdata/default/repros/Issue8004.t.sol | 12 +- testdata/default/repros/Issue8006.t.sol | 6 +- testdata/default/repros/Issue8168.t.sol | 7 +- testdata/default/repros/Issue8277.t.sol | 7 +- testdata/default/repros/Issue8287.t.sol | 7 +- testdata/default/repros/Issue8383.t.sol | 322 - testdata/default/repros/Issue8566.t.sol | 7 +- testdata/default/repros/Issue8639.t.sol | 10 +- testdata/default/repros/Issue8971.t.sol | 6 +- testdata/default/repros/Issue9643.t.sol | 9 +- .../deploy.sol/31337/run-latest.json | 1 - testdata/default/script/deploy.sol | 38 - testdata/default/spec/ShanghaiCompat.t.sol | 7 +- .../default/trace/ConflictingSignatures.t.sol | 41 - testdata/default/trace/Trace.t.sol | 98 - testdata/default/vyper/CounterTest.vy | 4 +- testdata/fixtures/File/ignored/.gitignore | 1 + .../fixtures/SolidityGeneration/Fastlane.json | 1219 --- .../SolidityGeneration/GaugeController.json | 471 - .../SolidityGeneration/GeneratedFastLane.sol | 130 - .../GeneratedGaugeController.sol | 50 - .../GeneratedLiquidityGaugeV4.sol | 79 - .../GeneratedNamedInterface.sol | 7 - .../GeneratedUnnamedInterface.sol | 7 - .../SolidityGeneration/InterfaceABI.json | 64 - .../SolidityGeneration/LiquidityGaugeV4.json | 530 - .../SolidityGeneration/WithStructs.json | 1293 --- .../SolidityGeneration/WithStructs.sol | 138 - testdata/fixtures/Toml/test.toml | 6 + testdata/forge-std-rev | 2 +- testdata/foundry.toml | 97 +- testdata/multi-version/Counter.sol | 2 + testdata/multi-version/cheats/GetCode.t.sol | 7 +- testdata/multi-version/cheats/GetCode17.t.sol | 7 +- testdata/paris/cheats/Fork.t.sol | 6 +- testdata/paris/cheats/GasSnapshots.t.sol | 37 +- testdata/paris/cheats/LastCallGas.t.sol | 7 +- testdata/paris/core/BeforeTest.t.sol | 22 +- testdata/paris/fork/Transact.t.sol | 16 +- testdata/paris/spec/ShanghaiCompat.t.sol | 7 +- testdata/{default/vyper => src}/Counter.vy | 0 testdata/{default/vyper => src}/ICounter.vyi | 0 .../ds-test/src/test.sol => utils/DSTest.sol} | 0 testdata/utils/Test.sol | 11 + testdata/{cheats => utils}/Vm.sol | 173 +- testdata/{default/logs => utils}/console.sol | 0 typos.toml | 25 + 1406 files changed, 164180 insertions(+), 59900 deletions(-) create mode 100644 .circleci/cargo.yml create mode 100644 .circleci/ci-web3-gamefi.yml create mode 100644 .circleci/ci.yml create mode 100644 .circleci/ci_cargo.yml create mode 100644 .circleci/ci_v1.yml create mode 100644 .circleci/dev_stage.yml create mode 100644 .circleci/web3_defi_gamefi.yml create mode 100644 .codesandbox/tasks.json delete mode 100644 .codespellrc create mode 100644 .deps/remix-tests/remix_accounts.sol create mode 100644 .deps/remix-tests/remix_tests.sol delete mode 100644 .devcontainer/Dockerfile.dev delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/CRATE_CHECKS_FAILURE_TEMPLATE.md create mode 100644 .github/FLAKY_TEST_FAILURE_TEMPLATE.md create mode 100644 .github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/scripts/bump-tempo.sh create mode 100755 .github/scripts/combine-benchmarks.sh create mode 100644 .github/scripts/commit-benchmark-results.sh create mode 100755 .github/scripts/compare-nightly.sh create mode 100644 .github/scripts/contracts/BatchCounter.sol create mode 100644 .github/scripts/contracts/BatchRevertTest.s.sol.template create mode 100644 .github/scripts/contracts/BatchTest.s.sol.template create mode 100644 .github/scripts/contracts/CounterWithRequire.sol create mode 100644 .github/scripts/contracts/DeployAndCall.s.sol create mode 100755 .github/scripts/format-pr-comment.sh delete mode 100644 .github/scripts/move-tag.js delete mode 100644 .github/scripts/prune-prereleases.js create mode 100644 .github/scripts/read-benchmark-results.sh create mode 100755 .github/scripts/setup-foundryup.sh create mode 100755 .github/scripts/shellcheck.sh create mode 100644 .github/scripts/tempo-check.sh create mode 100644 .github/scripts/tempo-deploy.sh create mode 100644 .github/scripts/tempo-mpp.sh create mode 100644 .github/scripts/tempo-wallet.sh create mode 100644 .github/workflows/Docker.yml create mode 100644 .github/workflows/apisec-scan.yml create mode 100644 .github/workflows/benchmarks-nightly.yml create mode 100644 .github/workflows/benchmarks.yml create mode 100644 .github/workflows/bump-tempo.yml create mode 100644 .github/workflows/ci-mpp.yml create mode 100644 .github/workflows/ci-tempo.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/crate-checks.yml delete mode 100644 .github/workflows/dependencies.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/google.yml delete mode 100644 .github/workflows/nextest.yml create mode 100644 .github/workflows/nix.yml create mode 100644 .github/workflows/npm.yml create mode 100644 .github/workflows/snyk-container.yml create mode 100644 .github/workflows/static.yml create mode 100644 .github/workflows/test-flaky.yml create mode 100644 .gitmodules delete mode 100644 Dockerfile.cross create mode 100644 benches/Cargo.toml create mode 100644 benches/LATEST.md create mode 100644 benches/README.md create mode 100644 benches/src/lib.rs create mode 100644 benches/src/main.rs create mode 100644 benches/src/results.rs create mode 100755 benchmark.sh create mode 100644 counter/.gas-snapshot rename crates/forge/assets/workflowTemplate.yml => counter/.github/workflows/test.yml (94%) create mode 100644 counter/.gitignore create mode 100644 counter/README.md create mode 100644 counter/doc/doc-filelist.js create mode 100644 counter/doc/doc-script.js create mode 100644 counter/doc/doc-style.css create mode 100644 counter/foundry.toml create mode 160000 counter/lib/forge-std create mode 160000 counter/lib/openzeppelin-contracts create mode 100644 counter/remappings.txt rename crates/forge/assets/CounterTemplate.s.sol => counter/script/Counter.s.sol (100%) create mode 100644 counter/soldeer.lock rename crates/forge/assets/CounterTemplate.sol => counter/src/Counter.sol (100%) rename crates/forge/assets/CounterTemplate.t.sol => counter/test/Counter.t.sol (100%) rename crates/anvil/src/{server => }/error.rs (100%) delete mode 100644 crates/anvil/src/eth/backend/env.rs create mode 100644 crates/anvil/src/eth/backend/mem/optimism.rs create mode 100644 crates/anvil/src/eth/backend/tempo.rs rename crates/anvil/src/eth/{error.rs => error/mod.rs} (74%) create mode 100644 crates/anvil/src/eth/error/optimism.rs delete mode 100644 crates/anvil/src/evm.rs create mode 100644 crates/anvil/src/evm/mod.rs create mode 100644 crates/anvil/src/evm/optimism.rs delete mode 100644 crates/anvil/src/hardfork.rs create mode 100644 crates/anvil/src/server/beacon/error.rs create mode 100644 crates/anvil/src/server/beacon/handlers.rs create mode 100644 crates/anvil/src/server/beacon/mod.rs create mode 100644 crates/anvil/src/server/beacon/utils.rs rename crates/anvil/src/server/{handler.rs => rpc_handlers.rs} (88%) create mode 100644 crates/anvil/tests/it/beacon_api.rs create mode 100644 crates/anvil/tests/it/eip2935.rs create mode 100644 crates/anvil/tests/it/tempo.rs create mode 100644 crates/cast/src/call_spec.rs create mode 100644 crates/cast/src/cmd/b2e_payload.rs create mode 100644 crates/cast/src/cmd/batch_mktx.rs create mode 100644 crates/cast/src/cmd/batch_send.rs create mode 100644 crates/cast/src/cmd/erc20.rs create mode 100644 crates/cast/src/cmd/keychain.rs create mode 100644 crates/cast/src/cmd/miner.rs create mode 100644 crates/cast/src/cmd/tempo.rs create mode 100644 crates/cast/src/cmd/tip20/create.rs create mode 100644 crates/cast/src/cmd/tip20/mine.rs create mode 100644 crates/cast/src/cmd/tip20/mod.rs create mode 100644 crates/cast/src/cmd/trace.rs create mode 100644 crates/cast/src/cmd/vaddr/create.rs create mode 100644 crates/cast/src/cmd/vaddr/mod.rs create mode 100644 crates/cast/src/cmd/vaddr/resolve.rs create mode 100644 crates/cast/src/cmd/vaddr/watch.rs create mode 100644 crates/cast/src/debug.rs create mode 100644 crates/cast/src/tempo.rs create mode 100644 crates/cast/tests/cli/erc20.rs create mode 100644 crates/cast/tests/cli/keychain.rs create mode 100644 crates/cast/tests/fixtures/TestToken.sol create mode 100644 crates/cast/tests/fixtures/interface_inherited_struct.json create mode 100644 crates/cheatcodes/src/inspector/analysis.rs delete mode 100644 crates/chisel/src/history.rs delete mode 100644 crates/chisel/src/session_source.rs create mode 100644 crates/chisel/src/source.rs delete mode 100644 crates/chisel/tests/cache.rs create mode 100644 crates/chisel/tests/it/main.rs create mode 100644 crates/chisel/tests/it/repl/mod.rs create mode 100644 crates/chisel/tests/it/repl/session.rs create mode 100644 crates/cli-markdown/Cargo.toml create mode 100644 crates/cli-markdown/src/lib.rs create mode 100644 crates/cli/src/clap.rs create mode 100644 crates/cli/src/opts/build/utils.rs rename crates/{common/src => cli/src/opts}/evm.rs (70%) create mode 100644 crates/cli/src/opts/rpc_common.rs create mode 100644 crates/cli/src/opts/tempo.rs create mode 100644 crates/cli/src/utils/default_directives.txt create mode 100644 crates/cli/src/utils/tempo.rs create mode 100644 crates/common/src/comments/comment.rs create mode 100644 crates/common/src/comments/inline_config.rs create mode 100644 crates/common/src/comments/mod.rs delete mode 100644 crates/common/src/errors/artifacts.rs create mode 100644 crates/common/src/iter.rs create mode 100644 crates/common/src/mapping_slots.rs create mode 100644 crates/common/src/provider/curl_transport.rs create mode 100644 crates/common/src/provider/mpp/keys.rs create mode 100644 crates/common/src/provider/mpp/mod.rs create mode 100644 crates/common/src/provider/mpp/persist.rs create mode 100644 crates/common/src/provider/mpp/session.rs create mode 100644 crates/common/src/provider/mpp/transport.rs create mode 100644 crates/common/src/provider/mpp/ws.rs delete mode 100644 crates/common/src/reports.rs create mode 100644 crates/common/src/slot_identifier.rs create mode 100644 crates/common/src/tempo/auth.rs create mode 100644 crates/common/src/tempo/keystore.rs create mode 100644 crates/common/src/tempo/mod.rs create mode 100644 crates/common/src/tempo/tests.rs delete mode 100644 crates/common/src/transactions.rs create mode 100644 crates/common/src/transactions/broadcast.rs create mode 100644 crates/common/src/transactions/builder.rs create mode 100644 crates/common/src/transactions/mod.rs create mode 100644 crates/common/src/transactions/receipt.rs create mode 100644 crates/config/assets/config.schema.json create mode 100644 crates/config/spec/Cargo.toml create mode 100644 crates/config/spec/src/lib.rs create mode 100644 crates/config/src/extend.rs create mode 100644 crates/doc/src/parser/source.rs create mode 100644 crates/evm/core/src/bytecode.rs delete mode 100644 crates/evm/core/src/either_evm.rs delete mode 100644 crates/evm/core/src/evm.rs create mode 100644 crates/evm/core/src/evm/eth.rs create mode 100644 crates/evm/core/src/evm/mod.rs create mode 100644 crates/evm/core/src/evm/op.rs create mode 100644 crates/evm/core/src/evm/tempo.rs delete mode 100644 crates/evm/core/src/fork/init.rs create mode 100644 crates/evm/core/src/hardfork.rs delete mode 100644 crates/evm/core/src/opcodes.rs create mode 100644 crates/evm/core/src/tempo.rs create mode 100644 crates/evm/evm/src/executors/corpus.rs create mode 100644 crates/evm/evm/src/executors/sancov.rs create mode 100644 crates/evm/evm/src/inspectors/tempo_labels.rs create mode 100644 crates/evm/fuzz/src/strategies/literals.rs create mode 100644 crates/evm/fuzz/src/strategies/mutators.rs create mode 100644 crates/evm/hardforks/Cargo.toml create mode 100644 crates/evm/hardforks/src/lib.rs create mode 100644 crates/evm/networks/Cargo.toml create mode 100644 crates/evm/networks/README.md create mode 100644 crates/evm/networks/src/celo/mod.rs create mode 100644 crates/evm/networks/src/celo/transfer.rs create mode 100644 crates/evm/networks/src/lib.rs create mode 100644 crates/evm/networks/src/optimism.rs create mode 100644 crates/evm/sancov/Cargo.toml create mode 100644 crates/evm/sancov/README.md create mode 100644 crates/evm/sancov/src/lib.rs create mode 100644 crates/evm/traces/src/backtrace/mod.rs create mode 100644 crates/evm/traces/src/backtrace/source_map.rs delete mode 100644 crates/evm/traces/src/identifier/etherscan.rs create mode 100644 crates/evm/traces/src/identifier/external.rs delete mode 100644 crates/fmt/src/buffer.rs delete mode 100644 crates/fmt/src/chunk.rs delete mode 100644 crates/fmt/src/comments.rs delete mode 100644 crates/fmt/src/formatter.rs delete mode 100644 crates/fmt/src/helpers.rs delete mode 100644 crates/fmt/src/inline_config.rs delete mode 100644 crates/fmt/src/macros.rs create mode 100644 crates/fmt/src/pp/convenience.rs create mode 100644 crates/fmt/src/pp/helpers.rs create mode 100644 crates/fmt/src/pp/mod.rs create mode 100644 crates/fmt/src/pp/ring.rs delete mode 100644 crates/fmt/src/solang_ext/ast_eq.rs delete mode 100644 crates/fmt/src/solang_ext/loc.rs delete mode 100644 crates/fmt/src/solang_ext/mod.rs delete mode 100644 crates/fmt/src/solang_ext/safe_unwrap.rs create mode 100644 crates/fmt/src/state/common.rs create mode 100644 crates/fmt/src/state/mod.rs create mode 100644 crates/fmt/src/state/sol.rs create mode 100644 crates/fmt/src/state/yul.rs delete mode 100644 crates/fmt/src/string.rs delete mode 100644 crates/fmt/src/visit.rs create mode 100644 crates/fmt/testdata/BlockComments/tab.fmt.sol create mode 100644 crates/fmt/testdata/BlockCommentsFunction/tab.fmt.sol create mode 100644 crates/fmt/testdata/CommentEmptyLine/fmt.sol create mode 100644 crates/fmt/testdata/CommentEmptyLine/original.sol create mode 100644 crates/fmt/testdata/DocComments/block.fmt.sol create mode 100644 crates/fmt/testdata/DocComments/line.fmt.sol create mode 100644 crates/fmt/testdata/DocComments/tab.fmt.sol create mode 100644 crates/fmt/testdata/EmitStatement/120.compact.fmt.sol create mode 100644 crates/fmt/testdata/EmitStatement/120.fmt.sol rename crates/fmt/testdata/FunctionDefinition/{params-first.fmt.sol => params-always.fmt.sol} (97%) create mode 100644 crates/fmt/testdata/IfStatement2/120.fmt.sol create mode 100644 crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol create mode 100644 crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol create mode 100644 crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol create mode 100644 crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol create mode 100644 crates/fmt/testdata/OperatorExpressions/120.fmt.sol create mode 100644 crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol create mode 100644 crates/fmt/testdata/Repros/sorted.fmt.sol create mode 100644 crates/fmt/testdata/Repros/tab.fmt.sol create mode 100644 crates/fmt/testdata/ReprosCalls/110.fmt.sol create mode 100644 crates/fmt/testdata/ReprosCalls/120.fmt.sol create mode 100644 crates/fmt/testdata/ReprosCalls/80.fmt.sol create mode 100644 crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol create mode 100644 crates/fmt/testdata/ReprosCalls/original.sol create mode 100644 crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol create mode 100644 crates/fmt/testdata/ReprosFunctionDefs/original.sol create mode 100644 crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol create mode 100644 crates/fmt/testdata/StructFieldAccess/fmt.sol create mode 100644 crates/fmt/testdata/StructFieldAccess/original.sol create mode 100644 crates/forge/assets/solidity/CounterTemplate.s.sol create mode 100644 crates/forge/assets/solidity/CounterTemplate.sol create mode 100644 crates/forge/assets/solidity/CounterTemplate.t.sol create mode 100644 crates/forge/assets/solidity/workflowTemplate.yml create mode 100644 crates/forge/assets/tempo/MailTemplate.s.sol create mode 100644 crates/forge/assets/tempo/MailTemplate.sol create mode 100644 crates/forge/assets/tempo/MailTemplate.t.sol create mode 100644 crates/forge/assets/tempo/README.md create mode 100644 crates/forge/assets/tempo/workflowTemplate.yml create mode 100644 crates/forge/assets/vyper/CounterTemplate.s.sol create mode 100644 crates/forge/assets/vyper/CounterTemplate.t.sol create mode 100644 crates/forge/assets/vyper/CounterTemplate.vy create mode 100644 crates/forge/assets/vyper/ICounterTemplate.sol create mode 100644 crates/forge/assets/vyper/workflowTemplate.yml create mode 100644 crates/forge/src/lockfile.rs create mode 100644 crates/forge/tests/cli/backtrace.rs create mode 100644 crates/forge/tests/cli/bind.rs create mode 100644 crates/forge/tests/cli/fmt.rs create mode 100644 crates/forge/tests/cli/fmt_integration.rs delete mode 100644 crates/forge/tests/cli/geiger.rs create mode 100644 crates/forge/tests/cli/install.rs create mode 100644 crates/forge/tests/cli/json.rs create mode 100644 crates/forge/tests/cli/lint/geiger.rs create mode 100644 crates/forge/tests/cli/precompiles.rs create mode 100644 crates/forge/tests/cli/test_cmd/core.rs create mode 100644 crates/forge/tests/cli/test_cmd/fuzz.rs create mode 100644 crates/forge/tests/cli/test_cmd/invariant/common.rs create mode 100644 crates/forge/tests/cli/test_cmd/invariant/mod.rs rename testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol => crates/forge/tests/cli/test_cmd/invariant/storage.rs (59%) create mode 100644 crates/forge/tests/cli/test_cmd/invariant/target.rs create mode 100644 crates/forge/tests/cli/test_cmd/logs.rs rename crates/forge/tests/cli/{test_cmd.rs => test_cmd/mod.rs} (76%) create mode 100644 crates/forge/tests/cli/test_cmd/repros.rs create mode 100644 crates/forge/tests/cli/test_cmd/spec.rs create mode 100644 crates/forge/tests/cli/test_cmd/table.rs create mode 100644 crates/forge/tests/cli/test_cmd/trace.rs create mode 100644 crates/forge/tests/fixtures/CreateXScript.sol create mode 100644 crates/forge/tests/fixtures/ExpectEmitParamFailures.t.sol create mode 100644 crates/forge/tests/fixtures/ExpectEmitParamHarness.sol create mode 100644 crates/forge/tests/fixtures/backtraces/Backtrace.t.sol create mode 100644 crates/forge/tests/fixtures/backtraces/DelegateCall.sol create mode 100644 crates/forge/tests/fixtures/backtraces/ForkBacktrace.t.sol create mode 100644 crates/forge/tests/fixtures/backtraces/ForkedERC20Wrapper.sol create mode 100644 crates/forge/tests/fixtures/backtraces/LibraryBacktrace.t.sol create mode 100644 crates/forge/tests/fixtures/backtraces/LibraryConsumer.sol create mode 100644 crates/forge/tests/fixtures/backtraces/MultipleLibraryBacktrace.t.sol create mode 100644 crates/forge/tests/fixtures/backtraces/MultipleLibraryConsumer.sol create mode 100644 crates/forge/tests/fixtures/backtraces/NestedCalls.sol create mode 100644 crates/forge/tests/fixtures/backtraces/SimpleRevert.sol create mode 100644 crates/forge/tests/fixtures/backtraces/StaticCall.sol create mode 100644 crates/forge/tests/fixtures/backtraces/libraries/ExternalMathLib.sol create mode 100644 crates/forge/tests/fixtures/backtraces/libraries/InternalMathLib.sol create mode 100644 crates/forge/tests/fixtures/backtraces/libraries/MultipleLibraries.sol create mode 100644 crates/forge/tests/fixtures/invariant_traces.svg delete mode 100644 crates/forge/tests/it/cheats.rs delete mode 100644 crates/forge/tests/it/config.rs delete mode 100644 crates/forge/tests/it/core.rs delete mode 100644 crates/forge/tests/it/fork.rs delete mode 100644 crates/forge/tests/it/fs.rs delete mode 100644 crates/forge/tests/it/fuzz.rs delete mode 100644 crates/forge/tests/it/inline.rs delete mode 100644 crates/forge/tests/it/invariant.rs delete mode 100644 crates/forge/tests/it/main.rs delete mode 100644 crates/forge/tests/it/repros.rs delete mode 100644 crates/forge/tests/it/spec.rs delete mode 100644 crates/forge/tests/it/test_helpers.rs delete mode 100644 crates/forge/tests/it/vyper.rs delete mode 100644 crates/forge/tests/rpc-cache-keyfile create mode 100644 crates/lint/docs/README.md create mode 100644 crates/lint/docs/_template.md create mode 100644 crates/lint/docs/asm-keccak256.md create mode 100644 crates/lint/docs/block-timestamp.md create mode 100644 crates/lint/docs/boolean-cst.md create mode 100644 crates/lint/docs/boolean-equal.md create mode 100644 crates/lint/docs/could-be-immutable.md create mode 100644 crates/lint/docs/custom-errors.md create mode 100644 crates/lint/docs/divide-before-multiply.md create mode 100644 crates/lint/docs/erc20-unchecked-transfer.md create mode 100644 crates/lint/docs/incorrect-erc20-interface.md create mode 100644 crates/lint/docs/incorrect-erc721-interface.md create mode 100644 crates/lint/docs/incorrect-shift.md create mode 100644 crates/lint/docs/inline-assembly.md create mode 100644 crates/lint/docs/interface-file-naming.md create mode 100644 crates/lint/docs/interface-naming.md create mode 100644 crates/lint/docs/missing-zero-check.md create mode 100644 crates/lint/docs/mixed-case-function.md create mode 100644 crates/lint/docs/mixed-case-variable.md create mode 100644 crates/lint/docs/multi-contract-file.md create mode 100644 crates/lint/docs/named-struct-fields.md create mode 100644 crates/lint/docs/pascal-case-struct.md create mode 100644 crates/lint/docs/pragma-inconsistent.md create mode 100644 crates/lint/docs/rtlo.md create mode 100644 crates/lint/docs/screaming-snake-case-const.md create mode 100644 crates/lint/docs/screaming-snake-case-immutable.md create mode 100644 crates/lint/docs/too-many-digits.md create mode 100644 crates/lint/docs/tx-origin.md create mode 100644 crates/lint/docs/unaliased-plain-import.md create mode 100644 crates/lint/docs/unchecked-call.md create mode 100644 crates/lint/docs/unsafe-cheatcode.md create mode 100644 crates/lint/docs/unsafe-typecast.md create mode 100644 crates/lint/docs/unused-import.md create mode 100644 crates/lint/docs/unused-state-variables.md create mode 100644 crates/lint/docs/unwrapped-modifier-logic.md create mode 100644 crates/lint/docs/var-read-using-this.md create mode 100644 crates/lint/src/linter/early.rs create mode 100644 crates/lint/src/linter/late.rs create mode 100644 crates/lint/src/linter/mod.rs create mode 100644 crates/lint/src/linter/project.rs create mode 100644 crates/lint/src/sol/codesize/mod.rs create mode 100644 crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs create mode 100644 crates/lint/src/sol/gas/custom_errors.rs create mode 100644 crates/lint/src/sol/gas/immutable.rs create mode 100644 crates/lint/src/sol/gas/unused_state_variables.rs create mode 100644 crates/lint/src/sol/gas/var_read_using_this.rs create mode 100644 crates/lint/src/sol/high/rtlo.rs create mode 100644 crates/lint/src/sol/high/unchecked_calls.rs create mode 100644 crates/lint/src/sol/info/boolean_cst.rs create mode 100644 crates/lint/src/sol/info/boolean_equal.rs create mode 100644 crates/lint/src/sol/info/imports.rs create mode 100644 crates/lint/src/sol/info/inline_assembly.rs create mode 100644 crates/lint/src/sol/info/interface_naming.rs create mode 100644 crates/lint/src/sol/info/multi_contract_file.rs create mode 100644 crates/lint/src/sol/info/named_struct_fields.rs create mode 100644 crates/lint/src/sol/info/pragma_directive.rs create mode 100644 crates/lint/src/sol/info/too_many_digits.rs create mode 100644 crates/lint/src/sol/info/unsafe_cheatcodes.rs create mode 100644 crates/lint/src/sol/low/block_timestamp.rs create mode 100644 crates/lint/src/sol/low/missing_zero_check.rs create mode 100644 crates/lint/src/sol/low/mod.rs create mode 100644 crates/lint/src/sol/med/incorrect_erc20_interface.rs create mode 100644 crates/lint/src/sol/med/incorrect_erc721_interface.rs create mode 100644 crates/lint/src/sol/med/tx_origin.rs create mode 100644 crates/lint/src/sol/med/unsafe_typecast.rs create mode 100644 crates/lint/testdata/.gitignore create mode 100644 crates/lint/testdata/BlockTimestamp.sol create mode 100644 crates/lint/testdata/BlockTimestamp.stderr create mode 100644 crates/lint/testdata/BooleanCst.sol create mode 100644 crates/lint/testdata/BooleanCst.stderr create mode 100644 crates/lint/testdata/BooleanEqual.sol create mode 100644 crates/lint/testdata/BooleanEqual.stderr create mode 100644 crates/lint/testdata/CouldBeImmutable.sol create mode 100644 crates/lint/testdata/CouldBeImmutable.stderr create mode 100644 crates/lint/testdata/CustomErrors.sol create mode 100644 crates/lint/testdata/CustomErrors.stderr create mode 100644 crates/lint/testdata/Imports.sol create mode 100644 crates/lint/testdata/Imports.stderr create mode 100644 crates/lint/testdata/IncorrectERC20Interface.sol create mode 100644 crates/lint/testdata/IncorrectERC20Interface.stderr create mode 100644 crates/lint/testdata/IncorrectERC721Interface.sol create mode 100644 crates/lint/testdata/IncorrectERC721Interface.stderr create mode 100644 crates/lint/testdata/InlineAssembly.sol create mode 100644 crates/lint/testdata/InlineAssembly.stderr create mode 100644 crates/lint/testdata/MissingZeroCheck.sol create mode 100644 crates/lint/testdata/MissingZeroCheck.stderr create mode 100644 crates/lint/testdata/MultiContractFile.sol create mode 100644 crates/lint/testdata/MultiContractFile.stderr create mode 100644 crates/lint/testdata/MultiContractFile_InterfaceLibrary.sol create mode 100644 crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr create mode 100644 crates/lint/testdata/NamedStructFields.sol create mode 100644 crates/lint/testdata/NamedStructFields.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol create mode 100644 crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentOrVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.sol create mode 100644 crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.sol create mode 100644 crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr create mode 100644 crates/lint/testdata/Rtlo.sol create mode 100644 crates/lint/testdata/Rtlo.stderr create mode 100644 crates/lint/testdata/RtloCommentsOnly.sol create mode 100644 crates/lint/testdata/RtloCommentsOnly.stderr create mode 100644 crates/lint/testdata/SoloInterfaces.sol create mode 100644 crates/lint/testdata/TooManyDigits.sol create mode 100644 crates/lint/testdata/TooManyDigits.stderr create mode 100644 crates/lint/testdata/TxOrigin.sol create mode 100644 crates/lint/testdata/TxOrigin.stderr create mode 100644 crates/lint/testdata/UncheckedCall.sol create mode 100644 crates/lint/testdata/UncheckedCall.stderr create mode 100644 crates/lint/testdata/UncheckedTransferERC20.sol create mode 100644 crates/lint/testdata/UncheckedTransferERC20.stderr create mode 100644 crates/lint/testdata/UnsafeCheatcodes.sol create mode 100644 crates/lint/testdata/UnsafeCheatcodes.stderr create mode 100644 crates/lint/testdata/UnsafeTypecast.sol create mode 100644 crates/lint/testdata/UnsafeTypecast.stderr create mode 100644 crates/lint/testdata/UnusedStateVariables.sol create mode 100644 crates/lint/testdata/UnusedStateVariables.stderr create mode 100644 crates/lint/testdata/UnwrappedModifierLogic.sol create mode 100644 crates/lint/testdata/UnwrappedModifierLogic.stderr create mode 100644 crates/lint/testdata/VarReadUsingThis.sol create mode 100644 crates/lint/testdata/VarReadUsingThis.stderr create mode 100644 crates/lint/testdata/auxiliary/ImportsAnotherFile.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsAnotherFile2.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsConstants.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsFile.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsSomeFile.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsSomeFile2.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsTypes.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsUtils.sol create mode 100644 crates/lint/testdata/auxiliary/ImportsUtils2.sol create mode 100644 crates/lint/testdata/auxiliary/Test.sol create mode 100644 crates/primitives/Cargo.toml create mode 100644 crates/primitives/src/lib.rs create mode 100644 crates/primitives/src/network/mod.rs create mode 100644 crates/primitives/src/network/optimism.rs create mode 100644 crates/primitives/src/network/receipt.rs create mode 100644 crates/primitives/src/transaction/envelope.rs create mode 100644 crates/primitives/src/transaction/mod.rs create mode 100644 crates/primitives/src/transaction/optimism.rs create mode 100644 crates/primitives/src/transaction/receipt.rs create mode 100644 crates/primitives/src/transaction/request.rs create mode 100644 crates/test-utils/src/etherscan.rs create mode 100644 crates/test-utils/src/ext.rs create mode 100644 crates/test-utils/src/prj.rs delete mode 100644 crates/wallets/Cargo.toml delete mode 100644 crates/wallets/src/error.rs delete mode 100644 crates/wallets/src/lib.rs delete mode 100644 crates/wallets/src/multi_wallet.rs delete mode 100644 crates/wallets/src/raw_wallet.rs create mode 100644 crates/wallets/src/tempo.rs delete mode 100644 crates/wallets/src/utils.rs delete mode 100644 crates/wallets/src/wallet.rs delete mode 100644 crates/wallets/src/wallet_signer.rs create mode 100644 doc/doc-filelist.js create mode 100644 doc/doc-script.js create mode 100644 doc/doc-style.css create mode 100644 docs/dev/networks.md create mode 100644 dprint.json create mode 100644 npm/.env.example create mode 100644 npm/.gitignore create mode 100644 npm/@foundry-rs/anvil/README.md create mode 100644 npm/@foundry-rs/anvil/package.json create mode 100644 npm/@foundry-rs/cast/README.md create mode 100644 npm/@foundry-rs/cast/package.json create mode 100644 npm/@foundry-rs/chisel/README.md create mode 100644 npm/@foundry-rs/chisel/package.json create mode 100644 npm/@foundry-rs/forge/README.md create mode 100644 npm/@foundry-rs/forge/package.json create mode 100644 npm/README.md create mode 100644 npm/bun.lock create mode 100644 npm/bunfig.toml create mode 100644 npm/env.d.ts create mode 100644 npm/package.json create mode 100755 npm/scripts/check.sh create mode 100644 npm/scripts/prepublish.mjs create mode 100755 npm/scripts/publish-meta.mjs create mode 100644 npm/scripts/publish.mjs create mode 100755 npm/scripts/stage-from-artifact.mjs create mode 100644 npm/src/bin.mjs create mode 100644 npm/src/const.mjs create mode 100644 npm/src/generate-package-json.mjs create mode 100644 npm/src/install.mjs create mode 100644 npm/tsconfig.json create mode 100644 sleep.json create mode 100644 testdata/default/cheats/CurrentFilePath.t.sol create mode 100644 testdata/default/cheats/Ed25519.t.sol create mode 100644 testdata/default/cheats/ExecuteTransaction.t.sol create mode 100644 testdata/default/cheats/GetRawBlockHeader.t.sol create mode 100644 testdata/default/cheats/GetStorageSlots.t.sol create mode 100644 testdata/default/cheats/Rlp.t.sol create mode 100644 testdata/default/cheats/Seed.t.sol create mode 100644 testdata/default/cheats/Shuffle.t.sol create mode 100644 testdata/default/cheats/StateDiffBytesString.t.sol create mode 100644 testdata/default/cheats/StateDiffMappings.t.sol create mode 100644 testdata/default/cheats/StateDiffStorageLayout.t.sol create mode 100644 testdata/default/cheats/StateDiffStructTest.t.sol delete mode 100644 testdata/default/core/FailingTestAfterFailedSetup.t.sol delete mode 100644 testdata/default/core/LegacyAssertions.t.sol delete mode 100644 testdata/default/core/PaymentFailure.t.sol rename testdata/default/fs/{Default.t.sol => ReadOnly.sol} (68%) delete mode 100644 testdata/default/fuzz/Fuzz.t.sol delete mode 100644 testdata/default/fuzz/FuzzCollection.t.sol delete mode 100644 testdata/default/fuzz/FuzzFailurePersist.t.sol delete mode 100644 testdata/default/fuzz/FuzzInt.t.sol delete mode 100644 testdata/default/fuzz/FuzzPositive.t.sol delete mode 100644 testdata/default/fuzz/FuzzUint.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantAssume.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol delete mode 100644 testdata/default/fuzz/invariant/common/InvariantTest1.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetContracts.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/target/TargetSenders.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol delete mode 100644 testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol create mode 100644 testdata/default/linking/samefile_union/Libs.sol create mode 100644 testdata/default/linking/samefile_union/SameFileUnion.t.sol delete mode 100644 testdata/default/logs/DebugLogs.t.sol delete mode 100644 testdata/default/logs/HardhatLogs.t.sol create mode 100644 testdata/default/repros/Issue10957.t.sol create mode 100644 testdata/default/repros/Issue11353.t.sol create mode 100644 testdata/default/repros/Issue11616.t.sol create mode 100644 testdata/default/repros/Issue12075.t.sol create mode 100644 testdata/default/repros/Issue14212.t.sol delete mode 100644 testdata/default/repros/Issue2851.t.sol delete mode 100644 testdata/default/repros/Issue3055.t.sol delete mode 100644 testdata/default/repros/Issue3189.t.sol delete mode 100644 testdata/default/repros/Issue3347.t.sol delete mode 100644 testdata/default/repros/Issue3437.t.sol delete mode 100644 testdata/default/repros/Issue3596.t.sol delete mode 100644 testdata/default/repros/Issue3723.t.sol delete mode 100644 testdata/default/repros/Issue4832.t.sol delete mode 100644 testdata/default/repros/Issue6170.t.sol delete mode 100644 testdata/default/repros/Issue6355.t.sol delete mode 100644 testdata/default/repros/Issue6501.t.sol delete mode 100644 testdata/default/repros/Issue8383.t.sol delete mode 100644 testdata/default/script/broadcast/deploy.sol/31337/run-latest.json delete mode 100644 testdata/default/script/deploy.sol delete mode 100644 testdata/default/trace/ConflictingSignatures.t.sol delete mode 100644 testdata/default/trace/Trace.t.sol create mode 100644 testdata/fixtures/File/ignored/.gitignore delete mode 100644 testdata/fixtures/SolidityGeneration/Fastlane.json delete mode 100644 testdata/fixtures/SolidityGeneration/GaugeController.json delete mode 100644 testdata/fixtures/SolidityGeneration/GeneratedFastLane.sol delete mode 100644 testdata/fixtures/SolidityGeneration/GeneratedGaugeController.sol delete mode 100644 testdata/fixtures/SolidityGeneration/GeneratedLiquidityGaugeV4.sol delete mode 100644 testdata/fixtures/SolidityGeneration/GeneratedNamedInterface.sol delete mode 100644 testdata/fixtures/SolidityGeneration/GeneratedUnnamedInterface.sol delete mode 100644 testdata/fixtures/SolidityGeneration/InterfaceABI.json delete mode 100644 testdata/fixtures/SolidityGeneration/LiquidityGaugeV4.json delete mode 100644 testdata/fixtures/SolidityGeneration/WithStructs.json delete mode 100644 testdata/fixtures/SolidityGeneration/WithStructs.sol rename testdata/{default/vyper => src}/Counter.vy (100%) rename testdata/{default/vyper => src}/ICounter.vyi (100%) rename testdata/{lib/ds-test/src/test.sol => utils/DSTest.sol} (100%) create mode 100644 testdata/utils/Test.sol rename testdata/{cheats => utils}/Vm.sol (88%) rename testdata/{default/logs => utils}/console.sol (100%) create mode 100644 typos.toml diff --git a/.cargo/config.toml b/.cargo/config.toml index 76cf725f9e2e2..ca844fb33e15b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,11 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" +bless-lints = "test -p forge --test ui -- --bless" + +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml index 62291703e26a7..2ef62819f07dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,31 +1,22 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference version: 2.1 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs + jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job + test: docker: - # Specify the version you desire here - # See: https://circleci.com/developer/images/image/cimg/base - - image: cimg/base:current - - # Add steps to the job - # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps + - image: ghcr.io/foundry-rs/foundry:latest steps: - # Checkout the code as the first step. - checkout - run: - name: "Say hello" - command: "echo Hello, World!" - -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows + name: Install submodules + command: git submodule update --init --recursive + - run: + name: Build + command: forge build + - run: + name: Test + command: forge test -vvv + workflows: - say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. - # Inside the workflow, you define the jobs you want to run. + main: jobs: - - say-hello \ No newline at end of file + - test diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.codespellrc b/.codespellrc deleted file mode 100644 index 929bd589b7112..0000000000000 --- a/.codespellrc +++ /dev/null @@ -1,3 +0,0 @@ -[codespell] -skip = .git,target,testdata,Cargo.toml,Cargo.lock -ignore-words-list = crate,ser,ratatui,Caf,froms,strat diff --git a/.config/nextest.toml b/.config/nextest.toml index 86960c3abb1cd..ffd5dcb7c5508 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,18 +1,23 @@ [test-groups] -chisel-serial = { max-threads = 1 } [profile.default] retries = { backoff = "exponential", count = 2, delay = "5s", jitter = true } -slow-timeout = { period = "1m", terminate-after = 3 } +slow-timeout = { period = "30s", terminate-after = 3 } +# Exclude flaky tests from regular CI runs - they run in nightly test-flaky workflow +default-filter = "not test(/flaky_/)" [[profile.default.overrides]] -filter = "test(/ext_integration|can_test_forge_std/)" +filter = "test(/ext_integration/)" slow-timeout = { period = "5m", terminate-after = 4 } +# Do not re-run so that `cargo cheats` is ran locally. [[profile.default.overrides]] filter = "package(foundry-cheatcodes-spec)" retries = 0 -[[profile.default.overrides]] -filter = "package(chisel)" -test-group = "chisel-serial" +# Profile for running flaky tests (used by nightly CI and for local debugging) +# Run with: cargo nextest run --profile flaky +[profile.flaky] +default-filter = "test(/flaky_/)" +retries = { backoff = "exponential", count = 5, delay = "10s", jitter = true } +slow-timeout = { period = "60s", terminate-after = 3 } diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.devcontainer/Dockerfile.dev b/.devcontainer/Dockerfile.dev deleted file mode 100644 index 9017115744e89..0000000000000 --- a/.devcontainer/Dockerfile.dev +++ /dev/null @@ -1,84 +0,0 @@ -FROM ubuntu:22.04 - -ARG USERNAME=foundry -ARG USER_UID=1000 -ARG USER_GID=$USER_UID -ARG PYTHON_VERSION=3.11 -ARG NODE_MAJOR=20 -ARG VYPER_VERSION=0.4.0 - -ENV DEBIAN_FRONTEND=noninteractive -ENV CARGO_TERM_COLOR=always \ - RUST_BACKTRACE=full - -WORKDIR /workspace - -RUN apt-get update && apt-get install -y --no-install-recommends \ - # Build tools - build-essential \ - clang \ - lld \ - pkg-config \ - # Network/SSL - curl \ - ca-certificates \ - gnupg \ - libssl-dev \ - # Version control & utils - git \ - sudo \ - unzip \ - # Python - python${PYTHON_VERSION} \ - python3-pip \ - python${PYTHON_VERSION}-venv \ - # Add Node.js repo - && mkdir -p /etc/apt/keyrings \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ - # Update again after adding repo and install Node.js - && apt-get update && apt-get install -y --no-install-recommends \ - nodejs \ - # Clean up apt cache - && apt-get clean && rm -rf /var/lib/apt/lists/* - -# Ensure python points to the installed python version -RUN ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \ - ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 - -# Create non-root user with sudo privileges -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME -s /bin/bash \ - # Setup sudo without password prompt - && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME \ - # Add user to the sudo group (standard practice) - && usermod -aG sudo $USERNAME - -# Switch to the non-root user -USER $USERNAME -WORKDIR /home/$USERNAME - -# --- User-specific installations --- - -# Install Bun -ENV BUN_INSTALL="/home/$USERNAME/.bun" -ENV PATH="$BUN_INSTALL/bin:$PATH" -RUN curl -fsSL https://bun.sh/install | bash - -# Install Rust & cargo-nextest -ENV CARGO_HOME="/home/$USERNAME/.cargo" -ENV RUSTUP_HOME="/home/$USERNAME/.rustup" -ENV PATH="$CARGO_HOME/bin:$PATH" -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ - && cargo install cargo-nextest --locked - -# Install Vyper using pip -# Ensure pip user install directory is in PATH -ENV PYTHONUSERBASE="/home/$USERNAME/.local" -ENV PATH="$PYTHONUSERBASE/bin:$PATH" -RUN pip3 install --user vyper==${VYPER_VERSION} - -# Switch back to the main workspace directory -WORKDIR /workspace - diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1c2eb26f5a9f6..0000000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,49 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. -{ - "name": "Foundry Development", - "build": { - "context": "..", - "dockerfile": "Dockerfile.dev" - }, - - "features": { - "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": true, - "configureZshAsDefaultShell": true, - "installOhMyZsh": true, - "upgradePackages": true - } - }, - - "forwardPorts": [], - - "postCreateCommand": "rustup default stable && rustup update", - - "customizations": { - "vscode": { - "extensions": [ - "rust-lang.rust-analyzer", - "serayuzgur.crates", - "tamasfe.even-better-toml", - "ms-python.python", - "dbaeumer.vscode-eslint", - "oven.bun-vscode" - ], - "settings": { - "rust-analyzer.checkOnSave": true, - "rust-analyzer.cargo.features": "all" - } - } - }, - - "remoteUser": "foundry", - - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", - - "workspaceFolder": "/workspace", - - "mounts": [ - "source=${localEnv:HOME}/.cargo/registry,target=/home/foundry/.cargo/registry,type=bind,consistency=cached", - "source=${localEnv:HOME}/.cargo/git,target=/home/foundry/.cargo/git,type=bind,consistency=cached" - ] -} diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 876f5c0b15fc6..61c75f9f401a5 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -10,3 +10,6 @@ # fmt: all (#3398) 860d083183b51a6f8d865408ef1a44aa694d6862 + +# chore: bump to rust edition 2024 (#10802) +710a1584aae8e0f8ca8d5ba552632dc72381091e diff --git a/.gitattributes b/.gitattributes index a1451e6b491a5..077f620291111 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,13 @@ crates/cheatcodes/assets/*.json linguist-generated testdata/cheats/Vm.sol linguist-generated +bun.lock linguist-generated # See *.rs diff=rust crates/lint/testdata/* text eol=lf +testdata/fixtures/**/* eol=lf + +dprint.json linguist-language=JSON-with-Comments +.devcontainer/devcontainer.json linguist-language=JSON-with-Comments + +.env.example linguist-language=Dotenv \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 63b25bbbcb260..a6726847fd70b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @danipopes @klkvr @mattsse @grandizzy @yash-atreya @zerosnacks +* @danipopes @mattsse @grandizzy @zerosnacks @0xrusowsky @mablr @figtracer @stevencartavia diff --git a/.github/CRATE_CHECKS_FAILURE_TEMPLATE.md b/.github/CRATE_CHECKS_FAILURE_TEMPLATE.md new file mode 100644 index 0000000000000..40e5177e648da --- /dev/null +++ b/.github/CRATE_CHECKS_FAILURE_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: "bug: nightly crate-checks workflow failed" +labels: P-normal, T-bug +--- + +The nightly crate-checks workflow (`cargo hack check`) has failed. This means one or more crates don't compile in isolation — likely a missing dependency or feature flag masked by Cargo's workspace feature unification. + +Check the [crate-checks workflow page]({{ env.WORKFLOW_URL }}) for details. + +This issue was raised by the workflow at `.github/workflows/crate-checks.yml`. diff --git a/.github/FLAKY_TEST_FAILURE_TEMPLATE.md b/.github/FLAKY_TEST_FAILURE_TEMPLATE.md new file mode 100644 index 0000000000000..1553914b7ad3f --- /dev/null +++ b/.github/FLAKY_TEST_FAILURE_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: "bug: flaky tests workflow failed" +labels: P-normal, T-bug +--- + +The nightly flaky tests workflow has failed. This indicates external API rate limiting, RPC reliability issues, or other intermittent failures that may affect users. + +Check the [flaky tests workflow page]({{ env.WORKFLOW_URL }}) for details. + +This issue was raised by the workflow at `.github/workflows/test-flaky.yml`. diff --git a/.github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md b/.github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md new file mode 100644 index 0000000000000..0d7a9bb84959d --- /dev/null +++ b/.github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: "bug: flaky tests workflow failed (isolate)" +labels: P-normal, T-bug +--- + +The nightly flaky tests workflow (with isolation mode enabled) has failed. This indicates external API rate limiting, RPC reliability issues, or other intermittent failures that may affect users. + +Check the [flaky tests workflow page]({{ env.WORKFLOW_URL }}) for details. + +This issue was raised by the workflow at `.github/workflows/test-isolate.yml`. diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml index 5a5f7e7808994..281cf2fd254ff 100644 --- a/.github/ISSUE_TEMPLATE/BUG-FORM.yml +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -1,58 +1,61 @@ name: Bug report description: File a bug report +type: Bug labels: ["T-bug", "T-needs-triage"] body: - - type: markdown - attributes: - value: | - Please ensure that the bug has not already been filed in the issue tracker. + - type: markdown + attributes: + value: | + Please ensure that the bug has not already been filed in the issue tracker. - Thanks for taking the time to report this bug! - - type: dropdown - attributes: - label: Component - description: What component is the bug in? - multiple: true - options: - - Forge - - Cast - - Anvil - - Foundryup - - Chisel - - Other (please describe) - validations: - required: true - - type: checkboxes - attributes: - label: Have you ensured that all of these are up to date? - options: - - label: Foundry - - label: Foundryup - - type: input - attributes: - label: What version of Foundry are you on? - placeholder: "Run forge --version and paste the output here" - - type: input - attributes: - label: What version of Foundryup are you on? - placeholder: "Run foundryup --version and paste the output here" - - type: input - attributes: - label: What command(s) is the bug in? - description: Leave empty if not relevant - placeholder: "For example: forge test" - - type: dropdown - attributes: - label: Operating System - description: What operating system are you on? - options: - - Windows - - macOS (Intel) - - macOS (Apple Silicon) - - Linux - - type: textarea - attributes: - label: Describe the bug - description: Please include relevant Solidity snippets as well if relevant. - validations: - required: true \ No newline at end of file + Thanks for taking the time to report this bug! + - type: dropdown + id: component + attributes: + label: Component + description: What component is the bug in? + multiple: true + options: + - Forge + - Cast + - Anvil + - Foundryup + - Chisel + - Other (please describe) + validations: + required: true + - type: checkboxes + attributes: + label: Have you ensured that all of these are up to date? + options: + - label: Foundry + - label: Foundryup + - type: input + attributes: + label: What version of Foundry are you on? + placeholder: "Run forge --version and paste the output here" + - type: input + attributes: + label: What version of Foundryup are you on? + placeholder: "Run foundryup --version and paste the output here" + - type: input + attributes: + label: What command(s) is the bug in? + description: Leave empty if not relevant + placeholder: "For example: forge test" + - type: dropdown + attributes: + label: Operating System + description: What operating system are you on? + options: + - Windows + - macOS (Intel) + - macOS (Apple Silicon) + - Linux + - type: textarea + id: description + attributes: + label: Describe the bug + description: Please include relevant Solidity snippets as well if relevant. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml index 581eb5c0d5eed..4268a73baa8b6 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml @@ -1,34 +1,37 @@ name: Feature request description: Suggest a feature +type: Feature labels: ["T-feature", "T-needs-triage"] body: - - type: markdown - attributes: - value: | - Please ensure that the feature has not already been requested in the issue tracker. + - type: markdown + attributes: + value: | + Please ensure that the feature has not already been requested in the issue tracker. - Thanks for helping us improve Foundry! - - type: dropdown - attributes: - label: Component - description: What component is the feature for? - multiple: true - options: - - Forge - - Cast - - Anvil - - Foundryup - - Chisel - - Other (please describe) - validations: - required: true - - type: textarea - attributes: - label: Describe the feature you would like - description: Please also describe what the feature is aiming to solve, if relevant. - validations: - required: true - - type: textarea - attributes: - label: Additional context - description: Add any other context to the feature (like screenshots, resources) + Thanks for helping us improve Foundry! + - type: dropdown + id: component + attributes: + label: Component + description: What component is the feature for? + multiple: true + options: + - Forge + - Cast + - Anvil + - Foundryup + - Chisel + - Other (please describe) + validations: + required: true + - type: textarea + id: description + attributes: + label: Describe the feature you would like + description: Please also describe what the feature is aiming to solve, if relevant. + validations: + required: true + - type: textarea + attributes: + label: Additional context + description: Add any other context to the feature (like screenshots, resources) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..edd3e4a15ddbc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, Safari] + - Version [e.g. 22] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 16e0182b117ac..3660fa19ab9d9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ the requirements below. Bug fixes and new features should include tests. -Contributors guide: https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md +Contributors guide: https://github.com/foundry-rs/foundry/blob/HEAD/CONTRIBUTING.md The contributors guide includes instructions for running rustfmt and building the documentation. @@ -31,4 +31,4 @@ the code change. - [ ] Added Tests - [ ] Added Documentation -- [ ] Breaking changes \ No newline at end of file +- [ ] Breaking changes diff --git a/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md b/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md new file mode 100644 index 0000000000000..8f72899c63611 --- /dev/null +++ b/.github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: "bug: tempo nightly workflow failed" +labels: P-high, T-bug +--- + +The nightly Tempo workflow (mainnet and testnet checks) has failed. This indicates a regression in Foundry's Tempo support or an issue with the Tempo mainnet/testnet endpoints. + +Check the [tempo nightly workflow page]({{ env.WORKFLOW_URL }}) for details. + +This issue was raised by the workflow at `.github/workflows/ci-tempo.yml`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000000..ad7b297a7a874 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,39 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + groups: + actions-weekly: + applies-to: "version-updates" + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + cargo-weekly: + applies-to: "version-updates" + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "npm" + directory: "/npm" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + npm-weekly: + applies-to: "version-updates" + patterns: ["*"] + update-types: ["minor", "patch"] diff --git a/.github/scripts/bump-tempo.sh b/.github/scripts/bump-tempo.sh new file mode 100644 index 0000000000000..50ac7f85d8b39 --- /dev/null +++ b/.github/scripts/bump-tempo.sh @@ -0,0 +1,145 @@ +#!/bin/bash +set -euo pipefail + +# Bump all dependencies from the tempoxyz/tempo repository to the latest commit on main. +# All tempo dependencies share the same commit revision. +# +# Usage: +# ./bump-tempo.sh [--dry-run] +# +# Requirements: +# - gh (GitHub CLI) must be installed and authenticated +# +# Outputs (for GitHub Actions): +# Sets outputs in $GITHUB_OUTPUT if it exists: +# - current_rev: The current tempo revision +# - latest_rev: The latest tempo revision on main +# - updated: "true" if dependencies were updated, "false" otherwise +# - changelog: Path to changelog file (if updated) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +CARGO_TOML="$REPO_ROOT/Cargo.toml" + +DRY_RUN=false +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true + echo "Running in dry-run mode (no changes will be made)" +fi + +# Get the current tempo revision from any dependency using tempoxyz/tempo +# They all share the same rev, so we just grab the first one +get_current_rev() { + grep 'git = "https://github.com/tempoxyz/tempo"' "$CARGO_TOML" | head -1 | sed 's/.*rev = "\([^"]*\)".*/\1/' +} + +# Get the latest commit SHA from tempoxyz/tempo main branch +get_latest_rev() { + gh api repos/tempoxyz/tempo/commits/main --jq '.sha' +} + +# Generate changelog between two revisions +generate_changelog() { + local old_rev="$1" + local new_rev="$2" + local output_file="$3" + + echo "Fetching commits from ${old_rev:0:7} to ${new_rev:0:7}..." + + local commits + # shellcheck disable=SC2016 # Single quotes intentional for jq expression + commits=$(gh api "repos/tempoxyz/tempo/compare/${old_rev}...${new_rev}" \ + --jq '.commits[] | "- [`\(.sha[0:7])`](https://github.com/tempoxyz/tempo/commit/\(.sha)) \(.commit.message | split("\n")[0])"') + + { + echo "## Tempo Dependency Updates" + echo "" + echo "Bumped all \`tempo*\` dependencies from [\`${old_rev:0:7}\`](https://github.com/tempoxyz/tempo/commit/${old_rev}) to [\`${new_rev:0:7}\`](https://github.com/tempoxyz/tempo/commit/${new_rev})." + echo "" + echo "### Commits" + echo "" + echo "$commits" + } > "$output_file" + + echo "Changelog written to $output_file" +} + +# Update Cargo.toml with new revision +update_cargo_toml() { + local old_rev="$1" + local new_rev="$2" + + sed -i "s|git = \"https://github.com/tempoxyz/tempo\", rev = \"$old_rev\"|git = \"https://github.com/tempoxyz/tempo\", rev = \"$new_rev\"|g" "$CARGO_TOML" + echo "Updated Cargo.toml: $old_rev -> $new_rev" +} + +# Regenerate Cargo.lock (may fail if dependencies don't build) +regenerate_lockfile() { + echo "" + echo "Regenerating Cargo.lock..." + if cargo generate-lockfile 2>&1; then + echo "Cargo.lock regenerated successfully" + set_output "lockfile_updated" "true" + else + echo "WARNING: Failed to regenerate Cargo.lock (dependencies may not build)" + set_output "lockfile_updated" "false" + fi +} + +# Set GitHub Actions output if running in CI +set_output() { + local name="$1" + local value="$2" + + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "$name=$value" >> "$GITHUB_OUTPUT" + fi +} + +main() { + echo "=== Bump Tempo Dependencies ===" + echo "" + + CURRENT_REV=$(get_current_rev) + echo "Current revision: $CURRENT_REV" + set_output "current_rev" "$CURRENT_REV" + + LATEST_REV=$(get_latest_rev) + echo "Latest revision: $LATEST_REV" + set_output "latest_rev" "$LATEST_REV" + + if [[ "$CURRENT_REV" == "$LATEST_REV" ]]; then + echo "" + echo "Already up to date. No changes needed." + set_output "updated" "false" + exit 0 + fi + + echo "" + echo "Update available: ${CURRENT_REV:0:7} -> ${LATEST_REV:0:7}" + + CHANGELOG_FILE="$REPO_ROOT/tempo-changelog.md" + + echo "" + generate_changelog "$CURRENT_REV" "$LATEST_REV" "$CHANGELOG_FILE" + set_output "changelog" "$CHANGELOG_FILE" + + if [[ "$DRY_RUN" == "true" ]]; then + echo "" + echo "[DRY RUN] Would update Cargo.toml" + echo "[DRY RUN] Would regenerate Cargo.lock" + echo "" + echo "=== Changelog Preview ===" + cat "$CHANGELOG_FILE" + else + update_cargo_toml "$CURRENT_REV" "$LATEST_REV" + regenerate_lockfile + fi + + set_output "updated" "true" + + echo "" + echo "=== Done ===" +} + +main "$@" diff --git a/.github/scripts/combine-benchmarks.sh b/.github/scripts/combine-benchmarks.sh new file mode 100755 index 0000000000000..90adc8fe90132 --- /dev/null +++ b/.github/scripts/combine-benchmarks.sh @@ -0,0 +1,187 @@ +#!/bin/bash +set -euo pipefail + +# Script to combine individual benchmark results into LATEST.md +# Usage: ./combine-benchmarks.sh + +OUTPUT_DIR="${1:-benches}" + +# Create output directory if it doesn't exist +mkdir -p "$OUTPUT_DIR" + +# Define the benchmark files and their section names +declare -A BENCHMARK_FILES=( + ["forge_test_bench.md"]="Forge Test" + ["forge_isolate_test_bench.md"]="Forge Test (Isolated)" + ["forge_build_bench.md"]="Forge Build" + ["forge_coverage_bench.md"]="Forge Coverage" +) + +# Function to extract a specific section from a benchmark file +extract_section() { + local file=$1 + local section=$2 + local in_section=0 + + while IFS= read -r line; do + if [[ "$line" =~ ^##[[:space:]]+"$section" ]]; then + in_section=1 + echo "$line" + elif [[ $in_section -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+"$section" ]]; then + break + elif [[ $in_section -eq 1 ]]; then + echo "$line" + fi + done < "$file" +} + +# Function to extract summary info (repos and versions) from a file +extract_summary_info() { + local file=$1 + local in_summary=0 + local in_repos=0 + local in_versions=0 + + while IFS= read -r line; do + # Check for Summary section + if [[ "$line" =~ ^##[[:space:]]+Summary ]]; then + in_summary=1 + continue + fi + + # Check for Repositories Tested subsection + if [[ $in_summary -eq 1 && "$line" =~ ^###[[:space:]]+Repositories[[:space:]]+Tested ]]; then + in_repos=1 + echo "### Repositories Tested" + echo + continue + fi + + # Check for Foundry Versions subsection + if [[ $in_summary -eq 1 && "$line" =~ ^###[[:space:]]+Foundry[[:space:]]+Versions ]]; then + in_repos=0 + in_versions=1 + echo "### Foundry Versions" + echo + continue + fi + + # End of summary section + if [[ $in_summary -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+Summary ]]; then + break + fi + + # Output repo or version lines + if [[ ($in_repos -eq 1 || $in_versions -eq 1) && -n "$line" ]]; then + echo "$line" + fi + done < "$file" +} + +# Function to extract benchmark table from a section +extract_benchmark_table() { + local file=$1 + local section=$2 + local in_section=0 + local found_table=0 + + while IFS= read -r line; do + if [[ "$line" =~ ^##[[:space:]]+"$section" ]]; then + in_section=1 + continue + elif [[ $in_section -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+"$section" ]]; then + break + elif [[ $in_section -eq 1 ]]; then + # Skip empty lines before table + if [[ -z "$line" && $found_table -eq 0 ]]; then + continue + fi + # Detect table start + if [[ "$line" =~ ^\|[[:space:]]*Repository ]]; then + found_table=1 + fi + # Output table lines + if [[ $found_table -eq 1 && -n "$line" ]]; then + echo "$line" + fi + fi + done < "$file" +} + +# Function to extract system information +extract_system_info() { + local file=$1 + awk '/^## System Information/ { found=1; next } found { print }' "$file" +} + +# Start building LATEST.md +cat > "$OUTPUT_DIR/LATEST.md" << EOF +# 📊 Foundry Benchmark Results + +**Generated at**: $(date -u '+%Y-%m-%d %H:%M:%S UTC') + +EOF + +# Process each benchmark file +FIRST_FILE=1 +SYSTEM_INFO="" + +for bench_file in "forge_test_bench.md" "forge_isolate_test_bench.md" "forge_build_bench.md" "forge_coverage_bench.md"; do + if [ -f "$OUTPUT_DIR/$bench_file" ]; then + echo "Processing $bench_file..." + + SECTION_NAME="${BENCHMARK_FILES[$bench_file]:-$bench_file}" + + # Grouped output for ShellCheck SC2129 + { + # Add section header + echo "## $SECTION_NAME" + echo + + # Add summary info + extract_summary_info "$OUTPUT_DIR/$bench_file" + echo + + # Handle benchmark tables + if [[ "$bench_file" == "forge_test_bench.md" ]]; then + extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Test" + echo + if grep -q "^## Forge Fuzz Test" "$OUTPUT_DIR/$bench_file"; then + echo "## Forge Fuzz Test" + echo + extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Fuzz Test" + fi + elif [[ "$bench_file" == "forge_build_bench.md" ]]; then + echo "### No Cache" + echo + extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Build (No Cache)" + echo + echo "### With Cache" + echo + extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Build (With Cache)" + else + extract_benchmark_table "$OUTPUT_DIR/$bench_file" "$SECTION_NAME" + fi + echo + } >> "$OUTPUT_DIR/LATEST.md" + + # Extract system info from first file only + if [[ $FIRST_FILE -eq 1 ]]; then + SYSTEM_INFO=$(extract_system_info "$OUTPUT_DIR/$bench_file") + FIRST_FILE=0 + fi + else + echo "Warning: $bench_file not found, skipping..." + fi +done + +# Add system information at the end +if [[ -n "$SYSTEM_INFO" ]]; then + { + echo "## System Information" + echo + echo "$SYSTEM_INFO" + } >> "$OUTPUT_DIR/LATEST.md" +fi + +echo "Successfully combined benchmark results into $OUTPUT_DIR/LATEST.md" diff --git a/.github/scripts/commit-benchmark-results.sh b/.github/scripts/commit-benchmark-results.sh new file mode 100644 index 0000000000000..f7dba8980fd64 --- /dev/null +++ b/.github/scripts/commit-benchmark-results.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +# Script to commit and push benchmark results. +# +# This script is intended to run from the lightweight `publish-results` job, +# which checks out the repo with credentials and only operates on the +# trusted artifact produced by the benchmark job. Keeping the write-scoped +# token away from the bench job (which runs untrusted third-party builds) +# limits the blast radius of a compromised dependency. +# +# Usage: ./commit-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" +GITHUB_EVENT_NAME="${2:-workflow_dispatch}" +GITHUB_REPOSITORY="${3:-}" + +if [ ! -f "$OUTPUT_DIR/LATEST.md" ]; then + echo "Error: $OUTPUT_DIR/LATEST.md not found, nothing to commit" + exit 1 +fi + +echo "Configuring git..." +git config --local user.email "action@github.com" +git config --local user.name "GitHub Action" + +# Decide which branch to commit to based on the event. +BRANCH_NAME="" +case "$GITHUB_EVENT_NAME" in + workflow_dispatch) + echo "Manual workflow run detected, creating new branch..." + BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH_NAME" + echo "Created branch: $BRANCH_NAME" + ;; + pull_request) + if [ -z "${GITHUB_HEAD_REF:-}" ]; then + echo "Error: GITHUB_HEAD_REF not set for pull_request event" + exit 1 + fi + echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" + git fetch origin "$GITHUB_HEAD_REF" + git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" + BRANCH_NAME="$GITHUB_HEAD_REF" + ;; + *) + echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" + echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" + exit 1 + ;; +esac + +# Always emit the branch name so downstream steps (e.g. PR creation) can use it. +echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + +echo "Adding benchmark file..." +git add "$OUTPUT_DIR/LATEST.md" + +if git diff --staged --quiet; then + echo "No changes to commit" + echo "committed=false" >> "$GITHUB_OUTPUT" + exit 0 +fi + +echo "Committing benchmark results..." +git commit -m "chore(\`benches\`): update benchmark results + +🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) + +Co-Authored-By: github-actions " + +echo "Pushing to repository..." +git push origin "$BRANCH_NAME" +echo "Successfully pushed benchmark results to $BRANCH_NAME" +echo "committed=true" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh new file mode 100755 index 0000000000000..549c4c80e846b --- /dev/null +++ b/.github/scripts/compare-nightly.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Compare two nightly benchmark JSON summaries and report regressions. +# +# Usage: compare-nightly.sh [warn_pct] [fail_pct] +# Exits 0 if no regressions, 1 if any metric exceeds fail_pct. +# Exits 0 gracefully when prev.json is missing (first run / gap > 7 days). +set -euo pipefail + +PREV_JSON="${1:-}" +TODAY_JSON="${2:-}" +WARN="${3:-1}" +FAIL="${4:-3}" + +PREV_JSON="$PREV_JSON" TODAY_JSON="$TODAY_JSON" WARN="$WARN" FAIL="$FAIL" \ +python3 - <<'EOF' +import json, os, sys + +warn = float(os.environ["WARN"]) +fail = float(os.environ["FAIL"]) + +prev_path = os.environ.get("PREV_JSON", "") +prev = {} +if prev_path and os.path.isfile(prev_path): + with open(prev_path) as f: + prev = json.load(f) +with open(os.environ["TODAY_JSON"]) as f: + today = json.load(f) + +print("## Nightly Benchmark Regression Report\n") +print("| Benchmark | Stable | Nightly | Δ | Status |") +print("|-----------|--------|---------|---|--------|") + +has_regression = False +all_keys = sorted(prev.keys() | today.keys()) +for key in all_keys: + t = today.get(key) + p = prev.get(key) + if t is None: + print(f"| `{key}` | {p:.5f}s | N/A | — | ⚠️ Missing |") + has_regression = True + continue + if p is None: + print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") + continue + if p == 0: + delta = float('inf') if t > 0 else 0.0 + else: + delta = (t - p) / p * 100 + if delta >= fail: + status = "🔴 Regression" + has_regression = True + elif delta >= warn: + status = "🟡 Warning" + elif delta <= -warn: + status = "🟢 Improvement" + else: + status = "➡️ OK" + sign = "+" if delta > 0 else "" + print(f"| `{key}` | {p}s | {t}s | {sign}{delta:.1f}% | {status} |") + +sys.exit(1 if has_regression else 0) +EOF diff --git a/.github/scripts/contracts/BatchCounter.sol b/.github/scripts/contracts/BatchCounter.sol new file mode 100644 index 0000000000000..5dbded961a7c4 --- /dev/null +++ b/.github/scripts/contracts/BatchCounter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract BatchCounter { + uint256 public number; + + constructor(uint256 initialNumber) { + number = initialNumber; + } + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/.github/scripts/contracts/BatchRevertTest.s.sol.template b/.github/scripts/contracts/BatchRevertTest.s.sol.template new file mode 100644 index 0000000000000..ab033fcda7bb6 --- /dev/null +++ b/.github/scripts/contracts/BatchRevertTest.s.sol.template @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; + +interface ICounter { + function increment() external; + function setNumber(uint256 newNumber) external; + function number() external view returns (uint256); +} + +contract BatchRevertTestScript is Script { + function run() public { + address counter = ${REQUIRE_COUNTER}; + vm.startBroadcast(); + + // First call succeeds + ICounter(counter).setNumber(600); + // Second call fails (require > 100 fails with 50) + ICounter(counter).setNumber(50); + + vm.stopBroadcast(); + } +} diff --git a/.github/scripts/contracts/BatchTest.s.sol.template b/.github/scripts/contracts/BatchTest.s.sol.template new file mode 100644 index 0000000000000..730735d1cbfd6 --- /dev/null +++ b/.github/scripts/contracts/BatchTest.s.sol.template @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; + +interface ICounter { + function increment() external; + function setNumber(uint256 newNumber) external; + function number() external view returns (uint256); +} + +contract BatchTestScript is Script { + function run() public { + address counter = ${REQUIRE_COUNTER}; + vm.startBroadcast(); + + // Multiple calls that will be batched into a single transaction + ICounter(counter).setNumber(500); + ICounter(counter).increment(); + ICounter(counter).increment(); + ICounter(counter).increment(); + + vm.stopBroadcast(); + } +} diff --git a/.github/scripts/contracts/CounterWithRequire.sol b/.github/scripts/contracts/CounterWithRequire.sol new file mode 100644 index 0000000000000..fc23820915124 --- /dev/null +++ b/.github/scripts/contracts/CounterWithRequire.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + require(newNumber > 100, "bad number"); + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/.github/scripts/contracts/DeployAndCall.s.sol b/.github/scripts/contracts/DeployAndCall.s.sol new file mode 100644 index 0000000000000..0a344ba1647e5 --- /dev/null +++ b/.github/scripts/contracts/DeployAndCall.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {BatchCounter} from "../src/BatchCounter.sol"; + +contract DeployAndCallScript is Script { + function run() public { + vm.startBroadcast(); + + // Deploy contract (CREATE as first call) + BatchCounter counter = new BatchCounter(100); + + // Call the newly deployed contract in the same batch + counter.setNumber(200); + counter.increment(); + counter.increment(); + + // Final number should be 202 (200 + 2 increments) + console.log("Deployed BatchCounter at:", address(counter)); + + vm.stopBroadcast(); + } +} diff --git a/.github/scripts/create-tag.js b/.github/scripts/create-tag.js index 042f9950f6173..db56419e168f3 100644 --- a/.github/scripts/create-tag.js +++ b/.github/scripts/create-tag.js @@ -5,10 +5,12 @@ module.exports = async ({ github, context }, tagName) => { repo: context.repo.repo, ref: `refs/tags/${tagName}`, sha: context.sha, - force: true, }); } catch (err) { - console.error(`Failed to create tag: ${tagName}`); - console.error(err); + if (err.status === 422 && String(err.message).includes("Reference already exists")) { + console.log(`Tag already exists: ${tagName}`); + return; + } + throw err; } }; diff --git a/.github/scripts/format-pr-comment.sh b/.github/scripts/format-pr-comment.sh new file mode 100755 index 0000000000000..74ad095464aba --- /dev/null +++ b/.github/scripts/format-pr-comment.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -euo pipefail + +# Script to format benchmark results for PR comment +# Usage: ./format-pr-comment.sh + +RESULTS_FILE="${1:-}" + +if [ -z "$RESULTS_FILE" ] || [ ! -f "$RESULTS_FILE" ]; then + echo "Error: Benchmark results file not provided or does not exist" + exit 1 +fi + +# Read the file content +CONTENT=$(cat "$RESULTS_FILE") + +# Find where "## Forge Build" starts and split the content +# Extract everything before "## Forge Build" +BEFORE_FORGE_BUILD=$(echo "$CONTENT" | awk '/^## Forge Build$/ {exit} {print}') + +# Extract everything from "## Forge Build" onwards +FROM_FORGE_BUILD=$(echo "$CONTENT" | awk '/^## Forge Build$/ {found=1} found {print}') + +# Output the formatted comment with dropdown +cat << EOF +${BEFORE_FORGE_BUILD} + +
+📈 View all benchmark results + +${FROM_FORGE_BUILD} + +
+EOF \ No newline at end of file diff --git a/.github/scripts/format.sh b/.github/scripts/format.sh index 9bd1f950fdeaf..45cf09d8637ba 100755 --- a/.github/scripts/format.sh +++ b/.github/scripts/format.sh @@ -3,5 +3,10 @@ set -eo pipefail # We have to ignore at shell level because testdata/ is not a valid Foundry project, # so running `forge fmt` with `--root testdata` won't actually check anything -cargo run --bin forge -- fmt "$@" \ - $(find testdata -name '*.sol' ! -name Vm.sol ! -name console.sol) +sol_files=() +while IFS= read -r -d '' file; do + sol_files+=("$file") +done < <(find testdata -name '*.sol' ! -name Vm.sol ! -name console.sol -print0) + +# Run forge fmt on all found files +cargo run --bin forge -- fmt "$@" "${sol_files[@]}" diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py index 4ad0c3acc34dc..b60ea7048884d 100755 --- a/.github/scripts/matrices.py +++ b/.github/scripts/matrices.py @@ -67,36 +67,27 @@ def __init__( profile = os.environ.get("PROFILE") is_pr = os.environ.get("EVENT_NAME") == "pull_request" -t_linux_x86 = Target("ubuntu-latest", "x86_64-unknown-linux-gnu", "linux-amd64") -# TODO: Figure out how to make this work -# t_linux_arm = Target("ubuntu-latest", "aarch64-unknown-linux-gnu", "linux-aarch64") -t_macos = Target("macos-latest", "aarch64-apple-darwin", "macosx-aarch64") -t_windows = Target("windows-latest", "x86_64-pc-windows-msvc", "windows-amd64") -targets = [t_linux_x86, t_windows] if is_pr else [t_linux_x86, t_macos, t_windows] +t_linux_x86 = Target( + "depot-ubuntu-latest-16", "x86_64-unknown-linux-gnu", "linux-amd64" +) +t_linux_arm = Target( + "depot-ubuntu-latest-arm-16", "aarch64-unknown-linux-gnu", "linux-aarch64" +) +t_macos = Target("depot-macos-latest", "aarch64-apple-darwin", "macosx-aarch64") +t_windows = Target("depot-windows-latest-16", "x86_64-pc-windows-msvc", "windows-amd64") +targets = [t_linux_x86] if is_pr else [t_linux_x86, t_linux_arm, t_macos, t_windows] config = [ Case( - name="unit", - filter="!kind(test)", + name="all", + filter="!test(/\\bext_integration/)", n_partitions=1, pr_cross_platform=True, ), Case( - name="integration", - filter="kind(test) & !test(/\\b(issue|ext_integration)/)", - n_partitions=3, - pr_cross_platform=True, - ), - Case( - name="integration / issue-repros", - filter="package(=forge) & test(/\\bissue/)", - n_partitions=2, - pr_cross_platform=False, - ), - Case( - name="integration / external", + name="external", filter="package(=forge) & test(/\\bext_integration/)", - n_partitions=2, + n_partitions=1, pr_cross_platform=False, ), ] @@ -120,11 +111,13 @@ def main(): s = f"{partition}/{case.n_partitions}" name += f" ({s})" flags += f" --partition count:{s}" - + if profile == "isolate": flags += " --features=isolate-by-default" name += os_str + flags += " --no-fail-fast" + obj = Expanded( name=name, runner_label=target.runner_label, diff --git a/.github/scripts/move-tag.js b/.github/scripts/move-tag.js deleted file mode 100644 index d1fe815be57cc..0000000000000 --- a/.github/scripts/move-tag.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = async ({ github, context }, tagName) => { - try { - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${tagName}`, - sha: context.sha, - force: true, - }); - } catch (err) { - console.error(`Failed to move nightly tag.`); - console.error(`This should only happen the first time.`); - console.error(err); - } -}; diff --git a/.github/scripts/prune-prereleases.js b/.github/scripts/prune-prereleases.js deleted file mode 100644 index d0d6bf465e81a..0000000000000 --- a/.github/scripts/prune-prereleases.js +++ /dev/null @@ -1,68 +0,0 @@ -// In case node 21 is not used. -function groupBy(array, keyOrIterator) { - var iterator; - - // use the function passed in, or create one - if(typeof keyOrIterator !== 'function') { - const key = String(keyOrIterator); - iterator = function (item) { return item[key]; }; - } else { - iterator = keyOrIterator; - } - - return array.reduce(function (memo, item) { - const key = iterator(item); - memo[key] = memo[key] || []; - memo[key].push(item); - return memo; - }, {}); -} - -module.exports = async ({ github, context }) => { - console.log("Pruning old prereleases"); - - // doc: https://docs.github.com/en/rest/releases/releases - const { data: releases } = await github.rest.repos.listReleases({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - - let nightlies = releases.filter( - release => - // Only consider releases tagged `nightly-${SHA}` for deletion - release.tag_name.includes("nightly") && - release.tag_name !== "nightly" - ); - - // Pruning rules: - // 1. only keep the earliest (by created_at) release of the month - // 2. to keep the newest 30 nightlies (to make sure nightlies are kept until the next monthly release) - // Notes: - // - This addresses https://github.com/foundry-rs/foundry/issues/6732 - // - Name of the release may deviate from created_at due to the usage of different timezones. - - // Group releases by months. - // Per doc: - // > The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. - const groups = groupBy(nightlies, i => i.created_at.slice(0, 7)); - const nightliesToPrune = Object.values(groups) - .reduce((acc, cur) => acc.concat(cur.slice(0, -1)), []) // rule 1 - .slice(30); // rule 2 - - for (const nightly of nightliesToPrune) { - console.log(`Deleting nightly: ${nightly.tag_name}`); - await github.rest.repos.deleteRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: nightly.id, - }); - console.log(`Deleting nightly tag: ${nightly.tag_name}`); - await github.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${nightly.tag_name}`, - }); - } - - console.log("Done."); -}; diff --git a/.github/scripts/read-benchmark-results.sh b/.github/scripts/read-benchmark-results.sh new file mode 100644 index 0000000000000..548611a7d204a --- /dev/null +++ b/.github/scripts/read-benchmark-results.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +# Script to read benchmark results and emit them as GitHub Actions outputs. +# This script performs no git operations — it only reads the combined +# benchmark file and writes outputs for the workflow to consume. +# +# Usage: ./read-benchmark-results.sh + +OUTPUT_DIR="${1:-benches}" + +echo "Reading benchmark results from $OUTPUT_DIR..." + +if [ -f "$OUTPUT_DIR/LATEST.md" ]; then + # Output full results + { + echo 'results<> "$GITHUB_OUTPUT" + + # Format results for PR comment + echo "Formatting results for PR comment..." + FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") + + { + echo 'pr_comment<> "$GITHUB_OUTPUT" + + echo "Successfully read and formatted benchmark results" +else + echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" + echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" +fi diff --git a/.github/scripts/setup-foundryup.sh b/.github/scripts/setup-foundryup.sh new file mode 100755 index 0000000000000..4ac30d64ea72d --- /dev/null +++ b/.github/scripts/setup-foundryup.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -euo pipefail + +# Script to setup foundryup in CI environment +# This ensures foundryup is available in PATH for the benchmark binary + +echo "Setting up foundryup..." + +# Check if foundryup script exists in the repo +if [ ! -f "foundryup/foundryup" ]; then + echo "Error: foundryup/foundryup script not found in repository" + exit 1 +fi + +# Copy foundryup to a location in PATH +echo "Copying foundryup to /usr/local/bin..." +sudo cp foundryup/foundryup /usr/local/bin/foundryup +sudo chmod +x /usr/local/bin/foundryup + +# Verify foundryup is accessible +if ! command -v foundryup &> /dev/null; then + echo "Error: foundryup not found in PATH after installation" + exit 1 +fi + +echo "foundryup is now available at: $(which foundryup)" + +# Create foundry directories +echo "Creating foundry directories..." + +# Use FOUNDRY_DIR if set, otherwise default to $HOME/.foundry +FOUNDRY_DIR="${FOUNDRY_DIR:-$HOME/.foundry}" +echo "Using FOUNDRY_DIR: $FOUNDRY_DIR" + +# Create all necessary directories +mkdir -p "$FOUNDRY_DIR/bin" +mkdir -p "$FOUNDRY_DIR/versions" +mkdir -p "$FOUNDRY_DIR/share/man/man1" + +# Export PATH for current session +export PATH="$FOUNDRY_DIR/bin:$PATH" + +# Run foundryup to install default version +echo "Installing default foundry version..." +foundryup + +# Verify installation +if command -v forge &> /dev/null; then + echo "Forge installed successfully: $(forge --version)" +else + echo "Warning: forge not found in PATH after installation" +fi + +echo "Foundry setup complete!" \ No newline at end of file diff --git a/.github/scripts/shellcheck.sh b/.github/scripts/shellcheck.sh new file mode 100755 index 0000000000000..a0e6902d898e0 --- /dev/null +++ b/.github/scripts/shellcheck.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# runs shellcheck and prints GitHub Actions annotations for each warning and error +# https://github.com/koalaman/shellcheck + +IGNORE_DIRS=( + "./.git/*" + "./target/*" +) + +ignore_args=() +for dir in "${IGNORE_DIRS[@]}"; do + ignore_args+=(-not -path "$dir") +done + +find . -name "*.sh" "${ignore_args[@]}" -exec shellcheck -f gcc {} + | \ + while IFS=: read -r file line col severity msg; do + level="warning" + [[ "$severity" == *error* ]] && level="error" + file="${file#./}" + echo "::${level} file=${file},line=${line},col=${col}::${file}:${line}:${col}:${msg}" + done + +exit "${PIPESTATUS[0]}" diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh new file mode 100644 index 0000000000000..0de494fb7823c --- /dev/null +++ b/.github/scripts/tempo-check.sh @@ -0,0 +1,899 @@ +#!/bin/bash +set -euo pipefail + +# Get the directory where this script lives +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Non-verification tempo checks: local tests, fork tests, cast commands, DEX operations + +# Hardfork version, defaults to T3 (latest features) +HARDFORK="${TEMPO_HARDFORK:-T3}" + +# Fee token address, defaults to native fee token +FEE_TOKEN="${TEMPO_FEE_TOKEN:-0x20c0000000000000000000000000000000000000}" + +# Build fee token args if not using native token (array for safe expansion) +FEE_TOKEN_ARG=() +if [[ "$FEE_TOKEN" != "0x20c0000000000000000000000000000000000000" ]]; then + FEE_TOKEN_ARG=(--tempo.fee-token "$FEE_TOKEN") +fi + +echo -e "\n=== USING HARDFORK: $HARDFORK ===" +echo -e "=== USING FEE TOKEN: $FEE_TOKEN ===" + +# Fund an address and wait for the fee token balance to be non-zero +fund_and_wait() { + local addr="$1" + for i in {1..100}; do + OUT=$(cast rpc tempo_fundAddress "$addr" --rpc-url "$ETH_RPC_URL" 2>&1 || true) + if echo "$OUT" | jq -e 'arrays' >/dev/null 2>&1; then + echo "$OUT" | jq + break + fi + echo "[$i] $OUT" + sleep 0.2 + done + echo "Waiting for $addr to be funded..." + for i in {1..30}; do + BAL=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$addr" 2>/dev/null || echo "0") + if [[ "$BAL" != "0" && -n "$BAL" ]]; then + echo "Funded with $BAL fee tokens" + return 0 + fi + if [[ $i -eq 30 ]]; then + echo "ERROR: Funding timed out for $addr" + exit 1 + fi + sleep 1 + done +} + +echo -e "\n=== INIT TEMPO PROJECT ===" +tmp_dir=$(mktemp -d) +cd "$tmp_dir" +forge init -n tempo tempo-check +cd tempo-check + +echo -e "\n=== FORGE TEST (LOCAL) ===" +TEMPO_FEE_TOKEN='' forge test + +echo -e "\n=== FORGE SCRIPT (LOCAL) ===" +TEMPO_FEE_TOKEN='' forge script script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" + +echo -e "\n=== START TEMPO DEVNET TESTS ===" + +# Export fee token for fork tests (templates use vm.envOr to read it) +export TEMPO_FEE_TOKEN="$FEE_TOKEN" + +echo -e "\n=== TEMPO VERSION ===" +cast client --rpc-url "$ETH_RPC_URL" + +echo -e "\n=== FORGE TEST (DEVNET) ===" +forge test --rpc-url "$ETH_RPC_URL" + +echo -e "\n=== FORGE SCRIPT (DEVNET) ===" +forge script ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --rpc-url "$ETH_RPC_URL" + +echo -e "\n=== CREATE AND FUND ADDRESS ===" +wallet_json="$(cast wallet new --json)" +ADDR="$(jq -r '.[0].address' <<<"$wallet_json")" +PK="$(jq -r '.[0].private_key' <<<"$wallet_json")" +printf "address: %s\nprivate_key: %s\n" "$ADDR" "$PK" +fund_and_wait "$ADDR" + +echo -e "\n=== ADD AlphaUSD FEE TOKEN LIQUIDITY ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast send 0xfeec000000000000000000000000000000000000 'mint(address,address,uint256,address)' 0x20C0000000000000000000000000000000000001 0x20C0000000000000000000000000000000000000 1000000000 0x6c4143BEd3A13cf9E5E43d45C60aD816FC091d0c --private-key "$PK" --rpc-url "$ETH_RPC_URL" +else + echo "skipped (custom fee token set)" +fi + +echo -e "\n=== ADD BetaUSD FEE TOKEN LIQUIDITY ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast send 0xfeec000000000000000000000000000000000000 'mint(address,address,uint256,address)' 0x20C0000000000000000000000000000000000002 0x20C0000000000000000000000000000000000000 1000000000 0x6c4143BEd3A13cf9E5E43d45C60aD816FC091d0c --private-key "$PK" --rpc-url "$ETH_RPC_URL" +else + echo "skipped (custom fee token set)" +fi + +echo -e "\n=== ADD ThetaUSD FEE TOKEN LIQUIDITY ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast send 0xfeec000000000000000000000000000000000000 'mint(address,address,uint256,address)' 0x20C0000000000000000000000000000000000003 0x20C0000000000000000000000000000000000000 1000000000 0x6c4143BEd3A13cf9E5E43d45C60aD816FC091d0c --private-key "$PK" --rpc-url "$ETH_RPC_URL" +else + echo "skipped (custom fee token set)" +fi + +echo -e "\n=== CAST ERC20 TRANSFER WITH FEE TOKEN ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast erc20 transfer --tempo.fee-token 0x20C0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000002 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url "$ETH_RPC_URL" --private-key "$PK" + cast erc20 transfer --tempo.fee-token 0x20C0000000000000000000000000000000000003 0x20c0000000000000000000000000000000000002 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url "$ETH_RPC_URL" --private-key "$PK" +else + cast erc20 transfer ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} "${FEE_TOKEN}" 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url "$ETH_RPC_URL" --private-key "$PK" +fi + +echo -e "\n=== CAST ERC20 APPROVE WITH FEE TOKEN ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast erc20 approve --tempo.fee-token 0x20C0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000002 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url "$ETH_RPC_URL" --private-key "$PK" + cast erc20 approve --tempo.fee-token 0x20C0000000000000000000000000000000000003 0x20c0000000000000000000000000000000000002 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url "$ETH_RPC_URL" --private-key "$PK" +else + echo "skipped (custom fee token set)" +fi + +echo -e "\n=== CAST SEND WITH FEE TOKEN ===" +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + cast send --tempo.fee-token 0x20C0000000000000000000000000000000000002 --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" + cast send --tempo.fee-token 0x20C0000000000000000000000000000000000003 --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" +else + cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" +fi + +echo -e "\n=== CAST MKTX WITH FEE TOKEN ===" +cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" + +echo -e "\n=== CAST MKTX WITH NONCE-KEY (2D Nonce) ===" +# Each nonce-key has its own nonce sequence starting at 0 +cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --nonce 0 --tempo.nonce-key 1 + +echo -e "\n=== CAST SEND WITH NONCE-KEY (2D Nonce) ===" +# Use a different nonce-key (2) with nonce 0 since each key starts fresh +cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --nonce 0 --tempo.nonce-key 2 + +echo -e "\n=== CAST MKTX WITH EXPIRING NONCE (TIP-1009) ===" +# Use the node's block timestamp to avoid clock skew between CI runner and devnet. +BLOCK_TS=$(cast block latest --rpc-url "$ETH_RPC_URL" -f timestamp) +cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --tempo.expiring-nonce --tempo.valid-before "$((BLOCK_TS + 25))" + +echo -e "\n=== CAST SEND WITH EXPIRING NONCE (TIP-1009) ===" +BLOCK_TS=$(cast block latest --rpc-url "$ETH_RPC_URL" -f timestamp) +cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --tempo.expiring-nonce --tempo.valid-before "$((BLOCK_TS + 25))" + +echo -e "\n=== CAST MKTX WITH EXPIRING NONCE + VALID-AFTER ===" +BLOCK_TS=$(cast block latest --rpc-url "$ETH_RPC_URL" -f timestamp) +cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --tempo.expiring-nonce --tempo.valid-before "$((BLOCK_TS + 25))" --tempo.valid-after "$((BLOCK_TS + 5))" + +echo -e "\n=== CAST SEND WITH EXPIRING NONCE + VALID-AFTER ===" +sleep 6 # Wait for valid_after to pass +BLOCK_TS=$(cast block latest --rpc-url "$ETH_RPC_URL" -f timestamp) +cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" --tempo.expiring-nonce --tempo.valid-before "$((BLOCK_TS + 25))" --tempo.valid-after "$((BLOCK_TS - 1))" + +echo -e "\n=== SETUP ACCESS KEY ===" +# Create an access key for testing +access_wallet_json="$(cast wallet new --json)" +ACCESS_KEY="$(jq -r '.[0].private_key' <<<"$access_wallet_json")" +ACCESS_KEY_ADDR="$(jq -r '.[0].address' <<<"$access_wallet_json")" +printf "Access key address: %s\n" "$ACCESS_KEY_ADDR" + +# Authorize the access key on-chain first (required for gas estimation) +# Account Keychain precompile: 0xAAAAAAAA00000000000000000000000000000000 +# SignatureType: 0 = Secp256k1, Expiry: 1893456000 (year 2030), enforceLimits: false, limits: [], allowAnyCalls: true +if [[ "$HARDFORK" == "T2" ]]; then + # Legacy: authorizeKey with flat params (pre-T3) + cast send --rpc-url "$ETH_RPC_URL" 0xAAAAAAAA00000000000000000000000000000000 \ + 'authorizeKey(address,uint8,uint64,bool,(address,uint256)[])' \ + "$ACCESS_KEY_ADDR" 0 1893456000 false "[]" \ + --private-key "$PK" +else + # TIP-1011 (T3+): authorizeKey takes a KeyRestrictions struct + # KeyRestrictions = (uint64 expiry, bool enforceLimits, TokenLimit[] limits, bool allowAnyCalls, CallScope[] allowedCalls) + # TokenLimit = (address token, uint256 amount, uint64 period) + # CallScope = (address target, SelectorRule[] selectorRules) + # SelectorRule = (bytes4 selector, address[] recipients) + cast send --rpc-url "$ETH_RPC_URL" 0xAAAAAAAA00000000000000000000000000000000 \ + 'authorizeKey(address,uint8,(uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[])) ' \ + "$ACCESS_KEY_ADDR" 0 "(1893456000,false,[],true,[])" \ + --private-key "$PK" +fi + +# Fund the access key address (needed for gas) +fund_and_wait "$ACCESS_KEY_ADDR" + +echo -e "\n=== CAST MKTX WITH ACCESS-KEY ===" +# Use original address as root account (access key signs on behalf of root) +cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --tempo.access-key "$ACCESS_KEY" --tempo.root-account "$ADDR" + +echo -e "\n=== CAST SEND WITH ACCESS-KEY ===" +# Send transaction using the access key (Keychain signature wrapped in AA transaction) +cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --tempo.access-key "$ACCESS_KEY" --tempo.root-account "$ADDR" + +# --- cast keychain subcommand tests --- + +echo -e "\n=== CAST KEYCHAIN: AUTHORIZE ===" +kc_wallet_json="$(cast wallet new --json)" +KC_KEY_PK="$(jq -r '.[0].private_key' <<<"$kc_wallet_json")" +KC_KEY_ADDR="$(jq -r '.[0].address' <<<"$kc_wallet_json")" +printf "Keychain key address: %s\n" "$KC_KEY_ADDR" + +cast keychain auth "$KC_KEY_ADDR" secp256k1 1893456000 \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + +echo -e "\n=== CAST KEYCHAIN: KEY-INFO ===" +KC_INFO=$(cast keychain info "$ADDR" "$KC_KEY_ADDR" --rpc-url "$ETH_RPC_URL") +echo "$KC_INFO" +echo "$KC_INFO" | grep -q "secp256k1" + +echo -e "\n=== CAST KEYCHAIN: KEY-INFO --json ===" +KC_INFO_JSON=$(cast keychain info "$ADDR" "$KC_KEY_ADDR" --rpc-url "$ETH_RPC_URL" --json) +echo "$KC_INFO_JSON" | jq -e '.signatureType == "secp256k1"' + +echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH LIMIT ===" +kc_limited_json="$(cast wallet new --json)" +KC_LIMITED_ADDR="$(jq -r '.[0].address' <<<"$kc_limited_json")" +cast keychain auth "$KC_LIMITED_ADDR" secp256k1 1893456000 \ + --limit "$FEE_TOKEN:1000000000" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + +echo -e "\n=== CAST KEYCHAIN: REMAINING-LIMIT ===" +KC_REMAINING=$(cast keychain rl "$ADDR" "$KC_LIMITED_ADDR" "$FEE_TOKEN" --rpc-url "$ETH_RPC_URL") +echo "Remaining: $KC_REMAINING" +[[ "$KC_REMAINING" != "0" ]] || { echo "ERROR: expected non-zero limit"; exit 1; } + +echo -e "\n=== CAST KEYCHAIN: REMAINING-LIMIT --json ===" +KC_REMAINING_JSON=$(cast keychain rl "$ADDR" "$KC_LIMITED_ADDR" "$FEE_TOKEN" --rpc-url "$ETH_RPC_URL" --json) +echo "$KC_REMAINING_JSON" | jq -e '. != "0"' + +echo -e "\n=== CAST KEYCHAIN: UPDATE-LIMIT ===" +cast keychain ul "$KC_LIMITED_ADDR" "$FEE_TOKEN" 500000000 \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + +echo -e "\n=== CAST KEYCHAIN: VERIFY UPDATE-LIMIT ===" +KC_UPDATED=$(cast keychain rl "$ADDR" "$KC_LIMITED_ADDR" "$FEE_TOKEN" --rpc-url "$ETH_RPC_URL") +echo "Remaining after update: $KC_UPDATED" +[[ "$KC_UPDATED" == "500000000" ]] || { echo "ERROR: expected 500000000 after update-limit, got $KC_UPDATED"; exit 1; } + +echo -e "\n=== CAST KEYCHAIN: REVOKE ===" +cast keychain rev "$KC_KEY_ADDR" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + +# Verify revocation +KC_INFO_REV=$(cast keychain info "$ADDR" "$KC_KEY_ADDR" --rpc-url "$ETH_RPC_URL") +echo "$KC_INFO_REV" +echo "$KC_INFO_REV" | grep -q "revoked" + +echo -e "\n=== CAST KEYCHAIN: REVOKED KEY REJECTION ===" +# Fund the revoked key so failure is due to revocation, not gas +fund_and_wait "$KC_KEY_ADDR" +if cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_KEY_PK" --tempo.root-account "$ADDR" 2>&1; then + echo "ERROR: revoked key should have been rejected" + exit 1 +fi +echo "OK: revoked key correctly rejected" + +echo -e "\n=== CAST KEYCHAIN: DUPLICATE AUTHORIZE REJECTION ===" +# Try to authorize KC_LIMITED_ADDR again — should fail with KeyAlreadyExists +if cast keychain auth "$KC_LIMITED_ADDR" secp256k1 1893456000 \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} 2>&1; then + echo "ERROR: duplicate authorize should have been rejected" + exit 1 +fi +echo "OK: duplicate authorize correctly rejected" + +# --- T3-only scope / call-restriction tests --- +if [[ "$HARDFORK" == "T3" ]]; then + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH --scope (ADDRESS ONLY, UNRESTRICTED) ===" + kc_scoped_json="$(cast wallet new --json)" + KC_SCOPED_PK="$(jq -r '.[0].private_key' <<<"$kc_scoped_json")" + KC_SCOPED_ADDR="$(jq -r '.[0].address' <<<"$kc_scoped_json")" + cast keychain auth "$KC_SCOPED_ADDR" secp256k1 1893456000 \ + --scope 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + + echo -e "\n=== CAST KEYCHAIN: SCOPE ALLOWED TARGET ===" + fund_and_wait "$KC_SCOPED_ADDR" + cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_SCOPED_PK" --tempo.root-account "$ADDR" + echo "OK: scoped key allowed to call permitted target" + + echo -e "\n=== CAST KEYCHAIN: SCOPE BLOCKED TARGET ===" + if cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 'doesNotExist()' \ + --tempo.access-key "$KC_SCOPED_PK" --tempo.root-account "$ADDR" 2>&1; then + echo "ERROR: scoped key should have been blocked for disallowed target" + exit 1 + fi + echo "OK: scoped key correctly blocked for disallowed target" + + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH --scope + SELECTORS ===" + kc_sel_json="$(cast wallet new --json)" + KC_SEL_PK="$(jq -r '.[0].private_key' <<<"$kc_sel_json")" + KC_SEL_ADDR="$(jq -r '.[0].address' <<<"$kc_sel_json")" + cast keychain auth "$KC_SEL_ADDR" secp256k1 1893456000 \ + --scope "$FEE_TOKEN:transfer,approve" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: authorized key with selector-scoped restrictions" + + echo -e "\n=== CAST KEYCHAIN: SELECTOR-SCOPED TRANSFER ALLOWED ===" + fund_and_wait "$KC_SEL_ADDR" + cast erc20 transfer ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} "$FEE_TOKEN" \ + 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 100 \ + --rpc-url "$ETH_RPC_URL" \ + --tempo.access-key "$KC_SEL_PK" --tempo.root-account "$ADDR" + echo "OK: selector-scoped key allowed transfer on permitted TIP-20" + + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH --scopes JSON ===" + kc_json_json="$(cast wallet new --json)" + KC_JSON_ADDR="$(jq -r '.[0].address' <<<"$kc_json_json")" + cast keychain auth "$KC_JSON_ADDR" secp256k1 1893456000 \ + --scopes "[{\"target\":\"$FEE_TOKEN\",\"selectors\":[\"transfer\"]},{\"target\":\"0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D\"}]" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: authorized key with --scopes JSON" + + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH MULTIPLE LIMITS ===" + kc_multi_json="$(cast wallet new --json)" + KC_MULTI_ADDR="$(jq -r '.[0].address' <<<"$kc_multi_json")" + cast keychain auth "$KC_MULTI_ADDR" secp256k1 1893456000 \ + --limit "$FEE_TOKEN:1000000" \ + --limit "0x20C0000000000000000000000000000000000001:2000000" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + + # Verify both limits + KC_MULTI_RL1=$(cast keychain rl "$ADDR" "$KC_MULTI_ADDR" "$FEE_TOKEN" --rpc-url "$ETH_RPC_URL") + KC_MULTI_RL2=$(cast keychain rl "$ADDR" "$KC_MULTI_ADDR" 0x20C0000000000000000000000000000000000001 --rpc-url "$ETH_RPC_URL") + echo "Limit 1: $KC_MULTI_RL1 (expected 1000000), Limit 2: $KC_MULTI_RL2 (expected 2000000)" + [[ "$KC_MULTI_RL1" == "1000000" ]] || { echo "ERROR: limit 1 mismatch"; exit 1; } + [[ "$KC_MULTI_RL2" == "2000000" ]] || { echo "ERROR: limit 2 mismatch"; exit 1; } + + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH RAW HEX SELECTOR ===" + kc_hex_json="$(cast wallet new --json)" + KC_HEX_PK="$(jq -r '.[0].private_key' <<<"$kc_hex_json")" + KC_HEX_ADDR="$(jq -r '.[0].address' <<<"$kc_hex_json")" + # increment() selector = 0xd09de08a + cast keychain auth "$KC_HEX_ADDR" secp256k1 1893456000 \ + --scope "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D:0xd09de08a" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: authorized key with raw hex selector" + + echo -e "\n=== CAST KEYCHAIN: RAW HEX SELECTOR ALLOWED ===" + fund_and_wait "$KC_HEX_ADDR" + cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_HEX_PK" --tempo.root-account "$ADDR" + echo "OK: raw hex selector key allowed to call increment()" + + echo -e "\n=== CAST KEYCHAIN: SET-SCOPE ===" + # Create a new unrestricted key, then add scope restrictions via set-scope + kc_ss_json="$(cast wallet new --json)" + KC_SS_PK="$(jq -r '.[0].private_key' <<<"$kc_ss_json")" + KC_SS_ADDR="$(jq -r '.[0].address' <<<"$kc_ss_json")" + cast keychain auth "$KC_SS_ADDR" secp256k1 1893456000 \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + + # Now restrict it to only the counter contract + cast keychain ss "$KC_SS_ADDR" \ + --scope 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: set-scope applied" + + echo -e "\n=== CAST KEYCHAIN: SET-SCOPE ALLOWED ===" + fund_and_wait "$KC_SS_ADDR" + cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_SS_PK" --tempo.root-account "$ADDR" + echo "OK: set-scope key allowed to call permitted target" + + echo -e "\n=== CAST KEYCHAIN: SET-SCOPE BLOCKED ===" + if cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 'doesNotExist()' \ + --tempo.access-key "$KC_SS_PK" --tempo.root-account "$ADDR" 2>&1; then + echo "ERROR: set-scope key should have been blocked for disallowed target" + exit 1 + fi + echo "OK: set-scope key correctly blocked for disallowed target" + + echo -e "\n=== CAST KEYCHAIN: REMOVE-SCOPE (BEFORE — CALL SUCCEEDS) ===" + cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_SS_PK" --tempo.root-account "$ADDR" + echo "OK: call to scoped target succeeds before remove-scope" + + echo -e "\n=== CAST KEYCHAIN: REMOVE-SCOPE ===" + cast keychain rs "$KC_SS_ADDR" 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: remove-scope applied" + + echo -e "\n=== CAST KEYCHAIN: REMOVE-SCOPE (AFTER — CALL FAILS) ===" + if cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' \ + --tempo.access-key "$KC_SS_PK" --tempo.root-account "$ADDR" 2>&1; then + echo "ERROR: call should have been blocked after remove-scope" + exit 1 + fi + echo "OK: call correctly blocked after remove-scope" + + echo -e "\n=== CAST KEYCHAIN: AUTHORIZE WITH RECIPIENT RESTRICTION ===" + kc_recip_json="$(cast wallet new --json)" + KC_RECIP_PK="$(jq -r '.[0].private_key' <<<"$kc_recip_json")" + KC_RECIP_ADDR="$(jq -r '.[0].address' <<<"$kc_recip_json")" + # Only allow transfer to a specific recipient + ALLOWED_RECIPIENT="0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F" + cast keychain auth "$KC_RECIP_ADDR" secp256k1 1893456000 \ + --scope "$FEE_TOKEN:transfer@$ALLOWED_RECIPIENT" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: authorized key with recipient-restricted transfer" + + echo -e "\n=== CAST KEYCHAIN: RECIPIENT-SCOPED TRANSFER ALLOWED ===" + fund_and_wait "$KC_RECIP_ADDR" + cast erc20 transfer ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} "$FEE_TOKEN" \ + "$ALLOWED_RECIPIENT" 100 \ + --rpc-url "$ETH_RPC_URL" \ + --tempo.access-key "$KC_RECIP_PK" --tempo.root-account "$ADDR" + echo "OK: recipient-scoped transfer allowed to permitted recipient" + + echo -e "\n=== CAST KEYCHAIN: --scopes JSON WITH RECIPIENTS ===" + kc_jsonr_json="$(cast wallet new --json)" + KC_JSONR_ADDR="$(jq -r '.[0].address' <<<"$kc_jsonr_json")" + cast keychain auth "$KC_JSONR_ADDR" secp256k1 1893456000 \ + --scopes "[{\"target\":\"$FEE_TOKEN\",\"selectors\":[{\"selector\":\"transfer\",\"recipients\":[\"$ALLOWED_RECIPIENT\"]}]},{\"target\":\"0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D\"}]" \ + --rpc-url "$ETH_RPC_URL" --private-key "$PK" ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} + echo "OK: authorized key with --scopes JSON including recipients" +else + echo -e "\n=== SKIPPING T3-only scope/call-restriction tests (HARDFORK=$HARDFORK) ===" +fi # end T3-only scope tests + +echo -e "\n=== SETUP SPONSOR ===" +# Create a sponsor wallet for testing sponsored (gasless) transactions +sponsor_wallet_json="$(cast wallet new --json)" +SPONSOR_PK="$(jq -r '.[0].private_key' <<<"$sponsor_wallet_json")" +SPONSOR_ADDR="$(jq -r '.[0].address' <<<"$sponsor_wallet_json")" +printf "Sponsor address: %s\n" "$SPONSOR_ADDR" + +# Fund the sponsor address (sponsor pays gas) +fund_and_wait "$SPONSOR_ADDR" + +echo -e "\n=== CAST SEND WITH SPONSOR (--tempo.sponsor-signature) ===" +# Test sponsored transactions using pre-signed signature. +# Step 1: Get the fee_payer_signature_hash using --tempo.print-sponsor-hash +# Step 2: Sign it with the sponsor's private key +# Step 3: Send with --tempo.sponsor and --tempo.sponsor-signature + +# Step 1: Get the hash that the sponsor needs to sign +FEE_PAYER_HASH=$(cast mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" \ + --tempo.print-sponsor-hash) +printf "Fee payer signature hash: %s\n" "$FEE_PAYER_HASH" + +# Step 2: Sponsor signs the hash +SPONSOR_SIG=$(cast wallet sign --private-key "$SPONSOR_PK" "$FEE_PAYER_HASH" --no-hash) +printf "Sponsor signature: %s\n" "$SPONSOR_SIG" + +# Step 3: Send the sponsored transaction with the signature +RECEIPT=$(cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$PK" \ + --tempo.sponsor "$SPONSOR_ADDR" --tempo.sponsor-signature "$SPONSOR_SIG" --json) + +# Verify the fee_payer in the receipt matches the sponsor address +RECEIPT_FEE_PAYER=$(echo "$RECEIPT" | jq -r '.feePayer // .fee_payer // empty') +if [[ -z "$RECEIPT_FEE_PAYER" ]]; then + echo "ERROR: feePayer not found in receipt" + echo "Receipt: $RECEIPT" + exit 1 +fi + +# Normalize addresses for comparison (lowercase) +RECEIPT_FEE_PAYER_LOWER=$(echo "$RECEIPT_FEE_PAYER" | tr '[:upper:]' '[:lower:]') +SPONSOR_ADDR_LOWER=$(echo "$SPONSOR_ADDR" | tr '[:upper:]' '[:lower:]') +if [[ "$RECEIPT_FEE_PAYER_LOWER" != "$SPONSOR_ADDR_LOWER" ]]; then + echo "ERROR: Receipt feePayer ($RECEIPT_FEE_PAYER) does not match sponsor ($SPONSOR_ADDR)" + exit 1 +fi +echo "SUCCESS: Receipt feePayer ($RECEIPT_FEE_PAYER) matches sponsor address" + +# Batch transaction tests (available on all hardforks) +echo -e "\n=== CAST BATCH-MKTX (NATIVE BATCHING) ===" +# Build a batch transaction with multiple calls as a single type 0x76 transaction +cast batch-mktx ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --private-key "$PK" + +echo -e "\n=== CAST BATCH-SEND (NATIVE BATCHING) ===" +# Send a batch transaction with multiple calls as a single type 0x76 transaction +cast batch-send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --private-key "$PK" + +echo -e "\n=== CAST BATCH-SEND WITH VALUE SYNTAX (NATIVE BATCHING) ===" +# Test batch transaction with value syntax (currently using 0 value) +# TODO: Update to use non-zero value (e.g., 0.0001ether) once tempo#2294 is merged +# and the node supports per-call value transfers in batch transactions. +cast batch-send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D:0:increment()" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --private-key "$PK" + +echo -e "\n=== DEPLOY COUNTER WITH REQUIRE ===" +# Use CounterWithRequire.sol (has require(newNumber > 100)) for batch revert testing +cp "$SCRIPT_DIR/contracts/CounterWithRequire.sol" src/Counter.sol +forge build +REQUIRE_COUNTER_OUTPUT=$(forge create ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} src/Counter.sol:Counter --rpc-url "$ETH_RPC_URL" --private-key "$PK" --broadcast 2>&1) +echo "Deploy output: $REQUIRE_COUNTER_OUTPUT" +# Extract address from human-readable output (avoids jq parse errors from stderr log pollution) +REQUIRE_COUNTER=$(echo "$REQUIRE_COUNTER_OUTPUT" | sed -n 's/.*Deployed to: \(0x[a-fA-F0-9]*\).*/\1/p') +if [[ "$REQUIRE_COUNTER" == "null" || -z "$REQUIRE_COUNTER" ]]; then + echo "ERROR: Failed to deploy Counter with require" + exit 1 +fi +echo "Counter with require deployed at: $REQUIRE_COUNTER" + +echo -e "\n=== CAST BATCH-SEND REVERT TEST ===" +# Test that batch reverts atomically when one call fails +# setNumber(1) fails because require(newNumber > 100) +if cast batch-send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + --call "$REQUIRE_COUNTER::increment()" \ + --call "$REQUIRE_COUNTER::setNumber(uint256):1" \ + --private-key "$PK" 2>&1; then + echo "ERROR: Batch should have reverted but succeeded" + exit 1 +fi +echo "OK: Batch correctly reverted (setNumber(1) failed require > 100)" + +echo -e "\n=== CAST BATCH-SEND WITH ARGS AND ENCODED CALLDATA ===" +# Test batch with both function arguments and pre-encoded calldata +# First call: pre-encoded calldata for setNumber(200) +# Second call: function signature with args setNumber(101) +# Final number should be 101 (second call executes last) +ENCODED_CALLDATA=$(cast calldata "setNumber(uint256)" 200) +echo "Encoded calldata for setNumber(200): $ENCODED_CALLDATA" +cast batch-send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + --call "$REQUIRE_COUNTER::$ENCODED_CALLDATA" \ + --call "$REQUIRE_COUNTER::setNumber(uint256):101" \ + --private-key "$PK" + +NUMBER=$(cast call --rpc-url "$ETH_RPC_URL" "$REQUIRE_COUNTER" "number()(uint256)") +echo "Counter number after batch: $NUMBER (expected: 101)" + +echo -e "\n=== FORGE SCRIPT --BATCH (NATIVE BATCHING) ===" +# Create a script that calls multiple contracts and batch them into a single tx +# Use template file and substitute REQUIRE_COUNTER address +sed "s/\${REQUIRE_COUNTER}/${REQUIRE_COUNTER}/" "$SCRIPT_DIR/contracts/BatchTest.s.sol.template" > script/BatchTest.s.sol + +# Get number before batch +NUMBER_BEFORE=$(cast call --rpc-url "$ETH_RPC_URL" "$REQUIRE_COUNTER" "number()(uint256)") +echo "Counter number before forge script --batch: $NUMBER_BEFORE" + +# Run forge script with --batch flag +forge script script/BatchTest.s.sol --broadcast --batch ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" --private-key "$PK" + +# Verify all calls executed atomically +NUMBER_AFTER=$(cast call --rpc-url "$ETH_RPC_URL" "$REQUIRE_COUNTER" "number()(uint256)") +echo "Counter number after forge script --batch: $NUMBER_AFTER (expected: 503)" +if [[ "$NUMBER_AFTER" != "503" ]]; then + echo "ERROR: Expected number to be 503 (500 + 3 increments), got $NUMBER_AFTER" + exit 1 +fi +echo "OK: forge script --batch executed all calls atomically" + +echo -e "\n=== FORGE SCRIPT --BATCH WITH DEPLOY + CALLS ===" +# Test deploying a contract and calling it in the same batch transaction +# This tests the CREATE + CALL pattern (CREATE must be first) +cp "$SCRIPT_DIR/contracts/BatchCounter.sol" src/BatchCounter.sol +cp "$SCRIPT_DIR/contracts/DeployAndCall.s.sol" script/DeployAndCall.s.sol + +forge build + +# Build verification args if VERIFIER_URL is set (same pattern as tempo-deploy.sh) +VERIFY_ARG=() +if [[ -n "${VERIFIER_URL:-}" ]]; then + VERIFY_ARG=(--verify --retries 10 --delay 10) + echo "Will verify deployed contract via $VERIFIER_URL" +fi + +# Run forge script with --batch flag - deploys and calls atomically +forge script script/DeployAndCall.s.sol --broadcast --batch ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} --rpc-url "$ETH_RPC_URL" --private-key "$PK" + +echo "OK: forge script --batch with deploy + calls executed atomically" + +echo -e "\n=== FORGE SCRIPT --BATCH REVERT TEST ===" +# Test that batch reverts atomically when one call in the script fails +# Use template file and substitute REQUIRE_COUNTER address +sed "s/\${REQUIRE_COUNTER}/${REQUIRE_COUNTER}/" "$SCRIPT_DIR/contracts/BatchRevertTest.s.sol.template" > script/BatchRevertTest.s.sol + +NUMBER_BEFORE_REVERT=$(cast call --rpc-url "$ETH_RPC_URL" "$REQUIRE_COUNTER" "number()(uint256)") +echo "Counter number before batch revert test: $NUMBER_BEFORE_REVERT" + +if forge script script/BatchRevertTest.s.sol --broadcast --batch ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" --private-key "$PK" 2>&1; then + echo "ERROR: Batch script should have reverted but succeeded" + exit 1 +fi + +# Verify number unchanged (atomic revert) +NUMBER_AFTER_REVERT=$(cast call --rpc-url "$ETH_RPC_URL" "$REQUIRE_COUNTER" "number()(uint256)") +echo "Counter number after batch revert: $NUMBER_AFTER_REVERT (expected: $NUMBER_BEFORE_REVERT - unchanged)" +if [[ "$NUMBER_AFTER_REVERT" != "$NUMBER_BEFORE_REVERT" ]]; then + echo "ERROR: Expected number to remain $NUMBER_BEFORE_REVERT after atomic revert, got $NUMBER_AFTER_REVERT" + exit 1 +fi +echo "OK: forge script --batch correctly reverted atomically" + +echo -e "\n=== DEPLOY HIGH GAS CONTRACT ===" +# Deploy a contract that can burn ~15M gas via cold storage writes (mapping) +# Each cold SSTORE to a new slot costs ~22,000 gas; ~650 iterations ≈ 15M gas +cat > src/GasBurner.sol <<'SOLEOF' +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; +contract GasBurner { + mapping(uint256 => uint256) public values; + function burn(uint256 iterations) public { + for (uint256 i; i < iterations; i++) { + values[i] = i; + } + } +} +SOLEOF +forge build + +GAS_BURNER_OUTPUT=$(forge create ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} src/GasBurner.sol:GasBurner --rpc-url "$ETH_RPC_URL" --private-key "$PK" --broadcast 2>&1) +echo "Deploy output: $GAS_BURNER_OUTPUT" +GAS_BURNER=$(echo "$GAS_BURNER_OUTPUT" | sed -n 's/.*Deployed to: \(0x[a-fA-F0-9]*\).*/\1/p') +GAS_BURNER_TX=$(echo "$GAS_BURNER_OUTPUT" | sed -n 's/.*Transaction hash: \(0x[a-fA-F0-9]*\).*/\1/p') +if [[ -z "$GAS_BURNER" ]]; then + echo "ERROR: Failed to deploy GasBurner" + exit 1 +fi +echo "GasBurner deployed at: $GAS_BURNER (tx: $GAS_BURNER_TX)" + +echo -e "\n=== CAST SEND HIGH GAS TX (~15M gas) ===" +GAS_BURN_RECEIPT=$(cast send ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} --rpc-url "$ETH_RPC_URL" \ + "$GAS_BURNER" 'burn(uint256)' 650 --gas-limit 15000000 --private-key "$PK" --json) +GAS_BURN_TX=$(echo "$GAS_BURN_RECEIPT" | jq -r '.transactionHash') +echo "High gas tx: $GAS_BURN_TX" +GAS_USED=$(echo "$GAS_BURN_RECEIPT" | jq -r '.gasUsed') +GAS_USED_DEC=$((GAS_USED)) +echo "Gas used: $GAS_USED_DEC" + +echo -e "\n=== DEPLOY LARGE CONTRACT ===" +# Deploy a contract at exactly the 24KB EIP-170 code size limit (24576 bytes deployed bytecode). +# 24174 bytes of padding + contract overhead = exactly 24576 bytes deployed. +PADDING=$(python3 -c "print('ff' * 24174)") +cat > src/MaxSizeContract.sol <&1) +echo "Deploy output: $MAX_SIZE_OUTPUT" +MAX_SIZE_ADDR=$(echo "$MAX_SIZE_OUTPUT" | sed -n 's/.*Deployed to: \(0x[a-fA-F0-9]*\).*/\1/p') +if [[ -z "$MAX_SIZE_ADDR" ]]; then + echo "ERROR: Failed to deploy MaxSizeContract" + exit 1 +fi +echo "MaxSizeContract deployed at: $MAX_SIZE_ADDR" + +# Verify deployed code size hits the 24KB EIP-170 limit +# cast code returns hex string with 0x prefix; subtract 2 for prefix, divide by 2 for bytes +CODE_HEX=$(cast code --rpc-url "$ETH_RPC_URL" "$MAX_SIZE_ADDR") +CODE_SIZE=$(( (${#CODE_HEX} - 2) / 2 )) +echo "Deployed code size: $CODE_SIZE bytes (limit: 24576)" +if [[ $CODE_SIZE -ne 24576 ]]; then + echo "ERROR: Deployed code size $CODE_SIZE != 24576 (expected exactly the EIP-170 limit)" + exit 1 +fi +echo "OK: Large contract deployed at exactly the EIP-170 limit ($CODE_SIZE bytes)" + +# Verify the contract is callable +PING_RESULT=$(cast call --rpc-url "$ETH_RPC_URL" "$MAX_SIZE_ADDR" 'ping()(uint256)') +if [[ "$PING_RESULT" != "1" ]]; then + echo "ERROR: ping() returned $PING_RESULT, expected 1" + exit 1 +fi +echo "OK: MaxSizeContract ping() returned 1" + +# Skip DEX/liquidity tests when using custom fee token (they assume multiple fee tokens) +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + echo -e "\n=== CHANGE USER DEFAULT FEE TOKEN ===" + cast send --rpc-url "$ETH_RPC_URL" 0xfeec000000000000000000000000000000000000 'setUserToken(address)' 0x20C0000000000000000000000000000000000002 --private-key "$PK" + cast send --rpc-url "$ETH_RPC_URL" 0xfeec000000000000000000000000000000000000 'setUserToken(address)' 0x20C0000000000000000000000000000000000000 --private-key "$PK" + + echo -e "\n=== ADD LIQUIDITY: APPROVE DEX ===" + cast erc20 approve 0x20c0000000000000000000000000000000000002 0xdec0000000000000000000000000000000000000 10000000000 --rpc-url "$ETH_RPC_URL" --private-key "$PK" + cast erc20 approve 0x20c0000000000000000000000000000000000000 0xdec0000000000000000000000000000000000000 10000000000 --rpc-url "$ETH_RPC_URL" --private-key "$PK" + + echo -e "\n=== ADD LIQUIDITY: PLACE BID ===" + cast send 0xdec0000000000000000000000000000000000000 "place(address,uint128,bool,int16)" 0x20c0000000000000000000000000000000000002 100000000 true 10 --private-key "$PK" -r "$ETH_RPC_URL" + + echo -e "\n=== ADD LIQUIDITY: PLACE ASK ===" + cast send 0xdec0000000000000000000000000000000000000 "place(address,uint128,bool,int16)" 0x20c0000000000000000000000000000000000002 100000000 false 10 --private-key "$PK" -r "$ETH_RPC_URL" + + echo -e "\n=== ADD LIQUIDITY: PLACE FLIP ===" + cast send 0xdec0000000000000000000000000000000000000 "placeFlip(address,uint128,bool,int16,int16)" 0x20c0000000000000000000000000000000000002 100000000 true -10 10 --private-key "$PK" -r "$ETH_RPC_URL" + + echo -e "\n=== ADD LIQUIDITY: SWAP EXACT AMOUNT IN ===" + cast send 0xdec0000000000000000000000000000000000000 "swapExactAmountIn(address,address,uint128,uint128)" 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000002 100000000 9000000 --private-key "$PK" -r "$ETH_RPC_URL" + + echo -e "\n=== ADD LIQUIDITY: SWAP EXACT AMOUNT OUT ===" + cast send 0xdec0000000000000000000000000000000000000 "swapExactAmountOut(address,address,uint128,uint128)" 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000000 9000000 100000000 --private-key "$PK" -r "$ETH_RPC_URL" +else + echo -e "\n=== CHANGE USER DEFAULT FEE TOKEN ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: APPROVE DEX ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: PLACE BID ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: PLACE ASK ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: PLACE FLIP ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: SWAP EXACT AMOUNT IN ===" + echo "skipped (custom fee token set)" + + echo -e "\n=== ADD LIQUIDITY: SWAP EXACT AMOUNT OUT ===" + echo "skipped (custom fee token set)" +fi + +echo -e "\n=== ANVIL LOCAL TESTS ===" + +ANVIL_PORT=8546 +echo "Starting local anvil..." +# Pass hardfork to anvil (lowercase for CLI compatibility) +ANVIL_HARDFORK=$(echo "$HARDFORK" | tr '[:upper:]' '[:lower:]') +anvil --tempo --hardfork "$ANVIL_HARDFORK" --port $ANVIL_PORT & +ANVIL_PID=$! + +# Ensure anvil is stopped on script exit +trap 'kill "$ANVIL_PID" 2>/dev/null || true' EXIT + +# Wait for anvil to be ready (max 10 seconds) +for i in {1..10}; do + if cast client --rpc-url "http://127.0.0.1:$ANVIL_PORT" 2>/dev/null; then + echo "Anvil fork started successfully" + break + fi + if [[ $i -eq 10 ]]; then + echo "ERROR: Anvil fork failed to start" + exit 1 + fi + sleep 1 +done + +ALICE_PK="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +echo -e "\n=== ANVIL LOCAL: CHECK CLIENT VERSION ===" +cast client --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL LOCAL: CHECK CHAIN ID ===" +cast chain-id --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL LOCAL: FORGE TEST ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge test --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL LOCAL: FORGE SCRIPT SIMULATE ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge script ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$ALICE_PK" + +echo -e "\n=== ANVIL LOCAL: FORGE SCRIPT BROADCAST ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge script ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$ALICE_PK" --broadcast + +echo -e "\n=== ANVIL LOCAL: CAST SEND ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$ALICE_PK" + +echo -e "\n=== ANVIL LOCAL: ERC20 TRANSFER ===" +cast erc20 transfer --tempo.fee-token "$FEE_TOKEN" 0x20c0000000000000000000000000000000000000 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$ALICE_PK" + +echo -e "\n=== ANVIL LOCAL: CAST SEND WITH NONCE-KEY (2D Nonce) ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$ALICE_PK" --nonce 0 --tempo.nonce-key 100 + +echo -e "\n=== ANVIL LOCAL: CAST SEND WITH EXPIRING NONCE ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$ALICE_PK" --tempo.expiring-nonce --tempo.valid-before "$(($(date +%s) + 25))" + +echo -e "\n=== ANVIL LOCAL: BATCH SEND ===" +cast batch-send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --private-key "$ALICE_PK" + +# Stop anvil +kill "$ANVIL_PID" 2>/dev/null || true +trap - EXIT + +echo -e "\n=== ANVIL LOCAL TESTS COMPLETE ===" + +echo -e "\n=== ANVIL FORK TESTS ===" +# Use a fresh wallet for fork tests to avoid fee token exhaustion from prior devnet tests +echo -e "\n=== ANVIL FORK: CREATE AND FUND FRESH WALLET ===" +fork_wallet_json="$(cast wallet new --json)" +FORK_ADDR="$(jq -r '.[0].address' <<<"$fork_wallet_json")" +FORK_PK="$(jq -r '.[0].private_key' <<<"$fork_wallet_json")" +printf "Fork test address: %s\n" "$FORK_ADDR" +fund_and_wait "$FORK_ADDR" + +# Set the fee token on devnet before forking so the fork snapshot includes it +cast send --rpc-url "$ETH_RPC_URL" 0xfeec000000000000000000000000000000000000 \ + 'setUserToken(address)' "$FEE_TOKEN" --private-key "$FORK_PK" + +ANVIL_PORT=8547 +echo "Starting forked anvil..." +# Pass hardfork to anvil (lowercase for CLI compatibility) +ANVIL_HARDFORK=$(echo "$HARDFORK" | tr '[:upper:]' '[:lower:]') +anvil --tempo --hardfork "$ANVIL_HARDFORK" --fork-url "$ETH_RPC_URL" --port $ANVIL_PORT --retries 10 --timeout 60000 & +ANVIL_PID=$! + +# Ensure anvil is stopped on script exit +trap 'kill "$ANVIL_PID" 2>/dev/null || true' EXIT + +# Wait for anvil to be ready (max 10 seconds) +for i in {1..10}; do + if cast client --rpc-url "http://127.0.0.1:$ANVIL_PORT" 2>/dev/null; then + echo "Anvil fork started successfully" + break + fi + if [[ $i -eq 10 ]]; then + echo "ERROR: Anvil fork failed to start" + exit 1 + fi + sleep 1 +done + +echo -e "\n=== ANVIL FORK: CHECK CLIENT VERSION ===" +cast client --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL FORK: CHECK CHAIN ID ===" +cast chain-id --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL FORK: CHECK BLOCK NUMBER ===" +cast block-number --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL FORK: FORGE TEST ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge test --rpc-url http://127.0.0.1:$ANVIL_PORT + +echo -e "\n=== ANVIL FORK: FORGE SCRIPT SIMULATE ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge script --tempo.fee-token "$FEE_TOKEN" script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$FORK_PK" + +echo -e "\n=== ANVIL FORK: FORGE SCRIPT BROADCAST ===" +TEMPO_FEE_TOKEN="$FEE_TOKEN" forge script --tempo.fee-token "$FEE_TOKEN" script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$FORK_PK" --broadcast + +echo -e "\n=== ANVIL FORK: CAST SEND ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$FORK_PK" + +echo -e "\n=== ANVIL FORK: ERC20 TRANSFER ===" +cast erc20 transfer --tempo.fee-token "$FEE_TOKEN" 0x20c0000000000000000000000000000000000000 0x4ef5DFf69C1514f4Dbf85aA4F9D95F804F64275F 123456 --rpc-url http://127.0.0.1:$ANVIL_PORT --private-key "$FORK_PK" + +echo -e "\n=== ANVIL FORK: CAST SEND WITH NONCE-KEY (2D Nonce) ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$FORK_PK" --nonce 0 --tempo.nonce-key 100 + +echo -e "\n=== ANVIL FORK: CAST SEND WITH EXPIRING NONCE ===" +cast send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT 0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D 'increment()' --private-key "$FORK_PK" --tempo.expiring-nonce --tempo.valid-before "$(($(date +%s) + 25))" + +echo -e "\n=== ANVIL FORK: BATCH SEND ===" +cast batch-send --tempo.fee-token "$FEE_TOKEN" --rpc-url http://127.0.0.1:$ANVIL_PORT \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --call "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D::increment()" \ + --private-key "$FORK_PK" + +# Stop anvil +kill "$ANVIL_PID" 2>/dev/null || true +trap - EXIT + +echo -e "\n=== ANVIL FORK TESTS COMPLETE ===" + +echo -e "\n=== CHISEL FORK TESTS ===" +# Test chisel forking the Tempo network - precompiles should be accessible from fork + +# Helper to check address has code via chisel fork +check_has_code() { + local name="$1" addr="$2" + local result + result=$(chisel --fork-url "$ETH_RPC_URL" eval "address($addr).code.length > 0" 2>&1 | grep "Value:" | awk '{print $NF}') + if [[ "$result" != "true" ]]; then + echo "ERROR: $name ($addr) should have code when forking Tempo" + exit 1 + fi + echo "OK: $name has code" +} + +check_has_code "PathUSD" "0x20C0000000000000000000000000000000000000" +check_has_code "AlphaUSD" "0x20C0000000000000000000000000000000000001" +check_has_code "Nonce" "0x4e4F4E4345000000000000000000000000000000" +check_has_code "AccountKeychain" "0xaAAAaaAA00000000000000000000000000000000" + +echo -e "\n=== CHISEL FORK TESTS COMPLETE ===" diff --git a/.github/scripts/tempo-deploy.sh b/.github/scripts/tempo-deploy.sh new file mode 100644 index 0000000000000..850ed33fce86a --- /dev/null +++ b/.github/scripts/tempo-deploy.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -euo pipefail + +# Deployment checks: forge script and forge create with optional --verify flag +# If VERIFIER_URL is set, adds --verify flag to deployment commands + +# Fee token address, defaults to native fee token +FEE_TOKEN="${TEMPO_FEE_TOKEN:-0x20c0000000000000000000000000000000000000}" + +# Build fee token args if not using native token (array for safe expansion) +FEE_TOKEN_ARG=() +if [[ "$FEE_TOKEN" != "0x20c0000000000000000000000000000000000000" ]]; then + FEE_TOKEN_ARG=(--tempo.fee-token "$FEE_TOKEN") +fi + +# If VERIFIER_URL is set, add the --verify flag to forge commands +VERIFY_ARG=() +if [[ -n "${VERIFIER_URL:-}" ]]; then + VERIFY_ARG=(--verify --retries 10 --delay 10) + echo -e "\n=== USING VERIFIER: $VERIFIER_URL ===" +else + echo -e "\n=== VERIFIER_URL not set, skipping verification ===" +fi + +echo -e "\n=== USING FEE TOKEN: $FEE_TOKEN ===" + +echo -e "\n=== INIT TEMPO PROJECT ===" +tmp_dir=$(mktemp -d) +cd "$tmp_dir" +forge init -n tempo tempo-deploy +cd tempo-deploy + +if [[ -n "${PRIVATE_KEY:-}" ]]; then + echo -e "\n=== USING PROVIDED PRIVATE KEY ===" + PK="$PRIVATE_KEY" + ADDR="$(cast wallet address "$PK")" + printf "\naddress: %s\n" "$ADDR" +else + echo -e "\n=== CREATE AND FUND ADDRESS ===" + wallet_json="$(cast wallet new --json)" + ADDR="$(jq -r '.[0].address' <<<"$wallet_json")" + PK="$(jq -r '.[0].private_key' <<<"$wallet_json")" + + for i in {1..100}; do + OUT=$(cast rpc tempo_fundAddress "$ADDR" --rpc-url "$ETH_RPC_URL" 2>&1 || true) + + if echo "$OUT" | jq -e 'arrays' >/dev/null 2>&1; then + echo "$OUT" | jq + break + fi + + echo "[$i] $OUT" + sleep 0.2 + done + + printf "\naddress: %s\nprivate_key: %s\n" "$ADDR" "$PK" + + echo -e "\n=== WAIT FOR BLOCKS TO MINE ===" + sleep 5 +fi + +echo -e "\n=== FORGE SCRIPT DEPLOY ===" +forge script ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} + +echo -e "\n=== FORGE SCRIPT DEPLOY WITH FEE TOKEN ===" +forge script ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} script/Mail.s.sol --sig "run(string)" "$(date +%s%N)" --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} + +echo -e "\n=== FORGE CREATE DEPLOY ===" +forge create ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} src/Mail.sol:Mail --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} --constructor-args "$FEE_TOKEN" + +echo -e "\n=== FORGE CREATE DEPLOY WITH FEE TOKEN ===" +CHAIN_ID=$(cast chain-id --rpc-url "$ETH_RPC_URL") +if [[ ${#FEE_TOKEN_ARG[@]} -eq 0 ]]; then + if [[ "$CHAIN_ID" == "4217" ]]; then + echo "Skipping alternate fee token test on mainnet (chain 4217)" + else + # Test alternate fee tokens only on testnet + forge create --tempo.fee-token 0x20C0000000000000000000000000000000000002 src/Mail.sol:Mail --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} --constructor-args "$FEE_TOKEN" + forge create --tempo.fee-token 0x20C0000000000000000000000000000000000003 src/Mail.sol:Mail --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} --constructor-args "$FEE_TOKEN" + fi +else + forge create ${FEE_TOKEN_ARG[@]+"${FEE_TOKEN_ARG[@]}"} src/Mail.sol:Mail --private-key "$PK" --rpc-url "$ETH_RPC_URL" --broadcast ${VERIFY_ARG[@]+"${VERIFY_ARG[@]}"} --constructor-args "$FEE_TOKEN" +fi + +echo -e "\n=== DEPLOYMENT TESTS COMPLETE ===" diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh new file mode 100644 index 0000000000000..33da016fb683c --- /dev/null +++ b/.github/scripts/tempo-mpp.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash +# MPP (Machine Payments Protocol) end-to-end test script +# +# Prerequisites: +# - Either `TEMPO_PRIVATE_KEY` is set, or Tempo wallet is configured: `tempo wallet login` +# - Wallet funded with TEMPO on moderato testnet +# - Foundry binaries built: `cargo build --bin cast --bin forge --bin anvil --bin chisel` +# +# Usage: +# ./scripts/mpp-test.sh [binary-dir] +# +# Examples: +# ./scripts/mpp-test.sh # uses cast/forge from PATH +# ./scripts/mpp-test.sh ./target/debug # use debug builds + +set -euo pipefail + +BIN_DIR="${1:-}" +if [ -n "$BIN_DIR" ]; then + BIN_DIR="$(cd "$BIN_DIR" && pwd)" + CAST="$BIN_DIR/cast" + FORGE="$BIN_DIR/forge" + ANVIL="$BIN_DIR/anvil" + CHISEL="$BIN_DIR/chisel" +else + CAST="cast" + FORGE="forge" + ANVIL="anvil" + CHISEL="chisel" +fi +export MPP_DEPOSIT="${MPP_DEPOSIT:-100000}" +TEMPO_AUTO_FUND="${TEMPO_AUTO_FUND:-0}" +TEMPO_AUTO_FUND_ATTEMPTS="${TEMPO_AUTO_FUND_ATTEMPTS:-3}" +MIN_BALANCE="${MPP_MIN_BALANCE:-$((MPP_DEPOSIT + 50000))}" +RPC_MPP="https://rpc.mpp.moderato.tempo.xyz" +RPC="https://rpc.moderato.tempo.xyz" +TOKEN="0x20c0000000000000000000000000000000000000" # TEMPO TIP-20 + +if ! command -v "$CAST" &>/dev/null; then + echo "ERROR: cast binary not found at '$CAST'. Install with: foundryup" + exit 1 +fi +if ! command -v "$FORGE" &>/dev/null; then + echo "ERROR: forge binary not found at '$FORGE'. Install with: foundryup" + exit 1 +fi +if ! command -v "$ANVIL" &>/dev/null; then + echo "ERROR: anvil binary not found at '$ANVIL'. Install with: foundryup" + exit 1 +fi +if ! command -v "$CHISEL" &>/dev/null; then + echo "ERROR: chisel binary not found at '$CHISEL'. Install with: foundryup" + exit 1 +fi + +KEYS_FILE="${TEMPO_HOME:-$HOME/.tempo}/wallet/keys.toml" +if [ -n "${TEMPO_PRIVATE_KEY:-}" ]; then + WALLET=$("$CAST" wallet address --private-key "$TEMPO_PRIVATE_KEY") +elif [ -f "$KEYS_FILE" ]; then + WALLET=$(grep -m1 'wallet_address' "$KEYS_FILE" | sed 's/.*= *"\(.*\)"/\1/') +else + echo "ERROR: Set TEMPO_PRIVATE_KEY or configure Tempo wallet with: tempo wallet login" + exit 1 +fi +echo "Wallet: $WALLET" +echo "RPC: $RPC_MPP" +echo "" +WALLET_LOWER=$(printf '%s' "$WALLET" | tr '[:upper:]' '[:lower:]') + +# 1. Check balance before +echo "=== 1. Balance BEFORE ===" +BEFORE=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC") +echo "$BEFORE" +BEFORE_RAW=$(echo "$BEFORE" | awk '{print $1}') + +if [ "$BEFORE_RAW" -lt "$MIN_BALANCE" ] && [ "$TEMPO_AUTO_FUND" = "1" ]; then + echo "Balance below threshold, requesting faucet funds for $WALLET_LOWER" + ATTEMPT=0 + while [ "$BEFORE_RAW" -lt "$MIN_BALANCE" ] && [ "$ATTEMPT" -lt "$TEMPO_AUTO_FUND_ATTEMPTS" ]; do + ATTEMPT=$((ATTEMPT + 1)) + echo "Faucet attempt $ATTEMPT/$TEMPO_AUTO_FUND_ATTEMPTS" + "$CAST" rpc tempo_fundAddress "$WALLET_LOWER" --rpc-url "$RPC" >/dev/null + sleep 2 + BEFORE=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC") + echo "$BEFORE" + BEFORE_RAW=$(echo "$BEFORE" | awk '{print $1}') + done +fi + +if [ "$BEFORE_RAW" -lt "$MIN_BALANCE" ]; then + echo "ERROR: Wallet balance too low for MPP e2e. Need at least $MIN_BALANCE units of $TOKEN, got $BEFORE_RAW. Refill the CI wallet." + exit 1 +fi + +# 2. Call block-number through MPP-gated endpoint +echo "" +echo "=== 2. cast block-number (via MPP) ===" +FROM_BLOCK=$("$CAST" block-number --rpc-url "$RPC") +BLOCK=$("$CAST" block-number --rpc-url "$RPC_MPP") +echo "Block: $BLOCK" + +# Wait for channel open tx to settle (2 blocks ≈ 6s) +echo "Waiting for channel open tx to settle..." +sleep 6 + +# 3. Check balance after +echo "" +echo "=== 3. Balance AFTER ===" +AFTER=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC") +echo "$AFTER" + +AFTER_RAW=$(echo "$AFTER" | awk '{print $1}') +SPENT=$((BEFORE_RAW - AFTER_RAW)) +echo "Spent: $SPENT units (channel deposit + gas)" + +# 4. Find and inspect the escrow transaction +echo "" +echo "=== 4. Escrow transaction ===" +TX=$("$CAST" logs --from-block "$FROM_BLOCK" --to-block latest \ + --address 0xe1c4d3dce17bc111181ddf716f75bae49e61a336 \ + --rpc-url "$RPC" | grep transactionHash | tail -1 | awk '{print $2}' || true) + +if [ -n "$TX" ]; then + echo "Tx: $TX" + "$CAST" tx "$TX" --rpc-url "$RPC" +else + echo "No new escrow tx (channel reused from previous session)" +fi + +# 5. Verify a second call reuses the channel (no new deposit) +echo "" +echo "=== 5. Second call (channel reuse) ===" +BEFORE2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') +BLOCK2=$("$CAST" block-number --rpc-url "$RPC_MPP") +AFTER2=$("$CAST" erc20 balance "$TOKEN" "$WALLET" --rpc-url "$RPC" | awk '{print $1}') +SPENT2=$((BEFORE2 - AFTER2)) +echo "Block: $BLOCK2" +echo "Spent: $SPENT2 units (should be 0 — channel reused from ~/.tempo/channels.db)" + +# 6. forge script via MPP +echo "" +echo "=== 6. forge script (via MPP) ===" +TMPDIR=$(mktemp -d) +trap 'rm -rf $TMPDIR' EXIT +(cd "$TMPDIR" && "$FORGE" init --no-git --quiet) +cat > "$TMPDIR/script/Mpp.s.sol" <<'SOL' +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "forge-std/Script.sol"; +contract MppCheck is Script { + function run() public view { + console.log("block", block.number); + console.log("chain", block.chainid); + } +} +SOL +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") +"$FORGE" script "$TMPDIR/script/Mpp.s.sol" --rpc-url "$RPC_MPP" --root "$TMPDIR" +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") +echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" + +# 7. forge test with vm.createSelectFork via MPP +echo "" +echo "=== 7. forge test with createSelectFork (via MPP) ===" +cat > "$TMPDIR/test/Mpp.t.sol" </dev/null; then break; fi + sleep 1 +done +echo "chain-id: $("$CAST" chain-id --rpc-url http://localhost:8555)" +kill $ANVIL_PID 2>/dev/null +wait $ANVIL_PID 2>/dev/null +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") +echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" + +# 9. chisel fork via MPP +echo "" +echo "=== 9. chisel --fork-url (via MPP) ===" +VCNT_BEFORE=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") +echo 'block.number' | "$CHISEL" --fork-url "$RPC_MPP" 2>&1 | grep -E "Decimal|Type" +VCNT_AFTER=$(sqlite3 ~/.tempo/channels.db "SELECT cumulative_amount FROM channels LIMIT 1") +echo "Vouchers paid: +$((VCNT_AFTER - VCNT_BEFORE)) ($((( VCNT_AFTER - VCNT_BEFORE ) / 1000)) RPC calls via MPP)" + +echo "" +echo "=== Done ===" diff --git a/.github/scripts/tempo-wallet.sh b/.github/scripts/tempo-wallet.sh new file mode 100644 index 0000000000000..784c708b04327 --- /dev/null +++ b/.github/scripts/tempo-wallet.sh @@ -0,0 +1,95 @@ +#!/bin/bash +set -euo pipefail + +# Tempo wallet keys.toml fallback tests +# Exercises --from / --sender resolving the signer from ~/.tempo/wallet/keys.toml +# without requiring --private-key or --tempo.access-key. +# +# Creates a fresh direct-mode wallet (wallet_address == key_address) and writes +# a keys.toml for it, so the test is self-contained and doesn't require a +# pre-provisioned keychain entry. + +# Fee token address, defaults to native fee token +FEE_TOKEN="${TEMPO_FEE_TOKEN:-0x20c0000000000000000000000000000000000000}" + +FEE_TOKEN_ARG=() +if [[ "$FEE_TOKEN" != "0x20c0000000000000000000000000000000000000" ]]; then + FEE_TOKEN_ARG=(--tempo.fee-token "$FEE_TOKEN") +fi + +# Fund an address and wait for the fee token balance to be non-zero +fund_and_wait() { + local addr="$1" + for i in {1..100}; do + OUT=$(cast rpc tempo_fundAddress "$addr" --rpc-url "$ETH_RPC_URL" 2>&1 || true) + if echo "$OUT" | jq -e 'arrays' >/dev/null 2>&1; then + echo "$OUT" | jq + break + fi + echo "[$i] $OUT" + sleep 0.2 + done + echo "Waiting for $addr to be funded..." + for i in {1..30}; do + BAL=$(cast call --rpc-url "$ETH_RPC_URL" "$FEE_TOKEN" 'balanceOf(address)(uint256)' "$addr" 2>/dev/null || echo "0") + if [[ "$BAL" != "0" && -n "$BAL" ]]; then + echo "Funded with $BAL fee tokens" + return 0 + fi + if [[ $i -eq 30 ]]; then + echo "ERROR: Funding timed out for $addr" + exit 1 + fi + sleep 1 + done +} + +echo -e "\n=== CREATE DIRECT-MODE WALLET ===" +wallet_json="$(cast wallet new --json)" +WALLET_ADDR="$(jq -r '.[0].address' <<<"$wallet_json")" +WALLET_PK="$(jq -r '.[0].private_key' <<<"$wallet_json")" +printf "address: %s\n" "$WALLET_ADDR" + +echo -e "\n=== WRITE keys.toml ===" +mkdir -p "${TEMPO_HOME:-$HOME/.tempo}/wallet" +cat > "${TEMPO_HOME:-$HOME/.tempo}/wallet/keys.toml" <> "$GITHUB_PATH" + + - name: Build benchmark binary + run: cargo build --locked --release --bin foundry-bench + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + - name: Install hyperfine + run: | + curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ + rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu + + # --- Stable benchmarks --- + + - name: Run forge test benchmarks (stable) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions stable \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_test \ + --json-output "stable-${DATE}-forge_test.json" \ + --verbose + + - name: Run forge fuzz test benchmarks (stable) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions stable \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_fuzz_test \ + --json-output "stable-${DATE}-forge_fuzz_test.json" \ + --verbose + + - name: Run forge isolate test benchmarks (stable) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions stable \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_isolate_test \ + --json-output "stable-${DATE}-forge_isolate_test.json" \ + --verbose + + - name: Run forge build benchmarks (stable) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions stable \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --json-output "stable-${DATE}-forge_build.json" \ + --verbose + + - name: Run forge coverage benchmarks (stable) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions stable \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_coverage \ + --json-output "stable-${DATE}-forge_coverage.json" \ + --verbose + + # --- Nightly benchmarks --- + + - name: Run forge test benchmarks (nightly) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_test \ + --json-output "nightly-${DATE}-forge_test.json" \ + --verbose + + - name: Run forge fuzz test benchmarks (nightly) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_fuzz_test \ + --json-output "nightly-${DATE}-forge_fuzz_test.json" \ + --verbose + + - name: Run forge isolate test benchmarks (nightly) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_isolate_test \ + --json-output "nightly-${DATE}-forge_isolate_test.json" \ + --verbose + + - name: Run forge build benchmarks (nightly) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --json-output "nightly-${DATE}-forge_build.json" \ + --verbose + + - name: Run forge coverage benchmarks (nightly) + continue-on-error: true + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + run: | + DATE=$(date -u +%Y-%m-%d) + ./target/release/foundry-bench --output-dir ./benches \ + --versions nightly \ + --repos "$AAVE_V4_REPO" \ + --benchmarks forge_coverage \ + --json-output "nightly-${DATE}-forge_coverage.json" \ + --verbose + + - name: Merge benchmark JSON results + run: | + DATE=$(date -u +%Y-%m-%d) + shopt -s nullglob + + stable_parts=( benches/stable-${DATE}-*.json ) + nightly_parts=( benches/nightly-${DATE}-*.json ) + + if [[ ${#stable_parts[@]} -eq 0 && ${#nightly_parts[@]} -eq 0 ]]; then + echo "No benchmark results produced — all steps failed." + exit 1 + fi + + if [[ ${#stable_parts[@]} -gt 0 ]]; then + jq -s 'add' "${stable_parts[@]}" > "benches/stable-${DATE}.json" + echo "Merged ${#stable_parts[@]} stable result file(s) into stable-${DATE}.json" + fi + + if [[ ${#nightly_parts[@]} -gt 0 ]]; then + jq -s 'add' "${nightly_parts[@]}" > "benches/nightly-${DATE}.json" + echo "Merged ${#nightly_parts[@]} nightly result file(s) into nightly-${DATE}.json" + fi + + - name: Compare stable vs nightly results + id: compare + run: | + DATE=$(date -u +%Y-%m-%d) + STABLE_JSON="benches/stable-${DATE}.json" + NIGHTLY_JSON="benches/nightly-${DATE}.json" + if ./.github/scripts/compare-nightly.sh "$STABLE_JSON" "$NIGHTLY_JSON" > regression.md 2>&1; then + echo "has_regression=false" >> "$GITHUB_OUTPUT" + else + echo "has_regression=true" >> "$GITHUB_OUTPUT" + fi + cat regression.md + + - name: Upload benchmark results + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: bench-results + retention-days: 7 + path: | + benches/stable-*.json + benches/nightly-*.json + regression.md + + report-regression: + name: Report Regression + needs: run-benchmarks + if: needs.run-benchmarks.outputs.has_regression == 'true' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Download benchmark results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: bench-results + path: results/ + + - name: Open regression issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + DATE=$(date -u +%Y-%m-%d) + BODY="$(cat results/regression.md) + + --- + + **Run**: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + **Date**: ${DATE} + **Repo benchmarked**: \`aave/aave-v4\` (pinned commit) + **Threshold**: 🔴 >=3% regression, 🟡 >=1% warning" + + gh issue create \ + --title "[Nightly Regression] ${DATE}" \ + --body "$BODY" \ + --label "regression" \ + --repo "$GH_REPO" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000000000..a136703abc294 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,255 @@ +name: Foundry Benchmarks + +permissions: {} + +on: + workflow_dispatch: + inputs: + pr_number: + description: "PR number to comment on (optional)" + required: false + type: string + versions: + description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'v1.5.1,v1.7.0')" + required: false + type: string + repos: + description: "Comma-separated repos to benchmark. Each entry: org/repo[:rev] (e.g. aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35). Leave empty to use the per-benchmark default repo lists." + required: false + type: string + +env: + DEFAULT_VERSIONS: "v1.5.1,v1.7.0" + + # Repos to benchmark per step. Each comma-separated entry has the form + # org/repo[:rev][ ] + # where anything after the first whitespace is appended to every benchmark + # command for that repo (use this to skip a broken test contract via e.g. + # `--nmc BrokenTest`, so a single failing test does not fail the whole CI). + TEST_REPOS: >- + ithacaxyz/account:v0.5.7, + vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test', + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', + sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting + + ISOLATE_TEST_REPOS: >- + ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest, + vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest', + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest', + sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting + + BUILD_REPOS: >- + ithacaxyz/account:v0.5.7, + vectorized/solady:v0.1.26, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, + sparkdotfi/spark-psm:v1.0.0 + + COVERAGE_REPOS: >- + ithacaxyz/account:v0.5.7, + uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75, + sparkdotfi/spark-psm:v1.0.0 + + RUSTC_WRAPPER: "sccache" + +jobs: + run-benchmarks: + name: Run All Benchmarks + runs-on: depot-ubuntu-24.04-32 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + + - name: Setup Foundry + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + ./.github/scripts/setup-foundryup.sh + printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" + + - name: Build benchmark binary + run: cargo build --locked --release --bin foundry-bench + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + - name: Install hyperfine + run: | + curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ + rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu + + - name: Run forge test benchmarks + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} + REPOS: ${{ github.event.inputs.repos || env.TEST_REPOS }} + run: | + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions "$VERSIONS" \ + --repos "$REPOS" \ + --benchmarks forge_test,forge_fuzz_test \ + --output-file forge_test_bench.md \ + --verbose + + - name: Run forge isolate test benchmarks + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} + REPOS: ${{ github.event.inputs.repos || env.ISOLATE_TEST_REPOS }} + run: | + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions "$VERSIONS" \ + --repos "$REPOS" \ + --benchmarks forge_isolate_test \ + --output-file forge_isolate_test_bench.md \ + --verbose + + - name: Run forge build benchmarks + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} + REPOS: ${{ github.event.inputs.repos || env.BUILD_REPOS }} + run: | + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions "$VERSIONS" \ + --repos "$REPOS" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --output-file forge_build_bench.md \ + --verbose + + - name: Run forge coverage benchmarks + env: + FOUNDRY_DIR: ${{ github.workspace }}/.foundry + VERSIONS: ${{ github.event.inputs.versions || env.DEFAULT_VERSIONS }} + REPOS: ${{ github.event.inputs.repos || env.COVERAGE_REPOS }} + run: | + ./target/release/foundry-bench --output-dir ./benches --force-install \ + --versions "$VERSIONS" \ + --repos "$REPOS" \ + --benchmarks forge_coverage \ + --output-file forge_coverage_bench.md \ + --verbose + + - name: Combine benchmark results + run: ./.github/scripts/combine-benchmarks.sh benches + + - name: Read benchmark results + id: benchmark_results + run: ./.github/scripts/read-benchmark-results.sh benches + + - name: Upload benchmark results as artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: benchmark-results + path: | + benches/forge_test_bench.md + benches/forge_isolate_test_bench.md + benches/forge_build_bench.md + benches/forge_coverage_bench.md + benches/LATEST.md + + outputs: + pr_comment: ${{ steps.benchmark_results.outputs.pr_comment }} + + publish-results: + name: Publish Results + needs: run-benchmarks + runs-on: ubuntu-latest + # All git writes happen here, on a clean ubuntu-latest runner that has + # never executed third-party benchmark code. + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + # persist-credentials defaults to true so we can push. + + - name: Download benchmark results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: benchmark-results + path: benches/ + + - name: Commit benchmark results + id: commit_results + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: ./.github/scripts/commit-benchmark-results.sh benches "${{ github.event_name }}" "${{ github.repository }}" + + - name: Create PR for manual runs + if: github.event_name == 'workflow_dispatch' && steps.commit_results.outputs.committed == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BRANCH_NAME: ${{ steps.commit_results.outputs.branch_name }} + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} + with: + script: | + const branchName = process.env.BRANCH_NAME; + const prComment = process.env.PR_COMMENT; + + // Create the pull request + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'chore(bench): update benchmark results', + head: branchName, + base: 'master', + body: `## Benchmark Results Update + + This PR contains the latest benchmark results from a manual workflow run. + + ${prComment} + + --- + + 🤖 This PR was automatically generated by the [Foundry Benchmarks workflow](https://github.com/${{ github.repository }}/actions).` + }); + + console.log(`Created PR #${pr.number}: ${pr.html_url}`); + + - name: Comment on PR + if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PR_COMMENT: ${{ needs.run-benchmarks.outputs.pr_comment }} + with: + script: | + const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; + const prComment = process.env.PR_COMMENT; + + const comment = `${prComment} + + --- + + 🤖 This comment was automatically generated by the [Foundry Benchmarks workflow](https://github.com/${{ github.repository }}/actions). + + To run benchmarks manually: Go to [Actions](https://github.com/${{ github.repository }}/actions/workflows/benchmarks.yml) → "Run workflow"`; + + github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/.github/workflows/bump-forge-std.yml b/.github/workflows/bump-forge-std.yml index 137e8c465e914..e95acd2881d31 100644 --- a/.github/workflows/bump-forge-std.yml +++ b/.github/workflows/bump-forge-std.yml @@ -2,21 +2,28 @@ name: bump-forge-std +permissions: {} + on: schedule: - - cron: "0 0 * * *" - workflow_dispatch: + - cron: "0 0 * * *" # Run daily at midnight UTC + workflow_dispatch: # Needed so we can run it manually jobs: update-tag: name: update forge-std tag runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Fetch and update forge-std tag run: curl 'https://api.github.com/repos/foundry-rs/forge-std/tags' | jq '.[0].commit.sha' -jr > testdata/forge-std-rev - name: Create pull request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: commit-message: "chore: bump forge-std version used for tests" title: "chore(tests): bump forge-std version" diff --git a/.github/workflows/bump-tempo.yml b/.github/workflows/bump-tempo.yml new file mode 100644 index 0000000000000..ffbd68d772d81 --- /dev/null +++ b/.github/workflows/bump-tempo.yml @@ -0,0 +1,59 @@ +name: Bump Tempo Dependencies + +permissions: {} + +on: + schedule: + # Run daily at 00:00 UTC + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + bump-tempo: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Bump tempo dependencies + id: bump + env: + GH_TOKEN: ${{ github.token }} + run: ./.github/scripts/bump-tempo.sh + + - name: Create Pull Request + if: steps.bump.outputs.updated == 'true' + env: + GH_TOKEN: ${{ github.token }} + LATEST_REV: ${{ steps.bump.outputs.latest_rev }} + CHANGELOG_FILE: ${{ steps.bump.outputs.changelog }} + run: | + BRANCH_NAME="deps/bump-tempo-${LATEST_REV:0:7}" + + # Skip if a PR already exists for this branch + EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number') + if [[ -n "$EXISTING_PR" ]]; then + echo "PR #${EXISTING_PR} already exists for ${BRANCH_NAME}, skipping." + exit 0 + fi + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Create and push branch (force-push in case a stale branch exists from a prior failed run) + git checkout -b "$BRANCH_NAME" + git add Cargo.toml Cargo.lock + git commit -m "chore(deps): bump tempo dependencies to ${LATEST_REV:0:7}" + git push --force-with-lease origin "$BRANCH_NAME" + + # Create PR + gh pr create \ + --title "chore(deps): bump tempo dependencies to ${LATEST_REV:0:7}" \ + --body-file "$CHANGELOG_FILE" \ + --base main \ + --head "$BRANCH_NAME" diff --git a/.github/workflows/ci-mpp.yml b/.github/workflows/ci-mpp.yml new file mode 100644 index 0000000000000..3a608b6500a90 --- /dev/null +++ b/.github/workflows/ci-mpp.yml @@ -0,0 +1,60 @@ +name: CI MPP + +permissions: {} + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" + +jobs: + mpp-check: + runs-on: depot-ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + steps: + # Checkout the repository + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + + # Build and install binaries + - name: Build and install Foundry binaries + run: | + cargo build --profile dev --locked -p forge -p cast -p anvil -p chisel + echo "${{ github.workspace }}/target/debug" >> "$GITHUB_PATH" + + - name: Run MPP e2e test + env: + TEMPO_PRIVATE_KEY: ${{ secrets.TEMPO_PRIVATE_KEY }} + TEMPO_KEYS_TOML_B64: ${{ secrets.TEMPO_KEYS_TOML_B64 }} + MPP_API_KEY: ${{ secrets.MPP_API_KEY }} + MPP_DEPOSIT: "1000000" + TEMPO_AUTO_FUND: "1" + run: | + if [ -n "${TEMPO_PRIVATE_KEY:-}" ]; then + echo "::notice::Using TEMPO_PRIVATE_KEY for MPP e2e" + elif [ -n "${TEMPO_KEYS_TOML_B64:-}" ]; then + mkdir -p ~/.tempo/wallet + echo "$TEMPO_KEYS_TOML_B64" | tr -d '[:space:]' | base64 -d > ~/.tempo/wallet/keys.toml + else + echo "::warning::TEMPO_PRIVATE_KEY or TEMPO_KEYS_TOML_B64 secret not set, skipping MPP e2e" + exit 0 + fi + ./.github/scripts/tempo-mpp.sh "$(which cast | xargs dirname)" diff --git a/.github/workflows/ci-tempo.yml b/.github/workflows/ci-tempo.yml new file mode 100644 index 0000000000000..1ef4c760f324e --- /dev/null +++ b/.github/workflows/ci-tempo.yml @@ -0,0 +1,155 @@ +name: CI Tempo + +permissions: {} + +on: + push: + branches: [master] + pull_request: + schedule: + - cron: "0 2 * * *" # Run daily at 2 AM UTC (offset from other nightlies) + workflow_dispatch: + inputs: + network: + description: "Tempo network to check" + required: true + type: choice + options: + - testnet + - devnet + - mainnet + - all + scripts: + description: "Which scripts to run" + required: false + type: choice + default: "both" + options: + - check + - deploy + - both + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" + +jobs: + tempo-check: + runs-on: depot-ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + steps: + # Checkout the repository + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + + # Build and install binaries + - name: Build and install Foundry binaries + run: | + cargo build --profile dev --locked -p forge -p cast -p anvil -p chisel + echo "${{ github.workspace }}/target/debug" >> "$GITHUB_PATH" + + - name: Check Tempo fork schedule compatibility + env: + TEMPO_MAINNET_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} + TEMPO_TESTNET_RPC_URL: ${{ secrets.TEMPO_TESTNET_RPC_URL }} + TEMPO_DEVNET_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + run: | + cargo test --locked -p foundry-common --lib tempo::tests::test_fork_schedule_parses_configured_rpcs -- --exact --nocapture + + - name: Run Tempo check on mainnet + if: | + github.event_name == 'schedule' || + github.event.inputs.network == 'mainnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_MAINNET_RPC_URL }} + TEMPO_FEE_TOKEN: "0x20c0000000000000000000000000000000000000" + VERIFIER_URL: ${{ secrets.VERIFIER_URL }} + PRIVATE_KEY: ${{ secrets.THROW_AWAY_MAINNET_PKEY }} + SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} + run: | + if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-check.sh + fi + if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-deploy.sh + fi + + - name: Run Tempo check on testnet + if: | + github.event_name == 'schedule' || + github.event.inputs.network == 'testnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_TESTNET_RPC_URL }} + VERIFIER_URL: ${{ secrets.VERIFIER_URL }} + SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} + run: | + if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-check.sh + fi + if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-deploy.sh + fi + + - name: Run Tempo check on devnet + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + SCRIPTS: ${{ github.event.inputs.scripts || 'both' }} + run: | + if [ "$SCRIPTS" = "check" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-check.sh + fi + if [ "$SCRIPTS" = "deploy" ] || [ "$SCRIPTS" = "both" ]; then + ./.github/scripts/tempo-deploy.sh + fi + + - name: Run Tempo wallet tests on devnet + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + github.event.inputs.network == 'devnet' || + github.event.inputs.network == 'all' + env: + ETH_RPC_URL: ${{ secrets.TEMPO_DEVNET_RPC_URL }} + run: ./.github/scripts/tempo-wallet.sh + + # If the nightly run fails, this will create an issue to signal so. + issue: + name: Open an issue + runs-on: ubuntu-latest + needs: [tempo-check] + if: failure() && github.event_name == 'schedule' + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/TEMPO_NIGHTLY_FAILURE_TEMPLATE.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000..9b63b3e01b4b1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,203 @@ +name: CI + +permissions: {} + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" + +jobs: + test: + uses: ./.github/workflows/test.yml + permissions: + contents: read + with: + profile: default + secrets: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} + + docs: + uses: ./.github/workflows/docs.yml + permissions: + contents: read + pages: write + id-token: write + + doctest: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo test --workspace --doc --locked + + no-default-features: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo build --workspace --no-default-features --locked + + typos: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 + + shellcheck: + runs-on: depot-ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Shellcheck + shell: bash + run: ./.github/scripts/shellcheck.sh + + clippy: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: nightly + components: clippy + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo clippy --workspace --all-targets --all-features --locked + env: + RUSTFLAGS: -Dwarnings + + rustfmt: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: nightly + components: rustfmt + - run: cargo fmt --all --check + + forge-fmt: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - name: forge fmt + shell: bash + run: ./.github/scripts/format.sh --check + + deny: + uses: tempoxyz/ci/.github/workflows/deny.yml@268b3ce142717ff86c58fbbcc3abc3f109f0fb8d # main + permissions: + contents: read + + codeql: + name: analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Initialize CodeQL + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + with: + category: "/language:${{matrix.language}}" + + ci-success: + runs-on: ubuntu-latest + if: always() + permissions: {} + needs: + - test + - docs + - doctest + - no-default-features + - typos + - clippy + - rustfmt + - forge-fmt + - deny + - codeql + timeout-minutes: 30 + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/crate-checks.yml b/.github/workflows/crate-checks.yml new file mode 100644 index 0000000000000..f0d460da6fbb1 --- /dev/null +++ b/.github/workflows/crate-checks.yml @@ -0,0 +1,58 @@ +# Nightly job to verify each crate compiles independently (catches missing deps/features +# masked by Cargo's workspace feature unification). + +name: crate-checks + +permissions: {} + +on: + schedule: + - cron: "0 2 * * *" # Run daily at 2 AM UTC (offset from test-flaky and test-isolate) + workflow_dispatch: # Needed so we can run it manually + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" + +jobs: + crate-checks: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 + with: + tool: cargo-hack + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - run: cargo hack check --locked + + issue: + name: Open an issue + runs-on: ubuntu-latest + needs: [crate-checks] + if: failure() + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/CRATE_CHECKS_FAILURE_TEMPLATE.md diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml deleted file mode 100644 index d22bbffcab714..0000000000000 --- a/.github/workflows/dependencies.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Runs `cargo update` periodically. - -name: dependencies - -on: - schedule: - # Run weekly - - cron: "0 0 * * SUN" - workflow_dispatch: - # Needed so we can run it manually - -permissions: - contents: write - pull-requests: write - -jobs: - update: - uses: ithacaxyz/ci/.github/workflows/cargo-update-pr.yml@main - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index fdd069cf2e7ef..6120735657ee6 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,80 +1,138 @@ name: docker +permissions: {} + on: - # Trigger without any parameters a proactive rebuild - workflow_dispatch: {} + workflow_dispatch: + inputs: + tag_name: + default: nightly-dev + description: The tag we're building for + type: string workflow_call: inputs: tag_name: required: true type: string +concurrency: + group: docker-${{ github.head_ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: REGISTRY: ghcr.io - # Will resolve to foundry-rs/foundry IMAGE_NAME: ${{ github.repository }} + # Keep in sync with `release.yml`. + RUST_PROFILE: dist + RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer + jobs: build: name: build and push - runs-on: Linux-22.04 + runs-on: depot-ubuntu-latest permissions: + attestations: write + artifact-metadata: write + contents: read id-token: write packages: write - contents: read - timeout-minutes: 120 + timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - cache-on-failure: true - - name: Install cross - id: cross_main - run: | - cargo install cross + persist-credentials: false + # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Login into registry ${{ env.REGISTRY }} - # Ensure this doesn't trigger on PR's - if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} # Extract metadata (tags, labels) for Docker # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # Creates an additional 'latest' or 'nightly' tag - # If the job is triggered via cron schedule, tag nightly and nightly-{SHA} - # If the job is triggered via workflow dispatch and on a master branch, tag branch and latest - # Otherwise, just tag as the branch name + # Derive Docker tags from inputs.tag_name: + # - v{major}.{minor}.{patch}: tagged as :v1.2.3, :v1.2, :v1, and :latest + # - nightly-{SHA}: tagged as :nightly-{SHA} and :nightly + # - anything else: tagged as-is - name: Finalize Docker Metadata id: docker_tagging run: | - if [[ "${{ github.event_name }}" == 'schedule' ]]; then - echo "cron trigger, assigning nightly tag" - echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_REF##*/}" == "main" ]] || [[ ${GITHUB_REF##*/} == "master" ]]; then - echo "manual trigger from master/main branch, assigning latest tag" - echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT - else - echo "Neither scheduled nor manual release from main branch. Just tagging as branch name" - echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/}" >> $GITHUB_OUTPUT - fi + TAG="${{ inputs.tag_name }}" + REGISTRY="${{ env.REGISTRY }}" + IMAGE="${{ env.IMAGE_NAME }}" + + if [[ "$TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + MAJOR="v${BASH_REMATCH[1]}" + MINOR="v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" + printf "version tag release, assigning %s, %s, %s, and latest tags\n" "$TAG" "$MINOR" "$MAJOR" + printf "docker_tags=%s/%s:%s,%s/%s:%s,%s/%s:%s,%s/%s:latest\n" "$REGISTRY" "$IMAGE" "$TAG" "$REGISTRY" "$IMAGE" "$MINOR" "$REGISTRY" "$IMAGE" "$MAJOR" "$REGISTRY" "$IMAGE" >> "$GITHUB_OUTPUT" + elif [[ "$TAG" =~ ^nightly- ]]; then + printf "nightly release, assigning %s and nightly tags\n" "$TAG" + printf "docker_tags=%s/%s:%s,%s/%s:nightly\n" "$REGISTRY" "$IMAGE" "$TAG" "$REGISTRY" "$IMAGE" >> "$GITHUB_OUTPUT" + else + printf "tagging as %s\n" "$TAG" + printf "docker_tags=%s/%s:%s\n" "$REGISTRY" "$IMAGE" "$TAG" >> "$GITHUB_OUTPUT" + fi # Log docker metadata to explicitly know what is being pushed - name: Inspect Docker Metadata run: | - echo "TAGS -> ${{ steps.docker_tagging.outputs.docker_tags }}" - echo "LABELS -> ${{ steps.meta.outputs.labels }}" + printf "TAGS -> %s\n" "${{ steps.docker_tagging.outputs.docker_tags }}" + printf "LABELS -> %s\n" "${{ steps.meta.outputs.labels }}" + + - name: Set up Depot CLI + uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 + + - name: Build and push Foundry image + id: build + uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 + with: + build-args: | + RUST_PROFILE=${{ env.RUST_PROFILE }} + RUST_FEATURES=${{ env.RUST_FEATURES }} + TAG_NAME=${{ inputs.tag_name }} + VERGEN_GIT_SHA=${{ github.sha }} + project: 8gkbxxjrpw + context: . + tags: ${{ steps.docker_tagging.outputs.docker_tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + push: true + no-cache: true + sbom: true + provenance: mode=max - - name: Build and push foundry image - run: make DOCKER_IMAGE_NAME=${{ steps.docker_tagging.outputs.docker_tags }} CARGO_TAG_NAME=${{ inputs.tag_name }} PROFILE=maxperf docker-build-push + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + + - name: Sign image with cosign (keyless) + env: + DOCKER_TAGS: ${{ steps.docker_tagging.outputs.docker_tags }} + DIGEST: ${{ steps.build.outputs.digest }} + shell: bash + run: | + set -euo pipefail + IFS=',' read -r -a tags <<< "$DOCKER_TAGS" + for tag in "${tags[@]}"; do + # Strip any tag suffix and pin to immutable digest, then sign. + ref="${tag%%:*}@${DIGEST}" + printf 'Signing %s\n' "$ref" + cosign sign --yes "$ref" + done + + - name: Image build provenance attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000000..45d00708394f5 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,60 @@ +name: docs + +permissions: {} + +on: + push: + branches: + - master + workflow_call: + +concurrency: + group: docs-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + docs: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: nightly + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - name: Build documentation + run: cargo doc --workspace --all-features --no-deps --document-private-items --locked + env: + RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options + - name: Setup Pages + if: github.ref_name == 'master' && github.event_name == 'push' + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - name: Upload artifact + if: github.ref_name == 'master' && github.event_name == 'push' + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: ./target/doc + + deploy-docs: + if: github.ref_name == 'master' && github.event_name == 'push' + needs: [docs] + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..569c7b00a4b7f --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@3da1e46a907576cefaa90c484278bb5b259dd395' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml deleted file mode 100644 index 855e5933c1b6d..0000000000000 --- a/.github/workflows/nextest.yml +++ /dev/null @@ -1,98 +0,0 @@ -# Reusable workflow for running tests via `cargo nextest` - -name: nextest - -on: - workflow_call: - inputs: - profile: - required: true - type: string - -concurrency: - group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -jobs: - matrices: - name: build matrices - runs-on: ubuntu-latest - outputs: - test-matrix: ${{ steps.gen.outputs.test-matrix }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - name: Generate matrices - id: gen - env: - EVENT_NAME: ${{ github.event_name }} - PROFILE: ${{ inputs.profile }} - run: | - output=$(python3 .github/scripts/matrices.py) - echo "::debug::test-matrix=$output" - echo "test-matrix=$output" >> $GITHUB_OUTPUT - - test: - name: test ${{ matrix.name }} - runs-on: ${{ matrix.runner_label }} - timeout-minutes: 60 - needs: matrices - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} - env: - ETH_RPC_URL: https://reth-ethereum.ithaca.xyz/rpc - CARGO_PROFILE_DEV_DEBUG: 0 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - target: ${{ matrix.target }} - - uses: taiki-e/install-action@nextest - - # External tests dependencies - - name: Setup Node.js - if: contains(matrix.name, 'external') - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Install Bun - if: contains(matrix.name, 'external') && !contains(matrix.runner_label, 'windows') - uses: oven-sh/setup-bun@v1 - with: - bun-version: latest - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.11 - - name: Install Vyper - # Also update vyper version in .devcontainer/Dockerfile.dev - run: pip --version && pip install vyper==0.4.0 - - - name: Forge RPC cache - uses: actions/cache@v4 - with: - path: | - ~/.foundry/cache - ~/.config/.foundry/cache - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Setup Git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: Test - env: - SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} - HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} - WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} - run: cargo nextest run ${{ matrix.flags }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000000000..258c86e9e72c2 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,53 @@ +name: nix + +permissions: {} + +on: + schedule: + - cron: "0 0 * * SUN" # Run weekly on Sundays at midnight UTC + workflow_dispatch: # Needed so we can run it manually + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Opens a PR with an updated flake.lock file + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: DeterminateSystems/update-flake-lock@ff43f160ef7014ae1a1fd85699fb6a44f436135b # main + with: + pr-title: "Update flake.lock" + pr-labels: | + L-ignore + A-dependencies + + build: + strategy: + matrix: + runs-on: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.runs-on }} + permissions: + contents: read + steps: + - uses: DeterminateSystems/determinate-nix-action@2be1df9ed6cfd12d52bfbba7af79472420fa5299 # v3.18.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Update flake.lock + run: nix flake update + + - name: Activate nix env + run: nix develop -c echo Ok + + - name: Check that we can compile all crates + run: nix develop -c cargo check --all-targets diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml new file mode 100644 index 0000000000000..fadfbd5d907e2 --- /dev/null +++ b/.github/workflows/npm.yml @@ -0,0 +1,324 @@ +name: npm + +permissions: {} + +on: + workflow_run: + types: [completed] + workflows: [release] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +env: + NPM_CONFIG_PROVENANCE: true + NPM_REGISTRY_URL: "https://registry.npmjs.org" + +jobs: + publish-arch: + permissions: + contents: read + actions: read + id-token: write + environment: release + name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} + runs-on: ubuntu-latest + strategy: + max-parallel: 3 + fail-fast: false + matrix: + include: + - tool: forge + os: linux + arch: amd64 + - tool: forge + os: linux + arch: arm64 + - tool: forge + os: darwin + arch: amd64 + - tool: forge + os: darwin + arch: arm64 + - tool: forge + os: win32 + arch: amd64 + - tool: cast + os: linux + arch: amd64 + - tool: cast + os: linux + arch: arm64 + - tool: cast + os: darwin + arch: amd64 + - tool: cast + os: darwin + arch: arm64 + - tool: cast + os: win32 + arch: amd64 + - tool: anvil + os: linux + arch: amd64 + - tool: anvil + os: linux + arch: arm64 + - tool: anvil + os: darwin + arch: amd64 + - tool: anvil + os: darwin + arch: arm64 + - tool: anvil + os: win32 + arch: amd64 + - tool: chisel + os: linux + arch: amd64 + - tool: chisel + os: linux + arch: arm64 + - tool: chisel + os: darwin + arch: amd64 + - tool: chisel + os: darwin + arch: arm64 + - tool: chisel + os: win32 + arch: amd64 + # Run automatically after a successful 'release' workflow, or manually if a run_id is provided + if: >- + ${{ + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + (github.event_name == 'workflow_dispatch' && inputs.run_id != '') + }} + outputs: + RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Validate Trusted Artifact Source + run: | + set -euo pipefail + + if [[ "${{ github.event_name }}" == "workflow_run" ]]; then + [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]] || { echo "ERROR: Upstream workflow_run did not succeed."; exit 1; } + [[ "${{ github.event.workflow_run.head_repository.full_name }}" == "${{ github.repository }}" ]] || { echo "ERROR: Upstream run repository mismatch."; exit 1; } + [[ "${{ github.event.workflow_run.event }}" != "pull_request" ]] || { echo "ERROR: Refusing artifacts from pull_request-triggered upstream runs."; exit 1; } + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + [[ -n "${{ inputs.run_id }}" ]] || { echo "ERROR: inputs.run_id is required for workflow_dispatch."; exit 1; } + [[ "${{ inputs.run_id }}" =~ ^[0-9]+$ ]] || { echo "ERROR: inputs.run_id must be numeric."; exit 1; } + else + echo "ERROR: Unsupported event: ${{ github.event_name }}" + exit 1 + fi + + - name: Set Isolated Artifact Directory + id: paths + run: | + set -euo pipefail + printf 'artifact_dir=%s\n' "$RUNNER_TEMP/foundry_artifacts" >> "$GITHUB_OUTPUT" + + - name: Prepare Isolated Artifact Directory + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + mkdir -p "$ARTIFACT_DIR" + ls -la "$ARTIFACT_DIR" || true + + - name: Download Release Assets + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + merge-multiple: true + # Download all foundry artifacts from the triggering release run + pattern: "foundry_*" + # Extract artifacts into an isolated temp directory, not the workspace + path: ${{ steps.paths.outputs.artifact_dir }} + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Validate Downloaded Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Validating artifacts in: $ARTIFACT_DIR" + + if [[ ! -d "$ARTIFACT_DIR" ]]; then + echo "ERROR: Artifact directory does not exist: $ARTIFACT_DIR" >&2 + exit 1 + fi + + if ! find "$ARTIFACT_DIR" -mindepth 1 -print -quit | grep -q .; then + echo "ERROR: Artifact directory is empty: $ARTIFACT_DIR" >&2 + exit 1 + fi + + # Reject files with suspicious paths (absolute paths or parent directory traversals) + # Use null-delimited paths to safely handle filenames with newlines or whitespace + while IFS= read -r -d '' path; do + rel="${path#"$ARTIFACT_DIR"/}" + if [[ "$rel" == /* ]] || [[ "$rel" == *".."* ]]; then + echo "ERROR: Suspicious artifact path detected: $rel" >&2 + exit 1 + fi + done < <(find "$ARTIFACT_DIR" -type f -print0) + + echo "Artifact validation completed successfully." + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + + - name: Setup Node (for npm publish auth) + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Derive RELEASE_VERSION + id: release-version + working-directory: ./npm + env: + NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Artifacts in $ARTIFACT_DIR:" + ls -la "$ARTIFACT_DIR" || true + + # Derive RELEASE_VERSION from any foundry artifact we downloaded + # Expected names: foundry___.{tar.gz,zip} + first_file=$(ls "$ARTIFACT_DIR"/foundry_* 2>/dev/null | head -n1 || true) + if [[ -z "${first_file}" ]]; then + echo "No foundry artifacts found to publish" >&2 + exit 1 + fi + version_part=$(basename "$first_file") + version_part=${version_part#foundry_} + export RELEASE_VERSION=${version_part%%_*} + echo "Detected RELEASE_VERSION=$RELEASE_VERSION" + + printf 'RELEASE_VERSION=%s\n' "$RELEASE_VERSION" >>"$GITHUB_OUTPUT" + + - name: Stage Binary Into Package + working-directory: ./npm + env: + RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + bun ./scripts/stage-from-artifact.mjs \ + --tool '${{ matrix.tool }}' \ + --platform '${{ matrix.os }}' \ + --arch '${{ matrix.arch }}' \ + --release-version "$RELEASE_VERSION" \ + --artifact-dir "$ARTIFACT_DIR" + + - name: Sanity Check Binary + working-directory: ./npm + run: | + set -euo pipefail + TOOL='${{ matrix.tool }}' + PLATFORM='${{ matrix.os }}' + ARCH='${{ matrix.arch }}' + + PKG_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + BIN="$PKG_DIR/bin/${TOOL}" + if [[ "$PLATFORM" == "win32" ]]; then + BIN="$PKG_DIR/bin/${TOOL}.exe" + fi + echo "Verifying binary at: $BIN" + ls -la "$BIN" + if [[ ! -f "$BIN" ]]; then + echo "ERROR: Binary not found at $BIN" >&2 + exit 1 + fi + + if [[ "${{ matrix.os }}" != "win32" ]]; then + if [[ ! -x "$BIN" ]]; then + echo "ERROR: Binary not marked executable" >&2 + exit 1 + fi + fi + + - name: Publish ${{ matrix.os }}-${{ matrix.arch }} Binary + working-directory: ./npm + env: + PROVENANCE: true + VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} + RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} + run: | + set -euo pipefail + + TOOL='${{ matrix.tool }}' + PLATFORM='${{ matrix.os }}' + ARCH='${{ matrix.arch }}' + + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + ls -la "$PACKAGE_DIR" + + bun ./scripts/publish.mjs "$PACKAGE_DIR" + + echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + + publish-meta: + permissions: + contents: read + actions: read + id-token: write + environment: release + needs: publish-arch + name: Publish Meta Package + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + + - name: Setup Node (for npm publish auth) + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Install Dependencies + working-directory: ./npm + run: bun install --frozen-lockfile + + - name: Typecheck + working-directory: ./npm + run: bun tsc --project tsconfig.json --noEmit + + - name: Publish Meta Packages + working-directory: ./npm + run: | + set -euo pipefail + bun ./scripts/publish-meta.mjs --release-version "$RELEASE_VERSION" + env: + PROVENANCE: true + VERSION_NAME: ${{ env.RELEASE_VERSION }} + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b18d4ad1dd2c..38fa791fb655f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,10 @@ name: release +permissions: {} + on: push: tags: - - "stable" - - "rc" - - "rc-*" - "v*.*.*" schedule: - cron: "0 6 * * *" @@ -14,32 +13,46 @@ on: env: CARGO_TERM_COLOR: always IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} - PROFILE: maxperf - STABLE_VERSION: "v1.1.0" + + # Keep in sync with `docker-publish.yml`. + RUST_PROFILE: dist + RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer jobs: prepare: name: Prepare release runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: write + pull-requests: read outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} release_name: ${{ steps.release_info.outputs.release_name }} changelog: ${{ steps.build_changelog.outputs.changelog }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + persist-credentials: false fetch-depth: 0 - name: Compute release name and tag id: release_info run: | if [[ ${IS_NIGHTLY} == 'true' ]]; then - echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT - echo "release_name=Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT + printf 'tag_name=%s\n' "nightly-${GITHUB_SHA}" >> "$GITHUB_OUTPUT" + printf 'release_name=%s\n' "Nightly ($(date '+%Y-%m-%d'))" >> "$GITHUB_OUTPUT" + # Find the previous nightly tag for changelog generation, + # sorted by tag creation date (most recent first). + PREV_NIGHTLY=$(git tag -l 'nightly-*' --sort=-creatordate | head -n1) + printf 'from_tag=%s\n' "$PREV_NIGHTLY" >> "$GITHUB_OUTPUT" else - echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT - echo "release_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + printf 'tag_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + printf 'release_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + # Find the previous stable release tag (v*.*.*) for changelog generation, + # skipping the current tag so we diff from the last stable release. + PREV_STABLE=$(git tag -l 'v*.*.*' --sort=-v:refname | grep -v "^${GITHUB_REF_NAME}$" | head -n1) + printf 'from_tag=%s\n' "$PREV_STABLE" >> "$GITHUB_OUTPUT" fi # Creates a `nightly-SHA` tag for this specific nightly @@ -48,7 +61,7 @@ jobs: # the changelog. - name: Create build-specific nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v7 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: TAG_NAME: ${{ steps.release_info.outputs.tag_name }} with: @@ -58,27 +71,71 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@v4 + uses: mikepenz/release-changelog-builder-action@bcae7115752d4ed746ff92feb666574428a79415 # v6.2.1 with: configuration: "./.github/changelog.json" - fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || env.STABLE_VERSION }} + fromTag: ${{ steps.release_info.outputs.from_tag || '' }} toTag: ${{ steps.release_info.outputs.tag_name }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Create the GitHub release as a draft up-front so that all matrix jobs + # only upload assets to an existing draft. With immutable releases + # enabled, an immutable release is sealed when it is published, so all + # assets must be attached before that. Stable releases are then left as + # a draft for a maintainer to publish manually; nightlies are auto- + # published by the `publish-nightly` job at the end of the workflow. + - name: Create draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG_NAME: ${{ steps.release_info.outputs.tag_name }} + RELEASE_NAME: ${{ steps.release_info.outputs.release_name }} + CHANGELOG: ${{ steps.build_changelog.outputs.changelog }} + shell: bash + run: | + set -euo pipefail + if release_json="$(gh release view "$TAG_NAME" --json isDraft 2>/dev/null)"; then + is_draft="$(printf '%s' "$release_json" | grep -o '"isDraft":[^,}]*' | cut -d: -f2 | tr -d ' ')" + if [[ "$is_draft" == "true" ]]; then + echo "Draft release $TAG_NAME already exists; reusing it." + exit 0 + fi + echo "::error::Release $TAG_NAME is already published; cannot upload more assets to an immutable release." + exit 1 + fi + notes_file="$(mktemp)" + printf '%s' "$CHANGELOG" > "$notes_file" + flags=(--draft --verify-tag --title "$RELEASE_NAME" --notes-file "$notes_file") + if [[ "$IS_NIGHTLY" == "true" ]]; then + flags+=(--prerelease) + fi + gh release create "$TAG_NAME" "${flags[@]}" + release-docker: name: Release Docker needs: prepare uses: ./.github/workflows/docker-publish.yml + permissions: + attestations: write + artifact-metadata: write + contents: read + id-token: write + packages: write with: tag_name: ${{ needs.prepare.outputs.tag_name }} + # This job uploads assets to the draft release created in `prepare`. + # Stable releases stay as drafts for a maintainer to publish manually; + # nightlies are auto-published by the `publish-nightly` job below. Either + # way, GitHub's immutable-releases setting seals the release at publish. release: permissions: - id-token: write - contents: write attestations: write - name: ${{ matrix.target }} (${{ matrix.runner }}) + artifact-metadata: write + contents: write + id-token: write + name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 needs: prepare @@ -90,29 +147,28 @@ jobs: # `target`: Rust build target triple # `platform` and `arch`: Used in tarball names # `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 - - runner: Linux-22.04 + # These are pinned to the oldest runner versions to support old libc/SDK versions. + - runner: depot-ubuntu-22.04-16 target: x86_64-unknown-linux-gnu svm_target_platform: linux-amd64 platform: linux arch: amd64 - - runner: Linux-22.04 + - runner: depot-ubuntu-22.04-16 target: x86_64-unknown-linux-musl svm_target_platform: linux-amd64 platform: alpine arch: amd64 - - runner: Linux-22.04 + - runner: depot-ubuntu-22.04-arm-16 target: aarch64-unknown-linux-gnu svm_target_platform: linux-aarch64 platform: linux arch: arm64 - - runner: Linux-22.04 + - runner: depot-ubuntu-22.04-16 target: aarch64-unknown-linux-musl svm_target_platform: linux-aarch64 platform: alpine arch: arm64 - # This is pinned to `macos-13-large` to support old SDK versions. - # If the runner is deprecated it should be pinned to the oldest available version of the runner. - - runner: macos-13-large + - runner: macos-14-large target: x86_64-apple-darwin svm_target_platform: macosx-amd64 platform: darwin @@ -122,31 +178,33 @@ jobs: svm_target_platform: macosx-aarch64 platform: darwin arch: arm64 - - runner: Windows + - runner: depot-windows-latest-16 target: x86_64-pc-windows-msvc svm_target_platform: windows-amd64 platform: win32 arch: amd64 steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v2 + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: - key: ${{ matrix.target }} - cache-on-failure: true + toolchain: stable + targets: ${{ matrix.target }} + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - name: Apple M1 setup if: matrix.target == 'aarch64-apple-darwin' run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV + printf 'SDKROOT=%s\n' "$(xcrun -sdk macosx --show-sdk-path)" >> "$GITHUB_ENV" + printf 'MACOSX_DEPLOYMENT_TARGET=%s\n' "$(xcrun -sdk macosx --show-sdk-platform-version)" >> "$GITHUB_ENV" - name: cross setup - if: matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-gnu' + if: contains(matrix.target, 'musl') run: | - cargo install cross + cargo install cross --locked \ + --git https://github.com/cross-rs/cross \ + --rev baf457efc2555225af47963475bd70e8d2f5993f - name: Build binaries env: @@ -154,12 +212,12 @@ jobs: SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} PLATFORM_NAME: ${{ matrix.platform }} TARGET: ${{ matrix.target }} - OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} shell: bash run: | set -eo pipefail - flags=(--target $TARGET --profile $PROFILE --bins - --no-default-features --features aws-kms,gcp-kms,cli,asm-keccak) + flags=(--locked --target $TARGET --profile $RUST_PROFILE --bins + --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. if [[ "$TARGET" != *msvc* && "$TARGET" != "aarch64-unknown-linux-gnu" ]]; then @@ -168,7 +226,7 @@ jobs: [[ "$TARGET" == *windows* ]] && ext=".exe" - if [[ "$TARGET" == *-musl || "$TARGET" == "aarch64-unknown-linux-gnu" ]]; then + if [[ "$TARGET" == *-musl ]]; then cross build "${flags[@]}" else cargo build "${flags[@]}" @@ -176,44 +234,84 @@ jobs: bins=(anvil cast chisel forge) for name in "${bins[@]}"; do - bin=$OUT_DIR/$name$ext - echo "" + bin="$OUT_DIR/$name$ext" + printf '\n' file "$bin" || true du -h "$bin" || true ldd "$bin" || true $bin --version || true - echo "${name}_bin_path=${bin}" >> $GITHUB_ENV + printf '%s_bin_path=%s\n' "$name" "$bin" >> "$GITHUB_ENV" done - name: Archive binaries id: artifacts env: PLATFORM_NAME: ${{ matrix.platform }} - OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} ARCH: ${{ matrix.arch }} shell: bash run: | if [[ "$PLATFORM_NAME" == "linux" || "$PLATFORM_NAME" == "alpine" ]]; then - tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel - echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT + tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel + printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" elif [ "$PLATFORM_NAME" == "darwin" ]; then # We need to use gtar here otherwise the archive is corrupt. # See: https://github.com/actions/virtual-environments/issues/2619 - gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel - echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT + gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel + printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" else - cd $OUT_DIR + cd "$OUT_DIR" 7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ - echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> $GITHUB_OUTPUT + printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" + fi + printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" + printf "foundry_sbom=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.spdx.json" >> "$GITHUB_OUTPUT" + printf "foundry_checksum=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sha256" >> "$GITHUB_OUTPUT" + printf "foundry_signature=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sigstore.json" >> "$GITHUB_OUTPUT" + + - name: Generate archive checksum + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + shell: bash + run: | + set -euo pipefail + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$FILE_NAME" > "$FOUNDRY_CHECKSUM" + else + shasum -a 256 "$FILE_NAME" > "$FOUNDRY_CHECKSUM" fi + cat "$FOUNDRY_CHECKSUM" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + + - name: Generate SBOM (SPDX) + env: + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash + run: | + set -euo pipefail + syft scan dir:. \ + --source-name foundry \ + --source-version "$VERSION_NAME" \ + -o spdx-json="$FOUNDRY_SBOM" + + - name: Upload build artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + retention-days: 1 + name: ${{ steps.artifacts.outputs.file_name }} + path: ${{ steps.artifacts.outputs.file_name }} - name: Build man page id: man if: matrix.target == 'x86_64-unknown-linux-gnu' env: - OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} shell: bash run: | @@ -227,80 +325,111 @@ jobs: gzip anvil.1 gzip chisel.1 tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz - echo "foundry_man=foundry_man_${VERSION_NAME}.tar.gz" >> $GITHUB_OUTPUT + printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - # Creates the release for this specific version - - name: Create release - uses: softprops/action-gh-release@v2 - with: - name: ${{ needs.prepare.outputs.release_name }} - tag_name: ${{ needs.prepare.outputs.tag_name }} - prerelease: ${{ env.IS_NIGHTLY == 'true' }} - body: ${{ needs.prepare.outputs.changelog }} - files: | - ${{ steps.artifacts.outputs.file_name }} - ${{ steps.man.outputs.foundry_man }} - - - name: Binaries attestation - uses: actions/attest-build-provenance@v2 + - name: Binaries and archive provenance attestation + id: attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: | ${{ env.anvil_bin_path }} ${{ env.cast_bin_path }} ${{ env.chisel_bin_path }} ${{ env.forge_bin_path }} + ${{ steps.artifacts.outputs.file_name }} - # If this is a nightly release, it also updates the release - # tagged `nightly` for compatibility with `foundryup` - - name: Update nightly release - if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@v2 + - name: Archive SBOM attestation + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: - name: "Nightly" - tag_name: "nightly" - prerelease: true - body: ${{ needs.prepare.outputs.changelog }} - files: | - ${{ steps.artifacts.outputs.file_name }} - ${{ steps.man.outputs.foundry_man }} + subject-path: ${{ steps.artifacts.outputs.file_name }} + sbom-path: ${{ steps.artifacts.outputs.foundry_sbom }} - cleanup: - name: Release cleanup - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: release - if: always() - steps: - - uses: actions/checkout@v4 + - name: Install cosign + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 - # Moves the `nightly` tag to `HEAD` - - name: Move nightly tag - if: ${{ env.IS_NIGHTLY == 'true' }} - uses: actions/github-script@v7 - with: - script: | - const moveTag = require('./.github/scripts/move-tag.js') - await moveTag({ github, context }, 'nightly') + - name: Sign archive with cosign (keyless) + env: + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} + shell: bash + run: | + set -euo pipefail + cosign sign-blob \ + --yes \ + --bundle "$FOUNDRY_SIGNATURE" \ + "$FILE_NAME" - - name: Delete old nightlies - uses: actions/github-script@v7 - with: - script: | - const prunePrereleases = require('./.github/scripts/prune-prereleases.js') - await prunePrereleases({github, context}) + - name: Record attestation URL + env: + ATTESTATION_URL: ${{ steps.attestation.outputs.attestation-url }} + FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} + shell: bash + run: | + set -euo pipefail + printf '%s\n' "$ATTESTATION_URL" > "$FOUNDRY_ATTESTATION" + + # Upload assets to the draft release created in `prepare`. Stable + # releases stay as drafts after this workflow finishes; a maintainer + # publishes them manually. Nightlies are auto-published below. + - name: Upload assets to draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG_NAME: ${{ needs.prepare.outputs.tag_name }} + FILE_NAME: ${{ steps.artifacts.outputs.file_name }} + FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} + FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }} + FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }} + FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }} + FOUNDRY_MAN: ${{ steps.man.outputs.foundry_man }} + shell: bash + run: | + set -euo pipefail + files=( + "$FILE_NAME" + "$FOUNDRY_ATTESTATION" + "$FOUNDRY_SBOM" + "$FOUNDRY_CHECKSUM" + "$FOUNDRY_SIGNATURE" + ) + if [[ -n "${FOUNDRY_MAN:-}" ]]; then + files+=("$FOUNDRY_MAN") + fi + gh release upload "$TAG_NAME" "${files[@]}" --clobber + + # Auto-publish nightly releases once all assets have been uploaded so that + # foundryup and other consumers see them immediately. Stable releases are + # left as drafts for a maintainer to publish manually. + publish-nightly: + name: Publish nightly release + runs-on: ubuntu-latest + needs: [prepare, release-docker, release] + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + permissions: + contents: write + steps: + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG_NAME: ${{ needs.prepare.outputs.tag_name }} + shell: bash + run: gh release edit "$TAG_NAME" --draft=false # If any of the jobs fail, this will create a high-priority issue to signal so. issue: name: Open an issue runs-on: ubuntu-latest - needs: [prepare, release-docker, release, cleanup] + needs: [prepare, release-docker, release, publish-nightly] if: failure() permissions: - issues: write contents: read + issues: write steps: - - uses: actions/checkout@v4 - - uses: JasonEtco/create-an-issue@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..fcfbbbbec7948 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v6 + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test-flaky.yml b/.github/workflows/test-flaky.yml new file mode 100644 index 0000000000000..2694ba847cfa7 --- /dev/null +++ b/.github/workflows/test-flaky.yml @@ -0,0 +1,131 @@ +# Daily CI job to run flaky tests that are excluded from regular CI via nextest default-filter + +name: test-flaky + +permissions: {} + +on: + schedule: + - cron: "0 1 * * *" # Run daily at 1 AM UTC (offset from test-isolate) + workflow_dispatch: # Needed so we can run it manually + +concurrency: + group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" + +jobs: + test: + name: flaky tests + runs-on: depot-ubuntu-latest-16 + timeout-minutes: 60 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 + with: + tool: nextest + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.14" + - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev + run: pip --version && pip install vyper==0.4.3 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - name: Test flaky tests + env: + SVM_TARGET_PLATFORM: linux-amd64 + ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} + run: cargo nextest run --locked --profile flaky --no-fail-fast + + # Run flaky tests with isolation enabled + test-isolate: + name: flaky tests (isolate) + runs-on: depot-ubuntu-latest-16 + timeout-minutes: 60 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: stable + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 + with: + tool: nextest + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.14" + - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev + run: pip --version && pip install vyper==0.4.3 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - name: Test flaky tests with isolation + env: + SVM_TARGET_PLATFORM: linux-amd64 + ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} + run: cargo nextest run --locked --profile flaky --features=isolate-by-default --no-fail-fast + + # If any of the jobs fail, this will create a normal-priority issue to signal so. + issue: + name: Open an issue + runs-on: ubuntu-latest + needs: [test] + if: failure() + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/FLAKY_TEST_FAILURE_TEMPLATE.md + + # If isolated flaky tests fail, create a normal-priority issue. + issue-isolate: + name: Open an isolated flaky issue + runs-on: ubuntu-latest + needs: [test-isolate] + if: failure() + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml index 119a6bd55adec..0e4f95a07eeaa 100644 --- a/.github/workflows/test-isolate.yml +++ b/.github/workflows/test-isolate.yml @@ -1,14 +1,27 @@ -# Daily CI job to run tests with isolation mode enabled by default +# CI job to run tests with isolation mode enabled by default name: test-isolate +permissions: {} + on: - schedule: - - cron: "0 0 * * *" - workflow_dispatch: + push: + branches: [master] + workflow_dispatch: # Needed so we can run it manually + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" jobs: nextest: - uses: ./.github/workflows/nextest.yml + uses: ./.github/workflows/test.yml + permissions: + contents: read with: profile: isolate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 716abb3648f13..daa4822b6e395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,146 +1,124 @@ +# Reusable workflow for running tests via `cargo nextest` + name: test +permissions: {} + on: - push: - branches: - - master - - main - - dev - - pull_request: + workflow_call: + inputs: + profile: + required: true + type: string + secrets: + ETHERSCAN_API_KEY: + required: false + ARBITRUM_RPC: + required: false + ETH_SEPOLIA_RPC: + required: false + concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" + RUST_MIN_STACK: 4194304 # 2 * the default (2 * 1024 * 1024) jobs: - nextest: - uses: ./.github/workflows/nextest.yml - with: - profile: default - secrets: inherit - - docs: + matrices: + name: build matrices runs-on: ubuntu-latest - timeout-minutes: 30 + permissions: + contents: read + outputs: + test-matrix: ${{ steps.gen.outputs.test-matrix }} steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - cache-on-failure: true - - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items - env: - RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - - name: Deploy documentation - uses: peaceiris/actions-gh-pages@v3 - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: target/doc - force_orphan: true - commit_message: "Deploy documentation [skip ci]" - - name: Push changes - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - to pass before a push is allowed. - git commit -S -m "Auto-update gh-pages" - git push origin --force gh-pages - -### Solution **Review Repository Rules:** - - doctest: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - cache-on-failure: true - - run: cargo test --workspace --doc + python-version: "3.14" + - name: Generate matrices + id: gen + env: + EVENT_NAME: ${{ github.event_name }} + PROFILE: ${{ inputs.profile }} + shell: bash + run: | + output=$(python3 .github/scripts/matrices.py) + printf '::debug::test-matrix=%s\n' "$output" + printf 'test-matrix=%s\n' "$output" >> "$GITHUB_OUTPUT" - codespell: - runs-on: ubuntu-latest - timeout-minutes: 30 + test: + name: test ${{ matrix.name }} + runs-on: ${{ matrix.runner_label }} + timeout-minutes: 60 + permissions: + contents: read + needs: matrices + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} steps: - - uses: actions/checkout@v4 - - uses: codespell-project/actions-codespell@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - skip: "*.json" - - clippy: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@clippy - - uses: Swatinem/rust-cache@v2 + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: - cache-on-failure: true - - run: cargo clippy --workspace --all-targets --all-features - env: - RUSTFLAGS: -Dwarnings - - rustfmt: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + toolchain: stable + target: ${{ matrix.target }} + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458 # v2.75.19 with: - toolchain: nightly-2024-02-03 - components: rustfmt - - run: cargo fmt --all --check + tool: nextest - forge-fmt: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + # External tests dependencies + - name: Setup Node.js + if: contains(matrix.name, 'external') + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - cache-on-failure: true - - name: forge fmt - shell: bash - run: ./.github/scripts/format.sh --check - - crate-checks: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v2 + node-version: 24 + - name: Install Bun + if: contains(matrix.name, 'external') + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: - cache-on-failure: true - - run: cargo hack check --each-feature --exclude-features isolate-by-default - - deny: - uses: ithacaxyz/ci/.github/workflows/deny.yml@main + bun-version: latest + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.14" + - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev + run: pip --version && pip install vyper==0.4.3 - ci-success: - runs-on: ubuntu-latest - if: always() - needs: - - nextest - - docs - - doctest - - codespell - - clippy - - rustfmt - - forge-fmt - - crate-checks - - deny - timeout-minutes: 30 - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 + - name: Foundry test cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: - jobs: ${{ toJSON(needs) }} + path: | + ~/.foundry/cache + ~/.config/.foundry/cache + testdata/cache + testdata/out + # Use a unique key for each run to always update the cache. + key: ${{ runner.os }}-foundry-${{ matrix.name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-foundry-${{ matrix.name }}- + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + - name: Setup Git config + run: | + git config --global user.name "GitHub Actions Bot" + git config --global user.email "<>" + git config --global url."https://github.com/".insteadOf "git@github.com:" + - name: Test + env: + SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} + ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} + ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} + run: cargo nextest run --locked ${{ matrix.flags }} diff --git a/.gitignore b/.gitignore index 9297bbbc7d4f2..788edbe04935f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,26 @@ .DS_STORE /target* +/*.sol +CLAUDE.md + +# Foundry artifacts out/ snapshots/ -out.json + +# IDEs and extensions .idea .vscode +.claude +.zed + +# NPM +.env +node_modules +npm/dist +npm/bin +_ +*.tgz +.vercel +.vite +.wrangler +*.zip diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b85713d56cdf..47b59471eb1d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,31 @@ around bugs and participate in reviewing PRs. ### Contributions Related to Spelling and Grammar -At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or -elsewhere. +At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or elsewhere. + +### Contributions Assisted by AI + +If you are using any kind of AI assistance while contributing to Foundry, +**this must be disclosed in the pull request**, along with the extent to +which AI assistance was used (e.g. docs only vs. code generation). + +If PR responses are being generated by an AI, disclose that as well. + +As a small exception, trivial tab-completion doesn't need to be disclosed, +so long as it is limited to single keywords or short phrases. + +An example disclosure: + +> This PR was written primarily by Claude Code. + +Or a more detailed disclosure: + +> I consulted ChatGPT to understand the codebase but the solution +> was fully authored manually by myself. + +Failure to disclose this makes it difficult to determine how much scrutiny to apply to the contribution as it is unclear how much the contributor themselves understands the code they propose to merge. + +If you do not disclose your use of AI assistance and reviewers suspect your contribution to be AI generated, it will likely be refused on those grounds. ### Asking for help @@ -86,7 +109,7 @@ Please also make sure that the following commands pass if you have changed the c cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check -cargo +nightly clippy --all --all-targets --all-features -- -D warning +cargo +nightly clippy --workspace --all-targets --all-features -- -D warnings ``` or alternatively: @@ -108,9 +131,9 @@ If you are working in VSCode, we recommend you install the [rust-analyzer](https If you are working on a larger feature, we encourage you to open up a draft pull request, to make sure that other contributors are not duplicating work. -If you would like to test the binaries built from your change, see [foundryup](https://github.com/foundry-rs/foundry/tree/master/foundryup). +If you would like to test the binaries built from your change, see [foundryup](https://github.com/foundry-rs/foundry/tree/HEAD/foundryup). -If you would like to use a debugger with breakpoints to debug a patch you might be working on, keep in mind we currently strip debug info for faster builds, which is _not_ the default. Therefore, to use a debugger, you need to enable it on the workspace [`Cargo.toml`'s `dev` profile](https://github.com/foundry-rs/foundry/tree/master/Cargo.toml#L15-L18). +If you would like to use a debugger with breakpoints to debug a patch you might be working on, keep in mind we currently strip debug info for faster builds, which is _not_ the default. Therefore, to use a debugger, you need to enable it on the workspace [`Cargo.toml`'s `dev` profile](https://github.com/foundry-rs/foundry/tree/HEAD/Cargo.toml#L15-L18). #### Adding tests @@ -186,7 +209,7 @@ Be aware that _how_ you communicate requests and reviews in your feedback can ha ##### Abandoned or stale pull requests -If a pull request appears to be abandoned or stalled, it is polite to first check with the contributor to see if they intend to continue the work before checking if they would mind if you took it over (especially if it just has nits left). When doing so, it is courteous to give the original contributor credit for the work they started, either by preserving their name and e-mail address in the commit log, or by using the `Author: ` or `Co-authored-by: ` metadata tag in the commits. +If a pull request appears to be abandoned or stalled, it is polite to first check with the contributor to see if they intend to continue the work before checking if they would mind if you took it over (especially if it just has nits left). When doing so, it is courteous to give the original contributor credit for the work they started, either by preserving their name and e-mail address in the commit log, or by using the `Author:` or `Co-authored-by:` metadata tag in the commits. _Adapted from the [ethers-rs contributing guide](https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md)_. @@ -198,9 +221,9 @@ Releases are automatically done by the release workflow when a tag is pushed, ho 2. Update documentation links 3. Perform a final audit for breaking changes. -[rust-coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md +[rust-coc]: https://www.rust-lang.org/policies/code-of-conduct [dev-tg]: https://t.me/foundry_rs [foundry-book]: https://github.com/foundry-rs/foundry-book [support-tg]: https://t.me/foundry_support [mcve]: https://stackoverflow.com/help/mcve -[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment +[hiding-a-comment]: https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-disruptive-comments diff --git a/Cargo.lock b/Cargo.lock index c6cae03ce7321..dd0019575831e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,28 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] [[package]] name = "aes" @@ -25,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -35,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -43,74 +53,113 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8010fc7e9e8643ef4e758cdccf3eef26734594aedf88a9d5ed35e51837d42ef" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips 2.0.4", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde 2.0.4", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", +] + [[package]] name = "alloy-chains" -version = "0.2.2" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517e5acbd38b6d4c59da380e8bbadc6d365bf001903ce46cf5521c53c647e07b" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives", + "alloy-rlp", "num_enum", "serde", - "strum 0.27.1", + "strum", ] [[package]] name = "alloy-consensus" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7329eb72d95576dfb8813175bcf671198fb24266b0b3e520052a513e30c284df" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-trie", + "alloy-tx-macros", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", "k256", "once_cell", - "rand 0.8.5", - "secp256k1", + "rand 0.8.6", + "secp256k1 0.30.0", "serde", + "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-consensus-any" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31b286aeef04a32720c10defd21c3aa6c626154ac442b55f6d472caeb1c6741" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-contract" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1658352ca9425d7b5bbb3ae364bc276ab18d4afae06f5faf00377b6964fdf68" +checksum = "de3df0aadc569a8b277808a7d0ad0e421180654ea36a3c59e9ed2bb968c9a1cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -125,27 +174,41 @@ dependencies = [ "alloy-transport", "futures", "futures-util", - "thiserror 2.0.12", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-core" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e8604b0c092fabc80d075ede181c9b9e596249c70b99253082d7e689836529" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", ] [[package]] name = "alloy-dyn-abi" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cc14d832bc3331ca22a1c7819de1ede99f58f61a7d123952af7dde8de124a6" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "arbitrary", - "derive_arbitrary", - "derive_more 2.0.1", + "derive_more", "itoa", "proptest", "serde", "serde_json", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -158,117 +221,177 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-eip2930" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip5792" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ceb16e7fe5a95825305f218ccd356665f848831f94ce2bbf55339bf5d21e88a" +dependencies = [ + "alloy-primitives", + "alloy-serde 2.0.4", "serde", + "serde_json", ] [[package]] name = "alloy-eip7702" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "k256", "serde", - "thiserror 2.0.12", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "once_cell", + "serde", ] [[package]] name = "alloy-eips" -version = "1.0.7" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 1.8.3", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "serde", + "serde_with", + "sha2 0.10.9", +] + +[[package]] +name = "alloy-eips" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa190bfa5340aee544ac831114876fa73bc8da487095b49a5ea153a6a4656ea" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", + "serde_with", "sha2 0.10.9", ] [[package]] name = "alloy-ens" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecbc80925fcfa5fa20ad45392535a98f586ee96ad75f8e801ca7e4d9176e0c4" +checksum = "d5638cbbffb318d440fdb009de019090d8d117dae40de9d10cdb29891ea59eb9" dependencies = [ "alloy-contract", "alloy-primitives", "alloy-provider", "alloy-sol-types", "async-trait", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-evm" -version = "0.10.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394b09cf3a32773eedf11828987f9c72dfa74545040be0422e3f5f09a2a3fab9" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", - "derive_more 2.0.1", - "op-alloy-consensus", - "op-revm", + "derive_more", "revm", - "thiserror 2.0.12", + "thiserror 2.0.18", + "tracing", ] [[package]] name = "alloy-genesis" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b81b2dfd278d58af8bfde8753fa4685407ba8fbad8bc88a2bb0e065eed48478" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-trie", + "borsh", "serde", + "serde_with", ] [[package]] name = "alloy-hardforks" -version = "0.2.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbff8445282ec080c2673692062bd4930d7a0d6bda257caf138cfc650c503000" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", "alloy-primitives", "auto_impl", "dyn-clone", + "serde", ] [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -278,130 +401,133 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ab2dba5dca01ad4281b4d4726a18e2012a20e3950bfc2a90c5376840555366" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", + "http 1.4.0", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-network" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ed07e76fbc72790a911ea100cdfbe85b1f12a097c91b948042e854959d140e" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.0.1", + "derive_more", "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-network-primitives" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05aa52713c376f797b3c7077708585f22a5c3053a7b1b2b355ea98edeb2052d" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-op-evm" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f32538cc243ec5d4603da9845cc2f5254c6a3a78e82475beb1a2a1de6c0d36c" +version = "0.31.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-op-hardforks", "alloy-primitives", "auto_impl", - "op-alloy-consensus", + "op-alloy", "op-revm", "revm", + "thiserror 2.0.18", ] [[package]] name = "alloy-op-hardforks" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ddfbb5cc9f614efa5d56e0d7226214bb67b29271d44b6ddfcbbe25eb0ff898b" +version = "0.4.7" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ + "alloy-chains", "alloy-hardforks", + "alloy-primitives", "auto_impl", + "serde", ] [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_arbitrary", - "derive_more 2.0.1", - "foldhash", - "getrandom 0.3.3", - "hashbrown 0.15.3", - "indexmap 2.9.0", + "derive_more", + "foldhash 0.2.0", + "getrandom 0.4.2", + "hashbrown 0.16.1", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", "proptest-derive", - "rand 0.9.1", + "rand 0.9.4", + "rapidhash", "ruint", - "rustc-hash 2.1.1", + "rustc-hash", "serde", "sha3", - "tiny-keccak", ] [[package]] name = "alloy-provider" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a3f7a59c276c6e410267e77a166f9297dbe74e4605f1abf625e29d85c53144" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-anvil", "alloy-rpc-types-debug", "alloy-rpc-types-eth", "alloy-rpc-types-trace", @@ -419,13 +545,13 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru 0.13.0", + "lru", "parking_lot", - "pin-project 1.1.10", - "reqwest", + "pin-project", + "reqwest 0.13.3", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -434,13 +560,14 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3cbf02fdedbec7aadc7a77080b6b143752fa792c7fd49b86fd854257688bd4" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", "alloy-transport", + "auto_impl", "bimap", "futures", "parking_lot", @@ -455,9 +582,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.12" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -466,20 +593,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.12" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "alloy-rpc-client" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f185483536cbcbf55971077140e03548dad4f3a4ddb35044bcdc01b8f02ce1" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -488,139 +615,175 @@ dependencies = [ "alloy-transport-http", "alloy-transport-ipc", "alloy-transport-ws", - "async-stream", "futures", - "pin-project 1.1.10", - "reqwest", + "pin-project", + "reqwest 0.13.3", "serde", "serde_json", "tokio", "tokio-stream", "tower", "tracing", - "tracing-futures", "url", "wasmtimer", ] [[package]] name = "alloy-rpc-types" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347dfd77ba4d74886dba9e2872ff64fb246001b08868d27baec94e7248503e08" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e15bd6456742d6dcadacf3cd238a90a8a7aa9f00bc7cc641ae272f5d3f5d4f" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67971a228100ac65bd86e90439028853435f21796330ef08f00a70a918a84126" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", + "alloy-network-primitives", + "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-rpc-types-beacon" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" +dependencies = [ + "alloy-eips 2.0.4", + "alloy-primitives", + "alloy-rpc-types-engine", + "derive_more", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-debug" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe8bc37b23e788c0f8081a7eec34fd439cfa8d4f137f6e987803fb2a733866ca" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", + "alloy-rlp", + "derive_more", "serde", + "serde_with", ] [[package]] name = "alloy-rpc-types-engine" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bcf49fe91b3d621440dcc5bb067afaeba5ca4b07f59e42fb7af42944146a8c0" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", - "derive_more 2.0.1", + "alloy-serde 2.0.4", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.6", "serde", - "strum 0.27.1", + "strum", ] [[package]] name = "alloy-rpc-types-eth" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d9b4293dfd4721781d33ee40de060376932d4a55d421cf6618ad66ff97cc52" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-sol-types", "itertools 0.14.0", "serde", "serde_json", - "thiserror 2.0.12", + "serde_with", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-trace" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f68f020452c0d570b4eee22d4ffda9e4eda68ebcf67e1199d6dff48097f442b" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a82f15f296c2c83c55519d21ca07801fb58b118878b0f4777250968e49f4fe" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-serde" -version = "1.0.7" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7d927aa39ca51545ae4c9cf4bdb2cbc1f6b46ab4b54afc3ed9255f93eedbce" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "serde", @@ -629,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63771b50008d2b079187e9e74a08235ab16ecaf4609b4eb895e2890a3bcd465" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -641,32 +804,33 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-signer-aws" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a8f3d6f2e11b5a4f73ab34f3b2b526684e6b26e2a2ebf2264c1dae45030451" +checksum = "1258987fbc82716b5153ec7bb95a8a295e7640871b8f03d8ec7c4000dc80c215" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", + "aws-config", "aws-sdk-kms", "k256", "spki", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-gcp" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af53c8eb7b94b8a9b915b64a0fc56b4c1fde247d8aeb421504f25d673a4d360a" +checksum = "7ffc2a49bca5b73c6964711b57452f6c36a6bcb7f845ab7e9ad05b5a828d0161" dependencies = [ "alloy-consensus", "alloy-network", @@ -676,15 +840,15 @@ dependencies = [ "gcloud-sdk", "k256", "spki", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-ledger" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a43041a6f1c5b63659860311ab14ea765bf9cef3293322988d896a0b8c88c" +checksum = "94e11ddaddfb98c1ddce737dc440225565b0ae0987ac9ad5e59a85db5904878c" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -695,16 +859,16 @@ dependencies = [ "async-trait", "coins-ledger", "futures-util", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.28", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-signer-local" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db906294ee7876bd332cd760f460d30de183554434e07fc19d7d54e16a7aeaf0" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -715,65 +879,82 @@ dependencies = [ "coins-bip39", "eth-keystore", "k256", - "rand 0.8.5", - "thiserror 2.0.12", + "rand 0.8.6", + "thiserror 2.0.18", + "zeroize", ] [[package]] name = "alloy-signer-trezor" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2277b489b01e6178fc5404c978bdc82b211c9c50de4b1e0efb75ecea3eb2bf54" +checksum = "44eb341d0013784da6a39e5bbdc11b95d6744993b12a1c3fd55df795a850dd42" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.28", + "thiserror 2.0.18", "tracing", "trezor-client", ] +[[package]] +name = "alloy-signer-turnkey" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ff16b4166fb90bbe79bd1e49244824fb3cadc6b8cd11e9c8a002c1f8c07492" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "thiserror 2.0.18", + "tracing", + "turnkey_client", +] + [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.9.0", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "sha3", + "syn 2.0.117", "syn-solidity", - "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" dependencies = [ "alloy-json-abi", "const-hex", @@ -783,25 +964,25 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -811,20 +992,20 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9b645fe4f4e6582cfbb4a8d20cedcf5aa23548e92eacbdacac6278b425e023" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "auto_impl", "base64 0.22.1", - "derive_more 2.0.1", + "derive_more", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -834,13 +1015,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee18869ecabe658ff6316e7db7c25d958c7d10f0a1723c2f7447d4f402920b66" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest", + "itertools 0.14.0", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -849,9 +1031,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff95f0b3a3bd2b80a53a52f7649ea6ef3e7e91ff4bd439871199ec68b1b69038" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -859,7 +1041,7 @@ dependencies = [ "bytes", "futures", "interprocess", - "pin-project 1.1.10", + "pin-project", "serde", "serde_json", "tokio", @@ -869,57 +1051,51 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.7" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c838e7562d16110fba3590a20c7765281c1a302cf567e1806d0c3ce1352b58" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 1.3.1", + "http 1.4.0", "rustls", "serde_json", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tracing", + "url", "ws_stream_wasm", ] [[package]] name = "alloy-trie" -version = "0.8.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", - "derive_more 2.0.1", + "derive_more", "nybbles", "serde", "smallvec", + "thiserror 2.0.18", "tracing", ] [[package]] -name = "ammonia" -version = "4.1.0" +name = "alloy-tx-macros" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ada2ee439075a3e70b6992fce18ac4e407cd05aea9ca3f75d2c0b0c20bbb364" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ - "cssparser", - "html5ever", - "maplit", - "tendril", - "url", + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -934,20 +1110,45 @@ name = "annotate-snippets" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.2", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" dependencies = [ "anstyle", "memchr", - "unicode-width 0.2.0", + "unicode-width 0.2.2", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -957,82 +1158,91 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-lossy" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b" +checksum = "d9ca7d0f520afcd6d817970d0b2d5fd7c630c75e7783cae046b8b8a783c5befa" dependencies = [ "anstyle", ] [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] -name = "anstyle-query" -version = "1.1.2" +name = "anstyle-parse" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ - "windows-sys 0.59.0", + "utf8parse", ] [[package]] -name = "anstyle-svg" -version = "0.1.7" +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-svg" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35" +checksum = "e22d9f3dea8bbda97c75bd0f0203e23f1e190d6d6f27a40e10063946dc4d4362" dependencies = [ - "anstream", "anstyle", "anstyle-lossy", + "anstyle-parse 0.2.7", "html-escape", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anvil" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", - "alloy-hardforks", "alloy-network", "alloy-op-evm", - "alloy-op-hardforks", "alloy-primitives", "alloy-provider", "alloy-pubsub", "alloy-rlp", "alloy-rpc-types", - "alloy-serde", + "alloy-rpc-types-beacon", + "alloy-rpc-types-eth", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -1046,8 +1256,8 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_fig", "ctrlc", + "ethereum_ssz", "eyre", "fdlimit", "flate2", @@ -1055,7 +1265,8 @@ dependencies = [ "foundry-common", "foundry-config", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", + "foundry-primitives", "foundry-test-utils", "futures", "hyper", @@ -1064,47 +1275,54 @@ dependencies = [ "op-alloy-rpc-types", "op-revm", "parking_lot", - "rand 0.8.5", - "rand 0.9.1", + "rand 0.8.6", + "rand 0.9.4", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", "serde_json", "tempfile", - "thiserror 2.0.12", + "tempo-alloy", + "tempo-chainspec", + "tempo-evm", + "tempo-precompiles", + "tempo-primitives", + "tempo-revm", + "thiserror 2.0.18", "tokio", "tracing", - "tracing-subscriber 0.3.20", + "tracing-subscriber 0.3.23", "yansi", ] [[package]] name = "anvil-core" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips", + "alloy-eip5792", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 2.0.4", "bytes", "foundry-common", "foundry-evm", - "op-alloy-consensus", - "op-revm", - "rand 0.9.1", + "foundry-primitives", + "rand 0.9.4", "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "anvil-rpc" -version = "1.2.1" +version = "1.7.2" dependencies = [ "serde", "serde_json", @@ -1112,7 +1330,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.2.1" +version = "1.7.2" dependencies = [ "anvil-rpc", "async-trait", @@ -1122,10 +1340,10 @@ dependencies = [ "futures", "interprocess", "parking_lot", - "pin-project 1.1.10", + "pin-project", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio-util", "tower-http", "tracing", @@ -1133,29 +1351,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] -[[package]] -name = "ariadne" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f5e3dca4e09a6f340a61a0e9c7b61e030c69fc27bf29d73218f7e5e3b7638f" -dependencies = [ - "unicode-width 0.1.14", - "yansi", -] - [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1193,7 +1401,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.5", "itertools 0.13.0", "num-bigint", "num-integer", @@ -1286,7 +1494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -1324,7 +1532,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -1339,7 +1547,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.5", ] [[package]] @@ -1413,7 +1621,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -1423,7 +1631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1433,7 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1443,7 +1651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1459,26 +1667,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ "serde", -] - -[[package]] -name = "ascii-canvas" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" -dependencies = [ - "term", + "zeroize", ] [[package]] name = "async-compression" -version = "0.4.23" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ - "flate2", - "futures-core", - "memchr", + "compression-codecs", + "compression-core", "pin-project-lite", "tokio", ] @@ -1511,18 +1710,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -1538,9 +1737,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] @@ -1575,20 +1774,20 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.6.3" +version = "1.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a18fd934af6ae7ca52410d4548b98eb895aab0f1ea417d168d85db1434a141" +checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1605,8 +1804,8 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 1.3.1", - "ring", + "http 1.4.0", + "sha1", "time", "tokio", "tracing", @@ -1616,9 +1815,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.3" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -1628,21 +1827,21 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ - "bindgen", "cc", "cmake", "dunce", @@ -1651,9 +1850,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.7" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1664,26 +1863,28 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "bytes-utils", "fastrand", - "http 0.2.12", - "http-body 0.4.6", + "http 1.4.0", + "http-body 1.0.1", "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.17.0", + "uuid 1.23.1", ] [[package]] name = "aws-sdk-kms" -version = "1.71.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de993b5250e1aa051f4091ab772ce164de8c078ee9793fdee033b20f7d371ad" +checksum = "c41ae6a33da941457e89075ef8ca5b4870c8009fe4dceeba82fce2f30f313ac6" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1691,21 +1892,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sso" -version = "1.70.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83447efb7179d8e2ad2afb15ceb9c113debbc2ecdf109150e338e2e28b86190b" +checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1713,21 +1916,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.71.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f9bfbbda5e2b9fe330de098f14558ee8b38346408efe9f2e9cee82dc1636a4" +checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1735,21 +1940,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.71.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17b984a66491ec08b4f4097af8911251db79296b3e4a763060b45805746264f" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -1758,15 +1965,16 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3734aecf9ff79aa401a6ca099d076535ab465ff76b46440cf567c8e70b65dc13" +checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1777,7 +1985,7 @@ dependencies = [ "hex", "hmac", "http 0.2.12", - "http 1.3.1", + "http 1.4.0", "percent-encoding", "sha2 0.10.9", "time", @@ -1786,9 +1994,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" dependencies = [ "futures-util", "pin-project-lite", @@ -1797,18 +2005,19 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.1" +version = "0.63.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "bytes-utils", "futures-core", - "http 0.2.12", - "http 1.3.1", - "http-body 0.4.6", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", "percent-encoding", "pin-project-lite", "pin-utils", @@ -1817,15 +2026,15 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.2" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e44697a9bded898dcd0b1cb997430d949b87f4f8940d91023ae9062bf218250" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", "h2", - "http 1.3.1", + "http 1.4.0", "hyper", "hyper-rustls", "hyper-util", @@ -1834,33 +2043,34 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "tokio", + "tokio-rustls", "tower", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.62.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.1.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.7" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" dependencies = [ "aws-smithy-types", "urlencoding", @@ -1868,9 +2078,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14302f06d1d5b7d333fd819943075b13d27c7700b414f574c3c35859bfb55d5e" +checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1881,9 +2091,10 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.3.1", + "http 1.4.0", "http-body 0.4.6", "http-body 1.0.1", + "http-body-util", "pin-project-lite", "pin-utils", "tokio", @@ -1892,32 +2103,44 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" dependencies = [ "aws-smithy-async", + "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.3.1", + "http 1.4.0", "pin-project-lite", "tokio", "tracing", "zeroize", ] +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "aws-smithy-types" -version = "1.3.1" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" dependencies = [ "base64-simd", "bytes", "bytes-utils", "http 0.2.12", - "http 1.3.1", + "http 1.4.0", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1932,18 +2155,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -1955,16 +2178,16 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper", @@ -1975,15 +2198,14 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -1992,18 +2214,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -2012,9 +2233,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -2022,7 +2243,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2055,9 +2276,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bech32" @@ -2071,29 +2292,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.101", - "which 4.4.2", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -2111,15 +2309,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -2133,11 +2331,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2153,6 +2351,21 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", + "zeroize", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -2171,11 +2384,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "blst" -version = "0.3.14" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", @@ -2183,11 +2414,152 @@ dependencies = [ "zeroize", ] +[[package]] +name = "boa_ast" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6339a700715bda376f5ea65c76e8fe8fc880930d8b0638cea68e7f3da6538e0a" +dependencies = [ + "bitflags 2.11.1", + "boa_interner", + "boa_macros", + "boa_string", + "indexmap 2.14.0", + "num-bigint", + "rustc-hash", +] + +[[package]] +name = "boa_engine" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1521be326f8a5c8887e95d4ce7f002917a002a23f7b93b9a6a2bf50ed4157824" +dependencies = [ + "aligned-vec", + "arrayvec", + "bitflags 2.11.1", + "boa_ast", + "boa_gc", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_string", + "bytemuck", + "cfg-if", + "cow-utils", + "dashmap", + "dynify", + "fast-float2", + "float16", + "futures-channel", + "futures-concurrency", + "futures-lite", + "hashbrown 0.16.1", + "icu_normalizer", + "indexmap 2.14.0", + "intrusive-collections", + "itertools 0.14.0", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "paste", + "portable-atomic", + "rand 0.9.4", + "regress", + "rustc-hash", + "ryu-js", + "serde", + "serde_json", + "small_btree", + "static_assertions", + "tag_ptr", + "tap", + "thin-vec", + "thiserror 2.0.18", + "time", + "xsum", +] + +[[package]] +name = "boa_gc" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17323a98cf2e631afacf1a6d659c1212c48a68bacfa85afab0a66ade80582e51" +dependencies = [ + "boa_macros", + "boa_string", + "hashbrown 0.16.1", + "thin-vec", +] + +[[package]] +name = "boa_interner" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20510b8b02bcde9b0a01cf34c0c308c56156503d1d91cdab4c8cfbd292b747ea" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "once_cell", + "phf 0.13.1", + "rustc-hash", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5822cb4f146d243060e588bc5a5f2e709683fdad3d7111f42c48e6b5c921d23d" +dependencies = [ + "cfg-if", + "cow-utils", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "boa_parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd957fa9fa93e3a001a8aba5a5cd40c2bbfde486378be4c4b472fd304aaddb" +dependencies = [ + "bitflags 2.11.1", + "boa_ast", + "boa_interner", + "boa_macros", + "fast-float2", + "icu_properties", + "num-bigint", + "num-traits", + "regress", + "rustc-hash", +] + +[[package]] +name = "boa_string" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2da1d7f4a76fd9040788a122f0d807910800a7b86f5952e9244848c36511de" +dependencies = [ + "fast-float2", + "itoa", + "paste", + "rustc-hash", + "ryu-js", + "static_assertions", +] + [[package]] name = "bon" -version = "3.6.3" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" dependencies = [ "bon-macros", "rustversion", @@ -2195,19 +2567,49 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.3" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", ] +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + [[package]] name = "bs58" version = "0.5.1" @@ -2220,9 +2622,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -2231,9 +2633,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -2243,9 +2645,23 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "byteorder" @@ -2255,9 +2671,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -2272,30 +2688,11 @@ dependencies = [ "either", ] -[[package]] -name = "bzip2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -2308,11 +2705,11 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2332,27 +2729,23 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "thiserror 1.0.69", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cast" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", + "alloy-eips 2.0.4", "alloy-ens", + "alloy-evm", "alloy-hardforks", "alloy-json-abi", "alloy-json-rpc", @@ -2361,45 +2754,50 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde", + "alloy-rpc-types-beacon", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "alloy-sol-types", "alloy-transport", "anvil", - "aws-sdk-kms", "chrono", "clap", "clap_complete", - "clap_complete_fig", "comfy-table", + "dirs", "dunce", "evmole", "eyre", + "forge-fmt", "foundry-block-explorers", "foundry-cli", "foundry-common", "foundry-compilers", "foundry-config", + "foundry-debugger", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", "foundry-test-utils", "foundry-wallets", "futures", - "gcloud-sdk", "itertools 0.14.0", "op-alloy-consensus", "op-alloy-flz", - "rand 0.8.5", - "rand 0.9.1", + "op-alloy-network", + "rand 0.8.6", + "rand 0.9.4", "rayon", "regex", "revm", "rpassword", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "tempfile", + "tempo-alloy", + "tempo-contracts", + "tempo-primitives", "tokio", "tracing", "yansi", @@ -2407,38 +2805,30 @@ dependencies = [ [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.2.24" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -2446,9 +2836,33 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chisel" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2462,34 +2876,34 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-solang-parser", - "regex", - "reqwest", - "revm", + "foundry-test-utils", + "itertools 0.14.0", + "reqwest 0.13.3", + "rexpect", "rustyline", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", - "solar-parse", - "strum 0.27.1", + "solar-compiler", + "tempfile", "time", - "tokio", "tracing", - "tracing-subscriber 0.3.20", + "tracing-subscriber 0.3.23", "walkdir", "yansi", ] [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -2526,26 +2940,16 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", + "zeroize", ] [[package]] name = "clap" -version = "4.5.38" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -2553,9 +2957,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeab6a5cdfc795a05538422012f20a5496f050223c91be4e5420bfd13c641fb1" +checksum = "9d92b1fab272fe943881b77cc6e920d6543e5b1bfadbd5ed81c7c5a755742394" dependencies = [ "clap", "log", @@ -2563,33 +2967,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", "terminal_size", "unicase", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "clap_complete" -version = "4.5.50" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" +checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" dependencies = [ "clap", ] [[package]] -name = "clap_complete_fig" -version = "4.5.2" +name = "clap_complete_nushell" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b" +checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" dependencies = [ "clap", "clap_complete", @@ -2597,42 +3001,42 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clearscreen" -version = "4.0.1" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4" +checksum = "d669bb552908e336ad5681789752033b45566b7e591aeaac7a614e58e5d6d8f2" dependencies = [ - "nix 0.29.0", + "nix 0.31.2", "terminfo", - "thiserror 2.0.12", - "which 7.0.3", - "windows-sys 0.59.0", + "thiserror 2.0.18", + "which", + "windows-sys 0.61.2", ] [[package]] name = "cliclack" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c420bdc04c123a2df04d9c5a07289195f00007af6e45ab18f55e56dc7e04b8" +checksum = "aa510b739c618c679375ea9c5af44ce9f591289546e874ad5910e7ce7df79844" dependencies = [ - "console", + "console 0.15.11", "indicatif", "once_cell", "strsim", @@ -2642,22 +3046,28 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", ] [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96" + [[package]] name = "coins-bip32" version = "0.12.0" @@ -2685,7 +3095,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2 0.12.2", - "rand 0.8.5", + "rand 0.8.6", "sha2 0.10.9", "thiserror 1.0.69", ] @@ -2711,15 +3121,14 @@ dependencies = [ [[package]] name = "coins-ledger" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9bc0994d0aa0f4ade5f3a9baf4a8d936f250278c85a1124b401860454246ab" +checksum = "c707b8909cef367cd04a11b0d71d65ab34a625d295a07869dd2fff2ca95bf688" dependencies = [ "async-trait", "byteorder", "cfg-if", "const-hex", - "getrandom 0.2.16", "hidapi-rusb", "js-sys", "log", @@ -2734,56 +3143,65 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e1761c0e16f8883bbbb8ce5990867f4f06bf11a0253da6495a04ce4b6ef0ec" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ "backtrace", "color-spantrace", "eyre", "indenter", "once_cell", - "owo-colors 4.2.1", + "owo-colors", "tracing-error", ] [[package]] name = "color-spantrace" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ddd8d5bfda1e11a501d0a7303f3bfed9aa632ebdb859be40d0fd70478ed70d5" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", - "owo-colors 4.2.1", + "owo-colors", "tracing-core", "tracing-error", ] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "2.2.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", ] [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ "crossterm", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] @@ -2792,11 +3210,134 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" +[[package]] +name = "commonware-codec" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f06e32817f35fb517ceb6102d984f9a85fde85666c96f053638e323b8597f2f7" +dependencies = [ + "bytes", + "cfg-if", + "commonware-macros", + "paste", + "rand 0.8.6", + "rand_chacha 0.3.1", + "thiserror 2.0.18", +] + +[[package]] +name = "commonware-cryptography" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f09b55dd5510c3b7613a573606a41961c2788709ffde053d4e644bec0bff2c" +dependencies = [ + "anyhow", + "aws-lc-rs", + "blake3", + "blst", + "bytes", + "cfg-if", + "chacha20poly1305", + "commonware-codec", + "commonware-macros", + "commonware-math", + "commonware-parallel", + "commonware-utils", + "crc-fast", + "ctutils", + "ecdsa", + "ed25519-consensus", + "getrandom 0.2.17", + "num-rational", + "num-traits", + "p256", + "rand 0.8.6", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2 0.10.9", + "thiserror 2.0.18", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "commonware-macros" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd313d9299e13bf995999c7a0ed8cc570eef6cd0972fcffc6e2c682cfba6663" +dependencies = [ + "commonware-macros-impl", + "tokio", +] + +[[package]] +name = "commonware-macros-impl" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc385e646d91b5397c93816985421878d627839834f7cf85a8da2ac9f8b98b7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "toml", +] + +[[package]] +name = "commonware-math" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d834ed8bf601e113b9cd2ba284dd0e95adf558933dc727f52f8879434cb286" +dependencies = [ + "bytes", + "commonware-codec", + "commonware-macros", + "commonware-parallel", + "commonware-utils", + "rand_core 0.6.4", +] + +[[package]] +name = "commonware-parallel" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db29306a40279ad54d06b42c623a05fbb5333546b5003c921796bc856b423106" +dependencies = [ + "cfg-if", + "commonware-macros", + "rayon", +] + +[[package]] +name = "commonware-utils" +version = "2026.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf66d7b5c89489d71b0669bda2e014e7c9ffcdf65629ae31886efe5361b1179" +dependencies = [ + "bytes", + "cfg-if", + "commonware-codec", + "commonware-macros", + "futures", + "getrandom 0.2.17", + "hashbrown 0.16.1", + "num-bigint", + "num-integer", + "num-rational", + "num-traits", + "parking_lot", + "pin-project", + "rand 0.8.6", + "thiserror 2.0.18", + "tokio", + "zeroize", +] + [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -2806,6 +3347,23 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2824,10 +3382,22 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width 0.2.2", + "windows-sys 0.61.2", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -2840,15 +3410,14 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" dependencies = [ "cfg-if", - "cpufeatures", - "hex", + "cpufeatures 0.2.17", "proptest", - "serde", + "serde_core", ] [[package]] @@ -2859,11 +3428,12 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -2879,15 +3449,15 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -2904,9 +3474,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -2918,6 +3488,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -2927,30 +3503,55 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc-fast" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin", +] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -2987,15 +3588,17 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "crossterm_winapi", + "derive_more", + "document-features", "mio", "parking_lot", - "rustix 0.38.44", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -3012,9 +3615,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -3030,35 +3633,22 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] [[package]] -name = "cssparser" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" +name = "crypto-common" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "quote", - "syn 2.0.101", + "hybrid-array", ] [[package]] @@ -3072,85 +3662,153 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.7" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ - "nix 0.30.1", - "windows-sys 0.59.0", + "dispatch2", + "nix 0.31.2", + "windows-sys 0.61.2", ] [[package]] -name = "darling" -version = "0.20.11" +name = "ctutils" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "758e5ed90be3c8abff7f9a6f37ab7f6d8c59c2210d448b81f3f508134aec84e4" dependencies = [ - "darling_core", - "darling_macro", + "cmov", ] [[package]] -name = "darling_core" -version = "0.20.11" +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.101", + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", ] [[package]] -name = "darling_macro" -version = "0.20.11" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "darling_core", + "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] -name = "dashmap" -version = "6.1.0" +name = "curve25519-dalek-ng" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", ] [[package]] -name = "data-encoding" -version = "2.9.0" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] [[package]] -name = "dbus" -version = "0.9.7" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "libc", - "libdbus-sys", - "winapi", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] -name = "deflate64" -version = "0.1.9" +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" @@ -3165,12 +3823,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -3186,24 +3844,24 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -3221,10 +3879,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -3234,65 +3892,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", + "syn 2.0.117", ] [[package]] name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", - "unicode-xid", + "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.101", + "rustc_version 0.4.1", + "syn 2.0.117", "unicode-xid", ] [[package]] name = "dialoguer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" dependencies = [ - "console", + "console 0.16.3", "shell-words", - "tempfile", - "thiserror 1.0.69", "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -3310,10 +3952,20 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -3332,7 +3984,19 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "block2", + "libc", + "objc2", ] [[package]] @@ -3343,14 +4007,23 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "doctest-file" -version = "1.0.0" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" + +[[package]] +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] [[package]] name = "dotenvy" @@ -3365,31 +4038,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] -name = "dtoa" -version = "1.0.10" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "dtoa-short" -version = "0.3.5" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "dunce" -version = "1.0.5" +name = "dynify" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "81acb15628a3e22358bf73de5e7e62360b8a777dbcb5fc9ac7dfa9ae73723747" +dependencies = [ + "dynify-macros", +] [[package]] -name = "dyn-clone" -version = "1.0.19" +name = "dynify-macros" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "ecdsa" @@ -3406,6 +4084,21 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -3415,9 +4108,15 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "either" version = "1.15.0" @@ -3462,26 +4161,17 @@ dependencies = [ [[package]] name = "email-address-parser" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe19a4967eca30062be4abaf813d929ba48b3bfb21830367f7e1baae37f213a" +checksum = "e981c3b50d728bb498dd0f860a7228ef17e19efef5cc2c6e30d78ebce13bcaa7" dependencies = [ "console_error_panic_hook", "pest", "pest_derive", - "quick-xml 0.18.1", + "quick-xml 0.39.2", "wasm-bindgen", ] -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -3505,53 +4195,67 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-ordinalize" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "env_filter", "jiff", "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -3560,22 +4264,23 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.6" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", + "serde_core", "typeid", ] [[package]] name = "errno" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3596,7 +4301,7 @@ dependencies = [ "hex", "hmac", "pbkdf2 0.11.0", - "rand 0.8.5", + "rand 0.8.6", "scrypt", "serde", "serde_json", @@ -3606,6 +4311,46 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -3619,9 +4364,9 @@ dependencies = [ [[package]] name = "evm-disassembler" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded685d9f07315ff689ba56e7d84e6f1e782db19b531a46c34061a733bba7258" +checksum = "ace07bd9e99c61333fa7b95b264f0f814bdfc4ec6714f67ffd8e703b25edb2be" dependencies = [ "eyre", "hex", @@ -3629,13 +4374,13 @@ dependencies = [ [[package]] name = "evmole" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c29ecc930ee2ed03083436c2ddd7e5292c3c3bcda65f6a37369502d578a853f1" +checksum = "61e332670cb19161dee3f15ae86bfb5e4361bb1716d7c1995bd309b5360cb630" dependencies = [ "alloy-dyn-abi", "alloy-primitives", - "indexmap 2.9.0", + "indexmap 2.14.0", ] [[package]] @@ -3648,11 +4393,29 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -3683,7 +4446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] @@ -3708,32 +4471,31 @@ dependencies = [ ] [[package]] -name = "figment" -version = "0.10.19" +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "figment2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +checksum = "87d63dee16df12076c7770919713c0b92f4e1c85eac828dc2ade0b6c998f016b" dependencies = [ "atomic", "parking_lot", - "pear", "serde", "tempfile", - "toml 0.8.22", + "toml_edit 0.25.11+spec-1.1.0", "uncased", "version_check", ] [[package]] -name = "filetime" -version = "0.2.25" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed-hash" @@ -3742,25 +4504,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] [[package]] -name = "fixedbitset" -version = "0.5.7" +name = "fixed-map" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ed19add84e8cb9e8cc5f7074de0324247149ffef0b851e215fb0edc50c229b" +dependencies = [ + "fixed-map-derive", + "serde", +] + +[[package]] +name = "fixed-map-derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float16" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bffafbd079d520191c7c2779ae9cf757601266cf4167d3f659ff09617ff8483" +dependencies = [ + "cfg-if", + "rustc_version 0.2.3", ] [[package]] @@ -3775,19 +4569,30 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-awesome-as-a-crate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "932dcfbd51320af5f27f1ba02d2e567dec332cac7d2c221ba45d8e767264c4dc" + [[package]] name = "forge" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", + "alloy-consensus", "alloy-dyn-abi", "alloy-hardforks", "alloy-json-abi", "alloy-network", "alloy-primitives", "alloy-provider", - "alloy-rpc-types", - "alloy-serde", "alloy-signer", "alloy-signer-local", "alloy-transport", @@ -3796,7 +4601,6 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_fig", "clearscreen", "comfy-table", "dunce", @@ -3816,44 +4620,43 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", "foundry-linking", - "foundry-solang-parser", "foundry-test-utils", "foundry-wallets", - "futures", "globset", "indicatif", "inferno", "itertools 0.14.0", "mockall", - "opener 0.7.2", + "opener", "parking_lot", - "paste", "path-slash", "proptest", "quick-junit", + "rand 0.9.4", "rayon", "regex", - "reqwest", + "reqwest 0.13.3", "revm", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "similar", "similar-asserts", - "solar-interface", - "solar-parse", + "solar-compiler", "soldeer-commands", - "strum 0.27.1", + "soldeer-core", + "strum", "svm-rs", "tempfile", - "thiserror 2.0.12", + "tempo-alloy", + "thiserror 2.0.18", "tokio", - "toml 0.8.22", - "toml_edit", + "toml_edit 0.24.1+spec-1.1.0", "tower-http", "tracing", + "url", "watchexec", "watchexec-events", "watchexec-signals", @@ -3862,71 +4665,68 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-primitives", - "derive_more 2.0.1", + "derive_more", "eyre", - "forge-fmt", "foundry-common", "foundry-compilers", "foundry-config", - "foundry-solang-parser", "itertools 0.14.0", - "mdbook", + "mdbook-driver", "rayon", "regex", "serde", "serde_json", - "thiserror 2.0.12", - "toml 0.8.22", + "solar-compiler", + "thiserror 2.0.18", + "toml", "tracing", ] [[package]] name = "forge-fmt" -version = "1.2.1" +version = "1.7.2" dependencies = [ - "alloy-primitives", - "ariadne", + "foundry-common", "foundry-config", - "foundry-solang-parser", + "foundry-test-utils", "itertools 0.14.0", - "similar-asserts", - "thiserror 2.0.12", - "toml 0.8.22", - "tracing", - "tracing-subscriber 0.3.20", + "similar", + "snapbox", + "solar-compiler", + "toml", ] [[package]] name = "forge-lint" -version = "1.2.1" +version = "1.7.2" dependencies = [ + "eyre", + "foundry-common", "foundry-compilers", "foundry-config", "heck", "rayon", - "solar-ast", - "solar-interface", - "solar-parse", - "thiserror 2.0.12", + "solar-compiler", + "thiserror 2.0.18", ] [[package]] name = "forge-script" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 2.0.4", + "alloy-evm", "alloy-json-abi", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde", "alloy-signer", "clap", "dialoguer", @@ -3934,7 +4734,6 @@ dependencies = [ "eyre", "forge-script-sequence", "forge-verify", - "foundry-block-explorers", "foundry-cheatcodes", "foundry-cli", "foundry-common", @@ -3942,6 +4741,7 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-evm-networks", "foundry-linking", "foundry-wallets", "futures", @@ -3949,10 +4749,13 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "revm-inspectors", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "tempfile", + "tempo-alloy", + "tempo-primitives", + "thiserror 2.0.18", "tokio", "tracing", "yansi", @@ -3960,7 +4763,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-network", "alloy-primitives", @@ -3976,7 +4779,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -3986,15 +4789,16 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "serde_json", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "forge-verify" -version = "1.2.1" +version = "1.7.2" dependencies = [ + "alloy-consensus", "alloy-dyn-abi", + "alloy-evm", "alloy-json-abi", "alloy-primitives", "alloy-provider", @@ -4008,52 +4812,70 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", - "foundry-evm-core", + "foundry-evm-networks", "foundry-test-utils", "futures", "itertools 0.14.0", "regex", - "reqwest", + "reqwest 0.13.3", "revm", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "tempfile", "tokio", "tracing", + "url", "yansi", ] [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] +[[package]] +name = "foundry-bench" +version = "1.7.2" +dependencies = [ + "chrono", + "clap", + "color-eyre", + "eyre", + "foundry-common", + "foundry-compilers", + "foundry-test-utils", + "once_cell", + "rayon", + "serde", + "serde_json", +] + [[package]] name = "foundry-block-explorers" -version = "0.17.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6129df264d2bd245ade4ed21a92f605bfd592dca8fb8e6e1adde1b9bd4288681" +checksum = "786287256b3da26184e25d66685d33fd208d0b6e2098895cf7e880f3ce18eca1" dependencies = [ "alloy-chains", "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest", - "semver 1.0.26", + "reqwest 0.13.3", + "semver 1.0.28", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "foundry-cheatcodes" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4073,6 +4895,7 @@ dependencies = [ "base64 0.22.1", "dialoguer", "ecdsa", + "ed25519-consensus", "eyre", "forge-script-sequence", "foundry-cheatcodes-spec", @@ -4080,6 +4903,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm-core", + "foundry-evm-fuzz", "foundry-evm-traces", "foundry-wallets", "itertools 0.14.0", @@ -4089,192 +4913,227 @@ dependencies = [ "p256", "parking_lot", "proptest", - "rand 0.9.1", - "rand_chacha 0.9.0", + "rand 0.9.4", "revm", "revm-inspectors", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", - "thiserror 2.0.12", - "toml 0.8.22", + "solar-compiler", + "thiserror 2.0.18", + "toml", "tracing", "walkdir", ] [[package]] name = "foundry-cheatcodes-spec" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-sol-types", "foundry-macros", - "schemars", + "schemars 1.2.1", "serde", "serde_json", ] [[package]] name = "foundry-cli" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-ens", "alloy-json-abi", + "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rlp", + "alloy-signer", "cfg-if", "clap", + "clap_complete", + "clap_complete_nushell", "color-eyre", "dotenvy", + "dunce", "eyre", - "forge-fmt", - "foundry-block-explorers", + "foundry-cli-markdown", "foundry-common", "foundry-compilers", "foundry-config", - "foundry-debugger", "foundry-evm", + "foundry-evm-networks", "foundry-wallets", "futures", "indicatif", "itertools 0.14.0", "mimalloc", + "path-slash", "rayon", "regex", "rustls", "serde", "serde_json", + "solar-compiler", "strsim", - "strum 0.27.1", + "strum", "tempfile", + "tempo-primitives", "tikv-jemallocator", "tokio", + "toml", "tracing", - "tracing-subscriber 0.3.20", + "tracing-subscriber 0.3.23", "tracing-tracy", - "tracy-client", "yansi", ] +[[package]] +name = "foundry-cli-markdown" +version = "1.7.2" +dependencies = [ + "clap", + "pretty_assertions", +] + [[package]] name = "foundry-common" -version = "1.2.1" +version = "1.7.2" dependencies = [ + "alloy-chains", "alloy-consensus", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-abi", "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-pubsub", + "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde", + "alloy-rpc-types-engine", + "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "alloy-transport", - "alloy-transport-http", "alloy-transport-ipc", "alloy-transport-ws", - "anstream", + "anstream 0.6.21", "anstyle", "axum", + "base64 0.22.1", "chrono", "ciborium", "clap", "comfy-table", + "dirs", "dunce", "eyre", + "flate2", "foundry-block-explorers", "foundry-common-fmt", "foundry-compilers", "foundry-config", + "foundry-evm-hardforks", + "foundry-wallets", + "futures", "itertools 0.14.0", "jiff", + "k256", + "mpp", "num-format", + "op-alloy-network", + "op-alloy-rpc-types", "path-slash", - "reqwest", - "semver 1.0.26", + "regex", + "reqwest 0.13.3", + "revm", + "rustls", + "semver 1.0.28", "serde", "serde_json", - "solar-parse", - "solar-sema", - "terminal_size", - "thiserror 2.0.12", + "sha2 0.10.9", + "solar-compiler", + "tempfile", + "tempo-alloy", + "tempo-primitives", + "thiserror 2.0.18", "tokio", + "tokio-tungstenite 0.28.0", + "toml", "tower", "tracing", "url", - "vergen", + "vergen-gitcl", "walkdir", "yansi", ] [[package]] name = "foundry-common-fmt" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", "alloy-primitives", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 2.0.4", "chrono", + "comfy-table", + "eyre", "foundry-macros", + "op-alloy-consensus", + "op-alloy-rpc-types", "revm", "serde", "serde_json", "similar-asserts", + "tempo-alloy", "yansi", ] [[package]] name = "foundry-compilers" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43ecf9100de8d31242b7a7cf965196e3d8fb424a931a975a7882897726f4c46" +checksum = "8a46823427db611c6b97506ee8f26b4424058366abcc9a97ac5c4c3419798518" dependencies = [ "alloy-json-abi", "alloy-primitives", "auto_impl", - "derive_more 1.0.0", - "dirs", + "derive_more", "dyn-clone", "foundry-compilers-artifacts", "foundry-compilers-core", "fs_extra", - "futures-util", - "home", "itertools 0.14.0", "path-slash", - "rand 0.8.5", + "rand 0.9.4", "rayon", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", - "sha2 0.10.9", - "solar-parse", - "solar-sema", + "sha2 0.11.0", + "solar-compiler", "svm-rs", "svm-rs-builds", "tempfile", - "thiserror 2.0.12", - "tokio", + "thiserror 2.0.18", "tracing", - "winnow", + "winnow 0.7.15", "yansi", ] [[package]] name = "foundry-compilers-artifacts" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07ebb99f087075b97b68837de385f6cba07ca32314852a6507ce0dac5d66cd8" +checksum = "c29c6a49e82684909549e8e3a63a9dea4c92dfeb1b5b3a5e3ad683db42324ac3" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4282,60 +5141,56 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ddf6d61defb08103e351dc16df637c42d69407bae0f8789f2662a6161e73f5" +checksum = "02d3e554932001133f433473b3bdb37c74b4a648750ab61cd7966e7db0ead16d" dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers-core", - "futures-util", + "memchr", "path-slash", "rayon", - "semver 1.0.26", + "regex", + "semver 1.0.28", "serde", "serde_json", - "serde_repr", - "thiserror 2.0.12", - "tokio", + "thiserror 2.0.18", "tracing", - "walkdir", "yansi", ] [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65a777326b8e0bba45345043ed80ceab5e84f3e474ba2ac19673b31076f3c6b4" +checksum = "583168b6df6b73cc3de8d205a922dca58a9c0b4c2774f6b115b3ff7e79d23900" dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers-artifacts-solc", "foundry-compilers-core", - "path-slash", - "semver 1.0.26", + "semver 1.0.28", "serde", ] [[package]] name = "foundry-compilers-core" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be02912b222eb5bd8a4e8a249a488af3e2fb4fc08e0f17a3bc402406a91295b5" +checksum = "b9f80dd5d9f3ed065e412ecef2da3904ea21708d53f79fac29a3547c10ae947f" dependencies = [ "alloy-primitives", - "cfg-if", "dunce", "fs_extra", "path-slash", "regex", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "svm-rs", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "walkdir", "xxhash-rust", @@ -4343,7 +5198,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-chains", "alloy-primitives", @@ -4351,38 +5206,39 @@ dependencies = [ "dirs", "dunce", "eyre", - "figment", + "figment2", "foundry-block-explorers", "foundry-compilers", + "foundry-evm-hardforks", + "foundry-evm-networks", "glob", "globset", "heck", "itertools 0.14.0", "mesc", - "number_prefix", "path-slash", + "rayon", "regex", - "reqwest", - "revm", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "similar-asserts", - "solar-interface", - "solar-parse", + "snapbox", + "solar-compiler", "soldeer-core", "tempfile", - "thiserror 2.0.12", - "toml 0.8.22", - "toml_edit", + "thiserror 2.0.18", + "toml", + "toml_edit 0.24.1+spec-1.1.0", "tracing", + "unit-prefix", "walkdir", "yansi", ] [[package]] name = "foundry-debugger" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-primitives", "crossterm", @@ -4400,12 +5256,12 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-dyn-abi", - "alloy-evm", "alloy-json-abi", "alloy-primitives", + "alloy-rpc-types", "alloy-sol-types", "eyre", "foundry-cheatcodes", @@ -4415,24 +5271,33 @@ dependencies = [ "foundry-evm-core", "foundry-evm-coverage", "foundry-evm-fuzz", + "foundry-evm-hardforks", + "foundry-evm-networks", + "foundry-evm-sancov", "foundry-evm-traces", "indicatif", "parking_lot", "proptest", + "rayon", "revm", "revm-inspectors", "serde", - "thiserror 2.0.12", + "serde_json", + "tempo-precompiles", + "tempo-primitives", + "thiserror 2.0.18", + "tokio", "tracing", + "uuid 1.23.1", ] [[package]] name = "foundry-evm-abi" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-primitives", "alloy-sol-types", - "derive_more 2.0.1", + "derive_more", "foundry-common-fmt", "foundry-macros", "itertools 0.14.0", @@ -4440,36 +5305,51 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.2.1" +version = "1.7.2" dependencies = [ + "alloy-chains", "alloy-consensus", "alloy-dyn-abi", "alloy-evm", "alloy-genesis", + "alloy-hardforks", "alloy-json-abi", "alloy-network", "alloy-op-evm", "alloy-primitives", "alloy-provider", + "alloy-rlp", "alloy-rpc-types", + "alloy-serde 2.0.4", "alloy-sol-types", + "anvil", "auto_impl", "eyre", "foundry-cheatcodes-spec", "foundry-common", "foundry-config", "foundry-evm-abi", + "foundry-evm-hardforks", + "foundry-evm-networks", "foundry-fork-db", "foundry-test-utils", "futures", "itertools 0.14.0", + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-rpc-types", "op-revm", "parking_lot", "revm", "revm-inspectors", "serde", "serde_json", - "thiserror 2.0.12", + "tempo-alloy", + "tempo-contracts", + "tempo-evm", + "tempo-precompiles", + "tempo-revm", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -4477,7 +5357,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-primitives", "eyre", @@ -4486,13 +5366,14 @@ dependencies = [ "foundry-evm-core", "rayon", "revm", - "semver 1.0.26", + "semver 1.0.28", + "solar-compiler", "tracing", ] [[package]] name = "foundry-evm-fuzz" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4507,21 +5388,58 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "proptest", - "rand 0.9.1", + "rand 0.9.4", "revm", "serde", - "thiserror 2.0.12", + "solar-compiler", + "thiserror 2.0.18", "tracing", ] +[[package]] +name = "foundry-evm-hardforks" +version = "1.7.2" +dependencies = [ + "alloy-chains", + "alloy-hardforks", + "alloy-op-hardforks", + "alloy-rpc-types", + "foundry-compilers", + "op-revm", + "revm", + "serde", + "tempo-chainspec", +] + +[[package]] +name = "foundry-evm-networks" +version = "1.7.2" +dependencies = [ + "alloy-chains", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives", + "clap", + "foundry-evm-hardforks", + "revm", + "serde", + "serde_json", +] + +[[package]] +name = "foundry-evm-sancov" +version = "1.7.2" + [[package]] name = "foundry-evm-traces" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", "alloy-sol-types", + "async-trait", "eyre", "foundry-block-explorers", "foundry-common", @@ -4531,24 +5449,29 @@ dependencies = [ "foundry-linking", "futures", "itertools 0.14.0", + "memchr", "rayon", + "reqwest 0.13.3", "revm", "revm-inspectors", "serde", "serde_json", - "solar-parse", + "solar-compiler", "tempfile", + "tempo-contracts", + "tempo-precompiles", "tokio", "tracing", + "yansi", ] [[package]] name = "foundry-fork-db" -version = "0.15.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b02fb598e4a8ae7b7af7c256081a419b071eacf5e03537806b339b3151409403" +checksum = "f2ce9e91abb900e4ab5346d06b97c3784ae6ed4fc44b9fa22d3dc712b7e21be9" dependencies = [ - "alloy-consensus", + "alloy-chains", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -4558,50 +5481,66 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "url", + "zstd", ] [[package]] name = "foundry-linking" -version = "1.2.1" +version = "1.7.2" dependencies = [ "alloy-primitives", "foundry-compilers", - "semver 1.0.26", - "thiserror 2.0.12", + "rayon", + "semver 1.0.28", + "thiserror 2.0.18", ] [[package]] name = "foundry-macros" -version = "1.2.1" +version = "1.7.2" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] -name = "foundry-solang-parser" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c5fc5210af6b311ab9c6bce41d651d1cd0ab62ea5e0e7fde9635697fb19180" +name = "foundry-primitives" +version = "1.7.2" dependencies = [ - "itertools 0.14.0", - "lalrpop", - "lalrpop-util", - "phf", - "thiserror 2.0.12", - "unicode-xid", + "alloy-consensus", + "alloy-evm", + "alloy-network", + "alloy-op-evm", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types", + "alloy-rpc-types-eth", + "alloy-serde 2.0.4", + "alloy-signer", + "derive_more", + "op-alloy-consensus", + "op-alloy-rpc-types", + "op-revm", + "revm", + "serde", + "serde_json", + "tempo-alloy", + "tempo-primitives", + "tempo-revm", ] [[package]] name = "foundry-test-utils" -version = "1.2.1" +version = "1.7.2" dependencies = [ + "alloy-chains", "alloy-primitives", "alloy-provider", "eyre", @@ -4612,63 +5551,68 @@ dependencies = [ "foundry-config", "idna_adapter", "parking_lot", - "rand 0.9.1", + "rand 0.9.4", "regex", + "reqwest 0.13.3", "serde_json", "snapbox", + "svm-rs", "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.20", + "tracing-subscriber 0.3.23", "ui_test", - "zip-extract", ] [[package]] name = "foundry-wallets" -version = "1.2.1" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f2cc1ff7ea002addf41bd9c5785cd34f6a7b9658998c50035cc860ffebc3a9" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", "alloy-primitives", + "alloy-rlp", "alloy-signer", "alloy-signer-aws", "alloy-signer-gcp", "alloy-signer-ledger", "alloy-signer-local", "alloy-signer-trezor", + "alloy-signer-turnkey", "alloy-sol-types", "async-trait", "aws-config", - "aws-sdk-kms", + "axum", "clap", "derive_builder", + "dirs", "eth-keystore", "eyre", - "foundry-config", - "gcloud-sdk", "rpassword", + "rusqlite", "serde", - "thiserror 2.0.12", + "serde_json", + "tempo-primitives", + "thiserror 2.0.18", "tokio", + "toml", + "tower", + "tower-http", "tracing", + "uuid 1.23.1", + "webbrowser", ] [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" - -[[package]] -name = "fs4" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" dependencies = [ - "rustix 1.0.7", - "windows-sys 0.59.0", + "futures-core", ] [[package]] @@ -4704,9 +5648,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -4719,25 +5663,38 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -4746,38 +5703,51 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -4787,7 +5757,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -4799,9 +5768,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gcloud-sdk" -version = "0.27.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44163d7c0d0bc470caa43766beda779e584034abd6b681319d8f9be2fc194eba" +checksum = "6b5b58d8683fa308be9bc58caece4972315a0b2547f9da16962511f0915d5b53" dependencies = [ "async-trait", "bytes", @@ -4810,33 +5779,34 @@ dependencies = [ "hyper", "jsonwebtoken", "once_cell", - "prost", - "prost-types", - "reqwest", + "prost 0.14.3", + "prost-types 0.14.3", + "reqwest 0.13.3", "secret-vault-value", "serde", "serde_json", "tokio", "tonic", + "tonic-prost", "tower", "tower-layer", - "tower-util", "tracing", "url", ] [[package]] name = "generator" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" dependencies = [ "cc", "cfg-if", "libc", "log", "rustversion", - "windows", + "windows-link", + "windows-result", ] [[package]] @@ -4852,48 +5822,73 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", @@ -4915,17 +5910,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.9.0", + "http 1.4.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -4934,19 +5929,20 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -4955,7 +5951,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -4969,55 +5965,68 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", "allocator-api2", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] -name = "heck" -version = "0.5.0" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "hashlink" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -5045,11 +6054,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5063,14 +6072,12 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.31.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953cbbe631aae7fc0a112702ad5d3aaf09da38beaf45ea84610d6e1c358f569c" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" dependencies = [ "log", - "mac", "markup5ever", - "match_token", ] [[package]] @@ -5086,12 +6093,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -5113,7 +6119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -5124,7 +6130,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "pin-project-lite", ] @@ -5147,17 +6153,27 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "httparse", "httpdate", @@ -5170,20 +6186,19 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.3.1", + "http 1.4.0", "hyper", "hyper-util", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.0", + "webpki-roots 1.0.7", ] [[package]] @@ -5201,29 +6216,34 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -5243,6 +6263,101 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b24a59706036ba941c9476a55cd57b82b77f38a3c667d637ee7cabbc85eaedc" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a97b8ac6235e69506e8dacfb2adf38461d2ce6d3e9bd9c94c4cbc3cd4400a4" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -5251,9 +6366,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -5282,9 +6397,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ "crossbeam-deque", "globset", @@ -5313,14 +6428,14 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "index_vec" @@ -5341,64 +6456,62 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.17.0", "serde", + "serde_core", ] [[package]] name = "indicatif" -version = "0.17.11" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ - "console", - "number_prefix", + "console 0.16.3", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.2", + "unit-prefix", "web-time", ] [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inferno" -version = "0.12.2" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2094aecddc672e902cd773bad7071542f63641e01e9187c3bba4b43005e837e9" +checksum = "90807d610575744524d9bdc69f3885d96f0e6c3354565b0828354a7ff2a262b8" dependencies = [ "ahash", "itoa", "log", "num-format", "once_cell", - "quick-xml 0.37.5", + "quick-xml 0.39.2", "rgb", "str_stack", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -5423,22 +6536,22 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.7" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling", + "darling 0.23.0", "indoc", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "interprocess" -version = "2.2.3" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -5446,31 +6559,63 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset 0.9.1", +] + +[[package]] +name = "inturn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2efbe120e37f17bb33fcdc82bc1c65087242608be37ace3cf7ebf49f3164e37" +dependencies = [ + "boxcar", + "bumpalo", + "dashmap", + "hashbrown 0.14.5", + "thread_local", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi 0.5.1", + "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -5510,67 +6655,101 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", - "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", - "serde", - "windows-sys 0.59.0", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] -name = "jiff-tzdb" -version = "0.1.4" +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] [[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ - "jiff-tzdb", + "quote", + "syn 2.0.117", ] [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -5587,23 +6766,37 @@ dependencies = [ ] [[package]] -name = "jsonwebtoken" -version = "9.3.1" +name = "jsonrpsee-types" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ - "base64 0.22.1", - "js-sys", - "pem", - "ring", + "http 1.4.0", "serde", "serde_json", - "simple_asn1", + "thiserror 2.0.18", ] [[package]] -name = "k256" -version = "0.13.4" +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", +] + +[[package]] +name = "k256" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ @@ -5617,83 +6810,68 @@ dependencies = [ ] [[package]] -name = "keccak" -version = "0.1.5" +name = "kasuari" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ - "cpufeatures", + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", ] [[package]] -name = "keccak-asm" -version = "0.1.4" +name = "keccak" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "digest 0.10.7", - "sha3-asm", + "cpufeatures 0.2.17", ] [[package]] -name = "kqueue" -version = "1.1.1" +name = "keccak-asm" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" dependencies = [ - "kqueue-sys", - "libc", + "digest 0.10.7", + "sha3-asm", ] [[package]] -name = "kqueue-sys" -version = "1.0.4" +name = "konst" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" dependencies = [ - "bitflags 1.3.2", - "libc", + "konst_macro_rules", ] [[package]] -name = "lalrpop" -version = "0.22.2" +name = "konst_macro_rules" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.14.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "sha3", - "string_cache", - "term", - "unicode-xid", - "walkdir", -] +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" [[package]] -name = "lalrpop-util" -version = "0.22.2" +name = "kqueue" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ - "regex-automata", - "rustversion", + "kqueue-sys", + "libc", ] [[package]] -name = "lasso" -version = "0.7.3" +name = "kqueue-sys" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" dependencies = [ - "dashmap", - "hashbrown 0.14.5", + "bitflags 2.11.1", + "libc", ] [[package]] @@ -5703,10 +6881,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "lazycell" -version = "1.3.0" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "levenshtein" @@ -5716,101 +6894,43 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libdbus-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "libloading" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" -dependencies = [ - "cfg-if", - "windows-targets 0.53.0", -] +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" dependencies = [ "cc", - "libc", ] [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.9.1", "libc", - "redox_syscall", -] - -[[package]] -name = "libsecp256k1" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" -dependencies = [ - "arrayref", - "base64 0.22.1", - "digest 0.9.0", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.8.5", - "serde", - "sha2 0.9.9", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", ] [[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" +name = "libsqlite3-sys" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" dependencies = [ - "libsecp256k1-core", + "cc", + "pkg-config", + "vcpkg", ] [[package]] @@ -5826,32 +6946,46 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "line-clipping" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.11.1", +] [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ "value-bag", ] @@ -5866,25 +7000,16 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber 0.3.20", -] - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.3", + "tracing-subscriber 0.3.23", ] [[package]] name = "lru" -version = "0.13.0" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.16.1", ] [[package]] @@ -5893,27 +7018,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder", - "crc", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "mac" version = "0.1.1" @@ -5928,43 +7032,20 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "markup5ever" -version = "0.16.1" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a8096766c229e8c88a3900c9b44b7e06aa7f7343cc229158c3e58ef8f9973a" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" dependencies = [ "log", "tendril", "web_atoms", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "matchers" version = "0.2.0" @@ -5981,95 +7062,197 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "mdbook" -version = "0.4.50" +name = "mdbook-core" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72bc08f096e1fb15cfc382babe218317c2897d2040f967c4db40d156ca28e21" +checksum = "39a3873d4afac65583f1acb56ff058df989d5b4a2464bb02c785549727d307ee" dependencies = [ - "ammonia", "anyhow", - "chrono", - "clap", - "clap_complete", - "elasticlunr-rs", - "env_logger", - "handlebars", - "hex", - "log", - "memchr", - "opener 0.8.1", - "pulldown-cmark", "regex", "serde", "serde_json", - "sha2 0.10.9", + "toml", + "tracing", +] + +[[package]] +name = "mdbook-driver" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a229930b29a9908560883e1f386eae25d8a971d259a80f49916a50627f04a42d" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "mdbook-core", + "mdbook-html", + "mdbook-markdown", + "mdbook-preprocessor", + "mdbook-renderer", + "mdbook-summary", + "regex", + "serde", + "serde_json", "shlex", "tempfile", - "toml 0.5.11", + "toml", "topological-sort", + "tracing", ] [[package]] -name = "memchr" -version = "2.7.4" +name = "mdbook-html" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "9dee80c03c65e3212fb528b8c9be5568a6a85cf795d03cf9fd6ba39ad52069ca" +dependencies = [ + "anyhow", + "ego-tree", + "elasticlunr-rs", + "font-awesome-as-a-crate", + "handlebars", + "hex", + "html5ever", + "indexmap 2.14.0", + "mdbook-core", + "mdbook-markdown", + "mdbook-renderer", + "pulldown-cmark", + "regex", + "serde", + "serde_json", + "sha2 0.10.9", + "tracing", +] [[package]] -name = "memoffset" -version = "0.7.1" +name = "mdbook-markdown" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "07c41bf35212f5d8b83e543aa6a4887dc5709c8489c5fb9ed00f1b51ce1a2cc6" dependencies = [ - "autocfg", + "pulldown-cmark", + "regex", + "tracing", ] [[package]] -name = "mesc" -version = "0.3.0" +name = "mdbook-preprocessor" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04b0347d2799ef17df4623dbcb03531031142105168e0c549e0bf1f980e9e7e" +checksum = "4d87bf40be0597f26f0822f939a64f02bf92c4655ba04490aadbf83601a013bb" dependencies = [ + "anyhow", + "mdbook-core", "serde", "serde_json", - "thiserror 1.0.69", ] [[package]] -name = "miette" -version = "7.6.0" +name = "mdbook-renderer" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +checksum = "93ed59f225b3ae4283c56bea633db83184627a090d892908bd66990c68e10b43" dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width 0.1.14", + "anyhow", + "mdbook-core", + "serde", + "serde_json", ] [[package]] -name = "miette-derive" -version = "7.6.0" +name = "mdbook-summary" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +checksum = "c00d85b291d67a69c92e939450390fe34d6ea418a868c8f7b42f0b300af35a7b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "anyhow", + "mdbook-core", + "memchr", + "pulldown-cmark", + "serde", + "tracing", ] [[package]] -name = "mimalloc" -version = "0.1.46" +name = "memchr" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" -dependencies = [ - "libmimalloc-sys", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mesc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04b0347d2799ef17df4623dbcb03531031142105168e0c549e0bf1f980e9e7e" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "metrics" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" +dependencies = [ + "portable-atomic", + "rapidhash", +] + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mimalloc" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] @@ -6090,30 +7273,31 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "mockall" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" dependencies = [ "cfg-if", "downcast", @@ -6125,16 +7309,71 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "modular-bitfield" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mpp" +version = "0.10.0" +source = "git+https://github.com/tempoxyz/mpp-rs?rev=554d20112eb014bd223d54de7f152ca59b2aa4fd#554d20112eb014bd223d54de7f152ca59b2aa4fd" +dependencies = [ + "alloy", + "async-stream", + "base64 0.22.1", + "futures-core", + "futures-util", + "hex", + "hmac", + "http 1.4.0", + "rand 0.9.4", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_json_canonicalizer", + "sha2 0.10.9", + "tempo-alloy", + "tempo-primitives", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-tungstenite 0.26.2", + "uuid 1.23.1", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -6143,11 +7382,11 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "newtype-uuid" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ba303c7a8f8fdee1fe1513cfd918f50f1c69bf65c91b39217bfc2b2af5c081" +checksum = "5c012d14ef788ab066a347d19e3dda699916c92293b05b85ba2c76b8c82d2830" dependencies = [ - "uuid 1.17.0", + "uuid 1.23.1", ] [[package]] @@ -6168,17 +7407,17 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -6186,11 +7425,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -6220,21 +7459,20 @@ checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" [[package]] name = "normpath" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "notify" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.1", - "filetime", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -6243,22 +7481,25 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.1", +] [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6283,6 +7524,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -6296,9 +7538,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-format" @@ -6368,33 +7610,34 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -6406,109 +7649,194 @@ dependencies = [ "libc", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "nybbles" -version = "0.3.4" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" dependencies = [ "alloy-rlp", - "const-hex", + "cfg-if", "proptest", + "ruint", "serde", "smallvec", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "objc2", +] + [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "once_map" -version = "0.4.21" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd2cae3bec3936bbed1ccc5a3343b3738858182419f9c0522c7260c80c430b0" +checksum = "b685c8311c9171d1bd2895222965d25616b2de2cb5819dd3504ed9250df9fecd" dependencies = [ "ahash", - "hashbrown 0.15.3", + "hashbrown 0.17.0", "parking_lot", "stable_deref_trait", ] +[[package]] +name = "op-alloy" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" +dependencies = [ + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-provider", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", +] + [[package]] name = "op-alloy-consensus" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb35d16e5420e43e400a235783e3d18b6ba564917139b668b48e9ac42cb3d35a" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", - "derive_more 2.0.1", + "alloy-serde 2.0.4", + "bytes", + "derive_more", + "reth-codecs", + "reth-zstd-compressors", "serde", - "thiserror 2.0.12", + "serde_with", + "thiserror 2.0.18", ] [[package]] name = "op-alloy-flz" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef71f23a8caf6f2a2d5cafbdc44956d44e6014dcb9aa58abf7e4e6481c6ec34" +checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" + +[[package]] +name = "op-alloy-network" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-provider", + "alloy-rpc-types-eth", + "op-alloy-consensus", + "op-alloy-rpc-types", +] + +[[package]] +name = "op-alloy-provider" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "async-trait", + "op-alloy-rpc-types-engine", +] [[package]] name = "op-alloy-rpc-types" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7534a0ec6b8409edc511acbe77abe7805aa63129b98e9a915bb4eb8555eaa6ff" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", + "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", - "derive_more 2.0.1", + "alloy-serde 2.0.4", + "derive_more", "op-alloy-consensus", + "reth-rpc-traits", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-rpc-types-engine" +version = "0.24.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-serde 2.0.4", + "ethereum_ssz", + "ethereum_ssz_derive", + "op-alloy-consensus", + "serde", + "sha2 0.10.9", + "snap", + "thiserror 2.0.18", ] [[package]] name = "op-revm" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47296d449fbe2d5cc74ab6e1213dee88cae3e2fd238343bec605c3c687bbcfab" +version = "19.0.0" +source = "git+https://github.com/ethereum-optimism/optimism?rev=e3b59e76588f99db17205f5601e45a5b00f0bfbb#e3b59e76588f99db17205f5601e45a5b00f0bfbb" dependencies = [ "auto_impl", - "once_cell", "revm", "serde", ] @@ -6521,32 +7849,20 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "opener" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0812e5e4df08da354c851a3376fead46db31c2214f849d3de356d774d057681" -dependencies = [ - "bstr", - "dbus", - "normpath", - "windows-sys 0.59.0", -] - -[[package]] -name = "opener" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de96cad6ee771be7f68df884d3767460b4684012308d8342ed5623fe62b2628c" +checksum = "a2fa337e0cf13357c13ef1dc108df1333eb192f75fc170bea03fcf1fd404c2ee" dependencies = [ "bstr", "normpath", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "option-ext" @@ -6562,15 +7878,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "owo-colors" -version = "4.2.1" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "p256" @@ -6581,18 +7891,10 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", + "serdect", "sha2 0.10.9", ] -[[package]] -name = "pad" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -6602,6 +7904,7 @@ dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", + "bytes", "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", @@ -6618,7 +7921,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -6629,9 +7932,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -6639,15 +7942,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -6682,69 +7985,45 @@ dependencies = [ ] [[package]] -name = "pear" -version = "0.2.9" +name = "pem" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", + "base64 0.22.1", + "serde_core", ] [[package]] -name = "pear_codegen" -version = "0.2.9" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.101", + "base64ct", ] [[package]] -name = "pem" -version = "3.0.5" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" -dependencies = [ - "base64 0.22.1", - "serde", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", - "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -6752,38 +8031,27 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ - "once_cell", "pest", "sha2 0.10.9", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap 2.9.0", -] - [[package]] name = "pharos" version = "0.5.3" @@ -6799,9 +8067,18 @@ name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.13.1", "serde", ] @@ -6811,8 +8088,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -6821,21 +8108,31 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", - "rand 0.8.5", + "phf_shared 0.11.3", + "rand 0.8.6", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -6848,50 +8145,39 @@ dependencies = [ ] [[package]] -name = "pin-project" -version = "0.4.30" +name = "phf_shared" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "pin-project-internal 0.4.30", + "siphasher", ] [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ - "pin-project-internal 1.1.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -6911,25 +8197,45 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "poly1305" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -6953,9 +8259,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "predicates-core", @@ -6963,38 +8269,47 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettydiff" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abec3fb083c10660b3854367697da94c674e9e82aa7511014dc958beeb7215e9" +checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" dependencies = [ - "owo-colors 3.5.0", - "pad", + "owo-colors", ] [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -7004,6 +8319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", + "serdect", ] [[package]] @@ -7019,11 +8335,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -7045,40 +8361,27 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", - "version_check", - "yansi", -] - [[package]] name = "process-wrap" -version = "8.2.1" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" +checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.9.0", - "nix 0.30.1", + "indexmap 2.14.0", + "nix 0.31.2", "tokio", "tracing", "windows", @@ -7086,17 +8389,16 @@ dependencies = [ [[package]] name = "proptest" -version = "1.6.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.1", - "lazy_static", + "bitflags 2.11.1", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.4", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -7106,52 +8408,84 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", ] [[package]] name = "prost" -version = "0.13.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.3", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", ] [[package]] name = "prost-types" -version = "0.13.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ - "prost", + "prost 0.14.3", ] [[package]] name = "protobuf" -version = "3.3.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" dependencies = [ "once_cell", "protobuf-support", @@ -7160,20 +8494,20 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.3.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" dependencies = [ "thiserror 1.0.69", ] [[package]] name = "pulldown-cmark" -version = "0.10.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "memchr", "pulldown-cmark-escape", "unicase", @@ -7181,9 +8515,24 @@ dependencies = [ [[package]] name = "pulldown-cmark-escape" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] [[package]] name = "quick-error" @@ -7193,52 +8542,52 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-junit" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" +checksum = "6ee9342d671fae8d66b3ae9fd7a9714dfd089c04d2a8b1ec0436ef77aee15e5f" dependencies = [ "chrono", - "indexmap 2.9.0", + "indexmap 2.14.0", "newtype-uuid", - "quick-xml 0.37.5", + "quick-xml 0.38.4", "strip-ansi-escapes", - "thiserror 2.0.12", - "uuid 1.17.0", + "thiserror 2.0.18", + "uuid 1.23.1", ] [[package]] name = "quick-xml" -version = "0.18.1" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -7246,20 +8595,21 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", - "rand 0.9.1", + "rand 0.9.4", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -7267,32 +8617,38 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "radium" @@ -7312,9 +8668,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -7324,12 +8680,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", ] @@ -7350,7 +8706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -7359,54 +8715,115 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "serde", ] [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rand 0.9.4", + "rustversion", ] [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.9.1", - "cassowary", + "bitflags 2.11.1", "compact_str", + "hashbrown 0.16.1", + "indoc", + "itertools 0.14.0", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.2", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.16.1", "indoc", "instability", - "itertools 0.13.0", - "lru 0.12.5", - "paste", - "strum 0.26.3", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width 0.2.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.1", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -7414,9 +8831,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -7430,29 +8847,49 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -7461,86 +8898,568 @@ dependencies = [ ] [[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "regress" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" +dependencies = [ + "hashbrown 0.16.1", + "memchr", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", + "webpki-roots 1.0.7", +] + +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + +[[package]] +name = "reth-chainspec" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "auto_impl", + "derive_more", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "bytes", + "modular-bitfield", + "parity-scale-codec", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-consensus" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-db-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "arrayvec", + "bytes", + "derive_more", + "metrics", + "modular-bitfield", + "reth-codecs", + "reth-db-models", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "roaring", + "serde", +] + +[[package]] +name = "reth-db-models" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-eips 2.0.4", + "alloy-primitives", + "bytes", + "modular-bitfield", + "reth-codecs", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-ethereum-consensus" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-primitives-traits", + "tracing", +] + +[[package]] +name = "reth-ethereum-forks" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-primitives", + "alloy-rpc-types-eth", + "reth-codecs", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-evm" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "derive_more", + "futures-util", + "rayon", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-evm-ethereum" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-primitives", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "derive_more", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", + "serde", + "serde_with", +] + +[[package]] +name = "reth-network-peers" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "secp256k1 0.30.0", + "serde_with", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "reth-primitives-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie", + "byteorder", + "bytes", + "dashmap", + "derive_more", + "modular-bitfield", + "once_cell", + "quanta", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1 0.30.0", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-prune-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-primitives", + "derive_more", + "modular-bitfield", + "reth-codecs", + "serde", + "strum", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-revm" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-rpc-convert" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "auto_impl", + "dyn-clone", + "jsonrpsee-types", + "reth-evm", + "reth-primitives-traits", + "reth-rpc-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-rpc-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-stages-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-primitives", + "bytes", + "modular-bitfield", + "reth-codecs", + "reth-trie-common", + "serde", +] + +[[package]] +name = "reth-static-file-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-primitives", + "derive_more", + "fixed-map", + "reth-stages-types", + "serde", + "strum", + "tracing", +] + +[[package]] +name = "reth-storage-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", + "serde_json", +] + +[[package]] +name = "reth-storage-errors" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "alloy-eips 2.0.4", + "alloy-primitives", + "alloy-rlp", + "derive_more", + "reth-codecs", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "revm-state", + "thiserror 2.0.18", ] [[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +name = "reth-trie-common" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=38c627c#38c627ce8f1a3bb82bed8a6beb3016f62c50016d" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde 2.0.4", + "alloy-trie", + "arrayvec", + "bytes", + "derive_more", + "itertools 0.14.0", + "nybbles", + "reth-codecs", + "reth-primitives-traits", + "revm-database", + "serde", + "serde_with", +] [[package]] -name = "reqwest" -version = "0.12.15" +name = "reth-zstd-compressors" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ - "async-compression", - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots 0.26.11", - "windows-registry", + "zstd", ] [[package]] name = "revm" -version = "24.0.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3ae9d1b08303eb5150dcf820a29e14235cf3f24f6c09024458a4dcbffe6695" +checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" dependencies = [ "revm-bytecode", "revm-context", @@ -7557,23 +9476,23 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "4.0.1" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91f9b90b3bab18942252de2d970ee8559794c49ca7452b2cc1774456040f8fb" +checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" dependencies = [ "bitvec", - "once_cell", - "phf", + "phf 0.13.1", "revm-primitives", "serde", ] [[package]] name = "revm-context" -version = "5.0.0" +version = "16.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b181214eb2bbb76ee9d6195acba19857d991d2cdb9a65b7cb6939c30250a3966" +checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" dependencies = [ + "bitvec", "cfg-if", "derive-where", "revm-bytecode", @@ -7586,9 +9505,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "5.0.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b844f48a411e62c7dde0f757bf5cce49c85b86d6fc1d3b2722c07f2bec4c3ce" +checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -7602,11 +9521,11 @@ dependencies = [ [[package]] name = "revm-database" -version = "4.0.1" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3fbe34f6bb00a9c3155723b3718b9cb9f17066ba38f9eb101b678cd3626775" +checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" dependencies = [ - "alloy-eips", + "alloy-eips 1.8.3", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -7616,23 +9535,26 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "4.0.1" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8acd36784a6d95d5b9e1b7be3ce014f1e759abb59df1fa08396b30f71adc2a" +checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" dependencies = [ "auto_impl", + "either", "revm-primitives", "revm-state", "serde", + "thiserror 2.0.18", ] [[package]] name = "revm-handler" -version = "5.0.0" +version = "18.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9204e3ac1a8edb850cc441a6a1d0f2251c0089e5fffdaba11566429e6c64e" +checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" dependencies = [ "auto_impl", + "derive-where", "revm-bytecode", "revm-context", "revm-context-interface", @@ -7646,11 +9568,12 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "5.0.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae4881eeae6ff35417c8569bc7cc03b6c0969869ee2c9b3945a39b4f9fa58bc5" +checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" dependencies = [ "auto_impl", + "either", "revm-context", "revm-database-interface", "revm-handler", @@ -7663,82 +9586,99 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.23.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b50ef375dbacefecfdacf8f02afc31df98acc5d8859a6f2b24d121ff2a740a8" +checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-sol-types", "anstyle", + "boa_engine", + "boa_gc", "colorchoice", "revm", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "revm-interpreter" -version = "20.0.0" +version = "35.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5ee65e57375c6639b0f50555e92a4f1b2434349dd32f52e2176f5c711171697" +checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" dependencies = [ "revm-bytecode", "revm-context-interface", "revm-primitives", + "revm-state", "serde", ] [[package]] name = "revm-precompile" -version = "21.0.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9311e735123d8d53a02af2aa81877bba185be7c141be7f931bb3d2f3af449c" +checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" dependencies = [ "ark-bls12-381", "ark-bn254", "ark-ec", "ark-ff 0.5.0", "ark-serialize 0.5.0", + "arrayref", "aurora-engine-modexp", "blst", "c-kzg", "cfg-if", "k256", - "libsecp256k1", - "once_cell", "p256", + "revm-context-interface", "revm-primitives", "ripemd", - "secp256k1", + "secp256k1 0.31.1", "sha2 0.10.9", ] [[package]] name = "revm-primitives" -version = "19.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ea2ea0134568ee1e14281ce52f60e2710d42be316888d464c53e37ff184fd8" +checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" dependencies = [ "alloy-primitives", "num_enum", + "once_cell", "serde", ] [[package]] name = "revm-state" -version = "4.0.1" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" +checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" dependencies = [ - "bitflags 2.9.1", + "alloy-eip7928", + "bitflags 2.11.1", "revm-bytecode", "revm-primitives", "serde", ] +[[package]] +name = "rexpect" +version = "0.6.2" +source = "git+https://github.com/rust-cli/rexpect?rev=2ed0b1898d7edaf6a85bedbae71a01cc578958fc#2ed0b1898d7edaf6a85bedbae71a01cc578958fc" +dependencies = [ + "comma", + "nix 0.30.1", + "regex", + "tempfile", + "thiserror 2.0.18", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -7751,9 +9691,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ "bytemuck", ] @@ -7766,9 +9706,9 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -7791,37 +9731,48 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "roaring" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" +dependencies = [ + "bytemuck", + "byteorder", +] + [[package]] name = "rpassword" -version = "7.4.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "5ac5b223d9738ef56e0b98305410be40fa0941bf6036c56f1506751e43552d64" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" -version = "0.0.3" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "ruint" -version = "1.15.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -7831,11 +9782,11 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.1", + "rand 0.8.6", + "rand 0.9.4", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -7857,24 +9808,32 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rusqlite" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rustc-demangle" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" dependencies = [ - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -7883,6 +9842,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -7898,7 +9866,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.28", ] [[package]] @@ -7915,35 +9883,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -7957,9 +9912,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -7968,47 +9923,65 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "rustls-pki-types", + "web-time", + "zeroize", ] [[package]] -name = "rustls-pki-types" -version = "1.12.0" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "web-time", - "zeroize", + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", @@ -8018,11 +9991,11 @@ dependencies = [ [[package]] name = "rustyline" -version = "15.0.0" +version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" +checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cfg-if", "clipboard-win", "fd-lock", @@ -8030,19 +10003,25 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.29.0", + "nix 0.30.1", "radix_trie", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "utf8parse", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "ryu-js" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" [[package]] name = "salsa20" @@ -8073,20 +10052,33 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] name = "schemars" -version = "0.8.22" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -8094,14 +10086,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -8150,11 +10142,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", - "secp256k1-sys", + "rand 0.8.6", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -8164,14 +10167,21 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "secret-vault-value" -version = "0.3.9" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc32a777b53b3433b974c9c26b6d502a50037f8da94e46cb8ce2ced2cfdfaea0" +checksum = "471de2a3d4b361569b862e04491696237381641ae5808f8e69ea08de991cd306" dependencies = [ - "prost", - "prost-types", "serde", "serde_json", "zeroize", @@ -8179,12 +10189,12 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.0", + "bitflags 2.11.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -8192,32 +10202,48 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser", + "semver-parser 0.10.3", ] [[package]] name = "semver" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", + "serde_core", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "semver-parser" version = "0.10.3" @@ -8235,22 +10261,32 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -8261,59 +10297,61 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "serde_fmt" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.14.0", "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] -name = "serde_path_to_error" -version = "0.1.17" +name = "serde_json_canonicalizer" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "fe52319a927259afbfa5180c5157cd8167edfd3e8c254f9558c7fef44c5649f2" dependencies = [ - "itoa", + "ryu-js", "serde", + "serde_json", ] [[package]] -name = "serde_repr" +name = "serde_path_to_error" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "itoa", + "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -8330,17 +10368,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", - "serde", - "serde_derive", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -8348,14 +10387,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -8375,7 +10414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -8387,27 +10426,38 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest 0.10.7", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -8415,9 +10465,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -8434,9 +10484,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -8456,9 +10506,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -8467,10 +10517,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -8486,9 +10537,25 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -8506,42 +10573,48 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" dependencies = [ - "console", + "console 0.15.11", "similar", ] [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", ] [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "small_btree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba60d2df92ba73864714808ca68c059734853e6ab722b40e1cf543ebb3a057a" dependencies = [ - "autocfg", + "arrayvec", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -8552,13 +10625,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "snapbox" -version = "0.6.21" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" +checksum = "6c1abc378119f77310836665f8523018532cf7e3faeb3b10b01da5a7321bf8e1" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "anstyle-svg", "normalize-line-endings", @@ -8571,121 +10650,129 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.10" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" +checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74" dependencies = [ - "anstream", + "anstream 0.6.21", ] [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "solar-ast" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7960f42b73c63b78df9e223ea88ca87b7aaef91e3b5084b89d79ec31ad9fc8e3" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-primitives", "bumpalo", "either", - "num-bigint", "num-rational", - "semver 1.0.26", + "semver 1.0.28", + "solar-data-structures", + "solar-interface", + "solar-macros", + "strum", +] + +[[package]] +name = "solar-compiler" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" +dependencies = [ + "alloy-primitives", + "solar-ast", + "solar-config", "solar-data-structures", "solar-interface", "solar-macros", - "strum 0.27.1", - "typed-arena", + "solar-parse", + "solar-sema", ] [[package]] name = "solar-config" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ff62c74517305ddee6fdfa741bac8195826d2820586659341063c991fa4a9e" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "strum 0.27.1", + "colorchoice", + "strum", ] [[package]] name = "solar-data-structures" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c5934e613b60cfb0b2eadc035bac1fb57641026e4c2fb3683e9580391bd109" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.9.0", + "indexmap 2.14.0", "parking_lot", "rayon", - "rustc-hash 2.1.1", + "rustc-hash", "smallvec", ] [[package]] name = "solar-interface" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a614174b9fa673b0a01cc8308d18578f32dd091359786368964bbd19e94e59f7" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ - "annotate-snippets", - "anstream", + "annotate-snippets 0.12.15", + "anstream 0.6.21", "anstyle", - "const-hex", - "derive_builder", - "derive_more 2.0.1", + "derive_more", "dunce", + "inturn", "itertools 0.14.0", "itoa", - "lasso", - "match_cfg", "normalize-path", + "once_map", "rayon", "scoped-tls", + "semver 1.0.28", "serde", "serde_json", "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "solar-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccbdb5d9ecd4127af47279f99475f9fbff6ad3d03afc98ba78e1998bc07c0d46" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "solar-parse" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7965e27a45ca85135ffd45839f9623131175fb43d2ad3526d32de0946f250f1b" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-primitives", - "bitflags 2.9.1", + "bitflags 2.11.1", "bumpalo", "itertools 0.14.0", "memchr", "num-bigint", "num-rational", "num-traits", + "ruint", "smallvec", "solar-ast", "solar-data-structures", @@ -8695,15 +10782,14 @@ dependencies = [ [[package]] name = "solar-sema" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e695824aaf984aa01493ebd5e7e83ddf84629d4a2667a0adf25cb6b65fa63e" +version = "0.1.8" +source = "git+https://github.com/paradigmxyz/solar?rev=530f129#530f129b1b2d7138df973dd71d2fc1e592b593d7" dependencies = [ "alloy-json-abi", "alloy-primitives", - "bitflags 2.9.1", + "bitflags 2.11.1", "bumpalo", - "derive_more 2.0.1", + "derive_more", "either", "once_map", "paste", @@ -8715,23 +10801,22 @@ dependencies = [ "solar-interface", "solar-macros", "solar-parse", - "strum 0.27.1", + "strum", "thread_local", "tracing", - "typed-arena", ] [[package]] name = "soldeer-commands" -version = "0.5.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5234cb6edcb3ac81b73c4554391e47c78d5493ac17c59115d2e72b87746816" +checksum = "dfd18a52d398ab7defa85ea5a3f94ca335e8ae0b40c76e8f7d5cc1ea0924e356" dependencies = [ "bon", "clap", "clap-verbosity-flag", "cliclack", - "derive_more 2.0.1", + "derive_more", "email-address-parser", "env_logger", "path-slash", @@ -8742,14 +10827,14 @@ dependencies = [ [[package]] name = "soldeer-core" -version = "0.5.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a67f45108aec81281be2c23000bde114d568ac3899026779f21656bbc3d85671" +checksum = "465e595b179b442eec8f6cd8afde9e3d429c7de9bb779a228496921e3f195a81" dependencies = [ "bon", "chrono", "const-hex", - "derive_more 2.0.1", + "derive_more", "dunce", "home", "ignore", @@ -8757,30 +10842,37 @@ dependencies = [ "path-slash", "rayon", "regex", - "reqwest", + "reqwest 0.12.28", "sanitize-filename", - "semver 1.0.26", + "semver 1.0.28", "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", - "toml_edit", - "uuid 1.17.0", + "toml_edit 0.23.10+spec-1.0.0", + "uuid 1.23.1", "zip", "zip-extract", ] [[package]] name = "spanned" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86af297923fbcfd107c20a189a6e9c872160df71a7190ae4a7a6c5dce4b2feb6" +checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" dependencies = [ + "anyhow", "bstr", "color-eyre", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "spki" version = "0.7.3" @@ -8793,9 +10885,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -8811,25 +10903,24 @@ checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.13.1", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -8851,46 +10942,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" -dependencies = [ - "strum_macros 0.27.1", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.101", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -8899,17 +10967,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "sval" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9739f56c5d0c44a5ed45473ec868af02eb896af8c05f616673a31e1d1bb09" +checksum = "2eb9318255ebd817902d7e279d8f8e39b35b1b9954decd5eb9ea0e30e5fd2b6a" [[package]] name = "sval_buffer" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39b07436a8c271b34dad5070c634d1d3d76d6776e938ee97b4a66a5e8003d0b" +checksum = "12571299185e653fdb0fbfe36cd7f6529d39d4e747a60b15a3f34574b7b97c61" dependencies = [ "sval", "sval_ref", @@ -8917,18 +10991,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffcb072d857431bf885580dacecf05ed987bac931230736739a79051dbf3499b" +checksum = "39526f24e997706c0de7f03fb7371f7f5638b66a504ded508e20ad173d0a3677" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f214f427ad94a553e5ca5514c95c6be84667cbc5568cce957f03f3477d03d5c" +checksum = "933dd3bb26965d682280fcc49400ac2a05036f4ee1e6dbd61bf8402d5a5c3a54" dependencies = [ "itoa", "ryu", @@ -8937,9 +11011,9 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ed34b32e638dec9a99c8ac92d0aa1220d40041026b625474c2b6a4d6f4feb" +checksum = "a0cda08f6d5c9948024a6551077557b1fdcc3880ff2f20ae839667d2ec2d87ed" dependencies = [ "itoa", "ryu", @@ -8948,9 +11022,9 @@ dependencies = [ [[package]] name = "sval_nested" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14bae8fcb2f24fee2c42c1f19037707f7c9a29a0cda936d2188d48a961c4bb2a" +checksum = "88d49d5e6c1f9fd0e53515819b03a97ca4eb1bff5c8ee097c43391c09ecfb19f" dependencies = [ "sval", "sval_buffer", @@ -8959,52 +11033,51 @@ dependencies = [ [[package]] name = "sval_ref" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4eaea3821d3046dcba81d4b8489421da42961889902342691fb7eab491d79e" +checksum = "14f876c5a78405375b4e19cbb9554407513b59c93dea12dc6a4af4e1d30899ca" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.14.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172dd4aa8cb3b45c8ac8f3b4111d644cd26938b0643ede8f93070812b87fb339" +checksum = "5f9ccd3b7f7200239a655e517dd3fd48d960b9111ad24bd6a5e055bef17607c7" dependencies = [ - "serde", + "serde_core", "sval", "sval_nested", ] [[package]] name = "svm-rs" -version = "0.5.16" +version = "0.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d304f1b54e9c83ec8f0537c9dd40d46344bd9142cc528d5242c4b6fe11ced0" +checksum = "4572dd9845e37ca0293acb5fe591a7f61b51f1b7b62d3dc6fb8e99e2664f3755" dependencies = [ "const-hex", "dirs", - "fs4", - "reqwest", - "semver 1.0.26", + "reqwest 0.13.3", + "semver 1.0.28", "serde", "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", "url", "zip", ] [[package]] name = "svm-rs-builds" -version = "0.5.16" +version = "0.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed5035d2abae3cd98c201b116ff22a82861589c060f3e4b687ce951cf381a6e" +checksum = "74224f62f19c1309caa071de7c1c9c1ad1d7551d2f881af4046f3d71880c820a" dependencies = [ "const-hex", - "semver 1.0.26", + "semver 1.0.28", "serde_json", "svm-rs", ] @@ -9022,9 +11095,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -9033,14 +11106,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -9052,13 +11125,24 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9073,6 +11157,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tag_ptr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05" + [[package]] name = "tap" version = "1.0.1" @@ -9081,15 +11171,202 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tempo-alloy" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips 2.0.4", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-serde 2.0.4", + "alloy-signer-local", + "alloy-sol-types", + "alloy-transport", + "async-trait", + "dashmap", + "derive_more", + "futures", + "serde", + "tempo-chainspec", + "tempo-contracts", + "tempo-primitives", + "tracing", +] + +[[package]] +name = "tempo-chainspec" +version = "1.5.3" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-genesis", + "alloy-hardforks", + "alloy-primitives", + "once_cell", + "paste", + "reth-chainspec", + "reth-network-peers", + "serde", + "serde_json", + "tempo-primitives", +] + +[[package]] +name = "tempo-consensus" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-ethereum-consensus", + "reth-primitives-traits", + "tempo-chainspec", + "tempo-primitives", + "thiserror 2.0.18", +] + +[[package]] +name = "tempo-contracts" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-contract", + "alloy-primitives", + "alloy-sol-types", + "serde", +] + +[[package]] +name = "tempo-evm" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "commonware-codec", + "commonware-cryptography", + "derive_more", + "reth-chainspec", + "reth-consensus", + "reth-evm", + "reth-evm-ethereum", + "reth-primitives-traits", + "reth-revm", + "tempo-chainspec", + "tempo-consensus", + "tempo-contracts", + "tempo-primitives", + "tempo-revm", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "tempo-precompiles" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy", + "alloy-evm", + "commonware-codec", + "commonware-cryptography", + "derive_more", + "revm", + "scoped-tls", + "tempo-chainspec", + "tempo-contracts", + "tempo-precompiles-macros", + "tempo-primitives", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "tempo-precompiles-macros" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tempo-primitives" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-consensus", + "alloy-eips 2.0.4", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde 2.0.4", + "aws-lc-rs", + "base64 0.22.1", + "derive_more", + "ed25519-consensus", + "modular-bitfield", + "once_cell", + "p256", + "reth-codecs", + "reth-db-api", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-rpc-convert", + "revm", + "serde", + "serde_json", + "sha2 0.10.9", + "tempo-contracts", +] + +[[package]] +name = "tempo-revm" +version = "1.6.0" +source = "git+https://github.com/tempoxyz/tempo?rev=6bf9903d6a75cc264029dcf54183adea38d3cfaa#6bf9903d6a75cc264029dcf54183adea38d3cfaa" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-primitives", + "alloy-sol-types", + "auto_impl", + "derive_more", + "reth-evm", + "reth-storage-api", + "revm", + "serde", + "tempo-chainspec", + "tempo-contracts", + "tempo-precompiles", + "tempo-primitives", + "thiserror 2.0.18", + "tracing", ] [[package]] @@ -9103,24 +11380,14 @@ dependencies = [ "utf-8", ] -[[package]] -name = "term" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a984c8d058c627faaf5e8e2ed493fa3c51771889196de1016cf9c1c6e90d750" -dependencies = [ - "home", - "windows-sys 0.59.0", -] - [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -9131,8 +11398,8 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom", - "phf", - "phf_codegen", + "phf 0.11.3", + "phf_codegen 0.11.3", ] [[package]] @@ -9149,9 +11416,15 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + [[package]] name = "thiserror" version = "1.0.69" @@ -9163,11 +11436,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -9178,28 +11451,27 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -9213,9 +11485,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" dependencies = [ "cc", "libc", @@ -9223,9 +11495,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -9233,9 +11505,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -9243,41 +11515,43 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ - "crunchy", + "displaydoc", + "serde_core", + "zerovec", ] [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -9290,11 +11564,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -9303,47 +11576,35 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", - "tokio", -] - [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -9356,6 +11617,18 @@ name = "tokio-tungstenite" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -9363,15 +11636,27 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tungstenite", + "tungstenite 0.28.0", "webpki-roots 0.26.11", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.29.0", +] + [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -9382,77 +11667,115 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] -name = "toml" -version = "0.8.22" +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "indexmap 2.9.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "serde_core", ] [[package]] name = "toml_datetime" -version = "0.6.9" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.9.0", - "serde", + "indexmap 2.14.0", + "serde_core", "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] -name = "toml_write" -version = "0.1.1" +name = "toml_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" -version = "0.13.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", "base64 0.22.1", "bytes", "h2", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper", "hyper-timeout", "hyper-util", "percent-encoding", - "pin-project 1.1.10", - "prost", - "rustls-native-certs", + "pin-project", "socket2", + "sync_wrapper", "tokio", "tokio-rustls", "tokio-stream", @@ -9460,6 +11783,18 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "webpki-roots 1.0.7", +] + +[[package]] +name = "tonic-prost" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +dependencies = [ + "bytes", + "prost 0.14.3", + "tonic", ] [[package]] @@ -9470,13 +11805,13 @@ checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.9.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -9489,24 +11824,28 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.4" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.9.1", + "async-compression", + "bitflags 2.11.1", "bytes", + "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", + "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", "tracing", @@ -9524,23 +11863,11 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" -[[package]] -name = "tower-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" -dependencies = [ - "futures-core", - "futures-util", - "pin-project 0.4.30", - "tower-service", -] - [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -9550,20 +11877,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -9576,19 +11903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", - "tracing-subscriber 0.3.20", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "futures", - "futures-task", - "pin-project 1.1.10", - "tracing", + "tracing-subscriber 0.3.23", ] [[package]] @@ -9613,9 +11928,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -9636,15 +11951,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", - "tracing-subscriber 0.3.20", + "tracing-subscriber 0.3.23", "tracy-client", ] [[package]] name = "tracy-client" -version = "0.18.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" +checksum = "a4f6fc3baeac5d86ab90c772e9e30620fc653bf1864295029921a15ef478e6a5" dependencies = [ "loom", "once_cell", @@ -9654,9 +11969,9 @@ dependencies = [ [[package]] name = "tracy-client-sys" -version = "0.24.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" +checksum = "c5f7c95348f20c1c913d72157b3c6dee6ea3e30b3d19502c5a7f6d3f160dacbf" dependencies = [ "cc", "windows-targets 0.52.6", @@ -9664,15 +11979,15 @@ dependencies = [ [[package]] name = "trezor-client" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10636211ab89c96ed2824adc5ec0d081e1080aeacc24c37abb318dcb31dcc779" +checksum = "87873db279766278a7e56b01139943e00a45afc079fc8fa6651e949f2234c3f6" dependencies = [ "byteorder", "hex", "protobuf", "rusb", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] @@ -9690,22 +12005,83 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.4", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", "httparse", "log", - "rand 0.9.1", + "rand 0.9.4", "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.12", + "thiserror 2.0.18", "utf-8", ] [[package]] -name = "typed-arena" -version = "2.0.2" +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.4", + "sha1", + "thiserror 2.0.18", +] + +[[package]] +name = "turnkey_api_key_stamper" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f72e05a07cb04163efff0c766521ebacbb268a851db83d419b7c56df90d046" +dependencies = [ + "base64 0.22.1", + "hex", + "k256", + "p256", + "rand_core 0.6.4", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "turnkey_client" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +checksum = "f216ea270ec4a37daa491679b716962cda7819cac982b49088979b2edf6067df" +dependencies = [ + "mime", + "prost 0.12.6", + "prost-types 0.12.6", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "turnkey_api_key_stamper", +] [[package]] name = "typeid" @@ -9715,9 +12091,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -9727,11 +12103,11 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ui_test" -version = "0.29.2" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1211b1111c752c73b33073d2958072be08825fd97c9ab4d83444da361a06634b" +checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8" dependencies = [ - "annotate-snippets", + "annotate-snippets 0.11.5", "anyhow", "bstr", "cargo-platform", @@ -9780,9 +12156,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -9792,9 +12168,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-joining-type" @@ -9810,28 +12186,28 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width 0.2.2", ] [[package]] @@ -9842,9 +12218,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -9852,6 +12228,28 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -9860,13 +12258,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", + "serde_derive", ] [[package]] @@ -9881,11 +12281,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" [[package]] name = "utf8_iter" @@ -9905,19 +12311,19 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "serde", ] [[package]] name = "uuid" -version = "1.17.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -9929,9 +12335,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -9939,20 +12345,20 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35540706617d373b118d550d41f5dfe0b78a0c195dc13c6815e92e2638432306" +checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" dependencies = [ "erased-serde", - "serde", + "serde_core", "serde_fmt", ] [[package]] name = "value-bag-sval2" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe7e140a2658cc16f7ee7a86e413e803fc8f9b5127adc8755c19f9fefa63a52" +checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" dependencies = [ "sval", "sval_buffer", @@ -9971,14 +12377,41 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.2" +version = "10.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +checksum = "2d7cb4a83971db3f6ae36f0aa41eaf5985d2e2b469581fa755c132f9c2a1ec89" dependencies = [ "anyhow", - "cfg-if", + "bon", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-gitcl" +version = "10.0.0-beta.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba14c9676943b2899cea2ed7ea194b89b3d13564a3c93a61882a978b123a41c" +dependencies = [ + "anyhow", + "bon", "rustversion", "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "10.0.0-beta.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb684e6d170ef15a9b3c20561779a50ba8c806f8acdaff47c0a2c5c4c6cadd43" +dependencies = [ + "anyhow", + "bon", + "getset", + "rustversion", ] [[package]] @@ -10032,63 +12465,56 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen 0.51.0", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasm-bindgen" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.101", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10096,31 +12522,66 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.101", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ - "unicode-ident", + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -10129,11 +12590,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver 1.0.28", +] + [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", @@ -10145,29 +12618,31 @@ dependencies = [ [[package]] name = "watchexec" -version = "8.0.1" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc35794a21139060aca512393e9b1a225fe48fc11edee65c84d6d76b25a53331" +checksum = "3de3c4a47a75176b13fc7b3f421a80a55b13cab5b3200547774f9b25d43a79a0" dependencies = [ "async-priority-channel", "atomic-take", "futures", + "libc", "miette", "normalize-path", "notify", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", "watchexec-events", "watchexec-signals", "watchexec-supervisor", + "windows-sys 0.61.2", ] [[package]] name = "watchexec-events" -version = "6.0.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4a8973a20c7d30198a12272519163168a9ba8b687693ec9d1f027b75b860d1" +checksum = "ad87c046fa1050d22100e7d234db2cbf6ffd020b0ae2deff4bef6faa8f71ac44" dependencies = [ "notify-types", "watchexec-signals", @@ -10175,20 +12650,20 @@ dependencies = [ [[package]] name = "watchexec-signals" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377729679262964c27e6a28f360a84b7aedb172b59841301c1c77922305dfd83" +checksum = "3fd4537617a323437550d34c73a6aeeb1b489bbcc526e63f044ca3e59347101f" dependencies = [ "miette", "nix 0.30.1", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "watchexec-supervisor" -version = "5.0.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a45c50ea6b2795f3d070ad621618c8737bb98f6bc2eb4847e8e8e2ce2f446c" +checksum = "a710aaac2dfcfb8a2e117c2f5e926bf10c533345f468f2017160c5f0e9ee0c53" dependencies = [ "futures", "process-wrap", @@ -10200,9 +12675,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -10220,63 +12695,73 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "string_cache", "string_cache_codegen", ] [[package]] -name = "webpki-roots" -version = "0.26.11" +name = "webbrowser" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ - "webpki-roots 1.0.0", + "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "rustls-pki-types", + "webpki-roots 1.0.7", ] [[package]] -name = "which" -version = "4.4.2" +name = "webpki-roots" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", + "rustls-pki-types", ] [[package]] name = "which" -version = "7.0.3" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" dependencies = [ - "either", - "env_home", - "rustix 1.0.7", - "winsafe", + "libc", ] [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -10296,11 +12781,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10311,44 +12796,43 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ "windows-core", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core", "windows-link", @@ -10357,37 +12841,37 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core", "windows-link", @@ -10395,38 +12879,29 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -10449,6 +12924,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -10467,25 +12960,26 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows-threading" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ "windows-link", ] @@ -10498,9 +12992,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -10510,9 +13004,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -10522,9 +13016,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -10534,9 +13028,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -10546,9 +13040,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -10558,9 +13052,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -10570,9 +13064,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -10582,39 +13076,139 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] [[package]] -name = "winsafe" -version = "0.0.19" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "bitflags 2.9.1", + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver 1.0.28", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "ws_stream_wasm" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" dependencies = [ "async_io_stream", "futures", @@ -10623,7 +13217,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -10638,12 +13232,30 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "xmlparser" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xsum" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -10651,109 +13263,175 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] -name = "xz2" -version = "0.1.7" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" dependencies = [ - "lzma-sys", + "is-terminal", ] [[package]] -name = "yansi" -version = "1.0.1" +name = "yoke" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "is-terminal", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", + "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "zip" -version = "2.4.2" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" dependencies = [ - "aes", "arbitrary", - "bzip2", - "constant_time_eq", "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", "flate2", - "getrandom 0.3.3", - "hmac", - "indexmap 2.9.0", - "lzma-rs", + "indexmap 2.14.0", "memchr", - "pbkdf2 0.12.2", - "sha1", - "thiserror 2.0.12", - "time", - "xz2", - "zeroize", "zopfli", - "zstd", ] [[package]] name = "zip-extract" -version = "0.2.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" +checksum = "7fa5b9958fd0b5b685af54f2c3fa21fca05fe295ebaf3e77b6d24d96c4174037" dependencies = [ "log", - "thiserror 1.0.69", + "thiserror 2.0.18", "zip", ] +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zopfli" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", @@ -10781,9 +13459,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 3baefd5dca836..c0090ba7e2f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "benches/", "crates/anvil/", "crates/anvil/core/", "crates/anvil/rpc/", @@ -17,45 +18,112 @@ members = [ "crates/evm/coverage/", "crates/evm/evm/", "crates/evm/fuzz/", + "crates/evm/sancov/", + "crates/evm/hardforks/", "crates/evm/traces/", "crates/fmt/", "crates/forge/", - "crates/script-sequence/", + "crates/lint/", "crates/macros/", + "crates/primitives/", + "crates/script-sequence/", "crates/test-utils/", - "crates/lint/", + "crates/cli-markdown/", ] resolver = "2" [workspace.package] -version = "1.2.1" -edition = "2021" -# Remember to update clippy.toml as well -rust-version = "1.86" +version = "1.7.2" +edition = "2024" +rust-version = "1.89" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" -homepage = "https://github.com/foundry-rs/foundry" +homepage = "https://getfoundry.sh" repository = "https://github.com/foundry-rs/foundry" exclude = ["benches/", "tests/", "test-data/", "testdata/"] [workspace.lints.clippy] -dbg-macro = "warn" +borrow_as_ptr = "warn" +branches_sharing_code = "warn" +clear_with_drain = "warn" +cloned_instead_of_copied = "warn" +collection_is_never_read = "warn" +dbg_macro = "warn" +derive_partial_eq_without_eq = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" -manual-string-new = "warn" -uninlined-format-args = "warn" -use-self = "warn" -redundant-clone = "warn" -octal-escapes = "allow" -# until is fixed -literal-string-with-formatting-args = "allow" +flat_map_option = "warn" +from_iter_instead_of_collect = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +if_not_else = "warn" +imprecise_flops = "warn" +iter_on_empty_collections = "warn" +iter_with_drain = "warn" +iter_without_into_iter = "warn" +large_stack_frames = "warn" +manual_assert = "warn" +manual_clamp = "warn" +manual_string_new = "warn" +missing_const_for_fn = "warn" +mutex_integer = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +needless_continue = "warn" +needless_for_each = "warn" +nonstandard_macro_braces = "warn" +option_as_ref_cloned = "warn" +path_buf_push_overwrite = "warn" +read_zero_byte_vec = "warn" +redundant_clone = "warn" +single_char_pattern = "warn" +redundant_else = "warn" +string_lit_as_bytes = "warn" +string_lit_chars_any = "warn" +suboptimal_flops = "warn" +suspicious_operation_groupings = "warn" +trailing_empty_array = "warn" +trait_duplication_in_bounds = "warn" +transmute_undefined_repr = "warn" +trivial_regex = "warn" +tuple_array_conversions = "warn" +uninhabited_references = "warn" +unnecessary_struct_initialization = "warn" +uninlined_format_args = "warn" +unnecessary_self_imports = "warn" +unnested_or_patterns = "warn" +unused_peekable = "warn" +unused_rounding = "warn" +use_self = "warn" +useless_let_if_seq = "warn" +while_float = "warn" +zero_sized_map_values = "warn" + +# Explicitly allowed nursery lints — too noisy or have known false positives. +as_ptr_cast_mut = "allow" +cognitive_complexity = "allow" +debug_assert_with_mut_call = "allow" +fallible_impl_from = "allow" +future_not_send = "allow" +needless_collect = "allow" +non_send_fields_in_send_ty = "allow" +redundant_pub_crate = "allow" +significant_drop_in_scrutinee = "allow" +significant_drop_tightening = "allow" +too_long_first_doc_paragraph = "allow" + +# Specific allows. result_large_err = "allow" [workspace.lints.rust] +rust_2018_idioms = { level = "deny", priority = -1 } +unused_must_use = "deny" redundant_imports = "warn" -redundant-lifetimes = "warn" -rust-2018-idioms = "warn" -unused-must-use = "warn" -# unreachable-pub = "warn" +redundant_lifetimes = "warn" [workspace.lints.rustdoc] all = "warn" @@ -81,17 +149,22 @@ codegen-units = 16 [profile.profiling] inherits = "release" debug = "full" -split-debuginfo = "unpacked" -strip = false +strip = "none" [profile.bench] inherits = "profiling" -[profile.maxperf] +[profile.dist] inherits = "release" lto = "fat" codegen-units = 1 +[profile.fuzz] +inherits = "release" +lto = "thin" +strip = "none" +debug = "line-tables-only" + # Speed up tests and dev build. [profile.dev.package] # Solc and artifacts. @@ -101,13 +174,17 @@ foundry-compilers.opt-level = 3 serde_json.opt-level = 3 serde.opt-level = 3 -foundry-solang-parser.opt-level = 3 -lalrpop-util.opt-level = 3 - +solar-compiler.opt-level = 3 solar-ast.opt-level = 3 solar-data-structures.opt-level = 3 solar-interface.opt-level = 3 solar-parse.opt-level = 3 +solar-sema.opt-level = 3 + +# CLI. +clap.opt-level = 3 +clap_builder.opt-level = 3 +clap_lex.opt-level = 3 # EVM. alloy-dyn-abi.opt-level = 3 @@ -115,29 +192,41 @@ alloy-json-abi.opt-level = 3 alloy-primitives.opt-level = 3 alloy-sol-type-parser.opt-level = 3 alloy-sol-types.opt-level = 3 -hashbrown.opt-level = 3 -foldhash.opt-level = 3 -keccak.opt-level = 3 +alloy-evm.opt-level = 3 + +bitvec.opt-level = 3 revm.opt-level = 3 -revm-primitives.opt-level = 3 -revm-interpreter.opt-level = 3 -revm-precompile.opt-level = 3 -revm-database-interface.opt-level = 3 -revm-database.opt-level = 3 revm-bytecode.opt-level = 3 -revm-state.opt-level = 3 -revm-context-interface.opt-level = 3 revm-context.opt-level = 3 +revm-context-interface.opt-level = 3 +revm-database.opt-level = 3 +revm-database-interface.opt-level = 3 +revm-handler.opt-level = 3 revm-inspector.opt-level = 3 +revm-interpreter.opt-level = 3 +revm-precompile.opt-level = 3 +revm-primitives.opt-level = 3 +revm-state.opt-level = 3 + +revm-inspectors.opt-level = 3 + ruint.opt-level = 3 + sha2.opt-level = 3 sha3.opt-level = 3 -tiny-keccak.opt-level = 3 -bitvec.opt-level = 3 +keccak.opt-level = 3 # Fuzzing. proptest.opt-level = 3 foundry-evm-fuzz.opt-level = 3 +rand.opt-level = 3 +rand_chacha.opt-level = 3 +ppv-lite86.opt-level = 3 + +# Backtraces. +addr2line.opt-level = 3 +backtrace.opt-level = 3 +gimli.opt-level = 3 # Forking. axum.opt-level = 3 @@ -146,147 +235,209 @@ axum.opt-level = 3 scrypt.opt-level = 3 # Misc. +hashbrown.opt-level = 3 +foldhash.opt-level = 3 rayon.opt-level = 3 +rayon-core.opt-level = 3 regex.opt-level = 3 regex-syntax.opt-level = 3 regex-automata.opt-level = 3 +walkdir.opt-level = 3 # Override packages which aren't perf-sensitive for faster compilation speed and smaller binary size. [profile.release.package] -alloy-sol-macro-expander.opt-level = "z" -figment.opt-level = "z" -foundry-compilers-artifacts-solc.opt-level = "z" -foundry-config.opt-level = "z" -html5ever.opt-level = "z" -mdbook.opt-level = "z" -prettyplease.opt-level = "z" -protobuf.opt-level = "z" -pulldown-cmark.opt-level = "z" -syn-solidity.opt-level = "z" -syn.opt-level = "z" -trezor-client.opt-level = "z" +alloy-sol-macro-expander.opt-level = "s" +figment2.opt-level = "s" +foundry-compilers-artifacts-solc.opt-level = "s" +foundry-config.opt-level = "s" +html5ever.opt-level = "s" +mdbook-driver.opt-level = "s" +prettyplease.opt-level = "s" +protobuf.opt-level = "s" +pulldown-cmark.opt-level = "s" +syn-solidity.opt-level = "s" +syn.opt-level = "s" +trezor-client.opt-level = "s" + +# Networking stack (not perf-critical for CLI tools). +reqwest.opt-level = "s" +hyper.opt-level = "s" +hyper-util.opt-level = "s" +h2.opt-level = "s" +rustls.opt-level = "s" +tower.opt-level = "s" +tower-http.opt-level = "s" + +# Doc generation / templating. +mdbook-core.opt-level = "s" +mdbook-html.opt-level = "s" +font-awesome-as-a-crate.opt-level = "s" +handlebars.opt-level = "s" + +# Watch mode. +watchexec.opt-level = "s" +watchexec-events.opt-level = "s" +watchexec-signals.opt-level = "s" +watchexec-supervisor.opt-level = "s" + +# Soldeer package manager. +soldeer-commands.opt-level = "s" +soldeer-core.opt-level = "s" + +# Parsing / editing (not perf-sensitive in CLI). +toml_edit.opt-level = "s" +toml.opt-level = "s" + +# Block explorers / verification. +foundry-block-explorers.opt-level = "s" + +# Misc CLI dependencies. +clap_builder.opt-level = "s" +clap_complete.opt-level = "s" +comfy-table.opt-level = "s" +indicatif.opt-level = "s" +dialoguer.opt-level = "s" +ratatui.opt-level = "s" +crossterm.opt-level = "s" + +# Alloy JSON (parsing, not hot path). +alloy-json-abi.opt-level = "s" [workspace.dependencies] -anvil = { path = "crates/anvil" } -cast = { path = "crates/cast" } -chisel = { path = "crates/chisel" } -forge = { path = "crates/forge" } - -forge-doc = { path = "crates/doc" } -forge-fmt = { path = "crates/fmt" } -forge-lint = { path = "crates/lint" } -forge-verify = { path = "crates/verify" } -forge-script = { path = "crates/script" } -forge-sol-macro-gen = { path = "crates/sol-macro-gen" } -forge-script-sequence = { path = "crates/script-sequence" } -foundry-cheatcodes = { path = "crates/cheatcodes" } +anvil = { path = "crates/anvil", default-features = false } +cast = { path = "crates/cast", default-features = false } +chisel = { path = "crates/chisel", default-features = false } +forge = { path = "crates/forge", default-features = false } + +forge-doc = { path = "crates/doc", default-features = false } +forge-fmt = { path = "crates/fmt", default-features = false } +forge-lint = { path = "crates/lint", default-features = false } +forge-verify = { path = "crates/verify", default-features = false } +forge-script = { path = "crates/script", default-features = false } +forge-sol-macro-gen = { path = "crates/sol-macro-gen", default-features = false } +forge-script-sequence = { path = "crates/script-sequence", default-features = false } +foundry-cheatcodes = { path = "crates/cheatcodes", default-features = false } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } -foundry-cli = { path = "crates/cli" } -foundry-common = { path = "crates/common" } -foundry-common-fmt = { path = "crates/common/fmt" } +foundry-cli = { path = "crates/cli", default-features = false } +foundry-cli-markdown = { path = "crates/cli-markdown" } +foundry-common = { path = "crates/common", default-features = false } +foundry-common-fmt = { path = "crates/common/fmt", default-features = false } foundry-config = { path = "crates/config" } -foundry-debugger = { path = "crates/debugger" } -foundry-evm = { path = "crates/evm/evm" } +foundry-debugger = { path = "crates/debugger", default-features = false } +foundry-evm = { path = "crates/evm/evm", default-features = false } foundry-evm-abi = { path = "crates/evm/abi" } -foundry-evm-core = { path = "crates/evm/core" } -foundry-evm-coverage = { path = "crates/evm/coverage" } -foundry-evm-fuzz = { path = "crates/evm/fuzz" } -foundry-evm-traces = { path = "crates/evm/traces" } +foundry-evm-core = { path = "crates/evm/core", default-features = false } +foundry-evm-coverage = { path = "crates/evm/coverage", default-features = false } +foundry-evm-hardforks = { path = "crates/evm/hardforks", default-features = false } +foundry-evm-networks = { path = "crates/evm/networks", default-features = false } +foundry-evm-fuzz = { path = "crates/evm/fuzz", default-features = false } +foundry-evm-sancov = { path = "crates/evm/sancov" } +foundry-evm-traces = { path = "crates/evm/traces", default-features = false } foundry-macros = { path = "crates/macros" } -foundry-test-utils = { path = "crates/test-utils" } -foundry-wallets = { path = "crates/wallets" } +foundry-test-utils = { path = "crates/test-utils", default-features = false } +foundry-wallets = { version = "0.1.0", default-features = false } foundry-linking = { path = "crates/linking" } +foundry-primitives = { path = "crates/primitives", default-features = false } # solc & compilation utilities -foundry-block-explorers = { version = "0.17.0", default-features = false } -foundry-compilers = { version = "0.16.1", default-features = false } -foundry-fork-db = "0.15" -solang-parser = { version = "=0.3.8", package = "foundry-solang-parser" } -solar-ast = { version = "=0.1.3", default-features = false } -solar-parse = { version = "=0.1.3", default-features = false } -solar-interface = { version = "=0.1.3", default-features = false } -solar-sema = { version = "=0.1.3", default-features = false } +foundry-block-explorers = { version = "0.23.1", default-features = false } +foundry-compilers = { version = "0.20.0", default-features = false, features = [ + "rustls", + "svm-solc", +] } +foundry-fork-db = { version = "0.26.0", features = ["zstd"] } +solar = { package = "solar-compiler", version = "=0.1.8", default-features = false } +svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ + "rustls", +] } ## alloy -alloy-consensus = { version = "1.0.7", default-features = false } -alloy-contract = { version = "1.0.7", default-features = false } -alloy-eips = { version = "1.0.7", default-features = false } -alloy-ens = { version = "1.0.7", default-features = false } -alloy-genesis = { version = "1.0.7", default-features = false } -alloy-json-rpc = { version = "1.0.7", default-features = false } -alloy-network = { version = "1.0.7", default-features = false } -alloy-provider = { version = "1.0.7", default-features = false } -alloy-pubsub = { version = "1.0.7", default-features = false } -alloy-rpc-client = { version = "1.0.7", default-features = false } -alloy-rpc-types = { version = "1.0.7", default-features = true } -alloy-serde = { version = "1.0.7", default-features = false } -alloy-signer = { version = "1.0.7", default-features = false } -alloy-signer-aws = { version = "1.0.7", default-features = false } -alloy-signer-gcp = { version = "1.0.7", default-features = false } -alloy-signer-ledger = { version = "1.0.7", default-features = false } -alloy-signer-local = { version = "1.0.7", default-features = false } -alloy-signer-trezor = { version = "1.0.7", default-features = false } -alloy-transport = { version = "1.0.7", default-features = false } -alloy-transport-http = { version = "1.0.7", default-features = false } -alloy-transport-ipc = { version = "1.0.7", default-features = false } -alloy-transport-ws = { version = "1.0.7", default-features = false } -alloy-hardforks = { version = "0.2.6", default-features = false } -alloy-op-hardforks = { version = "0.2.6", default-features = false } +alloy-consensus = { version = "2.0.1", default-features = false } +alloy-contract = { version = "2.0.1", default-features = false } +alloy-eips = { version = "2.0.1", default-features = false } +alloy-eip5792 = { version = "2.0.1", default-features = false } +alloy-ens = { version = "2.0.1", default-features = false } +alloy-genesis = { version = "2.0.1", default-features = false } +alloy-json-rpc = { version = "2.0.1", default-features = false } +alloy-network = { version = "2.0.1", default-features = false } +alloy-provider = { version = "2.0.1", default-features = false } +alloy-pubsub = { version = "2.0.1", default-features = false } +alloy-rpc-client = { version = "2.0.1", default-features = false } +alloy-rpc-types = { version = "2.0.1", default-features = true } +alloy-rpc-types-beacon = { version = "2.0.1", default-features = true } +alloy-rpc-types-engine = { version = "2.0.1", default-features = false } +alloy-rpc-types-eth = { version = "2.0.1", default-features = false } +alloy-serde = { version = "2.0.1", default-features = false } +alloy-signer = { version = "2.0.1", default-features = false } +alloy-signer-aws = { version = "2.0.1", default-features = false } +alloy-signer-gcp = { version = "2.0.1", default-features = false } +alloy-signer-ledger = { version = "2.0.1", default-features = false } +alloy-signer-local = { version = "2.0.1", default-features = false } +alloy-signer-trezor = { version = "2.0.1", default-features = false } +alloy-signer-turnkey = { version = "2.0.1", default-features = false } +alloy-transport = { version = "2.0.1", default-features = false } +alloy-transport-http = { version = "2.0.1", default-features = false } +alloy-transport-ipc = { version = "2.0.1", default-features = false } +alloy-transport-ws = { version = "2.0.1", default-features = false } +alloy-hardforks = { version = "0.4.7", default-features = false } +alloy-op-hardforks = { version = "0.4.7", default-features = false } ## alloy-core -alloy-dyn-abi = "1.0" -alloy-json-abi = "1.0" -alloy-primitives = { version = "1.0", features = [ +alloy-dyn-abi = "1.5.2" +alloy-json-abi = "1.5.2" +alloy-primitives = { version = "1.5.2", features = [ "getrandom", "rand", "map-fxhash", "map-foldhash", ] } -alloy-sol-macro-expander = "1.0" -alloy-sol-macro-input = "1.0" -alloy-sol-types = "1.0" +alloy-sol-macro-expander = "1.5.2" +alloy-sol-macro-input = "1.5.2" +alloy-sol-types = "1.5.2" alloy-chains = "0.2" alloy-rlp = "0.3" -alloy-trie = "0.8.1" +alloy-trie = "0.9" ## op-alloy -op-alloy-consensus = "0.17.1" -op-alloy-rpc-types = "0.17.1" -op-alloy-flz = "0.13.0" - -## revm -revm = { version = "24.0.0", default-features = false } -revm-inspectors = { version = "0.23.0", features = ["serde"] } -op-revm = { version = "5.0.0", default-features = false } +op-alloy-consensus = "0.24.0" +op-alloy-network = "0.24.0" +op-alloy-rpc-types = "0.24.0" +op-alloy-flz = "0.13.1" ## alloy-evm -alloy-evm = "0.10.0" -alloy-op-evm = "0.10.0" +alloy-evm = "0.34.0" +alloy-op-evm = "0.31.0" + +# revm +revm = { version = "38.0.0", default-features = false } +revm-inspectors = { version = "0.39.0", features = ["serde"] } +op-revm = { version = "19.0.0", default-features = false } ## cli anstream = "0.6" anstyle = "1.0" -terminal_size = "0.4" +dialoguer = { version = "0.12", default-features = false, features = [ + "password", +] } +clap_complete = "4" +clap_complete_nushell = "4" # macros proc-macro2 = "1.0" quote = "1.0" syn = "2.0" async-trait = "0.1" -derive_more = { version = "2.0", features = ["full"] } +derive_more = { version = "2.1", features = ["full"] } thiserror = "2" # allocators mimalloc = "0.1" tikv-jemallocator = "0.6" -tracy-client = "0.18" # misc auto_impl = "1" -aws-config = { version = "1", default-features = true } -aws-sdk-kms = { version = "1", default-features = false } -bytes = "1.8" +bytes = "1.11" walkdir = "2" prettyplease = "0.2" base64 = "0.22" @@ -300,61 +451,92 @@ color-eyre = "0.6" comfy-table = "7" dirs = "6" dunce = "1" -evm-disassembler = "0.5" +evm-disassembler = "0.6" evmole = "0.8" eyre = "0.6" -figment = "0.10" -futures = "0.3" -hyper = "1.5" -indicatif = "0.17" +figment = { package = "figment2", version = "0.11" } +futures = { version = "0.3", default-features = false } +hyper = "1.8" +indicatif = "0.18" itertools = "0.14" jsonpath_lib = "0.3" k256 = "0.13" mesc = "0.3" +memchr = "2.7" num-format = "0.4" parking_lot = "0.12" -proptest = "1" +proptest = "1.9.0" rand = "0.9" rand_08 = { package = "rand", version = "0.8" } -rand_chacha = "0.9.0" rayon = "1" regex = { version = "1", default-features = false } -reqwest = { version = "0.12", default-features = false, features = [ - "rustls-tls", - "rustls-tls-native-roots", -] } +reqwest = { version = "0.13", default-features = false, features = ["rustls"] } rustls = "0.23" semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } -similar-asserts = "1.6" -soldeer-commands = "=0.5.4" -soldeer-core = { version = "=0.5.4", features = ["serde"] } +similar-asserts = "1.7" +soldeer-commands = "=0.10.0" +soldeer-core = { version = "=0.10.1", features = ["serde"] } strum = "0.27" -tempfile = "3.13" +tempfile = "3.23" tokio = "1" -toml = "0.8" +tokio-tungstenite = { version = "0.28", features = ["rustls-tls-webpki-roots"] } +toml = "0.9" +toml_edit = "0.24" tower = "0.5" tower-http = "0.6" tracing = "0.1" tracing-subscriber = "0.3" url = "2" -vergen = { version = "8", default-features = false } +vergen = { package = "vergen-gitcl", version = "10.0.0-beta.5", default-features = false, features = [ + "build", + "cargo", +] } yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } path-slash = "0.2" -jiff = "0.2" +jiff = { version = "0.2", default-features = false, features = [ + "std", + "perf-inline", +] } heck = "0.5" +uuid = "1.19.0" +flate2 = "1.1" +ethereum_ssz = "0.10" + +# Tempo +mpp = { git = "https://github.com/tempoxyz/mpp-rs", rev = "554d20112eb014bd223d54de7f152ca59b2aa4fd", default-features = false, features = [ + "tempo", + "client", + "reqwest-rustls-tls", + "ws", +] } +tempo-chainspec = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ + "serde", +] } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-evm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false } +tempo-revm = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa", default-features = false, features = [ + "serde", +] } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-precompiles = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } ## Pinned dependencies. Enabled for the workspace in crates/test-utils. +# testing +snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } + # Use unicode-rs which has a smaller binary size than the default ICU4X as the IDNA backend, used # by the `url` crate. # See the `idna_adapter` README.md for more details: https://docs.rs/crate/idna_adapter/latest idna_adapter = "=1.1.0" -# Avoid duplicating `zip 2` and `zip 3`. Remove once all `zip` dependencies are updated to `zip 3`. -zip-extract = "=0.2.1" [patch.crates-io] +# https://github.com/rust-cli/rexpect/pull/106 +rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6a85bedbae71a01cc578958fc" } + ## alloy-core # alloy-dyn-abi = { path = "../../alloy-rs/core/crates/dyn-abi" } # alloy-json-abi = { path = "../../alloy-rs/core/crates/json-abi" } @@ -367,38 +549,63 @@ zip-extract = "=0.2.1" # syn-solidity = { path = "../../alloy-rs/core/crates/syn-solidity" } ## alloy -# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } -# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-consensus-any = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-eip5792 = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-ens = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-signer-turnkey = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy-tx-macros = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } +# alloy = { git = "https://github.com/alloy-rs/alloy", rev = "395521c" } ## alloy-evm -# alloy-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "8076e12" } -# alloy-op-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "8076e12" } - -## revm -# revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } -# op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } -# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } - -## foundry -# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" } +# alloy-evm = { git = "https://github.com/paradigmxyz/evm.git", rev = "04d8e4a" } + +## op-revm / op-alloy / alloy-op-evm +op-revm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-consensus = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-network = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +op-alloy-rpc-types = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-evm = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } +alloy-op-hardforks = { git = "https://github.com/ethereum-optimism/optimism", rev = "e3b59e76588f99db17205f5601e45a5b00f0bfbb" } + +## foundry-fork-db +# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-core", rev = "2f90eb86d4549fa15a8cc2d99bfc1039bc083977" } + +## tempo — unify crates.io versions (pulled by mpp) with git rev +tempo-primitives = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-alloy = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } +tempo-contracts = { git = "https://github.com/tempoxyz/tempo", rev = "6bf9903d6a75cc264029dcf54183adea38d3cfaa" } + +# solar +solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } +solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } +solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } +solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } + +[workspace.metadata.cargo-shear] +ignored = ["idna_adapter", "cast", "chisel", "forge", "alloy-contract"] diff --git a/Dockerfile b/Dockerfile index 57983fe283b4d..ee19df0905576 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,81 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 -FROM alpine:3.21 as build-environment +FROM rust:1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS chef +WORKDIR /app -ARG TARGETARCH -WORKDIR /opt +RUN apt update && apt install -y build-essential libssl-dev git pkg-config curl perl +RUN set -eux; \ + BINSTALL_VERSION="v1.18.1"; \ + case "$(dpkg --print-architecture)" in \ + amd64) ARCH="x86_64-unknown-linux-musl"; SHA256="cf2a4b54494ea8555d6349685e9a301efc1051d9fba6308c76914b2486f8700f" ;; \ + arm64) ARCH="aarch64-unknown-linux-musl"; SHA256="c55962a0115f9716b709216de7f8bdd59d6ba8738779e60b051b4593f677717a" ;; \ + *) echo "unsupported architecture" >&2; exit 1 ;; \ + esac; \ + curl -L --proto '=https' --tlsv1.2 -sSf \ + "https://github.com/cargo-bins/cargo-binstall/releases/download/${BINSTALL_VERSION}/cargo-binstall-${ARCH}.tgz" \ + -o /tmp/cargo-binstall.tgz; \ + echo "${SHA256} /tmp/cargo-binstall.tgz" | sha256sum -c -; \ + tar -xzf /tmp/cargo-binstall.tgz -C /usr/local/cargo/bin cargo-binstall; \ + rm /tmp/cargo-binstall.tgz +RUN cargo binstall -y cargo-chef sccache -RUN apk add clang lld curl build-base linux-headers git \ - && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh \ - && chmod +x ./rustup.sh \ - && ./rustup.sh -y +# Prepare the cargo-chef recipe. +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json -RUN [[ "$TARGETARCH" = "arm64" ]] && echo "export CFLAGS=-mno-outline-atomics" >> $HOME/.profile || true +# Build the project. +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json -WORKDIR /opt/foundry -COPY . . +ARG RUST_PROFILE +ARG RUST_FEATURES -# see -RUN git update-index --force-write-index +ENV CARGO_INCREMENTAL=0 \ + RUSTC_WRAPPER=sccache \ + SCCACHE_DIR=/sccache -RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/opt/foundry/target \ - source $HOME/.profile && cargo build --release --features cast/aws-kms,cast/gcp-kms,forge/aws-kms,forge/gcp-kms \ - && mkdir out \ - && mv target/release/forge out/forge \ - && mv target/release/cast out/cast \ - && mv target/release/anvil out/anvil \ - && mv target/release/chisel out/chisel \ - && strip out/forge \ - && strip out/cast \ - && strip out/chisel \ - && strip out/anvil; +# Build dependencies. +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \ + --mount=type=cache,target=$SCCACHE_DIR,sharing=shared \ + cargo chef cook --recipe-path recipe.json --profile ${RUST_PROFILE} --no-default-features --features "${RUST_FEATURES}" -FROM alpine:3.21 as foundry-client +ARG TAG_NAME="dev" +ENV TAG_NAME=$TAG_NAME +ARG VERGEN_GIT_SHA="ffffffffffffffffffffffffffffffffffffffff" +ENV VERGEN_GIT_SHA=$VERGEN_GIT_SHA -RUN apk add --no-cache linux-headers git gcompat libstdc++ +# Build the project. +COPY . . +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \ + --mount=type=cache,target=$SCCACHE_DIR,sharing=shared \ + cargo build --profile ${RUST_PROFILE} --no-default-features --features "${RUST_FEATURES}" \ + && sccache --show-stats || true -COPY --from=build-environment /opt/foundry/out/forge /usr/local/bin/forge -COPY --from=build-environment /opt/foundry/out/cast /usr/local/bin/cast -COPY --from=build-environment /opt/foundry/out/anvil /usr/local/bin/anvil -COPY --from=build-environment /opt/foundry/out/chisel /usr/local/bin/chisel +# `dev` profile outputs to the `target/debug` directory. +RUN ln -s /app/target/debug /app/target/dev \ + && mkdir -p /app/output \ + && mv \ + /app/target/${RUST_PROFILE}/forge \ + /app/target/${RUST_PROFILE}/cast \ + /app/target/${RUST_PROFILE}/anvil \ + /app/target/${RUST_PROFILE}/chisel \ + /app/output/ -RUN adduser -Du 1000 foundry +FROM ubuntu:22.04@sha256:eb29ed27b0821dca09c2e28b39135e185fc1302036427d5f4d70a41ce8fd7659 AS runtime -ENTRYPOINT ["/bin/sh", "-c"] +# Install runtime dependencies. +RUN apt update && apt install -y git +COPY --from=builder /app/output/* /usr/local/bin/ + +RUN groupadd -g 1000 foundry && \ + useradd -m -u 1000 -g foundry foundry +USER foundry + +ENTRYPOINT ["/bin/sh", "-c"] LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.name="Foundry" \ diff --git a/Dockerfile.cross b/Dockerfile.cross deleted file mode 100644 index 3efdde14ae7c0..0000000000000 --- a/Dockerfile.cross +++ /dev/null @@ -1,28 +0,0 @@ -# This image is meant to enable cross-architecture builds. -# It assumes the foundry binaries have already been compiled for `$TARGETPLATFORM` and are -# locatable in `./dist/bin/$TARGETARCH` -FROM ubuntu:22.04 - -# Filled by docker buildx -ARG TARGETARCH - -RUN apt update && apt install -y git - -COPY ./dist/bin/$TARGETARCH/* /usr/local/bin/ - -RUN groupadd -g 1000 foundry && \ - useradd -m -u 1000 -g foundry foundry - -USER foundry - -ENTRYPOINT ["/bin/sh", "-c"] - -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Foundry" \ - org.label-schema.description="Foundry" \ - org.label-schema.url="https://getfoundry.sh" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/foundry-rs/foundry.git" \ - org.label-schema.vendor="Foundry-rs" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/FUNDING.json b/FUNDING.json index 8f127f0f4e008..99e9e5c51a20f 100644 --- a/FUNDING.json +++ b/FUNDING.json @@ -7,4 +7,4 @@ "opRetro": { "projectId": "0x4562c0630907577f433cad78c7e2cc03349d918b6c14ef982f11a2678f5999ad" } -} \ No newline at end of file +} diff --git a/Makefile b/Makefile index 8b9ccd21acdb8..64e66ab78c618 100644 --- a/Makefile +++ b/Makefile @@ -5,120 +5,161 @@ # Cargo profile for builds. PROFILE ?= dev + # The docker image name DOCKER_IMAGE_NAME ?= ghcr.io/foundry-rs/foundry:latest + BIN_DIR = dist/bin CARGO_TARGET_DIR ?= target # List of features to use when building. Can be overridden via the environment. # No jemalloc on Windows ifeq ($(OS),Windows_NT) - FEATURES ?= aws-kms gcp-kms cli asm-keccak + FEATURES ?= aws-kms gcp-kms turnkey cli asm-keccak else - FEATURES ?= jemalloc aws-kms gcp-kms cli asm-keccak + FEATURES ?= jemalloc aws-kms gcp-kms turnkey cli asm-keccak endif ##@ Help .PHONY: help help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Build .PHONY: build build: ## Build the project. - cargo build --features "$(FEATURES)" --profile "$(PROFILE)" - -# The following commands use `cross` to build a cross-compile. -# -# These commands require that: -# -# - `cross` is installed (`cargo install cross`). -# - Docker is running. -# - The current user is in the `docker` group. -# -# The resulting binaries will be created in the `target/` directory. -build-%: - cross build --target $* --features "$(FEATURES)" --profile "$(PROFILE)" - -.PHONY: docker-build-push -docker-build-push: docker-build-prepare ## Build and push a cross-arch Docker image tagged with DOCKER_IMAGE_NAME. - FEATURES="jemalloc aws-kms gcp-kms cli asm-keccak" $(MAKE) build-x86_64-unknown-linux-gnu - mkdir -p $(BIN_DIR)/amd64 - for bin in anvil cast chisel forge; do \ - cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/amd64/; \ - done - - FEATURES="aws-kms gcp-kms cli asm-keccak" $(MAKE) build-aarch64-unknown-linux-gnu - mkdir -p $(BIN_DIR)/arm64 - for bin in anvil cast chisel forge; do \ - cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/arm64/; \ - done - - docker buildx build --file ./Dockerfile.cross . \ - --platform linux/amd64,linux/arm64 \ - $(foreach tag,$(shell echo $(DOCKER_IMAGE_NAME) | tr ',' ' '),--tag $(tag)) \ - --provenance=false \ - --push - -.PHONY: docker-build-prepare -docker-build-prepare: ## Prepare the Docker build environment. - docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install amd64,arm64 - @if ! docker buildx inspect cross-builder &> /dev/null; then \ - echo "Creating a new buildx builder instance"; \ - docker buildx create --use --driver docker-container --name cross-builder; \ - else \ - echo "Using existing buildx builder instance"; \ - docker buildx use cross-builder; \ - fi + cargo build --locked --features "$(FEATURES)" --profile "$(PROFILE)" + +.PHONY: build-docker +build-docker: ## Build the docker image. + docker build . -t "$(DOCKER_IMAGE_NAME)" \ + --build-arg "RUST_PROFILE=$(PROFILE)" \ + --build-arg "RUST_FEATURES=$(FEATURES)" \ + --build-arg "TAG_NAME=dev" \ + --build-arg "VERGEN_GIT_SHA=$(shell git rev-parse HEAD)" ##@ Test +## Run unit/doc tests and generate html coverage report in `target/llvm-cov/html` folder. +## Notice that `llvm-cov` supports doc tests only in nightly builds because the `--doc` flag +## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). +.PHONY: test-coverage +test-coverage: + cargo +nightly llvm-cov --no-report nextest --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ + cargo +nightly llvm-cov --no-report --doc --locked && \ + cargo +nightly llvm-cov report --doctests --open + .PHONY: test-unit test-unit: ## Run unit tests. - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration)/)' + cargo nextest run --workspace --locked -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .PHONY: test-doc test-doc: ## Run doc tests. - cargo test --doc --workspace + cargo test --doc --workspace --locked .PHONY: test test: ## Run all tests. - make test-unit && \ - make test-doc + $(MAKE) test-unit && \ + $(MAKE) test-doc ##@ Linting +.PHONY: fmt fmt: ## Run all formatters. cargo +nightly fmt ./.github/scripts/format.sh --check +.PHONY: lint-clippy lint-clippy: ## Run clippy on the codebase. cargo +nightly clippy \ --workspace \ --all-targets \ --all-features \ + --locked \ -- -D warnings -lint-codespell: ## Run codespell on the codebase. - @command -v codespell >/dev/null || { \ - echo "codespell not found. Please install it by running the command `pipx install codespell` or refer to the following link for more information: https://github.com/codespell-project/codespell" \ +.PHONY: lint-clippy-fix +lint-clippy-fix: ## Run clippy on the codebase and fix warnings. + cargo +nightly clippy \ + --workspace \ + --all-targets \ + --all-features \ + --locked \ + --fix \ + --allow-dirty \ + --allow-staged \ + -- -D warnings + +.PHONY: lint-typos +lint-typos: ## Run typos on the codebase. + @command -v typos >/dev/null || { \ + echo "typos not found. Please install it by running the command 'cargo install typos-cli' or refer to the following link for more information: https://github.com/crate-ci/typos"; \ exit 1; \ } - codespell --skip "*.json" + typos +.PHONY: lint lint: ## Run all linters. - make fmt && \ - make lint-clippy && \ - make lint-codespell + $(MAKE) fmt && \ + $(MAKE) lint-clippy && \ + $(MAKE) lint-typos + +##@ Documentation + +.PHONY: doc +doc: ## Build the documentation. + RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options --show-type-layout --generate-link-to-definition" \ + cargo +nightly doc \ + --workspace \ + --all-features \ + --document-private-items \ + --no-deps \ + --locked ##@ Other +.PHONY: lock +lock: ## Update the Cargo.lock file with the current dependencies. + cargo fetch + .PHONY: clean clean: ## Clean the project. cargo clean -pr: ## Run all tests and linters in preparation for a PR. - make lint && \ - make test +.PHONY: deny +deny: ## Perform a `cargo` deny check. + cargo deny --locked --all-features check all + +.PHONY: check +check: ## Run a feature check on all crates and binaries. + cargo hack check --locked --feature-powerset --depth 1 + +.PHONY: shear +shear: ## Run `cargo shear` to check for unused dependencies. + cargo shear --locked + +.PHONY: pr +pr: ## Run all checks and tests. + $(MAKE) deny && \ + $(MAKE) lint && \ + $(MAKE) test && \ + $(MAKE) doc + +# dprint formatting commands +.PHONY: dprint-fmt +dprint-fmt: ## Format code with dprint + @if ! command -v dprint > /dev/null; then \ + echo "Installing dprint..."; \ + cargo install dprint; \ + fi + dprint fmt + +.PHONY: dprint-check +dprint-check: ## Check formatting with dprint + @if ! command -v dprint > /dev/null; then \ + echo "Installing dprint..."; \ + cargo install dprint; \ + fi + dprint check diff --git a/README.md b/README.md index 4fa5510481304..90cd1d8a7865e 100644 --- a/README.md +++ b/README.md @@ -4,364 +4,93 @@   [![Github Actions][gha-badge]][gha-url] [![Telegram Chat][tg-badge]][tg-url] [![Telegram Support][tg-support-badge]][tg-support-url] -![Foundry](https://img.shields.io/badge/Foundry-grey?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAElElEQVR4nH1VUUhUaRg9984YdzBpkqR0Z210rIESIXSabEbcHgydrpNRRj00kWaztj0U1MOW0MOIbD300IvLMqBpMTGYxdoqyoRNDUESBDWwUuPugCSSsTM7u0Oj1/+efdiMcmnP2/fDd77D4f/OB6xCa2urQZbllVICYGtqanK1tLS4AdgAyAAgyzJaW1sNq/ulT4twOGw4fPiwAGDp7Ow8VV1d7bVarRWxWCw/k8mgsbExm0wmZ+Lx+M/Xr1//CcAsSVmSJH01McLhsAEAnE5nx+Tk5B/xeJxOp5N9fX2sqqqixWLhnTt36HA4GIvFGI1GU3V1df5Pe/9D1t7eHkgkEuzo6GBPT49WWloq7Ha7fujQITocDu7atUs3m83i6tWr2okTJ/jixQuePn265zPScDhskGUZe/fubXv8+DFv3rypbdiwQaxbt46RSIT79u3j0NAQb926RVVVOT4+TqvVyvz8fD0YDC5NTk6ysbHxlCRJ/5KSlAAURyKRTFNTkwAg7t69S5/Px76+Pq7GyMgI9+/fz9HRUQIQO3bsEKOjo38DsJCUJADw+/0BVVW7otHo8ps3b4yvXr3CxMQETCYTTCYTNE0DAOTl5SGXy0FRFOzZswdmsxkVFRXLNTU1xmg0+kNvb+/3AGAcGBiI7969Wwcg6urq+OTJE967d49btmzh9PT0R3WJRIKBQIDBYJBTU1NsaGggAGGz2fTe3t5fAeQZAWwuLi4uP3nypOT1emEwGFBeXo7a2losLCygoaEB/f39MJlMCIVCkCQJBw8ehNVqhcfjQXNzs1RSUiKtX7++DEAZqqqq3KFQiABYUFDAM2fOkCQXFxdJkvfv32dhYSG9Xi+vXbvG2dnZj4oDgQCLioqoKAqHhobodDq/Mc7NzUklJSUIBoOw2WzYtm0blpeXsWbNGkxMTODp06doa2vD4OAgNm7cCIvFApLQdR3nzp3Dzp078fLlSxQVFeHdu3cAgIpHjx69/zBUX5k+MDBAt9vNY8eOsbu7m6lUigcOHKDL5WImkyHJz9TGYrEcALsMIPn69esZTdMIgM+ePUNXVxdu376NsrIyuN1uXLp0CWazGcPDw3C5XFBVFWfPnkVNTQ18Pp+ezWY5MzPzO4DfAABHjhzpJslUKqVdvHiR4+PjbG9vZy6XI0kuLS0xmUxSCEGS9Pv9LC0tpdFoZGVlpSaEoM/nuwIAKx/7q5GRkb9CoZBQVVWcP3+ez58/J0mm02kODg7ywoULjMViTKfTtNvtXLt2LTdt2qTncrnlsbGxLICvSUqfrl5HJBLh1NTUkhBCJ8mFhQX29/dTVVUWFBTwwYMH1HWdly9fpqIoeiKRWJqfn2d1dXWnLMuf7zMAHD16tGd+fn7FZy2bzYrKykodAAFQVVV9cXFRkNTevn3Lubk5trS0XPnfxHE4HN8ODw+nV/yanp6mx+Ohx+P5aIMQgmNjY3/W1tZ+t5rsSwG7+fjx4/76+vrm7du32woLC00AkE6n38fj8ZmHDx/+cuPGjR8BJL8YsCtYdQIMALYqilKvKEo9APuHty+egH8A3GfFDJXmxmMAAAAASUVORK5CYII%3D&link=https%3A%2F%2Fbook.getfoundry.sh%2F) -[gha-badge]: https://img.shields.io/github/actions/workflow/status/foundry-rs/foundry/test.yml?branch=master +[gha-badge]: https://img.shields.io/github/actions/workflow/status/foundry-rs/foundry/test.yml?branch=master&style=flat-square [gha-url]: https://github.com/foundry-rs/foundry/actions [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_rs [tg-url]: https://t.me/foundry_rs [tg-support-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=support&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_support [tg-support-url]: https://t.me/foundry_support -**[Install](https://book.getfoundry.sh/getting-started/installation)** -| [User Book][foundry-book] -| [Developer Docs](./docs/dev/README.md) +**[Install](https://getfoundry.sh/getting-started/installation)** +| [Docs][foundry-docs] +| [Benchmarks](https://www.getfoundry.sh/benchmarks) +| [Developer Guidelines](./docs/dev/README.md) | [Contributing](./CONTRIBUTING.md) +| [Crate Docs](https://foundry-rs.github.io/foundry) --- -### Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. +Blazing fast, portable and modular toolkit for Ethereum application development, written in Rust. -Foundry consists of: - -- [**Forge**](#forge): Build, test, fuzz, debug and deploy [Solidity][solidity] contracts, like Hardhat, Brownie, Ape. -- [**Cast**](#cast): A Swiss Army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- [**Anvil**](#anvil): Fast local Ethereum development node, akin to Hardhat Network, Tenderly. -- [**Chisel**](#chisel): Fast, utilitarian, and verbose Solidity REPL. - -**Need help getting started with Foundry? Read the [📖 Foundry Book][foundry-book]!** +- [**Forge**](https://getfoundry.sh/forge) — Build, test, fuzz, debug and deploy Solidity contracts. +- [**Cast**](https://getfoundry.sh/cast) — Swiss Army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- [**Anvil**](https://getfoundry.sh/anvil) — Fast local Ethereum development node. +- [**Chisel**](https://getfoundry.sh/chisel) — Fast, utilitarian and verbose Solidity REPL. ![Demo](.github/assets/demo.gif) -## Features - -- **High-Performance Compilation** - - - **Fast and Flexible**: Automatically detects and installs the required Solidity compiler version. - - **Solidity and Vyper Support**: Fully supports both Solidity and Vyper out-of-the-box. - - **Incremental Compilation**: Re-compiles only changed files, saving time. - - **Parallelized Pipeline**: Leverages multi-core systems for ultra-fast builds. - - **Broad Compatibility**: Supports non-standard directory structures, including [Hardhat repos](https://twitter.com/gakonst/status/1461289225337421829). - -- **Advanced Testing** - - - **No Context Switching**: Write tests directly in Solidity. - - **Fuzz Testing**: Quickly identify edge cases with input shrinking and counter-example generation. - - **Invariant Testing**: Ensure complex system properties hold across a wide range of inputs. - - **Debugging Made Easy**: Use [forge-std](https://github.com/foundry-rs/forge-std)'s `console.sol` for flexible debug logging. - - **Interactive Debugger**: Step through your Solidity code with Foundry's interactive debugger, making it easy to pinpoint issues. - -- **Powerful Runtime Features** - - - **RPC Forking**: Fast and efficient remote RPC forking backed by [Alloy][alloy]. - - **Lightweight & Portable**: No dependency on Nix or other package managers for installation. - -- **Streamlined CI/CD** - - - **Optimized CI**: Accelerate builds, run tests and execute scripts using [Foundry's GitHub action][foundry-gha]. - ## Installation -Getting started is very easy: - -Install `foundryup`: - -``` +```sh curl -L https://foundry.paradigm.xyz | bash -``` - -Next, run `foundryup`. - -It will automatically install the latest version of the precompiled binaries: [`forge`](#forge), [`cast`](#cast), [`anvil`](#anvil), and [`chisel`](#chisel). - -``` foundryup ``` -**Done!** - -For additional details see the [installation guide](https://book.getfoundry.sh/getting-started/installation) in the [Foundry Book][foundry-book]. - -If you're experiencing any issues while installing, check out [Getting Help](#getting-help) and the [FAQ](https://book.getfoundry.sh/faq). - -## How Fast? +See the [installation guide](https://getfoundry.sh/getting-started/installation) for more details. -Forge is quite fast at both compiling (leveraging `solc` with [foundry-compilers]) and testing. +To verify a downloaded release archive or container image, see [Verifying Releases](./SECURITY.md#verifying-releases). -See the benchmarks below. Older benchmarks against [DappTools][dapptools] can be found in the [v0.2.0 announcement post][benchmark-post] and in the [Convex Shutdown Simulation][convex] repository. - -### Testing Benchmarks - -| Project | Type | [Forge 1.0][foundry-1.0] | [Forge 0.2][foundry-0.2] | DappTools | Speedup | -| --------------------------------------------- | -------------------- | ------------------------ | ------------------------ | --------- | -------------- | -| [vectorized/solady][solady] | Unit / Fuzz | 0.9s | 2.3s | - | 2.6x | -| [morpho-org/morpho-blue][morpho-blue] | Invariant | 0.7s | 1m43s | - | 147.1x | -| [morpho-org/morpho-blue-oracles][morpho-blue] | Integration (Cold) | 6.1s | 6.3s | - | 1.04x | -| [morpho-org/morpho-blue-oracles][morpho-blue] | Integration (Cached) | 0.6s | 0.9s | - | 1.50x | -| [transmissions11/solmate][solmate] | Unit / Fuzz | 2.7s | 2.8s | 6m34s | 1.03x / 140.0x | -| [reflexer-labs/geb][geb] | Unit / Fuzz | 0.2s | 0.4s | 23s | 2.0x / 57.5x | - -_In the above benchmarks, compilation was always skipped_ - -**Takeaway: Forge dramatically outperforms the competition, delivering blazing-fast execution speeds while continuously expanding its robust feature set.** - -### Compilation Benchmarks - -
- - - - +## Getting Started - - - - - -  - -
- -**Takeaway: Forge compilation is consistently faster than Hardhat by a factor of `2.1x` to `5.2x`, depending on the amount of caching involved.** - -## Forge - -Forge helps you build, test, fuzz, debug and deploy Solidity contracts. - -The best way to understand Forge is to simply try it (in less than 30 seconds!). - -First, let's initialize a new `counter` example repository: - -```sh -forge init counter -``` - -Next `cd` into `counter` and build : +Initialize a new project, build and test: ```sh +forge init counter && cd counter forge build -``` - -```console -[⠊] Compiling... -[⠔] Compiling 27 files with Solc 0.8.28 -[⠒] Solc 0.8.28 finished in 452.13ms -Compiler run successful! -``` - -Let's [test](https://book.getfoundry.sh/forge/tests#tests) our contracts: - -```sh forge test ``` -```console -[⠊] Compiling... -No files changed, compilation skipped - -Ran 2 tests for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 31121, ~: 31277) -[PASS] test_Increment() (gas: 31293) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.35ms (4.86ms CPU time) - -Ran 1 test suite in 5.91ms (5.35ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) -``` - -Finally, let's run our deployment script: - -```sh -forge script script/Counter.s.sol -``` - -```console -[⠊] Compiling... -No files changed, compilation skipped -Script ran successfully. -Gas used: 109037 - -If you wish to simulate on-chain transactions pass a RPC URL. -``` - -Run `forge --help` to explore the full list of available subcommands and their usage. - -More documentation can be found in the [forge][foundry-book-forge] section of the Foundry Book. - -## Cast - -Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. - -Here are a few examples of what you can do: - -**Check the latest block on Ethereum Mainnet**: +Interact with a live network: ```sh cast block-number --rpc-url https://eth.merkle.io -``` - -**Check the Ether balance of `vitalik.eth`** - -```sh cast balance vitalik.eth --ether --rpc-url https://eth.merkle.io ``` -**Replay and trace a transaction** - -```sh -cast run 0x9c32042f5e997e27e67f82583839548eb19dc78c4769ad6218657c17f2a5ed31 --rpc-url https://eth.merkle.io -``` - -Optionally, pass `--etherscan-api-key ` to decode transaction traces using verified source maps, providing more detailed and human-readable information. - ---- - -Run `cast --help` to explore the full list of available subcommands and their usage. - -More documentation can be found in the [cast][foundry-book-cast] section of the Foundry Book. - -## Anvil - -Anvil is a fast local Ethereum development node. - -Let's fork Ethereum mainnet at the latest block: +Fork mainnet locally: ```sh anvil --fork-url https://eth.merkle.io ``` -You can use those same `cast` subcommands against your `anvil` instance: - -```sh -cast block-number -``` - ---- - -Run `anvil --help` to explore the full list of available features and their usage. - -More documentation can be found in the [anvil][foundry-book-anvil] section of the Foundry Book. - -## Chisel - -Chisel is a fast, utilitarian, and verbose Solidity REPL. - -To use Chisel, simply type `chisel`. - -```sh -chisel -``` - -From here, start writing Solidity code! Chisel will offer verbose feedback on each input. - -Create a variable `a` and query it: - -```console -➜ uint256 a = 123; -➜ a -Type: uint256 -├ Hex: 0x7b -├ Hex (full word): 0x000000000000000000000000000000000000000000000000000000000000007b -└ Decimal: 123 -``` - -Finally, run `!source` to see `a` was applied: - -```solidity -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; - -import {Vm} from "forge-std/Vm.sol"; - -contract REPL { - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - - /// @notice REPL contract entry point - function run() public { - uint256 a = 123; - } -} -``` - ---- - -Run `chisel --help` to explore the full list of available features and their usage. - -More documentation can be found in the [chisel][foundry-book-chisel] section of the Foundry Book. - -## Configuration - -Foundry is highly configurable, allowing you to tailor it to your needs. Configuration is managed via a file called [`foundry.toml`](./crates/config) located in the root of your project or any parent directory. For a full list of configuration options, refer to the [config package documentation](./crates/config/README.md#all-options). - -**Profiles and Namespaces** - -- Configuration can be organized into **profiles**, which are arbitrarily namespaced for flexibility. -- The default profile is named `default`. Learn more in the [Default Profile section](./crates/config/README.md#default-profile). -- To select a different profile, set the `FOUNDRY_PROFILE` environment variable. -- Override specific settings using environment variables prefixed with `FOUNDRY_` (e.g., `FOUNDRY_SRC`). - ---- - -You can find additional [setup and configurations guides][foundry-book-config] in the [Foundry Book][foundry-book] and in the [config crate](./crates/config/README.md): - -- [Configuring with `foundry.toml`](https://book.getfoundry.sh/config/) -- [Setting up VSCode][vscode-setup] -- [Shell autocompletions][shell-setup] +Read the [Foundry Docs][foundry-docs] to learn more. ## Contributing -See our [contributing guidelines](./CONTRIBUTING.md). +Contributions are welcome and highly appreciated. To get started, check out the [contributing guidelines](./CONTRIBUTING.md). -## Getting Help +Join our [Telegram][tg-url] to chat about the development of Foundry. -First, see if the answer to your question can be found in the [Foundy Book][foundry-book], or in the relevant crate. +## Support -If the answer is not there: +Having trouble? Check the [Foundry Docs][foundry-docs], join the [support Telegram][tg-support-url], or [open an issue](https://github.com/foundry-rs/foundry/issues/new). -- Join the [support Telegram][tg-support-url] to get help, or -- Open a [discussion](https://github.com/foundry-rs/foundry/discussions/new) with your question, or -- Open an issue with [the bug](https://github.com/foundry-rs/foundry/issues/new) +#### License -If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/foundry_rs) to chat with us about the development of Foundry! + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + -## License - -Licensed under either of [Apache License](./LICENSE-APACHE), Version -2.0 or [MIT License](./LICENSE-MIT) at your option. +
+ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in these crates by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + -## Acknowledgements - -- Foundry is a clean-room rewrite of the testing framework [DappTools][dapptools]. None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] (now [foundry-compilers]) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. -- [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. -- [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. -- All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs), [alloy][alloy] & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. - -[solidity]: https://soliditylang.org/ -[foundry-book]: https://book.getfoundry.sh -[foundry-book-config]: https://book.getfoundry.sh/config/ -[foundry-book-forge]: https://book.getfoundry.sh/reference/forge/ -[foundry-book-anvil]: https://book.getfoundry.sh/reference/anvil/ -[foundry-book-cast]: https://book.getfoundry.sh/reference/cast/ -[foundry-book-chisel]: https://book.getfoundry.sh/reference/chisel/ -[foundry-gha]: https://github.com/foundry-rs/foundry-toolchain -[foundry-compilers]: https://github.com/foundry-rs/compilers -[ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ -[solady]: https://github.com/Vectorized/solady -[openzeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v5.1 -[morpho-blue]: https://github.com/morpho-org/morpho-blue -[foundry-compilers]: https://github.com/foundry-rs/compilers -[solmate]: https://github.com/transmissions11/solmate/ -[geb]: https://github.com/reflexer-labs/geb -[benchmark-post]: https://www.paradigm.xyz/2022/03/foundry-02#blazing-fast-compilation--testing -[convex]: https://github.com/mds1/convex-shutdown-simulation -[vscode-setup]: https://book.getfoundry.sh/config/vscode.html -[shell-setup]: https://book.getfoundry.sh/config/shell-autocompletion.html -[foundry-0.2]: https://github.com/foundry-rs/foundry/releases/tag/nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a -[foundry-1.0]: https://github.com/foundry-rs/foundry/releases/tag/nightly-59f354c179f4e7f6d7292acb3d068815c79286d1 -[dapptools]: https://github.com/dapphub/dapptools -[alloy]: https://github.com/alloy-rs/alloy +[foundry-docs]: https://getfoundry.sh diff --git a/SECURITY.md b/SECURITY.md index bea27ad1140c0..6296066db5e73 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,113 @@ ## Reporting a Vulnerability -Contact [security@ithaca.xyz](mailto:security@ithaca.xyz). +Contact [security@tempo.xyz](mailto:security@tempo.xyz). + +## Verifying Releases + +Every official Foundry release ships with multiple, independent integrity +artifacts. All signing is keyless via [Sigstore](https://www.sigstore.dev/) — +no Foundry-managed key material is involved, and every signature is recorded +in the public [Rekor](https://docs.sigstore.dev/logging/overview/) transparency +log. The signing identity is the GitHub Actions OIDC token of this repository's +`release.yml` / `docker-publish.yml` workflows. + +### Per-release artifacts + +For each `foundry___.{tar.gz,zip}` archive on the +[releases page](https://github.com/foundry-rs/foundry/releases), the same +release also publishes: + +| Suffix | Purpose | +| --- | --- | +| `.sha256` | SHA-256 checksum of the archive (`sha256sum` format) | +| `.sigstore.json` | Cosign keyless signature bundle (cert + signature + Rekor proof) over the archive | +| `.spdx.json` | SPDX 2.3 SBOM of the source workspace used for the build | +| `.attestation.txt` | URL of the GitHub artifact-attestation summary | + +In addition, GitHub stores SLSA build-provenance and SBOM attestations against +the archive's digest; these are queryable via `gh attestation` without +downloading anything else. + +### Verifying an archive + +Pick whichever toolchain you have available — they verify the same signatures. + +#### Option 1: GitHub CLI (simplest) + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry +``` + +This computes the file's digest, fetches the matching attestation from GitHub, +and verifies the Sigstore signature plus the SLSA provenance predicate. Add +`--signer-workflow foundry-rs/foundry/.github/workflows/release.yml` to also +require the workflow identity. + +To verify the SBOM attestation specifically: + +```bash +gh attestation verify foundry_v1.4.0_linux_amd64.tar.gz \ + --repo foundry-rs/foundry \ + --predicate-type 'https://spdx.dev/Document/v2.3' +``` + +#### Option 2: Cosign (offline-friendly) + +Download the archive and its `.sigstore.json` bundle from the release page, +then: + +```bash +cosign verify-blob \ + --bundle foundry_v1.4.0_linux_amd64.sigstore.json \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/release\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ + foundry_v1.4.0_linux_amd64.tar.gz +``` + +For nightly builds the certificate identity points at `refs/heads/master` +instead of a tag; the regex above matches both. + +#### Option 3: Plain checksum (integrity only) + +```bash +sha256sum -c foundry_v1.4.0_linux_amd64.sha256 # GNU coreutils +shasum -a 256 -c foundry_v1.4.0_linux_amd64.sha256 # macOS +``` + +This proves the bytes match what was uploaded, but says nothing about who +uploaded them. Combine with one of the verifications above for end-to-end +trust. + +### Verifying the Docker image + +Container signatures and attestations are pushed as OCI referrers to GHCR, so +no separate files need to be downloaded. + +```bash +# Cosign keyless signature on the image +cosign verify ghcr.io/foundry-rs/foundry:v1.4.0 \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' + +# SLSA build-provenance attestation +gh attestation verify oci://ghcr.io/foundry-rs/foundry:v1.4.0 \ + --repo foundry-rs/foundry + +# Inspect the buildx-attached SBOM and provenance +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .SBOM }}' +docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 \ + --format '{{ json .Provenance }}' +``` + +To pin to an immutable digest (recommended for reproducible deployments): + +```bash +docker pull ghcr.io/foundry-rs/foundry:v1.4.0 +DIGEST=$(docker buildx imagetools inspect ghcr.io/foundry-rs/foundry:v1.4.0 --format '{{ .Manifest.Digest }}') +cosign verify "ghcr.io/foundry-rs/foundry@${DIGEST}" \ + --certificate-identity-regexp '^https://github.com/foundry-rs/foundry/\.github/workflows/(release|docker-publish)\.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000000000..0b7b6c4c19039 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "foundry-bench" +description = "Foundry benchmark runner" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[[bin]] +name = "foundry-bench" +path = "src/main.rs" + +[dependencies] +foundry-test-utils.workspace = true +foundry-common.workspace = true +foundry-compilers = { workspace = true, features = ["project-util"] } +eyre.workspace = true +color-eyre.workspace = true +serde.workspace = true +serde_json.workspace = true +chrono = { version = "0.4", features = ["serde"] } +rayon.workspace = true +clap = { version = "4", features = ["derive"] } +once_cell = "1.21" diff --git a/benches/LATEST.md b/benches/LATEST.md new file mode 100644 index 0000000000000..cb75f8d68780b --- /dev/null +++ b/benches/LATEST.md @@ -0,0 +1,108 @@ +# 📊 Foundry Benchmark Results + +**Generated at**: 2026-05-02 21:53:46 UTC + +## Forge Test + +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.78 s | 0.965 s | +| vectorized-solady | 0.995 s | 0.645 s | +| uniswap-v4-core | 5.97 s | 1.51 s | +| sparkdotfi-spark-psm | 19.98 s | 10.20 s | + +## Forge Fuzz Test + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 2.54 s | 0.923 s | +| vectorized-solady | 0.929 s | 0.617 s | +| uniswap-v4-core | 6.44 s | 1.40 s | +| sparkdotfi-spark-psm | 2.25 s | 2.03 s | + +## Forge Test (Isolated) + +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 3.05 s | 1.02 s | +| vectorized-solady | 0.871 s | 0.741 s | +| uniswap-v4-core | 6.81 s | 1.68 s | +| sparkdotfi-spark-psm | 21.96 s | 11.26 s | + +## Forge Build + +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [vectorized/solady](https://github.com/vectorized/solady) +3. [uniswap/v4-core](https://github.com/uniswap/v4-core) +4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +### No Cache + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 34.58 s | 33.29 s | +| vectorized-solady | 14.40 s | 14.41 s | +| uniswap-v4-core | 2m 17.6s | 2m 17.7s | +| sparkdotfi-spark-psm | 12.62 s | 12.61 s | + +### With Cache + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 0.083 s | 0.089 s | +| vectorized-solady | 0.062 s | 0.064 s | +| uniswap-v4-core | 0.071 s | 0.074 s | +| sparkdotfi-spark-psm | 0.066 s | 0.068 s | + +## Forge Coverage + +### Repositories Tested + +1. [ithacaxyz/account](https://github.com/ithacaxyz/account) +2. [uniswap/v4-core](https://github.com/uniswap/v4-core) +3. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) +### Foundry Versions + +- **v1.5.1**: forge Version: 1.5.1-v1.5.1 (b0a9dd9 2025-12-19) +- **v1.7.0**: forge Version: 1.6.0-v1.7.0 (f83bad9 2026-04-28) + +| Repository | v1.5.1 | v1.7.0 | +|------------|----------|----------| +| ithacaxyz-account | 29.35 s | 18.69 s | +| uniswap-v4-core | 1m 26.8s | 1m 4.1s | +| sparkdotfi-spark-psm | 2m 1.6s | 1m 28.4s | + +## System Information + + +- **OS**: linux +- **CPU**: 32 +- **Rustc**: rustc 1.95.0 (59807616e 2026-04-14) diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 0000000000000..fee254a18067b --- /dev/null +++ b/benches/README.md @@ -0,0 +1,132 @@ +# Foundry Benchmarks + +This directory contains performance benchmarks for Foundry commands across multiple repositories and Foundry versions. + +## Prerequisites + +Before running the benchmarks, ensure you have the following installed: + +1. **Rust and Cargo** - Required for building the benchmark binary + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +2. **Foundryup** - The Foundry toolchain installer + + ```bash + curl -L https://foundry.paradigm.xyz | bash + foundryup + ``` + +3. **Git** - For cloning benchmark repositories + +4. [**Hyperfine**](https://github.com/sharkdp/hyperfine/blob/master/README.md) - The benchmarking tool used by foundry-bench + +5. **Node.js and npm** - Some repositories require npm dependencies + +## Running Benchmarks + +Build and install the benchmark runner: + +```bash +cargo build --release --bin foundry-bench +``` + +To install `foundry-bench` to your PATH: + +```bash +cd benches && cargo install --path . --bin foundry-bench +``` + +#### Run with default settings + +```bash +# Run all benchmarks on default repos with stable and nightly versions +foundry-bench --versions stable,nightly +``` + +#### Run with custom configurations + +```bash +# Bench specific versions +foundry-bench --versions stable,nightly,v1.0.0 + +# Run on specific repositories. Default rev for the repo is "main" +foundry-bench --repos ithacaxyz/account,Vectorized/solady + +# Test specific repository with custom revision +foundry-bench --repos ithacaxyz/account:main,Vectorized/solady:v0.0.123 + +# Run only specific benchmarks +foundry-bench --benchmarks forge_build_with_cache,forge_test + +# Run only fuzz tests +foundry-bench --benchmarks forge_fuzz_test + +# Run coverage benchmark +foundry-bench --benchmarks forge_coverage + +# Combine options +foundry-bench \ + --versions stable,nightly \ + --repos ithacaxyz/account \ + --benchmarks forge_build_with_cache + +# Force install Foundry versions +foundry-bench --force-install + +# Verbose output to see hyperfine logs +foundry-bench --verbose + +# Output to specific directory +foundry-bench --output-dir ./results --output-file LATEST_RESULTS.md +``` + +#### Command-line Options + +- `--versions ` - Comma-separated list of Foundry versions (default: stable,nightly) +- `--repos ` - Comma-separated list of repos in org/repo[:rev] format (default: ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22) +- `--benchmarks ` - Comma-separated list of benchmarks to run +- `--force-install` - Force installation of Foundry versions +- `--verbose` - Show detailed benchmark output +- `--output-dir ` - Directory for output files (default: benches) +- `--output-file ` - Name of the output file (default: LATEST.md) + +## Benchmark Structure + +- `forge_test` - Benchmarks `forge test` command across repos +- `forge_build_no_cache` - Benchmarks `forge build` with clean cache +- `forge_build_with_cache` - Benchmarks `forge build` with existing cache +- `forge_fuzz_test` - Benchmarks `forge test` with only fuzz tests (tests with parameters) +- `forge_coverage` - Benchmarks `forge coverage --ir-minimum` command across repos + +## Configuration + +The benchmark binary uses command-line arguments to configure which repositories and versions to test. The default repositories are: + +- `ithacaxyz/account:v0.3.2` +- `Vectorized/solady:v0.1.22` + +You can override these using the `--repos` flag with the format `org/repo[:rev]`. + +## Results + +Benchmark results are saved to `benches/LATEST.md` (or custom output file specified with `--output-file`). The report includes: + +- Summary of versions and repositories tested +- Performance comparison tables for each benchmark type showing: + - Mean execution time + - Min/Max times + - Standard deviation + - Relative performance comparison between versions +- System information (OS, CPU cores) +- Detailed hyperfine benchmark results in JSON format + +## Troubleshooting + +1. **Foundry version not found**: Use `--force-install` flag or manually install with `foundryup --install ` +2. **Repository clone fails**: Check network connectivity and repository access +3. **Build failures**: Some repositories may have specific dependencies - check their README files +4. **Hyperfine not found**: Install hyperfine using the instructions in Prerequisites +5. **npm/Node.js errors**: Ensure Node.js and npm are installed for repositories that require them diff --git a/benches/src/lib.rs b/benches/src/lib.rs new file mode 100644 index 0000000000000..8faaa8c87ccff --- /dev/null +++ b/benches/src/lib.rs @@ -0,0 +1,531 @@ +//! Foundry benchmark runner. + +use crate::results::{HyperfineOutput, HyperfineResult}; +use eyre::{Result, WrapErr}; +use foundry_common::{sh_eprintln, sh_println}; +use foundry_compilers::project_util::TempProject; +use foundry_test_utils::util::clone_remote; +use once_cell::sync::Lazy; +use std::{ + path::{Path, PathBuf}, + process::Command, + str::FromStr, +}; + +pub mod results; + +/// Default number of runs for benchmarks +pub const RUNS: u32 = 5; + +/// Configuration for repositories to benchmark +#[derive(Debug, Clone)] +pub struct RepoConfig { + pub name: String, + pub org: String, + pub repo: String, + pub rev: String, + /// Optional extra arguments appended to every benchmark command for this + /// repo (e.g. `--nmc BrokenTest` to skip a broken test contract). + pub extra_args: Option, +} + +impl FromStr for RepoConfig { + type Err = eyre::Error; + + /// Parse a repo spec of the form `org/repo[:rev][ ]`. + /// + /// Anything after the first whitespace is treated as extra arguments + /// appended to every benchmark command for this repo. + fn from_str(spec: &str) -> Result { + let spec = spec.trim(); + // Anything after the first whitespace is per-repo extra args. + let (head, extra_args) = match spec.split_once(char::is_whitespace) { + Some((head, rest)) => (head, Some(rest.trim().to_string())), + None => (spec, None), + }; + + let (repo_path, custom_rev) = match head.split_once(':') { + Some((path, rev)) => (path, Some(rev)), + None => (head, None), + }; + + let (org, repo) = repo_path.split_once('/').ok_or_else(|| { + eyre::eyre!("Invalid repo format '{spec}'. Expected 'org/repo' or 'org/repo:rev'") + })?; + + // Inherit defaults from BENCHMARK_REPOS when available, otherwise build + // a fresh config. Custom rev / extra args always override. + let mut config = BENCHMARK_REPOS + .iter() + .find(|r| r.org == org && r.repo == repo) + .cloned() + .unwrap_or_else(|| Self { + name: format!("{org}-{repo}"), + org: org.to_string(), + repo: repo.to_string(), + rev: "main".to_string(), + extra_args: None, + }); + + if let Some(rev) = custom_rev { + config.rev = rev.to_string(); + } + config.extra_args = extra_args; + + let _ = sh_println!("Parsed repo spec '{spec}' -> {config:?}"); + Ok(config) + } +} + +/// Available repositories for benchmarking +pub fn default_benchmark_repos() -> Vec { + vec![ + RepoConfig { + name: "ithacaxyz-account".to_string(), + org: "ithacaxyz".to_string(), + repo: "account".to_string(), + rev: "main".to_string(), + extra_args: None, + }, + RepoConfig { + name: "solady".to_string(), + org: "Vectorized".to_string(), + repo: "solady".to_string(), + rev: "main".to_string(), + extra_args: None, + }, + ] +} + +// Keep a lazy static for compatibility +pub static BENCHMARK_REPOS: Lazy> = Lazy::new(default_benchmark_repos); + +/// Foundry versions to benchmark +/// +/// To add more versions for comparison, install them first: +/// ```bash +/// foundryup --install stable +/// foundryup --install nightly +/// foundryup --install v0.2.0 # Example specific version +/// ``` +/// +/// Then add the version strings to this array. Supported formats: +/// - "stable" - Latest stable release +/// - "nightly" - Latest nightly build +/// - "v0.2.0" - Specific version tag +/// - "commit-hash" - Specific commit hash +/// - "nightly-rev" - Nightly build with specific revision +pub static FOUNDRY_VERSIONS: &[&str] = &["stable", "nightly"]; + +/// A benchmark project that represents a cloned repository ready for testing +pub struct BenchmarkProject { + pub name: String, + pub temp_project: TempProject, + pub root_path: PathBuf, + /// Optional extra arguments appended to every benchmark command. + pub extra_args: Option, +} + +impl BenchmarkProject { + /// Set up a benchmark project by cloning the repository + #[allow(unused_must_use)] + pub fn setup(config: &RepoConfig) -> Result { + let temp_project = + TempProject::dapptools().wrap_err("Failed to create temporary project")?; + + // Get root path before clearing + let root_path = temp_project.root().to_path_buf(); + let root = root_path.to_str().unwrap(); + + // Remove all files in the directory + let root_path = root_path.canonicalize()?; + for entry in std::fs::read_dir(&root_path)? { + let entry = entry?; + let path = entry.path(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(root_path.canonicalize().unwrap_or_else(|_| root_path.clone())) { + sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); + } else { + std::fs::remove_file(&canon).ok(); + } + } + + // Clone the repository + let repo_url = format!("https://github.com/{}/{}.git", config.org, config.repo); + clone_remote(&repo_url, root, true); + + // Checkout specific revision if provided + if !config.rev.is_empty() && config.rev != "main" && config.rev != "master" { + let status = Command::new("git") + .current_dir(root) + .args(["checkout", &config.rev]) + .status() + .wrap_err("Failed to checkout revision")?; + + if !status.success() { + eyre::bail!("Git checkout failed for {}", config.name); + } + } + + // Git submodules are already cloned via --recursive flag + // But npm dependencies still need to be installed + Self::install_npm_dependencies(&root_path)?; + + sh_println!(" ✅ Project {} setup complete at {}", config.name, root); + Ok(Self { + name: config.name.clone(), + root_path, + temp_project, + extra_args: config.extra_args.clone(), + }) + } + + /// Append `self.extra_args` to a benchmark shell command, if any. + fn cmd(&self, base: &str) -> String { + match self.extra_args.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + Some(extra) => format!("{base} {extra}"), + None => base.to_string(), + } + } + + /// Install npm dependencies if package.json exists + #[allow(unused_must_use)] + fn install_npm_dependencies(root: &Path) -> Result<()> { + if root.join("package.json").exists() { + sh_println!(" 📦 Running npm install..."); + let status = Command::new("npm") + .current_dir(root) + .args(["install"]) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .wrap_err("Failed to run npm install")?; + + if status.success() { + sh_println!(" ✅ npm install completed successfully"); + } else { + sh_println!( + " ⚠️ Warning: npm install failed with exit code: {:?}", + status.code() + ); + } + } + Ok(()) + } + + /// Run a command with hyperfine and return the results + /// + /// # Arguments + /// * `benchmark_name` - Name of the benchmark for organizing output + /// * `version` - Foundry version being benchmarked + /// * `command` - The command to benchmark + /// * `runs` - Number of runs to perform + /// * `setup` - Optional setup command to run before the benchmark series (e.g., "forge build") + /// * `prepare` - Optional prepare command to run before each timing run (e.g., "forge clean") + /// * `conclude` - Optional conclude command to run after each timing run (e.g., cleanup) + /// * `verbose` - Whether to show command output + /// + /// # Hyperfine flags used: + /// * `--runs` - Number of timing runs + /// * `--setup` - Execute before the benchmark series (not before each run) + /// * `--prepare` - Execute before each timing run + /// * `--conclude` - Execute after each timing run + /// * `--export-json` - Export results to JSON for parsing + /// * `--shell=bash` - Use bash for shell command execution + /// * `--show-output` - Show command output (when verbose) + #[allow(clippy::too_many_arguments)] + fn hyperfine( + &self, + benchmark_name: &str, + version: &str, + command: &str, + runs: u32, + setup: Option<&str>, + prepare: Option<&str>, + conclude: Option<&str>, + verbose: bool, + ) -> Result { + // Create structured temp directory for JSON output + // Format: ////.json + let temp_dir = std::env::temp_dir(); + let json_dir = + temp_dir.join("foundry-bench").join(benchmark_name).join(version).join(&self.name); + std::fs::create_dir_all(&json_dir)?; + + let json_path = json_dir.join(format!("{benchmark_name}.json")); + + // Build hyperfine command + let mut hyperfine_cmd = Command::new("hyperfine"); + hyperfine_cmd + .current_dir(&self.root_path) + .arg("--runs") + .arg(runs.to_string()) + .arg("--export-json") + .arg(&json_path) + .arg("--shell=bash"); + + // Add optional setup command + if let Some(setup_cmd) = setup { + hyperfine_cmd.arg("--setup").arg(setup_cmd); + } + + // Add optional prepare command + if let Some(prepare_cmd) = prepare { + hyperfine_cmd.arg("--prepare").arg(prepare_cmd); + } + + // Add optional conclude command + if let Some(conclude_cmd) = conclude { + hyperfine_cmd.arg("--conclude").arg(conclude_cmd); + } + + if verbose { + hyperfine_cmd.arg("--show-output"); + hyperfine_cmd.stderr(std::process::Stdio::inherit()); + hyperfine_cmd.stdout(std::process::Stdio::inherit()); + } + + // Add the benchmark command last + hyperfine_cmd.arg(command); + + let status = hyperfine_cmd.status().wrap_err("Failed to run hyperfine")?; + if !status.success() { + eyre::bail!("Hyperfine failed for command: {}", command); + } + + // Read and parse the JSON output + let json_content = std::fs::read_to_string(json_path)?; + let output: HyperfineOutput = serde_json::from_str(&json_content)?; + + // Extract the first result (we only run one command at a time) + output.results.into_iter().next().ok_or_else(|| eyre::eyre!("No results from hyperfine")) + } + + /// Benchmark forge test + pub fn bench_forge_test( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + // Build before running tests + self.hyperfine( + "forge_test", + version, + &self.cmd("forge test"), + runs, + Some("forge build"), + None, + None, + verbose, + ) + } + + /// Benchmark forge build with cache + pub fn bench_forge_build_with_cache( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + self.hyperfine( + "forge_build_with_cache", + version, + &self.cmd("FOUNDRY_LINT_LINT_ON_BUILD=false forge build"), + runs, + None, + Some("forge build"), + None, + verbose, + ) + } + + /// Benchmark forge build without cache + pub fn bench_forge_build_no_cache( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + // Clean before each timing run + self.hyperfine( + "forge_build_no_cache", + version, + &self.cmd("FOUNDRY_LINT_LINT_ON_BUILD=false forge build"), + runs, + Some("forge clean"), + None, + Some("forge clean"), + verbose, + ) + } + + /// Benchmark forge fuzz tests + pub fn bench_forge_fuzz_test( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + // Build before running fuzz tests + self.hyperfine( + "forge_fuzz_test", + version, + &self.cmd(r#"forge test --match-test "test[^(]*\([^)]+\)""#), + runs, + Some("forge build"), + None, + None, + verbose, + ) + } + + /// Benchmark forge coverage + pub fn bench_forge_coverage( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + // No setup needed, forge coverage builds internally + // Use --ir-minimum to avoid "Stack too deep" errors + self.hyperfine( + "forge_coverage", + version, + &self.cmd("forge coverage --ir-minimum"), + runs, + None, + None, + None, + verbose, + ) + } + + /// Benchmark forge test with --isolate flag + pub fn bench_forge_isolate_test( + &self, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + // Build before running tests + self.hyperfine( + "forge_isolate_test", + version, + &self.cmd("forge test --isolate"), + runs, + Some("forge build"), + None, + None, + verbose, + ) + } + + /// Get the root path of the project + pub fn root(&self) -> &Path { + &self.root_path + } + + /// Run a specific benchmark by name + pub fn run( + &self, + benchmark: &str, + version: &str, + runs: u32, + verbose: bool, + ) -> Result { + match benchmark { + "forge_test" => self.bench_forge_test(version, runs, verbose), + "forge_build_no_cache" => self.bench_forge_build_no_cache(version, runs, verbose), + "forge_build_with_cache" => self.bench_forge_build_with_cache(version, runs, verbose), + "forge_fuzz_test" => self.bench_forge_fuzz_test(version, runs, verbose), + "forge_coverage" => self.bench_forge_coverage(version, runs, verbose), + "forge_isolate_test" => self.bench_forge_isolate_test(version, runs, verbose), + _ => eyre::bail!("Unknown benchmark: {}", benchmark), + } + } +} + +/// Switch to a specific foundry version +#[allow(unused_must_use)] +pub fn switch_foundry_version(version: &str) -> Result<()> { + let output = Command::new("foundryup") + .args(["--use", version]) + .output() + .wrap_err("Failed to run foundryup")?; + + // Check if the error is about forge --version failing + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("command failed") && stderr.contains("forge --version") { + eyre::bail!( + "Foundry binaries maybe corrupted. Please reinstall by running `foundryup --install `" + ); + } + + if !output.status.success() { + sh_eprintln!("foundryup stderr: {stderr}"); + eyre::bail!("Failed to switch to foundry version: {}", version); + } + + sh_println!(" Successfully switched to version: {version}"); + Ok(()) +} + +/// Get the current forge version +pub fn get_forge_version() -> Result { + let output = Command::new("forge") + .args(["--version"]) + .output() + .wrap_err("Failed to get forge version")?; + + if !output.status.success() { + eyre::bail!("forge --version failed"); + } + + let version = + String::from_utf8(output.stdout).wrap_err("Invalid UTF-8 in forge version output")?; + + Ok(version.lines().next().unwrap_or("unknown").to_string()) +} + +/// Get the full forge version details including commit hash and date +pub fn get_forge_version_details() -> Result { + let output = Command::new("forge") + .args(["--version"]) + .output() + .wrap_err("Failed to get forge version")?; + + if !output.status.success() { + eyre::bail!("forge --version failed"); + } + + let full_output = + String::from_utf8(output.stdout).wrap_err("Invalid UTF-8 in forge version output")?; + + // Extract relevant lines and format them + let lines: Vec<&str> = full_output.lines().collect(); + if lines.len() >= 3 { + // Extract version, commit, and timestamp + let version = lines[0].trim(); + let commit = lines[1].trim().replace("Commit SHA: ", ""); + let timestamp = lines[2].trim().replace("Build Timestamp: ", ""); + + // Format as: "forge 1.2.3-nightly (51650ea 2025-06-27)" + let short_commit = &commit[..7]; // First 7 chars of commit hash + let date = timestamp.split('T').next().unwrap_or(×tamp); + + Ok(format!("{version} ({short_commit} {date})")) + } else { + // Fallback to just the first line if format is unexpected + Ok(lines.first().unwrap_or(&"unknown").to_string()) + } +} diff --git a/benches/src/main.rs b/benches/src/main.rs new file mode 100644 index 0000000000000..8d7134b1c25bc --- /dev/null +++ b/benches/src/main.rs @@ -0,0 +1,270 @@ +use clap::Parser; +use eyre::{Result, WrapErr}; +use foundry_bench::{ + BENCHMARK_REPOS, BenchmarkProject, FOUNDRY_VERSIONS, RUNS, RepoConfig, get_forge_version, + get_forge_version_details, + results::{BenchmarkResults, HyperfineResult}, + switch_foundry_version, +}; +use foundry_common::sh_println; +use rayon::prelude::*; +use std::{fs, path::PathBuf, process::Command, sync::Mutex}; + +const ALL_BENCHMARKS: [&str; 6] = [ + "forge_test", + "forge_build_no_cache", + "forge_build_with_cache", + "forge_fuzz_test", + "forge_coverage", + "forge_isolate_test", +]; + +/// Foundry Benchmark Runner +#[derive(Parser, Debug)] +#[clap(name = "foundry-bench", about = "Run Foundry benchmarks across multiple versions")] +struct Cli { + /// Comma-separated list of Foundry versions to test (e.g., stable,nightly,v1.2.0) + #[clap(long, value_delimiter = ',')] + versions: Option>, + + /// Force install Foundry versions + #[clap(long)] + force_install: bool, + + /// Show verbose output + #[clap(long)] + verbose: bool, + + /// Directory where the aggregated benchmark results will be written. + #[clap(long, default_value = ".")] + output_dir: PathBuf, + + /// Name of the output file. Defaults to LATEST.md unless --json-output is set + /// without this flag, in which case no Markdown is written. + #[clap(long)] + output_file: Option, + + /// Filename for a flat JSON summary (benchmark/repo -> mean_seconds). + /// Resolved relative to --output-dir. Used by the nightly regression comparison script. + #[clap(long)] + json_output: Option, + + /// Run only specific benchmarks (comma-separated: + /// forge_test,forge_build_no_cache,forge_build_with_cache,forge_fuzz_test,forge_coverage) + #[clap(long, value_delimiter = ',')] + benchmarks: Option>, + + /// Comma-separated list of repositories to benchmark. + /// + /// Each entry has the form `org/repo[:rev][ ]`. Anything + /// after the first whitespace is appended to every benchmark command for + /// that repo (handy to skip a broken test contract via e.g. + /// `--nmc BrokenTest`). + /// + /// Examples: + /// `ithacaxyz/account:v0.5.7` + /// `vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest'` + #[clap(long, value_delimiter = ',')] + repos: Option>, +} + +/// Mutex to prevent concurrent foundryup calls +static FOUNDRY_LOCK: Mutex<()> = Mutex::new(()); +fn switch_version_safe(version: &str) -> Result<()> { + let _lock = FOUNDRY_LOCK.lock().unwrap(); + switch_foundry_version(version) +} + +#[allow(unused_must_use)] +fn main() -> Result<()> { + color_eyre::install()?; + let cli = Cli::parse(); + + // Check if hyperfine is installed + let hyperfine_check = Command::new("hyperfine").arg("--version").output(); + if hyperfine_check.is_err() || !hyperfine_check.unwrap().status.success() { + eyre::bail!( + "hyperfine is not installed. Please install it first: https://github.com/sharkdp/hyperfine" + ); + } + + // Determine versions to test + let versions = if let Some(v) = cli.versions { + v + } else { + FOUNDRY_VERSIONS.iter().map(|&s| s.to_string()).collect() + }; + + // Get repo configurations + let repos = if let Some(repo_specs) = cli.repos.clone() { + repo_specs.iter().map(|spec| spec.parse::()).collect::>>()? + } else { + BENCHMARK_REPOS.clone() + }; + + sh_println!("🚀 Foundry Benchmark Runner"); + sh_println!("Running with versions: {}", versions.join(", ")); + sh_println!( + "Running on repos: {}", + repos.iter().map(|r| format!("{}/{}", r.org, r.repo)).collect::>().join(", ") + ); + + // Install versions if requested + if cli.force_install { + install_foundry_versions(&versions)?; + } + + // Determine benchmarks to run + let benchmarks = if let Some(b) = cli.benchmarks { + b.into_iter().filter(|b| ALL_BENCHMARKS.contains(&b.as_str())).collect() + } else { + // Default: run all benchmarks except fuzz tests and coverage (which can be slow) + vec!["forge_test", "forge_build_no_cache", "forge_build_with_cache"] + .into_iter() + .map(String::from) + .collect::>() + }; + + sh_println!("Running benchmarks: {}", benchmarks.join(", ")); + + let mut results = BenchmarkResults::new(); + // Set the first version as baseline + if let Some(first_version) = versions.first() { + results.set_baseline_version(first_version.clone()); + } + + // Setup all projects upfront before version loop + sh_println!("📦 Setting up projects to benchmark"); + let projects: Vec<(RepoConfig, BenchmarkProject)> = repos + .par_iter() + .map(|repo_config| -> Result<(RepoConfig, BenchmarkProject)> { + sh_println!("Setting up {}/{}", repo_config.org, repo_config.repo); + let project = BenchmarkProject::setup(repo_config).wrap_err(format!( + "Failed to setup project for {}/{}", + repo_config.org, repo_config.repo + ))?; + Ok((repo_config.clone(), project)) + }) + .collect::>>()?; + + sh_println!("✅ All projects setup complete"); + + // Create a list of all benchmark tasks (same for all versions) + let benchmark_tasks: Vec<_> = projects + .iter() + .flat_map(|(repo_config, project)| { + benchmarks + .iter() + .map(move |benchmark| (repo_config.clone(), project, benchmark.clone())) + }) + .collect(); + + sh_println!("Will run {} benchmark tasks per version", benchmark_tasks.len()); + + // Run benchmarks for each version + for version in &versions { + sh_println!("🔧 Switching to Foundry version: {version}"); + switch_version_safe(version)?; + + // Verify the switch and capture full version details + let current = get_forge_version()?; + sh_println!("Current version: {}", current.trim()); + + // Get and store the full version details with commit hash and date + let version_details = get_forge_version_details()?; + results.add_version_details(version, version_details); + + sh_println!("Running benchmark tasks for version {version}..."); + + // Run all benchmarks sequentially + let version_results = benchmark_tasks + .iter() + .map(|(repo_config, project, benchmark)| -> Result<(String, String, HyperfineResult)> { + sh_println!("Running {} on {}/{}", benchmark, repo_config.org, repo_config.repo); + + // Determine runs based on benchmark type + let runs = match benchmark.as_str() { + "forge_coverage" => 1, // Coverage runs only once as an exception + _ => RUNS, // Use default RUNS constant for all other benchmarks + }; + + // Run the appropriate benchmark + let result = project.run(benchmark, version, runs, cli.verbose); + + match result { + Ok(hyperfine_result) => { + sh_println!( + " {} on {}/{}: {:.3}s ± {:.3}s", + benchmark, + repo_config.org, + repo_config.repo, + hyperfine_result.mean, + hyperfine_result.stddev.unwrap_or(0.0) + ); + Ok((repo_config.name.clone(), benchmark.clone(), hyperfine_result)) + } + Err(e) => { + eyre::bail!( + "Benchmark {} failed for {}/{}: {}", + benchmark, + repo_config.org, + repo_config.repo, + e + ); + } + } + }) + .collect::>>()?; + + // Add all collected results to the main results structure + for (repo_name, benchmark, hyperfine_result) in version_results { + results.add_result(&benchmark, version, &repo_name, hyperfine_result); + } + } + + // Write Markdown report unless --json-output is set without an explicit --output-file. + let md_filename = match cli.output_file { + Some(f) => Some(f), + None if cli.json_output.is_none() => Some("LATEST.md".to_string()), + None => None, + }; + if let Some(filename) = md_filename { + sh_println!("📝 Generating report..."); + let markdown = results.generate_markdown(&versions, &repos); + let output_path = cli.output_dir.join(filename); + fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; + sh_println!("✅ Report written to: {}", output_path.display()); + } + + if let Some(json_filename) = cli.json_output { + let summary = results.generate_json_summary(&versions); + let json = + serde_json::to_string_pretty(&summary).wrap_err("Failed to serialize JSON summary")?; + let json_path = cli.output_dir.join(json_filename); + fs::write(&json_path, json).wrap_err("Failed to write JSON summary")?; + sh_println!("✅ JSON summary written to: {}", json_path.display()); + } + + Ok(()) +} + +#[allow(unused_must_use)] +fn install_foundry_versions(versions: &[String]) -> Result<()> { + sh_println!("Installing Foundry versions..."); + + for version in versions { + sh_println!("Installing {version}..."); + + let status = Command::new("foundryup") + .args(["--install", version, "--force"]) + .status() + .wrap_err("Failed to run foundryup")?; + + if !status.success() { + eyre::bail!("Failed to install Foundry version: {}", version); + } + } + + sh_println!("✅ All versions installed successfully"); + Ok(()) +} diff --git a/benches/src/results.rs b/benches/src/results.rs new file mode 100644 index 0000000000000..e7d57250fc9a1 --- /dev/null +++ b/benches/src/results.rs @@ -0,0 +1,280 @@ +use crate::RepoConfig; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, process::Command, thread}; + +/// Hyperfine benchmark result +#[derive(Debug, Deserialize, Serialize)] +pub struct HyperfineResult { + pub command: String, + pub mean: f64, + pub stddev: Option, + pub median: f64, + pub user: f64, + pub system: f64, + pub min: f64, + pub max: f64, + pub times: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub exit_codes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option>, +} + +/// Hyperfine JSON output format +#[derive(Debug, Deserialize, Serialize)] +pub struct HyperfineOutput { + pub results: Vec, +} + +/// Aggregated benchmark results +#[derive(Debug, Default)] +pub struct BenchmarkResults { + /// Map of benchmark_name -> version -> repo -> result + pub data: HashMap>>, + /// Track the baseline version for comparison + pub baseline_version: Option, + /// Map of version name -> full version details + pub version_details: HashMap, +} + +impl BenchmarkResults { + pub fn new() -> Self { + Self::default() + } + + pub fn set_baseline_version(&mut self, version: String) { + self.baseline_version = Some(version); + } + + pub fn add_result( + &mut self, + benchmark: &str, + version: &str, + repo: &str, + result: HyperfineResult, + ) { + self.data + .entry(benchmark.to_string()) + .or_default() + .entry(version.to_string()) + .or_default() + .insert(repo.to_string(), result); + } + + pub fn add_version_details(&mut self, version: &str, details: String) { + self.version_details.insert(version.to_string(), details); + } + + /// Generate a flat JSON summary mapping `"benchmark/repo" -> mean_seconds`. + /// + /// Used by the nightly regression comparison script. + pub fn generate_json_summary(&self, versions: &[String]) -> HashMap { + let mut summary = HashMap::new(); + for (benchmark_name, version_data) in &self.data { + for version in versions { + if let Some(repo_data) = version_data.get(version) { + for (repo_name, result) in repo_data { + let key = format!("{benchmark_name}/{repo_name}"); + let rounded = (result.mean * 10_000.0).round() / 10_000.0; + summary.insert(key, rounded); + } + } + } + } + summary + } + + pub fn generate_markdown(&self, versions: &[String], repos: &[RepoConfig]) -> String { + let mut output = String::new(); + + // Header + output.push_str("# Foundry Benchmark Results\n\n"); + output.push_str(&format!( + "**Date**: {}\n\n", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S") + )); + + // Summary + output.push_str("## Summary\n\n"); + // Count actual repos that have results + let mut repos_with_results = std::collections::HashSet::new(); + for version_data in self.data.values() { + for repo_data in version_data.values() { + for repo_name in repo_data.keys() { + repos_with_results.insert(repo_name.clone()); + } + } + } + output.push_str(&format!( + "Benchmarked {} Foundry versions across {} repositories.\n\n", + versions.len(), + repos_with_results.len() + )); + + // Repositories tested + output.push_str("### Repositories Tested\n\n"); + for (i, repo) in repos.iter().enumerate() { + output.push_str(&format!( + "{}. [{}/{}](https://github.com/{}/{})\n", + i + 1, + repo.org, + repo.repo, + repo.org, + repo.repo + )); + } + output.push('\n'); + + // Versions tested + output.push_str("### Foundry Versions\n\n"); + for version in versions { + if let Some(details) = self.version_details.get(version) { + output.push_str(&format!("- **{version}**: {}\n", details.trim())); + } else { + output.push_str(&format!("- {version}\n")); + } + } + output.push('\n'); + + // Results for each benchmark type + for (benchmark_name, version_data) in &self.data { + output.push_str(&self.generate_benchmark_table( + benchmark_name, + version_data, + versions, + repos, + )); + } + + // System info + output.push_str("## System Information\n\n"); + output.push_str(&format!("- **OS**: {}\n", std::env::consts::OS)); + output.push_str(&format!( + "- **CPU**: {}\n", + thread::available_parallelism().map_or(1, |n| n.get()) + )); + output.push_str(&format!( + "- **Rustc**: {}\n", + get_rustc_version().unwrap_or_else(|_| "unknown".to_string()) + )); + + output + } + + /// Generate a complete markdown table for a single benchmark type + /// + /// This includes the section header, table header, separator, and all rows + fn generate_benchmark_table( + &self, + benchmark_name: &str, + version_data: &HashMap>, + versions: &[String], + repos: &[RepoConfig], + ) -> String { + let mut output = String::new(); + + // Section header + output.push_str(&format!("## {}\n\n", format_benchmark_name(benchmark_name))); + + // Create table header + output.push_str("| Repository |"); + for version in versions { + output.push_str(&format!(" {version} |")); + } + output.push('\n'); + + // Table separator + output.push_str("|------------|"); + for _ in versions { + output.push_str("----------|"); + } + output.push('\n'); + + // Table rows + output.push_str(&generate_table_rows(version_data, versions, repos)); + output.push('\n'); + + output + } +} + +/// Generate table rows for benchmark results +/// +/// This function creates the markdown table rows for each repository, +/// showing the benchmark results for each version. +fn generate_table_rows( + version_data: &HashMap>, + versions: &[String], + repos: &[RepoConfig], +) -> String { + let mut output = String::new(); + + for repo in repos { + output.push_str(&format!("| {} |", repo.name)); + + for version in versions { + let cell_content = get_benchmark_cell_content(version_data, version, &repo.name); + output.push_str(&format!(" {cell_content} |")); + } + + output.push('\n'); + } + + output +} + +/// Get the content for a single benchmark table cell +/// +/// Returns the formatted duration or "N/A" if no data is available. +/// The nested if-let statements handle the following cases: +/// 1. Check if version data exists +/// 2. Check if repository data exists for this version +fn get_benchmark_cell_content( + version_data: &HashMap>, + version: &str, + repo_name: &str, +) -> String { + // Check if we have data for this version + if let Some(repo_data) = version_data.get(version) && + // Check if we have data for this repository + let Some(result) = repo_data.get(repo_name) + { + return format_duration_seconds(result.mean); + } + + "N/A".to_string() +} + +pub fn format_benchmark_name(name: &str) -> String { + match name { + "forge_test" => "Forge Test", + "forge_build_no_cache" => "Forge Build (No Cache)", + "forge_build_with_cache" => "Forge Build (With Cache)", + "forge_fuzz_test" => "Forge Fuzz Test", + "forge_coverage" => "Forge Coverage", + "forge_isolate_test" => "Forge Test (Isolated)", + _ => name, + } + .to_string() +} + +pub fn format_duration_seconds(seconds: f64) -> String { + if seconds < 0.001 { + format!("{:.2} ms", seconds * 1000.0) + } else if seconds < 1.0 { + format!("{seconds:.3} s") + } else if seconds < 60.0 { + format!("{seconds:.2} s") + } else { + let minutes = (seconds / 60.0).floor(); + let remaining_seconds = seconds % 60.0; + format!("{minutes:.0}m {remaining_seconds:.1}s") + } +} + +pub fn get_rustc_version() -> Result { + let output = Command::new("rustc").arg("--version").output()?; + + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) +} diff --git a/benchmark.sh b/benchmark.sh new file mode 100755 index 0000000000000..ac6159099069b --- /dev/null +++ b/benchmark.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +versions="v1.5.1,v1.7.0" + +# Repositories +ITHACA_ACCOUNT="ithacaxyz/account:v0.5.7" +SOLADY_REPO="vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test'" +AAVE_V4="aave/aave-v4:af1f0f2ba323ac6fbaaee3abf6be060c78e22d35" +UNISWAP_V4_CORE="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc TickMathTestTest" +SPARK_PSM="sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting" + +SOLADY_ISOLATE="vectorized/solady:v0.1.26 --nmc 'SafeTransferLibTest|LifebuoyTest|LibBitTest|Base58Test|LibStringTest'" +ITHACA_ISOLATE="ithacaxyz/account:v0.5.7 --nmc SimulateExecuteTest" + +SOLADY_BUILD="vectorized/solady:v0.1.26" +UNISWAP_BUILD="uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75" +SPARK_PSM_BUILD="sparkdotfi/spark-psm:v1.0.0" + +# Benches +echo "===========FORGE TEST BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_REPO,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ + --benchmarks forge_test,forge_fuzz_test \ + --output-dir ./benches \ + --output-file forge_test_bench.md + +echo "===========FORGE ISOLATE TEST BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ISOLATE,$SOLADY_ISOLATE,$AAVE_V4,$UNISWAP_V4_CORE,$SPARK_PSM" \ + --benchmarks forge_isolate_test \ + --output-dir ./benches \ + --output-file forge_isolate_test_bench.md + +echo "===========FORGE BUILD BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$SOLADY_BUILD,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_build_no_cache,forge_build_with_cache \ + --output-dir ./benches \ + --output-file forge_build_bench.md + +echo "===========FORGE COVERAGE BENCHMARKS===========" + +foundry-bench --versions "$versions" \ + --repos "$ITHACA_ACCOUNT,$AAVE_V4,$UNISWAP_BUILD,$SPARK_PSM_BUILD" \ + --benchmarks forge_coverage \ + --output-dir ./benches \ + --output-file forge_coverage_bench.md + +echo "===========BENCHMARKS COMPLETED===========" diff --git a/clippy.toml b/clippy.toml index f4a551607f2fe..731817759020c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,5 +1,3 @@ -msrv = "1.86" - # `bytes::Bytes` is included by default and `alloy_primitives::Bytes` is a wrapper around it, # so it is safe to ignore it as well. ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] diff --git a/counter/.gas-snapshot b/counter/.gas-snapshot new file mode 100644 index 0000000000000..797ceebb2f595 --- /dev/null +++ b/counter/.gas-snapshot @@ -0,0 +1,2 @@ +CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 30410, ~: 32354) +CounterTest:test_Increment() (gas: 31851) \ No newline at end of file diff --git a/crates/forge/assets/workflowTemplate.yml b/counter/.github/workflows/test.yml similarity index 94% rename from crates/forge/assets/workflowTemplate.yml rename to counter/.github/workflows/test.yml index 4481ec6a8b2de..34a4a527be6f9 100644 --- a/crates/forge/assets/workflowTemplate.yml +++ b/counter/.github/workflows/test.yml @@ -10,6 +10,9 @@ env: jobs: check: + strategy: + fail-fast: true + name: Foundry project runs-on: ubuntu-latest steps: diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..052b88bb6516b --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,18 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env + + +# Soldeer +/dependencies diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/doc/doc-filelist.js b/counter/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/counter/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/counter/doc/doc-script.js b/counter/doc/doc-script.js new file mode 100644 index 0000000000000..62eeda3efc308 --- /dev/null +++ b/counter/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +let sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/counter/doc/doc-style.css b/counter/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/counter/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..c27b8ed21ba0b --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,9 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] + +[dependencies] +forge-std = "1.15.0" + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/remappings.txt b/counter/remappings.txt new file mode 100644 index 0000000000000..6c93bbb0c4b12 --- /dev/null +++ b/counter/remappings.txt @@ -0,0 +1 @@ +forge-std-1.15.0/=dependencies/forge-std-1.15.0/ diff --git a/crates/forge/assets/CounterTemplate.s.sol b/counter/script/Counter.s.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.s.sol rename to counter/script/Counter.s.sol diff --git a/counter/soldeer.lock b/counter/soldeer.lock new file mode 100644 index 0000000000000..af6c8601cd7ba --- /dev/null +++ b/counter/soldeer.lock @@ -0,0 +1,6 @@ +[[dependencies]] +name = "forge-std" +version = "1.15.0" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_15_0_27-02-2026_08:26:17_forge-std-1.15.zip" +checksum = "40d9b3b3d786eec4cd05fb9d818616015cbe7b8866643a9f0854495c938588c4" +integrity = "92accf4f7850eb9f5832f0ea77d633d36ebe993efc6d6c9f32369d31befc8a75" diff --git a/crates/forge/assets/CounterTemplate.sol b/counter/src/Counter.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.sol rename to counter/src/Counter.sol diff --git a/crates/forge/assets/CounterTemplate.t.sol b/counter/test/Counter.t.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.t.sol rename to counter/test/Counter.t.sol diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 23ff53b271895..d6404b21e2e8e 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -20,18 +20,24 @@ required-features = ["cli"] [dependencies] # foundry internal -anvil-core = { path = "core" } +anvil-core = { path = "core", default-features = false } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } -foundry-cli.workspace = true +foundry-cli = { workspace = true, optional = true } foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true -foundry-evm-core.workspace = true +foundry-evm-networks.workspace = true +foundry-primitives = { workspace = true, default-features = false } +tempo-chainspec.workspace = true +tempo-primitives.workspace = true +tempo-precompiles.workspace = true +tempo-evm.workspace = true +tempo-revm.workspace = true # alloy -alloy-evm.workspace = true -alloy-op-evm.workspace = true +alloy-evm = { workspace = true, features = ["call-util"] } +alloy-op-evm = { workspace = true, optional = true } alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } @@ -43,6 +49,8 @@ alloy-signer-local = { workspace = true, features = ["mnemonic"] } alloy-sol-types = { workspace = true, features = ["std"] } alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } alloy-rpc-types = { workspace = true, features = ["anvil", "trace", "txpool"] } +alloy-rpc-types-beacon.workspace = true +alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true alloy-provider = { workspace = true, features = [ "reqwest", @@ -55,9 +63,8 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true -alloy-hardforks.workspace = true -alloy-op-hardforks.workspace = true -op-alloy-consensus = { workspace = true, features = ["serde"] } +op-alloy-consensus = { workspace = true, features = ["serde"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } # revm revm = { workspace = true, features = [ @@ -67,7 +74,7 @@ revm = { workspace = true, features = [ "c-kzg", ] } revm-inspectors.workspace = true -op-revm.workspace = true +op-revm = { workspace = true, optional = true } # axum related axum.workspace = true @@ -84,16 +91,16 @@ futures.workspace = true async-trait.workspace = true # misc -flate2 = "1.0" +flate2.workspace = true serde_json.workspace = true serde.workspace = true thiserror.workspace = true yansi.workspace = true tempfile.workspace = true itertools.workspace = true -rand.workspace = true rand_08.workspace = true eyre.workspace = true +ethereum_ssz.workspace = true # cli clap = { version = "4", features = [ @@ -101,28 +108,41 @@ clap = { version = "4", features = [ "env", "wrap_help", ], optional = true } -clap_complete = { version = "4", optional = true } +clap_complete = { workspace = true, optional = true } chrono.workspace = true ctrlc = { version = "3", optional = true } fdlimit = { version = "0.3", optional = true } -clap_complete_fig = "4" [dev-dependencies] alloy-provider = { workspace = true, features = ["txpool-api"] } alloy-pubsub.workspace = true +rand.workspace = true +reqwest.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } -op-alloy-rpc-types.workspace = true +tempo-alloy.workspace = true [features] -default = ["cli", "jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] -jemalloc = ["foundry-cli/jemalloc"] -mimalloc = ["foundry-cli/mimalloc"] -tracy-allocator = ["foundry-cli/tracy-allocator"] +default = ["cli", "jemalloc", "asm-keccak", "optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "anvil-core/optimism", + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli?/optimism", +] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] +jemalloc = ["foundry-cli?/jemalloc"] +mimalloc = ["foundry-cli?/mimalloc"] +tracy-allocator = ["foundry-cli?/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ + "dep:foundry-cli", "clap", "clap_complete", "dep:fdlimit", @@ -130,3 +150,4 @@ cmd = [ "anvil-server/clap", "tokio/signal", ] +js-tracer = ["revm-inspectors/js-tracer"] diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 0b64c630bda9b..8456413a78b1f 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -15,23 +15,23 @@ workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true +foundry-primitives = { workspace = true, default-features = false } revm = { workspace = true, default-features = false, features = [ "std", "serde", "memory_limit", "c-kzg", ] } -op-revm.workspace = true alloy-primitives = { workspace = true, features = ["serde", "rlp"] } alloy-rpc-types = { workspace = true, features = ["anvil", "trace"] } alloy-serde.workspace = true alloy-rlp.workspace = true alloy-eips.workspace = true +alloy-eip5792.workspace = true alloy-consensus = { workspace = true, features = ["k256", "kzg"] } -alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } -op-alloy-consensus = { workspace = true, features = ["serde"] } alloy-network.workspace = true +alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } serde.workspace = true serde_json.workspace = true bytes.workspace = true @@ -39,3 +39,11 @@ bytes.workspace = true # misc rand.workspace = true thiserror.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-primitives/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index fa68530e0f26b..60a51e7dda28b 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -1,125 +1,51 @@ -use super::transaction::{TransactionInfo, TypedReceipt}; -use alloy_consensus::{proofs::calculate_transaction_root, Header, EMPTY_OMMER_ROOT_HASH}; -use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; -use alloy_rlp::{RlpDecodable, RlpEncodable}; +use super::transaction::TransactionInfo; +use alloy_consensus::{ + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, proofs::calculate_transaction_root, +}; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::Network; +use foundry_primitives::FoundryTxEnvelope; -// Type alias to optionally support impersonated transactions -type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction; +use crate::eth::transaction::MaybeImpersonatedTransaction; -/// Container type that gathers all block data +/// Type alias for a block containing potentially impersonated transactions. +pub type Block = alloy_consensus::Block>; + +/// Container type that gathers all block data, generic over a [`Network`]. #[derive(Clone, Debug)] -pub struct BlockInfo { - pub block: Block, +pub struct BlockInfo { + pub block: Block, pub transactions: Vec, - pub receipts: Vec, -} - -/// An Ethereum Block -#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)] -pub struct Block { - pub header: Header, - pub transactions: Vec, - pub ommers: Vec
, -} - -impl Block { - /// Creates a new block. - /// - /// Note: if the `impersonate-tx` feature is enabled this will also accept - /// `MaybeImpersonatedTransaction`. - pub fn new(partial_header: PartialHeader, transactions: impl IntoIterator) -> Self - where - T: Into, - { - let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); - let transactions1: Vec = - transactions.clone().into_iter().map(Into::into).collect(); - let transactions_root = calculate_transaction_root(&transactions1); - - Self { - header: Header { - parent_hash: partial_header.parent_hash, - beneficiary: partial_header.beneficiary, - ommers_hash: EMPTY_OMMER_ROOT_HASH, - state_root: partial_header.state_root, - transactions_root, - receipts_root: partial_header.receipts_root, - logs_bloom: partial_header.logs_bloom, - difficulty: partial_header.difficulty, - number: partial_header.number, - gas_limit: partial_header.gas_limit, - gas_used: partial_header.gas_used, - timestamp: partial_header.timestamp, - extra_data: partial_header.extra_data, - mix_hash: partial_header.mix_hash, - withdrawals_root: partial_header.withdrawals_root, - blob_gas_used: partial_header.blob_gas_used, - excess_blob_gas: partial_header.excess_blob_gas, - parent_beacon_block_root: partial_header.parent_beacon_block_root, - nonce: partial_header.nonce, - base_fee_per_gas: partial_header.base_fee, - requests_hash: partial_header.requests_hash, - }, - transactions, - ommers: vec![], - } - } + pub receipts: Vec, } -/// Partial header definition without ommers hash and transactions root -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct PartialHeader { - pub parent_hash: B256, - pub beneficiary: Address, - pub state_root: B256, - pub receipts_root: B256, - pub logs_bloom: Bloom, - pub difficulty: U256, - pub number: u64, - pub gas_limit: u64, - pub gas_used: u64, - pub timestamp: u64, - pub extra_data: Bytes, - pub mix_hash: B256, - pub blob_gas_used: Option, - pub excess_blob_gas: Option, - pub parent_beacon_block_root: Option, - pub nonce: B64, - pub base_fee: Option, - pub withdrawals_root: Option, - pub requests_hash: Option, -} - -impl From
for PartialHeader { - fn from(value: Header) -> Self { - Self { - parent_hash: value.parent_hash, - beneficiary: value.beneficiary, - state_root: value.state_root, - receipts_root: value.receipts_root, - logs_bloom: value.logs_bloom, - difficulty: value.difficulty, - number: value.number, - gas_limit: value.gas_limit, - gas_used: value.gas_used, - timestamp: value.timestamp, - extra_data: value.extra_data, - mix_hash: value.mix_hash, - nonce: value.nonce, - base_fee: value.base_fee_per_gas, - blob_gas_used: value.blob_gas_used, - excess_blob_gas: value.excess_blob_gas, - parent_beacon_block_root: value.parent_beacon_block_root, - requests_hash: value.requests_hash, - withdrawals_root: value.withdrawals_root, - } - } +/// Helper function to create a new block with Header and Anvil transactions, generic over the +/// transaction envelope with a default of [`FoundryTxEnvelope`]. +/// +/// Note: if the `impersonate-tx` feature is enabled this will also accept +/// `MaybeImpersonatedTransaction`. +pub fn create_block( + mut header: Header, + transactions: impl IntoIterator, +) -> Block +where + Tx: Encodable2718, + T: Into>, +{ + let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); + let transactions_root = calculate_transaction_root(&transactions); + + header.transactions_root = transactions_root; + header.ommers_hash = EMPTY_OMMER_ROOT_HASH; + + let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None }; + Block::new(header, body) } #[cfg(test)] mod tests { use alloy_primitives::{ - b256, + Address, B64, B256, Bloom, U256, b256, hex::{self, FromHex}, }; use alloy_rlp::Decodable; @@ -151,6 +77,8 @@ mod tests { parent_beacon_block_root: Default::default(), base_fee_per_gas: None, requests_hash: None, + block_access_list_hash: None, + slot_number: None, }; let encoded = alloy_rlp::encode(&header); @@ -192,6 +120,8 @@ mod tests { nonce: B64::ZERO, base_fee_per_gas: None, requests_hash: None, + block_access_list_hash: None, + slot_number: None, }; header.encode(&mut data); @@ -225,6 +155,8 @@ mod tests { parent_beacon_block_root: None, base_fee_per_gas: None, requests_hash: None, + block_access_list_hash: None, + slot_number: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); @@ -257,6 +189,8 @@ mod tests { excess_blob_gas: None, parent_beacon_block_root: None, requests_hash: None, + block_access_list_hash: None, + slot_number: None, }; assert_eq!(header.hash_slow(), expected_hash); } @@ -268,7 +202,7 @@ mod tests { let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - let block = Block::decode(&mut data.as_slice()).unwrap(); + let block = ::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index ac7db74892003..9d90ee623363b 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,6 +1,10 @@ use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; -use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256}; +use alloy_primitives::{ + Address, B64, B256, Bytes, TxHash, U256, + map::{HashMap, HashSet}, +}; use alloy_rpc_types::{ + BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, anvil::{Forking, MineOptions}, pubsub::{Params as SubscriptionParams, SubscriptionKind}, request::TransactionRequest, @@ -9,12 +13,13 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, + parity::TraceType, }, - BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, }; use alloy_serde::WithOtherFields; use foundry_common::serde_helpers::{ - deserialize_number, deserialize_number_opt, deserialize_number_seq, + deserialize_number, deserialize_number_opt, deserialize_number_seq, deserialize_u64_seq, + deserialize_u64_seq_opt, }; pub mod block; @@ -27,7 +32,7 @@ use self::serde_helpers::*; /// Wrapper type that ensures the type is named `params` #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] -pub struct Params { +pub struct Params { #[serde(default)] pub params: T, } @@ -43,6 +48,10 @@ pub enum EthRequest { #[serde(rename = "web3_sha3", with = "sequence")] Web3Sha3(Bytes), + /// Returns the current Ethereum protocol version. + #[serde(rename = "eth_protocolVersion", with = "empty_params")] + EthProtocolVersion(()), + #[serde(rename = "eth_chainId", with = "empty_params")] EthChainId(()), @@ -52,6 +61,10 @@ pub enum EthRequest { #[serde(rename = "net_listening", with = "empty_params")] NetListening(()), + /// Returns the number of hashes per second with which the node is mining. + #[serde(rename = "eth_hashrate", with = "empty_params")] + EthHashrate(()), + #[serde(rename = "eth_gasPrice", with = "empty_params")] EthGasPrice(()), @@ -67,6 +80,10 @@ pub enum EthRequest { #[serde(rename = "eth_blockNumber", with = "empty_params")] EthBlockNumber(()), + /// Returns the client coinbase address. + #[serde(rename = "eth_coinbase", with = "empty_params")] + EthCoinbase(()), + #[serde(rename = "eth_getBalance")] EthGetBalance(Address, Option), @@ -79,6 +96,10 @@ pub enum EthRequest { #[serde(rename = "eth_getStorageAt")] EthGetStorageAt(Address, U256, Option), + /// Returns storage values for multiple accounts and slots in a single call. + #[serde(rename = "eth_getStorageValues")] + EthGetStorageValues(HashMap>, Option), + #[serde(rename = "eth_getBlockByHash")] EthGetBlockByHash(B256, bool), @@ -144,9 +165,15 @@ pub enum EthRequest { #[serde(rename = "eth_sendTransaction", with = "sequence")] EthSendTransaction(Box>), + #[serde(rename = "eth_sendTransactionSync", with = "sequence")] + EthSendTransactionSync(Box>), + #[serde(rename = "eth_sendRawTransaction", with = "sequence")] EthSendRawTransaction(Bytes), + #[serde(rename = "eth_sendRawTransactionSync", with = "sequence")] + EthSendRawTransactionSync(Bytes), + #[serde(rename = "eth_call")] EthCall( WithOtherFields, @@ -169,11 +196,26 @@ pub enum EthRequest { #[serde(default)] Option>, ), + #[serde(rename = "eth_fillTransaction", with = "sequence")] + EthFillTransaction(WithOtherFields), + #[serde(rename = "eth_getTransactionByHash", with = "sequence")] EthGetTransactionByHash(TxHash), + /// Returns the blob for a given blob versioned hash. + #[serde(rename = "anvil_getBlobByHash", with = "sequence")] + GetBlobByHash(B256), + + /// Returns the blobs for a given transaction hash. + #[serde(rename = "anvil_getBlobsByTransactionHash", with = "sequence")] + GetBlobByTransactionHash(TxHash), + + /// Returns the genesis time for the chain + #[serde(rename = "anvil_getGenesisTime", with = "empty_params")] + GetGenesisTime(()), + #[serde(rename = "eth_getTransactionByBlockHashAndIndex")] - EthGetTransactionByBlockHashAndIndex(TxHash, Index), + EthGetTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getTransactionByBlockNumberAndIndex")] EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), @@ -182,7 +224,7 @@ pub enum EthRequest { EthGetRawTransactionByHash(TxHash), #[serde(rename = "eth_getRawTransactionByBlockHashAndIndex")] - EthGetRawTransactionByBlockHashAndIndex(TxHash, Index), + EthGetRawTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getRawTransactionByBlockNumberAndIndex")] EthGetRawTransactionByBlockNumberAndIndex(BlockNumber, Index), @@ -250,6 +292,9 @@ pub enum EthRequest { #[serde(rename = "eth_syncing", with = "empty_params")] EthSyncing(()), + #[serde(rename = "eth_config", with = "empty_params")] + EthConfig(()), + /// geth's `debug_getRawTransaction` endpoint #[serde(rename = "debug_getRawTransaction", with = "sequence")] DebugGetRawTransaction(TxHash), @@ -266,6 +311,22 @@ pub enum EthRequest { #[serde(default)] GethDebugTracingCallOptions, ), + /// reth's `debug_codeByHash` endpoint + #[serde(rename = "debug_codeByHash")] + DebugCodeByHash(B256, #[serde(default)] Option), + + /// reth's `debug_dbGet` endpoint + #[serde(rename = "debug_dbGet")] + DebugDbGet(String), + + /// geth's `debug_traceBlockByHash` endpoint + #[serde(rename = "debug_traceBlockByHash")] + DebugTraceBlockByHash(B256, #[serde(default)] GethDebugTracingOptions), + + /// geth's `debug_traceBlockByNumber` endpoint + #[serde(rename = "debug_traceBlockByNumber")] + DebugTraceBlockByNumber(BlockNumber, #[serde(default)] GethDebugTracingOptions), + /// Trace transaction endpoint for parity's `trace_transaction` #[serde(rename = "trace_transaction", with = "sequence")] TraceTransaction(B256), @@ -281,6 +342,13 @@ pub enum EthRequest { #[serde(rename = "trace_filter", with = "sequence")] TraceFilter(TraceFilter), + /// Trace transaction endpoint for parity's `trace_replayBlockTransactions` + #[serde(rename = "trace_replayBlockTransactions")] + TraceReplayBlockTransactions( + #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, + HashSet, + ), + // Custom endpoints, they're not extracted to a separate type out of serde convenience /// send transactions impersonating specific account and contract addresses. #[serde( @@ -303,6 +371,11 @@ pub enum EthRequest { with = "sequence" )] AutoImpersonateAccount(bool), + + /// Registers a signature/address pair for faking `ecrecover` results + #[serde(rename = "anvil_impersonateSignature", with = "sequence")] + ImpersonateSignature(Bytes, Address), + /// Returns true if automatic mining is enabled, and false. #[serde(rename = "anvil_getAutomine", alias = "hardhat_getAutomine", with = "empty_params")] GetAutoMine(()), @@ -328,7 +401,7 @@ pub enum EthRequest { #[serde( rename = "anvil_setIntervalMining", alias = "evm_setIntervalMining", - with = "sequence" + deserialize_with = "deserialize_u64_seq" )] SetIntervalMining(u64), @@ -337,11 +410,7 @@ pub enum EthRequest { GetIntervalMining(()), /// Removes transactions from the pool - #[serde( - rename = "anvil_dropTransaction", - alias = "hardhat_dropTransaction", - with = "sequence" - )] + #[serde(rename = "anvil_dropTransaction", alias = "hardhat_dropTransaction", with = "sequence")] DropTransaction(B256), /// Removes transactions from the pool @@ -380,11 +449,19 @@ pub enum EthRequest { #[serde( rename = "anvil_dealERC20", alias = "hardhat_dealERC20", - alias = "anvil_setERC20Balance", - alias = "tenderly_setErc20Balance" + alias = "anvil_setERC20Balance" )] DealERC20(Address, Address, #[serde(deserialize_with = "deserialize_number")] U256), + /// Sets the ERC20 allowance for a spender + #[serde(rename = "anvil_setERC20Allowance")] + SetERC20Allowance( + Address, + Address, + Address, + #[serde(deserialize_with = "deserialize_number")] U256, + ), + /// Sets the code of a contract #[serde(rename = "anvil_setCode", alias = "hardhat_setCode")] SetCode(Address, Bytes), @@ -408,7 +485,7 @@ pub enum EthRequest { SetCoinbase(Address), /// Sets the chain id - #[serde(rename = "anvil_setChainId", with = "sequence")] + #[serde(rename = "anvil_setChainId", deserialize_with = "deserialize_u64_seq")] SetChainId(u64), /// Enable or disable logging @@ -507,7 +584,7 @@ pub enum EthRequest { /// Similar to `evm_increaseTime` but takes sets a block timestamp `interval`. /// /// The timestamp of the next block will be computed as `lastBlock_timestamp + interval`. - #[serde(rename = "anvil_setBlockTimestampInterval", with = "sequence")] + #[serde(rename = "anvil_setBlockTimestampInterval", deserialize_with = "deserialize_u64_seq")] EvmSetBlockTimeStampInterval(u64), /// Removes a `anvil_setBlockTimestampInterval` if it exists @@ -529,11 +606,6 @@ pub enum EthRequest { #[serde(rename = "eth_sendUnsignedTransaction", with = "sequence")] EthSendUnsignedTransaction(Box>), - /// Turn on call traces for transactions that are returned to the user when they execute a - /// transaction (instead of just txhash/receipt) - #[serde(rename = "anvil_enableTraces", with = "empty_params")] - EnableTraces(()), - /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. /// Ref: @@ -632,6 +704,14 @@ pub enum EthRequest { #[serde(deserialize_with = "deserialize_number")] U256, ), + /// Returns the transaction by sender and nonce + /// Returns the full transaction data. + #[serde(rename = "eth_getTransactionBySenderAndNonce")] + EthGetTransactionBySenderAndNonce( + Address, + #[serde(deserialize_with = "deserialize_number")] U256, + ), + /// Otterscan's `ots_getTransactionBySenderAndNonce` endpoint /// Given an ETH contract address, returns the tx hash and the direct address who created the /// contract. @@ -647,30 +727,28 @@ pub enum EthRequest { Reorg(ReorgOptions), /// Rollback the chain - #[serde(rename = "anvil_rollback", with = "sequence")] + #[serde(rename = "anvil_rollback", deserialize_with = "deserialize_u64_seq_opt")] Rollback(Option), - /// Wallet - #[serde(rename = "wallet_getCapabilities", with = "empty_params")] - WalletGetCapabilities(()), - - /// Wallet send_tx - #[serde( - rename = "wallet_sendTransaction", - alias = "odyssey_sendTransaction", - with = "sequence" - )] - WalletSendTransaction(Box>), + /// Sets the fee token for a user (Tempo-only) + #[serde(rename = "anvil_setFeeToken")] + SetFeeToken(Address, Address), - /// Add an address to the [`DelegationCapability`] of the wallet - /// - /// [`DelegationCapability`]: wallet::DelegationCapability - #[serde(rename = "anvil_addCapability", with = "sequence")] - AnvilAddCapability(Address), + /// Sets the fee token for a validator (Tempo-only) + #[serde(rename = "anvil_setValidatorFeeToken")] + SetValidatorFeeToken(Address, Address), - /// Set the executor (sponsor) wallet - #[serde(rename = "anvil_setExecutor", with = "sequence")] - AnvilSetExecutor(String), + /// Mints FeeAMM liquidity for a token pair (Tempo-only) + #[serde(rename = "anvil_setFeeAmmLiquidity")] + SetFeeAmmLiquidity( + /// user_token + Address, + /// validator_token + Address, + /// amount + #[serde(deserialize_with = "deserialize_number")] + U256, + ), } /// Represents ethereum JSON-RPC API @@ -853,6 +931,68 @@ mod tests { let _req = serde_json::from_value::(value).unwrap(); } + #[test] + fn test_numeric_params_accept_hex_and_decimal_strings() { + // Standard JSON-RPC clients (web3.js / ethers / viem) encode numeric params as hex + // strings. These methods keep their internal `u64` types, but use a U64-aware + // deserializer for RPC params. + let parse = |s: &str| { + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + serde_json::from_value::(value) + }; + + match parse(r#"{"method": "anvil_setIntervalMining", "params": [100]}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setIntervalMining", "params": 100}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setIntervalMining", "params": ["0x64"]}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setIntervalMining", "params": "0x64"}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setIntervalMining", "params": ["100"]}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setIntervalMining", "params": "100"}"#).unwrap() { + EthRequest::SetIntervalMining(interval) => assert_eq!(interval, 100), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setChainId", "params": ["0x539"]}"#).unwrap() { + EthRequest::SetChainId(chain_id) => assert_eq!(chain_id, 1337), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_setBlockTimestampInterval", "params": ["0xa"]}"#).unwrap() + { + EthRequest::EvmSetBlockTimeStampInterval(interval) => assert_eq!(interval, 10), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_rollback", "params": ["0x5"]}"#).unwrap() { + EthRequest::Rollback(depth) => assert_eq!(depth, Some(5)), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_rollback", "params": []}"#).unwrap() { + EthRequest::Rollback(depth) => assert_eq!(depth, None), + req => panic!("unexpected request: {req:?}"), + } + match parse(r#"{"method": "anvil_rollback", "params": [null]}"#).unwrap() { + EthRequest::Rollback(depth) => assert_eq!(depth, None), + req => panic!("unexpected request: {req:?}"), + } + + assert!( + parse(r#"{"method": "anvil_setIntervalMining", "params": ["0x10000000000000000"]}"#) + .is_err() + ); + } + #[test] fn test_custom_drop_tx() { let s = r#"{"method": "anvil_dropTransaction", "params": diff --git a/crates/anvil/core/src/eth/serde_helpers.rs b/crates/anvil/core/src/eth/serde_helpers.rs index 05ec3b25fd04f..d111d32f1e3b4 100644 --- a/crates/anvil/core/src/eth/serde_helpers.rs +++ b/crates/anvil/core/src/eth/serde_helpers.rs @@ -2,7 +2,7 @@ pub mod sequence { use serde::{ - de::DeserializeOwned, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer, + Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned, ser::SerializeSeq, }; pub fn serialize(val: &T, s: S) -> Result @@ -25,7 +25,7 @@ pub mod sequence { return Err(serde::de::Error::custom(format!( "expected params sequence with length 1 but got {}", seq.len() - ))) + ))); } Ok(seq.remove(0)) } @@ -44,7 +44,7 @@ pub mod empty_params { return Err(serde::de::Error::custom(format!( "expected params sequence with length 0 but got {}", seq.len() - ))) + ))); } Ok(()) } @@ -52,58 +52,19 @@ pub mod empty_params { /// A module that deserializes either a BlockNumberOrTag, or a simple number. pub mod lenient_block_number { + pub use alloy_eips::eip1898::LenientBlockNumberOrTag; use alloy_rpc_types::BlockNumberOrTag; use serde::{Deserialize, Deserializer}; - /// Following the spec the block parameter is either: - /// - /// > HEX String - an integer block number - /// > String "earliest" for the earliest/genesis block - /// > String "latest" - for the latest mined block - /// > String "pending" - for the pending state/transactions - /// - /// and with EIP-1898: - /// > blockNumber: QUANTITY - a block number - /// > blockHash: DATA - a block hash - /// - /// - /// - /// EIP-1898 does not all calls that use `BlockNumber` like `eth_getBlockByNumber` and doesn't - /// list raw integers as supported. - /// - /// However, there are dev node implementations that support integers, such as ganache: - /// - /// N.B.: geth does not support ints in `eth_getBlockByNumber` - pub fn lenient_block_number<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - LenientBlockNumber::deserialize(deserializer).map(Into::into) - } + /// deserializes either a BlockNumberOrTag, or a simple number. + pub use alloy_eips::eip1898::lenient_block_number_or_tag::deserialize as lenient_block_number; /// Same as `lenient_block_number` but requires to be `[num; 1]` pub fn lenient_block_number_seq<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - let num = <[LenientBlockNumber; 1]>::deserialize(deserializer)?[0].into(); + let num = <[LenientBlockNumberOrTag; 1]>::deserialize(deserializer)?[0].into(); Ok(num) } - - /// Various block number representations, See [`lenient_block_number()`] - #[derive(Clone, Copy, Deserialize)] - #[serde(untagged)] - pub enum LenientBlockNumber { - BlockNumber(BlockNumberOrTag), - Num(u64), - } - - impl From for BlockNumberOrTag { - fn from(b: LenientBlockNumber) -> Self { - match b { - LenientBlockNumber::BlockNumber(b) => b, - LenientBlockNumber::Num(b) => b.into(), - } - } - } } diff --git a/crates/anvil/core/src/eth/subscription.rs b/crates/anvil/core/src/eth/subscription.rs index d0cada75e830b..eaf4d8ce373ef 100644 --- a/crates/anvil/core/src/eth/subscription.rs +++ b/crates/anvil/core/src/eth/subscription.rs @@ -1,6 +1,6 @@ //! Subscription types use alloy_primitives::hex; -use rand::{distr::Alphanumeric, rng, Rng}; +use rand::{Rng, distr::Alphanumeric, rng}; use std::fmt; /// Unique subscription id @@ -46,7 +46,7 @@ pub struct HexIdProvider { impl HexIdProvider { /// Generates a random hex encoded Id - pub fn gen(&self) -> String { + pub fn generate(&self) -> String { let id: String = (&mut rng()).sample_iter(Alphanumeric).map(char::from).take(self.len).collect(); let out = hex::encode(id); @@ -62,5 +62,5 @@ impl Default for HexIdProvider { /// Returns a new random hex identifier pub fn hex_id() -> String { - HexIdProvider::default().gen() + HexIdProvider::default().generate() } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index ae6a7262565ca..056d7a9602855 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,1058 +1,183 @@ //! Transaction related types use alloy_consensus::{ - transaction::{ - eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, - Recovered, TxEip7702, - }, - Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930, - TxEnvelope, TxLegacy, TxReceipt, Typed2718, + Transaction, Typed2718, + crypto::RecoveryError, + transaction::{SignerRecoverable, TxHashRef}, }; -use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; -use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; -use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256, U64}; -use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; -use alloy_rpc_types::{ - request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError, - Transaction as RpcTransaction, TransactionReceipt, -}; -use alloy_serde::{OtherFields, WithOtherFields}; + +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, B256, Bytes, TxHash}; +use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; use foundry_evm::traces::CallTraceNode; -use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; -use op_revm::{transaction::deposit::DepositTransactionParts, OpTransaction}; -use revm::{context::TxEnv, interpreter::InstructionResult}; +use foundry_primitives::FoundryTxEnvelope; +use revm::interpreter::InstructionResult; use serde::{Deserialize, Serialize}; -use std::ops::{Deref, Mul}; - -/// Converts a [TransactionRequest] into a [TypedTransactionRequest]. -/// Should be removed once the call builder abstraction for providers is in place. -pub fn transaction_request_to_typed( - tx: WithOtherFields, -) -> Option { - let WithOtherFields:: { - inner: - TransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - max_fee_per_blob_gas, - blob_versioned_hashes, - gas, - value, - input, - nonce, - access_list, - sidecar, - transaction_type, - authorization_list, - chain_id: _, - }, - other, - } = tx; - - // Special case: OP-stack deposit tx - if transaction_type == Some(0x7E) || has_optimism_fields(&other) { - let mint = other.get_deserialized::("mint")?.map(|m| m.to::()).ok()?; - - return Some(TypedTransactionRequest::Deposit(TxDeposit { - from: from.unwrap_or_default(), - source_hash: other.get_deserialized::("sourceHash")?.ok()?, - to: to.unwrap_or_default(), - mint, - value: value.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - is_system_transaction: other.get_deserialized::("isSystemTx")?.ok()?, - input: input.into_input().unwrap_or_default(), - })); - } - - // EIP7702 - if transaction_type == Some(4) || authorization_list.is_some() { - return Some(TypedTransactionRequest::EIP7702(TxEip7702 { - nonce: nonce.unwrap_or_default(), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::ZERO), - input: input.into_input().unwrap_or_default(), - // requires to - to: to?.into_to()?, - chain_id: 0, - access_list: access_list.unwrap_or_default(), - authorization_list: authorization_list.unwrap(), - })); - } - - match ( - transaction_type, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - access_list.as_ref(), - max_fee_per_blob_gas, - blob_versioned_hashes.as_ref(), - sidecar.as_ref(), - to, - ) { - // legacy transaction - (Some(0), _, None, None, None, None, None, None, _) | - (None, Some(_), None, None, None, None, None, None, _) => { - Some(TypedTransactionRequest::Legacy(TxLegacy { - nonce: nonce.unwrap_or_default(), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::ZERO), - input: input.into_input().unwrap_or_default(), - to: to.unwrap_or_default(), - chain_id: None, - })) - } - // EIP2930 - (Some(1), _, None, None, _, None, None, None, _) | - (None, _, None, None, Some(_), None, None, None, _) => { - Some(TypedTransactionRequest::EIP2930(TxEip2930 { - nonce: nonce.unwrap_or_default(), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::ZERO), - input: input.into_input().unwrap_or_default(), - to: to.unwrap_or_default(), - chain_id: 0, - access_list: access_list.unwrap_or_default(), - })) - } - // EIP1559 - (Some(2), None, _, _, _, _, None, None, _) | - (None, None, Some(_), _, _, _, None, None, _) | - (None, None, _, Some(_), _, _, None, None, _) | - (None, None, None, None, None, _, None, None, _) => { - // Empty fields fall back to the canonical transaction schema. - Some(TypedTransactionRequest::EIP1559(TxEip1559 { - nonce: nonce.unwrap_or_default(), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::ZERO), - input: input.into_input().unwrap_or_default(), - to: to.unwrap_or_default(), - chain_id: 0, - access_list: access_list.unwrap_or_default(), - })) - } - // EIP4844 - (Some(3), None, _, _, _, _, Some(_), _, to) => { - let tx = TxEip4844 { - nonce: nonce.unwrap_or_default(), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), - max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::ZERO), - input: input.into_input().unwrap_or_default(), - to: match to.unwrap_or(TxKind::Create) { - TxKind::Call(to) => to, - TxKind::Create => Address::ZERO, - }, - chain_id: 0, - access_list: access_list.unwrap_or_default(), - blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(), - }; - - if let Some(sidecar) = sidecar { - Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar( - TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar), - ))) - } else { - Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx))) - } - } - _ => None, - } -} - -pub fn has_optimism_fields(other: &OtherFields) -> bool { - other.contains_key("sourceHash") && - other.contains_key("mint") && - other.contains_key("isSystemTx") -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TypedTransactionRequest { - Legacy(TxLegacy), - EIP2930(TxEip2930), - EIP1559(TxEip1559), - EIP7702(TxEip7702), - EIP4844(TxEip4844Variant), - Deposit(TxDeposit), -} +use std::ops::Deref; -/// A wrapper for [TypedTransaction] that allows impersonating accounts. +/// A wrapper for a transaction envelope that allows impersonating accounts. /// /// This is a helper that carries the `impersonated` sender so that the right hash -/// [TypedTransaction::impersonated_hash] can be created. +/// can be created. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct MaybeImpersonatedTransaction { - pub transaction: TypedTransaction, - pub impersonated_sender: Option
, +pub struct MaybeImpersonatedTransaction { + transaction: T, + impersonated_sender: Option
, } -impl MaybeImpersonatedTransaction { +impl Typed2718 for MaybeImpersonatedTransaction { + fn ty(&self) -> u8 { + self.transaction.ty() + } +} + +impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction - pub fn new(transaction: TypedTransaction) -> Self { + pub const fn new(transaction: T) -> Self { Self { transaction, impersonated_sender: None } } /// Creates a new impersonated transaction wrapper using the given sender - pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self { + pub const fn impersonated(transaction: T, impersonated_sender: Address) -> Self { Self { transaction, impersonated_sender: Some(impersonated_sender) } } + /// Returns whether the transaction is impersonated + pub const fn is_impersonated(&self) -> bool { + self.impersonated_sender.is_some() + } + + /// Returns the inner transaction. + pub fn into_inner(self) -> T { + self.transaction + } +} + +impl MaybeImpersonatedTransaction { /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { + pub fn recover(&self) -> Result { if let Some(sender) = self.impersonated_sender { return Ok(sender); } - self.transaction.recover() + self.transaction.recover_signer() } - /// Returns whether the transaction is impersonated - pub fn is_impersonated(&self) -> bool { - self.impersonated_sender.is_some() - } - - /// Returns the hash of the transaction + /// Returns the hash of the transaction. + /// + /// If the transaction is impersonated, returns a unique hash derived by appending the + /// impersonated sender address to the encoded transaction before hashing. pub fn hash(&self) -> B256 { if let Some(sender) = self.impersonated_sender { - return self.transaction.impersonated_hash(sender) + let mut buffer = Vec::new(); + self.transaction.encode(&mut buffer); + buffer.extend_from_slice(sender.as_ref()); + return B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice()); } - self.transaction.hash() + *self.transaction.tx_hash() + } +} + +impl Encodable2718 for MaybeImpersonatedTransaction { + fn encode_2718_len(&self) -> usize { + self.transaction.encode_2718_len() + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + self.transaction.encode_2718(out) } } -impl Encodable for MaybeImpersonatedTransaction { +impl Encodable for MaybeImpersonatedTransaction { fn encode(&self, out: &mut dyn bytes::BufMut) { self.transaction.encode(out) } } -impl From for TypedTransaction { - fn from(value: MaybeImpersonatedTransaction) -> Self { +impl From> for FoundryTxEnvelope { + fn from(value: MaybeImpersonatedTransaction) -> Self { value.transaction } } -impl From for MaybeImpersonatedTransaction { - fn from(value: TypedTransaction) -> Self { +impl From for MaybeImpersonatedTransaction { + fn from(value: T) -> Self { Self::new(value) } } -impl Decodable for MaybeImpersonatedTransaction { +impl Decodable for MaybeImpersonatedTransaction { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - TypedTransaction::decode(buf).map(Self::new) + T::decode(buf).map(Self::new) } } -impl AsRef for MaybeImpersonatedTransaction { - fn as_ref(&self) -> &TypedTransaction { +impl AsRef for MaybeImpersonatedTransaction { + fn as_ref(&self) -> &T { &self.transaction } } -impl Deref for MaybeImpersonatedTransaction { - type Target = TypedTransaction; +impl Deref for MaybeImpersonatedTransaction { + type Target = T; fn deref(&self) -> &Self::Target { &self.transaction } } -impl From for RpcTransaction { - fn from(value: MaybeImpersonatedTransaction) -> Self { - let hash = value.hash(); - let sender = value.recover().unwrap_or_default(); - to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender) - } -} - -pub fn to_alloy_transaction_with_hash_and_sender( - transaction: TypedTransaction, - hash: B256, - from: Address, -) -> RpcTransaction { - match transaction { - TypedTransaction::Legacy(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP2930(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP1559(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP4844(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP7702(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::Deposit(_t) => { - unreachable!("cannot reach here, handled in `transaction_build` ") - } - } -} - /// Queued transaction #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingTransaction { +pub struct PendingTransaction { /// The actual transaction - pub transaction: MaybeImpersonatedTransaction, + pub transaction: MaybeImpersonatedTransaction, /// the recovered sender of this transaction sender: Address, - /// hash of `transaction`, so it can easily be reused with encoding and hashing agan + /// hash of `transaction`, so it can easily be reused with encoding and hashing again hash: TxHash, } -impl PendingTransaction { - pub fn new(transaction: TypedTransaction) -> Result { - let sender = transaction.recover()?; - let hash = transaction.hash(); - Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash }) - } - - pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self { - let hash = transaction.impersonated_hash(sender); - Self { - transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender), - sender, - hash, - } - } - - pub fn nonce(&self) -> u64 { - self.transaction.nonce() - } - - pub fn hash(&self) -> &TxHash { +impl PendingTransaction { + pub const fn hash(&self) -> &TxHash { &self.hash } - pub fn sender(&self) -> &Address { + pub const fn sender(&self) -> &Address { &self.sender } - - /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) - /// expects. - /// - /// Base [`TxEnv`] is encapsulated in the [`op_revm::OpTransaction`] - pub fn to_revm_tx_env(&self) -> OpTransaction { - fn transact_to(kind: &TxKind) -> TxKind { - match kind { - TxKind::Call(c) => TxKind::Call(*c), - TxKind::Create => TxKind::Create, - } - } - - let caller = *self.sender(); - match &self.transaction.transaction { - TypedTransaction::Legacy(tx) => { - let chain_id = tx.tx().chain_id; - let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); - OpTransaction::new(TxEnv { - caller, - kind: transact_to(to), - data: input.clone(), - chain_id, - nonce: *nonce, - value: (*value), - gas_price: *gas_price, - gas_priority_fee: None, - gas_limit: *gas_limit, - access_list: vec![].into(), - tx_type: 0, - ..Default::default() - }) - } - TypedTransaction::EIP2930(tx) => { - let TxEip2930 { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - input, - access_list, - .. - } = tx.tx(); - OpTransaction::new(TxEnv { - caller, - kind: transact_to(to), - data: input.clone(), - chain_id: Some(*chain_id), - nonce: *nonce, - value: *value, - gas_price: *gas_price, - gas_priority_fee: None, - gas_limit: *gas_limit, - access_list: access_list.clone(), - tx_type: 1, - ..Default::default() - }) - } - TypedTransaction::EIP1559(tx) => { - let TxEip1559 { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - to, - value, - input, - access_list, - .. - } = tx.tx(); - OpTransaction::new(TxEnv { - caller, - kind: transact_to(to), - data: input.clone(), - chain_id: Some(*chain_id), - nonce: *nonce, - value: *value, - gas_price: *max_fee_per_gas, - gas_priority_fee: Some(*max_priority_fee_per_gas), - gas_limit: *gas_limit, - access_list: access_list.clone(), - tx_type: 2, - ..Default::default() - }) - } - TypedTransaction::EIP4844(tx) => { - let TxEip4844 { - chain_id, - nonce, - max_fee_per_blob_gas, - max_fee_per_gas, - max_priority_fee_per_gas, - gas_limit, - to, - value, - input, - access_list, - blob_versioned_hashes, - .. - } = tx.tx().tx(); - OpTransaction::new(TxEnv { - caller, - kind: TxKind::Call(*to), - data: input.clone(), - chain_id: Some(*chain_id), - nonce: *nonce, - value: *value, - gas_price: *max_fee_per_gas, - gas_priority_fee: Some(*max_priority_fee_per_gas), - max_fee_per_blob_gas: *max_fee_per_blob_gas, - blob_hashes: blob_versioned_hashes.clone(), - gas_limit: *gas_limit, - access_list: access_list.clone(), - tx_type: 3, - ..Default::default() - }) - } - TypedTransaction::EIP7702(tx) => { - let TxEip7702 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - to, - value, - access_list, - authorization_list, - input, - } = tx.tx(); - - let mut tx = TxEnv { - caller, - kind: TxKind::Call(*to), - data: input.clone(), - chain_id: Some(*chain_id), - nonce: *nonce, - value: *value, - gas_price: *max_fee_per_gas, - gas_priority_fee: Some(*max_priority_fee_per_gas), - gas_limit: *gas_limit, - access_list: access_list.clone(), - tx_type: 4, - ..Default::default() - }; - tx.set_signed_authorization(authorization_list.clone()); - - OpTransaction::new(tx) - } - TypedTransaction::Deposit(tx) => { - let chain_id = tx.chain_id(); - let TxDeposit { - source_hash, - to, - mint, - value, - gas_limit, - is_system_transaction, - input, - .. - } = tx; - - let base = TxEnv { - caller, - kind: transact_to(to), - data: input.clone(), - chain_id, - nonce: 0, - value: *value, - gas_price: 0, - gas_priority_fee: None, - gas_limit: { *gas_limit }, - access_list: vec![].into(), - tx_type: DEPOSIT_TX_TYPE_ID, - ..Default::default() - }; - - let deposit = DepositTransactionParts { - source_hash: *source_hash, - mint: Some(*mint), - is_system_transaction: *is_system_transaction, - }; - - OpTransaction { base, deposit, enveloped_tx: None } - } - } - } -} - -/// Container type for signed, typed transactions. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum TypedTransaction { - /// Legacy transaction type - Legacy(Signed), - /// EIP-2930 transaction - EIP2930(Signed), - /// EIP-1559 transaction - EIP1559(Signed), - /// EIP-4844 transaction - EIP4844(Signed), - /// EIP-7702 transaction - EIP7702(Signed), - /// op-stack deposit transaction - Deposit(TxDeposit), -} - -impl TryFrom for TypedTransaction { - type Error = ConversionError; - - fn try_from(value: AnyRpcTransaction) -> Result { - let WithOtherFields { inner, .. } = value.0; - let from = inner.inner.signer(); - match inner.inner.into_inner() { - AnyTxEnvelope::Ethereum(tx) => match tx { - TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), - TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), - TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), - TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), - TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), - }, - AnyTxEnvelope::Unknown(mut tx) => { - // Try to convert to deposit transaction - if tx.ty() == DEPOSIT_TX_TYPE_ID { - tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); - let deposit_tx = - tx.inner.fields.deserialize_into::().map_err(|e| { - ConversionError::Custom(format!( - "Failed to deserialize deposit tx: {e}" - )) - })?; - - return Ok(Self::Deposit(deposit_tx)); - }; - - Err(ConversionError::Custom("UnknownTxType".to_string())) - } - } - } } -impl TypedTransaction { - /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702 - pub fn is_dynamic_fee(&self) -> bool { - matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_)) - } - - pub fn gas_price(&self) -> u128 { - match self { - Self::Legacy(tx) => tx.tx().gas_price, - Self::EIP2930(tx) => tx.tx().gas_price, - Self::EIP1559(tx) => tx.tx().max_fee_per_gas, - Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas, - Self::EIP7702(tx) => tx.tx().max_fee_per_gas, - Self::Deposit(_) => 0, - } - } - - pub fn gas_limit(&self) -> u64 { - match self { - Self::Legacy(tx) => tx.tx().gas_limit, - Self::EIP2930(tx) => tx.tx().gas_limit, - Self::EIP1559(tx) => tx.tx().gas_limit, - Self::EIP4844(tx) => tx.tx().tx().gas_limit, - Self::EIP7702(tx) => tx.tx().gas_limit, - Self::Deposit(tx) => tx.gas_limit, - } - } - - pub fn value(&self) -> U256 { - U256::from(match self { - Self::Legacy(tx) => tx.tx().value, - Self::EIP2930(tx) => tx.tx().value, - Self::EIP1559(tx) => tx.tx().value, - Self::EIP4844(tx) => tx.tx().tx().value, - Self::EIP7702(tx) => tx.tx().value, - Self::Deposit(tx) => tx.value, - }) - } - - pub fn data(&self) -> &Bytes { - match self { - Self::Legacy(tx) => &tx.tx().input, - Self::EIP2930(tx) => &tx.tx().input, - Self::EIP1559(tx) => &tx.tx().input, - Self::EIP4844(tx) => &tx.tx().tx().input, - Self::EIP7702(tx) => &tx.tx().input, - Self::Deposit(tx) => &tx.input, - } - } - - /// Returns the transaction type - pub fn r#type(&self) -> Option { - match self { - Self::Legacy(_) => None, - Self::EIP2930(_) => Some(1), - Self::EIP1559(_) => Some(2), - Self::EIP4844(_) => Some(3), - Self::EIP7702(_) => Some(4), - Self::Deposit(_) => Some(0x7E), - } - } - - /// Max cost of the transaction - /// It is the gas limit multiplied by the gas price, - /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob - /// gas) is also added - pub fn max_cost(&self) -> u128 { - let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price()); - - if self.is_eip4844() { - max_cost = max_cost.saturating_add( - self.blob_gas() - .map(|g| g as u128) - .unwrap_or(0) - .mul(self.max_fee_per_blob_gas().unwrap_or(0)), - ) - } - - max_cost - } - - pub fn blob_gas(&self) -> Option { - match self { - Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()), - _ => None, - } - } - - pub fn max_fee_per_blob_gas(&self) -> Option { - match self { - Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas), - _ => None, - } - } - - /// Returns a helper type that contains commonly used values as fields - pub fn essentials(&self) -> TransactionEssentials { - match self { - Self::Legacy(t) => TransactionEssentials { - kind: t.tx().to, - input: t.tx().input.clone(), - nonce: t.tx().nonce, - gas_limit: t.tx().gas_limit, - gas_price: Some(t.tx().gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - value: t.tx().value, - chain_id: t.tx().chain_id, - access_list: Default::default(), - }, - Self::EIP2930(t) => TransactionEssentials { - kind: t.tx().to, - input: t.tx().input.clone(), - nonce: t.tx().nonce, - gas_limit: t.tx().gas_limit, - gas_price: Some(t.tx().gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - value: t.tx().value, - chain_id: Some(t.tx().chain_id), - access_list: t.tx().access_list.clone(), - }, - Self::EIP1559(t) => TransactionEssentials { - kind: t.tx().to, - input: t.tx().input.clone(), - nonce: t.tx().nonce, - gas_limit: t.tx().gas_limit, - gas_price: None, - max_fee_per_gas: Some(t.tx().max_fee_per_gas), - max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - value: t.tx().value, - chain_id: Some(t.tx().chain_id), - access_list: t.tx().access_list.clone(), - }, - Self::EIP4844(t) => TransactionEssentials { - kind: TxKind::Call(t.tx().tx().to), - input: t.tx().tx().input.clone(), - nonce: t.tx().tx().nonce, - gas_limit: t.tx().tx().gas_limit, - gas_price: Some(t.tx().tx().max_fee_per_blob_gas), - max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), - max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas), - max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), - blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), - value: t.tx().tx().value, - chain_id: Some(t.tx().tx().chain_id), - access_list: t.tx().tx().access_list.clone(), - }, - Self::EIP7702(t) => TransactionEssentials { - kind: TxKind::Call(t.tx().to), - input: t.tx().input.clone(), - nonce: t.tx().nonce, - gas_limit: t.tx().gas_limit, - gas_price: Some(t.tx().max_fee_per_gas), - max_fee_per_gas: Some(t.tx().max_fee_per_gas), - max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - value: t.tx().value, - chain_id: Some(t.tx().chain_id), - access_list: t.tx().access_list.clone(), - }, - Self::Deposit(t) => TransactionEssentials { - kind: t.to, - input: t.input.clone(), - nonce: 0, - gas_limit: t.gas_limit, - gas_price: Some(0), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - value: t.value, - chain_id: t.chain_id(), - access_list: Default::default(), - }, - } - } - - pub fn nonce(&self) -> u64 { - match self { - Self::Legacy(t) => t.tx().nonce, - Self::EIP2930(t) => t.tx().nonce, - Self::EIP1559(t) => t.tx().nonce, - Self::EIP4844(t) => t.tx().tx().nonce, - Self::EIP7702(t) => t.tx().nonce, - Self::Deposit(_t) => 0, - } - } - - pub fn chain_id(&self) -> Option { - match self { - Self::Legacy(t) => t.tx().chain_id, - Self::EIP2930(t) => Some(t.tx().chain_id), - Self::EIP1559(t) => Some(t.tx().chain_id), - Self::EIP4844(t) => Some(t.tx().tx().chain_id), - Self::EIP7702(t) => Some(t.tx().chain_id), - Self::Deposit(t) => t.chain_id(), - } - } - - pub fn as_legacy(&self) -> Option<&Signed> { - match self { - Self::Legacy(tx) => Some(tx), - _ => None, - } - } - - /// Returns true whether this tx is a legacy transaction - pub fn is_legacy(&self) -> bool { - matches!(self, Self::Legacy(_)) - } - - /// Returns true whether this tx is a EIP1559 transaction - pub fn is_eip1559(&self) -> bool { - matches!(self, Self::EIP1559(_)) - } - - /// Returns true whether this tx is a EIP2930 transaction - pub fn is_eip2930(&self) -> bool { - matches!(self, Self::EIP2930(_)) - } - - /// Returns true whether this tx is a EIP4844 transaction - pub fn is_eip4844(&self) -> bool { - matches!(self, Self::EIP4844(_)) - } - - /// Returns the hash of the transaction. - /// - /// Note: If this transaction has the Impersonated signature then this returns a modified unique - /// hash. This allows us to treat impersonated transactions as unique. - pub fn hash(&self) -> B256 { - match self { - Self::Legacy(t) => *t.hash(), - Self::EIP2930(t) => *t.hash(), - Self::EIP1559(t) => *t.hash(), - Self::EIP4844(t) => *t.hash(), - Self::EIP7702(t) => *t.hash(), - Self::Deposit(t) => t.tx_hash(), - } - } - - /// Returns the hash if the transaction is impersonated (using a fake signature) - /// - /// This appends the `address` before hashing it - pub fn impersonated_hash(&self, sender: Address) -> B256 { - let mut buffer = Vec::new(); - Encodable::encode(self, &mut buffer); - buffer.extend_from_slice(sender.as_ref()); - B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice()) - } - - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - match self { - Self::Legacy(tx) => tx.recover_signer(), - Self::EIP2930(tx) => tx.recover_signer(), - Self::EIP1559(tx) => tx.recover_signer(), - Self::EIP4844(tx) => tx.recover_signer(), - Self::EIP7702(tx) => tx.recover_signer(), - Self::Deposit(tx) => Ok(tx.from), - } - } - - /// Returns what kind of transaction this is - pub fn kind(&self) -> TxKind { - match self { - Self::Legacy(tx) => tx.tx().to, - Self::EIP2930(tx) => tx.tx().to, - Self::EIP1559(tx) => tx.tx().to, - Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), - Self::EIP7702(tx) => TxKind::Call(tx.tx().to), - Self::Deposit(tx) => tx.to, - } - } - - /// Returns the callee if this transaction is a call - pub fn to(&self) -> Option
{ - self.kind().to().copied() - } - - /// Returns the Signature of the transaction - pub fn signature(&self) -> Signature { - match self { - Self::Legacy(tx) => *tx.signature(), - Self::EIP2930(tx) => *tx.signature(), - Self::EIP1559(tx) => *tx.signature(), - Self::EIP4844(tx) => *tx.signature(), - Self::EIP7702(tx) => *tx.signature(), - Self::Deposit(_) => Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ), - } +impl PendingTransaction { + pub fn new(transaction: T) -> Result { + let transaction = MaybeImpersonatedTransaction::new(transaction); + let sender = transaction.recover()?; + let hash = transaction.hash(); + Ok(Self { transaction, sender, hash }) } -} -impl Encodable for TypedTransaction { - fn encode(&self, out: &mut dyn bytes::BufMut) { - if !self.is_legacy() { - Header { list: false, payload_length: self.encode_2718_len() }.encode(out); - } - - self.encode_2718(out); + pub fn with_impersonated(transaction: T, sender: Address) -> Self { + let transaction = MaybeImpersonatedTransaction::impersonated(transaction, sender); + let hash = transaction.hash(); + Self { transaction, sender, hash } } -} - -impl Decodable for TypedTransaction { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let mut h_decode_copy = *buf; - let header = alloy_rlp::Header::decode(&mut h_decode_copy)?; - - // Legacy TX - if header.list { - return Ok(TxEnvelope::decode(buf)?.into()); - } - - // Check byte after header - let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; - if ty != 0x7E { - Ok(TxEnvelope::decode(buf)?.into()) + /// Converts a [`MaybeImpersonatedTransaction`] into a [`PendingTransaction`]. + pub fn from_maybe_impersonated( + transaction: MaybeImpersonatedTransaction, + ) -> Result { + if let Some(impersonated) = transaction.impersonated_sender { + Ok(Self::with_impersonated(transaction.transaction, impersonated)) } else { - Ok(Self::Deposit(TxDeposit::decode_2718(buf)?)) + Self::new(transaction.transaction) } } } -impl Typed2718 for TypedTransaction { - fn ty(&self) -> u8 { - self.r#type().unwrap_or(0) - } -} - -impl Encodable2718 for TypedTransaction { - fn encode_2718_len(&self) -> usize { - match self { - Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::Deposit(tx) => 1 + tx.length(), - } - } - - fn encode_2718(&self, out: &mut dyn BufMut) { - match self { - Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::Deposit(tx) => { - tx.encode_2718(out); - } - } - } -} - -impl Decodable2718 for TypedTransaction { - fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == 0x7E { - return Ok(Self::Deposit(TxDeposit::decode(buf)?)) - } - match TxEnvelope::typed_decode(ty, buf)? { - TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), - TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), - TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), - TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), - _ => unreachable!(), - } - } - - fn fallback_decode(buf: &mut &[u8]) -> Result { - match TxEnvelope::fallback_decode(buf)? { - TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), - _ => unreachable!(), - } - } -} - -impl From for TypedTransaction { - fn from(value: TxEnvelope) -> Self { - match value { - TxEnvelope::Legacy(tx) => Self::Legacy(tx), - TxEnvelope::Eip2930(tx) => Self::EIP2930(tx), - TxEnvelope::Eip1559(tx) => Self::EIP1559(tx), - TxEnvelope::Eip4844(tx) => Self::EIP4844(tx), - _ => unreachable!(), - } +impl PendingTransaction { + pub fn nonce(&self) -> u64 { + self.transaction.nonce() } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TransactionEssentials { - pub kind: TxKind, - pub input: Bytes, - pub nonce: u64, - pub gas_limit: u64, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub max_fee_per_blob_gas: Option, - pub blob_versioned_hashes: Option>, - pub value: U256, - pub chain_id: Option, - pub access_list: AccessList, -} - /// Represents all relevant information of an executed transaction #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct TransactionInfo { @@ -1067,635 +192,3 @@ pub struct TransactionInfo { pub nonce: u64, pub gas_used: u64, } - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct DepositReceipt> { - #[serde(flatten)] - pub inner: ReceiptWithBloom, - #[serde(default, with = "alloy_serde::quantity::opt")] - pub deposit_nonce: Option, - #[serde(default, with = "alloy_serde::quantity::opt")] - pub deposit_receipt_version: Option, -} - -impl DepositReceipt { - fn payload_len(&self) -> usize { - self.inner.receipt.status.length() + - self.inner.receipt.cumulative_gas_used.length() + - self.inner.logs_bloom.length() + - self.inner.receipt.logs.length() + - self.deposit_nonce.map_or(0, |n| n.length()) + - self.deposit_receipt_version.map_or(0, |n| n.length()) - } - - /// Returns the rlp header for the receipt payload. - fn receipt_rlp_header(&self) -> alloy_rlp::Header { - alloy_rlp::Header { list: true, payload_length: self.payload_len() } - } - - /// Encodes the receipt data. - fn encode_fields(&self, out: &mut dyn BufMut) { - self.receipt_rlp_header().encode(out); - self.inner.status().encode(out); - self.inner.receipt.cumulative_gas_used.encode(out); - self.inner.logs_bloom.encode(out); - self.inner.receipt.logs.encode(out); - if let Some(n) = self.deposit_nonce { - n.encode(out); - } - if let Some(n) = self.deposit_receipt_version { - n.encode(out); - } - } - - /// Decodes the receipt payload - fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result { - let b: &mut &[u8] = &mut &**buf; - let rlp_head = alloy_rlp::Header::decode(b)?; - if !rlp_head.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - let started_len = b.len(); - let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0; - - let status = Decodable::decode(b)?; - let cumulative_gas_used = Decodable::decode(b)?; - let logs_bloom = Decodable::decode(b)?; - let logs: Vec = Decodable::decode(b)?; - let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; - let deposit_nonce_version = - remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; - - let this = Self { - inner: ReceiptWithBloom { - receipt: Receipt { status, cumulative_gas_used, logs }, - logs_bloom, - }, - deposit_nonce, - deposit_receipt_version: deposit_nonce_version, - }; - - let consumed = started_len - b.len(); - if consumed != rlp_head.payload_length { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: rlp_head.payload_length, - got: consumed, - }); - } - - *buf = *b; - Ok(this) - } -} - -impl alloy_rlp::Encodable for DepositReceipt { - fn encode(&self, out: &mut dyn BufMut) { - self.encode_fields(out); - } - - fn length(&self) -> usize { - let payload_length = self.payload_len(); - payload_length + length_of_length(payload_length) - } -} - -impl alloy_rlp::Decodable for DepositReceipt { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - Self::decode_receipt(buf) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum TypedReceipt> { - #[serde(rename = "0x0", alias = "0x00")] - Legacy(ReceiptWithBloom), - #[serde(rename = "0x1", alias = "0x01")] - EIP2930(ReceiptWithBloom), - #[serde(rename = "0x2", alias = "0x02")] - EIP1559(ReceiptWithBloom), - #[serde(rename = "0x3", alias = "0x03")] - EIP4844(ReceiptWithBloom), - #[serde(rename = "0x4", alias = "0x04")] - EIP7702(ReceiptWithBloom), - #[serde(rename = "0x7E", alias = "0x7e")] - Deposit(DepositReceipt), -} - -impl TypedReceipt { - pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom { - match self { - Self::Legacy(r) | - Self::EIP1559(r) | - Self::EIP2930(r) | - Self::EIP4844(r) | - Self::EIP7702(r) => r, - Self::Deposit(r) => &r.inner, - } - } -} - -impl From> for ReceiptWithBloom { - fn from(value: TypedReceipt) -> Self { - match value { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::EIP4844(r) | - TypedReceipt::EIP7702(r) => r, - TypedReceipt::Deposit(r) => r.inner, - } - } -} - -impl From>> for OtsReceipt { - fn from(value: TypedReceipt>) -> Self { - let r#type = match value { - TypedReceipt::Legacy(_) => 0x00, - TypedReceipt::EIP2930(_) => 0x01, - TypedReceipt::EIP1559(_) => 0x02, - TypedReceipt::EIP4844(_) => 0x03, - TypedReceipt::EIP7702(_) => 0x04, - TypedReceipt::Deposit(_) => 0x7E, - } as u8; - let receipt = ReceiptWithBloom::>::from(value); - let status = receipt.status(); - let cumulative_gas_used = receipt.cumulative_gas_used(); - let logs = receipt.logs().to_vec(); - let logs_bloom = receipt.logs_bloom; - - Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type } - } -} - -impl TypedReceipt { - pub fn cumulative_gas_used(&self) -> u64 { - self.as_receipt_with_bloom().cumulative_gas_used() - } - - pub fn logs_bloom(&self) -> &Bloom { - &self.as_receipt_with_bloom().logs_bloom - } - - pub fn logs(&self) -> &[Log] { - self.as_receipt_with_bloom().logs() - } -} - -impl From> for TypedReceipt> { - fn from(value: ReceiptEnvelope) -> Self { - match value { - ReceiptEnvelope::Legacy(r) => Self::Legacy(r), - ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r), - ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r), - ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r), - _ => unreachable!(), - } - } -} - -impl Encodable for TypedReceipt { - fn encode(&self, out: &mut dyn bytes::BufMut) { - match self { - Self::Legacy(r) => r.encode(out), - receipt => { - let payload_len = match receipt { - Self::EIP2930(r) => r.length() + 1, - Self::EIP1559(r) => r.length() + 1, - Self::EIP4844(r) => r.length() + 1, - Self::Deposit(r) => r.length() + 1, - _ => unreachable!("receipt already matched"), - }; - - match receipt { - Self::EIP2930(r) => { - Header { list: true, payload_length: payload_len }.encode(out); - 1u8.encode(out); - r.encode(out); - } - Self::EIP1559(r) => { - Header { list: true, payload_length: payload_len }.encode(out); - 2u8.encode(out); - r.encode(out); - } - Self::EIP4844(r) => { - Header { list: true, payload_length: payload_len }.encode(out); - 3u8.encode(out); - r.encode(out); - } - Self::Deposit(r) => { - Header { list: true, payload_length: payload_len }.encode(out); - 0x7Eu8.encode(out); - r.encode(out); - } - _ => unreachable!("receipt already matched"), - } - } - } - } -} - -impl Decodable for TypedReceipt { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - use bytes::Buf; - use std::cmp::Ordering; - - // a receipt is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy receipt, so let's - // check if the first byte is between 0x80 and 0xbf. - let rlp_type = *buf - .first() - .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?; - - match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - let _header = Header::decode(buf)?; - let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom( - "typed receipt cannot be decoded from an empty slice", - ))?; - if receipt_type == 0x01 { - buf.advance(1); - ::decode(buf).map(TypedReceipt::EIP2930) - } else if receipt_type == 0x02 { - buf.advance(1); - ::decode(buf).map(TypedReceipt::EIP1559) - } else if receipt_type == 0x03 { - buf.advance(1); - ::decode(buf).map(TypedReceipt::EIP4844) - } else if receipt_type == 0x7E { - buf.advance(1); - ::decode(buf).map(TypedReceipt::Deposit) - } else { - Err(alloy_rlp::Error::Custom("invalid receipt type")) - } - } - Ordering::Equal => { - Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding")) - } - Ordering::Greater => { - ::decode(buf).map(TypedReceipt::Legacy) - } - } - } -} - -impl Typed2718 for TypedReceipt { - fn ty(&self) -> u8 { - match self { - Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID, - Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID, - Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID, - Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID, - Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID, - Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, - } - } -} - -impl Encodable2718 for TypedReceipt { - fn encode_2718_len(&self) -> usize { - match self { - Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(), - Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(), - Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(), - Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(), - Self::EIP7702(r) => 1 + r.length(), - Self::Deposit(r) => 1 + r.length(), - } - } - - fn encode_2718(&self, out: &mut dyn BufMut) { - if let Some(ty) = self.type_flag() { - out.put_u8(ty); - } - match self { - Self::Legacy(r) | - Self::EIP2930(r) | - Self::EIP1559(r) | - Self::EIP4844(r) | - Self::EIP7702(r) => r.encode(out), - Self::Deposit(r) => r.encode(out), - } - } -} - -impl Decodable2718 for TypedReceipt { - fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == 0x7E { - return Ok(Self::Deposit(DepositReceipt::decode(buf)?)); - } - match ReceiptEnvelope::typed_decode(ty, buf)? { - ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), - ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), - ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), - _ => unreachable!(), - } - } - - fn fallback_decode(buf: &mut &[u8]) -> Result { - match ReceiptEnvelope::fallback_decode(buf)? { - ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), - _ => unreachable!(), - } - } -} - -pub type ReceiptResponse = TransactionReceipt>>; - -pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option { - let WithOtherFields { - inner: - TransactionReceipt { - transaction_hash, - transaction_index, - block_hash, - block_number, - gas_used, - contract_address, - effective_gas_price, - from, - to, - blob_gas_price, - blob_gas_used, - inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type }, - }, - other, - } = receipt; - - Some(TransactionReceipt { - transaction_hash, - transaction_index, - block_hash, - block_number, - gas_used, - contract_address, - effective_gas_price, - from, - to, - blob_gas_price, - blob_gas_used, - inner: match r#type { - 0x00 => TypedReceipt::Legacy(receipt_with_bloom), - 0x01 => TypedReceipt::EIP2930(receipt_with_bloom), - 0x02 => TypedReceipt::EIP1559(receipt_with_bloom), - 0x03 => TypedReceipt::EIP4844(receipt_with_bloom), - 0x7E => TypedReceipt::Deposit(DepositReceipt { - inner: receipt_with_bloom, - deposit_nonce: other - .get_deserialized::("depositNonce") - .transpose() - .ok()? - .map(|v| v.to()), - deposit_receipt_version: other - .get_deserialized::("depositReceiptVersion") - .transpose() - .ok()? - .map(|v| v.to()), - }), - _ => return None, - }, - }) -} - -#[cfg(test)] -mod tests { - use alloy_primitives::{b256, hex, LogData}; - use std::str::FromStr; - - use super::*; - - #[test] - fn test_decode_call() { - let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; - let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap(); - - let tx = TxLegacy { - nonce: 2u64, - gas_price: 1000000000u128, - gas_limit: 100000, - to: TxKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: U256::from(1000000000000000u64), - input: Bytes::default(), - chain_id: Some(4), - }; - - let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); - - let tx = TypedTransaction::Legacy(Signed::new_unchecked( - tx, - signature, - b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"), - )); - - assert_eq!(tx, decoded); - } - - #[test] - fn test_decode_create_goerli() { - // test that an example create tx from goerli decodes properly - let tx_bytes = - hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471") - .unwrap(); - let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap(); - } - - #[test] - fn can_recover_sender() { - // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f - let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap(); - - let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { - panic!("decoding TypedTransaction failed"); - }; - - assert_eq!( - tx.hash(), - &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f" - .parse::() - .unwrap() - ); - assert_eq!( - tx.recover_signer().unwrap(), - "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::
().unwrap() - ); - } - - // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 - // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 - #[test] - fn test_decode_live_4844_tx() { - use alloy_primitives::{address, b256}; - - // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 - let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); - let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); - assert_eq!(res.r#type(), Some(3)); - - let tx = match res { - TypedTransaction::EIP4844(tx) => tx, - _ => unreachable!(), - }; - - assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064")); - - assert_eq!( - tx.tx().tx().blob_versioned_hashes, - vec![ - b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"), - b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), - b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), - b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), - b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") - ] - ); - - let from = tx.recover_signer().unwrap(); - assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); - } - - #[test] - fn test_decode_encode_deposit_tx() { - // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" - .parse::() - .unwrap(); - - // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 - let raw_tx = alloy_primitives::hex::decode( - "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", - ) - .unwrap(); - let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); - - let mut encoded = Vec::new(); - dep_tx.encode_2718(&mut encoded); - - assert_eq!(raw_tx, encoded); - - assert_eq!(tx_hash, dep_tx.hash()); - } - - #[test] - fn can_recover_sender_not_normalized() { - let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); - - let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { - panic!("decoding TypedTransaction failed"); - }; - - assert_eq!(tx.tx().input, Bytes::from(b"")); - assert_eq!(tx.tx().gas_price, 1); - assert_eq!(tx.tx().gas_limit, 21000); - assert_eq!(tx.tx().nonce, 0); - if let TxKind::Call(to) = tx.tx().to { - assert_eq!( - to, - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::
().unwrap() - ); - } else { - panic!("expected a call transaction"); - } - assert_eq!(tx.tx().value, U256::from(0x0au64)); - assert_eq!( - tx.recover_signer().unwrap(), - "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::
().unwrap() - ); - } - - #[test] - fn encode_legacy_receipt() { - let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let mut data = vec![]; - let receipt = TypedReceipt::Legacy(ReceiptWithBloom { - receipt: Receipt { - status: false.into(), - cumulative_gas_used: 0x1, - logs: vec![Log { - address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), - data: LogData::new_unchecked( - vec![ - B256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - B256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - Bytes::from_str("0100ff").unwrap(), - ), - }], - }, - logs_bloom: [0; 256].into(), - }); - - receipt.encode(&mut data); - - // check that the rlp length equals the length of the expected rlp - assert_eq!(receipt.length(), expected.len()); - assert_eq!(data, expected); - } - - #[test] - fn decode_legacy_receipt() { - let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let expected = TypedReceipt::Legacy(ReceiptWithBloom { - receipt: Receipt { - status: false.into(), - cumulative_gas_used: 0x1, - logs: vec![Log { - address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), - data: LogData::new_unchecked( - vec![ - B256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - B256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - Bytes::from_str("0100ff").unwrap(), - ), - }], - }, - logs_bloom: [0; 256].into(), - }); - - let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); - - assert_eq!(receipt, expected); - } - - #[test] - fn deser_to_type_tx() { - let tx = r#" - { - "EIP1559": { - "chainId": "0x7a69", - "nonce": "0x0", - "gas": "0x5209", - "maxFeePerGas": "0x77359401", - "maxPriorityFeePerGas": "0x1", - "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "value": "0x0", - "accessList": [], - "input": "0x", - "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", - "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", - "yParity": "0x0", - "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" - } - }"#; - - let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap(); - } -} diff --git a/crates/anvil/core/src/eth/wallet.rs b/crates/anvil/core/src/eth/wallet.rs index 8676ec2fbf053..0fe703e1be0d0 100644 --- a/crates/anvil/core/src/eth/wallet.rs +++ b/crates/anvil/core/src/eth/wallet.rs @@ -1,63 +1,28 @@ -use alloy_primitives::{map::HashMap, Address, ChainId, U64}; -use serde::{Deserialize, Serialize}; - -/// The capability to perform [EIP-7702][eip-7702] delegations, sponsored by the sequencer. -/// -/// The sequencer will only perform delegations, and act on behalf of delegated accounts, if the -/// account delegates to one of the addresses specified within this capability. -/// -/// [eip-7702]: https://eips.ethereum.org/EIPS/eip-7702 -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] -pub struct DelegationCapability { - /// A list of valid delegation contracts. - pub addresses: Vec
, -} - -/// Wallet capabilities for a specific chain. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] -pub struct Capabilities { - /// The capability to delegate. - pub delegation: DelegationCapability, -} - -/// A map of wallet capabilities per chain ID. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] -pub struct WalletCapabilities(HashMap); - -impl WalletCapabilities { - /// Get the capabilities of the wallet API for the specified chain ID. - pub fn get(&self, chain_id: ChainId) -> Option<&Capabilities> { - self.0.get(&U64::from(chain_id)) - } - - pub fn insert(&mut self, chain_id: ChainId, capabilities: Capabilities) { - self.0.insert(U64::from(chain_id), capabilities); - } -} +pub use alloy_eip5792::*; #[derive(Debug, thiserror::Error)] pub enum WalletError { /// The transaction value is not 0. /// /// The value should be 0 to prevent draining the sequencer. - #[error("tx value not zero")] + #[error("transaction value must be zero for delegated transactions")] ValueNotZero, /// The from field is set on the transaction. /// /// Requests with the from field are rejected, since it is implied that it will always be the /// sequencer. - #[error("tx from field is set")] + #[error("transaction 'from' field should not be set for delegated transactions")] FromSet, /// The nonce field is set on the transaction. /// /// Requests with the nonce field set are rejected, as this is managed by the sequencer. - #[error("tx nonce is set")] + #[error("transaction nonce should not be set for delegated transactions")] NonceSet, /// An authorization item was invalid. /// /// The item is invalid if it tries to delegate an account to a contract that is not /// whitelisted. - #[error("invalid authorization address")] + #[error("invalid authorization address: contract is not whitelisted for delegation")] InvalidAuthorization, /// The to field of the transaction was invalid. /// @@ -66,14 +31,14 @@ pub enum WalletError { /// - There is no bytecode at the destination, or /// - The bytecode is not an EIP-7702 delegation designator, or /// - The delegation designator points to a contract that is not whitelisted - #[error("the destination of the transaction is not a delegated account")] + #[error("transaction destination is not a valid delegated account")] IllegalDestination, /// The transaction request was invalid. /// /// This is likely an internal error, as most of the request is built by the sequencer. - #[error("invalid tx request")] + #[error("invalid transaction request format")] InvalidTransactionRequest, /// An internal error occurred. - #[error("internal error")] + #[error("internal server error occurred")] InternalError, } diff --git a/crates/anvil/core/src/lib.rs b/crates/anvil/core/src/lib.rs index 5d51d4691da7c..82412439ddb7f 100644 --- a/crates/anvil/core/src/lib.rs +++ b/crates/anvil/core/src/lib.rs @@ -3,7 +3,7 @@ //! Core Ethereum types for Anvil. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] /// Various Ethereum types pub mod eth; diff --git a/crates/anvil/rpc/src/error.rs b/crates/anvil/rpc/src/error.rs index 4eec040acf59c..2e5877d5f2b8b 100644 --- a/crates/anvil/rpc/src/error.rs +++ b/crates/anvil/rpc/src/error.rs @@ -84,7 +84,7 @@ pub enum ErrorCode { InvalidParams, /// internal call error InternalError, - /// Failed to send transaction, See also + /// Failed to send transaction, See also TransactionRejected, /// Custom geth error code, ExecutionError, @@ -94,7 +94,7 @@ pub enum ErrorCode { impl ErrorCode { /// Returns the error code as `i64` - pub fn code(&self) -> i64 { + pub const fn code(&self) -> i64 { match *self { Self::ParseError => -32700, Self::InvalidRequest => -32600, diff --git a/crates/anvil/rpc/src/lib.rs b/crates/anvil/rpc/src/lib.rs index bd5382cee17ad..328f5f8564ee1 100644 --- a/crates/anvil/rpc/src/lib.rs +++ b/crates/anvil/rpc/src/lib.rs @@ -3,7 +3,7 @@ //! JSON-RPC types. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] /// JSON-RPC request bindings pub mod request; diff --git a/crates/anvil/rpc/src/request.rs b/crates/anvil/rpc/src/request.rs index 5cb8510b80f2b..eade8b25fe3cd 100644 --- a/crates/anvil/rpc/src/request.rs +++ b/crates/anvil/rpc/src/request.rs @@ -84,7 +84,7 @@ impl From for serde_json::Value { } } -fn no_params() -> RequestParams { +const fn no_params() -> RequestParams { RequestParams::None } @@ -113,7 +113,7 @@ impl fmt::Display for Id { } } -fn null_id() -> Id { +const fn null_id() -> Id { Id::Null } diff --git a/crates/anvil/rpc/src/response.rs b/crates/anvil/rpc/src/response.rs index 2fd01327d80a3..b7f2d9f478092 100644 --- a/crates/anvil/rpc/src/response.rs +++ b/crates/anvil/rpc/src/response.rs @@ -10,21 +10,20 @@ use serde::{Deserialize, Serialize}; pub struct RpcResponse { // JSON RPC version jsonrpc: Version, - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, + id: Id, #[serde(flatten)] result: ResponseResult, } impl From for RpcResponse { fn from(e: RpcError) -> Self { - Self { jsonrpc: Version::V2, id: None, result: ResponseResult::Error(e) } + Self { jsonrpc: Version::V2, id: Id::Null, result: ResponseResult::Error(e) } } } impl RpcResponse { pub fn new(id: Id, content: impl Into) -> Self { - Self { jsonrpc: Version::V2, id: Some(id), result: content.into() } + Self { jsonrpc: Version::V2, id, result: content.into() } } pub fn invalid_request(id: Id) -> Self { @@ -50,7 +49,7 @@ impl ResponseResult { Self::Success(serde_json::to_value(&content).unwrap()) } - pub fn error(error: RpcError) -> Self { + pub const fn error(error: RpcError) -> Self { Self::Error(error) } } diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index dd15959b113a6..d2a2d4430131e 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -27,7 +27,7 @@ impl ServerConfig { } /// Whether to enable CORS. - pub fn set_cors(mut self, cors: bool) -> Self { + pub const fn set_cors(mut self, cors: bool) -> Self { self.no_cors = !cors; self } diff --git a/crates/anvil/server/src/handler.rs b/crates/anvil/server/src/handler.rs index 844cf6a46e0f1..95659d9eefbf6 100644 --- a/crates/anvil/server/src/handler.rs +++ b/crates/anvil/server/src/handler.rs @@ -5,26 +5,30 @@ use anvil_rpc::{ response::{Response, RpcResponse}, }; use axum::{ - extract::{rejection::JsonRejection, State}, Json, + extract::{State, rejection::JsonRejection}, + http::StatusCode, + response::{IntoResponse, Response as AxumResponse}, }; -use futures::{future, FutureExt}; +use futures::{FutureExt, future}; /// Handles incoming JSON-RPC Request. // NOTE: `handler` must come first because the `request` extractor consumes the request body. pub async fn handle( State((handler, _)): State<(Http, Ws)>, request: Result, JsonRejection>, -) -> Json { - Json(match request { +) -> AxumResponse { + match request { Ok(Json(req)) => handle_request(req, handler) .await - .unwrap_or_else(|| Response::error(RpcError::invalid_request())), + .map(Json) + .map(IntoResponse::into_response) + .unwrap_or_else(|| StatusCode::NO_CONTENT.into_response()), Err(err) => { warn!(target: "rpc", ?err, "invalid request"); - Response::error(RpcError::invalid_request()) + Json(Response::error(RpcError::invalid_request())).into_response() } - }) + } } /// Handle the JSON-RPC [Request] @@ -44,6 +48,11 @@ pub async fn handle_request( match req { Request::Single(call) => handle_call(call, handler).await.map(Response::Single), Request::Batch(calls) => { + if calls.is_empty() { + return Some(Response::Batch(vec![anvil_rpc::response::RpcResponse::from( + RpcError::invalid_request(), + )])); + } future::join_all(calls.into_iter().map(move |call| handle_call(call, handler.clone()))) .map(responses_as_batch) .await @@ -68,3 +77,72 @@ async fn handle_call(call: RpcCall, handler: Handler) -> Op } } } + +#[cfg(test)] +mod tests { + use super::*; + use anvil_rpc::{ + request::{RequestParams, RpcNotification, Version}, + response::ResponseResult, + }; + use axum::body::to_bytes; + use std::{ + pin::pin, + task::{Context, Poll, Waker}, + }; + + #[derive(Clone)] + struct TestHandler; + + #[async_trait::async_trait] + impl RpcHandler for TestHandler { + type Request = serde_json::Value; + + async fn on_request(&self, request: Self::Request) -> ResponseResult { + ResponseResult::success(request) + } + } + + fn notification() -> RpcCall { + RpcCall::Notification(RpcNotification { + jsonrpc: Some(Version::V2), + method: "eth_subscribe".to_owned(), + params: RequestParams::None, + }) + } + + fn run_ready(future: F) -> F::Output { + let waker = Waker::noop(); + let mut cx = Context::from_waker(waker); + let mut future = pin!(future); + match future.as_mut().poll(&mut cx) { + Poll::Ready(output) => output, + Poll::Pending => panic!("future unexpectedly pending"), + } + } + + #[test] + fn empty_batch_returns_invalid_request() { + let response = run_ready(handle_request(Request::Batch(vec![]), TestHandler)); + + assert_eq!(response, Some(Response::error(RpcError::invalid_request()))); + } + + #[test] + fn notification_only_batch_returns_no_response() { + let response = run_ready(handle_request(Request::Batch(vec![notification()]), TestHandler)); + + assert_eq!(response, None); + } + + #[test] + fn http_notification_only_batch_returns_no_content() { + let response = run_ready(handle( + State((TestHandler, ())), + Ok(Json(Request::Batch(vec![notification()]))), + )); + + assert_eq!(response.status(), StatusCode::NO_CONTENT); + assert!(run_ready(to_bytes(response.into_body(), usize::MAX)).unwrap().is_empty()); + } +} diff --git a/crates/anvil/server/src/ipc.rs b/crates/anvil/server/src/ipc.rs index 392eb47acfd46..757cdb05b2a2e 100644 --- a/crates/anvil/server/src/ipc.rs +++ b/crates/anvil/server/src/ipc.rs @@ -1,12 +1,11 @@ //! IPC handling -use crate::{error::RequestError, pubsub::PubSubConnection, PubSubRpcHandler}; +use crate::{PubSubRpcHandler, error::RequestError, pubsub::PubSubConnection}; use anvil_rpc::request::Request; use bytes::{BufMut, BytesMut}; -use futures::{ready, Sink, Stream, StreamExt}; +use futures::{Sink, Stream, StreamExt, ready}; use interprocess::local_socket::{self as ls, tokio::prelude::*}; use std::{ - future::Future, io, pin::Pin, task::{Context, Poll}, @@ -24,7 +23,7 @@ pub struct IpcEndpoint { impl IpcEndpoint { /// Creates a new endpoint with the given handler - pub fn new(handler: Handler, path: String) -> Self { + pub const fn new(handler: Handler, path: String) -> Self { Self { handler, path } } @@ -159,7 +158,7 @@ impl tokio_util::codec::Decoder for JsonRpcCodec { return match String::from_utf8(bts.as_ref().to_vec()) { Ok(val) => Ok(Some(val)), Err(_) => Ok(None), - } + }; } } Ok(None) diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index 07567466772e5..5ff7501bdbe1f 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -1,7 +1,7 @@ //! Bootstrap [axum] RPC servers. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; @@ -12,10 +12,10 @@ use anvil_rpc::{ response::{ResponseResult, RpcResponse}, }; use axum::{ - extract::DefaultBodyLimit, - http::{header, HeaderValue, Method}, - routing::{post, MethodRouter}, Router, + extract::DefaultBodyLimit, + http::{HeaderValue, Method, header}, + routing::{MethodRouter, post}, }; use serde::de::DeserializeOwned; use std::fmt; diff --git a/crates/anvil/server/src/pubsub.rs b/crates/anvil/server/src/pubsub.rs index 8e5ac9b3849bb..55c774505c9b7 100644 --- a/crates/anvil/server/src/pubsub.rs +++ b/crates/anvil/server/src/pubsub.rs @@ -1,4 +1,4 @@ -use crate::{error::RequestError, handler::handle_request, RpcHandler}; +use crate::{RpcHandler, error::RequestError, handler::handle_request}; use anvil_rpc::{ error::RpcError, request::Request, @@ -11,7 +11,6 @@ use serde::de::DeserializeOwned; use std::{ collections::VecDeque, fmt, - future::Future, hash::Hash, pin::Pin, sync::Arc, @@ -68,7 +67,7 @@ impl PubSubContext { let mut subscriptions = self.subscriptions.lock(); if let Some(idx) = subscriptions.iter().position(|(i, _)| id == i) { trace!(target: "rpc", ?id, "removed subscription"); - return Some(subscriptions.swap_remove(idx).1) + return Some(subscriptions.swap_remove(idx).1); } None } @@ -118,7 +117,7 @@ pub struct PubSubConnection { /// The established connection connection: Connection, /// currently in progress requests - processing: Vec + Send>>>, + processing: Vec> + Send>>>, /// pending messages to send pending: VecDeque, } @@ -143,12 +142,10 @@ impl PubSubConnection handle_request(req, handler) - .await - .unwrap_or_else(|| Response::error(RpcError::invalid_request())), + Ok(req) => handle_request(req, handler).await, Err(err) => { error!(target: "rpc", ?err, "invalid request"); - Response::error(RpcError::invalid_request()) + Some(Response::error(RpcError::invalid_request())) } } })); @@ -174,7 +171,7 @@ where error!(target: "rpc", ?err, "Failed to send message"); } } else { - break + break; } } @@ -183,7 +180,7 @@ where if let Poll::Ready(Err(err)) = pin.connection.poll_flush_unpin(cx) { trace!(target: "rpc", ?err, "websocket err"); // close the connection - return Poll::Ready(()) + return Poll::Ready(()); } loop { @@ -195,25 +192,25 @@ where Err(err) => match err { RequestError::Axum(err) => { trace!(target: "rpc", ?err, "client disconnected"); - return Poll::Ready(()) + return Poll::Ready(()); } RequestError::Io(err) => { trace!(target: "rpc", ?err, "client disconnected"); - return Poll::Ready(()) + return Poll::Ready(()); } RequestError::Serde(err) => { pin.process_request(Err(err)); } RequestError::Disconnect => { trace!(target: "rpc", "client disconnected"); - return Poll::Ready(()) + return Poll::Ready(()); } }, _ => {} }, Poll::Ready(None) => { trace!(target: "rpc", "socket connection finished"); - return Poll::Ready(()) + return Poll::Ready(()); } Poll::Pending => break, } @@ -222,13 +219,15 @@ where let mut progress = false; for n in (0..pin.processing.len()).rev() { let mut req = pin.processing.swap_remove(n); + #[allow(clippy::collapsible_match)] match req.poll_unpin(cx) { - Poll::Ready(resp) => { + Poll::Ready(Some(resp)) => { if let Ok(text) = serde_json::to_string(&resp) { pin.pending.push_back(text); progress = true; } } + Poll::Ready(None) => {} Poll::Pending => pin.processing.push(req), } } @@ -239,6 +238,7 @@ where 'outer: for n in (0..subscriptions.len()).rev() { let (id, mut sub) = subscriptions.swap_remove(n); 'inner: loop { + #[allow(clippy::collapsible_match)] match sub.poll_next_unpin(cx) { Poll::Ready(Some(res)) => { if let Ok(text) = serde_json::to_string(&res) { @@ -256,8 +256,75 @@ where } if !progress { - return Poll::Pending + return Poll::Pending; } } } } + +#[cfg(test)] +mod tests { + use super::*; + use anvil_rpc::{ + request::{RequestParams, RpcCall, RpcNotification, Version}, + response::RpcResponse, + }; + use std::{pin::pin, task::Waker}; + + #[derive(Clone)] + struct TestHandler; + + #[async_trait::async_trait] + impl PubSubRpcHandler for TestHandler { + type Request = serde_json::Value; + type SubscriptionId = u64; + type Subscription = futures::stream::Empty; + + async fn on_request( + &self, + _request: Self::Request, + _cx: PubSubContext, + ) -> ResponseResult { + ResponseResult::success(serde_json::Value::Null) + } + } + + fn notification() -> RpcCall { + RpcCall::Notification(RpcNotification { + jsonrpc: Some(Version::V2), + method: "eth_subscribe".to_owned(), + params: RequestParams::None, + }) + } + + fn run_ready(future: F) -> F::Output { + let waker = Waker::noop(); + let mut cx = Context::from_waker(waker); + let mut future = pin!(future); + match future.as_mut().poll(&mut cx) { + Poll::Ready(output) => output, + Poll::Pending => panic!("future unexpectedly pending"), + } + } + + #[test] + fn process_request_keeps_empty_batch_invalid() { + let mut connection = PubSubConnection::new((), TestHandler); + connection.process_request(Ok(Request::Batch(vec![]))); + + let response = run_ready(connection.processing.pop().unwrap()); + assert_eq!( + response, + Some(Response::Single(RpcResponse::from(RpcError::invalid_request()))) + ); + } + + #[test] + fn process_request_skips_notification_only_batch_response() { + let mut connection = PubSubConnection::new((), TestHandler); + connection.process_request(Ok(Request::Batch(vec![notification()]))); + + let response = run_ready(connection.processing.pop().unwrap()); + assert_eq!(response, None); + } +} diff --git a/crates/anvil/server/src/ws.rs b/crates/anvil/server/src/ws.rs index c082414e9e2d0..531641455feae 100644 --- a/crates/anvil/server/src/ws.rs +++ b/crates/anvil/server/src/ws.rs @@ -1,13 +1,13 @@ -use crate::{error::RequestError, pubsub::PubSubConnection, PubSubRpcHandler}; +use crate::{PubSubRpcHandler, error::RequestError, pubsub::PubSubConnection}; use anvil_rpc::request::Request; use axum::{ extract::{ - ws::{Message, WebSocket}, State, WebSocketUpgrade, + ws::{Message, WebSocket}, }, response::Response, }; -use futures::{ready, Sink, Stream}; +use futures::{Sink, Stream, ready}; use std::{ pin::Pin, task::{Context, Poll}, diff --git a/crates/anvil/src/args.rs b/crates/anvil/src/args.rs index 336c42742e6cb..4dee7f830fa15 100644 --- a/crates/anvil/src/args.rs +++ b/crates/anvil/src/args.rs @@ -1,12 +1,14 @@ use crate::opts::{Anvil, AnvilSubcommand}; use clap::{CommandFactory, Parser}; use eyre::Result; -use foundry_cli::{handler, utils}; +use foundry_cli::utils; /// Run the `anvil` command line interface. pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let mut args = Anvil::parse(); args.global.init()?; args.node.evm.resolve_rpc_alias(); @@ -16,10 +18,7 @@ pub fn run() -> Result<()> { /// Setup the exception handler and other utilities. pub fn setup() -> Result<()> { - utils::install_crypto_provider(); - handler::install(); - utils::load_dotenv(); - utils::enable_paint(); + utils::common_setup(); Ok(()) } @@ -36,18 +35,12 @@ pub fn run_command(args: Anvil) -> Result<()> { &mut std::io::stdout(), ); } - AnvilSubcommand::GenerateFigSpec => clap_complete::generate( - clap_complete_fig::Fig, - &mut Anvil::command(), - "anvil", - &mut std::io::stdout(), - ), } - return Ok(()) + return Ok(()); } let _ = fdlimit::raise_fd_limit(); - tokio::runtime::Builder::new_multi_thread().enable_all().build()?.block_on(args.node.run()) + args.global.tokio_runtime().block_on(args.node.run()) } #[cfg(test)] @@ -79,7 +72,9 @@ mod tests { let args: Anvil = Anvil::parse_from(["anvil", "completions", "bash"]); assert!(matches!( args.cmd, - Some(AnvilSubcommand::Completions { shell: clap_complete::Shell::Bash }) + Some(AnvilSubcommand::Completions { + shell: foundry_cli::clap::Shell::ClapCompleteShell(clap_complete::Shell::Bash) + }) )); } } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 4ca9624d57f6e..57bdfd988773b 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,32 +1,37 @@ use crate::{ - config::{ForkChoice, DEFAULT_MNEMONIC}, - eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID, + AccountGenerator, CHAIN_ID, NodeConfig, + config::{DEFAULT_MNEMONIC, ForkChoice}, + eth::{EthApi, backend::db::SerializableState, pool::transactions::TransactionOrder}, }; use alloy_genesis::Genesis; -use alloy_op_hardforks::OpHardfork; -use alloy_primitives::{utils::Unit, B256, U256}; +use alloy_network::Network; +use alloy_primitives::{Address, B256, U256, map::HashMap, utils::Unit}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; +#[cfg(feature = "optimism")] +use foundry_evm::hardfork::OpHardfork; +use foundry_evm::hardfork::{EthereumHardfork, FoundryHardfork}; +use foundry_evm_networks::NetworkConfigs; +use foundry_primitives::FoundryReceiptEnvelope; use futures::FutureExt; -use rand_08::{rngs::StdRng, SeedableRng}; +use rand_08::{SeedableRng, rngs::StdRng}; use std::{ - future::Future, net::IpAddr, path::{Path, PathBuf}, pin::Pin, str::FromStr, sync::{ - atomic::{AtomicUsize, Ordering}, Arc, + atomic::{AtomicUsize, Ordering}, }, task::{Context, Poll}, time::Duration, }; +use tempo_chainspec::hardfork::TempoHardfork; use tokio::time::{Instant, Interval}; #[derive(Clone, Debug, Parser)] @@ -100,7 +105,7 @@ pub struct NodeArgs { #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] pub no_mining: bool, - #[arg(long, visible_alias = "mixed-mining", requires = "block_time")] + #[arg(long, requires = "block_time")] pub mixed_mining: bool, /// The hosts the server will listen on. @@ -168,6 +173,13 @@ pub struct NodeArgs { )] pub load_state: Option, + /// Fund specific accounts with custom balances on startup. + /// + /// Accepts multiple address:balance pairs where balance is in ETH. + /// Example: --fund-accounts 0x1234...5678:1000 0xabcd...ef01:5000 + #[arg(long, value_name = "ADDRESS:AMOUNT", value_delimiter = ' ', num_args = 1..)] + pub fund_accounts: Vec, + #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] pub ipc: Option>, @@ -188,13 +200,21 @@ pub struct NodeArgs { #[arg(long)] pub transaction_block_keeper: Option, + /// Maximum number of transactions in a block. + #[arg(long)] + pub max_transactions: Option, + #[command(flatten)] pub evm: AnvilEvmArgs, #[command(flatten)] pub server_config: ServerConfig, - /// Path to the cache directory where states are stored. + /// Path to the cache directory where persisted states are stored (see + /// `--max-persisted-states`). + /// + /// Note: This does not affect the fork RPC cache location (`storage.json`), which is stored in + /// `~/.foundry/cache/rpc///`. #[arg(long, value_name = "PATH")] pub cache_path: Option, } @@ -216,20 +236,29 @@ impl NodeArgs { let compute_units_per_second = if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second }; - let hardfork = match &self.hardfork { - Some(hf) => { - if self.evm.optimism { - Some(OpHardfork::from_str(hf)?.into()) - } else { - Some(EthereumHardfork::from_str(hf)?.into()) + // Validate that secondary fork URLs don't have conflicting block number suffixes + if self.evm.fork_url.len() > 1 { + for fork in &self.evm.fork_url[1..] { + if fork.block.is_some() { + eyre::bail!( + "Block number suffixes (@block) on secondary --fork-url values are not supported. \ + Use --fork-block-number to set the fork block for all endpoints." + ); } } + } + + let funded_accounts = self.parse_funded_accounts()?; + + let hardfork = match &self.hardfork { + Some(hf) => Some(parse_hardfork(hf, &self.evm.networks)?), None => None, }; Ok(NodeConfig::default() .with_gas_limit(self.evm.gas_limit) .disable_block_gas_limit(self.evm.disable_block_gas_limit) + .enable_tx_gas_limit(self.evm.enable_tx_gas_limit) .with_gas_price(self.evm.gas_price) .with_hardfork(hardfork) .with_blocktime(self.block_time) @@ -246,7 +275,7 @@ impl NodeArgs { _ => self .evm .fork_url - .as_ref() + .first() .and_then(|f| f.block) .map(|num| ForkChoice::Block(num as i128)), }) @@ -256,10 +285,10 @@ impl NodeArgs { .fork_request_retries(self.evm.fork_request_retries) .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) - .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) + .with_fork_urls(self.evm.fork_url.into_iter().map(|f| f.url).collect()) .with_base_fee(self.evm.block_base_fee_per_gas) .disable_min_priority_fee(self.evm.disable_min_priority_fee) - .with_storage_caching(self.evm.no_storage_caching) + .with_no_storage_caching(self.evm.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) .set_silent(shell::is_quiet()) @@ -277,37 +306,68 @@ impl NodeArgs { .set_pruned_history(self.prune_history) .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) + .with_max_transactions(self.max_transactions) .with_max_persisted_states(self.max_persisted_states) - .with_optimism(self.evm.optimism) - .with_odyssey(self.evm.odyssey) + .with_networks(self.evm.networks) .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer) + .with_disable_pool_balance_checks(self.evm.disable_pool_balance_checks) .with_slots_in_an_epoch(self.slots_in_an_epoch) .with_memory_limit(self.evm.memory_limit) - .with_cache_path(self.cache_path)) + .with_cache_path(self.cache_path) + .with_funded_accounts(funded_accounts)) + } + + fn parse_funded_accounts(&self) -> eyre::Result> { + let mut accounts = HashMap::default(); + for entry in &self.fund_accounts { + let parts: Vec<&str> = entry.split(':').collect(); + if parts.len() != 2 { + eyre::bail!( + "Invalid fund-accounts entry '{}'. Expected format: ADDRESS:AMOUNT", + entry + ); + } + let address = parts[0] + .parse::
() + .map_err(|e| eyre::eyre!("Invalid address '{}': {}", parts[0], e))?; + let amount: u64 = parts[1] + .parse() + .map_err(|e| eyre::eyre!("Invalid amount '{}': {}", parts[1], e))?; + let balance = Unit::ETHER.wei().saturating_mul(U256::from(amount)); + accounts.insert(address, balance); + } + Ok(accounts) } fn account_generator(&self) -> AccountGenerator { - let mut gen = AccountGenerator::new(self.accounts as usize) + let mut generator = AccountGenerator::new(self.accounts as usize) .phrase(DEFAULT_MNEMONIC) .chain_id(self.evm.chain_id.unwrap_or(CHAIN_ID.into())); if let Some(ref mnemonic) = self.mnemonic { - gen = gen.phrase(mnemonic); + generator = generator.phrase(mnemonic); } else if let Some(count) = self.mnemonic_random { let mut rng = rand_08::thread_rng(); let mnemonic = match Mnemonic::::new_with_count(&mut rng, count) { Ok(mnemonic) => mnemonic.to_phrase(), - Err(_) => DEFAULT_MNEMONIC.to_string(), + Err(err) => { + warn!(target: "node", ?count, %err, "failed to generate mnemonic, falling back to 12-word random mnemonic"); + // Fallback: generate a valid 12-word random mnemonic instead of using + // DEFAULT_MNEMONIC + Mnemonic::::new_with_count(&mut rng, 12) + .expect("valid default word count") + .to_phrase() + } }; - gen = gen.phrase(mnemonic); + generator = generator.phrase(mnemonic); } else if let Some(seed) = self.mnemonic_seed { let mut seed = StdRng::seed_from_u64(seed); let mnemonic = Mnemonic::::new(&mut seed).to_phrase(); - gen = gen.phrase(mnemonic); + generator = generator.phrase(mnemonic); } if let Some(ref derivation) = self.derivation_path { - gen = gen.derivation_path(derivation); + generator = generator.derivation_path(derivation); } - gen + generator } /// Returns the location where to dump the state to. @@ -404,6 +464,10 @@ pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. + /// + /// Multiple `--fork-url` flags can be provided to distribute requests across endpoints + /// using round-robin load balancing. On failure, the retry layer rotates to the next + /// endpoint. #[arg( long, short, @@ -411,7 +475,7 @@ pub struct AnvilEvmArgs { value_name = "URL", help_heading = "Fork config" )] - pub fork_url: Option, + pub fork_url: Vec, /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// @@ -450,7 +514,7 @@ pub struct AnvilEvmArgs { )] pub fork_block_number: Option, - /// Fetch state from a specific transaction hash over a remote endpoint. + /// Fetch state from after a specific transaction hash has been applied over a remote endpoint. /// /// See --fork-url. #[arg( @@ -533,6 +597,10 @@ pub struct AnvilEvmArgs { )] pub disable_block_gas_limit: bool, + /// Enable the transaction gas limit check as imposed by EIP-7825 (Osaka hardfork). + #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")] + pub enable_tx_gas_limit: bool, + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] @@ -585,50 +653,80 @@ pub struct AnvilEvmArgs { #[arg(long, visible_alias = "auto-unlock")] pub auto_impersonate: bool, - /// Run an Optimism chain - #[arg(long, visible_alias = "optimism")] - pub optimism: bool, - /// Disable the default create2 deployer #[arg(long, visible_alias = "no-create2")] pub disable_default_create2_deployer: bool, + /// Disable pool balance checks + #[arg(long)] + pub disable_pool_balance_checks: bool, + /// The memory limit per EVM execution in bytes. #[arg(long)] pub memory_limit: Option, - /// Enable Odyssey features - #[arg(long, alias = "alphanet")] - pub odyssey: bool, + #[command(flatten)] + pub networks: NetworkConfigs, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section /// of the project configuration file. /// Does nothing if the fork-url is not a configured alias. +/// +/// When an alias maps to an `RpcEndpoint` with multiple `endpoints`, all URLs are expanded +/// into additional `--fork-url` entries for multi-endpoint load balancing. impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { - if let Some(fork_url) = &self.fork_url { - if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { - if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { - self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); + if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { + let mut resolved_urls = Vec::new(); + for fork_url in &self.fork_url { + let mut endpoints = config.rpc_endpoints.clone().resolved(); + if let Some(endpoint) = endpoints.remove(&fork_url.url) { + // Alias matched — expand all URLs from the endpoint config + match endpoint.all_urls() { + Ok(urls) => { + for (i, url) in urls.into_iter().enumerate() { + resolved_urls.push(ForkUrl { + url, + // Only the first URL inherits the block suffix + block: if i == 0 { fork_url.block } else { None }, + }); + } + } + Err(e) => { + warn!(target: "node", alias=%fork_url.url, %e, "could not resolve all endpoints, using primary endpoint only"); + if let Ok(url) = endpoint.url() { + resolved_urls.push(ForkUrl { url, block: fork_url.block }); + } else { + resolved_urls.push(fork_url.clone()); + } + } + } + } else if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { + // Try mesc or other resolution + resolved_urls.push(ForkUrl { url: url.to_string(), block: fork_url.block }); + } else { + // Not an alias — keep as-is + resolved_urls.push(fork_url.clone()); } } + self.fork_url = resolved_urls; } } } /// Helper type to periodically dump the state of the chain to disk -struct PeriodicStateDumper { +struct PeriodicStateDumper { in_progress_dump: Option + Send + Sync + 'static>>>, - api: EthApi, + api: EthApi, dump_state: Option, preserve_historical_states: bool, interval: Interval, } -impl PeriodicStateDumper { +impl> PeriodicStateDumper { fn new( - api: EthApi, + api: EthApi, dump_state: Option, interval: Duration, preserve_historical_states: bool, @@ -652,7 +750,7 @@ impl PeriodicStateDumper { } /// Infallible state dump - async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) { + async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) { trace!(path=?dump_state, "Dumping state on shutdown"); match api.serialized_state(preserve_historical_states).await { Ok(state) => { @@ -670,13 +768,13 @@ impl PeriodicStateDumper { } // An endless future that periodically dumps the state to disk if configured. -impl Future for PeriodicStateDumper { +impl> Future for PeriodicStateDumper { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); if this.dump_state.is_none() { - return Poll::Pending + return Poll::Pending; } loop { @@ -687,7 +785,7 @@ impl Future for PeriodicStateDumper { } Poll::Pending => { this.in_progress_dump = Some(flush); - return Poll::Pending + return Poll::Pending; } } } @@ -698,7 +796,7 @@ impl Future for PeriodicStateDumper { this.in_progress_dump = Some(Box::pin(Self::dump_state(api, path, this.preserve_historical_states))); } else { - break + break; } } @@ -728,7 +826,7 @@ impl StateFile { } let mut state = Self { path, state: None }; if !state.path.exists() { - return Ok(state) + return Ok(state); } state.state = Some(SerializableState::load(&state.path).map_err(|err| err.to_string())?); @@ -763,20 +861,33 @@ impl FromStr for ForkUrl { fn from_str(s: &str) -> Result { if let Some((url, block)) = s.rsplit_once('@') { if block == "latest" { - return Ok(Self { url: url.to_string(), block: None }) + return Ok(Self { url: url.to_string(), block: None }); } // this will prevent false positives for auths `user:password@example.com` if !block.is_empty() && !block.contains(':') && !block.contains('.') { let block: u64 = block .parse() .map_err(|_| format!("Failed to parse block number: `{block}`"))?; - return Ok(Self { url: url.to_string(), block: Some(block) }) + return Ok(Self { url: url.to_string(), block: Some(block) }); } } Ok(Self { url: s.to_string(), block: None }) } } +/// Parses a hardfork string against the active network configuration. +fn parse_hardfork(hf: &str, networks: &NetworkConfigs) -> eyre::Result { + #[cfg(feature = "optimism")] + if networks.is_optimism() { + return Ok(OpHardfork::from_str(hf)?.into()); + } + if networks.is_tempo() { + Ok(TempoHardfork::from_str(hf)?.into()) + } else { + Ok(EthereumHardfork::from_str(hf)?.into()) + } +} + /// Clap's value parser for genesis. Loads a genesis.json file. fn read_genesis_file(path: &str) -> Result { foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) @@ -886,6 +997,16 @@ mod tests { assert!(args.is_err()); } + #[test] + fn can_parse_enable_tx_gas_limit() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--enable-tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + + // Also test the alias + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--tx-gas-limit"]); + assert!(args.evm.enable_tx_gas_limit); + } + #[test] fn can_parse_disable_code_size_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]); @@ -920,15 +1041,76 @@ mod tests { ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); - env::set_var("ANVIL_IP_ADDR", "1.1.1.1"); + unsafe { env::set_var("ANVIL_IP_ADDR", "1.1.1.1") }; let args = NodeArgs::parse_from(["anvil"]); assert_eq!(args.host, vec!["1.1.1.1".parse::().unwrap()]); - env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2"); + unsafe { env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2") }; let args = NodeArgs::parse_from(["anvil"]); assert_eq!( args.host, ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); } + + #[test] + fn can_parse_multiple_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545", + "--fork-url", + "http://localhost:8546", + "--fork-url", + "http://localhost:8547", + ]); + assert_eq!(args.evm.fork_url.len(), 3); + assert_eq!(args.evm.fork_url[0].url, "http://localhost:8545"); + assert_eq!(args.evm.fork_url[1].url, "http://localhost:8546"); + assert_eq!(args.evm.fork_url[2].url, "http://localhost:8547"); + + // Block suffix on first URL should work + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546", + ]); + assert_eq!(args.evm.fork_url[0].block, Some(1000000)); + assert_eq!(args.evm.fork_url[1].block, None); + } + + #[test] + fn rejects_block_suffix_on_secondary_fork_urls() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http://localhost:8545@1000000", + "--fork-url", + "http://localhost:8546@2000000", + ]); + let result = args.into_node_config(); + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains("Block number suffixes"), + "should reject block suffix on secondary fork URL" + ); + } + + #[test] + fn fork_dependent_args_require_fork_url() { + // All these args have `requires = "fork_url"` — they should fail without --fork-url + let cases = [ + vec!["anvil", "--fork-header", "X-Api-Key: test"], + vec!["anvil", "--timeout", "5000"], + vec!["anvil", "--retries", "3"], + vec!["anvil", "--fork-block-number", "100"], + vec!["anvil", "--fork-retry-backoff", "500"], + ]; + for args in &cases { + let result = NodeArgs::try_parse_from(args); + assert!(result.is_err(), "expected error when using {:?} without --fork-url", args[1]); + } + } } diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index d86515f9f8fba..ecf83aa43735d 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -1,8 +1,8 @@ use crate::{ + EthereumHardfork, FeeManager, PrecompileFactory, eth::{ backend::{ db::{Db, SerializableState}, - env::Env, fork::{ClientFork, ClientForkConfig}, genesis::GenesisConfig, mem::fork_db::ForkedDatabase, @@ -11,59 +11,65 @@ use crate::{ fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::{PoolTransaction, TransactionOrder}, }, - hardfork::{ethereum_hardfork_from_block_tag, spec_id_from_ethereum_hardfork, ChainHardfork}, mem::{self, in_memory_db::MemDb}, - EthereumHardfork, FeeManager, PrecompileFactory, }; use alloy_consensus::BlockHeader; +use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; +use alloy_evm::EvmEnv; use alloy_genesis::Genesis; -use alloy_network::{AnyNetwork, TransactionResponse}; -use alloy_op_hardforks::OpHardfork; -use alloy_primitives::{hex, map::HashMap, utils::Unit, BlockNumber, TxHash, U256}; +use alloy_network::{AnyNetwork, BlockResponse, TransactionResponse}; +use alloy_primitives::{Address, BlockNumber, TxHash, U256, hex, map::HashMap, utils::Unit}; use alloy_provider::Provider; -use alloy_rpc_types::{Block, BlockNumberOrTag}; +use alloy_rpc_types::BlockNumberOrTag; use alloy_signer::Signer; use alloy_signer_local::{ - coins_bip39::{English, Mnemonic}, MnemonicBuilder, PrivateKeySigner, + coins_bip39::{English, Mnemonic}, }; use alloy_transport::TransportError; use anvil_server::ServerConfig; use eyre::{Context, Result}; use foundry_common::{ - provider::{ProviderBuilder, RetryProvider}, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, + provider::{ProviderBuilder, RetryProvider}, }; use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - utils::apply_chain_and_block_specific_env_changes, + hardfork::FoundryHardfork, + utils::{ + apply_chain_and_block_specific_env_changes, block_env_from_header, + get_blob_base_fee_update_fraction, + }, }; -use foundry_evm_core::AsEnvMut; +use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; -use op_revm::OpTransaction; use parking_lot::RwLock; use rand_08::thread_rng; use revm::{ - context::{BlockEnv, CfgEnv, TxEnv}, + context::{BlockEnv, CfgEnv}, context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId, }; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::{ fmt::Write as FmtWrite, - fs::File, - io, net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::Arc, time::Duration, }; +use tempo_chainspec::hardfork::TempoHardfork; use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE; +use foundry_evm::{ + traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier}, + utils::get_blob_params, +}; +use foundry_evm_networks::NetworkConfigs; /// Default port the rpc will open pub const NODE_PORT: u16 = 8545; @@ -96,6 +102,8 @@ pub struct NodeConfig { pub gas_limit: Option, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, + /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) + pub enable_tx_gas_limit: bool, /// Default gas price for all txs pub gas_price: Option, /// Default base fee @@ -105,7 +113,7 @@ pub struct NodeConfig { /// Default blob excess gas and price pub blob_excess_gas_and_price: Option, /// The hardfork to use - pub hardfork: Option, + pub hardfork: Option, /// Signer accounts that will be initialised with `genesis_balance` in the genesis block pub genesis_accounts: Vec, /// Native token balance of every genesis account in the genesis block @@ -126,11 +134,13 @@ pub struct NodeConfig { pub port: u16, /// maximum number of transactions in a block pub max_transactions: usize, - /// url of the rpc server that should be used for any rpc calls - pub eth_rpc_url: Option, + /// Fork URLs for RPC calls. The first entry is the primary endpoint. + /// When multiple URLs are provided, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// pins the block number or transaction hash for the state fork pub fork_choice: Option, - /// headers to use with `eth_rpc_url` + /// headers to use with fork RPC endpoints pub fork_headers: Vec, /// specifies chain id for cache to skip fetching from remote in offline-start mode pub fork_chain_id: Option, @@ -182,20 +192,23 @@ pub struct NodeConfig { pub transaction_block_keeper: Option, /// Disable the default CREATE2 deployer pub disable_default_create2_deployer: bool, - /// Enable Optimism deposit transaction - pub enable_optimism: bool, + /// Disable pool balance checks + pub disable_pool_balance_checks: bool, /// Slots in an epoch pub slots_in_an_epoch: u64, /// The memory limit per EVM execution in bytes. pub memory_limit: Option, /// Factory used by `anvil` to extend the EVM's precompiles. pub precompile_factory: Option>, - /// Enable Odyssey features. - pub odyssey: bool, + /// Networks to enable features for. + pub networks: NetworkConfigs, /// Do not print log messages. pub silent: bool, - /// The path where states are cached. + /// The path where persisted states are cached (used with `max_persisted_states`). + /// This does not affect the fork RPC cache location. pub cache_path: Option, + /// Accounts to fund with specific balances on startup (address -> balance in wei). + pub funded_accounts: HashMap, } impl NodeConfig { @@ -232,7 +245,7 @@ Private Keys let _ = write!(s, "\n({idx}) 0x{hex}"); } - if let Some(ref gen) = self.account_generator { + if let Some(generator) = &self.account_generator { let _ = write!( s, r#" @@ -242,8 +255,8 @@ Wallet Mnemonic: {} Derivation path: {} "#, - gen.phrase, - gen.get_derivation_path() + generator.phrase, + generator.get_derivation_path() ); } @@ -259,12 +272,19 @@ Block number: {} Block hash: {:?} Chain ID: {} "#, - fork.eth_rpc_url(), + fork.eth_rpc_url().as_deref().unwrap_or("none"), fork.block_number(), fork.block_hash(), fork.chain_id() ); + if self.fork_urls.len() > 1 { + let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len()); + for (i, url) in self.fork_urls.iter().enumerate() { + let _ = writeln!(s, " ({i}) {url}"); + } + } + if let Some(tx_hash) = fork.transaction_hash() { let _ = writeln!(s, "Transaction hash: {tx_hash}"); } @@ -365,9 +385,9 @@ Genesis Number private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes()))); } - if let Some(ref gen) = self.account_generator { - let phrase = gen.get_phrase().to_string(); - let derivation_path = gen.get_derivation_path().to_string(); + if let Some(generator) = &self.account_generator { + let phrase = generator.get_phrase().to_string(); + let derivation_path = generator.get_derivation_path().to_string(); wallet_description.insert("derivation_path".to_string(), derivation_path); wallet_description.insert("mnemonic".to_string(), phrase); @@ -384,7 +404,7 @@ Genesis Number json!({ "available_accounts": available_accounts, "private_keys": private_keys, - "endpoint": fork.eth_rpc_url(), + "endpoint": fork.eth_rpc_url().unwrap_or_default(), "block_number": fork.block_number(), "block_hash": fork.block_hash(), "chain_id": fork.chain_id(), @@ -415,6 +435,12 @@ impl NodeConfig { Self { enable_tracing: true, port: 0, silent: true, ..Default::default() } } + /// Returns a test config with Tempo network enabled. + #[doc(hidden)] + pub fn test_tempo() -> Self { + Self { networks: NetworkConfigs::with_tempo(), ..Self::test() } + } + /// Returns a new config which does not initialize any accounts on node startup. pub fn empty_state() -> Self { Self { @@ -429,12 +455,15 @@ impl NodeConfig { impl Default for NodeConfig { fn default() -> Self { // generate some random wallets - let genesis_accounts = - AccountGenerator::new(10).phrase(DEFAULT_MNEMONIC).gen().expect("Invalid mnemonic."); + let genesis_accounts = AccountGenerator::new(10) + .phrase(DEFAULT_MNEMONIC) + .generate() + .expect("Invalid mnemonic."); Self { chain_id: None, gas_limit: None, disable_block_gas_limit: false, + enable_tx_gas_limit: false, gas_price: None, hardfork: None, signer_accounts: genesis_accounts.clone(), @@ -447,9 +476,8 @@ impl Default for NodeConfig { no_mining: false, mixed_mining: false, port: NODE_PORT, - // TODO make this something dependent on block capacity max_transactions: 1_000, - eth_rpc_url: None, + fork_urls: vec![], fork_choice: None, account_generator: None, base_fee: None, @@ -480,13 +508,14 @@ impl Default for NodeConfig { init_state: None, transaction_block_keeper: None, disable_default_create2_deployer: false, - enable_optimism: false, + disable_pool_balance_checks: false, slots_in_an_epoch: 32, memory_limit: None, precompile_factory: None, - odyssey: false, + networks: Default::default(), silent: false, cache_path: None, + funded_accounts: HashMap::default(), } } } @@ -494,57 +523,82 @@ impl Default for NodeConfig { impl NodeConfig { /// Returns the memory limit of the node #[must_use] - pub fn with_memory_limit(mut self, mems_value: Option) -> Self { + pub const fn with_memory_limit(mut self, mems_value: Option) -> Self { self.memory_limit = mems_value; self } - /// Returns the base fee to use + + /// Returns the base fee to use. + /// + /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+). pub fn get_base_fee(&self) -> u64 { + let default = if self.networks.is_tempo() { + TempoHardfork::from(self.get_hardfork()).base_fee() + } else { + INITIAL_BASE_FEE + }; self.base_fee .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) - .unwrap_or(INITIAL_BASE_FEE) + .unwrap_or(default) } - /// Returns the base fee to use + /// Returns the gas price to use. + /// + /// In Tempo mode, defaults to the hardfork-specific base fee. pub fn get_gas_price(&self) -> u128 { - self.gas_price.unwrap_or(INITIAL_GAS_PRICE) + let default = if self.networks.is_tempo() { + TempoHardfork::from(self.get_hardfork()).base_fee() as u128 + } else { + INITIAL_GAS_PRICE + }; + self.gas_price.unwrap_or(default) } pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { - if let Some(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price { - *blob_excess_gas_and_price - } else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas) - { - BlobExcessGasAndPrice::new(excess_blob_gas, false) + if let Some(value) = self.blob_excess_gas_and_price { + value } else { - // If no excess blob gas is configured, default to 0 - BlobExcessGasAndPrice::new(0, false) + let excess_blob_gas = + self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0); + BlobExcessGasAndPrice::new( + excess_blob_gas, + get_blob_base_fee_update_fraction( + self.get_chain_id(), + self.get_genesis_timestamp(), + ), + ) } } + /// Returns the [`BlobParams`] that should be used. + pub fn get_blob_params(&self) -> BlobParams { + get_blob_params(self.get_chain_id(), self.get_genesis_timestamp()) + } + /// Returns the hardfork to use - pub fn get_hardfork(&self) -> ChainHardfork { - if self.odyssey { - return ChainHardfork::Ethereum(EthereumHardfork::default()); - } + pub fn get_hardfork(&self) -> FoundryHardfork { if let Some(hardfork) = self.hardfork { return hardfork; } - if self.enable_optimism { - return OpHardfork::default().into(); + #[cfg(feature = "optimism")] + if self.networks.is_optimism() { + return foundry_evm::hardforks::OpHardfork::default().into(); + } + if self.networks.is_tempo() { + return TempoHardfork::default().into(); } EthereumHardfork::default().into() } /// Sets a custom code size limit #[must_use] - pub fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { + pub const fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { self.code_size_limit = code_size_limit; self } /// Disables code size limit #[must_use] - pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { + pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { if disable_code_size_limit { self.code_size_limit = Some(usize::MAX); } @@ -584,6 +638,7 @@ impl NodeConfig { pub fn set_chain_id(&mut self, chain_id: Option>) { self.chain_id = chain_id.map(Into::into); let chain_id = self.get_chain_id(); + self.networks.with_chain_id(chain_id); self.genesis_accounts.iter_mut().for_each(|wallet| { *wallet = wallet.clone().with_chain_id(Some(chain_id)); }); @@ -594,7 +649,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub const fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } @@ -603,14 +658,23 @@ impl NodeConfig { /// /// If set to `true` block gas limit will not be enforced #[must_use] - pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { + pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { self.disable_block_gas_limit = disable_block_gas_limit; self } + /// Enable tx gas limit check + /// + /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) + #[must_use] + pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { + self.enable_tx_gas_limit = enable_tx_gas_limit; + self + } + /// Sets the gas price #[must_use] - pub fn with_gas_price(mut self, gas_price: Option) -> Self { + pub const fn with_gas_price(mut self, gas_price: Option) -> Self { self.gas_price = gas_price; self } @@ -632,6 +696,15 @@ impl NodeConfig { self } + /// Sets the max number of transactions in a block + #[must_use] + pub const fn with_max_transactions(mut self, max_transactions: Option) -> Self { + if let Some(max_transactions) = max_transactions { + self.max_transactions = max_transactions; + } + self + } + /// Sets max number of blocks with transactions to keep in memory #[must_use] pub fn with_transaction_block_keeper>( @@ -644,14 +717,14 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee(mut self, base_fee: Option) -> Self { + pub const fn with_base_fee(mut self, base_fee: Option) -> Self { self.base_fee = base_fee; self } /// Disable the enforcement of a minimum suggested priority fee #[must_use] - pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { self.disable_min_priority_fee = disable_min_priority_fee; self } @@ -697,7 +770,7 @@ impl NodeConfig { /// Sets the hardfork #[must_use] - pub fn with_hardfork(mut self, hardfork: Option) -> Self { + pub const fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } @@ -719,7 +792,7 @@ impl NodeConfig { /// Sets both the genesis accounts and the signer accounts /// so that `genesis_accounts == accounts` pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result { - let accounts = generator.gen()?; + let accounts = generator.generate()?; self.account_generator = Some(generator); Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts)) } @@ -751,21 +824,21 @@ impl NodeConfig { /// If set to `true` auto mining will be disabled #[must_use] - pub fn with_no_mining(mut self, no_mining: bool) -> Self { + pub const fn with_no_mining(mut self, no_mining: bool) -> Self { self.no_mining = no_mining; self } /// Sets the slots in an epoch #[must_use] - pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { self.slots_in_an_epoch = slots_in_an_epoch; self } /// Sets the port to use #[must_use] - pub fn with_port(mut self, port: u16) -> Self { + pub const fn with_port(mut self, port: u16) -> Self { self.port = port; self } @@ -789,22 +862,25 @@ impl NodeConfig { self } - /// Disables storage caching #[must_use] - pub fn no_storage_caching(self) -> Self { - self.with_storage_caching(true) + pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { + self.no_storage_caching = no_storage_caching; + self } + /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience). #[must_use] - pub fn with_storage_caching(mut self, storage_caching: bool) -> Self { - self.no_storage_caching = storage_caching; + pub fn with_eth_rpc_url>(mut self, eth_rpc_url: Option) -> Self { + if let Some(url) = eth_rpc_url { + self.fork_urls = vec![url.into()]; + } self } - /// Sets the `eth_rpc_url` to use when forking + /// Sets the fork URLs for load-balanced multi-endpoint forking. #[must_use] - pub fn with_eth_rpc_url>(mut self, eth_rpc_url: Option) -> Self { - self.eth_rpc_url = eth_rpc_url.map(Into::into); + pub fn with_fork_urls(mut self, fork_urls: Vec) -> Self { + self.fork_urls = fork_urls; self } @@ -832,12 +908,12 @@ impl NodeConfig { /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { + pub const fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id; self } - /// Sets the `fork_headers` to use with `eth_rpc_url` + /// Sets the `fork_headers` to use with fork RPC endpoints #[must_use] pub fn with_fork_headers(mut self, headers: Vec) -> Self { self.fork_headers = headers; @@ -846,7 +922,7 @@ impl NodeConfig { /// Sets the `fork_request_timeout` to use for requests #[must_use] - pub fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { + pub const fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { if let Some(fork_request_timeout) = fork_request_timeout { self.fork_request_timeout = fork_request_timeout; } @@ -855,7 +931,7 @@ impl NodeConfig { /// Sets the `fork_request_retries` to use for spurious networks #[must_use] - pub fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { + pub const fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { if let Some(fork_request_retries) = fork_request_retries { self.fork_request_retries = fork_request_retries; } @@ -864,7 +940,7 @@ impl NodeConfig { /// Sets the initial `fork_retry_backoff` for rate limits #[must_use] - pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { + pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { if let Some(fork_retry_backoff) = fork_retry_backoff { self.fork_retry_backoff = fork_retry_backoff; } @@ -875,7 +951,10 @@ impl NodeConfig { /// /// See also, #[must_use] - pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { + pub const fn fork_compute_units_per_second( + mut self, + compute_units_per_second: Option, + ) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { self.compute_units_per_second = compute_units_per_second; } @@ -884,35 +963,35 @@ impl NodeConfig { /// Sets whether to enable tracing #[must_use] - pub fn with_tracing(mut self, enable_tracing: bool) -> Self { + pub const fn with_tracing(mut self, enable_tracing: bool) -> Self { self.enable_tracing = enable_tracing; self } /// Sets whether to enable steps tracing #[must_use] - pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { + pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { self.enable_steps_tracing = enable_steps_tracing; self } /// Sets whether to print `console.log` invocations to stdout. #[must_use] - pub fn with_print_logs(mut self, print_logs: bool) -> Self { + pub const fn with_print_logs(mut self, print_logs: bool) -> Self { self.print_logs = print_logs; self } /// Sets whether to print traces to stdout. #[must_use] - pub fn with_print_traces(mut self, print_traces: bool) -> Self { + pub const fn with_print_traces(mut self, print_traces: bool) -> Self { self.print_traces = print_traces; self } /// Sets whether to enable autoImpersonate #[must_use] - pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { + pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { self.enable_auto_impersonate = enable_auto_impersonate; self } @@ -931,7 +1010,7 @@ impl NodeConfig { } #[must_use] - pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { + pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { self.transaction_order = transaction_order; self } @@ -947,11 +1026,8 @@ impl NodeConfig { /// Prints the config info pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> { if let Some(path) = &self.config_out { - let file = io::BufWriter::new( - File::create(path).wrap_err("unable to create anvil config description file")?, - ); let value = self.as_json(fork); - serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?; + foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?; } if !self.silent { sh_println!("{}", self.as_string(fork))?; @@ -963,7 +1039,7 @@ impl NodeConfig { /// /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { - if self.no_storage_caching || self.eth_rpc_url.is_none() { + if self.no_storage_caching || self.fork_urls.is_empty() { return None; } let chain_id = self.get_chain_id(); @@ -971,17 +1047,17 @@ impl NodeConfig { Config::foundry_block_cache_file(chain_id, block) } - /// Sets whether to enable optimism support + /// Sets whether to disable the default create2 deployer #[must_use] - pub fn with_optimism(mut self, enable_optimism: bool) -> Self { - self.enable_optimism = enable_optimism; + pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + self.disable_default_create2_deployer = yes; self } - /// Sets whether to disable the default create2 deployer + /// Sets whether to disable pool balance checks #[must_use] - pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { - self.disable_default_create2_deployer = yes; + pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { + self.disable_pool_balance_checks = yes; self } @@ -992,37 +1068,68 @@ impl NodeConfig { self } - /// Sets whether to enable Odyssey support + /// Enable features for provided networks. + #[must_use] + pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self { + self.networks = networks; + self + } + + /// Enable Tempo network features. #[must_use] - pub fn with_odyssey(mut self, odyssey: bool) -> Self { - self.odyssey = odyssey; + pub fn with_tempo(mut self) -> Self { + self.networks = NetworkConfigs::with_tempo(); + self + } + + /// Enable Optimism network features. + #[cfg(feature = "optimism")] + #[must_use] + pub fn with_optimism(mut self) -> Self { + self.networks = NetworkConfigs::with_optimism(); self } /// Makes the node silent to not emit anything on stdout #[must_use] - pub fn silent(self) -> Self { + pub const fn silent(self) -> Self { self.set_silent(true) } #[must_use] - pub fn set_silent(mut self, silent: bool) -> Self { + pub const fn set_silent(mut self, silent: bool) -> Self { self.silent = silent; self } - /// Sets the path where states are cached + /// Sets the path where persisted states are cached (used with `max_persisted_states`). + /// + /// Note: This does not control the fork RPC cache location (`storage.json`), which uses + /// `~/.foundry/cache/rpc///` via [`Config::foundry_block_cache_file`]. #[must_use] pub fn with_cache_path(mut self, cache_path: Option) -> Self { self.cache_path = cache_path; self } + /// Sets accounts to fund with custom balances on startup. + #[must_use] + pub fn with_funded_accounts(mut self, accounts: HashMap) -> Self { + self.funded_accounts = accounts; + self + } + /// Configures everything related to env, backend and database and returns the /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> Result { + pub(crate) async fn setup(&mut self) -> Result> + where + N: alloy_network::Network< + TxEnvelope = foundry_primitives::FoundryTxEnvelope, + ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope, + >, + { // configure the revm environment let mut cfg = CfgEnv::default(); @@ -1036,36 +1143,40 @@ impl NodeConfig { cfg.disable_eip3607 = true; cfg.disable_block_gas_limit = self.disable_block_gas_limit; + if !self.enable_tx_gas_limit { + cfg.tx_gas_limit_cap = Some(u64::MAX); + } + if let Some(value) = self.memory_limit { cfg.memory_limit = value; } let spec_id = cfg.spec; - let mut env = Env::new( + let mut evm_env = EvmEnv::new( cfg, BlockEnv { gas_limit: self.gas_limit(), basefee: self.get_base_fee(), ..Default::default() }, - OpTransaction { - base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, - ..Default::default() - }, - self.enable_optimism, ); + let base_fee_params: BaseFeeParams = + self.networks.base_fee_params(self.get_genesis_timestamp()); + let fees = FeeManager::new( spec_id, self.get_base_fee(), !self.disable_min_priority_fee, self.get_gas_price(), self.get_blob_excess_gas_and_price(), + self.get_blob_params(), + base_fee_params, ); let (db, fork): (Arc>>, Option) = - if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { - self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? + if let Some(eth_rpc_url) = self.fork_urls.first().cloned() { + self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await? } else { (Arc::new(TokioRwLock::new(Box::::default())), None) }; @@ -1075,16 +1186,16 @@ impl NodeConfig { // --chain-id flag gets precedence over the genesis.json chain id // if self.chain_id.is_none() { - env.evm_env.cfg_env.chain_id = genesis.config.chain_id; + evm_env.cfg_env.chain_id = genesis.config.chain_id; } - env.evm_env.block_env.timestamp = genesis.timestamp; + evm_env.block_env.timestamp = U256::from(genesis.timestamp); if let Some(base_fee) = genesis.base_fee_per_gas { - env.evm_env.block_env.basefee = base_fee.try_into()?; + evm_env.block_env.basefee = base_fee.try_into()?; } if let Some(number) = genesis.number { - env.evm_env.block_env.number = number; + evm_env.block_env.number = U256::from(number); } - env.evm_env.block_env.beneficiary = genesis.coinbase; + evm_env.block_env.beneficiary = genesis.coinbase; } let genesis = GenesisConfig { @@ -1095,17 +1206,27 @@ impl NodeConfig { genesis_init: self.genesis.clone(), }; + let mut decoder_builder = CallTraceDecoderBuilder::new(); + if self.print_traces { + // if traces should get printed we configure the decoder with the signatures cache + if let Ok(identifier) = SignaturesIdentifier::new(false) { + debug!(target: "node", "using signature identifier"); + decoder_builder = decoder_builder.with_signature_identifier(identifier); + } + } + // only memory based backend for now let backend = mem::Backend::with_genesis( db, - Arc::new(RwLock::new(env)), + Arc::new(RwLock::new(evm_env)), + self.networks, genesis, fees, Arc::new(RwLock::new(fork)), self.enable_steps_tracing, self.print_logs, self.print_traces, - self.odyssey, + Arc::new(decoder_builder.build()), self.prune_history, self.max_persisted_states, self.transaction_block_keeper, @@ -1117,15 +1238,20 @@ impl NodeConfig { // Writes the default create2 deployer to the backend, // if the option is not disabled and we are not forking. - if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { + if !self.disable_default_create2_deployer && self.fork_urls.is_empty() { backend .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await .wrap_err("failed to create default create2 deployer")?; } - if let Some(state) = self.init_state.clone() { - backend.load_state(state).await.wrap_err("failed to load init state")?; + if !self.funded_accounts.is_empty() { + for (address, balance) in &self.funded_accounts { + backend + .set_balance(*address, *balance) + .await + .wrap_err_with(|| format!("failed to fund account {address}"))?; + } } Ok(backend) @@ -1140,10 +1266,10 @@ impl NodeConfig { pub async fn setup_fork_db( &mut self, eth_rpc_url: String, - env: &mut Env, + evm_env: &mut EvmEnv, fees: &FeeManager, ) -> Result<(Arc>>, Option)> { - let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; + let (db, config) = self.setup_fork_db_config(eth_rpc_url, evm_env, fees).await?; let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); let fork = ClientFork::new(config, Arc::clone(&db)); Ok((db, Some(fork))) @@ -1157,17 +1283,20 @@ impl NodeConfig { pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, - env: &mut Env, + evm_env: &mut EvmEnv, fees: &FeeManager, - ) -> Result<(ForkedDatabase, ClientForkConfig)> { + ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); + + // Always bootstrap with the primary URL only to avoid race conditions + // where discovery calls (get_chain_id, find_latest_fork_block, get_block) + // hit different endpoints that may be at different chain tips. let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) .initial_backoff(self.fork_retry_backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .max_retry(self.fork_request_retries) - .initial_backoff(1000) .headers(self.fork_headers.clone()) .build() .wrap_err("failed to establish provider to fork url")?, @@ -1183,16 +1312,8 @@ impl NodeConfig { let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { - // Auto-adjust hardfork if not specified, but only if we're forking mainnet. let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; - if alloy_chains::NamedChain::Mainnet == chain_id { - let hardfork: EthereumHardfork = - ethereum_hardfork_from_block_tag(fork_block_number); - - env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); - self.hardfork = Some(ChainHardfork::Ethereum(hardfork)); - } Some(U256::from(chain_id)) } else { None @@ -1234,59 +1355,15 @@ latest block number: {latest_block}" let gas_limit = self.fork_gas_limit(&block); self.gas_limit = Some(gas_limit); - env.evm_env.block_env = BlockEnv { - number: fork_block_number, - timestamp: block.header.timestamp, - difficulty: block.header.difficulty, - // ensures prevrandao is set - prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + evm_env.block_env = BlockEnv { gas_limit, // Keep previous `coinbase` and `basefee` value - beneficiary: env.evm_env.block_env.beneficiary, - basefee: env.evm_env.block_env.basefee, - ..Default::default() + beneficiary: evm_env.block_env.beneficiary, + basefee: evm_env.block_env.basefee, + ..block_env_from_header(&block.header) }; - // if not set explicitly we use the base fee of the latest block - if self.base_fee.is_none() { - if let Some(base_fee) = block.header.base_fee_per_gas { - self.base_fee = Some(base_fee); - env.evm_env.block_env.basefee = base_fee; - // this is the base fee of the current block, but we need the base fee of - // the next block - let next_block_base_fee = fees.get_next_block_base_fee_per_gas( - block.header.gas_used, - gas_limit, - block.header.base_fee_per_gas.unwrap_or_default(), - ); - - // update next base fee - fees.set_base_fee(next_block_base_fee); - } - if let (Some(blob_excess_gas), Some(blob_gas_used)) = - (block.header.excess_blob_gas, block.header.blob_gas_used) - { - env.evm_env.block_env.blob_excess_gas_and_price = - Some(BlobExcessGasAndPrice::new(blob_excess_gas, false)); - let next_block_blob_excess_gas = - fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used); - fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( - next_block_blob_excess_gas, - false, - )); - } - } - - // use remote gas price - if self.gas_price.is_none() { - if let Ok(gas_price) = provider.get_gas_price().await { - self.gas_price = Some(gas_price); - fees.set_gas_price(gas_price); - } - } - - let block_hash = block.header.hash; - + // Determine chain_id early so we can use it consistently let chain_id = if let Some(chain_id) = self.chain_id { chain_id } else { @@ -1298,46 +1375,129 @@ latest block number: {latest_block}" // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); - env.evm_env.cfg_env.chain_id = chain_id; - env.tx.base.chain_id = chain_id.into(); + evm_env.cfg_env.chain_id = chain_id; chain_id }; + + // Auto-detect hardfork from chain activation data if not explicitly set. + if self.hardfork.is_none() + && let Some(hardfork) = + FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp()) + { + evm_env.cfg_env.spec = SpecId::from(hardfork); + self.hardfork = Some(hardfork); + } + + // if not set explicitly we use the base fee of the latest block + if self.base_fee.is_none() + && let Some(base_fee) = block.header.base_fee_per_gas() + { + self.base_fee = Some(base_fee); + evm_env.block_env.basefee = base_fee; + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = fees.get_next_block_base_fee_per_gas( + block.header.gas_used(), + gas_limit, + block.header.base_fee_per_gas().unwrap_or_default(), + ); + + // update next base fee + fees.set_base_fee(next_block_base_fee); + } + + if let (Some(blob_excess_gas), Some(blob_gas_used)) = + (block.header.excess_blob_gas(), block.header.blob_gas_used()) + { + // Derive blob params using the fork block timestamp regardless of explicit base fee. + let blob_params = get_blob_params(chain_id, block.header.timestamp()); + + evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( + blob_excess_gas, + blob_params.update_fraction as u64, + )); + + fees.set_blob_params(blob_params); + + let next_block_blob_excess_gas = + fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used); + fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_blob_excess_gas, + blob_params.update_fraction as u64, + )); + } + + // use remote gas price + if self.gas_price.is_none() + && let Ok(gas_price) = provider.get_gas_price().await + { + self.gas_price = Some(gas_price); + fees.set_gas_price(gas_price); + } + + let block_hash = block.header.hash; + let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id - apply_chain_and_block_specific_env_changes::(env.as_env_mut(), &block); + apply_chain_and_block_specific_env_changes::( + evm_env, + &block, + self.networks, + ); - let meta = BlockchainDbMeta::new(env.evm_env.block_env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) } else { BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) }; + // After bootstrap, rebuild the provider with round-robin if multiple URLs are + // configured. This ensures bootstrap used only the primary endpoint for consistency, + // while ongoing requests are distributed across all endpoints. + let provider = if self.fork_urls.len() > 1 { + debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider"); + Arc::new( + ProviderBuilder::new(ð_rpc_url) + .timeout(self.fork_request_timeout) + .initial_backoff(self.fork_retry_backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .max_retry(self.fork_request_retries) + .headers(self.fork_headers.clone()) + .build_fallback(self.fork_urls.clone()) + .wrap_err("failed to establish round-robin provider to fork urls")?, + ) + } else { + provider + }; + // This will spawn the background thread that will use the provider to fetch // blockchain data from the other client - let backend = SharedBackend::spawn_backend_thread( + let backend = SharedBackend::spawn_backend( Arc::clone(&provider), block_chain_db.clone(), Some(fork_block_number.into()), - ); + ) + .await; let config = ClientForkConfig { - eth_rpc_url, + fork_urls: self.fork_urls.clone(), block_number: fork_block_number, block_hash, transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()), provider, chain_id, override_chain_id, - timestamp: block.header.timestamp, - base_fee: block.header.base_fee_per_gas.map(|g| g as u128), + timestamp: block.header.timestamp(), + base_fee: block.header.base_fee_per_gas().map(|g| g as u128), timeout: self.fork_request_timeout, retries: self.fork_request_retries, backoff: self.fork_retry_backoff, compute_units_per_second: self.compute_units_per_second, + headers: self.fork_headers.clone(), total_difficulty: block.header.total_difficulty.unwrap_or_default(), - blob_gas_used: block.header.blob_gas_used.map(|g| g as u128), - blob_excess_gas_and_price: env.evm_env.block_env.blob_excess_gas_and_price, + blob_gas_used: block.header.blob_gas_used().map(|g| g as u128), + blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price, force_transactions, }; @@ -1354,15 +1514,12 @@ latest block number: {latest_block}" /// we only use the gas limit value of the block if it is non-zero and the block gas /// limit is enabled, since there are networks where this is not used and is always /// `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also - pub(crate) fn fork_gas_limit( - &self, - block: &Block, - ) -> u64 { + pub(crate) fn fork_gas_limit>(&self, block: &B) -> u64 { if !self.disable_block_gas_limit { if let Some(gas_limit) = self.gas_limit { return gas_limit; - } else if block.header.gas_limit() > 0 { - return block.header.gas_limit(); + } else if block.header().gas_limit() > 0 { + return block.header().gas_limit(); } } @@ -1388,7 +1545,7 @@ latest block number: {latest_block}" async fn derive_block_and_transactions( fork_choice: &ForkChoice, provider: &Arc, -) -> eyre::Result<(BlockNumber, Option>)> { +) -> eyre::Result<(BlockNumber, Option>>)> { match fork_choice { ForkChoice::Block(block_number) => { let block_number = *block_number; @@ -1406,7 +1563,9 @@ async fn derive_block_and_transactions( .get_transaction_by_hash(transaction_hash.0.into()) .await? .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?; - let transaction_block_number = transaction.block_number.unwrap(); + let transaction_block_number = transaction.block_number().ok_or_else(|| { + eyre::eyre!("fork transaction is not mined yet (no block number)") + })?; // Get the block pertaining to the fork transaction let transaction_block = provider @@ -1448,7 +1607,7 @@ pub enum ForkChoice { impl ForkChoice { /// Returns the block number to fork from - pub fn block_number(&self) -> Option { + pub const fn block_number(&self) -> Option { match self { Self::Block(block_number) => Some(*block_number), Self::Transaction(_) => None, @@ -1456,7 +1615,7 @@ impl ForkChoice { } /// Returns the transaction hash to fork from - pub fn transaction_hash(&self) -> Option { + pub const fn transaction_hash(&self) -> Option { match self { Self::Block(_) => None, Self::Transaction(transaction_hash) => Some(*transaction_hash), @@ -1486,12 +1645,12 @@ pub struct PruneStateHistoryConfig { impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported - pub fn is_state_history_supported(&self) -> bool { + pub const fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } /// Returns true if this setting was enabled. - pub fn is_config_enabled(&self) -> bool { + pub const fn is_config_enabled(&self) -> bool { self.enabled } @@ -1551,7 +1710,7 @@ impl AccountGenerator { } impl AccountGenerator { - pub fn gen(&self) -> eyre::Result> { + pub fn generate(&self) -> eyre::Result> { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); // use the derivation path @@ -1590,10 +1749,10 @@ async fn find_latest_fork_block>( // walk back from the head of the chain, but at most 2 blocks, which should be more than enough // leeway for _ in 0..2 { - if let Some(block) = provider.get_block(num.into()).await? { - if !block.header.hash.is_zero() { - break; - } + if let Some(block) = provider.get_block(num.into()).await? + && !block.header.hash.is_zero() + { + break; } // block not actually finalized, so we try the block before num = num.saturating_sub(1) diff --git a/crates/anvil/src/server/error.rs b/crates/anvil/src/error.rs similarity index 100% rename from crates/anvil/src/server/error.rs rename to crates/anvil/src/error.rs diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index ec6fbeb4227a1..a0d600024eda6 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,11 +1,9 @@ use super::{ - backend::{ - db::MaybeFullDatabase, - mem::{state, BlockRequest, State}, - }, - sign::build_typed_transaction, + backend::mem::{BlockRequest, DatabaseRef, State}, + sign::build_impersonated, }; use crate::{ + ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, eth::{ backend::{ self, @@ -21,36 +19,37 @@ use crate::{ macros::node_info, miner::FixedBlockTimeMiner, pool::{ + Pool, transactions::{ - to_marker, PoolTransaction, TransactionOrder, TransactionPriority, TxMarker, + PoolTransaction, TransactionOrder, TransactionPriority, TxMarker, to_marker, }, - Pool, }, sign::{self, Signer}, }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, - ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; use alloy_consensus::{ - transaction::{eip4844::TxEip4844Variant, Recovered}, - Account, + Blob, BlockHeader, Transaction, TrieAccount, TxEip4844Variant, transaction::Recovered, }; use alloy_dyn_abi::TypedData; -use alloy_eips::eip2718::Encodable2718; +use alloy_eips::{ + eip2718::Encodable2718, + eip7910::{EthConfig, EthForkConfig}, +}; +use alloy_evm::overrides::{OverrideBlockHashes, apply_state_overrides}; use alloy_network::{ - eip2718::Decodable2718, AnyRpcBlock, AnyRpcTransaction, BlockResponse, Ethereum, NetworkWallet, - TransactionBuilder, TransactionResponse, + AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, NetworkTransactionBuilder, + ReceiptResponse, TransactionBuilder, TransactionBuilder4844, TransactionResponse, + eip2718::Decodable2718, }; use alloy_primitives::{ + Address, B64, B256, Bytes, TxHash, TxKind, U64, U256, map::{HashMap, HashSet}, - Address, Bytes, Signature, TxHash, TxKind, B256, B64, U256, U64, -}; -use alloy_provider::utils::{ - eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS, - EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, }; use alloy_rpc_types::{ + AccessList, AccessListResult, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Work, anvil::{ ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, }, @@ -59,46 +58,50 @@ use alloy_rpc_types::{ state::{AccountOverride, EvmOverrides, StateOverridesBuilder}, trace::{ filter::TraceFilter, - geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, + geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult}, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, - AccessList, AccessListResult, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, - EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Work, }; +use alloy_rpc_types_eth::FillTransaction; use alloy_serde::WithOtherFields; -use alloy_sol_types::{sol, SolCall, SolValue}; +use alloy_sol_types::{SolCall, SolValue, sol}; use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ - block::BlockInfo, - transaction::{ - transaction_request_to_typed, PendingTransaction, ReceiptResponse, TypedTransaction, - TypedTransactionRequest, - }, - wallet::{WalletCapabilities, WalletError}, EthRequest, + block::BlockInfo, + transaction::{MaybeImpersonatedTransaction, PendingTransaction}, }, types::{ReorgOptions, TransactionData}, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; -use foundry_common::provider::ProviderBuilder; -use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; +use foundry_common::{ + provider::ProviderBuilder, + version::{COMMIT_SHA, SEMVER_VERSION}, +}; +use foundry_evm::decode::RevertDecoder; +use foundry_primitives::{ + FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, + FoundryTxReceipt, FoundryTxType, FoundryTypedTx, +}; use futures::{ + StreamExt, TryFutureExt, channel::{mpsc::Receiver, oneshot}, - StreamExt, }; use parking_lot::RwLock; use revm::{ - bytecode::Bytecode, context::BlockEnv, context_interface::{block::BlobExcessGasAndPrice, result::Output}, - database::{CacheDB, DatabaseRef}, - interpreter::{return_ok, return_revert, InstructionResult}, + database::CacheDB, + interpreter::{InstructionResult, return_ok, return_revert}, primitives::eip7702::PER_EMPTY_ACCOUNT_COST, }; -use std::{future::Future, sync::Arc, time::Duration}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; +use std::{sync::Arc, time::Duration}; +use tokio::{ + sync::mpsc::{UnboundedReceiver, unbounded_channel}, + try_join, +}; /// The client version: `anvil/v{major}.{minor}.{patch}` pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); @@ -106,17 +109,16 @@ pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); /// The entry point for executing eth api RPC call - The Eth RPC interface. /// /// This type is cheap to clone and can be used concurrently -#[derive(Clone)] -pub struct EthApi { +pub struct EthApi { /// The transaction pool - pool: Arc, + pool: Arc>, /// Holds all blockchain related data /// In-Memory only for now - pub backend: Arc, + pub backend: Arc>, /// Whether this node is mining is_mining: bool, /// available signers - signers: Arc>>, + signers: Arc>>>, /// data required for `eth_feeHistory` fee_history_cache: FeeHistoryCache, /// max number of items kept in fee cache @@ -125,11 +127,11 @@ pub struct EthApi { /// /// This access is required in order to adjust miner settings based on requests received from /// custom RPC endpoints - miner: Miner, + miner: Miner, /// allows to enabled/disable logging logger: LoggingManager, /// Tracks all active filters - filters: Filters, + filters: Filters, /// How transactions are ordered in the pool transaction_order: Arc>, /// Whether we're listening for RPC calls @@ -138,18 +140,39 @@ pub struct EthApi { instance_id: Arc>, } -impl EthApi { +impl Clone for EthApi { + fn clone(&self) -> Self { + Self { + pool: self.pool.clone(), + backend: self.backend.clone(), + is_mining: self.is_mining, + signers: self.signers.clone(), + fee_history_cache: self.fee_history_cache.clone(), + fee_history_limit: self.fee_history_limit, + miner: self.miner.clone(), + logger: self.logger.clone(), + filters: self.filters.clone(), + transaction_order: self.transaction_order.clone(), + net_listening: self.net_listening, + instance_id: self.instance_id.clone(), + } + } +} + +// == impl EthApi generic methods == + +impl EthApi { /// Creates a new instance #[expect(clippy::too_many_arguments)] pub fn new( - pool: Arc, - backend: Arc, - signers: Arc>>, + pool: Arc>, + backend: Arc>, + signers: Arc>>>, fee_history_cache: FeeHistoryCache, fee_history_limit: u64, - miner: Miner, + miner: Miner, logger: LoggingManager, - filters: Filters, + filters: Filters, transactions_order: TransactionOrder, ) -> Self { Self { @@ -168,189 +191,1530 @@ impl EthApi { } } - /// Executes the [EthRequest] and returns an RPC [ResponseResult]. - pub async fn execute(&self, request: EthRequest) -> ResponseResult { - trace!(target: "rpc::api", "executing eth request"); - let response = match request.clone() { - EthRequest::Web3ClientVersion(()) => self.client_version().to_rpc_result(), - EthRequest::Web3Sha3(content) => self.sha3(content).to_rpc_result(), - EthRequest::EthGetAccount(addr, block) => { - self.get_account(addr, block).await.to_rpc_result() - } - EthRequest::EthGetAccountInfo(addr, block) => { - self.get_account_info(addr, block).await.to_rpc_result() - } - EthRequest::EthGetBalance(addr, block) => { - self.balance(addr, block).await.to_rpc_result() - } - EthRequest::EthGetTransactionByHash(hash) => { - self.transaction_by_hash(hash).await.to_rpc_result() - } - EthRequest::EthSendTransaction(request) => { - self.send_transaction(*request).await.to_rpc_result() - } - EthRequest::EthChainId(_) => self.eth_chain_id().to_rpc_result(), - EthRequest::EthNetworkId(_) => self.network_id().to_rpc_result(), - EthRequest::NetListening(_) => self.net_listening().to_rpc_result(), - EthRequest::EthGasPrice(_) => self.eth_gas_price().to_rpc_result(), - EthRequest::EthMaxPriorityFeePerGas(_) => { - self.gas_max_priority_fee_per_gas().to_rpc_result() - } - EthRequest::EthBlobBaseFee(_) => self.blob_base_fee().to_rpc_result(), - EthRequest::EthAccounts(_) => self.accounts().to_rpc_result(), - EthRequest::EthBlockNumber(_) => self.block_number().to_rpc_result(), - EthRequest::EthGetStorageAt(addr, slot, block) => { - self.storage_at(addr, slot, block).await.to_rpc_result() - } - EthRequest::EthGetBlockByHash(hash, full) => { - if full { - self.block_by_hash_full(hash).await.to_rpc_result() - } else { - self.block_by_hash(hash).await.to_rpc_result() - } - } - EthRequest::EthGetBlockByNumber(num, full) => { - if full { - self.block_by_number_full(num).await.to_rpc_result() - } else { - self.block_by_number(num).await.to_rpc_result() - } - } - EthRequest::EthGetTransactionCount(addr, block) => { - self.transaction_count(addr, block).await.to_rpc_result() - } - EthRequest::EthGetTransactionCountByHash(hash) => { - self.block_transaction_count_by_hash(hash).await.to_rpc_result() - } - EthRequest::EthGetTransactionCountByNumber(num) => { - self.block_transaction_count_by_number(num).await.to_rpc_result() - } - EthRequest::EthGetUnclesCountByHash(hash) => { - self.block_uncles_count_by_hash(hash).await.to_rpc_result() - } - EthRequest::EthGetUnclesCountByNumber(num) => { - self.block_uncles_count_by_number(num).await.to_rpc_result() - } - EthRequest::EthGetCodeAt(addr, block) => { - self.get_code(addr, block).await.to_rpc_result() - } - EthRequest::EthGetProof(addr, keys, block) => { - self.get_proof(addr, keys, block).await.to_rpc_result() - } - EthRequest::EthSign(addr, content) => self.sign(addr, content).await.to_rpc_result(), - EthRequest::PersonalSign(content, addr) => { - self.sign(addr, content).await.to_rpc_result() - } - EthRequest::EthSignTransaction(request) => { - self.sign_transaction(*request).await.to_rpc_result() - } - EthRequest::EthSignTypedData(addr, data) => { - self.sign_typed_data(addr, data).await.to_rpc_result() - } - EthRequest::EthSignTypedDataV3(addr, data) => { - self.sign_typed_data_v3(addr, data).await.to_rpc_result() - } - EthRequest::EthSignTypedDataV4(addr, data) => { - self.sign_typed_data_v4(addr, &data).await.to_rpc_result() - } - EthRequest::EthSendRawTransaction(tx) => { - self.send_raw_transaction(tx).await.to_rpc_result() - } - EthRequest::EthCall(call, block, state_override, block_overrides) => self - .call(call, block, EvmOverrides::new(state_override, block_overrides)) - .await - .to_rpc_result(), - EthRequest::EthSimulateV1(simulation, block) => { - self.simulate_v1(simulation, block).await.to_rpc_result() - } - EthRequest::EthCreateAccessList(call, block) => { - self.create_access_list(call, block).await.to_rpc_result() - } - EthRequest::EthEstimateGas(call, block, state_override, block_overrides) => self - .estimate_gas(call, block, EvmOverrides::new(state_override, block_overrides)) - .await - .to_rpc_result(), - EthRequest::EthGetRawTransactionByHash(hash) => { - self.raw_transaction(hash).await.to_rpc_result() - } - EthRequest::EthGetRawTransactionByBlockHashAndIndex(hash, index) => { - self.raw_transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() - } - EthRequest::EthGetRawTransactionByBlockNumberAndIndex(num, index) => { - self.raw_transaction_by_block_number_and_index(num, index).await.to_rpc_result() - } - EthRequest::EthGetTransactionByBlockHashAndIndex(hash, index) => { - self.transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() - } - EthRequest::EthGetTransactionByBlockNumberAndIndex(num, index) => { - self.transaction_by_block_number_and_index(num, index).await.to_rpc_result() - } - EthRequest::EthGetTransactionReceipt(tx) => { - self.transaction_receipt(tx).await.to_rpc_result() - } - EthRequest::EthGetBlockReceipts(number) => { - self.block_receipts(number).await.to_rpc_result() - } - EthRequest::EthGetUncleByBlockHashAndIndex(hash, index) => { - self.uncle_by_block_hash_and_index(hash, index).await.to_rpc_result() - } - EthRequest::EthGetUncleByBlockNumberAndIndex(num, index) => { - self.uncle_by_block_number_and_index(num, index).await.to_rpc_result() - } - EthRequest::EthGetLogs(filter) => self.logs(filter).await.to_rpc_result(), - EthRequest::EthGetWork(_) => self.work().to_rpc_result(), - EthRequest::EthSyncing(_) => self.syncing().to_rpc_result(), - EthRequest::EthSubmitWork(nonce, pow, digest) => { - self.submit_work(nonce, pow, digest).to_rpc_result() - } - EthRequest::EthSubmitHashRate(rate, id) => { - self.submit_hashrate(rate, id).to_rpc_result() - } - EthRequest::EthFeeHistory(count, newest, reward_percentiles) => { - self.fee_history(count, newest, reward_percentiles).await.to_rpc_result() - } - // non eth-standard rpc calls - EthRequest::DebugGetRawTransaction(hash) => { - self.raw_transaction(hash).await.to_rpc_result() - } - // non eth-standard rpc calls - EthRequest::DebugTraceTransaction(tx, opts) => { - self.debug_trace_transaction(tx, opts).await.to_rpc_result() - } - // non eth-standard rpc calls - EthRequest::DebugTraceCall(tx, block, opts) => { - self.debug_trace_call(tx, block, opts).await.to_rpc_result() - } - EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), - EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), - EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), - EthRequest::ImpersonateAccount(addr) => { - self.anvil_impersonate_account(addr).await.to_rpc_result() - } - EthRequest::StopImpersonatingAccount(addr) => { - self.anvil_stop_impersonating_account(addr).await.to_rpc_result() - } - EthRequest::AutoImpersonateAccount(enable) => { - self.anvil_auto_impersonate_account(enable).await.to_rpc_result() - } - EthRequest::GetAutoMine(()) => self.anvil_get_auto_mine().to_rpc_result(), - EthRequest::Mine(blocks, interval) => { - self.anvil_mine(blocks, interval).await.to_rpc_result() - } - EthRequest::SetAutomine(enabled) => { - self.anvil_set_auto_mine(enabled).await.to_rpc_result() - } - EthRequest::SetIntervalMining(interval) => { - self.anvil_set_interval_mining(interval).to_rpc_result() - } - EthRequest::GetIntervalMining(()) => self.anvil_get_interval_mining().to_rpc_result(), - EthRequest::DropTransaction(tx) => { - self.anvil_drop_transaction(tx).await.to_rpc_result() + /// Returns the current gas price + pub fn gas_price(&self) -> u128 { + if self.backend.is_eip1559() { + if self.backend.is_min_priority_fee_enforced() { + (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) + } else { + self.backend.base_fee() as u128 } - EthRequest::DropAllTransactions() => { - self.anvil_drop_all_transactions().await.to_rpc_result() + } else { + self.backend.fees().raw_gas_price() + } + } + + /// Returns the suggested fee cap. + /// + /// Returns at least [MIN_SUGGESTED_PRIORITY_FEE] + fn lowest_suggestion_tip(&self) -> u128 { + let block_number = self.backend.best_number(); + let latest_cached_block = self.fee_history_cache.lock().get(&block_number).cloned(); + + match latest_cached_block { + Some(block) => block.rewards.iter().copied().min(), + None => self.fee_history_cache.lock().values().flat_map(|b| b.rewards.clone()).min(), + } + .map(|fee| fee.max(MIN_SUGGESTED_PRIORITY_FEE)) + .unwrap_or(MIN_SUGGESTED_PRIORITY_FEE) + } + + /// Returns true if auto mining is enabled, and false. + /// + /// Handler for ETH RPC call: `anvil_getAutomine` + pub fn anvil_get_auto_mine(&self) -> Result { + node_info!("anvil_getAutomine"); + Ok(self.miner.is_auto_mine()) + } + + /// Returns the value of mining interval, if set. + /// + /// Handler for ETH RPC call: `anvil_getIntervalMining`. + pub fn anvil_get_interval_mining(&self) -> Result> { + node_info!("anvil_getIntervalMining"); + Ok(self.miner.get_interval()) + } + + /// Enables or disables, based on the single boolean argument, the automatic mining of new + /// blocks with each new transaction submitted to the network. + /// + /// Handler for ETH RPC call: `evm_setAutomine` + pub async fn anvil_set_auto_mine(&self, enable_automine: bool) -> Result<()> { + node_info!("evm_setAutomine"); + if self.miner.is_auto_mine() { + if enable_automine { + return Ok(()); } - EthRequest::Reset(fork) => { + self.miner.set_mining_mode(MiningMode::None); + } else if enable_automine { + let listener = self.pool.add_ready_listener(); + let mode = MiningMode::instant(1_000, listener); + self.miner.set_mining_mode(mode); + } + Ok(()) + } + + /// Sets the mining behavior to interval with the given interval (seconds) + /// + /// Handler for ETH RPC call: `evm_setIntervalMining` + pub fn anvil_set_interval_mining(&self, secs: u64) -> Result<()> { + node_info!("evm_setIntervalMining"); + let mining_mode = if secs == 0 { + MiningMode::None + } else { + let block_time = Duration::from_secs(secs); + + // This ensures that memory limits are stricter in interval-mine mode + self.backend.update_interval_mine_block_time(block_time); + + MiningMode::FixedBlockTime(FixedBlockTimeMiner::new(block_time)) + }; + self.miner.set_mining_mode(mining_mode); + Ok(()) + } + + /// Removes transactions from the pool + /// + /// Handler for RPC call: `anvil_dropTransaction` + pub async fn anvil_drop_transaction(&self, tx_hash: B256) -> Result> { + node_info!("anvil_dropTransaction"); + Ok(self.pool.drop_transaction(tx_hash).map(|tx| tx.hash())) + } + + /// Removes all transactions from the pool + /// + /// Handler for RPC call: `anvil_dropAllTransactions` + pub async fn anvil_drop_all_transactions(&self) -> Result<()> { + node_info!("anvil_dropAllTransactions"); + self.pool.clear(); + Ok(()) + } + + pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { + node_info!("anvil_setChainId"); + self.backend.set_chain_id(chain_id); + Ok(()) + } + + /// Modifies the balance of an account. + /// + /// Handler for RPC call: `anvil_setBalance` + pub async fn anvil_set_balance(&self, address: Address, balance: U256) -> Result<()> { + node_info!("anvil_setBalance"); + self.backend.set_balance(address, balance).await?; + Ok(()) + } + + /// Sets the code of a contract. + /// + /// Handler for RPC call: `anvil_setCode` + pub async fn anvil_set_code(&self, address: Address, code: Bytes) -> Result<()> { + node_info!("anvil_setCode"); + self.backend.set_code(address, code).await?; + Ok(()) + } + + /// Sets the nonce of an address. + /// + /// Handler for RPC call: `anvil_setNonce` + pub async fn anvil_set_nonce(&self, address: Address, nonce: U256) -> Result<()> { + node_info!("anvil_setNonce"); + self.backend.set_nonce(address, nonce).await?; + Ok(()) + } + + /// Writes a single slot of the account's storage. + /// + /// Handler for RPC call: `anvil_setStorageAt` + pub async fn anvil_set_storage_at( + &self, + address: Address, + slot: U256, + val: B256, + ) -> Result { + node_info!("anvil_setStorageAt"); + self.backend.set_storage_at(address, slot, val).await?; + Ok(true) + } + + /// Enable or disable logging. + /// + /// Handler for RPC call: `anvil_setLoggingEnabled` + pub async fn anvil_set_logging(&self, enable: bool) -> Result<()> { + node_info!("anvil_setLoggingEnabled"); + self.logger.set_enabled(enable); + Ok(()) + } + + /// Set the minimum gas price for the node. + /// + /// Handler for RPC call: `anvil_setMinGasPrice` + pub async fn anvil_set_min_gas_price(&self, gas: U256) -> Result<()> { + node_info!("anvil_setMinGasPrice"); + if self.backend.is_eip1559() { + return Err(RpcError::invalid_params( + "anvil_setMinGasPrice is not supported when EIP-1559 is active", + ) + .into()); + } + self.backend.set_gas_price(gas.saturating_to()); + Ok(()) + } + + /// Sets the base fee of the next block. + /// + /// Handler for RPC call: `anvil_setNextBlockBaseFeePerGas` + pub async fn anvil_set_next_block_base_fee_per_gas(&self, basefee: U256) -> Result<()> { + node_info!("anvil_setNextBlockBaseFeePerGas"); + if !self.backend.is_eip1559() { + return Err(RpcError::invalid_params( + "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", + ) + .into()); + } + self.backend.set_base_fee(basefee.saturating_to()); + Ok(()) + } + + /// Sets the coinbase address. + /// + /// Handler for RPC call: `anvil_setCoinbase` + pub async fn anvil_set_coinbase(&self, address: Address) -> Result<()> { + node_info!("anvil_setCoinbase"); + self.backend.set_coinbase(address); + Ok(()) + } + + /// Retrieves the Anvil node configuration params. + /// + /// Handler for RPC call: `anvil_nodeInfo` + pub async fn anvil_node_info(&self) -> Result { + node_info!("anvil_nodeInfo"); + + let evm_env = self.backend.evm_env().read(); + let fork_config = self.backend.get_fork(); + let tx_order = self.transaction_order.read(); + let hard_fork = self.backend.hardfork().name(); + + Ok(NodeInfo { + current_block_number: self.backend.best_number(), + current_block_timestamp: evm_env.block_env.timestamp.saturating_to(), + current_block_hash: self.backend.best_hash(), + hard_fork, + transaction_order: match *tx_order { + TransactionOrder::Fifo => "fifo".to_string(), + TransactionOrder::Fees => "fees".to_string(), + }, + environment: NodeEnvironment { + base_fee: self.backend.base_fee() as u128, + chain_id: self.backend.chain_id().to::(), + gas_limit: self.backend.gas_limit(), + gas_price: self.gas_price(), + }, + fork_config: fork_config + .map(|fork| { + let config = fork.config.read(); + + NodeForkConfig { + fork_url: config.eth_rpc_url().map(|s| s.to_string()), + fork_block_number: Some(config.block_number), + fork_retry_backoff: Some(config.backoff.as_millis()), + } + }) + .unwrap_or_default(), + network: self.backend.is_tempo().then(|| "tempo".to_string()), + }) + } + + /// Retrieves metadata about the Anvil instance. + /// + /// Handler for RPC call: `anvil_metadata` + pub async fn anvil_metadata(&self) -> Result { + node_info!("anvil_metadata"); + let fork_config = self.backend.get_fork(); + + Ok(Metadata { + client_version: CLIENT_VERSION.to_string(), + client_semver: Some(SEMVER_VERSION.to_string()), + client_commit_sha: Some(COMMIT_SHA.to_string()), + chain_id: self.backend.chain_id().to::(), + latest_block_hash: self.backend.best_hash(), + latest_block_number: self.backend.best_number(), + instance_id: *self.instance_id.read(), + forked_network: fork_config.map(|cfg| ForkedNetwork { + chain_id: cfg.chain_id(), + fork_block_number: cfg.block_number(), + fork_block_hash: cfg.block_hash(), + }), + snapshots: self.backend.list_state_snapshots(), + }) + } + + pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> { + node_info!("anvil_removePoolTransactions"); + self.pool.remove_transactions_by_address(address); + Ok(()) + } + + /// Snapshot the state of the blockchain at the current block. + /// + /// Handler for RPC call: `evm_snapshot` + pub async fn evm_snapshot(&self) -> Result { + node_info!("evm_snapshot"); + Ok(self.backend.create_state_snapshot().await) + } + + /// Jump forward in time by the given amount of time, in seconds. + /// + /// Handler for RPC call: `evm_increaseTime` + pub async fn evm_increase_time(&self, seconds: U256) -> Result { + node_info!("evm_increaseTime"); + Ok(self.backend.time().increase_time(seconds.try_into().unwrap_or(u64::MAX)) as i64) + } + + /// Similar to `evm_increaseTime` but takes the exact timestamp that you want in the next block + /// + /// Handler for RPC call: `evm_setNextBlockTimestamp` + pub fn evm_set_next_block_timestamp(&self, seconds: u64) -> Result<()> { + node_info!("evm_setNextBlockTimestamp"); + self.backend.time().set_next_block_timestamp(seconds) + } + + /// Sets the specific timestamp and returns the number of seconds between the given timestamp + /// and the current time. + /// + /// The `timestamp` is in seconds. The `evm_setTime` JSON-RPC method accepts both seconds and + /// milliseconds (values above 1e12 are treated as milliseconds); the RPC handler normalises + /// the input to seconds before calling this function. + /// + /// Handler for RPC call: `evm_setTime` + pub fn evm_set_time(&self, timestamp: u64) -> Result { + node_info!("evm_setTime"); + let now = self.backend.time().current_call_timestamp(); + self.backend.time().reset(timestamp); + + // number of seconds between the given timestamp and the current time. + let offset = timestamp.saturating_sub(now); + Ok(offset) + } + + /// Set the next block gas limit + /// + /// Handler for RPC call: `evm_setBlockGasLimit` + pub fn evm_set_block_gas_limit(&self, gas_limit: U256) -> Result { + node_info!("evm_setBlockGasLimit"); + self.backend.set_gas_limit(gas_limit.saturating_to()); + Ok(true) + } + + /// Sets an interval for the block timestamp + /// + /// Handler for RPC call: `anvil_setBlockTimestampInterval` + pub fn evm_set_block_timestamp_interval(&self, seconds: u64) -> Result<()> { + node_info!("anvil_setBlockTimestampInterval"); + self.backend.time().set_block_timestamp_interval(seconds); + Ok(()) + } + + /// Sets an interval for the block timestamp + /// + /// Handler for RPC call: `anvil_removeBlockTimestampInterval` + pub fn evm_remove_block_timestamp_interval(&self) -> Result { + node_info!("anvil_removeBlockTimestampInterval"); + Ok(self.backend.time().remove_block_timestamp_interval()) + } + + /// Sets the backend rpc url + /// + /// Handler for ETH RPC call: `anvil_setRpcUrl` + pub async fn anvil_set_rpc_url(&self, url: String) -> Result<()> { + node_info!("anvil_setRpcUrl"); + if let Some(fork) = self.backend.get_fork() { + let mut config = fork.config.write(); + // let interval = config.provider.get_interval(); + let new_provider = Arc::new( + ProviderBuilder::new(&url).max_retry(10).initial_backoff(1000).build().map_err( + |_| { + TransportErrorKind::custom_str( + format!("Failed to parse invalid url {url}").as_str(), + ) + }, + // TODO: Add interval + )?, // .interval(interval), + ); + config.provider = new_provider; + trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url().unwrap_or("none"), url); + config.fork_urls = vec![url.clone()]; + } + // Keep node_config in sync so anvil_reset(None) uses the updated URL + self.backend.node_config.write().await.fork_urls = vec![url]; + Ok(()) + } + + /// Returns the number of transactions currently pending for inclusion in the next block(s), as + /// well as the ones that are being scheduled for future execution only. + /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) + /// + /// Handler for ETH RPC call: `txpool_status` + pub async fn txpool_status(&self) -> Result { + node_info!("txpool_status"); + Ok(self.pool.txpool_status()) + } + + /// Executes the future on a new blocking task. + async fn on_blocking_task(&self, c: C) -> Result + where + C: FnOnce(Self) -> F, + F: Future> + Send + 'static, + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let this = self.clone(); + let f = c(this); + tokio::task::spawn_blocking(move || { + tokio::runtime::Handle::current().block_on(async move { + let res = f.await; + let _ = tx.send(res); + }) + }); + rx.await.map_err(|_| BlockchainError::Internal("blocking task panicked".to_string()))? + } + + /// Updates the `TransactionOrder` + pub fn set_transaction_order(&self, order: TransactionOrder) { + *self.transaction_order.write() = order; + } + + /// Returns the chain ID used for transaction + pub fn chain_id(&self) -> u64 { + self.backend.chain_id().to::() + } + + /// Returns the configured fork, if any. + pub fn get_fork(&self) -> Option { + self.backend.get_fork() + } + + /// Returns the current instance's ID. + pub fn instance_id(&self) -> B256 { + *self.instance_id.read() + } + + /// Resets the instance ID. + pub fn reset_instance_id(&self) { + *self.instance_id.write() = B256::random(); + } + + /// Returns the first signer that can sign for the given address + #[expect(clippy::borrowed_box)] + pub fn get_signer(&self, address: Address) -> Option<&Box>> { + self.signers.iter().find(|signer| signer.is_signer_for(address)) + } + + /// Returns a new listeners for ready transactions + pub fn new_ready_transactions(&self) -> Receiver { + self.pool.add_ready_listener() + } + + /// Returns true if forked + pub fn is_fork(&self) -> bool { + self.backend.is_fork() + } + + /// Returns the current state root + pub async fn state_root(&self) -> Option { + self.backend.get_db().read().await.maybe_state_root() + } + + /// Returns true if the `addr` is currently impersonated + pub fn is_impersonated(&self, addr: Address) -> bool { + self.backend.cheats().is_impersonated(addr) + } + + /// Returns a new accessor for certain storage elements + pub fn storage_info(&self) -> StorageInfo { + StorageInfo::new(Arc::clone(&self.backend)) + } + + /// Handler for RPC call: `anvil_getBlobByHash` + #[allow(clippy::large_stack_frames)] + pub fn anvil_get_blob_by_versioned_hash( + &self, + hash: B256, + ) -> Result> { + node_info!("anvil_getBlobByHash"); + Ok(self.backend.get_blob_by_versioned_hash(hash)?) + } + + /// Handler for RPC call: `anvil_getBlobsByBlockId` + pub fn anvil_get_blobs_by_block_id( + &self, + block_id: impl Into, + versioned_hashes: Vec, + ) -> Result>> { + node_info!("anvil_getBlobsByBlockId"); + Ok(self.backend.get_blobs_by_block_id(block_id, versioned_hashes)?) + } + + /// Returns the genesis time for the Beacon chain + /// + /// Handler for Beacon API call: `GET /eth/v1/beacon/genesis` + pub fn anvil_get_genesis_time(&self) -> Result { + node_info!("anvil_getGenesisTime"); + Ok(self.backend.genesis_time()) + } + + /// Reset the fork to a fresh forked state, and optionally update the fork config. + /// + /// If `forking` is `None` then this will disable forking entirely. + /// + /// Handler for RPC call: `anvil_reset` + pub async fn anvil_reset(&self, forking: Option) -> Result<()> { + self.reset_instance_id(); + node_info!("anvil_reset"); + if let Some(forking) = forking { + // if we're resetting the fork we need to reset the instance id + self.backend.reset_fork(forking).await?; + } else { + // Reset to a fresh in-memory state + self.backend.reset_to_in_mem().await?; + } + // Clear pending transactions since they reference the old chain state. + self.pool.clear(); + Ok(()) + } + + /// Revert the state of the blockchain to a previous snapshot. + /// Takes a single parameter, which is the snapshot id to revert to. + /// + /// Handler for RPC call: `evm_revert` + pub async fn evm_revert(&self, id: U256) -> Result { + node_info!("evm_revert"); + self.backend.revert_state_snapshot(id).await + } + + /// Send transactions impersonating specific account and contract addresses. + /// + /// Handler for ETH RPC call: `anvil_impersonateAccount` + pub async fn anvil_impersonate_account(&self, address: Address) -> Result<()> { + node_info!("anvil_impersonateAccount"); + self.backend.impersonate(address); + Ok(()) + } + + /// Stops impersonating an account if previously set with `anvil_impersonateAccount`. + /// + /// Handler for ETH RPC call: `anvil_stopImpersonatingAccount` + pub async fn anvil_stop_impersonating_account(&self, address: Address) -> Result<()> { + node_info!("anvil_stopImpersonatingAccount"); + self.backend.stop_impersonating(address); + Ok(()) + } + + /// If set to true will make every account impersonated + /// + /// Handler for ETH RPC call: `anvil_autoImpersonateAccount` + pub async fn anvil_auto_impersonate_account(&self, enabled: bool) -> Result<()> { + node_info!("anvil_autoImpersonateAccount"); + self.backend.auto_impersonate_account(enabled); + Ok(()) + } + + /// Registers a new address and signature pair to impersonate. + pub async fn anvil_impersonate_signature( + &self, + signature: Bytes, + address: Address, + ) -> Result<()> { + node_info!("anvil_impersonateSignature"); + self.backend.impersonate_signature(signature, address).await + } + + /// Returns a new block event stream that yields Notifications when a new block was added + pub fn new_block_notifications(&self) -> NewBlockNotifications { + self.backend.new_block_notifications() + } + + /// Returns the current client version. + /// + /// Handler for ETH RPC call: `web3_clientVersion` + pub fn client_version(&self) -> Result { + node_info!("web3_clientVersion"); + Ok(CLIENT_VERSION.to_string()) + } + + /// Returns Keccak-256 (not the standardized SHA3-256) of the given data. + /// + /// Handler for ETH RPC call: `web3_sha3` + pub fn sha3(&self, bytes: Bytes) -> Result { + node_info!("web3_sha3"); + let hash = alloy_primitives::keccak256(bytes.as_ref()); + Ok(alloy_primitives::hex::encode_prefixed(&hash[..])) + } + + /// Returns protocol version encoded as a string (quotes are necessary). + /// + /// Handler for ETH RPC call: `eth_protocolVersion` + pub fn protocol_version(&self) -> Result { + node_info!("eth_protocolVersion"); + Ok(1) + } + + /// Returns the number of hashes per second that the node is mining with. + /// + /// Handler for ETH RPC call: `eth_hashrate` + pub fn hashrate(&self) -> Result { + node_info!("eth_hashrate"); + Ok(U256::ZERO) + } + + /// Returns the client coinbase address. + /// + /// Handler for ETH RPC call: `eth_coinbase` + pub fn author(&self) -> Result
{ + node_info!("eth_coinbase"); + Ok(self.backend.coinbase()) + } + + /// Returns true if client is actively mining new blocks. + /// + /// Handler for ETH RPC call: `eth_mining` + pub fn is_mining(&self) -> Result { + node_info!("eth_mining"); + Ok(self.is_mining) + } + + /// Returns the chain ID used for transaction signing at the + /// current best block. None is returned if not + /// available. + /// + /// Handler for ETH RPC call: `eth_chainId` + pub fn eth_chain_id(&self) -> Result> { + node_info!("eth_chainId"); + Ok(Some(self.backend.chain_id().to::())) + } + + /// Returns the same as `chain_id` + /// + /// Handler for ETH RPC call: `eth_networkId` + pub fn network_id(&self) -> Result> { + node_info!("eth_networkId"); + let chain_id = self.backend.chain_id().to::(); + Ok(Some(format!("{chain_id}"))) + } + + /// Returns true if client is actively listening for network connections. + /// + /// Handler for ETH RPC call: `net_listening` + pub fn net_listening(&self) -> Result { + node_info!("net_listening"); + Ok(self.net_listening) + } + + /// Returns the current gas price + fn eth_gas_price(&self) -> Result { + node_info!("eth_gasPrice"); + Ok(U256::from(self.gas_price())) + } + + /// Returns the excess blob gas and current blob gas price + pub fn excess_blob_gas_and_price(&self) -> Result> { + Ok(self.backend.excess_blob_gas_and_price()) + } + + /// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or + /// 'tip', to get a transaction included in the current block. + /// + /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` + pub fn gas_max_priority_fee_per_gas(&self) -> Result { + self.max_priority_fee_per_gas() + } + + /// Returns the base fee per blob required to send a EIP-4844 tx. + /// + /// Handler for ETH RPC call: `eth_blobBaseFee` + pub fn blob_base_fee(&self) -> Result { + Ok(U256::from(self.backend.fees().base_fee_per_blob_gas())) + } + + /// Returns the block gas limit + pub fn gas_limit(&self) -> U256 { + U256::from(self.backend.gas_limit()) + } + + /// Returns the accounts list + /// + /// Handler for ETH RPC call: `eth_accounts` + pub fn accounts(&self) -> Result> { + node_info!("eth_accounts"); + let mut unique = HashSet::new(); + let mut accounts: Vec
= Vec::new(); + for signer in self.signers.iter() { + accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); + } + accounts.extend( + self.backend + .cheats() + .impersonated_accounts() + .into_iter() + .filter(|acc| unique.insert(*acc)), + ); + Ok(accounts.into_iter().collect()) + } + + /// Returns the number of most recent block. + /// + /// Handler for ETH RPC call: `eth_blockNumber` + pub fn block_number(&self) -> Result { + node_info!("eth_blockNumber"); + Ok(U256::from(self.backend.best_number())) + } + + /// Returns block with given hash. + /// + /// Handler for ETH RPC call: `eth_getBlockByHash` + pub async fn block_by_hash(&self, hash: B256) -> Result> { + node_info!("eth_getBlockByHash"); + self.backend.block_by_hash(hash).await + } + + /// Returns a _full_ block with given hash. + /// + /// Handler for ETH RPC call: `eth_getBlockByHash` + pub async fn block_by_hash_full(&self, hash: B256) -> Result> { + node_info!("eth_getBlockByHash"); + self.backend.block_by_hash_full(hash).await + } + + /// Returns the number of transactions in a block with given hash. + /// + /// Handler for ETH RPC call: `eth_getBlockTransactionCountByHash` + pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { + node_info!("eth_getBlockTransactionCountByHash"); + let block = self.backend.block_by_hash(hash).await?; + let txs = block.map(|b| match b.transactions() { + BlockTransactions::Full(txs) => U256::from(txs.len()), + BlockTransactions::Hashes(txs) => U256::from(txs.len()), + BlockTransactions::Uncle => U256::from(0), + }); + Ok(txs) + } + + /// Returns the number of uncles in a block with given hash. + /// + /// Handler for ETH RPC call: `eth_getUncleCountByBlockHash` + pub async fn block_uncles_count_by_hash(&self, hash: B256) -> Result { + node_info!("eth_getUncleCountByBlockHash"); + let block = + self.backend.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?; + Ok(U256::from(block.uncles.len())) + } + + /// Returns the number of uncles in a block with given block number. + /// + /// Handler for ETH RPC call: `eth_getUncleCountByBlockNumber` + pub async fn block_uncles_count_by_number(&self, block_number: BlockNumber) -> Result { + node_info!("eth_getUncleCountByBlockNumber"); + let block = self + .backend + .block_by_number(block_number) + .await? + .ok_or(BlockchainError::BlockNotFound)?; + Ok(U256::from(block.uncles.len())) + } + + /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). + /// + /// Handler for ETH RPC call: `eth_signTypedData` + pub async fn sign_typed_data( + &self, + _address: Address, + _data: serde_json::Value, + ) -> Result { + node_info!("eth_signTypedData"); + Err(BlockchainError::RpcUnimplemented) + } + + /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). + /// + /// Handler for ETH RPC call: `eth_signTypedData_v3` + pub async fn sign_typed_data_v3( + &self, + _address: Address, + _data: serde_json::Value, + ) -> Result { + node_info!("eth_signTypedData_v3"); + Err(BlockchainError::RpcUnimplemented) + } + + /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. + /// + /// Handler for ETH RPC call: `eth_signTypedData_v4` + pub async fn sign_typed_data_v4(&self, address: Address, data: &TypedData) -> Result { + node_info!("eth_signTypedData_v4"); + let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; + let signature = signer.sign_typed_data(address, data).await?; + let signature = alloy_primitives::hex::encode(signature.as_bytes()); + Ok(format!("0x{signature}")) + } + + /// The sign method calculates an Ethereum specific signature + /// + /// Handler for ETH RPC call: `eth_sign` + pub async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result { + node_info!("eth_sign"); + let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; + let signature = + alloy_primitives::hex::encode(signer.sign(address, content.as_ref()).await?.as_bytes()); + Ok(format!("0x{signature}")) + } + + /// Returns transaction at given block hash and index. + /// + /// Handler for ETH RPC call: `eth_getTransactionByBlockHashAndIndex` + pub async fn transaction_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> Result> { + node_info!("eth_getTransactionByBlockHashAndIndex"); + self.backend.transaction_by_block_hash_and_index(hash, index).await + } + + /// Returns transaction by given block number and index. + /// + /// Handler for ETH RPC call: `eth_getTransactionByBlockNumberAndIndex` + pub async fn transaction_by_block_number_and_index( + &self, + block: BlockNumber, + idx: Index, + ) -> Result> { + node_info!("eth_getTransactionByBlockNumberAndIndex"); + self.backend.transaction_by_block_number_and_index(block, idx).await + } + + /// Returns an uncles at given block and index. + /// + /// Handler for ETH RPC call: `eth_getUncleByBlockHashAndIndex` + pub async fn uncle_by_block_hash_and_index( + &self, + block_hash: B256, + idx: Index, + ) -> Result> { + node_info!("eth_getUncleByBlockHashAndIndex"); + let number = + self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; + if let Some(fork) = self.get_fork() + && fork.predates_fork_inclusive(number) + { + return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?); + } + // It's impossible to have uncles outside of fork mode + Ok(None) + } + + /// Returns an uncles at given block and index. + /// + /// Handler for ETH RPC call: `eth_getUncleByBlockNumberAndIndex` + pub async fn uncle_by_block_number_and_index( + &self, + block_number: BlockNumber, + idx: Index, + ) -> Result> { + node_info!("eth_getUncleByBlockNumberAndIndex"); + let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; + if let Some(fork) = self.get_fork() + && fork.predates_fork_inclusive(number) + { + return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?); + } + // It's impossible to have uncles outside of fork mode + Ok(None) + } + + /// Returns the hash of the current block, the seedHash, and the boundary condition to be met. + /// + /// Handler for ETH RPC call: `eth_getWork` + pub fn work(&self) -> Result { + node_info!("eth_getWork"); + Err(BlockchainError::RpcUnimplemented) + } + + /// Returns the sync status, always be fails. + /// + /// Handler for ETH RPC call: `eth_syncing` + pub fn syncing(&self) -> Result { + node_info!("eth_syncing"); + Ok(false) + } + + /// Returns the current configuration of the chain. + /// This is useful for finding out what precompiles and system contracts are available. + /// + /// Note: the activation timestamp is always 0 as the configuration is set at genesis. + /// Note: the `fork_id` is always `0x00000000` as this node does not participate in any forking + /// on the network. + /// Note: the `next` and `last` fields are always `null` as this node does not participate in + /// any forking on the network. + /// + /// Handler for ETH RPC call: `eth_config` + pub fn config(&self) -> Result { + node_info!("eth_config"); + Ok(EthConfig { + current: EthForkConfig { + activation_time: 0, + blob_schedule: self.backend.blob_params(), + chain_id: self.backend.chain_id().to::(), + fork_id: Bytes::from_static(&[0; 4]), + precompiles: self.backend.precompiles(), + system_contracts: self.backend.system_contracts(), + }, + next: None, + last: None, + }) + } + + /// Used for submitting a proof-of-work solution. + /// + /// Handler for ETH RPC call: `eth_submitWork` + pub fn submit_work(&self, _: B64, _: B256, _: B256) -> Result { + node_info!("eth_submitWork"); + Err(BlockchainError::RpcUnimplemented) + } + + /// Used for submitting mining hashrate. + /// + /// Handler for ETH RPC call: `eth_submitHashrate` + pub fn submit_hashrate(&self, _: U256, _: B256) -> Result { + node_info!("eth_submitHashrate"); + Err(BlockchainError::RpcUnimplemented) + } + + /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. + /// + /// Handler for ETH RPC call: `eth_feeHistory` + pub async fn fee_history( + &self, + block_count: U256, + newest_block: BlockNumber, + reward_percentiles: Vec, + ) -> Result { + node_info!("eth_feeHistory"); + // max number of blocks in the requested range + + let current = self.backend.best_number(); + let slots_in_an_epoch = 32u64; + + let number = match newest_block { + BlockNumber::Latest | BlockNumber::Pending => current, + BlockNumber::Earliest => 0, + BlockNumber::Number(n) => n, + BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + }; + + // check if the number predates the fork, if in fork mode + if let Some(fork) = self.get_fork() { + // if we're still at the forked block we don't have any history and can't compute it + // efficiently, instead we fetch it from the fork + if fork.predates_fork_inclusive(number) { + return fork + .fee_history(block_count.to(), BlockNumber::Number(number), &reward_percentiles) + .await + .map_err(BlockchainError::AlloyForkProvider); + } + } + + const MAX_BLOCK_COUNT: u64 = 1024u64; + let block_count = block_count.saturating_to::().min(MAX_BLOCK_COUNT); + + // highest and lowest block num in the requested range + let highest = number; + let lowest = highest.saturating_sub(block_count.saturating_sub(1)); + + // only support ranges that are in cache range + if lowest < self.backend.best_number().saturating_sub(self.fee_history_limit) { + return Err(FeeHistoryError::InvalidBlockRange.into()); + } + + let mut response = FeeHistory { + oldest_block: lowest, + base_fee_per_gas: Vec::new(), + gas_used_ratio: Vec::new(), + reward: Some(Default::default()), + base_fee_per_blob_gas: Default::default(), + blob_gas_used_ratio: Default::default(), + }; + let mut rewards = Vec::new(); + + { + let fee_history = self.fee_history_cache.lock(); + + // iter over the requested block range + for n in lowest..=highest { + // + if let Some(block) = fee_history.get(&n) { + response.base_fee_per_gas.push(block.base_fee); + response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0)); + response.blob_gas_used_ratio.push(block.blob_gas_used_ratio); + response.gas_used_ratio.push(block.gas_used_ratio); + + // requested percentiles + if !reward_percentiles.is_empty() { + let mut block_rewards = Vec::new(); + let resolution_per_percentile: f64 = 2.0; + for p in &reward_percentiles { + let p = p.clamp(0.0, 100.0); + let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; + let reward = block.rewards.get(index as usize).map_or(0, |r| *r); + block_rewards.push(reward); + } + rewards.push(block_rewards); + } + } + } + } + + response.reward = Some(rewards); + + // add the next block's base fee to the response + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + response.base_fee_per_gas.push(self.backend.fees().base_fee() as u128); + + // Same goes for the `base_fee_per_blob_gas`: + // > [..] includes the next block after the newest of the returned range, because this + // > value can be derived from the newest block. + response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas()); + + Ok(response) + } + + /// Introduced in EIP-1159, a Geth-specific and simplified priority fee oracle. + /// Leverages the already existing fee history cache. + /// + /// Returns a suggestion for a gas tip cap for dynamic fee transactions. + /// + /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` + pub fn max_priority_fee_per_gas(&self) -> Result { + node_info!("eth_maxPriorityFeePerGas"); + Ok(U256::from(self.lowest_suggestion_tip())) + } + + /// Returns code by its hash + /// + /// Handler for RPC call: `debug_codeByHash` + pub async fn debug_code_by_hash( + &self, + hash: B256, + block_id: Option, + ) -> Result> { + node_info!("debug_codeByHash"); + self.backend.debug_code_by_hash(hash, block_id).await + } + + /// Returns the value associated with a key from the database + /// Only supports bytecode lookups. + /// + /// Handler for RPC call: `debug_dbGet` + pub async fn debug_db_get(&self, key: String) -> Result> { + node_info!("debug_dbGet"); + self.backend.debug_db_get(key).await + } + + /// Returns traces for the transaction hash via parity's tracing endpoint + /// + /// Handler for RPC call: `trace_transaction` + pub async fn trace_transaction(&self, tx_hash: B256) -> Result> { + node_info!("trace_transaction"); + self.backend.trace_transaction(tx_hash).await + } + + /// Returns traces for the transaction hash via parity's tracing endpoint + /// + /// Handler for RPC call: `trace_block` + pub async fn trace_block(&self, block: BlockNumber) -> Result> { + node_info!("trace_block"); + self.backend.trace_block(block).await + } + + /// Returns filtered traces over blocks + /// + /// Handler for RPC call: `trace_filter` + pub async fn trace_filter( + &self, + filter: TraceFilter, + ) -> Result> { + node_info!("trace_filter"); + self.backend.trace_filter(filter).await + } + + /// Replays all transactions in a block returning the requested traces for each transaction + /// + /// Handler for RPC call: `trace_replayBlockTransactions` + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result> { + node_info!("trace_replayBlockTransactions"); + self.backend.trace_replay_block_transactions(block, trace_types).await + } +} + +impl> EthApi { + /// Returns the current state + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + self.backend.serialized_state(preserve_historical_states).await + } +} + +// == impl EthApi anvil endpoints == + +impl EthApi { + /// Create a buffer that represents all state on the chain, which can be loaded to separate + /// process by calling `anvil_loadState` + /// + /// Handler for RPC call: `anvil_dumpState` + pub async fn anvil_dump_state( + &self, + preserve_historical_states: Option, + ) -> Result { + node_info!("anvil_dumpState"); + self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await + } + + /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or + /// storage. + /// + /// Handler for RPC call: `anvil_loadState` + pub async fn anvil_load_state(&self, buf: Bytes) -> Result { + node_info!("anvil_loadState"); + self.backend.load_state_bytes(buf).await + } + + async fn block_request( + &self, + block_number: Option, + ) -> Result> { + let block_request = match block_number { + Some(BlockId::Number(BlockNumber::Pending)) => { + let pending_txs = self.pool.ready_transactions().collect(); + BlockRequest::Pending(pending_txs) + } + _ => { + let number = self.backend.ensure_block_number(block_number).await?; + BlockRequest::Number(number) + } + }; + Ok(block_request) + } + + /// Increases the balance of an account. + /// + /// Handler for RPC call: `anvil_addBalance` + pub async fn anvil_add_balance(&self, address: Address, balance: U256) -> Result<()> { + node_info!("anvil_addBalance"); + let current_balance = self.backend.get_balance(address, None).await?; + self.backend.set_balance(address, current_balance.saturating_add(balance)).await?; + Ok(()) + } + + /// Rollback the chain to a specific depth. + /// + /// e.g depth = 3 + /// A -> B -> C -> D -> E + /// A -> B + /// + /// Depth specifies the height to rollback the chain back to. Depth must not exceed the current + /// chain height, i.e. can't rollback past the genesis block. + /// + /// Handler for RPC call: `anvil_rollback` + pub async fn anvil_rollback(&self, depth: Option) -> Result<()> { + node_info!("anvil_rollback"); + let depth = depth.unwrap_or(1); + + // Check reorg depth doesn't exceed current chain height + let current_height = self.backend.best_number(); + let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( + RpcError::invalid_params(format!( + "Rollback depth must not exceed current chain height: current height {current_height}, depth {depth}" + )), + ))?; + + // Get the common ancestor block + let common_block = + self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; + + self.backend.rollback(common_block).await?; + Ok(()) + } + + /// Estimates the gas usage of the `request` with the state. + /// + /// This will execute the transaction request and find the best gas limit via binary search. + fn do_estimate_gas_with_state( + &self, + mut request: WithOtherFields, + state: &dyn DatabaseRef, + block_env: BlockEnv, + ) -> Result { + // If the request is a simple native token transfer we can optimize + // We assume it's a transfer if we have no input data. + // Skip this optimization for Tempo mode since native ETH transfers are not allowed + // and Tempo AA transactions have higher intrinsic gas costs (~46k). + if !self.backend.is_tempo() { + let to = request.to.as_ref().and_then(TxKind::to); + + // check certain fields to see if the request could be a simple transfer + let maybe_transfer = (request.input.input().is_none() + || request.input.input().is_some_and(|data| data.is_empty())) + && request.authorization_list.is_none() + && request.access_list.is_none() + && request.blob_versioned_hashes.is_none(); + + if maybe_transfer + && let Some(to) = to + && let Ok(target_code) = self.backend.get_code_with_state(&state, *to) + && target_code.as_ref().is_empty() + { + return Ok(MIN_TRANSACTION_GAS); + } + } + + let fees = FeeDetails::new( + request.gas_price, + request.max_fee_per_gas, + request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, + )? + .or_zero_fees(); + + // get the highest possible gas limit, either the request's set value or the currently + // configured gas limit + let mut highest_gas_limit = request.gas.map_or(block_env.gas_limit.into(), |g| g as u128); + + // Tempo AA transactions pay fees in ERC-20 tokens, not ETH + let is_tempo_tx = request.other.get("feeToken").is_some_and(|v| !v.is_null()); + + let gas_price = fees.gas_price.unwrap_or_default(); + // If we have non-zero gas price, cap gas limit by sender balance. + // Skip this check for Tempo transactions which pay with fee tokens, not ETH. + if gas_price > 0 + && !is_tempo_tx + && let Some(from) = request.from + { + let mut available_funds = self.backend.get_balance_with_state(state, from)?; + if let Some(value) = request.value { + if value > available_funds { + return Err(InvalidTransactionError::InsufficientFunds.into()); + } + // safe: value < available_funds + available_funds -= value; + } + // amount of gas the sender can afford with the `gas_price` + let allowance = available_funds.checked_div(U256::from(gas_price)).unwrap_or_default(); + highest_gas_limit = std::cmp::min(highest_gas_limit, allowance.saturating_to()); + } + + let mut call_to_estimate = request.clone(); + call_to_estimate.gas = Some(highest_gas_limit as u64); + + // execute the call without writing to db + let ethres = + self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); + + let gas_used = match ethres.try_into()? { + GasEstimationCallResult::Success(gas) => Ok(gas), + GasEstimationCallResult::OutOfGas => { + Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) + } + GasEstimationCallResult::Revert(output) => { + Err(InvalidTransactionError::Revert(output).into()) + } + GasEstimationCallResult::EvmError(err) => { + warn!(target: "node", "estimation failed due to {:?}", err); + Err(BlockchainError::EvmError(err)) + } + }?; + + // at this point we know the call succeeded but want to find the _best_ (lowest) gas the + // transaction succeeds with. we find this by doing a binary search over the + // possible range NOTE: this is the gas the transaction used, which is less than the + // transaction requires to succeed + + // Get the starting lowest gas needed depending on the transaction kind. + let mut lowest_gas_limit = determine_base_gas_by_kind(&request); + + // pick a point that's close to the estimated gas + let mut mid_gas_limit = + std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); + + // Binary search for the ideal gas limit + while (highest_gas_limit - lowest_gas_limit) > 1 { + request.gas = Some(mid_gas_limit as u64); + let ethres = self.backend.call_with_state( + &state, + request.clone(), + fees.clone(), + block_env.clone(), + ); + + match ethres.try_into()? { + GasEstimationCallResult::Success(_) => { + // If the transaction succeeded, we can set a ceiling for the highest gas limit + // at the current midpoint, as spending any more gas would + // make no sense (as the TX would still succeed). + highest_gas_limit = mid_gas_limit; + } + GasEstimationCallResult::OutOfGas + | GasEstimationCallResult::Revert(_) + | GasEstimationCallResult::EvmError(_) => { + // If the transaction failed, we can set a floor for the lowest gas limit at the + // current midpoint, as spending any less gas would make no + // sense (as the TX would still revert due to lack of gas). + // + // We don't care about the reason here, as we known that transaction is correct + // as it succeeded earlier + lowest_gas_limit = mid_gas_limit; + } + }; + // new midpoint + mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; + } + + trace!(target : "node", "Estimated Gas for call {:?}", highest_gas_limit); + + Ok(highest_gas_limit) + } + + /// Executes the [EthRequest] and returns an RPC [ResponseResult]. + #[allow(clippy::large_stack_frames)] + pub async fn execute(&self, request: EthRequest) -> ResponseResult { + trace!(target: "rpc::api", "executing eth request"); + let response = match request.clone() { + EthRequest::EthProtocolVersion(()) => self.protocol_version().to_rpc_result(), + EthRequest::Web3ClientVersion(()) => self.client_version().to_rpc_result(), + EthRequest::Web3Sha3(content) => self.sha3(content).to_rpc_result(), + EthRequest::EthGetAccount(addr, block) => { + self.get_account(addr, block).await.to_rpc_result() + } + EthRequest::EthGetAccountInfo(addr, block) => { + self.get_account_info(addr, block).await.to_rpc_result() + } + EthRequest::EthGetBalance(addr, block) => { + self.balance(addr, block).await.to_rpc_result() + } + EthRequest::EthGetTransactionByHash(hash) => { + self.transaction_by_hash(hash).await.to_rpc_result() + } + EthRequest::EthSendTransaction(request) => { + self.send_transaction(*request).await.to_rpc_result() + } + EthRequest::EthSendTransactionSync(request) => { + self.send_transaction_sync(*request).await.to_rpc_result() + } + EthRequest::EthChainId(_) => self.eth_chain_id().to_rpc_result(), + EthRequest::EthNetworkId(_) => self.network_id().to_rpc_result(), + EthRequest::NetListening(_) => self.net_listening().to_rpc_result(), + EthRequest::EthHashrate(()) => self.hashrate().to_rpc_result(), + EthRequest::EthGasPrice(_) => self.eth_gas_price().to_rpc_result(), + EthRequest::EthMaxPriorityFeePerGas(_) => { + self.gas_max_priority_fee_per_gas().to_rpc_result() + } + EthRequest::EthBlobBaseFee(_) => self.blob_base_fee().to_rpc_result(), + EthRequest::EthAccounts(_) => self.accounts().to_rpc_result(), + EthRequest::EthBlockNumber(_) => self.block_number().to_rpc_result(), + EthRequest::EthCoinbase(()) => self.author().to_rpc_result(), + EthRequest::EthGetStorageAt(addr, slot, block) => { + self.storage_at(addr, slot, block).await.to_rpc_result() + } + EthRequest::EthGetStorageValues(requests, block) => { + self.storage_values(requests, block).await.to_rpc_result() + } + EthRequest::EthGetBlockByHash(hash, full) => { + if full { + self.block_by_hash_full(hash).await.to_rpc_result() + } else { + self.block_by_hash(hash).await.to_rpc_result() + } + } + EthRequest::EthGetBlockByNumber(num, full) => { + if full { + self.block_by_number_full(num).await.to_rpc_result() + } else { + self.block_by_number(num).await.to_rpc_result() + } + } + EthRequest::EthGetTransactionCount(addr, block) => { + self.transaction_count(addr, block).await.to_rpc_result() + } + EthRequest::EthGetTransactionCountByHash(hash) => { + self.block_transaction_count_by_hash(hash).await.to_rpc_result() + } + EthRequest::EthGetTransactionCountByNumber(num) => { + self.block_transaction_count_by_number(num).await.to_rpc_result() + } + EthRequest::EthGetUnclesCountByHash(hash) => { + self.block_uncles_count_by_hash(hash).await.to_rpc_result() + } + EthRequest::EthGetUnclesCountByNumber(num) => { + self.block_uncles_count_by_number(num).await.to_rpc_result() + } + EthRequest::EthGetCodeAt(addr, block) => { + self.get_code(addr, block).await.to_rpc_result() + } + EthRequest::EthGetProof(addr, keys, block) => { + self.get_proof(addr, keys, block).await.to_rpc_result() + } + EthRequest::EthSign(addr, content) => self.sign(addr, content).await.to_rpc_result(), + EthRequest::PersonalSign(content, addr) => { + self.sign(addr, content).await.to_rpc_result() + } + EthRequest::EthSignTransaction(request) => { + self.sign_transaction(*request).await.to_rpc_result() + } + EthRequest::EthSignTypedData(addr, data) => { + self.sign_typed_data(addr, data).await.to_rpc_result() + } + EthRequest::EthSignTypedDataV3(addr, data) => { + self.sign_typed_data_v3(addr, data).await.to_rpc_result() + } + EthRequest::EthSignTypedDataV4(addr, data) => { + self.sign_typed_data_v4(addr, &data).await.to_rpc_result() + } + EthRequest::EthSendRawTransaction(tx) => { + self.send_raw_transaction(tx).await.to_rpc_result() + } + EthRequest::EthSendRawTransactionSync(tx) => { + self.send_raw_transaction_sync(tx).await.to_rpc_result() + } + EthRequest::EthCall(call, block, state_override, block_overrides) => self + .call(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), + EthRequest::EthSimulateV1(simulation, block) => { + self.simulate_v1(simulation, block).await.to_rpc_result() + } + EthRequest::EthCreateAccessList(call, block) => { + self.create_access_list(call, block).await.to_rpc_result() + } + EthRequest::EthEstimateGas(call, block, state_override, block_overrides) => self + .estimate_gas(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), + EthRequest::EthFillTransaction(request) => { + self.fill_transaction(request).await.to_rpc_result() + } + EthRequest::EthGetRawTransactionByHash(hash) => { + self.raw_transaction(hash).await.to_rpc_result() + } + EthRequest::GetBlobByHash(hash) => { + self.anvil_get_blob_by_versioned_hash(hash).to_rpc_result() + } + EthRequest::GetBlobByTransactionHash(hash) => { + self.anvil_get_blob_by_tx_hash(hash).to_rpc_result() + } + EthRequest::GetGenesisTime(()) => self.anvil_get_genesis_time().to_rpc_result(), + EthRequest::EthGetRawTransactionByBlockHashAndIndex(hash, index) => { + self.raw_transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() + } + EthRequest::EthGetRawTransactionByBlockNumberAndIndex(num, index) => { + self.raw_transaction_by_block_number_and_index(num, index).await.to_rpc_result() + } + EthRequest::EthGetTransactionByBlockHashAndIndex(hash, index) => { + self.transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() + } + EthRequest::EthGetTransactionByBlockNumberAndIndex(num, index) => { + self.transaction_by_block_number_and_index(num, index).await.to_rpc_result() + } + EthRequest::EthGetTransactionReceipt(tx) => { + self.transaction_receipt(tx).await.to_rpc_result() + } + EthRequest::EthGetBlockReceipts(number) => { + self.block_receipts(number).await.to_rpc_result() + } + EthRequest::EthGetUncleByBlockHashAndIndex(hash, index) => { + self.uncle_by_block_hash_and_index(hash, index).await.to_rpc_result() + } + EthRequest::EthGetUncleByBlockNumberAndIndex(num, index) => { + self.uncle_by_block_number_and_index(num, index).await.to_rpc_result() + } + EthRequest::EthGetLogs(filter) => self.logs(filter).await.to_rpc_result(), + EthRequest::EthGetWork(_) => self.work().to_rpc_result(), + EthRequest::EthSyncing(_) => self.syncing().to_rpc_result(), + EthRequest::EthConfig(_) => self.config().to_rpc_result(), + EthRequest::EthSubmitWork(nonce, pow, digest) => { + self.submit_work(nonce, pow, digest).to_rpc_result() + } + EthRequest::EthSubmitHashRate(rate, id) => { + self.submit_hashrate(rate, id).to_rpc_result() + } + EthRequest::EthFeeHistory(count, newest, reward_percentiles) => { + self.fee_history(count, newest, reward_percentiles).await.to_rpc_result() + } + // non eth-standard rpc calls + EthRequest::DebugGetRawTransaction(hash) => { + self.raw_transaction(hash).await.to_rpc_result() + } + // non eth-standard rpc calls + EthRequest::DebugTraceTransaction(tx, opts) => { + self.debug_trace_transaction(tx, opts).await.to_rpc_result() + } + // non eth-standard rpc calls + EthRequest::DebugTraceCall(tx, block, opts) => { + self.debug_trace_call(tx, block, opts).await.to_rpc_result() + } + EthRequest::DebugCodeByHash(hash, block) => { + self.debug_code_by_hash(hash, block).await.to_rpc_result() + } + EthRequest::DebugDbGet(key) => self.debug_db_get(key).await.to_rpc_result(), + EthRequest::DebugTraceBlockByHash(block_hash, opts) => { + self.debug_trace_block_by_hash(block_hash, opts).await.to_rpc_result() + } + EthRequest::DebugTraceBlockByNumber(block_number, opts) => { + self.debug_trace_block_by_number(block_number, opts).await.to_rpc_result() + } + EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), + EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), + EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), + EthRequest::TraceReplayBlockTransactions(block, trace_types) => { + self.trace_replay_block_transactions(block, trace_types).await.to_rpc_result() + } + EthRequest::ImpersonateAccount(addr) => { + self.anvil_impersonate_account(addr).await.to_rpc_result() + } + EthRequest::StopImpersonatingAccount(addr) => { + self.anvil_stop_impersonating_account(addr).await.to_rpc_result() + } + EthRequest::AutoImpersonateAccount(enable) => { + self.anvil_auto_impersonate_account(enable).await.to_rpc_result() + } + EthRequest::ImpersonateSignature(signature, address) => { + self.anvil_impersonate_signature(signature, address).await.to_rpc_result() + } + EthRequest::GetAutoMine(()) => self.anvil_get_auto_mine().to_rpc_result(), + EthRequest::Mine(blocks, interval) => { + self.anvil_mine(blocks, interval).await.to_rpc_result() + } + EthRequest::SetAutomine(enabled) => { + self.anvil_set_auto_mine(enabled).await.to_rpc_result() + } + EthRequest::SetIntervalMining(interval) => { + self.anvil_set_interval_mining(interval).to_rpc_result() + } + EthRequest::GetIntervalMining(()) => self.anvil_get_interval_mining().to_rpc_result(), + EthRequest::DropTransaction(tx) => { + self.anvil_drop_transaction(tx).await.to_rpc_result() + } + EthRequest::DropAllTransactions() => { + self.anvil_drop_all_transactions().await.to_rpc_result() + } + EthRequest::Reset(fork) => { self.anvil_reset(fork.and_then(|p| p.params)).await.to_rpc_result() } EthRequest::SetBalance(addr, val) => { @@ -362,6 +1726,10 @@ impl EthApi { EthRequest::DealERC20(addr, token_addr, val) => { self.anvil_deal_erc20(addr, token_addr, val).await.to_rpc_result() } + EthRequest::SetERC20Allowance(owner, spender, token_addr, val) => self + .anvil_set_erc20_allowance(owner, spender, token_addr, val) + .await + .to_rpc_result(), EthRequest::SetCode(addr, code) => { self.anvil_set_code(addr, code).await.to_rpc_result() } @@ -394,7 +1762,7 @@ impl EthApi { if time >= U256::from(u64::MAX) { return ResponseResult::Error(RpcError::invalid_params( "The timestamp is too big", - )) + )); } let time = time.to::(); self.evm_set_next_block_timestamp(time).to_rpc_result() @@ -403,9 +1771,17 @@ impl EthApi { if timestamp >= U256::from(u64::MAX) { return ResponseResult::Error(RpcError::invalid_params( "The timestamp is too big", - )) + )); } - let time = timestamp.to::(); + // evm_setTime accepts either seconds or milliseconds for Ganache compatibility. + // Timestamps above 1e12 are interpreted as milliseconds and converted to + // seconds; below that threshold they are treated as seconds directly. + let raw = timestamp.to::(); + let time = if raw > 1_000_000_000_000 { + Duration::from_millis(raw).as_secs() + } else { + raw + }; self.evm_set_time(time).to_rpc_result() } EthRequest::EvmSetBlockGasLimit(gas_limit) => { @@ -423,11 +1799,10 @@ impl EthApi { EthRequest::EvmMineDetailed(mine) => { self.evm_mine_detailed(mine.and_then(|p| p.params)).await.to_rpc_result() } - EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).to_rpc_result(), + EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).await.to_rpc_result(), EthRequest::EthSendUnsignedTransaction(tx) => { self.eth_send_unsigned_transaction(*tx).await.to_rpc_result() } - EthRequest::EnableTraces(_) => self.anvil_enable_traces().await.to_rpc_result(), EthRequest::EthNewFilter(filter) => self.new_filter(filter).await.to_rpc_result(), EthRequest::EthGetFilterChanges(id) => self.get_filter_changes(&id).await, EthRequest::EthNewBlockFilter(_) => self.new_block_filter().await.to_rpc_result(), @@ -471,6 +1846,9 @@ impl EthApi { EthRequest::OtsGetTransactionBySenderAndNonce(address, nonce) => { self.ots_get_transaction_by_sender_and_nonce(address, nonce).await.to_rpc_result() } + EthRequest::EthGetTransactionBySenderAndNonce(sender, nonce) => { + self.transaction_by_sender_and_nonce(sender, nonce).await.to_rpc_result() + } EthRequest::OtsGetContractCreator(address) => { self.ots_get_contract_creator(address).await.to_rpc_result() } @@ -481,14 +1859,16 @@ impl EthApi { self.anvil_reorg(reorg_options).await.to_rpc_result() } EthRequest::Rollback(depth) => self.anvil_rollback(depth).await.to_rpc_result(), - EthRequest::WalletGetCapabilities(()) => self.get_capabilities().to_rpc_result(), - EthRequest::WalletSendTransaction(tx) => { - self.wallet_send_transaction(*tx).await.to_rpc_result() + EthRequest::SetFeeToken(user, token) => { + self.anvil_set_fee_token(user, token).await.to_rpc_result() } - EthRequest::AnvilAddCapability(addr) => self.anvil_add_capability(addr).to_rpc_result(), - EthRequest::AnvilSetExecutor(executor_pk) => { - self.anvil_set_executor(executor_pk).to_rpc_result() + EthRequest::SetValidatorFeeToken(validator, token) => { + self.anvil_set_validator_fee_token(validator, token).await.to_rpc_result() } + EthRequest::SetFeeAmmLiquidity(user_token, validator_token, amount) => self + .anvil_set_fee_amm_liquidity(user_token, validator_token, amount) + .await + .to_rpc_result(), }; if let ResponseResult::Error(err) = &response { @@ -500,25 +1880,14 @@ impl EthApi { response } - fn sign_request( - &self, - from: &Address, - request: TypedTransactionRequest, - ) -> Result { - match request { - TypedTransactionRequest::Deposit(_) => { - let nil_signature = Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ); - return build_typed_transaction(request, nil_signature) - } + fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { + match typed_tx { + #[cfg(feature = "optimism")] + FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { if signer.accounts().contains(from) { - let signature = signer.sign_transaction(request.clone(), from)?; - return build_typed_transaction(request, signature) + return signer.sign_transaction_from(from, typed_tx); } } } @@ -526,178 +1895,16 @@ impl EthApi { Err(BlockchainError::NoSignerAvailable) } - async fn block_request(&self, block_number: Option) -> Result { - let block_request = match block_number { - Some(BlockId::Number(BlockNumber::Pending)) => { - let pending_txs = self.pool.ready_transactions().collect(); - BlockRequest::Pending(pending_txs) - } - _ => { - let number = self.backend.ensure_block_number(block_number).await?; - BlockRequest::Number(number) - } - }; - Ok(block_request) - } - async fn inner_raw_transaction(&self, hash: B256) -> Result> { match self.pool.get_transaction(hash) { Some(tx) => Ok(Some(tx.transaction.encoded_2718().into())), None => match self.backend.transaction_by_hash(hash).await? { - Some(tx) => Ok(Some(tx.inner.inner.encoded_2718().into())), + Some(tx) => Ok(Some(tx.as_ref().encoded_2718().into())), None => Ok(None), }, } } - /// Returns the current client version. - /// - /// Handler for ETH RPC call: `web3_clientVersion` - pub fn client_version(&self) -> Result { - node_info!("web3_clientVersion"); - Ok(CLIENT_VERSION.to_string()) - } - - /// Returns Keccak-256 (not the standardized SHA3-256) of the given data. - /// - /// Handler for ETH RPC call: `web3_sha3` - pub fn sha3(&self, bytes: Bytes) -> Result { - node_info!("web3_sha3"); - let hash = alloy_primitives::keccak256(bytes.as_ref()); - Ok(alloy_primitives::hex::encode_prefixed(&hash[..])) - } - - /// Returns protocol version encoded as a string (quotes are necessary). - /// - /// Handler for ETH RPC call: `eth_protocolVersion` - pub fn protocol_version(&self) -> Result { - node_info!("eth_protocolVersion"); - Ok(1) - } - - /// Returns the number of hashes per second that the node is mining with. - /// - /// Handler for ETH RPC call: `eth_hashrate` - pub fn hashrate(&self) -> Result { - node_info!("eth_hashrate"); - Ok(U256::ZERO) - } - - /// Returns the client coinbase address. - /// - /// Handler for ETH RPC call: `eth_coinbase` - pub fn author(&self) -> Result
{ - node_info!("eth_coinbase"); - Ok(self.backend.coinbase()) - } - - /// Returns true if client is actively mining new blocks. - /// - /// Handler for ETH RPC call: `eth_mining` - pub fn is_mining(&self) -> Result { - node_info!("eth_mining"); - Ok(self.is_mining) - } - - /// Returns the chain ID used for transaction signing at the - /// current best block. None is returned if not - /// available. - /// - /// Handler for ETH RPC call: `eth_chainId` - pub fn eth_chain_id(&self) -> Result> { - node_info!("eth_chainId"); - Ok(Some(self.backend.chain_id().to::())) - } - - /// Returns the same as `chain_id` - /// - /// Handler for ETH RPC call: `eth_networkId` - pub fn network_id(&self) -> Result> { - node_info!("eth_networkId"); - let chain_id = self.backend.chain_id().to::(); - Ok(Some(format!("{chain_id}"))) - } - - /// Returns true if client is actively listening for network connections. - /// - /// Handler for ETH RPC call: `net_listening` - pub fn net_listening(&self) -> Result { - node_info!("net_listening"); - Ok(self.net_listening) - } - - /// Returns the current gas price - fn eth_gas_price(&self) -> Result { - node_info!("eth_gasPrice"); - Ok(U256::from(self.gas_price())) - } - - /// Returns the current gas price - pub fn gas_price(&self) -> u128 { - if self.backend.is_eip1559() { - if self.backend.is_min_priority_fee_enforced() { - (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) - } else { - self.backend.base_fee() as u128 - } - } else { - self.backend.fees().raw_gas_price() - } - } - - /// Returns the excess blob gas and current blob gas price - pub fn excess_blob_gas_and_price(&self) -> Result> { - Ok(self.backend.excess_blob_gas_and_price()) - } - - /// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or - /// 'tip', to get a transaction included in the current block. - /// - /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` - pub fn gas_max_priority_fee_per_gas(&self) -> Result { - self.max_priority_fee_per_gas() - } - - /// Returns the base fee per blob required to send a EIP-4844 tx. - /// - /// Handler for ETH RPC call: `eth_blobBaseFee` - pub fn blob_base_fee(&self) -> Result { - Ok(U256::from(self.backend.fees().base_fee_per_blob_gas())) - } - - /// Returns the block gas limit - pub fn gas_limit(&self) -> U256 { - U256::from(self.backend.gas_limit()) - } - - /// Returns the accounts list - /// - /// Handler for ETH RPC call: `eth_accounts` - pub fn accounts(&self) -> Result> { - node_info!("eth_accounts"); - let mut unique = HashSet::new(); - let mut accounts: Vec
= Vec::new(); - for signer in self.signers.iter() { - accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); - } - accounts.extend( - self.backend - .cheats() - .impersonated_accounts() - .into_iter() - .filter(|acc| unique.insert(*acc)), - ); - Ok(accounts.into_iter().collect()) - } - - /// Returns the number of most recent block. - /// - /// Handler for ETH RPC call: `eth_blockNumber` - pub fn block_number(&self) -> Result { - node_info!("eth_blockNumber"); - Ok(U256::from(self.backend.best_number())) - } - /// Returns balance of the given account. /// /// Handler for ETH RPC call: `eth_getBalance` @@ -706,12 +1913,11 @@ impl EthApi { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.get_balance(address, number).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.get_balance(address, number).await?); } self.backend.get_balance(address, Some(block_request)).await @@ -724,35 +1930,62 @@ impl EthApi { &self, address: Address, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getAccount"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.get_account(address, number).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.get_account(address, number).await?); } self.backend.get_account_at_block(address, Some(block_request)).await } /// Returns the account information including balance, nonce, code and storage + /// + /// Note: This isn't support by all providers pub async fn get_account_info( &self, address: Address, block_number: Option, ) -> Result { node_info!("eth_getAccountInfo"); - let account = self - .backend - .get_account_at_block(address, Some(self.block_request(block_number).await?)) - .await?; - let code = - self.backend.get_code(address, Some(self.block_request(block_number).await?)).await?; + + if let Some(fork) = self.get_fork() { + let block_request = self.block_request(block_number).await?; + // check if the number predates the fork, if in fork mode + if let BlockRequest::Number(number) = block_request { + trace!(target: "node", "get_account_info: fork block {}, requested block {number}", fork.block_number()); + return if fork.predates_fork(number) { + // if this predates the fork we need to fetch balance, nonce, code individually + // because the provider might not support this endpoint + let balance = fork.get_balance(address, number).map_err(BlockchainError::from); + let code = fork.get_code(address, number).map_err(BlockchainError::from); + let nonce = self.get_transaction_count(address, Some(number.into())); + let (balance, code, nonce) = try_join!(balance, code, nonce)?; + + Ok(alloy_rpc_types::eth::AccountInfo { balance, nonce, code }) + } else { + // Anvil node is at the same block or higher than the fork block, + // return account info from backend to reflect current state. + let account_info = self.backend.get_account(address).await?; + let code = self.backend.get_code(address, Some(block_request)).await?; + Ok(alloy_rpc_types::eth::AccountInfo { + balance: account_info.balance, + nonce: account_info.nonce, + code, + }) + }; + } + } + + let account = self.get_account(address, block_number); + let code = self.get_code(address, block_number); + let (account, code) = try_join!(account, code)?; Ok(alloy_rpc_types::eth::AccountInfo { balance: account.balance, nonce: account.nonce, @@ -772,33 +2005,57 @@ impl EthApi { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(B256::from( - fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, - )); - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(B256::from( + fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, + )); } self.backend.storage_at(address, index, Some(block_request)).await } - /// Returns block with given hash. + /// Returns storage values for multiple accounts and slots in a single call. /// - /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash(&self, hash: B256) -> Result> { - node_info!("eth_getBlockByHash"); - self.backend.block_by_hash(hash).await - } + /// Handler for ETH RPC call: `eth_getStorageValues` + pub async fn storage_values( + &self, + requests: HashMap>, + block_number: Option, + ) -> Result>> { + node_info!("eth_getStorageValues"); + + let total_slots: usize = requests.values().map(|s| s.len()).sum(); + if total_slots > 1024 { + return Err(BlockchainError::RpcError(RpcError::invalid_params(format!( + "total slot count {total_slots} exceeds limit 1024" + )))); + } - /// Returns a _full_ block with given hash. - /// - /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash_full(&self, hash: B256) -> Result> { - node_info!("eth_getBlockByHash"); - self.backend.block_by_hash_full(hash).await + let block_request = self.block_request(block_number).await?; + + // check if the number predates the fork, if in fork mode + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + let mut result: HashMap> = HashMap::default(); + for (address, slots) in requests { + let mut values = Vec::with_capacity(slots.len()); + for slot in &slots { + let val = fork + .storage_at(address, (*slot).into(), Some(BlockNumber::Number(number))) + .await?; + values.push(B256::from(val)); + } + result.insert(address, values); + } + return Ok(result); + } + + self.backend.storage_values(requests, Some(block_request)).await } /// Returns block with given number. @@ -833,24 +2090,10 @@ impl EthApi { pub async fn transaction_count( &self, address: Address, - block_number: Option, - ) -> Result { - node_info!("eth_getTransactionCount"); - self.get_transaction_count(address, block_number).await.map(U256::from) - } - - /// Returns the number of transactions in a block with given hash. - /// - /// Handler for ETH RPC call: `eth_getBlockTransactionCountByHash` - pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { - node_info!("eth_getBlockTransactionCountByHash"); - let block = self.backend.block_by_hash(hash).await?; - let txs = block.map(|b| match b.transactions() { - BlockTransactions::Full(txs) => U256::from(txs.len()), - BlockTransactions::Hashes(txs) => U256::from(txs.len()), - BlockTransactions::Uncle => U256::from(0), - }); - Ok(txs) + block_number: Option, + ) -> Result { + node_info!("eth_getTransactionCount"); + self.get_transaction_count(address, block_number).await.map(U256::from) } /// Returns the number of transactions in a block with given block number. @@ -864,7 +2107,7 @@ impl EthApi { let block_request = self.block_request(Some(block_number.into())).await?; if let BlockRequest::Pending(txs) = block_request { let block = self.backend.pending_block(txs).await; - return Ok(Some(U256::from(block.transactions.len()))); + return Ok(Some(U256::from(block.block.body.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; let txs = block.map(|b| match b.transactions() { @@ -875,29 +2118,6 @@ impl EthApi { Ok(txs) } - /// Returns the number of uncles in a block with given hash. - /// - /// Handler for ETH RPC call: `eth_getUncleCountByBlockHash` - pub async fn block_uncles_count_by_hash(&self, hash: B256) -> Result { - node_info!("eth_getUncleCountByBlockHash"); - let block = - self.backend.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?; - Ok(U256::from(block.uncles.len())) - } - - /// Returns the number of uncles in a block with given block number. - /// - /// Handler for ETH RPC call: `eth_getUncleCountByBlockNumber` - pub async fn block_uncles_count_by_number(&self, block_number: BlockNumber) -> Result { - node_info!("eth_getUncleCountByBlockNumber"); - let block = self - .backend - .block_by_number(block_number) - .await? - .ok_or(BlockchainError::BlockNotFound)?; - Ok(U256::from(block.uncles.len())) - } - /// Returns the code at given address at given time (block number). /// /// Handler for ETH RPC call: `eth_getCode` @@ -905,12 +2125,11 @@ impl EthApi { node_info!("eth_getCode"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.get_code(address, number).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.get_code(address, number).await?); } self.backend.get_code(address, Some(block_request)).await } @@ -930,88 +2149,33 @@ impl EthApi { // If we're in forking mode, or still on the forked block (no blocks mined yet) then we can // delegate the call. - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork_inclusive(number) { - return Ok(fork.get_proof(address, keys, Some(number.into())).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork_inclusive(number) + { + return Ok(fork.get_proof(address, keys, Some(number.into())).await?); } let proof = self.backend.prove_account_at(address, keys, Some(block_request)).await?; Ok(proof) } - /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). - /// - /// Handler for ETH RPC call: `eth_signTypedData` - pub async fn sign_typed_data( - &self, - _address: Address, - _data: serde_json::Value, - ) -> Result { - node_info!("eth_signTypedData"); - Err(BlockchainError::RpcUnimplemented) - } - - /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). - /// - /// Handler for ETH RPC call: `eth_signTypedData_v3` - pub async fn sign_typed_data_v3( - &self, - _address: Address, - _data: serde_json::Value, - ) -> Result { - node_info!("eth_signTypedData_v3"); - Err(BlockchainError::RpcUnimplemented) - } - - /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. - /// - /// Handler for ETH RPC call: `eth_signTypedData_v4` - pub async fn sign_typed_data_v4(&self, address: Address, data: &TypedData) -> Result { - node_info!("eth_signTypedData_v4"); - let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = signer.sign_typed_data(address, data).await?; - let signature = alloy_primitives::hex::encode(signature.as_bytes()); - Ok(format!("0x{signature}")) - } - - /// The sign method calculates an Ethereum specific signature - /// - /// Handler for ETH RPC call: `eth_sign` - pub async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result { - node_info!("eth_sign"); - let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = - alloy_primitives::hex::encode(signer.sign(address, content.as_ref()).await?.as_bytes()); - Ok(format!("0x{signature}")) - } - /// Signs a transaction /// /// Handler for ETH RPC call: `eth_signTransaction` pub async fn sign_transaction( &self, - mut request: WithOtherFields, + request: WithOtherFields, ) -> Result { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, _) = self.request_nonce(&request, from).await?; - if request.gas.is_none() { - // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await - { - request.gas = Some(gas.to()); - } - } - - let request = self.build_typed_tx_request(request, nonce)?; + let request = self.build_tx_request(request, nonce).await?; let signed_transaction = self.sign_request(&from, request)?.encoded_2718(); Ok(alloy_primitives::hex::encode_prefixed(signed_transaction)) @@ -1022,47 +2186,89 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendTransaction` pub async fn send_transaction( &self, - mut request: WithOtherFields, + request: WithOtherFields, ) -> Result { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; - if request.gas.is_none() { - // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await - { - request.gas = Some(gas.to()); - } - } - - let request = self.build_typed_tx_request(request, nonce)?; + let typed_tx = self.build_tx_request(request, nonce).await?; // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { - let bypass_signature = self.impersonated_signature(&request); - let transaction = sign::build_typed_transaction(request, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; trace!(target : "node", ?from, "eth_sendTransaction: impersonating"); PendingTransaction::with_impersonated(transaction, from) } else { - let transaction = self.sign_request(&from, request)?; + let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? }; // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; - debug_assert!(requires != provides); + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } + /// Waits for a transaction to be included in a block and returns its receipt (no timeout). + async fn await_transaction_inclusion(&self, hash: TxHash) -> Result { + let mut stream = self.new_block_notifications(); + // Check if the transaction is already included before listening for new blocks. + if let Some(receipt) = self.backend.transaction_receipt(hash).await? { + return Ok(receipt); + } + while let Some(notification) = stream.next().await { + if let Some(block) = self.backend.get_block_by_hash(notification.hash) + && block.body.transactions.iter().any(|tx| tx.hash() == hash) + && let Some(receipt) = self.backend.transaction_receipt(hash).await? + { + return Ok(receipt); + } + } + + Err(BlockchainError::Message("Failed to await transaction inclusion".to_string())) + } + + /// Waits for a transaction to be included in a block and returns its receipt, with timeout. + async fn check_transaction_inclusion(&self, hash: TxHash) -> Result { + const TIMEOUT_DURATION: Duration = Duration::from_secs(30); + tokio::time::timeout(TIMEOUT_DURATION, self.await_transaction_inclusion(hash)) + .await + .unwrap_or_else(|_elapsed| { + Err(BlockchainError::TransactionConfirmationTimeout { + hash, + duration: TIMEOUT_DURATION, + }) + }) + } + + /// Sends a transaction and waits for receipt + /// + /// Handler for ETH RPC call: `eth_sendTransactionSync` + pub async fn send_transaction_sync( + &self, + request: WithOtherFields, + ) -> Result { + node_info!("eth_sendTransactionSync"); + let hash = self.send_transaction(request).await?; + + let receipt = self.check_transaction_inclusion(hash).await?; + + Ok(receipt) + } + /// Sends signed transaction, returning its hash. /// /// Handler for ETH RPC call: `eth_sendRawTransaction` @@ -1073,7 +2279,7 @@ impl EthApi { return Err(BlockchainError::EmptyRawTransactionData); } - let transaction = TypedTransaction::decode_2718(&mut data) + let transaction = FoundryTxEnvelope::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; self.ensure_typed_transaction_supported(&transaction)?; @@ -1083,24 +2289,40 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let on_chain_nonce = self.backend.current_nonce(*pending_transaction.sender()).await?; let from = *pending_transaction.sender(); - let nonce = pending_transaction.transaction.nonce(); - let requires = required_marker(nonce, on_chain_nonce, from); - let priority = self.transaction_priority(&pending_transaction.transaction); - let pool_transaction = PoolTransaction { - requires, - provides: vec![to_marker(nonce, *pending_transaction.sender())], - pending_transaction, - priority, + + // Tempo txs use a 2D nonce system — no sequential ordering by account nonce. + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + let on_chain_nonce = self.backend.current_nonce(from).await?; + let nonce = pending_transaction.transaction.nonce(); + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) }; + let pool_transaction = + PoolTransaction { requires, provides, pending_transaction, priority }; + let tx = self.pool.add_transaction(pool_transaction)?; trace!(target: "node", "Added transaction: [{:?}] sender={:?}", tx.hash(), from); Ok(*tx.hash()) } + /// Sends signed transaction, returning its receipt. + /// + /// Handler for ETH RPC call: `eth_sendRawTransactionSync` + pub async fn send_raw_transaction_sync(&self, tx: Bytes) -> Result { + node_info!("eth_sendRawTransactionSync"); + + let hash = self.send_raw_transaction(tx).await?; + let receipt = self.check_transaction_inclusion(hash).await?; + + Ok(receipt) + } + /// Call contract, returning the output data. /// /// Handler for ETH RPC call: `eth_call` @@ -1113,17 +2335,16 @@ impl EthApi { node_info!("eth_call"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - if overrides.has_state() || overrides.has_block() { - return Err(BlockchainError::EvmOverrideError( - "not available on past forked blocks".to_string(), - )); - } - return Ok(fork.call(&request, Some(number.into())).await?) - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( + "not available on past forked blocks".to_string(), + )); } + return Ok(fork.call(&request, Some(number.into())).await?); } let fees = FeeDetails::new( @@ -1153,12 +2374,11 @@ impl EthApi { node_info!("eth_simulateV1"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.simulate_v1(&request, Some(number.into())).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.simulate_v1(&request, Some(number.into())).await?); } // this can be blocking for a bit, especially in forking mode @@ -1193,12 +2413,11 @@ impl EthApi { node_info!("eth_createAccessList"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.create_access_list(&request, Some(number.into())).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.create_access_list(&request, Some(number.into())).await?); } self.backend @@ -1214,12 +2433,8 @@ impl EthApi { // execute again but with access list set request.access_list = Some(access_list.clone()); - let (exit, out, gas_used, _) = self.backend.call_with_state( - &state, - request.clone(), - FeeDetails::zero(), - block_env, - )?; + let (exit, out, gas_used, _) = + self.backend.call_with_state(&state, request, FeeDetails::zero(), block_env)?; ensure_return_ok(exit, &out)?; Ok(AccessListResult { @@ -1251,286 +2466,195 @@ impl EthApi { .map(U256::from) } - /// Get transaction by its hash. - /// - /// This will check the storage for a matching transaction, if no transaction exists in storage - /// this will also scan the mempool for a matching pending transaction - /// - /// Handler for ETH RPC call: `eth_getTransactionByHash` - pub async fn transaction_by_hash(&self, hash: B256) -> Result> { - node_info!("eth_getTransactionByHash"); - let mut tx = self.pool.get_transaction(hash).map(|pending| { - let from = *pending.sender(); - let tx = transaction_build( - Some(*pending.hash()), - pending.transaction, - None, - None, - Some(self.backend.base_fee()), - ); - - let WithOtherFields { inner: mut tx, other } = tx.0; - // we set the from field here explicitly to the set sender of the pending transaction, - // in case the transaction is impersonated. - tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); - - AnyRpcTransaction(WithOtherFields { inner: tx, other }) - }); - if tx.is_none() { - tx = self.backend.transaction_by_hash(hash).await? - } - - Ok(tx) - } - - /// Returns transaction at given block hash and index. + /// Fills a transaction request with default values for missing fields. /// - /// Handler for ETH RPC call: `eth_getTransactionByBlockHashAndIndex` - pub async fn transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> Result> { - node_info!("eth_getTransactionByBlockHashAndIndex"); - self.backend.transaction_by_block_hash_and_index(hash, index).await - } - - /// Returns transaction by given block number and index. + /// This method populates missing transaction fields like nonce, gas limit, + /// chain ID, and fee parameters with appropriate defaults. /// - /// Handler for ETH RPC call: `eth_getTransactionByBlockNumberAndIndex` - pub async fn transaction_by_block_number_and_index( + /// Handler for ETH RPC call: `eth_fillTransaction` + pub async fn fill_transaction( &self, - block: BlockNumber, - idx: Index, - ) -> Result> { - node_info!("eth_getTransactionByBlockNumberAndIndex"); - self.backend.transaction_by_block_number_and_index(block, idx).await - } + mut request: WithOtherFields, + ) -> Result> { + node_info!("eth_fillTransaction"); - /// Returns transaction receipt by transaction hash. - /// - /// Handler for ETH RPC call: `eth_getTransactionReceipt` - pub async fn transaction_receipt(&self, hash: B256) -> Result> { - node_info!("eth_getTransactionReceipt"); - let tx = self.pool.get_transaction(hash); - if tx.is_some() { - return Ok(None); - } - self.backend.transaction_receipt(hash).await - } + let from = match request.as_ref().from() { + Some(from) => from, + None => self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable)?, + }; - /// Returns block receipts by block number. - /// - /// Handler for ETH RPC call: `eth_getBlockReceipts` - pub async fn block_receipts(&self, number: BlockId) -> Result>> { - node_info!("eth_getBlockReceipts"); - self.backend.block_receipts(number).await - } + let nonce = if let Some(nonce) = request.as_ref().nonce() { + nonce + } else { + self.request_nonce(&request, from).await?.0 + }; - /// Returns an uncles at given block and index. - /// - /// Handler for ETH RPC call: `eth_getUncleByBlockHashAndIndex` - pub async fn uncle_by_block_hash_and_index( - &self, - block_hash: B256, - idx: Index, - ) -> Result> { - node_info!("eth_getUncleByBlockHashAndIndex"); - let number = - self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; - if let Some(fork) = self.get_fork() { - if fork.predates_fork_inclusive(number) { - return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?) - } + // Prefill gas limit with estimated gas, bubble up the error if the gas estimation fails + // This is a workaround to avoid the error being swallowed by the `build_tx_request` + // function + if request.as_ref().gas_limit().is_none() { + let estimated_gas = + self.estimate_gas(request.clone(), None, EvmOverrides::default()).await?; + request.as_mut().set_gas_limit(estimated_gas.to()); } - // It's impossible to have uncles outside of fork mode - Ok(None) - } - /// Returns an uncles at given block and index. - /// - /// Handler for ETH RPC call: `eth_getUncleByBlockNumberAndIndex` - pub async fn uncle_by_block_number_and_index( - &self, - block_number: BlockNumber, - idx: Index, - ) -> Result> { - node_info!("eth_getUncleByBlockNumberAndIndex"); - let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; - if let Some(fork) = self.get_fork() { - if fork.predates_fork_inclusive(number) { - return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?) - } - } - // It's impossible to have uncles outside of fork mode - Ok(None) - } + let typed_tx = self.build_tx_request(request, nonce).await?; + let tx = build_impersonated(typed_tx); - /// Returns logs matching given filter object. - /// - /// Handler for ETH RPC call: `eth_getLogs` - pub async fn logs(&self, filter: Filter) -> Result> { - node_info!("eth_getLogs"); - self.backend.logs(filter).await - } + let raw = tx.encoded_2718().into(); - /// Returns the hash of the current block, the seedHash, and the boundary condition to be met. - /// - /// Handler for ETH RPC call: `eth_getWork` - pub fn work(&self) -> Result { - node_info!("eth_getWork"); - Err(BlockchainError::RpcUnimplemented) - } + let mut tx = + transaction_build(None, MaybeImpersonatedTransaction::new(tx), None, None, None); - /// Returns the sync status, always be fails. - /// - /// Handler for ETH RPC call: `eth_syncing` - pub fn syncing(&self) -> Result { - node_info!("eth_syncing"); - Ok(false) + // Set the correct `from` address (overrides the recovered zero address from dummy + // signature) + tx.0.inner.inner = Recovered::new_unchecked(tx.0.inner.inner.into_inner(), from); + + Ok(FillTransaction { raw, tx }) } - /// Used for submitting a proof-of-work solution. - /// - /// Handler for ETH RPC call: `eth_submitWork` - pub fn submit_work(&self, _: B64, _: B256, _: B256) -> Result { - node_info!("eth_submitWork"); - Err(BlockchainError::RpcUnimplemented) + /// Handler for RPC call: `anvil_getBlobsByTransactionHash` + pub fn anvil_get_blob_by_tx_hash(&self, hash: B256) -> Result>> { + node_info!("anvil_getBlobsByTransactionHash"); + Ok(self.backend.get_blob_by_tx_hash(hash)?) } - /// Used for submitting mining hashrate. + /// Get transaction by its hash. /// - /// Handler for ETH RPC call: `eth_submitHashrate` - pub fn submit_hashrate(&self, _: U256, _: B256) -> Result { - node_info!("eth_submitHashrate"); - Err(BlockchainError::RpcUnimplemented) + /// This will check the storage for a matching transaction, if no transaction exists in storage + /// this will also scan the mempool for a matching pending transaction + /// + /// Handler for ETH RPC call: `eth_getTransactionByHash` + pub async fn transaction_by_hash(&self, hash: B256) -> Result> { + node_info!("eth_getTransactionByHash"); + let mut tx = self.pool.get_transaction(hash).map(|pending| { + let from = *pending.sender(); + let tx = transaction_build( + Some(*pending.hash()), + pending.transaction, + None, + None, + Some(self.backend.base_fee()), + ); + + let WithOtherFields { inner: mut tx, other } = tx.0; + // we set the from field here explicitly to the set sender of the pending transaction, + // in case the transaction is impersonated. + tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); + + AnyRpcTransaction(WithOtherFields { inner: tx, other }) + }); + if tx.is_none() { + tx = self.backend.transaction_by_hash(hash).await? + } + + Ok(tx) } - /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. + /// Returns the transaction by sender and nonce. /// - /// Handler for ETH RPC call: `eth_feeHistory` - pub async fn fee_history( + /// This will check the mempool for pending transactions first, then perform a binary search + /// over mined blocks to find the transaction. + /// + /// Handler for ETH RPC call: `eth_getTransactionBySenderAndNonce` + pub async fn transaction_by_sender_and_nonce( &self, - block_count: U256, - newest_block: BlockNumber, - reward_percentiles: Vec, - ) -> Result { - node_info!("eth_feeHistory"); - // max number of blocks in the requested range + sender: Address, + nonce: U256, + ) -> Result> { + node_info!("eth_getTransactionBySenderAndNonce"); - let current = self.backend.best_number(); - let slots_in_an_epoch = 32u64; + // check pending txs first + for pending_tx in self.pool.ready_transactions().chain(self.pool.pending_transactions()) { + if U256::from(pending_tx.pending_transaction.nonce()) == nonce + && *pending_tx.pending_transaction.sender() == sender + { + let tx = transaction_build( + Some(*pending_tx.pending_transaction.hash()), + pending_tx.pending_transaction.transaction.clone(), + None, + None, + Some(self.backend.base_fee()), + ); - let number = match newest_block { - BlockNumber::Latest | BlockNumber::Pending => current, - BlockNumber::Earliest => 0, - BlockNumber::Number(n) => n, - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), - }; + let WithOtherFields { inner: mut tx, other } = tx.0; + // we set the from field here explicitly to the set sender of the pending + // transaction, in case the transaction is impersonated. + let from = *pending_tx.pending_transaction.sender(); + tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); - // check if the number predates the fork, if in fork mode - if let Some(fork) = self.get_fork() { - // if we're still at the forked block we don't have any history and can't compute it - // efficiently, instead we fetch it from the fork - if fork.predates_fork_inclusive(number) { - return fork - .fee_history(block_count.to(), BlockNumber::Number(number), &reward_percentiles) - .await - .map_err(BlockchainError::AlloyForkProvider); + return Ok(Some(AnyRpcTransaction(WithOtherFields { inner: tx, other }))); } } - const MAX_BLOCK_COUNT: u64 = 1024u64; - let block_count = block_count.to::().min(MAX_BLOCK_COUNT); + let highest_nonce = self.transaction_count(sender, None).await?.saturating_to::(); + let target_nonce = nonce.saturating_to::(); - // highest and lowest block num in the requested range - let highest = number; - let lowest = highest.saturating_sub(block_count.saturating_sub(1)); + // if the nonce is higher or equal to the highest nonce, the transaction doesn't exist + if target_nonce >= highest_nonce { + return Ok(None); + } - // only support ranges that are in cache range - if lowest < self.backend.best_number().saturating_sub(self.fee_history_limit) { - return Err(FeeHistoryError::InvalidBlockRange.into()); + // no mined blocks yet + let latest_block = self.backend.best_number(); + if latest_block == 0 { + return Ok(None); } - let mut response = FeeHistory { - oldest_block: lowest, - base_fee_per_gas: Vec::new(), - gas_used_ratio: Vec::new(), - reward: Some(Default::default()), - base_fee_per_blob_gas: Default::default(), - blob_gas_used_ratio: Default::default(), - }; - let mut rewards = Vec::new(); + // binary search for the block containing the transaction + let mut low = 1u64; + let mut high = latest_block; - { - let fee_history = self.fee_history_cache.lock(); + while low <= high { + let mid = low + (high - low) / 2; + let mid_nonce = + self.transaction_count(sender, Some(mid.into())).await?.saturating_to::(); - // iter over the requested block range - for n in lowest..=highest { - // - if let Some(block) = fee_history.get(&n) { - response.base_fee_per_gas.push(block.base_fee); - response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0)); - response.blob_gas_used_ratio.push(block.blob_gas_used_ratio); - response.gas_used_ratio.push(block.gas_used_ratio); + if mid_nonce > target_nonce { + high = mid - 1; + } else { + low = mid + 1; + } + } - // requested percentiles - if !reward_percentiles.is_empty() { - let mut block_rewards = Vec::new(); - let resolution_per_percentile: f64 = 2.0; - for p in &reward_percentiles { - let p = p.clamp(0.0, 100.0); - let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; - let reward = block.rewards.get(index as usize).map_or(0, |r| *r); - block_rewards.push(reward); - } - rewards.push(block_rewards); - } + // search in the target block + let target_block = low; + if target_block <= latest_block + && let Some(txs) = + self.backend.mined_transactions_by_block_number(target_block.into()).await + { + for tx in txs { + if tx.from() == sender && tx.nonce() == target_nonce { + return Ok(Some(tx)); } } } - response.reward = Some(rewards); - - // add the next block's base fee to the response - // The spec states that `base_fee_per_gas` "[..] includes the next block after the - // newest of the returned range, because this value can be derived from the - // newest block" - response.base_fee_per_gas.push(self.backend.fees().base_fee() as u128); - - // Same goes for the `base_fee_per_blob_gas`: - // > [..] includes the next block after the newest of the returned range, because this - // > value can be derived from the newest block. - response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas()); - - Ok(response) + Ok(None) } - /// Introduced in EIP-1159, a Geth-specific and simplified priority fee oracle. - /// Leverages the already existing fee history cache. - /// - /// Returns a suggestion for a gas tip cap for dynamic fee transactions. + /// Returns transaction receipt by transaction hash. /// - /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` - pub fn max_priority_fee_per_gas(&self) -> Result { - node_info!("eth_maxPriorityFeePerGas"); - Ok(U256::from(self.lowest_suggestion_tip())) + /// Handler for ETH RPC call: `eth_getTransactionReceipt` + pub async fn transaction_receipt(&self, hash: B256) -> Result> { + node_info!("eth_getTransactionReceipt"); + self.backend.transaction_receipt(hash).await } - /// Returns the suggested fee cap. + /// Returns block receipts by block number. /// - /// Returns at least [MIN_SUGGESTED_PRIORITY_FEE] - fn lowest_suggestion_tip(&self) -> u128 { - let block_number = self.backend.best_number(); - let latest_cached_block = self.fee_history_cache.lock().get(&block_number).cloned(); + /// Handler for ETH RPC call: `eth_getBlockReceipts` + pub async fn block_receipts(&self, number: BlockId) -> Result>> { + node_info!("eth_getBlockReceipts"); + self.backend.block_receipts(number).await + } - match latest_cached_block { - Some(block) => block.rewards.iter().copied().min(), - None => self.fee_history_cache.lock().values().flat_map(|b| b.rewards.clone()).min(), - } - .map(|fee| fee.max(MIN_SUGGESTED_PRIORITY_FEE)) - .unwrap_or(MIN_SUGGESTED_PRIORITY_FEE) + /// Returns logs matching given filter object. + /// + /// Handler for ETH RPC call: `eth_getLogs` + pub async fn logs(&self, filter: Filter) -> Result> { + node_info!("eth_getLogs"); + self.backend.logs(filter).await } /// Creates a filter object, based on filter options, to notify when the state changes (logs). @@ -1588,7 +2712,7 @@ impl EthApi { if let Some(filter) = self.filters.get_log_filter(id).await { self.backend.logs(filter).await } else { - Ok(Vec::new()) + Err(BlockchainError::FilterNotFound) } } @@ -1648,6 +2772,30 @@ impl EthApi { self.backend.debug_trace_transaction(tx_hash, opts).await } + /// Returns traces for all transactions in a block by hash. + /// + /// Handler for RPC call: `debug_traceBlockByHash` + pub async fn debug_trace_block_by_hash( + &self, + block_hash: B256, + opts: GethDebugTracingOptions, + ) -> Result> { + node_info!("debug_traceBlockByHash"); + self.backend.debug_trace_block_by_hash(block_hash, opts).await + } + + /// Returns traces for all transactions in a block by number. + /// + /// Handler for RPC call: `debug_traceBlockByNumber` + pub async fn debug_trace_block_by_number( + &self, + block_number: BlockNumber, + opts: GethDebugTracingOptions, + ) -> Result> { + node_info!("debug_traceBlockByNumber"); + self.backend.debug_trace_block_by_number(block_number, opts).await + } + /// Returns traces for the transaction for geth's tracing endpoint /// /// Handler for RPC call: `debug_traceCall` @@ -1664,113 +2812,24 @@ impl EthApi { request.max_fee_per_gas, request.max_priority_fee_per_gas, request.max_fee_per_blob_gas, - )? - .or_zero_fees(); - - let result: std::result::Result = - self.backend.call_with_tracing(request, fees, Some(block_request), opts).await; - result - } - - /// Returns traces for the transaction hash via parity's tracing endpoint - /// - /// Handler for RPC call: `trace_transaction` - pub async fn trace_transaction(&self, tx_hash: B256) -> Result> { - node_info!("trace_transaction"); - self.backend.trace_transaction(tx_hash).await - } - - /// Returns traces for the transaction hash via parity's tracing endpoint - /// - /// Handler for RPC call: `trace_block` - pub async fn trace_block(&self, block: BlockNumber) -> Result> { - node_info!("trace_block"); - self.backend.trace_block(block).await - } - - /// Returns filtered traces over blocks - /// - /// Handler for RPC call: `trace_filter` - pub async fn trace_filter( - &self, - filter: TraceFilter, - ) -> Result> { - node_info!("trace_filter"); - self.backend.trace_filter(filter).await - } -} - -// == impl EthApi anvil endpoints == - -impl EthApi { - /// Send transactions impersonating specific account and contract addresses. - /// - /// Handler for ETH RPC call: `anvil_impersonateAccount` - pub async fn anvil_impersonate_account(&self, address: Address) -> Result<()> { - node_info!("anvil_impersonateAccount"); - self.backend.impersonate(address); - Ok(()) - } - - /// Stops impersonating an account if previously set with `anvil_impersonateAccount`. - /// - /// Handler for ETH RPC call: `anvil_stopImpersonatingAccount` - pub async fn anvil_stop_impersonating_account(&self, address: Address) -> Result<()> { - node_info!("anvil_stopImpersonatingAccount"); - self.backend.stop_impersonating(address); - Ok(()) - } - - /// If set to true will make every account impersonated - /// - /// Handler for ETH RPC call: `anvil_autoImpersonateAccount` - pub async fn anvil_auto_impersonate_account(&self, enabled: bool) -> Result<()> { - node_info!("anvil_autoImpersonateAccount"); - self.backend.auto_impersonate_account(enabled); - Ok(()) - } - - /// Returns true if auto mining is enabled, and false. - /// - /// Handler for ETH RPC call: `anvil_getAutomine` - pub fn anvil_get_auto_mine(&self) -> Result { - node_info!("anvil_getAutomine"); - Ok(self.miner.is_auto_mine()) - } + )? + .or_zero_fees(); - /// Returns the value of mining interval, if set. - /// - /// Handler for ETH RPC call: `anvil_getIntervalMining`. - pub fn anvil_get_interval_mining(&self) -> Result> { - node_info!("anvil_getIntervalMining"); - Ok(self.miner.get_interval()) + let result: std::result::Result = + self.backend.call_with_tracing(request, fees, Some(block_request), opts).await; + result } +} - /// Enables or disables, based on the single boolean argument, the automatic mining of new - /// blocks with each new transaction submitted to the network. - /// - /// Handler for ETH RPC call: `evm_setAutomine` - pub async fn anvil_set_auto_mine(&self, enable_automine: bool) -> Result<()> { - node_info!("evm_setAutomine"); - if self.miner.is_auto_mine() { - if enable_automine { - return Ok(()); - } - self.miner.set_mining_mode(MiningMode::None); - } else if enable_automine { - let listener = self.pool.add_ready_listener(); - let mode = MiningMode::instant(1_000, listener); - self.miner.set_mining_mode(mode); - } - Ok(()) - } +// == impl EthApi anvil endpoints == +impl EthApi { /// Mines a series of blocks. /// /// Handler for ETH RPC call: `anvil_mine` pub async fn anvil_mine(&self, num_blocks: Option, interval: Option) -> Result<()> { node_info!("anvil_mine"); - let interval = interval.map(|i| i.to::()); + let interval = interval.map(|i| i.saturating_to::()); let blocks = num_blocks.unwrap_or(U256::from(1)); if blocks.is_zero() { return Ok(()); @@ -1778,7 +2837,7 @@ impl EthApi { self.on_blocking_task(|this| async move { // mine all the blocks - for _ in 0..blocks.to::() { + for _ in 0..blocks.saturating_to::() { // If we have an interval, jump forwards in time to the "next" timestamp if let Some(interval) = interval { this.backend.time().increase_time(interval); @@ -1792,118 +2851,45 @@ impl EthApi { Ok(()) } - /// Sets the mining behavior to interval with the given interval (seconds) - /// - /// Handler for ETH RPC call: `evm_setIntervalMining` - pub fn anvil_set_interval_mining(&self, secs: u64) -> Result<()> { - node_info!("evm_setIntervalMining"); - let mining_mode = if secs == 0 { - MiningMode::None - } else { - let block_time = Duration::from_secs(secs); - - // This ensures that memory limits are stricter in interval-mine mode - self.backend.update_interval_mine_block_time(block_time); - - MiningMode::FixedBlockTime(FixedBlockTimeMiner::new(block_time)) - }; - self.miner.set_mining_mode(mining_mode); - Ok(()) - } - - /// Removes transactions from the pool - /// - /// Handler for RPC call: `anvil_dropTransaction` - pub async fn anvil_drop_transaction(&self, tx_hash: B256) -> Result> { - node_info!("anvil_dropTransaction"); - Ok(self.pool.drop_transaction(tx_hash).map(|tx| tx.hash())) - } - - /// Removes all transactions from the pool - /// - /// Handler for RPC call: `anvil_dropAllTransactions` - pub async fn anvil_drop_all_transactions(&self) -> Result<()> { - node_info!("anvil_dropAllTransactions"); - self.pool.clear(); - Ok(()) - } - - /// Reset the fork to a fresh forked state, and optionally update the fork config. - /// - /// If `forking` is `None` then this will disable forking entirely. - /// - /// Handler for RPC call: `anvil_reset` - pub async fn anvil_reset(&self, forking: Option) -> Result<()> { - node_info!("anvil_reset"); - if let Some(forking) = forking { - // if we're resetting the fork we need to reset the instance id - self.reset_instance_id(); - self.backend.reset_fork(forking).await - } else { - Err(BlockchainError::RpcUnimplemented) - } - } - - pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { - node_info!("anvil_setChainId"); - self.backend.set_chain_id(chain_id); - Ok(()) - } - - /// Modifies the balance of an account. + /// Helper function to find the storage slot for an ERC20 function call by testing slots + /// from an access list until one produces the expected result. /// - /// Handler for RPC call: `anvil_setBalance` - pub async fn anvil_set_balance(&self, address: Address, balance: U256) -> Result<()> { - node_info!("anvil_setBalance"); - self.backend.set_balance(address, balance).await?; - Ok(()) - } - - /// Increases the balance of an account. + /// Rather than trying to reverse-engineer the storage layout, this function uses a + /// "trial and error" approach: try overriding each slot that the function accesses, + /// and see which one actually affects the function's return value. /// - /// Handler for RPC call: `anvil_addBalance` - pub async fn anvil_add_balance(&self, address: Address, balance: U256) -> Result<()> { - node_info!("anvil_addBalance"); - let current_balance = self.backend.get_balance(address, None).await?; - self.backend.set_balance(address, current_balance + balance).await?; - Ok(()) - } - - /// Deals ERC20 tokens to a address + /// ## Parameters + /// - `token_address`: The ERC20 token contract address + /// - `calldata`: The encoded function call (e.g., `balanceOf(user)` or `allowance(owner, + /// spender)`) + /// - `expected_value`: The value we want to set (balance or allowance amount) /// - /// Handler for RPC call: `anvil_dealERC20` - pub async fn anvil_deal_erc20( + /// ## Returns + /// The storage slot (B256) that contains the target ERC20 data, or an error if no slot is + /// found. + async fn find_erc20_storage_slot( &self, - address: Address, token_address: Address, - balance: U256, - ) -> Result<()> { - node_info!("anvil_dealERC20"); - - sol! { - #[sol(rpc)] - contract IERC20 { - function balanceOf(address target) external view returns (uint256); - } - } - - let calldata = IERC20::balanceOfCall { target: address }.abi_encode(); + calldata: Bytes, + expected_value: U256, + ) -> Result { let tx = TransactionRequest::default().with_to(token_address).with_input(calldata.clone()); - // first collect all the slots that are used by the balanceOf call + // first collect all the slots that are used by the function call let access_list_result = self.create_access_list(WithOtherFields::new(tx.clone()), None).await?; let access_list = access_list_result.access_list; - // now we can iterate over all the accessed slots and try to find the one that contains the - // balance by overriding the slot and checking the `balanceOfCall` of + // iterate over all the accessed slots and try to find the one that contains the + // target value by overriding the slot and checking the function call result for item in access_list.0 { if item.address != token_address { continue; }; for slot in &item.storage_keys { - let account_override = AccountOverride::default() - .with_state_diff(std::iter::once((*slot, B256::from(balance.to_be_bytes())))); + let account_override = AccountOverride::default().with_state_diff(std::iter::once( + (*slot, B256::from(expected_value.to_be_bytes())), + )); let state_override = StateOverridesBuilder::default() .append(token_address, account_override) @@ -1918,201 +2904,92 @@ impl EthApi { continue; }; - let Ok(result_balance) = U256::abi_decode(&result) else { + let Ok(result_value) = U256::abi_decode(&result) else { // response returned something other than a U256 continue; }; - if result_balance == balance { - self.anvil_set_storage_at( - token_address, - U256::from_be_bytes(slot.0), - B256::from(balance.to_be_bytes()), - ) - .await?; - return Ok(()); + if result_value == expected_value { + return Ok(*slot); } } } - // unable to set the balance - Err(BlockchainError::Message("Unable to set ERC20 balance, no slot found".to_string())) - } - - /// Sets the code of a contract. - /// - /// Handler for RPC call: `anvil_setCode` - pub async fn anvil_set_code(&self, address: Address, code: Bytes) -> Result<()> { - node_info!("anvil_setCode"); - self.backend.set_code(address, code).await?; - Ok(()) - } - - /// Sets the nonce of an address. - /// - /// Handler for RPC call: `anvil_setNonce` - pub async fn anvil_set_nonce(&self, address: Address, nonce: U256) -> Result<()> { - node_info!("anvil_setNonce"); - self.backend.set_nonce(address, nonce).await?; - Ok(()) + Err(BlockchainError::Message("Unable to find storage slot".to_string())) } - /// Writes a single slot of the account's storage. + /// Deals ERC20 tokens to a address /// - /// Handler for RPC call: `anvil_setStorageAt` - pub async fn anvil_set_storage_at( + /// Handler for RPC call: `anvil_dealERC20` + pub async fn anvil_deal_erc20( &self, address: Address, - slot: U256, - val: B256, - ) -> Result { - node_info!("anvil_setStorageAt"); - self.backend.set_storage_at(address, slot, val).await?; - Ok(true) - } - - /// Enable or disable logging. - /// - /// Handler for RPC call: `anvil_setLoggingEnabled` - pub async fn anvil_set_logging(&self, enable: bool) -> Result<()> { - node_info!("anvil_setLoggingEnabled"); - self.logger.set_enabled(enable); - Ok(()) - } - - /// Set the minimum gas price for the node. - /// - /// Handler for RPC call: `anvil_setMinGasPrice` - pub async fn anvil_set_min_gas_price(&self, gas: U256) -> Result<()> { - node_info!("anvil_setMinGasPrice"); - if self.backend.is_eip1559() { - return Err(RpcError::invalid_params( - "anvil_setMinGasPrice is not supported when EIP-1559 is active", - ) - .into()); - } - self.backend.set_gas_price(gas.to()); - Ok(()) - } - - /// Sets the base fee of the next block. - /// - /// Handler for RPC call: `anvil_setNextBlockBaseFeePerGas` - pub async fn anvil_set_next_block_base_fee_per_gas(&self, basefee: U256) -> Result<()> { - node_info!("anvil_setNextBlockBaseFeePerGas"); - if !self.backend.is_eip1559() { - return Err(RpcError::invalid_params( - "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", - ) - .into()); - } - self.backend.set_base_fee(basefee.to()); - Ok(()) - } - - /// Sets the coinbase address. - /// - /// Handler for RPC call: `anvil_setCoinbase` - pub async fn anvil_set_coinbase(&self, address: Address) -> Result<()> { - node_info!("anvil_setCoinbase"); - self.backend.set_coinbase(address); - Ok(()) - } - - /// Create a buffer that represents all state on the chain, which can be loaded to separate - /// process by calling `anvil_loadState` - /// - /// Handler for RPC call: `anvil_dumpState` - pub async fn anvil_dump_state( - &self, - preserve_historical_states: Option, - ) -> Result { - node_info!("anvil_dumpState"); - self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await - } - - /// Returns the current state - pub async fn serialized_state( - &self, - preserve_historical_states: bool, - ) -> Result { - self.backend.serialized_state(preserve_historical_states).await - } - - /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or - /// storage. - /// - /// Handler for RPC call: `anvil_loadState` - pub async fn anvil_load_state(&self, buf: Bytes) -> Result { - node_info!("anvil_loadState"); - self.backend.load_state_bytes(buf).await - } - - /// Retrieves the Anvil node configuration params. - /// - /// Handler for RPC call: `anvil_nodeInfo` - pub async fn anvil_node_info(&self) -> Result { - node_info!("anvil_nodeInfo"); - - let env = self.backend.env().read(); - let fork_config = self.backend.get_fork(); - let tx_order = self.transaction_order.read(); - let hard_fork: &str = env.evm_env.cfg_env.spec.into(); + token_address: Address, + balance: U256, + ) -> Result<()> { + node_info!("anvil_dealERC20"); - Ok(NodeInfo { - current_block_number: self.backend.best_number(), - current_block_timestamp: env.evm_env.block_env.timestamp, - current_block_hash: self.backend.best_hash(), - hard_fork: hard_fork.to_string(), - transaction_order: match *tx_order { - TransactionOrder::Fifo => "fifo".to_string(), - TransactionOrder::Fees => "fees".to_string(), - }, - environment: NodeEnvironment { - base_fee: self.backend.base_fee() as u128, - chain_id: self.backend.chain_id().to::(), - gas_limit: self.backend.gas_limit(), - gas_price: self.gas_price(), - }, - fork_config: fork_config - .map(|fork| { - let config = fork.config.read(); + sol! { + #[sol(rpc)] + contract IERC20 { + function balanceOf(address target) external view returns (uint256); + } + } - NodeForkConfig { - fork_url: Some(config.eth_rpc_url.clone()), - fork_block_number: Some(config.block_number), - fork_retry_backoff: Some(config.backoff.as_millis()), - } - }) - .unwrap_or_default(), - }) + let calldata = IERC20::balanceOfCall { target: address }.abi_encode().into(); + + // Find the storage slot that contains the balance + let slot = + self.find_erc20_storage_slot(token_address, calldata, balance).await.map_err(|_| { + BlockchainError::Message("Unable to set ERC20 balance, no slot found".to_string()) + })?; + + // Set the storage slot to the desired balance + self.anvil_set_storage_at( + token_address, + U256::from_be_bytes(slot.0), + B256::from(balance.to_be_bytes()), + ) + .await?; + + Ok(()) } - /// Retrieves metadata about the Anvil instance. + /// Sets the ERC20 allowance for a spender /// - /// Handler for RPC call: `anvil_metadata` - pub async fn anvil_metadata(&self) -> Result { - node_info!("anvil_metadata"); - let fork_config = self.backend.get_fork(); + /// Handler for RPC call: `anvil_set_erc20_allowance` + pub async fn anvil_set_erc20_allowance( + &self, + owner: Address, + spender: Address, + token_address: Address, + amount: U256, + ) -> Result<()> { + node_info!("anvil_setERC20Allowance"); - Ok(Metadata { - client_version: CLIENT_VERSION.to_string(), - chain_id: self.backend.chain_id().to::(), - latest_block_hash: self.backend.best_hash(), - latest_block_number: self.backend.best_number(), - instance_id: *self.instance_id.read(), - forked_network: fork_config.map(|cfg| ForkedNetwork { - chain_id: cfg.chain_id(), - fork_block_number: cfg.block_number(), - fork_block_hash: cfg.block_hash(), - }), - snapshots: self.backend.list_state_snapshots(), - }) - } + sol! { + #[sol(rpc)] + contract IERC20 { + function allowance(address owner, address spender) external view returns (uint256); + } + } + + let calldata = IERC20::allowanceCall { owner, spender }.abi_encode().into(); + + // Find the storage slot that contains the allowance + let slot = + self.find_erc20_storage_slot(token_address, calldata, amount).await.map_err(|_| { + BlockchainError::Message("Unable to set ERC20 allowance, no slot found".to_string()) + })?; + + // Set the storage slot to the desired allowance + self.anvil_set_storage_at( + token_address, + U256::from_be_bytes(slot.0), + B256::from(amount.to_be_bytes()), + ) + .await?; - pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> { - node_info!("anvil_removePoolTransactions"); - self.pool.remove_transactions_by_address(address); Ok(()) } @@ -2157,7 +3034,7 @@ impl EthApi { if let Some((_, num)) = pairs.iter().find(|(_, num)| *num >= depth) { return Err(BlockchainError::RpcError(RpcError::invalid_params(format!( "Block number for reorg tx will exceed the reorged chain height. Block number {num} must not exceed (depth-1) {}", - depth-1 + depth - 1 )))); } @@ -2168,24 +3045,24 @@ impl EthApi { // address -> cumulative nonce let mut nonces: HashMap = HashMap::default(); - let mut txs: HashMap>> = HashMap::default(); + let mut txs: HashMap>>> = + HashMap::default(); for pair in pairs { let (tx_data, block_index) = pair; let pending = match tx_data { TransactionData::Raw(bytes) => { let mut data = bytes.as_ref(); - let decoded = TypedTransaction::decode_2718(&mut data) + let decoded = FoundryTxEnvelope::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; PendingTransaction::new(decoded)? } - TransactionData::JSON(req) => { - let mut tx_req = WithOtherFields::new(req); - let from = tx_req.from.map(Ok).unwrap_or_else(|| { + TransactionData::JSON(request) => { + let from = request.from.map(Ok).unwrap_or_else(|| { self.accounts()? .first() - .cloned() + .copied() .ok_or(BlockchainError::NoSignerAvailable) })?; @@ -2193,36 +3070,24 @@ impl EthApi { let curr_nonce = nonces.entry(from).or_insert( self.get_transaction_count( from, - Some(common_block.header.number.into()), + Some(common_block.header.number().into()), ) .await?, ); - // Estimate gas - if tx_req.gas.is_none() { - if let Ok(gas) = self - .estimate_gas(tx_req.clone(), None, EvmOverrides::default()) - .await - { - tx_req.gas = Some(gas.to()); - } - } - // Build typed transaction request - let typed = self.build_typed_tx_request(tx_req, *curr_nonce)?; + let typed_tx = self.build_tx_request(request.into(), *curr_nonce).await?; // Increment nonce *curr_nonce += 1; // Handle signer and convert to pending transaction if self.is_impersonated(from) { - let bypass_signature = self.impersonated_signature(&typed); - let transaction = - sign::build_typed_transaction(typed, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::with_impersonated(transaction, from) } else { - let transaction = self.sign_request(&from, typed)?; + let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? } @@ -2240,109 +3105,6 @@ impl EthApi { Ok(()) } - /// Rollback the chain to a specific depth. - /// - /// e.g depth = 3 - /// A -> B -> C -> D -> E - /// A -> B - /// - /// Depth specifies the height to rollback the chain back to. Depth must not exceed the current - /// chain height, i.e. can't rollback past the genesis block. - /// - /// Handler for RPC call: `anvil_rollback` - pub async fn anvil_rollback(&self, depth: Option) -> Result<()> { - node_info!("anvil_rollback"); - let depth = depth.unwrap_or(1); - - // Check reorg depth doesn't exceed current chain height - let current_height = self.backend.best_number(); - let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( - RpcError::invalid_params(format!( - "Rollback depth must not exceed current chain height: current height {current_height}, depth {depth}" - )), - ))?; - - // Get the common ancestor block - let common_block = - self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; - - self.backend.rollback(common_block).await?; - Ok(()) - } - - /// Snapshot the state of the blockchain at the current block. - /// - /// Handler for RPC call: `evm_snapshot` - pub async fn evm_snapshot(&self) -> Result { - node_info!("evm_snapshot"); - Ok(self.backend.create_state_snapshot().await) - } - - /// Revert the state of the blockchain to a previous snapshot. - /// Takes a single parameter, which is the snapshot id to revert to. - /// - /// Handler for RPC call: `evm_revert` - pub async fn evm_revert(&self, id: U256) -> Result { - node_info!("evm_revert"); - self.backend.revert_state_snapshot(id).await - } - - /// Jump forward in time by the given amount of time, in seconds. - /// - /// Handler for RPC call: `evm_increaseTime` - pub async fn evm_increase_time(&self, seconds: U256) -> Result { - node_info!("evm_increaseTime"); - Ok(self.backend.time().increase_time(seconds.try_into().unwrap_or(u64::MAX)) as i64) - } - - /// Similar to `evm_increaseTime` but takes the exact timestamp that you want in the next block - /// - /// Handler for RPC call: `evm_setNextBlockTimestamp` - pub fn evm_set_next_block_timestamp(&self, seconds: u64) -> Result<()> { - node_info!("evm_setNextBlockTimestamp"); - self.backend.time().set_next_block_timestamp(seconds) - } - - /// Sets the specific timestamp and returns the number of seconds between the given timestamp - /// and the current time. - /// - /// Handler for RPC call: `evm_setTime` - pub fn evm_set_time(&self, timestamp: u64) -> Result { - node_info!("evm_setTime"); - let now = self.backend.time().current_call_timestamp(); - self.backend.time().reset(timestamp); - - // number of seconds between the given timestamp and the current time. - let offset = timestamp.saturating_sub(now); - Ok(Duration::from_millis(offset).as_secs()) - } - - /// Set the next block gas limit - /// - /// Handler for RPC call: `evm_setBlockGasLimit` - pub fn evm_set_block_gas_limit(&self, gas_limit: U256) -> Result { - node_info!("evm_setBlockGasLimit"); - self.backend.set_gas_limit(gas_limit.to()); - Ok(true) - } - - /// Sets an interval for the block timestamp - /// - /// Handler for RPC call: `anvil_setBlockTimestampInterval` - pub fn evm_set_block_timestamp_interval(&self, seconds: u64) -> Result<()> { - node_info!("anvil_setBlockTimestampInterval"); - self.backend.time().set_block_timestamp_interval(seconds); - Ok(()) - } - - /// Sets an interval for the block timestamp - /// - /// Handler for RPC call: `anvil_removeBlockTimestampInterval` - pub fn evm_remove_block_timestamp_interval(&self) -> Result { - node_info!("anvil_removeBlockTimestampInterval"); - Ok(self.backend.time().remove_block_timestamp_interval()) - } - /// Mine blocks, instantly. /// /// Handler for RPC call: `evm_mine` @@ -2384,34 +3146,25 @@ impl EthApi { BlockTransactions::Hashes(_) | BlockTransactions::Uncle => unreachable!(), }; for tx in block_txs.iter_mut() { - if let Some(receipt) = self.backend.mined_transaction_receipt(tx.tx_hash()) { - if let Some(output) = receipt.out { - // insert revert reason if failure - if !receipt - .inner - .inner - .as_receipt_with_bloom() - .receipt - .status - .coerce_status() - { - if let Some(reason) = - RevertDecoder::new().maybe_decode(&output, None) - { - tx.other.insert( - "revertReason".to_string(), - serde_json::to_value(reason).expect("Infallible"), - ); - } - } + if let Some(receipt) = self.backend.mined_transaction_receipt(tx.tx_hash()) + && let Some(output) = receipt.out + { + // insert revert reason if failure + if !receipt.inner.as_ref().status() + && let Some(reason) = RevertDecoder::new().maybe_decode(&output, None) + { tx.other.insert( - "output".to_string(), - serde_json::to_value(output).expect("Infallible"), + "revertReason".to_string(), + serde_json::to_value(reason).expect("Infallible"), ); } + tx.other.insert( + "output".to_string(), + serde_json::to_value(output).expect("Infallible"), + ); } } - block.transactions = BlockTransactions::Full(block_txs.to_vec()); + block.transactions = BlockTransactions::Full(block_txs.clone()); blocks.push(block); } } @@ -2419,49 +3172,6 @@ impl EthApi { Ok(blocks) } - /// Sets the reported block number - /// - /// Handler for ETH RPC call: `anvil_setBlock` - pub fn anvil_set_block(&self, block_number: u64) -> Result<()> { - node_info!("anvil_setBlock"); - self.backend.set_block_number(block_number); - Ok(()) - } - - /// Sets the backend rpc url - /// - /// Handler for ETH RPC call: `anvil_setRpcUrl` - pub fn anvil_set_rpc_url(&self, url: String) -> Result<()> { - node_info!("anvil_setRpcUrl"); - if let Some(fork) = self.backend.get_fork() { - let mut config = fork.config.write(); - // let interval = config.provider.get_interval(); - let new_provider = Arc::new( - ProviderBuilder::new(&url).max_retry(10).initial_backoff(1000).build().map_err( - |_| { - TransportErrorKind::custom_str( - format!("Failed to parse invalid url {url}").as_str(), - ) - }, - // TODO: Add interval - )?, // .interval(interval), - ); - config.provider = new_provider; - trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); - config.eth_rpc_url = url; - } - Ok(()) - } - - /// Turn on call traces for transactions that are returned to the user when they execute a - /// transaction (instead of just txhash/receipt) - /// - /// Handler for ETH RPC call: `anvil_enableTraces` - pub async fn anvil_enable_traces(&self) -> Result<()> { - node_info!("anvil_enableTraces"); - Err(BlockchainError::RpcUnimplemented) - } - /// Execute a transaction regardless of signature status /// /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` @@ -2475,10 +3185,9 @@ impl EthApi { let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; - let request = self.build_typed_tx_request(request, nonce)?; + let typed_tx = self.build_tx_request(request, nonce).await?; - let bypass_signature = self.impersonated_signature(&request); - let transaction = sign::build_typed_transaction(request, bypass_signature)?; + let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; @@ -2487,22 +3196,17 @@ impl EthApi { // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; - let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce, from)]; + let (requires, provides) = if let Some((requires, provides)) = + tempo_parallel_nonce_markers(&pending_transaction) + { + (requires, provides) + } else { + (required_marker(nonce, on_chain_nonce, from), vec![to_marker(nonce, from)]) + }; self.add_pending_transaction(pending_transaction, requires, provides) } - /// Returns the number of transactions currently pending for inclusion in the next block(s), as - /// well as the ones that are being scheduled for future execution only. - /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) - /// - /// Handler for ETH RPC call: `txpool_status` - pub async fn txpool_status(&self) -> Result { - node_info!("txpool_status"); - Ok(self.pool.txpool_status()) - } - /// Returns a summary of all the transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// @@ -2513,10 +3217,10 @@ impl EthApi { node_info!("txpool_inspect"); let mut inspect = TxpoolInspect::default(); - fn convert(tx: Arc) -> TxpoolInspectSummary { + fn convert(tx: Arc>) -> TxpoolInspectSummary { let tx = &tx.pending_transaction.transaction; let to = tx.to(); - let gas_price = tx.gas_price(); + let gas_price = tx.max_fee_per_gas(); let value = tx.value(); let gas = tx.gas_limit(); TxpoolInspectSummary { to, value, gas, gas_price } @@ -2534,7 +3238,7 @@ impl EthApi { entry.insert(key, convert(pending)); } for queued in self.pool.pending_transactions() { - let entry = inspect.pending.entry(*queued.pending_transaction.sender()).or_default(); + let entry = inspect.queued.entry(*queued.pending_transaction.sender()).or_default(); let key = queued.pending_transaction.nonce().to_string(); entry.insert(key, convert(queued)); } @@ -2550,7 +3254,7 @@ impl EthApi { pub async fn txpool_content(&self) -> Result> { node_info!("txpool_content"); let mut content = TxpoolContent::::default(); - fn convert(tx: Arc) -> Result { + fn convert(tx: Arc>) -> Result { let from = *tx.pending_transaction.sender(); let tx = transaction_build( Some(tx.hash()), @@ -2576,168 +3280,17 @@ impl EthApi { let key = pending.pending_transaction.nonce().to_string(); entry.insert(key, convert(pending)?); } - for queued in self.pool.pending_transactions() { - let entry = content.pending.entry(*queued.pending_transaction.sender()).or_default(); - let key = queued.pending_transaction.nonce().to_string(); - entry.insert(key, convert(queued)?); - } - - Ok(content) - } -} - -// ===== impl Wallet endppoints ===== -impl EthApi { - /// Get the capabilities of the wallet. - /// - /// See also [EIP-5792][eip-5792]. - /// - /// [eip-5792]: https://eips.ethereum.org/EIPS/eip-5792 - pub fn get_capabilities(&self) -> Result { - node_info!("wallet_getCapabilities"); - Ok(self.backend.get_capabilities()) - } - - pub async fn wallet_send_transaction( - &self, - mut request: WithOtherFields, - ) -> Result { - node_info!("wallet_sendTransaction"); - - // Validate the request - // reject transactions that have a non-zero value to prevent draining the executor. - if request.value.is_some_and(|val| val > U256::ZERO) { - return Err(WalletError::ValueNotZero.into()) - } - - // reject transactions that have from set, as this will be the executor. - if request.from.is_some() { - return Err(WalletError::FromSet.into()); - } - - // reject transaction requests that have nonce set, as this is managed by the executor. - if request.nonce.is_some() { - return Err(WalletError::NonceSet.into()); - } - - let capabilities = self.backend.get_capabilities(); - let valid_delegations: &[Address] = capabilities - .get(self.chain_id()) - .map(|caps| caps.delegation.addresses.as_ref()) - .unwrap_or_default(); - - if let Some(authorizations) = &request.authorization_list { - if authorizations.iter().any(|auth| !valid_delegations.contains(&auth.address)) { - return Err(WalletError::InvalidAuthorization.into()); - } - } - - // validate the destination address - match (request.authorization_list.is_some(), request.to) { - // if this is an eip-1559 tx, ensure that it is an account that delegates to a - // whitelisted address - (false, Some(TxKind::Call(addr))) => { - let acc = self.backend.get_account(addr).await?; - - let delegated_address = acc - .code - .map(|code| match code { - Bytecode::Eip7702(c) => c.address(), - _ => Address::ZERO, - }) - .unwrap_or_default(); - - // not a whitelisted address, or not an eip-7702 bytecode - if delegated_address == Address::ZERO || - !valid_delegations.contains(&delegated_address) - { - return Err(WalletError::IllegalDestination.into()); - } - } - // if it's an eip-7702 tx, let it through - (true, _) => (), - // create tx's disallowed - _ => return Err(WalletError::IllegalDestination.into()), - } - - let wallet = self.backend.executor_wallet().ok_or(WalletError::InternalError)?; - - let from = NetworkWallet::::default_signer_address(&wallet); - - let nonce = self.get_transaction_count(from, Some(BlockId::latest())).await?; - - request.nonce = Some(nonce); - - let chain_id = self.chain_id(); - - request.chain_id = Some(chain_id); - - request.from = Some(from); - - let gas_limit_fut = - self.estimate_gas(request.clone(), Some(BlockId::latest()), EvmOverrides::default()); - - let fees_fut = self.fee_history( - U256::from(EIP1559_FEE_ESTIMATION_PAST_BLOCKS), - BlockNumber::Latest, - vec![EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], - ); - - let (gas_limit, fees) = tokio::join!(gas_limit_fut, fees_fut); - - let gas_limit = gas_limit?; - let fees = fees?; - - request.gas = Some(gas_limit.to()); - - let base_fee = fees.latest_block_base_fee().unwrap_or_default(); - - let estimation = eip1559_default_estimator(base_fee, &fees.reward.unwrap_or_default()); - - request.max_fee_per_gas = Some(estimation.max_fee_per_gas); - request.max_priority_fee_per_gas = Some(estimation.max_priority_fee_per_gas); - request.gas_price = None; - - let envelope = request.build(&wallet).await.map_err(|_| WalletError::InternalError)?; - - self.send_raw_transaction(envelope.encoded_2718().into()).await - } - - /// Add an address to the delegation capability of wallet. - /// - /// This entails that the executor will now be able to sponsor transactions to this address. - pub fn anvil_add_capability(&self, address: Address) -> Result<()> { - node_info!("anvil_addCapability"); - self.backend.add_capability(address); - Ok(()) - } + for queued in self.pool.pending_transactions() { + let entry = content.queued.entry(*queued.pending_transaction.sender()).or_default(); + let key = queued.pending_transaction.nonce().to_string(); + entry.insert(key, convert(queued)?); + } - pub fn anvil_set_executor(&self, executor_pk: String) -> Result
{ - node_info!("anvil_setExecutor"); - self.backend.set_executor(executor_pk) + Ok(content) } } -impl EthApi { - /// Executes the future on a new blocking task. - async fn on_blocking_task(&self, c: C) -> Result - where - C: FnOnce(Self) -> F, - F: Future> + Send + 'static, - R: Send + 'static, - { - let (tx, rx) = oneshot::channel(); - let this = self.clone(); - let f = c(this); - tokio::task::spawn_blocking(move || { - tokio::runtime::Handle::current().block_on(async move { - let res = f.await; - let _ = tx.send(res); - }) - }); - rx.await.map_err(|_| BlockchainError::Internal("blocking task panicked".to_string()))? - } - +impl EthApi { /// Executes the `evm_mine` and returns the number of blocks mined async fn do_evm_mine(&self, opts: Option) -> Result { let mut blocks_to_mine = 1u64; @@ -2780,17 +3333,16 @@ impl EthApi { ) -> Result { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - if overrides.has_state() || overrides.has_block() { - return Err(BlockchainError::EvmOverrideError( - "not available on past forked blocks".to_string(), - )); - } - return Ok(fork.estimate_gas(&request, Some(number.into())).await?) - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( + "not available on past forked blocks".to_string(), + )); } + return Ok(fork.estimate_gas(&request, Some(number.into())).await?); } // this can be blocking for a bit, especially in forking mode @@ -2800,197 +3352,26 @@ impl EthApi { .with_database_at(Some(block_request), |state, mut block| { let mut cache_db = CacheDB::new(state); if let Some(state_overrides) = overrides.state { - state::apply_state_overrides( + apply_state_overrides( state_overrides.into_iter().collect(), &mut cache_db, )?; } if let Some(block_overrides) = overrides.block { - state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); + cache_db.apply_block_overrides(*block_overrides, &mut block); } - this.do_estimate_gas_with_state(request, cache_db.as_dyn(), block) + this.do_estimate_gas_with_state(request, &cache_db, block) }) .await? }) .await } - /// Estimates the gas usage of the `request` with the state. - /// - /// This will execute the transaction request and find the best gas limit via binary search. - fn do_estimate_gas_with_state( - &self, - mut request: WithOtherFields, - state: &dyn DatabaseRef, - block_env: BlockEnv, - ) -> Result { - // If the request is a simple native token transfer we can optimize - // We assume it's a transfer if we have no input data. - let to = request.to.as_ref().and_then(TxKind::to); - - // check certain fields to see if the request could be a simple transfer - let maybe_transfer = request.input.input().is_none() && - request.access_list.is_none() && - request.blob_versioned_hashes.is_none(); - - if maybe_transfer { - if let Some(to) = to { - if let Ok(target_code) = self.backend.get_code_with_state(&state, *to) { - if target_code.as_ref().is_empty() { - return Ok(MIN_TRANSACTION_GAS); - } - } - } - } - - let fees = FeeDetails::new( - request.gas_price, - request.max_fee_per_gas, - request.max_priority_fee_per_gas, - request.max_fee_per_blob_gas, - )? - .or_zero_fees(); - - // get the highest possible gas limit, either the request's set value or the currently - // configured gas limit - let mut highest_gas_limit = request.gas.map_or(block_env.gas_limit.into(), |g| g as u128); - - let gas_price = fees.gas_price.unwrap_or_default(); - // If we have non-zero gas price, cap gas limit by sender balance - if gas_price > 0 { - if let Some(from) = request.from { - let mut available_funds = self.backend.get_balance_with_state(state, from)?; - if let Some(value) = request.value { - if value > available_funds { - return Err(InvalidTransactionError::InsufficientFunds.into()); - } - // safe: value < available_funds - available_funds -= value; - } - // amount of gas the sender can afford with the `gas_price` - let allowance = - available_funds.checked_div(U256::from(gas_price)).unwrap_or_default(); - highest_gas_limit = std::cmp::min(highest_gas_limit, allowance.saturating_to()); - } - } - - let mut call_to_estimate = request.clone(); - call_to_estimate.gas = Some(highest_gas_limit as u64); - - // execute the call without writing to db - let ethres = - self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); - - let gas_used = match ethres.try_into()? { - GasEstimationCallResult::Success(gas) => Ok(gas), - GasEstimationCallResult::OutOfGas => { - Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) - } - GasEstimationCallResult::Revert(output) => { - Err(InvalidTransactionError::Revert(output).into()) - } - GasEstimationCallResult::EvmError(err) => { - warn!(target: "node", "estimation failed due to {:?}", err); - Err(BlockchainError::EvmError(err)) - } - }?; - - // at this point we know the call succeeded but want to find the _best_ (lowest) gas the - // transaction succeeds with. we find this by doing a binary search over the - // possible range NOTE: this is the gas the transaction used, which is less than the - // transaction requires to succeed - - // Get the starting lowest gas needed depending on the transaction kind. - let mut lowest_gas_limit = determine_base_gas_by_kind(&request); - - // pick a point that's close to the estimated gas - let mut mid_gas_limit = - std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); - - // Binary search for the ideal gas limit - while (highest_gas_limit - lowest_gas_limit) > 1 { - request.gas = Some(mid_gas_limit as u64); - let ethres = self.backend.call_with_state( - &state, - request.clone(), - fees.clone(), - block_env.clone(), - ); - - match ethres.try_into()? { - GasEstimationCallResult::Success(_) => { - // If the transaction succeeded, we can set a ceiling for the highest gas limit - // at the current midpoint, as spending any more gas would - // make no sense (as the TX would still succeed). - highest_gas_limit = mid_gas_limit; - } - GasEstimationCallResult::OutOfGas | - GasEstimationCallResult::Revert(_) | - GasEstimationCallResult::EvmError(_) => { - // If the transaction failed, we can set a floor for the lowest gas limit at the - // current midpoint, as spending any less gas would make no - // sense (as the TX would still revert due to lack of gas). - // - // We don't care about the reason here, as we known that transaction is correct - // as it succeeded earlier - lowest_gas_limit = mid_gas_limit; - } - }; - // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - } - - trace!(target : "node", "Estimated Gas for call {:?}", highest_gas_limit); - - Ok(highest_gas_limit) - } - - /// Updates the `TransactionOrder` - pub fn set_transaction_order(&self, order: TransactionOrder) { - *self.transaction_order.write() = order; - } - /// Returns the priority of the transaction based on the current `TransactionOrder` - fn transaction_priority(&self, tx: &TypedTransaction) -> TransactionPriority { + fn transaction_priority(&self, tx: &FoundryTxEnvelope) -> TransactionPriority { self.transaction_order.read().priority(tx) } - /// Returns the chain ID used for transaction - pub fn chain_id(&self) -> u64 { - self.backend.chain_id().to::() - } - - /// Returns the configured fork, if any. - pub fn get_fork(&self) -> Option { - self.backend.get_fork() - } - - /// Returns the current instance's ID. - pub fn instance_id(&self) -> B256 { - *self.instance_id.read() - } - - /// Resets the instance ID. - pub fn reset_instance_id(&self) { - *self.instance_id.write() = B256::random(); - } - - /// Returns the first signer that can sign for the given address - #[expect(clippy::borrowed_box)] - pub fn get_signer(&self, address: Address) -> Option<&Box> { - self.signers.iter().find(|signer| signer.is_signer_for(address)) - } - - /// Returns a new block event stream that yields Notifications when a new block was added - pub fn new_block_notifications(&self) -> NewBlockNotifications { - self.backend.new_block_notifications() - } - - /// Returns a new listeners for ready transactions - pub fn new_ready_transactions(&self) -> Receiver { - self.pool.add_ready_listener() - } - /// Returns a listener for pending transactions, yielding full transactions pub fn full_pending_transactions(&self) -> UnboundedReceiver { let (tx, rx) = unbounded_channel(); @@ -3000,10 +3381,10 @@ impl EthApi { tokio::spawn(async move { while let Some(hash) = hashes.next().await { - if let Ok(Some(txn)) = this.transaction_by_hash(hash).await { - if tx.send(txn).is_err() { - break; - } + if let Ok(Some(txn)) = this.transaction_by_hash(hash).await + && tx.send(txn).is_err() + { + break; } } }); @@ -3011,16 +3392,6 @@ impl EthApi { rx } - /// Returns a new accessor for certain storage elements - pub fn storage_info(&self) -> StorageInfo { - StorageInfo::new(Arc::clone(&self.backend)) - } - - /// Returns true if forked - pub fn is_fork(&self) -> bool { - self.backend.is_fork() - } - /// Mines exactly one block pub async fn mine_one(&self) { let transactions = self.pool.ready_transactions().collect::>(); @@ -3045,11 +3416,11 @@ impl EthApi { let mut partial_block = self.backend.convert_block(block.clone()); - let mut block_transactions = Vec::with_capacity(block.transactions.len()); + let mut block_transactions = Vec::with_capacity(block.body.transactions.len()); let base_fee = self.backend.base_fee(); for info in transactions { - let tx = block.transactions.get(info.transaction_index as usize)?.clone(); + let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); let tx = transaction_build( Some(info.transaction_hash), @@ -3066,130 +3437,80 @@ impl EthApi { Some(partial_block) } - fn build_typed_tx_request( + /// Prepares transaction request by filling missing fields using Anvil's API, then attempts + /// to build a [`FoundryTypedTx`]. + async fn build_tx_request( &self, request: WithOtherFields, nonce: u64, - ) -> Result { - let chain_id = request.chain_id.unwrap_or_else(|| self.chain_id()); - let max_fee_per_gas = request.max_fee_per_gas; - let max_fee_per_blob_gas = request.max_fee_per_blob_gas; - let gas_price = request.gas_price; - - let gas_limit = request.gas.unwrap_or_else(|| self.backend.gas_limit()); - let from = request.from; - - let request = match transaction_request_to_typed(request) { - Some(TypedTransactionRequest::Legacy(mut m)) => { - m.nonce = nonce; - m.chain_id = Some(chain_id); - m.gas_limit = gas_limit; - if gas_price.is_none() { - m.gas_price = self.gas_price(); - } - TypedTransactionRequest::Legacy(m) - } - Some(TypedTransactionRequest::EIP2930(mut m)) => { - m.nonce = nonce; - m.chain_id = chain_id; - m.gas_limit = gas_limit; - if gas_price.is_none() { - m.gas_price = self.gas_price(); - } - TypedTransactionRequest::EIP2930(m) - } - Some(TypedTransactionRequest::EIP1559(mut m)) => { - m.nonce = nonce; - m.chain_id = chain_id; - m.gas_limit = gas_limit; - if max_fee_per_gas.is_none() { - m.max_fee_per_gas = self.gas_price(); - } - TypedTransactionRequest::EIP1559(m) - } - Some(TypedTransactionRequest::EIP7702(mut m)) => { - m.nonce = nonce; - m.chain_id = chain_id; - m.gas_limit = gas_limit; - if max_fee_per_gas.is_none() { - m.max_fee_per_gas = self.gas_price(); - } - TypedTransactionRequest::EIP7702(m) - } - Some(TypedTransactionRequest::EIP4844(m)) => { - TypedTransactionRequest::EIP4844(match m { - // We only accept the TxEip4844 variant which has the sidecar. - TxEip4844Variant::TxEip4844WithSidecar(mut m) => { - m.tx.nonce = nonce; - m.tx.chain_id = chain_id; - m.tx.gas_limit = gas_limit; - if max_fee_per_gas.is_none() { - m.tx.max_fee_per_gas = self.gas_price(); - } - if max_fee_per_blob_gas.is_none() { - m.tx.max_fee_per_blob_gas = self - .excess_blob_gas_and_price() - .unwrap_or_default() - .map_or(0, |g| g.blob_gasprice) - } - TxEip4844Variant::TxEip4844WithSidecar(m) - } - TxEip4844Variant::TxEip4844(mut tx) => { - if !self.backend.skip_blob_validation(from) { - return Err(BlockchainError::FailedToDecodeTransaction) - } - - // Allows 4844 with no sidecar when impersonation is active. - tx.nonce = nonce; - tx.chain_id = chain_id; - tx.gas_limit = gas_limit; - if max_fee_per_gas.is_none() { - tx.max_fee_per_gas = self.gas_price(); - } - if max_fee_per_blob_gas.is_none() { - tx.max_fee_per_blob_gas = self - .excess_blob_gas_and_price() - .unwrap_or_default() - .map_or(0, |g| g.blob_gasprice) - } + ) -> Result { + let mut request = Into::::into(request); + let from = request.from().or(self.accounts()?.first().copied()); + + // Fill common fields for all tx types + request.chain_id().is_none().then(|| request.set_chain_id(self.chain_id())); + request.nonce().is_none().then(|| request.set_nonce(nonce)); + request.kind().is_none().then(|| request.set_kind(TxKind::default())); + if request.gas_limit().is_none() { + request.set_gas_limit( + self.do_estimate_gas( + request.as_ref().clone().into(), + None, + EvmOverrides::default(), + ) + .await + .map(|v| v as u64) + .unwrap_or(self.backend.gas_limit()), + ); + } - TxEip4844Variant::TxEip4844(tx) - } - }) - } - Some(TypedTransactionRequest::Deposit(mut m)) => { - m.gas_limit = gas_limit; - TypedTransactionRequest::Deposit(m) + // Fill missing tx type specific fields + if let Err((tx_type, _)) = request.missing_keys() { + if matches!(tx_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { + request.gas_price().is_none().then(|| request.set_gas_price(self.gas_price())); + } + if tx_type == FoundryTxType::Eip2930 { + request + .access_list() + .is_none() + .then(|| request.set_access_list(Default::default())); + } + if matches!( + tx_type, + FoundryTxType::Eip1559 + | FoundryTxType::Eip4844 + | FoundryTxType::Eip7702 + | FoundryTxType::Tempo + ) { + request + .max_fee_per_gas() + .is_none() + .then(|| request.set_max_fee_per_gas(self.gas_price())); + request + .max_priority_fee_per_gas() + .is_none() + .then(|| request.set_max_priority_fee_per_gas(MIN_SUGGESTED_PRIORITY_FEE)); + } + if tx_type == FoundryTxType::Eip4844 { + request.as_ref().max_fee_per_blob_gas().is_none().then(|| { + request.as_mut().set_max_fee_per_blob_gas( + self.backend.fees().get_next_block_blob_base_fee_per_gas(), + ) + }); } - None => return Err(BlockchainError::FailedToDecodeTransaction), - }; - Ok(request) - } - - /// Returns true if the `addr` is currently impersonated - pub fn is_impersonated(&self, addr: Address) -> bool { - self.backend.cheats().is_impersonated(addr) - } + } - /// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC - fn impersonated_signature(&self, request: &TypedTransactionRequest) -> Signature { - match request { - // Only the legacy transaction type requires v to be in {27, 28}, thus - // requiring the use of Parity::NonEip155 - TypedTransactionRequest::Legacy(_) => Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ), - TypedTransactionRequest::EIP2930(_) | - TypedTransactionRequest::EIP1559(_) | - TypedTransactionRequest::EIP7702(_) | - TypedTransactionRequest::EIP4844(_) | - TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ), + match request + .build_unsigned() + .map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))? + { + FoundryTypedTx::Eip4844(TxEip4844Variant::TxEip4844(_)) + if !self.backend.skip_blob_validation(from) => + { + // If blob validation is not skipped, reject TxEip4844 variant without sidecar. + Err(BlockchainError::FailedToDecodeTransaction) + } + res => Ok(res), } } @@ -3201,12 +3522,11 @@ impl EthApi { ) -> Result { let block_request = self.block_request(block_number).await?; - if let BlockRequest::Number(number) = block_request { - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.get_nonce(address, number).await?) - } - } + if let BlockRequest::Number(number) = block_request + && let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.get_nonce(address, number).await?); } self.backend.get_nonce(address, block_request).await @@ -3234,10 +3554,11 @@ impl EthApi { /// Adds the given transaction to the pool fn add_pending_transaction( &self, - pending_transaction: PendingTransaction, + pending_transaction: PendingTransaction, requires: Vec, provides: Vec, ) -> Result { + debug_assert!(requires != provides); let from = *pending_transaction.sender(); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = @@ -3247,21 +3568,73 @@ impl EthApi { Ok(*tx.hash()) } - /// Returns the current state root - pub async fn state_root(&self) -> Option { - self.backend.get_db().read().await.maybe_state_root() - } - /// additional validation against hardfork - fn ensure_typed_transaction_supported(&self, tx: &TypedTransaction) -> Result<()> { + fn ensure_typed_transaction_supported(&self, tx: &FoundryTxEnvelope) -> Result<()> { match &tx { - TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(), - TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(), - TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(), - TypedTransaction::EIP7702(_) => self.backend.ensure_eip7702_active(), - TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(), - TypedTransaction::Legacy(_) => Ok(()), + FoundryTxEnvelope::Eip2930(_) => self.backend.ensure_eip2930_active(), + FoundryTxEnvelope::Eip1559(_) => self.backend.ensure_eip1559_active(), + FoundryTxEnvelope::Eip4844(_) => self.backend.ensure_eip4844_active(), + FoundryTxEnvelope::Eip7702(_) => self.backend.ensure_eip7702_active(), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::Deposit(_) => self.backend.ensure_op_deposits_active(), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::PostExec(_) => Err(BlockchainError::InvalidTransactionRequest( + "not implemented for post-exec tx".to_string(), + )), + FoundryTxEnvelope::Legacy(_) => Ok(()), + FoundryTxEnvelope::Tempo(_) => self.backend.ensure_tempo_active(), + } + } + + /// Sets the fee token for a user address. + /// + /// Handler for RPC call: `anvil_setFeeToken` + /// + /// Only supported when running in Tempo mode (`--tempo`). + pub async fn anvil_set_fee_token(&self, user: Address, token: Address) -> Result<()> { + node_info!("anvil_setFeeToken"); + if !self.backend.is_tempo() { + return Err(BlockchainError::RpcUnimplemented); + } + self.backend.set_fee_token(user, token).await?; + Ok(()) + } + + /// Sets the fee token for a validator address. + /// + /// Handler for RPC call: `anvil_setValidatorFeeToken` + /// + /// Only supported when running in Tempo mode (`--tempo`). + pub async fn anvil_set_validator_fee_token( + &self, + validator: Address, + token: Address, + ) -> Result<()> { + node_info!("anvil_setValidatorFeeToken"); + if !self.backend.is_tempo() { + return Err(BlockchainError::RpcUnimplemented); + } + self.backend.set_validator_fee_token(validator, token).await?; + Ok(()) + } + + /// Mints FeeAMM liquidity for a token pair. + /// + /// Handler for RPC call: `anvil_setFeeAmmLiquidity` + /// + /// Only supported when running in Tempo mode (`--tempo`). + pub async fn anvil_set_fee_amm_liquidity( + &self, + user_token: Address, + validator_token: Address, + amount: U256, + ) -> Result<()> { + node_info!("anvil_setFeeAmmLiquidity"); + if !self.backend.is_tempo() { + return Err(BlockchainError::RpcUnimplemented); } + self.backend.set_fee_amm_liquidity(user_token, validator_token, amount).await?; + Ok(()) } } @@ -3270,10 +3643,20 @@ fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> V return Vec::new(); } let prev_nonce = provided_nonce.saturating_sub(1); - if on_chain_nonce <= prev_nonce { - vec![to_marker(prev_nonce, from)] + if on_chain_nonce <= prev_nonce { vec![to_marker(prev_nonce, from)] } else { Vec::new() } +} + +fn tempo_parallel_nonce_markers( + pending_transaction: &PendingTransaction, +) -> Option<(Vec, Vec)> { + // Tempo txs with non-zero nonce_key use a 2D nonce system and should not + // be sequenced by account nonce markers. + if let FoundryTxEnvelope::Tempo(aa_tx) = pending_transaction.transaction.as_ref() + && !aa_tx.tx().nonce_key.is_zero() + { + Some((vec![], vec![pending_transaction.hash().to_vec()])) } else { - Vec::new() + None } } @@ -3290,40 +3673,23 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result Ok(out), - return_revert!() => Err(InvalidTransactionError::Revert(Some(out.0.into())).into()), + return_revert!() => Err(InvalidTransactionError::Revert(Some(out)).into()), reason => Err(BlockchainError::EvmError(reason)), } } /// Determines the minimum gas needed for a transaction depending on the transaction kind. fn determine_base_gas_by_kind(request: &WithOtherFields) -> u128 { - match transaction_request_to_typed(request.clone()) { - Some(request) => match request { - TypedTransactionRequest::Legacy(req) => match req.to { - TxKind::Call(_) => MIN_TRANSACTION_GAS, - TxKind::Create => MIN_CREATE_GAS, - }, - TypedTransactionRequest::EIP1559(req) => match req.to { - TxKind::Call(_) => MIN_TRANSACTION_GAS, - TxKind::Create => MIN_CREATE_GAS, - }, - TypedTransactionRequest::EIP7702(req) => { - MIN_TRANSACTION_GAS + - req.authorization_list.len() as u128 * PER_EMPTY_ACCOUNT_COST as u128 - } - TypedTransactionRequest::EIP2930(req) => match req.to { - TxKind::Call(_) => MIN_TRANSACTION_GAS, - TxKind::Create => MIN_CREATE_GAS, - }, - TypedTransactionRequest::EIP4844(_) => MIN_TRANSACTION_GAS, - TypedTransactionRequest::Deposit(req) => match req.to { - TxKind::Call(_) => MIN_TRANSACTION_GAS, - TxKind::Create => MIN_CREATE_GAS, - }, - }, - // Tighten the gas limit upwards if we don't know the transaction type to avoid deployments - // failing. - _ => MIN_CREATE_GAS, + match request.kind() { + Some(TxKind::Call(_)) => { + MIN_TRANSACTION_GAS + + request.inner().authorization_list.as_ref().map_or(0, |auths_list| { + auths_list.len() as u128 * PER_EMPTY_ACCOUNT_COST as u128 + }) + } + Some(TxKind::Create) => MIN_CREATE_GAS, + // Tighten the gas limit upwards if we don't know the tx kind to avoid deployments failing. + None => MIN_CREATE_GAS, } } @@ -3336,6 +3702,8 @@ enum GasEstimationCallResult { } /// Converts the result of a call to revm EVM into a [`GasEstimationCallResult`]. +/// +/// Expected to stay up to date with: impl TryFrom, u128, State)>> for GasEstimationCallResult { type Error = BlockchainError; @@ -3345,49 +3713,51 @@ impl TryFrom, u128, State)>> for GasEs Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) => { Ok(Self::OutOfGas) } + // Tempo intrinsic gas errors come through as Message variants + Err(BlockchainError::Message(ref msg)) + if msg.contains("insufficient gas for intrinsic cost") => + { + Ok(Self::OutOfGas) + } Err(err) => Err(err), Ok((exit, output, gas, _)) => match exit { - return_ok!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + return_ok!() => Ok(Self::Success(gas)), + // Revert opcodes: InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), - - InstructionResult::OutOfGas | - InstructionResult::MemoryOOG | - InstructionResult::MemoryLimitOOG | - InstructionResult::PrecompileOOG | - InstructionResult::InvalidOperandOOG | - InstructionResult::ReentrancySentryOOG => Ok(Self::OutOfGas), - - InstructionResult::OpcodeNotFound | - InstructionResult::CallNotAllowedInsideStatic | - InstructionResult::StateChangeDuringStaticCall | - InstructionResult::InvalidExtDelegateCallTarget | - InstructionResult::InvalidEXTCALLTarget | - InstructionResult::InvalidFEOpcode | - InstructionResult::InvalidJump | - InstructionResult::NotActivated | - InstructionResult::StackUnderflow | - InstructionResult::StackOverflow | - InstructionResult::OutOfOffset | - InstructionResult::CreateCollision | - InstructionResult::OverflowPayment | - InstructionResult::PrecompileError | - InstructionResult::NonceOverflow | - InstructionResult::CreateContractSizeLimit | - InstructionResult::CreateContractStartingWithEF | - InstructionResult::CreateInitCodeSizeLimit | - InstructionResult::FatalExternalError | - InstructionResult::OutOfFunds | - InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), - - // Handle Revm EOF InstructionResults: not supported - InstructionResult::ReturnContractInNotInitEOF | - InstructionResult::EOFOpcodeDisabledInLegacy | - InstructionResult::SubRoutineStackOverflow | - InstructionResult::CreateInitCodeStartingEF00 | - InstructionResult::InvalidEOFInitCode | - InstructionResult::EofAuxDataOverflow | - InstructionResult::EofAuxDataTooSmall => Ok(Self::EvmError(exit)), + InstructionResult::CallTooDeep + | InstructionResult::OutOfFunds + | InstructionResult::CreateInitCodeStartingEF00 + | InstructionResult::InvalidEOFInitCode + | InstructionResult::InvalidExtDelegateCallTarget => Ok(Self::EvmError(exit)), + + // Out of gas errors: + InstructionResult::OutOfGas + | InstructionResult::MemoryOOG + | InstructionResult::MemoryLimitOOG + | InstructionResult::PrecompileOOG + | InstructionResult::InvalidOperandOOG + | InstructionResult::ReentrancySentryOOG => Ok(Self::OutOfGas), + + // Other errors: + InstructionResult::OpcodeNotFound + | InstructionResult::CallNotAllowedInsideStatic + | InstructionResult::StateChangeDuringStaticCall + | InstructionResult::InvalidFEOpcode + | InstructionResult::InvalidJump + | InstructionResult::NotActivated + | InstructionResult::StackUnderflow + | InstructionResult::StackOverflow + | InstructionResult::OutOfOffset + | InstructionResult::CreateCollision + | InstructionResult::OverflowPayment + | InstructionResult::PrecompileError + | InstructionResult::NonceOverflow + | InstructionResult::CreateContractSizeLimit + | InstructionResult::CreateContractStartingWithEF + | InstructionResult::CreateInitCodeSizeLimit + | InstructionResult::InvalidImmediateEncoding + | InstructionResult::FatalExternalError => Ok(Self::EvmError(exit)), }, } } diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index b2131a3ce08f0..bbde83bf6a735 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -1,8 +1,20 @@ //! Support for "cheat codes" / bypass functions -use alloy_primitives::{map::AddressHashSet, Address}; +use alloy_evm::precompiles::{Precompile, PrecompileInput}; +use alloy_primitives::{ + Address, Bytes, + map::{AddressHashSet, foldhash::HashMap}, +}; use parking_lot::RwLock; -use std::sync::Arc; +use revm::precompile::{ + PrecompileHalt, PrecompileId, PrecompileOutput, PrecompileResult, call_eth_precompile, + secp256k1::ec_recover_run, utilities::right_pad, +}; +use std::{borrow::Cow, sync::Arc}; + +/// ID for the [`CheatEcrecover::precompile_id`] precompile. +static PRECOMPILE_ID_CHEAT_ECRECOVER: PrecompileId = + PrecompileId::Custom(Cow::Borrowed("cheat_ecrecover")); /// Manages user modifications that may affect the node's behavior /// @@ -16,25 +28,18 @@ pub struct CheatsManager { impl CheatsManager { /// Sets the account to impersonate /// - /// This also accepts the actual code hash if the address is a contract to bypass EIP-3607 - /// /// Returns `true` if the account is already impersonated pub fn impersonate(&self, addr: Address) -> bool { - trace!(target: "cheats", "Start impersonating {:?}", addr); - let mut state = self.state.write(); + trace!(target: "cheats", %addr, "start impersonating"); // When somebody **explicitly** impersonates an account we need to store it so we are able // to return it from `eth_accounts`. That's why we do not simply call `is_impersonated()` // which does not check that list when auto impersonation is enabled. - if state.impersonated_accounts.contains(&addr) { - // need to check if already impersonated, so we don't overwrite the code - return true - } - state.impersonated_accounts.insert(addr) + !self.state.write().impersonated_accounts.insert(addr) } /// Removes the account that from the impersonated set pub fn stop_impersonating(&self, addr: &Address) { - trace!(target: "cheats", "Stop impersonating {:?}", addr); + trace!(target: "cheats", %addr, "stop impersonating"); self.state.write().impersonated_accounts.remove(addr); } @@ -63,6 +68,21 @@ impl CheatsManager { pub fn impersonated_accounts(&self) -> AddressHashSet { self.state.read().impersonated_accounts.clone() } + + /// Registers an override so that `ecrecover(signature)` returns `addr`. + pub fn add_recover_override(&self, sig: Bytes, addr: Address) { + self.state.write().signature_overrides.insert(sig, addr); + } + + /// If an override exists for `sig`, returns the address; otherwise `None`. + pub fn get_recover_override(&self, sig: &Bytes) -> Option
{ + self.state.read().signature_overrides.get(sig).copied() + } + + /// Returns true if any ecrecover overrides have been registered. + pub fn has_recover_overrides(&self) -> bool { + !self.state.read().signature_overrides.is_empty() + } } /// Container type for all the state variables @@ -72,4 +92,68 @@ pub struct CheatsState { pub impersonated_accounts: AddressHashSet, /// If set to true will make the `is_impersonated` function always return true pub auto_impersonate_accounts: bool, + /// Overrides for ecrecover: Signature => Address + pub signature_overrides: HashMap, +} + +impl CheatEcrecover { + pub const fn new(cheats: Arc) -> Self { + Self { cheats } + } +} + +impl Precompile for CheatEcrecover { + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { + if !self.cheats.has_recover_overrides() { + return Ok(call_eth_precompile(ec_recover_run, input.data, input.gas, input.reservoir)); + } + + const ECRECOVER_BASE: u64 = 3_000; + if input.gas < ECRECOVER_BASE { + return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, input.reservoir)); + } + let padded = right_pad::<128>(input.data); + let v = padded[63]; + let mut sig_bytes = [0u8; 65]; + sig_bytes[..64].copy_from_slice(&padded[64..128]); + sig_bytes[64] = v; + let sig_bytes_wrapped = Bytes::copy_from_slice(&sig_bytes); + if let Some(addr) = self.cheats.get_recover_override(&sig_bytes_wrapped) { + let mut out = [0u8; 32]; + out[12..].copy_from_slice(addr.as_slice()); + return Ok(PrecompileOutput::new( + ECRECOVER_BASE, + Bytes::copy_from_slice(&out), + input.reservoir, + )); + } + Ok(call_eth_precompile(ec_recover_run, input.data, input.gas, input.reservoir)) + } + + fn precompile_id(&self) -> &PrecompileId { + &PRECOMPILE_ID_CHEAT_ECRECOVER + } + + fn supports_caching(&self) -> bool { + false + } +} + +/// A custom ecrecover precompile that supports cheat-based signature overrides. +#[derive(Clone, Debug)] +pub struct CheatEcrecover { + cheats: Arc, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn impersonate_returns_false_then_true() { + let mgr = CheatsManager::default(); + let addr = Address::from([1u8; 20]); + assert!(!mgr.impersonate(addr)); + assert!(mgr.impersonate(addr)); + } } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index e39baee9193df..d1c842d12d108 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,38 +1,48 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::mem::storage::MinedTransaction; -use alloy_consensus::Header; -use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, B256, U256}; +use std::{ + collections::BTreeMap, + fmt::{self, Debug}, + path::Path, +}; + +use alloy_consensus::{BlockBody, Header}; +use alloy_eips::eip4895::Withdrawals; +use alloy_network::Network; +use alloy_primitives::{ + Address, B256, Bytes, U256, keccak256, + map::{AddressMap, HashMap}, +}; use alloy_rpc_types::BlockId; use anvil_core::eth::{ block::Block, - transaction::{MaybeImpersonatedTransaction, TransactionInfo, TypedReceipt, TypedTransaction}, + transaction::{MaybeImpersonatedTransaction, TransactionInfo}, }; use foundry_common::errors::FsPathError; use foundry_evm::backend::{ BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot, }; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; use revm::{ + Database, DatabaseCommit, bytecode::Bytecode, context::BlockEnv, + context_interface::block::BlobExcessGasAndPrice, database::{CacheDB, DatabaseRef, DbAccount}, - primitives::KECCAK_EMPTY, + primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, state::AccountInfo, - Database, DatabaseCommit, }; use serde::{ - de::{MapAccess, Visitor}, Deserialize, Deserializer, Serialize, + de::{Error as DeError, MapAccess, Visitor}, }; -use std::{collections::BTreeMap, fmt, path::Path}; +use serde_json::Value; -/// Helper trait get access to the full state data of the database -pub trait MaybeFullDatabase: DatabaseRef { - /// Returns a reference to the database as a `dyn DatabaseRef`. - // TODO: Required until trait upcasting is stabilized: - fn as_dyn(&self) -> &dyn DatabaseRef; +use crate::mem::storage::MinedTransaction; - fn maybe_as_full_db(&self) -> Option<&HashMap> { +/// Helper trait get access to the full state data of the database +pub trait MaybeFullDatabase: DatabaseRef + Debug { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { None } @@ -55,11 +65,7 @@ impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { - fn as_dyn(&self) -> &dyn DatabaseRef { - T::as_dyn(self) - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { T::maybe_as_full_db(self) } @@ -78,13 +84,86 @@ where /// Helper trait to reset the DB if it's forked pub trait MaybeForkedDatabase { - fn maybe_reset(&mut self, _url: Option, block_number: BlockId) -> Result<(), String>; + fn maybe_reset(&mut self, _urls: Vec, block_number: BlockId) -> Result<(), String>; fn maybe_flush_cache(&self) -> Result<(), String>; fn maybe_inner(&self) -> Result<&BlockchainDb, String>; } +/// `dyn Db` satisfies all `alloy_evm::Database` requirements via its supertraits, but the +/// blanket impl has an implicit `Sized` bound. Provide an explicit impl. +impl alloy_evm::Database for dyn Db {} + +/// A wrapper around [`CacheDB`]. +#[derive(Debug)] +pub struct AnvilCacheDB(pub CacheDB); + +impl> AnvilCacheDB { + pub fn new(inner: T) -> Self { + Self(CacheDB::new(inner)) + } +} + +impl> std::ops::Deref for AnvilCacheDB { + type Target = CacheDB; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> std::ops::DerefMut for AnvilCacheDB { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl + fmt::Debug> Database for AnvilCacheDB { + type Error = DatabaseError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + self.0.basic(address) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.0.code_by_hash(code_hash) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + self.0.storage(address, index) + } + + fn block_hash(&mut self, number: u64) -> Result { + self.0.block_hash(number) + } +} + +impl> DatabaseRef for AnvilCacheDB { + type Error = DatabaseError; + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + self.0.basic_ref(address) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.0.code_by_hash_ref(code_hash) + } + + fn storage_ref(&self, address: Address, index: U256) -> Result { + self.0.storage_ref(address, index) + } + + fn block_hash_ref(&self, number: u64) -> Result { + self.0.block_hash_ref(number) + } +} + +impl + fmt::Debug> DatabaseCommit for AnvilCacheDB { + fn commit(&mut self, changes: revm::state::EvmState) { + self.0.commit(changes) + } +} + /// This bundles all required revm traits pub trait Db: DatabaseRef @@ -115,7 +194,7 @@ pub trait Db: Ok(()) } - /// Sets the balance of the given address + /// Sets the code of the given address fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> { let mut info = self.basic(address)?.unwrap_or_default(); let code_hash = if code.as_ref().is_empty() { @@ -129,7 +208,7 @@ pub trait Db: Ok(()) } - /// Sets the balance of the given address + /// Sets the storage value at the given slot for the address fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>; /// inserts a blockhash for the given number @@ -147,7 +226,7 @@ pub trait Db: /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { - for (addr, account) in state.accounts.into_iter() { + for (addr, account) in state.accounts { let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) @@ -167,10 +246,11 @@ pub trait Db: Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0))) }, nonce, + account_id: None, }, ); - for (k, v) in account.storage.into_iter() { + for (k, v) in account.storage { self.set_storage_at(addr, k, v)?; } } @@ -194,13 +274,6 @@ pub trait Db: fn current_state(&self) -> StateDb; } -impl dyn Db { - // TODO: Required until trait upcasting is stabilized: - pub fn as_dbref(&self) -> &dyn DatabaseRef { - self.as_dyn() - } -} - /// Convenience impl only used to use any `Db` on the fly as the db layer for revm's CacheDB /// This is useful to create blocks without actually writing to the `Db`, but rather in the cache of /// the `CacheDB` see also @@ -242,12 +315,8 @@ impl + Send + Sync + Clone + fmt::Debug> D } } -impl> MaybeFullDatabase for CacheDB { - fn as_dyn(&self) -> &dyn DatabaseRef { - self - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { +impl + Debug> MaybeFullDatabase for CacheDB { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.cache.accounts) } @@ -267,15 +336,14 @@ impl> MaybeFullDatabase for CacheDB { } fn read_as_state_snapshot(&self) -> StateSnapshot { - let db_accounts = self.cache.accounts.clone(); let mut accounts = HashMap::default(); let mut account_storage = HashMap::default(); - for (addr, acc) in db_accounts { - account_storage.insert(addr, acc.storage.clone()); - let mut info = acc.info; + for (addr, acc) in &self.cache.accounts { + account_storage.insert(*addr, acc.storage.clone()); + let mut info = acc.info.clone(); info.code = self.cache.contracts.get(&info.code_hash).cloned(); - accounts.insert(addr, info); + accounts.insert(*addr, info); } let block_hashes = self.cache.block_hashes.clone(); @@ -307,7 +375,7 @@ impl> MaybeFullDatabase for CacheDB { } impl> MaybeForkedDatabase for CacheDB { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } @@ -321,6 +389,7 @@ impl> MaybeForkedDatabase for CacheDB { } /// Represents a state at certain point +#[derive(Debug)] pub struct StateDb(pub(crate) Box); impl StateDb { @@ -355,11 +424,7 @@ impl DatabaseRef for StateDb { } impl MaybeFullDatabase for StateDb { - fn as_dyn(&self) -> &dyn DatabaseRef { - self.0.as_dyn() - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { self.0.maybe_as_full_db() } @@ -380,14 +445,132 @@ impl MaybeFullDatabase for StateDb { } } +/// Legacy block environment from before v1.3. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct LegacyBlockEnv { + pub number: Option, + #[serde(alias = "coinbase")] + pub beneficiary: Option
, + pub timestamp: Option, + pub gas_limit: Option, + pub basefee: Option, + pub difficulty: Option, + pub prevrandao: Option, + pub blob_excess_gas_and_price: Option, +} + +/// Legacy blob excess gas and price structure from before v1.3. +#[derive(Debug, Deserialize)] +pub struct LegacyBlobExcessGasAndPrice { + pub excess_blob_gas: u64, + pub blob_gasprice: u64, +} + +/// Legacy string or u64 type from before v1.3. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum StringOrU64 { + Hex(String), + Dec(u64), +} + +impl StringOrU64 { + pub fn to_u64(&self) -> Option { + match self { + Self::Dec(n) => Some(*n), + Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()), + } + } + + pub fn to_u256(&self) -> Option { + match self { + Self::Dec(n) => Some(U256::from(*n)), + Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()), + } + } +} + +/// Converts a `LegacyBlockEnv` to a `BlockEnv`, handling the conversion of legacy fields. +impl TryFrom for BlockEnv { + type Error = &'static str; + + fn try_from(legacy: LegacyBlockEnv) -> Result { + Ok(Self { + number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO), + beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO), + timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE), + gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX), + basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0), + difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO), + prevrandao: legacy.prevrandao.or(Some(B256::ZERO)), + slot_num: 0, + blob_excess_gas_and_price: legacy + .blob_excess_gas_and_price + .map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice)) + .or_else(|| { + Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE)) + }), + }) + } +} + +/// Custom deserializer for `BlockEnv` that handles both v1.2 and v1.3+ formats. +fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value: Option = Option::deserialize(deserializer)?; + let Some(value) = value else { + return Ok(None); + }; + + if let Ok(env) = BlockEnv::deserialize(&value) { + return Ok(Some(env)); + } + + let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| { + D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}")) + })?; + + Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?)) +} + +/// Custom deserializer for `best_block_number` that handles both v1.2 and v1.3+ formats. +fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value: Option = Option::deserialize(deserializer)?; + let Some(value) = value else { + return Ok(None); + }; + + let number = match value { + Value::Number(n) => n.as_u64(), + Value::String(s) => { + if let Some(s) = s.strip_prefix("0x") { + u64::from_str_radix(s, 16).ok() + } else { + s.parse().ok() + } + } + _ => None, + }; + + Ok(number) +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SerializableState { /// The block number of the state /// /// Note: This is an Option for backwards compatibility: + #[serde(deserialize_with = "deserialize_block_env_compat")] pub block: Option, pub accounts: BTreeMap, /// The best block number of the state, can be different from block number (Arbitrum chain). + #[serde(deserialize_with = "deserialize_best_block_number_compat")] pub best_block_number: Option, #[serde(default)] pub blocks: Vec, @@ -412,7 +595,7 @@ impl SerializableState { } /// This is used as the clap `value_parser` implementation - #[allow(dead_code)] + #[cfg(feature = "cmd")] pub(crate) fn parse(path: &str) -> Result { Self::load(path).map_err(|err| err.to_string()) } @@ -467,8 +650,8 @@ where #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum SerializableTransactionType { - TypedTransaction(TypedTransaction), - MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), + TypedTransaction(FoundryTxEnvelope), + MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -476,35 +659,37 @@ pub struct SerializableBlock { pub header: Header, pub transactions: Vec, pub ommers: Vec
, + #[serde(default)] + pub withdrawals: Option, } impl From for SerializableBlock { fn from(block: Block) -> Self { Self { header: block.header, - transactions: block.transactions.into_iter().map(Into::into).collect(), - ommers: block.ommers.into_iter().collect(), + transactions: block.body.transactions.into_iter().map(Into::into).collect(), + ommers: block.body.ommers.into_iter().collect(), + withdrawals: block.body.withdrawals, } } } impl From for Block { fn from(block: SerializableBlock) -> Self { - Self { - header: block.header, - transactions: block.transactions.into_iter().map(Into::into).collect(), - ommers: block.ommers.into_iter().collect(), - } + let transactions = block.transactions.into_iter().map(Into::into).collect(); + let ommers = block.ommers; + let body = BlockBody { transactions, ommers, withdrawals: block.withdrawals }; + Self::new(block.header, body) } } -impl From for SerializableTransactionType { - fn from(transaction: MaybeImpersonatedTransaction) -> Self { +impl From> for SerializableTransactionType { + fn from(transaction: MaybeImpersonatedTransaction) -> Self { Self::MaybeImpersonatedTransaction(transaction) } } -impl From for MaybeImpersonatedTransaction { +impl From for MaybeImpersonatedTransaction { fn from(transaction: SerializableTransactionType) -> Self { match transaction { SerializableTransactionType::TypedTransaction(tx) => Self::new(tx), @@ -516,13 +701,15 @@ impl From for MaybeImpersonatedTransaction { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableTransaction { pub info: TransactionInfo, - pub receipt: TypedReceipt, + pub receipt: FoundryReceiptEnvelope, pub block_hash: B256, pub block_number: u64, } -impl From for SerializableTransaction { - fn from(transaction: MinedTransaction) -> Self { +impl> From> + for SerializableTransaction +{ + fn from(transaction: MinedTransaction) -> Self { Self { info: transaction.info, receipt: transaction.receipt, @@ -532,7 +719,9 @@ impl From for SerializableTransaction { } } -impl From for MinedTransaction { +impl> From + for MinedTransaction +{ fn from(transaction: SerializableTransaction) -> Self { Self { info: transaction.info, @@ -590,21 +779,20 @@ mod test { }, "transactions": [ { - "EIP1559": { - "chainId": "0x7a69", - "nonce": "0x0", - "gas": "0x5209", - "maxFeePerGas": "0x77359401", - "maxPriorityFeePerGas": "0x1", - "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "value": "0x0", - "accessList": [], - "input": "0x", - "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", - "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", - "yParity": "0x0", - "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" - } + "type": "0x2", + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" } ], "ommers": [] @@ -613,4 +801,36 @@ mod test { let _block: SerializableBlock = serde_json::from_str(block).unwrap(); } + + #[test] + fn test_block_withdrawals_preserved() { + use alloy_eips::eip4895::Withdrawal; + + // create a block with withdrawals (like post-Shanghai blocks) + let withdrawal = Withdrawal { + index: 42, + validator_index: 123, + address: Address::repeat_byte(1), + amount: 1000, + }; + + let header = Header::default(); + let body = BlockBody { + transactions: vec![], + ommers: vec![], + withdrawals: Some(vec![withdrawal].into()), + }; + let block = Block::new(header, body); + + // convert to SerializableBlock and back + let serializable = SerializableBlock::from(block); + let restored = Block::from(serializable); + + // withdrawals should be preserved + assert!(restored.body.withdrawals.is_some()); + let withdrawals = restored.body.withdrawals.unwrap(); + assert_eq!(withdrawals.len(), 1); + assert_eq!(withdrawals[0].index, 42); + assert_eq!(withdrawals[0].validator_index, 123); + } } diff --git a/crates/anvil/src/eth/backend/env.rs b/crates/anvil/src/eth/backend/env.rs deleted file mode 100644 index d4b8de797023d..0000000000000 --- a/crates/anvil/src/eth/backend/env.rs +++ /dev/null @@ -1,30 +0,0 @@ -use alloy_evm::EvmEnv; -use foundry_evm::EnvMut; -use foundry_evm_core::AsEnvMut; -use op_revm::OpTransaction; -use revm::context::{BlockEnv, CfgEnv, TxEnv}; - -/// Helper container type for [`EvmEnv`] and [`OpTransaction`]. -#[derive(Clone, Debug, Default)] -pub struct Env { - pub evm_env: EvmEnv, - pub tx: OpTransaction, - pub is_optimism: bool, -} - -/// Helper container type for [`EvmEnv`] and [`OpTransaction`]. -impl Env { - pub fn new(cfg: CfgEnv, block: BlockEnv, tx: OpTransaction, is_optimism: bool) -> Self { - Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx, is_optimism } - } -} - -impl AsEnvMut for Env { - fn as_env_mut(&mut self) -> EnvMut<'_> { - EnvMut { - block: &mut self.evm_env.block_env, - cfg: &mut self.evm_env.cfg_env, - tx: &mut self.tx.base, - } - } -} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 2ae228e39dd54..c41937055fdd4 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -1,508 +1,546 @@ use crate::{ eth::{ - backend::{ - db::Db, env::Env, mem::op_haltreason_to_instruction_result, - validate::TransactionValidator, - }, - error::InvalidTransactionError, + backend::cheats::CheatsManager, error::InvalidTransactionError, pool::transactions::PoolTransaction, }, - inject_precompiles, - mem::inspector::AnvilInspector, - PrecompileFactory, + mem::inspector::{AnvilInspector, InspectorTxConfig}, }; use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root, Receipt, ReceiptWithBloom, + Eip658Value, Transaction, TransactionEnvelope, TxReceipt, + transaction::{Either, Recovered}, }; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams}; -use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EthEvm, Evm}; -use alloy_op_evm::OpEvm; -use alloy_primitives::{Bloom, BloomInput, Log, B256}; -use anvil_core::eth::{ - block::{Block, BlockInfo, PartialHeader}, - transaction::{ - DepositReceipt, PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction, +use alloy_eips::{ + Encodable2718, eip2935, eip4788, + eip7702::{RecoveredAuthority, RecoveredAuthorization}, +}; +use alloy_evm::{ + Evm, FromRecoveredTx, FromTxWithEncoded, RecoveredTx, + block::{ + BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError, + ExecutableTx, GasOutput, OnStateHook, StateChangePreBlockSource, StateChangeSource, + StateDB, TxResult, + }, + eth::{ + EthTxResult, + receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}, }, }; -use foundry_evm::{backend::DatabaseError, traces::CallTraceNode}; -use foundry_evm_core::either_evm::EitherEvm; -use op_revm::{precompiles::OpPrecompiles, L1BlockInfo, OpContext}; +use alloy_primitives::{Address, B256, Bytes, U256}; +use anvil_core::eth::transaction::{ + MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo, +}; +use foundry_evm::core::{env::FoundryTransaction, evm::IntoInstructionResult}; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType}; use revm::{ - context::{Block as RevmBlock, BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext}, - context_interface::result::{EVMError, ExecutionResult, Output}, - database::WrapDatabaseRef, - handler::{instructions::EthInstructions, EthPrecompiles}, + Database, DatabaseCommit, + context::Block as RevmBlock, + context_interface::result::{ExecutionResult, Output, ResultAndState}, interpreter::InstructionResult, - precompile::{secp256r1::P256VERIFY, PrecompileSpecId, Precompiles}, primitives::hardfork::SpecId, - Database, DatabaseRef, Inspector, Journal, + state::AccountInfo, }; -use std::sync::Arc; +use std::{fmt, fmt::Debug, mem::take, sync::Arc}; + +/// Receipt builder for Foundry/Anvil that handles all transaction types +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct FoundryReceiptBuilder; + +impl ReceiptBuilder for FoundryReceiptBuilder { + type Transaction = FoundryTxEnvelope; + type Receipt = FoundryReceiptEnvelope; + + fn build_receipt( + &self, + ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>, + ) -> FoundryReceiptEnvelope { + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + } + .with_bloom(); + + match ctx.tx_type { + FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt), + FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt), + FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), + FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), + FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), + #[cfg(feature = "optimism")] + FoundryTxType::Deposit => { + unreachable!("deposit receipts are built in commit_transaction") + } + #[cfg(feature = "optimism")] + FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt), + FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), + } + } +} -/// Represents an executed transaction (transacted on the DB) +/// Result of executing a transaction in [`AnvilBlockExecutor`]. +/// +/// Wraps [`EthTxResult`] with the sender address, needed for deposit nonce resolution. #[derive(Debug)] -pub struct ExecutedTransaction { - transaction: Arc, - exit_reason: InstructionResult, - out: Option, - gas_used: u64, - logs: Vec, - traces: Vec, - nonce: u64, +pub struct AnvilTxResult { + pub inner: EthTxResult, + pub sender: Address, } -// == impl ExecutedTransaction == +impl TxResult for AnvilTxResult { + type HaltReason = H; -impl ExecutedTransaction { - /// Creates the receipt for the transaction - fn create_receipt(&self, cumulative_gas_used: &mut u64) -> TypedReceipt { - let logs = self.logs.clone(); - *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); + fn result(&self) -> &ResultAndState { + self.inner.result() + } - // successful return see [Return] - let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); - let receipt_with_bloom: ReceiptWithBloom = Receipt { - status: (status_code == 1).into(), - cumulative_gas_used: *cumulative_gas_used, - logs, - } - .into(); - - match &self.transaction.pending_transaction.transaction.transaction { - TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), - TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), - TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), - TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), - TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), - TypedTransaction::Deposit(_tx) => TypedReceipt::Deposit(DepositReceipt { - inner: receipt_with_bloom, - deposit_nonce: Some(0), - deposit_receipt_version: Some(1), - }), - } + fn into_result(self) -> ResultAndState { + self.inner.into_result() } } -/// Represents the outcome of mining a new block -#[derive(Clone, Debug)] -pub struct ExecutedTransactions { - /// The block created after executing the `included` transactions - pub block: BlockInfo, - /// All transactions included in the block - pub included: Vec>, - /// All transactions that were invalid at the point of their execution and were not included in - /// the block - pub invalid: Vec>, +/// Block executor for Anvil that implements [`BlockExecutor`]. +/// +/// Wraps an EVM instance and produces [`FoundryReceiptEnvelope`] receipts. +/// Validation (gas limits, blob gas, transaction validity) is handled by the +/// caller before transactions are fed to this executor. +pub struct AnvilBlockExecutor { + /// The EVM instance used for execution. + evm: E, + /// Parent block hash — needed for EIP-2935 system call. + parent_hash: B256, + /// The active spec id, used to gate hardfork-specific behavior. + spec_id: SpecId, + /// Receipt builder. + receipt_builder: FoundryReceiptBuilder, + /// Receipts of executed transactions. + receipts: Vec, + /// Total gas used by transactions in this block. + gas_used: u64, + /// Blob gas used by the block. + blob_gas_used: u64, + /// Optional state change hook. + state_hook: Option>, } -/// An executor for a series of transactions -pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { - /// where to insert the transactions - pub db: &'a mut Db, - /// type used to validate before inclusion - pub validator: &'a V, - /// all pending transactions - pub pending: std::vec::IntoIter>, - pub block_env: BlockEnv, - /// The configuration environment and spec id - pub cfg_env: CfgEnv, - pub parent_hash: B256, - /// Cumulative gas used by all executed transactions - pub gas_used: u64, - /// Cumulative blob gas used by all executed transactions - pub blob_gas_used: u64, - pub enable_steps_tracing: bool, - pub odyssey: bool, - pub optimism: bool, - pub print_logs: bool, - pub print_traces: bool, - /// Precompiles to inject to the EVM. - pub precompile_factory: Option>, - pub blob_params: BlobParams, +impl fmt::Debug for AnvilBlockExecutor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AnvilBlockExecutor") + .field("evm", &self.evm) + .field("parent_hash", &self.parent_hash) + .field("spec_id", &self.spec_id) + .field("gas_used", &self.gas_used) + .field("blob_gas_used", &self.blob_gas_used) + .field("receipts", &self.receipts.len()) + .finish_non_exhaustive() + } } -impl TransactionExecutor<'_, DB, V> { - /// Executes all transactions and puts them in a new block with the provided `timestamp` - pub fn execute(mut self) -> ExecutedTransactions { - let mut transactions = Vec::new(); - let mut transaction_infos = Vec::new(); - let mut receipts = Vec::new(); - let mut bloom = Bloom::default(); - let mut cumulative_gas_used = 0u64; - let mut invalid = Vec::new(); - let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit; - let parent_hash = self.parent_hash; - let block_number = self.block_env.number; - let difficulty = self.block_env.difficulty; - let beneficiary = self.block_env.beneficiary; - let timestamp = self.block_env.timestamp; - let base_fee = if self.cfg_env.spec.is_enabled_in(SpecId::LONDON) { - Some(self.block_env.basefee) - } else { - None - }; +impl AnvilBlockExecutor { + /// Creates a new [`AnvilBlockExecutor`]. + pub fn new(evm: E, parent_hash: B256, spec_id: SpecId) -> Self { + Self { + evm, + parent_hash, + spec_id, + receipt_builder: FoundryReceiptBuilder, + receipts: Vec::new(), + gas_used: 0, + blob_gas_used: 0, + state_hook: None, + } + } +} - let is_shanghai = self.cfg_env.spec >= SpecId::SHANGHAI; - let is_cancun = self.cfg_env.spec >= SpecId::CANCUN; - let is_prague = self.cfg_env.spec >= SpecId::PRAGUE; - let excess_blob_gas = if is_cancun { self.block_env.blob_excess_gas() } else { None }; - let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; - - for tx in self.into_iter() { - let tx = match tx { - TransactionExecutionOutcome::Executed(tx) => { - included.push(tx.transaction.clone()); - tx - } - TransactionExecutionOutcome::Exhausted(tx) => { - trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction"); - continue - } - TransactionExecutionOutcome::BlobGasExhausted(tx) => { - trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction"); - continue - } - TransactionExecutionOutcome::Invalid(tx, _) => { - trace!(target: "backend", ?tx, "skipping invalid transaction"); - invalid.push(tx); - continue - } - TransactionExecutionOutcome::DatabaseError(_, err) => { - // Note: this is only possible in forking mode, if for example a rpc request - // failed - trace!(target: "backend", ?err, "Failed to execute transaction due to database error"); - continue - } - }; - if is_cancun { - let tx_blob_gas = tx - .transaction - .pending_transaction - .transaction - .transaction - .blob_gas() - .unwrap_or(0); - cumulative_blob_gas_used = - Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas)); +impl BlockExecutor for AnvilBlockExecutor +where + E: Evm< + DB: StateDB, + Tx: FromRecoveredTx + FromTxWithEncoded, + >, +{ + type Transaction = FoundryTxEnvelope; + type Receipt = FoundryReceiptEnvelope; + type Evm = E; + type Result = AnvilTxResult; + + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + // EIP-2935: store parent block hash in history storage contract. + if self.spec_id >= SpecId::PRAGUE { + let result = self + .evm + .transact_system_call( + eip4788::SYSTEM_ADDRESS, + eip2935::HISTORY_STORAGE_ADDRESS, + Bytes::copy_from_slice(self.parent_hash.as_slice()), + ) + .map_err(BlockExecutionError::other)?; + + if let Some(hook) = &mut self.state_hook { + hook.on_state( + StateChangeSource::PreBlock(StateChangePreBlockSource::BlockHashesContract), + &result.state, + ); } - let receipt = tx.create_receipt(&mut cumulative_gas_used); + self.evm.db_mut().commit(result.state); + } + Ok(()) + } - let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - build_logs_bloom(logs.clone(), &mut bloom); + fn execute_transaction_without_commit( + &mut self, + tx: impl ExecutableTx, + ) -> Result { + let (tx_env, tx) = tx.into_parts(); + + let block_available_gas = self.evm.block().gas_limit() - self.gas_used; + if tx.tx().gas_limit() > block_available_gas { + return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: tx.tx().gas_limit(), + block_available_gas, + } + .into()); + } - let contract_address = out.as_ref().and_then(|out| { - if let Output::Create(_, contract_address) = out { - trace!(target: "backend", "New contract deployed: at {:?}", contract_address); - *contract_address - } else { - None - } - }); - - let transaction_index = transaction_infos.len() as u64; - let info = TransactionInfo { - transaction_hash: transaction.hash(), - transaction_index, - from: *transaction.pending_transaction.sender(), - to: transaction.pending_transaction.transaction.to(), - contract_address, - traces, - exit, - out: out.map(Output::into_data), - nonce: tx.nonce, - gas_used: tx.gas_used, - }; - - transaction_infos.push(info); - receipts.push(receipt); - transactions.push(transaction.pending_transaction.transaction.clone()); + let sender = *tx.signer(); + + let result = self.evm.transact(tx_env).map_err(|err| { + let hash = tx.tx().trie_hash(); + BlockExecutionError::evm(err, hash) + })?; + + Ok(AnvilTxResult { + inner: EthTxResult { + result, + blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(), + tx_type: tx.tx().tx_type(), + }, + sender, + }) + } + + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { + let AnvilTxResult { + inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] + sender, + } = output; + + if let Some(hook) = &mut self.state_hook { + hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); } - let receipts_root = calculate_receipt_root(&receipts); + let gas_used = result.tx_gas_used(); + self.gas_used += gas_used; - let partial_header = PartialHeader { - parent_hash, - beneficiary, - state_root: self.db.maybe_state_root().unwrap_or_default(), - receipts_root, - logs_bloom: bloom, - difficulty, - number: block_number, - gas_limit, - gas_used: cumulative_gas_used, - timestamp, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - base_fee, - parent_beacon_block_root: is_cancun.then_some(Default::default()), - blob_gas_used: cumulative_blob_gas_used, - excess_blob_gas, - withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), + if self.spec_id >= SpecId::CANCUN { + self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); + } + + #[cfg(feature = "optimism")] + let receipt = if tx_type == FoundryTxType::Deposit { + let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), + cumulative_gas_used: self.gas_used, + logs: result.into_logs(), + } + .with_bloom(); + FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom { + receipt: op_alloy_consensus::OpDepositReceipt { + inner: receipt.receipt, + deposit_nonce, + deposit_receipt_version: deposit_nonce.map(|_| 1), + }, + logs_bloom: receipt.logs_bloom, + }) + } else { + self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }) }; + #[cfg(not(feature = "optimism"))] + let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx_type, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + }); + + self.receipts.push(receipt); + self.evm.db_mut().commit(state); + + GasOutput::new(gas_used) + } - let block = Block::new(partial_header, transactions.clone()); - let block = BlockInfo { block, transactions: transaction_infos, receipts }; - ExecutedTransactions { block, included, invalid } + fn finish( + self, + ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> + { + Ok(( + self.evm, + BlockExecutionResult { + receipts: self.receipts, + requests: Default::default(), + gas_used: self.gas_used, + blob_gas_used: self.blob_gas_used, + }, + )) } - fn env_for(&self, tx: &PendingTransaction) -> Env { - let mut tx_env = tx.to_revm_tx_env(); + fn set_state_hook(&mut self, hook: Option>) { + self.state_hook = hook; + } - if self.optimism { - tx_env.enveloped_tx = Some(alloy_rlp::encode(&tx.transaction.transaction).into()); - } + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } - Env::new(self.cfg_env.clone(), self.block_env.clone(), tx_env, self.optimism) + fn evm(&self) -> &Self::Evm { + &self.evm + } + + fn receipts(&self) -> &[FoundryReceiptEnvelope] { + &self.receipts } } -/// Represents the result of a single transaction execution attempt -#[derive(Debug)] -pub enum TransactionExecutionOutcome { - /// Transaction successfully executed - Executed(ExecutedTransaction), - /// Invalid transaction not executed - Invalid(Arc, InvalidTransactionError), - /// Execution skipped because could exceed gas limit - Exhausted(Arc), - /// Execution skipped because it exceeded the blob gas limit - BlobGasExhausted(Arc), - /// When an error occurred during execution - DatabaseError(Arc, DatabaseError), +/// Result of executing pool transactions against a block executor. +pub struct ExecutedPoolTransactions { + /// Successfully included transactions. + pub included: Vec>>, + /// Transactions that failed validation. + pub invalid: Vec>>, + /// Transactions skipped because they're not yet valid (e.g., valid_after in the future). + /// These remain in the pool and should be retried later. + pub not_yet_valid: Vec>>, + /// Per-transaction execution info. + pub tx_info: Vec, + /// The raw pending transactions that were included (in order). + pub txs: Vec>, } -impl Iterator for &mut TransactionExecutor<'_, DB, V> { - type Item = TransactionExecutionOutcome; +/// Gas-related configuration for pool transaction execution. +/// +/// Bundles parameters that cannot be derived from the generic `Evm` trait +/// (which doesn't expose `cfg()`), so callers construct this from `EvmEnv` +/// before calling [`execute_pool_transactions`]. +pub struct PoolTxGasConfig { + pub disable_block_gas_limit: bool, + pub tx_gas_limit_cap: Option, + pub tx_gas_limit_cap_resolved: u64, + pub max_blob_gas_per_block: u64, + pub is_cancun: bool, +} - fn next(&mut self) -> Option { - let transaction = self.pending.next()?; - let sender = *transaction.pending_transaction.sender(); - let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) { - Ok(account) => account, - Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)), +/// Executes pool transactions against a block executor, handling validation, +/// execution, commit, inspector drain, and result collection. +/// +/// This is the shared core of `do_mine_block` and `with_pending_block`. +#[allow(clippy::type_complexity)] +pub fn execute_pool_transactions( + executor: &mut B, + pool_transactions: &[Arc>], + gas_config: &PoolTxGasConfig, + inspector_config: &InspectorTxConfig, + cheats: &CheatsManager, + validator: &dyn Fn( + &PendingTransaction, + &AccountInfo, + ) -> Result<(), InvalidTransactionError>, +) -> ExecutedPoolTransactions +where + B: BlockExecutor< + Transaction = FoundryTxEnvelope, + Evm: Evm, + >, + B::Receipt: TxReceipt, + ::HaltReason: Clone + IntoInstructionResult, + ::Tx: FromTxWithEncoded + FoundryTransaction, +{ + let gas_limit = executor.evm().block().gas_limit(); + + let mut included = Vec::new(); + let mut invalid = Vec::new(); + let mut not_yet_valid = Vec::new(); + let mut tx_info: Vec = Vec::new(); + let mut transactions = Vec::new(); + let mut blob_gas_used = 0u64; + + for pool_tx in pool_transactions { + let pending = &pool_tx.pending_transaction; + let sender = *pending.sender(); + let block_timestamp = executor.evm().block().timestamp(); + + if let FoundryTxEnvelope::Tempo(aa_tx) = pending.transaction.as_ref() + && let Some(valid_after) = aa_tx.tx().valid_after + && U256::from(valid_after.get()) > block_timestamp + { + trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash()); + not_yet_valid.push(pool_tx.clone()); + continue; + } + + let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default()) + { + Ok(acc) => acc, + Err(err) => { + trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash()); + continue; + } }; - let env = self.env_for(&transaction.pending_transaction); - // check that we comply with the block's gas limit, if not disabled - let max_gas = self.gas_used.saturating_add(env.tx.base.gas_limit); - if !env.evm_env.cfg_env.disable_block_gas_limit && max_gas > env.evm_env.block_env.gas_limit + let tx_env = + build_tx_env_for_pending::::Tx>(pending, cheats); + + // Gas limit checks + let cumulative_gas = + executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); + let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); + if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit { + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction"); + continue; + } + + // Osaka EIP-7825 tx gas limit cap check + if gas_config.tx_gas_limit_cap.is_none() + && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved { - return Some(TransactionExecutionOutcome::Exhausted(transaction)) + trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction"); + continue; } - // check that we comply with the block's blob gas limit - let max_blob_gas = self.blob_gas_used.saturating_add( - transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0), - ); - if max_blob_gas > self.blob_params.max_blob_gas_per_block() { - return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) + // Blob gas check + let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); + if blob_gas_used.saturating_add(tx_blob_gas) > gas_config.max_blob_gas_per_block { + trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction"); + continue; } - // validate before executing - if let Err(err) = self.validator.validate_pool_transaction_for( - &transaction.pending_transaction, - &account, - &env, - ) { - warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err); - return Some(TransactionExecutionOutcome::Invalid(transaction, err)); + // Validate + if let Err(err) = validator(pending, &account) { + warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err); + invalid.push(pool_tx.clone()); + continue; } let nonce = account.nonce; - let mut inspector = AnvilInspector::default().with_tracing(); - if self.enable_steps_tracing { - inspector = inspector.with_steps_tracing(); - } - if self.print_logs { - inspector = inspector.with_log_collector(); - } - if self.print_traces { - inspector = inspector.with_trace_printer(); - } + let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); + trace!(target: "backend", "[{:?}] executing", pool_tx.hash()); + match executor.execute_transaction_without_commit((tx_env, recovered)) { + Ok(result) => { + let exec_result = result.result().result.clone(); + let gas_used = result.result().result.tx_gas_used(); - let exec_result = { - let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); + executor.commit_transaction(result); - if self.odyssey { - inject_precompiles(&mut evm, vec![P256VERIFY]); - } + let traces = + executor.evm_mut().inspector_mut().finish_transaction(inspector_config); - if let Some(factory) = &self.precompile_factory { - inject_precompiles(&mut evm, factory.precompiles()); - } + if gas_config.is_cancun { + blob_gas_used = blob_gas_used.saturating_add(tx_blob_gas); + } - trace!(target: "backend", "[{:?}] executing", transaction.hash()); - // transact and commit the transaction - match evm.transact_commit(env.tx) { - Ok(exec_result) => exec_result, - Err(err) => { - warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); - match err { - EVMError::Database(err) => { - return Some(TransactionExecutionOutcome::DatabaseError( - transaction, - err, - )) - } - EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid( - transaction, - err.into(), - )) - } - // This will correspond to prevrandao not set, and it should never happen. - // If it does, it's a bug. - e => panic!("failed to execute transaction: {e}"), + let (exit_reason, out, _logs) = match exec_result { + ExecutionResult::Success { reason, logs, output, .. } => { + (reason.into(), Some(output), logs) } - } - } - }; + ExecutionResult::Revert { output, .. } => { + (InstructionResult::Revert, Some(Output::Call(output)), Vec::new()) + } + ExecutionResult::Halt { reason, .. } => { + (reason.into_instruction_result(), None, Vec::new()) + } + }; - if self.print_traces { - inspector.print_traces(); - } - inspector.print_logs(); + if exit_reason == InstructionResult::OutOfGas { + warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash()); + } - let (exit_reason, gas_used, out, logs) = match exec_result { - ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (reason.into(), gas_used, Some(output), Some(logs)) + trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out); + trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used); + + let contract_address = pending.transaction.to().is_none().then(|| { + let addr = sender.create(nonce); + trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); + addr + }); + + // TODO: replace `TransactionInfo` with alloy receipt/transaction types + let transaction_index = tx_info.len() as u64; + let info = TransactionInfo { + transaction_hash: pool_tx.hash(), + transaction_index, + from: sender, + to: pending.transaction.to(), + contract_address, + traces, + exit: exit_reason, + out: out.map(Output::into_data), + nonce, + gas_used, + }; + + included.push(pool_tx.clone()); + tx_info.push(info); + transactions.push(pending.transaction.clone()); } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) - } - ExecutionResult::Halt { reason, gas_used } => { - (op_haltreason_to_instruction_result(reason), gas_used, None, None) + Err(err) => { + if err.as_validation().is_some() { + warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err); + invalid.push(pool_tx.clone()); + } else { + trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash()); + } } - }; - - if exit_reason == InstructionResult::OutOfGas { - // this currently useful for debugging estimations - warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash()) } - - trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); - - // Track the total gas used for total gas per block checks - self.gas_used = self.gas_used.saturating_add(gas_used); - - // Track the total blob gas used for total blob gas per blob checks - if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() { - self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas); - } - - trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used); - - let tx = ExecutedTransaction { - transaction, - exit_reason, - out, - gas_used, - logs: logs.unwrap_or_default(), - traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(), - nonce, - }; - - Some(TransactionExecutionOutcome::Executed(tx)) } -} -/// Inserts all logs into the bloom -fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { - for log in logs { - bloom.accrue(BloomInput::Raw(&log.address[..])); - for topic in log.topics() { - bloom.accrue(BloomInput::Raw(&topic[..])); - } - } + ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions } } -/// Creates a database with given database and inspector, optionally enabling odyssey features. -pub fn new_evm_with_inspector( - db: DB, - env: &Env, - inspector: I, -) -> EitherEvm +/// Builds the EVM transaction env from a pending pool transaction. +pub fn build_tx_env_for_pending(tx: &PendingTransaction, cheats: &CheatsManager) -> T where - DB: Database, - I: Inspector> + Inspector>, + Tx: Transaction + Encodable2718, + T: FromTxWithEncoded + FoundryTransaction, { - if env.is_optimism { - let op_cfg = env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS); - let op_context = OpContext { - journaled_state: { - let mut journal = Journal::new(db); - // Converting SpecId into OpSpecId - journal.set_spec_id(env.evm_env.cfg_env.spec); - journal - }, - block: env.evm_env.block_env.clone(), - cfg: op_cfg.clone(), - tx: env.tx.clone(), - chain: L1BlockInfo::default(), - local: LocalContext::default(), - error: Ok(()), - }; - - let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles(); - let op_evm = op_revm::OpEvm(RevmEvm::new_with_inspector( - op_context, - inspector, - EthInstructions::default(), - PrecompilesMap::from_static(op_precompiles), - )); - - let op = OpEvm::new(op_evm, true); - - EitherEvm::Op(op) - } else { - let spec = env.evm_env.cfg_env.spec; - let eth_context = EthEvmContext { - journaled_state: { - let mut journal = Journal::new(db); - journal.set_spec_id(spec); - journal - }, - block: env.evm_env.block_env.clone(), - cfg: env.evm_env.cfg_env.clone(), - tx: env.tx.base.clone(), - chain: (), - local: LocalContext::default(), - error: Ok(()), - }; - - let eth_precompiles = EthPrecompiles { - precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), - spec, - } - .precompiles; - let eth_evm = RevmEvm::new_with_inspector( - eth_context, - inspector, - EthInstructions::default(), - PrecompilesMap::from_static(eth_precompiles), - ); - - let eth = EthEvm::new(eth_evm, true); - - EitherEvm::Eth(eth) + let encoded = tx.transaction.encoded_2718().into(); + let mut tx_env: T = + FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded); + + if let Some(signed_auths) = tx.transaction.authorization_list() + && cheats.has_recover_overrides() + { + let auth_list = tx_env.authorization_list_mut(); + let cheated_auths = signed_auths + .iter() + .zip(take(auth_list)) + .map(|(signed_auth, either_auth)| { + either_auth.right_and_then(|recovered_auth| { + if recovered_auth.authority().is_none() + && let Ok(signature) = signed_auth.signature() + && let Some(override_addr) = + cheats.get_recover_override(&signature.as_bytes().into()) + { + Either::Right(RecoveredAuthorization::new_unchecked( + recovered_auth.into_parts().0, + RecoveredAuthority::Valid(override_addr), + )) + } else { + Either::Right(recovered_auth) + } + }) + }) + .collect(); + *tx_env.authorization_list_mut() = cheated_auths; } -} -/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. -pub fn new_evm_with_inspector_ref<'db, DB, I>( - db: &'db DB, - env: &Env, - inspector: &'db mut I, -) -> EitherEvm, &'db mut I, PrecompilesMap> -where - DB: DatabaseRef + 'db + ?Sized, - I: Inspector>> - + Inspector>>, - WrapDatabaseRef<&'db DB>: Database, -{ - new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) + tx_env } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 841d740abc44f..4f260023602f4 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,34 +1,35 @@ //! Support for forking off another client use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; -use alloy_consensus::Account; +use alloy_consensus::{BlockHeader, TrieAccount}; use alloy_eips::eip2930::AccessListResult; -use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse}; +use alloy_network::{ + AnyNetwork, AnyRpcBlock, BlockResponse, Network, TransactionResponse, + primitives::HeaderResponse, +}; use alloy_primitives::{ - map::{FbHashMap, HashMap}, - Address, Bytes, StorageValue, B256, U256, + Address, B256, Bytes, StorageValue, U256, + map::{FbHashMap, HashMap, HashSet}, }; use alloy_provider::{ - ext::{DebugApi, TraceApi}, Provider, + ext::{DebugApi, TraceApi}, }; use alloy_rpc_types::{ - request::TransactionRequest, + BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, + FeeHistory, Filter, Log, simulate::{SimulatePayload, SimulatedBlock}, trace::{ - geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace as Trace, + geth::{GethDebugTracingOptions, GethTrace, TraceResult}, + parity::{LocalizedTransactionTrace as Trace, TraceResultsWithTransactionHash, TraceType}, }, - BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, - FeeHistory, Filter, Log, }; -use alloy_serde::WithOtherFields; use alloy_transport::TransportError; -use anvil_core::eth::transaction::{convert_to_anvil_receipt, ReceiptResponse}; use foundry_common::provider::{ProviderBuilder, RetryProvider}; +use foundry_primitives::{FoundryTxEnvelope, FoundryTxReceipt}; use parking_lot::{ - lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, + lock_api::{RwLockReadGuard, RwLockWriteGuard}, }; use revm::context_interface::block::BlobExcessGasAndPrice; use std::{sync::Arc, time::Duration}; @@ -39,73 +40,23 @@ use tokio::sync::RwLock as AsyncRwLock; /// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively /// fetch the requested data from the remote client, if it wasn't already fetched. #[derive(Clone, Debug)] -pub struct ClientFork { +pub struct ClientFork { /// Contains the cached data - pub storage: Arc>, + pub storage: Arc>>, /// contains the info how the fork is configured // Wrapping this in a lock, ensures we can update this on the fly via additional custom RPC // endpoints - pub config: Arc>, + pub config: Arc>>, /// This also holds a handle to the underlying database pub database: Arc>>, } -impl ClientFork { +impl ClientFork { /// Creates a new instance of the fork - pub fn new(config: ClientForkConfig, database: Arc>>) -> Self { + pub fn new(config: ClientForkConfig, database: Arc>>) -> Self { Self { storage: Default::default(), config: Arc::new(RwLock::new(config)), database } } - /// Reset the fork to a fresh forked state, and optionally update the fork config - pub async fn reset( - &self, - url: Option, - block_number: impl Into, - ) -> Result<(), BlockchainError> { - let block_number = block_number.into(); - { - self.database - .write() - .await - .maybe_reset(url.clone(), block_number) - .map_err(BlockchainError::Internal)?; - } - - if let Some(url) = url { - self.config.write().update_url(url)?; - let override_chain_id = self.config.read().override_chain_id; - let chain_id = if let Some(chain_id) = override_chain_id { - chain_id - } else { - self.provider().get_chain_id().await? - }; - self.config.write().chain_id = chain_id; - } - - let provider = self.provider(); - let block = - provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?; - let block_hash = block.header.hash; - let timestamp = block.header.timestamp; - let base_fee = block.header.base_fee_per_gas; - let total_difficulty = block.header.total_difficulty.unwrap_or_default(); - - let number = block.header.number; - self.config.write().update_block( - number, - block_hash, - timestamp, - base_fee.map(|g| g as u128), - total_difficulty, - ); - - self.clear_cached_storage(); - - self.database.write().await.insert_block_hash(U256::from(number), block_hash); - - Ok(()) - } - /// Removes all data cached from previous responses pub fn clear_cached_storage(&self) { self.storage.write().clear() @@ -146,23 +97,23 @@ impl ClientFork { self.config.read().block_hash } - pub fn eth_rpc_url(&self) -> String { - self.config.read().eth_rpc_url.clone() + pub fn eth_rpc_url(&self) -> Option { + self.config.read().eth_rpc_url().map(|s| s.to_string()) } pub fn chain_id(&self) -> u64 { self.config.read().chain_id } - fn provider(&self) -> Arc { + fn provider(&self) -> Arc> { self.config.read().provider.clone() } - fn storage_read(&self) -> RwLockReadGuard<'_, RawRwLock, ForkedStorage> { + fn storage_read(&self) -> RwLockReadGuard<'_, RawRwLock, ForkedStorage> { self.storage.read() } - fn storage_write(&self) -> RwLockWriteGuard<'_, RawRwLock, ForkedStorage> { + fn storage_write(&self) -> RwLockWriteGuard<'_, RawRwLock, ForkedStorage> { self.storage.write() } @@ -186,55 +137,6 @@ impl ClientFork { self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await } - /// Sends `eth_call` - pub async fn call( - &self, - request: &WithOtherFields, - block: Option, - ) -> Result { - let block = block.unwrap_or(BlockNumber::Latest); - let res = self.provider().call(request.clone()).block(block.into()).await?; - - Ok(res) - } - - /// Sends `eth_simulateV1` - pub async fn simulate_v1( - &self, - request: &SimulatePayload, - block: Option, - ) -> Result>, TransportError> { - let mut simulate_call = self.provider().simulate(request); - if let Some(n) = block { - simulate_call = simulate_call.number(n.as_number().unwrap()); - } - - let res = simulate_call.await?; - - Ok(res) - } - - /// Sends `eth_estimateGas` - pub async fn estimate_gas( - &self, - request: &WithOtherFields, - block: Option, - ) -> Result { - let block = block.unwrap_or_default(); - let res = self.provider().estimate_gas(request.clone()).block(block.into()).await?; - - Ok(res as u128) - } - - /// Sends `eth_createAccessList` - pub async fn create_access_list( - &self, - request: &WithOtherFields, - block: Option, - ) -> Result { - self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await - } - pub async fn storage_at( &self, address: Address, @@ -274,7 +176,7 @@ impl ClientFork { let code = self.provider().get_code_at(address).block_id(block_id).await?; let mut storage = self.storage_write(); - storage.code_at.insert((address, blocknumber), code.clone().0.into()); + storage.code_at.insert((address, blocknumber), code.clone()); Ok(code) } @@ -297,76 +199,11 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_account={:?}", address); self.provider().get_account(address).block_id(blocknumber.into()).await } - pub async fn transaction_by_block_number_and_index( - &self, - number: u64, - index: usize, - ) -> Result, TransportError> { - if let Some(block) = self.block_by_number(number).await? { - match block.transactions() { - BlockTransactions::Full(txs) => { - if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())); - } - } - BlockTransactions::Hashes(hashes) => { - if let Some(tx_hash) = hashes.get(index) { - return self.transaction_by_hash(*tx_hash).await; - } - } - // TODO(evalir): Is it possible to reach this case? Should we support it - BlockTransactions::Uncle => panic!("Uncles not supported"), - } - } - Ok(None) - } - - pub async fn transaction_by_block_hash_and_index( - &self, - hash: B256, - index: usize, - ) -> Result, TransportError> { - if let Some(block) = self.block_by_hash(hash).await? { - match block.transactions() { - BlockTransactions::Full(txs) => { - if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())); - } - } - BlockTransactions::Hashes(hashes) => { - if let Some(tx_hash) = hashes.get(index) { - return self.transaction_by_hash(*tx_hash).await; - } - } - // TODO(evalir): Is it possible to reach this case? Should we support it - BlockTransactions::Uncle => panic!("Uncles not supported"), - } - } - Ok(None) - } - - pub async fn transaction_by_hash( - &self, - hash: B256, - ) -> Result, TransportError> { - trace!(target: "backend::fork", "transaction_by_hash={:?}", hash); - if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() { - return Ok(tx); - } - - let tx = self.provider().get_transaction_by_hash(hash).await?; - if let Some(tx) = tx.clone() { - let mut storage = self.storage_write(); - storage.transactions.insert(hash, tx); - } - Ok(tx) - } - pub async fn trace_transaction(&self, hash: B256) -> Result, TransportError> { if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() { return Ok(traces); @@ -397,6 +234,44 @@ impl ClientFork { Ok(trace) } + pub async fn debug_code_by_hash( + &self, + code_hash: B256, + block_id: Option, + ) -> Result, TransportError> { + self.provider().debug_code_by_hash(code_hash, block_id).await + } + + pub async fn debug_trace_block_by_hash( + &self, + block_hash: B256, + opts: GethDebugTracingOptions, + ) -> Result, TransportError> { + if let Some(traces) = self.storage_read().geth_block_traces.get(&block_hash).cloned() { + return Ok(traces); + } + + let trace_results = self.provider().debug_trace_block_by_hash(block_hash, opts).await?; + + let mut storage = self.storage_write(); + storage.geth_block_traces.insert(block_hash, trace_results.clone()); + + Ok(trace_results) + } + + pub async fn debug_trace_block_by_number( + &self, + number: u64, + opts: GethDebugTracingOptions, + ) -> Result, TransportError> { + if let Ok(Some(block)) = self.provider().get_block_by_number(number.into()).await { + let block_hash = block.header().hash(); + return self.debug_trace_block_by_hash(block_hash, opts).await; + } + + self.provider().debug_trace_block_by_number(number.into(), opts).await + } + pub async fn trace_block(&self, number: u64) -> Result, TransportError> { if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() { return Ok(traces); @@ -411,68 +286,184 @@ impl ClientFork { Ok(traces) } - pub async fn transaction_receipt( + pub async fn trace_replay_block_transactions( &self, - hash: B256, - ) -> Result, BlockchainError> { - if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() { - return Ok(Some(receipt)); + number: u64, + trace_types: HashSet, + ) -> Result, TransportError> { + // Forward to upstream provider for historical blocks + let params = (number, trace_types.iter().map(|t| format!("{t:?}")).collect::>()); + self.provider().raw_request("trace_replayBlockTransactions".into(), params).await + } + + /// Reset the fork to a fresh forked state, and optionally update the fork config + pub async fn reset( + &self, + urls: Vec, + block_number: impl Into, + ) -> Result<(), BlockchainError> { + let block_number = block_number.into(); + { + self.database + .write() + .await + .maybe_reset(urls.clone(), block_number) + .map_err(BlockchainError::Internal)?; } - if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? { - let receipt = - convert_to_anvil_receipt(receipt).ok_or(BlockchainError::FailedToDecodeReceipt)?; - let mut storage = self.storage_write(); - storage.transaction_receipts.insert(hash, receipt.clone()); - return Ok(Some(receipt)); + if !urls.is_empty() { + self.config.write().update_urls(urls)?; + let override_chain_id = self.config.read().override_chain_id; + let chain_id = if let Some(chain_id) = override_chain_id { + chain_id + } else { + self.provider().get_chain_id().await? + }; + self.config.write().chain_id = chain_id; } - Ok(None) + let provider = self.provider(); + let block = + provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?; + let block_hash = block.header().hash(); + let timestamp = block.header().timestamp(); + let base_fee = block.header().base_fee_per_gas(); + let total_difficulty = block.header().difficulty(); + + let number = block.header().number(); + self.config.write().update_block( + number, + block_hash, + timestamp, + base_fee.map(|g| g as u128), + total_difficulty, + ); + + self.clear_cached_storage(); + + self.database.write().await.insert_block_hash(U256::from(number), block_hash); + + Ok(()) } - pub async fn block_receipts( + /// Sends `eth_call` + pub async fn call( &self, - number: u64, - ) -> Result>, BlockchainError> { - if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { - return Ok(receipts); + request: &N::TransactionRequest, + block: Option, + ) -> Result { + let block = block.unwrap_or(BlockNumber::Latest); + let res = self.provider().call(request.clone()).block(block.into()).await?; + + Ok(res) + } + + /// Sends `eth_simulateV1` + pub async fn simulate_v1( + &self, + request: &SimulatePayload, + block: Option, + ) -> Result>, TransportError> { + let mut simulate_call = self.provider().simulate(request); + if let Some(n) = block { + simulate_call = simulate_call.number(n.as_number().unwrap()); } - // TODO Needs to be removed. - // Since alloy doesn't indicate in the result whether the block exists, - // this is being temporarily implemented in anvil. - if self.predates_fork_inclusive(number) { - let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?; - let receipts = receipts - .map(|r| { - r.into_iter() - .map(|r| { - convert_to_anvil_receipt(r) - .ok_or(BlockchainError::FailedToDecodeReceipt) - }) - .collect::, _>>() - }) - .transpose()?; + let res = simulate_call.await?; - if let Some(receipts) = receipts.clone() { - let mut storage = self.storage_write(); - storage.block_receipts.insert(number, receipts); + Ok(res) + } + + /// Sends `eth_estimateGas` + pub async fn estimate_gas( + &self, + request: &N::TransactionRequest, + block: Option, + ) -> Result { + let block = block.unwrap_or_default(); + let res = self.provider().estimate_gas(request.clone()).block(block.into()).await?; + + Ok(res as u128) + } + + /// Sends `eth_createAccessList` + pub async fn create_access_list( + &self, + request: &N::TransactionRequest, + block: Option, + ) -> Result { + self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await + } + + pub async fn transaction_by_block_number_and_index( + &self, + number: u64, + index: usize, + ) -> Result, TransportError> { + let block = self.block_by_number(number).await?; + self.transaction_at_block_index(block, index).await + } + + pub async fn transaction_by_block_hash_and_index( + &self, + hash: B256, + index: usize, + ) -> Result, TransportError> { + let block = self.block_by_hash(hash).await?; + self.transaction_at_block_index(block, index).await + } + + async fn transaction_at_block_index( + &self, + block: Option, + index: usize, + ) -> Result, TransportError> { + if let Some(block) = block { + match block.transactions() { + BlockTransactions::Full(txs) => { + if let Some(tx) = txs.get(index) { + return Ok(Some(tx.clone())); + } + } + BlockTransactions::Hashes(hashes) => { + if let Some(tx_hash) = hashes.get(index) { + return self.transaction_by_hash(*tx_hash).await; + } + } + BlockTransactions::Uncle => {} } + } + Ok(None) + } - return Ok(receipts); + pub async fn transaction_by_hash( + &self, + hash: B256, + ) -> Result, TransportError> { + trace!(target: "backend::fork", "transaction_by_hash={:?}", hash); + if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() { + return Ok(tx); } - Ok(None) + let tx = self.provider().get_transaction_by_hash(hash).await?; + if let Some(tx) = tx.clone() { + let mut storage = self.storage_write(); + storage.transactions.insert(hash, tx); + } + Ok(tx) } - pub async fn block_by_hash(&self, hash: B256) -> Result, TransportError> { + pub async fn block_by_hash( + &self, + hash: B256, + ) -> Result, TransportError> { if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() { - block.transactions.convert_to_hashes(); + block.transactions_mut().convert_to_hashes(); return Ok(Some(block)); } Ok(self.fetch_full_block(hash).await?.map(|mut b| { - b.transactions.convert_to_hashes(); + b.transactions_mut().convert_to_hashes(); b })) } @@ -480,9 +471,11 @@ impl ClientFork { pub async fn block_by_hash_full( &self, hash: B256, - ) -> Result, TransportError> { - if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(self.convert_to_full_block(block))); + ) -> Result, TransportError> { + if let Some(block) = self.storage_read().blocks.get(&hash).cloned() + && let Some(block) = self.convert_to_full_block(block) + { + return Ok(Some(block)); } self.fetch_full_block(hash).await } @@ -490,20 +483,20 @@ impl ClientFork { pub async fn block_by_number( &self, block_number: u64, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(mut block) = self .storage_read() .hashes .get(&block_number) .and_then(|hash| self.storage_read().blocks.get(hash).cloned()) { - block.transactions.convert_to_hashes(); + block.transactions_mut().convert_to_hashes(); return Ok(Some(block)); } let mut block = self.fetch_full_block(block_number).await?; if let Some(block) = &mut block { - block.transactions.convert_to_hashes(); + block.transactions_mut().convert_to_hashes(); } Ok(block) } @@ -511,15 +504,16 @@ impl ClientFork { pub async fn block_by_number_full( &self, block_number: u64, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self .storage_read() .hashes .get(&block_number) .copied() .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) + && let Some(block) = self.convert_to_full_block(block) { - return Ok(Some(self.convert_to_full_block(block))); + return Ok(Some(block)); } self.fetch_full_block(block_number).await @@ -528,10 +522,10 @@ impl ClientFork { async fn fetch_full_block( &self, block_id: impl Into, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self.provider().get_block(block_id.into()).full().await? { - let hash = block.header.hash; - let block_number = block.header.number; + let hash = block.header().hash(); + let block_number = block.header().number(); let mut storage = self.storage_write(); // also insert all transactions let block_txs = match block.transactions() { @@ -547,6 +541,74 @@ impl ClientFork { Ok(None) } + /// Converts a block of hashes into a full block + fn convert_to_full_block(&self, mut block: N::BlockResponse) -> Option { + let storage = self.storage.read(); + let transactions = block + .transactions() + .hashes() + .map(|hash| storage.transactions.get(&hash).cloned()) + .collect::>>()?; + *block.transactions_mut() = BlockTransactions::Full(transactions); + Some(block) + } +} + +impl ClientFork { + pub async fn transaction_receipt( + &self, + hash: B256, + ) -> Result, BlockchainError> { + if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() { + return Ok(Some(receipt)); + } + + if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? { + let receipt = FoundryTxReceipt::try_from(receipt) + .map_err(|_| BlockchainError::FailedToDecodeReceipt)?; + let mut storage = self.storage_write(); + storage.transaction_receipts.insert(hash, receipt.clone()); + return Ok(Some(receipt)); + } + + Ok(None) + } + + pub async fn block_receipts( + &self, + number: u64, + ) -> Result>, BlockchainError> { + if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { + return Ok(receipts); + } + + // TODO Needs to be removed. + // Since alloy doesn't indicate in the result whether the block exists, + // this is being temporarily implemented in anvil. + if self.predates_fork_inclusive(number) { + let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?; + let receipts = receipts + .map(|r| { + r.into_iter() + .map(|r| { + FoundryTxReceipt::try_from(r) + .map_err(|_| BlockchainError::FailedToDecodeReceipt) + }) + .collect::, _>>() + }) + .transpose()?; + + if let Some(receipts) = receipts.clone() { + let mut storage = self.storage_write(); + storage.block_receipts.insert(number, receipts); + } + + return Ok(receipts); + } + + Ok(None) + } + pub async fn uncle_by_block_hash_and_index( &self, hash: B256, @@ -574,8 +636,8 @@ impl ClientFork { block: AnyRpcBlock, index: usize, ) -> Result, TransportError> { - let block_hash = block.header.hash; - let block_number = block.header.number; + let block_hash = block.header().hash(); + let block_number = block.header().number(); if let Some(uncles) = self.storage_read().uncles.get(&block_hash) { return Ok(uncles.get(index).cloned()); } @@ -592,41 +654,22 @@ impl ClientFork { self.storage_write().uncles.insert(block_hash, uncles.clone()); Ok(uncles.get(index).cloned()) } - - /// Converts a block of hashes into a full block - fn convert_to_full_block(&self, mut block: AnyRpcBlock) -> AnyRpcBlock { - let storage = self.storage.read(); - let block_txs_len = match block.transactions { - BlockTransactions::Full(ref txs) => txs.len(), - BlockTransactions::Hashes(ref hashes) => hashes.len(), - // TODO: Should this be supported at all? - BlockTransactions::Uncle => 0, - }; - let mut transactions = Vec::with_capacity(block_txs_len); - for tx in block.transactions.hashes() { - if let Some(tx) = storage.transactions.get(&tx).cloned() { - transactions.push(tx); - } - } - // TODO: fix once blocks have generic transactions - block.inner.transactions = BlockTransactions::Full(transactions); - - block - } } /// Contains all fork metadata #[derive(Clone, Debug)] -pub struct ClientForkConfig { - pub eth_rpc_url: String, +pub struct ClientForkConfig { + /// All fork URLs. The first entry is the primary endpoint. + /// When multiple URLs are present, requests are distributed using + /// round-robin load balancing with retry-based failover. + pub fork_urls: Vec, /// The block number of the forked block pub block_number: u64, /// The hash of the forked block pub block_hash: B256, /// The transaction hash we forked off of, if any. pub transaction_hash: Option, - // TODO make provider agnostic - pub provider: Arc, + pub provider: Arc>, pub chain_id: u64, pub override_chain_id: Option, /// The timestamp for the forked block @@ -645,34 +688,49 @@ pub struct ClientForkConfig { pub backoff: Duration, /// available CUPS pub compute_units_per_second: u64, + /// Headers to include with RPC requests + pub headers: Vec, /// total difficulty of the chain until this block pub total_difficulty: U256, /// Transactions to force include in the forked chain - pub force_transactions: Option>, + pub force_transactions: Option>>, } -impl ClientForkConfig { - /// Updates the provider URL +impl ClientForkConfig { + /// Returns the primary RPC URL (first entry in `fork_urls`). + pub fn eth_rpc_url(&self) -> Option<&str> { + self.fork_urls.first().map(|s| s.as_str()) + } + + /// Updates the provider URLs /// /// # Errors /// /// This will fail if no new provider could be established (erroneous URL) - fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { - // let interval = self.provider.get_interval(); - self.provider = Arc::new( - ProviderBuilder::new(url.as_str()) - .timeout(self.timeout) - // .timeout_retry(self.retries) - .max_retry(self.retries) - .initial_backoff(self.backoff.as_millis() as u64) - .compute_units_per_second(self.compute_units_per_second) - .build() - .map_err(|_| BlockchainError::InvalidUrl(url.clone()))?, // .interval(interval), - ); - trace!(target: "fork", "Updated rpc url {}", url); - self.eth_rpc_url = url; + fn update_urls(&mut self, urls: Vec) -> Result<(), BlockchainError> { + let primary = urls.first().ok_or_else(|| { + BlockchainError::InvalidUrl("at least one fork URL required".to_string()) + })?; + + let builder = ProviderBuilder::::new(primary.as_str()) + .timeout(self.timeout) + .max_retry(self.retries) + .initial_backoff(self.backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .headers(self.headers.clone()); + + self.provider = Arc::new(if urls.len() > 1 { + builder + .build_fallback(urls.clone()) + .map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + } else { + builder.build().map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))? + }); + trace!(target: "fork", "Updated fork urls: {:?}", urls); + self.fork_urls = urls; Ok(()) } + /// Updates the block forked off `(block number, block hash, timestamp)` pub fn update_block( &mut self, @@ -694,22 +752,42 @@ impl ClientForkConfig { /// Contains cached state fetched to serve EthApi requests /// /// This is used as a cache so repeated requests to the same data are not sent to the remote client -#[derive(Clone, Debug, Default)] -pub struct ForkedStorage { - pub uncles: FbHashMap<32, Vec>, - pub blocks: FbHashMap<32, AnyRpcBlock>, +#[derive(Clone, Debug)] +pub struct ForkedStorage { + pub uncles: FbHashMap<32, Vec>, + pub blocks: FbHashMap<32, N::BlockResponse>, pub hashes: HashMap, - pub transactions: FbHashMap<32, AnyRpcTransaction>, - pub transaction_receipts: FbHashMap<32, ReceiptResponse>, + pub transactions: FbHashMap<32, N::TransactionResponse>, + pub transaction_receipts: FbHashMap<32, FoundryTxReceipt>, pub transaction_traces: FbHashMap<32, Vec>, pub logs: HashMap>, pub geth_transaction_traces: FbHashMap<32, GethTrace>, + pub geth_block_traces: FbHashMap<32, Vec>, pub block_traces: HashMap>, - pub block_receipts: HashMap>, + pub block_receipts: HashMap>, pub code_at: HashMap<(Address, u64), Bytes>, } -impl ForkedStorage { +impl Default for ForkedStorage { + fn default() -> Self { + Self { + uncles: Default::default(), + blocks: Default::default(), + hashes: Default::default(), + transactions: Default::default(), + transaction_receipts: Default::default(), + transaction_traces: Default::default(), + logs: Default::default(), + geth_transaction_traces: Default::default(), + geth_block_traces: Default::default(), + block_traces: Default::default(), + block_receipts: Default::default(), + code_at: Default::default(), + } + } +} + +impl ForkedStorage { /// Clears all data pub fn clear(&mut self) { // simply replace with a completely new, empty instance diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 597dd1ee2d179..a2db1c1d66e80 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -32,6 +32,7 @@ impl GenesisConfig { // we set this to empty so `Database::code_by_hash` doesn't get called code: Some(Default::default()), nonce: 0, + account_id: None, }; (address, info) }) @@ -65,6 +66,7 @@ impl GenesisConfig { nonce: nonce.unwrap_or_default(), code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), code, + account_id: None, } } } diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index 0f539f9373dcb..c739cd26d1de6 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -1,9 +1,10 @@ //! Handler that can get current storage related data use crate::mem::Backend; -use alloy_network::AnyRpcBlock; +use alloy_consensus::TxReceipt; +use alloy_network::{AnyRpcBlock, Network}; use alloy_primitives::B256; -use anvil_core::eth::{block::Block, transaction::TypedReceipt}; +use anvil_core::eth::block::Block; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. @@ -12,30 +13,20 @@ use std::{fmt, sync::Arc}; /// fetch ethereum storage related data // TODO(mattsee): once we have multiple Backend types, this should be turned into a trait #[derive(Clone)] -pub struct StorageInfo { - backend: Arc, +pub struct StorageInfo { + backend: Arc>, } -impl StorageInfo { - pub(crate) fn new(backend: Arc) -> Self { +impl StorageInfo { + pub(crate) const fn new(backend: Arc>) -> Self { Self { backend } } - /// Returns the receipts of the current block - pub fn current_receipts(&self) -> Option> { - self.backend.mined_receipts(self.backend.best_hash()) - } - /// Returns the current block pub fn current_block(&self) -> Option { self.backend.get_block(self.backend.best_number()) } - /// Returns the receipts of the block with the given hash - pub fn receipts(&self, hash: B256) -> Option> { - self.backend.mined_receipts(hash) - } - /// Returns the block with the given hash pub fn block(&self, hash: B256) -> Option { self.backend.get_block_by_hash(hash) @@ -48,7 +39,22 @@ impl StorageInfo { } } -impl fmt::Debug for StorageInfo { +impl StorageInfo +where + N::ReceiptEnvelope: TxReceipt, +{ + /// Returns the receipts of the current block + pub fn current_receipts(&self) -> Option> { + self.backend.mined_receipts(self.backend.best_hash()) + } + + /// Returns the receipts of the block with the given hash + pub fn receipts(&self, hash: B256) -> Option> { + self.backend.mined_receipts(hash) + } +} + +impl fmt::Debug for StorageInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StorageInfo").finish_non_exhaustive() } diff --git a/crates/anvil/src/eth/backend/mem/cache.rs b/crates/anvil/src/eth/backend/mem/cache.rs index d4b2779da32b6..524ef9cbd74bb 100644 --- a/crates/anvil/src/eth/backend/mem/cache.rs +++ b/crates/anvil/src/eth/backend/mem/cache.rs @@ -55,24 +55,21 @@ impl DiskStateCache { } } - /// Stores the snapshot for the given hash + /// Stores the snapshot for the given hash synchronously. /// - /// Note: this writes the state on a new spawned task - /// - /// Caution: this requires a running tokio Runtime. - pub fn write(&mut self, hash: B256, state: StateSnapshot) { - self.with_cache_file(hash, |file| { - tokio::task::spawn(async move { - match foundry_common::fs::write_json_file(&file, &state) { - Ok(_) => { - trace!(target: "backend", ?hash, "wrote state json file"); - } - Err(err) => { - error!(target: "backend", %err, ?hash, "Failed to load state snapshot"); - } - }; - }); - }); + /// Returns `true` if the write was successful, `false` otherwise. + pub fn write(&mut self, hash: B256, state: &StateSnapshot) -> bool { + self.with_cache_file(hash, |file| match foundry_common::fs::write_json_file(&file, state) { + Ok(_) => { + trace!(target: "backend", ?hash, "wrote state json file"); + true + } + Err(err) => { + error!(target: "backend", %err, ?hash, "Failed to write state snapshot"); + false + } + }) + .unwrap_or(false) } /// Loads the snapshot file for the given hash @@ -119,11 +116,7 @@ fn build_tmp_dir(p: Option<&Path>) -> io::Result { let prefix = now.format("anvil-state-%d-%m-%Y-%H-%M").to_string(); builder.prefix(&prefix); - if let Some(p) = p { - builder.tempdir_in(p) - } else { - builder.tempdir() - } + if let Some(p) = p { builder.tempdir_in(p) } else { builder.tempdir() } } #[cfg(test)] @@ -136,20 +129,12 @@ mod tests { let dir = tempdir().unwrap(); let p = dir.path(); let cache_dir = build_tmp_dir(Some(p)).unwrap(); - assert!(cache_dir - .path() - .file_name() - .unwrap() - .to_str() - .unwrap() - .starts_with("anvil-state-")); + assert!( + cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-") + ); let cache_dir = build_tmp_dir(None).unwrap(); - assert!(cache_dir - .path() - .file_name() - .unwrap() - .to_str() - .unwrap() - .starts_with("anvil-state-")); + assert!( + cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-") + ); } } diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 56a4944ada5a8..497e8ac8e4428 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -2,23 +2,22 @@ use crate::eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }; -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_network::Network; +use alloy_primitives::{Address, B256, U256, map::AddressMap}; use alloy_rpc_types::BlockId; use foundry_evm::{ - backend::{ - BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction, StateSnapshot, - }, + backend::{BlockchainDb, DatabaseResult, RevertStateSnapshotAction, StateSnapshot}, fork::database::ForkDbStateSnapshot, }; use revm::{ context::BlockEnv, - database::{Database, DatabaseRef, DbAccount}, + database::{Database, DbAccount}, state::AccountInfo, }; pub use foundry_evm::fork::database::ForkedDatabase; -impl Db for ForkedDatabase { +impl Db for ForkedDatabase { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.database_mut().insert_account(address, account) } @@ -88,12 +87,8 @@ impl Db for ForkedDatabase { } } -impl MaybeFullDatabase for ForkedDatabase { - fn as_dyn(&self) -> &dyn DatabaseRef { - self - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { +impl MaybeFullDatabase for ForkedDatabase { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.database().cache.accounts) } @@ -127,21 +122,27 @@ impl MaybeFullDatabase for ForkedDatabase { } } -impl MaybeFullDatabase for ForkDbStateSnapshot { - fn as_dyn(&self) -> &dyn DatabaseRef { - self - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { +impl MaybeFullDatabase for ForkDbStateSnapshot { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.local.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { - std::mem::take(&mut self.state_snapshot) + let mut state_snapshot = std::mem::take(&mut self.state_snapshot); + let local_state_snapshot = self.local.clear_into_state_snapshot(); + state_snapshot.accounts.extend(local_state_snapshot.accounts); + state_snapshot.storage.extend(local_state_snapshot.storage); + state_snapshot.block_hashes.extend(local_state_snapshot.block_hashes); + state_snapshot } fn read_as_state_snapshot(&self) -> StateSnapshot { - self.state_snapshot.clone() + let mut state_snapshot = self.state_snapshot.clone(); + let local_state_snapshot = self.local.read_as_state_snapshot(); + state_snapshot.accounts.extend(local_state_snapshot.accounts); + state_snapshot.storage.extend(local_state_snapshot.storage); + state_snapshot.block_hashes.extend(local_state_snapshot.block_hashes); + state_snapshot } fn clear(&mut self) { @@ -154,9 +155,9 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { } } -impl MaybeForkedDatabase for ForkedDatabase { - fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { - self.reset(url, block_number) +impl MaybeForkedDatabase for ForkedDatabase { + fn maybe_reset(&mut self, urls: Vec, block_number: BlockId) -> Result<(), String> { + self.reset(urls, block_number) } fn maybe_flush_cache(&self) -> Result<(), String> { diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 08a78b43406c7..1d06ad03cae2f 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -7,7 +7,7 @@ use crate::{ }, mem::state::state_root, }; -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, map::AddressMap}; use alloy_rpc_types::BlockId; use foundry_evm::backend::{BlockchainDb, DatabaseResult, StateSnapshot}; use revm::{ @@ -106,11 +106,7 @@ impl Db for MemDb { } impl MaybeFullDatabase for MemDb { - fn as_dyn(&self) -> &dyn DatabaseRef { - self - } - - fn maybe_as_full_db(&self) -> Option<&HashMap> { + fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.inner.cache.accounts) } @@ -132,7 +128,7 @@ impl MaybeFullDatabase for MemDb { } impl MaybeForkedDatabase for MemDb { - fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + fn maybe_reset(&mut self, _urls: Vec, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } @@ -148,7 +144,7 @@ impl MaybeForkedDatabase for MemDb { #[cfg(test)] mod tests { use super::*; - use alloy_primitives::{address, Bytes}; + use alloy_primitives::{Bytes, address}; use revm::{bytecode::Bytecode, primitives::KECCAK_EMPTY}; use std::collections::BTreeMap; @@ -168,6 +164,7 @@ mod tests { code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, + account_id: None, }, ); dump_db @@ -210,6 +207,7 @@ mod tests { code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, + account_id: None, }, ); diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 465febdcd28a4..bb63e74e912e0 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,26 +1,27 @@ //! Anvil specific [`revm::Inspector`] implementation use crate::eth::macros::node_info; -use alloy_evm::eth::EthEvmContext; use alloy_primitives::{Address, Log, U256}; use foundry_evm::{ - backend::DatabaseError, call_inspectors, decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, traces::{ - render_trace_arena_inner, CallTraceDecoder, SparsedTraceArena, TracingInspectorConfig, + CallTraceDecoder, CallTraceNode, SparsedTraceArena, TracingInspectorConfig, + render_trace_arena_inner, }, }; use revm::{ + Inspector, context::ContextTr, inspector::JournalExt, interpreter::{ - interpreter::EthInterpreter, CallInputs, CallOutcome, CreateInputs, CreateOutcome, - Interpreter, + CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, + interpreter::EthInterpreter, }, - Database, Inspector, }; +use revm_inspectors::transfer::TransferInspector; +use std::sync::Arc; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Clone, Debug, Default)] @@ -29,30 +30,73 @@ pub struct AnvilInspector { pub tracer: Option, /// Collects all `console.sol` logs pub log_collector: Option, + /// Collects all internal ETH transfers as ERC20 transfer events. + pub transfer: Option, +} + +/// Configuration for per-transaction inspector lifecycle. +#[derive(Clone, Debug)] +pub struct InspectorTxConfig { + /// Whether to print traces to stdout. + pub print_traces: bool, + /// Whether to print logs to stdout. + pub print_logs: bool, + /// Whether to enable step-level tracing (with state diffs). + pub enable_steps_tracing: bool, + /// Decoder for populating trace labels. + pub call_trace_decoder: Arc, } impl AnvilInspector { + /// Finish a transaction: print traces/logs, drain the tracer, and reset for the next tx. + /// + /// Returns the collected call trace nodes from the finished transaction. + pub fn finish_transaction(&mut self, config: &InspectorTxConfig) -> Vec { + // Print before draining so the tracer is still populated. + if config.print_traces { + self.print_traces(config.call_trace_decoder.clone()); + } + self.print_logs(); + + let traces = self.tracer.take().map(|t| t.into_traces().into_nodes()).unwrap_or_default(); + + // Reinstall tracer for next tx. + let tracing_config = if config.enable_steps_tracing { + TracingInspectorConfig::all().with_state_diffs() + } else { + TracingInspectorConfig::all().set_steps(false) + }; + self.tracer = Some(TracingInspector::new(tracing_config)); + + // Reset log collector for next tx. + if config.print_logs { + self.log_collector = Some(LogCollector::Capture { logs: Vec::new() }); + } + + traces + } + /// Called after the inspecting the evm /// /// This will log all `console.sol` logs pub fn print_logs(&self) { - if let Some(collector) = &self.log_collector { - print_logs(&collector.logs); + if let Some(LogCollector::Capture { logs }) = &self.log_collector { + print_logs(logs); } } /// Consumes the type and prints the traces. - pub fn into_print_traces(mut self) { + pub fn into_print_traces(mut self, decoder: Arc) { if let Some(a) = self.tracer.take() { - print_traces(a) + print_traces(a, decoder); } } /// Called after the inspecting the evm /// This will log all traces - pub fn print_traces(&self) { + pub fn print_traces(&self, decoder: Arc) { if let Some(a) = self.tracer.clone() { - print_traces(a) + print_traces(a, decoder); } } @@ -62,6 +106,7 @@ impl AnvilInspector { self } + /// Configures the `TracingInspector` [`revm::Inspector`] pub fn with_tracing_config(mut self, config: TracingInspectorConfig) -> Self { self.tracer = Some(TracingInspector::new(config)); self @@ -75,7 +120,13 @@ impl AnvilInspector { /// Configures the `Tracer` [`revm::Inspector`] with a log collector pub fn with_log_collector(mut self) -> Self { - self.log_collector = Some(Default::default()); + self.log_collector = Some(LogCollector::Capture { logs: Vec::new() }); + self + } + + /// Configures the `Tracer` [`revm::Inspector`] with a transfer event collector + pub fn with_transfers(mut self) -> Self { + self.transfer = Some(TransferInspector::new(false).with_logs(true)); self } @@ -93,26 +144,23 @@ impl AnvilInspector { /// # Panics /// /// If called outside tokio runtime -fn print_traces(tracer: TracingInspector) { +fn print_traces(tracer: TracingInspector, decoder: Arc) { let arena = tokio::task::block_in_place(move || { tokio::runtime::Handle::current().block_on(async move { let mut arena = tracer.into_traces(); - let decoder = CallTraceDecoder::new(); decoder.populate_traces(arena.nodes_mut()).await; arena }) }); let traces = SparsedTraceArena { arena, ignored: Default::default() }; - node_info!("Traces:"); - node_info!("{}", render_trace_arena_inner(&traces, false, true)); + let trace = render_trace_arena_inner(&traces, false, true); + node_info!(Traces = %format!("\n{}", trace)); } -impl Inspector for AnvilInspector +impl Inspector for AnvilInspector where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, + CTX: ContextTr, { fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut CTX) { call_inspectors!([&mut self.tracer], |inspector| { @@ -132,17 +180,24 @@ where }); } - fn log(&mut self, interp: &mut Interpreter, ecx: &mut CTX, log: Log) { + #[allow(clippy::redundant_clone)] + fn log(&mut self, ecx: &mut CTX, log: Log) { call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { - // TODO: rm the log.clone - inspector.log(interp, ecx, log.clone()); + inspector.log(ecx, log.clone()); + }); + } + + #[allow(clippy::redundant_clone)] + fn log_full(&mut self, interp: &mut Interpreter, ecx: &mut CTX, log: Log) { + call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { + inspector.log_full(interp, ecx, log.clone()); }); } fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { call_inspectors!( #[ret] - [&mut self.tracer, &mut self.log_collector], + [&mut self.tracer, &mut self.log_collector, &mut self.transfer], |inspector| inspector.call(ecx, inputs).map(Some), ); None @@ -155,11 +210,11 @@ where } fn create(&mut self, ecx: &mut CTX, inputs: &mut CreateInputs) -> Option { - if let Some(tracer) = &mut self.tracer { - if let Some(out) = tracer.create(ecx, inputs) { - return Some(out); - } - } + call_inspectors!( + #[ret] + [&mut self.tracer, &mut self.transfer], + |inspector| inspector.create(ecx, inputs).map(Some), + ); None } @@ -169,11 +224,10 @@ where } } - #[inline] fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - if let Some(tracer) = &mut self.tracer { - Inspector::>::selfdestruct(tracer, contract, target, value); - } + call_inspectors!([&mut self.tracer, &mut self.transfer], |inspector| { + Inspector::::selfdestruct(inspector, contract, target, value) + }); } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 321ed41ea3925..19f39f3d368de 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,14 +1,17 @@ //! In-memory blockchain backend. - use self::state::trie_storage; + use crate::{ + ForkChoice, NodeConfig, PrecompileFactory, config::PruneStateHistoryConfig, eth::{ backend::{ - cheats::CheatsManager, - db::{Db, MaybeFullDatabase, SerializableState}, - env::Env, - executor::{ExecutedTransactions, TransactionExecutor}, + cheats::{CheatEcrecover, CheatsManager}, + db::{AnvilCacheDB, Db, MaybeFullDatabase, SerializableState, StateDb}, + executor::{ + AnvilBlockExecutor, ExecutedPoolTransactions, PoolTxGasConfig, + execute_pool_transactions, + }, fork::ClientFork, genesis::GenesisConfig, mem::{ @@ -16,48 +19,54 @@ use crate::{ storage::MinedTransactionReceipt, }, notifications::{NewBlockNotification, NewBlockNotifications}, - time::{utc_from_secs, TimeManager}, + tempo::AnvilStorageProvider, + time::{TimeManager, utc_from_secs}, validate::TransactionValidator, }, error::{BlockchainError, ErrDetail, InvalidTransactionError}, fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, pool::transactions::PoolTransaction, - sign::build_typed_transaction, - util::get_precompiles_for, + sign::build_impersonated, }, - inject_precompiles, mem::{ - inspector::AnvilInspector, + inspector::{AnvilInspector, InspectorTxConfig}, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, - ForkChoice, NodeConfig, PrecompileFactory, }; use alloy_chains::NamedChain; use alloy_consensus::{ + Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait, + TrieAccount, TxEnvelope, TxReceipt, Typed2718, + constants::EMPTY_WITHDRAWALS, proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, - Account, BlockHeader, EnvKzgSettings, Header, Receipt, ReceiptWithBloom, Signed, - Transaction as TransactionTrait, TxEnvelope, }; use alloy_eips::{ - eip1559::BaseFeeParams, - eip2718::{ - EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, - LEGACY_TX_TYPE_ID, - }, - eip7840::BlobParams, + BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash, + eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, eip7910::SystemContract, +}; +use alloy_evm::{ + Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded, + block::{BlockExecutionResult, BlockExecutor, StateDB}, + eth::EthEvmContext, + overrides::{OverrideBlockHashes, apply_state_overrides}, + precompiles::{DynPrecompile, Precompile, PrecompilesMap}, }; -use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, Database, Evm}; use alloy_network::{ - AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, - EthereumWallet, UnknownTxEnvelope, UnknownTypedTransaction, + AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network, + NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction, }; +#[cfg(feature = "optimism")] +use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; use alloy_primitives::{ - address, hex, keccak256, logs_bloom, map::HashMap, utils::Unit, Address, Bytes, TxHash, TxKind, - B256, U256, U64, + Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, + map::{AddressMap, HashMap, HashSet}, }; use alloy_rpc_types::{ + AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, + Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, anvil::Forking, request::TransactionRequest, serde_helpers::JsonStorageKey, @@ -66,108 +75,165 @@ use alloy_rpc_types::{ trace::{ filter::TraceFilter, geth::{ - GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, - GethDebugTracingOptions, GethTrace, NoopFrame, + FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, + TraceResult, }, - parity::LocalizedTransactionTrace, + parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, - AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, - EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, - Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; -use alloy_signer::Signature; -use alloy_signer_local::PrivateKeySigner; -use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; +use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer}; use anvil_core::eth::{ - block::{Block, BlockInfo}, - transaction::{ - has_optimism_fields, transaction_request_to_typed, DepositReceipt, - MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, TransactionInfo, - TypedReceipt, TypedTransaction, - }, - wallet::{Capabilities, DelegationCapability, WalletCapabilities}, + block::{Block, BlockInfo, create_block}, + transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo}, }; use anvil_rpc::error::RpcError; use chrono::Datelike; use eyre::{Context, Result}; -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use flate2::{Compression, read::GzDecoder, write::GzEncoder}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, + core::precompiles::EC_RECOVER, decode::RevertDecoder, + hardfork::FoundryHardfork, inspectors::AccessListInspector, - traces::TracingInspectorConfig, + traces::{ + CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector, + TracingInspectorConfig, + }, + utils::{ + block_env_from_header, get_blob_base_fee_update_fraction, + get_blob_base_fee_update_fraction_by_spec_id, + }, }; -use foundry_evm_core::either_evm::EitherEvm; -use futures::channel::mpsc::{unbounded, UnboundedSender}; -use op_alloy_consensus::DEPOSIT_TX_TYPE_ID; -use op_revm::{ - transaction::deposit::DepositTransactionParts, OpContext, OpHaltReason, OpTransaction, +use foundry_evm_networks::NetworkConfigs; +#[cfg(feature = "optimism")] +use foundry_primitives::get_deposit_tx_parts; +use foundry_primitives::{ + FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, + FoundryTxReceipt, }; -use parking_lot::{Mutex, RwLock}; +use futures::channel::mpsc::{UnboundedSender, unbounded}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait}; +#[cfg(feature = "optimism")] +use op_revm::{OpSpecId, OpTransaction, transaction::deposit::DepositTransactionParts}; + +/// Side-channel container for OP-specific deposit info produced by +/// [`Backend::build_call_env`] and consumed by the OP transact path. +/// +/// When the `optimism` feature is enabled, this is an alias for +/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized +/// stand-in so the eth/tempo dispatch chain still type-checks. +#[cfg(feature = "optimism")] +type OpCallDepositInfo = DepositTransactionParts; +#[cfg(not(feature = "optimism"))] +#[derive(Default, Clone, Debug)] +struct OpCallDepositInfo; + +/// Marker trait that abstracts over the per-network inspector trait bounds +/// required by the in-memory backend. The OP bound is only included when the +/// `optimism` feature is enabled. +#[cfg(feature = "optimism")] +pub trait BackendInspector: + Inspector> + Inspector> + Inspector> +{ +} +#[cfg(feature = "optimism")] +impl BackendInspector for T where + T: Inspector> + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +pub trait BackendInspector: + Inspector> + Inspector> +{ +} +#[cfg(not(feature = "optimism"))] +impl BackendInspector for T where + T: Inspector> + Inspector> +{ +} +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ - context::{Block as RevmBlock, BlockEnv, TxEnv}, + DatabaseCommit, Inspector, + context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, - result::{ExecutionResult, Output, ResultAndState}, + result::{ExecutionResult, HaltReason, Output, ResultAndState}, }, - database::{CacheDB, DatabaseRef, WrapDatabaseRef}, + database::{CacheDB, DbAccount, WrapDatabaseRef}, interpreter::InstructionResult, - precompile::secp256r1::P256VERIFY, - primitives::{hardfork::SpecId, KECCAK_EMPTY}, + precompile::{PrecompileSpecId, Precompiles}, + primitives::{KECCAK_EMPTY, hardfork::SpecId}, state::AccountInfo, - DatabaseCommit, Inspector, }; -use revm_inspectors::transfer::TransferInspector; use std::{ collections::BTreeMap, + fmt::{self, Debug}, io::{Read, Write}, - ops::Not, + ops::{Mul, Not}, path::PathBuf, sync::Arc, time::Duration, }; -use storage::{Blockchain, MinedTransaction, DEFAULT_HISTORY_LIMIT}; +use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction}; +use tempo_chainspec::hardfork::TempoHardfork; +use tempo_evm::evm::TempoEvmFactory; +use tempo_precompiles::{ + storage::StorageCtx, + tip_fee_manager::{IFeeManager, TipFeeManager}, + tip20::{ISSUER_ROLE, ITIP20, TIP20Token}, +}; +use tempo_primitives::TEMPO_TX_TYPE_ID; +use tempo_revm::{ + TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv, evm::TempoContext, + gas_params::tempo_gas_params, +}; use tokio::sync::RwLock as AsyncRwLock; -use super::executor::new_evm_with_inspector_ref; - pub mod cache; pub mod fork_db; pub mod in_memory_db; pub mod inspector; +#[cfg(feature = "optimism")] +pub mod optimism; pub mod state; pub mod storage; +/// Helper trait that combines revm::DatabaseRef with Debug. +/// This is needed because alloy-evm requires Debug on Database implementations. +/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef. +pub trait DatabaseRef: revm::DatabaseRef + Debug {} +impl DatabaseRef for T where T: revm::DatabaseRef + Debug {} +impl DatabaseRef for dyn crate::eth::backend::db::Db {} + // Gas per transaction not creating a contract. pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. pub const MIN_CREATE_GAS: u128 = 53000; -// Executor -pub const EXECUTOR: Address = address!("0x6634F723546eCc92277e8a2F93d4f248bf1189ea"); -pub const EXECUTOR_PK: &str = "0x502d47e1421cb9abef497096728e69f07543232b93ef24de4998e18b5fd9ba0f"; -// P256 Batch Delegation Contract: https://odyssey-explorer.ithaca.xyz/address/0x35202a6E6317F3CC3a177EeEE562D3BcDA4a6FcC -pub const P256_DELEGATION_CONTRACT: Address = - address!("0x35202a6e6317f3cc3a177eeee562d3bcda4a6fcc"); -// Runtime code of the P256 delegation contract -pub const P256_DELEGATION_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610018575b361561001657005b005b5f3560e01c806309c5eabe146100c75780630cb6aaf1146100c257806330f6a8e5146100bd5780635fce1927146100b8578063641cdfe2146100b357806376ba882d146100ae5780638d80ff0a146100a9578063972ce4bc146100a4578063a78fc2441461009f578063a82e44e01461009a5763b34893910361000e576108e1565b6108b5565b610786565b610646565b6105ba565b610529565b6103f8565b6103a2565b61034c565b6102c0565b61020b565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176100fc57604052565b6100cc565b6080810190811067ffffffffffffffff8211176100fc57604052565b60a0810190811067ffffffffffffffff8211176100fc57604052565b90601f8019910116810190811067ffffffffffffffff8211176100fc57604052565b6040519061016a608083610139565b565b67ffffffffffffffff81116100fc57601f01601f191660200190565b9291926101948261016c565b916101a26040519384610139565b8294818452818301116101be578281602093845f960137010152565b5f80fd5b9080601f830112156101be578160206101dd93359101610188565b90565b60206003198201126101be576004359067ffffffffffffffff82116101be576101dd916004016101c2565b346101be57610219366101e0565b3033036102295761001690610ae6565b636f6a1b8760e11b5f5260045ffd5b634e487b7160e01b5f52603260045260245ffd5b5f54811015610284575f8080526005919091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630191565b610238565b8054821015610284575f52600560205f20910201905f90565b906040516102af816100e0565b602060018294805484520154910152565b346101be5760203660031901126101be576004355f548110156101be576102e69061024c565b5060ff815416600182015491610306600360ff60028401541692016102a2565b926040519215158352602083015260028110156103385760a09260209160408401528051606084015201516080820152f35b634e487b7160e01b5f52602160045260245ffd5b346101be575f3660031901126101be576020600254604051908152f35b6004359063ffffffff821682036101be57565b6064359063ffffffff821682036101be57565b6084359063ffffffff821682036101be57565b346101be5760203660031901126101be576103bb610369565b303303610229576103cb9061024c565b50805460ff19169055005b60609060231901126101be57602490565b60609060831901126101be57608490565b346101be5760803660031901126101be57610411610369565b60205f61041d366103d6565b60015461043161042c82610a0b565b600155565b60405184810191825260e086901b6001600160e01b031916602083015261046581602484015b03601f198101835282610139565b51902060ff61047660408401610a19565b161583146104fe576104b2601b925b85813591013590604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156104f9575f51306001600160a01b03909116036104ea576104df6100169161024c565b50805460ff19169055565b638baa579f60e01b5f5260045ffd5b610a27565b6104b2601c92610485565b60409060031901126101be57600490565b6044359060028210156101be57565b346101be5760803660031901126101be5761054336610509565b61054b61051a565b606435903033036102295761059192610580610587926040519461056e86610101565b60018652602086015260408501610a32565b36906105f3565b6060820152610a3e565b5f545f1981019081116105b55760405163ffffffff919091168152602090f35b0390f35b6109f7565b6100166105c6366101e0565b610ae6565b60409060231901126101be57604051906105e4826100e0565b60243582526044356020830152565b91908260409103126101be5760405161060b816100e0565b6020808294803584520135910152565b6084359081151582036101be57565b60a4359081151582036101be57565b359081151582036101be57565b346101be5760a03660031901126101be5760043567ffffffffffffffff81116101be576106779036906004016101c2565b610680366105cb565b61068861037c565b61069061061b565b906002546106a56106a082610a0b565b600255565b6040516106bb8161045788602083019586610b6a565b51902091610747575b6106d06106d69161024c565b50610b7b565b906106e86106e48351151590565b1590565b610738576020820151801515908161072e575b5061071f576107129260606106e493015191610ce3565b6104ea5761001690610ae6565b632572e3a960e01b5f5260045ffd5b905042115f6106fb565b637dd286d760e11b5f5260045ffd5b905f61077361045761076760209460405192839187830160209181520190565b60405191828092610b58565b039060025afa156104f9575f51906106c4565b346101be5760e03660031901126101be576107a036610509565b6107a861051a565b6064359060205f6107b8366103e7565b6001546107c761042c82610a0b565b60408051808601928352883560208401528589013591830191909152606082018790526107f78160808401610457565b51902060ff61080860408401610a19565b161583146108aa5760408051918252601b602083015282359082015290830135606082015280608081015b838052039060015afa156104f9575f51306001600160a01b03909116036104ea5761087a926105806105879261086761015b565b6001815294602086015260408501610a32565b6105b161089361088a5f54610ad8565b63ffffffff1690565b60405163ffffffff90911681529081906020820190565b610833601c92610485565b346101be575f3660031901126101be576020600154604051908152f35b359061ffff821682036101be57565b346101be5760c03660031901126101be5760043567ffffffffffffffff81116101be576109129036906004016101c2565b61091b366105cb565b906064359167ffffffffffffffff83116101be5760a060031984360301126101be576040516109498161011d565b836004013567ffffffffffffffff81116101be5761096d90600436918701016101c2565b8152602484013567ffffffffffffffff81116101be57840193366023860112156101be5760846109db916109ae610016973690602460048201359101610188565b60208501526109bf604482016108d2565b60408501526109d0606482016108d2565b606085015201610639565b60808201526109e861038f565b916109f161062a565b93610bc3565b634e487b7160e01b5f52601160045260245ffd5b5f1981146105b55760010190565b3560ff811681036101be5790565b6040513d5f823e3d90fd5b60028210156103385752565b5f54680100000000000000008110156100fc57806001610a6192015f555f610289565b610ac557610a7e82511515829060ff801983541691151516179055565b6020820151600182015560028101604083015160028110156103385761016a9360039260609260ff8019835416911617905501519101906020600191805184550151910155565b634e487b7160e01b5f525f60045260245ffd5b5f198101919082116105b557565b80519060205b828110610af857505050565b808201805160f81c600182015160601c91601581015160358201519384915f9493845f14610b4257505050506001146101be575b15610b3a5701605501610aec565b3d5f803e3d5ffd5b5f95508594506055019130811502175af1610b2c565b805191908290602001825e015f815290565b6020906101dd939281520190610b58565b90604051610b8881610101565b6060610bbe6003839560ff8154161515855260018101546020860152610bb860ff60028301541660408701610a32565b016102a2565b910152565b93909192600254610bd66106a082610a0b565b604051610bec8161045789602083019586610b6a565b51902091610c50575b6106d0610c019161024c565b91610c0f6106e48451151590565b6107385760208301518015159081610c46575b5061071f57610c399360606106e494015192610e0d565b6104ea5761016a90610ae6565b905042115f610c22565b905f610c7061045761076760209460405192839187830160209181520190565b039060025afa156104f9575f5190610bf5565b3d15610cad573d90610c948261016c565b91610ca26040519384610139565b82523d5f602084013e565b606090565b8051601f101561028457603f0190565b8051602010156102845760400190565b908151811015610284570160200190565b5f9291839260208251920151906020815191015191604051936020850195865260408501526060840152608083015260a082015260a08152610d2660c082610139565b519060145afa610d34610c83565b81610d74575b81610d43575090565b600160f81b91506001600160f81b031990610d6f90610d6190610cb2565b516001600160f81b03191690565b161490565b80516020149150610d3a565b60405190610d8f604083610139565b6015825274113a3cb832911d113bb2b130baba34371733b2ba1160591b6020830152565b9061016a6001610de3936040519485916c1131b430b63632b733b2911d1160991b6020840152602d830190610b58565b601160f91b815203601e19810185520183610139565b610e069060209392610b58565b9081520190565b92919281516025815110908115610f0a575b50610ef957610e2c610d80565b90610e596106e460208501938451610e53610e4c606089015161ffff1690565b61ffff1690565b91610f9b565b610f01576106e4610e8d610e88610457610e83610ea1956040519283916020830160209181520190565b611012565b610db3565b8351610e53610e4c604088015161ffff1690565b610ef9575f610eb96020925160405191828092610b58565b039060025afa156104f9575f610ee360209261076783519151610457604051938492888401610df9565b039060025afa156104f9576101dd915f51610ce3565b505050505f90565b50505050505f90565b610f2b9150610f1e610d616106e492610cc2565b6080850151151590610f31565b5f610e1f565b906001600160f81b0319600160f81b831601610f955780610f85575b610f8057601f60fb1b600160fb1b821601610f69575b50600190565b600160fc1b90811614610f7c575f610f63565b5f90565b505f90565b50600160fa1b8181161415610f4d565b50505f90565b80519282515f5b858110610fb457505050505050600190565b8083018084116105b5578281101561100757610fe56001600160f81b0319610fdc8488610cd2565b51169187610cd2565b516001600160f81b03191603610ffd57600101610fa2565b5050505050505f90565b505050505050505f90565b80516060929181611021575050565b9092506003600284010460021b604051937f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f603f52602085019282860191602083019460208284010190600460038351955f85525b0191603f8351818160121c16515f538181600c1c1651600153818160061c165160025316516003535f5181520190878210156110db5760049060039061109a565b5095505f93600393604092520160405206600204809303613d3d60f01b81525203825256fea26469706673582212200ba93b78f286a25ece47e9403c47be9862f9b8b70ba1a95098667b90c47308b064736f6c634300081a0033"); -// Experimental ERC20 -pub const EXP_ERC20_CONTRACT: Address = address!("0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E"); -// Runtime code of the experimental ERC20 contract -pub const EXP_ERC20_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610010575b005b5f3560e01c806306fdde03146106f7578063095ea7b31461068c57806318160ddd1461066757806323b872dd146105a15780632bb7c5951461050e578063313ce567146104f35780633644e5151461045557806340c10f191461043057806370a08231146103fe5780637ecebe00146103cc57806395d89b4114610366578063a9059cbb146102ea578063ad0c8fdd146102ad578063d505accf146100fb5763dd62ed3e0361000e57346100f75760403660031901126100f7576100d261075c565b6100da610772565b602052637f5e9f20600c525f5260206034600c2054604051908152f35b5f80fd5b346100f75760e03660031901126100f75761011461075c565b61011c610772565b6084359160643560443560ff851685036100f757610138610788565b60208101906e04578706572696d656e74455243323608c1b8252519020908242116102a0576040519360018060a01b03169460018060a01b03169565383775081901600e52855f5260c06020600c20958654957f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252602082019586528660408301967fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc688528b6060850198468a528c608087019330855260a08820602e527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9885252528688525260a082015220604e526042602c205f5260ff1660205260a43560405260c43560605260208060805f60015afa93853d5103610293577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92594602094019055856303faf4f960a51b176040526034602c2055a3005b63ddafbaef5f526004601cfd5b631a15a3cc5f526004601cfd5b5f3660031901126100f7576103e834023481046103e814341517156102d65761000e90336107ac565b634e487b7160e01b5f52601160045260245ffd5b346100f75760403660031901126100f75761030361075c565b602435906387a211a2600c52335f526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c335f51602061080d5f395f51905f52602080a3602060405160018152f35b63f4d678b85f526004601cfd5b346100f7575f3660031901126100f757604051604081019080821067ffffffffffffffff8311176103b8576103b491604052600381526204558560ec1b602082015260405191829182610732565b0390f35b634e487b7160e01b5f52604160045260245ffd5b346100f75760203660031901126100f7576103e561075c565b6338377508600c525f52602080600c2054604051908152f35b346100f75760203660031901126100f75761041761075c565b6387a211a2600c525f52602080600c2054604051908152f35b346100f75760403660031901126100f75761000e61044c61075c565b602435906107ac565b346100f7575f3660031901126100f757602060a0610471610788565b828101906e04578706572696d656e74455243323608c1b8252519020604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252838201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604082015246606082015230608082015220604051908152f35b346100f7575f3660031901126100f757602060405160128152f35b346100f75760203660031901126100f7576004356387a211a2600c52335f526020600c2090815490818111610359575f80806103e88487839688039055806805345cdf77eb68f44c54036805345cdf77eb68f44c5580835282335f51602061080d5f395f51905f52602083a304818115610598575b3390f11561058d57005b6040513d5f823e3d90fd5b506108fc610583565b346100f75760603660031901126100f7576105ba61075c565b6105c2610772565b604435908260601b33602052637f5e9f208117600c526034600c20908154918219610643575b506387a211a2915017600c526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c9060018060a01b03165f51602061080d5f395f51905f52602080a3602060405160018152f35b82851161065a57846387a211a293039055856105e8565b6313be252b5f526004601cfd5b346100f7575f3660031901126100f75760206805345cdf77eb68f44c54604051908152f35b346100f75760403660031901126100f7576106a561075c565b60243590602052637f5e9f20600c52335f52806034600c20555f52602c5160601c337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3602060405160018152f35b346100f7575f3660031901126100f7576103b4610712610788565b6e04578706572696d656e74455243323608c1b6020820152604051918291825b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f757565b602435906001600160a01b03821682036100f757565b604051906040820182811067ffffffffffffffff8211176103b857604052600f8252565b6805345cdf77eb68f44c548281019081106107ff576805345cdf77eb68f44c556387a211a2600c525f526020600c20818154019055602052600c5160601c5f5f51602061080d5f395f51905f52602080a3565b63e5cfe9575f526004601cfdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fbe302881d9891005ba1448ba48547cc1cb17dea1a5c4011dfcb035de325bb1d64736f6c634300081b0033"); pub type State = foundry_evm::utils::StateChangeset; /// A block request, which includes the Pool Transactions if it's Pending -#[derive(Debug)] -pub enum BlockRequest { - Pending(Vec>), +pub enum BlockRequest { + Pending(Vec>>), Number(u64), } -impl BlockRequest { - pub fn block_number(&self) -> BlockNumber { +impl fmt::Debug for BlockRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(), + Self::Number(n) => f.debug_tuple("Number").field(n).finish(), + } + } +} + +impl BlockRequest { + pub const fn block_number(&self) -> BlockNumber { match *self { Self::Pending(_) => BlockNumber::Pending, Self::Number(n) => BlockNumber::Number(n), @@ -176,8 +242,7 @@ impl BlockRequest { } /// Gives access to the [revm::Database] -#[derive(Clone)] -pub struct Backend { +pub struct Backend { /// Access to [`revm::Database`] abstraction. /// /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding @@ -199,11 +264,15 @@ pub struct Backend { /// executed. db: Arc>>, /// stores all block related data in memory. - blockchain: Blockchain, + blockchain: Blockchain, /// Historic states of previous blocks. states: Arc>, - /// Env data of the chain - env: Arc>, + /// EVM environment data of the chain (block env, cfg env). + evm_env: Arc>, + /// Network configuration (optimism, custom precompiles, etc.) + networks: NetworkConfigs, + /// The active hardfork. + hardfork: FoundryHardfork, /// This is set if this is currently forked off another client. fork: Arc>>, /// Provides time related info, like timestamp. @@ -221,266 +290,71 @@ pub struct Backend { enable_steps_tracing: bool, print_logs: bool, print_traces: bool, - odyssey: bool, + /// Recorder used for decoding traces, used together with print_traces + call_trace_decoder: Arc, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, - node_config: Arc>, + pub(crate) node_config: Arc>, /// Slots in an epoch slots_in_an_epoch: u64, /// Precompiles to inject to the EVM. precompile_factory: Option>, /// Prevent race conditions during mining mining: Arc>, - // === wallet === // - capabilities: Arc>, - executor_wallet: Arc>>, + /// Disable pool balance checks + disable_pool_balance_checks: bool, } -impl Backend { - /// Initialises the balance of the given accounts - #[expect(clippy::too_many_arguments)] - pub async fn with_genesis( - db: Arc>>, - env: Arc>, - genesis: GenesisConfig, - fees: FeeManager, - fork: Arc>>, - enable_steps_tracing: bool, - print_logs: bool, - print_traces: bool, - odyssey: bool, - prune_state_history_config: PruneStateHistoryConfig, - max_persisted_states: Option, - transaction_block_keeper: Option, - automine_block_time: Option, - cache_path: Option, - node_config: Arc>, - ) -> Result { - // if this is a fork then adjust the blockchain storage - let blockchain = if let Some(fork) = fork.read().as_ref() { - trace!(target: "backend", "using forked blockchain at {}", fork.block_number()); - Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty()) - } else { - let env = env.read(); - Blockchain::new( - &env, - env.evm_env.cfg_env.spec, - fees.is_eip1559().then(|| fees.base_fee()), - genesis.timestamp, - genesis.number, - ) - }; - - let start_timestamp = if let Some(fork) = fork.read().as_ref() { - fork.timestamp() - } else { - genesis.timestamp - }; - - let mut states = if prune_state_history_config.is_config_enabled() { - // if prune state history is enabled, configure the state cache only for memory - prune_state_history_config - .max_memory_history - .map(|limit| InMemoryBlockStates::new(limit, 0)) - .unwrap_or_default() - .memory_only() - } else if max_persisted_states.is_some() { - max_persisted_states - .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit)) - .unwrap_or_default() - } else { - Default::default() - }; - - if let Some(cache_path) = cache_path { - states = states.disk_path(cache_path); - } - - let (slots_in_an_epoch, precompile_factory) = { - let cfg = node_config.read().await; - (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) - }; - - let (capabilities, executor_wallet) = if odyssey { - // Insert account that sponsors the delegated txs. And deploy P256 delegation contract. - let mut db = db.write().await; - - let _ = db.set_code( - P256_DELEGATION_CONTRACT, - Bytes::from_static(P256_DELEGATION_RUNTIME_CODE), - ); - - // Insert EXP ERC20 contract - let _ = db.set_code(EXP_ERC20_CONTRACT, Bytes::from_static(EXP_ERC20_RUNTIME_CODE)); - - let init_balance = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); // 10K ETH - - // Add ETH - let _ = db.set_balance(EXP_ERC20_CONTRACT, init_balance); - let _ = db.set_balance(EXECUTOR, init_balance); - - let mut capabilities = WalletCapabilities::default(); - - let chain_id = env.read().evm_env.cfg_env.chain_id; - capabilities.insert( - chain_id, - Capabilities { - delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, - }, - ); - - let signer: PrivateKeySigner = EXECUTOR_PK.parse().unwrap(); - - let executor_wallet = EthereumWallet::new(signer); - - (capabilities, Some(executor_wallet)) - } else { - (WalletCapabilities::default(), None) - }; - - let backend = Self { - db, - blockchain, - states: Arc::new(RwLock::new(states)), - env, - fork, - time: TimeManager::new(start_timestamp), - cheats: Default::default(), - new_block_listeners: Default::default(), - fees, - genesis, - active_state_snapshots: Arc::new(Mutex::new(Default::default())), - enable_steps_tracing, - print_logs, - print_traces, - odyssey, - prune_state_history_config, - transaction_block_keeper, - node_config, - slots_in_an_epoch, - precompile_factory, - mining: Arc::new(tokio::sync::Mutex::new(())), - capabilities: Arc::new(RwLock::new(capabilities)), - executor_wallet: Arc::new(RwLock::new(executor_wallet)), - }; - - if let Some(interval_block_time) = automine_block_time { - backend.update_interval_mine_block_time(interval_block_time); +impl Clone for Backend { + fn clone(&self) -> Self { + Self { + db: self.db.clone(), + blockchain: self.blockchain.clone(), + states: self.states.clone(), + evm_env: self.evm_env.clone(), + networks: self.networks, + hardfork: self.hardfork, + fork: self.fork.clone(), + time: self.time.clone(), + cheats: self.cheats.clone(), + fees: self.fees.clone(), + genesis: self.genesis.clone(), + new_block_listeners: self.new_block_listeners.clone(), + active_state_snapshots: self.active_state_snapshots.clone(), + enable_steps_tracing: self.enable_steps_tracing, + print_logs: self.print_logs, + print_traces: self.print_traces, + call_trace_decoder: self.call_trace_decoder.clone(), + prune_state_history_config: self.prune_state_history_config, + transaction_block_keeper: self.transaction_block_keeper, + node_config: self.node_config.clone(), + slots_in_an_epoch: self.slots_in_an_epoch, + precompile_factory: self.precompile_factory.clone(), + mining: self.mining.clone(), + disable_pool_balance_checks: self.disable_pool_balance_checks, } - - // Note: this can only fail in forking mode, in which case we can't recover - backend.apply_genesis().await.wrap_err("failed to create genesis")?; - Ok(backend) - } - - /// Writes the CREATE2 deployer code directly to the database at the address provided. - pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> { - self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?; - - Ok(()) - } - - /// Get the capabilities of the wallet. - /// - /// Currently the only capability is [`DelegationCapability`]. - /// - /// [`DelegationCapability`]: anvil_core::eth::wallet::DelegationCapability - pub(crate) fn get_capabilities(&self) -> WalletCapabilities { - self.capabilities.read().clone() - } - - /// Updates memory limits that should be more strict when auto-mine is enabled - pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { - self.states.write().update_interval_mine_block_time(block_time) - } - - pub(crate) fn executor_wallet(&self) -> Option { - self.executor_wallet.read().clone() - } - - /// Adds an address to the [`DelegationCapability`] of the wallet. - pub(crate) fn add_capability(&self, address: Address) { - let chain_id = self.env.read().evm_env.cfg_env.chain_id; - let mut capabilities = self.capabilities.write(); - let mut capability = capabilities.get(chain_id).cloned().unwrap_or_default(); - capability.delegation.addresses.push(address); - capabilities.insert(chain_id, capability); - } - - pub(crate) fn set_executor(&self, executor_pk: String) -> Result { - let signer: PrivateKeySigner = - executor_pk.parse().map_err(|_| RpcError::invalid_params("Invalid private key"))?; - - let executor = signer.address(); - let wallet = EthereumWallet::new(signer); - - *self.executor_wallet.write() = Some(wallet); - - Ok(executor) } +} - /// Applies the configured genesis settings - /// - /// This will fund, create the genesis accounts - async fn apply_genesis(&self) -> Result<(), DatabaseError> { - trace!(target: "backend", "setting genesis balances"); - - if self.fork.read().is_some() { - // fetch all account first - let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len()); - for address in self.genesis.accounts.iter().copied() { - let db = Arc::clone(&self.db); - - // The forking Database backend can handle concurrent requests, we can fetch all dev - // accounts concurrently by spawning the job to a new task - genesis_accounts_futures.push(tokio::task::spawn(async move { - let db = db.read().await; - let info = db.basic_ref(address)?.unwrap_or_default(); - Ok::<_, DatabaseError>((address, info)) - })); - } - - let genesis_accounts = futures::future::join_all(genesis_accounts_futures).await; - - let mut db = self.db.write().await; - - for res in genesis_accounts { - let (address, mut info) = res.unwrap()?; - info.balance = self.genesis.balance; - db.insert_account(address, info.clone()); - } - } else { - let mut db = self.db.write().await; - for (account, info) in self.genesis.account_infos() { - db.insert_account(account, info); - } - - // insert the new genesis hash to the database so it's available for the next block in - // the evm - db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); - } - - let db = self.db.write().await; - // apply the genesis.json alloc - self.genesis.apply_genesis_json_alloc(db)?; - - trace!(target: "backend", "set genesis balances"); - - Ok(()) +impl fmt::Debug for Backend { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Backend").finish_non_exhaustive() } +} +// Methods that are generic over any Network. +impl Backend { /// Sets the account to impersonate /// /// Returns `true` if the account is already impersonated pub fn impersonate(&self, addr: Address) -> bool { if self.cheats.impersonated_accounts().contains(&addr) { - return true + return true; } // Ensure EIP-3607 is disabled - let mut env = self.env.write(); - env.evm_env.cfg_env.disable_eip3607 = true; + self.evm_env.write().cfg_env.disable_eip3607 = true; self.cheats.impersonate(addr) } @@ -516,167 +390,48 @@ impl Backend { self.fork.read().is_some() } - pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env.read().evm_env.cfg_env.spec) + /// Writes the CREATE2 deployer code directly to the database at the address provided. + pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> { + self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?; + Ok(()) } - /// Resets the fork to a fresh state - pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> { - if !self.is_fork() { - if let Some(eth_rpc_url) = forking.clone().json_rpc_url { - let mut env = self.env.read().clone(); + /// Updates memory limits that should be more strict when auto-mine is enabled + pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { + self.states.write().update_interval_mine_block_time(block_time) + } - let (db, config) = { - let mut node_config = self.node_config.write().await; + /// Returns the `TimeManager` responsible for timestamps + pub const fn time(&self) -> &TimeManager { + &self.time + } - // we want to force the correct base fee for the next block during - // `setup_fork_db_config` - node_config.base_fee.take(); + /// Returns the `CheatsManager` responsible for executing cheatcodes + pub const fn cheats(&self) -> &CheatsManager { + &self.cheats + } - node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await? - }; + /// Whether to skip blob validation + pub fn skip_blob_validation(&self, impersonator: Option
) -> bool { + self.cheats().auto_impersonate_accounts() + || impersonator + .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr)) + } - *self.db.write().await = Box::new(db); + /// Returns the `FeeManager` that manages fee/pricings + pub const fn fees(&self) -> &FeeManager { + &self.fees + } - let fork = ClientFork::new(config, Arc::clone(&self.db)); + /// The EVM environment data of the blockchain + pub const fn evm_env(&self) -> &Arc> { + &self.evm_env + } - *self.env.write() = env; - *self.fork.write() = Some(fork); - } else { - return Err(RpcError::invalid_params( - "Forking not enabled and RPC URL not provided to start forking", - ) - .into()); - } - } - - if let Some(fork) = self.get_fork() { - let block_number = - forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); - // reset the fork entirely and reapply the genesis config - fork.reset(forking.json_rpc_url.clone(), block_number).await?; - let fork_block_number = fork.block_number(); - let fork_block = fork - .block_by_number(fork_block_number) - .await? - .ok_or(BlockchainError::BlockNotFound)?; - // update all settings related to the forked block - { - if let Some(fork_url) = forking.json_rpc_url { - self.reset_block_number(fork_url, fork_block_number).await?; - } else { - // If rpc url is unspecified, then update the fork with the new block number and - // existing rpc url, this updates the cache path - { - let maybe_fork_url = { self.node_config.read().await.eth_rpc_url.clone() }; - if let Some(fork_url) = maybe_fork_url { - self.reset_block_number(fork_url, fork_block_number).await?; - } - } - - let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block); - let mut env = self.env.write(); - - env.evm_env.cfg_env.chain_id = fork.chain_id(); - env.evm_env.block_env = BlockEnv { - number: fork_block_number, - timestamp: fork_block.header.timestamp, - gas_limit, - difficulty: fork_block.header.difficulty, - prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), - // Keep previous `beneficiary` and `basefee` value - beneficiary: env.evm_env.block_env.beneficiary, - basefee: env.evm_env.block_env.basefee, - ..env.evm_env.block_env.clone() - }; - - // this is the base fee of the current block, but we need the base fee of - // the next block - let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - fork_block.header.gas_used, - gas_limit, - fork_block.header.base_fee_per_gas.unwrap_or_default(), - ); - - self.fees.set_base_fee(next_block_base_fee); - } - - // reset the time to the timestamp of the forked block - self.time.reset(fork_block.header.timestamp); - - // also reset the total difficulty - self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); - } - // reset storage - *self.blockchain.storage.write() = BlockchainStorage::forked( - fork.block_number(), - fork.block_hash(), - fork.total_difficulty(), - ); - self.states.write().clear(); - self.db.write().await.clear(); - - self.apply_genesis().await?; - - trace!(target: "backend", "reset fork"); - - Ok(()) - } else { - Err(RpcError::invalid_params("Forking not enabled").into()) - } - } - - async fn reset_block_number( - &self, - fork_url: String, - fork_block_number: u64, - ) -> Result<(), BlockchainError> { - let mut node_config = self.node_config.write().await; - node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128)); - - let mut env = self.env.read().clone(); - let (forked_db, client_fork_config) = - node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await?; - - *self.db.write().await = Box::new(forked_db); - let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); - *self.fork.write() = Some(fork); - *self.env.write() = env; - - Ok(()) - } - - /// Returns the `TimeManager` responsible for timestamps - pub fn time(&self) -> &TimeManager { - &self.time - } - - /// Returns the `CheatsManager` responsible for executing cheatcodes - pub fn cheats(&self) -> &CheatsManager { - &self.cheats - } - - /// Whether to skip blob validation - pub fn skip_blob_validation(&self, impersonator: Option
) -> bool { - self.cheats().auto_impersonate_accounts() || - impersonator - .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr)) - } - - /// Returns the `FeeManager` that manages fee/pricings - pub fn fees(&self) -> &FeeManager { - &self.fees - } - - /// The env data of the blockchain - pub fn env(&self) -> &Arc> { - &self.env - } - - /// Returns the current best hash of the chain - pub fn best_hash(&self) -> B256 { - self.blockchain.storage.read().best_hash - } + /// Returns the current best hash of the chain + pub fn best_hash(&self) -> B256 { + self.blockchain.storage.read().best_hash + } /// Returns the current best number of the chain pub fn best_number(&self) -> u64 { @@ -685,22 +440,31 @@ impl Backend { /// Sets the block number pub fn set_block_number(&self, number: u64) { - let mut env = self.env.write(); - env.evm_env.block_env.number = number; + self.evm_env.write().block_env.number = U256::from(number); } /// Returns the client coinbase address. pub fn coinbase(&self) -> Address { - self.env.read().evm_env.block_env.beneficiary + self.evm_env.read().block_env.beneficiary } /// Returns the client coinbase address. pub fn chain_id(&self) -> U256 { - U256::from(self.env.read().evm_env.cfg_env.chain_id) + U256::from(self.evm_env.read().cfg_env.chain_id) } pub fn set_chain_id(&self, chain_id: u64) { - self.env.write().evm_env.cfg_env.chain_id = chain_id; + self.evm_env.write().cfg_env.chain_id = chain_id; + } + + /// Returns the genesis data for the Beacon API. + pub const fn genesis_time(&self) -> u64 { + self.genesis.timestamp + } + + /// Returns the configured genesis block number. + pub const fn genesis_number(&self) -> u64 { + self.genesis.number } /// Returns balance of the given account. @@ -715,7 +479,7 @@ impl Backend { /// Sets the coinbase address pub fn set_coinbase(&self, address: Address) { - self.env.write().evm_env.block_env.beneficiary = address; + self.evm_env.write().block_env.beneficiary = address; } /// Sets the nonce of the given address @@ -730,7 +494,7 @@ impl Backend { /// Sets the code of the given address pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> { - self.db.write().await.set_code(address, code.0.into()) + self.db.write().await.set_code(address, code) } /// Sets the value for the given slot of the given address @@ -745,7 +509,7 @@ impl Backend { /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().evm_env.cfg_env.spec + *self.evm_env.read().spec_id() } /// Returns true for post London @@ -774,17 +538,81 @@ impl Backend { } /// Returns true if op-stack deposits are active - pub fn is_optimism(&self) -> bool { - self.env.read().is_optimism + #[cfg(feature = "optimism")] + pub const fn is_optimism(&self) -> bool { + self.networks.is_optimism() + } + + /// Returns true if op-stack deposits are active. + /// + /// Always `false` when built without the `optimism` feature. + #[cfg(not(feature = "optimism"))] + pub const fn is_optimism(&self) -> bool { + false + } + + /// Returns true if Tempo network mode is active + pub const fn is_tempo(&self) -> bool { + self.networks.is_tempo() + } + + /// Returns the active hardfork. + pub const fn hardfork(&self) -> FoundryHardfork { + self.hardfork + } + + /// Returns the precompiles for the current spec. + pub fn precompiles(&self) -> BTreeMap { + let spec_id = self.spec_id(); + let precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)); + + let mut precompiles_map = BTreeMap::::default(); + for (address, precompile) in precompiles.inner() { + precompiles_map.insert(precompile.id().name().to_string(), *address); + } + + // Extend with configured network precompiles. + precompiles_map.extend(self.networks.precompiles()); + + if let Some(factory) = &self.precompile_factory { + for (address, precompile) in factory.precompiles() { + precompiles_map.insert(precompile.precompile_id().to_string(), address); + } + } + + precompiles_map + } + + /// Returns the system contracts for the current spec. + pub fn system_contracts(&self) -> BTreeMap { + let mut system_contracts = BTreeMap::::default(); + + let spec_id = self.spec_id(); + + if spec_id >= SpecId::CANCUN { + system_contracts.extend(SystemContract::cancun()); + } + + if spec_id >= SpecId::PRAGUE { + system_contracts.extend(SystemContract::prague(None)); + } + + system_contracts } /// Returns [`BlobParams`] corresponding to the current spec. pub fn blob_params(&self) -> BlobParams { - if self.env.read().evm_env.cfg_env.spec >= SpecId::PRAGUE { - BlobParams::prague() - } else { - BlobParams::cancun() + let spec_id = self.spec_id(); + + if spec_id >= SpecId::OSAKA { + return BlobParams::osaka(); + } + + if spec_id >= SpecId::PRAGUE { + return BlobParams::prague(); } + + BlobParams::cancun() } /// Returns an error if EIP1559 is not active (pre Berlin) @@ -818,21 +646,54 @@ impl Backend { } /// Returns an error if op-stack deposits are not active - pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { + #[cfg(feature = "optimism")] + pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { - return Ok(()) + return Ok(()); } Err(BlockchainError::DepositTransactionUnsupported) } + /// Returns an error if Tempo transactions are not active + pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> { + if self.is_tempo() { + return Ok(()); + } + Err(BlockchainError::TempoTransactionUnsupported) + } + + /// Builds the [`InspectorTxConfig`] from the backend's current settings. + fn inspector_tx_config(&self) -> InspectorTxConfig { + InspectorTxConfig { + print_traces: self.print_traces, + print_logs: self.print_logs, + enable_steps_tracing: self.enable_steps_tracing, + call_trace_decoder: self.call_trace_decoder.clone(), + } + } + + /// Builds the [`PoolTxGasConfig`] from the given EVM environment. + fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig { + let spec_id = *evm_env.spec_id(); + let is_cancun = spec_id >= SpecId::CANCUN; + let blob_params = self.blob_params(); + PoolTxGasConfig { + disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit, + tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap, + tx_gas_limit_cap_resolved: evm_env.cfg_env.tx_gas_limit_cap(), + max_blob_gas_per_block: blob_params.max_blob_gas_per_block(), + is_cancun, + } + } + /// Returns the block gas limit pub fn gas_limit(&self) -> u64 { - self.env.read().evm_env.block_env.gas_limit + self.evm_env.read().block_env.gas_limit } /// Sets the block gas limit pub fn set_gas_limit(&self, gas_limit: u64) { - self.env.write().evm_env.block_env.gas_limit = gas_limit; + self.evm_env.write().block_env.gas_limit = gas_limit; } /// Returns the current base fee @@ -841,7 +702,7 @@ impl Backend { } /// Returns whether the minimum suggested priority fee is enforced - pub fn is_min_priority_fee_enforced(&self) -> bool { + pub const fn is_min_priority_fee_enforced(&self) -> bool { self.fees.is_min_priority_fee_enforced() } @@ -883,566 +744,664 @@ impl Backend { id } - /// Reverts the state to the state snapshot identified by the given `id`. - pub async fn revert_state_snapshot(&self, id: U256) -> Result { - let block = { self.active_state_snapshots.lock().remove(&id) }; - if let Some((num, hash)) = block { - let best_block_hash = { - // revert the storage that's newer than the snapshot - let current_height = self.best_number(); - let mut storage = self.blockchain.storage.write(); + pub fn list_state_snapshots(&self) -> BTreeMap { + self.active_state_snapshots.lock().clone().into_iter().collect() + } - for n in ((num + 1)..=current_height).rev() { - trace!(target: "backend", "reverting block {}", n); - if let Some(hash) = storage.hashes.remove(&n) { - if let Some(block) = storage.blocks.remove(&hash) { - for tx in block.transactions { - let _ = storage.transactions.remove(&tx.hash()); - } - } - } - } + /// Returns the environment for the next block + fn next_evm_env(&self) -> EvmEnv { + let mut evm_env = self.evm_env.read().clone(); + // increase block number for this block + evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1)); + evm_env.block_env.basefee = self.base_fee(); + evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price(); + evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp()); + evm_env + } - storage.best_number = num; - storage.best_hash = hash; - hash - }; - let block = - self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; + /// Builds [`Inspector`] with the configured options. + fn build_inspector(&self) -> AnvilInspector { + let mut inspector = AnvilInspector::default(); - let reset_time = block.header.timestamp; - self.time.reset(reset_time); + if self.print_logs { + inspector = inspector.with_log_collector(); + } + if self.print_traces { + inspector = inspector.with_trace_printer(); + } - let mut env = self.env.write(); - env.evm_env.block_env = BlockEnv { - number: num, - timestamp: block.header.timestamp, - difficulty: block.header.difficulty, - // ensures prevrandao is set - prevrandao: Some(block.header.mix_hash.unwrap_or_default()), - gas_limit: block.header.gas_limit, - // Keep previous `beneficiary` and `basefee` value - beneficiary: env.evm_env.block_env.beneficiary, - basefee: env.evm_env.block_env.basefee, - ..Default::default() - } + inspector + } + + /// Builds an inspector configured for block mining (tracing always enabled). + fn build_mining_inspector(&self) -> AnvilInspector { + let mut inspector = AnvilInspector::default().with_tracing(); + if self.enable_steps_tracing { + inspector = inspector.with_steps_tracing(); } - Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove)) + if self.print_logs { + inspector = inspector.with_log_collector(); + } + if self.print_traces { + inspector = inspector.with_trace_printer(); + } + inspector } - pub fn list_state_snapshots(&self) -> BTreeMap { - self.active_state_snapshots.lock().clone().into_iter().collect() + /// Returns a new block event stream that yields Notifications when a new block was added + pub fn new_block_notifications(&self) -> NewBlockNotifications { + let (tx, rx) = unbounded(); + self.new_block_listeners.lock().push(tx); + trace!(target: "backed", "added new block listener"); + rx } - /// Get the current state. - pub async fn serialized_state( - &self, - preserve_historical_states: bool, - ) -> Result { - let at = self.env.read().evm_env.block_env.clone(); - let best_number = self.blockchain.storage.read().best_number; - let blocks = self.blockchain.storage.read().serialized_blocks(); - let transactions = self.blockchain.storage.read().serialized_transactions(); - let historical_states = if preserve_historical_states { - Some(self.states.write().serialized_states()) - } else { - None - }; + /// Notifies all `new_block_listeners` about the new block + fn notify_on_new_block(&self, header: Header, hash: B256) { + // cleanup closed notification streams first, if the channel is closed we can remove the + // sender half for the set + self.new_block_listeners.lock().retain(|tx| !tx.is_closed()); - let state = self.db.read().await.dump_state( - at, - best_number, - blocks, - transactions, - historical_states, - )?; - state.ok_or_else(|| { - RpcError::invalid_params("Dumping state not supported with the current configuration") - .into() - }) - } + let notification = NewBlockNotification { hash, header: Arc::new(header) }; - /// Write all chain data to serialized bytes buffer - pub async fn dump_state( - &self, - preserve_historical_states: bool, - ) -> Result { - let state = self.serialized_state(preserve_historical_states).await?; - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder - .write_all(&serde_json::to_vec(&state).unwrap_or_default()) - .map_err(|_| BlockchainError::DataUnavailable)?; - Ok(encoder.finish().unwrap_or_default().into()) + self.new_block_listeners + .lock() + .retain(|tx| tx.unbounded_send(notification.clone()).is_ok()); } - /// Apply [SerializableState] data to the backend storage. - pub async fn load_state(&self, state: SerializableState) -> Result { - // load the blocks and transactions into the storage - self.blockchain.storage.write().load_blocks(state.blocks.clone()); - self.blockchain.storage.write().load_transactions(state.transactions.clone()); - // reset the block env - if let Some(block) = state.block.clone() { - self.env.write().evm_env.block_env = block.clone(); - - // Set the current best block number. - // Defaults to block number for compatibility with existing state files. - let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); + /// Returns the block number for the given block id + pub fn convert_block_number(&self, block: Option) -> u64 { + let current = self.best_number(); + match block.unwrap_or(BlockNumber::Latest) { + BlockNumber::Latest | BlockNumber::Pending => current, + BlockNumber::Earliest => 0, + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), + } + } - if let Some((number, hash)) = fork_num_and_hash { - let best_number = state.best_block_number.unwrap_or(block.number); - trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); - // If the state.block_number is greater than the fork block number, set best number - // to the state block number. - // Ref: https://github.com/foundry-rs/foundry/issues/9539 - if best_number > number { - self.blockchain.storage.write().best_number = best_number; - let best_hash = - self.blockchain.storage.read().hash(best_number.into()).ok_or_else( - || { - BlockchainError::RpcError(RpcError::internal_error_with(format!( - "Best hash not found for best number {best_number}", - ))) - }, - )?; - self.blockchain.storage.write().best_hash = best_hash; - } else { - // If loading state file on a fork, set best number to the fork block number. - // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 - self.blockchain.storage.write().best_number = number; - self.blockchain.storage.write().best_hash = hash; + /// Returns the block and its hash for the given id + fn get_block_with_hash(&self, id: impl Into) -> Option<(Block, B256)> { + let hash = match id.into() { + BlockId::Hash(hash) => hash.block_hash, + BlockId::Number(number) => { + let storage = self.blockchain.storage.read(); + let slots_in_an_epoch = self.slots_in_an_epoch; + match number { + BlockNumber::Latest => storage.best_hash, + BlockNumber::Earliest => storage.genesis_hash, + BlockNumber::Pending => return None, + BlockNumber::Number(num) => *storage.hashes.get(&num)?, + BlockNumber::Safe => { + if storage.best_number > (slots_in_an_epoch) { + *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? + } else { + storage.genesis_hash // treat the genesis block as safe "by definition" + } + } + BlockNumber::Finalized => { + if storage.best_number > (slots_in_an_epoch * 2) { + *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? + } else { + storage.genesis_hash + } + } } - } else { - let best_number = state.best_block_number.unwrap_or(block.number); - self.blockchain.storage.write().best_number = best_number; - - // Set the current best block hash; - let best_hash = - self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { - BlockchainError::RpcError(RpcError::internal_error_with(format!( - "Best hash not found for best number {best_number}", - ))) - })?; - - self.blockchain.storage.write().best_hash = best_hash; } - } - - if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number) { - let header = &latest.header; - let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used, - header.gas_limit, - header.base_fee_per_gas.unwrap_or_default(), - ); - let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( - header.excess_blob_gas.unwrap_or_default(), - header.blob_gas_used.unwrap_or_default(), - ); - - // update next base fee - self.fees.set_base_fee(next_block_base_fee); - self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( - next_block_excess_blob_gas, - false, - )); - } - - if !self.db.write().await.load_state(state.clone())? { - return Err(RpcError::invalid_params( - "Loading state not supported with the current configuration", - ) - .into()); - } - - if let Some(historical_states) = state.historical_states { - self.states.write().load_states(historical_states); - } - - Ok(true) + }; + let block = self.get_block_by_hash(hash)?; + Some((block, hash)) } - /// Deserialize and add all chain data to the backend storage - pub async fn load_state_bytes(&self, buf: Bytes) -> Result { - let orig_buf = &buf.0[..]; - let mut decoder = GzDecoder::new(orig_buf); - let mut decoded_data = Vec::new(); - - let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() { - decoder - .read_to_end(decoded_data.as_mut()) - .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - &decoded_data - } else { - &buf.0 - }) - .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - - self.load_state(state).await + pub fn get_block(&self, id: impl Into) -> Option { + self.get_block_with_hash(id).map(|(block, _)| block) } - /// Returns the environment for the next block - fn next_env(&self) -> Env { - let mut env = self.env.read().clone(); - // increase block number for this block - env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(1); - env.evm_env.block_env.basefee = self.base_fee(); - env.evm_env.block_env.timestamp = self.time.current_call_timestamp(); - env - } - - /// Creates an EVM instance with optionally injected precompiles. - fn new_evm_with_inspector_ref<'db, I>( - &self, - db: &'db dyn DatabaseRef, - env: &Env, - inspector: &'db mut I, - ) -> EitherEvm< - WrapDatabaseRef<&'db dyn DatabaseRef>, - &'db mut I, - PrecompilesMap, - > - where - I: Inspector>>> - + Inspector>>>, - WrapDatabaseRef<&'db dyn DatabaseRef>: - Database, - { - let mut evm = new_evm_with_inspector_ref(db, env, inspector); + pub fn get_block_by_hash(&self, hash: B256) -> Option { + self.blockchain.get_block_by_hash(&hash) + } - if self.odyssey { - inject_precompiles(&mut evm, vec![P256VERIFY]); - } + /// Returns the traces for the given transaction + pub(crate) fn mined_parity_trace_transaction( + &self, + hash: B256, + ) -> Option> { + self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces()) + } - if let Some(factory) = &self.precompile_factory { - inject_precompiles(&mut evm, factory.precompiles()); + /// Returns the traces for the given block + pub(crate) fn mined_parity_trace_block( + &self, + block: u64, + ) -> Option> { + let block = self.get_block(block)?; + let mut traces = vec![]; + let storage = self.blockchain.storage.read(); + for tx in block.body.transactions { + if let Some(mined_tx) = storage.transactions.get(&tx.hash()) { + traces.extend(mined_tx.parity_traces()); + } } + Some(traces) + } - evm + /// Returns the mined transaction for the given hash + pub(crate) fn mined_transaction(&self, hash: B256) -> Option> { + self.blockchain.storage.read().transactions.get(&hash).cloned() } - /// executes the transactions without writing to the underlying database - pub async fn inspect_tx( + /// Overrides the given signature to impersonate the specified address during ecrecover. + pub async fn impersonate_signature( &self, - tx: Arc, - ) -> Result< - (InstructionResult, Option, u64, State, Vec), - BlockchainError, - > { - let mut env = self.next_env(); - env.tx = tx.pending_transaction.to_revm_tx_env(); + signature: Bytes, + address: Address, + ) -> Result<(), BlockchainError> { + self.cheats.add_recover_override(signature, address); + Ok(()) + } - if env.is_optimism { - env.tx.enveloped_tx = - Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); + /// Returns code by its hash + pub async fn debug_code_by_hash( + &self, + code_hash: B256, + block_id: Option, + ) -> Result, BlockchainError> { + if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) { + return Ok(Some(code.original_bytes())); } - - let db = self.db.read().await; - let mut inspector = self.build_inspector(); - let mut evm = self.new_evm_with_inspector_ref(db.as_dyn(), &env, &mut inspector); - let ResultAndState { result, state } = evm.transact(env.tx)?; - let (exit_reason, gas_used, out, logs) = match result { - ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (reason.into(), gas_used, Some(output), Some(logs)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) - } - ExecutionResult::Halt { reason, gas_used } => { - let eth_reason = op_haltreason_to_instruction_result(reason); - (eth_reason, gas_used, None, None) - } - }; - - drop(evm); - inspector.print_logs(); - - if self.print_traces { - inspector.print_traces(); + if let Some(fork) = self.get_fork() { + return Ok(fork.debug_code_by_hash(code_hash, block_id).await?); } - Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) + Ok(None) } - /// Creates the pending block + /// Returns the value associated with a key from the database + /// Currently only supports bytecode lookups. /// - /// This will execute all transaction in the order they come but will not mine the block - pub async fn pending_block(&self, pool_transactions: Vec>) -> BlockInfo { - self.with_pending_block(pool_transactions, |_, block| block).await - } - - /// Creates the pending block + /// Based on Reth implementation: /// - /// This will execute all transaction in the order they come but will not mine the block - pub async fn with_pending_block( - &self, - pool_transactions: Vec>, - f: F, - ) -> T - where - F: FnOnce(Box, BlockInfo) -> T, - { - let db = self.db.read().await; - let env = self.next_env(); + /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash) + /// Total key length must be 33 bytes. + pub async fn debug_db_get(&self, key: String) -> Result, BlockchainError> { + let key_bytes = if key.starts_with("0x") { + hex::decode(&key) + .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))? + } else { + key.into_bytes() + }; - let mut cache_db = CacheDB::new(&*db); + // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash) + if key_bytes.len() != 33 { + return Err(BlockchainError::Message(format!( + "Invalid key length: expected 33 bytes, got {}", + key_bytes.len() + ))); + } - let storage = self.blockchain.storage.read(); + // Check for bytecode prefix (0x63 = 'c' in ASCII) + if key_bytes[0] != 0x63 { + return Err(BlockchainError::Message( + "Key prefix must be 0x63 for code hash lookups".to_string(), + )); + } - let executor = TransactionExecutor { - db: &mut cache_db, - validator: self, - pending: pool_transactions.into_iter(), - block_env: env.evm_env.block_env.clone(), - cfg_env: env.evm_env.cfg_env, - parent_hash: storage.best_hash, - gas_used: 0, - blob_gas_used: 0, - enable_steps_tracing: self.enable_steps_tracing, - print_logs: self.print_logs, - print_traces: self.print_traces, - precompile_factory: self.precompile_factory.clone(), - odyssey: self.odyssey, - optimism: self.is_optimism(), - blob_params: self.blob_params(), - }; + let code_hash = B256::from_slice(&key_bytes[1..33]); - // create a new pending block - let executed = executor.execute(); - f(Box::new(cache_db), executed.block) + // Use the existing debug_code_by_hash method to retrieve the bytecode + self.debug_code_by_hash(code_hash, None).await } - /// Mines a new block and stores it. - /// - /// this will execute all transaction in the order they come in and return all the markers they - /// provide. - pub async fn mine_block( - &self, - pool_transactions: Vec>, - ) -> MinedBlockOutcome { - self.do_mine_block(pool_transactions).await + fn mined_block_by_hash(&self, hash: B256) -> Option { + let block = self.blockchain.get_block_by_hash(&hash)?; + Some(self.convert_block_with_hash(block, Some(hash))) } - async fn do_mine_block( + pub(crate) async fn mined_transactions_by_block_number( &self, - pool_transactions: Vec>, - ) -> MinedBlockOutcome { - let _mining_guard = self.mining.lock().await; - trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len()); + number: BlockNumber, + ) -> Option> { + if let Some(block) = self.get_block(number) { + return self.mined_transactions_in_block(&block); + } + None + } - let (outcome, header, block_hash) = { - let current_base_fee = self.base_fee(); - let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price(); + /// Returns all transactions given a block + pub(crate) fn mined_transactions_in_block( + &self, + block: &Block, + ) -> Option> { + let mut transactions = Vec::with_capacity(block.body.transactions.len()); + let base_fee = block.header.base_fee_per_gas(); + let storage = self.blockchain.storage.read(); + for hash in block.body.transactions.iter().map(|tx| tx.hash()) { + let info = storage.transactions.get(&hash)?.info.clone(); + let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); - let mut env = self.env.read().clone(); + let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee); + transactions.push(tx); + } + Some(transactions) + } - if env.evm_env.block_env.basefee == 0 { - // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` - // 0 is only possible if it's manually set - env.evm_env.cfg_env.disable_base_fee = true; - } + pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { + let (block, hash) = self.get_block_with_hash(number)?; + let mut block = self.convert_block_with_hash(block, Some(hash)); + block.transactions.convert_to_hashes(); + Some(block) + } - let block_number = self.blockchain.storage.read().best_number.saturating_add(1); + pub fn get_full_block(&self, id: impl Into) -> Option { + let (block, hash) = self.get_block_with_hash(id)?; + let transactions = self.mined_transactions_in_block(&block)?; + let mut block = self.convert_block_with_hash(block, Some(hash)); + block.inner.transactions = BlockTransactions::Full(transactions); + Some(block) + } - // increase block number for this block - if is_arbitrum(env.evm_env.cfg_env.chain_id) { - // Temporary set `env.block.number` to `block_number` for Arbitrum chains. - env.evm_env.block_env.number = block_number; - } else { - env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(1); - } + /// Takes a block as it's stored internally and returns the eth api conform block format. + pub fn convert_block(&self, block: Block) -> AnyRpcBlock { + self.convert_block_with_hash(block, None) + } - env.evm_env.block_env.basefee = current_base_fee; - env.evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + /// Takes a block as it's stored internally and returns the eth api conform block format. + /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`. + pub fn convert_block_with_hash(&self, block: Block, known_hash: Option) -> AnyRpcBlock { + let size = U256::from(alloy_rlp::encode(&block).len() as u32); - // pick a random value for prevrandao - env.evm_env.block_env.prevrandao = Some(B256::random()); + let header = block.header.clone(); + let transactions = block.body.transactions; - let best_hash = self.blockchain.storage.read().best_hash; + let hash = known_hash.unwrap_or_else(|| header.hash_slow()); + let Header { number, withdrawals_root, .. } = header; - if self.prune_state_history_config.is_state_history_supported() { - let db = self.db.read().await.current_state(); - // store current state before executing all transactions - self.states.write().insert(best_hash, db); - } + let block = AlloyBlock { + header: AlloyHeader { + inner: AnyHeader::from(header), + hash, + total_difficulty: Some(self.total_difficulty()), + size: Some(size), + }, + transactions: alloy_rpc_types::BlockTransactions::Hashes( + transactions.into_iter().map(|tx| tx.hash()).collect(), + ), + uncles: vec![], + withdrawals: withdrawals_root.map(|_| Default::default()), + }; - let (executed_tx, block_hash) = { - let mut db = self.db.write().await; + let mut block = WithOtherFields::new(block); - // finally set the next block timestamp, this is done just before execution, because - // there can be concurrent requests that can delay acquiring the db lock and we want - // to ensure the timestamp is as close as possible to the actual execution. - env.evm_env.block_env.timestamp = self.time.next_timestamp(); - - let executor = TransactionExecutor { - db: &mut **db, - validator: self, - pending: pool_transactions.into_iter(), - block_env: env.evm_env.block_env.clone(), - cfg_env: env.evm_env.cfg_env.clone(), - parent_hash: best_hash, - gas_used: 0, - blob_gas_used: 0, - enable_steps_tracing: self.enable_steps_tracing, - print_logs: self.print_logs, - print_traces: self.print_traces, - odyssey: self.odyssey, - precompile_factory: self.precompile_factory.clone(), - optimism: self.is_optimism(), - blob_params: self.blob_params(), - }; - let executed_tx = executor.execute(); + // If Arbitrum, apply chain specifics to converted block. + if is_arbitrum(self.chain_id().to::()) { + // Set `l1BlockNumber` field. + block.other.insert("l1BlockNumber".to_string(), number.into()); + } - // we also need to update the new blockhash in the db itself - let block_hash = executed_tx.block.block.header.hash_slow(); - db.insert_block_hash(U256::from(executed_tx.block.block.header.number), block_hash); + // Add Tempo-specific header fields for compatibility with TempoNetwork provider. + if self.is_tempo() { + let timestamp = block.header.timestamp(); + let gas_limit = block.header.gas_limit(); + block.other.insert( + "timestampMillis".to_string(), + serde_json::Value::String(format!("0x{:x}", timestamp.saturating_mul(1000))), + ); + block.other.insert( + "mainBlockGeneralGasLimit".to_string(), + serde_json::Value::String(format!("0x{gas_limit:x}")), + ); + block + .other + .insert("sharedGasLimit".to_string(), serde_json::Value::String("0x0".to_string())); + block.other.insert( + "timestampMillisPart".to_string(), + serde_json::Value::String("0x0".to_string()), + ); + } - (executed_tx, block_hash) - }; + AnyRpcBlock::from(block) + } - // create the new block with the current timestamp - let ExecutedTransactions { block, included, invalid } = executed_tx; - let BlockInfo { block, transactions, receipts } = block; + pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { + trace!(target: "backend", "get block by hash {:?}", hash); + if let tx @ Some(_) = self.mined_block_by_hash(hash) { + return Ok(tx); + } - let header = block.header.clone(); + if let Some(fork) = self.get_fork() { + return Ok(fork.block_by_hash(hash).await?); + } - trace!( - target: "backend", - "Mined block {} with {} tx {:?}", - block_number, - transactions.len(), - transactions.iter().map(|tx| tx.transaction_hash).collect::>() - ); - let mut storage = self.blockchain.storage.write(); - // update block metadata - storage.best_number = block_number; - storage.best_hash = block_hash; - // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with - // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27 - if !self.is_eip3675() { - storage.total_difficulty = - storage.total_difficulty.saturating_add(header.difficulty); + Ok(None) + } + + pub async fn block_by_hash_full( + &self, + hash: B256, + ) -> Result, BlockchainError> { + trace!(target: "backend", "get block by hash {:?}", hash); + if let tx @ Some(_) = self.get_full_block(hash) { + return Ok(tx); + } + + if let Some(fork) = self.get_fork() { + return Ok(fork.block_by_hash_full(hash).await?); + } + + Ok(None) + } + + pub async fn block_by_number( + &self, + number: BlockNumber, + ) -> Result, BlockchainError> { + trace!(target: "backend", "get block by number {:?}", number); + if let tx @ Some(_) = self.mined_block_by_number(number) { + return Ok(tx); + } + + if let Some(fork) = self.get_fork() { + let number = self.convert_block_number(Some(number)); + if fork.predates_fork_inclusive(number) { + return Ok(fork.block_by_number(number).await?); } + } - storage.blocks.insert(block_hash, block); - storage.hashes.insert(block_number, block_hash); + Ok(None) + } - node_info!(""); - // insert all transactions - for (info, receipt) in transactions.into_iter().zip(receipts) { - // log some tx info - node_info!(" Transaction: {:?}", info.transaction_hash); - if let Some(contract) = &info.contract_address { - node_info!(" Contract created: {contract}"); - } - node_info!(" Gas used: {}", receipt.cumulative_gas_used()); - if !info.exit.is_ok() { - let r = RevertDecoder::new().decode( - info.out.as_ref().map(|b| &b[..]).unwrap_or_default(), - Some(info.exit), - ); - node_info!(" Error: reverted with: {r}"); - } - node_info!(""); + pub async fn block_by_number_full( + &self, + number: BlockNumber, + ) -> Result, BlockchainError> { + trace!(target: "backend", "get block by number {:?}", number); + if let tx @ Some(_) = self.get_full_block(number) { + return Ok(tx); + } - let mined_tx = MinedTransaction { info, receipt, block_hash, block_number }; - storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); + if let Some(fork) = self.get_fork() { + let number = self.convert_block_number(Some(number)); + if fork.predates_fork_inclusive(number) { + return Ok(fork.block_by_number_full(number).await?); } + } - // remove old transactions that exceed the transaction block keeper - if let Some(transaction_block_keeper) = self.transaction_block_keeper { - if storage.blocks.len() > transaction_block_keeper { - let to_clear = block_number - .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX)); - storage.remove_block_transactions_by_number(to_clear) + Ok(None) + } + + /// Converts the `BlockNumber` into a numeric value + /// + /// # Errors + /// + /// returns an error if the requested number is larger than the current height + pub async fn ensure_block_number>( + &self, + block_id: Option, + ) -> Result { + let current = self.best_number(); + let requested = + match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { + BlockId::Hash(hash) => { + self.block_by_hash(hash.block_hash) + .await? + .ok_or(BlockchainError::BlockNotFound)? + .header + .number } - } + BlockId::Number(num) => match num { + BlockNumber::Latest | BlockNumber::Pending => current, + BlockNumber::Earliest => U64::ZERO.to::(), + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), + }, + }; - // we intentionally set the difficulty to `0` for newer blocks - env.evm_env.block_env.difficulty = U256::from(0); + if requested > current { + Err(BlockchainError::BlockOutOfRange(current, requested)) + } else { + Ok(requested) + } + } - // update env with new values - *self.env.write() = env; + /// Injects all configured precompiles into the given precompile map. + /// + /// This applies three layers: + /// 1. Network-specific precompiles (e.g. Tempo, OP) + /// 2. User-provided precompiles via [`PrecompileFactory`] + /// 3. Cheatcode ecrecover overrides (if active) + fn inject_precompiles(&self, precompiles: &mut PrecompilesMap) { + self.networks.inject_precompiles(precompiles); - let timestamp = utc_from_secs(header.timestamp); + if let Some(factory) = &self.precompile_factory { + precompiles.extend_precompiles(factory.precompiles()); + } - node_info!(" Block Number: {}", block_number); - node_info!(" Block Hash: {:?}", block_hash); - if timestamp.year() > 9999 { - // rf2822 panics with more than 4 digits - node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339()); - } else { - node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822()); - } + let cheats = Arc::new(self.cheats.clone()); + if cheats.has_recover_overrides() { + let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats)); + precompiles.apply_precompile(&EC_RECOVER, move |_| { + Some(DynPrecompile::new_stateful( + cheat_ecrecover.precompile_id().clone(), + move |input| cheat_ecrecover.call(input), + )) + }); + } + } - let outcome = MinedBlockOutcome { block_number, included, invalid }; + /// Creates a concrete EVM, injects precompiles, transacts, and returns the result mapped + /// to [`HaltReason`] so all call sites share a single halt-reason type. + fn transact_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TxEnv, + op_deposit: OpCallDepositInfo, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: BackendInspector>, + WrapDatabaseRef<&'db DB>: Database, + { + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_tx = OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() }; + return self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx); + } + // `op_deposit` only matters on the OP path; eth/tempo ignore it. + let _ = op_deposit; + if self.is_tempo() { + self.transact_tempo_with_inspector_ref(db, evm_env, inspector, TempoTxEnv::from(tx_env)) + } else { + self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env) + } + } - (outcome, header, block_hash) - }; - let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used, - header.gas_limit, - header.base_fee_per_gas.unwrap_or_default(), + /// Eth path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an Ethereum EVM, injects precompiles, and transacts with a + /// plain [`TxEnv`]. + fn transact_eth_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TxEnv, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let mut evm = EthEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + evm_env.clone(), + inspector, ); - let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( - header.excess_blob_gas.unwrap_or_default(), - header.blob_gas_used.unwrap_or_default(), + self.inject_precompiles(evm.precompiles_mut()); + Ok(evm.transact(tx_env)?) + } + + /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct + /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`]. + fn transact_envelope_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx: &FoundryTxEnvelope, + sender: Address, + ) -> Result<(ResultAndState, TxEnv), BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: BackendInspector>, + WrapDatabaseRef<&'db DB>: Database, + { + if tx.is_tempo() { + let tx_env: TempoTxEnv = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = tx_env.inner.clone(); + let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?; + return Ok((result, base)); + } + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_tx: OpTransaction = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = op_tx.base.clone(); + let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?; + return Ok((result, base)); + } + let tx_env: TxEnv = + FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into()); + let base = tx_env.clone(); + let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?; + Ok((result, base)) + } + + /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`]. + fn transact_tempo_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: TempoTxEnv, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let hardfork = TempoHardfork::from(self.hardfork); + let tempo_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)), + TempoBlockEnv { inner: evm_env.block_env.clone(), timestamp_millis_part: 0 }, ); + let mut evm = TempoEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + tempo_env, + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + let result = evm.transact(tx_env)?; + Ok(ResultAndState { + result: result.result.map_haltreason(|h| match h { + TempoHaltReason::Ethereum(eth) => eth, + _ => HaltReason::PrecompileError, + }), + state: result.state, + }) + } - // update next base fee - self.fees.set_base_fee(next_block_base_fee); - self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( - next_block_excess_blob_gas, - false, - )); + /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and + /// executes pool transactions. Returns the execution results and drops the EVM. + #[allow(clippy::too_many_arguments, clippy::type_complexity)] + fn execute_with_block_executor( + &self, + db: DB, + evm_env: &EvmEnv, + parent_hash: B256, + spec_id: SpecId, + pool_transactions: &[Arc>], + gas_config: &PoolTxGasConfig, + inspector_tx_config: &InspectorTxConfig, + validator: &dyn Fn( + &PendingTransaction, + &AccountInfo, + ) -> Result<(), InvalidTransactionError>, + ) -> (ExecutedPoolTransactions, BlockExecutionResult) + where + DB: StateDB, + { + let inspector = self.build_mining_inspector(); + + macro_rules! run { + ($evm:expr) => {{ + self.inject_precompiles($evm.precompiles_mut()); + let mut executor = AnvilBlockExecutor::new($evm, parent_hash, spec_id); + executor.apply_pre_execution_changes().expect("pre-execution changes failed"); + let pool_result = execute_pool_transactions( + &mut executor, + pool_transactions, + gas_config, + inspector_tx_config, + self.cheats(), + validator, + ); + let (evm, block_result) = executor.finish().expect("executor finish failed"); + drop(evm); + (pool_result, block_result) + }}; + } - // notify all listeners - self.notify_on_new_block(header, block_hash); + #[cfg(feature = "optimism")] + if self.is_optimism() { + let op_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), + evm_env.block_env.clone(), + ); + let mut evm = + OpEvmFactory::::default().create_evm_with_inspector(db, op_env, inspector); + return run!(evm); + } - outcome + if self.is_tempo() { + let hardfork = TempoHardfork::from(self.hardfork); + let tempo_env = EvmEnv::new( + evm_env + .cfg_env + .clone() + .with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)), + TempoBlockEnv { inner: evm_env.block_env.clone(), timestamp_millis_part: 0 }, + ); + let mut evm = + TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector); + run!(evm) + } else { + let mut evm = + EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector); + run!(evm) + } } - /// Executes the [TransactionRequest] without writing to the DB + /// ## EVM settings /// - /// # Errors + /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: : /// - /// Returns an error if the `block_number` is greater than the current height - pub async fn call( - &self, - request: WithOtherFields, - fee_details: FeeDetails, - block_request: Option, - overrides: EvmOverrides, - ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { - self.with_database_at(block_request, |state, mut block| { - let block_number = block.number; - let (exit, out, gas, state) = { - let mut cache_db = CacheDB::new(state); - if let Some(state_overrides) = overrides.state { - state::apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?; - } - if let Some(block_overrides) = overrides.block { - state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); - } - self.call_with_state(cache_db.as_dyn(), request, fee_details, block) - }?; - trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); - Ok((exit, out, gas, state)) - }).await? - } - - /// ## EVM settings - /// - /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: : - /// - /// - `disable_eip3607` is set to `true` - /// - `disable_base_fee` is set to `true` - /// - `nonce` check is skipped if `request.nonce` is None - fn build_call_env( + /// - `disable_eip3607` is set to `true` + /// - `disable_base_fee` is set to `true` + /// - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap + /// - `nonce` check is skipped + fn build_call_env( &self, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Env { + ) -> (EvmEnv, TxEnv, OpCallDepositInfo) { + let tx_type = request.minimal_tx_type() as u8; + let WithOtherFields:: { inner: TransactionRequest { @@ -1457,28 +1416,11 @@ impl Backend { nonce, sidecar: _, chain_id, - transaction_type, - max_fee_per_gas, - max_priority_fee_per_gas, .. // Rest of the gas fees related fields are taken from `fee_details` }, other, } = request; - let tx_type = transaction_type.unwrap_or_else(|| { - if authorization_list.is_some() { - EIP7702_TX_TYPE_ID - } else if blob_versioned_hashes.is_some() { - EIP4844_TX_TYPE_ID - } else if max_fee_per_gas.is_some() || max_priority_fee_per_gas.is_some() { - EIP1559_TX_TYPE_ID - } else if access_list.is_some() { - EIP2930_TX_TYPE_ID - } else { - LEGACY_TX_TYPE_ID - } - }); - let FeeDetails { gas_price, max_fee_per_gas, @@ -1487,18 +1429,22 @@ impl Backend { } = fee_details; let gas_limit = gas.unwrap_or(block_env.gas_limit); - let mut env = self.env.read().clone(); - env.evm_env.block_env = block_env; + let mut evm_env = self.evm_env.read().clone(); + evm_env.block_env = block_env; // we want to disable this in eth_call, since this is common practice used by other node // impls and providers - env.evm_env.cfg_env.disable_block_gas_limit = true; + evm_env.cfg_env.disable_block_gas_limit = true; + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); // The basefee should be ignored for calls against state for // - eth_call // - eth_estimateGas // - eth_createAccessList // - tracing - env.evm_env.cfg_env.disable_base_fee = true; + evm_env.cfg_env.disable_base_fee = true; + + // Disable nonce check in revm + evm_env.cfg_env.disable_nonce_check = true; let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| { self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE) @@ -1506,18 +1452,14 @@ impl Backend { let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); - let mut base = TxEnv { + let mut tx_env = TxEnv { caller, gas_limit, gas_price, gas_priority_fee: max_priority_fee_per_gas, max_fee_per_blob_gas: max_fee_per_blob_gas .or_else(|| { - if !blob_hashes.is_empty() { - env.evm_env.block_env.blob_gasprice() - } else { - Some(0) - } + if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() } }) .unwrap_or_default(), kind: match to { @@ -1527,793 +1469,1645 @@ impl Backend { tx_type, value: value.unwrap_or_default(), data: input.into_input().unwrap_or_default(), - chain_id: Some(chain_id.unwrap_or(self.env.read().evm_env.cfg_env.chain_id)), + chain_id: Some(chain_id.unwrap_or(self.chain_id().to::())), access_list: access_list.unwrap_or_default(), blob_hashes, ..Default::default() }; - base.set_signed_authorization(authorization_list.unwrap_or_default()); - env.tx = OpTransaction { base, ..Default::default() }; + tx_env.set_signed_authorization(authorization_list.unwrap_or_default()); if let Some(nonce) = nonce { - env.tx.base.nonce = nonce; - } else { - // Disable nonce check in revm - env.evm_env.cfg_env.disable_nonce_check = true; + tx_env.nonce = nonce; } - if env.evm_env.block_env.basefee == 0 { + if evm_env.block_env.basefee == 0 { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set - env.evm_env.cfg_env.disable_base_fee = true; - } - - // Deposit transaction? - if transaction_type == Some(DEPOSIT_TX_TYPE_ID) && has_optimism_fields(&other) { - let deposit = DepositTransactionParts { - source_hash: other - .get_deserialized::("sourceHash") - .map(|sh| sh.unwrap_or_default()) - .unwrap_or_default(), - mint: other - .get_deserialized::("mint") - .map(|m| m.unwrap_or_default()) - .or(None), - is_system_transaction: other - .get_deserialized::("isSystemTx") - .map(|st| st.unwrap_or_default()) - .unwrap_or_default(), - }; - env.tx.deposit = deposit; - } - - env - } - - /// Builds [`Inspector`] with the configured options. - fn build_inspector(&self) -> AnvilInspector { - let mut inspector = AnvilInspector::default(); - - if self.print_logs { - inspector = inspector.with_log_collector(); + evm_env.cfg_env.disable_base_fee = true; } - if self.print_traces { - inspector = inspector.with_trace_printer(); - } - - inspector - } - - /// Simulates the payload by executing the calls in request. - pub async fn simulate( - &self, - request: SimulatePayload, - block_request: Option, - ) -> Result>, BlockchainError> { - self.with_database_at(block_request, |state, mut block_env| { - let SimulatePayload { - block_state_calls, - trace_transfers, - validation, - return_full_transactions, - } = request; - let mut cache_db = CacheDB::new(state); - let mut block_res = Vec::with_capacity(block_state_calls.len()); - - // execute the blocks - for block in block_state_calls { - let SimBlock { block_overrides, state_overrides, calls } = block; - let mut call_res = Vec::with_capacity(calls.len()); - let mut log_index = 0; - let mut gas_used = 0; - let mut transactions = Vec::with_capacity(calls.len()); - let mut receipts = Vec::new(); - let mut logs= Vec::new(); - - // apply state overrides before executing the transactions - if let Some(state_overrides) = state_overrides { - state::apply_state_overrides(state_overrides, &mut cache_db)?; - } - if let Some(block_overrides) = block_overrides { - state::apply_block_overrides(block_overrides, &mut cache_db, &mut block_env); - } - - // execute all calls in that block - for (req_idx, request) in calls.into_iter().enumerate() { - let fee_details = FeeDetails::new( - request.gas_price, - request.max_fee_per_gas, - request.max_priority_fee_per_gas, - request.max_fee_per_blob_gas, - )? - .or_zero_fees(); - - let mut env = self.build_call_env( - WithOtherFields::new(request.clone()), - fee_details, - block_env.clone(), - ); - - // Always disable EIP-3607 - env.evm_env.cfg_env.disable_eip3607 = true; - - if !validation { - env.evm_env.cfg_env.disable_base_fee = !validation; - env.evm_env.block_env.basefee = 0; - } - - // transact - let ResultAndState { result, state } = if trace_transfers { - // prepare inspector to capture transfer inside the evm so they are - // recorded and included in logs - let mut inspector = TransferInspector::new(false).with_logs(true); - let mut evm= self.new_evm_with_inspector_ref( - cache_db.as_dyn(), - &env, - &mut inspector, - ); - - trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.transact(env.tx)? - } else { - let mut inspector = self.build_inspector(); - let mut evm = self.new_evm_with_inspector_ref( - cache_db.as_dyn(), - &env, - &mut inspector, - ); - trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.transact(env.tx)? - }; - trace!(target: "backend", ?result, ?request, "simulate call"); - - // commit the transaction - cache_db.commit(state); - gas_used += result.gas_used(); - - // TODO: this is likely incomplete - // create the transaction from a request - let from = request.from.unwrap_or_default(); - let request = - transaction_request_to_typed(WithOtherFields::new(request)).unwrap(); - let tx = build_typed_transaction( - request, - Signature::new(Default::default(), Default::default(), false), - )?; - let rpc_tx = transaction_build( - None, - MaybeImpersonatedTransaction::impersonated(tx, from), - None, - None, - Some(block_env.basefee), - ); - transactions.push(rpc_tx); - - let return_data = result.output().cloned().unwrap_or_default(); - let sim_res = SimCallResult { - return_data, - gas_used: result.gas_used(), - status: result.is_success(), - error: result.is_success().not().then(|| { - alloy_rpc_types::simulate::SimulateError { - code: -3200, - message: "execution failed".to_string(), - } - }), - logs: result.clone() - .into_logs() - .into_iter() - .enumerate() - .map(|(idx, log)| Log { - inner: log, - block_number: Some(block_env.number), - block_timestamp: Some(block_env.timestamp), - transaction_index: Some(req_idx as u64), - log_index: Some((idx + log_index) as u64), - removed: false, - - block_hash: None, - transaction_hash: None, - }) - .collect(), - }; - let receipt = Receipt { - status: result.is_success().into(), - cumulative_gas_used: result.gas_used(), - logs:sim_res.logs.clone() - }; - receipts.push(receipt.with_bloom()); - logs.extend(sim_res.logs.clone().iter().map(|log| log.inner.clone())); - log_index += sim_res.logs.len(); - call_res.push(sim_res); - } - let transactions_envelopes: Vec = transactions - .iter() - .map(|tx| AnyTxEnvelope::from(tx.clone())) - .collect(); - let header = Header { - logs_bloom: logs_bloom(logs.iter()), - transactions_root: calculate_transaction_root(&transactions_envelopes), - receipts_root: calculate_receipt_root(&transactions_envelopes), - parent_hash: Default::default(), - ommers_hash: Default::default(), - beneficiary: block_env.beneficiary, - state_root: Default::default(), - difficulty: Default::default(), - number: block_env.number, - gas_limit: block_env.gas_limit, - gas_used, - timestamp: block_env.timestamp, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - base_fee_per_gas: Some(block_env.basefee), - withdrawals_root: None, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - requests_hash: None, - }; - let mut block = alloy_rpc_types::Block { - header: AnyRpcHeader { - hash: header.hash_slow(), - inner: header.into(), - total_difficulty: None, - size: None, - }, - uncles: vec![], - transactions: BlockTransactions::Full(transactions), - withdrawals: None, - }; - - if !return_full_transactions { - block.transactions.convert_to_hashes(); - } - - let simulated_block = SimulatedBlock { - inner: AnyRpcBlock::new(WithOtherFields::new(block)), - calls: call_res, - }; - - // update block env - block_env.number += 1; - block_env.timestamp += 12; - block_env.basefee = simulated_block - .inner - .header - .next_block_base_fee(BaseFeeParams::ethereum()) - .unwrap_or_default(); - block_res.push(simulated_block); - } + // Deposit transaction? (only valid when op-stack deposits are active) + #[cfg(feature = "optimism")] + let op_deposit = if self.ensure_op_deposits_active().is_ok() + && let Ok(deposit) = get_deposit_tx_parts(&other) + { + deposit + } else { + OpCallDepositInfo::default() + }; + #[cfg(not(feature = "optimism"))] + let op_deposit = { + // `other` carries OP-only deposit fields; consumed only when feature is enabled. + let _ = &other; + OpCallDepositInfo + }; - Ok(block_res) - }) - .await? + (evm_env, tx_env, op_deposit) } pub fn call_with_state( &self, - state: &dyn DatabaseRef, + state: &dyn DatabaseRef, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { let mut inspector = self.build_inspector(); - let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state } = evm.transact(env.tx)?; - let (exit_reason, gas_used, out) = match result { - ExecutionResult::Success { reason, gas_used, output, .. } => { - (reason.into(), gas_used, Some(output)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output))) - } - ExecutionResult::Halt { reason, gas_used } => { - (op_haltreason_to_instruction_result(reason), gas_used, None) - } - }; - drop(evm); + // Extract Tempo-specific fields before `build_call_env` consumes `other`. + let tempo_overrides = self.is_tempo().then(|| { + let fee_token = + request.other.get_deserialized::
("feeToken").and_then(|r| r.ok()); + let nonce_key = request + .other + .get_deserialized::("nonceKey") + .and_then(|r| r.ok()) + .unwrap_or_default(); + let valid_before = request + .other + .get_deserialized::("validBefore") + .and_then(|r| r.ok()) + .map(|v| v.saturating_to::()); + let valid_after = request + .other + .get_deserialized::("validAfter") + .and_then(|r| r.ok()) + .map(|v| v.saturating_to::()); + (fee_token, nonce_key, valid_before, valid_after) + }); + + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); + + let ResultAndState { result, state } = + if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides { + use tempo_primitives::transaction::Call; + + let base = tx_env; + let mut tempo_tx = TempoTxEnv::from(base.clone()); + tempo_tx.fee_token = fee_token; + + if !nonce_key.is_zero() || valid_before.is_some() || valid_after.is_some() { + // For gas estimation we don't have a signed tx, so generate a + // unique hash for expiring-nonce replay protection. The nonce + // manager needs a non-zero hash; the actual value doesn't matter + // because the state is discarded after estimation. + let estimation_hash = keccak256(base.data.as_ref()); + tempo_tx.tempo_tx_env = Some(Box::new(TempoBatchCallEnv { + nonce_key, + valid_before, + valid_after, + aa_calls: vec![Call { to: base.kind, value: base.value, input: base.data }], + tx_hash: estimation_hash, + expiring_nonce_hash: Some(estimation_hash), + ..Default::default() + })); + } + self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)? + } else { + self.transact_with_inspector_ref( + state, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )? + }; + + let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); inspector.print_logs(); if self.print_traces { - inspector.into_print_traces(); + inspector.into_print_traces(self.call_trace_decoder.clone()); } Ok((exit_reason, out, gas_used as u128, state)) } - pub async fn call_with_tracing( + pub fn build_access_list_with_state( &self, + state: &dyn DatabaseRef, request: WithOtherFields, fee_details: FeeDetails, - block_request: Option, - opts: GethDebugTracingCallOptions, - ) -> Result { - let GethDebugTracingCallOptions { tracing_options, block_overrides, state_overrides } = - opts; - let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; - - self.with_database_at(block_request, |state, mut block| { - let block_number = block.number; - - let mut cache_db = CacheDB::new(state); - if let Some(state_overrides) = state_overrides { - state::apply_state_overrides(state_overrides, &mut cache_db)?; - } - if let Some(block_overrides) = block_overrides { - state::apply_block_overrides(block_overrides, &mut cache_db, &mut block); - } + block_env: BlockEnv, + ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> { + let mut inspector = + AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - if let Some(tracer) = tracer { - return match tracer { - GethDebugTracerType::BuiltInTracer(tracer) => match tracer { - GethDebugBuiltInTracerType::CallTracer => { - let call_config = tracer_config - .into_call_config() - .map_err(|e| (RpcError::invalid_params(e.to_string())))?; + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env); + let ResultAndState { result, state: _ } = + self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env, op_deposit)?; + let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); + let access_list = inspector.access_list(); + Ok((exit_reason, out, gas_used, access_list)) + } - let mut inspector = self.build_inspector().with_tracing_config( - TracingInspectorConfig::from_geth_call_config(&call_config), - ); + pub fn get_code_with_state( + &self, + state: &dyn DatabaseRef, + address: Address, + ) -> Result { + trace!(target: "backend", "get code for {:?}", address); + let account = state.basic_ref(address)?.unwrap_or_default(); + if account.code_hash == KECCAK_EMPTY { + // if the code hash is `KECCAK_EMPTY`, we check no further + return Ok(Default::default()); + } + let code = if let Some(code) = account.code { + code + } else { + state.code_by_hash_ref(account.code_hash)? + }; + Ok(code.bytes()[..code.len()].to_vec().into()) + } - let env = self.build_call_env(request, fee_details, block); - let mut evm = self.new_evm_with_inspector_ref( - cache_db.as_dyn(), - &env, - &mut inspector, - ); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; - - drop(evm); - let tracing_inspector = inspector.tracer.expect("tracer disappeared"); - - Ok(tracing_inspector - .into_geth_builder() - .geth_call_traces(call_config, result.gas_used()) - .into()) - } - GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), - GethDebugBuiltInTracerType::FourByteTracer | - GethDebugBuiltInTracerType::PreStateTracer | - GethDebugBuiltInTracerType::MuxTracer | - GethDebugBuiltInTracerType::FlatCallTracer => { - Err(RpcError::invalid_params("unsupported tracer type").into()) - } - }, - - GethDebugTracerType::JsTracer(_code) => { - Err(RpcError::invalid_params("unsupported tracer type").into()) - } - } - } - - // defaults to StructLog tracer used since no tracer is specified - let mut inspector = self - .build_inspector() - .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); - - let env = self.build_call_env(request, fee_details, block); - let mut evm = self.new_evm_with_inspector_ref(cache_db.as_dyn(), &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; - - let (exit_reason, gas_used, out) = match result { - ExecutionResult::Success { reason, gas_used, output, .. } => { - (reason.into(), gas_used, Some(output)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output))) - } - ExecutionResult::Halt { reason, gas_used } => { - (op_haltreason_to_instruction_result(reason), gas_used, None) - } - }; - - drop(evm); - let tracing_inspector = inspector.tracer.expect("tracer disappeared"); - let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); - - trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); - - let res = tracing_inspector - .into_geth_builder() - .geth_traces(gas_used, return_value, config) - .into(); - - Ok(res) - }) - .await? - } - - pub fn build_access_list_with_state( + pub fn get_balance_with_state( &self, - state: &dyn DatabaseRef, - request: WithOtherFields, - fee_details: FeeDetails, - block_env: BlockEnv, - ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> { - let mut inspector = - AccessListInspector::new(request.access_list.clone().unwrap_or_default()); - - let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact(env.tx)?; - let (exit_reason, gas_used, out) = match result { - ExecutionResult::Success { reason, gas_used, output, .. } => { - (reason.into(), gas_used, Some(output)) - } - ExecutionResult::Revert { gas_used, output } => { - (InstructionResult::Revert, gas_used, Some(Output::Call(output))) - } - ExecutionResult::Halt { reason, gas_used } => { - (op_haltreason_to_instruction_result(reason), gas_used, None) - } - }; - drop(evm); - let access_list = inspector.access_list(); - Ok((exit_reason, out, gas_used, access_list)) + state: D, + address: Address, + ) -> Result + where + D: DatabaseRef, + { + trace!(target: "backend", "get balance for {:?}", address); + Ok(state.basic_ref(address)?.unwrap_or_default().balance) } - /// returns all receipts for the given transactions - fn get_receipts(&self, tx_hashes: impl IntoIterator) -> Vec { - let storage = self.blockchain.storage.read(); - let mut receipts = vec![]; + pub async fn transaction_by_block_number_and_index( + &self, + number: BlockNumber, + index: Index, + ) -> Result, BlockchainError> { + if let Some(block) = self.mined_block_by_number(number) { + return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index)); + } - for hash in tx_hashes { - if let Some(tx) = storage.transactions.get(&hash) { - receipts.push(tx.receipt.clone()); + if let Some(fork) = self.get_fork() { + let number = self.convert_block_number(Some(number)); + if fork.predates_fork(number) { + return Ok(fork + .transaction_by_block_number_and_index(number, index.into()) + .await?); } } - receipts + Ok(None) } - /// Returns the logs of the block that match the filter - async fn logs_for_block( + pub async fn transaction_by_block_hash_and_index( &self, - filter: Filter, hash: B256, - ) -> Result, BlockchainError> { - if let Some(block) = self.blockchain.get_block_by_hash(&hash) { - return Ok(self.mined_logs_for_block(filter, block)); + index: Index, + ) -> Result, BlockchainError> { + if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { + return Ok(tx); } if let Some(fork) = self.get_fork() { - return Ok(fork.logs(&filter).await?); + return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?); } - Ok(Vec::new()) + Ok(None) } - /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter` - fn mined_logs_for_block(&self, filter: Filter, block: Block) -> Vec { - let mut all_logs = Vec::new(); - let block_hash = block.header.hash_slow(); - let mut block_log_index = 0u32; - - let storage = self.blockchain.storage.read(); - - for tx in block.transactions { - let Some(tx) = storage.transactions.get(&tx.hash()) else { - continue; - }; - - let logs = tx.receipt.logs(); - let transaction_hash = tx.info.transaction_hash; + pub fn mined_transaction_by_block_hash_and_index( + &self, + block_hash: B256, + index: Index, + ) -> Option { + let (info, block, tx) = { + let storage = self.blockchain.storage.read(); + let block = storage.blocks.get(&block_hash).cloned()?; + let index: usize = index.into(); + let tx = block.body.transactions.get(index)?.clone(); + let info = storage.transactions.get(&tx.hash())?.info.clone(); + (info, block, tx) + }; - for log in logs { - if filter.matches(log) { - all_logs.push(Log { - inner: log.clone(), - block_hash: Some(block_hash), - block_number: Some(block.header.number), - block_timestamp: Some(block.header.timestamp), - transaction_hash: Some(transaction_hash), - transaction_index: Some(tx.info.transaction_index), - log_index: Some(block_log_index as u64), - removed: false, - }); - } - block_log_index += 1; - } - } - all_logs + Some(transaction_build( + Some(info.transaction_hash), + tx, + Some(&block), + Some(info), + block.header.base_fee_per_gas(), + )) } - /// Returns the logs that match the filter in the given range of blocks - async fn logs_for_range( + pub async fn transaction_by_hash( &self, - filter: &Filter, - mut from: u64, - to: u64, - ) -> Result, BlockchainError> { - let mut all_logs = Vec::new(); - - // get the range that predates the fork if any - if let Some(fork) = self.get_fork() { - let mut to_on_fork = to; - - if !fork.predates_fork(to) { - // adjust the ranges - to_on_fork = fork.block_number(); - } - - if fork.predates_fork_inclusive(from) { - // this data is only available on the forked client - let filter = filter.clone().from_block(from).to_block(to_on_fork); - all_logs = fork.logs(&filter).await?; - - // update the range - from = fork.block_number() + 1; - } + hash: B256, + ) -> Result, BlockchainError> { + trace!(target: "backend", "transaction_by_hash={:?}", hash); + if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { + return Ok(tx); } - for number in from..=to { - if let Some(block) = self.get_block(number) { - all_logs.extend(self.mined_logs_for_block(filter.clone(), block)); - } + if let Some(fork) = self.get_fork() { + return fork + .transaction_by_hash(hash) + .await + .map_err(BlockchainError::AlloyForkProvider); } - Ok(all_logs) + Ok(None) } - /// Returns the logs according to the filter - pub async fn logs(&self, filter: Filter) -> Result, BlockchainError> { - trace!(target: "backend", "get logs [{:?}]", filter); - if let Some(hash) = filter.get_block_hash() { - self.logs_for_block(filter, hash).await - } else { - let best = self.best_number(); - let to_block = - self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); - let from_block = - self.convert_block_number(filter.block_option.get_from_block().copied()); - if from_block > best { - // requested log range does not exist yet - return Ok(vec![]); - } + pub fn mined_transaction_by_hash(&self, hash: B256) -> Option { + let (info, block) = { + let storage = self.blockchain.storage.read(); + let MinedTransaction { info, block_hash, .. } = + storage.transactions.get(&hash)?.clone(); + let block = storage.blocks.get(&block_hash).cloned()?; + (info, block) + }; + let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); - self.logs_for_range(&filter, from_block, to_block).await - } + Some(transaction_build( + Some(info.transaction_hash), + tx, + Some(&block), + Some(info), + block.header.base_fee_per_gas(), + )) } - pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { - trace!(target: "backend", "get block by hash {:?}", hash); - if let tx @ Some(_) = self.mined_block_by_hash(hash) { - return Ok(tx); + /// Returns the traces for the given transaction + pub async fn trace_transaction( + &self, + hash: B256, + ) -> Result, BlockchainError> { + if let Some(traces) = self.mined_parity_trace_transaction(hash) { + return Ok(traces); } if let Some(fork) = self.get_fork() { - return Ok(fork.block_by_hash(hash).await?); + return Ok(fork.trace_transaction(hash).await?); } - Ok(None) + Ok(vec![]) } - pub async fn block_by_hash_full( + /// Returns the traces for the given block + pub async fn trace_block( &self, - hash: B256, - ) -> Result, BlockchainError> { - trace!(target: "backend", "get block by hash {:?}", hash); - if let tx @ Some(_) = self.get_full_block(hash) { - return Ok(tx); + block: BlockNumber, + ) -> Result, BlockchainError> { + let number = self.convert_block_number(Some(block)); + if let Some(traces) = self.mined_parity_trace_block(number) { + return Ok(traces); } - if let Some(fork) = self.get_fork() { - return Ok(fork.block_by_hash_full(hash).await?) + if let Some(fork) = self.get_fork() + && fork.predates_fork(number) + { + return Ok(fork.trace_block(number).await?); } - Ok(None) + Ok(vec![]) } - fn mined_block_by_hash(&self, hash: B256) -> Option { - let block = self.blockchain.get_block_by_hash(&hash)?; - Some(self.convert_block(block)) + /// Replays all transactions in a block and returns the requested traces for each transaction + pub async fn trace_replay_block_transactions( + &self, + block: BlockNumber, + trace_types: HashSet, + ) -> Result, BlockchainError> { + let block_number = self.convert_block_number(Some(block)); + + // Try mined blocks first + if let Some(results) = + self.mined_parity_trace_replay_block_transactions(block_number, &trace_types) + { + return Ok(results); + } + + // Fallback to fork if block predates fork + if let Some(fork) = self.get_fork() + && fork.predates_fork(block_number) + { + return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?); + } + + Ok(vec![]) } - pub(crate) async fn mined_transactions_by_block_number( + /// Returns the trace results for all transactions in a mined block by replaying them + fn mined_parity_trace_replay_block_transactions( &self, - number: BlockNumber, - ) -> Option> { - if let Some(block) = self.get_block(number) { - return self.mined_transactions_in_block(&block); + block_number: u64, + trace_types: &HashSet, + ) -> Option> { + let block = self.get_block(block_number)?; + + // Execute this in the context of the parent state + let parent_hash = block.header.parent_hash; + let trace_config = TracingInspectorConfig::from_parity_config(trace_types); + + let read_guard = self.states.upgradable_read(); + if let Some(state) = read_guard.get_state(&parent_hash) { + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state = write_guard.get_on_disk_state(&parent_hash)?; + self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) } - None } - /// Returns all transactions given a block - pub(crate) fn mined_transactions_in_block( + /// Replays all transactions in a block with the tracing inspector to generate TraceResults + fn replay_block_transactions_with_inspector( &self, block: &Block, - ) -> Option> { - let mut transactions = Vec::with_capacity(block.transactions.len()); - let base_fee = block.header.base_fee_per_gas; - let storage = self.blockchain.storage.read(); - for hash in block.transactions.iter().map(|tx| tx.hash()) { - let info = storage.transactions.get(&hash)?.info.clone(); - let tx = block.transactions.get(info.transaction_index as usize)?.clone(); + parent_state: &StateDb, + trace_config: TracingInspectorConfig, + trace_types: &HashSet, + ) -> Option> { + let mut cache_db = CacheDB::new(Box::new(parent_state)); + let mut results = Vec::new(); + + // Configure the block environment + let mut evm_env = self.evm_env.read().clone(); + evm_env.block_env = block_env_from_header(&block.header); + + // Execute each transaction in the block with tracing + for tx_envelope in &block.body.transactions { + let tx_hash = tx_envelope.hash(); + + // Create a fresh inspector for this transaction + let mut inspector = TracingInspector::new(trace_config); + + // Prepare transaction environment and execute + let pending_tx = + PendingTransaction::from_maybe_impersonated(tx_envelope.clone()).ok()?; + let (result, _) = self + .transact_envelope_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + pending_tx.transaction.as_ref(), + *pending_tx.sender(), + ) + .ok()?; - let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee); - transactions.push(tx); - } - Some(transactions) - } + // Build TraceResults from the inspector and execution result + let full_trace = inspector + .into_parity_builder() + .into_trace_results_with_state(&result, trace_types, &cache_db) + .ok()?; - pub async fn block_by_number( - &self, - number: BlockNumber, - ) -> Result, BlockchainError> { - trace!(target: "backend", "get block by number {:?}", number); - if let tx @ Some(_) = self.mined_block_by_number(number) { - return Ok(tx); - } + results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace }); - if let Some(fork) = self.get_fork() { - let number = self.convert_block_number(Some(number)); - if fork.predates_fork_inclusive(number) { - return Ok(fork.block_by_number(number).await?) - } + // Commit the state changes for the next transaction + cache_db.commit(result.state); } - Ok(None) + Some(results) } - pub async fn block_by_number_full( + // Returns the traces matching a given filter + pub async fn trace_filter( &self, - number: BlockNumber, - ) -> Result, BlockchainError> { - trace!(target: "backend", "get block by number {:?}", number); - if let tx @ Some(_) = self.get_full_block(number) { - return Ok(tx); + filter: TraceFilter, + ) -> Result, BlockchainError> { + let matcher = filter.matcher(); + let start = filter.from_block.unwrap_or(0); + let end = filter.to_block.unwrap_or_else(|| self.best_number()); + + if start > end { + return Err(BlockchainError::RpcError(RpcError::invalid_params( + "invalid block range, ensure that to block is greater than from block".to_string(), + ))); } - if let Some(fork) = self.get_fork() { - let number = self.convert_block_number(Some(number)); - if fork.predates_fork_inclusive(number) { - return Ok(fork.block_by_number_full(number).await?) - } + let dist = end - start; + if dist > 300 { + return Err(BlockchainError::RpcError(RpcError::invalid_params( + "block range too large, currently limited to 300".to_string(), + ))); } - Ok(None) - } + // Accumulate tasks for block range + let mut trace_tasks = vec![]; + for num in start..=end { + trace_tasks.push(self.trace_block(num.into())); + } - pub fn get_block(&self, id: impl Into) -> Option { - let hash = match id.into() { - BlockId::Hash(hash) => hash.block_hash, - BlockId::Number(number) => { - let storage = self.blockchain.storage.read(); - let slots_in_an_epoch = self.slots_in_an_epoch; - match number { - BlockNumber::Latest => storage.best_hash, - BlockNumber::Earliest => storage.genesis_hash, - BlockNumber::Pending => return None, - BlockNumber::Number(num) => *storage.hashes.get(&num)?, - BlockNumber::Safe => { - if storage.best_number > (slots_in_an_epoch) { - *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? - } else { - storage.genesis_hash // treat the genesis block as safe "by definition" - } - } - BlockNumber::Finalized => { - if storage.best_number > (slots_in_an_epoch * 2) { - *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? - } else { - storage.genesis_hash - } - } - } - } + // Execute tasks and filter traces + let traces = futures::future::try_join_all(trace_tasks).await?; + let filtered_traces = + traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace)); + + // Apply after and count + let filtered_traces: Vec<_> = if let Some(after) = filter.after { + filtered_traces.skip(after as usize).collect() + } else { + filtered_traces.collect() + }; + + let filtered_traces: Vec<_> = if let Some(count) = filter.count { + filtered_traces.into_iter().take(count as usize).collect() + } else { + filtered_traces }; - self.get_block_by_hash(hash) + + Ok(filtered_traces) } - pub fn get_block_by_hash(&self, hash: B256) -> Option { - self.blockchain.get_block_by_hash(&hash) + pub fn get_blobs_by_block_id( + &self, + id: impl Into, + versioned_hashes: Vec, + ) -> Result>> { + Ok(self.get_block(id).map(|block| { + block + .body + .transactions + .iter() + .filter_map(|tx| tx.as_ref().sidecar()) + .flat_map(|sidecar| { + sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter()) + }) + .filter(|(_, commitment)| { + // Filter blobs by versioned_hashes if provided + versioned_hashes.is_empty() + || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice())) + }) + .map(|(blob, _)| *blob) + .collect() + })) } - pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { - let block = self.get_block(number)?; - let mut block = self.convert_block(block); - block.transactions.convert_to_hashes(); - Some(block) + #[allow(clippy::large_stack_frames)] + pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result> { + let storage = self.blockchain.storage.read(); + for block in storage.blocks.values() { + for tx in &block.body.transactions { + let typed_tx = tx.as_ref(); + if let Some(sidecar) = typed_tx.sidecar() { + for versioned_hash in sidecar.sidecar.versioned_hashes() { + if versioned_hash == hash + && let Some(index) = + sidecar.sidecar.commitments().iter().position(|commitment| { + kzg_to_versioned_hash(commitment.as_slice()) == *hash + }) + && let Some(blob) = sidecar.sidecar.blobs().get(index) + { + return Ok(Some(*blob)); + } + } + } + } + } + Ok(None) } - pub fn get_full_block(&self, id: impl Into) -> Option { - let block = self.get_block(id)?; - let transactions = self.mined_transactions_in_block(&block)?; - let mut block = self.convert_block(block); - block.inner.transactions = BlockTransactions::Full(transactions); + /// Initialises the balance of the given accounts + #[expect(clippy::too_many_arguments)] + pub async fn with_genesis( + db: Arc>>, + env: Arc>, + networks: NetworkConfigs, + genesis: GenesisConfig, + fees: FeeManager, + fork: Arc>>, + enable_steps_tracing: bool, + print_logs: bool, + print_traces: bool, + call_trace_decoder: Arc, + prune_state_history_config: PruneStateHistoryConfig, + max_persisted_states: Option, + transaction_block_keeper: Option, + automine_block_time: Option, + cache_path: Option, + node_config: Arc>, + ) -> Result { + // if this is a fork then adjust the blockchain storage + let blockchain = if let Some(fork) = fork.read().as_ref() { + trace!(target: "backend", "using forked blockchain at {}", fork.block_number()); + Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty()) + } else { + Blockchain::new( + &env.read(), + fees.is_eip1559().then(|| fees.base_fee()), + genesis.timestamp, + genesis.number, + ) + }; - Some(block) - } + // Sync EVM block.number with genesis for non-fork mode. + // Fork mode syncs in setup_fork_db_config() instead. + if fork.read().is_none() { + env.write().block_env.number = U256::from(genesis.number); + } - /// Takes a block as it's stored internally and returns the eth api conform block format. - pub fn convert_block(&self, block: Block) -> AnyRpcBlock { - let size = U256::from(alloy_rlp::encode(&block).len() as u32); + let start_timestamp = if let Some(fork) = fork.read().as_ref() { + fork.timestamp() + } else { + genesis.timestamp + }; - let Block { header, transactions, .. } = block; + let mut states = if prune_state_history_config.is_config_enabled() { + // if prune state history is enabled, configure the state cache only for memory + prune_state_history_config + .max_memory_history + .map(|limit| InMemoryBlockStates::new(limit, 0)) + .unwrap_or_default() + .memory_only() + } else if max_persisted_states.is_some() { + max_persisted_states + .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit)) + .unwrap_or_default() + } else { + Default::default() + }; - let hash = header.hash_slow(); - let Header { number, withdrawals_root, .. } = header; + if let Some(cache_path) = cache_path { + states = states.disk_path(cache_path); + } - let block = AlloyBlock { - header: AlloyHeader { - inner: AnyHeader::from(header), - hash, - total_difficulty: Some(self.total_difficulty()), - size: Some(size), - }, - transactions: alloy_rpc_types::BlockTransactions::Hashes( - transactions.into_iter().map(|tx| tx.hash()).collect(), - ), - uncles: vec![], - withdrawals: withdrawals_root.map(|_| Default::default()), + let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = { + let cfg = node_config.read().await; + ( + cfg.slots_in_an_epoch, + cfg.precompile_factory.clone(), + cfg.disable_pool_balance_checks, + cfg.get_hardfork(), + ) }; - let mut block = WithOtherFields::new(block); + let backend = Self { + db, + blockchain, + states: Arc::new(RwLock::new(states)), + evm_env: env, + networks, + hardfork, + fork, + time: TimeManager::new(start_timestamp), + cheats: Default::default(), + new_block_listeners: Default::default(), + fees, + genesis, + active_state_snapshots: Arc::new(Mutex::new(Default::default())), + enable_steps_tracing, + print_logs, + print_traces, + call_trace_decoder, + prune_state_history_config, + transaction_block_keeper, + node_config, + slots_in_an_epoch, + precompile_factory, + mining: Arc::new(tokio::sync::Mutex::new(())), + disable_pool_balance_checks, + }; - // If Arbitrum, apply chain specifics to converted block. - if is_arbitrum(self.env.read().evm_env.cfg_env.chain_id) { - // Set `l1BlockNumber` field. - block.other.insert("l1BlockNumber".to_string(), number.into()); + if let Some(interval_block_time) = automine_block_time { + backend.update_interval_mine_block_time(interval_block_time); } - AnyRpcBlock::from(block) + // Note: this can only fail in forking mode, in which case we can't recover + backend.apply_genesis().await.wrap_err("failed to create genesis")?; + Ok(backend) } - /// Converts the `BlockNumber` into a numeric value - /// - /// # Errors + /// Applies the configured genesis settings /// - /// returns an error if the requested number is larger than the current height - pub async fn ensure_block_number>( - &self, - block_id: Option, - ) -> Result { - let current = self.best_number(); - let requested = - match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { - BlockId::Hash(hash) => { - self.block_by_hash(hash.block_hash) - .await? - .ok_or(BlockchainError::BlockNotFound)? - .header - .number - } - BlockId::Number(num) => match num { - BlockNumber::Latest | BlockNumber::Pending => self.best_number(), - BlockNumber::Earliest => U64::ZERO.to::(), - BlockNumber::Number(num) => num, - BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), - }, - }; + /// This will fund, create the genesis accounts + async fn apply_genesis(&self) -> Result<(), DatabaseError> { + trace!(target: "backend", "setting genesis balances"); - if requested > current { - Err(BlockchainError::BlockOutOfRange(current, requested)) - } else { - Ok(requested) - } + if self.fork.read().is_some() { + // fetch all account first + let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len()); + for address in self.genesis.accounts.iter().copied() { + let db = Arc::clone(&self.db); + + // The forking Database backend can handle concurrent requests, we can fetch all dev + // accounts concurrently by spawning the job to a new task + genesis_accounts_futures.push(tokio::task::spawn(async move { + let db = db.read().await; + let info = db.basic_ref(address)?.unwrap_or_default(); + Ok::<_, DatabaseError>((address, info)) + })); + } + + let genesis_accounts = futures::future::join_all(genesis_accounts_futures).await; + + let mut db = self.db.write().await; + + for res in genesis_accounts { + let (address, mut info) = res.unwrap()?; + info.balance = self.genesis.balance; + db.insert_account(address, info.clone()); + } + } else { + let mut db = self.db.write().await; + for (account, info) in self.genesis.account_infos() { + db.insert_account(account, info); + } + + // insert the new genesis hash to the database so it's available for the next block in + // the evm + db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); + + // Deploy EIP-2935 blockhash history storage contract if Prague is active. + if self.spec_id() >= SpecId::PRAGUE { + db.set_code( + eip2935::HISTORY_STORAGE_ADDRESS, + eip2935::HISTORY_STORAGE_CODE.clone(), + )?; + } + } + + let db = self.db.write().await; + // apply the genesis.json alloc + self.genesis.apply_genesis_json_alloc(db)?; + + // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode). + // In fork mode, precompiles are inherited from the forked origin. + if self.networks.is_tempo() && !self.is_fork() { + let chain_id = self.evm_env.read().cfg_env.chain_id; + let timestamp = self.genesis.timestamp; + let test_accounts: Vec
= self.genesis.accounts.clone(); + let hardfork = TempoHardfork::from(self.hardfork); + let mut db = self.db.write().await; + crate::eth::backend::tempo::initialize_tempo_precompiles( + &mut **db, + chain_id, + timestamp, + &test_accounts, + hardfork, + ) + .map_err(|e| { + tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}"); + DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}"))) + })?; + trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len()); + } + + trace!(target: "backend", "set genesis balances"); + + Ok(()) + } + + /// Resets the fork to a fresh state + pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> { + if !self.is_fork() { + if let Some(eth_rpc_url) = forking.json_rpc_url.clone() { + let mut evm_env = self.evm_env.read().clone(); + + let (db, config) = { + let mut node_config = self.node_config.write().await; + + // we want to force the correct base fee for the next block during + // `setup_fork_db_config` + node_config.base_fee.take(); + node_config.fork_urls = vec![eth_rpc_url.clone()]; + + node_config.setup_fork_db_config(eth_rpc_url, &mut evm_env, &self.fees).await? + }; + + *self.db.write().await = Box::new(db); + + let fork = ClientFork::new(config, Arc::clone(&self.db)); + + *self.evm_env.write() = evm_env; + *self.fork.write() = Some(fork); + } else { + return Err(RpcError::invalid_params( + "Forking not enabled and RPC URL not provided to start forking", + ) + .into()); + } + } + + if let Some(fork) = self.get_fork() { + let block_number = + forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); + // reset the fork entirely and reapply the genesis config + let reset_urls = + forking.json_rpc_url.as_ref().map(|url| vec![url.clone()]).unwrap_or_default(); + fork.reset(reset_urls, block_number).await?; + let fork_block_number = fork.block_number(); + let fork_block = fork + .block_by_number(fork_block_number) + .await? + .ok_or(BlockchainError::BlockNotFound)?; + // update all settings related to the forked block + { + if let Some(fork_url) = forking.json_rpc_url { + self.reset_block_number(fork_url, fork_block_number).await?; + } else { + // If rpc url is unspecified, then update the fork with the new block number and + // existing rpc url, this updates the cache path + { + let maybe_fork_url = + { self.node_config.read().await.fork_urls.first().cloned() }; + if let Some(fork_url) = maybe_fork_url { + self.reset_block_number(fork_url, fork_block_number).await?; + } + } + + let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block); + let mut env = self.evm_env.write(); + + env.cfg_env.chain_id = fork.chain_id(); + env.block_env = BlockEnv { + number: U256::from(fork_block_number), + timestamp: U256::from(fork_block.header.timestamp()), + gas_limit, + difficulty: fork_block.header.difficulty(), + prevrandao: Some(fork_block.header.mix_hash().unwrap_or_default()), + // Keep previous `beneficiary` and `basefee` value + beneficiary: env.block_env.beneficiary, + basefee: env.block_env.basefee, + ..env.block_env.clone() + }; + + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + fork_block.header.gas_used(), + gas_limit, + fork_block.header.base_fee_per_gas().unwrap_or_default(), + ); + + self.fees.set_base_fee(next_block_base_fee); + } + + // reset the time to the timestamp of the forked block + self.time.reset(fork_block.header.timestamp()); + + // also reset the total difficulty + self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); + } + // reset storage + *self.blockchain.storage.write() = BlockchainStorage::forked( + fork.block_number(), + fork.block_hash(), + fork.total_difficulty(), + ); + self.states.write().clear(); + self.db.write().await.clear(); + + self.apply_genesis().await?; + + trace!(target: "backend", "reset fork"); + + Ok(()) + } else { + Err(RpcError::invalid_params("Forking not enabled").into()) + } + } + + /// Resets the backend to a fresh in-memory state, clearing all existing data + pub async fn reset_to_in_mem(&self) -> Result<(), BlockchainError> { + // Clear the fork if any exists + *self.fork.write() = None; + + let genesis_timestamp = self.genesis.timestamp; + let genesis_number = self.genesis.number; + + // Reset environment to genesis state + { + let mut env = self.evm_env.write(); + env.block_env.number = U256::from(genesis_number); + env.block_env.timestamp = U256::from(genesis_timestamp); + // Reset other block env fields to their defaults + env.block_env.basefee = self.fees.base_fee(); + env.block_env.prevrandao = Some(B256::ZERO); + } + + // Clear all storage and reinitialize with genesis + let base_fee = self.fees.is_eip1559().then(|| self.fees.base_fee()); + *self.blockchain.storage.write() = BlockchainStorage::new( + &self.evm_env.read(), + base_fee, + genesis_timestamp, + genesis_number, + ); + self.states.write().clear(); + + // Clear the database + self.db.write().await.clear(); + + // Reset time manager + self.time.reset(genesis_timestamp); + + // Reset fees to initial state + if self.fees.is_eip1559() { + self.fees.set_base_fee(crate::eth::fees::INITIAL_BASE_FEE); + } + + self.fees.set_gas_price(crate::eth::fees::INITIAL_GAS_PRICE); + + // Reapply genesis configuration + self.apply_genesis().await?; + + trace!(target: "backend", "reset to fresh in-memory state"); + + Ok(()) + } + + async fn reset_block_number( + &self, + fork_url: String, + fork_block_number: u64, + ) -> Result<(), BlockchainError> { + let mut node_config = self.node_config.write().await; + node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128)); + // Update fork_urls so setup_fork_db_config uses the correct URL set + node_config.fork_urls = vec![fork_url.clone()]; + + let mut evm_env = self.evm_env.read().clone(); + let (forked_db, client_fork_config) = + node_config.setup_fork_db_config(fork_url, &mut evm_env, &self.fees).await?; + + *self.db.write().await = Box::new(forked_db); + let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); + *self.fork.write() = Some(fork); + *self.evm_env.write() = evm_env; + + Ok(()) + } + + /// Reverts the state to the state snapshot identified by the given `id`. + pub async fn revert_state_snapshot(&self, id: U256) -> Result { + let block = { self.active_state_snapshots.lock().remove(&id) }; + if let Some((num, hash)) = block { + let best_block_hash = { + // revert the storage that's newer than the snapshot + let current_height = self.best_number(); + let mut storage = self.blockchain.storage.write(); + + for n in ((num + 1)..=current_height).rev() { + trace!(target: "backend", "reverting block {}", n); + if let Some(hash) = storage.hashes.remove(&n) + && let Some(block) = storage.blocks.remove(&hash) + { + for tx in block.body.transactions { + let _ = storage.transactions.remove(&tx.hash()); + } + } + } + + storage.best_number = num; + storage.best_hash = hash; + hash + }; + let block = + self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; + + let reset_time = block.header.timestamp(); + self.time.reset(reset_time); + + let mut env = self.evm_env.write(); + env.block_env = BlockEnv { + number: U256::from(num), + timestamp: U256::from(block.header.timestamp()), + difficulty: block.header.difficulty(), + // ensures prevrandao is set + prevrandao: Some(block.header.mix_hash().unwrap_or_default()), + gas_limit: block.header.gas_limit(), + // Keep previous `beneficiary` and `basefee` value + beneficiary: env.block_env.beneficiary, + basefee: env.block_env.basefee, + ..Default::default() + } + } + Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove)) + } + + /// executes the transactions without writing to the underlying database + pub async fn inspect_tx( + &self, + tx: Arc>, + ) -> Result< + (InstructionResult, Option, u64, State, Vec), + BlockchainError, + > { + let evm_env = self.next_evm_env(); + let db = self.db.read().await; + let mut inspector = self.build_inspector(); + let (ResultAndState { result, state }, _) = self.transact_envelope_with_inspector_ref( + &**db, + &evm_env, + &mut inspector, + tx.pending_transaction.transaction.as_ref(), + *tx.pending_transaction.sender(), + )?; + let (exit_reason, gas_used, out, logs) = unpack_execution_result(result); + + inspector.print_logs(); + + if self.print_traces { + inspector.print_traces(self.call_trace_decoder.clone()); + } + + Ok((exit_reason, out, gas_used, state, logs)) + } +} + +impl Backend +where + N::ReceiptEnvelope: TxReceipt, +{ + /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter` + fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec { + let mut all_logs = Vec::new(); + let mut block_log_index = 0u32; + + let storage = self.blockchain.storage.read(); + + for tx in block.body.transactions { + let Some(tx) = storage.transactions.get(&tx.hash()) else { + continue; + }; + + let logs = tx.receipt.logs(); + let transaction_hash = tx.info.transaction_hash; + + for log in logs { + if filter.matches(log) { + all_logs.push(Log { + inner: log.clone(), + block_hash: Some(block_hash), + block_number: Some(block.header.number()), + block_timestamp: Some(block.header.timestamp()), + transaction_hash: Some(transaction_hash), + transaction_index: Some(tx.info.transaction_index), + log_index: Some(block_log_index as u64), + removed: false, + }); + } + block_log_index += 1; + } + } + all_logs + } + + /// Returns the logs of the block that match the filter + async fn logs_for_block( + &self, + filter: Filter, + hash: B256, + ) -> Result, BlockchainError> { + if let Some(block) = self.blockchain.get_block_by_hash(&hash) { + return Ok(self.mined_logs_for_block(filter, block, hash)); + } + + if let Some(fork) = self.get_fork() { + return Ok(fork.logs(&filter).await?); + } + + Err(BlockchainError::UnknownBlock) + } + + /// Returns the logs that match the filter in the given range of blocks + async fn logs_for_range( + &self, + filter: &Filter, + mut from: u64, + to: u64, + ) -> Result, BlockchainError> { + let mut all_logs = Vec::new(); + + // get the range that predates the fork if any + if let Some(fork) = self.get_fork() { + let to_on_fork = if fork.predates_fork(to) { + to + } else { + // adjust the ranges + fork.block_number() + }; + + if fork.predates_fork_inclusive(from) { + // this data is only available on the forked client + let filter = filter.clone().from_block(from).to_block(to_on_fork); + all_logs = fork.logs(&filter).await?; + + // update the range + from = fork.block_number() + 1; + } + } + + for number in from..=to { + if let Some((block, hash)) = self.get_block_with_hash(number) { + all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash)); + } + } + + Ok(all_logs) + } + + /// Returns the logs according to the filter + pub async fn logs(&self, filter: Filter) -> Result, BlockchainError> { + trace!(target: "backend", "get logs [{:?}]", filter); + if let Some(hash) = filter.get_block_hash() { + self.logs_for_block(filter, hash).await + } else { + let best = self.best_number(); + let to_block = + self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); + let from_block = + self.convert_block_number(filter.block_option.get_from_block().copied()); + if from_block > best { + return Err(BlockchainError::BlockOutOfRange(best, from_block)); + } + + self.logs_for_range(&filter, from_block, to_block).await + } + } + + /// Returns all receipts of the block + pub fn mined_receipts(&self, hash: B256) -> Option> { + let block = self.mined_block_by_hash(hash)?; + let mut receipts = Vec::new(); + let storage = self.blockchain.storage.read(); + for tx in block.transactions.hashes() { + let receipt = storage.transactions.get(&tx)?.receipt.clone(); + receipts.push(receipt); + } + Some(receipts) + } +} + +// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now. +impl Backend +where + Self: TransactionValidator, + N: Network, +{ + /// Mines a new block and stores it. + /// + /// this will execute all transaction in the order they come in and return all the markers they + /// provide. + pub async fn mine_block( + &self, + pool_transactions: Vec>>, + ) -> MinedBlockOutcome { + self.do_mine_block(pool_transactions).await + } + + /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions. + fn build_block_info( + evm_env: &EvmEnv, + parent_hash: B256, + number: u64, + state_root: B256, + block_result: BlockExecutionResult, + transactions: Vec>, + transaction_infos: Vec, + ) -> BlockInfo { + let spec_id = *evm_env.spec_id(); + let is_shanghai = spec_id >= SpecId::SHANGHAI; + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + + let receipts_root = calculate_receipt_root(&block_result.receipts); + let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used); + let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| { + b.accrue_bloom(r.logs_bloom()); + b + }); + + let header = Header { + parent_hash, + ommers_hash: Default::default(), + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root: Default::default(), + receipts_root, + logs_bloom: bloom, + difficulty: evm_env.block_env.difficulty, + number, + gas_limit: evm_env.block_env.gas_limit, + gas_used: block_result.gas_used, + timestamp: evm_env.block_env.timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: Default::default(), + base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee), + parent_beacon_block_root: is_cancun.then_some(Default::default()), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None }, + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), + block_access_list_hash: None, + slot_number: None, + }; + + let block = create_block(header, transactions); + BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts } + } + + async fn do_mine_block( + &self, + pool_transactions: Vec>>, + ) -> MinedBlockOutcome { + let _mining_guard = self.mining.lock().await; + trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len()); + + let (outcome, header, block_hash) = { + let current_base_fee = self.base_fee(); + let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price(); + + let mut evm_env = self.evm_env.read().clone(); + + if evm_env.block_env.basefee == 0 { + // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` + // 0 is only possible if it's manually set + evm_env.cfg_env.disable_base_fee = true; + } + + let block_number = self.blockchain.storage.read().best_number.saturating_add(1); + + // increase block number for this block + if is_arbitrum(evm_env.cfg_env.chain_id) { + // Temporary set `env.block.number` to `block_number` for Arbitrum chains. + evm_env.block_env.number = U256::from(block_number); + } else { + evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1)); + } + + evm_env.block_env.basefee = current_base_fee; + evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + + let best_hash = self.blockchain.storage.read().best_hash; + + let mut input = Vec::with_capacity(40); + input.extend_from_slice(best_hash.as_slice()); + input.extend_from_slice(&block_number.to_le_bytes()); + evm_env.block_env.prevrandao = Some(keccak256(&input)); + + if self.prune_state_history_config.is_state_history_supported() { + let db = self.db.read().await.current_state(); + // store current state before executing all transactions + self.states.write().insert(best_hash, db); + } + + let (block_info, included, invalid, not_yet_valid, block_hash) = { + let mut db = self.db.write().await; + + // finally set the next block timestamp, this is done just before execution, because + // there can be concurrent requests that can delay acquiring the db lock and we want + // to ensure the timestamp is as close as possible to the actual execution. + evm_env.block_env.timestamp = U256::from(self.time.next_timestamp()); + + let spec_id = *evm_env.spec_id(); + + let inspector_tx_config = self.inspector_tx_config(); + let gas_config = self.pool_tx_gas_config(&evm_env); + + let (pool_result, block_result) = self.execute_with_block_executor( + &mut **db, + &evm_env, + best_hash, + spec_id, + &pool_transactions, + &gas_config, + &inspector_tx_config, + &|pending, account| { + self.validate_pool_transaction_for(pending, account, &evm_env) + }, + ); + + let included = pool_result.included; + let invalid = pool_result.invalid; + let not_yet_valid = pool_result.not_yet_valid; + + let state_root = db.maybe_state_root().unwrap_or_default(); + let block_info = Self::build_block_info( + &evm_env, + best_hash, + block_number, + state_root, + block_result, + pool_result.txs, + pool_result.tx_info, + ); + + // update the new blockhash in the db itself + let block_hash = block_info.block.header.hash_slow(); + db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash); + + (block_info, included, invalid, not_yet_valid, block_hash) + }; + + // create the new block with the current timestamp + let BlockInfo { block, transactions, receipts } = block_info; + + let header = block.header.clone(); + + trace!( + target: "backend", + "Mined block {} with {} tx {:?}", + block_number, + transactions.len(), + transactions.iter().map(|tx| tx.transaction_hash).collect::>() + ); + let mut storage = self.blockchain.storage.write(); + // update block metadata + storage.best_number = block_number; + storage.best_hash = block_hash; + // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with + // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27 + if !self.is_eip3675() { + storage.total_difficulty = + storage.total_difficulty.saturating_add(header.difficulty); + } + + storage.blocks.insert(block_hash, block); + storage.hashes.insert(block_number, block_hash); + + node_info!(""); + // insert all transactions + for (info, receipt) in transactions.into_iter().zip(receipts) { + // log some tx info + node_info!(" Transaction: {:?}", info.transaction_hash); + if let Some(contract) = &info.contract_address { + node_info!(" Contract created: {contract}"); + } + node_info!(" Gas used: {}", receipt.cumulative_gas_used()); + if !info.exit.is_ok() { + let r = RevertDecoder::new().decode( + info.out.as_ref().map(|b| &b[..]).unwrap_or_default(), + Some(info.exit), + ); + node_info!(" Error: reverted with: {r}"); + } + node_info!(""); + + let mined_tx = MinedTransaction { info, receipt, block_hash, block_number }; + storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); + } + + // remove old transactions that exceed the transaction block keeper + if let Some(transaction_block_keeper) = self.transaction_block_keeper + && storage.blocks.len() > transaction_block_keeper + { + let to_clear = block_number + .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX)); + storage.remove_block_transactions_by_number(to_clear) + } + + // we intentionally set the difficulty to `0` for newer blocks + evm_env.block_env.difficulty = U256::from(0); + + // update env with new values + *self.evm_env.write() = evm_env; + + let timestamp = utc_from_secs(header.timestamp); + + node_info!(" Block Number: {}", block_number); + node_info!(" Block Hash: {:?}", block_hash); + if timestamp.year() > 9999 { + // rf2822 panics with more than 4 digits + node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339()); + } else { + node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822()); + } + + let outcome = MinedBlockOutcome { block_number, included, invalid, not_yet_valid }; + + (outcome, header, block_hash) + }; + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + header.gas_used, + header.gas_limit, + header.base_fee_per_gas.unwrap_or_default(), + ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas.unwrap_or_default(), + header.blob_gas_used.unwrap_or_default(), + ); + + // update next base fee + self.fees.set_base_fee(next_block_base_fee); + + self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_excess_blob_gas, + get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()), + )); + + // notify all listeners + self.notify_on_new_block(header, block_hash); + + outcome + } + + /// Reorg the chain to a common height and execute blocks to build new chain. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + /// + /// Finally, `do_mine_block` is called to create the new chain. + pub async fn reorg( + &self, + depth: u64, + tx_pairs: HashMap>>>, + common_block: Block, + ) -> Result<(), BlockchainError> { + self.rollback(common_block).await?; + // Create the new reorged chain, filling the blocks with transactions if supplied + for i in 0..depth { + let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); + let outcome = self.do_mine_block(to_be_mined).await; + node_info!( + " Mined reorg block number {}. With {} valid txs and with invalid {} txs", + outcome.block_number, + outcome.included.len(), + outcome.invalid.len() + ); + } + + Ok(()) + } + + /// Creates the pending block + /// + /// This will execute all transaction in the order they come but will not mine the block + pub async fn pending_block( + &self, + pool_transactions: Vec>>, + ) -> BlockInfo { + self.with_pending_block(pool_transactions, |_, block| block).await + } + + /// Creates the pending block + /// + /// This will execute all transaction in the order they come but will not mine the block + pub async fn with_pending_block( + &self, + pool_transactions: Vec>>, + f: F, + ) -> T + where + F: FnOnce(Box, BlockInfo) -> T, + { + let db = self.db.read().await; + let evm_env = self.next_evm_env(); + + let mut cache_db = AnvilCacheDB::new(&*db); + + let parent_hash = self.blockchain.storage.read().best_hash; + + let spec_id = *evm_env.spec_id(); + + let inspector_tx_config = self.inspector_tx_config(); + let gas_config = self.pool_tx_gas_config(&evm_env); + + let (pool_result, block_result) = self.execute_with_block_executor( + &mut cache_db, + &evm_env, + parent_hash, + spec_id, + &pool_transactions, + &gas_config, + &inspector_tx_config, + &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env), + ); + + // Extract inner CacheDB (which implements MaybeFullDatabase) + let cache_db = cache_db.0; + + let state_root = cache_db.maybe_state_root().unwrap_or_default(); + let block_number = evm_env.block_env.number.saturating_to(); + let block_info = Self::build_block_info( + &evm_env, + parent_hash, + block_number, + state_root, + block_result, + pool_result.txs, + pool_result.tx_info, + ); + + f(Box::new(cache_db), block_info) + } + + /// Returns the ERC20/TIP20 token balance for an account. + /// + /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if + /// the call fails (e.g. the token contract doesn't exist). + pub async fn get_fee_token_balance( + &self, + token: Address, + account: Address, + ) -> Result { + // balanceOf(address) selector: 0x70a08231 + let mut calldata = vec![0x70, 0xa0, 0x82, 0x31]; + // ABI-encode the address (left-padded to 32 bytes) + calldata.extend_from_slice(&[0u8; 12]); + calldata.extend_from_slice(account.as_slice()); + + let request = WithOtherFields::new(TransactionRequest { + from: Some(Address::ZERO), + to: Some(TxKind::Call(token)), + input: calldata.into(), + ..Default::default() + }); + + let fee_details = FeeDetails::zero(); + let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?; + + // Check if call succeeded + if exit != InstructionResult::Return && exit != InstructionResult::Stop { + // Return zero balance if call failed (token might not exist) + return Ok(U256::ZERO); + } + + // Decode U256 from output + match out { + Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])), + _ => Ok(U256::ZERO), + } + } + + /// Executes the [TransactionRequest] without writing to the DB + /// + /// # Errors + /// + /// Returns an error if the `block_number` is greater than the current height + pub async fn call( + &self, + request: WithOtherFields, + fee_details: FeeDetails, + block_request: Option>, + overrides: EvmOverrides, + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { + self.with_database_at(block_request, |state, mut block| { + let block_number = block.number; + let (exit, out, gas, state) = { + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = overrides.state { + apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?; + } + if let Some(block_overrides) = overrides.block { + cache_db.apply_block_overrides(*block_overrides, &mut block); + } + self.call_with_state(&cache_db, request, fee_details, block) + }?; + trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); + Ok((exit, out, gas, state)) + }).await? } - pub fn convert_block_number(&self, block: Option) -> u64 { - let current = self.best_number(); - match block.unwrap_or(BlockNumber::Latest) { - BlockNumber::Latest | BlockNumber::Pending => current, - BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num, - BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), - } + pub async fn call_with_tracing( + &self, + request: WithOtherFields, + fee_details: FeeDetails, + block_request: Option>, + opts: GethDebugTracingCallOptions, + ) -> Result { + let GethDebugTracingCallOptions { + tracing_options, block_overrides, state_overrides, .. + } = opts; + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; + + self.with_database_at(block_request, |state, mut block| { + let block_number = block.number; + + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = state_overrides { + apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + cache_db.apply_block_overrides(block_overrides, &mut block); + } + + if let Some(tracer) = tracer { + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::CallTracer => { + let call_config = tracer_config + .into_call_config() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; + + let mut inspector = self.build_inspector().with_tracing_config( + TracingInspectorConfig::from_geth_call_config(&call_config), + ); + + let (evm_env, tx_env, op_deposit) = + self.build_call_env(request, fee_details, block); + let ResultAndState { result, state: _ } = self + .transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; + + inspector.print_logs(); + if self.print_traces { + inspector.print_traces(self.call_trace_decoder.clone()); + } + + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); + + Ok(tracing_inspector + .into_geth_builder() + .geth_call_traces(call_config, result.tx_gas_used()) + .into()) + } + GethDebugBuiltInTracerType::PreStateTracer => { + let pre_state_config = tracer_config + .into_pre_state_config() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; + + let mut inspector = TracingInspector::new( + TracingInspectorConfig::from_geth_prestate_config( + &pre_state_config, + ), + ); + + let (evm_env, tx_env, op_deposit) = + self.build_call_env(request, fee_details, block); + let result = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; + + Ok(inspector + .into_geth_builder() + .geth_prestate_traces(&result, &pre_state_config, cache_db)? + .into()) + } + GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), + GethDebugBuiltInTracerType::FourByteTracer + | GethDebugBuiltInTracerType::MuxTracer + | GethDebugBuiltInTracerType::FlatCallTracer + | GethDebugBuiltInTracerType::Erc7562Tracer => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + }, + #[cfg(not(feature = "js-tracer"))] + GethDebugTracerType::JsTracer(_) => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + #[cfg(feature = "js-tracer")] + GethDebugTracerType::JsTracer(code) => { + let config = tracer_config.into_json(); + let mut inspector = + revm_inspectors::tracing::js::JsInspector::new(code, config) + .map_err(|err| BlockchainError::Message(err.to_string()))?; + + let (evm_env, tx_env, op_deposit) = + self.build_call_env(request, fee_details, block.clone()); + let result = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env.clone(), + op_deposit, + )?; + let res = inspector + .json_result(result, &tx_env, &block, &cache_db) + .map_err(|err| BlockchainError::Message(err.to_string()))?; + + Ok(GethTrace::JS(res)) + } + }; + } + + // defaults to StructLog tracer used since no tracer is specified + let mut inspector = self + .build_inspector() + .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); + + let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block); + let ResultAndState { result, state: _ } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; + + let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result); + + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); + let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default(); + + trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); + + let res = tracing_inspector + .into_geth_builder() + .geth_traces(gas_used, return_value, config) + .into(); + + Ok(res) + }) + .await? } /// Helper function to execute a closure with the database at a specific block pub async fn with_database_at( &self, - block_request: Option, + block_request: Option>, f: F, ) -> Result where @@ -2324,17 +3118,7 @@ impl Backend { let result = self .with_pending_block(pool_transactions, |state, block| { let block = block.block; - let block = BlockEnv { - number: block.header.number, - beneficiary: block.header.beneficiary, - timestamp: block.header.timestamp, - difficulty: block.header.difficulty, - prevrandao: Some(block.header.mix_hash), - basefee: block.header.base_fee_per_gas.unwrap_or_default(), - gas_limit: block.header.gas_limit, - ..Default::default() - }; - f(state, block) + f(state, block_env_from_header(&block.header)) }) .await; return Ok(result); @@ -2343,37 +3127,36 @@ impl Backend { None => None, }; let block_number = self.convert_block_number(block_number); + let current_number = self.best_number(); + + // Reject requests for future blocks that don't exist yet + if block_number > current_number { + return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); + } - if block_number < self.env.read().evm_env.block_env.number { + if block_number < current_number { if let Some((block_hash, block)) = self .block_by_number(BlockNumber::Number(block_number)) .await? .map(|block| (block.header.hash, block)) { - if let Some(state) = self.states.write().get(&block_hash) { - let block = BlockEnv { - number: block_number, - beneficiary: block.header.beneficiary, - timestamp: block.header.timestamp, - difficulty: block.header.difficulty, - prevrandao: block.header.mix_hash, - basefee: block.header.base_fee_per_gas.unwrap_or_default(), - gas_limit: block.header.gas_limit, - ..Default::default() - }; - return Ok(f(Box::new(state), block)); + let read_guard = self.states.upgradable_read(); + if let Some(state_db) = read_guard.get_state(&block_hash) { + return Ok(f(Box::new(state_db), block_env_from_header(&block.header))); + } + + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + if let Some(state) = write_guard.get_on_disk_state(&block_hash) { + return Ok(f(Box::new(state), block_env_from_header(&block.header))); } } warn!(target: "backend", "Not historic state found for block={}", block_number); - return Err(BlockchainError::BlockOutOfRange( - self.env.read().evm_env.block_env.number, - block_number, - )); + return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); } let db = self.db.read().await; - let block = self.env.read().evm_env.block_env.clone(); + let block = self.evm_env.read().block_env.clone(); Ok(f(Box::new(&**db), block)) } @@ -2381,7 +3164,7 @@ impl Backend { &self, address: Address, index: U256, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| { trace!(target: "backend", "get storage for {:?} at {:?}", address, index); @@ -2391,6 +3174,28 @@ impl Backend { .await? } + /// Returns storage values for multiple accounts and slots in a single call. + pub async fn storage_values( + &self, + requests: HashMap>, + block_request: Option>, + ) -> Result>, BlockchainError> { + self.with_database_at(block_request, |db, _| { + trace!(target: "backend", "get storage values for {} addresses", requests.len()); + let mut result: HashMap> = HashMap::default(); + for (address, slots) in &requests { + let mut values = Vec::with_capacity(slots.len()); + for slot in slots { + let val = db.storage_ref(*address, (*slot).into())?; + values.push(val.into()); + } + result.insert(*address, values); + } + Ok(result) + }) + .await? + } + /// Returns the code of the address /// /// If the code is not present and fork mode is enabled then this will try to fetch it from the @@ -2398,37 +3203,18 @@ impl Backend { pub async fn get_code( &self, address: Address, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await? } - pub fn get_code_with_state( - &self, - state: &dyn DatabaseRef, - address: Address, - ) -> Result { - trace!(target: "backend", "get code for {:?}", address); - let account = state.basic_ref(address)?.unwrap_or_default(); - if account.code_hash == KECCAK_EMPTY { - // if the code hash is `KECCAK_EMPTY`, we check no further - return Ok(Default::default()); - } - let code = if let Some(code) = account.code { - code - } else { - state.code_by_hash_ref(account.code_hash)? - }; - Ok(code.bytes()[..code.len()].to_vec().into()) - } - /// Returns the balance of the address /// /// If the requested number predates the fork then this will fetch it from the endpoint pub async fn get_balance( &self, address: Address, - block_request: Option, + block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address)) .await? @@ -2437,8 +3223,8 @@ impl Backend { pub async fn get_account_at_block( &self, address: Address, - block_request: Option, - ) -> Result { + block_request: Option>, + ) -> Result { self.with_database_at(block_request, |block_db, _| { let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; let account = db.get(&address).cloned().unwrap_or_default(); @@ -2446,35 +3232,23 @@ impl Backend { let code_hash = account.info.code_hash; let balance = account.info.balance; let nonce = account.info.nonce; - Ok(Account { balance, nonce, code_hash, storage_root }) + Ok(TrieAccount { balance, nonce, code_hash, storage_root }) }) .await? } - pub fn get_balance_with_state( - &self, - state: D, - address: Address, - ) -> Result - where - D: DatabaseRef, - { - trace!(target: "backend", "get balance for {:?}", address); - Ok(state.basic_ref(address)?.unwrap_or_default().balance) - } - /// Returns the nonce of the address /// /// If the requested number predates the fork then this will fetch it from the endpoint pub async fn get_nonce( &self, address: Address, - block_request: BlockRequest, + block_request: BlockRequest, ) -> Result { - if let BlockRequest::Pending(pool_transactions) = &block_request { - if let Some(value) = get_pool_transactions_nonce(pool_transactions, address) { - return Ok(value); - } + if let BlockRequest::Pending(pool_transactions) = &block_request + && let Some(value) = get_pool_transactions_nonce(pool_transactions, address) + { + return Ok(value); } let final_block_request = match block_request { BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()), @@ -2488,97 +3262,485 @@ impl Backend { .await? } + fn replay_tx_with_inspector( + &self, + hash: B256, + mut inspector: I, + f: F, + ) -> Result + where + for<'a> I: BackendInspector>>> + 'a, + for<'a> F: + FnOnce(ResultAndState, CacheDB>, I, TxEnv, EvmEnv) -> T, + { + let block = { + let storage = self.blockchain.storage.read(); + let MinedTransaction { block_hash, .. } = storage + .transactions + .get(&hash) + .cloned() + .ok_or(BlockchainError::TransactionNotFound)?; + + storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)? + }; + + let index = block + .body + .transactions + .iter() + .position(|tx| tx.hash() == hash) + .expect("transaction not found in block"); + + let pool_txs: Vec>> = block.body.transactions + [..index] + .iter() + .map(|tx| { + let pending_tx = + PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid"); + Arc::new(PoolTransaction { + pending_transaction: pending_tx, + requires: vec![], + provides: vec![], + priority: crate::eth::pool::transactions::TransactionPriority(0), + }) + }) + .collect(); + + let trace = |parent_state: &StateDb| -> Result { + let mut cache_db = AnvilCacheDB::new(Box::new(parent_state)); + + // configure the blockenv for the block of the transaction + let mut evm_env = self.evm_env.read().clone(); + + evm_env.block_env = block_env_from_header(&block.header); + + let spec_id = *evm_env.spec_id(); + + let inspector_tx_config = self.inspector_tx_config(); + let gas_config = self.pool_tx_gas_config(&evm_env); + + self.execute_with_block_executor( + &mut cache_db, + &evm_env, + block.header.parent_hash, + spec_id, + &pool_txs, + &gas_config, + &inspector_tx_config, + &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env), + ); + + // Extract inner CacheDB to match the expected types for the target tx execution + let cache_db = cache_db.0; + + let target_tx = block.body.transactions[index].clone(); + let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?; + let (result, base_tx_env) = self.transact_envelope_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + target_tx.transaction.as_ref(), + *target_tx.sender(), + )?; + + Ok(f(result, cache_db, inspector, base_tx_env, evm_env)) + }; + + let read_guard = self.states.upgradable_read(); + if let Some(state) = read_guard.get_state(&block.header.parent_hash) { + trace(state) + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state = write_guard + .get_on_disk_state(&block.header.parent_hash) + .ok_or(BlockchainError::BlockNotFound)?; + trace(state) + } + } + + /// Traces the transaction with the js tracer + #[cfg(feature = "js-tracer")] + pub async fn trace_tx_with_js_tracer( + &self, + hash: B256, + code: String, + opts: GethDebugTracingOptions, + ) -> Result { + let GethDebugTracingOptions { tracer_config, .. } = opts; + let config = tracer_config.into_json(); + let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) + .map_err(|err| BlockchainError::Message(err.to_string()))?; + let trace = self.replay_tx_with_inspector( + hash, + inspector, + |result, cache_db, mut inspector, tx_env, evm_env| { + inspector + .json_result( + result, + &alloy_evm::IntoTxEnv::into_tx_env(tx_env), + &evm_env.block_env, + &cache_db, + ) + .map_err(|e| BlockchainError::Message(e.to_string())) + }, + )??; + Ok(GethTrace::JS(trace)) + } + + /// Prove an account's existence or nonexistence in the state trie. + /// + /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address) + pub async fn prove_account_at( + &self, + address: Address, + keys: Vec, + block_request: Option>, + ) -> Result { + let block_number = block_request.as_ref().map(|r| r.block_number()); + + self.with_database_at(block_request, |block_db, _| { + trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); + + let mut builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); + + for (key, account) in trie_accounts(db) { + builder.add_leaf(key, &account); + } + + let _ = builder.root(); + + let proof = builder + .take_proof_nodes() + .into_nodes_sorted() + .into_iter() + .map(|(_, v)| v) + .collect(); + let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys); + + let account_proof = AccountProof { + address, + balance: account.info.balance, + nonce: account.info.nonce, + code_hash: account.info.code_hash, + storage_hash, + account_proof: proof, + storage_proof: keys + .into_iter() + .zip(storage_proofs) + .map(|(key, proof)| { + let storage_key: U256 = key.into(); + let value = account.storage.get(&storage_key).copied().unwrap_or_default(); + StorageProof { key: JsonStorageKey::Hash(key), value, proof } + }) + .collect(), + }; + + Ok(account_proof) + }) + .await? + } +} + +impl Backend +where + N: Network, +{ + /// Rollback the chain to a common height. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { + let hash = common_block.header.hash_slow(); + + // Get the database at the common block + let common_state = { + let return_state_or_throw_err = + |db: Option<&StateDb>| -> Result, BlockchainError> { + let state_db = db.ok_or(BlockchainError::DataUnavailable)?; + let db_full = + state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + Ok(db_full.clone()) + }; + + let read_guard = self.states.upgradable_read(); + if let Some(db) = read_guard.get_state(&hash) { + return_state_or_throw_err(Some(db))? + } else { + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + return_state_or_throw_err(write_guard.get_on_disk_state(&hash))? + } + }; + + { + // Unwind the storage back to the common ancestor first + let removed_blocks = + self.blockchain.storage.write().unwind_to(common_block.header.number(), hash); + + // Clean up in-memory and on-disk states for removed blocks + let removed_hashes: Vec<_> = + removed_blocks.iter().map(|b| b.header.hash_slow()).collect(); + self.states.write().remove_block_states(&removed_hashes); + + // Set environment back to common block + let mut env = self.evm_env.write(); + env.block_env.number = U256::from(common_block.header.number()); + env.block_env.timestamp = U256::from(common_block.header.timestamp()); + env.block_env.gas_limit = common_block.header.gas_limit(); + env.block_env.difficulty = common_block.header.difficulty(); + env.block_env.prevrandao = common_block.header.mix_hash(); + + self.time.reset(env.block_env.timestamp.saturating_to()); + } + + { + // Collect block hashes before acquiring db lock to avoid holding blockchain storage + // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can + // access. + let block_hashes: Vec<_> = { + let storage = self.blockchain.storage.read(); + let min_block = common_block.header.number().saturating_sub(256); + storage + .hashes + .iter() + .filter(|(num, _)| **num >= min_block) + .map(|(&num, &hash)| (num, hash)) + .collect() + }; + + // Acquire db lock once for the entire restore operation to reduce lock churn. + let mut db = self.db.write().await; + db.clear(); + + // Insert account info before storage to prevent fork-mode RPC fetches after clear. + for (address, acc) in common_state { + db.insert_account(address, acc.info); + for (key, value) in acc.storage { + db.set_storage_at(address, key.into(), value.into())?; + } + } + + // Restore block hashes from blockchain storage (now unwound, contains only valid + // blocks). + for (block_num, hash) in block_hashes { + db.insert_block_hash(U256::from(block_num), hash); + } + } + + Ok(()) + } + /// Returns the traces for the given transaction - pub async fn trace_transaction( + pub async fn debug_trace_transaction( + &self, + hash: B256, + opts: GethDebugTracingOptions, + ) -> Result { + #[cfg(feature = "js-tracer")] + if let Some(tracer_type) = opts.tracer.as_ref() + && tracer_type.is_js() + { + return self + .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone()) + .await; + } + + if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await { + return trace; + } + + if let Some(fork) = self.get_fork() { + return Ok(fork.debug_trace_transaction(hash, opts).await?); + } + + Ok(GethTrace::Default(Default::default())) + } + + /// Returns geth-style traces for all transactions in a block by hash. + pub async fn debug_trace_block_by_hash( &self, - hash: B256, - ) -> Result, BlockchainError> { - if let Some(traces) = self.mined_parity_trace_transaction(hash) { + block_hash: B256, + opts: GethDebugTracingOptions, + ) -> Result, BlockchainError> { + if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) { + let mut traces = Vec::new(); + for tx in &block.body.transactions { + let tx_hash = tx.hash(); + match self.debug_trace_transaction(tx_hash, opts.clone()).await { + Ok(trace) => { + traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) }); + } + Err(error) => { + traces.push(TraceResult::Error { + error: error.to_string(), + tx_hash: Some(tx_hash), + }); + } + } + } return Ok(traces); } if let Some(fork) = self.get_fork() { - return Ok(fork.trace_transaction(hash).await?) + return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?); } - Ok(vec![]) + Err(BlockchainError::BlockNotFound) } - /// Returns the traces for the given transaction - pub(crate) fn mined_parity_trace_transaction( + /// Returns geth-style traces for all transactions in a block by number. + pub async fn debug_trace_block_by_number( &self, - hash: B256, - ) -> Option> { - self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces()) - } - - /// Returns the traces for the given transaction - pub(crate) fn mined_transaction(&self, hash: B256) -> Option { - self.blockchain.storage.read().transactions.get(&hash).cloned() - } + block_number: BlockNumber, + opts: GethDebugTracingOptions, + ) -> Result, BlockchainError> { + let number = self.convert_block_number(Some(block_number)); + + if let Some(block) = self.get_block(BlockId::Number(BlockNumber::Number(number))) { + let mut traces = Vec::new(); + for tx in &block.body.transactions { + let tx_hash = tx.hash(); + match self.debug_trace_transaction(tx_hash, opts.clone()).await { + Ok(trace) => { + traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) }); + } + Err(error) => { + traces.push(TraceResult::Error { + error: error.to_string(), + tx_hash: Some(tx_hash), + }); + } + } + } + return Ok(traces); + } - /// Returns the traces for the given block - pub(crate) fn mined_parity_trace_block( - &self, - block: u64, - ) -> Option> { - let block = self.get_block(block)?; - let mut traces = vec![]; - let storage = self.blockchain.storage.read(); - for tx in block.transactions { - traces.extend(storage.transactions.get(&tx.hash())?.parity_traces()); + if let Some(fork) = self.get_fork() { + return Ok(fork.debug_trace_block_by_number(number, opts).await?); } - Some(traces) + + Err(BlockchainError::BlockNotFound) } - /// Returns the traces for the given transaction - pub async fn debug_trace_transaction( + fn geth_trace( &self, - hash: B256, + tx: &MinedTransaction, opts: GethDebugTracingOptions, ) -> Result { - if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()) { - return trace; - } + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; + + if let Some(tracer) = tracer { + match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::FourByteTracer => { + let inspector = FourByteInspector::default(); + let res = self.replay_tx_with_inspector( + tx.info.transaction_hash, + inspector, + |_, _, inspector, _, _| FourByteFrame::from(inspector).into(), + )?; + return Ok(res); + } + GethDebugBuiltInTracerType::CallTracer => { + return match tracer_config.into_call_config() { + Ok(call_config) => { + let inspector = TracingInspector::new( + TracingInspectorConfig::from_geth_call_config(&call_config), + ); + let frame = self.replay_tx_with_inspector( + tx.info.transaction_hash, + inspector, + |_, _, inspector, _, _| { + inspector + .geth_builder() + .geth_call_traces( + call_config, + tx.receipt.cumulative_gas_used(), + ) + .into() + }, + )?; + Ok(frame) + } + Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), + }; + } + GethDebugBuiltInTracerType::PreStateTracer => { + return match tracer_config.into_pre_state_config() { + Ok(pre_state_config) => { + let inspector = TracingInspector::new( + TracingInspectorConfig::from_geth_prestate_config( + &pre_state_config, + ), + ); + let frame = self.replay_tx_with_inspector( + tx.info.transaction_hash, + inspector, + |state, db, inspector, _, _| { + inspector.geth_builder().geth_prestate_traces( + &state, + &pre_state_config, + db, + ) + }, + )??; + Ok(frame.into()) + } + Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), + }; + } + GethDebugBuiltInTracerType::NoopTracer + | GethDebugBuiltInTracerType::MuxTracer + | GethDebugBuiltInTracerType::Erc7562Tracer + | GethDebugBuiltInTracerType::FlatCallTracer => {} + }, + GethDebugTracerType::JsTracer(_code) => {} + } - if let Some(fork) = self.get_fork() { - return Ok(fork.debug_trace_transaction(hash, opts).await?) + return Ok(NoopFrame::default().into()); } - Ok(GethTrace::Default(Default::default())) + // default structlog tracer + Ok(GethTraceBuilder::new(tx.info.traces.clone()) + .geth_traces( + tx.receipt.cumulative_gas_used(), + tx.info.out.clone().unwrap_or_default(), + config, + ) + .into()) } - fn mined_geth_trace_transaction( + async fn mined_geth_trace_transaction( &self, hash: B256, opts: GethDebugTracingOptions, ) -> Option> { - self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts)) + self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts)) } - /// Returns the traces for the given block - pub async fn trace_block( + /// returns all receipts for the given transactions + fn get_receipts( &self, - block: BlockNumber, - ) -> Result, BlockchainError> { - let number = self.convert_block_number(Some(block)); - if let Some(traces) = self.mined_parity_trace_block(number) { - return Ok(traces); - } + tx_hashes: impl IntoIterator, + ) -> Vec { + let storage = self.blockchain.storage.read(); + let mut receipts = vec![]; - if let Some(fork) = self.get_fork() { - if fork.predates_fork(number) { - return Ok(fork.trace_block(number).await?) + for hash in tx_hashes { + if let Some(tx) = storage.transactions.get(&hash) { + receipts.push(tx.receipt.clone()); } } - Ok(vec![]) + receipts } pub async fn transaction_receipt( &self, hash: B256, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { return Ok(Some(receipt.inner)); } @@ -2586,7 +3748,7 @@ impl Backend { if let Some(fork) = self.get_fork() { let receipt = fork.transaction_receipt(hash).await?; let number = self.convert_block_number( - receipt.clone().and_then(|r| r.block_number).map(BlockNumber::from), + receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from), ); if fork.predates_fork_inclusive(number) { @@ -2597,446 +3759,565 @@ impl Backend { Ok(None) } - // Returns the traces matching a given filter - pub async fn trace_filter( - &self, - filter: TraceFilter, - ) -> Result, BlockchainError> { - let matcher = filter.matcher(); - let start = filter.from_block.unwrap_or(0); - let end = filter.to_block.unwrap_or_else(|| self.best_number()); + /// Returns all transaction receipts of the block + pub fn mined_block_receipts(&self, id: impl Into) -> Option> { + let mut receipts = Vec::new(); + let block = self.get_block(id)?; - if start > end { - return Err(BlockchainError::RpcError(RpcError::invalid_params( - "invalid block range, ensure that to block is greater than from block".to_string(), - ))); + for transaction in block.body.transactions { + let receipt = self.mined_transaction_receipt(transaction.hash())?; + receipts.push(receipt.inner); } - let dist = end - start; - if dist > 300 { - return Err(BlockchainError::RpcError(RpcError::invalid_params( - "block range too large, currently limited to 300".to_string(), - ))); - } + Some(receipts) + } - // Accumulate tasks for block range - let mut trace_tasks = vec![]; - for num in start..=end { - trace_tasks.push(self.trace_block(num.into())); - } + /// Returns the transaction receipt for the given hash + pub(crate) fn mined_transaction_receipt( + &self, + hash: B256, + ) -> Option> { + let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = + self.blockchain.get_transaction_by_hash(&hash)?; - // Execute tasks and filter traces - let traces = futures::future::try_join_all(trace_tasks).await?; - let filtered_traces = - traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace)); + let index = info.transaction_index as usize; + let block = self.blockchain.get_block_by_hash(&block_hash)?; + let transaction = block.body.transactions[index].clone(); - // Apply after and count - let filtered_traces: Vec<_> = if let Some(after) = filter.after { - filtered_traces.skip(after as usize).collect() - } else { - filtered_traces.collect() - }; + // Cancun specific + let excess_blob_gas = block.header.excess_blob_gas(); + let blob_gas_price = + alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default()); + let blob_gas_used = transaction.blob_gas_used(); - let filtered_traces: Vec<_> = if let Some(count) = filter.count { - filtered_traces.into_iter().take(count as usize).collect() - } else { - filtered_traces + let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas()); + + let receipts = self.get_receipts(block.body.transactions.iter().map(|tx| tx.hash())); + let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); + + let tx_receipt = tx_receipt.convert_logs_rpc( + BlockNumHash::new(block.header.number(), block_hash), + block.header.timestamp(), + info.transaction_hash, + info.transaction_index, + next_log_index, + ); + + let receipt = TransactionReceipt { + inner: tx_receipt, + transaction_hash: info.transaction_hash, + transaction_index: Some(info.transaction_index), + block_number: Some(block.header.number()), + gas_used: info.gas_used, + contract_address: info.contract_address, + effective_gas_price, + block_hash: Some(block_hash), + from: info.from, + to: info.to, + blob_gas_price: Some(blob_gas_price), + blob_gas_used, }; - Ok(filtered_traces) + // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API) + let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp()); + if self.is_tempo() { + inner = inner.with_fee_payer(info.from); + } + Some(MinedTransactionReceipt { inner, out: info.out }) } - /// Returns all receipts of the block - pub fn mined_receipts(&self, hash: B256) -> Option> { - let block = self.mined_block_by_hash(hash)?; - let mut receipts = Vec::new(); - let storage = self.blockchain.storage.read(); - for tx in block.transactions.hashes() { - let receipt = storage.transactions.get(&tx)?.receipt.clone(); - receipts.push(receipt); + /// Returns the blocks receipts for the given number + pub async fn block_receipts( + &self, + number: BlockId, + ) -> Result>, BlockchainError> { + if let Some(receipts) = self.mined_block_receipts(number) { + return Ok(Some(receipts)); } - Some(receipts) + + if let Some(fork) = self.get_fork() { + let number = match self.ensure_block_number(Some(number)).await { + Err(_) => return Ok(None), + Ok(n) => n, + }; + + if fork.predates_fork_inclusive(number) { + let receipts = fork.block_receipts(number).await?; + + return Ok(receipts); + } + } + + Ok(None) } +} - /// Returns all transaction receipts of the block - pub fn mined_block_receipts(&self, id: impl Into) -> Option> { - let mut receipts = Vec::new(); - let block = self.get_block(id)?; +impl> Backend { + /// Get the current state. + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + let at = self.evm_env.read().block_env.clone(); + let best_number = self.blockchain.storage.read().best_number; + let blocks = self.blockchain.storage.read().serialized_blocks(); + let transactions = self.blockchain.storage.read().serialized_transactions(); + let historical_states = + preserve_historical_states.then(|| self.states.write().serialized_states()); + + let state = self.db.read().await.dump_state( + at, + best_number, + blocks, + transactions, + historical_states, + )?; + state.ok_or_else(|| { + RpcError::invalid_params("Dumping state not supported with the current configuration") + .into() + }) + } + + /// Write all chain data to serialized bytes buffer + pub async fn dump_state( + &self, + preserve_historical_states: bool, + ) -> Result { + let state = self.serialized_state(preserve_historical_states).await?; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(&serde_json::to_vec(&state).unwrap_or_default()) + .map_err(|_| BlockchainError::DataUnavailable)?; + Ok(encoder.finish().unwrap_or_default().into()) + } + + /// Apply [SerializableState] data to the backend storage. + pub async fn load_state(&self, state: SerializableState) -> Result { + // load the blocks and transactions into the storage + self.blockchain.storage.write().load_blocks(state.blocks.clone()); + self.blockchain.storage.write().load_transactions(state.transactions.clone()); + // reset the block env + if let Some(block) = state.block.clone() { + self.evm_env.write().block_env = block.clone(); + + // Set the current best block number. + // Defaults to block number for compatibility with existing state files. + let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); + + let best_number = state.best_block_number.unwrap_or(block.number.saturating_to()); + if let Some((number, hash)) = fork_num_and_hash { + trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); + // If the state.block_number is greater than the fork block number, set best number + // to the state block number. + // Ref: https://github.com/foundry-rs/foundry/issues/9539 + if best_number > number { + self.blockchain.storage.write().best_number = best_number; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else( + || { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + }, + )?; + self.blockchain.storage.write().best_hash = best_hash; + } else { + // If loading state file on a fork, set best number to the fork block number. + // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 + self.blockchain.storage.write().best_number = number; + self.blockchain.storage.write().best_hash = hash; + } + } else { + self.blockchain.storage.write().best_number = best_number; + + // Set the current best block hash; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + })?; - for transaction in block.transactions { - let receipt = self.mined_transaction_receipt(transaction.hash())?; - receipts.push(receipt.inner); + self.blockchain.storage.write().best_hash = best_hash; + } } - Some(receipts) - } + if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number()) { + let header = &latest.header; + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + header.gas_used(), + header.gas_limit(), + header.base_fee_per_gas().unwrap_or_default(), + ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas().unwrap_or_default(), + header.blob_gas_used().unwrap_or_default(), + ); - /// Returns the transaction receipt for the given hash - pub(crate) fn mined_transaction_receipt(&self, hash: B256) -> Option { - let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = - self.blockchain.get_transaction_by_hash(&hash)?; + // update next base fee + self.fees.set_base_fee(next_block_base_fee); - let index = info.transaction_index as usize; - let block = self.blockchain.get_block_by_hash(&block_hash)?; - let transaction = block.transactions[index].clone(); + self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_excess_blob_gas, + get_blob_base_fee_update_fraction( + self.evm_env.read().cfg_env.chain_id, + header.timestamp, + ), + )); + } - // Cancun specific - let excess_blob_gas = block.header.excess_blob_gas; - let blob_gas_price = - alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default()); - let blob_gas_used = transaction.blob_gas(); - - let effective_gas_price = match transaction.transaction { - TypedTransaction::Legacy(t) => t.tx().gas_price, - TypedTransaction::EIP2930(t) => t.tx().gas_price, - TypedTransaction::EIP1559(t) => block - .header - .base_fee_per_gas - .map_or(self.base_fee() as u128, |g| g as u128) - .saturating_add(t.tx().max_priority_fee_per_gas), - TypedTransaction::EIP4844(t) => block - .header - .base_fee_per_gas - .map_or(self.base_fee() as u128, |g| g as u128) - .saturating_add(t.tx().tx().max_priority_fee_per_gas), - TypedTransaction::EIP7702(t) => block - .header - .base_fee_per_gas - .map_or(self.base_fee() as u128, |g| g as u128) - .saturating_add(t.tx().max_priority_fee_per_gas), - TypedTransaction::Deposit(_) => 0_u128, - }; + if !self.db.write().await.load_state(state.clone())? { + return Err(RpcError::invalid_params( + "Loading state not supported with the current configuration", + ) + .into()); + } - let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); - let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); + if let Some(historical_states) = state.historical_states { + self.states.write().load_states(historical_states); + } - let receipt = tx_receipt.as_receipt_with_bloom().receipt.clone(); - let receipt = Receipt { - status: receipt.status, - cumulative_gas_used: receipt.cumulative_gas_used, - logs: receipt - .logs - .into_iter() - .enumerate() - .map(|(index, log)| alloy_rpc_types::Log { - inner: log, - block_hash: Some(block_hash), - block_number: Some(block.header.number), - block_timestamp: Some(block.header.timestamp), - transaction_hash: Some(info.transaction_hash), - transaction_index: Some(info.transaction_index), - log_index: Some((next_log_index + index) as u64), - removed: false, - }) - .collect(), - }; - let receipt_with_bloom = - ReceiptWithBloom { receipt, logs_bloom: tx_receipt.as_receipt_with_bloom().logs_bloom }; - - let inner = match tx_receipt { - TypedReceipt::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), - TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), - TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), - TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), - TypedReceipt::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), - TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt { - inner: receipt_with_bloom, - deposit_nonce: r.deposit_nonce, - deposit_receipt_version: r.deposit_receipt_version, - }), - }; + Ok(true) + } - let inner = TransactionReceipt { - inner, - transaction_hash: info.transaction_hash, - transaction_index: Some(info.transaction_index), - block_number: Some(block.header.number), - gas_used: info.gas_used, - contract_address: info.contract_address, - effective_gas_price, - block_hash: Some(block_hash), - from: info.from, - to: info.to, - blob_gas_price: Some(blob_gas_price), - blob_gas_used, - }; + /// Deserialize and add all chain data to the backend storage + pub async fn load_state_bytes(&self, buf: Bytes) -> Result { + let orig_buf = &buf.0[..]; + let mut decoder = GzDecoder::new(orig_buf); + let mut decoded_data = Vec::new(); + + let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() { + decoder + .read_to_end(decoded_data.as_mut()) + .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; + &decoded_data + } else { + &buf.0 + }) + .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) + self.load_state(state).await } +} - /// Returns the blocks receipts for the given number - pub async fn block_receipts( +impl Backend { + /// Simulates the payload by executing the calls in request. + pub async fn simulate( &self, - number: BlockId, - ) -> Result>, BlockchainError> { - if let Some(receipts) = self.mined_block_receipts(number) { - return Ok(Some(receipts)); - } - - if let Some(fork) = self.get_fork() { - let number = match self.ensure_block_number(Some(number)).await { - Err(_) => return Ok(None), - Ok(n) => n, - }; - - if fork.predates_fork_inclusive(number) { - let receipts = fork.block_receipts(number).await?; + request: SimulatePayload, + block_request: Option>, + ) -> Result>, BlockchainError> { + self.with_database_at(block_request, |state, mut block_env| { + let SimulatePayload { + block_state_calls, + trace_transfers, + validation, + return_full_transactions, + } = request; + let mut cache_db = CacheDB::new(state); + let mut block_res = Vec::with_capacity(block_state_calls.len()); - return Ok(receipts); - } - } + // execute the blocks + for block in block_state_calls { + let SimBlock { block_overrides, state_overrides, calls } = block; + let mut call_res = Vec::with_capacity(calls.len()); + let mut log_index = 0; + let mut gas_used = 0; + let mut transactions = Vec::with_capacity(calls.len()); + let mut logs= Vec::new(); - Ok(None) - } + // apply state overrides before executing the transactions + if let Some(state_overrides) = state_overrides { + apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + cache_db.apply_block_overrides(block_overrides, &mut block_env); + } - pub async fn transaction_by_block_number_and_index( - &self, - number: BlockNumber, - index: Index, - ) -> Result, BlockchainError> { - if let Some(block) = self.mined_block_by_number(number) { - return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index)); - } + // execute all calls in that block + for (req_idx, request) in calls.into_iter().enumerate() { + let fee_details = FeeDetails::new( + request.gas_price, + request.max_fee_per_gas, + request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, + )? + .or_zero_fees(); - if let Some(fork) = self.get_fork() { - let number = self.convert_block_number(Some(number)); - if fork.predates_fork(number) { - return Ok(fork.transaction_by_block_number_and_index(number, index.into()).await?) - } - } + let (mut evm_env, tx_env, op_deposit) = self.build_call_env( + WithOtherFields::new(request.clone()), + fee_details, + block_env.clone(), + ); - Ok(None) - } + // Always disable EIP-3607 + evm_env.cfg_env.disable_eip3607 = true; - pub async fn transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> Result, BlockchainError> { - if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { - return Ok(tx); - } + if !validation { + evm_env.cfg_env.disable_base_fee = !validation; + evm_env.block_env.basefee = 0; + } - if let Some(fork) = self.get_fork() { - return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?) - } + let mut inspector = self.build_inspector(); - Ok(None) - } + // transact + if trace_transfers { + inspector = inspector.with_transfers(); + } + trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env"); + let ResultAndState { result, state } = self.transact_with_inspector_ref( + &cache_db, + &evm_env, + &mut inspector, + tx_env, + op_deposit, + )?; + trace!(target: "backend", ?result, ?request, "simulate call"); - pub fn mined_transaction_by_block_hash_and_index( - &self, - block_hash: B256, - index: Index, - ) -> Option { - let (info, block, tx) = { - let storage = self.blockchain.storage.read(); - let block = storage.blocks.get(&block_hash).cloned()?; - let index: usize = index.into(); - let tx = block.transactions.get(index)?.clone(); - let info = storage.transactions.get(&tx.hash())?.info.clone(); - (info, block, tx) - }; + inspector.print_logs(); + if self.print_traces { + inspector.into_print_traces(self.call_trace_decoder.clone()); + } - Some(transaction_build( - Some(info.transaction_hash), - tx, - Some(&block), - Some(info), - block.header.base_fee_per_gas, - )) - } + // commit the transaction + cache_db.commit(state); + gas_used += result.tx_gas_used(); - pub async fn transaction_by_hash( - &self, - hash: B256, - ) -> Result, BlockchainError> { - trace!(target: "backend", "transaction_by_hash={:?}", hash); - if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { - return Ok(tx); - } + // create the transaction from a request + let from = request.from.unwrap_or_default(); - if let Some(fork) = self.get_fork() { - return fork.transaction_by_hash(hash).await.map_err(BlockchainError::AlloyForkProvider) - } + let mut request = Into::::into(WithOtherFields::new(request)); + request.prep_for_submission(); - Ok(None) - } + let typed_tx = request.build_unsigned().map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))?; - pub fn mined_transaction_by_hash(&self, hash: B256) -> Option { - let (info, block) = { - let storage = self.blockchain.storage.read(); - let MinedTransaction { info, block_hash, .. } = - storage.transactions.get(&hash)?.clone(); - let block = storage.blocks.get(&block_hash).cloned()?; - (info, block) - }; - let tx = block.transactions.get(info.transaction_index as usize)?.clone(); + let tx = build_impersonated(typed_tx); + let tx_hash = tx.hash(); + let rpc_tx = transaction_build( + None, + MaybeImpersonatedTransaction::impersonated(tx, from), + None, + None, + Some(block_env.basefee), + ); + transactions.push(rpc_tx); - Some(transaction_build( - Some(info.transaction_hash), - tx, - Some(&block), - Some(info), - block.header.base_fee_per_gas, - )) - } + let return_data = result.output().cloned().unwrap_or_default(); + let sim_res = SimCallResult { + return_data, + gas_used: result.tx_gas_used(), + max_used_gas: None, + status: result.is_success(), + error: result.is_success().not().then(|| { + alloy_rpc_types::simulate::SimulateError { + code: -3200, + message: "execution failed".to_string(), + data: None, + } + }), + logs: result.clone() + .into_logs() + .into_iter() + .enumerate() + .map(|(idx, log)| Log { + inner: log, + block_number: Some(block_env.number.saturating_to()), + block_timestamp: Some(block_env.timestamp.saturating_to()), + transaction_index: Some(req_idx as u64), + log_index: Some((idx + log_index) as u64), + removed: false, - /// Prove an account's existence or nonexistence in the state trie. - /// - /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address) - pub async fn prove_account_at( - &self, - address: Address, - keys: Vec, - block_request: Option, - ) -> Result { - let block_number = block_request.as_ref().map(|r| r.block_number()); + block_hash: None, + transaction_hash: Some(tx_hash), + }) + .collect(), + }; + logs.extend(sim_res.logs.iter().map(|log| log.inner.clone())); + log_index += sim_res.logs.len(); + call_res.push(sim_res); + } - self.with_database_at(block_request, |block_db, _| { - trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); - let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; - let account = db.get(&address).cloned().unwrap_or_default(); + let transactions_envelopes: Vec = transactions + .iter() + .map(|tx| AnyTxEnvelope::from(tx.clone())) + .collect(); + let header = Header { + logs_bloom: logs_bloom(logs.iter()), + transactions_root: calculate_transaction_root(&transactions_envelopes), + receipts_root: calculate_receipt_root(&transactions_envelopes), + parent_hash: Default::default(), + beneficiary: block_env.beneficiary, + state_root: Default::default(), + difficulty: Default::default(), + number: block_env.number.saturating_to(), + gas_limit: block_env.gas_limit, + gas_used, + timestamp: block_env.timestamp.saturating_to(), + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + base_fee_per_gas: Some(block_env.basefee), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + ..Default::default() + }; + let mut block = alloy_rpc_types::Block { + header: AnyRpcHeader { + hash: header.hash_slow(), + inner: header.into(), + total_difficulty: None, + size: None, + }, + uncles: vec![], + transactions: BlockTransactions::Full(transactions), + withdrawals: None, + }; - let mut builder = HashBuilder::default() - .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); + if !return_full_transactions { + block.transactions.convert_to_hashes(); + } - for (key, account) in trie_accounts(db) { - builder.add_leaf(key, &account); - } + for res in &mut call_res { + res.logs.iter_mut().for_each(|log| { + log.block_hash = Some(block.header.hash); + }); + } - let _ = builder.root(); + let simulated_block = SimulatedBlock { + inner: AnyRpcBlock::new(WithOtherFields::new(block)), + calls: call_res, + }; - let proof = builder - .take_proof_nodes() - .into_nodes_sorted() - .into_iter() - .map(|(_, v)| v) - .collect(); - let storage_proofs = prove_storage(&account.storage, &keys); + // update block env + block_env.number += U256::from(1); + block_env.timestamp += U256::from(12); + block_env.basefee = simulated_block + .inner + .header + .next_block_base_fee(self.fees.base_fee_params()) + .unwrap_or_default(); - let account_proof = AccountProof { - address, - balance: account.info.balance, - nonce: account.info.nonce, - code_hash: account.info.code_hash, - storage_hash: storage_root(&account.storage), - account_proof: proof, - storage_proof: keys - .into_iter() - .zip(storage_proofs) - .map(|(key, proof)| { - let storage_key: U256 = key.into(); - let value = account.storage.get(&storage_key).cloned().unwrap_or_default(); - StorageProof { key: JsonStorageKey::Hash(key), value, proof } - }) - .collect(), - }; + block_res.push(simulated_block); + } - Ok(account_proof) + Ok(block_res) }) .await? } - /// Returns a new block event stream - pub fn new_block_notifications(&self) -> NewBlockNotifications { - let (tx, rx) = unbounded(); - self.new_block_listeners.lock().push(tx); - trace!(target: "backed", "added new block listener"); - rx - } - - /// Notifies all `new_block_listeners` about the new block - fn notify_on_new_block(&self, header: Header, hash: B256) { - // cleanup closed notification streams first, if the channel is closed we can remove the - // sender half for the set - self.new_block_listeners.lock().retain(|tx| !tx.is_closed()); + pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result>> { + // Try to get the mined transaction by hash + if let Some(tx) = self.mined_transaction_by_hash(hash) + && let Ok(typed_tx) = FoundryTxEnvelope::try_from(tx) + && let Some(sidecar) = typed_tx.sidecar() + { + return Ok(Some(sidecar.sidecar.blobs().to_vec())); + } - let notification = NewBlockNotification { hash, header: Arc::new(header) }; + Ok(None) + } - self.new_block_listeners - .lock() - .retain(|tx| tx.unbounded_send(notification.clone()).is_ok()); + /// Sets the fee token for a user address (Tempo-only). + pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> { + let hardfork = self.hardfork(); + let chain_id = self.evm_env.read().cfg_env.chain_id; + let timestamp = U256::from(self.evm_env.read().block_env.timestamp); + let block_number: u64 = self.evm_env.read().block_env.number.to(); + let mut db = self.db.write().await; + let mut storage = AnvilStorageProvider::new( + &mut **db, + chain_id, + timestamp, + block_number, + hardfork.into(), + ); + StorageCtx::enter(&mut storage, || { + let mut fee_manager = TipFeeManager::new(); + fee_manager + .set_user_token(user, IFeeManager::setUserTokenCall { token }) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))) + }) } - /// Reorg the chain to a common height and execute blocks to build new chain. - /// - /// The state of the chain is rewound using `rewind` to the common block, including the db, - /// storage, and env. - /// - /// Finally, `do_mine_block` is called to create the new chain. - pub async fn reorg( + /// Sets the fee token for a validator address (Tempo-only). + pub async fn set_validator_fee_token( &self, - depth: u64, - tx_pairs: HashMap>>, - common_block: Block, - ) -> Result<(), BlockchainError> { - self.rollback(common_block).await?; - // Create the new reorged chain, filling the blocks with transactions if supplied - for i in 0..depth { - let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); - let outcome = self.do_mine_block(to_be_mined).await; - node_info!( - " Mined reorg block number {}. With {} valid txs and with invalid {} txs", - outcome.block_number, - outcome.included.len(), - outcome.invalid.len() - ); - } - - Ok(()) + validator: Address, + token: Address, + ) -> DatabaseResult<()> { + let hardfork = self.hardfork(); + let chain_id = self.evm_env.read().cfg_env.chain_id; + let timestamp = U256::from(self.evm_env.read().block_env.timestamp); + let block_number: u64 = self.evm_env.read().block_env.number.to(); + let mut db = self.db.write().await; + let mut storage = AnvilStorageProvider::new( + &mut **db, + chain_id, + timestamp, + block_number, + hardfork.into(), + ); + StorageCtx::enter(&mut storage, || { + let mut fee_manager = TipFeeManager::new(); + // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes + fee_manager + .set_validator_token( + validator, + IFeeManager::setValidatorTokenCall { token }, + Address::ZERO, + ) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))) + }) } - /// Rollback the chain to a common height. - /// - /// The state of the chain is rewound using `rewind` to the common block, including the db, - /// storage, and env. - pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { - // Get the database at the common block - let common_state = { - let mut state = self.states.write(); - let state_db = state - .get(&common_block.header.hash_slow()) - .ok_or(BlockchainError::DataUnavailable)?; - let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; - db_full.clone() - }; - - { - // Set state to common state - self.db.write().await.clear(); - for (address, acc) in common_state { - for (key, value) in acc.storage { - self.db.write().await.set_storage_at(address, key.into(), value.into())?; - } - self.db.write().await.insert_account(address, acc.info); + /// Mints FeeAMM liquidity for a token pair (Tempo-only). + pub async fn set_fee_amm_liquidity( + &self, + user_token: Address, + validator_token: Address, + amount: U256, + ) -> DatabaseResult<()> { + let hardfork = self.hardfork(); + let chain_id = self.evm_env.read().cfg_env.chain_id; + let timestamp = U256::from(self.evm_env.read().block_env.timestamp); + let block_number: u64 = self.evm_env.read().block_env.number.to(); + let admin = Address::ZERO; + let mut db = self.db.write().await; + let mut storage = AnvilStorageProvider::new( + &mut **db, + chain_id, + timestamp, + block_number, + hardfork.into(), + ); + StorageCtx::enter(&mut storage, || { + // Mint the required tokens to admin so it can provide liquidity. + // grant_role_internal bypasses the caller check, matching genesis seeding. + for &token_address in &[user_token, validator_token] { + let mut token = TIP20Token::from_address(token_address) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}"))))?; + token + .grant_role_internal(admin, *ISSUER_ROLE) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}"))))?; + token + .mint(admin, ITIP20::mintCall { to: admin, amount }) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}"))))?; } - } - - { - // Unwind the storage back to the common ancestor - self.blockchain - .storage - .write() - .unwind_to(common_block.header.number, common_block.header.hash_slow()); - - // Set environment back to common block - let mut env = self.env.write(); - env.evm_env.block_env.number = common_block.header.number; - env.evm_env.block_env.timestamp = common_block.header.timestamp; - env.evm_env.block_env.gas_limit = common_block.header.gas_limit; - env.evm_env.block_env.difficulty = common_block.header.difficulty; - env.evm_env.block_env.prevrandao = Some(common_block.header.mix_hash); - - self.time.reset(env.evm_env.block_env.timestamp); - } - Ok(()) + let mut fee_manager = TipFeeManager::new(); + fee_manager + .mint(admin, user_token, validator_token, amount, admin) + .map_err(|e| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}"))))?; + Ok(()) + }) } } -/// Get max nonce from transaction pool by address +/// Get max nonce from transaction pool by address. fn get_pool_transactions_nonce( - pool_transactions: &[Arc], + pool_transactions: &[Arc>], address: Address, ) -> Option { if let Some(highest_nonce) = pool_transactions @@ -3046,166 +4327,281 @@ fn get_pool_transactions_nonce( .max() { let tx_count = highest_nonce.saturating_add(1); - return Some(tx_count) + return Some(tx_count); } None } #[async_trait::async_trait] -impl TransactionValidator for Backend { +impl TransactionValidator for Backend +where + N: Network, +{ async fn validate_pool_transaction( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, ) -> Result<(), BlockchainError> { let address = *tx.sender(); let account = self.get_account(address).await?; - let env = self.next_env(); - Ok(self.validate_pool_transaction_for(tx, &account, &env)?) + let evm_env = self.next_evm_env(); + + // Tempo AA: validate time bounds and fee token balance (async checks) + if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() { + let tempo_tx = aa_tx.tx(); + let current_time = evm_env.block_env.timestamp.saturating_to::(); + + // Reject if valid_before is expired or too close to current time (< 3 seconds) + const AA_VALID_BEFORE_MIN_SECS: u64 = 3; + if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) { + let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS); + if valid_before <= min_allowed { + return Err(InvalidTransactionError::TempoValidBeforeExpired { + valid_before, + min_allowed, + } + .into()); + } + } + + // Reject if valid_after is too far in the future (> 1 hour) + const AA_VALID_AFTER_MAX_SECS: u64 = 3600; + if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) { + let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS); + if valid_after > max_allowed { + return Err(InvalidTransactionError::TempoValidAfterTooFar { + valid_after, + max_allowed, + } + .into()); + } + } + + // Fee token balance check + let fee_payer = tempo_tx.recover_fee_payer(address).unwrap_or(address); + let fee_token = + tempo_tx.fee_token.unwrap_or(foundry_evm::core::tempo::PATH_USD_ADDRESS); + + // gas_limit * max_fee_per_gas in wei, scaled to 6-decimal token units + let required_wei = + U256::from(tempo_tx.gas_limit).saturating_mul(U256::from(tempo_tx.max_fee_per_gas)); + let required = required_wei / U256::from(10u64.pow(12)); + + let balance = self.get_fee_token_balance(fee_token, fee_payer).await?; + if balance < required { + return Err(InvalidTransactionError::TempoInsufficientFeeTokenBalance { + balance, + required, + } + .into()); + } + } + + Ok(self.validate_pool_transaction_for(tx, &account, &evm_env)?) } fn validate_pool_transaction_for( &self, - pending: &PendingTransaction, + pending: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; if let Some(tx_chain_id) = tx.chain_id() { let chain_id = self.chain_id(); if chain_id.to::() != tx_chain_id { - if let Some(legacy) = tx.as_legacy() { + if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() { // - if env.evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && - legacy.tx().chain_id.is_none() - { - warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); + if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() { + debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); return Err(InvalidTransactionError::IncompatibleEIP155); } } else { - warn!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); + debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); return Err(InvalidTransactionError::InvalidChainId); } } } - if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 { - warn!(target: "backend", "[{:?}] gas too low", tx.hash()); - return Err(InvalidTransactionError::GasTooLow); + // Reject native value transfers on Tempo networks + if self.is_tempo() && !tx.value().is_zero() { + warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash()); + return Err(InvalidTransactionError::TempoNativeValueTransfer); } - // Check gas limit, iff block gas limit is set. - if !env.evm_env.cfg_env.disable_block_gas_limit && - tx.gas_limit() > env.evm_env.block_env.gas_limit - { - warn!(target: "backend", "[{:?}] gas too high", tx.hash()); - return Err(InvalidTransactionError::GasTooHigh(ErrDetail { - detail: String::from("tx.gas_limit > env.block.gas_limit"), - })); + // Tempo AA: cap authorization list size + if let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref() { + const MAX_TEMPO_AUTHORIZATIONS: usize = 16; + let auth_count = aa_tx.tx().tempo_authorization_list.len(); + if auth_count > MAX_TEMPO_AUTHORIZATIONS { + warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count); + return Err(InvalidTransactionError::TempoTooManyAuthorizations { + count: auth_count, + max: MAX_TEMPO_AUTHORIZATIONS, + }); + } } - // check nonce - let is_deposit_tx = - matches!(&pending.transaction.transaction, TypedTransaction::Deposit(_)); + // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system) + #[cfg(feature = "optimism")] + let is_deposit_tx = pending.transaction.as_ref().is_deposit(); + #[cfg(not(feature = "optimism"))] + let is_deposit_tx = false; + let is_tempo_tx = pending.transaction.as_ref().is_tempo(); let nonce = tx.nonce(); - if nonce < account.nonce && !is_deposit_tx { - warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); + if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx { + debug!(target: "backend", "[{:?}] nonce too low", tx.hash()); return Err(InvalidTransactionError::NonceTooLow); } - if env.evm_env.cfg_env.spec >= SpecId::LONDON { - if tx.gas_price() < env.evm_env.block_env.basefee.into() && !is_deposit_tx { - warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.evm_env.block_env.basefee); - return Err(InvalidTransactionError::FeeCapTooLow); - } - - if let (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) = - (tx.essentials().max_priority_fee_per_gas, tx.essentials().max_fee_per_gas) - { - if max_priority_fee_per_gas > max_fee_per_gas { - warn!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); - return Err(InvalidTransactionError::TipAboveFeeCap); - } - } - } - - // EIP-4844 Cancun hard fork validation steps - if env.evm_env.cfg_env.spec >= SpecId::CANCUN && tx.transaction.is_eip4844() { - // Light checks first: see if the blob fee cap is too low. - if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { - if let Some(blob_gas_and_price) = &env.evm_env.block_env.blob_excess_gas_and_price { - if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice { - warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); - return Err(InvalidTransactionError::BlobFeeCapTooLow); - } - } - } - + // EIP-4844 structural validation + if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() { // Heavy (blob validation) checks - let tx = match &tx.transaction { - TypedTransaction::EIP4844(tx) => tx.tx(), + let blob_tx = match tx.as_ref() { + FoundryTxEnvelope::Eip4844(tx) => tx.tx(), _ => unreachable!(), }; - let blob_count = tx.tx().blob_versioned_hashes.len(); + let blob_count = blob_tx.tx().blob_versioned_hashes.len(); // Ensure there are blob hashes. if blob_count == 0 { - return Err(InvalidTransactionError::NoBlobHashes) + return Err(InvalidTransactionError::NoBlobHashes); } - // Ensure the tx does not exceed the max blobs per block. - let max_blob_count = self.blob_params().max_blob_count as usize; - if blob_count > max_blob_count { - return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blob_count)) + // Ensure the tx does not exceed the max blobs per transaction. + let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize; + if blob_count > max_blobs_per_tx { + return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx)); } // Check for any blob validation errors if not impersonating. - if !self.skip_blob_validation(Some(*pending.sender())) { - if let Err(err) = tx.validate(EnvKzgSettings::default().get()) { - return Err(InvalidTransactionError::BlobTransactionValidationError(err)) - } + if !self.skip_blob_validation(Some(*pending.sender())) + && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get()) + { + return Err(InvalidTransactionError::BlobTransactionValidationError(err)); + } + } + + // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit + if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create { + let max_initcode_size = evm_env + .cfg_env + .limit_contract_code_size + .map(|limit| limit.saturating_mul(2)) + .unwrap_or(revm::primitives::eip3860::MAX_INITCODE_SIZE); + if tx.input().len() > max_initcode_size { + return Err(InvalidTransactionError::MaxInitCodeSizeExceeded); } } - let max_cost = tx.max_cost(); - let value = tx.value(); + // Balance and fee related checks + if !self.disable_pool_balance_checks { + // Gas limit validation + if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 { + debug!(target: "backend", "[{:?}] gas too low", tx.hash()); + return Err(InvalidTransactionError::GasTooLow); + } + + // Check tx gas limit against block gas limit, if block gas limit is set. + if !evm_env.cfg_env.disable_block_gas_limit + && tx.gas_limit() > evm_env.block_env.gas_limit + { + debug!(target: "backend", "[{:?}] gas too high", tx.hash()); + return Err(InvalidTransactionError::GasTooHigh(ErrDetail { + detail: String::from("tx.gas_limit > env.block.gas_limit"), + })); + } + + // Check tx gas limit against tx gas limit cap (Osaka hard fork and later). + if evm_env.cfg_env.tx_gas_limit_cap.is_none() + && tx.gas_limit() > evm_env.cfg_env().tx_gas_limit_cap() + { + debug!(target: "backend", "[{:?}] gas too high", tx.hash()); + return Err(InvalidTransactionError::GasTooHigh(ErrDetail { + detail: String::from("tx.gas_limit > env.cfg.tx_gas_limit_cap"), + })); + } + + // EIP-1559 fee validation (London hard fork and later). + if evm_env.cfg_env.spec >= SpecId::LONDON { + if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx { + debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee); + return Err(InvalidTransactionError::FeeCapTooLow); + } - match &tx.transaction { - TypedTransaction::Deposit(deposit_tx) => { - // Deposit transactions - // https://specs.optimism.io/protocol/deposits.html#execution - // 1. no gas cost check required since already have prepaid gas from L1 - // 2. increment account balance by deposited amount before checking for sufficient - // funds `tx.value <= existing account value + deposited value` - if value > account.balance + U256::from(deposit_tx.mint) { - warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender()); - return Err(InvalidTransactionError::InsufficientFunds); + if let (Some(max_priority_fee_per_gas), max_fee_per_gas) = + (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas()) + && max_priority_fee_per_gas > max_fee_per_gas + { + debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); + return Err(InvalidTransactionError::TipAboveFeeCap); } } - _ => { - // check sufficient funds: `gas * price + value` - let req_funds = max_cost.checked_add(value.saturating_to()).ok_or_else(|| { - warn!(target: "backend", "[{:?}] cost too high", tx.hash()); - InvalidTransactionError::InsufficientFunds - })?; - if account.balance < U256::from(req_funds) { - warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); - return Err(InvalidTransactionError::InsufficientFunds); + + // EIP-4844 blob fee validation + if evm_env.cfg_env.spec >= SpecId::CANCUN + && tx.is_eip4844() + && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() + && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price + && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice + { + debug!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); + return Err(InvalidTransactionError::BlobFeeCapTooLow( + max_fee_per_blob_gas, + blob_gas_and_price.blob_gasprice, + )); + } + + let max_cost = + (tx.gas_limit() as u128).saturating_mul(tx.max_fee_per_gas()).saturating_add( + tx.blob_gas_used() + .map(|g| g as u128) + .unwrap_or(0) + .mul(tx.max_fee_per_blob_gas().unwrap_or(0)), + ); + let value = tx.value(); + match tx.as_ref() { + #[cfg(feature = "optimism")] + FoundryTxEnvelope::Deposit(deposit_tx) => { + // Deposit transactions + // https://specs.optimism.io/protocol/deposits.html#execution + // 1. no gas cost check required since already have prepaid gas from L1 + // 2. increment account balance by deposited amount before checking for + // sufficient funds `tx.value <= existing account value + deposited value` + if value > account.balance + U256::from(deposit_tx.mint) { + debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender()); + return Err(InvalidTransactionError::InsufficientFunds); + } + } + FoundryTxEnvelope::Tempo(_) => { + // Tempo AA transactions pay gas with fee tokens, not ETH. + // Fee token balance is validated in validate_pool_transaction (async). + } + _ => { + // check sufficient funds: `gas * price + value` + let req_funds = + max_cost.checked_add(value.saturating_to()).ok_or_else(|| { + debug!(target: "backend", "[{:?}] cost too high", tx.hash()); + InvalidTransactionError::InsufficientFunds + })?; + if account.balance < U256::from(req_funds) { + debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); + return Err(InvalidTransactionError::InsufficientFunds); + } } } } - Ok(()) } fn validate_for( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError> { - self.validate_pool_transaction_for(tx, account, env)?; + self.validate_pool_transaction_for(tx, account, evm_env)?; if tx.nonce() > account.nonce { return Err(InvalidTransactionError::NonceTooHigh); } @@ -3213,15 +4609,25 @@ impl TransactionValidator for Backend { } } +/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature. +fn rehash(signed: Signed, hash: B256) -> Signed +where + T: alloy_consensus::transaction::RlpEcdsaEncodableTx, +{ + let (t, sig, _) = signed.into_parts(); + Signed::new_unchecked(t, sig, hash) +} + /// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data pub fn transaction_build( tx_hash: Option, - eth_transaction: MaybeImpersonatedTransaction, + eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, base_fee: Option, ) -> AnyRpcTransaction { - if let TypedTransaction::Deposit(ref deposit_tx) = eth_transaction.transaction { + #[cfg(feature = "optimism")] + if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() { let dep_tx = deposit_tx; let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit"); @@ -3252,9 +4658,10 @@ pub fn transaction_build( block_hash: block .as_ref() .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), - block_number: block.as_ref().map(|block| block.header.number), + block_number: block.as_ref().map(|block| block.header.number()), transaction_index: info.as_ref().map(|info| info.transaction_index), effective_gas_price: None, + block_timestamp: block.as_ref().map(|block| block.header.timestamp()), }; return AnyRpcTransaction::from(WithOtherFields::new(tx)); @@ -3265,73 +4672,71 @@ pub fn transaction_build( } } - let mut transaction: Transaction = eth_transaction.clone().into(); + if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() { + let from = eth_transaction.recover().unwrap_or_default(); + let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction"); + let maybe_tempo_fields = OtherFields::try_from(ser); + + match maybe_tempo_fields { + Ok(fields) => { + let inner = UnknownTypedTransaction { + ty: AnyTxType(TEMPO_TX_TYPE_ID), + fields, + memo: Default::default(), + }; - let effective_gas_price = if !eth_transaction.is_dynamic_fee() { - transaction.effective_gas_price(base_fee) - } else if block.is_none() && info.is_none() { - // transaction is not mined yet, gas price is considered just `max_fee_per_gas` - transaction.max_fee_per_gas() - } else { - // if transaction is already mined, gas price is considered base fee + priority - // fee: the effective gas price. - let base_fee = base_fee.map_or(0u128, |g| g as u128); - let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas().unwrap_or(0); + let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope { + hash: eth_transaction.hash(), + inner, + }); - base_fee.saturating_add(max_priority_fee_per_gas) - }; + let tx = Transaction { + inner: Recovered::new_unchecked(envelope, from), + block_hash: block.as_ref().map(|block| block.header.hash_slow()), + block_number: block.as_ref().map(|block| block.header.number()), + transaction_index: info.as_ref().map(|info| info.transaction_index), + effective_gas_price: None, + block_timestamp: block.as_ref().map(|block| block.header.timestamp()), + }; - transaction.effective_gas_price = Some(effective_gas_price); + return AnyRpcTransaction::from(WithOtherFields::new(tx)); + } + Err(_) => { + error!(target: "backend", "failed to serialize tempo transaction"); + } + } + } - let envelope = transaction.inner; + let from = eth_transaction.recover().unwrap_or_default(); + let effective_gas_price = eth_transaction.effective_gas_price(base_fee); // if a specific hash was provided we update the transaction's hash // This is important for impersonated transactions since they all use the // `BYPASS_SIGNATURE` which would result in different hashes // Note: for impersonated transactions this only concerns pending transactions because - // there's // no `info` yet. - let hash = tx_hash.unwrap_or(*envelope.tx_hash()); - - let envelope = match envelope.into_inner() { - TxEnvelope::Legacy(signed_tx) => { - let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); - AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(new_signed)) - } - TxEnvelope::Eip1559(signed_tx) => { - let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); - AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(new_signed)) - } - TxEnvelope::Eip2930(signed_tx) => { - let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); - AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(new_signed)) - } - TxEnvelope::Eip4844(signed_tx) => { - let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); - AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(new_signed)) - } - TxEnvelope::Eip7702(signed_tx) => { - let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); - AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(new_signed)) - } + // there's no `info` yet. + let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash()); + + let eth_envelope = FoundryTxEnvelope::from(eth_transaction) + .try_into_eth() + .expect("non-standard transactions are handled above"); + + let envelope = match eth_envelope { + TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))), + TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))), + TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))), + TxEnvelope::Eip4844(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash))), + TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))), }; let tx = Transaction { - inner: Recovered::new_unchecked( - envelope, - eth_transaction.recover().expect("can recover signed tx"), - ), - block_hash: block - .as_ref() - .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), - block_number: block.as_ref().map(|block| block.header.number), + inner: Recovered::new_unchecked(envelope, from), + block_hash: block.as_ref().map(|block| block.header.hash_slow()), + block_number: block.as_ref().map(|block| block.header.number()), transaction_index: info.as_ref().map(|info| info.transaction_index), // deprecated effective_gas_price: Some(effective_gas_price), + block_timestamp: block.as_ref().map(|block| block.header.timestamp()), }; AnyRpcTransaction::from(WithOtherFields::new(tx)) } @@ -3341,7 +4746,10 @@ pub fn transaction_build( /// `storage_key` is the hash of the desired storage key, meaning /// this will only work correctly under a secure trie. /// `storage_key` == keccak(key) -pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec> { +pub fn prove_storage( + storage: &alloy_primitives::map::U256Map, + keys: &[B256], +) -> (B256, Vec>) { let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect(); let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone())); @@ -3350,7 +4758,7 @@ pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec, keys: &[B256]) -> Vec bool { if let Ok(chain) = NamedChain::try_from(chain_id) { - return chain.is_arbitrum() + return chain.is_arbitrum(); } false } -pub fn op_haltreason_to_instruction_result(op_reason: OpHaltReason) -> InstructionResult { - match op_reason { - OpHaltReason::Base(eth_h) => eth_h.into(), - OpHaltReason::FailedDeposit => InstructionResult::Stop, +/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs. +fn unpack_execution_result( + result: ExecutionResult, +) -> (InstructionResult, u64, Option, Vec) { + match result { + ExecutionResult::Success { reason, gas, output, logs, .. } => { + (reason.into(), gas.tx_gas_used(), Some(output), logs) + } + ExecutionResult::Revert { gas, output, logs, .. } => { + (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs) + } + ExecutionResult::Halt { reason, gas, logs, .. } => { + (reason.into_instruction_result(), gas.tx_gas_used(), None, logs) + } + } +} + +/// Converts a halt reason into an [`InstructionResult`]. +/// +/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`) +/// so that anvil code doesn't need to match on each variant directly. +pub use foundry_evm::core::evm::IntoInstructionResult; + +#[cfg(test)] +mod tests { + use crate::{NodeConfig, spawn}; + + #[tokio::test] + async fn test_deterministic_block_mining() { + // Test that mine_block produces deterministic block hashes with same initial conditions + let genesis_timestamp = 1743944919u64; + + // Create two identical backends + let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into()); + let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into()); + + let (api_a, _handle_a) = spawn(config_a).await; + let (api_b, _handle_b) = spawn(config_b).await; + + // Mine empty blocks (no transactions) on both backends + let outcome_a_1 = api_a.backend.mine_block(vec![]).await; + let outcome_b_1 = api_b.backend.mine_block(vec![]).await; + + // Both should mine the same block number + assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number); + + // Get the actual blocks to compare hashes + let block_a_1 = + api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap(); + let block_b_1 = + api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap(); + + // The block hashes should be identical + assert_eq!( + block_a_1.header.hash, block_b_1.header.hash, + "Block hashes should be deterministic. Got {} vs {}", + block_a_1.header.hash, block_b_1.header.hash + ); + + // Mine another block to ensure it remains deterministic + let outcome_a_2 = api_a.backend.mine_block(vec![]).await; + let outcome_b_2 = api_b.backend.mine_block(vec![]).await; + + let block_a_2 = + api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap(); + let block_b_2 = + api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap(); + + assert_eq!( + block_a_2.header.hash, block_b_2.header.hash, + "Second block hashes should also be deterministic. Got {} vs {}", + block_a_2.header.hash, block_b_2.header.hash + ); + + // Ensure the blocks are different (sanity check) + assert_ne!( + block_a_1.header.hash, block_a_2.header.hash, + "Different blocks should have different hashes" + ); } } diff --git a/crates/anvil/src/eth/backend/mem/optimism.rs b/crates/anvil/src/eth/backend/mem/optimism.rs new file mode 100644 index 0000000000000..e9a94cc254fb7 --- /dev/null +++ b/crates/anvil/src/eth/backend/mem/optimism.rs @@ -0,0 +1,61 @@ +//! Optimism-specific transact helpers for the in-memory backend. + +use super::Backend; +use crate::eth::error::BlockchainError; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use alloy_network::Network; +use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx}; +use foundry_evm::backend::DatabaseError; +use op_revm::{OpHaltReason, OpSpecId, OpTransaction}; +use revm::{ + DatabaseRef, Inspector, + context::{ + TxEnv, + result::{EVMError, HaltReason, ResultAndState}, + }, + database_interface::WrapDatabaseRef, +}; + +impl Backend { + /// Optimism path of [`Backend::transact_with_inspector_ref`]. + /// + /// Creates an OP EVM, injects precompiles, transacts, and maps the + /// OP-specific halt reason back to the shared [`HaltReason`]. + pub(super) fn transact_op_with_inspector_ref<'db, I, DB>( + &self, + db: &'db DB, + evm_env: &EvmEnv, + inspector: &mut I, + tx_env: OpTransaction, + ) -> Result, BlockchainError> + where + DB: DatabaseRef + ?Sized, + I: Inspector>>, + WrapDatabaseRef<&'db DB>: Database, + { + let op_env = EvmEnv::new( + evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(OpSpecId::ISTHMUS), + evm_env.block_env.clone(), + ); + let mut evm = OpEvmFactory::default().create_evm_with_inspector( + WrapDatabaseRef(db), + op_env, + inspector, + ); + self.inject_precompiles(evm.precompiles_mut()); + let result = evm.transact(OpTx(tx_env)).map_err(|e| match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::CustomAny(err) => EVMError::CustomAny(err), + EVMError::Transaction(t) => EVMError::Transaction(t), + })?; + Ok(ResultAndState { + result: result.result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }), + state: result.state, + }) + } +} diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index 7adb49f15512d..768b895006f74 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,17 +1,12 @@ //! Support for generating the state root for memdb storage -use crate::eth::error::BlockchainError; -use alloy_primitives::{keccak256, map::HashMap, Address, B256, U256}; +use alloy_primitives::{ + B256, U256, keccak256, + map::{AddressMap, U256Map}, +}; use alloy_rlp::Encodable; -use alloy_rpc_types::{state::StateOverride, BlockOverrides}; use alloy_trie::{HashBuilder, Nibbles}; -use foundry_evm::backend::DatabaseError; -use revm::{ - bytecode::Bytecode, - context::BlockEnv, - database::{CacheDB, DatabaseRef, DbAccount}, - state::AccountInfo, -}; +use revm::{database::DbAccount, state::AccountInfo}; pub fn build_root(values: impl IntoIterator)>) -> B256 { let mut builder = HashBuilder::default(); @@ -22,17 +17,17 @@ pub fn build_root(values: impl IntoIterator)>) -> B256 } /// Builds state root from the given accounts -pub fn state_root(accounts: &HashMap) -> B256 { +pub fn state_root(accounts: &AddressMap) -> B256 { build_root(trie_accounts(accounts)) } /// Builds storage root from the given storage -pub fn storage_root(storage: &HashMap) -> B256 { +pub fn storage_root(storage: &U256Map) -> B256 { build_root(trie_storage(storage)) } /// Builds iterator over stored key-value pairs ready for storage trie root calculation. -pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { +pub fn trie_storage(storage: &U256Map) -> Vec<(Nibbles, Vec)> { let mut storage = storage .iter() .map(|(key, value)| { @@ -40,27 +35,27 @@ pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { (Nibbles::unpack(keccak256(key.to_be_bytes::<32>())), data) }) .collect::>(); - storage.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + storage.sort_by_key(|(key, _)| *key); storage } /// Builds iterator over stored key-value pairs ready for account trie root calculation. -pub fn trie_accounts(accounts: &HashMap) -> Vec<(Nibbles, Vec)> { - let mut accounts = accounts +pub fn trie_accounts(accounts: &AddressMap) -> Vec<(Nibbles, Vec)> { + let mut accounts: Vec<(Nibbles, Vec)> = accounts .iter() .map(|(address, account)| { let data = trie_account_rlp(&account.info, &account.storage); (Nibbles::unpack(keccak256(*address)), data) }) - .collect::>(); - accounts.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + .collect(); + accounts.sort_by_key(|(key, _)| *key); accounts } /// Returns the RLP for this account. -pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Vec { +pub fn trie_account_rlp(info: &AccountInfo, storage: &U256Map) -> Vec { let mut out: Vec = Vec::new(); let list: [&dyn Encodable; 4] = [&info.nonce, &info.balance, &storage_root(storage), &info.code_hash]; @@ -69,103 +64,3 @@ pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Ve out } - -/// Applies the given state overrides to the given CacheDB -pub fn apply_state_overrides( - overrides: StateOverride, - cache_db: &mut CacheDB, -) -> Result<(), BlockchainError> -where - D: DatabaseRef, -{ - for (account, account_overrides) in &overrides { - let mut account_info = cache_db.basic_ref(*account)?.unwrap_or_default(); - - if let Some(nonce) = account_overrides.nonce { - account_info.nonce = nonce; - } - if let Some(code) = &account_overrides.code { - account_info.code = Some(Bytecode::new_raw(code.to_vec().into())); - } - if let Some(balance) = account_overrides.balance { - account_info.balance = balance; - } - - cache_db.insert_account_info(*account, account_info); - - // We ensure that not both state and state_diff are set. - // If state is set, we must mark the account as "NewlyCreated", so that the old storage - // isn't read from - match (&account_overrides.state, &account_overrides.state_diff) { - (Some(_), Some(_)) => { - return Err(BlockchainError::StateOverrideError( - "state and state_diff can't be used together".to_string(), - )) - } - (None, None) => (), - (Some(new_account_state), None) => { - cache_db.replace_account_storage( - *account, - new_account_state - .iter() - .map(|(key, value)| ((*key).into(), (*value).into())) - .collect(), - )?; - } - (None, Some(account_state_diff)) => { - for (key, value) in account_state_diff { - cache_db.insert_account_storage(*account, (*key).into(), (*value).into())?; - } - } - }; - } - Ok(()) -} - -/// Applies the given block overrides to the env and updates overridden block hashes in the db. -pub fn apply_block_overrides( - overrides: BlockOverrides, - cache_db: &mut CacheDB, - env: &mut BlockEnv, -) { - let BlockOverrides { - number, - difficulty, - time, - gas_limit, - coinbase, - random, - base_fee, - block_hash, - } = overrides; - - if let Some(block_hashes) = block_hash { - // override block hashes - cache_db - .cache - .block_hashes - .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash))) - } - - if let Some(number) = number { - env.number = number.saturating_to(); - } - if let Some(difficulty) = difficulty { - env.difficulty = difficulty; - } - if let Some(time) = time { - env.timestamp = time; - } - if let Some(gas_limit) = gas_limit { - env.gas_limit = gas_limit; - } - if let Some(coinbase) = coinbase { - env.beneficiary = coinbase; - } - if let Some(random) = random { - env.prevrandao = Some(random); - } - if let Some(base_fee) = base_fee { - env.basefee = base_fee.saturating_to(); - } -} diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 252435291cc68..a80f6bfd1f5e8 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -5,40 +5,36 @@ use crate::eth::{ MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates, SerializableTransaction, StateDb, }, - env::Env, mem::cache::DiskStateCache, }, - error::BlockchainError, pool::transactions::PoolTransaction, }; -use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_consensus::{BlockHeader, Header, constants::EMPTY_WITHDRAWALS}; use alloy_eips::eip7685::EMPTY_REQUESTS_HASH; +use alloy_evm::EvmEnv; +use alloy_network::Network; use alloy_primitives::{ + B256, Bytes, U256, map::{B256HashMap, HashMap}, - Bytes, B256, U256, }; use alloy_rpc_types::{ + BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo, trace::{ - geth::{ - FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, - GethDebugTracingOptions, GethTrace, NoopFrame, - }, otterscan::{InternalOperation, OperationType}, parity::LocalizedTransactionTrace, }, - BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo, }; use anvil_core::eth::{ - block::{Block, PartialHeader}, - transaction::{MaybeImpersonatedTransaction, ReceiptResponse, TransactionInfo, TypedReceipt}, + block::{Block, create_block}, + transaction::{MaybeImpersonatedTransaction, TransactionInfo}, }; -use anvil_rpc::error::RpcError; use foundry_evm::{ backend::MemDb, - traces::{ - CallKind, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig, - }, + traces::{CallKind, ParityTraceBuilder, TracingInspectorConfig}, }; +#[cfg(test)] +use foundry_primitives::FoundryNetwork; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; use parking_lot::RwLock; use revm::{context::Block as RevmBlock, primitives::hardfork::SpecId}; use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration}; @@ -89,7 +85,7 @@ impl InMemoryBlockStates { } /// Configures no disk caching - pub fn memory_only(mut self) -> Self { + pub const fn memory_only(mut self) -> Self { self.max_on_disk_limit = 0; self } @@ -116,7 +112,7 @@ impl InMemoryBlockStates { } /// Returns true if only memory caching is supported. - fn is_memory_only(&self) -> bool { + const fn is_memory_only(&self) -> bool { self.max_on_disk_limit == 0 } @@ -156,9 +152,19 @@ impl InMemoryBlockStates { // only write to disk if supported if !self.is_memory_only() { let state_snapshot = state.0.clear_into_state_snapshot(); - self.disk_cache.write(hash, state_snapshot); - self.on_disk_states.insert(hash, state); - self.oldest_on_disk.push_back(hash); + if self.disk_cache.write(hash, &state_snapshot) { + // Write succeeded, move state to on-disk tracking + self.on_disk_states.insert(hash, state); + self.oldest_on_disk.push_back(hash); + } else { + // Write failed, restore state to memory to avoid data loss + state.init_from_state_snapshot(state_snapshot); + self.states.insert(hash, state); + self.present.push_front(hash); + // Increase limit temporarily to prevent infinite retry loop + self.in_memory_limit = self.in_memory_limit.saturating_add(1); + break; + } } } } @@ -173,21 +179,25 @@ impl InMemoryBlockStates { } } - /// Returns the state for the given `hash` if present - pub fn get(&mut self, hash: &B256) -> Option<&StateDb> { - self.states.get(hash).or_else(|| { - if let Some(state) = self.on_disk_states.get_mut(hash) { - if let Some(cached) = self.disk_cache.read(*hash) { - state.init_from_state_snapshot(cached); - return Some(state); - } - } - None - }) + /// Returns the in-memory state for the given `hash` if present + pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { + self.states.get(hash) + } + + /// Returns on-disk state for the given `hash` if present + pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> { + if let Some(state) = self.on_disk_states.get_mut(hash) + && let Some(cached) = self.disk_cache.read(*hash) + { + state.init_from_state_snapshot(cached); + return Some(state); + } + + None } /// Sets the maximum number of stats we keep in memory - pub fn set_cache_limit(&mut self, limit: usize) { + pub const fn set_cache_limit(&mut self, limit: usize) { self.in_memory_limit = limit; } @@ -201,6 +211,20 @@ impl InMemoryBlockStates { } } + /// Removes states for the given block hashes. + /// + /// This is used during chain rollback to clean up states for blocks that are no longer part + /// of the canonical chain. + pub fn remove_block_states(&mut self, hashes: &[B256]) { + for hash in hashes { + self.states.remove(hash); + self.on_disk_states.remove(hash); + self.disk_cache.remove(*hash); + } + self.present.retain(|h| !hashes.contains(h)); + self.oldest_on_disk.retain(|h| !hashes.contains(h)); + } + /// Serialize all states to a list of serializable historical states pub fn serialized_states(&mut self) -> SerializableHistoricalStates { // Get in-memory states @@ -250,8 +274,8 @@ impl Default for InMemoryBlockStates { } /// Stores the blockchain data (blocks, transactions) -#[derive(Clone)] -pub struct BlockchainStorage { +#[derive(Clone, Debug)] +pub struct BlockchainStorage { /// all stored blocks (block hash -> block) pub blocks: B256HashMap, /// mapping from block number -> block hash @@ -264,40 +288,40 @@ pub struct BlockchainStorage { pub genesis_hash: B256, /// Mapping from the transaction hash to a tuple containing the transaction as well as the /// transaction receipt - pub transactions: B256HashMap, + pub transactions: B256HashMap>, /// The total difficulty of the chain until this block pub total_difficulty: U256, } -impl BlockchainStorage { +impl BlockchainStorage { /// Creates a new storage with a genesis block pub fn new( - env: &Env, - spec_id: SpecId, + evm_env: &EvmEnv, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { - let is_shanghai = spec_id >= SpecId::SHANGHAI; - let is_cancun = spec_id >= SpecId::CANCUN; - let is_prague = spec_id >= SpecId::PRAGUE; + let is_shanghai = *evm_env.spec_id() >= SpecId::SHANGHAI; + let is_cancun = *evm_env.spec_id() >= SpecId::CANCUN; + let is_prague = *evm_env.spec_id() >= SpecId::PRAGUE; // create a dummy genesis block - let partial_header = PartialHeader { + let header = Header { timestamp, - base_fee, - gas_limit: env.evm_env.block_env.gas_limit, - beneficiary: env.evm_env.block_env.beneficiary, - difficulty: env.evm_env.block_env.difficulty, - blob_gas_used: env.evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0), - excess_blob_gas: env.evm_env.block_env.blob_excess_gas(), + base_fee_per_gas: base_fee, + gas_limit: evm_env.block_env.gas_limit, + beneficiary: evm_env.block_env.beneficiary, + difficulty: evm_env.block_env.difficulty, + blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas: evm_env.block_env.blob_excess_gas(), number: genesis_number, parent_beacon_block_root: is_cancun.then_some(Default::default()), withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), ..Default::default() }; - let block = Block::new::(partial_header, vec![]); + let block = + create_block(header, Vec::>::new()); let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; let best_number = genesis_number; @@ -337,17 +361,24 @@ impl BlockchainStorage { /// /// The block identified by `block_number` and `block_hash` is __non-inclusive__, i.e. it will /// remain in the state. - pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) { + pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) -> Vec { + let mut removed = vec![]; let best_num: u64 = self.best_number; for i in (block_number + 1)..=best_num { - if let Some(hash) = self.hashes.remove(&i) { + if let Some(hash) = self.hashes.get(&i).copied() { + // First remove the block's transactions while the mappings still exist + self.remove_block_transactions_by_number(i); + + // Now remove the block from storage (may already be empty of txs) and drop mapping if let Some(block) = self.blocks.remove(&hash) { - self.remove_block_transactions_by_number(block.header.number); + removed.push(block); } + self.hashes.remove(&i); } } self.best_hash = block_hash; self.best_number = block_number; + removed } pub fn empty() -> Self { @@ -372,15 +403,36 @@ impl BlockchainStorage { /// Removes all stored transactions for the given block hash pub fn remove_block_transactions(&mut self, block_hash: B256) { if let Some(block) = self.blocks.get_mut(&block_hash) { - for tx in &block.transactions { + for tx in &block.body.transactions { self.transactions.remove(&tx.hash()); } - block.transactions.clear(); + block.body.transactions.clear(); + } + } + + /// Serialize all blocks in storage + pub fn serialized_blocks(&self) -> Vec { + self.blocks.values().map(|block| block.clone().into()).collect() + } + + /// Deserialize and add all blocks data to the backend storage + pub fn load_blocks(&mut self, serializable_blocks: Vec) { + for serializable_block in &serializable_blocks { + let block: Block = serializable_block.clone().into(); + let block_hash = block.header.hash_slow(); + let block_number = block.header.number(); + self.blocks.insert(block_hash, block); + self.hashes.insert(block_number, block_hash); + + // Update genesis_hash if we are loading block 0, so that Finalized/Safe/Earliest + // block tag lookups return the correct hash. + // See: https://github.com/foundry-rs/foundry/issues/12645 + if block_number == 0 { + self.genesis_hash = block_hash; + } } } -} -impl BlockchainStorage { /// Returns the hash for [BlockNumberOrTag] pub fn hash(&self, number: BlockNumberOrTag) -> Option { let slots_in_an_epoch = 32; @@ -405,55 +457,40 @@ impl BlockchainStorage { } } } +} - pub fn serialized_blocks(&self) -> Vec { - self.blocks.values().map(|block| block.clone().into()).collect() - } - +impl> BlockchainStorage { pub fn serialized_transactions(&self) -> Vec { - self.transactions.values().map(|tx: &MinedTransaction| tx.clone().into()).collect() + self.transactions.values().map(|tx: &MinedTransaction| tx.clone().into()).collect() } - /// Deserialize and add all blocks data to the backend storage - pub fn load_blocks(&mut self, serializable_blocks: Vec) { - for serializable_block in &serializable_blocks { - let block: Block = serializable_block.clone().into(); - let block_hash = block.header.hash_slow(); - let block_number = block.header.number; - self.blocks.insert(block_hash, block); - self.hashes.insert(block_number, block_hash); - } - } - - /// Deserialize and add all blocks data to the backend storage + /// Deserialize and add all transactions data to the backend storage pub fn load_transactions(&mut self, serializable_transactions: Vec) { for serializable_transaction in &serializable_transactions { - let transaction: MinedTransaction = serializable_transaction.clone().into(); + let transaction: MinedTransaction = serializable_transaction.clone().into(); self.transactions.insert(transaction.info.transaction_hash, transaction); } } } /// A simple in-memory blockchain -#[derive(Clone)] -pub struct Blockchain { +#[derive(Clone, Debug)] +pub struct Blockchain { /// underlying storage that supports concurrent reads - pub storage: Arc>, + pub storage: Arc>>, } -impl Blockchain { +impl Blockchain { /// Creates a new storage with a genesis block pub fn new( - env: &Env, - spec_id: SpecId, + evm_env: &EvmEnv, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new( - env, - spec_id, + evm_env, base_fee, timestamp, genesis_number, @@ -483,7 +520,7 @@ impl Blockchain { self.storage.read().blocks.get(hash).cloned() } - pub fn get_transaction_by_hash(&self, hash: &B256) -> Option { + pub fn get_transaction_by_hash(&self, hash: &B256) -> Option> { self.storage.read().transactions.get(hash).cloned() } @@ -494,27 +531,51 @@ impl Blockchain { } /// Represents the outcome of mining a new block -#[derive(Clone, Debug)] -pub struct MinedBlockOutcome { +pub struct MinedBlockOutcome { /// The block that was mined pub block_number: u64, /// All transactions included in the block - pub included: Vec>, + pub included: Vec>>, /// All transactions that were attempted to be included but were invalid at the time of /// execution - pub invalid: Vec>, + pub invalid: Vec>>, + /// Transactions skipped because they're not yet valid (e.g., valid_after in the future). + /// These remain in the pool and should be retried later. + pub not_yet_valid: Vec>>, +} + +impl Clone for MinedBlockOutcome { + fn clone(&self) -> Self { + Self { + block_number: self.block_number, + included: self.included.clone(), + invalid: self.invalid.clone(), + not_yet_valid: self.not_yet_valid.clone(), + } + } +} + +impl fmt::Debug for MinedBlockOutcome { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MinedBlockOutcome") + .field("block_number", &self.block_number) + .field("included", &self.included.len()) + .field("invalid", &self.invalid.len()) + .field("not_yet_valid", &self.not_yet_valid.len()) + .finish() + } } /// Container type for a mined transaction #[derive(Clone, Debug)] -pub struct MinedTransaction { +pub struct MinedTransaction { pub info: TransactionInfo, - pub receipt: TypedReceipt, + pub receipt: N::ReceiptEnvelope, pub block_hash: B256, pub block_number: u64, } -impl MinedTransaction { +impl MinedTransaction { /// Returns the traces of the transaction for `trace_transaction` pub fn parity_traces(&self) -> Vec { ParityTraceBuilder::new( @@ -528,6 +589,7 @@ impl MinedTransaction { block_hash: Some(self.block_hash), block_number: Some(self.block_number), base_fee: None, + block_timestamp: None, }) } @@ -543,64 +605,26 @@ impl MinedTransaction { CallKind::Create2 => OperationType::OpCreate2, _ => return None, }; - let mut from = node.trace.caller; - let mut to = node.trace.address; - let mut value = node.trace.value; - if node.is_selfdestruct() { - from = node.trace.address; - to = node.trace.selfdestruct_refund_target.unwrap_or_default(); - value = node.trace.selfdestruct_transferred_value.unwrap_or_default(); - } + let (from, to, value) = if node.is_selfdestruct() { + ( + node.trace.address, + node.trace.selfdestruct_refund_target.unwrap_or_default(), + node.trace.selfdestruct_transferred_value.unwrap_or_default(), + ) + } else { + (node.trace.caller, node.trace.address, node.trace.value) + }; Some(InternalOperation { r#type, from, to, value }) }) .collect() } - - pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> Result { - let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; - - if let Some(tracer) = tracer { - match tracer { - GethDebugTracerType::BuiltInTracer(tracer) => match tracer { - GethDebugBuiltInTracerType::FourByteTracer => { - let inspector = FourByteInspector::default(); - return Ok(FourByteFrame::from(inspector).into()); - } - GethDebugBuiltInTracerType::CallTracer => { - return match tracer_config.into_call_config() { - Ok(call_config) => Ok(GethTraceBuilder::new(self.info.traces.clone()) - .geth_call_traces(call_config, self.receipt.cumulative_gas_used()) - .into()), - Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), - }; - } - GethDebugBuiltInTracerType::PreStateTracer | - GethDebugBuiltInTracerType::NoopTracer | - GethDebugBuiltInTracerType::MuxTracer | - GethDebugBuiltInTracerType::FlatCallTracer => {} - }, - GethDebugTracerType::JsTracer(_code) => {} - } - - return Ok(NoopFrame::default().into()); - } - - // default structlog tracer - Ok(GethTraceBuilder::new(self.info.traces.clone()) - .geth_traces( - self.receipt.cumulative_gas_used(), - self.info.out.clone().unwrap_or_default(), - config, - ) - .into()) - } } /// Intermediary Anvil representation of a receipt #[derive(Clone, Debug)] -pub struct MinedTransactionReceipt { +pub struct MinedTransactionReceipt { /// The actual json rpc receipt object - pub inner: ReceiptResponse, + pub inner: N::ReceiptResponse, /// Output data for the transaction pub out: Option, } @@ -609,9 +633,8 @@ pub struct MinedTransactionReceipt { mod tests { use super::*; use crate::eth::backend::db::Db; - use alloy_primitives::{hex, Address}; + use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; - use anvil_core::eth::transaction::TypedTransaction; use revm::{database::DatabaseRef, state::AccountInfo}; #[test] @@ -663,7 +686,7 @@ mod tests { assert_eq!(storage.on_disk_states.len(), 1); assert!(storage.on_disk_states.contains_key(&one)); - let loaded = storage.get(&one).unwrap(); + let loaded = storage.get_on_disk_state(&one).unwrap(); let acc = loaded.basic_ref(addr).unwrap().unwrap(); assert_eq!(acc.balance, U256::from(1337u64)); @@ -688,45 +711,108 @@ mod tests { // wait for files to be flushed tokio::time::sleep(std::time::Duration::from_secs(1)).await; - assert_eq!(storage.on_disk_states.len(), num_states - storage.min_in_memory_limit); + let on_disk_states_len = num_states - storage.min_in_memory_limit; + + assert_eq!(storage.on_disk_states.len(), on_disk_states_len); assert_eq!(storage.present.len(), storage.min_in_memory_limit); for idx in 0..num_states { let hash = B256::from(U256::from(idx)); let addr = Address::from_word(hash); - let loaded = storage.get(&hash).unwrap(); + + let loaded = if idx < on_disk_states_len { + storage.get_on_disk_state(&hash).unwrap() + } else { + storage.get_state(&hash).unwrap() + }; + let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; assert_eq!(acc.balance, U256::from(balance)); } } + #[test] + fn test_remove_block_states_on_rollback() { + let mut storage = InMemoryBlockStates::new(10, MAX_ON_DISK_HISTORY_LIMIT); + + // Insert 5 states + let hashes: Vec = (0..5) + .map(|i| { + let hash = B256::from(U256::from(i)); + let mut state = MemDb::default(); + let addr = Address::from_word(hash); + state.insert_account(addr, AccountInfo::from_balance(U256::from(i * 100))); + storage.insert(hash, StateDb::new(state)); + hash + }) + .collect(); + + assert_eq!(storage.present.len(), 5); + + // Simulate rollback: remove the last 3 blocks + let removed_hashes = &hashes[2..]; + storage.remove_block_states(removed_hashes); + + // Only the first 2 states should remain + assert_eq!(storage.present.len(), 2); + assert!(storage.get_state(&hashes[0]).is_some()); + assert!(storage.get_state(&hashes[1]).is_some()); + for h in removed_hashes { + assert!(storage.get_state(h).is_none()); + assert!(!storage.present.contains(h)); + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_remove_block_states_cleans_disk_cache() { + // Use limit=1 to force states to disk + let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT); + + let hash_a = B256::from(U256::from(1)); + let hash_b = B256::from(U256::from(2)); + + storage.insert(hash_a, StateDb::new(MemDb::default())); + storage.insert(hash_b, StateDb::new(MemDb::default())); + + // Wait for disk flush + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + assert!(storage.on_disk_states.contains_key(&hash_a)); + + // Remove hash_a (on disk) + storage.remove_block_states(&[hash_a]); + + assert!(!storage.on_disk_states.contains_key(&hash_a)); + assert!(!storage.oldest_on_disk.contains(&hash_a)); + assert!(storage.get_on_disk_state(&hash_a).is_none()); + } + // verifies that blocks and transactions in BlockchainStorage remain the same when dumped and // reloaded #[test] fn test_storage_dump_reload_cycle() { - let mut dump_storage = BlockchainStorage::empty(); + let mut dump_storage = BlockchainStorage::::empty(); - let partial_header = PartialHeader { gas_limit: 123456, ..Default::default() }; + let header = Header { gas_limit: 123456, ..Default::default() }; let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; - let tx: MaybeImpersonatedTransaction = - TypedTransaction::decode(&mut &bytes_first[..]).unwrap().into(); - let block = - Block::new::(partial_header.clone(), vec![tx.clone()]); + let tx: MaybeImpersonatedTransaction = + FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into(); + let block = create_block(header.clone(), vec![tx.clone()]); let block_hash = block.header.hash_slow(); dump_storage.blocks.insert(block_hash, block); let serialized_blocks = dump_storage.serialized_blocks(); let serialized_transactions = dump_storage.serialized_transactions(); - let mut load_storage = BlockchainStorage::empty(); + let mut load_storage = BlockchainStorage::::empty(); load_storage.load_blocks(serialized_blocks); load_storage.load_transactions(serialized_transactions); let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); - assert_eq!(loaded_block.header.gas_limit, { partial_header.gas_limit }); - let loaded_tx = loaded_block.transactions.first().unwrap(); + assert_eq!(loaded_block.header.gas_limit(), header.gas_limit()); + let loaded_tx = loaded_block.body.transactions.first().unwrap(); assert_eq!(loaded_tx, &tx); } } diff --git a/crates/anvil/src/eth/backend/mod.rs b/crates/anvil/src/eth/backend/mod.rs index be2c90b576800..93e74e50be0fd 100644 --- a/crates/anvil/src/eth/backend/mod.rs +++ b/crates/anvil/src/eth/backend/mod.rs @@ -8,10 +8,10 @@ pub mod mem; pub mod cheats; pub mod time; -pub mod env; pub mod executor; pub mod fork; pub mod genesis; pub mod info; pub mod notifications; +pub mod tempo; pub mod validate; diff --git a/crates/anvil/src/eth/backend/tempo.rs b/crates/anvil/src/eth/backend/tempo.rs new file mode 100644 index 0000000000000..88db5fbcd0dfe --- /dev/null +++ b/crates/anvil/src/eth/backend/tempo.rs @@ -0,0 +1,318 @@ +//! Tempo precompile and fee token initialization for Anvil. +//! +//! When running in Tempo mode, Anvil needs to set up Tempo-specific precompiles +//! and fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD) to enable proper +//! transaction validation. +//! +//! This module provides a storage provider adapter for Anvil's `Db` trait and +//! uses the shared initialization logic from `foundry-evm-core`. + +use alloy_primitives::{Address, U256, address}; +use foundry_evm::core::tempo::{PATH_USD_ADDRESS, initialize_tempo_genesis}; +use revm::{ + context::journaled_state::JournalCheckpoint, + state::{AccountInfo, Bytecode}, +}; +use std::collections::HashMap; +use tempo_chainspec::hardfork::TempoHardfork; +use tempo_precompiles::{ + TIP_FEE_MANAGER_ADDRESS, + account_keychain::{ + AccountKeychain, + IAccountKeychain::{KeyRestrictions, SignatureType}, + authorizeKeyCall, + }, + error::TempoPrecompileError, + storage::{PrecompileStorageProvider, StorageCtx}, + tip_fee_manager::{IFeeManager, TipFeeManager}, + tip20::{ITIP20, TIP20Token}, +}; + +use super::db::Db; + +/// Sender address used for genesis initialization. +const SENDER: Address = address!("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38"); +/// Admin address used for genesis initialization. +const ADMIN: Address = address!("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"); + +const PATH_USD: Address = PATH_USD_ADDRESS; +const ALPHA_USD: Address = address!("0x20C0000000000000000000000000000000000001"); +const BETA_USD: Address = address!("0x20C0000000000000000000000000000000000002"); +const THETA_USD: Address = address!("0x20C0000000000000000000000000000000000003"); + +/// Storage provider adapter for Anvil's Db to work with Tempo precompiles. +pub struct AnvilStorageProvider<'a> { + db: &'a mut dyn Db, + chain_id: u64, + timestamp: U256, + block_number: u64, + gas_used: u64, + gas_refunded: i64, + reservoir: u64, + transient: HashMap<(Address, U256), U256>, + hardfork: TempoHardfork, +} + +impl<'a> AnvilStorageProvider<'a> { + pub fn new( + db: &'a mut dyn Db, + chain_id: u64, + timestamp: U256, + block_number: u64, + hardfork: TempoHardfork, + ) -> Self { + Self { + db, + chain_id, + timestamp, + block_number, + gas_used: 0, + gas_refunded: 0, + reservoir: 0, + transient: HashMap::new(), + hardfork, + } + } +} + +impl PrecompileStorageProvider for AnvilStorageProvider<'_> { + fn spec(&self) -> TempoHardfork { + self.hardfork + } + + fn chain_id(&self) -> u64 { + self.chain_id + } + + fn timestamp(&self) -> U256 { + self.timestamp + } + + fn block_number(&self) -> u64 { + self.block_number + } + + fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> { + self.db.insert_account( + address, + AccountInfo { + code_hash: code.hash_slow(), + code: Some(code), + nonce: 1, + ..Default::default() + }, + ); + Ok(()) + } + + fn with_account_info( + &mut self, + address: Address, + f: &mut dyn FnMut(&AccountInfo), + ) -> Result<(), TempoPrecompileError> { + use revm::DatabaseRef; + if let Some(info) = + self.db.basic_ref(address).map_err(|e| TempoPrecompileError::Fatal(e.to_string()))? + { + f(&info); + Ok(()) + } else { + Err(TempoPrecompileError::Fatal(format!("account '{address}' not found"))) + } + } + + fn sstore( + &mut self, + address: Address, + key: U256, + value: U256, + ) -> Result<(), TempoPrecompileError> { + use alloy_primitives::B256; + self.db + .set_storage_at(address, B256::from(key), B256::from(value)) + .map_err(|e| TempoPrecompileError::Fatal(e.to_string())) + } + + fn sload(&mut self, address: Address, key: U256) -> Result { + revm::Database::storage(self.db, address, key) + .map_err(|e| TempoPrecompileError::Fatal(e.to_string())) + } + + fn tstore( + &mut self, + address: Address, + key: U256, + value: U256, + ) -> Result<(), TempoPrecompileError> { + self.transient.insert((address, key), value); + Ok(()) + } + + fn tload(&mut self, address: Address, key: U256) -> Result { + Ok(self.transient.get(&(address, key)).copied().unwrap_or(U256::ZERO)) + } + + fn emit_event( + &mut self, + _address: Address, + _event: alloy_primitives::LogData, + ) -> Result<(), TempoPrecompileError> { + Ok(()) + } + + fn deduct_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> { + self.gas_used = self.gas_used.saturating_add(gas); + Ok(()) + } + + fn gas_used(&self) -> u64 { + self.gas_used + } + + fn state_gas_used(&self) -> u64 { + 0 + } + + fn gas_limit(&self) -> u64 { + u64::MAX + } + + fn gas_refunded(&self) -> i64 { + self.gas_refunded + } + + fn reservoir(&self) -> u64 { + self.reservoir + } + + fn refund_gas(&mut self, gas: i64) { + self.gas_refunded = self.gas_refunded.saturating_add(gas); + } + + fn beneficiary(&self) -> Address { + Address::ZERO + } + + fn is_static(&self) -> bool { + false + } + + fn checkpoint(&mut self) -> JournalCheckpoint { + JournalCheckpoint { log_i: 0, journal_i: 0, selfdestructed_i: 0 } + } + + fn checkpoint_commit(&mut self, _checkpoint: JournalCheckpoint) {} + + fn checkpoint_revert(&mut self, _checkpoint: JournalCheckpoint) {} +} + +/// Initialize Tempo precompiles and fee tokens for Anvil. +/// +/// This sets up the same precompiles and tokens as Tempo's genesis, enabling +/// proper fee token validation for transactions. +/// +/// Additionally, mints fee tokens to the provided test accounts so they can +/// send transactions in Tempo mode. +pub fn initialize_tempo_precompiles( + db: &mut dyn Db, + chain_id: u64, + timestamp: u64, + test_accounts: &[Address], + hardfork: TempoHardfork, +) -> Result<(), TempoPrecompileError> { + let timestamp = U256::from(timestamp); + + let mut storage = AnvilStorageProvider::new(db, chain_id, timestamp, 0, hardfork); + + // Initialize base Tempo genesis (precompiles and tokens) + initialize_tempo_genesis(&mut storage, ADMIN, SENDER)?; + + // Mint fee tokens to test accounts + // u64::MAX per account - safe since u128::MAX can hold ~18 quintillion u64::MAX values + let mint_amount = U256::from(u64::MAX); + let tokens = [PATH_USD, ALPHA_USD, BETA_USD, THETA_USD]; + + StorageCtx::enter(&mut storage, || -> Result<(), TempoPrecompileError> { + // Mint fee tokens to test accounts + for &token_address in &tokens { + let mut token = TIP20Token::from_address(token_address)?; + for &account in test_accounts { + token.mint(ADMIN, ITIP20::mintCall { to: account, amount: mint_amount })?; + } + } + + // Register secp256k1 keys for test accounts in the AccountKeychain + // This allows them to sign Tempo transactions using their private keys. + // The key ID is the account address itself (standard for secp256k1 keys). + let mut keychain = AccountKeychain::new(); + for &account in test_accounts { + // Seed tx_origin so ensure_admin_caller passes on T2+ (requires + // tx_origin != zero && tx_origin == msg_sender). + keychain.set_tx_origin(account)?; + keychain.authorize_key( + account, // msg_sender (root account authorizes its own key) + authorizeKeyCall { + keyId: account, // key ID = account address for secp256k1 + signatureType: SignatureType::Secp256k1, + config: KeyRestrictions { + expiry: u64::MAX, // never expires + enforceLimits: false, // no spending limits + limits: vec![], + allowAnyCalls: true, + allowedCalls: vec![], + }, + }, + )?; + } + + // Initialize TipFeeManager and set default fee tokens for test accounts + // Alice (0) -> AlphaUSD, Bob (1) -> BetaUSD, Charlie (2) -> ThetaUSD, others -> PathUSD + let mut fee_manager = TipFeeManager::new(); + fee_manager.initialize()?; + + for (i, &account) in test_accounts.iter().enumerate() { + let fee_token = match i { + 0 => ALPHA_USD, // Alice + 1 => BETA_USD, // Bob + 2 => THETA_USD, // Charlie + _ => PATH_USD, // Everyone else + }; + fee_manager + .set_user_token(account, IFeeManager::setUserTokenCall { token: fee_token })?; + } + + // Mint fee tokens to the FeeManager contract for liquidity operations + for &token_address in &tokens { + let mut token = TIP20Token::from_address(token_address)?; + token.mint( + ADMIN, + ITIP20::mintCall { to: TIP_FEE_MANAGER_ADDRESS, amount: mint_amount }, + )?; + } + + // Mint pairwise FeeAMM liquidity for all fee token pairs (both directions) + // This enables EIP-1559/legacy transactions by allowing fee swaps between tokens + // Liquidity amount: 10^10 tokens (matching Tempo genesis) + let liquidity_amount = U256::from(10u64.pow(10)); + + // Create bidirectional liquidity pools between all fee tokens + // Pools are directional: user_token -> validator_token + for &user_token in &tokens { + for &validator_token in &tokens { + if user_token != validator_token { + fee_manager.mint( + ADMIN, + user_token, + validator_token, + liquidity_amount, + ADMIN, + )?; + } + } + } + + Ok(()) + })?; + + Ok(()) +} diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index 3ae9524f0e5eb..056941942c419 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -7,7 +7,7 @@ use std::{sync::Arc, time::Duration}; /// Returns the `Utc` datetime for the given seconds since unix epoch pub fn utc_from_secs(secs: u64) -> DateTime { - DateTime::from_timestamp(secs as i64, 0).unwrap() + DateTime::from_timestamp(secs as i64, 0).unwrap_or(DateTime::::MAX_UTC) } /// Manages block time @@ -73,7 +73,7 @@ impl TimeManager { if timestamp < *self.last_timestamp.read() { return Err(BlockchainError::TimestampError(format!( "{timestamp} is lower than previous block's timestamp" - ))) + ))); } self.next_exact_timestamp.write().replace(timestamp); Ok(()) diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index 0a22ea5f5a880..858602787ca60 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -1,30 +1,28 @@ //! Support for validating transactions at certain stages -use crate::eth::{ - backend::env::Env, - error::{BlockchainError, InvalidTransactionError}, -}; +use crate::eth::error::{BlockchainError, InvalidTransactionError}; +use alloy_evm::EvmEnv; use anvil_core::eth::transaction::PendingTransaction; use revm::state::AccountInfo; /// A trait for validating transactions #[async_trait::async_trait] -pub trait TransactionValidator { +pub trait TransactionValidator { /// Validates the transaction's validity when it comes to nonce, payment /// /// This is intended to be checked before the transaction makes it into the pool and whether it /// should rather be outright rejected if the sender has insufficient funds. async fn validate_pool_transaction( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, ) -> Result<(), BlockchainError>; /// Validates the transaction against a specific account before entering the pool fn validate_pool_transaction_for( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -32,8 +30,8 @@ pub trait TransactionValidator { /// This should succeed if the transaction is ready to be executed fn validate_for( &self, - tx: &PendingTransaction, + tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + evm_env: &EvmEnv, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error/mod.rs similarity index 74% rename from crates/anvil/src/eth/error.rs rename to crates/anvil/src/eth/error/mod.rs index bd0c3b6e798d7..482df681b184a 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error/mod.rs @@ -1,7 +1,8 @@ //! Aggregated error type for this module -use crate::eth::pool::transactions::PoolTransaction; -use alloy_primitives::{Bytes, SignatureError}; +use alloy_consensus::crypto::RecoveryError; +use alloy_evm::overrides::StateOverrideError; +use alloy_primitives::{B256, Bytes, SignatureError, TxHash, U256}; use alloy_rpc_types::BlockNumberOrTag; use alloy_signer::Error as SignerError; use alloy_transport::TransportError; @@ -11,12 +12,16 @@ use anvil_rpc::{ response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; -use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, }; use serde::Serialize; +use tempo_revm::TempoInvalidTransaction; +use tokio::time::Duration; + +#[cfg(feature = "optimism")] +mod optimism; pub(crate) type Result = std::result::Result; @@ -40,11 +45,13 @@ pub enum BlockchainError { FailedToDecodeReceipt, #[error("Failed to decode state")] FailedToDecodeStateDump, - #[error("Prevrandao not in th EVM's environment after merge")] + #[error("Prevrandao not in the EVM's environment after merge")] PrevrandaoNotSet, #[error(transparent)] SignatureError(#[from] SignatureError), #[error(transparent)] + RecoveryError(#[from] RecoveryError), + #[error(transparent)] SignerError(#[from] SignerError), #[error("Rpc Endpoint not implemented")] RpcUnimplemented, @@ -68,6 +75,11 @@ pub enum BlockchainError { BlockOutOfRange(u64, u64), #[error("Resource not found")] BlockNotFound, + #[error("unknown block")] + UnknownBlock, + /// Thrown when a requested transaction is not found + #[error("transaction not found")] + TransactionNotFound, #[error("Required data unavailable")] DataUnavailable, #[error("Trie error: {0}")] @@ -80,22 +92,47 @@ pub enum BlockchainError { TimestampError(String), #[error(transparent)] DatabaseError(#[from] DatabaseError), - #[error("EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later.")] + #[error( + "EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later." + )] EIP1559TransactionUnsupportedAtHardfork, - #[error("Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later.")] + #[error( + "Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later." + )] EIP2930TransactionUnsupportedAtHardfork, - #[error("EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later.")] + #[error( + "EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later." + )] EIP4844TransactionUnsupportedAtHardfork, - #[error("EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later.")] + #[error( + "EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later." + )] EIP7702TransactionUnsupportedAtHardfork, - #[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")] + #[error( + "op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'." + )] DepositTransactionUnsupported, - #[error("UnknownTransactionType not supported ")] + #[error( + "tempo transaction received but is not supported.\n\nYou can use it by running anvil with '--tempo'." + )] + TempoTransactionUnsupported, + #[error("Unknown transaction type not supported")] UnknownTransactionType, #[error("Excess blob gas not set.")] ExcessBlobGasNotSet, #[error("{0}")] Message(String), + #[error("Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}")] + TransactionConfirmationTimeout { + /// Hash of the transaction that timed out + hash: B256, + /// Duration that was waited before timing out + duration: Duration, + }, + #[error("Invalid transaction request: {0}")] + InvalidTransactionRequest(String), + #[error("filter not found")] + FilterNotFound, } impl From for BlockchainError { @@ -123,24 +160,22 @@ where }, EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), } } } -impl From> for BlockchainError +impl From> for BlockchainError where T: Into, { - fn from(err: EVMError) -> Self { + fn from(err: EVMError) -> Self { match err { EVMError::Transaction(err) => match err { - OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), - OpTransactionError::DepositSystemTxPostRegolith => { - Self::DepositTransactionUnsupported - } - OpTransactionError::HaltedDepositPostRegolith => { - Self::DepositTransactionUnsupported + TempoInvalidTransaction::EthInvalidTransaction(err) => { + InvalidTransactionError::from(err).into() } + err => Self::Message(format!("tempo transaction error: {err}")), }, EVMError::Header(err) => match err { InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, @@ -148,26 +183,28 @@ where }, EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), } } } impl From for BlockchainError { fn from(value: WalletError) -> Self { + Self::Message(value.to_string()) + } +} + +impl From> for BlockchainError +where + E: Into, +{ + fn from(value: StateOverrideError) -> Self { match value { - WalletError::ValueNotZero => Self::Message("tx value not zero".to_string()), - WalletError::FromSet => Self::Message("tx from field is set".to_string()), - WalletError::NonceSet => Self::Message("tx nonce is set".to_string()), - WalletError::InvalidAuthorization => { - Self::Message("invalid authorization address".to_string()) - } - WalletError::IllegalDestination => Self::Message( - "the destination of the transaction is not a delegated account".to_string(), - ), - WalletError::InternalError => Self::Message("internal error".to_string()), - WalletError::InvalidTransactionRequest => { - Self::Message("invalid tx request".to_string()) - } + StateOverrideError::InvalidBytecode(err) => Self::StateOverrideError(err.to_string()), + StateOverrideError::BothStateAndStateDiff(addr) => Self::StateOverrideError(format!( + "state and state_diff can't be used together for account {addr}", + )), + StateOverrideError::Database(err) => err.into(), } } } @@ -179,9 +216,9 @@ pub enum PoolError { CyclicTransaction, /// Thrown if a replacement transaction's gas price is below the already imported transaction #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")] - ReplacementUnderpriced(Box), + ReplacementUnderpriced(TxHash), #[error("Tx: [{0:?}] already Imported")] - AlreadyImported(Box), + AlreadyImported(TxHash), } /// Errors that can occur with `eth_feeHistory` @@ -259,7 +296,7 @@ pub enum InvalidTransactionError { /// Thrown when the block's `blob_gas_price` is greater than tx-specified /// `max_fee_per_blob_gas` after Cancun. #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")] - BlobFeeCapTooLow, + BlobFeeCapTooLow(u128, u128), /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard /// fork. #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")] @@ -287,12 +324,32 @@ pub enum InvalidTransactionError { /// Thrown when an access list is used before the berlin hard fork. #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")] AuthorizationListNotSupported, + #[error("Transaction gas limit is greater than the block gas limit, gas_limit: {0}, cap: {1}")] + TxGasLimitGreaterThanCap(u64, u64), /// Forwards error from the revm #[error(transparent)] Revm(revm::context_interface::result::InvalidTransaction), /// Deposit transaction error post regolith #[error("op-deposit failure post regolith")] DepositTxErrorPostRegolith, + /// Missing enveloped transaction + #[error("missing enveloped transaction")] + MissingEnvelopedTx, + /// Native ETH value transfers are not allowed in Tempo mode + #[error("native value transfer not allowed in Tempo mode")] + TempoNativeValueTransfer, + /// Tempo transaction valid_before is expired or too close to current time + #[error("Tempo tx valid_before ({valid_before}) must be > current time + 3s ({min_allowed})")] + TempoValidBeforeExpired { valid_before: u64, min_allowed: u64 }, + /// Tempo transaction valid_after is too far in the future + #[error("Tempo tx valid_after ({valid_after}) must be <= current time + 1h ({max_allowed})")] + TempoValidAfterTooFar { valid_after: u64, max_allowed: u64 }, + /// Tempo transaction has too many authorizations + #[error("Tempo tx has too many authorizations ({count}), max allowed is {max}")] + TempoTooManyAuthorizations { count: usize, max: usize }, + /// Tempo transaction fee payer has insufficient fee token balance + #[error("insufficient fee token balance: have {balance}, need {required}")] + TempoInsufficientFeeTokenBalance { balance: U256, required: U256 }, } impl From for InvalidTransactionError { @@ -308,7 +365,7 @@ impl From for InvalidTransactionError { Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } InvalidTransaction::GasFloorMoreThanGasLimit { .. } => { - Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) + Self::GasTooHigh(ErrDetail { detail: String::from("GasFloorMoreThanGasLimit") }) } InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA, InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds, @@ -318,7 +375,10 @@ impl From for InvalidTransactionError { InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh, InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow, InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported, - InvalidTransaction::BlobGasPriceGreaterThanMax => Self::BlobFeeCapTooLow, + InvalidTransaction::BlobGasPriceGreaterThanMax { + block_blob_gas_price, + tx_max_fee_per_blob_gas, + } => Self::BlobFeeCapTooLow(block_blob_gas_price, tx_max_fee_per_blob_gas), InvalidTransaction::BlobVersionedHashesNotSupported => { Self::BlobVersionedHashesNotSupported } @@ -330,28 +390,24 @@ impl From for InvalidTransactionError { InvalidTransaction::AuthorizationListNotSupported => { Self::AuthorizationListNotSupported } - InvalidTransaction::AuthorizationListInvalidFields | - InvalidTransaction::Eip1559NotSupported | - InvalidTransaction::Eip2930NotSupported | - InvalidTransaction::Eip4844NotSupported | - InvalidTransaction::Eip7702NotSupported | - InvalidTransaction::EofCreateShouldHaveToAddress | - InvalidTransaction::EmptyAuthorizationList | - InvalidTransaction::Eip7873NotSupported | - InvalidTransaction::Eip7873MissingTarget => Self::Revm(err), - } - } -} + InvalidTransaction::TxGasLimitGreaterThanCap { gas_limit, cap } => { + Self::TxGasLimitGreaterThanCap(gas_limit, cap) + } -impl From for InvalidTransactionError { - fn from(value: OpTransactionError) -> Self { - match value { - OpTransactionError::Base(err) => err.into(), - OpTransactionError::DepositSystemTxPostRegolith | - OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + InvalidTransaction::AuthorizationListInvalidFields + | InvalidTransaction::Eip1559NotSupported + | InvalidTransaction::Eip2930NotSupported + | InvalidTransaction::Eip4844NotSupported + | InvalidTransaction::Eip7702NotSupported + | InvalidTransaction::EmptyAuthorizationList + | InvalidTransaction::Eip7873NotSupported + | InvalidTransaction::Eip7873MissingTarget + | InvalidTransaction::MissingChainId + | InvalidTransaction::Str(_) => Self::Revm(err), } } } + /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -393,6 +449,9 @@ impl ToRpcResponseResult for Result { BlockchainError::ChainIdNotAvailable => { RpcError::invalid_params("Chain Id not available") } + BlockchainError::TransactionConfirmationTimeout { .. } => { + RpcError::internal_error_with("Transaction confirmation timeout") + } BlockchainError::InvalidTransaction(err) => match err { InvalidTransactionError::Revert(data) => { // this mimics geth revert error @@ -404,7 +463,7 @@ impl ToRpcResponseResult for Result { msg = format!("{msg}: {reason}"); } RpcError { - // geth returns this error code on reverts, See + // geth returns this error code on reverts, See code: ErrorCode::ExecutionError, message: msg.into(), data: serde_json::to_value(data).ok(), @@ -465,9 +524,13 @@ impl ToRpcResponseResult for Result { err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), } } - err @ BlockchainError::EvmError(_) => { - RpcError::internal_error_with(err.to_string()) - } + err @ BlockchainError::EvmError(_) => RpcError { + // VM halts are execution failures, not JSON-RPC server faults. REVERT has a + // dedicated code/data path above; other halts, such as invalid opcode, do not. + code: ErrorCode::TransactionRejected, + message: err.to_string().into(), + data: None, + }, err @ BlockchainError::EvmOverrideError(_) => { RpcError::invalid_params(err.to_string()) } @@ -482,6 +545,11 @@ impl ToRpcResponseResult for Result { message: err.to_string().into(), data: None, }, + err @ BlockchainError::TransactionNotFound => RpcError { + code: ErrorCode::ServerError(-32001), + message: err.to_string().into(), + data: None, + }, err @ BlockchainError::DataUnavailable => { RpcError::internal_error_with(err.to_string()) } @@ -513,6 +581,9 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::DepositTransactionUnsupported => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::TempoTransactionUnsupported => { + RpcError::invalid_params(err.to_string()) + } err @ BlockchainError::ExcessBlobGasNotSet => { RpcError::invalid_params(err.to_string()) } @@ -520,6 +591,22 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::UnknownTransactionType => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::InvalidTransactionRequest(_) => { + RpcError::invalid_params(err.to_string()) + } + err @ BlockchainError::RecoveryError(_) => { + RpcError::invalid_params(err.to_string()) + } + BlockchainError::FilterNotFound => RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }, + err @ BlockchainError::UnknownBlock => RpcError { + code: ErrorCode::ServerError(-32000), + message: err.to_string().into(), + data: None, + }, } .into(), } diff --git a/crates/anvil/src/eth/error/optimism.rs b/crates/anvil/src/eth/error/optimism.rs new file mode 100644 index 0000000000000..1207fde30a72a --- /dev/null +++ b/crates/anvil/src/eth/error/optimism.rs @@ -0,0 +1,62 @@ +//! Optimism-specific error conversions for [`BlockchainError`] and +//! [`InvalidTransactionError`]. + +use super::{BlockchainError, InvalidTransactionError}; +use op_revm::OpTransactionError; +use revm::context_interface::result::{EVMError, InvalidHeader}; + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => { + let op_err: OpTransactionError = err.0; + EVMError::::Transaction(op_err).into() + } + EVMError::Header(err) => EVMError::::Header(err).into(), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + EVMError::CustomAny(err) => Self::Message(err.to_string()), + } + } +} + +impl From for InvalidTransactionError { + fn from(value: OpTransactionError) -> Self { + match value { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::DepositSystemTxPostRegolith + | OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, + } + } +} diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index acec3a29201ac..e5cfa9ef7a08a 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -1,19 +1,15 @@ use std::{ collections::BTreeMap, fmt, - future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, }; -use alloy_consensus::Header; -use alloy_eips::{ - calc_next_block_base_fee, eip1559::BaseFeeParams, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA, - eip7840::BlobParams, -}; +use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; +use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; +use alloy_network::Network; use alloy_primitives::B256; -use anvil_core::eth::transaction::TypedTransaction; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; use revm::{context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId}; @@ -38,15 +34,13 @@ pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; /// Minimum suggested priority fee pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128; -pub fn default_elasticity() -> f64 { - 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64 -} - /// Stores the fee related information #[derive(Clone, Debug)] pub struct FeeManager { /// Hardfork identifier spec_id: SpecId, + /// The blob params that determine blob fees + blob_params: Arc>, /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined @@ -62,6 +56,8 @@ pub struct FeeManager { /// This will be constant value unless changed manually gas_price: Arc>, elasticity: Arc>, + /// Network-specific base fee params for EIP-1559 calculations + base_fee_params: BaseFeeParams, } impl FeeManager { @@ -71,48 +67,50 @@ impl FeeManager { is_min_priority_fee_enforced: bool, gas_price: u128, blob_excess_gas_and_price: BlobExcessGasAndPrice, + blob_params: BlobParams, + base_fee_params: BaseFeeParams, ) -> Self { + let elasticity = 1f64 / base_fee_params.elasticity_multiplier as f64; Self { spec_id, + blob_params: Arc::new(RwLock::new(blob_params)), base_fee: Arc::new(RwLock::new(base_fee)), is_min_priority_fee_enforced, gas_price: Arc::new(RwLock::new(gas_price)), blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), - elasticity: Arc::new(RwLock::new(default_elasticity())), + elasticity: Arc::new(RwLock::new(elasticity)), + base_fee_params, } } + /// Returns the base fee params used for EIP-1559 calculations + pub const fn base_fee_params(&self) -> BaseFeeParams { + self.base_fee_params + } + pub fn elasticity(&self) -> f64 { *self.elasticity.read() } /// Returns true for post London - pub fn is_eip1559(&self) -> bool { + pub const fn is_eip1559(&self) -> bool { (self.spec_id as u8) >= (SpecId::LONDON as u8) } - pub fn is_eip4844(&self) -> bool { + pub const fn is_eip4844(&self) -> bool { (self.spec_id as u8) >= (SpecId::CANCUN as u8) } /// Calculates the current blob gas price pub fn blob_gas_price(&self) -> u128 { - if self.is_eip4844() { - self.base_fee_per_blob_gas() - } else { - 0 - } + if self.is_eip4844() { self.base_fee_per_blob_gas() } else { 0 } } pub fn base_fee(&self) -> u64 { - if self.is_eip1559() { - *self.base_fee.read() - } else { - 0 - } + if self.is_eip1559() { *self.base_fee.read() } else { 0 } } - pub fn is_min_priority_fee_enforced(&self) -> bool { + pub const fn is_min_priority_fee_enforced(&self) -> bool { self.is_min_priority_fee_enforced } @@ -122,19 +120,11 @@ impl FeeManager { } pub fn excess_blob_gas_and_price(&self) -> Option { - if self.is_eip4844() { - Some(*self.blob_excess_gas_and_price.read()) - } else { - None - } + self.is_eip4844().then(|| *self.blob_excess_gas_and_price.read()) } pub fn base_fee_per_blob_gas(&self) -> u128 { - if self.is_eip4844() { - self.blob_excess_gas_and_price.read().blob_gasprice - } else { - 0 - } + if self.is_eip4844() { self.blob_excess_gas_and_price.read().blob_gasprice } else { 0 } } /// Returns the current gas price @@ -168,30 +158,44 @@ impl FeeManager { // It means it was set by the user deliberately and therefore we treat it as a constant. // Therefore, we skip the base fee calculation altogether and we return 0. if self.base_fee() == 0 { - return 0 + return 0; } - calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas) + calc_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas, self.base_fee_params) } - /// Calculates the next block blob base fee, using the provided excess blob gas - pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 { - alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas as u64) + /// Calculates the next block blob base fee. + pub fn get_next_block_blob_base_fee_per_gas(&self) -> u128 { + self.blob_params().calc_blob_fee(self.blob_excess_gas_and_price.read().excess_blob_gas) } - /// Calculates the next block blob excess gas, using the provided parent blob gas used and - /// parent blob excess gas - pub fn get_next_block_blob_excess_gas(&self, blob_gas_used: u64, blob_excess_gas: u64) -> u64 { - alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used, blob_excess_gas) + /// Calculates the next block blob excess gas, using the provided parent blob excess gas and + /// parent blob gas used + pub fn get_next_block_blob_excess_gas(&self, blob_excess_gas: u64, blob_gas_used: u64) -> u64 { + self.blob_params().next_block_excess_blob_gas_osaka( + blob_excess_gas, + blob_gas_used, + self.base_fee(), + ) } -} -/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { - calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) + /// Configures the blob params + pub fn set_blob_params(&self, blob_params: BlobParams) { + *self.blob_params.write() = blob_params; + } + + /// Returns the active [`BlobParams`] + pub fn blob_params(&self) -> BlobParams { + *self.blob_params.read() + } } /// An async service that takes care of the `FeeHistory` cache -pub struct FeeHistoryService { +pub struct FeeHistoryService +where + N::ReceiptEnvelope: TxReceipt, +{ + /// blob parameters for the current spec + blob_params: BlobParams, /// incoming notifications about new blocks new_blocks: NewBlockNotifications, /// contains all fee history related entries @@ -199,25 +203,35 @@ pub struct FeeHistoryService { /// number of items to consider fee_history_limit: u64, /// a type that can fetch ethereum-storage data - storage_info: StorageInfo, + storage_info: StorageInfo, } -impl FeeHistoryService { - pub fn new( +impl FeeHistoryService +where + N::ReceiptEnvelope: TxReceipt, +{ + pub const fn new( + blob_params: BlobParams, new_blocks: NewBlockNotifications, cache: FeeHistoryCache, - storage_info: StorageInfo, + storage_info: StorageInfo, ) -> Self { - Self { new_blocks, cache, fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, storage_info } + Self { + blob_params, + new_blocks, + cache, + fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, + storage_info, + } } /// Returns the configured history limit - pub fn fee_history_limit(&self) -> u64 { + pub const fn fee_history_limit(&self) -> u64 { self.fee_history_limit } /// Inserts a new cache entry for the given block - pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) { + pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &impl BlockHeader) { let (result, block_number) = self.create_cache_entry(hash, header); self.insert_cache_entry(result, block_number); } @@ -226,7 +240,7 @@ impl FeeHistoryService { fn create_cache_entry( &self, hash: B256, - header: &Header, + header: &impl BlockHeader, ) -> (FeeHistoryCacheItem, Option) { // percentile list from 0.0 to 100.0 with a 0.5 resolution. // this will create 200 percentile points @@ -242,12 +256,13 @@ impl FeeHistoryService { }; let mut block_number: Option = None; - let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default(); - let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128); - let blob_gas_used = header.blob_gas_used.map(|g| g as u128); - let base_fee_per_blob_gas = header.blob_fee(BlobParams::cancun()); + let base_fee = header.base_fee_per_gas().unwrap_or_default(); + let excess_blob_gas = header.excess_blob_gas().map(|g| g as u128); + let blob_gas_used = header.blob_gas_used().map(|g| g as u128); + let base_fee_per_blob_gas = header.blob_fee(self.blob_params); + let mut item = FeeHistoryCacheItem { - base_fee, + base_fee: base_fee as u128, gas_used_ratio: 0f64, blob_gas_used_ratio: 0f64, rewards: Vec::new(), @@ -260,52 +275,40 @@ impl FeeHistoryService { let current_receipts = self.storage_info.receipts(hash); if let (Some(block), Some(receipts)) = (current_block, current_receipts) { - block_number = Some(block.header.number); - - let gas_used = block.header.gas_used as f64; - let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); - item.gas_used_ratio = gas_used / block.header.gas_limit as f64; - item.blob_gas_used_ratio = - blob_gas_used.map(|g| g / MAX_BLOBS_PER_BLOCK_ELECTRA as f64).unwrap_or(0 as f64); + block_number = Some(block.header.number()); + + let gas_used = block.header.gas_used() as f64; + let blob_gas_used = block.header.blob_gas_used().map(|g| g as f64); + item.gas_used_ratio = gas_used / block.header.gas_limit() as f64; + item.blob_gas_used_ratio = blob_gas_used + .map(|g| { + let max = self.blob_params.max_blob_gas_per_block() as f64; + if max == 0.0 { 0.0 } else { g / max } + }) + .unwrap_or(0.0); // extract useful tx info (gas_used, effective_reward) let mut transactions: Vec<(_, _)> = receipts .iter() .enumerate() .map(|(i, receipt)| { - let gas_used = receipt.cumulative_gas_used(); - let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction) - { - Some(TypedTransaction::Legacy(t)) => { - t.tx().gas_price.saturating_sub(base_fee) - } - Some(TypedTransaction::EIP2930(t)) => { - t.tx().gas_price.saturating_sub(base_fee) - } - Some(TypedTransaction::EIP1559(t)) => t - .tx() - .max_priority_fee_per_gas - .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), - // TODO: This probably needs to be extended to extract 4844 info. - Some(TypedTransaction::EIP4844(t)) => t - .tx() - .tx() - .max_priority_fee_per_gas - .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)), - Some(TypedTransaction::EIP7702(t)) => t - .tx() - .max_priority_fee_per_gas - .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), - Some(TypedTransaction::Deposit(_)) => 0, - None => 0, - }; + let cumulative = receipt.cumulative_gas_used(); + let prev_cumulative = + if i > 0 { receipts[i - 1].cumulative_gas_used() } else { 0 }; + let gas_used = cumulative - prev_cumulative; + let effective_reward = block + .body + .transactions + .get(i) + .map(|tx| tx.as_ref().effective_tip_per_gas(base_fee).unwrap_or(0)) + .unwrap_or(0); (gas_used, effective_reward) }) .collect(); // sort by effective reward asc - transactions.sort_by(|(_, a), (_, b)| a.cmp(b)); + transactions.sort_by_key(|(_, reward)| *reward); // calculate percentile rewards item.rewards = reward_percentiles @@ -313,17 +316,17 @@ impl FeeHistoryService { .filter_map(|p| { let target_gas = (p * gas_used / 100f64) as u64; let mut sum_gas = 0; - for (gas_used, effective_reward) in transactions.iter().cloned() { + for (gas_used, effective_reward) in transactions.iter().copied() { sum_gas += gas_used; if target_gas <= sum_gas { - return Some(effective_reward) + return Some(effective_reward); } } None }) .collect(); } else { - item.rewards = reward_percentiles.iter().map(|_| 0).collect(); + item.rewards = vec![0; reward_percentiles.len()]; } (item, block_number) } @@ -347,7 +350,10 @@ impl FeeHistoryService { } // An endless future that listens for new blocks and updates the cache -impl Future for FeeHistoryService { +impl Future for FeeHistoryService +where + N::ReceiptEnvelope: TxReceipt, +{ type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -386,7 +392,7 @@ pub struct FeeDetails { impl FeeDetails { /// All values zero - pub fn zero() -> Self { + pub const fn zero() -> Self { Self { gas_price: Some(0), max_fee_per_gas: Some(0), @@ -396,7 +402,7 @@ impl FeeDetails { } /// If neither `gas_price` nor `max_fee_per_gas` is `Some`, this will set both to `0` - pub fn or_zero_fees(self) -> Self { + pub const fn or_zero_fees(self) -> Self { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; @@ -409,7 +415,7 @@ impl FeeDetails { } /// Turns this type into a tuple - pub fn split(self) -> (Option, Option, Option, Option) { + pub const fn split(self) -> (Option, Option, Option, Option) { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) @@ -432,29 +438,13 @@ impl FeeDetails { max_fee_per_blob_gas: None, }) } - (_, max_fee, max_priority, None) => { - // eip-1559 - // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. - if let Some(max_priority) = max_priority { - let max_fee = max_fee.unwrap_or_default(); - if max_priority > max_fee { - return Err(BlockchainError::InvalidFeeInput) - } - } - Ok(Self { - gas_price: max_fee, - max_fee_per_gas: max_fee, - max_priority_fee_per_gas: max_priority, - max_fee_per_blob_gas: None, - }) - } (_, max_fee, max_priority, max_fee_per_blob_gas) => { // eip-1559 // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. if let Some(max_priority) = max_priority { let max_fee = max_fee.unwrap_or_default(); if max_priority > max_fee { - return Err(BlockchainError::InvalidFeeInput) + return Err(BlockchainError::InvalidFeeInput); } } Ok(Self { diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index fec6ff966738b..53a10b6b66707 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -1,23 +1,22 @@ //! Mines transactions -use crate::eth::pool::{transactions::PoolTransaction, Pool}; +use crate::eth::pool::{Pool, transactions::PoolTransaction}; use alloy_primitives::TxHash; use futures::{ channel::mpsc::Receiver, stream::{Fuse, StreamExt}, task::AtomicWaker, }; -use parking_lot::{lock_api::RwLockWriteGuard, RawRwLock, RwLock}; +use parking_lot::{RawRwLock, RwLock, lock_api::RwLockWriteGuard}; use std::{ fmt, sync::Arc, - task::{ready, Context, Poll}, + task::{Context, Poll}, time::Duration, }; use tokio::time::{Interval, MissedTickBehavior}; -#[derive(Clone, Debug)] -pub struct Miner { +pub struct Miner { /// The mode this miner currently operates in mode: Arc>, /// used for task wake up when the mining mode was forcefully changed @@ -26,10 +25,29 @@ pub struct Miner { inner: Arc, /// Transactions included into the pool before any others are. /// Done once on startup. - force_transactions: Option>>, + force_transactions: Option>>>, } -impl Miner { +impl Clone for Miner { + fn clone(&self) -> Self { + Self { + mode: self.mode.clone(), + inner: self.inner.clone(), + force_transactions: self.force_transactions.clone(), + } + } +} + +impl fmt::Debug for Miner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Miner") + .field("mode", &self.mode) + .field("force_transactions", &self.force_transactions.as_ref().map(|txs| txs.len())) + .finish_non_exhaustive() + } +} + +impl Miner { /// Returns a new miner with that operates in the given `mode`. pub fn new(mode: MiningMode) -> Self { Self { @@ -45,7 +63,7 @@ impl Miner { /// there are not other transactions in the pool. pub fn with_forced_transactions( mut self, - force_transactions: Option>, + force_transactions: Option>>, ) -> Self { self.force_transactions = force_transactions.map(|tx| tx.into_iter().map(Arc::new).collect()); @@ -66,7 +84,7 @@ impl Miner { pub fn get_interval(&self) -> Option { let mode = self.mode.read(); if let MiningMode::FixedBlockTime(ref mm) = *mode { - return Some(mm.interval.period().as_secs()) + return Some(mm.interval.period().as_secs()); } None } @@ -85,17 +103,17 @@ impl Miner { /// May return an empty list, if no transactions are ready. pub fn poll( &mut self, - pool: &Arc, + pool: &Arc>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>>> { self.inner.register(cx); - let next = ready!(self.mode.write().poll(pool, cx)); if let Some(mut transactions) = self.force_transactions.take() { - transactions.extend(next); - Poll::Ready(transactions) - } else { - Poll::Ready(next) + if let Poll::Ready(next) = self.mode.write().poll(pool, cx) { + transactions.extend(next); + } + return Poll::Ready(transactions); } + self.mode.write().poll(pool, cx) } } @@ -135,7 +153,7 @@ pub enum MiningMode { /// A miner that constructs a new block every `interval` tick FixedBlockTime(FixedBlockTimeMiner), - /// A minner that uses both Auto and FixedBlockTime + /// A miner that uses both Auto and FixedBlockTime Mixed(ReadyTransactionMiner, FixedBlockTimeMiner), } @@ -160,11 +178,11 @@ impl MiningMode { } /// polls the [Pool] and returns those transactions that should be put in a block, if any. - pub fn poll( + pub fn poll( &mut self, - pool: &Arc, + pool: &Arc>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>>> { match self { Self::None => Poll::Pending, Self::Auto(miner) => miner.poll(pool, cx), @@ -217,10 +235,14 @@ impl FixedBlockTimeMiner { Self { interval } } - fn poll(&mut self, pool: &Arc, cx: &mut Context<'_>) -> Poll>> { + fn poll( + &mut self, + pool: &Arc>, + cx: &mut Context<'_>, + ) -> Poll>>> { if self.interval.poll_tick(cx).is_ready() { // drain the pool - return Poll::Ready(pool.ready_transactions().collect()) + return Poll::Ready(pool.ready_transactions().collect()); } Poll::Pending } @@ -243,14 +265,18 @@ pub struct ReadyTransactionMiner { } impl ReadyTransactionMiner { - fn poll(&mut self, pool: &Arc, cx: &mut Context<'_>) -> Poll>> { + fn poll( + &mut self, + pool: &Arc>, + cx: &mut Context<'_>, + ) -> Poll>>> { // always drain the notification stream so that we're woken up as soon as there's a new tx while let Poll::Ready(Some(_hash)) = self.rx.poll_next_unpin(cx) { self.has_pending_txs = Some(true); } if self.has_pending_txs == Some(false) { - return Poll::Pending + return Poll::Pending; } let transactions = @@ -260,7 +286,7 @@ impl ReadyTransactionMiner { self.has_pending_txs = Some(transactions.len() >= self.max_transactions); if transactions.is_empty() { - return Poll::Pending + return Poll::Pending; } Poll::Ready(transactions) @@ -274,3 +300,44 @@ impl fmt::Debug for ReadyTransactionMiner { .finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Address, hex}; + use alloy_rlp::Decodable; + use anvil_core::eth::transaction::PendingTransaction; + use foundry_primitives::FoundryTxEnvelope; + use futures::task::noop_waker; + + fn forced_tx() -> PoolTransaction { + let raw = hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap(); + let tx = FoundryTxEnvelope::decode(&mut &raw[..]).unwrap(); + let sender: Address = "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse().unwrap(); + let pending = PendingTransaction::with_impersonated(tx, sender); + PoolTransaction::new(pending) + } + + #[test] + fn poll_consumes_forced_transactions_before_mode_is_ready() { + let forced = forced_tx(); + let forced_hash = forced.hash(); + + let pool = Arc::new(Pool::default()); + let mut miner = Miner::new(MiningMode::None).with_forced_transactions(Some(vec![forced])); + + let waker = noop_waker(); + let mut cx = Context::from_waker(&waker); + + let polled = miner.poll(&pool, &mut cx); + let txs = match polled { + Poll::Ready(txs) => txs, + Poll::Pending => panic!("expected forced transactions to be returned immediately"), + }; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].hash(), forced_hash); + + // Forced transactions are consumed exactly once. + assert!(miner.poll(&pool, &mut cx).is_pending()); + } +} diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 129329991c03b..2ccf65ba721f5 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -1,89 +1,29 @@ use crate::eth::{ + EthApi, error::{BlockchainError, Result}, macros::node_info, - EthApi, }; -use alloy_consensus::Transaction as TransactionTrait; +use alloy_consensus::{BlockHeader, Transaction as TransactionTrait}; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse, - TransactionResponse, + ReceiptResponse, TransactionResponse, }; -use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_rpc_types::{ + Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, trace::{ otterscan::{ BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions, OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, }, - parity::{ - Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, - RewardAction, TraceOutput, - }, + parity::{Action, CreateAction, CreateOutput, TraceOutput}, }, - Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, }; -use itertools::Itertools; - +use foundry_primitives::FoundryNetwork; use futures::future::join_all; +use itertools::Itertools; -pub fn mentions_address(trace: LocalizedTransactionTrace, address: Address) -> Option { - match (trace.trace.action, trace.trace.result) { - (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => { - trace.transaction_hash - } - (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. }))) - if created_address == address => - { - trace.transaction_hash - } - (Action::Create(CreateAction { from, .. }), _) if from == address => trace.transaction_hash, - (Action::Reward(RewardAction { author, .. }), _) if author == address => { - trace.transaction_hash - } - _ => None, - } -} - -/// Converts the list of traces for a transaction into the expected Otterscan format. -/// -/// Follows format specified in the [`ots_traceTransaction`](https://docs.otterscan.io/api-docs/ots-api#ots_tracetransaction) spec. -pub fn batch_build_ots_traces(traces: Vec) -> Vec { - traces - .into_iter() - .filter_map(|trace| { - let output = trace - .trace - .result - .map(|r| match r { - TraceOutput::Call(output) => output.output, - TraceOutput::Create(output) => output.code, - }) - .unwrap_or_default(); - match trace.trace.action { - Action::Call(call) => Some(TraceEntry { - r#type: match call.call_type { - CallType::Call => "CALL", - CallType::CallCode => "CALLCODE", - CallType::DelegateCall => "DELEGATECALL", - CallType::StaticCall => "STATICCALL", - CallType::AuthCall => "AUTHCALL", - CallType::None => "NONE", - } - .to_string(), - depth: trace.trace.trace_address.len() as u32, - from: call.from, - to: call.to, - value: Some(call.value), - input: call.input, - output, - }), - Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None, - } - }) - .collect() -} - -impl EthApi { +impl EthApi { /// Otterscan currently requires this endpoint, even though it's not part of the `ots_*`. /// Ref: /// @@ -93,7 +33,7 @@ impl EthApi { &self, number: BlockNumber, ) -> Result> { - node_info!("ots_getApiLevel"); + node_info!("erigon_getHeaderByNumber"); self.backend.block_by_number(number).await } @@ -126,20 +66,29 @@ impl EthApi { } /// Trace a transaction and generate a trace call tree. + /// Converts the list of traces for a transaction into the expected Otterscan format. + /// + /// Follows format specified in the [`ots_traceTransaction`](https://docs.otterscan.io/api-docs/ots-api#ots_tracetransaction) spec. pub async fn ots_trace_transaction(&self, hash: B256) -> Result> { node_info!("ots_traceTransaction"); - - Ok(batch_build_ots_traces(self.backend.trace_transaction(hash).await?)) + let traces = self + .backend + .trace_transaction(hash) + .await? + .into_iter() + .filter_map(|trace| TraceEntry::from_transaction_trace(&trace.trace)) + .collect(); + Ok(traces) } /// Given a transaction hash, returns its raw revert reason. pub async fn ots_get_transaction_error(&self, hash: B256) -> Result { node_info!("ots_getTransactionError"); - if let Some(receipt) = self.backend.mined_transaction_receipt(hash) { - if !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() { - return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())); - } + if let Some(receipt) = self.backend.mined_transaction_receipt(hash) + && !receipt.inner.as_ref().status() + { + return Ok(receipt.out.unwrap_or_default()); } Ok(Bytes::default()) @@ -206,9 +155,12 @@ impl EthApi { let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block - // considering only post-fork + // considering only post-fork (or post-genesis in non-fork mode) let from = if block_number == 0 { best } else { block_number - 1 }; - let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let to = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let first_page = from >= best; let mut last_page = false; @@ -220,7 +172,8 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| mentions_address(trace, address)) + .filter(|trace| trace.contains_address(address)) + .filter_map(|trace| trace.transaction_hash) .unique(); if res.len() >= page_size { @@ -248,8 +201,11 @@ impl EthApi { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); - // we go from the first post-fork block, up to the tip - let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + // we go from the first post-fork (or post-genesis) block, up to the tip + let first_block = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; @@ -267,7 +223,8 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| mentions_address(trace, address)) + .filter(|trace| trace.contains_address(address)) + .filter_map(|trace| trace.transaction_hash) .unique(); if res.len() >= page_size { @@ -297,7 +254,10 @@ impl EthApi { ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); - let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); + let from = self + .get_fork() + .map(|f| f.block_number() + 1) + .unwrap_or_else(|| self.backend.genesis_number() + 1); let to = self.backend.best_number(); for n in (from..=to).rev() { @@ -382,9 +342,9 @@ impl EthApi { }) .collect::>>()?; - let total_fees = receipts - .iter() - .fold(0, |acc, receipt| acc + (receipt.gas_used as u128) * receipt.effective_gas_price); + let total_fees = receipts.iter().fold(0, |acc, receipt| { + acc + (receipt.gas_used() as u128) * receipt.effective_gas_price() + }); let Block { header, uncles, transactions, withdrawals } = block.into_inner(); @@ -418,18 +378,19 @@ impl EthApi { txs.iter().skip(page * page_size).take(page_size).cloned().collect(), ), BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( - txs.iter().skip(page * page_size).take(page_size).cloned().collect(), + txs.iter().skip(page * page_size).take(page_size).copied().collect(), ), BlockTransactions::Uncle => unreachable!(), }; let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash)); - let receipts = join_all(receipt_futs.map(|r| async { + // Reuse timestamp from the block we already have + let timestamp = block.header.timestamp(); + + let receipts = join_all(receipt_futs.map(|r| async move { if let Ok(Some(r)) = r.await { - let block = self.block_by_number(r.block_number.unwrap().into()).await?; - let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; - let receipt = r.map_inner(OtsReceipt::from); + let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { Err(BlockchainError::BlockNotFound) @@ -468,9 +429,15 @@ impl EthApi { let receipts = join_all(receipt_futs.map(|r| async { if let Ok(Some(r)) = r.await { - let block = self.block_by_number(r.block_number.unwrap().into()).await?; - let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; - let receipt = r.map_inner(OtsReceipt::from); + // Try to get timestamp from receipt's other fields first (set by mined receipts), + // fallback to block lookup for fork receipts that may not have it + let timestamp = if let Some(ts) = r.block_timestamp() { + ts + } else { + let block = self.block_by_number(r.block_number().unwrap().into()).await?; + block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp() + }; + let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { Err(BlockchainError::BlockNotFound) diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 2133ea083df24..d6a99f50cd08f 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -36,92 +36,51 @@ use crate::{ }, mem::storage::MinedBlockOutcome, }; +use alloy_consensus::Transaction; use alloy_primitives::{Address, TxHash}; use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; -use futures::channel::mpsc::{channel, Receiver, Sender}; +use futures::channel::mpsc::{Receiver, Sender, channel}; use parking_lot::{Mutex, RwLock}; use std::{collections::VecDeque, fmt, sync::Arc}; pub mod transactions; /// Transaction pool that performs validation. -#[derive(Default)] -pub struct Pool { +pub struct Pool { /// processes all pending transactions - inner: RwLock, + inner: RwLock>, /// listeners for new ready transactions transaction_listener: Mutex>>, } +impl Default for Pool { + fn default() -> Self { + Self { inner: RwLock::new(PoolInner::default()), transaction_listener: Default::default() } + } +} + // == impl Pool == -impl Pool { +impl Pool { /// Returns an iterator that yields all transactions that are currently ready - pub fn ready_transactions(&self) -> TransactionsIterator { + pub fn ready_transactions(&self) -> TransactionsIterator { self.inner.read().ready_transactions() } /// Returns all transactions that are not ready to be included in a block yet - pub fn pending_transactions(&self) -> Vec> { + pub fn pending_transactions(&self) -> Vec>> { self.inner.read().pending_transactions.transactions().collect() } - /// Returns the _pending_ transaction for that `hash` if it exists in the mempool - pub fn get_transaction(&self, hash: TxHash) -> Option { - self.inner.read().get_transaction(hash) - } - /// Returns the number of tx that are ready and queued for further execution pub fn txpool_status(&self) -> TxpoolStatus { // Note: naming differs here compared to geth's `TxpoolStatus` - let pending: u64 = self.ready_transactions().count().try_into().unwrap_or(0); + let pending: u64 = self.inner.read().ready_transactions.len().try_into().unwrap_or(0); let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0); TxpoolStatus { pending, queued } } - /// Invoked when a set of transactions ([Self::ready_transactions()]) was executed. - /// - /// This will remove the transactions from the pool. - pub fn on_mined_block(&self, outcome: MinedBlockOutcome) -> PruneResult { - let MinedBlockOutcome { block_number, included, invalid } = outcome; - - // remove invalid transactions from the pool - self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect()); - - // prune all the markers the mined transactions provide - let res = self - .prune_markers(block_number, included.into_iter().flat_map(|tx| tx.provides.clone())); - trace!(target: "txpool", "pruned transaction markers {:?}", res); - res - } - - /// Removes ready transactions for the given iterator of identifying markers. - /// - /// For each marker we can remove transactions in the pool that either provide the marker - /// directly or are a dependency of the transaction associated with that marker. - pub fn prune_markers( - &self, - block_number: u64, - markers: impl IntoIterator, - ) -> PruneResult { - debug!(target: "txpool", ?block_number, "pruning transactions"); - self.inner.write().prune_markers(markers) - } - - /// Adds a new transaction to the pool - pub fn add_transaction(&self, tx: PoolTransaction) -> Result { - let added = self.inner.write().add_transaction(tx)?; - if let AddedTransaction::Ready(ref ready) = added { - self.notify_listener(ready.hash); - // also notify promoted transactions - for promoted in ready.promoted.iter().copied() { - self.notify_listener(promoted); - } - } - Ok(added) - } - /// Adds a new transaction listener to the pool that gets notified about every new ready /// transaction pub fn add_ready_listener(&self) -> Receiver { @@ -136,13 +95,19 @@ impl Pool { self.inner.read().contains(tx_hash) } + /// Removes all transactions from the pool + pub fn clear(&self) { + let mut pool = self.inner.write(); + pool.clear(); + } + /// Remove the given transactions from the pool - pub fn remove_invalid(&self, tx_hashes: Vec) -> Vec> { + pub fn remove_invalid(&self, tx_hashes: Vec) -> Vec>> { self.inner.write().remove_invalid(tx_hashes) } /// Remove transactions by sender - pub fn remove_transactions_by_address(&self, sender: Address) -> Vec> { + pub fn remove_transactions_by_address(&self, sender: Address) -> Vec>> { self.inner.write().remove_transactions_by_address(sender) } @@ -151,25 +116,29 @@ impl Pool { /// This is similar to `[Pool::remove_invalid()]` but for a single transaction. /// /// **Note**: this will also drop any transaction that depend on the `tx` - pub fn drop_transaction(&self, tx: TxHash) -> Option> { + pub fn drop_transaction(&self, tx: TxHash) -> Option>> { trace!(target: "txpool", "Dropping transaction: [{:?}]", tx); let removed = { let mut pool = self.inner.write(); pool.ready_transactions.remove_with_markers(vec![tx], None) }; - trace!(target: "txpool", "Dropped transactions: {:?}", removed); + trace!(target: "txpool", "Dropped transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); - let mut dropped = None; - if !removed.is_empty() { - dropped = removed.into_iter().find(|t| *t.pending_transaction.hash() == tx); + if removed.is_empty() { + None + } else { + removed.into_iter().find(|t| *t.pending_transaction.hash() == tx) } - dropped } - /// Removes all transactions from the pool - pub fn clear(&self) { - let mut pool = self.inner.write(); - pool.clear(); + /// Notifies listeners if the transaction was added to the ready queue. + fn notify_ready(&self, tx: &AddedTransaction) { + if let AddedTransaction::Ready(ready) = tx { + self.notify_listener(ready.hash); + for promoted in ready.promoted.iter().copied() { + self.notify_listener(promoted); + } + } } /// notifies all listeners about the transaction @@ -200,20 +169,93 @@ impl Pool { } } +impl Pool { + /// Returns the _pending_ transaction for that `hash` if it exists in the mempool + pub fn get_transaction(&self, hash: TxHash) -> Option> { + self.inner.read().get_transaction(hash) + } +} + +impl Pool { + /// Invoked when a set of transactions ([Self::ready_transactions()]) was executed. + /// + /// This will remove the transactions from the pool. + pub fn on_mined_block(self: &Arc, outcome: MinedBlockOutcome) -> PruneResult { + let MinedBlockOutcome { block_number, included, invalid, not_yet_valid } = outcome; + + // remove invalid transactions from the pool + self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect()); + + // prune all the markers the mined transactions provide + let res = self + .prune_markers(block_number, included.into_iter().flat_map(|tx| tx.provides.clone())); + trace!(target: "txpool", "pruned transaction markers {:?}", res); + + // Re-notify the miner about not-yet-valid transactions so they'll be retried. + // Delay by 1 second to let time advance before the next mining attempt. + if !not_yet_valid.is_empty() { + let tx_hashes: Vec<_> = not_yet_valid.iter().map(|tx| tx.hash()).collect(); + let pool = Arc::clone(self); + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + for hash in tx_hashes { + trace!(target: "txpool", "re-notifying for not-yet-valid tx: {:?}", hash); + pool.notify_listener(hash); + } + }); + } + + res + } + + /// Removes ready transactions for the given iterator of identifying markers. + /// + /// For each marker we can remove transactions in the pool that either provide the marker + /// directly or are a dependency of the transaction associated with that marker. + pub fn prune_markers( + &self, + block_number: u64, + markers: impl IntoIterator, + ) -> PruneResult { + debug!(target: "txpool", ?block_number, "pruning transactions"); + let res = self.inner.write().prune_markers(markers); + for tx in &res.promoted { + self.notify_ready(tx); + } + res + } + + /// Adds a new transaction to the pool + pub fn add_transaction( + &self, + tx: PoolTransaction, + ) -> Result, PoolError> { + let added = self.inner.write().add_transaction(tx)?; + self.notify_ready(&added); + Ok(added) + } +} + /// A Transaction Pool /// /// Contains all transactions that are ready to be executed -#[derive(Debug, Default)] -struct PoolInner { - ready_transactions: ReadyTransactions, - pending_transactions: PendingTransactions, +#[derive(Debug)] +struct PoolInner { + ready_transactions: ReadyTransactions, + pending_transactions: PendingTransactions, +} + +impl Default for PoolInner { + fn default() -> Self { + Self { ready_transactions: Default::default(), pending_transactions: Default::default() } + } } // == impl PoolInner == -impl PoolInner { +impl PoolInner { /// Returns an iterator over transactions that are ready. - fn ready_transactions(&self) -> TransactionsIterator { + fn ready_transactions(&self) -> TransactionsIterator { self.ready_transactions.get_transactions() } @@ -223,23 +265,11 @@ impl PoolInner { self.pending_transactions.clear(); } - /// checks both pools for the matching transaction - /// - /// Returns `None` if the transaction does not exist in the pool - fn get_transaction(&self, hash: TxHash) -> Option { - if let Some(pending) = self.pending_transactions.get(&hash) { - return Some(pending.transaction.pending_transaction.clone()) - } - Some( - self.ready_transactions.get(&hash)?.transaction.transaction.pending_transaction.clone(), - ) - } - /// Returns an iterator over all transactions in the pool filtered by the sender pub fn transactions_by_sender( &self, sender: Address, - ) -> impl Iterator> + '_ { + ) -> impl Iterator>> + '_ { let pending_txs = self .pending_transactions .transactions() @@ -258,20 +288,74 @@ impl PoolInner { self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash) } - fn add_transaction(&mut self, tx: PoolTransaction) -> Result { + /// Remove the given transactions from the pool + fn remove_invalid(&mut self, tx_hashes: Vec) -> Vec>> { + // early exit in case there is no invalid transactions. + if tx_hashes.is_empty() { + return vec![]; + } + trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes); + + let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); + removed.extend(self.pending_transactions.remove(tx_hashes)); + + trace!(target: "txpool", "Removed invalid transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); + + removed + } + + /// Remove transactions by sender address + fn remove_transactions_by_address(&mut self, sender: Address) -> Vec>> { + let tx_hashes = + self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::>(); + + if tx_hashes.is_empty() { + return vec![]; + } + + trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes); + + let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); + removed.extend(self.pending_transactions.remove(tx_hashes)); + + trace!(target: "txpool", "Removed transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); + + removed + } +} + +impl PoolInner { + /// checks both pools for the matching transaction + /// + /// Returns `None` if the transaction does not exist in the pool + fn get_transaction(&self, hash: TxHash) -> Option> { + if let Some(pending) = self.pending_transactions.get(&hash) { + return Some(pending.transaction.pending_transaction.clone()); + } + Some( + self.ready_transactions.get(&hash)?.transaction.transaction.pending_transaction.clone(), + ) + } +} + +impl PoolInner { + fn add_transaction( + &mut self, + tx: PoolTransaction, + ) -> Result, PoolError> { if self.contains(&tx.hash()) { - warn!(target: "txpool", "[{:?}] Already imported", tx.hash()); - return Err(PoolError::AlreadyImported(Box::new(tx))) + debug!(target: "txpool", "[{:?}] Already imported", tx.hash()); + return Err(PoolError::AlreadyImported(tx.hash())); } let tx = PendingPoolTransaction::new(tx, self.ready_transactions.provided_markers()); - trace!(target: "txpool", "[{:?}] {:?}", tx.transaction.hash(), tx); + trace!(target: "txpool", "[{:?}] ready={}", tx.transaction.hash(), tx.is_ready()); // If all markers are not satisfied import to future if !tx.is_ready() { let hash = tx.transaction.hash(); self.pending_transactions.add_transaction(tx)?; - return Ok(AddedTransaction::Pending { hash }) + return Ok(AddedTransaction::Pending { hash }); } self.add_ready_transaction(tx) } @@ -279,8 +363,8 @@ impl PoolInner { /// Adds the transaction to the ready queue fn add_ready_transaction( &mut self, - tx: PendingPoolTransaction, - ) -> Result { + tx: PendingPoolTransaction, + ) -> Result, PoolError> { let hash = tx.transaction.hash(); trace!(target: "txpool", "adding ready transaction [{:?}]", hash); let mut ready = ReadyTransaction::new(hash); @@ -311,10 +395,9 @@ impl PoolInner { if is_new_tx { debug!(target: "txpool", "[{:?}] Failed to add tx: {:?}", current_hash, err); - return Err(err) - } else { - ready.discarded.push(current_hash); + return Err(err); } + ready.discarded.push(current_hash); } } is_new_tx = false; @@ -325,7 +408,7 @@ impl PoolInner { // the pending queue if ready.removed.iter().any(|tx| *tx.hash() == hash) { self.ready_transactions.clear_transactions(&ready.promoted); - return Err(PoolError::CyclicTransaction) + return Err(PoolError::CyclicTransaction); } Ok(AddedTransaction::Ready(ready)) @@ -335,7 +418,7 @@ impl PoolInner { /// /// This will effectively remove those transactions that satisfy the markers and transactions /// from the pending queue might get promoted to if the markers unlock them. - pub fn prune_markers(&mut self, markers: impl IntoIterator) -> PruneResult { + pub fn prune_markers(&mut self, markers: impl IntoIterator) -> PruneResult { let mut imports = vec![]; let mut pruned = vec![]; @@ -361,54 +444,19 @@ impl PoolInner { PruneResult { pruned, failed, promoted } } - - /// Remove the given transactions from the pool - pub fn remove_invalid(&mut self, tx_hashes: Vec) -> Vec> { - // early exit in case there is no invalid transactions. - if tx_hashes.is_empty() { - return vec![] - } - trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes); - - let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); - removed.extend(self.pending_transactions.remove(tx_hashes)); - - trace!(target: "txpool", "Removed invalid transactions: {:?}", removed); - - removed - } - - /// Remove transactions by sender address - pub fn remove_transactions_by_address(&mut self, sender: Address) -> Vec> { - let tx_hashes = - self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::>(); - - if tx_hashes.is_empty() { - return vec![] - } - - trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes); - - let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); - removed.extend(self.pending_transactions.remove(tx_hashes)); - - trace!(target: "txpool", "Removed transactions: {:?}", removed); - - removed - } } /// Represents the outcome of a prune -pub struct PruneResult { +pub struct PruneResult { /// a list of added transactions that a pruned marker satisfied - pub promoted: Vec, + pub promoted: Vec>, /// all transactions that failed to be promoted and now are discarded pub failed: Vec, /// all transactions that were pruned from the ready pool - pub pruned: Vec>, + pub pruned: Vec>>, } -impl fmt::Debug for PruneResult { +impl fmt::Debug for PruneResult { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PruneResult {{ ")?; write!( @@ -428,7 +476,7 @@ impl fmt::Debug for PruneResult { } #[derive(Clone, Debug)] -pub struct ReadyTransaction { +pub struct ReadyTransaction { /// the hash of the submitted transaction hash: TxHash, /// transactions promoted to the ready queue @@ -436,10 +484,10 @@ pub struct ReadyTransaction { /// transaction that failed and became discarded discarded: Vec, /// Transactions removed from the Ready pool - removed: Vec>, + removed: Vec>>, } -impl ReadyTransaction { +impl ReadyTransaction { pub fn new(hash: TxHash) -> Self { Self { hash, @@ -451,9 +499,9 @@ impl ReadyTransaction { } #[derive(Clone, Debug)] -pub enum AddedTransaction { +pub enum AddedTransaction { /// transaction was successfully added and being processed - Ready(ReadyTransaction), + Ready(ReadyTransaction), /// Transaction was successfully added but not yet queued for processing Pending { /// the hash of the submitted transaction @@ -461,8 +509,8 @@ pub enum AddedTransaction { }, } -impl AddedTransaction { - pub fn hash(&self) -> &TxHash { +impl AddedTransaction { + pub const fn hash(&self) -> &TxHash { match self { Self::Ready(tx) => &tx.hash, Self::Pending { hash } => hash, diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 6cafa4c4cd3ce..5280987483dd7 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,16 +1,26 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; +use alloy_consensus::{ + Transaction, Typed2718, + crypto::RecoveryError, + transaction::{SignerRecoverable, TxHashRef}, +}; use alloy_network::AnyRpcTransaction; use alloy_primitives::{ - map::{HashMap, HashSet}, Address, TxHash, + map::{HashMap, HashSet}, }; -use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; +use alloy_rlp::Encodable; +use anvil_core::eth::transaction::PendingTransaction; use parking_lot::RwLock; use std::{cmp::Ordering, collections::BTreeSet, fmt, str::FromStr, sync::Arc, time::Instant}; /// A unique identifying marker for a transaction pub type TxMarker = Vec; +/// Result type for replaced transactions: the replaced pool transactions and the hashes they +/// unlock. +type ReplacedTransactions = (Vec>>, Vec); + /// creates an unique identifier for aan (`nonce` + `Address`) combo pub fn to_marker(nonce: u64, from: Address) -> TxMarker { let mut data = [0u8; 28]; @@ -36,10 +46,10 @@ pub enum TransactionOrder { impl TransactionOrder { /// Returns the priority of the transactions - pub fn priority(&self, tx: &TypedTransaction) -> TransactionPriority { + pub fn priority(&self, tx: &T) -> TransactionPriority { match self { Self::Fifo => TransactionPriority::default(), - Self::Fees => TransactionPriority(tx.gas_price()), + Self::Fees => TransactionPriority(tx.max_fee_per_gas()), } } } @@ -67,9 +77,9 @@ pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] -pub struct PoolTransaction { +pub struct PoolTransaction { /// the pending eth transaction - pub pending_transaction: PendingTransaction, + pub pending_transaction: PendingTransaction, /// Markers required by the transaction pub requires: Vec, /// Markers that this transaction provides @@ -80,8 +90,8 @@ pub struct PoolTransaction { // == impl PoolTransaction == -impl PoolTransaction { - pub fn new(transaction: PendingTransaction) -> Self { +impl PoolTransaction { + pub const fn new(transaction: PendingTransaction) -> Self { Self { pending_transaction: transaction, requires: vec![], @@ -89,38 +99,48 @@ impl PoolTransaction { priority: TransactionPriority(0), } } + /// Returns the hash of this transaction - pub fn hash(&self) -> TxHash { + pub const fn hash(&self) -> TxHash { *self.pending_transaction.hash() } +} - /// Returns the gas pric of this transaction - pub fn gas_price(&self) -> u128 { - self.pending_transaction.transaction.gas_price() +impl PoolTransaction { + /// Returns the max fee per gas of this transaction + pub fn max_fee_per_gas(&self) -> u128 { + self.pending_transaction.transaction.max_fee_per_gas() } +} +impl PoolTransaction { /// Returns the type of the transaction pub fn tx_type(&self) -> u8 { - self.pending_transaction.transaction.r#type().unwrap_or_default() + self.pending_transaction.transaction.ty() } } -impl fmt::Debug for PoolTransaction { +impl fmt::Debug for PoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; + write!(fmt, "hash: {:?}, ", self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; write!(fmt, "provides: [{}], ", hex_fmt_many(self.provides.iter()))?; - write!(fmt, "raw tx: {:?}", &self.pending_transaction)?; + write!(fmt, "raw tx: {:?}", self.pending_transaction)?; write!(fmt, "}}")?; Ok(()) } } -impl TryFrom for PoolTransaction { +impl TryFrom for PoolTransaction +where + T: SignerRecoverable + TxHashRef + Encodable + TryFrom, + >::Error: Into, + RecoveryError: Into, +{ type Error = eyre::Error; fn try_from(value: AnyRpcTransaction) -> Result { - let typed_transaction = TypedTransaction::try_from(value)?; + let typed_transaction = T::try_from(value).map_err(Into::into)?; let pending_transaction = PendingTransaction::new(typed_transaction)?; Ok(Self { pending_transaction, @@ -130,22 +150,31 @@ impl TryFrom for PoolTransaction { }) } } + /// A waiting pool of transaction that are pending, but not yet ready to be included in a new block. /// /// Keeps a set of transactions that are waiting for other transactions -#[derive(Clone, Debug, Default)] -pub struct PendingTransactions { +#[derive(Clone, Debug)] +pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, /// mapping of the markers of a transaction to the hash of the transaction waiting_markers: HashMap, TxHash>, /// the transactions that are not ready yet are waiting for another tx to finish - waiting_queue: HashMap, + waiting_queue: HashMap>, } -// == impl PendingTransactions == +impl Default for PendingTransactions { + fn default() -> Self { + Self { + required_markers: Default::default(), + waiting_markers: Default::default(), + waiting_queue: Default::default(), + } + } +} -impl PendingTransactions { +impl PendingTransactions { /// Returns the number of transactions that are currently waiting pub fn len(&self) -> usize { self.waiting_queue.len() @@ -163,52 +192,17 @@ impl PendingTransactions { } /// Returns an iterator over all transactions in the waiting pool - pub fn transactions(&self) -> impl Iterator> + '_ { + pub fn transactions(&self) -> impl Iterator>> + '_ { self.waiting_queue.values().map(|tx| tx.transaction.clone()) } - /// Adds a transaction to Pending queue of transactions - pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> { - assert!(!tx.is_ready(), "transaction must not be ready"); - assert!( - !self.waiting_queue.contains_key(&tx.transaction.hash()), - "transaction is already added" - ); - - if let Some(replace) = self - .waiting_markers - .get(&tx.transaction.provides) - .and_then(|hash| self.waiting_queue.get(hash)) - { - // check if underpriced - if tx.transaction.gas_price() < replace.transaction.gas_price() { - warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]", tx.transaction.hash()); - return Err(PoolError::ReplacementUnderpriced(Box::new( - tx.transaction.as_ref().clone(), - ))) - } - } - - // add all missing markers - for marker in &tx.missing_markers { - self.required_markers.entry(marker.clone()).or_default().insert(tx.transaction.hash()); - } - - // also track identifying markers - self.waiting_markers.insert(tx.transaction.provides.clone(), tx.transaction.hash()); - // add tx to the queue - self.waiting_queue.insert(tx.transaction.hash(), tx); - - Ok(()) - } - /// Returns true if given transaction is part of the queue pub fn contains(&self, hash: &TxHash) -> bool { self.waiting_queue.contains_key(hash) } /// Returns the transaction for the hash if it's pending - pub fn get(&self, hash: &TxHash) -> Option<&PendingPoolTransaction> { + pub fn get(&self, hash: &TxHash) -> Option<&PendingPoolTransaction> { self.waiting_queue.get(hash) } @@ -219,7 +213,7 @@ impl PendingTransactions { pub fn mark_and_unlock( &mut self, markers: impl IntoIterator>, - ) -> Vec { + ) -> Vec> { let mut unlocked_ready = Vec::new(); for mark in markers { let mark = mark.as_ref(); @@ -244,7 +238,7 @@ impl PendingTransactions { /// Removes the transactions associated with the given hashes /// /// Returns all removed transactions. - pub fn remove(&mut self, hashes: Vec) -> Vec> { + pub fn remove(&mut self, hashes: Vec) -> Vec>> { let mut removed = vec![]; for hash in hashes { if let Some(waiting_tx) = self.waiting_queue.remove(&hash) { @@ -267,24 +261,57 @@ impl PendingTransactions { } } +impl PendingTransactions { + /// Adds a transaction to Pending queue of transactions + pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> { + assert!(!tx.is_ready(), "transaction must not be ready"); + assert!( + !self.waiting_queue.contains_key(&tx.transaction.hash()), + "transaction is already added" + ); + + if let Some(replace) = self + .waiting_markers + .get(&tx.transaction.provides) + .and_then(|hash| self.waiting_queue.get(hash)) + { + // check if underpriced + if tx.transaction.max_fee_per_gas() <= replace.transaction.max_fee_per_gas() { + warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]", tx.transaction.hash()); + return Err(PoolError::ReplacementUnderpriced(tx.transaction.hash())); + } + } + + // add all missing markers + for marker in &tx.missing_markers { + self.required_markers.entry(marker.clone()).or_default().insert(tx.transaction.hash()); + } + + // also track identifying markers + self.waiting_markers.insert(tx.transaction.provides.clone(), tx.transaction.hash()); + // add tx to the queue + self.waiting_queue.insert(tx.transaction.hash(), tx); + + Ok(()) + } +} + /// A transaction in the pool #[derive(Clone)] -pub struct PendingPoolTransaction { - pub transaction: Arc, +pub struct PendingPoolTransaction { + pub transaction: Arc>, /// markers required and have not been satisfied yet by other transactions in the pool pub missing_markers: HashSet, /// timestamp when the tx was added pub added_at: Instant, } -// == impl PendingTransaction == - -impl PendingPoolTransaction { - /// Creates a new `PendingTransaction`. +impl PendingPoolTransaction { + /// Creates a new `PendingPoolTransaction`. /// /// Determines the markers that are still missing before this transaction can be moved to the /// ready queue. - pub fn new(transaction: PoolTransaction, provided: &HashMap) -> Self { + pub fn new(transaction: PoolTransaction, provided: &HashMap) -> Self { let missing_markers = transaction .requires .iter() @@ -309,7 +336,7 @@ impl PendingPoolTransaction { } } -impl fmt::Debug for PendingPoolTransaction { +impl fmt::Debug for PendingPoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PendingTransaction {{ ")?; write!(fmt, "added_at: {:?}, ", self.added_at)?; @@ -319,19 +346,17 @@ impl fmt::Debug for PendingPoolTransaction { } } -pub struct TransactionsIterator { - all: HashMap, - awaiting: HashMap, - independent: BTreeSet, +pub struct TransactionsIterator { + all: HashMap>, + awaiting: HashMap)>, + independent: BTreeSet>, _invalid: HashSet, } -// == impl TransactionsIterator == - -impl TransactionsIterator { +impl TransactionsIterator { /// Depending on number of satisfied requirements insert given ref /// either to awaiting set or to best set. - fn independent_or_awaiting(&mut self, satisfied: usize, tx_ref: PoolTransactionRef) { + fn independent_or_awaiting(&mut self, satisfied: usize, tx_ref: PoolTransactionRef) { if satisfied >= tx_ref.transaction.requires.len() { // If we have satisfied all deps insert to best self.independent.insert(tx_ref); @@ -342,8 +367,8 @@ impl TransactionsIterator { } } -impl Iterator for TransactionsIterator { - type Item = Arc; +impl Iterator for TransactionsIterator { + type Item = Arc>; fn next(&mut self) -> Option { loop { @@ -371,14 +396,14 @@ impl Iterator for TransactionsIterator { } } - return Some(best.transaction) + return Some(best.transaction); } } } /// transactions that are ready to be included in a block. -#[derive(Clone, Debug, Default)] -pub struct ReadyTransactions { +#[derive(Clone, Debug)] +pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// /// this way we can determine when transactions where submitted to the pool @@ -386,17 +411,26 @@ pub struct ReadyTransactions { /// markers that are provided by `ReadyTransaction`s provided_markers: HashMap, /// transactions that are ready - ready_tx: Arc>>, + ready_tx: Arc>>>, /// independent transactions that can be included directly and don't require other transactions /// Sorted by their id - independent_transactions: BTreeSet, + independent_transactions: BTreeSet>, } -// == impl ReadyTransactions == +impl Default for ReadyTransactions { + fn default() -> Self { + Self { + id: 0, + provided_markers: Default::default(), + ready_tx: Default::default(), + independent_transactions: Default::default(), + } + } +} -impl ReadyTransactions { +impl ReadyTransactions { /// Returns an iterator over all transactions - pub fn get_transactions(&self) -> TransactionsIterator { + pub fn get_transactions(&self) -> TransactionsIterator { TransactionsIterator { all: self.ready_tx.read().clone(), independent: self.independent_transactions.clone(), @@ -417,125 +451,34 @@ impl ReadyTransactions { self.ready_tx.read().contains_key(hash) } + /// Returns the number of ready transactions without cloning the snapshot + pub fn len(&self) -> usize { + self.ready_tx.read().len() + } + + /// Returns true if there are no ready transactions + pub fn is_empty(&self) -> bool { + self.ready_tx.read().is_empty() + } + /// Returns the transaction for the hash if it's in the ready pool but not yet mined - pub fn get(&self, hash: &TxHash) -> Option { + pub fn get(&self, hash: &TxHash) -> Option> { self.ready_tx.read().get(hash).cloned() } - pub fn provided_markers(&self) -> &HashMap { + pub const fn provided_markers(&self) -> &HashMap { &self.provided_markers } - fn next_id(&mut self) -> u64 { + const fn next_id(&mut self) -> u64 { let id = self.id; self.id = self.id.wrapping_add(1); id } - /// Adds a new transactions to the ready queue. - /// - /// # Panics - /// - /// If the pending transaction is not ready ([`PendingPoolTransaction::is_ready`]) - /// or the transaction is already included. - pub fn add_transaction( - &mut self, - tx: PendingPoolTransaction, - ) -> Result>, PoolError> { - assert!(tx.is_ready(), "transaction must be ready",); - assert!( - !self.ready_tx.read().contains_key(&tx.transaction.hash()), - "transaction already included" - ); - - let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?; - - let id = self.next_id(); - let hash = tx.transaction.hash(); - - let mut independent = true; - let mut requires_offset = 0; - let mut ready = self.ready_tx.write(); - // Add links to transactions that unlock the current one - for mark in &tx.transaction.requires { - // Check if the transaction that satisfies the mark is still in the queue. - if let Some(other) = self.provided_markers.get(mark) { - let tx = ready.get_mut(other).expect("hash included;"); - tx.unlocks.push(hash); - // tx still depends on other tx - independent = false; - } else { - requires_offset += 1; - } - } - - // update markers - for mark in tx.transaction.provides.iter().cloned() { - self.provided_markers.insert(mark, hash); - } - - let transaction = PoolTransactionRef { id, transaction: tx.transaction }; - - // add to the independent set - if independent { - self.independent_transactions.insert(transaction.clone()); - } - - // insert to ready queue - ready.insert(hash, ReadyTransaction { transaction, unlocks, requires_offset }); - - Ok(replaced_tx) - } - - /// Removes and returns those transactions that got replaced by the `tx` - fn replaced_transactions( - &mut self, - tx: &PoolTransaction, - ) -> Result<(Vec>, Vec), PoolError> { - // check if we are replacing transactions - let remove_hashes: HashSet<_> = - tx.provides.iter().filter_map(|mark| self.provided_markers.get(mark)).collect(); - - // early exit if we are not replacing anything. - if remove_hashes.is_empty() { - return Ok((Vec::new(), Vec::new())) - } - - // check if we're replacing the same transaction and if it can be replaced - - let mut unlocked_tx = Vec::new(); - { - // construct a list of unlocked transactions - // also check for transactions that shouldn't be replaced because underpriced - let ready = self.ready_tx.read(); - for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(*hash)) { - // if we're attempting to replace a transaction that provides the exact same markers - // (addr + nonce) then we check for gas price - if to_remove.provides() == tx.provides { - // check if underpriced - if tx.pending_transaction.transaction.gas_price() <= to_remove.gas_price() { - warn!(target: "txpool", "ready replacement transaction underpriced [{:?}]", tx.hash()); - return Err(PoolError::ReplacementUnderpriced(Box::new(tx.clone()))) - } else { - trace!(target: "txpool", "replacing ready transaction [{:?}] with higher gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash()); - } - } - - unlocked_tx.extend(to_remove.unlocks.iter().cloned()) - } - } - - let remove_hashes = remove_hashes.into_iter().copied().collect::>(); - - let new_provides = tx.provides.iter().cloned().collect::>(); - let removed_tx = self.remove_with_markers(remove_hashes, Some(new_provides)); - - Ok((removed_tx, unlocked_tx)) - } - /// Removes the transactions from the ready queue and returns the removed transactions. /// This will also remove all transactions that depend on those. - pub fn clear_transactions(&mut self, tx_hashes: &[TxHash]) -> Vec> { + pub fn clear_transactions(&mut self, tx_hashes: &[TxHash]) -> Vec>> { self.remove_with_markers(tx_hashes.to_vec(), None) } @@ -543,7 +486,7 @@ impl ReadyTransactions { /// /// This will also remove all transactions that lead to the transaction that provides the /// marker. - pub fn prune_tags(&mut self, marker: TxMarker) -> Vec> { + pub fn prune_tags(&mut self, marker: TxMarker) -> Vec>> { let mut removed_tx = vec![]; // the markers to remove @@ -572,11 +515,7 @@ impl ReadyTransactions { if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } - if tx2.unlocks.is_empty() { - Some(tx2.transaction.transaction.provides.clone()) - } else { - None - } + tx2.unlocks.is_empty().then(|| tx2.transaction.transaction.provides.clone()) }; // find previous transactions @@ -620,7 +559,7 @@ impl ReadyTransactions { &mut self, mut tx_hashes: Vec, marker_filter: Option>, - ) -> Vec> { + ) -> Vec>> { let mut removed = Vec::new(); let mut ready = self.ready_tx.write(); @@ -639,12 +578,11 @@ impl ReadyTransactions { // remove from unlocks for mark in &tx.transaction.transaction.requires { - if let Some(hash) = self.provided_markers.get(mark) { - if let Some(tx) = ready.get_mut(hash) { - if let Some(idx) = tx.unlocks.iter().position(|i| i == hash) { - tx.unlocks.swap_remove(idx); - } - } + if let Some(provider_hash) = self.provided_markers.get(mark) + && let Some(provider_tx) = ready.get_mut(provider_hash) + && let Some(idx) = provider_tx.unlocks.iter().position(|i| i == &hash) + { + provider_tx.unlocks.swap_remove(idx); } } @@ -665,30 +603,139 @@ impl ReadyTransactions { } } +impl ReadyTransactions { + /// Adds a new transactions to the ready queue. + /// + /// # Panics + /// + /// If the pending transaction is not ready ([`PendingPoolTransaction::is_ready`]) + /// or the transaction is already included. + pub fn add_transaction( + &mut self, + tx: PendingPoolTransaction, + ) -> Result>>, PoolError> { + assert!(tx.is_ready(), "transaction must be ready",); + assert!( + !self.ready_tx.read().contains_key(&tx.transaction.hash()), + "transaction already included" + ); + + let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?; + + let id = self.next_id(); + let hash = tx.transaction.hash(); + + let mut independent = true; + let mut requires_offset = 0; + let mut ready = self.ready_tx.write(); + // Add links to transactions that unlock the current one + for mark in &tx.transaction.requires { + // Check if the transaction that satisfies the mark is still in the queue. + if let Some(other) = self.provided_markers.get(mark) { + let tx = ready.get_mut(other).expect("hash included;"); + tx.unlocks.push(hash); + // tx still depends on other tx + independent = false; + } else { + requires_offset += 1; + } + } + + // update markers + for mark in tx.transaction.provides.iter().cloned() { + self.provided_markers.insert(mark, hash); + } + + let transaction = PoolTransactionRef { id, transaction: tx.transaction }; + + // add to the independent set + if independent { + self.independent_transactions.insert(transaction.clone()); + } + + // insert to ready queue + ready.insert(hash, ReadyTransaction { transaction, unlocks, requires_offset }); + + Ok(replaced_tx) + } + + /// Removes and returns those transactions that got replaced by the `tx` + fn replaced_transactions( + &mut self, + tx: &PoolTransaction, + ) -> Result, PoolError> { + // check if we are replacing transactions + let remove_hashes: HashSet<_> = + tx.provides.iter().filter_map(|mark| self.provided_markers.get(mark)).collect(); + + // early exit if we are not replacing anything. + if remove_hashes.is_empty() { + return Ok((Vec::new(), Vec::new())); + } + + // check if we're replacing the same transaction and if it can be replaced + let mut unlocked_tx = Vec::new(); + { + // construct a list of unlocked transactions + // also check for transactions that shouldn't be replaced because underpriced + let ready = self.ready_tx.read(); + for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(*hash)) { + // if we're attempting to replace a transaction that provides the exact same markers + // (addr + nonce) then we check for gas price + if to_remove.provides() == tx.provides { + // check if underpriced + if tx.pending_transaction.transaction.max_fee_per_gas() + <= to_remove.max_fee_per_gas() + { + warn!(target: "txpool", "ready replacement transaction underpriced [{:?}]", tx.hash()); + return Err(PoolError::ReplacementUnderpriced(tx.hash())); + } + trace!(target: "txpool", "replacing ready transaction [{:?}] with higher gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash()); + } + + unlocked_tx.extend(to_remove.unlocks.iter().copied()) + } + } + + let remove_hashes = remove_hashes.into_iter().copied().collect::>(); + + let new_provides = tx.provides.iter().cloned().collect::>(); + let removed_tx = self.remove_with_markers(remove_hashes, Some(new_provides)); + + Ok((removed_tx, unlocked_tx)) + } +} + /// A reference to a transaction in the pool -#[derive(Clone, Debug)] -pub struct PoolTransactionRef { +#[derive(Debug)] +pub struct PoolTransactionRef { /// actual transaction - pub transaction: Arc, + pub transaction: Arc>, /// identifier used to internally compare the transaction in the pool pub id: u64, } -impl Eq for PoolTransactionRef {} +impl Clone for PoolTransactionRef { + fn clone(&self) -> Self { + Self { transaction: Arc::clone(&self.transaction), id: self.id } + } +} -impl PartialEq for PoolTransactionRef { +impl Eq for PoolTransactionRef {} + +impl PartialEq for PoolTransactionRef { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } -impl PartialOrd for PoolTransactionRef { +impl PartialOrd for PoolTransactionRef { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for PoolTransactionRef { +impl Ord for PoolTransactionRef { fn cmp(&self, other: &Self) -> Ordering { self.transaction .priority @@ -697,23 +744,35 @@ impl Ord for PoolTransactionRef { } } -#[derive(Clone, Debug)] -pub struct ReadyTransaction { +#[derive(Debug)] +pub struct ReadyTransaction { /// ref to the actual transaction - pub transaction: PoolTransactionRef, + pub transaction: PoolTransactionRef, /// tracks the transactions that get unlocked by this transaction pub unlocks: Vec, /// amount of required markers that are inherently provided pub requires_offset: usize, } -impl ReadyTransaction { +impl Clone for ReadyTransaction { + fn clone(&self) -> Self { + Self { + transaction: self.transaction.clone(), + unlocks: self.unlocks.clone(), + requires_offset: self.requires_offset, + } + } +} + +impl ReadyTransaction { pub fn provides(&self) -> &[TxMarker] { &self.transaction.transaction.provides } +} - pub fn gas_price(&self) -> u128 { - self.transaction.transaction.gas_price() +impl ReadyTransaction { + pub fn max_fee_per_gas(&self) -> u128 { + self.transaction.transaction.max_fee_per_gas() } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 44f8a6be33f64..d1736c3093056 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,15 +1,18 @@ use crate::eth::error::BlockchainError; +#[cfg(feature = "optimism")] +use alloy_consensus::Sealed; use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; -use alloy_network::TxSignerSync; -use alloy_primitives::{map::AddressHashMap, Address, Signature, B256}; +use alloy_network::{Network, TxSignerSync}; +use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; use alloy_signer::Signer as AlloySigner; use alloy_signer_local::PrivateKeySigner; -use anvil_core::eth::transaction::{TypedTransaction, TypedTransactionRequest}; +use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx}; +use tempo_primitives::TempoSignature; -/// A transaction signer +/// Network-agnostic signing: messages, typed data, and hashes. #[async_trait::async_trait] -pub trait Signer: Send + Sync { +pub trait MessageSigner: Send + Sync { /// returns the available accounts for this signer fn accounts(&self) -> Vec
; @@ -31,13 +34,22 @@ pub trait Signer: Send + Sync { /// Signs the given hash. async fn sign_hash(&self, address: Address, hash: B256) -> Result; +} - /// signs a transaction request using the given account in request - fn sign_transaction( +/// A transaction signer, generic over the network. +/// +/// Modelled after alloy's `NetworkWallet`: the +/// [`sign_transaction_from`](Signer::sign_transaction_from) method takes an +/// unsigned transaction and returns the fully-signed envelope in one step. +pub trait Signer: MessageSigner { + /// Signs an unsigned transaction and returns the signed envelope. + /// + /// Mirrors `NetworkWallet::sign_transaction_from`. + fn sign_transaction_from( &self, - request: TypedTransactionRequest, - address: &Address, - ) -> Result; + sender: &Address, + tx: N::UnsignedTx, + ) -> Result; } /// Maintains developer keys @@ -49,13 +61,13 @@ pub struct DevSigner { impl DevSigner { pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); - let accounts = addresses.iter().cloned().zip(accounts).collect(); + let accounts = addresses.iter().copied().zip(accounts).collect(); Self { addresses, accounts } } } #[async_trait::async_trait] -impl Signer for DevSigner { +impl MessageSigner for DevSigner { fn accounts(&self) -> Vec
{ self.addresses.clone() } @@ -90,51 +102,73 @@ impl Signer for DevSigner { Ok(signer.sign_hash(&hash).await?) } +} - fn sign_transaction( +impl Signer for DevSigner { + fn sign_transaction_from( &self, - request: TypedTransactionRequest, - address: &Address, - ) -> Result { - let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?; - match request { - TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - TypedTransactionRequest::EIP7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), - TypedTransactionRequest::Deposit(_) => { + sender: &Address, + tx: FoundryTypedTx, + ) -> Result { + let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?; + let envelope = match tx { + FoundryTypedTx::Legacy(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Legacy(t.into_signed(sig)) + } + FoundryTypedTx::Eip2930(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip2930(t.into_signed(sig)) + } + FoundryTypedTx::Eip1559(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip1559(t.into_signed(sig)) + } + FoundryTypedTx::Eip7702(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip7702(t.into_signed(sig)) + } + FoundryTypedTx::Eip4844(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Eip4844(t.into_signed(sig)) + } + #[cfg(feature = "optimism")] + FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } - } + #[cfg(feature = "optimism")] + FoundryTypedTx::PostExec(_) => { + unreachable!("op post-exec txs should not be signed") + } + FoundryTypedTx::Tempo(mut t) => { + let sig = signer.sign_transaction_sync(&mut t)?; + FoundryTxEnvelope::Tempo(t.into_signed(sig.into())) + } + }; + Ok(envelope) } } -/// converts the `request` into a [`TypedTransactionRequest`] with the given signature -/// -/// # Errors +/// Builds a TxEnvelope from UnsignedTx with a zeroed signature. /// -/// This will fail if the `signature` contains an erroneous recovery id. -pub fn build_typed_transaction( - request: TypedTransactionRequest, - signature: Signature, -) -> Result { - let tx = match request { - TypedTransactionRequest::Legacy(tx) => TypedTransaction::Legacy(tx.into_signed(signature)), - TypedTransactionRequest::EIP2930(tx) => { - TypedTransaction::EIP2930(tx.into_signed(signature)) +/// Used for impersonated accounts, where transactions are accepted without a valid signature. +pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { + let signature = Signature::new(Default::default(), Default::default(), false); + match typed_tx { + FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)), + FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)), + FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), + FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)), + FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)), + #[cfg(feature = "optimism")] + FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)), + #[cfg(feature = "optimism")] + FoundryTypedTx::PostExec(_) => { + unreachable!("op post-exec txs should not be impersonated") } - TypedTransactionRequest::EIP1559(tx) => { - TypedTransaction::EIP1559(tx.into_signed(signature)) + FoundryTypedTx::Tempo(tx) => { + let tempo_sig: TempoSignature = signature.into(); + FoundryTxEnvelope::Tempo(tx.into_signed(tempo_sig)) } - TypedTransactionRequest::EIP7702(tx) => { - TypedTransaction::EIP7702(tx.into_signed(signature)) - } - TypedTransactionRequest::EIP4844(tx) => { - TypedTransaction::EIP4844(tx.into_signed(signature)) - } - TypedTransactionRequest::Deposit(tx) => TypedTransaction::Deposit(tx), - }; - - Ok(tx) + } } diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index beb73276c3b4d..36772dccd0982 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -1,58 +1,12 @@ -use alloy_primitives::Address; -use revm::{ - precompile::{PrecompileSpecId, Precompiles}, - primitives::hardfork::SpecId, -}; -use std::fmt; - -pub fn get_precompiles_for(spec_id: SpecId) -> Vec
{ - Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).addresses().copied().collect() -} - -/// wrapper type that displays byte as hex -pub struct HexDisplay<'a>(&'a [u8]); +use alloy_primitives::hex; +use itertools::Itertools; +/// Formats values as hex strings, separated by commas. pub fn hex_fmt_many(i: I) -> String where I: IntoIterator, T: AsRef<[u8]>, { - i.into_iter() - .map(|item| HexDisplay::from(item.as_ref()).to_string()) - .collect::>() - .join(", ") -} - -impl<'a> HexDisplay<'a> { - pub fn from(b: &'a [u8]) -> Self { - HexDisplay(b) - } -} - -impl fmt::Display for HexDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0.len() < 1027 { - for byte in self.0 { - f.write_fmt(format_args!("{byte:02x}"))?; - } - } else { - for byte in &self.0[0..512] { - f.write_fmt(format_args!("{byte:02x}"))?; - } - f.write_str("...")?; - for byte in &self.0[self.0.len() - 512..] { - f.write_fmt(format_args!("{byte:02x}"))?; - } - } - Ok(()) - } -} - -impl fmt::Debug for HexDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in self.0 { - f.write_fmt(format_args!("{byte:02x}"))?; - } - Ok(()) - } + let items = i.into_iter().map(|item| hex::encode(item.as_ref())).format(", "); + format!("{items}") } diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs deleted file mode 100644 index 0d70250c4cb6e..0000000000000 --- a/crates/anvil/src/evm.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::fmt::Debug; - -use alloy_evm::{ - eth::EthEvmContext, - precompiles::{DynPrecompile, PrecompilesMap}, - Database, Evm, -}; -use foundry_evm_core::either_evm::EitherEvm; -use op_revm::OpContext; -use revm::{precompile::PrecompileWithAddress, Inspector}; - -/// Object-safe trait that enables injecting extra precompiles when using -/// `anvil` as a library. -pub trait PrecompileFactory: Send + Sync + Unpin + Debug { - /// Returns a set of precompiles to extend the EVM with. - fn precompiles(&self) -> Vec; -} - -/// Inject precompiles into the EVM dynamically. -pub fn inject_precompiles( - evm: &mut EitherEvm, - precompiles: Vec, -) where - DB: Database, - I: Inspector> + Inspector>, -{ - for p in precompiles { - evm.precompiles_mut() - .apply_precompile(p.address(), |_| Some(DynPrecompile::from(*p.precompile()))); - } -} - -#[cfg(test)] -mod tests { - use std::convert::Infallible; - - use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EthEvm, Evm, EvmEnv}; - use alloy_op_evm::OpEvm; - use alloy_primitives::{address, Address, Bytes, TxKind, U256}; - use foundry_evm_core::either_evm::EitherEvm; - use itertools::Itertools; - use op_revm::{precompiles::OpPrecompiles, L1BlockInfo, OpContext, OpSpecId, OpTransaction}; - use revm::{ - context::{CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, - database::{EmptyDB, EmptyDBTyped}, - handler::{instructions::EthInstructions, EthPrecompiles}, - inspector::NoOpInspector, - interpreter::interpreter::EthInterpreter, - precompile::{ - PrecompileOutput, PrecompileResult, PrecompileSpecId, PrecompileWithAddress, - Precompiles, - }, - primitives::hardfork::SpecId, - Journal, - }; - - use crate::{inject_precompiles, PrecompileFactory}; - - // A precompile activated in the `Prague` spec. - const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); - - // A precompile activated in the `Isthmus` spec. - const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); - - // A custom precompile address and payload for testing. - const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); - const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; - - #[derive(Debug)] - struct CustomPrecompileFactory; - - impl PrecompileFactory for CustomPrecompileFactory { - fn precompiles(&self) -> Vec { - vec![PrecompileWithAddress::from(( - PRECOMPILE_ADDR, - custom_echo_precompile as fn(&[u8], u64) -> PrecompileResult, - ))] - } - } - - /// Custom precompile that echoes the input data. - /// In this example it uses `0xdeadbeef` as the input data, returning it as output. - fn custom_echo_precompile(input: &[u8], _gas_limit: u64) -> PrecompileResult { - Ok(PrecompileOutput { bytes: Bytes::copy_from_slice(input), gas_used: 0 }) - } - - /// Creates a new EVM instance with the custom precompile factory. - fn create_eth_evm( - spec: SpecId, - ) -> (foundry_evm::Env, EitherEvm, NoOpInspector, PrecompilesMap>) - { - let eth_env = foundry_evm::Env { - evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) }, - tx: TxEnv { - kind: TxKind::Call(PRECOMPILE_ADDR), - data: PAYLOAD.into(), - ..Default::default() - }, - }; - - let eth_evm_context = EthEvmContext { - journaled_state: Journal::new(EmptyDB::default()), - block: eth_env.evm_env.block_env.clone(), - cfg: eth_env.evm_env.cfg_env.clone(), - tx: eth_env.tx.clone(), - chain: (), - local: LocalContext::default(), - error: Ok(()), - }; - - let eth_precompiles = EthPrecompiles { - precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), - spec, - } - .precompiles; - let eth_evm = EitherEvm::Eth(EthEvm::new( - RevmEvm::new_with_inspector( - eth_evm_context, - NoOpInspector, - EthInstructions::>::default(), - PrecompilesMap::from_static(eth_precompiles), - ), - true, - )); - - (eth_env, eth_evm) - } - - /// Creates a new OP EVM instance with the custom precompile factory. - fn create_op_evm( - spec: SpecId, - op_spec: OpSpecId, - ) -> ( - crate::eth::backend::env::Env, - EitherEvm, NoOpInspector, PrecompilesMap>, - ) { - let op_env = crate::eth::backend::env::Env { - evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) }, - tx: OpTransaction:: { - base: TxEnv { - kind: TxKind::Call(PRECOMPILE_ADDR), - data: PAYLOAD.into(), - ..Default::default() - }, - ..Default::default() - }, - is_optimism: true, - }; - - let mut chain = L1BlockInfo::default(); - - if op_spec == OpSpecId::ISTHMUS { - chain.operator_fee_constant = Some(U256::from(0)); - chain.operator_fee_scalar = Some(U256::from(0)); - } - - let op_cfg = op_env.evm_env.cfg_env.clone().with_spec(op_spec); - let op_evm_context = OpContext { - journaled_state: { - let mut journal = Journal::new(EmptyDB::default()); - // Converting SpecId into OpSpecId - journal.set_spec_id(op_env.evm_env.cfg_env.spec); - journal - }, - block: op_env.evm_env.block_env.clone(), - cfg: op_cfg.clone(), - tx: op_env.tx.clone(), - chain, - local: LocalContext::default(), - error: Ok(()), - }; - - let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles(); - let op_evm = EitherEvm::Op(OpEvm::new( - op_revm::OpEvm(RevmEvm::new_with_inspector( - op_evm_context, - NoOpInspector, - EthInstructions::>::default(), - PrecompilesMap::from_static(op_precompiles), - )), - true, - )); - - (op_env, op_evm) - } - - #[test] - fn build_eth_evm_with_extra_precompiles_default_spec() { - let (env, mut evm) = create_eth_evm(SpecId::default()); - - // Check that the Prague precompile IS present when using the default spec. - assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = match &mut evm { - EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(), - _ => unreachable!(), - }; - - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_eth_evm_with_extra_precompiles_london_spec() { - let (env, mut evm) = create_eth_evm(SpecId::LONDON); - - // Check that the Prague precompile IS NOT present when using the London spec. - assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = match &mut evm { - EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(), - _ => unreachable!(), - }; - - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_op_evm_with_extra_precompiles_default_spec() { - let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::default()); - - // Check that the Isthmus precompile IS present when using the default spec. - assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - - // Check that the Prague precompile IS present when using the default spec. - assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = match &mut evm { - EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(), - _ => unreachable!(), - }; - - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } - - #[test] - fn build_op_evm_with_extra_precompiles_bedrock_spec() { - let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::BEDROCK); - - // Check that the Isthmus precompile IS NOT present when using the `OpSpecId::BEDROCK` spec. - assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); - - // Check that the Prague precompile IS NOT present when using the `OpSpecId::BEDROCK` spec. - assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); - - assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); - - assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); - - let result = match &mut evm { - EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(), - _ => unreachable!(), - }; - - assert!(result.result.is_success()); - assert_eq!(result.result.output(), Some(&PAYLOAD.into())); - } -} diff --git a/crates/anvil/src/evm/mod.rs b/crates/anvil/src/evm/mod.rs new file mode 100644 index 0000000000000..85e43d371b097 --- /dev/null +++ b/crates/anvil/src/evm/mod.rs @@ -0,0 +1,162 @@ +use alloy_evm::precompiles::DynPrecompile; +use alloy_primitives::Address; +use std::fmt::Debug; + +#[cfg(feature = "optimism")] +mod optimism; + +/// Object-safe trait that enables injecting extra precompiles when using +/// `anvil` as a library. +pub trait PrecompileFactory: Send + Sync + Unpin + Debug { + /// Returns a set of precompiles to extend the EVM with. + fn precompiles(&self) -> Vec<(Address, DynPrecompile)>; +} + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use crate::PrecompileFactory; + use alloy_evm::{ + EthEvm, Evm, + eth::EthEvmContext, + precompiles::{DynPrecompile, PrecompilesMap}, + }; + use alloy_primitives::{Address, Bytes, TxKind, address}; + use itertools::Itertools; + use revm::{ + Journal, + context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + handler::{EthPrecompiles, instructions::EthInstructions}, + inspector::NoOpInspector, + interpreter::interpreter::EthInterpreter, + precompile::{PrecompileOutput, PrecompileSpecId, PrecompileStatus, Precompiles}, + primitives::hardfork::SpecId, + }; + + // A precompile activated in the `Prague` spec (BLS12-381 G2 map). + pub(super) const ETH_PRAGUE_PRECOMPILE: Address = + address!("0x0000000000000000000000000000000000000011"); + + // A precompile activated in the `Osaka` spec (EIP-7951). + const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + // A custom precompile address and payload for testing. + pub(super) const PRECOMPILE_ADDR: Address = + address!("0x0000000000000000000000000000000000000071"); + pub(super) const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; + + #[derive(Debug)] + pub(super) struct CustomPrecompileFactory; + + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec<(Address, DynPrecompile)> { + use alloy_evm::precompiles::PrecompileInput; + vec![( + PRECOMPILE_ADDR, + DynPrecompile::from(|input: PrecompileInput<'_>| { + Ok(PrecompileOutput { + status: PrecompileStatus::Success, + bytes: Bytes::copy_from_slice(input.data), + gas_used: 0, + gas_refunded: 0, + state_gas_used: 0, + reservoir: input.reservoir, + }) + }), + )] + } + } + + /// Creates a new Eth EVM instance. + fn create_eth_evm( + spec: SpecId, + ) -> (TxEnv, EthEvm, NoOpInspector, PrecompilesMap>) { + let tx_env = TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }; + + let eth_evm_context = EthEvmContext { + journaled_state: Journal::new(EmptyDB::default()), + block: BlockEnv::default(), + cfg: CfgEnv::new_with_spec(spec), + tx: tx_env.clone(), + chain: (), + local: LocalContext::default(), + error: Ok(()), + }; + + let eth_precompiles = EthPrecompiles { + precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), + spec, + } + .precompiles; + let eth_evm = EthEvm::new( + RevmEvm::new_with_inspector( + eth_evm_context, + NoOpInspector, + EthInstructions::>::new_mainnet_with_spec( + spec, + ), + PrecompilesMap::from_static(eth_precompiles), + ), + true, + ); + + (tx_env, eth_evm) + } + + #[test] + fn build_eth_evm_with_extra_precompiles_osaka_spec() { + let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA); + + assert!(evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx_env).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_eth_evm_with_extra_precompiles_london_spec() { + let (tx_env, mut evm) = create_eth_evm(SpecId::LONDON); + + assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx_env).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_eth_evm_with_extra_precompiles_prague_spec() { + let (tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE); + + assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx_env).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } +} diff --git a/crates/anvil/src/evm/optimism.rs b/crates/anvil/src/evm/optimism.rs new file mode 100644 index 0000000000000..526375fec31ea --- /dev/null +++ b/crates/anvil/src/evm/optimism.rs @@ -0,0 +1,87 @@ +//! Optimism-specific EVM helpers. + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use super::super::tests::{ + CustomPrecompileFactory, ETH_PRAGUE_PRECOMPILE, PAYLOAD, PRECOMPILE_ADDR, + }; + use crate::PrecompileFactory; + use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; + use alloy_op_evm::{OpEvm, OpEvmFactory, OpTx}; + use alloy_primitives::{Address, TxKind, U256, address}; + use itertools::Itertools; + use op_revm::{OpSpecId, OpTransaction}; + use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + inspector::NoOpInspector, + primitives::hardfork::SpecId, + }; + + // A precompile activated in the `Isthmus` spec. + const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + /// Creates a new OP EVM instance. + fn create_op_evm( + _spec: SpecId, + op_spec: OpSpecId, + ) -> (OpTx, OpEvm, NoOpInspector, PrecompilesMap, OpTx>) { + let tx = OpTx(OpTransaction:: { + base: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + ..Default::default() + }); + + let mut evm = OpEvmFactory::::default().create_evm_with_inspector( + EmptyDB::default(), + EvmEnv::new(CfgEnv::new_with_spec(op_spec), BlockEnv::default()), + NoOpInspector, + ); + + if op_spec == OpSpecId::ISTHMUS { + evm.ctx_mut().chain.operator_fee_constant = Some(U256::ZERO); + evm.ctx_mut().chain.operator_fee_scalar = Some(U256::ZERO); + } + + (tx, evm) + } + + #[test] + fn build_op_evm_with_extra_precompiles_isthmus_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); + + assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_bedrock_spec() { + let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK); + + assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = evm.transact(tx).unwrap(); + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } +} diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index c0c8e7aef15e1..98db8a5455df9 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -1,14 +1,19 @@ //! Support for polling based filters use crate::{ + StorageInfo, eth::{backend::notifications::NewBlockNotifications, error::ToRpcResponseResult}, pubsub::filter_logs, - StorageInfo, }; -use alloy_primitives::{map::HashMap, TxHash}; +use alloy_consensus::TxReceipt; +use alloy_network::Network; +use alloy_primitives::{TxHash, map::HashMap}; use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; -use anvil_rpc::response::ResponseResult; -use futures::{channel::mpsc::Receiver, Stream, StreamExt}; +use anvil_rpc::{ + error::{ErrorCode, RpcError}, + response::ResponseResult, +}; +use futures::{Stream, StreamExt, channel::mpsc::Receiver}; use std::{ pin::Pin, sync::Arc, @@ -18,23 +23,34 @@ use std::{ use tokio::sync::Mutex; /// Type alias for filters identified by their id and their expiration timestamp -type FilterMap = Arc>>; +type FilterMap = Arc, Instant)>>>; /// timeout after which to remove an active filter if it wasn't polled since then pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters -#[derive(Clone, Debug)] -pub struct Filters { +pub struct Filters { /// all currently active filters - active_filters: FilterMap, + active_filters: FilterMap, /// How long we keep a live the filter after the last poll keepalive: Duration, } -impl Filters { +impl Clone for Filters { + fn clone(&self) -> Self { + Self { active_filters: self.active_filters.clone(), keepalive: self.keepalive } + } +} + +impl std::fmt::Debug for Filters { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Filters").field("keepalive", &self.keepalive).finish_non_exhaustive() + } +} + +impl Filters { /// Adds a new `EthFilter` to the set - pub async fn add_filter(&self, filter: EthFilter) -> String { + pub async fn add_filter(&self, filter: EthFilter) -> String { let id = new_id(); trace!(target: "node::filter", "Adding new filter id {}", id); let mut filters = self.active_filters.lock().await; @@ -42,39 +58,23 @@ impl Filters { id } - pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { - { - let mut filters = self.active_filters.lock().await; - if let Some((filter, deadline)) = filters.get_mut(id) { - let resp = filter - .next() - .await - .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); - *deadline = self.next_deadline(); - return resp - } - } - warn!(target: "node::filter", "No filter found for {}", id); - ResponseResult::success(Vec::<()>::new()) - } - /// Returns the original `Filter` of an `eth_newFilter` pub async fn get_log_filter(&self, id: &str) -> Option { let filters = self.active_filters.lock().await; - if let Some((EthFilter::Logs(ref log), _)) = filters.get(id) { - return log.filter.filter.clone() + if let Some((EthFilter::Logs(log), _)) = filters.get(id) { + return log.filter.filter.clone(); } None } /// Removes the filter identified with the `id` - pub async fn uninstall_filter(&self, id: &str) -> Option { + pub async fn uninstall_filter(&self, id: &str) -> Option> { trace!(target: "node::filter", "Uninstalling filter id {}", id); self.active_filters.lock().await.remove(id).map(|(f, _)| f) } /// The duration how long to keep alive stale filters - pub fn keep_alive(&self) -> Duration { + pub const fn keep_alive(&self) -> Duration { self.keepalive } @@ -91,14 +91,39 @@ impl Filters { active_filters.retain(|id, (_, deadline)| { if now > *deadline { trace!(target: "node::filter",?id, "Evicting stale filter"); - return false + return false; } true }); } } -impl Default for Filters { +impl Filters +where + N::ReceiptEnvelope: TxReceipt, +{ + pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { + { + let mut filters = self.active_filters.lock().await; + if let Some((filter, deadline)) = filters.get_mut(id) { + let resp = filter + .next() + .await + .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); + *deadline = self.next_deadline(); + return resp; + } + } + warn!(target: "node::filter", "No filter found for {}", id); + ResponseResult::error(RpcError { + code: ErrorCode::ServerError(-32000), + message: "filter not found".into(), + data: None, + }) + } +} + +impl Default for Filters { fn default() -> Self { Self { active_filters: Arc::new(Default::default()), @@ -113,14 +138,26 @@ fn new_id() -> String { } /// Represents a poll based filter -#[derive(Debug)] -pub enum EthFilter { - Logs(Box), +pub enum EthFilter { + Logs(Box>), Blocks(NewBlockNotifications), PendingTransactions(Receiver), } -impl Stream for EthFilter { +impl std::fmt::Debug for EthFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Logs(_) => f.debug_tuple("Logs").finish(), + Self::Blocks(_) => f.debug_tuple("Blocks").finish(), + Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(), + } + } +} + +impl Stream for EthFilter +where + N::ReceiptEnvelope: TxReceipt, +{ type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -146,21 +183,29 @@ impl Stream for EthFilter { } /// Listens for new blocks and matching logs emitted in that block -#[derive(Debug)] -pub struct LogsFilter { +pub struct LogsFilter { /// listener for new blocks pub blocks: NewBlockNotifications, /// accessor for block storage - pub storage: StorageInfo, + pub storage: StorageInfo, /// matcher with all provided filter params pub filter: FilteredParams, /// existing logs that matched the filter when the listener was installed /// - /// They'll be returned on the first pill + /// They'll be returned on the first poll pub historic: Option>, } -impl LogsFilter { +impl std::fmt::Debug for LogsFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive() + } +} + +impl LogsFilter +where + N::ReceiptEnvelope: TxReceipt, +{ /// Returns all the logs since the last time this filter was polled pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs deleted file mode 100644 index f5e063aafc625..0000000000000 --- a/crates/anvil/src/hardfork.rs +++ /dev/null @@ -1,124 +0,0 @@ -use alloy_hardforks::EthereumHardfork; -use alloy_op_hardforks::OpHardfork::{self}; -use alloy_rpc_types::BlockNumberOrTag; - -use op_revm::OpSpecId; -use revm::primitives::hardfork::SpecId; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ChainHardfork { - Ethereum(EthereumHardfork), - Optimism(OpHardfork), -} - -impl From for ChainHardfork { - fn from(value: EthereumHardfork) -> Self { - Self::Ethereum(value) - } -} - -impl From for ChainHardfork { - fn from(value: OpHardfork) -> Self { - Self::Optimism(value) - } -} - -impl From for SpecId { - fn from(fork: ChainHardfork) -> Self { - match fork { - ChainHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), - ChainHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), - } - } -} - -/// Map an EthereumHardfork enum into its corresponding SpecId. -pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { - match hardfork { - EthereumHardfork::Frontier => SpecId::FRONTIER, - EthereumHardfork::Homestead => SpecId::HOMESTEAD, - EthereumHardfork::Dao => SpecId::DAO_FORK, - EthereumHardfork::Tangerine => SpecId::TANGERINE, - EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - EthereumHardfork::Byzantium => SpecId::BYZANTIUM, - EthereumHardfork::Constantinople => SpecId::CONSTANTINOPLE, - EthereumHardfork::Petersburg => SpecId::PETERSBURG, - EthereumHardfork::Istanbul => SpecId::ISTANBUL, - EthereumHardfork::MuirGlacier => SpecId::MUIR_GLACIER, - EthereumHardfork::Berlin => SpecId::BERLIN, - EthereumHardfork::London => SpecId::LONDON, - EthereumHardfork::ArrowGlacier => SpecId::ARROW_GLACIER, - EthereumHardfork::GrayGlacier => SpecId::GRAY_GLACIER, - EthereumHardfork::Paris => SpecId::MERGE, - EthereumHardfork::Shanghai => SpecId::SHANGHAI, - EthereumHardfork::Cancun => SpecId::CANCUN, - EthereumHardfork::Prague => SpecId::PRAGUE, - EthereumHardfork::Osaka => SpecId::OSAKA, - } -} - -/// Map an OptimismHardfork enum into its corresponding OpSpecId. -pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { - match hardfork { - OpHardfork::Bedrock => OpSpecId::BEDROCK, - OpHardfork::Regolith => OpSpecId::REGOLITH, - OpHardfork::Canyon => OpSpecId::CANYON, - OpHardfork::Ecotone => OpSpecId::ECOTONE, - OpHardfork::Fjord => OpSpecId::FJORD, - OpHardfork::Granite => OpSpecId::GRANITE, - OpHardfork::Holocene => OpSpecId::HOLOCENE, - OpHardfork::Isthmus => OpSpecId::ISTHMUS, - OpHardfork::Interop => OpSpecId::INTEROP, - } -} - -/// Convert a `BlockNumberOrTag` into an `EthereumHardfork`. -pub fn ethereum_hardfork_from_block_tag(block: impl Into) -> EthereumHardfork { - let num = match block.into() { - BlockNumberOrTag::Earliest => 0, - BlockNumberOrTag::Number(num) => num, - _ => u64::MAX, - }; - - EthereumHardfork::from_mainnet_block_number(num) -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_hardforks::ethereum::mainnet::*; - #[allow(unused_imports)] - use alloy_rpc_types::BlockNumberOrTag; - - #[test] - fn test_ethereum_spec_id_mapping() { - assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER); - assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD); - - // Test latest hardforks - assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN); - assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE); - } - - #[test] - fn test_optimism_spec_id_mapping() { - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - - // Test latest hardforks - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); - assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); - } - - #[test] - fn test_hardfork_from_block_tag_numbers() { - assert_eq!( - ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1), - EthereumHardfork::Frontier - ); - assert_eq!( - ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1), - EthereumHardfork::London - ); - } -} diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 5ee4fead559b2..435918b3c6d83 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -1,33 +1,40 @@ //! Anvil is a fast local Ethereum development node. -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; use crate::{ + error::{NodeError, NodeResult}, eth::{ + EthApi, backend::{info::StorageInfo, mem}, fees::{FeeHistoryService, FeeManager}, miner::{Miner, MiningMode}, pool::Pool, sign::{DevSigner, Signer as EthSigner}, - EthApi, }, filter::Filters, logging::{LoggingManager, NodeLogLayer}, - server::error::{NodeError, NodeResult}, service::NodeService, shutdown::Signal, tasks::TaskManager, }; +use alloy_eips::eip7840::BlobParams; use alloy_primitives::{Address, U256}; use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; -use eyre::Result; +use eyre::{Result, WrapErr}; use foundry_common::provider::{ProviderBuilder, RetryProvider}; +pub use foundry_evm::hardfork::EthereumHardfork; +use foundry_primitives::FoundryNetwork; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; +use revm::primitives::hardfork::SpecId; use server::try_spawn_ipc; use std::{ - future::Future, net::SocketAddr, pin::Pin, sync::Arc, @@ -37,22 +44,22 @@ use tokio::{ runtime::Handle, task::{JoinError, JoinHandle}, }; +use tracing_subscriber::EnvFilter; /// contains the background service that drives the node mod service; mod config; pub use config::{ - AccountGenerator, ForkChoice, NodeConfig, CHAIN_ID, DEFAULT_GAS_LIMIT, VERSION_MESSAGE, + AccountGenerator, CHAIN_ID, DEFAULT_GAS_LIMIT, ForkChoice, NodeConfig, VERSION_MESSAGE, }; -mod hardfork; -pub use alloy_hardforks::EthereumHardfork; +mod error; /// ethereum related implementations pub mod eth; /// Evm related abstractions mod evm; -pub use evm::{inject_precompiles, PrecompileFactory}; +pub use evm::PrecompileFactory; /// support for polling filters pub mod filter; @@ -108,7 +115,7 @@ extern crate tracing; /// # Ok(()) /// # } /// ``` -pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { +pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { try_spawn(config).await.expect("failed to spawn node") } @@ -132,11 +139,17 @@ pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { /// # Ok(()) /// # } /// ``` -pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { +pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); - let backend = Arc::new(config.setup().await?); + let backend = config.setup::().await?; + + if let Some(state) = config.init_state.clone() { + backend.load_state(state).await.wrap_err("failed to load init state")?; + } + + let backend = Arc::new(backend); if config.enable_auto_impersonate { backend.auto_impersonate_account(true); @@ -181,7 +194,8 @@ pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { _ => Miner::new(mode), }; - let dev_signer: Box = Box::new(DevSigner::new(signer_accounts)); + let dev_signer: Box> = + Box::new(DevSigner::new(signer_accounts)); let mut signers = vec![dev_signer]; if let Some(genesis) = genesis { let genesis_signers = genesis @@ -197,6 +211,11 @@ pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let fee_history_cache = Arc::new(Mutex::new(Default::default())); let fee_history_service = FeeHistoryService::new( + match backend.spec_id() { + SpecId::OSAKA => BlobParams::osaka(), + SpecId::PRAGUE => BlobParams::prague(), + _ => BlobParams::cancun(), + }, backend.new_block_notifications(), Arc::clone(&fee_history_cache), StorageInfo::new(Arc::clone(&backend)), @@ -272,9 +291,9 @@ pub struct NodeHandle { /// The address of the running rpc server. addresses: Vec, /// Join handle for the Node Service. - pub node_service: JoinHandle>, + node_service: JoinHandle>, /// Join handles (one per socket) for the Anvil server. - pub servers: Vec>>, + servers: Vec>>, /// The future that joins the ipc server, if any. ipc_task: Option, /// A signal that fires the shutdown, fired on drop. @@ -289,12 +308,19 @@ impl Drop for NodeHandle { if let Some(signal) = self._signal.take() { let _ = signal.fire(); } + self.node_service.abort(); + for server in &self.servers { + server.abort(); + } + if let Some(ipc_task) = &self.ipc_task { + ipc_task.abort(); + } } } impl NodeHandle { /// The [NodeConfig] the node was launched with. - pub fn config(&self) -> &NodeConfig { + pub const fn config(&self) -> &NodeConfig { &self.config } @@ -371,7 +397,7 @@ impl NodeHandle { } /// Native token balance of every genesis account in the genesis block. - pub fn genesis_balance(&self) -> U256 { + pub const fn genesis_balance(&self) -> U256 { self.config.genesis_balance } @@ -381,14 +407,14 @@ impl NodeHandle { } /// Returns the shutdown signal. - pub fn shutdown_signal(&self) -> &Option { + pub const fn shutdown_signal(&self) -> &Option { &self._signal } /// Returns mutable access to the shutdown signal. /// /// This can be used to extract the Signal. - pub fn shutdown_signal_mut(&mut self) -> &mut Option { + pub const fn shutdown_signal_mut(&mut self) -> &mut Option { &mut self._signal } @@ -407,7 +433,7 @@ impl NodeHandle { /// /// # } /// ``` - pub fn task_manager(&self) -> &TaskManager { + pub const fn task_manager(&self) -> &TaskManager { &self.task_manager } } @@ -422,9 +448,8 @@ impl Future for NodeHandle { if let Some(mut ipc) = pin.ipc_task.take() { if let Poll::Ready(res) = ipc.poll_unpin(cx) { return Poll::Ready(res.map(|()| Ok(()))); - } else { - pin.ipc_task = Some(ipc); } + pin.ipc_task = Some(ipc); } // poll the node service task @@ -448,10 +473,23 @@ pub fn init_tracing() -> LoggingManager { use tracing_subscriber::prelude::*; let manager = LoggingManager::default(); - // check whether `RUST_LOG` is explicitly set - let _ = if std::env::var("RUST_LOG").is_ok() { - tracing_subscriber::Registry::default() - .with(tracing_subscriber::EnvFilter::from_default_env()) + + let _ = if let Ok(rust_log_val) = std::env::var("RUST_LOG") + && !rust_log_val.contains('=') + { + // Mutate the given filter to include `node` logs if it is not already present. + // This prevents the unexpected behaviour of not seeing any node logs if a RUST_LOG + // is already present that doesn't set it. + let rust_log_val = if rust_log_val.contains("node") { + rust_log_val + } else { + format!("{rust_log_val},node=info") + }; + + let env_filter: EnvFilter = + rust_log_val.parse().expect("failed to parse modified RUST_LOG"); + tracing_subscriber::registry() + .with(env_filter) .with(tracing_subscriber::fmt::layer()) .try_init() } else { diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index e738254cbbec5..31737aa4a3e07 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -2,8 +2,8 @@ use parking_lot::RwLock; use std::sync::Arc; -use tracing::{subscriber::Interest, Metadata}; -use tracing_subscriber::{layer::Context, Layer}; +use tracing::{Metadata, subscriber::Interest}; +use tracing_subscriber::{Layer, layer::Context}; /// The target that identifies the events intended to be logged to stdout pub(crate) const NODE_USER_LOG_TARGET: &str = "node::user"; @@ -22,7 +22,7 @@ pub struct NodeLogLayer { impl NodeLogLayer { /// Returns a new instance of this layer - pub fn new(state: LoggingManager) -> Self { + pub const fn new(state: LoggingManager) -> Self { Self { state } } } @@ -42,9 +42,9 @@ where } fn enabled(&self, metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool { - self.state.is_enabled() && - (metadata.target() == NODE_USER_LOG_TARGET || - metadata.target() == EVM_CONSOLE_LOG_TARGET) + self.state.is_enabled() + && (metadata.target() == NODE_USER_LOG_TARGET + || metadata.target() == EVM_CONSOLE_LOG_TARGET) } } diff --git a/crates/anvil/src/opts.rs b/crates/anvil/src/opts.rs index 10b40a5c97c9a..e5fb89b21a635 100644 --- a/crates/anvil/src/opts.rs +++ b/crates/anvil/src/opts.rs @@ -24,10 +24,6 @@ pub enum AnvilSubcommand { #[command(visible_alias = "com")] Completions { #[arg(value_enum)] - shell: clap_complete::Shell, + shell: foundry_cli::clap::Shell, }, - - /// Generate Fig autocompletion spec. - #[command(visible_alias = "fig")] - GenerateFigSpec, } diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index 69f7896c72f78..4ceb8be0bfc64 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -1,13 +1,14 @@ use crate::{ - eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, StorageInfo, + eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, }; -use alloy_network::AnyRpcTransaction; -use alloy_primitives::{TxHash, B256}; -use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log, Transaction}; -use anvil_core::eth::{block::Block, subscription::SubscriptionId, transaction::TypedReceipt}; +use alloy_consensus::{BlockHeader, TxReceipt}; +use alloy_network::{AnyRpcTransaction, Network}; +use alloy_primitives::{B256, TxHash}; +use alloy_rpc_types::{FilteredParams, Log, Transaction, pubsub::SubscriptionResult}; +use anvil_core::eth::{block::Block, subscription::SubscriptionId}; use anvil_rpc::{request::Version, response::ResponseResult}; -use futures::{channel::mpsc::Receiver, ready, Stream, StreamExt}; +use futures::{Stream, StreamExt, channel::mpsc::Receiver, ready}; use serde::Serialize; use std::{ collections::VecDeque, @@ -17,16 +18,27 @@ use std::{ use tokio::sync::mpsc::UnboundedReceiver; /// Listens for new blocks and matching logs emitted in that block -#[derive(Debug)] -pub struct LogsSubscription { +pub struct LogsSubscription { pub blocks: NewBlockNotifications, - pub storage: StorageInfo, + pub storage: StorageInfo, pub filter: FilteredParams, pub queued: VecDeque, pub id: SubscriptionId, } -impl LogsSubscription { +impl std::fmt::Debug for LogsSubscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LogsSubscription") + .field("filter", &self.filter) + .field("id", &self.id) + .finish_non_exhaustive() + } +} + +impl LogsSubscription +where + N::ReceiptEnvelope: TxReceipt, +{ fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if let Some(log) = self.queued.pop_front() { @@ -69,7 +81,7 @@ pub struct EthSubscriptionResponse { } impl EthSubscriptionResponse { - pub fn new(params: EthSubscriptionParams) -> Self { + pub const fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } } } @@ -83,15 +95,28 @@ pub struct EthSubscriptionParams { } /// Represents an ethereum Websocket subscription -#[derive(Debug)] -pub enum EthSubscription { - Logs(Box), - Header(NewBlockNotifications, StorageInfo, SubscriptionId), +pub enum EthSubscription { + Logs(Box>), + Header(NewBlockNotifications, StorageInfo, SubscriptionId), PendingTransactions(Receiver, SubscriptionId), FullPendingTransactions(UnboundedReceiver, SubscriptionId), } -impl EthSubscription { +impl std::fmt::Debug for EthSubscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Logs(_) => f.debug_tuple("Logs").finish(), + Self::Header(..) => f.debug_tuple("Header").finish(), + Self::PendingTransactions(..) => f.debug_tuple("PendingTransactions").finish(), + Self::FullPendingTransactions(..) => f.debug_tuple("FullPendingTransactions").finish(), + } + } +} + +impl EthSubscription +where + N::ReceiptEnvelope: TxReceipt, +{ fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { Self::Logs(listener) => listener.poll(cx), @@ -134,7 +159,10 @@ impl EthSubscription { } } -impl Stream for EthSubscription { +impl Stream for EthSubscription +where + N::ReceiptEnvelope: TxReceipt, +{ type Item = serde_json::Value; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -147,7 +175,10 @@ impl Stream for EthSubscription { } /// Returns all the logs that match the given filter -pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec { +pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec +where + R: TxReceipt, +{ /// Determines whether to add this log fn add_log( block_hash: B256, @@ -156,11 +187,11 @@ pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredP params: &FilteredParams, ) -> bool { if params.filter.is_some() { - let block_number = block.header.number; - if !params.filter_block_range(block_number) || - !params.filter_block_hash(block_hash) || - !params.filter_address(&l.address) || - !params.filter_topics(l.topics()) + let block_number = block.header.number(); + if !params.filter_block_range(block_number) + || !params.filter_block_hash(block_hash) + || !params.filter_address(&l.address) + || !params.filter_topics(l.topics()) { return false; } @@ -172,18 +203,18 @@ pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredP let mut logs = vec![]; let mut log_index: u32 = 0; for (receipt_index, receipt) in receipts.into_iter().enumerate() { - let transaction_hash = block.transactions[receipt_index].hash(); + let transaction_hash = block.body.transactions[receipt_index].hash(); for log in receipt.logs() { if add_log(block_hash, log, &block, filter) { logs.push(Log { inner: log.clone(), block_hash: Some(block_hash), - block_number: Some(block.header.number), + block_number: Some(block.header.number()), transaction_hash: Some(transaction_hash), transaction_index: Some(receipt_index as u64), log_index: Some(log_index as u64), removed: false, - block_timestamp: Some(block.header.timestamp), + block_timestamp: Some(block.header.timestamp()), }); } log_index += 1; diff --git a/crates/anvil/src/server/beacon/error.rs b/crates/anvil/src/server/beacon/error.rs new file mode 100644 index 0000000000000..0a08e870ad70e --- /dev/null +++ b/crates/anvil/src/server/beacon/error.rs @@ -0,0 +1,132 @@ +//! Beacon API error types + +use axum::{ + Json, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + fmt::{self, Display}, +}; + +/// Represents a Beacon API error response +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct BeaconError { + /// HTTP status code + #[serde(skip)] + pub status_code: u16, + /// Error code + pub code: BeaconErrorCode, + /// Error message + pub message: Cow<'static, str>, +} + +impl BeaconError { + /// Creates a new beacon error with the given code + pub fn new(code: BeaconErrorCode, message: impl Into>) -> Self { + let status_code = code.status_code(); + Self { status_code, code, message: message.into() } + } + + /// Helper function to create a 400 Bad Request error for invalid block ID + pub fn invalid_block_id(block_id: impl Display) -> Self { + Self::new(BeaconErrorCode::BadRequest, format!("Invalid block ID: {block_id}")) + } + + /// Helper function to create a 404 Not Found error for block not found + pub fn block_not_found() -> Self { + Self::new(BeaconErrorCode::NotFound, "Block not found") + } + + /// Helper function to create a 500 Internal Server Error + pub fn internal_error() -> Self { + Self::new(BeaconErrorCode::InternalError, "Internal server error") + } + + /// Helper function to create a 410 Gone error for deprecated endpoints + pub fn deprecated_endpoint_with_hint(hint: impl Display) -> Self { + Self::new(BeaconErrorCode::Gone, format!("This endpoint is deprecated. {hint}")) + } + + /// Converts to an Axum response + pub fn into_response(self) -> Response { + let status = + StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); + + ( + status, + Json(serde_json::json!({ + "code": self.code as u16, + "message": self.message, + })), + ) + .into_response() + } +} + +impl fmt::Display for BeaconError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.code.as_str(), self.message) + } +} + +impl std::error::Error for BeaconError {} + +impl IntoResponse for BeaconError { + fn into_response(self) -> Response { + Self::into_response(self) + } +} + +/// Beacon API error codes following the beacon chain specification +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u16)] +pub enum BeaconErrorCode { + BadRequest = 400, + NotFound = 404, + Gone = 410, + InternalError = 500, +} + +impl BeaconErrorCode { + /// Returns the HTTP status code for this error + pub const fn status_code(&self) -> u16 { + *self as u16 + } + + /// Returns a string representation of the error code + pub const fn as_str(&self) -> &'static str { + match self { + Self::BadRequest => "Bad Request", + Self::NotFound => "Not Found", + Self::Gone => "Gone", + Self::InternalError => "Internal Server Error", + } + } +} + +impl fmt::Display for BeaconErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_beacon_error_codes() { + assert_eq!(BeaconErrorCode::BadRequest.status_code(), 400); + assert_eq!(BeaconErrorCode::NotFound.status_code(), 404); + assert_eq!(BeaconErrorCode::InternalError.status_code(), 500); + } + + #[test] + fn test_beacon_error_display() { + let err = BeaconError::invalid_block_id("current"); + assert_eq!(err.to_string(), "Bad Request: Invalid block ID: current"); + } +} diff --git a/crates/anvil/src/server/beacon/handlers.rs b/crates/anvil/src/server/beacon/handlers.rs new file mode 100644 index 0000000000000..73d533fa606c3 --- /dev/null +++ b/crates/anvil/src/server/beacon/handlers.rs @@ -0,0 +1,162 @@ +use super::{error::BeaconError, utils::must_be_ssz}; +use crate::eth::EthApi; +use alloy_eips::BlockId; +use alloy_network::Network; +use alloy_primitives::{B256, aliases::B32}; +use alloy_rpc_types_beacon::{ + genesis::{GenesisData, GenesisResponse}, + sidecar::GetBlobsResponse, +}; +use axum::{ + Json, + extract::{Path, Query, State}, + http::HeaderMap, + response::{IntoResponse, Response}, +}; +use ssz::Encode; +use std::{collections::HashMap, str::FromStr as _}; + +/// Handles incoming Beacon API requests for blob sidecars +/// +/// This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead. +/// +/// GET /eth/v1/beacon/blob_sidecars/{block_id} +pub async fn handle_get_blob_sidecars( + State(_api): State>, + Path(_block_id): Path, + Query(_params): Query>, +) -> Response { + BeaconError::deprecated_endpoint_with_hint("Use `GET /eth/v1/beacon/blobs/{block_id}` instead.") + .into_response() +} + +/// Handles incoming Beacon API requests for blobs +/// +/// GET /eth/v1/beacon/blobs/{block_id} +pub async fn handle_get_blobs( + headers: HeaderMap, + State(api): State>, + Path(block_id): Path, + Query(versioned_hashes): Query>, +) -> Response { + // Parse block_id from path parameter + let Ok(block_id) = BlockId::from_str(&block_id) else { + return BeaconError::invalid_block_id(block_id).into_response(); + }; + + // Parse versioned hashes from query parameters + // Supports comma-separated format: ?versioned_hashes=0x...,0x... + let versioned_hashes: Vec = match versioned_hashes.get("versioned_hashes") { + Some(s) => { + let mut hashes = Vec::new(); + for hash in s.split(',') { + let hash = hash.trim(); + if hash.is_empty() { + continue; + } + match B256::from_str(hash) { + Ok(h) => hashes.push(h), + Err(_) => { + return BeaconError::new( + super::error::BeaconErrorCode::BadRequest, + format!("Invalid versioned hash: {hash}"), + ) + .into_response(); + } + } + } + hashes + } + None => Vec::new(), + }; + + // Get the blob sidecars using existing EthApi logic + match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) { + Ok(Some(blobs)) => { + if must_be_ssz(&headers) { + blobs.as_ssz_bytes().into_response() + } else { + Json(GetBlobsResponse { + execution_optimistic: false, + finalized: false, + data: blobs, + }) + .into_response() + } + } + Ok(None) => BeaconError::block_not_found().into_response(), + Err(_) => BeaconError::internal_error().into_response(), + } +} + +/// Handles incoming Beacon API requests for genesis details +/// +/// Only returns the `genesis_time`, other fields are set to zero. +/// +/// GET /eth/v1/beacon/genesis +pub async fn handle_get_genesis(State(api): State>) -> Response { + match api.anvil_get_genesis_time() { + Ok(genesis_time) => Json(GenesisResponse { + data: GenesisData { + genesis_time, + genesis_validators_root: B256::ZERO, + genesis_fork_version: B32::ZERO, + }, + }) + .into_response(), + Err(_) => BeaconError::internal_error().into_response(), + } +} +#[cfg(test)] +mod tests { + use super::*; + use axum::http::HeaderValue; + + fn header_map_with_accept(accept: &str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(axum::http::header::ACCEPT, HeaderValue::from_str(accept).unwrap()); + headers + } + + #[test] + fn test_must_be_ssz() { + let test_cases = vec![ + (None, false, "no Accept header"), + (Some("application/json"), false, "JSON only"), + (Some("application/octet-stream"), true, "octet-stream only"), + (Some("application/octet-stream;q=1.0,application/json;q=0.9"), true, "SSZ preferred"), + ( + Some("application/json;q=1.0,application/octet-stream;q=0.9"), + false, + "JSON preferred", + ), + (Some("application/octet-stream;q=0.5,application/json;q=0.5"), false, "equal quality"), + ( + Some("text/html;q=0.9, application/octet-stream;q=1.0, application/json;q=0.8"), + true, + "multiple types", + ), + ( + Some("application/octet-stream ; q=1.0 , application/json ; q=0.9"), + true, + "whitespace handling", + ), + (Some("application/octet-stream, application/json;q=0.9"), true, "default quality"), + ]; + + for (accept_header, expected, description) in test_cases { + let headers = match accept_header { + None => HeaderMap::new(), + Some(header) => header_map_with_accept(header), + }; + assert_eq!( + must_be_ssz(&headers), + expected, + "Test case '{}' failed: expected {}, got {}", + description, + expected, + !expected + ); + } + } +} diff --git a/crates/anvil/src/server/beacon/mod.rs b/crates/anvil/src/server/beacon/mod.rs new file mode 100644 index 0000000000000..f0138747c8b00 --- /dev/null +++ b/crates/anvil/src/server/beacon/mod.rs @@ -0,0 +1,22 @@ +//! Beacon Node REST API implementation for Anvil. + +use axum::{Router, routing::get}; + +use crate::eth::EthApi; +use alloy_network::Network; + +mod error; +mod handlers; +mod utils; + +/// Configures an [`axum::Router`] that handles Beacon REST API calls. +pub fn router(api: EthApi) -> Router { + Router::new() + .route( + "/eth/v1/beacon/blob_sidecars/{block_id}", + get(handlers::handle_get_blob_sidecars::), + ) + .route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs::)) + .route("/eth/v1/beacon/genesis", get(handlers::handle_get_genesis::)) + .with_state(api) +} diff --git a/crates/anvil/src/server/beacon/utils.rs b/crates/anvil/src/server/beacon/utils.rs new file mode 100644 index 0000000000000..946663e996a0d --- /dev/null +++ b/crates/anvil/src/server/beacon/utils.rs @@ -0,0 +1,39 @@ +use hyper::HeaderMap; + +/// Helper function to determine if the Accept header indicates a preference for SSZ (octet-stream) +/// over JSON. +pub fn must_be_ssz(headers: &HeaderMap) -> bool { + headers + .get(axum::http::header::ACCEPT) + .and_then(|v| v.to_str().ok()) + .map(|accept_str| { + let mut octet_stream_q = 0.0; + let mut json_q = 0.0; + + // Parse each media type in the Accept header + for media_type in accept_str.split(',') { + let media_type = media_type.trim(); + let quality = media_type + .split(';') + .find_map(|param| { + let param = param.trim(); + if let Some(q) = param.strip_prefix("q=") { + q.parse::().ok() + } else { + None + } + }) + .unwrap_or(1.0); // Default quality factor is 1.0 + + if media_type.starts_with("application/octet-stream") { + octet_stream_q = quality; + } else if media_type.starts_with("application/json") { + json_q = quality; + } + } + + // Prefer octet-stream if it has higher quality factor + octet_stream_q > json_q + }) + .unwrap_or(false) +} diff --git a/crates/anvil/src/server/mod.rs b/crates/anvil/src/server/mod.rs index c488bcdc15ca3..1f6d78d2a9466 100644 --- a/crates/anvil/src/server/mod.rs +++ b/crates/anvil/src/server/mod.rs @@ -1,15 +1,17 @@ -//! Contains the code to launch an Ethereum RPC server. +//! This module provides the infrastructure to launch an Ethereum JSON-RPC server +//! (via HTTP, WebSocket, and IPC) and Beacon Node REST API. use crate::{EthApi, IpcTask}; -use anvil_server::{ipc::IpcEndpoint, ServerConfig}; +use anvil_server::{ServerConfig, ipc::IpcEndpoint}; use axum::Router; +use foundry_primitives::FoundryNetwork; use futures::StreamExt; -use handler::{HttpEthRpcHandler, PubSubEthRpcHandler}; -use std::{future::Future, io, net::SocketAddr, pin::pin}; +use rpc_handlers::{HttpEthRpcHandler, PubSubEthRpcHandler}; +use std::{io, net::SocketAddr, pin::pin}; use tokio::net::TcpListener; -pub mod error; -mod handler; +mod beacon; +mod rpc_handlers; /// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. /// @@ -17,7 +19,7 @@ mod handler; /// future that runs it. pub async fn serve( addr: SocketAddr, - api: EthApi, + api: EthApi, config: ServerConfig, ) -> io::Result>> { let tcp_listener = TcpListener::bind(addr).await?; @@ -27,17 +29,26 @@ pub async fn serve( /// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. pub async fn serve_on( tcp_listener: TcpListener, - api: EthApi, + api: EthApi, config: ServerConfig, ) -> io::Result<()> { axum::serve(tcp_listener, router(api, config).into_make_service()).await } -/// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. -pub fn router(api: EthApi, config: ServerConfig) -> Router { +/// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS, +/// and Beacon REST API calls. +pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); - let ws = PubSubEthRpcHandler::new(api); - anvil_server::http_ws_router(config, http, ws) + let ws = PubSubEthRpcHandler::new(api.clone()); + + // JSON-RPC router + let rpc_router = anvil_server::http_ws_router(config, http, ws); + + // Beacon REST API router + let beacon_router = beacon::router(api); + + // Merge the routers + rpc_router.merge(beacon_router) } /// Launches an ipc server at the given path in a new task @@ -46,12 +57,12 @@ pub fn router(api: EthApi, config: ServerConfig) -> Router { /// /// Panics if setting up the IPC connection was unsuccessful. #[track_caller] -pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { +pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { try_spawn_ipc(api, path).expect("failed to establish ipc connection") } /// Launches an ipc server at the given path in a new task. -pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { +pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { let handler = PubSubEthRpcHandler::new(api); let ipc = IpcEndpoint::new(handler, path); let incoming = ipc.incoming()?; diff --git a/crates/anvil/src/server/handler.rs b/crates/anvil/src/server/rpc_handlers.rs similarity index 88% rename from crates/anvil/src/server/handler.rs rename to crates/anvil/src/server/rpc_handlers.rs index 3c474e9ee4b23..c531ba2705713 100644 --- a/crates/anvil/src/server/handler.rs +++ b/crates/anvil/src/server/rpc_handlers.rs @@ -1,27 +1,28 @@ //! Contains RPC handlers use crate::{ + EthApi, eth::error::to_rpc_result, pubsub::{EthSubscription, LogsSubscription}, - EthApi, }; use alloy_rpc_types::{ - pubsub::{Params, SubscriptionKind}, FilteredParams, + pubsub::{Params, SubscriptionKind}, }; -use anvil_core::eth::{subscription::SubscriptionId, EthPubSub, EthRequest, EthRpcCall}; +use anvil_core::eth::{EthPubSub, EthRequest, EthRpcCall, subscription::SubscriptionId}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; +use foundry_primitives::FoundryNetwork; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] pub struct HttpEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub const fn new(api: EthApi) -> Self { Self { api } } } @@ -39,12 +40,12 @@ impl RpcHandler for HttpEthRpcHandler { #[derive(Clone)] pub struct PubSubEthRpcHandler { /// Access to the node - api: EthApi, + api: EthApi, } impl PubSubEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` - pub fn new(api: EthApi) -> Self { + pub const fn new(api: EthApi) -> Self { Self { api } } @@ -63,6 +64,7 @@ impl PubSubEthRpcHandler { Params::None => None, Params::Logs(filter) => Some(filter.clone()), Params::Bool(_) => None, + Params::TransactionReceipts(_) => None, }; let params = FilteredParams::new(filter.map(|b| *b)); @@ -71,7 +73,7 @@ impl PubSubEthRpcHandler { if raw_params.is_bool() { return ResponseResult::Error(RpcError::invalid_params( "Expected params for logs subscription", - )) + )); } trace!(target: "rpc::ws", "received logs subscription {:?}", params); @@ -107,12 +109,15 @@ impl PubSubEthRpcHandler { _ => { return ResponseResult::Error(RpcError::invalid_params( "Expected boolean parameter for newPendingTransactions", - )) + )); } } } + SubscriptionKind::TransactionReceipts => { + return RpcError::internal_error_with("Not implemented").into(); + } SubscriptionKind::Syncing => { - return RpcError::internal_error_with("Not implemented").into() + return RpcError::internal_error_with("Not implemented").into(); } }; @@ -129,7 +134,7 @@ impl PubSubEthRpcHandler { impl PubSubRpcHandler for PubSubEthRpcHandler { type Request = EthRpcCall; type SubscriptionId = SubscriptionId; - type Subscription = EthSubscription; + type Subscription = EthSubscription; async fn on_request(&self, request: Self::Request, cx: PubSubContext) -> ResponseResult { trace!(target: "rpc", "received pubsub request {:?}", request); diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index f1519434e1553..88a47b55a1418 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -1,19 +1,22 @@ //! background service use crate::{ + NodeResult, eth::{ + backend::validate::TransactionValidator, fees::FeeHistoryService, miner::Miner, - pool::{transactions::PoolTransaction, Pool}, + pool::{Pool, transactions::PoolTransaction}, }, filter::Filters, - mem::{storage::MinedBlockOutcome, Backend}, - NodeResult, + mem::{Backend, storage::MinedBlockOutcome}, }; +use alloy_consensus::TxReceipt; +use alloy_network::Network; +use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; use futures::{FutureExt, Stream, StreamExt}; use std::{ collections::VecDeque, - future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -26,28 +29,35 @@ use tokio::{task::JoinHandle, time::Interval}; /// transactions for the next block, then those transactions are handed off to the backend to /// construct a new block, if all transactions were successfully included in a new block they get /// purged from the `Pool`. -pub struct NodeService { +pub struct NodeService +where + N::ReceiptEnvelope: TxReceipt, +{ /// The pool that holds all transactions. - pool: Arc, + pool: Arc>, /// Creates new blocks. - block_producer: BlockProducer, + block_producer: BlockProducer, /// The miner responsible to select transactions from the `pool`. - miner: Miner, + miner: Miner, /// Maintenance task for fee history related tasks. - fee_history: FeeHistoryService, + fee_history: FeeHistoryService, /// Tracks all active filters - filters: Filters, + filters: Filters, /// The interval at which to check for filters that need to be evicted filter_eviction_interval: Interval, } -impl NodeService { +impl NodeService +where + Backend: TransactionValidator, + N: Network, +{ pub fn new( - pool: Arc, - backend: Arc, - miner: Miner, - fee_history: FeeHistoryService, - filters: Filters, + pool: Arc>, + backend: Arc>, + miner: Miner, + fee_history: FeeHistoryService, + filters: Filters, ) -> Self { let start = tokio::time::Instant::now() + filters.keep_alive(); let filter_eviction_interval = tokio::time::interval_at(start, filters.keep_alive()); @@ -62,7 +72,11 @@ impl NodeService { } } -impl Future for NodeService { +impl Future for NodeService +where + Backend: TransactionValidator, + N: Network, +{ type Output = NodeResult<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -83,7 +97,7 @@ impl Future for NodeService { pin.block_producer.queued.push_back(transactions); } else { // no progress made - break + break; } } @@ -101,25 +115,35 @@ impl Future for NodeService { } } +type MiningResult = (MinedBlockOutcome<::TxEnvelope>, Arc>); + /// A type that exclusively mines one block at a time #[must_use = "streams do nothing unless polled"] -struct BlockProducer { +struct BlockProducer { /// Holds the backend if no block is being mined - idle_backend: Option>, + idle_backend: Option>>, /// Single active future that mines a new block - block_mining: Option)>>, + block_mining: Option>>, /// backlog of sets of transactions ready to be mined - queued: VecDeque>>, + queued: VecDeque>>>, } -impl BlockProducer { - fn new(backend: Arc) -> Self { +impl BlockProducer +where + Backend: TransactionValidator, + N: Network, +{ + fn new(backend: Arc>) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } } } -impl Stream for BlockProducer { - type Item = MinedBlockOutcome; +impl Stream for BlockProducer +where + Backend: TransactionValidator + Send + Sync + 'static, + N: Network + 'static, +{ + type Item = MinedBlockOutcome; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); @@ -154,10 +178,9 @@ impl Stream for BlockProducer { Err(err) => { panic!("miner task failed: {err}"); } - } - } else { - pin.block_mining = Some(mining) + }; } + pin.block_mining = Some(mining) } Poll::Pending diff --git a/crates/anvil/src/shutdown.rs b/crates/anvil/src/shutdown.rs index 83fbb80f8e7a2..b91fdbd2fcd67 100644 --- a/crates/anvil/src/shutdown.rs +++ b/crates/anvil/src/shutdown.rs @@ -1,12 +1,11 @@ //! Helper for shutdown signals use futures::{ + FutureExt, channel::oneshot, future::{FusedFuture, Shared}, - FutureExt, }; use std::{ - future::Future, pin::Pin, task::{Context, Poll}, }; diff --git a/crates/anvil/src/tasks/block_listener.rs b/crates/anvil/src/tasks/block_listener.rs index 0b9388224ca06..073368a8afdb9 100644 --- a/crates/anvil/src/tasks/block_listener.rs +++ b/crates/anvil/src/tasks/block_listener.rs @@ -3,12 +3,11 @@ use crate::shutdown::Shutdown; use futures::{FutureExt, Stream, StreamExt}; use std::{ - future::Future, pin::Pin, task::{Context, Poll}, }; -/// A Future that will execute a given `task` for each new block that +/// A Future that will execute a given `task` for each new block that arrives on the stream. pub struct BlockListener { stream: St, task_factory: F, @@ -21,7 +20,7 @@ where St: Stream, F: Fn(::Item) -> Fut, { - pub fn new(on_shutdown: Shutdown, block_stream: St, task_factory: F) -> Self { + pub const fn new(on_shutdown: Shutdown, block_stream: St, task_factory: F) -> Self { Self { stream: block_stream, task_factory, task: None, on_shutdown } } } @@ -38,7 +37,7 @@ where let pin = self.get_mut(); if pin.on_shutdown.poll_unpin(cx).is_ready() { - return Poll::Ready(()) + return Poll::Ready(()); } let mut block = None; @@ -46,7 +45,7 @@ where while let Poll::Ready(maybe_block) = pin.stream.poll_next_unpin(cx) { if maybe_block.is_none() { // stream complete - return Poll::Ready(()) + return Poll::Ready(()); } block = maybe_block; } @@ -55,10 +54,10 @@ where pin.task = Some(Box::pin((pin.task_factory)(block))); } - if let Some(mut task) = pin.task.take() { - if task.poll_unpin(cx).is_pending() { - pin.task = Some(task); - } + if let Some(mut task) = pin.task.take() + && task.poll_unpin(cx).is_pending() + { + pin.task = Some(task); } Poll::Pending } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 763050e023b66..10def5d64c04e 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -2,13 +2,15 @@ #![allow(rustdoc::private_doc_tests)] -use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; -use alloy_network::{AnyHeader, AnyNetwork}; +use crate::{EthApi, shutdown::Shutdown, tasks::block_listener::BlockListener}; +use alloy_consensus::BlockHeader; +use alloy_network::{BlockResponse, Network}; use alloy_primitives::B256; use alloy_provider::Provider; use alloy_rpc_types::anvil::Forking; +use foundry_primitives::FoundryNetwork; use futures::StreamExt; -use std::{fmt, future::Future}; +use std::fmt; use tokio::{runtime::Handle, task::JoinHandle}; pub mod block_listener; @@ -24,7 +26,7 @@ pub struct TaskManager { impl TaskManager { /// Creates a new instance of the task manager - pub fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { + pub const fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { Self { tokio_handle, on_shutdown } } @@ -38,12 +40,17 @@ impl TaskManager { self.tokio_handle.spawn(task) } - /// Spawns the blocking task. - pub fn spawn_blocking(&self, task: impl Future + Send + 'static) { + /// Spawns the blocking task and returns a handle to it. + /// + /// Returning the `JoinHandle` allows callers to cancel the task or await its completion. + pub fn spawn_blocking( + &self, + task: impl Future + Send + 'static, + ) -> JoinHandle<()> { let handle = self.tokio_handle.clone(); self.tokio_handle.spawn_blocking(move || { handle.block_on(task); - }); + }) } /// Spawns a new task that listens for new blocks and resets the forked provider for every new @@ -52,20 +59,21 @@ impl TaskManager { /// ``` /// use alloy_network::Ethereum; /// use alloy_provider::RootProvider; - /// use anvil::{spawn, NodeConfig}; + /// use anvil::{NodeConfig, spawn}; /// /// # async fn t() { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// - /// let provider = RootProvider::connect_builtin(endpoint).await.unwrap(); + /// let provider = RootProvider::connect(endpoint).await.unwrap(); /// - /// handle.task_manager().spawn_reset_on_new_polled_blocks(provider, api); + /// handle.task_manager().spawn_reset_on_new_polled_blocks::(provider, api); /// # } /// ``` - pub fn spawn_reset_on_new_polled_blocks

(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) where - P: Provider + Clone + Unpin + 'static, + N: Network, + P: Provider + Clone + Unpin + 'static, { self.spawn_block_poll_listener(provider.clone(), move |hash| { let provider = provider.clone(); @@ -75,7 +83,7 @@ impl TaskManager { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: Some(block.header.number), + block_number: Some(block.header().number()), })) .await; } @@ -86,9 +94,10 @@ impl TaskManager { /// Spawns a new [`BlockListener`] task that listens for new blocks (poll-based) See also /// [`Provider::watch_blocks`] and executes the future the `task_factory` returns for the new /// block hash - pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) + pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) where - P: Provider + 'static, + N: Network, + P: Provider + 'static, F: Fn(B256) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { @@ -110,28 +119,29 @@ impl TaskManager { /// ``` /// use alloy_network::Ethereum; /// use alloy_provider::RootProvider; - /// use anvil::{spawn, NodeConfig}; + /// use anvil::{NodeConfig, spawn}; /// /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// - /// let provider = RootProvider::connect_builtin("ws://...").await.unwrap(); + /// let provider = RootProvider::connect("ws://...").await.unwrap(); /// - /// handle.task_manager().spawn_reset_on_subscribed_blocks(provider, api); + /// handle.task_manager().spawn_reset_on_subscribed_blocks::(provider, api); /// /// # } /// ``` - pub fn spawn_reset_on_subscribed_blocks

(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) where - P: Provider + 'static, + N: Network, + P: Provider + 'static, { - self.spawn_block_subscription(provider, move |header| { + self.spawn_block_subscription(provider, move |header: N::HeaderResponse| { let api = api.clone(); async move { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: Some(header.number), + block_number: Some(header.number()), })) .await; } @@ -141,10 +151,11 @@ impl TaskManager { /// Spawns a new [`BlockListener`] task that listens for new blocks (via subscription) See also /// [`Provider::subscribe_blocks()`] and executes the future the `task_factory` returns for the /// new block hash - pub fn spawn_block_subscription(&self, provider: P, task_factory: F) + pub fn spawn_block_subscription(&self, provider: P, task_factory: F) where - P: Provider + 'static, - F: Fn(alloy_rpc_types::Header) -> Fut + Unpin + Send + Sync + 'static, + N: Network, + P: Provider + 'static, + F: Fn(N::HeaderResponse) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); diff --git a/crates/anvil/test-data/state-dump-legacy-stress.json b/crates/anvil/test-data/state-dump-legacy-stress.json index b7a18c94cad40..fa51109c2fffc 100644 --- a/crates/anvil/test-data/state-dump-legacy-stress.json +++ b/crates/anvil/test-data/state-dump-legacy-stress.json @@ -1 +1 @@ -{"block":{"number":5,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1722941643,"gas_limit":30000000,"basefee":316710010,"difficulty":"0x0","prevrandao":"0xe7ef87fc7c2090741a6749a087e4ca8092cb4d07136008799e4ebeac3b69e34a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0x1088aa62285a00","code":"0x","storage":{}},"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd":{"nonce":1,"balance":"0x0","code":"0x6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x19ba1fac55eea44d12a01372a8eb0c2ebbf9ca21":{"nonce":1,"balance":"0x21e19df7c2963f0ac6b","code":"0x","storage":{}},"0x19c6ab860dbe2bc433574193a4409770a8748bf6":{"nonce":1,"balance":"0x21e19df8da6b7bdc410","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x40567ec443c1d1872af5155755ac3803cc3fe61e":{"nonce":1,"balance":"0x21e19da82562f921b40","code":"0x","storage":{}},"0x47d08dad17ccb558b3ea74b1a0e73a9cc804a9dc":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","storage":{"0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0x0"}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":2,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x8138ef7cf908021d117e542120b7a39065016107":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","storage":{}},"0x83a0444b93927c3afcbe46e522280390f748e171":{"nonce":1,"balance":"0x0","code":"0x6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c63430008110033","storage":{"0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b":"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xc67e2bd3108604cf0168c0e5ef9cd6d78b9bb14b":{"nonce":1,"balance":"0x21e19c6edb7e2445f20","code":"0x","storage":{}},"0xeb045d78d273107348b0300c01d29b7552d622ab":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e08b86820a43ea","code":"0x","storage":{}}},"best_block_number":5,"blocks":[{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xcd346446ed010523161f40a5f2b512def549bfb79e165b4354488738416481f2","transactionsRoot":"0xb3a4689832e0b599260ae70362ffcf224b60571b35ff8836904a3d81e2675d66","receiptsRoot":"0x2d13fdc120ab90536fed583939de7fb68b64926a306c1f629593ca9c2c93b198","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x3ea90d","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x2e0b6260","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354"}}],"ommers":[]},{"header":{"parentHash":"0x026ae0c6ae91f186a9befa1ac8be30eea35e30e77de51a731085221e5cd39209","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x6e4969a136061ca7a390d12830d47a151585325a8d396819fb2b958ff85e9f8f","receiptsRoot":"0xc3e81df67d3e2a6c8345a954ef250cfcc41abcc2292a5aa263071124533fc9ad","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x3c0f6","timestamp":"0x66b200ce","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x18993a68","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328"}}],"ommers":[]},{"header":{"parentHash":"0x3d22100ac0ee8d5cde334f7f926191a861b0648971ebc179547df28a0224c6d0","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9511d4711e5c30a72b0bff38a261daa75dcc5ba8b772d970a5c742244b4c861b","transactionsRoot":"0xba5fff578d3d6c2cd63acbe9bca353eaa6fe22a5c408956eff49106e0a96c507","receiptsRoot":"0xbae111f01cb07677e3a8c5031546138407c01bc964d3493d732dc4edf47d36d3","logsBloom":"0x00000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000020000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000001000000000000000000000400000001000010000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x5","gasLimit":"0x1c9c380","gasUsed":"0xcae7","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x12e09c7a","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xcc4d","maxFeePerGas":"0x557e5ec4","maxPriorityFeePerGas":"0x3b9aca00","to":"0x83a0444b93927c3afcbe46e522280390f748e171","value":"0x0","accessList":[],"input":"0x3659cfe6000000000000000000000000108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xf88e7b19ee347145c257e0cf7ac4ecc2bae83ca79d7edaa231e71d3213aeb151"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9c8eaf493f8b4edce2ba1647343eadcc0989cf461e712c0a6253ff2ca1842bb7","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xdd07c07470e1deff3749831f0f1ad8d4b6e35505e83b3c6ea14181716197cd8a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x24a1ab52","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200c9","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xf6930be4847cac5017bbcbec2756eed19f36b4196526a98a88e311c296e3a9be","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cc","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x200d75e8","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x4","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1592fbf9","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x149d41e3b89d8324cef3feff98ef308e97bafe8745cc8461c60172bc7d4c44ba","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x0b44110186e52ff0ceb6b0776ca2992c94144a4ed712eef65ea038260ef0fcc7","receiptsRoot":"0xc2823b8eb4730d9f2657137cc2ddc2c4f22ab68e0ab826236cf6a1551ca2b3a5","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0xe61f9","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xe94d1","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060008061002661006d60201b61081b1760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610141565b60008060405160200161007f90610121565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b600061010b60238361009e565b9150610116826100af565b604082019050919050565b6000602082019050818103600083015261013a816100fe565b9050919050565b611000806101506000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x4feae6769d748b4f0f7c9bf21d782236c88f13906789a3ec602961296e4c3e43"}}],"ommers":[]},{"header":{"parentHash":"0xb3535af5103fd1c2bbd6dc7ff23f0799037a6542c231ebcb85abd776560fa512","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x23d74fb99ff6e42cbb5c33f92b078e37be6af2b6092459b103ff7059a6517ebc","transactionsRoot":"0x9eab45eca206fe11c107ea985c7d02fcfa442836aea3e04ba11dc4df587d5aa6","receiptsRoot":"0xe25abcfa973db8c55f73292137c626430de130a382ad4466337fefb0f7c8fde0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x3ce3f","timestamp":"0x66b200cd","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1c0bc72b","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3d8a8","maxFeePerGas":"0x6211577c","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060405161068538038061068583398181016040528101906100329190610275565b818181600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361009b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6100ae8161019d60201b61004f1760201c565b6100ef57806040517f8a8b41ec0000000000000000000000000000000000000000000000000000000081526004016100e691906102c4565b60405180910390fd5b806100fe6101b060201b60201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050806101536101e160201b6100621760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050610414565b600080823b905060008111915050919050565b6000806040516020016101c290610362565b6040516020818303038152906040528051906020012090508091505090565b6000806040516020016101f3906103f4565b6040516020818303038152906040528051906020012090508091505090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024282610217565b9050919050565b61025281610237565b811461025d57600080fd5b50565b60008151905061026f81610249565b92915050565b6000806040838503121561028c5761028b610212565b5b600061029a85828601610260565b92505060206102ab85828601610260565b9150509250929050565b6102be81610237565b82525050565b60006020820190506102d960008301846102b5565b92915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b600061034c6021836102df565b9150610357826102f0565b604082019050919050565b6000602082019050818103600083015261037b8161033f565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006103de6023836102df565b91506103e982610382565b604082019050919050565b6000602082019050818103600083015261040d816103d1565b9050919050565b610262806104236000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c6343000811003300000000000000000000000047d08dad17ccb558b3ea74b1a0e73a9cc804a9dc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xb6794d5c7abed6f91d447e8efb72ef2580595a6d7c8dee57ba1dbb330970146a"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x29dd5614","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]}]} \ No newline at end of file +{"block":{"number":5,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1722941643,"gas_limit":30000000,"basefee":316710010,"difficulty":"0x0","prevrandao":"0xe7ef87fc7c2090741a6749a087e4ca8092cb4d07136008799e4ebeac3b69e34a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0x1088aa62285a00","code":"0x","storage":{}},"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd":{"nonce":1,"balance":"0x0","code":"0x6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x19ba1fac55eea44d12a01372a8eb0c2ebbf9ca21":{"nonce":1,"balance":"0x21e19df7c2963f0ac6b","code":"0x","storage":{}},"0x19c6ab860dbe2bc433574193a4409770a8748bf6":{"nonce":1,"balance":"0x21e19df8da6b7bdc410","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x40567ec443c1d1872af5155755ac3803cc3fe61e":{"nonce":1,"balance":"0x21e19da82562f921b40","code":"0x","storage":{}},"0x47d08dad17ccb558b3ea74b1a0e73a9cc804a9dc":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","storage":{"0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0x0"}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":2,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x8138ef7cf908021d117e542120b7a39065016107":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","storage":{}},"0x83a0444b93927c3afcbe46e522280390f748e171":{"nonce":1,"balance":"0x0","code":"0x6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c63430008110033","storage":{"0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b":"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xc67e2bd3108604cf0168c0e5ef9cd6d78b9bb14b":{"nonce":1,"balance":"0x21e19c6edb7e2445f20","code":"0x","storage":{}},"0xeb045d78d273107348b0300c01d29b7552d622ab":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e08b86820a43ea","code":"0x","storage":{}}},"best_block_number":5,"blocks":[{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xcd346446ed010523161f40a5f2b512def549bfb79e165b4354488738416481f2","transactionsRoot":"0xb3a4689832e0b599260ae70362ffcf224b60571b35ff8836904a3d81e2675d66","receiptsRoot":"0x2d13fdc120ab90536fed583939de7fb68b64926a306c1f629593ca9c2c93b198","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x3ea90d","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x2e0b6260","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0x026ae0c6ae91f186a9befa1ac8be30eea35e30e77de51a731085221e5cd39209","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x6e4969a136061ca7a390d12830d47a151585325a8d396819fb2b958ff85e9f8f","receiptsRoot":"0xc3e81df67d3e2a6c8345a954ef250cfcc41abcc2292a5aa263071124533fc9ad","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x3c0f6","timestamp":"0x66b200ce","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x18993a68","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0x3d22100ac0ee8d5cde334f7f926191a861b0648971ebc179547df28a0224c6d0","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9511d4711e5c30a72b0bff38a261daa75dcc5ba8b772d970a5c742244b4c861b","transactionsRoot":"0xba5fff578d3d6c2cd63acbe9bca353eaa6fe22a5c408956eff49106e0a96c507","receiptsRoot":"0xbae111f01cb07677e3a8c5031546138407c01bc964d3493d732dc4edf47d36d3","logsBloom":"0x00000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000020000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000001000000000000000000000400000001000010000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x5","gasLimit":"0x1c9c380","gasUsed":"0xcae7","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x12e09c7a","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x343a","nonce":"0x0","gas":"0xcc4d","maxFeePerGas":"0x557e5ec4","maxPriorityFeePerGas":"0x3b9aca00","to":"0x83a0444b93927c3afcbe46e522280390f748e171","value":"0x0","accessList":[],"input":"0x3659cfe6000000000000000000000000108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xf88e7b19ee347145c257e0cf7ac4ecc2bae83ca79d7edaa231e71d3213aeb151","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9c8eaf493f8b4edce2ba1647343eadcc0989cf461e712c0a6253ff2ca1842bb7","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xdd07c07470e1deff3749831f0f1ad8d4b6e35505e83b3c6ea14181716197cd8a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x24a1ab52","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200c9","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xf6930be4847cac5017bbcbec2756eed19f36b4196526a98a88e311c296e3a9be","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cc","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x200d75e8","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x4","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1592fbf9","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x149d41e3b89d8324cef3feff98ef308e97bafe8745cc8461c60172bc7d4c44ba","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x0b44110186e52ff0ceb6b0776ca2992c94144a4ed712eef65ea038260ef0fcc7","receiptsRoot":"0xc2823b8eb4730d9f2657137cc2ddc2c4f22ab68e0ab826236cf6a1551ca2b3a5","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0xe61f9","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x343a","nonce":"0x0","gas":"0xe94d1","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060008061002661006d60201b61081b1760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610141565b60008060405160200161007f90610121565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b600061010b60238361009e565b9150610116826100af565b604082019050919050565b6000602082019050818103600083015261013a816100fe565b9050919050565b611000806101506000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x4feae6769d748b4f0f7c9bf21d782236c88f13906789a3ec602961296e4c3e43","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0xb3535af5103fd1c2bbd6dc7ff23f0799037a6542c231ebcb85abd776560fa512","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x23d74fb99ff6e42cbb5c33f92b078e37be6af2b6092459b103ff7059a6517ebc","transactionsRoot":"0x9eab45eca206fe11c107ea985c7d02fcfa442836aea3e04ba11dc4df587d5aa6","receiptsRoot":"0xe25abcfa973db8c55f73292137c626430de130a382ad4466337fefb0f7c8fde0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x3ce3f","timestamp":"0x66b200cd","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1c0bc72b","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x343a","nonce":"0x0","gas":"0x3d8a8","maxFeePerGas":"0x6211577c","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060405161068538038061068583398181016040528101906100329190610275565b818181600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361009b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6100ae8161019d60201b61004f1760201c565b6100ef57806040517f8a8b41ec0000000000000000000000000000000000000000000000000000000081526004016100e691906102c4565b60405180910390fd5b806100fe6101b060201b60201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050806101536101e160201b6100621760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050610414565b600080823b905060008111915050919050565b6000806040516020016101c290610362565b6040516020818303038152906040528051906020012090508091505090565b6000806040516020016101f3906103f4565b6040516020818303038152906040528051906020012090508091505090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024282610217565b9050919050565b61025281610237565b811461025d57600080fd5b50565b60008151905061026f81610249565b92915050565b6000806040838503121561028c5761028b610212565b5b600061029a85828601610260565b92505060206102ab85828601610260565b9150509250929050565b6102be81610237565b82525050565b60006020820190506102d960008301846102b5565b92915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b600061034c6021836102df565b9150610357826102f0565b604082019050919050565b6000602082019050818103600083015261037b8161033f565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006103de6023836102df565b91506103e982610382565b604082019050919050565b6000602082019050818103600083015261040d816103d1565b9050919050565b610262806104236000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c6343000811003300000000000000000000000047d08dad17ccb558b3ea74b1a0e73a9cc804a9dc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xb6794d5c7abed6f91d447e8efb72ef2580595a6d7c8dee57ba1dbb330970146a","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x29dd5614","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump-legacy.json b/crates/anvil/test-data/state-dump-legacy.json index 26fa019b20fb5..e7b8544dfb6f1 100644 --- a/crates/anvil/test-data/state-dump-legacy.json +++ b/crates/anvil/test-data/state-dump-legacy.json @@ -1 +1 @@ -{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724762147,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xb92480171c0235f8c6710a4047d7ee14a3be58c630839fb4422826ff3a013e44","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc823","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}}],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdc80e","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xa00dc0c9ee9a888e67ea32d8772f8cc28eff62448c9ec985ee941fcbc921ba59","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc814","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}}],"ommers":[]}]} \ No newline at end of file +{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724762147,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xb92480171c0235f8c6710a4047d7ee14a3be58c630839fb4422826ff3a013e44","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc823","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","type":"0x2"}],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdc80e","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xa00dc0c9ee9a888e67ea32d8772f8cc28eff62448c9ec985ee941fcbc921ba59","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc814","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","type":"0x2"}],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump.json b/crates/anvil/test-data/state-dump.json index 4b7e66b17f56d..e7a1e37f62809 100644 --- a/crates/anvil/test-data/state-dump.json +++ b/crates/anvil/test-data/state-dump.json @@ -1 +1,314 @@ -{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724763179,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file +{ + "block": { + "number": 2, + "beneficiary": "0x0000000000000000000000000000000000000000", + "timestamp": 1724763179, + "gas_limit": 30000000, + "basefee": 875175000, + "difficulty": "0x0", + "prevrandao": "0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "nonce": 0, + "balance": "0xa410", + "code": "0x", + "storage": {} + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 0, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 1, + "balance": "0x21e19e0b90393da9b38", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 1, + "balance": "0x21e19e0b6a140b55df8", + "code": "0x", + "storage": {} + } + }, + "best_block_number": 2, + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x66cdcc25", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [], + "ommers": [] + }, + { + "header": { + "parentHash": "0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175", + "transactionsRoot": "0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc29", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853", + "s": "0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0", + "yParity": "0x0", + "hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + }, + { + "header": { + "parentHash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1", + "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x2", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdcc2b", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x342a1c58", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "transaction": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "type": "0x2" + }, + "impersonated_sender": null + } + ], + "ommers": [] + } + ], + "transactions": [ + { + "info": { + "transaction_hash": "0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3", + "transaction_index": 0, + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70", + "block_number": 1 + }, + { + "info": { + "transaction_hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515", + "transaction_index": 0, + "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "maybe_precompile": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x", + "output": "0x", + "gas_used": 0, + "gas_limit": 1, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + }, + "gas_refund_counter": 0 + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 0, + "gas_used": 21000 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0", + "block_number": 2 + } + ] +} diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 721aa404b36e1..2106395b8c627 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -2,10 +2,35 @@ use alloy_consensus::EMPTY_ROOT_HASH; use alloy_eips::BlockNumberOrTag; -use alloy_hardforks::EthereumHardfork; -use alloy_primitives::Address; +use alloy_network::{ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{Address, B256, U256, bytes, hex}; use alloy_provider::Provider; -use anvil::{spawn, NodeConfig}; +use alloy_rpc_types::TransactionRequest; +use alloy_sol_types::SolCall; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; + +#[tokio::test(flavor = "multi_thread")] +async fn dropping_handle_releases_http_listener() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let addr = *handle.socket_address(); + + drop(handle); + + tokio::time::timeout(std::time::Duration::from_secs(1), async { + loop { + match tokio::net::TcpListener::bind(addr).await { + Ok(listener) => { + drop(listener); + break; + } + Err(_) => tokio::time::sleep(std::time::Duration::from_millis(10)).await, + } + } + }) + .await + .expect("HTTP listener should shut down after NodeHandle is dropped"); +} #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { @@ -135,3 +160,95 @@ async fn test_can_use_default_genesis_block_number() { assert_eq!(0, provider.get_block(0.into()).await.unwrap().unwrap().header.number); } + +/// Verify that genesis block number affects both RPC and EVM execution layer. +#[tokio::test(flavor = "multi_thread")] +async fn test_number_opcode_reflects_genesis_block_number() { + let genesis_number: u64 = 4242; + let (api, handle) = + spawn(NodeConfig::test().with_genesis_block_number(Some(genesis_number))).await; + let provider = handle.http_provider(); + + // RPC layer should return configured genesis number + let bn = provider.get_block_number().await.unwrap(); + assert_eq!(bn, genesis_number); + + // Deploy bytecode that returns block.number + // 0x43 (NUMBER) 0x5f (PUSH0) 0x52 (MSTORE) 0x60 0x20 (PUSH1 0x20) 0x5f (PUSH0) 0xf3 (RETURN) + let target = Address::random(); + api.anvil_set_code(target, bytes!("435f5260205ff3")).await.unwrap(); + + // EVM execution should reflect genesis number (+ 1 for pending block) + let tx = alloy_rpc_types::TransactionRequest::default().with_to(target); + let out = provider.call(tx.into()).await.unwrap(); + let returned = U256::from_be_slice(out.as_ref()); + assert_eq!(returned, U256::from(genesis_number + 1)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_recover_signature() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + alloy_sol_types::sol! { + #[sol(rpc)] + contract TestRecover { + function testRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, address expected) external pure { + address recovered = ecrecover(hash, v, r, s); + require(recovered == expected, "ecrecover failed: address mismatch"); + } + } + } + let bytecode = hex::decode( + "0x60808060405234601557610125908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63bff0b743146023575f80fd5b3460eb5760a036600319011260eb5760243560ff811680910360eb576084356001600160a01b038116929083900360eb5760805f916020936004358252848201526044356040820152606435606082015282805260015afa1560e0575f516001600160a01b031603609057005b60405162461bcd60e51b815260206004820152602260248201527f65637265636f766572206661696c65643a2061646472657373206d69736d61746044820152610c6d60f31b6064820152608490fd5b6040513d5f823e3d90fd5b5f80fdfea264697066735822122006368b42bca31c97f2c409a1cc5186dc899d4255ecc28db7bbb0ad285dc82ae464736f6c634300081c0033", + ).unwrap(); + + let tx = TransactionRequest::default().with_deploy_code(bytecode); + let receipt = provider.send_transaction(tx.into()).await.unwrap().get_receipt().await.unwrap(); + let contract_address = receipt.contract_address().unwrap(); + let contract = TestRecover::new(contract_address, &provider); + + let sig = alloy_primitives::hex::decode("11".repeat(65)).unwrap(); + let r = B256::from_slice(&sig[0..32]); + let s = B256::from_slice(&sig[32..64]); + let v = sig[64]; + let fake_hash = B256::random(); + let expected = alloy_primitives::address!("0x1234567890123456789012345678901234567890"); + api.anvil_impersonate_signature(sig.clone().into(), expected).await.unwrap(); + let result = contract.testRecover(fake_hash, v, r, s, expected).call().await; + assert!(result.is_ok(), "ecrecover failed: {:?}", result.err()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fake_signature_transaction() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + alloy_sol_types::sol! { + #[sol(rpc)] + contract TestRecover { + function testRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, address expected) external pure { + address recovered = ecrecover(hash, v, r, s); + require(recovered == expected, "ecrecover failed: address mismatch"); + } + } + } + let bytecode = hex::decode( + "0x60808060405234601557610125908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63bff0b743146023575f80fd5b3460eb5760a036600319011260eb5760243560ff811680910360eb576084356001600160a01b038116929083900360eb5760805f916020936004358252848201526044356040820152606435606082015282805260015afa1560e0575f516001600160a01b031603609057005b60405162461bcd60e51b815260206004820152602260248201527f65637265636f766572206661696c65643a2061646472657373206d69736d61746044820152610c6d60f31b6064820152608490fd5b6040513d5f823e3d90fd5b5f80fdfea264697066735822122006368b42bca31c97f2c409a1cc5186dc899d4255ecc28db7bbb0ad285dc82ae464736f6c634300081c0033", + ).unwrap(); + + let tx = TransactionRequest::default().with_deploy_code(bytecode); + let _receipt = provider.send_transaction(tx.into()).await.unwrap().get_receipt().await.unwrap(); + + let sig = alloy_primitives::hex::decode("11".repeat(65)).unwrap(); + let r = B256::from_slice(&sig[0..32]); + let s = B256::from_slice(&sig[32..64]); + let v = sig[64]; + let fake_hash = B256::random(); + let expected = alloy_primitives::address!("0x1234567890123456789012345678901234567890"); + api.anvil_impersonate_signature(sig.clone().into(), expected).await.unwrap(); + let calldata = TestRecover::testRecoverCall { hash: fake_hash, v, r, s, expected }.abi_encode(); + let tx = TransactionRequest::default().with_input(calldata); + let pending = provider.send_transaction(tx.into()).await.unwrap(); + let result = pending.get_receipt().await; + + assert!(result.is_ok(), "ecrecover failed: {:?}", result.err()); +} diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 5b4bcd78d2059..81dc089f64265 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -1,39 +1,30 @@ //! tests for custom anvil endpoints use crate::{ - abi::{self, Greeter, Multicall, BUSD}, + abi::{self, BUSD, Greeter, Multicall}, fork::fork_config, utils::http_provider_with_signer, }; use alloy_consensus::{SignableTransaction, TxEip1559}; -use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; -use alloy_primitives::{address, fixed_bytes, utils::Unit, Address, Bytes, TxKind, U256}; -use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_primitives::{Address, Bytes, TxKind, U256, address, fixed_bytes}; +use alloy_provider::{Provider, ext::TxPoolApi}; use alloy_rpc_types::{ + BlockId, BlockNumberOrTag, TransactionRequest, anvil::{ ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, }, - BlockId, BlockNumberOrTag, TransactionRequest, }; use alloy_serde::WithOtherFields; -use anvil::{ - eth::{ - api::CLIENT_VERSION, - backend::mem::{EXECUTOR, P256_DELEGATION_CONTRACT, P256_DELEGATION_RUNTIME_CODE}, - }, - spawn, NodeConfig, -}; +use anvil::{NodeConfig, eth::api::CLIENT_VERSION, spawn}; use anvil_core::{ - eth::{ - wallet::{Capabilities, DelegationCapability, WalletCapabilities}, - EthRequest, - }, + eth::EthRequest, types::{ReorgOptions, TransactionData}, }; -use revm::primitives::hardfork::SpecId; +use foundry_common::version::{COMMIT_SHA, SEMVER_VERSION}; +use foundry_evm::hardfork::EthereumHardfork; + use std::{ - future::IntoFuture, str::FromStr, time::{Duration, SystemTime}, }; @@ -449,13 +440,13 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); - let hard_fork: &str = SpecId::PRAGUE.into(); + let hard_fork = NodeConfig::test().get_hardfork().name(); let expected_node_info = NodeInfo { current_block_number: 0_u64, current_block_timestamp: 1, current_block_hash: block.header.hash, - hard_fork: hard_fork.to_string(), + hard_fork, transaction_order: "fees".to_owned(), environment: NodeEnvironment { base_fee: U256::from_str("0x3b9aca00").unwrap().to(), @@ -468,6 +459,7 @@ async fn can_get_node_info() { fork_block_number: None, fork_retry_backoff: None, }, + network: None, }; assert_eq!(node_info, expected_node_info); @@ -490,6 +482,8 @@ async fn can_get_metadata() { latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), + client_semver: Some(SEMVER_VERSION.to_string()), + client_commit_sha: Some(COMMIT_SHA.to_string()), instance_id: api.instance_id(), forked_network: None, snapshots: Default::default(), @@ -515,6 +509,8 @@ async fn can_get_metadata_on_fork() { latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), + client_semver: Some(SEMVER_VERSION.to_string()), + client_commit_sha: Some(COMMIT_SHA.to_string()), instance_id: api.instance_id(), forked_network: Some(ForkedNetwork { chain_id, @@ -667,9 +663,9 @@ async fn can_remove_pool_transactions() { } #[tokio::test(flavor = "multi_thread")] -async fn test_reorg() { +async fn flaky_test_reorg() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); @@ -717,7 +713,8 @@ async fn test_reorg() { // Verify that historic blocks are still accessible for num in (0..14).rev() { - let _ = provider.get_block_by_number(num.into()).full().await.unwrap(); + let block = provider.get_block_by_number(num.into()).full().await.unwrap(); + assert!(block.is_some(), "Historic block {num} should be accessible after reorg"); } // Send a few more transaction to verify the chain can still progress @@ -796,104 +793,155 @@ async fn test_reorg() { } #[tokio::test(flavor = "multi_thread")] -async fn test_rollback() { +async fn test_reorg_blockhash_opcode_consistency() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - // Mine 5 blocks - for _ in 0..5 { - api.mine_one().await; - } + let multicall = Multicall::deploy(&provider).await.unwrap(); - // Get block 4 for later comparison - let block4 = provider.get_block(4.into()).await.unwrap().unwrap(); + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(200) })).await.unwrap(); - // Rollback with None should rollback 1 block - api.anvil_rollback(None).await.unwrap(); + let tip_before_reorg = api.block_number().unwrap().to::(); - // Assert we're at block 4 and the block contents are kept the same - let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); - assert_eq!(head, block4); + let mut cached_hashes: Vec<(u64, alloy_primitives::B256, alloy_primitives::B256)> = Vec::new(); + for i in 1..=10 { + let block_num = tip_before_reorg - i; + let rpc_hash = + provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; + let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); + assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); + cached_hashes.push((block_num, rpc_hash, opcode_hash)); + } - // Get block 1 for comparison - let block1 = provider.get_block(1.into()).await.unwrap().unwrap(); + let tx = TransactionRequest::default(); + api.anvil_reorg(ReorgOptions { + depth: 5, + tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], + }) + .await + .unwrap(); - // Rollback to block 1 - let depth = 3; // from block 4 to block 1 - api.anvil_rollback(Some(depth)).await.unwrap(); + api.mine_one().await; - // Assert we're at block 1 and the block contents are kept the same - let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); - assert_eq!(head, block1); + for (block_num, rpc_before, opcode_before) in &cached_hashes { + let rpc_after = + provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; + let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); + if *block_num <= tip_before_reorg.saturating_sub(5) { + assert_eq!( + rpc_after, *rpc_before, + "Block {block_num}: hash should not change for non-reorged blocks" + ); + assert_eq!( + opcode_after, *opcode_before, + "Block {block_num}: BLOCKHASH should not change for non-reorged blocks" + ); + } + assert_eq!( + rpc_after, opcode_after, + "Block {block_num}: RPC ({rpc_after}) and BLOCKHASH opcode ({opcode_after}) should match after reorg" + ); + } } -// === wallet endpoints === // #[tokio::test(flavor = "multi_thread")] -async fn can_get_wallet_capabilities() { - let (api, handle) = spawn(NodeConfig::test().with_odyssey(true)).await; - +async fn test_reorg_deep_blockhash_consistency() { + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let init_sponsor_bal = provider.get_balance(EXECUTOR).await.unwrap(); - - let expected_bal = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); - assert_eq!(init_sponsor_bal, expected_bal); - - let p256_code = provider.get_code_at(P256_DELEGATION_CONTRACT).await.unwrap(); + let multicall = Multicall::deploy(&provider).await.unwrap(); - assert_eq!(p256_code, Bytes::from_static(P256_DELEGATION_RUNTIME_CODE)); + // Mine 300 blocks (more than BLOCKHASH limit of 256) + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(300) })).await.unwrap(); - let capabilities = api.get_capabilities().unwrap(); + let tip_before_reorg = api.block_number().unwrap().to::(); + assert!(tip_before_reorg > 256, "Need more than 256 blocks for this test"); - let mut expect_caps = WalletCapabilities::default(); - let cap: Capabilities = Capabilities { - delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, - }; - expect_caps.insert(api.chain_id(), cap); - - assert_eq!(capabilities, expect_caps); -} + // Check blocks within the 256 window before reorg + let mut cached_hashes: Vec<(u64, alloy_primitives::B256)> = Vec::new(); + for i in [1, 10, 100, 200, 255] { + let block_num = tip_before_reorg - i; + let rpc_hash = + provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; + let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); + assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); + cached_hashes.push((block_num, rpc_hash)); + } -#[tokio::test(flavor = "multi_thread")] -async fn can_add_capability() { - let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; + // Perform a deep reorg (50 blocks) + let tx = TransactionRequest::default(); + api.anvil_reorg(ReorgOptions { + depth: 50, + tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], + }) + .await + .unwrap(); - let init_capabilities = api.get_capabilities().unwrap(); + api.mine_one().await; - let mut expect_caps = WalletCapabilities::default(); - let cap: Capabilities = Capabilities { - delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, - }; - expect_caps.insert(api.chain_id(), cap); + let tip_after_reorg = api.block_number().unwrap().to::(); - assert_eq!(init_capabilities, expect_caps); + // Verify blocks still in the 256 window have consistent hashes + for (block_num, rpc_before) in &cached_hashes { + // Skip blocks that were reorged + if *block_num > tip_after_reorg - 50 { + continue; + } + let rpc_after = + provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; + let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); + assert_eq!( + rpc_after, *rpc_before, + "Block {block_num}: hash should not change for non-reorged blocks" + ); + assert_eq!( + rpc_after, opcode_after, + "Block {block_num}: RPC and BLOCKHASH should match after deep reorg" + ); + } - let new_cap_addr = Address::with_last_byte(1); + // Verify BLOCKHASH returns 0 for blocks outside the 256 window + let old_block = tip_after_reorg.saturating_sub(257); + if old_block > 0 { + let opcode_hash = multicall.getBlockHash(U256::from(old_block)).call().await.unwrap(); + assert_eq!( + opcode_hash, + alloy_primitives::B256::ZERO, + "BLOCKHASH should return 0 for blocks outside 256 window" + ); + } +} - api.anvil_add_capability(new_cap_addr).unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn test_rollback() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - let capabilities = api.get_capabilities().unwrap(); + // Mine 5 blocks + for _ in 0..5 { + api.mine_one().await; + } - let cap: Capabilities = Capabilities { - delegation: DelegationCapability { - addresses: vec![P256_DELEGATION_CONTRACT, new_cap_addr], - }, - }; - expect_caps.insert(api.chain_id(), cap); + // Get block 4 for later comparison + let block4 = provider.get_block(4.into()).await.unwrap().unwrap(); - assert_eq!(capabilities, expect_caps); -} + // Rollback with None should rollback 1 block + api.anvil_rollback(None).await.unwrap(); -#[tokio::test(flavor = "multi_thread")] -async fn can_set_executor() { - let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; + // Assert we're at block 4 and the block contents are kept the same + let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(head, block4); - let expected_addr = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); - let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + // Get block 1 for comparison + let block1 = provider.get_block(1.into()).await.unwrap().unwrap(); - let executor = api.anvil_set_executor(pk).unwrap(); + // Rollback to block 1 + let depth = 3; // from block 4 to block 1 + api.anvil_rollback(Some(depth)).await.unwrap(); - assert_eq!(executor, expected_addr); + // Assert we're at block 1 and the block contents are kept the same + let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(head, block1); } #[tokio::test(flavor = "multi_thread")] @@ -947,15 +995,14 @@ async fn test_mine_blk_with_prev_timestamp() { let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let third_blk_num = block.header.number; - let third_blk_timestmap = block.header.timestamp; + let third_blk_timestamp = block.header.timestamp; assert_eq!(third_blk_num, init_number + 2); - assert_ne!(third_blk_timestmap, next_blk_timestamp); - assert!(third_blk_timestmap > next_blk_timestamp); + assert_ne!(third_blk_timestamp, next_blk_timestamp); + assert!(third_blk_timestamp > next_blk_timestamp); } // increase time by 0 seconds i.e next_block_timestamp = prev_block_timestamp -// api.evm_increase_time(0).unwrap(); #[tokio::test(flavor = "multi_thread")] async fn test_increase_time_by_zero() { let (api, handle) = spawn(NodeConfig::test()).await; @@ -966,7 +1013,7 @@ async fn test_increase_time_by_zero() { let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; - let _ = api.evm_increase_time(U256::ZERO).await; + api.evm_set_next_block_timestamp(init_timestamp).unwrap(); api.mine_one().await; @@ -1003,7 +1050,7 @@ async fn evm_mine_blk_with_same_timestamp() { // mine 4 blocks instantly. #[tokio::test(flavor = "multi_thread")] -async fn test_mine_blks_with_same_timestamp() { +async fn test_mine_blk_with_same_timestamp() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); @@ -1055,3 +1102,211 @@ async fn test_mine_first_block_with_interval() { let second_block = api.block_by_number(2.into()).await.unwrap().unwrap(); assert_eq!(second_block.header.timestamp, init_timestamp + 120); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_reset_non_fork() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + // Get initial state + let init_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + let init_accounts = api.accounts().unwrap(); + let init_balance = provider.get_balance(init_accounts[0]).await.unwrap(); + + // Store the instance id before reset + let instance_id_before = api.instance_id(); + + // Mine some blocks and make transactions + for _ in 0..5 { + api.mine_one().await; + } + + // Send a transaction + let to = Address::random(); + let val = U256::from(1337); + let tx = TransactionRequest::default().with_from(init_accounts[0]).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + // Check state has changed + let block_before_reset = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert!(block_before_reset.header.number > init_block.header.number); + + let balance_before_reset = provider.get_balance(init_accounts[0]).await.unwrap(); + assert!(balance_before_reset < init_balance); + + let to_balance_before_reset = provider.get_balance(to).await.unwrap(); + assert_eq!(to_balance_before_reset, val); + + // Reset to fresh in-memory state (non-fork) + api.anvil_reset(None).await.unwrap(); + + // Check instance id has changed + let instance_id_after = api.instance_id(); + assert_ne!(instance_id_before, instance_id_after); + + // Check we're back at genesis + let block_after_reset = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(block_after_reset.header.number, 0); + + // Check accounts are restored to initial state + let balance_after_reset = provider.get_balance(init_accounts[0]).await.unwrap(); + assert_eq!(balance_after_reset, init_balance); + + // Check the recipient's balance is zero + let to_balance_after_reset = provider.get_balance(to).await.unwrap(); + assert_eq!(to_balance_after_reset, U256::ZERO); + + // Test we can continue mining after reset + api.mine_one().await; + let new_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(new_block.header.number, 1); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_reset_fork_to_non_fork() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + // Verify we're in fork mode + let metadata = api.anvil_metadata().await.unwrap(); + assert!(metadata.forked_network.is_some()); + + // Mine some blocks + for _ in 0..3 { + api.mine_one().await; + } + + // Reset to non-fork mode + api.anvil_reset(None).await.unwrap(); + + // Verify we're no longer in fork mode + let metadata_after = api.anvil_metadata().await.unwrap(); + assert!(metadata_after.forked_network.is_none()); + + // Check we're at block 0 + let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(block.header.number, 0); + + // Verify we can still mine blocks + api.mine_one().await; + let new_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); + assert_eq!(new_block.header.number, 1); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_node_info_tempo_t0() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + + let node_info = api.anvil_node_info().await.unwrap(); + + let provider = handle.http_provider(); + + let block_number = provider.get_block_number().await.unwrap(); + let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); + + let expected_node_info = NodeInfo { + current_block_number: 0_u64, + current_block_timestamp: 1, + current_block_hash: block.header.hash, + hard_fork: "T0".to_string(), + transaction_order: "fees".to_owned(), + environment: NodeEnvironment { + base_fee: 10_000_000_000, + chain_id: 31337, + gas_limit: 30_000_000, + gas_price: 11_000_000_000, + }, + fork_config: NodeForkConfig { + fork_url: None, + fork_block_number: None, + fork_retry_backoff: None, + }, + network: Some("tempo".to_string()), + }; + + assert_eq!(node_info, expected_node_info); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_node_info_tempo_t1() { + use tempo_chainspec::hardfork::TempoHardfork; + + let config = NodeConfig::test_tempo().with_hardfork(Some(TempoHardfork::T1.into())); + let (api, handle) = spawn(config).await; + + let node_info = api.anvil_node_info().await.unwrap(); + + let provider = handle.http_provider(); + + let block_number = provider.get_block_number().await.unwrap(); + let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); + + let expected_node_info = NodeInfo { + current_block_number: 0_u64, + current_block_timestamp: 1, + current_block_hash: block.header.hash, + hard_fork: "T1".to_string(), + transaction_order: "fees".to_owned(), + environment: NodeEnvironment { + base_fee: 20_000_000_000, + chain_id: 31337, + gas_limit: 30_000_000, + gas_price: 21_000_000_000, + }, + fork_config: NodeForkConfig { + fork_url: None, + fork_block_number: None, + fork_retry_backoff: None, + }, + network: Some("tempo".to_string()), + }; + + assert_eq!(node_info, expected_node_info); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_deal_erc20_tempo() { + use alloy_primitives::address; + use foundry_evm::core::tempo::PATH_USD_ADDRESS; + + const ALPHA_USD_ADDRESS: Address = address!("0x20C0000000000000000000000000000000000001"); + + let (api, _handle) = spawn(NodeConfig::test_tempo()).await; + + let target = Address::random(); + + // TIP20 tokens are precompile-backed — anvil_dealERC20 uses access-list slot probing + // which doesn't discover precompile storage slots. Verify this fails gracefully. + for token_addr in [PATH_USD_ADDRESS, ALPHA_USD_ADDRESS] { + let amount = U256::from(5_000_000); // 5 tokens (6 decimals) + + let result = api.anvil_deal_erc20(target, token_addr, amount).await; + assert!( + result.is_err(), + "anvil_dealERC20 should fail for precompile-based TIP20 {token_addr}" + ); + + let err = result.unwrap_err().to_string(); + assert!( + err.contains("no slot found"), + "Error should mention slot discovery failure, got: {err}" + ); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_default_base_fee_tempo() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + assert_eq!( + block.header.base_fee_per_gas, + Some(10_000_000_000), + "T0 base fee should be 10 billion attodollars" + ); +} diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index ce807eed6aa88..209ecc6e74455 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -1,21 +1,29 @@ //! general eth api tests use crate::{ - abi::{Multicall, SimpleStorage}, - utils::{connect_pubsub_with_wallet, http_provider_with_signer}, + abi::{Multicall, SimpleStorage, VendingMachine}, + utils::{connect_pubsub_with_wallet, http_provider, http_provider_with_signer}, +}; +use alloy_consensus::{ + BlobTransactionSidecar, SidecarBuilder, SignableTransaction, SimpleCoder, Transaction, + TxEip1559, +}; +use alloy_network::{ + EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844, TxSignerSync, }; -use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{ + Address, B256, ChainId, U256, b256, bytes, map::{AddressHashMap, B256HashMap, HashMap}, - Address, ChainId, B256, U256, }; use alloy_provider::Provider; use alloy_rpc_types::{ - request::TransactionRequest, state::AccountOverride, BlockId, BlockNumberOrTag, - BlockTransactions, + BlockId, BlockNumberOrTag, BlockTransactions, request::TransactionRequest, + state::AccountOverride, }; use alloy_serde::WithOtherFields; -use anvil::{eth::api::CLIENT_VERSION, spawn, NodeConfig, CHAIN_ID}; +use alloy_sol_types::SolCall; +use anvil::{CHAIN_ID, EthereumHardfork, NodeConfig, eth::api::CLIENT_VERSION, spawn}; +use foundry_test_utils::rpc; use futures::join; use std::time::Duration; @@ -46,10 +54,12 @@ async fn can_dev_get_balance() { #[tokio::test(flavor = "multi_thread")] async fn can_get_price() { - let (_api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let _ = provider.get_gas_price().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + assert!(gas_price > 0); + assert_eq!(gas_price, api.gas_price()); } #[tokio::test(flavor = "multi_thread")] @@ -57,7 +67,12 @@ async fn can_get_accounts() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let _ = provider.get_accounts().await.unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let dev_accounts: Vec<_> = handle.dev_accounts().collect(); + assert_eq!(accounts.len(), dev_accounts.len()); + for account in dev_accounts { + assert!(accounts.contains(&account), "Missing dev account {account}"); + } } #[tokio::test(flavor = "multi_thread")] @@ -93,10 +108,14 @@ async fn can_modify_chain_id() { #[tokio::test(flavor = "multi_thread")] async fn can_get_network_id() { - let (api, _handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - let chain_id = api.network_id().unwrap().unwrap(); - assert_eq!(chain_id, CHAIN_ID.to_string()); + let network_id = api.network_id().unwrap().unwrap(); + assert_eq!(network_id, CHAIN_ID.to_string()); + + let provider_network_id = provider.get_net_version().await.unwrap(); + assert_eq!(provider_network_id, CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] @@ -403,3 +422,259 @@ async fn can_mine_while_mining() { let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); assert_eq!(block.header.number, total_blocks); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_raw_tx_sync() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + + let from = wallets[0].address(); + let mut tx = TxEip1559 { + max_fee_per_gas: eip1559_est.max_fee_per_gas, + max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas, + gas_limit: 100000, + chain_id: 31337, + to: alloy_primitives::TxKind::Call(from), + input: bytes!("11112222"), + ..Default::default() + }; + let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); + + let tx = tx.into_signed(signature); + let mut encoded = Vec::new(); + tx.eip2718_encode(&mut encoded); + + let receipt = api.send_raw_transaction_sync(encoded.into()).await.unwrap(); + assert_eq!(receipt.from(), wallets[1].address()); + assert_eq!(receipt.to(), tx.to()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_tx_sync() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); + + let from = wallets[0].address(); + let tx = TransactionRequest::default() + .with_from(from) + .into_create() + .with_nonce(0) + .with_input(logger_bytecode); + + let receipt = api.send_transaction_sync(WithOtherFields::new(tx)).await.unwrap(); + assert_eq!(receipt.from(), wallets[0].address()); +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "no debug_"] +async fn can_get_code_by_hash() { + let (api, _) = + spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; + + // The code hash for DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE + let code_hash = b256!("2fa86add0aed31f33a762c9d88e807c475bd51d0f52bd0955754b2608f7e4989"); + + let code = api.debug_code_by_hash(code_hash, None).await.unwrap(); + assert_eq!(&code.unwrap(), foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_fills_chain_id() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + let tx_req = TransactionRequest::default() + .with_from(from) + .with_to(Address::random()) + .with_gas_limit(21_000); + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Should fill with the chain id from provider + assert!(filled.tx.chain_id().is_some()); + assert_eq!(filled.tx.chain_id().unwrap(), CHAIN_ID); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_fills_nonce() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + // Send a transaction to increment nonce + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + // Now the account should have nonce 1 + let tx_req = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1000)) + .with_gas_limit(21_000); + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + assert_eq!(filled.tx.nonce(), 1); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_preserves_provided_fields() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + let provided_nonce = 100u64; + let provided_gas_limit = 50_000u64; + + let tx_req = TransactionRequest::default() + .with_from(from) + .with_to(Address::random()) + .with_value(U256::from(1000)) + .with_nonce(provided_nonce) + .with_gas_limit(provided_gas_limit); + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Should preserve the provided nonce and gas limit + assert_eq!(filled.tx.nonce(), provided_nonce); + assert_eq!(filled.tx.gas_limit(), provided_gas_limit); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_fills_all_missing_fields() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + // Create a simple transfer transaction with minimal fields + let tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Should fill all required fields and be EIP-1559 + assert!(filled.tx.is_eip1559()); + assert!(filled.tx.gas_limit() > 0); + assert!(filled.tx.max_fee_per_gas() > 0); + assert!(filled.tx.max_priority_fee_per_gas().is_some()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_eip4844_blob_fee() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + let mut builder = SidecarBuilder::::new(); + builder.ingest(b"dummy blob"); + let sidecar: BlobTransactionSidecar = builder.build().unwrap(); + + // EIP-4844 blob transaction with sidecar but no blob fee + let mut tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); + tx_req.sidecar = Some(sidecar.into()); + tx_req.transaction_type = Some(3); // EIP-4844 + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Blob transaction should have max_fee_per_blob_gas filled + assert!( + filled.tx.max_fee_per_blob_gas().is_some(), + "max_fee_per_blob_gas should be filled for blob tx" + ); + assert!(filled.tx.blob_versioned_hashes().is_some(), "blob_versioned_hashes should be present"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_eip4844_preserves_blob_fee() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + let provided_blob_fee = 5_000_000u128; + + let mut builder = SidecarBuilder::::new(); + builder.ingest(b"dummy blob"); + let sidecar: BlobTransactionSidecar = builder.build().unwrap(); + + // EIP-4844 blob transaction with blob fee already set + let mut tx_req = TransactionRequest::default() + .with_from(from) + .with_to(Address::random()) + .with_max_fee_per_blob_gas(provided_blob_fee); + tx_req.sidecar = Some(sidecar.into()); + tx_req.transaction_type = Some(3); // EIP-4844 + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Should preserve the provided blob fee + assert_eq!( + filled.tx.max_fee_per_blob_gas(), + Some(provided_blob_fee), + "should preserve provided max_fee_per_blob_gas" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_non_blob_tx_no_blob_fee() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let from = wallet.address(); + + // EIP-1559 transaction without blob fields + let mut tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); + tx_req.transaction_type = Some(2); // EIP-1559 + + let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); + + // Non-blob transaction should NOT have blob fee filled + assert!( + filled.tx.max_fee_per_blob_gas().is_none(), + "max_fee_per_blob_gas should not be set for non-blob tx" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fill_transaction_reverts_on_gas_estimation_failure() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + let from = accounts[0].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + // Deploy VendingMachine contract + let contract = VendingMachine::deploy(&provider).await.unwrap(); + let contract_address = *contract.address(); + + // Call buy function with insufficient ether + let tx_req = TransactionRequest::default() + .with_from(from) + .with_to(contract_address) + .with_input(VendingMachine::buyCall { amount: U256::from(10) }.abi_encode()); + + // fill_transaction should fail because gas estimation fails due to revert + let result = api.fill_transaction(WithOtherFields::new(tx_req)).await; + + assert!(result.is_err(), "fill_transaction should return an error when gas estimation fails"); + let error_message = result.unwrap_err().to_string(); + assert!( + error_message.contains("execution reverted"), + "Error should indicate a revert, got: {error_message}" + ); +} diff --git a/crates/anvil/tests/it/beacon_api.rs b/crates/anvil/tests/it/beacon_api.rs new file mode 100644 index 0000000000000..2f865dd20e80a --- /dev/null +++ b/crates/anvil/tests/it/beacon_api.rs @@ -0,0 +1,259 @@ +use crate::utils::http_provider; +use alloy_consensus::{Blob, BlobTransactionSidecar, SidecarBuilder, SimpleCoder, Transaction}; +use alloy_network::{TransactionBuilder, TransactionBuilder4844}; +use alloy_primitives::{B256, FixedBytes, U256, b256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_rpc_types_beacon::{genesis::GenesisResponse, sidecar::GetBlobsResponse}; +use alloy_serde::WithOtherFields; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; +use ssz::Decode; + +#[tokio::test(flavor = "multi_thread")] +async fn test_beacon_api_get_blob_sidecars() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + // Test Beacon API endpoint using HTTP client + let client = reqwest::Client::new(); + let url = format!("{}/eth/v1/beacon/blob_sidecars/latest", handle.http_endpoint()); + + // This endpoint is deprecated, so we expect a 410 Gone response + let response = client.get(&url).send().await.unwrap(); + assert_eq!( + response.text().await.unwrap(), + r#"{"code":410,"message":"This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead."}"#, + "Expected deprecation message for blob_sidecars endpoint" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_beacon_api_get_blobs() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + + // Disable auto-mining so we can include multiple transactions in the same block + api.anvil_set_auto_mine(false).await.unwrap(); + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + // Create multiple blob transactions to be included in the same block + let blob_data = + [b"Hello Beacon API - Blob 1", b"Hello Beacon API - Blob 2", b"Hello Beacon API - Blob 3"]; + + let mut pending_txs = Vec::new(); + + // Send all transactions without waiting for receipts + for (i, data) in blob_data.iter().enumerate() { + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(data.as_slice()); + let sidecar = sidecar.build::().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(i as u64) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar_4844(sidecar) + .value(U256::from(100)); + + let tx = WithOtherFields::new(tx); + + let pending = provider.send_transaction(tx).await.unwrap(); + pending_txs.push(pending); + } + + // Mine a block to include all transactions + api.evm_mine(None).await.unwrap(); + + // Get receipts for all transactions + let mut receipts = Vec::new(); + for pending in pending_txs { + let receipt = pending.get_receipt().await.unwrap(); + receipts.push(receipt); + } + + // Verify all transactions were included in the same block + let block_number = receipts[0].block_number.unwrap(); + for (i, receipt) in receipts.iter().enumerate() { + assert_eq!( + receipt.block_number.unwrap(), + block_number, + "Transaction {i} was not included in block {block_number}" + ); + } + + // Extract the actual versioned hashes from the mined transactions + let mut actual_versioned_hashes = Vec::new(); + for receipt in &receipts { + let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); + if let Some(blob_versioned_hashes) = tx.blob_versioned_hashes() { + actual_versioned_hashes.extend(blob_versioned_hashes.iter().copied()); + } + } + + // Test Beacon API endpoint using HTTP client + let client = reqwest::Client::new(); + let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_number); + + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + assert_eq!( + response.headers().get("content-type").and_then(|h| h.to_str().ok()), + Some("application/json"), + "Expected application/json content-type header" + ); + + let blobs_response: GetBlobsResponse = response.json().await.unwrap(); + // Verify response structure + assert!(!blobs_response.execution_optimistic); + assert!(!blobs_response.finalized); + + // Verify we have blob data from all transactions + assert_eq!(blobs_response.data.len(), 3, "Expected 3 blobs from 3 transactions"); + + // Test response with SSZ encoding + let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_number); + let response = client + .get(&url) + .header(axum::http::header::ACCEPT, "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + assert_eq!( + response.headers().get("content-type").and_then(|h| h.to_str().ok()), + Some("application/octet-stream"), + "Expected application/octet-stream content-type header" + ); + + let body_bytes = response.bytes().await.unwrap(); + + // Decode the SSZ-encoded blobs in a spawned thread with larger stack to handle recursion + let decoded_blobs = std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) // 8MB stack for SSZ decoding of large blobs + .spawn(move || Vec::::from_ssz_bytes(&body_bytes)) + .expect("Failed to spawn decode thread") + .join() + .expect("Decode thread panicked") + .expect("Failed to decode SSZ-encoded blobs"); + + // Verify we got exactly 3 blobs + assert_eq!( + decoded_blobs.len(), + 3, + "Expected 3 blobs from SSZ-encoded response, got {}", + decoded_blobs.len() + ); + + // Verify the decoded blobs match the JSON response blobs + for (i, (decoded, json)) in decoded_blobs.iter().zip(blobs_response.data.iter()).enumerate() { + assert_eq!(decoded, json, "Blob {i} mismatch between SSZ and JSON responses"); + } + + // Test filtering with versioned_hashes query parameter - single hash + let url = format!( + "{}/eth/v1/beacon/blobs/{}?versioned_hashes={}", + handle.http_endpoint(), + block_number, + actual_versioned_hashes[1] + ); + let response = client.get(&url).send().await.unwrap(); + let status = response.status(); + if status != reqwest::StatusCode::OK { + let error_body = response.text().await.unwrap(); + panic!("Expected OK status, got {status}: {error_body}"); + } + let blobs_response: GetBlobsResponse = response.json().await.unwrap(); + assert_eq!( + blobs_response.data.len(), + 1, + "Expected 1 blob when filtering by single versioned_hash" + ); + + // Test filtering with versioned_hashes query parameter - multiple versioned_hashes + // (comma-separated) + let url = format!( + "{}/eth/v1/beacon/blobs/{}?versioned_hashes={},{}", + handle.http_endpoint(), + block_number, + actual_versioned_hashes[0], + actual_versioned_hashes[2] + ); + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + let blobs_response: GetBlobsResponse = response.json().await.unwrap(); + assert_eq!( + blobs_response.data.len(), + 2, + "Expected 2 blobs when filtering by two versioned_hashes" + ); + + // Test filtering with non-existent versioned_hash + let non_existent_hash = + b256!("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + let url = format!( + "{}/eth/v1/beacon/blobs/{}?versioned_hashes={}", + handle.http_endpoint(), + block_number, + non_existent_hash + ); + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + let blobs_response: GetBlobsResponse = response.json().await.unwrap(); + assert_eq!( + blobs_response.data.len(), + 0, + "Expected 0 blobs when filtering by non-existent versioned_hash" + ); + + // Test with special block identifiers + let test_ids = vec!["latest", "finalized", "safe", "earliest"]; + for block_id in test_ids { + let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_id); + assert_eq!(client.get(&url).send().await.unwrap().status(), reqwest::StatusCode::OK); + } + let url = format!("{}/eth/v1/beacon/blobs/pending", handle.http_endpoint()); + assert_eq!(client.get(&url).send().await.unwrap().status(), reqwest::StatusCode::NOT_FOUND); + + // Test with hex block number + let url = format!("{}/eth/v1/beacon/blobs/0x{block_number:x}", handle.http_endpoint()); + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + // Test with non-existent block + let url = format!("{}/eth/v1/beacon/blobs/999999", handle.http_endpoint()); + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::NOT_FOUND); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_beacon_api_get_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + // Test Beacon API genesis endpoint using HTTP client + let client = reqwest::Client::new(); + let url = format!("{}/eth/v1/beacon/genesis", handle.http_endpoint()); + + let response = client.get(&url).send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let genesis_response: GenesisResponse = response.json().await.unwrap(); + + assert!(genesis_response.data.genesis_time > 0); + assert_eq!(genesis_response.data.genesis_validators_root, B256::ZERO); + assert_eq!( + genesis_response.data.genesis_fork_version, + FixedBytes::from([0x00, 0x00, 0x00, 0x00]) + ); +} diff --git a/crates/anvil/tests/it/eip2935.rs b/crates/anvil/tests/it/eip2935.rs new file mode 100644 index 0000000000000..8bb2beef9605b --- /dev/null +++ b/crates/anvil/tests/it/eip2935.rs @@ -0,0 +1,70 @@ +use crate::utils::http_provider; +use alloy_eips::{BlockNumberOrTag, eip2935::HISTORY_STORAGE_ADDRESS}; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_contract_deployed_at_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); + assert!(!code.is_empty(), "EIP-2935 history storage contract should be deployed at genesis"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_stores_parent_block_hash() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + // Mine a few blocks so there are parent hashes to store + api.mine_one().await; + api.mine_one().await; + api.mine_one().await; + + // Block 1's hash should be stored when block 2 was mined + let block1 = provider + .get_block_by_number(BlockNumberOrTag::from(1)) + .await + .unwrap() + .expect("block 1 should exist"); + let block1_hash = block1.header.hash; + + // Query the history storage contract for block 1's hash. + // The EIP-2935 contract uses raw calldata (not ABI-encoded): pass the block number + // as a 32-byte big-endian word directly. + let call_data: [u8; 32] = U256::from(1).to_be_bytes(); + let tx = TransactionRequest::default().with_to(HISTORY_STORAGE_ADDRESS).with_input(call_data); + let result = provider.call(tx.into()).await.unwrap(); + + let stored_hash = alloy_primitives::B256::from_slice(&result); + assert_eq!(stored_hash, block1_hash, "EIP-2935 contract should store parent block hash"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_no_system_call_on_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + // At genesis (block 0), the contract should exist but no system call should have + // written any parent hash into its storage. Check raw storage slot 0 directly. + let slot = provider.get_storage_at(HISTORY_STORAGE_ADDRESS, U256::from(0)).await.unwrap(); + assert_eq!(slot, U256::ZERO, "No hash should be stored in the contract at genesis"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn eip2935_not_deployed_before_prague() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); + assert!(code.is_empty(), "EIP-2935 contract should NOT be deployed before Prague"); +} diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 2d713c3d955e3..627fd2e8c59fc 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -1,16 +1,17 @@ use crate::utils::{http_provider, http_provider_with_signer}; -use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction}; +use alloy_consensus::{BlobTransactionSidecar, SidecarBuilder, SimpleCoder, Transaction}; use alloy_eips::{ - eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK_DENCUN}, Typed2718, + eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK_DENCUN}, }; -use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844}; -use alloy_primitives::{b256, Address, U256}; -use alloy_provider::Provider; +use alloy_primitives::{Address, U256, b256}; +use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; +use foundry_test_utils::rpc; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction() { @@ -35,12 +36,10 @@ async fn can_send_eip4844_transaction() { .with_max_fee_per_blob_gas(gas_price + 1) .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) - .with_blob_sidecar(sidecar) + .with_blob_sidecar_4844(sidecar) .value(U256::from(5)); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -48,6 +47,83 @@ async fn can_send_eip4844_transaction() { assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei } +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction_fork() { + let node_config = NodeConfig::test() + .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) + .with_fork_block_number(Some(23432306u64)) + .with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + let provider = handle.http_provider(); + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(alice) + .with_to(bob) + .with_blob_sidecar_4844(sidecar.clone()); + + let pending_tx = provider.send_transaction(tx.into()).await.unwrap(); + let receipt = pending_tx.get_receipt().await.unwrap(); + let tx_hash = receipt.transaction_hash; + + let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction_eth_send_transaction() { + let node_config = NodeConfig::test() + .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) + .with_fork_block_number(Some(23552208u64)) + .with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(alice) + .with_to(bob) + .with_blob_sidecar_4844(sidecar.clone()); + + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let receipt = pending_tx.get_receipt().await.unwrap(); + let tx_hash = receipt.transaction_hash; + + let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction_with_eip7594_sidecar_format() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into())); + let (api, handle) = spawn(node_config).await; + let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build_7594().unwrap(); + + let tx = + TransactionRequest::default().with_from(alice).with_to(bob).with_blob_sidecar_7594(sidecar); + + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let receipt = pending_tx.get_receipt().await.unwrap(); + let tx_hash = receipt.transaction_hash; + + let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); +} + #[tokio::test(flavor = "multi_thread")] async fn can_send_multiple_blobs_in_one_tx() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); @@ -75,10 +151,8 @@ async fn can_send_multiple_blobs_in_one_tx() { .with_max_fee_per_blob_gas(gas_price + 1) .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) - .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + .with_blob_sidecar_4844(sidecar); + let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -113,10 +187,8 @@ async fn cannot_exceed_six_blobs() { .with_max_fee_per_blob_gas(gas_price + 1) .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) - .with_blob_sidecar(sidecar); - let mut tx = WithOtherFields::new(tx); - - tx.populate_blob_hashes(); + .with_blob_sidecar_4844(sidecar); + let tx = WithOtherFields::new(tx); let err = provider.send_transaction(tx).await.unwrap_err(); @@ -153,11 +225,9 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { .with_max_fee_per_blob_gas(gas_price + 1) .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) - .with_blob_sidecar(sidecar); + .with_blob_sidecar_4844(sidecar); let mut tx = WithOtherFields::new(tx); - tx.populate_blob_hashes(); - let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; @@ -167,9 +237,8 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { let num_blobs_second = sidecar.clone().take().len() as u64; let sidecar = sidecar.build().unwrap(); - tx.set_blob_sidecar(sidecar); + tx.set_blob_sidecar_4844(sidecar); tx.set_nonce(1); - tx.populate_blob_hashes(); let second_tx = provider.send_transaction(tx).await.unwrap(); api.mine_one().await; @@ -224,7 +293,7 @@ async fn can_correctly_estimate_blob_gas_with_recommended_fillers() { let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar = sidecar.build().unwrap(); - let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(tx); // Send the transaction and wait for the broadcast. @@ -270,7 +339,7 @@ async fn can_correctly_estimate_blob_gas_with_recommended_fillers_with_signer() let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar = sidecar.build().unwrap(); - let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(tx); // Send the transaction and wait for the broadcast. @@ -346,3 +415,73 @@ async fn can_bypass_sidecar_requirement() { assert_eq!(tx.inner.ty(), 3); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_blobs_by_versioned_hash() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + let to = wallets[1].address(); + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); + + let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar_4844(sidecar.clone()) + .value(U256::from(5)); + + let tx = WithOtherFields::new(tx); + + let _receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let hash = sidecar.versioned_hash_for_blob(0).unwrap(); + // api.anvil_set_auto_mine(true).await.unwrap(); + let blob = api.anvil_get_blob_by_versioned_hash(hash).unwrap().unwrap(); + assert_eq!(blob, sidecar.blobs[0]); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_blobs_by_tx_hash() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + let to = wallets[1].address(); + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); + + let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar_4844(sidecar.clone()) + .value(U256::from(5)); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let hash = receipt.transaction_hash; + api.anvil_set_auto_mine(true).await.unwrap(); + let blobs = api.anvil_get_blob_by_tx_hash(hash).unwrap().unwrap(); + assert_eq!(blobs, sidecar.blobs); +} diff --git a/crates/anvil/tests/it/eip7702.rs b/crates/anvil/tests/it/eip7702.rs index 9424360119e4e..93678a2bb3634 100644 --- a/crates/anvil/tests/it/eip7702.rs +++ b/crates/anvil/tests/it/eip7702.rs @@ -1,13 +1,13 @@ use crate::utils::http_provider; -use alloy_consensus::{transaction::TxEip7702, SignableTransaction}; -use alloy_hardforks::EthereumHardfork; +use alloy_consensus::{SignableTransaction, transaction::TxEip7702}; use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; -use alloy_primitives::{bytes, U256}; +use alloy_primitives::{U256, bytes}; use alloy_provider::{PendingTransactionConfig, Provider}; use alloy_rpc_types::{Authorization, TransactionRequest}; use alloy_serde::WithOtherFields; -use alloy_signer::SignerSync; -use anvil::{spawn, NodeConfig}; +use alloy_signer::{Signature, SignerSync}; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip7702_tx() { @@ -153,3 +153,73 @@ async fn can_send_eip7702_request() { assert_eq!(log.topics().len(), 0); assert_eq!(log.data().data, log_data); } + +#[tokio::test(flavor = "multi_thread")] +async fn eip7702_authorization_bypass() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + + // deploy simple contract forwarding calldata to LOG0 + // PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7) + // PUSH1(25) RETURN + let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); + + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + + let from = wallets[0].address(); + let tx = TransactionRequest::default() + .with_from(from) + .into_create() + .with_nonce(0) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_input(logger_bytecode); + + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert!(receipt.status()); + + let contract = receipt.contract_address.unwrap(); + let authorization = Authorization { + chain_id: U256::from(31337u64), + address: contract, + nonce: provider.get_transaction_count(from).await.unwrap(), + }; + let fake_auth_sig = Signature::new(U256::ZERO, U256::ZERO, true); + api.anvil_impersonate_signature(fake_auth_sig.as_bytes().into(), from).await.unwrap(); + let authorization = authorization.into_signed(fake_auth_sig); + + let log_data = bytes!("11112222"); + let mut tx = TxEip7702 { + max_fee_per_gas: eip1559_est.max_fee_per_gas, + max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas, + gas_limit: 100000, + chain_id: 31337, + to: from, + input: bytes!("11112222"), + authorization_list: vec![authorization], + ..Default::default() + }; + let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); + + let tx = tx.into_signed(signature); + let mut encoded = Vec::new(); + tx.eip2718_encode(&mut encoded); + + let receipt = + provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap(); + let log = &receipt.inner.inner.logs()[0]; + // assert that log was from EOA which signed authorization + assert_eq!(log.address(), from); + assert_eq!(log.topics().len(), 0); + assert_eq!(log.data().data, log_data); +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index e4e1cda1a31d3..61b0326992942 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1,27 +1,39 @@ //! various fork related test use crate::{ - abi::{Greeter, ERC721}, + abi::{ERC721, Greeter}, utils::{http_provider, http_provider_with_signer}, }; use alloy_chains::NamedChain; +use alloy_eips::{ + eip7840::BlobParams, + eip7910::{EthConfig, SystemContract}, +}; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; -use alloy_primitives::{address, b256, bytes, uint, Address, Bytes, TxHash, TxKind, U256, U64}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, U64, U256, address, b256, bytes, uint}; use alloy_provider::Provider; use alloy_rpc_types::{ + AccountInfo, BlockId, BlockNumberOrTag, anvil::Forking, request::{TransactionInput, TransactionRequest}, state::EvmOverrides, - BlockId, BlockNumberOrTag, }; use alloy_serde::WithOtherFields; use alloy_signer_local::PrivateKeySigner; -use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; +use anvil::{EthereumHardfork, NodeConfig, NodeHandle, PrecompileFactory, eth::EthApi, spawn}; use foundry_common::provider::get_http_provider; use foundry_config::Config; +use foundry_evm_networks::NetworkConfigs; +use foundry_primitives::FoundryNetwork; use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint}; use futures::StreamExt; -use std::{sync::Arc, thread::sleep, time::Duration}; +use revm::precompile::PrecompileStatus; +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, + thread::sleep, + time::Duration, +}; const BLOCK_NUMBER: u64 = 14_608_400u64; const DEAD_BALANCE_AT_BLOCK_NUMBER: u128 = 12_556_069_338_441_120_059_867u128; @@ -31,9 +43,9 @@ const BLOCK_TIMESTAMP: u64 = 1_650_274_250u64; /// Represents an anvil fork of an anvil node #[expect(unused)] pub struct LocalFork { - origin_api: EthApi, + origin_api: EthApi, origin_handle: NodeHandle, - fork_api: EthApi, + fork_api: EthApi, fork_handle: NodeHandle, } @@ -546,7 +558,7 @@ async fn can_reset_fork_to_new_fork() { let optimism = next_rpc_endpoint(NamedChain::Optimism); api.anvil_reset(Some(Forking { - json_rpc_url: Some(optimism.to_string()), + json_rpc_url: Some(optimism.clone()), block_number: Some(124659890), })) .await @@ -820,7 +832,40 @@ async fn test_fork_init_base_fee() { } #[tokio::test(flavor = "multi_thread")] -async fn test_reset_fork_on_new_blocks() { +async fn test_fork_init_blob_base_fee_with_explicit_base_fee() { + let fork_rpc_url = rpc::next_http_archive_rpc_url(); + let fork_block_number = 24_127_158u64; + let (default_api, _) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(fork_rpc_url.clone())) + .with_fork_block_number(Some(fork_block_number)), + ) + .await; + let explicit_base_fee = default_api + .block_by_number(BlockNumberOrTag::Latest) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + let (explicit_api, _) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(fork_rpc_url)) + .with_fork_block_number(Some(fork_block_number)) + .with_base_fee(Some(explicit_base_fee)), + ) + .await; + + let default_blob_base_fee = default_api.blob_base_fee().unwrap(); + let explicit_blob_base_fee = explicit_api.blob_base_fee().unwrap(); + + assert!(default_blob_base_fee > U256::from(1)); + assert_eq!(explicit_blob_base_fee, default_blob_base_fee); +} + +#[tokio::test(flavor = "multi_thread")] +async fn flaky_test_reset_fork_on_new_blocks() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; @@ -830,7 +875,9 @@ async fn test_reset_fork_on_new_blocks() { let current_block = anvil_provider.get_block_number().await.unwrap(); - handle.task_manager().spawn_reset_on_new_polled_blocks(provider.clone(), api); + handle + .task_manager() + .spawn_reset_on_new_polled_blocks::(provider.clone(), api); let mut stream = provider .watch_blocks() @@ -1192,7 +1239,7 @@ async fn test_fork_reset_basefee() { // #[tokio::test(flavor = "multi_thread")] -async fn test_arbitrum_fork_dev_balance() { +async fn flaky_test_arbitrum_fork_dev_balance() { let (api, handle) = spawn( fork_config() .with_fork_block_number(None::) @@ -1209,8 +1256,8 @@ async fn test_arbitrum_fork_dev_balance() { // #[tokio::test(flavor = "multi_thread")] -async fn test_arb_fork_mining() { - let fork_block_number = 266137031u64; +async fn flaky_test_arb_fork_mining() { + let fork_block_number = 394274860u64; let fork_rpc = next_rpc_endpoint(NamedChain::Arbitrum); let (api, _handle) = spawn( fork_config() @@ -1230,7 +1277,7 @@ async fn test_arb_fork_mining() { // #[tokio::test(flavor = "multi_thread")] -async fn test_arbitrum_fork_block_number() { +async fn flaky_test_arbitrum_fork_block_number() { // fork to get initial block for test let (_, handle) = spawn( fork_config() @@ -1402,6 +1449,31 @@ async fn test_immutable_fork_transaction_hash() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_block_by_number_full_refetches_missing_cached_transactions() { + let (api, _) = spawn(fork_config()).await; + + let block = + api.block_by_number_full(BlockNumberOrTag::Number(BLOCK_NUMBER)).await.unwrap().unwrap(); + let block_txs = block.transactions.as_transactions().unwrap(); + let original_len = block_txs.len(); + let missing_hash = *block_txs[0].tx_hash(); + + let fork = api.backend.get_fork().unwrap(); + { + let mut storage = fork.storage.write(); + assert!(storage.transactions.remove(&missing_hash).is_some()); + } + + let refreshed = + api.block_by_number_full(BlockNumberOrTag::Number(BLOCK_NUMBER)).await.unwrap().unwrap(); + let refreshed_txs = refreshed.transactions.as_transactions().unwrap(); + + assert_eq!(refreshed_txs.len(), original_len); + assert_eq!(refreshed_txs[0].tx_hash(), &missing_hash); + assert!(fork.storage.read().transactions.contains_key(&missing_hash)); +} + // #[tokio::test(flavor = "multi_thread")] async fn test_fork_query_at_fork_block() { @@ -1490,6 +1562,31 @@ async fn test_set_erc20_balance() { assert_eq!(new_balance, value); } +#[tokio::test(flavor = "multi_thread")] +async fn test_set_erc20_allowance() { + let config: NodeConfig = fork_config(); + let owner = config.genesis_accounts[0].address(); + let spender = config.genesis_accounts[1].address(); + let (api, handle) = spawn(config).await; + + let provider = handle.http_provider(); + + alloy_sol_types::sol! { + #[sol(rpc)] + contract ERC20 { + function allowance(address owner, address spender) external view returns (uint256); + } + } + let dai = address!("0x6B175474E89094C44Da98b954EedeAC495271d0F"); + let erc20 = ERC20::new(dai, provider); + let value = U256::from(500); + + api.anvil_set_erc20_allowance(owner, spender, dai, value).await.unwrap(); + + let allowance = erc20.allowance(owner, spender).call().await.unwrap(); + assert_eq!(allowance, value); +} + #[tokio::test(flavor = "multi_thread")] async fn test_add_balance() { let config: NodeConfig = fork_config(); @@ -1515,7 +1612,7 @@ async fn test_reset_updates_cache_path_when_rpc_url_not_provided() { let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); - async fn get_block_from_cache_path(api: &mut EthApi) -> u64 { + async fn get_block_from_cache_path(api: &mut EthApi) -> u64 { let db = api.backend.get_db().read().await; let cache_path = db.maybe_inner().unwrap().cache().cache_path().unwrap(); cache_path @@ -1572,9 +1669,9 @@ async fn test_fork_get_account() { assert_eq!( alice_acc.balance, - alice_bal - - (U256::from(142) + - U256::from(receipt.gas_used as u128 * receipt.effective_gas_price)), + alice_bal + - (U256::from(142) + + U256::from(receipt.gas_used as u128 * receipt.effective_gas_price)), ); assert_eq!(alice_acc.nonce, alice_nonce + 1); @@ -1582,3 +1679,394 @@ async fn test_fork_get_account() { assert_eq!(alice_acc_init, alice_acc_prev_block); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_get_account_info() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + let info = provider + .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) + // predates fork + .number(BLOCK_NUMBER - 1) + .await + .unwrap(); + assert_eq!( + info, + AccountInfo { + balance: U256::from(14353753764795095694u64), + nonce: 6689, + code: Default::default(), + } + ); + + // Check account info at block number, see https://github.com/foundry-rs/foundry/issues/12072 + let info = provider + .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) + // predates fork + .number(BLOCK_NUMBER) + .await + .unwrap(); + assert_eq!( + info, + AccountInfo { + balance: U256::from(14352720829244098514u64), + nonce: 6690, + code: Default::default(), + } + ); + + // Mine and check account info at new block number, see https://github.com/foundry-rs/foundry/issues/12148 + api.evm_mine(None).await.unwrap(); + let info = provider + .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) + // predates fork + .number(BLOCK_NUMBER + 1) + .await + .unwrap(); + assert_eq!( + info, + AccountInfo { + balance: U256::from(14352720829244098514u64), + nonce: 6690, + code: Default::default(), + } + ); +} + +fn assert_hardfork_config( + config: &EthConfig, + expected_blob_params: &BlobParams, + expected_precompiles: &[Address], + expected_system_contracts: &BTreeMap, +) { + assert!(config.next.is_none()); + assert!(config.last.is_none()); + + let current = &config.current; + + assert_eq!(current.activation_time, 0); + assert_eq!(current.chain_id, 31337); + assert_eq!(current.fork_id, Bytes::from(vec![0, 0, 0, 0])); + + assert_eq!(¤t.blob_schedule, expected_blob_params); + + assert_eq!( + current.precompiles.values().copied().collect::>(), + expected_precompiles.iter().copied().collect::>(), + ); + + assert_eq!(current.system_contracts, *expected_system_contracts); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_config_with_cancun_hardfork() { + let (api, _handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into()))).await; + + let config = api.config().unwrap(); + + let expected_blob_params = BlobParams { + target_blob_count: 3, + max_blob_count: 6, + update_fraction: 3338477, + min_blob_fee: 1, + max_blobs_per_tx: 6, + blob_base_cost: 0, + }; + + // <= Cancun precompiles + let expected_precompiles = [ + address!("0000000000000000000000000000000000000001"), + address!("0000000000000000000000000000000000000002"), + address!("0000000000000000000000000000000000000003"), + address!("0000000000000000000000000000000000000004"), + address!("0000000000000000000000000000000000000005"), + address!("0000000000000000000000000000000000000006"), + address!("0000000000000000000000000000000000000007"), + address!("0000000000000000000000000000000000000008"), + address!("0000000000000000000000000000000000000009"), + address!("000000000000000000000000000000000000000a"), + ]; + + let expected_system_contracts = BTreeMap::from([( + SystemContract::BeaconRoots, + address!("000f3df6d732807ef1319fb7b8bb8522d0beac02"), + )]); + + assert_hardfork_config( + &config, + &expected_blob_params, + &expected_precompiles, + &expected_system_contracts, + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_config_with_prague_hardfork_with_celo() { + let (api, _handle) = spawn( + NodeConfig::test() + .with_hardfork(Some(EthereumHardfork::Prague.into())) + .with_networks(NetworkConfigs::with_celo()), + ) + .await; + + let config = api.config().unwrap(); + + let expected_blob_params = BlobParams { + target_blob_count: 6, + max_blob_count: 9, + update_fraction: 5007716, + min_blob_fee: 1, + max_blobs_per_tx: 9, + blob_base_cost: 0, + }; + + // <= Prague + Celo precompiles + let expected_precompiles = [ + address!("0000000000000000000000000000000000000001"), + address!("0000000000000000000000000000000000000002"), + address!("0000000000000000000000000000000000000003"), + address!("0000000000000000000000000000000000000004"), + address!("0000000000000000000000000000000000000005"), + address!("0000000000000000000000000000000000000006"), + address!("0000000000000000000000000000000000000007"), + address!("0000000000000000000000000000000000000008"), + address!("0000000000000000000000000000000000000009"), + address!("000000000000000000000000000000000000000a"), + address!("000000000000000000000000000000000000000b"), + address!("000000000000000000000000000000000000000c"), + address!("000000000000000000000000000000000000000d"), + address!("000000000000000000000000000000000000000e"), + address!("000000000000000000000000000000000000000f"), + address!("0000000000000000000000000000000000000010"), + address!("0000000000000000000000000000000000000011"), + address!("00000000000000000000000000000000000000fd"), // `celo transfer` + ]; + + let expected_system_contracts = BTreeMap::from([ + (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), + ( + SystemContract::ConsolidationRequestPredeploy, + address!("0000bbddc7ce488642fb579f8b00f3a590007251"), + ), + (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), + (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), + ( + SystemContract::WithdrawalRequestPredeploy, + address!("00000961ef480eb55e80d19ad83579a64c007002"), + ), + ]); + + assert_hardfork_config( + &config, + &expected_blob_params, + &expected_precompiles, + &expected_system_contracts, + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_config_with_osaka_hardfork() { + let (api, _handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into()))).await; + + let config = api.config().unwrap(); + + let expected_blob_params = BlobParams { + target_blob_count: 6, + max_blob_count: 9, + update_fraction: 5007716, + min_blob_fee: 1, + max_blobs_per_tx: 6, + blob_base_cost: 8192, + }; + + // <= Osaka precompiles + let expected_precompiles = [ + address!("0000000000000000000000000000000000000001"), + address!("0000000000000000000000000000000000000002"), + address!("0000000000000000000000000000000000000003"), + address!("0000000000000000000000000000000000000004"), + address!("0000000000000000000000000000000000000005"), + address!("0000000000000000000000000000000000000006"), + address!("0000000000000000000000000000000000000007"), + address!("0000000000000000000000000000000000000008"), + address!("0000000000000000000000000000000000000009"), + address!("000000000000000000000000000000000000000a"), + address!("000000000000000000000000000000000000000b"), + address!("000000000000000000000000000000000000000c"), + address!("000000000000000000000000000000000000000d"), + address!("000000000000000000000000000000000000000e"), + address!("000000000000000000000000000000000000000f"), + address!("0000000000000000000000000000000000000010"), + address!("0000000000000000000000000000000000000011"), + address!("0000000000000000000000000000000000000100"), + ]; + + let expected_system_contracts = BTreeMap::from([ + (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), + ( + SystemContract::ConsolidationRequestPredeploy, + address!("0000bbddc7ce488642fb579f8b00f3a590007251"), + ), + (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), + (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), + ( + SystemContract::WithdrawalRequestPredeploy, + address!("00000961ef480eb55e80d19ad83579a64c007002"), + ), + ]); + + assert_hardfork_config( + &config, + &expected_blob_params, + &expected_precompiles, + &expected_system_contracts, + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_config_with_osaka_hardfork_with_precompile_factory() { + #[derive(Debug)] + struct CustomPrecompileFactory; + + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec<(Address, alloy_evm::precompiles::DynPrecompile)> { + vec![( + address!("0x0000000000000000000000000000000000000071"), + alloy_evm::precompiles::DynPrecompile::from( + |input: alloy_evm::precompiles::PrecompileInput<'_>| { + Ok(revm::precompile::PrecompileOutput { + bytes: Bytes::copy_from_slice(input.data), + gas_used: 0, + gas_refunded: 0, + status: PrecompileStatus::Success, + state_gas_used: 0, + reservoir: input.reservoir, + }) + }, + ), + )] + } + } + + let (api, _handle) = spawn( + NodeConfig::test() + .with_hardfork(Some(EthereumHardfork::Osaka.into())) + .with_precompile_factory(CustomPrecompileFactory), + ) + .await; + + let config = api.config().unwrap(); + + let expected_blob_params = BlobParams { + target_blob_count: 6, + max_blob_count: 9, + update_fraction: 5007716, + min_blob_fee: 1, + max_blobs_per_tx: 6, + blob_base_cost: 8192, + }; + + // <= Osaka precompiles + custom precompile + let expected_precompiles = [ + address!("0000000000000000000000000000000000000001"), + address!("0000000000000000000000000000000000000002"), + address!("0000000000000000000000000000000000000003"), + address!("0000000000000000000000000000000000000004"), + address!("0000000000000000000000000000000000000005"), + address!("0000000000000000000000000000000000000006"), + address!("0000000000000000000000000000000000000007"), + address!("0000000000000000000000000000000000000008"), + address!("0000000000000000000000000000000000000009"), + address!("000000000000000000000000000000000000000a"), + address!("000000000000000000000000000000000000000b"), + address!("000000000000000000000000000000000000000c"), + address!("000000000000000000000000000000000000000d"), + address!("000000000000000000000000000000000000000e"), + address!("000000000000000000000000000000000000000f"), + address!("0000000000000000000000000000000000000010"), + address!("0000000000000000000000000000000000000011"), + address!("0000000000000000000000000000000000000071"), // `custom_echo` + address!("0000000000000000000000000000000000000100"), + ]; + let expected_system_contracts = BTreeMap::from([ + (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), + ( + SystemContract::ConsolidationRequestPredeploy, + address!("0000bbddc7ce488642fb579f8b00f3a590007251"), + ), + (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), + (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), + ( + SystemContract::WithdrawalRequestPredeploy, + address!("00000961ef480eb55e80d19ad83579a64c007002"), + ), + ]); + + assert_hardfork_config( + &config, + &expected_blob_params, + &expected_precompiles, + &expected_system_contracts, + ); +} + +// Regression tests: verify that `anvil_setRpcUrl` and `anvil_reset` keep +// `ClientForkConfig.fork_urls` in sync so that subsequent resets don't +// silently revert to stale URLs. + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_rpc_url_syncs_fork_config() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Verify initial fork URL + let fork = api.backend.get_fork().unwrap(); + assert_eq!(fork.config.read().fork_urls, vec![origin_url.clone()]); + + // Spawn a second origin to use as the new URL + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Set RPC URL via the API + api.anvil_set_rpc_url(new_url.clone()).await.unwrap(); + + // Verify ClientForkConfig is updated + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should be updated after anvil_setRpcUrl" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_reset_with_url_updates_fork_urls() { + // Spawn an origin node and fork off it + let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await; + let origin_url = origin_handle.http_endpoint(); + + let (api, _handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_url.clone()))).await; + + // Spawn a second origin + let (_origin2_api, origin2_handle) = spawn(NodeConfig::test()).await; + let new_url = origin2_handle.http_endpoint(); + + // Reset fork with a new URL + api.anvil_reset(Some(Forking { json_rpc_url: Some(new_url.clone()), block_number: None })) + .await + .unwrap(); + + // Verify the fork config uses the new URL, not the old one + let fork = api.backend.get_fork().unwrap(); + assert_eq!( + fork.config.read().fork_urls, + vec![new_url.clone()], + "ClientForkConfig.fork_urls should reflect the new URL after anvil_reset" + ); +} diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index f3bcd01ddfe47..2becfb386d9f9 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -2,11 +2,11 @@ use crate::utils::http_provider_with_signer; use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{uint, Address, U256, U64}; +use alloy_primitives::{Address, U64, U256, uint}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{eth::fees::INITIAL_BASE_FEE, spawn, NodeConfig}; +use anvil::{NodeConfig, eth::fees::INITIAL_BASE_FEE, spawn}; const GAS_TRANSFER: u64 = 21_000; @@ -181,10 +181,11 @@ async fn test_tip_above_fee_cap() { let res = provider.send_transaction(tx.clone()).await; assert!(res.is_err()); - assert!(res - .unwrap_err() - .to_string() - .contains("max priority fee per gas higher than max fee per gas")); + assert!( + res.unwrap_err() + .to_string() + .contains("max priority fee per gas higher than max fee per gas") + ); } #[tokio::test(flavor = "multi_thread")] @@ -215,3 +216,46 @@ async fn test_can_use_fee_history() { assert_eq!(latest_fee_history_fee, next_base_fee as u64); } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_estimate_gas_empty_data() { + let (api, handle) = spawn(NodeConfig::test()).await; + let accounts = handle.dev_accounts().collect::>(); + let from = accounts[0]; + let to = accounts[1]; + + let tx_without_data = + TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(1)); + + let gas_without_data = api + .estimate_gas(WithOtherFields::new(tx_without_data), None, Default::default()) + .await + .unwrap(); + + let tx_with_empty_data = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1)) + .with_input(vec![]); + + let gas_with_empty_data = api + .estimate_gas(WithOtherFields::new(tx_with_empty_data), None, Default::default()) + .await + .unwrap(); + + let tx_with_data = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1)) + .with_input(vec![0x12, 0x34]); + + let gas_with_data = api + .estimate_gas(WithOtherFields::new(tx_with_data), None, Default::default()) + .await + .unwrap(); + + assert_eq!(gas_without_data, U256::from(GAS_TRANSFER)); + assert_eq!(gas_with_empty_data, U256::from(GAS_TRANSFER)); + assert!(gas_with_data > U256::from(GAS_TRANSFER)); + assert_eq!(gas_without_data, gas_with_empty_data); +} diff --git a/crates/anvil/tests/it/genesis.rs b/crates/anvil/tests/it/genesis.rs index 627e515d1065c..d60cb089128ce 100644 --- a/crates/anvil/tests/it/genesis.rs +++ b/crates/anvil/tests/it/genesis.rs @@ -4,7 +4,7 @@ use crate::fork::fork_config; use alloy_genesis::Genesis; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use std::str::FromStr; const GENESIS: &str = r#"{ diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index e5d99e01b333d..c65130bfc0f23 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -3,7 +3,7 @@ use crate::{init_tracing, utils::connect_pubsub}; use alloy_primitives::U256; use alloy_provider::Provider; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use futures::StreamExt; use tempfile::TempDir; @@ -23,7 +23,6 @@ fn ipc_config() -> (Option, NodeConfig) { } #[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "TODO")] async fn can_get_block_number_ipc() { init_tracing(); @@ -40,7 +39,6 @@ async fn can_get_block_number_ipc() { } #[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "TODO")] async fn test_sub_new_heads_ipc() { init_tracing(); diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index 5dbb28c2dd58c..df5086e505f9a 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -5,11 +5,12 @@ use crate::{ utils::{http_provider_with_signer, ws_provider_with_signer}, }; use alloy_network::EthereumWallet; -use alloy_primitives::{map::B256HashSet, B256}; +use alloy_primitives::{B256, map::B256HashSet}; use alloy_provider::Provider; use alloy_rpc_types::{BlockNumberOrTag, Filter}; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use futures::StreamExt; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { @@ -130,7 +131,7 @@ async fn get_all_events() { .collect::, _>>() .unwrap() .into_iter() - .flat_map(|receipt| receipt.unwrap().inner.inner.inner.receipt.logs) + .flat_map(|receipt| receipt.unwrap().inner.inner.inner.receipt.logs.clone()) .collect::>(); assert_eq!(receipt_logs.len(), logs.len()); @@ -195,3 +196,20 @@ async fn watch_events() { assert_eq!(log.1.block_hash.unwrap(), hash); } } + +#[tokio::test(flavor = "multi_thread")] +async fn get_logs_unknown_block_hash_returns_error() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let unknown_hash = + B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let filter = Filter::new().at_block_hash(unknown_hash); + + let err = provider.get_logs(&filter).await.unwrap_err(); + assert!( + err.to_string().contains("unknown block"), + "expected `unknown block` in error, got: {err}" + ); +} diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index c8c9c437e96af..84cc9b861bbbf 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -2,6 +2,8 @@ mod abi; mod anvil; mod anvil_api; mod api; +mod beacon_api; +mod eip2935; mod eip4844; mod eip7702; mod fork; @@ -9,6 +11,7 @@ mod gas; mod genesis; mod ipc; mod logs; +#[cfg(feature = "optimism")] mod optimism; mod otterscan; mod proof; @@ -16,17 +19,13 @@ mod pubsub; mod revert; mod sign; mod simulate; +#[cfg(feature = "cmd")] mod state; +mod tempo; mod traces; mod transaction; mod txpool; pub mod utils; mod wsapi; -pub(crate) fn init_tracing() { - let _ = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); -} - -fn main() {} +pub use foundry_test_utils::init_tracing; diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index f871cca724a97..c900e0569c9db 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -2,14 +2,16 @@ use crate::utils::{http_provider, http_provider_with_signer}; use alloy_eips::eip2718::Encodable2718; -use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{b256, Address, TxHash, TxKind, U256}; +use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder}; +use alloy_primitives::{Address, Bloom, TxHash, TxKind, U256, b256}; use alloy_provider::Provider; -use alloy_rpc_types::TransactionRequest; +use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, eth::fees::INITIAL_BASE_FEE, spawn}; +use foundry_evm_networks::NetworkConfigs; use op_alloy_consensus::TxDeposit; use op_alloy_rpc_types::OpTransactionFields; +use serde_json::{Value, json}; #[tokio::test(flavor = "multi_thread")] async fn test_deposits_not_supported_if_optimism_disabled() { @@ -35,7 +37,6 @@ async fn test_deposits_not_supported_if_optimism_disabled() { deposit_receipt_version: None, }; - // TODO: Test this let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = WithOtherFields { inner: tx, other }; @@ -48,7 +49,8 @@ async fn test_deposits_not_supported_if_optimism_disabled() { #[tokio::test(flavor = "multi_thread")] async fn test_send_value_deposit_transaction() { // enable the Optimism flag - let (api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; + let (api, handle) = + spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -95,7 +97,8 @@ async fn test_send_value_deposit_transaction() { #[tokio::test(flavor = "multi_thread")] async fn test_send_value_raw_deposit_transaction() { // enable the Optimism flag - let (api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; + let (api, handle) = + spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -127,7 +130,7 @@ async fn test_send_value_raw_deposit_transaction() { }; let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = WithOtherFields { inner: tx, other }; - let tx_envelope = tx.build(&signer).await.unwrap(); + let tx_envelope: alloy_network::AnyTxEnvelope = tx.build(&signer).await.unwrap(); let mut tx_buffer = Vec::with_capacity(tx_envelope.encode_2718_len()); tx_envelope.encode_2718(&mut tx_buffer); let tx_encoded = tx_buffer.as_slice(); @@ -151,7 +154,8 @@ async fn test_send_value_raw_deposit_transaction() { #[tokio::test(flavor = "multi_thread")] async fn test_deposit_transaction_hash_matches_sepolia() { // enable the Optimism flag - let (_api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; + let (_api, handle) = + spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -182,7 +186,8 @@ async fn test_deposit_transaction_hash_matches_sepolia() { #[tokio::test(flavor = "multi_thread")] async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value() { // enable the Optimism flag - let (_api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; + let (_api, handle) = + spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let provider = http_provider(&handle.http_endpoint()); @@ -220,3 +225,124 @@ async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value( // recipient should've received the entire deposited value assert_eq!(recipient_new_balance, U256::from(send_value)); } + +#[test] +fn preserves_op_fields_in_convert_to_anvil_receipt() { + let receipt_json = json!({ + "status": "0x1", + "cumulativeGasUsed": "0x74e483", + "logs": [], + "logsBloom": Bloom::default(), + "type": "0x2", + "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d", + "transactionIndex": "0x10", + "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d", + "blockNumber": "0x7b1ab93", + "gasUsed": "0xc222", + "effectiveGasPrice": "0x18961", + "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0", + "to": "0x4200000000000000000000000000000000000000", + "contractAddress": Value::Null, + "l1BaseFeeScalar": "0x146b", + "l1BlobBaseFee": "0x6a83078", + "l1BlobBaseFeeScalar": "0xf79c5", + "l1Fee": "0x51a9af7fd3", + "l1GasPrice": "0x972fe4acc", + "l1GasUsed": "0x640", + }); + + let receipt: alloy_network::AnyTransactionReceipt = + serde_json::from_value(receipt_json).expect("valid receipt json"); + + let converted = + foundry_primitives::FoundryTxReceipt::try_from(receipt).expect("conversion should succeed"); + let converted_json = serde_json::to_value(&converted).expect("serialize to json"); + + for (key, expected) in [ + ("l1Fee", "0x51a9af7fd3"), + ("l1GasPrice", "0x972fe4acc"), + ("l1GasUsed", "0x640"), + ("l1BaseFeeScalar", "0x146b"), + ("l1BlobBaseFee", "0x6a83078"), + ("l1BlobBaseFeeScalar", "0xf79c5"), + ] { + let got = converted_json.get(key).and_then(Value::as_str); + assert_eq!(got, Some(expected), "field `{key}` mismatch"); + } +} + +const GAS_TRANSFER: u64 = 21_000; + +/// Test that Optimism uses Canyon base fee params instead of Ethereum params. +/// +/// Optimism Canyon uses different EIP-1559 parameters: +/// - elasticity_multiplier: 6 (vs Ethereum's 2) +/// - base_fee_max_change_denominator: 250 (vs Ethereum's 8) +/// +/// This means with a full block: +/// - Ethereum: base_fee increases by base_fee * 1 / 8 = 12.5% +/// - Optimism: base_fee increases by base_fee * 5 / 250 = 2% +#[tokio::test(flavor = "multi_thread")] +async fn test_optimism_base_fee_params() { + // Spawn an Optimism node with a gas limit equal to one transfer (full block scenario) + let (_api, handle) = spawn( + NodeConfig::test() + .with_networks(NetworkConfigs::with_optimism()) + .with_base_fee(Some(INITIAL_BASE_FEE)) + .with_gas_limit(Some(GAS_TRANSFER)), + ) + .await; + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + // Send first transaction to fill the block + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + // Send second transaction to fill the next block + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + assert!(next_base_fee > base_fee, "base fee should increase with full block"); + + // Optimism Canyon formula: base_fee * (elasticity - 1) / denominator = base_fee * 5 / 250 + // = INITIAL_BASE_FEE * 5 / 250 = 1_000_000_000 * 5 / 250 = 20_000_000 + // + // Note: Ethereum would be INITIAL_BASE_FEE + 125_000_000 (12.5% increase) + let expected_op_increase = INITIAL_BASE_FEE * 5 / 250; // 2% increase = 20_000_000 + assert_eq!( + next_base_fee, + INITIAL_BASE_FEE + expected_op_increase, + "Optimism should use Canyon base fee params (2% max increase), not Ethereum's (12.5%)" + ); + + // Explicitly verify it's NOT using Ethereum params (which would give 12.5% increase) + let ethereum_increase = INITIAL_BASE_FEE / 8; // 125_000_000 + assert_ne!( + next_base_fee, + INITIAL_BASE_FEE + ethereum_increase, + "Should not be using Ethereum base fee params" + ); +} diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index ffd70d1349ddb..8943e025780a0 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,17 +1,17 @@ //! Tests for otterscan endpoints. use crate::abi::Multicall; -use alloy_hardforks::EthereumHardfork; use alloy_network::TransactionResponse; -use alloy_primitives::{address, Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, U256, address}; use alloy_provider::Provider; use alloy_rpc_types::{ - trace::otterscan::{InternalOperation, OperationType, TraceEntry}, BlockNumberOrTag, TransactionRequest, + trace::otterscan::{InternalOperation, OperationType, TraceEntry}, }; use alloy_serde::WithOtherFields; -use alloy_sol_types::{sol, SolCall, SolError, SolValue}; -use anvil::{spawn, NodeConfig}; +use alloy_sol_types::{SolCall, SolError, SolValue, sol}; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs index c1dfa74d3d07b..a03ac47646405 100644 --- a/crates/anvil/tests/it/proof.rs +++ b/crates/anvil/tests/it/proof.rs @@ -1,11 +1,12 @@ //! tests for `eth_getProof` -use alloy_primitives::{address, fixed_bytes, Address, Bytes, B256, U256}; -use anvil::{eth::EthApi, spawn, NodeConfig}; +use alloy_primitives::{Address, B256, Bytes, U256, address, fixed_bytes}; +use anvil::{NodeConfig, eth::EthApi, spawn}; +use foundry_primitives::FoundryNetwork; use std::{collections::BTreeMap, str::FromStr}; async fn verify_account_proof( - api: &EthApi, + api: &EthApi, address: Address, proof: impl IntoIterator, ) { @@ -17,7 +18,7 @@ async fn verify_account_proof( } async fn verify_storage_proof( - api: &EthApi, + api: &EthApi, address: Address, slot: B256, proof: impl IntoIterator, @@ -55,14 +56,17 @@ async fn test_account_proof() { .await .unwrap(); + // Note: proof values account for EIP-2935 history storage contract deployed at genesis. verify_account_proof(&api, address!("0x2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", - "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", @@ -70,17 +74,19 @@ async fn test_account_proof() { ]).await; verify_account_proof(&api, address!("0x62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x1ed9b1dd266b607ee278726d324b855a093394a6"), [ - "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", + "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", - "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; } diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index 04515d83aa02b..5b96c7f85de7f 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -8,7 +8,7 @@ use alloy_pubsub::Subscription; use alloy_rpc_types::{Block as AlloyBlock, Filter, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use futures::StreamExt; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index 47e217419572f..a15454fa5593e 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -1,11 +1,11 @@ use crate::abi::VendingMachine; use alloy_network::TransactionBuilder; -use alloy_primitives::{bytes, U256}; +use alloy_primitives::{U256, bytes}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn test_deploy_reverting() { @@ -28,6 +28,38 @@ async fn test_deploy_reverting() { assert!(!receipt.inner.inner.status()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_invalid_opcode_rpc_error_code() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + // Deploy a contract whose runtime bytecode is the invalid opcode 0xfe. + let code = bytes!("60fe60005360016000f3"); + let tx = TransactionRequest::default().from(sender).with_deploy_code(code); + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract = receipt.contract_address.unwrap(); + + for (method, params) in [ + ("eth_call", serde_json::json!([{ "from": sender, "to": contract }, "latest"])), + ("eth_estimateGas", serde_json::json!([{ "from": sender, "to": contract }])), + ] { + let error = rpc_error(&handle.http_endpoint(), method, params).await; + assert_eq!(error["code"], serde_json::json!(-32003), "{error}"); + assert!(error.get("data").is_none(), "{error}"); + + let message = error["message"].as_str().unwrap(); + assert!(message.contains("EVM error InvalidFEOpcode"), "{error}"); + assert!(!message.contains("execution reverted"), "{error}"); + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { sol!( @@ -124,3 +156,21 @@ async fn test_solc_revert_custom_errors() { let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); } + +async fn rpc_error(endpoint: &str, method: &str, params: serde_json::Value) -> serde_json::Value { + let response = reqwest::Client::new() + .post(endpoint) + .json(&serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": method, + "params": params, + })) + .send() + .await + .unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let body = response.json::().await.unwrap(); + body.get("error").cloned().unwrap_or_else(|| panic!("expected JSON-RPC error, got {body}")) +} diff --git a/crates/anvil/tests/it/sign.rs b/crates/anvil/tests/it/sign.rs index 0ff56f3641199..b6481115c3be0 100644 --- a/crates/anvil/tests/it/sign.rs +++ b/crates/anvil/tests/it/sign.rs @@ -6,7 +6,7 @@ use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_signer::Signer; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data() { @@ -304,7 +304,10 @@ async fn can_sign_transaction() { // sign it via the eth_signTransaction API let signed_tx = api.sign_transaction(tx).await.unwrap(); - assert_eq!(signed_tx, "0x02f866827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c001a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da"); + assert_eq!( + signed_tx, + "0x02f866827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c001a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da" + ); } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/anvil/tests/it/simulate.rs b/crates/anvil/tests/it/simulate.rs index e83a4c5a05955..1491b53a30039 100644 --- a/crates/anvil/tests/it/simulate.rs +++ b/crates/anvil/tests/it/simulate.rs @@ -1,13 +1,13 @@ //! general eth api tests -use alloy_primitives::{address, TxKind, U256}; +use alloy_primitives::{TxKind, U256, address}; use alloy_rpc_types::{ + BlockOverrides, request::TransactionRequest, simulate::{SimBlock, SimulatePayload}, state::{AccountOverride, StateOverridesBuilder}, - BlockOverrides, }; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use foundry_test_utils::rpc; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs index fdb4c8b128679..2c677231e07ad 100644 --- a/crates/anvil/tests/it/state.rs +++ b/crates/anvil/tests/it/state.rs @@ -2,12 +2,18 @@ use crate::abi::Greeter; use alloy_network::{ReceiptResponse, TransactionBuilder}; -use alloy_primitives::{address, utils::Unit, Bytes, Uint, U256}; +use alloy_primitives::{Bytes, U256, Uint, address, b256, utils::Unit}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, eth::backend::db::SerializableState, spawn}; use foundry_test_utils::rpc::next_http_archive_rpc_url; +use revm::{ + context_interface::block::BlobExcessGasAndPrice, + primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, +}; +use serde_json::json; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn can_load_state() { @@ -42,6 +48,65 @@ async fn can_load_state() { assert_eq!(num, U256::from(num_from_tag)); } +// +#[tokio::test(flavor = "multi_thread")] +async fn finalized_block_hash_consistent_after_load_state() { + use alloy_eips::BlockNumberOrTag; + + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, _handle) = spawn(NodeConfig::test()).await; + + api.mine_one().await; + + // Get the original genesis block hash + let original_genesis = api.block_by_number(BlockNumberOrTag::Number(0)).await.unwrap().unwrap(); + let original_genesis_hash = original_genesis.header.hash; + + let state = api.serialized_state(false).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &state).unwrap(); + + // Load state with a different genesis timestamp. + // The new instance will create its own genesis block with a different timestamp, + // but then load_state should overwrite it. The bug is that genesis_hash field isn't updated. + let (api, _handle) = spawn( + NodeConfig::test() + .with_genesis_timestamp(Some(original_genesis.header.timestamp + 1000)) + .with_init_state_path(state_file), + ) + .await; + + // Query finalized block - should return genesis (block 0) since best_number is small + let finalized_block = api.block_by_number(BlockNumberOrTag::Finalized).await.unwrap().unwrap(); + let finalized_hash = finalized_block.header.hash; + let finalized_number = finalized_block.header.number; + + // Query block by the finalized block's number directly + let block_by_number = + api.block_by_number(BlockNumberOrTag::Number(finalized_number)).await.unwrap().unwrap(); + let block_by_number_hash = block_by_number.header.hash; + + // Verify the loaded genesis matches the original + assert_eq!( + block_by_number_hash, original_genesis_hash, + "Loaded genesis should match original genesis hash" + ); + + // Both finalized and block 0 should return the same hash + assert_eq!( + finalized_hash, block_by_number_hash, + "Finalized block hash should match block queried by number" + ); + + // Also verify Earliest block tag returns consistent hash + let earliest_block = api.block_by_number(BlockNumberOrTag::Earliest).await.unwrap().unwrap(); + assert_eq!( + earliest_block.header.hash, original_genesis_hash, + "Earliest block hash should match original genesis hash" + ); +} + #[tokio::test(flavor = "multi_thread")] async fn can_load_existing_state_legacy() { let state_file = "test-data/state-dump-legacy.json"; @@ -309,3 +374,436 @@ async fn computes_next_base_fee_after_loading_state() { let base_fee_after_reload = api.backend.fees().base_fee(); assert_eq!(base_fee_after_reload, base_fee_after_one_tx); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_backward_compatibility_deserialization_v1_2() { + let old_format = r#"{ + "block": { + "number": "0x5", + "coinbase": "0x1234567890123456789012345678901234567890", + "timestamp": "0x688c83b5", + "gas_limit": "0x1c9c380", + "basefee": "0x3b9aca00", + "difficulty": "0x0", + "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": {}, + "best_block_number": "0x5", + "blocks": [], + "transactions": [] + }"#; + + let state: SerializableState = serde_json::from_str(old_format).unwrap(); + assert!(state.block.is_some()); + let block_env = state.block.unwrap(); + assert_eq!(block_env.number, U256::from(5)); + // Verify coinbase was converted to beneficiary + assert_eq!(block_env.beneficiary, address!("0x1234567890123456789012345678901234567890")); + + // New format with beneficiary and numeric values + let new_format = r#"{ + "block": { + "number": 6, + "beneficiary": "0x1234567890123456789012345678901234567891", + "timestamp": 1751619509, + "gas_limit": 30000000, + "basefee": 1000000000, + "difficulty": "0x0", + "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": {}, + "best_block_number": 6, + "blocks": [], + "transactions": [] + }"#; + + let state: SerializableState = serde_json::from_str(new_format).unwrap(); + assert!(state.block.is_some()); + let block_env = state.block.unwrap(); + assert_eq!(block_env.number, U256::from(6)); + assert_eq!(block_env.beneficiary, address!("0x1234567890123456789012345678901234567891")); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_backward_compatibility_mixed_formats_deserialization_v1_2() { + let mixed_format = json!({ + "block": { + "number": "0x3", + "coinbase": "0x1111111111111111111111111111111111111111", + "timestamp": 1751619509, + "gas_limit": "0x1c9c380", + "basefee": 1000000000, + "difficulty": "0x0", + "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": {}, + "best_block_number": 3, + "blocks": [], + "transactions": [] + }); + + let state: SerializableState = serde_json::from_str(&mixed_format.to_string()).unwrap(); + let block_env = state.block.unwrap(); + + assert_eq!(block_env.number, U256::from(3)); + assert_eq!(block_env.beneficiary, address!("0x1111111111111111111111111111111111111111")); + assert_eq!(block_env.timestamp, U256::from(1751619509)); + assert_eq!(block_env.gas_limit, 0x1c9c380); + assert_eq!(block_env.basefee, 1_000_000_000); + assert_eq!(block_env.difficulty, U256::ZERO); + assert_eq!( + block_env.prevrandao.unwrap(), + b256!("ecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5") + ); + + let blob = block_env.blob_excess_gas_and_price.unwrap(); + assert_eq!(blob.excess_blob_gas, 0); + assert_eq!(blob.blob_gasprice, 1); + + assert_eq!(state.best_block_number, Some(3)); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_backward_compatibility_optional_fields_deserialization_v1_2() { + let partial_old_format = json!({ + "block": { + "number": "0x1", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x688c83b5", + "gas_limit": "0x1c9c380", + "basefee": "0x3b9aca00", + "difficulty": "0x0", + "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5" + // Missing blob_excess_gas_and_price - should be None + }, + "accounts": {}, + "best_block_number": "0x1" + // Missing blocks and transactions arrays - should default to empty + }); + + let state: SerializableState = serde_json::from_str(&partial_old_format.to_string()).unwrap(); + + let block_env = state.block.unwrap(); + assert_eq!(block_env.number, U256::from(1)); + assert_eq!(block_env.beneficiary, address!("0x0000000000000000000000000000000000000000")); + assert_eq!(block_env.timestamp, U256::from(0x688c83b5)); + assert_eq!(block_env.gas_limit, 0x1c9c380); + assert_eq!(block_env.basefee, 0x3b9aca00); + assert_eq!(block_env.difficulty, U256::ZERO); + assert_eq!( + block_env.prevrandao.unwrap(), + b256!("ecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5") + ); + assert_eq!( + block_env.blob_excess_gas_and_price, + Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE)) + ); + + assert_eq!(state.best_block_number, Some(1)); + assert!(state.blocks.is_empty()); + assert!(state.transactions.is_empty()); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_backward_compatibility_state_dump_deserialization_v1_2() { + let tmp = tempfile::tempdir().unwrap(); + let old_state_file = tmp.path().join("old_state.json"); + + // A simple state dump with a single block containing one transaction of a Counter contract + // deployment. + let old_state_json = json!({ + "block": { + "number": "0x1", + "coinbase": "0x0000000000000000000000000000000000000001", + "timestamp": "0x688c83b5", + "gas_limit": "0x1c9c380", + "basefee": "0x3b9aca00", + "difficulty": "0x0", + "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "nonce": 0, + "balance": "0x26481", + "code": "0x", + "storage": {} + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 0, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x5fbdb2315678afecb367f032d93f642f64180aa3": { + "nonce": 1, + "balance": "0x0", + "code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 1, + "balance": "0x21e19e03b1e9e55d17f", + "code": "0x", + "storage": {} + } + }, + "best_block_number": "0x1", + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x688c83b0", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "transactions": [], + "ommers": [] + }, + { + "header": { + "parentHash": "0x25097583380d90c4ac42b454ed7d2f59450ed3a16fdcf7f7bd93295aa126a901", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6e005b459ac9acefa5f47fd2d7ff8ca81a91794fdc5f7fbc3e2faeeaefe5d516", + "transactionsRoot": "0x59f0457ec18e2181c186f49d9ac911b33b5f4f55db5c494022147346bcfc9837", + "receiptsRoot": "0x88ac48b910f796aab7407814203b3a15a04a812f387e92efeccc92a2ecf809da", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1", + "gasLimit": "0x1c9c380", + "gasUsed": "0x26481", + "timestamp": "0x688c83b5", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "transactions": [ + { + "transaction": { + "type": "0x2", + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x31c41", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": null, + "value": "0x0", + "accessList": [], + "input": "0x6080604052348015600e575f5ffd5b506101e18061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", + "r": "0xa7398e28ca9a56b423cab87aeb3612378bac9c5684aaf778a78943f2637fd731", + "s": "0x583511da658f564253c8c0f9ee1820ef370f23556be504b304ac1292f869d9a0", + "yParity": "0x0", + "v": "0x0", + "hash": "0x9e4846328caa09cbe8086d11b7e115adf70390e79ff203d8e5f37785c2a890be" + }, + "impersonated_sender": null + } + ], + "ommers": [] + } + ], + "transactions": [ + { + "info": { + "transaction_hash": "0x9e4846328caa09cbe8086d11b7e115adf70390e79ff203d8e5f37785c2a890be", + "transaction_index": 0, + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "contract_address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "traces": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "0x6080604052348015600e575f5ffd5b506101e18061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", + "output": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", + "gas_used": 96345, + "gas_limit": 143385, + "gas_refund_counter": 0, + "status": "Return", + "steps": [], + "decoded": null + }, + "logs": [], + "ordering": [] + } + ], + "exit": "Return", + "out": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", + "nonce": 0, + "gas_used": 156801 + }, + "receipt": { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x26481", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x313ea0d32d662434a55a20d7c58544e6baaea421b6eccf4b68392dec2a76d771", + "block_number": 1 + } + ], + "historical_states": null + }); + + // Write the old state to file. + foundry_common::fs::write_json_file(&old_state_file, &old_state_json).unwrap(); + + // Test deserializing the old state dump directly. + let deserialized_state: SerializableState = serde_json::from_value(old_state_json).unwrap(); + + // Verify the old state was loaded correctly with `coinbase` to `beneficiary` conversion. + let block_env = deserialized_state.block.unwrap(); + assert_eq!(block_env.number, U256::from(1)); + assert_eq!(block_env.beneficiary, address!("0000000000000000000000000000000000000001")); + assert_eq!(block_env.gas_limit, 0x1c9c380); + assert_eq!(block_env.basefee, 0x3b9aca00); + + // Verify best_block_number hex string parsing. + assert_eq!(deserialized_state.best_block_number, Some(1)); + + // Verify account data was preserved. + assert_eq!(deserialized_state.accounts.len(), 13); + + // Test specific accounts from the old dump. + let deployer_addr = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(); + let deployer_account = deserialized_state.accounts.get(&deployer_addr).unwrap(); + assert_eq!(deployer_account.nonce, 1); + assert_eq!(deployer_account.balance, U256::from_str("0x21e19e03b1e9e55d17f").unwrap()); + + // Test contract account. + let contract_addr = "0x5fbdb2315678afecb367f032d93f642f64180aa3".parse().unwrap(); + let contract_account = deserialized_state.accounts.get(&contract_addr).unwrap(); + assert_eq!(contract_account.nonce, 1); + assert_eq!(contract_account.balance, U256::ZERO); + assert!(!contract_account.code.is_empty()); + + // Verify blocks and transactions are preserved. + assert_eq!(deserialized_state.blocks.len(), 2); + assert_eq!(deserialized_state.transactions.len(), 1); + + // Test that Anvil can load this old state dump. + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(&old_state_file)).await; + + // Verify the state was loaded correctly. + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, U256::from(1)); + + // Verify account balances are preserved. + let provider = _handle.http_provider(); + let deployer_balance = provider.get_balance(deployer_addr).await.unwrap(); + assert_eq!(deployer_balance, U256::from_str("0x21e19e03b1e9e55d17f").unwrap()); + let contract_balance = provider.get_balance(contract_addr).await.unwrap(); + assert_eq!(contract_balance, U256::ZERO); + + // Verify contract code is preserved. + let contract_code = provider.get_code_at(contract_addr).await.unwrap(); + assert!(!contract_code.is_empty()); +} diff --git a/crates/anvil/tests/it/tempo.rs b/crates/anvil/tests/it/tempo.rs new file mode 100644 index 0000000000000..65fde8ebe0c75 --- /dev/null +++ b/crates/anvil/tests/it/tempo.rs @@ -0,0 +1,2790 @@ +//! Tests for Tempo-specific features in Anvil. +//! +//! This module tests Tempo's payment-native protocol features including: +//! - TIP20 fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD) +//! - Tempo precompiles initialization (sentinel bytecode) +//! - Native value transfer rejection +//! - Basic transaction behavior in Tempo mode + +use std::num::NonZeroU64; + +use alloy_consensus::Typed2718; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{ReceiptResponse, TransactionBuilder, TransactionResponse}; +use alloy_primitives::{Address, Bytes, TxKind, U256, address}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use alloy_signer_local::PrivateKeySigner; +use alloy_sol_types::sol; +use anvil::{NodeConfig, spawn}; +use foundry_evm::core::tempo::{PATH_USD_ADDRESS, TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS}; +use tempo_alloy::primitives::TempoTxEnvelope; +use tempo_precompiles::{DEFAULT_FEE_TOKEN, TIP_FEE_MANAGER_ADDRESS}; +use tempo_primitives::{ + AASigned, TempoSignature, TempoTransaction, + transaction::{Call, PrimitiveSignature}, +}; + +const PATH_USD: Address = PATH_USD_ADDRESS; +const ALPHA_USD: Address = address!("0x20C0000000000000000000000000000000000001"); +const BETA_USD: Address = address!("0x20C0000000000000000000000000000000000002"); +const THETA_USD: Address = address!("0x20C0000000000000000000000000000000000003"); + +/// Gas limit for TIP20 transfer calls (precompile interactions need more gas). +const TIP20_TRANSFER_GAS: u64 = 300_000; + +sol! { + #[sol(rpc)] + interface IFeeManagerRpc { + function userTokens(address user) external view returns (address); + function validatorTokens(address validator) external view returns (address); + + struct Pool { + uint128 reserveUserToken; + uint128 reserveValidatorToken; + } + + function getPool(address userToken, address validatorToken) external view returns (Pool memory); + function getPoolId(address userToken, address validatorToken) external pure returns (bytes32); + function totalSupply(bytes32 poolId) external view returns (uint256); + } +} + +sol! { + #[sol(rpc)] + interface IERC20 { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); + } +} + +// ============================================================================ +// Tempo Genesis: Precompile Initialization +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_precompiles_have_code() { + let (api, _handle) = spawn(NodeConfig::test_tempo()).await; + + // Tempo precompiles should have sentinel bytecode (0xef) + for addr in TEMPO_PRECOMPILE_ADDRESSES { + let code = api.get_code(*addr, None).await.unwrap(); + assert!(!code.is_empty(), "Precompile {addr} should have code"); + } + + // All TIP20 token addresses should also have code + for addr in TEMPO_TIP20_TOKENS { + let code = api.get_code(*addr, None).await.unwrap(); + assert!(!code.is_empty(), "Token {addr} should have code deployed"); + } +} + +// ============================================================================ +// Tempo Genesis: Fee Token Metadata +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tip20_token_metadata() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let tokens = [ + (PATH_USD, "PathUSD", "PathUSD"), + (ALPHA_USD, "AlphaUSD", "AlphaUSD"), + (BETA_USD, "BetaUSD", "BetaUSD"), + (THETA_USD, "ThetaUSD", "ThetaUSD"), + ]; + + for (addr, expected_name, expected_symbol) in tokens { + let token = IERC20::new(addr, &provider); + let name = token.name().call().await.unwrap(); + let symbol = token.symbol().call().await.unwrap(); + let decimals = token.decimals().call().await.unwrap(); + + assert_eq!(name, expected_name, "Token at {addr} name mismatch"); + assert_eq!(symbol, expected_symbol, "Token at {addr} symbol mismatch"); + assert_eq!(decimals, 6, "All TIP20 tokens should use 6 decimals"); + } +} + +// ============================================================================ +// Tempo Genesis: Fee Token Balances +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_fee_token_balances_minted_to_dev_accounts() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let dev_accounts: Vec

= handle.dev_accounts().collect(); + assert!(!dev_accounts.is_empty()); + + for account in dev_accounts.iter().take(3) { + for token_addr in [PATH_USD, ALPHA_USD, BETA_USD, THETA_USD] { + let token = IERC20::new(token_addr, &provider); + let balance = token.balanceOf(*account).call().await.unwrap(); + assert!( + balance > U256::ZERO, + "Account {account} should have {token_addr} balance, got 0" + ); + } + } +} + +// ============================================================================ +// Tempo Genesis: Dev Account Balance +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_dev_accounts_have_balance() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let genesis_balance = handle.genesis_balance(); + + for account in handle.dev_accounts() { + let balance = provider.get_balance(account).await.unwrap(); + assert_eq!(balance, genesis_balance, "Dev account {account} should have genesis balance"); + } +} + +// ============================================================================ +// TIP20 Token Operations: Transfer +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tip20_transfer() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + + let sender_balance_before = token.balanceOf(sender).call().await.unwrap(); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(1_000_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.status()); + + let sender_balance_after = token.balanceOf(sender).call().await.unwrap(); + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + + assert_eq!( + sender_balance_before - transfer_amount, + sender_balance_after, + "Sender balance should decrease by transfer amount" + ); + assert_eq!( + recipient_balance_before + transfer_amount, + recipient_balance_after, + "Recipient balance should increase by transfer amount" + ); +} + +// ============================================================================ +// TIP20 Token Operations: Approve and TransferFrom +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tip20_approve_and_transfer_from() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let owner = accounts[0]; + let spender = accounts[1]; + let recipient = accounts[2]; + + let token = IERC20::new(BETA_USD, &provider); + + // Owner approves spender + let approve_amount = U256::from(5_000_000); + let approve_call = token.approve(spender, approve_amount); + let calldata: Bytes = approve_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(owner) + .to(BETA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let allowance = token.allowance(owner, spender).call().await.unwrap(); + assert_eq!(allowance, approve_amount); + + // Spender transfers from owner to recipient + let transfer_amount = U256::from(2_000_000); + let transfer_from_call = token.transferFrom(owner, recipient, transfer_amount); + let calldata: Bytes = transfer_from_call.calldata().clone(); + + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let tx = TransactionRequest::default() + .from(spender) + .to(BETA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.status()); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!(recipient_balance_before + transfer_amount, recipient_balance_after); + + let allowance_after = token.allowance(owner, spender).call().await.unwrap(); + assert_eq!(allowance_after, approve_amount - transfer_amount); +} + +// ============================================================================ +// TIP20 Token Operations: Total Supply +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tip20_total_supply() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let token = IERC20::new(PATH_USD, &provider); + let total_supply = token.totalSupply().call().await.unwrap(); + + assert!(total_supply > U256::ZERO, "Total supply should be non-zero"); +} + +// ============================================================================ +// TIP20 Token Operations: Transfer Between Different Fee Tokens +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_transfer_between_different_fee_tokens() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + for token_addr in [PATH_USD, ALPHA_USD, BETA_USD, THETA_USD] { + let token = IERC20::new(token_addr, &provider); + let balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(100_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(sender) + .to(token_addr) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.status(), "Transfer for {token_addr} failed"); + + let balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!(balance_after, balance_before + transfer_amount); + } +} + +// ============================================================================ +// TIP20 Token Operations: All Fee Tokens Have Correct Metadata +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_all_fee_tokens_have_correct_metadata() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let tokens = [ + (PATH_USD, "PathUSD"), + (ALPHA_USD, "AlphaUSD"), + (BETA_USD, "BetaUSD"), + (THETA_USD, "ThetaUSD"), + ]; + + for (addr, expected_name) in tokens { + let token = IERC20::new(addr, &provider); + let name = token.name().call().await.unwrap(); + let decimals = token.decimals().call().await.unwrap(); + + assert_eq!(name, expected_name, "Token at {addr} should be named {expected_name}"); + assert_eq!(decimals, 6, "All TIP20 tokens use 6 decimals"); + } +} + +// ============================================================================ +// TIP20 Token Operations: Transfer Emits Event +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_transfer_emits_event() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let from = accounts[0]; + let to = accounts[1]; + + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_amount = U256::from(1_000_000); + let transfer_call = token.transfer(to, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(from) + .to(ALPHA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(!receipt.inner.logs().is_empty(), "Transfer should emit event"); + + let log = &receipt.inner.logs()[0]; + assert_eq!(log.address(), ALPHA_USD); + + let transfer_topic = + alloy_primitives::keccak256("Transfer(address,address,uint256)".as_bytes()); + assert_eq!(log.topics()[0], transfer_topic); +} + +// ============================================================================ +// Tempo Transactions: Native Value Transfer Rejected +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_native_value_transfer_rejected() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let from = accounts[0]; + let to = accounts[1]; + + let tx = TransactionRequest::default() + .from(from) + .to(to) + .value(U256::from(1_000_000_000_000_000_000u64)); // 1 ETH + + let tx = WithOtherFields::new(tx); + let result = provider.send_transaction(tx).await; + assert!(result.is_err(), "Native ETH transfers should be rejected in Tempo mode"); + + let err = result.unwrap_err().to_string(); + assert!( + err.contains("native value transfer not allowed"), + "Expected 'native value transfer not allowed' error, got: {err}" + ); +} + +// ============================================================================ +// Tempo Transactions: Zero-Value EIP-1559 Tx Succeeds +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_zero_value_tx_succeeds() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + // TIP20 transfer (value=0, only calldata) + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1_000_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.status()); +} + +// ============================================================================ +// Tempo Transactions: Contract Deployment +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_contract_deployment() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + + // Minimal contract: PUSH1 0x00 PUSH1 0x00 RETURN (returns empty) + let bytecode = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); + + let tx = + TransactionRequest::default().from(sender).with_input(bytecode).with_gas_limit(100_000); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.status()); + assert!(receipt.contract_address.is_some(), "Should have deployed a contract"); +} + +// ============================================================================ +// Tempo Transactions: Nonce Increments +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_nonce_increments() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let from = accounts[0]; + let to = accounts[1]; + + let nonce_before = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce_before, 0); + + // Send a TIP20 transfer + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_call = token.transfer(to, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(from) + .to(ALPHA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let nonce_after = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce_after, 1); +} + +// ============================================================================ +// Tempo AA Transaction Tests (Type 0x76) +// ============================================================================ + +/// Helper to get the private key for a dev account. +fn dev_key(index: u32) -> PrivateKeySigner { + let mnemonic = "test test test test test test test test test test test junk"; + alloy_signer_local::MnemonicBuilder::::default() + .phrase(mnemonic) + .index(index) + .expect("valid mnemonic") + .build() + .expect("valid key") +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_basic() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(100_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::ZERO, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Tempo AA transaction should succeed"); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + transfer_amount, + "Recipient should receive transfer amount" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_with_2d_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + // Send two transactions with different nonce keys (can be parallelized) + let nonce_keys = [U256::from(1), U256::from(2)]; + + for (i, nonce_key) in nonce_keys.iter().enumerate() { + let transfer_amount = U256::from(50_000 * (i + 1) as u64); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: *nonce_key, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Tempo AA transaction with nonce_key {nonce_key} should succeed"); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_with_valid_before() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_before = current_time + 30; + + let transfer_call = token.transfer(recipient, U256::from(75_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(3), + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Tempo AA transaction with valid_before should succeed"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_with_valid_after() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_after = current_time; + let valid_before = current_time + 30; + + let transfer_call = token.transfer(recipient, U256::from(60_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(4), + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: NonZeroU64::new(valid_after), + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!( + receipt.status(), + "Tempo AA transaction with valid_after (already valid) should succeed" + ); +} + +// ============================================================================ +// Tempo AA Transaction Error Cases +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_expired_valid_before() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_before = current_time.saturating_sub(10); // 10 seconds ago + + let transfer_call = token.transfer(recipient, U256::from(50_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(100), + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let result = provider.send_raw_transaction(&encoded).await; + assert!(result.is_err(), "Transaction with expired valid_before should be rejected"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_valid_after_future() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_after = current_time + 5; + let valid_before = current_time + 60; + let transfer_amount = U256::from(50_000); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(101), + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: NonZeroU64::new(valid_after), + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + // Transaction enters pool but is not yet valid + let pending = provider.send_raw_transaction(&encoded).await.unwrap(); + let tx_hash = *pending.tx_hash(); + + api.mine_one().await; + let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap(); + assert!(receipt.is_none(), "Transaction should not be mined before valid_after"); + let recipient_balance = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!(recipient_balance, recipient_balance_before); + + // Advance time past valid_after + api.evm_set_next_block_timestamp(valid_after + 1).unwrap(); + api.mine_one().await; + + let receipt = pending.get_receipt().await.unwrap(); + assert!(receipt.status(), "Transaction should succeed after valid_after time"); + let recipient_balance = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!(recipient_balance, recipient_balance_before + transfer_amount); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_nonce_replay_same_key() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let nonce_key = U256::from(200); + + // First transaction with nonce 0 + let transfer_call = token.transfer(recipient, U256::from(50_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx1 = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { + to: TxKind::Call(PATH_USD), + value: U256::ZERO, + input: calldata.clone(), + }], + access_list: Default::default(), + nonce_key, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx1.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx1, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "First transaction should succeed"); + + // Second transaction with nonce=1 on the same key should succeed + let transfer_call2 = token.transfer(recipient, U256::from(60_000)); + let calldata2: Bytes = transfer_call2.calldata().clone(); + + let tempo_tx2 = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata2 }], + access_list: Default::default(), + nonce_key, + nonce: 1, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx2.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx2, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash2 = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt2 = tx_hash2.get_receipt().await.unwrap(); + assert!(receipt2.status(), "Second transaction with nonce=1 should succeed"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_parallel_nonces_different_keys() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + // Send two transactions with the SAME nonce (0) but DIFFERENT nonce keys + let mut tx_hashes = vec![]; + + for nonce_key_val in [300u64, 301u64] { + let transfer_call = token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(nonce_key_val), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + tx_hashes.push(tx_hash); + } + + for tx_hash in tx_hashes { + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "Parallel transactions with different nonce keys should succeed"); + } + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + U256::from(20_000), + "Recipient should receive both transfers" + ); +} + +// ============================================================================ +// Gas Estimation +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_call = token.transfer(accounts[1], U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default().from(accounts[0]).to(ALPHA_USD).with_input(calldata); + + let gas_estimate = provider.estimate_gas(tx.into()).await.unwrap(); + + // TIP20 transfer should use more than 21000 gas + assert!(gas_estimate > 21000, "TIP20 transfer should use more than 21000 gas"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_for_contract_call() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_call = token.transfer(accounts[1], U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default().from(accounts[0]).to(ALPHA_USD).with_input(calldata); + + let gas_estimate = provider.estimate_gas(tx.into()).await.unwrap(); + + // Contract call should use more gas than simple transfer + assert!(gas_estimate > 21000, "Contract call should use more than 21000 gas"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_with_value_fails() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + + // Gas estimation with native value should fail in Tempo mode + let tx = TransactionRequest::default() + .from(accounts[0]) + .to(accounts[1]) + .value(U256::from(1_000_000_000_000_000_000u64)); + + let result = provider.estimate_gas(tx.into()).await; + assert!(result.is_err(), "Gas estimation with native value should fail in Tempo mode"); +} + +// ============================================================================ +// Gas Price & Base Fee +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_price() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let gas_price = provider.get_gas_price().await.unwrap(); + + assert!(gas_price > 0, "Gas price should be non-zero"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_base_fee() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + + assert!(block.header.base_fee_per_gas.is_some()); +} + +// ============================================================================ +// Fee Token Deduction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_eip1559_fee_token_deduction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + // Check fee token balance before (ALPHA_USD is the default fee token) + let fee_token = IERC20::new(ALPHA_USD, &provider); + let fee_balance_before = fee_token.balanceOf(sender).call().await.unwrap(); + + // Transfer PATH_USD so balance change is only from gas fees, not the transfer itself + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(100_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let base_fee = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .max_fee_per_gas(base_fee * 2) + .max_priority_fee_per_gas(base_fee / 10); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "Transaction should succeed"); + + // Fee token balance should have decreased (gas fees paid in ALPHA_USD) + let fee_balance_after = fee_token.balanceOf(sender).call().await.unwrap(); + assert!( + fee_balance_after < fee_balance_before, + "Fee token balance should decrease after paying gas (before: {fee_balance_before}, after: {fee_balance_after})" + ); +} + +// ============================================================================ +// Anvil Control: Set Balance +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_balance() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let random_address = Address::random(); + let new_balance = U256::from(1_000_000_000_000_000_000u64); + + let balance_before = provider.get_balance(random_address).await.unwrap(); + assert_eq!(balance_before, U256::ZERO); + + api.anvil_set_balance(random_address, new_balance).await.unwrap(); + + let balance_after = provider.get_balance(random_address).await.unwrap(); + assert_eq!(balance_after, new_balance); +} + +// ============================================================================ +// Anvil Control: Set Code +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_code() { + let (api, _handle) = spawn(NodeConfig::test_tempo()).await; + + let target = Address::random(); + + let code_before = api.get_code(target, None).await.unwrap(); + assert!(code_before.is_empty()); + + let bytecode = vec![0x60, 0x00, 0x60, 0x00, 0xf3]; // PUSH 0, PUSH 0, RETURN + api.anvil_set_code(target, bytecode.clone().into()).await.unwrap(); + + let code_after = api.get_code(target, None).await.unwrap(); + assert_eq!(code_after.as_ref(), bytecode.as_slice()); +} + +// ============================================================================ +// Anvil Control: Auto Mine Toggle +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_auto_mine_toggle() { + let (api, _handle) = spawn(NodeConfig::test_tempo()).await; + + assert!(api.anvil_get_auto_mine().unwrap()); + + api.anvil_set_auto_mine(false).await.unwrap(); + assert!(!api.anvil_get_auto_mine().unwrap()); + + api.anvil_set_auto_mine(true).await.unwrap(); + assert!(api.anvil_get_auto_mine().unwrap()); +} + +// ============================================================================ +// Anvil Control: Manual Mining +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_manual_mining() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let block_before = provider.get_block_number().await.unwrap(); + + api.mine_one().await; + + let block_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_after, block_before + 1); + + api.anvil_mine(Some(U256::from(5)), None).await.unwrap(); + + let block_final = provider.get_block_number().await.unwrap(); + assert_eq!(block_final, block_after + 5); +} + +// ============================================================================ +// Anvil Control: Impersonate Account +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_impersonate_account() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let impersonated = handle.dev_accounts().next().unwrap(); + let recipient = handle.dev_accounts().nth(1).unwrap(); + + api.anvil_impersonate_account(impersonated).await.unwrap(); + + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(impersonated) + .to(ALPHA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + api.anvil_stop_impersonating_account(impersonated).await.unwrap(); +} + +// ============================================================================ +// Anvil Control: Snapshot and Revert +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_snapshot_and_revert() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let from = accounts[0]; + let to = accounts[1]; + + let token = IERC20::new(ALPHA_USD, &provider); + let balance_before = token.balanceOf(to).call().await.unwrap(); + let block_before = provider.get_block_number().await.unwrap(); + + let snapshot_id = api.evm_snapshot().await.unwrap(); + + let transfer_call = token.transfer(to, U256::from(1_000_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(from) + .to(ALPHA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let balance_after_tx = token.balanceOf(to).call().await.unwrap(); + assert!(balance_after_tx > balance_before); + + api.evm_revert(snapshot_id).await.unwrap(); + + let balance_reverted = token.balanceOf(to).call().await.unwrap(); + let block_reverted = provider.get_block_number().await.unwrap(); + + assert_eq!(balance_reverted, balance_before); + assert_eq!(block_reverted, block_before); +} + +// ============================================================================ +// Block & Chain: Tempo Mode Enabled +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_mode_enabled_by_default() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let block_number = provider.get_block_number().await.unwrap(); + assert_eq!(block_number, 0); +} + +// ============================================================================ +// Block & Chain: Fee Tokens Deployed +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_fee_tokens_deployed() { + let (api, _handle) = spawn(NodeConfig::test_tempo()).await; + + for token in [PATH_USD, ALPHA_USD, BETA_USD, THETA_USD] { + let code = api.get_code(token, None).await.unwrap(); + assert!(!code.is_empty(), "Token {token} should have code deployed"); + } +} + +// ============================================================================ +// Block & Chain: Block Has Timestamp +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_block_has_timestamp() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + + let block = provider.get_block(1.into()).await.unwrap().unwrap(); + assert!(block.header.timestamp > 0, "Block should have a timestamp"); +} + +// ============================================================================ +// Block & Chain: Block Timestamp Increases +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_block_timestamp_increases() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + let block1 = provider.get_block(1.into()).await.unwrap().unwrap(); + + let future_timestamp = block1.header.timestamp + 100; + api.evm_set_next_block_timestamp(future_timestamp).unwrap(); + + api.mine_one().await; + let block2 = provider.get_block(2.into()).await.unwrap().unwrap(); + + assert_eq!(block2.header.timestamp, future_timestamp); + assert!(block2.header.timestamp > block1.header.timestamp); +} + +// ============================================================================ +// Block & Chain: Block Timestamps Are Monotonic +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_block_timestamps_are_monotonic() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + let block1 = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let timestamp1 = block1.header.timestamp; + + let future_timestamp = timestamp1 + 10; + api.evm_set_next_block_timestamp(future_timestamp).unwrap(); + + api.mine_one().await; + let block2 = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let timestamp2 = block2.header.timestamp; + + assert!( + timestamp2 > timestamp1, + "Block timestamps must be strictly increasing: {timestamp2} should be > {timestamp1}", + ); + assert_eq!(timestamp2, future_timestamp, "Block timestamp should match the set value"); +} + +// ============================================================================ +// Block & Chain: Block Gas Limit +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_block_gas_limit() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.mine_one().await; + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + + assert!(block.header.gas_limit > 0); +} + +// ============================================================================ +// Block & Chain: Transaction Respects Gas Limit +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_transaction_respects_gas_limit() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + + let token = IERC20::new(ALPHA_USD, &provider); + let transfer_call = token.transfer(accounts[1], U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx = TransactionRequest::default() + .from(accounts[0]) + .to(ALPHA_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + assert!(receipt.gas_used <= TIP20_TRANSFER_GAS); +} + +// ============================================================================ +// Block & Chain: Multiple Transactions in Block +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_multiple_transactions_in_block() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let accounts: Vec
= handle.dev_accounts().collect(); + + let token = IERC20::new(ALPHA_USD, &provider); + + let transfer1 = token.transfer(accounts[1], U256::from(1000)); + let calldata1: Bytes = transfer1.calldata().clone(); + let tx1 = TransactionRequest::default() + .from(accounts[0]) + .to(ALPHA_USD) + .with_input(calldata1) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let transfer2 = token.transfer(accounts[3], U256::from(2000)); + let calldata2: Bytes = transfer2.calldata().clone(); + let tx2 = TransactionRequest::default() + .from(accounts[2]) + .to(ALPHA_USD) + .with_input(calldata2) + .with_gas_limit(TIP20_TRANSFER_GAS); + + let tx1 = WithOtherFields::new(tx1); + let tx2 = WithOtherFields::new(tx2); + + let pending1 = provider.send_transaction(tx1).await.unwrap(); + let pending2 = provider.send_transaction(tx2).await.unwrap(); + + api.mine_one().await; + + let receipt1 = pending1.get_receipt().await.unwrap(); + let receipt2 = pending2.get_receipt().await.unwrap(); + + assert_eq!(receipt1.block_number, receipt2.block_number); +} + +// ============================================================================ +// Block & Chain: Chain ID +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_chain_id() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 31337); +} + +// ============================================================================ +// Block & Chain: Custom Chain ID +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_custom_chain_id() { + let custom_chain_id = 42069u64; + let (_api, handle) = spawn(NodeConfig::test_tempo().with_chain_id(Some(custom_chain_id))).await; + let provider = handle.http_provider(); + + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, custom_chain_id); +} + +// ============================================================================ +// Tempo AA: Expiring Nonce Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_expiring_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_before = current_time + 25; + + let transfer_call = token.transfer(recipient, U256::from(80_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::MAX, + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Tempo AA transaction with expiring nonce should succeed"); +} + +// ============================================================================ +// Tempo AA: Expiring Nonce Replay +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_expiring_nonce_replay() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let current_time = block.header.timestamp; + let valid_before = current_time + 25; + + let transfer_call = token.transfer(recipient, U256::from(50_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::MAX, + nonce: 0, + fee_payer_signature: None, + valid_before: NonZeroU64::new(valid_before), + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let first_tx_hash = *tx_hash.tx_hash(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "First expiring nonce transaction should succeed"); + + // Replay the exact same transaction bytes + let result = provider.send_raw_transaction(&encoded).await; + + if let Ok(pending) = result { + let second_tx_hash = *pending.tx_hash(); + assert_eq!( + first_tx_hash, second_tx_hash, + "Replaying same transaction should return same tx hash (not execute again)" + ); + } +} + +// ============================================================================ +// Tempo AA: Multiple Calls in Single Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_multiple_calls() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient1 = accounts[1]; + let recipient2 = accounts[2]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let recipient1_balance_before = token.balanceOf(recipient1).call().await.unwrap(); + let recipient2_balance_before = token.balanceOf(recipient2).call().await.unwrap(); + + let amount1 = U256::from(25_000); + let amount2 = U256::from(35_000); + + let call1_data: Bytes = token.transfer(recipient1, amount1).calldata().clone(); + let call2_data: Bytes = token.transfer(recipient2, amount2).calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS * 2, + calls: vec![ + Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: call1_data }, + Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: call2_data }, + ], + access_list: Default::default(), + nonce_key: U256::from(5), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Tempo AA transaction with multiple calls should succeed"); + + let recipient1_balance_after = token.balanceOf(recipient1).call().await.unwrap(); + let recipient2_balance_after = token.balanceOf(recipient2).call().await.unwrap(); + + assert_eq!(recipient1_balance_after, recipient1_balance_before + amount1); + assert_eq!(recipient2_balance_after, recipient2_balance_before + amount2); +} + +// ============================================================================ +// Tempo AA: Nonce Keys Are Isolated +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_nonce_keys_are_isolated() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx1 = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { + to: TxKind::Call(PATH_USD), + value: U256::ZERO, + input: calldata.clone(), + }], + access_list: Default::default(), + nonce_key: U256::from(100), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx1.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx1, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "First tx with nonce_key=100 should succeed"); + + let tempo_tx2 = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(101), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx2.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx2, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash2 = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt2 = tx_hash2.get_receipt().await.unwrap(); + assert!( + receipt2.status(), + "Tx with different nonce_key should succeed even with same nonce value" + ); +} + +// ============================================================================ +// Tempo AA: Explicit Fee Token Selection +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_explicit_fee_token_selection() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + let signer = dev_key(0); + + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let path_token = IERC20::new(PATH_USD, &provider); + let alpha_token = IERC20::new(ALPHA_USD, &provider); + let path_balance_before = path_token.balanceOf(sender).call().await.unwrap(); + let alpha_balance_before = alpha_token.balanceOf(sender).call().await.unwrap(); + + let transfer_call = path_token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(PATH_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(400), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "Transaction with explicit fee token should succeed"); + + let path_balance_after = path_token.balanceOf(sender).call().await.unwrap(); + let alpha_balance_after = alpha_token.balanceOf(sender).call().await.unwrap(); + + assert!( + path_balance_after < path_balance_before, + "PATH_USD balance should decrease (transfer + fees)" + ); + assert_eq!( + alpha_balance_after, alpha_balance_before, + "ALPHA_USD balance should not change when using PATH_USD for fees" + ); +} + +// ============================================================================ +// Tempo AA: Fee Token Swap (Different Tokens) +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_fee_token_swap_different_tokens() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let alpha_token = IERC20::new(ALPHA_USD, &provider); + let alice_alpha_before = alpha_token.balanceOf(sender).call().await.unwrap(); + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(100_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let base_fee = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .max_fee_per_gas(base_fee * 2) + .max_priority_fee_per_gas(base_fee / 10); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "Transaction should succeed"); + + let alice_alpha_after = alpha_token.balanceOf(sender).call().await.unwrap(); + assert!( + alice_alpha_after < alice_alpha_before, + "Alice's AlphaUSD should decrease due to gas fees" + ); +} + +// ============================================================================ +// Tempo AA: Receipt Fields +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_transaction_receipt_fields() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(50_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(500), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + + assert!(receipt.status(), "Transaction should succeed"); + assert!(receipt.gas_used > 0, "Gas used should be non-zero"); + assert!(!receipt.inner.logs().is_empty(), "Should have Transfer event logs"); +} + +// ============================================================================ +// Tempo AA: Get Transaction By Hash +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_get_transaction_by_hash() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(50_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(501), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let pending = provider.send_raw_transaction(&encoded).await.unwrap(); + let tx_hash = *pending.tx_hash(); + pending.get_receipt().await.unwrap(); + + let tx = api.transaction_by_hash(tx_hash).await.unwrap(); + assert!(tx.is_some(), "Transaction should be retrievable by hash"); + + let tx = tx.unwrap(); + assert_eq!(tx.ty(), 0x76, "Transaction type should be 0x76 (Tempo)"); + assert_eq!(TransactionResponse::from(&tx), sender, "From address should match sender"); +} + +// ============================================================================ +// Tempo AA: Wrong Chain ID Rejected +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_wrong_chain_id_rejected() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let correct_chain_id = provider.get_chain_id().await.unwrap(); + let wrong_chain_id = correct_chain_id + 1; + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id: wrong_chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(1), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let result = provider.send_raw_transaction(&encoded).await; + assert!(result.is_err(), "Transaction with wrong chain ID should be rejected"); +} + +// ============================================================================ +// Tempo AA: Gas Too Low Rejected +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_gas_too_low_rejected() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: 1000, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(2), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let result = provider.send_raw_transaction(&encoded).await; + assert!(result.is_err(), "Transaction with gas limit below intrinsic cost should be rejected"); +} + +// ============================================================================ +// Tempo AA: Value In Call Rejected +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_value_in_call_rejected() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { + to: TxKind::Call(recipient), + value: U256::from(1_000_000), + input: Bytes::new(), + }], + access_list: Default::default(), + nonce_key: U256::from(3), + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let result = provider.send_raw_transaction(&encoded).await; + if let Ok(pending) = result { + let receipt = pending.get_receipt().await; + if let Ok(r) = receipt { + assert!(!r.status(), "Transaction with ETH value should fail in Tempo mode"); + } + } +} + +// ============================================================================ +// Tempo AA: Nonce Too High Rejected +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_tempo_aa_nonce_too_high_rejected() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let transfer_call = token.transfer(recipient, U256::from(10_000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(ALPHA_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: TIP20_TRANSFER_GAS, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::from(999), + nonce: 5, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + // Transaction may be accepted into pool but should fail during execution + let result = provider.send_raw_transaction(&encoded).await; + if result.is_ok() { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } +} + +// ============================================================================ +// Gas Estimation: Tempo AA Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default().from(accounts[0]).to(PATH_USD).with_input(calldata), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + assert!( + gas_estimate > 21000, + "Tempo AA gas estimate should be greater than 21000, got: {gas_estimate}" + ); +} + +// ============================================================================ +// Gas Estimation: Tempo AA with 2D Nonce +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_with_2d_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no 2D nonce) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + // 2D nonce tx with nonce=0 (new nonce key) + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!("0x64")), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // New 2D nonce key (nonce=0) charges COLD_SLOAD + SSTORE_SET = 22100 gas + let nonce_key_delta = gas_estimate - baseline_gas; + assert!( + nonce_key_delta >= 22_100, + "2D nonce should add >= 22100 gas, got delta: {nonce_key_delta} \ + (baseline: {baseline_gas}, 2d_nonce: {gas_estimate})" + ); +} + +// ============================================================================ +// Gas Estimation: Tempo AA with Expiring Nonce +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_tempo_aa_expiring_nonce() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no expiring nonce) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + let max_nonce_key = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(max_nonce_key)), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // At T0, expiring nonces are treated as 2D nonces (22100 gas). + // At T1+, this charges EXPIRING_NONCE_GAS = 13000 instead. + let expiring_delta = gas_estimate - baseline_gas; + assert!( + expiring_delta >= 22_100, + "Expiring nonce should add >= 22100 gas at T0, got delta: {expiring_delta} \ + (baseline: {baseline_gas}, expiring: {gas_estimate})" + ); +} + +// ============================================================================ +// Gas Estimation: T1 Hardfork Nonce Gas Costs +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_t1_nonce_costs() { + use tempo_chainspec::hardfork::TempoHardfork; + + let (_api, handle) = + spawn(NodeConfig::test_tempo().with_hardfork(Some(TempoHardfork::T1.into()))).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Baseline: plain AA tx (no nonce key) + let baseline_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_gas = provider.estimate_gas(baseline_tx).await.unwrap(); + + // TIP-1000: nonce=0 pays 250k for account creation + assert!( + baseline_gas > 250_000, + "T1 baseline should include 250k (TIP-1000), got: {baseline_gas}" + ); + + // Existing 2D nonce key (nonce=1) charges 5,000 gas + let nonce_2d_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(1), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!("0x64")), + ] + .into_iter() + .collect(), + }; + let nonce_2d_gas = provider.estimate_gas(nonce_2d_tx).await.unwrap(); + + let baseline_nonce1_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(1), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + let baseline_nonce1_gas = provider.estimate_gas(baseline_nonce1_tx).await.unwrap(); + let nonce_2d_delta = nonce_2d_gas - baseline_nonce1_gas; + + assert!( + nonce_2d_delta >= 5_000, + "T1: existing 2D nonce key should add >= 5000 gas, got: {nonce_2d_delta}" + ); + + // TIP-1000: nonce=0 should cost 250k more than nonce=1 + let tip1000_delta = baseline_gas - baseline_nonce1_gas; + assert!( + tip1000_delta >= 250_000, + "T1: TIP-1000 delta should be >= 250000, got: {tip1000_delta}" + ); + + // Expiring nonce (nonce_key=MAX) at T1 should charge ~13K for ring buffer ops + // (2*COLD_SLOAD + WARM_SLOAD + 3*WARM_SSTORE_RESET), NOT 22K like at T0. + let max_nonce_key = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + let block = provider.get_block(BlockNumberOrTag::Latest.into()).await.unwrap().unwrap(); + let valid_before = block.header.timestamp + 25; + + let expiring_tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(max_nonce_key)), + ("validBefore".to_string(), serde_json::json!(valid_before)), + ] + .into_iter() + .collect(), + }; + let expiring_gas = provider.estimate_gas(expiring_tx).await.unwrap(); + + // Compare against baseline_nonce1 (nonce=1, no nonce key) since both the baseline (nonce=0) + // and expiring tx (nonce=0) include the 250k account creation cost. + let expiring_delta = expiring_gas - baseline_nonce1_gas; + assert!( + expiring_delta >= 13_000, + "T1: expiring nonce should add at least ~13K gas for ring buffer ops, got delta: {expiring_delta}" + ); +} + +// ============================================================================ +// Gas Estimation: 2D Nonce Estimate Sufficient for Real Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_2d_nonce_converges() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + let signer = dev_key(0); + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + // Estimate gas with 2D nonce + let nonce_key = U256::from(0x64); + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()) + .with_nonce(0), + other: [ + ("feeToken".to_string(), serde_json::json!(PATH_USD.to_string())), + ("nonceKey".to_string(), serde_json::json!(format!("{nonce_key:#x}"))), + ] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // Send the actual transaction with the estimated gas to verify it's sufficient + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(PATH_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: gas_estimate, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!( + receipt.status(), + "2D nonce transaction should succeed with estimated gas: {gas_estimate}" + ); + assert!( + receipt.gas_used() <= gas_estimate, + "Gas used ({}) should be <= estimate ({}) for 2D nonce tx", + receipt.gas_used(), + gas_estimate + ); +} + +// ============================================================================ +// Gas Estimation: Converges for Tempo Intrinsic Gas +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_estimation_converges_for_tempo_intrinsic_gas() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let transfer_call = token.transfer(recipient, U256::from(1000)); + let calldata: Bytes = transfer_call.calldata().clone(); + + let tx: WithOtherFields = WithOtherFields { + inner: TransactionRequest::default() + .from(accounts[0]) + .to(PATH_USD) + .with_input(calldata.clone()), + other: [("feeToken".to_string(), serde_json::json!(PATH_USD.to_string()))] + .into_iter() + .collect(), + }; + + let gas_estimate = provider.estimate_gas(tx).await.unwrap(); + + // Send the actual transaction with the estimated gas to verify convergence + let signer = dev_key(0); + let chain_id = provider.get_chain_id().await.unwrap(); + let base_fee = provider.get_gas_price().await.unwrap(); + + let tempo_tx = TempoTransaction { + chain_id, + fee_token: Some(PATH_USD), + max_priority_fee_per_gas: base_fee / 10, + max_fee_per_gas: base_fee * 2, + gas_limit: gas_estimate, + calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }], + access_list: Default::default(), + nonce_key: U256::ZERO, + nonce: 0, + fee_payer_signature: None, + valid_before: None, + valid_after: None, + key_authorization: None, + tempo_authorization_list: vec![], + }; + + let sig_hash = tempo_tx.signature_hash(); + let signature = signer.sign_hash(&sig_hash).await.unwrap(); + let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)); + let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig); + let envelope = TempoTxEnvelope::AA(signed_tx); + + let mut encoded = Vec::new(); + envelope.encode_2718(&mut encoded); + + let tx_hash = provider.send_raw_transaction(&encoded).await.unwrap(); + let receipt = tx_hash.get_receipt().await.unwrap(); + assert!(receipt.status(), "Transaction should succeed with estimated gas: {gas_estimate}"); + + assert!( + receipt.gas_used() <= gas_estimate, + "Gas used ({}) should be <= estimate ({})", + receipt.gas_used(), + gas_estimate + ); +} + +// ============================================================================ +// EIP-1559 Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_eip1559_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(500_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let base_fee = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .max_fee_per_gas(base_fee * 2) + .max_priority_fee_per_gas(base_fee / 10); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "EIP-1559 transaction should succeed"); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + transfer_amount, + "Recipient should receive transfer amount" + ); +} + +// ============================================================================ +// Legacy Transaction +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_legacy_transaction() { + let (_api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let accounts: Vec
= handle.dev_accounts().collect(); + let sender = accounts[0]; + let recipient = accounts[1]; + + let token = IERC20::new(PATH_USD, &provider); + let recipient_balance_before = token.balanceOf(recipient).call().await.unwrap(); + + let transfer_amount = U256::from(250_000); + let transfer_call = token.transfer(recipient, transfer_amount); + let calldata: Bytes = transfer_call.calldata().clone(); + + let gas_price = provider.get_gas_price().await.unwrap(); + + let tx = TransactionRequest::default() + .from(sender) + .to(PATH_USD) + .with_input(calldata) + .with_gas_limit(TIP20_TRANSFER_GAS) + .with_gas_price(gas_price); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status(), "Legacy transaction should succeed"); + + let recipient_balance_after = token.balanceOf(recipient).call().await.unwrap(); + assert_eq!( + recipient_balance_after, + recipient_balance_before + transfer_amount, + "Recipient should receive transfer amount" + ); +} + +// ============================================================================ +// TipFeeManager RPC Methods +// ============================================================================ + +/// `anvil_setFeeToken` sets the fee token for a user address, readable via `userTokens`. +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_fee_token() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let user = Address::random(); + let fee_manager = IFeeManagerRpc::new(TIP_FEE_MANAGER_ADDRESS, &provider); + + // Before: user has no token set (returns zero address) + let token_before = fee_manager.userTokens(user).call().await.unwrap(); + assert_eq!(token_before, Address::ZERO, "User should have no fee token initially"); + + // Set user fee token to ALPHA_USD + api.anvil_set_fee_token(user, ALPHA_USD).await.unwrap(); + + // After: userTokens returns the configured token + let token_after = fee_manager.userTokens(user).call().await.unwrap(); + assert_eq!(token_after, ALPHA_USD, "User fee token should be set to ALPHA_USD"); +} + +/// `anvil_setFeeToken` returns an error when Tempo mode is not active. +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_fee_token_non_tempo_fails() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + let result = api.anvil_set_fee_token(Address::random(), ALPHA_USD).await; + assert!(result.is_err(), "anvil_setFeeToken should fail outside of Tempo mode"); +} + +/// `anvil_setValidatorFeeToken` sets the fee token for a validator, readable via `validatorTokens`. +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_validator_fee_token() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let validator = Address::random(); + let fee_manager = IFeeManagerRpc::new(TIP_FEE_MANAGER_ADDRESS, &provider); + + // Before: validator has no token set (returns zero address) + let token_before = fee_manager.validatorTokens(validator).call().await.unwrap(); + assert_eq!(token_before, DEFAULT_FEE_TOKEN, "Validator should have no fee token initially"); + + // Set validator fee token to BETA_USD + api.anvil_set_validator_fee_token(validator, BETA_USD).await.unwrap(); + + // After: validatorTokens returns the configured token + let token_after = fee_manager.validatorTokens(validator).call().await.unwrap(); + assert_eq!(token_after, BETA_USD, "Validator fee token should be set to BETA_USD"); +} + +/// `anvil_setValidatorFeeToken` returns an error when Tempo mode is not active. +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_validator_fee_token_non_tempo_fails() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + let result = api.anvil_set_validator_fee_token(Address::random(), BETA_USD).await; + assert!(result.is_err(), "anvil_setValidatorFeeToken should fail outside of Tempo mode"); +} + +/// `anvil_setFeeAmmLiquidity` mints AMM liquidity for a token pair, +/// verifiable via `getPool` (non-zero reserves) and `totalSupply` (non-zero LP supply). +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_fee_amm_liquidity() { + let (api, handle) = spawn(NodeConfig::test_tempo()).await; + let provider = handle.http_provider(); + + let fee_manager = IFeeManagerRpc::new(TIP_FEE_MANAGER_ADDRESS, &provider); + + // Genesis pre-seeds all pairs between fee tokens; record current state before adding more + let pool_before = fee_manager.getPool(PATH_USD, ALPHA_USD).call().await.unwrap(); + let pool_id = fee_manager.getPoolId(PATH_USD, ALPHA_USD).call().await.unwrap(); + let lp_supply_before = fee_manager.totalSupply(pool_id).call().await.unwrap(); + + let amount = U256::from(1_000_000u64); + api.anvil_set_fee_amm_liquidity(PATH_USD, ALPHA_USD, amount).await.unwrap(); + + // After minting: reserves and LP supply should have increased + let pool_after = fee_manager.getPool(PATH_USD, ALPHA_USD).call().await.unwrap(); + let lp_supply_after = fee_manager.totalSupply(pool_id).call().await.unwrap(); + + assert!( + pool_after.reserveValidatorToken > pool_before.reserveValidatorToken, + "Validator token reserve should increase after minting liquidity" + ); + assert!( + lp_supply_after > lp_supply_before, + "LP token total supply should increase after minting liquidity" + ); +} + +/// `anvil_setFeeAmmLiquidity` returns an error when Tempo mode is not active. +#[tokio::test(flavor = "multi_thread")] +async fn test_anvil_set_fee_amm_liquidity_non_tempo_fails() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + let result = + api.anvil_set_fee_amm_liquidity(PATH_USD, ALPHA_USD, U256::from(1_000_000u64)).await; + assert!(result.is_err(), "anvil_setFeeAmmLiquidity should fail outside of Tempo mode"); +} diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 2e073ca9c8735..2509a523c7cd5 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,34 +1,37 @@ +use std::collections::HashMap; + use crate::{ abi::{Multicall, SimpleStorage}, fork::fork_config, utils::http_provider_with_signer, }; use alloy_eips::BlockId; -use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{ - hex::{self, FromHex}, Address, Bytes, U256, + hex::{self, FromHex}, }; use alloy_provider::{ - ext::{DebugApi, TraceApi}, Provider, + ext::{DebugApi, TraceApi}, }; use alloy_rpc_types::{ + BlockNumberOrTag, TransactionRequest, state::StateOverride, trace::{ filter::{TraceFilter, TraceFilterMode}, geth::{ - CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, - GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, + AccountState, CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, PreStateConfig, + PreStateFrame, }, - parity::{Action, LocalizedTransactionTrace}, + parity::{Action, ChangedType, LocalizedTransactionTrace, TraceType}, }, - TransactionRequest, }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; +use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { @@ -799,11 +802,11 @@ async fn test_trace_filter() { for i in 0..=5 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); - assert_eq!(traces.len(), 5); + assert_eq!(traces.len(), 6); // Test filtering by address let tracer = TraceFilter { @@ -928,3 +931,507 @@ async fn test_trace_filter() { let traces = api.trace_filter(tracer).await.unwrap(); assert_eq!(traces.len(), 5); } + +#[cfg(feature = "js-tracer")] +#[tokio::test(flavor = "multi_thread")] +async fn test_call_tracer_debug_trace_call_js_tracer() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallets = handle.dev_wallets().collect::>(); + let deployer: EthereumWallet = wallets[0].clone().into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); + + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + let internal_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*multicall_contract.address()) + .with_input(internal_call_tx_calldata); + + let js_tracer_code = r#" +{ +data: [], +step: function(log) { + var op = log.op.toString(); + if (op === "SLOAD") { + this.data.push(log.getPC() + ": SLOAD " + log.contract.getAddress() + ":" + log.stack.peek(0)); + this.data.push(" Result: " + log.stack.peek(0)); + } else if (op === "SSTORE") { + this.data.push(log.getPC() + ": SSTORE " + log.contract.getAddress() + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0)); + } +}, +result: function() { + return this.data; +}, +fault: function(log) {} +} +"#; + + let result = api + .debug_trace_call( + WithOtherFields::new(internal_call_tx), + Some(BlockId::latest()), + GethDebugTracingCallOptions::default() + .with_tracing_options(GethDebugTracingOptions::js_tracer(js_tracer_code)), + ) + .await + .unwrap(); + + let expected = vec![ + "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", + " Result: 0", + "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", + " Result: 1", + "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", + " Result: 1", + "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1", + "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998", + "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", + " Result: 0", + "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0", + ]; + + let actual: Vec = result + .try_into_json_value() + .ok() + .and_then(|val| val.as_array().cloned()) + .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + + assert_eq!(actual, expected); +} + +#[cfg(feature = "js-tracer")] +#[tokio::test(flavor = "multi_thread")] +async fn test_debug_trace_transaction_js_tracer() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = crate::utils::http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + api.anvil_add_balance(from, U256::MAX).await.unwrap(); + api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap(); + + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + let internal_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*multicall_contract.address()) + .with_input(internal_call_tx_calldata) + .with_gas_limit(1_000_000) + .with_max_fee_per_gas(100_000_000_000) + .with_max_priority_fee_per_gas(100_000_000_000); + + let receipt = provider + .send_transaction(internal_call_tx.into()) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let js_tracer_code = r#" +{ +data: [], +step: function(log) { + var op = log.op.toString(); + var pc = log.getPC(); + var addr = log.contract.getAddress(); + + if (op === "SLOAD") { + this.data.push(pc + ": SLOAD " + addr + ":" + log.stack.peek(0)); + this.data.push(" Result: " + log.stack.peek(0)); + } else if (op === "SSTORE") { + this.data.push(pc + ": SSTORE " + addr + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0)); + } +}, +result: function() { + return this.data; +}, +fault: function(log) {} +} +"#; + + let expected = vec![ + "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", + " Result: 0", + "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", + " Result: 1", + "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", + " Result: 1", + "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1", + "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998", + "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", + " Result: 0", + "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0", + ]; + let result = api + .debug_trace_transaction( + receipt.transaction_hash, + GethDebugTracingOptions::js_tracer(js_tracer_code), + ) + .await + .unwrap(); + + let actual: Vec = result + .try_into_json_value() + .ok() + .and_then(|val| val.as_array().cloned()) + .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + + assert_eq!(actual, expected); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_call_tracer_debug_trace_call_pre_state_tracer() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallets = handle.dev_wallets().collect::>(); + let deployer: EthereumWallet = wallets[0].clone().into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); + + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + let internal_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*multicall_contract.address()) + .with_input(internal_call_tx_calldata); + + let result = api + .debug_trace_call( + WithOtherFields::new(internal_call_tx), + Some(BlockId::latest()), + GethDebugTracingCallOptions::default().with_tracing_options( + GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()), + ), + ) + .await + .unwrap(); + + let expected = r#" +{ + "0x0000000000000000000000000000000000000000": { + "balance": "0x12670f" + }, + "0x5fbdb2315678afecb367f032d93f642f64180aa3": { + "balance": "0x0", + "nonce": 1 + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0x56bc75e2d63100000" + }, + "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": { + "balance": "0x0", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + } +} + "#; + let expected: HashMap = serde_json::from_str(expected).unwrap(); + + match result { + GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => { + for (addr, acc) in pre_state_mode.0 { + let expected_acc = expected.get(&addr).unwrap(); + assert_eq!(acc.balance, expected_acc.balance); + assert_eq!(acc.nonce, expected_acc.nonce); + let expected_storage = &expected_acc.storage; + for (slot, value) in acc.storage { + assert_eq!(value, *expected_storage.get(&slot).unwrap()) + } + } + } + _ => unreachable!(), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_debug_trace_transaction_pre_state_tracer() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = crate::utils::http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + api.anvil_add_balance(from, U256::MAX).await.unwrap(); + api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap(); + + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + let internal_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*multicall_contract.address()) + .with_input(internal_call_tx_calldata) + .with_gas_limit(1_000_000) + .with_max_fee_per_gas(100_000_000_000) + .with_max_priority_fee_per_gas(100_000_000_000); + + let receipt = provider + .send_transaction(internal_call_tx.into()) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + let result = api + .debug_trace_transaction( + receipt.transaction_hash, + GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()), + ) + .await + .unwrap(); + + let expected = r#" +{ + "0x0000000000000000000000000000000000000000": { + "balance": "1206031000000000" + }, + "0x5fbdb2315678afecb367f032d93f642f64180aa3": { + "balance": "0x0", + "nonce": 1 + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": { + "balance": "0x0", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + } +} + "#; + let expected: HashMap = serde_json::from_str(expected).unwrap(); + + match result { + GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => { + for (addr, acc) in pre_state_mode.0 { + let expected_acc = expected.get(&addr).unwrap(); + assert_eq!(acc.balance, expected_acc.balance); + assert_eq!(acc.nonce, expected_acc.nonce); + let expected_storage = &expected_acc.storage; + for (slot, value) in acc.storage { + assert_eq!(value, *expected_storage.get(&slot).unwrap()) + } + } + } + _ => unreachable!(), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_trace_replay_block_transactions_local() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let amount = U256::from(1000000u64); + + // Send first transaction + let tx1 = TransactionRequest::default().to(to).value(amount).from(from); + let tx1 = WithOtherFields::new(tx1); + let pending_tx1 = provider.send_transaction(tx1).await.unwrap(); + + // Send second transaction with different value + let tx2 = TransactionRequest::default().to(to).value(amount).from(from); + let tx2 = WithOtherFields::new(tx2); + let pending_tx2 = provider.send_transaction(tx2).await.unwrap(); + + api.mine_one().await; + let receipt1 = pending_tx1.get_receipt().await.unwrap(); + let receipt2 = pending_tx2.get_receipt().await.unwrap(); + + let block_number = receipt2.block_number.unwrap(); + + // Replay the block transactions with call trace type + // Pass block number as hex string as per Ethereum RPC spec + let results = api + .trace_replay_block_transactions( + block_number.into(), + vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(), + ) + .await + .unwrap(); + + // Verify we have traces for both transactions + assert_eq!(results.len(), 2, "Should have traces for 2 transactions"); + + // Verify first transaction hash matches + assert_eq!(results[0].transaction_hash, receipt1.transaction_hash); + + // Verify second transaction hash matches + assert_eq!(results[1].transaction_hash, receipt2.transaction_hash); + + // Verify trace types are present and accurate + for result in results { + let full_trace = &result.full_trace; + + // Verify Trace (call trace) is present and accurate + assert!(!full_trace.trace.is_empty(), "Trace should not be empty"); + let first_trace = &full_trace.trace[0]; + match &first_trace.action { + Action::Call(call) => { + assert_eq!(call.from, from, "Call from address should match"); + assert_eq!(call.to, to, "Call to address should match"); + } + _ => panic!("Expected Call action, got {:?}", first_trace.action), + } + + // Verify VmTrace is present + assert!(full_trace.vm_trace.is_some(), "VmTrace should be present when requested"); + + // Verify StateDiff is present + assert!(full_trace.state_diff.is_some(), "StateDiff should be present when requested"); + // Verify balance change is correct in state diff + let ChangedType:: { from, to } = + full_trace.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap(); + assert_eq!( + to.checked_sub(*from).unwrap(), + amount, + "Incorrect balance change in state diff" + ); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_debug_trace_block_by_number() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let amount = U256::from(1000); + + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let block_number = receipt.block_number.unwrap(); + + let traces = api + .backend + .debug_trace_block_by_number( + BlockNumberOrTag::Number(block_number), + GethDebugTracingOptions::default() + .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)), + ) + .await + .unwrap(); + + assert_eq!(traces.len(), 1); + + match &traces[0] { + alloy_rpc_types::trace::geth::TraceResult::Success { result, .. } => match result { + GethTrace::CallTracer(call_frame) => { + assert_eq!(call_frame.from, from); + assert_eq!(call_frame.to.unwrap(), to); + assert_eq!(call_frame.value, Some(amount)); + } + _ => unreachable!("expected CallTracer"), + }, + alloy_rpc_types::trace::geth::TraceResult::Error { error, .. } => { + panic!("trace failed: {error}"); + } + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_debug_trace_block_by_hash() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let amount = U256::from(2000); + + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let block_hash = receipt.block_hash.unwrap(); + + let traces = api + .backend + .debug_trace_block_by_hash( + block_hash, + GethDebugTracingOptions::default() + .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)), + ) + .await + .unwrap(); + + assert_eq!(traces.len(), 1); + + match &traces[0] { + alloy_rpc_types::trace::geth::TraceResult::Success { result, .. } => match result { + GethTrace::CallTracer(call_frame) => { + assert_eq!(call_frame.from, from); + assert_eq!(call_frame.to.unwrap(), to); + assert_eq!(call_frame.value, Some(amount)); + } + _ => unreachable!("expected CallTracer"), + }, + alloy_rpc_types::trace::geth::TraceResult::Error { error, .. } => { + panic!("trace failed: {error}"); + } + } +} diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 7cb681af85a14..86836de97cae6 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -2,20 +2,22 @@ use crate::{ abi::{Greeter, Multicall, SimpleStorage}, utils::{connect_pubsub, http_provider_with_signer}, }; -use alloy_hardforks::EthereumHardfork; -use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; -use alloy_primitives::{address, hex, map::B256HashSet, Address, Bytes, FixedBytes, U256}; +use alloy_consensus::Transaction; +use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; +use alloy_primitives::{Address, Bytes, FixedBytes, U256, address, hex, map::B256HashSet}; use alloy_provider::{Provider, WsConnect}; use alloy_rpc_types::{ - state::{AccountOverride, EvmOverrides, StateOverride, StateOverridesBuilder}, AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockOverrides, BlockTransactions, TransactionRequest, + state::{AccountOverride, EvmOverrides, StateOverride, StateOverridesBuilder}, }; use alloy_serde::WithOtherFields; use alloy_sol_types::SolValue; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use eyre::Ok; -use futures::{future::join_all, FutureExt, StreamExt}; +use foundry_evm::hardfork::EthereumHardfork; +use futures::{FutureExt, StreamExt, future::join_all}; +use revm::primitives::eip7825::TX_GAS_LIMIT_CAP; use std::{str::FromStr, time::Duration}; use tokio::time::timeout; @@ -178,17 +180,16 @@ async fn can_replace_transaction() { assert_eq!(block.transactions.len(), 1); assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions); - // FIXME: Unable to get receipt despite hotfix in https://github.com/alloy-rs/alloy/pull/614 - - // lower priced transaction was replaced - // let _lower_priced_receipt = lower_priced_pending_tx.get_receipt().await.unwrap(); - // let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); + // verify the higher priced transaction was included + let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); + assert_eq!(higher_priced_receipt.transaction_hash, higher_tx_hash); - // assert_eq!(1, block.transactions.len()); - // assert_eq!( - // BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), - // block.transactions - // ); + // verify only one transaction was included in the block (lower priced was replaced) + assert_eq!(1, block.transactions.len()); + assert_eq!( + BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] @@ -898,6 +899,42 @@ async fn test_tx_receipt() { assert!(tx.contract_address.is_some()); } +// +#[tokio::test(flavor = "multi_thread")] +async fn test_reverted_contract_creation_has_contract_address() { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + + // Init code that immediately reverts: PUSH1 0x00 PUSH1 0x00 REVERT (0x60006000fd) + let reverting_init_code = hex!("60006000fd"); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(reverting_init_code.to_vec()); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + // Transaction should have reverted + assert!(!receipt.status()); + + // `to` field should be none (contract creation) + assert!(receipt.to.is_none()); + + // `contractAddress` should still be set even though the transaction reverted + // This matches geth's behavior: https://github.com/ethereum/go-ethereum/issues/27937 + assert!( + receipt.contract_address.is_some(), + "contractAddress should be set for reverted contract creation" + ); + + // Verify the computed address is correct (sender.create(nonce)) + let expected_addr = wallet.address().create(0); + assert_eq!(receipt.contract_address, Some(expected_addr)); +} + #[tokio::test(flavor = "multi_thread")] async fn can_stream_pending_transactions() { let (_api, handle) = @@ -961,14 +998,15 @@ async fn can_stream_pending_transactions() { complete => unreachable!(), }; - if watch_received.len() == num_txs && sub_received.len() == num_txs { - if let Some(sent) = &sent { - assert_eq!(sent.len(), watch_received.len()); - let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::(); - assert_eq!(sent_txs, watch_received.iter().copied().collect()); - assert_eq!(sent_txs, sub_received.iter().copied().collect()); - break - } + if watch_received.len() == num_txs + && sub_received.len() == num_txs + && let Some(sent) = &sent + { + assert_eq!(sent.len(), watch_received.len()); + let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::(); + assert_eq!(sent_txs, watch_received.iter().copied().collect()); + assert_eq!(sent_txs, sub_received.iter().copied().collect()); + break; } } } @@ -1171,7 +1209,9 @@ async fn test_block_override() { // function getBlockNumber() external view returns (uint256) { // return block.number; // } - let code = hex!("6080604052348015600e575f5ffd5b50600436106026575f3560e01c806342cbb15c14602a575b5f5ffd5b60306044565b604051603b91906061565b60405180910390f35b5f43905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea26469706673582212207741266d8151c5e7d1a96fc1697f8fc94e60e730b3f2861d398339c74a2180d464736f6c634300081e0033"); + let code = hex!( + "6080604052348015600e575f5ffd5b50600436106026575f3560e01c806342cbb15c14602a575b5f5ffd5b60306044565b604051603b91906061565b60405180910390f35b5f43905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea26469706673582212207741266d8151c5e7d1a96fc1697f8fc94e60e730b3f2861d398339c74a2180d464736f6c634300081e0033" + ); let account_override = AccountOverride { balance: Some(U256::from(1e18)), ..Default::default() }; @@ -1293,7 +1333,7 @@ async fn can_mine_multiple_in_block() { // ensures that the gas estimate is running on pending block by default #[tokio::test(flavor = "multi_thread")] -async fn estimates_gas_prague() { +async fn can_estimate_gas_prague() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; @@ -1305,3 +1345,168 @@ async fn estimates_gas_prague() { .with_to(address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8")); api.estimate_gas(WithOtherFields::new(req), None, EvmOverrides::default()).await.unwrap(); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_tx_osaka_valid_with_limit_enabled() { + let (_api, handle) = spawn( + NodeConfig::test() + .enable_tx_gas_limit(true) + .with_hardfork(Some(EthereumHardfork::Osaka.into())), + ) + .await; + let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let sender = wallet.address(); + let recipient = Address::random(); + + let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); + + // gas limit below the cap is accepted + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1); + let tx = WithOtherFields::new(tx); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let tx_receipt = pending_tx.get_receipt().await.unwrap(); + assert!(tx_receipt.inner.inner.is_success()); + + // gas limit at the cap is accepted + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP); + let tx = WithOtherFields::new(tx); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let tx_receipt = pending_tx.get_receipt().await.unwrap(); + assert!(tx_receipt.inner.inner.is_success()); + + // gas limit above the cap is rejected + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1); + let tx = WithOtherFields::new(tx); + let err = provider.send_transaction(tx).await.unwrap_err().to_string(); + assert!( + err.contains("intrinsic gas too high -- tx.gas_limit > env.cfg.tx_gas_limit_cap"), + "{err}" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_tx_osaka_valid_with_limit_disabled() { + let (_api, handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into()))).await; + let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let sender = wallet.address(); + let recipient = Address::random(); + + let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); + + // gas limit below the cap is accepted + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1); + let tx = WithOtherFields::new(tx); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let tx_receipt = pending_tx.get_receipt().await.unwrap(); + assert!(tx_receipt.inner.inner.is_success()); + + // gas limit at the cap is accepted + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP); + let tx = WithOtherFields::new(tx); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let tx_receipt = pending_tx.get_receipt().await.unwrap(); + assert!(tx_receipt.inner.inner.is_success()); + + // gas limit above the cap is accepted when the limit is disabled + let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1); + let tx = WithOtherFields::new(tx); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + let tx_receipt = pending_tx.get_receipt().await.unwrap(); + assert!(tx_receipt.inner.inner.is_success()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_tx_by_sender_and_nonce() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_wallets().collect::>(); + let sender = accounts[0].address(); + let recipient = accounts[1].address(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let mut tx_hashes = std::collections::BTreeMap::new(); + + // send 4 transactions from the same sender with consecutive nonces + for i in 0..4 { + let tx_request = TransactionRequest::default() + .to(recipient) + .value(U256::from(1000 + i)) + .from(sender) + .nonce(i as u64); + + let tx = WithOtherFields::new(tx_request); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + tx_hashes.insert(i as u64, *pending_tx.tx_hash()); + } + + // mine all transactions + api.mine_one().await; + + for nonce in 0..4 { + let result: Option = provider + .client() + .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(nonce))) + .await + .unwrap(); + + assert!(result.is_some()); + let found_tx = result.unwrap(); + + assert_eq!(found_tx.inner.nonce(), nonce); + assert_eq!(found_tx.from(), sender); + assert_eq!(found_tx.inner.to(), Some(recipient)); + assert_eq!(found_tx.inner.value(), U256::from(1000 + nonce)); + assert_eq!(found_tx.inner.tx_hash(), tx_hashes[&nonce]); + } + + let result: Option = provider + .client() + .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(999))) + .await + .unwrap(); + assert!(result.is_none()); + + let different_sender = accounts[2].address(); + let result: Option = provider + .client() + .request("eth_getTransactionBySenderAndNonce", (different_sender, U256::from(0))) + .await + .unwrap(); + assert!(result.is_none()); + + // send a pending transaction with explicit nonce 4 + let pending_tx_request = + TransactionRequest::default().to(recipient).value(U256::from(5000)).from(sender).nonce(4); + + let tx = WithOtherFields::new(pending_tx_request); + let pending_tx = provider.send_transaction(tx).await.unwrap(); + + // find the pending transaction with nonce 4 + let result: Option = provider + .client() + .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4))) + .await + .unwrap(); + + assert!(result.is_some()); + let found_tx = result.unwrap(); + assert_eq!(found_tx.inner.nonce(), 4); + assert_eq!(found_tx.inner.tx_hash(), *pending_tx.tx_hash()); + + api.mine_one().await; + + let result: Option = provider + .client() + .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4))) + .await + .unwrap(); + + assert!(result.is_some()); + let found_tx = result.unwrap(); + assert_eq!(found_tx.inner.nonce(), 4); +} diff --git a/crates/anvil/tests/it/txpool.rs b/crates/anvil/tests/it/txpool.rs index c329b27fa9130..7e553cca1d100 100644 --- a/crates/anvil/tests/it/txpool.rs +++ b/crates/anvil/tests/it/txpool.rs @@ -1,11 +1,11 @@ //! txpool related tests -use alloy_network::TransactionBuilder; +use alloy_network::{ReceiptResponse, TransactionBuilder}; use alloy_primitives::U256; -use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_provider::{Provider, ext::TxPoolApi}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn geth_txpool() { @@ -26,10 +26,8 @@ async fn geth_txpool() { let tx = WithOtherFields::new(tx); // send a few transactions - let mut txs = Vec::new(); for _ in 0..10 { - let tx_hash = provider.send_transaction(tx.clone()).await.unwrap(); - txs.push(tx_hash); + let _ = provider.send_transaction(tx.clone()).await.unwrap(); } // we gave a 20s block time, should be plenty for us to get the txpool's content @@ -56,3 +54,131 @@ async fn geth_txpool() { assert!(content.contains_key(&nonce.to_string())); } } + +#[tokio::test(flavor = "multi_thread")] +async fn geth_txpool_separates_queued_transactions() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + api.anvil_set_auto_mine(false).await.unwrap(); + + let accounts = handle.dev_wallets().collect::>(); + let account = accounts[0].address(); + let recipient = accounts[1].address(); + let gas_price = 221435145689u128; + + let pending_value = U256::from(42); + let pending_tx = TransactionRequest::default() + .with_to(recipient) + .with_from(account) + .with_value(pending_value) + .with_gas_price(gas_price) + .with_nonce(0); + let pending_tx = WithOtherFields::new(pending_tx); + + let queued_value = U256::from(84); + let queued_tx = TransactionRequest::default() + .with_to(recipient) + .with_from(account) + .with_value(queued_value) + .with_gas_price(gas_price) + .with_nonce(2); + let queued_tx = WithOtherFields::new(queued_tx); + + let _ = provider.send_transaction(pending_tx).await.unwrap(); + let _ = provider.send_transaction(queued_tx).await.unwrap(); + + let status = provider.txpool_status().await.unwrap(); + assert_eq!(status.pending, 1); + assert_eq!(status.queued, 1); + + let inspect = provider.txpool_inspect().await.unwrap(); + let pending = inspect.pending.get(&account).unwrap(); + assert_eq!(pending.len(), 1); + assert!(!pending.contains_key("2")); + let pending_summary = pending.get("0").unwrap(); + assert_eq!(pending_summary.gas_price, gas_price); + assert_eq!(pending_summary.value, pending_value); + assert_eq!(pending_summary.gas, 21000); + assert_eq!(pending_summary.to.unwrap(), recipient); + + let queued = inspect.queued.get(&account).unwrap(); + assert_eq!(queued.len(), 1); + assert!(!queued.contains_key("0")); + let queued_summary = queued.get("2").unwrap(); + assert_eq!(queued_summary.gas_price, gas_price); + assert_eq!(queued_summary.value, queued_value); + assert_eq!(queued_summary.gas, 21000); + assert_eq!(queued_summary.to.unwrap(), recipient); + + let content = provider.txpool_content().await.unwrap(); + let pending = content.pending.get(&account).unwrap(); + assert_eq!(pending.len(), 1); + assert!(pending.contains_key("0")); + assert!(!pending.contains_key("2")); + + let queued = content.queued.get(&account).unwrap(); + assert_eq!(queued.len(), 1); + assert!(queued.contains_key("2")); + assert!(!queued.contains_key("0")); +} + +// Cf. https://github.com/foundry-rs/foundry/issues/11239 +#[tokio::test(flavor = "multi_thread")] +async fn accepts_spend_after_funding_when_pool_checks_disabled() { + // Spawn with pool balance checks disabled + let (api, handle) = spawn(NodeConfig::test().with_disable_pool_balance_checks(true)).await; + let provider = handle.http_provider(); + + // Work with pending pool (no automine) + api.anvil_set_auto_mine(false).await.unwrap(); + + // Funder is a dev account controlled by the node + let funder = provider.get_accounts().await.unwrap().remove(0); + + // Recipient/spender is a random address with zero balance that we'll impersonate + let spender = alloy_primitives::Address::random(); + api.anvil_set_balance(spender, U256::from(0u64)).await.unwrap(); + api.anvil_impersonate_account(spender).await.unwrap(); + + // Ensure tx1 (funding) has higher gas price so it's mined before tx2 within the same block + let gas_price_fund = 2_000_000_000_000u128; // 2_000 gwei + let gas_price_spend = 1_000_000_000u128; // 1 gwei + + let fund_value = U256::from(1_000_000_000_000_000_000u128); // 1 ether + + // tx1: fund spender from funder + let tx1 = TransactionRequest::default() + .with_from(funder) + .with_to(spender) + .with_value(fund_value) + .with_gas_price(gas_price_fund); + let tx1 = WithOtherFields::new(tx1); + + // tx2: spender attempts to send value greater than their pre-funding balance (0), + // which would normally be rejected by pool balance checks, but should be accepted when disabled + let spend_value = fund_value - U256::from(21_000u64) * U256::from(gas_price_spend); + let tx2 = TransactionRequest::default() + .with_from(spender) + .with_to(funder) + .with_value(spend_value) + .with_gas_price(gas_price_spend); + let tx2 = WithOtherFields::new(tx2); + + // Publish both transactions (funding first, then spend-before-funding-is-mined) + let sent1 = provider.send_transaction(tx1).await.unwrap(); + let sent2 = provider.send_transaction(tx2).await.unwrap(); + + // Both should be accepted into the pool (pending) + let status = provider.txpool_status().await.unwrap(); + assert_eq!(status.pending, 2); + assert_eq!(status.queued, 0); + + // Mine a block and ensure both succeed + api.evm_mine(None).await.unwrap(); + + let receipt1 = sent1.get_receipt().await.unwrap(); + let receipt2 = sent2.get_receipt().await.unwrap(); + assert!(receipt1.status()); + assert!(receipt2.status()); +} diff --git a/crates/anvil/tests/it/utils.rs b/crates/anvil/tests/it/utils.rs index 3c7e74bafcb2e..4a82c27745edd 100644 --- a/crates/anvil/tests/it/utils.rs +++ b/crates/anvil/tests/it/utils.rs @@ -1,10 +1,10 @@ use alloy_network::{Ethereum, EthereumWallet}; use alloy_provider::{ - fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, Identity, RootProvider, + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, }; use foundry_common::provider::{ - get_http_provider, ProviderBuilder, RetryProvider, RetryProviderWithSigner, + ProviderBuilder, RetryProvider, RetryProviderWithSigner, get_http_provider, }; pub fn http_provider(http_endpoint: &str) -> RetryProvider { diff --git a/crates/anvil/tests/it/wsapi.rs b/crates/anvil/tests/it/wsapi.rs index ebe853a7d8789..ad5e7c87714e7 100644 --- a/crates/anvil/tests/it/wsapi.rs +++ b/crates/anvil/tests/it/wsapi.rs @@ -2,7 +2,7 @@ use alloy_primitives::U256; use alloy_provider::Provider; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number_ws() { diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 2dda3ba341872..03be26a631a11 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -26,9 +26,11 @@ foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true -foundry-evm-core.workspace = true +foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-evm-networks.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } +forge-fmt.workspace = true alloy-chains.workspace = true alloy-consensus = { workspace = true, features = ["serde", "kzg"] } @@ -47,39 +49,37 @@ alloy-provider = { workspace = true, features = [ ] } alloy-rlp.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } -alloy-serde.workspace = true +alloy-rpc-types-beacon.workspace = true alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } alloy-signer.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true alloy-ens = { workspace = true, features = ["provider"] } -alloy-hardforks.workspace = true +alloy-eips.workspace = true +tempo-alloy.workspace = true +tempo-contracts.workspace = true +tempo-primitives.workspace = true +alloy-evm.workspace = true -op-alloy-flz.workspace = true -op-alloy-consensus = { workspace = true, features = ["alloy-compat"] } +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-flz = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } chrono.workspace = true eyre.workspace = true futures.workspace = true -revm.workspace = true +revm = { workspace = true, features = ["optional_balance_check"] } rand.workspace = true rand_08.workspace = true rayon.workspace = true serde_json.workspace = true serde.workspace = true -# aws-kms -aws-sdk-kms = { workspace = true, default-features = false, optional = true } - -# gcp-kms -gcloud-sdk = { version = "0.27", default-features = false, optional = true } - # bin foundry-cli.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -clap_complete = "4" -clap_complete_fig = "4" +clap_complete.workspace = true comfy-table.workspace = true dunce.workspace = true itertools.workspace = true @@ -93,15 +93,28 @@ yansi.workspace = true evmole.workspace = true [dev-dependencies] +alloy-hardforks.workspace = true +alloy-serde.workspace = true +dirs.workspace = true anvil.workspace = true foundry-test-utils.workspace = true [features] -default = ["jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] -aws-kms = ["foundry-wallets/aws-kms", "dep:aws-sdk-kms"] -gcp-kms = ["foundry-wallets/gcp-kms", "dep:gcloud-sdk"] +aws-kms = ["foundry-wallets/aws-kms"] +gcp-kms = ["foundry-wallets/gcp-kms"] +turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "dep:op-alloy-flz", + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "foundry-common/optimism", + "foundry-evm-networks/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 6c686e24d0706..fc278cdfbc2a6 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -1,54 +1,60 @@ use crate::{ + Cast, SimpleCast, + cmd::erc20::IERC20, opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs}, traces::identifier::SignaturesIdentifier, - Cast, SimpleCast, + tx::CastTxSender, }; -use alloy_consensus::transaction::{Recovered, SignerRecoverable}; use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; -use alloy_ens::{namehash, ProviderEnsExt}; -use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_ens::{ProviderEnsExt, namehash}; +use alloy_network::Ethereum; +use alloy_primitives::{Address, B256, eip191_hash_message, hex, keccak256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use clap::{CommandFactory, Parser}; use clap_complete::generate; -use eyre::Result; -use foundry_cli::{handler, utils, utils::LoadConfig}; +use eyre::{Result, WrapErr}; +use foundry_cli::utils::{self, LoadConfig}; use foundry_common::{ abi::{get_error, get_event}, - fmt::{format_tokens, format_tokens_raw, format_uint_exp}, + fmt::{format_tokens, format_uint_exp, serialize_value_as_json}, fs, + provider::ProviderBuilder, selectors::{ - decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, - import_selectors, parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, - SelectorKind, + ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic, + decode_function_selector, decode_selectors, import_selectors, parse_signatures, + pretty_calldata, }, shell, stdin, }; +use foundry_evm_networks::NetworkVariant; +#[cfg(feature = "optimism")] +use op_alloy_network::Optimism; use std::time::Instant; +use tempo_alloy::TempoNetwork; /// Run the `cast` command-line interface. pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = CastArgs::parse(); args.global.init()?; - - run_command(args) + args.global.tokio_runtime().block_on(run_command(args)) } /// Setup the global logger and other utilities. pub fn setup() -> Result<()> { - utils::install_crypto_provider(); - handler::install(); - utils::load_dotenv(); + utils::common_setup(); utils::subscriber(); - utils::enable_paint(); Ok(()) } /// Run the subcommand. -#[tokio::main] +#[allow(clippy::large_stack_frames)] pub async fn run_command(args: CastArgs) -> Result<()> { match args.cmd { // Constants @@ -110,9 +116,9 @@ pub async fn run_command(args: CastArgs) -> Result<()> { }; sh_println!("0x{output}")? } - CastSubcommand::ToCheckSumAddress { address } => { + CastSubcommand::ToCheckSumAddress { address, chain_id } => { let value = stdin::unwrap_line(address)?; - sh_println!("{}", value.to_checksum(None))? + sh_println!("{}", value.to_checksum(chain_id))? } CastSubcommand::ToUint256 { value } => { let value = stdin::unwrap_line(value)?; @@ -166,6 +172,10 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let value = stdin::unwrap_line(bytes)?; sh_println!("{}", SimpleCast::to_bytes32(&value)?)? } + CastSubcommand::Pad { data, right, left: _, len } => { + let value = stdin::unwrap_line(data)?; + sh_println!("{}", SimpleCast::pad(&value, right, len)?)? + } CastSubcommand::FormatBytes32String { string } => { let value = stdin::unwrap_line(string)?; sh_println!("{}", SimpleCast::format_bytes32_string(&value)?)? @@ -185,14 +195,30 @@ pub async fn run_command(args: CastArgs) -> Result<()> { print_tokens(&tokens); } CastSubcommand::AbiEncode { sig, packed, args } => { - if !packed { - sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? - } else { + if packed { sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? + } else { + sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? } } - CastSubcommand::DecodeCalldata { sig, calldata } => { - let tokens = SimpleCast::calldata_decode(&sig, &calldata, true)?; + CastSubcommand::AbiEncodeEvent { sig, args } => { + let log_data = SimpleCast::abi_encode_event(&sig, &args)?; + for (i, topic) in log_data.topics().iter().enumerate() { + sh_println!("[topic{}]: {}", i, topic)?; + } + if !log_data.data.is_empty() { + sh_println!("[data]: {}", hex::encode_prefixed(log_data.data))?; + } + } + CastSubcommand::DecodeCalldata { sig, calldata, file } => { + let raw_hex = if let Some(file_path) = file { + let contents = fs::read_to_string(&file_path)?; + contents.trim().to_string() + } else { + calldata.unwrap() + }; + + let tokens = SimpleCast::calldata_decode(&sig, &raw_hex, true)?; print_tokens(&tokens); } CastSubcommand::CalldataEncode { sig, args, file } => { @@ -218,7 +244,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let event = get_event(event_sig.as_str())?; event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)? } else { - let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let data = crate::strip_0x(&data); let selector = data.get(..64).unwrap_or_default(); let selector = selector.parse()?; let identified_event = @@ -238,7 +264,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let error = if let Some(err_sig) = sig { get_error(err_sig.as_str())? } else { - let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let data = crate::strip_0x(&data); let selector = data.get(..8).unwrap_or_default(); let identified_error = SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await; @@ -257,6 +283,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, CastSubcommand::Artifact(cmd) => cmd.run().await?, CastSubcommand::Bind(cmd) => cmd.run().await?, + CastSubcommand::B2EPayload(cmd) => cmd.run().await?, CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; sh_println!("{}", pretty_calldata(&calldata, offline).await?)?; @@ -293,8 +320,13 @@ pub async fn run_command(args: CastArgs) -> Result<()> { match erc20 { Some(token) => { - let balance = - Cast::new(&provider).erc20_balance(token, account_addr, block).await?; + let balance = IERC20::new(token, &provider) + .balanceOf(account_addr) + .block(block.unwrap_or_default()) + .call() + .await?; + + sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?; sh_println!("{}", format_uint_exp(balance))? } None => { @@ -315,15 +347,43 @@ pub async fn run_command(args: CastArgs) -> Result<()> { Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? )? } - CastSubcommand::Block { block, full, field, rpc } => { + CastSubcommand::Block { block, full, fields, raw, rpc, network } => { let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; - sh_println!( - "{}", + // Can use either --raw or specify raw as a field + let output = if raw || fields.contains(&"raw".into()) { + match network { + #[cfg(feature = "optimism")] + Some(NetworkVariant::Optimism) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + Some(NetworkVariant::Tempo) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + // Ethereum (default) or no --raw flag + _ => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) + .await? + } + } + } else { + let provider = utils::get_provider(&config)?; Cast::new(provider) - .block(block.unwrap_or(BlockId::Number(Latest)), full, field) + .block(block.unwrap_or(BlockId::Number(Latest)), full, fields) .await? - )? + }; + sh_println!("{}", output)? } CastSubcommand::BlockNumber { rpc, block } => { let config = rpc.load_config()?; @@ -368,12 +428,21 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codesize(who, block).await?)? } - CastSubcommand::ComputeAddress { address, nonce, rpc } => { - let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; - + CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => { let address = stdin::unwrap_line(address)?; - let computed = Cast::new(provider).compute_address(address, nonce).await?; + let computed = { + // For CREATE2, init_code_hash is needed to compute the address + if let Some(init_code_hash) = init_code_hash { + address.create2(salt.unwrap_or(B256::ZERO), init_code_hash) + } else if let Some(init_code) = init_code { + address.create2(salt.unwrap_or(B256::ZERO), keccak256(hex::decode(init_code)?)) + } else { + // For CREATE, rpc is needed to compute the address + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + Cast::new(provider).compute_address(address, nonce).await? + } + }; sh_println!("Computed Address: {}", computed.to_checksum(None))? } CastSubcommand::Disassemble { bytecode } => { @@ -400,7 +469,9 @@ pub async fn run_command(args: CastArgs) -> Result<()> { { if resolve { let resolved = &resolve_results[pos]; - sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}")? + sh_println!( + "{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}" + )? } else { sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")? } @@ -486,24 +557,44 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let provider = utils::get_provider(&config)?; sh_println!( "{}", - Cast::new(provider) + CastTxSender::new(provider) .receipt(tx_hash, field, confirmations, None, cast_async) .await? )? } CastSubcommand::Run(cmd) => cmd.run().await?, CastSubcommand::SendTx(cmd) => cmd.run().await?, - CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc } => { + CastSubcommand::BatchMakeTx(cmd) => cmd.run().await?, + CastSubcommand::BatchSend(cmd) => cmd.run().await?, + CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request, network } => { let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; - // Can use either --raw or specify raw as a field - let raw = raw || field.as_ref().is_some_and(|f| f == "raw"); + let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); + let output = match network { + #[cfg(feature = "optimism")] + Some(NetworkVariant::Optimism) => { + let provider = ProviderBuilder::::from_config(&config)?.build()?; - sh_println!( - "{}", - Cast::new(&provider).transaction(tx_hash, from, nonce, field, raw).await? - )? + Cast::new(&provider) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) + .await? + } + Some(NetworkVariant::Tempo) => { + let provider = + ProviderBuilder::::from_config(&config)?.build()?; + Cast::new(&provider) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) + .await? + } + // Ethereum (default) or no --raw flag + _ => { + let provider = utils::get_provider(&config)?; + Cast::new(&provider) + .transaction(tx_hash, from, nonce, field, is_raw, to_request) + .await? + } + }; + sh_println!("{}", output)? } // 4Byte @@ -596,7 +687,10 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; - let address = provider.resolve_name(&who).await?; + let address = provider + .resolve_name(&who) + .await + .wrap_err(format!("Failed to resolve ENS name: {who}"))?; if verify { let name = provider.lookup_address(&address).await?; eyre::ensure!( @@ -626,7 +720,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { }; } CastSubcommand::HashMessage { message } => { - let message = stdin::unwrap_line(message)?; + let message = stdin::unwrap(message, false)?; sh_println!("{}", eip191_hash_message(message))? } CastSubcommand::SigEvent { event_string } => { @@ -696,37 +790,50 @@ pub async fn run_command(args: CastArgs) -> Result<()> { CastSubcommand::Completions { shell } => { generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout()) } - CastSubcommand::GenerateFigSpec => clap_complete::generate( - clap_complete_fig::Fig, - &mut CastArgs::command(), - "cast", - &mut std::io::stdout(), - ), CastSubcommand::Logs(cmd) => cmd.run().await?, - CastSubcommand::DecodeTransaction { tx } => { + CastSubcommand::DecodeTransaction { tx, network } => { let tx = stdin::unwrap_line(tx)?; - let tx = SimpleCast::decode_raw_transaction(&tx)?; - - if let Ok(signer) = tx.recover_signer() { - let recovered = Recovered::new_unchecked(tx, signer); - sh_println!("{}", serde_json::to_string_pretty(&recovered)?)?; - } else { - sh_println!("{}", serde_json::to_string_pretty(&tx)?)?; - } + let decoded_tx = match network { + #[cfg(feature = "optimism")] + Some(NetworkVariant::Optimism) => { + SimpleCast::decode_raw_transaction::(&tx)? + } + Some(NetworkVariant::Tempo) => { + SimpleCast::decode_raw_transaction::(&tx)? + } + _ => SimpleCast::decode_raw_transaction::(&tx)?, + }; + sh_println!("{decoded_tx}")?; + } + CastSubcommand::RecoverAuthority { auth } => { + let auth: SignedAuthorization = serde_json::from_str(&auth)?; + sh_println!("{}", auth.recover_authority()?)?; } CastSubcommand::TxPool { command } => command.run().await?, + CastSubcommand::Erc20Token { command } => command.run().await?, + CastSubcommand::Tip20Token { command } => command.run().await?, + CastSubcommand::Keychain { command } => command.run().await?, + CastSubcommand::Tempo { command } => command.run().await?, + CastSubcommand::VirtualAddress { command } => command.run().await?, + #[cfg(feature = "optimism")] CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } + CastSubcommand::Trace(cmd) => cmd.run().await?, }; - /// Prints slice of tokens using [`format_tokens`] or [`format_tokens_raw`] depending whether - /// the shell is in JSON mode. + /// Prints slice of tokens using [`format_tokens`] or [`serialize_value_as_json`] depending + /// whether the shell is in JSON mode. /// /// This is included here to avoid a cyclic dependency between `fmt` and `common`. fn print_tokens(tokens: &[DynSolValue]) { if shell::is_json() { - let tokens: Vec = format_tokens_raw(tokens).collect(); + let tokens: Vec = tokens + .iter() + .cloned() + .map(|t| serialize_value_as_json(t, None)) + .collect::>>() + .unwrap(); let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap()); } else { let tokens = format_tokens(tokens); diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 1b71d65131b0f..085df0b55e89a 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{utils::ParseUnits, Sign, I256, U256}; +use alloy_primitives::{I256, Sign, U256, utils::ParseUnits}; use eyre::Result; use std::{ convert::Infallible, @@ -120,27 +120,11 @@ impl Base { // anyway; // strip prefix when using u128::from_str_radix because it does not recognize it as // valid. - _ if s.starts_with("0b") => match u64::from_str_radix(&s[2..], 2) { - Ok(_) => Ok(Self::Binary), - Err(e) => match e.kind() { - IntErrorKind::PosOverflow => Ok(Self::Binary), - _ => Err(eyre::eyre!("could not parse binary value: {}", e)), - }, - }, - _ if s.starts_with("0o") => match u64::from_str_radix(&s[2..], 8) { - Ok(_) => Ok(Self::Octal), - Err(e) => match e.kind() { - IntErrorKind::PosOverflow => Ok(Self::Octal), - _ => Err(eyre::eyre!("could not parse octal value: {e}")), - }, - }, - _ if s.starts_with("0x") => match u64::from_str_radix(&s[2..], 16) { - Ok(_) => Ok(Self::Hexadecimal), - Err(e) => match e.kind() { - IntErrorKind::PosOverflow => Ok(Self::Hexadecimal), - _ => Err(eyre::eyre!("could not parse hexadecimal value: {e}")), - }, - }, + _ if s.starts_with("0b") => Self::detect_prefixed(s, 2, Self::Binary, "binary"), + _ if s.starts_with("0o") => Self::detect_prefixed(s, 8, Self::Octal, "octal"), + _ if s.starts_with("0x") => { + Self::detect_prefixed(s, 16, Self::Hexadecimal, "hexadecimal") + } // No prefix => first try parsing as decimal _ => match U256::from_str_radix(s, 10) { // Can be both, ambiguous but default to Decimal @@ -155,6 +139,18 @@ impl Base { } } + /// Detects a prefixed base by stripping the 2-char prefix and parsing the remainder. + /// `PosOverflow` is treated as a valid match since the digits are correct for the base. + fn detect_prefixed(s: &str, radix: u32, base: Self, label: &str) -> Result { + match u64::from_str_radix(&s[2..], radix) { + Ok(_) => Ok(base), + Err(e) => match e.kind() { + IntErrorKind::PosOverflow => Ok(base), + _ => Err(eyre::eyre!("could not parse {label} value: {e}")), + }, + } + } + /// Returns the Rust standard prefix for a base pub const fn prefix(&self) -> &str { match self { @@ -318,7 +314,7 @@ impl NumberWithBase { } /// Creates a copy of the number with the provided base. - pub fn with_base(&self, base: Base) -> Self { + pub const fn with_base(&self, base: Base) -> Self { Self { number: self.number, is_nonnegative: self.is_nonnegative, base } } @@ -340,17 +336,17 @@ impl NumberWithBase { /// Returns a copy of the underlying number as an unsigned integer. If the value is negative /// then the two's complement of its absolute value will be returned. - pub fn number(&self) -> U256 { + pub const fn number(&self) -> U256 { self.number } /// Returns whether the underlying number is positive or zero. - pub fn is_nonnegative(&self) -> bool { + pub const fn is_nonnegative(&self) -> bool { self.is_nonnegative } /// Returns the underlying base. Defaults to [Decimal][Base]. - pub fn base(&self) -> Base { + pub const fn base(&self) -> Base { self.base } @@ -360,7 +356,7 @@ impl NumberWithBase { } /// Sets the number's base to format to. - pub fn set_base(&mut self, base: Base) -> &mut Self { + pub const fn set_base(&mut self, base: Base) -> &mut Self { self.base = base; self } @@ -384,11 +380,7 @@ impl NumberWithBase { } Base::Hexadecimal => format!("{:x}", self.number), }; - if s.starts_with('0') { - s.trim_start_matches('0').to_string() - } else { - s - } + if s.starts_with('0') { s.trim_start_matches('0').to_string() } else { s } } fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> { @@ -445,11 +437,7 @@ impl ToBase for NumberWithBase { fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = self.with_base(base); - if add_prefix { - Ok(format!("{n:#?}")) - } else { - Ok(format!("{n:?}")) - } + if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } @@ -458,11 +446,7 @@ impl ToBase for I256 { fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from(*self).with_base(base); - if add_prefix { - Ok(format!("{n:#?}")) - } else { - Ok(format!("{n:?}")) - } + if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } @@ -471,11 +455,7 @@ impl ToBase for U256 { fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from(*self).with_base(base); - if add_prefix { - Ok(format!("{n:#?}")) - } else { - Ok(format!("{n:?}")) - } + if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } @@ -492,11 +472,7 @@ impl ToBase for str { fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from_str(self)?.with_base(base); - if add_prefix { - Ok(format!("{n:#?}")) - } else { - Ok(format!("{n:?}")) - } + if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } diff --git a/crates/cast/src/call_spec.rs b/crates/cast/src/call_spec.rs new file mode 100644 index 0000000000000..a1be179fbecc7 --- /dev/null +++ b/crates/cast/src/call_spec.rs @@ -0,0 +1,219 @@ +//! Call specification parsing for batch transactions. +//! +//! Parses call specs in the format: `to[:][:[:]]` or `to[:][:<0xrawdata>]` +//! +//! Examples: +//! - `0x123` - Just an address (empty call) +//! - `0x123:0.1ether` - ETH transfer +//! - `0x123::transfer(address,uint256):0x789,1000` - Contract call with signature +//! - `0x123::0xabcdef` - Contract call with raw calldata + +use alloy_network::Network; +use alloy_primitives::{Address, Bytes, U256, hex}; +use alloy_provider::Provider; +use eyre::{Result, WrapErr, eyre}; +use foundry_cli::utils::parse_function_args; +use foundry_config::Chain; +use std::str::FromStr; +use tempo_primitives::transaction::Call; + +/// A parsed call specification for batch transactions. +#[derive(Debug, Clone)] +pub struct CallSpec { + /// Target address (required) + pub to: Address, + /// ETH value to send (optional, defaults to 0) + pub value: U256, + /// Function signature, e.g., "transfer(address,uint256)" (optional) + pub sig: Option, + /// Function arguments (optional) + pub args: Vec, + /// Raw calldata if provided instead of sig+args (optional) + pub data: Option, +} + +impl CallSpec { + /// Parse a call spec string. + /// + /// Format: `to[:][:[:]]` or `to[:][:<0xrawdata>]` + /// + /// The delimiter is `:` but we need to be careful about: + /// - Colons in function signatures (none expected) + /// - Colons in hex addresses (none expected) + /// - We use double-colon `::` to separate value from sig/data when value is empty + pub fn parse(s: &str) -> Result { + let s = s.trim(); + if s.is_empty() { + return Err(eyre!("Empty call specification")); + } + + // Split by `:` but handle `::` for empty value + let parts: Vec<&str> = s.split(':').collect(); + + if parts.is_empty() { + return Err(eyre!("Invalid call specification: {}", s)); + } + + // First part is always the address + let to = Address::from_str(parts[0]) + .map_err(|e| eyre!("Invalid address '{}': {}", parts[0], e))?; + + let mut value = U256::ZERO; + let mut sig = None; + let mut args = Vec::new(); + let mut data = None; + + // Parse remaining parts + // Pattern: to:value:sig:args or to::sig:args (empty value) or to:value:0xdata + let mut idx = 1; + + // Check for value (non-empty, not starting with 0x unless it's a number) + if idx < parts.len() { + let part = parts[idx]; + if !part.is_empty() && !part.starts_with("0x") && !part.contains('(') { + // This looks like a value + value = parse_ether_or_wei(part)?; + idx += 1; + } else if part.is_empty() { + // Empty value (::), skip + idx += 1; + } + } + + // Check for sig/data + if idx < parts.len() { + let part = parts[idx]; + if part.starts_with("0x") { + // Raw calldata + data = Some(Bytes::from( + hex::decode(part).map_err(|e| eyre!("Invalid hex data '{}': {}", part, e))?, + )); + } else if !part.is_empty() { + // Function signature + sig = Some(part.to_string()); + idx += 1; + + // Collect remaining parts as args (comma-separated in the last part) + if idx < parts.len() { + let args_str = parts[idx..].join(":"); + args = args_str.split(',').map(|s| s.trim().to_string()).collect(); + } + } + } + + Ok(Self { to, value, sig, args, data }) + } + + /// Resolves this spec into a [`Call`], encoding function arguments if needed. + /// `i` is the 0-based index of this call; displayed as `i + 1` in error messages. + pub async fn resolve>( + &self, + i: usize, + chain: Chain, + provider: &P, + etherscan_api_key: Option<&str>, + etherscan_api_url: Option<&str>, + ) -> Result { + let input = if let Some(data) = &self.data { + data.clone() + } else if let Some(sig) = &self.sig { + let (encoded, _) = parse_function_args( + sig, + self.args.clone(), + Some(self.to), + chain, + provider, + etherscan_api_key, + etherscan_api_url, + ) + .await + .map_err(|e| eyre!("Failed to encode call {}: {e}", i + 1))?; + Bytes::from(encoded) + } else { + Bytes::new() + }; + Ok(Call { to: self.to.into(), value: self.value, input }) + } +} + +impl FromStr for CallSpec { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} + +/// Parse a value string that can be in ether notation (e.g., "0.1ether") or raw wei. +fn parse_ether_or_wei(s: &str) -> Result { + // Use alloy's DynSolType coercion which handles "1ether", "1gwei", "1000" etc. + if s.starts_with("0x") || s.starts_with("0X") { + U256::from_str(s).map_err(|e| eyre!("Invalid hex value '{}': {}", s, e)) + } else { + alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), s) + .wrap_err_with(|| format!("Invalid value '{s}'"))? + .as_uint() + .map(|(v, _)| v) + .ok_or_else(|| eyre!("Could not parse value '{}'", s)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_address_only() { + let spec = CallSpec::parse("0x1234567890123456789012345678901234567890").unwrap(); + assert_eq!( + spec.to, + "0x1234567890123456789012345678901234567890".parse::
().unwrap() + ); + assert_eq!(spec.value, U256::ZERO); + assert!(spec.sig.is_none()); + assert!(spec.args.is_empty()); + assert!(spec.data.is_none()); + } + + #[test] + fn test_parse_with_value() { + let spec = CallSpec::parse("0x1234567890123456789012345678901234567890:1ether").unwrap(); + assert_eq!(spec.value, parse_ether_or_wei("1ether").unwrap()); + assert!(spec.sig.is_none()); + } + + #[test] + fn test_parse_hex_value() { + assert_eq!(parse_ether_or_wei("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_or_wei("0X10").unwrap(), U256::from(16)); + } + + #[test] + fn test_parse_with_sig() { + let spec = CallSpec::parse( + "0x1234567890123456789012345678901234567890::transfer(address,uint256):0xabc,1000", + ) + .unwrap(); + assert_eq!(spec.value, U256::ZERO); + assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string())); + assert_eq!(spec.args, vec!["0xabc", "1000"]); + } + + #[test] + fn test_parse_with_value_and_sig() { + let spec = CallSpec::parse( + "0x1234567890123456789012345678901234567890:0.5ether:transfer(address,uint256):0xabc,1000", + ) + .unwrap(); + assert_eq!(spec.value, parse_ether_or_wei("0.5ether").unwrap()); + assert_eq!(spec.sig, Some("transfer(address,uint256)".to_string())); + } + + #[test] + fn test_parse_with_raw_data() { + let spec = CallSpec::parse("0x1234567890123456789012345678901234567890::0xabcdef").unwrap(); + assert_eq!(spec.value, U256::ZERO); + assert!(spec.sig.is_none()); + assert_eq!(spec.data, Some(Bytes::from(hex::decode("abcdef").unwrap()))); + } +} diff --git a/crates/cast/src/cmd/access_list.rs b/crates/cast/src/cmd/access_list.rs index 700a37b22a887..9776502145afd 100644 --- a/crates/cast/src/cmd/access_list.rs +++ b/crates/cast/src/cmd/access_list.rs @@ -1,16 +1,20 @@ use crate::{ - tx::{CastTxBuilder, SenderKind}, Cast, + tx::{CastTxBuilder, SenderKind}, }; use alloy_ens::NameOrAddress; +use alloy_network::{Ethereum, Network}; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; use foundry_cli::{ - opts::{EthereumOpts, TransactionOpts}, - utils::{self, LoadConfig}, + opts::{RpcOpts, TransactionOpts}, + utils::LoadConfig, }; +use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; +use foundry_wallets::WalletOpts; use std::str::FromStr; +use tempo_alloy::TempoNetwork; /// CLI arguments for `cast access-list`. #[derive(Debug, Parser)] @@ -27,9 +31,16 @@ pub struct AccessListArgs { sig: Option, /// The arguments of the function to call. - #[arg(value_name = "ARGS")] + #[arg(value_name = "ARGS", allow_negative_numbers = true)] args: Vec, + /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. + #[arg( + long, + conflicts_with_all = &["sig", "args"] + )] + data: Option, + /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. @@ -40,16 +51,34 @@ pub struct AccessListArgs { tx: TransactionOpts, #[command(flatten)] - eth: EthereumOpts, + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, } impl AccessListArgs { pub async fn run(self) -> Result<()> { - let Self { to, sig, args, tx, eth, block } = self; + if self.tx.tempo.is_tempo() { + self.run_with_network::().await + } else { + self.run_with_network::().await + } + } + + pub async fn run_with_network(self) -> Result<()> + where + N::TransactionRequest: FoundryTransactionBuilder, + { + let Self { to, mut sig, args, data, tx, rpc, wallet, block } = self; + + if let Some(data) = data { + sig = Some(data); + } - let config = eth.load_config()?; - let provider = utils::get_provider(&config)?; - let sender = SenderKind::from_wallet_opts(eth.wallet).await?; + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let sender = SenderKind::from_wallet_opts(wallet).await?; let (tx, _) = CastTxBuilder::new(&provider, tx, &config) .await? @@ -57,15 +86,48 @@ impl AccessListArgs { .await? .with_code_sig_and_args(None, sig, args) .await? - .build_raw(sender) + .raw() + .build(sender) .await?; - let cast = Cast::new(&provider); - - let access_list: String = cast.access_list(&tx, block).await?; + let access_list: String = Cast::new(&provider).access_list(&tx, block).await?; sh_println!("{access_list}")?; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + use clap::error::ErrorKind; + + #[test] + fn can_parse_access_list_data() { + let data = hex::encode("hello"); + let args = AccessListArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + assert_eq!(args.data, Some(data)); + + let data = hex::encode_prefixed("hello"); + let args = AccessListArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + assert_eq!(args.data, Some(data)); + } + + #[test] + fn data_conflicts_with_sig_and_args() { + let err = AccessListArgs::try_parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000001", + "transfer(address,uint256)", + "0x0000000000000000000000000000000000000002", + "1", + "--data", + "0x1234", + ]) + .unwrap_err(); + + assert_eq!(err.kind(), ErrorKind::ArgumentConflict); + } +} diff --git a/crates/cast/src/cmd/artifact.rs b/crates/cast/src/cmd/artifact.rs index 79ec880d34cfd..72fc844339ffa 100644 --- a/crates/cast/src/cmd/artifact.rs +++ b/crates/cast/src/cmd/artifact.rs @@ -1,19 +1,21 @@ use super::{ creation_code::{fetch_creation_code_from_etherscan, parse_code_output}, - interface::{fetch_abi_from_etherscan, load_abi_from_file}, + interface::load_abi_from_file, }; use alloy_primitives::Address; use alloy_provider::Provider; -use clap::{command, Parser}; +use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, fetch_abi_from_etherscan}, }; use foundry_common::fs; use serde_json::json; use std::path::PathBuf; +foundry_config::impl_figment_convert!(ArtifactArgs, etherscan, rpc); + /// CLI arguments for `cast artifact`. #[derive(Parser)] pub struct ArtifactArgs { @@ -45,24 +47,25 @@ pub struct ArtifactArgs { impl ArtifactArgs { pub async fn run(self) -> Result<()> { - let Self { contract, mut etherscan, rpc, output: output_location, abi_path } = self; + let mut config = self.load_config()?; + + let Self { contract, output: output_location, abi_path, etherscan: _, rpc: _ } = self; - let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let chain = provider.get_chain_id().await?; - etherscan.chain = Some(chain.into()); + config.chain = Some(chain.into()); let abi = if let Some(ref abi_path) = abi_path { load_abi_from_file(abi_path, None)? } else { - fetch_abi_from_etherscan(contract, ðerscan).await? + fetch_abi_from_etherscan(contract, &config).await? }; let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?; - let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; let bytecode = - parse_code_output(bytecode, contract, ðerscan, abi_path.as_deref(), true, false) + parse_code_output(bytecode, contract, &config, abi_path.as_deref(), true, false) .await?; let artifact = json!({ diff --git a/crates/cast/src/cmd/b2e_payload.rs b/crates/cast/src/cmd/b2e_payload.rs new file mode 100644 index 0000000000000..7e3fc9a05d63f --- /dev/null +++ b/crates/cast/src/cmd/b2e_payload.rs @@ -0,0 +1,110 @@ +//! Command Line handler to convert Beacon block's execution payload to Execution format. + +use std::path::PathBuf; + +use alloy_rpc_types_beacon::payload::BeaconBlockData; +use clap::{Parser, builder::ValueParser}; +use eyre::{Result, eyre}; +use foundry_common::{fs, sh_print}; + +/// CLI arguments for `cast b2e-payload`, convert Beacon block's execution payload to Execution +/// format. +#[derive(Parser)] +pub struct B2EPayloadArgs { + /// Input data, it can be either a file path to JSON file or raw JSON string containing the + /// beacon block + #[arg(value_name = "INPUT", value_parser=ValueParser::new(parse_input_source), help = "File path to JSON file or raw JSON string containing the beacon block")] + pub input: InputSource, +} + +impl B2EPayloadArgs { + pub async fn run(self) -> Result<()> { + let beacon_block_json = match self.input { + InputSource::Json(json) => json, + InputSource::File(path) => fs::read_to_string(&path) + .map_err(|e| eyre!("Failed to read JSON file '{}': {}", path.display(), e))?, + }; + + let beacon_block_data: BeaconBlockData = serde_json::from_str(&beacon_block_json) + .map_err(|e| eyre!("Failed to parse beacon block JSON: {}", e))?; + + let execution_payload = beacon_block_data.execution_payload(); + + // Output raw execution payload + let output = serde_json::to_string(&execution_payload) + .map_err(|e| eyre!("Failed to serialize execution payload: {}", e))?; + sh_print!("{}", output)?; + + Ok(()) + } +} + +/// Represents the different input sources for beacon block data +#[derive(Debug, Clone)] +pub enum InputSource { + /// Path to a JSON file containing beacon block data + File(PathBuf), + /// Raw JSON string containing beacon block data + Json(String), +} + +fn parse_input_source(s: &str) -> Result { + // Try parsing as JSON first + if serde_json::from_str::(s).is_ok() { + return Ok(InputSource::Json(s.to_string())); + } + + // Otherwise treat as file path + Ok(InputSource::File(PathBuf::from(s))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_input_source_json_object() { + let json_input = r#"{"execution_payload": {"block_hash": "0x123"}}"#; + let result = parse_input_source(json_input).unwrap(); + + match result { + InputSource::Json(json) => assert_eq!(json, json_input), + InputSource::File(_) => panic!("Expected JSON input, got File"), + } + } + + #[test] + fn test_parse_input_source_json_array() { + let json_input = r#"[{"block": "data"}]"#; + let result = parse_input_source(json_input).unwrap(); + + match result { + InputSource::Json(json) => assert_eq!(json, json_input), + InputSource::File(_) => panic!("Expected JSON input, got File"), + } + } + + #[test] + fn test_parse_input_source_file_path() { + let file_path = + "block-12225729-6ceadbf2a6adbbd64cbec33fdebbc582f25171cd30ac43f641cbe76ac7313ddf.json"; + let result = parse_input_source(file_path).unwrap(); + + match result { + InputSource::File(path) => assert_eq!(path, PathBuf::from(file_path)), + InputSource::Json(_) => panic!("Expected File input, got JSON"), + } + } + + #[test] + fn test_parse_input_source_malformed_but_not_json() { + let malformed = "not-json-{"; + let result = parse_input_source(malformed).unwrap(); + + // Should be treated as file path since it's not valid JSON + match result { + InputSource::File(path) => assert_eq!(path, PathBuf::from(malformed)), + InputSource::Json(_) => panic!("Expected File input, got File"), + } + } +} diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs new file mode 100644 index 0000000000000..e25798086d512 --- /dev/null +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -0,0 +1,171 @@ +//! `cast batch-mktx` command implementation. +//! +//! Creates a signed or unsigned batch transaction using Tempo's native call batching. +//! Outputs the RLP-encoded transaction hex. + +use crate::{ + call_spec::CallSpec, + tx::{self, CastTxBuilder}, +}; +use alloy_consensus::SignableTransaction; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder}; +use alloy_primitives::Address; +use alloy_provider::Provider; +use alloy_signer::Signer; +use clap::Parser; +use eyre::{Result, eyre}; +use foundry_cli::{ + opts::{EthereumOpts, TransactionOpts}, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; +use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; +use tempo_alloy::TempoNetwork; + +/// CLI arguments for `cast batch-mktx`. +/// +/// Creates a signed (or unsigned) batch transaction. +#[derive(Debug, Parser)] +pub struct BatchMakeTxArgs { + /// Call specifications in format: `to[:][:[:]]` or `to[:][:<0xdata>]` + /// + /// Examples: + /// --call "0x123:0.1ether" (ETH transfer) + /// --call "0x456::transfer(address,uint256):0x789,1000" (ERC20 transfer) + /// --call "0xabc::0x123def" (raw calldata) + #[arg(long = "call", value_name = "SPEC", required = true)] + pub calls: Vec, + + #[command(flatten)] + pub tx: TransactionOpts, + + #[command(flatten)] + pub eth: EthereumOpts, + + /// Generate a raw RLP-encoded unsigned transaction. + #[arg(long)] + pub raw_unsigned: bool, + + /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender + #[arg(long, requires = "from", conflicts_with = "raw_unsigned")] + pub ethsign: bool, +} + +impl BatchMakeTxArgs { + pub async fn run(self) -> Result<()> { + let Self { calls, mut tx, eth, raw_unsigned, ethsign } = self; + let has_nonce = tx.nonce.is_some(); + + if calls.is_empty() { + return Err(eyre!("No calls specified. Use --call to specify at least one call.")); + } + + let config = eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + + // Resolve signer to detect keychain mode + let (signer, tempo_access_key) = eth.wallet.maybe_signer().await?; + + // Parse all call specs + let call_specs: Vec = + calls.iter().map(|s| CallSpec::parse(s)).collect::>>()?; + + // Get chain for parsing function args + let chain = utils::get_chain(config.chain, &provider).await?; + let etherscan_config = config.get_etherscan_config_with_chain(Some(chain)).ok().flatten(); + let etherscan_api_key = etherscan_config.as_ref().map(|c| c.key.clone()); + let etherscan_api_url = etherscan_config.map(|c| c.api_url); + + let mut tempo_calls = Vec::with_capacity(call_specs.len()); + for (i, spec) in call_specs.iter().enumerate() { + tempo_calls.push( + spec.resolve( + i, + chain, + &provider, + etherscan_api_key.as_deref(), + etherscan_api_url.as_deref(), + ) + .await?, + ); + } + + sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; + + // Preserve key_id for modes that do not call build_with_access_key, such as raw unsigned. + if let Some(ref access_key) = tempo_access_key { + tx.tempo.key_id = Some(access_key.key_address); + } + + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + + // Set calls on the transaction + builder.tx.calls = tempo_calls; + + // Set dummy "to" from first call + let first_call_to = call_specs.first().map(|s| s.to); + let builder = builder.with_to(first_call_to.map(Into::into)).await?; + let tx_builder = builder.with_code_sig_and_args(None, None, vec![]).await?; + + if raw_unsigned { + if eth.wallet.from.is_none() && !has_nonce { + eyre::bail!( + "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" + ); + } + + let from = eth.wallet.from.unwrap_or(Address::ZERO); + let (tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + let raw_tx = + alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); + sh_println!("{raw_tx}")?; + return Ok(()); + } + + if ethsign { + let (tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + let signed_tx = provider.sign_transaction(tx).await?; + sh_println!("{signed_tx}")?; + return Ok(()); + } + + // Default: use local signer + let signer = match signer { + Some(s) => s, + None => eth.wallet.signer().await?, + }; + + let signed_tx = if let Some(ref access_key) = tempo_access_key { + let (tx, _) = + tx_builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + let raw_tx = tx + .sign_with_access_key( + &provider, + &signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + alloy_primitives::hex::encode(raw_tx) + } else { + tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?; + let (tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + let envelope = tx.build(&EthereumWallet::new(signer)).await?; + alloy_primitives::hex::encode(envelope.encoded_2718()) + }; + + sh_println!("0x{signed_tx}")?; + + Ok(()) + } +} diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs new file mode 100644 index 0000000000000..33128ae897898 --- /dev/null +++ b/crates/cast/src/cmd/batch_send.rs @@ -0,0 +1,182 @@ +//! `cast batch-send` command implementation. +//! +//! Sends a batch of calls as a single Tempo transaction using native call batching. +//! Unlike upstream Foundry's sequential transactions, this uses a single type 0x76 +//! transaction with multiple calls executed atomically. + +use crate::{ + call_spec::CallSpec, + cmd::send::{cast_send, cast_send_with_access_key}, + tx::{self, CastTxBuilder, SendTxOpts}, +}; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_signer::Signer; +use clap::Parser; +use eyre::{Result, eyre}; +use foundry_cli::{ + opts::TransactionOpts, + utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; +use foundry_common::provider::ProviderBuilder; +use std::time::Duration; +use tempo_alloy::TempoNetwork; + +/// CLI arguments for `cast batch-send`. +/// +/// Sends multiple calls as a single atomic Tempo transaction. +#[derive(Debug, Parser)] +pub struct BatchSendArgs { + /// Call specifications in format: `to[:][:[:]]` or `to[:][:<0xdata>]` + /// + /// Examples: + /// --call "0x123:0.1ether" (ETH transfer) + /// --call "0x456::transfer(address,uint256):0x789,1000" (ERC20 transfer) + /// --call "0xabc::0x123def" (raw calldata) + /// --call "0x123:1ether:deposit()" (value + function call) + #[arg(long = "call", value_name = "SPEC", required = true)] + pub calls: Vec, + + #[command(flatten)] + pub send_tx: SendTxOpts, + + #[command(flatten)] + pub tx: TransactionOpts, + + /// Send via `eth_sendTransaction` using the `--from` argument or $ETH_FROM as sender + #[arg(long, requires = "from")] + pub unlocked: bool, +} + +impl BatchSendArgs { + pub async fn run(self) -> Result<()> { + let Self { calls, send_tx, mut tx, unlocked } = self; + + if calls.is_empty() { + return Err(eyre!("No calls specified. Use --call to specify at least one call.")); + } + + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)) + } + + // Resolve signer to detect keychain mode + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + + // Parse all call specs + let call_specs: Vec = + calls.iter().map(|s| CallSpec::parse(s)).collect::>>()?; + + // Get chain for parsing function args + let chain = utils::get_chain(config.chain, &provider).await?; + let etherscan_config = config.get_etherscan_config_with_chain(Some(chain)).ok().flatten(); + let etherscan_api_key = etherscan_config.as_ref().map(|c| c.key.clone()); + let etherscan_api_url = etherscan_config.map(|c| c.api_url); + + // Build Vec from specs + let mut tempo_calls = Vec::with_capacity(call_specs.len()); + for (i, spec) in call_specs.iter().enumerate() { + tempo_calls.push( + spec.resolve( + i, + chain, + &provider, + etherscan_api_key.as_deref(), + etherscan_api_url.as_deref(), + ) + .await?, + ); + } + + sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?; + + // Preserve key_id for modes that do not call build_with_access_key, such as unlocked. + if let Some(ref access_key) = tempo_access_key { + tx.tempo.key_id = Some(access_key.key_address); + } + + // Build transaction request with calls + let mut builder = CastTxBuilder::::new(&provider, tx, &config).await?; + + // Access the inner tx and set calls + builder.tx.calls = tempo_calls; + + // We need to set a dummy "to" to satisfy the state machine, but the calls field + // will be used by build_aa. Set to first call's target. + let first_call_to = call_specs.first().map(|s| s.to); + let builder = builder.with_to(first_call_to.map(Into::into)).await?; + + // Use empty sig/args since we're using calls directly + let builder = builder.with_code_sig_and_args(None, None, vec![]).await?; + + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + + if unlocked { + let (tx, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + cast_send( + provider, + tx, + send_tx.cast_async, + send_tx.sync, + send_tx.confirmations, + timeout, + ) + .await + } else { + let signer = match signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; + + if let Some(ref access_key) = tempo_access_key { + let (tx_request, _) = + builder.build_with_access_key(access_key.wallet_address, access_key).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + cast_send_with_access_key( + &provider, + tx_request, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?; + let (tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + let wallet = EthereumWallet::from(signer); + let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() + .wallet(wallet) + .connect_provider(&provider); + + cast_send( + provider, + tx_request, + send_tx.cast_async, + send_tx.sync, + send_tx.confirmations, + timeout, + ) + .await?; + } + + Ok(()) + } + } +} diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 6a13678184d36..05c51d6d1968c 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -1,37 +1,51 @@ +use super::run::fetch_contracts_bytecode_from_trace; use crate::{ + Cast, + debug::handle_traces, traces::TraceKind, tx::{CastTxBuilder, SenderKind}, - Cast, }; use alloy_ens::NameOrAddress; -use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_network::{Network, NetworkTransactionBuilder, TransactionBuilder}; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256, hex, map::HashMap}; +use alloy_provider::Provider; use alloy_rpc_types::{ + BlockId, BlockNumberOrTag, BlockOverrides, state::{StateOverride, StateOverridesBuilder}, - BlockId, BlockNumberOrTag, }; use clap::Parser; use eyre::Result; use foundry_cli::{ - opts::{EthereumOpts, TransactionOpts}, - utils::{self, handle_traces, parse_ether_value, TraceResult}, + opts::{ChainValueParser, RpcOpts, TransactionOpts}, + utils::{LoadConfig, TraceResult, parse_ether_value}, +}; +use foundry_common::{ + FoundryTransactionBuilder, + abi::{encode_function_args, get_func}, + provider::{ProviderBuilder, curl_transport::generate_curl_command}, + sh_println, shell, }; -use foundry_common::shell; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ + Chain, Config, figment::{ - self, + self, Metadata, Profile, value::{Dict, Map}, - Figment, Metadata, Profile, }, - Config, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ + core::{ + FoundryBlock, FoundryTransaction, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, + }, executors::TracingExecutor, opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; +use foundry_wallets::WalletOpts; use regex::Regex; -use revm::context::TransactionType; use std::{str::FromStr, sync::LazyLock}; // matches override pattern
:: @@ -70,6 +84,7 @@ pub struct CallArgs { sig: Option, /// The arguments of the function to call. + #[arg(allow_negative_numbers = true)] args: Vec, /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. @@ -83,11 +98,22 @@ pub struct CallArgs { #[arg(long, default_value_t = false)] trace: bool, + /// Disables the labels in the traces. + /// Can only be set with `--trace`. + #[arg(long, default_value_t = false, requires = "trace")] + disable_labels: bool, + /// Opens an interactive debugger. /// Can only be used with `--trace`. #[arg(long, requires = "trace")] debug: bool, + /// Identify internal functions in traces. + /// + /// This will trace internal functions and decode stack parameters. + /// + /// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a + /// single function is matched, similarly to `--debug`, for performance reasons. #[arg(long, requires = "trace")] decode_internal: bool, @@ -107,10 +133,6 @@ pub struct CallArgs { #[arg(long, short)] block: Option, - /// Enable Odyssey features. - #[arg(long, alias = "alphanet")] - pub odyssey: bool, - #[command(subcommand)] command: Option, @@ -118,36 +140,56 @@ pub struct CallArgs { tx: TransactionOpts, #[command(flatten)] - eth: EthereumOpts, + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, + + #[arg( + short, + long, + alias = "chain-id", + env = "CHAIN", + value_parser = ChainValueParser::default(), + )] + pub chain: Option, /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] pub with_local_artifacts: bool, - /// Override the balance of an account. - /// Format: address:balance - #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE")] + /// Override the accounts balance. + /// Format: "address:balance,address:balance" + #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE", value_delimiter = ',')] pub balance_overrides: Option>, - /// Override the nonce of an account. - /// Format: address:nonce - #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE")] + /// Override the accounts nonce. + /// Format: "address:nonce,address:nonce" + #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE", value_delimiter = ',')] pub nonce_overrides: Option>, - /// Override the code of an account. - /// Format: address:code - #[arg(long = "override-code", value_name = "ADDRESS:CODE")] + /// Override the accounts code. + /// Format: "address:code,address:code" + #[arg(long = "override-code", value_name = "ADDRESS:CODE", value_delimiter = ',')] pub code_overrides: Option>, - /// Override the state of an account. - /// Format: address:slot:value - #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE")] + /// Override the accounts state and replace the current state entirely with the new one. + /// Format: "address:slot:value,address:slot:value" + #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')] pub state_overrides: Option>, - /// Override the state diff of an account. - /// Format: address:slot:value - #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE")] + /// Override the accounts state specific slots and preserve the rest of the state. + /// Format: "address:slot:value,address:slot:value" + #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')] pub state_diff_overrides: Option>, + + /// Override the block timestamp. + #[arg(long = "block.time", value_name = "TIME")] + pub block_time: Option, + + /// Override the block number. + #[arg(long = "block.number", value_name = "NUMBER")] + pub block_number: Option, } #[derive(Debug, Parser)] @@ -162,6 +204,7 @@ pub enum CallSubcommands { sig: Option, /// The arguments of the constructor. + #[arg(allow_negative_numbers = true)] args: Vec, /// Ether to send in the transaction. @@ -176,17 +219,48 @@ pub enum CallSubcommands { impl CallArgs { pub async fn run(self) -> Result<()> { - let figment = Into::::into(&self.eth).merge(&self); + // Handle --curl mode early, before any provider interaction + if self.rpc.curl { + return self.run_curl().await; + } + if self.tx.tempo.is_tempo() { + return self.run_with_network::().await; + } + + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + if let Some(chain) = self.chain { + evm_opts.networks = evm_opts.networks.with_chain_id(chain.id()); + } + evm_opts.infer_network_from_fork().await; + + if evm_opts.networks.is_tempo() { + return self.run_with_network::().await; + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_network::().await; + } + + self.run_with_network::().await + } + + pub async fn run_with_network(self) -> Result<()> + where + ::TransactionRequest: FoundryTransactionBuilder, + { + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::from_provider(figment)?.sanitized(); let state_overrides = self.get_state_overrides()?; + let block_overrides = self.get_block_overrides()?; let Self { to, mut sig, mut args, mut tx, - eth, command, block, trace, @@ -196,6 +270,8 @@ impl CallArgs { labels, data, with_local_artifacts, + disable_labels, + wallet, .. } = self; @@ -203,8 +279,8 @@ impl CallArgs { sig = Some(data); } - let provider = utils::get_provider(&config)?; - let sender = SenderKind::from_wallet_opts(eth.wallet).await?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let sender = SenderKind::from_wallet_opts(wallet).await?; let from = sender.address(); let code = if let Some(CallSubcommands::Create { @@ -230,7 +306,8 @@ impl CallArgs { .await? .with_code_sig_and_args(code, sig, args) .await? - .build_raw(sender) + .raw() + .build(sender) .await?; if trace { @@ -240,12 +317,23 @@ impl CallArgs { } let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, odyssey) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; + let (mut evm_env, tx_env, fork, chain, networks) = + TracingExecutor::::get_fork_material(&mut config, evm_opts).await?; // modify settings that usually set in eth_call - env.evm_env.cfg_env.disable_block_gas_limit = true; - env.evm_env.block_env.gas_limit = u64::MAX; + evm_env.cfg_env.disable_block_gas_limit = true; + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + evm_env.block_env.set_gas_limit(u64::MAX); + + // Apply the block overrides. + if let Some(block_overrides) = block_overrides { + if let Some(number) = block_overrides.number { + evm_env.block_env.set_number(number.to()); + } + if let Some(time) = block_overrides.time { + evm_env.block_env.set_timestamp(U256::from(time)); + } + } let trace_mode = TraceMode::Call .with_debug(debug) @@ -255,30 +343,54 @@ impl CallArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::new( - env, + let mut executor = TracingExecutor::::new( + (evm_env, tx_env), fork, evm_version, trace_mode, - odyssey, + networks, create2_deployer, + state_overrides, )?; - let value = tx.value.unwrap_or_default(); - let input = tx.inner.input.into_input().unwrap_or_default(); - let tx_kind = tx.inner.to.expect("set by builder"); - let env_tx = &mut executor.env_mut().tx; + let value = tx.value().unwrap_or_default(); + let input = tx.input().cloned().unwrap_or_default(); + let tx_kind = tx.kind().expect("set by builder"); + let env_tx = executor.tx_env_mut(); - if let Some(tx_type) = tx.inner.transaction_type { - env_tx.tx_type = tx_type; + // Set transaction options with --trace + if let Some(gas_limit) = tx.gas_limit() { + env_tx.set_gas_limit(gas_limit); } - if let Some(access_list) = tx.inner.access_list { - env_tx.access_list = access_list; + if let Some(gas_price) = tx.gas_price() { + env_tx.set_gas_price(gas_price); + } - if env_tx.tx_type == TransactionType::Legacy as u8 { - env_tx.tx_type = TransactionType::Eip2930 as u8; - } + if let Some(max_fee_per_gas) = tx.max_fee_per_gas() { + env_tx.set_gas_price(max_fee_per_gas); + } + + if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas() { + env_tx.set_gas_priority_fee(Some(max_priority_fee_per_gas)); + } + + if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() { + env_tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + } + + if let Some(nonce) = tx.nonce() { + env_tx.set_nonce(nonce); + } + + env_tx.set_tx_type(tx.output_tx_type().into()); + + if let Some(access_list) = tx.access_list().cloned() { + env_tx.set_access_list(access_list); + } + + if let Some(auth) = tx.authorization_list().cloned() { + env_tx.set_signed_authorization(auth); } let trace = match tx_kind { @@ -292,29 +404,112 @@ impl CallArgs { ), }; + let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?; handle_traces( trace, &config, chain, + &contracts_bytecode, labels, with_local_artifacts, debug, decode_internal, + disable_labels, + None, ) .await?; return Ok(()); } - sh_println!( - "{}", - Cast::new(provider).call(&tx, func.as_ref(), block, state_overrides).await? - )?; + let response = Cast::new(&provider) + .call(&tx, func.as_ref(), block, state_overrides, block_overrides) + .await?; + + if response == "0x" + && let Some(contract_address) = tx.to() + { + let code = provider.get_code_at(contract_address).await?; + if code.is_empty() { + sh_warn!("Contract code is empty")?; + } + } + sh_println!("{}", response)?; Ok(()) } - /// Parse state overrides from command line arguments - pub fn get_state_overrides(&self) -> eyre::Result { + + /// Handle --curl mode by generating curl command without any RPC interaction. + async fn run_curl(self) -> Result<()> { + let config = self.rpc.load_config()?; + let url = config.get_rpc_url_or_localhost_http()?; + let jwt = config.get_rpc_jwt_secret()?; + + // Get call data - either from --data or from sig + args + let data = if let Some(data) = &self.data { + hex::decode(data)? + } else if let Some(sig) = &self.sig { + // If sig is already hex data, use it directly + if let Ok(data) = hex::decode(sig) { + data + } else { + // Parse function signature and encode args + let func = get_func(sig)?; + encode_function_args(&func, &self.args)? + } + } else { + Vec::new() + }; + + // Resolve the destination address (must be a raw address for curl mode) + let to = self.to.as_ref().map(|n| match n { + NameOrAddress::Address(addr) => Ok(*addr), + NameOrAddress::Name(name) => { + eyre::bail!("ENS names are not supported with --curl. Please use a raw address instead of '{}'", name) + } + }).transpose()?; + + // Build eth_call params + let call_object = serde_json::json!({ + "to": to, + "data": format!("0x{}", hex::encode(&data)), + }); + + let block_param = self + .block + .map(|b| serde_json::to_value(b).unwrap_or(serde_json::json!("latest"))) + .unwrap_or(serde_json::json!("latest")); + + let params = serde_json::json!([call_object, block_param]); + + let curl_cmd = generate_curl_command( + url.as_ref(), + "eth_call", + params, + config.eth_rpc_headers.as_deref(), + jwt.as_deref(), + ); + + sh_println!("{}", curl_cmd)?; + Ok(()) + } + + /// Parse state overrides from command line arguments. + pub fn get_state_overrides(&self) -> eyre::Result> { + // Early return if no override set - + if [ + self.balance_overrides.as_ref(), + self.nonce_overrides.as_ref(), + self.code_overrides.as_ref(), + self.state_overrides.as_ref(), + self.state_diff_overrides.as_ref(), + ] + .iter() + .all(Option::is_none) + { + return Ok(None); + } + let mut state_overrides_builder = StateOverridesBuilder::default(); // Parse balance overrides @@ -338,21 +533,43 @@ impl CallArgs { state_overrides_builder.with_code(addr.parse()?, Bytes::from_str(code_str)?); } - // Parse state overrides - for override_str in self.state_overrides.iter().flatten() { - let (addr, slot, value) = address_slot_value_override(override_str)?; - state_overrides_builder = - state_overrides_builder.with_state(addr, [(slot.into(), value.into())]); + type StateOverrides = HashMap>; + let parse_state_overrides = + |overrides: &Option>| -> Result { + let mut state_overrides: StateOverrides = StateOverrides::default(); + + overrides.iter().flatten().try_for_each(|s| -> Result<(), eyre::Report> { + let (addr, slot, value) = address_slot_value_override(s)?; + state_overrides.entry(addr).or_default().insert(slot.into(), value.into()); + Ok(()) + })?; + + Ok(state_overrides) + }; + + // Parse and apply state overrides + for (addr, entries) in parse_state_overrides(&self.state_overrides)? { + state_overrides_builder = state_overrides_builder.with_state(addr, entries); } - // Parse state diff overrides - for override_str in self.state_diff_overrides.iter().flatten() { - let (addr, slot, value) = address_slot_value_override(override_str)?; - state_overrides_builder = - state_overrides_builder.with_state_diff(addr, [(slot.into(), value.into())]); + // Parse and apply state diff overrides + for (addr, entries) in parse_state_overrides(&self.state_diff_overrides)? { + state_overrides_builder = state_overrides_builder.with_state_diff(addr, entries) } - Ok(state_overrides_builder.build()) + Ok(Some(state_overrides_builder.build())) + } + + /// Parse block overrides from command line arguments. + pub fn get_block_overrides(&self) -> eyre::Result> { + let mut overrides = BlockOverrides::default(); + if let Some(number) = self.block_number { + overrides = overrides.with_number(U256::from(number)); + } + if let Some(time) = self.block_time { + overrides = overrides.with_time(time); + } + if overrides.is_empty() { Ok(None) } else { Ok(Some(overrides)) } } } @@ -364,10 +581,6 @@ impl figment::Provider for CallArgs { fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); - if self.odyssey { - map.insert("odyssey".into(), self.odyssey.into()); - } - if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } @@ -399,7 +612,115 @@ fn address_slot_value_override(address_override: &str) -> Result<(Address, U256, #[cfg(test)] mod tests { use super::*; - use alloy_primitives::hex; + use alloy_primitives::{U64, address, b256, fixed_bytes}; + + #[test] + fn test_get_state_overrides() { + let call_args = CallArgs::parse_from([ + "foundry-cli", + "--override-balance", + "0x0000000000000000000000000000000000000001:2", + "--override-nonce", + "0x0000000000000000000000000000000000000001:3", + "--override-code", + "0x0000000000000000000000000000000000000001:0x04", + "--override-state", + "0x0000000000000000000000000000000000000001:5:6", + "--override-state-diff", + "0x0000000000000000000000000000000000000001:7:8", + ]); + let overrides = call_args.get_state_overrides().unwrap().unwrap(); + let address = address!("0x0000000000000000000000000000000000000001"); + if let Some(account_override) = overrides.get(&address) { + if let Some(balance) = account_override.balance { + assert_eq!(balance, U256::from(2)); + } + if let Some(nonce) = account_override.nonce { + assert_eq!(nonce, 3); + } + if let Some(code) = &account_override.code { + assert_eq!(*code, Bytes::from([0x04])); + } + if let Some(state) = &account_override.state + && let Some(value) = state.get(&b256!( + "0x0000000000000000000000000000000000000000000000000000000000000005" + )) + { + assert_eq!( + *value, + b256!("0x0000000000000000000000000000000000000000000000000000000000000006") + ); + } + if let Some(state_diff) = &account_override.state_diff + && let Some(value) = state_diff.get(&b256!( + "0x0000000000000000000000000000000000000000000000000000000000000007" + )) + { + assert_eq!( + *value, + b256!("0x0000000000000000000000000000000000000000000000000000000000000008") + ); + } + } + } + + #[test] + fn test_get_state_overrides_empty() { + let call_args = CallArgs::parse_from([""]); + let overrides = call_args.get_state_overrides().unwrap(); + assert_eq!(overrides, None); + } + + #[test] + fn test_get_block_overrides() { + let mut call_args = CallArgs::parse_from([""]); + call_args.block_number = Some(1); + call_args.block_time = Some(2); + let overrides = call_args.get_block_overrides().unwrap().unwrap(); + assert_eq!(overrides.number, Some(U256::from(1))); + assert_eq!(overrides.time, Some(2)); + } + + #[test] + fn test_get_block_overrides_empty() { + let call_args = CallArgs::parse_from([""]); + let overrides = call_args.get_block_overrides().unwrap(); + assert_eq!(overrides, None); + } + + #[test] + fn test_address_value_override_success() { + let text = "0x0000000000000000000000000000000000000001:2"; + let (address, value) = address_value_override(text).unwrap(); + assert_eq!(address, "0x0000000000000000000000000000000000000001"); + assert_eq!(value, "2"); + } + + #[test] + fn test_address_value_override_error() { + let text = "invalid_value"; + let error = address_value_override(text).unwrap_err(); + assert_eq!(error.to_string(), "Invalid override invalid_value. Expected
:"); + } + + #[test] + fn test_address_slot_value_override_success() { + let text = "0x0000000000000000000000000000000000000001:2:3"; + let (address, slot, value) = address_slot_value_override(text).unwrap(); + assert_eq!(*address, fixed_bytes!("0x0000000000000000000000000000000000000001")); + assert_eq!(slot, U256::from(2)); + assert_eq!(value, U256::from(3)); + } + + #[test] + fn test_address_slot_value_override_error() { + let text = "invalid_value"; + let error = address_slot_value_override(text).unwrap_err(); + assert_eq!( + error.to_string(), + "Invalid override invalid_value. Expected
::" + ); + } #[test] fn can_parse_call_data() { @@ -468,4 +789,53 @@ mod tests { Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()]) ); } + + #[test] + fn test_negative_args_with_flags() { + // Test that negative args work with flags + let args = CallArgs::parse_from([ + "foundry-cli", + "--trace", + "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", + "process(int256)", + "-999999", + "--debug", + ]); + + assert!(args.trace); + assert!(args.debug); + assert_eq!(args.args, vec!["-999999"]); + } + + #[test] + fn test_transaction_opts_with_trace() { + // Test that transaction options are correctly parsed when using --trace + let args = CallArgs::parse_from([ + "foundry-cli", + "--trace", + "--gas-limit", + "1000000", + "--gas-price", + "20000000000", + "--priority-gas-price", + "2000000000", + "--nonce", + "42", + "--value", + "1000000000000000000", // 1 ETH + "--blob-gas-price", + "10000000000", + "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", + "balanceOf(address)", + "0x123456789abcdef123456789abcdef123456789a", + ]); + + assert!(args.trace); + assert_eq!(args.tx.gas_limit, Some(U256::from(1000000u32))); + assert_eq!(args.tx.gas_price, Some(U256::from(20000000000u64))); + assert_eq!(args.tx.priority_gas_price, Some(U256::from(2000000000u64))); + assert_eq!(args.tx.nonce, Some(U64::from(42))); + assert_eq!(args.tx.value, Some(U256::from(1000000000000000000u64))); + assert_eq!(args.tx.blob_gas_price, Some(U256::from(10000000000u64))); + } } diff --git a/crates/cast/src/cmd/constructor_args.rs b/crates/cast/src/cmd/constructor_args.rs index 3d60673857f37..fcf872f36ff27 100644 --- a/crates/cast/src/cmd/constructor_args.rs +++ b/crates/cast/src/cmd/constructor_args.rs @@ -1,16 +1,16 @@ -use super::{ - creation_code::fetch_creation_code_from_etherscan, - interface::{fetch_abi_from_etherscan, load_abi_from_file}, -}; +use super::{creation_code::fetch_creation_code_from_etherscan, interface::load_abi_from_file}; use alloy_dyn_abi::DynSolType; use alloy_primitives::{Address, Bytes}; use alloy_provider::Provider; -use clap::{command, Parser}; -use eyre::{eyre, OptionExt, Result}; +use clap::Parser; +use eyre::{OptionExt, Result, eyre}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, fetch_abi_from_etherscan}, }; +use foundry_config::Config; + +foundry_config::impl_figment_convert!(ConstructorArgsArgs, etherscan, rpc); /// CLI arguments for `cast creation-args`. #[derive(Parser)] @@ -32,16 +32,16 @@ pub struct ConstructorArgsArgs { impl ConstructorArgsArgs { pub async fn run(self) -> Result<()> { - let Self { contract, mut etherscan, rpc, abi_path } = self; + let mut config = self.load_config()?; + + let Self { contract, abi_path, etherscan: _, rpc: _ } = self; - let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let chain = provider.get_chain_id().await?; - etherscan.chain = Some(chain.into()); + config.chain = Some(provider.get_chain_id().await?.into()); - let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; - let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; + let args_arr = parse_constructor_args(bytecode, contract, &config, abi_path).await?; for arg in args_arr { let _ = sh_println!("{arg}"); } @@ -54,13 +54,13 @@ impl ConstructorArgsArgs { async fn parse_constructor_args( bytecode: Bytes, contract: Address, - etherscan: &EtherscanOpts, + config: &Config, abi_path: Option, ) -> Result> { let abi = if let Some(abi_path) = abi_path { load_abi_from_file(&abi_path, None)? } else { - fetch_abi_from_etherscan(contract, etherscan).await? + fetch_abi_from_etherscan(contract, config).await? }; let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; @@ -73,21 +73,27 @@ async fn parse_constructor_args( } let args_size = constructor.inputs.len() * 32; + if bytecode.len() < args_size { + return Err(eyre!( + "Invalid creation bytecode length: have {} bytes, need at least {} for {} constructor inputs", + bytecode.len(), + args_size, + constructor.inputs.len() + )); + } let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); let display_args: Vec = args_bytes .chunks(32) .enumerate() - .map(|(i, arg)| { - format_arg(&constructor.inputs[i].ty, arg).expect("Failed to format argument.") - }) - .collect(); + .map(|(i, arg)| format_arg(&constructor.inputs[i].ty, arg)) + .collect::>>()?; Ok(display_args) } fn format_arg(ty: &str, arg: &[u8]) -> Result { - let arg_type: DynSolType = ty.parse().expect("Invalid ABI type."); + let arg_type: DynSolType = ty.parse()?; let decoded = arg_type.abi_decode(arg)?; let bytes = Bytes::from(arg.to_vec()); diff --git a/crates/cast/src/cmd/create2.rs b/crates/cast/src/cmd/create2.rs index 3cab2caf0c8c9..056dbd73fcc83 100644 --- a/crates/cast/src/cmd/create2.rs +++ b/crates/cast/src/cmd/create2.rs @@ -1,15 +1,9 @@ -use alloy_primitives::{hex, keccak256, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, hex, keccak256}; use clap::Parser; use eyre::{Result, WrapErr}; -use rand::{rngs::StdRng, RngCore, SeedableRng}; +use rand::{RngCore, SeedableRng, rngs::StdRng}; use regex::RegexSetBuilder; -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Instant, -}; +use std::time::Instant; // https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; @@ -154,7 +148,7 @@ impl Create2Args { if let Some(suffix) = ends_with { regexs.push(format!( r"{}$", - get_regex_hex_string(suffix).wrap_err("invalid prefix hex provided")? + get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")? )) } @@ -165,9 +159,9 @@ impl Create2Args { let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?; - let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); - if let Some(threads) = threads { - n_threads = n_threads.min(threads); + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); } if cfg!(test) { n_threads = n_threads.min(2); @@ -195,59 +189,27 @@ impl Create2Args { sh_println!( "Starting to generate deterministic contract address with {n_threads} threads..." )?; - let mut handles = Vec::with_capacity(n_threads); - let found = Arc::new(AtomicBool::new(false)); let timer = Instant::now(); - - // Loops through all possible salts in parallel until a result is found. - // Each thread iterates over `(i..).step_by(n_threads)`. - for i in 0..n_threads { - // Create local copies for the thread. - let increment = n_threads; - let regex = regex.clone(); - let regex_len = regex.patterns().len(); - let found = Arc::clone(&found); - handles.push(std::thread::spawn(move || { - // Read the first bytes of the salt as a usize to be able to increment it. - struct B256Aligned(B256, [usize; 0]); - let mut salt = B256Aligned(salt, []); - // SAFETY: B256 is aligned to `usize`. - let salt_word = unsafe { - &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() - }; - // Important: add the thread index to the salt to avoid duplicate results. - *salt_word = salt_word.wrapping_add(i); - - let mut checksum = [0; 42]; - loop { - // Stop if a result was found in another thread. - if found.load(Ordering::Relaxed) { - break None; - } - - // Calculate the `CREATE2` address. - #[expect(clippy::needless_borrows_for_generic_args)] - let addr = deployer.create2(&salt.0, &init_code_hash); - - // Check if the regex matches the calculated address' checksum. - let _ = addr.to_checksum_raw(&mut checksum, None); - // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string - // is safe. - let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) }; - if regex.matches(s).into_iter().count() == regex_len { - // Notify other threads that we found a result. - found.store(true, Ordering::Relaxed); - break Some((addr, salt.0)); - } - - // Increment the salt for the next iteration. - *salt_word = salt_word.wrapping_add(increment); - } - })); - } - - let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); - let (address, salt) = results.into_iter().next().unwrap(); + let regex_len = regex.patterns().len(); + let mut checksum_buf = [0u8; 42]; + let mut hex_buf = [0u8; 40]; + let (address, salt) = super::miner::mine_salt(salt, n_threads, move |salt| { + #[expect(clippy::needless_borrows_for_generic_args)] + let addr = deployer.create2(&salt, &init_code_hash); + // Use checksum format only when case_sensitive is enabled — it requires an extra + // keccak256 call, so we fall back to plain hex when case sensitivity is off. + let s = if case_sensitive { + let _ = addr.to_checksum_raw(&mut checksum_buf, None); + // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string. + unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) } + } else { + // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits). + let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf); + unsafe { std::str::from_utf8_unchecked(&hex_buf) } + }; + (regex.matches(s).into_iter().count() == regex_len).then_some((addr, salt)) + }) + .ok_or_else(|| eyre::eyre!("create2 salt mining failed: all threads panicked"))?; sh_println!("Successfully found contract address in {:?}", timer.elapsed())?; sh_println!("Address: {address}")?; sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?; diff --git a/crates/cast/src/cmd/creation_code.rs b/crates/cast/src/cmd/creation_code.rs index db47b1bdec523..e24106509c41d 100644 --- a/crates/cast/src/cmd/creation_code.rs +++ b/crates/cast/src/cmd/creation_code.rs @@ -1,17 +1,19 @@ -use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; +use super::interface::load_abi_from_file; use crate::SimpleCast; use alloy_consensus::Transaction; +use alloy_network::AnyNetwork; use alloy_primitives::{Address, Bytes}; -use alloy_provider::{ext::TraceApi, Provider}; +use alloy_provider::{Provider, RootProvider, ext::TraceApi}; use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; -use clap::{command, Parser}; -use eyre::{eyre, OptionExt, Result}; -use foundry_block_explorers::Client; +use clap::Parser; +use eyre::{OptionExt, Result, eyre}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{self, LoadConfig}, + utils::{self, LoadConfig, fetch_abi_from_etherscan}, }; -use foundry_common::provider::RetryProvider; +use foundry_config::Config; + +foundry_config::impl_figment_convert!(CreationCodeArgs, etherscan, rpc); /// CLI arguments for `cast creation-code`. #[derive(Parser)] @@ -45,20 +47,21 @@ pub struct CreationCodeArgs { impl CreationCodeArgs { pub async fn run(self) -> Result<()> { - let Self { contract, mut etherscan, rpc, disassemble, without_args, only_args, abi_path } = + let mut config = self.load_config()?; + + let Self { contract, disassemble, without_args, only_args, abi_path, etherscan: _, rpc: _ } = self; - let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let chain = provider.get_chain_id().await?; - etherscan.chain = Some(chain.into()); + config.chain = Some(chain.into()); - let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; let bytecode = parse_code_output( bytecode, contract, - ðerscan, + &config, abi_path.as_deref(), without_args, only_args, @@ -82,7 +85,7 @@ impl CreationCodeArgs { pub async fn parse_code_output( bytecode: Bytes, contract: Address, - etherscan: &EtherscanOpts, + config: &Config, abi_path: Option<&str>, without_args: bool, only_args: bool, @@ -94,7 +97,7 @@ pub async fn parse_code_output( let abi = if let Some(abi_path) = abi_path { load_abi_from_file(abi_path, None)? } else { - fetch_abi_from_etherscan(contract, etherscan).await? + fetch_abi_from_etherscan(contract, config).await? }; let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; @@ -131,14 +134,14 @@ pub async fn parse_code_output( /// Fetches the creation code of a contract from Etherscan and RPC. pub async fn fetch_creation_code_from_etherscan( contract: Address, - etherscan: &EtherscanOpts, - provider: RetryProvider, + config: &Config, + provider: RootProvider, ) -> Result { - let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); - let api_version = config.get_etherscan_api_version(Some(chain)); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new_with_api_version(chain, api_key, api_version)?; + let client = config + .get_etherscan_config_with_chain(Some(chain))? + .ok_or_else(|| eyre!("No Etherscan API key configured for chain {chain}"))? + .into_client_with_no_proxy(config.eth_rpc_no_proxy)?; let creation_data = client.contract_creation_data(contract).await?; let creation_tx_hash = creation_data.transaction_hash; let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; @@ -157,13 +160,13 @@ pub async fn fetch_creation_code_from_etherscan( })?; for trace in traces { - if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result { - if address == contract { - creation_bytecode = match trace.trace.action { - Action::Create(CreateAction { init, .. }) => Some(init), - _ => None, - }; - } + if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result + && address == contract + { + creation_bytecode = match trace.trace.action { + Action::Create(CreateAction { init, .. }) => Some(init), + _ => None, + }; } } diff --git a/crates/cast/src/cmd/da_estimate.rs b/crates/cast/src/cmd/da_estimate.rs index f4dfe98fe1d8f..23197b6ee7a56 100644 --- a/crates/cast/src/cmd/da_estimate.rs +++ b/crates/cast/src/cmd/da_estimate.rs @@ -1,15 +1,16 @@ //! Estimates the data availability size of a block for opstack. use alloy_consensus::BlockHeader; -use alloy_network::eip2718::Encodable2718; +use alloy_network::{AnyNetwork, BlockResponse, Ethereum, Network, eip2718::Encodable2718}; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; -use foundry_cli::{ - opts::RpcOpts, - utils::{self, LoadConfig}, -}; -use op_alloy_consensus::OpTxEnvelope; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::provider::ProviderBuilder; +use foundry_config::Config; +use foundry_evm_networks::NetworkVariant; +use op_alloy_network::Optimism; /// CLI arguments for `cast da-estimate`. #[derive(Debug, Parser)] @@ -18,31 +19,46 @@ pub struct DAEstimateArgs { pub block: BlockId, #[command(flatten)] pub rpc: RpcOpts, + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, } impl DAEstimateArgs { /// Load the RPC URL from the config file. - pub async fn run(self) -> eyre::Result<()> { - let Self { block, rpc } = self; + pub async fn run(self) -> Result<()> { + let Self { block, rpc, network } = self; let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; - let block = provider - .get_block(block) - .full() - .await? - .ok_or_else(|| eyre::eyre!("Block not found"))?; - - let block_number = block.header.number(); - let tx_count = block.transactions.len(); - let mut da_estimate = 0; - for tx in block.into_transactions_iter() { - // try to convert into opstack transaction - let tx = OpTxEnvelope::try_from(tx)?; - da_estimate += op_alloy_flz::tx_estimated_size_fjord(&tx.encoded_2718()); + let network = match network { + Some(n) => n, + None => { + let provider = ProviderBuilder::::from_config(&config)?.build()?; + provider.get_chain_id().await?.into() + } + }; + match network { + NetworkVariant::Optimism => da_estimate::(&config, block).await, + NetworkVariant::Ethereum => da_estimate::(&config, block).await, + NetworkVariant::Tempo => Err(eyre::eyre!( + "DA estimation is not supported for Tempo: EIP-4844 blob transactions are not available on this network" + )), } + } +} - sh_println!("Estimated data availability size for block {block_number} with {tx_count} transactions: {da_estimate}")?; +pub async fn da_estimate(config: &Config, block_id: BlockId) -> Result<()> { + let provider = ProviderBuilder::::from_config(config)?.build()?; + let block = + provider.get_block(block_id).full().await?.ok_or_else(|| eyre::eyre!("Block not found"))?; - Ok(()) + let block_number = block.header().number(); + let tx_count = block.transactions().len(); + let mut da_estimate = 0; + for tx in block.transactions().txns() { + da_estimate += op_alloy_flz::tx_estimated_size_fjord(&tx.as_ref().encoded_2718()); } + sh_println!( + "Estimated data availability size for block {block_number} with {tx_count} transactions: {da_estimate}" + )?; + Ok(()) } diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs new file mode 100644 index 0000000000000..27e33ec03e7e7 --- /dev/null +++ b/crates/cast/src/cmd/erc20.rs @@ -0,0 +1,608 @@ +use std::{str::FromStr, time::Duration}; + +use crate::{ + cmd::send::{cast_send, cast_send_with_access_key}, + format_uint_exp, + tx::{CastTxSender, SendTxOpts, TxParams}, +}; +use alloy_consensus::{SignableTransaction, Signed}; +use alloy_eips::BlockId; +use alloy_ens::NameOrAddress; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_provider::{Provider, fillers::RecommendedFillers}; +use alloy_signer::Signature; +use alloy_sol_types::sol; +use clap::Parser; +use foundry_cli::{ + opts::RpcOpts, + utils::{LoadConfig, get_chain, get_provider}, +}; +use foundry_common::{ + FoundryTransactionBuilder, + fmt::{UIfmt, UIfmtReceiptExt}, + provider::{ProviderBuilder, RetryProviderWithSigner}, + shell, + tempo::TEMPO_BROWSER_GAS_BUFFER, +}; +#[doc(hidden)] +pub use foundry_config::{Chain, utils::*}; +use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::PATH_USD_ADDRESS; + +sol! { + #[sol(rpc)] + interface IERC20 { + #[derive(Debug)] + function name() external view returns (string); + function symbol() external view returns (string); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); + function balanceOf(address owner) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function mint(address to, uint256 amount) external; + function burn(uint256 amount) external; + } +} + +/// Creates a provider with a pre-resolved signer. +pub(crate) fn build_provider_with_signer( + tx_opts: &SendTxOpts, + signer: WalletSigner, +) -> eyre::Result> +where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, +{ + let config = tx_opts.eth.load_config()?; + let wallet = EthereumWallet::from(signer); + let provider = ProviderBuilder::::from_config(&config)?.build_with_wallet(wallet)?; + if let Some(interval) = tx_opts.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)) + } + Ok(provider) +} + +/// Interact with ERC20 tokens. +#[derive(Debug, Parser, Clone)] +pub enum Erc20Subcommand { + /// Query ERC20 token balance. + #[command(visible_alias = "b")] + Balance { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The owner to query balance for. + #[arg(value_parser = NameOrAddress::from_str)] + owner: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Transfer ERC20 tokens. + #[command(visible_aliases = ["t", "send"])] + Transfer { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The recipient address. + #[arg(value_parser = NameOrAddress::from_str)] + to: NameOrAddress, + + /// The amount to transfer. + amount: String, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, + + /// Approve ERC20 token spending. + #[command(visible_alias = "a")] + Approve { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The spender address. + #[arg(value_parser = NameOrAddress::from_str)] + spender: NameOrAddress, + + /// The amount to approve. + amount: String, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, + + /// Query ERC20 token allowance. + #[command(visible_alias = "al")] + Allowance { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The owner address. + #[arg(value_parser = NameOrAddress::from_str)] + owner: NameOrAddress, + + /// The spender address. + #[arg(value_parser = NameOrAddress::from_str)] + spender: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token name. + #[command(visible_alias = "n")] + Name { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token symbol. + #[command(visible_alias = "s")] + Symbol { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token decimals. + #[command(visible_alias = "d")] + Decimals { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token total supply. + #[command(visible_alias = "ts")] + TotalSupply { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Mint ERC20 tokens (if the token supports minting). + #[command(visible_alias = "m")] + Mint { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The recipient address. + #[arg(value_parser = NameOrAddress::from_str)] + to: NameOrAddress, + + /// The amount to mint. + amount: String, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, + + /// Burn ERC20 tokens. + #[command(visible_alias = "bu")] + Burn { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The amount to burn. + amount: String, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, +} + +impl Erc20Subcommand { + const fn rpc_opts(&self) -> &RpcOpts { + match self { + Self::Allowance { rpc, .. } => rpc, + Self::Approve { send_tx, .. } => &send_tx.eth.rpc, + Self::Balance { rpc, .. } => rpc, + Self::Transfer { send_tx, .. } => &send_tx.eth.rpc, + Self::Name { rpc, .. } => rpc, + Self::Symbol { rpc, .. } => rpc, + Self::Decimals { rpc, .. } => rpc, + Self::TotalSupply { rpc, .. } => rpc, + Self::Mint { send_tx, .. } => &send_tx.eth.rpc, + Self::Burn { send_tx, .. } => &send_tx.eth.rpc, + } + } + + const fn erc20_opts(&self) -> Option<&TxParams> { + match self { + Self::Approve { tx, .. } + | Self::Transfer { tx, .. } + | Self::Mint { tx, .. } + | Self::Burn { tx, .. } => Some(tx), + Self::Allowance { .. } + | Self::Balance { .. } + | Self::Name { .. } + | Self::Symbol { .. } + | Self::Decimals { .. } + | Self::TotalSupply { .. } => None, + } + } + + const fn uses_browser_send(&self) -> bool { + match self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => send_tx.browser.browser, + _ => false, + } + } + + async fn should_use_tempo_network( + &self, + tempo_access_key: &Option, + ) -> eyre::Result { + if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo()) + || tempo_access_key.is_some() + { + return Ok(true); + } + + if self.uses_browser_send() { + let config = self.rpc_opts().load_config()?; + return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo()); + } + + Ok(false) + } + + pub async fn run(self) -> eyre::Result<()> { + // Resolve the signer once for state-changing variants. + let (signer, tempo_access_key) = match &self { + Self::Transfer { send_tx, .. } + | Self::Approve { send_tx, .. } + | Self::Mint { send_tx, .. } + | Self::Burn { send_tx, .. } => { + // Only attempt Tempo lookup if --from is set (avoids unnecessary I/O). + if send_tx.eth.wallet.from.is_some() { + let (s, ak) = send_tx.eth.wallet.maybe_signer().await?; + (s, ak) + } else { + (None, None) + } + } + _ => (None, None), + }; + + let is_tempo = self.should_use_tempo_network(&tempo_access_key).await?; + + if is_tempo { + self.run_generic::(signer, tempo_access_key).await + } else { + self.run_generic::(signer, None).await + } + } + + pub async fn run_generic( + self, + pre_resolved_signer: Option, + tempo_keychain: Option, + ) -> eyre::Result<()> + where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, + { + let config = self.rpc_opts().load_config()?; + + // Macro to DRY the keychain-vs-normal send pattern for state-changing ops. + // The only thing that varies per variant is the IERC20 call expression. + macro_rules! erc20_send { + ( + $token:expr, + $send_tx:expr, + $tx_opts:expr, | + $erc20:ident, + $provider:ident | + $build_tx:expr + ) => {{ + let timeout = $send_tx.timeout.unwrap_or(config.transaction_timeout); + if let Some(ref access_key) = tempo_keychain { + let signer = pre_resolved_signer + .as_ref() + .ok_or_else(|| eyre::eyre!("signer required for access key"))?; + let $provider = + ProviderBuilder::::from_config(&config)?.build()?; + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + $tx_opts.apply::( + &mut tx, + get_chain(config.chain, &$provider).await?.is_legacy(), + ); + cast_send_with_access_key( + &$provider, + tx, + signer, + access_key, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? + } else if let Some(browser) = $send_tx.browser.run::().await? { + let $provider = ProviderBuilder::::from_config(&config)?.build()?; + if let Some(interval) = $send_tx.poll_interval { + $provider.client().set_poll_interval(Duration::from_secs(interval)); + } + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + let chain = get_chain(config.chain, &$provider).await?; + $tx_opts.apply::(&mut tx, chain.is_legacy()); + if chain.is_tempo() && tx.fee_token().is_none() { + tx.set_fee_token(PATH_USD_ADDRESS); + } + fill_tx(&$provider, &mut tx, browser.address(), chain).await?; + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&$provider) + .print_tx_result( + tx_hash, + $send_tx.cast_async, + $send_tx.confirmations, + timeout, + ) + .await? + } else { + let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?); + let $provider = build_provider_with_signer::(&$send_tx, signer)?; + let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider); + let mut tx = { $build_tx }.into_transaction_request(); + $tx_opts.apply::( + &mut tx, + get_chain(config.chain, &$provider).await?.is_legacy(), + ); + cast_send( + $provider, + tx, + $send_tx.cast_async, + $send_tx.sync, + $send_tx.confirmations, + timeout, + ) + .await? + } + }}; + } + + match self { + // Read-only + Self::Allowance { token, owner, spender, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + let owner = owner.resolve(&provider).await?; + let spender = spender.resolve(&provider).await?; + + let allowance = IERC20::new(token, &provider) + .allowance(owner, spender) + .block(block.unwrap_or_default()) + .call() + .await?; + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&allowance.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(allowance))? + } + } + Self::Balance { token, owner, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + let owner = owner.resolve(&provider).await?; + + let balance = IERC20::new(token, &provider) + .balanceOf(owner) + .block(block.unwrap_or_default()) + .call() + .await?; + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&balance.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(balance))? + } + } + Self::Name { token, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + + let name = IERC20::new(token, &provider) + .name() + .block(block.unwrap_or_default()) + .call() + .await?; + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&name)?)? + } else { + sh_println!("{}", name)? + } + } + Self::Symbol { token, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + + let symbol = IERC20::new(token, &provider) + .symbol() + .block(block.unwrap_or_default()) + .call() + .await?; + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&symbol)?)? + } else { + sh_println!("{}", symbol)? + } + } + Self::Decimals { token, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + + let decimals = IERC20::new(token, &provider) + .decimals() + .block(block.unwrap_or_default()) + .call() + .await?; + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&decimals)?)? + } else { + sh_println!("{}", decimals)? + } + } + Self::TotalSupply { token, block, .. } => { + let provider = get_provider(&config)?; + let token = token.resolve(&provider).await?; + + let total_supply = IERC20::new(token, &provider) + .totalSupply() + .block(block.unwrap_or_default()) + .call() + .await?; + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&total_supply.to_string())?)? + } else { + sh_println!("{}", format_uint_exp(total_supply))? + } + } + // State-changing + Self::Transfer { token, to, amount, send_tx, tx: tx_opts, .. } => { + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.transfer(to.resolve(&provider).await?, U256::from_str(&amount)?) + }) + } + Self::Approve { token, spender, amount, send_tx, tx: tx_opts, .. } => { + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.approve(spender.resolve(&provider).await?, U256::from_str(&amount)?) + }) + } + Self::Mint { token, to, amount, send_tx, tx: tx_opts, .. } => { + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.mint(to.resolve(&provider).await?, U256::from_str(&amount)?) + }) + } + Self::Burn { token, amount, send_tx, tx: tx_opts, .. } => { + erc20_send!(token, send_tx, tx_opts, |erc20, provider| { + erc20.burn(U256::from_str(&amount)?) + }) + } + }; + Ok(()) + } +} + +/// Fills from, chain_id, nonce, fees, and gas limit on a transaction request for the browser +/// wallet path. Mirrors the filling logic in the shared tx builder but operates on a +/// pre-built transaction request from the sol! macro rather than through the builder pipeline. +/// Only fills fields that haven't already been set by the user. +async fn fill_tx>( + provider: &P, + tx: &mut N::TransactionRequest, + from: Address, + chain: Chain, +) -> eyre::Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + tx.set_from(from); + tx.set_chain_id(chain.id()); + + if tx.nonce().is_none() { + tx.set_nonce(provider.get_transaction_count(from).await?); + } + + let legacy = chain.is_legacy(); + + if legacy { + if tx.gas_price().is_none() { + tx.set_gas_price(provider.get_gas_price().await?); + } + } else if tx.max_fee_per_gas().is_none() || tx.max_priority_fee_per_gas().is_none() { + let estimate = provider.estimate_eip1559_fees().await?; + if tx.max_fee_per_gas().is_none() { + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } + if tx.max_priority_fee_per_gas().is_none() { + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + } + + if tx.gas_limit().is_none() { + let mut estimated = provider.estimate_gas(tx.clone()).await?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if chain.is_tempo() { + estimated += TEMPO_BROWSER_GAS_BUFFER; + } + + tx.set_gas_limit(estimated); + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/estimate.rs b/crates/cast/src/cmd/estimate.rs index 51368b6d0a538..405266b60457a 100644 --- a/crates/cast/src/cmd/estimate.rs +++ b/crates/cast/src/cmd/estimate.rs @@ -1,15 +1,19 @@ use crate::tx::{CastTxBuilder, SenderKind}; use alloy_ens::NameOrAddress; +use alloy_network::{Ethereum, Network}; use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; use foundry_cli::{ - opts::{EthereumOpts, TransactionOpts}, - utils::{self, parse_ether_value, LoadConfig}, + opts::{RpcOpts, TransactionOpts}, + utils::{LoadConfig, parse_ether_value}, }; +use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; +use foundry_wallets::WalletOpts; use std::str::FromStr; +use tempo_alloy::TempoNetwork; /// CLI arguments for `cast estimate`. #[derive(Debug, Parser)] @@ -22,6 +26,7 @@ pub struct EstimateArgs { sig: Option, /// The arguments of the function to call. + #[arg(allow_negative_numbers = true)] args: Vec, /// The block height to query at. @@ -30,6 +35,15 @@ pub struct EstimateArgs { #[arg(long, short = 'B')] block: Option, + /// Calculate the cost of a transaction using the network gas price. + /// + /// If not specified the amount of gas will be estimated. + #[arg(long)] + cost: bool, + + #[command(flatten)] + wallet: WalletOpts, + #[command(subcommand)] command: Option, @@ -37,7 +51,7 @@ pub struct EstimateArgs { tx: TransactionOpts, #[command(flatten)] - eth: EthereumOpts, + rpc: RpcOpts, } #[derive(Debug, Parser)] @@ -52,6 +66,7 @@ pub enum EstimateSubcommands { sig: Option, /// Constructor arguments + #[arg(allow_negative_numbers = true)] args: Vec, /// Ether to send in the transaction @@ -66,11 +81,22 @@ pub enum EstimateSubcommands { impl EstimateArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, mut tx, block, eth, command } = self; + if self.tx.tempo.is_tempo() { + self.run_with_network::().await + } else { + self.run_with_network::().await + } + } - let config = eth.load_config()?; - let provider = utils::get_provider(&config)?; - let sender = SenderKind::from_wallet_opts(eth.wallet).await?; + pub async fn run_with_network(self) -> Result<()> + where + N::TransactionRequest: FoundryTransactionBuilder, + { + let Self { to, mut sig, mut args, mut tx, block, cost, wallet, rpc, command } = self; + + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let sender = SenderKind::from_wallet_opts(wallet).await?; let code = if let Some(EstimateSubcommands::Create { code, @@ -95,11 +121,19 @@ impl EstimateArgs { .await? .with_code_sig_and_args(code, sig, args) .await? - .build_raw(sender) + .raw() + .build(sender) .await?; let gas = provider.estimate_gas(tx).block(block.unwrap_or_default()).await?; - sh_println!("{gas}")?; + if cost { + let gas_price_wei = provider.get_gas_price().await?; + let cost = gas_price_wei * gas as u128; + let cost_eth = cost as f64 / 1e18; + sh_println!("{cost_eth}")?; + } else { + sh_println!("{gas}")?; + } Ok(()) } } diff --git a/crates/cast/src/cmd/interface.rs b/crates/cast/src/cmd/interface.rs index 992f5b833eac3..2e2051822dafc 100644 --- a/crates/cast/src/cmd/interface.rs +++ b/crates/cast/src/cmd/interface.rs @@ -1,12 +1,16 @@ -use alloy_json_abi::{ContractObject, JsonAbi}; +use alloy_json_abi::{ContractObject, JsonAbi, ToSolConfig}; use alloy_primitives::Address; use clap::Parser; use eyre::{Context, Result}; -use foundry_block_explorers::Client; -use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; +use forge_fmt::FormatterConfig; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{LoadConfig, fetch_abi_from_etherscan}, +}; use foundry_common::{ + ContractsByArtifact, compile::{PathOrContractInfo, ProjectCompiler}, - find_target_path, fs, shell, ContractsByArtifact, + find_target_path, fs, shell, }; use foundry_config::load_config; use itertools::Itertools; @@ -46,17 +50,23 @@ pub struct InterfaceArgs { )] output: Option, + /// If set, generate all types in a single interface, inlining any inherited or library types. + /// + /// This can fail if there are structs with the same name in different interfaces. + #[arg(long)] + flatten: bool, + #[command(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { - let Self { contract, name, pragma, output: output_location, etherscan } = self; + let Self { contract, name, pragma, output: output_location, flatten, etherscan } = self; // Determine if the target contract is an ABI file, a local contract or an Ethereum address. - let abis = if Path::new(&contract).is_file() && - fs::read_to_string(&contract) + let abis = if Path::new(&contract).is_file() + && fs::read_to_string(&contract) .ok() .and_then(|content| serde_json::from_str::(&content).ok()) .is_some() @@ -64,13 +74,16 @@ impl InterfaceArgs { load_abi_from_file(&contract, name)? } else { match Address::from_str(&contract) { - Ok(address) => fetch_abi_from_etherscan(address, ðerscan).await?, + Ok(address) => fetch_abi_from_etherscan(address, ðerscan.load_config()?).await?, Err(_) => load_abi_from_artifact(&contract)?, } }; + // Build config for to_sol conversion. + let config = flatten.then(|| ToSolConfig::new().one_contract(true)); + // Retrieve interfaces from the array of ABIs. - let interfaces = get_interfaces(abis)?; + let interfaces = get_interfaces(abis, config)?; // Print result or write to file. let res = if shell::is_json() { @@ -136,32 +149,27 @@ fn load_abi_from_artifact(path_or_contract: &str) -> Result Result> { - let config = etherscan.load_config()?; - let chain = config.chain.unwrap_or_default(); - let api_version = config.get_etherscan_api_version(Some(chain)); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new_with_api_version(chain, api_key, api_version)?; - let source = client.contract_source_code(address).await?; - source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect() -} - /// Converts a vector of tuples containing the ABI and contract name into a vector of /// `InterfaceSource` objects. -fn get_interfaces(abis: Vec<(JsonAbi, String)>) -> Result> { +fn get_interfaces( + abis: Vec<(JsonAbi, String)>, + config: Option, +) -> Result> { abis.into_iter() .map(|(contract_abi, name)| { - let source = match foundry_cli::utils::abi_to_solidity(&contract_abi, &name) { + let source = match forge_fmt::format( + &contract_abi.to_sol(&name, config.clone()), + FormatterConfig::default(), + ) + .into_result() + { Ok(generated_source) => generated_source, Err(e) => { sh_warn!("Failed to format interface for {name}: {e}")?; - contract_abi.to_sol(&name, None) + contract_abi.to_sol(&name, config.clone()) } }; + Ok(InterfaceSource { json_abi: serde_json::to_string_pretty(&contract_abi)?, source }) }) .collect() diff --git a/crates/cast/src/cmd/keychain.rs b/crates/cast/src/cmd/keychain.rs new file mode 100644 index 0000000000000..897a01c39202a --- /dev/null +++ b/crates/cast/src/cmd/keychain.rs @@ -0,0 +1,1938 @@ +use alloy_ens::NameOrAddress; +use std::time::Duration; + +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, U256, hex, keccak256}; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_signer::Signer; +use alloy_sol_types::SolCall; +use alloy_transport::TransportError; +use chrono::DateTime; +use clap::Parser; +use eyre::Result; +use foundry_cli::{ + opts::{RpcOpts, TransactionOpts}, + utils::LoadConfig, +}; +use foundry_common::{ + FoundryTransactionBuilder, + provider::ProviderBuilder, + sh_warn, shell, + tempo::{ + self, KeyType, KeysFile, TEMPO_BROWSER_GAS_BUFFER, WalletType, read_tempo_keys_file, + tempo_keys_path, + }, +}; +use foundry_evm::hardfork::TempoHardfork; +use serde::Deserialize; +use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; +use tempo_contracts::precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, IAccountKeychain, + IAccountKeychain::{ + CallScope, KeyInfo, KeyRestrictions, LegacyTokenLimit, SelectorRule, SignatureType, + TokenLimit, + }, + ITIP20, PATH_USD_ADDRESS, + account_keychain::{authorizeKeyCall, legacyAuthorizeKeyCall}, +}; +use yansi::Paint; + +use foundry_cli::utils::{maybe_print_resolved_lane, resolve_lane}; + +use crate::{ + cmd::send::cast_send, + tx::{CastTxBuilder, CastTxSender, SendTxOpts}, +}; + +/// Tempo keychain management commands. +/// +/// Manage access keys stored in `~/.tempo/wallet/keys.toml` and query or modify +/// on-chain key state via the AccountKeychain precompile. +#[derive(Debug, Parser)] +pub enum KeychainSubcommand { + /// List all keys from the local keys.toml file. + #[command(visible_alias = "ls")] + List, + + /// Show all keys for a specific wallet address from the local keys.toml file. + Show { + /// The wallet address to look up. + wallet_address: Address, + }, + + /// Check on-chain provisioning status of a key via the AccountKeychain precompile. + #[command(visible_alias = "info")] + Check { + /// The wallet (account) address. + wallet_address: Address, + + /// The key address to check. + key_address: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Inspect an access key policy using the local key registry and on-chain state. + Inspect { + /// The key address to inspect. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Authorize a new key on-chain via the AccountKeychain precompile. + #[command(visible_alias = "auth")] + Authorize { + /// The key address to authorize. + key_address: Address, + + /// Signature type: secp256k1, p256, or webauthn. + #[arg(default_value = "secp256k1", value_parser = parse_signature_type)] + key_type: SignatureType, + + /// Expiry timestamp (unix seconds). Defaults to u64::MAX (never expires). + #[arg(default_value_t = u64::MAX)] + expiry: u64, + + /// Enforce spending limits for this key. + #[arg(long)] + enforce_limits: bool, + + /// Spending limit in TOKEN:AMOUNT format. Can be specified multiple times. + #[arg(long = "limit", value_parser = parse_limit)] + limits: Vec, + + /// Call scope restriction in `TARGET[:SELECTORS[@RECIPIENTS]]` format. + /// TARGET alone allows all calls. `TARGET:transfer,approve` restricts to those selectors. + /// `TARGET:transfer@0x123` restricts selector to specific recipients. + #[arg(long = "scope", value_parser = parse_scope)] + scope: Vec, + + /// Call scope restrictions as a JSON array. + /// Format: `[{"target":"0x...","selectors":["transfer"]}]` or + /// `[{"target":"0x...","selectors":[{"selector":"transfer","recipients":["0x..."]}]}]` + #[arg(long = "scopes", value_parser = parse_scopes_json_wrapped, conflicts_with = "scope")] + scopes_json: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Revoke an authorized key on-chain via the AccountKeychain precompile. + #[command(visible_alias = "rev")] + Revoke { + /// The key address to revoke. + key_address: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Query the remaining spending limit for a key on a specific token. + #[command(name = "rl", visible_alias = "remaining-limit")] + RemainingLimit { + /// The wallet (account) address. + wallet_address: Address, + + /// The key address. + key_address: Address, + + /// The token address. + token: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Update the spending limit for a key on a specific token. + #[command(name = "ul", visible_alias = "update-limit")] + UpdateLimit { + /// The key address. + key_address: Address, + + /// The token address. + token: Address, + + /// The new spending limit. + new_limit: U256, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Set allowed call scopes for a key. + #[command(name = "ss", visible_alias = "set-scope")] + SetScope { + /// The key address. + key_address: Address, + + /// Call scope restriction in `TARGET[:SELECTORS[@RECIPIENTS]]` format. + #[arg(long = "scope", required = true, value_parser = parse_scope)] + scope: Vec, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove call scope for a key on a target. + #[command(name = "rs", visible_alias = "remove-scope")] + RemoveScope { + /// The key address. + key_address: Address, + + /// The target address to remove scope for. + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Read or edit TIP-1011 access-key permissions. + Policy { + #[command(subcommand)] + command: KeychainPolicySubcommand, + }, +} + +/// Higher-level access-key policy editing commands. +#[derive(Debug, Parser)] +pub enum KeychainPolicySubcommand { + /// Add or widen an allowed call rule for a target contract. + AddCall { + /// The key address to update. + key_address: Address, + + /// Root account address. Required when the key is not present in the local keys.toml. + #[arg(long, visible_alias = "wallet-address", value_name = "ADDRESS")] + root_account: Option
, + + /// Target contract address. + #[arg(long)] + target: Address, + + /// Function selector, full signature, or known TIP-20 shorthand. + #[arg(long, value_parser = parse_selector_arg)] + selector: SelectorArg, + + /// Optional recipient/spender restrictions for selector calls. + #[arg(long, value_delimiter = ',')] + recipients: Vec
, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Update a token spending limit amount for a key. + SetLimit { + /// The key address to update. + key_address: Address, + + /// Token address, numeric TIP-20 token id, or PathUSD. + #[arg(long, value_parser = parse_policy_token)] + token: Address, + + /// New raw token-denominated limit. + #[arg(long)] + amount: U256, + + /// Limit period such as 7d, 24h, or 3600s. + /// + /// The current AccountKeychain update entrypoint cannot change periods, so non-zero + /// values are rejected. + #[arg(long, value_parser = parse_period)] + period: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, + + /// Remove all allowed-call rules for a target contract. + RemoveTarget { + /// The key address to update. + key_address: Address, + + /// Target contract address to remove. + #[arg(long)] + target: Address, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + send_tx: SendTxOpts, + }, +} + +#[derive(Debug, Clone, Copy)] +pub struct SelectorArg([u8; 4]); + +fn parse_signature_type(s: &str) -> Result { + match s.to_lowercase().as_str() { + "secp256k1" => Ok(SignatureType::Secp256k1), + "p256" => Ok(SignatureType::P256), + "webauthn" => Ok(SignatureType::WebAuthn), + _ => Err(format!("unknown signature type: {s} (expected secp256k1, p256, or webauthn)")), + } +} + +const fn signature_type_name(t: &SignatureType) -> &'static str { + match t { + SignatureType::Secp256k1 => "secp256k1", + SignatureType::P256 => "p256", + SignatureType::WebAuthn => "webauthn", + _ => "unknown", + } +} + +const fn signature_type_label(t: &SignatureType) -> &'static str { + match t { + SignatureType::Secp256k1 => "Secp256k1", + SignatureType::P256 => "P256", + SignatureType::WebAuthn => "WebAuthn", + _ => "unknown", + } +} + +const fn key_type_name(t: &KeyType) -> &'static str { + match t { + KeyType::Secp256k1 => "secp256k1", + KeyType::P256 => "p256", + KeyType::WebAuthn => "webauthn", + } +} + +const fn key_type_label(t: &KeyType) -> &'static str { + match t { + KeyType::Secp256k1 => "Secp256k1", + KeyType::P256 => "P256", + KeyType::WebAuthn => "WebAuthn", + } +} + +const fn wallet_type_name(t: &WalletType) -> &'static str { + match t { + WalletType::Local => "local", + WalletType::Passkey => "passkey", + } +} + +/// Parse a `--limit TOKEN:AMOUNT` flag value. +fn parse_limit(s: &str) -> Result { + let (token_str, amount_str) = s + .split_once(':') + .ok_or_else(|| format!("invalid limit format: {s} (expected TOKEN:AMOUNT)"))?; + let token: Address = + token_str.parse().map_err(|e| format!("invalid token address '{token_str}': {e}"))?; + let amount: U256 = + amount_str.parse().map_err(|e| format!("invalid amount '{amount_str}': {e}"))?; + Ok(TokenLimit { token, amount, period: 0 }) +} + +/// Parse a `--scope TARGET[:SELECTORS[@RECIPIENTS]]` flag value. +/// +/// Formats: +/// - `0xAddr` — allow all calls to target +/// - `0xAddr:transfer,approve` — allow only those selectors (by name or 4-byte hex) +/// - `0xAddr:transfer@0xRecipient` — selector with recipient restriction +fn parse_scope(s: &str) -> Result { + let (target_str, selectors_str) = match s.split_once(':') { + Some((t, sel)) => (t, Some(sel)), + None => (s, None), + }; + + let target: Address = + target_str.parse().map_err(|e| format!("invalid target address '{target_str}': {e}"))?; + + let selector_rules = match selectors_str { + None => vec![], + Some(sel_str) => parse_selector_rules(sel_str)?, + }; + + Ok(CallScope { target, selectorRules: selector_rules }) +} + +/// Parse comma-separated selectors, each optionally with `@recipient1,recipient2,...`. +/// +/// Example: `transfer,approve` or `transfer@0x123` or `0xd09de08a` +fn parse_selector_rules(s: &str) -> Result, String> { + let mut rules = Vec::new(); + + for part in s.split(',') { + let part = part.trim(); + if part.is_empty() { + continue; + } + + let (selector_str, recipients_str) = match part.split_once('@') { + Some((sel, recip)) => (sel, Some(recip)), + None => (part, None), + }; + + let selector = parse_selector_bytes(selector_str)?; + + let recipients = match recipients_str { + None => vec![], + Some(r) => r + .split(',') + .filter(|s| !s.trim().is_empty()) + .map(|addr_str| { + let addr_str = addr_str.trim(); + addr_str + .parse::
() + .map_err(|e| format!("invalid recipient address '{addr_str}': {e}")) + }) + .collect::, _>>()?, + }; + + rules.push(SelectorRule { selector: selector.into(), recipients }); + } + + Ok(rules) +} + +/// Parse a selector string: a 4-byte hex (`0xd09de08a`), a full signature +/// (`transfer(address,uint256)`), or a well-known TIP-20 function name shorthand. +/// +/// Recognized shorthands: `transfer`, `approve`, `transferFrom`, `transferWithMemo`, +/// `transferFromWithMemo`. These resolve to the standard ERC20/TIP-20 signatures. +/// Unknown names without parentheses are hashed as `name()`. +fn parse_selector_bytes(s: &str) -> Result<[u8; 4], String> { + let s = s.trim(); + if s.starts_with("0x") || s.starts_with("0X") { + let hex_str = &s[2..]; + if hex_str.len() != 8 { + return Err(format!("hex selector must be 4 bytes (8 hex chars), got: {s}")); + } + let bytes = hex::decode(hex_str).map_err(|e| format!("invalid hex selector '{s}': {e}"))?; + let mut arr = [0u8; 4]; + arr.copy_from_slice(&bytes); + Ok(arr) + } else { + // Expand well-known TIP-20 shorthands to full signatures. + let sig = if s.contains('(') { + s.to_string() + } else { + match s { + "transfer" => "transfer(address,uint256)".to_string(), + "approve" => "approve(address,uint256)".to_string(), + "transferFrom" => "transferFrom(address,address,uint256)".to_string(), + "transferWithMemo" => "transferWithMemo(address,uint256,bytes32)".to_string(), + "transferFromWithMemo" => { + "transferFromWithMemo(address,address,uint256,bytes32)".to_string() + } + _ => format!("{s}()"), + } + }; + let hash = keccak256(sig.as_bytes()); + let mut arr = [0u8; 4]; + arr.copy_from_slice(&hash[..4]); + Ok(arr) + } +} + +fn parse_selector_arg(s: &str) -> Result { + parse_selector_bytes(s).map(SelectorArg) +} + +fn parse_policy_token(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "pathusd" | "path_usd" | "path-usd" | "usd" => Ok(PATH_USD_ADDRESS), + _ => foundry_cli::utils::parse_fee_token_address(s).map_err(|e| e.to_string()), + } +} + +fn parse_period(s: &str) -> Result { + let s = s.trim(); + if s.is_empty() { + return Err("period cannot be empty".to_string()); + } + + let split = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len()); + if split == 0 { + return Err(format!( + "invalid period '{s}': expected a number followed by s, m, h, d, or w" + )); + } + + let value: u64 = + s[..split].parse().map_err(|e| format!("invalid period value '{}': {e}", &s[..split]))?; + let multiplier = match &s[split..].to_ascii_lowercase()[..] { + "" | "s" => 1, + "m" => 60, + "h" => 60 * 60, + "d" => 24 * 60 * 60, + "w" => 7 * 24 * 60 * 60, + unit => { + return Err(format!( + "invalid period unit '{unit}' in '{s}' (expected s, m, h, d, or w)" + )); + } + }; + + value.checked_mul(multiplier).ok_or_else(|| format!("period '{s}' is too large")) +} + +/// Represents a single scope entry in JSON format for `--scopes`. +#[derive(serde::Deserialize)] +struct JsonCallScope { + target: Address, + #[serde(default)] + selectors: Option>, +} + +/// A selector entry can be either a plain string or an object with recipients. +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum JsonSelectorEntry { + Name(String), + WithRecipients(JsonSelectorWithRecipients), +} + +#[derive(serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct JsonSelectorWithRecipients { + selector: String, + #[serde(default)] + recipients: Vec
, +} + +/// Parse `--scopes` JSON flag value. +fn parse_scopes_json(s: &str) -> Result, String> { + let entries: Vec = + serde_json::from_str(s).map_err(|e| format!("invalid --scopes JSON: {e}"))?; + + let mut scopes = Vec::new(); + for entry in entries { + let selector_rules = match entry.selectors { + None => vec![], + Some(sels) => { + let mut rules = Vec::new(); + for sel_entry in sels { + let (selector_str, recipients) = match sel_entry { + JsonSelectorEntry::Name(name) => (name, vec![]), + JsonSelectorEntry::WithRecipients(r) => (r.selector, r.recipients), + }; + let selector = parse_selector_bytes(&selector_str) + .map_err(|e| format!("in --scopes JSON: {e}"))?; + rules.push(SelectorRule { selector: selector.into(), recipients }); + } + rules + } + }; + scopes.push(CallScope { target: entry.target, selectorRules: selector_rules }); + } + + Ok(scopes) +} + +/// Newtype wrapper for parsed `--scopes` JSON so clap can treat it as a single value. +#[derive(Debug, Clone)] +pub struct ScopesJson(Vec); + +/// Parse `--scopes` JSON flag value into the newtype wrapper. +fn parse_scopes_json_wrapped(s: &str) -> Result { + parse_scopes_json(s).map(ScopesJson) +} + +impl KeychainSubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::List => run_list(), + Self::Show { wallet_address } => run_show(wallet_address), + Self::Check { wallet_address, key_address, rpc } => { + run_check(wallet_address, key_address, rpc).await + } + Self::Inspect { key_address, root_account, rpc } => { + run_inspect(key_address, root_account, rpc).await + } + Self::Authorize { + key_address, + key_type, + expiry, + enforce_limits, + limits, + scope, + scopes_json, + tx, + send_tx, + } => { + let all_scopes = if let Some(ScopesJson(json_scopes)) = scopes_json { + json_scopes + } else { + scope + }; + run_authorize( + key_address, + key_type, + expiry, + enforce_limits, + limits, + all_scopes, + tx, + send_tx, + ) + .await + } + Self::Revoke { key_address, tx, send_tx } => run_revoke(key_address, tx, send_tx).await, + Self::RemainingLimit { wallet_address, key_address, token, rpc } => { + run_remaining_limit(wallet_address, key_address, token, rpc).await + } + Self::UpdateLimit { key_address, token, new_limit, tx, send_tx } => { + run_update_limit(key_address, token, new_limit, tx, send_tx).await + } + Self::SetScope { key_address, scope, tx, send_tx } => { + run_set_scope(key_address, scope, tx, send_tx).await + } + Self::RemoveScope { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await + } + Self::Policy { command } => command.run().await, + } + } +} + +impl KeychainPolicySubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::AddCall { + key_address, + root_account, + target, + selector, + recipients, + tx, + send_tx, + } => { + run_policy_add_call( + key_address, + root_account, + target, + selector.0, + recipients, + tx, + send_tx, + ) + .await + } + Self::SetLimit { key_address, token, amount, period, tx, send_tx } => { + run_policy_set_limit(key_address, token, amount, period, tx, send_tx).await + } + Self::RemoveTarget { key_address, target, tx, send_tx } => { + run_remove_scope(key_address, target, tx, send_tx).await + } + } + } +} + +/// `cast keychain list` — display all entries from keys.toml. +fn run_list() -> Result<()> { + let keys_file = load_keys_file()?; + + if keys_file.keys.is_empty() { + sh_println!("No keys found in keys.toml.")?; + return Ok(()); + } + + if shell::is_json() { + let entries: Vec<_> = keys_file.keys.iter().map(key_entry_to_json).collect(); + sh_println!("{}", serde_json::to_string_pretty(&entries)?)?; + return Ok(()); + } + + for (i, entry) in keys_file.keys.iter().enumerate() { + if i > 0 { + sh_println!()?; + } + print_key_entry(entry)?; + } + + Ok(()) +} + +/// `cast keychain show ` — show keys for a specific wallet. +fn run_show(wallet_address: Address) -> Result<()> { + let keys_file = load_keys_file()?; + + let entries: Vec<_> = + keys_file.keys.iter().filter(|e| e.wallet_address == wallet_address).collect(); + + if entries.is_empty() { + sh_println!("No keys found for wallet {wallet_address}.")?; + return Ok(()); + } + + if shell::is_json() { + let json: Vec<_> = entries.iter().map(|e| key_entry_to_json(e)).collect(); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + for (i, entry) in entries.iter().enumerate() { + if i > 0 { + sh_println!()?; + } + print_key_entry(entry)?; + } + + Ok(()) +} + +#[derive(Debug, Clone)] +struct LocalLimitMetadata { + token: Address, + amount: String, +} + +#[derive(Debug, Clone)] +struct KeyMetadata { + root_account: Address, + key_type: Option, + limits: Vec, +} + +#[derive(Debug, Clone)] +struct InspectedLimit { + token: Address, + configured_amount: Option, + remaining: U256, + period_end: Option, +} + +#[derive(Debug, Clone)] +enum AllowedCallsView { + Unsupported, + Unrestricted, + Scoped(Vec), +} + +/// `cast keychain inspect ` — inspect on-chain key policy. +async fn run_inspect( + key_address: Address, + root_account: Option
, + rpc: RpcOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let info: KeyInfo = provider.get_keychain_key(metadata.root_account, key_address).await?; + let provisioned = info.keyId != Address::ZERO; + let is_t3 = is_tempo_hardfork_active(&provider, TempoHardfork::T3).await?; + + let mut limits = Vec::new(); + if info.enforceLimits { + for local_limit in &metadata.limits { + let (remaining, period_end) = if is_t3 { + let limit = provider + .get_keychain_remaining_limit_with_period( + metadata.root_account, + key_address, + local_limit.token, + ) + .await?; + (limit.remaining, Some(limit.periodEnd)) + } else { + let remaining = provider + .account_keychain() + .getRemainingLimit(metadata.root_account, key_address, local_limit.token) + .call() + .await?; + (remaining, None) + }; + + limits.push(InspectedLimit { + token: local_limit.token, + configured_amount: Some(local_limit.amount.clone()), + remaining, + period_end, + }); + } + } + + let allowed_calls = if is_t3 { + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + if allowed.isScoped { + AllowedCallsView::Scoped(allowed.scopes) + } else { + AllowedCallsView::Unrestricted + } + } else { + AllowedCallsView::Unsupported + }; + + if shell::is_json() { + let key_type = if provisioned { + signature_type_name(&info.signatureType).to_string() + } else { + metadata + .key_type + .map(|key_type| key_type_name(&key_type).to_string()) + .unwrap_or_else(|| "unknown".to_string()) + }; + let json = serde_json::json!({ + "root_account": metadata.root_account.to_string(), + "key_id": key_address.to_string(), + "provisioned": provisioned, + "type": key_type, + "expiry": provisioned.then_some(info.expiry), + "expiry_human": provisioned.then(|| format_expiry_for_inspect(info.expiry)), + "enforce_limits": info.enforceLimits, + "is_revoked": info.isRevoked, + "limits": limits.iter().map(inspected_limit_to_json).collect::>(), + "allowed_calls": allowed_calls_to_json(&allowed_calls), + }); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + let key_type = if provisioned { + signature_type_label(&info.signatureType) + } else { + metadata.key_type.map(|key_type| key_type_label(&key_type)).unwrap_or("unknown") + }; + + sh_println!("Root account: {}", metadata.root_account)?; + sh_println!("Key id: {key_address}")?; + sh_println!("Type: {key_type}")?; + + if info.isRevoked { + sh_println!("Status: revoked")?; + } else if !provisioned { + sh_println!("Status: not provisioned")?; + } else { + sh_println!("Status: active")?; + sh_println!("Expiry: {}", format_expiry_for_inspect(info.expiry))?; + } + + print_inspected_limits(info.enforceLimits, &limits)?; + print_allowed_calls(&allowed_calls)?; + + Ok(()) +} + +/// `cast keychain check` / `cast keychain info` — query on-chain key status. +async fn run_check(wallet_address: Address, key_address: Address, rpc: RpcOpts) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let info: KeyInfo = provider.get_keychain_key(wallet_address, key_address).await?; + + let provisioned = info.keyId != Address::ZERO; + + if shell::is_json() { + let json = serde_json::json!({ + "wallet_address": wallet_address.to_string(), + "key_address": key_address.to_string(), + "provisioned": provisioned, + "signatureType": signature_type_name(&info.signatureType), + "key_id": info.keyId.to_string(), + "expiry": info.expiry, + "expiry_human": format_expiry(info.expiry), + "enforce_limits": info.enforceLimits, + "is_revoked": info.isRevoked, + }); + sh_println!("{}", serde_json::to_string_pretty(&json)?)?; + return Ok(()); + } + + sh_println!("Wallet: {wallet_address}")?; + sh_println!("Key: {key_address}")?; + + if info.isRevoked { + sh_println!("Status: {} revoked", "✗".red())?; + return Ok(()); + } + + if !provisioned { + sh_println!("Status: {} not provisioned", "✗".red())?; + return Ok(()); + } + + // Status line: active key. + { + sh_println!("Status: {} active", "✓".green())?; + } + + sh_println!("Signature Type: {}", signature_type_name(&info.signatureType))?; + sh_println!("Key ID: {}", info.keyId)?; + + // Expiry: show human-readable date and whether it's expired. + let expiry_str = format_expiry(info.expiry); + if info.expiry == u64::MAX { + sh_println!("Expiry: {}", expiry_str)?; + } else { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + if info.expiry <= now { + sh_println!("Expiry: {} ({})", expiry_str, "expired".red())?; + } else { + sh_println!("Expiry: {}", expiry_str)?; + } + } + + sh_println!("Spending Limits: {}", if info.enforceLimits { "enforced" } else { "none" })?; + + Ok(()) +} + +/// `cast keychain authorize` / `cast keychain auth` — authorize a key on-chain. +#[allow(clippy::too_many_arguments)] +async fn run_authorize( + key_address: Address, + key_type: SignatureType, + expiry: u64, + enforce_limits: bool, + limits: Vec, + allowed_calls: Vec, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let enforce = enforce_limits || !limits.is_empty(); + + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let calldata = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + // T3+ authorizeKey(address,SignatureType,KeyRestrictions) + let restrictions = KeyRestrictions { + expiry, + enforceLimits: enforce, + limits, + allowAnyCalls: allowed_calls.is_empty(), + allowedCalls: allowed_calls, + }; + authorizeKeyCall { keyId: key_address, signatureType: key_type, config: restrictions } + .abi_encode() + } else { + // Legacy (pre-T3) authorizeKey(address,SignatureType,uint64,bool,LegacyTokenLimit[]) + let legacy_limits: Vec = limits + .into_iter() + .map(|l| LegacyTokenLimit { token: l.token, amount: l.amount }) + .collect(); + legacyAuthorizeKeyCall { + keyId: key_address, + signatureType: key_type, + expiry, + enforceLimits: enforce, + limits: legacy_limits, + } + .abi_encode() + }; + + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain revoke` / `cast keychain rev` — revoke a key on-chain. +async fn run_revoke( + key_address: Address, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = IAccountKeychain::revokeKeyCall { keyId: key_address }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain rl` — query remaining spending limit. +async fn run_remaining_limit( + wallet_address: Address, + key_address: Address, + token: Address, + rpc: RpcOpts, +) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let remaining: U256 = if is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + provider.get_keychain_remaining_limit(wallet_address, key_address, token).await? + } else { + // Pre-T3: use the legacy getRemainingLimit(address,address,address) + provider + .account_keychain() + .getRemainingLimit(wallet_address, key_address, token) + .call() + .await? + }; + + if shell::is_json() { + sh_println!("{}", serde_json::json!({ "remaining": remaining.to_string() }))?; + } else { + sh_println!("{remaining}")?; + } + + Ok(()) +} + +/// `cast keychain ul` — update spending limit. +async fn run_update_limit( + key_address: Address, + token: Address, + new_limit: U256, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = IAccountKeychain::updateSpendingLimitCall { + keyId: key_address, + token, + newLimit: new_limit, + } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain ss` — set allowed call scopes. +async fn run_set_scope( + key_address: Address, + scopes: Vec, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = + IAccountKeychain::setAllowedCallsCall { keyId: key_address, scopes }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain rs` — remove call scope for a target. +async fn run_remove_scope( + key_address: Address, + target: Address, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let calldata = + IAccountKeychain::removeAllowedCallsCall { keyId: key_address, target }.abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain policy add-call` — merge a selector rule into a target scope. +async fn run_policy_add_call( + key_address: Address, + root_account: Option
, + target: Address, + selector: [u8; 4], + recipients: Vec
, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + let metadata = resolve_key_metadata(key_address, root_account)?; + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if !is_tempo_hardfork_active(&provider, TempoHardfork::T3).await? { + eyre::bail!("allowed-call policy editing requires the Tempo T3 hardfork"); + } + + let allowed = provider + .account_keychain() + .getAllowedCalls(metadata.root_account, key_address) + .call() + .await?; + + let new_rule = SelectorRule { selector: selector.into(), recipients }; + let existing_target = allowed + .isScoped + .then(|| allowed.scopes.into_iter().find(|scope| scope.target == target)) + .flatten(); + + let (target_scope, changed) = match existing_target { + Some(mut scope) => { + if scope.selectorRules.is_empty() { + sh_warn!( + "Allowed calls for {} already allow any selector; leaving wildcard scope unchanged", + address_label_with_address(target) + )?; + } + let changed = add_selector_rule_to_scope(&mut scope, new_rule); + (scope, changed) + } + None => (CallScope { target, selectorRules: vec![new_rule] }, true), + }; + + if !changed { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ "status": "already_present", "target": target.to_string() }) + )?; + } else { + sh_println!("Allowed call already present for {}", address_label_with_address(target))?; + } + return Ok(()); + } + + let calldata = + IAccountKeychain::setAllowedCallsCall { keyId: key_address, scopes: vec![target_scope] } + .abi_encode(); + send_keychain_tx(calldata, tx_opts, &send_tx).await +} + +/// `cast keychain policy set-limit` — update a spending limit amount. +async fn run_policy_set_limit( + key_address: Address, + token: Address, + amount: U256, + period: Option, + tx_opts: TransactionOpts, + send_tx: SendTxOpts, +) -> Result<()> { + if period.is_some_and(|period| period != 0) { + eyre::bail!( + "--period is not supported by the current AccountKeychain updateSpendingLimit \ + precompile; periods can only be set when authorizing a key" + ); + } + + // updateSpendingLimit authorizes against msg.sender; the root account is not part of calldata. + run_update_limit(key_address, token, amount, tx_opts, send_tx).await +} + +/// Shared helper to send a keychain precompile transaction. +async fn send_keychain_tx( + calldata: Vec, + mut tx_opts: TransactionOpts, + send_tx: &SendTxOpts, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let print_sponsor_hash = tx_opts.tempo.print_sponsor_hash; + let tempo_sponsor = + if print_sponsor_hash { None } else { tx_opts.tempo.sponsor_config().await? }; + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)); + } + + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx_opts.tempo.nonce_key` from the lane. + let resolved_lane = resolve_lane(&mut tx_opts.tempo, &config.root)?; + + let builder = CastTxBuilder::new(&provider, tx_opts, &config) + .await? + .with_to(Some(NameOrAddress::Address(ACCOUNT_KEYCHAIN_ADDRESS))) + .await? + .with_code_sig_and_args(None, Some(hex::encode_prefixed(&calldata)), vec![]) + .await?; + + // Keychain management calls are authorized by the root account. Access keys can use their + // permissions, but cannot mutate their own key policy. + let browser = send_tx.browser.run::().await?; + + if print_sponsor_hash { + let from = if let Some(ref browser) = browser { + browser.address() + } else { + signer + .as_ref() + .ok_or_else(|| { + eyre::eyre!( + "--tempo.print-sponsor-hash requires a root account signer, such as \ + --browser, --private-key, or --keystore" + ) + })? + .address() + }; + + let (tx, _) = builder.build(from).await?; + let hash = tx + .compute_sponsor_hash(from) + .ok_or_else(|| eyre::eyre!("This network does not support sponsored transactions"))?; + if shell::is_json() { + sh_println!("{}", serde_json::json!({ "sponsor_hash": format!("{hash:?}") }))?; + } else { + sh_println!("{hash:?}")?; + } + return Ok(()); + } + + if let Some(browser) = browser { + let chain = builder.chain(); + let (mut tx, _) = builder.build(browser.address()).await?; + if chain.is_tempo() + && let Some(gas) = tx.gas_limit() + { + tx.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, browser.address()).await?; + } + + let tx_hash = browser.send_transaction_via_browser(tx).await?; + CastTxSender::new(&provider) + .print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout) + .await?; + } else if tempo_access_key.is_some() { + eyre::bail!( + "keychain policy changes must be signed by the root account; the selected `--from` \ + resolved to a Tempo access key. Use `--browser` for passkey roots, or pass a root \ + account signer with `--private-key`, `--keystore`, Ledger, Trezor, AWS, GCP, or Turnkey." + ); + } else { + let signer = match signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; + let from = signer.address(); + let (mut tx, _) = builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } + + let wallet = EthereumWallet::from(signer); + let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default() + .wallet(wallet) + .connect_provider(&provider); + + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct AnvilNodeInfo { + hard_fork: Option, + network: Option, +} + +async fn is_tempo_hardfork_active

(provider: &P, hardfork: TempoHardfork) -> Result +where + P: Provider, +{ + match provider.is_hardfork_active(hardfork).await { + Ok(active) => Ok(active), + Err(err) if is_rpc_method_not_found(&err) => { + match anvil_tempo_hardfork_active(provider, hardfork).await { + Ok(Some(active)) => Ok(active), + _ => Err(err.into()), + } + } + Err(err) => Err(err.into()), + } +} + +async fn anvil_tempo_hardfork_active

( + provider: &P, + hardfork: TempoHardfork, +) -> Result, TransportError> +where + P: Provider, +{ + let info = provider.raw_request::<_, AnvilNodeInfo>("anvil_nodeInfo".into(), ()).await?; + Ok(active_from_anvil_node_info(&info, hardfork)) +} + +fn active_from_anvil_node_info(info: &AnvilNodeInfo, hardfork: TempoHardfork) -> Option { + (info.network.as_deref() == Some("tempo")).then(|| { + info.hard_fork + .as_deref() + .and_then(|active_hardfork| active_hardfork.parse::().ok()) + .is_some_and(|active_hardfork| active_hardfork >= hardfork) + }) +} + +fn is_rpc_method_not_found(err: &TransportError) -> bool { + err.as_error_resp().is_some_and(|payload| payload.code == -32601) +} + +fn resolve_key_metadata( + key_address: Address, + root_account: Option

, +) -> Result { + let keys_file = read_tempo_keys_file(); + + if let Some(root_account) = root_account { + if let Some(keys_file) = keys_file.as_ref() + && let Some(entry) = keys_file.keys.iter().find(|entry| { + entry.wallet_address == root_account + && key_entry_effective_key(entry) == key_address + }) + { + return Ok(key_metadata_from_entry(entry)); + } + + return Ok(KeyMetadata { root_account, key_type: None, limits: Vec::new() }); + } + + let Some(keys_file) = keys_file.as_ref() else { + eyre::bail!( + "key {key_address} was not found because the local keys file could not be read at {}; pass --root-account", + tempo_keys_path_display() + ); + }; + + let matches: Vec<_> = keys_file + .keys + .iter() + .filter(|entry| key_entry_effective_key(entry) == key_address) + .collect(); + + if matches.is_empty() { + eyre::bail!( + "key {key_address} was not found in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let root_account = matches[0].wallet_address; + if matches.iter().any(|entry| entry.wallet_address != root_account) { + eyre::bail!( + "key {key_address} matches multiple root accounts in {}; pass --root-account", + tempo_keys_path_display() + ); + } + + let entry = + matches.iter().copied().find(|entry| !entry.limits.is_empty()).unwrap_or(matches[0]); + Ok(key_metadata_from_entry(entry)) +} + +fn key_entry_effective_key(entry: &tempo::KeyEntry) -> Address { + entry.key_address.unwrap_or(entry.wallet_address) +} + +fn key_metadata_from_entry(entry: &tempo::KeyEntry) -> KeyMetadata { + KeyMetadata { + root_account: entry.wallet_address, + key_type: Some(entry.key_type), + limits: entry + .limits + .iter() + .map(|limit| LocalLimitMetadata { token: limit.currency, amount: limit.limit.clone() }) + .collect(), + } +} + +fn tempo_keys_path_display() -> String { + tempo_keys_path() + .map(|path| path.display().to_string()) + .unwrap_or_else(|| "(unknown)".to_string()) +} + +fn add_selector_rule_to_scope(scope: &mut CallScope, rule: SelectorRule) -> bool { + if scope.selectorRules.is_empty() { + return false; + } + + let Some(existing_rule) = + scope.selectorRules.iter_mut().find(|existing| existing.selector == rule.selector) + else { + scope.selectorRules.push(rule); + return true; + }; + + if existing_rule.recipients.is_empty() { + return false; + } + + if rule.recipients.is_empty() { + existing_rule.recipients = Vec::new(); + return true; + } + + let mut changed = false; + for recipient in rule.recipients { + if !existing_rule.recipients.contains(&recipient) { + existing_rule.recipients.push(recipient); + changed = true; + } + } + changed +} + +fn inspected_limit_to_json(limit: &InspectedLimit) -> serde_json::Value { + serde_json::json!({ + "token": limit.token.to_string(), + "token_label": address_label(limit.token), + "configured_amount": limit.configured_amount.as_deref(), + "remaining": limit.remaining.to_string(), + "period_end": limit.period_end, + "period_end_human": limit.period_end.and_then(|period_end| { + (period_end != 0).then(|| format_period_end(period_end)) + }), + }) +} + +fn allowed_calls_to_json(allowed_calls: &AllowedCallsView) -> serde_json::Value { + match allowed_calls { + AllowedCallsView::Unsupported => serde_json::json!({ + "mode": "unsupported", + "scopes": [], + }), + AllowedCallsView::Unrestricted => serde_json::json!({ + "mode": "any", + "scopes": [], + }), + AllowedCallsView::Scoped(scopes) => serde_json::json!({ + "mode": if scopes.is_empty() { "none" } else { "scoped" }, + "scopes": scopes.iter().map(call_scope_to_json).collect::>(), + }), + } +} + +fn call_scope_to_json(scope: &CallScope) -> serde_json::Value { + serde_json::json!({ + "target": scope.target.to_string(), + "target_label": address_label(scope.target), + "selectors": scope.selectorRules.iter().map(selector_rule_to_json).collect::>(), + }) +} + +fn selector_rule_to_json(rule: &SelectorRule) -> serde_json::Value { + serde_json::json!({ + "selector": selector_hex(&rule.selector.0), + "signature": selector_signature(&rule.selector.0), + "recipients": rule.recipients.iter().map(ToString::to_string).collect::>(), + }) +} + +fn print_inspected_limits(enforce_limits: bool, limits: &[InspectedLimit]) -> Result<()> { + if !enforce_limits { + sh_println!("Limits: none")?; + return Ok(()); + } + + sh_println!("Limits:")?; + if limits.is_empty() { + sh_println!(" enforced, but no local limit metadata was found")?; + return Ok(()); + } + + for limit in limits { + let configured = limit.configured_amount.as_deref().unwrap_or("unknown"); + let period = limit + .period_end + .and_then(|period_end| { + (period_end != 0).then(|| format!(" ({})", format_period_end(period_end))) + }) + .unwrap_or_default(); + sh_println!( + " {}: {} / {} remaining{}", + address_label(limit.token), + limit.remaining, + configured, + period + )?; + } + + Ok(()) +} + +fn print_allowed_calls(allowed_calls: &AllowedCallsView) -> Result<()> { + match allowed_calls { + AllowedCallsView::Unsupported => sh_println!("Allowed calls: unsupported before T3")?, + AllowedCallsView::Unrestricted => sh_println!("Allowed calls: any")?, + AllowedCallsView::Scoped(scopes) if scopes.is_empty() => { + sh_println!("Allowed calls: none")?; + } + AllowedCallsView::Scoped(scopes) => { + sh_println!("Allowed calls:")?; + for scope in scopes { + sh_println!(" {}:", address_label_with_address(scope.target))?; + if scope.selectorRules.is_empty() { + sh_println!(" any selector")?; + continue; + } + + for rule in &scope.selectorRules { + sh_println!( + " {} -> {}", + format_selector(&rule.selector.0), + format_recipients(&rule.recipients) + )?; + } + } + } + } + + Ok(()) +} + +fn address_label(address: Address) -> String { + if address == PATH_USD_ADDRESS { "PathUSD".to_string() } else { address.to_string() } +} + +fn address_label_with_address(address: Address) -> String { + if address == PATH_USD_ADDRESS { format!("PathUSD ({address})") } else { address.to_string() } +} + +fn format_selector(selector: &[u8; 4]) -> String { + selector_signature(selector).map(str::to_string).unwrap_or_else(|| selector_hex(selector)) +} + +fn selector_signature(selector: &[u8; 4]) -> Option<&'static str> { + if selector == &ITIP20::transferCall::SELECTOR { + Some("transfer(address,uint256)") + } else if selector == &ITIP20::approveCall::SELECTOR { + Some("approve(address,uint256)") + } else if selector == &ITIP20::transferFromCall::SELECTOR { + Some("transferFrom(address,address,uint256)") + } else if selector == &ITIP20::transferWithMemoCall::SELECTOR { + Some("transferWithMemo(address,uint256,bytes32)") + } else if selector == &ITIP20::transferFromWithMemoCall::SELECTOR { + Some("transferFromWithMemo(address,address,uint256,bytes32)") + } else if selector == &ITIP20::mintCall::SELECTOR { + Some("mint(address,uint256)") + } else if selector == &ITIP20::burnCall::SELECTOR { + Some("burn(uint256)") + } else { + None + } +} + +fn selector_hex(selector: &[u8; 4]) -> String { + hex::encode_prefixed(selector) +} + +fn format_recipients(recipients: &[Address]) -> String { + if recipients.is_empty() { + return "any recipient".to_string(); + } + + let recipients = recipients.iter().map(ToString::to_string).collect::>().join(", "); + format!("recipients [{recipients}]") +} + +fn format_expiry_for_inspect(expiry: u64) -> String { + if expiry == u64::MAX { + return "never".to_string(); + } + + format!("{} ({})", format_timestamp_iso(expiry), format_relative_timestamp(expiry)) +} + +fn format_period_end(period_end: u64) -> String { + format!("period resets {}", format_relative_timestamp(period_end)) +} + +fn format_timestamp_iso(timestamp: u64) -> String { + DateTime::from_timestamp(timestamp as i64, 0) + .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) + .unwrap_or_else(|| timestamp.to_string()) +} + +fn format_relative_timestamp(timestamp: u64) -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + if timestamp == now { + "now".to_string() + } else if timestamp > now { + format!("in {}", format_duration_words(timestamp - now)) + } else { + format!("{} ago", format_duration_words(now - timestamp)) + } +} + +fn format_duration_words(seconds: u64) -> String { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + const DAY: u64 = 24 * HOUR; + + if seconds >= DAY { + let days = seconds / DAY; + if days == 1 { "1 day".to_string() } else { format!("{days} days") } + } else if seconds >= HOUR { + format!("{}h", seconds / HOUR) + } else if seconds >= MINUTE { + format!("{}m", seconds / MINUTE) + } else { + format!("{seconds}s") + } +} + +fn format_expiry(expiry: u64) -> String { + if expiry == u64::MAX { + return "never".to_string(); + } + DateTime::from_timestamp(expiry as i64, 0) + .map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()) + .unwrap_or_else(|| expiry.to_string()) +} + +fn load_keys_file() -> Result { + match read_tempo_keys_file() { + Some(f) => Ok(f), + None => { + let path = tempo_keys_path() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| "(unknown)".to_string()); + eyre::bail!("could not read keys file at {path}") + } + } +} + +fn print_key_entry(entry: &tempo::KeyEntry) -> Result<()> { + sh_println!("Wallet: {}", entry.wallet_address)?; + sh_println!("Wallet Type: {}", wallet_type_name(&entry.wallet_type))?; + sh_println!("Chain ID: {}", entry.chain_id)?; + sh_println!("Key Type: {}", key_type_name(&entry.key_type))?; + + if let Some(key_address) = entry.key_address { + sh_println!("Key Address: {key_address}")?; + + if key_address == entry.wallet_address { + sh_println!("Mode: direct (EOA)")?; + } else { + sh_println!("Mode: keychain (access key)")?; + } + } else { + sh_println!("Key Address: (not set)")?; + sh_println!("Mode: direct (EOA)")?; + } + + if let Some(expiry) = entry.expiry { + sh_println!("Expiry: {}", format_expiry(expiry))?; + } + + sh_println!("Has Key: {}", entry.has_inline_key())?; + sh_println!("Has Auth: {}", entry.key_authorization.is_some())?; + + if !entry.limits.is_empty() { + sh_println!("Limits:")?; + for limit in &entry.limits { + sh_println!(" {} → {}", limit.currency, limit.limit)?; + } + } + + Ok(()) +} + +fn key_entry_to_json(entry: &tempo::KeyEntry) -> serde_json::Value { + let is_direct = entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let limits: Vec<_> = entry + .limits + .iter() + .map(|l| { + serde_json::json!({ + "currency": l.currency.to_string(), + "limit": l.limit, + }) + }) + .collect(); + + serde_json::json!({ + "wallet_address": entry.wallet_address.to_string(), + "wallet_type": wallet_type_name(&entry.wallet_type), + "chain_id": entry.chain_id, + "key_type": key_type_name(&entry.key_type), + "key_address": entry.key_address.map(|a: Address| a.to_string()), + "mode": if is_direct { "direct" } else { "keychain" }, + "expiry": entry.expiry, + "expiry_human": entry.expiry.map(format_expiry), + "has_key": entry.has_inline_key(), + "has_authorization": entry.key_authorization.is_some(), + "limits": limits, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_json_rpc::ErrorPayload; + use std::str::FromStr; + + #[test] + fn test_parse_selector_bytes_named() { + let sel = parse_selector_bytes("transfer").unwrap(); + assert_eq!(sel, keccak256(b"transfer(address,uint256)")[..4]); + + let sel = parse_selector_bytes("approve").unwrap(); + assert_eq!(sel, keccak256(b"approve(address,uint256)")[..4]); + + let sel = parse_selector_bytes("transferWithMemo").unwrap(); + assert_eq!(sel, keccak256(b"transferWithMemo(address,uint256,bytes32)")[..4]); + } + + #[test] + fn test_parse_selector_bytes_hex() { + let sel = parse_selector_bytes("0xaabbccdd").unwrap(); + assert_eq!(sel, [0xaa, 0xbb, 0xcc, 0xdd]); + + let sel = parse_selector_bytes("0xd09de08a").unwrap(); + assert_eq!(sel, [0xd0, 0x9d, 0xe0, 0x8a]); + } + + #[test] + fn test_parse_selector_bytes_hex_invalid() { + assert!(parse_selector_bytes("0xaabb").is_err()); + assert!(parse_selector_bytes("0xaabbccddee").is_err()); + assert!(parse_selector_bytes("0xzzzzzzzz").is_err()); + } + + #[test] + fn test_parse_selector_bytes_full_signature() { + let sel = parse_selector_bytes("increment()").unwrap(); + assert_eq!(sel, keccak256(b"increment()")[..4]); + } + + #[test] + fn test_parse_selector_rules_simple() { + let rules = parse_selector_rules("transfer,approve").unwrap(); + assert_eq!(rules.len(), 2); + assert!(rules[0].recipients.is_empty()); + assert!(rules[1].recipients.is_empty()); + } + + #[test] + fn test_parse_selector_rules_with_recipient() { + let rules = + parse_selector_rules("transfer@0x1111111111111111111111111111111111111111").unwrap(); + assert_eq!(rules.len(), 1); + assert_eq!(rules[0].recipients.len(), 1); + assert_eq!( + rules[0].recipients[0], + Address::from_str("0x1111111111111111111111111111111111111111").unwrap() + ); + } + + #[test] + fn test_parse_selector_rules_hex_with_recipient() { + let rules = + parse_selector_rules("0xaabbccdd@0x1111111111111111111111111111111111111111").unwrap(); + assert_eq!(rules.len(), 1); + assert_eq!(rules[0].selector.0, [0xaa, 0xbb, 0xcc, 0xdd]); + assert_eq!(rules[0].recipients.len(), 1); + } + + #[test] + fn test_parse_scope_target_only() { + let scope = parse_scope("0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D").unwrap(); + assert_eq!( + scope.target, + Address::from_str("0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D").unwrap() + ); + assert!(scope.selectorRules.is_empty()); + } + + #[test] + fn test_parse_scope_with_selectors() { + let scope = + parse_scope("0x20c0000000000000000000000000000000000001:transfer,approve").unwrap(); + assert_eq!(scope.selectorRules.len(), 2); + assert!(scope.selectorRules[0].recipients.is_empty()); + assert!(scope.selectorRules[1].recipients.is_empty()); + } + + #[test] + fn test_parse_scope_hex_selector() { + let scope = parse_scope("0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D:0xaabbccdd").unwrap(); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].selector.0, [0xaa, 0xbb, 0xcc, 0xdd]); + assert!(scope.selectorRules[0].recipients.is_empty()); + } + + #[test] + fn test_parse_scope_selector_with_recipient() { + let scope = parse_scope( + "0x20c0000000000000000000000000000000000001:transfer@0x1111111111111111111111111111111111111111", + ) + .unwrap(); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].recipients.len(), 1); + } + + #[test] + fn test_parse_scopes_json_plain() { + let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":["transfer","approve"]},{"target":"0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D"}]"#; + let result = parse_scopes_json(json).unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].selectorRules.len(), 2); + assert!(result[1].selectorRules.is_empty()); + } + + #[test] + fn test_parse_scopes_json_with_recipients() { + let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":[{"selector":"transfer","recipients":["0x1111111111111111111111111111111111111111"]}]}]"#; + let result = parse_scopes_json(json).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].selectorRules.len(), 1); + assert_eq!(result[0].selectorRules[0].recipients.len(), 1); + } + + #[test] + fn test_parse_scopes_json_deny_unknown_fields() { + let json = r#"[{"target":"0x20c0000000000000000000000000000000000001","selectors":[{"selector":"transfer","recipients":[],"bogus":true}]}]"#; + assert!(parse_scopes_json(json).is_err()); + } + + #[test] + fn test_parse_policy_token_path_usd() { + assert_eq!(parse_policy_token("PathUSD").unwrap(), PATH_USD_ADDRESS); + assert_eq!(parse_policy_token("path-usd").unwrap(), PATH_USD_ADDRESS); + } + + #[test] + fn test_parse_period_units() { + assert_eq!(parse_period("0").unwrap(), 0); + assert_eq!(parse_period("30s").unwrap(), 30); + assert_eq!(parse_period("5m").unwrap(), 300); + assert_eq!(parse_period("2h").unwrap(), 7200); + assert_eq!(parse_period("7d").unwrap(), 604800); + assert_eq!(parse_period("2w").unwrap(), 1209600); + assert!(parse_period("1mo").is_err()); + } + + #[test] + fn test_add_selector_rule_merges_recipients() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let second = Address::from_str("0x2222222222222222222222222222222222222222").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![second], + }, + ); + + assert!(changed); + assert_eq!(scope.selectorRules.len(), 1); + assert_eq!(scope.selectorRules[0].recipients, vec![first, second]); + } + + #[test] + fn test_add_selector_rule_empty_recipients_widens_to_any() { + let first = Address::from_str("0x1111111111111111111111111111111111111111").unwrap(); + let mut scope = CallScope { + target: PATH_USD_ADDRESS, + selectorRules: vec![SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![first], + }], + }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("approve").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(changed); + assert!(scope.selectorRules[0].recipients.is_empty()); + } + + #[test] + fn test_add_selector_rule_target_wildcard_is_unchanged() { + let mut scope = CallScope { target: PATH_USD_ADDRESS, selectorRules: vec![] }; + + let changed = add_selector_rule_to_scope( + &mut scope, + SelectorRule { + selector: parse_selector_bytes("transfer").unwrap().into(), + recipients: vec![], + }, + ); + + assert!(!changed); + assert!(scope.selectorRules.is_empty()); + } + + #[test] + fn test_policy_set_limit_parses() { + let key = "0x1111111111111111111111111111111111111111"; + + let command = KeychainSubcommand::try_parse_from([ + "keychain", + "policy", + "set-limit", + key, + "--token", + "PathUSD", + "--amount", + "123", + ]) + .unwrap(); + + match command { + KeychainSubcommand::Policy { + command: + KeychainPolicySubcommand::SetLimit { key_address, token, amount, period, .. }, + } => { + assert_eq!(key_address, Address::from_str(key).unwrap()); + assert_eq!(token, PATH_USD_ADDRESS); + assert_eq!(amount, U256::from(123)); + assert_eq!(period, None); + } + other => panic!("unexpected command: {other:?}"), + } + } + + #[test] + fn test_active_from_anvil_node_info_requires_tempo_network() { + let tempo_t3 = + AnvilNodeInfo { network: Some("tempo".to_string()), hard_fork: Some("T3".to_string()) }; + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T2), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T3), Some(true)); + assert_eq!(active_from_anvil_node_info(&tempo_t3, TempoHardfork::T4), Some(false)); + + let ethereum_t3 = AnvilNodeInfo { + network: Some("ethereum".to_string()), + hard_fork: Some("T3".to_string()), + }; + assert_eq!(active_from_anvil_node_info(ðereum_t3, TempoHardfork::T3), None); + } + + #[test] + fn test_rpc_method_not_found_detection() { + let method_missing: TransportError = + TransportError::ErrorResp(ErrorPayload::method_not_found()); + assert!(is_rpc_method_not_found(&method_missing)); + + let internal_error: TransportError = + TransportError::ErrorResp(ErrorPayload::internal_error()); + assert!(!is_rpc_method_not_found(&internal_error)); + + let transport_error = alloy_transport::TransportErrorKind::backend_gone(); + assert!(!is_rpc_method_not_found(&transport_error)); + } +} diff --git a/crates/cast/src/cmd/logs.rs b/crates/cast/src/cmd/logs.rs index beb9afbef2015..9ce651303e180 100644 --- a/crates/cast/src/cmd/logs.rs +++ b/crates/cast/src/cmd/logs.rs @@ -3,11 +3,14 @@ use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; use alloy_ens::NameOrAddress; use alloy_json_abi::Event; use alloy_network::AnyNetwork; -use alloy_primitives::{hex::FromHex, Address, B256}; +use alloy_primitives::{Address, B256, hex::FromHex}; use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::EthereumOpts, utils, utils::LoadConfig}; +use foundry_cli::{ + opts::RpcOpts, + utils::{self, LoadConfig}, +}; use itertools::Itertools; use std::{io, str::FromStr}; @@ -27,11 +30,8 @@ pub struct LogsArgs { to_block: Option, /// The contract address to filter on. - #[arg( - long, - value_parser = NameOrAddress::from_str - )] - address: Option, + #[arg(long, value_parser = NameOrAddress::from_str)] + address: Option>, /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. @@ -48,22 +48,40 @@ pub struct LogsArgs { #[arg(long)] subscribe: bool, + /// Number of blocks to query in each chunk when the provider has range limits. + /// Defaults to 10000 blocks per chunk. + #[arg(long, default_value_t = 10000)] + query_size: u64, + #[command(flatten)] - eth: EthereumOpts, + rpc: RpcOpts, } impl LogsArgs { pub async fn run(self) -> Result<()> { - let Self { from_block, to_block, address, sig_or_topic, topics_or_args, subscribe, eth } = - self; - - let config = eth.load_config()?; + let Self { + from_block, + to_block, + address, + sig_or_topic, + topics_or_args, + subscribe, + query_size, + rpc, + } = self; + + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); - - let address = match address { - Some(address) => Some(address.resolve(&provider).await?), + let addresses = match address { + Some(addresses) => Some( + futures::future::try_join_all(addresses.into_iter().map(|address| { + let provider = provider.clone(); + async move { address.resolve(&provider).await } + })) + .await?, + ), None => None, }; @@ -72,12 +90,12 @@ impl LogsArgs { let to_block = cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; - let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; + let filter = build_filter(from_block, to_block, addresses, sig_or_topic, topics_or_args)?; if !subscribe { - let logs = cast.filter_logs(filter).await?; + let logs = cast.filter_logs_chunked(filter, query_size).await?; sh_println!("{logs}")?; - return Ok(()) + return Ok(()); } // FIXME: this is a hotfix for @@ -101,7 +119,7 @@ impl LogsArgs { fn build_filter( from_block: Option, to_block: Option, - address: Option
, + address: Option>, sig_or_topic: Option, topics_or_args: Vec, ) -> Result { @@ -223,7 +241,9 @@ mod tests { address: ValueOrArray::Value(address.unwrap()).into(), topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; - let filter = build_filter(from_block, to_block, address, None, vec![]).unwrap(); + let filter = + build_filter(from_block, to_block, address.map(|addr| vec![addr]), None, vec![]) + .unwrap(); assert_eq!(filter, expected) } @@ -270,7 +290,7 @@ mod tests { #[test] fn test_build_filter_sig_with_arguments() { let addr = Address::from_str(ADDRESS).unwrap(); - let addr = U256::from(U160::from_be_bytes(addr.0 .0)); + let addr = U256::from(U160::from_be_bytes(addr.0.0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), @@ -295,7 +315,7 @@ mod tests { #[test] fn test_build_filter_sig_with_skipped_arguments() { let addr = Address::from_str(ADDRESS).unwrap(); - let addr = U256::from(U160::from_be_bytes(addr.0 .0)); + let addr = U256::from(U160::from_be_bytes(addr.0.0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), @@ -365,6 +385,29 @@ mod tests { assert_eq!(filter, expected) } + #[test] + fn test_build_filter_with_multiple_addresses() { + let expected = Filter { + block_option: FilterBlockOption::Range { from_block: None, to_block: None }, + address: vec![Address::ZERO, ADDRESS.parse().unwrap()].into(), + topics: [ + vec![TRANSFER_TOPIC.parse().unwrap()].into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], + }; + let filter = build_filter( + None, + None, + Some(vec![Address::ZERO, ADDRESS.parse().unwrap()]), + Some(TRANSFER_TOPIC.to_string()), + vec![], + ) + .unwrap(); + assert_eq!(filter, expected) + } + #[test] fn test_build_filter_sig_with_mismatched_argument() { let err = build_filter( diff --git a/crates/cast/src/cmd/miner.rs b/crates/cast/src/cmd/miner.rs new file mode 100644 index 0000000000000..3b901235258ae --- /dev/null +++ b/crates/cast/src/cmd/miner.rs @@ -0,0 +1,52 @@ +use alloy_primitives::B256; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + +/// Mines a salt by iterating B256 values in parallel until `check` returns `Some`. +/// +/// Each of the `n_threads` threads starts at `salt + thread_index` and steps by `n_threads`, +/// ensuring non-overlapping coverage. Returns `None` only if all threads panicked. +pub(crate) fn mine_salt(salt: B256, n_threads: usize, check: F) -> Option +where + T: Send + 'static, + F: FnMut(B256) -> Option + Clone + Send + 'static, +{ + let found = Arc::new(AtomicBool::new(false)); + let mut handles = Vec::with_capacity(n_threads); + + for i in 0..n_threads { + let increment = n_threads; + let found = Arc::clone(&found); + let mut check = check.clone(); + + handles.push(std::thread::spawn(move || { + #[repr(C)] +#[repr(C, align(8))] +struct B256Aligned(B256); + let mut salt = B256Aligned(salt, []); + // SAFETY: `B256` is aligned to `usize`. + let salt_word = unsafe { + &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() + }; + // Important: offset by thread index to avoid duplicate work across threads. + *salt_word = salt_word.wrapping_add(i); + + loop { + if found.load(Ordering::Relaxed) { + break None; + } + + if let Some(result) = check(salt.0) { + found.store(true, Ordering::Relaxed); + break Some(result); + } + + *salt_word = salt_word.wrapping_add(increment); + } + })); + } + + handles.into_iter().find_map(|h| h.join().ok().flatten()) +} diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index ebfb5afeadd47..67178cd093d7b 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -1,16 +1,22 @@ use crate::tx::{self, CastTxBuilder}; +use alloy_consensus::{SignableTransaction, Signed}; +use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; -use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; -use alloy_primitives::hex; +use alloy_network::{ + Ethereum, EthereumWallet, Network, NetworkTransactionBuilder, TransactionBuilder, +}; +use alloy_primitives::{Address, hex}; use alloy_provider::Provider; -use alloy_signer::Signer; +use alloy_signer::{Signature, Signer}; use clap::Parser; -use eyre::{OptionExt, Result}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{get_provider, LoadConfig}, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, }; +use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder}; use std::{path::PathBuf, str::FromStr}; +use tempo_alloy::TempoNetwork; /// CLI arguments for `cast mktx`. #[derive(Debug, Parser)] @@ -25,6 +31,7 @@ pub struct MakeTxArgs { sig: Option, /// The arguments of the function to call. + #[arg(allow_negative_numbers = true)] args: Vec, #[command(subcommand)] @@ -49,7 +56,7 @@ pub struct MakeTxArgs { /// Generate a raw RLP-encoded unsigned transaction. /// /// Relaxes the wallet requirement. - #[arg(long, requires = "from")] + #[arg(long)] raw_unsigned: bool, /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender @@ -69,13 +76,33 @@ pub enum MakeTxSubcommands { sig: Option, /// The constructor arguments. + #[arg(allow_negative_numbers = true)] args: Vec, }, } impl MakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; + if self.tx.tempo.is_tempo() { + self.run_generic::().await + } else { + self.run_generic::().await + } + } + + pub async fn run_generic(self) -> Result<()> + where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: FoundryTransactionBuilder, + { + let Self { to, mut sig, mut args, command, mut tx, path, eth, raw_unsigned, ethsign } = + self; + + let print_sponsor_hash = tx.tempo.print_sponsor_hash; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -94,9 +121,14 @@ impl MakeTxArgs { let config = eth.load_config()?; - let provider = get_provider(&config)?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `tx.tempo.nonce_key` from the lane. + // Must happen before `tx.clone()` so the cloned tx carries the resolved nonce_key. + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; - let tx_builder = CastTxBuilder::new(&provider, tx, &config) + let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) .await? .with_to(to) .await? @@ -104,10 +136,48 @@ impl MakeTxArgs { .await? .with_blob_data(blob_data)?; + // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. + if print_sponsor_hash { + // Resolve the signer to derive the actual sender address, since the + // sponsor hash commits to the sender. + let signer = eth.wallet.signer().await?; + let from = signer.address(); + let (tx, _) = tx_builder.build(from).await?; + let hash = tx.compute_sponsor_hash(from).ok_or_else(|| { + eyre::eyre!("This network does not support sponsored transactions") + })?; + sh_println!("{hash:?}")?; + return Ok(()); + } + + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + if raw_unsigned { // Build unsigned raw tx - let from = eth.wallet.from.ok_or_eyre("missing `--from` address")?; - let raw_tx = tx_builder.build_unsigned_raw(from).await?; + // Check if nonce is provided when --from is not specified + // See: + if eth.wallet.from.is_none() && tx.nonce.is_none() { + eyre::bail!( + "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" + ); + } + if tempo_sponsor.is_some() && eth.wallet.from.is_none() { + eyre::bail!( + "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender" + ); + } + + // Use zero address as placeholder for unsigned transactions + let from = eth.wallet.from.unwrap_or(Address::ZERO); + + let (mut tx, _) = tx_builder.build(from).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } + let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; return Ok(()); @@ -116,7 +186,11 @@ impl MakeTxArgs { if ethsign { // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has // unlocked accounts. - let (tx, _) = tx_builder.build(config.sender).await?; + let (mut tx, _) = tx_builder.build(config.sender).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, config.sender).await?; + } let signed_tx = provider.sign_transaction(tx).await?; sh_println!("{signed_tx}")?; @@ -130,7 +204,11 @@ impl MakeTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = tx_builder.build(&signer).await?; + let (mut tx, _) = tx_builder.build(&signer).await?; + maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx, from).await?; + } let tx = tx.build(&EthereumWallet::new(signer)).await?; diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 482cb77b4e343..0b1b26615694a 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -7,20 +7,31 @@ pub mod access_list; pub mod artifact; +pub mod b2e_payload; +pub mod batch_mktx; +pub mod batch_send; pub mod bind; pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; +#[cfg(feature = "optimism")] pub mod da_estimate; +pub mod erc20; pub mod estimate; pub mod find_block; pub mod interface; +pub mod keychain; pub mod logs; +pub(crate) mod miner; pub mod mktx; pub mod rpc; pub mod run; pub mod send; pub mod storage; +pub mod tempo; +pub mod tip20; +pub mod trace; pub mod txpool; +pub mod vaddr; pub mod wallet; diff --git a/crates/cast/src/cmd/rpc.rs b/crates/cast/src/cmd/rpc.rs index 22ac6f43512cd..8883c3fbb5be2 100644 --- a/crates/cast/src/cmd/rpc.rs +++ b/crates/cast/src/cmd/rpc.rs @@ -2,6 +2,7 @@ use crate::Cast; use clap::Parser; use eyre::Result; use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; +use foundry_common::shell; use itertools::Itertools; /// CLI arguments for `cast rpc`. @@ -37,7 +38,6 @@ impl RpcArgs { let Self { raw, method, params, rpc } = self; let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; let params = if raw { if params.is_empty() { @@ -52,7 +52,15 @@ impl RpcArgs { } else { serde_json::Value::Array(params.into_iter().map(value_or_string).collect()) }; - sh_println!("{}", Cast::new(provider).rpc(&method, params).await?)?; + + let provider = utils::get_provider(&config)?; + let result = Cast::new(provider).rpc(&method, params).await?; + if shell::is_json() { + let result: serde_json::Value = serde_json::from_str(&result)?; + sh_println!("{}", serde_json::to_string_pretty(&result)?)?; + } else { + sh_println!("{}", result)?; + } Ok(()) } } diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index c586cf4c9ded4..7e52a9e265f25 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -1,33 +1,48 @@ -use alloy_consensus::Transaction; -use alloy_network::{AnyNetwork, TransactionResponse}; +use crate::{ + debug::handle_traces, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, +}; +use alloy_consensus::{BlockHeader, Transaction, transaction::SignerRecoverable}; + +use alloy_evm::FromRecoveredTx; +use alloy_network::{BlockResponse, TransactionResponse}; +use alloy_primitives::{ + Address, Bytes, U256, + map::{AddressSet, HashMap}, +}; use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{handle_traces, init_progress, TraceResult}, + utils::{TraceResult, init_progress}, +}; +use foundry_common::{ + SYSTEM_TRANSACTION_TYPE, is_known_system_sender, provider::ProviderBuilder, shell, }; -use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ + Config, figment::{ - self, + self, Metadata, Profile, value::{Dict, Map}, - Figment, Metadata, Profile, }, - Config, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ - executors::{EvmError, TracingExecutor}, + core::{ + FoundryBlock as _, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, + }, + executors::{EvmError, Executor, TracingExecutor}, + hardforks::FoundryHardfork, opts::EvmOpts, - traces::{InternalTraceMode, TraceMode}, - utils::configure_tx_env, - Env, + traces::{InternalTraceMode, TraceMode, Traces}, }; -use foundry_evm_core::env::AsEnvMut; - -use crate::utils::apply_chain_and_block_specific_env_changes; +use futures::TryFutureExt; +use revm::{DatabaseRef, context::Block}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -43,6 +58,10 @@ pub struct RunArgs { #[arg(long)] decode_internal: bool, + /// Defines the depth of a trace + #[arg(long)] + trace_depth: Option, + /// Print out opcode traces. #[arg(long, short)] trace_printer: bool, @@ -53,6 +72,14 @@ pub struct RunArgs { #[arg(long)] quick: bool, + /// Whether to replay system transactions. + #[arg(long, alias = "sys")] + replay_system_txes: bool, + + /// Disables the labels in the traces. + #[arg(long, default_value_t = false)] + disable_labels: bool, + /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth @@ -71,26 +98,6 @@ pub struct RunArgs { #[arg(long)] evm_version: Option, - /// Sets the number of assumed available compute units per second for this provider - /// - /// default value: 330 - /// - /// See also, - #[arg(long, alias = "cups", value_name = "CUPS")] - pub compute_units_per_second: Option, - - /// Disables rate limiting for this node's provider. - /// - /// default value: false - /// - /// See also, - #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] - pub no_rate_limit: bool, - - /// Enables Odyssey features. - #[arg(long, alias = "alphanet")] - pub odyssey: bool, - /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] pub with_local_artifacts: bool, @@ -98,6 +105,10 @@ pub struct RunArgs { /// Disable block gas limit check. #[arg(long)] pub disable_block_gas_limit: bool, + + /// Enable the tx gas limit checks as imposed by Osaka (EIP-7825). + #[arg(long)] + pub enable_tx_gas_limit: bool, } impl RunArgs { @@ -107,14 +118,41 @@ impl RunArgs { /// /// Note: This executes the transaction(s) as is: Cheatcodes are disabled pub async fn run(self) -> Result<()> { - let figment = Into::::into(&self.rpc).merge(&self); + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); + let mut evm_opts = figment.extract::()?; + + // Auto-detect network from fork chain ID when not explicitly configured. + evm_opts.infer_network_from_fork().await; + + if evm_opts.networks.is_tempo() { + return self.run_with_evm::().await; + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_with_evm::().await; + } + + self.run_with_evm::().await + } + + async fn run_with_evm(self) -> Result<()> { + let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::from_provider(figment)?.sanitized(); - let compute_units_per_second = - if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; + let label = self.label; + let with_local_artifacts = self.with_local_artifacts; + let debug = self.debug; + let decode_internal = self.decode_internal; + let disable_labels = self.disable_labels; + let compute_units_per_second = if self.rpc.common.no_rpc_rate_limit { + Some(u64::MAX) + } else { + self.rpc.common.compute_units_per_second + }; - let provider = foundry_cli::utils::get_provider_builder(&config)? + let provider = ProviderBuilder::::from_config(&config)? .compute_units_per_second_opt(compute_units_per_second) .build()?; @@ -126,8 +164,9 @@ impl RunArgs { .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; // check if the tx is a system transaction - if is_known_system_sender(tx.from()) || - tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + if !self.replay_system_txes + && (is_known_system_sender(tx.from()) + || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)) { return Err(eyre::eyre!( "{:?} is a system transaction.\nReplaying system transactions is currently not supported.", @@ -135,40 +174,55 @@ impl RunArgs { )); } - let tx_block_number = - tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; - - // fetch the block the transaction was mined in - let block = provider.get_block(tx_block_number.into()).full().await?; + let tx_block_number = tx + .block_number() + .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, odyssey) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; + let (block, (mut evm_env, tx_env, fork, chain, networks)) = tokio::try_join!( + // fetch the block the transaction was mined in + provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into), + TracingExecutor::::get_fork_material(&mut config, evm_opts) + )?; + let mut evm_version = self.evm_version; - env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; - env.evm_env.block_env.number = tx_block_number; + evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; + + // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825). + // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true. + if !self.enable_tx_gas_limit { + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + } + + evm_env.cfg_env.limit_contract_code_size = None; + evm_env.block_env.set_number(U256::from(tx_block_number)); if let Some(block) = &block { - env.evm_env.block_env.timestamp = block.header.timestamp; - env.evm_env.block_env.beneficiary = block.header.beneficiary; - env.evm_env.block_env.difficulty = block.header.difficulty; - env.evm_env.block_env.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.evm_env.block_env.gas_limit = block.header.gas_limit; - - // TODO: we need a smarter way to map the block to the corresponding evm_version for - // commonly used chains + evm_env.block_env = block_env_from_header(block.header()); + + // Resolve the correct spec for the block using the same approach as reth: walk + // known chain activation conditions to find the latest active fork. Falls back + // to a blob-gas heuristic for unknown chains. if evm_version.is_none() { - // if the block has the excess_blob_gas field, we assume it's a Cancun block - if block.header.excess_blob_gas.is_some() { + if let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp( + evm_env.cfg_env.chain_id, + block.header().timestamp(), + ) { + evm_env.cfg_env.set_spec_and_mainnet_gas_params(hardfork.into()); + } else if block.header().excess_blob_gas().is_some() { + // TODO: add glamsterdam header field checks in the future evm_version = Some(EvmVersion::Cancun); } } - apply_chain_and_block_specific_env_changes::(env.as_env_mut(), block); + apply_chain_and_block_specific_env_changes::( + &mut evm_env, + block, + config.networks, + ); } let trace_mode = TraceMode::Call @@ -179,20 +233,17 @@ impl RunArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::new( - env.clone(), + let mut executor = TracingExecutor::::new( + (evm_env.clone(), tx_env), fork, evm_version, trace_mode, - odyssey, + networks, create2_deployer, + None, )?; - let mut env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx.clone(), - executor.spec_id(), - ); + + evm_env.cfg_env.set_spec_and_mainnet_gas_params(executor.spec_id()); // Set the state to the moment right before the transaction if !self.quick { @@ -201,19 +252,20 @@ impl RunArgs { } if let Some(block) = block { - let pb = init_progress(block.transactions.len() as u64, "tx"); + let pb = init_progress(block.transactions().len() as u64, "tx"); pb.set_position(0); - let BlockTransactions::Full(ref txs) = block.transactions else { - return Err(eyre::eyre!("Could not get block txs")) + let BlockTransactions::Full(ref txs) = *block.transactions() else { + return Err(eyre::eyre!("Could not get block txs")); }; for (index, tx) in txs.iter().enumerate() { - // System transactions such as on L2s don't contain any pricing info so - // we skip them otherwise this would cause - // reverts - if is_known_system_sender(tx.from()) || - tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + // Replay system transactions only if running with `sys` option. + // System transactions such as on L2s don't contain any pricing info so it + // could cause reverts. + if !self.replay_system_txes + && (is_known_system_sender(tx.from()) + || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)) { pb.set_position((index + 1) as u64); continue; @@ -222,20 +274,26 @@ impl RunArgs { break; } - configure_tx_env(&mut env.as_env_mut(), &tx.inner); + let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); + + evm_env.cfg_env.disable_balance_check = true; if let Some(to) = Transaction::to(tx) { trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); - executor.transact_with_env(env.clone()).wrap_err_with(|| { - format!( - "Failed to execute transaction: {:?} in block {}", - tx.tx_hash(), - env.evm_env.block_env.number - ) - })?; + executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( + || { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + evm_env.block_env.number() + ) + }, + )?; } else { trace!(tx=?tx.tx_hash(), "executing previous create transaction"); - if let Err(error) = executor.deploy_with_env(env.clone(), None) { + if let Err(error) = + executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) + { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), @@ -244,9 +302,9 @@ impl RunArgs { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), - env.evm_env.block_env.number + evm_env.block_env.number() ) - }) + }); } } } @@ -261,25 +319,33 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env.as_env_mut(), &tx.inner); + let tx_env = TxEnvFor::::from_recovered_tx(tx.as_ref(), tx.from()); + + if tx.as_ref().recover_signer().is_ok_and(|signer| signer != tx.from()) { + evm_env.cfg_env.disable_balance_check = true; + } if let Some(to) = Transaction::to(&tx) { trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); - TraceResult::try_from(executor.transact_with_env(env))? + TraceResult::from(executor.transact_with_env(evm_env, tx_env)?) } else { trace!(tx=?tx.tx_hash(), "executing create transaction"); - TraceResult::try_from(executor.deploy_with_env(env, None))? + TraceResult::try_from(executor.deploy_with_env(evm_env, tx_env, None))? } }; + let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &result)?; handle_traces( result, &config, chain, - self.label, - self.with_local_artifacts, - self.debug, - self.decode_internal, + &contracts_bytecode, + label, + with_local_artifacts, + debug, + decode_internal, + disable_labels, + self.trace_depth, ) .await?; @@ -287,6 +353,45 @@ impl RunArgs { } } +pub fn fetch_contracts_bytecode_from_trace( + executor: &Executor, + result: &TraceResult, +) -> Result> { + let mut contracts_bytecode = HashMap::default(); + if let Some(ref traces) = result.traces { + contracts_bytecode.extend(gather_trace_addresses(traces).filter_map(|addr| { + // All relevant bytecodes should already be cached in the executor. + let code = executor + .backend() + .basic_ref(addr) + .inspect_err(|e| _ = sh_warn!("Failed to fetch code for {addr}: {e}")) + .ok()?? + .code? + .bytes(); + if code.is_empty() { + return None; + } + Some((addr, code)) + })); + } + Ok(contracts_bytecode) +} + +fn gather_trace_addresses(traces: &Traces) -> impl Iterator { + let mut addresses = AddressSet::default(); + for (_, trace) in traces { + for node in trace.arena.nodes() { + if !node.trace.address.is_zero() { + addresses.insert(node.trace.address); + } + if !node.trace.caller.is_zero() { + addresses.insert(node.trace.caller); + } + } + } + addresses.into_iter() +} + impl figment::Provider for RunArgs { fn metadata(&self) -> Metadata { Metadata::named("RunArgs") @@ -295,18 +400,10 @@ impl figment::Provider for RunArgs { fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); - if self.odyssey { - map.insert("odyssey".into(), self.odyssey.into()); - } - if let Some(api_key) = &self.etherscan.key { map.insert("etherscan_api_key".into(), api_key.as_str().into()); } - if let Some(api_version) = &self.etherscan.api_version { - map.insert("etherscan_api_version".into(), api_version.to_string().into()); - } - if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 500c65f6723c2..421aae1f2153e 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -1,21 +1,31 @@ -use crate::{ - tx::{self, CastTxBuilder}, - Cast, -}; +use std::{path::PathBuf, str::FromStr, time::Duration}; + +use alloy_consensus::{SignableTransaction, Signed}; use alloy_ens::NameOrAddress; -use alloy_network::{AnyNetwork, EthereumWallet}; -use alloy_provider::{Provider, ProviderBuilder}; -use alloy_rpc_types::TransactionRequest; -use alloy_serde::WithOtherFields; -use alloy_signer::Signer; +use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder}; +use alloy_primitives::Address; +use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_signer::{Signature, Signer}; use clap::Parser; -use eyre::{eyre, Result}; +use eyre::{Result, eyre}; use foundry_cli::{ - opts::{EthereumOpts, TransactionOpts}, - utils, - utils::LoadConfig, + opts::TransactionOpts, + utils::{LoadConfig, maybe_print_resolved_lane, resolve_lane}, +}; +use foundry_common::{ + FoundryTransactionBuilder, + fmt::{UIfmt, UIfmtReceiptExt}, + provider::ProviderBuilder, + tempo::TEMPO_BROWSER_GAS_BUFFER, +}; +use foundry_wallets::{TempoAccessKeyConfig, WalletSigner}; +use tempo_alloy::TempoNetwork; + +use crate::{ + cmd::tip20::iso4217_warning_message, + tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}, }; -use std::{path::PathBuf, str::FromStr}; +use tempo_contracts::precompiles::{TIP20_FACTORY_ADDRESS, is_iso4217_currency}; /// CLI arguments for `cast send`. #[derive(Debug, Parser)] @@ -30,15 +40,18 @@ pub struct SendTxArgs { sig: Option, /// The arguments of the function to call. + #[arg(allow_negative_numbers = true)] args: Vec, - /// Only print the transaction hash and exit immediately. - #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] - cast_async: bool, + /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. + #[arg( + long, + conflicts_with_all = &["sig", "args"] + )] + data: Option, - /// The number of confirmations until the receipt is fetched. - #[arg(long, default_value = "1")] - confirmations: u64, + #[command(flatten)] + send_tx: SendTxOpts, #[command(subcommand)] command: Option, @@ -47,16 +60,13 @@ pub struct SendTxArgs { #[arg(long, requires = "from")] unlocked: bool, - /// Timeout for sending the transaction. - #[arg(long, env = "ETH_TIMEOUT")] - pub timeout: Option, + /// Skip confirmation prompts (e.g. non-ISO 4217 currency warnings). + #[arg(long)] + force: bool, #[command(flatten)] tx: TransactionOpts, - #[command(flatten)] - eth: EthereumOpts, - /// The path of blob data to be sent. #[arg( long, @@ -80,29 +90,48 @@ pub enum SendTxSubcommands { sig: Option, /// The arguments of the function to call. + #[arg(allow_negative_numbers = true)] args: Vec, }, } impl SendTxArgs { - #[expect(dependency_on_unit_never_type_fallback)] - pub async fn run(self) -> eyre::Result<()> { - let Self { - eth, - to, - mut sig, - cast_async, - mut args, - tx, - confirmations, - command, - unlocked, - path, - timeout, - } = self; + pub async fn run(self) -> Result<()> { + // Resolve the signer early so we know if it's a Tempo access key. + let (signer, tempo_access_key) = self.send_tx.eth.wallet.maybe_signer().await?; + + if tempo_access_key.is_some() || self.tx.tempo.is_tempo() { + self.run_generic::(signer, tempo_access_key).await + } else { + self.run_generic::(signer, None).await + } + } + + pub async fn run_generic( + self, + pre_resolved_signer: Option, + access_key: Option, + ) -> Result<()> + where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, + { + let Self { to, mut sig, mut args, data, send_tx, mut tx, command, unlocked, force, path } = + self; + + let print_sponsor_hash = tx.tempo.print_sponsor_hash; + let expires_at = tx.tempo.resolve_expires(); + let tempo_sponsor = + if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? }; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; + if let Some(data) = data { + sig = Some(data); + } + let code = if let Some(SendTxSubcommands::Create { code, sig: constructor_sig, @@ -111,13 +140,17 @@ impl SendTxArgs { { // ensure we don't violate settings for transactions that can't be CREATE: 7702 and 4844 // which require mandatory target - if to.is_none() && tx.auth.is_some() { - return Err(eyre!("EIP-7702 transactions can't be CREATE transactions and require a destination address")); + if to.is_none() && !tx.auth.is_empty() { + return Err(eyre!( + "EIP-7702 transactions can't be CREATE transactions and require a destination address" + )); } // ensure we don't violate settings for transactions that can't be CREATE: 7702 and 4844 // which require mandatory target if to.is_none() && blob_data.is_some() { - return Err(eyre!("EIP-4844 transactions can't be CREATE transactions and require a destination address")); + return Err(eyre!( + "EIP-4844 transactions can't be CREATE transactions and require a destination address" + )); } sig = constructor_sig; @@ -127,8 +160,44 @@ impl SendTxArgs { None }; - let config = eth.load_config()?; - let provider = utils::get_provider(&config)?; + // Validate ISO 4217 currency code for TIP20Factory createToken calls. + if let Some(ref to_addr) = to { + let is_factory = match to_addr { + NameOrAddress::Address(addr) => *addr == TIP20_FACTORY_ADDRESS, + NameOrAddress::Name(name) => { + Address::from_str(name).ok() == Some(TIP20_FACTORY_ADDRESS) + } + }; + + if !force + && is_factory + && let Some(ref sig_str) = sig + && sig_str.starts_with("createToken") + && let Some(currency) = args.get(2) + && !is_iso4217_currency(currency) + { + sh_warn!("{}", iso4217_warning_message(currency))?; + let response: String = foundry_common::prompt!("\nContinue anyway? [y/N] ")?; + if !matches!(response.trim(), "y" | "Y") { + sh_println!("Aborted.")?; + return Ok(()); + } + } + } + + let config = send_tx.eth.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?; + + if let Some(interval) = send_tx.poll_interval { + provider.client().set_poll_interval(Duration::from_secs(interval)) + } + + // Inject access key ID into TempoOpts so it's set before gas estimation. + if let Some(ref ak) = access_key { + tx.tempo.key_id = Some(ak.key_address); + } let builder = CastTxBuilder::new(&provider, tx, &config) .await? @@ -138,13 +207,42 @@ impl SendTxArgs { .await? .with_blob_data(blob_data)?; - let timeout = timeout.unwrap_or(config.transaction_timeout); + // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. + if print_sponsor_hash { + let (tx, from) = if let Some(ref ak) = access_key { + let (tx, _) = builder.build_with_access_key(ak.wallet_address, ak).await?; + (tx, ak.wallet_address) + } else { + // Use the pre-resolved signer to derive the actual sender address, since the + // sponsor hash commits to the sender. + let signer = pre_resolved_signer.as_ref().ok_or_else(|| { + eyre!("--tempo.print-sponsor-hash requires a signer (e.g. --private-key)") + })?; + let from = signer.address(); + let (tx, _) = builder.build(from).await?; + (tx, from) + }; + let hash = tx + .compute_sponsor_hash(from) + .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?; + sh_println!("{hash:?}")?; + return Ok(()); + } + + if let Some(ts) = expires_at { + sh_println!("Transaction expires at unix timestamp {ts}")?; + } + + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + + // Launch browser signer if `--browser` flag is set + let browser = send_tx.browser.run::().await?; // Case 1: // Default to sending via eth_sendTransaction if the --unlocked flag is passed. // This should be the only way this RPC method is used as it requires a local node // or remote RPC with unlocked accounts. - if unlocked { + if unlocked && browser.is_none() { // only check current chain id if it was specified in the config if let Some(config_chain) = config.chain { let current_chain_id = provider.get_chain_id().await?; @@ -154,7 +252,7 @@ impl SendTxArgs { if config_chain_id != current_chain_id { sh_warn!("Switching to chain {}", config_chain)?; provider - .raw_request( + .raw_request::<_, ()>( "wallet_switchEthereumChain".into(), [serde_json::json!({ "chainId": format!("0x{:x}", config_chain_id), @@ -164,51 +262,174 @@ impl SendTxArgs { } } - let (tx, _) = builder.build(config.sender).await?; + let (mut tx_request, _) = builder.build(config.sender).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, config.sender).await?; + } - cast_send(provider, tx, cast_async, confirmations, timeout).await + cast_send( + provider, + tx_request, + send_tx.cast_async, + send_tx.sync, + send_tx.confirmations, + timeout, + ) + .await // Case 2: + // Browser wallet signs and sends the transaction in one step. + } else if let Some(browser) = browser { + let chain = builder.chain(); + let (mut tx_request, _) = builder.build(browser.address()).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if chain.is_tempo() + && let Some(gas) = tx_request.gas_limit() + { + tx_request.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER); + } + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, browser.address()).await?; + } + + let tx_hash = browser.send_transaction_via_browser(tx_request).await?; + + let cast = CastTxSender::new(&provider); + cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await + // Case 3: + // Tempo access key (keychain) signing. Uses `sign_with_access_key` which + // handles the provisioning check and embeds `key_authorization` when needed. + } else if let Some(ak) = access_key { + let signer = match pre_resolved_signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; + let (mut tx_request, _) = builder.build_with_access_key(ak.wallet_address, &ak).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, ak.wallet_address).await?; + } + cast_send_with_access_key( + &provider, + tx_request, + &signer, + &ak, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await + // Case 4: // An option to use a local signer was provided. // If we cannot successfully instantiate a local signer, then we will assume we don't have // enough information to sign and we must bail. } else { - // Retrieve the signer, and bail if it can't be constructed. - let signer = eth.wallet.signer().await?; + let signer = match pre_resolved_signer { + Some(s) => s, + None => send_tx.eth.wallet.signer().await?, + }; let from = signer.address(); - tx::validate_from_address(eth.wallet.from, from)?; + tx::validate_from_address(send_tx.eth.wallet.from, from)?; - let (tx, _) = builder.build(&signer).await?; + let (mut tx_request, _) = builder.build(&signer).await?; + maybe_print_resolved_lane( + resolved_lane.as_ref(), + tx_request.nonce().unwrap_or_default(), + )?; + + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut tx_request, from).await?; + } let wallet = EthereumWallet::from(signer); - let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .wallet(wallet) .connect_provider(&provider); - cast_send(provider, tx, cast_async, confirmations, timeout).await + cast_send( + provider, + tx_request, + send_tx.cast_async, + send_tx.sync, + send_tx.confirmations, + timeout, + ) + .await } } } -async fn cast_send>( +pub(crate) async fn cast_send>( provider: P, - tx: WithOtherFields, + tx: N::TransactionRequest, cast_async: bool, + sync: bool, confs: u64, timeout: u64, -) -> Result<()> { - let cast = Cast::new(provider); - let pending_tx = cast.send(tx).await?; - - let tx_hash = pending_tx.inner().tx_hash(); - - if cast_async { - sh_println!("{tx_hash:#x}")?; - } else { - let receipt = - cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?; +) -> Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, +{ + let cast = CastTxSender::new(provider); + + if sync { + // Send transaction and wait for receipt synchronously + let receipt = cast.send_sync(tx).await?; sh_println!("{receipt}")?; + } else { + let pending_tx = cast.send(tx).await?; + let tx_hash = *pending_tx.inner().tx_hash(); + cast.print_tx_result(tx_hash, cast_async, confs, timeout).await?; } Ok(()) } + +/// Signs a transaction with a Tempo access key and sends it via `send_raw_transaction`. +/// +/// Sets `from` and `key_id` on the transaction before signing, making it idempotent for txs built +/// with [`CastTxBuilder`] (fields already set) and also with sol!-bindings (fields not yet set). +/// +/// NOTE: The default implementation returns an error. Only `TempoNetwork` supports this. +pub(crate) async fn cast_send_with_access_key>( + provider: &P, + mut tx: N::TransactionRequest, + signer: &WalletSigner, + access_key: &TempoAccessKeyConfig, + cast_async: bool, + confirmations: u64, + timeout: u64, +) -> Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, +{ + tx.set_from(access_key.wallet_address); + tx.set_key_id(access_key.key_address); + let raw_tx = tx + .sign_with_access_key( + provider, + signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); + CastTxSender::new(provider).print_tx_result(tx_hash, cast_async, confirmations, timeout).await +} diff --git a/crates/cast/src/cmd/storage.rs b/crates/cast/src/cmd/storage.rs index 1bd3cf1468ebc..df5cbae1c935c 100644 --- a/crates/cast/src/cmd/storage.rs +++ b/crates/cast/src/cmd/storage.rs @@ -1,13 +1,12 @@ -use crate::{opts::parse_slot, Cast}; +use crate::{Cast, opts::parse_slot}; use alloy_ens::NameOrAddress; use alloy_network::AnyNetwork; use alloy_primitives::{Address, B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; +use comfy_table::{Cell, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; use eyre::Result; -use foundry_block_explorers::Client; use foundry_cli::{ opts::{BuildOpts, EtherscanOpts, RpcOpts}, utils, @@ -15,20 +14,21 @@ use foundry_cli::{ }; use foundry_common::{ abi::find_source, - compile::{etherscan_project, ProjectCompiler}, + compile::{ProjectCompiler, etherscan_project}, shell, }; use foundry_compilers::{ + Artifact, Project, artifacts::{ConfigurableContractArtifact, Contract, StorageLayout}, compilers::{ - solc::{Solc, SolcCompiler}, Compiler, + solc::{Solc, SolcCompiler}, }, - Artifact, Project, }; use foundry_config::{ - figment::{self, value::Dict, Metadata, Profile}, - impl_figment_convert_cast, Config, + Config, + figment::{self, Metadata, Profile, value::Dict}, + impl_figment_convert_cast, }; use semver::Version; use serde::{Deserialize, Serialize}; @@ -48,7 +48,11 @@ pub struct StorageArgs { /// The storage slot number. If not provided, it gets the full storage layout. #[arg(value_parser = parse_slot)] - slot: Option, + base_slot: Option, + + /// The storage offset from the base slot. If not provided, it is assumed to be zero. + #[arg(value_parser = str::parse::, default_value_t = U256::ZERO)] + offset: U256, /// The known proxy address. If provided, the storage layout is retrieved from this address. #[arg(long,value_parser = NameOrAddress::from_str)] @@ -68,6 +72,10 @@ pub struct StorageArgs { #[command(flatten)] build: BuildOpts, + + /// Specify the solc version to compile with. Overrides detected version. + #[arg(long, value_parser = Version::parse)] + solc_version: Option, } impl_figment_convert_cast!(StorageArgs); @@ -90,14 +98,22 @@ impl StorageArgs { pub async fn run(self) -> Result<()> { let config = self.load_config()?; - let Self { address, slot, block, build, .. } = self; + let Self { address, base_slot, offset, block, build, .. } = self; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; // Slot was provided, perform a simple RPC call - if let Some(slot) = slot { + if let Some(slot) = base_slot { let cast = Cast::new(provider); - sh_println!("{}", cast.storage(address, slot, block).await?)?; + sh_println!( + "{}", + cast.storage( + address, + (Into::::into(slot).saturating_add(offset)).into(), + block + ) + .await? + )?; return Ok(()); } @@ -119,25 +135,23 @@ impl StorageArgs { artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage( - provider, - address, - block, - artifact, - !shell::is_json(), - ) - .await; + return fetch_and_print_storage(provider, address, block, artifact).await; } } - if !self.etherscan.has_key() { - eyre::bail!("You must provide an Etherscan API key if you're fetching a remote contract's storage."); - } - let chain = utils::get_chain(config.chain, &provider).await?; - let api_version = config.get_etherscan_api_version(Some(chain)); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new_with_api_version(chain, api_key, api_version)?; + let etherscan_api_key = self.etherscan.key(); + let client = match config.get_etherscan_config_with_chain(Some(chain))? { + Some(etherscan_config) => { + etherscan_config.into_client_with_no_proxy(config.eth_rpc_no_proxy)? + } + None => { + let api_key = etherscan_api_key.ok_or_else(|| { + eyre::eyre!("You must provide an Etherscan API key if you're fetching a remote contract's storage.") + })?; + foundry_block_explorers::Client::new(chain, api_key)? + } + }; let source = if let Some(proxy) = self.proxy { find_source(client, proxy.resolve(&provider).await?).await? } else { @@ -148,21 +162,49 @@ impl StorageArgs { eyre::bail!("Contract at provided address is not a valid Solidity contract") } - let version = metadata.compiler_version()?; - let auto_detect = version < MIN_SOLC; - - // Create a new temp project - // TODO: Cache instead of using a temp directory: metadata from Etherscan won't change - let root = tempfile::tempdir()?; - let root_path = root.path(); - let mut project = etherscan_project(metadata, root_path)?; + // Create or reuse a persistent cache for Etherscan sources; fall back to a temp dir + let mut temp_dir = None; + let root_path = if let Some(cache_root) = + foundry_config::Config::foundry_etherscan_chain_cache_dir(chain) + { + let sources_root = cache_root.join("sources"); + let contract_root = sources_root.join(format!("{address}")); + if let Err(err) = std::fs::create_dir_all(&contract_root) { + sh_warn!("Could not create etherscan cache dir, falling back to temp: {err}")?; + let tmp = tempfile::tempdir()?; + let path = tmp.path().to_path_buf(); + temp_dir = Some(tmp); + path + } else { + contract_root + } + } else { + let tmp = tempfile::tempdir()?; + let path = tmp.path().to_path_buf(); + temp_dir = Some(tmp); + path + }; + let mut project = etherscan_project(metadata, &root_path)?; add_storage_layout_output(&mut project); - project.compiler = if auto_detect { + // Decide on compiler to use (user override -> metadata -> autodetect) + let meta_version = metadata.compiler_version()?; + let mut auto_detect = false; + let desired = if let Some(user_version) = self.solc_version { + if user_version < MIN_SOLC { + sh_warn!( + "The provided --solc-version is {user_version} while the minimum version for \ + storage layouts is {MIN_SOLC} and as a result the output may be empty." + )?; + } + SolcCompiler::Specific(Solc::find_or_install(&user_version)?) + } else if meta_version < MIN_SOLC { + auto_detect = true; SolcCompiler::AutoDetect } else { - SolcCompiler::Specific(Solc::find_or_install(&version)?) + SolcCompiler::Specific(Solc::find_or_install(&meta_version)?) }; + project.compiler.solc = Some(desired); // Compile let mut out = ProjectCompiler::new().quiet(true).compile(&project)?; @@ -172,11 +214,14 @@ impl StorageArgs { .find(|(name, _)| name == &metadata.contract_name) .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; - if is_storage_layout_empty(&artifact.storage_layout) && auto_detect { + if auto_detect && is_storage_layout_empty(&artifact.storage_layout) { // try recompiling with the minimum version - sh_warn!("The requested contract was compiled with {version} while the minimum version for storage layouts is {MIN_SOLC} and as a result the output may be empty.")?; + sh_warn!( + "The requested contract was compiled with {meta_version} while the minimum version \ + for storage layouts is {MIN_SOLC} and as a result the output may be empty.", + )?; let solc = Solc::find_or_install(&MIN_SOLC)?; - project.compiler = SolcCompiler::Specific(solc); + project.compiler.solc = Some(SolcCompiler::Specific(solc)); if let Ok(output) = ProjectCompiler::new().quiet(true).compile(&project) { out = output; let (_, new_artifact) = out @@ -190,10 +235,8 @@ impl StorageArgs { artifact }; - // Clear temp directory - root.close()?; - - fetch_and_print_storage(provider, address, block, artifact, !shell::is_json()).await + drop(temp_dir); + fetch_and_print_storage(provider, address, block, artifact).await } } @@ -241,7 +284,6 @@ async fn fetch_and_print_storage>( address: Address, block: Option, artifact: &ConfigurableContractArtifact, - pretty: bool, ) -> Result<()> { if is_storage_layout_empty(&artifact.storage_layout) { sh_warn!("Storage layout is empty.")?; @@ -249,7 +291,7 @@ async fn fetch_and_print_storage>( } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); let values = fetch_storage_slots(provider, address, block, &layout).await?; - print_storage(layout, values, pretty) + print_storage(layout, values) } } @@ -274,8 +316,8 @@ async fn fetch_storage_slots>( futures::future::try_join_all(requests).await } -fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { - if !pretty { +fn print_storage(layout: StorageLayout, values: Vec) -> Result<()> { + if shell::is_json() { let values: Vec<_> = layout .storage .iter() @@ -296,7 +338,11 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) } let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(vec![ Cell::new("Name"), @@ -335,20 +381,16 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) fn add_storage_layout_output>(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; project.update_output_selection(|selection| { - selection.0.values_mut().for_each(|contract_selection| { - contract_selection - .values_mut() - .for_each(|selection| selection.push("storageLayout".to_string())) - }); + for contract_selection in selection.0.values_mut() { + for selection in contract_selection.values_mut() { + selection.push("storageLayout".to_string()); + } + } }) } -fn is_storage_layout_empty(storage_layout: &Option) -> bool { - if let Some(ref s) = storage_layout { - s.storage.is_empty() - } else { - true - } +const fn is_storage_layout_empty(storage_layout: &Option) -> bool { + if let Some(s) = storage_layout { s.storage.is_empty() } else { true } } #[cfg(test)] @@ -361,12 +403,22 @@ mod tests { StorageArgs::parse_from(["foundry-cli", "addr.eth", "--etherscan-api-key", "dummykey"]); assert_eq!(args.etherscan.key(), Some("dummykey".to_string())); - std::env::set_var("ETHERSCAN_API_KEY", "FXY"); + unsafe { + std::env::set_var("ETHERSCAN_API_KEY", "FXY"); + } let config = args.load_config().unwrap(); - std::env::remove_var("ETHERSCAN_API_KEY"); + unsafe { + std::env::remove_var("ETHERSCAN_API_KEY"); + } assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); let key = config.get_etherscan_api_key(None).unwrap(); assert_eq!(key, "dummykey".to_string()); } + + #[test] + fn parse_solc_version_arg() { + let args = StorageArgs::parse_from(["foundry-cli", "addr.eth", "--solc-version", "0.8.10"]); + assert_eq!(args.solc_version, Some(Version::parse("0.8.10").unwrap())); + } } diff --git a/crates/cast/src/cmd/tempo.rs b/crates/cast/src/cmd/tempo.rs new file mode 100644 index 0000000000000..6744e6b73c039 --- /dev/null +++ b/crates/cast/src/cmd/tempo.rs @@ -0,0 +1,45 @@ +use clap::Parser; +use eyre::Result; +use foundry_common::tempo::{EnsureAccessKeyConfig, ensure_access_key}; + +/// Tempo wallet integration commands. +#[derive(Debug, Parser)] +pub enum TempoSubcommand { + /// Authorize a new access key against your Tempo wallet via wallet.tempo. + /// + /// Persists the key to `$TEMPO_HOME/wallet/keys.toml` (default + /// `~/.tempo/wallet/keys.toml`). Also runs automatically on a 402 from a + /// Tempo RPC when no local key is configured. + /// + /// Env: `TEMPO_HOME`, `TEMPO_CLI_AUTH_URL` (override auth service). + Login { + /// Chain ID to authorize the key for. Defaults to Tempo mainnet (4217). + #[arg(long, default_value_t = 4217)] + chain_id: u64, + + /// Print the authorization URL to stderr instead of opening a browser. + #[arg(long)] + no_browser: bool, + }, +} + +impl TempoSubcommand { + pub async fn run(self) -> Result<()> { + match self { + Self::Login { chain_id, no_browser } => { + let mut cfg = EnsureAccessKeyConfig::from_env(chain_id); + if no_browser { + cfg.no_browser = true; + } + let outcome = ensure_access_key(cfg).await?; + let _ = foundry_common::sh_println!( + "Authorized key {} for wallet {} on chain {}", + outcome.key_address, + outcome.wallet_address, + outcome.chain_id, + ); + Ok(()) + } + } + } +} diff --git a/crates/cast/src/cmd/tip20/create.rs b/crates/cast/src/cmd/tip20/create.rs new file mode 100644 index 0000000000000..3417674f1d470 --- /dev/null +++ b/crates/cast/src/cmd/tip20/create.rs @@ -0,0 +1,113 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_ens::NameOrAddress; +use alloy_primitives::B256; +use alloy_sol_types::sol; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::provider::ProviderBuilder; +use tempo_alloy::TempoNetwork; +use tempo_contracts::precompiles::{TIP20_FACTORY_ADDRESS, is_iso4217_currency}; + +sol! { + #[sol(rpc)] + interface ITIP20Factory { + function createToken( + string memory name, + string memory symbol, + string memory currency, + address quoteToken, + address admin, + bytes32 salt + ) external returns (address token); + } +} + +/// Returns a warning message for non-ISO 4217 currency codes used in TIP-20 token creation. +pub(crate) fn iso4217_warning_message(currency: &str) -> String { + let hyperlink = |url: &str| format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\"); + let tip20_docs = hyperlink("https://docs.tempo.xyz/protocol/tip20/overview"); + let iso_docs = hyperlink("https://www.iso.org/iso-4217-currency-codes.html"); + + format!( + "\"{currency}\" is not a recognized ISO 4217 currency code.\n\ + \n\ + If the token you are trying to deploy is a fiat-backed stablecoin, Tempo strongly\n\ + recommends that the currency code field be the ISO-4217 currency code of the fiat\n\ + currency your token tracks (e.g. \"USD\", \"EUR\", \"GBP\").\n\ + \n\ + The currency field is IMMUTABLE after token creation and affects fee payment\n\ + eligibility, DEX routing, and quote token pairing. Only \"USD\"-denominated tokens\n\ + can be used to pay transaction fees on Tempo.\n\ + \n\ + Learn more:\n \ + - Tempo TIP-20 docs: {tip20_docs}\n \ + - ISO 4217 standard: {iso_docs}" + ) +} + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + name: String, + symbol: String, + currency: String, + quote_token: NameOrAddress, + admin: NameOrAddress, + salt: B256, + force: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> eyre::Result<()> { + let (signer, tempo_access_key) = if send_tx.eth.wallet.from.is_some() { + send_tx.eth.wallet.maybe_signer().await? + } else { + (None, None) + }; + + let config = send_tx.eth.rpc.load_config()?; + + if !is_iso4217_currency(¤cy) && !force { + sh_warn!("{}", super::iso4217_warning_message(¤cy))?; + let response: String = foundry_common::prompt!("\nContinue anyway? [y/N] ")?; + if !matches!(response.trim(), "y" | "Y") { + sh_println!("Aborted.")?; + return Ok(()); + } + } + + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let quote_token_addr = quote_token.resolve(&provider).await?; + let admin_addr = admin.resolve(&provider).await?; + + let mut tx = ITIP20Factory::new(TIP20_FACTORY_ADDRESS, &provider) + .createToken(name, symbol, currency, quote_token_addr, admin_addr, salt) + .into_transaction_request(); + + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + if let Some(ref access_key) = tempo_access_key { + let signer = signer.as_ref().ok_or_else(|| eyre::eyre!("access key requires a signer"))?; + cast_send_with_access_key( + &provider, + tx, + signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?); + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/tip20/mine.rs b/crates/cast/src/cmd/tip20/mine.rs new file mode 100644 index 0000000000000..0367450a19b06 --- /dev/null +++ b/crates/cast/src/cmd/tip20/mine.rs @@ -0,0 +1,254 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256, keccak256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::provider::ProviderBuilder; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use std::time::{Duration, Instant}; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{MasterId, TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +pub(crate) struct Output { + pub(crate) salt: B256, + pub(crate) registration_hash: B256, + pub(crate) master_id: MasterId, + pub(crate) zero_tag_virtual_address: Address, +} + +pub(super) fn run( + master: Address, + salt: Option, + threads: Option, + seed: Option, + no_random: bool, +) -> Result { + if !master.is_valid_master() { + eyre::bail!( + "invalid master address {master}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + if let Some(salt) = salt { + let output = derive(master, salt); + if !has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + print_output(&output, None)?; + return Ok(output); + } + + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut salt[..]); + } + + sh_println!("Mining TIP-1022 salt for {master} with {n_threads} threads...")?; + + let timer = Instant::now(); + let output = mine(master, salt, n_threads, POW_BYTES)?; + print_output(&output, Some(timer.elapsed()))?; + Ok(output) +} + +pub(super) async fn register( + master: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!( + "--register requires a signer or Tempo keychain identity (for example --private-key or --from)" + ) + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != master { + eyre::bail!( + "registration sender mismatch: mined salt is for {master}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt}) on Tempo...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} + +pub(crate) fn mine( + master: Address, + salt: B256, + n_threads: usize, + pow_bytes: usize, +) -> Result { + let mut packed = [0u8; 52]; + packed[..20].copy_from_slice(master.as_slice()); + + crate::cmd::miner::mine_salt(salt, n_threads, move |salt| { + packed[20..].copy_from_slice(salt.as_slice()); + let registration_hash = keccak256(packed); + + has_pow(®istration_hash, pow_bytes).then(|| { + let master_id = MasterId::from_slice(®istration_hash[4..8]); + let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); + Output { salt, registration_hash, master_id, zero_tag_virtual_address } + }) + }) + .ok_or_else(|| eyre::eyre!("virtual master mining failed: all threads panicked")) +} + +pub(crate) fn derive(master: Address, salt: B256) -> Output { + let registration_hash = registration_hash(master, salt); + let master_id = MasterId::from_slice(®istration_hash[4..8]); + let zero_tag_virtual_address = Address::new_virtual(master_id, UserTag::ZERO); + + Output { salt, registration_hash, master_id, zero_tag_virtual_address } +} + +pub(crate) fn registration_hash(master: Address, salt: B256) -> B256 { + let mut packed = [0u8; 52]; + packed[..20].copy_from_slice(master.as_slice()); + packed[20..].copy_from_slice(salt.as_slice()); + keccak256(packed) +} + +pub(crate) fn has_pow(registration_hash: &B256, pow_bytes: usize) -> bool { + registration_hash[..pow_bytes].iter().all(|byte| *byte == 0) +} + +fn print_output(output: &Output, elapsed: Option) -> Result<()> { + let header = if let Some(elapsed) = elapsed { + format!("Found salt in {elapsed:?}\n") + } else { + String::new() + }; + + sh_println!( + r#"{header}Salt: {} +Registration hash: {} +Master ID: {} +Zero-tag address: {}"#, + output.salt, + output.registration_hash, + output.master_id, + output.zero_tag_virtual_address, + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, b256}; + + #[test] + fn derives_master_id_and_zero_tag_address() { + let master = address!("0x1234567890123456789012345678901234567890"); + let salt = b256!("0x0000000000000000000000000000000000000000000000000000000000000001"); + let output = derive(master, salt); + + assert_eq!( + output.registration_hash, + b256!("0x661db5481211842e0330ea3e4cf0b4e7e5abd2314161ce16e9a99e7460480f21"), + ); + assert_eq!(output.master_id, MasterId::from([0x12, 0x11, 0x84, 0x2e])); + assert_eq!( + output.zero_tag_virtual_address, + address!("0x1211842efdfdfdfdfdfdfdfdfdfd000000000000"), + ); + assert_eq!(output.master_id, MasterId::from_slice(&output.registration_hash[4..8])); + assert_eq!( + output.zero_tag_virtual_address, + Address::new_virtual(output.master_id, UserTag::ZERO), + ); + } + + #[test] + fn mines_pow_with_reduced_difficulty() -> Result<()> { + let master = address!("0x1234567890123456789012345678901234567890"); + let output = mine(master, B256::ZERO, 1, 1)?; + + assert_eq!( + output.salt, + b256!("0x000000000000000000000000000000000000000000000000f301000000000000"), + ); + assert_eq!(output.registration_hash[0], 0); + assert_eq!(output.master_id, MasterId::from_slice(&output.registration_hash[4..8])); + assert_eq!( + output.zero_tag_virtual_address, + Address::new_virtual(output.master_id, UserTag::ZERO), + ); + Ok(()) + } + + #[test] + fn has_pow_checks_leading_zero_bytes() { + let mut hash = B256::ZERO; + assert!(has_pow(&hash, 4)); + assert!(has_pow(&hash, 0)); + + hash[3] = 1; + assert!(!has_pow(&hash, 4)); + assert!(has_pow(&hash, 3)); + assert!(has_pow(&hash, 0)); + } + + #[test] + fn rejects_invalid_master_addresses() { + assert!(!Address::ZERO.is_valid_master()); + assert!(!address!("0x00000000fdfdfdfdfdfdfdfdfdfd000000000001").is_valid_master()); + assert!(!address!("0x20c0000000000000000000000000000000000001").is_valid_master()); + } +} diff --git a/crates/cast/src/cmd/tip20/mod.rs b/crates/cast/src/cmd/tip20/mod.rs new file mode 100644 index 0000000000000..edb4c3b7a57b3 --- /dev/null +++ b/crates/cast/src/cmd/tip20/mod.rs @@ -0,0 +1,111 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_ens::NameOrAddress; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use std::str::FromStr; + +mod create; +pub(crate) use create::iso4217_warning_message; +pub(crate) mod mine; + +/// TIP-20 token operations (Tempo). +#[derive(Debug, Parser, Clone)] +pub enum Tip20Subcommand { + /// Create a new TIP-20 token via the TIP20Factory. + #[command(visible_alias = "c")] + Create { + /// The token name (e.g. "US Dollar Coin"). + name: String, + + /// The token symbol (e.g. "USDC"). + symbol: String, + + /// The ISO 4217 currency code (e.g. "USD", "EUR", "GBP"). + /// This field is IMMUTABLE after creation and affects fee payment + /// eligibility, DEX routing, and quote token pairing. + currency: String, + + /// The TIP-20 quote token address used for exchange pricing. + #[arg(value_parser = NameOrAddress::from_str)] + quote_token: NameOrAddress, + + /// The admin address to receive DEFAULT_ADMIN_ROLE on the new token. + #[arg(value_parser = NameOrAddress::from_str)] + admin: NameOrAddress, + + /// A unique salt for deterministic address derivation (hex-encoded bytes32). + salt: B256, + + /// Skip the ISO 4217 currency code validation warning. + #[arg(long)] + force: bool, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, + + /// Mine a TIP-1022 salt for virtual address' master registration on Tempo. + #[command(visible_alias = "m")] + Mine { + /// Address that will call `registerVirtualMaster(bytes32)`. + #[arg(value_name = "ADDRESS")] + master: Address, + + /// Salt to validate directly instead of mining one. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Number of threads to use. Specifying 0 defaults to the number of logical cores. + #[arg(global = true, long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// The random number generator's seed, used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Don't initialize the salt with a random value, and instead use the default value of 0. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Submit `registerVirtualMaster(bytes32)` on Tempo after finding or validating the salt. + #[arg(long, conflicts_with_all = ["seed", "no_random"])] + register: bool, + + #[command(flatten)] + send_tx: SendTxOpts, + + #[command(flatten)] + tx: TxParams, + }, +} + +impl Tip20Subcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + name, + symbol, + currency, + quote_token, + admin, + salt, + force, + send_tx, + tx, + } => { + create::run(name, symbol, currency, quote_token, admin, salt, force, send_tx, tx) + .await?; + } + Self::Mine { master, salt, threads, seed, no_random, register, send_tx, tx } => { + let output = mine::run(master, salt, threads, seed, no_random)?; + if register { + mine::register(master, output.salt, send_tx, tx).await?; + } + } + } + Ok(()) + } +} diff --git a/crates/cast/src/cmd/trace.rs b/crates/cast/src/cmd/trace.rs new file mode 100644 index 0000000000000..9ae675d824fa4 --- /dev/null +++ b/crates/cast/src/cmd/trace.rs @@ -0,0 +1,89 @@ +use alloy_eips::Encodable2718; +use alloy_network::AnyRpcTransaction; +use alloy_primitives::hex; +use alloy_provider::ext::TraceApi; +use clap::Parser; +use eyre::Result; +use foundry_cli::{ + opts::RpcOpts, + utils::{self, LoadConfig}, +}; +use foundry_common::stdin; + +/// CLI arguments for `cast trace`. +#[derive(Debug, Parser)] +pub struct TraceArgs { + /// Transaction hash (for trace_transaction) or raw tx hex/JSON (for trace_rawTransaction + /// with --raw) + tx: Option, + + /// Use trace_rawTransaction instead of trace_transaction. + /// Required when passing raw transaction hex or JSON instead of a tx hash. + #[arg(long)] + raw: bool, + + /// Include the basic trace of the transaction. + #[arg(long, requires = "raw")] + trace: bool, + + /// Include the full trace of the virtual machine's state during transaction execution + #[arg(long, requires = "raw")] + vm_trace: bool, + + /// Include state changes caused by the transaction (requires --raw). + #[arg(long, requires = "raw")] + state_diff: bool, + + #[command(flatten)] + rpc: RpcOpts, +} + +impl TraceArgs { + pub async fn run(self) -> Result<()> { + let config = self.rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let input = stdin::unwrap_line(self.tx)?; + + let trimmed = input.trim(); + let is_json = trimmed.starts_with('{'); + let is_raw_hex = trimmed.starts_with("0x") && trimmed.len() > 66; + + let result = if self.raw { + // trace_rawTransaction: accepts raw hex OR JSON tx + let raw_bytes = if is_raw_hex { + hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed))? + } else if is_json { + let tx: AnyRpcTransaction = serde_json::from_str(trimmed)?; + tx.as_ref().encoded_2718().clone() + } else { + hex::decode(trimmed)? + }; + + let mut trace_builder = provider.trace_raw_transaction(&raw_bytes); + + if self.trace { + trace_builder = trace_builder.trace(); + } + if self.vm_trace { + trace_builder = trace_builder.vm_trace(); + } + if self.state_diff { + trace_builder = trace_builder.state_diff(); + } + + if trace_builder.get_trace_types().map(|t| t.is_empty()).unwrap_or(true) { + eyre::bail!("No trace type specified. Use --trace, --vm-trace, or --state-diff"); + } + + serde_json::to_string_pretty(&trace_builder.await?)? + } else { + // trace_transaction: use tx hash directly + let hash = input.parse()?; + let traces = provider.trace_transaction(hash).await?; + serde_json::to_string_pretty(&traces)? + }; + + sh_println!("{}", result)?; + Ok(()) + } +} diff --git a/crates/cast/src/cmd/txpool.rs b/crates/cast/src/cmd/txpool.rs index eb43e2306a791..2947295ff4d98 100644 --- a/crates/cast/src/cmd/txpool.rs +++ b/crates/cast/src/cmd/txpool.rs @@ -40,22 +40,26 @@ impl TxPoolSubcommands { Self::Content { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; - sh_println!("{:#?}", provider.txpool_content().await?)?; + let content = provider.txpool_content().await?; + sh_println!("{}", serde_json::to_string_pretty(&content)?)?; } Self::ContentFrom { from, args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; - sh_println!("{:#?}", provider.txpool_content_from(from).await?)?; + let content = provider.txpool_content_from(from).await?; + sh_println!("{}", serde_json::to_string_pretty(&content)?)?; } Self::Inspect { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; - sh_println!("{:#?}", provider.txpool_inspect().await?)?; + let inspect = provider.txpool_inspect().await?; + sh_println!("{}", serde_json::to_string_pretty(&inspect)?)?; } Self::Status { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; - sh_println!("{:#?}", provider.txpool_status().await?)?; + let status = provider.txpool_status().await?; + sh_println!("{}", serde_json::to_string_pretty(&status)?)?; } }; diff --git a/crates/cast/src/cmd/vaddr/create.rs b/crates/cast/src/cmd/vaddr/create.rs new file mode 100644 index 0000000000000..0563b09601323 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/create.rs @@ -0,0 +1,181 @@ +use crate::{ + cmd::{ + erc20::build_provider_with_signer, + send::{cast_send, cast_send_with_access_key}, + tip20::mine, + }, + tx::{SendTxOpts, TxParams}, +}; +use alloy_primitives::{Address, B256}; +use alloy_signer::Signer; +use eyre::Result; +use foundry_cli::utils::{LoadConfig, get_chain}; +use foundry_common::{provider::ProviderBuilder, shell}; +use rand::{RngCore, SeedableRng, rngs::StdRng}; +use serde_json::json; +use std::time::Instant; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; +use tempo_primitives::{TempoAddressExt, UserTag}; + +const POW_BYTES: usize = 4; + +#[allow(clippy::too_many_arguments)] +pub(super) async fn run( + owner: Address, + salt: Option, + tag: u64, + count: u32, + threads: Option, + seed: Option, + no_random: bool, + no_register: bool, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + if count == 0 { + // no virtual addresses to compute + return Ok(()); + } + + if !owner.is_valid_master() { + eyre::bail!( + "invalid owner address {owner}; see https://docs.tempo.xyz/protocol/tips/tip-1022" + ); + } + + let output = if let Some(salt) = salt { + let output = mine::derive(owner, salt); + if !mine::has_pow(&output.registration_hash, POW_BYTES) { + eyre::bail!( + "provided salt does not satisfy TIP-1022 proof of work: {}", + output.registration_hash + ); + } + output + } else { + let mut n_threads = threads.unwrap_or(0); + if n_threads == 0 { + n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + } + + let mut start_salt = B256::ZERO; + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_os_rng(), + }; + rng.fill_bytes(&mut start_salt[..]); + } + + if !shell::is_json() { + sh_println!("Mining TIP-1022 salt for {owner} with {n_threads} threads...")?; + } + let timer = Instant::now(); + let output = mine::mine(owner, start_salt, n_threads, POW_BYTES)?; + if !shell::is_json() { + sh_println!("Found salt in {:?}", timer.elapsed())?; + } + output + }; + + const MAX_USER_TAG: u64 = 0x0000_FFFF_FFFF_FFFF; + let mut virtual_addresses = Vec::with_capacity(count as usize); + for i in 0..count { + let tag_value = tag + .checked_add(i as u64) + .filter(|&t| t <= MAX_USER_TAG) + .ok_or_else(|| eyre::eyre!("tag overflow: tag + count exceeds the 6-byte user tag range (max {MAX_USER_TAG:#x})"))?; + let raw = tag_value.to_be_bytes(); + let user_tag = UserTag::new(raw[2..].try_into().expect("slice is 6 bytes")); + let vaddr = Address::new_virtual(output.master_id, user_tag); + virtual_addresses.push((user_tag, vaddr)); + } + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "salt": format!("{}", output.salt), + "registration_hash": format!("{}", output.registration_hash), + "master_id": format!("{}", output.master_id), + "virtual_addresses": virtual_addresses.iter().map(|(tag, addr)| json!({ + "tag": format!("{tag}"), + "address": format!("{addr}"), + })).collect::>(), + }))? + )?; + } else { + sh_println!( + "Salt: {} +Registration hash: {} +Master ID: {}", + output.salt, + output.registration_hash, + output.master_id, + )?; + sh_println!("\nVirtual addresses:")?; + for (tag, vaddr) in &virtual_addresses { + sh_println!(" tag={tag} {vaddr}")?; + } + } + + if no_register { + return Ok(()); + } + + register(owner, output.salt, send_tx, tx_opts).await +} + +async fn register( + owner: Address, + salt: B256, + send_tx: SendTxOpts, + tx_opts: TxParams, +) -> Result<()> { + let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?; + let signer = signer.ok_or_else(|| { + eyre::eyre!("cast vaddr create requires a signer (for example --private-key or --from)") + })?; + + let sender = + tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address()); + + if sender != owner { + eyre::bail!( + "signer mismatch: salt is for {owner}, but the configured signer would register as {sender}" + ); + } + + let config = send_tx.eth.load_config()?; + let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider) + .registerVirtualMaster(salt) + .into_transaction_request(); + tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); + + sh_println!("Submitting registerVirtualMaster({salt})...")?; + + if let Some(ref access_key) = tempo_access_key { + cast_send_with_access_key( + &provider, + tx, + &signer, + access_key, + send_tx.cast_async, + send_tx.confirmations, + timeout, + ) + .await?; + } else { + let provider = build_provider_with_signer::(&send_tx, signer)?; + cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout) + .await?; + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/mod.rs b/crates/cast/src/cmd/vaddr/mod.rs new file mode 100644 index 0000000000000..446d1e7ece5e2 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/mod.rs @@ -0,0 +1,131 @@ +use crate::tx::{SendTxOpts, TxParams}; +use alloy_primitives::{Address, B256}; +use clap::Parser; +use foundry_cli::opts::RpcOpts; + +mod create; +mod resolve; +mod watch; + +/// TIP-1022 virtual address registry operations (Tempo). +/// +/// Virtual addresses are deterministic 20-byte aliases (masterId || VIRTUAL_MAGIC || userTag) +/// that auto-forward TIP-20 deposits to a registered master wallet at the protocol level, +/// with no on-chain sweep transaction required. +/// +/// See: +#[derive(Debug, Parser, Clone)] +pub enum VaddrSubcommand { + /// Mine a TIP-1022 proof-of-work salt, register as a virtual address master, and print + /// derived virtual addresses for the given owner. + #[command(visible_alias = "c")] + Create { + /// The master (owner) address that will control all virtual addresses under this + /// registration. Must not be the zero address, a virtual address, or a TIP-20 token. + #[arg(long, value_name = "ADDRESS")] + owner: Address, + + /// Use this salt directly instead of mining one. Must satisfy the 32-bit PoW requirement. + #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")] + salt: Option, + + /// Starting user tag for the derived virtual address output (hex-encoded 6 bytes). + #[arg(long, default_value = "0", value_name = "U64")] + tag: u64, + + /// Number of virtual addresses to derive and print. + #[arg(long, default_value = "1", value_name = "N")] + count: u32, + + /// Number of threads to use for mining. Defaults to number of logical cores. + #[arg(long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// Seed for the random number generator used to initialize the salt search. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Start salt search from zero instead of a random value. + #[arg(long, conflicts_with = "seed")] + no_random: bool, + + /// Mine and print the salt and derived virtual addresses without submitting the + /// registerVirtualMaster transaction. + #[arg(long)] + no_register: bool, + + #[command(flatten)] + send_tx: Box, + + #[command(flatten)] + tx: Box, + }, + + /// Resolve a virtual address to its registered master and decode its components. + #[command(visible_alias = "r")] + Resolve { + /// The virtual address to resolve. + #[arg(value_name = "ADDRESS")] + addr: Address, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Watch (tail) incoming TIP-20 transfers to a virtual address. + #[command(visible_alias = "w")] + Watch { + /// The virtual address to monitor. + #[arg(value_name = "ADDRESS")] + addr: Address, + + /// Filter on a specific TIP-20 token address. Watches all tokens if omitted. + #[arg(long, value_name = "ADDRESS")] + token: Option
, + + /// Block number to start from. Defaults to the current latest block. + #[arg(long, value_name = "BLOCK")] + from_block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, +} + +impl VaddrSubcommand { + pub async fn run(self) -> eyre::Result<()> { + match self { + Self::Create { + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + send_tx, + tx, + } => { + create::run( + owner, + salt, + tag, + count, + threads, + seed, + no_random, + no_register, + *send_tx, + *tx, + ) + .await? + } + Self::Resolve { addr, rpc } => resolve::run(addr, rpc).await?, + Self::Watch { addr, token, from_block, rpc } => { + watch::run(addr, token, from_block, rpc).await? + } + } + Ok(()) + } +} diff --git a/crates/cast/src/cmd/vaddr/resolve.rs b/crates/cast/src/cmd/vaddr/resolve.rs new file mode 100644 index 0000000000000..96936f4fe4713 --- /dev/null +++ b/crates/cast/src/cmd/vaddr/resolve.rs @@ -0,0 +1,52 @@ +use alloy_primitives::{Address, hex}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use tempo_alloy::{ + TempoNetwork, + contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry}, +}; + +pub(super) async fn run(addr: Address, rpc: RpcOpts) -> Result<()> { + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let registry = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider); + + let decode_builder = registry.decodeVirtualAddress(addr); + let resolve_builder = registry.resolveVirtualAddress(addr); + let (decoded, master) = tokio::try_join!(decode_builder.call(), resolve_builder.call())?; + + if !decoded.isVirtual { + sh_println!("{addr} is not a virtual address")?; + return Ok(()); + } + + let master_id = decoded.masterId; + let user_tag = decoded.userTag; + let master: Address = master; + + if shell::is_json() { + let master_address = if master.is_zero() { None } else { Some(format!("{master}")) }; + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "address": format!("{addr}"), + "master_id": format!("0x{}", hex::encode(master_id)), + "user_tag": format!("0x{}", hex::encode(user_tag)), + "master_address": master_address, + }))? + )?; + } else { + sh_println!("Virtual address: {addr}")?; + sh_println!("Master ID: 0x{}", hex::encode(master_id))?; + sh_println!("User tag: 0x{}", hex::encode(user_tag))?; + if master.is_zero() { + sh_println!("Master address: (unregistered)")?; + } else { + sh_println!("Master address: {master}")?; + } + } + + Ok(()) +} diff --git a/crates/cast/src/cmd/vaddr/watch.rs b/crates/cast/src/cmd/vaddr/watch.rs new file mode 100644 index 0000000000000..dc159d5e37c8e --- /dev/null +++ b/crates/cast/src/cmd/vaddr/watch.rs @@ -0,0 +1,108 @@ +use alloy_primitives::{Address, B256, keccak256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use eyre::Result; +use foundry_cli::{opts::RpcOpts, utils::LoadConfig}; +use foundry_common::{provider::ProviderBuilder, shell}; +use serde_json::json; +use std::sync::LazyLock; +use tempo_alloy::TempoNetwork; +use tempo_primitives::TempoAddressExt; + +static TRANSFER_TOPIC: LazyLock = + LazyLock::new(|| keccak256(b"Transfer(address,address,uint256)")); + +pub(super) async fn run( + addr: Address, + token: Option
, + from_block: Option, + rpc: RpcOpts, +) -> Result<()> { + if !addr.is_virtual() { + eyre::bail!("{addr} is not a virtual address"); + } + + let config = rpc.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + + // Transfer(address indexed from, address indexed to, uint256 value) + // topic[0] = event sig, topic[1] = from, topic[2] = to + let to_topic: B256 = { + let mut buf = [0u8; 32]; + buf[12..].copy_from_slice(addr.as_slice()); + buf.into() + }; + + let start = from_block.map(BlockNumberOrTag::Number).unwrap_or(BlockNumberOrTag::Latest); + + let mut filter = + Filter::new().event_signature(*TRANSFER_TOPIC).topic2(to_topic).from_block(start); + + if let Some(tok) = token { + filter = filter.address(tok); + } + + if !shell::is_json() { + sh_println!("Watching transfers to {addr}... (Ctrl-C to stop)")?; + } + + // Fetch logs from the requested start block (historical when from_block is set) + let logs = provider.get_logs(&filter).await?; + for log in &logs { + print_transfer_log(log)?; + } + + // Poll for new logs + let mut last_block = provider.get_block_number().await?; + loop { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let current = provider.get_block_number().await?; + if current > last_block { + let poll_filter = filter.clone().from_block(last_block + 1).to_block(current); + let new_logs = provider.get_logs(&poll_filter).await?; + for log in &new_logs { + print_transfer_log(log)?; + } + last_block = current; + } + } +} + +fn print_transfer_log(log: &alloy_rpc_types::Log) -> Result<()> { + let block = log.block_number.unwrap_or(0); + let tx = log.transaction_hash.unwrap_or_default(); + let token = log.address(); + + // Decode topics: topic[1]=from, topic[2]=to + let from = log.topics().get(1).map(|t| { + let mut addr = [0u8; 20]; + addr.copy_from_slice(&t[12..]); + Address::from(addr) + }); + + // Decode amount from data + let amount = if log.data().data.len() >= 32 { + alloy_primitives::U256::from_be_slice(&log.data().data[..32]) + } else { + alloy_primitives::U256::ZERO + }; + + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string(&json!({ + "block": block, + "tx": format!("{tx}"), + "token": format!("{token}"), + "from": from.map(|a| format!("{a}")).unwrap_or_default(), + "amount": amount.to_string(), + }))? + )?; + } else { + sh_println!( + "block={block} tx={tx} token={token} from={} amount={amount}", + from.map(|a| a.to_string()).unwrap_or_default(), + )?; + } + Ok(()) +} diff --git a/crates/cast/src/cmd/wallet/list.rs b/crates/cast/src/cmd/wallet/list.rs index fd3551be79b5b..9555dbe4a4f93 100644 --- a/crates/cast/src/cmd/wallet/list.rs +++ b/crates/cast/src/cmd/wallet/list.rs @@ -4,7 +4,7 @@ use std::env; use foundry_common::{fs, sh_err, sh_println}; use foundry_config::Config; -use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; +use foundry_wallets::wallet_multi::MultiWalletOptsBuilder; /// CLI arguments for `cast wallet list`. #[derive(Clone, Debug, Parser)] @@ -23,13 +23,25 @@ pub struct ListArgs { trezor: bool, /// List accounts from AWS KMS. + /// + /// Ensure either one of AWS_KMS_KEY_IDS (comma-separated) or AWS_KMS_KEY_ID environment + /// variables are set. #[arg(long, hide = !cfg!(feature = "aws-kms"))] aws: bool, /// List accounts from Google Cloud KMS. + /// + /// Ensure the following environment variables are set: GCP_PROJECT_ID, GCP_LOCATION, + /// GCP_KEY_RING, GCP_KEY_NAME, GCP_KEY_VERSION. + /// + /// See: #[arg(long, hide = !cfg!(feature = "gcp-kms"))] gcp: bool, + /// List accounts from Turnkey. + #[arg(long, hide = !cfg!(feature = "turnkey"))] + turnkey: bool, + /// List all configured accounts. #[arg(long, group = "hw-wallets")] all: bool, @@ -42,11 +54,17 @@ pub struct ListArgs { impl ListArgs { pub async fn run(self) -> Result<()> { // list local accounts as files in keystore dir, no need to unlock / provide password - if self.dir.is_some() || - self.all || - (!self.ledger && !self.trezor && !self.aws && !self.gcp) + if self.dir.is_some() + || self.all + || (!self.ledger && !self.trezor && !self.aws && !self.gcp) { - let _ = self.list_local_senders(); + match self.list_local_senders() { + Ok(()) => {} + Err(e) if !self.all => { + sh_err!("{}", e)?; + } + _ => {} + } } // Create options for multi wallet - ledger, trezor and AWS @@ -56,7 +74,10 @@ impl ListArgs { .trezor(self.trezor || self.all) .aws(self.aws || self.all) .gcp(self.gcp || (self.all && gcp_env_vars_set())) + .turnkey(self.turnkey || self.all) .interactives(0) + .interactive(false) + .browser(Default::default()) .build() .expect("build multi wallet"); @@ -75,11 +96,10 @@ impl ListArgs { }) } } - Err(e) => { - if !self.all { - sh_err!("{}", e)?; - } + Err(e) if !self.all => { + sh_err!("{}", e)?; } + _ => {} } }; } @@ -87,12 +107,13 @@ impl ListArgs { list_senders!(list_opts.ledgers(), "Ledger"); list_senders!(list_opts.trezors(), "Trezor"); list_senders!(list_opts.aws_signers(), "AWS"); + list_senders!(list_opts.gcp_signers(), "GCP"); Ok(()) } fn list_local_senders(&self) -> Result<()> { - let keystore_path = self.dir.clone().unwrap_or_default(); + let keystore_path = self.dir.as_deref().unwrap_or_default(); let keystore_dir = if keystore_path.is_empty() { // Create the keystore default directory if it doesn't exist let default_dir = Config::foundry_keystores_dir().unwrap(); @@ -105,11 +126,13 @@ impl ListArgs { // List all files within the keystore directory. for entry in std::fs::read_dir(keystore_dir)? { let path = entry?.path(); - if path.is_file() { - if let Some(file_name) = path.file_name() { - if let Some(name) = file_name.to_str() { - sh_println!("{name} (Local)")?; - } + if path.is_file() + && let Some(file_name) = path.file_name() + && let Some(name) = file_name.to_str() + { + // Extract address from keystore filename format: UTC--{timestamp}--{address} + if let Some(address) = name.split("--").last() { + sh_println!("0x{} (Local)", address)?; } } } diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index e15c53ad33fea..b2378d8bfdc58 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -1,15 +1,12 @@ use alloy_chains::Chain; use alloy_dyn_abi::TypedData; -use alloy_primitives::{hex, Address, Signature, B256, U256}; +use alloy_primitives::{Address, B256, Signature, U256, hex}; use alloy_provider::Provider; use alloy_rpc_types::Authorization; -use alloy_signer::{ - k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey}, - Signer, -}; +use alloy_signer::Signer; use alloy_signer_local::{ - coins_bip39::{English, Entropy, Mnemonic}, MnemonicBuilder, PrivateKeySigner, + coins_bip39::{English, Entropy, Mnemonic}, }; use clap::Parser; use eyre::{Context, Result}; @@ -45,18 +42,22 @@ pub enum WalletSubcommands { /// Triggers a hidden password prompt for the JSON keystore. /// /// Deprecated: prompting for a hidden password is now the default. - #[arg(long, short, requires = "path", conflicts_with = "unsafe_password")] + #[arg(long, short, conflicts_with = "unsafe_password")] password: bool, /// Password for the JSON keystore in cleartext. /// /// This is UNSAFE to use and we recommend using the --password. - #[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] + #[arg(long, env = "CAST_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, /// Number of wallets to generate. #[arg(long, short, default_value = "1")] number: u32, + + /// Overwrite existing keystore files without prompting. + #[arg(long)] + force: bool, }, /// Generates a random BIP39 mnemonic phrase @@ -90,6 +91,22 @@ pub enum WalletSubcommands { wallet: WalletOpts, }, + /// Derive accounts from a mnemonic + #[command(visible_alias = "d")] + Derive { + /// The accounts will be derived from the specified mnemonic phrase. + #[arg(value_name = "MNEMONIC")] + mnemonic: String, + + /// Number of accounts to display. + #[arg(long, short, default_value = "1")] + accounts: Option, + + /// Insecure mode: display private keys in the terminal. + #[arg(long, default_value = "false")] + insecure: bool, + }, + /// Sign a message or typed data. #[command(visible_alias = "s")] Sign { @@ -139,6 +156,12 @@ pub enum WalletSubcommands { #[arg(long)] chain: Option, + /// If set, indicates the authorization will be broadcast by the signing account itself. + /// This means the nonce used will be the current nonce + 1 (to account for the + /// transaction that will include this authorization). + #[arg(long, conflicts_with = "nonce")] + self_broadcast: bool, + #[command(flatten)] wallet: WalletOpts, }, @@ -150,6 +173,15 @@ pub enum WalletSubcommands { /// /// Treats 0x-prefixed strings as hex encoded bytes. /// Non 0x-prefixed strings are treated as raw input message. + /// + /// The message will be prefixed with the Ethereum Signed Message header and hashed before + /// signing, unless `--no-hash` is provided. + /// + /// Typed data can be provided as a json string or a file name. + /// Use --data flag to denote the message is a string of typed data. + /// Use --data --from-file to denote the message is a file name containing typed data. + /// The data will be combined and hashed using the EIP712 specification before signing. + /// The data should be formatted as JSON. message: String, /// The signature to verify. @@ -158,6 +190,18 @@ pub enum WalletSubcommands { /// The address of the message signer. #[arg(long, short)] address: Address, + + /// Treat the message as JSON typed data. + #[arg(long)] + data: bool, + + /// Treat the message as a file containing JSON typed data. Requires `--data`. + #[arg(long, requires = "data")] + from_file: bool, + + /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. + #[arg(long, conflicts_with = "data")] + no_hash: bool, }, /// Import a private key into an encrypted keystore. @@ -204,7 +248,7 @@ pub enum WalletSubcommands { /// Derives private key from mnemonic #[command(name = "private-key", visible_alias = "pk", aliases = &["derive-private-key", "--derive-private-key"])] PrivateKey { - /// If provided, the private key will be derived from the specified menomonic phrase. + /// If provided, the private key will be derived from the specified mnemonic phrase. #[arg(value_name = "MNEMONIC")] mnemonic_override: Option, @@ -266,84 +310,162 @@ pub enum WalletSubcommands { impl WalletSubcommands { pub async fn run(self) -> Result<()> { match self { - Self::New { path, account_name, unsafe_password, number, .. } => { + Self::New { path, account_name, unsafe_password, number, password, force } => { let mut rng = thread_rng(); - let mut json_values = if shell::is_json() { Some(vec![]) } else { None }; - if let Some(path) = path { - let path = match dunce::canonicalize(path.clone()) { - Ok(path) => path, - // If the path doesn't exist, it will fail to be canonicalized, - // so we attach more context to the error message. + let mut json_values = shell::is_json().then(std::vec::Vec::new); + + let path = if let Some(path) = path { + match dunce::canonicalize(&path) { + Ok(path) => { + if !path.is_dir() { + // we require path to be an existing directory + eyre::bail!("`{}` is not a directory", path.display()); + } + Some(path) + } Err(e) => { - eyre::bail!("If you specified a directory, please make sure it exists, or create it before running `cast wallet new `.\n{path} is not a directory.\nError: {}", e); + eyre::bail!( + "If you specified a directory, please make sure it exists, or create it before running `cast wallet new `.\n{path} is not a directory.\nError: {}", + e + ); } - }; - if !path.is_dir() { - // we require path to be an existing directory - eyre::bail!("`{}` is not a directory", path.display()); } + } else if unsafe_password.is_some() || password { + let path = Config::foundry_keystores_dir().ok_or_else(|| { + eyre::eyre!("Could not find the default keystore directory.") + })?; + fs::create_dir_all(&path)?; + Some(path) + } else { + None + }; - let password = if let Some(password) = unsafe_password { - password - } else { - // if no --unsafe-password was provided read via stdin - rpassword::prompt_password("Enter secret: ")? - }; - - for i in 0..number { - let account_name_ref = account_name.as_deref().map(|name| match number { - 1 => name.to_string(), - _ => format!("{}_{}", name, i + 1), - }); - - let (wallet, uuid) = PrivateKeySigner::new_keystore( - &path, - &mut rng, - password.clone(), - account_name_ref.as_deref(), - )?; - let identifier = account_name_ref.as_deref().unwrap_or(&uuid); - - if let Some(json) = json_values.as_mut() { - json.push(json!({ - "address": wallet.address().to_checksum(None), - "path": format!("{}", path.join(identifier).display()), - })); + match path { + Some(path) => { + let password = if let Some(password) = unsafe_password { + password } else { - sh_println!( - "Created new encrypted keystore file: {}", - path.join(identifier).display() + // if no --unsafe-password was provided read via stdin + rpassword::prompt_password("Enter secret: ")? + }; + + // Prevent accidental overwriting: check all target files upfront + if !force && let Some(ref acc_name) = account_name { + let mut existing_files = Vec::new(); + + for i in 0..number { + let name = match number { + 1 => acc_name.clone(), + _ => format!("{}_{}", acc_name, i + 1), + }; + let file_path = path.join(&name); + if file_path.exists() { + existing_files.push(name); + } + } + + if !existing_files.is_empty() { + use std::io::Write; + + sh_eprintln!("The following keystore file(s) already exist:")?; + for file in &existing_files { + sh_eprintln!(" - {file}")?; + } + sh_print!( + "\nDo you want to overwrite all {} file(s)? [y/N]: ", + existing_files.len() + )?; + std::io::stdout().flush()?; + + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + + if !input.trim().eq_ignore_ascii_case("y") { + eyre::bail!("Operation cancelled. No keystores were modified."); + } + } + } + for i in 0..number { + let account_name_ref = + account_name.as_deref().map(|name| match number { + 1 => name.to_string(), + _ => format!("{}_{}", name, i + 1), + }); + + let (wallet, uuid) = PrivateKeySigner::new_keystore( + &path, + &mut rng, + password.clone(), + account_name_ref.as_deref(), )?; - sh_println!("Address: {}", wallet.address().to_checksum(None))?; + let identifier = account_name_ref.as_deref().unwrap_or(&uuid); + + if let Some(json) = json_values.as_mut() { + json.push(if shell::verbosity() > 0 { + json!({ + "address": wallet.address().to_checksum(None), + "public_key": format!("0x{}", hex::encode(wallet.public_key())), + "path": format!("{}", path.join(identifier).display()), + }) + } else { + json!({ + "address": wallet.address().to_checksum(None), + "path": format!("{}", path.join(identifier).display()), + }) + }); + } else { + sh_println!( + "Created new encrypted keystore file: {}", + path.join(identifier).display() + )?; + sh_println!("Address: {}", wallet.address().to_checksum(None))?; + if shell::verbosity() > 0 { + sh_println!( + "Public key: 0x{}", + hex::encode(wallet.public_key()) + )?; + } + } } } - - if let Some(json) = json_values.as_ref() { - sh_println!("{}", serde_json::to_string_pretty(json)?)?; - } - } else { - for _ in 0..number { - let wallet = PrivateKeySigner::random_with(&mut rng); - - if let Some(json) = json_values.as_mut() { - json.push(json!({ - "address": wallet.address().to_checksum(None), - "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), - })) - } else { - sh_println!("Successfully created new keypair.")?; - sh_println!("Address: {}", wallet.address().to_checksum(None))?; - sh_println!( - "Private key: 0x{}", - hex::encode(wallet.credential().to_bytes()) - )?; + None => { + for _ in 0..number { + let wallet = PrivateKeySigner::random_with(&mut rng); + + if let Some(json) = json_values.as_mut() { + json.push(if shell::verbosity() > 0 { + json!({ + "address": wallet.address().to_checksum(None), + "public_key": format!("0x{}", hex::encode(wallet.public_key())), + "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), + }) + } else { + json!({ + "address": wallet.address().to_checksum(None), + "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), + }) + }); + } else { + sh_println!("Successfully created new keypair.")?; + sh_println!("Address: {}", wallet.address().to_checksum(None))?; + if shell::verbosity() > 0 { + sh_println!( + "Public key: 0x{}", + hex::encode(wallet.public_key()) + )?; + } + sh_println!( + "Private key: 0x{}", + hex::encode(wallet.credential().to_bytes()) + )?; + } } } + } - if let Some(json) = json_values.as_ref() { - sh_println!("{}", serde_json::to_string_pretty(json)?)?; - } + if let Some(json) = json_values.as_ref() { + sh_println!("{}", serde_json::to_string_pretty(json)?)?; } } Self::NewMnemonic { words, accounts, entropy } => { @@ -377,16 +499,28 @@ impl WalletSubcommands { let mut accounts = json!([]); for (i, wallet) in wallets.iter().enumerate() { + let public_key = hex::encode(wallet.public_key()); let private_key = hex::encode(wallet.credential().to_bytes()); if format_json { - accounts.as_array_mut().unwrap().push(json!({ - "address": format!("{}", wallet.address()), - "private_key": format!("0x{}", private_key), - })); + accounts.as_array_mut().unwrap().push(if shell::verbosity() > 0 { + json!({ + "address": format!("{}", wallet.address()), + "public_key": format!("0x{}", public_key), + "private_key": format!("0x{}", private_key), + }) + } else { + json!({ + "address": format!("{}", wallet.address()), + "private_key": format!("0x{}", private_key), + }) + }); } else { sh_println!("- Account {i}:")?; sh_println!("Address: {}", wallet.address())?; - sh_println!("Private key: 0x{private_key}\n")?; + if shell::verbosity() > 0 { + sh_println!("Public key: 0x{}", public_key)?; + } + sh_println!("Private key: 0x{}\n", private_key)?; } } @@ -413,6 +547,54 @@ impl WalletSubcommands { let addr = wallet.address(); sh_println!("{}", addr.to_checksum(None))?; } + Self::Derive { mnemonic, accounts, insecure } => { + let format_json = shell::is_json(); + let mut accounts_json = json!([]); + for i in 0..accounts.unwrap_or(1) { + let wallet = WalletOpts { + raw: RawWalletOpts { + mnemonic: Some(mnemonic.clone()), + mnemonic_index: i as u32, + ..Default::default() + }, + ..Default::default() + } + .signer() + .await?; + + match wallet { + WalletSigner::Local(local_wallet) => { + let address = local_wallet.address().to_checksum(None); + let private_key = hex::encode(local_wallet.credential().to_bytes()); + if format_json { + if insecure { + accounts_json.as_array_mut().unwrap().push(json!({ + "address": format!("{}", address), + "private_key": format!("0x{}", private_key), + })); + } else { + accounts_json.as_array_mut().unwrap().push(json!({ + "address": format!("{}", address) + })); + } + } else { + sh_println!("- Account {i}:")?; + if insecure { + sh_println!("Address: {}", address)?; + sh_println!("Private key: 0x{}\n", private_key)?; + } else { + sh_println!("Address: {}\n", address)?; + } + } + } + _ => eyre::bail!("Only local wallets are supported by this command"), + } + } + + if format_json { + sh_println!("{}", serde_json::to_string_pretty(&accounts_json)?)?; + } + } Self::PublicKey { wallet, private_key_override } => { let wallet = private_key_override .map(|pk| WalletOpts { @@ -423,23 +605,12 @@ impl WalletSubcommands { .signer() .await?; - let private_key_bytes = match wallet { - WalletSigner::Local(wallet) => wallet.credential().to_bytes(), + let public_key = match wallet { + WalletSigner::Local(wallet) => wallet.public_key(), _ => eyre::bail!("Only local wallets are supported by this command"), }; - let secret_key = SecretKey::from_slice(&private_key_bytes) - .map_err(|e| eyre::eyre!("Invalid private key: {}", e))?; - - // Get the public key from the private key - let public_key = secret_key.public_key(); - - // Serialize it as uncompressed (65 bytes: 0x04 || X (32 bytes) || Y (32 bytes)) - let pubkey_bytes = public_key.to_encoded_point(false); - // Strip the 1-byte prefix (0x04) to get 64 bytes for Ethereum use - let ethereum_pubkey = &pubkey_bytes.as_bytes()[1..]; - - sh_println!("0x{}", hex::encode(ethereum_pubkey))?; + sh_println!("0x{}", hex::encode(public_key))?; } Self::Sign { message, data, from_file, no_hash, wallet } => { let wallet = wallet.signer().await?; @@ -481,13 +652,20 @@ impl WalletSubcommands { sh_println!("0x{}", hex::encode(sig.as_bytes()))?; } } - Self::SignAuth { rpc, nonce, chain, wallet, address } => { + Self::SignAuth { rpc, nonce, chain, wallet, address, self_broadcast } => { let wallet = wallet.signer().await?; let provider = utils::get_provider(&rpc.load_config()?)?; let nonce = if let Some(nonce) = nonce { nonce } else { - provider.get_transaction_count(wallet.address()).await? + let current_nonce = provider.get_transaction_count(wallet.address()).await?; + if self_broadcast { + // When self-broadcasting, the authorization nonce needs to be +1 + // because the transaction itself will consume the current nonce + current_nonce + 1 + } else { + current_nonce + } }; let chain_id = if let Some(chain) = chain { chain.id() @@ -511,7 +689,7 @@ impl WalletSubcommands { )?; } else { sh_println!( - "Successfully signed!\n Nonce: {}\n Chain ID: {}\n Address: {}\n Signature: 0x{}", + "Successfully signed!\n Nonce: {}\n Chain ID: {}\n Address: {}\n Signature: {}", nonce, chain_id, wallet.address(), @@ -523,8 +701,25 @@ impl WalletSubcommands { sh_println!("{}", hex::encode_prefixed(alloy_rlp::encode(&auth)))?; } } - Self::Verify { message, signature, address } => { - let recovered_address = Self::recover_address_from_message(&message, &signature)?; + Self::Verify { message, signature, address, data, from_file, no_hash } => { + let recovered_address = if data { + let typed_data: TypedData = if from_file { + // data is a file name, read json from file + foundry_common::fs::read_json_file(message.as_ref())? + } else { + // data is a json string + serde_json::from_str(&message)? + }; + Self::recover_address_from_typed_data(&typed_data, &signature)? + } else if no_hash { + Self::recover_address_from_message_no_hash( + &hex::decode(&message)?[..].try_into()?, + &signature, + )? + } else { + Self::recover_address_from_message(&message, &signature)? + }; + if address == recovered_address { sh_println!("Validation succeeded. Address {address} signed this message.")?; } else { @@ -584,8 +779,7 @@ flag to set your key via: )?; let address = wallet.address(); let success_message = format!( - "`{}` keystore was saved successfully. Address: {:?}", - &account_name, address, + "`{account_name}` keystore was saved successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } @@ -620,7 +814,7 @@ flag to set your key via: format!("Failed to remove keystore file at {}", keystore_path.display()) })?; - let success_message = format!("`{}` keystore was removed successfully.", &name); + let success_message = format!("`{name}` keystore was removed successfully."); sh_println!("{}", success_message.green())?; } Self::PrivateKey { @@ -691,8 +885,7 @@ flag to set your key via: let private_key = B256::from_slice(&wallet.credential().to_bytes()); - let success_message = - format!("{}'s private key is: {}", &account_name, private_key); + let success_message = format!("{account_name}'s private key is: {private_key}"); sh_println!("{}", success_message.green())?; } @@ -750,10 +943,9 @@ flag to set your key via: Some(&account_name), )?; + let address = wallet.address(); let success_message = format!( - "Password for keystore `{}` was changed successfully. Address: {:?}", - &account_name, - wallet.address(), + "Password for keystore `{account_name}` was changed successfully. Address: {address:?}", ); sh_println!("{}", success_message.green())?; } @@ -770,6 +962,22 @@ flag to set your key via: Ok(signature.recover_address_from_msg(message)?) } + /// Recovers an address from the specified message and signature. + fn recover_address_from_message_no_hash( + prehash: &B256, + signature: &Signature, + ) -> Result
{ + Ok(signature.recover_address_from_prehash(prehash)?) + } + + /// Recovers an address from the specified EIP-712 typed data and signature. + fn recover_address_from_typed_data( + typed_data: &TypedData, + signature: &Signature, + ) -> Result
{ + Ok(signature.recover_address_from_prehash(&typed_data.eip712_signing_hash()?)?) + } + /// Strips the 0x prefix from a hex string and decodes it to bytes. /// /// Treats the string as raw bytes if it doesn't start with 0x. @@ -784,7 +992,7 @@ flag to set your key via: #[cfg(test)] mod tests { use super::*; - use alloy_primitives::address; + use alloy_primitives::{address, keccak256}; use std::str::FromStr; #[test] @@ -824,6 +1032,28 @@ mod tests { assert_eq!(address, recovered_address.unwrap()); } + #[test] + fn can_verify_signed_hex_message_no_hash() { + let prehash = keccak256("hello"); + let signature = Signature::from_str("433ec3d37e4f1253df15e2dea412fed8e915737730f74b3dfb1353268f932ef5557c9158e0b34bce39de28d11797b42e9b1acb2749230885fe075aedc3e491a41b").unwrap(); + let address = address!("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"); // private key = 1 + let recovered_address = + WalletSubcommands::recover_address_from_message_no_hash(&prehash, &signature); + assert!(recovered_address.is_ok()); + assert_eq!(address, recovered_address.unwrap()); + } + + #[test] + fn can_verify_signed_typed_data() { + let typed_data: TypedData = serde_json::from_str(r#"{"domain":{"name":"Test","version":"1","chainId":1,"verifyingContract":"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"},"message":{"value":123},"primaryType":"Data","types":{"Data":[{"name":"value","type":"uint256"}]}}"#).unwrap(); + let signature = Signature::from_str("0285ff83b93bd01c14e201943af7454fe2bc6c98be707a73888c397d6ae3b0b92f73ca559f81cbb19fe4e0f1dc4105bd7b647c6a84b033057977cf2ec982daf71b").unwrap(); + let address = address!("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"); // private key = 1 + let recovered_address = + WalletSubcommands::recover_address_from_typed_data(&typed_data, &signature); + assert!(recovered_address.is_ok()); + assert_eq!(address, recovered_address.unwrap()); + } + #[test] fn can_parse_wallet_sign_data() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]); @@ -882,4 +1112,20 @@ mod tests { _ => panic!("expected WalletSubcommands::ChangePassword"), } } + + #[test] + fn wallet_sign_auth_nonce_and_self_broadcast_conflict() { + let result = WalletSubcommands::try_parse_from([ + "foundry-cli", + "sign-auth", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + "--nonce", + "42", + "--self-broadcast", + ]); + assert!( + result.is_err(), + "expected error when both --nonce and --self-broadcast are provided" + ); + } } diff --git a/crates/cast/src/cmd/wallet/vanity.rs b/crates/cast/src/cmd/wallet/vanity.rs index 1c80e3165e858..ffcd63be65cc5 100644 --- a/crates/cast/src/cmd/wallet/vanity.rs +++ b/crates/cast/src/cmd/wallet/vanity.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{hex, Address}; +use alloy_primitives::{Address, hex}; use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; use alloy_signer_local::PrivateKeySigner; use clap::Parser; @@ -91,7 +91,7 @@ impl VanityArgs { } macro_rules! find_vanity { - ($m:ident, $nonce: ident) => { + ($m:ident, $nonce:ident) => { if let Some(nonce) = $nonce { find_vanity_address_with_nonce($m, nonce) } else { @@ -149,8 +149,8 @@ impl VanityArgs { "Successfully found vanity address in {:.3} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}", timer.elapsed().as_secs_f64(), if nonce.is_some() { "\nContract address: " } else { "" }, - if nonce.is_some() { - wallet.address().create(nonce.unwrap()).to_checksum(None) + if let Some(nonce_val) = nonce { + wallet.address().create(nonce_val).to_checksum(None) } else { String::new() }, @@ -193,15 +193,16 @@ pub fn find_vanity_address_with_nonce( wallet_generator().find_any(create_nonce_matcher(matcher, nonce)).map(|(key, _)| key.into()) } -/// Creates a nonce matcher function, which takes a reference to a [GeneratedWallet] and returns +/// Creates a matcher function, which takes a reference to a [GeneratedWallet] and returns /// whether it found a match or not by using `matcher`. #[inline] pub fn create_matcher(matcher: T) -> impl Fn(&GeneratedWallet) -> bool { move |(_, addr)| matcher.is_match(addr) } -/// Creates a nonce matcher function, which takes a reference to a [GeneratedWallet] and a nonce and -/// returns whether it found a match or not by using `matcher`. +/// Creates a contract address matcher function that uses the specified nonce. +/// The returned function takes a reference to a [GeneratedWallet] and returns +/// whether the contract address created with the nonce matches using `matcher`. #[inline] pub fn create_nonce_matcher( matcher: T, diff --git a/crates/cast/src/debug.rs b/crates/cast/src/debug.rs new file mode 100644 index 0000000000000..823dedde99059 --- /dev/null +++ b/crates/cast/src/debug.rs @@ -0,0 +1,101 @@ +use std::str::FromStr; + +use alloy_chains::Chain; +use alloy_primitives::{Address, Bytes, map::HashMap}; +use foundry_cli::utils::{TraceResult, print_traces}; +use foundry_common::{ContractsByArtifact, compile::ProjectCompiler, shell}; +use foundry_config::Config; +use foundry_debugger::Debugger; +use foundry_evm::traces::{ + CallTraceDecoderBuilder, DebugTraceIdentifier, + debug::ContractSources, + identifier::{SignaturesIdentifier, TraceIdentifiers}, +}; + +/// labels the traces, conditionally prints them or opens the debugger +#[expect(clippy::too_many_arguments)] +pub(crate) async fn handle_traces( + mut result: TraceResult, + config: &Config, + chain: Chain, + contracts_bytecode: &HashMap, + labels: Vec, + with_local_artifacts: bool, + debug: bool, + decode_internal: bool, + disable_label: bool, + trace_depth: Option, +) -> eyre::Result<()> { + let (known_contracts, mut sources) = if with_local_artifacts { + let _ = sh_println!("Compiling project to generate artifacts"); + let project = config.project()?; + let compiler = ProjectCompiler::new(); + let output = compiler.compile(&project)?; + ( + Some(ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + )), + ContractSources::from_project_output(&output, project.root(), None)?, + ) + } else { + (None, ContractSources::default()) + }; + + let labels = labels.iter().filter_map(|label_str| { + let mut iter = label_str.split(':'); + + if let Some(addr) = iter.next() + && let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) + { + return Some((address, label.to_string())); + } + None + }); + let config_labels = config.labels.clone().into_iter(); + + let mut builder = CallTraceDecoderBuilder::new() + .with_labels(labels.chain(config_labels)) + .with_signature_identifier(SignaturesIdentifier::from_config(config)?) + .with_label_disabled(disable_label) + .with_chain_id(Some(chain.id())); + let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?; + if let Some(contracts) = &known_contracts { + builder = builder.with_known_contracts(contracts); + identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode); + } + + let mut decoder = builder.build(); + + for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { + decoder.identify(trace, &mut identifier); + } + + if decode_internal || debug { + if let Some(ref etherscan_identifier) = identifier.external { + sources.merge(etherscan_identifier.get_compiled_contracts().await?); + } + + if debug { + let mut debugger = Debugger::builder() + .traces(result.traces.expect("missing traces")) + .decoder(&decoder) + .sources(sources) + .build(); + debugger.try_run_tui()?; + return Ok(()); + } + + decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); + } + + print_traces( + &mut result, + &decoder, + shell::verbosity() > 0, + shell::verbosity() > 4, + trace_depth, + ) + .await?; + + Ok(()) +} diff --git a/crates/cast/src/errors.rs b/crates/cast/src/errors.rs index 3e6c5977bfa4c..03ec7c3178a6f 100644 --- a/crates/cast/src/errors.rs +++ b/crates/cast/src/errors.rs @@ -20,7 +20,10 @@ impl fmt::Display for FunctionSignatureError { } Self::MissingEtherscan { sig } => { writeln!(f, "Failed to determine function signature for `{sig}`")?; - writeln!(f, "To lookup a function signature of a deployed contract by name, a valid ETHERSCAN_API_KEY must be set.")?; + writeln!( + f, + "To lookup a function signature of a deployed contract by name, a valid ETHERSCAN_API_KEY must be set." + )?; write!(f, "\tOr did you mean:\t {sig}()") } Self::UnknownChain(chain) => { diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 434d1fec892ba..2b1b03486bf04 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,94 +1,91 @@ //! Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg))] -use alloy_consensus::TxEnvelope; +#[macro_use] +extern crate foundry_common; +#[macro_use] +extern crate tracing; + +use alloy_consensus::{ + BlockHeader, + transaction::{Recovered, SignerRecoverable}, +}; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; +use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; -use alloy_network::{AnyNetwork, AnyRpcTransaction}; +use alloy_network::{AnyNetwork, BlockResponse, Network, TransactionBuilder}; use alloy_primitives::{ - hex, - utils::{keccak256, ParseUnits, Unit}, - Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256, U64, + Address, B256, I256, Keccak256, LogData, Selector, TxHash, U64, U256, hex, + utils::{ParseUnits, Unit, keccak256}, }; -use alloy_provider::{ - network::eip2718::{Decodable2718, Encodable2718}, - PendingTransactionBuilder, Provider, -}; -use alloy_rlp::Decodable; +use alloy_provider::{PendingTransactionBuilder, Provider, network::eip2718::Decodable2718}; +use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types::{ - state::StateOverride, BlockId, BlockNumberOrTag, Filter, TransactionRequest, + BlockId, BlockNumberOrTag, BlockOverrides, Filter, FilterBlockOption, Log, state::StateOverride, }; -use alloy_serde::WithOtherFields; -use alloy_sol_types::sol; use base::{Base, NumberWithBase, ToBase}; use chrono::DateTime; use eyre::{Context, ContextCompat, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_common::{ - abi::{encode_function_args, get_func}, + abi::{coerce_value, encode_function_args, encode_function_args_packed, get_event, get_func}, compile::etherscan_project, + flatten, fmt::*, - fs, get_pretty_tx_receipt_attr, shell, TransactionReceiptWithRevertReason, + fs, shell, }; -use foundry_compilers::flatten::Flattener; use foundry_config::Chain; -use foundry_evm_core::ic::decode_instructions; -use futures::{future::Either, FutureExt, StreamExt}; +use foundry_evm::core::bytecode::InstIter; +use futures::{FutureExt, StreamExt, future::Either}; +#[cfg(feature = "optimism")] +use op_alloy_consensus as _; + use rayon::prelude::*; +use serde::Serialize; use std::{ borrow::Cow, fmt::Write, io, + marker::PhantomData, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, - time::Duration, }; use tokio::signal::ctrl_c; -use foundry_common::abi::encode_function_args_packed; pub use foundry_evm::*; pub mod args; pub mod cmd; pub mod opts; +pub mod tempo; pub mod base; +pub mod call_spec; +pub(crate) mod debug; pub mod errors; mod rlp_converter; pub mod tx; use rlp_converter::Item; -#[macro_use] -extern crate tracing; - -#[macro_use] -extern crate foundry_common; - // TODO: CastContract with common contract initializers? Same for CastProviders? -sol! { - #[sol(rpc)] - interface IERC20 { - #[derive(Debug)] - function balanceOf(address owner) external view returns (uint256); - } -} - -pub struct Cast

{ +pub struct Cast { provider: P, + _phantom: PhantomData, } -impl> Cast

{ +impl + Clone + Unpin, N: Network> Cast { /// Creates a new Cast instance from the provided client /// /// # Example /// /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { @@ -98,8 +95,8 @@ impl> Cast

{ /// # Ok(()) /// # } /// ``` - pub fn new(provider: P) -> Self { - Self { provider } + pub const fn new(provider: P) -> Self { + Self { provider, _phantom: PhantomData } } /// Makes a read-only call to the specified address @@ -108,7 +105,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::{Address, U256, Bytes}; - /// use alloy_rpc_types::{TransactionRequest, state::{StateOverride, AccountOverride}}; + /// use alloy_rpc_types::{TransactionRequest, BlockOverrides, state::{StateOverride, AccountOverride}}; /// use alloy_serde::WithOtherFields; /// use cast::Cast; /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; @@ -134,49 +131,51 @@ impl> Cast

{ /// account_override.balance = Some(U256::from(1000)); /// state_override.insert(to, account_override); /// let state_override_object = StateOverridesBuilder::default().build(); + /// let block_override_object = BlockOverrides::default(); /// /// let cast = Cast::new(alloy_provider); - /// let data = cast.call(&tx, None, None, state_override_object).await?; + /// let data = cast.call(&tx, None, None, Some(state_override_object), Some(block_override_object)).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` pub async fn call( &self, - req: &WithOtherFields, + req: &N::TransactionRequest, func: Option<&Function>, block: Option, - state_override: StateOverride, + state_override: Option, + block_override: Option, ) -> Result { - let res = self + let mut call = self .provider .call(req.clone()) .block(block.unwrap_or_default()) - .overrides(state_override) - .await?; - - let mut decoded = vec![]; + .with_block_overrides_opt(block_override); + if let Some(state_override) = state_override { + call = call.overrides(state_override) + } - if let Some(func) = func { + let res = call.await?; + let decoded = if let Some(func) = func { // decode args into tokens - decoded = match func.abi_decode_output(res.as_ref()) { + match func.abi_decode_output(res.as_ref()) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract if res.is_empty() { // check that the recipient is a contract that can be called - if let Some(TxKind::Call(addr)) = req.to { + if let Some(addr) = req.to() { if let Ok(code) = self .provider .get_code_at(addr) .block_id(block.unwrap_or_default()) .await + && code.is_empty() { - if code.is_empty() { - eyre::bail!("contract {addr:?} does not have any code") - } + eyre::bail!("contract {addr:?} does not have any code") } - } else if Some(TxKind::Create) == req.to { + } else if req.to().is_none() { eyre::bail!("tx req is a contract deployment"); } else { eyre::bail!("recipient is None"); @@ -186,14 +185,19 @@ impl> Cast

{ "could not decode output; did you specify the wrong function return data type?" ); } - }; - } + } + } else { + vec![] + }; // handle case when return type is not specified Ok(if decoded.is_empty() { res.to_string() } else if shell::is_json() { - let tokens = decoded.iter().map(format_token_raw).collect::>(); + let tokens = decoded + .into_iter() + .map(|value| serialize_value_as_json(value, None)) + .collect::>>()?; serde_json::to_string_pretty(&tokens).unwrap() } else { // seth compatible user-friendly return type conversions @@ -233,7 +237,7 @@ impl> Cast

{ /// ``` pub async fn access_list( &self, - req: &WithOtherFields, + req: &N::TransactionRequest, block: Option, ) -> Result { let access_list = @@ -244,7 +248,7 @@ impl> Cast

{ let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", &al.address.to_checksum(None))); + s.push(format!("- address: {}", al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { @@ -262,55 +266,12 @@ impl> Cast

{ Ok(self.provider.get_balance(who).block_id(block.unwrap_or_default()).await?) } - /// Sends a transaction to the specified address - /// - /// # Example - /// - /// ``` - /// use cast::{Cast}; - /// use alloy_primitives::{Address, U256, Bytes}; - /// use alloy_serde::WithOtherFields; - /// use alloy_rpc_types::{TransactionRequest}; - /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; - /// use std::str::FromStr; - /// use alloy_sol_types::{sol, SolCall}; - /// - /// sol!( - /// function greet(string greeting) public; - /// ); - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;; - /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; - /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode(); - /// let bytes = Bytes::from_iter(greeting.iter()); - /// let gas = U256::from_str("200000").unwrap(); - /// let value = U256::from_str("1").unwrap(); - /// let nonce = U256::from_str("1").unwrap(); - /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); - /// let tx = WithOtherFields::new(tx); - /// let cast = Cast::new(provider); - /// let data = cast.send(tx).await?; - /// println!("{:#?}", data); - /// # Ok(()) - /// # } - /// ``` - pub async fn send( - &self, - tx: WithOtherFields, - ) -> Result> { - let res = self.provider.send_transaction(tx).await?; - - Ok(res) - } - /// Publishes a raw transaction to the network /// /// # Example /// /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { @@ -322,162 +283,13 @@ impl> Cast

{ /// # Ok(()) /// # } /// ``` - pub async fn publish( - &self, - mut raw_tx: String, - ) -> Result> { - raw_tx = match raw_tx.strip_prefix("0x") { - Some(s) => s.to_string(), - None => raw_tx, - }; - let tx = hex::decode(raw_tx)?; + pub async fn publish(&self, raw_tx: String) -> Result> { + let tx = hex::decode(strip_0x(&raw_tx))?; let res = self.provider.send_raw_transaction(&tx).await?; Ok(res) } - /// # Example - /// - /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; - /// use cast::Cast; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; - /// let cast = Cast::new(provider); - /// let block = cast.block(5, true, None).await?; - /// println!("{}", block); - /// # Ok(()) - /// # } - /// ``` - pub async fn block>( - &self, - block: B, - full: bool, - field: Option, - ) -> Result { - let block = block.into(); - if let Some(ref field) = field { - if field == "transactions" && !full { - eyre::bail!("use --full to view transactions") - } - } - - let block = self - .provider - .get_block(block) - .kind(full.into()) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - let block = if let Some(ref field) = field { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } else if shell::is_json() { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - }; - - Ok(block) - } - - async fn block_field_as_num>(&self, block: B, field: String) -> Result { - Self::block( - self, - block.into(), - false, - // Select only select field - Some(field), - ) - .await? - .parse() - .map_err(Into::into) - } - - pub async fn base_fee>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await - } - - pub async fn age>(&self, block: B) -> Result { - let timestamp_str = - Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); - Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) - } - - pub async fn timestamp>(&self, block: B) -> Result { - Self::block_field_as_num(self, block, "timestamp".to_string()).await - } - - pub async fn chain(&self) -> Result<&str> { - let genesis_hash = Self::block( - self, - 0, - false, - // Select only block hash - Some(String::from("hash")), - ) - .await?; - - Ok(match &genesis_hash[..] { - "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Self::block(self, 1920000, false, Some("hash".to_string())).await?)[..] { - "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { - "etclive" - } - _ => "ethlive", - } - } - "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", - "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", - "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { - "optimism-mainnet" - } - "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { - "optimism-goerli" - } - "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { - "optimism-kovan" - } - "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", - "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { - "fraxtal-testnet" - } - "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { - "arbitrum-mainnet" - } - "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", - "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", - "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", - "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", - "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", - "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { - "polygon-pos-amoy-testnet" - } - "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", - "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { - "polygon-zkevm-cardona-testnet" - } - "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", - "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", - "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", - "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", - "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Self::block(self, 1, false, Some(String::from("hash"))).await?)[..] { - "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { - "avalanche-fuji" - } - _ => "avalanche", - } - } - "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", - "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", - _ => "unknown", - }) - } - pub async fn chain_id(&self) -> Result { Ok(self.provider.get_chain_id().await?) } @@ -494,7 +306,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -582,7 +394,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -631,7 +443,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -661,7 +473,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::{Address, U256}; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -684,7 +496,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -720,7 +532,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// @@ -737,369 +549,599 @@ impl> Cast

{ pub async fn codesize(&self, who: Address, block: Option) -> Result { let code = self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); - Ok(format!("{}", code.len())) + Ok(code.len().to_string()) } + /// Perform a raw JSON-RPC request + /// /// # Example /// /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?; - /// println!("{}", tx); + /// let result = cast + /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) + /// .await?; + /// println!("{}", result); /// # Ok(()) /// # } /// ``` - pub async fn transaction( - &self, - tx_hash: Option, - from: Option, - nonce: Option, - field: Option, - raw: bool, - ) -> Result { - let tx = if let Some(tx_hash) = tx_hash { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - self.provider - .get_transaction_by_hash(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? - } else if let Some(from) = from { - // If nonce is not provided, uses 0. - let nonce = U64::from(nonce.unwrap_or_default()); - let from = from.resolve(self.provider.root()).await?; - - self.provider - .raw_request::<_, Option>( - "eth_getTransactionBySenderAndNonce".into(), - (from, nonce), - ) - .await? - .ok_or_else(|| { - eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) - })? - } else { - eyre::bail!("tx hash or from address is required") - }; - - Ok(if raw { - format!("0x{}", hex::encode(tx.inner.inner.encoded_2718())) - } else if let Some(field) = field { - get_pretty_tx_attr(&tx.inner, field.as_str()) - .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? - } else if shell::is_json() { - // to_value first to sort json object keys - serde_json::to_value(&tx)?.to_string() - } else { - tx.pretty() - }) + pub async fn rpc(&self, method: &str, params: V) -> Result + where + V: alloy_json_rpc::RpcSend, + { + let res = self + .provider + .raw_request::(Cow::Owned(method.to_string()), params) + .await?; + Ok(serde_json::to_string(&res)?) } + /// Returns the slot + /// /// # Example /// /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_primitives::{Address, B256}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?; - /// println!("{}", receipt); + /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; + /// let slot = B256::ZERO; + /// let storage = cast.storage(addr, slot, None).await?; + /// println!("{}", storage); /// # Ok(()) /// # } /// ``` - pub async fn receipt( + pub async fn storage( &self, - tx_hash: String, - field: Option, - confs: u64, - timeout: Option, - cast_async: bool, + from: Address, + slot: B256, + block: Option, ) -> Result { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - - let mut receipt: TransactionReceiptWithRevertReason = - match self.provider.get_transaction_receipt(tx_hash).await? { - Some(r) => r, - None => { - // if the async flag is provided, immediately exit if no tx is found, otherwise - // try to poll for it - if cast_async { - eyre::bail!("tx not found: {:?}", tx_hash) - } else { - PendingTransactionBuilder::new(self.provider.root().clone(), tx_hash) - .with_required_confirmations(confs) - .with_timeout(timeout.map(Duration::from_secs)) - .get_receipt() - .await? + Ok(format!( + "{:?}", + B256::from( + self.provider + .get_storage_at(from, slot.into()) + .block_id(block.unwrap_or_default()) + .await? + ) + )) + } + + pub async fn filter_logs(&self, filter: Filter) -> Result { + let logs = self.provider.get_logs(&filter).await?; + Self::format_logs(logs) + } + + /// Retrieves logs using chunked requests to handle large block ranges. + /// + /// Automatically divides large block ranges into smaller chunks to avoid provider limits + /// and processes them with controlled concurrency to prevent rate limiting. + pub async fn filter_logs_chunked(&self, filter: Filter, chunk_size: u64) -> Result { + let logs = self.get_logs_chunked(&filter, chunk_size).await?; + Self::format_logs(logs) + } + + fn format_logs(logs: Vec) -> Result { + let res = if shell::is_json() { + serde_json::to_string(&logs)? + } else { + let mut s = vec![]; + for log in logs { + let pretty = log + .pretty() + .replacen('\n', "- ", 1) // Remove empty first line + .replace('\n', "\n "); // Indent + s.push(pretty); + } + s.join("\n") + }; + Ok(res) + } + + fn extract_block_range(filter: &Filter) -> (Option, Option) { + let FilterBlockOption::Range { from_block, to_block } = &filter.block_option else { + return (None, None); + }; + + (from_block.and_then(|b| b.as_number()), to_block.and_then(|b| b.as_number())) + } + + /// Retrieves logs with automatic chunking fallback. + /// + /// First tries to fetch logs for the entire range. If that fails, + /// falls back to concurrent chunked requests with rate limiting. + async fn get_logs_chunked(&self, filter: &Filter, chunk_size: u64) -> Result> + where + P: Clone + Unpin, + { + // Try the full range first + if let Ok(logs) = self.provider.get_logs(filter).await { + return Ok(logs); + } + + // Fallback: use concurrent chunked approach + self.get_logs_chunked_concurrent(filter, chunk_size).await + } + + /// Retrieves logs using concurrent chunked requests with rate limiting. + /// + /// Divides the block range into chunks and processes them with a maximum of + /// 5 concurrent requests. Falls back to single-block queries if chunks fail. + async fn get_logs_chunked_concurrent( + &self, + filter: &Filter, + chunk_size: u64, + ) -> Result> + where + P: Clone + Unpin, + { + let (from_block, to_block) = Self::extract_block_range(filter); + let (Some(from), Some(to)) = (from_block, to_block) else { + return self.provider.get_logs(filter).await.map_err(Into::into); + }; + + if from >= to { + return Ok(vec![]); + } + + // Create chunk ranges using iterator + let chunk_ranges: Vec<(u64, u64)> = (from..to) + .step_by(chunk_size as usize) + .map(|chunk_start| (chunk_start, (chunk_start + chunk_size).min(to))) + .collect(); + + // Process chunks with controlled concurrency using buffered stream + let mut all_results: Vec<(u64, Vec)> = futures::stream::iter(chunk_ranges) + .map(|(start_block, chunk_end)| { + let chunk_filter = filter.clone().from_block(start_block).to_block(chunk_end - 1); + let provider = self.provider.clone(); + + async move { + // Try direct chunk request with simplified fallback + match provider.get_logs(&chunk_filter).await { + Ok(logs) => (start_block, logs), + Err(_) => { + // Simple fallback: try individual blocks in this chunk + let mut fallback_logs = Vec::new(); + for single_block in start_block..chunk_end { + let single_filter = chunk_filter + .clone() + .from_block(single_block) + .to_block(single_block); + if let Ok(logs) = provider.get_logs(&single_filter).await { + fallback_logs.extend(logs); + } + } + (start_block, fallback_logs) + } } } - } - .into(); + }) + .buffered(5) // Limit to 5 concurrent requests to avoid rate limits + .collect() + .await; - // Allow to fail silently - let _ = receipt.update_revert_reason(&self.provider).await; + // Sort once at the end by block number and flatten + all_results.sort_by_key(|(block_num, _)| *block_num); - Ok(if let Some(ref field) = field { - get_pretty_tx_receipt_attr(&receipt, field) - .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))? - } else if shell::is_json() { - // to_value first to sort json object keys - serde_json::to_value(&receipt)?.to_string() - } else { - receipt.pretty() - }) + let mut all_logs = Vec::new(); + for (_, logs) in all_results { + all_logs.extend(logs); + } + + Ok(all_logs) } - /// Perform a raw JSON-RPC request + /// Converts a block identifier into a block number. + /// + /// If the block identifier is a block number, then this function returns the block number. If + /// the block identifier is a block hash, then this function returns the block number of + /// that block hash. If the block identifier is `None`, then this function returns `None`. /// /// # Example /// /// ``` - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_primitives::fixed_bytes; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; /// use cast::Cast; + /// use std::{convert::TryFrom, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let result = cast - /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) + /// + /// let block_number = cast.convert_block_number(Some(BlockId::number(5))).await?; + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(5))); + /// + /// let block_number = cast + /// .convert_block_number(Some(BlockId::hash(fixed_bytes!( + /// "0000000000000000000000000000000000000000000000000000000000001234" + /// )))) /// .await?; - /// println!("{}", result); + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(4660))); + /// + /// let block_number = cast.convert_block_number(None).await?; + /// assert_eq!(block_number, None); /// # Ok(()) /// # } /// ``` - pub async fn rpc(&self, method: &str, params: V) -> Result - where - V: alloy_json_rpc::RpcSend, - { - let res = self - .provider - .raw_request::(Cow::Owned(method.to_string()), params) - .await?; - Ok(serde_json::to_string(&res)?) + pub async fn convert_block_number( + &self, + block: Option, + ) -> Result, eyre::Error> { + match block { + Some(block) => match block { + BlockId::Number(block_number) => Ok(Some(block_number)), + BlockId::Hash(hash) => { + let block = self.provider.get_block_by_hash(hash.block_hash).await?; + Ok(block.map(|block| block.header().number()).map(BlockNumberOrTag::from)) + } + }, + None => Ok(None), + } } - /// Returns the slot + /// Sets up a subscription to the given filter and writes the logs to the given output. /// /// # Example /// /// ``` - /// use alloy_primitives::{Address, B256}; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_primitives::Address; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use alloy_rpc_types::Filter; + /// use alloy_transport::BoxTransport; + /// use cast::Cast; + /// use std::{io, str::FromStr}; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("wss://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// + /// let filter = + /// Filter::new().address(Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?); + /// let mut output = io::stdout(); + /// cast.subscribe(filter, &mut output).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn subscribe(&self, filter: Filter, output: &mut dyn io::Write) -> Result<()> { + // Initialize the subscription stream for logs + let mut subscription = self.provider.subscribe_logs(&filter).await?.into_stream(); + + // Check if a to_block is specified, if so, subscribe to blocks + let mut block_subscription = if filter.get_to_block().is_some() { + Some(self.provider.subscribe_blocks().await?.into_stream()) + } else { + None + }; + + let format_json = shell::is_json(); + let to_block_number = filter.get_to_block(); + + // If output should be JSON, start with an opening bracket + if format_json { + write!(output, "[")?; + } + + let mut first = true; + + loop { + tokio::select! { + // If block subscription is present, listen to it to avoid blocking indefinitely past the desired to_block + block = if let Some(bs) = &mut block_subscription { + Either::Left(bs.next().fuse()) + } else { + Either::Right(futures::future::pending()) + } => { + if let (Some(block), Some(to_block)) = (block, to_block_number) + && block.number() > to_block { + break; + } + }, + // Process incoming log + log = subscription.next() => { + if format_json { + if !first { + write!(output, ",")?; + } + first = false; + let log_str = serde_json::to_string(&log).unwrap(); + write!(output, "{log_str}")?; + } else { + let log_str = log.pretty() + .replacen('\n', "- ", 1) // Remove empty first line + .replace('\n', "\n "); // Indent + writeln!(output, "{log_str}")?; + } + }, + // Break on cancel signal, to allow for closing JSON bracket + _ = ctrl_c() => { + break; + }, + else => break, + } + } + + // If output was JSON, end with a closing bracket + if format_json { + write!(output, "]")?; + } + + Ok(()) + } +} + +impl, N: Network> Cast +where + N::HeaderResponse: UIfmtHeaderExt, + N::BlockResponse: UIfmt, +{ + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; - /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; - /// let slot = B256::ZERO; - /// let storage = cast.storage(addr, slot, None).await?; - /// println!("{}", storage); + /// let block = cast.block(5, true, vec![]).await?; + /// println!("{}", block); /// # Ok(()) /// # } /// ``` - pub async fn storage( + pub async fn block>( &self, - from: Address, - slot: B256, - block: Option, + block: B, + full: bool, + fields: Vec, ) -> Result { - Ok(format!( - "{:?}", - B256::from( - self.provider - .get_storage_at(from, slot.into()) - .block_id(block.unwrap_or_default()) - .await? - ) - )) - } + let block = block.into(); + if fields.contains(&"transactions".into()) && !full { + eyre::bail!("use --full to view transactions") + } - pub async fn filter_logs(&self, filter: Filter) -> Result { - let logs = self.provider.get_logs(&filter).await?; + let block = self + .provider + .get_block(block) + .kind(full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - let res = if shell::is_json() { - serde_json::to_string(&logs)? + Ok(if !fields.is_empty() { + let mut result = String::new(); + for field in fields { + result.push_str( + &get_pretty_block_attr::(&block, &field) + .unwrap_or_else(|| format!("{field} is not a valid block field")), + ); + + result.push('\n'); + } + result.trim_end().to_string() + } else if shell::is_json() { + serde_json::to_value(&block).unwrap().to_string() } else { - let mut s = vec![]; - for log in logs { - let pretty = log - .pretty() - .replacen('\n', "- ", 1) // Remove empty first line - .replace('\n', "\n "); // Indent - s.push(pretty); + block.pretty() + }) + } + + async fn block_field_as_num>(&self, block: B, field: String) -> Result { + Self::block( + self, + block.into(), + false, + // Select only select field + vec![field], + ) + .await? + .parse() + .map_err(Into::into) + } + + pub async fn base_fee>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await + } + + pub async fn age>(&self, block: B) -> Result { + let timestamp_str = + Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); + let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); + Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) + } + + pub async fn timestamp>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, "timestamp".to_string()).await + } + + pub async fn chain(&self) -> Result<&str> { + let genesis_hash = Self::block( + self, + 0, + false, + // Select only block hash + vec![String::from("hash")], + ) + .await?; + + Ok(match &genesis_hash[..] { + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { + match &(Self::block(self, 1920000, false, vec![String::from("hash")]).await?)[..] { + "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { + "etclive" + } + _ => "ethlive", + } + } + "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", + "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", + "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { + "optimism-mainnet" + } + "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { + "optimism-goerli" + } + "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { + "optimism-kovan" + } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } + "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { + "arbitrum-mainnet" + } + "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", + "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", + "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", + "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", + "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", + "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { + "polygon-pos-amoy-testnet" + } + "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", + "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { + "polygon-zkevm-cardona-testnet" } - s.join("\n") - }; - Ok(res) + "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", + "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", + "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", + "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", + "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { + match &(Self::block(self, 1, false, vec![String::from("hash")]).await?)[..] { + "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { + "avalanche-fuji" + } + _ => "avalanche", + } + } + "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", + "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", + _ => "unknown", + }) } +} - /// Converts a block identifier into a block number. - /// - /// If the block identifier is a block number, then this function returns the block number. If - /// the block identifier is a block hash, then this function returns the block number of - /// that block hash. If the block identifier is `None`, then this function returns `None`. - /// +impl, N: Network> Cast +where + N::Header: Encodable, +{ /// # Example /// /// ``` - /// use alloy_primitives::fixed_bytes; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; - /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::Ethereum}; /// use cast::Cast; - /// use std::{convert::TryFrom, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, Ethereum>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// - /// let block_number = cast.convert_block_number(Some(BlockId::number(5))).await?; - /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(5))); - /// - /// let block_number = cast - /// .convert_block_number(Some(BlockId::hash(fixed_bytes!( - /// "0000000000000000000000000000000000000000000000000000000000001234" - /// )))) - /// .await?; - /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(4660))); - /// - /// let block_number = cast.convert_block_number(None).await?; - /// assert_eq!(block_number, None); + /// let block = cast.block_raw(5, true).await?; + /// println!("{}", block); /// # Ok(()) /// # } /// ``` - pub async fn convert_block_number( - &self, - block: Option, - ) -> Result, eyre::Error> { - match block { - Some(block) => match block { - BlockId::Number(block_number) => Ok(Some(block_number)), - BlockId::Hash(hash) => { - let block = self.provider.get_block_by_hash(hash.block_hash).await?; - Ok(block.map(|block| block.header.number).map(BlockNumberOrTag::from)) - } - }, - None => Ok(None), - } + pub async fn block_raw>(&self, block: B, full: bool) -> Result { + let block_id = block.into(); + + let block = self + .provider + .get_block(block_id) + .kind(full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?; + + let encoded = alloy_rlp::encode(block.header().as_ref()); + + Ok(format!("0x{}", hex::encode(encoded))) } +} - /// Sets up a subscription to the given filter and writes the logs to the given output. - /// +impl, N: Network> Cast +where + N::TxEnvelope: Serialize + UIfmtSignatureExt, + N::TransactionResponse: UIfmt, +{ /// # Example /// /// ``` - /// use alloy_primitives::Address; - /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; - /// use alloy_rpc_types::Filter; - /// use alloy_transport::BoxTransport; + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; - /// use std::{io, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("wss://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// - /// let filter = - /// Filter::new().address(Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?); - /// let mut output = io::stdout(); - /// cast.subscribe(filter, &mut output).await?; + /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; + /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false, false).await?; + /// println!("{}", tx); /// # Ok(()) /// # } /// ``` - pub async fn subscribe(&self, filter: Filter, output: &mut dyn io::Write) -> Result<()> { - // Initialize the subscription stream for logs - let mut subscription = self.provider.subscribe_logs(&filter).await?.into_stream(); + pub async fn transaction( + &self, + tx_hash: Option, + from: Option, + nonce: Option, + field: Option, + raw: bool, + to_request: bool, + ) -> Result { + let tx = if let Some(tx_hash) = tx_hash { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + self.provider + .get_transaction_by_hash(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? + } else if let Some(from) = from { + // If nonce is not provided, uses 0. + let nonce = U64::from(nonce.unwrap_or_default()); + let from = from.resolve(self.provider.root()).await?; - // Check if a to_block is specified, if so, subscribe to blocks - let mut block_subscription = if filter.get_to_block().is_some() { - Some(self.provider.subscribe_blocks().await?.into_stream()) + self.provider + .raw_request::<_, Option>( + "eth_getTransactionBySenderAndNonce".into(), + (from, nonce), + ) + .await? + .ok_or_else(|| { + eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) + })? } else { - None + eyre::bail!("tx hash or from address is required") }; - let format_json = shell::is_json(); - let to_block_number = filter.get_to_block(); - - // If output should be JSON, start with an opening bracket - if format_json { - write!(output, "[")?; - } - - let mut first = true; - - loop { - tokio::select! { - // If block subscription is present, listen to it to avoid blocking indefinitely past the desired to_block - block = if let Some(bs) = &mut block_subscription { - Either::Left(bs.next().fuse()) - } else { - Either::Right(futures::future::pending()) - } => { - if let (Some(block), Some(to_block)) = (block, to_block_number) { - if block.number > to_block { - break; - } - } - }, - // Process incoming log - log = subscription.next() => { - if format_json { - if !first { - write!(output, ",")?; - } - first = false; - let log_str = serde_json::to_string(&log).unwrap(); - write!(output, "{log_str}")?; - } else { - let log_str = log.pretty() - .replacen('\n', "- ", 1) // Remove empty first line - .replace('\n', "\n "); // Indent - writeln!(output, "{log_str}")?; - } - }, - // Break on cancel signal, to allow for closing JSON bracket - _ = ctrl_c() => { - break; - }, - else => break, - } - } - - // If output was JSON, end with a closing bracket - if format_json { - write!(output, "]")?; - } - - Ok(()) - } - - pub async fn erc20_balance( - &self, - token: Address, - owner: Address, - block: Option, - ) -> Result { - Ok(IERC20::new(token, &self.provider) - .balanceOf(owner) - .block(block.unwrap_or_default()) - .call() - .await?) + Ok(if raw { + let encoded = tx.as_ref().encoded_2718(); + format!("0x{}", hex::encode(encoded)) + } else if let Some(ref field) = field { + get_pretty_tx_attr::(&tx, field.as_str()) + .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.clone()))? + } else if shell::is_json() { + // to_value first to sort json object keys + serde_json::to_value(&tx)?.to_string() + } else if to_request { + serde_json::to_string_pretty(&Into::::into(tx))? + } else { + tx.pretty() + }) } } @@ -1156,7 +1198,7 @@ impl SimpleCast { DynSolType::Uint(n) => { if MAX { let mut max = U256::MAX; - if n < 255 { + if n < 256 { max &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1)); } Ok(max.to_string()) @@ -1234,12 +1276,7 @@ impl SimpleCast { /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_fixed_point(value: &str, decimals: &str) -> Result { - // TODO: https://github.com/alloy-rs/core/pull/461 - let units: Unit = if let Ok(x) = decimals.parse() { - Unit::new(x).ok_or_else(|| eyre::eyre!("invalid unit"))? - } else { - decimals.parse()? - }; + let units: Unit = Unit::from_str(decimals)?; let n = ParseUnits::parse_units(value, units)?; Ok(n.to_string()) } @@ -1301,7 +1338,7 @@ impl SimpleCast { let mut out = String::new(); for s in values { let s = s.as_ref(); - out.push_str(s.strip_prefix("0x").unwrap_or(s)) + out.push_str(strip_0x(s)) } format!("0x{out}") } @@ -1413,6 +1450,7 @@ impl SimpleCast { /// assert_eq!(Cast::parse_units("2.5", 6)?, "2500000"); /// assert_eq!(Cast::parse_units("1.0", 12)?, "1000000000000"); // 12 decimals /// assert_eq!(Cast::parse_units("1.23", 3)?, "1230"); // 3 decimals + /// /// # Ok(()) /// # } /// ``` @@ -1434,6 +1472,7 @@ impl SimpleCast { /// assert_eq!(Cast::format_units("2500000", 6)?, "2.500000"); /// assert_eq!(Cast::format_units("1000000000000", 12)?, "1"); // 12 decimals /// assert_eq!(Cast::format_units("1230", 3)?, "1.230"); // 3 decimals + /// /// # Ok(()) /// # } /// ``` @@ -1618,6 +1657,48 @@ impl SimpleCast { Ok(hex::encode_prefixed(bytes32)) } + /// Pads hex data to a specified length + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let padded = Cast::pad("abcd", true, 20)?; + /// assert_eq!(padded, "0xabcd000000000000000000000000000000000000"); + /// + /// let padded = Cast::pad("abcd", false, 20)?; + /// assert_eq!(padded, "0x000000000000000000000000000000000000abcd"); + /// + /// let padded = Cast::pad("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", true, 32)?; + /// assert_eq!(padded, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2000000000000000000000000"); + /// + /// let padded = Cast::pad("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", false, 32)?; + /// assert_eq!(padded, "0x000000000000000000000000C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + /// + /// let err = Cast::pad("1234", false, 1).unwrap_err(); + /// assert_eq!(err.to_string(), "input length exceeds target length"); + /// + /// let err = Cast::pad("foobar", false, 32).unwrap_err(); + /// assert_eq!(err.to_string(), "input is not a valid hex"); + /// + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn pad(s: &str, right: bool, len: usize) -> Result { + let s = strip_0x(s); + let hex_len = len * 2; + + // Validate input + if s.len() > hex_len { + eyre::bail!("input length exceeds target length"); + } + if !s.chars().all(|c| c.is_ascii_hexdigit()) { + eyre::bail!("input is not a valid hex"); + } + + Ok(if right { format!("0x{s:0hex_len$}") }) + } + /// Decodes string from bytes32 value pub fn parse_bytes32_string(s: &str) -> Result { let bytes = hex::decode(s)?; @@ -1751,7 +1832,7 @@ impl SimpleCast { let func = get_func(sig)?; match encode_function_args(&func, args) { Ok(res) => Ok(hex::encode_prefixed(&res[4..])), - Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), + Err(e) => eyre::bail!("Could not ABI encode the function and arguments: {e}"), } } @@ -1781,11 +1862,91 @@ impl SimpleCast { let func = get_func(sig.as_str())?; let encoded = match encode_function_args_packed(&func, args) { Ok(res) => hex::encode(res), - Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), + Err(e) => eyre::bail!("Could not ABI encode the function and arguments: {e}"), }; Ok(format!("0x{encoded}")) } + /// Performs ABI encoding of an event to produce the topics and data. + /// + /// # Example + /// + /// ``` + /// use alloy_primitives::hex; + /// use cast::SimpleCast as Cast; + /// + /// let log_data = Cast::abi_encode_event( + /// "Transfer(address indexed from, address indexed to, uint256 value)", + /// &[ + /// "0x1234567890123456789012345678901234567890", + /// "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + /// "1000", + /// ], + /// ) + /// .unwrap(); + /// + /// // topic0 is the event selector + /// assert_eq!(log_data.topics().len(), 3); + /// assert_eq!( + /// log_data.topics()[0].to_string(), + /// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + /// ); + /// assert_eq!( + /// log_data.topics()[1].to_string(), + /// "0x0000000000000000000000001234567890123456789012345678901234567890" + /// ); + /// assert_eq!( + /// log_data.topics()[2].to_string(), + /// "0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd" + /// ); + /// assert_eq!( + /// hex::encode_prefixed(log_data.data), + /// "0x00000000000000000000000000000000000000000000000000000000000003e8" + /// ); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn abi_encode_event(sig: &str, args: &[impl AsRef]) -> Result { + let event = get_event(sig)?; + let tokens = std::iter::zip(&event.inputs, args) + .map(|(input, arg)| coerce_value(&input.ty, arg.as_ref())) + .collect::>>()?; + + let mut topics = vec![event.selector()]; + let mut data_tokens: Vec = Vec::new(); + + for (input, token) in event.inputs.iter().zip(tokens) { + if input.indexed { + let ty = DynSolType::parse(&input.ty)?; + if matches!( + ty, + DynSolType::String + | DynSolType::Bytes + | DynSolType::Array(_) + | DynSolType::Tuple(_) + ) { + // For dynamic types, hash the encoded value + let encoded = token.abi_encode(); + let hash = keccak256(encoded); + topics.push(hash); + } else { + // For fixed-size types, encode directly to 32 bytes + let mut encoded = [0u8; 32]; + let token_encoded = token.abi_encode(); + if token_encoded.len() <= 32 { + let start = 32 - token_encoded.len(); + encoded[start..].copy_from_slice(&token_encoded); + } + topics.push(B256::from(encoded)); + } + } else { + // Non-indexed parameters go into data + data_tokens.extend_from_slice(&token.abi_encode()); + } + } + + Ok(LogData::new_unchecked(topics, data_tokens.into())) + } + /// Performs ABI encoding to produce the hexadecimal calldata with the given arguments. /// /// # Example @@ -1844,20 +2005,20 @@ impl SimpleCast { match k_ty { // For value types, `h` pads the value to 32 bytes in the same way as when storing the // value in memory. - DynSolType::Bool | - DynSolType::Int(_) | - DynSolType::Uint(_) | - DynSolType::FixedBytes(_) | - DynSolType::Address | - DynSolType::Function => hasher.update(k.as_word().unwrap()), + DynSolType::Bool + | DynSolType::Int(_) + | DynSolType::Uint(_) + | DynSolType::FixedBytes(_) + | DynSolType::Address + | DynSolType::Function => hasher.update(k.as_word().unwrap()), // For strings and byte arrays, `h(k)` is just the unpadded data. DynSolType::String | DynSolType::Bytes => hasher.update(k.as_packed_seq().unwrap()), - DynSolType::Array(..) | - DynSolType::FixedArray(..) | - DynSolType::Tuple(..) | - DynSolType::CustomStruct { .. } => { + DynSolType::Array(..) + | DynSolType::FixedArray(..) + | DynSolType::Tuple(..) + | DynSolType::CustomStruct { .. } => { eyre::bail!("Type `{k_ty}` is not supported as a mapping key") } } @@ -1899,8 +2060,11 @@ impl SimpleCast { /// ``` pub fn keccak(data: &str) -> Result { // Hex-decode if data starts with 0x. - let hash = - if data.starts_with("0x") { keccak256(hex::decode(data)?) } else { keccak256(data) }; + let hash = if data.starts_with("0x") { + keccak256(hex::decode(data.trim_end())?) + } else { + keccak256(data) + }; Ok(hash.to_string()) } @@ -2053,7 +2217,7 @@ impl SimpleCast { let project = etherscan_project(metadata, tmp.path())?; let target_path = project.find_contract_path(&metadata.contract_name)?; - let flattened = Flattener::new(project, &target_path)?.flatten(); + let flattened = flatten(project, &target_path)?; if let Some(path) = output_path { fs::create_dir_all(path.parent().unwrap())?; @@ -2083,23 +2247,9 @@ impl SimpleCast { /// ``` pub fn disassemble(code: &[u8]) -> Result { let mut output = String::new(); - - for step in decode_instructions(code)? { - write!(output, "{:08x}: ", step.pc)?; - - if let Some(op) = step.op { - write!(output, "{op}")?; - } else { - write!(output, "INVALID")?; - } - - if !step.immediate.is_empty() { - write!(output, " {}", hex::encode_prefixed(step.immediate))?; - } - - writeln!(output)?; + for (pc, inst) in InstIter::new(code).with_pc() { + writeln!(output, "{pc:08x}: {inst}")?; } - Ok(output) } @@ -2126,7 +2276,7 @@ impl SimpleCast { eyre::bail!("invalid function signature"); }; - let num_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + let num_threads = rayon::current_num_threads(); let found = AtomicBool::new(false); let result: Option<(u32, String, String)> = @@ -2204,19 +2354,26 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_network::Ethereum; /// use cast::SimpleCast as Cast; /// /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; - /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; + /// let tx_envelope = Cast::decode_raw_transaction::(&tx)?; /// # Ok::<(), eyre::Report>(()) - pub fn decode_raw_transaction(tx: &str) -> Result { + pub fn decode_raw_transaction>( + tx: &str, + ) -> Result { let tx_hex = hex::decode(tx)?; - let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; - Ok(tx) + let tx: N::TxEnvelope = Decodable2718::decode_2718(&mut tx_hex.as_slice())?; + if let Ok(signer) = tx.recover_signer() { + Ok(serde_json::to_string_pretty(&Recovered::new_unchecked(tx, signer))?) + } else { + Ok(serde_json::to_string_pretty(&tx)?) + } } } -fn strip_0x(s: &str) -> &str { +pub(crate) fn strip_0x(s: &str) -> &str { s.strip_prefix("0x").unwrap_or(s) } @@ -2226,7 +2383,7 @@ fn explorer_client( api_url: Option, explorer_url: Option, ) -> Result { - let mut builder = Client::builder().with_chain_id(chain); + let mut builder = Client::builder(); let deduced = chain.etherscan_urls(); @@ -2249,7 +2406,7 @@ fn explorer_client( #[cfg(test)] mod tests { - use super::SimpleCast as Cast; + use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json}; use alloy_primitives::hex; #[test] @@ -2367,6 +2524,47 @@ mod tests { ); } + #[test] + fn calldata_decode_nested_json() { + let calldata = "0xdb5b0ed700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772bf190000000000000000000000000000000000000000000000000000000000020716000000000000000000000000af9d27ffe4d51ed54ac8eec78f2785d7e11e5ab100000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000404366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb73188600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5"; + let sig = "sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],uint64,uint64,address,bytes)"; + let decoded = Cast::calldata_decode(sig, calldata, true).unwrap(); + let json_value = serialize_value_as_json(DynSolValue::Array(decoded), None).unwrap(); + let expected = serde_json::json!([ + [ + [ + "0x04366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27", + "0x0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + [ + "0x093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed74", + "0x0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + [ + "0x60f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf7", + "0x0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + [ + "0x93dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb731886", + "0x0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + 1735573273, + 132886, + "0xAF9d27ffe4d51eD54AC8eEc78f2785D7E11E5ab1", + "0x334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5" + ]); + assert_eq!(json_value, expected); + } + #[test] fn concat_hex() { assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); @@ -2387,23 +2585,21 @@ mod tests { fn disassemble_incomplete_sequence() { let incomplete = &hex!("60"); // PUSH1 let disassembled = Cast::disassemble(incomplete).unwrap(); - assert_eq!(disassembled, "00000000: PUSH1 0x00\n"); + assert_eq!(disassembled, "00000000: PUSH1\n"); let complete = &hex!("6000"); // PUSH1 0x00 - let disassembled = Cast::disassemble(complete); - assert!(disassembled.is_ok()); + let disassembled = Cast::disassemble(complete).unwrap(); + assert_eq!(disassembled, "00000000: PUSH1 0x00\n"); let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes - let disassembled = Cast::disassemble(incomplete).unwrap(); + assert_eq!(disassembled, "00000000: PUSH32\n"); + let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes + let disassembled = Cast::disassemble(complete).unwrap(); assert_eq!( disassembled, - "00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n" + "00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\n" ); - - let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes - let disassembled = Cast::disassemble(complete); - assert!(disassembled.is_ok()); } } diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 4ba86111bd9e4..763eb132ddb5c 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,26 +1,31 @@ +#[cfg(feature = "optimism")] +use crate::cmd::da_estimate::DAEstimateArgs; use crate::cmd::{ - access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs, + access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, + batch_mktx::BatchMakeTxArgs, batch_send::BatchSendArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, - da_estimate::DAEstimateArgs, estimate::EstimateArgs, find_block::FindBlockArgs, - interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, - send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands, wallet::WalletSubcommands, + erc20::Erc20Subcommand, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, keychain::KeychainSubcommand, logs::LogsArgs, mktx::MakeTxArgs, + rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, tempo::TempoSubcommand, + tip20::Tip20Subcommand, trace::TraceArgs, txpool::TxPoolSubcommands, vaddr::VaddrSubcommand, + wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; -use alloy_primitives::{Address, Selector, B256, U256}; +use alloy_primitives::{Address, B256, Selector, U256}; use alloy_rpc_types::BlockId; -use clap::{Parser, Subcommand, ValueHint}; +use clap::{ArgAction, Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; +use foundry_evm_networks::NetworkVariant; use std::{path::PathBuf, str::FromStr}; - /// A Swiss Army knife for interacting with Ethereum applications from the command line. #[derive(Parser)] #[command( name = "cast", version = SHORT_VERSION, long_version = LONG_VERSION, - after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", + after_help = "Find more information in the book: https://getfoundry.sh/cast/overview", next_display_order = None, )] pub struct Cast { @@ -115,6 +120,8 @@ pub enum CastSubcommand { ToCheckSumAddress { /// The address to convert. address: Option

, + /// EIP-155 chain ID to encode the address using EIP-1191. + chain_id: Option, }, /// Convert hex data to an ASCII string. @@ -149,6 +156,25 @@ pub enum CastSubcommand { bytes: Option, }, + /// Pads hex data to a specified length. + #[command(visible_aliases = &["pd"])] + Pad { + /// The hex data to pad. + data: Option, + + /// Right-pad the data (instead of left-pad). + #[arg(long)] + right: bool, + + /// Left-pad the data (default). + #[arg(long, conflicts_with = "right")] + left: bool, + + /// Target length in bytes (default: 32). + #[arg(long, default_value = "32")] + len: usize, + }, + /// Convert an integer into a fixed point number. #[command(visible_aliases = &["--to-fix", "tf", "2f"])] ToFixedPoint { @@ -357,14 +383,22 @@ pub enum CastSubcommand { block: Option, /// If specified, only get the given field of the block. - #[arg(long, short)] - field: Option, + #[arg(short, long = "field", aliases = ["fields"], num_args = 0.., action = ArgAction::Append, value_delimiter = ',')] + fields: Vec, + + /// Print the raw RLP encoded block header. + #[arg(long, conflicts_with = "fields")] + raw: bool, #[arg(long, env = "CAST_FULL_BLOCK")] full: bool, #[command(flatten)] rpc: RpcOpts, + + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, }, /// Get the latest block number. @@ -422,9 +456,31 @@ pub enum CastSubcommand { address: Option
, /// The nonce of the deployer address. - #[arg(long)] + #[arg( + long, + conflicts_with = "salt", + conflicts_with = "init_code", + conflicts_with = "init_code_hash" + )] nonce: Option, + /// The salt for CREATE2 address computation. + #[arg(long, conflicts_with = "nonce")] + salt: Option, + + /// The init code for CREATE2 address computation. + #[arg( + long, + requires = "salt", + conflicts_with = "init_code_hash", + conflicts_with = "nonce" + )] + init_code: Option, + + /// The init code hash for CREATE2 address computation. + #[arg(long, requires = "salt", conflicts_with = "init_code", conflicts_with = "nonce")] + init_code_hash: Option, + #[command(flatten)] rpc: RpcOpts, }, @@ -468,6 +524,14 @@ pub enum CastSubcommand { #[command(flatten)] rpc: RpcOpts, + + /// If specified, the transaction will be converted to a TransactionRequest JSON format. + #[arg(long)] + to_request: bool, + + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, }, /// Get the transaction receipt for a transaction. @@ -495,6 +559,14 @@ pub enum CastSubcommand { #[command(name = "send", visible_alias = "s")] SendTx(SendTxArgs), + /// Build and sign a batch transaction (Tempo). + #[command(name = "batch-mktx", visible_alias = "bm")] + BatchMakeTx(BatchMakeTxArgs), + + /// Sign and publish a batch transaction (Tempo). + #[command(name = "batch-send", visible_alias = "bs")] + BatchSend(BatchSendArgs), + /// Publish a raw transaction to the network. #[command(name = "publish", visible_alias = "p")] PublishTx { @@ -523,7 +595,12 @@ pub enum CastSubcommand { sig: String, /// The ABI-encoded calldata. - calldata: String, + #[arg(required_unless_present = "file", index = 2)] + calldata: Option, + + /// Load ABI-encoded calldata from a file instead. + #[arg(long = "file", short = 'f', conflicts_with = "calldata")] + file: Option, }, /// Decode ABI-encoded string. @@ -588,6 +665,17 @@ pub enum CastSubcommand { args: Vec, }, + /// ABI encode an event and its arguments to generate topics and data. + #[command(visible_alias = "aee")] + AbiEncodeEvent { + /// The event signature. + sig: String, + + /// The arguments of the event. + #[arg(allow_hyphen_values = true)] + args: Vec, + }, + /// Compute the storage slot for an entry in a mapping. #[command(visible_alias = "in")] Index { @@ -987,6 +1075,10 @@ pub enum CastSubcommand { #[command(visible_alias = "bi")] Bind(BindArgs), + /// Convert Beacon payload to execution payload. + #[command(visible_alias = "b2e")] + B2EPayload(B2EPayloadArgs), + /// Get the selector for a function. #[command(visible_alias = "si")] Sig { @@ -1009,13 +1101,9 @@ pub enum CastSubcommand { #[command(visible_alias = "com")] Completions { #[arg(value_enum)] - shell: clap_complete::Shell, + shell: foundry_cli::clap::Shell, }, - /// Generate Fig autocompletion spec. - #[command(visible_alias = "fig")] - GenerateFigSpec, - /// Runs a published transaction in a local environment and prints the trace. #[command(visible_alias = "r")] Run(RunArgs), @@ -1046,7 +1134,18 @@ pub enum CastSubcommand { /// Decodes a raw signed EIP 2718 typed transaction #[command(visible_aliases = &["dt", "decode-tx"])] - DecodeTransaction { tx: Option }, + DecodeTransaction { + /// Encoded transaction + tx: Option, + + /// Specify the Network for correct encoding. + #[arg(long, short, num_args = 1, value_name = "NETWORK")] + network: Option, + }, + + /// Recovery an EIP-7702 authority from a Authorization JSON string. + #[command(visible_aliases = &["decode-auth"])] + RecoverAuthority { auth: String }, /// Extracts function selectors and arguments from bytecode #[command(visible_alias = "sel")] @@ -1066,8 +1165,46 @@ pub enum CastSubcommand { command: TxPoolSubcommands, }, /// Estimates the data availability size of a given opstack block. + #[cfg(feature = "optimism")] #[command(name = "da-estimate")] DAEstimate(DAEstimateArgs), + + /// ERC20 token operations. + #[command(visible_alias = "erc20")] + Erc20Token { + #[command(subcommand)] + command: Erc20Subcommand, + }, + + /// TIP-20 token operations (Tempo). + #[command(visible_alias = "tip20")] + Tip20Token { + #[command(subcommand)] + command: Tip20Subcommand, + }, + + /// Tempo keychain (access key) management. + #[command(visible_alias = "kc")] + Keychain { + #[command(subcommand)] + command: KeychainSubcommand, + }, + + /// Tempo wallet integration (login, etc.). + Tempo { + #[command(subcommand)] + command: TempoSubcommand, + }, + + /// TIP-1022 virtual address registry operations (Tempo). + #[command(visible_alias = "vaddr")] + VirtualAddress { + #[command(subcommand)] + command: VaddrSubcommand, + }, + + #[command(name = "trace")] + Trace(TraceArgs), } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index ced4a825979f0..f386edddc541e 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{hex, U256}; +use alloy_primitives::{U256, hex}; use alloy_rlp::{Buf, Decodable, Encodable, Header}; use eyre::Context; use serde_json::Value; @@ -11,7 +11,7 @@ use std::fmt; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Item { Data(Vec), - Array(Vec), + Array(Vec), } impl Encodable for Item { diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs new file mode 100644 index 0000000000000..737c33f5b70de --- /dev/null +++ b/crates/cast/src/tempo.rs @@ -0,0 +1,3 @@ +//! Tempo transaction helpers used by Cast-facing commands. + +pub use foundry_common::tempo::{TempoSponsor, TempoSponsorPreview, resolve_tempo_sponsor_signer}; diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 829d8e571ca80..b58136f4ae9de 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -1,33 +1,112 @@ use crate::traces::identifier::SignaturesIdentifier; -use alloy_consensus::{SidecarBuilder, SignableTransaction, SimpleCoder}; +use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_dyn_abi::ErrorExt; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; -use alloy_network::{ - AnyNetwork, AnyTypedTransaction, TransactionBuilder, TransactionBuilder4844, - TransactionBuilder7702, -}; -use alloy_primitives::{hex, Address, Bytes, TxKind, U256}; -use alloy_provider::Provider; -use alloy_rpc_types::{AccessList, Authorization, TransactionInput, TransactionRequest}; -use alloy_serde::WithOtherFields; +use alloy_network::{Network, TransactionBuilder}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U64, U256, hex}; +use alloy_provider::{PendingTransactionBuilder, Provider}; +use alloy_rpc_types::{AccessList, Authorization, TransactionInputKind}; use alloy_signer::Signer; use alloy_transport::TransportError; -use eyre::Result; -use foundry_block_explorers::EtherscanApiVersion; +use clap::Args; +use eyre::{Result, WrapErr}; use foundry_cli::{ - opts::{CliAuthorizationList, TransactionOpts}, + opts::{CliAuthorizationList, EthereumOpts, TempoOpts, TransactionOpts}, utils::{self, parse_function_args}, }; -use foundry_common::fmt::format_tokens; +use foundry_common::{ + FoundryTransactionBuilder, TransactionReceiptWithRevertReason, fmt::*, + get_pretty_receipt_w_reason_attr, shell, +}; use foundry_config::{Chain, Config}; -use foundry_wallets::{WalletOpts, WalletSigner}; +use foundry_wallets::{BrowserWalletOpts, TempoAccessKeyConfig, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; -use std::fmt::Write; +use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration}; + +#[derive(Debug, Clone, Args)] +pub struct SendTxOpts { + /// Only print the transaction hash and exit immediately. + #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] + pub cast_async: bool, + + /// Wait for transaction receipt synchronously instead of polling. + /// Note: uses `eth_sendTransactionSync` which may not be supported by all clients. + #[arg(long, conflicts_with = "async")] + pub sync: bool, + + /// The number of confirmations until the receipt is fetched. + #[arg(long, default_value = "1")] + pub confirmations: u64, + + /// Timeout for sending the transaction. + #[arg(long, env = "ETH_TIMEOUT")] + pub timeout: Option, + + /// Polling interval for transaction receipts (in seconds). + #[arg(long, alias = "poll-interval", env = "ETH_POLL_INTERVAL")] + pub poll_interval: Option, + + /// Ethereum options + #[command(flatten)] + pub eth: EthereumOpts, + + /// Browser wallet options + #[command(flatten)] + pub browser: BrowserWalletOpts, +} + +/// Transaction options shared across cast commands that submit on-chain transactions. +#[derive(Debug, Clone, Args)] +#[command(next_help_heading = "Transaction options")] +pub struct TxParams { + /// Gas limit for the transaction. + #[arg(long, env = "ETH_GAS_LIMIT")] + pub gas_limit: Option, + + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. + #[arg(long, env = "ETH_GAS_PRICE")] + pub gas_price: Option, + + /// Max priority fee per gas for EIP1559 transactions. + #[arg(long, env = "ETH_PRIORITY_GAS_PRICE")] + pub priority_gas_price: Option, + + /// Nonce for the transaction. + #[arg(long)] + pub nonce: Option, + + #[command(flatten)] + pub tempo: TempoOpts, +} + +impl TxParams { + pub(crate) fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if let Some(gas_limit) = self.gas_limit { + tx.set_gas_limit(gas_limit.to()); + } + + if let Some(gas_price) = self.gas_price { + if legacy { + tx.set_gas_price(gas_price.to()); + } else { + tx.set_max_fee_per_gas(gas_price.to()); + } + } + + if !legacy && let Some(priority_fee) = self.priority_gas_price { + tx.set_max_priority_fee_per_gas(priority_fee.to()); + } + + self.tempo.apply::(tx, self.nonce.map(|n| n.to())); + } +} /// Different sender kinds used by [`CastTxBuilder`]. -#[expect(clippy::large_enum_variant)] pub enum SenderKind<'a> { /// An address without signer. Used for read-only calls and transactions sent through unlocked /// accounts. @@ -35,7 +114,7 @@ pub enum SenderKind<'a> { /// A reference to a signer. Signer(&'a WalletSigner), /// An owned signer. - OwnedSigner(WalletSigner), + OwnedSigner(Box), } impl SenderKind<'_> { @@ -56,10 +135,10 @@ impl SenderKind<'_> { /// If from is not specified, but there is a signer configured, returns the signer's address /// If from is not specified and there is no signer configured, returns zero address pub async fn from_wallet_opts(opts: WalletOpts) -> Result { - if let Some(from) = opts.from { + if let (Some(signer), _) = opts.maybe_signer().await? { + Ok(Self::OwnedSigner(Box::new(signer))) + } else if let Some(from) = opts.from { Ok(from.into()) - } else if let Ok(signer) = opts.signer().await { - Ok(Self::OwnedSigner(signer)) } else { Ok(Address::ZERO.into()) } @@ -69,7 +148,7 @@ impl SenderKind<'_> { pub fn as_signer(&self) -> Option<&WalletSigner> { match self { Self::Signer(signer) => Some(signer), - Self::OwnedSigner(signer) => Some(signer), + Self::OwnedSigner(signer) => Some(signer.as_ref()), _ => None, } } @@ -89,7 +168,7 @@ impl<'a> From<&'a WalletSigner> for SenderKind<'a> { impl From for SenderKind<'_> { fn from(signer: WalletSigner) -> Self { - Self::OwnedSigner(signer) + Self::OwnedSigner(Box::new(signer)) } } @@ -98,16 +177,16 @@ pub fn validate_from_address( specified_from: Option
, signer_address: Address, ) -> Result<()> { - if let Some(specified_from) = specified_from { - if specified_from != signer_address { - eyre::bail!( + if let Some(specified_from) = specified_from + && specified_from != signer_address + { + eyre::bail!( "\ The specified sender via CLI/env vars does not match the sender configured via the hardware wallet's HD Path. Please use the `--hd-path ` parameter to specify the BIP32 Path which corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." ) - } } Ok(()) } @@ -130,75 +209,232 @@ pub struct InputState { func: Option, } -/// Builder type constructing [TransactionRequest] from cast send/mktx inputs. +pub struct CastTxSender { + provider: P, + _phantom: PhantomData, +} + +impl> CastTxSender +where + N::TransactionRequest: FoundryTransactionBuilder, + N::ReceiptResponse: UIfmt + UIfmtReceiptExt, +{ + /// Creates a new Cast instance responsible for sending transactions. + pub const fn new(provider: P) -> Self { + Self { provider, _phantom: PhantomData } + } + + /// Sends a transaction and waits for receipt synchronously + pub async fn send_sync(&self, tx: N::TransactionRequest) -> Result { + let mut receipt = TransactionReceiptWithRevertReason:: { + receipt: self.provider.send_transaction_sync(tx).await?, + revert_reason: None, + }; + // Allow to fail silently + let _ = receipt.update_revert_reason(&self.provider).await; + + self.format_receipt(receipt, None) + } + + /// Sends a transaction to the specified address + /// + /// # Example + /// + /// ``` + /// use cast::tx::CastTxSender; + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_serde::WithOtherFields; + /// use alloy_rpc_types::{TransactionRequest}; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; + /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; /// + /// + /// sol!( + /// function greet(string greeting) public; + /// ); + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;; + /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; + /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; + /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); + /// let gas = U256::from_str("200000").unwrap(); + /// let value = U256::from_str("1").unwrap(); + /// let nonce = U256::from_str("1").unwrap(); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); + /// let tx = WithOtherFields::new(tx); + /// let cast = CastTxSender::new(provider); + /// let data = cast.send(tx).await?; + /// println!("{:#?}", data); + /// # Ok(()) + /// # } + /// ``` + pub async fn send(&self, tx: N::TransactionRequest) -> Result> { + let res = self.provider.send_transaction(tx).await?; + + Ok(res) + } + + /// Sends a raw RLP-encoded transaction via `eth_sendRawTransaction`. + /// + /// Used for transaction types that the standard Alloy network stack doesn't understand + /// (e.g., Tempo transactions). + pub async fn send_raw(&self, raw_tx: &[u8]) -> Result> { + let res = self.provider.send_raw_transaction(raw_tx).await?; + Ok(res) + } + + /// Prints the transaction hash (if async) or waits for the receipt and prints it. + /// + /// This is the shared "output" path used by both the normal send flow and the browser wallet + /// flow (which sends the transaction out-of-band and only has a tx hash). + pub async fn print_tx_result( + &self, + tx_hash: B256, + cast_async: bool, + confs: u64, + timeout: u64, + ) -> Result<()> { + if cast_async { + sh_println!("{tx_hash:#x}")?; + } else { + let receipt = + self.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?; + sh_println!("{receipt}")?; + } + Ok(()) + } + + /// # Example + /// + /// ``` + /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; + /// use cast::tx::CastTxSender; + /// + /// async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; + /// let cast = CastTxSender::new(provider); + /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; + /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?; + /// println!("{}", receipt); + /// # Ok(()) + /// # } + /// ``` + pub async fn receipt( + &self, + tx_hash: String, + field: Option, + confs: u64, + timeout: Option, + cast_async: bool, + ) -> Result { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + + let mut receipt = TransactionReceiptWithRevertReason:: { + receipt: match self.provider.get_transaction_receipt(tx_hash).await? { + Some(r) => r, + None => { + // if the async flag is provided, immediately exit if no tx is found, otherwise + // try to poll for it + if cast_async { + eyre::bail!("tx not found: {:?}", tx_hash) + } + PendingTransactionBuilder::::new(self.provider.root().clone(), tx_hash) + .with_required_confirmations(confs) + .with_timeout(timeout.map(Duration::from_secs)) + .get_receipt() + .await? + } + }, + revert_reason: None, + }; + + // Allow to fail silently + let _ = receipt.update_revert_reason(&self.provider).await; + + self.format_receipt(receipt, field) + } + + /// Helper method to format transaction receipts consistently + fn format_receipt( + &self, + receipt: TransactionReceiptWithRevertReason, + field: Option, + ) -> Result { + Ok(if let Some(ref field) = field { + get_pretty_receipt_w_reason_attr(&receipt, field) + .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))? + } else if shell::is_json() { + // to_value first to sort json object keys + serde_json::to_value(&receipt)?.to_string() + } else { + receipt.pretty() + }) + } +} + +/// Builder type constructing generic TransactionRequest from cast send/mktx inputs. /// /// It is implemented as a stateful builder with expected state transition of [InitState] -> /// [ToState] -> [InputState]. #[derive(Debug)] -pub struct CastTxBuilder { +pub struct CastTxBuilder { provider: P, - tx: WithOtherFields, + pub(crate) tx: N::TransactionRequest, /// Whether the transaction should be sent as a legacy transaction. legacy: bool, blob: bool, - auth: Option, + /// Whether the blob transaction should use EIP-4844 (legacy) format instead of EIP-7594. + eip4844: bool, + /// Whether to fill gas, fees and nonce. Set to `false` for read-only calls + /// (eth_call, eth_estimateGas, eth_createAccessList). + fill: bool, + auth: Vec, chain: Chain, etherscan_api_key: Option, - etherscan_api_version: EtherscanApiVersion, + etherscan_api_url: Option, access_list: Option>, state: S, } -impl> CastTxBuilder { +impl CastTxBuilder { + /// Returns the resolved chain for this builder. + pub const fn chain(&self) -> Chain { + self.chain + } +} + +impl> CastTxBuilder +where + N::TransactionRequest: FoundryTransactionBuilder, +{ /// Creates a new instance of [CastTxBuilder] filling transaction with fields present in /// provided [TransactionOpts]. pub async fn new(provider: P, tx_opts: TransactionOpts, config: &Config) -> Result { - let mut tx = WithOtherFields::::default(); + let mut tx = N::TransactionRequest::default(); let chain = utils::get_chain(config.chain, &provider).await?; - let etherscan_api_version = config.get_etherscan_api_version(Some(chain)); - let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); + let etherscan_config = config.get_etherscan_config_with_chain(Some(chain)).ok().flatten(); + let etherscan_api_key = etherscan_config.as_ref().map(|c| c.key.clone()); + let etherscan_api_url = etherscan_config.map(|c| c.api_url); // mark it as legacy if requested or the chain is legacy and no 7702 is provided. - let legacy = tx_opts.legacy || (chain.is_legacy() && tx_opts.auth.is_none()); - - if let Some(gas_limit) = tx_opts.gas_limit { - tx.set_gas_limit(gas_limit.to()); - } - - if let Some(value) = tx_opts.value { - tx.set_value(value); - } + let legacy = tx_opts.legacy || (chain.is_legacy() && tx_opts.auth.is_empty()); - if let Some(gas_price) = tx_opts.gas_price { - if legacy { - tx.set_gas_price(gas_price.to()); - } else { - tx.set_max_fee_per_gas(gas_price.to()); - } - } - - if !legacy { - if let Some(priority_fee) = tx_opts.priority_gas_price { - tx.set_max_priority_fee_per_gas(priority_fee.to()); - } - } - - if let Some(max_blob_fee) = tx_opts.blob_gas_price { - tx.set_max_fee_per_blob_gas(max_blob_fee.to()) - } - - if let Some(nonce) = tx_opts.nonce { - tx.set_nonce(nonce.to()); - } + // Apply gas, value, fee, and network-specific options. + tx_opts.apply::(&mut tx, legacy); Ok(Self { provider, tx, legacy, blob: tx_opts.blob, + eip4844: tx_opts.eip4844, + fill: true, chain, etherscan_api_key, - etherscan_api_version, + etherscan_api_url, auth: tx_opts.auth, access_list: tx_opts.access_list, state: InitState, @@ -206,16 +442,18 @@ impl> CastTxBuilder { } /// Sets [TxKind] for this builder and changes state to [ToState]. - pub async fn with_to(self, to: Option) -> Result> { + pub async fn with_to(self, to: Option) -> Result> { let to = if let Some(to) = to { Some(to.resolve(&self.provider).await?) } else { None }; Ok(CastTxBuilder { provider: self.provider, tx: self.tx, legacy: self.legacy, blob: self.blob, + eip4844: self.eip4844, + fill: self.fill, chain: self.chain, etherscan_api_key: self.etherscan_api_key, - etherscan_api_version: self.etherscan_api_version, + etherscan_api_url: self.etherscan_api_url, auth: self.auth, access_list: self.access_list, state: ToState { to }, @@ -223,7 +461,10 @@ impl> CastTxBuilder { } } -impl> CastTxBuilder { +impl> CastTxBuilder +where + N::TransactionRequest: FoundryTransactionBuilder, +{ /// Accepts user-provided code, sig and args params and constructs calldata for the transaction. /// If code is present, input will be set to code + encoded constructor arguments. If no code is /// present, input is set to just provided arguments. @@ -232,7 +473,7 @@ impl> CastTxBuilder { code: Option, sig: Option, args: Vec, - ) -> Result> { + ) -> Result> { let (mut args, func) = if let Some(sig) = sig { parse_function_args( &sig, @@ -241,7 +482,7 @@ impl> CastTxBuilder { self.chain, &self.provider, self.etherscan_api_key.as_deref(), - self.etherscan_api_version, + self.etherscan_api_url.as_deref(), ) .await? } else { @@ -257,8 +498,8 @@ impl> CastTxBuilder { }; if self.state.to.is_none() && code.is_none() { - let has_value = self.tx.value.is_some_and(|v| !v.is_zero()); - let has_auth = self.auth.is_some(); + let has_value = self.tx.value().is_some_and(|v| !v.is_zero()); + let has_auth = !self.auth.is_empty(); // We only allow user to omit the recipient address if transaction is an EIP-7702 tx // without a value. if !has_auth || has_value { @@ -271,9 +512,11 @@ impl> CastTxBuilder { tx: self.tx, legacy: self.legacy, blob: self.blob, + eip4844: self.eip4844, + fill: self.fill, chain: self.chain, etherscan_api_key: self.etherscan_api_key, - etherscan_api_version: self.etherscan_api_version, + etherscan_api_url: self.etherscan_api_url, auth: self.auth, access_list: self.access_list, state: InputState { kind: self.state.to.into(), input, func }, @@ -281,139 +524,212 @@ impl> CastTxBuilder { } } -impl> CastTxBuilder { - /// Builds [TransactionRequest] and fiils missing fields. Returns a transaction which is ready - /// to be broadcasted. +impl> CastTxBuilder +where + N::TransactionRequest: FoundryTransactionBuilder, +{ + /// Builds the TransactionRequest. Fills gas, fees and nonce unless [`raw`](Self::raw) was + /// called. pub async fn build( self, sender: impl Into>, - ) -> Result<(WithOtherFields, Option)> { - self._build(sender, true, false).await + ) -> Result<(N::TransactionRequest, Option)> { + let fill = self.fill; + self._build(sender, fill, None).await } - /// Builds [TransactionRequest] without filling missing fields. Used for read-only calls such as - /// eth_call, eth_estimateGas, etc - pub async fn build_raw( - self, - sender: impl Into>, - ) -> Result<(WithOtherFields, Option)> { - self._build(sender, false, false).await - } - - /// Builds an unsigned RLP-encoded raw transaction. + /// Builds a transaction that will be signed by a Tempo access key. /// - /// Returns the hex encoded string representation of the transaction. - pub async fn build_unsigned_raw(self, from: Address) -> Result { - let (tx, _) = self._build(SenderKind::Address(from), true, true).await?; - let tx = tx.build_unsigned()?; - match tx { - AnyTypedTransaction::Ethereum(t) => Ok(hex::encode_prefixed(t.encoded_for_signing())), - _ => eyre::bail!("Cannot generate unsigned transaction for non-Ethereum transactions"), - } + /// The access-key id is set before gas estimation. If the access key needs on-chain + /// provisioning, its authorization is embedded before access-list/gas estimation and before + /// any sponsor digest can be computed. + pub async fn build_with_access_key( + mut self, + sender: impl Into>, + access_key: &TempoAccessKeyConfig, + ) -> Result<(N::TransactionRequest, Option)> { + self.tx.set_key_id(access_key.key_address); + let fill = self.fill; + self._build(sender, fill, Some(access_key)).await } async fn _build( mut self, sender: impl Into>, fill: bool, - unsigned: bool, - ) -> Result<(WithOtherFields, Option)> { + access_key: Option<&TempoAccessKeyConfig>, + ) -> Result<(N::TransactionRequest, Option)> { + // prepare let sender = sender.into(); - let from = sender.address(); + self.prepare(&sender); + + // For batch transactions with calls, clear `to` and `value` so the node correctly + // identifies this as an AA batch transaction. The `calls` field determines the actual + // targets. If `to` is set, `build_aa()` would add a spurious extra call. + self.tx.clear_batch_to(); + + // resolve + let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; + self.resolve_auth(&sender, tx_nonce).await?; + if let Some(access_key) = access_key { + self.tx + .prepare_access_key_authorization( + &self.provider, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + } + self.resolve_access_list().await?; - self.tx.set_kind(self.state.kind); + // fill + if fill { + self.fill_fees().await?; + } - // we set both fields to the same value because some nodes only accept the legacy `data` field: - let input = Bytes::copy_from_slice(&self.state.input); - self.tx.input = TransactionInput { input: Some(input.clone()), data: Some(input) }; + Ok((self.tx, self.state.func)) + } - self.tx.set_from(from); + /// Sets the core transaction fields from the builder state: kind, input, from, and chain id. + fn prepare(&mut self, sender: &SenderKind<'_>) { + self.tx.set_kind(self.state.kind); + // We set both fields to the same value because some nodes only accept the legacy + // `data` field: https://github.com/foundry-rs/foundry/issues/7764#issuecomment-2210453249 + self.tx.set_input_kind(self.state.input.clone(), TransactionInputKind::Both); + self.tx.set_from(sender.address()); self.tx.set_chain_id(self.chain.id()); + } - let tx_nonce = if let Some(nonce) = self.tx.nonce { - nonce + /// Resolves the transaction nonce. Returns the existing nonce or fetches one from the + /// provider. Only sets it on the transaction when `fill` is true. + async fn resolve_nonce(&mut self, from: Address, fill: bool) -> Result { + if let Some(nonce) = self.tx.nonce() { + Ok(nonce) } else { let nonce = self.provider.get_transaction_count(from).await?; if fill { - self.tx.nonce = Some(nonce); + self.tx.set_nonce(nonce); } - nonce - }; - - if !unsigned { - self.resolve_auth(sender, tx_nonce).await?; - } else if self.auth.is_some() { - let Some(CliAuthorizationList::Signed(signed_auth)) = self.auth.take() else { - eyre::bail!( - "SignedAuthorization needs to be provided for generating unsigned 7702 txs" - ) - }; - - self.tx.set_authorization_list(vec![signed_auth]); + Ok(nonce) } + } + /// Resolves the access list. Fetches from the provider if `--access-list` was passed without + /// a value. + async fn resolve_access_list(&mut self) -> Result<()> { if let Some(access_list) = match self.access_list.take() { None => None, - // --access-list provided with no value, call the provider to create it Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list), - // Access list provided as a string, attempt to parse it Some(Some(access_list)) => Some(access_list), } { self.tx.set_access_list(access_list); } + Ok(()) + } + + /// Parses the passed --auth values and sets the authorization list on the transaction. + /// + /// If a signer is available in `sender`, address-based auths will be signed. + /// If no signer is available, all auths must be pre-signed. + async fn resolve_auth(&mut self, sender: &SenderKind<'_>, tx_nonce: u64) -> Result<()> { + if self.auth.is_empty() { + return Ok(()); + } + + let auths = std::mem::take(&mut self.auth); + + // Validate that at most one address-based auth is provided (multiple addresses are + // almost always unintended). + let address_auth_count = + auths.iter().filter(|a| matches!(a, CliAuthorizationList::Address(_))).count(); + if address_auth_count > 1 { + eyre::bail!( + "Multiple address-based authorizations provided. Only one address can be specified; \ + use pre-signed authorizations (hex-encoded) for multiple authorizations." + ); + } - if !fill { - return Ok((self.tx, self.state.func)); + let mut signed_auths = Vec::with_capacity(auths.len()); + + for auth in auths { + let signed_auth = match auth { + CliAuthorizationList::Address(address) => { + let auth = Authorization { + chain_id: U256::from(self.chain.id()), + nonce: tx_nonce + 1, + address, + }; + + let Some(signer) = sender.as_signer() else { + eyre::bail!( + "No signer available to sign authorization. \ + Provide a pre-signed authorization (hex-encoded) instead." + ); + }; + let signature = signer.sign_hash(&auth.signature_hash()).await?; + + auth.into_signed(signature) + } + CliAuthorizationList::Signed(auth) => auth, + }; + signed_auths.push(signed_auth); } - if self.legacy && self.tx.gas_price.is_none() { - self.tx.gas_price = Some(self.provider.get_gas_price().await?); + self.tx.set_authorization_list(signed_auths); + + Ok(()) + } + + /// Fills gas price, EIP-1559 fees, blob fees, and gas limit from the provider. + /// + /// Only fills values that haven't been explicitly set by the user. + async fn fill_fees(&mut self) -> Result<()> { + if self.legacy && self.tx.gas_price().is_none() { + self.tx.set_gas_price(self.provider.get_gas_price().await?); } - if self.blob && self.tx.max_fee_per_blob_gas.is_none() { - self.tx.max_fee_per_blob_gas = Some(self.provider.get_blob_base_fee().await?) + if self.blob && self.tx.max_fee_per_blob_gas().is_none() { + self.tx.set_max_fee_per_blob_gas(self.provider.get_blob_base_fee().await?) } - if !self.legacy && - (self.tx.max_fee_per_gas.is_none() || self.tx.max_priority_fee_per_gas.is_none()) + if !self.legacy + && (self.tx.max_fee_per_gas().is_none() || self.tx.max_priority_fee_per_gas().is_none()) { let estimate = self.provider.estimate_eip1559_fees().await?; - if !self.legacy { - if self.tx.max_fee_per_gas.is_none() { - self.tx.max_fee_per_gas = Some(estimate.max_fee_per_gas); - } + if self.tx.max_fee_per_gas().is_none() { + self.tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } - if self.tx.max_priority_fee_per_gas.is_none() { - self.tx.max_priority_fee_per_gas = Some(estimate.max_priority_fee_per_gas); - } + if self.tx.max_priority_fee_per_gas().is_none() { + self.tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); } } - if self.tx.gas.is_none() { + if self.tx.gas_limit().is_none() { self.estimate_gas().await?; } - Ok((self.tx, self.state.func)) + Ok(()) } /// Estimate tx gas from provider call. Tries to decode custom error if execution reverted. async fn estimate_gas(&mut self) -> Result<()> { match self.provider.estimate_gas(self.tx.clone()).await { Ok(estimated) => { - self.tx.gas = Some(estimated); + self.tx.set_gas_limit(estimated); Ok(()) } Err(err) => { if let TransportError::ErrorResp(payload) = &err { // If execution reverted with code 3 during provider gas estimation then try // to decode custom errors and append it to the error message. - if payload.code == 3 { - if let Some(data) = &payload.data { - if let Ok(Some(decoded_error)) = decode_execution_revert(data).await { - eyre::bail!("Failed to estimate gas: {}: {}", err, decoded_error) - } - } + if payload.code == 3 + && let Some(data) = &payload.data + && let Ok(Some(decoded_error)) = decode_execution_revert(data).await + { + eyre::bail!("Failed to estimate gas: {}: {}", err, decoded_error) } } eyre::bail!("Failed to estimate gas: {}", err) @@ -421,50 +737,30 @@ impl> CastTxBuilder { } } - /// Parses the passed --auth value and sets the authorization list on the transaction. - async fn resolve_auth(&mut self, sender: SenderKind<'_>, tx_nonce: u64) -> Result<()> { - let Some(auth) = self.auth.take() else { return Ok(()) }; - - let auth = match auth { - CliAuthorizationList::Address(address) => { - let auth = Authorization { - chain_id: U256::from(self.chain.id()), - nonce: tx_nonce + 1, - address, - }; - - let Some(signer) = sender.as_signer() else { - eyre::bail!("No signer available to sign authorization"); - }; - let signature = signer.sign_hash(&auth.signature_hash()).await?; - - auth.into_signed(signature) - } - CliAuthorizationList::Signed(auth) => auth, - }; - - self.tx.set_authorization_list(vec![auth]); - - Ok(()) - } -} - -impl CastTxBuilder -where - P: Provider, -{ + /// Populates the blob sidecar for the transaction if any blob data was provided. pub fn with_blob_data(mut self, blob_data: Option>) -> Result { let Some(blob_data) = blob_data else { return Ok(self) }; let mut coder = SidecarBuilder::::default(); coder.ingest(&blob_data); - let sidecar = coder.build()?; - self.tx.set_blob_sidecar(sidecar); - self.tx.populate_blob_hashes(); + if self.eip4844 { + let sidecar = coder.build_4844()?; + self.tx.set_blob_sidecar_4844(sidecar); + } else { + let sidecar = coder.build_7594()?; + self.tx.set_blob_sidecar_7594(sidecar); + } Ok(self) } + + /// Skips gas, fee and nonce filling. Use for read-only calls + /// (eth_call, eth_estimateGas, eth_createAccessList). + pub const fn raw(mut self) -> Self { + self.fill = false; + self + } } /// Helper function that tries to decode custom error name and inputs from error payload data. @@ -475,12 +771,12 @@ async fn decode_execution_revert(data: &RawValue) -> Result> { SignaturesIdentifier::new(false)?.identify_error(selector.try_into().unwrap()).await { let mut decoded_error = known_error.name.clone(); - if !known_error.inputs.is_empty() { - if let Ok(error) = known_error.decode_error(&err_data) { - write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?; - } + if !known_error.inputs.is_empty() + && let Ok(error) = known_error.decode_error(&err_data) + { + write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?; } - return Ok(Some(decoded_error)) + return Ok(Some(decoded_error)); } Ok(None) } diff --git a/crates/cast/tests/cli/erc20.rs b/crates/cast/tests/cli/erc20.rs new file mode 100644 index 0000000000000..20d9b657839b2 --- /dev/null +++ b/crates/cast/tests/cli/erc20.rs @@ -0,0 +1,608 @@ +//! Contains various tests for checking cast erc20 subcommands + +use alloy_primitives::U256; +use anvil::{NodeConfig, NodeHandle}; +use foundry_test_utils::util::OutputExt; + +mod anvil_const { + /// First Anvil account + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + + /// Second Anvil account + pub const _PK2: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + + /// Contract address deploying from ADDR1 with nonce 0 + pub const TOKEN: &str = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; +} + +fn get_u256_from_cmd(cmd: &mut foundry_test_utils::TestCommand, args: &[&str]) -> U256 { + let output = cmd.cast_fuse().args(args).assert_success().get_output().stdout_lossy(); + + // Parse balance from output (format: "100000000000000000000 [1e20]") + output.split_whitespace().next().unwrap().parse().unwrap() +} + +fn get_balance( + cmd: &mut foundry_test_utils::TestCommand, + token: &str, + address: &str, + rpc: &str, +) -> U256 { + get_u256_from_cmd(cmd, &["erc20", "balance", token, address, "--rpc-url", rpc]) +} + +fn get_allowance( + cmd: &mut foundry_test_utils::TestCommand, + token: &str, + owner: &str, + spender: &str, + rpc: &str, +) -> U256 { + get_u256_from_cmd(cmd, &["erc20", "allowance", token, owner, spender, "--rpc-url", rpc]) +} + +/// Helper function to deploy TestToken contract +fn deploy_test_token( + cmd: &mut foundry_test_utils::TestCommand, + rpc: &str, + private_key: &str, +) -> String { + cmd.args([ + "create", + "--private-key", + private_key, + "--rpc-url", + rpc, + "--broadcast", + "src/TestToken.sol:TestToken", + ]) + .assert_success(); + + // Return the standard deployment address (nonce 0 from first account) + anvil_const::TOKEN.to_string() +} + +/// Helper to setup anvil node and deploy test token +async fn setup_token_test( + prj: &foundry_test_utils::TestProject, + cmd: &mut foundry_test_utils::TestCommand, +) -> (String, String, NodeHandle) { + let (_, handle) = anvil::spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + + // Deploy TestToken contract + foundry_test_utils::util::initialize(prj.root()); + prj.add_source("TestToken.sol", include_str!("../fixtures/TestToken.sol")); + let token = deploy_test_token(cmd, &rpc, anvil_const::PK1); + + (rpc, token, handle) +} + +// tests that `balance` and `transfer` commands works correctly +forgetest_async!(erc20_transfer_approve_success, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + // Test constants + let transfer_amount = U256::from(100_000_000_000_000_000_000u128); // 100 tokens (18 decimals) + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens total supply + + // Verify initial balances + let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr1_balance_before, initial_supply); + assert_eq!(addr2_balance_before, U256::ZERO); + + // Test ERC20 transfer from ADDR1 to ADDR2 + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify balance changes after transfer + let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr1_balance_after, addr1_balance_before - transfer_amount); + assert_eq!(addr2_balance_after, addr2_balance_before + transfer_amount); +}); + +// tests that `approve` and `allowance` commands works correctly +forgetest_async!(erc20_approval_allowance, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + // ADDR1 approves ADDR2 to spend their tokens + let approve_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens + cmd.cast_fuse() + .args([ + "erc20", + "approve", + &token, + anvil_const::ADDR2, + &approve_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify allowance was set + let allowance = get_allowance(&mut cmd, &token, anvil_const::ADDR1, anvil_const::ADDR2, &rpc); + assert_eq!(allowance, approve_amount); +}); + +// tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly +forgetest_async!(erc20_metadata_success, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + // Test name + let output = cmd + .cast_fuse() + .args(["erc20", "name", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "Test Token"); + + // Test symbol + let output = cmd + .cast_fuse() + .args(["erc20", "symbol", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "TEST"); + + // Test decimals + let output = cmd + .cast_fuse() + .args(["erc20", "decimals", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "18"); + + // Test totalSupply + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); +}); + +// tests that `mint` command works correctly +forgetest_async!(erc20_mint_success, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let mint_amount = U256::from(500_000_000_000_000_000_000u128); // 500 tokens + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens + + // Get initial balance and supply + let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr2_balance_before, U256::ZERO); + + // Mint tokens to ADDR2 (only owner can mint) + cmd.cast_fuse() + .args([ + "erc20", + "mint", + &token, + anvil_const::ADDR2, + &mint_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, // PK1 is the owner/deployer + ]) + .assert_success(); + + // Verify balance increased + let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr2_balance_after, mint_amount); + + // Verify totalSupply increased + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, initial_supply + mint_amount); +}); + +// tests that `burn` command works correctly +forgetest_async!(erc20_burn_success, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let burn_amount = U256::from(200_000_000_000_000_000_000u128); // 200 tokens + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens + + // Get initial balance + let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + assert_eq!(addr1_balance_before, initial_supply); + + // Burn tokens from ADDR1 + cmd.cast_fuse() + .args([ + "erc20", + "burn", + &token, + &burn_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify balance decreased + let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + assert_eq!(addr1_balance_after, addr1_balance_before - burn_amount); + + // Verify totalSupply decreased + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, initial_supply - burn_amount); +}); + +// tests that `transfer` command works with gas options +forgetest_async!(erc20_transfer_with_gas_opts, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let transfer_amount = U256::from(100_000_000_000_000_000_000u128); // 100 tokens + + // Transfer with explicit gas limit and gas price + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + "--gas-limit", + "100000", + "--gas-price", + "2000000000", + ]) + .assert_success(); + + // Verify transfer succeeded + let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(balance, transfer_amount); +}); + +// tests that `transfer` command fails with insufficient gas limit +forgetest_async!(erc20_transfer_insufficient_gas, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let transfer_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens + + // Transfer with insufficient gas limit (ERC20 transfer needs ~50k gas) + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + "--gas-limit", + "1000", // Way too low for ERC20 transfer + ]) + .assert_failure(); + + // Verify transfer did NOT occur + let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(balance, U256::ZERO); +}); + +// tests that `transfer` command fails with incorrect nonce +forgetest_async!(erc20_transfer_incorrect_nonce, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let transfer_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens + + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Transfer with nonce too low + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + "--nonce", + "0", // Too low nonce + ]) + .assert_failure(); + + // Verify transfer did NOT occur + let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(balance, transfer_amount); // 2nd transfer failed +}); + +// tests that the --curl flag outputs a valid curl command for cast erc20 balance +casttest!(curl_erc20_balance, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xdead000000000000000000000000000000000000"; + let owner = "0xbeef000000000000000000000000000000000000"; + + let output = cmd + .args(["erc20", "balance", token, owner, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast erc20 name +casttest!(curl_erc20_name, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xdead000000000000000000000000000000000000"; + + let output = cmd + .args(["erc20", "name", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast erc20 decimals +casttest!(curl_erc20_decimals, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xdead000000000000000000000000000000000000"; + + let output = cmd + .args(["erc20", "decimals", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast erc20 total-supply +casttest!(curl_erc20_total_supply, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xdead000000000000000000000000000000000000"; + + let output = cmd + .args(["erc20", "total-supply", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for erc20 balance +casttest!(erc20_curl_balance, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC + let owner = "0xdead000000000000000000000000000000000000"; + + let output = cmd + .args(["erc20", "balance", token, owner, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for erc20 name +casttest!(erc20_curl_name, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC + + let output = cmd + .args(["erc20", "name", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for erc20 decimals +casttest!(erc20_curl_decimals, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC + + let output = cmd + .args(["erc20", "decimals", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for erc20 total-supply +casttest!(erc20_curl_total_supply, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC + + let output = cmd + .args(["erc20", "total-supply", token, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// tests that `balance` command works correctly with --json flag +forgetest_async!(erc20_balance_json, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "balance", &token, anvil_const::ADDR1, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + + let balance_str: String = serde_json::from_str(&output).expect("valid json string"); + let balance: U256 = balance_str.parse().unwrap(); + assert_eq!(balance, U256::from(1_000_000_000_000_000_000_000u128)); +}); + +// tests that `allowance` command works correctly with --json flag +forgetest_async!(erc20_allowance_json, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + // First approve some tokens + let approve_amount = U256::from(50_000_000_000_000_000_000u128); + cmd.cast_fuse() + .args([ + "erc20", + "approve", + &token, + anvil_const::ADDR2, + &approve_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Check allowance with JSON flag + let output = cmd + .cast_fuse() + .args([ + "--json", + "erc20", + "allowance", + &token, + anvil_const::ADDR1, + anvil_const::ADDR2, + "--rpc-url", + &rpc, + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let allowance_str: String = serde_json::from_str(&output).expect("valid json string"); + let allowance: U256 = allowance_str.parse().unwrap(); + assert_eq!(allowance, approve_amount); +}); + +// tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly with --json +// flag +forgetest_async!(erc20_metadata_json, |prj, cmd| { + let (rpc, token, _handle) = setup_token_test(&prj, &mut cmd).await; + + // Test name with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "name", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let name: String = serde_json::from_str(&output).expect("valid json string"); + assert_eq!(name, "Test Token"); + + // Test symbol with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "symbol", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let symbol: String = serde_json::from_str(&output).expect("valid json string"); + assert_eq!(symbol, "TEST"); + + // Test decimals with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "decimals", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let decimals: u8 = output.trim().parse().expect("valid number"); + assert_eq!(decimals, 18); + + // Test totalSupply with --json + let output = cmd + .cast_fuse() + .args(["--json", "erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply_str: String = serde_json::from_str(&output).expect("valid json string"); + let total_supply: U256 = total_supply_str.parse().unwrap(); + assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); +}); diff --git a/crates/cast/tests/cli/keychain.rs b/crates/cast/tests/cli/keychain.rs new file mode 100644 index 0000000000000..88e9e16983cc5 --- /dev/null +++ b/crates/cast/tests/cli/keychain.rs @@ -0,0 +1,76 @@ +//! CLI tests for `cast keychain` subcommands. + +use anvil::NodeConfig; +use foundry_test_utils::util::OutputExt; + +/// Anvil test accounts (standard mnemonic). +mod accounts { + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + pub const TOKEN: &str = "0x20C000000000000000000000b9537d11c60E8b50"; // PathUSD +} + +// `cast keychain rl --json` must emit `{"remaining":""}`, not a bare string. +casttest!(keychain_rl_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "rl", + accounts::ADDR1, + accounts::ADDR2, + accounts::TOKEN, + "--rpc-url", + &rpc, + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain rl --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + assert!( + parsed.get("remaining").is_some(), + "expected 'remaining' key in JSON output, got: {output}" + ); + // Must not be a bare string (old bug: `"0"`) + assert!(!parsed.is_string(), "JSON output must not be a bare string, got: {output}"); +}); + +// `cast keychain authorize --tempo.print-sponsor-hash --json` must emit +// `{"sponsor_hash":"0x..."}`, not a raw hex string. +casttest!(keychain_authorize_sponsor_hash_json_is_object, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let rpc = handle.http_endpoint(); + + let output = cmd + .args([ + "keychain", + "authorize", + accounts::ADDR2, // key to authorize + "--private-key", + accounts::PK1, + "--rpc-url", + &rpc, + "--tempo.print-sponsor-hash", + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let parsed: serde_json::Value = serde_json::from_str(output.trim()) + .expect("cast keychain authorize --tempo.print-sponsor-hash --json should emit valid JSON"); + assert!(parsed.is_object(), "expected JSON object, got: {output}"); + let hash = parsed + .get("sponsor_hash") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("expected 'sponsor_hash' key in JSON output, got: {output}")); + assert!(hash.starts_with("0x"), "sponsor_hash should be 0x-prefixed, got: {hash}"); + assert_eq!(hash.len(), 66, "sponsor_hash should be 32-byte hex (66 chars), got: {hash}"); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index eca950b2f89d4..4fe21c0c77088 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,25 +1,34 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_eips::Decodable2718; use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; -use alloy_primitives::{address, b256, Bytes, B256}; +use alloy_primitives::{B256, Bytes, U256, address, b256, hex}; use alloy_provider::{Provider, ProviderBuilder}; -use alloy_rpc_types::{BlockNumberOrTag, Index, TransactionRequest}; +use alloy_rpc_types::{Authorization, BlockNumberOrTag, Index, TransactionRequest}; +use alloy_signer::Signer; +use alloy_signer_local::PrivateKeySigner; use anvil::NodeConfig; +use foundry_evm::core::tempo::PATH_USD_ADDRESS; use foundry_test_utils::{ rpc::{ next_etherscan_api_key, next_http_archive_rpc_url, next_http_rpc_endpoint, next_rpc_endpoint, next_ws_rpc_endpoint, }, + snapbox::IntoData as _, str, util::OutputExt, }; -use std::{fs, io::Write, path::Path, str::FromStr}; +use serde_json::json; +use std::{fs, path::Path, str::FromStr}; +use tempo_primitives::TempoTxEnvelope; #[macro_use] extern crate foundry_test_utils; +mod erc20; +mod keychain; mod selectors; casttest!(print_short_version, |_prj, cmd| { @@ -55,7 +64,7 @@ Options: -j, --threads Number of threads to use. Specifying 0 defaults to the number of logical cores - +... [aliases: --jobs] -V, --version @@ -73,23 +82,28 @@ Display options: --json Format log messages as JSON + --md + Format log messages as Markdown + -q, --quiet Do not print log messages -v, --verbosity... Verbosity level of the log messages. - +... Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). - +... Depending on the context the verbosity levels have different meanings. - +... For example, the verbosity levels of the EVM are: - 2 (-vv): Print logs for all tests. - 3 (-vvv): Print execution traces for failing tests. - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes + and + backtraces with line numbers. -Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html +Find more information in the book: https://getfoundry.sh/cast/overview "#]]); }); @@ -134,13 +148,69 @@ transactions: [ "#]]); // - cmd.cast_fuse().args(["block", "15007840", "-f", "hash", "--rpc-url", eth_rpc_url.as_str()]); + cmd.cast_fuse().args([ + "block", + "15007840", + "-f", + "hash,timestamp", + "--rpc-url", + eth_rpc_url.as_str(), + ]); cmd.assert_success().stdout_eq(str![[r#" 0x950091817a57e22b6c1f3b951a15f52d41ac89b299cc8f9c89bb6d185f80c415 +1655904485 "#]]); }); +casttest!(block_raw, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + let output = cmd + .args(["block", "22934900", "--rpc-url", eth_rpc_url.as_str(), "--raw"]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + // Hash the output with keccak256 + let hash = alloy_primitives::keccak256(hex::decode(output).unwrap()); + + // Verify the Mainnet's block #22934900 header hash equals the expected value + // obtained with go-ethereum's `block.Header().Hash()` method + assert_eq!( + hash.to_string(), + "0x49fd7f3b9ba5d67fa60197027f09454d4cac945e8f271edcc84c3fd5872446d3" + ); +}); + +casttest!(block_raw_tempo, |_prj, cmd| { + // https://explore.tempo.xyz/block/8386710 + let output = cmd + .args([ + "block", + "8386710", + "--rpc-url", + "https://rpc.moderato.tempo.xyz", + "--raw", + "-n", + "tempo", + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + let hash = alloy_primitives::keccak256(hex::decode(output).unwrap()); + + assert_eq!( + hash.to_string(), + "0xcd6170dc28b888bcb93ed1ad76a6bea4ad9977b678db5d462df83d35ec9b8d15" + ); +}); + // tests that the `cast find-block` command works correctly casttest!(finds_block, |_prj, cmd| { // Construct args @@ -157,6 +227,60 @@ casttest!(finds_block, |_prj, cmd| { "#]]); }); +// tests that we can create a new wallet +casttest!(new_wallet, |_prj, cmd| { + cmd.args(["wallet", "new"]).assert_success().stdout_eq(str![[r#" +Successfully created new keypair. +[ADDRESS] +[PRIVATE_KEY] + +"#]]); +}); + +// tests that we can create a new wallet (verbose variant) +casttest!(new_wallet_verbose, |_prj, cmd| { + cmd.args(["wallet", "new", "-v"]).assert_success().stdout_eq(str![[r#" +Successfully created new keypair. +[ADDRESS] +[PUBLIC_KEY] +[PRIVATE_KEY] + +"#]]); +}); + +// tests that we can create a new wallet with json output +casttest!(new_wallet_json, |_prj, cmd| { + cmd.args(["wallet", "new", "--json"]).assert_success().stdout_eq( + str![[r#" +[ + { + "address": "{...}", + "private_key": "{...}" + } +] + +"#]] + .is_json(), + ); +}); + +// tests that we can create a new wallet with json output (verbose variant) +casttest!(new_wallet_json_verbose, |_prj, cmd| { + cmd.args(["wallet", "new", "--json", "-v"]).assert_success().stdout_eq( + str![[r#" +[ + { + "address": "{...}", + "public_key": "{...}", + "private_key": "{...}" + } +] + +"#]] + .is_json(), + ); +}); + // tests that we can create a new wallet with keystore casttest!(new_wallet_keystore_with_password, |_prj, cmd| { cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]) @@ -168,6 +292,104 @@ Created new encrypted keystore file: [..] "#]]); }); +// tests that we can create a new wallet with keystore (verbose variant) +casttest!(new_wallet_keystore_with_password_verbose, |_prj, cmd| { + cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-v"]) + .assert_success() + .stdout_eq(str![[r#" +Created new encrypted keystore file: [..] +[ADDRESS] +[PUBLIC_KEY] + +"#]]); +}); + +// tests that `cast wallet new` prompts before overwriting an existing keystore file +casttest!(new_wallet_keystore_overwrite_protection, |prj, cmd| { + // Create the initial keystore + cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]).assert_success(); + + // Attempt to overwrite with stdin "n" — should be cancelled + cmd.cast_fuse() + .current_dir(prj.root()) + .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]) + .stdin("n\n") + .assert_failure() + .stderr_eq(str![[r#" +The following keystore file(s) already exist: + - test-account +Error: Operation cancelled. No keystores were modified. + +"#]]); +}); + +// tests that `cast wallet new --force` overwrites existing keystore files without prompting +casttest!(new_wallet_keystore_overwrite_force, |prj, cmd| { + // Create the initial keystore + cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]).assert_success(); + + // Overwrite with --force — should succeed without prompting + cmd.cast_fuse() + .current_dir(prj.root()) + .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "--force"]) + .assert_success() + .stdout_eq(str![[r#" +Created new encrypted keystore file: [..] +[ADDRESS] + +"#]]); +}); + +// tests that `cast wallet new -n 2` prompts before overwriting existing keystore files +casttest!(new_wallet_keystore_overwrite_protection_multiple, |prj, cmd| { + // Create 2 keystores: test-account_1 and test-account_2 + cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-n", "2"]) + .assert_success(); + + // Attempt to overwrite with stdin "n" — should list both and cancel + cmd.cast_fuse() + .current_dir(prj.root()) + .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-n", "2"]) + .stdin("n\n") + .assert_failure() + .stderr_eq(str![[r#" +The following keystore file(s) already exist: + - test-account_1 + - test-account_2 +Error: Operation cancelled. No keystores were modified. + +"#]]); +}); + +// tests that we can create a new wallet with default keystore location +casttest!(new_wallet_default_keystore, |_prj, cmd| { + cmd.args(["wallet", "new", "--unsafe-password", "test"]).assert_success().stdout_eq(str![[ + r#" +Created new encrypted keystore file: [..] +[ADDRESS] + +"# + ]]); + + // Verify the default keystore directory was created + let keystore_path = dirs::home_dir().unwrap().join(".foundry").join("keystores"); + assert!(keystore_path.exists()); + assert!(keystore_path.is_dir()); +}); + +// tests that we can outputting multiple keys without a keystore path +casttest!(new_wallet_multiple_keys, |_prj, cmd| { + cmd.args(["wallet", "new", "-n", "2"]).assert_success().stdout_eq(str![[r#" +Successfully created new keypair. +[ADDRESS] +[PRIVATE_KEY] +Successfully created new keypair. +[ADDRESS] +[PRIVATE_KEY] + +"#]]); +}); + // tests that we can get the address of a keystore file casttest!(wallet_address_keystore_with_password_file, |_prj, cmd| { let keystore_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/keystore"); @@ -387,6 +609,84 @@ casttest!(wallet_sign_typed_data_file, |_prj, cmd| { "#]]); }); +// tests that `cast wallet sign typed-data` passes with type names containing colons +// +casttest!(wallet_sign_typed_data_with_colon_succeeds, |_prj, cmd| { + let typed_data_with_colon = r#"{ + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Test:Message": [ + {"name": "content", "type": "string"} + ] + }, + "primaryType": "Test:Message", + "domain": { + "name": "TestDomain", + "version": "1", + "chainId": 1, + "verifyingContract": "0x0000000000000000000000000000000000000000" + }, + "message": { + "content": "Hello" + } + }"#; + + cmd.args([ + "wallet", + "sign", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--data", + typed_data_with_colon, + ]).assert_success().stdout_eq(str![[r#" +0xf91c67e845a4d468d1f876f457ffa01e65468641fc121453705242d21de39b266c278592b085814ab1e9adc938cc26b1d64bb61f80b437df077777c4283612291b + +"#]]); +}); + +// tests that the same data without colon works correctly +// +casttest!(wallet_sign_typed_data_without_colon_works, |_prj, cmd| { + let typed_data_without_colon = r#"{ + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "TestMessage": [ + {"name": "content", "type": "string"} + ] + }, + "primaryType": "TestMessage", + "domain": { + "name": "TestDomain", + "version": "1", + "chainId": 1, + "verifyingContract": "0x0000000000000000000000000000000000000000" + }, + "message": { + "content": "Hello" + } + }"#; + + cmd.args([ + "wallet", + "sign", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--data", + typed_data_without_colon, + ]) + .assert_success(); +}); + // tests that `cast wallet sign-auth message` outputs the expected signature casttest!(wallet_sign_auth, |_prj, cmd| { cmd.args([ @@ -404,6 +704,85 @@ casttest!(wallet_sign_auth, |_prj, cmd| { "#]]); }); +// tests that `cast wallet sign-auth --self-broadcast` uses nonce + 1 +casttest!(wallet_sign_auth_self_broadcast, async |_prj, cmd| { + use alloy_rlp::Decodable; + use alloy_signer_local::PrivateKeySigner; + + let (_, handle) = + anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; + let endpoint = handle.http_endpoint(); + + let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + let signer: PrivateKeySigner = private_key.parse().unwrap(); + let signer_address = signer.address(); + let delegate_address = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + // Get the current nonce from the RPC + let provider = ProviderBuilder::new().connect_http(endpoint.parse().unwrap()); + let current_nonce = provider.get_transaction_count(signer_address).await.unwrap(); + + // First, get the auth without --self-broadcast (should use current nonce) + let output_normal = cmd + .args([ + "wallet", + "sign-auth", + "--private-key", + private_key, + "--rpc-url", + &endpoint, + &delegate_address.to_string(), + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + // Then, get the auth with --self-broadcast (should use current nonce + 1) + let output_self_broadcast = cmd + .cast_fuse() + .args([ + "wallet", + "sign-auth", + "--private-key", + private_key, + "--rpc-url", + &endpoint, + "--self-broadcast", + &delegate_address.to_string(), + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + // The outputs should be different due to different nonces + assert_ne!( + output_normal, output_self_broadcast, + "self-broadcast should produce different signature due to nonce + 1" + ); + + // Decode the RLP to verify the nonces + let normal_bytes = hex::decode(output_normal.strip_prefix("0x").unwrap()).unwrap(); + let self_broadcast_bytes = + hex::decode(output_self_broadcast.strip_prefix("0x").unwrap()).unwrap(); + + let normal_auth = + alloy_eips::eip7702::SignedAuthorization::decode(&mut normal_bytes.as_slice()).unwrap(); + let self_broadcast_auth = + alloy_eips::eip7702::SignedAuthorization::decode(&mut self_broadcast_bytes.as_slice()) + .unwrap(); + + assert_eq!(normal_auth.nonce(), current_nonce, "normal auth should have current nonce"); + assert_eq!( + self_broadcast_auth.nonce(), + current_nonce + 1, + "self-broadcast auth should have current nonce + 1" + ); +}); + // tests that `cast wallet list` outputs the local accounts casttest!(wallet_list_local_accounts, |prj, cmd| { let keystore_path = prj.root().join("keystore"); @@ -473,7 +852,8 @@ casttest!(wallet_mnemonic_from_entropy, |_prj, cmd| { "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", ]) .assert_success() - .stdout_eq(str![[r#" + .stdout_eq( + str![[r#" Generating mnemonic from provided entropy... Successfully generated a new mnemonic. Phrase: @@ -493,7 +873,50 @@ Address: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a -"#]]); +"#]] + .raw(), + ); +}); + +// tests that `cast wallet new-mnemonic --entropy` outputs the expected mnemonic (verbose variant) +casttest!(wallet_mnemonic_from_entropy_verbose, |_prj, cmd| { + cmd.args([ + "wallet", + "new-mnemonic", + "--accounts", + "3", + "--entropy", + "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", + "-v", + ]) + .assert_success() + .stdout_eq( + str![[r#" +Generating mnemonic from provided entropy... +Successfully generated a new mnemonic. +Phrase: +test test test test test test test test test test test junk + +Accounts: +- Account 0: +Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Public key: 0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5 +Private key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +- Account 1: +Address: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +Public key: 0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4 +Private key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +- Account 2: +Address: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +Public key: 0x9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb +Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a + + +"#]] + .raw(), + ); }); // tests that `cast wallet new-mnemonic --json` outputs the expected mnemonic @@ -530,40 +953,191 @@ casttest!(wallet_mnemonic_from_entropy_json, |_prj, cmd| { "#]]); }); -// tests that `cast wallet private-key` with arguments outputs the private key -casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { +// tests that `cast wallet new-mnemonic --json` outputs the expected mnemonic (verbose variant) +casttest!(wallet_mnemonic_from_entropy_json_verbose, |_prj, cmd| { cmd.args([ "wallet", - "private-key", - "test test test test test test test test test test test junk", - "1", + "new-mnemonic", + "--accounts", + "3", + "--entropy", + "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", + "--json", + "-v", ]) .assert_success() .stdout_eq(str![[r#" -0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d +{ + "mnemonic": "test test test test test test test test test test test junk", + "accounts": [ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "public_key": "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "public_key": "0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4", + "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "public_key": "0x9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb", + "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" + } + ] +} "#]]); }); -// tests that `cast wallet private-key` with options outputs the private key -casttest!(wallet_private_key_from_mnemonic_option, |_prj, cmd| { +// tests that `cast wallet derive` outputs the addresses of the accounts derived from the mnemonic +casttest!(wallet_derive_mnemonic, |_prj, cmd| { cmd.args([ "wallet", - "private-key", - "--mnemonic", + "derive", + "--accounts", + "3", "test test test test test test test test test test test junk", - "--mnemonic-index", - "1", ]) .assert_success() .stdout_eq(str![[r#" -0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d +- Account 0: +[ADDRESS] -"#]]); -}); -// tests that `cast wallet public-key` correctly derives and outputs the public key -casttest!(wallet_public_key_with_private_key, |_prj, cmd| { - cmd.args([ +- Account 1: +[ADDRESS] + +- Account 2: +[ADDRESS] + + +"#]]); +}); + +// tests that `cast wallet derive` with insecure flag outputs the addresses and private keys of the +// accounts derived from the mnemonic +casttest!(wallet_derive_mnemonic_insecure, |_prj, cmd| { + cmd.args([ + "wallet", + "derive", + "--accounts", + "3", + "--insecure", + "test test test test test test test test test test test junk", + ]) + .assert_success() + .stdout_eq(str![[r#" +- Account 0: +[ADDRESS] +[PRIVATE_KEY] + +- Account 1: +[ADDRESS] +[PRIVATE_KEY] + +- Account 2: +[ADDRESS] +[PRIVATE_KEY] + + +"#]]); +}); + +// tests that `cast wallet derive` with json flag outputs the addresses of the accounts derived from +// the mnemonic in JSON format +casttest!(wallet_derive_mnemonic_json, |_prj, cmd| { + cmd.args([ + "wallet", + "derive", + "--accounts", + "3", + "--json", + "test test test test test test test test test test test junk", + ]) + .assert_success() + .stdout_eq(str![[r#" +[ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" + } +] + +"#]]); +}); + +// tests that `cast wallet derive` with insecure and json flag outputs the addresses and private +// keys of the accounts derived from the mnemonic in JSON format +casttest!(wallet_derive_mnemonic_insecure_json, |_prj, cmd| { + cmd.args([ + "wallet", + "derive", + "--accounts", + "3", + "--insecure", + "--json", + "test test test test test test test test test test test junk", + ]) + .assert_success() + .stdout_eq(str![[r#" +[ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" + } +] + +"#]]); +}); + +// tests that `cast wallet private-key` with arguments outputs the private key +casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { + cmd.args([ + "wallet", + "private-key", + "test test test test test test test test test test test junk", + "1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +"#]]); +}); + +// tests that `cast wallet private-key` with options outputs the private key +casttest!(wallet_private_key_from_mnemonic_option, |_prj, cmd| { + cmd.args([ + "wallet", + "private-key", + "--mnemonic", + "test test test test test test test test test test test junk", + "--mnemonic-index", + "1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +"#]]); +}); +// tests that `cast wallet public-key` correctly derives and outputs the public key +casttest!(wallet_public_key_with_private_key, |_prj, cmd| { + cmd.args([ "wallet", "public-key", "--raw-private-key", @@ -760,6 +1334,31 @@ casttest!(estimate_function_gas, |_prj, cmd| { assert!(output.ge(&0)); }); +// tests that `cast estimate --cost` is working correctly. +casttest!(estimate_function_cost, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // ensure we get a positive non-error value for cost estimate + let output: f64 = cmd + .args([ + "estimate", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth + "--value", + "100", + "deposit()", + "--rpc-url", + eth_rpc_url.as_str(), + "--cost", + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .parse() + .unwrap(); + assert!(output > 0f64); +}); + // tests that `cast estimate --create` is working correctly. casttest!(estimate_contract_deploy_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); @@ -880,6 +1479,38 @@ casttest!(rpc_with_args, |_prj, cmd| { "#]]); }); +// test for cast_rpc with arguments +casttest!(rpc_format_as_json, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // Call `cast rpc eth_getBlockByNumber 0x123 false` + cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "0x123", "false", "--json"]) + .assert_json_stdout(str![[r#" +{ + "hash": "0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524", + "parentHash": "0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0xbb7b8287f3f0a933474a79eae42cbca977791171", + "stateRoot": "0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x494433b31", + "number": "0x123", + "gasLimit": "0x1388", + "gasUsed": "0x0", + "timestamp": "0x55ba4564", + "extraData": "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32", + "mixHash": "0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0", + "nonce": "0x29d6547c196e00e0", + "size": "0x220", + "uncles": [], + "transactions": [] +} + +"#]]); +}); + // test for cast_rpc with raw params casttest!(rpc_raw_params, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); @@ -903,11 +1534,9 @@ casttest!(rpc_raw_params, |_prj, cmd| { casttest!(rpc_raw_params_stdin, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); - // Call `echo "\n[\n\"0x123\",\nfalse\n]\n" | cast rpc eth_getBlockByNumber --raw + // Call `echo "\n[\n\"0x123\",\nfalse\n]\n" | cast rpc eth_getBlockByNumber --raw cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "--raw"]).stdin( - |mut stdin| { - stdin.write_all(b"\n[\n\"0x123\",\nfalse\n]\n").unwrap(); - }, + b"\n[\n\"0x123\",\nfalse\n]\n" ) .assert_json_stdout(str![[r#" {"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} @@ -943,6 +1572,27 @@ Transaction successfully executed. "#]]); }); +// Regression test: pre-Berlin tx (block 12243999) must use Istanbul gas costs. +// Without correct hardfork resolution, this tx OOGs under Prague/Berlin cold SLOAD costs. +casttest!(run_pre_berlin_tx_uses_correct_spec, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + cmd.args([ + "run", + "-v", + "0xbb4dece05b8d41a2f79475f76daccf7abdd816f6813897cd02ef8509205ebecb", + "--quick", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +... +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that `cast --to-base` commands are working correctly. casttest!(to_base, |_prj, cmd| { let values = [ @@ -972,6 +1622,7 @@ casttest!(to_base, |_prj, cmd| { }); // tests that revert reason is only present if transaction has reverted. + casttest!(receipt_revert_reason, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); @@ -983,27 +1634,25 @@ casttest!(receipt_revert_reason, |_prj, cmd| { rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x2cfe65be49863676b6dbc04d58176a14f39b123f1e2f4fea0383a2d82c2c50d0 blockNumber 16239315 -contractAddress +contractAddress {} cumulativeGasUsed 10743428 effectiveGasPrice 10539984136 from 0x199D5ED7F45F4eE35960cF22EAde2076e95B253F gasUsed 21000 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 1 (success) transactionHash 0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e transactionIndex 116 type 0 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c - -"#]]); +"#,"", "", "", "")); let rpc = next_http_archive_rpc_url(); @@ -1016,32 +1665,31 @@ to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x883f974b17ca7b28cb970798d1c80f4d4bb427473dc6d39b2a7fe24edc02902d blockNumber 14839405 -contractAddress +contractAddress {} cumulativeGasUsed 20273649 effectiveGasPrice 21491736378 from 0x3cF412d970474804623bb4e3a42dE13F9bCa5436 gasUsed 24952 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 0 (failed) transactionHash 0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c transactionIndex 173 type 2 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 revertReason [..]Transaction too old, data: "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000135472616e73616374696f6e20746f6f206f6c6400000000000000000000000000" - -"#]]); +"#,"","","","")); }); - // tests that the revert reason is loaded using the correct `from` address. -casttest!(revert_reason_from, |_prj, cmd| { +// Flaky: Sepolia RPC may not return the revertReason field depending on provider +// support for debug/trace APIs. +casttest!(flaky_revert_reason_from, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); // https://sepolia.etherscan.io/tx/0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 cmd.args([ @@ -1051,28 +1699,26 @@ casttest!(revert_reason_from, |_prj, cmd| { rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x32663d7730c9ea8e1de6d99854483e25fcc05bb56c91c0cc82f9f04944fbffc1 blockNumber 7823353 -contractAddress +contractAddress {} cumulativeGasUsed 7500797 effectiveGasPrice 14296851013 from 0x3583fF95f96b356d716881C871aF7Eb55ea34a93 gasUsed 25815 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 0 (failed) transactionHash 0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 transactionIndex 96 type 0 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x91b5d4111a4C038153b24e31F75ccdC47123595d -revertReason Counter is too large, data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014436f756e74657220697320746f6f206c61726765000000000000000000000000" - -"#]]); +... +"#, "", "", "", "")); }); // tests that `cast --parse-bytes32-address` command is working correctly. @@ -1188,6 +1834,24 @@ casttest!(logs_sig_2, |_prj, cmd| { .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); +casttest!(logs_chunked_large_range, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + cmd.args([ + "logs", + "--rpc-url", + rpc.as_str(), + "--from-block", + "18000000", + "--to-block", + "18050000", + "--query-size", + "1000", + "Transfer(address indexed from, address indexed to, uint256 value)", + "0xA0b86a33E6441d02dd8C6B2b7E5D1E3eD7F73b4b", + ]) + .assert_success(); +}); + casttest!(mktx, |_prj, cmd| { cmd.args([ "mktx", @@ -1297,6 +1961,73 @@ casttest!(mktx_raw_unsigned, |_prj, cmd| { ]]); }); +casttest!(mktx_raw_unsigned_no_from_missing_chain, async |_prj, cmd| { + // As chain is not provided, a query is made to the provider to get the chain id, before the + // tx is built. Anvil is configured to use chain id 1 so that the produced tx will + // be the same as in the `mktx_raw_unsigned` test. + let (_, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(1u64))).await; + cmd.args([ + "mktx", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + "--raw-unsigned", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success() + .stdout_eq(str![[ + r#"0x02e80180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c0 + +"# + ]]); +}); + +casttest!(mktx_raw_unsigned_no_from_missing_gas_pricing, async |_prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test()).await; + cmd.args([ + "mktx", + "--nonce", + "0", + "0x0000000000000000000000000000000000000001", + "--raw-unsigned", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success() + .stdout_eq(str![[ + r#"0x02e5827a69800184773594018252089400000000000000000000000000000000000000018080c0 + +"# + ]]); +}); + +casttest!(mktx_raw_unsigned_no_from_missing_nonce, |_prj, cmd| { + cmd.args([ + "mktx", + "--chain", + "1", + "--gas-limit", + "21000", + "--gas-price", + "20000000000", + "0x742d35Cc6634C0532925a3b8D6Ac6F67C9c2b7FD", + "--raw-unsigned", + ]) + .assert_failure() + .stderr_eq(str![[ + r#"Error: Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce + +"# + ]]); +}); + casttest!(mktx_ethsign, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); @@ -1328,6 +2059,55 @@ casttest!(mktx_ethsign, async |_prj, cmd| { ]]); }); +// tests that `cast mktx --tempo.lane ` resolves the lane against a `tempo.lanes.toml` file at +// the project root, sets the corresponding `nonce_key` on the produced Tempo AA transaction. +casttest!(mktx_tempo_lane_resolves_nonce_key, |prj, cmd| { + // Write a shared lanes file at the project root. + let lanes_path = prj.root().join("tempo.lanes.toml"); + fs::write(&lanes_path, "deploy = 1\nops = 2\npayments = 42\n").unwrap(); + + let output = cmd + .current_dir(prj.root()) + .args([ + "mktx", + "--tempo.lane", + "payments", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]) + .assert_success() + .get_output() + .clone(); + + // The resolved-lane breadcrumb is printed to stderr so it doesn't pollute stdout + // (which carries the raw signed transaction). + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("lane: payments (nonce_key=42, nonce=0)"), + "expected lane breadcrumb on stderr, got: {stderr}", + ); + + // Decode the produced signed Tempo AA transaction and verify it carries the + // resolved 2D nonce key. + let stdout = String::from_utf8_lossy(&output.stdout); + let raw_hex = stdout.trim().trim_start_matches("0x"); + let raw = hex::decode(raw_hex).expect("decode hex output"); + let envelope = TempoTxEnvelope::decode_2718(&mut raw.as_slice()).expect("decode tempo tx"); + assert!(envelope.is_aa(), "expected Tempo AA transaction, got: {envelope:?}"); + assert_eq!(envelope.nonce_key(), Some(U256::from(42_u64))); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -1357,8 +2137,36 @@ casttest!(tx_raw, |_prj, cmd| { "#]]); }); +casttest!(tx_to_request_json, |_prj, cmd| { + let rpc = next_http_rpc_endpoint(); + + // + cmd.args([ + "tx", + "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", + "--to-request", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +{ + "from": "0x199d5ed7f45f4ee35960cf22eade2076e95b253f", + "to": "0x91da5bf3f8eb72724e6f50ec6c3d199c6355c59c", + "gasPrice": "0x2743b6508", + "gas": "0x7530", + "value": "0xa0a73f33e9e4cc", + "input": "0x", + "nonce": "0x4c54", + "chainId": "0x1", + "type": "0x0" +} + +"#]]); +}); + casttest!(tx_using_sender_and_nonce, |_prj, cmd| { - let rpc = "https://reth-ethereum.ithaca.xyz/rpc"; + let rpc = next_http_archive_rpc_url(); // let args = vec![ "tx", @@ -1367,7 +2175,7 @@ casttest!(tx_using_sender_and_nonce, |_prj, cmd| { "--nonce", "113642", "--rpc-url", - rpc, + rpc.as_str(), ]; cmd.args(args).assert_success().stdout_eq(str![[r#" @@ -1376,20 +2184,19 @@ blockNumber 22287055 from 0x4648451b5F87FF8F0F7D622bD40574bb97E25980 transactionIndex 230 effectiveGasPrice 363392048 - -accessList [] +hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 +type 2 chainId 1 +nonce 113642 gasLimit 350000 -hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 -input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 maxFeePerGas 675979146 maxPriorityFeePerGas 1337 -nonce 113642 -r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c -s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a to 0xdAC17F958D2ee523a2206206994597C13D831ec7 -type 2 value 0 +accessList [] +input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 +r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c +s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a yParity 1 ... "#]]); @@ -1469,11 +2276,116 @@ casttest!(storage, |_prj, cmd| { .stdout_eq(str![[r#" 0x000000000000000000000000000000000000000000000000000000174876e800 +"#]]); + + let decimal_slot_offset_from_total_supply_slot = "0x08"; + let decimal_slot_offset_from_total_supply_slot_uint = "8"; + let rpc = next_http_archive_rpc_url(); + cmd.cast_fuse() + .args([ + "storage", + usdt, + total_supply_slot, + decimal_slot_offset_from_total_supply_slot, + "--rpc-url", + &rpc, + ]) + .assert_success() + .stdout_eq(str![[r#" +0x0000000000000000000000000000000000000000000000000000000000000006 + +"#]]); + + let rpc = next_http_archive_rpc_url(); + cmd.cast_fuse() + .args([ + "storage", + usdt, + total_supply_slot, + decimal_slot_offset_from_total_supply_slot_uint, + "--rpc-url", + &rpc, + ]) + .assert_success() + .stdout_eq(str![[r#" +0x0000000000000000000000000000000000000000000000000000000000000006 + +"#]]); +}); + +casttest!(flaky_storage_with_valid_solc_version_1, |_prj, cmd| { + cmd.args([ + "storage", + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + "--solc-version", + "0.8.10", + "--rpc-url", + next_http_archive_rpc_url().as_str(), + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + ]) + .assert_success(); +}); + +casttest!(flaky_storage_with_valid_solc_version_2, |_prj, cmd| { + cmd.args([ + "storage", + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + "--solc-version", + "0.8.23", + "--rpc-url", + next_http_archive_rpc_url().as_str(), + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + ]) + .assert_success(); +}); + +casttest!(flaky_storage_with_invalid_solc_version_1, |_prj, cmd| { + let output = cmd + .args([ + "storage", + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + "--solc-version", + "0.4.0", + "--rpc-url", + next_http_archive_rpc_url().as_str(), + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + ]) + .assert_failure() + .get_output() + .stderr + .clone(); + let stderr = String::from_utf8_lossy(&output); + assert!( + stderr.contains( + "Warning: The provided --solc-version is 0.4.0 while the minimum version for storage layouts is 0.6.5" + ), + "stderr did not contain expected warning. Full stderr:\n{stderr}" + ); +}); + +casttest!(flaky_storage_with_invalid_solc_version_2, |_prj, cmd| { + cmd.args([ + "storage", + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + "--solc-version", + "0.8.2", + "--rpc-url", + next_http_archive_rpc_url().as_str(), + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Encountered invalid solc version in contracts/Create2Deployer.sol: No solc version exists that matches the version requirement: ^0.8.9 + "#]]); }); // -casttest!(storage_layout_simple, |_prj, cmd| { +casttest!(flaky_storage_layout_simple, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -1500,7 +2412,7 @@ casttest!(storage_layout_simple, |_prj, cmd| { }); // -casttest!(storage_layout_simple_json, |_prj, cmd| { +casttest!(flaky_storage_layout_simple_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -1517,7 +2429,7 @@ casttest!(storage_layout_simple_json, |_prj, cmd| { }); // -casttest!(storage_layout_complex, |_prj, cmd| { +casttest!(flaky_storage_layout_complex, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -1565,7 +2477,42 @@ casttest!(storage_layout_complex, |_prj, cmd| { "#]]); }); -casttest!(storage_layout_complex_proxy, |_prj, cmd| { +casttest!(flaky_storage_layout_complex_md, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_http_archive_rpc_url().as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + "--md", + ]) + .assert_success() + .stdout_eq(str![[r#" + +| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | +|-------------------------------|--------------------------------------------------------------------|------|--------|-------|--------------------------------------------------|--------------------------------------------------------------------|---------------------------------| +| _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault | +| _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault | +| _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault | +| _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +| _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | + + +"#]]); +}); + +casttest!(flaky_storage_layout_complex_proxy, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -1607,7 +2554,7 @@ casttest!(storage_layout_complex_proxy, |_prj, cmd| { "#]]); }); -casttest!(storage_layout_complex_json, |_prj, cmd| { +casttest!(flaky_storage_layout_complex_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", @@ -1625,14 +2572,14 @@ casttest!(storage_layout_complex_json, |_prj, cmd| { casttest!(balance, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); - let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + let dai = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; - let usdt_result = cmd + let dai_result = cmd .args([ "balance", "0x0000000000000000000000000000000000000000", "--erc20", - usdt, + dai, "--rpc-url", &rpc, ]) @@ -1648,7 +2595,7 @@ casttest!(balance, |_prj, cmd| { "balance", "0x0000000000000000000000000000000000000000", "--erc721", - usdt, + dai, "--rpc-url", &rpc, ]) @@ -1658,8 +2605,8 @@ casttest!(balance, |_prj, cmd| { .trim() .to_string(); - assert_ne!(usdt_result, "0"); - assert_eq!(alias_result, usdt_result); + assert_ne!(dai_result, "0"); + assert_eq!(alias_result, dai_result); }); // tests that `cast interface` excludes the constructor @@ -1698,9 +2645,53 @@ interface Interface { ]]); }); +// tests that `cast interface --flatten` inlines inherited struct types into the interface +// +casttest!(interface_flatten, |prj, cmd| { + let interface = include_str!("../fixtures/interface_inherited_struct.json"); + + let path = prj.root().join("interface_inherited_struct.json"); + fs::write(&path, interface).unwrap(); + + // Without --flatten, a separate library is generated for the struct + cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ + r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +library IBase { + struct TestStruct { + address asset; + } +} + +interface Interface { + function test(IBase.TestStruct memory param) external; +} + +"# + ]]); + + // With --flatten, the struct is inlined into the interface + cmd.cast_fuse().arg("interface").arg("--flatten").arg(&path).assert_success().stdout_eq(str![ + [r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface Interface { + // Types from `IBase` + struct TestStruct { + address asset; + } + + function test(TestStruct memory param) external; +} + +"#] + ]); +}); + // tests that fetches WETH interface from etherscan // -casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_weth_interface_from_etherscan, |_prj, cmd| { cmd.args([ "interface", "--etherscan-api-key", @@ -1774,8 +2765,10 @@ casttest!(ens_resolve_no_dot_eth, |_prj, cmd| { cmd.args(["resolve-name", "emo", "--rpc-url", ð_rpc_url, "--verify"]) .assert_failure() .stderr_eq(str![[r#" -Error: ENS resolver not found for name "emo" +Error: Failed to resolve ENS name: emo +Context: +- [..] "#]]); }); @@ -1920,6 +2913,114 @@ casttest!(send_eip7702, async |_prj, cmd| { "#]]); }); +casttest!(send_eip7702_multiple_auth, async |_prj, cmd| { + let (_api, handle) = + anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; + let endpoint = handle.http_endpoint(); + + // Create a pre-signed authorization using a different signer (account index 1) + let signer: PrivateKeySigner = + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d".parse().unwrap(); + // Anvil default chain_id is 31337 + let auth = Authorization { + chain_id: U256::from(31337), + // Delegate to account index 2 + address: address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), + nonce: 0, + }; + let signature = signer.sign_hash(&auth.signature_hash()).await.unwrap(); + let signed_auth = auth.into_signed(signature); + let encoded_auth = hex::encode_prefixed(alloy_rlp::encode(&signed_auth)); + + // Send transaction with multiple --auth flags: one address and one pre-signed authorization + let output = cmd + .args([ + "send", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--auth", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "--auth", + &encoded_auth, + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + "--gas-limit", + "100000", + "--json", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Extract transaction hash from JSON output + let json: serde_json::Value = serde_json::from_str(&output).unwrap(); + let tx_hash = json["transactionHash"].as_str().unwrap(); + + // Use cast tx to verify multiple authorizations were included + let tx_output = cmd + .cast_fuse() + .args(["tx", tx_hash, "--rpc-url", &endpoint, "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + + let tx_json: serde_json::Value = serde_json::from_str(&tx_output).unwrap(); + let auth_list = tx_json["authorizationList"].as_array().unwrap(); + + // Verify we have 2 authorizations + assert_eq!(auth_list.len(), 2, "Expected 2 authorizations in the transaction"); +}); + +// Test that multiple address-based authorizations are rejected +casttest!(send_eip7702_multiple_address_auth_rejected, async |_prj, cmd| { + let (_api, handle) = + anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; + let endpoint = handle.http_endpoint(); + + cmd.args([ + "send", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--auth", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "--auth", + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + ]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: Multiple address-based authorizations provided. Only one address can be specified; use pre-signed authorizations (hex-encoded) for multiple authorizations. + +"#]]); +}); + +casttest!(send_sync, async |_prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let endpoint = handle.http_endpoint(); + + let output = cmd + .args([ + "send", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "--value", + "1", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + "--sync", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + assert!(output.contains("transactionHash")); + assert!(output.contains("blockNumber")); + assert!(output.contains("gasUsed")); +}); + casttest!(hash_message, |_prj, cmd| { cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#" 0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 @@ -1977,7 +3078,7 @@ casttest!(format_units, |_prj, cmd| { // tests that fetches a sample contract creation code // -casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_creation_code_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", @@ -1996,7 +3097,7 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { // tests that fetches a sample contract creation args bytes // -casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", @@ -2016,7 +3117,7 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { // tests that displays a sample contract creation args // -casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_constructor_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "constructor-args", @@ -2034,7 +3135,7 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { }); // -casttest!(test_non_mainnet_traces, |prj, cmd| { +casttest!(flaky_test_non_mainnet_traces, |prj, cmd| { prj.clear(); cmd.args([ "run", @@ -2050,7 +3151,7 @@ Executing previous transactions from the block. Traces: [33841] FiatTokenProxy::fallback(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) ├─ [26673] FiatTokenV2_2::approve(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) [delegatecall] - │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, value: 164054805 [1.64e8]) + │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, amount: 164054805 [1.64e8]) │ └─ ← [Return] true └─ ← [Return] true ... @@ -2058,9 +3159,37 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // -casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { +casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "artifact", @@ -2098,8 +3227,7 @@ contract LocalProjectContract { } } "#, - ) - .unwrap(); + ); prj.add_script( "LocalProjectScript", r#" @@ -2114,8 +3242,7 @@ contract LocalProjectScript is Script { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2193,6 +3320,7 @@ forgetest_async!(show_state_changes_in_traces, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); // Deploy counter contract. cmd.args([ "script", @@ -2252,7 +3380,7 @@ Transaction successfully executed. }); // tests cast can decode external libraries traces with project cached selectors -forgetest_async!(decode_external_libraries_with_cached_selectors, |prj, cmd| { +forgetest_async!(flaky_decode_external_libraries_with_cached_selectors, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); @@ -2266,8 +3394,7 @@ library ExternalLib { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterInExternalLib", r#" @@ -2282,8 +3409,7 @@ contract CounterInExternalLib { } } "#, - ) - .unwrap(); + ); prj.add_script( "CounterInExternalLibScript", r#" @@ -2297,8 +3423,7 @@ contract CounterInExternalLibScript is Script { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2318,9 +3443,9 @@ contract CounterInExternalLibScript is Script { .unwrap() .tx_hash(); - // Cache project selectors. + // Build and cache project selectors. + cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); - // Assert cast with local artifacts can decode external lib signature. cmd.cast_fuse() .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) @@ -2329,9 +3454,9 @@ contract CounterInExternalLibScript is Script { ... Traces: [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - ├─ [..] 0x52F3e85EC3F0f9D0a2200D646482fcD134D5adc9::updateCounterInExternalLib(0, 100) [delegatecall] + ├─ [..] [..]::updateCounterInExternalLib(0, 100) [delegatecall] │ └─ ← [Stop] - └─ ← [Return] 62 bytes of code + └─ ← [Return] [..] bytes of code Transaction successfully executed. @@ -2359,11 +3484,12 @@ forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| { .assert_success(); }); -// https://github.com/foundry-rs/foundry/issues/10189 -forgetest_async!(cast_call_custom_override, |prj, cmd| { +// https://github.com/foundry-rs/foundry/issues/10848 +forgetest_async!(cast_call_disable_labels, |prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_source( "Counter", r#" @@ -2375,8 +3501,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Deploy counter contract. cmd.args([ @@ -2407,23 +3532,174 @@ contract Counter { "#]]); - // Override balance, `getBalance()` should return overridden value. + // Override state, `number()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--labels", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:WETH", "--rpc-url", &handle.http_endpoint(), - "--override-balance", - "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111", - "getBalance(address)(uint256)", - "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--override-state", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", + "number()(uint256)", + "--trace", ]) .assert_success() .stdout_eq(str![[r#" -4369 +Traces: + [2402] WETH::number() + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 -"#]]); + +Transaction successfully executed. +[GAS] + +"#]]); + + // Override state, `number()` with `disable_labels`. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--labels", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:WETH", + "--rpc-url", + &handle.http_endpoint(), + "--override-state", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", + "number()(uint256)", + "--trace", + "--disable-labels", + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number() + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/10189 +forgetest_async!(cast_call_custom_override, |prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); + prj.add_source( + "Counter", + r#" +contract Counter { + uint256 public number; + + function getBalance(address target) public returns (uint256) { + return target.balance; + } +} + "#, + ); + + // Deploy counter contract. + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterScript", + ]) + .assert_success(); + + // Override state, `number()` should return overridden value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-state", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", + "number()(uint256)", + ]) + .assert_success() + .stdout_eq(str![[r#" +4660 + +"#]]); + + // Override state, `number()` should return overridden value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-state", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", + "number()(uint256)", + "--trace", + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number() + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 + + +Transaction successfully executed. +[GAS] + +"#]]); + + // Override balance, `getBalance()` should return overridden value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-balance", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111", + "getBalance(address)(uint256)", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + ]) + .assert_success() + .stdout_eq(str![[r#" +4369 + +"#]]); + + // Override balance, `getBalance()` should return overridden value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-balance", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111", + "getBalance(address)(uint256)", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--trace", + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [747] 0x5FbDB2315678afecb367f032d93F642f64180aa3::getBalance(0x5FbDB2315678afecb367f032d93F642f64180aa3) + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001111 + + +Transaction successfully executed. +[GAS] + +"#]]); // Override code with // contract Counter { @@ -2444,6 +3720,28 @@ contract Counter { .stderr_eq(str![[r#" Error: server returned an error response: error code 3: execution reverted, data: "0x" +"#]]); + + // Override code with + // contract Counter { + // uint256 public number1; + // } + // Calling `number()` should revert. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-code", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", + "number()(uint256)", + "--trace" + ]) + .assert_success() + .stderr_eq(str![[r#" +Error: Transaction failed. + "#]]); // Calling `number1()` with overridden state should return new value. @@ -2463,6 +3761,32 @@ Error: server returned an error response: error code 3: execution reverted, data .stdout_eq(str![[r#" 8738 +"#]]); + + // Calling `number1()` with overridden state should return new value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-code", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", + "--override-state", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", + "number1()(uint256)", + "--trace" + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number1() + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000002222 + + +Transaction successfully executed. +[GAS] + "#]]); // Calling `number1()` with overridden state should return new value. @@ -2482,11 +3806,37 @@ Error: server returned an error response: error code 3: execution reverted, data .stdout_eq(str![[r#" 8738 +"#]]); + + // Calling `number1()` with overridden state should return new value. + cmd.cast_fuse() + .args([ + "call", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &handle.http_endpoint(), + "--override-code", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", + "--override-state-diff", + "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", + "number1()(uint256)", + "--trace", + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number1() + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000002222 + + +Transaction successfully executed. +[GAS] + "#]]); }); // https://github.com/foundry-rs/foundry/issues/9541 -forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { +forgetest_async!(flaky_cast_run_impersonated_tx, |_prj, cmd| { let (_api, handle) = anvil::spawn( NodeConfig::test() .with_auto_impersonate(true) @@ -2520,7 +3870,7 @@ forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { }); // -casttest!(fetch_src_blockscout, |_prj, cmd| { +casttest!(flaky_fetch_src_blockscout, |_prj, cmd| { let url = "https://eth.blockscout.com/api"; let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); @@ -2544,7 +3894,7 @@ contract WETH9 { ..."#]]); }); -casttest!(fetch_src_default, |_prj, cmd| { +casttest!(flaky_fetch_src_default, |_prj, cmd| { let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let etherscan_api_key = next_etherscan_api_key(); @@ -2561,62 +3911,63 @@ contract WETH9 { // // -casttest!(odyssey_can_run_p256_precompile, |_prj, cmd| { +casttest!(flaky_osaka_can_run_p256_precompile, |_prj, cmd| { cmd.args([ "run", "0x17b2de59ebd7dfd2452a3638a16737b6b65ae816c1c5571631dc0d80b63c41de", "--rpc-url", next_rpc_endpoint(NamedChain::Base).as_str(), "--quick", - "--odyssey", + "--evm-version", + "osaka", ]) .assert_success() .stdout_eq(str![[r#" Traces: - [88087] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) ├─ [2241] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback(00) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000b55b053230e4effb6609de652fca73fd1c29804 ├─ [9750] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [staticcall] │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [delegatecall] - │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 - │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 - ├─ [61992] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::00000000(00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) - │ ├─ [21620] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] - │ │ ├─ [18617] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] + │ │ └─ ← [Return] 62393 [6.239e4] + │ └─ ← [Return] 62393 [6.239e4] + ├─ [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::[..]() + │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] + │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] │ │ │ ├─ [2369] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::pauseFlag() [staticcall] │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 │ │ │ ├─ [120] PRECOMPILES::sha256(0x7b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d) [staticcall] │ │ │ │ └─ ← [Return] 0xc13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f │ │ │ ├─ [96] PRECOMPILES::sha256(0x424242424242424242424242424242424242424242424242424242424242424201000000c13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f) [staticcall] │ │ │ │ └─ ← [Return] 0xc544bd9a4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0 - │ │ │ ├─ [3450] 0x0000000000000000000000000000000000000100::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] + │ │ │ ├─ [..] P256VERIFY::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b - │ ├─ [5994] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::9e49fbf1(0000000000000000000000000000000000000000000000000000000000000017) - │ │ ├─ [5608] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::9e49fbf1(0000000000000000000000000000000000000000000000000000000000000017) [delegatecall] + │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::checkAndIncrementNonce(23) + │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::checkAndIncrementNonce(23) [delegatecall] │ │ │ └─ ← [Stop] │ │ └─ ← [Return] │ ├─ [3250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] │ │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] - │ │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b - │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b - │ ├─ [16411] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::f81d87a7(000000000000000000000000000000000000000000000000000000000000060f1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) - │ │ ├─ [15711] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::f81d87a7(000000000000000000000000000000000000000000000000000000000000060f1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall] + │ │ │ └─ ← [Return] 38539 [3.853e4] + │ │ └─ ← [Return] 38539 [3.853e4] + │ ├─ [16411] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + │ │ ├─ [15711] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall] │ │ │ ├─ [12963] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) │ │ │ │ ├─ [12263] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) [delegatecall] - │ │ │ │ │ ├─ emit Transfer(param0: 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E, param1: 0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, param2: 1551) - │ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 - │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + │ │ │ │ │ ├─ emit Transfer(from: 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E, to: 0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, amount: 1551) + │ │ │ │ │ └─ ← [Return] true + │ │ │ │ └─ ← [Return] true │ │ │ └─ ← [Stop] │ │ └─ ← [Return] │ ├─ [1250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] │ │ ├─ [553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] - │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a - │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a - │ ├─ [5675] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::00000001(00000000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) - │ │ ├─ [4148] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) - │ │ │ ├─ [3693] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] + │ │ │ └─ ← [Return] 40090 [4.009e4] + │ │ └─ ← [Return] 40090 [4.009e4] + │ ├─ [..] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::[..]() + │ │ ├─ [..] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) + │ │ │ ├─ [..] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] │ │ │ │ ├─ [435] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback() │ │ │ │ │ ├─ [55] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::fallback() [delegatecall] │ │ │ │ │ │ └─ ← [Stop] @@ -2658,8 +4009,7 @@ contract SimpleStorage { } } "#, - ) - .unwrap(); + ); prj.add_script( "SimpleStorageScript", r#" @@ -2673,8 +4023,7 @@ contract SimpleStorageScript is Script { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2704,13 +4053,13 @@ contract SimpleStorageScript is Script { &handle.http_endpoint(), ]) .assert_failure().stderr_eq(str![[r#" -Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8": AddressInsufficientBalance(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 1000) +Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8"[..] "#]]); }); // -casttest!(estimate_base_da, |_prj, cmd| { +casttest!(flaky_estimate_base_da, |_prj, cmd| { cmd.args(["da-estimate", "30558838", "-r", "https://mainnet.base.org/"]) .assert_success() .stdout_eq(str![[r#" @@ -2718,3 +4067,1184 @@ Estimated data availability size for block 30558838 with 225 transactions: 52916 "#]]); }); + +// +casttest!(cast_call_return_array_of_tuples, |_prj, cmd| { + cmd.args([ + "call", + "0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b", + "facets() returns ((address,bytes4[])[])", + "--rpc-url", + "https://rpc.viction.xyz", + ]) + .assert_success() + .stdout_eq(str![[r#" +[[..]] + +"#]]); +}); + +// +casttest!(cast_call_on_contract_with_no_code_prints_warning, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "call", + "0x0000000000000000000000000000000000000000", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stderr_eq(str![[r#" +Warning: Contract code is empty + +"#]]) + .stdout_eq(str![[r#" +0x + +"#]]); +}); + +// +#[cfg(feature = "optimism")] +casttest!(tx_raw_opstack_deposit, |_prj, cmd| { + cmd.args([ + "tx", + "0xf403cba612d1c01c027455c0d97427ccd5f7f99aac30017e065f81d1e30244ea", + "--raw", + "-n", + "optimism", + "--rpc-url", + "https://sepolia.base.org", + ]).assert_success() + .stdout_eq(str![[r#" +0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +"#]]); +}); + +casttest!(tx_raw_tempo, |_prj, cmd| { + cmd.args([ + "tx", + "0xa24c6bbeea629a80be79e970a9749d0cbc6ee31625a0b75f585c173ab15a18ec", + "--raw", + "-n", + "tempo", + "--rpc-url", + "https://rpc.moderato.tempo.xyz", + ]).assert_success() + .stdout_eq(str![[r#" +0x76f8cf82a5bf1485059682f018830494e5f85ef85c9420c0000000000000000000007d9cc57068833ea780b84440c10f190000000000000000000000008a871f4189067637cfc4cc1500abd6244bf1df740000000000000000000000000000000000000000000000000000000005f5e100c08082057e80809420c000000000000000000000000000000000000080c0b841eb100c4cbd96903bf9e97968c0982670bb90fc191ee4544c7ff32d44e901dbea3f6fbdd58255051135c2fe1aa81583a270d96009cbe375f4605ef15971273a4f1b + +"#]]); +}); + +// Test that cast send --create works correctly with constructor arguments +// +forgetest_async!(cast_send_create_with_constructor_args, |prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let endpoint = handle.http_endpoint(); + + // Deploy a simple contract with constructor arguments + // Contract source that takes constructor args + prj.add_source( + "ConstructorContract", + r#" +contract ConstructorContract { + uint256 public value; + string public name; + + constructor(uint256 _value, string memory _name) { + value = _value; + name = _name; + } + + function getValue() public view returns (uint256) { + return value; + } +} +"#, + ); + + // Compile to get bytecode + cmd.forge_fuse().args(["build"]).assert_success(); + + // Get the compiled bytecode + let bytecode_path = prj.root().join("out/ConstructorContract.sol/ConstructorContract.json"); + let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); + let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); + let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); + + // Use cast send --create with constructor arguments + let output = cmd + .cast_fuse() + .args([ + "send", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + "--create", + bytecode, + "constructor(uint256,string)", + "42", + "TestContract", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Extract the deployed contract address from output + let lines: Vec<&str> = output.lines().collect(); + let mut address = None; + for line in lines { + if line.contains("contractAddress") { + let parts: Vec<&str> = line.split_whitespace().collect(); + address = Some(parts[1]); + break; + } + } + let address = address.expect("Contract address not found in output"); + + // Verify the contract was deployed correctly by calling getValue() + let value_output = cmd + .cast_fuse() + .args(["call", address, "getValue()", "--rpc-url", &endpoint]) + .assert_success() + .get_output() + .stdout_lossy(); + + // The value should be 42 (0x2a in hex) + assert!( + value_output.contains("0x000000000000000000000000000000000000000000000000000000000000002a") + ); +}); + +// Test that cast estimate --create works correctly with constructor arguments +// +casttest!(cast_estimate_create_with_constructor_args, |prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // Add a simple contract with constructor arguments + prj.add_source( + "EstimateContract", + r#" +contract EstimateContract { + uint256 public value; + string public name; + + constructor(uint256 _value, string memory _name) { + value = _value; + name = _name; + } +} +"#, + ); + + // Compile to get bytecode + cmd.forge_fuse().args(["build"]).assert_success(); + + // Get the compiled bytecode + let bytecode_path = prj.root().join("out/EstimateContract.sol/EstimateContract.json"); + let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); + let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); + let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); + + let output = cmd + .cast_fuse() + .args([ + "estimate", + "--rpc-url", + eth_rpc_url.as_str(), + "--create", + bytecode, + "constructor(uint256,string)", + "100", + "TestContract", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Parse the gas estimate + let gas_estimate = output.trim().parse::().expect("Failed to parse gas estimate"); + + // Gas estimate should be positive and reasonable for contract deployment + assert!(gas_estimate > 50000, "Gas estimate too low for contract deployment"); + assert!(gas_estimate < 5000000, "Gas estimate unreasonably high"); +}); + +// Test edge case: empty constructor arguments +// +forgetest_async!(cast_send_create_empty_constructor, |prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let endpoint = handle.http_endpoint(); + + // Simple contract with no constructor arguments + prj.add_source( + "SimpleContract", + r#" +contract SimpleContract { + uint256 public constant VALUE = 42; +} +"#, + ); + + // Compile + cmd.forge_fuse().args(["build"]).assert_success(); + + // Get bytecode + let bytecode_path = prj.root().join("out/SimpleContract.sol/SimpleContract.json"); + let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); + let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); + let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); + + // Deploy with empty constructor + let output = cmd + .cast_fuse() + .args([ + "send", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + "--create", + bytecode, + "constructor()", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify deployment succeeded + assert!(output.contains("contractAddress")); +}); + +// Test complex constructor arguments (multiple types) +// +forgetest_async!(cast_send_create_complex_constructor, |prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let endpoint = handle.http_endpoint(); + + // Contract with complex constructor + prj.add_source( + "ComplexContract", + r#" +contract ComplexContract { + address public owner; + uint256[] public values; + bool public active; + + constructor(address _owner, uint256[] memory _values, bool _active) { + owner = _owner; + values = _values; + active = _active; + } + + function getValuesLength() public view returns (uint256) { + return values.length; + } +} +"#, + ); + + // Compile + cmd.forge_fuse().args(["build"]).assert_success(); + + // Get bytecode + let bytecode_path = prj.root().join("out/ComplexContract.sol/ComplexContract.json"); + let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); + let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); + let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); + + // Deploy with complex arguments + let output = cmd + .cast_fuse() + .args([ + "send", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + "--create", + bytecode, + "constructor(address,uint256[],bool)", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "[1,2,3,4,5]", + "true", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Extract deployed address + let lines: Vec<&str> = output.lines().collect(); + let mut address = None; + for line in lines { + if line.contains("contractAddress") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + address = Some(parts[1]); + break; + } + } + } + let address = address.expect("Contract address not found in output"); + + // Verify the array length was set correctly + let length_output = cmd + .cast_fuse() + .args(["call", address, "getValuesLength()", "--rpc-url", &endpoint]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Should return 5 (0x5 in hex) + assert!( + length_output + .contains("0x0000000000000000000000000000000000000000000000000000000000000005") + ); +}); + +casttest!(recover_authority, |_prj, cmd| { + let auth = r#"{ + "chainId": "0x1", + "address": "0xb684710e6d5914ad6e64493de2a3c424cc43e970", + "nonce": "0x3dc1", + "yParity": "0x1", + "r": "0x2f15ba55009fcd3682cd0f9c9645dd94e616f9a969ba3f1a5a2d871f9fe0f2b4", + "s": "0x53c332a83312d0b17dd4c16eeb15b1ff5223398b14e0a55c70762e8f3972b7a5" + }"#; + cmd.args(["recover-authority", auth]).assert_success().stdout_eq(str![[r#" +0x17816E9A858b161c3E37016D139cf618056CaCD4 + +"#]]); +}); + +// +// tests `cast code --disassemble` +casttest!(can_disassemble_contract_code, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "code", + "--disassemble", + "--rpc-url", + rpc.as_str(), + "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C", + ]) + .assert_success() + .stdout_eq(str![[r#" +00000000: PUSH1 0x60 +00000002: PUSH1 0x40 +00000004: MSTORE +00000005: CALLDATASIZE +00000006: ISZERO +00000007: PUSH2 0x010f +0000000a: JUMPI +0000000b: PUSH4 0xffffffff +00000010: PUSH29 0x0100000000000000000000000000000000000000000000000000000000 +0000002e: PUSH1 0x00 +... +"#]]); +}); + +// tests that cast call --trace selects TempoEvmNetwork when Tempo is inferred from +// the fork RPC, or when a Tempo chain ID is provided explicitly via --chain. +casttest!(cast_call_trace_selects_tempo_network, async |_prj, cmd| { + let (_, tempo_handle) = anvil::spawn(NodeConfig::test_tempo()).await; + let (_, eth_handle) = anvil::spawn(NodeConfig::test()).await; + + let token = PATH_USD_ADDRESS.to_string(); + for (name, rpc, extra_args) in [ + ("inferred Tempo RPC", tempo_handle.http_endpoint(), Vec::<&str>::new()), + ("explicit Tempo --chain", eth_handle.http_endpoint(), vec!["--chain", "4217"]), + ] { + cmd.cast_fuse(); + let mut args = vec!["call", &token, "decimals()(uint8)", "--rpc-url", &rpc, "--trace"]; + args.extend(extra_args); + + let output = cmd.args(args).assert_success().get_output().stdout_lossy(); + + assert!( + output.contains("PathUSD::decimals()") && output.contains("← [Return] 6"), + "expected traced Tempo TIP20 call to execute successfully for {name}, got:\n{output}" + ); + } +}); + +// tests that cast call properly applies state diff override +// +casttest!(cast_call_can_override_state_diff, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "call", + "--rpc-url", + rpc.as_str(), + "--data", + "0x", + "0x1EA77b250eF79e917A5A637D5BB82D0980653F1B", + "--override-state-diff", + "0x1EA77b250eF79e917A5A637D5BB82D0980653F1B:1:1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x1337 + +"#]]); + cmd.args(["--trace"]).assert_success().stdout_eq(str![[r#" +Traces: + [7281] 0x1EA77b250eF79e917A5A637D5BB82D0980653F1B::fallback() + ├─ [2275] 0xe537cb8a46Bd179c0C36aB7E3Fdecd759C8B80fc::fallback() [delegatecall] + │ └─ ← [Return] 0x1337 + └─ ← [Return] 0x1337 + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// Tests for negative number argument parsing +// Ensures that negative numbers in function arguments are properly parsed +// instead of being treated as command flags + +// Test that cast call accepts negative numbers as function arguments +casttest!(cast_call_negative_numbers, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + // Test with negative int parameter - should not treat -456789 as a flag + cmd.args([ + "call", + "0xAbCdEf1234567890aBcDeF1234567890aBcDeF12", + "processValue(int128)", + "-456789", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success(); +}); + +// Test negative numbers with multiple parameters +casttest!(cast_call_multiple_negative_numbers, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "call", + "--rpc-url", + rpc.as_str(), + "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", + "calculateDelta(int64,int32,uint16)", + "-987654321", + "-42", + "65535", + ]) + .assert_success(); +}); + +// Test negative numbers mixed with flags +casttest!(cast_call_negative_with_flags, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "call", + "--trace", // flag before + "0x9876543210FeDcBa9876543210FeDcBa98765432", + "updateBalance(int256)", + "-777888", + "--rpc-url", + rpc.as_str(), // flag after + ]) + .assert_success(); +}); + +// Test that actual invalid flags are still caught +casttest!(cast_call_invalid_flag_still_caught, |_prj, cmd| { + cmd.args([ + "call", + "--invalid-flag", // This should be caught as invalid + "0x5555555555555555555555555555555555555555", + ]) + .assert_failure() + .stderr_eq(str![[r#" +error: unexpected argument '--invalid-flag' found + + tip: to pass '--invalid-flag' as a value, use '-- --invalid-flag' + +Usage: cast[..] call [OPTIONS] [TO] [SIG] [ARGS]... [COMMAND] + +For more information, try '--help'. + +"#]]); +}); + +// Test cast estimate with negative numbers +casttest!(cast_estimate_negative_numbers, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "estimate", + "0xBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBb", + "rebalance(int64)", + "-8888", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success(); +}); + +// Test cast mktx with negative numbers +casttest!(cast_mktx_negative_numbers, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "mktx", + "0x1111111111111111111111111111111111111111", + "settleDebt(int256)", + "-15000", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil wallet #0 + "--rpc-url", + rpc.as_str(), + "--gas-limit", + "100000", + ]) + .assert_success(); +}); + +// Test cast mktx with EIP-4844 blob transaction (legacy format) +casttest!(cast_mktx_eip4844_blob, |prj, cmd| { + // Create a temporary blob data file + let blob_data = b"dummy blob data for testing"; + let blob_path = prj.root().join("blob_data.bin"); + fs::write(&blob_path, blob_data).unwrap(); + + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "100000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "--blob", + "--eip4844", + "--blob-gas-price", + "1000000", + "--path", + blob_path.to_str().unwrap(), + "0x0000000000000000000000000000000000000001", + ]) + .assert_success(); +}); + +// Test cast mktx with EIP-7594 blob transaction (default format) +casttest!(cast_mktx_eip7594_blob, |prj, cmd| { + // Create a temporary blob data file + let blob_data = b"dummy peerdas blob data for testing"; + let blob_path = prj.root().join("peerdas_blob_data.bin"); + fs::write(&blob_path, blob_data).unwrap(); + + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "100000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "--blob", + "--blob-gas-price", + "1000000", + "--path", + blob_path.to_str().unwrap(), + "0x0000000000000000000000000000000000000001", + ]) + .assert_success(); +}); + +// Test cast access-list with negative numbers +casttest!(cast_access_list_negative_numbers, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Sepolia); + cmd.args([ + "access-list", + "0x9999999999999999999999999999999999999999", + "adjustPosition(int128)", + "-33333", + "--gas-limit", + "1000000", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success(); +}); + +// tests that cast call properly applies multiple state diff overrides +// +casttest!(cast_call_can_override_several_state_diff, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + cmd.args([ + "call", + "--trace", + "--from", + "0xf6F444fD3B0088c1375671c05A7513661beFa4e6", + "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332", + "--rpc-url", + rpc.as_str(), + "--block", + "23290753", + "--data", + "0xe75235b8", + "--override-state-diff", + "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xf0af0268363540b847b4c07f2f9a0401c607c1b11ebca511724a71755dfd4137:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:4:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8:0,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947:0", + ]) + .assert_success() + .stdout_eq(str![[r#" +... + [..] 0x5EA1d9A6dDC3A0329378a327746D71A2019eC332::getThreshold() +... + +"#]]); + + cmd.cast_fuse().args([ + "call", + "--trace", + "--from", + "0x2066901073a33ba2500274704aB04763875cF210", + "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332", + "--rpc-url", + rpc.as_str(), + "--block", + "23290753", + "--data", + "0x2f54bf6e0000000000000000000000002066901073a33ba2500274704ab04763875cf210", + "--override-state-diff", + "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xf0af0268363540b847b4c07f2f9a0401c607c1b11ebca511724a71755dfd4137:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:4:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8:0,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947:0", + ]) + .assert_success() + .stdout_eq(str![[r#" +... + [..] 0x5EA1d9A6dDC3A0329378a327746D71A2019eC332::isOwner(0x2066901073a33ba2500274704aB04763875cF210) +... +"#]]); +}); + +casttest!(correct_json_serialization, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + // cast calldata "decimals()" + let calldata = "0x313ce567"; + let tokens = [ + "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT + "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI + "0x6b175474e89094c44da98b954eedeac495271d0f", // WETH + ]; + let calldata_args = format!( + "[{}]", + tokens + .iter() + .map(|token| format!("({token},false,{calldata})")) + .collect::>() + .join(",") + ); + let args = vec![ + "call", + "--json", + "--rpc-url", + rpc.as_str(), + "0xcA11bde05977b3631167028862bE2a173976CA11", + "aggregate3((address,bool,bytes)[])((bool,bytes)[])", + &calldata_args, + ]; + let expected_output = json!([[ + [true, "0x0000000000000000000000000000000000000000000000000000000000000006"], + [true, "0x0000000000000000000000000000000000000000000000000000000000000012"], + [true, "0x0000000000000000000000000000000000000000000000000000000000000012"] + ]]); + let decoded: serde_json::Value = + serde_json::from_slice(&cmd.args(args).assert_success().get_output().stdout) + .expect("not valid json"); + assert_eq!(decoded, expected_output); +}); + +// Test cast abi-encode-event with indexed parameters +casttest!(abi_encode_event_indexed, |_prj, cmd| { + cmd.args([ + "abi-encode-event", + "Transfer(address indexed from, address indexed to, uint256 value)", + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "1000", + ]) + .assert_success() + .stdout_eq(str![[r#" +[topic0]: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef +[topic1]: 0x0000000000000000000000001234567890123456789012345678901234567890 +[topic2]: 0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd +[data]: 0x00000000000000000000000000000000000000000000000000000000000003e8 + +"#]]); +}); + +// Test cast abi-encode-event with no indexed parameters +casttest!(abi_encode_event_no_indexed, |_prj, cmd| { + cmd.args([ + "abi-encode-event", + "Approval(address owner, address spender, uint256 value)", + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "2000" + ]) + .assert_success() + .stdout_eq(str![[r#" +[topic0]: 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 +[data]: 0x0000000000000000000000001234567890123456789012345678901234567890000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd00000000000000000000000000000000000000000000000000000000000007d0 + +"#]]); +}); + +// Test cast abi-encode-event with dynamic indexed parameter (string) +casttest!(abi_encode_event_dynamic_indexed, |_prj, cmd| { + cmd.args(["abi-encode-event", "Log(string indexed message, uint256 data)", "hello", "42"]) + .assert_success() + .stdout_eq(str![[r#" +[topic0]: 0xdd970dd9b5bfe707922155b058a407655cb18288b807e2216442bca8ad83d6b5 +[topic1]: 0x984002fcc0ca639f96622add24c2edd2fe72c65e71ca3faa243e091e0bc7cdab +[data]: 0x000000000000000000000000000000000000000000000000000000000000002a + +"#]]); +}); + +// Test cast run Celo transfer with precompiles. +casttest!( + #[ignore = "requires debug_traceTransaction, which most free Celo RPC endpoints no longer support"] + flaky_run_celo_with_precompiles, + |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Celo); + cmd.args([ + "run", + "0xa652b9f41bb1a617ea6b2835b3316e79f0f21b8264e7bcd20e57c4092a70a0f6", + "--quick", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [17776] 0x471EcE3750Da237f93B8E339c536989b8978a438::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) + ├─ [12370] 0xFeA1B35f1D5f2A58532a70e7A32e6F2D3Bc4F7B1::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) [delegatecall] + │ ├─ [9000] CELO_TRANSFER_PRECOMPILE::00000000(00000000000000008106680ba7095cfd8f4351a8b7041da3060afb83000000000000000000000000d2eb2d37d238caeff39cfa36a013299c6dbac56a00000000000000000000000000000000000000000000000001ea4644d3010000) + │ │ └─ ← [Return] + │ ├─ emit Transfer(param0: 0x8106680Ba7095CfD8F4351a8B7041da3060Afb83, param1: 0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, param2: 138000000000000000 [1.38e17]) + │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + + +Transaction successfully executed. +[GAS] + +"#]]); + } +); +casttest!(keccak_stdin_bytes, |_prj, cmd| { + cmd.args(["keccak"]).stdin("0x12").assert_success().stdout_eq(str![[r#" +0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa + +"#]]); +}); + +casttest!(keccak_stdin_bytes_with_newline, |_prj, cmd| { + cmd.args(["keccak"]).stdin("0x12\n").assert_success().stdout_eq(str![[r#" +0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa + +"#]]); +}); + +// Test cast send with raw --data flag using encoded calldata +forgetest_async!(cast_send_with_data, |prj, cmd| { + let (api, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); + + // Deploy counter contract + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterScript", + ]) + .assert_success(); + + // setNumber(111) encoded: selector 0x3fb5c1cb + uint256(111) + let calldata = "0x3fb5c1cb000000000000000000000000000000000000000000000000000000000000006f"; + + // Send tx using --data instead of sig+args + cmd.cast_fuse() + .args([ + "send", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "--data", + calldata, + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + + // Verify via trace that setNumber(111) was called + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + cmd.cast_fuse() + .args([ + "run", + format!("{tx_hash}").as_str(), + "-vvvvv", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) + ├─ storage changes: + │ @ 0: 0 → 111 + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// tests that the --curl flag outputs a valid curl command for cast rpc +casttest!(curl_rpc, |_prj, cmd| { + let rpc = "https://eth.example.com"; + + let output = cmd + .args(["rpc", "eth_blockNumber", "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("-H 'Content-Type: application/json'")); + assert!(output.contains("eth_blockNumber")); + assert!(output.contains("jsonrpc")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast block-number +casttest!(curl_block_number, |_prj, cmd| { + let rpc = "https://eth.example.com"; + + let output = cmd + .args(["block-number", "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_blockNumber")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast chain-id +casttest!(curl_chain_id, |_prj, cmd| { + let rpc = "https://eth.example.com"; + + let output = cmd + .args(["chain-id", "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_chainId")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast gas-price +casttest!(curl_gas_price, |_prj, cmd| { + let rpc = "https://eth.example.com"; + + let output = cmd + .args(["gas-price", "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_gasPrice")); + assert!(output.contains(rpc)); +}); + +// tests that the --curl flag outputs a valid curl command for cast call +casttest!(curl_call, |_prj, cmd| { + let rpc = "https://eth.example.com"; + let to = "0xdead000000000000000000000000000000000000"; + + let output = cmd + .args(["call", to, "balanceOf(address)(uint256)", to, "--rpc-url", rpc, "--curl"]) + .assert_success() + .get_output() + .stdout_lossy(); + + // Verify curl command structure + assert!(output.contains("curl -X POST")); + assert!(output.contains("eth_call")); + assert!(output.contains(rpc)); +}); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that invalid hex calldata (odd length) produces a clear error message +casttest!(cast_call_invalid_hex_calldata_error, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0x0", // Invalid: odd length hex + "--rpc-url", + rpc.as_str(), + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Invalid hex calldata '0x0': odd number of digits + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that valid hex calldata works correctly +casttest!(cast_call_valid_hex_calldata, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0x00", // Valid: even length hex + "--rpc-url", + rpc.as_str(), + ]) + .assert_success(); +}); + +// https://github.com/foundry-rs/foundry/issues/11584 +// Tests that invalid hex with uppercase 0X prefix also produces clear error +casttest!(cast_call_invalid_hex_uppercase_prefix, |_prj, cmd| { + let rpc = next_rpc_endpoint(NamedChain::Mainnet); + cmd.args([ + "call", + "0xdead000000000000000000000000000000000000", + "--data", + "0X1", // Invalid: odd length hex with uppercase prefix + "--rpc-url", + rpc.as_str(), + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Invalid hex calldata '0X1': odd number of digits + +"#]]); +}); + +// Test decode-tx with a valid EIP-1559 Ethereum transaction +casttest!(cast_decode_tx_ethereum, |_prj, cmd| { + // Ethereum mainnet 0x02d2ae7454273bcc02405276b208c03b83ea979ec06aa6f9bc48f81ca343dc1d + let tx = "0x02f8b1018223e48374667184147d0df48301388094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000594bd0e0c83d619e375459f0f9b85a17cb8391b400000000000000000000000000000000000000000000000000000000295d9980c080a0704a930876b48fc99cbee17597dc6660c82cd4de5d6f4ace58fe0fcf3bbcb942a07c94560ed0850c9b8e9ab5f3c50902113decea95db3a6bac7c76edd11b7138aa"; + let output = cmd.args(["decode-tx", tx]).assert_success().get_output().stdout.clone(); + let output: String = serde_json::from_slice(&output).unwrap(); + let decoded: serde_json::Value = serde_json::from_str(&output).unwrap(); + assert_eq!(decoded["type"], "0x2"); + assert_eq!(decoded["nonce"], String::from("0x23e4")); +}); + +// Test decode-tx with --network tempo accepts the flag and decodes correctly +casttest!(cast_decode_tx_tempo, |_prj, cmd| { + // Tempo mainnet 0xa26f2dc8ed22d65ad5e5b3acc40295d89c331fd1e79d34b13baa3f6f47b136dc + let tx = "0x76f9033a821079843b9aca0085098bca5a0083241bc4f9011cf85c9420c000000000000000000000b9537d11c60e8b5080b844095ea7b30000000000000000000000000901aed692c755b870f9605e56baa66c35beff6900000000000000000000000000000000000000000000000000000000000f4240f8bc940901aed692c755b870f9605e56baa66c35beff6980b8a4c79ea485000000000000000000000000b48141c3da5030def992bdc686f0e9a8729206b600000000000000000000000020c000000000000000000000b9537d11c60e8b5000000000000000000000000000000000000000000000000000000000000f424055d3e824159a36fa0d16bbe5c91f497568124441cc6731b8638263d82bfeea6f0000000000000000000000007cfdf901fba309a4a9189a56bede35701aea96dac0a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808469b2c5ef809420c000000000000000000000b9537d11c60e8b5080c0f9016ef83a82107980947cfdf901fba309a4a9189a56bede35701aea96da8469da52c0dbda9420c000000000000000000000b9537d11c60e8b508405f5e100b9012f02a7cb28053c8ee4e5394fc67a0018dc1c622dad5ce3591b8ca13094ae86d11ba61d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22355068657374624a6a6d4c4c514331622d696f6b5334755f496748307a4d4a6b5a3677385f4e667532596b222c226f726967696e223a2268747470733a2f2f77616c6c65742e74656d706f2e78797a222c2263726f73734f726967696e223a66616c73657d763ed1d6d008091ef06390b2d3150e326795daeba580f0bebc84242d503f13e71328ee2af9426777d4fa5ee148753262ea41b5503522967a6b877c04e5c0c2a7e1af1c624e48eba171e5f521d8f4c89f80b04ecb3f5ba6060109ccb56d344ed129f2a97fb8757cb3cbdcbc636b949fedad4b74490af444a49f5b83d6e0bb0750b8560339e87712af0f3c9c3c1f7c9c57190bb8c8db125d721b2cbf2ba3ac52332b11e0f5d397406da076668a40840135e950e5967bc5f032e8502e33ea91899b90cd8f6106d27efb934a4dac5dcfd0ca5c491733faf91c1c"; + let output = cmd + .args(["decode-tx", "--network", "tempo", tx]) + .assert_success() + .get_output() + .stdout + .clone(); + let output: String = serde_json::from_slice(&output).unwrap(); + let decoded: serde_json::Value = serde_json::from_str(&output).unwrap(); + assert_eq!(decoded["type"], "0x76"); + assert_eq!(decoded["feeToken"], "0x20c000000000000000000000b9537d11c60e8b50"); +}); + +// Test decode-tx with invalid hex input +casttest!(cast_decode_tx_invalid, |_prj, cmd| { + cmd.args(["decode-tx", "0xinvalid"]).assert_failure(); +}); + +// Test that `--network tempo` and `-n tempo` (short form) produce identical output for decode-tx. +// Uses a known Tempo mainnet transaction. +casttest!(cast_decode_tx_network_flag_short_and_long_equivalent, |_prj, cmd| { + let tx = "0x76f8cf82a5bf1485059682f018830494e5f85ef85c9420c0000000000000000000007d9cc57068833ea780b84440c10f190000000000000000000000008a871f4189067637cfc4cc1500abd6244bf1df740000000000000000000000000000000000000000000000000000000005f5e100c08082057e80809420c000000000000000000000000000000000000080c0b841eb100c4cbd96903bf9e97968c0982670bb90fc191ee4544c7ff32d44e901dbea3f6fbdd58255051135c2fe1aa81583a270d96009cbe375f4605ef15971273a4f1b"; + + let via_long = cmd + .args(["decode-tx", "--network", "tempo", tx]) + .assert_success() + .get_output() + .stdout + .clone(); + + let via_short = cmd + .cast_fuse() + .args(["decode-tx", "-n", "tempo", tx]) + .assert_success() + .get_output() + .stdout + .clone(); + + assert_eq!(via_long, via_short, "--network tempo and -n tempo should produce same output"); +}); + +// Test that `--network optimism` and `-n optimism` produce identical output for decode-tx. +// Uses a known OP-stack deposit transaction (same tx as tx_raw_opstack_deposit test). +#[cfg(feature = "optimism")] +casttest!(cast_decode_tx_network_optimism_short_and_long_equivalent, |_prj, cmd| { + let tx = "0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + let via_long = cmd + .args(["decode-tx", "--network", "optimism", tx]) + .assert_success() + .get_output() + .stdout + .clone(); + + let via_short = cmd + .cast_fuse() + .args(["decode-tx", "-n", "optimism", tx]) + .assert_success() + .get_output() + .stdout + .clone(); + + assert_eq!( + via_long, via_short, + "--network optimism and -n optimism should produce same output" + ); +}); + +// Test that `cast run --evm-version` correctly updates gas parameters for historical blocks. +// Mainnet tx 0xb856d9...d05d9647 is a Homestead-era tx (block 1,625,693). +// EXP gas pricing differs between Homestead (10 gas/byte) and Spurious Dragon+ (50 gas/byte). +// Without the fix, `set_spec()` only updated the spec discriminant but not the gas_params table, +// so the executor would use stale (latest) gas pricing even when `--evm-version homestead` is set. +casttest!(run_evm_version_updates_gas_params, |_prj, cmd| { + let rpc = next_http_archive_rpc_url(); + let tx = "0xb856d9c8dffeaa317d89ed6abba861d007a708c54971da91233abcd2d05d9647"; + + // Run with --evm-version homestead: gas must match on-chain gasUsed (166651). + let homestead_output = cmd + .args(["run", tx, "--quick", "--rpc-url", rpc.as_str(), "--evm-version", "homestead"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!( + homestead_output.contains("Gas used: 166651"), + "expected Homestead gas (166651), got: {homestead_output}" + ); + + // Run with --evm-version spuriousDragon: higher gas due to EXP repricing (50 vs 10 gas/byte). + let sd_output = cmd + .cast_fuse() + .args(["run", tx, "--quick", "--rpc-url", rpc.as_str(), "--evm-version", "spuriousDragon"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!( + sd_output.contains("Gas used: 177241"), + "expected Spurious Dragon gas (177241), got: {sd_output}" + ); +}); + +// Tests for `cast vaddr` JSON output +casttest!(vaddr_create_json_output, |_prj, cmd| { + // Use a pre-computed salt that satisfies the 4-byte PoW requirement for this owner. + // Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 + // Owner: 0x1234567890123456789012345678901234567890 + let out = cmd + .args([ + "--json", + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + "--count", + "2", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let v: serde_json::Value = serde_json::from_str(out.trim()).expect("valid JSON"); + assert_eq!(v["salt"], "0x0000000000000000000000000000000000000000000000003ee0a78d00000000"); + assert_eq!( + v["registration_hash"], + "0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396" + ); + assert_eq!(v["master_id"], "0x2f51c0c4"); + let addrs = v["virtual_addresses"].as_array().expect("array"); + assert_eq!(addrs.len(), 2); + assert_eq!(addrs[0]["tag"], "0x000000000000"); + assert_eq!( + addrs[0]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000000" + ); + assert_eq!(addrs[1]["tag"], "0x000000000001"); + assert_eq!( + addrs[1]["address"].as_str().unwrap().to_lowercase(), + "0x2f51c0c4fdfdfdfdfdfdfdfdfdfd000000000001" + ); +}); + +casttest!(vaddr_create_plain_output, |_prj, cmd| { + cmd.args([ + "vaddr", + "create", + "--owner", + "0x1234567890123456789012345678901234567890", + "--salt", + "0x0000000000000000000000000000000000000000000000003ee0a78d00000000", + "--no-register", + ]) + .assert_success() + .stdout_eq(str![[r#" +Salt: 0x0000000000000000000000000000000000000000000000003ee0a78d00000000 +Registration hash: 0x000000002f51c0c4f66f3910f799c6b98e2123ef43a401a062eb8ee07498c396 +Master ID: 0x2f51c0c4 + +Virtual addresses: + tag=0x000000000000 [..] + +"#]]); +}); diff --git a/crates/cast/tests/cli/selectors.rs b/crates/cast/tests/cli/selectors.rs index d3a7e84b88079..09fb2c95384ce 100644 --- a/crates/cast/tests/cli/selectors.rs +++ b/crates/cast/tests/cli/selectors.rs @@ -1,7 +1,7 @@ use foundry_test_utils::util::OutputExt; use std::path::Path; -casttest!(error_decode_with_openchain, |prj, cmd| { +casttest!(flaky_error_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" ValueTooHigh(uint256,uint256) @@ -11,7 +11,7 @@ ValueTooHigh(uint256,uint256) "#]]); }); -casttest!(fourbyte, |_prj, cmd| { +casttest!(flaky_fourbyte, |_prj, cmd| { cmd.args(["4byte", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) @@ -27,7 +27,7 @@ For more information, try '--help'. "#]]); }); -casttest!(fourbyte_calldata, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 @@ -36,14 +36,14 @@ casttest!(fourbyte_calldata, |_prj, cmd| { "#]]); }); -casttest!(fourbyte_calldata_only_selector, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata_only_selector, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) "#]]); }); -casttest!(fourbyte_calldata_alias, |_prj, cmd| { +casttest!(flaky_fourbyte_calldata_alias, |_prj, cmd| { cmd.args(["4byte-decode", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 @@ -52,7 +52,7 @@ casttest!(fourbyte_calldata_alias, |_prj, cmd| { "#]]); }); -casttest!(fourbyte_event, |_prj, cmd| { +casttest!(flaky_fourbyte_event, |_prj, cmd| { cmd.args(["4byte-event", "0x7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6"]) .assert_success() .stdout_eq(str![[r#" @@ -61,7 +61,7 @@ updateAuthority(address,uint8) "#]]); }); -casttest!(fourbyte_event_2, |_prj, cmd| { +casttest!(flaky_fourbyte_event_2, |_prj, cmd| { cmd.args(["4byte-event", "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd"]) .assert_success() .stdout_eq(str![[r#" @@ -70,7 +70,7 @@ canCall(address,address,bytes4) "#]]); }); -casttest!(upload_signatures, |_prj, cmd| { +casttest!(flaky_upload_signatures, |_prj, cmd| { // test no prefix is accepted as function let output = cmd .args(["upload-signature", "transfer(address,uint256)"]) @@ -140,7 +140,7 @@ casttest!(event_decode_with_sig, |_prj, cmd| { cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" [ - "78", + 78, "0x0000000000000000000000000000000000D0004F" ] @@ -148,7 +148,7 @@ casttest!(event_decode_with_sig, |_prj, cmd| { }); // tests cast can decode event with Openchain API -casttest!(event_decode_with_openchain, |prj, cmd| { +casttest!(flaky_event_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-event", "0xe27c4c1372396a3d15a9922f74f9dfc7c72b1ad6d63868470787249c356454c1000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]).assert_success().stdout_eq(str![[r#" BaseCurrencySet(address,uint256) @@ -168,7 +168,7 @@ casttest!(error_decode_with_sig, |_prj, cmd| { cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" [ - "101", + 101, "0x0000000000000000000000000000000000D0004F" ] @@ -176,7 +176,7 @@ casttest!(error_decode_with_sig, |_prj, cmd| { }); // tests cast can decode error and event when using local sig identifiers cache -forgetest_init!(error_event_decode_with_cache, |prj, cmd| { +forgetest_init!(flaky_error_event_decode_with_cache, |prj, cmd| { prj.add_source( "LocalProjectContract", r#" @@ -185,9 +185,9 @@ contract ContractWithCustomError { event MyUniqueEventWithinLocalProject(uint256 a, address b); } "#, - ) - .unwrap(); - // Store selectors in local cache. + ); + // Build and cache project selectors. + cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); // Assert cast can decode custom error with local cache. @@ -211,3 +211,38 @@ MyUniqueEventWithinLocalProject(uint256,address) "#]]); }); + +forgetest!(flaky_cache_selectors_from_extra_abis, |prj, cmd| { + // Create folder with ABI JSON files containing a unique error + let abis_dir = prj.root().join("external_abis"); + std::fs::create_dir(&abis_dir).unwrap(); + std::fs::write( + abis_dir.join("test.json"), + r#"[{ + "type": "error", + "name": "MyUniqueExtraAbiError", + "inputs": [ + {"name": "value", "type": "uint256"}, + {"name": "flag", "type": "bool"} + ] + }]"#, + ) + .unwrap(); + + cmd.forge_fuse() + .args(["selectors", "cache", "--extra-abis-path", abis_dir.to_str().unwrap()]) + .assert_success(); + + // Verify with cast decode-error (uses local cache via SignaturesIdentifier) + // Selector for MyUniqueExtraAbiError(uint256,bool) is 0x7819b107 + // Encoded: selector + uint256(42) + bool(true) + cmd.cast_fuse() + .args(["decode-error", "0x7819b107000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000001"]) + .assert_success() + .stdout_eq(str![[r#" +MyUniqueExtraAbiError(uint256,bool) +42 +true + +"#]]); +}); diff --git a/crates/cast/tests/fixtures/TestToken.sol b/crates/cast/tests/fixtures/TestToken.sol new file mode 100644 index 0000000000000..53f0d62187e38 --- /dev/null +++ b/crates/cast/tests/fixtures/TestToken.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestToken { + string public name = "Test Token"; + string public symbol = "TEST"; + uint8 public decimals = 18; + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + constructor() { + totalSupply = 1000 * 10**decimals; + balanceOf[msg.sender] = totalSupply; + emit Transfer(address(0), msg.sender, totalSupply); + } + + function transfer(address to, uint256 amount) external returns (bool) { + require(balanceOf[msg.sender] >= amount, "Insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + return true; + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + require(balanceOf[from] >= amount, "Insufficient balance"); + require(allowance[from][msg.sender] >= amount, "Insufficient allowance"); + + balanceOf[from] -= amount; + balanceOf[to] += amount; + allowance[from][msg.sender] -= amount; + + emit Transfer(from, to, amount); + return true; + } + + function mint(address to, uint256 amount) external { + totalSupply += amount; + balanceOf[to] += amount; + emit Transfer(address(0), to, amount); + } + + function burn(uint256 amount) external { + uint256 balance = balanceOf[msg.sender]; + amount = (amount > balance) ? balance : amount; + + totalSupply -= amount; + balanceOf[msg.sender] -= amount; + emit Transfer(msg.sender, address(0), amount); + } +} diff --git a/crates/cast/tests/fixtures/interface_inherited_struct.json b/crates/cast/tests/fixtures/interface_inherited_struct.json new file mode 100644 index 0000000000000..b82c0829b5ac7 --- /dev/null +++ b/crates/cast/tests/fixtures/interface_inherited_struct.json @@ -0,0 +1 @@ +[{"type":"function","name":"test","inputs":[{"name":"param","type":"tuple","internalType":"struct IBase.TestStruct","components":[{"name":"asset","type":"address","internalType":"address"}]}],"outputs":[],"stateMutability":"nonpayable"}] diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 8269364d231fb..0eab12331be04 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -20,10 +20,13 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true +foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true foundry-wallets.workspace = true forge-script-sequence.workspace = true +solar.workspace = true + alloy-dyn-abi.workspace = true alloy-evm.workspace = true alloy-json-abi.workspace = true @@ -45,16 +48,16 @@ alloy-chains.workspace = true alloy-ens.workspace = true base64.workspace = true -dialoguer = "0.11" +dialoguer.workspace = true eyre.workspace = true itertools.workspace = true jsonpath_lib.workspace = true k256.workspace = true -memchr = "2.7" +memchr.workspace = true p256 = "0.13" +ed25519-consensus = "2.1" ecdsa = "0.16" rand.workspace = true -rand_chacha.workspace = true revm.workspace = true revm-inspectors.workspace = true semver.workspace = true @@ -65,3 +68,13 @@ tracing.workspace = true walkdir.workspace = true proptest.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", + "forge-script-sequence/optimism", +] diff --git a/crates/cheatcodes/README.md b/crates/cheatcodes/README.md index 7f776392eb744..5be3bc9614d98 100644 --- a/crates/cheatcodes/README.md +++ b/crates/cheatcodes/README.md @@ -26,6 +26,7 @@ The JSON interface is guaranteed to be stable, and can be used by third-party to the Foundry cheatcodes externally. For example, here are some tools that make use of the JSON interface: + - Internally, this is used to generate [a simple Solidity interface](../../testdata/cheats/Vm.sol) for testing - Used by [`forge-std`](https://github.com/foundry-rs/forge-std) to generate [user-friendly Solidity interfaces](https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol) - (WIP) Used by [the Foundry book](https://github.com/foundry-rs/book) to generate [the cheatcodes reference](https://book.getfoundry.sh/cheatcodes) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index eb541e269a888..01de77b9c95fd 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -463,6 +463,16 @@ "name": "depth", "ty": "uint64", "description": "Call depth traversed during the recording of state differences" + }, + { + "name": "oldNonce", + "ty": "uint64", + "description": "The previous nonce of the accessed account." + }, + { + "name": "newNonce", + "ty": "uint64", + "description": "The new nonce of the accessed account." } ] }, @@ -754,9 +764,9 @@ "func": { "id": "accesses", "description": "Gets all accessed reads and write slot from a `vm.record` session, for a given address.", - "declaration": "function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);", + "declaration": "function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "accesses(address)", "selector": "0x65bc9481", "selectorBytes": [ @@ -854,7 +864,7 @@ "func": { "id": "assertApproxEqAbsDecimal_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256,string)", @@ -894,7 +904,7 @@ "func": { "id": "assertApproxEqAbsDecimal_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256,string)", @@ -934,7 +944,7 @@ "func": { "id": "assertApproxEqAbs_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(uint256,uint256,uint256,string)", @@ -974,7 +984,7 @@ "func": { "id": "assertApproxEqAbs_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(int256,int256,uint256,string)", @@ -1014,7 +1024,7 @@ "func": { "id": "assertApproxEqRelDecimal_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256,string)", @@ -1054,7 +1064,7 @@ "func": { "id": "assertApproxEqRelDecimal_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256,string)", @@ -1094,7 +1104,7 @@ "func": { "id": "assertApproxEqRel_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(uint256,uint256,uint256,string)", @@ -1134,7 +1144,7 @@ "func": { "id": "assertApproxEqRel_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", - "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(int256,int256,uint256,string)", @@ -1174,7 +1184,7 @@ "func": { "id": "assertEqDecimal_1", "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(uint256,uint256,uint256,string)", @@ -1214,7 +1224,7 @@ "func": { "id": "assertEqDecimal_3", "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(int256,int256,uint256,string)", @@ -1254,7 +1264,7 @@ "func": { "id": "assertEq_1", "description": "Asserts that two `bool` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bool left, bool right, string calldata error) external pure;", + "declaration": "function assertEq(bool left, bool right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool,bool,string)", @@ -1294,7 +1304,7 @@ "func": { "id": "assertEq_11", "description": "Asserts that two `string` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(string calldata left, string calldata right, string calldata error) external pure;", + "declaration": "function assertEq(string calldata left, string calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string,string,string)", @@ -1334,7 +1344,7 @@ "func": { "id": "assertEq_13", "description": "Asserts that two `bytes` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes,bytes,string)", @@ -1374,7 +1384,7 @@ "func": { "id": "assertEq_15", "description": "Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool[],bool[],string)", @@ -1414,7 +1424,7 @@ "func": { "id": "assertEq_17", "description": "Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256[],uint256[],string)", @@ -1454,7 +1464,7 @@ "func": { "id": "assertEq_19", "description": "Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256[],int256[],string)", @@ -1514,7 +1524,7 @@ "func": { "id": "assertEq_21", "description": "Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address[],address[],string)", @@ -1554,7 +1564,7 @@ "func": { "id": "assertEq_23", "description": "Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32[],bytes32[],string)", @@ -1594,7 +1604,7 @@ "func": { "id": "assertEq_25", "description": "Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string[],string[],string)", @@ -1634,7 +1644,7 @@ "func": { "id": "assertEq_27", "description": "Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes[],bytes[],string)", @@ -1654,7 +1664,7 @@ "func": { "id": "assertEq_3", "description": "Asserts that two `uint256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertEq(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256,uint256,string)", @@ -1694,7 +1704,7 @@ "func": { "id": "assertEq_5", "description": "Asserts that two `int256` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertEq(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256,int256,string)", @@ -1734,7 +1744,7 @@ "func": { "id": "assertEq_7", "description": "Asserts that two `address` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(address left, address right, string calldata error) external pure;", + "declaration": "function assertEq(address left, address right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address,address,string)", @@ -1774,7 +1784,7 @@ "func": { "id": "assertEq_9", "description": "Asserts that two `bytes32` values are equal and includes error message into revert string on failure.", - "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32,bytes32,string)", @@ -1814,7 +1824,7 @@ "func": { "id": "assertFalse_1", "description": "Asserts that the given condition is false and includes error message into revert string on failure.", - "declaration": "function assertFalse(bool condition, string calldata error) external pure;", + "declaration": "function assertFalse(bool condition, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertFalse(bool,string)", @@ -1854,7 +1864,7 @@ "func": { "id": "assertGeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(uint256,uint256,uint256,string)", @@ -1894,7 +1904,7 @@ "func": { "id": "assertGeDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(int256,int256,uint256,string)", @@ -1934,7 +1944,7 @@ "func": { "id": "assertGe_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGe(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertGe(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(uint256,uint256,string)", @@ -1974,7 +1984,7 @@ "func": { "id": "assertGe_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGe(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertGe(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(int256,int256,string)", @@ -2014,7 +2024,7 @@ "func": { "id": "assertGtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(uint256,uint256,uint256,string)", @@ -2054,7 +2064,7 @@ "func": { "id": "assertGtDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(int256,int256,uint256,string)", @@ -2094,7 +2104,7 @@ "func": { "id": "assertGt_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGt(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertGt(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(uint256,uint256,string)", @@ -2134,7 +2144,7 @@ "func": { "id": "assertGt_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertGt(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertGt(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(int256,int256,string)", @@ -2174,7 +2184,7 @@ "func": { "id": "assertLeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(uint256,uint256,uint256,string)", @@ -2214,7 +2224,7 @@ "func": { "id": "assertLeDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(int256,int256,uint256,string)", @@ -2254,7 +2264,7 @@ "func": { "id": "assertLe_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLe(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertLe(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(uint256,uint256,string)", @@ -2294,7 +2304,7 @@ "func": { "id": "assertLe_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLe(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertLe(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(int256,int256,string)", @@ -2334,7 +2344,7 @@ "func": { "id": "assertLtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(uint256,uint256,uint256,string)", @@ -2374,7 +2384,7 @@ "func": { "id": "assertLtDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", - "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(int256,int256,uint256,string)", @@ -2414,7 +2424,7 @@ "func": { "id": "assertLt_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLt(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertLt(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(uint256,uint256,string)", @@ -2454,7 +2464,7 @@ "func": { "id": "assertLt_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", - "declaration": "function assertLt(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertLt(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(int256,int256,string)", @@ -2494,7 +2504,7 @@ "func": { "id": "assertNotEqDecimal_1", "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(uint256,uint256,uint256,string)", @@ -2534,7 +2544,7 @@ "func": { "id": "assertNotEqDecimal_3", "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", - "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(int256,int256,uint256,string)", @@ -2574,7 +2584,7 @@ "func": { "id": "assertNotEq_1", "description": "Asserts that two `bool` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bool left, bool right, string calldata error) external pure;", + "declaration": "function assertNotEq(bool left, bool right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool,bool,string)", @@ -2614,7 +2624,7 @@ "func": { "id": "assertNotEq_11", "description": "Asserts that two `string` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string,string,string)", @@ -2654,7 +2664,7 @@ "func": { "id": "assertNotEq_13", "description": "Asserts that two `bytes` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes,bytes,string)", @@ -2694,7 +2704,7 @@ "func": { "id": "assertNotEq_15", "description": "Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool[],bool[],string)", @@ -2734,7 +2744,7 @@ "func": { "id": "assertNotEq_17", "description": "Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256[],uint256[],string)", @@ -2774,7 +2784,7 @@ "func": { "id": "assertNotEq_19", "description": "Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256[],int256[],string)", @@ -2834,7 +2844,7 @@ "func": { "id": "assertNotEq_21", "description": "Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address[],address[],string)", @@ -2874,7 +2884,7 @@ "func": { "id": "assertNotEq_23", "description": "Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32[],bytes32[],string)", @@ -2914,7 +2924,7 @@ "func": { "id": "assertNotEq_25", "description": "Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string[],string[],string)", @@ -2954,7 +2964,7 @@ "func": { "id": "assertNotEq_27", "description": "Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes[],bytes[],string)", @@ -2974,7 +2984,7 @@ "func": { "id": "assertNotEq_3", "description": "Asserts that two `uint256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;", + "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256,uint256,string)", @@ -3014,7 +3024,7 @@ "func": { "id": "assertNotEq_5", "description": "Asserts that two `int256` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(int256 left, int256 right, string calldata error) external pure;", + "declaration": "function assertNotEq(int256 left, int256 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256,int256,string)", @@ -3054,7 +3064,7 @@ "func": { "id": "assertNotEq_7", "description": "Asserts that two `address` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(address left, address right, string calldata error) external pure;", + "declaration": "function assertNotEq(address left, address right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address,address,string)", @@ -3094,7 +3104,7 @@ "func": { "id": "assertNotEq_9", "description": "Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.", - "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32,bytes32,string)", @@ -3134,7 +3144,7 @@ "func": { "id": "assertTrue_1", "description": "Asserts that the given condition is true and includes error message into revert string on failure.", - "declaration": "function assertTrue(bool condition, string calldata error) external pure;", + "declaration": "function assertTrue(bool condition, string calldata err) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertTrue(bool,string)", @@ -3330,6 +3340,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "bound_0", + "description": "Returns an uint256 value bounded in given range and different from the current one.", + "declaration": "function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256);", + "visibility": "external", + "mutability": "view", + "signature": "bound(uint256,uint256,uint256)", + "selector": "0x5a6c1eed", + "selectorBytes": [ + 90, + 108, + 30, + 237 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "bound_1", + "description": "Returns an int256 value bounded in given range and different from the current one.", + "declaration": "function bound(int256 current, int256 min, int256 max) external view returns (int256);", + "visibility": "external", + "mutability": "view", + "signature": "bound(int256,int256,int256)", + "selector": "0x8f48fc07", + "selectorBytes": [ + 143, + 72, + 252, + 7 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "breakpoint_0", @@ -3614,9 +3664,9 @@ "func": { "id": "contains", "description": "Returns true if `search` is found in `subject`, false otherwise.", - "declaration": "function contains(string calldata subject, string calldata search) external returns (bool result);", + "declaration": "function contains(string calldata subject, string calldata search) external pure returns (bool result);", "visibility": "external", - "mutability": "", + "mutability": "pure", "signature": "contains(string,string)", "selector": "0x3fb18aec", "selectorBytes": [ @@ -3708,7 +3758,7 @@ }, "group": "utilities", "status": "stable", - "safety": "safe" + "safety": "unsafe" }, { "func": { @@ -3730,6 +3780,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "createEd25519Key", + "description": "Generates an Ed25519 key pair from a deterministic salt.\nReturns (publicKey, privateKey) as 32-byte values.", + "declaration": "function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "createEd25519Key(bytes32)", + "selector": "0x1ef3f27a", + "selectorBytes": [ + 30, + 243, + 242, + 122 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "createFork_0", @@ -3910,6 +3980,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "currentFilePath", + "description": "Get the source file path of the currently running test or script contract,\nrelative to the project root.", + "declaration": "function currentFilePath() external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "currentFilePath()", + "selector": "0x9b45555c", + "selectorBytes": [ + 155, + 69, + 85, + 92 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "deal", @@ -4017,7 +4107,7 @@ { "func": { "id": "deployCode_0", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4037,7 +4127,7 @@ { "func": { "id": "deployCode_1", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4057,7 +4147,7 @@ { "func": { "id": "deployCode_2", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts `msg.value`.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4077,7 +4167,7 @@ { "func": { "id": "deployCode_3", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4097,7 +4187,7 @@ { "func": { "id": "deployCode_4", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4117,7 +4207,7 @@ { "func": { "id": "deployCode_5", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4137,7 +4227,7 @@ { "func": { "id": "deployCode_6", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts `msg.value`.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4157,7 +4247,7 @@ { "func": { "id": "deployCode_7", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4177,7 +4267,7 @@ { "func": { "id": "deriveKey_0", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "description": "Derive a private key from a provided mnemonic string (or mnemonic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", "declaration": "function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", @@ -4197,7 +4287,7 @@ { "func": { "id": "deriveKey_1", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat `{derivationPath}{index}`.", + "description": "Derive a private key from a provided mnemonic string (or mnemonic file path)\nat `{derivationPath}{index}`.", "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", @@ -4217,7 +4307,7 @@ { "func": { "id": "deriveKey_2", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "description": "Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", "declaration": "function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", @@ -4237,7 +4327,7 @@ { "func": { "id": "deriveKey_3", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat `{derivationPath}{index}`.", + "description": "Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language\nat `{derivationPath}{index}`.", "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", @@ -4294,6 +4384,106 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "eip712HashStruct_0", + "description": "Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data.\nSupports 2 different inputs:\n 1. Name of the type (i.e. \"PermitSingle\"):\n * requires previous binding generation with `forge bind-json`.\n * bindings will be retrieved from the path configured in `foundry.toml`.\n 2. String representation of the type (i.e. \"Foo(Bar bar) Bar(uint256 baz)\").\n * Note: the cheatcode will use the canonical type even if the input is malformated\n with the wrong order of elements or with extra whitespaces.", + "declaration": "function eip712HashStruct(string calldata typeNameOrDefinition, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashStruct(string,bytes)", + "selector": "0xaedeaebc", + "selectorBytes": [ + 174, + 222, + 174, + 188 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashStruct_1", + "description": "Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data.\nRequires previous binding generation with `forge bind-json`.\nParams:\n * `bindingsPath`: path where the output of `forge bind-json` is stored.\n * `typeName`: Name of the type (i.e. \"PermitSingle\").\n * `abiEncodedData`: ABI-encoded data for the struct that is being hashed.", + "declaration": "function eip712HashStruct(string calldata bindingsPath, string calldata typeName, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashStruct(string,string,bytes)", + "selector": "0x6d06c57c", + "selectorBytes": [ + 109, + 6, + 197, + 124 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashType_0", + "description": "Generates the hash of the canonical EIP-712 type representation.\nSupports 2 different inputs:\n 1. Name of the type (i.e. \"Transaction\"):\n * requires previous binding generation with `forge bind-json`.\n * bindings will be retrieved from the path configured in `foundry.toml`.\n 2. String representation of the type (i.e. \"Foo(Bar bar) Bar(uint256 baz)\").\n * Note: the cheatcode will output the canonical type even if the input is malformated\n with the wrong order of elements or with extra whitespaces.", + "declaration": "function eip712HashType(string calldata typeNameOrDefinition) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashType(string)", + "selector": "0x6792e9e2", + "selectorBytes": [ + 103, + 146, + 233, + 226 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashType_1", + "description": "Generates the hash of the canonical EIP-712 type representation.\nRequires previous binding generation with `forge bind-json`.\nParams:\n * `bindingsPath`: path where the output of `forge bind-json` is stored.\n * `typeName`: Name of the type (i.e. \"Transaction\").", + "declaration": "function eip712HashType(string calldata bindingsPath, string calldata typeName) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashType(string,string)", + "selector": "0x18fb6406", + "selectorBytes": [ + 24, + 251, + 100, + 6 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashTypedData", + "description": "Generates a ready-to-sign digest of human-readable typed data following the EIP-712 standard.", + "declaration": "function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashTypedData(string)", + "selector": "0xea25e615", + "selectorBytes": [ + 234, + 37, + 230, + 21 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "ensNamehash", @@ -4918,9 +5108,9 @@ "func": { "id": "eth_getLogs", "description": "Gets all the logs according to specified filter.", - "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs);", + "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external view returns (EthGetLogs[] memory logs);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", "selector": "0x35e1349b", "selectorBytes": [ @@ -4934,6 +5124,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "executeTransaction", + "description": "Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode).\nThe transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP.\nReturns the execution output bytes.\nThis cheatcode is not allowed in `forge script` contexts.", + "declaration": "function executeTransaction(bytes calldata rawTx) external returns (bytes memory);", + "visibility": "external", + "mutability": "", + "signature": "executeTransaction(bytes)", + "selector": "0x943d7209", + "selectorBytes": [ + 148, + 61, + 114, + 9 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "exists", @@ -5237,7 +5447,7 @@ { "func": { "id": "expectEmit_0", - "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", @@ -5277,7 +5487,7 @@ { "func": { "id": "expectEmit_2", - "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.\nMust be placed immediately before the call you want to assert on. If the next call reverts and the\nrevert is caught by the caller (low-level call or try/catch), the expectation remains active and may\nbe satisfied by a log emitted from a later call.", "declaration": "function expectEmit() external;", "visibility": "external", "mutability": "", @@ -5794,6 +6004,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "fromRlp", + "description": "RLP decodes an RLP payload into a list of bytes.", + "declaration": "function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data);", + "visibility": "external", + "mutability": "pure", + "signature": "fromRlp(bytes)", + "selector": "0x1e1d8b63", + "selectorBytes": [ + 30, + 29, + 139, + 99 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "fsMetadata", @@ -5994,6 +6224,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getChainId", + "description": "Gets the current `block.chainid` of the currently selected environment.\nYou should use this instead of `block.chainid` if you use `vm.selectFork` or `vm.createSelectFork`, as `block.chainid` could be assumed\nto be constant across a transaction, and as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getChainId() external view returns (uint256 blockChainId);", + "visibility": "external", + "mutability": "view", + "signature": "getChainId()", + "selector": "0x3408e470", + "selectorBytes": [ + 52, + 8, + 228, + 112 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getChain_0", @@ -6134,6 +6384,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getEvmVersion", + "description": "Returns the test or script execution evm version.\n**Note:** The execution evm version is not the same as the compilation one.", + "declaration": "function getEvmVersion() external pure returns (string memory evm);", + "visibility": "external", + "mutability": "pure", + "signature": "getEvmVersion()", + "selector": "0xaa2bb222", + "selectorBytes": [ + 170, + 43, + 178, + 34 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getFoundryVersion", @@ -6178,9 +6448,9 @@ "func": { "id": "getMappingKeyAndParentOf", "description": "Gets the map key and parent of a mapping at a given slot, for a given address.", - "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent);", + "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external view returns (bool found, bytes32 key, bytes32 parent);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getMappingKeyAndParentOf(address,bytes32)", "selector": "0x876e24e6", "selectorBytes": [ @@ -6198,9 +6468,9 @@ "func": { "id": "getMappingLength", "description": "Gets the number of elements in the mapping at the given slot, for a given address.", - "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length);", + "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getMappingLength(address,bytes32)", "selector": "0x2f2fd63f", "selectorBytes": [ @@ -6218,9 +6488,9 @@ "func": { "id": "getMappingSlotAt", "description": "Gets the elements at index idx of the mapping at the given slot, for a given address. The\nindex must be less than the length of the mapping (i.e. the number of keys in the mapping).", - "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value);", + "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getMappingSlotAt(address,bytes32,uint256)", "selector": "0xebc73ab4", "selectorBytes": [ @@ -6258,9 +6528,9 @@ "func": { "id": "getNonce_1", "description": "Get the nonce of a `Wallet`.", - "declaration": "function getNonce(Wallet calldata wallet) external returns (uint64 nonce);", + "declaration": "function getNonce(Wallet calldata wallet) external view returns (uint64 nonce);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getNonce((address,uint256,uint256,uint256))", "selector": "0xa5748aad", "selectorBytes": [ @@ -6274,13 +6544,33 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getRawBlockHeader", + "description": "Gets the RLP encoded block header for a given block number.\nReturns the block header in the same format as `cast block --raw`.", + "declaration": "function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader);", + "visibility": "external", + "mutability": "view", + "signature": "getRawBlockHeader(uint256)", + "selector": "0x2c667606", + "selectorBytes": [ + 44, + 102, + 118, + 6 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getRecordedLogs", "description": "Gets all the recorded logs.", - "declaration": "function getRecordedLogs() external returns (Log[] memory logs);", + "declaration": "function getRecordedLogs() external view returns (Log[] memory logs);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getRecordedLogs()", "selector": "0x191553a4", "selectorBytes": [ @@ -6294,6 +6584,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getRecordedLogsJson", + "description": "Gets all the recorded logs, in JSON format.", + "declaration": "function getRecordedLogsJson() external view returns (string memory logsJson);", + "visibility": "external", + "mutability": "view", + "signature": "getRecordedLogsJson()", + "selector": "0x3b171111", + "selectorBytes": [ + 59, + 23, + 17, + 17 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getStateDiff", @@ -6334,13 +6644,53 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getStorageAccesses", + "description": "Returns an array of `StorageAccess` from current `vm.stateStateDiffRecording` session", + "declaration": "function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses);", + "visibility": "external", + "mutability": "view", + "signature": "getStorageAccesses()", + "selector": "0x2899b1d0", + "selectorBytes": [ + 40, + 153, + 177, + 208 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getStorageSlots", + "description": "Returns an array of storage slots occupied by the specified variable.", + "declaration": "function getStorageSlots(address target, string calldata variableName) external view returns (uint256[] memory slots);", + "visibility": "external", + "mutability": "view", + "signature": "getStorageSlots(address,string)", + "selector": "0xefa136d9", + "selectorBytes": [ + 239, + 161, + 54, + 217 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getWallets", "description": "Returns addresses of available unlocked wallets in the script environment.", - "declaration": "function getWallets() external returns (address[] memory wallets);", + "declaration": "function getWallets() external view returns (address[] memory wallets);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "getWallets()", "selector": "0xdb7a4605", "selectorBytes": [ @@ -8136,6 +8486,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "publicKeyEd25519", + "description": "Derives the Ed25519 public key from a private key.", + "declaration": "function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey);", + "visibility": "external", + "mutability": "pure", + "signature": "publicKeyEd25519(bytes32)", + "selector": "0x27f44236", + "selectorBytes": [ + 39, + 244, + 66, + 54 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "publicKeyP256", @@ -8160,9 +8530,9 @@ "func": { "id": "randomAddress", "description": "Returns a random `address`.", - "declaration": "function randomAddress() external returns (address);", + "declaration": "function randomAddress() external view returns (address);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "randomAddress()", "selector": "0xd5bee9f5", "selectorBytes": [ @@ -8300,9 +8670,9 @@ "func": { "id": "randomUint_0", "description": "Returns a random uint256 value.", - "declaration": "function randomUint() external returns (uint256);", + "declaration": "function randomUint() external view returns (uint256);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "randomUint()", "selector": "0x25124730", "selectorBytes": [ @@ -8320,9 +8690,9 @@ "func": { "id": "randomUint_1", "description": "Returns random uint256 value between the provided range (=min..=max).", - "declaration": "function randomUint(uint256 min, uint256 max) external returns (uint256);", + "declaration": "function randomUint(uint256 min, uint256 max) external view returns (uint256);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "randomUint(uint256,uint256)", "selector": "0xd61b051b", "selectorBytes": [ @@ -8360,9 +8730,9 @@ "func": { "id": "readCallers", "description": "Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification.", - "declaration": "function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin);", + "declaration": "function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin);", "visibility": "external", - "mutability": "", + "mutability": "view", "signature": "readCallers()", "selector": "0x4ad0bac9", "selectorBytes": [ @@ -8716,6 +9086,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "resolveEnv", + "description": "Resolves the env variable placeholders of a given input string.", + "declaration": "function resolveEnv(string calldata input) external returns (string memory);", + "visibility": "external", + "mutability": "", + "signature": "resolveEnv(string)", + "selector": "0xddd2128d", + "selectorBytes": [ + 221, + 210, + 18, + 141 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "resumeGasMetering", @@ -9478,7 +9868,7 @@ }, "group": "utilities", "status": "stable", - "safety": "safe" + "safety": "unsafe" }, { "func": { @@ -9498,7 +9888,7 @@ }, "group": "utilities", "status": "stable", - "safety": "safe" + "safety": "unsafe" }, { "func": { @@ -9540,6 +9930,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "setEvmVersion", + "description": "Set the exact test or script execution evm version, e.g. `berlin`, `cancun`.\n**Note:** The execution evm version is not the same as the compilation one.", + "declaration": "function setEvmVersion(string calldata evm) external;", + "visibility": "external", + "mutability": "", + "signature": "setEvmVersion(string)", + "selector": "0x43179f5a", + "selectorBytes": [ + 67, + 23, + 159, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "setNonce", @@ -9580,6 +9990,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "setSeed", + "description": "Set RNG seed.", + "declaration": "function setSeed(uint256 seed) external;", + "visibility": "external", + "mutability": "", + "signature": "setSeed(uint256)", + "selector": "0xc32a50f9", + "selectorBytes": [ + 195, + 42, + 80, + 249 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "shuffle", @@ -9664,9 +10094,9 @@ "func": { "id": "signCompact_0", "description": "Signs data with a `Wallet`.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", - "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs);", + "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", - "mutability": "", + "mutability": "pure", "signature": "signCompact((address,uint256,uint256,uint256),bytes32)", "selector": "0x3d0e292f", "selectorBytes": [ @@ -9800,6 +10230,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signEd25519", + "description": "Signs a message with namespace using Ed25519.\nThe signature covers namespace || message for domain separation.\nReturns a 64-byte Ed25519 signature.", + "declaration": "function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature);", + "visibility": "external", + "mutability": "pure", + "signature": "signEd25519(bytes,bytes,bytes32)", + "selector": "0xef609c65", + "selectorBytes": [ + 239, + 96, + 156, + 101 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "signP256", @@ -9820,13 +10270,33 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signWithNonceUnsafe", + "description": "Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce`\nas the raw ephemeral k value in ECDSA (instead of deriving it deterministically).", + "declaration": "function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "signWithNonceUnsafe(uint256,bytes32,uint256)", + "selector": "0x2012783a", + "selectorBytes": [ + 32, + 18, + 120, + 58 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "sign_0", "description": "Signs data with a `Wallet`.", - "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);", + "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", - "mutability": "", + "mutability": "pure", "signature": "sign((address,uint256,uint256,uint256),bytes32)", "selector": "0xb25c5a25", "selectorBytes": [ @@ -10682,6 +11152,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "toRlp", + "description": "RLP encodes a list of bytes into an RLP payload.", + "declaration": "function toRlp(bytes[] calldata data) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toRlp(bytes[])", + "selector": "0xa7ed3885", + "selectorBytes": [ + 167, + 237, + 56, + 133 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "toString_0", @@ -10942,6 +11432,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "verifyEd25519", + "description": "Verifies an Ed25519 signature over namespace || message.\nReturns true if signature is valid, false otherwise.", + "declaration": "function verifyEd25519(bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey) external pure returns (bool valid);", + "visibility": "external", + "mutability": "pure", + "signature": "verifyEd25519(bytes,bytes,bytes,bytes32)", + "selector": "0xd08c2888", + "selectorBytes": [ + 208, + 140, + 40, + 136 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "warmSlot", @@ -11045,7 +11555,7 @@ { "func": { "id": "writeJson_1", - "description": "Write a serialized JSON object to an **existing** JSON file, replacing a value with key = \nThis is useful to replace a specific value of a JSON file, without having to parse the entire thing.", + "description": "Write a serialized JSON object to an **existing** JSON file, replacing a value with key = \nThis is useful to replace a specific value of a JSON file, without having to parse the entire thing.\nThis cheatcode will create new keys if they didn't previously exist.", "declaration": "function writeJson(string calldata json, string calldata path, string calldata valueKey) external;", "visibility": "external", "mutability": "", @@ -11105,7 +11615,7 @@ { "func": { "id": "writeToml_1", - "description": "Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = \nThis is useful to replace a specific value of a TOML file, without having to parse the entire thing.", + "description": "Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = \nThis is useful to replace a specific value of a TOML file, without having to parse the entire thing.\nThis cheatcode will create new keys if they didn't previously exist.", "declaration": "function writeToml(string calldata json, string calldata path, string calldata valueKey) external;", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index 9ffc13d6121d8..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,109 +1,88 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", - "required": [ - "cheatcodes", - "enums", - "errors", - "events", - "structs" - ], "properties": { "cheatcodes": { "description": "All the cheatcodes.", "type": "array", "items": { - "$ref": "#/definitions/Cheatcode" + "$ref": "#/$defs/Cheatcode" } }, "enums": { "description": "Cheatcode enums.", "type": "array", "items": { - "$ref": "#/definitions/Enum" + "$ref": "#/$defs/Enum" } }, "errors": { "description": "Cheatcode errors.", "type": "array", "items": { - "$ref": "#/definitions/Error" + "$ref": "#/$defs/Error" } }, "events": { "description": "Cheatcode events.", "type": "array", "items": { - "$ref": "#/definitions/Event" + "$ref": "#/$defs/Event" } }, "structs": { "description": "Cheatcode structs.", "type": "array", "items": { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" } } }, - "definitions": { + "required": [ + "errors", + "events", + "enums", + "structs", + "cheatcodes" + ], + "$defs": { "Cheatcode": { "description": "Specification of a single cheatcode. Extends [`Function`] with additional metadata.", "type": "object", - "required": [ - "func", - "group", - "safety", - "status" - ], "properties": { "func": { "description": "The Solidity function declaration.", - "allOf": [ - { - "$ref": "#/definitions/Function" - } - ] + "$ref": "#/$defs/Function" }, "group": { "description": "The group that the cheatcode belongs to.", - "allOf": [ - { - "$ref": "#/definitions/Group" - } - ] + "$ref": "#/$defs/Group" }, "safety": { - "description": "Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an unexpected way.", - "allOf": [ - { - "$ref": "#/definitions/Safety" - } - ] + "description": "Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an\nunexpected way.", + "$ref": "#/$defs/Safety" }, "status": { "description": "The current status of the cheatcode. E.g. whether it is stable or experimental, etc.", - "allOf": [ - { - "$ref": "#/definitions/Status" - } - ] + "$ref": "#/$defs/Status" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "func", + "group", + "status", + "safety" + ] }, "Enum": { "description": "A Solidity enumeration.", "type": "object", - "required": [ - "description", - "name", - "variants" - ], "properties": { "description": { - "description": "The description of the enum. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the enum.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { @@ -114,111 +93,102 @@ "description": "The variants of the enum.", "type": "array", "items": { - "$ref": "#/definitions/EnumVariant" + "$ref": "#/$defs/EnumVariant" } } - } + }, + "required": [ + "name", + "description", + "variants" + ] }, "EnumVariant": { "description": "A variant of an [`Enum`].", "type": "object", - "required": [ - "description", - "name" - ], "properties": { "description": { - "description": "The description of the variant. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the variant.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the variant.", "type": "string" } - } + }, + "required": [ + "name", + "description" + ] }, "Error": { "description": "A Solidity custom error.", "type": "object", - "required": [ - "declaration", - "description", - "name" - ], "properties": { "declaration": { "description": "The Solidity error declaration, including full type, parameter names, etc.", "type": "string" }, "description": { - "description": "The description of the error. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the error.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the error.", "type": "string" } - } + }, + "required": [ + "name", + "description", + "declaration" + ] }, "Event": { "description": "A Solidity event.", "type": "object", - "required": [ - "declaration", - "description", - "name" - ], "properties": { "declaration": { "description": "The Solidity event declaration, including full type, parameter names, etc.", "type": "string" }, "description": { - "description": "The description of the event. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the event.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the event.", "type": "string" } - } + }, + "required": [ + "name", + "description", + "declaration" + ] }, "Function": { "description": "Solidity function.", "type": "object", - "required": [ - "declaration", - "description", - "id", - "mutability", - "selector", - "selectorBytes", - "signature", - "visibility" - ], "properties": { "declaration": { - "description": "The Solidity function declaration, including full type and parameter names, visibility, etc.", + "description": "The Solidity function declaration, including full type and parameter names, visibility,\netc.", "type": "string" }, "description": { - "description": "The description of the function. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the function.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "id": { - "description": "The function's unique identifier. This is the function name, optionally appended with an index if it is overloaded.", + "description": "The function's unique identifier. This is the function name, optionally appended with an\nindex if it is overloaded.", "type": "string" }, "mutability": { "description": "The Solidity function state mutability attribute.", - "allOf": [ - { - "$ref": "#/definitions/Mutability" - } - ] + "$ref": "#/$defs/Mutability" }, "selector": { - "description": "The hex-encoded, \"0x\"-prefixed 4-byte function selector, which is the Keccak-256 hash of `signature`.", + "description": "The hex-encoded, \"0x\"-prefixed 4-byte function selector,\nwhich is the Keccak-256 hash of `signature`.", "type": "string" }, "selectorBytes": { @@ -227,97 +197,84 @@ "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "maximum": 255, + "minimum": 0 }, "maxItems": 4, "minItems": 4 }, "signature": { - "description": "The standard function signature used to calculate `selector`. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector", + "description": "The standard function signature used to calculate `selector`.\nSee the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector", "type": "string" }, "visibility": { - "description": "The Solidity function visibility attribute. This is currently always `external`, but this may change in the future.", - "allOf": [ - { - "$ref": "#/definitions/Visibility" - } - ] + "description": "The Solidity function visibility attribute. This is currently always `external`, but this\nmay change in the future.", + "$ref": "#/$defs/Visibility" } - } + }, + "required": [ + "id", + "description", + "declaration", + "visibility", + "mutability", + "signature", + "selector", + "selectorBytes" + ] }, "Group": { - "description": "Cheatcode groups. Initially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol].\n\n[vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol", + "description": "Cheatcode groups.\nInitially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol].\n\n[vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol", "oneOf": [ { "description": "Cheatcodes that read from, or write to the current EVM execution state.\n\nExamples: any of the `record` cheatcodes, `chainId`, `coinbase`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", "type": "string", - "enum": [ - "evm" - ] + "const": "evm" }, { "description": "Cheatcodes that interact with how a test is run.\n\nExamples: `assume`, `skip`, `expectRevert`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", "type": "string", - "enum": [ - "testing" - ] + "const": "testing" }, { "description": "Cheatcodes that interact with how a script is run.\n\nExamples: `broadcast`, `startBroadcast`, `stopBroadcast`.\n\nSafety: safe.", "type": "string", - "enum": [ - "scripting" - ] + "const": "scripting" }, { "description": "Cheatcodes that interact with the OS or filesystem.\n\nExamples: `ffi`, `projectRoot`, `writeFile`.\n\nSafety: safe.", "type": "string", - "enum": [ - "filesystem" - ] + "const": "filesystem" }, { "description": "Cheatcodes that interact with the program's environment variables.\n\nExamples: `setEnv`, `envBool`, `envOr`.\n\nSafety: safe.", "type": "string", - "enum": [ - "environment" - ] + "const": "environment" }, { "description": "Utility cheatcodes that deal with string parsing and manipulation.\n\nExamples: `toString`. `parseBytes`.\n\nSafety: safe.", "type": "string", - "enum": [ - "string" - ] + "const": "string" }, { "description": "Utility cheatcodes that deal with parsing values from and converting values to JSON.\n\nExamples: `serializeJson`, `parseJsonUint`, `writeJson`.\n\nSafety: safe.", "type": "string", - "enum": [ - "json" - ] + "const": "json" }, { "description": "Utility cheatcodes that deal with parsing values from and converting values to TOML.\n\nExamples: `parseToml`, `writeToml`.\n\nSafety: safe.", "type": "string", - "enum": [ - "toml" - ] + "const": "toml" }, { "description": "Cryptography-related cheatcodes.\n\nExamples: `sign*`.\n\nSafety: safe.", "type": "string", - "enum": [ - "crypto" - ] + "const": "crypto" }, { "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", "type": "string", - "enum": [ - "utilities" - ] + "const": "utilities" } ] }, @@ -327,23 +284,17 @@ { "description": "Disallows modification or access of state.", "type": "string", - "enum": [ - "pure" - ] + "const": "pure" }, { "description": "Disallows modification of state.", "type": "string", - "enum": [ - "view" - ] + "const": "view" }, { "description": "Allows modification of state.", "type": "string", - "enum": [ - "" - ] + "const": "" } ] }, @@ -353,16 +304,12 @@ { "description": "The cheatcode is not safe to use in scripts.", "type": "string", - "enum": [ - "unsafe" - ] + "const": "unsafe" }, { "description": "The cheatcode is safe to use in scripts.", "type": "string", - "enum": [ - "safe" - ] + "const": "safe" } ] }, @@ -372,23 +319,16 @@ { "description": "The cheatcode and its API is currently stable.", "type": "string", - "enum": [ - "stable" - ] + "const": "stable" }, { - "description": "The cheatcode is unstable, meaning it may contain bugs and may break its API on any release.\n\nUse of experimental cheatcodes will result in a warning.", + "description": "The cheatcode is unstable, meaning it may contain bugs and may break its API on any\nrelease.\n\nUse of experimental cheatcodes will result in a warning.", "type": "string", - "enum": [ - "experimental" - ] + "const": "experimental" }, { "description": "The cheatcode has been deprecated, meaning it will be removed in a future release.\n\nContains the optional reason for deprecation.\n\nUse of deprecated cheatcodes is discouraged and will result in a warning.", "type": "object", - "required": [ - "deprecated" - ], "properties": { "deprecated": { "type": [ @@ -397,61 +337,55 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "deprecated" + ] }, { "description": "The cheatcode has been removed and is no longer available for use.\n\nUse of removed cheatcodes will result in a hard error.", "type": "string", - "enum": [ - "removed" - ] + "const": "removed" }, { - "description": "The cheatcode is only used internally for foundry testing and may be changed or removed at any time.\n\nUse of internal cheatcodes is discouraged and will result in a warning.", + "description": "The cheatcode is only used internally for foundry testing and may be changed or removed at\nany time.\n\nUse of internal cheatcodes is discouraged and will result in a warning.", "type": "string", - "enum": [ - "internal" - ] + "const": "internal" } ] }, "Struct": { "description": "A Solidity struct.", "type": "object", - "required": [ - "description", - "fields", - "name" - ], "properties": { "description": { - "description": "The description of the struct. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the struct.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "fields": { "description": "The fields of the struct.", "type": "array", "items": { - "$ref": "#/definitions/StructField" + "$ref": "#/$defs/StructField" } }, "name": { "description": "The name of the struct.", "type": "string" } - } + }, + "required": [ + "name", + "description", + "fields" + ] }, "StructField": { "description": "A [`Struct`] field.", "type": "object", - "required": [ - "description", - "name", - "ty" - ], "properties": { "description": { - "description": "The description of the field. This is a markdown string derived from the NatSpec documentation.", + "description": "The description of the field.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { @@ -462,7 +396,12 @@ "description": "The type of the field.", "type": "string" } - } + }, + "required": [ + "name", + "ty", + "description" + ] }, "Visibility": { "description": "Solidity function visibility attribute. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#function-visibility", @@ -470,30 +409,22 @@ { "description": "The function is only visible externally.", "type": "string", - "enum": [ - "external" - ] + "const": "external" }, { "description": "Visible externally and internally.", "type": "string", - "enum": [ - "public" - ] + "const": "public" }, { "description": "Only visible internally.", "type": "string", - "enum": [ - "internal" - ] + "const": "internal" }, { "description": "Only visible in the current contract", "type": "string", - "enum": [ - "private" - ] + "const": "private" } ] } diff --git a/crates/cheatcodes/spec/Cargo.toml b/crates/cheatcodes/spec/Cargo.toml index 458f6b73067ad..c8abf9e0c58c6 100644 --- a/crates/cheatcodes/spec/Cargo.toml +++ b/crates/cheatcodes/spec/Cargo.toml @@ -20,7 +20,7 @@ alloy-sol-types = { workspace = true, features = ["json"] } serde.workspace = true # schema -schemars = { version = "0.8", optional = true } +schemars = { version = "1.1", optional = true } [dev-dependencies] serde_json.workspace = true diff --git a/crates/cheatcodes/spec/src/cheatcode.rs b/crates/cheatcodes/spec/src/cheatcode.rs index bce501d45d7bd..89cfb31f1df32 100644 --- a/crates/cheatcodes/spec/src/cheatcode.rs +++ b/crates/cheatcodes/spec/src/cheatcode.rs @@ -135,23 +135,21 @@ impl Group { /// /// Some groups are inherently safe or unsafe, while others are ambiguous and will return /// `None`. - #[inline] pub const fn safety(self) -> Option { match self { Self::Evm | Self::Testing => None, - Self::Scripting | - Self::Filesystem | - Self::Environment | - Self::String | - Self::Json | - Self::Toml | - Self::Crypto | - Self::Utilities => Some(Safety::Safe), + Self::Scripting + | Self::Filesystem + | Self::Environment + | Self::String + | Self::Json + | Self::Toml + | Self::Crypto + | Self::Utilities => Some(Safety::Safe), } } /// Returns this value as a string. - #[inline] pub const fn as_str(self) -> &'static str { match self { Self::Evm => "evm", @@ -184,7 +182,6 @@ pub enum Safety { impl Safety { /// Returns this value as a string. - #[inline] pub const fn as_str(self) -> &'static str { match self { Self::Safe => "safe", @@ -193,7 +190,6 @@ impl Safety { } /// Returns whether this value is safe. - #[inline] pub const fn is_safe(self) -> bool { matches!(self, Self::Safe) } diff --git a/crates/cheatcodes/spec/src/function.rs b/crates/cheatcodes/spec/src/function.rs index 4c747fe152ee5..c67cc8ca80034 100644 --- a/crates/cheatcodes/spec/src/function.rs +++ b/crates/cheatcodes/spec/src/function.rs @@ -64,7 +64,6 @@ impl fmt::Display for Visibility { impl Visibility { /// Returns the string representation of the visibility. - #[inline] pub const fn as_str(self) -> &'static str { match self { Self::External => "external", @@ -99,7 +98,6 @@ impl fmt::Display for Mutability { impl Mutability { /// Returns the string representation of the mutability. - #[inline] pub const fn as_str(self) -> &'static str { match self { Self::Pure => "pure", diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 7d1dbe33ac9e6..202b590769857 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -1,7 +1,7 @@ //! Cheatcode specification for Foundry. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt}; @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -117,8 +117,7 @@ mod tests { #[cfg(feature = "schema")] const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json"); - const IFACE_PATH: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/cheats/Vm.sol"); + const IFACE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/utils/Vm.sol"); /// Generates the `cheatcodes.json` file contents. fn json_cheatcodes() -> String { @@ -170,16 +169,16 @@ interface Vm {{ /// Checks that the `file` has the specified `contents`. If that is not the /// case, updates the file and then fails the test. fn ensure_file_contents(file: &Path, contents: &str) { - if let Ok(old_contents) = fs::read_to_string(file) { - if normalize_newlines(&old_contents) == normalize_newlines(contents) { - // File is already up to date. - return - } + if let Ok(old_contents) = fs::read_to_string(file) + && normalize_newlines(&old_contents) == normalize_newlines(contents) + { + // File is already up to date. + return; } eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 1dac51fd0ca5a..12cfd19017770 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -278,6 +278,10 @@ interface Vm { StorageAccess[] storageAccesses; /// Call depth traversed during the recording of state differences uint64 depth; + /// The previous nonce of the accessed account. + uint64 oldNonce; + /// The new nonce of the accessed account. + uint64 newNonce; } /// The result of the `stopDebugTraceRecording` call @@ -371,7 +375,7 @@ interface Vm { /// Get the nonce of a `Wallet`. #[cheatcode(group = Evm, safety = Safe)] - function getNonce(Wallet calldata wallet) external returns (uint64 nonce); + function getNonce(Wallet calldata wallet) external view returns (uint64 nonce); /// Loads a storage slot from an address. #[cheatcode(group = Evm, safety = Safe)] @@ -409,7 +413,7 @@ interface Vm { /// Gets all accessed reads and write slot from a `vm.record` session, for a given address. #[cheatcode(group = Evm, safety = Safe)] - function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); + function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); /// Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order, /// along with the context of the calls @@ -428,6 +432,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getStateDiffJson() external view returns (string memory diff); + /// Returns an array of storage slots occupied by the specified variable. + #[cheatcode(group = Evm, safety = Safe)] + function getStorageSlots(address target, string calldata variableName) external view returns (uint256[] memory slots); + + /// Returns an array of `StorageAccess` from current `vm.stateStateDiffRecording` session + #[cheatcode(group = Evm, safety = Safe)] + function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses); + // -------- Recording Map Writes -------- /// Starts recording all map SSTOREs for later retrieval. @@ -440,21 +452,29 @@ interface Vm { /// Gets the number of elements in the mapping at the given slot, for a given address. #[cheatcode(group = Evm, safety = Safe)] - function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); + function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length); /// Gets the elements at index idx of the mapping at the given slot, for a given address. The /// index must be less than the length of the mapping (i.e. the number of keys in the mapping). #[cheatcode(group = Evm, safety = Safe)] - function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value); /// Gets the map key and parent of a mapping at a given slot, for a given address. #[cheatcode(group = Evm, safety = Safe)] function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external + view returns (bool found, bytes32 key, bytes32 parent); // -------- Block and Transaction Properties -------- + /// Gets the current `block.chainid` of the currently selected environment. + /// You should use this instead of `block.chainid` if you use `vm.selectFork` or `vm.createSelectFork`, as `block.chainid` could be assumed + /// to be constant across a transaction, and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getChainId() external view returns (uint256 blockChainId); + /// Sets `block.chainid`. #[cheatcode(group = Evm, safety = Unsafe)] function chainId(uint256 newChainId) external; @@ -522,6 +542,11 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getBlockTimestamp() external view returns (uint256 timestamp); + /// Gets the RLP encoded block header for a given block number. + /// Returns the block header in the same format as `cast block --raw`. + #[cheatcode(group = Evm, safety = Safe)] + function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader); + /// Sets `block.blobbasefee` #[cheatcode(group = Evm, safety = Unsafe)] function blobBaseFee(uint256 newBlobBaseFee) external; @@ -538,6 +563,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; + /// Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode). + /// The transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP. + /// Returns the execution output bytes. + /// + /// This cheatcode is not allowed in `forge script` contexts. + #[cheatcode(group = Evm, safety = Unsafe)] + function executeTransaction(bytes calldata rawTx) external returns (bytes memory); + // -------- Account State -------- /// Sets an address' balance. @@ -584,6 +617,18 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function coolSlot(address target, bytes32 slot) external; + /// Returns the test or script execution evm version. + /// + /// **Note:** The execution evm version is not the same as the compilation one. + #[cheatcode(group = Evm, safety = Safe)] + function getEvmVersion() external pure returns (string memory evm); + + /// Set the exact test or script execution evm version, e.g. `berlin`, `cancun`. + /// + /// **Note:** The execution evm version is not the same as the compilation one. + #[cheatcode(group = Evm, safety = Safe)] + function setEvmVersion(string calldata evm) external; + // -------- Call Manipulation -------- // --- Mocks --- @@ -698,7 +743,7 @@ interface Vm { /// Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification. #[cheatcode(group = Evm, safety = Unsafe)] - function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin); // ----- Arbitrary Snapshots ----- @@ -870,6 +915,7 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external + view returns (EthGetLogs[] memory logs); // --- Behavior --- @@ -911,7 +957,11 @@ interface Vm { /// Gets all the recorded logs. #[cheatcode(group = Evm, safety = Safe)] - function getRecordedLogs() external returns (Log[] memory logs); + function getRecordedLogs() external view returns (Log[] memory logs); + + /// Gets all the recorded logs, in JSON format. + #[cheatcode(group = Evm, safety = Safe)] + function getRecordedLogsJson() external view returns (string memory logsJson); // -------- Gas Metering -------- @@ -1032,6 +1082,9 @@ interface Vm { /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; @@ -1043,6 +1096,9 @@ interface Vm { /// Prepare an expected log with all topic and data checks enabled. /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. + /// Must be placed immediately before the call you want to assert on. If the next call reverts and the + /// revert is caught by the caller (low-level call or try/catch), the expectation remains active and may + /// be satisfied by a log emitted from a later call. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit() external; @@ -1193,7 +1249,7 @@ interface Vm { /// Asserts that the given condition is true and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertTrue(bool condition, string calldata error) external pure; + function assertTrue(bool condition, string calldata err) external pure; /// Asserts that the given condition is false. #[cheatcode(group = Testing, safety = Safe)] @@ -1201,7 +1257,7 @@ interface Vm { /// Asserts that the given condition is false and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertFalse(bool condition, string calldata error) external pure; + function assertFalse(bool condition, string calldata err) external pure; /// Asserts that two `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1209,7 +1265,7 @@ interface Vm { /// Asserts that two `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(bool left, bool right, string calldata err) external pure; /// Asserts that two `uint256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1217,7 +1273,7 @@ interface Vm { /// Asserts that two `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right, string calldata err) external pure; /// Asserts that two `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1225,7 +1281,7 @@ interface Vm { /// Asserts that two `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right, string calldata err) external pure; /// Asserts that two `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1233,7 +1289,7 @@ interface Vm { /// Asserts that two `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(address left, address right, string calldata error) external pure; + function assertEq(address left, address right, string calldata err) external pure; /// Asserts that two `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1241,7 +1297,7 @@ interface Vm { /// Asserts that two `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata err) external pure; /// Asserts that two `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1249,7 +1305,7 @@ interface Vm { /// Asserts that two `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right, string calldata err) external pure; /// Asserts that two `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1257,7 +1313,7 @@ interface Vm { /// Asserts that two `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure; /// Asserts that two arrays of `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1265,7 +1321,7 @@ interface Vm { /// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `uint256 values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1273,7 +1329,7 @@ interface Vm { /// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1281,7 +1337,7 @@ interface Vm { /// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1289,7 +1345,7 @@ interface Vm { /// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1297,7 +1353,7 @@ interface Vm { /// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1305,7 +1361,7 @@ interface Vm { /// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1313,7 +1369,7 @@ interface Vm { /// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1322,7 +1378,7 @@ interface Vm { /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1331,7 +1387,7 @@ interface Vm { /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1339,7 +1395,7 @@ interface Vm { /// Asserts that two `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(bool left, bool right, string calldata err) external pure; /// Asserts that two `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1347,7 +1403,7 @@ interface Vm { /// Asserts that two `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata err) external pure; /// Asserts that two `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1355,7 +1411,7 @@ interface Vm { /// Asserts that two `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right, string calldata err) external pure; /// Asserts that two `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1363,7 +1419,7 @@ interface Vm { /// Asserts that two `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(address left, address right, string calldata err) external pure; /// Asserts that two `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1371,7 +1427,7 @@ interface Vm { /// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure; /// Asserts that two `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1379,7 +1435,7 @@ interface Vm { /// Asserts that two `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata err) external pure; /// Asserts that two `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1387,7 +1443,7 @@ interface Vm { /// Asserts that two `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure; /// Asserts that two arrays of `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1395,7 +1451,7 @@ interface Vm { /// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1403,7 +1459,7 @@ interface Vm { /// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1411,7 +1467,7 @@ interface Vm { /// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1419,7 +1475,7 @@ interface Vm { /// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1427,7 +1483,7 @@ interface Vm { /// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1435,7 +1491,7 @@ interface Vm { /// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure; /// Asserts that two arrays of `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] @@ -1443,7 +1499,7 @@ interface Vm { /// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1452,7 +1508,7 @@ interface Vm { /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] @@ -1461,7 +1517,7 @@ interface Vm { /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1470,7 +1526,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1479,7 +1535,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. @@ -1489,7 +1545,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. @@ -1499,7 +1555,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1508,7 +1564,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1517,7 +1573,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. @@ -1527,7 +1583,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. @@ -1537,7 +1593,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1546,7 +1602,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] @@ -1555,7 +1611,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. @@ -1565,7 +1621,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. @@ -1575,7 +1631,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1584,7 +1640,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(uint256 left, uint256 right, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] @@ -1593,7 +1649,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right, string calldata err) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. @@ -1603,7 +1659,7 @@ interface Vm { /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. @@ -1613,7 +1669,7 @@ interface Vm { /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] @@ -1622,7 +1678,7 @@ interface Vm { /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] @@ -1631,7 +1687,7 @@ interface Vm { /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. @@ -1646,7 +1702,7 @@ interface Vm { uint256 right, uint256 maxDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. @@ -1662,7 +1718,7 @@ interface Vm { int256 right, uint256 maxDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. @@ -1674,7 +1730,7 @@ interface Vm { /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% @@ -1685,7 +1741,7 @@ interface Vm { /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] - function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% @@ -1707,7 +1763,7 @@ interface Vm { uint256 right, uint256 maxPercentDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. @@ -1730,7 +1786,7 @@ interface Vm { int256 right, uint256 maxPercentDelta, uint256 decimals, - string calldata error + string calldata err ) external pure; /// Returns true if the current Foundry version is greater than or equal to the given version. @@ -1778,6 +1834,11 @@ interface Vm { #[cheatcode(group = Filesystem)] function projectRoot() external view returns (string memory path); + /// Get the source file path of the currently running test or script contract, + /// relative to the project root. + #[cheatcode(group = Filesystem)] + function currentFilePath() external view returns (string memory path); + /// Returns the time since unix epoch in milliseconds. #[cheatcode(group = Filesystem)] function unixTime() external view returns (uint256 milliseconds); @@ -1887,11 +1948,13 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath) external returns (address deployedAddress); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] @@ -1899,6 +1962,7 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] @@ -1906,6 +1970,7 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] @@ -1913,11 +1978,13 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] @@ -1925,6 +1992,7 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] @@ -1932,6 +2000,7 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] @@ -2014,6 +2083,10 @@ interface Vm { // ======== Environment Variables ======== + /// Resolves the env variable placeholders of a given input string. + #[cheatcode(group = Environment)] + function resolveEnv(string calldata input) external returns (string memory); + /// Sets environment variables. #[cheatcode(group = Environment)] function setEnv(string calldata name, string calldata value) external; @@ -2171,7 +2244,6 @@ interface Vm { function isContext(ForgeContext context) external view returns (bool result); // ======== Scripts ======== - // -------- Broadcasting Transactions -------- /// Has the next call (at this call depth only) create transactions that can later be signed and sent onchain. @@ -2258,7 +2330,7 @@ interface Vm { /// Returns addresses of available unlocked wallets in the script environment. #[cheatcode(group = Scripting)] - function getWallets() external returns (address[] memory wallets); + function getWallets() external view returns (address[] memory wallets); // ======== Utilities ======== @@ -2324,7 +2396,7 @@ interface Vm { function indexOf(string calldata input, string calldata key) external pure returns (uint256); /// Returns true if `search` is found in `subject`, false otherwise. #[cheatcode(group = String)] - function contains(string calldata subject, string calldata search) external returns (bool result); + function contains(string calldata subject, string calldata search) external pure returns (bool result); // ======== JSON Parsing and Manipulation ======== @@ -2527,6 +2599,7 @@ interface Vm { /// Write a serialized JSON object to an **existing** JSON file, replacing a value with key = /// This is useful to replace a specific value of a JSON file, without having to parse the entire thing. + /// This cheatcode will create new keys if they didn't previously exist. #[cheatcode(group = Json)] function writeJson(string calldata json, string calldata path, string calldata valueKey) external; @@ -2632,6 +2705,7 @@ interface Vm { /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. + /// This cheatcode will create new keys if they didn't previously exist. #[cheatcode(group = Toml)] function writeToml(string calldata json, string calldata path, string calldata valueKey) external; @@ -2653,7 +2727,7 @@ interface Vm { /// Signs data with a `Wallet`. #[cheatcode(group = Crypto)] - function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs data with a `Wallet`. /// @@ -2661,12 +2735,17 @@ interface Vm { /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. #[cheatcode(group = Crypto)] - function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with `privateKey` using the secp256k1 curve. #[cheatcode(group = Crypto)] function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce` + /// as the raw ephemeral k value in ECDSA (instead of deriving it deterministically). + #[cheatcode(group = Crypto)] + function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Signs `digest` with `privateKey` using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the @@ -2723,25 +2802,53 @@ interface Vm { #[cheatcode(group = Crypto)] function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); - /// Derive a private key from a provided mnenomic string (or mnenomic file path) + /// Generates an Ed25519 key pair from a deterministic salt. + /// Returns (publicKey, privateKey) as 32-byte values. + #[cheatcode(group = Crypto, safety = Safe)] + function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey); + + /// Derives the Ed25519 public key from a private key. + #[cheatcode(group = Crypto, safety = Safe)] + function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey); + + /// Signs a message with namespace using Ed25519. + /// The signature covers namespace || message for domain separation. + /// Returns a 64-byte Ed25519 signature. + #[cheatcode(group = Crypto, safety = Safe)] + function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) + external + pure + returns (bytes memory signature); + + /// Verifies an Ed25519 signature over namespace || message. + /// Returns true if signature is valid, false otherwise. + #[cheatcode(group = Crypto, safety = Safe)] + function verifyEd25519( + bytes calldata signature, + bytes calldata namespace, + bytes calldata message, + bytes32 publicKey + ) external pure returns (bool valid); + + /// Derive a private key from a provided mnemonic string (or mnemonic file path) /// at the derivation path `m/44'/60'/0'/0/{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); - /// Derive a private key from a provided mnenomic string (or mnenomic file path) + /// Derive a private key from a provided mnemonic string (or mnemonic file path) /// at `{derivationPath}{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey); - /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language /// at the derivation path `m/44'/60'/0'/0/{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey); - /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language /// at `{derivationPath}{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) @@ -2809,13 +2916,17 @@ interface Vm { #[cheatcode(group = Utilities)] function ensNamehash(string calldata name) external pure returns (bytes32); + /// Returns an uint256 value bounded in given range and different from the current one. + #[cheatcode(group = Utilities)] + function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256); + /// Returns a random uint256 value. #[cheatcode(group = Utilities)] - function randomUint() external returns (uint256); + function randomUint() external view returns (uint256); /// Returns random uint256 value between the provided range (=min..=max). #[cheatcode(group = Utilities)] - function randomUint(uint256 min, uint256 max) external returns (uint256); + function randomUint(uint256 min, uint256 max) external view returns (uint256); /// Returns a random `uint256` value of given bits. #[cheatcode(group = Utilities)] @@ -2823,7 +2934,11 @@ interface Vm { /// Returns a random `address`. #[cheatcode(group = Utilities)] - function randomAddress() external returns (address); + function randomAddress() external view returns (address); + + /// Returns an int256 value bounded in given range and different from the current one. + #[cheatcode(group = Utilities)] + function bound(int256 current, int256 min, int256 max) external view returns (int256); /// Returns a random `int256` value. #[cheatcode(group = Utilities)] @@ -2859,16 +2974,16 @@ interface Vm { function resumeTracing() external view; /// Utility cheatcode to copy storage of `from` contract to another `to` contract. - #[cheatcode(group = Utilities)] + #[cheatcode(group = Utilities, safety = Unsafe)] function copyStorage(address from, address to) external; /// Utility cheatcode to set arbitrary storage for given target address. - #[cheatcode(group = Utilities)] + #[cheatcode(group = Utilities, safety = Unsafe)] function setArbitraryStorage(address target) external; /// Utility cheatcode to set arbitrary storage for given target address and overwrite /// any storage slots that have been previously set. - #[cheatcode(group = Utilities)] + #[cheatcode(group = Utilities, safety = Unsafe)] function setArbitraryStorage(address target, bool overwrite) external; /// Sorts an array in ascending order. @@ -2879,6 +2994,10 @@ interface Vm { #[cheatcode(group = Utilities)] function shuffle(uint256[] calldata array) external returns (uint256[] memory); + /// Set RNG seed. + #[cheatcode(group = Utilities)] + function setSeed(uint256 seed) external; + /// Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer. /// This allows type-safe access to the initcode payload that would be used for contract creation. /// Example usage: @@ -2888,6 +3007,62 @@ interface Vm { /// catch (bytes memory interceptedInitcode) { initcode = interceptedInitcode; } #[cheatcode(group = Utilities, safety = Unsafe)] function interceptInitcode() external; + + /// Generates the hash of the canonical EIP-712 type representation. + /// + /// Supports 2 different inputs: + /// 1. Name of the type (i.e. "Transaction"): + /// * requires previous binding generation with `forge bind-json`. + /// * bindings will be retrieved from the path configured in `foundry.toml`. + /// + /// 2. String representation of the type (i.e. "Foo(Bar bar) Bar(uint256 baz)"). + /// * Note: the cheatcode will output the canonical type even if the input is malformated + /// with the wrong order of elements or with extra whitespaces. + #[cheatcode(group = Utilities)] + function eip712HashType(string calldata typeNameOrDefinition) external pure returns (bytes32 typeHash); + + /// Generates the hash of the canonical EIP-712 type representation. + /// Requires previous binding generation with `forge bind-json`. + /// + /// Params: + /// * `bindingsPath`: path where the output of `forge bind-json` is stored. + /// * `typeName`: Name of the type (i.e. "Transaction"). + #[cheatcode(group = Utilities)] + function eip712HashType(string calldata bindingsPath, string calldata typeName) external pure returns (bytes32 typeHash); + + /// Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data. + /// + /// Supports 2 different inputs: + /// 1. Name of the type (i.e. "PermitSingle"): + /// * requires previous binding generation with `forge bind-json`. + /// * bindings will be retrieved from the path configured in `foundry.toml`. + /// + /// 2. String representation of the type (i.e. "Foo(Bar bar) Bar(uint256 baz)"). + /// * Note: the cheatcode will use the canonical type even if the input is malformated + /// with the wrong order of elements or with extra whitespaces. + #[cheatcode(group = Utilities)] + function eip712HashStruct(string calldata typeNameOrDefinition, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + + /// Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data. + /// Requires previous binding generation with `forge bind-json`. + /// + /// Params: + /// * `bindingsPath`: path where the output of `forge bind-json` is stored. + /// * `typeName`: Name of the type (i.e. "PermitSingle"). + /// * `abiEncodedData`: ABI-encoded data for the struct that is being hashed. + #[cheatcode(group = Utilities)] + function eip712HashStruct(string calldata bindingsPath, string calldata typeName, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + + /// Generates a ready-to-sign digest of human-readable typed data following the EIP-712 standard. + #[cheatcode(group = Utilities)] + function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest); + + /// RLP encodes a list of bytes into an RLP payload. + #[cheatcode(group = Utilities)] + function toRlp(bytes[] calldata data) external pure returns (bytes memory); + /// RLP decodes an RLP payload into a list of bytes. + #[cheatcode(group = Utilities)] + function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data); } } @@ -2902,13 +3077,13 @@ impl PartialEq for ForgeContext { (_, Self::ScriptGroup) => { matches!(self, Self::ScriptDryRun | Self::ScriptBroadcast | Self::ScriptResume) } - (Self::Test, Self::Test) | - (Self::Snapshot, Self::Snapshot) | - (Self::Coverage, Self::Coverage) | - (Self::ScriptDryRun, Self::ScriptDryRun) | - (Self::ScriptBroadcast, Self::ScriptBroadcast) | - (Self::ScriptResume, Self::ScriptResume) | - (Self::Unknown, Self::Unknown) => true, + (Self::Test, Self::Test) + | (Self::Snapshot, Self::Snapshot) + | (Self::Coverage, Self::Coverage) + | (Self::ScriptDryRun, Self::ScriptDryRun) + | (Self::ScriptBroadcast, Self::ScriptBroadcast) + | (Self::ScriptResume, Self::ScriptResume) + | (Self::Unknown, Self::Unknown) => true, _ => false, } } diff --git a/crates/cheatcodes/src/base64.rs b/crates/cheatcodes/src/base64.rs index 4aa4ba74a0e4b..7d5ab05ddfe20 100644 --- a/crates/cheatcodes/src/base64.rs +++ b/crates/cheatcodes/src/base64.rs @@ -1,31 +1,40 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_sol_types::SolValue; use base64::prelude::*; +use foundry_evm_core::evm::FoundryEvmNetwork; + +fn encode_base64(data: impl AsRef<[u8]>) -> Result { + Ok(BASE64_STANDARD.encode(data).abi_encode()) +} + +fn encode_base64_url(data: impl AsRef<[u8]>) -> Result { + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) +} impl Cheatcode for toBase64_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; - Ok(BASE64_STANDARD.encode(data).abi_encode()) + encode_base64(data) } } impl Cheatcode for toBase64_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; - Ok(BASE64_STANDARD.encode(data).abi_encode()) + encode_base64(data) } } impl Cheatcode for toBase64URL_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; - Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + encode_base64_url(data) } } impl Cheatcode for toBase64URL_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; - Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + encode_base64_url(data) } } diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 210c76553cba7..a5e3d35a985ed 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,15 +1,14 @@ use super::Result; use crate::Vm::Rpc; -use alloy_primitives::{map::AddressHashMap, U256}; -use foundry_common::{fs::normalize_path, ContractsByArtifact}; -use foundry_compilers::{utils::canonicalize, ArtifactId, ProjectPathsConfig}; +use alloy_primitives::{Address, U256, map::AddressHashMap}; +use foundry_common::{ContractsByArtifact, fs::normalize_path}; +use foundry_compilers::{ArtifactId, ProjectPathsConfig, utils::canonicalize}; use foundry_config::{ - cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions, - ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, + Config, FsPermissions, ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, + cache::StorageCachingConfig, fs_permissions::FsAccessKind, }; use foundry_evm_core::opts::EvmOpts; use std::{ - collections::HashMap, path::{Path, PathBuf}, time::Duration, }; @@ -33,14 +32,14 @@ pub struct CheatsConfig { pub rpc_endpoints: ResolvedRpcEndpoints, /// Project's paths as configured pub paths: ProjectPathsConfig, + /// Path to the directory that contains the bindings generated by `forge bind-json`. + pub bind_json_path: PathBuf, /// Filesystem permissions for cheatcodes like `writeFile`, `readFile` pub fs_permissions: FsPermissions, /// Project root pub root: PathBuf, /// Absolute Path to broadcast dir i.e project_root/broadcast pub broadcast: PathBuf, - /// Paths (directories) where file reading/writing is allowed - pub allowed_paths: Vec, /// How the evm was configured by the user pub evm_opts: EvmOpts, /// Address labels from config @@ -57,18 +56,8 @@ pub struct CheatsConfig { pub seed: Option, /// Whether to allow `expectRevert` to work for internal calls. pub internal_expect_revert: bool, - /// Mapping of chain aliases to chain data - pub chains: HashMap, - /// Mapping of chain IDs to their aliases - pub chain_id_to_alias: HashMap, -} - -/// Chain data for getChain cheatcodes -#[derive(Clone, Debug)] -pub struct ChainData { - pub name: String, - pub chain_id: u64, - pub default_rpc_url: String, // Store default RPC URL + /// Fee token to use for Tempo transactions. + pub fee_token: Option
, } impl CheatsConfig { @@ -78,11 +67,8 @@ impl CheatsConfig { evm_opts: EvmOpts, available_artifacts: Option, running_artifact: Option, + fee_token: Option
, ) -> Self { - let mut allowed_paths = vec![config.root.clone()]; - allowed_paths.extend(config.libs.iter().cloned()); - allowed_paths.extend(config.allow_paths.iter().cloned()); - let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); @@ -98,10 +84,10 @@ impl CheatsConfig { no_storage_caching: config.no_storage_caching, rpc_endpoints, paths: config.project_paths(), + bind_json_path: config.bind_json.out.clone(), fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()), root: config.root.clone(), broadcast: config.root.clone().join(&config.broadcast), - allowed_paths, evm_opts, labels: config.labels.clone(), available_artifacts, @@ -109,14 +95,19 @@ impl CheatsConfig { assertions_revert: config.assertions_revert, seed: config.fuzz.seed, internal_expect_revert: config.allow_internal_expect_revert, - chains: HashMap::new(), - chain_id_to_alias: HashMap::new(), + fee_token, } } /// Returns a new `CheatsConfig` configured with the given `Config` and `EvmOpts`. pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self { - Self::new(config, evm_opts, self.available_artifacts.clone(), self.running_artifact.clone()) + Self::new( + config, + evm_opts, + self.available_artifacts.clone(), + self.running_artifact.clone(), + self.fee_token, + ) } /// Attempts to canonicalize (see [std::fs::canonicalize]) the path. @@ -195,6 +186,9 @@ impl CheatsConfig { pub fn rpc_endpoint(&self, url_or_alias: &str) -> Result { if let Some(endpoint) = self.rpc_endpoints.get(url_or_alias) { Ok(endpoint.clone().try_resolve()) + } else if let Some(builtin_url) = foundry_config::builtin_rpc_url(url_or_alias) { + let url = RpcEndpointUrl::Url(builtin_url.to_string()); + Ok(RpcEndpoint::new(url).resolve()) } else { // check if it's a URL or a path to an existing file to an ipc socket if url_or_alias.starts_with("http") || @@ -218,77 +212,6 @@ impl CheatsConfig { } Ok(urls) } - - /// Initialize default chain data (similar to initializeStdChains in Solidity) - pub fn initialize_chain_data(&mut self) { - if !self.chains.is_empty() { - return; // Already initialized - } - - // Use the same function to create chains - let chains = create_default_chains(); - - // Add all chains to the config - for (alias, data) in chains { - self.set_chain_with_default_rpc_url(&alias, data); - } - } - - /// Set chain with default RPC URL (similar to setChainWithDefaultRpcUrl in Solidity) - pub fn set_chain_with_default_rpc_url(&mut self, alias: &str, data: ChainData) { - // Store the default RPC URL is already stored in the data - // No need to clone it separately - - // Add chain data - self.set_chain_data(alias, data); - } - - /// Set chain data for a specific alias - pub fn set_chain_data(&mut self, alias: &str, data: ChainData) { - // Remove old chain ID mapping if it exists - if let Some(old_data) = self.chains.get(alias) { - self.chain_id_to_alias.remove(&old_data.chain_id); - } - - // Add new mappings - self.chain_id_to_alias.insert(data.chain_id, alias.to_string()); - self.chains.insert(alias.to_string(), data); - } - - /// Get chain data by alias - pub fn get_chain_data_by_alias_non_mut(&self, alias: &str) -> Result { - // Initialize chains if not already done - if self.chains.is_empty() { - // Create a temporary copy with initialized chains - // This is inefficient but handles the edge case - let temp_chains = create_default_chains(); - - if let Some(data) = temp_chains.get(alias) { - return Ok(data.clone()); - } - } else { - // Normal path - chains are initialized - if let Some(data) = self.chains.get(alias) { - return Ok(data.clone()); - } - } - - // Chain not found in either case - Err(fmt_err!("vm.getChain: Chain with alias \"{}\" not found", alias)) - } - - /// Get RPC URL for an alias - pub fn get_rpc_url_non_mut(&self, alias: &str) -> Result { - // Try to get from config first - match self.rpc_endpoint(alias) { - Ok(endpoint) => Ok(endpoint.url()?), - Err(_) => { - // If not in config, try to get default URL - let chain_data = self.get_chain_data_by_alias_non_mut(alias)?; - Ok(chain_data.default_rpc_url) - } - } - } } impl Default for CheatsConfig { @@ -303,8 +226,8 @@ impl Default for CheatsConfig { paths: ProjectPathsConfig::builder().build_with_root("./"), fs_permissions: Default::default(), root: Default::default(), + bind_json_path: PathBuf::default().join("utils").join("jsonBindings.sol"), broadcast: Default::default(), - allowed_paths: vec![], evm_opts: Default::default(), labels: Default::default(), available_artifacts: Default::default(), @@ -312,399 +235,11 @@ impl Default for CheatsConfig { assertions_revert: true, seed: None, internal_expect_revert: false, - chains: HashMap::new(), - chain_id_to_alias: HashMap::new(), + fee_token: None, } } } -// Helper function to set default chains -fn create_default_chains() -> HashMap { - let mut chains = HashMap::new(); - - // Define all chains in one place - chains.insert( - "anvil".to_string(), - ChainData { - name: "Anvil".to_string(), - chain_id: 31337, - default_rpc_url: "http://127.0.0.1:8545".to_string(), - }, - ); - - chains.insert( - "mainnet".to_string(), - ChainData { - name: "Mainnet".to_string(), - chain_id: 1, - default_rpc_url: "https://eth.llamarpc.com".to_string(), - }, - ); - - chains.insert( - "sepolia".to_string(), - ChainData { - name: "Sepolia".to_string(), - chain_id: 11155111, - default_rpc_url: "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001" - .to_string(), - }, - ); - - chains.insert( - "holesky".to_string(), - ChainData { - name: "Holesky".to_string(), - chain_id: 17000, - default_rpc_url: "https://rpc.holesky.ethpandaops.io".to_string(), - }, - ); - - chains.insert( - "optimism".to_string(), - ChainData { - name: "Optimism".to_string(), - chain_id: 10, - default_rpc_url: "https://mainnet.optimism.io".to_string(), - }, - ); - - chains.insert( - "optimism_sepolia".to_string(), - ChainData { - name: "Optimism Sepolia".to_string(), - chain_id: 11155420, - default_rpc_url: "https://sepolia.optimism.io".to_string(), - }, - ); - - chains.insert( - "arbitrum_one".to_string(), - ChainData { - name: "Arbitrum One".to_string(), - chain_id: 42161, - default_rpc_url: "https://arb1.arbitrum.io/rpc".to_string(), - }, - ); - - chains.insert( - "arbitrum_one_sepolia".to_string(), - ChainData { - name: "Arbitrum One Sepolia".to_string(), - chain_id: 421614, - default_rpc_url: "https://sepolia-rollup.arbitrum.io/rpc".to_string(), - }, - ); - - chains.insert( - "arbitrum_nova".to_string(), - ChainData { - name: "Arbitrum Nova".to_string(), - chain_id: 42170, - default_rpc_url: "https://nova.arbitrum.io/rpc".to_string(), - }, - ); - - chains.insert( - "polygon".to_string(), - ChainData { - name: "Polygon".to_string(), - chain_id: 137, - default_rpc_url: "https://polygon-rpc.com".to_string(), - }, - ); - - chains.insert( - "polygon_amoy".to_string(), - ChainData { - name: "Polygon Amoy".to_string(), - chain_id: 80002, - default_rpc_url: "https://rpc-amoy.polygon.technology".to_string(), - }, - ); - - chains.insert( - "avalanche".to_string(), - ChainData { - name: "Avalanche".to_string(), - chain_id: 43114, - default_rpc_url: "https://api.avax.network/ext/bc/C/rpc".to_string(), - }, - ); - - chains.insert( - "avalanche_fuji".to_string(), - ChainData { - name: "Avalanche Fuji".to_string(), - chain_id: 43113, - default_rpc_url: "https://api.avax-test.network/ext/bc/C/rpc".to_string(), - }, - ); - - chains.insert( - "bnb_smart_chain".to_string(), - ChainData { - name: "BNB Smart Chain".to_string(), - chain_id: 56, - default_rpc_url: "https://bsc-dataseed1.binance.org".to_string(), - }, - ); - - chains.insert( - "bnb_smart_chain_testnet".to_string(), - ChainData { - name: "BNB Smart Chain Testnet".to_string(), - chain_id: 97, - default_rpc_url: "https://rpc.ankr.com/bsc_testnet_chapel".to_string(), - }, - ); - - chains.insert( - "gnosis_chain".to_string(), - ChainData { - name: "Gnosis Chain".to_string(), - chain_id: 100, - default_rpc_url: "https://rpc.gnosischain.com".to_string(), - }, - ); - - chains.insert( - "moonbeam".to_string(), - ChainData { - name: "Moonbeam".to_string(), - chain_id: 1284, - default_rpc_url: "https://rpc.api.moonbeam.network".to_string(), - }, - ); - - chains.insert( - "moonriver".to_string(), - ChainData { - name: "Moonriver".to_string(), - chain_id: 1285, - default_rpc_url: "https://rpc.api.moonriver.moonbeam.network".to_string(), - }, - ); - - chains.insert( - "moonbase".to_string(), - ChainData { - name: "Moonbase".to_string(), - chain_id: 1287, - default_rpc_url: "https://rpc.testnet.moonbeam.network".to_string(), - }, - ); - - chains.insert( - "base_sepolia".to_string(), - ChainData { - name: "Base Sepolia".to_string(), - chain_id: 84532, - default_rpc_url: "https://sepolia.base.org".to_string(), - }, - ); - - chains.insert( - "base".to_string(), - ChainData { - name: "Base".to_string(), - chain_id: 8453, - default_rpc_url: "https://mainnet.base.org".to_string(), - }, - ); - - chains.insert( - "blast_sepolia".to_string(), - ChainData { - name: "Blast Sepolia".to_string(), - chain_id: 168587773, - default_rpc_url: "https://sepolia.blast.io".to_string(), - }, - ); - - chains.insert( - "blast".to_string(), - ChainData { - name: "Blast".to_string(), - chain_id: 81457, - default_rpc_url: "https://rpc.blast.io".to_string(), - }, - ); - - chains.insert( - "fantom_opera".to_string(), - ChainData { - name: "Fantom Opera".to_string(), - chain_id: 250, - default_rpc_url: "https://rpc.ankr.com/fantom/".to_string(), - }, - ); - - chains.insert( - "fantom_opera_testnet".to_string(), - ChainData { - name: "Fantom Opera Testnet".to_string(), - chain_id: 4002, - default_rpc_url: "https://rpc.ankr.com/fantom_testnet/".to_string(), - }, - ); - - chains.insert( - "fraxtal".to_string(), - ChainData { - name: "Fraxtal".to_string(), - chain_id: 252, - default_rpc_url: "https://rpc.frax.com".to_string(), - }, - ); - - chains.insert( - "fraxtal_testnet".to_string(), - ChainData { - name: "Fraxtal Testnet".to_string(), - chain_id: 2522, - default_rpc_url: "https://rpc.testnet.frax.com".to_string(), - }, - ); - - chains.insert( - "berachain_bartio_testnet".to_string(), - ChainData { - name: "Berachain bArtio Testnet".to_string(), - chain_id: 80084, - default_rpc_url: "https://bartio.rpc.berachain.com".to_string(), - }, - ); - - chains.insert( - "flare".to_string(), - ChainData { - name: "Flare".to_string(), - chain_id: 14, - default_rpc_url: "https://flare-api.flare.network/ext/C/rpc".to_string(), - }, - ); - - chains.insert( - "flare_coston2".to_string(), - ChainData { - name: "Flare Coston2".to_string(), - chain_id: 114, - default_rpc_url: "https://coston2-api.flare.network/ext/C/rpc".to_string(), - }, - ); - - chains.insert( - "mode".to_string(), - ChainData { - name: "Mode".to_string(), - chain_id: 34443, - default_rpc_url: "https://mode.drpc.org".to_string(), - }, - ); - - chains.insert( - "mode_sepolia".to_string(), - ChainData { - name: "Mode Sepolia".to_string(), - chain_id: 919, - default_rpc_url: "https://sepolia.mode.network".to_string(), - }, - ); - - chains.insert( - "zora".to_string(), - ChainData { - name: "Zora".to_string(), - chain_id: 7777777, - default_rpc_url: "https://zora.drpc.org".to_string(), - }, - ); - - chains.insert( - "zora_sepolia".to_string(), - ChainData { - name: "Zora Sepolia".to_string(), - chain_id: 999999999, - default_rpc_url: "https://sepolia.rpc.zora.energy".to_string(), - }, - ); - - chains.insert( - "race".to_string(), - ChainData { - name: "Race".to_string(), - chain_id: 6805, - default_rpc_url: "https://racemainnet.io".to_string(), - }, - ); - - chains.insert( - "race_sepolia".to_string(), - ChainData { - name: "Race Sepolia".to_string(), - chain_id: 6806, - default_rpc_url: "https://racemainnet.io".to_string(), - }, - ); - - chains.insert( - "metal".to_string(), - ChainData { - name: "Metal".to_string(), - chain_id: 1750, - default_rpc_url: "https://metall2.drpc.org".to_string(), - }, - ); - - chains.insert( - "metal_sepolia".to_string(), - ChainData { - name: "Metal Sepolia".to_string(), - chain_id: 1740, - default_rpc_url: "https://testnet.rpc.metall2.com".to_string(), - }, - ); - - chains.insert( - "binary".to_string(), - ChainData { - name: "Binary".to_string(), - chain_id: 624, - default_rpc_url: "https://rpc.zero.thebinaryholdings.com".to_string(), - }, - ); - - chains.insert( - "binary_sepolia".to_string(), - ChainData { - name: "Binary Sepolia".to_string(), - chain_id: 625, - default_rpc_url: "https://rpc.zero.thebinaryholdings.com".to_string(), - }, - ); - - chains.insert( - "orderly".to_string(), - ChainData { - name: "Orderly".to_string(), - chain_id: 291, - default_rpc_url: "https://rpc.orderly.network".to_string(), - }, - ); - - chains.insert( - "orderly_sepolia".to_string(), - ChainData { - name: "Orderly Sepolia".to_string(), - chain_id: 4460, - default_rpc_url: "https://testnet-rpc.orderly.org".to_string(), - }, - ); - - chains -} - #[cfg(test)] mod tests { use super::*; @@ -716,6 +251,7 @@ mod tests { Default::default(), None, None, + None, ) } diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index be6829ee3110b..7a5f11370535e 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -1,58 +1,76 @@ //! Implementations of [`Crypto`](spec::Group::Crypto) Cheatcodes. use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; -use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, keccak256}; use alloy_signer::{Signer, SignerSync}; use alloy_signer_local::{ + LocalSigner, MnemonicBuilder, PrivateKeySigner, coins_bip39::{ ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean, Portuguese, Spanish, Wordlist, }, - LocalSigner, MnemonicBuilder, PrivateKeySigner, }; use alloy_sol_types::SolValue; +use foundry_evm_core::evm::FoundryEvmNetwork; use k256::{ - ecdsa::SigningKey, + FieldBytes, Scalar, + ecdsa::{SigningKey, hazmat}, elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, }; + use p256::ecdsa::{ - signature::hazmat::PrehashSigner, Signature as P256Signature, SigningKey as P256SigningKey, + Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner, +}; + +use ed25519_consensus::{ + Signature as Ed25519Signature, SigningKey as Ed25519SigningKey, + VerificationKey as Ed25519VerificationKey, }; /// The BIP32 default derivation path prefix. const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; impl Cheatcode for createWallet_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { walletLabel } = self; create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state) } } impl Cheatcode for createWallet_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; create_wallet(privateKey, None, state) } } impl Cheatcode for createWallet_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey, walletLabel } = self; create_wallet(privateKey, Some(walletLabel), state) } } impl Cheatcode for sign_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; Ok(encode_full_sig(sig)) } } +impl Cheatcode for signWithNonceUnsafeCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let pk: U256 = self.privateKey; + let digest: B256 = self.digest; + let nonce: U256 = self.nonce; + let sig: alloy_primitives::Signature = sign_with_nonce(&pk, &digest, &nonce)?; + Ok(encode_full_sig(sig)) + } +} + impl Cheatcode for signCompact_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; Ok(encode_compact_sig(sig)) @@ -60,35 +78,35 @@ impl Cheatcode for signCompact_0Call { } impl Cheatcode for deriveKey_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index } = self; derive_key::(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index) } } impl Cheatcode for deriveKey_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index } = self; derive_key::(mnemonic, derivationPath, *index) } } impl Cheatcode for deriveKey_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index, language } = self; derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language) } } impl Cheatcode for deriveKey_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index, language } = self; derive_key_str(mnemonic, derivationPath, *index, language) } } impl Cheatcode for rememberKeyCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = parse_wallet(privateKey)?; let address = inject_wallet(state, wallet); @@ -97,7 +115,7 @@ impl Cheatcode for rememberKeyCall { } impl Cheatcode for rememberKeys_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, count } = self; let wallets = derive_wallets::(mnemonic, derivationPath, *count)?; let mut addresses = Vec::
::with_capacity(wallets.len()); @@ -111,7 +129,7 @@ impl Cheatcode for rememberKeys_0Call { } impl Cheatcode for rememberKeys_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, language, count } = self; let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?; let mut addresses = Vec::
::with_capacity(wallets.len()); @@ -124,14 +142,17 @@ impl Cheatcode for rememberKeys_1Call { } } -fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner) -> Address { +fn inject_wallet( + state: &mut Cheatcodes, + wallet: LocalSigner, +) -> Address { let address = wallet.address(); state.wallets().add_local_signer(wallet); address } impl Cheatcode for sign_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; Ok(encode_full_sig(sig)) @@ -139,7 +160,7 @@ impl Cheatcode for sign_1Call { } impl Cheatcode for signCompact_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; Ok(encode_compact_sig(sig)) @@ -147,7 +168,7 @@ impl Cheatcode for signCompact_1Call { } impl Cheatcode for sign_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; Ok(encode_full_sig(sig)) @@ -155,7 +176,7 @@ impl Cheatcode for sign_2Call { } impl Cheatcode for signCompact_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; Ok(encode_compact_sig(sig)) @@ -163,7 +184,7 @@ impl Cheatcode for signCompact_2Call { } impl Cheatcode for sign_3Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; Ok(encode_full_sig(sig)) @@ -171,7 +192,7 @@ impl Cheatcode for sign_3Call { } impl Cheatcode for signCompact_3Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; Ok(encode_compact_sig(sig)) @@ -179,14 +200,14 @@ impl Cheatcode for signCompact_3Call { } impl Cheatcode for signP256Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; sign_p256(privateKey, digest) } } impl Cheatcode for publicKeyP256Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let pub_key = parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false); @@ -197,11 +218,43 @@ impl Cheatcode for publicKeyP256Call { } } +impl Cheatcode for createEd25519KeyCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { salt } = self; + create_ed25519_key(salt) + } +} + +impl Cheatcode for publicKeyEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + public_key_ed25519(privateKey) + } +} + +impl Cheatcode for signEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { namespace, message, privateKey } = self; + sign_ed25519(namespace, message, privateKey) + } +} + +impl Cheatcode for verifyEd25519Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { signature, namespace, message, publicKey } = self; + verify_ed25519(signature, namespace, message, publicKey) + } +} + /// Using a given private key, return its public ETH address, its public key affine x and y /// coordinates, and its private key (see the 'Wallet' struct) /// /// If 'label' is set to 'Some()', assign that label to the associated ETH address in state -fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result { +fn create_wallet( + private_key: &U256, + label: Option<&str>, + state: &mut Cheatcodes, +) -> Result { let key = parse_private_key(private_key)?; let addr = alloy_signer::utils::secret_key_to_address(&key); @@ -241,8 +294,88 @@ fn sign(private_key: &U256, digest: &B256) -> Result Result { + let d_scalar: Scalar = + ::from_repr(private_key.to_be_bytes().into()) + .into_option() + .ok_or_else(|| fmt_err!("invalid private key scalar"))?; + if bool::from(d_scalar.is_zero()) { + return Err(fmt_err!("private key cannot be 0")); + } + + let k_scalar: Scalar = + ::from_repr(nonce.to_be_bytes().into()) + .into_option() + .ok_or_else(|| fmt_err!("invalid nonce scalar"))?; + if bool::from(k_scalar.is_zero()) { + return Err(fmt_err!("nonce cannot be 0")); + } + + let mut z = [0u8; 32]; + z.copy_from_slice(digest.as_slice()); + let z_fb: FieldBytes = FieldBytes::from(z); + + // Hazmat signing using the scalar `d` (SignPrimitive is implemented for `Scalar`) + // Note: returns (Signature, Option) + let (sig_raw, recid_opt) = + >::try_sign_prehashed( + &d_scalar, k_scalar, &z_fb, + ) + .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?; + + // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid) + let (sig_low, flipped) = + if let Some(norm) = sig_raw.normalize_s() { (norm, true) } else { (sig_raw, false) }; + + let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into()); + let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into()); + + // Determine v parity in {0,1} + let v_parity = if let Some(id) = recid_opt { + let mut v = id.to_byte() & 1; + if flipped { + v ^= 1; + } + v + } else { + // Fallback: choose parity by recovery to expected address + let expected_addr = { + let sk: SigningKey = parse_private_key(private_key)?; + alloy_signer::utils::secret_key_to_address(&sk) + }; + // Try v = 0 + let cand0 = alloy_primitives::Signature::new(r_u256, s_u256, false); + if cand0.recover_address_from_prehash(digest).ok() == Some(expected_addr) { + return Ok(cand0); + } + // Try v = 1 + let cand1 = alloy_primitives::Signature::new(r_u256, s_u256, true); + if cand1.recover_address_from_prehash(digest).ok() == Some(expected_addr) { + return Ok(cand1); + } + return Err(fmt_err!("failed to determine recovery id for signature")); + }; + + let y_parity = v_parity != 0; + Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity)) +} + +fn sign_with_wallet( + state: &mut Cheatcodes, signer: Option
, digest: &B256, ) -> Result { @@ -261,7 +394,9 @@ fn sign_with_wallet( } else if signers.len() == 1 { *signers.keys().next().unwrap() } else { - bail!("could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one"); + bail!( + "could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one" + ); }; let wallet = signers @@ -287,7 +422,7 @@ fn validate_private_key(private_key: &U256) -> Result<()> ensure!(*private_key != U256::ZERO, "private key cannot be 0"); let order = U256::from_be_slice(&C::ORDER.to_be_byte_array()); ensure!( - *private_key < U256::from_be_slice(&C::ORDER.to_be_byte_array()), + *private_key < order, "private key must be less than the {curve:?} curve order ({order})", curve = C::default(), ); @@ -305,6 +440,47 @@ fn parse_private_key_p256(private_key: &U256) -> Result { Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) } +fn parse_signing_key_ed25519(private_key: &B256) -> Result { + Ed25519SigningKey::try_from(private_key.as_slice()) + .map_err(|e| fmt_err!("invalid Ed25519 private key: {e}")) +} + +fn create_ed25519_key(salt: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(salt)?; + let public_key = B256::from_slice(signing_key.verification_key().as_ref()); + Ok((public_key, *salt).abi_encode()) +} + +fn public_key_ed25519(private_key: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(private_key)?; + Ok(B256::from_slice(signing_key.verification_key().as_ref()).abi_encode()) +} + +fn sign_ed25519(namespace: &[u8], message: &[u8], private_key: &B256) -> Result { + let signing_key = parse_signing_key_ed25519(private_key)?; + let combined = [namespace, message].concat(); + let signature: [u8; 64] = signing_key.sign(&combined).into(); + Ok(signature.to_vec().abi_encode()) +} + +fn verify_ed25519(signature: &[u8], namespace: &[u8], message: &[u8], public_key: &B256) -> Result { + if signature.len() != 64 { + return Ok(false.abi_encode()); + } + + let Ok(verification_key) = Ed25519VerificationKey::try_from(public_key.as_slice()) else { + return Ok(false.abi_encode()); + }; + + let Ok(sig_bytes): Result<[u8; 64], _> = signature.try_into() else { + return Ok(false.abi_encode()); + }; + + let combined = [namespace, message].concat(); + let valid = verification_key.verify(&Ed25519Signature::from(sig_bytes), &combined).is_ok(); + Ok(valid.abi_encode()) +} + pub(super) fn parse_wallet(private_key: &U256) -> Result { parse_private_key(private_key).map(PrivateKeySigner::from) } @@ -390,7 +566,8 @@ fn derive_wallets( #[cfg(test)] mod tests { use super::*; - use alloy_primitives::{hex::FromHex, FixedBytes}; + use alloy_primitives::{FixedBytes, hex::FromHex}; + use k256::elliptic_curve::Curve; use p256::ecdsa::signature::hazmat::PrehashVerifier; #[test] @@ -421,7 +598,10 @@ mod tests { ) .unwrap(); let result = sign_p256(&pk, &digest); - assert_eq!(result.err().unwrap().to_string(), "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"); + assert_eq!( + result.err().unwrap().to_string(), + "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)" + ); } #[test] @@ -433,4 +613,170 @@ mod tests { let result = sign_p256(&U256::ZERO, &digest); assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); } + + #[test] + fn test_sign_with_nonce_varies_and_recovers() { + // Given a fixed private key and digest + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ) + .unwrap(); + + // Two distinct nonces + let n1: U256 = U256::from(123u64); + let n2: U256 = U256::from(456u64); + + // Sign with both nonces + let sig1 = sign_with_nonce(&pk_u256, &digest, &n1).expect("sig1"); + let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2"); + + // (r,s) must differ when nonce differs + assert!( + sig1.r() != sig2.r() || sig1.s() != sig2.s(), + "signatures should differ with different nonces" + ); + + // ecrecover must yield the address for both signatures + let sk = parse_private_key(&pk_u256).unwrap(); + let expected = alloy_signer::utils::secret_key_to_address(&sk); + + assert_eq!(sig1.recover_address_from_prehash(&digest).unwrap(), expected); + assert_eq!(sig2.recover_address_from_prehash(&digest).unwrap(), expected); + } + + #[test] + fn test_sign_with_nonce_zero_nonce_errors() { + // nonce = 0 should be rejected + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ) + .unwrap(); + let n0: U256 = U256::ZERO; + + let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}"); + } + + #[test] + fn test_sign_with_nonce_nonce_ge_order_errors() { + // nonce >= n should be rejected + use k256::Secp256k1; + // Curve order n as U256 + let n_u256 = U256::from_be_slice(&Secp256k1::ORDER.to_be_byte_array()); + + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + ) + .unwrap(); + + // Try exactly n (>= n invalid) + let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}"); + } + + #[test] + fn test_create_ed25519_key_determinism() { + let salt = B256::from([1u8; 32]); + let result1 = create_ed25519_key(&salt).unwrap(); + let result2 = create_ed25519_key(&salt).unwrap(); + assert_eq!(result1, result2, "same salt should produce same keys"); + } + + #[test] + fn test_create_ed25519_key_different_salts() { + let salt1 = B256::from([1u8; 32]); + let salt2 = B256::from([2u8; 32]); + let result1 = create_ed25519_key(&salt1).unwrap(); + let result2 = create_ed25519_key(&salt2).unwrap(); + assert_ne!(result1, result2, "different salts should produce different keys"); + } + + #[test] + fn test_public_key_ed25519_consistency() { + let salt = B256::from([42u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (expected_public, private): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let derived_public_result = public_key_ed25519(&private).unwrap(); + let derived_public = B256::abi_decode(&derived_public_result).unwrap(); + + assert_eq!(expected_public, derived_public, "derived public key should match"); + } + + #[test] + fn test_sign_and_verify_ed25519_valid() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, private_key): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let namespace = b"test.namespace"; + let message = b"hello world"; + let sig_result = sign_ed25519(namespace, message, &private_key).unwrap(); + let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); + + let verify_result = verify_ed25519(&sig_bytes, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + + assert!(valid, "signature should be valid"); + } + + #[test] + fn test_verify_ed25519_invalid_signature() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let invalid_sig = [0u8; 64]; + let namespace = b"test.namespace"; + let message = b"hello world"; + + let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + + assert!(!valid, "invalid signature should not verify"); + } + + #[test] + fn test_verify_ed25519_namespace_separation() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, private_key): (B256, B256) = + <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let namespace_a = b"namespace.a"; + let message = b"message"; + let sig_result = sign_ed25519(namespace_a, message, &private_key).unwrap(); + let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); + + let namespace_b = b"namespace.b"; + let verify_result = verify_ed25519(&sig_bytes, namespace_b, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(!valid, "signature with namespace A should not verify with namespace B"); + + let verify_result = verify_ed25519(&sig_bytes, namespace_a, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(valid, "signature should verify with correct namespace"); + } + + #[test] + fn test_verify_ed25519_invalid_signature_length() { + let salt = B256::from([123u8; 32]); + let create_result = create_ed25519_key(&salt).unwrap(); + let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); + + let invalid_sig = [0u8; 32]; + let namespace = b"test"; + let message = b"message"; + + let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); + let valid = bool::abi_decode(&verify_result).unwrap(); + assert!(!valid, "signature with wrong length should not verify"); + } } diff --git a/crates/cheatcodes/src/env.rs b/crates/cheatcodes/src/env.rs index bba8069fb0ac5..c49b1d21a31af 100644 --- a/crates/cheatcodes/src/env.rs +++ b/crates/cheatcodes/src/env.rs @@ -1,15 +1,16 @@ //! Implementations of [`Environment`](spec::Group::Environment) cheatcodes. -use crate::{string, Cheatcode, Cheatcodes, Error, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, Error, Result, Vm::*, string}; use alloy_dyn_abi::DynSolType; use alloy_sol_types::SolValue; +use foundry_evm_core::evm::FoundryEvmNetwork; use std::{env, sync::OnceLock}; /// Stores the forge execution context for the duration of the program. -static FORGE_CONTEXT: OnceLock = OnceLock::new(); +pub static FORGE_CONTEXT: OnceLock = OnceLock::new(); impl Cheatcode for setEnvCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name: key, value } = self; if key.is_empty() { Err(fmt_err!("environment variable key can't be empty")) @@ -20,112 +21,123 @@ impl Cheatcode for setEnvCall { } else if value.contains('\0') { Err(fmt_err!("environment variable value can't contain NUL character `\\0`")) } else { - env::set_var(key, value); + unsafe { + env::set_var(key, value); + } Ok(Default::default()) } } } +impl Cheatcode for resolveEnvCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + let resolved = foundry_config::resolve::interpolate(input) + .map_err(|e| fmt_err!("failed to resolve env var: {e}"))?; + Ok(resolved.abi_encode()) + } +} + impl Cheatcode for envExistsCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(env::var(name).is_ok().abi_encode()) } } impl Cheatcode for envBool_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bool) } } impl Cheatcode for envUint_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Uint(256)) } } impl Cheatcode for envInt_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Int(256)) } } impl Cheatcode for envAddress_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Address) } } impl Cheatcode for envBytes32_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::FixedBytes(32)) } } impl Cheatcode for envString_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::String) } } impl Cheatcode for envBytes_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bytes) } } impl Cheatcode for envBool_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bool) } } impl Cheatcode for envUint_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Uint(256)) } } impl Cheatcode for envInt_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Int(256)) } } impl Cheatcode for envAddress_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Address) } } impl Cheatcode for envBytes32_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::FixedBytes(32)) } } impl Cheatcode for envString_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::String) } } impl Cheatcode for envBytes_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bytes) } @@ -133,7 +145,7 @@ impl Cheatcode for envBytes_1Call { // bool impl Cheatcode for envOr_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bool) } @@ -141,7 +153,7 @@ impl Cheatcode for envOr_0Call { // uint256 impl Cheatcode for envOr_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Uint(256)) } @@ -149,7 +161,7 @@ impl Cheatcode for envOr_1Call { // int256 impl Cheatcode for envOr_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Int(256)) } @@ -157,7 +169,7 @@ impl Cheatcode for envOr_2Call { // address impl Cheatcode for envOr_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Address) } @@ -165,7 +177,7 @@ impl Cheatcode for envOr_3Call { // bytes32 impl Cheatcode for envOr_4Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::FixedBytes(32)) } @@ -173,7 +185,7 @@ impl Cheatcode for envOr_4Call { // string impl Cheatcode for envOr_5Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::String) } @@ -181,7 +193,7 @@ impl Cheatcode for envOr_5Call { // bytes impl Cheatcode for envOr_6Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bytes) } @@ -189,7 +201,7 @@ impl Cheatcode for envOr_6Call { // bool[] impl Cheatcode for envOr_7Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Bool) } @@ -197,7 +209,7 @@ impl Cheatcode for envOr_7Call { // uint256[] impl Cheatcode for envOr_8Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Uint(256)) } @@ -205,7 +217,7 @@ impl Cheatcode for envOr_8Call { // int256[] impl Cheatcode for envOr_9Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Int(256)) } @@ -213,7 +225,7 @@ impl Cheatcode for envOr_9Call { // address[] impl Cheatcode for envOr_10Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Address) } @@ -221,7 +233,7 @@ impl Cheatcode for envOr_10Call { // bytes32[] impl Cheatcode for envOr_11Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::FixedBytes(32)) } @@ -229,7 +241,7 @@ impl Cheatcode for envOr_11Call { // string[] impl Cheatcode for envOr_12Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::String) } @@ -237,15 +249,15 @@ impl Cheatcode for envOr_12Call { // bytes[] impl Cheatcode for envOr_13Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; - let default = defaultValue.to_vec(); + let default = defaultValue.clone(); env_array_default(name, delim, &default, &DynSolType::Bytes) } } impl Cheatcode for isContextCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { context } = self; Ok((FORGE_CONTEXT.get() == Some(context)).abi_encode()) } @@ -308,10 +320,14 @@ mod tests { fn parse_env_uint() { let key = "parse_env_uint"; let value = "t"; - env::set_var(key, value); + unsafe { + env::set_var(key, value); + } let err = env(key, &DynSolType::Uint(256)).unwrap_err().to_string(); assert_eq!(err.matches("$parse_env_uint").count(), 2, "{err:?}"); - env::remove_var(key); + unsafe { + env::remove_var(key); + } } } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index 414c7dbed8f90..fbdf2592891b8 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -1,5 +1,5 @@ use crate::Vm; -use alloy_primitives::{hex, Address, Bytes}; +use alloy_primitives::{Bytes, hex}; use alloy_signer::Error as SignerError; use alloy_signer_local::LocalSignerError; use alloy_sol_types::SolError; @@ -65,19 +65,6 @@ macro_rules! ensure { }; } -macro_rules! ensure_not_precompile { - ($address:expr, $ctxt:expr) => { - if $ctxt.is_precompile($address) { - return Err($crate::error::precompile_error($address)); - } - }; -} - -#[cold] -pub(crate) fn precompile_error(address: &Address) -> Error { - fmt_err!("cannot use precompile {address} as an argument") -} - /// Error thrown by cheatcodes. // This uses a custom repr to minimize the size of the error. // The repr is basically `enum { Cow<'static, str>, Cow<'static, [u8]> }` @@ -155,7 +142,6 @@ impl Error { } /// Returns the kind of this error. - #[inline] pub fn kind(&self) -> ErrorKind<'_> { let data = self.data(); if self.is_str { @@ -167,38 +153,31 @@ impl Error { } /// Returns the raw data of this error. - #[inline] - pub fn data(&self) -> &[u8] { + pub const fn data(&self) -> &[u8] { unsafe { &*self.data } } /// Returns `true` if this error is a human-readable string. - #[inline] - pub fn is_str(&self) -> bool { + pub const fn is_str(&self) -> bool { self.is_str } - #[inline] fn new_str(data: &'static str) -> Self { Self::_new(true, false, data.as_bytes()) } - #[inline] fn new_string(data: String) -> Self { Self::_new(true, true, Box::into_raw(data.into_boxed_str().into_boxed_bytes())) } - #[inline] fn new_bytes(data: &'static [u8]) -> Self { Self::_new(false, false, data) } - #[inline] fn new_vec(data: Vec) -> Self { Self::_new(false, true, Box::into_raw(data.into_boxed_slice())) } - #[inline] fn _new(is_str: bool, drop: bool, data: *const [u8]) -> Self { debug_assert!(!data.is_null()); Self { is_str, drop, data } @@ -262,7 +241,6 @@ impl From> for Error { } impl From for Error { - #[inline] fn from(value: Bytes) -> Self { Self::new_vec(value.into()) } @@ -284,7 +262,6 @@ impl_from!( alloy_dyn_abi::Error, alloy_primitives::SignatureError, alloy_consensus::crypto::RecoveryError, - eyre::Report, FsPathError, hex::FromHexError, BackendError, @@ -308,6 +285,12 @@ impl> From> for Error { } } +impl From for Error { + fn from(err: eyre::Report) -> Self { + Self::new_string(foundry_common::errors::display_chain(&err)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 573ba31f939e7..613a5199c8338 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,38 +1,58 @@ //! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{ - inspector::{Ecx, RecordDebugStepInfo}, BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result, - Vm::*, + Vm::*, inspector::RecordDebugStepInfo, }; -use alloy_consensus::TxEnvelope; +use alloy_consensus::transaction::SignerRecoverable; +use alloy_evm::FromRecoveredTx; use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; +use alloy_network::eip2718::EIP4844_TX_TYPE_ID; +use alloy_primitives::{ + Address, B256, U256, hex, keccak256, + map::{B256Map, HashMap}, +}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; -use foundry_common::fs::{read_json_file, write_json_file}; +use foundry_common::{ + TransactionMaybeSigned, + fs::{read_json_file, write_json_file}, + slot_identifier::{ + ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier, + SlotInfo, + }, +}; +use foundry_compilers::artifacts::EvmVersion; use foundry_evm_core::{ - backend::{DatabaseExt, RevertStateSnapshotAction}, + FoundryBlock, FoundryTransaction, + backend::{DatabaseError, DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, - ContextExt, + env::FoundryContextExt, + evm::{FoundryEvmNetwork, TxEnvFor, TxEnvelopeFor}, + utils::get_blob_base_fee_update_fraction_by_spec_id, }; -use foundry_evm_traces::StackSnapshotType; +use foundry_evm_traces::TraceMode; use itertools::Itertools; use rand::Rng; use revm::{ + Database, bytecode::Bytecode, - context::{Block, JournalTr}, - primitives::{hardfork::SpecId, KECCAK_EMPTY}, - state::Account, + context::{Block, Cfg, ContextTr, Host, JournalTr, Transaction, result::ExecutionResult}, + inspector::JournalExt, + primitives::{KECCAK_EMPTY, hardfork::SpecId}, + state::{Account, AccountStatus}, }; use std::{ - collections::{btree_map::Entry, BTreeMap}, + collections::{BTreeMap, HashSet, btree_map::Entry}, fmt::Display, path::Path, + str::FromStr, }; mod record_debug_step; -use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace}; +use foundry_common::fmt::format_token_raw; +use foundry_config::evm_spec_id; +use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace}; use serde::Serialize; mod fork; @@ -40,6 +60,18 @@ pub(crate) mod mapping; pub(crate) mod mock; pub(crate) mod prank; +/// JSON-serializable log entry for `getRecordedLogsJson`. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct LogJson { + /// The topics of the log, including the signature, if any. + topics: Vec, + /// The raw data of the log, hex-encoded with 0x prefix. + data: String, + /// The address of the log's emitter. + emitter: String, +} + /// Records storage slots reads and writes. #[derive(Clone, Debug, Default)] pub struct RecordAccess { @@ -102,6 +134,11 @@ struct SlotStateDiff { previous_value: B256, /// Current storage value. new_value: B256, + /// Storage layout metadata (variable name, type, offset). + /// Only present when contract has storage layout output. + /// This includes decoded values when available. + #[serde(skip_serializing_if = "Option::is_none", flatten)] + slot_info: Option, } /// Balance diff info. @@ -114,14 +151,28 @@ struct BalanceDiff { new_value: U256, } +/// Nonce diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct NonceDiff { + /// Initial nonce value. + previous_value: u64, + /// Current nonce value. + new_value: u64, +} + /// Account state diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] struct AccountStateDiffs { /// Address label, if any set. label: Option, + /// Contract identifier from artifact. e.g "src/Counter.sol:Counter" + contract: Option, /// Account balance changes. balance_diff: Option, + /// Account nonce changes. + nonce_diff: Option, /// State changes, per slot. state_diff: BTreeMap, } @@ -132,25 +183,62 @@ impl Display for AccountStateDiffs { if let Some(label) = &self.label { writeln!(f, "label: {label}")?; } + if let Some(contract) = &self.contract { + writeln!(f, "contract: {contract}")?; + } // Print balance diff if changed. - if let Some(balance_diff) = &self.balance_diff { - if balance_diff.previous_value != balance_diff.new_value { - writeln!( - f, - "- balance diff: {} → {}", - balance_diff.previous_value, balance_diff.new_value - )?; - } + if let Some(balance_diff) = &self.balance_diff + && balance_diff.previous_value != balance_diff.new_value + { + writeln!( + f, + "- balance diff: {} → {}", + balance_diff.previous_value, balance_diff.new_value + )?; + } + // Print nonce diff if changed. + if let Some(nonce_diff) = &self.nonce_diff + && nonce_diff.previous_value != nonce_diff.new_value + { + writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?; } // Print state diff if any. if !&self.state_diff.is_empty() { writeln!(f, "- state diff:")?; for (slot, slot_changes) in &self.state_diff { - writeln!( - f, - "@ {slot}: {} → {}", - slot_changes.previous_value, slot_changes.new_value - )?; + match &slot_changes.slot_info { + Some(slot_info) => { + if let Some(decoded) = &slot_info.decoded { + // Have slot info with decoded values - show decoded values + writeln!( + f, + "@ {slot} ({}, {}): {} → {}", + slot_info.label, + slot_info.slot_type.dyn_sol_type, + format_token_raw(&decoded.previous_value), + format_token_raw(&decoded.new_value) + )?; + } else { + // Have slot info but no decoded values - show raw hex values + writeln!( + f, + "@ {slot} ({}, {}): {} → {}", + slot_info.label, + slot_info.slot_type.dyn_sol_type, + slot_changes.previous_value, + slot_changes.new_value + )?; + } + } + None => { + // No slot info - show raw hex values + writeln!( + f, + "@ {slot}: {} → {}", + slot_changes.previous_value, slot_changes.new_value + )?; + } + } } } @@ -159,7 +247,7 @@ impl Display for AccountStateDiffs { } impl Cheatcode for addrCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = super::crypto::parse_wallet(privateKey)?; Ok(wallet.address().abi_encode()) @@ -167,25 +255,30 @@ impl Cheatcode for addrCall { } impl Cheatcode for getNonce_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; get_nonce(ccx, account) } } impl Cheatcode for getNonce_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { wallet } = self; get_nonce(ccx, &wallet.addr) } } impl Cheatcode for loadCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, slot } = *self; - ensure_not_precompile!(&target, ccx); - ccx.ecx.journaled_state.load_account(target)?; - let mut val = ccx.ecx.journaled_state.sload(target, slot.into())?; + ccx.ensure_not_precompile(&target)?; + + ccx.ecx.journal_mut().load_account(target)?; + let mut val = ccx + .ecx + .journal_mut() + .sload(target, slot.into()) + .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?; if val.is_cold && val.data.is_zero() { if ccx.state.has_arbitrary_storage(&target) { @@ -218,7 +311,7 @@ impl Cheatcode for loadCall { } impl Cheatcode for loadAllocsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); @@ -235,47 +328,47 @@ impl Cheatcode for loadAllocsCall { }; // Then, load the allocs into the database. - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - db.load_allocs(&allocs, journal) + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + db.load_allocs(&allocs, inner) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } } impl Cheatcode for cloneAccountCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { source, target } = self; - let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); - let account = journal.load_account(db, *source)?; - let genesis = &genesis_account(account.data); - db.clone_account(genesis, target, journal)?; + let account = ccx.ecx.journal_mut().load_account(*source)?; + let genesis = genesis_account(account.data); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + db.clone_account(&genesis, target, inner)?; // Cloned account should persist in forked envs. - ccx.ecx.journaled_state.database.add_persistent_account(*target); + ccx.ecx.db_mut().add_persistent_account(*target); Ok(Default::default()) } } impl Cheatcode for dumpStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); // Do not include system account or empty accounts in the dump. let skip = |key: &Address, val: &Account| { - key == &CHEATCODE_ADDRESS || - key == &CALLER || - key == &HARDHAT_CONSOLE_ADDRESS || - key == &TEST_CONTRACT_ADDRESS || - key == &ccx.caller || - key == &ccx.state.config.evm_opts.sender || - val.is_empty() + key == &CHEATCODE_ADDRESS + || key == &CALLER + || key == &HARDHAT_CONSOLE_ADDRESS + || key == &TEST_CONTRACT_ADDRESS + || key == &ccx.caller + || key == &ccx.state.config.evm_opts.sender + || val.is_empty() }; let alloc = ccx .ecx - .journaled_state - .state() + .journal_mut() + .evm_state_mut() .iter_mut() .filter(|(key, val)| !skip(key, val)) .map(|(key, val)| (key, genesis_account(val))) @@ -287,7 +380,7 @@ impl Cheatcode for dumpStateCall { } impl Cheatcode for recordCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recording_accesses = true; state.accesses.clear(); @@ -296,14 +389,14 @@ impl Cheatcode for recordCall { } impl Cheatcode for stopRecordCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { state.recording_accesses = false; Ok(Default::default()) } } impl Cheatcode for accessesCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target } = *self; let result = ( state.accesses.reads.entry(target).or_default().as_slice(), @@ -314,7 +407,7 @@ impl Cheatcode for accessesCall { } impl Cheatcode for recordLogsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_logs = Some(Default::default()); Ok(Default::default()) @@ -322,14 +415,30 @@ impl Cheatcode for recordLogsCall { } impl Cheatcode for getRecordedLogsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode()) } } +impl Cheatcode for getRecordedLogsJsonCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default(); + let json_logs: Vec<_> = logs + .into_iter() + .map(|log| LogJson { + topics: log.topics.iter().map(|t| format!("{t}")).collect(), + data: hex::encode_prefixed(&log.data), + emitter: format!("{}", log.emitter), + }) + .collect(); + Ok(serde_json::to_string(&json_logs)?.abi_encode()) + } +} + impl Cheatcode for pauseGasMeteringCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.paused = true; Ok(Default::default()) @@ -337,7 +446,7 @@ impl Cheatcode for pauseGasMeteringCall { } impl Cheatcode for resumeGasMeteringCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.resume(); Ok(Default::default()) @@ -345,7 +454,7 @@ impl Cheatcode for resumeGasMeteringCall { } impl Cheatcode for resetGasMeteringCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.reset(); Ok(Default::default()) @@ -353,7 +462,7 @@ impl Cheatcode for resetGasMeteringCall { } impl Cheatcode for lastCallGasCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let Some(last_call_gas) = &state.gas_metering.last_call_gas else { bail!("no external call was made yet"); @@ -362,160 +471,171 @@ impl Cheatcode for lastCallGasCall { } } +impl Cheatcode for getChainIdCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let Self {} = self; + Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode()) + } +} + impl Cheatcode for chainIdCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newChainId } = self; - ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); - ccx.ecx.cfg.chain_id = newChainId.to(); + ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64"); + ccx.ecx.cfg_mut().chain_id = newChainId.to(); Ok(Default::default()) } } impl Cheatcode for coinbaseCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newCoinbase } = self; - ccx.ecx.block.beneficiary = *newCoinbase; + ccx.ecx.block_mut().set_beneficiary(*newCoinbase); Ok(Default::default()) } } impl Cheatcode for difficultyCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.ecx.cfg.spec < SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() < SpecId::MERGE, "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.difficulty = *newDifficulty; + ccx.ecx.block_mut().set_difficulty(*newDifficulty); Ok(Default::default()) } } impl Cheatcode for feeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newBasefee } = self; - ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64 - 1"); - ccx.ecx.block.basefee = newBasefee.saturating_to(); + ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64"); + ccx.ecx.block_mut().set_basefee(newBasefee.saturating_to()); Ok(Default::default()) } } impl Cheatcode for prevrandao_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.prevrandao = Some(*newPrevrandao); + ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao)); Ok(Default::default()) } } impl Cheatcode for prevrandao_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::MERGE, + (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.block.prevrandao = Some((*newPrevrandao).into()); + ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into())); Ok(Default::default()) } } impl Cheatcode for blobhashesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { hashes } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`blobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - ccx.ecx.tx.blob_hashes.clone_from(hashes); + ccx.ecx.tx_mut().set_blob_hashes(hashes.clone()); + // force this as 4844 txtype + ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID); Ok(Default::default()) } } impl Cheatcode for getBlobhashesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`getBlobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode()) + Ok(ccx.ecx.tx().blob_versioned_hashes().to_vec().abi_encode()) } } impl Cheatcode for rollCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newHeight } = self; - ensure!(*newHeight <= U256::from(u64::MAX), "block height must be less than 2^64 - 1"); - ccx.ecx.block.number = newHeight.saturating_to(); + ccx.ecx.block_mut().set_number(*newHeight); Ok(Default::default()) } } impl Cheatcode for getBlockNumberCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.number.abi_encode()) + Ok(ccx.ecx.block().number().abi_encode()) } } impl Cheatcode for txGasPriceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newGasPrice } = self; - ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64 - 1"); - ccx.ecx.tx.gas_price = newGasPrice.saturating_to(); + ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64"); + ccx.ecx.tx_mut().set_gas_price(newGasPrice.saturating_to()); Ok(Default::default()) } } impl Cheatcode for warpCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newTimestamp } = self; - ensure!(*newTimestamp <= U256::from(u64::MAX), "timestamp must be less than 2^64 - 1"); - ccx.ecx.block.timestamp = newTimestamp.saturating_to(); + ccx.ecx.block_mut().set_timestamp(*newTimestamp); Ok(Default::default()) } } impl Cheatcode for getBlockTimestampCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.timestamp.abi_encode()) + Ok(ccx.ecx.block().timestamp().abi_encode()) } } impl Cheatcode for blobBaseFeeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { newBlobBaseFee } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`blobBaseFee` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - let is_prague = ccx.ecx.cfg.spec >= SpecId::PRAGUE; - ccx.ecx.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to(), is_prague); + + let spec: SpecId = (*ccx.ecx.cfg().spec()).into(); + ccx.ecx.block_mut().set_blob_excess_gas_and_price( + (*newBlobBaseFee).to(), + get_blob_base_fee_update_fraction_by_spec_id(spec), + ); Ok(Default::default()) } } impl Cheatcode for getBlobBaseFeeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode()) + Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode()) } } impl Cheatcode for dealCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account: address, newBalance: new_balance } = *self; let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); @@ -526,19 +646,19 @@ impl Cheatcode for dealCall { } impl Cheatcode for etchCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, newRuntimeBytecode } = self; - ensure_not_precompile!(target, ccx); - ccx.ecx.journaled_state.load_account(*target)?; - let bytecode = Bytecode::new_raw_checked(Bytes::copy_from_slice(newRuntimeBytecode)) + ccx.ensure_not_precompile(target)?; + ccx.ecx.journal_mut().load_account(*target)?; + let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone()) .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?; - ccx.ecx.journaled_state.set_code(*target, bytecode); + ccx.ecx.journal_mut().set_code(*target, bytecode); Ok(Default::default()) } } impl Cheatcode for resetNonceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces @@ -553,7 +673,7 @@ impl Cheatcode for resetNonceCall { } impl Cheatcode for setNonceCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; // nonce must increment only @@ -569,7 +689,7 @@ impl Cheatcode for setNonceCall { } impl Cheatcode for setNonceUnsafeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; @@ -578,20 +698,22 @@ impl Cheatcode for setNonceUnsafeCall { } impl Cheatcode for storeCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, slot, value } = *self; - ensure_not_precompile!(&target, ccx); - // ensure the account is touched - let _ = journaled_account(ccx.ecx, target)?; - ccx.ecx.journaled_state.sstore(target, slot.into(), value.into())?; + ccx.ensure_not_precompile(&target)?; + ensure_loaded_account(ccx.ecx, target)?; + ccx.ecx + .journal_mut() + .sstore(target, slot.into(), value.into()) + .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?; Ok(Default::default()) } } impl Cheatcode for coolCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target } = self; - if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { + if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) { account.unmark_touch(); account.storage.values_mut().for_each(|slot| slot.mark_cold()); } @@ -600,7 +722,7 @@ impl Cheatcode for coolCall { } impl Cheatcode for accessListCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { access } = self; let access_list = access .iter() @@ -615,7 +737,7 @@ impl Cheatcode for accessListCall { } impl Cheatcode for noAccessListCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; // Set to empty option in order to override previous applied access list. if state.access_list.is_some() { @@ -626,7 +748,7 @@ impl Cheatcode for noAccessListCall { } impl Cheatcode for warmSlotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), false); Ok(Default::default()) @@ -634,7 +756,7 @@ impl Cheatcode for warmSlotCall { } impl Cheatcode for coolSlotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), true); Ok(Default::default()) @@ -642,28 +764,28 @@ impl Cheatcode for coolSlotCall { } impl Cheatcode for readCallersCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth()) + read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth()) } } impl Cheatcode for snapshotValue_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { name, value } = self; inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) } } impl Cheatcode for snapshotValue_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { group, name, value } = self; inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } impl Cheatcode for snapshotGasLastCall_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { name } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); @@ -673,7 +795,7 @@ impl Cheatcode for snapshotGasLastCall_0Call { } impl Cheatcode for snapshotGasLastCall_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { name, group } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); @@ -688,35 +810,35 @@ impl Cheatcode for snapshotGasLastCall_1Call { } impl Cheatcode for startSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { name } = self; inner_start_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for startSnapshotGas_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { group, name } = self; inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; inner_stop_gas_snapshot(ccx, None, None) } } impl Cheatcode for stopSnapshotGas_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { name } = self; inner_stop_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { group, name } = self; inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } @@ -724,14 +846,14 @@ impl Cheatcode for stopSnapshotGas_2Call { // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } impl Cheatcode for snapshotStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } @@ -739,14 +861,14 @@ impl Cheatcode for snapshotStateCall { // Deprecated in favor of `revertToStateCall` impl Cheatcode for revertToCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } impl Cheatcode for revertToStateCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } @@ -754,14 +876,14 @@ impl Cheatcode for revertToStateCall { // Deprecated in favor of `revertToStateAndDeleteCall` impl Cheatcode for revertToAndDeleteCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } impl Cheatcode for revertToStateAndDeleteCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } @@ -769,53 +891,59 @@ impl Cheatcode for revertToStateAndDeleteCall { // Deprecated in favor of `deleteStateSnapshotCall` impl Cheatcode for deleteSnapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; - inner_delete_state_snapshot(ccx, *snapshotId) + let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId); + Ok(result.abi_encode()) } } impl Cheatcode for deleteStateSnapshotCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { snapshotId } = self; - inner_delete_state_snapshot(ccx, *snapshotId) + let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId); + Ok(result.abi_encode()) } } // Deprecated in favor of `deleteStateSnapshotsCall` impl Cheatcode for deleteSnapshotsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - inner_delete_state_snapshots(ccx) + ccx.ecx.db_mut().delete_state_snapshots(); + Ok(Default::default()) } } impl Cheatcode for deleteStateSnapshotsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - inner_delete_state_snapshots(ccx) + ccx.ecx.db_mut().delete_state_snapshots(); + Ok(Default::default()) } } impl Cheatcode for startStateDiffRecordingCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_account_diffs_stack = Some(Default::default()); + // Enable mapping recording to track mapping slot accesses + state.mapping_slots.get_or_insert_default(); Ok(Default::default()) } } impl Cheatcode for stopAndReturnStateDiffCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; get_state_diff(state) } } impl Cheatcode for getStateDiffCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let mut diffs = String::new(); - let state_diffs = get_recorded_state_diffs(state); + let state_diffs = get_recorded_state_diffs(ccx); for (address, state_diffs) in state_diffs { diffs.push_str(&format!("{address}\n")); diffs.push_str(&format!("{state_diffs}\n")); @@ -825,29 +953,124 @@ impl Cheatcode for getStateDiffCall { } impl Cheatcode for getStateDiffJsonCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { - let state_diffs = get_recorded_state_diffs(state); + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let state_diffs = get_recorded_state_diffs(ccx); Ok(serde_json::to_string(&state_diffs)?.abi_encode()) } } +impl Cheatcode for getStorageSlotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let Self { target, variableName } = self; + + let storage_layout = get_contract_data(ccx, *target) + .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone())) + .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?; + + trace!(storage = ?storage_layout.storage, "fetched storage"); + + let variable_name_lower = variableName.to_lowercase(); + let storage = storage_layout + .storage + .iter() + .find(|s| s.label.to_lowercase() == variable_name_lower) + .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?; + + let storage_type = storage_layout + .types + .get(&storage.storage_type) + .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?; + + if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY + { + return Err(fmt_err!( + "cannot get storage slots for variables with mapping or dynamic array types" + )); + } + + let slot = U256::from_str(&storage.slot).map_err(|_| { + fmt_err!("invalid slot {} format for variable {variableName}", storage.slot) + })?; + + let mut slots = Vec::new(); + + // Always push the base slot + slots.push(slot); + + if storage_type.encoding == ENCODING_INPLACE { + // For inplace encoding, calculate the number of slots needed + let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| { + fmt_err!( + "invalid number_of_bytes {} for variable {variableName}", + storage_type.number_of_bytes + ) + })?; + let num_slots = num_bytes.div_ceil(U256::from(32)); + + // Start from 1 since base slot is already added + for i in 1..num_slots.to::() { + slots.push(slot + U256::from(i)); + } + } + + if storage_type.encoding == ENCODING_BYTES { + // Try to check if it's a long bytes/string by reading the current storage + // value + if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) { + let value_bytes = value.data.to_be_bytes::<32>(); + let length_byte = value_bytes[31]; + // Check if it's a long bytes/string (LSB is 1) + if length_byte & 1 == 1 { + // Calculate data slots for long bytes/string + let length: U256 = value.data >> 1; + let num_data_slots = length.to::().div_ceil(32); + let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0); + + for i in 0..num_data_slots { + slots.push(data_start + U256::from(i)); + } + } + } + } + + Ok(slots.abi_encode()) + } +} + +impl Cheatcode for getStorageAccessesCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let mut storage_accesses = Vec::new(); + + if let Some(recorded_diffs) = &state.recorded_account_diffs_stack { + for account_accesses in recorded_diffs.iter().flatten() { + storage_accesses.extend(account_accesses.storageAccesses.clone()); + } + } + + Ok(storage_accesses.abi_encode()) + } +} + impl Cheatcode for broadcastRawTransactionCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { - let tx = TxEnvelope::decode(&mut self.data.as_ref()) + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let tx = TxEnvelopeFor::::decode(&mut self.data.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; - let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); - db.transact_from_tx( - &tx.clone().into(), - env.to_owned(), - journal, - &mut *executor.get_inspector(ccx.state), - )?; + let sender = + tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?; + let tx_env = TxEnvFor::::from_recovered_tx(&tx, sender); + let from = sender; + + executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?; if ccx.state.broadcast.is_some() { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ccx.ecx.journaled_state.database.active_fork_url(), - transaction: tx.try_into()?, + rpc: ccx.ecx.db().active_fork_url(), + transaction: TransactionMaybeSigned::Signed { tx, from }, }); } @@ -856,26 +1079,177 @@ impl Cheatcode for broadcastRawTransactionCall { } impl Cheatcode for setBlockhashCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { blockNumber, blockHash } = *self; - ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64 - 1"); + ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64"); ensure!( - blockNumber <= U256::from(ccx.ecx.block.number), + blockNumber <= U256::from(ccx.ecx.block().number()), "block number must be less than or equal to the current block number" ); - ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash); + ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash); Ok(Default::default()) } } +impl Cheatcode for executeTransactionCall { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + use crate::env::FORGE_CONTEXT; + + // Block in script contexts. + if let Some(ctx) = FORGE_CONTEXT.get() + && *ctx == ForgeContext::ScriptGroup + { + return Err(fmt_err!("executeTransaction is not allowed in forge script")); + } + + // Decode the RLP-encoded signed transaction. + let tx = TxEnvelopeFor::::decode(&mut self.rawTx.as_ref()) + .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; + + // Build TxEnv from the recovered transaction. + let sender = + tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?; + let tx_env = TxEnvFor::::from_recovered_tx(&tx, sender); + + // Save current env for restoration after execution. + let cached_evm_env = ccx.ecx.evm_clone(); + let cached_tx_env = ccx.ecx.tx_clone(); + + // Override env for isolated execution. + ccx.ecx.block_mut().set_basefee(0); + ccx.ecx.set_tx(tx_env); + ccx.ecx.tx_mut().set_gas_price(0); + ccx.ecx.tx_mut().set_gas_priority_fee(None); + + // Enable nonce checks for realistic simulation. + ccx.ecx.cfg_mut().disable_nonce_check = false; + + // EIP-3860: enforce initcode size limit. + ccx.ecx.cfg_mut().limit_contract_initcode_size = + Some(revm::primitives::eip3860::MAX_INITCODE_SIZE); + + // Reset the tx gas limit cap so revm applies the spec-defined default (EIP-7825). + // Normal test execution sets `Some(u64::MAX)` to disable the cap; clearing it here + // lets the nested EVM enforce the real network limit for realistic simulation. + ccx.ecx.cfg_mut().tx_gas_limit_cap = None; + + // Snapshot the modified env for EVM construction. + let modified_evm_env = ccx.ecx.evm_clone(); + let modified_tx_env = ccx.ecx.tx_clone(); + + // Mark as inner context so isolation mode doesn't trigger a nested transact_inner + // when the inner EVM executes calls at depth == 1. + executor.set_in_inner_context(true, Some(sender)); + + // Clone journaled state and mark all accounts/slots cold. + let cold_state = { + let (_, journal) = ccx.ecx.db_journal_inner_mut(); + let mut state = journal.state.clone(); + for (addr, acc_mut) in &mut state { + if journal.warm_addresses.is_cold(addr) { + acc_mut.mark_cold(); + } + for slot_mut in acc_mut.storage.values_mut() { + slot_mut.is_cold = true; + slot_mut.original_value = slot_mut.present_value; + } + } + state + }; + + let mut res = None; + let mut cold_state = Some(cold_state); + let mut nested_evm_env = { + let (db, _) = ccx.ecx.db_journal_inner_mut(); + executor.with_fresh_nested_evm(ccx.state, db, modified_evm_env, &mut |evm| { + // SAFETY: closure is called exactly once by the executor. + evm.journal_inner_mut().state = cold_state.take().expect("called once"); + // Set depth to 1 for proper trace collection. + evm.journal_inner_mut().depth = 1; + res = Some(evm.transact_raw(modified_tx_env.clone())); + Ok(()) + })? + }; + let res = res.unwrap(); + + // Restore env, preserving cheatcode cfg/block changes from the nested EVM + // but restoring the original tx and basefee (which we zeroed for the nested call) + // as well as cfg overrides that were applied only for the nested execution. + nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee()); + nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check; + nested_evm_env.cfg_env.limit_contract_initcode_size = + cached_evm_env.cfg_env.limit_contract_initcode_size; + nested_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap; + ccx.ecx.set_evm(nested_evm_env); + ccx.ecx.set_tx(cached_tx_env); + + // Reset inner context flag. + executor.set_in_inner_context(false, None); + + let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?; + + // Merge state changes back into the parent journaled state. + for (addr, mut acc) in res.state { + let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else { + ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc); + continue; + }; + + // Preserve warm account status from parent context. + if acc.status.contains(AccountStatus::Cold) + && !acc_mut.status.contains(AccountStatus::Cold) + { + acc.status -= AccountStatus::Cold; + } + acc_mut.info = acc.info; + acc_mut.status |= acc.status; + + // Merge storage changes. + for (key, val) in acc.storage { + let Some(slot_mut) = acc_mut.storage.get_mut(&key) else { + acc_mut.storage.insert(key, val); + continue; + }; + slot_mut.present_value = val.present_value; + slot_mut.is_cold &= val.is_cold; + } + } + + // Return output bytes. + let output = match res.result { + ExecutionResult::Success { output, .. } => output.into_data(), + ExecutionResult::Halt { reason, .. } => { + return Err(fmt_err!("transaction halted: {reason:?}")); + } + ExecutionResult::Revert { output, .. } => { + return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output))); + } + }; + + Ok(output.abi_encode()) + } +} + impl Cheatcode for startDebugTraceRecordingCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { - let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { - return Err(Error::from("no tracer initiated, consider adding -vvv flag")) + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let Some(tracer) = executor.tracing_inspector() else { + return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; + if ccx.state.record_debug_steps_info.is_some() { + bail!("debug trace recording was already started"); + } + let mut info = RecordDebugStepInfo { // will be updated later start_node_idx: 0, @@ -883,13 +1257,8 @@ impl Cheatcode for startDebugTraceRecordingCall { original_tracer_config: *tracer.config(), }; - // turn on tracer configuration for recording - tracer.update_config(|config| { - config - .set_steps(true) - .set_memory_snapshots(true) - .set_stack_snapshots(StackSnapshotType::Full) - }); + // turn on tracer debug configuration for recording + *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None"); // track where the recording starts if let Some(last_node) = tracer.traces().nodes().last() { @@ -902,13 +1271,17 @@ impl Cheatcode for startDebugTraceRecordingCall { } impl Cheatcode for stopAndReturnDebugTraceRecordingCall { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { - let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { - return Err(Error::from("no tracer initiated, consider adding -vvv flag")) + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let Some(tracer) = executor.tracing_inspector() else { + return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; let Some(record_info) = ccx.state.record_debug_steps_info else { - return Err(Error::from("nothing recorded")) + return Err(Error::from("nothing recorded")); }; // Use the trace nodes to flatten the call trace @@ -916,7 +1289,7 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { let steps = flatten_call_trace(0, root, record_info.start_node_idx); let debug_steps: Vec = - steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect(); + steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect(); // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage. if !record_info.original_tracer_config.record_steps { tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| { @@ -936,57 +1309,86 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { } } -pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { - let account = ccx.ecx.journaled_state.load_account(*address)?; - Ok(account.info.nonce.abi_encode()) +impl Cheatcode for setEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let Self { evm } = self; + let spec_id = evm_spec_id( + EvmVersion::from_str(evm) + .map_err(|_| Error::from(format!("invalid evm version {evm}")))?, + ); + ccx.state.execution_evm_version = Some(spec_id); + Ok(Default::default()) + } } -fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - Ok(db.snapshot_state(journal, &mut env).abi_encode()) +impl Cheatcode for getEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let spec = (*ccx.ecx.cfg().spec()).into(); + Ok(spec.to_string().to_lowercase().abi_encode()) + } } -fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let result = if let Some(journaled_state) = - db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep) - { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state.inner = journaled_state; - true - } else { - false - }; - Ok(result.abi_encode()) +pub(super) fn get_nonce( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + address: &Address, +) -> Result { + let account = ccx.ecx.journal_mut().load_account(*address)?; + Ok(account.data.info.nonce.abi_encode()) } -fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - - let result = if let Some(journaled_state) = - db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove) - { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state.inner = journaled_state; - true - } else { - false - }; - Ok(result.abi_encode()) +fn inner_snapshot_state(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let evm_env = ccx.ecx.evm_clone(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + let id = db.snapshot_state(inner, &evm_env); + Ok(id.abi_encode()) } -fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id); - Ok(result.abi_encode()) +fn inner_revert_to_state( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + snapshot_id: U256, +) -> Result { + let mut evm_env = ccx.ecx.evm_clone(); + let caller = ccx.ecx.caller(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + if let Some(restored) = db.revert_state( + snapshot_id, + inner, + &mut evm_env, + caller, + RevertStateSnapshotAction::RevertKeep, + ) { + *inner = restored; + ccx.ecx.set_evm(evm_env); + Ok(true.abi_encode()) + } else { + Ok(false.abi_encode()) + } } -fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { - ccx.ecx.journaled_state.database.delete_state_snapshots(); - Ok(Default::default()) +fn inner_revert_to_state_and_delete( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + snapshot_id: U256, +) -> Result { + let mut evm_env = ccx.ecx.evm_clone(); + let caller = ccx.ecx.caller(); + let (db, inner) = ccx.ecx.db_journal_inner_mut(); + if let Some(restored) = db.revert_state( + snapshot_id, + inner, + &mut evm_env, + caller, + RevertStateSnapshotAction::RevertRemove, + ) { + *inner = restored; + ccx.ecx.set_evm(evm_env); + Ok(true.abi_encode()) + } else { + Ok(false.abi_encode()) + } } -fn inner_value_snapshot( - ccx: &mut CheatsCtxt, +fn inner_value_snapshot( + ccx: &mut CheatsCtxt<'_, '_, FEN>, group: Option, name: Option, value: String, @@ -998,8 +1400,8 @@ fn inner_value_snapshot( Ok(Default::default()) } -fn inner_last_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_last_gas_snapshot( + ccx: &mut CheatsCtxt<'_, '_, FEN>, group: Option, name: Option, value: u64, @@ -1011,14 +1413,13 @@ fn inner_last_gas_snapshot( Ok(value.abi_encode()) } -fn inner_start_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_start_gas_snapshot( + ccx: &mut CheatsCtxt<'_, '_, FEN>, group: Option, name: Option, ) -> Result { // Revert if there is an active gas snapshot as we can only have one active snapshot at a time. - if ccx.state.gas_metering.active_gas_snapshot.is_some() { - let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); + if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot { bail!("gas snapshot was already started with group: {group} and name: {name}"); } @@ -1028,7 +1429,7 @@ fn inner_start_gas_snapshot( group: group.clone(), name: name.clone(), gas_used: 0, - depth: ccx.ecx.journaled_state.depth(), + depth: ccx.ecx.journal().depth(), }); ccx.state.gas_metering.active_gas_snapshot = Some((group, name)); @@ -1038,16 +1439,16 @@ fn inner_start_gas_snapshot( Ok(Default::default()) } -fn inner_stop_gas_snapshot( - ccx: &mut CheatsCtxt, +fn inner_stop_gas_snapshot( + ccx: &mut CheatsCtxt<'_, '_, FEN>, group: Option, name: Option, ) -> Result { // If group and name are not provided, use the last snapshot group and name. - let (group, name) = group.zip(name).unwrap_or_else(|| { - let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); - (group, name) - }); + let (group, name) = group + .zip(name) + .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone()) + .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?; if let Some(record) = ccx .state @@ -1076,10 +1477,11 @@ fn inner_stop_gas_snapshot( .retain(|record| record.group != group && record.name != name); // Clear last snapshot cache if we have an exact match. - if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot { - if snapshot_group == &group && snapshot_name == &name { - ccx.state.gas_metering.active_gas_snapshot = None; - } + if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot + && snapshot_group == &group + && snapshot_name == &name + { + ccx.state.gas_metering.active_gas_snapshot = None; } Ok(value.abi_encode()) @@ -1089,8 +1491,8 @@ fn inner_stop_gas_snapshot( } // Derives the snapshot group and name from the provided group and name or the running contract. -fn derive_snapshot_name( - ccx: &CheatsCtxt, +fn derive_snapshot_name( + ccx: &CheatsCtxt<'_, '_, FEN>, group: Option, name: Option, ) -> (String, String) { @@ -1124,7 +1526,11 @@ fn derive_snapshot_name( /// - If no caller modification is active: /// - caller_mode will be equal to [CallerMode::None], /// - `msg.sender` and `tx.origin` will be equal to the default sender address. -fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result { +fn read_callers( + state: &Cheatcodes, + default_sender: &Address, + call_depth: usize, +) -> Result { let mut mode = CallerMode::None; let mut new_caller = default_sender; let mut new_origin = default_sender; @@ -1148,13 +1554,23 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<'a>( - ecx: Ecx<'a, '_, '_>, +pub(super) fn journaled_account< + CTX: ContextTr, Journal: JournalExt>, +>( + ecx: &mut CTX, addr: Address, -) -> Result<&'a mut Account> { - ecx.journaled_state.load_account(addr)?; - ecx.journaled_state.touch(addr); - Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) +) -> Result<&mut Account> { + ensure_loaded_account(ecx, addr)?; + Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded")) +} + +pub(super) fn ensure_loaded_account>>( + ecx: &mut CTX, + addr: Address, +) -> Result<()> { + ecx.journal_mut().load_account(addr)?; + ecx.journal_mut().touch_account(addr); + Ok(()) } /// Consumes recorded account accesses and returns them as an abi encoded @@ -1164,7 +1580,7 @@ pub(super) fn journaled_account<'a>( /// In the case where `stopAndReturnStateDiff` is called at a lower /// depth than `startStateDiffRecording`, multiple `Vec` /// will be flattened, preserving the order of the accesses. -fn get_state_diff(state: &mut Cheatcodes) -> Result { +fn get_state_diff(state: &mut Cheatcodes) -> Result { let res = state .recorded_account_diffs_stack .replace(Default::default()) @@ -1193,15 +1609,51 @@ fn genesis_account(account: &Account) -> GenesisAccount { } /// Helper function to returns state diffs recorded for each changed account. -fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap { +fn get_recorded_state_diffs( + ccx: &mut CheatsCtxt<'_, '_, FEN>, +) -> BTreeMap { let mut state_diffs: BTreeMap = BTreeMap::default(); - if let Some(records) = &state.recorded_account_diffs_stack { + + // First, collect all unique addresses we need to look up + let mut addresses_to_lookup = HashSet::new(); + if let Some(records) = &ccx.state.recorded_account_diffs_stack { + for account_access in records.iter().flatten() { + if !account_access.storageAccesses.is_empty() + || account_access.oldBalance != account_access.newBalance + { + addresses_to_lookup.insert(account_access.account); + for storage_access in &account_access.storageAccesses { + if storage_access.isWrite && !storage_access.reverted { + addresses_to_lookup.insert(storage_access.account); + } + } + } + } + } + + // Look up contract names and storage layouts for all addresses + let mut contract_names = HashMap::new(); + let mut storage_layouts = HashMap::new(); + for address in addresses_to_lookup { + if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) { + contract_names.insert(address, artifact_id.identifier()); + + // Also get storage layout if available + if let Some(storage_layout) = &contract_data.storage_layout { + storage_layouts.insert(address, storage_layout.clone()); + } + } + } + + // Now process the records + if let Some(records) = &ccx.state.recorded_account_diffs_stack { records .iter() .flatten() .filter(|account_access| { - !account_access.storageAccesses.is_empty() || - account_access.oldBalance != account_access.newBalance + !account_access.storageAccesses.is_empty() + || account_access.oldBalance != account_access.newBalance + || account_access.oldNonce != account_access.newNonce }) .for_each(|account_access| { // Record account balance diffs. @@ -1209,7 +1661,8 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap BTreeMap>(); + // Record account state diffs. for storage_access in &account_access.storageAccesses { if storage_access.isWrite && !storage_access.reverted { let account_diff = state_diffs .entry(storage_access.account) .or_insert_with(|| AccountStateDiffs { - label: state.labels.get(&storage_access.account).cloned(), + label: ccx.state.labels.get(&storage_access.account).cloned(), + contract: contract_names.get(&storage_access.account).cloned(), ..Default::default() }); + let layout = storage_layouts.get(&storage_access.account); // Update state diff. Do not overwrite the initial value if already set. - match account_diff.state_diff.entry(storage_access.slot) { + let entry = match account_diff.state_diff.entry(storage_access.slot) { Entry::Vacant(slot_state_diff) => { + // Get storage layout info for this slot + // Include mapping slots if available for the account + let mapping_slots = ccx + .state + .mapping_slots + .as_ref() + .and_then(|slots| slots.get(&storage_access.account)); + + let slot_info = layout.and_then(|layout| { + let decoder = SlotIdentifier::new(layout.clone()); + decoder.identify(&storage_access.slot, mapping_slots).or_else( + || { + // Create a map of new values for bytes/string + // identification. These values are used to determine + // the length of the data which helps determine how many + // slots to search + let current_base_slot_values = raw_changes_by_slot + .iter() + .map(|(slot, (_, new_val))| (*slot, *new_val)) + .collect::>(); + decoder.identify_bytes_or_string( + &storage_access.slot, + ¤t_base_slot_values, + ) + }, + ) + }); + slot_state_diff.insert(SlotStateDiff { previous_value: storage_access.previousValue, new_value: storage_access.newValue, - }); + slot_info, + }) + } + Entry::Occupied(slot_state_diff) => { + let entry = slot_state_diff.into_mut(); + entry.new_value = storage_access.newValue; + entry } - Entry::Occupied(mut slot_state_diff) => { - slot_state_diff.get_mut().new_value = storage_access.newValue; + }; + + // Update decoded values if we have slot info + if let Some(slot_info) = &mut entry.slot_info { + slot_info.decode_values(entry.previous_value, storage_access.newValue); + if slot_info.is_bytes_or_string() { + slot_info.decode_bytes_or_string_values( + &storage_access.slot, + &raw_changes_by_slot, + ); } } } @@ -1252,11 +1780,66 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap( + ccx: &'a mut CheatsCtxt<'_, '_, FEN>, + address: Address, +) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> { + // Check if we have available artifacts to match against + let artifacts = ccx.state.config.available_artifacts.as_ref()?; + + // Try to load the account and get its code + let account = ccx.ecx.journal_mut().load_account(address).ok()?; + let code = account.data.info.code.as_ref()?; + + // Skip if code is empty + if code.is_empty() { + return None; + } + + // Try to find the artifact by deployed code + let code_bytes = code.original_bytes(); + // First check for proxy patterns + let hex_str = hex::encode(&code_bytes); + let find_by_suffix = + |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix)); + // Simple proxy detection based on storage slot patterns + if hex_str.contains(EIP1967_IMPL_SLOT) + && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy") + { + return Some(result); + } else if hex_str.contains(EIP1822_PROXIABLE_SLOT) + && let Some(result) = find_by_suffix(":UUPSUpgradeable") + { + return Some(result); + } + + // Try exact match + if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) { + return Some(result); + } + + // Fallback to fuzzy matching if exact match fails + artifacts.find_by_deployed_code(&code_bytes) +} + /// Helper function to set / unset cold storage slot of the target address. -fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) { - if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target) { - if let Some(storage_slot) = account.storage.get_mut(&slot) { - storage_slot.is_cold = cold; - } +fn set_cold_slot( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + target: Address, + slot: U256, + cold: bool, +) { + if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target) + && let Some(storage_slot) = account.storage.get_mut(&slot) + { + storage_slot.is_cold = cold; } } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 65a7a04d401cc..3fa95c2a3a298 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,21 +1,25 @@ use crate::{ - json::json_value_to_token, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, - Result, Vm::*, + Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, Result, Vm::*, + json::json_value_to_token, }; use alloy_dyn_abi::DynSolValue; +use alloy_evm::EvmEnv; +use alloy_network::AnyNetwork; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; use foundry_common::provider::ProviderBuilder; -use foundry_evm_core::{fork::CreateFork, AsEnvMut, ContextExt}; +use foundry_evm_core::{ + FoundryContextExt, backend::JournaledState, evm::FoundryEvmNetwork, fork::CreateFork, +}; +use revm::context::ContextTr; impl Cheatcode for activeForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; ccx.ecx - .journaled_state - .database + .db() .active_fork_id() .map(|id| id.abi_encode()) .ok_or_else(|| fmt_err!("no active fork")) @@ -23,197 +27,201 @@ impl Cheatcode for activeForkCall { } impl Cheatcode for createFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { blockNumber } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork(None, (*blockNumber).to(), &mut env, journal)?; - Ok(Default::default()) + fork_env_op(ccx.ecx, |db, evm_env, _, inner| { + db.roll_fork(None, (*blockNumber).to(), evm_env, inner) + }) } } impl Cheatcode for rollFork_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { txHash } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork_to_transaction(None, *txHash, &mut env, journal)?; - Ok(Default::default()) + fork_env_op(ccx.ecx, |db, evm_env, _, inner| { + db.roll_fork_to_transaction(None, *txHash, evm_env, inner) + }) } } impl Cheatcode for rollFork_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut env, journal)?; - Ok(Default::default()) + fork_env_op(ccx.ecx, |db, evm_env, _, inner| { + db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, inner) + }) } } impl Cheatcode for rollFork_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut env, journal)?; - Ok(Default::default()) + fork_env_op(ccx.ecx, |db, evm_env, _, inner| { + db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, inner) + }) } } impl Cheatcode for selectForkCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - db.select_fork(*forkId, &mut env, journal)?; - Ok(Default::default()) + fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { + db.select_fork(*forkId, evm_env, tx_env, inner) + }) } } impl Cheatcode for transact_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { txHash } = *self; transact(ccx, executor, txHash, None) } } impl Cheatcode for transact_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { forkId, txHash } = *self; transact(ccx, executor, txHash, Some(forkId)) } } impl Cheatcode for allowCheatcodesCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.allow_cheatcode_access(*account); + ccx.ecx.db_mut().allow_cheatcode_access(*account); Ok(Default::default()) } } impl Cheatcode for makePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account); + ccx.ecx.db_mut().add_persistent_account(*account); Ok(Default::default()) } } impl Cheatcode for makePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account0, account1 } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account0); - ccx.ecx.journaled_state.database.add_persistent_account(*account1); + ccx.ecx.db_mut().add_persistent_account(*account0); + ccx.ecx.db_mut().add_persistent_account(*account1); Ok(Default::default()) } } impl Cheatcode for makePersistent_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account0, account1, account2 } = self; - ccx.ecx.journaled_state.database.add_persistent_account(*account0); - ccx.ecx.journaled_state.database.add_persistent_account(*account1); - ccx.ecx.journaled_state.database.add_persistent_account(*account2); + ccx.ecx.db_mut().add_persistent_account(*account0); + ccx.ecx.db_mut().add_persistent_account(*account1); + ccx.ecx.db_mut().add_persistent_account(*account2); Ok(Default::default()) } } impl Cheatcode for makePersistent_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.journaled_state.database.add_persistent_account(*account); + ccx.ecx.db_mut().add_persistent_account(*account); } Ok(Default::default()) } } impl Cheatcode for revokePersistent_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; - ccx.ecx.journaled_state.database.remove_persistent_account(account); + ccx.ecx.db_mut().remove_persistent_account(account); Ok(Default::default()) } } impl Cheatcode for revokePersistent_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.journaled_state.database.remove_persistent_account(account); + ccx.ecx.db_mut().remove_persistent_account(account); } Ok(Default::default()) } } impl Cheatcode for isPersistentCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { account } = self; - Ok(ccx.ecx.journaled_state.database.is_persistent(account).abi_encode()) + Ok(ccx.ecx.db().is_persistent(account).abi_encode()) } } impl Cheatcode for rpc_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { method, params } = self; - let url = ccx - .ecx - .journaled_state - .database - .active_fork_url() - .ok_or_else(|| fmt_err!("no active fork URL found"))?; + let url = + ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; rpc_call(&url, method, params) } } impl Cheatcode for rpc_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { urlOrAlias, method, params } = self; let url = state.config.rpc_endpoint(urlOrAlias)?.url()?; rpc_call(&url, method, params) @@ -221,24 +229,20 @@ impl Cheatcode for rpc_1Call { } impl Cheatcode for eth_getLogsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { - bail!("blocks in block range must be less than 2^64 - 1") + bail!("blocks in block range must be less than 2^64") }; if topics.len() > 4 { bail!("topics array must contain at most 4 elements") } - let url = ccx - .ecx - .journaled_state - .database - .active_fork_url() - .ok_or_else(|| fmt_err!("no active fork URL found"))?; - let provider = ProviderBuilder::new(&url).build()?; + let url = + ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + let provider = ProviderBuilder::::new(&url).build()?; let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); for (i, &topic) in topics.iter().enumerate() { filter.topics[i] = topic.into(); @@ -266,51 +270,81 @@ impl Cheatcode for eth_getLogsCall { } } +impl Cheatcode for getRawBlockHeaderCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let Self { blockNumber } = self; + let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?; + let provider = ProviderBuilder::::new(&url).build()?; + let block_number = u64::try_from(blockNumber) + .map_err(|_| fmt_err!("block number must be less than 2^64"))?; + let block = + foundry_common::block_on(async move { provider.get_block(block_number.into()).await }) + .map_err(|e| fmt_err!("failed to get block: {e}"))? + .ok_or_else(|| fmt_err!("block {block_number} not found"))?; + + let header: alloy_consensus::Header = block + .into_inner() + .header + .inner + .try_into_header() + .map_err(|e| fmt_err!("failed to convert to header: {e}"))?; + Ok(alloy_rlp::encode(&header).abi_encode()) + } +} + /// Creates and then also selects the new fork -fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { +fn create_select_fork( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + url_or_alias: &str, + block: Option, +) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let id = db.create_select_fork(fork, &mut env, journal)?; - Ok(id.abi_encode()) + fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { + db.create_select_fork(fork, evm_env, tx_env, inner) + }) } /// Creates a new fork -fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { +fn create_fork( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + url_or_alias: &str, + block: Option, +) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.ecx.journaled_state.database.create_fork(fork)?; + let id = ccx.ecx.db_mut().create_fork(fork)?; Ok(id.abi_encode()) } /// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction( - ccx: &mut CheatsCtxt, +fn create_select_fork_at_transaction( + ccx: &mut CheatsCtxt<'_, '_, FEN>, url_or_alias: &str, transaction: &B256, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; - let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); - let id = db.create_select_fork_at_transaction(fork, &mut env, journal, *transaction)?; - Ok(id.abi_encode()) + fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| { + db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction) + }) } /// Creates a new fork at the given transaction -fn create_fork_at_transaction( - ccx: &mut CheatsCtxt, +fn create_fork_at_transaction( + ccx: &mut CheatsCtxt<'_, '_, FEN>, url_or_alias: &str, transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.ecx.journaled_state.database.create_fork_at_transaction(fork, *transaction)?; + let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } /// Creates the request object for a new fork request -fn create_fork_request( - ccx: &mut CheatsCtxt, +fn create_fork_request( + ccx: &mut CheatsCtxt<'_, '_, FEN>, url_or_alias: &str, block: Option, ) -> Result { @@ -326,16 +360,36 @@ fn create_fork_request( evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]); } let fork = CreateFork { - enable_caching: !ccx.state.config.no_storage_caching && - ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), + enable_caching: !ccx.state.config.no_storage_caching + && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - env: ccx.ecx.as_env_mut().to_owned(), evm_opts, }; Ok(fork) } -fn check_broadcast(state: &Cheatcodes) -> Result<()> { +/// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes +/// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork, +/// createSelectFork). +fn fork_env_op( + ecx: &mut CTX, + f: impl FnOnce( + &mut CTX::Db, + &mut EvmEnv, + &mut CTX::Tx, + &mut JournaledState, + ) -> eyre::Result, +) -> Result { + let mut evm_env = ecx.evm_clone(); + let mut tx_env = ecx.tx_clone(); + let (db, inner) = ecx.db_journal_inner_mut(); + let result = f(db, &mut evm_env, &mut tx_env, inner)?; + ecx.set_evm(evm_env); + ecx.set_tx(tx_env); + Ok(result.abi_encode()) +} + +fn check_broadcast(state: &Cheatcodes) -> Result<()> { if state.broadcast.is_none() { Ok(()) } else { @@ -343,20 +397,13 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { } } -fn transact( - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, +fn transact( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, transaction: B256, fork_id: Option, ) -> Result { - let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); - db.transact( - fork_id, - transaction, - env.to_owned(), - journal, - &mut *executor.get_inspector(ccx.state), - )?; + executor.transact_on_db(ccx.state, ccx.ecx, fork_id, transaction)?; Ok(Default::default()) } @@ -364,22 +411,27 @@ fn transact( // state of caller contract is not lost when fork changes). // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 -fn persist_caller(ccx: &mut CheatsCtxt) { - ccx.ecx.journaled_state.database.add_persistent_account(ccx.caller); +fn persist_caller(ccx: &mut CheatsCtxt<'_, '_, FEN>) { + ccx.ecx.db_mut().add_persistent_account(ccx.caller); } /// Performs an Ethereum JSON-RPC request to the given endpoint. fn rpc_call(url: &str, method: &str, params: &str) -> Result { - let provider = ProviderBuilder::new(url).build()?; + let provider = ProviderBuilder::::new(url).build()?; let params_json: serde_json::Value = serde_json::from_str(params)?; let result = foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) .map_err(|err| fmt_err!("{method:?}: {err}"))?; let result_as_tokens = convert_to_bytes( - &json_value_to_token(&result).map_err(|err| fmt_err!("failed to parse result: {err}"))?, + &json_value_to_token(&result, None) + .map_err(|err| fmt_err!("failed to parse result: {err}"))?, ); - Ok(result_as_tokens.abi_encode()) + let payload = match &result_as_tokens { + DynSolValue::Bytes(b) => b.clone(), + _ => result_as_tokens.abi_encode(), + }; + Ok(DynSolValue::Bytes(payload).abi_encode()) } /// Convert fixed bytes and address values to bytes in order to prevent encoding issues. @@ -391,9 +443,6 @@ fn convert_to_bytes(token: &DynSolValue) -> DynSolValue { DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec()) } DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), - // Convert tuple values to prevent encoding issues. - // See: - DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()), val => val.clone(), } } diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs index 29c12d6d3113c..e8620548e8186 100644 --- a/crates/cheatcodes/src/evm/mapping.rs +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -1,65 +1,19 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; -use alloy_primitives::{ - keccak256, - map::{AddressHashMap, B256HashMap}, - Address, B256, U256, -}; +use alloy_primitives::{Address, B256}; use alloy_sol_types::SolValue; -use revm::{ - bytecode::opcode, - interpreter::{interpreter_types::Jumps, Interpreter}, -}; - -/// Recorded mapping slots. -#[derive(Clone, Debug, Default)] -pub struct MappingSlots { - /// Holds mapping parent (slots => slots) - pub parent_slots: B256HashMap, - - /// Holds mapping key (slots => key) - pub keys: B256HashMap, - - /// Holds mapping child (slots => slots[]) - pub children: B256HashMap>, - - /// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record - /// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in - /// `data_low`, higher 256 bits in `data_high`. - /// This is needed for mapping_key detect if the slot is for some mapping and record that. - pub seen_sha3: B256HashMap<(B256, B256)>, -} - -impl MappingSlots { - /// Tries to insert a mapping slot. Returns true if it was inserted. - pub fn insert(&mut self, slot: B256) -> bool { - match self.seen_sha3.get(&slot).copied() { - Some((key, parent)) => { - if self.keys.contains_key(&slot) { - return false - } - self.keys.insert(slot, key); - self.parent_slots.insert(slot, parent); - self.children.entry(parent).or_default().push(slot); - self.insert(parent); - true - } - None => false, - } - } -} +use foundry_common::mapping_slots::MappingSlots; +use foundry_evm_core::evm::FoundryEvmNetwork; impl Cheatcode for startMappingRecordingCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; - if state.mapping_slots.is_none() { - state.mapping_slots = Some(Default::default()); - } + state.mapping_slots.get_or_insert_default(); Ok(Default::default()) } } impl Cheatcode for stopMappingRecordingCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mapping_slots = None; Ok(Default::default()) @@ -67,7 +21,7 @@ impl Cheatcode for stopMappingRecordingCall { } impl Cheatcode for getMappingLengthCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot } = self; let result = slot_child(state, target, mappingSlot).map(Vec::len).unwrap_or(0); Ok((result as u64).abi_encode()) @@ -75,7 +29,7 @@ impl Cheatcode for getMappingLengthCall { } impl Cheatcode for getMappingSlotAtCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot, idx } = self; let result = slot_child(state, target, mappingSlot) .and_then(|set| set.get(idx.saturating_to::())) @@ -86,7 +40,7 @@ impl Cheatcode for getMappingSlotAtCall { } impl Cheatcode for getMappingKeyAndParentOfCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, elementSlot: slot } = self; let mut found = false; let mut key = &B256::ZERO; @@ -106,40 +60,17 @@ impl Cheatcode for getMappingKeyAndParentOfCall { } } -fn mapping_slot<'a>(state: &'a Cheatcodes, target: &'a Address) -> Option<&'a MappingSlots> { +fn mapping_slot<'a, FEN: FoundryEvmNetwork>( + state: &'a Cheatcodes, + target: &'a Address, +) -> Option<&'a MappingSlots> { state.mapping_slots.as_ref()?.get(target) } -fn slot_child<'a>( - state: &'a Cheatcodes, +fn slot_child<'a, FEN: FoundryEvmNetwork>( + state: &'a Cheatcodes, target: &'a Address, slot: &'a B256, ) -> Option<&'a Vec> { mapping_slot(state, target)?.children.get(slot) } - -#[cold] -pub(crate) fn step(mapping_slots: &mut AddressHashMap, interpreter: &Interpreter) { - match interpreter.bytecode.opcode() { - opcode::KECCAK256 => { - if interpreter.stack.peek(1) == Ok(U256::from(0x40)) { - let address = interpreter.input.target_address; - let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); - let data = interpreter.memory.slice_len(offset, 0x40); - let low = B256::from_slice(&data[..0x20]); - let high = B256::from_slice(&data[0x20..]); - let result = keccak256(&*data); - - mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); - } - } - opcode::SSTORE => { - if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address) { - if let Ok(slot) = interpreter.stack.peek(0) { - mapping_slots.insert(slot.into()); - } - } - } - _ => {} - } -} diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index dc2c2137ba524..60d331e8a86a8 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -1,6 +1,11 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; -use revm::{bytecode::Bytecode, context::JournalTr, interpreter::InstructionResult}; +use foundry_evm_core::evm::FoundryEvmNetwork; +use revm::{ + bytecode::Bytecode, + context::{ContextTr, JournalTr}, + interpreter::InstructionResult, +}; use std::{cmp::Ordering, collections::VecDeque}; /// Mocked call data. @@ -39,7 +44,7 @@ impl Ord for MockCallDataContext { } impl Cheatcode for clearMockedCallsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mocked_calls = Default::default(); Ok(Default::default()) @@ -47,7 +52,7 @@ impl Cheatcode for clearMockedCallsCall { } impl Cheatcode for mockCall_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -57,16 +62,17 @@ impl Cheatcode for mockCall_0Call { } impl Cheatcode for mockCall_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.journaled_state.load_account(*callee)?; + let _ = make_acc_non_empty(callee, ccx)?; + mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCall_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -83,9 +89,10 @@ impl Cheatcode for mockCall_2Call { } impl Cheatcode for mockCall_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.journaled_state.load_account(*callee)?; + let _ = make_acc_non_empty(callee, ccx)?; + mock_call( ccx.state, callee, @@ -99,7 +106,7 @@ impl Cheatcode for mockCall_3Call { } impl Cheatcode for mockCalls_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -109,16 +116,17 @@ impl Cheatcode for mockCalls_0Call { } impl Cheatcode for mockCalls_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.journaled_state.load_account(*callee)?; + let _ = make_acc_non_empty(callee, ccx)?; + mock_calls(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCallRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -128,7 +136,7 @@ impl Cheatcode for mockCallRevert_0Call { } impl Cheatcode for mockCallRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -138,7 +146,7 @@ impl Cheatcode for mockCallRevert_1Call { } impl Cheatcode for mockCallRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -155,7 +163,7 @@ impl Cheatcode for mockCallRevert_2Call { } impl Cheatcode for mockCallRevert_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; @@ -172,7 +180,7 @@ impl Cheatcode for mockCallRevert_3Call { } impl Cheatcode for mockFunctionCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, target, data } = self; state.mocked_functions.entry(*callee).or_default().insert(data.clone(), *target); @@ -180,8 +188,8 @@ impl Cheatcode for mockFunctionCall { } } -fn mock_call( - state: &mut Cheatcodes, +fn mock_call( + state: &mut Cheatcodes, callee: &Address, cdata: &Bytes, value: Option<&U256>, @@ -191,8 +199,8 @@ fn mock_call( mock_calls(state, callee, cdata, value, std::slice::from_ref(rdata), ret_type) } -fn mock_calls( - state: &mut Cheatcodes, +fn mock_calls( + state: &mut Cheatcodes, callee: &Address, cdata: &Bytes, value: Option<&U256>, @@ -200,7 +208,7 @@ fn mock_calls( ret_type: InstructionResult, ) { state.mocked_calls.entry(*callee).or_default().insert( - MockCallDataContext { calldata: Bytes::copy_from_slice(cdata), value: value.copied() }, + MockCallDataContext { calldata: cdata.clone(), value: value.copied() }, rdata_vec .iter() .map(|rdata| MockCallReturnData { ret_type, data: rdata.clone() }) @@ -210,13 +218,17 @@ fn mock_calls( // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. -fn make_acc_non_empty(callee: &Address, ecx: &mut CheatsCtxt) -> Result { - let acc = ecx.journaled_state.load_account(*callee)?; - - let empty_bytecode = acc.info.code.as_ref().is_none_or(Bytecode::is_empty); +fn make_acc_non_empty( + callee: &Address, + ccx: &mut CheatsCtxt<'_, '_, FEN>, +) -> Result { + let empty_bytecode = { + let acc = ccx.ecx.journal_mut().load_account(*callee)?; + acc.info.code.as_ref().is_none_or(Bytecode::is_empty) + }; if empty_bytecode { let code = Bytecode::new_raw(Bytes::from_static(&[0u8])); - ecx.journaled_state.set_code(*callee, code); + ccx.ecx.journal_mut().set_code(*callee, code); } Ok(Default::default()) diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 96ab6cb9df925..0669dfa076822 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -1,6 +1,7 @@ -use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; +use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_primitives::Address; -use revm::{context::JournalTr, interpreter::Host}; +use foundry_evm_core::evm::FoundryEvmNetwork; +use revm::context::{ContextTr, JournalTr, Transaction}; /// Prank information. #[derive(Clone, Copy, Debug, Default)] @@ -25,7 +26,7 @@ pub struct Prank { impl Prank { /// Create a new prank. - pub fn new( + pub const fn new( prank_caller: Address, prank_origin: Address, new_caller: Address, @@ -48,96 +49,96 @@ impl Prank { /// Apply the prank by setting `used` to true if it is false /// Only returns self in the case it is updated (first application) - pub fn first_time_applied(&self) -> Option { - if self.used { - None - } else { - Some(Self { used: true, ..*self }) - } + pub const fn first_time_applied(&self) -> Option { + if self.used { None } else { Some(Self { used: true, ..*self }) } } } impl Cheatcode for prank_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true, false) } } impl Cheatcode for startPrank_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false, false) } } impl Cheatcode for prank_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true, false) } } impl Cheatcode for startPrank_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false, false) } } impl Cheatcode for prank_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, true, *delegateCall) } } impl Cheatcode for startPrank_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, false, *delegateCall) } } impl Cheatcode for prank_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), true, *delegateCall) } } impl Cheatcode for startPrank_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), false, *delegateCall) } } impl Cheatcode for stopPrankCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - ccx.state.pranks.remove(&ccx.ecx.journaled_state.depth()); + ccx.state.pranks.remove(&ccx.ecx.journal().depth()); Ok(Default::default()) } } -fn prank( - ccx: &mut CheatsCtxt, +fn prank( + ccx: &mut CheatsCtxt<'_, '_, FEN>, new_caller: &Address, new_origin: Option<&Address>, single_call: bool, delegate_call: bool, ) -> Result { + // Ensure that we load the account of the pranked address and mark it as touched. + // This is necessary to ensure that account state changes (such as the account's `nonce`) are + // properly tracked. + let account = journaled_account(ccx.ecx, *new_caller)?; + // Ensure that code exists at `msg.sender` if delegate calling. if delegate_call { - let code = ccx - .load_account_code(*new_caller) - .ok_or_else(|| eyre::eyre!("cannot `prank` delegate call from an EOA"))?; - - ensure!(!code.data.is_empty(), "cannot `prank` delegate call from an EOA"); + ensure!( + account.info.code.as_ref().is_some_and(|code| !code.is_empty()), + "cannot `prank` delegate call from an EOA" + ); } - let depth = ccx.ecx.journaled_state.depth(); + let depth = ccx.ecx.journal().depth(); if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.get_prank(depth) { ensure!(used, "cannot overwrite a prank until it is applied at least once"); // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. @@ -151,7 +152,7 @@ fn prank( let prank = Prank::new( ccx.caller, - ccx.ecx.tx.caller, + ccx.ecx.tx().caller(), *new_caller, new_origin.copied(), depth, diff --git a/crates/cheatcodes/src/evm/record_debug_step.rs b/crates/cheatcodes/src/evm/record_debug_step.rs index bc78c2425c414..3691ceb52ad00 100644 --- a/crates/cheatcodes/src/evm/record_debug_step.rs +++ b/crates/cheatcodes/src/evm/record_debug_step.rs @@ -3,17 +3,25 @@ use alloy_primitives::{Bytes, U256}; use foundry_evm_traces::CallTraceArena; use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult}; -use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; -use revm_inspectors::tracing::types::{CallTraceStep, RecordedMemory, TraceMemberOrder}; +use foundry_evm_core::buffer::{BufferKind, get_buffer_accesses}; +use revm_inspectors::tracing::types::{ + CallTraceNode, CallTraceStep, RecordedMemory, TraceMemberOrder, +}; use spec::Vm::DebugStep; +// Context for a CallTraceStep, includes depth and contract address. +pub(crate) struct CallTraceCtx<'a> { + pub node: &'a CallTraceNode, + pub step: &'a CallTraceStep, +} + // Do a depth first traverse of the nodes and steps and return steps // that are after `node_start_idx` -pub(crate) fn flatten_call_trace( +pub(crate) fn flatten_call_trace<'a>( root: usize, - arena: &CallTraceArena, + arena: &'a CallTraceArena, node_start_idx: usize, -) -> Vec<&CallTraceStep> { +) -> Vec> { let mut steps = Vec::new(); let mut record_started = false; @@ -31,7 +39,7 @@ fn recursive_flatten_call_trace<'a>( arena: &'a CallTraceArena, node_start_idx: usize, record_started: &mut bool, - flatten_steps: &mut Vec<&'a CallTraceStep>, + flatten_steps: &mut Vec>, ) { // Once node_idx exceeds node_start_idx, start recording steps // for all the recursive processing. @@ -43,11 +51,9 @@ fn recursive_flatten_call_trace<'a>( for order in &node.ordering { match order { - TraceMemberOrder::Step(step_idx) => { - if *record_started { - let step = &node.trace.steps[*step_idx]; - flatten_steps.push(step); - } + TraceMemberOrder::Step(step_idx) if *record_started => { + let step = &node.trace.steps[*step_idx]; + flatten_steps.push(CallTraceCtx { node, step }); } TraceMemberOrder::Call(call_idx) => { let child_node_idx = node.children[*call_idx]; @@ -65,25 +71,34 @@ fn recursive_flatten_call_trace<'a>( } // Function to convert CallTraceStep to DebugStep -pub(crate) fn convert_call_trace_to_debug_step(step: &CallTraceStep) -> DebugStep { - let opcode = step.op.get(); - let stack = get_stack_inputs_for_opcode(opcode, step.stack.as_ref()); - - let memory = get_memory_input_for_opcode(opcode, step.stack.as_ref(), step.memory.as_ref()); - - let is_out_of_gas = step.status == InstructionResult::OutOfGas || - step.status == InstructionResult::MemoryOOG || - step.status == InstructionResult::MemoryLimitOOG || - step.status == InstructionResult::PrecompileOOG || - step.status == InstructionResult::InvalidOperandOOG; +pub(crate) fn convert_call_trace_ctx_to_debug_step(ctx: &CallTraceCtx) -> DebugStep { + let opcode = ctx.step.op.get(); + let stack = get_stack_inputs_for_opcode(opcode, ctx.step.stack.as_deref()); + + let memory = + get_memory_input_for_opcode(opcode, ctx.step.stack.as_deref(), ctx.step.memory.as_ref()); + + let is_out_of_gas = matches!( + ctx.step.status, + Some( + InstructionResult::OutOfGas + | InstructionResult::MemoryOOG + | InstructionResult::MemoryLimitOOG + | InstructionResult::PrecompileOOG + | InstructionResult::InvalidOperandOOG + ) + ); + + let depth = ctx.node.trace.depth as u64 + 1; + let contract_addr = ctx.node.execution_address(); DebugStep { stack, memoryInput: memory, - opcode: step.op.get(), - depth: step.depth, + opcode: ctx.step.op.get(), + depth, isOutOfGas: is_out_of_gas, - contractAddr: step.contract, + contractAddr: contract_addr, } } @@ -91,17 +106,17 @@ pub(crate) fn convert_call_trace_to_debug_step(step: &CallTraceStep) -> DebugSte // is the last value of the vector fn get_memory_input_for_opcode( opcode: u8, - stack: Option<&Vec>, + stack: Option<&[U256]>, memory: Option<&RecordedMemory>, ) -> Bytes { let mut memory_input = Bytes::new(); let Some(stack_data) = stack else { return memory_input }; let Some(memory_data) = memory else { return memory_input }; - if let Some(accesses) = get_buffer_accesses(opcode, stack_data) { - if let Some((BufferKind::Memory, access)) = accesses.read { - memory_input = get_slice_from_memory(memory_data.as_bytes(), access.offset, access.len); - } + if let Some(accesses) = get_buffer_accesses(opcode, stack_data) + && let Some((BufferKind::Memory, access)) = accesses.read + { + memory_input = get_slice_from_memory(memory_data.as_bytes(), access.offset, access.len); }; memory_input @@ -109,7 +124,7 @@ fn get_memory_input_for_opcode( // The expected `stack` here is from the trace stack, where the top of the stack // is the last value of the vector -fn get_stack_inputs_for_opcode(opcode: u8, stack: Option<&Vec>) -> Vec { +fn get_stack_inputs_for_opcode(opcode: u8, stack: Option<&[U256]>) -> Vec { let mut inputs = Vec::new(); let Some(op) = OpCode::new(opcode) else { return inputs }; diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index f1985eb32bbc1..1436883c13aee 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,22 +1,27 @@ //! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. use super::string::parse; -use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use crate::{ + Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*, inspector::exec_create, +}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_network::AnyTransactionReceipt; -use alloy_primitives::{hex, map::Entry, Bytes, U256}; -use alloy_provider::network::ReceiptResponse; +use alloy_network::{Network, ReceiptResponse}; +use alloy_primitives::{Bytes, U256, hex, map::Entry}; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use revm::{context::CreateScheme, interpreter::CreateInputs}; +use foundry_evm_core::evm::FoundryEvmNetwork; +use revm::{ + context::{Cfg, ContextTr, CreateScheme, JournalTr}, + interpreter::CreateInputs, +}; use revm_inspectors::tracing::types::CallKind; use semver::Version; use std::{ - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader}, path::{Path, PathBuf}, process::Command, sync::mpsc, @@ -26,7 +31,7 @@ use std::{ use walkdir::WalkDir; impl Cheatcode for existsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.exists().abi_encode()) @@ -34,7 +39,7 @@ impl Cheatcode for existsCall { } impl Cheatcode for fsMetadataCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -60,7 +65,7 @@ impl Cheatcode for fsMetadataCall { } impl Cheatcode for isDirCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.is_dir().abi_encode()) @@ -68,7 +73,7 @@ impl Cheatcode for isDirCall { } impl Cheatcode for isFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.is_file().abi_encode()) @@ -76,14 +81,27 @@ impl Cheatcode for isFileCall { } impl Cheatcode for projectRootCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.config.root.display().to_string().abi_encode()) } } +impl Cheatcode for currentFilePathCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let artifact = state + .config + .running_artifact + .as_ref() + .ok_or_else(|| fmt_err!("no running contract found"))?; + let relative = artifact.source.strip_prefix(&state.config.root).unwrap_or(&artifact.source); + Ok(relative.display().to_string().abi_encode()) + } +} + impl Cheatcode for unixTimeCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; let difference = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -93,7 +111,7 @@ impl Cheatcode for unixTimeCall { } impl Cheatcode for closeFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -104,7 +122,7 @@ impl Cheatcode for closeFileCall { } impl Cheatcode for copyFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { from, to } = self; let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?; let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?; @@ -116,7 +134,7 @@ impl Cheatcode for copyFileCall { } impl Cheatcode for createDirCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?; @@ -125,44 +143,44 @@ impl Cheatcode for createDirCall { } impl Cheatcode for readDir_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; read_dir(state, path.as_ref(), 1, false) } } impl Cheatcode for readDir_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth } = self; read_dir(state, path.as_ref(), *maxDepth, false) } } impl Cheatcode for readDir_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth, followLinks } = self; read_dir(state, path.as_ref(), *maxDepth, *followLinks) } } impl Cheatcode for readFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - Ok(fs::read_to_string(path)?.abi_encode()) + Ok(fs::locked_read_to_string(path)?.abi_encode()) } } impl Cheatcode for readFileBinaryCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - Ok(fs::read(path)?.abi_encode()) + Ok(fs::locked_read(path)?.abi_encode()) } } impl Cheatcode for readLineCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; @@ -188,7 +206,7 @@ impl Cheatcode for readLineCall { } impl Cheatcode for readLinkCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { linkPath: path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let target = fs::read_link(path)?; @@ -197,7 +215,7 @@ impl Cheatcode for readLinkCall { } impl Cheatcode for removeDirCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?; @@ -206,7 +224,7 @@ impl Cheatcode for removeDirCall { } impl Cheatcode for removeFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; state.config.ensure_not_foundry_toml(&path)?; @@ -223,29 +241,27 @@ impl Cheatcode for removeFileCall { } impl Cheatcode for writeFileCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data.as_bytes()) } } impl Cheatcode for writeFileBinaryCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data) } } impl Cheatcode for writeLineCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data: line } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; state.config.ensure_not_foundry_toml(&path)?; if state.fs_commit { - let mut file = std::fs::OpenOptions::new().append(true).create(true).open(path)?; - - writeln!(file, "{line}")?; + fs::locked_write_line(path, line)?; } Ok(Default::default()) @@ -253,7 +269,7 @@ impl Cheatcode for writeLineCall { } impl Cheatcode for getArtifactPathByCodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { code } = self; let (artifact_id, _) = state .config @@ -267,7 +283,7 @@ impl Cheatcode for getArtifactPathByCodeCall { } impl Cheatcode for getArtifactPathByDeployedCodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { deployedCode } = self; let (artifact_id, _) = state .config @@ -281,70 +297,102 @@ impl Cheatcode for getArtifactPathByDeployedCodeCall { } impl Cheatcode for getCodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, false)?.abi_encode()) } } impl Cheatcode for getDeployedCodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, true)?.abi_encode()) } } impl Cheatcode for deployCode_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path } = self; deploy_code(ccx, executor, path, None, None, None) } } impl Cheatcode for deployCode_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args } = self; deploy_code(ccx, executor, path, Some(args), None, None) } } impl Cheatcode for deployCode_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, value } = self; deploy_code(ccx, executor, path, None, Some(*value), None) } } impl Cheatcode for deployCode_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, value } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), None) } } impl Cheatcode for deployCode_4Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, salt } = self; deploy_code(ccx, executor, path, None, None, Some((*salt).into())) } } impl Cheatcode for deployCode_5Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, salt } = self; deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into())) } } impl Cheatcode for deployCode_6Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, value, salt } = self; deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into())) } } impl Cheatcode for deployCode_7Call { - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let Self { artifactPath: path, constructorArgs: args, value, salt } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into())) } @@ -352,15 +400,21 @@ impl Cheatcode for deployCode_7Call { /// Helper function to deploy contract from artifact code. /// Uses CREATE2 scheme if salt specified. -fn deploy_code( - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, +fn deploy_code( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, path: &str, constructor_args: Option<&Bytes>, value: Option, salt: Option, ) -> Result { let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec(); + + // If active broadcast then set flag to deploy from code. + if let Some(broadcast) = &mut ccx.state.broadcast { + broadcast.deploy_from_code = true; + } + if let Some(args) = constructor_args { bytecode.extend_from_slice(args); } @@ -368,19 +422,25 @@ fn deploy_code( let scheme = if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create }; - let outcome = executor.exec_create( - CreateInputs { - caller: ccx.caller, + // If prank active at current depth, then use it as caller for create input. + let caller = + ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller); + + let outcome = exec_create( + executor, + CreateInputs::new( + caller, scheme, - value: value.unwrap_or(U256::ZERO), - init_code: bytecode.into(), - gas_limit: ccx.gas_limit, - }, + value.unwrap_or(U256::ZERO), + bytecode.into(), + ccx.gas_limit, + 0, + ), ccx, )?; if !outcome.result.result.is_ok() { - return Err(crate::Error::from(outcome.result.output)) + return Err(crate::Error::from(outcome.result.output)); } let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?; @@ -388,7 +448,7 @@ fn deploy_code( Ok(address.abi_encode()) } -/// Returns the path to the json artifact depending on the input +/// Returns the bytecode from a JSON artifact file. /// /// Can parse following input formats: /// - `path/to/artifact.json` @@ -398,7 +458,15 @@ fn deploy_code( /// - `path/to/contract.sol:0.8.23` /// - `ContractName` /// - `ContractName:0.8.23` -fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { +/// +/// This function is safe to use with contracts that have library dependencies. +/// `alloy_json_abi::ContractObject` validates bytecode during JSON parsing and will +/// reject artifacts with unlinked library placeholders. +fn get_artifact_code( + state: &Cheatcodes, + path: &str, + deployed: bool, +) -> Result { let path = if path.ends_with(".json") { PathBuf::from(path) } else { @@ -438,165 +506,195 @@ fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result>(); let artifact = match &filtered[..] { - [] => Err(fmt_err!("no matching artifact found")), - [artifact] => Ok(*artifact), + [] => None, + [artifact] => Some(Ok(*artifact)), filtered => { let mut filtered = filtered.to_vec(); // If we know the current script/test contract solc version, try to filter by it - state - .config - .running_artifact - .as_ref() - .and_then(|running| { - // Firstly filter by version - filtered.retain(|(id, _)| id.version == running.version); - - // Return artifact if only one matched - if filtered.len() == 1 { - return Some(filtered[0]) - } - - // Try filtering by profile as well - filtered.retain(|(id, _)| id.profile == running.profile); - - if filtered.len() == 1 { - Some(filtered[0]) - } else { - None - } - }) - .ok_or_else(|| fmt_err!("multiple matching artifacts found")) + Some( + state + .config + .running_artifact + .as_ref() + .and_then(|running| { + // Firstly filter by version + filtered.retain(|(id, _)| id.version == running.version); + + // Return artifact if only one matched + if filtered.len() == 1 { + return Some(filtered[0]); + } + + // Try filtering by profile as well + filtered.retain(|(id, _)| id.profile == running.profile); + + (filtered.len() == 1).then(|| filtered[0]) + }) + .ok_or_else(|| fmt_err!("multiple matching artifacts found")), + ) } - }?; - - let maybe_bytecode = if deployed { - artifact.1.deployed_bytecode().cloned() - } else { - artifact.1.bytecode().cloned() }; - return maybe_bytecode - .ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")); - } else { - let path_in_artifacts = - match (file.map(|f| f.to_string_lossy().to_string()), contract_name) { - (Some(file), Some(contract_name)) => { - PathBuf::from(format!("{file}/{contract_name}.json")) - } - (None, Some(contract_name)) => { - PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) - } - (Some(file), None) => { - let name = file.replace(".sol", ""); - PathBuf::from(format!("{file}/{name}.json")) - } - _ => bail!("invalid artifact path"), + if let Some(artifact) = artifact { + let artifact = artifact?; + let maybe_bytecode = if deployed { + artifact.1.deployed_bytecode().cloned() + } else { + artifact.1.bytecode().cloned() }; - state.config.paths.artifacts.join(path_in_artifacts) + return maybe_bytecode.ok_or_else(|| { + fmt_err!("no bytecode for contract; is it abstract or unlinked?") + }); + } } + + // Fallback: construct path manually when no artifacts list or no match found + let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name) + { + (Some(file), Some(contract_name)) => { + PathBuf::from(format!("{file}/{contract_name}.json")) + } + (None, Some(contract_name)) => { + PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) + } + (Some(file), None) => { + let name = file.replace(".sol", ""); + PathBuf::from(format!("{file}/{name}.json")) + } + _ => bail!("invalid artifact path"), + }; + + state.config.paths.artifacts.join(path_in_artifacts) }; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data = fs::read_to_string(path)?; + let data = fs::read_to_string(path).map_err(|e| { + if state.config.available_artifacts.is_some() { + fmt_err!("no matching artifact found") + } else { + e.into() + } + })?; let artifact = serde_json::from_str::(&data)?; let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode }; maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")) } impl Cheatcode for ffiCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; let output = ffi(state, input)?; - // TODO: check exit code? + + // Check the exit code of the command. + if output.exitCode != 0 { + // If the command failed, return an error with the exit code and stderr. + return Err(fmt_err!( + "ffi command {:?} exited with code {}. stderr: {}", + input, + output.exitCode, + String::from_utf8_lossy(&output.stderr) + )); + } + + // If the command succeeded but still wrote to stderr, log it as a warning. if !output.stderr.is_empty() { let stderr = String::from_utf8_lossy(&output.stderr); - error!(target: "cheatcodes", ?input, ?stderr, "non-empty stderr"); + warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr"); } - // we already hex-decoded the stdout in `ffi` + + // We already hex-decoded the stdout in the `ffi` helper function. Ok(output.stdout.abi_encode()) } } impl Cheatcode for tryFfiCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; ffi(state, input).map(|res| res.abi_encode()) } } impl Cheatcode for promptCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_input).map(|res| res.abi_encode()) } } impl Cheatcode for promptSecretCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_password).map(|res| res.abi_encode()) } } impl Cheatcode for promptSecretUintCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256)) } } impl Cheatcode for promptAddressCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Address) } } impl Cheatcode for promptUintCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256)) } } -pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result { +pub(super) fn write_file( + state: &Cheatcodes, + path: &Path, + contents: &[u8], +) -> Result { let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; // write access to foundry.toml is not allowed state.config.ensure_not_foundry_toml(&path)?; if state.fs_commit { - fs::write(path, contents)?; + fs::locked_write(path, contents)?; } Ok(Default::default()) } -fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) -> Result { +fn read_dir( + state: &Cheatcodes, + path: &Path, + max_depth: u64, + follow_links: bool, +) -> Result { let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let paths: Vec = WalkDir::new(root) .min_depth(1) @@ -626,7 +724,7 @@ fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) Ok(paths.abi_encode()) } -fn ffi(state: &Cheatcodes, input: &[String]) -> Result { +fn ffi(state: &Cheatcodes, input: &[String]) -> Result { ensure!( state.config.ffi, "FFI is disabled; add the `--ffi` flag to allow tests to call external commands" @@ -666,8 +764,8 @@ fn prompt_password(prompt_text: &str) -> Result { Password::new().with_prompt(prompt_text).interact() } -fn prompt( - state: &Cheatcodes, +fn prompt( + state: &Cheatcodes, prompt_text: &str, input: fn(&str) -> Result, ) -> Result { @@ -692,10 +790,10 @@ fn prompt( } impl Cheatcode for getBroadcastCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, *chainId, &state.config.broadcast, @@ -707,13 +805,13 @@ impl Cheatcode for getBroadcastCall { } impl Cheatcode for getBroadcasts_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(map_broadcast_tx_type(*txType)); - let broadcasts = reader.read()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -728,12 +826,12 @@ impl Cheatcode for getBroadcasts_0Call { } impl Cheatcode for getBroadcasts_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?; - let broadcasts = reader.read()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -748,11 +846,11 @@ impl Cheatcode for getBroadcasts_1Call { } impl Cheatcode for getDeployment_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { contractName } = self; - let chain_id = ccx.ecx.cfg.chain_id; + let chain_id = ccx.ecx.cfg().chain_id(); - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, chain_id, &ccx.state.config.broadcast, @@ -764,10 +862,10 @@ impl Cheatcode for getDeployment_0Call { } impl Cheatcode for getDeployment_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; - let latest_broadcast = latest_broadcast( + let latest_broadcast = latest_broadcast::<::Network>( contractName, *chainId, &state.config.broadcast, @@ -779,14 +877,14 @@ impl Cheatcode for getDeployment_1Call { } impl Cheatcode for getDeploymentsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(CallKind::Create) .with_tx_type(CallKind::Create2); - let broadcasts = reader.read()?; + let broadcasts = reader.read::<::Network>()?; let summaries = broadcasts .into_iter() @@ -812,15 +910,15 @@ fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind { } } -fn parse_broadcast_results( - results: Vec<(TransactionWithMetadata, AnyTransactionReceipt)>, +fn parse_broadcast_results( + results: Vec<(TransactionWithMetadata, N::ReceiptResponse)>, ) -> Vec { results .into_iter() .map(|(tx, receipt)| BroadcastTxSummary { - txHash: receipt.transaction_hash, - blockNumber: receipt.block_number.unwrap_or_default(), - txType: match tx.opcode { + txHash: receipt.transaction_hash(), + blockNumber: receipt.block_number().unwrap_or_default(), + txType: match tx.call_kind { CallKind::Call => BroadcastTxType::Call, CallKind::Create => BroadcastTxType::Create, CallKind::Create2 => BroadcastTxType::Create2, @@ -832,19 +930,22 @@ fn parse_broadcast_results( .collect() } -fn latest_broadcast( +fn latest_broadcast( contract_name: &String, chain_id: u64, broadcast_path: &Path, filters: Vec, -) -> Result { +) -> Result +where + N::TxEnvelope: for<'d> serde::Deserialize<'d>, +{ let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?; for filter in filters { reader = reader.with_tx_type(filter); } - let broadcast = reader.read_latest()?; + let broadcast = reader.read_latest::()?; let results = reader.into_tx_receipts(broadcast); @@ -860,7 +961,9 @@ fn latest_broadcast( mod tests { use super::*; use crate::CheatsConfig; - use std::sync::Arc; + use alloy_primitives::{address, b256}; + use foundry_evm_core::evm::TempoEvmNetwork; + use std::{env, fs as stdfs, sync::Arc}; fn cheats() -> Cheatcodes { let config = CheatsConfig { @@ -889,6 +992,29 @@ mod tests { assert_eq!(output.stdout, Bytes::from(msg.as_bytes())); } + #[test] + fn test_ffi_fails_on_error_code() { + let mut cheats = cheats(); + + // Use a command that is guaranteed to fail with a non-zero exit code on any platform. + #[cfg(unix)] + let args = vec!["false".to_string()]; + #[cfg(windows)] + let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()]; + + let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats); + + // Assert that the cheatcode returned an error. + assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded"); + + // Assert that the error message contains the expected information. + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("exited with code 1"), + "Error message did not contain exit code: {err_msg}" + ); + } + #[test] fn test_artifact_parsing() { let s = include_str!("../../evm/test-data/solc-obj.json"); @@ -898,4 +1024,121 @@ mod tests { let artifact: ContractObject = serde_json::from_str(s).unwrap(); assert!(artifact.deployed_bytecode.is_some()); } + + #[test] + fn test_alloy_json_abi_rejects_unlinked_bytecode() { + let artifact_json = r#"{ + "abi": [], + "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868" + }"#; + + let result: Result = serde_json::from_str(artifact_json); + assert!(result.is_err(), "should reject unlinked bytecode with placeholders"); + let err = result.unwrap_err().to_string(); + assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder")); + } + + fn unique_temp_dir(prefix: &str) -> PathBuf { + env::temp_dir().join(format!( + "foundry-cheatcodes-{prefix}-{}", + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() + )) + } + + #[test] + fn test_latest_broadcast_reads_tempo_sequences() { + let root = unique_temp_dir("tempo-broadcast"); + let broadcast_path = root.join("broadcast"); + let sequence_dir = broadcast_path.join("Counter.s.sol").join("31337"); + stdfs::create_dir_all(&sequence_dir).unwrap(); + + let tx_hash = "0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f"; + let block_hash = "0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66"; + let from = "0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"; + let contract_address = "0x20c0000000000000000000000000000000000000"; + let zero_bloom = format!("0x{}", "0".repeat(512)); + + let sequence = serde_json::json!({ + "transactions": [{ + "hash": tx_hash, + "transactionType": "CREATE", + "contractName": "Counter", + "contractAddress": contract_address, + "function": serde_json::Value::Null, + "arguments": serde_json::Value::Null, + "transaction": { + "type": "0x76", + "from": from, + "to": serde_json::Value::Null, + "data": "0x", + "value": "0x0", + "gas": "0x5208", + "nonce": "0x0", + "accessList": [], + "calls": [], + "nonceKey": "0x0", + "feePayerSignature": serde_json::Value::Null, + "validBefore": serde_json::Value::Null, + "validAfter": serde_json::Value::Null, + "keyAuthorization": serde_json::Value::Null, + "aaAuthorizationList": [] + }, + "additionalContracts": [], + "isFixedGasLimit": false + }], + "receipts": [{ + "type": "0x76", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": zero_bloom, + "transactionHash": tx_hash, + "transactionIndex": "0x0", + "blockHash": block_hash, + "blockNumber": "0x7", + "gasUsed": "0x5208", + "effectiveGasPrice": "0x1", + "from": from, + "to": serde_json::Value::Null, + "contractAddress": contract_address, + "feePayer": from + }], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1, + "chain": 31337, + "commit": serde_json::Value::Null + }); + + fs::write_json_file(&sequence_dir.join("run-1.json"), &sequence).unwrap(); + + let latest = latest_broadcast::<::Network>( + &"Counter".to_owned(), + 31337, + &broadcast_path, + vec![CallKind::Create], + ) + .unwrap(); + + assert_eq!( + latest.txHash, + b256!("04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f") + ); + assert_eq!(latest.blockNumber, 7); + assert!(matches!(latest.txType, BroadcastTxType::Create)); + assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); + assert!(latest.success); + + if root.exists() { + let root_canon = stdfs::canonicalize(&root).unwrap(); + let temp_canon = stdfs::canonicalize(env::temp_dir()).unwrap(); + assert!( + root_canon.starts_with(&temp_canon), + "refusing to remove non-temp test directory: {}", + root_canon.display() + ); + stdfs::remove_dir_all(&root).unwrap(); + } + } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 4d9b5cd5ef5ed..27545c1b6cd33 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1,11 +1,12 @@ //! Cheatcode EVM inspector. use crate::{ + Cheatcode, CheatsConfig, CheatsCtxt, Error, Result, + Vm::{self, AccountAccess}, evm::{ - mapping::{self, MappingSlots}, + DealRecord, GasRecord, RecordAccess, journaled_account, mock::{MockCallDataContext, MockCallReturnData}, prank::Prank, - DealRecord, GasRecord, RecordAccess, }, inspector::utils::CommonCreateInput, script::{Broadcast, Wallets}, @@ -18,145 +19,147 @@ use crate::{ revert_handlers, }, utils::IgnoredTraces, - CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, - Vm::{self, AccountAccess}, }; -use alloy_consensus::BlobTransactionSidecar; -use alloy_evm::eth::EthEvmContext; -use alloy_network::TransactionBuilder4844; +use alloy_consensus::BlobTransactionSidecarVariant; +use alloy_network::{Ethereum, Network, TransactionBuilder}; use alloy_primitives::{ - hex, + Address, B256, Bytes, Log, TxKind, U256, hex, map::{AddressHashMap, HashMap, HashSet}, - Address, Bytes, Log, TxKind, B256, U256, -}; -use alloy_rpc_types::{ - request::{TransactionInput, TransactionRequest}, - AccessList, }; +use alloy_rpc_types::AccessList; use alloy_sol_types::{SolCall, SolInterface, SolValue}; -use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_common::{ + FoundryTransactionBuilder, SELECTOR_LEN, TransactionMaybeSigned, + mapping_slots::{MappingSlots, step as mapping_step}, +}; use foundry_evm_core::{ + Breakpoints, EvmEnv, FoundryTransaction, InspectorExt, abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, - evm::{new_evm_with_existing_context, FoundryEvm}, - InspectorExt, + env::FoundryContextExt, + evm::{ + BlockEnvFor, EthEvmNetwork, FoundryContextFor, FoundryEvmFactory, FoundryEvmNetwork, + NestedEvmClosure, SpecFor, TransactionRequestFor, TxEnvFor, with_cloned_context, + }, +}; +use foundry_evm_traces::{ + TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier, }; -use foundry_evm_traces::{TracingInspector, TracingInspectorConfig}; -use foundry_wallets::multi_wallet::MultiWallet; +use foundry_wallets::wallet_multi::MultiWallet; use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; -use rand::{Rng, SeedableRng}; -use rand_chacha::ChaChaRng; +use rand::Rng; use revm::{ + Inspector, bytecode::opcode as op, - context::{result::EVMError, BlockEnv, JournalTr, LocalContext, TransactionType}, - context_interface::{transaction::SignedAuthorization, CreateScheme}, + context::{Cfg, ContextTr, Host, JournalTr, Transaction, TransactionType, result::EVMError}, + context_interface::{CreateScheme, transaction::SignedAuthorization}, handler::FrameResult, + inspector::JournalExt, interpreter::{ - interpreter_types::{Jumps, MemoryTr}, - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, Host, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, + interpreter_types::{Jumps, LoopControl, MemoryTr}, }, - state::EvmStorageSlot, - Inspector, Journal, }; use serde_json::Value; use std::{ cmp::max, collections::{BTreeMap, VecDeque}, + fmt::Debug, fs::File, io::BufReader, ops::Range, path::PathBuf, - sync::Arc, + sync::{Arc, OnceLock}, }; mod utils; -pub type Ecx<'a, 'b, 'c> = &'a mut EthEvmContext<&'b mut (dyn DatabaseExt + 'c)>; +pub mod analysis; +pub use analysis::CheatcodeAnalysis; -/// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to -/// [Cheatcodes]. -/// -/// This is needed for cases when inspector itself needs mutable access to [Cheatcodes] state and -/// allows us to correctly execute arbitrary EVM frames from inside cheatcode implementations. -pub trait CheatcodesExecutor { - /// Core trait method accepting mutable reference to [Cheatcodes] and returning - /// [revm::Inspector]. - fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box; - - /// Obtains [FoundryEvm] instance and executes the given CREATE frame. - fn exec_create( +/// Helper trait for running nested EVM operations from inside cheatcode implementations. +pub trait CheatcodesExecutor { + /// Runs a closure with a nested EVM built from the current context. + /// The inspector is assembled internally — never exposed to the caller. + fn with_nested_evm( &mut self, - inputs: CreateInputs, - ccx: &mut CheatsCtxt, - ) -> Result> { - with_evm(self, ccx, |evm| { - evm.inner.ctx.journaled_state.depth += 1; + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result<(), EVMError>; - let frame = FrameInput::Create(Box::new(inputs)); - - let outcome = match evm.run_execution(frame)? { - FrameResult::Call(_) | FrameResult::EOFCreate(_) => unreachable!(), - FrameResult::Create(create) => create, - }; - - evm.inner.ctx.journaled_state.depth -= 1; - - Ok(outcome) - }) - } + /// Replays a historical transaction on the database. Inspector is assembled internally. + fn transact_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + fork_id: Option, + transaction: B256, + ) -> eyre::Result<()>; + + /// Executes a `TransactionRequest` on the database. Inspector is assembled internally. + fn transact_from_tx_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + tx: TxEnvFor, + ) -> eyre::Result<()>; + + /// Runs a closure with a fresh nested EVM built from a raw database and environment. + /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back. + /// The caller is responsible for state merging. Used by `executeTransactionCall`. + /// Returns the final EVM environment after the closure runs (consumed without cloning). + #[allow(clippy::type_complexity)] + fn with_fresh_nested_evm( + &mut self, + cheats: &mut Cheatcodes, + db: &mut as ContextTr>::Db, + evm_env: EvmEnv, BlockEnvFor>, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result, BlockEnvFor>, EVMError>; - fn console_log(&mut self, ccx: &mut CheatsCtxt, msg: &str) { - self.get_inspector(ccx.state).console_log(msg); - } + /// Simulates `console.log` invocation. + fn console_log(&mut self, msg: &str); /// Returns a mutable reference to the tracing inspector if it is available. - fn tracing_inspector(&mut self) -> Option<&mut Option> { + fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { None } -} -/// Constructs [FoundryEvm] and runs a given closure with it. -fn with_evm( - executor: &mut E, - ccx: &mut CheatsCtxt, - f: F, -) -> Result> -where - E: CheatcodesExecutor + ?Sized, - F: for<'a, 'b> FnOnce( - &mut FoundryEvm<'a, &'b mut dyn InspectorExt>, - ) -> Result>, -{ - let mut inspector = executor.get_inspector(ccx.state); - let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); - - let ctx = EthEvmContext { - block: ccx.ecx.block.clone(), - cfg: ccx.ecx.cfg.clone(), - tx: ccx.ecx.tx.clone(), - journaled_state: Journal { - inner: ccx.ecx.journaled_state.inner.clone(), - database: &mut *ccx.ecx.journaled_state.database as &mut dyn DatabaseExt, - }, - local: LocalContext::default(), - chain: (), - error, - }; - - let mut evm = new_evm_with_existing_context(ctx, &mut *inspector); + /// Marks that the next EVM frame is an "inner context" so that isolation mode does not + /// trigger a nested `transact_inner`. `original_origin` is stored for the existing + /// inner-context adjustment logic that restores `tx.origin`. + fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option
) {} +} - let res = f(&mut evm)?; +/// Builds a sub-EVM from the current context and executes the given CREATE frame. +pub(crate) fn exec_create( + executor: &mut dyn CheatcodesExecutor, + inputs: CreateInputs, + ccx: &mut CheatsCtxt<'_, '_, FEN>, +) -> std::result::Result> { + let mut inputs = Some(inputs); + let mut outcome = None; + executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| { + let inputs = inputs.take().unwrap(); + evm.journal_inner_mut().depth += 1; + + let frame = FrameInput::Create(Box::new(inputs)); + + let result = match evm.run_execution(frame)? { + FrameResult::Call(_) => unreachable!(), + FrameResult::Create(create) => create, + }; - ccx.ecx.journaled_state.inner = evm.inner.ctx.journaled_state.inner; - ccx.ecx.block = evm.inner.ctx.block; - ccx.ecx.tx = evm.inner.ctx.tx; - ccx.ecx.cfg = evm.inner.ctx.cfg; - ccx.ecx.error = evm.inner.ctx.error; + evm.journal_inner_mut().depth -= 1; - Ok(res) + outcome = Some(result); + Ok(()) + })?; + Ok(outcome.unwrap()) } /// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an @@ -164,10 +167,59 @@ where #[derive(Debug, Default, Clone, Copy)] struct TransparentCheatcodesExecutor; -impl CheatcodesExecutor for TransparentCheatcodesExecutor { - fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box { - Box::new(cheats) +impl CheatcodesExecutor for TransparentCheatcodesExecutor { + fn with_nested_evm( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result<(), EVMError> { + with_cloned_context(ecx, |db, evm_env, journal_inner| { + let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, cheats); + *evm.journal_inner_mut() = journal_inner; + f(&mut *evm)?; + let sub_inner = evm.journal_inner_mut().clone(); + let sub_evm_env = evm.to_evm_env(); + Ok((sub_evm_env, sub_inner)) + }) + } + + fn with_fresh_nested_evm( + &mut self, + cheats: &mut Cheatcodes, + db: &mut as ContextTr>::Db, + evm_env: EvmEnv, BlockEnvFor>, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result, BlockEnvFor>, EVMError> { + let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, cheats); + f(&mut *evm)?; + Ok(evm.to_evm_env()) } + + fn transact_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + fork_id: Option, + transaction: B256, + ) -> eyre::Result<()> { + let evm_env = ecx.evm_clone(); + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact(fork_id, transaction, evm_env, inner, cheats) + } + + fn transact_from_tx_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + tx: TxEnvFor, + ) -> eyre::Result<()> { + let evm_env = ecx.evm_clone(); + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact_from_tx(tx, evm_env, inner, cheats) + } + + fn console_log(&mut self, _msg: &str) {} } macro_rules! try_or_return { @@ -195,7 +247,6 @@ impl Clone for TestContext { impl TestContext { /// Clears the context. - #[inline] pub fn clear(&mut self) { self.opened_read_files.clear(); } @@ -203,11 +254,11 @@ impl TestContext { /// Helps collecting transactions from different forks. #[derive(Clone, Debug)] -pub struct BroadcastableTransaction { +pub struct BroadcastableTransaction { /// The optional RPC URL. pub rpc: Option, /// The transaction to broadcast. - pub transaction: TransactionMaybeSigned, + pub transaction: TransactionMaybeSigned, } #[derive(Clone, Debug, Copy)] @@ -248,12 +299,12 @@ pub struct GasMetering { impl GasMetering { /// Start the gas recording. - pub fn start(&mut self) { + pub const fn start(&mut self) { self.recording = true; } /// Stop the gas recording. - pub fn stop(&mut self) { + pub const fn stop(&mut self) { self.recording = false; } @@ -309,10 +360,18 @@ impl ArbitraryStorage { /// Saves arbitrary storage value for a given address: /// - store value in changed values cache. /// - update account's storage with given value. - pub fn save(&mut self, ecx: Ecx, address: Address, slot: U256, data: U256) { + pub fn save( + &mut self, + ecx: &mut CTX, + address: Address, + slot: U256, + data: U256, + ) { self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); - if let Ok(mut account) = ecx.journaled_state.load_account(address) { - account.storage.insert(slot, EvmStorageSlot::new(data)); + if ecx.journal_mut().load_account(address).is_ok() { + ecx.journal_mut() + .sstore(address, slot, data) + .expect("could not set arbitrary storage value"); } } @@ -321,7 +380,13 @@ impl ArbitraryStorage { /// existing value. /// - if no value was yet generated for given slot, then save new value in cache and update both /// source and target storages. - pub fn copy(&mut self, ecx: Ecx, target: Address, slot: U256, new_value: U256) -> U256 { + pub fn copy( + &mut self, + ecx: &mut CTX, + target: Address, + slot: U256, + new_value: U256, + ) -> U256 { let source = self.copies.get(&target).expect("missing arbitrary copy target entry"); let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage"); let value = match storage_cache.get(&slot) { @@ -329,22 +394,24 @@ impl ArbitraryStorage { None => { storage_cache.insert(slot, new_value); // Update source storage with new value. - if let Ok(mut source_account) = ecx.journaled_state.load_account(*source) { - source_account.storage.insert(slot, EvmStorageSlot::new(new_value)); + if ecx.journal_mut().load_account(*source).is_ok() { + ecx.journal_mut() + .sstore(*source, slot, new_value) + .expect("could not copy arbitrary storage value"); } new_value } }; // Update target storage with new value. - if let Ok(mut target_account) = ecx.journaled_state.load_account(target) { - target_account.storage.insert(slot, EvmStorageSlot::new(value)); + if ecx.journal_mut().load_account(target).is_ok() { + ecx.journal_mut().sstore(target, slot, value).expect("could not set storage"); } value } } /// List of transactions that can be broadcasted. -pub type BroadcastableTransactions = VecDeque; +pub type BroadcastableTransactions = VecDeque>; /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// @@ -364,20 +431,23 @@ pub type BroadcastableTransactions = VecDeque; /// cheatcode address: by default, the caller, test contract and newly deployed contracts are /// allowed to execute cheatcodes #[derive(Clone, Debug)] -pub struct Cheatcodes { +pub struct Cheatcodes { + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities + pub analysis: Option, + /// The block environment /// /// Used in the cheatcode handler to overwrite the block environment separately from the /// execution block environment. - pub block: Option, + pub block: Option>, - /// Currently active EIP-7702 delegation that will be consumed when building the next + /// Currently active EIP-7702 delegations that will be consumed when building the next /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during /// transaction construction. - pub active_delegation: Option, + pub active_delegations: Vec, /// The active EIP-4844 blob that will be attached to the next call. - pub active_blob_sidecar: Option, + pub active_blob_sidecar: Option, /// The gas price. /// @@ -440,7 +510,7 @@ pub struct Cheatcodes { pub broadcast: Option, /// Scripting based transactions - pub broadcastable_transactions: BroadcastableTransactions, + pub broadcastable_transactions: BroadcastableTransactions, /// Current EIP-2930 access lists. pub access_list: Option, @@ -485,9 +555,6 @@ pub struct Cheatcodes { /// strategies. test_runner: Option, - /// Temp Rng since proptest hasn't been updated to rand 0.9 - rng: Option, - /// Ignored traces. pub ignored_traces: IgnoredTraces, @@ -498,6 +565,12 @@ pub struct Cheatcodes { pub deprecated: HashMap<&'static str, Option<&'static str>>, /// Unlocked wallets used in scripts and testing of scripts. pub wallets: Option, + /// Signatures identifier for decoding events and functions + signatures_identifier: OnceLock>, + /// Used to determine whether the broadcasted call has dynamic gas limit. + pub dynamic_gas_limit: bool, + // Custom execution evm version. + pub execution_evm_version: Option>, } // This is not derived because calling this in `fn new` with `..Default::default()` creates a second @@ -509,15 +582,16 @@ impl Default for Cheatcodes { } } -impl Cheatcodes { +impl Cheatcodes { /// Creates a new `Cheatcodes` with the given settings. pub fn new(config: Arc) -> Self { Self { + analysis: None, fs_commit: true, labels: config.labels.clone(), config, block: Default::default(), - active_delegation: Default::default(), + active_delegations: Default::default(), active_blob_sidecar: Default::default(), gas_price: Default::default(), pranks: Default::default(), @@ -552,10 +626,17 @@ impl Cheatcodes { arbitrary_storage: Default::default(), deprecated: Default::default(), wallets: Default::default(), - rng: Default::default(), + signatures_identifier: Default::default(), + dynamic_gas_limit: Default::default(), + execution_evm_version: None, } } + /// Enables cheatcode analysis capabilities by providing a solar compiler instance. + pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) { + self.analysis = Some(analysis); + } + /// Returns the configured prank at given depth or the first prank configured at a lower depth. /// For example, if pranks configured for depth 1, 3 and 5, the prank for depth 4 is the one /// configured at depth 3. @@ -573,12 +654,22 @@ impl Cheatcodes { self.wallets = Some(wallets); } + /// Adds a delegation to the active delegations list. + pub fn add_delegation(&mut self, authorization: SignedAuthorization) { + self.active_delegations.push(authorization); + } + + /// Returns the signatures identifier. + pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> { + self.signatures_identifier.get_or_init(|| SignaturesIdentifier::new(true).ok()).as_ref() + } + /// Decodes the input data and applies the cheatcode. fn apply_cheatcode( &mut self, - ecx: Ecx, + ecx: &mut FoundryContextFor<'_, FEN>, call: &CallInputs, - executor: &mut dyn CheatcodesExecutor, + executor: &mut dyn CheatcodesExecutor, ) -> Result { // decode the cheatcode call let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| { @@ -597,7 +688,7 @@ impl Cheatcodes { // ensure the caller is allowed to execute cheatcodes, // but only if the backend is in forking mode - ecx.journaled_state.database.ensure_cheatcode_access_forking_mode(&caller)?; + ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?; apply_dispatch( &decoded, @@ -611,11 +702,14 @@ impl Cheatcodes { /// /// There may be cheatcodes in the constructor of the new contract, in order to allow them /// automatically we need to determine the new address. - fn allow_cheatcodes_on_create(&self, ecx: Ecx, caller: Address, created_address: Address) { - if ecx.journaled_state.depth <= 1 || - ecx.journaled_state.database.has_cheatcode_access(&caller) - { - ecx.journaled_state.database.allow_cheatcode_access(created_address); + fn allow_cheatcodes_on_create( + &self, + ecx: &mut FoundryContextFor, + caller: Address, + created_address: Address, + ) { + if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) { + ecx.db_mut().allow_cheatcode_access(created_address); } } @@ -624,12 +718,12 @@ impl Cheatcodes { /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support /// access lists themselves. - fn apply_accesslist(&mut self, ecx: Ecx) { + fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor) { if let Some(access_list) = &self.access_list { - ecx.tx.access_list = access_list.clone(); + ecx.tx_mut().set_access_list(access_list.clone()); - if ecx.tx.tx_type == TransactionType::Legacy as u8 { - ecx.tx.tx_type = TransactionType::Eip2930 as u8; + if ecx.tx().tx_type() == TransactionType::Legacy as u8 { + ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8); } } } @@ -638,7 +732,7 @@ impl Cheatcodes { /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. - pub fn on_revert(&mut self, ecx: Ecx) { + pub fn on_revert(&mut self, ecx: &mut FoundryContextFor) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. @@ -647,7 +741,7 @@ impl Cheatcodes { } // we only want to apply cleanup top level - if ecx.journaled_state.depth() > 0 { + if ecx.journal().depth() > 0 { return; } @@ -655,291 +749,31 @@ impl Cheatcodes { // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine // which rolls back any transfers. while let Some(record) = self.eth_deals.pop() { - if let Some(acc) = ecx.journaled_state.state.get_mut(&record.address) { + if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) { acc.info.balance = record.old_balance; } } } - // common create functionality for both legacy and EOF. - fn create_common(&mut self, ecx: Ecx, mut input: Input) -> Option - where - Input: CommonCreateInput, - { - // Check if we should intercept this create - if self.intercept_next_create_call { - // Reset the flag - self.intercept_next_create_call = false; - - // Get initcode from the input - let output = input.init_code(); - - // Return a revert with the initcode as error data - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas: Gas::new(input.gas_limit()), - }, - address: None, - }); - } - - let gas = Gas::new(input.gas_limit()); - let curr_depth = ecx.journaled_state.depth(); - - // Apply our prank - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth >= prank.depth && input.caller() == prank.prank_caller { - let mut prank_applied = false; - - // At the target depth we set `msg.sender` - if curr_depth == prank.depth { - input.set_caller(prank.new_caller); - prank_applied = true; - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; - prank_applied = true; - } - - // If prank applied for first time, then update - if prank_applied { - if let Some(applied_prank) = prank.first_time_applied() { - self.pranks.insert(curr_depth, applied_prank); - } - } - } - } - - // Apply EIP-2930 access list - self.apply_accesslist(ecx); - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - if curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { - if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Error::encode(err), - gas, - }, - address: None, - }); - } - - ecx.tx.caller = broadcast.new_origin; - - if curr_depth == broadcast.depth { - input.set_caller(broadcast.new_origin); - let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit()); - - let account = &ecx.journaled_state.inner.state()[&broadcast.new_origin]; - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.journaled_state.database.active_fork_url(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: None, - value: Some(input.value()), - input: TransactionInput::new(input.init_code()), - nonce: Some(account.info.nonce), - gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, - ..Default::default() - } - .into(), - }); - - input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); - } - } - } - - // Allow cheatcodes from the address of the new contract - let address = input.allow_cheatcodes(self, ecx); - - // If `recordAccountAccesses` has been called, record the create - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - recorded_account_diffs_stack.push(vec![AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), - }, - accessor: input.caller(), - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on (eof)create_end - newBalance: U256::ZERO, // updated on (eof)create_end - value: input.value(), - data: input.init_code(), - reverted: false, - deployedCode: Bytes::new(), // updated on (eof)create_end - storageAccesses: vec![], // updated on (eof)create_end - depth: curr_depth as u64, - }]); - } - - None - } - - // common create_end functionality for both legacy and EOF. - fn create_end_common( - &mut self, - ecx: Ecx, - call: Option<&CreateInputs>, - outcome: &mut CreateOutcome, - ) { - let curr_depth = ecx.journaled_state.depth(); - - // Clean up pranks - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth == prank.depth { - ecx.tx.caller = prank.prank_origin; - - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - std::mem::take(&mut self.pranks); - } - } - } - - // Clean up broadcasts - if let Some(broadcast) = &self.broadcast { - if curr_depth == broadcast.depth { - ecx.tx.caller = broadcast.original_origin; - - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - } - - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if curr_depth <= expected_revert.depth && - matches!(expected_revert.kind, ExpectedRevertKind::Default) - { - let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match revert_handlers::handle_expect_revert( - false, - true, - self.config.internal_expect_revert, - &expected_revert, - outcome.result.result, - outcome.result.output.clone(), - &self.config.available_artifacts, - ) { - Ok((address, retdata)) => { - expected_revert.actual_count += 1; - if expected_revert.actual_count < expected_revert.count { - self.expected_revert = Some(expected_revert.clone()); - } - - outcome.result.result = InstructionResult::Return; - outcome.result.output = retdata; - outcome.address = address; - } - Err(err) => { - outcome.result.result = InstructionResult::Revert; - outcome.result.output = err.abi_encode().into(); - } - }; - } - } - - // If `startStateDiffRecording` has been called, update the `reverted` status of the - // previous call depth's recorded accesses, if any - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - // The root call cannot be recorded. - if curr_depth > 0 { - if let Some(last_depth) = &mut recorded_account_diffs_stack.pop() { - // Update the reverted status of all deeper calls if this call reverted, in - // accordance with EVM behavior - if outcome.result.is_revert() { - last_depth.iter_mut().for_each(|element| { - element.reverted = true; - element - .storageAccesses - .iter_mut() - .for_each(|storage_access| storage_access.reverted = true); - }) - } - - if let Some(create_access) = last_depth.first_mut() { - // Assert that we're at the correct depth before recording post-create state - // changes. Depending on what depth the cheat was called at, there - // may not be any pending calls to update if execution has - // percolated up to a higher depth. - let depth = ecx.journaled_state.depth(); - if create_access.depth == depth as u64 { - debug_assert_eq!( - create_access.kind as u8, - crate::Vm::AccountAccessKind::Create as u8 - ); - if let Some(address) = outcome.address { - if let Ok(created_acc) = ecx.journaled_state.load_account(address) { - create_access.newBalance = created_acc.info.balance; - create_access.deployedCode = created_acc - .info - .code - .clone() - .unwrap_or_default() - .original_bytes(); - } - } - } - // Merge the last depth's AccountAccesses into the AccountAccesses at the - // current depth, or push them back onto the pending - // vector if higher depths were not recorded. This - // preserves ordering of accesses. - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.append(last_depth); - } else { - recorded_account_diffs_stack.push(last_depth.clone()); - } - } - } - } - } - - // Match the create against expected_creates - if !self.expected_creates.is_empty() { - if let (Some(address), Some(call)) = (outcome.address, call) { - if let Ok(created_acc) = ecx.journaled_state.load_account(address) { - let bytecode = - created_acc.info.code.clone().unwrap_or_default().original_bytes(); - if let Some((index, _)) = - self.expected_creates.iter().find_position(|expected_create| { - expected_create.deployer == call.caller && - expected_create.create_scheme.eq(call.scheme.into()) && - expected_create.bytecode == bytecode - }) - { - self.expected_creates.swap_remove(index); - } - } - } - } - } - pub fn call_with_executor( &mut self, - ecx: Ecx, + ecx: &mut FoundryContextFor<'_, FEN>, call: &mut CallInputs, - executor: &mut impl CheatcodesExecutor, + executor: &mut dyn CheatcodesExecutor, ) -> Option { + // Apply custom execution evm version. + if let Some(spec_id) = self.execution_evm_version { + ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id); + } + let gas = Gas::new(call.gas_limit); - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); // At the root call to test function or script `run()`/`setUp()` functions, we are // decreasing sender nonce to ensure that it matches on-chain nonce once we start // broadcasting. if curr_depth == 0 { - let sender = ecx.tx.caller; + let sender = ecx.tx().caller(); let account = match super::evm::journaled_account(ecx, sender) { Ok(account) => account, Err(err) => { @@ -950,7 +784,9 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), - }) + was_precompile_called: false, + precompile_call_logs: vec![], + }); } }; let prev = account.info.nonce; @@ -968,6 +804,8 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], }), Err(err) => Some(CallOutcome { result: InterpreterResult { @@ -976,6 +814,8 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], }), }; } @@ -984,6 +824,13 @@ impl Cheatcodes { return None; } + // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but + // precompile calls don't create an interpreter frame so we must also track it here. + // The callee executes at `curr_depth + 1`. + if let Some(expected) = &mut self.expected_revert { + expected.max_depth = max(curr_depth + 1, expected.max_depth); + } + // Handle expected calls // Grab the different calldatas expected. @@ -1009,80 +856,109 @@ impl Cheatcodes { } } - // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { - let ctx = MockCallDataContext { - calldata: call.input.bytes(ecx), - value: call.transfer_value(), - }; - - if let Some(return_data_queue) = - match mocks.get_mut(&ctx) { - Some(queue) => Some(queue), - None => mocks - .iter_mut() - .find(|(mock, _)| { - call.input.bytes(ecx).get(..mock.calldata.len()) == - Some(&mock.calldata[..]) && - mock.value - .is_none_or(|value| Some(value) == call.transfer_value()) - }) - .map(|(_, v)| v), - } - { - if let Some(return_data) = if return_data_queue.len() == 1 { - // If the mocked calls stack has a single element in it, don't empty it - return_data_queue.front().map(|x| x.to_owned()) - } else { - // Else, we pop the front element - return_data_queue.pop_front() - } { - return Some(CallOutcome { - result: InterpreterResult { - result: return_data.ret_type, - output: return_data.data, - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }); - } - } - } - // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` - if prank.delegate_call && curr_depth == prank.depth { - if let CallScheme::DelegateCall | CallScheme::ExtDelegateCall = call.scheme { - call.target_address = prank.new_caller; - call.caller = prank.new_caller; - if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; - } + if prank.delegate_call + && curr_depth == prank.depth + && call.scheme == CallScheme::DelegateCall + { + call.target_address = prank.new_caller; + call.caller = prank.new_caller; + if let Some(new_origin) = prank.new_origin { + ecx.tx_mut().set_caller(new_origin); } } if curr_depth >= prank.depth && call.caller == prank.prank_caller { - let mut prank_applied = false; - // At the target depth we set `msg.sender` - if curr_depth == prank.depth { + let prank_applied = if curr_depth == prank.depth { + // Ensure new caller is loaded and touched + let _ = journaled_account(ecx, prank.new_caller); call.caller = prank.new_caller; - prank_applied = true; - } + true + } else { + false + }; // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; - prank_applied = true; - } + let prank_applied = if let Some(new_origin) = prank.new_origin { + ecx.tx_mut().set_caller(new_origin); + true + } else { + prank_applied + }; // If prank applied for first time, then update - if prank_applied { - if let Some(applied_prank) = prank.first_time_applied() { - self.pranks.insert(curr_depth, applied_prank); + if prank_applied && let Some(applied_prank) = prank.first_time_applied() { + self.pranks.insert(curr_depth, applied_prank); + } + } + } + + // Handle mocked calls + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { + let ctx = MockCallDataContext { + calldata: call.input.bytes(ecx), + value: call.transfer_value(), + }; + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..]) + && mock.value.is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned()) + { + if let Some(value) = call.transfer_value() { + let checkpoint = ecx.journal_mut().checkpoint(); + match ecx.journal_mut().transfer_loaded( + call.transfer_from(), + call.transfer_to(), + value, + ) { + None => { + if return_data.ret_type.is_ok() { + ecx.journal_mut().checkpoint_commit(); + } else { + ecx.journal_mut().checkpoint_revert(checkpoint); + } + } + Some(err) => { + ecx.journal_mut().checkpoint_revert(checkpoint); + return Some(CallOutcome { + result: InterpreterResult { + result: err.into(), + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], + }); + } } } + + // If the mocked calls stack has a single element in it, don't empty it + if return_data_queue.len() > 1 { + return_data_queue.pop_front(); + } + + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], + }); } } @@ -1091,6 +967,11 @@ impl Cheatcodes { // Apply our broadcast if let Some(broadcast) = &self.broadcast { + // Additional check as transfers in forge scripts seem to be estimated at 2300 + // by revm leading to "Intrinsic gas too low" failure when simulated on chain. + let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit; + self.dynamic_gas_limit = false; + // We only apply a broadcast *to a specific depth*. // // We do this because any subsequent contract calls *must* exist on chain and @@ -1099,7 +980,7 @@ impl Cheatcodes { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the // broadcast.origin. - ecx.tx.caller = broadcast.new_origin; + ecx.tx_mut().set_caller(broadcast.new_origin); call.caller = broadcast.new_origin; // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here @@ -1107,7 +988,7 @@ impl Cheatcodes { // into 1559, in the cli package, relatively easily once we // know the target chain supports EIP-1559. if !call.is_static { - if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { + if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1115,29 +996,33 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], }); } - let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, call.gas_limit); - - let input = TransactionInput::new(call.input.bytes(ecx)); - + let input = call.input.bytes(ecx); + let chain_id = ecx.cfg().chain_id(); + let rpc = ecx.db().active_fork_url(); let account = - ecx.journaled_state.inner.state().get_mut(&broadcast.new_origin).unwrap(); - - let mut tx_req = TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(TxKind::from(Some(call.target_address))), - value: call.transfer_value(), - input, - nonce: Some(account.info.nonce), - chain_id: Some(ecx.cfg.chain_id), - gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, - ..Default::default() - }; + ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap(); + + let mut tx_req = TransactionRequestFor::::default() + .with_from(broadcast.new_origin) + .with_to(call.target_address) + .with_value(call.transfer_value().unwrap_or_default()) + .with_input(input) + .with_nonce(account.info.nonce) + .with_chain_id(chain_id); + if is_fixed_gas_limit { + tx_req.set_gas_limit(call.gas_limit) + } - match (self.active_delegation.take(), self.active_blob_sidecar.take()) { - (Some(_), Some(_)) => { + let active_delegations = std::mem::take(&mut self.active_delegations); + // Set active blob sidecar, if any. + if let Some(blob_sidecar) = self.active_blob_sidecar.take() { + // Ensure blob and delegation are not set for the same tx. + if !active_delegations.is_empty() { let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible"; return Some(CallOutcome { result: InterpreterResult { @@ -1146,28 +1031,33 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], }); } - (Some(auth_list), None) => { - tx_req.authorization_list = Some(vec![auth_list]); - tx_req.sidecar = None; + tx_req.set_blob_sidecar(blob_sidecar); + } - // Increment nonce to reflect the signed authorization. - account.info.nonce += 1; - } - (None, Some(blob_sidecar)) => { - tx_req.set_blob_sidecar(blob_sidecar); - tx_req.authorization_list = None; - } - (None, None) => { - tx_req.sidecar = None; - tx_req.authorization_list = None; + // Apply active EIP-7702 delegations, if any. + if !active_delegations.is_empty() { + for auth in &active_delegations { + let Ok(authority) = auth.recover_authority() else { + continue; + }; + if authority == broadcast.new_origin { + // Increment nonce of broadcasting account to reflect signed + // authorization. + account.info.nonce += 1; + } } + tx_req.set_authorization_list(active_delegations); + } + if let Some(fee_token) = self.config.fee_token { + tx_req.set_fee_token(fee_token); } - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.journaled_state.database.active_fork_url(), - transaction: tx_req.into(), + rpc, + transaction: TransactionMaybeSigned::new(tx_req), }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); @@ -1178,8 +1068,7 @@ impl Cheatcodes { debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } } else if broadcast.single_call { - let msg = - "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; + let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1187,6 +1076,8 @@ impl Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: false, + precompile_call_logs: vec![], }); } } @@ -1196,24 +1087,20 @@ impl Cheatcodes { if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code - let initialized; - let old_balance; - if let Ok(acc) = ecx.journaled_state.load_account(call.target_address) { - initialized = acc.info.exists(); - old_balance = acc.info.balance; - } else { - initialized = false; - old_balance = U256::ZERO; - } + let (initialized, old_balance, old_nonce) = + if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { + (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce) + } else { + (false, U256::ZERO, 0) + }; + let kind = match call.scheme { CallScheme::Call => crate::Vm::AccountAccessKind::Call, CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall, - CallScheme::ExtCall => crate::Vm::AccountAccessKind::Call, - CallScheme::ExtStaticCall => crate::Vm::AccountAccessKind::StaticCall, - CallScheme::ExtDelegateCall => crate::Vm::AccountAccessKind::DelegateCall, }; + // Record this call by pushing it to a new pending vector; all subsequent calls at // that depth will be pushed to the same vector. When the call ends, the // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be @@ -1221,8 +1108,8 @@ impl Cheatcodes { // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: call.caller, account: call.bytecode_address, @@ -1230,16 +1117,14 @@ impl Cheatcodes { initialized, oldBalance: old_balance, newBalance: U256::ZERO, // updated on call_end + oldNonce: old_nonce, + newNonce: 0, // updated on call_end value: call.call_value(), data: call.input.bytes(ecx), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step - depth: ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"), + depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"), }]); } @@ -1247,12 +1132,7 @@ impl Cheatcodes { } pub fn rng(&mut self) -> &mut impl Rng { - // Prop test uses rand 8 whereas alloy-core has been bumped to rand 9 - // self.test_runner().rng() - self.rng.get_or_insert_with(|| match self.config.seed { - Some(seed) => ChaChaRng::from_seed(seed.to_be_bytes::<32>()), - None => ChaChaRng::from_os_rng(), - }) + self.test_runner().rng() } pub fn test_runner(&mut self) -> &mut TestRunner { @@ -1265,6 +1145,13 @@ impl Cheatcodes { }) } + pub fn set_seed(&mut self, seed: U256) { + self.test_runner = Some(TestRunner::new_with_rng( + proptest::test_runner::Config::default(), + TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()), + )); + } + /// Returns existing or set a default `ArbitraryStorage` option. /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage. pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage { @@ -1289,8 +1176,8 @@ impl Cheatcodes { ) -> bool { match &self.arbitrary_storage { Some(storage) => { - storage.overwrites.contains(address) && - storage + storage.overwrites.contains(address) + && storage .values .get(address) .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot)) @@ -1307,35 +1194,46 @@ impl Cheatcodes { None => false, } } + + /// Returns struct definitions from the analysis, if available. + pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> { + self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()) + } } -impl Inspector> for Cheatcodes { - #[inline] - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { +impl Inspector> for Cheatcodes { + fn initialize_interp( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut FoundryContextFor<'_, FEN>, + ) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { - ecx.block = block; + ecx.set_block(block); } if let Some(gas_price) = self.gas_price.take() { - ecx.tx.gas_price = gas_price; + ecx.tx_mut().set_gas_price(gas_price); } // Record gas for current frame. if self.gas_metering.paused { - self.gas_metering.paused_frames.push(interpreter.control.gas); + self.gas_metering.paused_frames.push(interpreter.gas); } // `expectRevert`: track the max call depth during `expectRevert` if let Some(expected) = &mut self.expected_revert { - expected.max_depth = max(ecx.journaled_state.depth(), expected.max_depth); + expected.max_depth = max(ecx.journal().depth(), expected.max_depth); } } - #[inline] - fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { self.pc = interpreter.bytecode.pc(); + if self.broadcast.is_some() { + self.set_gas_limit_type(interpreter); + } + // `pauseGasMetering`: pause / resume interpreter gas. if self.gas_metering.paused { self.meter_gas(interpreter); @@ -1360,13 +1258,13 @@ impl Inspector> for Cheatcodes { if !self.allowed_mem_writes.is_empty() { self.check_mem_opcodes( interpreter, - ecx.journaled_state.depth().try_into().expect("journaled state depth exceeds u64"), + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"), ); } // `startMappingRecording`: record SSTORE and KECCAK256. if let Some(mapping_slots) = &mut self.mapping_slots { - mapping::step(mapping_slots, interpreter); + mapping_step(mapping_slots, interpreter); } // `snapshotGas*`: take a snapshot of the current gas. @@ -1375,8 +1273,7 @@ impl Inspector> for Cheatcodes { } } - #[inline] - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -1385,61 +1282,83 @@ impl Inspector> for Cheatcodes { self.meter_gas_check(interpreter); } - // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage. - if self.arbitrary_storage.is_some() { - self.arbitrary_storage_end(interpreter, ecx); - } + // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage. + if self.arbitrary_storage.is_some() { + self.arbitrary_storage_end(interpreter, ecx); + } + } + + fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) { + if !self.expected_emits.is_empty() + && let Some(err) = expect::handle_expect_emit(self, &log, None) + { + // Because we do not have access to the interpreter here, we cannot fail the test + // immediately. In most cases the failure will still be caught on `call_end`. + // In the rare case it is not, we log the error here. + let _ = sh_err!("{err:?}"); + } + + // `recordLogs` + record_logs(&mut self.recorded_logs, &log); } - fn log(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: Log) { + fn log_full( + &mut self, + interpreter: &mut Interpreter, + _ecx: &mut FoundryContextFor<'_, FEN>, + log: Log, + ) { if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, &log, interpreter); + expect::handle_expect_emit(self, &log, Some(interpreter)); } // `recordLogs` - if let Some(storage_recorded_logs) = &mut self.recorded_logs { - storage_recorded_logs.push(Vm::Log { - topics: log.data.topics().to_vec(), - data: log.data.data.clone(), - emitter: log.address, - }); - } + record_logs(&mut self.recorded_logs, &log); } - fn call(&mut self, ecx: Ecx, inputs: &mut CallInputs) -> Option { + fn call( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + inputs: &mut CallInputs, + ) -> Option { Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) } - fn call_end(&mut self, ecx: Ecx, call: &CallInputs, outcome: &mut CallOutcome) { - let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || - call.target_address == HARDHAT_CONSOLE_ADDRESS; + fn call_end( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + call: &CallInputs, + outcome: &mut CallOutcome, + ) { + let cheatcode_call = call.target_address == CHEATCODE_ADDRESS + || call.target_address == HARDHAT_CONSOLE_ADDRESS; // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook. // This should be placed before the revert handling, because we might exit early there if !cheatcode_call { // Clean up pranks - let curr_depth = ecx.journaled_state.depth(); - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth == prank.depth { - ecx.tx.caller = prank.prank_origin; - - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - self.pranks.remove(&curr_depth); - } + let curr_depth = ecx.journal().depth(); + if let Some(prank) = &self.get_prank(curr_depth) + && curr_depth == prank.depth + { + ecx.tx_mut().set_caller(prank.prank_origin); + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + self.pranks.remove(&curr_depth); } } // Clean up broadcast - if let Some(broadcast) = &self.broadcast { - if curr_depth == broadcast.depth { - ecx.tx.caller = broadcast.original_origin; + if let Some(broadcast) = &self.broadcast + && curr_depth == broadcast.depth + { + ecx.tx_mut().set_caller(broadcast.original_origin); - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - let _ = self.broadcast.take(); - } + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + let _ = self.broadcast.take(); } } } @@ -1453,7 +1372,7 @@ impl Inspector> for Cheatcodes { } // allow multiple cheatcode calls at the same depth - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth <= assume_no_revert.depth && !cheatcode_call { // Discard run if we're at the same depth as cheatcode, call reverted, and no // specific reason was supplied @@ -1477,11 +1396,10 @@ impl Inspector> for Cheatcodes { outcome.result.result = InstructionResult::Revert; outcome.result.output = error.abi_encode().into(); } - } - } else { - // Call didn't revert, reset `assume_no_revert` state. - self.assume_no_revert = None; + }; } + // Call didn't revert, reset `assume_no_revert` state. + self.assume_no_revert = None; } } @@ -1493,14 +1411,14 @@ impl Inspector> for Cheatcodes { // Record current reverter address if expect revert is set with expected reverter // address and no actual reverter was set yet or if we're expecting more than one // revert. - if expected_revert.reverter.is_some() && - (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + if expected_revert.reverter.is_some() + && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) { expected_revert.reverted_by = Some(call.target_address); } } - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth <= expected_revert.depth { let needs_processing = match expected_revert.kind { ExpectedRevertKind::Default => !cheatcode_call, @@ -1530,7 +1448,7 @@ impl Inspector> for Cheatcodes { Ok((_, retdata)) => { expected_revert.actual_count += 1; if expected_revert.actual_count < expected_revert.count { - self.expected_revert = Some(expected_revert.clone()); + self.expected_revert = Some(expected_revert); } outcome.result.result = InstructionResult::Return; outcome.result.output = retdata; @@ -1559,7 +1477,7 @@ impl Inspector> for Cheatcodes { let gas = outcome.result.gas; self.gas_metering.last_call_gas = Some(crate::Vm::Gas { gasLimit: gas.limit(), - gasTotalUsed: gas.spent(), + gasTotalUsed: gas.total_gas_spent(), gasMemoryUsed: 0, gasRefunded: gas.refunded(), gasRemaining: gas.remaining(), @@ -1569,46 +1487,61 @@ impl Inspector> for Cheatcodes { // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // The root call cannot be recorded. - if ecx.journaled_state.depth() > 0 { - if let Some(last_recorded_depth) = &mut recorded_account_diffs_stack.pop() { - // Update the reverted status of all deeper calls if this call reverted, in - // accordance with EVM behavior - if outcome.result.is_revert() { - last_recorded_depth.iter_mut().for_each(|element| { - element.reverted = true; - element - .storageAccesses - .iter_mut() - .for_each(|storage_access| storage_access.reverted = true); - }) + if ecx.journal().depth() > 0 + && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop() + { + // Update the reverted status of all deeper calls if this call reverted, in + // accordance with EVM behavior + if outcome.result.is_revert() { + for element in &mut *last_recorded_depth { + element.reverted = true; + for storage_access in &mut element.storageAccesses { + storage_access.reverted = true; + } } + } - if let Some(call_access) = last_recorded_depth.first_mut() { - // Assert that we're at the correct depth before recording post-call state - // changes. Depending on the depth the cheat was - // called at, there may not be any pending - // calls to update if execution has percolated up to a higher depth. - let curr_depth = ecx.journaled_state.depth(); - if call_access.depth == curr_depth as u64 { - if let Ok(acc) = ecx.journaled_state.load_account(call.target_address) { - debug_assert!(access_is_call(call_access.kind)); - call_access.newBalance = acc.info.balance; - } - } - // Merge the last depth's AccountAccesses into the AccountAccesses at the - // current depth, or push them back onto the pending - // vector if higher depths were not recorded. This - // preserves ordering of accesses. - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.append(last_recorded_depth); - } else { - recorded_account_diffs_stack.push(last_recorded_depth.clone()); - } + if let Some(call_access) = last_recorded_depth.first_mut() { + // Assert that we're at the correct depth before recording post-call state + // changes. Depending on the depth the cheat was + // called at, there may not be any pending + // calls to update if execution has percolated up to a higher depth. + let curr_depth = ecx.journal().depth(); + if call_access.depth == curr_depth as u64 + && let Ok(acc) = ecx.journal_mut().load_account(call.target_address) + { + debug_assert!(access_is_call(call_access.kind)); + call_access.newBalance = acc.data.info.balance; + call_access.newNonce = acc.data.info.nonce; + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the + // current depth, or push them back onto the pending + // vector if higher depths were not recorded. This + // preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.extend(last_recorded_depth); + } else { + recorded_account_diffs_stack.push(last_recorded_depth); } } } } + // this will ensure we don't have false positives when trying to diagnose reverts in fork + // mode + let diag = self.fork_revert_diagnostic.take(); + + // If the call already reverted, preserve that primary failure and skip post-call + // expect* validation so it cannot overwrite the original revert. + if outcome.result.is_revert() { + // if there's a revert and a previous call was diagnosed as fork related revert then we + // can return a better error here + if let Some(err) = diag { + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + } + return; + } + // At the end of the call, // we need to check if we've found all the emits. // We know we've found all the expected emits in the right order @@ -1624,7 +1557,7 @@ impl Inspector> for Cheatcodes { .expected_emits .iter() .any(|(expected, _)| { - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); expected.depth == curr_depth }) && // Ignore staticcalls @@ -1649,19 +1582,19 @@ impl Inspector> for Cheatcodes { }, }; - if count != expected.count { - Some((expected, count)) - } else { - None - } + (count != expected.count).then_some((expected, count)) }) .collect::>(); // Revert if not all emits expected were matched. - if self.expected_emits.iter().any(|(expected, _)| !expected.found && expected.count > 0) + if let Some((expected, _)) = self + .expected_emits + .iter() + .find(|(expected, _)| !expected.found && expected.count > 0) { outcome.result.result = InstructionResult::Revert; - outcome.result.output = "log != expected log".abi_encode().into(); + let error_msg = expected.mismatch_error.as_deref().unwrap_or("log != expected log"); + outcome.result.output = error_msg.abi_encode().into(); return; } @@ -1686,36 +1619,22 @@ impl Inspector> for Cheatcodes { self.expected_emits.clear() } - // this will ensure we don't have false positives when trying to diagnose reverts in fork - // mode - let diag = self.fork_revert_diagnostic.take(); - - // if there's a revert and a previous call was diagnosed as fork related revert then we can - // return a better error here - if outcome.result.is_revert() { - if let Some(err) = diag { - outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return; - } - } - // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TxKind::Call(test_contract) = ecx.tx.kind { + if let TxKind::Call(test_contract) = ecx.tx().kind() { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork - if ecx.journaled_state.db().is_forked_mode() && - outcome.result.result == InstructionResult::Stop && - call.target_address != test_contract + if ecx.db().is_forked_mode() + && outcome.result.result == InstructionResult::Stop + && call.target_address != test_contract { - let journaled_state = ecx.journaled_state.clone(); self.fork_revert_diagnostic = - ecx.journaled_state.db().diagnose_revert(call.target_address, &journaled_state); + ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state()); } } // If the depth is 0, then this is the root call terminating - if ecx.journaled_state.depth() == 0 { + if ecx.journal().depth() == 0 { // If we already have a revert, we shouldn't run the below logic as it can obfuscate an // earlier error that happened first with unrelated information about // another error when using cheatcodes. @@ -1777,7 +1696,13 @@ impl Inspector> for Cheatcodes { // Check if we have any leftover expected emits // First, if any emits were found at the root call, then we its ok and we remove them. - self.expected_emits.retain(|(expected, _)| expected.count > 0 && !expected.found); + // For count=0 expectations, NOT being found is success, so mark them as found + for (expected, _) in &mut self.expected_emits { + if expected.count == 0 && !expected.found { + expected.found = true; + } + } + self.expected_emits.retain(|(expected, _)| !expected.found); // If not empty, we got mismatched emits if !self.expected_emits.is_empty() { let msg = if outcome.result.is_ok() { @@ -1806,19 +1731,296 @@ impl Inspector> for Cheatcodes { } } - fn create(&mut self, ecx: Ecx, call: &mut CreateInputs) -> Option { - self.create_common(ecx, call) + fn create( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + mut input: &mut CreateInputs, + ) -> Option { + // Apply custom execution evm version. + if let Some(spec_id) = self.execution_evm_version { + ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id); + } + + let gas = Gas::new(input.gas_limit()); + // Check if we should intercept this create + if self.intercept_next_create_call { + // Reset the flag + self.intercept_next_create_call = false; + + // Get initcode from the input + let output = input.init_code(); + + // Return a revert with the initcode as error data + return Some(CreateOutcome { + result: InterpreterResult { result: InstructionResult::Revert, output, gas }, + address: None, + }); + } + + let curr_depth = ecx.journal().depth(); + + // Apply our prank + if let Some(prank) = &self.get_prank(curr_depth) + && curr_depth >= prank.depth + && input.caller() == prank.prank_caller + { + // At the target depth we set `msg.sender` + let prank_applied = if curr_depth == prank.depth { + // Ensure new caller is loaded and touched + let _ = journaled_account(ecx, prank.new_caller); + input.set_caller(prank.new_caller); + true + } else { + false + }; + + // At the target depth, or deeper, we set `tx.origin` + let prank_applied = if let Some(new_origin) = prank.new_origin { + ecx.tx_mut().set_caller(new_origin); + true + } else { + prank_applied + }; + + // If prank applied for first time, then update + if prank_applied && let Some(applied_prank) = prank.first_time_applied() { + self.pranks.insert(curr_depth, applied_prank); + } + } + + // Apply EIP-2930 access list + self.apply_accesslist(ecx); + + // Apply our broadcast + if let Some(broadcast) = &mut self.broadcast + && curr_depth >= broadcast.depth + && input.caller() == broadcast.original_caller + { + if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }); + } + + ecx.tx_mut().set_caller(broadcast.new_origin); + + if curr_depth == broadcast.depth || broadcast.deploy_from_code { + // Reset deploy from code flag for upcoming calls; + broadcast.deploy_from_code = false; + + input.set_caller(broadcast.new_origin); + + let rpc = ecx.db().active_fork_url(); + let account = &ecx.journal().evm_state()[&broadcast.new_origin]; + let mut tx_req = TransactionRequestFor::::default() + .with_from(broadcast.new_origin) + .with_kind(TxKind::Create) + .with_value(input.value()) + .with_input(input.init_code()) + .with_nonce(account.info.nonce); + if let Some(fee_token) = self.config.fee_token { + tx_req.set_fee_token(fee_token); + } + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc, + transaction: TransactionMaybeSigned::new(tx_req), + }); + + input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); + } + } + + // Allow cheatcodes from the address of the new contract + let address = input.allow_cheatcodes(self, ecx); + + // If `recordAccountAccesses` has been called, record the create + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + recorded_account_diffs_stack.push(vec![AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), + }, + accessor: input.caller(), + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + oldNonce: 0, // new contract starts with nonce 0 + newNonce: 1, // updated on create_end (contracts start with nonce 1) + value: input.value(), + data: input.init_code(), + reverted: false, + deployedCode: Bytes::new(), // updated on create_end + storageAccesses: vec![], // updated on create_end + depth: curr_depth as u64, + }]); + } + + None } - fn create_end(&mut self, ecx: Ecx, call: &CreateInputs, outcome: &mut CreateOutcome) { - self.create_end_common(ecx, Some(call), outcome) + fn create_end( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + call: &CreateInputs, + outcome: &mut CreateOutcome, + ) { + let call = Some(call); + let curr_depth = ecx.journal().depth(); + + // Clean up pranks + if let Some(prank) = &self.get_prank(curr_depth) + && curr_depth == prank.depth + { + ecx.tx_mut().set_caller(prank.prank_origin); + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.pranks); + } + } + + // Clean up broadcasts + if let Some(broadcast) = &self.broadcast + && curr_depth == broadcast.depth + { + ecx.tx_mut().set_caller(broadcast.original_origin); + + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } + } + + // Handle expected reverts + if let Some(expected_revert) = &mut self.expected_revert + && curr_depth <= expected_revert.depth + && matches!(expected_revert.kind, ExpectedRevertKind::Default) + { + // Mirror the logic in `call_end`: when an expected reverter address is set + // and we don't yet have one (or we're matching multiple reverts), record the + // would-be deployed address as the reverter. revm guarantees `outcome.address` + // is `Some(_)` whenever the constructor actually ran (including the revert + // case); it is only `None` for pre-frame rejection (depth/balance/nonce), + // for which a reverter address is meaningless. + if outcome.result.is_revert() + && expected_revert.reverter.is_some() + && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + && let Some(addr) = outcome.address + { + expected_revert.reverted_by = Some(addr); + } + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match revert_handlers::handle_expect_revert( + false, + true, + self.config.internal_expect_revert, + &expected_revert, + outcome.result.result, + outcome.result.output.clone(), + &self.config.available_artifacts, + ) { + Ok((address, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + } + Err(err) => { + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + } + }; + } + + // If `startStateDiffRecording` has been called, update the `reverted` status of the + // previous call depth's recorded accesses, if any + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + // The root call cannot be recorded. + if curr_depth > 0 + && let Some(last_depth) = &mut recorded_account_diffs_stack.pop() + { + // Update the reverted status of all deeper calls if this call reverted, in + // accordance with EVM behavior + if outcome.result.is_revert() { + for element in &mut *last_depth { + element.reverted = true; + for storage_access in &mut element.storageAccesses { + storage_access.reverted = true; + } + } + } + + if let Some(create_access) = last_depth.first_mut() { + // Assert that we're at the correct depth before recording post-create state + // changes. Depending on what depth the cheat was called at, there + // may not be any pending calls to update if execution has + // percolated up to a higher depth. + let depth = ecx.journal().depth(); + if create_access.depth == depth as u64 { + debug_assert_eq!( + create_access.kind as u8, + crate::Vm::AccountAccessKind::Create as u8 + ); + if let Some(address) = outcome.address + && let Ok(created_acc) = ecx.journal_mut().load_account(address) + { + create_access.newBalance = created_acc.data.info.balance; + create_access.newNonce = created_acc.data.info.nonce; + create_access.deployedCode = created_acc + .data + .info + .code + .clone() + .unwrap_or_default() + .original_bytes(); + } + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the + // current depth, or push them back onto the pending + // vector if higher depths were not recorded. This + // preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.append(last_depth); + } else { + recorded_account_diffs_stack.push(last_depth.clone()); + } + } + } + } + + // Match the create against expected_creates + if !self.expected_creates.is_empty() + && let (Some(address), Some(call)) = (outcome.address, call) + && let Ok(created_acc) = ecx.journal_mut().load_account(address) + { + let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes(); + if let Some((index, _)) = + self.expected_creates.iter().find_position(|expected_create| { + expected_create.deployer == call.caller() + && expected_create.create_scheme.eq(call.scheme().into()) + && expected_create.bytecode == bytecode + }) + { + self.expected_creates.swap_remove(index); + } + } } } -impl InspectorExt for Cheatcodes { - fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &CreateInputs) -> bool { - if let CreateScheme::Create2 { .. } = inputs.scheme { - let depth = ecx.journaled_state.depth(); +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { + if let CreateScheme::Create2 { .. } = inputs.scheme() { let target_depth = if let Some(prank) = &self.get_prank(depth) { prank.depth } else if let Some(broadcast) = &self.broadcast { @@ -1827,8 +2029,8 @@ impl InspectorExt for Cheatcodes { 1 }; - depth == target_depth && - (self.broadcast.is_some() || self.config.always_use_create_2_factory) + depth == target_depth + && (self.broadcast.is_some() || self.config.always_use_create_2_factory) } else { false } @@ -1839,42 +2041,45 @@ impl InspectorExt for Cheatcodes { } } -impl Cheatcodes { +impl Cheatcodes { #[cold] fn meter_gas(&mut self, interpreter: &mut Interpreter) { if let Some(paused_gas) = self.gas_metering.paused_frames.last() { // Keep gas constant if paused. // Make sure we record the memory changes so that memory expansion is not paused. - let memory = *interpreter.control.gas.memory(); - interpreter.control.gas = *paused_gas; - interpreter.control.gas.memory_mut().words_num = memory.words_num; - interpreter.control.gas.memory_mut().expansion_cost = memory.expansion_cost; + let memory = *interpreter.gas.memory(); + interpreter.gas = *paused_gas; + interpreter.gas.memory_mut().words_num = memory.words_num; + interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost; } else { // Record frame paused gas. - self.gas_metering.paused_frames.push(interpreter.control.gas); + self.gas_metering.paused_frames.push(interpreter.gas); } } #[cold] - fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - if matches!(interpreter.control.instruction_result, InstructionResult::Continue) { + fn meter_gas_record( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut FoundryContextFor<'_, FEN>, + ) { + if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() { self.gas_metering.gas_records.iter_mut().for_each(|record| { - let curr_depth = ecx.journaled_state.depth(); + let curr_depth = ecx.journal().depth(); if curr_depth == record.depth { // Skip the first opcode of the first call frame as it includes the gas cost of // creating the snapshot. if self.gas_metering.last_gas_used != 0 { let gas_diff = interpreter - .control .gas - .spent() + .total_gas_spent() .saturating_sub(self.gas_metering.last_gas_used); record.gas_used = record.gas_used.saturating_add(gas_diff); } // Update `last_gas_used` to the current spent gas for the next iteration to // compare against. - self.gas_metering.last_gas_used = interpreter.control.gas.spent(); + self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent(); } }); } @@ -1883,27 +2088,34 @@ impl Cheatcodes { #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. - if will_exit(interpreter.control.instruction_result) { + if let Some(interpreter_action) = interpreter.bytecode.action.as_ref() + && will_exit(interpreter_action) + { self.gas_metering.paused_frames.pop(); } } #[cold] fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) { - interpreter.control.gas = Gas::new(interpreter.control.gas.limit()); + let mut gas = Gas::new(interpreter.gas.limit()); + gas.memory_mut().words_num = interpreter.gas.memory().words_num; + gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost; + interpreter.gas = gas; self.gas_metering.reset = false; } #[cold] fn meter_gas_check(&mut self, interpreter: &mut Interpreter) { - if will_exit(interpreter.control.instruction_result) { + if let Some(interpreter_action) = interpreter.bytecode.action.as_ref() + && will_exit(interpreter_action) + { // Reset gas if spent is less than refunded. // This can happen if gas was paused / resumed or reset. // https://github.com/foundry-rs/foundry/issues/4370 - if interpreter.control.gas.spent() < - u64::try_from(interpreter.control.gas.refunded()).unwrap_or_default() + if interpreter.gas.total_gas_spent() + < u64::try_from(interpreter.gas.refunded()).unwrap_or_default() { - interpreter.control.gas = Gas::new(interpreter.control.gas.limit()); + interpreter.gas = Gas::new(interpreter.gas.limit()); } } } @@ -1916,19 +2128,23 @@ impl Cheatcodes { /// cache) from mapped source address to the target address. /// - generates arbitrary value and saves it in target address storage. #[cold] - fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn arbitrary_storage_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut FoundryContextFor<'_, FEN>, + ) { let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD { (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address) } else { - return + return; }; let Some(value) = ecx.sload(target_address, key) else { return; }; - if (value.is_cold && value.data.is_zero()) || - self.should_overwrite_arbitrary_storage(&target_address, key) + if (value.is_cold && value.data.is_zero()) + || self.should_overwrite_arbitrary_storage(&target_address, key) { if self.has_arbitrary_storage(&target_address) { let arbitrary_value = self.rng().random(); @@ -1968,20 +2184,30 @@ impl Cheatcodes { } #[cold] - fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + fn record_state_diffs( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut FoundryContextFor<'_, FEN>, + ) { let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; match interpreter.bytecode.opcode() { op::SELFDESTRUCT => { // Ensure that we're not selfdestructing a context recording was initiated on let Some(last) = account_accesses.last_mut() else { return }; - // get previous balance and initialized status of the target account + // get previous balance, nonce and initialized status of the target account let target = try_or_return!(interpreter.stack.peek(0)); let target = Address::from_word(B256::from(target)); - let (initialized, old_balance) = ecx - .journaled_state + let (initialized, old_balance, old_nonce) = ecx + .journal_mut() .load_account(target) - .map(|account| (account.info.exists(), account.info.balance)) + .map(|account| { + ( + account.data.info.exists(), + account.data.info.balance, + account.data.info.nonce, + ) + }) .unwrap_or_default(); // load balance of this account @@ -1993,8 +2219,8 @@ impl Cheatcodes { // register access for the target account last.push(crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: target, @@ -2002,13 +2228,15 @@ impl Cheatcodes { initialized, oldBalance: old_balance, newBalance: old_balance + value, + oldNonce: old_nonce, + newNonce: old_nonce, // nonce doesn't change on selfdestruct value, data: Bytes::new(), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], depth: ecx - .journaled_state + .journal() .depth() .try_into() .expect("journaled state depth exceeds u64"), @@ -2023,13 +2251,14 @@ impl Cheatcodes { // Try to include present value for informational purposes, otherwise assume // it's not set (zero value) - let mut present_value = U256::ZERO; // Try to load the account and the slot's present value - if ecx.journaled_state.load_account(address).is_ok() { - if let Some(previous) = ecx.sload(address, key) { - present_value = previous.data; - } - } + let present_value = if ecx.journal_mut().load_account(address).is_ok() + && let Some(previous) = ecx.sload(address, key) + { + previous.data + } else { + U256::ZERO + }; let access = crate::Vm::StorageAccess { account: interpreter.input.target_address, slot: key.into(), @@ -2038,11 +2267,8 @@ impl Cheatcodes { newValue: present_value.into(), reverted: false, }; - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); append_storage_access(last, access, curr_depth); } op::SSTORE => { @@ -2053,12 +2279,13 @@ impl Cheatcodes { let address = interpreter.input.target_address; // Try to load the account and the slot's previous value, otherwise, assume it's // not set (zero value) - let mut previous_value = U256::ZERO; - if ecx.journaled_state.load_account(address).is_ok() { - if let Some(previous) = ecx.sload(address, key) { - previous_value = previous.data; - } - } + let previous_value = if ecx.journal_mut().load_account(address).is_ok() + && let Some(previous) = ecx.sload(address, key) + { + previous.data + } else { + U256::ZERO + }; let access = crate::Vm::StorageAccess { account: address, @@ -2068,11 +2295,8 @@ impl Cheatcodes { newValue: value.into(), reverted: false, }; - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); append_storage_access(last, access, curr_depth); } @@ -2087,24 +2311,18 @@ impl Cheatcodes { }; let address = Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0)))); - let initialized; - let balance; - if let Ok(acc) = ecx.journaled_state.load_account(address) { - initialized = acc.info.exists(); - balance = acc.info.balance; - } else { - initialized = false; - balance = U256::ZERO; - } - let curr_depth = ecx - .journaled_state - .depth() - .try_into() - .expect("journaled state depth exceeds u64"); + let (initialized, balance, nonce) = + if let Ok(acc) = ecx.journal_mut().load_account(address) { + (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce) + } else { + (false, U256::ZERO, 0) + }; + let curr_depth = + ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"); let account_access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), + forkId: ecx.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: address, @@ -2112,6 +2330,8 @@ impl Cheatcodes { initialized, oldBalance: balance, newBalance: balance, + oldNonce: nonce, + newNonce: nonce, // EXT* operations don't change nonce value: U256::ZERO, data: Bytes::new(), reverted: false, @@ -2303,6 +2523,20 @@ impl Cheatcodes { (REVERT, 0, 1, false), ); } + + #[cold] + fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) { + match interpreter.bytecode.opcode() { + op::CREATE2 => self.dynamic_gas_limit = true, + op::CALL => { + // If first element of the stack is close to current remaining gas then assume + // dynamic gas limit. + self.dynamic_gas_limit = + try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100 + } + _ => self.dynamic_gas_limit = false, + } + } } /// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, @@ -2320,44 +2554,38 @@ fn disallowed_mem_write( "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}", dest_offset, size, - ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") + ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ") ); - interpreter.control.instruction_result = InstructionResult::Revert; - interpreter.control.next_action = InterpreterAction::Return { - result: InterpreterResult { - output: Error::encode(revert_string), - gas: interpreter.control.gas, - result: InstructionResult::Revert, - }, - }; -} - -// Determines if the gas limit on a given call was manually set in the script and should therefore -// not be overwritten by later estimations -fn check_if_fixed_gas_limit(ecx: &Ecx, call_gas_limit: u64) -> bool { - // If the gas limit was not set in the source code it is set to the estimated gas left at the - // time of the call, which should be rather close to configured gas limit. - // TODO: Find a way to reliably make this determination. - // For example by generating it in the compilation or EVM simulation process - ecx.tx.gas_limit > ecx.block.gas_limit && - call_gas_limit <= ecx.block.gas_limit - // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic - // gas too low" failure when simulated on chain - && call_gas_limit > 2300 + interpreter.bytecode.set_action(InterpreterAction::new_return( + InstructionResult::Revert, + Bytes::from(revert_string.into_bytes()), + interpreter.gas, + )); } /// Returns true if the kind of account access is a call. -fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { +const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { matches!( kind, - crate::Vm::AccountAccessKind::Call | - crate::Vm::AccountAccessKind::StaticCall | - crate::Vm::AccountAccessKind::CallCode | - crate::Vm::AccountAccessKind::DelegateCall + crate::Vm::AccountAccessKind::Call + | crate::Vm::AccountAccessKind::StaticCall + | crate::Vm::AccountAccessKind::CallCode + | crate::Vm::AccountAccessKind::DelegateCall ) } +/// Records a log into the recorded logs vector, if it exists. +fn record_logs(recorded_logs: &mut Option>, log: &Log) { + if let Some(storage_recorded_logs) = recorded_logs { + storage_recorded_logs.push(Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + }); + } +} + /// Appends an AccountAccess that resumes the recording of the current context. fn append_storage_access( last: &mut Vec, @@ -2393,6 +2621,8 @@ fn append_storage_access( // The remaining fields are defaults oldBalance: U256::ZERO, newBalance: U256::ZERO, + oldNonce: 0, + newNonce: 0, value: U256::ZERO, data: Bytes::new(), deployedCode: Bytes::new(), @@ -2404,34 +2634,66 @@ fn append_storage_access( } } +/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor. +const fn cheatcode_of(_: &T) -> &'static spec::Cheatcode<'static> { + T::CHEATCODE +} + +fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.signature.split('(').next().unwrap() +} + +const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.id +} + +const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { + cheat.func.signature +} + /// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch( +fn apply_dispatch( calls: &Vm::VmCalls, - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { - let cheat = calls_as_dyn_cheatcode(calls); + // Extract metadata for logging/deprecation via CheatcodeDef. + macro_rules! get_cheatcode { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)* + } + }; + } + let cheat = vm_calls!(get_cheatcode); - let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()).entered(); - trace!(target: "cheatcodes", cheat = ?cheat.as_debug(), "applying"); + let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered(); + trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying"); - if let spec::Status::Deprecated(replacement) = *cheat.status() { - ccx.state.deprecated.insert(cheat.signature(), replacement); + if let spec::Status::Deprecated(replacement) = cheat.status { + ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement); } - // Apply the cheatcode. - let mut result = cheat.dyn_apply(ccx, executor); + // Monomorphized dispatch: calls apply_full directly, no trait objects. + macro_rules! dispatch { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)* + } + }; + } + let mut result = vm_calls!(dispatch); // Format the error message to include the cheatcode name. - if let Err(e) = &mut result { - if e.is_str() { - let name = cheat.name(); - // Skip showing the cheatcode name for: - // - assertions: too verbose, and can already be inferred from the error message - // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl` - if !name.contains("assert") && name != "rpcUrl" { - *e = fmt_err!("vm.{name}: {e}"); - } + if let Err(e) = &mut result + && e.is_str() + { + let name = cheatcode_name(cheat); + // Skip showing the cheatcode name for: + // - assertions: too verbose, and can already be inferred from the error message + // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl` + if !name.contains("assert") && name != "rpcUrl" { + *e = fmt_err!("vm.{name}: {e}"); } } @@ -2446,18 +2708,12 @@ fn apply_dispatch( result } -fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { - macro_rules! as_dyn { - ($($variant:ident),*) => { - match calls { - $(Vm::VmCalls::$variant(cheat) => cheat,)* - } - }; - } - vm_calls!(as_dyn) -} - /// Helper function to check if frame execution will exit. -fn will_exit(ir: InstructionResult) -> bool { - !matches!(ir, InstructionResult::Continue | InstructionResult::CallOrCreate) +const fn will_exit(action: &InterpreterAction) -> bool { + match action { + InterpreterAction::Return(result) => { + result.result.is_ok_or_revert() || result.result.is_error() + } + _ => false, + } } diff --git a/crates/cheatcodes/src/inspector/analysis.rs b/crates/cheatcodes/src/inspector/analysis.rs new file mode 100644 index 0000000000000..806caa50a2080 --- /dev/null +++ b/crates/cheatcodes/src/inspector/analysis.rs @@ -0,0 +1,168 @@ +//! Cheatcode information, extracted from the syntactic and semantic analysis of the sources. + +use foundry_common::fmt::{StructDefinitions, TypeDefMap}; +use solar::sema::{self, Compiler, Gcx, hir}; +use std::sync::{Arc, OnceLock}; +use thiserror::Error; + +/// Represents a failure in one of the lazy analysis steps. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum AnalysisError { + /// Indicates that the resolution of struct definitions failed. + #[error("unable to resolve struct definitions")] + StructDefinitionsResolutionFailed, +} + +/// Provides cached, on-demand syntactic and semantic analysis of a completed `Compiler` instance. +/// +/// This struct acts as a facade over the `Compiler`, offering lazy-loaded analysis for tools like +/// cheatcode inspectors. It assumes the compiler has already completed parsing and lowering. +/// +/// # Adding with new analyses types +/// +/// To add support for a new type of cached analysis, follow this pattern: +/// +/// 1. Add a new `pub OnceCell>` field to `CheatcodeAnalysis`, where `T` is +/// the type of the data that you are adding support for. +/// +/// 2. Implement a getter method for the new field. Inside the getter, use +/// `self.field.get_or_init()` to compute and cache the value on the first call. +/// +/// 3. Inside the closure passed to `get_or_init()`, create a dedicated visitor to traverse the HIR +/// using `self.compiler.enter()` and collect the required data. +/// +/// This ensures all analyses remain lazy, efficient, and consistent with the existing design. +#[derive(Clone)] +pub struct CheatcodeAnalysis { + /// A shared, thread-safe reference to solar's `Compiler` instance. + pub compiler: Arc, + + /// Cached struct definitions in the sources. + /// Used to keep field order when parsing JSON values. + struct_defs: OnceLock>, +} + +impl std::fmt::Debug for CheatcodeAnalysis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CheatcodeAnalysis") + .field("compiler", &"") + .field("struct_defs", &self.struct_defs) + .finish() + } +} + +impl CheatcodeAnalysis { + pub const fn new(compiler: Arc) -> Self { + Self { compiler, struct_defs: OnceLock::new() } + } + + /// Lazily initializes and returns the struct definitions. + pub fn struct_defs(&self) -> Result<&StructDefinitions, &AnalysisError> { + self.struct_defs + .get_or_init(|| { + self.compiler.enter(|compiler| { + let gcx = compiler.gcx(); + + StructDefinitionResolver::new(gcx).process() + }) + }) + .as_ref() + } +} + +// -- STRUCT DEFINITIONS ------------------------------------------------------- + +/// Generates a map of all struct definitions from the HIR using the resolved `Ty` system. +struct StructDefinitionResolver<'gcx> { + gcx: Gcx<'gcx>, + struct_defs: TypeDefMap, +} + +impl<'gcx> StructDefinitionResolver<'gcx> { + /// Constructs a new generator. + pub const fn new(gcx: Gcx<'gcx>) -> Self { + Self { gcx, struct_defs: TypeDefMap::new() } + } + + /// Processes the HIR to generate all the struct definitions. + pub fn process(mut self) -> Result { + for id in self.hir().strukt_ids() { + self.resolve_struct_definition(id)?; + } + Ok(self.struct_defs.into()) + } + + #[inline] + fn hir(&self) -> &'gcx hir::Hir<'gcx> { + &self.gcx.hir + } + + /// The recursive core of the generator. Resolves a single struct and adds it to the cache. + fn resolve_struct_definition(&mut self, id: hir::StructId) -> Result<(), AnalysisError> { + let qualified_name = self.get_fully_qualified_name(id); + if self.struct_defs.contains_key(&qualified_name) { + return Ok(()); + } + + let hir = self.hir(); + let strukt = hir.strukt(id); + let mut fields = Vec::with_capacity(strukt.fields.len()); + + for &field_id in strukt.fields { + let var = hir.variable(field_id); + let name = + var.name.ok_or(AnalysisError::StructDefinitionsResolutionFailed)?.to_string(); + if let Some(ty_str) = self.ty_to_string(self.gcx.type_of_hir_ty(&var.ty)) { + fields.push((name, ty_str)); + } + } + + // Only insert if there are fields, to avoid adding empty entries + if !fields.is_empty() { + self.struct_defs.insert(qualified_name, fields); + } + + Ok(()) + } + + /// Converts a resolved `Ty` into its canonical string representation. + fn ty_to_string(&mut self, ty: sema::Ty<'gcx>) -> Option { + let ty = ty.peel_refs(); + let res = match ty.kind { + sema::ty::TyKind::Elementary(e) => e.to_string(), + sema::ty::TyKind::Array(ty, size) => { + let inner_type = self.ty_to_string(ty)?; + format!("{inner_type}[{size}]") + } + sema::ty::TyKind::DynArray(ty) => { + let inner_type = self.ty_to_string(ty)?; + format!("{inner_type}[]") + } + sema::ty::TyKind::Struct(id) => { + // Ensure the nested struct is resolved before proceeding. + self.resolve_struct_definition(id).ok()?; + self.get_fully_qualified_name(id) + } + sema::ty::TyKind::Udvt(ty, _) => self.ty_to_string(ty)?, + // For now, map enums to `uint8` + sema::ty::TyKind::Enum(_) => "uint8".to_string(), + // For now, map contracts to `address` + sema::ty::TyKind::Contract(_) => "address".to_string(), + // Explicitly disallow unsupported types + _ => return None, + }; + + Some(res) + } + + /// Helper to get the fully qualified name `Contract.Struct`. + fn get_fully_qualified_name(&self, id: hir::StructId) -> String { + let hir = self.hir(); + let strukt = hir.strukt(id); + if let Some(contract_id) = strukt.contract { + format!("{}.{}", hir.contract(contract_id).name.as_str(), strukt.name.as_str()) + } else { + strukt.name.as_str().into() + } + } +} diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index c82f9023fafd4..85b279f7e1790 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -1,7 +1,11 @@ -use super::Ecx; use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; -use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; +use foundry_evm_core::evm::{FoundryContextFor, FoundryEvmNetwork}; +use revm::{ + context::ContextTr, + inspector::JournalExt, + interpreter::{CreateInputs, CreateScheme}, +}; /// Common behaviour of legacy and EOF create inputs. pub(crate) trait CommonCreateInput { @@ -11,31 +15,42 @@ pub(crate) trait CommonCreateInput { fn init_code(&self) -> Bytes; fn scheme(&self) -> Option; fn set_caller(&mut self, caller: Address); - fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address; - fn computed_created_address(&self) -> Option
; + fn log_debug( + &self, + cheatcode: &mut Cheatcodes, + scheme: &CreateScheme, + ); + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + ) -> Address; } impl CommonCreateInput for &mut CreateInputs { fn caller(&self) -> Address { - self.caller + CreateInputs::caller(self) } fn gas_limit(&self) -> u64 { - self.gas_limit + CreateInputs::gas_limit(self) } fn value(&self) -> U256 { - self.value + CreateInputs::value(self) } fn init_code(&self) -> Bytes { - self.init_code.clone() + CreateInputs::init_code(self).clone() } fn scheme(&self) -> Option { - Some(self.scheme) + Some(CreateInputs::scheme(self)) } fn set_caller(&mut self, caller: Address) { - self.caller = caller; + CreateInputs::set_call(self, caller); } - fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme) { + fn log_debug( + &self, + cheatcode: &mut Cheatcodes, + scheme: &CreateScheme, + ) { let kind = match scheme { CreateScheme::Create => "create", CreateScheme::Create2 { .. } => "create2", @@ -43,55 +58,16 @@ impl CommonCreateInput for &mut CreateInputs { }; debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address { - let old_nonce = ecx - .journaled_state - .state - .get(&self.caller) - .map(|acc| acc.info.nonce) - .unwrap_or_default(); + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + ) -> Address { + let caller = CreateInputs::caller(self); + let old_nonce = + ecx.journal().evm_state().get(&caller).map(|acc| acc.info.nonce).unwrap_or_default(); let created_address = self.created_address(old_nonce); - cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); + cheatcodes.allow_cheatcodes_on_create(ecx, caller, created_address); created_address } - fn computed_created_address(&self) -> Option
{ - None - } -} - -impl CommonCreateInput for &mut EOFCreateInputs { - fn caller(&self) -> Address { - self.caller - } - fn gas_limit(&self) -> u64 { - self.gas_limit - } - fn value(&self) -> U256 { - self.value - } - fn init_code(&self) -> Bytes { - match &self.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { initcode, .. } => initcode.raw.clone(), - } - } - fn scheme(&self) -> Option { - None - } - fn set_caller(&mut self, caller: Address) { - self.caller = caller; - } - fn log_debug(&self, cheatcode: &mut Cheatcodes, _scheme: &CreateScheme) { - debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable eofcreate"); - } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address { - let created_address = - <&mut EOFCreateInputs as CommonCreateInput>::computed_created_address(self) - .unwrap_or_default(); - cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); - created_address - } - fn computed_created_address(&self) -> Option
{ - self.kind.created_address().copied() - } } diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 6ad36e4742901..9af337e223eea 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -1,171 +1,177 @@ //! Implementations of [`Json`](spec::Group::Json) cheatcodes. -use crate::{string, Cheatcode, Cheatcodes, Result, Vm::*}; -use alloy_dyn_abi::{eip712_parser::EncodeType, DynSolType, DynSolValue, Resolver}; -use alloy_primitives::{hex, Address, B256, I256}; +use crate::{Cheatcode, Cheatcodes, Result, Vm::*, string}; +use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, eip712_parser::EncodeType}; +use alloy_primitives::{Address, B256, I256, U256, hex}; use alloy_sol_types::SolValue; -use foundry_common::fs; +use foundry_common::{fmt::StructDefinitions, fs}; use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::evm::FoundryEvmNetwork; use serde_json::{Map, Value}; -use std::{borrow::Cow, collections::BTreeMap}; +use std::{ + borrow::Cow, + collections::{BTreeMap, BTreeSet}, +}; impl Cheatcode for keyExistsCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } impl Cheatcode for keyExistsJsonCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } impl Cheatcode for parseJson_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json } = self; - parse_json(json, "$") + parse_json(json, "$", state.struct_defs()) } } impl Cheatcode for parseJson_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json(json, key) + parse_json(json, key, state.struct_defs()) } } impl Cheatcode for parseJsonUintCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Uint(256)) } } impl Cheatcode for parseJsonUintArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } impl Cheatcode for parseJsonIntCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Int(256)) } } impl Cheatcode for parseJsonIntArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } impl Cheatcode for parseJsonBoolCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bool) } } impl Cheatcode for parseJsonBoolArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } impl Cheatcode for parseJsonAddressCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Address) } } impl Cheatcode for parseJsonAddressArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } impl Cheatcode for parseJsonStringCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::String) } } impl Cheatcode for parseJsonStringArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String))) } } impl Cheatcode for parseJsonBytesCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bytes) } } impl Cheatcode for parseJsonBytesArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } impl Cheatcode for parseJsonBytes32Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseJsonBytes32ArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } impl Cheatcode for parseJsonType_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, typeDescription } = self; - parse_json_coerce(json, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?) + .map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonType_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; - parse_json_coerce(json, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?) + .map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonTypeArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; - let ty = resolve_type(typeDescription)?; + let ty = resolve_type(typeDescription, state.struct_defs())?; parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonKeysCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_keys(json, key) } } impl Cheatcode for serializeJsonCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, value } = self; *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?; Ok(value.abi_encode()) @@ -173,56 +179,56 @@ impl Cheatcode for serializeJsonCall { } impl Cheatcode for serializeBool_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeUint_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeInt_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeAddress_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeBytes32_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32)) } } impl Cheatcode for serializeString_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.clone().into()) } } impl Cheatcode for serializeBytes_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.to_vec().into()) } } impl Cheatcode for serializeBool_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -234,7 +240,7 @@ impl Cheatcode for serializeBool_1Call { } impl Cheatcode for serializeUint_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -246,7 +252,7 @@ impl Cheatcode for serializeUint_1Call { } impl Cheatcode for serializeInt_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -258,7 +264,7 @@ impl Cheatcode for serializeInt_1Call { } impl Cheatcode for serializeAddress_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -270,7 +276,7 @@ impl Cheatcode for serializeAddress_1Call { } impl Cheatcode for serializeBytes32_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -282,7 +288,7 @@ impl Cheatcode for serializeBytes32_1Call { } impl Cheatcode for serializeString_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -294,7 +300,7 @@ impl Cheatcode for serializeString_1Call { } impl Cheatcode for serializeBytes_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, @@ -308,26 +314,26 @@ impl Cheatcode for serializeBytes_1Call { } impl Cheatcode for serializeJsonType_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeDescription, value } = self; - let ty = resolve_type(typeDescription)?; + let ty = resolve_type(typeDescription, state.struct_defs())?; let value = ty.abi_decode(value)?; - let value = serialize_value_as_json(value)?; + let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?; Ok(value.to_string().abi_encode()) } } impl Cheatcode for serializeJsonType_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, typeDescription, value } = self; - let ty = resolve_type(typeDescription)?; + let ty = resolve_type(typeDescription, state.struct_defs())?; let value = ty.abi_decode(value)?; serialize_json(state, objectKey, valueKey, value) } } impl Cheatcode for serializeUintToHexCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; let hex = format!("0x{value:x}"); serialize_json(state, objectKey, valueKey, hex.into()) @@ -335,7 +341,7 @@ impl Cheatcode for serializeUintToHexCall { } impl Cheatcode for writeJson_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); let json_string = serde_json::to_string_pretty(&json)?; @@ -344,19 +350,22 @@ impl Cheatcode for writeJson_0Call { } impl Cheatcode for writeJson_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { - let Self { json, path, valueKey } = self; - let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json: value, path, valueKey } = self; + // Read, parse, and update the JSON object. + // If the file doesn't exist, start with an empty JSON object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data_s = fs::read_to_string(data_path)?; - let data = serde_json::from_str(&data_s)?; - let value = - jsonpath_lib::replace_with(data, &canonicalize_json_path(valueKey), &mut |_| { - Some(json.clone()) - })?; - - let json_string = serde_json::to_string_pretty(&value)?; + let mut data = if data_path.exists() { + let data_string = fs::locked_read_to_string(&data_path)?; + serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string)) + } else { + Value::Object(Default::default()) + }; + upsert_json_value(&mut data, value, valueKey)?; + + // Write the updated content back to the file + let json_string = serde_json::to_string_pretty(&data)?; super::fs::write_file(state, path.as_ref(), json_string.as_bytes()) } } @@ -368,10 +377,10 @@ pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result { Ok(exists.abi_encode()) } -pub(super) fn parse_json(json: &str, path: &str) -> Result { +pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result { let value = parse_json_str(json)?; let selected = select(&value, path)?; - let sol = json_to_sol(&selected)?; + let sol = json_to_sol(defs, &selected)?; Ok(encode(sol)) } @@ -396,6 +405,7 @@ pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result parse_json_array(array, ty), (Value::Object(object), ty) => parse_json_map(object, ty), (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())), + (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty), _ => string::parse_value(&to_string(value), ty), } } @@ -462,10 +472,10 @@ fn parse_json_str(json: &str) -> Result { serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}")) } -fn json_to_sol(json: &[&Value]) -> Result> { +fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result> { let mut sol = Vec::with_capacity(json.len()); for value in json { - sol.push(json_value_to_token(value)?); + sol.push(json_value_to_token(value, defs)?); } Ok(sol) } @@ -493,11 +503,7 @@ fn encode(values: Vec) -> Vec { /// Canonicalize a json path key to always start from the root of the document. /// Read more about json path syntax: pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { - if !path.starts_with('$') { - format!("${path}").into() - } else { - path.into() - } + if path.starts_with('$') { path.into() } else { format!("${path}").into() } } /// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer @@ -507,22 +513,55 @@ pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { /// it will call itself to convert each of it's value and encode the whole as a /// Tuple #[instrument(target = "cheatcodes", level = "trace", ret)] -pub(super) fn json_value_to_token(value: &Value) -> Result { +pub(super) fn json_value_to_token( + value: &Value, + defs: Option<&StructDefinitions>, +) -> Result { + if let Some(defs) = defs { + _json_value_to_token(value, defs) + } else { + _json_value_to_token(value, &StructDefinitions::default()) + } +} + +fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result { match value { Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)), Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)), - Value::Array(array) => { - array.iter().map(json_value_to_token).collect::>().map(DynSolValue::Array) - } - value @ Value::Object(_) => { - // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) - let ordered_object: BTreeMap = - serde_json::from_value(value.clone()).unwrap(); - ordered_object - .values() - .map(json_value_to_token) - .collect::>() - .map(DynSolValue::Tuple) + Value::Array(array) => array + .iter() + .map(|v| _json_value_to_token(v, defs)) + .collect::>() + .map(DynSolValue::Array), + Value::Object(map) => { + // Try to find a struct definition that matches the object keys. + let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect(); + let matching_def = defs.values().find(|fields| { + fields.len() == keys.len() + && fields.iter().map(|(name, _)| name.as_str()).collect::>() == keys + }); + + if let Some(fields) = matching_def { + // Found a struct with matching field names, use the order from the definition. + fields + .iter() + .map(|(name, _)| { + // unwrap is safe because we know the key exists. + _json_value_to_token(map.get(name).unwrap(), defs) + }) + .collect::>() + .map(DynSolValue::Tuple) + } else { + // Fallback to alphabetical sorting if no matching struct is found. + // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) + let ordered_object: BTreeMap<_, _> = + map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + ordered_object + .values() + .map(|value| _json_value_to_token(value, defs)) + .collect::>() + .map(DynSolValue::Tuple) + } } Value::Number(number) => { if let Some(f) = number.as_f64() { @@ -567,12 +606,13 @@ pub(super) fn json_value_to_token(value: &Value) -> Result { Err(fmt_err!("unsupported JSON number: {number}")) } Value::String(string) => { + // Hanfl hex strings if let Some(mut val) = string.strip_prefix("0x") { let s; if val.len() == 39 { - return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into()) + return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into()); } - if val.len() % 2 != 0 { + if !val.len().is_multiple_of(2) { s = format!("0{val}"); val = &s[..]; } @@ -584,51 +624,24 @@ pub(super) fn json_value_to_token(value: &Value) -> Result { }); } } - Ok(DynSolValue::String(string.to_owned())) - } - } -} -/// Serializes given [DynSolValue] into a [serde_json::Value]. -fn serialize_value_as_json(value: DynSolValue) -> Result { - match value { - DynSolValue::Bool(b) => Ok(Value::Bool(b)), - DynSolValue::String(s) => { - // Strings are allowed to contain stringified JSON objects, so we try to parse it like - // one first. - if let Ok(map) = serde_json::from_str(&s) { - Ok(Value::Object(map)) - } else { - Ok(Value::String(s)) + // Handle large numbers that were potentially encoded as strings because they exceed the + // capacity of a 64-bit integer. + // Note that number-like strings that *could* fit in an `i64`/`u64` will fall through + // and be treated as literal strings. + if let Ok(n) = string.parse::() + && i64::try_from(n).is_err() + { + return Ok(DynSolValue::Int(n, 256)); + } else if let Ok(n) = string.parse::() + && u64::try_from(n).is_err() + { + return Ok(DynSolValue::Uint(n, 256)); } - } - DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))), - DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))), - DynSolValue::Int(i, _) => { - // let serde handle number parsing - let n = serde_json::from_str(&i.to_string())?; - Ok(Value::Number(n)) - } - DynSolValue::Uint(i, _) => { - // let serde handle number parsing - let n = serde_json::from_str(&i.to_string())?; - Ok(Value::Number(n)) - } - DynSolValue::Address(a) => Ok(Value::String(a.to_string())), - DynSolValue::Array(e) | DynSolValue::FixedArray(e) => { - Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::>()?)) - } - DynSolValue::CustomStruct { name: _, prop_names, tuple } => { - let values = - tuple.into_iter().map(serialize_value_as_json).collect::>>()?; - let map = prop_names.into_iter().zip(values).collect(); - Ok(Value::Object(map)) + // Otherwise, treat as a regular string + Ok(DynSolValue::String(string.to_owned())) } - DynSolValue::Tuple(values) => Ok(Value::Array( - values.into_iter().map(serialize_value_as_json).collect::>()?, - )), - DynSolValue::Function(_) => bail!("cannot serialize function pointer"), } } @@ -640,13 +653,13 @@ fn serialize_value_as_json(value: DynSolValue) -> Result { /// object, so that the user can use that as a value to a new invocation of the same function with a /// new object key. This enables the user to reuse the same function to crate arbitrarily complex /// object structures (JSON). -fn serialize_json( - state: &mut Cheatcodes, +fn serialize_json( + state: &mut Cheatcodes, object_key: &str, value_key: &str, value: DynSolValue, ) -> Result { - let value = serialize_value_as_json(value)?; + let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?; let map = state.serialized_jsons.entry(object_key.into()).or_default(); map.insert(value_key.into(), value); let stringified = serde_json::to_string(map).unwrap(); @@ -654,38 +667,154 @@ fn serialize_json( } /// Resolves a [DynSolType] from user input. -pub(super) fn resolve_type(type_description: &str) -> Result { +pub(super) fn resolve_type( + type_description: &str, + struct_defs: Option<&StructDefinitions>, +) -> Result { + let ordered_ty = |ty| -> Result { + if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) } + }; + if let Ok(ty) = DynSolType::parse(type_description) { - return Ok(ty); + return ordered_ty(ty); }; if let Ok(encoded) = EncodeType::parse(type_description) { let main_type = encoded.types[0].type_name; let mut resolver = Resolver::default(); - for t in encoded.types { + for t in &encoded.types { resolver.ingest(t.to_owned()); } - return Ok(resolver.resolve(main_type)?) - }; + // Get the alphabetically-sorted type from the resolver, and reorder if necessary. + return ordered_ty(resolver.resolve(main_type)?); + } bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string") } +/// Upserts a value into a JSON object based on a dot-separated key. +/// +/// This function navigates through a mutable `serde_json::Value` object using a +/// path-like key. It creates nested JSON objects if they do not exist along the path. +/// The value is inserted at the final key in the path. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the `serde_json::Value` to be modified. +/// * `value` - The string representation of the value to upsert. This string is first parsed as +/// JSON, and if that fails, it's treated as a plain JSON string. +/// * `key` - A dot-separated string representing the path to the location for upserting. +pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> { + // Parse the path key into segments. + let canonical_key = canonicalize_json_path(key); + let parts: Vec<&str> = canonical_key + .strip_prefix("$.") + .unwrap_or(key) + .split('.') + .filter(|s| !s.is_empty()) + .collect(); + + if parts.is_empty() { + return Err(fmt_err!("'valueKey' cannot be empty or just '$'")); + } + + // Separate the final key from the path. + // Traverse the objects, creating intermediary ones if necessary. + if let Some((key_to_insert, path_to_parent)) = parts.split_last() { + let mut current_level = data; + + for segment in path_to_parent { + if !current_level.is_object() { + return Err(fmt_err!("path segment '{segment}' does not resolve to an object.")); + } + current_level = current_level + .as_object_mut() + .unwrap() + .entry(segment.to_string()) + .or_insert(Value::Object(Map::new())); + } + + // Upsert the new value + if let Some(parent_obj) = current_level.as_object_mut() { + parent_obj.insert( + key_to_insert.to_string(), + serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())), + ); + } else { + return Err(fmt_err!("final destination is not an object, cannot insert key.")); + } + } + + Ok(()) +} + +/// Recursively traverses a `DynSolType` and reorders the fields of any +/// `CustomStruct` variants according to the provided `StructDefinitions`. +/// +/// This is necessary because the EIP-712 resolver sorts struct fields alphabetically, +/// but we want to respect the order defined in the Solidity source code. +fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result { + match ty { + DynSolType::CustomStruct { name, prop_names, tuple } => { + if let Some(def) = struct_defs.get(&name)? { + // The incoming `prop_names` and `tuple` are alphabetically sorted. + let type_map: std::collections::HashMap = + prop_names.into_iter().zip(tuple).collect(); + + let mut sorted_props = Vec::with_capacity(def.len()); + let mut sorted_tuple = Vec::with_capacity(def.len()); + for (field_name, _) in def { + sorted_props.push(field_name.clone()); + if let Some(field_ty) = type_map.get(field_name) { + sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?); + } else { + bail!( + "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'" + ); + } + } + Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple }) + } else { + // No definition found, so we can't reorder. However, we still reorder its children + // in case they have known structs. + let new_tuple = tuple + .into_iter() + .map(|t| reorder_type(t, struct_defs)) + .collect::>>()?; + Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple }) + } + } + DynSolType::Array(inner) => { + Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?))) + } + DynSolType::FixedArray(inner, len) => { + Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len)) + } + DynSolType::Tuple(inner) => Ok(DynSolType::Tuple( + inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::>>()?, + )), + _ => Ok(ty), + } +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::FixedBytes; - use proptest::strategy::Strategy; + use foundry_common::fmt::{TypeDefMap, serialize_value_as_json}; + use proptest::{arbitrary::any, prop_oneof, strategy::Strategy}; + use std::collections::HashSet; - fn contains_tuple(value: &DynSolValue) -> bool { - match value { - DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => true, - DynSolValue::Array(v) | DynSolValue::FixedArray(v) => { - v.first().is_some_and(contains_tuple) - } - _ => false, - } + fn valid_value(value: &DynSolValue) -> bool { + (match value { + DynSolValue::String(s) if s == "{}" => false, + + DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false, + + DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value), + _ => true, + }) && value.as_type().is_some() } /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and @@ -713,18 +842,55 @@ mod tests { } fn guessable_types() -> impl proptest::strategy::Strategy { - proptest::arbitrary::any::() - .prop_map(fixup_guessable) - .prop_filter("tuples are not supported", |v| !contains_tuple(v)) - .prop_filter("filter out values without type", |v| v.as_type().is_some()) + any::().prop_map(fixup_guessable).prop_filter("invalid value", valid_value) + } + + /// A proptest strategy for generating a (simple) `DynSolValue::CustomStruct` + /// and its corresponding `StructDefinitions` object. + fn custom_struct_strategy() -> impl Strategy { + // Define a strategy for basic field names and values. + let field_name_strat = "[a-z]{4,12}"; + let field_value_strat = prop_oneof![ + any::().prop_map(DynSolValue::Bool), + any::().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)), + any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address), + any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)), + ".*".prop_map(DynSolValue::String), + ]; + + // Combine them to create a list of unique fields that preserve the random order. + let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8) + .prop_map(|fields| { + let mut unique_fields = Vec::with_capacity(fields.len()); + let mut seen_names = HashSet::new(); + for (name, value) in fields { + if seen_names.insert(name.clone()) { + unique_fields.push((name, value)); + } + } + unique_fields + }); + + // Generate the `CustomStruct` and its definition. + ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| { + let (prop_names, tuple): (Vec, Vec) = + fields.clone().into_iter().unzip(); + let def_fields: Vec<(String, String)> = fields + .iter() + .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string())) + .collect(); + let mut defs_map = TypeDefMap::default(); + defs_map.insert(struct_name.clone(), def_fields); + (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple }) + }) } // Tests to ensure that conversion [DynSolValue] -> [serde_json::Value] -> [DynSolValue] proptest::proptest! { #[test] fn test_json_roundtrip_guessed(v in guessable_types()) { - let json = serialize_value_as_json(v.clone()).unwrap(); - let value = json_value_to_token(&json).unwrap(); + let json = serialize_value_as_json(v.clone(), None).unwrap(); + let value = json_value_to_token(&json, None).unwrap(); // do additional abi_encode -> abi_decode to avoid zero signed integers getting decoded as unsigned and causing assert_eq to fail. let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap(); @@ -732,10 +898,221 @@ mod tests { } #[test] - fn test_json_roundtrip(v in proptest::arbitrary::any::().prop_filter("filter out values without type", |v| v.as_type().is_some())) { - let json = serialize_value_as_json(v.clone()).unwrap(); + fn test_json_roundtrip(v in any::().prop_filter("filter out values without type", |v| v.as_type().is_some())) { + let json = serialize_value_as_json(v.clone(), None).unwrap(); let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap(); - assert_eq!(value, v); + assert_eq!(value, v); + } + + #[test] + fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) { + let json = serialize_value_as_json(v.clone(), Some(&struct_defs)).unwrap(); + let sol_type = v.as_type().unwrap(); + let parsed_value = parse_json_as(&json, &sol_type).unwrap(); + assert_eq!(parsed_value, v); } } + + #[test] + fn test_resolve_type_with_definitions() -> Result<()> { + // Define a struct with fields in a specific order (not alphabetical) + let mut struct_defs = TypeDefMap::new(); + struct_defs.insert( + "Apple".to_string(), + vec![ + ("color".to_string(), "string".to_string()), + ("sweetness".to_string(), "uint8".to_string()), + ("sourness".to_string(), "uint8".to_string()), + ], + ); + struct_defs.insert( + "FruitStall".to_string(), + vec![ + ("name".to_string(), "string".to_string()), + ("apples".to_string(), "Apple[]".to_string()), + ], + ); + + // Simulate resolver output: type string, using alphabetical order for fields. + let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)"; + + // Resolve type and ensure struct definition order is preserved. + let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap(); + if let DynSolType::CustomStruct { name, prop_names, tuple } = ty { + assert_eq!(name, "FruitStall"); + assert_eq!(prop_names, vec!["name", "apples"]); + assert_eq!(tuple.len(), 2); + assert_eq!(tuple[0], DynSolType::String); + + if let DynSolType::Array(apple_ty_boxed) = &tuple[1] + && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed + { + assert_eq!(*name, "Apple"); + // Check that the inner struct's fields are also in definition order. + assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]); + assert_eq!( + *tuple, + vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)] + ); + + return Ok(()); + } + } + panic!("Expected FruitStall and Apple to be CustomStruct"); + } + + #[test] + fn test_resolve_type_without_definitions() -> Result<()> { + // Simulate resolver output: type string, using alphabetical order for fields. + let ty_desc = "Person(bool active,uint256 age,string name)"; + + // Resolve the type without providing any struct definitions and ensure that original + // (alphabetical) order is unchanged. + let ty = resolve_type(ty_desc, None).unwrap(); + if let DynSolType::CustomStruct { name, prop_names, tuple } = ty { + assert_eq!(name, "Person"); + assert_eq!(prop_names, vec!["active", "age", "name"]); + assert_eq!(tuple.len(), 3); + assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]); + return Ok(()); + } + panic!("Expected Person to be CustomStruct"); + } + + #[test] + fn test_resolve_type_for_array_of_structs() -> Result<()> { + // Define a struct with fields in a specific, non-alphabetical order. + let mut struct_defs = TypeDefMap::new(); + struct_defs.insert( + "Item".to_string(), + vec![ + ("name".to_string(), "string".to_string()), + ("price".to_string(), "uint256".to_string()), + ("id".to_string(), "uint256".to_string()), + ], + ); + + // Simulate resolver output: type string, using alphabetical order for fields. + let ty_desc = "Item(uint256 id,string name,uint256 price)"; + + // Resolve type and ensure struct definition order is preserved. + let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap(); + let array_ty = DynSolType::Array(Box::new(ty)); + if let DynSolType::Array(item_ty) = array_ty + && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty + { + assert_eq!(name, "Item"); + assert_eq!(prop_names, vec!["name", "price", "id"]); + assert_eq!( + tuple, + vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)] + ); + return Ok(()); + } + panic!("Expected CustomStruct in array"); + } + + #[test] + fn test_parse_json_missing_field() { + // Define a struct with a specific field order. + let mut struct_defs = TypeDefMap::new(); + struct_defs.insert( + "Person".to_string(), + vec![ + ("name".to_string(), "string".to_string()), + ("age".to_string(), "uint256".to_string()), + ], + ); + + // JSON missing the "age" field + let json_str = r#"{ "name": "Alice" }"#; + + // Simulate resolver output: type string, using alphabetical order for fields. + let type_description = "Person(uint256 age,string name)"; + let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap(); + + // Now, attempt to parse the incomplete JSON using the ordered type. + let json_value: Value = serde_json::from_str(json_str).unwrap(); + let result = parse_json_as(&json_value, &ty); + + // Should fail with a missing field error because `parse_json_map` requires all fields. + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object")); + } + + #[test] + fn test_serialize_json_with_struct_def_order() { + // Define a struct with a specific, non-alphabetical field order. + let mut struct_defs = TypeDefMap::new(); + struct_defs.insert( + "Item".to_string(), + vec![ + ("name".to_string(), "string".to_string()), + ("id".to_string(), "uint256".to_string()), + ("active".to_string(), "bool".to_string()), + ], + ); + + // Create a DynSolValue instance for the struct. + let item_struct = DynSolValue::CustomStruct { + name: "Item".to_string(), + prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()], + tuple: vec![ + DynSolValue::String("Test Item".to_string()), + DynSolValue::Uint(U256::from(123), 256), + DynSolValue::Bool(true), + ], + }; + + // Serialize the value to JSON and verify that the order is preserved. + let json_value = serialize_value_as_json(item_struct, Some(&struct_defs.into())).unwrap(); + let json_string = serde_json::to_string(&json_value).unwrap(); + assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#); + } + + #[test] + fn test_json_full_cycle_typed_with_struct_defs() { + // Define a struct with a specific, non-alphabetical field order. + let mut struct_defs = TypeDefMap::new(); + struct_defs.insert( + "Wallet".to_string(), + vec![ + ("owner".to_string(), "address".to_string()), + ("balance".to_string(), "uint256".to_string()), + ("id".to_string(), "bytes32".to_string()), + ], + ); + + // Create the "original" DynSolValue instance. + let owner_address = Address::from([1; 20]); + let wallet_id = B256::from([2; 32]); + let original_wallet = DynSolValue::CustomStruct { + name: "Wallet".to_string(), + prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()], + tuple: vec![ + DynSolValue::Address(owner_address), + DynSolValue::Uint(U256::from(5000), 256), + DynSolValue::FixedBytes(wallet_id, 32), + ], + }; + + // Serialize it. The resulting JSON should respect the struct definition order. + let json_value = + serialize_value_as_json(original_wallet.clone(), Some(&struct_defs.clone().into())) + .unwrap(); + let json_string = serde_json::to_string(&json_value).unwrap(); + assert_eq!( + json_string, + format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#) + ); + + // Resolve the type, which should also respect the struct definition order. + let type_description = "Wallet(uint256 balance,bytes32 id,address owner)"; + let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap(); + + // Parse the JSON using the correctly ordered resolved type. Ensure that it is identical to + // the original one. + let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap(); + assert_eq!(parsed_value, original_wallet); + } } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index ca45a937b0b63..f3e922e5c14b4 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -3,7 +3,7 @@ //! Foundry cheatcodes implementations. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #![allow(elided_lifetimes_in_paths)] // Cheats context uses 3 lifetimes #[macro_use] @@ -15,18 +15,21 @@ pub extern crate foundry_cheatcodes_spec as spec; #[macro_use] extern crate tracing; -use alloy_evm::eth::EthEvmContext; use alloy_primitives::Address; -use foundry_evm_core::backend::DatabaseExt; -use spec::Status; +use foundry_evm_core::{ + backend::DatabaseExt, + evm::{FoundryContextFor, FoundryEvmNetwork}, +}; +use revm::context::{ContextTr, JournalTr}; +pub use Vm::ForgeContext; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; +pub use foundry_evm_core::evm::NestedEvmClosure; pub use inspector::{ BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, }; pub use spec::{CheatcodeDef, Vm}; -pub use Vm::ForgeContext; #[macro_use] mod error; @@ -47,6 +50,7 @@ mod evm; mod fs; mod inspector; +pub use inspector::CheatcodeAnalysis; mod json; @@ -63,11 +67,11 @@ mod toml; mod utils; /// Cheatcode implementation. -pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { +pub(crate) trait Cheatcode: CheatcodeDef { /// Applies this cheatcode to the given state. /// /// Implement this function if you don't need access to the EVM data. - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let _ = state; unimplemented!("{}", Self::CHEATCODE.func.id) } @@ -76,7 +80,7 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { self.apply(ccx.state) } @@ -84,69 +88,30 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the executor. #[inline(always)] - fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { let _ = executor; self.apply_stateful(ccx) } } -pub(crate) trait DynCheatcode: 'static { - fn cheatcode(&self) -> &'static spec::Cheatcode<'static>; - - fn as_debug(&self) -> &dyn std::fmt::Debug; - - fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result; -} - -impl DynCheatcode for T { - #[inline] - fn cheatcode(&self) -> &'static spec::Cheatcode<'static> { - Self::CHEATCODE - } - - #[inline] - fn as_debug(&self) -> &dyn std::fmt::Debug { - self - } - - #[inline] - fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { - self.apply_full(ccx, executor) - } -} - -impl dyn DynCheatcode { - pub(crate) fn name(&self) -> &'static str { - self.cheatcode().func.signature.split('(').next().unwrap() - } - - pub(crate) fn id(&self) -> &'static str { - self.cheatcode().func.id - } - - pub(crate) fn signature(&self) -> &'static str { - self.cheatcode().func.signature - } - - pub(crate) fn status(&self) -> &Status<'static> { - &self.cheatcode().status - } -} - -/// The cheatcode context, used in `Cheatcode`. -pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { +/// The cheatcode context. +pub struct CheatsCtxt<'a, 'db, FEN: FoundryEvmNetwork + 'db> { /// The cheatcodes inspector state. - pub(crate) state: &'cheats mut Cheatcodes, - /// The EVM data. - pub(crate) ecx: &'evm mut EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, + pub(crate) state: &'a mut Cheatcodes, + /// The EVM context. + pub(crate) ecx: &'a mut FoundryContextFor<'db, FEN>, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. pub(crate) gas_limit: u64, } -impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { - type Target = EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; +impl<'a, 'db, FEN: FoundryEvmNetwork> std::ops::Deref for CheatsCtxt<'a, 'db, FEN> { + type Target = FoundryContextFor<'db, FEN>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -154,16 +119,24 @@ impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { } } -impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> { +impl<'db, FEN: FoundryEvmNetwork> std::ops::DerefMut for CheatsCtxt<'_, 'db, FEN> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.ecx + self.ecx } } -impl CheatsCtxt<'_, '_, '_, '_> { - #[inline] +impl CheatsCtxt<'_, '_, FEN> { + pub(crate) fn ensure_not_precompile(&self, address: &Address) -> Result<()> { + if self.is_precompile(address) { Err(precompile_error(address)) } else { Ok(()) } + } + pub(crate) fn is_precompile(&self, address: &Address) -> bool { - self.ecx.journaled_state.inner.precompiles.contains(address) + self.ecx.journal().precompile_addresses().contains(address) } } + +#[cold] +fn precompile_error(address: &Address) -> Error { + fmt_err!("cannot use precompile {address} as an argument") +} diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index ce27b79253cd3..70d497d14c2a1 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,109 +1,110 @@ //! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. -use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; +use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_consensus::{SidecarBuilder, SimpleCoder}; -use alloy_primitives::{Address, Uint, B256, U256}; +use alloy_primitives::{Address, B256, U256, Uint}; use alloy_rpc_types::Authorization; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; -use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; +use foundry_evm_core::evm::FoundryEvmNetwork; +use foundry_wallets::{WalletSigner, wallet_multi::MultiWallet}; use parking_lot::Mutex; use revm::{ bytecode::Bytecode, - context::JournalTr, + context::{Cfg, ContextTr, JournalTr, Transaction}, context_interface::transaction::SignedAuthorization, - primitives::{hardfork::SpecId, KECCAK_EMPTY}, + primitives::{KECCAK_EMPTY, hardfork::SpecId}, }; use std::sync::Arc; impl Cheatcode for broadcast_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; broadcast(ccx, None, true) } } impl Cheatcode for broadcast_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } impl Cheatcode for broadcast_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } impl Cheatcode for attachDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { signedDelegation } = self; attach_delegation(ccx, signedDelegation, false) } } impl Cheatcode for attachDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { signedDelegation, crossChain } = self; attach_delegation(ccx, signedDelegation, *crossChain) } } impl Cheatcode for signDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, false) } } impl Cheatcode for signDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, false) } } impl Cheatcode for signDelegation_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey, crossChain } = *self; sign_delegation(ccx, privateKey, implementation, None, crossChain, false) } } impl Cheatcode for signAndAttachDelegation_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, true) } } impl Cheatcode for signAndAttachDelegation_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, true) } } impl Cheatcode for signAndAttachDelegation_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { implementation, privateKey, crossChain } = *self; sign_delegation(ccx, privateKey, implementation, None, crossChain, true) } } /// Helper function to attach an EIP-7702 delegation. -fn attach_delegation( - ccx: &mut CheatsCtxt, +fn attach_delegation( + ccx: &mut CheatsCtxt<'_, '_, FEN>, delegation: &SignedDelegation, cross_chain: bool, ) -> Result { let SignedDelegation { v, r, s, nonce, implementation } = delegation; // Set chain id to 0 if universal deployment is preferred. // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#protection-from-malleability-cross-chain - let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg().chain_id()) }; let auth = Authorization { address: *implementation, nonce: *nonce, chain_id }; let signed_auth = SignedAuthorization::new_unchecked( @@ -113,14 +114,14 @@ fn attach_delegation( U256::from_be_bytes(s.0), ); write_delegation(ccx, signed_auth.clone())?; - ccx.state.active_delegation = Some(signed_auth); + ccx.state.add_delegation(signed_auth); Ok(Default::default()) } /// Helper function to sign and attach (if needed) an EIP-7702 delegation. /// Uses the provided nonce, otherwise retrieves and increments the nonce of the EOA. -fn sign_delegation( - ccx: &mut CheatsCtxt, +fn sign_delegation( + ccx: &mut CheatsCtxt<'_, '_, FEN>, private_key: Uint<256, 4>, implementation: Address, nonce: Option, @@ -131,11 +132,19 @@ fn sign_delegation( let nonce = if let Some(nonce) = nonce { nonce } else { - let authority_acc = ccx.ecx.journaled_state.load_account(signer.address())?; - // If we don't have a nonce then use next auth account nonce. - authority_acc.data.info.nonce + 1 + let account_nonce = { + let authority_acc = ccx.ecx.journal_mut().load_account(signer.address())?; + authority_acc.info.nonce + }; + // Calculate next nonce considering existing active delegations + next_delegation_nonce( + &ccx.state.active_delegations, + signer.address(), + &ccx.state.broadcast, + account_nonce, + ) }; - let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg().chain_id()) }; let auth = Authorization { address: implementation, nonce, chain_id }; let sig = signer.sign_hash_sync(&auth.signature_hash())?; @@ -143,7 +152,7 @@ fn sign_delegation( if attach { let signed_auth = SignedAuthorization::new_unchecked(auth, sig.v() as u8, sig.r(), sig.s()); write_delegation(ccx, signed_auth.clone())?; - ccx.state.active_delegation = Some(signed_auth); + ccx.state.add_delegation(signed_auth); } Ok(SignedDelegation { v: sig.v() as u8, @@ -155,62 +164,113 @@ fn sign_delegation( .abi_encode()) } -fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> { +/// Returns the next valid nonce for a delegation, considering existing active delegations. +fn next_delegation_nonce( + active_delegations: &[SignedAuthorization], + authority: Address, + broadcast: &Option, + account_nonce: u64, +) -> u64 { + match active_delegations + .iter() + .rfind(|auth| auth.recover_authority().is_ok_and(|recovered| recovered == authority)) + { + Some(auth) => { + // Increment nonce of last recorded delegation. + auth.nonce + 1 + } + None => { + // First time a delegation is added for this authority. + if let Some(broadcast) = broadcast { + // Increment nonce if authority is the sender of transaction. + if broadcast.new_origin == authority { + return account_nonce + 1; + } + } + // Return current nonce if authority is not the sender of transaction. + account_nonce + } + } +} + +fn write_delegation( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + auth: SignedAuthorization, +) -> Result<()> { let authority = auth.recover_authority().map_err(|e| format!("{e}"))?; - let authority_acc = ccx.ecx.journaled_state.load_account(authority)?; - if authority_acc.data.info.nonce + 1 != auth.nonce { - return Err("invalid nonce".into()); + let account_nonce = { + let authority_acc = ccx.ecx.journal_mut().load_account(authority)?; + authority_acc.info.nonce + }; + + let expected_nonce = next_delegation_nonce( + &ccx.state.active_delegations, + authority, + &ccx.state.broadcast, + account_nonce, + ); + + if expected_nonce != auth.nonce { + return Err(format!( + "invalid nonce for {authority:?}: expected {expected_nonce}, got {}", + auth.nonce + ) + .into()); } if auth.address.is_zero() { // Set empty code if the delegation address of authority is 0x. // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#behavior. - ccx.ecx.journaled_state.set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); + ccx.ecx.journal_mut().set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); } else { let bytecode = Bytecode::new_eip7702(*auth.address()); - ccx.ecx.journaled_state.set_code(authority, bytecode); + ccx.ecx.journal_mut().set_code(authority, bytecode); } Ok(()) } impl Cheatcode for attachBlobCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { blob } = self; ensure!( - ccx.ecx.cfg.spec >= SpecId::CANCUN, + (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN, "`attachBlob` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(blob); - let sidecar = sidecar.build().map_err(|e| format!("{e}"))?; - ccx.state.active_blob_sidecar = Some(sidecar); + let sidecar_variant = if (*ccx.ecx.cfg().spec()).into() < SpecId::OSAKA { + sidecar.build_4844().map_err(|e| format!("{e}"))?.into() + } else { + sidecar.build_7594().map_err(|e| format!("{e}"))?.into() + }; + ccx.state.active_blob_sidecar = Some(sidecar_variant); Ok(Default::default()) } } impl Cheatcode for startBroadcast_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; broadcast(ccx, None, false) } } impl Cheatcode for startBroadcast_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } impl Cheatcode for startBroadcast_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } impl Cheatcode for stopBroadcastCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); @@ -221,7 +281,7 @@ impl Cheatcode for stopBroadcastCall { } impl Cheatcode for getWalletsCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let wallets = ccx.state.wallets().signers().unwrap_or_default(); Ok(wallets.abi_encode()) } @@ -239,6 +299,8 @@ pub struct Broadcast { pub depth: usize, /// Whether the prank stops by itself after the next call pub single_call: bool, + /// Whether `vm.deployCode` cheatcode is used to deploy from code. + pub deploy_from_code: bool, } /// Contains context for wallet management. @@ -250,7 +312,7 @@ pub struct WalletsInner { pub provided_sender: Option
, } -/// Clonable wrapper around [`WalletsInner`]. +/// Cloneable wrapper around [`WalletsInner`]. #[derive(Debug, Clone)] pub struct Wallets { /// Inner data. @@ -272,12 +334,6 @@ impl Wallets { .unwrap_or_else(|| panic!("not all instances were dropped")) } - /// Locks inner Mutex and adds a signer to the [MultiWallet]. - pub fn add_private_key(&self, private_key: &B256) -> Result<()> { - self.add_local_signer(PrivateKeySigner::from_bytes(private_key)?); - Ok(()) - } - /// Locks inner Mutex and adds a signer to the [MultiWallet]. pub fn add_local_signer(&self, wallet: PrivateKeySigner) { self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet)); @@ -285,17 +341,13 @@ impl Wallets { /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. pub fn signers(&self) -> Result> { - Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect()) + Ok(self.inner.lock().multi_wallet.signers()?.keys().copied().collect()) } /// Number of signers in the [MultiWallet]. pub fn len(&self) -> usize { let mut inner = self.inner.lock(); - let signers = inner.multi_wallet.signers(); - if signers.is_err() { - return 0; - } - signers.unwrap().len() + inner.multi_wallet.signers().map_or(0, |signers| signers.len()) } /// Whether the [MultiWallet] is empty. @@ -305,15 +357,19 @@ impl Wallets { } /// Sets up broadcasting from a script using `new_origin` as the sender. -fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bool) -> Result { - let depth = ccx.ecx.journaled_state.depth(); +fn broadcast( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + new_origin: Option<&Address>, + single_call: bool, +) -> Result { + let depth = ccx.ecx.journal().depth(); ensure!( ccx.state.get_prank(depth).is_none(), "you have an active prank; broadcasting and pranks are not compatible" ); ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already"); - let mut new_origin = new_origin.cloned(); + let mut new_origin = new_origin.copied(); if new_origin.is_none() { let mut wallets = ccx.state.wallets().inner.lock(); @@ -327,13 +383,17 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo } } } + let new_origin = new_origin.unwrap_or(ccx.ecx.tx().caller()); + // Ensure new origin is loaded and touched. + let _ = journaled_account(ccx.ecx, new_origin)?; let broadcast = Broadcast { - new_origin: new_origin.unwrap_or(ccx.ecx.tx.caller), + new_origin, original_caller: ccx.caller, - original_origin: ccx.ecx.tx.caller, + original_origin: ccx.ecx.tx().caller(), depth, single_call, + deploy_from_code: false, }; debug!(target: "cheatcodes", ?broadcast, "started"); ccx.state.broadcast = Some(broadcast); @@ -343,7 +403,11 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo /// Sets up broadcasting from a script with the sender derived from `private_key`. /// Adds this private key to `state`'s `wallets` vector to later be used for signing /// if broadcast is successful. -fn broadcast_key(ccx: &mut CheatsCtxt, private_key: &U256, single_call: bool) -> Result { +fn broadcast_key( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + private_key: &U256, + single_call: bool, +) -> Result { let wallet = super::crypto::parse_wallet(private_key)?; let new_origin = wallet.address(); diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index 080d9bc0820ff..a1ff1ba415cdf 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -2,12 +2,13 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{hex, U256}; +use alloy_primitives::{U256, hex}; use alloy_sol_types::SolValue; +use foundry_evm_core::evm::FoundryEvmNetwork; // address impl Cheatcode for toString_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } @@ -15,7 +16,7 @@ impl Cheatcode for toString_0Call { // bytes impl Cheatcode for toString_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } @@ -23,7 +24,7 @@ impl Cheatcode for toString_1Call { // bytes32 impl Cheatcode for toString_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } @@ -31,7 +32,7 @@ impl Cheatcode for toString_2Call { // bool impl Cheatcode for toString_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } @@ -39,7 +40,7 @@ impl Cheatcode for toString_3Call { // uint256 impl Cheatcode for toString_4Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } @@ -47,84 +48,84 @@ impl Cheatcode for toString_4Call { // int256 impl Cheatcode for toString_5Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } impl Cheatcode for parseBytesCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bytes) } } impl Cheatcode for parseAddressCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Address) } } impl Cheatcode for parseUintCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Uint(256)) } } impl Cheatcode for parseIntCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Int(256)) } } impl Cheatcode for parseBytes32Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseBoolCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bool) } } impl Cheatcode for toLowercaseCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_lowercase().abi_encode()) } } impl Cheatcode for toUppercaseCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_uppercase().abi_encode()) } } impl Cheatcode for trimCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.trim().abi_encode()) } } impl Cheatcode for replaceCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, from, to } = self; Ok(input.replace(from, to).abi_encode()) } } impl Cheatcode for splitCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, delimiter } = self; let parts: Vec<&str> = input.split(delimiter).collect(); Ok(parts.abi_encode()) @@ -132,14 +133,14 @@ impl Cheatcode for splitCall { } impl Cheatcode for indexOfCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, key } = self; Ok(input.find(key).map(U256::from).unwrap_or(U256::MAX).abi_encode()) } } impl Cheatcode for containsCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { subject, search } = self; Ok(subject.contains(search).abi_encode()) } @@ -166,7 +167,6 @@ where } } -#[instrument(target = "cheatcodes", level = "debug", skip(ty), fields(%ty), ret)] pub(super) fn parse_value(s: &str, ty: &DynSolType) -> Result { match ty.coerce_str(s) { Ok(value) => Ok(value), @@ -191,13 +191,13 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option { - if !s.starts_with("0x") && hex::check_raw(s) { - return Some(Err("missing hex prefix (\"0x\") for hex string")); - } + DynSolType::Int(_) + | DynSolType::Uint(_) + | DynSolType::FixedBytes(_) + | DynSolType::Bytes + if !s.starts_with("0x") && hex::check_raw(s) => + { + return Some(Err("missing hex prefix (\"0x\") for hex string")); } _ => {} } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index bc36accccde12..06e8cbdb66ab1 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -5,7 +5,8 @@ use alloy_chains::Chain as AlloyChain; use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; -use foundry_evm_core::constants::MAGIC_SKIP; +use foundry_evm_core::{constants::MAGIC_SKIP, evm::FoundryEvmNetwork}; +use revm::context::{ContextTr, JournalTr}; use std::str::FromStr; pub(crate) mod assert; @@ -14,28 +15,28 @@ pub(crate) mod expect; pub(crate) mod revert_handlers; impl Cheatcode for breakpoint_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } } impl Cheatcode for getFoundryVersionCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(SEMVER_VERSION.abi_encode()) } } impl Cheatcode for rpcUrlCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { rpcAlias } = self; let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode(); Ok(url) @@ -43,21 +44,21 @@ impl Cheatcode for rpcUrlCall { } impl Cheatcode for rpcUrlsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for rpcUrlStructsCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for sleepCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { duration } = self; let sleep_duration = std::time::Duration::from_millis(duration.saturating_to()); std::thread::sleep(sleep_duration); @@ -66,19 +67,26 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skip_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { skipTest } = *self; - skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) + if skipTest { + // Skip should not work if called deeper than at test level. + // Since we're not returning the magic skip bytes, this will cause a test failure. + ensure!(ccx.ecx.journal().depth() <= 1, "`skip` can only be used at test level"); + Err([MAGIC_SKIP, &[]].concat().into()) + } else { + Ok(Default::default()) + } } } impl Cheatcode for skip_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { skipTest, reason } = self; if *skipTest { // Skip should not work if called deeper than at test level. // Since we're not returning the magic skip bytes, this will cause a test failure. - ensure!(ccx.ecx.journaled_state.depth <= 1, "`skip` can only be used at test level"); + ensure!(ccx.ecx.journal().depth() <= 1, "`skip` can only be used at test level"); Err([MAGIC_SKIP, reason.as_bytes()].concat().into()) } else { Ok(Default::default()) @@ -87,14 +95,14 @@ impl Cheatcode for skip_1Call { } impl Cheatcode for getChain_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainAlias } = self; get_chain(state, chainAlias) } } impl Cheatcode for getChain_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainId } = self; // Convert the chainId to a string and use the existing get_chain function let chain_id_str = chainId.to_string(); @@ -103,7 +111,12 @@ impl Cheatcode for getChain_1Call { } /// Adds or removes the given breakpoint to the state. -fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result { +fn breakpoint( + state: &mut Cheatcodes, + caller: &Address, + s: &str, + add: bool, +) -> Result { let mut chars = s.chars(); let (Some(point), None) = (chars.next(), chars.next()) else { bail!("breakpoints must be exactly one character"); @@ -120,28 +133,33 @@ fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> R } /// Gets chain information for the given alias. -fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { +fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { // Parse the chain alias - works for both chain names and IDs let alloy_chain = AlloyChain::from_str(chain_alias) .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?; + let chain_name = alloy_chain.to_string(); + let chain_id = alloy_chain.id(); // Check if this is an unknown chain ID by comparing the name to the chain ID // When a numeric ID is passed for an unknown chain, alloy_chain.to_string() will return the ID // So if they match, it's likely an unknown chain ID - if alloy_chain.to_string() == alloy_chain.id().to_string() { + if chain_name == chain_id.to_string() { return Err(fmt_err!("invalid chain alias: {chain_alias}")); } - // First, try to get RPC URL from the user's config in foundry.toml - let rpc_url = state.config.rpc_endpoint(chain_alias).ok().and_then(|e| e.url().ok()); - - // If we couldn't get a URL from config, return an empty string - let rpc_url = rpc_url.unwrap_or_default(); + // Try to retrieve RPC URL and chain alias from user's config in foundry.toml. + let (rpc_url, chain_alias) = if let Some(rpc_url) = + state.config.rpc_endpoint(&chain_name).ok().and_then(|e| e.url().ok()) + { + (rpc_url, chain_name.clone()) + } else { + (String::new(), chain_alias.to_string()) + }; let chain_struct = Chain { - name: alloy_chain.to_string(), - chainId: U256::from(alloy_chain.id()), - chainAlias: chain_alias.to_string(), + name: chain_name, + chainId: U256::from(chain_id), + chainAlias: chain_alias, rpcUrl: rpc_url, }; diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 7bb433c88259f..12d625768a0c9 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -1,67 +1,85 @@ use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; -use alloy_primitives::{hex, I256, U256}; +use alloy_primitives::{I256, U256, U512}; use foundry_evm_core::{ abi::console::{format_units_int, format_units_uint}, backend::GLOBAL_FAIL_SLOT, constants::CHEATCODE_ADDRESS, + decode::ASSERTION_FAILED_PREFIX, + evm::FoundryEvmNetwork, }; use itertools::Itertools; -use revm::context::JournalTr; -use std::fmt::{Debug, Display}; +use revm::context::{ContextTr, JournalTr}; +use std::{borrow::Cow, fmt}; const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); -#[derive(Debug, thiserror::Error)] -#[error("assertion failed")] -struct SimpleAssertionError; +struct ComparisonAssertionError<'a, T> { + kind: AssertionKind, + left: &'a T, + right: &'a T, +} -#[derive(thiserror::Error, Debug)] -enum ComparisonAssertionError<'a, T> { - Ne { left: &'a T, right: &'a T }, - Eq { left: &'a T, right: &'a T }, - Ge { left: &'a T, right: &'a T }, - Gt { left: &'a T, right: &'a T }, - Le { left: &'a T, right: &'a T }, - Lt { left: &'a T, right: &'a T }, -} - -macro_rules! format_values { - ($self:expr, $format_fn:expr) => { - match $self { - Self::Ne { left, right } => format!("{} == {}", $format_fn(left), $format_fn(right)), - Self::Eq { left, right } => format!("{} != {}", $format_fn(left), $format_fn(right)), - Self::Ge { left, right } => format!("{} < {}", $format_fn(left), $format_fn(right)), - Self::Gt { left, right } => format!("{} <= {}", $format_fn(left), $format_fn(right)), - Self::Le { left, right } => format!("{} > {}", $format_fn(left), $format_fn(right)), - Self::Lt { left, right } => format!("{} >= {}", $format_fn(left), $format_fn(right)), +#[derive(Clone, Copy)] +enum AssertionKind { + Eq, + Ne, + Gt, + Ge, + Lt, + Le, +} + +impl AssertionKind { + const fn inverse(self) -> Self { + match self { + Self::Eq => Self::Ne, + Self::Ne => Self::Eq, + Self::Gt => Self::Le, + Self::Ge => Self::Lt, + Self::Lt => Self::Ge, + Self::Le => Self::Gt, } - }; + } + + const fn to_str(self) -> &'static str { + match self { + Self::Eq => "==", + Self::Ne => "!=", + Self::Gt => ">", + Self::Ge => ">=", + Self::Lt => "<", + Self::Le => "<=", + } + } +} + +impl ComparisonAssertionError<'_, T> { + fn format_values(&self, f: impl Fn(&T) -> D) -> String { + format!("{} {} {}", f(self.left), self.kind.inverse().to_str(), f(self.right)) + } } -impl ComparisonAssertionError<'_, T> { +impl ComparisonAssertionError<'_, T> { fn format_for_values(&self) -> String { - format_values!(self, T::to_string) + self.format_values(T::to_string) } } -impl ComparisonAssertionError<'_, Vec> { +impl ComparisonAssertionError<'_, Vec> { fn format_for_arrays(&self) -> String { - let formatter = |v: &Vec| format!("[{}]", v.iter().format(", ")); - format_values!(self, formatter) + self.format_values(|v| format!("[{}]", v.iter().format(", "))) } } impl ComparisonAssertionError<'_, U256> { fn format_with_decimals(&self, decimals: &U256) -> String { - let formatter = |v: &U256| format_units_uint(v, decimals); - format_values!(self, formatter) + self.format_values(|v| format_units_uint(v, decimals)) } } impl ComparisonAssertionError<'_, I256> { fn format_with_decimals(&self, decimals: &U256) -> String { - let formatter = |v: &I256| format_units_int(v, decimals); - format_values!(self, formatter) + self.format_values(|v| format_units_int(v, decimals)) } } @@ -108,8 +126,8 @@ enum EqRelDelta { Undefined, } -impl Display for EqRelDelta { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for EqRelDelta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Defined(delta) => write!(f, "{}", format_delta_percent(delta)), Self::Undefined => write!(f, "undefined"), @@ -146,7 +164,7 @@ impl EqRelAssertionError { format_units_uint(&f.left, decimals), format_units_uint(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } @@ -161,92 +179,101 @@ impl EqRelAssertionError { format_units_int(&f.left, decimals), format_units_int(&f.right, decimals), format_delta_percent(&f.max_delta), - &f.real_delta, + f.real_delta, ), Self::Overflow => self.to_string(), } } } -type ComparisonResult<'a, T> = Result, ComparisonAssertionError<'a, T>>; +type ComparisonResult<'a, T> = Result<(), ComparisonAssertionError<'a, T>>; -fn handle_assertion_result( - result: core::result::Result, ERR>, - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, - error_formatter: impl Fn(&ERR) -> String, +#[cold] +fn handle_assertion_result( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + err: E, + error_formatter: Option<&dyn Fn(&E) -> String>, error_msg: Option<&str>, - format_error: bool, ) -> Result { - match result { - Ok(_) => Ok(Default::default()), - Err(err) => { - let error_msg = error_msg.unwrap_or("assertion failed"); - let msg = if format_error { - format!("{error_msg}: {}", error_formatter(&err)) - } else { - error_msg.to_string() - }; - if ccx.state.config.assertions_revert { - Err(msg.into()) - } else { - executor.console_log(ccx, &msg); - ccx.ecx.journaled_state.sstore( - CHEATCODE_ADDRESS, - GLOBAL_FAIL_SLOT, - U256::from(1), - )?; - Ok(Default::default()) - } - } + let error_msg = error_msg.unwrap_or(ASSERTION_FAILED_PREFIX); + let msg = if let Some(error_formatter) = error_formatter { + Cow::Owned(format!("{error_msg}: {}", error_formatter(&err))) + } else { + Cow::Borrowed(error_msg) + }; + handle_assertion_result_mono(ccx, executor, msg) +} + +fn handle_assertion_result_mono( + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, + msg: Cow<'_, str>, +) -> Result { + if ccx.state.config.assertions_revert { + Err(msg.into_owned().into()) + } else { + executor.console_log(&msg); + ccx.ecx.journal_mut().sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; + Ok(Default::default()) } } /// Implements [crate::Cheatcode] for pairs of cheatcodes. /// /// Accepts a list of pairs of cheatcodes, where the first cheatcode is the one that doesn't contain -/// a custom error message, and the second one contains it at `error` field. +/// a custom error message, and the second one contains it at `err` field. /// -/// Passed `args` are the common arguments for both cheatcode structs (excluding `error` field). +/// Passed `args` are the common arguments for both cheatcode structs (excluding `err` field). /// /// Macro also accepts an optional closure that formats the error returned by the assertion. macro_rules! impl_assertions { - (|$($arg:ident),*| $body:expr, $format_error:literal, $(($no_error:ident, $with_error:ident)),* $(,)?) => { - impl_assertions!(@args_tt |($($arg),*)| $body, |e| e.to_string(), $format_error, $(($no_error, $with_error),)*); + (|$($arg:ident),*| $body:expr, false, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + impl_assertions! { @args_tt |($($arg),*)| $body, None, $(($no_error, $with_error)),* } }; (|$($arg:ident),*| $body:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { - impl_assertions!(@args_tt |($($arg),*)| $body, |e| e.to_string(), true, $(($no_error, $with_error),)*); + impl_assertions! { @args_tt |($($arg),*)| $body, Some(&ToString::to_string), $(($no_error, $with_error)),* } }; (|$($arg:ident),*| $body:expr, $error_formatter:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { - impl_assertions!(@args_tt |($($arg),*)| $body, $error_formatter, true, $(($no_error, $with_error)),*); + impl_assertions! { @args_tt |($($arg),*)| $body, Some(&$error_formatter), $(($no_error, $with_error)),* } }; + // We convert args to `tt` and later expand them back into tuple to allow usage of expanded args inside of // each assertion type context. - (@args_tt |$args:tt| $body:expr, $error_formatter:expr, $format_error:literal, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + (@args_tt |$args:tt| $body:expr, $error_formatter:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { $( - impl_assertions!(@impl $no_error, $with_error, $args, $body, $error_formatter, $format_error); + impl_assertions! { @impl $no_error, $with_error, $args, $body, $error_formatter } )* }; - (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr, $format_error:literal) => { + + (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr) => { impl crate::Cheatcode for $no_error { - fn apply_full( + fn apply_full( &self, - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg),* } = self; - handle_assertion_result($body, ccx, executor, $error_formatter, None, $format_error) + match $body { + Ok(()) => Ok(Default::default()), + Err(err) => handle_assertion_result(ccx, executor, err, $error_formatter, None) + } } } impl crate::Cheatcode for $with_error { - fn apply_full( + fn apply_full( &self, - ccx: &mut CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { - let Self { $($arg),*, error} = self; - handle_assertion_result($body, ccx, executor, $error_formatter, Some(error), $format_error) + let Self { $($arg,)* err } = self; + match $body { + Ok(()) => Ok(Default::default()), + Err(assertion_err) => { + handle_assertion_result(ccx, executor, assertion_err, $error_formatter, Some(err)) + } + } } } }; @@ -266,38 +293,25 @@ impl_assertions! { impl_assertions! { |left, right| assert_eq(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertEq_0Call, assertEq_1Call), (assertEq_2Call, assertEq_3Call), (assertEq_4Call, assertEq_5Call), (assertEq_6Call, assertEq_7Call), (assertEq_8Call, assertEq_9Call), (assertEq_10Call, assertEq_11Call), -} - -impl_assertions! { - |left, right| assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), - |e| e.format_for_values(), (assertEq_12Call, assertEq_13Call), } impl_assertions! { |left, right| assert_eq(left, right), - |e| e.format_for_arrays(), + ComparisonAssertionError::format_for_arrays, (assertEq_14Call, assertEq_15Call), (assertEq_16Call, assertEq_17Call), (assertEq_18Call, assertEq_19Call), (assertEq_20Call, assertEq_21Call), (assertEq_22Call, assertEq_23Call), (assertEq_24Call, assertEq_25Call), -} - -impl_assertions! { - |left, right| assert_eq( - &left.iter().map(hex::encode_prefixed).collect::>(), - &right.iter().map(hex::encode_prefixed).collect::>(), - ), - |e| e.format_for_arrays(), (assertEq_26Call, assertEq_27Call), } @@ -310,38 +324,25 @@ impl_assertions! { impl_assertions! { |left, right| assert_not_eq(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertNotEq_0Call, assertNotEq_1Call), (assertNotEq_2Call, assertNotEq_3Call), (assertNotEq_4Call, assertNotEq_5Call), (assertNotEq_6Call, assertNotEq_7Call), (assertNotEq_8Call, assertNotEq_9Call), (assertNotEq_10Call, assertNotEq_11Call), -} - -impl_assertions! { - |left, right| assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), - |e| e.format_for_values(), (assertNotEq_12Call, assertNotEq_13Call), } impl_assertions! { |left, right| assert_not_eq(left, right), - |e| e.format_for_arrays(), + ComparisonAssertionError::format_for_arrays, (assertNotEq_14Call, assertNotEq_15Call), (assertNotEq_16Call, assertNotEq_17Call), (assertNotEq_18Call, assertNotEq_19Call), (assertNotEq_20Call, assertNotEq_21Call), (assertNotEq_22Call, assertNotEq_23Call), (assertNotEq_24Call, assertNotEq_25Call), -} - -impl_assertions! { - |left, right| assert_not_eq( - &left.iter().map(hex::encode_prefixed).collect::>(), - &right.iter().map(hex::encode_prefixed).collect::>(), - ), - |e| e.format_for_arrays(), (assertNotEq_26Call, assertNotEq_27Call), } @@ -354,7 +355,7 @@ impl_assertions! { impl_assertions! { |left, right| assert_gt(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertGt_0Call, assertGt_1Call), (assertGt_2Call, assertGt_3Call), } @@ -368,7 +369,7 @@ impl_assertions! { impl_assertions! { |left, right| assert_ge(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertGe_0Call, assertGe_1Call), (assertGe_2Call, assertGe_3Call), } @@ -382,7 +383,7 @@ impl_assertions! { impl_assertions! { |left, right| assert_lt(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertLt_0Call, assertLt_1Call), (assertLt_2Call, assertLt_3Call), } @@ -396,7 +397,7 @@ impl_assertions! { impl_assertions! { |left, right| assert_le(left, right), - |e| e.format_for_values(), + ComparisonAssertionError::format_for_values, (assertLe_0Call, assertLe_1Call), (assertLe_2Call, assertLe_3Call), } @@ -452,43 +453,59 @@ impl_assertions! { (assertApproxEqRelDecimal_2Call, assertApproxEqRelDecimal_3Call), } -fn assert_true(condition: bool) -> Result, SimpleAssertionError> { - if condition { - Ok(Default::default()) - } else { - Err(SimpleAssertionError) - } +const fn assert_true(condition: bool) -> Result<(), ()> { + if condition { Ok(()) } else { Err(()) } } -fn assert_false(condition: bool) -> Result, SimpleAssertionError> { - if !condition { - Ok(Default::default()) - } else { - Err(SimpleAssertionError) - } +const fn assert_false(condition: bool) -> Result<(), ()> { + assert_true(!condition) } fn assert_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left == right { - Ok(Default::default()) + Ok(()) } else { - Err(ComparisonAssertionError::Eq { left, right }) + Err(ComparisonAssertionError { kind: AssertionKind::Eq, left, right }) } } fn assert_not_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left != right { - Ok(Default::default()) + if left == right { + Err(ComparisonAssertionError { kind: AssertionKind::Ne, left, right }) } else { - Err(ComparisonAssertionError::Ne { left, right }) + Ok(()) } } -fn get_delta_uint(left: U256, right: U256) -> U256 { +fn assert_gt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left > right { - left - right + Ok(()) + } else { + Err(ComparisonAssertionError { kind: AssertionKind::Gt, left, right }) + } +} + +fn assert_ge<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left >= right { + Ok(()) + } else { + Err(ComparisonAssertionError { kind: AssertionKind::Ge, left, right }) + } +} + +fn assert_lt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left < right { + Ok(()) + } else { + Err(ComparisonAssertionError { kind: AssertionKind::Lt, left, right }) + } +} + +fn assert_le<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left <= right { + Ok(()) } else { - right - left + Err(ComparisonAssertionError { kind: AssertionKind::Le, left, right }) } } @@ -497,25 +514,30 @@ fn get_delta_int(left: I256, right: I256) -> U256 { let (right_sign, right_abs) = right.into_sign_and_abs(); if left_sign == right_sign { - if left_abs > right_abs { - left_abs - right_abs - } else { - right_abs - left_abs - } + if left_abs > right_abs { left_abs - right_abs } else { right_abs - left_abs } } else { - left_abs + right_abs + left_abs.wrapping_add(right_abs) } } +/// Calculates the relative delta for an absolute difference. +/// +/// Avoids overflow in the multiplication by using [`U512`] to hold the intermediary result. +fn calc_delta_full(abs_diff: U256, right: U256) -> Result> { + let delta = U512::from(abs_diff) * U512::from(10).pow(U512::from(EQ_REL_DELTA_RESOLUTION)) + / U512::from(right); + U256::checked_from_limbs_slice(delta.as_limbs()).ok_or(EqRelAssertionError::Overflow) +} + fn uint_assert_approx_eq_abs( left: U256, right: U256, max_delta: U256, -) -> Result, Box>> { - let delta = get_delta_uint(left, right); +) -> Result<(), Box>> { + let delta = left.abs_diff(right); if delta <= max_delta { - Ok(Default::default()) + Ok(()) } else { Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) } @@ -525,11 +547,11 @@ fn int_assert_approx_eq_abs( left: I256, right: I256, max_delta: U256, -) -> Result, Box>> { +) -> Result<(), Box>> { let delta = get_delta_int(left, right); if delta <= max_delta { - Ok(Default::default()) + Ok(()) } else { Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) } @@ -539,27 +561,23 @@ fn uint_assert_approx_eq_rel( left: U256, right: U256, max_delta: U256, -) -> Result, EqRelAssertionError> { +) -> Result<(), EqRelAssertionError> { if right.is_zero() { if left.is_zero() { - return Ok(Default::default()) - } else { - return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { - left, - right, - max_delta, - real_delta: EqRelDelta::Undefined, - }))) - }; + return Ok(()); + } + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))); } - let delta = get_delta_uint(left, right) - .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) - .ok_or(EqRelAssertionError::Overflow)? / - right; + let delta = calc_delta_full::(left.abs_diff(right), right)?; if delta <= max_delta { - Ok(Default::default()) + Ok(()) } else { Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, @@ -574,28 +592,23 @@ fn int_assert_approx_eq_rel( left: I256, right: I256, max_delta: U256, -) -> Result, EqRelAssertionError> { +) -> Result<(), EqRelAssertionError> { if right.is_zero() { if left.is_zero() { - return Ok(Default::default()) - } else { - return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { - left, - right, - max_delta, - real_delta: EqRelDelta::Undefined, - }))) + return Ok(()); } + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))); } - let (_, abs_right) = right.into_sign_and_abs(); - let delta = get_delta_int(left, right) - .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) - .ok_or(EqRelAssertionError::Overflow)? / - abs_right; + let delta = calc_delta_full::(get_delta_int(left, right), right.unsigned_abs())?; if delta <= max_delta { - Ok(Default::default()) + Ok(()) } else { Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, @@ -605,35 +618,3 @@ fn int_assert_approx_eq_rel( }))) } } - -fn assert_gt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left > right { - Ok(Default::default()) - } else { - Err(ComparisonAssertionError::Gt { left, right }) - } -} - -fn assert_ge<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left >= right { - Ok(Default::default()) - } else { - Err(ComparisonAssertionError::Ge { left, right }) - } -} - -fn assert_lt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left < right { - Ok(Default::default()) - } else { - Err(ComparisonAssertionError::Lt { left, right }) - } -} - -fn assert_le<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { - if left <= right { - Ok(Default::default()) - } else { - Err(ComparisonAssertionError::Le { left, right }) - } -} diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs index 6cdf3e621aece..570314fa12b8c 100644 --- a/crates/cheatcodes/src/test/assume.rs +++ b/crates/cheatcodes/src/test/assume.rs @@ -1,8 +1,9 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; use alloy_primitives::Address; -use foundry_evm_core::constants::MAGIC_ASSUME; +use foundry_evm_core::{constants::MAGIC_ASSUME, evm::FoundryEvmNetwork}; +use revm::context::{ContextTr, JournalTr}; use spec::Vm::{ - assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, PotentialRevert, + PotentialRevert, assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, }; use std::fmt::Debug; @@ -44,46 +45,42 @@ impl AcceptableRevertParameters { } impl Cheatcode for assumeCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { condition } = self; - if *condition { - Ok(Default::default()) - } else { - Err(Error::from(MAGIC_ASSUME)) - } + if *condition { Ok(Default::default()) } else { Err(Error::from(MAGIC_ASSUME)) } } } impl Cheatcode for assumeNoRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth, vec![]) + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + assume_no_revert(ccx.state, ccx.ecx.journal().depth(), vec![]) } } impl Cheatcode for assumeNoRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { potentialRevert } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth, + ccx.ecx.journal().depth(), vec![AcceptableRevertParameters::from(potentialRevert)], ) } } impl Cheatcode for assumeNoRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { potentialReverts } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth, + ccx.ecx.journal().depth(), potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), ) } } -fn assume_no_revert( - state: &mut Cheatcodes, +fn assume_no_revert( + state: &mut Cheatcodes, depth: usize, parameters: Vec, ) -> Result { diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 1917331de54de..5ef5b46ff4919 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -4,13 +4,20 @@ use std::{ }; use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; +use alloy_dyn_abi::{DynSolValue, EventExt}; +use alloy_json_abi::Event; use alloy_primitives::{ - map::{hash_map::Entry, AddressHashMap, HashMap}, - Address, Bytes, LogData as RawLog, U256, + Address, Bytes, LogData as RawLog, U256, hex, + map::{AddressHashMap, HashMap, hash_map::Entry}, }; +use foundry_common::{abi::get_indexed_event, fmt::format_token}; +use foundry_evm_core::evm::FoundryEvmNetwork; +use foundry_evm_traces::DecodedCallLog; use revm::{ - context::JournalTr, - interpreter::{InstructionResult, Interpreter, InterpreterAction, InterpreterResult}, + context::{ContextTr, JournalTr}, + interpreter::{ + InstructionResult, Interpreter, InterpreterAction, interpreter_types::LoopControl, + }, }; use super::revert_handlers::RevertParameters; @@ -67,7 +74,7 @@ pub enum ExpectedRevertKind { #[derive(Clone, Debug)] pub struct ExpectedRevert { /// The expected data returned by the revert, None being any. - pub reason: Option>, + pub reason: Option, /// The depth at which the revert is expected. pub depth: usize, /// The type of expected revert. @@ -108,6 +115,8 @@ pub struct ExpectedEmit { pub found: bool, /// Number of times the log is expected to be emitted pub count: u64, + /// Stores mismatch details if a log didn't match + pub mismatch_error: Option, } #[derive(Clone, Debug)] @@ -146,7 +155,7 @@ impl From for CreateScheme { } impl CreateScheme { - pub fn eq(&self, create_scheme: Self) -> bool { + pub const fn eq(&self, create_scheme: Self) -> bool { matches!( (self, create_scheme), (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. }) @@ -155,28 +164,28 @@ impl CreateScheme { } impl Cheatcode for expectCall_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data } = self; expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount) } } impl Cheatcode for expectCall_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data, count } = self; expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count) } } impl Cheatcode for expectCall_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data } = self; expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount) } } impl Cheatcode for expectCall_3Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data, count } = self; expect_call( state, @@ -192,7 +201,7 @@ impl Cheatcode for expectCall_3Call { } impl Cheatcode for expectCall_4Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data } = self; expect_call( state, @@ -208,7 +217,7 @@ impl Cheatcode for expectCall_4Call { } impl Cheatcode for expectCall_5Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data, count } = self; expect_call( state, @@ -224,7 +233,7 @@ impl Cheatcode for expectCall_5Call { } impl Cheatcode for expectCallMinGas_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data } = self; expect_call( state, @@ -240,7 +249,7 @@ impl Cheatcode for expectCallMinGas_0Call { } impl Cheatcode for expectCallMinGas_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data, count } = self; expect_call( state, @@ -256,11 +265,11 @@ impl Cheatcode for expectCallMinGas_1Call { } impl Cheatcode for expectEmit_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, @@ -270,11 +279,11 @@ impl Cheatcode for expectEmit_0Call { } impl Cheatcode for expectEmit_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, @@ -284,25 +293,25 @@ impl Cheatcode for expectEmit_1Call { } impl Cheatcode for expectEmit_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, 1) } } impl Cheatcode for expectEmit_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), false, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, 1) } } impl Cheatcode for expectEmit_4Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, @@ -312,11 +321,11 @@ impl Cheatcode for expectEmit_4Call { } impl Cheatcode for expectEmit_5Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, @@ -326,32 +335,25 @@ impl Cheatcode for expectEmit_5Call { } impl Cheatcode for expectEmit_6Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { count } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, count) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, count) } } impl Cheatcode for expectEmit_7Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { emitter, count } = *self; - expect_emit( - ccx.state, - ccx.ecx.journaled_state.depth(), - [true; 5], - Some(emitter), - false, - count, - ) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, count) } } impl Cheatcode for expectEmitAnonymous_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], None, true, @@ -361,11 +363,11 @@ impl Cheatcode for expectEmitAnonymous_0Call { } impl Cheatcode for expectEmitAnonymous_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), true, @@ -375,47 +377,47 @@ impl Cheatcode for expectEmitAnonymous_1Call { } impl Cheatcode for expectEmitAnonymous_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, true, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, true, 1) } } impl Cheatcode for expectEmitAnonymous_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), true, 1) + expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), true, 1) } } impl Cheatcode for expectCreateCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create) } } impl Cheatcode for expectCreate2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create2) } } impl Cheatcode for expectRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, 1) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, 1) } } impl Cheatcode for expectRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -425,42 +427,26 @@ impl Cheatcode for expectRevert_1Call { } impl Cheatcode for expectRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData } = self; - expect_revert( - ccx.state, - Some(revertData), - ccx.ecx.journaled_state.depth(), - false, - false, - None, - 1, - ) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, 1) } } impl Cheatcode for expectRevert_3Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { reverter } = self; - expect_revert( - ccx.state, - None, - ccx.ecx.journaled_state.depth(), - false, - false, - Some(*reverter), - 1, - ) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), 1) } } impl Cheatcode for expectRevert_4Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -470,12 +456,12 @@ impl Cheatcode for expectRevert_4Call { } impl Cheatcode for expectRevert_5Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -485,19 +471,19 @@ impl Cheatcode for expectRevert_5Call { } impl Cheatcode for expectRevert_6Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { count } = self; - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, *count) + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, *count) } } impl Cheatcode for expectRevert_7Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -507,12 +493,12 @@ impl Cheatcode for expectRevert_7Call { } impl Cheatcode for expectRevert_8Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, None, @@ -522,12 +508,12 @@ impl Cheatcode for expectRevert_8Call { } impl Cheatcode for expectRevert_9Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { reverter, count } = self; expect_revert( ccx.state, None, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -537,12 +523,12 @@ impl Cheatcode for expectRevert_9Call { } impl Cheatcode for expectRevert_10Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -552,12 +538,12 @@ impl Cheatcode for expectRevert_10Call { } impl Cheatcode for expectRevert_11Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, false, Some(*reverter), @@ -567,12 +553,12 @@ impl Cheatcode for expectRevert_11Call { } impl Cheatcode for expectPartialRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, true, None, @@ -582,12 +568,12 @@ impl Cheatcode for expectPartialRevert_0Call { } impl Cheatcode for expectPartialRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), false, true, Some(*reverter), @@ -597,18 +583,18 @@ impl Cheatcode for expectPartialRevert_1Call { } impl Cheatcode for _expectCheatcodeRevert_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None, 1) + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + expect_revert(ccx.state, None, ccx.ecx.journal().depth(), true, false, None, 1) } } impl Cheatcode for _expectCheatcodeRevert_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), - ccx.ecx.journaled_state.depth(), + ccx.ecx.journal().depth(), true, false, None, @@ -618,39 +604,31 @@ impl Cheatcode for _expectCheatcodeRevert_1Call { } impl Cheatcode for _expectCheatcodeRevert_2Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { revertData } = self; - expect_revert( - ccx.state, - Some(revertData), - ccx.ecx.journaled_state.depth(), - true, - false, - None, - 1, - ) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), true, false, None, 1) } } impl Cheatcode for expectSafeMemoryCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth().try_into()?) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journal().depth().try_into()?) } } impl Cheatcode for stopExpectSafeMemoryCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self {} = self; - ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth().try_into()?); + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journal().depth().try_into()?); Ok(Default::default()) } } impl Cheatcode for expectSafeMemoryCallCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, (ccx.ecx.journaled_state.depth() + 1).try_into()?) + expect_safe_memory(ccx.state, min, max, (ccx.ecx.journal().depth() + 1).try_into()?) } } @@ -660,7 +638,7 @@ impl RevertParameters for ExpectedRevert { } fn reason(&self) -> Option<&[u8]> { - self.reason.as_deref() + self.reason.as_ref().map(|b| &***b) } fn partial_match(&self) -> bool { @@ -685,8 +663,8 @@ impl RevertParameters for ExpectedRevert { /// address(0xc4f3) and selector `0xd34db33f` to be made at least once. If the amount of calls is /// 0, the test will fail. If the call is made more than once, the test will pass. #[expect(clippy::too_many_arguments)] // It is what it is -fn expect_call( - state: &mut Cheatcodes, +fn expect_call( + state: &mut Cheatcodes, target: &Address, calldata: &Bytes, value: Option<&U256>, @@ -697,17 +675,17 @@ fn expect_call( ) -> Result { let expecteds = state.expected_calls.entry(*target).or_default(); - if let Some(val) = value { - if *val > U256::ZERO { - // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas - // to ensure that the basic fallback function can be called. - let positive_value_cost_stipend = 2300; - if let Some(gas) = &mut gas { - *gas += positive_value_cost_stipend; - } - if let Some(min_gas) = &mut min_gas { - *min_gas += positive_value_cost_stipend; - } + if let Some(val) = value + && *val > U256::ZERO + { + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = 2300; + if let Some(gas) = &mut gas { + *gas += positive_value_cost_stipend; + } + if let Some(min_gas) = &mut min_gas { + *min_gas += positive_value_cost_stipend; } } @@ -752,16 +730,24 @@ fn expect_call( Ok(Default::default()) } -fn expect_emit( - state: &mut Cheatcodes, +fn expect_emit( + state: &mut Cheatcodes, depth: usize, checks: [bool; 5], address: Option
, anonymous: bool, count: u64, ) -> Result { - let expected_emit = - ExpectedEmit { depth, checks, address, found: false, log: None, anonymous, count }; + let expected_emit = ExpectedEmit { + depth, + checks, + address, + found: false, + log: None, + anonymous, + count, + mismatch_error: None, + }; if let Some(found_emit_pos) = state.expected_emits.iter().position(|(emit, _)| emit.found) { // The order of emits already found (back of queue) should not be modified, hence push any // new emit before first found emit. @@ -774,11 +760,15 @@ fn expect_emit( Ok(Default::default()) } -pub(crate) fn handle_expect_emit( - state: &mut Cheatcodes, +pub(crate) fn handle_expect_emit( + state: &mut Cheatcodes, log: &alloy_primitives::Log, - interpreter: &mut Interpreter, -) { + mut interpreter: Option<&mut Interpreter>, +) -> Option<&'static str> { + // This function returns an optional string indicating a failure reason. + // If the string is `Some`, it indicates that the expectation failed with the provided reason. + let mut failure_reason = None; + // Fill or check the expected emits. // We expect for emit checks to be filled as they're declared (from oldest to newest), // so we fill them and push them to the back of the queue. @@ -790,7 +780,32 @@ pub(crate) fn handle_expect_emit( // This allows a contract to arbitrarily emit more events than expected (additive behavior), // as long as all the previous events were matched in the order they were expected to be. if state.expected_emits.iter().all(|(expected, _)| expected.found) { - return + return failure_reason; + } + + // Check count=0 expectations against this log - fail immediately if violated + for (expected_emit, _) in &state.expected_emits { + if expected_emit.count == 0 + && !expected_emit.found + && let Some(expected_log) = &expected_emit.log + && checks_topics_and_data(expected_emit.checks, expected_log, log) + // Check revert address + && (expected_emit.address.is_none() || expected_emit.address == Some(log.address)) + { + if let Some(interpreter) = &mut interpreter { + // This event was emitted but we expected it NOT to be (count=0) + // Fail immediately + interpreter.bytecode.set_action(InterpreterAction::new_return( + InstructionResult::Revert, + Error::encode("log emitted but expected 0 times"), + interpreter.gas, + )); + } else { + failure_reason = Some("log emitted but expected 0 times"); + } + + return failure_reason; + } } let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none()); @@ -804,11 +819,19 @@ pub(crate) fn handle_expect_emit( .unwrap_or(state.expected_emits.len()) .saturating_sub(1) } else { - // Otherwise, if all expected logs are filled, we start to check any unmatched event + // if all expected logs are filled, check any unmatched event // in the declared order, so we start from the front (like a queue). - 0 + // Skip count=0 expectations as they are handled separately above + state.expected_emits.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0) }; + // If there are only count=0 expectations left, we can return early + if !should_fill_logs + && state.expected_emits.iter().all(|(emit, _)| emit.found || emit.count == 0) + { + return failure_reason; + } + let (mut event_to_fill_or_check, mut count_map) = state .expected_emits .remove(index_to_fill_or_check) @@ -823,34 +846,29 @@ pub(crate) fn handle_expect_emit( state .expected_emits .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map)); + } else if let Some(interpreter) = &mut interpreter { + interpreter.bytecode.set_action(InterpreterAction::new_return( + InstructionResult::Revert, + Error::encode("use vm.expectEmitAnonymous to match anonymous events"), + interpreter.gas, + )); } else { - interpreter.control.instruction_result = InstructionResult::Revert; - interpreter.control.next_action = InterpreterAction::Return { - result: InterpreterResult { - output: Error::encode("use vm.expectEmitAnonymous to match anonymous events"), - gas: interpreter.control.gas, - result: InstructionResult::Revert, - }, - }; + failure_reason = Some("use vm.expectEmitAnonymous to match anonymous events"); } - return + + return failure_reason; }; // Increment/set `count` for `log.address` and `log.data` match count_map.entry(log.address) { Entry::Occupied(mut entry) => { - // Checks and inserts the log into the map. - // If the log doesn't pass the checks, it is ignored and `count` is not incremented. let log_count_map = entry.get_mut(); log_count_map.insert(&log.data); } Entry::Vacant(entry) => { let mut log_count_map = LogCountMap::new(&event_to_fill_or_check); - if log_count_map.satisfies_checks(&log.data) { log_count_map.insert(&log.data); - - // Entry is only inserted if it satisfies the checks. entry.insert(log_count_map); } } @@ -858,16 +876,42 @@ pub(crate) fn handle_expect_emit( event_to_fill_or_check.found = || -> bool { if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) { - return false + // Store detailed mismatch information + + // Try to decode the events if we have a signature identifier + let (expected_decoded, actual_decoded) = if let Some(signatures_identifier) = + state.signatures_identifier() + && !event_to_fill_or_check.anonymous + { + ( + decode_event(signatures_identifier, expected), + decode_event(signatures_identifier, log), + ) + } else { + (None, None) + }; + event_to_fill_or_check.mismatch_error = Some(get_emit_mismatch_message( + event_to_fill_or_check.checks, + expected, + log, + event_to_fill_or_check.anonymous, + expected_decoded.as_ref(), + actual_decoded.as_ref(), + )); + return false; } // Maybe match source address. if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) { + event_to_fill_or_check.mismatch_error = Some(format!( + "log emitter mismatch: expected={:#x}, got={:#x}", + event_to_fill_or_check.address.unwrap(), + log.address + )); return false; } let expected_count = event_to_fill_or_check.count; - match event_to_fill_or_check.address { Some(emitter) => count_map .get(&emitter) @@ -888,6 +932,8 @@ pub(crate) fn handle_expect_emit( // appear. state.expected_emits.push_front((event_to_fill_or_check, count_map)); } + + failure_reason } /// Handles expected emits specified by the `expectEmit` cheatcodes. @@ -923,11 +969,11 @@ impl LogCountMap { if self.map.contains_key(log) { self.map.entry(log.clone()).and_modify(|c| *c += 1); - return true + return true; } if !self.satisfies_checks(log) { - return false + return false; } self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1); @@ -942,7 +988,7 @@ impl LogCountMap { pub fn count(&self, log: &RawLog) -> u64 { if !self.satisfies_checks(log) { - return 0 + return 0; } self.count_unchecked() @@ -953,8 +999,8 @@ impl LogCountMap { } } -fn expect_create( - state: &mut Cheatcodes, +fn expect_create( + state: &mut Cheatcodes, bytecode: Bytes, deployer: Address, create_scheme: CreateScheme, @@ -965,8 +1011,8 @@ fn expect_create( Ok(Default::default()) } -fn expect_revert( - state: &mut Cheatcodes, +fn expect_revert( + state: &mut Cheatcodes, reason: Option<&[u8]>, depth: usize, cheatcode: bool, @@ -979,7 +1025,7 @@ fn expect_revert( "you must call another function prior to expecting a second revert" ); state.expected_revert = Some(ExpectedRevert { - reason: reason.map(<[_]>::to_vec), + reason: reason.map(Bytes::copy_from_slice), depth, kind: if cheatcode { ExpectedRevertKind::Cheatcode { pending_processing: true } @@ -998,7 +1044,7 @@ fn expect_revert( fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool { if log.topics().len() != expected.topics().len() { - return false + return false; } // Check topics. @@ -1009,18 +1055,231 @@ fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> .filter(|(i, _)| checks[*i]) .all(|(i, topic)| topic == &expected.topics()[i]) { - return false + return false; } // Check data if checks[4] && expected.data.as_ref() != log.data.as_ref() { - return false + return false; } true } -fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result { +fn decode_event( + identifier: &foundry_evm_traces::identifier::SignaturesIdentifier, + log: &RawLog, +) -> Option { + let topics = log.topics(); + if topics.is_empty() { + return None; + } + let t0 = topics[0]; // event sig + // Try to identify the event + let event = foundry_common::block_on(identifier.identify_event(t0))?; + + // Check if event already has indexed information from signatures + let has_indexed_info = event.inputs.iter().any(|p| p.indexed); + // Only use get_indexed_event if the event doesn't have indexing info + let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) }; + + // Try to decode the event + if let Ok(decoded) = indexed_event.decode_log(log) { + let params = reconstruct_params(&indexed_event, &decoded); + + let decoded_params = params + .into_iter() + .zip(indexed_event.inputs.iter()) + .map(|(param, input)| (input.name.clone(), format_token(¶m))) + .collect(); + + return Some(DecodedCallLog { + name: Some(indexed_event.name), + params: Some(decoded_params), + }); + } + + None +} + +/// Restore the order of the params of a decoded event +fn reconstruct_params(event: &Event, decoded: &alloy_dyn_abi::DecodedEvent) -> Vec { + let mut indexed = 0; + let mut unindexed = 0; + let mut inputs = vec![]; + for input in &event.inputs { + if input.indexed && indexed < decoded.indexed.len() { + inputs.push(decoded.indexed[indexed].clone()); + indexed += 1; + } else if unindexed < decoded.body.len() { + inputs.push(decoded.body[unindexed].clone()); + unindexed += 1; + } + } + inputs +} + +/// Gets a detailed mismatch message for emit assertions +pub(crate) fn get_emit_mismatch_message( + checks: [bool; 5], + expected: &RawLog, + actual: &RawLog, + is_anonymous: bool, + expected_decoded: Option<&DecodedCallLog>, + actual_decoded: Option<&DecodedCallLog>, +) -> String { + // Early return for completely different events or incompatible structures + + // 1. Different number of topics + if actual.topics().len() != expected.topics().len() { + return name_mismatched_logs(expected_decoded, actual_decoded); + } + + // 2. Different event signatures (for non-anonymous events) + if !is_anonymous + && checks[0] + && (!expected.topics().is_empty() && !actual.topics().is_empty()) + && expected.topics()[0] != actual.topics()[0] + { + return name_mismatched_logs(expected_decoded, actual_decoded); + } + + let expected_data = expected.data.as_ref(); + let actual_data = actual.data.as_ref(); + + // 3. Check data + if checks[4] && expected_data != actual_data { + // Different lengths or not ABI-encoded + if expected_data.len() != actual_data.len() + || !expected_data.len().is_multiple_of(32) + || expected_data.is_empty() + { + return name_mismatched_logs(expected_decoded, actual_decoded); + } + } + + // expected and actual events are the same, so check individual parameters + let mut mismatches = Vec::new(); + + // Check topics (indexed parameters) + for (i, (expected_topic, actual_topic)) in + expected.topics().iter().zip(actual.topics().iter()).enumerate() + { + // Skip topic[0] for non-anonymous events (already checked above) + if i == 0 && !is_anonymous { + continue; + } + + // Only check if the corresponding check flag is set + if i < checks.len() && checks[i] && expected_topic != actual_topic { + let param_idx = if is_anonymous { + i // For anonymous events, topic[0] is param 0 + } else { + i - 1 // For regular events, topic[0] is event signature, so topic[1] is param 0 + }; + mismatches + .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}")); + } + } + + // Check data (non-indexed parameters) + if checks[4] && expected_data != actual_data { + let num_indexed_params = if is_anonymous { + expected.topics().len() + } else { + expected.topics().len().saturating_sub(1) + }; + + for (i, (expected_chunk, actual_chunk)) in + expected_data.chunks(32).zip(actual_data.chunks(32)).enumerate() + { + if expected_chunk != actual_chunk { + let param_idx = num_indexed_params + i; + mismatches.push(format!( + "param {}: expected={}, got={}", + param_idx, + hex::encode_prefixed(expected_chunk), + hex::encode_prefixed(actual_chunk) + )); + } + } + } + + if mismatches.is_empty() { + name_mismatched_logs(expected_decoded, actual_decoded) + } else { + // Build the error message with event names if available + let event_prefix = match (expected_decoded, actual_decoded) { + (Some(expected_dec), Some(actual_dec)) if expected_dec.name == actual_dec.name => { + format!( + "{} param mismatch", + expected_dec.name.as_ref().unwrap_or(&"log".to_string()) + ) + } + _ => { + if is_anonymous { + "anonymous log mismatch".to_string() + } else { + "log mismatch".to_string() + } + } + }; + + // Add parameter details if available from decoded events + let detailed_mismatches = if let (Some(expected_dec), Some(actual_dec)) = + (expected_decoded, actual_decoded) + && let (Some(expected_params), Some(actual_params)) = + (&expected_dec.params, &actual_dec.params) + { + mismatches + .into_iter() + .map(|basic_mismatch| { + // Try to find the parameter name and decoded value + if let Some(param_idx) = basic_mismatch + .split(' ') + .nth(1) + .and_then(|s| s.trim_end_matches(':').parse::().ok()) + && param_idx < expected_params.len() + && param_idx < actual_params.len() + { + let (expected_name, expected_value) = &expected_params[param_idx]; + let (_actual_name, actual_value) = &actual_params[param_idx]; + let param_name = if expected_name.is_empty() { + &format!("param{param_idx}") + } else { + expected_name + }; + return format!( + "{param_name}: expected={expected_value}, got={actual_value}", + ); + } + basic_mismatch + }) + .collect::>() + } else { + mismatches + }; + + format!("{} at {}", event_prefix, detailed_mismatches.join(", ")) + } +} + +/// Formats the generic mismatch message: "log != expected log" to include event names if available +fn name_mismatched_logs( + expected_decoded: Option<&DecodedCallLog>, + actual_decoded: Option<&DecodedCallLog>, +) -> String { + let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log"); + let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log"); + format!("{actual_name} != expected {expected_name}") +} + +fn expect_safe_memory( + state: &mut Cheatcodes, + start: u64, + end: u64, + depth: u64, +) -> Result { ensure!(start < end, "memory range start ({start}) is greater than end ({end})"); #[expect(clippy::single_range_in_vec_init)] // Wanted behaviour let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]); diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs index 92026b9a86697..6ba333f63f402 100644 --- a/crates/cheatcodes/src/test/revert_handlers.rs +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -1,9 +1,10 @@ use crate::{Error, Result}; -use alloy_primitives::{address, hex, Address, Bytes}; +use alloy_dyn_abi::{DynSolValue, ErrorExt}; +use alloy_primitives::{Address, Bytes, address, hex}; use alloy_sol_types::{SolError, SolValue}; -use foundry_common::ContractsByArtifact; +use foundry_common::{ContractsByArtifact, abi::get_error}; use foundry_evm_core::decode::RevertDecoder; -use revm::interpreter::{return_ok, InstructionResult}; +use revm::interpreter::{InstructionResult, return_ok}; use spec::Vm; use super::{ @@ -64,14 +65,13 @@ fn handle_revert( ) -> Result<(), Error> { // If expected reverter address is set then check it matches the actual reverter. if let (Some(expected_reverter), Some(&actual_reverter)) = (revert_params.reverter(), reverter) + && expected_reverter != actual_reverter { - if expected_reverter != actual_reverter { - return Err(fmt_err!( - "Reverter != expected reverter: {} != {}", - actual_reverter, - expected_reverter - )); - } + return Err(fmt_err!( + "Reverter != expected reverter: {} != {}", + actual_reverter, + expected_reverter + )); } let expected_reason = revert_params.reason(); @@ -94,27 +94,38 @@ fn handle_revert( // Try decoding as known errors. actual_revert = decode_revert(actual_revert); - if actual_revert == expected_reason || - (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) + if actual_revert == expected_reason + || (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) { - Ok(()) - } else { - let (actual, expected) = if let Some(contracts) = known_contracts { - let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi)); - ( - &decoder.decode(actual_revert.as_slice(), Some(status)), - &decoder.decode(expected_reason, Some(status)), - ) - } else { - (&stringify(&actual_revert), &stringify(expected_reason)) - }; + return Ok(()); + } - if expected == actual { - return Ok(()); - } + // If expected reason is `Error(string)` then decode and compare with actual revert. + // See + if expected_reason.len() >= 4 + && let Ok(e) = get_error("Error(string)") + && let Ok(dec) = e.decode_error(expected_reason) + && let Some(DynSolValue::String(revert_str)) = dec.body.first() + && revert_str.as_str() == String::from_utf8_lossy(&actual_revert) + { + return Ok(()); + } - Err(fmt_err!("Error != expected error: {} != {}", actual, expected)) + let (actual, expected) = if let Some(contracts) = known_contracts { + let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi)); + ( + &decoder.decode(actual_revert.as_slice(), Some(status)), + &decoder.decode(expected_reason, Some(status)), + ) + } else { + (&stringify(&actual_revert), &stringify(expected_reason)) + }; + + if expected == actual { + return Ok(()); } + + Err(fmt_err!("Error != expected error: {} != {}", actual, expected)) } pub(crate) fn handle_assume_no_revert( @@ -172,6 +183,7 @@ pub(crate) fn handle_expect_revert( } if expected_revert.count == 0 { + // If no specific reason or reverter is expected, we just check if it reverted if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { ensure!( matches!(status, return_ok!()), @@ -184,41 +196,79 @@ pub(crate) fn handle_expect_revert( let mut reason_match = expected_revert.reason.as_ref().map(|_| false); let mut reverter_match = expected_revert.reverter.as_ref().map(|_| false); - // Reverter check - if let (Some(expected_reverter), Some(actual_reverter)) = - (expected_revert.reverter, expected_revert.reverted_by) - { - if expected_reverter == actual_reverter { + // If we expect no reverts with a specific reason/reverter, but got a revert, + // we need to check if it matches our criteria + if matches!(status, return_ok!()) { + // No revert occurred, which is what we expected + Ok(success_return()) + } else { + // We got a revert, but we expected 0 reverts + // We need to check if this revert matches our expected criteria + + // Reverter check + if let (Some(expected_reverter), Some(actual_reverter)) = + (expected_revert.reverter, expected_revert.reverted_by) + && expected_reverter == actual_reverter + { reverter_match = Some(true); } - } - // Reason check - let expected_reason = expected_revert.reason.as_deref(); - if let Some(expected_reason) = expected_reason { - let mut actual_revert: Vec = retdata.into(); - actual_revert = decode_revert(actual_revert); + // Reason check + let expected_reason = expected_revert.reason(); + if let Some(expected_reason) = expected_reason { + let mut actual_revert: Vec = retdata.to_vec(); + actual_revert = decode_revert(actual_revert); - if actual_revert == expected_reason { - reason_match = Some(true); + if actual_revert == expected_reason { + reason_match = Some(true); + } + } + + match (reason_match, reverter_match) { + (Some(true), Some(true)) => Err(fmt_err!( + "expected 0 reverts with reason: {}, from address: {}, but got one", + stringify(expected_reason.unwrap_or_default()), + expected_revert.reverter.unwrap() + )), + (Some(true), None) => Err(fmt_err!( + "expected 0 reverts with reason: {}, but got one", + stringify(expected_reason.unwrap_or_default()) + )), + (None, Some(true)) => Err(fmt_err!( + "expected 0 reverts from address: {}, but got one", + expected_revert.reverter.unwrap() + )), + _ => { + // The revert doesn't match our criteria, which means it's a different revert + // For expectRevert with count=0, any revert should fail the test + let decoded_revert = decode_revert(retdata.to_vec()); + + // Provide more specific error messages based on what was expected + if let Some(reverter) = expected_revert.reverter { + if expected_revert.reason.is_some() { + Err(fmt_err!( + "call reverted with '{}' from {}, but expected 0 reverts with reason '{}' from {}", + stringify(&decoded_revert), + expected_revert.reverted_by.unwrap_or_default(), + stringify(expected_reason.unwrap_or_default()), + reverter + )) + } else { + Err(fmt_err!( + "call reverted with '{}' from {}, but expected 0 reverts from {}", + stringify(&decoded_revert), + expected_revert.reverted_by.unwrap_or_default(), + reverter + )) + } + } else { + Err(fmt_err!( + "call reverted with '{}' when it was expected not to revert", + stringify(&decoded_revert) + )) + } + } } - }; - - match (reason_match, reverter_match) { - (Some(true), Some(true)) => Err(fmt_err!( - "expected 0 reverts with reason: {}, from address: {}, but got one", - &stringify(expected_reason.unwrap_or_default()), - expected_revert.reverter.unwrap() - )), - (Some(true), None) => Err(fmt_err!( - "expected 0 reverts with reason: {}, but got one", - &stringify(expected_reason.unwrap_or_default()) - )), - (None, Some(true)) => Err(fmt_err!( - "expected 0 reverts from address: {}, but got one", - expected_revert.reverter.unwrap() - )), - _ => Ok(success_return()), } } else { ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); @@ -239,10 +289,9 @@ fn decode_revert(revert: Vec) -> Vec { if matches!( revert.get(..4).map(|s| s.try_into().unwrap()), Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) - ) { - if let Ok(decoded) = Vec::::abi_decode(&revert[4..]) { - return decoded; - } + ) && let Ok(decoded) = Vec::::abi_decode(&revert[4..]) + { + return decoded; } revert } diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs index b55ef2d16eedf..ced2fa98c60b2 100644 --- a/crates/cheatcodes/src/toml.rs +++ b/crates/cheatcodes/src/toml.rs @@ -1,170 +1,198 @@ //! Implementations of [`Toml`](spec::Group::Toml) cheatcodes. use crate::{ - json::{ - canonicalize_json_path, check_json_key_exists, parse_json, parse_json_coerce, - parse_json_keys, resolve_type, - }, Cheatcode, Cheatcodes, Result, Vm::*, + json::{ + check_json_key_exists, parse_json, parse_json_coerce, parse_json_keys, resolve_type, + upsert_json_value, + }, }; use alloy_dyn_abi::DynSolType; use alloy_sol_types::SolValue; -use foundry_common::fs; +use foundry_common::{fmt::StructDefinitions, fs}; use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::evm::FoundryEvmNetwork; use serde_json::Value as JsonValue; use toml::Value as TomlValue; impl Cheatcode for keyExistsTomlCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; check_json_key_exists(&toml_to_json_string(toml)?, key) } } impl Cheatcode for parseToml_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml } = self; - parse_toml(toml, "$") + parse_toml( + toml, + "$", + state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), + ) } } impl Cheatcode for parseToml_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml(toml, key) + parse_toml( + toml, + key, + state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), + ) } } impl Cheatcode for parseTomlUintCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Uint(256)) } } impl Cheatcode for parseTomlUintArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } impl Cheatcode for parseTomlIntCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Int(256)) } } impl Cheatcode for parseTomlIntArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } impl Cheatcode for parseTomlBoolCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bool) } } impl Cheatcode for parseTomlBoolArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } impl Cheatcode for parseTomlAddressCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Address) } } impl Cheatcode for parseTomlAddressArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } impl Cheatcode for parseTomlStringCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::String) } } impl Cheatcode for parseTomlStringArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String))) } } impl Cheatcode for parseTomlBytesCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bytes) } } impl Cheatcode for parseTomlBytesArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } impl Cheatcode for parseTomlBytes32Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseTomlBytes32ArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } impl Cheatcode for parseTomlType_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, typeDescription } = self; - parse_toml_coerce(toml, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + parse_toml_coerce( + toml, + "$", + &resolve_type( + typeDescription, + state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), + )?, + ) + .map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlType_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; - parse_toml_coerce(toml, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + parse_toml_coerce( + toml, + key, + &resolve_type( + typeDescription, + state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), + )?, + ) + .map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlTypeArrayCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; - let ty = resolve_type(typeDescription)?; + let ty = resolve_type( + typeDescription, + state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), + )?; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlKeysCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_keys(toml, key) } } impl Cheatcode for writeToml_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let value = serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); @@ -175,21 +203,22 @@ impl Cheatcode for writeToml_0Call { } impl Cheatcode for writeToml_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { - let Self { json, path, valueKey } = self; - let json = - serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json: value, path, valueKey } = self; + // Read and parse the TOML file. + // If the file doesn't exist, start with an empty object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let toml_data = fs::read_to_string(data_path)?; - let json_data: JsonValue = - toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?; - let value = - jsonpath_lib::replace_with(json_data, &canonicalize_json_path(valueKey), &mut |_| { - Some(json.clone()) - })?; - - let toml_string = format_json_to_toml(value)?; + let mut json_data: JsonValue = if data_path.exists() { + let toml_data = fs::locked_read_to_string(&data_path)?; + toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))? + } else { + JsonValue::Object(Default::default()) + }; + upsert_json_value(&mut json_data, value, valueKey)?; + + // Serialize back to TOML and write the updated content back to the file + let toml_string = format_json_to_toml(json_data)?; super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) } } @@ -200,8 +229,8 @@ fn parse_toml_str(toml: &str) -> Result { } /// Parse a TOML string and return the value at the given path. -fn parse_toml(toml: &str, key: &str) -> Result { - parse_json(&toml_to_json_string(toml)?, key) +fn parse_toml(toml: &str, key: &str, struct_defs: Option<&StructDefinitions>) -> Result { + parse_json(&toml_to_json_string(toml)?, key, struct_defs) } /// Parse a TOML string and return the value at the given path, coercing it to the given type. @@ -228,14 +257,17 @@ fn format_json_to_toml(json: JsonValue) -> Result { } /// Convert a TOML value to a JSON value. -fn toml_to_json_value(toml: TomlValue) -> JsonValue { +pub(super) fn toml_to_json_value(toml: TomlValue) -> JsonValue { match toml { TomlValue::String(s) => match s.as_str() { "null" => JsonValue::Null, _ => JsonValue::String(s), }, TomlValue::Integer(i) => JsonValue::Number(i.into()), - TomlValue::Float(f) => JsonValue::Number(serde_json::Number::from_f64(f).unwrap()), + TomlValue::Float(f) => match serde_json::Number::from_f64(f) { + Some(n) => JsonValue::Number(n), + None => JsonValue::String(f.to_string()), + }, TomlValue::Boolean(b) => JsonValue::Bool(b), TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()), TomlValue::Table(t) => { diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 4bdc239036d53..fcaf6e9c6c3bb 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -1,14 +1,22 @@ //! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; -use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, TypedData, eip712_parser::EncodeType}; use alloy_ens::namehash; -use alloy_primitives::{aliases::B32, map::HashMap, B64, U256}; +use alloy_primitives::{B64, Bytes, I256, U256, aliases::B32, keccak256, map::HashMap}; +use alloy_rlp::{Decodable, Encodable}; use alloy_sol_types::SolValue; -use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; +use foundry_common::{TYPE_BINDING_PREFIX, fs}; +use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::{constants::DEFAULT_CREATE2_DEPLOYER, evm::FoundryEvmNetwork}; +use foundry_evm_fuzz::strategies::BoundMutator; use proptest::prelude::Strategy; -use rand::{seq::SliceRandom, Rng, RngCore}; -use revm::context::JournalTr; +use rand::{Rng, RngCore, seq::SliceRandom}; +use revm::{ + context::{ContextTr, JournalTr}, + inspector::JournalExt, +}; +use std::path::PathBuf; /// Contains locations of traces ignored via cheatcodes. /// @@ -25,7 +33,7 @@ pub struct IgnoredTraces { } impl Cheatcode for labelCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account, newLabel } = self; state.labels.insert(*account, newLabel.clone()); Ok(Default::default()) @@ -33,7 +41,7 @@ impl Cheatcode for labelCall { } impl Cheatcode for getLabelCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account } = self; Ok(match state.labels.get(account) { Some(label) => label.abi_encode(), @@ -43,56 +51,76 @@ impl Cheatcode for getLabelCall { } impl Cheatcode for computeCreateAddressCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { nonce, deployer } = self; - ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64 - 1"); + ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64"); Ok(deployer.create(nonce.to()).abi_encode()) } } impl Cheatcode for computeCreate2Address_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash, deployer } = self; Ok(deployer.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for computeCreate2Address_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash } = self; Ok(DEFAULT_CREATE2_DEPLOYER.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for ensNamehashCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(namehash(name).abi_encode()) } } +impl Cheatcode for bound_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { current, min, max } = *self; + let Some(mutated) = U256::bound(current, min, max, state.test_runner()) else { + bail!("cannot bound {current} in [{min}, {max}] range") + }; + Ok(mutated.abi_encode()) + } +} + +impl Cheatcode for bound_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { current, min, max } = *self; + let Some(mutated) = I256::bound(current, min, max, state.test_runner()) else { + bail!("cannot bound {current} in [{min}, {max}] range") + }; + Ok(mutated.abi_encode()) + } +} + impl Cheatcode for randomUint_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { random_uint(state, None, None) } } impl Cheatcode for randomUint_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { min, max } = *self; random_uint(state, None, Some((min, max))) } } impl Cheatcode for randomUint_2Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_uint(state, Some(bits), None) } } impl Cheatcode for randomAddressCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { Ok(DynSolValue::type_strategy(&DynSolType::Address) .new_tree(state.test_runner()) .unwrap() @@ -102,27 +130,27 @@ impl Cheatcode for randomAddressCall { } impl Cheatcode for randomInt_0Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { random_int(state, None) } } impl Cheatcode for randomInt_1Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_int(state, Some(bits)) } } impl Cheatcode for randomBoolCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_bool: bool = state.rng().random(); Ok(rand_bool.abi_encode()) } } impl Cheatcode for randomBytesCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { len } = *self; ensure!( len <= U256::from(usize::MAX), @@ -135,33 +163,33 @@ impl Cheatcode for randomBytesCall { } impl Cheatcode for randomBytes4Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u32 = state.rng().next_u32(); Ok(B32::from(rand_u32).abi_encode()) } } impl Cheatcode for randomBytes8Call { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u64 = state.rng().next_u64(); Ok(B64::from(rand_u64).abi_encode()) } } impl Cheatcode for pauseTracingCall { - fn apply_full( + fn apply_full( &self, - ccx: &mut crate::CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { - let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { + let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to pause - return Ok(Default::default()) + return Ok(Default::default()); }; // If paused earlier, ignore the call if ccx.state.ignored_traces.last_pause_call.is_some() { - return Ok(Default::default()) + return Ok(Default::default()); } let cur_node = &tracer.traces().nodes().last().expect("no trace nodes"); @@ -172,19 +200,19 @@ impl Cheatcode for pauseTracingCall { } impl Cheatcode for resumeTracingCall { - fn apply_full( + fn apply_full( &self, - ccx: &mut crate::CheatsCtxt, - executor: &mut dyn CheatcodesExecutor, + ccx: &mut CheatsCtxt<'_, '_, FEN>, + executor: &mut dyn CheatcodesExecutor, ) -> Result { - let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { + let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to unpause - return Ok(Default::default()) + return Ok(Default::default()); }; let Some(start) = ccx.state.ignored_traces.last_pause_call.take() else { // Nothing to unpause - return Ok(Default::default()) + return Ok(Default::default()); }; let node = &tracer.traces().nodes().last().expect("no trace nodes"); @@ -195,19 +223,18 @@ impl Cheatcode for resumeTracingCall { } impl Cheatcode for interceptInitcodeCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; - if !state.intercept_next_create_call { - state.intercept_next_create_call = true; - } else { + if state.intercept_next_create_call { bail!("vm.interceptInitcode() has already been called") } + state.intercept_next_create_call = true; Ok(Default::default()) } } impl Cheatcode for setArbitraryStorage_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, false); @@ -216,7 +243,7 @@ impl Cheatcode for setArbitraryStorage_0Call { } impl Cheatcode for setArbitraryStorage_1Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { target, overwrite } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, *overwrite); @@ -225,7 +252,7 @@ impl Cheatcode for setArbitraryStorage_1Call { } impl Cheatcode for copyStorageCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { let Self { from, to } = self; ensure!( @@ -233,11 +260,12 @@ impl Cheatcode for copyStorageCall { "target address cannot have arbitrary storage" ); - if let Ok(from_account) = ccx.ecx.journaled_state.load_account(*from) { + if let Ok(from_account) = ccx.ecx.journal_mut().load_account(*from) { let from_storage = from_account.storage.clone(); - if let Ok(mut to_account) = ccx.ecx.journaled_state.load_account(*to) { - to_account.storage = from_storage; - if let Some(ref mut arbitrary_storage) = &mut ccx.state.arbitrary_storage { + if ccx.ecx.journal_mut().load_account(*to).is_ok() { + // SAFETY: We ensured the account was already loaded. + ccx.ecx.journal_mut().evm_state_mut().get_mut(to).unwrap().storage = from_storage; + if let Some(arbitrary_storage) = &mut ccx.state.arbitrary_storage { arbitrary_storage.mark_copy(from, to); } } @@ -248,7 +276,7 @@ impl Cheatcode for copyStorageCall { } impl Cheatcode for sortCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut sorted_values = array.clone(); @@ -259,7 +287,7 @@ impl Cheatcode for sortCall { } impl Cheatcode for shuffleCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { + fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut shuffled_values = array.clone(); @@ -270,9 +298,21 @@ impl Cheatcode for shuffleCall { } } +impl Cheatcode for setSeedCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result { + let Self { seed } = self; + ccx.state.set_seed(*seed); + Ok(Default::default()) + } +} + /// Helper to generate a random `uint` value (with given bits or bounded if specified) /// from type strategy. -fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, U256)>) -> Result { +fn random_uint( + state: &mut Cheatcodes, + bits: Option, + bounds: Option<(U256, U256)>, +) -> Result { if let Some(bits) = bits { // Generate random with specified bits. ensure!(bits <= U256::from(256), "number of bits cannot exceed 256"); @@ -280,7 +320,7 @@ fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, .new_tree(state.test_runner()) .unwrap() .current() - .abi_encode()) + .abi_encode()); } if let Some((min, max)) = bounds { @@ -293,7 +333,7 @@ fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, random_number %= inclusive_modulo; } random_number += min; - return Ok(random_number.abi_encode()) + return Ok(random_number.abi_encode()); } // Generate random `uint256` value. @@ -305,7 +345,7 @@ fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, } /// Helper to generate a random `int` value (with given bits if specified) from type strategy. -fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { +fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { let no_bits = bits.unwrap_or(U256::from(256)); ensure!(no_bits <= U256::from(256), "number of bits cannot exceed 256"); Ok(DynSolValue::type_strategy(&DynSolType::Int(no_bits.to::())) @@ -314,3 +354,170 @@ fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { .current() .abi_encode()) } + +impl Cheatcode for eip712HashType_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { typeNameOrDefinition } = self; + + let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; + + Ok(keccak256(type_def.as_bytes()).to_vec()) + } +} + +impl Cheatcode for eip712HashType_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bindingsPath, typeName } = self; + + let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; + let type_def = get_type_def_from_bindings(typeName, path, &state.config.root)?; + + Ok(keccak256(type_def.as_bytes()).to_vec()) + } +} + +impl Cheatcode for eip712HashStruct_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { typeNameOrDefinition, abiEncodedData } = self; + + let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; + let primary = &type_def[..type_def.find('(').unwrap_or(type_def.len())]; + + get_struct_hash(primary, &type_def, abiEncodedData) + } +} + +impl Cheatcode for eip712HashStruct_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bindingsPath, typeName, abiEncodedData } = self; + + let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; + let type_def = get_type_def_from_bindings(typeName, path, &state.config.root)?; + + get_struct_hash(typeName, &type_def, abiEncodedData) + } +} + +impl Cheatcode for eip712HashTypedDataCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { jsonData } = self; + let typed_data: TypedData = serde_json::from_str(jsonData)?; + let digest = typed_data.eip712_signing_hash()?; + + Ok(digest.to_vec()) + } +} + +/// Returns EIP-712 canonical type definition from the provided string type representation or type +/// name. If type name provided, then it looks up bindings from file generated by `forge bind-json`. +fn get_canonical_type_def( + name_or_def: &String, + state: &mut Cheatcodes, + path: Option, +) -> Result { + let type_def = if name_or_def.contains('(') { + // If the input contains '(', it must be the type definition. + EncodeType::parse(name_or_def).and_then(|parsed| parsed.canonicalize())? + } else { + // Otherwise, it must be the type name. + let path = path.as_ref().unwrap_or(&state.config.bind_json_path); + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + get_type_def_from_bindings(name_or_def, path, &state.config.root)? + }; + + Ok(type_def) +} + +/// Returns the EIP-712 type definition from the bindings in the provided path. +/// Assumes that read validation for the path has already been checked. +fn get_type_def_from_bindings(name: &String, path: PathBuf, root: &PathBuf) -> Result { + let content = fs::read_to_string(&path)?; + + let type_defs: HashMap<&str, &str> = content + .lines() + .filter_map(|line| { + let relevant = line.trim().strip_prefix(TYPE_BINDING_PREFIX)?; + let (name, def) = relevant.split_once('=')?; + Some((name.trim(), def.trim().strip_prefix('"')?.strip_suffix("\";")?)) + }) + .collect(); + + match type_defs.get(name.as_str()) { + Some(value) => Ok(value.to_string()), + None => { + let bindings = + type_defs.keys().map(|k| format!(" - {k}")).collect::>().join("\n"); + + bail!( + "'{}' not found in '{}'.{}", + name, + path.strip_prefix(root).unwrap_or(&path).to_string_lossy(), + if bindings.is_empty() { + String::new() + } else { + format!("\nAvailable bindings:\n{bindings}\n") + } + ); + } + } +} + +/// Returns the EIP-712 struct hash for provided name, definition and ABI encoded data. +fn get_struct_hash(primary: &str, type_def: &String, abi_encoded_data: &Bytes) -> Result { + let mut resolver = Resolver::default(); + + // Populate the resolver by ingesting the canonical type definition, and then get the + // corresponding `DynSolType` of the primary type. + resolver + .ingest_string(type_def) + .map_err(|e| fmt_err!("Resolver failed to ingest type definition: {e}"))?; + + let resolved_sol_type = resolver + .resolve(primary) + .map_err(|e| fmt_err!("Failed to resolve EIP-712 primary type '{primary}': {e}"))?; + + // ABI-decode the bytes into `DynSolValue::CustomStruct`. + let sol_value = resolved_sol_type.abi_decode(abi_encoded_data.as_ref()).map_err(|e| { + fmt_err!("Failed to ABI decode using resolved_sol_type directly for '{primary}': {e}.") + })?; + + // Use the resolver to properly encode the data. + let encoded_data: Vec = resolver + .encode_data(&sol_value) + .map_err(|e| fmt_err!("Failed to EIP-712 encode data for struct '{primary}': {e}"))? + .ok_or_else(|| fmt_err!("EIP-712 data encoding returned 'None' for struct '{primary}'"))?; + + // Compute the type hash of the primary type. + let type_hash = resolver + .type_hash(primary) + .map_err(|e| fmt_err!("Failed to compute typeHash for EIP712 type '{primary}': {e}"))?; + + // Compute the struct hash of the concatenated type hash and encoded data. + let mut bytes_to_hash = Vec::with_capacity(32 + encoded_data.len()); + bytes_to_hash.extend_from_slice(type_hash.as_slice()); + bytes_to_hash.extend_from_slice(&encoded_data); + + Ok(keccak256(&bytes_to_hash).to_vec()) +} + +impl Cheatcode for toRlpCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + + let mut buf = Vec::new(); + data.encode(&mut buf); + + Ok(Bytes::from(buf).abi_encode()) + } +} + +impl Cheatcode for fromRlpCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { rlp } = self; + + let decoded: Vec = Vec::::decode(&mut rlp.as_ref()) + .map_err(|e| fmt_err!("Failed to decode RLP: {e}"))?; + + Ok(decoded.abi_encode()) + } +} diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs index d88fa09df0f76..2b8f81518a621 100644 --- a/crates/cheatcodes/src/version.rs +++ b/crates/cheatcodes/src/version.rs @@ -1,25 +1,33 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; +use foundry_evm_core::evm::FoundryEvmNetwork; use semver::Version; use std::cmp::Ordering; impl Cheatcode for foundryVersionCmpCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| (cmp as i8).abi_encode()) } } impl Cheatcode for foundryVersionAtLeastCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { + fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| cmp.is_ge().abi_encode()) } } fn foundry_version_cmp(version: &str) -> Result { - version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) + version_cmp(strip_semver_metadata(SEMVER_VERSION), version) +} + +/// Strips pre-release (e.g. `-nightly`, `-dev`) and build metadata +/// (e.g. `+..`) from a version string +/// so we compare on `MAJOR.MINOR.PATCH` only. +fn strip_semver_metadata(version: &str) -> &str { + version.split(['-', '+']).next().unwrap() } fn version_cmp(version_a: &str, version_b: &str) -> Result { @@ -32,10 +40,70 @@ fn parse_version(version: &str) -> Result { let version = Version::parse(version).map_err(|e| fmt_err!("invalid version `{version}`: {e}"))?; if !version.pre.is_empty() { - return Err(fmt_err!("invalid version `{version}`: pre-release versions are not supported")); + return Err(fmt_err!( + "invalid version `{version}`: pre-release versions are not supported" + )); } if !version.build.is_empty() { return Err(fmt_err!("invalid version `{version}`: build metadata is not supported")); } Ok(version) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strips_build_metadata_only() { + // Tagged release: `1.7.1+..` + assert_eq!(strip_semver_metadata("1.7.1+abc1234567.1737036656.release"), "1.7.1"); + } + + #[test] + fn strips_pre_release_and_build_metadata() { + // Nightly: `1.7.1-nightly+..` + assert_eq!(strip_semver_metadata("1.7.1-nightly+abc1234567.1737036656.release"), "1.7.1"); + // Dev: `1.7.1-dev+..` + assert_eq!(strip_semver_metadata("1.7.1-dev+abc1234567.1737036656.debug"), "1.7.1"); + } + + #[test] + fn strips_plain_version() { + assert_eq!(strip_semver_metadata("1.7.1"), "1.7.1"); + } + + #[test] + fn version_cmp_orders_correctly() { + assert_eq!(version_cmp("1.7.1", "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp("1.7.1", "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "1.7.2").unwrap(), Ordering::Less); + assert_eq!(version_cmp("1.7.1", "0.0.1").unwrap(), Ordering::Greater); + assert_eq!(version_cmp("1.7.1", "99.0.0").unwrap(), Ordering::Less); + } + + #[test] + fn parse_version_rejects_pre_release_and_build_metadata() { + // User-supplied versions must be plain `MAJOR.MINOR.PATCH`. + assert!(parse_version("1.7.1-nightly").is_err()); + assert!(parse_version("1.7.1+abc").is_err()); + assert!(parse_version("not-a-version").is_err()); + assert!(parse_version("1.7.1").is_ok()); + } + + #[test] + fn cmp_works_against_full_semver_version_strings() { + // Simulate comparing each shape of `SEMVER_VERSION` against a user-supplied version. + for current in [ + "1.7.1+abc1234567.1737036656.release", + "1.7.1-nightly+abc1234567.1737036656.release", + "1.7.1-dev+abc1234567.1737036656.debug", + "1.7.1", + ] { + let stripped = strip_semver_metadata(current); + assert_eq!(version_cmp(stripped, "1.7.1").unwrap(), Ordering::Equal); + assert_eq!(version_cmp(stripped, "1.7.0").unwrap(), Ordering::Greater); + assert_eq!(version_cmp(stripped, "1.7.2").unwrap(), Ordering::Less); + } + } +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 8e88cb928c69e..bb673c9219e10 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -1,11 +1,8 @@ [package] name = "chisel" -authors = [ - "clabby ", - "asnared ", -] description = "Fast, utilitarian, and verbose Solidity REPL" +authors.workspace = true version.workspace = true edition.workspace = true rust-version.workspace = true @@ -25,10 +22,15 @@ path = "bin/main.rs" forge-fmt.workspace = true foundry-cli.workspace = true foundry-common.workspace = true -foundry-compilers = { workspace = true, features = ["project-util", "full"] } +foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm.workspace = true + +tempfile.workspace = true + +solar.workspace = true + alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } alloy-primitives = { workspace = true, features = [ "serde", @@ -41,18 +43,13 @@ alloy-json-abi.workspace = true clap = { version = "4", features = ["derive", "env", "wrap_help"] } dirs.workspace = true eyre.workspace = true -regex.workspace = true reqwest.workspace = true -revm.workspace = true -rustyline = "15" +rustyline = "17" +itertools.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true -solar-parse.workspace = true -strum = { workspace = true, features = ["derive"] } time = { version = "0.3", features = ["formatting"] } -tokio = { workspace = true, features = ["full"] } yansi.workspace = true tracing.workspace = true walkdir.workspace = true @@ -60,9 +57,19 @@ walkdir.workspace = true [dev-dependencies] tracing-subscriber.workspace = true +# REPL tests only work on Unix. +[target.'cfg(unix)'.dev-dependencies] +foundry-test-utils.workspace = true +rexpect = "0.6" + [features] -default = ["jemalloc"] +default = ["jemalloc", "asm-keccak", "optimism"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-cli/optimism", +] diff --git a/crates/chisel/src/args.rs b/crates/chisel/src/args.rs index 3837953b5f3b2..48e0fabad9cc0 100644 --- a/crates/chisel/src/args.rs +++ b/crates/chisel/src/args.rs @@ -1,59 +1,50 @@ use crate::{ - history::chisel_history_file, - prelude::{ChiselCommand, ChiselDispatcher, DispatchResult, SolidityHelper}, + opts::{Chisel, ChiselSubcommand}, + prelude::{ChiselCommand, ChiselDispatcher, SolidityHelper}, }; use clap::Parser; use eyre::{Context, Result}; -use foundry_cli::{ - handler, - utils::{self, LoadConfig}, -}; +use foundry_cli::utils::{self, LoadConfig}; use foundry_common::fs; -use foundry_config::{ - figment::{ - value::{Dict, Map}, - Metadata, Profile, Provider, - }, - Config, -}; -use rustyline::{config::Configurer, error::ReadlineError, Editor}; -use std::path::PathBuf; -use tracing::debug; +use rustyline::{Editor, config::Configurer, error::ReadlineError}; +use std::{ops::ControlFlow, path::PathBuf}; use yansi::Paint; -use crate::opts::{Chisel, ChiselSubcommand}; - /// Run the `chisel` command line interface. pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Chisel::parse(); args.global.init()?; - - run_command(args) + args.global.tokio_runtime().block_on(run_command(args)) } /// Setup the global logger and other utilities. pub fn setup() -> Result<()> { - utils::install_crypto_provider(); - handler::install(); + utils::common_setup(); utils::subscriber(); - utils::load_dotenv(); Ok(()) } +macro_rules! try_cf { + ($e:expr) => { + match $e { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => return Ok(()), + } + }; +} + /// Run the subcommand. -#[tokio::main] pub async fn run_command(args: Chisel) -> Result<()> { - // Keeps track of whether or not an interrupt was the last input - let mut interrupt = false; - // Load configuration let (config, evm_opts) = args.load_config_and_evm_opts()?; // Create a new cli dispatcher - let mut dispatcher = ChiselDispatcher::new(crate::session_source::SessionSourceConfig { + let mut dispatcher = ChiselDispatcher::new(crate::source::SessionSourceConfig { // Enable traces if any level of verbosity was passed traces: config.verbosity > 0, foundry_config: config, @@ -61,152 +52,68 @@ pub async fn run_command(args: Chisel) -> Result<()> { evm_opts, backend: None, calldata: None, + ir_minimum: args.ir_minimum, })?; // Execute prelude Solidity source files evaluate_prelude(&mut dispatcher, args.prelude).await?; - // Check for chisel subcommands - match &args.cmd { - Some(ChiselSubcommand::List) => { - let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await; - match sessions { - DispatchResult::CommandSuccess(Some(session_list)) => { - sh_println!("{session_list}")?; - } - DispatchResult::CommandFailed(e) => sh_err!("{e}")?, - _ => panic!("Unexpected result: Please report this bug."), - } - return Ok(()) - } - Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => { - // For both of these subcommands, we need to attempt to load the session from cache - match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await { - DispatchResult::CommandSuccess(_) => { /* Continue */ } - DispatchResult::CommandFailed(e) => { - sh_err!("{e}")?; - return Ok(()) - } - _ => panic!("Unexpected result! Please report this bug."), - } - - // If the subcommand was `view`, print the source and exit. - if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) { - match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await { - DispatchResult::CommandSuccess(Some(source)) => { - sh_println!("{source}")?; - } - _ => panic!("Unexpected result! Please report this bug."), - } - return Ok(()) - } - } - Some(ChiselSubcommand::ClearCache) => { - match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await { - DispatchResult::CommandSuccess(Some(msg)) => sh_println!("{}", msg.green())?, - DispatchResult::CommandFailed(e) => sh_err!("{e}")?, - _ => panic!("Unexpected result! Please report this bug."), - } - return Ok(()) - } - Some(ChiselSubcommand::Eval { command }) => { - dispatch_repl_line(&mut dispatcher, command).await?; - return Ok(()) - } - None => { /* No chisel subcommand present; Continue */ } + if let Some(cmd) = args.cmd { + try_cf!(handle_cli_command(&mut dispatcher, cmd).await?); + return Ok(()); } - // Create a new rustyline Editor let mut rl = Editor::::new()?; - rl.set_helper(Some(SolidityHelper::default())); - - // automatically add lines to history + rl.set_helper(Some(dispatcher.helper.clone())); rl.set_auto_add_history(true); - - // load history - if let Some(chisel_history) = chisel_history_file() { - let _ = rl.load_history(&chisel_history); + if let Some(path) = chisel_history_file() { + let _ = rl.load_history(&path); } - // Print welcome header sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?; - // Begin Rustyline loop + // REPL loop. + let mut interrupt = false; loop { - // Get the prompt from the dispatcher - // Variable based on status of the last entry - let prompt = dispatcher.get_prompt(); - - // Read the next line - let next_string = rl.readline(prompt.as_ref()); - - // Try to read the string - match next_string { + match rl.readline(&dispatcher.get_prompt()) { Ok(line) => { debug!("dispatching next line: {line}"); - // Clear interrupt flag + // Clear interrupt flag. interrupt = false; - // Dispatch and match results - let errored = dispatch_repl_line(&mut dispatcher, &line).await?; - rl.helper_mut().unwrap().set_errored(errored); + // Dispatch and match results. + let r = dispatcher.dispatch(&line).await; + dispatcher.helper.set_errored(r.is_err()); + match r { + Ok(ControlFlow::Continue(())) => {} + Ok(ControlFlow::Break(())) => break, + Err(e) => { + sh_err!("{}", foundry_common::errors::display_chain(&e))?; + } + } } Err(ReadlineError::Interrupted) => { if interrupt { - break - } else { - sh_println!("(To exit, press Ctrl+C again)")?; - interrupt = true; + break; } + sh_println!("(To exit, press Ctrl+C again)")?; + interrupt = true; } Err(ReadlineError::Eof) => break, Err(err) => { - sh_err!("{err:?}")?; - break + sh_err!("{err}")?; + break; } } } - if let Some(chisel_history) = chisel_history_file() { - let _ = rl.save_history(&chisel_history); + if let Some(path) = chisel_history_file() { + let _ = rl.save_history(&path); } Ok(()) } -/// [Provider] impl -impl Provider for Chisel { - fn metadata(&self) -> Metadata { - Metadata::named("Script Args Provider") - } - - fn data(&self) -> Result, foundry_config::figment::Error> { - Ok(Map::from([(Config::selected_profile(), Dict::default())])) - } -} - -/// Evaluate a single Solidity line. -async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> Result { - let r = dispatcher.dispatch(line).await; - match &r { - DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => { - debug!(%line, ?msg, "dispatch success"); - if let Some(msg) = msg { - sh_println!("{}", msg.green())?; - } - }, - DispatchResult::UnrecognizedCommand(e) => sh_err!("{e}")?, - DispatchResult::SolangParserFailed(e) => { - sh_err!("{}", "Compilation error".red())?; - sh_eprintln!("{}", format!("{e:?}").red())?; - } - DispatchResult::FileIoError(e) => sh_err!("{}", format!("File IO - {e}").red())?, - DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => sh_err!("{}", msg.red())?, - DispatchResult::Failure(None) => sh_err!("Please report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose")?, - } - Ok(r.is_error()) -} - /// Evaluate multiple Solidity source files contained within a /// Chisel prelude directory. async fn evaluate_prelude( @@ -216,7 +123,7 @@ async fn evaluate_prelude( let Some(prelude_dir) = maybe_prelude else { return Ok(()) }; if prelude_dir.is_file() { sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?; - load_prelude_file(dispatcher, prelude_dir).await?; + try_cf!(load_prelude_file(dispatcher, prelude_dir).await?); sh_println!("{}\n", "Prelude source file loaded successfully!".green())?; } else { let prelude_sources = fs::files_with_ext(&prelude_dir, "sol"); @@ -224,7 +131,7 @@ async fn evaluate_prelude( for source_file in prelude_sources { print_success_msg = true; sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?; - load_prelude_file(dispatcher, source_file).await?; + try_cf!(load_prelude_file(dispatcher, source_file).await?); } if print_success_msg { @@ -235,11 +142,36 @@ async fn evaluate_prelude( } /// Loads a single Solidity file into the prelude. -async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> Result<()> { +async fn load_prelude_file( + dispatcher: &mut ChiselDispatcher, + file: PathBuf, +) -> Result> { let prelude = fs::read_to_string(file) .wrap_err("Could not load source file. Are you sure this path is correct?")?; - dispatch_repl_line(dispatcher, &prelude).await?; - Ok(()) + dispatcher.dispatch(&prelude).await +} + +async fn handle_cli_command( + d: &mut ChiselDispatcher, + cmd: ChiselSubcommand, +) -> Result> { + match cmd { + ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await, + ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await, + ChiselSubcommand::View { id } => { + let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await? + else { + return Ok(ControlFlow::Break(())); + }; + d.dispatch_command(ChiselCommand::Source).await + } + ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await, + ChiselSubcommand::Eval { command } => d.dispatch(&command).await, + } +} + +fn chisel_history_file() -> Option { + foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history")) } #[cfg(test)] diff --git a/crates/chisel/src/cmd.rs b/crates/chisel/src/cmd.rs index c13272bc90e2b..c212a582d3a49 100644 --- a/crates/chisel/src/cmd.rs +++ b/crates/chisel/src/cmd.rs @@ -1,146 +1,187 @@ -//! ChiselCommand -//! -//! This module holds the [ChiselCommand] enum, which contains all builtin commands that -//! can be executed within the REPL. +use crate::prelude::CHISEL_CHAR; +use alloy_primitives::Address; +use clap::{CommandFactory, Parser}; +use itertools::Itertools; +use yansi::Paint; -use crate::prelude::ChiselDispatcher; -use std::{error::Error, str::FromStr}; -use strum::EnumIter; - -/// Builtin chisel command variants -#[derive(Debug, EnumIter)] +/// Chisel REPL commands. +#[derive(Debug, Parser)] +#[command(disable_help_flag = true, disable_help_subcommand = true)] pub enum ChiselCommand { - /// Print helpful information about chisel + /// Display all commands. + #[command(visible_alias = "h", next_help_heading = "General")] Help, - /// Quit the REPL + + /// Quit the REPL. + #[command(visible_alias = "q")] Quit, - /// Clear the current session source + + /// Executes a shell command. + #[command(visible_alias = "e")] + Exec { + /// Command to execute. + command: String, + /// Command arguments. + #[arg(trailing_var_arg = true)] + args: Vec, + }, + + /// Clear the current session source. + #[command(visible_alias = "c", next_help_heading = "Session")] Clear, - /// Print the generated source contract + + /// Print the generated source contract. + #[command(visible_alias = "so")] Source, - /// Save the current session to the cache - /// Takes: `` - Save, - /// Load a previous session from cache - /// Takes: `` - /// + + /// Save the current session to the cache. + #[command(visible_alias = "s")] + Save { + /// Optional session ID. + id: Option, + }, + + /// Load a previous session from cache. /// WARNING: This will overwrite the current session (though the current session will be - /// optimistically cached) - Load, - /// List all cached sessions + /// optimistically cached). + #[command(visible_alias = "l")] + Load { + /// Session ID to load. + id: String, + }, + + /// List all cached sessions. + #[command(name = "list", visible_alias = "ls")] ListSessions, - /// Clear the cache of all stored sessions + + /// Clear the cache of all stored sessions. + #[command(name = "clearcache", visible_alias = "cc")] ClearCache, - /// Fork an RPC in the current session - /// Takes - Fork, - /// Enable / disable traces for the current session + + /// Export the current REPL session source to a Script file. + #[command(visible_alias = "ex")] + Export, + + /// Fetch an interface of a verified contract on Etherscan. + #[command(visible_alias = "fe")] + Fetch { + /// Contract address. + addr: Address, + /// Interface name. + name: String, + }, + + /// Open the current session in an editor. + Edit, + + /// Fork an RPC in the current session. + #[command(visible_alias = "f", next_help_heading = "Environment")] + Fork { + /// Fork URL, environment variable, or RPC endpoints alias (empty to return to local + /// network). + url: Option, + }, + + /// Enable / disable traces for the current session. + #[command(visible_alias = "t")] Traces, - /// Set calldata (`msg.data`) for the current session (appended after function selector) - Calldata, - /// Dump the raw memory + + /// Set calldata (`msg.data`) for the current session (appended after function selector). Clears + /// it if no argument provided. + #[command(visible_alias = "cd")] + Calldata { + /// Calldata (empty to clear). + data: Option, + }, + + /// Dump the raw memory. + #[command(name = "memdump", visible_alias = "md", next_help_heading = "Debug")] MemDump, - /// Dump the raw stack + + /// Dump the raw stack. + #[command(name = "stackdump", visible_alias = "sd")] StackDump, - /// Export the current REPL session source to a Script file - Export, - /// Fetch an interface of a verified contract on Etherscan - /// Takes: ` ` - Fetch, - /// Executes a shell command - Exec, - /// Display the raw value of a variable's stack allocation. - RawStack, - /// Open the current session in an editor - Edit, + + /// Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in + /// length, this will display their memory pointer. + #[command(name = "rawstack", visible_alias = "rs")] + RawStack { + /// Variable name. + var: String, + }, } -/// Attempt to convert a string slice to a `ChiselCommand` -impl FromStr for ChiselCommand { - type Err = Box; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "help" | "h" => Ok(Self::Help), - "quit" | "q" => Ok(Self::Quit), - "clear" | "c" => Ok(Self::Clear), - "source" | "so" => Ok(Self::Source), - "save" | "s" => Ok(Self::Save), - "list" | "ls" => Ok(Self::ListSessions), - "load" | "l" => Ok(Self::Load), - "clearcache" | "cc" => Ok(Self::ClearCache), - "fork" | "f" => Ok(Self::Fork), - "traces" | "t" => Ok(Self::Traces), - "calldata" | "cd" => Ok(Self::Calldata), - "memdump" | "md" => Ok(Self::MemDump), - "stackdump" | "sd" => Ok(Self::StackDump), - "export" | "ex" => Ok(Self::Export), - "fetch" | "fe" => Ok(Self::Fetch), - "exec" | "e" => Ok(Self::Exec), - "rawstack" | "rs" => Ok(Self::RawStack), - "edit" => Ok(Self::Edit), - _ => Err(ChiselDispatcher::make_error(format!( - "Unknown command \"{s}\"! See available commands with `!help`.", - )) - .into()), - } +impl ChiselCommand { + pub fn parse(input: &str) -> eyre::Result { + let args = input.split_whitespace(); + let args = std::iter::once("chisel").chain(args); + Self::try_parse_from(args) + .map_err(|e| eyre::eyre!("{}; for more information, see `!help`", e.kind())) } -} -/// A category for [ChiselCommand]s -#[derive(Debug, EnumIter)] -pub enum CmdCategory { - /// General category - General, - /// Session category - Session, - /// Environment category - Env, - /// Debug category - Debug, + pub fn format_help() -> String { + let cmd = Self::command(); + let mut categories = Vec::new(); + let mut cat = None; + for sub in cmd.get_subcommands() { + if let Some(cat_) = sub.get_next_help_heading() + && Some(cat_) != cat + { + cat = Some(cat_); + categories.push((cat_, vec![])); + } + categories.last_mut().unwrap().1.push(sub); + } + format!( + "{}\n{}", + format!("{CHISEL_CHAR} Chisel help\n=============").cyan(), + categories + .iter() + .map(|(cat, cat_cmds)| { + format!( + "{}\n{}\n", + cat.magenta(), + cat_cmds + .iter() + .map(|&cmd| format!( + "\t{}{} - {}", + std::iter::once(cmd.get_name()) + .chain(cmd.get_visible_aliases()) + .map(|s| format!("!{}", s.green())) + .format(" | "), + { + let usage = get_usage(cmd); + if usage.is_empty() { + String::new() + } else { + format!(" {usage}") + } + } + .green(), + cmd.get_about().expect("command is missing about"), + )) + .format("\n") + ) + }) + .format("\n") + ) + } } -impl core::fmt::Display for CmdCategory { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let string = match self { - Self::General => "General", - Self::Session => "Session", - Self::Env => "Environment", - Self::Debug => "Debug", - }; - f.write_str(string) +fn get_usage(cmd: &clap::Command) -> String { + let s = cmd.clone().render_usage().to_string(); + if let Some(idx) = s.find(['[', '<']) { + return s[idx..].to_string(); } + String::new() } -/// A command descriptor type -pub type CmdDescriptor = (&'static [&'static str], &'static str, CmdCategory); - -/// Convert a `ChiselCommand` into a `CmdDescriptor` tuple -impl From for CmdDescriptor { - fn from(cmd: ChiselCommand) -> Self { - match cmd { - // General - ChiselCommand::Help => (&["help", "h"], "Display all commands", CmdCategory::General), - ChiselCommand::Quit => (&["quit", "q"], "Quit Chisel", CmdCategory::General), - ChiselCommand::Exec => (&["exec [args]", "e [args]"], "Execute a shell command and print the output", CmdCategory::General), - // Session - ChiselCommand::Clear => (&["clear", "c"], "Clear current session source", CmdCategory::Session), - ChiselCommand::Source => (&["source", "so"], "Display the source code of the current session", CmdCategory::Session), - ChiselCommand::Save => (&["save [id]", "s [id]"], "Save the current session to cache", CmdCategory::Session), - ChiselCommand::Load => (&["load ", "l "], "Load a previous session ID from cache", CmdCategory::Session), - ChiselCommand::ListSessions => (&["list", "ls"], "List all cached sessions", CmdCategory::Session), - ChiselCommand::ClearCache => (&["clearcache", "cc"], "Clear the chisel cache of all stored sessions", CmdCategory::Session), - ChiselCommand::Export => (&["export", "ex"], "Export the current session source to a script file", CmdCategory::Session), - ChiselCommand::Fetch => (&["fetch ", "fe "], "Fetch the interface of a verified contract on Etherscan", CmdCategory::Session), - // Environment - ChiselCommand::Fork => (&["fork ", "f "], "Fork an RPC for the current session. Supply 0 arguments to return to a local network", CmdCategory::Env), - ChiselCommand::Traces => (&["traces", "t"], "Enable / disable traces for the current session", CmdCategory::Env), - ChiselCommand::Calldata => (&["calldata [data]", "cd [data]"], "Set calldata (`msg.data`) for the current session (appended after function selector). Clears it if no argument provided.", CmdCategory::Env), - // Debug - ChiselCommand::MemDump => (&["memdump", "md"], "Dump the raw memory of the current state", CmdCategory::Debug), - ChiselCommand::StackDump => (&["stackdump", "sd"], "Dump the raw stack of the current state", CmdCategory::Debug), - ChiselCommand::Edit => (&["edit"], "Open the current session in an editor", CmdCategory::Session), - ChiselCommand::RawStack => (&["rawstack ", "rs "], "Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in length, this will display their memory pointer.", CmdCategory::Debug), - } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_help() { + let _ = sh_eprintln!("{}", ChiselCommand::format_help()); } } diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 5dd6c882ffed6..b4bf1c738ee85 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -4,38 +4,35 @@ //! of both builtin commands and Solidity snippets. use crate::{ - prelude::{ - ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, - SessionSourceConfig, SolidityHelper, - }, - session_source::SessionSource, + prelude::{ChiselCommand, ChiselResult, ChiselSession, SessionSourceConfig, SolidityHelper}, + source::SessionSource, }; -use alloy_json_abi::{InternalType, JsonAbi}; -use alloy_primitives::{hex, Address}; +use alloy_primitives::{Address, hex}; +use eyre::{Context, Result}; use forge_fmt::FormatterConfig; +use foundry_cli::utils::fetch_abi_from_etherscan; use foundry_config::RpcEndpointUrl; use foundry_evm::{ decode::decode_console_logs, traces::{ - decode_trace_arena, + CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, decode_trace_arena, identifier::{SignaturesIdentifier, TraceIdentifiers}, - render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + render_trace_arena, }, }; -use regex::Regex; use reqwest::Url; -use serde::{Deserialize, Serialize}; -use solang_parser::diagnostics::Diagnostic; +use solar::{ + parse::lexer::token::{RawLiteralKind, RawTokenKind}, + sema::ast::Base, +}; use std::{ borrow::Cow, - error::Error, io::Write, + ops::ControlFlow, path::{Path, PathBuf}, process::Command, - sync::LazyLock, }; -use strum::IntoEnumIterator; -use tracing::debug; +use tempfile::Builder; use yansi::Paint; /// Prompt arrow character. @@ -49,97 +46,24 @@ pub const COMMAND_LEADER: char = '!'; /// Chisel character pub const CHISEL_CHAR: &str = "⚒️"; -/// Matches Solidity comments -static COMMENT_RE: LazyLock = - LazyLock::new(|| Regex::new(r"^\s*(?://.*\s*$)|(/*[\s\S]*?\*/\s*$)").unwrap()); - -/// Matches Ethereum addresses that are not strings -static ADDRESS_RE: LazyLock = LazyLock::new(|| { - Regex::new(r#"(?m)(([^"']\s*)|^)(?P
0x[a-fA-F0-9]{40})((\s*[^"'\w])|$)"#).unwrap() -}); - /// Chisel input dispatcher #[derive(Debug)] pub struct ChiselDispatcher { - /// A Chisel Session pub session: ChiselSession, -} - -/// Chisel dispatch result variants -#[derive(Debug)] -pub enum DispatchResult { - /// A Generic Dispatch Success - Success(Option), - /// A Generic Failure - Failure(Option), - /// A successful ChiselCommand Execution - CommandSuccess(Option), - /// A failure to parse a Chisel Command - UnrecognizedCommand(Box), - /// The solang parser failed - SolangParserFailed(Vec), - /// A Command Failed with error message - CommandFailed(String), - /// File IO Error - FileIoError(Box), -} - -impl DispatchResult { - /// Returns `true` if the result is an error. - pub fn is_error(&self) -> bool { - matches!( - self, - Self::Failure(_) | - Self::CommandFailed(_) | - Self::UnrecognizedCommand(_) | - Self::SolangParserFailed(_) | - Self::FileIoError(_) - ) - } -} - -/// A response from the Etherscan API's `getabi` action -#[derive(Debug, Serialize, Deserialize)] -pub struct EtherscanABIResponse { - /// The status of the response - /// "1" = success | "0" = failure - pub status: String, - /// The message supplied by the API - pub message: String, - /// The result returned by the API. Will be `None` if the request failed. - pub result: Option, -} - -/// Used to format ABI parameters into valid solidity function / error / event param syntax -/// TODO: Smarter resolution of storage location, defaults to "memory" for all types -/// that cannot be stored on the stack. -macro_rules! format_param { - ($param:expr) => {{ - let param = $param; - format!("{}{}", param.ty, if param.is_complex_type() { " memory" } else { "" }) - }}; + pub helper: SolidityHelper, } /// Helper function that formats solidity source with the given [FormatterConfig] pub fn format_source(source: &str, config: FormatterConfig) -> eyre::Result { - match forge_fmt::parse(source) { - Ok(parsed) => { - let mut formatted_source = String::default(); - - if forge_fmt::format_to(&mut formatted_source, parsed, config).is_err() { - eyre::bail!("Could not format source!"); - } - - Ok(formatted_source) - } - Err(_) => eyre::bail!("Formatter could not parse source!"), - } + let formatted = forge_fmt::format(source, config).into_result()?; + Ok(formatted) } impl ChiselDispatcher { /// Associated public function to create a new Dispatcher instance pub fn new(config: SessionSourceConfig) -> eyre::Result { - ChiselSession::new(config).map(|session| Self { session }) + let session = ChiselSession::new(config)?; + Ok(Self { session, helper: Default::default() }) } /// Returns the optional ID of the current session. @@ -148,13 +72,13 @@ impl ChiselDispatcher { } /// Returns the [`SessionSource`]. - pub fn source(&self) -> &SessionSource { - &self.session.session_source + pub const fn source(&self) -> &SessionSource { + &self.session.source } /// Returns the [`SessionSource`]. - pub fn source_mut(&mut self) -> &mut SessionSource { - &mut self.session.session_source + pub const fn source_mut(&mut self) -> &mut SessionSource { + &mut self.session.source } fn format_source(&self) -> eyre::Result { @@ -181,835 +105,449 @@ impl ChiselDispatcher { } } - /// Dispatches a [ChiselCommand] - /// - /// ### Takes - /// - /// - A [ChiselCommand] - /// - An array of arguments - /// - /// ### Returns - /// - /// A [DispatchResult] containing feedback on the dispatch's execution. - pub async fn dispatch_command(&mut self, cmd: ChiselCommand, args: &[&str]) -> DispatchResult { - match cmd { - ChiselCommand::Help => { - let all_descriptors = - ChiselCommand::iter().map(CmdDescriptor::from).collect::>(); - DispatchResult::CommandSuccess(Some(format!( - "{}\n{}", - format!("{CHISEL_CHAR} Chisel help\n=============").cyan(), - CmdCategory::iter() - .map(|cat| { - // Get commands in the current category - let cat_cmds = &all_descriptors - .iter() - .filter(|(_, _, c)| { - std::mem::discriminant(c) == std::mem::discriminant(&cat) - }) - .collect::>(); - - // Format the help menu for the current category - format!( - "{}\n{}\n", - cat.magenta(), - cat_cmds - .iter() - .map(|(cmds, desc, _)| format!( - "\t{} - {}", - cmds.iter() - .map(|cmd| format!("!{}", cmd.green())) - .collect::>() - .join(" | "), - desc - )) - .collect::>() - .join("\n") - ) - }) - .collect::>() - .join("\n") - ))) - } - ChiselCommand::Quit => { - // Exit the process with status code `0` for success. - std::process::exit(0); - } - ChiselCommand::Clear => { - self.source_mut().drain_run(); - self.source_mut().drain_global_code(); - self.source_mut().drain_top_level_code(); - DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) - } - ChiselCommand::Save => { - if args.len() <= 1 { - // If a new name was supplied, overwrite the ID of the current session. - if args.len() == 1 { - // TODO: Should we delete the old cache file if the id of the session - // changes? - self.session.id = Some(args[0].to_owned()); - } + /// Dispatches an input as a command via [Self::dispatch_command] or as a Solidity snippet. + pub async fn dispatch(&mut self, mut input: &str) -> Result> { + if let Some(command) = input.strip_prefix(COMMAND_LEADER) { + return match ChiselCommand::parse(command) { + Ok(cmd) => self.dispatch_command(cmd).await, + Err(e) => eyre::bail!("unrecognized command: {e}"), + }; + } - if let Err(e) = self.session.write() { - return DispatchResult::FileIoError(e.into()) - } - DispatchResult::CommandSuccess(Some(format!( - "Saved session to cache with ID = {}", - self.session.id.as_ref().unwrap() - ))) - } else { - DispatchResult::CommandFailed(Self::make_error(format!( - "Too many arguments supplied: [{}]. Please check command syntax.", - args.join(", ") - ))) - } - } - ChiselCommand::Load => { - if args.len() != 1 { - // Must supply a session ID as the argument. - return DispatchResult::CommandFailed(Self::make_error( - "Must supply a session ID as the argument.", - )) - } + let source = self.source_mut(); - // Use args as the name - let name = args[0]; - // Try to save the current session before loading another - // Don't save an empty session - if !self.source().run_code.is_empty() { - if let Err(e) = self.session.write() { - return DispatchResult::FileIoError(e.into()) - } - let _ = sh_println!("{}", "Saved current session!".green()); - } + input = input.trim(); + let (only_trivia, new_input) = preprocess(input); + input = &*new_input; - // Parse the arguments - let new_session = match name { - "latest" => ChiselSession::latest(), - _ => ChiselSession::load(name), - }; - - // WARNING: Overwrites the current session - if let Ok(mut new_session) = new_session { - // Regenerate [IntermediateOutput]; It cannot be serialized. - // - // SAFETY - // Should never panic due to the checks performed when the session was created - // in the first place. - new_session.session_source.build().unwrap(); - - self.session = new_session; - DispatchResult::CommandSuccess(Some(format!( - "Loaded Chisel session! (ID = {})", - self.session.id.as_ref().unwrap() - ))) - } else { - DispatchResult::CommandFailed(Self::make_error("Failed to load session!")) - } + // If the input is a comment, add it to the run code so we avoid running with empty input + if only_trivia { + debug!(?input, "matched trivia"); + if !input.is_empty() { + source.add_run_code(input); } - ChiselCommand::ListSessions => match ChiselSession::list_sessions() { - Ok(sessions) => DispatchResult::CommandSuccess(Some(format!( - "{}\n{}", - format!("{CHISEL_CHAR} Chisel Sessions").cyan(), - sessions - .iter() - .map(|(time, name)| { - format!("{} - {}", format!("{time:?}").blue(), name) - }) - .collect::>() - .join("\n") - ))), - Err(_) => DispatchResult::CommandFailed(Self::make_error( - "No sessions found. Use the `!save` command to save a session.", - )), - }, - ChiselCommand::Source => match self.format_source() { - Ok(formatted_source) => DispatchResult::CommandSuccess(Some( - SolidityHelper::new().highlight(&formatted_source).into_owned(), - )), - Err(_) => { - DispatchResult::CommandFailed(String::from("Failed to format session source")) - } - }, - ChiselCommand::ClearCache => match ChiselSession::clear_cache() { - Ok(_) => { - self.session.id = None; - DispatchResult::CommandSuccess(Some(String::from("Cleared chisel cache!"))) - } - Err(_) => DispatchResult::CommandFailed(Self::make_error( - "Failed to clear cache! Check file permissions or disk space.", - )), - }, - ChiselCommand::Fork => { - if args.is_empty() || args[0].trim().is_empty() { - self.source_mut().config.evm_opts.fork_url = None; - return DispatchResult::CommandSuccess(Some( - "Now using local environment.".to_string(), - )) - } - if args.len() != 1 { - return DispatchResult::CommandFailed(Self::make_error( - "Must supply a session ID as the argument.", - )) - } - let arg = *args.first().unwrap(); - - // If the argument is an RPC alias designated in the - // `[rpc_endpoints]` section of the `foundry.toml` within - // the pwd, use the URL matched to the key. - let endpoint = if let Some(endpoint) = - self.source_mut().config.foundry_config.rpc_endpoints.get(arg) - { - endpoint.clone() - } else { - RpcEndpointUrl::Env(arg.to_string()).into() - }; - let fork_url = match endpoint.resolve().url() { - Ok(fork_url) => fork_url, - Err(e) => { - return DispatchResult::CommandFailed(Self::make_error(format!( - "\"{}\" ENV Variable not set!", - e.var - ))) - } - }; + return Ok(ControlFlow::Continue(())); + } - // Check validity of URL - if Url::parse(&fork_url).is_err() { - return DispatchResult::CommandFailed(Self::make_error( - "Invalid fork URL! Please provide a valid RPC endpoint URL.", - )) - } + // Create new source with exact input appended and parse + let (new_source, do_execute) = source.clone_with_new_line(input.to_string())?; - // Create success message before moving the fork_url - let success_msg = format!("Set fork URL to {}", &fork_url.yellow()); + let (cf, res) = source.inspect(input).await?; + if let Some(res) = &res { + let _ = sh_println!("{res}"); + } + if cf.is_break() { + debug!(%input, ?res, "inspect success"); + return Ok(ControlFlow::Continue(())); + } - // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] - // field - self.source_mut().config.evm_opts.fork_url = Some(fork_url); + if do_execute { + self.execute_and_replace(new_source).await.map(ControlFlow::Continue) + } else { + let out = new_source.build()?; + debug!(%input, ?out, "skipped execute and rebuild source"); + *self.source_mut() = new_source; + Ok(ControlFlow::Continue(())) + } + } - // Clear the backend so that it is re-instantiated with the new fork - // upon the next execution of the session source. - self.source_mut().config.backend = None; + /// Decodes traces in the given [`ChiselResult`]. + // TODO: Add `known_contracts` back in. + pub async fn decode_traces( + session_config: &SessionSourceConfig, + result: &mut ChiselResult, + // known_contracts: &ContractsByArtifact, + ) -> eyre::Result { + let chain_id = session_config.evm_opts.get_remote_chain_id().await; - DispatchResult::CommandSuccess(Some(success_msg)) - } - ChiselCommand::Traces => { - self.source_mut().config.traces = !self.source_mut().config.traces; - DispatchResult::CommandSuccess(Some(format!( - "{} traces!", - if self.source_mut().config.traces { "Enabled" } else { "Disabled" } - ))) - } - ChiselCommand::Calldata => { - // remove empty space, double quotes, and 0x prefix - let arg = args - .first() - .map(|s| s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'')) - .map(|s| s.strip_prefix("0x").unwrap_or(s)) - .unwrap_or(""); - - if arg.is_empty() { - self.source_mut().config.calldata = None; - return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) - } + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(result.labeled_addresses.clone()) + .with_signature_identifier(SignaturesIdentifier::from_config( + &session_config.foundry_config, + )?) + .with_chain_id(chain_id.map(|c| c.id())) + .build(); - let calldata = hex::decode(arg); - match calldata { - Ok(calldata) => { - self.source_mut().config.calldata = Some(calldata); - DispatchResult::CommandSuccess(Some(format!( - "Set calldata to '{}'", - arg.yellow() - ))) - } - Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Invalid calldata: {e}" - ))), - } - } - ChiselCommand::MemDump | ChiselCommand::StackDump => { - match self.source_mut().execute().await { - Ok((_, res)) => { - if let Some((stack, mem, _)) = res.state.as_ref() { - if matches!(cmd, ChiselCommand::MemDump) { - // Print memory by word - (0..mem.len()).step_by(32).for_each(|i| { - let _ = sh_println!( - "{}: {}", - format!("[0x{:02x}:0x{:02x}]", i, i + 32).yellow(), - hex::encode_prefixed(&mem[i..i + 32]).cyan() - ); - }); - } else { - // Print all stack items - (0..stack.len()).rev().for_each(|i| { - let _ = sh_println!( - "{}: {}", - format!("[{}]", stack.len() - i - 1).yellow(), - format!("0x{:02x}", stack[i]).cyan() - ); - }); - } - DispatchResult::CommandSuccess(None) - } else { - DispatchResult::CommandFailed(Self::make_error( - "Run function is empty.", - )) - } - } - Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), - } + let mut identifier = + TraceIdentifiers::new().with_external(&session_config.foundry_config, chain_id)?; + if !identifier.is_empty() { + for (_, trace) in &mut result.traces { + decoder.identify(trace, &mut identifier); } - ChiselCommand::Export => { - // Check if the current session inherits `Script.sol` before exporting - - // Check if the pwd is a foundry project - if !Path::new("foundry.toml").exists() { - return DispatchResult::CommandFailed(Self::make_error( - "Must be in a foundry project to export source to script.", - )); - } + } + Ok(decoder) + } - // Create "script" dir if it does not already exist. - if !Path::new("script").exists() { - if let Err(e) = std::fs::create_dir_all("script") { - return DispatchResult::CommandFailed(Self::make_error(e.to_string())) - } - } + /// Display the gathered traces of a REPL execution. + pub async fn show_traces( + decoder: &CallTraceDecoder, + result: &mut ChiselResult, + ) -> eyre::Result<()> { + if result.traces.is_empty() { + return Ok(()); + } - match self.format_source() { - Ok(formatted_source) => { - // Write session source to `script/REPL.s.sol` - if let Err(e) = - std::fs::write(PathBuf::from("script/REPL.s.sol"), formatted_source) - { - return DispatchResult::CommandFailed(Self::make_error(e.to_string())) - } - - DispatchResult::CommandSuccess(Some(String::from( - "Exported session source to script/REPL.s.sol!", - ))) - } - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), - } + sh_println!("{}", "Traces:".green())?; + for (kind, trace) in &mut result.traces { + // Display all Setup + Execution traces. + if matches!(kind, TraceKind::Setup | TraceKind::Execution) { + decode_trace_arena(trace, decoder).await; + sh_println!("{}", render_trace_arena(trace))?; } - ChiselCommand::Fetch => { - if args.len() != 2 { - return DispatchResult::CommandFailed(Self::make_error( - "Incorrect number of arguments supplied. Expected:
", - )) - } + } - let request_url = format!( - "https://api.etherscan.io/api?module=contract&action=getabi&address={}{}", - args[0], - if let Some(api_key) = - self.source().config.foundry_config.etherscan_api_key.as_ref() - { - format!("&apikey={api_key}") - } else { - String::default() - } - ); - - // TODO: Not the cleanest method of building a solidity interface from - // the ABI, but does the trick. Might want to pull this logic elsewhere - // and/or refactor at some point. - match reqwest::get(&request_url).await { - Ok(response) => { - let json = response.json::().await.unwrap(); - if json.status == "1" && json.result.is_some() { - let abi = json.result.unwrap(); - let abi: serde_json::Result = serde_json::from_str(&abi); - if let Ok(abi) = abi { - let mut interface = format!( - "// Interface of {}\ninterface {} {{\n", - args[0], args[1] - ); - - // Add error definitions - abi.errors().for_each(|err| { - interface.push_str(&format!( - "\terror {}({});\n", - err.name, - err.inputs - .iter() - .map(|input| { - let mut param_type = &input.ty; - // If complex type then add the name of custom type. - // see . - if input.is_complex_type() { - if let Some( - InternalType::Enum { contract: _, ty } | - InternalType::Struct { contract: _, ty } | - InternalType::Other { contract: _, ty }, - ) = &input.internal_type - { - param_type = ty; - } - } - format!("{} {}", param_type, input.name) - }) - .collect::>() - .join(",") - )); - }); - // Add event definitions - abi.events().for_each(|event| { - interface.push_str(&format!( - "\tevent {}({});\n", - event.name, - event - .inputs - .iter() - .map(|input| { - let mut formatted = input.ty.to_string(); - if input.indexed { - formatted.push_str(" indexed"); - } - formatted - }) - .collect::>() - .join(",") - )); - }); - // Add function definitions - abi.functions().for_each(|func| { - interface.push_str(&format!( - "\tfunction {}({}) external{}{};\n", - func.name, - func.inputs - .iter() - .map(|input| format_param!(input)) - .collect::>() - .join(","), - match func.state_mutability { - alloy_json_abi::StateMutability::Pure => " pure", - alloy_json_abi::StateMutability::View => " view", - alloy_json_abi::StateMutability::Payable => " payable", - _ => "", - }, - if func.outputs.is_empty() { - String::default() - } else { - format!( - " returns ({})", - func.outputs - .iter() - .map(|output| format_param!(output)) - .collect::>() - .join(",") - ) - } - )); - }); - // Close interface definition - interface.push('}'); - - // Add the interface to the source outright - no need to verify - // syntax via compilation and/or - // parsing. - self.source_mut().with_global_code(&interface); - - DispatchResult::CommandSuccess(Some(format!( - "Added {}'s interface to source as `{}`", - args[0], args[1] - ))) - } else { - DispatchResult::CommandFailed(Self::make_error( - "Contract is not verified!", - )) - } - } else if let Some(error_msg) = json.result { - DispatchResult::CommandFailed(Self::make_error(format!( - "Could not fetch interface - \"{error_msg}\"" - ))) - } else { - DispatchResult::CommandFailed(Self::make_error(format!( - "Could not fetch interface - \"{}\"", - json.message - ))) - } + Ok(()) + } + + async fn execute_and_replace(&mut self, mut new_source: SessionSource) -> Result<()> { + let mut res = new_source.execute().await?; + let failed = !res.success; + if new_source.config.traces || failed { + if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res).await { + Self::show_traces(&decoder, &mut res).await?; + + // Show console logs, if there are any + let decoded_logs = decode_console_logs(&res.logs); + if !decoded_logs.is_empty() { + let _ = sh_println!("{}", "Logs:".green()); + for log in decoded_logs { + let _ = sh_println!(" {log}"); } - Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Failed to communicate with Etherscan API: {e}" - ))), } } - ChiselCommand::Exec => { - if args.is_empty() { - return DispatchResult::CommandFailed(Self::make_error( - "No command supplied! Please provide a valid command after '!'.", - )) - } - let mut cmd = Command::new(args[0]); - if args.len() > 1 { - cmd.args(args[1..].iter().copied()); - } - - match cmd.output() { - Ok(output) => { - std::io::stdout().write_all(&output.stdout).unwrap(); - std::io::stdout().write_all(&output.stderr).unwrap(); - DispatchResult::CommandSuccess(None) - } - Err(e) => DispatchResult::CommandFailed(e.to_string()), - } + if failed { + // If the contract execution failed, continue on without + // updating the source. + eyre::bail!("Failed to execute edited contract!"); } - ChiselCommand::Edit => { - // create a temp file with the content of the run code - let mut temp_file_path = std::env::temp_dir(); - temp_file_path.push("chisel-tmp.sol"); - let result = std::fs::File::create(&temp_file_path) - .map(|mut file| file.write_all(self.source().run_code.as_bytes())); - if let Err(e) = result { - return DispatchResult::CommandFailed(format!( - "Could not write to a temporary file: {e}" - )) - } + } - // open the temp file with the editor - let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); - let mut cmd = Command::new(editor); - cmd.arg(&temp_file_path); - - match cmd.status() { - Ok(status) => { - if !status.success() { - if let Some(status_code) = status.code() { - return DispatchResult::CommandFailed(format!( - "Editor exited with status {status_code}" - )) - } else { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } - } - } - Err(_) => { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } - } + // the code could be compiled, save it + *self.source_mut() = new_source; - let mut new_session_source = self.source().clone(); - if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { - new_session_source.drain_run(); - new_session_source.with_run_code(&edited_code); - } else { - return DispatchResult::CommandFailed( - "Could not read the edited file".to_string(), - ) - } + Ok(()) + } +} - // if the editor exited successfully, try to compile the new code - match new_session_source.execute().await { - Ok((_, mut res)) => { - let failed = !res.success; - if new_session_source.config.traces || failed { - if let Ok(decoder) = - Self::decode_traces(&new_session_source.config, &mut res).await - { - if let Err(e) = Self::show_traces(&decoder, &mut res).await { - return DispatchResult::CommandFailed(e.to_string()) - }; - - // Show console logs, if there are any - let decoded_logs = decode_console_logs(&res.logs); - if !decoded_logs.is_empty() { - let _ = sh_println!("{}", "Logs:".green()); - for log in decoded_logs { - let _ = sh_println!(" {log}"); - } - } - } - - if failed { - // If the contract execution failed, continue on without - // updating the source. - return DispatchResult::CommandFailed(Self::make_error( - "Failed to execute edited contract!", - )); - } - } - - // the code could be compiled, save it - *self.source_mut() = new_session_source; - DispatchResult::CommandSuccess(Some(String::from( - "Successfully edited `run()` function's body!", - ))) - } - Err(_) => { - DispatchResult::CommandFailed("The code could not be compiled".to_string()) - } - } - } - ChiselCommand::RawStack => { - let len = args.len(); - if len != 1 { - let msg = match len { - 0 => "No variable supplied!", - _ => "!rawstack only takes one argument.", - }; - return DispatchResult::CommandFailed(Self::make_error(msg)) - } +/// [`ChiselCommand`] implementations. +impl ChiselDispatcher { + /// Dispatches a [`ChiselCommand`]. + pub async fn dispatch_command(&mut self, cmd: ChiselCommand) -> Result> { + match cmd { + ChiselCommand::Quit => Ok(ControlFlow::Break(())), + cmd => self.dispatch_command_impl(cmd).await.map(ControlFlow::Continue), + } + } - // Store the variable that we want to inspect - let to_inspect = args.first().unwrap(); + async fn dispatch_command_impl(&mut self, cmd: ChiselCommand) -> Result<()> { + match cmd { + ChiselCommand::Help => self.show_help(), + ChiselCommand::Quit => unreachable!(), + ChiselCommand::Clear => self.clear_source(), + ChiselCommand::Save { id } => self.save_session(id), + ChiselCommand::Load { id } => self.load_session(&id), + ChiselCommand::ListSessions => self.list_sessions(), + ChiselCommand::Source => self.show_source(), + ChiselCommand::ClearCache => self.clear_cache(), + ChiselCommand::Fork { url } => self.set_fork(url), + ChiselCommand::Traces => self.toggle_traces(), + ChiselCommand::Calldata { data } => self.set_calldata(data.as_deref()), + ChiselCommand::MemDump => self.show_mem_dump().await, + ChiselCommand::StackDump => self.show_stack_dump().await, + ChiselCommand::Export => self.export(), + ChiselCommand::Fetch { addr, name } => self.fetch_interface(addr, name).await, + ChiselCommand::Exec { command, args } => self.exec_command(command, args), + ChiselCommand::Edit => self.edit_session().await, + ChiselCommand::RawStack { var } => self.show_raw_stack(var).await, + } + } - // Get a mutable reference to the session source - let source = self.source_mut(); + pub(crate) fn show_help(&self) -> Result<()> { + sh_println!("{}", ChiselCommand::format_help()) + } - // Copy the variable's stack contents into a bytes32 variable without updating - // the current session source. - let line = format!("bytes32 __raw__; assembly {{ __raw__ := {to_inspect} }}"); - if let Ok((new_source, _)) = source.clone_with_new_line(line) { - match new_source.inspect("__raw__").await { - Ok((_, Some(res))) => return DispatchResult::CommandSuccess(Some(res)), - Ok((_, None)) => {} - Err(e) => return DispatchResult::CommandFailed(Self::make_error(e)), - } - } + pub(crate) fn clear_source(&mut self) -> Result<()> { + self.source_mut().clear(); + sh_println!("Cleared session!") + } - DispatchResult::CommandFailed( - "Variable must exist within `run()` function.".to_string(), - ) - } + pub(crate) fn save_session(&mut self, id: Option) -> Result<()> { + // If a new name was supplied, overwrite the ID of the current session. + if let Some(id) = id { + // TODO: Should we delete the old cache file if the id of the session changes? + self.session.id = Some(id); } + + self.session.write()?; + sh_println!("Saved session to cache with ID = {}", self.session.id.as_ref().unwrap()) } - /// Dispatches an input as a command via [Self::dispatch_command] or as a Solidity snippet. - pub async fn dispatch(&mut self, mut input: &str) -> DispatchResult { - // Check if the input is a builtin command. - // Commands are denoted with a `!` leading character. - if input.starts_with(COMMAND_LEADER) { - let split: Vec<&str> = input.split_whitespace().collect(); - let raw_cmd = &split[0][1..]; - - return match raw_cmd.parse::() { - Ok(cmd) => self.dispatch_command(cmd, &split[1..]).await, - Err(e) => DispatchResult::UnrecognizedCommand(e), - } + pub(crate) fn load_session(&mut self, id: &str) -> Result<()> { + // Try to save the current session before loading another. + // Don't save an empty session. + if !self.source().run_code.is_empty() { + self.session.write()?; + sh_println!("{}", "Saved current session!".green())?; } - if input.trim().is_empty() { - debug!("empty dispatch input"); - return DispatchResult::Success(None) + + let new_session = match id { + "latest" => ChiselSession::latest(), + id => ChiselSession::load(id), } + .wrap_err("failed to load session")?; - // Get a mutable reference to the session source - let source = self.source_mut(); + new_session.source.build()?; + self.session = new_session; + sh_println!("Loaded Chisel session! (ID = {})", self.session.id.as_ref().unwrap()) + } - // If the input is a comment, add it to the run code so we avoid running with empty input - if COMMENT_RE.is_match(input) { - debug!(%input, "matched comment"); - source.with_run_code(input); - return DispatchResult::Success(None) + pub(crate) fn list_sessions(&self) -> Result<()> { + let sessions = ChiselSession::get_sessions()?; + if sessions.is_empty() { + eyre::bail!("No sessions found. Use the `!save` command to save a session."); } + sh_println!( + "{}\n{}", + format!("{CHISEL_CHAR} Chisel Sessions").cyan(), + sessions + .iter() + .map(|(time, name)| format!("{} - {}", format!("{time:?}").blue(), name)) + .collect::>() + .join("\n") + ) + } - // If there is an address (or multiple addresses) in the input, ensure that they are - // encoded with a valid checksum per EIP-55. - let mut heap_input = input.to_string(); - ADDRESS_RE.captures_iter(input).for_each(|m| { - // Convert the match to a string slice - let match_str = m.name("address").expect("exists").as_str(); - - // We can always safely unwrap here due to the regex matching. - let addr: Address = match_str.parse().expect("Valid address regex"); - // Replace all occurrences of the address with a checksummed version - heap_input = heap_input.replace(match_str, &addr.to_string()); - }); - // Replace the old input with the formatted input. - input = &heap_input; + pub(crate) fn show_source(&self) -> Result<()> { + let formatted = self.format_source().wrap_err("failed to format session source")?; + let highlighted = self.helper.highlight(&formatted); + sh_println!("{highlighted}") + } - // Create new source with exact input appended and parse - let (mut new_source, do_execute) = match source.clone_with_new_line(input.to_string()) { - Ok(new) => new, - Err(e) => { - return DispatchResult::CommandFailed(Self::make_error(format!( - "Failed to parse input! {e}" - ))) - } + pub(crate) fn clear_cache(&mut self) -> Result<()> { + ChiselSession::clear_cache().wrap_err("failed to clear cache")?; + self.session.id = None; + sh_println!("Cleared chisel cache!") + } + + pub(crate) fn set_fork(&mut self, url: Option) -> Result<()> { + let Some(url) = url else { + self.source_mut().config.evm_opts.fork_url = None; + sh_println!("Now using local environment.")?; + return Ok(()); }; - // TODO: Cloning / parsing the session source twice on non-inspected inputs kinda sucks. - // Should change up how this works. - match source.inspect(input).await { - // Continue and print - Ok((true, Some(res))) => { - let _ = sh_println!("{res}"); - } - Ok((true, None)) => {} - // Return successfully - Ok((false, res)) => { - debug!(%input, ?res, "inspect success"); - return DispatchResult::Success(res) - } + // If the argument is an RPC alias designated in the + // `[rpc_endpoints]` section of the `foundry.toml` within + // the pwd, use the URL matched to the key. + let endpoint = if let Some(endpoint) = + self.source_mut().config.foundry_config.rpc_endpoints.get(&url) + { + endpoint.clone() + } else { + RpcEndpointUrl::Env(url).into() + }; + let fork_url = endpoint.resolve().url()?; - // Return with the error - Err(e) => return DispatchResult::CommandFailed(Self::make_error(e)), + if let Err(e) = Url::parse(&fork_url) { + eyre::bail!("invalid fork URL: {e}"); } - if do_execute { - match new_source.execute().await { - Ok((_, mut res)) => { - let failed = !res.success; - - // If traces are enabled or there was an error in execution, show the execution - // traces. - if new_source.config.traces || failed { - if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res).await - { - if let Err(e) = Self::show_traces(&decoder, &mut res).await { - return DispatchResult::CommandFailed(e.to_string()) - }; - - // Show console logs, if there are any - let decoded_logs = decode_console_logs(&res.logs); - if !decoded_logs.is_empty() { - let _ = sh_println!("{}", "Logs:".green()); - for log in decoded_logs { - let _ = sh_println!(" {log}"); - } - } - - // If the contract execution failed, continue on without adding the new - // line to the source. - if failed { - return DispatchResult::Failure(Some(Self::make_error( - "Failed to execute REPL contract!", - ))) - } - } - } + sh_println!("Set fork URL to {}", fork_url.yellow())?; - // Replace the old session source with the new version - *self.source_mut() = new_source; + self.source_mut().config.evm_opts.fork_url = Some(fork_url); + // Clear the backend so that it is re-instantiated with the new fork + // upon the next execution of the session source. + self.source_mut().config.backend = None; - DispatchResult::Success(None) - } - Err(e) => DispatchResult::Failure(Some(e.to_string())), + Ok(()) + } + + pub(crate) fn toggle_traces(&mut self) -> Result<()> { + let t = &mut self.source_mut().config.traces; + *t = !*t; + sh_println!("{} traces!", if *t { "Enabled" } else { "Disabled" }) + } + + pub(crate) fn set_calldata(&mut self, data: Option<&str>) -> Result<()> { + // remove empty space, double quotes, and 0x prefix + let arg = data + .map(|s| s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'')) + .map(|s| s.strip_prefix("0x").unwrap_or(s)) + .unwrap_or(""); + + if arg.is_empty() { + self.source_mut().config.calldata = None; + sh_println!("Calldata cleared.")?; + return Ok(()); + } + + let calldata = hex::decode(arg); + match calldata { + Ok(calldata) => { + self.source_mut().config.calldata = Some(calldata); + sh_println!("Set calldata to '{}'", arg.yellow()) } - } else { - match new_source.build() { - Ok(out) => { - debug!(%input, ?out, "skipped execute and rebuild source"); - *self.source_mut() = new_source; - DispatchResult::Success(None) - } - Err(e) => DispatchResult::Failure(Some(e.to_string())), + Err(e) => { + eyre::bail!("Invalid calldata: {e}") } } } - /// Decodes traces in the [ChiselResult] - /// TODO: Add `known_contracts` back in. - /// - /// ### Takes - /// - /// - A reference to a [SessionSourceConfig] - /// - A mutable reference to a [ChiselResult] - /// - /// ### Returns - /// - /// Optionally, a [CallTraceDecoder] - pub async fn decode_traces( - session_config: &SessionSourceConfig, - result: &mut ChiselResult, - // known_contracts: &ContractsByArtifact, - ) -> eyre::Result { - let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_signature_identifier(SignaturesIdentifier::from_config( - &session_config.foundry_config, - )?) - .build(); + pub(crate) async fn show_mem_dump(&mut self) -> Result<()> { + let res = self.source_mut().execute().await?; + let Some((_, mem)) = res.state.as_ref() else { + eyre::bail!("Run function is empty."); + }; + for i in (0..mem.len()).step_by(32) { + let _ = sh_println!( + "{}: {}", + format!("[0x{:02x}:0x{:02x}]", i, i + 32).yellow(), + hex::encode_prefixed(&mem[i..i + 32]).cyan() + ); + } + Ok(()) + } - let mut identifier = TraceIdentifiers::new().with_etherscan( - &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id().await, - )?; - if !identifier.is_empty() { - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut identifier); - } + pub(crate) async fn show_stack_dump(&mut self) -> Result<()> { + let res = self.source_mut().execute().await?; + let Some((stack, _)) = res.state.as_ref() else { + eyre::bail!("Run function is empty."); + }; + for i in (0..stack.len()).rev() { + let _ = sh_println!( + "{}: {}", + format!("[{}]", stack.len() - i - 1).yellow(), + format!("0x{:02x}", stack[i]).cyan() + ); } - Ok(decoder) + Ok(()) } - /// Display the gathered traces of a REPL execution. - /// - /// ### Takes - /// - /// - A reference to a [CallTraceDecoder] - /// - A mutable reference to a [ChiselResult] - /// - /// ### Returns - /// - /// Optionally, a unit type signifying a successful result. - pub async fn show_traces( - decoder: &CallTraceDecoder, - result: &mut ChiselResult, - ) -> eyre::Result<()> { - if result.traces.is_empty() { - eyre::bail!("Unexpected error: No traces gathered. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); + pub(crate) fn export(&self) -> Result<()> { + // Check if the pwd is a foundry project + if !Path::new("foundry.toml").exists() { + eyre::bail!("Must be in a foundry project to export source to script."); } - sh_println!("{}", "Traces:".green())?; - for (kind, trace) in &mut result.traces { - // Display all Setup + Execution traces. - if matches!(kind, TraceKind::Setup | TraceKind::Execution) { - decode_trace_arena(trace, decoder).await; - sh_println!("{}", render_trace_arena(trace))?; - } + // Create "script" dir if it does not already exist. + if !Path::new("script").exists() { + std::fs::create_dir_all("script")?; } + let formatted_source = self.format_source()?; + std::fs::write(PathBuf::from("script/REPL.s.sol"), formatted_source)?; + sh_println!("Exported session source to script/REPL.s.sol!") + } + + /// Fetches an interface from Etherscan + pub(crate) async fn fetch_interface(&mut self, address: Address, name: String) -> Result<()> { + let abis = fetch_abi_from_etherscan(address, &self.source().config.foundry_config) + .await + .wrap_err("Failed to fetch ABI from Etherscan")?; + let (abi, _) = abis + .into_iter() + .next() + .ok_or_else(|| eyre::eyre!("No ABI found for address {address} on Etherscan"))?; + let code = forge_fmt::format(&abi.to_sol(&name, None), FormatterConfig::default()) + .into_result()?; + self.source_mut().add_global_code(&code); + sh_println!("Added {address}'s interface to source as `{name}`") + } + + pub(crate) fn exec_command(&self, command: String, args: Vec) -> Result<()> { + let mut cmd = Command::new(command); + cmd.args(args); + let _ = cmd.status()?; Ok(()) } - /// Format a type that implements [std::fmt::Display] as a chisel error string. - /// - /// ### Takes - /// - /// A generic type implementing the [std::fmt::Display] trait. - /// - /// ### Returns - /// - /// A formatted error [String]. - pub fn make_error(msg: T) -> String { - format!("{}", msg.red()) + pub(crate) async fn edit_session(&mut self) -> Result<()> { + // create a temp file with the content of the run code + let mut tmp = Builder::new() + .prefix("chisel-") + .suffix(".sol") + .tempfile() + .wrap_err("Could not create temporary file")?; + tmp.as_file_mut() + .write_all(self.source().run_code.as_bytes()) + .wrap_err("Could not write to temporary file")?; + + // open the temp file with the editor + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); + let mut cmd = Command::new(editor); + cmd.arg(tmp.path()); + let st = cmd.status()?; + if !st.success() { + eyre::bail!("Editor exited with {st}"); + } + + let edited_code = std::fs::read_to_string(tmp.path())?; + let mut new_source = self.source().clone(); + new_source.clear_run(); + new_source.add_run_code(&edited_code); + + // if the editor exited successfully, try to compile the new code + self.execute_and_replace(new_source).await?; + sh_println!("Successfully edited `run()` function's body!") + } + + pub(crate) async fn show_raw_stack(&mut self, var: String) -> Result<()> { + let source = self.source_mut(); + let line = format!("bytes32 __raw__; assembly {{ __raw__ := {var} }}"); + if let Ok((new_source, _)) = source.clone_with_new_line(line) + && let (_, Some(res)) = new_source.inspect("__raw__").await? + { + sh_println!("{res}")?; + return Ok(()); + } + + eyre::bail!("Variable must exist within `run()` function.") } } +/// Preprocesses addresses to ensure they are correctly checksummed and returns whether the input +/// only contained trivia (comments, whitespace). +fn preprocess(input: &str) -> (bool, Cow<'_, str>) { + let mut only_trivia = true; + let mut new_input = Cow::Borrowed(input); + for (pos, token) in solar::parse::Cursor::new(input).with_position() { + use RawTokenKind::{BlockComment, LineComment, Literal, Whitespace}; + + if matches!(token.kind, Whitespace | LineComment { .. } | BlockComment { .. }) { + continue; + } + only_trivia = false; + + // Ensure that addresses are correctly checksummed. + if let Literal { kind: RawLiteralKind::Int { base: Base::Hexadecimal, .. } } = token.kind + && token.len == 42 + { + let range = pos..pos + 42; + if let Ok(addr) = input[range.clone()].parse::
() { + new_input.to_mut().replace_range(range, addr.to_checksum_buffer(None).as_str()); + } + } + } + (only_trivia, new_input) +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_comment_regex() { - assert!(COMMENT_RE.is_match("// line comment")); - assert!(COMMENT_RE.is_match(" \n// line \tcomment\n")); - assert!(!COMMENT_RE.is_match("// line \ncomment")); - - assert!(COMMENT_RE.is_match("/* block comment */")); - assert!(COMMENT_RE.is_match(" \t\n /* block \n \t comment */\n")); - assert!(!COMMENT_RE.is_match("/* block \n \t comment */\nwith \tother")); - } + fn test_trivia() { + fn only_trivia(s: &str) -> bool { + let (only_trivia, _new_input) = preprocess(s); + only_trivia + } + assert!(only_trivia("// line comment")); + assert!(only_trivia(" \n// line \tcomment\n")); + assert!(!only_trivia("// line \ncomment")); - #[test] - fn test_address_regex() { - assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4")); - assert!(ADDRESS_RE.is_match(" 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4 ")); - assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4,")); - assert!(ADDRESS_RE.is_match("(0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4)")); - assert!(!ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4aaa")); - assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); - assert!(!ADDRESS_RE.is_match("' 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); - assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + assert!(only_trivia("/* block comment */")); + assert!(only_trivia(" \t\n /* block \n \t comment */\n")); + assert!(!only_trivia("/* block \n \t comment */\nwith \tother")); } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index b490cdfc77c72..2ec057b8167c0 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -2,148 +2,50 @@ //! //! This module contains the execution logic for the [SessionSource]. -use crate::prelude::{ - ChiselDispatcher, ChiselResult, ChiselRunner, IntermediateOutput, SessionSource, SolidityHelper, -}; +use crate::prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_json_abi::EventParam; -use alloy_primitives::{hex, Address, B256, U256}; -use core::fmt::Debug; +use alloy_primitives::{Address, B256, U256, hex}; use eyre::{Result, WrapErr}; use foundry_compilers::Artifact; use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, inspectors::CheatsConfig, traces::TraceMode, }; -use solang_parser::pt::{self, CodeLocation}; -use std::str::FromStr; -use tracing::debug; +use solar::{ + ast::{BinOpKind, ElementaryType, FunctionKind, LitKind, StateMutability, StrKind, UnOpKind}, + interface::Symbol, + sema::{ + hir::{ + ContractId, Event, Expr, ExprKind, Function, ItemId, Res, StmtKind, Type as HirType, + TypeKind, Visibility, + }, + ty::{Gcx, Ty, TyKind}, + }, +}; +use std::ops::ControlFlow; use yansi::Paint; -const USIZE_MAX_AS_U256: U256 = U256::from_limbs([usize::MAX as u64, 0, 0, 0]); - /// Executor implementation for [SessionSource] impl SessionSource { /// Runs the source with the [ChiselRunner] - /// - /// ### Returns - /// - /// Optionally, a tuple containing the [Address] of the deployed REPL contract as well as - /// the [ChiselResult]. - /// - /// Returns an error if compilation fails. - pub async fn execute(&mut self) -> Result<(Address, ChiselResult)> { + pub async fn execute(&mut self) -> Result { // Recompile the project and ensure no errors occurred. - let compiled = self.build()?; - if let Some((_, contract)) = - compiled.clone().compiler_output.contracts_into_iter().find(|(name, _)| name == "REPL") - { - // These *should* never panic after a successful compilation. + let output = self.build()?; + + let (bytecode, final_pc) = output.enter(|output| -> Result<_> { + let contract = output + .repl_contract() + .ok_or_else(|| eyre::eyre!("failed to find REPL contract"))?; + trace!(?contract, "REPL contract"); let bytecode = contract .get_bytecode_bytes() .ok_or_else(|| eyre::eyre!("No bytecode found for `REPL` contract"))?; - let deployed_bytecode = contract - .get_deployed_bytecode_bytes() - .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; - - // Fetch the run function's body statement - let run_func_statements = compiled.intermediate.run_func_body()?; - - // Record loc of first yul block return statement (if any). - // This is used to decide which is the final statement within the `run()` method. - // see . - let last_yul_return = run_func_statements.iter().find_map(|statement| { - if let pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } = statement { - if let Some(statement) = block.statements.last() { - if let pt::YulStatement::FunctionCall(yul_call) = statement { - if yul_call.id.name == "return" { - return Some(statement.loc()) - } - } - } - } - None - }); - - // Find the last statement within the "run()" method and get the program - // counter via the source map. - if let Some(final_statement) = run_func_statements.last() { - // If the final statement is some type of block (assembly, unchecked, or regular), - // we need to find the final statement within that block. Otherwise, default to - // the source loc of the final statement of the `run()` function's block. - // - // There is some code duplication within the arms due to the difference between - // the [pt::Statement] type and the [pt::YulStatement] types. - let mut source_loc = match final_statement { - pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - // Select last non variable declaration statement, see . - let last_statement = block.statements.iter().rev().find(|statement| { - !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) - }); - if let Some(statement) = last_statement { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the asm block. Because we use saturating sub to get the second - // to last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - pt::Statement::Block { loc: _, unchecked: _, statements } => { - if let Some(statement) = statements.last() { - statement.loc() - } else { - // In the case where the block is empty, attempt to grab the statement - // before the block. Because we use saturating sub to get the second to - // last index, this can always be safely unwrapped. - run_func_statements - .get(run_func_statements.len().saturating_sub(2)) - .unwrap() - .loc() - } - } - _ => final_statement.loc(), - }; - - // Consider yul return statement as final statement (if it's loc is lower) . - if let Some(yul_return) = last_yul_return { - if yul_return.end() < source_loc.start() { - source_loc = yul_return; - } - } - - // Map the source location of the final statement of the `run()` function to its - // corresponding runtime program counter - let final_pc = { - let offset = source_loc.start() as u32; - let length = (source_loc.end() - source_loc.start()) as u32; - contract - .get_source_map_deployed() - .unwrap() - .unwrap() - .into_iter() - .zip(InstructionIter::new(&deployed_bytecode)) - .filter(|(s, _)| s.offset() == offset && s.length() == length) - .map(|(_, i)| i.pc) - .max() - .unwrap_or_default() - }; - - // Create a new runner - let mut runner = self.prepare_runner(final_pc).await?; - - // Return [ChiselResult] or bubble up error - runner.run(bytecode.into_owned()) - } else { - // Return a default result if no statements are present. - Ok((Address::ZERO, ChiselResult::default())) - } - } else { - eyre::bail!("Failed to find REPL contract!") - } + Ok((bytecode.into_owned(), output.final_pc(contract)?)) + })?; + let final_pc = final_pc.unwrap_or_default(); + let mut runner = self.build_runner(final_pc).await?; + runner.run(bytecode) } /// Inspect a contract element inside of the current session @@ -157,13 +59,13 @@ impl SessionSource { /// If the input is valid `Ok((continue, formatted_output))` where: /// - `continue` is true if the input should be appended to the source /// - `formatted_output` is the formatted value, if any - pub async fn inspect(&self, input: &str) -> Result<(bool, Option)> { + pub async fn inspect(&self, input: &str) -> Result<(ControlFlow<()>, Option)> { let line = format!("bytes memory inspectoor = abi.encode({input});"); - let mut source = match self.clone_with_new_line(line.clone()) { + let mut source = match self.clone_with_new_line(line) { Ok((source, _)) => source, Err(err) => { - debug!(%err, "failed to build new source"); - return Ok((true, None)) + debug!(%err, "failed to build new source for inspection"); + return Ok((ControlFlow::Continue(()), None)); } }; @@ -172,16 +74,16 @@ impl SessionSource { // Events and tuples fails compilation due to it not being able to be encoded in // `inspectoor`. If that happens, try executing without the inspector. let (mut res, err) = match source.execute().await { - Ok((_, res)) => (res, None), + Ok(res) => (res, None), Err(err) => { debug!(?err, %input, "execution failed"); match source_without_inspector.execute().await { - Ok((_, res)) => (res, Some(err)), + Ok(res) => (res, Some(err)), Err(_) => { if self.config.foundry_config.verbosity >= 3 { sh_err!("Could not inspect: {err}")?; } - return Ok((true, None)) + return Ok((ControlFlow::Continue(()), None)); } } } @@ -189,20 +91,14 @@ impl SessionSource { // If abi-encoding the input failed, check whether it is an event if let Some(err) = err { - let generated_output = source_without_inspector - .generated_output - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find generated output!"))?; - - let intermediate_contract = generated_output - .intermediate - .intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find intermediate contract!"))?; - - if let Some(event_definition) = intermediate_contract.event_definitions.get(input) { - let formatted = format_event_definition(event_definition)?; - return Ok((false, Some(formatted))) + let output = source_without_inspector.build()?; + + let formatted_event = output.enter(|output| { + let gcx = output.gcx(); + output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid))) + }); + if let Some(formatted_event) = formatted_event { + return Ok((ControlFlow::Break(()), Some(formatted_event?))); } // we were unable to check the event @@ -211,10 +107,11 @@ impl SessionSource { } debug!(%err, %input, "failed abi encode input"); - return Ok((false, None)) + return Ok((ControlFlow::Break(()), None)); } + drop(source_without_inspector); - let Some((stack, memory, _)) = &res.state else { + let Some((stack, memory)) = &res.state else { // Show traces and logs, if there are any, and return an error if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await { ChiselDispatcher::show_traces(&decoder, &mut res).await?; @@ -227,129 +124,152 @@ impl SessionSource { } } - return Err(eyre::eyre!("Failed to inspect expression")) + return Err(eyre::eyre!("Failed to inspect expression")); }; - let generated_output = source - .generated_output - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find generated output!"))?; - - // If the expression is a variable declaration within the REPL contract, use its type; - // otherwise, attempt to infer the type. - let contract_expr = generated_output - .intermediate - .repl_contract_expressions - .get(input) - .or_else(|| source.infer_inner_expr_type()); - - // If the current action is a function call, we get its return type - // otherwise it returns None - let function_call_return_type = - Type::get_function_return_type(contract_expr, &generated_output.intermediate); - - let (contract_expr, ty) = if let Some(function_call_return_type) = function_call_return_type - { - (function_call_return_type.0, function_call_return_type.1) - } else { - match contract_expr.and_then(|e| { - Type::ethabi(e, Some(&generated_output.intermediate)).map(|ty| (e, ty)) - }) { - Some(res) => res, - // this type was denied for inspection, continue - None => return Ok((true, None)), + // Either the expression referred to by `input`, or the last expression, + // which was wrapped in `abi.encode`. + let generated_output = source.build()?; + + // Inside the compiler closure, infer the DynSolType of the inspected expression and + // determine whether the REPL should continue. + let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> { + let gcx = out.gcx(); + + // Try direct lookup of `input` as a named variable in the REPL contract. + if let Some(direct_ty) = lookup_named_variable_type(gcx, input) { + return Some((false, direct_ty)); } + + // Otherwise, find the appended `bytes memory inspectoor = abi.encode();` + // and pull out the first call argument. + let block = out.run_func_body(); + let last = block.last()?; + let StmtKind::DeclSingle(vid) = last.kind else { return None }; + let var = gcx.hir.variable(vid); + let init = var.initializer?; + let ExprKind::Call(_callee, args, _) = &init.kind else { return None }; + let inner_expr = args.exprs().next()?; + + // If the call is `func()` returning a single value, prefer the function return type. + if let Some(ty) = get_function_return_type(gcx, inner_expr) { + return Some((should_continue(inner_expr), ty)); + } + + let ty = expr_to_dyn(gcx, inner_expr, true)?; + Some((should_continue(inner_expr), ty)) + }); + + let Some((cont, ty)) = res_ty else { + return Ok((ControlFlow::Continue(()), None)); }; // the file compiled correctly, thus the last stack item must be the memory offset of // the `bytes memory inspectoor` value - let mut offset = stack.last().unwrap().to::(); - let mem_offset = &memory[offset..offset + 32]; - let len = U256::try_from_be_slice(mem_offset).unwrap().to::(); - offset += 32; - let data = &memory[offset..offset + len]; - // `tokens` is guaranteed to have the same length as the provided types - let token = - DynSolType::abi_decode(&ty, data).wrap_err("Could not decode inspected values")?; - Ok((should_continue(contract_expr), Some(format_token(token)))) - } - - /// Gracefully attempts to extract the type of the expression within the `abi.encode(...)` - /// call inserted by the inspect function. - /// - /// ### Takes - /// - /// A reference to a [SessionSource] - /// - /// ### Returns - /// - /// Optionally, a [Type] - fn infer_inner_expr_type(&self) -> Option<&pt::Expression> { - let out = self.generated_output.as_ref()?; - let run = out.intermediate.run_func_body().ok()?.last(); - match run { - Some(pt::Statement::VariableDefinition( - _, - _, - Some(pt::Expression::FunctionCall(_, _, args)), - )) => { - // We can safely unwrap the first expression because this function - // will only be called on a session source that has just had an - // `inspectoor` variable appended to it. - Some(args.first().unwrap()) - } - _ => None, - } + let data = (|| -> Option<_> { + let mut offset: usize = stack.last()?.try_into().ok()?; + debug!("inspect memory @ {offset}: {}", hex::encode(memory)); + let mem_offset = memory.get(offset..offset + 32)?; + let len: usize = U256::try_from_be_slice(mem_offset)?.try_into().ok()?; + offset += 32; + memory.get(offset..offset + len) + })(); + let Some(data) = data else { + eyre::bail!("Failed to inspect last expression: could not retrieve data from memory") + }; + let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?; + let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }; + Ok((c, Some(format_token(token)))) } - /// Prepare a runner for the Chisel REPL environment - /// - /// ### Takes - /// - /// The final statement's program counter for the ChiselInspector - /// - /// ### Returns - /// - /// A configured [ChiselRunner] - async fn prepare_runner(&mut self, final_pc: usize) -> Result { - let env = - self.config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); + async fn build_runner(&mut self, final_pc: usize) -> Result { + let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?; - // Create an in-memory backend - let backend = match self.config.backend.take() { + let backend = match self.config.backend.clone() { Some(backend) => backend, None => { - let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); + let fork = self.config.evm_opts.get_fork( + &self.config.foundry_config, + evm_env.cfg_env.chain_id, + fork_block, + ); let backend = Backend::spawn(fork)?; self.config.backend = Some(backend.clone()); backend } }; - // Build a new executor - let executor = ExecutorBuilder::new() + let executor = ExecutorBuilder::default() .inspectors(|stack| { - stack.chisel_state(final_pc).trace_mode(TraceMode::Call).cheatcodes( - CheatsConfig::new( - &self.config.foundry_config, - self.config.evm_opts.clone(), - None, - None, + stack + .logs(self.config.foundry_config.live_logs) + .chisel_state(final_pc) + .trace_mode(TraceMode::Call) + .cheatcodes( + CheatsConfig::new( + &self.config.foundry_config, + self.config.evm_opts.clone(), + None, + None, + None, + ) + .into(), ) - .into(), - ) }) .gas_limit(self.config.evm_opts.gas_limit()) .spec_id(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) - .build(env, backend); + .build(evm_env, tx_env, backend); - // Create a [ChiselRunner] with a default balance of [U256::MAX] and - // the sender [Address::zero]. Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone())) } } +/// Looks up `name` as a named variable in the REPL contract (state variables or run() locals) +/// and returns its type as a [`DynSolType`]. +/// +/// Only top-level statements of `run()` are scanned. Variables declared inside nested blocks +/// (`if`, `for`, `while`, `unchecked`, etc.) are not visible here; the caller falls back to +/// the `inspectoor`-based path for those cases. +fn lookup_named_variable_type(gcx: Gcx<'_>, name: &str) -> Option { + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + + // State variables. + for vid in repl.variables() { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + + // Locals declared in run(). + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + for stmt in body.stmts { + match stmt.kind { + StmtKind::DeclSingle(vid) => { + let var = hir.variable(vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())); + } + } + StmtKind::DeclMulti(vids, _) => { + for vid in vids.iter().flatten() { + let var = hir.variable(*vid); + if var.name.map(|n| n.as_str() == name).unwrap_or(false) { + return solar_ty_to_dyn(gcx, gcx.type_of_item((*vid).into())); + } + } + } + _ => {} + } + } + None +} + /// Formats a value into an inspection message // TODO: Verbosity option fn format_token(token: DynSolValue) -> String { @@ -371,10 +291,8 @@ fn format_token(token: DynSolValue) -> String { format!( "0x{}", format!("{i:x}") - .char_indices() - .skip(64 - bit_len / 4) - .take(bit_len / 4) - .map(|(_, c)| c) + .chars() + .skip(if i.is_negative() { 64 - bit_len / 4 } else { 0 }) .collect::() ) .cyan(), @@ -386,16 +304,7 @@ fn format_token(token: DynSolValue) -> String { format!( "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", format!("uint{bit_len}").red(), - format!( - "0x{}", - format!("{i:x}") - .char_indices() - .skip(64 - bit_len / 4) - .take(bit_len / 4) - .map(|(_, c)| c) - .collect::() - ) - .cyan(), + format!("0x{i:x}").cyan(), hex::encode_prefixed(B256::from(i)).cyan(), i.cyan() ) @@ -463,50 +372,37 @@ fn format_token(token: DynSolValue) -> String { } } -/// Formats a [pt::EventDefinition] into an inspection message -/// -/// ### Takes -/// -/// An borrowed [pt::EventDefinition] -/// -/// ### Returns -/// -/// A formatted [pt::EventDefinition] for use in inspection output. -/// -/// TODO: Verbosity option -fn format_event_definition(event_definition: &pt::EventDefinition) -> Result { - let event_name = event_definition.name.as_ref().expect("Event has a name").to_string(); - let inputs = event_definition - .fields +/// Formats an [`Event`] into an inspection message. +// TODO: Verbosity option +fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result { + let event_name = event.name.as_str().to_string(); + let inputs = event + .parameters .iter() - .map(|param| { - let name = param - .name - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "".to_string()); - let kind = Type::from_expression(¶m.ty) - .and_then(Type::into_builtin) + .map(|&pid| { + let var = gcx.hir.variable(pid); + let name = + var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "".into()); + let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into())) .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?; Ok(EventParam { name, ty: kind.to_string(), components: vec![], - indexed: param.indexed, + indexed: var.indexed, internal_type: None, }) }) .collect::>>()?; - let event = - alloy_json_abi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; + let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous }; Ok(format!( "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}", "event".red(), SolidityHelper::new().highlight(&format!( "{}({})", - &event.name, - &event + event.name, + event .inputs .iter() .map(|param| format!( @@ -516,7 +412,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result>() @@ -532,901 +428,726 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result), - - /// (type, length) - FixedArray(Box, usize), +/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection. +/// +/// `lookup` controls whether user-defined type names are resolved via the HIR. +fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + match &expr.kind { + // Elementary type expression: `uint256`, `address`, etc. + ExprKind::Type(ty) => hir_ty_to_dyn(gcx, ty), + + // `type(T)`: only meaningful as the lhs of a member access. + ExprKind::TypeCall(_) => None, + + // Literals. + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Address(_) => Some(DynSolType::Address), + LitKind::Bool(_) => Some(DynSolType::Bool), + LitKind::Str(kind, _, _) => match kind { + StrKind::Hex => Some(DynSolType::Bytes), + StrKind::Str | StrKind::Unicode => Some(DynSolType::String), + }, + LitKind::Number(_) | LitKind::Rational(_) => Some(DynSolType::Uint(256)), + LitKind::Err(_) => None, + }, + + // Resolved identifier: `foo`. + ExprKind::Ident(reses) => { + let res = reses.first()?; + match *res { + Res::Item(ItemId::Variable(vid)) => { + solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) + } + Res::Item(ItemId::Struct(sid)) => { + // Struct reference used as a constructor produces a tuple of field types. + Some(DynSolType::Tuple( + gcx.struct_field_types(sid) + .iter() + .filter_map(|&t| solar_ty_to_dyn(gcx, t)) + .collect(), + )) + } + // Other items and builtins: handled by enclosing Call/Member expressions. + _ => None, + } + } - /// (type, index) - ArrayIndex(Box, Option), + // Index/access: `arr[i]`, `MyType[]`, `MyType[N]`. + ExprKind::Index(base, idx) => { + let base_ty = expr_to_dyn(gcx, base, lookup)?; + let num = + idx.and_then(|e| parse_number_literal(e)).and_then(|n| usize::try_from(n).ok()); + match &base.kind { + // Type-level indexing builds an array type expression. + ExprKind::Type(_) | ExprKind::TypeCall(_) => { + if let Some(n) = num { + Some(DynSolType::FixedArray(Box::new(base_ty), n)) + } else { + Some(DynSolType::Array(Box::new(base_ty))) + } + } + // Runtime indexing returns the element type. + _ => match base_ty { + DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => Some(*inner), + DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_) => { + Some(DynSolType::FixedBytes(1)) + } + other => Some(other), + }, + } + } - /// (types) - Tuple(Vec>), + // Slice: same type as the base. + ExprKind::Slice(base, _, _) => expr_to_dyn(gcx, base, lookup), - /// (name, params, returns) - Function(Box, Vec>, Vec>), + // Array literal `[a, b, c]`. + ExprKind::Array(values) => values + .first() + .and_then(|e| expr_to_dyn(gcx, e, lookup)) + .map(|ty| DynSolType::FixedArray(Box::new(ty), values.len())), - /// (lhs, rhs) - Access(Box, String), + // Tuple expression `(a, b, c)`. + ExprKind::Tuple(items) => Some(DynSolType::Tuple( + items.iter().filter_map(|opt| opt.and_then(|e| expr_to_dyn(gcx, e, lookup))).collect(), + )), - /// (types) - Custom(Vec), -} + // Member access `lhs.member`. + ExprKind::Member(_, _) => resolve_member(gcx, expr, lookup), -impl Type { - /// Convert a [pt::Expression] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Expression] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_expression(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::Type(_, ty) => Self::from_type(ty), - - pt::Expression::Variable(ident) => Some(Self::Custom(vec![ident.name.clone()])), - - // array - pt::Expression::ArraySubscript(_, expr, num) => { - // if num is Some then this is either an index operation (arr[]) - // or a FixedArray statement (new uint256[]) - Self::from_expression(expr).and_then(|ty| { - let boxed = Box::new(ty); - let num = num.as_deref().and_then(parse_number_literal).and_then(|n| { - // overflow check - if n > USIZE_MAX_AS_U256 { - None - } else { - Some(n.to::()) - } - }); - match expr.as_ref() { - // statement - pt::Expression::Type(_, _) => { - if let Some(num) = num { - Some(Self::FixedArray(boxed, num)) - } else { - Some(Self::Array(boxed)) - } - } - // index - pt::Expression::Variable(_) => { - Some(Self::ArrayIndex(boxed, num)) - } - _ => None - } - }) - } - pt::Expression::ArrayLiteral(_, values) => { - values.first().and_then(Self::from_expression).map(|ty| { - Self::FixedArray(Box::new(ty), values.len()) - }) - } + // Function/constructor call. + ExprKind::Call(_, _, _) => resolve_call(gcx, expr, lookup), - // tuple - pt::Expression::List(_, params) => Some(Self::Tuple(map_parameters(params))), + // `new T`: produces a value of type T. + ExprKind::New(ty) => hir_ty_to_dyn(gcx, ty), - // . - pt::Expression::MemberAccess(_, lhs, rhs) => { - Self::from_expression(lhs).map(|lhs| { - Self::Access(Box::new(lhs), rhs.name.clone()) - }) - } + // `payable(addr)`. + ExprKind::Payable(_) => Some(DynSolType::Address), - // - pt::Expression::Parenthesis(_, inner) | // () - pt::Expression::New(_, inner) | // new - pt::Expression::UnaryPlus(_, inner) | // + - // ops - pt::Expression::BitwiseNot(_, inner) | // ~ - pt::Expression::ArraySlice(_, inner, _, _) | // [*start*:*end*] - // assign ops - pt::Expression::PreDecrement(_, inner) | // -- - pt::Expression::PostDecrement(_, inner) | // -- - pt::Expression::PreIncrement(_, inner) | // ++ - pt::Expression::PostIncrement(_, inner) | // ++ - pt::Expression::Assign(_, inner, _) | // = ... - pt::Expression::AssignAdd(_, inner, _) | // += ... - pt::Expression::AssignSubtract(_, inner, _) | // -= ... - pt::Expression::AssignMultiply(_, inner, _) | // *= ... - pt::Expression::AssignDivide(_, inner, _) | // /= ... - pt::Expression::AssignModulo(_, inner, _) | // %= ... - pt::Expression::AssignAnd(_, inner, _) | // &= ... - pt::Expression::AssignOr(_, inner, _) | // |= ... - pt::Expression::AssignXor(_, inner, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, inner, _) | // <<= ... - pt::Expression::AssignShiftRight(_, inner, _) // >>= ... - => Self::from_expression(inner), - - // *condition* ? : - pt::Expression::ConditionalOperator(_, _, if_true, if_false) => { - Self::from_expression(if_true).or_else(|| Self::from_expression(if_false)) - } + // Ternary: prefer truthy branch's type, fall back to else branch. + ExprKind::Ternary(_, t, e) => { + expr_to_dyn(gcx, t, lookup).or_else(|| expr_to_dyn(gcx, e, lookup)) + } - // address - pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), - pt::Expression::HexNumberLiteral(_, s, _) => { - match s.parse::
() { - Ok(addr) => { - if *s == addr.to_checksum(None) { - Some(Self::Builtin(DynSolType::Address)) - } else { - Some(Self::Builtin(DynSolType::Uint(256))) - } - }, - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) + // Delete has no return type. + ExprKind::Delete(_) => None, + + // Unary operations. + ExprKind::Unary(op, inner) => match op.kind { + UnOpKind::Neg => expr_to_dyn(gcx, inner, lookup).map(|ty| match ty { + DynSolType::Uint(n) => DynSolType::Int(n), + DynSolType::Int(n) => DynSolType::Uint(n), + x => x, + }), + UnOpKind::Not => Some(DynSolType::Bool), + UnOpKind::BitNot + | UnOpKind::PreInc + | UnOpKind::PreDec + | UnOpKind::PostInc + | UnOpKind::PostDec => expr_to_dyn(gcx, inner, lookup), + }, + + // Binary operations. + ExprKind::Binary(lhs, op, rhs) => match op.kind { + BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Eq + | BinOpKind::Ne + | BinOpKind::And + | BinOpKind::Or => Some(DynSolType::Bool), + BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul | BinOpKind::Div => { + match (expr_to_dyn(gcx, lhs, false), expr_to_dyn(gcx, rhs, false)) { + (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_))) + | (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => { + Some(DynSolType::Int(256)) } + _ => Some(DynSolType::Uint(256)), } } + BinOpKind::Rem + | BinOpKind::Pow + | BinOpKind::BitAnd + | BinOpKind::BitOr + | BinOpKind::BitXor + | BinOpKind::Shl + | BinOpKind::Shr + | BinOpKind::Sar => Some(DynSolType::Uint(256)), + }, + + // Assignments: type of the lhs. + ExprKind::Assign(lhs, _, _) => expr_to_dyn(gcx, lhs, lookup), + + ExprKind::Err(_) => None, + } +} - // uint and int - // invert - pt::Expression::Negate(_, inner) => Self::from_expression(inner).map(Self::invert_int), - - // int if either operand is int - // TODO: will need an update for Solidity v0.8.18 user defined operators: - // https://github.com/ethereum/solidity/issues/13718#issuecomment-1341058649 - pt::Expression::Add(_, lhs, rhs) | - pt::Expression::Subtract(_, lhs, rhs) | - pt::Expression::Multiply(_, lhs, rhs) | - pt::Expression::Divide(_, lhs, rhs) => { - match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(DynSolType::Int(_)), Some(DynSolType::Int(_))) | - (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) | - (Some(DynSolType::Uint(_)), Some(DynSolType::Int(_))) => { - Some(Self::Builtin(DynSolType::Int(256))) - } - _ => { - Some(Self::Builtin(DynSolType::Uint(256))) - } +/// Converts a [`HirType`] to a [`DynSolType`]. +fn hir_ty_to_dyn(gcx: Gcx<'_>, ty: &HirType<'_>) -> Option { + match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + TypeKind::Array(arr) => { + let elem = hir_ty_to_dyn(gcx, &arr.element)?; + if let Some(size) = arr.size { + let n = parse_number_literal(size).and_then(|n| usize::try_from(n).ok()); + if let Some(n) = n { + Some(DynSolType::FixedArray(Box::new(elem), n)) + } else { + Some(DynSolType::Array(Box::new(elem))) } + } else { + Some(DynSolType::Array(Box::new(elem))) } - - // always assume uint - pt::Expression::Modulo(_, _, _) | - pt::Expression::Power(_, _, _) | - pt::Expression::BitwiseOr(_, _, _) | - pt::Expression::BitwiseAnd(_, _, _) | - pt::Expression::BitwiseXor(_, _, _) | - pt::Expression::ShiftRight(_, _, _) | - pt::Expression::ShiftLeft(_, _, _) | - pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(DynSolType::Uint(256))), - - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(_, _, _, _, _) => { - Some(Self::Builtin(DynSolType::Uint(256))) + } + TypeKind::Function(f) => match f.returns.len() { + 0 => None, + 1 => { + let var = gcx.hir.variable(f.returns[0]); + hir_ty_to_dyn(gcx, &var.ty) } + _ => Some(DynSolType::Tuple( + f.returns + .iter() + .filter_map(|&pid| hir_ty_to_dyn(gcx, &gcx.hir.variable(pid).ty)) + .collect(), + )), + }, + TypeKind::Mapping(m) => hir_ty_to_dyn(gcx, &m.value), + TypeKind::Custom(item) => solar_ty_to_dyn(gcx, gcx.type_of_item(*item)), + TypeKind::Err(_) => None, + } +} - // bool - pt::Expression::BoolLiteral(_, _) | - pt::Expression::And(_, _, _) | - pt::Expression::Or(_, _, _) | - pt::Expression::Equal(_, _, _) | - pt::Expression::NotEqual(_, _, _) | - pt::Expression::Less(_, _, _) | - pt::Expression::LessEqual(_, _, _) | - pt::Expression::More(_, _, _) | - pt::Expression::MoreEqual(_, _, _) | - pt::Expression::Not(_, _) => Some(Self::Builtin(DynSolType::Bool)), - - // string - pt::Expression::StringLiteral(_) => Some(Self::Builtin(DynSolType::String)), - - // bytes - pt::Expression::HexLiteral(_) => Some(Self::Builtin(DynSolType::Bytes)), - - // function - pt::Expression::FunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(Self::from_expression).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } - pt::Expression::NamedFunctionCall(_, name, args) => { - Self::from_expression(name).map(|name| { - let args = args.iter().map(|arg| Self::from_expression(&arg.expr)).collect(); - Self::Function(Box::new(name), args, vec![]) - }) - } +/// Resolves a member-access expression (`lhs.member`) to its [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Member`. +fn resolve_member(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Member(lhs, ident) = &expr.kind else { return None }; + let member = ident.name; + + // `type(T).member` — type introspection. + if let ExprKind::TypeCall(ty) = &lhs.kind { + return match member.as_str() { + "name" => Some(DynSolType::String), + "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), + "interfaceId" => Some(DynSolType::FixedBytes(4)), + // Only valid for integer types; custom types (enums) fall back to Uint(256). + "min" | "max" => match &ty.kind { + TypeKind::Elementary(et) => elementary_to_dyn(*et), + _ => Some(DynSolType::Uint(256)), + }, + _ => None, + }; + } - // explicitly None - pt::Expression::Delete(_, _) | pt::Expression::FunctionCallBlock(_, _, _) => None, - } + // Built-in namespace identifier: `block.timestamp`, `msg.sender`, `abi.encode`, etc. + if let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + && let Some(ty) = builtin_member(b.name().as_str(), member.as_str()) + { + return Some(ty); } - /// Convert a [pt::Type] to a [Type] - /// - /// ### Takes - /// - /// A reference to a [pt::Type] to convert. - /// - /// ### Returns - /// - /// Optionally, an owned [Type] - fn from_type(ty: &pt::Type) -> Option { - let ty = match ty { - pt::Type::Address | pt::Type::AddressPayable | pt::Type::Payable => { - Self::Builtin(DynSolType::Address) - } - pt::Type::Bool => Self::Builtin(DynSolType::Bool), - pt::Type::String => Self::Builtin(DynSolType::String), - pt::Type::Int(size) => Self::Builtin(DynSolType::Int(*size as usize)), - pt::Type::Uint(size) => Self::Builtin(DynSolType::Uint(*size as usize)), - pt::Type::Bytes(size) => Self::Builtin(DynSolType::FixedBytes(*size as usize)), - pt::Type::DynamicBytes => Self::Builtin(DynSolType::Bytes), - pt::Type::Mapping { value, .. } => Self::from_expression(value)?, - pt::Type::Function { params, returns, .. } => { - let params = map_parameters(params); - let returns = returns - .as_ref() - .map(|(returns, _)| map_parameters(returns)) - .unwrap_or_default(); - Self::Function( - Box::new(Self::Custom(vec!["__fn_type__".to_string()])), - params, - returns, - ) - } - // TODO: Rational numbers - pt::Type::Rational => return None, + // Elementary type used as a namespace: `address.balance`, `bytes.concat`, etc. + if let ExprKind::Type(ty) = &lhs.kind + && let TypeKind::Elementary(et) = &ty.kind + { + return match et { + ElementaryType::Address(_) => match member.as_str() { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + ElementaryType::Bytes => match member.as_str() { + "concat" => Some(DynSolType::Bytes), + _ => None, + }, + ElementaryType::String => match member.as_str() { + "concat" => Some(DynSolType::String), + _ => None, + }, + _ => None, }; - Some(ty) } - /// Handle special expressions like [global variables](https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables) - /// - /// See: - fn map_special(self) -> Self { - if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { - return self - } + // Members on a resolved DynSolType (`.length`, `.pop`, `.selector`, `.address`). + if let Some(lhs_ty) = expr_to_dyn(gcx, lhs, lookup) + && let Some(ty) = dyn_member(&lhs_ty, member.as_str()) + { + return Some(ty); + } - let mut types = Vec::with_capacity(5); - let mut args = None; - self.recurse(&mut types, &mut args); + // HIR lookup for user-defined type members. + if lookup && let Some(mut chain) = expr_name_chain(gcx, lhs) { + chain.insert(0, member); + return infer_custom_type(gcx, &mut chain, None).ok().flatten(); + } - let len = types.len(); - if len == 0 { - return self - } + None +} + +/// Returns the type of `builtin_ns.member` for built-in global namespaces. +fn builtin_member(builtin: &str, member: &str) -> Option { + match builtin { + "block" => match member { + "coinbase" => Some(DynSolType::Address), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | "chainid" + | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), + _ => None, + }, + "msg" => match member { + "sender" => Some(DynSolType::Address), + "gas" | "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), + _ => None, + }, + "tx" => match member { + "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), + _ => None, + }, + "address" => match member { + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), + _ => None, + }, + _ => None, + } +} + +/// Returns the type of `ty.member` for a known [`DynSolType`]. +fn dyn_member(ty: &DynSolType, member: &str) -> Option { + match member { + "length" => match ty { + DynSolType::Array(_) + | DynSolType::FixedArray(_, _) + | DynSolType::Bytes + | DynSolType::String + | DynSolType::FixedBytes(_) => Some(DynSolType::Uint(256)), + _ => None, + }, + "pop" => match ty { + DynSolType::Array(inner) => Some(*inner.clone()), + _ => None, + }, + // Address members. + "balance" => match ty { + DynSolType::Address => Some(DynSolType::Uint(256)), + _ => None, + }, + "code" => match ty { + DynSolType::Address => Some(DynSolType::Bytes), + _ => None, + }, + "codehash" => match ty { + DynSolType::Address => Some(DynSolType::FixedBytes(32)), + _ => None, + }, + "send" => match ty { + DynSolType::Address => Some(DynSolType::Bool), + _ => None, + }, + // External function members. + "selector" => Some(DynSolType::FixedBytes(4)), + "address" => Some(DynSolType::Address), + _ => None, + } +} + +/// Resolves a call expression to its return [`DynSolType`]. +/// +/// `expr` must be `ExprKind::Call`. +fn resolve_call(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option { + let ExprKind::Call(callee, args, _named) = &expr.kind else { return None }; - // Type members, like array, bytes etc - #[expect(clippy::single_match)] - match &self { - Self::Access(inner, access) => { - if let Some(ty) = inner.as_ref().clone().try_as_ethabi(None) { - // Array / bytes members - let ty = Self::Builtin(ty); - match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { - return Self::Builtin(DynSolType::Uint(256)) + // Type cast: `uint256(x)`, `address(y)`, etc. + if let ExprKind::Type(ty) = &callee.kind { + return hir_ty_to_dyn(gcx, ty); + } + + // Member call: `ns.method(...)`. + if let ExprKind::Member(lhs, method) = &callee.kind + && let ExprKind::Ident(reses) = &lhs.kind + && let Some(Res::Builtin(b)) = reses.first() + { + match b.name().as_str() { + "abi" => { + return match method.as_str() { + "decode" => { + let last = args.exprs().last()?; + match expr_to_dyn(gcx, last, false)? { + DynSolType::Tuple(tys) => Some(DynSolType::Tuple(tys)), + ty => Some(DynSolType::Tuple(vec![ty])), } - "pop" if ty.is_dynamic_array() => return ty, - _ => {} } - } + s if s.starts_with("encode") => Some(DynSolType::Bytes), + _ => None, + }; } + "string" if method.as_str() == "concat" => return Some(DynSolType::String), + "bytes" if method.as_str() == "concat" => return Some(DynSolType::Bytes), _ => {} } + } - let this = { - let name = types.last().unwrap().as_str(); - match len { - 0 => unreachable!(), - 1 => match name { + // Simple identifier call: built-in global functions and HIR function calls. + if let ExprKind::Ident(reses) = &callee.kind { + match reses.first() { + Some(Res::Builtin(b)) => { + return match b.name().as_str() { "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)), "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)), "ripemd160" => Some(DynSolType::FixedBytes(20)), "ecrecover" => Some(DynSolType::Address), _ => None, - }, - 2 => { - let access = types.first().unwrap().as_str(); - match name { - "block" => match access { - "coinbase" => Some(DynSolType::Address), - "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | - "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), - _ => None, - }, - "msg" => match access { - "sender" => Some(DynSolType::Address), - "gas" => Some(DynSolType::Uint(256)), - "value" => Some(DynSolType::Uint(256)), - "data" => Some(DynSolType::Bytes), - "sig" => Some(DynSolType::FixedBytes(4)), - _ => None, - }, - "tx" => match access { - "origin" => Some(DynSolType::Address), - "gasprice" => Some(DynSolType::Uint(256)), - _ => None, - }, - "abi" => match access { - "decode" => { - // args = Some([Bytes(_), Tuple(args)]) - // unwrapping is safe because this is first compiled by solc so - // it is guaranteed to be a valid call - let mut args = args.unwrap(); - let last = args.pop().unwrap(); - match last { - Some(ty) => { - return match ty { - Self::Tuple(_) => ty, - ty => Self::Tuple(vec![Some(ty)]), - } - } - None => None, - } - } - s if s.starts_with("encode") => Some(DynSolType::Bytes), - _ => None, - }, - "address" => match access { - "balance" => Some(DynSolType::Uint(256)), - "code" => Some(DynSolType::Bytes), - "codehash" => Some(DynSolType::FixedBytes(32)), - "send" => Some(DynSolType::Bool), - _ => None, - }, - "type" => match access { - "name" => Some(DynSolType::String), - "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), - "interfaceId" => Some(DynSolType::FixedBytes(4)), - "min" | "max" => Some( - // Either a builtin or an enum - (|| args?.pop()??.into_builtin())() - .unwrap_or(DynSolType::Uint(256)), - ), - _ => None, - }, - "string" => match access { - "concat" => Some(DynSolType::String), - _ => None, - }, - "bytes" => match access { - "concat" => Some(DynSolType::Bytes), - _ => None, - }, - _ => None, - } - } - _ => None, - } - }; - - this.map(Self::Builtin).unwrap_or_else(|| match types.last().unwrap().as_str() { - "this" | "super" => Self::Custom(types), - _ => match self { - Self::Custom(_) | Self::Access(_, _) => Self::Custom(types), - Self::Function(_, _, _) => self, - _ => unreachable!(), - }, - }) - } - - /// Recurses over itself, appending all the idents and function arguments in the order that they - /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { - match self { - Self::Builtin(ty) => types.push(ty.to_string()), - Self::Custom(tys) => types.extend(tys.clone()), - Self::Access(expr, name) => { - types.push(name.clone()); - expr.recurse(types, args); + }; } - Self::Function(fn_name, fn_args, _fn_ret) => { - if args.is_none() && !fn_args.is_empty() { - *args = Some(fn_args.clone()); + Some(Res::Item(ItemId::Function(fid))) if lookup => { + let func = gcx.hir.function(*fid); + if !matches!(func.state_mutability, StateMutability::View | StateMutability::Pure) { + return None; } - fn_name.recurse(types, args); + let ret_id = *func.returns.first()?; + return solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())); } _ => {} } } - /// Infers a custom type's true type by recursing up the parse tree - /// - /// ### Takes - /// - A reference to the [IntermediateOutput] - /// - An array of custom types generated by the `MemberAccess` arm of [Self::from_expression] - /// - An optional contract name. This should always be `None` when this function is first - /// called. - /// - /// ### Returns - /// - /// If successful, an `Ok(Some(DynSolType))` variant. - /// If gracefully failed, an `Ok(None)` variant. - /// If failed, an `Err(e)` variant. - fn infer_custom_type( - intermediate: &IntermediateOutput, - custom_type: &mut Vec, - contract_name: Option, - ) -> Result> { - if let Some("this") | Some("super") = custom_type.last().map(String::as_str) { - custom_type.pop(); + // Fall back to the callee's resolved type. + expr_to_dyn(gcx, callee, lookup) +} + +/// Extracts a name chain from a member-access expression tree for HIR lookup. +/// +/// The chain is ordered outermost-first so `a.b.c` produces `["c", "b", "a"]` with the root +/// identifier at the back. This matches the convention expected by [`infer_custom_type`]. +fn expr_name_chain(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option> { + match &expr.kind { + ExprKind::Ident(reses) => { + let res = reses.first()?; + let name = match *res { + Res::Item(ItemId::Variable(vid)) => gcx.hir.variable(vid).name?.name, + Res::Item(ItemId::Function(fid)) => gcx.hir.function(fid).name?.name, + Res::Item(ItemId::Contract(cid)) => gcx.hir.contract(cid).name.name, + Res::Builtin(b) => b.name(), + _ => return None, + }; + Some(vec![name]) } - if custom_type.is_empty() { - return Ok(None) + ExprKind::Member(lhs, ident) => { + let mut chain = expr_name_chain(gcx, lhs)?; + chain.insert(0, ident.name); + Some(chain) } + _ => None, + } +} - // If a contract exists with the given name, check its definitions for a match. - // Otherwise look in the `run` - if let Some(contract_name) = contract_name { - let intermediate_contract = intermediate - .intermediate_contracts - .get(&contract_name) - .ok_or_else(|| eyre::eyre!("Could not find intermediate contract!"))?; - - let cur_type = custom_type.last().unwrap(); - if let Some(func) = intermediate_contract.function_definitions.get(cur_type) { - // Check if the custom type is a function pointer member access - if let res @ Some(_) = func_members(func, custom_type) { - return Ok(res) - } - - // Because tuple types cannot be passed to `abi.encode`, we will only be - // receiving functions that have 0 or 1 return parameters here. - if func.returns.is_empty() { - eyre::bail!( - "This call expression does not return any values to inspect. Insert as statement." - ) - } +/// Infers a custom type's true type by recursing through the HIR. +/// +/// `custom_type` is a name chain ordered outermost-first (root at back). This is mutated during +/// resolution. `contract_id` narrows the search to a specific contract scope. +fn infer_custom_type( + gcx: Gcx<'_>, + custom_type: &mut Vec, + contract_id: Option, +) -> Result> { + if let Some(last) = custom_type.last() + && (last.as_str() == "this" || last.as_str() == "super") + { + custom_type.pop(); + } + if custom_type.is_empty() { + return Ok(None); + } - // Empty return types check is done above - let (_, param) = func.returns.first().unwrap(); - // Return type should always be present - let return_ty = ¶m.as_ref().unwrap().ty; - - // If the return type is a variable (not a type expression), re-enter the recursion - // on the same contract for a variable / struct search. It could be a contract, - // struct, array, etc. - if let pt::Expression::Variable(ident) = return_ty { - custom_type.push(ident.name.clone()); - return Self::infer_custom_type(intermediate, custom_type, Some(contract_name)) - } + if let Some(cid) = contract_id { + let hir = &gcx.hir; + let contract = hir.contract(cid); - // Check if our final function call alters the state. If it does, we bail so that it - // will be inserted normally without inspecting. If the state mutability was not - // expressly set, the function is inferred to alter state. - if let Some(pt::FunctionAttribute::Mutability(_mut)) = func - .attributes - .iter() - .find(|attr| matches!(attr, pt::FunctionAttribute::Mutability(_))) - { - if let pt::Mutability::Payable(_) = _mut { - eyre::bail!("This function mutates state. Insert as a statement.") - } - } else { - eyre::bail!("This function mutates state. Insert as a statement.") - } + let cur_name = *custom_type.last().unwrap(); + let cur = cur_name.as_str(); - Ok(Self::ethabi(return_ty, Some(intermediate))) - } else if let Some(var) = intermediate_contract.variable_definitions.get(cur_type) { - Self::infer_var_expr(&var.ty, Some(intermediate), custom_type) - } else if let Some(strukt) = intermediate_contract.struct_definitions.get(cur_type) { - let inner_types = strukt - .fields - .iter() - .map(|var| { - Self::ethabi(&var.ty, Some(intermediate)) - .ok_or_else(|| eyre::eyre!("Struct `{cur_type}` has invalid fields")) - }) - .collect::>>()?; - Ok(Some(DynSolType::Tuple(inner_types))) - } else { - eyre::bail!("Could not find any definition in contract \"{contract_name}\" for type: {custom_type:?}") - } - } else { - // Check if the custom type is a variable or function within the REPL contract before - // anything. If it is, we can stop here. - if let Ok(res) = Self::infer_custom_type(intermediate, custom_type, Some("REPL".into())) - { - return Ok(res) + // Function? + if let Some(fid) = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + let func = hir.function(fid); + if let res @ Some(_) = func_members(func, custom_type) { + return Ok(res); } - // Check if the first element of the custom type is a known contract. If it is, begin - // our recursion on that contract's definitions. - let name = custom_type.last().unwrap(); - let contract = intermediate.intermediate_contracts.get(name); - if contract.is_some() { - let contract_name = custom_type.pop(); - return Self::infer_custom_type(intermediate, custom_type, contract_name) + if func.returns.is_empty() { + eyre::bail!( + "This call expression does not return any values to inspect. Insert as statement." + ) } - // See [`Type::infer_var_expr`] - let name = custom_type.last().unwrap(); - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - return Self::infer_var_expr(expr, Some(intermediate), custom_type) + let sm = func.state_mutability; + if !matches!(sm, StateMutability::View | StateMutability::Pure) { + eyre::bail!("This function mutates state. Insert as a statement.") } - // The first element of our custom type was neither a variable or a function within the - // REPL contract, move on to globally available types gracefully. - Ok(None) + let ret_id = func.returns[0]; + let ret_var = hir.variable(ret_id); + return Ok(solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) + .or_else(|| hir_ty_to_dyn(gcx, &ret_var.ty))); } - } - /// Infers the type from a variable's type - fn infer_var_expr( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - custom_type: &mut Vec, - ) -> Result> { - // Resolve local (in `run` function) or global (in the `REPL` or other contract) variable - let res = match &expr { - // Custom variable handling - pt::Expression::Variable(ident) => { - let name = &ident.name; - - if let Some(intermediate) = intermediate { - // expression in `run` - if let Some(expr) = intermediate.repl_contract_expressions.get(name) { - Self::infer_var_expr(expr, Some(intermediate), custom_type) - } else if intermediate.intermediate_contracts.contains_key(name) { - if custom_type.len() > 1 { - // There is still some recursing left to do: jump into the contract. - custom_type.pop(); - Self::infer_custom_type(intermediate, custom_type, Some(name.clone())) - } else { - // We have no types left to recurse: return the address of the contract. - Ok(Some(DynSolType::Address)) - } - } else { - Err(eyre::eyre!("Could not infer variable type")) - } - } else { - Ok(None) - } - } - ty => Ok(Self::ethabi(ty, intermediate)), - }; - // re-run everything with the resolved variable in case we're accessing a builtin member - // for example array or bytes length etc - match res { - Ok(Some(ty)) => { - let box_ty = Box::new(Self::Builtin(ty.clone())); - let access = Self::Access(box_ty, custom_type.drain(..).next().unwrap_or_default()); - if let Some(mapped) = access.map_special().try_as_ethabi(intermediate) { - Ok(Some(mapped)) - } else { - Ok(Some(ty)) + // Variable? + if let Some(vid) = contract + .variables() + .find(|&v| hir.variable(v).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false)) + { + if let Some(ty) = solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) { + custom_type.pop(); + if custom_type.is_empty() { + return Ok(Some(ty)); } + let next_member = custom_type.drain(..).next().unwrap_or(Symbol::DUMMY); + return Ok(dyn_member(&ty, next_member.as_str()).or(Some(ty))); } - res => res, - } - } - - /// Attempt to convert this type into a [DynSolType] - /// - /// ### Takes - /// An immutable reference to an [IntermediateOutput] - /// - /// ### Returns - /// Optionally, a [DynSolType] - fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - Self::Tuple(types) => Some(DynSolType::Tuple(types_to_parameters(types, intermediate))), - Self::Array(inner) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::Array(Box::new(inner))), - }, - Self::FixedArray(inner, size) => match *inner { - ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => inner - .try_as_ethabi(intermediate) - .map(|inner| DynSolType::FixedArray(Box::new(inner), size)), - }, - ty @ Self::ArrayIndex(_, _) => ty.into_array_index(intermediate), - Self::Function(ty, _, _) => ty.try_as_ethabi(intermediate), - // should have been mapped to `Custom` in previous steps - Self::Access(_, _) => None, - Self::Custom(mut types) => { - // Cover any local non-state-modifying function call expressions - intermediate.and_then(|intermediate| { - Self::infer_custom_type(intermediate, &mut types, None).ok().flatten() - }) - } + let var = hir.variable(vid); + return infer_var_ty(gcx, &var.ty, custom_type); } - } - /// Equivalent to `Type::from_expression` + `Type::map_special` + `Type::try_as_ethabi` - fn ethabi( - expr: &pt::Expression, - intermediate: Option<&IntermediateOutput>, - ) -> Option { - Self::from_expression(expr) - .map(Self::map_special) - .and_then(|ty| ty.try_as_ethabi(intermediate)) - } - - /// Get the return type of a function call expression. - fn get_function_return_type<'a>( - contract_expr: Option<&'a pt::Expression>, - intermediate: &IntermediateOutput, - ) -> Option<(&'a pt::Expression, DynSolType)> { - let function_call = match contract_expr? { - pt::Expression::FunctionCall(_, function_call, _) => function_call, - _ => return None, - }; - let (contract_name, function_name) = match function_call.as_ref() { - pt::Expression::MemberAccess(_, contract_name, function_name) => { - (contract_name, function_name) + // Struct? + if let Some(sid) = contract.items.iter().find_map(|i| { + if let ItemId::Struct(sid) = i + && hir.strukt(*sid).name.as_str() == cur + { + Some(*sid) + } else { + None } - _ => return None, - }; - let contract_name = match contract_name.as_ref() { - pt::Expression::Variable(contract_name) => contract_name.to_owned(), - _ => return None, - }; - - let pt::Expression::Variable(contract_name) = - intermediate.repl_contract_expressions.get(&contract_name.name)? - else { - return None - }; - - let contract = intermediate - .intermediate_contracts - .get(&contract_name.name)? - .function_definitions - .get(&function_name.name)?; - let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) - } - - /// Inverts Int to Uint and vice-versa. - fn invert_int(self) -> Self { - match self { - Self::Builtin(DynSolType::Uint(n)) => Self::Builtin(DynSolType::Int(n)), - Self::Builtin(DynSolType::Int(n)) => Self::Builtin(DynSolType::Uint(n)), - x => x, + }) { + let inner = gcx + .struct_field_types(sid) + .iter() + .map(|&t| { + solar_ty_to_dyn(gcx, t) + .ok_or_else(|| eyre::eyre!("Struct `{cur}` has invalid fields")) + }) + .collect::>>()?; + return Ok(Some(DynSolType::Tuple(inner))); } - } - /// Returns the `DynSolType` contained by `Type::Builtin` - #[inline] - fn into_builtin(self) -> Option { - match self { - Self::Builtin(ty) => Some(ty), - _ => None, - } + eyre::bail!( + "Could not find any definition in contract \"{}\" for type: {custom_type:?}", + contract.name.as_str() + ) } - /// Returns the resulting `DynSolType` of indexing self - fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { - match self { - Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { - match inner.try_as_ethabi(intermediate) { - Some(DynSolType::Array(inner)) | Some(DynSolType::FixedArray(inner, _)) => { - Some(*inner) - } - Some(DynSolType::Bytes) | - Some(DynSolType::String) | - Some(DynSolType::FixedBytes(_)) => Some(DynSolType::FixedBytes(1)), - ty => ty, - } - } - _ => None, - } + let repl_id = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == "REPL").then_some(cid)); + if let Some(repl_id) = repl_id + && let Ok(res) = infer_custom_type(gcx, custom_type, Some(repl_id)) + { + return Ok(res); } - /// Returns whether this type is dynamic - #[inline] - fn is_dynamic(&self) -> bool { - match self { - // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are - // not dynamic, nor are tuples of non-dynamic types. - Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, - Self::Array(_) => true, - _ => false, - } + let last_name = *custom_type.last().unwrap(); + let last = last_name.as_str(); + let contract_match = gcx + .hir + .contracts_enumerated() + .find_map(|(cid, c)| (c.name.as_str() == last).then_some(cid)); + if let Some(cid) = contract_match { + custom_type.pop(); + return infer_custom_type(gcx, custom_type, Some(cid)); } - /// Returns whether this type is an array - #[inline] - fn is_array(&self) -> bool { - matches!( - self, - Self::Array(_) | - Self::FixedArray(_, _) | - Self::Builtin(DynSolType::Array(_)) | - Self::Builtin(DynSolType::FixedArray(_, _)) - ) - } + Ok(None) +} - /// Returns whether this type is a dynamic array (can call push, pop) - #[inline] - fn is_dynamic_array(&self) -> bool { - matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) +/// Infers the type from a variable's HIR type, optionally accessing a named member. +fn infer_var_ty( + gcx: Gcx<'_>, + ty: &HirType<'_>, + custom_type: &mut Vec, +) -> Result> { + let Some(ty) = hir_ty_to_dyn(gcx, ty) else { return Ok(None) }; + let next_member = custom_type.drain(..).next(); + if let Some(m) = next_member { + Ok(dyn_member(&ty, m.as_str()).or(Some(ty))) + } else { + Ok(Some(ty)) } +} - fn is_fixed_bytes(&self) -> bool { - matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) - } +/// Get the return type of a contract method call `receiver.method()`. +fn get_function_return_type(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option { + let ExprKind::Call(callee, _, _) = &expr.kind else { return None }; + let ExprKind::Member(obj, fn_ident) = &callee.kind else { return None }; + let ExprKind::Ident(reses) = &obj.kind else { return None }; + let res = reses.first()?; + let var_id = match res { + Res::Item(ItemId::Variable(vid)) => *vid, + _ => return None, + }; + let var_ty = gcx.type_of_item(var_id.into()).peel_refs(); + let cid = match var_ty.kind { + TyKind::Contract(cid) => cid, + _ => return None, + }; + + let hir = &gcx.hir; + let contract = hir.contract(cid); + let fid = contract + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some(fn_ident.as_str()))?; + let func = hir.function(fid); + let ret_id = *func.returns.first()?; + solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into())) } -/// Returns Some if the custom type is a function member access +/// Returns Some if the custom type is a function member access. /// /// Ref: #[inline] -fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { - if !matches!(func.ty, pt::FunctionTy::Function) { - return None +fn func_members(func: &Function<'_>, custom_type: &[Symbol]) -> Option { + if !matches!(func.kind, FunctionKind::Function) { + return None; } - - let vis = func.attributes.iter().find_map(|attr| match attr { - pt::FunctionAttribute::Visibility(vis) => Some(vis), - _ => None, - }); - match vis { - Some(pt::Visibility::External(_)) | Some(pt::Visibility::Public(_)) => { - match custom_type.first().unwrap().as_str() { - "address" => Some(DynSolType::Address), - "selector" => Some(DynSolType::FixedBytes(4)), - _ => None, - } - } + if !matches!(func.visibility, Visibility::External | Visibility::Public) { + return None; + } + match custom_type.first().unwrap().as_str() { + "address" => Some(DynSolType::Address), + "selector" => Some(DynSolType::FixedBytes(4)), _ => None, } } -/// Whether execution should continue after inspecting this expression +/// Whether execution should continue after inspecting this expression. #[inline] -fn should_continue(expr: &pt::Expression) -> bool { - match expr { - // assignments - pt::Expression::PreDecrement(_, _) | // -- - pt::Expression::PostDecrement(_, _) | // -- - pt::Expression::PreIncrement(_, _) | // ++ - pt::Expression::PostIncrement(_, _) | // ++ - pt::Expression::Assign(_, _, _) | // = ... - pt::Expression::AssignAdd(_, _, _) | // += ... - pt::Expression::AssignSubtract(_, _, _) | // -= ... - pt::Expression::AssignMultiply(_, _, _) | // *= ... - pt::Expression::AssignDivide(_, _, _) | // /= ... - pt::Expression::AssignModulo(_, _, _) | // %= ... - pt::Expression::AssignAnd(_, _, _) | // &= ... - pt::Expression::AssignOr(_, _, _) | // |= ... - pt::Expression::AssignXor(_, _, _) | // ^= ... - pt::Expression::AssignShiftLeft(_, _, _) | // <<= ... - pt::Expression::AssignShiftRight(_, _, _) // >>= ... - => { - true - } - +fn should_continue(expr: &Expr<'_>) -> bool { + match &expr.kind { + // assignments and compound assignments + ExprKind::Assign(_, _, _) => true, + // ++/-- pre/post operations + ExprKind::Unary(op, _) => matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ), // Array.pop() - pt::Expression::FunctionCall(_, lhs, _) => { - match lhs.as_ref() { - pt::Expression::MemberAccess(_, _inner, access) => access.name == "pop", - _ => false - } - } - - _ => false + ExprKind::Call(callee, _, _) => match &callee.kind { + ExprKind::Member(_, ident) => ident.as_str() == "pop", + _ => false, + }, + _ => false, } } -fn map_parameters(params: &[(pt::Loc, Option)]) -> Vec> { - params - .iter() - .map(|(_, param)| param.as_ref().and_then(|param| Type::from_expression(¶m.ty))) - .collect() +/// Parses an [`Expr`] number/hex literal into a `U256`. Returns `None` if the expression +/// is not a numeric literal. +/// +/// SubDenominations are already applied to numeric literals in solar's HIR. +const fn parse_number_literal(expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Lit(lit) => match &lit.kind { + LitKind::Number(n) => Some(*n), + _ => None, + }, + _ => None, + } } -fn types_to_parameters( - types: Vec>, - intermediate: Option<&IntermediateOutput>, -) -> Vec { - types.into_iter().filter_map(|ty| ty.and_then(|ty| ty.try_as_ethabi(intermediate))).collect() +/// Maps a solar [`ElementaryType`] to a [`DynSolType`]. +const fn elementary_to_dyn(et: ElementaryType) -> Option { + Some(match et { + ElementaryType::Address(_) => DynSolType::Address, + ElementaryType::Bool => DynSolType::Bool, + ElementaryType::String => DynSolType::String, + ElementaryType::Bytes => DynSolType::Bytes, + ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize), + ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize), + ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize), + // Fixed-point numbers are not yet representable as DynSolType. + ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None, + }) } -fn parse_number_literal(expr: &pt::Expression) -> Option { - match expr { - pt::Expression::NumberLiteral(_, num, exp, unit) => { - let num = U256::from_str(num).unwrap_or(U256::ZERO); - let exp = exp.parse().unwrap_or(0u32); - if exp > 77 { - None +/// Maps a solar [`Ty`] to a [`DynSolType`]. +fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option { + match ty.kind { + TyKind::Elementary(et) => elementary_to_dyn(et), + TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Array(elem, n) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + let size: usize = n.try_into().ok()?; + Some(DynSolType::FixedArray(Box::new(inner), size)) + } + TyKind::DynArray(elem) | TyKind::Slice(elem) => { + let inner = solar_ty_to_dyn(gcx, elem)?; + Some(DynSolType::Array(Box::new(inner))) + } + TyKind::Tuple(tys) => { + Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect())) + } + TyKind::Mapping(_, _) => None, + TyKind::Struct(sid) => Some(DynSolType::Tuple( + gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + TyKind::Enum(_) => Some(DynSolType::Uint(8)), + TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner), + TyKind::Contract(_) => Some(DynSolType::Address), + // For a function-pointer type we return the ABI type of what the call *produces*, not a + // representation of the pointer itself. This is intentional: chisel inspects values, so + // the interesting type is the returned value. A zero-return function pointer has no + // inspectable value, so we return `None`. + TyKind::FnPtr(f) => match f.returns.len() { + 0 => None, + 1 => solar_ty_to_dyn(gcx, f.returns[0]), + _ => Some(DynSolType::Tuple( + f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(), + )), + }, + TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner), + TyKind::IntLiteral(neg, size) => { + let bits = (size.bits() as usize).max(8); + // Round up to the nearest multiple of 8 bits, capped at 256. + let bits = bits.div_ceil(8) * 8; + let bits = bits.min(256); + if neg { + Some(DynSolType::Int(bits.max(8))) } else { - let exp = U256::from(10usize.pow(exp)); - let unit_mul = unit_multiplier(unit).ok()?; - Some(num * exp * unit_mul) + Some(DynSolType::Uint(bits.max(8))) } } - pt::Expression::HexNumberLiteral(_, num, unit) => { - let unit_mul = unit_multiplier(unit).ok()?; - num.parse::().map(|num| num * unit_mul).ok() + TyKind::StringLiteral(valid_utf8, _) => { + if valid_utf8 { + Some(DynSolType::String) + } else { + Some(DynSolType::Bytes) + } } - // TODO: Rational numbers - pt::Expression::RationalNumberLiteral(..) => None, + TyKind::Module(_) + | TyKind::BuiltinModule(_) + | TyKind::Error(_, _) + | TyKind::Event(_, _) + | TyKind::Err(_) => None, _ => None, } } -#[inline] -fn unit_multiplier(unit: &Option) -> Result { - if let Some(unit) = unit { - let mul = match unit.name.as_str() { - "seconds" => 1, - "minutes" => 60, - "hours" => 60 * 60, - "days" => 60 * 60 * 24, - "weeks" => 60 * 60 * 24 * 7, - "wei" => 1, - "gwei" => 10_usize.pow(9), - "ether" => 10_usize.pow(18), - other => eyre::bail!("unknown unit: {other}"), - }; - Ok(U256::from(mul)) - } else { - Ok(U256::from(1)) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Instruction { - pub pc: usize, - pub opcode: u8, - pub data: [u8; 32], - pub data_len: u8, -} - -struct InstructionIter<'a> { - bytes: &'a [u8], - offset: usize, -} - -impl<'a> InstructionIter<'a> { - pub fn new(bytes: &'a [u8]) -> Self { - Self { bytes, offset: 0 } - } -} - -impl Iterator for InstructionIter<'_> { - type Item = Instruction; - fn next(&mut self) -> Option { - let pc = self.offset; - self.offset += 1; - let opcode = *self.bytes.get(pc)?; - let (data, data_len) = if matches!(opcode, 0x60..=0x7F) { - let mut data = [0; 32]; - let data_len = (opcode - 0x60 + 1) as usize; - data[..data_len].copy_from_slice(&self.bytes[self.offset..self.offset + data_len]); - self.offset += data_len; - (data, data_len as u8) - } else { - ([0; 32], 0) - }; - Some(Instruction { pc, opcode, data, data_len }) - } -} - #[cfg(test)] mod tests { use super::*; use foundry_compilers::{error::SolcError, solc::Solc}; - use semver::Version; + use solar::sema::Compiler; use std::sync::Mutex; - #[test] - fn test_const() { - assert_eq!(USIZE_MAX_AS_U256.to::(), usize::MAX as u64); - assert_eq!(USIZE_MAX_AS_U256.to::(), usize::MAX as u64); - } - #[test] fn test_expressions() { static EXPRESSIONS: &[(&str, DynSolType)] = { @@ -1607,7 +1328,7 @@ mod tests { ("abi.encode(_, _)", Bytes), ("abi.encodePacked(_, _)", Bytes), ("abi.encodeWithSelector(bytes4, _, _)", Bytes), - ("abi.encodeCall(function(), (_, _))", Bytes), + ("abi.encodeCall(func(), (_, _))", Bytes), ("abi.encodeWithSignature(string, _, _)", Bytes), // @@ -1708,7 +1429,7 @@ mod tests { Ok((v, solc)) => { // successfully installed let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display()); - break + break; } Err(e) => { // try reinstalling @@ -1716,15 +1437,14 @@ mod tests { let solc = Solc::blocking_install(&version.parse().unwrap()); if solc.map_err(SolcError::from).is_ok() { *is_preinstalled = true; - break + break; } } } } } - let solc = Solc::find_or_install(&Version::new(0, 8, 19)).expect("could not install solc"); - SessionSource::new(solc, Default::default()) + SessionSource::new(Default::default()).unwrap() } fn array(ty: DynSolType) -> DynSolType { @@ -1735,51 +1455,66 @@ mod tests { DynSolType::FixedArray(Box::new(ty), len) } - fn parse(s: &mut SessionSource, input: &str, clear: bool) -> IntermediateOutput { + /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without + /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in + /// the run() body. + /// + /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so that + /// inputs which are syntactically valid but semantically rejected by solc (e.g. + /// `abi.decode(bytes, (uint8[13]))` or `a[0:3]` on a memory array) can still exercise the + /// HIR-based type-inference engine. + fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { if clear { - s.drain_run(); - s.drain_top_level_code(); - s.drain_global_code(); + s.clear(); } + // Always declare a sample enum so `Enum1` is available for `type(Enum1)` tests. *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; let input = format!("{};", input.trim_end().trim_end_matches(';')); - let (mut _s, _) = s.clone_with_new_line(input).unwrap(); - *s = _s.clone(); - let s = &mut _s; - - if let Err(e) = s.parse() { - for err in e { - let _ = sh_eprintln!("{}:{}: {}", err.loc.start(), err.loc.end(), err.message); + let (new_source, _) = s.clone_with_new_line(input).unwrap(); + *s = new_source.clone(); + + let src = new_source.to_repl_source(); + let sess = + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(sess); + + compiler.enter_mut(|c| -> Option { + // Stage 1: parse + lower (mutable access required). + let lowered = { + let mut pcx = c.parse(); + let file = c + .sess() + .source_map() + .new_source_file( + std::path::PathBuf::from(new_source.file_name.clone()), + src.clone(), + ) + .ok()?; + pcx.add_file(file); + pcx.parse(); + matches!(c.lower_asts(), Ok(ControlFlow::Continue(()))) + }; + if !lowered { + return None; } - let source = s.to_repl_source(); - panic!("could not parse input:\n{source}") - } - s.generate_intermediate_output().expect("could not generate intermediate output") - } - - fn expr(stmts: &[pt::Statement]) -> pt::Expression { - match stmts.last().expect("no statements") { - pt::Statement::Expression(_, e) => e.clone(), - s => panic!("Not an expression: {s:?}"), - } - } - - fn get_type( - s: &mut SessionSource, - input: &str, - clear: bool, - ) -> (Option, IntermediateOutput) { - let intermediate = parse(s, input, clear); - let run_func_body = intermediate.run_func_body().expect("no run func body"); - let expr = expr(run_func_body); - (Type::from_expression(&expr).map(Type::map_special), intermediate) - } - fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { - let (ty, intermediate) = get_type(s, input, clear); - ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) + // Stage 2: walk HIR (immutable access). + let gcx = c.gcx(); + let hir = &gcx.hir; + let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?; + let run_fid = repl + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?; + let body = hir.function(run_fid).body?; + let last = body.last()?; + let expr = match last.kind { + StmtKind::Expr(e) => e, + _ => return None, + }; + expr_to_dyn(gcx, expr, true) + }) } fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) @@ -1787,7 +1522,7 @@ mod tests { T: AsRef + std::fmt::Display + 'a, I: IntoIterator + 'a, { - for (input, expected) in input.into_iter() { + for (input, expected) in input { let input = input.as_ref(); let ty = get_type_ethabi(s, input, true); assert_eq!(ty.as_ref(), Some(expected), "\n{input}"); diff --git a/crates/chisel/src/history.rs b/crates/chisel/src/history.rs deleted file mode 100644 index a5dbba5b72948..0000000000000 --- a/crates/chisel/src/history.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! chisel history file - -use std::path::PathBuf; - -/// The name of the chisel history file -pub const CHISEL_HISTORY_FILE_NAME: &str = ".chisel_history"; - -/// Returns the path to foundry's global toml file that's stored at `~/.foundry/.chisel_history` -pub fn chisel_history_file() -> Option { - foundry_config::Config::foundry_dir().map(|p| p.join(CHISEL_HISTORY_FILE_NAME)) -} diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index b369dec13b1ea..5f8a99e7c83e7 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -1,23 +1,32 @@ //! Chisel is a fast, utilitarian, and verbose Solidity REPL. -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; +#[macro_use] +extern crate tracing; pub mod args; + pub mod cmd; + pub mod dispatcher; + pub mod executor; -pub mod history; + pub mod opts; + pub mod runner; + pub mod session; -pub mod session_source; -pub mod solidity_helper; + +pub mod source; + +mod solidity_helper; +pub use solidity_helper::SolidityHelper; pub mod prelude { - pub use crate::{ - cmd::*, dispatcher::*, runner::*, session::*, session_source::*, solidity_helper::*, - }; + pub use crate::{cmd::*, dispatcher::*, runner::*, session::*, solidity_helper::*, source::*}; } diff --git a/crates/chisel/src/opts.rs b/crates/chisel/src/opts.rs index c678b3895c6fc..40878592c7d04 100644 --- a/crates/chisel/src/opts.rs +++ b/crates/chisel/src/opts.rs @@ -1,13 +1,9 @@ use clap::{Parser, Subcommand}; -use foundry_cli::opts::{BuildOpts, GlobalArgs}; -use foundry_common::{ - evm::EvmArgs, - version::{LONG_VERSION, SHORT_VERSION}, -}; +use foundry_cli::opts::{BuildOpts, EvmArgs, GlobalArgs}; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::path::PathBuf; -// Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(Chisel, build, evm); +foundry_config::impl_figment_convert!(Chisel, build, evm); /// Fast, utilitarian, and verbose Solidity REPL. #[derive(Debug, Parser)] @@ -31,10 +27,17 @@ pub struct Chisel { #[arg(long, help_heading = "REPL options", long_help = format!( "Disable the default `Vm` import.\n\n\ The import is disabled by default if the Solc version is less than {}.", - crate::session_source::MIN_VM_VERSION + crate::source::MIN_VM_VERSION ))] pub no_vm: bool, + /// Enable viaIR with minimum optimization + /// + /// This can fix most of the "stack too deep" errors while resulting a + /// relatively accurate source map. + #[arg(long, help_heading = "REPL options")] + pub ir_minimum: bool, + #[command(flatten)] pub build: BuildOpts, @@ -45,25 +48,25 @@ pub struct Chisel { /// Chisel binary subcommands #[derive(Debug, Subcommand)] pub enum ChiselSubcommand { - /// List all cached sessions + /// List all cached sessions. List, - /// Load a cached session + /// Load a cached session. Load { /// The ID of the session to load. id: String, }, - /// View the source of a cached session + /// View the source of a cached session. View { /// The ID of the session to load. id: String, }, - /// Clear all cached chisel sessions from the cache directory + /// Clear all cached chisel sessions from the cache directory. ClearCache, - /// Simple evaluation of a command without entering the REPL + /// Simple evaluation of a command without entering the REPL. Eval { /// The command to be evaluated. command: String, diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index 72b083e1fb882..1b2a530378701 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -3,13 +3,13 @@ //! This module contains the `ChiselRunner` struct, which assists with deploying //! and calling the REPL contract on a in-memory REVM instance. -use alloy_primitives::{map::AddressHashMap, Address, Bytes, Log, U256}; +use alloy_primitives::{Address, Bytes, Log, U256, map::AddressHashMap}; use eyre::Result; use foundry_evm::{ + core::evm::EthEvmNetwork, executors::{DeployResult, Executor, RawCallResult}, traces::{TraceKind, Traces}, }; -use revm::interpreter::{return_ok, InstructionResult}; /// The function selector of the REPL contract's entrypoint, the `run()` function. static RUN_SELECTOR: [u8; 4] = [0xc0, 0x40, 0x62, 0x26]; @@ -21,7 +21,7 @@ static RUN_SELECTOR: [u8; 4] = [0xc0, 0x40, 0x62, 0x26]; #[derive(Debug)] pub struct ChiselRunner { /// The Executor - pub executor: Executor, + pub executor: Executor, /// An initial balance pub initial_balance: U256, /// The sender @@ -46,9 +46,9 @@ pub struct ChiselResult { /// Return data pub returned: Bytes, /// Called address - pub address: Option
, + pub address: Address, /// EVM State at the final instruction of the `run()` function - pub state: Option<(Vec, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec)>, } /// ChiselRunner implementation @@ -62,8 +62,8 @@ impl ChiselRunner { /// ### Returns /// /// A new [ChiselRunner] - pub fn new( - executor: Executor, + pub const fn new( + executor: Executor, initial_balance: U256, sender: Address, input: Option>, @@ -72,17 +72,7 @@ impl ChiselRunner { } /// Run a contract as a REPL session - /// - /// ### Takes - /// - /// The creation bytecode of the REPL contract - /// - /// ### Returns - /// - /// Optionally, a tuple containing the deployed address of the bytecode as well as a - /// [ChiselResult] containing information about the result of the call to the deployed REPL - /// contract. - pub fn run(&mut self, bytecode: Bytes) -> Result<(Address, ChiselResult)> { + pub fn run(&mut self, bytecode: Bytes) -> Result { // Set the sender's balance to [U256::MAX] for deployment of the REPL contract. self.executor.set_balance(self.sender, U256::MAX)?; @@ -102,112 +92,20 @@ impl ChiselRunner { calldata.append(&mut input); } - // Call the "run()" function of the REPL contract - let call_res = self.call(self.sender, address, Bytes::from(calldata), U256::from(0), true); + let res = self.executor.transact_raw(self.sender, address, calldata.into(), U256::ZERO)?; - call_res.map(|res| (address, res)) - } - - /// Executes the call. - /// - /// This will commit the changes if `commit` is true. - /// - /// This will return _estimated_ gas instead of the precise gas the call would consume, so it - /// can be used as `gas_limit`. - /// - /// Taken from Forge's script runner. - fn call( - &mut self, - from: Address, - to: Address, - calldata: Bytes, - value: U256, - commit: bool, - ) -> eyre::Result { - let fs_commit_changed = - if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes { - let original_fs_commit = cheatcodes.fs_commit; - cheatcodes.fs_commit = false; - original_fs_commit != cheatcodes.fs_commit - } else { - false - }; - - let mut res = self.executor.call_raw(from, to, calldata.clone(), value)?; - let mut gas_used = res.gas_used; - if matches!(res.exit_reason, return_ok!()) { - // store the current gas limit and reset it later - let init_gas_limit = self.executor.env().tx.gas_limit; - - // the executor will return the _exact_ gas value this transaction consumed, setting - // this value as gas limit will result in `OutOfGas` so to come up with a - // better estimate we search over a possible range we pick a higher gas - // limit 3x of a succeeded call should be safe - let mut highest_gas_limit = gas_used * 3; - let mut lowest_gas_limit = gas_used; - let mut last_highest_gas_limit = highest_gas_limit; - while (highest_gas_limit - lowest_gas_limit) > 1 { - let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env_mut().tx.gas_limit = mid_gas_limit; - let res = self.executor.call_raw(from, to, calldata.clone(), value)?; - match res.exit_reason { - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFunds => { - lowest_gas_limit = mid_gas_limit; - } - _ => { - highest_gas_limit = mid_gas_limit; - // if last two successful estimations only vary by 10%, we consider this to - // sufficiently accurate - const ACCURACY: u64 = 10; - if (last_highest_gas_limit - highest_gas_limit) * ACCURACY / - last_highest_gas_limit < - 1 - { - // update the gas - gas_used = highest_gas_limit; - break; - } - last_highest_gas_limit = highest_gas_limit; - } - } - } - // reset gas limit in the - self.executor.env_mut().tx.gas_limit = init_gas_limit; - } - - // if we changed `fs_commit` during gas limit search, re-execute the call with original - // value - if fs_commit_changed { - if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes { - cheatcodes.fs_commit = !cheatcodes.fs_commit; - } - - res = self.executor.call_raw(from, to, calldata.clone(), value)?; - } - - if commit { - // if explicitly requested we can now commit the call - res = self.executor.transact_raw(from, to, calldata, value)?; - } - - let RawCallResult { result, reverted, logs, traces, labels, chisel_state, .. } = res; + let RawCallResult { + result, reverted, logs, traces, labels, chisel_state, gas_used, .. + } = res; Ok(ChiselResult { returned: result, success: !reverted, gas_used, logs, - traces: traces - .map(|traces| { - // Manually adjust gas for the trace to add back the stipend/real used gas - - vec![(TraceKind::Execution, traces)] - }) - .unwrap_or_default(), + traces: traces.map(|traces| vec![(TraceKind::Execution, traces)]).unwrap_or_default(), labeled_addresses: labels, - address: None, + address, state: chisel_state, }) } diff --git a/crates/chisel/src/session.rs b/crates/chisel/src/session.rs index 2f293c1cd9172..fd487fc747439 100644 --- a/crates/chisel/src/session.rs +++ b/crates/chisel/src/session.rs @@ -7,13 +7,13 @@ use crate::prelude::{SessionSource, SessionSourceConfig}; use eyre::Result; use serde::{Deserialize, Serialize}; use std::path::Path; -use time::{format_description, OffsetDateTime}; +use time::{OffsetDateTime, format_description}; /// A Chisel REPL Session #[derive(Debug, Serialize, Deserialize)] pub struct ChiselSession { /// The `SessionSource` object that houses the REPL session. - pub session_source: SessionSource, + pub source: SessionSource, /// The current session's identifier pub id: Option, } @@ -30,9 +30,8 @@ impl ChiselSession { /// /// A new instance of [ChiselSession] pub fn new(config: SessionSourceConfig) -> Result { - let solc = config.solc()?; // Return initialized ChiselSession with set solc version - Ok(Self { session_source: SessionSource::new(solc, config), id: None }) + Ok(Self { source: SessionSource::new(config)?, id: None }) } /// Render the full source code for the current session. @@ -46,7 +45,7 @@ impl ChiselSession { /// This function will not panic, but will return a blank string if the /// session's [SessionSource] is None. pub fn contract_source(&self) -> String { - self.session_source.to_repl_source() + self.source.to_repl_source() } /// Clears the cache directory @@ -116,7 +115,7 @@ impl ChiselSession { let mut latest = if let Some(e) = entries.next() { e? } else { - return Ok((String::from("0"), format!("{cache_dir}chisel-0.json"))) + return Ok((String::from("0"), format!("{cache_dir}chisel-0.json"))); }; let mut session_num = 1; @@ -161,12 +160,8 @@ impl ChiselSession { Ok(()) } - /// Lists all available cached sessions - /// - /// ### Returns - /// - /// Optionally, a vector containing tuples of session IDs and cache-file names. - pub fn list_sessions() -> Result> { + /// Returns a list of all available cached sessions. + pub fn get_sessions() -> Result> { // Read the cache directory entries let cache_dir = Self::cache_dir()?; let entries = std::fs::read_dir(cache_dir)?; @@ -186,13 +181,7 @@ impl ChiselSession { file_name, )); } - - if sessions.is_empty() { - eyre::bail!("No sessions found!") - } else { - // Return the list of sessions and their modified times - Ok(sessions) - } + Ok(sessions) } /// Loads a specific ChiselSession from the specified cache file diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs deleted file mode 100644 index f6e36395c507e..0000000000000 --- a/crates/chisel/src/session_source.rs +++ /dev/null @@ -1,685 +0,0 @@ -//! Session Source -//! -//! This module contains the `SessionSource` struct, which is a minimal wrapper around -//! the REPL contract's source code. It provides simple compilation, parsing, and -//! execution helpers. - -use alloy_primitives::map::HashMap; -use eyre::Result; -use forge_fmt::solang_ext::SafeUnwrap; -use foundry_compilers::{ - artifacts::{CompilerOutput, Settings, SolcInput, Source, Sources}, - compilers::solc::Solc, -}; -use foundry_config::{Config, SolcReq}; -use foundry_evm::{backend::Backend, opts::EvmOpts}; -use semver::Version; -use serde::{Deserialize, Serialize}; -use solang_parser::{diagnostics::Diagnostic, pt}; -use std::{fs, path::PathBuf}; -use walkdir::WalkDir; -use yansi::Paint; - -/// The minimum Solidity version of the `Vm` interface. -pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); - -/// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) -static VM_SOURCE: &str = include_str!("../../../testdata/cheats/Vm.sol"); - -/// Intermediate output for the compiled [SessionSource] -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct IntermediateOutput { - /// All expressions within the REPL contract's run function and top level scope. - #[serde(skip)] - pub repl_contract_expressions: HashMap, - /// Intermediate contracts - #[serde(skip)] - pub intermediate_contracts: IntermediateContracts, -} - -/// A refined intermediate parse tree for a contract that enables easy lookups -/// of definitions. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct IntermediateContract { - /// All function definitions within the contract - #[serde(skip)] - pub function_definitions: HashMap>, - /// All event definitions within the contract - #[serde(skip)] - pub event_definitions: HashMap>, - /// All struct definitions within the contract - #[serde(skip)] - pub struct_definitions: HashMap>, - /// All variable definitions within the top level scope of the contract - #[serde(skip)] - pub variable_definitions: HashMap>, -} - -/// A defined type for a map of contract names to [IntermediateContract]s -type IntermediateContracts = HashMap; - -/// Full compilation output for the [SessionSource] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct GeneratedOutput { - /// The [IntermediateOutput] component - pub intermediate: IntermediateOutput, - /// The [CompilerOutput] component - pub compiler_output: CompilerOutput, -} - -/// Configuration for the [SessionSource] -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct SessionSourceConfig { - /// Foundry configuration - pub foundry_config: Config, - /// EVM Options - pub evm_opts: EvmOpts, - /// Disable the default `Vm` import. - pub no_vm: bool, - #[serde(skip)] - /// In-memory REVM db for the session's runner. - pub backend: Option, - /// Optionally enable traces for the REPL contract execution - pub traces: bool, - /// Optionally set calldata for the REPL contract execution - pub calldata: Option>, -} - -impl SessionSourceConfig { - /// Returns the solc version to use - /// - /// Solc version precedence - /// - Foundry configuration / `--use` flag - /// - Latest installed version via SVM - /// - Default: Latest 0.8.19 - pub(crate) fn solc(&self) -> Result { - let solc_req = if let Some(solc_req) = self.foundry_config.solc.clone() { - solc_req - } else if let Some(version) = Solc::installed_versions().into_iter().max() { - SolcReq::Version(version) - } else { - if !self.foundry_config.offline { - sh_print!("{}", "No solidity versions installed! ".green())?; - } - // use default - SolcReq::Version(Version::new(0, 8, 19)) - }; - - match solc_req { - SolcReq::Version(version) => { - let solc = if let Some(solc) = Solc::find_svm_installed_version(&version)? { - solc - } else { - if self.foundry_config.offline { - eyre::bail!("can't install missing solc {version} in offline mode") - } - sh_println!("{}", format!("Installing solidity version {version}...").green())?; - Solc::blocking_install(&version)? - }; - Ok(solc) - } - SolcReq::Local(solc) => { - if !solc.is_file() { - eyre::bail!("`solc` {} does not exist", solc.display()); - } - Ok(Solc::new(solc)?) - } - } - } -} - -/// REPL Session Source wrapper -/// -/// Heavily based on soli's [`ConstructedSource`](https://github.com/jpopesculian/soli/blob/master/src/main.rs#L166) -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SessionSource { - /// The file name - pub file_name: PathBuf, - /// The contract name - pub contract_name: String, - /// The solidity compiler version - pub solc: Solc, - /// Global level solidity code - /// - /// Typically, global-level code is present between the contract definition and the first - /// function (usually constructor) - pub global_code: String, - /// Top level solidity code - /// - /// Typically, this is code seen above the constructor - pub top_level_code: String, - /// Code existing within the "run()" function's scope - pub run_code: String, - /// The generated output - pub generated_output: Option, - /// Session Source configuration - pub config: SessionSourceConfig, -} - -impl SessionSource { - /// Creates a new source given a solidity compiler version - /// - /// # Panics - /// - /// If no Solc binary is set, cannot be found or the `--version` command fails - /// - /// ### Takes - /// - /// - An instance of [Solc] - /// - An instance of [SessionSourceConfig] - /// - /// ### Returns - /// - /// A new instance of [SessionSource] - #[track_caller] - pub fn new(solc: Solc, mut config: SessionSourceConfig) -> Self { - if solc.version < MIN_VM_VERSION && !config.no_vm { - tracing::info!(version=%solc.version, minimum=%MIN_VM_VERSION, "Disabling VM injection"); - config.no_vm = true; - } - - Self { - file_name: PathBuf::from("ReplContract.sol".to_string()), - contract_name: "REPL".to_string(), - solc, - config, - global_code: Default::default(), - top_level_code: Default::default(), - run_code: Default::default(), - generated_output: None, - } - } - - /// Clones a [SessionSource] without copying the [GeneratedOutput], as it will - /// need to be regenerated as soon as new code is added. - /// - /// ### Returns - /// - /// A shallow-cloned [SessionSource] - pub fn shallow_clone(&self) -> Self { - Self { - file_name: self.file_name.clone(), - contract_name: self.contract_name.clone(), - solc: self.solc.clone(), - global_code: self.global_code.clone(), - top_level_code: self.top_level_code.clone(), - run_code: self.run_code.clone(), - generated_output: None, - config: self.config.clone(), - } - } - - /// Clones the [SessionSource] and appends a new line of code. Will return - /// an error result if the new line fails to be parsed. - /// - /// ### Returns - /// - /// Optionally, a shallow-cloned [SessionSource] with the passed content appended to the - /// source code. - pub fn clone_with_new_line(&self, mut content: String) -> Result<(Self, bool)> { - let new_source = self.shallow_clone(); - if let Some(parsed) = parse_fragment(new_source.solc, new_source.config, &content) - .or_else(|| { - let new_source = self.shallow_clone(); - content.push(';'); - parse_fragment(new_source.solc, new_source.config, &content) - }) - .or_else(|| { - let new_source = self.shallow_clone(); - content = content.trim_end().trim_end_matches(';').to_string(); - parse_fragment(new_source.solc, new_source.config, &content) - }) - { - let mut new_source = self.shallow_clone(); - // Flag that tells the dispatcher whether to build or execute the session - // source based on the scope of the new code. - match parsed { - ParseTreeFragment::Function => new_source.with_run_code(&content), - ParseTreeFragment::Contract => new_source.with_top_level_code(&content), - ParseTreeFragment::Source => new_source.with_global_code(&content), - }; - - Ok((new_source, matches!(parsed, ParseTreeFragment::Function))) - } else { - eyre::bail!("\"{}\"", content.trim().to_owned()); - } - } - - // Fillers - - /// Appends global-level code to the source - pub fn with_global_code(&mut self, content: &str) -> &mut Self { - self.global_code.push_str(content.trim()); - self.global_code.push('\n'); - self.generated_output = None; - self - } - - /// Appends top-level code to the source - pub fn with_top_level_code(&mut self, content: &str) -> &mut Self { - self.top_level_code.push_str(content.trim()); - self.top_level_code.push('\n'); - self.generated_output = None; - self - } - - /// Appends code to the "run()" function - pub fn with_run_code(&mut self, content: &str) -> &mut Self { - self.run_code.push_str(content.trim()); - self.run_code.push('\n'); - self.generated_output = None; - self - } - - // Drains - - /// Clears global code from the source - pub fn drain_global_code(&mut self) -> &mut Self { - String::clear(&mut self.global_code); - self.generated_output = None; - self - } - - /// Clears top-level code from the source - pub fn drain_top_level_code(&mut self) -> &mut Self { - String::clear(&mut self.top_level_code); - self.generated_output = None; - self - } - - /// Clears the "run()" function's code - pub fn drain_run(&mut self) -> &mut Self { - String::clear(&mut self.run_code); - self.generated_output = None; - self - } - - /// Generates and [`SolcInput`] from the source. - /// - /// ### Returns - /// - /// A [`SolcInput`] object containing forge-std's `Vm` interface as well as the REPL contract - /// source. - pub fn compiler_input(&self) -> SolcInput { - let mut sources = Sources::new(); - sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); - - let remappings = self.config.foundry_config.get_all_remappings().collect::>(); - - // Include Vm.sol if forge-std remapping is not available - if !self.config.no_vm && !remappings.iter().any(|r| r.name.starts_with("forge-std")) { - sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); - } - - let settings = Settings { - remappings, - evm_version: self - .config - .foundry_config - .evm_version - .normalize_version_solc(&self.solc.version), - ..Default::default() - }; - - // we only care about the solidity source, so we can safely unwrap - SolcInput::resolve_and_build(sources, settings) - .into_iter() - .next() - .map(|i| i.sanitized(&self.solc.version)) - .expect("Solidity source not found") - } - - /// Compiles the source using [solang_parser] - /// - /// ### Returns - /// - /// A [pt::SourceUnit] if successful. - /// A vec of [solang_parser::diagnostics::Diagnostic]s if unsuccessful. - pub fn parse(&self) -> Result> { - solang_parser::parse(&self.to_repl_source(), 0).map(|(pt, _)| pt) - } - - /// Generate intermediate contracts for all contract definitions in the compilation source. - /// - /// ### Returns - /// - /// Optionally, a map of contract names to a vec of [IntermediateContract]s. - pub fn generate_intermediate_contracts(&self) -> Result> { - let mut res_map = HashMap::default(); - let parsed_map = self.compiler_input().sources; - for source in parsed_map.values() { - Self::get_intermediate_contract(&source.content, &mut res_map); - } - Ok(res_map) - } - - /// Generate intermediate output for the REPL contract - pub fn generate_intermediate_output(&self) -> Result { - // Parse generate intermediate contracts - let intermediate_contracts = self.generate_intermediate_contracts()?; - - // Construct variable definitions - let variable_definitions = intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find intermediate REPL contract!"))? - .variable_definitions - .clone() - .into_iter() - .map(|(k, v)| (k, v.ty)) - .collect::>(); - // Construct intermediate output - let mut intermediate_output = IntermediateOutput { - repl_contract_expressions: variable_definitions, - intermediate_contracts, - }; - - // Add all statements within the run function to the repl_contract_expressions map - for (key, val) in intermediate_output - .run_func_body()? - .clone() - .iter() - .flat_map(Self::get_statement_definitions) - { - intermediate_output.repl_contract_expressions.insert(key, val); - } - - Ok(intermediate_output) - } - - /// Compile the contract - /// - /// ### Returns - /// - /// Optionally, a [CompilerOutput] object that contains compilation artifacts. - pub fn compile(&self) -> Result { - // Compile the contract - let compiled = self.solc.compile_exact(&self.compiler_input())?; - - // Extract compiler errors - let errors = - compiled.errors.iter().filter(|error| error.severity.is_error()).collect::>(); - if !errors.is_empty() { - eyre::bail!( - "Compiler errors:\n{}", - errors.into_iter().map(|err| err.to_string()).collect::() - ); - } - - Ok(compiled) - } - - /// Builds the SessionSource from input into the complete CompiledOutput - /// - /// ### Returns - /// - /// Optionally, a [GeneratedOutput] object containing both the [CompilerOutput] and the - /// [IntermediateOutput]. - pub fn build(&mut self) -> Result { - // Compile - let compiler_output = self.compile()?; - - // Generate intermediate output - let intermediate_output = self.generate_intermediate_output()?; - - // Construct generated output - let generated_output = - GeneratedOutput { intermediate: intermediate_output, compiler_output }; - self.generated_output = Some(generated_output.clone()); // ehhh, need to not clone this. - Ok(generated_output) - } - - /// Convert the [SessionSource] to a valid Script contract - /// - /// ### Returns - /// - /// The [SessionSource] represented as a Forge Script contract. - pub fn to_script_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version; - let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; - - let script_import = - if !config.no_vm { "import {Script} from \"forge-std/Script.sol\";\n" } else { "" }; - - format!( - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^{major}.{minor}.{patch}; - -{script_import} -{global_code} - -contract {contract_name} is Script {{ - {top_level_code} - - /// @notice Script entry point - function run() public {{ - {run_code} - }} -}}"#, - ) - } - - /// Convert the [SessionSource] to a valid REPL contract - /// - /// ### Returns - /// - /// The [SessionSource] represented as a REPL contract. - pub fn to_repl_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version; - let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; - let (mut vm_import, mut vm_constant) = (String::new(), String::new()); - if !config.no_vm { - // Check if there's any `forge-std` remapping and determine proper path to it by - // searching remapping path. - if let Some(remapping) = config - .foundry_config - .remappings - .iter() - .find(|remapping| remapping.name == "forge-std/") - { - if let Some(vm_path) = WalkDir::new(&remapping.path.path) - .into_iter() - .filter_map(|e| e.ok()) - .find(|e| e.file_name() == "Vm.sol") - { - vm_import = format!("import {{Vm}} from \"{}\";\n", vm_path.path().display()); - vm_constant = "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n".to_string(); - } - } - } - - format!( - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^{major}.{minor}.{patch}; - -{vm_import} -{global_code} - -contract {contract_name} {{ - {vm_constant} - {top_level_code} - - /// @notice REPL contract entry point - function run() public {{ - {run_code} - }} -}}"#, - ) - } - - /// Gets the [IntermediateContract] for a Solidity source string and inserts it into the - /// passed `res_map`. In addition, recurses on any imported files as well. - /// - /// ### Takes - /// - `content` - A Solidity source string - /// - `res_map` - A mutable reference to a map of contract names to [IntermediateContract]s - pub fn get_intermediate_contract( - content: &str, - res_map: &mut HashMap, - ) { - if let Ok((pt::SourceUnit(source_unit_parts), _)) = solang_parser::parse(content, 0) { - let func_defs = source_unit_parts - .into_iter() - .filter_map(|sup| match sup { - pt::SourceUnitPart::ImportDirective(i) => match i { - pt::Import::Plain(s, _) | - pt::Import::Rename(s, _, _) | - pt::Import::GlobalSymbol(s, _, _) => { - let s = match s { - pt::ImportPath::Filename(s) => s.string, - pt::ImportPath::Path(p) => p.to_string(), - }; - let path = PathBuf::from(s); - - match fs::read_to_string(path) { - Ok(source) => { - Self::get_intermediate_contract(&source, res_map); - None - } - Err(_) => None, - } - } - }, - pt::SourceUnitPart::ContractDefinition(cd) => { - let mut intermediate = IntermediateContract::default(); - - cd.parts.into_iter().for_each(|part| match part { - pt::ContractPart::FunctionDefinition(def) => { - // Only match normal function definitions here. - if matches!(def.ty, pt::FunctionTy::Function) { - intermediate - .function_definitions - .insert(def.name.clone().unwrap().name, def); - } - } - pt::ContractPart::EventDefinition(def) => { - let event_name = def.name.safe_unwrap().name.clone(); - intermediate.event_definitions.insert(event_name, def); - } - pt::ContractPart::StructDefinition(def) => { - let struct_name = def.name.safe_unwrap().name.clone(); - intermediate.struct_definitions.insert(struct_name, def); - } - pt::ContractPart::VariableDefinition(def) => { - let var_name = def.name.safe_unwrap().name.clone(); - intermediate.variable_definitions.insert(var_name, def); - } - _ => {} - }); - Some((cd.name.safe_unwrap().name.clone(), intermediate)) - } - _ => None, - }) - .collect::>(); - res_map.extend(func_defs); - } - } - - /// Helper to deconstruct a statement - /// - /// ### Takes - /// - /// A reference to a [pt::Statement] - /// - /// ### Returns - /// - /// A vector containing tuples of the inner expressions' names, types, and storage locations. - pub fn get_statement_definitions(statement: &pt::Statement) -> Vec<(String, pt::Expression)> { - match statement { - pt::Statement::VariableDefinition(_, def, _) => { - vec![(def.name.safe_unwrap().name.clone(), def.ty.clone())] - } - pt::Statement::Expression(_, pt::Expression::Assign(_, left, _)) => { - if let pt::Expression::List(_, list) = left.as_ref() { - list.iter() - .filter_map(|(_, param)| { - param.as_ref().and_then(|param| { - param - .name - .as_ref() - .map(|name| (name.name.clone(), param.ty.clone())) - }) - }) - .collect() - } else { - Vec::default() - } - } - _ => Vec::default(), - } - } -} - -impl IntermediateOutput { - /// Helper function that returns the body of the REPL contract's "run" function. - /// - /// ### Returns - /// - /// Optionally, the last statement within the "run" function of the REPL contract. - pub fn run_func_body(&self) -> Result<&Vec> { - match self - .intermediate_contracts - .get("REPL") - .ok_or_else(|| eyre::eyre!("Could not find REPL intermediate contract!"))? - .function_definitions - .get("run") - .ok_or_else(|| eyre::eyre!("Could not find run function definition in REPL contract!"))? - .body - .as_ref() - .ok_or_else(|| eyre::eyre!("Could not find run function body!"))? - { - pt::Statement::Block { statements, .. } => Ok(statements), - _ => eyre::bail!("Could not find statements within run function body!"), - } - } -} - -/// A Parse Tree Fragment -/// -/// Used to determine whether an input will go to the "run()" function, -/// the top level of the contract, or in global scope. -#[derive(Debug)] -pub enum ParseTreeFragment { - /// Code for the global scope - Source, - /// Code for the top level of the contract - Contract, - /// Code for the "run()" function - Function, -} - -/// Parses a fragment of solidity code with solang_parser and assigns -/// it a scope within the [SessionSource]. -pub fn parse_fragment( - solc: Solc, - config: SessionSourceConfig, - buffer: &str, -) -> Option { - let mut base = SessionSource::new(solc, config); - - match base.clone().with_run_code(buffer).parse() { - Ok(_) => return Some(ParseTreeFragment::Function), - Err(e) => debug_errors(&e), - } - match base.clone().with_top_level_code(buffer).parse() { - Ok(_) => return Some(ParseTreeFragment::Contract), - Err(e) => debug_errors(&e), - } - match base.with_global_code(buffer).parse() { - Ok(_) => return Some(ParseTreeFragment::Source), - Err(e) => debug_errors(&e), - } - - None -} - -fn debug_errors(errors: &[Diagnostic]) { - if !tracing::enabled!(tracing::Level::DEBUG) { - return; - } - - for error in errors { - tracing::debug!("error: {}", error.message); - } -} diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 5087e7b47c377..c3212f8e89962 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -1,25 +1,24 @@ -//! SolidityHelper -//! //! This module contains the `SolidityHelper`, a [rustyline::Helper] implementation for -//! usage in Chisel. It is ported from [soli](https://github.com/jpopesculian/soli/blob/master/src/main.rs). +//! usage in Chisel. It was originally ported from [soli](https://github.com/jpopesculian/soli/blob/master/src/main.rs). use crate::{ dispatcher::PROMPT_ARROW, - prelude::{ChiselCommand, COMMAND_LEADER, PROMPT_ARROW_STR}, + prelude::{COMMAND_LEADER, ChiselCommand, PROMPT_ARROW_STR}, }; use rustyline::{ + Helper, completion::Completer, highlight::{CmdKind, Highlighter}, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, - Helper, }; -use solar_parse::{ - interface::{Session, SessionGlobals}, - token::{Token, TokenKind}, - Lexer, +use solar::parse::{ + Cursor, Lexer, + interface::Session, + lexer::token::{RawLiteralKind, RawTokenKind}, + token::Token, }; -use std::{borrow::Cow, ops::Range, str::FromStr}; +use std::{borrow::Cow, cell::RefCell, fmt, ops::Range, rc::Rc}; use yansi::{Color, Style}; /// The maximum length of an ANSI prefix + suffix characters using [SolidityHelper]. @@ -31,13 +30,17 @@ use yansi::{Color, Style}; /// * 4 - suffix: `\x1B[0m` const MAX_ANSI_LEN: usize = 9; -/// A rustyline helper for Solidity code +/// A rustyline helper for Solidity code. +#[derive(Clone)] pub struct SolidityHelper { + inner: Rc>, +} + +struct Inner { errored: bool, do_paint: bool, sess: Session, - globals: SessionGlobals, } impl Default for SolidityHelper { @@ -46,36 +49,47 @@ impl Default for SolidityHelper { } } +impl fmt::Debug for SolidityHelper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let this = self.inner.borrow(); + f.debug_struct("SolidityHelper") + .field("errored", &this.errored) + .field("do_paint", &this.do_paint) + .finish_non_exhaustive() + } +} + impl SolidityHelper { /// Create a new SolidityHelper. pub fn new() -> Self { Self { - errored: false, - do_paint: yansi::is_enabled(), - sess: Session::builder().with_silent_emitter(None).build(), - globals: SessionGlobals::new(), + inner: Rc::new(RefCell::new(Inner { + errored: false, + do_paint: yansi::is_enabled(), + sess: Session::builder().with_silent_emitter(None).build(), + })), } } /// Returns whether the helper is in an errored state. pub fn errored(&self) -> bool { - self.errored + self.inner.borrow().errored } /// Set the errored field. pub fn set_errored(&mut self, errored: bool) -> &mut Self { - self.errored = errored; + self.inner.borrow_mut().errored = errored; self } /// Highlights a Solidity source string. pub fn highlight<'a>(&self, input: &'a str) -> Cow<'a, str> { if !self.do_paint() { - return Cow::Borrowed(input) + return Cow::Borrowed(input); } // Highlight commands separately - if input.starts_with(COMMAND_LEADER) { + if let Some(full_cmd) = input.strip_prefix(COMMAND_LEADER) { let (cmd, rest) = match input.split_once(' ') { Some((cmd, rest)) => (cmd, Some(rest)), None => (input, None), @@ -86,7 +100,7 @@ impl SolidityHelper { // cmd out.push(COMMAND_LEADER); - let cmd_res = ChiselCommand::from_str(cmd); + let cmd_res = ChiselCommand::parse(full_cmd); let style = (if cmd_res.is_ok() { Color::Green } else { Color::Red }).foreground(); Self::paint_unchecked(cmd, style, &mut out); @@ -133,25 +147,46 @@ impl SolidityHelper { /// Validate that a source snippet is closed (i.e., all braces and parenthesis are matched). fn validate_closed(&self, input: &str) -> ValidationResult { - let mut depth = [0usize; 3]; - self.enter(|sess| { - for token in Lexer::new(sess, input) { - match token.kind { - TokenKind::OpenDelim(delim) => { - depth[delim as usize] += 1; + use RawLiteralKind::Str; + use RawTokenKind::{BlockComment, CloseDelim, Literal, OpenDelim}; + let mut stack = vec![]; + for token in Cursor::new(input) { + match token.kind { + OpenDelim(delim) => stack.push(delim), + CloseDelim(delim) => match (stack.pop(), delim) { + (Some(open), close) if open == close => {} + (Some(wanted), _) => { + let wanted = wanted.to_open_str(); + return ValidationResult::Invalid(Some(format!( + "Mismatched brackets: `{wanted}` is not properly closed" + ))); } - TokenKind::CloseDelim(delim) => { - depth[delim as usize] = depth[delim as usize].saturating_sub(1); + (None, c) => { + let c = c.to_close_str(); + return ValidationResult::Invalid(Some(format!( + "Mismatched brackets: `{c}` is unpaired" + ))); } - _ => {} + }, + + Literal { kind: Str { terminated, .. } } if !terminated => { + return ValidationResult::Incomplete; + } + + BlockComment { terminated, .. } if !terminated => { + return ValidationResult::Incomplete; } + + _ => {} } - }); - if depth == [0; 3] { - ValidationResult::Valid(None) - } else { - ValidationResult::Incomplete } + + // There are open brackets that are not properly closed. + if !stack.is_empty() { + return ValidationResult::Incomplete; + } + + ValidationResult::Valid(None) } /// Formats `input` with `style` into `out`, without checking `style.wrapping` or @@ -174,12 +209,13 @@ impl SolidityHelper { /// Returns whether to color the output. fn do_paint(&self) -> bool { - self.do_paint + self.inner.borrow().do_paint } /// Enters the session. fn enter(&self, f: impl FnOnce(&Session)) { - self.globals.set(|| self.sess.enter(|| f(&self.sess))); + let this = self.inner.borrow(); + this.sess.enter_sequential(|| f(&this.sess)); } } @@ -198,7 +234,7 @@ impl Highlighter for SolidityHelper { _default: bool, ) -> Cow<'b, str> { if !self.do_paint() { - return Cow::Borrowed(prompt) + return Cow::Borrowed(prompt); } let mut out = prompt.to_string(); @@ -217,7 +253,7 @@ impl Highlighter for SolidityHelper { if let Some(i) = out.find(PROMPT_ARROW) { let style = - if self.errored { Color::Red.foreground() } else { Color::Green.foreground() }; + if self.errored() { Color::Red.foreground() } else { Color::Green.foreground() }; out.replace_range(i..=i + 2, &Self::paint_unchecked_owned(PROMPT_ARROW_STR, style)); } @@ -244,9 +280,12 @@ impl Helper for SolidityHelper {} #[expect(non_upper_case_globals)] #[deny(unreachable_patterns)] fn token_style(token: &Token) -> Style { - use solar_parse::{ + use solar::parse::{ interface::kw::*, - token::{TokenKind::*, TokenLitKind::*}, + token::{ + TokenKind::{Arrow, Comment, FatArrow, Ident, Literal}, + TokenLitKind::{HexStr, Str, UnicodeStr}, + }, }; match token.kind { @@ -254,9 +293,9 @@ fn token_style(token: &Token) -> Style { Literal(..) => Color::Yellow.foreground(), Ident( - Memory | Storage | Calldata | Public | Private | Internal | External | Constant | - Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | - Modifier | Immutable | Unchecked, + Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure + | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | Modifier + | Immutable | Unchecked, ) => Color::Cyan.foreground(), Ident(s) if s.is_elementary_type() => Color::Blue.foreground(), @@ -270,3 +309,64 @@ fn token_style(token: &Token) -> Style { _ => Color::Primary.foreground(), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validate() { + let helper = SolidityHelper::new(); + let dbg_r = |r: ValidationResult| match r { + ValidationResult::Incomplete => "Incomplete".to_string(), + ValidationResult::Invalid(inner) => format!("Invalid({inner:?})"), + ValidationResult::Valid(inner) => format!("Valid({inner:?})"), + _ => "Unknown result".to_string(), + }; + let valid = |input: &str| { + let r = helper.validate_closed(input); + assert!(matches!(r, ValidationResult::Valid(None)), "{input:?}: {}", dbg_r(r)) + }; + let incomplete = |input: &str| { + let r = helper.validate_closed(input); + assert!(matches!(r, ValidationResult::Incomplete), "{input:?}: {}", dbg_r(r)) + }; + let invalid = |input: &str| { + let r = helper.validate_closed(input); + assert!(matches!(r, ValidationResult::Invalid(Some(_))), "{input:?}: {}", dbg_r(r)) + }; + + valid("1"); + valid("1 + 2"); + + valid("()"); + valid("{}"); + valid("[]"); + + incomplete("("); + incomplete("(("); + incomplete("["); + incomplete("{"); + incomplete("({"); + valid("({})"); + + invalid(")"); + invalid("]"); + invalid("}"); + invalid("(}"); + invalid("(})"); + invalid("[}"); + invalid("[}]"); + + incomplete("\""); + incomplete("\'"); + valid("\"\""); + valid("\'\'"); + + incomplete("/*"); + incomplete("/*/*"); + valid("/* */"); + valid("/* /* */"); + valid("/* /* */ */"); + } +} diff --git a/crates/chisel/src/source.rs b/crates/chisel/src/source.rs new file mode 100644 index 0000000000000..90c0bad874622 --- /dev/null +++ b/crates/chisel/src/source.rs @@ -0,0 +1,613 @@ +//! Session Source +//! +//! This module contains the `SessionSource` struct, which is a minimal wrapper around +//! the REPL contract's source code. It provides simple compilation, parsing, and +//! execution helpers. + +use eyre::Result; +use foundry_compilers::{ + Artifact, ProjectCompileOutput, + artifacts::{ConfigurableContractArtifact, Source, Sources}, + project::ProjectCompiler, + solc::Solc, +}; +use foundry_config::{Config, SolcReq}; +use foundry_evm::{backend::Backend, core::bytecode::InstIter, opts::EvmOpts}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use solar::{ + ast::{ItemKind, StmtKind as AstStmtKind, yul}, + interface::{Span, diagnostics::EmittedDiagnostics}, + sema::{ + CompilerRef, + hir::{Block, Contract, EventId, ItemId, Stmt, StmtKind as HirStmtKind}, + ty::Gcx, + }, +}; +use std::{cell::OnceCell, fmt}; +use walkdir::WalkDir; + +/// The minimum Solidity version of the `Vm` interface. +pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); + +/// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) +static VM_SOURCE: &str = include_str!("../../../testdata/utils/Vm.sol"); + +/// [`SessionSource`] build output. +pub struct GeneratedOutput { + output: ProjectCompileOutput, +} + +impl fmt::Debug for GeneratedOutput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GeneratedOutput").finish_non_exhaustive() + } +} + +impl GeneratedOutput { + /// Enters the solar compiler context, providing access to the HIR and `Gcx`. + pub fn enter( + &self, + f: impl for<'a, 'b, 'gcx> FnOnce(GeneratedOutputRef<'a, 'b, 'gcx>) -> R + Send, + ) -> R { + self.output + .parser() + .solc() + .compiler() + .enter(|c| f(GeneratedOutputRef { output: &self.output, compiler: c })) + } +} + +/// A scoped reference to a [`GeneratedOutput`] together with an entered solar compiler. +pub struct GeneratedOutputRef<'a, 'b, 'gcx> { + output: &'a ProjectCompileOutput, + pub(crate) compiler: &'b CompilerRef<'gcx>, +} + +impl<'gcx> GeneratedOutputRef<'_, '_, 'gcx> { + pub fn gcx(&self) -> Gcx<'gcx> { + self.compiler.gcx() + } + + pub fn repl_contract(&self) -> Option<&ConfigurableContractArtifact> { + self.output.find_first("REPL") + } + + /// Looks up the REPL contract in the HIR. + pub fn repl_contract_hir(&self) -> Option<&'gcx Contract<'gcx>> { + self.gcx().hir.contracts().find(|c| c.name.as_str() == "REPL") + } + + /// Returns the body block of the REPL `run()` function. + pub fn run_func_body(&self) -> Block<'gcx> { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir().expect("REPL contract not found in HIR"); + let f = c + .functions() + .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run")) + .expect("`run()` function not found in REPL contract"); + hir.function(f).body.expect("`run()` function does not have a body") + } + + /// Returns the [`EventId`] of an event named `input` in the REPL contract, if any. + pub fn get_event(&self, input: &str) -> Option { + let hir = &self.gcx().hir; + let c = self.repl_contract_hir()?; + c.items.iter().find_map(|id| { + if let ItemId::Event(eid) = id + && hir.event(*eid).name.as_str() == input + { + Some(*eid) + } else { + None + } + }) + } + + pub fn final_pc(&self, contract: &ConfigurableContractArtifact) -> Result> { + let deployed_bytecode = contract + .get_deployed_bytecode() + .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; + let deployed_bytecode_bytes = deployed_bytecode + .bytes() + .ok_or_else(|| eyre::eyre!("No deployed bytecode found for `REPL` contract"))?; + + // Fetch the run function's body statement + let run_body = self.run_func_body(); + + // Record loc of first yul block return statement (if any). + // This is used to decide which is the final statement within the `run()` method. + // see . + // + // Yul is not yet lowered to HIR (assembly statements appear as `StmtKind::Err`), + // so we walk the AST of the REPL source to find a top-level `return(...)` call + // inside any `assembly { ... }` block in `run()`. + let last_yul_return_span: Option = self.first_yul_return_span(); + + // Find the last statement within the "run()" method and get the program + // counter via the source map. + let Some(last_stmt) = run_body.last() else { return Ok(None) }; + + // If the final statement is some type of block (unchecked or regular), + // we need to find the final statement within that block. Otherwise, default to + // the source loc of the final statement of the `run()` function's block. + // + // Inline assembly blocks (lowered to `StmtKind::Err` in HIR in the pinned solar + // version) are handled separately via `trailing_assembly_last_stmt_span`, which + // walks the AST to recover the last meaningful Yul statement. + let source_stmt = match &last_stmt.kind { + HirStmtKind::UncheckedBlock(stmts) | HirStmtKind::Block(stmts) => { + if let Some(stmt) = stmts.last() { + stmt + } else { + // In the case where the block is empty, attempt to grab the statement + // before the block. Because we use saturating sub to get the second to + // last index, this can always be safely unwrapped. + &run_body[run_body.len().saturating_sub(2)] + } + } + _ => last_stmt, + }; + // If the trailing statement is an assembly block, prefer the last meaningful + // (non-`let`) Yul statement's span as the source location for `final_pc`. + // See . + // + // Two guards are required: + // 1. `StmtKind::Err`, assembly lowers to an error node in the current pinned solar + // version; this ensures we don't apply the AST fallback to properly-lowered stmts. + // 2. `trailing_assembly_last_stmt_span` returning `Some`, verifies via the AST that the + // failing HIR node actually corresponds to an assembly block (not some other lowering + // failure), and supplies the concrete span to use. + let mut source_span = if matches!(last_stmt.kind, HirStmtKind::Err(_)) + && let Some(span) = self.trailing_assembly_last_stmt_span() + { + span + } else { + self.stmt_span_without_semicolon(source_stmt) + }; + + // Consider yul return statement as final statement (if it's loc is lower). + if let Some(yul_return_span) = last_yul_return_span + && yul_return_span.hi() < source_span.lo() + { + source_span = yul_return_span; + } + + // Map the source location of the final statement of the `run()` function to its + // corresponding runtime program counter + let result = self + .compiler + .sess() + .source_map() + .span_to_source(source_span) + .map_err(|e| eyre::eyre!("failed to resolve span: {e:?}"))?; + let range = result.data; + let offset = range.start as u32; + let length = range.len() as u32; + trace!(%offset, %length, "find pc"); + let final_pc = contract + .get_source_map_deployed() + .ok_or_else(|| eyre::eyre!("No source map found for `REPL` contract"))?? + .into_iter() + .zip(InstIter::new(deployed_bytecode_bytes).with_pc().map(|(pc, _)| pc)) + .filter(|(s, _)| s.offset() == offset && s.length() == length) + .map(|(_, pc)| pc) + .max(); + trace!(?final_pc); + Ok(final_pc) + } + + /// Statements' ranges in the solc source map do not include the semicolon. + fn stmt_span_without_semicolon(&self, stmt: &Stmt<'_>) -> Span { + match stmt.kind { + HirStmtKind::DeclSingle(id) => { + let decl = self.gcx().hir.variable(id); + if let Some(expr) = decl.initializer { + stmt.span.with_hi(expr.span.hi()) + } else { + stmt.span + } + } + HirStmtKind::DeclMulti(_, expr) => stmt.span.with_hi(expr.span.hi()), + HirStmtKind::Expr(expr) => expr.span, + _ => stmt.span, + } + } + + /// Returns the AST `run()` body of the REPL contract, if any. + /// + /// Yul/assembly is not yet lowered to HIR in the pinned solar version, so we + /// keep around the AST to be able to inspect inline assembly blocks. + fn repl_run_ast_body(&self) -> Option<&'gcx solar::ast::Block<'gcx>> { + let contract = self.repl_contract_hir()?; + let source = self.gcx().sources.get(contract.source)?; + let ast = source.ast.as_ref()?; + + let contract_ast = ast.items.iter().find_map(|i| match &i.kind { + ItemKind::Contract(c) if c.name.as_str() == "REPL" => Some(c), + _ => None, + })?; + contract_ast.body.iter().find_map(|i| match &i.kind { + ItemKind::Function(f) if f.header.name.is_some_and(|n| n.as_str() == "run") => { + f.body.as_ref() + } + _ => None, + }) + } + + /// Returns the span of the first top-level `return(...)` call inside any + /// `assembly { ... }` block in the REPL `run()` function, if any. + fn first_yul_return_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + for stmt in run_body.stmts.iter() { + let AstStmtKind::Assembly(asm) = &stmt.kind else { continue }; + for ystmt in asm.block.stmts.iter() { + if let yul::StmtKind::Expr(e) = &ystmt.kind + && let yul::ExprKind::Call(call) = &e.kind + && call.name.as_str() == "return" + { + return Some(ystmt.span); + } + } + } + None + } + + /// If the last statement of the REPL `run()` function is an `assembly { ... }` block, + /// returns the span of its last non-`let` (i.e. non-VarDecl) Yul statement. + /// + /// This mirrors the legacy behavior used to pick a meaningful end-of-function PC when + /// the trailing statement is inline assembly. + fn trailing_assembly_last_stmt_span(&self) -> Option { + let run_body = self.repl_run_ast_body()?; + let AstStmtKind::Assembly(asm) = &run_body.stmts.last()?.kind else { return None }; + asm.block + .stmts + .iter() + .rev() + .find(|s| !matches!(s.kind, yul::StmtKind::VarDecl(_, _))) + .map(|s| s.span) + } +} + +/// Configuration for the [SessionSource] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct SessionSourceConfig { + /// Foundry configuration + pub foundry_config: Config, + /// EVM Options + pub evm_opts: EvmOpts, + /// Disable the default `Vm` import. + pub no_vm: bool, + /// In-memory REVM db for the session's runner. + #[serde(skip)] + pub backend: Option, + /// Optionally enable traces for the REPL contract execution + pub traces: bool, + /// Optionally set calldata for the REPL contract execution + pub calldata: Option>, + /// Enable viaIR with minimum optimization + /// + /// This can fix most of the "stack too deep" errors while resulting a + /// relatively accurate source map. + pub ir_minimum: bool, +} + +impl SessionSourceConfig { + /// Detect the solc version to know if VM can be injected. + pub fn detect_solc(&mut self) -> Result<()> { + if self.foundry_config.solc.is_none() { + let version = Solc::ensure_installed(&"*".parse().unwrap())?; + self.foundry_config.solc = Some(SolcReq::Version(version)); + } + if !self.no_vm + && let Some(version) = self.foundry_config.solc_version() + && version < MIN_VM_VERSION + { + info!(%version, minimum=%MIN_VM_VERSION, "Disabling VM injection"); + self.no_vm = true; + } + Ok(()) + } +} + +/// REPL Session Source wrapper +/// +/// Heavily based on soli's [`ConstructedSource`](https://github.com/jpopesculian/soli/blob/master/src/main.rs#L166) +#[derive(Debug, Serialize, Deserialize)] +pub struct SessionSource { + /// The file name + pub file_name: String, + /// The contract name + pub contract_name: String, + + /// Session Source configuration + pub config: SessionSourceConfig, + + /// Global level Solidity code. + /// + /// Above and outside all contract declarations, in the global context. + pub global_code: String, + /// Top level Solidity code. + /// + /// Within the contract declaration, but outside of the `run()` function. + pub contract_code: String, + /// The code to be executed in the `run()` function. + pub run_code: String, + + /// Cached VM source code. + #[serde(skip, default = "vm_source")] + vm_source: Source, + /// The generated output + #[serde(skip)] + output: OnceCell, +} + +fn vm_source() -> Source { + Source::new(VM_SOURCE) +} + +impl Clone for SessionSource { + fn clone(&self) -> Self { + Self { + file_name: self.file_name.clone(), + contract_name: self.contract_name.clone(), + global_code: self.global_code.clone(), + contract_code: self.contract_code.clone(), + run_code: self.run_code.clone(), + config: self.config.clone(), + vm_source: self.vm_source.clone(), + output: Default::default(), + } + } +} + +impl SessionSource { + /// Creates a new source given a solidity compiler version + /// + /// # Panics + /// + /// If no Solc binary is set, cannot be found or the `--version` command fails + /// + /// ### Takes + /// + /// - An instance of [Solc] + /// - An instance of [SessionSourceConfig] + /// + /// ### Returns + /// + /// A new instance of [SessionSource] + pub fn new(mut config: SessionSourceConfig) -> Result { + config.detect_solc()?; + Ok(Self { + file_name: "ReplContract.sol".to_string(), + contract_name: "REPL".to_string(), + config, + global_code: Default::default(), + contract_code: Default::default(), + run_code: Default::default(), + vm_source: vm_source(), + output: Default::default(), + }) + } + + /// Clones the [SessionSource] and appends a new line of code. + /// + /// Returns `true` if the new line was added to `run()`. + pub fn clone_with_new_line(&self, mut content: String) -> Result<(Self, bool)> { + if let Some((new_source, fragment)) = self + .parse_fragment(&content) + .or_else(|| { + content.push(';'); + self.parse_fragment(&content) + }) + .or_else(|| { + content = content.trim_end().trim_end_matches(';').to_string(); + self.parse_fragment(&content) + }) + { + Ok((new_source, matches!(fragment, ParseTreeFragment::Function))) + } else { + eyre::bail!("\"{}\"", content.trim()); + } + } + + /// Parses a fragment of Solidity code in memory and assigns it a scope within the + /// [`SessionSource`]. + fn parse_fragment(&self, buffer: &str) -> Option<(Self, ParseTreeFragment)> { + #[track_caller] + fn debug_errors(errors: &EmittedDiagnostics) { + debug!("{errors}"); + } + + let mut this = self.clone(); + match this.add_run_code(buffer).parse() { + Ok(()) => return Some((this, ParseTreeFragment::Function)), + Err(e) => debug_errors(&e), + } + this = self.clone(); + match this.add_contract_code(buffer).parse() { + Ok(()) => return Some((this, ParseTreeFragment::Contract)), + Err(e) => debug_errors(&e), + } + this = self.clone(); + match this.add_global_code(buffer).parse() { + Ok(()) => return Some((this, ParseTreeFragment::Source)), + Err(e) => debug_errors(&e), + } + None + } + + /// Append global-level code to the source. + pub fn add_global_code(&mut self, content: &str) -> &mut Self { + self.global_code.push_str(content.trim()); + self.global_code.push('\n'); + self.clear_output(); + self + } + + /// Append contract-level code to the source. + pub fn add_contract_code(&mut self, content: &str) -> &mut Self { + self.contract_code.push_str(content.trim()); + self.contract_code.push('\n'); + self.clear_output(); + self + } + + /// Append code to the `run()` function of the REPL contract. + pub fn add_run_code(&mut self, content: &str) -> &mut Self { + self.run_code.push_str(content.trim()); + self.run_code.push('\n'); + self.clear_output(); + self + } + + /// Clears all source code. + pub fn clear(&mut self) { + String::clear(&mut self.global_code); + String::clear(&mut self.contract_code); + String::clear(&mut self.run_code); + self.clear_output(); + } + + /// Clear the `run()` function code. + pub fn clear_run(&mut self) -> &mut Self { + String::clear(&mut self.run_code); + self.clear_output(); + self + } + + fn clear_output(&mut self) { + self.output.take(); + } + + /// Compiles the source if necessary. + pub fn build(&self) -> Result<&GeneratedOutput> { + // TODO: mimics `get_or_try_init` + if let Some(output) = self.output.get() { + return Ok(output); + } + let output = self.compile()?; + let output = GeneratedOutput { output }; + Ok(self.output.get_or_init(|| output)) + } + + /// Compiles the source. + #[cold] + fn compile(&self) -> Result { + let sources = self.get_sources(); + + let mut project = self.config.foundry_config.ephemeral_project()?; + self.config.foundry_config.disable_optimizations(&mut project, self.config.ir_minimum); + let mut output = ProjectCompiler::with_sources(&project, sources)?.compile()?; + + if output.has_compiler_errors() { + eyre::bail!("{output}"); + } + + // Drive HIR lowering and analysis so that subsequent `enter` queries can use them. + output.parser_mut().solc_mut().compiler_mut().enter_mut(|c| { + let _ = c.lower_asts(); + let _ = c.analysis(); + }); + + Ok(output) + } + + fn get_sources(&self) -> Sources { + let mut sources = Sources::new(); + + let src = self.to_repl_source(); + sources.insert(self.file_name.clone().into(), Source::new(src)); + + // Include Vm.sol if forge-std remapping is not available. + if !self.config.no_vm + && !self + .config + .foundry_config + .get_all_remappings() + .any(|r| r.name.starts_with("forge-std")) + { + sources.insert("forge-std/Vm.sol".into(), self.vm_source.clone()); + } + + sources + } + + /// Construct the REPL source. + pub fn to_repl_source(&self) -> String { + let Self { + contract_name, + global_code, + contract_code: top_level_code, + run_code, + config, + .. + } = self; + let (mut vm_import, mut vm_constant) = (String::new(), String::new()); + // Check if there's any `forge-std` remapping and determine proper path to it by + // searching remapping path. + if !config.no_vm + && let Some(remapping) = config + .foundry_config + .remappings + .iter() + .find(|remapping| remapping.name == "forge-std/") + && let Some(vm_path) = WalkDir::new(&remapping.path.path) + .into_iter() + .filter_map(|e| e.ok()) + .find(|e| e.file_name() == "Vm.sol") + { + vm_import = format!("import {{Vm}} from \"{}\";\n", vm_path.path().display()); + vm_constant = "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n".to_string(); + } + + format!( + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0; + +{vm_import} +{global_code} + +contract {contract_name} {{ + {vm_constant} + {top_level_code} + + /// @notice REPL contract entry point + function run() public {{ + {run_code} + }} +}}"#, + ) + } + + /// Parse the current source in memory using Solar. + pub(crate) fn parse(&self) -> Result<(), EmittedDiagnostics> { + let sess = + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(); + let _ = sess.enter_sequential(|| -> solar::interface::Result<()> { + let arena = solar::ast::Arena::new(); + let filename = self.file_name.clone().into(); + let src = self.to_repl_source(); + let mut parser = solar::parse::Parser::from_source_code(&sess, &arena, filename, src)?; + let _ast = parser.parse_file().map_err(|e| e.emit())?; + Ok(()) + }); + sess.dcx.emitted_errors().unwrap() + } +} + +/// A Parse Tree Fragment +/// +/// Used to determine whether an input will go to the "run()" function, +/// the top level of the contract, or in global scope. +#[derive(Debug)] +enum ParseTreeFragment { + /// Code for the global scope + Source, + /// Code for the top level of the contract + Contract, + /// Code for the "run()" function + Function, +} diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs deleted file mode 100644 index 75da9683f6d8d..0000000000000 --- a/crates/chisel/tests/cache.rs +++ /dev/null @@ -1,212 +0,0 @@ -use chisel::session::ChiselSession; -use foundry_compilers::artifacts::EvmVersion; -use foundry_config::Config; -use std::path::Path; - -#[test] -fn test_cache_directory() { - // Get the cache dir - // Should be ~/.foundry/cache/chisel - let cache_dir = ChiselSession::cache_dir().unwrap(); - - // Validate the cache directory - let home_dir = dirs::home_dir().unwrap(); - assert_eq!(cache_dir, format!("{}/.foundry/cache/chisel/", home_dir.to_str().unwrap())); -} - -#[test] -fn test_create_cache_directory() { - // Get the cache dir - let cache_dir = ChiselSession::cache_dir().unwrap(); - - // Create the cache directory - ChiselSession::create_cache_dir().unwrap(); - - // Validate the cache directory - assert!(Path::new(&cache_dir).exists()); -} - -#[test] -fn test_write_session() { - // Create the cache directory if it doesn't exist - let cache_dir = ChiselSession::cache_dir().unwrap(); - ChiselSession::create_cache_dir().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create a new session - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {e}")); - - // Write the session - let cached_session_name = env.write().unwrap(); - - // Count the number of items in the cache_dir directory - let mut num_items = std::fs::read_dir(&cache_dir).unwrap().count(); - num_items = if num_items > 0 { num_items - 1 } else { 0 }; - - // Validate the session - assert_eq!(cached_session_name, format!("{cache_dir}chisel-{num_items}.json")); -} - -#[test] -fn test_write_session_with_name() { - // Create the cache directory if it doesn't exist - let cache_dir = ChiselSession::cache_dir().unwrap(); - ChiselSession::create_cache_dir().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create a new session - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - env.id = Some(String::from("test")); - - // Write the session - let cached_session_name = env.write().unwrap(); - - // Validate the session - assert_eq!(cached_session_name, format!("{cache_dir}chisel-test.json")); -} - -#[test] -fn test_clear_cache() { - // Create a session to validate clearing a non-empty cache directory - let cache_dir = ChiselSession::cache_dir().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - ChiselSession::create_cache_dir().unwrap(); - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|_| panic!("Failed to create ChiselSession!")); - env.write().unwrap(); - - // Clear the cache - ChiselSession::clear_cache().unwrap(); - - // Validate there are no items in the cache dir - let num_items = std::fs::read_dir(cache_dir).unwrap().count(); - assert_eq!(num_items, 0); -} - -#[test] -fn test_list_sessions() { - // Create and clear the cache directory - ChiselSession::create_cache_dir().unwrap(); - ChiselSession::clear_cache().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create a new session - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - - env.write().unwrap(); - - // List the sessions - let sessions = ChiselSession::list_sessions().unwrap(); - - // Validate the sessions - assert_eq!(sessions.len(), 1); - assert_eq!(sessions[0].1, "chisel-0.json"); -} - -#[test] -fn test_load_cache() { - // Create and clear the cache directory - ChiselSession::create_cache_dir().unwrap(); - ChiselSession::clear_cache().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create a new session - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - env.write().unwrap(); - - // Load the session - let new_env = ChiselSession::load("0"); - - // Validate the session - assert!(new_env.is_ok()); - let new_env = new_env.unwrap(); - assert_eq!(new_env.id.unwrap(), String::from("0")); - assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); -} - -#[test] -fn test_write_same_session_multiple_times() { - // Create and clear the cache directory - ChiselSession::create_cache_dir().unwrap(); - ChiselSession::clear_cache().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create a new session - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - env.write().unwrap(); - env.write().unwrap(); - env.write().unwrap(); - env.write().unwrap(); - assert_eq!(ChiselSession::list_sessions().unwrap().len(), 1); -} - -#[test] -fn test_load_latest_cache() { - // Create and clear the cache directory - ChiselSession::create_cache_dir().unwrap(); - ChiselSession::clear_cache().unwrap(); - - // Force the solc version to be 0.8.19 - let foundry_config = Config { evm_version: EvmVersion::London, ..Default::default() }; - - // Create sessions - let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config: foundry_config.clone(), - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - env.write().unwrap(); - - let wait_time = std::time::Duration::from_millis(100); - std::thread::sleep(wait_time); - - let mut env2 = ChiselSession::new(chisel::session_source::SessionSourceConfig { - foundry_config, - ..Default::default() - }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); - env2.write().unwrap(); - - // Load the latest session - let new_env = ChiselSession::latest().unwrap(); - - // Validate the session - assert_eq!(new_env.id.unwrap(), "1"); - assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); -} diff --git a/crates/chisel/tests/it/main.rs b/crates/chisel/tests/it/main.rs new file mode 100644 index 0000000000000..bb5858dc0a98f --- /dev/null +++ b/crates/chisel/tests/it/main.rs @@ -0,0 +1,2 @@ +#[cfg(unix)] +mod repl; diff --git a/crates/chisel/tests/it/repl/mod.rs b/crates/chisel/tests/it/repl/mod.rs new file mode 100644 index 0000000000000..338b7d2043809 --- /dev/null +++ b/crates/chisel/tests/it/repl/mod.rs @@ -0,0 +1,296 @@ +mod session; +use session::ChiselSession; + +macro_rules! repl_test { + ($name:ident, | $cmd:ident | $test:expr) => { + repl_test!($name, "", |$cmd| $test); + }; + ($name:ident, $flags:expr, | $cmd:ident | $test:expr) => { + repl_test!($name, $flags, init = false, |$cmd| $test); + }; + ($name:ident, $flags:expr,init = $init:expr, | $cmd:ident | $test:expr) => { + #[test] + #[allow(unused_mut)] + fn $name() { + let mut $cmd = ChiselSession::new(stringify!($name), $flags, $init); + $test; + return (); // Fix "go to definition" due to `tokio::test`. + } + }; +} + +repl_test!(repl_help, |repl| { + repl.sendln_raw("!h"); + repl.expect("Chisel help"); + repl.expect_prompt(); +}); + +// Test abi encode/decode. +repl_test!(abi_encode_decode, |repl| { + repl.sendln("bytes memory encoded = abi.encode(42, \"hello\")"); + repl.sendln("(uint num, string memory str) = abi.decode(encoded, (uint, string))"); + repl.sendln("num"); + repl.expect("42"); + repl.sendln("str"); + repl.expect("hello"); +}); + +// Test 0x prefixed strings. +repl_test!(hex_string_interpretation, |repl| { + repl.sendln("string memory s = \"0x1234\""); + repl.sendln("s"); + // Should be treated as string, not hex literal. + repl.expect("0x1234"); +}); + +// Test cheatcodes availability. +repl_test!(cheatcodes_available, "", init = true, |repl| { + repl.sendln("address alice = address(0x1)"); + + repl.sendln("alice.balance"); + repl.expect("Decimal: 0"); + + repl.sendln("vm.deal();"); + repl.expect("Wrong argument count for function call"); + + repl.sendln("vm.deal(alice, 1 ether);"); + + repl.sendln("alice.balance"); + repl.expect("Decimal: 1000000000000000000"); +}); + +// Test empty inputs. +repl_test!(empty_input, |repl| { + repl.sendln(" \n \n\n \t \t \n \n\t\t\t\t \n \n"); +}); + +// Issue #4130: Test type(intN).min correctness. +repl_test!(int_min_values, |repl| { + repl.sendln("type(int8).min"); + repl.expect("-128"); + repl.sendln("type(int256).min"); + repl.expect("-57896044618658097711785492504343953926634992332820282019728792003956564819968"); +}); + +// Issue #4393: Test edit command with traces. +// TODO: test `!edit` +// repl_test!(edit_with_traces, |repl| { +// repl.sendln("!traces"); +// repl.sendln("uint x = 42"); +// repl.sendln("!edit"); +// // Should open editor without errors. +// repl.expect("Running"); +// }); + +// Test tuple support. +repl_test!(tuples, |repl| { + repl.sendln("(uint a, uint b) = (1, 2)"); + repl.sendln("a"); + repl.expect("Decimal: 1"); + repl.sendln("b"); + repl.expect("Decimal: 2"); +}); + +// Issue #4467: Test import. +repl_test!(import, "", init = true, |repl| { + repl.sendln("import {Counter} from \"src/Counter.sol\""); + repl.sendln("Counter c = new Counter()"); + // TODO: pre-existing inspection failure. + // repl.sendln("c.number()"); + repl.sendln("uint x = c.number();\nx"); + repl.expect("Decimal: 0"); + repl.sendln("c.increment();"); + // repl.sendln("c.number()"); + repl.sendln("x = c.number();\nx"); + repl.expect("Decimal: 1"); +}); + +// Issue #4617: Test code after assembly return. +repl_test!(assembly_return, |repl| { + repl.sendln("uint x = 1;"); + repl.sendln("assembly { mstore(0x0, 0x1337) return(0x0, 0x20) }"); + repl.sendln("x = 2;"); + repl.sendln("!md"); + // Should work without errors. + repl.expect("[0x00:0x20]: 0x0000000000000000000000000000000000000000000000000000000000001337"); +}); + +// Issue #4652: Test commands with trailing whitespace. +repl_test!(trailing_whitespace, |repl| { + repl.sendln("uint x = 42 "); + repl.sendln("x"); + repl.expect("Decimal: 42"); +}); + +// Issue #4652: Test that solc flags are respected. +repl_test!(solc_flags, "--use 0.8.23", |repl| { + repl.sendln("pragma solidity 0.8.24;"); + repl.expect("invalid solc version"); +}); + +// Issue #4915: `chisel eval` +repl_test!(eval_subcommand, "eval type(uint8).max", |repl| { + repl.expect("Decimal: 255"); +}); + +// Issue #4938: Test memory/stack dumps with assembly. +repl_test!(assembly_memory_dump, |repl| { + let input = r#" +uint256 value = 12345; +string memory str; +assembly { + str := add(mload(0x40), 0x80) + mstore(0x40, add(str, 0x20)) + mstore(str, 0) + let end := str +} +"#; + repl.sendln_raw(input.trim()); + repl.expect_prompts(3); + repl.sendln("value"); + repl.expect("Decimal: 12345"); + repl.sendln("!md"); + repl.expect("[0x00:0x20]"); +}); + +// Assembly as the final statement with a return — exercises the path where both +// `first_yul_return_span` and `trailing_assembly_last_stmt_span` resolve to the same `return(...)` +// span (no subsequent Solidity statement after the assembly block). +repl_test!(assembly_return_final, |repl| { + repl.sendln("uint x = 0xbeef;"); + repl.sendln("assembly { mstore(0x0, sload(0)) return(0x0, 0x20) }"); + repl.sendln("!md"); + repl.expect("[0x00:0x20]"); +}); + +// Assembly block without a `return(...)` call as an intermediate statement, exercises +// `first_yul_return_span` returning `None` while a subsequent Solidity statement is still evaluated +// correctly. +repl_test!(assembly_no_return_intermediate, |repl| { + repl.sendln("uint x = 1;"); + repl.sendln("assembly { x := add(x, 1) }"); + repl.sendln("x"); + repl.expect("Decimal: 2"); +}); + +// Issue #5051, #8978: Test EVM version normalization. +repl_test!(flaky_evm_version_normalization, "--use 0.7.6 --evm-version london", |repl| { + repl.sendln("uint x;\nx"); + repl.expect("Decimal: 0"); +}); + +// Issue #5481: Test function return values are displayed. +repl_test!(function_return_display, |repl| { + repl.sendln("function add(uint a, uint b) public pure returns (uint) { return a + b; }"); + repl.sendln("add(2, 3)"); + repl.expect("Decimal: 5"); +}); + +// Issue #5737: Test bytesN return types. +repl_test!(bytes_length_type, |repl| { + repl.sendln("bytes10 b = bytes10(0)"); + repl.sendln("b.length"); + repl.expect("Decimal: 10"); +}); + +// Issue #5737: Test bytesN indexing return type. +repl_test!(bytes_index_type, |repl| { + repl.sendln("bytes32 b = bytes32(uint256(0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20))"); + repl.sendln("b[3]"); + repl.expect("Data: 0x0400000000000000000000000000000000000000000000000000000000000000"); +}); + +// Issue #6618: Test fetching interface with structs. +repl_test!(fetch_interface_with_structs, |repl| { + repl.sendln_raw("!fe 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 IEntryPoint"); + repl.expect( + "Added 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789's interface to source as `IEntryPoint`", + ); + repl.expect_prompt(); + repl.sendln("uint256 x = 1;\nx"); + repl.expect("Decimal: 1"); +}); + +// Issue #7035: Test that hex strings aren't checksummed as addresses. +repl_test!(hex_string_no_checksum, |repl| { + repl.sendln("function test(string memory s) public pure returns (string memory) { return s; }"); + repl.sendln("test(\"0xe5f3af50fe5d0bf402a3c6f55ccc47d4307922d4\")"); + // Should return the exact string, not checksummed. + repl.expect("0xe5f3af50fe5d0bf402a3c6f55ccc47d4307922d4"); +}); + +// Issue #7050: Test enum min/max operations. +repl_test!(enum_min_max, |repl| { + repl.sendln("enum Color { Red, Green, Blue }"); + repl.sendln("type(Color).min"); + repl.expect("Decimal: 0"); + repl.sendln("type(Color).max"); + repl.expect("Decimal: 2"); +}); + +// Issue #9377: Test correct hex formatting for uint256. +repl_test!(uint256_hex_formatting, |repl| { + repl.sendln("uint256 x = 42"); + // Full word hex should be 64 chars (256 bits). + repl.sendln("x"); + repl.expect("0x000000000000000000000000000000000000000000000000000000000000002a"); +}); + +// Issue #9377: Test that full words are printed correctly. +repl_test!(full_word_hex_formatting, |repl| { + repl.sendln(r#"keccak256(abi.encode(uint256(keccak256("AgoraStableSwapStorage.OracleStorage")) - 1)) & ~bytes32(uint256(0xff))"#); + repl.expect( + "Hex (full word): 0x0a6b316b47a0cd26c1b582ae3dcffbd175283c221c3cb3d1c614e3e47f62a700", + ); +}); + +// Test that uint is printed properly with any size. +repl_test!(uint_formatting, |repl| { + for size in (8..=256).step_by(8) { + repl.sendln(&format!("type(uint{size}).max")); + repl.expect(&format!("Hex: 0x{}", "f".repeat(size / 4))); + + repl.sendln(&format!("uint{size}(2)")); + repl.expect("Hex: 0x2"); + } +}); + +// Test that int is printed properly with any size. +repl_test!(int_formatting, |repl| { + for size in (8..=256).step_by(8) { + let size_minus_1: usize = size / 4 - 1; + repl.sendln(&format!("type(int{size}).max")); + repl.expect(&format!("Hex: 0x7{}", "f".repeat(size_minus_1))); + + repl.sendln(&format!("int{size}(2)")); + repl.expect("Hex: 0x2"); + + repl.sendln(&format!("type(int{size}).min")); + repl.expect(&format!("Hex: 0x8{}", "0".repeat(size_minus_1))); + + repl.sendln(&format!("int{size}(-2)")); + repl.expect(&format!("Hex: 0x{}e", "f".repeat(size_minus_1))); + } +}); + +repl_test!(uninitialized_variables, |repl| { + repl.sendln("uint256 x;"); + repl.sendln("address y;"); + repl.sendln("assembly { y := not(x) }"); + + repl.sendln("x"); + repl.expect("Hex: 0x0"); + + repl.sendln("y"); + repl.expect("Data: 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"); +}); + +repl_test!(chisel_can_run_with_live_logs_flag, "--live-logs", init = true, |repl| { + repl.sendln("import {console} from 'forge-std/Script.sol';"); + repl.sendln("console.log('Hello, World!');"); + repl.expect("Hello, World!"); + + repl.sendln("console.log('Goodbye, World!');"); + repl.expect("Hello, World!"); // old log is also printed + repl.expect("Goodbye, World!"); +}); diff --git a/crates/chisel/tests/it/repl/session.rs b/crates/chisel/tests/it/repl/session.rs new file mode 100644 index 0000000000000..5076b00996ae7 --- /dev/null +++ b/crates/chisel/tests/it/repl/session.rs @@ -0,0 +1,121 @@ +use foundry_compilers::PathStyle; +use foundry_test_utils::TestProject; +use rexpect::{reader::Options, session::PtySession, spawn_with_options}; + +const TIMEOUT_SECS: u64 = 3; +const PROMPT: &str = "➜ "; + +/// Testing session for Chisel. +pub struct ChiselSession { + session: Box, + project: Box, + is_repl: bool, +} + +static SUBCOMMANDS: &[&str] = &["list", "load", "view", "clear-cache", "eval", "help"]; + +fn is_repl(args: &[String]) -> bool { + args.is_empty() + || !SUBCOMMANDS.iter().any(|subcommand| args.iter().any(|arg| arg == subcommand)) +} + +#[allow(dead_code)] +impl ChiselSession { + pub fn new(name: &str, flags: &str, init: bool) -> Self { + let project = foundry_test_utils::TestProject::new(name, PathStyle::Dapptools); + if init { + foundry_test_utils::util::initialize(project.root()); + project.initialize_default_contracts(); + } + + let bin = env!("CARGO_BIN_EXE_chisel"); + let mut command = std::process::Command::new(bin); + + // TODO: TTY works but logs become unreadable. + command.current_dir(project.root()); + command.env("NO_COLOR", "1"); + command.env("TERM", "dumb"); + + command.env("ETHERSCAN_API_KEY", foundry_test_utils::rpc::next_etherscan_api_key()); + + if !flags.is_empty() { + command.args(flags.split_whitespace()); + } + let args = command.get_args().map(|s| s.to_str().unwrap().to_string()).collect::>(); + + let session = spawn_with_options( + command, + Options { + timeout_ms: Some(TIMEOUT_SECS * 1000), + strip_ansi_escape_codes: false, + encoding: rexpect::Encoding::UTF8, + }, + ) + .unwrap(); + + let is_repl = is_repl(&args); + let mut session = Self { session: Box::new(session), project: Box::new(project), is_repl }; + + // Expect initial prompt only if we're in the REPL. + if session.is_repl() { + session.expect("Welcome to Chisel!"); + } + + session + } + + pub fn project(&self) -> &TestProject { + &self.project + } + + pub const fn is_repl(&self) -> bool { + self.is_repl + } + + /// Send a line to the REPL and expects the prompt to appear. + #[track_caller] + pub fn sendln(&mut self, line: &str) { + self.sendln_raw(line); + if self.is_repl() { + self.expect_prompt(); + } + } + + /// Send a line to the REPL without expecting the prompt to appear. + /// + /// You might want to call `expect_prompt` after this. + #[track_caller] + pub fn sendln_raw(&mut self, line: &str) { + match self.session.send_line(line) { + Ok(_) => (), + Err(e) => { + panic!("failed to send line {line:?}: {e}") + } + } + } + + /// Expect the needle to appear. + #[track_caller] + pub fn expect(&mut self, needle: &str) { + match self.session.exp_string(needle) { + Ok(_) => (), + Err(e) => { + panic!("failed to expect {needle:?}: {e}") + } + } + } + + /// Expect the prompt to appear. + #[track_caller] + pub fn expect_prompt(&mut self) { + self.expect(PROMPT); + } + + /// Expect the prompt to appear `n` times. + #[track_caller] + pub fn expect_prompts(&mut self, n: usize) { + for _ in 0..n { + self.expect_prompt(); + } + } +} diff --git a/crates/cli-markdown/Cargo.toml b/crates/cli-markdown/Cargo.toml new file mode 100644 index 0000000000000..7f039685d2cf1 --- /dev/null +++ b/crates/cli-markdown/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "foundry-cli-markdown" +description = "Generate Markdown documentation for clap CLIs" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +clap = { version = "4", features = ["env"] } + +[dev-dependencies] +clap = { version = "4", features = ["derive"] } +pretty_assertions = "1" diff --git a/crates/cli-markdown/src/lib.rs b/crates/cli-markdown/src/lib.rs new file mode 100644 index 0000000000000..7702fcf15b9a7 --- /dev/null +++ b/crates/cli-markdown/src/lib.rs @@ -0,0 +1,582 @@ +//! Generate Markdown documentation for clap command-line tools. +//! +//! This is a fork of [`clap-markdown`](https://crates.io/crates/clap-markdown) with the following +//! enhancements: +//! - Support for grouped options by help heading ([PR #48](https://github.com/ConnorGray/clap-markdown/pull/48)) +//! - Show environment variable names for arguments ([PR #50](https://github.com/ConnorGray/clap-markdown/pull/50)) +//! - Add version information to generated Markdown ([PR #52](https://github.com/ConnorGray/clap-markdown/pull/52)) + +use std::{ + collections::BTreeMap, + fmt::{self, Write}, +}; + +use clap::builder::PossibleValue; + +/// Options to customize the structure of the output Markdown document. +#[non_exhaustive] +pub struct MarkdownOptions { + title: Option, + show_footer: bool, + show_table_of_contents: bool, + show_aliases: bool, +} + +impl MarkdownOptions { + /// Construct a default instance of `MarkdownOptions`. + pub const fn new() -> Self { + Self { title: None, show_footer: true, show_table_of_contents: true, show_aliases: true } + } + + /// Set a custom title to use in the generated document. + pub fn title(mut self, title: String) -> Self { + self.title = Some(title); + self + } + + /// Whether to show the default footer advertising `clap-markdown`. + pub const fn show_footer(mut self, show: bool) -> Self { + self.show_footer = show; + self + } + + /// Whether to show the default table of contents. + pub const fn show_table_of_contents(mut self, show: bool) -> Self { + self.show_table_of_contents = show; + self + } + + /// Whether to show aliases for arguments and commands. + pub const fn show_aliases(mut self, show: bool) -> Self { + self.show_aliases = show; + self + } +} + +impl Default for MarkdownOptions { + fn default() -> Self { + Self::new() + } +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown() -> String { + let command = C::command(); + help_markdown_command(&command) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_custom(options: &MarkdownOptions) -> String { + let command = C::command(); + help_markdown_command_custom(&command, options) +} + +/// Format the help information for `command` as Markdown. +pub fn help_markdown_command(command: &clap::Command) -> String { + help_markdown_command_custom(command, &Default::default()) +} + +/// Format the help information for `command` as Markdown, with custom options. +pub fn help_markdown_command_custom(command: &clap::Command, options: &MarkdownOptions) -> String { + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, command, options); + buffer +} + +/// Format the help information for `command` as Markdown and print it. +/// +/// Output is printed to the standard output. +#[allow(clippy::disallowed_macros)] +pub fn print_help_markdown() { + let command = C::command(); + let mut buffer = String::with_capacity(100); + write_help_markdown(&mut buffer, &command, &Default::default()); + println!("{buffer}"); +} + +fn write_help_markdown(buffer: &mut String, command: &clap::Command, options: &MarkdownOptions) { + let title_name = get_canonical_name(command); + + let title = match options.title { + Some(ref title) => title.to_owned(), + None => format!("Command-Line Help for `{title_name}`"), + }; + writeln!(buffer, "# {title}\n",).unwrap(); + + writeln!( + buffer, + "This document contains the help content for the `{title_name}` command-line program.\n", + ) + .unwrap(); + + // Write the version if available (PR #52) + if let Some(version) = command.get_version() { + let version_str = version.to_string(); + + if version_str.contains('\n') { + // Multi-line version: use a code block + writeln!(buffer, "**Version:**\n\n```\n{}\n```\n", version_str.trim()).unwrap(); + } else { + // Single-line version: use inline code + writeln!(buffer, "**Version:** `{version_str}`\n").unwrap(); + } + } + + // Write the table of contents + if options.show_table_of_contents { + writeln!(buffer, "**Command Overview:**\n").unwrap(); + build_table_of_contents_markdown(buffer, Vec::new(), command, 0).unwrap(); + writeln!(buffer).unwrap(); + } + + // Write the commands/subcommands sections + build_command_markdown(buffer, Vec::new(), command, 0, options).unwrap(); + + // Write the footer + if options.show_footer { + write!( + buffer, + r#"
+ + + This document was generated automatically by + clap-markdown. + +"# + ) + .unwrap(); + } +} + +fn build_table_of_contents_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path; + command_path.push(title_name); + command_path + }; + + writeln!(buffer, "* [`{}`↴](#{})", command_path.join(" "), command_path.join("-"),)?; + + for subcommand in command.get_subcommands() { + build_table_of_contents_markdown(buffer, command_path.clone(), subcommand, _depth + 1)?; + } + + Ok(()) +} + +fn build_command_markdown( + buffer: &mut String, + parent_command_path: Vec, + command: &clap::Command, + _depth: usize, + options: &MarkdownOptions, +) -> std::fmt::Result { + // Don't document commands marked with `clap(hide = true)` + if command.is_hide_set() { + return Ok(()); + } + + let title_name = get_canonical_name(command); + + let command_path = { + let mut command_path = parent_command_path.clone(); + command_path.push(title_name); + command_path + }; + + // Write the markdown heading + writeln!(buffer, "## `{}`\n", command_path.join(" "))?; + + if let Some(long_about) = command.get_long_about() { + writeln!(buffer, "{long_about}\n")?; + } else if let Some(about) = command.get_about() { + writeln!(buffer, "{about}\n")?; + } + + if let Some(help) = command.get_before_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_before_help() { + writeln!(buffer, "{help}\n")?; + } + + writeln!( + buffer, + "**Usage:** `{}{}`\n", + if parent_command_path.is_empty() { + String::new() + } else { + let mut s = parent_command_path.join(" "); + s.push(' '); + s + }, + command.clone().render_usage().to_string().replace("Usage: ", "") + )?; + + if options.show_aliases { + let aliases = command.get_visible_aliases().collect::>(); + if let Some(aliases_str) = get_alias_string(&aliases) { + writeln!( + buffer, + "**{}:** {aliases_str}\n", + pluralize(aliases.len(), "Command Alias", "Command Aliases") + )?; + } + } + + if let Some(help) = command.get_after_long_help() { + writeln!(buffer, "{help}\n")?; + } else if let Some(help) = command.get_after_help() { + writeln!(buffer, "{help}\n")?; + } + + // Subcommands + if command.get_subcommands().next().is_some() { + writeln!(buffer, "###### **Subcommands:**\n")?; + + for subcommand in command.get_subcommands() { + if subcommand.is_hide_set() { + continue; + } + + let title_name = get_canonical_name(subcommand); + let about = match subcommand.get_about() { + Some(about) => about.to_string(), + None => String::new(), + }; + + writeln!(buffer, "* `{title_name}` — {about}",)?; + } + + writeln!(buffer)?; + } + + // Arguments (positional) + if command.get_positionals().next().is_some() { + writeln!(buffer, "###### **Arguments:**\n")?; + + for pos_arg in command.get_positionals() { + write_arg_markdown(buffer, pos_arg)?; + } + + writeln!(buffer)?; + } + + // Options (grouped by help heading) - PR #48 + let non_pos: Vec<_> = + command.get_arguments().filter(|arg| !arg.is_positional() && !arg.is_hide_set()).collect(); + + if !non_pos.is_empty() { + // Group arguments by help heading + let mut grouped_args: BTreeMap<&str, Vec<&clap::Arg>> = BTreeMap::new(); + + for arg in non_pos { + let heading = arg.get_help_heading().unwrap_or("Options"); + grouped_args.entry(heading).or_default().push(arg); + } + + // Write each group with its heading + for (heading, args) in grouped_args { + writeln!(buffer, "###### **{heading}:**\n")?; + + for arg in args { + write_arg_markdown(buffer, arg)?; + } + + writeln!(buffer)?; + } + } + + // Include extra space between commands + write!(buffer, "\n\n")?; + + for subcommand in command.get_subcommands() { + build_command_markdown(buffer, command_path.clone(), subcommand, _depth + 1, options)?; + } + + Ok(()) +} + +fn write_arg_markdown(buffer: &mut String, arg: &clap::Arg) -> fmt::Result { + // Markdown list item + write!(buffer, "* ")?; + + let value_name: String = match arg.get_value_names() { + Some([name, ..]) => name.as_str().to_owned(), + Some([]) => unreachable!("clap Arg::get_value_names() returned Some(..) of empty list"), + None => arg.get_id().to_string().to_ascii_uppercase(), + }; + + match (arg.get_short(), arg.get_long()) { + (Some(short), Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short}`, `--{long} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`, `--{long}`")? + } + } + (Some(short), None) => { + if arg.get_action().takes_values() { + write!(buffer, "`-{short} <{value_name}>`")? + } else { + write!(buffer, "`-{short}`")? + } + } + (None, Some(long)) => { + if arg.get_action().takes_values() { + write!(buffer, "`--{long} <{value_name}>`")? + } else { + write!(buffer, "`--{long}`")? + } + } + (None, None) => { + debug_assert!( + arg.is_positional(), + "unexpected non-positional Arg with neither short nor long name: {arg:?}" + ); + write!(buffer, "`<{value_name}>`",)?; + } + } + + if let Some(aliases) = arg.get_visible_aliases().as_deref() + && let Some(aliases_str) = get_alias_string(aliases) + { + write!(buffer, " [{}: {aliases_str}]", pluralize(aliases.len(), "alias", "aliases"))?; + } + + if let Some(help) = arg.get_long_help() { + buffer.push_str(&indent(&help.to_string(), " — ", " ")) + } else if let Some(short_help) = arg.get_help() { + writeln!(buffer, " — {short_help}")?; + } else { + writeln!(buffer)?; + } + + // Arg default values + if !arg.get_default_values().is_empty() { + let default_values: String = arg + .get_default_values() + .iter() + .map(|value| format!("`{}`", value.to_string_lossy())) + .collect::>() + .join(", "); + + if arg.get_default_values().len() > 1 { + writeln!(buffer, "\n Default values: {default_values}")?; + } else { + writeln!(buffer, "\n Default value: {default_values}")?; + } + } + + // Arg possible values + let possible_values: Vec = + arg.get_possible_values().into_iter().filter(|pv| !pv.is_hide_set()).collect(); + + if !possible_values.is_empty() && !matches!(arg.get_action(), clap::ArgAction::SetTrue) { + let any_have_help: bool = possible_values.iter().any(|pv| pv.get_help().is_some()); + + if any_have_help { + let text: String = possible_values + .iter() + .map(|pv| match pv.get_help() { + Some(help) => { + format!(" - `{}`:\n {}\n", pv.get_name(), help) + } + None => format!(" - `{}`\n", pv.get_name()), + }) + .collect::>() + .join(""); + + writeln!(buffer, "\n Possible values:\n{text}")?; + } else { + let text: String = possible_values + .iter() + .map(|pv| format!("`{}`", pv.get_name())) + .collect::>() + .join(", "); + + writeln!(buffer, "\n Possible values: {text}\n")?; + } + } + + // Arg environment variable (PR #50) + if !arg.is_hide_env_set() + && let Some(env) = arg.get_env() + { + writeln!(buffer, "\n Environment variable: `{}`", env.to_string_lossy())?; + } + + Ok(()) +} + +/// Utility function to get the canonical name of a command. +fn get_canonical_name(command: &clap::Command) -> String { + command + .get_display_name() + .or_else(|| command.get_bin_name()) + .map(|name| name.to_owned()) + .unwrap_or_else(|| command.get_name().to_owned()) +} + +/// Indents non-empty lines. The output always ends with a newline. +fn indent(s: &str, first: &str, rest: &str) -> String { + if s.is_empty() { + return "\n".to_string(); + } + let mut result = String::new(); + let mut first_line = true; + + for line in s.lines() { + if !line.is_empty() { + result.push_str(if first_line { first } else { rest }); + result.push_str(line); + first_line = false; + } + result.push('\n'); + } + result +} + +fn get_alias_string(aliases: &[&str]) -> Option { + if aliases.is_empty() { + return None; + } + + Some(aliases.iter().map(|alias| format!("`{alias}`")).collect::>().join(", ")) +} + +const fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { + if count == 1 { singular } else { plural } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Arg, Command}; + use pretty_assertions::assert_eq; + + #[test] + fn test_indent() { + assert_eq!(&indent("Header\n\nMore info", "___", "~~~~"), "___Header\n\n~~~~More info\n"); + assert_eq!( + &indent("Header\n\nMore info\n", "___", "~~~~"), + &indent("Header\n\nMore info", "___", "~~~~"), + ); + assert_eq!(&indent("", "___", "~~~~"), "\n"); + assert_eq!(&indent("\n", "___", "~~~~"), "\n"); + } + + #[test] + fn test_version_output() { + let app = Command::new("test-app").version("1.2.3").about("A test application"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:** `1.2.3`"), "Should contain version"); + } + + #[test] + fn test_multiline_version() { + let multi_line_version = "my-cli 1.2.3 (abc123)\nmy-lib 2.0.0 (789xyz)"; + + let app = Command::new("my-cli").version(multi_line_version).about("Multi-version CLI"); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("**Version:**\n\n```"), "Should use code block for multi-line"); + } + + #[test] + fn test_env_var_output() { + let app = Command::new("env-test").about("Test env var output").arg( + Arg::new("config") + .short('c') + .long("config") + .env("CONFIG_PATH") + .help("Path to config file"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!( + markdown.contains("Environment variable: `CONFIG_PATH`"), + "Should show env var. Output: {markdown}" + ); + } + + #[test] + fn test_grouped_options() { + let app = Command::new("grouped-app") + .about("Test app with grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .help_heading("General Options") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("input") + .short('i') + .long("input") + .help("Input file") + .help_heading("File Options") + .value_name("FILE"), + ) + .arg( + Arg::new("format") + .short('f') + .long("format") + .help("Output format") + .value_name("FORMAT"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **File Options:**"), "Should have File Options heading"); + assert!( + markdown.contains("###### **General Options:**"), + "Should have General Options heading" + ); + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + } + + #[test] + fn test_no_grouped_options_backward_compatibility() { + let app = Command::new("simple-app") + .about("Test app without grouped options") + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose output") + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("output").short('o').long("output").help("Output file").value_name("FILE"), + ); + + let markdown = + help_markdown_command_custom(&app, &MarkdownOptions::new().show_footer(false)); + + assert!(markdown.contains("###### **Options:**"), "Should have default Options heading"); + assert!(markdown.contains("`-v`, `--verbose`"), "Should have verbose option"); + assert!(markdown.contains("`-o`, `--output `"), "Should have output option"); + } +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 2eade5bdd2ccd..606b8291819e4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -13,18 +13,22 @@ repository.workspace = true workspace = true [dependencies] -forge-fmt.workspace = true +foundry-cli-markdown.workspace = true foundry-common.workspace = true foundry-config.workspace = true -foundry-debugger.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-evm-networks.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } -foundry-compilers = { workspace = true, features = ["full"] } -foundry-block-explorers.workspace = true +tempo-primitives.workspace = true + +foundry-compilers.workspace = true +solar.workspace = true alloy-eips.workspace = true alloy-dyn-abi.workspace = true +alloy-network.workspace = true +alloy-signer.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true @@ -34,6 +38,8 @@ alloy-ens = { workspace = true, features = ["provider"] } cfg-if = "1.0" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +clap_complete.workspace = true +clap_complete_nushell.workspace = true color-eyre.workspace = true dotenvy = "0.15" eyre.workspace = true @@ -41,20 +47,22 @@ futures.workspace = true indicatif.workspace = true itertools.workspace = true mimalloc = { workspace = true, optional = true } +path-slash.workspace = true rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true serde.workspace = true +toml.workspace = true strsim = "0.11" strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } tracing.workspace = true -tracy-client = { workspace = true, optional = true, features = ["demangle"] } yansi.workspace = true rustls = { workspace = true, features = ["ring"] } +dunce.workspace = true -tracing-tracy = { version = "0.11", optional = true } +tracing-tracy = { version = "0.11", optional = true, features = ["demangle"] } [dev-dependencies] tempfile.workspace = true @@ -63,7 +71,13 @@ tempfile.workspace = true tikv-jemallocator = { workspace = true, optional = true } [features] +default = ["optimism"] tracy = ["dep:tracing-tracy"] -tracy-allocator = ["dep:tracy-client", "tracy"] +tracy-allocator = ["tracy"] jemalloc = ["dep:tikv-jemallocator"] mimalloc = ["dep:mimalloc"] +optimism = [ + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-evm/optimism", +] diff --git a/crates/cli/src/clap.rs b/crates/cli/src/clap.rs new file mode 100644 index 0000000000000..f7f2a25420976 --- /dev/null +++ b/crates/cli/src/clap.rs @@ -0,0 +1,44 @@ +use clap_complete::{Shell as ClapCompleteShell, aot::Generator}; +use clap_complete_nushell::Nushell; + +#[derive(Clone, Copy)] +pub enum Shell { + ClapCompleteShell(ClapCompleteShell), + Nushell, +} + +impl clap::ValueEnum for Shell { + fn value_variants<'a>() -> &'a [Self] { + &[ + Self::ClapCompleteShell(ClapCompleteShell::Bash), + Self::ClapCompleteShell(ClapCompleteShell::Zsh), + Self::ClapCompleteShell(ClapCompleteShell::Fish), + Self::ClapCompleteShell(ClapCompleteShell::PowerShell), + Self::ClapCompleteShell(ClapCompleteShell::Elvish), + Self::Nushell, + ] + } + + fn to_possible_value(&self) -> Option { + match self { + Self::ClapCompleteShell(shell) => shell.to_possible_value(), + Self::Nushell => Some(clap::builder::PossibleValue::new("nushell")), + } + } +} + +impl Generator for Shell { + fn file_name(&self, name: &str) -> String { + match self { + Self::ClapCompleteShell(shell) => shell.file_name(name), + Self::Nushell => Nushell.file_name(name), + } + } + + fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) { + match self { + Self::ClapCompleteShell(shell) => shell.generate(cmd, buf), + Self::Nushell => Nushell.generate(cmd, buf), + } + } +} diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 147d2461f407f..763466d21185d 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,22 +1,16 @@ use eyre::EyreHandler; -use itertools::Itertools; use std::{error::Error, fmt}; /// A custom context type for Foundry specific error reporting via `eyre`. +#[derive(Default)] pub struct Handler { debug_handler: Option>, } -impl Default for Handler { - fn default() -> Self { - Self::new() - } -} - impl Handler { /// Create a new instance of the `Handler`. pub fn new() -> Self { - Self { debug_handler: None } + Self::default() } /// Override the debug handler with a custom one. @@ -28,8 +22,7 @@ impl Handler { impl EyreHandler for Handler { fn display(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Display; - foundry_common::errors::dedup_chain(error).into_iter().format("; ").fmt(f) + f.write_str(&foundry_common::errors::display_chain(error)) } fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -38,7 +31,7 @@ impl EyreHandler for Handler { } if f.alternate() { - return fmt::Debug::fmt(error, f) + return fmt::Debug::fmt(error, f); } let errors = foundry_common::errors::dedup_chain(error); @@ -80,7 +73,9 @@ impl EyreHandler for Handler { /// Panics are always caught by the more debug-centric handler. pub fn install() { if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "1"); + unsafe { + std::env::set_var("RUST_BACKTRACE", "1"); + } } let panic_section = diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 9c1fb848edaa9..8c85aa78ab429 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -3,7 +3,7 @@ //! Common CLI utilities. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -11,6 +11,10 @@ extern crate foundry_common; #[macro_use] extern crate tracing; +pub mod clap; pub mod handler; pub mod opts; pub mod utils; + +#[cfg(feature = "tracy")] +tracing_tracy::client::register_demangler!(); diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index dd7e49ec1c3ab..20e37682f93c4 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -3,20 +3,19 @@ use crate::{opts::CompilerOpts, utils::LoadConfig}; use clap::{Parser, ValueHint}; use eyre::Result; use foundry_compilers::{ - artifacts::{remappings::Remapping, RevertStrings}, + Project, + artifacts::{RevertStrings, remappings::Remapping}, compilers::multi::MultiCompiler, utils::canonicalized, - Project, }; use foundry_config::{ - figment, + Config, DenyLevel, Remappings, figment::{ + self, Figment, Metadata, Profile, Provider, error::Kind::InvalidType, value::{Dict, Map, Value}, - Figment, Metadata, Profile, Provider, }, filter::SkipBuildFilter, - Config, Remappings, }; use serde::Serialize; use std::path::PathBuf; @@ -49,9 +48,26 @@ pub struct BuildOpts { #[serde(skip_serializing_if = "Vec::is_empty")] pub ignored_error_codes: Vec, - /// Warnings will trigger a compiler error - #[arg(long, help_heading = "Compiler options")] + /// A compiler error will be triggered at the specified diagnostic level. + /// + /// Replaces the deprecated `--deny-warnings` flag. + /// + /// Possible values: + /// - `never`: Do not treat any diagnostics as errors. + /// - `warnings`: Treat warnings as errors. + /// - `notes`: Treat both, warnings and notes, as errors. + #[arg( + long, + short = 'D', + help_heading = "Compiler options", + value_name = "LEVEL", + conflicts_with = "deny_warnings" + )] #[serde(skip)] + pub deny: Option, + + /// Deprecated: use `--deny=warnings` instead. + #[arg(long = "deny-warnings", hide = true)] pub deny_warnings: bool, /// Do not auto-detect the `solc` version. @@ -167,18 +183,22 @@ impl BuildOpts { // Loads project's figment and merges the build cli arguments into it impl<'a> From<&'a BuildOpts> for Figment { fn from(args: &'a BuildOpts) -> Self { - let mut figment = if let Some(ref config_path) = args.project_paths.config_path { - if !config_path.exists() { - panic!("error: config-path `{}` does not exist", config_path.display()) - } - if !config_path.ends_with(Config::FILE_NAME) { - panic!("error: the config-path must be a path to a foundry.toml file") - } + let root = if let Some(config_path) = &args.project_paths.config_path { + assert!( + config_path.exists(), + "error: config-path `{}` does not exist", + config_path.display() + ); + assert!( + config_path.ends_with(Config::FILE_NAME), + "error: the config-path must be a path to a foundry.toml file" + ); let config_path = canonicalized(config_path); - Config::figment_with_root(config_path.parent().unwrap()) + config_path.parent().unwrap().to_path_buf() } else { - Config::figment_with_root(args.project_paths.project_root()) + args.project_paths.project_root() }; + let mut figment = Config::figment_with_root(root); // remappings should stack let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings()) @@ -220,7 +240,10 @@ impl Provider for BuildOpts { } if self.deny_warnings { - dict.insert("deny_warnings".to_string(), true.into()); + dict.insert("deny".to_string(), figment::value::Value::serialize(DenyLevel::Warnings)?); + _ = sh_warn!("`--deny-warnings` is being deprecated in favor of `--deny warnings`."); + } else if let Some(deny) = self.deny { + dict.insert("deny".to_string(), figment::value::Value::serialize(deny)?); } if self.via_ir { diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index 55c61dcbbedd7..e286f65101907 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -1,5 +1,5 @@ use clap::Parser; -use foundry_compilers::artifacts::{output_selection::ContractOutputSelection, EvmVersion}; +use foundry_compilers::artifacts::{EvmVersion, output_selection::ContractOutputSelection}; use serde::Serialize; mod core; @@ -8,6 +8,9 @@ pub use self::core::BuildOpts; mod paths; pub use self::paths::ProjectPathOpts; +mod utils; +pub use self::utils::*; + // A set of solc compiler settings that can be set via command line arguments, which are intended // to be merged into an existing `foundry_config::Config`. // diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index 263e03c14881a..70953887ace95 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -2,13 +2,13 @@ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::{ - figment, + Config, figment, figment::{ + Metadata, Profile, Provider, error::Kind::InvalidType, value::{Dict, Map, Value}, - Metadata, Profile, Provider, }, - find_project_root, remappings_from_env_var, Config, + find_project_root, remappings_from_env_var, }; use serde::Serialize; use std::path::PathBuf; diff --git a/crates/cli/src/opts/build/utils.rs b/crates/cli/src/opts/build/utils.rs new file mode 100644 index 0000000000000..1952886f36ee0 --- /dev/null +++ b/crates/cli/src/opts/build/utils.rs @@ -0,0 +1,225 @@ +use eyre::Result; +use foundry_compilers::{ + CompilerInput, Graph, Project, ProjectCompileOutput, ProjectPathsConfig, + artifacts::{Source, Sources}, + multi::{MultiCompilerLanguage, MultiCompilerParser}, + solc::{SOLC_EXTENSIONS, SolcLanguage, SolcVersionedInput}, +}; +use foundry_config::Config; +use rayon::prelude::*; +use solar::{interface::MIN_SOLIDITY_VERSION, sema::ParsingContext}; +use std::{ + collections::{HashSet, VecDeque}, + path::{Path, PathBuf}, +}; + +/// Configures a [`ParsingContext`] from [`Config`]. +/// +/// - Configures include paths, remappings +/// - Source files are added if `add_source_file` is set +/// - If no `project` is provided, it will spin up a new ephemeral project. +/// - If no `target_paths` are provided, all project files are processed. +/// - Only processes the subset of sources with the most up-to-date Solidity version. +pub fn configure_pcx( + pcx: &mut ParsingContext<'_>, + config: &Config, + project: Option<&Project>, + target_paths: Option<&[PathBuf]>, +) -> Result<()> { + // Process build options + let project = match project { + Some(project) => project, + None => &config.ephemeral_project()?, + }; + + let sources = match target_paths { + // If target files are provided, only process those sources + Some(targets) => { + let mut sources = Sources::new(); + for t in targets { + let path = dunce::canonicalize(t)?; + let source = Source::read(&path)?; + sources.insert(path, source); + } + sources + } + // Otherwise, process all project files + None => project.paths.read_input_files()?, + }; + + // Only process sources with latest Solidity version to avoid conflicts. + let graph = Graph::::resolve_sources(&project.paths, sources)?; + let (version, sources) = graph + // Resolve graph into mapping language -> version -> sources + .into_sources_by_version(project)? + .sources + .into_iter() + // Only interested in Solidity sources + .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity)) + .ok_or_else(|| eyre::eyre!("no Solidity sources"))? + .1 + .into_iter() + // Filter unsupported versions + .filter(|(v, _, _)| v >= &MIN_SOLIDITY_VERSION) + // Always pick the latest version + .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) + .map_or((MIN_SOLIDITY_VERSION, Sources::default()), |(v, s, _)| (v, s)); + + if sources.is_empty() { + sh_warn!("no files found. Solar doesn't support Solidity versions prior to 0.8.0")?; + } + + let solc = SolcVersionedInput::build( + sources, + config.solc_settings()?, + SolcLanguage::Solidity, + version, + ); + + configure_pcx_from_solc(pcx, &project.paths, &solc, true); + + Ok(()) +} + +/// Extracts Solar-compatible sources from a [`ProjectCompileOutput`]. +/// +/// # Note: +/// uses `output.graph().source_files()` and `output.artifact_ids()` rather than `output.sources()` +/// because sources aren't populated when build is skipped when there are no changes in the source +/// code. +pub fn get_solar_sources_from_compile_output( + config: &Config, + output: &ProjectCompileOutput, + target_paths: Option<&[PathBuf]>, + ignored_paths: Option<&[PathBuf]>, +) -> Result { + let is_solidity_file = |path: &Path| -> bool { + path.extension().and_then(|s| s.to_str()).is_some_and(|ext| SOLC_EXTENSIONS.contains(&ext)) + }; + + let is_ignored = |path: &Path| -> bool { + if let Some(ignored) = ignored_paths { + ignored.iter().any(|ignored_path| path == ignored_path) + } else { + false + } + }; + + // Collect source path targets + let mut source_paths: HashSet = if let Some(targets) = target_paths + && !targets.is_empty() + { + let mut source_paths = HashSet::new(); + let mut queue: VecDeque = targets + .iter() + .filter_map(|path| { + is_solidity_file(path).then(|| dunce::canonicalize(path).ok()).flatten() + }) + .collect(); + + while let Some(path) = queue.pop_front() { + if source_paths.insert(path.clone()) { + for import in output.graph().imports(path.as_path()) { + // Skip ignored imports to prevent solar from trying to compile them + if !is_ignored(import) { + queue.push_back(import.to_path_buf()); + } + } + } + } + + source_paths + } else { + output + .graph() + .source_files() + .filter_map(|idx| { + let path = output.graph().node_path(idx).to_path_buf(); + is_solidity_file(&path).then_some(path) + }) + .collect() + }; + + // Read all sources and find the latest version. + let (version, sources) = { + let (mut max_version, mut sources) = (MIN_SOLIDITY_VERSION, Sources::new()); + for (id, _) in output.artifact_ids() { + if let Ok(path) = dunce::canonicalize(&id.source) + && source_paths.remove(&path) + { + if id.version < MIN_SOLIDITY_VERSION { + continue; + } else if max_version < id.version { + max_version = id.version; + }; + + let source = Source::read(&path)?; + sources.insert(path, source); + } + } + + (max_version, sources) + }; + + let solc = SolcVersionedInput::build( + sources, + config.solc_settings()?, + SolcLanguage::Solidity, + version, + ); + + Ok(solc) +} + +/// Configures a [`ParsingContext`] from a [`ProjectCompileOutput`]. +pub fn configure_pcx_from_compile_output( + pcx: &mut ParsingContext<'_>, + config: &Config, + output: &ProjectCompileOutput, + target_paths: Option<&[PathBuf]>, +) -> Result<()> { + let solc = get_solar_sources_from_compile_output(config, output, target_paths, None)?; + configure_pcx_from_solc(pcx, &config.project_paths(), &solc, true); + Ok(()) +} + +/// Configures a [`ParsingContext`] from [`ProjectPathsConfig`] and [`SolcVersionedInput`]. +/// +/// - Configures include paths, remappings. +/// - Source files are added if `add_source_file` is set +pub fn configure_pcx_from_solc( + pcx: &mut ParsingContext<'_>, + project_paths: &ProjectPathsConfig, + vinput: &SolcVersionedInput, + add_source_files: bool, +) { + configure_pcx_from_solc_cli(pcx, project_paths, &vinput.cli_settings); + if add_source_files { + let sources = vinput + .input + .sources + .par_iter() + .filter_map(|(path, source)| { + pcx.sess.source_map().new_source_file(path.clone(), source.content.as_str()).ok() + }) + .collect::>(); + pcx.add_files(sources); + } +} + +fn configure_pcx_from_solc_cli( + pcx: &mut ParsingContext<'_>, + project_paths: &ProjectPathsConfig, + cli_settings: &foundry_compilers::solc::CliSettings, +) { + pcx.file_resolver + .set_current_dir(cli_settings.base_path.as_ref().unwrap_or(&project_paths.root)); + for remapping in &project_paths.remappings { + pcx.file_resolver.add_import_remapping(solar::sema::interface::config::ImportRemapping { + context: remapping.context.clone().unwrap_or_default(), + prefix: remapping.name.clone(), + path: remapping.path.clone(), + }); + } + pcx.file_resolver.add_include_paths(cli_settings.include_paths.iter().cloned()); +} diff --git a/crates/cli/src/opts/chain.rs b/crates/cli/src/opts/chain.rs index 2762481e08e86..3d8040cc99a88 100644 --- a/crates/cli/src/opts/chain.rs +++ b/crates/cli/src/opts/chain.rs @@ -33,7 +33,7 @@ impl TypedValueParser for ChainValueParser { Ok(Chain::from_id(id)) } else { // NamedChain::VARIANTS is a subset of all possible variants, since there are aliases: - // mumbai instead of polygon-mumbai etc + // amoy instead of polygon-amoy etc // // Parse first as NamedChain, if it fails parse with NamedChain::VARIANTS for displaying // the error to the user diff --git a/crates/cli/src/opts/dependency.rs b/crates/cli/src/opts/dependency.rs index b0e9eeadbb8e9..f65239c0d87c7 100644 --- a/crates/cli/src/opts/dependency.rs +++ b/crates/cli/src/opts/dependency.rs @@ -68,7 +68,7 @@ impl FromStr for Dependency { for (alias, real_org) in COMMON_ORG_ALIASES { if dependency.starts_with(alias) { dependency = dependency.replacen(alias, real_org, 1); - break + break; } } @@ -87,6 +87,8 @@ impl FromStr for Dependency { token.as_str(), project.trim_end_matches(".git") )) + } else if dependency.starts_with("git@") { + Some(format!("git@{brand}.{tld}:{}", project.trim_end_matches(".git"))) } else { Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))) } @@ -117,11 +119,11 @@ impl FromStr for Dependency { if tag_or_branch.is_none() { let maybe_tag_or_branch = split.next().unwrap(); - if let Some(actual_url) = split.next() { - if !maybe_tag_or_branch.contains('/') { - tag_or_branch = Some(maybe_tag_or_branch.to_string()); - url = actual_url; - } + if let Some(actual_url) = split.next() + && !maybe_tag_or_branch.contains('/') + { + tag_or_branch = Some(maybe_tag_or_branch.to_string()); + url = actual_url; } } @@ -160,7 +162,7 @@ mod tests { #[test] fn parses_dependencies() { - [ + for (input, expected_path, expected_tag, expected_alias) in [ ("gakonst/lootloose", "https://github.com/gakonst/lootloose", None, None), ("github.com/gakonst/lootloose", "https://github.com/gakonst/lootloose", None, None), ( @@ -176,17 +178,12 @@ mod tests { None, ), ( - "git@github.com:gakonst/lootloose@v1", - "https://github.com/gakonst/lootloose", - Some("v1"), - None, - ), - ( + "git@github.com:gakonst/lootloose@tag=v1", "git@github.com:gakonst/lootloose", - "https://github.com/gakonst/lootloose", - None, + Some("v1"), None, ), + ("git@github.com:gakonst/lootloose", "git@github.com:gakonst/lootloose", None, None), ( "https://gitlab.com/gakonst/lootloose", "https://gitlab.com/gakonst/lootloose", @@ -237,20 +234,18 @@ mod tests { Some("loot"), ), ( - "loot=git@github.com:gakonst/lootloose@v1", - "https://github.com/gakonst/lootloose", + "loot=git@github.com:gakonst/lootloose@tag=v1", + "git@github.com:gakonst/lootloose", Some("v1"), Some("loot"), ), - ] - .iter() - .for_each(|(input, expected_path, expected_tag, expected_alias)| { + ] { let dep = Dependency::from_str(input).unwrap(); assert_eq!(dep.url, Some(expected_path.to_string())); assert_eq!(dep.tag, expected_tag.map(ToString::to_string)); assert_eq!(dep.name, "lootloose"); assert_eq!(dep.alias, expected_alias.map(ToString::to_string)); - }); + } } #[test] @@ -270,28 +265,26 @@ mod tests { #[test] fn parses_contract_info() { - [ + for (input, expected_path, expected_name) in [ ( "src/contracts/Contracts.sol:Contract", Some("src/contracts/Contracts.sol"), "Contract", ), ("Contract", None, "Contract"), - ] - .iter() - .for_each(|(input, expected_path, expected_name)| { + ] { let contract = ContractInfo::from_str(input).unwrap(); assert_eq!(contract.path, expected_path.map(ToString::to_string)); assert_eq!(contract.name, expected_name.to_string()); - }); + } } #[test] fn contract_info_should_reject_without_name() { - ["src/contracts/", "src/contracts/Contracts.sol"].iter().for_each(|input| { + for input in ["src/contracts/", "src/contracts/Contracts.sol"] { let contract = ContractInfo::from_str(input); - assert!(contract.is_err()) - }); + assert!(contract.is_err()); + } } #[test] diff --git a/crates/common/src/evm.rs b/crates/cli/src/opts/evm.rs similarity index 70% rename from crates/common/src/evm.rs rename to crates/cli/src/opts/evm.rs index 4a7e459e8fa16..4fc437c7232f8 100644 --- a/crates/common/src/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -1,23 +1,20 @@ //! CLI arguments for configuring the EVM settings. -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256}; use clap::Parser; -use eyre::ContextCompat; use foundry_config::{ + Chain, Config, figment::{ - self, + self, Metadata, Profile, Provider, error::Kind::InvalidType, value::{Dict, Map, Value}, - Metadata, Profile, Provider, }, - Chain, Config, }; +use foundry_evm_networks::NetworkConfigs; use serde::Serialize; -use crate::shell; - -/// Map keyed by breakpoints char to their location (contract address, pc) -pub type Breakpoints = HashMap; +use crate::opts::RpcCommonOpts; +use foundry_common::shell; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. /// @@ -35,7 +32,7 @@ pub type Breakpoints = HashMap; /// ```ignore /// use foundry_config::Config; /// use forge::executor::opts::EvmOpts; -/// use foundry_common::evm::EvmArgs; +/// use foundry_cli::opts::EvmArgs; /// # fn t(args: EvmArgs) { /// let figment = Config::figment_with_root(".").merge(args); /// let opts = figment.extract::().unwrap(); @@ -44,31 +41,29 @@ pub type Breakpoints = HashMap; #[derive(Clone, Debug, Default, Serialize, Parser)] #[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc pub struct EvmArgs { - /// Fetch state over a remote endpoint instead of starting from an empty state. - /// - /// If you want to fetch state from a specific block number, see --fork-block-number. - #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")] - #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] - pub fork_url: Option, + /// Common RPC options (URL, timeout, rate limiting, etc.). + #[command(flatten)] + #[serde(flatten)] + pub rpc: RpcCommonOpts, /// Fetch state from a specific block number over a remote endpoint. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "BLOCK")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, /// Number of retries. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "RETRIES")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "RETRIES")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retries: Option, /// Initial retry backoff on encountering errors. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "BACKOFF")] + /// See --rpc-url. + #[arg(long, requires = "rpc_url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -78,7 +73,7 @@ pub struct EvmArgs { /// /// This flag overrides the project's configuration file. /// - /// See --fork-url. + /// See --rpc-url. #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, @@ -98,6 +93,11 @@ pub struct EvmArgs { #[serde(skip)] pub ffi: bool, + /// Whether to show `console.log` outputs in realtime during script/test execution + #[arg(long)] + #[serde(skip)] + pub live_logs: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. #[arg(long)] #[serde(skip)] @@ -108,26 +108,6 @@ pub struct EvmArgs { #[serde(skip_serializing_if = "Option::is_none")] pub create2_deployer: Option
, - /// Sets the number of assumed available compute units per second for this provider - /// - /// default value: 330 - /// - /// See also --fork-url and - #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] - pub compute_units_per_second: Option, - - /// Disables rate limiting for this node's provider. - /// - /// See also --fork-url and - #[arg( - long, - value_name = "NO_RATE_LIMITS", - help_heading = "Fork config", - visible_alias = "no-rate-limit" - )] - #[serde(skip)] - pub no_rpc_rate_limit: bool, - /// All ethereum environment related arguments #[command(flatten)] #[serde(flatten)] @@ -140,10 +120,10 @@ pub struct EvmArgs { #[serde(skip)] pub isolate: bool, - /// Whether to enable Odyssey features. - #[arg(long, alias = "alphanet")] + /// Network selection. + #[command(flatten)] #[serde(skip)] - pub odyssey: bool, + pub networks: NetworkConfigs, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -166,12 +146,12 @@ impl Provider for EvmArgs { dict.insert("ffi".to_string(), self.ffi.into()); } - if self.isolate { - dict.insert("isolate".to_string(), self.isolate.into()); + if self.live_logs { + dict.insert("live_logs".to_string(), self.live_logs.into()); } - if self.odyssey { - dict.insert("odyssey".to_string(), self.odyssey.into()); + if self.isolate { + dict.insert("isolate".to_string(), self.isolate.into()); } if self.always_use_create_2_factory { @@ -185,12 +165,24 @@ impl Provider for EvmArgs { dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into()); } - if self.no_rpc_rate_limit { - dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into()); + // Merge serde-skipped fields from the common RPC options. + if self.rpc.no_rpc_rate_limit { + dict.insert("no_rpc_rate_limit".to_string(), true.into()); + } + if self.rpc.accept_invalid_certs { + dict.insert("eth_rpc_accept_invalid_certs".to_string(), true.into()); + } + if self.rpc.no_proxy { + dict.insert("eth_rpc_no_proxy".to_string(), true.into()); } - if let Some(fork_url) = &self.fork_url { - dict.insert("eth_rpc_url".to_string(), fork_url.clone().into()); + // Only insert network flags when explicitly set via CLI to avoid overriding + // values from foundry.toml (NetworkConfigs is flattened in Config). + if let Some(name) = self.networks.active_network_name() { + dict.insert("network".to_string(), name.into()); + } + if self.networks.is_celo() { + dict.insert("celo".to_string(), true.into()); } Ok(Map::from([(Config::selected_profile(), dict)])) @@ -253,7 +245,7 @@ pub struct EnvArgs { pub block_prevrandao: Option, /// The block gas limit. - #[arg(long, visible_alias = "gas-limit", value_name = "GAS_LIMIT")] + #[arg(long, visible_alias = "gas-limit", value_name = "BLOCK_GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, @@ -266,16 +258,14 @@ pub struct EnvArgs { pub memory_limit: Option, /// Whether to disable the block gas limit checks. - #[arg(long, visible_alias = "no-gas-limit")] + #[arg(long, visible_aliases = &["no-block-gas-limit", "no-gas-limit"])] #[serde(skip_serializing_if = "std::ops::Not::not")] pub disable_block_gas_limit: bool, -} -impl EvmArgs { - /// Ensures that fork url exists and returns its reference. - pub fn ensure_fork_url(&self) -> eyre::Result<&String> { - self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.") - } + /// Whether to enable tx gas limit checks as imposed by Osaka (EIP-7825). + #[arg(long, visible_alias = "tx-gas-limit")] + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub enable_tx_gas_limit: bool, } /// We have to serialize chain IDs and not names because when extracting an EVM `Env`, it expects @@ -294,6 +284,40 @@ mod tests { use super::*; use foundry_config::NamedChain; + #[test] + fn compute_units_per_second_skips_when_none() { + let args = EvmArgs::default(); + let data = args.data().expect("provider data"); + let dict = data.get(&Config::selected_profile()).expect("profile dict"); + assert!( + !dict.contains_key("compute_units_per_second"), + "compute_units_per_second should be skipped when None" + ); + } + + #[test] + fn compute_units_per_second_present_when_some() { + let args = EvmArgs { + rpc: RpcCommonOpts { compute_units_per_second: Some(1000), ..Default::default() }, + ..Default::default() + }; + let data = args.data().expect("provider data"); + let dict = data.get(&Config::selected_profile()).expect("profile dict"); + let val = dict.get("compute_units_per_second").expect("cups present"); + assert_eq!(val, &Value::from(1000u64)); + } + + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + use clap::CommandFactory; + + let command = EvmArgs::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + #[test] fn can_parse_chain_id() { let args = EvmArgs { @@ -303,7 +327,7 @@ mod tests { let config = Config::from_provider(Config::figment().merge(args)).unwrap(); assert_eq!(config.chain, Some(NamedChain::Mainnet.into())); - let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "goerli"]); + let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "goerli"]); assert_eq!(env.chain, Some(NamedChain::Goerli.into())); } @@ -316,16 +340,16 @@ mod tests { let config = Config::from_provider(Config::figment().merge(args)).unwrap(); assert_eq!(config.memory_limit, Config::default().memory_limit); - let env = EnvArgs::parse_from(["foundry-common", "--memory-limit", "100"]); + let env = EnvArgs::parse_from(["foundry-cli", "--memory-limit", "100"]); assert_eq!(env.memory_limit, Some(100)); } #[test] fn test_chain_id() { - let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "1"]); + let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "1"]); assert_eq!(env.chain, Some(Chain::mainnet())); - let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "mainnet"]); + let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "mainnet"]); assert_eq!(env.chain, Some(Chain::mainnet())); let args = EvmArgs { env, ..Default::default() }; let config = Config::from_provider(Config::figment().merge(args)).unwrap(); diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index 665c504ab904d..fee75a5d34e87 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -18,7 +18,8 @@ pub struct GlobalArgs { /// - 2 (-vv): Print logs for all tests. /// - 3 (-vvv): Print execution traces for failing tests. /// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - /// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + /// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes and + /// backtraces with line numbers. #[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)] verbosity: Verbosity, @@ -30,6 +31,16 @@ pub struct GlobalArgs { #[arg(help_heading = "Display options", global = true, long, alias = "format-json", conflicts_with_all = &["quiet", "color"])] json: bool, + /// Format log messages as Markdown. + #[arg( + help_heading = "Display options", + global = true, + long, + alias = "markdown", + conflicts_with = "json" + )] + md: bool, + /// The color of the log messages. #[arg(help_heading = "Display options", global = true, long, value_enum)] color: Option, @@ -40,10 +51,28 @@ pub struct GlobalArgs { } impl GlobalArgs { + /// Check if `--markdown-help` was passed and print CLI reference as Markdown, then exit. + /// + /// This must be called **before** parsing arguments, since commands with required + /// subcommands would fail parsing before the flag is checked. + pub fn check_markdown_help() { + if std::env::args().any(|arg| arg == "--markdown-help") { + foundry_cli_markdown::print_help_markdown::(); + std::process::exit(0); + } + } + /// Initialize the global options. pub fn init(&self) -> eyre::Result<()> { // Set the global shell. - self.shell().set(); + let shell = self.shell(); + // Argument takes precedence over the env var global color choice. + match shell.color_choice() { + ColorChoice::Auto => {} + ColorChoice::Always => yansi::enable(), + ColorChoice::Never => yansi::disable(), + } + shell.set(); // Initialize the thread pool only if `threads` was requested to avoid unnecessary overhead. if self.threads.is_some() { @@ -51,9 +80,9 @@ impl GlobalArgs { } // Display a warning message if the current version is not stable. - if std::env::var("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_err() && - !self.json && - IS_NIGHTLY_VERSION + if IS_NIGHTLY_VERSION + && !self.json + && std::env::var_os("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_none() { let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE); } @@ -68,9 +97,12 @@ impl GlobalArgs { false => OutputMode::Normal, }; let color = self.json.then_some(ColorChoice::Never).or(self.color).unwrap_or_default(); - let format = match self.json { - true => OutputFormat::Json, - false => OutputFormat::Text, + let format = if self.json { + OutputFormat::Json + } else if self.md { + OutputFormat::Markdown + } else { + OutputFormat::Text }; Shell::new_with(format, mode, color, self.verbosity) @@ -80,6 +112,24 @@ impl GlobalArgs { pub fn force_init_thread_pool(&self) -> eyre::Result<()> { init_thread_pool(self.threads.unwrap_or(0)) } + + /// Creates a new tokio runtime. + #[track_caller] + pub fn tokio_runtime(&self) -> tokio::runtime::Runtime { + let mut builder = tokio::runtime::Builder::new_multi_thread(); + if let Some(threads) = self.threads + && threads > 0 + { + builder.worker_threads(threads); + } + builder.enable_all().build().expect("failed to create tokio runtime") + } + + /// Creates a new tokio runtime and blocks on the future. + #[track_caller] + pub fn block_on(&self, future: F) -> F::Output { + self.tokio_runtime().block_on(future) + } } /// Initialize the global thread pool. diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index 3b6b914c14aaa..0c074307445ab 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -1,13 +1,19 @@ mod build; mod chain; mod dependency; +mod evm; mod global; mod rpc; +mod rpc_common; +mod tempo; mod transaction; pub use build::*; pub use chain::*; pub use dependency::*; +pub use evm::*; pub use global::*; pub use rpc::*; +pub use rpc_common::*; +pub use tempo::*; pub use transaction::*; diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 2b508720a81a1..f846da5002354 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -1,15 +1,13 @@ -use crate::opts::ChainValueParser; -use alloy_chains::ChainKind; +use crate::opts::{ChainValueParser, RpcCommonOpts}; use clap::Parser; use eyre::Result; -use foundry_block_explorers::EtherscanApiVersion; use foundry_config::{ + Chain, Config, FigmentProviders, figment::{ - self, + self, Figment, Metadata, Profile, value::{Dict, Map}, - Metadata, Profile, }, - impl_figment_convert_cast, Chain, Config, + find_project_root, impl_figment_convert_cast, }; use foundry_wallets::WalletOpts; use serde::Serialize; @@ -18,10 +16,11 @@ use std::borrow::Cow; const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast"; #[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Rpc options")] pub struct RpcOpts { - /// The RPC endpoint, default value is http://localhost:8545. - #[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] - pub url: Option, + /// Common RPC options (URL, timeout, rate limiting, etc.). + #[command(flatten)] + pub common: RpcCommonOpts, /// Use the Flashbots RPC URL with fast mode (). /// @@ -43,17 +42,13 @@ pub struct RpcOpts { #[arg(long, env = "ETH_RPC_JWT_SECRET")] pub jwt_secret: Option, - /// Timeout for the RPC request in seconds. - /// - /// The specified timeout will be used to override the default timeout for RPC requests. - /// - /// Default value: 45 - #[arg(long, env = "ETH_RPC_TIMEOUT")] - pub rpc_timeout: Option, - /// Specify custom headers for RPC requests. #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] pub rpc_headers: Option>, + + /// Print the equivalent curl command instead of making the RPC request. + #[arg(long)] + pub curl: bool, } impl_figment_convert_cast!(RpcOpts); @@ -71,13 +66,23 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { - let url = match (self.flashbots, self.url.as_deref(), config) { - (true, ..) => Some(Cow::Borrowed(FLASHBOTS_URL)), - (false, Some(url), _) => Some(Cow::Borrowed(url)), - (false, None, Some(config)) => config.get_rpc_url().transpose()?, - (false, None, None) => None, - }; - Ok(url) + self.url_with_env(config, std::env::var("ETH_RPC_URL").ok()) + } + + fn url_with_env<'a>( + &'a self, + config: Option<&'a Config>, + env_url: Option, + ) -> Result>> { + if self.flashbots { + Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else if let Some(url) = self.common.rpc_url.as_deref() { + Ok(Some(Cow::Borrowed(url))) + } else if let Some(url) = env_url { + Ok(Some(Cow::Owned(url))) + } else { + self.common.url(config) + } } /// Returns the JWT secret. @@ -91,21 +96,30 @@ impl RpcOpts { } pub fn dict(&self) -> Dict { - let mut dict = Dict::new(); + let mut dict = self.common.dict(); + // `self.url(None)` already accounts for `flashbots` and the `ETH_RPC_URL` env var, + // so a single insert here covers both. if let Ok(Some(url)) = self.url(None) { dict.insert("eth_rpc_url".into(), url.into_owned().into()); } if let Ok(Some(jwt)) = self.jwt(None) { dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); } - if let Some(rpc_timeout) = self.rpc_timeout { - dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); - } if let Some(headers) = &self.rpc_headers { dict.insert("eth_rpc_headers".into(), headers.clone().into()); } + if self.curl { + dict.insert("eth_rpc_curl".into(), true.into()); + } dict } + + pub fn into_figment(self, all: bool) -> Figment { + let root = find_project_root(None).expect("could not determine project root"); + Config::with_root(&root) + .to_figment(if all { FigmentProviders::All } else { FigmentProviders::Cast }) + .merge(self) + } } #[derive(Clone, Debug, Default, Serialize, Parser)] @@ -115,16 +129,6 @@ pub struct EtherscanOpts { #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] pub key: Option, - /// The Etherscan API version. - #[arg( - short, - long = "etherscan-api-version", - alias = "api-version", - env = "ETHERSCAN_API_VERSION" - )] - #[serde(rename = "etherscan_api_version", skip_serializing_if = "Option::is_none")] - pub api_version: Option, - /// The chain name or EIP-155 chain ID. #[arg( short, @@ -166,16 +170,8 @@ impl EtherscanOpts { dict.insert("etherscan_api_key".into(), key.into()); } - if let Some(api_version) = &self.api_version { - dict.insert("etherscan_api_version".into(), api_version.to_string().into()); - } - if let Some(chain) = self.chain { - if let ChainKind::Id(id) = chain.kind() { - dict.insert("chain_id".into(), (*id).into()); - } else { - dict.insert("chain_id".into(), chain.to_string().into()); - } + dict.insert("chain_id".into(), chain.id().into()); } dict } @@ -217,6 +213,7 @@ impl figment::Provider for EthereumOpts { #[cfg(test)] mod tests { use super::*; + use clap::CommandFactory; #[test] fn parse_etherscan_opts() { @@ -228,4 +225,54 @@ mod tests { EtherscanOpts::parse_from(["foundry-cli", "--etherscan-api-key", ""]); assert!(!args.has_key()); } + + // + #[test] + fn named_chain_dict_inserts_numeric_id() { + // Chain 9745 is recognized as NamedChain::Plasma by alloy-chains. + // Previously, dict() would insert chain_id as the string "plasma", + // causing deserialization failure when EvmOpts expects u64. + let args = EtherscanOpts::parse_from(["foundry-cli", "--chain", "9745"]); + let dict = args.dict(); + let chain_id = dict.get("chain_id").expect("chain_id should be present"); + let id: u64 = chain_id.deserialize().expect("chain_id should deserialize as u64"); + assert_eq!(id, 9745); + } + + #[test] + fn rpc_url_arg_does_not_read_eth_rpc_url_env() { + let command = RpcOpts::command(); + let rpc_url = + command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg"); + + assert!(rpc_url.get_env().is_none()); + } + + #[test] + fn rpc_url_resolves_eth_rpc_url_env() { + let args = RpcOpts::default(); + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8545"); + } + + #[test] + fn explicit_rpc_url_takes_precedence_over_eth_rpc_url_env() { + let args = RpcOpts { + common: RpcCommonOpts { + rpc_url: Some("http://127.0.0.1:8546".to_string()), + ..Default::default() + }, + ..Default::default() + }; + let url = args + .url_with_env(None, Some("http://127.0.0.1:8545".to_string())) + .expect("url") + .expect("url"); + + assert_eq!(url.as_ref(), "http://127.0.0.1:8546"); + } } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs new file mode 100644 index 0000000000000..6a5fe5ed4e9e4 --- /dev/null +++ b/crates/cli/src/opts/rpc_common.rs @@ -0,0 +1,119 @@ +//! Common RPC options shared between `RpcOpts` and `EvmArgs`. + +use clap::Parser; +use eyre::Result; +use foundry_config::{ + Config, + figment::{ + self, Metadata, Profile, + value::{Dict, Map}, + }, +}; +use serde::Serialize; +use std::borrow::Cow; + +/// Common RPC-related options shared across CLI commands. +/// +/// This struct holds fields that both [`super::RpcOpts`] (cast) and +/// [`super::EvmArgs`] (forge/script) need, eliminating duplication and +/// making the two structs composable. +/// +/// Note: `ETH_RPC_URL` is intentionally **not** bound here as a clap env +/// fallback; otherwise it would be inherited by `EvmArgs` and silently +/// fork all `forge test` runs. Cast resolves `ETH_RPC_URL` explicitly +/// at the call site (see [`super::RpcOpts::url`]). +#[derive(Clone, Debug, Default, Serialize, Parser)] +pub struct RpcCommonOpts { + /// The RPC endpoint. + #[arg(short, long, visible_alias = "fork-url", value_name = "URL")] + #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] + pub rpc_url: Option, + + /// Allow insecure RPC connections (accept invalid HTTPS certificates). + /// + /// When the provider's inner runtime transport variant is HTTP, this configures the reqwest + /// client to accept invalid certificates. + #[arg(short = 'k', long = "insecure", default_value = "false")] + #[serde(skip)] + pub accept_invalid_certs: bool, + + /// Timeout for the RPC request in seconds. + /// + /// The specified timeout will be used to override the default timeout for RPC requests. + /// + /// Default value: 45 + #[arg(long, env = "ETH_RPC_TIMEOUT")] + #[serde(rename = "eth_rpc_timeout", skip_serializing_if = "Option::is_none")] + pub rpc_timeout: Option, + + /// Disable automatic proxy detection. + /// + /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where + /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment + /// variables and system proxy settings will be ignored. + #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")] + #[serde(skip)] + pub no_proxy: bool, + + /// Sets the number of assumed available compute units per second for this provider. + /// + /// default value: 330 + /// + /// See also + #[arg(long, alias = "cups", value_name = "CUPS")] + #[serde(skip_serializing_if = "Option::is_none")] + pub compute_units_per_second: Option, + + /// Disables rate limiting for this node's provider. + /// + /// See also + #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rate-limit")] + #[serde(skip)] + pub no_rpc_rate_limit: bool, +} + +impl figment::Provider for RpcCommonOpts { + fn metadata(&self) -> Metadata { + Metadata::named("RpcCommonOpts") + } + + fn data(&self) -> Result, figment::Error> { + Ok(Map::from([(Config::selected_profile(), self.dict())])) + } +} + +impl RpcCommonOpts { + /// Returns the RPC endpoint URL, resolving from CLI args or config. + pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + let url = match (self.rpc_url.as_deref(), config) { + (Some(url), _) => Some(Cow::Borrowed(url)), + (None, Some(config)) => config.get_rpc_url().transpose()?, + (None, None) => None, + }; + Ok(url) + } + + /// Builds a figment-compatible dictionary from these options. + pub fn dict(&self) -> Dict { + let mut dict = Dict::new(); + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); + } + if let Some(rpc_timeout) = self.rpc_timeout { + dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); + } + if self.accept_invalid_certs { + dict.insert("eth_rpc_accept_invalid_certs".into(), true.into()); + } + if self.no_proxy { + dict.insert("eth_rpc_no_proxy".into(), true.into()); + } + if let Some(cups) = self.compute_units_per_second { + dict.insert("compute_units_per_second".into(), cups.into()); + } + if self.no_rpc_rate_limit { + dict.insert("no_rpc_rate_limit".into(), true.into()); + } + dict + } +} diff --git a/crates/cli/src/opts/tempo.rs b/crates/cli/src/opts/tempo.rs new file mode 100644 index 0000000000000..e76af262c2489 --- /dev/null +++ b/crates/cli/src/opts/tempo.rs @@ -0,0 +1,423 @@ +use alloy_network::{Network, TransactionBuilder}; +use alloy_primitives::{Address, ruint::aliases::U256}; +use alloy_signer::{Signature, Signer}; +use clap::Parser; +use eyre::Result; +use foundry_common::{ + FoundryTransactionBuilder, + tempo::{TempoSponsor, resolve_tempo_sponsor_signer}, +}; +use std::{ + num::NonZeroU64, + path::PathBuf, + str::FromStr, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +use crate::utils::parse_fee_token_address; + +/// CLI options for Tempo transactions. +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Tempo")] +pub struct TempoOpts { + /// Fee token address for Tempo transactions. + /// + /// When set, builds a Tempo (type 0x76) transaction that pays gas fees + /// in the specified token. + /// + /// If this is not set, the fee token is chosen according to network rules. See the Tempo docs + /// for more information. + #[arg(long = "tempo.fee-token", value_parser = parse_fee_token_address)] + pub fee_token: Option
, + + /// Opt into TIP-1009 expiring-nonce mode with a validity window. + /// + /// Convenience flag that combines `--tempo.expiring-nonce` with a relative + /// `--tempo.valid-before`. Sets nonce_key = U256::MAX, nonce = 0, and valid_before = now + + /// seconds. + /// + /// Maximum value is 30 seconds. The transaction must be mined before the deadline or it + /// becomes permanently invalid, giving safe retry semantics: retries produce a fresh tx hash + /// and the old tx can never land late. + #[arg(long = "tempo.expires", value_name = "SECONDS", value_parser = parse_expires_seconds)] + pub expires: Option, + + /// Nonce key for Tempo parallelizable nonces. + /// + /// When set, builds a Tempo (type 0x76) transaction with the specified nonce key, + /// allowing multiple transactions with the same nonce but different keys + /// to be executed in parallel. If not set, the protocol nonce key (0) will be used. + /// + /// For more information see . + #[arg(long = "tempo.nonce-key", value_name = "NONCE_KEY", conflicts_with = "lane")] + pub nonce_key: Option, + + /// Named nonce lane for Tempo parallelizable nonces. + /// + /// Resolves a friendly lane name (e.g. `deploy`, `payments`) to a `nonce_key` via a + /// shared lanes file (default: `tempo.lanes.toml` at the project root). The lanes file + /// is a TOML map of `name = ` entries, e.g.: + /// + /// ```toml + /// deploy = 1 + /// ops = 2 + /// payments = 3 + /// ``` + /// + /// Mutually exclusive with `--tempo.nonce-key`. + #[arg(long = "tempo.lane", value_name = "NAME")] + pub lane: Option, + + /// Path to the Tempo lanes file used by `--tempo.lane`. + /// + /// Defaults to `tempo.lanes.toml` at the project root. + #[arg(long = "tempo.lanes-file", value_name = "PATH")] + pub lanes_file: Option, + + /// Sponsor (fee payer) address for Tempo sponsored transactions. + #[arg(long = "tempo.sponsor", value_name = "ADDRESS")] + pub sponsor: Option
, + + /// Sign Tempo sponsor digests in-band with the given signer URI. + /// + /// Supported forms include `env://VAR`, `keystore://PATH`, `account://NAME`, + /// `ledger://`, `trezor://`, `aws://`, `gcp://`, `turnkey://`, and + /// `private-key://KEY`. + #[arg( + long = "tempo.sponsor-signer", + value_name = "SIGNER", + requires = "sponsor", + conflicts_with = "sponsor_sig" + )] + pub sponsor_signer: Option, + + /// Sponsor (fee payer) signature for Tempo sponsored transactions. + /// + /// The sponsor signs the `fee_payer_signature_hash` to commit to paying gas fees + /// on behalf of the sender. Provide as a hex-encoded signature. + #[arg( + long = "tempo.sponsor-sig", + alias = "tempo.sponsor-signature", + value_parser = parse_signature, + requires = "sponsor", + conflicts_with = "sponsor_signer" + )] + pub sponsor_sig: Option, + + /// Print the sponsor signature hash and exit. + /// + /// Computes the `fee_payer_signature_hash` for the transaction so that a sponsor + /// knows what hash to sign. The transaction is not sent. + #[arg( + long = "tempo.print-sponsor-hash", + conflicts_with_all = &["sponsor", "sponsor_signer", "sponsor_sig"] + )] + pub print_sponsor_hash: bool, + + /// Access key ID for Tempo Keychain signature transactions. + /// + /// Used during gas estimation to override the key_id that would normally be + /// recovered from the signature. + #[arg(long = "tempo.key-id")] + pub key_id: Option
, + + /// Enable expiring nonce mode for Tempo transactions. + /// + /// Sets nonce to 0 and nonce_key to U256::MAX, enabling time-bounded transaction + /// validity via `--tempo.valid-before` and `--tempo.valid-after`. + #[arg(long = "tempo.expiring-nonce", requires = "valid_before", conflicts_with = "expires")] + pub expiring_nonce: bool, + + /// Upper bound timestamp for Tempo expiring nonce transactions. + /// + /// The transaction is only valid before this unix timestamp. + /// Requires `--tempo.expiring-nonce`. + #[arg(long = "tempo.valid-before", conflicts_with = "expires")] + pub valid_before: Option, + + /// Lower bound timestamp for Tempo expiring nonce transactions. + /// + /// The transaction is only valid after this unix timestamp. + /// Requires `--tempo.expiring-nonce`. + #[arg(long = "tempo.valid-after")] + pub valid_after: Option, +} + +impl TempoOpts { + /// Returns `true` if any Tempo-specific option is set. + pub const fn is_tempo(&self) -> bool { + self.fee_token.is_some() + || self.expires.is_some() + || self.nonce_key.is_some() + || self.lane.is_some() + || self.sponsor.is_some() + || self.sponsor_signer.is_some() + || self.sponsor_sig.is_some() + || self.print_sponsor_hash + || self.key_id.is_some() + || self.expiring_nonce + || self.valid_before.is_some() + || self.valid_after.is_some() + } + + /// Returns the absolute `valid_before` unix timestamp derived from `--tempo.expires`, if set. + pub fn expires_at(&self) -> Option { + let secs = self.expires?; + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Some(now.as_secs() + secs) + } + + /// Resolves `--tempo.expires` into concrete expiring-nonce fields. + /// + /// This computes the relative deadline once so later calls to [`Self::apply`] reuse the same + /// `valid_before` timestamp instead of deriving a fresh one. + pub fn resolve_expires(&mut self) -> Option { + let ts = self.expires_at()?; + self.expiring_nonce = true; + self.valid_before = Some(ts); + self.expires = None; + Some(ts) + } + + /// Returns `true` if a sponsor signature should be attached before submission. + pub const fn has_sponsor_submission(&self) -> bool { + self.sponsor.is_some() || self.sponsor_signer.is_some() || self.sponsor_sig.is_some() + } + + /// Resolves sponsor CLI options into a reusable sponsor config for transaction submission. + pub async fn sponsor_config(&self) -> Result> { + let Some(sponsor) = self.sponsor else { + return Ok(None); + }; + + let signer = if let Some(spec) = &self.sponsor_signer { + Some(Arc::new(Box::pin(resolve_tempo_sponsor_signer(spec)).await?)) + } else { + None + }; + + if let Some(signer) = &signer { + let signer_address = signer.address(); + if signer_address != sponsor { + eyre::bail!( + "Tempo sponsor signer address {signer_address} does not match --tempo.sponsor {sponsor}" + ); + } + } + + if signer.is_none() && self.sponsor_sig.is_none() { + eyre::bail!( + "--tempo.sponsor requires either --tempo.sponsor-signer or --tempo.sponsor-sig" + ); + } + + Ok(Some(TempoSponsor::new(sponsor, signer, self.sponsor_sig))) + } + + /// Applies Tempo-specific options to a transaction request. + /// + /// All setters are no-ops for non-Tempo networks, so this is safe to call unconditionally. + pub fn apply(&self, tx: &mut N::TransactionRequest, nonce: Option) + where + N::TransactionRequest: FoundryTransactionBuilder, + { + // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX. + // --tempo.expires is a convenience alias that also sets valid_before = now + duration. + if self.expiring_nonce || self.expires.is_some() { + tx.set_nonce(0); + tx.set_nonce_key(U256::MAX); + } else { + if let Some(nonce) = nonce { + tx.set_nonce(nonce); + } + if let Some(nonce_key) = self.nonce_key { + tx.set_nonce_key(nonce_key); + } + } + + if let Some(fee_token) = self.fee_token { + tx.set_fee_token(fee_token); + } + + // --tempo.expires sets valid_before relative to now; --tempo.valid-before takes a raw + // unix timestamp. The two flags are mutually exclusive (enforced by clap). + let effective_valid_before = self.expires_at().or(self.valid_before); + if let Some(valid_before) = effective_valid_before + && let Some(v) = NonZeroU64::new(valid_before) + { + tx.set_valid_before(v); + } + if let Some(valid_after) = self.valid_after + && let Some(v) = NonZeroU64::new(valid_after) + { + tx.set_valid_after(v); + } + + if let Some(key_id) = self.key_id { + tx.set_key_id(key_id); + } + + // Force AA tx type if sponsoring or printing sponsor hash. + // Note: the fee_payer_signature is NOT set here. It must be applied AFTER + // gas estimation so that `--tempo.print-sponsor-hash` and + // `--tempo.sponsor-signature` produce identical gas estimates. Callers + // should call `set_fee_payer_signature` on the built tx request. + if (self.has_sponsor_submission() || self.print_sponsor_hash) && tx.nonce_key().is_none() { + tx.set_nonce_key(U256::ZERO); + } + } +} + +fn parse_signature(s: &str) -> Result { + Signature::from_str(s).map_err(|e| format!("invalid signature: {e}")) +} + +/// Parses a seconds value for `--tempo.expires`, capped at the protocol maximum of 30 seconds. +fn parse_expires_seconds(s: &str) -> Result { + let secs: u64 = s + .parse() + .map_err(|_| format!("invalid value '{s}': expected an integer number of seconds"))?; + if secs > 30 { + return Err(format!("expires must be at most 30 seconds (got {secs})")); + } + Ok(secs) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + + #[test] + fn parses_lane_arg() { + let opts = TempoOpts::try_parse_from(["", "--tempo.lane", "deploy"]).unwrap(); + assert_eq!(opts.lane.as_deref(), Some("deploy")); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn lane_conflicts_with_nonce_key() { + let err = + TempoOpts::try_parse_from(["", "--tempo.lane", "deploy", "--tempo.nonce-key", "1"]) + .unwrap_err(); + assert!( + err.to_string().contains("cannot be used with"), + "expected clap conflict error, got: {err}", + ); + } + + #[test] + fn parse_expires_flag() { + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "30"]).unwrap(); + assert_eq!(opts.expires, Some(30)); + + let opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + assert_eq!(opts.expires, Some(10)); + + // exceeds 30s maximum + assert!(TempoOpts::try_parse_from(["", "--tempo.expires", "31"]).is_err()); + + // conflicts with --tempo.expiring-nonce + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.expires", + "30", + "--tempo.expiring-nonce", + "--tempo.valid-before", + "999" + ]) + .is_err() + ); + } + + #[test] + fn resolve_expires_materializes_valid_before() { + let before = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + let mut opts = TempoOpts::try_parse_from(["", "--tempo.expires", "10"]).unwrap(); + + let resolved = opts.resolve_expires().unwrap(); + let after = + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs(); + + assert!(resolved >= before + 10); + assert!(resolved <= after + 10); + assert!(opts.expiring_nonce); + assert_eq!(opts.valid_before, Some(resolved)); + assert_eq!(opts.expires, None); + assert_eq!(opts.expires_at(), None); + } + + #[test] + fn parse_fee_token_id() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.fee-token", + "0x20C0000000000000000000000000000000000002", + ]) + .unwrap(); + assert_eq!(opts.fee_token, Some(address!("0x20C0000000000000000000000000000000000002")),); + + // AlphaUSD token ID is 1u64 + let opts_with_id = TempoOpts::try_parse_from(["", "--tempo.fee-token", "1"]).unwrap(); + assert_eq!( + opts_with_id.fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")), + ); + } + + #[test] + fn parse_sponsor_signer() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert_eq!(opts.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + assert!(opts.sponsor_sig.is_none()); + assert!(opts.is_tempo()); + assert!(opts.has_sponsor_submission()); + } + + #[test] + fn sponsor_signer_requires_sponsor() { + assert!( + TempoOpts::try_parse_from(["", "--tempo.sponsor-signer", "env://SPONSOR"]).is_err() + ); + } + + #[test] + fn parse_sponsor_signature_alias() { + let opts = TempoOpts::try_parse_from([ + "", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signature", + "0x0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b", + ]) + .unwrap(); + + assert_eq!(opts.sponsor, Some(address!("0x1111111111111111111111111111111111111111"))); + assert!(opts.sponsor_sig.is_some()); + } + + #[test] + fn print_sponsor_hash_conflicts_with_sponsor_submission() { + assert!( + TempoOpts::try_parse_from([ + "", + "--tempo.print-sponsor-hash", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + ]) + .is_err() + ); + } +} diff --git a/crates/cli/src/opts/transaction.rs b/crates/cli/src/opts/transaction.rs index c0e229c35cda3..7731422c95df5 100644 --- a/crates/cli/src/opts/transaction.rs +++ b/crates/cli/src/opts/transaction.rs @@ -1,10 +1,13 @@ use std::str::FromStr; +use super::TempoOpts; use crate::utils::{parse_ether_value, parse_json}; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; -use alloy_primitives::{hex, Address, U256, U64}; +use alloy_network::{Network, TransactionBuilder}; +use alloy_primitives::{Address, U64, U256, hex}; use alloy_rlp::Decodable; use clap::Parser; +use foundry_common::FoundryTransactionBuilder; /// CLI helper to parse a EIP-7702 authorization list. /// Can be either a hex-encoded signed authorization or an address. @@ -76,11 +79,18 @@ pub struct TransactionOpts { #[arg(long)] pub legacy: bool, - /// Send a EIP-4844 blob transaction. + /// Send a blob transaction using EIP-7594 (PeerDAS) format. + /// + /// Note: Use with `--eip4844` for the legacy EIP-4844 format. #[arg(long, conflicts_with = "legacy")] pub blob: bool, - /// Gas price for EIP-4844 blob transaction. + /// Send a blob transaction using EIP-4844 (legacy) format instead of EIP-7594. Must be used + /// with `--blob`. + #[arg(long, conflicts_with = "legacy", requires = "blob")] + pub eip4844: bool, + + /// Gas price for EIP-7594/EIP-4844 blob transaction. #[arg(long, conflicts_with = "legacy", value_parser = parse_ether_value, env = "ETH_BLOB_GAS_PRICE", value_name = "BLOB_PRICE")] pub blob_gas_price: Option, @@ -88,7 +98,7 @@ pub struct TransactionOpts { /// /// Can be either a hex-encoded signed authorization or an address. #[arg(long, conflicts_with_all = &["legacy", "blob"])] - pub auth: Option, + pub auth: Vec, /// EIP-2930 access list. /// @@ -97,6 +107,44 @@ pub struct TransactionOpts { /// the `cast access-list` command. #[arg(long, value_parser = parse_json::)] pub access_list: Option>, + + #[command(flatten)] + pub tempo: TempoOpts, +} + +impl TransactionOpts { + /// Applies gas, value, fee, and network-specific options to a transaction request. + pub fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if let Some(gas_limit) = self.gas_limit { + tx.set_gas_limit(gas_limit.to()); + } + + if let Some(value) = self.value { + tx.set_value(value); + } + + if let Some(gas_price) = self.gas_price { + if legacy { + tx.set_gas_price(gas_price.to()); + } else { + tx.set_max_fee_per_gas(gas_price.to()); + } + } + + if !legacy && let Some(priority_fee) = self.priority_gas_price { + tx.set_max_priority_fee_per_gas(priority_fee.to()); + } + + if let Some(max_blob_fee) = self.blob_gas_price { + tx.set_max_fee_per_blob_gas(max_blob_fee.to()) + } + + // set network-specific options + self.tempo.apply::(tx, self.nonce.map(|n| n.to())); + } } #[cfg(test)] diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs index d752312580df4..13127ec368b5b 100644 --- a/crates/cli/src/utils/abi.rs +++ b/crates/cli/src/utils/abi.rs @@ -1,36 +1,40 @@ use alloy_chains::Chain; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; -use alloy_primitives::{hex, Address}; -use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_primitives::{Address, hex}; +use alloy_provider::{Network, Provider}; use eyre::{OptionExt, Result}; -use foundry_block_explorers::EtherscanApiVersion; -use foundry_common::abi::{encode_function_args, get_func, get_func_etherscan}; +use foundry_common::abi::{ + encode_function_args, encode_function_args_raw, get_func, get_func_etherscan, +}; use futures::future::join_all; -async fn resolve_name_args>(args: &[String], provider: &P) -> Vec { +async fn resolve_name_args>( + args: &[String], + provider: &P, +) -> Vec { join_all(args.iter().map(|arg| async { if arg.contains('.') { - let addr = NameOrAddress::Name(arg.to_string()).resolve(provider).await; + let addr = NameOrAddress::Name(arg.clone()).resolve(provider).await; match addr { Ok(addr) => addr.to_string(), - Err(_) => arg.to_string(), + Err(_) => arg.clone(), } } else { - arg.to_string() + arg.clone() } })) .await } -pub async fn parse_function_args>( +pub async fn parse_function_args>( sig: &str, args: Vec, to: Option
, chain: Chain, provider: &P, etherscan_api_key: Option<&str>, - etherscan_api_version: EtherscanApiVersion, + etherscan_api_url: Option<&str>, ) -> Result<(Vec, Option)> { if sig.trim().is_empty() { eyre::bail!("Function signature or calldata must be provided.") @@ -38,20 +42,32 @@ pub async fn parse_function_args>( let args = resolve_name_args(&args, provider).await; + // Try to decode as hex calldata first, otherwise treat as function signature if let Ok(data) = hex::decode(sig) { - return Ok((data, None)) + return Ok((data, None)); + } else if sig.starts_with("0x") || sig.starts_with("0X") { + let e = hex::decode(sig).unwrap_err(); + eyre::bail!("Invalid hex calldata '{}': {e}", sig); } let func = if sig.contains('(') { // a regular function signature with parentheses get_func(sig)? } else { + info!( + "function signature does not contain parentheses, fetching function data from Etherscan" + ); let etherscan_api_key = etherscan_api_key.ok_or_eyre( - "If you wish to fetch function data from Etherscan, please provide an Etherscan API key.", + "Function signature does not contain parentheses. If you wish to fetch function data from Etherscan, please provide an API key.", )?; let to = to.ok_or_eyre("A 'to' address must be provided to fetch function data.")?; - get_func_etherscan(sig, to, &args, chain, etherscan_api_key, etherscan_api_version).await? + get_func_etherscan(sig, to, &args, chain, etherscan_api_key, etherscan_api_url).await? }; - Ok((encode_function_args(&func, &args)?, Some(func))) + if to.is_none() { + // if this is a CREATE call we must exclude the (constructor) function selector: https://github.com/foundry-rs/foundry/issues/10947 + Ok((encode_function_args_raw(&func, &args)?, Some(func))) + } else { + Ok((encode_function_args(&func, &args)?, Some(func))) + } } diff --git a/crates/cli/src/utils/allocator.rs b/crates/cli/src/utils/allocator.rs index 2431e35d08cff..cf874e96efb1e 100644 --- a/crates/cli/src/utils/allocator.rs +++ b/crates/cli/src/utils/allocator.rs @@ -20,8 +20,7 @@ cfg_if::cfg_if! { // Wrap the allocator if the `tracy-allocator` feature is enabled. cfg_if::cfg_if! { if #[cfg(feature = "tracy-allocator")] { - type AllocatorWrapper = tracy_client::ProfiledAllocator; - tracy_client::register_demangler!(); + type AllocatorWrapper = tracing_tracy::client::ProfiledAllocator; const fn new_allocator_wrapper() -> AllocatorWrapper { AllocatorWrapper::new(AllocatorInner {}, 100) } diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 5f6d5b1074a92..4563760db4db1 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -1,39 +1,29 @@ use alloy_json_abi::JsonAbi; -use alloy_primitives::Address; use eyre::{Result, WrapErr}; -use foundry_common::{ - compile::ProjectCompiler, fs, selectors::SelectorKind, shell, ContractsByArtifact, - TestFunctionExt, -}; +use foundry_common::{TestFunctionExt, fs, fs::json_files, selectors::SelectorKind, shell}; use foundry_compilers::{ - artifacts::{CompactBytecode, Settings}, - cache::{CacheEntry, CompilerCache}, - utils::read_json_file, - Artifact, ArtifactId, ProjectCompileOutput, + Artifact, ArtifactId, ProjectCompileOutput, artifacts::CompactBytecode, utils::read_json_file, }; -use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; -use foundry_debugger::Debugger; +use foundry_config::{Chain, Config, NamedChain, error::ExtractConfigError, figment::Figment}; use foundry_evm::{ + core::evm::FoundryEvmNetwork, executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ - debug::{ContractSources, DebugTraceIdentifier}, - decode_trace_arena, - identifier::{SignaturesCache, SignaturesIdentifier, TraceIdentifiers}, - render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, + CallTraceDecoder, TraceKind, Traces, decode_trace_arena, identifier::SignaturesCache, + prune_trace_depth, render_trace_arena_inner, }, }; use std::{ fmt::Write, path::{Path, PathBuf}, - str::FromStr, }; use yansi::Paint; -/// Given a `Project`'s output, removes the matching ABI, Bytecode and -/// Runtime Bytecode of the given contract. +/// Given a `Project`'s output, finds the contract by path and name and returns its +/// ABI, creation bytecode, and `ArtifactId`. #[track_caller] -pub fn remove_contract( +pub fn find_contract_artifacts( output: ProjectCompileOutput, path: &Path, name: &str, @@ -48,14 +38,14 @@ pub fn remove_contract( } }) else { let mut err = format!("could not find artifact: `{name}`"); - if let Some(suggestion) = super::did_you_mean(name, other).pop() { - if suggestion != name { - err = format!( - r#"{err} + if let Some(suggestion) = super::did_you_mean(name, other).pop() + && suggestion != name + { + err = format!( + r#"{err} Did you mean `{suggestion}`?"# - ); - } + ); } eyre::bail!(err) }; @@ -73,53 +63,14 @@ pub fn remove_contract( Ok((abi, bin, id)) } -/// Helper function for finding a contract by ContractName -// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a -// contract name? -pub fn get_cached_entry_by_name( - cache: &CompilerCache, - name: &str, -) -> Result<(PathBuf, CacheEntry)> { - let mut cached_entry = None; - let mut alternatives = Vec::new(); - - for (abs_path, entry) in &cache.files { - for artifact_name in entry.artifacts.keys() { - if artifact_name == name { - if cached_entry.is_some() { - eyre::bail!( - "contract with duplicate name `{}`. please pass the path instead", - name - ) - } - cached_entry = Some((abs_path.to_owned(), entry.to_owned())); - } else { - alternatives.push(artifact_name); - } - } - } - - if let Some(entry) = cached_entry { - return Ok(entry); - } - - let mut err = format!("could not find artifact: `{name}`"); - if let Some(suggestion) = super::did_you_mean(name, &alternatives).pop() { - err = format!( - r#"{err} - - Did you mean `{suggestion}`?"# - ); - } - eyre::bail!(err) -} - /// Returns error if constructor has arguments. pub fn ensure_clean_constructor(abi: &JsonAbi) -> Result<()> { - if let Some(constructor) = &abi.constructor { - if !constructor.inputs.is_empty() { - eyre::bail!("Contract constructor should have no arguments. Add those arguments to `run(...)` instead, and call it with `--sig run(...)`."); - } + if let Some(constructor) = &abi.constructor + && !constructor.inputs.is_empty() + { + eyre::bail!( + "Contract constructor should have no arguments. Add those arguments to `run(...)` instead, and call it with `--sig run(...)`." + ); } Ok(()) } @@ -161,28 +112,34 @@ pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { /// True if the network calculates gas costs differently. pub fn has_different_gas_calc(chain_id: u64) -> bool { - if let Some(chain) = Chain::from(chain_id).named() { - return chain.is_arbitrum() || - matches!( + let chain = Chain::from(chain_id); + if let Some(chain) = chain.named() { + return chain.is_tempo() + || chain.is_arbitrum() + || chain.is_elastic() + || matches!( chain, - NamedChain::Acala | - NamedChain::AcalaMandalaTestnet | - NamedChain::AcalaTestnet | - NamedChain::Etherlink | - NamedChain::EtherlinkTestnet | - NamedChain::Karura | - NamedChain::KaruraTestnet | - NamedChain::Mantle | - NamedChain::MantleSepolia | - NamedChain::MantleTestnet | - NamedChain::Moonbase | - NamedChain::Moonbeam | - NamedChain::MoonbeamDev | - NamedChain::Moonriver | - NamedChain::Metis | - NamedChain::Abstract | - NamedChain::ZkSync | - NamedChain::ZkSyncTestnet + NamedChain::Acala + | NamedChain::AcalaMandalaTestnet + | NamedChain::AcalaTestnet + | NamedChain::Etherlink + | NamedChain::EtherlinkShadownet + | NamedChain::Karura + | NamedChain::KaruraTestnet + | NamedChain::Kusama + | NamedChain::Mantle + | NamedChain::MantleSepolia + | NamedChain::MegaEth + | NamedChain::MegaEthTestnet + | NamedChain::Metis + | NamedChain::Monad + | NamedChain::MonadTestnet + | NamedChain::Moonbase + | NamedChain::Moonbeam + | NamedChain::MoonbeamDev + | NamedChain::Moonriver + | NamedChain::Polkadot + | NamedChain::PolkadotTestnet ); } false @@ -239,6 +196,10 @@ pub trait LoadConfig { let mut evm_opts = figment.extract::().map_err(ExtractConfigError::new)?; let config = Config::from_provider(figment)?.sanitized(); + if config.networks != Default::default() { + evm_opts.networks = config.networks; + } + // update the fork url if it was an alias if let Some(fork_url) = config.get_rpc_url() { trace!(target: "forge::config", ?fork_url, "Update EvmOpts fork url"); @@ -290,22 +251,25 @@ pub struct TraceResult { impl TraceResult { /// Create a new [`TraceResult`] from a [`RawCallResult`]. - pub fn from_raw(raw: RawCallResult, trace_kind: TraceKind) -> Self { + pub fn from_raw( + raw: RawCallResult, + trace_kind: TraceKind, + ) -> Self { let RawCallResult { gas_used, traces, reverted, .. } = raw; Self { success: !reverted, traces: traces.map(|arena| vec![(trace_kind, arena)]), gas_used } } } -impl From for TraceResult { - fn from(result: DeployResult) -> Self { +impl From> for TraceResult { + fn from(result: DeployResult) -> Self { Self::from_raw(result.raw, TraceKind::Deployment) } } -impl TryFrom> for TraceResult { - type Error = EvmError; +impl TryFrom, EvmError>> for TraceResult { + type Error = EvmError; - fn try_from(value: Result) -> Result { + fn try_from(value: Result, EvmError>) -> Result { match value { Ok(result) => Ok(Self::from(result)), Err(EvmError::Execution(err)) => Ok(Self::from_raw(err.raw, TraceKind::Deployment)), @@ -314,103 +278,18 @@ impl TryFrom> for TraceResult { } } -impl From for TraceResult { - fn from(result: RawCallResult) -> Self { +impl From> for TraceResult { + fn from(result: RawCallResult) -> Self { Self::from_raw(result, TraceKind::Execution) } } -impl TryFrom> for TraceResult { - type Error = EvmError; - - fn try_from(value: Result) -> Result { - match value { - Ok(result) => Ok(Self::from(result)), - Err(err) => Err(EvmError::from(err)), - } - } -} - -/// labels the traces, conditionally prints them or opens the debugger -pub async fn handle_traces( - mut result: TraceResult, - config: &Config, - chain: Option, - labels: Vec, - with_local_artifacts: bool, - debug: bool, - decode_internal: bool, -) -> Result<()> { - let (known_contracts, mut sources) = if with_local_artifacts { - let _ = sh_println!("Compiling project to generate artifacts"); - let project = config.project()?; - let compiler = ProjectCompiler::new(); - let output = compiler.compile(&project)?; - ( - Some(ContractsByArtifact::new( - output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), - )), - ContractSources::from_project_output(&output, project.root(), None)?, - ) - } else { - (None, ContractSources::default()) - }; - - let labels = labels.iter().filter_map(|label_str| { - let mut iter = label_str.split(':'); - - if let Some(addr) = iter.next() { - if let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) { - return Some((address, label.to_string())); - } - } - None - }); - let config_labels = config.labels.clone().into_iter(); - - let mut builder = CallTraceDecoderBuilder::new() - .with_labels(labels.chain(config_labels)) - .with_signature_identifier(SignaturesIdentifier::from_config(config)?); - let mut identifier = TraceIdentifiers::new().with_etherscan(config, chain)?; - if let Some(contracts) = &known_contracts { - builder = builder.with_known_contracts(contracts); - identifier = identifier.with_local(contracts); - } - - let mut decoder = builder.build(); - - for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { - decoder.identify(trace, &mut identifier); - } - - if decode_internal || debug { - if let Some(ref etherscan_identifier) = identifier.etherscan { - sources.merge(etherscan_identifier.get_compiled_contracts().await?); - } - - if debug { - let mut debugger = Debugger::builder() - .traces(result.traces.expect("missing traces")) - .decoder(&decoder) - .sources(sources) - .build(); - debugger.try_run_tui()?; - return Ok(()) - } - - decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); - } - - print_traces(&mut result, &decoder, shell::verbosity() > 0, shell::verbosity() > 4).await?; - - Ok(()) -} - pub async fn print_traces( result: &mut TraceResult, decoder: &CallTraceDecoder, verbose: bool, state_changes: bool, + trace_depth: Option, ) -> Result<()> { let traces = result.traces.as_mut().expect("No traces found"); @@ -420,6 +299,11 @@ pub async fn print_traces( for (_, arena) in traces { decode_trace_arena(arena, decoder).await; + + if let Some(trace_depth) = trace_depth { + prune_trace_depth(arena, trace_depth); + } + sh_println!("{}", render_trace_arena_inner(arena, verbose, state_changes))?; } @@ -440,7 +324,10 @@ pub async fn print_traces( /// Traverse the artifacts in the project to generate local signatures and merge them into the cache /// file. -pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_dir: &Path) -> Result<()> { +pub fn cache_local_signatures(output: &ProjectCompileOutput) -> Result<()> { + let Some(cache_dir) = Config::foundry_cache_dir() else { + eyre::bail!("Failed to get `cache_dir` to generate local signatures."); + }; let path = cache_dir.join("signatures"); let mut signatures = SignaturesCache::load(&path); for (_, artifact) in output.artifacts() { @@ -458,3 +345,72 @@ pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_dir: &Path) - signatures.save(&path); Ok(()) } + +/// Traverses all files at `folder_path`, parses any JSON ABI files found, +/// and caches their function/event/error signatures to the local signatures cache. +pub fn cache_signatures_from_abis(folder_path: impl AsRef) -> Result<()> { + let Some(cache_dir) = Config::foundry_cache_dir() else { + eyre::bail!("Failed to get `cache_dir` to generate local signatures."); + }; + let path = cache_dir.join("signatures"); + let mut signatures = SignaturesCache::load(&path); + + json_files(folder_path.as_ref()) + .filter_map(|path| std::fs::read_to_string(&path).ok()) + .filter_map(|content| serde_json::from_str::(&content).ok()) + .for_each(|json_abi| signatures.extend_from_abi(&json_abi)); + + signatures.save(&path); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn test_cache_signatures_from_abis() { + let temp_dir = tempdir().unwrap(); + let abi_json = r#"[ + { + "type": "function", + "name": "myCustomFunction", + "inputs": [{"name": "amount", "type": "uint256"}], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "MyCustomEvent", + "inputs": [{"name": "value", "type": "uint256", "indexed": false}], + "anonymous": false + }, + { + "type": "error", + "name": "MyCustomError", + "inputs": [{"name": "code", "type": "uint256"}] + } + ]"#; + + let abi_path = temp_dir.path().join("test.json"); + fs::write(&abi_path, abi_json).unwrap(); + + cache_signatures_from_abis(temp_dir.path()).unwrap(); + + let cache_dir = Config::foundry_cache_dir().unwrap(); + let cache_path = cache_dir.join("signatures"); + let cache = SignaturesCache::load(&cache_path); + + let func_selector: alloy_primitives::Selector = "0x2e2dbaf7".parse().unwrap(); + assert!(cache.contains_key(&SelectorKind::Function(func_selector))); + + let event_selector: alloy_primitives::B256 = + "0x8cc20c47f3a2463817352f75dec0dbf43a7a771b5f6817a92bd5724c1f4aa745".parse().unwrap(); + assert!(cache.contains_key(&SelectorKind::Event(event_selector))); + + let error_selector: alloy_primitives::Selector = "0xd35f45de".parse().unwrap(); + assert!(cache.contains_key(&SelectorKind::Error(error_selector))); + } +} diff --git a/crates/cli/src/utils/default_directives.txt b/crates/cli/src/utils/default_directives.txt new file mode 100644 index 0000000000000..60b9cccd0558e --- /dev/null +++ b/crates/cli/src/utils/default_directives.txt @@ -0,0 +1,11 @@ +[ + // Low level networking + "hyper=off", + "hyper_util=off", + "h2=off", + "rustls=off", + // Tokio + "mio=off", + // Too verbose + "jsonpath_lib=off", +] diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 391d519d05621..bf9bf939f9e25 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1,18 +1,19 @@ use alloy_json_abi::JsonAbi; -use alloy_primitives::U256; -use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_primitives::{Address, U256, map::HashMap}; +use alloy_provider::{Network, Provider, RootProvider, network::AnyNetwork}; use eyre::{ContextCompat, Result}; -use foundry_common::{ - provider::{ProviderBuilder, RetryProvider}, - shell, -}; +use foundry_common::{provider::ProviderBuilder, shell}; use foundry_config::{Chain, Config}; +use itertools::Itertools; +use path_slash::PathExt; +use regex::Regex; use serde::de::DeserializeOwned; use std::{ ffi::OsStr, - future::Future, path::{Path, PathBuf}, process::{Command, Output, Stdio}, + str::FromStr, + sync::LazyLock, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing_subscriber::prelude::*; @@ -29,6 +30,9 @@ pub use abi::*; mod allocator; pub use allocator::*; +mod tempo; +pub use tempo::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; @@ -41,6 +45,13 @@ pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; +/// Regex used to parse `.gitmodules` file and capture the submodule path and branch. +pub static SUBMODULE_BRANCH_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r#"\[submodule "([^"]+)"\](?:[^\[]*?branch = ([^\s]+))"#).unwrap()); +/// Regex used to parse `git submodule status` output. +pub static SUBMODULE_STATUS_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"^[\s+-]?([a-f0-9]+)\s+([^\s]+)(?:\s+\([^)]+\))?$").unwrap()); + /// Useful extensions to [`std::path::Path`]. pub trait FoundryPathExt { /// Returns true if the [`Path`] ends with `.t.sol` @@ -73,22 +84,23 @@ impl> FoundryPathExt for T { /// Initializes a tracing Subscriber for logging pub fn subscriber() { - let registry = tracing_subscriber::Registry::default() - .with(tracing_subscriber::EnvFilter::from_default_env()); + let registry = tracing_subscriber::Registry::default().with(env_filter()); #[cfg(feature = "tracy")] let registry = registry.with(tracing_tracy::TracyLayer::default()); - registry.with(tracing_subscriber::fmt::layer()).init() + registry.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)).init() } -pub fn abi_to_solidity(abi: &JsonAbi, name: &str) -> Result { - let s = abi.to_sol(name, None); - let s = forge_fmt::format(&s)?; - Ok(s) +fn env_filter() -> tracing_subscriber::EnvFilter { + const DEFAULT_DIRECTIVES: &[&str] = &include!("./default_directives.txt"); + let mut filter = tracing_subscriber::EnvFilter::from_default_env(); + for &directive in DEFAULT_DIRECTIVES { + filter = filter.add_directive(directive.parse().unwrap()); + } + filter } -/// Returns a [RetryProvider] instantiated using [Config]'s -/// RPC -pub fn get_provider(config: &Config) -> Result { +/// Returns a [`RootProvider`] instantiated using [Config]'s RPC settings. +pub fn get_provider(config: &Config) -> Result> { get_provider_builder(config)?.build() } @@ -96,31 +108,13 @@ pub fn get_provider(config: &Config) -> Result { /// /// Defaults to `http://localhost:8545` and `Mainnet`. pub fn get_provider_builder(config: &Config) -> Result { - let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = ProviderBuilder::new(url.as_ref()); - - if let Ok(chain) = config.chain.unwrap_or_default().try_into() { - builder = builder.chain(chain); - } - - if let Some(jwt) = config.get_rpc_jwt_secret()? { - builder = builder.jwt(jwt.as_ref()); - } - - if let Some(rpc_timeout) = config.eth_rpc_timeout { - builder = builder.timeout(Duration::from_secs(rpc_timeout)); - } - - if let Some(rpc_headers) = config.eth_rpc_headers.clone() { - builder = builder.headers(rpc_headers); - } - - Ok(builder) + ProviderBuilder::from_config(config) } -pub async fn get_chain

(chain: Option, provider: P) -> Result +pub async fn get_chain(chain: Option, provider: P) -> Result where - P: Provider, + N: Network, + P: Provider, { match chain { Some(chain) => Ok(chain), @@ -135,8 +129,8 @@ where /// If the string represents an untagged amount (e.g. "100") then /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { - Ok(if value.starts_with("0x") { - U256::from_str_radix(value, 16)? + Ok(if value.starts_with("0x") || value.starts_with("0X") { + U256::from_str(value)? } else { alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? .as_uint() @@ -172,10 +166,12 @@ pub fn now() -> Duration { SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards") } -/// Runs the `future` in a new [`tokio::runtime::Runtime`] -pub fn block_on(future: F) -> F::Output { - let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt"); - rt.block_on(future) +/// Common setup for all CLI tools. Does not include [tracing subscriber](subscriber). +pub fn common_setup() { + install_crypto_provider(); + crate::handler::install(); + load_dotenv(); + enable_paint(); } /// Loads a dotenv file, from the cwd and the project root, ignoring potential failure. @@ -225,6 +221,20 @@ pub fn install_crypto_provider() { .expect("Failed to install default rustls crypto provider"); } +/// Fetches the ABI of a contract from Etherscan. +pub async fn fetch_abi_from_etherscan( + address: Address, + config: &foundry_config::Config, +) -> Result> { + let chain = config.chain.unwrap_or_default(); + let client = config + .get_etherscan_config_with_chain(Some(chain))? + .ok_or_else(|| eyre::eyre!("No Etherscan API key configured for chain {chain}"))? + .into_client_with_no_proxy(config.eth_rpc_no_proxy)?; + let source = client.contract_source_code(address).await?; + source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect() +} + /// Useful extensions to [`std::process::Command`]. pub trait CommandUtils { /// Returns the command's output if execution is successful, otherwise, throws an error. @@ -274,7 +284,7 @@ impl CommandUtils for Command { }; if !msg.is_empty() { err.push(':'); - err.push(if msg.lines().count() == 0 { ' ' } else { '\n' }); + err.push(if msg.lines().count() == 1 { ' ' } else { '\n' }); err.push_str(&msg); } Err(eyre::eyre!(err)) @@ -297,12 +307,10 @@ pub struct Git<'a> { } impl<'a> Git<'a> { - #[inline] pub fn new(root: &'a Path) -> Self { Self { root, quiet: shell::is_quiet(), shallow: false } } - #[inline] pub fn from_config(config: &'a Config) -> Self { Self::new(config.root.as_path()) } @@ -367,19 +375,16 @@ impl<'a> Git<'a> { .map(drop) } - #[inline] - pub fn root(self, root: &Path) -> Git<'_> { + pub const fn root(self, root: &Path) -> Git<'_> { Git { root, ..self } } - #[inline] - pub fn quiet(self, quiet: bool) -> Self { + pub const fn quiet(self, quiet: bool) -> Self { Self { quiet, ..self } } /// True to perform shallow clones - #[inline] - pub fn shallow(self, shallow: bool) -> Self { + pub const fn shallow(self, shallow: bool) -> Self { Self { shallow, ..self } } @@ -392,10 +397,26 @@ impl<'a> Git<'a> { .map(drop) } + /// Returns the current HEAD commit hash of the current branch. + pub fn head(self) -> Result { + self.cmd().args(["rev-parse", "HEAD"]).get_stdout_lossy() + } + + pub fn checkout_at(self, tag: impl AsRef, at: &Path) -> Result<()> { + self.cmd_at(at).arg("checkout").arg(tag).exec().map(drop) + } + pub fn init(self) -> Result<()> { self.cmd().arg("init").exec().map(drop) } + pub fn current_rev_branch(self, at: &Path) -> Result<(String, String)> { + let rev = self.cmd_at(at).args(["rev-parse", "HEAD"]).get_stdout_lossy()?; + let branch = + self.cmd_at(at).args(["rev-parse", "--abbrev-ref", "HEAD"]).get_stdout_lossy()?; + Ok((rev, branch)) + } + #[expect(clippy::should_implement_trait)] // this is not std::ops::Add clippy pub fn add(self, paths: I) -> Result<()> where @@ -457,6 +478,10 @@ impl<'a> Git<'a> { self.cmd().args(["rev-parse", "--is-inside-work-tree"]).status().map(|s| s.success()) } + pub fn is_repo_root(self) -> Result { + self.cmd().args(["rev-parse", "--show-cdup"]).get_stdout_lossy().map(|s| s.is_empty()) + } + pub fn is_clean(self) -> Result { self.cmd().args(["status", "--porcelain"]).exec().map(|out| out.stdout.is_empty()) } @@ -469,6 +494,26 @@ impl<'a> Git<'a> { .map(|stdout| !stdout.is_empty()) } + pub fn has_tag(self, tag: impl AsRef, at: &Path) -> Result { + self.cmd_at(at) + .args(["tag", "--list"]) + .arg(tag) + .get_stdout_lossy() + .map(|stdout| !stdout.is_empty()) + } + + pub fn has_rev(self, rev: impl AsRef, at: &Path) -> Result { + self.cmd_at(at) + .args(["cat-file", "-t"]) + .arg(rev) + .get_stdout_lossy() + .map(|stdout| &stdout == "commit") + } + + pub fn get_rev(self, tag_or_branch: impl AsRef, at: &Path) -> Result { + self.cmd_at(at).args(["rev-list", "-n", "1"]).arg(tag_or_branch).get_stdout_lossy() + } + pub fn ensure_clean(self) -> Result<()> { if self.is_clean()? { Ok(()) @@ -497,6 +542,63 @@ ignore them in the `.gitignore` file." self.cmd().arg("tag").get_stdout_lossy() } + /// Returns the tag the commit first appeared in. + /// + /// E.g Take rev = `abc1234`. This commit can be found in multiple releases (tags). + /// Consider releases: `v0.1.0`, `v0.2.0`, `v0.3.0` in chronological order, `rev` first appeared + /// in `v0.2.0`. + /// + /// Hence, `tag_for_commit("abc1234")` will return `v0.2.0`. + pub fn tag_for_commit(self, rev: &str, at: &Path) -> Result> { + self.cmd_at(at) + .args(["tag", "--contains"]) + .arg(rev) + .get_stdout_lossy() + .map(|stdout| stdout.lines().next().map(str::to_string)) + } + + /// Returns a list of tuples of submodule paths and their respective branches. + /// + /// This function reads the `.gitmodules` file and returns the paths of all submodules that have + /// a branch. The paths are relative to the Git::root_of(git.root) and not lib/ directory. + /// + /// `at` is the dir in which the `.gitmodules` file is located, this is the git root. + /// `lib` is name of the directory where the submodules are located. + pub fn read_submodules_with_branch( + self, + at: &Path, + lib: &OsStr, + ) -> Result> { + // Read the .gitmodules file + let gitmodules = foundry_common::fs::read_to_string(at.join(".gitmodules"))?; + + let paths = SUBMODULE_BRANCH_REGEX + .captures_iter(&gitmodules) + .map(|cap| { + let path_str = cap.get(1).unwrap().as_str(); + let path = PathBuf::from_str(path_str).unwrap(); + trace!(path = %path.display(), "unstripped path"); + + // Keep only the components that come after the lib directory. + // This needs to be done because the lockfile uses paths relative foundry project + // root whereas .gitmodules use paths relative to the git root which may not be the + // project root. e.g monorepo. + // Hence, if path is lib/solady, then `lib/solady` is kept. if path is + // packages/contract-bedrock/lib/solady, then `lib/solady` is kept. + let lib_pos = path.components().find_position(|c| c.as_os_str() == lib); + let path = path + .components() + .skip(lib_pos.map(|(i, _)| i).unwrap_or(0)) + .collect::(); + + let branch = cap.get(2).unwrap().as_str().to_string(); + (path, branch) + }) + .collect::>(); + + Ok(paths) + } + pub fn has_missing_dependencies(self, paths: I) -> Result where I: IntoIterator, @@ -509,7 +611,7 @@ ignore them in the `.gitignore` file." .map(|stdout| stdout.lines().any(|line| line.starts_with('-'))) } - /// Returns true if the given path has no submodules by checking `git submodule status` + /// Returns true if the given path has submodules by checking `git submodule status` pub fn has_submodules(self, paths: I) -> Result where I: IntoIterator, @@ -574,27 +676,84 @@ ignore them in the `.gitignore` file." .map(drop) } + /// If the status is prefix with `-`, the submodule is not initialized. + /// + /// Ref: + pub fn submodules_uninitialized(self) -> Result { + // keep behavior consistent with `has_missing_dependencies`, but avoid duplicating the + // "submodule status has '-' prefix" logic. + self.has_missing_dependencies(std::iter::empty::<&OsStr>()) + } + + /// Initializes the git submodules. pub fn submodule_init(self) -> Result<()> { self.cmd().stderr(self.stderr()).args(["submodule", "init"]).exec().map(drop) } + pub fn submodules(&self) -> Result { + self.cmd().args(["submodule", "status"]).get_stdout_lossy().map(|stdout| stdout.parse())? + } + pub fn submodule_sync(self) -> Result<()> { self.cmd().stderr(self.stderr()).args(["submodule", "sync"]).exec().map(drop) } - pub fn cmd(self) -> Command { + /// Get the URL of a submodule from git config + pub fn submodule_url(self, path: &Path) -> Result> { + self.cmd() + .args(["config", "--get", &format!("submodule.{}.url", path.to_slash_lossy())]) + .get_stdout_lossy() + .map(|url| Some(url.trim().to_string())) + } + + /// Returns the fetch URL of the given remote, or `None` if it doesn't exist. + pub fn remote_url(self, name: &str) -> Option { + self.cmd().args(["remote", "get-url", name]).get_stdout_lossy().ok() + } + + /// Sets the branch for a submodule. + pub fn set_submodule_branch(self, rel_path: &Path, branch: &str) -> Result<()> { + self.cmd().args(["submodule", "set-branch", "-b", branch]).arg(rel_path).exec().map(drop) + } + + /// Returns remote branch names as a newline-separated string. + pub fn remote_branches(self) -> Result { + self.cmd().args(["branch", "-r"]).get_stdout_lossy() + } + + /// Fetches a branch from origin and checks out a local tracking branch at the given path. + pub fn fetch_and_checkout_branch(self, at: &Path, branch: &str) -> Result<()> { + self.cmd_at(at).args(["fetch", "origin", branch]).exec().map_err(|e| { + eyre::eyre!( + "Could not fetch latest changes for branch {branch} in submodule at {}: {e}", + at.display() + ) + })?; + self.cmd_at(at) + .args(["checkout", "-B", branch, &format!("origin/{branch}")]) + .exec() + .map_err(|e| { + eyre::eyre!( + "Could not checkout and track origin/{branch} for submodule at {}: {e}", + at.display() + ) + })?; + Ok(()) + } + + fn cmd(self) -> Command { let mut cmd = Self::cmd_no_root(); cmd.current_dir(self.root); cmd } - pub fn cmd_at(self, path: &Path) -> Command { + fn cmd_at(self, path: &Path) -> Command { let mut cmd = Self::cmd_no_root(); cmd.current_dir(path); cmd } - pub fn cmd_no_root() -> Command { + fn cmd_no_root() -> Command { let mut cmd = Command::new("git"); cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); cmd @@ -602,14 +761,79 @@ ignore them in the `.gitignore` file." // don't set this in cmd() because it's not wanted for all commands fn stderr(self) -> Stdio { - if self.quiet { - Stdio::piped() - } else { - Stdio::inherit() - } + if self.quiet { Stdio::piped() } else { Stdio::inherit() } + } +} + +/// Deserialized `git submodule status lib/dep` output. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] +pub struct Submodule { + /// Current commit hash the submodule is checked out at. + rev: String, + /// Relative path to the submodule. + path: PathBuf, +} + +impl Submodule { + pub const fn new(rev: String, path: PathBuf) -> Self { + Self { rev, path } + } + + pub fn rev(&self) -> &str { + &self.rev + } + + pub const fn path(&self) -> &PathBuf { + &self.path + } +} + +impl FromStr for Submodule { + type Err = eyre::Report; + + fn from_str(s: &str) -> Result { + let caps = SUBMODULE_STATUS_REGEX + .captures(s) + .ok_or_else(|| eyre::eyre!("Invalid submodule status format"))?; + + Ok(Self { + rev: caps.get(1).unwrap().as_str().to_string(), + path: PathBuf::from(caps.get(2).unwrap().as_str()), + }) + } +} + +/// Deserialized `git submodule status` output. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Submodules(pub Vec); + +impl Submodules { + pub const fn len(&self) -> usize { + self.0.len() + } + + pub const fn is_empty(&self) -> bool { + self.0.is_empty() } } +impl FromStr for Submodules { + type Err = eyre::Report; + + fn from_str(s: &str) -> Result { + let subs = s.lines().map(str::parse).collect::>>()?; + Ok(Self(subs)) + } +} + +impl<'a> IntoIterator for &'a Submodules { + type Item = &'a Submodule; + type IntoIter = std::slice::Iter<'a, Submodule>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} #[cfg(test)] mod tests { use super::*; @@ -617,6 +841,37 @@ mod tests { use std::{env, fs::File, io::Write}; use tempfile::tempdir; + #[test] + fn parse_submodule_status() { + let s = "+8829465a08cac423dcf59852f21e448449c1a1a8 lib/openzeppelin-contracts (v4.8.0-791-g8829465a)"; + let sub = Submodule::from_str(s).unwrap(); + assert_eq!(sub.rev(), "8829465a08cac423dcf59852f21e448449c1a1a8"); + assert_eq!(sub.path(), Path::new("lib/openzeppelin-contracts")); + + let s = "-8829465a08cac423dcf59852f21e448449c1a1a8 lib/openzeppelin-contracts"; + let sub = Submodule::from_str(s).unwrap(); + assert_eq!(sub.rev(), "8829465a08cac423dcf59852f21e448449c1a1a8"); + assert_eq!(sub.path(), Path::new("lib/openzeppelin-contracts")); + + let s = "8829465a08cac423dcf59852f21e448449c1a1a8 lib/openzeppelin-contracts"; + let sub = Submodule::from_str(s).unwrap(); + assert_eq!(sub.rev(), "8829465a08cac423dcf59852f21e448449c1a1a8"); + assert_eq!(sub.path(), Path::new("lib/openzeppelin-contracts")); + } + + #[test] + fn parse_multiline_submodule_status() { + let s = r#"+d3db4ef90a72b7d24aa5a2e5c649593eaef7801d lib/forge-std (v1.9.4-6-gd3db4ef) ++8829465a08cac423dcf59852f21e448449c1a1a8 lib/openzeppelin-contracts (v4.8.0-791-g8829465a) +"#; + let subs = Submodules::from_str(s).unwrap().0; + assert_eq!(subs.len(), 2); + assert_eq!(subs[0].rev(), "d3db4ef90a72b7d24aa5a2e5c649593eaef7801d"); + assert_eq!(subs[0].path(), Path::new("lib/forge-std")); + assert_eq!(subs[1].rev(), "8829465a08cac423dcf59852f21e448449c1a1a8"); + assert_eq!(subs[1].path(), Path::new("lib/openzeppelin-contracts")); + } + #[test] fn foundry_path_ext_works() { let p = Path::new("contracts/MyTest.t.sol"); @@ -626,6 +881,16 @@ mod tests { assert!(!p.is_sol_test()); } + #[test] + fn parse_ether_value_accepts_hex_prefixed_wei() { + assert_eq!(parse_ether_value("0x10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0X10").unwrap(), U256::from(16)); + assert_eq!(parse_ether_value("0x12").unwrap(), U256::from(0x12)); + assert_eq!(parse_ether_value("0xff").unwrap(), U256::from(0xff)); + assert_eq!(parse_ether_value("100").unwrap(), U256::from(100)); + assert_eq!(parse_ether_value("1ether").unwrap(), U256::from(1000000000000000000u128)); + } + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { @@ -639,10 +904,10 @@ mod tests { let mut cwd_file = File::create(cwd_env).unwrap(); let mut prj_file = File::create(nested.join(".env")).unwrap(); - cwd_file.write_all("TESTCWDKEY=cwd_val".as_bytes()).unwrap(); + cwd_file.write_all(b"TESTCWDKEY=cwd_val").unwrap(); cwd_file.sync_all().unwrap(); - prj_file.write_all("TESTPRJKEY=prj_val".as_bytes()).unwrap(); + prj_file.write_all(b"TESTPRJKEY=prj_val").unwrap(); prj_file.sync_all().unwrap(); let cwd = env::current_dir().unwrap(); @@ -653,4 +918,89 @@ mod tests { assert_eq!(env::var("TESTCWDKEY").unwrap(), "cwd_val"); assert_eq!(env::var("TESTPRJKEY").unwrap(), "prj_val"); } + + #[test] + fn test_read_gitmodules_regex() { + let gitmodules = r#" + [submodule "lib/solady"] + path = lib/solady + url = "" + branch = v0.1.0 + [submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = "" + branch = v4.8.0-791-g8829465a + [submodule "lib/forge-std"] + path = lib/forge-std + url = "" +"#; + + let paths = SUBMODULE_BRANCH_REGEX + .captures_iter(gitmodules) + .map(|cap| { + ( + PathBuf::from_str(cap.get(1).unwrap().as_str()).unwrap(), + String::from(cap.get(2).unwrap().as_str()), + ) + }) + .collect::>(); + + assert_eq!(paths.get(Path::new("lib/solady")).unwrap(), "v0.1.0"); + assert_eq!( + paths.get(Path::new("lib/openzeppelin-contracts")).unwrap(), + "v4.8.0-791-g8829465a" + ); + + let no_branch_gitmodules = r#" + [submodule "lib/solady"] + path = lib/solady + url = "" + [submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = "" + [submodule "lib/forge-std"] + path = lib/forge-std + url = "" +"#; + let paths = SUBMODULE_BRANCH_REGEX + .captures_iter(no_branch_gitmodules) + .map(|cap| { + ( + PathBuf::from_str(cap.get(1).unwrap().as_str()).unwrap(), + String::from(cap.get(2).unwrap().as_str()), + ) + }) + .collect::>(); + + assert!(paths.is_empty()); + + let branch_in_between = r#" + [submodule "lib/solady"] + path = lib/solady + url = "" + [submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = "" + branch = v4.8.0-791-g8829465a + [submodule "lib/forge-std"] + path = lib/forge-std + url = "" + "#; + + let paths = SUBMODULE_BRANCH_REGEX + .captures_iter(branch_in_between) + .map(|cap| { + ( + PathBuf::from_str(cap.get(1).unwrap().as_str()).unwrap(), + String::from(cap.get(2).unwrap().as_str()), + ) + }) + .collect::>(); + + assert_eq!(paths.len(), 1); + assert_eq!( + paths.get(Path::new("lib/openzeppelin-contracts")).unwrap(), + "v4.8.0-791-g8829465a" + ); + } } diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. diff --git a/crates/cli/src/utils/tempo.rs b/crates/cli/src/utils/tempo.rs new file mode 100644 index 0000000000000..4b5715b9ebe08 --- /dev/null +++ b/crates/cli/src/utils/tempo.rs @@ -0,0 +1,205 @@ +//! Tempo utilities: fee token parsing and named nonce lanes (2D nonces). +//! +//! A "lane" is a friendly alias for a Tempo `nonce_key` (a [`U256`]). Lanes are defined in a +//! shared TOML file (default `tempo.lanes.toml` at the project root) so a team can reserve +//! independent sequential nonce streams for parallel scripts without coordinating on raw +//! `U256` selectors. +//! +//! Example `tempo.lanes.toml`: +//! +//! ```toml +//! deploy = 1 +//! ops = 2 +//! payments = 3 +//! ``` +//! +//! ```bash +//! cast erc20 transfer ... --tempo.lane payments +//! ``` + +use crate::opts::TempoOpts; +use alloy_primitives::{Address, U256}; +use eyre::{Result, eyre}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, +}; +use tempo_primitives::TempoAddressExt; + +/// Default name of the lanes file at the project root. +pub const DEFAULT_LANES_FILE: &str = "tempo.lanes.toml"; + +/// Result of resolving a `--tempo.lane ` argument against a lanes file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResolvedLane { + /// The lane name as provided on the CLI. + pub name: String, + /// The `nonce_key` the lane resolved to. + pub nonce_key: U256, +} + +/// Parses a fee token address. +pub fn parse_fee_token_address(address_or_id: &str) -> eyre::Result

{ + Address::from_str(address_or_id).or_else(|_| Ok(token_id_to_address(address_or_id.parse()?))) +} + +fn token_id_to_address(token_id: u64) -> Address { + let mut address_bytes = [0u8; 20]; + address_bytes[..12].copy_from_slice(&Address::TIP20_PREFIX); + address_bytes[12..20].copy_from_slice(&token_id.to_be_bytes()); + Address::from(address_bytes) +} + +/// Loads a TOML lanes file from `path`. +/// +/// Each top-level key is a lane name, and the value is the `nonce_key` (an integer or a +/// decimal/hex string parsed as [`U256`]). +pub fn load_lanes(path: &Path) -> Result> { + let contents = std::fs::read_to_string(path) + .map_err(|e| eyre!("failed to read tempo lanes file {}: {}", path.display(), e))?; + parse_lanes(&contents) + .map_err(|e| eyre!("failed to parse tempo lanes file {}: {}", path.display(), e)) +} + +fn parse_lanes(contents: &str) -> Result> { + let raw: BTreeMap = toml::from_str(contents)?; + let mut out = BTreeMap::new(); + for (name, value) in raw { + let nonce_key = match value { + toml::Value::Integer(n) => { + if n < 0 { + return Err(eyre!("invalid nonce_key for lane '{name}': must be non-negative")); + } + U256::from(n as u64) + } + toml::Value::String(s) => U256::from_str(s.trim()) + .map_err(|e| eyre!("invalid nonce_key for lane '{name}': {e}"))?, + other => { + return Err(eyre!( + "invalid nonce_key for lane '{name}': expected integer or string, got {}", + other.type_str(), + )); + } + }; + out.insert(name, nonce_key); + } + Ok(out) +} + +/// Resolves `opts.lane` against a lanes file and writes the resulting `nonce_key` to +/// `opts.nonce_key`. Returns the resolved lane (or `None` if no `--tempo.lane` was set). +/// +/// `root` is the project root used to locate the default lanes file +/// (`/tempo.lanes.toml`) when `--tempo.lanes-file` was not provided. +pub fn resolve_lane(opts: &mut TempoOpts, root: &Path) -> Result> { + let Some(lane_name) = opts.lane.clone() else { return Ok(None) }; + + let path: PathBuf = opts.lanes_file.clone().unwrap_or_else(|| root.join(DEFAULT_LANES_FILE)); + + if !path.exists() { + return Err(eyre!( + "tempo lanes file not found at {}\n\ + create it with `name = ` entries, e.g.:\n \ + deploy = 1\n \ + ops = 2\n \ + payments = 3", + path.display(), + )); + } + + let lanes = load_lanes(&path)?; + + let nonce_key = lanes.get(&lane_name).copied().ok_or_else(|| { + let mut known: Vec<&str> = lanes.keys().map(String::as_str).collect(); + known.sort_unstable(); + eyre!( + "lane '{lane_name}' not found in {} (known lanes: {})", + path.display(), + if known.is_empty() { "".to_string() } else { known.join(", ") }, + ) + })?; + + opts.nonce_key = Some(nonce_key); + Ok(Some(ResolvedLane { name: lane_name, nonce_key })) +} + +/// Prints `lane: (nonce_key=, nonce=)` to stderr (so it doesn't pollute +/// stdout for commands like `cast mktx` whose stdout is meant to be piped), giving +/// visibility into which 2D nonce lane was used. +pub fn maybe_print_resolved_lane(resolved: Option<&ResolvedLane>, nonce: u64) -> Result<()> { + if let Some(lane) = resolved { + sh_eprintln!("lane: {} (nonce_key={}, nonce={})", lane.name, lane.nonce_key, nonce)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_int_and_string_lane_values() { + let toml = r#" +deploy = 1 +ops = 2 +payments = "3" +big = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +"#; + let lanes = parse_lanes(toml).unwrap(); + assert_eq!(lanes.get("deploy"), Some(&U256::from(1u64))); + assert_eq!(lanes.get("ops"), Some(&U256::from(2u64))); + assert_eq!(lanes.get("payments"), Some(&U256::from(3u64))); + assert_eq!(lanes.get("big"), Some(&U256::MAX)); + } + + #[test] + fn parse_lanes_rejects_invalid_string() { + let toml = "broken = \"not-a-number\""; + let err = parse_lanes(toml).unwrap_err(); + assert!(err.to_string().contains("invalid nonce_key for lane 'broken'")); + } + + #[test] + fn resolve_lane_sets_nonce_key_and_returns_resolved() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 7\npayments = 42\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let resolved = resolve_lane(&mut opts, dir.path()).unwrap().unwrap(); + assert_eq!(resolved.name, "payments"); + assert_eq!(resolved.nonce_key, U256::from(42u64)); + assert_eq!(opts.nonce_key, Some(U256::from(42u64))); + } + + #[test] + fn resolve_lane_returns_none_when_no_lane() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts::default(); + let resolved = resolve_lane(&mut opts, dir.path()).unwrap(); + assert!(resolved.is_none()); + assert!(opts.nonce_key.is_none()); + } + + #[test] + fn resolve_lane_errors_when_file_missing() { + let dir = tempfile::tempdir().unwrap(); + let mut opts = TempoOpts { lane: Some("deploy".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + assert!(err.to_string().contains("tempo lanes file not found")); + } + + #[test] + fn resolve_lane_errors_when_lane_unknown() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join(DEFAULT_LANES_FILE); + std::fs::write(&path, "deploy = 1\nops = 2\n").unwrap(); + + let mut opts = TempoOpts { lane: Some("payments".to_string()), ..Default::default() }; + let err = resolve_lane(&mut opts, dir.path()).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("lane 'payments' not found")); + assert!(msg.contains("deploy, ops")); + } +} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ecfc9279ac39a..6921faabcb102 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,7 +17,9 @@ foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-primitives.workspace = true +alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-eips.workspace = true alloy-json-abi.workspace = true @@ -29,40 +31,46 @@ alloy-primitives = { workspace = true, features = [ "rlp", ] } alloy-provider.workspace = true +alloy-signer.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } -alloy-serde.workspace = true +alloy-rpc-types-engine = { workspace = true, features = ["jwt-aws-lc-rs"] } alloy-sol-types.workspace = true -alloy-transport-http = { workspace = true, features = [ - "reqwest", - "reqwest-rustls-tls", -] } alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true +rustls.workspace = true alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true -solar-parse.workspace = true -solar-sema.workspace = true +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } + +revm.workspace = true + +solar.workspace = true tower.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +alloy-rlp.workspace = true comfy-table.workspace = true +dirs.workspace = true dunce.workspace = true eyre.workspace = true itertools.workspace = true jiff.workspace = true num-format.workspace = true path-slash.workspace = true +regex.workspace = true reqwest.workspace = true semver.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true tokio.workspace = true +toml.workspace = true tracing.workspace = true url.workspace = true walkdir.workspace = true @@ -70,13 +78,34 @@ yansi.workspace = true anstream.workspace = true anstyle.workspace = true -terminal_size.workspace = true ciborium.workspace = true +flate2.workspace = true +tempo-alloy.workspace = true +tempo-primitives.workspace = true +mpp.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } +tokio-tungstenite.workspace = true +futures.workspace = true +alloy-signer-local.workspace = true +base64.workspace = true +sha2 = "0.10" +tempfile.workspace = true + [build-dependencies] chrono.workspace = true -vergen = { workspace = true, features = ["build", "git", "gitcl"] } +vergen = { workspace = true, features = ["build", "emit_and_set"] } [dev-dependencies] +foundry-evm-hardforks.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } axum = { workspace = true } +k256 = { workspace = true } + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "foundry-common-fmt/optimism", +] diff --git a/crates/common/build.rs b/crates/common/build.rs index 7b5c0736435cd..9afa01b5757ef 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -1,53 +1,32 @@ -use std::{env, error::Error}; +#![expect(clippy::disallowed_macros)] use chrono::DateTime; -use vergen::EmitBuilder; +use std::{error::Error, path::PathBuf}; -#[expect(clippy::disallowed_macros)] fn main() -> Result<(), Box> { - // Re-run the build script if the build script itself changes or if the - // environment variables change. println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-env-changed=TAG_NAME"); - println!("cargo:rerun-if-env-changed=PROFILE"); - - EmitBuilder::builder() - .build_date() - .build_timestamp() - .git_describe(false, true, None) - .git_sha(false) - .emit_and_set()?; - - // Set the Git SHA of the latest commit. - let sha = env::var("VERGEN_GIT_SHA")?; + + let build = vergen::Build::builder().build_date(true).build_timestamp(true).build(); + let git = vergen::Gitcl::builder().describe(false, true, None).sha(false).build(); + + vergen::Emitter::new().add_instructions(&build)?.add_instructions(&git)?.emit_and_set()?; + + let sha = env_var("VERGEN_GIT_SHA"); let sha_short = &sha[..10]; - // Set the version suffix and whether the version is a nightly build. - // if not on a tag: 0.3.0-dev+ba03de0019.1737036656.debug - // if on a tag: 0.3.0-stable+ba03de0019.1737036656.release - let tag_name = env::var("TAG_NAME") - .or_else(|_| env::var("CARGO_TAG_NAME")) - .unwrap_or_else(|_| String::from("dev")); - let (is_nightly, version_suffix) = if tag_name.contains("nightly") { - (true, "-nightly".to_string()) - } else { - (false, format!("-{tag_name}")) - }; - - // Whether the version is a nightly build. + let tag_name = try_env_var("TAG_NAME").unwrap_or_else(|| String::from("dev")); + let version = release_version(&env_var("CARGO_PKG_VERSION"), &tag_name); + let is_nightly = tag_name.starts_with("nightly"); + if is_nightly { println!("cargo:rustc-env=FOUNDRY_IS_NIGHTLY_VERSION=true"); } - // Set formatted version strings - let pkg_version = env::var("CARGO_PKG_VERSION")?; - - // Append the profile to the version string - let out_dir = env::var("OUT_DIR").unwrap(); - let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); + // `PROFILE` captures only release or debug. Get the actual name from the out directory. + let out_dir = PathBuf::from(env_var("OUT_DIR")); + let profile = out_dir.components().rev().nth(3).unwrap().as_os_str().to_str().unwrap(); - // Set the build timestamp. - let build_timestamp = env::var("VERGEN_BUILD_TIMESTAMP")?; + let build_timestamp = env_var("VERGEN_BUILD_TIMESTAMP"); let build_timestamp_unix = DateTime::parse_from_rfc3339(&build_timestamp)?.timestamp(); // The SemVer compatible version information for Foundry. @@ -57,14 +36,14 @@ fn main() -> Result<(), Box> { // - The build profile. // Example: forge 0.3.0-nightly+3cb96bde9b.1737036656.debug println!( - "cargo:rustc-env=FOUNDRY_SEMVER_VERSION={pkg_version}{version_suffix}+{sha_short}.{build_timestamp_unix}.{profile}" - ); + "cargo:rustc-env=FOUNDRY_SEMVER_VERSION={version}+{sha_short}.{build_timestamp_unix}.{profile}" + ); // The short version information for the Foundry CLI. // - The latest version from Cargo.toml // - The short SHA of the latest commit. // Example: 0.3.0-dev (3cb96bde9b) - println!("cargo:rustc-env=FOUNDRY_SHORT_VERSION={pkg_version}{version_suffix} ({sha_short} {build_timestamp})"); + println!("cargo:rustc-env=FOUNDRY_SHORT_VERSION={version} ({sha_short} {build_timestamp})"); // The long version information for the Foundry CLI. // - The latest version from Cargo.toml. @@ -81,10 +60,44 @@ fn main() -> Result<(), Box> { // Build Timestamp: 2025-01-16T15:04:03.522021223Z (1737039843) // Build Profile: debug // ``` - println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); - println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_1=Commit SHA: {sha}"); - println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_2=Build Timestamp: {build_timestamp} ({build_timestamp_unix})"); - println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_3=Build Profile: {profile}"); + let long_version = format!( + "\ +Version: {version} +Commit SHA: {sha} +Build Timestamp: {build_timestamp} ({build_timestamp_unix}) +Build Profile: {profile}" + ); + assert_eq!(long_version.lines().count(), 4); + for (i, line) in long_version.lines().enumerate() { + println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_{i}={line}"); + } + + // The long SHA of the latest commit. + // + // Example: 5186142d3bb4d1be7bb4ade548b77c8e2270717e + println!("cargo:rustc-env=FOUNDRY_COMMIT_SHA={sha}"); Ok(()) } + +fn env_var(name: &str) -> String { + try_env_var(name).unwrap() +} + +fn release_version(pkg_version: &str, tag_name: &str) -> String { + if let Some(version) = tag_name.strip_prefix('v') { + return version.to_owned(); + } + + // Normalize `nightly-` to `nightly` so tarball and Docker nightly + // artifacts produce the same version string. The commit identifier is + // already included in the SemVer build metadata (after `+`). + let normalized = if tag_name.starts_with("nightly-") { "nightly" } else { tag_name }; + + format!("{pkg_version}-{normalized}") +} + +fn try_env_var(name: &str) -> Option { + println!("cargo:rerun-if-env-changed={name}"); + std::env::var(name).ok() +} diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 2c9c5223df30f..179c71048da5b 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -16,18 +16,29 @@ workspace = true [dependencies] alloy-primitives.workspace = true alloy-dyn-abi = { workspace = true, features = ["eip712"] } +eyre.workspace = true # ui alloy-consensus.workspace = true +op-alloy-consensus = { workspace = true, optional = true } alloy-network.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } +op-alloy-rpc-types = { workspace = true, optional = true } alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true chrono.workspace = true revm.workspace = true yansi.workspace = true +comfy-table.workspace = true + +# Tempo +tempo-alloy.workspace = true [dev-dependencies] foundry-macros.workspace = true similar-asserts.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:op-alloy-consensus", "dep:op-alloy-rpc-types"] diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs index a8c4fe4ae5a7e..8b66335445046 100644 --- a/crates/common/fmt/src/console.rs +++ b/crates/common/fmt/src/console.rs @@ -1,5 +1,6 @@ use super::UIfmt; use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use comfy_table::{Table, TableComponent, presets::UTF8_FULL}; use std::fmt::{self, Write}; /// A piece is a portion of the format string which represents the next part to emit. @@ -184,10 +185,10 @@ impl ConsoleFmt for String { match spec { FormatSpec::String => self.clone(), FormatSpec::Object => format!("'{}'", self.clone()), - FormatSpec::Number | - FormatSpec::Integer | - FormatSpec::Exponential(_) | - FormatSpec::Hexadecimal => Self::from("NaN"), + FormatSpec::Number + | FormatSpec::Integer + | FormatSpec::Exponential(_) + | FormatSpec::Hexadecimal => Self::from("NaN"), } } } @@ -222,10 +223,10 @@ impl ConsoleFmt for U256 { let integer = amount / exp10; let decimal = (amount % exp10).to_string(); let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{integer}.{decimal}e{log}") - } else { + if decimal.is_empty() { format!("{integer}e{log}") + } else { + format!("{integer}.{decimal}e{log}") } } FormatSpec::Exponential(Some(precision)) => { @@ -234,10 +235,10 @@ impl ConsoleFmt for U256 { let integer = amount / exp10; let decimal = (amount % exp10).to_string(); let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{integer}.{decimal}") - } else { + if decimal.is_empty() { format!("{integer}") + } else { + format!("{integer}.{decimal}") } } } @@ -266,10 +267,10 @@ impl ConsoleFmt for I256 { let integer = (amount / exp10).twos_complement(); let decimal = (amount % exp10).twos_complement().to_string(); let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{sign}{integer}.{decimal}e{log}") - } else { + if decimal.is_empty() { format!("{sign}{integer}e{log}") + } else { + format!("{sign}{integer}.{decimal}e{log}") } } FormatSpec::Exponential(Some(precision)) => { @@ -279,10 +280,10 @@ impl ConsoleFmt for I256 { let integer = (amount / exp10).twos_complement(); let decimal = (amount % exp10).twos_complement().to_string(); let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{sign}{integer}.{decimal}") - } else { + if decimal.is_empty() { format!("{sign}{integer}") + } else { + format!("{sign}{integer}.{decimal}") } } } @@ -407,10 +408,40 @@ fn format_spec<'a>( } } +pub fn console_table_format( + keys: Option<&[&dyn ConsoleFmt]>, + values: &[&dyn ConsoleFmt], +) -> String { + let keys_strings: Vec = match keys { + Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(), + None => (0..values.len()).map(|i| i.to_string()).collect(), + }; + let values_strings: Vec = values.iter().map(|v| v.fmt(FormatSpec::String)).collect(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL); + table.set_style(TableComponent::VerticalLines, '│'); + table.set_style(TableComponent::HeaderLines, '─'); + table.set_style(TableComponent::MiddleHeaderIntersections, '┼'); + table.set_style(TableComponent::LeftHeaderIntersection, '├'); + table.set_style(TableComponent::RightHeaderIntersection, '┤'); + table.set_header(vec!["(index)", "Values"]); + table.remove_style(TableComponent::HorizontalLines); + table.remove_style(TableComponent::MiddleIntersections); + table.remove_style(TableComponent::LeftBorderIntersections); + table.remove_style(TableComponent::RightBorderIntersections); + for i in 0..keys_strings.len().max(values_strings.len()) { + let key = keys_strings.get(i).map(String::as_str).unwrap_or(""); + let value = values_strings.get(i).map(String::as_str).unwrap_or(""); + table.add_row(vec![key, value]); + } + table.to_string() +} + #[cfg(test)] mod tests { use super::*; - use alloy_primitives::{address, B256}; + use alloy_primitives::{B256, address}; use foundry_macros::ConsoleFmt; use std::str::FromStr; @@ -610,4 +641,80 @@ mod tests { let call = Logs::Log1(log1); assert_eq!(call.fmt(Default::default()), "foo 42 bar"); } + + #[test] + fn test_console_table_format() { + // auto-indexed, uint256 values + let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)]; + assert_eq!( + console_table_format(None, values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ 0 │ 100 │\n\ + │ 1 │ 200 │\n\ + │ 2 │ 300 │\n\ + └─────────┴────────┘" + ); + + // string keys, uint256 values + // key col expands to fit "charlie123" and value col expands to fit "20000000000000000" + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")]; + let values: &[&dyn ConsoleFmt] = &[ + &U256::from(1), + &U256::from_str("20000000000000000").unwrap(), + &U256::from_str("30000000000").unwrap(), + ]; + assert_eq!( + console_table_format(Some(keys), values), + "┌────────────┬───────────────────┐\n\ + │ (index) │ Values │\n\ + ├────────────┼───────────────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 20000000000000000 │\n\ + │ charlie123 │ 30000000000 │\n\ + └────────────┴───────────────────┘" + ); + + // empty table + assert_eq!( + console_table_format(None, &[]), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + └─────────┴────────┘" + ); + + // more keys than values + let keys: &[&dyn ConsoleFmt] = + &[&String::from("alice"), &String::from("bob"), &String::from("charlie")]; + let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ charlie │ │\n\ + └─────────┴────────┘" + ); + + // more values than keys + let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")]; + let values: &[&dyn ConsoleFmt] = + &[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)]; + assert_eq!( + console_table_format(Some(keys), values), + "┌─────────┬────────┐\n\ + │ (index) │ Values │\n\ + ├─────────┼────────┤\n\ + │ alice │ 1 │\n\ + │ bob │ 2 │\n\ + │ │ 3 │\n\ + │ │ 4 │\n\ + └─────────┴────────┘" + ); + } } diff --git a/crates/common/fmt/src/dynamic.rs b/crates/common/fmt/src/dynamic.rs index 266e53f0ee24e..1882d7b3d49b0 100644 --- a/crates/common/fmt/src/dynamic.rs +++ b/crates/common/fmt/src/dynamic.rs @@ -1,7 +1,12 @@ use super::{format_int_exp, format_uint_exp}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::hex; -use std::fmt; +use eyre::Result; +use serde_json::{Map, Value}; +use std::{ + collections::{BTreeMap, HashMap}, + fmt, +}; /// [`DynSolValue`] formatter. struct DynValueFormatter { @@ -102,14 +107,12 @@ struct DynValueDisplay<'a> { impl<'a> DynValueDisplay<'a> { /// Creates a new [`Display`](fmt::Display) wrapper for the given value. - #[inline] - fn new(value: &'a DynSolValue, raw: bool) -> Self { + const fn new(value: &'a DynSolValue, raw: bool) -> Self { Self { value, formatter: DynValueFormatter { raw } } } } impl fmt::Display for DynValueDisplay<'_> { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.formatter.value(self.value, f) } @@ -146,10 +149,143 @@ pub fn format_token_raw(value: &DynSolValue) -> String { DynValueDisplay::new(value, true).to_string() } +/// Serializes given [DynSolValue] into a [serde_json::Value]. +pub fn serialize_value_as_json( + value: DynSolValue, + defs: Option<&StructDefinitions>, +) -> Result { + if let Some(defs) = defs { + _serialize_value_as_json(value, defs) + } else { + _serialize_value_as_json(value, &StructDefinitions::default()) + } +} + +fn _serialize_value_as_json(value: DynSolValue, defs: &StructDefinitions) -> Result { + match value { + DynSolValue::Bool(b) => Ok(Value::Bool(b)), + DynSolValue::String(s) => { + // Strings are allowed to contain stringified JSON objects, so we try to parse it like + // one first. + if let Ok(map) = serde_json::from_str(&s) { + Ok(Value::Object(map)) + } else { + Ok(Value::String(s)) + } + } + DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))), + DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))), + DynSolValue::Int(i, _) => { + if let Ok(n) = i64::try_from(i) { + // Use `serde_json::Number` if the number can be accurately represented. + Ok(Value::Number(n.into())) + } else { + // Otherwise, fallback to its string representation to preserve precision and ensure + // compatibility with alloy's `DynSolType` coercion. + Ok(Value::String(i.to_string())) + } + } + DynSolValue::Uint(i, _) => { + if let Ok(n) = u64::try_from(i) { + // Use `serde_json::Number` if the number can be accurately represented. + Ok(Value::Number(n.into())) + } else { + // Otherwise, fallback to its string representation to preserve precision and ensure + // compatibility with alloy's `DynSolType` coercion. + Ok(Value::String(i.to_string())) + } + } + DynSolValue::Address(a) => Ok(Value::String(a.to_string())), + DynSolValue::Array(e) | DynSolValue::FixedArray(e) => Ok(Value::Array( + e.into_iter().map(|v| _serialize_value_as_json(v, defs)).collect::>()?, + )), + DynSolValue::CustomStruct { name, prop_names, tuple } => { + let values = tuple + .into_iter() + .map(|v| _serialize_value_as_json(v, defs)) + .collect::>>()?; + let mut map: HashMap = prop_names.into_iter().zip(values).collect(); + + // If the struct def is known, manually build a `Map` to preserve the order. + if let Some(fields) = defs.get(&name)? { + let mut ordered_map = Map::with_capacity(fields.len()); + for (field_name, _) in fields { + if let Some(serialized_value) = map.remove(field_name) { + ordered_map.insert(field_name.clone(), serialized_value); + } + } + // Explicitly return a `Value::Object` to avoid ambiguity. + return Ok(Value::Object(ordered_map)); + } + + // Otherwise, fall back to alphabetical sorting for deterministic output. + Ok(Value::Object(map.into_iter().collect::>())) + } + DynSolValue::Tuple(values) => Ok(Value::Array( + values.into_iter().map(|v| _serialize_value_as_json(v, defs)).collect::>()?, + )), + DynSolValue::Function(_) => eyre::bail!("cannot serialize function pointer"), + } +} + +// -- STRUCT DEFINITIONS ------------------------------------------------------- + +pub type TypeDefMap = BTreeMap>; + +#[derive(Debug, Clone, Default)] +pub struct StructDefinitions(TypeDefMap); + +impl From for StructDefinitions { + fn from(map: TypeDefMap) -> Self { + Self::new(map) + } +} + +impl StructDefinitions { + pub const fn new(map: TypeDefMap) -> Self { + Self(map) + } + + pub fn keys(&self) -> impl Iterator { + self.0.keys() + } + + pub fn values(&self) -> impl Iterator { + self.0.values().map(|v| v.as_slice()) + } + + pub fn get(&self, key: &str) -> eyre::Result> { + if let Some(value) = self.0.get(key) { + return Ok(Some(value)); + } + + let matches: Vec<&[(String, String)]> = self + .0 + .iter() + .filter_map(|(k, v)| { + if let Some((_, struct_name)) = k.split_once('.') + && struct_name == key + { + return Some(v.as_slice()); + } + None + }) + .collect(); + + match matches.len() { + 0 => Ok(None), + 1 => Ok(Some(matches[0])), + _ => eyre::bail!( + "there are several structs with the same name. Use `.{key}` instead." + ), + } + } +} + #[cfg(test)] mod tests { use super::*; - use alloy_primitives::{address, U256}; + use alloy_primitives::{U256, address}; #[test] fn parse_hex_uint() { diff --git a/crates/common/fmt/src/exp.rs b/crates/common/fmt/src/exp.rs index 84444615e6d09..73054f3805f59 100644 --- a/crates/common/fmt/src/exp.rs +++ b/crates/common/fmt/src/exp.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Sign, I256, U256}; +use alloy_primitives::{I256, Sign, U256}; use yansi::Paint; /// Returns the number expressed as a string in exponential notation @@ -15,7 +15,6 @@ use yansi::Paint; /// 1234124124 -> 1.23e9 /// 10000000 -> 1e7 /// ``` -#[inline] pub fn to_exp_notation(value: U256, precision: usize, trim_end_zeros: bool, sign: Sign) -> String { let stringified = value.to_string(); let exponent = stringified.len() - 1; @@ -55,7 +54,7 @@ pub fn to_exp_notation(value: U256, precision: usize, trim_end_zeros: bool, sign /// ``` pub fn format_uint_exp(num: U256) -> String { if num < U256::from(10_000) { - return num.to_string() + return num.to_string(); } let exp = to_exp_notation(num, 4, true, Sign::Positive); diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index fa1c4543d6463..45ed47263ce32 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -1,13 +1,21 @@ //! Helpers for formatting Ethereum types. +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + mod console; -pub use console::{console_format, ConsoleFmt, FormatSpec}; +pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format}; mod dynamic; -pub use dynamic::{format_token, format_token_raw, format_tokens, format_tokens_raw, parse_tokens}; +pub use dynamic::{ + StructDefinitions, TypeDefMap, format_token, format_token_raw, format_tokens, + format_tokens_raw, parse_tokens, serialize_value_as_json, +}; mod exp; pub use exp::{format_int_exp, format_uint_exp, to_exp_notation}; mod ui; -pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, EthValue, UIfmt}; +pub use ui::{ + EthValue, UIfmt, UIfmtHeaderExt, UIfmtReceiptExt, UIfmtSignatureExt, get_pretty_block_attr, + get_pretty_receipt_attr, get_pretty_tx_attr, +}; diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 89e18e5620183..e883810dcda34 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1,19 +1,33 @@ //! Helper trait and functions to format Ethereum types. +use std::num::NonZeroU64; + use alloy_consensus::{ - Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, Typed2718, + BlockHeader, Eip658Value, Signed, Transaction as TxTrait, TxEip1559, TxEip2930, + TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718, + transaction::TxHashRef, }; use alloy_network::{ - AnyHeader, AnyReceiptEnvelope, AnyRpcBlock, AnyRpcTransaction, AnyTransactionReceipt, - AnyTxEnvelope, ReceiptResponse, + AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope, + BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse, +}; +use alloy_primitives::{ + Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex, }; -use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, I256, U256, U64, U8}; use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; +use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; +use tempo_alloy::{ + primitives::{ + AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, + transaction::{Call, PrimitiveSignature}, + }, + rpc::{TempoHeaderResponse, TempoTransactionReceipt}, +}; /// length of the name column for pretty formatting `{:>20}{value}` const NAME_COLUMN_LEN: usize = 20usize; @@ -41,17 +55,15 @@ impl UIfmt for &T { impl UIfmt for Option { fn pretty(&self) -> String { - if let Some(ref inner) = self { - inner.pretty() - } else { - String::new() - } + if let Some(inner) = self { inner.pretty() } else { String::new() } } } impl UIfmt for [T] { fn pretty(&self) -> String { - if !self.is_empty() { + if self.is_empty() { + "[]".to_string() + } else { let mut s = String::with_capacity(self.len() * 64); s.push_str("[\n"); for item in self { @@ -63,15 +75,13 @@ impl UIfmt for [T] { } s.push(']'); s - } else { - "[]".to_string() } } } impl UIfmt for String { fn pretty(&self) -> String { - self.to_string() + self.clone() } } @@ -81,6 +91,12 @@ impl UIfmt for u64 { } } +impl UIfmt for NonZeroU64 { + fn pretty(&self) -> String { + self.get().pretty() + } +} + impl UIfmt for u128 { fn pretty(&self) -> String { self.to_string() @@ -117,12 +133,6 @@ impl UIfmt for Bloom { } } -impl UIfmt for TxType { - fn pretty(&self) -> String { - (*self as u8).to_string() - } -} - impl UIfmt for Vec { fn pretty(&self) -> String { self[..].pretty() @@ -162,37 +172,16 @@ impl UIfmt for Eip658Value { } } -impl UIfmt for AnyTransactionReceipt { +impl UIfmt for Signature { fn pretty(&self) -> String { - let Self { - inner: - TransactionReceipt { - transaction_hash, - transaction_index, - block_hash, - block_number, - from, - to, - gas_used, - contract_address, - effective_gas_price, - inner: - AnyReceiptEnvelope { - r#type: transaction_type, - inner: - ReceiptWithBloom { - receipt: Receipt { status, cumulative_gas_used, logs }, - logs_bloom, - }, - }, - blob_gas_price, - blob_gas_used, - }, - other, - } = self; + format!("[r: {}, s: {}, y_parity: {}]", self.r(), self.s(), self.v()) + } +} - let mut pretty = format!( - " +/// Pretty-prints the common fields of any `TransactionReceipt`. +fn pretty_receipt>(receipt: &TransactionReceipt, tx_type: u8) -> String { + let mut pretty = format!( + " blockHash {} blockNumber {} contractAddress {} @@ -209,31 +198,41 @@ transactionIndex {} type {} blobGasPrice {} blobGasUsed {}", - block_hash.pretty(), - block_number.pretty(), - contract_address.pretty(), - cumulative_gas_used.pretty(), - effective_gas_price.pretty(), - from.pretty(), - gas_used.pretty(), - serde_json::to_string(&logs).unwrap(), - logs_bloom.pretty(), - self.state_root().pretty(), - status.pretty(), - transaction_hash.pretty(), - transaction_index.pretty(), - transaction_type, - blob_gas_price.pretty(), - blob_gas_used.pretty() - ); + receipt.block_hash.pretty(), + receipt.block_number.pretty(), + receipt.contract_address.pretty(), + receipt.inner.cumulative_gas_used().pretty(), + receipt.effective_gas_price.pretty(), + receipt.from.pretty(), + receipt.gas_used.pretty(), + serde_json::to_string(receipt.inner.logs()).unwrap(), + receipt.inner.bloom().pretty(), + receipt.state_root().pretty(), + receipt.inner.status_or_post_state().pretty(), + receipt.transaction_hash.pretty(), + receipt.transaction_index.pretty(), + tx_type, + receipt.blob_gas_price.pretty(), + receipt.blob_gas_used.pretty() + ); + + if let Some(to) = receipt.to { + pretty.push_str(&format!("\nto {}", to.pretty())); + } - if let Some(to) = to { - pretty.push_str(&format!("\nto {}", to.pretty())); - } + pretty +} - // additional captured fields - pretty.push_str(&other.pretty()); +impl UIfmt for TransactionReceipt { + fn pretty(&self) -> String { + pretty_receipt(self, self.transaction_type() as u8) + } +} +impl UIfmt for AnyTransactionReceipt { + fn pretty(&self) -> String { + let mut pretty = pretty_receipt(&self.inner, self.inner.inner.r#type); + pretty.push_str(&self.other.pretty()); pretty } } @@ -264,13 +263,13 @@ transactionIndex: {}", } } -impl UIfmt for Block> { +impl UIfmt for Block { fn pretty(&self) -> String { format!( " {} transactions: {}", - pretty_block_basics(self), + pretty_generic_header_response(&self.header), self.transactions.pretty() ) } @@ -314,184 +313,275 @@ impl UIfmt for AccessListItem { } } -impl UIfmt for TxEnvelope { +impl UIfmt for TxLegacy { fn pretty(&self) -> String { - match &self { - Self::Eip2930(tx) => format!( - " -accessList {} + format!( + " chainId {} -gasLimit {} +nonce {} gasPrice {} -hash {} -input {} +gasLimit {} +to {} +value {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_price.pretty(), + self.gas_limit.pretty(), + self.to().pretty(), + self.value.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip2930 { + fn pretty(&self) -> String { + format!( + " +chainId {} nonce {} -r {} -s {} +gasPrice {} +gasLimit {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip1559(tx) => format!( - " accessList {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_price.pretty(), + self.gas_limit.pretty(), + self.to().pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip1559 { + fn pretty(&self) -> String { + format!( + " chainId {} +nonce {} gasLimit {} -hash {} -input {} maxFeePerGas {} maxPriorityFeePerGas {} +to {} +value {} +accessList {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_limit.pretty(), + self.max_fee_per_gas.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.to().pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxEip4844Variant { + fn pretty(&self) -> String { + use alloy_consensus::TxEip4844; + let tx: &TxEip4844 = match self { + Self::TxEip4844(tx) => tx, + Self::TxEip4844WithSidecar(tx) => tx.tx(), + }; + format!( + " +chainId {} nonce {} -r {} -s {} +gasLimit {} +maxFeePerGas {} +maxPriorityFeePerGas {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip4844(tx) => format!( - " accessList {} blobVersionedHashes {} +maxFeePerBlobGas {} +input {}", + tx.chain_id.pretty(), + tx.nonce.pretty(), + tx.gas_limit.pretty(), + tx.max_fee_per_gas.pretty(), + tx.max_priority_fee_per_gas.pretty(), + tx.to.pretty(), + tx.value.pretty(), + tx.access_list.pretty(), + tx.blob_versioned_hashes.pretty(), + tx.max_fee_per_blob_gas.pretty(), + tx.input.pretty(), + ) + } +} + +impl UIfmt for TxEip7702 { + fn pretty(&self) -> String { + format!( + " chainId {} +nonce {} gasLimit {} -hash {} -input {} -maxFeePerBlobGas {} maxFeePerGas {} maxPriorityFeePerGas {} -nonce {} -r {} -s {} to {} -type {} value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.blob_versioned_hashes().unwrap_or(&[]).pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_blob_gas().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - Self::Eip7702(tx) => format!( - " accessList {} authorizationList {} -chainId {} +input {}", + self.chain_id.pretty(), + self.nonce.pretty(), + self.gas_limit.pretty(), + self.max_fee_per_gas.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.to.pretty(), + self.value.pretty(), + self.access_list.pretty(), + self.authorization_list.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TxDeposit { + fn pretty(&self) -> String { + format!( + " +sourceHash {} +from {} +to {} +mint {} +value {} gasLimit {} -hash {} -input {} -maxFeePerGas {} +isSystemTransaction {} +input {}", + self.source_hash.pretty(), + self.from.pretty(), + self.to().pretty(), + self.mint.pretty(), + self.value.pretty(), + self.gas_limit.pretty(), + self.is_system_transaction, + self.input.pretty(), + ) + } +} + +impl UIfmt for TxPostExec { + fn pretty(&self) -> String { + format!( + " +blockNumber {} +gasRefundEntries {:?} +input {}", + self.payload.block_number.pretty(), + self.payload.gas_refund_entries, + self.input.pretty(), + ) + } +} + +impl UIfmt for Call { + fn pretty(&self) -> String { + format!( + "to: {}, value: {}, input: {}", + self.to.into_to().pretty(), + self.value.pretty(), + self.input.pretty(), + ) + } +} + +impl UIfmt for TempoTransaction { + fn pretty(&self) -> String { + format!( + " +chainId {} +feeToken {} maxPriorityFeePerGas {} +maxFeePerGas {} +gasLimit {} +calls {} +accessList {} +nonceKey {} nonce {} -r {} -s {} -to {} +feePayerSignature {} +validBefore {} +validAfter {}", + self.chain_id.pretty(), + self.fee_token.pretty(), + self.max_priority_fee_per_gas.pretty(), + self.max_fee_per_gas.pretty(), + self.gas_limit.pretty(), + self.calls.pretty(), + self.access_list.pretty(), + self.nonce_key.pretty(), + self.nonce.pretty(), + self.fee_payer_signature.pretty(), + self.valid_before.pretty(), + self.valid_after.pretty(), + ) + } +} + +impl UIfmt for TempoSignature { + fn pretty(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } +} + +impl UIfmt for AASigned { + fn pretty(&self) -> String { + format!( + " +hash {} type {} -value {} -yParity {}", - self.access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.authorization_list() - .as_ref() - .map(|l| l.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.chain_id().pretty(), - self.gas_limit().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - _ => format!( - " -gas {} -gasPrice {} +{} +tempoSignature {}", + self.hash().pretty(), + self.tx().ty(), + self.tx().pretty().trim_start(), + self.signature().pretty(), + ) + } +} + +impl UIfmt for Signed +where + Self: TxHashRef, +{ + fn pretty(&self) -> String { + format!( + " hash {} -input {} -nonce {} +type {} +{} r {} s {} -to {} -type {} -v {} -value {}", - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - self.as_legacy() - .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) - .unwrap_or_default(), - self.as_legacy() - .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) - .unwrap_or_default(), - self.to().pretty(), - self.ty(), - self.as_legacy() - .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) - .unwrap_or_default(), - self.value().pretty(), - ), +yParity {}", + self.tx_hash().pretty(), + self.ty(), + self.tx().pretty().trim_start(), + FixedBytes::from(self.signature().r()).pretty(), + FixedBytes::from(self.signature().s()).pretty(), + (if self.signature().v() { 1u64 } else { 0 }).pretty(), + ) + } +} + +impl UIfmt for TxEnvelope { + fn pretty(&self) -> String { + match self { + Self::Legacy(tx) => tx.pretty(), + Self::Eip2930(tx) => tx.pretty(), + Self::Eip1559(tx) => tx.pretty(), + Self::Eip4844(tx) => tx.pretty(), + Self::Eip7702(tx) => tx.pretty(), } } } @@ -504,245 +594,44 @@ impl UIfmt for AnyTxEnvelope { format!( " hash {} -type {} +type {:#x} {} ", tx.hash.pretty(), tx.ty(), - tx.inner.fields.pretty(), + tx.inner.fields.pretty().trim_start(), ) } } } } -impl UIfmt for Transaction { + +impl UIfmt for OpTxEnvelope { fn pretty(&self) -> String { - match &self.inner.inner() { - TxEnvelope::Eip2930(tx) => format!( - " -accessList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -gasPrice {} -hash {} -input {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.inner.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip1559(tx) => format!( - " -accessList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip4844(tx) => format!( - " -accessList {} -blobVersionedHashes {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerBlobGas {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.blob_versioned_hashes().unwrap_or(&[]).pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_blob_gas().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - TxEnvelope::Eip7702(tx) => format!( - " -accessList {} -authorizationList {} -blockHash {} -blockNumber {} -chainId {} -from {} -gasLimit {} -hash {} -input {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -type {} -value {} -yParity {}", - self.inner - .access_list() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.authorization_list() - .as_ref() - .map(|l| l.iter().collect::>()) - .unwrap_or_default() - .pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.chain_id().pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - tx.hash().pretty(), - self.input().pretty(), - self.max_fee_per_gas().pretty(), - self.max_priority_fee_per_gas().pretty(), - self.nonce().pretty(), - FixedBytes::from(tx.signature().r()).pretty(), - FixedBytes::from(tx.signature().s()).pretty(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner.ty(), - self.value().pretty(), - (if tx.signature().v() { 1u64 } else { 0 }).pretty(), - ), - _ => format!( - " -blockHash {} -blockNumber {} -from {} -gas {} -gasPrice {} -hash {} -input {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -v {} -value {}", - self.block_hash.pretty(), - self.block_number.pretty(), - self.inner.signer().pretty(), - self.gas_limit().pretty(), - self.gas_price().pretty(), - self.inner.tx_hash().pretty(), - self.input().pretty(), - self.nonce().pretty(), - self.inner - .as_legacy() - .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) - .unwrap_or_default(), - self.inner - .as_legacy() - .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) - .unwrap_or_default(), - self.to().pretty(), - self.transaction_index.pretty(), - self.inner - .as_legacy() - .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) - .unwrap_or_default(), - self.value().pretty(), - ), + match self { + Self::Legacy(tx) => tx.pretty(), + Self::Eip2930(tx) => tx.pretty(), + Self::Eip1559(tx) => tx.pretty(), + Self::Eip7702(tx) => tx.pretty(), + Self::Deposit(tx) => tx.pretty(), + Self::PostExec(tx) => tx.pretty(), } } } -impl UIfmt for Transaction { +impl UIfmt for TempoTxEnvelope { + fn pretty(&self) -> String { + match self { + Self::Legacy(tx) => tx.pretty(), + Self::Eip2930(tx) => tx.pretty(), + Self::Eip1559(tx) => tx.pretty(), + Self::Eip7702(tx) => tx.pretty(), + Self::AA(tx) => tx.pretty(), + } + } +} + +impl UIfmt for Transaction { fn pretty(&self) -> String { format!( " @@ -751,14 +640,27 @@ blockNumber {} from {} transactionIndex {} effectiveGasPrice {} -{} - ", +{}", self.block_hash.pretty(), self.block_number.pretty(), self.inner.signer().pretty(), self.transaction_index.pretty(), self.effective_gas_price.pretty(), - self.inner.pretty(), + self.inner.inner().pretty().trim_start(), + ) + } +} + +impl UIfmt for op_alloy_rpc_types::Transaction { + fn pretty(&self) -> String { + format!( + " +depositNonce {} +depositReceiptVersion {} +{}", + self.deposit_nonce.pretty(), + self.deposit_receipt_version.pretty(), + self.inner.pretty().trim_start(), ) } } @@ -787,103 +689,309 @@ impl UIfmt for WithOtherFields { #[expect(missing_docs)] pub enum EthValue { U64(U64), + Address(Address), U256(U256), U64Array(Vec), U256Array(Vec), Other(serde_json::Value), } -impl From for EthValue { - fn from(val: serde_json::Value) -> Self { - serde_json::from_value(val).expect("infallible") +impl From for EthValue { + fn from(val: serde_json::Value) -> Self { + serde_json::from_value(val).expect("infallible") + } +} + +impl UIfmt for EthValue { + fn pretty(&self) -> String { + match self { + Self::U64(num) => num.pretty(), + Self::U256(num) => num.pretty(), + Self::Address(addr) => addr.pretty(), + Self::U64Array(arr) => arr.pretty(), + Self::U256Array(arr) => arr.pretty(), + Self::Other(val) => val.to_string().trim_matches('"').to_string(), + } + } +} + +impl UIfmt for SignedAuthorization { + fn pretty(&self) -> String { + let signed_authorization = serde_json::to_string(self).unwrap_or("".to_string()); + + match self.recover_authority() { + Ok(authority) => format!( + "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}", + ), + Err(e) => format!( + "{{recoveredAuthority: , signedAuthority: {signed_authorization}}}", + ), + } + } +} + +pub trait UIfmtHeaderExt { + fn size_pretty(&self) -> String; + fn total_difficulty_pretty(&self) -> String; +} + +impl UIfmtHeaderExt for Header { + fn size_pretty(&self) -> String { + self.size.pretty() + } + + fn total_difficulty_pretty(&self) -> String { + self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() + } +} + +impl UIfmtHeaderExt for AnyRpcHeader { + fn size_pretty(&self) -> String { + self.size.pretty() + } + + fn total_difficulty_pretty(&self) -> String { + self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty() + } +} + +impl UIfmtHeaderExt for TempoHeaderResponse { + fn size_pretty(&self) -> String { + self.inner.size.pretty() + } + + fn total_difficulty_pretty(&self) -> String { + self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty() + } +} + +pub trait UIfmtSignatureExt { + fn signature_pretty(&self) -> Option<(String, String, String)>; +} + +impl UIfmtSignatureExt for TxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + let sig = self.signature(); + Some(( + FixedBytes::from(sig.r()).pretty(), + FixedBytes::from(sig.s()).pretty(), + U8::from_le_slice(&sig.as_bytes()[64..]).pretty(), + )) + } +} + +impl UIfmtSignatureExt for AnyTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + self.as_envelope().and_then(|envelope| envelope.signature_pretty()) + } +} + +impl UIfmtSignatureExt for OpTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + self.signature().map(|sig| { + ( + FixedBytes::from(sig.r()).pretty(), + FixedBytes::from(sig.s()).pretty(), + U8::from_le_slice(&sig.as_bytes()[64..]).pretty(), + ) + }) + } +} + +impl UIfmtSignatureExt for TempoTxEnvelope { + fn signature_pretty(&self) -> Option<(String, String, String)> { + let sig = match self { + Self::Legacy(tx) => Some(tx.signature()), + Self::Eip2930(tx) => Some(tx.signature()), + Self::Eip1559(tx) => Some(tx.signature()), + Self::Eip7702(tx) => Some(tx.signature()), + Self::AA(tempo_tx) => { + if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig)) = + tempo_tx.signature() + { + Some(sig) + } else { + None + } + } + }?; + Some(( + FixedBytes::from(sig.r()).pretty(), + FixedBytes::from(sig.s()).pretty(), + U8::from_le_slice(&sig.as_bytes()[64..]).pretty(), + )) + } +} + +pub trait UIfmtReceiptExt { + fn logs_pretty(&self) -> String; + fn logs_bloom_pretty(&self) -> String; + fn tx_type_pretty(&self) -> String; +} + +fn receipt_logs_pretty>(receipt: &TransactionReceipt) -> String { + serde_json::to_string(receipt.inner.logs()).unwrap_or_default() +} + +fn receipt_logs_bloom_pretty>(receipt: &TransactionReceipt) -> String { + receipt.inner.bloom().pretty() +} + +impl UIfmtReceiptExt for TransactionReceipt { + fn logs_pretty(&self) -> String { + receipt_logs_pretty(self) + } + + fn logs_bloom_pretty(&self) -> String { + receipt_logs_bloom_pretty(self) + } + + fn tx_type_pretty(&self) -> String { + self.transaction_type().to_string() + } +} + +impl UIfmtReceiptExt for AnyTransactionReceipt { + fn logs_pretty(&self) -> String { + receipt_logs_pretty(&self.inner) + } + + fn logs_bloom_pretty(&self) -> String { + receipt_logs_bloom_pretty(&self.inner) + } + + fn tx_type_pretty(&self) -> String { + self.inner.inner.r#type.to_string() + } +} + +impl UIfmt for TempoTransactionReceipt { + fn pretty(&self) -> String { + let receipt = &self.inner; + + let mut pretty = format!( + " +blockHash {} +blockNumber {} +contractAddress {} +cumulativeGasUsed {} +effectiveGasPrice {} +from {} +gasUsed {} +logs {} +logsBloom {} +root {} +status {} +transactionHash {} +transactionIndex {} +type {} +feePayer {} +feeToken {}", + receipt.block_hash().pretty(), + receipt.block_number().pretty(), + receipt.contract_address().pretty(), + receipt.cumulative_gas_used().pretty(), + receipt.effective_gas_price().pretty(), + receipt.from().pretty(), + receipt.gas_used().pretty(), + serde_json::to_string(receipt.inner.logs()).unwrap(), + receipt.inner.logs_bloom.pretty(), + self.state_root().pretty(), + receipt.status().pretty(), + receipt.transaction_hash().pretty(), + receipt.transaction_index().pretty(), + receipt.inner.receipt.tx_type as u8, + self.fee_payer.pretty(), + self.fee_token.pretty(), + ); + + if let Some(to) = receipt.to() { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + pretty } } -impl UIfmt for EthValue { - fn pretty(&self) -> String { - match self { - Self::U64(num) => num.pretty(), - Self::U256(num) => num.pretty(), - Self::U64Array(arr) => arr.pretty(), - Self::U256Array(arr) => arr.pretty(), - Self::Other(val) => val.to_string().trim_matches('"').to_string(), - } +impl UIfmtReceiptExt for TempoTransactionReceipt { + fn logs_pretty(&self) -> String { + serde_json::to_string(self.inner.inner.logs()).unwrap_or_default() } -} -impl UIfmt for SignedAuthorization { - fn pretty(&self) -> String { - let signed_authorization = serde_json::to_string(self).unwrap_or("".to_string()); + fn logs_bloom_pretty(&self) -> String { + self.inner.inner.logs_bloom.pretty() + } - match self.recover_authority() { - Ok(authority) => format!( - "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}", - ), - Err(e) => format!( - "{{recoveredAuthority: , signedAuthority: {signed_authorization}}}", - ), - } + fn tx_type_pretty(&self) -> String { + (self.inner.inner.receipt.tx_type as u8).to_string() } } /// Returns the `UiFmt::pretty()` formatted attribute of the transactions -pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option { - let sig = match &transaction.inner.inner() { - AnyTxEnvelope::Ethereum(envelope) => match &envelope { - TxEnvelope::Eip2930(tx) => Some(tx.signature()), - TxEnvelope::Eip1559(tx) => Some(tx.signature()), - TxEnvelope::Eip4844(tx) => Some(tx.signature()), - TxEnvelope::Eip7702(tx) => Some(tx.signature()), - TxEnvelope::Legacy(tx) => Some(tx.signature()), - }, - _ => None, - }; +pub fn get_pretty_tx_attr(transaction: &N::TransactionResponse, attr: &str) -> Option +where + N: Network, + N::TxEnvelope: UIfmtSignatureExt, +{ + let (r, s, v) = transaction.as_ref().signature_pretty().unwrap_or_default(); match attr { - "blockHash" | "block_hash" => Some(transaction.block_hash.pretty()), - "blockNumber" | "block_number" => Some(transaction.block_number.pretty()), - "from" => Some(transaction.inner.signer().pretty()), - "gas" => Some(transaction.gas_limit().pretty()), - "gasPrice" | "gas_price" => Some(Transaction::gas_price(transaction).pretty()), + "blockHash" | "block_hash" => { + Some(alloy_network::TransactionResponse::block_hash(transaction).pretty()) + } + "blockNumber" | "block_number" => { + Some(alloy_network::TransactionResponse::block_number(transaction).pretty()) + } + "from" => Some(alloy_network::TransactionResponse::from(transaction).pretty()), + "gas" => Some(TxTrait::gas_limit(transaction).pretty()), + "gasPrice" | "gas_price" => Some(TxTrait::max_fee_per_gas(transaction).pretty()), "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()), - "input" => Some(transaction.input().pretty()), - "nonce" => Some(transaction.nonce().to_string()), - "s" => sig.map(|s| FixedBytes::from(s.s()).pretty()), - "r" => sig.map(|s| FixedBytes::from(s.r()).pretty()), - "to" => Some(transaction.to().pretty()), - "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), - "v" => sig.map(|s| U8::from_be_slice(&s.as_bytes()[64..]).pretty()), - "value" => Some(transaction.value().pretty()), + "input" => Some(TxTrait::input(transaction).pretty()), + "nonce" => Some(TxTrait::nonce(transaction).to_string()), + "s" => Some(s), + "r" => Some(r), + "to" => Some(TxTrait::to(transaction).pretty()), + "transactionIndex" | "transaction_index" => { + Some(alloy_network::TransactionResponse::transaction_index(transaction).pretty()) + } + "v" => Some(v), + "value" => Some(TxTrait::value(transaction).pretty()), _ => None, } } -/// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &AnyRpcBlock, attr: &str) -> Option { +pub fn get_pretty_block_attr(block: &N::BlockResponse, attr: &str) -> Option +where + N: Network, + N::BlockResponse: BlockResponse
, + N::HeaderResponse: UIfmtHeaderExt, +{ match attr { - "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), - "difficulty" => Some(block.header.difficulty.pretty()), - "extraData" | "extra_data" => Some(block.header.extra_data.pretty()), - "gasLimit" | "gas_limit" => Some(block.header.gas_limit.pretty()), - "gasUsed" | "gas_used" => Some(block.header.gas_used.pretty()), - "hash" => Some(block.header.hash.pretty()), - "logsBloom" | "logs_bloom" => Some(block.header.logs_bloom.pretty()), - "miner" | "author" => Some(block.header.inner.beneficiary.pretty()), - "mixHash" | "mix_hash" => Some(block.header.mix_hash.pretty()), - "nonce" => Some(block.header.nonce.pretty()), - "number" => Some(block.header.number.pretty()), - "parentHash" | "parent_hash" => Some(block.header.parent_hash.pretty()), - "transactionsRoot" | "transactions_root" => Some(block.header.transactions_root.pretty()), - "receiptsRoot" | "receipts_root" => Some(block.header.receipts_root.pretty()), - "sha3Uncles" | "sha_3_uncles" => Some(block.header.ommers_hash.pretty()), - "size" => Some(block.header.size.pretty()), - "stateRoot" | "state_root" => Some(block.header.state_root.pretty()), - "timestamp" => Some(block.header.timestamp.pretty()), - "totalDifficulty" | "total_difficult" => Some(block.header.total_difficulty.pretty()), - "blobGasUsed" | "blob_gas_used" => Some(block.header.blob_gas_used.pretty()), - "excessBlobGas" | "excess_blob_gas" => Some(block.header.excess_blob_gas.pretty()), - "requestsHash" | "requests_hash" => Some(block.header.requests_hash.pretty()), + "baseFeePerGas" | "base_fee_per_gas" => Some(block.header().base_fee_per_gas().pretty()), + "difficulty" => Some(block.header().difficulty().pretty()), + "extraData" | "extra_data" => Some(block.header().extra_data().pretty()), + "gasLimit" | "gas_limit" => Some(block.header().gas_limit().pretty()), + "gasUsed" | "gas_used" => Some(block.header().gas_used().pretty()), + "hash" => Some(block.header().hash().pretty()), + "logsBloom" | "logs_bloom" => Some(block.header().logs_bloom().pretty()), + "miner" | "author" => Some(block.header().beneficiary().pretty()), + "mixHash" | "mix_hash" => Some(block.header().mix_hash().pretty()), + "nonce" => Some(block.header().nonce().pretty()), + "number" => Some(block.header().number().pretty()), + "parentHash" | "parent_hash" => Some(block.header().parent_hash().pretty()), + "transactionsRoot" | "transactions_root" => { + Some(block.header().transactions_root().pretty()) + } + "receiptsRoot" | "receipts_root" => Some(block.header().receipts_root().pretty()), + "sha3Uncles" | "sha_3_uncles" => Some(block.header().ommers_hash().pretty()), + "size" => Some(block.header().size_pretty()), + "stateRoot" | "state_root" => Some(block.header().state_root().pretty()), + "timestamp" => Some(block.header().timestamp().pretty()), + "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()), + "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()), + "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()), + "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()), other => { - if let Some(value) = block.other.get(other) { + if let Some(value) = block.other_fields().and_then(|fields| fields.get(other)) { let val = EthValue::from(value.clone()); return Some(val.pretty()); } @@ -892,42 +1000,34 @@ pub fn get_pretty_block_attr(block: &AnyRpcBlock, attr: &str) -> Option } } -fn pretty_block_basics(block: &Block>) -> String { - let Block { - header: - Header { - hash, - size, - total_difficulty, - inner: - AnyHeader { - parent_hash, - ommers_hash, - beneficiary, - state_root, - transactions_root, - receipts_root, - logs_bloom, - difficulty, - number, - gas_limit, - gas_used, - timestamp, - extra_data, - mix_hash, - nonce, - base_fee_per_gas, - withdrawals_root, - blob_gas_used, - excess_blob_gas, - parent_beacon_block_root, - requests_hash, - }, - }, - uncles: _, - transactions: _, - withdrawals: _, - } = block; +pub fn get_pretty_receipt_attr(receipt: &N::ReceiptResponse, attr: &str) -> Option +where + N: Network, + N::ReceiptResponse: ReceiptResponse + UIfmtReceiptExt, +{ + match attr { + "blockHash" | "block_hash" => Some(receipt.block_hash().pretty()), + "blockNumber" | "block_number" => Some(receipt.block_number().pretty()), + "contractAddress" | "contract_address" => Some(receipt.contract_address().pretty()), + "cumulativeGasUsed" | "cumulative_gas_used" => Some(receipt.cumulative_gas_used().pretty()), + "effectiveGasPrice" | "effective_gas_price" => Some(receipt.effective_gas_price().pretty()), + "from" => Some(receipt.from().pretty()), + "gasUsed" | "gas_used" => Some(receipt.gas_used().pretty()), + "logs" => Some(receipt.logs_pretty()), + "logsBloom" | "logs_bloom" => Some(receipt.logs_bloom_pretty()), + "root" | "stateRoot" | "state_root" => Some(receipt.state_root().pretty()), + "status" | "statusCode" | "status_code" => Some(receipt.status().pretty()), + "transactionHash" | "transaction_hash" => Some(receipt.transaction_hash().pretty()), + "transactionIndex" | "transaction_index" => Some(receipt.transaction_index().pretty()), + "to" => Some(receipt.to().pretty()), + "type" | "transaction_type" => Some(receipt.tx_type_pretty()), + "blobGasPrice" | "blob_gas_price" => Some(receipt.blob_gas_price().pretty()), + "blobGasUsed" | "blob_gas_used" => Some(receipt.blob_gas_used().pretty()), + _ => None, + } +} + +fn pretty_generic_header_response(header: &H) -> String { format!( " baseFeePerGas {} @@ -954,44 +1054,72 @@ totalDifficulty {} blobGasUsed {} excessBlobGas {} requestsHash {}", - base_fee_per_gas.pretty(), - difficulty.pretty(), - extra_data.pretty(), - gas_limit.pretty(), - gas_used.pretty(), - hash.pretty(), - logs_bloom.pretty(), - beneficiary.pretty(), - mix_hash.pretty(), - nonce.pretty(), - number.pretty(), - parent_hash.pretty(), - parent_beacon_block_root.pretty(), - transactions_root.pretty(), - receipts_root.pretty(), - ommers_hash.pretty(), - size.pretty(), - state_root.pretty(), - timestamp.pretty(), - chrono::DateTime::from_timestamp(*timestamp as i64, 0) - .expect("block timestamp in range") - .to_rfc2822(), - withdrawals_root.pretty(), - total_difficulty.pretty(), - blob_gas_used.pretty(), - excess_blob_gas.pretty(), - requests_hash.pretty(), + header.base_fee_per_gas().pretty(), + header.difficulty().pretty(), + header.extra_data().pretty(), + header.gas_limit().pretty(), + header.gas_used().pretty(), + header.hash().pretty(), + header.logs_bloom().pretty(), + header.beneficiary().pretty(), + header.mix_hash().pretty(), + header.nonce().pretty(), + header.number().pretty(), + header.parent_hash().pretty(), + header.parent_beacon_block_root().pretty(), + header.transactions_root().pretty(), + header.receipts_root().pretty(), + header.ommers_hash().pretty(), + header.size_pretty(), + header.state_root().pretty(), + header.timestamp().pretty(), + fmt_timestamp(header.timestamp()), + header.withdrawals_root().pretty(), + header.total_difficulty_pretty(), + header.blob_gas_used().pretty(), + header.excess_blob_gas().pretty(), + header.requests_hash().pretty(), ) } +/// Formats the timestamp to string +/// +/// Assumes timestamp is seconds, but handles millis if it is too large +fn fmt_timestamp(timestamp: u64) -> String { + // Tue Jan 19 2038 03:14:07 GMT+0000 + if timestamp > 2147483647 { + // assume this is in millis, incorrectly set to millis by a node + chrono::DateTime::from_timestamp_millis(timestamp as i64) + .expect("block timestamp in range") + .to_rfc3339() + } else { + // assume this is still in seconds + chrono::DateTime::from_timestamp(timestamp as i64, 0) + .expect("block timestamp in range") + .to_rfc2822() + } +} + #[cfg(test)] mod tests { use super::*; + use alloy_network::Ethereum; use alloy_primitives::B256; use alloy_rpc_types::Authorization; use similar_asserts::assert_eq; use std::str::FromStr; + #[test] + fn format_date_time() { + // Fri Aug 29 2025 08:05:38 GMT+0000 + let timestamp = 1756454738u64; + + let datetime = fmt_timestamp(timestamp); + assert_eq!(datetime, "Fri, 29 Aug 2025 08:05:38 +0000"); + let datetime = fmt_timestamp(timestamp * 1000); + assert_eq!(datetime, "2025-08-29T08:05:38+00:00"); + } + #[test] fn can_format_bytes32() { let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000") @@ -1009,6 +1137,57 @@ mod tests { #[test] fn can_pretty_print_optimism_tx() { + let s = r#" + { + "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae", + "blockNumber": "0x1b4", + "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6", + "gas": "0x11cbbdc", + "gasPrice": "0x0", + "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f", + "input": "0xd294f093", + "nonce": "0xa2", + "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF", + "transactionIndex": "0x0", + "value": "0x0", + "v": "0x38", + "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee", + "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583", + "depositNonce": "", + "depositReceiptVersion": "0x1" + } + "#; + + let tx: op_alloy_rpc_types::Transaction = serde_json::from_str(s).unwrap(); + assert_eq!( + tx.pretty().trim(), + r" +depositNonce +depositReceiptVersion 1 +blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae +blockNumber 436 +from 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6 +transactionIndex 0 +effectiveGasPrice 0 +hash 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f +type 0 +chainId 10 +nonce 162 +gasPrice 0 +gasLimit 18660316 +to 0x4a16A42407AA491564643E1dfc1fd50af29794eF +value 0 +input 0xd294f093 +r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee +s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +yParity 1 +" + .trim() + ); + } + + #[test] + fn can_pretty_print_optimism_tx_through_any() { let s = r#" { "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae", @@ -1036,23 +1215,26 @@ mod tests { } "#; - let tx: WithOtherFields = serde_json::from_str(s).unwrap(); + let tx: WithOtherFields> = serde_json::from_str(s).unwrap(); assert_eq!(tx.pretty().trim(), r" blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae blockNumber 436 from 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6 -gas 18660316 -gasPrice 0 +transactionIndex 0 +effectiveGasPrice 0 hash 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f -input 0xd294f093 +type 0 +chainId 10 nonce 162 -r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee -s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +gasPrice 0 +gasLimit 18660316 to 0x4a16A42407AA491564643E1dfc1fd50af29794eF -transactionIndex 0 -v 1 value 0 +input 0xd294f093 +r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee +s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +yParity 1 index 435 l1BlockNumber 12691036 l1Timestamp 1624460128 @@ -1083,17 +1265,29 @@ txType 0 "v": "0x1", "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1", "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc", - "chainId": "0x66a", - "accessList": [ - { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] }, - { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] } - ] + "chainId": "0x66a", + "accessList": [ + { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] }, + { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] } + ] } "#; - let tx: Transaction = serde_json::from_str(s).unwrap(); assert_eq!(tx.pretty().trim(), r" +blockHash 0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81 +blockNumber 76573 +from 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 +transactionIndex 2 +effectiveGasPrice 1000000000 +hash 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090 +type 1 +chainId 1642 +nonce 28 +gasPrice 1000000000 +gasLimit 27615 +to 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 +value 0 accessList [ 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [ 0x1122334455667788990011223344556677889900112233445566778899001122 @@ -1101,21 +1295,9 @@ accessList [ ] 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => [] ] -blockHash 0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81 -blockNumber 76573 -chainId 1642 -from 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 -gasLimit 27615 -gasPrice 1000000000 -hash 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090 input 0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a -nonce 28 r 0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1 s 0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc -to 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 -transactionIndex 2 -type 1 -value 0 yParity 1 ".trim() ); @@ -1157,27 +1339,28 @@ yParity 1 assert_eq!( tx.pretty().trim(), r" -accessList [ - 0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [ - 0x0000000000000000000000000000000000000000000000000000000000000000 - ] -] blockHash 0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125 blockNumber 30279 -chainId 1642 from 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00 -gasLimit 100000 +transactionIndex 65 +effectiveGasPrice 20000000000 hash 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244 -input 0x48600055323160015500 +type 2 +chainId 1642 +nonce 300 +gasLimit 100000 maxFeePerGas 20000000000 maxPriorityFeePerGas 20000000000 -nonce 300 -r 0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34 -s 0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158 to -transactionIndex 65 -type 2 value 0 +accessList [ + 0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [ + 0x0000000000000000000000000000000000000000000000000000000000000000 + ] +] +input 0x48600055323160015500 +r 0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34 +s 0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158 yParity 1 " .trim() @@ -1187,57 +1370,58 @@ yParity 1 #[test] fn can_pretty_print_eip4884() { let s = r#"{ - "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052", - "blockNumber": "0x2a1cb", - "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2", - "gas": "0x5208", - "gasPrice": "0x1d1a94a201c", - "maxFeePerGas": "0x1d1a94a201c", - "maxPriorityFeePerGas": "0x1d1a94a201c", - "maxFeePerBlobGas": "0x3e8", - "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00", - "input": "0x", - "nonce": "0x1b483", - "to": "0x000000000000000000000000000000000000f1c1", - "transactionIndex": "0x0", - "value": "0x0", - "type": "0x3", - "accessList": [], - "chainId": "0x1a1f0ff42", - "blobVersionedHashes": [ - "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76" - ], - "v": "0x0", - "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1", - "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b", - "yParity": "0x0" - } + "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052", + "blockNumber": "0x2a1cb", + "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2", + "gas": "0x5208", + "gasPrice": "0x1d1a94a201c", + "maxFeePerGas": "0x1d1a94a201c", + "maxPriorityFeePerGas": "0x1d1a94a201c", + "maxFeePerBlobGas": "0x3e8", + "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00", + "input": "0x", + "nonce": "0x1b483", + "to": "0x000000000000000000000000000000000000f1c1", + "transactionIndex": "0x0", + "value": "0x0", + "type": "0x3", + "accessList": [], + "chainId": "0x1a1f0ff42", + "blobVersionedHashes": [ + "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76" + ], + "v": "0x0", + "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1", + "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b", + "yParity": "0x0" + } "#; let tx: Transaction = serde_json::from_str(s).unwrap(); assert_eq!( tx.pretty().trim(), r" -accessList [] -blobVersionedHashes [ - 0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76 -] blockHash 0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052 blockNumber 172491 -chainId 7011893058 from 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2 -gasLimit 21000 +transactionIndex 0 +effectiveGasPrice 2000000000028 hash 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00 -input 0x -maxFeePerBlobGas 1000 +type 3 +chainId 7011893058 +nonce 111747 +gasLimit 21000 maxFeePerGas 2000000000028 maxPriorityFeePerGas 2000000000028 -nonce 111747 -r 0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1 -s 0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b to 0x000000000000000000000000000000000000f1C1 -transactionIndex 0 -type 3 value 0 +accessList [] +blobVersionedHashes [ + 0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76 +] +maxFeePerBlobGas 1000 +input 0x +r 0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1 +s 0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b yParity 0 " .trim() @@ -1248,21 +1432,26 @@ yParity 0 fn print_block_w_txs() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; let block: Block = serde_json::from_str(block).unwrap(); - let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 + let output = " +blockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 blockNumber 3 from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a -gas 90000 -gasPrice 20000000000 +transactionIndex 0 +effectiveGasPrice 20000000000 hash 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067 -input 0x +type 0 +chainId 1 nonce 2 +gasPrice 20000000000 +gasLimit 90000 +to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e +value 0 +input 0x r 0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88 s 0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e -to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e -transactionIndex 0 -v 0 -value 0".to_string(); - let txs = match block.transactions { +yParity 0" + .to_string(); + let txs = match block.transactions() { BlockTransactions::Full(txs) => txs, _ => panic!("not full transactions"), }; @@ -1314,41 +1503,47 @@ value 0".to_string(); #[test] fn test_pretty_tx_attr() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block> = serde_json::from_str(block).unwrap(); - let txs = match block.transactions { + let block: ::BlockResponse = serde_json::from_str(block).unwrap(); + let txs = match block.transactions() { BlockTransactions::Full(txes) => txes, _ => panic!("not full transactions"), }; - assert_eq!(None, get_pretty_tx_attr(&txs[0], "")); - assert_eq!(Some("3".to_string()), get_pretty_tx_attr(&txs[0], "blockNumber")); + assert_eq!(None, get_pretty_tx_attr::(&txs[0], "")); + assert_eq!(Some("3".to_string()), get_pretty_tx_attr::(&txs[0], "blockNumber")); assert_eq!( Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr(&txs[0], "from") + get_pretty_tx_attr::(&txs[0], "from") + ); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::(&txs[0], "gas")); + assert_eq!( + Some("20000000000".to_string()), + get_pretty_tx_attr::(&txs[0], "gasPrice") ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&txs[0], "gas")); - assert_eq!(Some("20000000000".to_string()), get_pretty_tx_attr(&txs[0], "gasPrice")); assert_eq!( Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr(&txs[0], "hash") + get_pretty_tx_attr::(&txs[0], "hash") ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&txs[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&txs[0], "nonce")); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr::(&txs[0], "nonce")); assert_eq!( Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr(&txs[0], "r") + get_pretty_tx_attr::(&txs[0], "r") ); assert_eq!( Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr(&txs[0], "s") + get_pretty_tx_attr::(&txs[0], "s") ); assert_eq!( Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr(&txs[0], "to") + get_pretty_tx_attr::(&txs[0], "to") + ); + assert_eq!( + Some("0".to_string()), + get_pretty_tx_attr::(&txs[0], "transactionIndex") ); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "transactionIndex")); - assert_eq!(Some("27".to_string()), get_pretty_tx_attr(&txs[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "value")); + assert_eq!(Some("27".to_string()), get_pretty_tx_attr::(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr::(&txs[0], "value")); } #[test] @@ -1371,7 +1566,7 @@ value 0".to_string(); "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", - "difficulty": "0x27f07", + "difficulty": "0x1", "totalDifficulty": "0x27f07", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "size": "0x27f07", @@ -1380,59 +1575,81 @@ value 0".to_string(); "gasUsed": "0x9f759", "timestamp": "0x54e34e8e", "transactions": [], - "uncles": [] + "uncles": [], } ); - let block: AnyRpcBlock = serde_json::from_value(json).unwrap(); + let block: ::BlockResponse = serde_json::from_value(json).unwrap(); - assert_eq!(None, get_pretty_block_attr(&block, "")); - assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "difficulty")); + assert_eq!(None, get_pretty_block_attr::(&block, "")); + assert_eq!( + Some("7".to_string()), + get_pretty_block_attr::(&block, "baseFeePerGas") + ); + assert_eq!(Some("1".to_string()), get_pretty_block_attr::(&block, "difficulty")); assert_eq!( Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()), - get_pretty_block_attr(&block, "extraData") + get_pretty_block_attr::(&block, "extraData") + ); + assert_eq!( + Some("653145".to_string()), + get_pretty_block_attr::(&block, "gasLimit") + ); + assert_eq!( + Some("653145".to_string()), + get_pretty_block_attr::(&block, "gasUsed") ); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasLimit")); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasUsed")); assert_eq!( Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), - get_pretty_block_attr(&block, "hash") + get_pretty_block_attr::(&block, "hash") ); - assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr(&block, "logsBloom")); + assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::(&block, "logsBloom")); assert_eq!( Some("0x0000000000000000000000000000000000000001".to_string()), - get_pretty_block_attr(&block, "miner") + get_pretty_block_attr::(&block, "miner") ); assert_eq!( Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()), - get_pretty_block_attr(&block, "mixHash") + get_pretty_block_attr::(&block, "mixHash") + ); + assert_eq!( + Some("0x0000000000000000".to_string()), + get_pretty_block_attr::(&block, "nonce") ); - assert_eq!(Some("0x0000000000000000".to_string()), get_pretty_block_attr(&block, "nonce")); - assert_eq!(Some("436".to_string()), get_pretty_block_attr(&block, "number")); + assert_eq!(Some("436".to_string()), get_pretty_block_attr::(&block, "number")); assert_eq!( Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), - get_pretty_block_attr(&block, "parentHash") + get_pretty_block_attr::(&block, "parentHash") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr(&block, "transactionsRoot") + get_pretty_block_attr::(&block, "transactionsRoot") ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr(&block, "receiptsRoot") + get_pretty_block_attr::(&block, "receiptsRoot") ); assert_eq!( Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()), - get_pretty_block_attr(&block, "sha3Uncles") + get_pretty_block_attr::(&block, "sha3Uncles") ); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "size")); + assert_eq!(Some("163591".to_string()), get_pretty_block_attr::(&block, "size")); assert_eq!( Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()), - get_pretty_block_attr(&block, "stateRoot") + get_pretty_block_attr::(&block, "stateRoot") + ); + assert_eq!( + Some("1424182926".to_string()), + get_pretty_block_attr::(&block, "timestamp") + ); + assert_eq!( + Some("163591".to_string()), + get_pretty_block_attr::(&block, "totalDifficulty") ); - assert_eq!(Some("1424182926".to_string()), get_pretty_block_attr(&block, "timestamp")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "totalDifficulty")); + + let pretty = pretty_generic_header_response(block.header()); + assert!(pretty.contains("difficulty 1"), "{pretty}"); + assert!(pretty.contains("totalDifficulty 163591"), "{pretty}"); } #[test] @@ -1508,4 +1725,187 @@ l1GasUsed 1600 r#"{recoveredAuthority: 0xf3eaBD0de6Ca1aE7fC4D81FfD6C9a40e5D5D7e30, signedAuthority: {"chainId":"0x1","address":"0x000000000000000000000000000000000000dead","nonce":"0x2a","yParity":"0x1","r":"0x14","s":"0x1e"}}"# ); } + + #[test] + fn can_pretty_print_tempo_tx() { + let s = r#"{ + "type":"0x76", + "chainId":"0xa5bd", + "feeToken":"0x20c0000000000000000000000000000000000001", + "maxPriorityFeePerGas":"0x0", + "maxFeePerGas":"0x2cb417800", + "gas":"0x2d178", + "calls":[ + { + "data":null, + "input":"0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680", + "to":"0x20c0000000000000000000000000000000000000", + "value":"0x0" + }, + { + "data":null, + "input":"0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330", + "to":"0xdec0000000000000000000000000000000000000", + "value":"0x0" + } + ], + "accessList":[], + "nonceKey":"0x0", + "nonce":"0x0", + "feePayerSignature":null, + "validBefore":null, + "validAfter":null, + "keyAuthorization":null, + "aaAuthorizationList":[], + "signature":{ + "pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd", + "pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e", + "r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f", + "s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e", + "type":"webAuthn", + "webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d" + }, + "hash":"0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd", + "blockHash":"0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689", + "blockNumber":"0x321fde", + "transactionIndex":"0x0", + "from":"0x566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f", + "gasPrice":"0x2540be400" + }"#; + + let tx: Transaction = serde_json::from_str(s).unwrap(); + + assert_eq!( + tx.pretty().trim(), + r#" +blockHash 0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689 +blockNumber 3284958 +from 0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F +transactionIndex 0 +effectiveGasPrice 10000000000 +hash 0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd +type 118 +chainId 42429 +feeToken 0x20C0000000000000000000000000000000000001 +maxPriorityFeePerGas 0 +maxFeePerGas 12000000000 +gasLimit 184696 +calls [ + to: 0x20C0000000000000000000000000000000000000, value: 0, input: 0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680 + to: 0xDEc0000000000000000000000000000000000000, value: 0, input: 0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330 +] +accessList [] +nonceKey 0 +nonce 0 +feePayerSignature +validBefore +validAfter +tempoSignature {"type":"webAuthn","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"} +"# + .trim() + ); + } + + #[test] + fn can_pretty_print_tempo_receipt() { + let s = r#"{"type":"0x76","status":"0x1","cumulativeGasUsed":"0x176d7f4","logs":[{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","gasUsed":"0xcc6a","effectiveGasPrice":"0x4a817c802","from":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd","to":"0x20c0000000000000000000000000000000000000","contractAddress":null,"feeToken":"0x20c0000000000000000000000000000000000003","feePayer":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"}"#; + + let tx: TempoTransactionReceipt = serde_json::from_str(s).unwrap(); + + assert_eq!( + tx.pretty().trim(), + r#" +blockHash 0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66 +blockNumber 6922711 +contractAddress +cumulativeGasUsed 24565748 +effectiveGasPrice 20000000002 +from 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd +gasUsed 52330 +logs [{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}] +logsBloom 0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000 +root +status true +transactionHash 0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f +transactionIndex 99 +type 118 +feePayer 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd +feeToken 0x20C0000000000000000000000000000000000003 +to 0x20C0000000000000000000000000000000000000 +"# + .trim() + ); + } + + #[test] + fn test_ethereum_receipt_uifmt() { + let s = r#"{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x1234567890123456789012345678901234567890123456789012345678901234","transactionIndex":"0x0","blockHash":"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd","blockNumber":"0x1","gasUsed":"0x5208","effectiveGasPrice":"0x3b9aca00","from":"0x1234567890123456789012345678901234567890","to":"0x0987654321098765432109876543210987654321","contractAddress":null}"#; + let receipt: TransactionReceipt = serde_json::from_str(s).unwrap(); + + let pretty_output = receipt.pretty(); + + assert!(pretty_output.contains("blockHash")); + assert!(pretty_output.contains("blockNumber")); + assert!(pretty_output.contains("status")); + assert!(pretty_output.contains("gasUsed")); + assert!(pretty_output.contains("transactionHash")); + assert!(pretty_output.contains("type")); + assert!( + pretty_output + .contains("0x1234567890123456789012345678901234567890123456789012345678901234") + ); + assert!(pretty_output.contains("1 (success)")); + assert!(pretty_output.contains("0x0987654321098765432109876543210987654321")); + } + + #[test] + fn test_get_pretty_receipt_attr() { + let receipt_json = serde_json::json!({ + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1234567890123456789012345678901234567890123456789012345678901234", + "transactionIndex": "0x0", + "blockHash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "blockNumber": "0x1", + "gasUsed": "0x5208", + "effectiveGasPrice": "0x3b9aca00", + "from": "0x1234567890123456789012345678901234567890", + "to": "0x0987654321098765432109876543210987654321", + "contractAddress": null + }); + + let receipt: ::ReceiptResponse = + serde_json::from_value(receipt_json).unwrap(); + + // Test basic receipt attributes + assert_eq!( + Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()), + get_pretty_receipt_attr::(&receipt, "blockHash") + ); + assert_eq!( + Some("1".to_string()), + get_pretty_receipt_attr::(&receipt, "blockNumber") + ); + assert_eq!( + Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()), + get_pretty_receipt_attr::(&receipt, "transactionHash") + ); + assert_eq!( + Some("21000".to_string()), + get_pretty_receipt_attr::(&receipt, "gasUsed") + ); + assert_eq!( + Some("true".to_string()), + get_pretty_receipt_attr::(&receipt, "status") + ); + assert_eq!( + Some("EIP-1559".to_string()), + get_pretty_receipt_attr::(&receipt, "type") + ); + assert_eq!(Some("[]".to_string()), get_pretty_receipt_attr::(&receipt, "logs")); + assert!(get_pretty_receipt_attr::(&receipt, "logsBloom").is_some()); + } } diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index cdba532d842dd..358f3edd01bb1 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -1,27 +1,31 @@ //! ABI related helper functions. +use alloy_chains::Chain; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Error, Event, Function, Param}; -use alloy_primitives::{hex, Address, LogData}; +use alloy_primitives::{Address, LogData, hex}; use eyre::{Context, ContextCompat, Result}; -use foundry_block_explorers::{ - contract::ContractMetadata, errors::EtherscanError, Client, EtherscanApiVersion, -}; -use foundry_config::Chain; -use std::{future::Future, pin::Pin}; +use foundry_block_explorers::{Client, contract::ContractMetadata, errors::EtherscanError}; +use std::pin::Pin; pub fn encode_args(inputs: &[Param], args: I) -> Result> where I: IntoIterator, S: AsRef, { + let args: Vec = args.into_iter().collect(); + + if inputs.len() != args.len() { + eyre::bail!("encode length mismatch: expected {} types, got {}", inputs.len(), args.len()) + } + std::iter::zip(inputs, args) .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) .collect() } /// Given a function and a vector of string arguments, it proceeds to convert the args to alloy -/// [DynSolValue]s and then ABI encode them. +/// [DynSolValue]s and then ABI encode them, prefixes the encoded data with the function selector. pub fn encode_function_args(func: &Function, args: I) -> Result> where I: IntoIterator, @@ -30,6 +34,16 @@ where Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?) } +/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy +/// [DynSolValue]s and then ABI encode them. Doesn't prefix the function selector. +pub fn encode_function_args_raw(func: &Function, args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + Ok(func.abi_encode_input_raw(&encode_args(&func.inputs, args)?)?) +} + /// Given a function and a vector of string arguments, it proceeds to convert the args to alloy /// [DynSolValue]s and encode them using the packed encoding. pub fn encode_function_args_packed(func: &Function, args: I) -> Result> @@ -37,6 +51,16 @@ where I: IntoIterator, S: AsRef, { + let args: Vec = args.into_iter().collect(); + + if func.inputs.len() != args.len() { + eyre::bail!( + "encode length mismatch: expected {} types, got {}", + func.inputs.len(), + args.len(), + ); + } + let params: Vec> = std::iter::zip(&func.inputs, args) .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) .collect::>>()? @@ -86,7 +110,7 @@ pub fn get_event(sig: &str) -> Result { /// Given an error signature string, it tries to parse it as a `Error` pub fn get_error(sig: &str) -> Result { - Error::parse(sig).wrap_err("could not parse event signature") + Error::parse(sig).wrap_err("could not parse error signature") } /// Given an event without indexed parameters and a rawlog, it tries to return the event with the @@ -101,8 +125,8 @@ pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event { if param.name.is_empty() { param.name = format!("param{index}"); } - if num_inputs == indexed_params || - (num_address_params == indexed_params && param.ty == "address") + if num_inputs == indexed_params + || (num_address_params == indexed_params && param.ty == "address") { param.indexed = true; } @@ -119,9 +143,13 @@ pub async fn get_func_etherscan( args: &[String], chain: Chain, etherscan_api_key: &str, - etherscan_api_version: EtherscanApiVersion, + etherscan_api_url: Option<&str>, ) -> Result { - let client = Client::new_with_api_version(chain, etherscan_api_key, etherscan_api_version)?; + let client = if let Some(api_url) = etherscan_api_url { + Client::builder().with_api_key(etherscan_api_key).with_api_url(api_url)?.build()? + } else { + Client::new(chain, etherscan_api_key)? + }; let source = find_source(client, contract).await?; let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?; @@ -131,7 +159,7 @@ pub async fn get_func_etherscan( for func in funcs { let res = encode_function_args(&func, args); if res.is_ok() { - return Ok(func) + return Ok(func); } } @@ -247,4 +275,43 @@ mod tests { assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2))); } + + #[test] + fn test_encode_args_length_validation() { + use alloy_json_abi::Param; + + let params = vec![ + Param { + name: "a".to_string(), + ty: "uint256".to_string(), + internal_type: None, + components: vec![], + }, + Param { + name: "b".to_string(), + ty: "address".to_string(), + internal_type: None, + components: vec![], + }, + ]; + + // Less arguments than parameters + let args = vec!["1"]; + let res = encode_args(¶ms, &args); + assert!(res.is_err()); + assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch")); + + // Exact number of arguments and parameters + let args = vec!["1", "0x0000000000000000000000000000000000000001"]; + let res = encode_args(¶ms, &args); + assert!(res.is_ok()); + let values = res.unwrap(); + assert_eq!(values.len(), 2); + + // More arguments than parameters + let args = vec!["1", "0x0000000000000000000000000000000000000001", "extra"]; + let res = encode_args(¶ms, &args); + assert!(res.is_err()); + assert!(format!("{}", res.unwrap_err()).contains("encode length mismatch")); + } } diff --git a/crates/common/src/calc.rs b/crates/common/src/calc.rs index 2d7d6fb9ebdc7..351ae8d3f8eae 100644 --- a/crates/common/src/calc.rs +++ b/crates/common/src/calc.rs @@ -1,7 +1,6 @@ //! Commonly used calculations. /// Returns the mean of the slice. -#[inline] pub fn mean(values: &[u64]) -> u64 { if values.is_empty() { return 0; @@ -11,7 +10,6 @@ pub fn mean(values: &[u64]) -> u64 { } /// Returns the median of a _sorted_ slice. -#[inline] pub fn median_sorted(values: &[u64]) -> u64 { if values.is_empty() { return 0; @@ -19,11 +17,7 @@ pub fn median_sorted(values: &[u64]) -> u64 { let len = values.len(); let mid = len / 2; - if len % 2 == 0 { - (values[mid - 1] + values[mid]) / 2 - } else { - values[mid] - } + if len.is_multiple_of(2) { (values[mid - 1] + values[mid]) / 2 } else { values[mid] } } #[cfg(test)] diff --git a/crates/common/src/comments/comment.rs b/crates/common/src/comments/comment.rs new file mode 100644 index 0000000000000..c19ade41ce9c8 --- /dev/null +++ b/crates/common/src/comments/comment.rs @@ -0,0 +1,70 @@ +//! Modified from [`rustc_ast::util::comments`](https://github.com/rust-lang/rust/blob/07d3fd1d9b9c1f07475b96a9d168564bf528db68/compiler/rustc_ast/src/util/comments.rs). + +use solar::parse::{ + ast::{CommentKind, Span}, + interface::BytePos, +}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum CommentStyle { + /// No code on either side of each line of the comment + Isolated, + /// Code exists to the left of the comment + Trailing, + /// Code before /* foo */ and after the comment + Mixed, + /// Just a manual blank line "\n\n", for layout + BlankLine, +} + +impl CommentStyle { + pub const fn is_mixed(&self) -> bool { + matches!(self, Self::Mixed) + } + pub const fn is_trailing(&self) -> bool { + matches!(self, Self::Trailing) + } + pub const fn is_isolated(&self) -> bool { + matches!(self, Self::Isolated) + } + pub const fn is_blank(&self) -> bool { + matches!(self, Self::BlankLine) + } +} + +#[derive(Clone, Debug)] +pub struct Comment { + pub lines: Vec, + pub span: Span, + pub style: CommentStyle, + pub is_doc: bool, + pub kind: CommentKind, +} + +impl Comment { + pub fn pos(&self) -> BytePos { + self.span.lo() + } + + pub const fn prefix(&self) -> Option<&'static str> { + if self.lines.is_empty() { + return None; + } + Some(match (self.kind, self.is_doc) { + (CommentKind::Line, false) => "//", + (CommentKind::Line, true) => "///", + (CommentKind::Block, false) => "/*", + (CommentKind::Block, true) => "/**", + }) + } + + pub const fn suffix(&self) -> Option<&'static str> { + if self.lines.is_empty() { + return None; + } + match self.kind { + CommentKind::Line => None, + CommentKind::Block => Some("*/"), + } + } +} diff --git a/crates/common/src/comments/inline_config.rs b/crates/common/src/comments/inline_config.rs new file mode 100644 index 0000000000000..cf736d1ad1f33 --- /dev/null +++ b/crates/common/src/comments/inline_config.rs @@ -0,0 +1,449 @@ +use solar::{ + interface::{BytePos, RelativeBytePos, SourceMap, Span}, + parse::ast::{self, Visit}, +}; +use std::{ + collections::{HashMap, hash_map::Entry}, + hash::Hash, + ops::ControlFlow, +}; + +/// A disabled formatting range. +#[derive(Debug, Clone, Copy)] +struct DisabledRange { + /// Start position, inclusive. + lo: T, + /// End position, inclusive. + hi: T, +} + +impl DisabledRange { + fn includes(&self, span: Span) -> bool { + span.lo() >= self.lo && span.hi() <= self.hi + } +} + +/// An inline config item +#[derive(Clone, Debug)] +pub enum InlineConfigItem { + /// Disables the next code (AST) item regardless of newlines + DisableNextItem(I), + /// Disables formatting on the current line + DisableLine(I), + /// Disables formatting between the next newline and the newline after + DisableNextLine(I), + /// Disables formatting for any code that follows this and before the next "disable-end" + DisableStart(I), + /// Disables formatting for any code that precedes this and after the previous "disable-start" + DisableEnd(I), +} + +impl InlineConfigItem> { + /// Parse an inline config item from a string. Validates IDs against available IDs. + pub fn parse(s: &str, available_ids: &[&str]) -> Result { + let (disable, relevant) = s.split_once('(').unwrap_or((s, "")); + let ids = if relevant.is_empty() || relevant == "all)" { + vec!["all".to_string()] + } else { + match relevant.split_once(')') { + Some((id_str, _)) => id_str.split(',').map(|s| s.trim().to_string()).collect(), + None => return Err(InvalidInlineConfigItem::Syntax(s.into())), + } + }; + + // Validate IDs + let mut invalid_ids = Vec::new(); + 'ids: for id in &ids { + if id == "all" { + continue; + } + for available_id in available_ids { + if *available_id == id { + continue 'ids; + } + } + invalid_ids.push(id.to_owned()); + } + + if !invalid_ids.is_empty() { + return Err(InvalidInlineConfigItem::Ids(invalid_ids)); + } + + let res = match disable { + "disable-next-item" => Self::DisableNextItem(ids), + "disable-line" => Self::DisableLine(ids), + "disable-next-line" => Self::DisableNextLine(ids), + "disable-start" => Self::DisableStart(ids), + "disable-end" => Self::DisableEnd(ids), + s => return Err(InvalidInlineConfigItem::Syntax(s.into())), + }; + + Ok(res) + } +} + +impl std::str::FromStr for InlineConfigItem<()> { + type Err = InvalidInlineConfigItem; + fn from_str(s: &str) -> Result { + Ok(match s { + "disable-next-item" => Self::DisableNextItem(()), + "disable-line" => Self::DisableLine(()), + "disable-next-line" => Self::DisableNextLine(()), + "disable-start" => Self::DisableStart(()), + "disable-end" => Self::DisableEnd(()), + s => return Err(InvalidInlineConfigItem::Syntax(s.into())), + }) + } +} + +#[derive(Debug)] +pub enum InvalidInlineConfigItem { + Syntax(String), + Ids(Vec), +} + +impl std::fmt::Display for InvalidInlineConfigItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Syntax(s) => write!(f, "invalid inline config item: {s}"), + Self::Ids(ids) => { + write!(f, "unknown id: '{}'", ids.join("', '")) + } + } + } +} + +/// A trait for `InlineConfigItem` types that can be iterated over to produce keys for storage. +pub trait ItemIdIterator { + type Item: Eq + Hash + Clone; + fn into_iter(self) -> impl IntoIterator; +} + +impl ItemIdIterator for () { + type Item = (); + fn into_iter(self) -> impl IntoIterator { + std::iter::once(()) + } +} + +impl ItemIdIterator for Vec { + type Item = String; + fn into_iter(self) -> impl IntoIterator { + self + } +} + +#[derive(Debug, Default)] +pub struct InlineConfig { + disabled_ranges: HashMap>, +} + +impl InlineConfig { + /// Build a new inline config with an iterator of inline config items and their locations in a + /// source file. + /// + /// # Panics + /// + /// Panics if `items` is not sorted in ascending order of [`Span`]s. + pub fn from_ast<'ast>( + items: impl IntoIterator)>, + ast: &'ast ast::SourceUnit<'ast>, + source_map: &SourceMap, + ) -> Self { + Self::build(items, source_map, |offset| NextItemFinder::new(offset).find(ast)) + } + + fn build( + items: impl IntoIterator)>, + source_map: &SourceMap, + mut find_next_item: impl FnMut(BytePos) -> Option, + ) -> Self { + let mut cfg = Self::new(); + let mut disabled_blocks = HashMap::new(); + + let mut prev_sp = Span::DUMMY; + for (sp, item) in items { + if cfg!(debug_assertions) { + assert!(sp >= prev_sp, "InlineConfig::new: unsorted items: {sp:?} < {prev_sp:?}"); + prev_sp = sp; + } + + cfg.disable_item(sp, item, source_map, &mut disabled_blocks, &mut find_next_item); + } + + for (id, (_, lo, hi)) in disabled_blocks { + cfg.disable(id, DisabledRange { lo, hi }); + } + + cfg + } + + fn new() -> Self { + Self { disabled_ranges: HashMap::new() } + } + + fn disable_many(&mut self, ids: I, range: DisabledRange) { + for id in ids.into_iter() { + self.disable(id, range); + } + } + + fn disable(&mut self, id: I::Item, range: DisabledRange) { + self.disabled_ranges.entry(id).or_default().push(range); + } + + fn disable_item( + &mut self, + span: Span, + item: InlineConfigItem, + source_map: &SourceMap, + disabled_blocks: &mut HashMap, + find_next_item: &mut dyn FnMut(BytePos) -> Option, + ) { + let result = source_map.span_to_source(span).unwrap(); + let file = result.file; + let comment_range = result.data; + let src = file.src.as_str(); + + #[allow(clippy::collapsible_match)] + match item { + InlineConfigItem::DisableNextItem(ids) => { + if let Some(next_item) = find_next_item(span.hi()) { + self.disable_many( + ids, + DisabledRange { lo: next_item.lo(), hi: next_item.hi() }, + ); + } + } + InlineConfigItem::DisableLine(ids) => { + let start = src[..comment_range.start].rfind('\n').map_or(0, |i| i); + let end = src[comment_range.end..] + .find('\n') + .map_or(src.len(), |i| comment_range.end + i); + self.disable_many( + ids, + DisabledRange { + lo: file.absolute_position(RelativeBytePos::from_usize(start)), + hi: file.absolute_position(RelativeBytePos::from_usize(end)), + }, + ); + } + InlineConfigItem::DisableNextLine(ids) => { + if let Some(offset) = src[comment_range.end..].find('\n') { + let next_line = comment_range.end + offset + 1; + if next_line < src.len() { + let end = src[next_line..].find('\n').map_or(src.len(), |i| next_line + i); + self.disable_many( + ids, + DisabledRange { + lo: file.absolute_position(RelativeBytePos::from_usize( + comment_range.start, + )), + hi: file.absolute_position(RelativeBytePos::from_usize(end)), + }, + ); + } + } + } + + InlineConfigItem::DisableStart(ids) => { + for id in ids.into_iter() { + disabled_blocks.entry(id).and_modify(|(depth, _, _)| *depth += 1).or_insert(( + 1, + span.lo(), + // Use file end as fallback for unclosed blocks + file.absolute_position(RelativeBytePos::from_usize(src.len())), + )); + } + } + InlineConfigItem::DisableEnd(ids) => { + for id in ids.into_iter() { + if let Entry::Occupied(mut entry) = disabled_blocks.entry(id) { + let (depth, lo, _) = entry.get_mut(); + *depth = depth.saturating_sub(1); + + if *depth == 0 { + let lo = *lo; + let (id, _) = entry.remove_entry(); + + self.disable(id, DisabledRange { lo, hi: span.hi() }); + } + } + } + } + } + } +} + +impl InlineConfig<()> { + /// Checks if a span is disabled (only applicable when inline config doesn't require an id). + pub fn is_disabled(&self, span: Span) -> bool { + if let Some(ranges) = self.disabled_ranges.get(&()) { + return ranges.iter().any(|range| range.includes(span)); + } + false + } +} + +impl InlineConfig +where + I::Item: std::borrow::Borrow, +{ + /// Checks if a span is disabled for a specific id. Also checks against "all", which disables + /// all rules. + pub fn is_id_disabled(&self, span: Span, id: &str) -> bool { + self.is_id_disabled_inner(span, id) + || (id != "all" && self.is_id_disabled_inner(span, "all")) + } + + fn is_id_disabled_inner(&self, span: Span, id: &str) -> bool { + if let Some(ranges) = self.disabled_ranges.get(id) + && ranges.iter().any(|range| range.includes(span)) + { + return true; + } + + false + } +} + +macro_rules! find_next_item { + ($self:expr, $x:expr, $span:expr, $walk:ident) => {{ + let span = $span; + // If the item is *entirely* before the offset, skip traversing it. + if span.hi() < $self.offset { + return ControlFlow::Continue(()); + } + // Check if this item starts after the offset. + if span.lo() > $self.offset { + return ControlFlow::Break(span); + } + // Otherwise, continue traversing inside this item. + $self.$walk($x) + }}; +} + +/// An AST visitor that finds the first `Item` that starts after a given offset. +#[derive(Debug)] +struct NextItemFinder { + /// The offset to search after. + offset: BytePos, +} + +impl NextItemFinder { + const fn new(offset: BytePos) -> Self { + Self { offset } + } + + /// Finds the next AST item or statement which a span that begins after the `offset`. + fn find<'ast>(&mut self, ast: &'ast ast::SourceUnit<'ast>) -> Option { + match self.visit_source_unit(ast) { + ControlFlow::Break(span) => Some(span), + ControlFlow::Continue(()) => None, + } + } +} + +impl<'ast> ast::Visit<'ast> for NextItemFinder { + type BreakValue = Span; + + fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow { + find_next_item!(self, item, item.span, walk_item) + } + + fn visit_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) -> ControlFlow { + find_next_item!(self, stmt, stmt.span, walk_stmt) + } + + fn visit_yul_stmt( + &mut self, + stmt: &'ast ast::yul::Stmt<'ast>, + ) -> ControlFlow { + find_next_item!(self, stmt, stmt.span, walk_yul_stmt) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + impl DisabledRange { + fn to_byte_pos(self) -> DisabledRange { + DisabledRange:: { + lo: BytePos::from_usize(self.lo), + hi: BytePos::from_usize(self.hi), + } + } + + fn includes(&self, range: std::ops::Range) -> bool { + self.to_byte_pos().includes(Span::new( + BytePos::from_usize(range.start), + BytePos::from_usize(range.end), + )) + } + } + + #[test] + fn test_disabled_range_includes() { + let strict = DisabledRange { lo: 10, hi: 20 }; + assert!(strict.includes(10..20)); + assert!(strict.includes(12..18)); + assert!(!strict.includes(5..15)); // Partial overlap fails + } + + #[test] + fn test_inline_config_item_from_str() { + assert!(matches!( + "disable-next-item".parse::>().unwrap(), + InlineConfigItem::DisableNextItem(()) + )); + assert!(matches!( + "disable-line".parse::>().unwrap(), + InlineConfigItem::DisableLine(()) + )); + assert!(matches!( + "disable-start".parse::>().unwrap(), + InlineConfigItem::DisableStart(()) + )); + assert!(matches!( + "disable-end".parse::>().unwrap(), + InlineConfigItem::DisableEnd(()) + )); + assert!("invalid".parse::>().is_err()); + } + + #[test] + fn test_inline_config_item_parse_with_lints() { + let lint_ids = vec!["lint1", "lint2"]; + + // No lints = "all" + match InlineConfigItem::parse("disable-line", &lint_ids).unwrap() { + InlineConfigItem::DisableLine(lints) => assert_eq!(lints, vec!["all"]), + _ => panic!("Wrong type"), + } + + // Valid single lint + match InlineConfigItem::parse("disable-start(lint1)", &lint_ids).unwrap() { + InlineConfigItem::DisableStart(lints) => assert_eq!(lints, vec!["lint1"]), + _ => panic!("Wrong type"), + } + + // Multiple lints with spaces + match InlineConfigItem::parse("disable-end(lint1, lint2)", &lint_ids).unwrap() { + InlineConfigItem::DisableEnd(lints) => assert_eq!(lints, vec!["lint1", "lint2"]), + _ => panic!("Wrong type"), + } + + // Invalid lint ID + assert!(matches!( + InlineConfigItem::parse("disable-line(unknown)", &lint_ids), + Err(InvalidInlineConfigItem::Ids(_)) + )); + + // Malformed syntax + assert!(matches!( + InlineConfigItem::parse("disable-line(lint1", &lint_ids), + Err(InvalidInlineConfigItem::Syntax(_)) + )); + } +} diff --git a/crates/common/src/comments/mod.rs b/crates/common/src/comments/mod.rs new file mode 100644 index 0000000000000..812590b126e71 --- /dev/null +++ b/crates/common/src/comments/mod.rs @@ -0,0 +1,463 @@ +use crate::iter::IterDelimited; +use solar::parse::{ + ast::{CommentKind, Span}, + interface::{BytePos, CharPos, SourceMap, source_map::SourceFile}, + lexer::token::RawTokenKind as TokenKind, +}; +use std::fmt; + +mod comment; +pub use comment::{Comment, CommentStyle}; + +pub mod inline_config; + +pub const DISABLE_START: &str = "forgefmt: disable-start"; +pub const DISABLE_END: &str = "forgefmt: disable-end"; + +pub struct Comments { + comments: std::collections::VecDeque, +} + +impl fmt::Debug for Comments { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Comments")?; + f.debug_list().entries(self.iter()).finish() + } +} + +impl Comments { + pub fn new( + sf: &SourceFile, + sm: &SourceMap, + normalize_cmnts: bool, + group_cmnts: bool, + tab_width: Option, + ) -> Self { + let gatherer = CommentGatherer::new(sf, sm, normalize_cmnts, tab_width).gather(); + + Self { + comments: if group_cmnts { gatherer.group().into() } else { gatherer.comments.into() }, + } + } + + pub fn peek(&self) -> Option<&Comment> { + self.comments.front() + } + + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option { + self.comments.pop_front() + } + + pub fn iter(&self) -> impl Iterator { + self.comments.iter() + } + + /// Adds a new comment at the beginning of the list. + /// + /// Should only be used when comments are gathered scattered, and must be manually sorted. + /// + /// **WARNING:** This struct works under the assumption that comments are always sorted by + /// ascending span position. It is the caller's responsibility to ensure that this premise + /// always holds true. + pub fn push_front(&mut self, cmnt: Comment) { + self.comments.push_front(cmnt) + } + + /// Finds the first trailing comment on the same line as `span_pos`, allowing for `Mixed` + /// style comments to appear before it. + /// + /// Returns the comment and its index in the buffer. + pub fn peek_trailing( + &self, + sm: &SourceMap, + span_pos: BytePos, + next_pos: Option, + ) -> Option<(&Comment, usize)> { + let span_line = sm.lookup_char_pos(span_pos).line; + for (i, cmnt) in self.iter().enumerate() { + // If we have moved to the next line, we can stop. + let comment_line = sm.lookup_char_pos(cmnt.pos()).line; + if comment_line != span_line { + break; + } + + // The comment must start after the given span position. + if cmnt.pos() < span_pos { + continue; + } + + // The comment must be before the next element. + if cmnt.pos() >= next_pos.unwrap_or_else(|| cmnt.pos() + BytePos(1)) { + break; + } + + // Stop when we find a trailing or a non-mixed comment + match cmnt.style { + CommentStyle::Mixed => {} + CommentStyle::Trailing => return Some((cmnt, i)), + _ => break, + } + } + None + } +} + +struct CommentGatherer<'ast> { + sf: &'ast SourceFile, + sm: &'ast SourceMap, + text: &'ast str, + start_bpos: BytePos, + pos: usize, + comments: Vec, + code_to_the_left: bool, + disabled_block_depth: usize, + tab_width: Option, +} + +impl<'ast> CommentGatherer<'ast> { + fn new( + sf: &'ast SourceFile, + sm: &'ast SourceMap, + normalize_cmnts: bool, + tab_width: Option, + ) -> Self { + Self { + sf, + sm, + text: sf.src.as_str(), + start_bpos: sf.start_pos, + pos: 0, + comments: Vec::new(), + code_to_the_left: false, + disabled_block_depth: if normalize_cmnts { 0 } else { 1 }, + tab_width, + } + } + + /// Consumes the gatherer and returns the collected comments. + fn gather(mut self) -> Self { + for token in solar::parse::Cursor::new(&self.text[self.pos..]) { + self.process_token(token); + } + self + } + + /// Post-processes a list of comments to group consecutive comments. + /// + /// Necessary for properly indenting multi-line trailing comments, which would + /// otherwise be parsed as a `Trailing` followed by several `Isolated`. + fn group(self) -> Vec { + let mut processed = Vec::new(); + let mut cursor = self.comments.into_iter().peekable(); + + while let Some(mut current) = cursor.next() { + if current.kind == CommentKind::Line + && (current.style.is_trailing() || current.style.is_isolated()) + { + let mut ref_line = self.sm.lookup_char_pos(current.span.hi()).line; + while let Some(next_comment) = cursor.peek() { + if !next_comment.style.is_isolated() + || next_comment.kind != CommentKind::Line + || ref_line + 1 != self.sm.lookup_char_pos(next_comment.span.lo()).line + { + break; + } + + let next_to_merge = cursor.next().unwrap(); + current.lines.extend(next_to_merge.lines); + current.span = current.span.to(next_to_merge.span); + ref_line += 1; + } + } + + processed.push(current); + } + + processed + } + + /// Creates a `Span` relative to the source file's start position. + fn make_span(&self, range: std::ops::Range) -> Span { + Span::new(self.start_bpos + range.start as u32, self.start_bpos + range.end as u32) + } + + /// Processes a single token from the source. + fn process_token(&mut self, token: solar::parse::lexer::token::RawToken) { + let token_range = self.pos..self.pos + token.len as usize; + let span = self.make_span(token_range.clone()); + let token_text = &self.text[token_range]; + + // Keep track of disabled blocks + if token_text.trim_start().contains(DISABLE_START) { + self.disabled_block_depth += 1; + } else if token_text.trim_start().contains(DISABLE_END) { + self.disabled_block_depth -= 1; + } + + #[allow(clippy::collapsible_match)] + match token.kind { + TokenKind::Whitespace => { + if let Some(mut idx) = token_text.find('\n') { + self.code_to_the_left = false; + + while let Some(next_newline) = token_text[idx + 1..].find('\n') { + idx += 1 + next_newline; + let pos = self.pos + idx; + self.comments.push(Comment { + is_doc: false, + kind: CommentKind::Line, + style: CommentStyle::BlankLine, + lines: vec![], + span: self.make_span(pos..pos), + }); + // If not disabled, early-exit as we want only a single blank line. + if self.disabled_block_depth == 0 { + break; + } + } + } + } + TokenKind::BlockComment { is_doc, .. } => { + let code_to_the_right = !matches!( + self.text[self.pos + token.len as usize..].chars().next(), + Some('\r' | '\n') + ); + let style = match (self.code_to_the_left, code_to_the_right) { + (_, true) => CommentStyle::Mixed, + (false, false) => CommentStyle::Isolated, + (true, false) => CommentStyle::Trailing, + }; + let kind = CommentKind::Block; + + // Count the number of chars since the start of the line by rescanning. + let pos_in_file = self.start_bpos + BytePos(self.pos as u32); + let line_begin_in_file = line_begin_pos(self.sf, pos_in_file); + let line_begin_pos = (line_begin_in_file - self.start_bpos).to_usize(); + let mut col = CharPos(self.text[line_begin_pos..self.pos].chars().count()); + + // To preserve alignment in multi-line non-doc comments, normalize the block based + // on its least-indented line. + if !is_doc && token_text.contains('\n') { + col = token_text.lines().skip(1).fold(col, |min, line| { + if line.is_empty() { + return min; + } + std::cmp::min( + CharPos(line.chars().count() - line.trim_start().chars().count()), + min, + ) + }) + }; + + let lines = self.split_block_comment_into_lines(token_text, is_doc, col); + self.comments.push(Comment { is_doc, kind, style, lines, span }) + } + TokenKind::LineComment { is_doc } => { + let line = + if self.disabled_block_depth != 0 { token_text } else { token_text.trim_end() }; + self.comments.push(Comment { + is_doc, + kind: CommentKind::Line, + style: if self.code_to_the_left { + CommentStyle::Trailing + } else { + CommentStyle::Isolated + }, + lines: vec![line.into()], + span, + }); + } + _ => { + self.code_to_the_left = true; + } + } + self.pos += token.len as usize; + } + + /// Splits a block comment into lines, ensuring that each line is properly formatted. + fn split_block_comment_into_lines( + &self, + text: &str, + is_doc: bool, + col: CharPos, + ) -> Vec { + // if formatting is disabled, return as is + if self.disabled_block_depth != 0 { + return vec![text.into()]; + } + + let mut res: Vec = vec![]; + let mut lines = text.lines(); + if let Some(line) = lines.next() { + let line = line.trim_end(); + // Ensure first line of a doc comment only has the `/**` decorator + if is_doc && let Some((_, second)) = line.split_once("/**") { + res.push("/**".to_string()); + if !second.trim().is_empty() { + let line = normalize_block_comment_ws(second, col).trim_end(); + // Ensure last line of a doc comment only has the `*/` decorator + if let Some((first, _)) = line.split_once("*/") { + if !first.trim().is_empty() { + res.push(format_doc_block_comment(first.trim_end(), self.tab_width)); + } + res.push(" */".to_string()); + } else { + res.push(format_doc_block_comment(line.trim_end(), self.tab_width)); + } + } + } else { + res.push(line.to_string()); + } + } + + for (pos, line) in lines.delimited() { + let line = normalize_block_comment_ws(line, col).trim_end().to_string(); + if !is_doc { + res.push(line); + continue; + } + if pos.is_last { + // Ensure last line of a doc comment only has the `*/` decorator + if let Some((first, _)) = line.split_once("*/") + && !first.trim().is_empty() + { + res.push(format_doc_block_comment(first.trim_end(), self.tab_width)); + } + res.push(" */".to_string()); + } else { + res.push(format_doc_block_comment(&line, self.tab_width)); + } + } + res + } +} + +/// Returns `None` if the first `col` chars of `s` contain a non-whitespace char. +/// Otherwise returns `Some(k)` where `k` is first char offset after that leading +/// whitespace. Note that `k` may be outside bounds of `s`. +fn all_whitespace(s: &str, col: CharPos) -> Option { + let mut idx = 0; + for (i, ch) in s.char_indices().take(col.to_usize()) { + if !ch.is_whitespace() { + return None; + } + idx = i + ch.len_utf8(); + } + Some(idx) +} + +/// Returns `Some(k)` where `k` is the byte offset of the first non-whitespace char. Returns `k = 0` +/// if `s` starts with a non-whitespace char. If `s` only contains whitespaces, returns `None`. +fn first_non_whitespace(s: &str) -> Option { + let mut len = 0; + for (i, ch) in s.char_indices() { + if ch.is_whitespace() { + len = ch.len_utf8() + } else { + return if i == 0 { Some(0) } else { Some(i + 1 - len) }; + } + } + None +} + +/// Returns a slice of `s` with a whitespace prefix removed based on `col`. If the first `col` chars +/// of `s` are all whitespace, returns a slice starting after that prefix. +fn normalize_block_comment_ws(s: &str, col: CharPos) -> &str { + let len = s.len(); + if let Some(col) = all_whitespace(s, col) { + return if col < len { &s[col..] } else { "" }; + } + if let Some(col) = first_non_whitespace(s) { + return &s[col..]; + } + s +} + +/// Formats a doc block comment line so that they have the ` *` decorator. +fn format_doc_block_comment(line: &str, tab_width: Option) -> String { + if line.is_empty() { + return (" *").to_string(); + } + + if let Some((_, rest_of_line)) = line.split_once('*') { + if rest_of_line.is_empty() { + (" *").to_string() + } else if let Some(tab_width) = tab_width { + let mut normalized = String::from(" *"); + line_with_tabs( + &mut normalized, + rest_of_line, + tab_width, + Some(Consolidation::MinOneTab), + ); + normalized + } else { + format!(" *{rest_of_line}",) + } + } else if let Some(tab_width) = tab_width { + let mut normalized = String::from(" *\t"); + line_with_tabs(&mut normalized, line, tab_width, Some(Consolidation::WithoutSpaces)); + normalized + } else { + format!(" * {line}") + } +} + +pub enum Consolidation { + MinOneTab, + WithoutSpaces, +} + +/// Normalizes the leading whitespace of a string slice according to a given tab width. +/// +/// It aggregates and converts leading whitespace (spaces and tabs) into a representation that +/// maximizes the amount of tabs. +pub fn line_with_tabs( + output: &mut String, + line: &str, + tab_width: usize, + strategy: Option, +) { + // Find the end of the leading whitespace (any sequence of spaces and tabs) + let first_non_ws = line.find(|c| c != ' ' && c != '\t').unwrap_or(line.len()); + let (leading_ws, rest_of_line) = line.split_at(first_non_ws); + + // Compute its equivalent length and derive the required amount of tabs and spaces + let total_width = + leading_ws.chars().fold(0, |width, c| width + if c == ' ' { 1 } else { tab_width }); + let (mut num_tabs, mut num_spaces) = (total_width / tab_width, total_width % tab_width); + + // Adjust based on the desired config + match strategy { + Some(Consolidation::MinOneTab) => { + if num_tabs == 0 && num_spaces != 0 { + (num_tabs, num_spaces) = (1, 0); + } else if num_spaces != 0 { + (num_tabs, num_spaces) = (num_tabs + 1, 0); + } + } + Some(Consolidation::WithoutSpaces) if num_spaces != 0 => { + (num_tabs, num_spaces) = (num_tabs + 1, 0); + } + _ => (), + }; + + // Append the normalized indentation and the rest of the line to the output + output.extend(std::iter::repeat_n('\t', num_tabs)); + output.extend(std::iter::repeat_n(' ', num_spaces)); + output.push_str(rest_of_line); +} + +/// Estimates the display width of a string, accounting for tabs. +pub fn estimate_line_width(line: &str, tab_width: usize) -> usize { + line.chars().fold(0, |width, c| width + if c == '\t' { tab_width } else { 1 }) +} + +/// Returns the `BytePos` of the beginning of the current line. +fn line_begin_pos(sf: &SourceFile, pos: BytePos) -> BytePos { + let pos = sf.relative_position(pos); + let line_index = sf.lookup_line(pos).unwrap(); + let line_start_pos = sf.lines()[line_index]; + sf.absolute_position(line_start_pos) +} diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 9d7f3735c5227..03bebb0583df6 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -1,26 +1,23 @@ //! Support for compiling [foundry_compilers::Project] use crate::{ - preprocessor::TestOptimizerPreprocessor, - reports::{report_kind, ReportKind}, - shell, - term::SpinnerReporter, - TestFunctionExt, + TestFunctionExt, preprocessor::DynamicTestLinkingPreprocessor, shell, term::SpinnerReporter, }; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; +use comfy_table::{Cell, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; use eyre::Result; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ - artifacts::{remappings::Remapping, BytecodeObject, Contract, Source}, + Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, + artifacts::{BytecodeObject, Contract, Source, remappings::Remapping}, compilers::{ - solc::{Solc, SolcCompiler}, Compiler, + solc::{Solc, SolcCompiler}, }, info::ContractInfo as CompilerContractInfo, + multi::{MultiCompiler, MultiCompilerSettings}, project::Preprocessor, report::{BasicStdoutReporter, NoReporter, Report}, solc::SolcSettings, - Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, }; use num_format::{Locale, ToFormattedString}; use std::{ @@ -29,9 +26,13 @@ use std::{ io::IsTerminal, path::{Path, PathBuf}, str::FromStr, + sync::Arc, time::Instant, }; +/// A Solar compiler instance, to grant syntactic and semantic analysis capabilities. +pub type Analysis = Arc; + /// Builder type to configure how to compile a project. /// /// This is merely a wrapper for [`Project::compile()`] which also prints to stdout depending on its @@ -41,9 +42,6 @@ pub struct ProjectCompiler { /// The root of the project. project_root: PathBuf, - /// Whether we are going to verify the contracts after compilation. - verify: Option, - /// Whether to also print contract names. print_names: Option, @@ -59,7 +57,7 @@ pub struct ProjectCompiler { /// Whether to ignore the contract initcode size limit introduced by EIP-3860. ignore_eip_3860: bool, - /// Extra files to include, that are not necessarily in the project's source dir. + /// Extra files to include, that are not necessarily in the project's source directory. files: Vec, /// Whether to compile with dynamic linking tests and scripts. @@ -79,7 +77,6 @@ impl ProjectCompiler { pub fn new() -> Self { Self { project_root: PathBuf::new(), - verify: None, print_names: None, print_sizes: None, quiet: Some(crate::shell::is_quiet()), @@ -90,23 +87,16 @@ impl ProjectCompiler { } } - /// Sets whether we are going to verify the contracts after compilation. - #[inline] - pub fn verify(mut self, yes: bool) -> Self { - self.verify = Some(yes); - self - } - /// Sets whether to print contract names. #[inline] - pub fn print_names(mut self, yes: bool) -> Self { + pub const fn print_names(mut self, yes: bool) -> Self { self.print_names = Some(yes); self } /// Sets whether to print contract sizes. #[inline] - pub fn print_sizes(mut self, yes: bool) -> Self { + pub const fn print_sizes(mut self, yes: bool) -> Self { self.print_sizes = Some(yes); self } @@ -114,21 +104,21 @@ impl ProjectCompiler { /// Sets whether to print anything at all. Overrides other `print` options. #[inline] #[doc(alias = "silent")] - pub fn quiet(mut self, yes: bool) -> Self { + pub const fn quiet(mut self, yes: bool) -> Self { self.quiet = Some(yes); self } /// Sets whether to bail on compiler errors. #[inline] - pub fn bail(mut self, yes: bool) -> Self { + pub const fn bail(mut self, yes: bool) -> Self { self.bail = Some(yes); self } /// Sets whether to ignore EIP-3860 initcode size limits. #[inline] - pub fn ignore_eip_3860(mut self, yes: bool) -> Self { + pub const fn ignore_eip_3860(mut self, yes: bool) -> Self { self.ignore_eip_3860 = yes; self } @@ -142,18 +132,19 @@ impl ProjectCompiler { /// Sets if tests should be dynamically linked. #[inline] - pub fn dynamic_test_linking(mut self, preprocess: bool) -> Self { + pub const fn dynamic_test_linking(mut self, preprocess: bool) -> Self { self.dynamic_test_linking = preprocess; self } /// Compiles the project. + #[instrument(target = "forge::compile", skip_all)] pub fn compile>( mut self, project: &Project, ) -> Result> where - TestOptimizerPreprocessor: Preprocessor, + DynamicTestLinkingPreprocessor: Preprocessor, { self.project_root = project.root().to_path_buf(); @@ -172,32 +163,22 @@ impl ProjectCompiler { let files = std::mem::take(&mut self.files); let preprocess = self.dynamic_test_linking; self.compile_with(|| { - let sources = if !files.is_empty() { - Source::read_all(files)? - } else { + let sources = if files.is_empty() { project.paths.read_input_files()? + } else { + Source::read_all(files)? }; let mut compiler = foundry_compilers::project::ProjectCompiler::with_sources(project, sources)?; if preprocess { - compiler = compiler.with_preprocessor(TestOptimizerPreprocessor); + compiler = compiler.with_preprocessor(DynamicTestLinkingPreprocessor); } compiler.compile().map_err(Into::into) }) } /// Compiles the project with the given closure - /// - /// # Example - /// - /// ```ignore - /// use foundry_common::compile::ProjectCompiler; - /// let config = foundry_config::Config::load().unwrap(); - /// let prj = config.project().unwrap(); - /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); - /// ``` - #[instrument(target = "forge::compile", skip_all)] fn compile_with, F>( self, f: F, @@ -208,7 +189,7 @@ impl ProjectCompiler { let quiet = self.quiet.unwrap_or(false); let bail = self.bail.unwrap_or(true); - let output = with_compilation_reporter(quiet, || { + let output = with_compilation_reporter(quiet, Some(self.project_root.clone()), || { tracing::debug!("compiling project"); let timer = Instant::now(); @@ -277,8 +258,7 @@ impl ProjectCompiler { sh_println!()?; } - let mut size_report = - SizeReport { report_kind: report_kind(), contracts: BTreeMap::new() }; + let mut size_report = SizeReport { contracts: BTreeMap::new() }; let mut artifacts: BTreeMap> = BTreeMap::new(); for (id, artifact) in output.artifact_ids().filter(|(id, _)| { @@ -298,8 +278,8 @@ impl ProjectCompiler { .as_ref() .map(|abi| { abi.functions().any(|f| { - f.test_function_kind().is_known() || - matches!(f.name.as_str(), "IS_TEST" | "IS_SCRIPT") + f.test_function_kind().is_known() + || matches!(f.name.as_str(), "IS_TEST" | "IS_SCRIPT") }) }) .unwrap_or(false); @@ -348,8 +328,6 @@ const CONTRACT_INITCODE_SIZE_LIMIT: usize = 49152; /// Contracts with info about their size pub struct SizeReport { - /// What kind of report to generate. - report_kind: ReportKind, /// `contract name -> info` pub contracts: BTreeMap, } @@ -388,15 +366,11 @@ impl SizeReport { impl Display for SizeReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self.report_kind { - ReportKind::Text => { - writeln!(f, "\n{}", self.format_table_output())?; - } - ReportKind::JSON => { - writeln!(f, "{}", self.format_json_output())?; - } + if shell::is_json() { + writeln!(f, "{}", self.format_json_output())?; + } else { + writeln!(f, "\n{}", self.format_table_output())?; } - Ok(()) } } @@ -425,7 +399,11 @@ impl SizeReport { fn format_table_output(&self) -> Table { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(vec![ Cell::new("Contract"), @@ -511,8 +489,6 @@ pub struct ContractInfo { /// /// If `quiet` no solc related output will be emitted to stdout. /// -/// If `verify` and it's a standalone script, throw error. Only allowed for projects. -/// /// **Note:** this expects the `target_path` to be absolute pub fn compile_target>( target_path: &Path, @@ -520,17 +496,14 @@ pub fn compile_target>( quiet: bool, ) -> Result> where - TestOptimizerPreprocessor: Preprocessor, + DynamicTestLinkingPreprocessor: Preprocessor, { ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project) } /// Creates a [Project] from an Etherscan source. -pub fn etherscan_project( - metadata: &Metadata, - target_path: impl AsRef, -) -> Result> { - let target_path = dunce::canonicalize(target_path.as_ref())?; +pub fn etherscan_project(metadata: &Metadata, target_path: &Path) -> Result { + let target_path = dunce::canonicalize(target_path)?; let sources_path = target_path.join(&metadata.contract_name); metadata.source_tree().write_to(&target_path)?; @@ -560,14 +533,18 @@ pub fn etherscan_project( .remappings(settings.remappings.clone()) .build_with_root(sources_path); + // TODO: detect vyper let v = metadata.compiler_version()?; let solc = Solc::find_or_install(&v)?; - let compiler = SolcCompiler::Specific(solc); + let compiler = MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None }; - Ok(ProjectBuilder::::default() - .settings(SolcSettings { - settings: SolcConfig::builder().settings(settings).build(), + Ok(ProjectBuilder::::default() + .settings(MultiCompilerSettings { + solc: SolcSettings { + settings: SolcConfig::builder().settings(settings).build(), + ..Default::default() + }, ..Default::default() }) .paths(paths) @@ -577,13 +554,17 @@ pub fn etherscan_project( } /// Configures the reporter and runs the given closure. -pub fn with_compilation_reporter(quiet: bool, f: impl FnOnce() -> O) -> O { +pub fn with_compilation_reporter( + quiet: bool, + project_root: Option, + f: impl FnOnce() -> O, +) -> O { #[expect(clippy::collapsible_else_if)] let reporter = if quiet || shell::is_json() { Report::new(NoReporter::default()) } else { if std::io::stdout().is_terminal() { - Report::new(SpinnerReporter::spawn()) + Report::new(SpinnerReporter::spawn(project_root)) } else { Report::new(BasicStdoutReporter::default()) } @@ -600,7 +581,7 @@ pub fn with_compilation_reporter(quiet: bool, f: impl FnOnce() -> O) -> O { /// - `Counter` - contract name only #[derive(Clone, PartialEq, Eq)] pub enum PathOrContractInfo { - /// Non-canoncalized path provided via CLI. + /// Non-canonicalized path provided via CLI. Path(PathBuf), /// Contract info provided via CLI. ContractInfo(CompilerContractInfo), @@ -610,7 +591,7 @@ impl PathOrContractInfo { /// Returns the path to the contract file if provided. pub fn path(&self) -> Option { match self { - Self::Path(path) => Some(path.to_path_buf()), + Self::Path(path) => Some(path.clone()), Self::ContractInfo(info) => info.path.as_ref().map(PathBuf::from), } } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 31c0a2345a9d7..54ad55dd2bafe 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,8 +1,8 @@ //! Commonly used constants. -use alloy_consensus::Typed2718; +use alloy_eips::Typed2718; use alloy_network::AnyTxEnvelope; -use alloy_primitives::{address, Address, Signature, B256}; +use alloy_primitives::{Address, B256, Signature, address}; use std::time::Duration; /// The dev chain-id, inherited from hardhat @@ -39,20 +39,40 @@ pub const ARBITRUM_SENDER: Address = address!("0x0000000000000000000000000000000 /// See also pub const OPTIMISM_SYSTEM_ADDRESS: Address = address!("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001"); +/// The system address, the sender of the first transaction in every block: +pub const MONAD_SYSTEM_ADDRESS: Address = address!("0x6f49a8F621353f12378d0046E7d7e4b9B249DC9e"); + +/// MegaETH system address for `Set Slots` in the MegaETH oracle. +/// +/// Transactions from this sender are submitted with gas price 0. +/// +/// See: +pub const MEGA_SYSTEM_ADDRESS: Address = address!("0xa887dcb9d5f39ef79272801d05abdf707cfbbd1d"); + /// Transaction identifier of System transaction types pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; /// Default user agent set as the header for requests that don't specify one. pub const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); -/// Returns whether the sender is a known system sender that is the first tx in every block. +/// Prefix for auto-generated type bindings using `forge bind-json`. +pub const TYPE_BINDING_PREFIX: &str = "string constant schema_"; + +/// Returns whether the sender is a known L2 system sender that is the first tx in every block. /// /// Transactions from these senders usually don't have a any fee information OR set absurdly high fees that exceed the gas limit (See: ) /// -/// See: [ARBITRUM_SENDER], [OPTIMISM_SYSTEM_ADDRESS] and [Address::ZERO] -#[inline] +/// See: [ARBITRUM_SENDER], [OPTIMISM_SYSTEM_ADDRESS], [MONAD_SYSTEM_ADDRESS], [MEGA_SYSTEM_ADDRESS] +/// and [Address::ZERO] pub fn is_known_system_sender(sender: Address) -> bool { - [ARBITRUM_SENDER, OPTIMISM_SYSTEM_ADDRESS, Address::ZERO].contains(&sender) + [ + ARBITRUM_SENDER, + OPTIMISM_SYSTEM_ADDRESS, + MONAD_SYSTEM_ADDRESS, + MEGA_SYSTEM_ADDRESS, + Address::ZERO, + ] + .contains(&sender) } pub fn is_impersonated_tx(tx: &AnyTxEnvelope) -> bool { @@ -65,7 +85,9 @@ pub fn is_impersonated_tx(tx: &AnyTxEnvelope) -> bool { pub fn is_impersonated_sig(sig: &Signature, ty: u8) -> bool { let impersonated_sig = Signature::from_scalars_and_parity(B256::with_last_byte(1), B256::with_last_byte(1), false); - if ty != SYSTEM_TRANSACTION_TYPE && sig == &impersonated_sig { + if ty != SYSTEM_TRANSACTION_TYPE + && (sig == &impersonated_sig || sig.r() == impersonated_sig.r()) + { return true; } false diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 86556f7194402..95c7d4083f34e 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -1,17 +1,18 @@ //! Commonly used contract types and functions. -use crate::compile::PathOrContractInfo; +use crate::{compile::PathOrContractInfo, find_metadata_start, strip_bytecode_placeholders}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Event, Function, JsonAbi}; -use alloy_primitives::{hex, Address, Bytes, Selector, B256}; +use alloy_primitives::{Address, B256, Bytes, Selector, hex}; use eyre::{OptionExt, Result}; use foundry_compilers::{ + ArtifactId, Project, ProjectCompileOutput, artifacts::{ - BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, - ConfigurableContractArtifact, ContractBytecodeSome, Offsets, + BytecodeObject, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow, + CompactDeployedBytecode, ConfigurableContractArtifact, ContractBytecodeSome, Offsets, + StorageLayout, }, utils::canonicalized, - ArtifactId, Project, ProjectCompileOutput, }; use std::{ collections::BTreeMap, @@ -75,6 +76,8 @@ pub struct ContractData { pub bytecode: Option, /// Contract runtime code. pub deployed_bytecode: Option, + /// Contract storage layout, if available. + pub storage_layout: Option>, } impl ContractData { @@ -87,6 +90,78 @@ impl ContractData { pub fn deployed_bytecode(&self) -> Option<&Bytes> { self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) } + + /// Returns the bytecode without placeholders, if present. + pub fn bytecode_without_placeholders(&self) -> Option { + strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?) + } + + /// Returns the deployed bytecode without placeholders, if present. + pub fn deployed_bytecode_without_placeholders(&self) -> Option { + strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?) + } +} + +/// Builder for creating a `ContractsByArtifact` instance, optionally including storage layouts +/// from project compile output. +pub struct ContractsByArtifactBuilder<'a> { + /// All compiled artifact bytecodes (borrowed). + artifacts: BTreeMap>, + /// Optionally collected storage layouts for matching artifact IDs. + storage_layouts: BTreeMap, +} + +impl<'a> ContractsByArtifactBuilder<'a> { + /// Creates a new builder from artifacts with present bytecode iterator. + pub fn new( + artifacts: impl IntoIterator)>, + ) -> Self { + Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() } + } + + /// Add storage layouts from the given `ProjectCompileOutput` to known artifacts. + pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self { + self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| { + artifact + .storage_layout + .as_ref() + .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone())) + })) + } + + /// Add storage layouts. + pub fn with_storage_layouts( + mut self, + layouts: impl IntoIterator, + ) -> Self { + self.storage_layouts.extend(layouts); + self + } + + /// Builds `ContractsByArtifact`. + pub fn build(self) -> ContractsByArtifact { + let map = self + .artifacts + .into_iter() + .filter_map(|(id, artifact)| { + let name = id.name.clone(); + let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact; + + Some(( + id.clone(), + ContractData { + name, + abi: abi?.into_owned(), + bytecode: bytecode.map(|b| b.into_owned().into()), + deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()), + storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())), + }, + )) + }) + .collect(); + + ContractsByArtifact(Arc::new(map)) + } } type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); @@ -110,6 +185,7 @@ impl ContractsByArtifact { abi: abi?, bytecode: bytecode.map(Into::into), deployed_bytecode: deployed_bytecode.map(Into::into), + storage_layout: None, }, )) }) @@ -175,94 +251,130 @@ impl ContractsByArtifact { return None; } - self.iter().find(|(_, contract)| { - let Some(deployed_bytecode) = &contract.deployed_bytecode else { - return false; - }; - let Some(deployed_code) = &deployed_bytecode.object else { - return false; - }; - - let len = match deployed_code { - BytecodeObject::Bytecode(ref bytes) => bytes.len(), - BytecodeObject::Unlinked(ref bytes) => bytes.len() / 2, - }; - - if len != code.len() { - return false; - } + let mut partial_match = None; + self.iter() + .find(|(id, contract)| { + let Some(deployed_bytecode) = &contract.deployed_bytecode else { + return false; + }; + let Some(deployed_code) = &deployed_bytecode.object else { + return false; + }; + + let len = match deployed_code { + BytecodeObject::Bytecode(bytes) => bytes.len(), + BytecodeObject::Unlinked(bytes) => bytes.len() / 2, + }; - // Collect ignored offsets by chaining link and immutable references. - let mut ignored = deployed_bytecode - .immutable_references - .values() - .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values())) - .flatten() - .cloned() - .collect::>(); - - // For libraries solidity adds a call protection prefix to the bytecode. We need to - // ignore it as it includes library address determined at runtime. - // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and - // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172 - let has_call_protection = match deployed_code { - BytecodeObject::Bytecode(ref bytes) => { - bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + if len != code.len() { + return false; } - BytecodeObject::Unlinked(ref bytes) => { - if let Ok(bytes) = - Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2]) - { + + // Collect ignored offsets by chaining link and immutable references. + let mut ignored = deployed_bytecode + .immutable_references + .values() + .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values())) + .flatten() + .cloned() + .collect::>(); + + // For libraries solidity adds a call protection prefix to the bytecode. We need to + // ignore it as it includes library address determined at runtime. + // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and + // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172 + let has_call_protection = match deployed_code { + BytecodeObject::Bytecode(bytes) => { bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) - } else { - false } + BytecodeObject::Unlinked(bytes) => { + if let Ok(bytes) = + Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2]) + { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } else { + false + } + } + }; + + if has_call_protection { + ignored.push(Offsets { start: 1, length: 20 }); } - }; - if has_call_protection { - ignored.push(Offsets { start: 1, length: 20 }); - } + let metadata_start = find_metadata_start(code); - ignored.sort_by_key(|o| o.start); + if let Some(metadata) = metadata_start { + ignored.push(Offsets { + start: metadata as u32, + length: (code.len() - metadata) as u32, + }); + } - let mut left = 0; - for offset in ignored { - let right = offset.start as usize; + ignored.sort_by_key(|o| o.start); - let matched = match deployed_code { - BytecodeObject::Bytecode(ref bytes) => bytes[left..right] == code[left..right], - BytecodeObject::Unlinked(ref bytes) => { - if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) { - bytes == code[left..right] - } else { - false + let mut left = 0; + for offset in ignored { + let right = offset.start as usize; + + let matched = match deployed_code { + BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right], + BytecodeObject::Unlinked(bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) { + bytes == code[left..right] + } else { + false + } + } + }; + + if !matched { + return false; + } + + left = right + offset.length as usize; + } + + let is_partial = if left < code.len() { + match deployed_code { + BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..], + BytecodeObject::Unlinked(bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) { + bytes == code[left..] + } else { + false + } } } + } else { + true }; - if !matched { + if !is_partial { return false; } - left = right + offset.length as usize; - } + let Some(metadata) = metadata_start else { return true }; - if left < code.len() { - match deployed_code { - BytecodeObject::Bytecode(ref bytes) => bytes[left..] == code[left..], - BytecodeObject::Unlinked(ref bytes) => { - if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) { - bytes == code[left..] + let exact_match = match deployed_code { + BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..], + BytecodeObject::Unlinked(bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) { + bytes == code[metadata..] } else { false } } + }; + + if exact_match { + true + } else { + partial_match = Some((*id, *contract)); + false } - } else { - true - } - }) + }) + .or(partial_match) } /// Finds a contract which has the same contract name or identifier as `id`. If more than one is @@ -271,25 +383,14 @@ impl ContractsByArtifact { &self, id: &str, ) -> Result>> { - let contracts = self - .iter() - .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) - .collect::>(); - - if contracts.len() > 1 { + let mut iter = + self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id); + let first = iter.next(); + if first.is_some() && iter.next().is_some() { eyre::bail!("{id} has more than one implementation."); } - Ok(contracts.first().cloned()) - } - - /// Finds abi for contract which has the same contract name or identifier as `id`. - pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option { - self.iter() - .find(|(artifact, _)| { - artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id - }) - .map(|(_, contract)| contract.abi.clone()) + Ok(first) } /// Finds abi by name or source path @@ -298,7 +399,7 @@ impl ContractsByArtifact { pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> { self.iter() .find(|(artifact, _)| { - artifact.name == name_or_path || artifact.source == PathBuf::from(name_or_path) + artifact.name == name_or_path || artifact.source == Path::new(name_or_path) }) .map(|(_, contract)| (contract.abi.clone(), contract.name.clone())) } @@ -308,7 +409,7 @@ impl ContractsByArtifact { let mut funcs = BTreeMap::new(); let mut events = BTreeMap::new(); let mut errors_abi = JsonAbi::new(); - for (_name, contract) in self.iter() { + for contract in self.values() { for func in contract.abi.functions() { funcs.insert(func.selector(), func.clone()); } @@ -381,7 +482,7 @@ pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 { /// # Safety /// /// `a` must be at least as long as `b`. -unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { +const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`, // however this function is very hot, and has been written to be as primitive as // possible for lower optimization levels. @@ -472,7 +573,7 @@ pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> R path.strip_prefix(project.root()).unwrap().display() ) })?; - return Ok(contract_path) + return Ok(contract_path); } // If ContractInfo.path hasn't been provided we try to find the contract using the name. // This will fail if projects have multiple contracts with the same name. In that case, @@ -500,7 +601,9 @@ pub fn find_matching_contract_artifact( .collect::>(); if possible_targets.is_empty() { - eyre::bail!("Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"); + eyre::bail!( + "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts" + ); } let (target_id, target_artifact) = possible_targets[0].clone(); @@ -511,22 +614,25 @@ pub fn find_matching_contract_artifact( // If all artifact_ids in `possible_targets` have the same name (without ".", indicates // additional compiler profiles), it means that there are multiple contracts in the // same file. - if !target_id.name.contains(".") && - possible_targets.iter().any(|(id, _)| id.name != target_id.name) + if !target_id.name.contains('.') + && possible_targets.iter().any(|(id, _)| id.name != target_id.name) { - eyre::bail!("Multiple contracts found in the same file, please specify the target : or "); + eyre::bail!( + "Multiple contracts found in the same file, please specify the target : or " + ); } // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the // same but `id.path` is different. let artifact = possible_targets .iter() - .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None }) + .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact)) .unwrap_or(target_artifact); Ok(artifact.clone()) } } + #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/src/errors/artifacts.rs b/crates/common/src/errors/artifacts.rs deleted file mode 100644 index 33814c57e4ddc..0000000000000 --- a/crates/common/src/errors/artifacts.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Errors that can occur when working with `solc` artifacts - -/// Error when encountering unlinked code -#[derive(Clone, Debug, thiserror::Error)] -pub enum UnlinkedByteCode { - /// `bytecode` is unlinked - #[error("Contract `{0}` has unlinked bytecode. Please check all libraries settings.")] - Bytecode(String), - /// `deployedBytecode` is unlinked - #[error("Contract `{0}` has unlinked deployed Bytecode. Please check all libraries settings.")] - DeployedBytecode(String), -} diff --git a/crates/common/src/errors/fs.rs b/crates/common/src/errors/fs.rs index 32bd48436cdf3..964761057bf30 100644 --- a/crates/common/src/errors/fs.rs +++ b/crates/common/src/errors/fs.rs @@ -3,41 +3,42 @@ use std::{ path::{Path, PathBuf}, }; -#[expect(unused_imports)] -use std::fs::{self, File}; - /// Various error variants for `fs` operations that serve as an addition to the io::Error which /// does not provide any information about the path. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum FsPathError { - /// Provides additional path context for [`fs::write`]. + /// Provides additional path context for [`std::fs::write`]. #[error("failed to write to {path:?}: {source}")] Write { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`fs::read`]. + /// Provides additional path context for [`std::fs::read`]. #[error("failed to read from {path:?}: {source}")] Read { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`fs::copy`]. + /// Provides additional path context for [`std::fs::copy`]. #[error("failed to copy from {from:?} to {to:?}: {source}")] Copy { source: io::Error, from: PathBuf, to: PathBuf }, - /// Provides additional path context for [`fs::read_link`]. + /// Provides additional path context for [`std::fs::read_link`]. #[error("failed to read from {path:?}: {source}")] ReadLink { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`File::create`]. + /// Provides additional path context for [`std::fs::File::create`]. #[error("failed to create file {path:?}: {source}")] CreateFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`fs::remove_file`]. + /// Provides additional path context for [`std::fs::remove_file`]. #[error("failed to remove file {path:?}: {source}")] RemoveFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`fs::create_dir`]. + /// Provides additional path context for [`std::fs::create_dir`]. #[error("failed to create dir {path:?}: {source}")] CreateDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`fs::remove_dir`]. + /// Provides additional path context for [`std::fs::remove_dir`]. #[error("failed to remove dir {path:?}: {source}")] RemoveDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`File::open`]. + /// Provides additional path context for [`std::fs::File::open`]. #[error("failed to open file {path:?}: {source}")] Open { source: io::Error, path: PathBuf }, + #[error("failed to lock file {path:?}: {source}")] + Lock { source: io::Error, path: PathBuf }, + #[error("failed to unlock file {path:?}: {source}")] + Unlock { source: io::Error, path: PathBuf }, /// Provides additional path context for the file whose contents should be parsed as JSON. #[error("failed to parse json file: {path:?}: {source}")] ReadJson { source: serde_json::Error, path: PathBuf }, @@ -47,66 +48,78 @@ pub enum FsPathError { } impl FsPathError { - /// Returns the complementary error variant for [`fs::write`]. + /// Returns the complementary error variant for [`std::fs::write`]. pub fn write(source: io::Error, path: impl Into) -> Self { Self::Write { source, path: path.into() } } - /// Returns the complementary error variant for [`fs::read`]. + /// Returns the complementary error variant for [`std::fs::read`]. pub fn read(source: io::Error, path: impl Into) -> Self { Self::Read { source, path: path.into() } } - /// Returns the complementary error variant for [`fs::copy`]. + /// Returns the complementary error variant for [`std::fs::copy`]. pub fn copy(source: io::Error, from: impl Into, to: impl Into) -> Self { Self::Copy { source, from: from.into(), to: to.into() } } - /// Returns the complementary error variant for [`fs::read_link`]. + /// Returns the complementary error variant for [`std::fs::read_link`]. pub fn read_link(source: io::Error, path: impl Into) -> Self { Self::ReadLink { source, path: path.into() } } - /// Returns the complementary error variant for [`File::create`]. + /// Returns the complementary error variant for [`std::fs::File::create`]. pub fn create_file(source: io::Error, path: impl Into) -> Self { Self::CreateFile { source, path: path.into() } } - /// Returns the complementary error variant for [`fs::remove_file`]. + /// Returns the complementary error variant for [`std::fs::remove_file`]. pub fn remove_file(source: io::Error, path: impl Into) -> Self { Self::RemoveFile { source, path: path.into() } } - /// Returns the complementary error variant for [`fs::create_dir`]. + /// Returns the complementary error variant for [`std::fs::create_dir`]. pub fn create_dir(source: io::Error, path: impl Into) -> Self { Self::CreateDir { source, path: path.into() } } - /// Returns the complementary error variant for [`fs::remove_dir`]. + /// Returns the complementary error variant for [`std::fs::remove_dir`]. pub fn remove_dir(source: io::Error, path: impl Into) -> Self { Self::RemoveDir { source, path: path.into() } } - /// Returns the complementary error variant for [`File::open`]. + /// Returns the complementary error variant for [`std::fs::File::open`]. pub fn open(source: io::Error, path: impl Into) -> Self { Self::Open { source, path: path.into() } } + + /// Returns the complementary error variant when locking a file. + pub fn lock(source: io::Error, path: impl Into) -> Self { + Self::Lock { source, path: path.into() } + } + + /// Returns the complementary error variant when unlocking a file. + pub fn unlock(source: io::Error, path: impl Into) -> Self { + Self::Unlock { source, path: path.into() } + } } impl AsRef for FsPathError { fn as_ref(&self) -> &Path { match self { - Self::Write { path, .. } | - Self::Read { path, .. } | - Self::ReadLink { path, .. } | - Self::Copy { from: path, .. } | - Self::CreateDir { path, .. } | - Self::RemoveDir { path, .. } | - Self::CreateFile { path, .. } | - Self::RemoveFile { path, .. } | - Self::Open { path, .. } | - Self::ReadJson { path, .. } | - Self::WriteJson { path, .. } => path, + Self::Write { path, .. } + | Self::Read { path, .. } + | Self::ReadLink { path, .. } + | Self::Copy { from: path, .. } + | Self::CreateDir { path, .. } + | Self::RemoveDir { path, .. } + | Self::CreateFile { path, .. } + | Self::RemoveFile { path, .. } + | Self::Open { path, .. } + | Self::Lock { path, .. } + | Self::Unlock { path, .. } + | Self::ReadJson { path, .. } + | Self::WriteJson { path, .. } => path, } } } @@ -114,15 +127,17 @@ impl AsRef for FsPathError { impl From for io::Error { fn from(value: FsPathError) -> Self { match value { - FsPathError::Write { source, .. } | - FsPathError::Read { source, .. } | - FsPathError::ReadLink { source, .. } | - FsPathError::Copy { source, .. } | - FsPathError::CreateDir { source, .. } | - FsPathError::RemoveDir { source, .. } | - FsPathError::CreateFile { source, .. } | - FsPathError::RemoveFile { source, .. } | - FsPathError::Open { source, .. } => source, + FsPathError::Write { source, .. } + | FsPathError::Read { source, .. } + | FsPathError::ReadLink { source, .. } + | FsPathError::Copy { source, .. } + | FsPathError::CreateDir { source, .. } + | FsPathError::RemoveDir { source, .. } + | FsPathError::CreateFile { source, .. } + | FsPathError::RemoveFile { source, .. } + | FsPathError::Open { source, .. } + | FsPathError::Lock { source, .. } + | FsPathError::Unlock { source, .. } => source, FsPathError::ReadJson { source, .. } | FsPathError::WriteJson { source, .. } => { source.into() diff --git a/crates/common/src/errors/mod.rs b/crates/common/src/errors/mod.rs index 5ecd1dcc04cb0..6a1b2bc28bf57 100644 --- a/crates/common/src/errors/mod.rs +++ b/crates/common/src/errors/mod.rs @@ -3,9 +3,6 @@ mod fs; pub use fs::FsPathError; -mod artifacts; -pub use artifacts::*; - mod private { use eyre::Chain; use std::error::Error; @@ -44,6 +41,23 @@ fn all_sources(err: &E) -> Vec { err.chain().map(|cause| cause.to_string().trim().to_string()).collect() } +/// Converts solar errors to an eyre error. +pub fn convert_solar_errors(dcx: &solar::interface::diagnostics::DiagCtxt) -> eyre::Result<()> { + match dcx.emitted_errors() { + Some(Ok(())) => Ok(()), + Some(Err(e)) if !e.is_empty() => eyre::bail!("solar reported errors:\n\n{e}"), + _ if dcx.has_errors().is_err() => { + // Non-buffer emitter: diagnostics already went to stderr; include the count. + let n = dcx.err_count(); + let plural = if n == 1 { "" } else { "s" }; + eyre::bail!( + "solar reported {n} error{plural}; see the diagnostic{plural} printed above" + ) + } + _ => Ok(()), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 3d061759c50cf..cb22287fb68b4 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -1,10 +1,11 @@ //! Contains various `std::fs` wrapper functions that also contain the target path in their errors. use crate::errors::FsPathError; -use serde::{de::DeserializeOwned, Serialize}; +use flate2::{Compression, read::GzDecoder, write::GzEncoder}; +use serde::{Serialize, de::DeserializeOwned}; use std::{ fs::{self, File}, - io::{BufWriter, Write}, + io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, path::{Component, Path, PathBuf}, }; @@ -49,6 +50,40 @@ pub fn read_json_file(path: &Path) -> Result { serde_json::from_str(&s).map_err(|source| FsPathError::ReadJson { source, path: path.into() }) } +/// Reads and decodes the json gzip file, then deserialize it into the provided type. +pub fn read_json_gzip_file(path: &Path) -> Result { + let file = open(path)?; + let reader = BufReader::new(file); + let decoder = GzDecoder::new(reader); + serde_json::from_reader(decoder) + .map_err(|source| FsPathError::ReadJson { source, path: path.into() }) +} + +/// Reads the entire contents of a locked shared file into a string. +pub fn locked_read_to_string(path: impl AsRef) -> Result { + let path = path.as_ref(); + let contents = locked_read(path)?; + String::from_utf8(contents).map_err(|err| FsPathError::read(std::io::Error::other(err), path)) +} + +/// Reads the entire contents of a locked shared file into a bytes vector. +pub fn locked_read(path: impl AsRef) -> Result> { + let path = path.as_ref(); + let mut file = + fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?; + file.lock_shared().map_err(|err| FsPathError::lock(err, path))?; + let contents = read_inner(path, &mut file)?; + file.unlock().map_err(|err| FsPathError::unlock(err, path))?; + Ok(contents) +} + +fn read_inner(path: &Path, file: &mut File) -> Result> { + let file_len = file.metadata().map_err(|err| FsPathError::open(err, path))?.len() as usize; + let mut buffer = Vec::with_capacity(file_len); + file.read_to_end(&mut buffer).map_err(|err| FsPathError::read(err, path))?; + Ok(buffer) +} + /// Writes the object as a JSON object. pub fn write_json_file(path: &Path, obj: &T) -> Result<()> { let file = create_file(path)?; @@ -67,12 +102,73 @@ pub fn write_pretty_json_file(path: &Path, obj: &T) -> Result<()> writer.flush().map_err(|e| FsPathError::write(e, path)) } +/// Writes the object as a gzip compressed file. +pub fn write_json_gzip_file(path: &Path, obj: &T) -> Result<()> { + let file = create_file(path)?; + let writer = BufWriter::new(file); + let mut encoder = GzEncoder::new(writer, Compression::default()); + serde_json::to_writer(&mut encoder, obj) + .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?; + // Ensure we surface any I/O errors on final gzip write and buffer flush. + let mut inner_writer = encoder.finish().map_err(|e| FsPathError::write(e, path))?; + inner_writer.flush().map_err(|e| FsPathError::write(e, path))?; + Ok(()) +} + /// Wrapper for `std::fs::write` pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { let path = path.as_ref(); fs::write(path, contents).map_err(|err| FsPathError::write(err, path)) } +/// Writes all content in an exclusive locked file. +pub fn locked_write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { + let path = path.as_ref(); + let mut file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .map_err(|err| FsPathError::open(err, path))?; + file.lock().map_err(|err| FsPathError::lock(err, path))?; + file.write_all(contents.as_ref()).map_err(|err| FsPathError::write(err, path))?; + file.unlock().map_err(|err| FsPathError::unlock(err, path)) +} + +/// Writes a line in an exclusive locked file. +pub fn locked_write_line(path: impl AsRef, line: &str) -> Result<()> { + let path = path.as_ref(); + if cfg!(windows) { + return locked_write_line_windows(path, line); + } + + let mut file = std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(path) + .map_err(|err| FsPathError::open(err, path))?; + + file.lock().map_err(|err| FsPathError::lock(err, path))?; + writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?; + file.unlock().map_err(|err| FsPathError::unlock(err, path)) +} + +// Locking fails on Windows if the file is opened in append mode. +fn locked_write_line_windows(path: &Path, line: &str) -> Result<()> { + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(path) + .map_err(|err| FsPathError::open(err, path))?; + file.lock().map_err(|err| FsPathError::lock(err, path))?; + + file.seek(SeekFrom::End(0)).map_err(|err| FsPathError::write(err, path))?; + writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?; + + file.unlock().map_err(|err| FsPathError::unlock(err, path)) +} + /// Wrapper for `std::fs::copy` pub fn copy(from: impl AsRef, to: impl AsRef) -> Result { let from = from.as_ref(); @@ -118,7 +214,7 @@ pub fn open(path: impl AsRef) -> Result { /// ref: pub fn normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() { components.next(); PathBuf::from(c.as_os_str()) } else { diff --git a/crates/common/src/io/macros.rs b/crates/common/src/io/macros.rs index 10e7cca4a2e3a..6f86f16a8fd1a 100644 --- a/crates/common/src/io/macros.rs +++ b/crates/common/src/io/macros.rs @@ -9,7 +9,7 @@ /// /// let response: String = prompt!("Would you like to continue? [y/N] ")?; /// if !matches!(response.as_str(), "y" | "Y") { -/// return Ok(()) +/// return Ok(()); /// } /// # Ok::<(), Box>(()) /// ``` @@ -146,7 +146,7 @@ macro_rules! __sh_dispatch { // Ensure that the global shell lock is held for as little time as possible. // Also avoids deadlocks in case of nested calls. (@impl $f:ident $shell:expr, $($args:tt)*) => { - match ::core::format_args!($($args)*) { + match format!($($args)*) { fmt => $crate::Shell::$f($shell, fmt), } }; @@ -178,7 +178,10 @@ mod tests { sh_println!("{:?}", { sh_println!("hi")?; - "nested" + solar::data_structures::fmt::from_fn(|f| { + let _ = sh_println!("even more nested"); + write!(f, "hi 2") + }) })?; Ok(()) diff --git a/crates/common/src/io/shell.rs b/crates/common/src/io/shell.rs index 19b3ae07e7900..f067ebfa95cd8 100644 --- a/crates/common/src/io/shell.rs +++ b/crates/common/src/io/shell.rs @@ -10,11 +10,11 @@ use eyre::Result; use serde::{Deserialize, Serialize}; use std::{ fmt, - io::{prelude::*, IsTerminal}, + io::{IsTerminal, prelude::*}, ops::DerefMut, sync::{ - atomic::{AtomicBool, Ordering}, Mutex, OnceLock, PoisonError, + atomic::{AtomicBool, Ordering}, }, }; @@ -43,43 +43,15 @@ pub fn is_json() -> bool { Shell::get().is_json() } -/// The global shell instance. -static GLOBAL_SHELL: OnceLock> = OnceLock::new(); - -/// Terminal width. -pub enum TtyWidth { - /// Not a terminal, or could not determine size. - NoTty, - /// A known width. - Known(usize), - /// A guess at the width. - Guess(usize), +/// Returns whether the output format is [`OutputFormat::Markdown`]. +pub fn is_markdown() -> bool { + Shell::get().is_markdown() } -impl TtyWidth { - /// Returns the width of the terminal from the environment, if known. - pub fn get() -> Self { - // use stderr - #[cfg(unix)] - let opt = terminal_size::terminal_size_of(std::io::stderr()); - #[cfg(not(unix))] - let opt = terminal_size::terminal_size(); - match opt { - Some((w, _)) => Self::Known(w.0 as usize), - None => Self::NoTty, - } - } - - /// Returns the width used by progress bars for the tty. - pub fn progress_max_width(&self) -> Option { - match *self { - Self::NoTty => None, - Self::Known(width) | Self::Guess(width) => Some(width), - } - } -} +/// The global shell instance. +static GLOBAL_SHELL: OnceLock> = OnceLock::new(); -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] /// The requested output mode. pub enum OutputMode { /// Default output @@ -91,40 +63,43 @@ pub enum OutputMode { impl OutputMode { /// Returns true if the output mode is `Normal`. - #[inline] pub fn is_normal(self) -> bool { self == Self::Normal } /// Returns true if the output mode is `Quiet`. - #[inline] pub fn is_quiet(self) -> bool { self == Self::Quiet } } /// The requested output format. -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum OutputFormat { /// Plain text output. #[default] Text, /// JSON output. Json, + /// Plain text with markdown tables. + Markdown, } impl OutputFormat { /// Returns true if the output format is `Text`. - #[inline] pub fn is_text(self) -> bool { self == Self::Text } /// Returns true if the output format is `Json`. - #[inline] pub fn is_json(self) -> bool { self == Self::Json } + + /// Returns true if the output format is `Markdown`. + pub fn is_markdown(self) -> bool { + self == Self::Markdown + } } /// The verbosity level. @@ -178,7 +153,7 @@ enum ShellOut { } /// Whether messages should use color output. -#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize, ValueEnum)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, ValueEnum)] pub enum ColorChoice { /// Intelligently guess whether to use color output (default). #[default] @@ -190,7 +165,6 @@ pub enum ColorChoice { } impl Default for Shell { - #[inline] fn default() -> Self { Self::new() } @@ -199,7 +173,6 @@ impl Default for Shell { impl Shell { /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose /// output. - #[inline] pub fn new() -> Self { Self::new_with( OutputFormat::Text, @@ -210,7 +183,6 @@ impl Shell { } /// Creates a new shell with the given color choice and verbosity. - #[inline] pub fn new_with( format: OutputFormat, mode: OutputMode, @@ -232,8 +204,7 @@ impl Shell { } /// Creates a shell that ignores all output. - #[inline] - pub fn empty() -> Self { + pub const fn empty() -> Self { Self { output: ShellOut::Empty(std::io::empty()), output_format: OutputFormat::Text, @@ -263,7 +234,6 @@ impl Shell { } /// Sets whether the next print should clear the current line and returns the previous value. - #[inline] pub fn set_needs_clear(&self, needs_clear: bool) -> bool { self.needs_clear.swap(needs_clear, Ordering::Relaxed) } @@ -273,52 +243,43 @@ impl Shell { self.output_format.is_json() } + /// Returns `true` if the output format is Markdown. + pub fn is_markdown(&self) -> bool { + self.output_format.is_markdown() + } + /// Returns `true` if the verbosity level is `Quiet`. pub fn is_quiet(&self) -> bool { self.output_mode.is_quiet() } /// Returns `true` if the `needs_clear` flag is set. - #[inline] pub fn needs_clear(&self) -> bool { self.needs_clear.load(Ordering::Relaxed) } /// Returns `true` if the `needs_clear` flag is unset. - #[inline] pub fn is_cleared(&self) -> bool { !self.needs_clear() } - /// Returns the width of the terminal in spaces, if any. - #[inline] - pub fn err_width(&self) -> TtyWidth { - match self.output { - ShellOut::Stream { stderr_tty: true, .. } => TtyWidth::get(), - _ => TtyWidth::NoTty, - } - } - /// Gets the output format of the shell. - #[inline] - pub fn output_format(&self) -> OutputFormat { + pub const fn output_format(&self) -> OutputFormat { self.output_format } /// Gets the output mode of the shell. - #[inline] - pub fn output_mode(&self) -> OutputMode { + pub const fn output_mode(&self) -> OutputMode { self.output_mode } /// Gets the verbosity of the shell when [`OutputMode::Normal`] is set. - #[inline] - pub fn verbosity(&self) -> Verbosity { + pub const fn verbosity(&self) -> Verbosity { self.verbosity } /// Sets the verbosity level. - pub fn set_verbosity(&mut self, verbosity: Verbosity) { + pub const fn set_verbosity(&mut self, verbosity: Verbosity) { self.verbosity = verbosity; } @@ -326,8 +287,7 @@ impl Shell { /// /// If we are not using a color stream, this will always return `Never`, even if the color /// choice has been set to something else. - #[inline] - pub fn color_choice(&self) -> ColorChoice { + pub const fn color_choice(&self) -> ColorChoice { match self.output { ShellOut::Stream { color_choice, .. } => color_choice, ShellOut::Empty(_) => ColorChoice::Never, @@ -335,8 +295,7 @@ impl Shell { } /// Returns `true` if stderr is a tty. - #[inline] - pub fn is_err_tty(&self) -> bool { + pub const fn is_err_tty(&self) -> bool { match self.output { ShellOut::Stream { stderr_tty, .. } => stderr_tty, ShellOut::Empty(_) => false, @@ -344,7 +303,6 @@ impl Shell { } /// Whether `stderr` supports color. - #[inline] pub fn err_supports_color(&self) -> bool { match &self.output { ShellOut::Stream { stderr, .. } => supports_color(stderr.current_choice()), @@ -353,7 +311,6 @@ impl Shell { } /// Whether `stdout` supports color. - #[inline] pub fn out_supports_color(&self) -> bool { match &self.output { ShellOut::Stream { stdout, .. } => supports_color(stdout.current_choice()), @@ -489,7 +446,6 @@ impl ShellOut { } /// Gets stdout as a [`io::Write`](Write) trait object. - #[inline] fn stdout(&mut self) -> &mut dyn Write { match self { Self::Stream { stdout, .. } => stdout, @@ -498,7 +454,6 @@ impl ShellOut { } /// Gets stderr as a [`io::Write`](Write) trait object. - #[inline] fn stderr(&mut self) -> &mut dyn Write { match self { Self::Stream { stderr, .. } => stderr, @@ -534,8 +489,7 @@ impl ShellOut { impl ColorChoice { /// Converts our color choice to [`anstream`]'s version. - #[inline] - fn to_anstream_color_choice(self) -> anstream::ColorChoice { + const fn to_anstream_color_choice(self) -> anstream::ColorChoice { match self { Self::Always => anstream::ColorChoice::Always, Self::Never => anstream::ColorChoice::Never, @@ -544,12 +498,11 @@ impl ColorChoice { } } -#[inline] -fn supports_color(choice: anstream::ColorChoice) -> bool { +const fn supports_color(choice: anstream::ColorChoice) -> bool { match choice { - anstream::ColorChoice::Always | - anstream::ColorChoice::AlwaysAnsi | - anstream::ColorChoice::Auto => true, + anstream::ColorChoice::Always + | anstream::ColorChoice::AlwaysAnsi + | anstream::ColorChoice::Auto => true, anstream::ColorChoice::Never => false, } } diff --git a/crates/common/src/io/stdin.rs b/crates/common/src/io/stdin.rs index 17b40a2cff1fe..f9da53f305f28 100644 --- a/crates/common/src/io/stdin.rs +++ b/crates/common/src/io/stdin.rs @@ -20,7 +20,6 @@ where } /// Shortcut for `(unwrap(a), unwrap(b))`. -#[inline] pub fn unwrap2(a: Option, b: Option) -> Result<(A, B)> where A: FromStr, @@ -50,7 +49,6 @@ where } /// Short-hand for `unwrap(value, true)`. -#[inline] pub fn unwrap_line(value: Option) -> Result where T: FromStr, @@ -62,7 +60,6 @@ where /// Reads bytes from [`stdin`][io::stdin] into a String. /// /// If `read_line` is true, stop at the first newline (the `0xA` byte). -#[inline] pub fn parse(read_line: bool) -> Result where T: FromStr, @@ -72,7 +69,6 @@ where } /// Short-hand for `parse(true)`. -#[inline] pub fn parse_line() -> Result where T: FromStr, @@ -84,7 +80,6 @@ where /// Reads bytes from [`stdin`][io::stdin] into a String. /// /// If `read_line` is true, stop at the first newline (the `0xA` byte). -#[inline] pub fn read(read_line: bool) -> Result { let bytes = read_bytes(read_line)?; @@ -106,8 +101,11 @@ pub fn read_bytes(read_line: bool) -> Result> { let mut buf = String::new(); stdin.read_line(&mut buf)?; // remove the trailing newline - if let Some(b'\n') = buf.as_bytes().last() { + if matches!(buf.as_bytes().last(), Some(b'\n')) { buf.pop(); + if matches!(buf.as_bytes().last(), Some(b'\r')) { + buf.pop(); + } } Ok(buf.into_bytes()) } else { diff --git a/crates/common/src/iter.rs b/crates/common/src/iter.rs new file mode 100644 index 0000000000000..09d16c5f30ae4 --- /dev/null +++ b/crates/common/src/iter.rs @@ -0,0 +1,31 @@ +use std::iter::Peekable; + +pub struct Delimited { + is_first: bool, + iter: Peekable, +} + +pub trait IterDelimited: Iterator + Sized { + fn delimited(self) -> Delimited { + Delimited { is_first: true, iter: self.peekable() } + } +} + +impl IterDelimited for I {} + +pub struct IteratorPosition { + pub is_first: bool, + pub is_last: bool, +} + +impl Iterator for Delimited { + type Item = (IteratorPosition, I::Item); + + fn next(&mut self) -> Option { + let item = self.iter.next()?; + let position = + IteratorPosition { is_first: self.is_first, is_last: self.iter.peek().is_none() }; + self.is_first = false; + Some((position, item)) + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index a0912c38caf8e..be6bb16b8ea0a 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -3,7 +3,7 @@ //! Common utilities for building and using foundry's tools. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[expect(unused_extern_crates)] // Used by `ConsoleFmt`. extern crate self as foundry_common; @@ -18,27 +18,31 @@ pub use foundry_common_fmt as fmt; pub mod abi; pub mod calc; +pub mod comments; pub mod compile; pub mod constants; pub mod contracts; pub mod errors; -pub mod evm; pub mod fs; +pub mod iter; +pub mod mapping_slots; mod preprocessor; pub mod provider; -pub mod reports; pub mod retry; pub mod selectors; pub mod serde_helpers; +pub mod slot_identifier; +pub mod tempo; pub mod term; pub mod traits; pub mod transactions; mod utils; pub mod version; +pub use compile::Analysis; pub use constants::*; pub use contracts::*; -pub use io::{shell, stdin, Shell}; +pub use io::{Shell, shell, stdin}; pub use traits::*; pub use transactions::*; pub use utils::*; diff --git a/crates/common/src/mapping_slots.rs b/crates/common/src/mapping_slots.rs new file mode 100644 index 0000000000000..fccf2f1bff927 --- /dev/null +++ b/crates/common/src/mapping_slots.rs @@ -0,0 +1,71 @@ +use alloy_primitives::{ + B256, U256, keccak256, + map::{AddressHashMap, B256HashMap}, +}; +use revm::{ + bytecode::opcode, + interpreter::{Interpreter, interpreter_types::Jumps}, +}; + +/// Recorded mapping slots. +#[derive(Clone, Debug, Default)] +pub struct MappingSlots { + /// Holds mapping parent (slots => slots) + pub parent_slots: B256HashMap, + + /// Holds mapping key (slots => key) + pub keys: B256HashMap, + + /// Holds mapping child (slots => slots[]) + pub children: B256HashMap>, + + /// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record + /// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in + /// `data_low`, higher 256 bits in `data_high`. + /// This is needed for mapping_key detect if the slot is for some mapping and record that. + pub seen_sha3: B256HashMap<(B256, B256)>, +} + +impl MappingSlots { + /// Tries to insert a mapping slot. Returns true if it was inserted. + pub fn insert(&mut self, slot: B256) -> bool { + match self.seen_sha3.get(&slot).copied() { + Some((key, parent)) => { + if self.keys.insert(slot, key).is_some() { + return false; + } + self.parent_slots.insert(slot, parent); + self.children.entry(parent).or_default().push(slot); + self.insert(parent); + true + } + None => false, + } + } +} + +/// Function to be used in Inspector::step to record mapping slots and keys +#[cold] +pub fn step(mapping_slots: &mut AddressHashMap, interpreter: &Interpreter) { + #[allow(clippy::collapsible_match)] + match interpreter.bytecode.opcode() { + opcode::KECCAK256 if interpreter.stack.peek(1) == Ok(U256::from(0x40)) => { + let address = interpreter.input.target_address; + let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); + let data = interpreter.memory.slice_len(offset, 0x40); + let low = B256::from_slice(&data[..0x20]); + let high = B256::from_slice(&data[0x20..]); + let result = keccak256(&*data); + + mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); + } + opcode::SSTORE => { + if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address) + && let Ok(slot) = interpreter.stack.peek(0) + { + mapping_slots.insert(slot.into()); + } + } + _ => {} + } +} diff --git a/crates/common/src/preprocessor/data.rs b/crates/common/src/preprocessor/data.rs index 78fe1098fcc56..d76c025a9f63a 100644 --- a/crates/common/src/preprocessor/data.rs +++ b/crates/common/src/preprocessor/data.rs @@ -1,9 +1,9 @@ use super::span_to_range; use foundry_compilers::artifacts::{Source, Sources}; use path_slash::PathExt; -use solar_parse::interface::{Session, SourceMap}; -use solar_sema::{ - hir::{Contract, ContractId, Hir}, +use solar::sema::{ + Gcx, + hir::{Contract, ContractId}, interface::source_map::FileName, }; use std::{ @@ -17,21 +17,19 @@ pub type PreprocessorData = BTreeMap; /// Collects preprocessor data from referenced contracts. pub(crate) fn collect_preprocessor_data( - sess: &Session, - hir: &Hir<'_>, + gcx: Gcx<'_>, referenced_contracts: &HashSet, ) -> PreprocessorData { let mut data = PreprocessorData::default(); for contract_id in referenced_contracts { - let contract = hir.contract(*contract_id); - let source = hir.source(contract.source); + let contract = gcx.hir.contract(*contract_id); + let source = gcx.hir.source(contract.source); let FileName::Real(path) = &source.file.name else { continue; }; - let contract_data = - ContractData::new(hir, *contract_id, contract, path, source, sess.source_map()); + let contract_data = ContractData::new(gcx, *contract_id, contract, path, source); data.insert(*contract_id, contract_data); } data @@ -77,19 +75,18 @@ pub(crate) struct ContractData { impl ContractData { fn new( - hir: &Hir<'_>, + gcx: Gcx<'_>, contract_id: ContractId, contract: &Contract<'_>, path: &Path, - source: &solar_sema::hir::Source<'_>, - source_map: &SourceMap, + source: &solar::sema::hir::Source<'_>, ) -> Self { let artifact = format!("{}:{}", path.to_slash_lossy(), contract.name); // Process data for contracts with constructor and parameters. let constructor_data = contract .ctor - .map(|ctor_id| hir.function(ctor_id)) + .map(|ctor_id| gcx.hir.function(ctor_id)) .filter(|ctor| !ctor.parameters.is_empty()) .map(|ctor| { let mut abi_encode_args = vec![]; @@ -97,9 +94,10 @@ impl ContractData { let mut arg_index = 0; for param_id in ctor.parameters { let src = source.file.src.as_str(); - let loc = span_to_range(source_map, hir.variable(*param_id).span); + let loc = + span_to_range(gcx.sess.source_map(), gcx.hir.variable(*param_id).span); let mut new_src = src[loc].replace(" memory ", " ").replace(" calldata ", " "); - if let Some(ident) = hir.variable(*param_id).name { + if let Some(ident) = gcx.hir.variable(*param_id).name { abi_encode_args.push(format!("args.{}", ident.name)); } else { // Generate an unique name if constructor arg doesn't have one. @@ -178,6 +176,7 @@ impl ContractData { let helper = format!( r#" +// SPDX-License-Identifier: MIT pragma solidity >=0.4.0; import "{path}"; diff --git a/crates/common/src/preprocessor/deps.rs b/crates/common/src/preprocessor/deps.rs index 1348a1928e60e..804801d98d8ba 100644 --- a/crates/common/src/preprocessor/deps.rs +++ b/crates/common/src/preprocessor/deps.rs @@ -4,10 +4,10 @@ use super::{ }; use foundry_compilers::Updates; use itertools::Itertools; -use solar_parse::interface::Session; -use solar_sema::{ - hir::{CallArgs, ContractId, Expr, ExprKind, Hir, NamedArg, Stmt, StmtKind, TypeKind, Visit}, - interface::{data_structures::Never, source_map::FileName, SourceMap}, +use solar::sema::{ + Gcx, Hir, + hir::{CallArgs, ContractId, Expr, ExprKind, NamedArg, Stmt, StmtKind, TypeKind, Visit}, + interface::{SourceMap, data_structures::Never, source_map::FileName}, }; use std::{ collections::{BTreeMap, BTreeSet, HashSet}, @@ -25,8 +25,7 @@ pub(crate) struct PreprocessorDependencies { impl PreprocessorDependencies { pub fn new( - sess: &Session, - hir: &Hir<'_>, + gcx: Gcx<'_>, paths: &[PathBuf], src_dir: &Path, root_dir: &Path, @@ -34,56 +33,70 @@ impl PreprocessorDependencies { ) -> Self { let mut preprocessed_contracts = BTreeMap::new(); let mut referenced_contracts = HashSet::new(); - for contract_id in hir.contract_ids() { - let contract = hir.contract(contract_id); - let source = hir.source(contract.source); + let mut current_mocks = HashSet::new(); + + // Helper closure for iterating candidate contracts to preprocess (tests and scripts). + let candidate_contracts = || { + gcx.hir.contract_ids().filter_map(|id| { + let contract = gcx.hir.contract(id); + let source = gcx.hir.source(contract.source); + let FileName::Real(path) = &source.file.name else { + return None; + }; - let FileName::Real(path) = &source.file.name else { - continue; - }; + if !paths.contains(path) { + trace!("{} is not test or script", path.display()); + return None; + } - // Collect dependencies only for tests and scripts. - if !paths.contains(path) { - let path = path.display(); - trace!("{path} is not test or script"); - continue; - } + Some((id, contract, source, path)) + }) + }; - // Do not collect dependencies for mock contracts. Walk through base contracts and - // check if they're from src dir. - if contract.linearized_bases.iter().any(|base_contract_id| { - let base_contract = hir.contract(*base_contract_id); - let FileName::Real(path) = &hir.source(base_contract.source).file.name else { - return false; - }; - path.starts_with(src_dir) + // Collect current mocks. + for (_, contract, _, path) in candidate_contracts() { + if contract.linearized_bases.iter().any(|base_id| { + let base = gcx.hir.contract(*base_id); + matches!( + &gcx.hir.source(base.source).file.name, + FileName::Real(base_path) if base_path.starts_with(src_dir) + ) }) { - // Record mock contracts to be evicted from preprocessed cache. - mocks.insert(root_dir.join(path)); - let path = path.display(); - trace!("found mock contract {path}"); + let mock_path = root_dir.join(path); + trace!("found mock contract {}", mock_path.display()); + current_mocks.insert(mock_path); + } + } + + // Collect dependencies for non-mock test/script contracts. + for (contract_id, contract, source, path) in candidate_contracts() { + let full_path = root_dir.join(path); + + if current_mocks.contains(&full_path) { + trace!("{} is a mock, skipping", path.display()); continue; - } else { - // Make sure current contract is not in list of mocks (could happen when a contract - // which used to be a mock is refactored to a non-mock implementation). - mocks.remove(&root_dir.join(path)); } - let mut deps_collector = BytecodeDependencyCollector::new( - sess.source_map(), - hir, - source.file.src.as_str(), - src_dir, - ); + // Make sure current contract is not in list of mocks (could happen when a contract + // which used to be a mock is refactored to a non-mock implementation). + mocks.remove(&full_path); + + let mut deps_collector = + BytecodeDependencyCollector::new(gcx, source.file.src.as_str(), src_dir); // Analyze current contract. let _ = deps_collector.walk_contract(contract); // Ignore empty test contracts declared in source files with other contracts. if !deps_collector.dependencies.is_empty() { preprocessed_contracts.insert(contract_id, deps_collector.dependencies); } + // Record collected referenced contract ids. referenced_contracts.extend(deps_collector.referenced_contracts); } + + // Add current mocks. + mocks.extend(current_mocks); + Self { preprocessed_contracts, referenced_contracts } } } @@ -105,8 +118,8 @@ enum BytecodeDependencyKind { value: Option, /// `salt` (if any) used when creating contract. salt: Option, - /// Whether it's a try contract creation statement. - try_stmt: bool, + /// Whether it's a try contract creation statement, with custom return. + try_stmt: Option, }, } @@ -122,44 +135,30 @@ pub(crate) struct BytecodeDependency { } /// Walks over contract HIR and collects [`BytecodeDependency`]s and referenced contracts. -struct BytecodeDependencyCollector<'hir> { +struct BytecodeDependencyCollector<'gcx, 'src> { /// Source map, used for determining contract item locations. - source_map: &'hir SourceMap, - /// Parsed HIR. - hir: &'hir Hir<'hir>, + gcx: Gcx<'gcx>, /// Source content of current contract. - src: &'hir str, + src: &'src str, /// Project source dir, used to determine if referenced contract is a source contract. - src_dir: &'hir Path, + src_dir: &'src Path, /// Dependencies collected for current contract. dependencies: Vec, /// Unique HIR ids of contracts referenced from current contract. referenced_contracts: HashSet, } -impl<'hir> BytecodeDependencyCollector<'hir> { - fn new( - source_map: &'hir SourceMap, - hir: &'hir Hir<'hir>, - src: &'hir str, - src_dir: &'hir Path, - ) -> Self { - Self { - source_map, - hir, - src, - src_dir, - dependencies: vec![], - referenced_contracts: HashSet::default(), - } +impl<'gcx, 'src> BytecodeDependencyCollector<'gcx, 'src> { + fn new(gcx: Gcx<'gcx>, src: &'src str, src_dir: &'src Path) -> Self { + Self { gcx, src, src_dir, dependencies: vec![], referenced_contracts: HashSet::default() } } /// Collects reference identified as bytecode dependency of analyzed contract. /// Discards any reference that is not in project src directory (e.g. external /// libraries or mock contracts that extend source contracts). fn collect_dependency(&mut self, dependency: BytecodeDependency) { - let contract = self.hir.contract(dependency.referenced_contract); - let source = self.hir.source(contract.source); + let contract = self.gcx.hir.contract(dependency.referenced_contract); + let source = self.gcx.hir.source(contract.source); let FileName::Real(path) = &source.file.name else { return; }; @@ -175,41 +174,39 @@ impl<'hir> BytecodeDependencyCollector<'hir> { } } -impl<'hir> Visit<'hir> for BytecodeDependencyCollector<'hir> { +impl<'gcx> Visit<'gcx> for BytecodeDependencyCollector<'gcx, '_> { type BreakValue = Never; - fn hir(&self) -> &'hir Hir<'hir> { - self.hir + fn hir(&self) -> &'gcx Hir<'gcx> { + &self.gcx.hir } - fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow { + fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow { + #[allow(clippy::collapsible_match)] match &expr.kind { ExprKind::Call(call_expr, call_args, named_args) => { if let Some(dependency) = handle_call_expr( self.src, - self.source_map, + self.gcx.sess.source_map(), expr, call_expr, call_args, named_args, - false, ) { self.collect_dependency(dependency); } } ExprKind::Member(member_expr, ident) => { - if let ExprKind::TypeCall(ty) = &member_expr.kind { - if let TypeKind::Custom(contract_id) = &ty.kind { - if ident.name.as_str() == "creationCode" { - if let Some(contract_id) = contract_id.as_contract() { - self.collect_dependency(BytecodeDependency { - kind: BytecodeDependencyKind::CreationCode, - loc: span_to_range(self.source_map, expr.span), - referenced_contract: contract_id, - }); - } - } - } + if let ExprKind::TypeCall(ty) = &member_expr.kind + && let TypeKind::Custom(contract_id) = &ty.kind + && ident.name.as_str() == "creationCode" + && let Some(contract_id) = contract_id.as_contract() + { + self.collect_dependency(BytecodeDependency { + kind: BytecodeDependencyKind::CreationCode, + loc: span_to_range(self.gcx.sess.source_map(), expr.span), + referenced_contract: contract_id, + }); } } _ => {} @@ -217,30 +214,42 @@ impl<'hir> Visit<'hir> for BytecodeDependencyCollector<'hir> { self.walk_expr(expr) } - fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) -> ControlFlow { - if let StmtKind::Try(stmt_try) = stmt.kind { - if let ExprKind::Call(call_expr, call_args, named_args) = &stmt_try.expr.kind { - if let Some(dependency) = handle_call_expr( - self.src, - self.source_map, - &stmt_try.expr, - call_expr, - call_args, - named_args, - true, - ) { - self.collect_dependency(dependency); - for clause in stmt_try.clauses { - for &var in clause.args { - self.visit_nested_var(var)?; - } - for stmt in clause.block { - self.visit_stmt(stmt)?; - } - } - return ControlFlow::Continue(()); + fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow { + if let StmtKind::Try(stmt_try) = stmt.kind + && let ExprKind::Call(call_expr, call_args, named_args) = &stmt_try.expr.kind + && let Some(mut dependency) = handle_call_expr( + self.src, + self.gcx.sess.source_map(), + &stmt_try.expr, + call_expr, + call_args, + named_args, + ) + { + let has_custom_return = if let Some(clause) = stmt_try.clauses.first() + && clause.args.len() == 1 + && let Some(ret_var) = clause.args.first() + && let TypeKind::Custom(_) = self.hir().variable(*ret_var).ty.kind + { + true + } else { + false + }; + + if let BytecodeDependencyKind::New { try_stmt, .. } = &mut dependency.kind { + *try_stmt = Some(has_custom_return); + } + self.collect_dependency(dependency); + + for clause in stmt_try.clauses { + for &var in clause.args { + self.visit_nested_var(var)?; + } + for stmt in clause.block.stmts { + self.visit_stmt(stmt)?; } } + return ControlFlow::Continue(()); } self.walk_stmt(stmt) } @@ -254,38 +263,36 @@ fn handle_call_expr( call_expr: &Expr<'_>, call_args: &CallArgs<'_>, named_args: &Option<&[NamedArg<'_>]>, - try_stmt: bool, ) -> Option { - if let ExprKind::New(ty_new) = &call_expr.kind { - if let TypeKind::Custom(item_id) = ty_new.kind { - if let Some(contract_id) = item_id.as_contract() { - let name_loc = span_to_range(source_map, ty_new.span); - let name = &src[name_loc]; - - // Calculate offset to remove named args, e.g. for an expression like - // `new Counter {value: 333} ( address(this))` - // the offset will be used to replace `{value: 333} ( ` with `(` - let call_args_offset = if named_args.is_some() && !call_args.is_empty() { - (call_args.span.lo() - ty_new.span.hi()).to_usize() - } else { - 0 - }; + if let ExprKind::New(ty_new) = &call_expr.kind + && let TypeKind::Custom(item_id) = ty_new.kind + && let Some(contract_id) = item_id.as_contract() + { + let name_loc = span_to_range(source_map, ty_new.span); + let name = &src[name_loc]; + + // Calculate offset to remove named args, e.g. for an expression like + // `new Counter {value: 333} ( address(this))` + // the offset will be used to replace `{value: 333} ( ` with `(` + let call_args_offset = if named_args.is_some() && !call_args.is_empty() { + (call_args.span.lo() - ty_new.span.hi()).to_usize() + } else { + 0 + }; - let args_len = parent_expr.span.hi() - ty_new.span.hi(); - return Some(BytecodeDependency { - kind: BytecodeDependencyKind::New { - name: name.to_string(), - args_length: args_len.to_usize(), - call_args_offset, - value: named_arg(src, named_args, "value", source_map), - salt: named_arg(src, named_args, "salt", source_map), - try_stmt, - }, - loc: span_to_range(source_map, call_expr.span), - referenced_contract: contract_id, - }) - } - } + let args_len = parent_expr.span.hi() - ty_new.span.hi(); + return Some(BytecodeDependency { + kind: BytecodeDependencyKind::New { + name: name.to_string(), + args_length: args_len.to_usize(), + call_args_offset, + value: named_arg(src, named_args, "value", source_map), + salt: named_arg(src, named_args, "salt", source_map), + try_stmt: None, + }, + loc: span_to_range(source_map, call_expr.span), + referenced_contract: contract_id, + }); } None } @@ -307,15 +314,26 @@ fn named_arg( /// Goes over all test/script files and replaces bytecode dependencies with cheatcode /// invocations. +/// +/// Special handling of try/catch statements with custom returns, where the try statement becomes +/// ```solidity +/// try this.addressToCounter() returns (Counter c) +/// ``` +/// and helper to cast address is appended +/// ```solidity +/// function addressToCounter(address addr) returns (Counter) { +/// return Counter(addr); +/// } +/// ``` pub(crate) fn remove_bytecode_dependencies( - hir: &Hir<'_>, + gcx: Gcx<'_>, deps: &PreprocessorDependencies, data: &PreprocessorData, ) -> Updates { let mut updates = Updates::default(); for (contract_id, deps) in &deps.preprocessed_contracts { - let contract = hir.contract(*contract_id); - let source = hir.source(contract.source); + let contract = gcx.hir.contract(*contract_id); + let source = gcx.hir.source(contract.source); let FileName::Real(path) = &source.file.name else { continue; }; @@ -326,6 +344,7 @@ pub(crate) fn remove_bytecode_dependencies( let vm_interface_name = format!("VmContractHelper{}", contract_id.get()); // `address(uint160(uint256(keccak256("hevm cheat code"))))` let vm = format!("{vm_interface_name}(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)"); + let mut try_catch_helpers: HashSet<&str> = HashSet::default(); for dep in deps { let Some(ContractData { artifact, constructor_data, .. }) = @@ -351,8 +370,14 @@ pub(crate) fn remove_bytecode_dependencies( salt, try_stmt, } => { - let (mut update, closing_seq) = if *try_stmt { - (String::new(), "})") + let (mut update, closing_seq) = if let Some(has_ret) = try_stmt { + if *has_ret { + // try this.addressToCounter1() returns (Counter c) + try_catch_helpers.insert(name); + (format!("this.addressTo{name}{id}(", id = contract_id.get()), "}))") + } else { + (String::new(), "})") + } } else { (format!("{name}(payable("), "})))") }; @@ -392,6 +417,30 @@ pub(crate) fn remove_bytecode_dependencies( } }; } + + // Add try catch statements after last function of the test contract. + if !try_catch_helpers.is_empty() + && let Some(last_fn_id) = contract.functions().last() + { + let last_fn_range = + span_to_range(gcx.sess.source_map(), gcx.hir.function(last_fn_id).span); + let to_address_fns = try_catch_helpers + .iter() + .map(|ty| { + format!( + r#" + function addressTo{ty}{id}(address addr) public pure returns ({ty}) {{ + return {ty}(addr); + }} + "#, + id = contract_id.get() + ) + }) + .collect::(); + + updates.insert((last_fn_range.end, last_fn_range.end, to_address_fns)); + } + let helper_imports = used_helpers.into_iter().map(|id| { let id = id.get(); format!( @@ -414,7 +463,7 @@ interface {vm_interface_name} {{ function deployCode(string memory _artifact, uint256 _value, bytes32 _salt) external returns (address); function deployCode(string memory _artifact, bytes memory _args, uint256 _value) external returns (address); function deployCode(string memory _artifact, bytes memory _args, uint256 _value, bytes32 _salt) external returns (address); - function getCode(string memory _artifact) external returns (bytes memory); + function getCode(string memory _artifact) external view returns (bytes memory); }}"# ), )); diff --git a/crates/common/src/preprocessor/mod.rs b/crates/common/src/preprocessor/mod.rs index ff8b0e66feae3..7499776cf2c72 100644 --- a/crates/common/src/preprocessor/mod.rs +++ b/crates/common/src/preprocessor/mod.rs @@ -1,35 +1,42 @@ +use crate::errors::convert_solar_errors; use foundry_compilers::{ - apply_updates, + Compiler, ProjectPathsConfig, SourceParser, apply_updates, artifacts::SolcLanguage, error::Result, multi::{MultiCompiler, MultiCompilerInput, MultiCompilerLanguage}, project::Preprocessor, solc::{SolcCompiler, SolcVersionedInput}, - Compiler, Language, ProjectPathsConfig, }; -use solar_parse::{ - ast::Span, - interface::{Session, SourceMap}, +use solar::parse::{ast::Span, interface::SourceMap}; +use std::{ + collections::HashSet, + ops::{ControlFlow, Range}, + path::PathBuf, }; -use solar_sema::{thread_local::ThreadLocal, ParsingContext}; -use std::{collections::HashSet, ops::Range, path::PathBuf}; mod data; use data::{collect_preprocessor_data, create_deploy_helpers}; mod deps; -use deps::{remove_bytecode_dependencies, PreprocessorDependencies}; +use deps::{PreprocessorDependencies, remove_bytecode_dependencies}; /// Returns the range of the given span in the source map. #[track_caller] fn span_to_range(source_map: &SourceMap, span: Span) -> Range { - source_map.span_to_source(span).unwrap().1 + source_map.span_to_range(span).unwrap() } +/// Preprocessor that replaces static bytecode linking in tests and scripts (`new Contract`) with +/// dynamic linkage through (`Vm.create*`). +/// +/// This allows for more efficient caching when iterating on tests. +/// +/// See . #[derive(Debug)] -pub struct TestOptimizerPreprocessor; +pub struct DynamicTestLinkingPreprocessor; -impl Preprocessor for TestOptimizerPreprocessor { +impl Preprocessor for DynamicTestLinkingPreprocessor { + #[instrument(name = "DynamicTestLinkingPreprocessor::preprocess", skip_all)] fn preprocess( &self, _solc: &SolcCompiler, @@ -39,14 +46,15 @@ impl Preprocessor for TestOptimizerPreprocessor { ) -> Result<()> { // Skip if we are not preprocessing any tests or scripts. Avoids unnecessary AST parsing. if !input.input.sources.iter().any(|(path, _)| paths.is_test_or_script(path)) { - trace!("no tests or sources to preprocess"); + trace!("no tests or scripts to preprocess"); return Ok(()); } - let sess = solar_session_from_solc(input); - let _ = sess.enter_parallel(|| -> solar_parse::interface::Result { - // Set up the parsing context with the project paths. - let mut parsing_context = solar_pcx_from_solc_no_sources(&sess, input, paths); + let mut compiler = + foundry_compilers::resolver::parse::SolParser::new(paths.with_language_ref()) + .into_compiler(); + let _ = compiler.enter_mut(|compiler| -> solar::interface::Result { + let mut pcx = compiler.parse(); // Add the sources into the context. // Include all sources in the source map so as to not re-load them from disk, but only @@ -54,52 +62,51 @@ impl Preprocessor for TestOptimizerPreprocessor { let mut preprocessed_paths = vec![]; let sources = &mut input.input.sources; for (path, source) in sources.iter() { - if let Ok(src_file) = - sess.source_map().new_source_file(path.clone(), source.content.as_str()) + if let Ok(src_file) = compiler + .sess() + .source_map() + .new_source_file(path.clone(), source.content.as_str()) + && paths.is_test_or_script(path) { - if paths.is_test_or_script(path) { - parsing_context.add_file(src_file); - preprocessed_paths.push(path.clone()); - } + pcx.add_file(src_file); + preprocessed_paths.push(path.clone()); } } // Parse and preprocess. - let hir_arena = ThreadLocal::new(); - if let Some(gcx) = parsing_context.parse_and_lower(&hir_arena)? { - let hir = &gcx.get().hir; - // Collect tests and scripts dependencies and identify mock contracts. - let deps = PreprocessorDependencies::new( - &sess, - hir, - &preprocessed_paths, - &paths.paths_relative().sources, - &paths.root, - mocks, - ); - // Collect data of source contracts referenced in tests and scripts. - let data = collect_preprocessor_data(&sess, hir, &deps.referenced_contracts); - - // Extend existing sources with preprocessor deploy helper sources. - sources.extend(create_deploy_helpers(&data)); - - // Generate and apply preprocessor source updates. - apply_updates(sources, remove_bytecode_dependencies(hir, &deps, &data)); - } + pcx.parse(); + let ControlFlow::Continue(()) = compiler.lower_asts()? else { return Ok(()) }; + let gcx = compiler.gcx(); + // Collect tests and scripts dependencies and identify mock contracts. + let deps = PreprocessorDependencies::new( + gcx, + &preprocessed_paths, + &paths.paths_relative().sources, + &paths.root, + mocks, + ); + // Collect data of source contracts referenced in tests and scripts. + let data = collect_preprocessor_data(gcx, &deps.referenced_contracts); + + // Extend existing sources with preprocessor deploy helper sources. + sources.extend(create_deploy_helpers(&data)); + + // Generate and apply preprocessor source updates. + apply_updates(sources, remove_bytecode_dependencies(gcx, &deps, &data)); Ok(()) }); // Warn if any diagnostics emitted during content parsing. - if let Err(err) = sess.emitted_errors().unwrap() { - warn!("failed preprocessing {err}"); + if let Err(err) = convert_solar_errors(compiler.dcx()) { + warn!(%err, "failed preprocessing"); } Ok(()) } } -impl Preprocessor for TestOptimizerPreprocessor { +impl Preprocessor for DynamicTestLinkingPreprocessor { fn preprocess( &self, compiler: &MultiCompiler, @@ -116,42 +123,3 @@ impl Preprocessor for TestOptimizerPreprocessor { self.preprocess(solc, input, &paths, mocks) } } - -fn solar_session_from_solc(solc: &SolcVersionedInput) -> Session { - use solar_parse::interface::config; - - Session::builder() - .with_buffer_emitter(Default::default()) - .opts(config::Opts { - language: match solc.input.language { - SolcLanguage::Solidity => config::Language::Solidity, - SolcLanguage::Yul => config::Language::Yul, - _ => unimplemented!(), - }, - - // TODO: ... - /* - evm_version: solc.input.settings.evm_version, - */ - ..Default::default() - }) - .build() -} - -fn solar_pcx_from_solc_no_sources<'sess>( - sess: &'sess Session, - solc: &SolcVersionedInput, - paths: &ProjectPathsConfig, -) -> ParsingContext<'sess> { - let mut pcx = ParsingContext::new(sess); - pcx.file_resolver.set_current_dir(solc.cli_settings.base_path.as_ref().unwrap_or(&paths.root)); - for remapping in &paths.remappings { - pcx.file_resolver.add_import_remapping(solar_sema::interface::config::ImportRemapping { - context: remapping.context.clone().unwrap_or_default(), - prefix: remapping.name.clone(), - path: remapping.path.clone(), - }); - } - pcx.file_resolver.add_include_paths(solc.cli_settings.include_paths.iter().cloned()); - pcx -} diff --git a/crates/common/src/provider/curl_transport.rs b/crates/common/src/provider/curl_transport.rs new file mode 100644 index 0000000000000..f2f3a44361f52 --- /dev/null +++ b/crates/common/src/provider/curl_transport.rs @@ -0,0 +1,203 @@ +//! Transport that outputs equivalent curl commands instead of making RPC requests. + +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_transport::{TransportError, TransportFut}; +use serde_json::Value; +use tower::Service; +use url::Url; + +/// Escapes a string for use in a single-quoted shell argument. +fn shell_escape(s: &str) -> String { + s.replace('\'', "'\"'\"'") +} + +/// Generates a curl command for an RPC request. +/// +/// This is a standalone helper that can be used to generate curl commands +/// without going through the transport layer. +pub fn generate_curl_command( + url: &str, + method: &str, + params: Value, + headers: Option<&[String]>, + jwt: Option<&str>, +) -> String { + let payload = serde_json::json!({ + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": 1 + }); + let payload_str = serde_json::to_string(&payload).unwrap_or_default(); + let escaped_payload = shell_escape(&payload_str); + + let mut cmd = String::from("curl -X POST"); + cmd.push_str(" -H 'Content-Type: application/json'"); + + if let Some(jwt) = jwt { + cmd.push_str(&format!(" -H 'Authorization: Bearer {}'", shell_escape(jwt))); + } + + if let Some(hdrs) = headers { + for h in hdrs { + cmd.push_str(&format!(" -H '{}'", shell_escape(h))); + } + } + + cmd.push_str(&format!(" --data-raw '{escaped_payload}'")); + cmd.push_str(&format!(" '{}'", shell_escape(url))); + + cmd +} + +/// A transport that prints curl commands instead of executing RPC requests. +/// +/// When a request is made through this transport, it will print the equivalent +/// curl command to stderr and return a dummy successful response. +#[derive(Clone, Debug)] +pub struct CurlTransport { + /// The URL to connect to. + url: Url, + /// The headers to use for requests. + headers: Vec, + /// The JWT to use for requests. + jwt: Option, +} + +impl CurlTransport { + /// Create a new curl transport with the given URL. + pub const fn new(url: Url) -> Self { + Self { url, headers: vec![], jwt: None } + } + + /// Set the headers for the transport. + pub fn with_headers(mut self, headers: Vec) -> Self { + self.headers = headers; + self + } + + /// Set the JWT for the transport. + pub fn with_jwt(mut self, jwt: Option) -> Self { + self.jwt = jwt; + self + } + + /// Generate a curl command for a request. + fn generate_curl_command(&self, req: &RequestPacket) -> String { + let payload_str = serde_json::to_string(req).unwrap_or_default(); + let escaped_payload = shell_escape(&payload_str); + + let mut cmd = String::from("curl -X POST"); + cmd.push_str(" -H 'Content-Type: application/json'"); + + if let Some(jwt) = &self.jwt { + cmd.push_str(&format!(" -H 'Authorization: Bearer {}'", shell_escape(jwt))); + } + + for h in &self.headers { + cmd.push_str(&format!(" -H '{}'", shell_escape(h))); + } + + cmd.push_str(&format!(" --data-raw '{escaped_payload}'")); + cmd.push_str(&format!(" '{}'", shell_escape(self.url.as_str()))); + + cmd + } + + /// Handle a request by printing the curl command. + pub fn request(&self, req: RequestPacket) -> TransportFut<'static> { + let curl_cmd = self.generate_curl_command(&req); + + Box::pin(async move { + // Print the curl command to stdout + let _ = crate::sh_println!("{curl_cmd}"); + + // Exit cleanly after printing the curl command + std::process::exit(0); + }) + } +} + +impl Service for CurlTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +impl Service for &CurlTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_json_rpc::{Id, Request}; + + fn make_test_request() -> RequestPacket { + let req: Request> = Request::new("eth_blockNumber", Id::Number(1), vec![]); + let serialized = req.serialize().unwrap(); + RequestPacket::Single(serialized) + } + + #[test] + fn test_basic_curl_command() { + let transport = CurlTransport::new("https://eth.example.com".parse().unwrap()); + let req = make_test_request(); + let cmd = transport.generate_curl_command(&req); + assert!(cmd.contains("eth_blockNumber")); + assert!(cmd.contains("https://eth.example.com")); + assert!(cmd.contains("jsonrpc")); + } + + #[test] + fn test_curl_with_headers() { + let transport = CurlTransport::new("https://eth.example.com".parse().unwrap()) + .with_headers(vec!["X-Custom: value".to_string()]); + let req = make_test_request(); + let cmd = transport.generate_curl_command(&req); + assert!(cmd.contains("X-Custom: value")); + } + + #[test] + fn test_curl_with_jwt() { + let transport = CurlTransport::new("https://eth.example.com".parse().unwrap()) + .with_jwt(Some("my-jwt-token".to_string())); + let req = make_test_request(); + let cmd = transport.generate_curl_command(&req); + assert!(cmd.contains("Authorization: Bearer my-jwt-token")); + } + + #[test] + fn test_shell_escape() { + let escaped = shell_escape("it's a test"); + assert_eq!(escaped, "it'\"'\"'s a test"); + } +} diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 0603b91f97d2d..1620342989a29 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -1,26 +1,41 @@ //! Provider-related instantiation and usage utilities. +pub mod curl_transport; +pub mod mpp; pub mod runtime_transport; use crate::{ - provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, + ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, + provider::{curl_transport::CurlTransport, runtime_transport::RuntimeTransportBuilder}, }; +use alloy_chains::NamedChain; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_network::{Network, NetworkWallet}; use alloy_provider::{ - fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, - network::{AnyNetwork, EthereumWallet}, Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, + fillers::{FillProvider, JoinFill, RecommendedFillers, WalletFiller}, + network::{AnyNetwork, EthereumWallet}, }; use alloy_rpc_client::ClientBuilder; -use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; +use alloy_transport::{ + TransportError, TransportFut, layers::RetryBackoffLayer, utils::guess_local_url, +}; use eyre::{Result, WrapErr}; -use foundry_config::NamedChain; +use foundry_config::Config; use reqwest::Url; use std::{ + marker::PhantomData, net::SocketAddr, path::{Path, PathBuf}, str::FromStr, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + task::{Context, Poll}, time::Duration, }; +use tower::Service; use url::ParseError; /// The assumed block time for unknown chains. @@ -34,20 +49,8 @@ const POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR: f32 = 0.6; pub type RetryProvider = RootProvider; /// Helper type alias for a retry provider with a signer -pub type RetryProviderWithSigner = FillProvider< - JoinFill< - JoinFill< - Identity, - JoinFill< - GasFiller, - JoinFill< - alloy_provider::fillers::BlobGasFiller, - JoinFill, - >, - >, - >, - WalletFiller, - >, +pub type RetryProviderWithSigner = FillProvider< + JoinFill::RecommendedFillers>, WalletFiller>, RootProvider, N, >; @@ -81,9 +84,61 @@ pub fn try_get_http_provider(builder: impl AsRef) -> Result ProviderBuilder::new(builder.as_ref()).build() } +/// A round-robin transport that distributes requests across multiple transports. +/// +/// Each request is sent to exactly one transport, rotating through the list. +/// Failover on error is handled by the retry layer above this service. +#[derive(Clone)] +pub struct RoundRobinService { + transports: Arc>, + next: Arc, +} + +impl RoundRobinService { + /// Creates a new round-robin service from a non-empty list of transports. + /// + /// # Panics + /// + /// Panics if `transports` is empty. + pub fn new(transports: Vec) -> Self { + assert!(!transports.is_empty(), "RoundRobinService requires at least one transport"); + Self { transports: Arc::new(transports), next: Arc::new(AtomicUsize::new(0)) } + } +} + +impl Service for RoundRobinService +where + S: Service< + RequestPacket, + Response = ResponsePacket, + Error = TransportError, + Future = TransportFut<'static>, + > + Clone + + Send + + Sync + + 'static, +{ + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + let transports = self.transports.clone(); + let idx = self.next.fetch_add(1, Ordering::Relaxed) % transports.len(); + let mut transport = transports[idx].clone(); + transport.call(req) + } +} + /// Helper type to construct a `RetryProvider` +/// +/// This builder is generic over the network type `N`, defaulting to `AnyNetwork`. #[derive(Debug)] -pub struct ProviderBuilder { +pub struct ProviderBuilder { // Note: this is a result, so we can easily chain builder calls url: Result, chain: NamedChain, @@ -96,10 +151,18 @@ pub struct ProviderBuilder { jwt: Option, headers: Vec, is_local: bool, + /// Whether to accept invalid certificates. + accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection. + no_proxy: bool, + /// Whether to output curl commands instead of making requests. + curl_mode: bool, + /// Phantom data for the network type. + _network: PhantomData, } -impl ProviderBuilder { - /// Creates a new builder instance +impl ProviderBuilder { + /// Creates a new ProviderBuilder helper instance. pub fn new(url_str: &str) -> Self { // a copy is needed for the next lines to work let mut url_str = url_str; @@ -145,7 +208,40 @@ impl ProviderBuilder { jwt: None, headers: vec![], is_local, + accept_invalid_certs: false, + no_proxy: false, + curl_mode: false, + _network: PhantomData, + } + } + + /// Constructs a [ProviderBuilder] instantiated using [Config] values. + /// + /// Defaults to `http://localhost:8545` and `Mainnet`. + pub fn from_config(config: &Config) -> Result { + let url = config.get_rpc_url_or_localhost_http()?; + let mut builder = Self::new(url.as_ref()); + + builder = builder.accept_invalid_certs(config.eth_rpc_accept_invalid_certs); + builder = builder.curl_mode(config.eth_rpc_curl); + + if let Ok(chain) = config.chain.unwrap_or_default().try_into() { + builder = builder.chain(chain); } + + if let Some(jwt) = config.get_rpc_jwt_secret()? { + builder = builder.jwt(jwt.as_ref()); + } + + if let Some(rpc_timeout) = config.eth_rpc_timeout { + builder = builder.timeout(Duration::from_secs(rpc_timeout)); + } + + if let Some(rpc_headers) = config.eth_rpc_headers.clone() { + builder = builder.headers(rpc_headers); + } + + Ok(builder) } /// Enables a request timeout. @@ -154,19 +250,19 @@ impl ProviderBuilder { /// response body has finished. /// /// Default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { + pub const fn timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } /// Sets the chain of the node the provider will connect to - pub fn chain(mut self, chain: NamedChain) -> Self { + pub const fn chain(mut self, chain: NamedChain) -> Self { self.chain = chain; self } /// How often to retry a failed request - pub fn max_retry(mut self, max_retry: u32) -> Self { + pub const fn max_retry(mut self, max_retry: u32) -> Self { self.max_retry = max_retry; self } @@ -185,7 +281,7 @@ impl ProviderBuilder { } /// The starting backoff delay to use after the first failed request - pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { + pub const fn initial_backoff(mut self, initial_backoff: u64) -> Self { self.initial_backoff = initial_backoff; self } @@ -193,7 +289,7 @@ impl ProviderBuilder { /// Sets the number of assumed available compute units per second /// /// See also, - pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + pub const fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { self.compute_units_per_second = compute_units_per_second; self } @@ -201,7 +297,10 @@ impl ProviderBuilder { /// Sets the number of assumed available compute units per second /// /// See also, - pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { + pub const fn compute_units_per_second_opt( + mut self, + compute_units_per_second: Option, + ) -> Self { if let Some(cups) = compute_units_per_second { self.compute_units_per_second = cups; } @@ -211,7 +310,7 @@ impl ProviderBuilder { /// Sets the provider to be local. /// /// This is useful for local dev nodes. - pub fn local(mut self, is_local: bool) -> Self { + pub const fn local(mut self, is_local: bool) -> Self { self.is_local = is_local; self } @@ -219,7 +318,7 @@ impl ProviderBuilder { /// Sets aggressive `max_retry` and `initial_backoff` values /// /// This is only recommend for local dev nodes - pub fn aggressive(self) -> Self { + pub const fn aggressive(self) -> Self { self.max_retry(100).initial_backoff(100).local(true) } @@ -242,8 +341,32 @@ impl ProviderBuilder { self } + /// Sets whether to accept invalid certificates. + pub const fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { + self.accept_invalid_certs = accept_invalid_certs; + self + } + + /// Sets whether to disable automatic proxy detection. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub const fn no_proxy(mut self, no_proxy: bool) -> Self { + self.no_proxy = no_proxy; + self + } + + /// Sets whether to output curl commands instead of making requests. + /// + /// When enabled, the provider will print equivalent curl commands to stdout + /// instead of actually executing the RPC requests. + pub const fn curl_mode(mut self, curl_mode: bool) -> Self { + self.curl_mode = curl_mode; + self + } + /// Constructs the `RetryProvider` taking all configs into account. - pub fn build(self) -> Result { + pub fn build(self) -> Result> { let Self { url, chain, @@ -254,16 +377,33 @@ impl ProviderBuilder { jwt, headers, is_local, + accept_invalid_certs, + no_proxy, + curl_mode, + .. } = self; let url = url?; let retry_layer = RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + // If curl_mode is enabled, use CurlTransport instead of RuntimeTransport + if curl_mode { + let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + let provider = AlloyProviderBuilder::<_, _, N>::default() + .connect_provider(RootProvider::new(client)); + + return Ok(provider); + } + let transport = RuntimeTransportBuilder::new(url) .with_timeout(timeout) .with_headers(headers) .with_jwt(jwt) + .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) .build(); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); @@ -279,14 +419,89 @@ impl ProviderBuilder { ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() - .connect_provider(RootProvider::new(client)); + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); + + Ok(provider) + } +} + +impl ProviderBuilder { + /// Constructs a `RetryProvider` backed by multiple URLs using round-robin load balancing. + /// + /// Each request is sent to exactly one transport, rotating through the list via + /// [`RoundRobinService`]. There is no health scoring or endpoint deprioritization. + /// On failure, the `RetryBackoffLayer` retries the request, which naturally hits + /// the next transport in the rotation. + pub fn build_fallback(self, urls: Vec) -> Result> { + let Self { + chain, + max_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + accept_invalid_certs, + no_proxy, + curl_mode, + .. + } = self; + + eyre::ensure!(!urls.is_empty(), "at least one fork URL is required"); + eyre::ensure!(!curl_mode, "curl mode is not supported with multiple fork URLs"); + + // Build a RuntimeTransport for each URL, using the same URL normalization + // as ProviderBuilder::new() (handles localhost:port, raw socket addrs, IPC paths) + let mut parsed_urls = Vec::with_capacity(urls.len()); + let transports: Vec<_> = urls + .iter() + .map(|url_str| { + let builder = Self::new(url_str); + let url = builder.url?; + parsed_urls.push(url.clone()); + Ok(RuntimeTransportBuilder::new(url) + .with_timeout(timeout) + .with_headers(headers.clone()) + .with_jwt(jwt.clone()) + .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) + .build()) + }) + .collect::>>()?; + + let round_robin = RoundRobinService::new(transports); + + let retry_layer = + RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + // Use normalized/parsed URLs for local detection, consistent with build() + let is_local = parsed_urls.iter().all(|url| guess_local_url(url.as_str())); + let client = ClientBuilder::default().layer(retry_layer).transport(round_robin, is_local); + + if !is_local { + client.set_poll_interval( + chain + .average_blocktime_hint() + .map(|hint| hint.min(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME)) + .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) + .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), + ); + } + + let provider = + AlloyProviderBuilder::<_, _, N>::default().connect_provider(RootProvider::new(client)); Ok(provider) } /// Constructs the `RetryProvider` with a wallet. - pub fn build_with_wallet(self, wallet: EthereumWallet) -> Result { + pub fn build_with_wallet + Clone>( + self, + wallet: W, + ) -> Result> + where + N: RecommendedFillers, + { let Self { url, chain, @@ -297,16 +512,35 @@ impl ProviderBuilder { jwt, headers, is_local, + accept_invalid_certs, + no_proxy, + curl_mode, + .. } = self; let url = url?; let retry_layer = RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + // If curl_mode is enabled, use CurlTransport instead of RuntimeTransport + if curl_mode { + let transport = CurlTransport::new(url).with_headers(headers).with_jwt(jwt); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + let provider = AlloyProviderBuilder::<_, _, N>::default() + .with_recommended_fillers() + .wallet(wallet) + .connect_provider(RootProvider::new(client)); + + return Ok(provider); + } + let transport = RuntimeTransportBuilder::new(url) .with_timeout(timeout) .with_headers(headers) .with_jwt(jwt) + .accept_invalid_certs(accept_invalid_certs) + .no_proxy(no_proxy) .build(); let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); @@ -315,12 +549,15 @@ impl ProviderBuilder { client.set_poll_interval( chain .average_blocktime_hint() + // we cap the poll interval because if not provided, chain would default to + // mainnet + .map(|hint| hint.min(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME)) .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), ); } - let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .with_recommended_fillers() .wallet(wallet) .connect_provider(RootProvider::new(client)); @@ -340,12 +577,16 @@ fn resolve_path(path: &Path) -> Result { #[cfg(windows)] fn resolve_path(path: &Path) -> Result { - if let Some(s) = path.to_str() { - if s.starts_with(r"\\.\pipe\") { - return Ok(path.to_path_buf()); - } + if let Some(s) = path.to_str() + && s.starts_with(r"\\.\pipe\") + { + return Ok(path.to_path_buf()); + } + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + std::env::current_dir().map(|d| d.join(path)).map_err(drop) } - Err(()) } #[cfg(test)] @@ -354,7 +595,7 @@ mod tests { #[test] fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); + let builder = ProviderBuilder::::new("localhost:8545"); assert!(builder.url.is_ok()); let url = builder.url.unwrap(); diff --git a/crates/common/src/provider/mpp/keys.rs b/crates/common/src/provider/mpp/keys.rs new file mode 100644 index 0000000000000..fa0fc80ed3d03 --- /dev/null +++ b/crates/common/src/provider/mpp/keys.rs @@ -0,0 +1,507 @@ +//! Auto-discovery of MPP signing keys from the Tempo wallet. +//! +//! Uses the shared Tempo keystore types from [`crate::tempo`] and adds +//! MPP-specific primary key selection logic (passkey > first entry with +//! inline key > first entry, mirroring `Keystore::primary_key()` in +//! `tempo-common`). + +use crate::tempo::{TEMPO_PRIVATE_KEY_ENV, WalletType, read_tempo_keys_file}; +use alloy_primitives::Address; +use std::env; +use tracing::debug; + +/// Options for MPP key discovery filtering. +#[derive(Debug, Default, Clone)] +pub struct DiscoverOptions { + /// Only consider keys matching this chain ID. + pub chain_id: Option, + /// Only consider keys whose spending limits include this currency. + pub currency: Option
, +} + +/// Discovered MPP key configuration. +/// +/// Contains the private key and optional keychain metadata for signing mode +/// configuration. +#[derive(Debug, Clone)] +pub struct MppKeyConfig { + /// The hex-encoded private key. + pub key: String, + /// Smart wallet address (for keychain signing mode). + pub wallet_address: Option
, + /// Key address / signer address (for keychain authorized signer). + pub key_address: Option
, + /// RLP-encoded signed key authorization (hex string). + pub key_authorization: Option, + /// Chain ID from the key entry in `keys.toml`. `None` when discovered from + /// the `TEMPO_PRIVATE_KEY` env var (no keychain metadata available). + pub chain_id: Option, + /// Currencies from the key's spending limits. + pub currencies: Vec
, +} + +/// Attempt to auto-discover an MPP signing key from the Tempo wallet. +/// +/// Returns `Some(hex_key)` if a key is found, `None` otherwise. +/// Never fails — discovery errors are silently ignored (logged at debug level). +pub fn discover_mpp_key() -> Option { + discover_mpp_config(Default::default()).map(|c| c.key) +} + +/// Discover MPP key configuration filtered by chain ID and/or currency. +/// +/// Filters keys.toml entries by `chain_id` and `currency` simultaneously, +/// then applies the standard priority rule (passkey > inline key > first) +/// within the filtered set. This ensures the selected key matches both the +/// target chain and the required currency. +pub fn discover_mpp_config(opts: DiscoverOptions) -> Option { + // 1. Check TEMPO_PRIVATE_KEY env var (no keychain metadata available) + if let Ok(key) = env::var(TEMPO_PRIVATE_KEY_ENV) { + let key = key.trim().to_string(); + if !key.is_empty() { + debug!("using MPP key from {TEMPO_PRIVATE_KEY_ENV} env var"); + return Some(MppKeyConfig { + key, + wallet_address: None, + key_address: None, + key_authorization: None, + chain_id: None, + currencies: vec![], + }); + } + } + + // 2. Read $TEMPO_HOME/wallet/keys.toml (default: ~/.tempo/wallet/keys.toml) + let keys_file = read_tempo_keys_file()?; + + // `expiry == 0` means "no expiry" on the wire. + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + // Pick primary key using the same deterministic order as + // `Keystore::primary_key()` in tempo-common: + // passkey > first entry with inline key > first entry + // Only entries with a usable inline key can provide a signing key. + // Filter by chain_id, currency, and freshness when provided. + let candidates: Vec<_> = keys_file + .keys + .iter() + .filter(|k| opts.chain_id.is_none_or(|cid| k.chain_id == cid)) + .filter(|k| { + opts.currency + .is_none_or(|cur| k.limits.is_empty() || k.limits.iter().any(|l| l.currency == cur)) + }) + .filter(|k| k.expiry.is_none_or(|e| e == 0 || e > now)) + .collect(); + + let primary = candidates + .iter() + .find(|k| k.wallet_type == WalletType::Passkey && k.has_inline_key()) + .or_else(|| candidates.iter().find(|k| k.has_inline_key())) + .or(candidates.first()) + .copied(); + + if let Some(entry) = primary + && let Some(key) = &entry.key + { + let key = key.trim().to_string(); + if !key.is_empty() { + debug!("using MPP key from tempo wallet keys file"); + return Some(MppKeyConfig { + key, + wallet_address: Some(entry.wallet_address), + key_address: entry.key_address, + key_authorization: entry.key_authorization.clone(), + chain_id: Some(entry.chain_id), + currencies: entry.limits.iter().map(|l| l.currency).collect(), + }); + } + } + + debug!("no usable key found in tempo keys file"); + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tempo::KeysFile; + use std::{io::Write, path::PathBuf}; + + /// Write a keys.toml to a temp dir and set TEMPO_HOME to point at it. + fn setup_keys_toml(toml_content: &str) -> (tempfile::TempDir, PathBuf) { + let dir = tempfile::tempdir().expect("tempdir"); + let wallet_dir = dir.path().join("wallet"); + std::fs::create_dir_all(&wallet_dir).expect("create wallet dir"); + let keys_path = wallet_dir.join("keys.toml"); + let mut f = std::fs::File::create(&keys_path).expect("create keys.toml"); + f.write_all(toml_content.as_bytes()).expect("write keys.toml"); + (dir, keys_path) + } + + #[test] + fn discover_from_tempo_home_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + let toml_content = format!( + r#" +[[keys]] +wallet_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +key_address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +key = "{key}" +chain_id = 4217 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + let discovered = discover_mpp_key(); + assert_eq!(discovered.as_deref(), Some(key)); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn discover_env_var_takes_priority_over_keys_toml() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let file_key = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let env_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + let toml_content = format!( + r#" +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{file_key}" +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::set_var("TEMPO_PRIVATE_KEY", env_key); + } + + let discovered = discover_mpp_key(); + assert_eq!(discovered.as_deref(), Some(env_key)); + + unsafe { + std::env::remove_var("TEMPO_HOME"); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + } + + #[test] + fn discover_returns_none_when_no_keys() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let (dir, _) = setup_keys_toml(""); + + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + let discovered = discover_mpp_key(); + assert!(discovered.is_none()); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn discover_skips_entries_without_inline_key() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + let toml_content = format!( + r#" +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000001" +chain_id = 4217 + +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{key}" +chain_id = 4217 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + let discovered = discover_mpp_key(); + assert_eq!(discovered.as_deref(), Some(key)); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn parse_keys_toml() { + let toml_str = r#" +[[keys]] +wallet_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +key_address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +chain_id = 4217 +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + assert_eq!(keys_file.keys.len(), 1); + assert_eq!( + keys_file.keys[0].key.as_deref(), + Some("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + ); + } + + #[test] + fn parse_keys_toml_no_inline_key() { + let toml_str = r#" +[[keys]] +wallet_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +key_address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +chain_id = 4217 +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + assert_eq!(keys_file.keys.len(), 1); + assert!(keys_file.keys[0].key.is_none()); + } + + #[test] + fn parse_keys_toml_multiple_entries() { + let toml_str = r#" +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000001" +key_address = "0x0000000000000000000000000000000000000002" +chain_id = 4217 + +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000003" +key_address = "0x0000000000000000000000000000000000000004" +key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" +chain_id = 4217 +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + assert_eq!(keys_file.keys.len(), 2); + assert!(keys_file.keys[0].key.is_none()); + assert!(keys_file.keys[1].key.is_some()); + } + + #[test] + fn parse_keys_toml_with_wallet_type() { + let toml_str = r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xpasskey_secret" +chain_id = 4217 + +[[keys]] +wallet_type = "local" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "0xlocal_secret" +chain_id = 4217 +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + assert_eq!(keys_file.keys.len(), 2); + assert_eq!(keys_file.keys[0].wallet_type, WalletType::Passkey); + assert_eq!(keys_file.keys[1].wallet_type, WalletType::Local); + } + + #[test] + fn primary_key_passkey_wins() { + let toml_str = r#" +[[keys]] +wallet_type = "local" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xlocal_key" + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "0xpasskey_key" +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + let primary = keys_file + .keys + .iter() + .find(|k| k.wallet_type == WalletType::Passkey) + .or_else(|| keys_file.keys.iter().find(|k| k.has_inline_key())) + .or(keys_file.keys.first()); + assert_eq!(primary.unwrap().key.as_deref(), Some("0xpasskey_key")); + } + + #[test] + fn primary_key_inline_key_over_no_key() { + let toml_str = r#" +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000001" + +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000002" +key = "0xthe_key" +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + let primary = keys_file + .keys + .iter() + .find(|k| k.wallet_type == WalletType::Passkey) + .or_else(|| keys_file.keys.iter().find(|k| k.has_inline_key())) + .or(keys_file.keys.first()); + assert_eq!(primary.unwrap().key.as_deref(), Some("0xthe_key")); + } + + #[test] + fn discover_filters_by_chain_id() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let mainnet_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let testnet_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let toml_content = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{mainnet_key}" +chain_id = 4217 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{testnet_key}" +chain_id = 42431 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + // Filter by testnet chain_id → returns testnet key (even though mainnet is first) + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(42431), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, testnet_key); + + // Filter by mainnet chain_id → returns mainnet key + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, mainnet_key); + + // No filter → returns first key (mainnet) + let config = discover_mpp_config(Default::default()); + assert_eq!(config.as_ref().unwrap().key, mainnet_key); + + // Filter by unknown chain_id → None + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(9999), ..Default::default() }); + assert!(config.is_none()); + + // Passkey priority within filtered set + let toml_mixed = format!( + r#" +[[keys]] +wallet_type = "local" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{mainnet_key}" +chain_id = 4217 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{testnet_key}" +chain_id = 4217 +"# + ); + let (dir2, _) = setup_keys_toml(&toml_mixed); + unsafe { std::env::set_var("TEMPO_HOME", dir2.path()) }; + + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!( + config.as_ref().unwrap().key, + testnet_key, + "passkey should win over local within the same chain_id" + ); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn discover_filters_expired_entries() { + // Expired entries must not be selected, so the next 402 re-triggers + // the device-code flow instead of returning a stale key. + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let expired_key = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let fresh_key = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let toml_content = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 + +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000002" +key = "{fresh_key}" +chain_id = 4217 +expiry = 0 +"# + ); + let (dir, _) = setup_keys_toml(&toml_content); + unsafe { + std::env::set_var("TEMPO_HOME", dir.path()); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + // Even though the expired entry comes first, discovery skips it. + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert_eq!(config.as_ref().unwrap().key, fresh_key); + + // With only the expired entry present, discovery returns None so the + // 402 path can run `ensure_access_key` again. + let only_expired = format!( + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "{expired_key}" +chain_id = 4217 +expiry = 1 +"# + ); + let (dir2, _) = setup_keys_toml(&only_expired); + unsafe { std::env::set_var("TEMPO_HOME", dir2.path()) }; + let config = + discover_mpp_config(DiscoverOptions { chain_id: Some(4217), ..Default::default() }); + assert!(config.is_none(), "expired-only keys.toml must not yield a usable key"); + + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn parse_keys_toml_unknown_fields_ignored() { + let toml_str = r#" +[[keys]] +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xsecret" +chain_id = 4217 +key_authorization = "0xauth_data" +expiry = 1750000000 +unknown_future_field = "should be ignored" + +[[keys.limits]] +currency = "0x20c000000000000000000000b9537d11c60e8b50" +limit = "1000" +"#; + let keys_file: KeysFile = toml::from_str(toml_str).unwrap(); + assert_eq!(keys_file.keys.len(), 1); + assert_eq!(keys_file.keys[0].key.as_deref(), Some("0xsecret")); + } +} diff --git a/crates/common/src/provider/mpp/mod.rs b/crates/common/src/provider/mpp/mod.rs new file mode 100644 index 0000000000000..a1a3ae8b8c483 --- /dev/null +++ b/crates/common/src/provider/mpp/mod.rs @@ -0,0 +1,10 @@ +//! MPP (Machine Payments Protocol) support for 402-gated RPC endpoints. +//! +//! - [`keys`]: Auto-discovery of signing keys from the Tempo wallet. +//! - [`transport`]: HTTP transport that handles 402 challenges automatically. + +pub mod keys; +pub mod persist; +pub mod session; +pub mod transport; +pub mod ws; diff --git a/crates/common/src/provider/mpp/persist.rs b/crates/common/src/provider/mpp/persist.rs new file mode 100644 index 0000000000000..803c7270e041e --- /dev/null +++ b/crates/common/src/provider/mpp/persist.rs @@ -0,0 +1,277 @@ +//! Persistent channel storage for MPP sessions. +//! +//! Stores open payment channel state in a SQLite database at +//! `$TEMPO_HOME/channels.db` (default: `~/.tempo/channels.db`). +//! This allows channel reuse across process invocations, avoiding the cost of +//! opening a new on-chain channel for every `cast` / `forge` command. + +use alloy_primitives::{Address, B256}; +use foundry_wallets::{Channel, ChannelDb}; +use mpp::client::channel_ops::ChannelEntry; +use std::{ + collections::HashMap, + sync::OnceLock, + time::{SystemTime, UNIX_EPOCH}, +}; +use tracing::{debug, warn}; + +use crate::tempo::tempo_home; + +/// Process-wide database handle. +fn global_db() -> Option<&'static ChannelDb> { + static DB: OnceLock> = OnceLock::new(); + DB.get_or_init(|| { + let path = tempo_home()?.join("channels.db"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Some(old) = + tempo_home().map(|h| h.join("foundry/channels.json")).filter(|p| p.exists()) + { + warn!( + ?old, + "found old channels.json — this file is no longer used; channels will be re-opened" + ); + } + + match ChannelDb::open(&path) { + Ok(db) => { + debug!(?path, "opened channel database"); + Some(db) + } + Err(e) => { + warn!(?path, %e, "failed to open channel database"); + None + } + } + }) + .as_ref() +} + +/// Reconstruct the composite HashMap key from a persisted `Channel`. +/// +/// Mirrors `SessionProvider::channel_key()` in session.rs. +fn channel_key_from_persisted(ch: &Channel) -> String { + let origin_hash = &alloy_primitives::keccak256(ch.origin.as_bytes()).to_string()[..18]; + format!( + "{}:{}:{}:{}:{}:{}:{}", + origin_hash, + ch.chain_id, + ch.payer, + ch.authorized_signer, + ch.payee, + ch.token, + ch.escrow_contract + ) + .to_lowercase() +} + +/// Whether a channel can still be used (active and not fully spent). +fn is_usable(ch: &Channel) -> bool { + if ch.state != "active" { + return false; + } + let cumulative: u128 = ch.cumulative_amount.parse().unwrap_or(u128::MAX); + let deposit: u128 = ch.deposit.parse().unwrap_or(0); + cumulative < deposit +} + +/// Convert a persisted `Channel` to a `ChannelEntry`. +pub fn to_channel_entry(ch: &Channel) -> Option { + let channel_id: B256 = ch.channel_id.parse().ok()?; + let salt: B256 = ch.salt.parse().ok()?; + let escrow_contract: Address = ch.escrow_contract.parse().ok()?; + let cumulative_amount: u128 = ch.cumulative_amount.parse().ok()?; + + Some(ChannelEntry { + channel_id, + salt, + cumulative_amount, + escrow_contract, + chain_id: ch.chain_id as u64, + opened: ch.state == "active", + }) +} + +/// Create a `Channel` from a `ChannelEntry` with metadata. +pub fn from_channel_entry( + entry: &ChannelEntry, + deposit: u128, + origin: &str, + payer: &Address, + payee: &Address, + token: &Address, + authorized_signer: &Address, +) -> Channel { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + Channel { + channel_id: entry.channel_id.to_string(), + version: 1, + origin: origin.to_string(), + request_url: String::new(), + chain_id: entry.chain_id as i64, + escrow_contract: entry.escrow_contract.to_string(), + token: token.to_string(), + payee: payee.to_string(), + payer: payer.to_string(), + authorized_signer: authorized_signer.to_string(), + salt: entry.salt.to_string(), + deposit: deposit.to_string(), + cumulative_amount: entry.cumulative_amount.to_string(), + challenge_echo: String::new(), + state: if entry.opened { "active" } else { "closed" }.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: now, + last_used_at: now, + } +} + +/// Load channels from database, evicting spent/inactive entries. +pub fn load_channels() -> HashMap { + let Some(db) = global_db() else { + return HashMap::new(); + }; + + let channels = match db.load() { + Ok(channels) => channels, + Err(e) => { + warn!(%e, "failed to load channels from database"); + return HashMap::new(); + } + }; + + let usable: HashMap = channels + .into_iter() + .filter(is_usable) + .map(|ch| { + let key = channel_key_from_persisted(&ch); + (key, ch) + }) + .collect(); + + debug!(count = usable.len(), "loaded persisted MPP channels"); + usable +} + +/// Save channels to database. +pub fn save_channels(channels: &HashMap) { + let Some(db) = global_db() else { + return; + }; + + for ch in channels.values() { + if let Err(e) = db.upsert(ch) { + warn!(%e, channel_id = %ch.channel_id, "failed to save channel"); + } + } + debug!(count = channels.len(), "saved MPP channels"); +} + +/// Delete a channel from the database by its channel ID. +pub fn delete_channel_from_db(channel_id: &str) { + let Some(db) = global_db() else { + return; + }; + if let Err(e) = db.delete(channel_id) { + warn!(%e, channel_id, "failed to delete channel from database"); + } +} + +/// Look up a usable persisted channel by key. +pub fn find_channel(channels: &HashMap, key: &str) -> Option { + channels.get(key).filter(|ch| is_usable(ch)).and_then(to_channel_entry) +} + +/// Insert or update a channel entry in memory only (no DB write). +pub fn upsert_channel_in_memory( + channels: &mut HashMap, + key: &str, + entry: &ChannelEntry, +) { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64; + + if let Some(existing) = channels.get_mut(key) { + existing.cumulative_amount = entry.cumulative_amount.to_string(); + existing.last_used_at = now; + existing.state = if entry.opened { "active" } else { "closed" }.to_string(); + } else { + warn!(key, "upsert_channel_in_memory called for unknown channel"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_channel(state: &str, cumulative: &str, deposit: &str) -> Channel { + Channel { + channel_id: format!("0x{}", "ab".repeat(32)), + version: 1, + origin: "https://rpc.mpp.moderato.tempo.xyz".to_string(), + request_url: String::new(), + chain_id: 42431, + escrow_contract: "0xe1c4d3dce17bc111181ddf716f75bae49e61a336".to_string(), + token: "0x20c0000000000000000000000000000000000000".to_string(), + payee: "0x3333333333333333333333333333333333333333".to_string(), + payer: "0x1111111111111111111111111111111111111111".to_string(), + authorized_signer: "0x1111111111111111111111111111111111111111".to_string(), + salt: format!("0x{}", "cd".repeat(32)), + deposit: deposit.to_string(), + cumulative_amount: cumulative.to_string(), + challenge_echo: String::new(), + state: state.to_string(), + close_requested_at: 0, + grace_ready_at: 0, + created_at: 1000, + last_used_at: 1000, + } + } + + #[test] + fn usable() { + assert!(is_usable(&test_channel("active", "5000", "100000"))); + assert!(!is_usable(&test_channel("active", "100000", "100000"))); + assert!(!is_usable(&test_channel("active", "200000", "100000"))); + assert!(!is_usable(&test_channel("closed", "0", "100000"))); + assert!(!is_usable(&test_channel("closing", "0", "100000"))); + } + + #[test] + fn channel_entry_round_trip() { + let entry = ChannelEntry { + channel_id: B256::random(), + salt: B256::random(), + cumulative_amount: 42000, + escrow_contract: Address::random(), + chain_id: 42431, + opened: true, + }; + + let payer = Address::random(); + let payee = Address::random(); + let token = Address::random(); + let persisted = + from_channel_entry(&entry, 100_000, "https://rpc.test", &payer, &payee, &token, &payer); + let restored = to_channel_entry(&persisted).expect("should parse back"); + + assert_eq!(restored.channel_id, entry.channel_id); + assert_eq!(restored.salt, entry.salt); + assert_eq!(restored.cumulative_amount, entry.cumulative_amount); + assert_eq!(restored.escrow_contract, entry.escrow_contract); + assert_eq!(restored.chain_id, entry.chain_id); + assert!(restored.opened); + } + + #[test] + fn find_channel_filters_unusable() { + let mut channels = HashMap::new(); + channels.insert("usable".into(), test_channel("active", "1000", "100000")); + channels.insert("spent".into(), test_channel("active", "100000", "100000")); + + assert!(find_channel(&channels, "usable").is_some()); + assert!(find_channel(&channels, "spent").is_none()); + assert!(find_channel(&channels, "missing").is_none()); + } +} diff --git a/crates/common/src/provider/mpp/session.rs b/crates/common/src/provider/mpp/session.rs new file mode 100644 index 0000000000000..c3e87f8cf42b5 --- /dev/null +++ b/crates/common/src/provider/mpp/session.rs @@ -0,0 +1,935 @@ +//! Tempo session payment provider with expiring nonces. +//! +//! Custom implementation that mirrors `tempoxyz/wallet`'s approach: uses +//! expiring nonces (`nonce=0`, `nonceKey=MAX`, `validBefore=now+25s`) for +//! channel open transactions instead of fetching sequential nonces via +//! `eth_getTransactionCount`. This avoids the chicken-and-egg problem when +//! the RPC endpoint is itself 402-gated. + +use super::persist; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use foundry_wallets::Channel; +use mpp::{ + client::{ + PaymentProvider, + channel_ops::{ + ChannelEntry, OpenPayloadOptions, build_credential, create_voucher_payload, + resolve_chain_id, resolve_escrow, + }, + tempo::signing::{TempoSigningMode, sign_and_encode_async}, + }, + error::MppError, + protocol::{ + core::{PaymentChallenge, PaymentCredential}, + intents::SessionRequest, + methods::tempo::session::TempoSessionExt, + }, + tempo::{Call, SessionCredentialPayload, compute_channel_id, sign_voucher}, +}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex, OnceLock}, +}; + +/// Shared per-origin in-memory channel state: (channels, key_provisioned). +type SharedChannelState = (Arc>>, Arc>); + +/// Process-wide channel state registry, keyed by origin URL. +/// +/// Stores per-origin in-memory channel maps and key provisioning state. +static GLOBAL_CHANNELS: OnceLock>> = OnceLock::new(); + +/// Process-wide persisted channel state, shared across ALL origins. +/// +/// Using a single map ensures saves from different origins don't clobber +/// each other's state. +static GLOBAL_PERSISTED: OnceLock>>> = OnceLock::new(); + +/// Tracks uncommitted channel state from the most recent payment. +/// +/// Used to defer persistence until the server confirms acceptance, preventing +/// local state from getting ahead of reality on failed open/top-up. +#[derive(Clone, Debug)] +enum PendingAction { + /// A new channel was opened but not yet confirmed by the server. + Open { key: String }, + /// A top-up was prepared but not yet confirmed by the server. + TopUp { key: String, old_deposit: String }, + /// A voucher cumulative_amount was advanced but not yet confirmed. + Voucher { key: String, old_cumulative: u128 }, +} + +/// Expiring nonce key (U256::MAX) — matches the charge flow. +const EXPIRING_NONCE_KEY: U256 = U256::MAX; + +/// Validity window (in seconds) for expiring nonce transactions. +const VALID_BEFORE_SECS: u64 = 25; + +/// Default gas limit for session open transactions. +const SESSION_OPEN_GAS_LIMIT: u64 = 10_000_000; + +/// Max fee per gas (20 gwei — Tempo's fixed base fee). +const MAX_FEE_PER_GAS: u128 = 20_000_000_000; + +/// Max priority fee per gas. +const MAX_PRIORITY_FEE_PER_GAS: u128 = 20_000_000_000; + +/// Tempo session provider using expiring nonces. +/// +/// Unlike mpp-rs's `TempoSessionProvider` which fetches sequential nonces +/// (requiring a non-gated RPC), this provider uses expiring nonces for +/// channel open transactions — matching how `tempoxyz/wallet` works. +#[derive(Clone)] +pub struct SessionProvider { + signer: mpp::PrivateKeySigner, + signing_mode: TempoSigningMode, + authorized_signer: Option
, + default_deposit: Option, + channels: Arc>>, + key_provisioned: Arc>, + persisted: Arc>>, + /// Tracks uncommitted open/top-up state for deferred persistence. + pending: Arc>>, + /// Chain ID from the key entry in `keys.toml` that was used to initialize + /// this provider. Used to reject challenges for a different chain. + key_chain_id: Option, + /// Currencies from the key's spending limits. Used to reject challenges + /// for currencies the key cannot pay with. + key_currencies: Vec
, + origin: String, +} + +impl std::fmt::Debug for SessionProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SessionProvider") + .field("signing_mode", &self.signing_mode) + .field("authorized_signer", &self.authorized_signer) + .field("default_deposit", &self.default_deposit) + .finish_non_exhaustive() + } +} + +impl SessionProvider { + /// Create a new session provider with the given signer and RPC origin URL. + /// + /// Channel state is shared process-wide: all `SessionProvider` instances + /// share the same in-memory channels and persisted state. This prevents + /// concurrent providers (e.g. multiple `forge script` providers for the + /// same URL) from reading stale `cumulative_amount` values from disk and + /// producing duplicate vouchers. + pub fn new(signer: mpp::PrivateKeySigner, origin: String) -> Self { + // Global persisted map shared across all origins. + let persisted = + GLOBAL_PERSISTED.get_or_init(|| Arc::new(Mutex::new(persist::load_channels()))).clone(); + + // Per-origin in-memory channel map + key provisioning state. + let (channels, key_provisioned) = { + let global = GLOBAL_CHANNELS.get_or_init(|| Mutex::new(HashMap::new())); + let mut map = global.lock().unwrap(); + map.entry(origin.clone()) + .or_insert_with(|| { + // Hydrate only channels belonging to this origin. + let mut channels: HashMap = HashMap::new(); + for (key, ch) in persisted.lock().unwrap().iter() { + if ch.origin == origin + && let Some(entry) = persist::to_channel_entry(ch) + { + channels.insert(key.clone(), entry); + } + } + (Arc::new(Mutex::new(channels)), Arc::new(Mutex::new(true))) + }) + .clone() + }; + + Self { + signer, + signing_mode: TempoSigningMode::Direct, + authorized_signer: None, + default_deposit: None, + channels, + key_provisioned, + persisted, + pending: Arc::new(Mutex::new(None)), + key_chain_id: None, + key_currencies: vec![], + origin, + } + } + + /// Set the signing mode (direct or keychain). + pub fn with_signing_mode(mut self, mode: TempoSigningMode) -> Self { + self.signing_mode = mode; + self + } + + /// Set the authorized signer address for keychain mode. + pub const fn with_authorized_signer(mut self, addr: Address) -> Self { + self.authorized_signer = Some(addr); + self + } + + /// Set the default deposit amount. + pub const fn with_default_deposit(mut self, deposit: u128) -> Self { + self.default_deposit = Some(deposit); + self + } + + /// Address that funds payments for this provider. + pub fn funding_wallet_address(&self) -> Address { + self.signing_mode.from_address(self.signer.address()) + } + + /// Chain ID from the selected wallet key, when known. + pub const fn key_chain_id(&self) -> Option { + self.key_chain_id + } + + /// Set the chain ID and currencies from the key entry used to initialize + /// this provider. Used to reject challenges for incompatible chains/currencies. + /// When `chain_id` is `None` (e.g. env var key), chain filtering is skipped. + pub fn with_key_filters(mut self, chain_id: Option, currencies: Vec
) -> Self { + self.key_chain_id = chain_id; + self.key_currencies = currencies; + self + } + + /// Check whether this provider's key is compatible with the given + /// chain ID and currency from a 402 challenge. + pub fn matches_challenge(&self, chain_id: Option, currency: Option
) -> bool { + if let Some(cid) = chain_id + && self.key_chain_id.is_some_and(|k| k != cid) + { + return false; + } + if let Some(cur) = currency + && !self.key_currencies.is_empty() + && !self.key_currencies.contains(&cur) + { + return false; + } + true + } + + /// Clear channels belonging to this origin (e.g. after server 410). + /// + /// Only removes channels whose `origin` matches `self.origin`, preserving + /// channels for other RPC endpoints. + pub fn clear_channels(&self) { + let origin = &self.origin; + // Lock order: channels → persisted (consistent with pay_session) + let mut channels = self.channels.lock().unwrap(); + let mut persisted = self.persisted.lock().unwrap(); + let keys_to_remove: Vec<(String, String)> = persisted + .iter() + .filter(|(_, ch)| ch.origin == *origin) + .map(|(k, ch): (&String, &Channel)| (k.clone(), ch.channel_id.clone())) + .collect(); + for (key, channel_id) in &keys_to_remove { + channels.remove(key); + persisted.remove(key); + persist::delete_channel_from_db(channel_id); + } + } + + /// Mark whether the access key has been provisioned on-chain. + pub fn set_key_provisioned(&self, provisioned: bool) { + *self.key_provisioned.lock().unwrap() = provisioned; + } + + /// Check whether the access key has been provisioned on-chain. + pub fn is_key_provisioned(&self) -> bool { + *self.key_provisioned.lock().unwrap() + } + + /// Persist any pending open/top-up/voucher state to disk. + /// + /// Called by the transport after the server confirms acceptance. + pub fn flush_pending(&self) { + let pending = self.pending.lock().unwrap().take(); + if pending.is_some() { + persist::save_channels(&self.persisted.lock().unwrap()); + } + } + + /// Commit a pending top-up (deposit increase) without flushing to disk. + /// + /// Called by the transport when the server returns 204 (top-up accepted). + /// The deposit increase is now committed, but the follow-up voucher is + /// tracked as a new pending action. + pub fn commit_topup_and_track_voucher(&self) { + let pending = self.pending.lock().unwrap().take(); + if let Some(PendingAction::TopUp { key, .. }) = pending { + // Top-up is now committed — read the current cumulative_amount + // so we can roll back just the voucher increment if needed. + let old_cumulative = + self.channels.lock().unwrap().get(&key).map(|e| e.cumulative_amount).unwrap_or(0); + *self.pending.lock().unwrap() = Some(PendingAction::Voucher { key, old_cumulative }); + } + } + + /// Roll back pending open/top-up/voucher state on failure. + /// + /// Called by the transport when the server rejects the payment or times out. + pub fn rollback_pending(&self) { + let pending = self.pending.lock().unwrap().take(); + if let Some(action) = pending { + match action { + PendingAction::Open { key } => { + self.channels.lock().unwrap().remove(&key); + self.persisted.lock().unwrap().remove(&key); + } + PendingAction::TopUp { key, old_deposit } => { + if let Some(p) = self.persisted.lock().unwrap().get_mut(&key) { + p.deposit = old_deposit; + } + } + PendingAction::Voucher { key, old_cumulative } => { + if let Some(entry) = self.channels.lock().unwrap().get_mut(&key) { + entry.cumulative_amount = old_cumulative; + } + if let Some(p) = self.persisted.lock().unwrap().get_mut(&key) { + p.cumulative_amount = old_cumulative.to_string(); + } + } + } + } + } + + fn channel_key( + origin: &str, + payer: &Address, + authorized_signer: Option
, + payee: &Address, + currency: &Address, + escrow: &Address, + chain_id: u64, + ) -> String { + // Use first 8 bytes of origin hash to scope the key without persisting + // the full URL (which may contain secrets in query params). + let origin_hash = &alloy_primitives::keccak256(origin.as_bytes()).to_string()[..18]; + let signer = authorized_signer.unwrap_or(*payer); + format!("{origin_hash}:{chain_id}:{payer}:{signer}:{payee}:{currency}:{escrow}") + .to_lowercase() + } + + fn resolve_deposit(&self, suggested: Option<&str>) -> Result { + let suggested_val = suggested.and_then(|s| s.parse::().ok()); + + // Local config takes priority. Warn when server suggests more so users + // can bump MPP_DEPOSIT if the default is too low. + if let (Some(sv), Some(local)) = (suggested_val, self.default_deposit) + && sv > local + { + let _ = sh_warn!( + "server-suggested deposit ({sv}) exceeds local default ({local}); \ + set MPP_DEPOSIT to override" + ); + } + + let amount = self.default_deposit.or(suggested_val); + + amount.ok_or_else(|| { + MppError::InvalidConfig("no deposit amount: set default_deposit".to_string()) + }) + } + + async fn create_open_tx( + &self, + payer: Address, + options: OpenPayloadOptions, + ) -> Result<(ChannelEntry, SessionCredentialPayload), MppError> { + use alloy_sol_types::SolCall as _; + + let authorized_signer = options.authorized_signer.unwrap_or(payer); + let salt = B256::random(); + + let channel_id = compute_channel_id( + payer, + options.payee, + options.currency, + salt, + authorized_signer, + options.escrow_contract, + options.chain_id, + ); + + alloy_sol_types::sol! { + interface ITIP20 { + function approve(address spender, uint256 amount) external returns (bool); + } + interface IEscrow { + function open( + address payee, + address token, + uint128 deposit, + bytes32 salt, + address authorizedSigner + ) external; + } + } + + let approve_data = + ITIP20::approveCall::new((options.escrow_contract, U256::from(options.deposit))) + .abi_encode(); + + let open_data = IEscrow::openCall::new(( + options.payee, + options.currency, + options.deposit, + salt, + authorized_signer, + )) + .abi_encode(); + + let calls = vec![ + Call { + to: TxKind::Call(options.currency), + value: U256::ZERO, + input: Bytes::from(approve_data), + }, + Call { + to: TxKind::Call(options.escrow_contract), + value: U256::ZERO, + input: Bytes::from(open_data), + }, + ]; + + let valid_before = { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + Some(now + VALID_BEFORE_SECS) + }; + + let tx = mpp::client::tempo::charge::tx_builder::build_tempo_tx( + mpp::client::tempo::charge::tx_builder::TempoTxOptions { + calls, + chain_id: options.chain_id, + fee_token: options.currency, + nonce: 0, + nonce_key: EXPIRING_NONCE_KEY, + gas_limit: SESSION_OPEN_GAS_LIMIT, + max_fee_per_gas: MAX_FEE_PER_GAS, + max_priority_fee_per_gas: MAX_PRIORITY_FEE_PER_GAS, + fee_payer: options.fee_payer, + valid_before, + key_authorization: (!*self.key_provisioned.lock().unwrap()) + .then(|| self.signing_mode.key_authorization().cloned()) + .flatten(), + }, + ); + + let signed_tx = sign_and_encode_async(tx, &self.signer, &self.signing_mode).await?; + + let voucher = sign_voucher( + &self.signer, + channel_id, + options.initial_amount, + options.escrow_contract, + options.chain_id, + ) + .await?; + + let entry = ChannelEntry { + channel_id, + salt, + cumulative_amount: options.initial_amount, + escrow_contract: options.escrow_contract, + chain_id: options.chain_id, + opened: true, + }; + + let signed_tx_hex = alloy_primitives::hex::encode_prefixed(&signed_tx); + let voucher_sig_hex = alloy_primitives::hex::encode_prefixed(&voucher); + + Ok(( + entry, + SessionCredentialPayload::Open { + payload_type: "transaction".to_string(), + channel_id: channel_id.to_string(), + transaction: signed_tx_hex, + authorized_signer: Some(format!("{authorized_signer}")), + cumulative_amount: options.initial_amount.to_string(), + signature: voucher_sig_hex, + }, + )) + } + + async fn create_topup_tx( + &self, + entry: &ChannelEntry, + additional_deposit: u128, + currency: Address, + fee_payer: bool, + ) -> Result { + use alloy_sol_types::SolCall as _; + + alloy_sol_types::sol! { + interface ITIP20 { + function approve(address spender, uint256 amount) external returns (bool); + } + interface IEscrow { + function topUp(bytes32 channelId, uint256 additionalDeposit) external; + } + } + + let approve_data = + ITIP20::approveCall::new((entry.escrow_contract, U256::from(additional_deposit))) + .abi_encode(); + let topup_data = + IEscrow::topUpCall::new((entry.channel_id, U256::from(additional_deposit))) + .abi_encode(); + + let calls = vec![ + Call { + to: TxKind::Call(currency), + value: U256::ZERO, + input: Bytes::from(approve_data), + }, + Call { + to: TxKind::Call(entry.escrow_contract), + value: U256::ZERO, + input: Bytes::from(topup_data), + }, + ]; + + let valid_before = { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + Some(now + VALID_BEFORE_SECS) + }; + + let tx = mpp::client::tempo::charge::tx_builder::build_tempo_tx( + mpp::client::tempo::charge::tx_builder::TempoTxOptions { + calls, + chain_id: entry.chain_id, + fee_token: currency, + nonce: 0, + nonce_key: EXPIRING_NONCE_KEY, + gas_limit: SESSION_OPEN_GAS_LIMIT, + max_fee_per_gas: MAX_FEE_PER_GAS, + max_priority_fee_per_gas: MAX_PRIORITY_FEE_PER_GAS, + fee_payer, + valid_before, + key_authorization: None, + }, + ); + + let signed_tx = sign_and_encode_async(tx, &self.signer, &self.signing_mode).await?; + + Ok(SessionCredentialPayload::TopUp { + payload_type: "transaction".to_string(), + channel_id: entry.channel_id.to_string(), + transaction: alloy_primitives::hex::encode_prefixed(&signed_tx), + additional_deposit: additional_deposit.to_string(), + }) + } +} + +impl SessionProvider { + /// Handle a charge intent by building and signing a TIP-20 transfer transaction. + async fn pay_charge( + &self, + challenge: &PaymentChallenge, + ) -> Result { + use mpp::client::tempo::charge::{SignOptions, TempoCharge}; + + let charge = TempoCharge::from_challenge(challenge)?; + + // Strip key_authorization from the signing mode when the key is already + // provisioned on-chain. Otherwise the payment tx includes a redundant + // key provisioning call that fails with "access key already exists". + let signing_mode = if *self.key_provisioned.lock().unwrap() { + match &self.signing_mode { + TempoSigningMode::Keychain { wallet, version, .. } => TempoSigningMode::Keychain { + wallet: *wallet, + key_authorization: None, + version: *version, + }, + other => other.clone(), + } + } else { + self.signing_mode.clone() + }; + + let options = SignOptions { signing_mode: Some(signing_mode), ..Default::default() }; + let signed = charge.sign_with_options(&self.signer, options).await?; + Ok(signed.into_credential()) + } +} + +impl PaymentProvider for SessionProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + async fn pay(&self, challenge: &PaymentChallenge) -> Result { + if challenge.intent.as_str() == "charge" { + return self.pay_charge(challenge).await; + } + self.pay_session(challenge).await + } +} + +impl SessionProvider { + async fn pay_session( + &self, + challenge: &PaymentChallenge, + ) -> Result { + let session_req: SessionRequest = challenge.request.decode().map_err(|e| { + MppError::InvalidConfig(format!("failed to decode session request: {e}")) + })?; + + let chain_id = resolve_chain_id(challenge); + let escrow_contract = resolve_escrow(challenge, chain_id, None)?; + let payee: Address = session_req + .recipient + .as_deref() + .ok_or_else(|| { + MppError::InvalidConfig("session challenge missing recipient".to_string()) + })? + .parse() + .map_err(|_e| MppError::InvalidConfig("invalid recipient address".to_string()))?; + let currency: Address = session_req + .currency + .parse() + .map_err(|_e| MppError::InvalidConfig("invalid currency address".to_string()))?; + let amount: u128 = session_req.parse_amount()?; + + let payer = self.signing_mode.from_address(self.signer.address()); + + let key = Self::channel_key( + &self.origin, + &payer, + self.authorized_signer, + &payee, + ¤cy, + &escrow_contract, + chain_id, + ); + + let voucher_info = { + let mut channels = self.channels.lock().unwrap(); + if let Some(entry) = channels.get_mut(&key) + && entry.opened + { + let deposit = self + .persisted + .lock() + .unwrap() + .get(&key) + .and_then(|p| p.deposit.parse::().ok()) + .unwrap_or(u128::MAX); + + if entry.cumulative_amount + amount > deposit { + Some(Err((entry.clone(), deposit))) + } else { + // Clone without incrementing — only commit after + // create_voucher_payload succeeds. + Some(Ok(entry.clone())) + } + } else { + None + } + }; + + if let Some(result) = voucher_info { + match result { + Err((entry, deposit)) => { + let additional = + self.resolve_deposit(session_req.suggested_deposit.as_deref())?; + tracing::debug!( + cumulative = entry.cumulative_amount, + amount, + deposit, + additional, + "channel deposit exhausted, topping up" + ); + + let payload = self + .create_topup_tx(&entry, additional, currency, session_req.fee_payer()) + .await?; + + // Update in-memory state but defer persistence until server confirms. + let old_deposit = { + let mut persisted = self.persisted.lock().unwrap(); + if let Some(p) = persisted.get_mut(&key) { + let old = p.deposit.clone(); + let old_val: u128 = old.parse().unwrap_or(0); + p.deposit = (old_val + additional).to_string(); + old + } else { + "0".to_string() + } + }; + *self.pending.lock().unwrap() = + Some(PendingAction::TopUp { key: key.clone(), old_deposit }); + + return Ok(build_credential(challenge, payload, chain_id, payer)); + } + Ok(entry) => { + let old_cumulative = entry.cumulative_amount; + let new_cumulative = old_cumulative + amount; + let payload = create_voucher_payload( + &self.signer, + entry.channel_id, + new_cumulative, + escrow_contract, + chain_id, + ) + .await?; + + // Payload succeeded — now commit the cumulative increment. + { + let mut channels = self.channels.lock().unwrap(); + if let Some(e) = channels.get_mut(&key) { + e.cumulative_amount = new_cumulative; + } + } + + // Update in-memory persisted state but never write to disk + // here — flush_pending() handles persistence after server + // confirms acceptance. + let updated_entry = ChannelEntry { cumulative_amount: new_cumulative, ..entry }; + let mut persisted = self.persisted.lock().unwrap(); + persist::upsert_channel_in_memory(&mut persisted, &key, &updated_entry); + drop(persisted); + + // Track the voucher so we can roll back cumulative_amount + // if the server rejects. + if self.pending.lock().unwrap().is_none() { + *self.pending.lock().unwrap() = + Some(PendingAction::Voucher { key, old_cumulative }); + } + + return Ok(build_credential(challenge, payload, chain_id, payer)); + } + } + } + + // No existing channel — open with expiring nonces + let deposit = self.resolve_deposit(session_req.suggested_deposit.as_deref())?; + + let (entry, payload) = self + .create_open_tx( + payer, + OpenPayloadOptions { + authorized_signer: self.authorized_signer, + escrow_contract, + payee, + currency, + deposit, + initial_amount: amount, + chain_id, + fee_payer: session_req.fee_payer(), + }, + ) + .await?; + + // Update in-memory state but defer disk persistence until server confirms. + self.channels.lock().unwrap().insert(key.clone(), entry.clone()); + let authorized_signer = self.authorized_signer.unwrap_or(payer); + self.persisted.lock().unwrap().insert( + key.clone(), + persist::from_channel_entry( + &entry, + deposit, + &self.origin, + &payer, + &payee, + ¤cy, + &authorized_signer, + ), + ); + *self.pending.lock().unwrap() = Some(PendingAction::Open { key }); + Ok(build_credential(challenge, payload, chain_id, payer)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mpp::client::tempo::signing::KeychainVersion; + use tempo_primitives::transaction::{ + KeyAuthorization, PrimitiveSignature, SignatureType, SignedKeyAuthorization, + }; + + /// Create a dummy `SignedKeyAuthorization` for tests. + fn test_key_authorization() -> SignedKeyAuthorization { + SignedKeyAuthorization { + authorization: KeyAuthorization::unrestricted( + 4217, + SignatureType::Secp256k1, + Address::ZERO, + ), + signature: PrimitiveSignature::from_bytes(&[0u8; 65]).expect("valid dummy signature"), + } + } + + fn strip_key_auth_if_provisioned( + mode: &TempoSigningMode, + provisioned: bool, + ) -> TempoSigningMode { + if provisioned { + match mode { + TempoSigningMode::Keychain { wallet, version, .. } => TempoSigningMode::Keychain { + wallet: *wallet, + key_authorization: None, + version: *version, + }, + other => other.clone(), + } + } else { + mode.clone() + } + } + + /// Generate a unique origin URL per test to avoid shared state collisions. + fn unique_origin() -> String { + format!("https://rpc-{}.example.com", alloy_primitives::B256::random()) + } + + #[test] + fn test_key_provisioned_default_is_true() { + let signer = mpp::PrivateKeySigner::random(); + let provider = SessionProvider::new(signer, unique_origin()); + assert!(*provider.key_provisioned.lock().unwrap()); + } + + #[test] + fn test_set_key_provisioned() { + let signer = mpp::PrivateKeySigner::random(); + let provider = SessionProvider::new(signer, unique_origin()); + provider.set_key_provisioned(false); + assert!(!*provider.key_provisioned.lock().unwrap()); + provider.set_key_provisioned(true); + assert!(*provider.key_provisioned.lock().unwrap()); + } + + #[test] + fn test_pay_charge_strips_key_auth_when_provisioned() { + let signer = mpp::PrivateKeySigner::random(); + let wallet = Address::repeat_byte(0xAA); + let signing_mode = TempoSigningMode::Keychain { + wallet, + key_authorization: Some(Box::new(test_key_authorization())), + version: KeychainVersion::V2, + }; + let provider = + SessionProvider::new(signer, unique_origin()).with_signing_mode(signing_mode); + + let provisioned = *provider.key_provisioned.lock().unwrap(); + let result_mode = strip_key_auth_if_provisioned(&provider.signing_mode, provisioned); + + assert!( + result_mode.key_authorization().is_none(), + "key_authorization should be stripped when key is provisioned" + ); + } + + #[test] + fn test_pay_charge_keeps_key_auth_when_not_provisioned() { + let signer = mpp::PrivateKeySigner::random(); + let wallet = Address::repeat_byte(0xAA); + let signing_mode = TempoSigningMode::Keychain { + wallet, + key_authorization: Some(Box::new(test_key_authorization())), + version: KeychainVersion::V2, + }; + let provider = + SessionProvider::new(signer, unique_origin()).with_signing_mode(signing_mode); + + provider.set_key_provisioned(false); + + let provisioned = *provider.key_provisioned.lock().unwrap(); + let result_mode = strip_key_auth_if_provisioned(&provider.signing_mode, provisioned); + + assert!( + result_mode.key_authorization().is_some(), + "key_authorization should be preserved when key is NOT provisioned" + ); + } + + #[test] + fn test_pay_charge_direct_mode_unaffected() { + let signer = mpp::PrivateKeySigner::random(); + let provider = SessionProvider::new(signer, unique_origin()) + .with_signing_mode(TempoSigningMode::Direct); + + let provisioned = *provider.key_provisioned.lock().unwrap(); + let result_mode = strip_key_auth_if_provisioned(&provider.signing_mode, provisioned); + + assert!( + matches!(result_mode, TempoSigningMode::Direct), + "Direct mode should pass through unchanged" + ); + } + + /// Verify that a payment serialization lock (mirroring `lock_pay()` in + /// `LazySessionProvider`) prevents concurrent voucher increments from + /// producing duplicate cumulative amounts. + #[tokio::test] + async fn test_concurrent_voucher_increments_are_unique() { + let channels: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + let key = "test-channel".to_string(); + channels.lock().unwrap().insert( + key.clone(), + ChannelEntry { + channel_id: Default::default(), + salt: Default::default(), + cumulative_amount: 0, + escrow_contract: Address::ZERO, + chain_id: 42431, + opened: true, + }, + ); + + // Mirrors the `pay_lock` tokio::sync::Mutex used in LazySessionProvider + // to serialize the 402 → pay → retry cycle. + let pay_lock = std::sync::Arc::new(tokio::sync::Mutex::new(())); + let amount: u128 = 1000; + let num_tasks = 20; + let results: Arc>> = Arc::new(Mutex::new(Vec::new())); + + let mut handles = Vec::new(); + for _ in 0..num_tasks { + let channels = channels.clone(); + let key = key.clone(); + let results = results.clone(); + let pay_lock = pay_lock.clone(); + handles.push(tokio::spawn(async move { + let _guard = pay_lock.lock().await; + let cumulative = { + let mut ch = channels.lock().unwrap(); + let entry = ch.get_mut(&key).unwrap(); + entry.cumulative_amount += amount; + entry.cumulative_amount + }; + results.lock().unwrap().push(cumulative); + })); + } + + for h in handles { + h.await.unwrap(); + } + + let mut amounts = results.lock().unwrap().clone(); + amounts.sort(); + amounts.dedup(); + assert_eq!( + amounts.len(), + num_tasks, + "each concurrent increment should produce a unique cumulative_amount" + ); + assert_eq!( + *amounts.last().unwrap(), + amount * num_tasks as u128, + "final cumulative_amount should equal amount × num_tasks" + ); + } +} diff --git a/crates/common/src/provider/mpp/transport.rs b/crates/common/src/provider/mpp/transport.rs new file mode 100644 index 0000000000000..9e3b16dedd59e --- /dev/null +++ b/crates/common/src/provider/mpp/transport.rs @@ -0,0 +1,1740 @@ +//! MPP (Machine Payments Protocol) HTTP transport. +//! +//! Wraps a standard reqwest HTTP transport with automatic 402 Payment Required +//! handling via the MPP protocol. When the RPC endpoint returns a 402 response, +//! this transport automatically pays the challenge and retries the request. + +use alloy_chains::Chain; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_transport::{TransportError, TransportErrorKind, TransportFut, TransportResult}; +use mpp::{ + client::PaymentProvider, + protocol::core::{ + AUTHORIZATION_HEADER, WWW_AUTHENTICATE_HEADER, format_authorization, + parse_www_authenticate_all, + }, +}; +use reqwest::{StatusCode, header::HeaderMap}; +use std::{ + collections::HashMap, + env, fmt, io, + io::IsTerminal, + process::{Command, Stdio}, + sync::{ + Arc, LazyLock, Mutex, + atomic::{AtomicBool, Ordering}, + }, + task, + time::Duration, +}; +use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard}; +use tower::Service; +use tracing::{Instrument, debug, debug_span, trace}; +use url::Url; + +use super::{ + keys::{DiscoverOptions, discover_mpp_config}, + session::SessionProvider, +}; + +/// Default deposit amount for new channels (in base units). +const DEFAULT_DEPOSIT: u128 = 100_000; + +/// Timeout for MPP retry requests (open/topUp may wait for on-chain settlement). +const MPP_RETRY_TIMEOUT: Duration = Duration::from_secs(120); + +/// Resolve the deposit amount from `MPP_DEPOSIT` env var or the default. +fn default_deposit() -> u128 { + env::var("MPP_DEPOSIT").ok().and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_DEPOSIT) +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct FundingContext { + wallet_address: Option, + token: Option, + chain_id: Option, +} + +impl FundingContext { + fn token_line(&self) -> String { + self.token + .as_ref() + .map(|token| format!("Requested payment token: {token}\n\n")) + .unwrap_or_default() + } + + fn network(&self) -> Option { + self.chain_id.filter(|chain| chain.is_tempo()).map(|chain| chain.to_string()) + } +} + +fn format_http_diagnostics(headers: &HeaderMap) -> String { + const DIAGNOSTIC_HEADERS: &[&str] = &["x-request-id", "cf-ray", "server", "report-to", "nel"]; + + let pairs: Vec = DIAGNOSTIC_HEADERS + .iter() + .filter_map(|name| { + headers.get(*name).and_then(|value| value.to_str().ok().map(|v| (*name, v))) + }) + .map(|(name, value)| format!("{name}: {value}")) + .collect(); + + if pairs.is_empty() { + String::new() + } else { + format!("\n\nHTTP diagnostics:\n{}", pairs.join("\n")) + } +} + +fn tempo_wallet_fund_help(ctx: &FundingContext) -> String { + let mut command = "tempo wallet fund".to_string(); + if let Some(address) = ctx.wallet_address { + command.push_str(&format!(" --address {address}")); + } + if let Some(network) = ctx.network() { + command.push_str(&format!(" --network {network}")); + } + + let mut no_browser = command.clone(); + no_browser.push_str(" --no-browser"); + + format!( + "\n\nTempo wallet payment could not be funded for this paid RPC request.\n\n{}\ + Fund the wallet, then rerun the command:\n {command}\n\n\ + If this CLI is running on a remote or headless host, use:\n {no_browser}", + ctx.token_line() + ) +} + +/// Decide whether the interactive `tempo wallet fund` flow may be launched. +/// +/// Policy (library-safe): +/// - never run inside CI +/// - never run unless both stdin and stderr are real terminals +/// - `FOUNDRY_MPP_NO_AUTO_FUND` is honored as an opt-out; it must not bypass CI/TTY guards in +/// shared transport code that may be embedded inside long-running RPC daemons. +fn interactive_tempo_fund_allowed( + no_auto_fund: Option<&str>, + in_ci: bool, + stdin_is_terminal: bool, + stderr_is_terminal: bool, +) -> bool { + if no_auto_fund.is_some_and(|v| { + !(v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off")) + }) { + return false; + } + + if in_ci { + return false; + } + + stdin_is_terminal && stderr_is_terminal +} + +fn can_run_interactive_tempo_fund() -> bool { + if cfg!(test) { + return false; + } + + interactive_tempo_fund_allowed( + std::env::var("FOUNDRY_MPP_NO_AUTO_FUND").ok().as_deref(), + std::env::var_os("CI").is_some(), + std::io::stdin().is_terminal(), + std::io::stderr().is_terminal(), + ) +} + +fn tempo_bin() -> String { + std::env::var("TEMPO_BIN").unwrap_or_else(|_| "tempo".to_string()) +} + +async fn run_interactive_tempo_fund(ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + + let tempo = tempo_bin(); + let mut args = vec!["wallet".to_string(), "fund".to_string()]; + if let Some(address) = ctx.wallet_address { + args.push("--address".to_string()); + args.push(address.to_string()); + } + if let Some(network) = ctx.network() { + args.push("--network".to_string()); + args.push(network); + } + + tracing::warn!( + token = ?ctx.token, + chain_id = ?ctx.chain_id, + "MPP payment could not be funded; opening `tempo wallet fund`" + ); + + let status = tokio::task::spawn_blocking(move || { + Command::new(tempo) + .args(args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + }) + .await + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to join tempo wallet fund process: {e}" + ))) + })? + .map_err(|e| { + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to run `tempo wallet fund`: {e}{}", + tempo_wallet_fund_help(ctx) + ))) + })?; + + if status.success() { + Ok(true) + } else { + Err(TransportErrorKind::custom(std::io::Error::other(format!( + "`tempo wallet fund` exited with status {status}{}", + tempo_wallet_fund_help(ctx) + )))) + } +} + +/// Single-attempt guard around [`run_interactive_tempo_fund`]. +/// +/// Ensures that for one logical request we launch `tempo wallet fund` at most +/// once, regardless of how many recovery paths (`do_request`, `pay_and_retry`, +/// `handle_response_or_retry_after_fund`, ...) attempt it. +async fn maybe_auto_fund(used: &AtomicBool, ctx: &FundingContext) -> TransportResult { + if !can_run_interactive_tempo_fund() { + return Ok(false); + } + if used.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Ok(false); + } + run_interactive_tempo_fund(ctx).await +} + +/// Returns true iff a 402 response carries a structured insufficient-balance +/// problem (RFC 9457 `PaymentErrorDetails`). +/// +/// We deliberately do **not** match on free-text body content or on generic +/// `verification-failed` problem types, as those have many non-funding causes +/// (bad signature, replay, expired challenge, clock skew, key provisioning, +/// malformed auth, ...). +fn should_suggest_tempo_fund(status: StatusCode, body: &[u8]) -> bool { + if status != StatusCode::PAYMENT_REQUIRED { + return false; + } + let Ok(problem) = serde_json::from_slice::(body) else { + return false; + }; + problem.problem_type.ends_with("/insufficient-balance") +} + +fn format_mpp_payment_failure( + error: impl fmt::Display, + ctx: &FundingContext, + suggest_fund: bool, +) -> String { + let message = error.to_string(); + if suggest_fund { + format!("MPP payment failed: {message}{}", tempo_wallet_fund_help(ctx)) + } else { + format!("MPP payment failed: {message}") + } +} + +/// Process-wide payment serialization locks, keyed by origin URL. +/// +/// Created eagerly so the lock exists before the first provider init, +/// preventing concurrent first-402 races. +static GLOBAL_PAY_LOCKS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +/// Production transport: lazily discovers MPP keys from the Tempo wallet on +/// first 402 response. +pub type LazyMppHttpTransport = MppHttpTransport; + +/// A payment provider that lazily initializes a [`SessionProvider`] from the +/// Tempo wallet configuration on first use. +#[derive(Clone, Debug)] +pub struct LazySessionProvider { + inner: Arc>>, + /// Eagerly-created, process-wide payment serialization lock for this origin. + pay_lock: Arc>, + origin: String, +} + +impl LazySessionProvider { + pub(super) fn new(origin: String) -> Self { + let pay_lock = GLOBAL_PAY_LOCKS + .lock() + .unwrap() + .entry(origin.clone()) + .or_insert_with(|| Arc::new(AsyncMutex::new(()))) + .clone(); + Self { inner: Arc::new(Mutex::new(None)), pay_lock, origin } + } + + fn set_key_provisioned(&self, provisioned: bool) { + if let Some(p) = self.inner.lock().unwrap().as_ref() { + p.set_key_provisioned(provisioned); + } + } + + fn clear_channels(&self) { + if let Some(p) = self.inner.lock().unwrap().as_ref() { + p.clear_channels(); + } + } + + pub(super) fn flush_pending(&self) { + if let Some(p) = self.inner.lock().unwrap().as_ref() { + p.flush_pending(); + } + } + + pub(super) fn rollback_pending(&self) { + if let Some(p) = self.inner.lock().unwrap().as_ref() { + p.rollback_pending(); + } + } + + fn commit_topup_and_track_voucher(&self) { + if let Some(p) = self.inner.lock().unwrap().as_ref() { + p.commit_topup_and_track_voucher(); + } + } + + /// Drop the cached `SessionProvider` so the next `get_or_init` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry, so a long-lived transport doesn't keep paying with + /// the superseded key. + fn invalidate(&self) { + *self.inner.lock().unwrap() = None; + } + + pub(super) fn get_or_init(&self, opts: DiscoverOptions) -> TransportResult { + let mut guard = self.inner.lock().unwrap(); + if let Some(ref provider) = *guard { + return Ok(provider.clone()); + } + + let config = discover_mpp_config(opts).ok_or_else(|| { + TransportErrorKind::custom(io::Error::other( + "RPC endpoint returned HTTP 402 Payment Required. \ + This endpoint requires payment via the Machine Payments Protocol (MPP).\n\n\ + Authorize an access key against your Tempo wallet:\n\ + \n cast tempo login\ + \n\nIn headless environments, pass `--no-browser` to print the authorization \ + URL instead of launching a browser:\n\ + \n cast tempo login --no-browser\ + \n\nSee https://docs.tempo.xyz for more information.", + )) + })?; + + let signer: mpp::PrivateKeySigner = config.key.parse().map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!("invalid MPP key: {e}"))) + })?; + + let signing_mode = if let Some(wallet) = config.wallet_address { + let key_authorization = config + .key_authorization + .as_ref() + .map(|hex_str| { + crate::tempo::decode_key_authorization(hex_str).map(Box::new).map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "invalid MPP key_authorization: {e}" + ))) + }) + }) + .transpose()?; + + mpp::client::tempo::signing::TempoSigningMode::Keychain { + wallet, + key_authorization, + version: mpp::client::tempo::signing::KeychainVersion::V2, + } + } else { + mpp::client::tempo::signing::TempoSigningMode::Direct + }; + + let mut provider = SessionProvider::new(signer, self.origin.clone()) + .with_signing_mode(signing_mode) + .with_default_deposit(default_deposit()) + .with_key_filters(config.chain_id, config.currencies); + + if let Some(addr) = config.key_address { + provider = provider.with_authorized_signer(addr); + } + + *guard = Some(provider.clone()); + Ok(provider) + } +} + +/// HTTP transport with automatic MPP (Machine Payments Protocol) 402 handling. +/// +/// Generic over the payment provider `P`. Works as a normal HTTP transport until +/// a 402 Payment Required response is received, then delegates payment to `P`. +#[derive(Clone, Debug)] +pub struct MppHttpTransport

{ + client: reqwest::Client, + url: Url, + provider: P, +} + +impl MppHttpTransport { + /// Create a new lazy MPP transport that discovers keys on first 402. + /// + /// Uses the provided `client` for all requests. Per-request timeouts are + /// extended on retry requests that involve on-chain settlement (channel + /// open/topUp). + pub fn lazy(client: reqwest::Client, url: Url) -> Self { + let origin = url.to_string(); + Self { client, url, provider: LazySessionProvider::new(origin) } + } +} + +impl

MppHttpTransport

{ + /// Create a new MPP transport with an explicit payment provider. + pub const fn new(client: reqwest::Client, url: Url, provider: P) -> Self { + Self { client, url, provider } + } + + /// Returns a reference to the underlying reqwest client. + pub const fn client(&self) -> &reqwest::Client { + &self.client + } +} + +#[allow(private_bounds)] +impl MppHttpTransport

+where + P::Provider: Send + Sync + 'static, +{ + async fn do_request(self, req: RequestPacket) -> TransportResult { + // Per-request guard: launch `tempo wallet fund` at most once for one + // logical request, regardless of how many recovery paths attempt it. + let auto_fund_used = AtomicBool::new(false); + self.do_request_inner(req, &auto_fund_used).await + } + + async fn do_request_inner( + self, + req: RequestPacket, + auto_fund_used: &AtomicBool, + ) -> TransportResult { + let body = serde_json::to_vec(&req).map_err(TransportErrorKind::custom)?; + let headers = req.headers(); + + let resp = self + .client + .post(self.url.clone()) + .headers(headers.clone()) + .header("content-type", "application/json") + .body(body.clone()) + .send() + .await + .map_err(TransportErrorKind::custom)?; + + if resp.status() != StatusCode::PAYMENT_REQUIRED { + return Self::handle_response(resp).await; + } + + // Serialize the entire 402 → pay → retry → response cycle. + // This prevents concurrent requests from opening duplicate channels + // or producing colliding expiring-nonce transactions. The lock is + // held until the retry response is fully handled. + let _pay_guard = self.provider.lock_pay().await; + + // No local key for any offered challenge → run device-code flow, + // invalidate the cached provider, and fetch a fresh 402 (the original + // may have expired during the browser/passkey flow). + let (resolved, challenge) = + if let Some(chain_id) = tempo_chain_needing_auth(&self.url, &resp) { + debug!(chain_id, "launching wallet.tempo authorization"); + let cfg = crate::tempo::EnsureAccessKeyConfig::from_env(chain_id); + crate::tempo::ensure_access_key(cfg).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "tempo access key authorization failed: {e}" + ))) + })?; + self.provider.invalidate_cached_provider(); + self.fetch_fresh_challenge(&headers, &body).await? + } else { + Self::select_challenge(&resp, &self.provider)? + }; + let funding_ctx = self.provider.funding_context(&challenge); + + debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP 402 challenge, paying"); + + let credential = match resolved.pay(&challenge).await { + Ok(credential) => credential, + Err(e) => { + // Only the explicit `InsufficientBalance` variant is treated as + // a fundable error. Any other failure must surface unchanged so + // we don't mask payment/protocol issues behind a fund prompt. + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + resolved.pay(&challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; + + let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(std::io::Error::other(format!( + "failed to format MPP credential: {e}" + ))) + })?; + + // Use a longer per-request timeout because the server may need to + // settle an on-chain transaction (channel open/topUp) before responding. + let retry_resp = self + .client + .post(self.url.clone()) + .timeout(MPP_RETRY_TIMEOUT) + .headers(headers.clone()) + .header("content-type", "application/json") + .header(AUTHORIZATION_HEADER, &auth_header) + .body(body.clone()) + .send() + .await + .map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(e) + })?; + + // 204 No Content → topUp accepted, re-pay with voucher + if retry_resp.status() == StatusCode::NO_CONTENT { + debug!("MPP topUp accepted (204), retrying with voucher"); + + // Top-up is confirmed — commit the deposit increase and start + // tracking the follow-up voucher cumulative bump separately. + self.provider.commit_topup_and_track_voucher(); + + let resolved = self.provider.resolve()?; + let voucher_resp = + self.pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used).await?; + + // Route the voucher response through the funding-aware handler so + // a final 402 here also gets the fund retry / contextual help. + let result = self + .handle_response_or_retry_after_fund( + voucher_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + // 410 Gone → channel stale + if retry_resp.status() == StatusCode::GONE { + debug!("MPP channel not found (410), clearing stale local state"); + self.provider.rollback_pending(); + self.provider.clear_channels(); + + return Err(TransportErrorKind::custom(io::Error::other( + "MPP channel not found on server (410 Gone). \ + The server may have restarted or the channel was closed externally.\n\ + Local channel state has been cleared. Re-run to open a new channel.", + ))); + } + + // Retry 402 → handle specific recoverable errors before giving up. + if retry_resp.status() == StatusCode::PAYMENT_REQUIRED { + let diagnostics = format_http_diagnostics(retry_resp.headers()); + let retry_body = retry_resp.bytes().await.map_err(TransportErrorKind::custom)?; + let retry_text = String::from_utf8_lossy(&retry_body); + + // Parse RFC 9457 Problem Details if present. The `type` URI is the + // structured error code; the `detail` string provides context. + let problem: Option = + serde_json::from_slice(&retry_body).ok(); + let problem_type = problem.as_ref().map(|p| p.problem_type.as_str()).unwrap_or(""); + let detail = problem.as_ref().map(|p| p.detail.as_str()).unwrap_or(""); + + // Stale voucher: another provider instance (or a previous process) + // already used a higher cumulative_amount. Re-pay with a fresh + // voucher whose amount will be strictly greater. + let is_stale_voucher = problem_type.ends_with("/stale-voucher") + || detail.contains("cumulativeAmount must be strictly greater"); + if is_stale_voucher { + debug!("MPP voucher stale, retrying with fresh voucher"); + let resolved = self.provider.resolve()?; + if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) { + let final_resp = self + .pay_and_retry(&challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + } + + // Retry with key_authorization when the error explicitly indicates + // the access key is not provisioned on-chain, or when verification + // failed and the key appears provisioned (first-time provisioning + // where key_auth was stripped but not yet provisioned on-chain). + // + // We fetch a fresh challenge because the server may have consumed + // the original challenge ID on first use. + let needs_key_provisioning = problem_type.ends_with("/key-not-provisioned") + || detail.contains("access key does not exist") + || detail.contains("key is not provisioned"); + + let needs_verification_retry = (problem_type.ends_with("/verification-failed") + || detail.contains("verification-failed")) + && self.provider.is_key_provisioned(); + + if needs_key_provisioning || needs_verification_retry { + debug!( + problem_type, + "MPP 402 key not provisioned/verification-failed, retrying with key_authorization" + ); + self.provider.set_key_provisioned(false); + self.provider.rollback_pending(); + + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; + + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + self.provider.rollback_pending(); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) + && maybe_auto_fund(auto_fund_used, &funding_ctx).await? + { + let (resolved, fresh_challenge) = + self.fetch_fresh_challenge(&headers, &body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, &headers, &body, auto_fund_used) + .await?; + + let result = self + .handle_response_or_retry_after_fund( + final_resp, + &headers, + &body, + &funding_ctx, + auto_fund_used, + ) + .await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + return result; + } + + let mut error_text = format!("{retry_text}{diagnostics}"); + if should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &retry_body) { + error_text.push_str(&tempo_wallet_fund_help(&funding_ctx)); + } + return Err(TransportErrorKind::http_error( + StatusCode::PAYMENT_REQUIRED.as_u16(), + error_text, + )); + } + + let result = Self::handle_response(retry_resp).await; + if result.is_ok() { + self.provider.set_key_provisioned(true); + self.provider.flush_pending(); + } else { + self.provider.rollback_pending(); + } + result + } + + /// Pay a challenge and send the authenticated retry request. + async fn pay_and_retry( + &self, + challenge: &mpp::protocol::core::PaymentChallenge, + provider: &P::Provider, + headers: &reqwest::header::HeaderMap, + body: &[u8], + auto_fund_used: &AtomicBool, + ) -> TransportResult { + let funding_ctx = self.provider.funding_context(challenge); + let credential = match provider.pay(challenge).await { + Ok(credential) => credential, + Err(e) => { + self.provider.rollback_pending(); + let is_insufficient = matches!(e, mpp::MppError::InsufficientBalance(_)); + if is_insufficient && maybe_auto_fund(auto_fund_used, &funding_ctx).await? { + provider.pay(challenge).await.map_err(|e2| { + let suggest = matches!(e2, mpp::MppError::InsufficientBalance(_)); + TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e2, &funding_ctx, suggest), + )) + })? + } else { + return Err(TransportErrorKind::custom(std::io::Error::other( + format_mpp_payment_failure(e, &funding_ctx, is_insufficient), + ))); + } + } + }; + + let auth_header = format_authorization(&credential).map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(io::Error::other(format!( + "failed to format MPP credential: {e}" + ))) + })?; + + self.client + .post(self.url.clone()) + .timeout(MPP_RETRY_TIMEOUT) + .headers(headers.clone()) + .header("content-type", "application/json") + .header(AUTHORIZATION_HEADER, auth_header) + .body(body.to_vec()) + .send() + .await + .map_err(|e| { + self.provider.rollback_pending(); + TransportErrorKind::custom(e) + }) + } + + async fn handle_response_or_retry_after_fund( + &self, + resp: reqwest::Response, + headers: &reqwest::header::HeaderMap, + body: &[u8], + funding_ctx: &FundingContext, + auto_fund_used: &AtomicBool, + ) -> TransportResult { + if resp.status() != StatusCode::PAYMENT_REQUIRED { + return Self::handle_response_with_funding(resp, Some(funding_ctx)).await; + } + + let diagnostics = format_http_diagnostics(resp.headers()); + let status = resp.status(); + let resp_body = resp.bytes().await.map_err(TransportErrorKind::custom)?; + + if should_suggest_tempo_fund(status, &resp_body) + && maybe_auto_fund(auto_fund_used, funding_ctx).await? + { + self.provider.rollback_pending(); + + let (resolved, fresh_challenge) = self.fetch_fresh_challenge(headers, body).await?; + let final_resp = self + .pay_and_retry(&fresh_challenge, &resolved, headers, body, auto_fund_used) + .await?; + return Self::handle_response_with_funding(final_resp, Some(funding_ctx)).await; + } + + let mut error_text = format!("{}{diagnostics}", String::from_utf8_lossy(&resp_body)); + if should_suggest_tempo_fund(status, &resp_body) { + error_text.push_str(&tempo_wallet_fund_help(funding_ctx)); + } + Err(TransportErrorKind::http_error(status.as_u16(), error_text)) + } + + /// Fetch a fresh 402 challenge from the server (unauthenticated request). + /// + /// Returns `Ok(Some((provider, challenge)))` if the server returns a 402 + /// with a matching challenge. Returns `Ok(None)` with the response handled + /// if the server returns a non-402 status. Errors on network or parse failures. + async fn fetch_fresh_challenge( + &self, + headers: &reqwest::header::HeaderMap, + body: &[u8], + ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { + let fresh_resp = self + .client + .post(self.url.clone()) + .timeout(MPP_RETRY_TIMEOUT) + .headers(headers.clone()) + .header("content-type", "application/json") + .body(body.to_vec()) + .send() + .await + .map_err(TransportErrorKind::custom)?; + + if fresh_resp.status() != StatusCode::PAYMENT_REQUIRED { + // Non-402 → return whatever the server sent (could be success or error). + let result = Self::handle_response(fresh_resp).await; + return Err(result.err().unwrap_or_else(|| { + TransportErrorKind::custom(io::Error::other( + "unexpected success on unauthenticated fresh probe", + )) + })); + } + + Self::select_challenge(&fresh_resp, &self.provider) + } + + /// Parse `WWW-Authenticate` challenges from a 402 response and resolve + /// the first one matching a locally configured key (chain + currency). + fn select_challenge( + resp: &reqwest::Response, + provider: &P, + ) -> TransportResult<(P::Provider, mpp::protocol::core::PaymentChallenge)> { + let challenges = parse_challenges(resp); + if challenges.is_empty() && resp.headers().get(WWW_AUTHENTICATE_HEADER).is_none() { + return Err(TransportErrorKind::custom(io::Error::other(format!( + "402 response missing WWW-Authenticate header{}", + format_http_diagnostics(resp.headers()) + )))); + } + + let mut last_resolve_err: Option = None; + let resolved_pair = challenges.iter().find_map(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + match provider.resolve_for(DiscoverOptions { chain_id, currency }) { + Ok(p) => p.supports(c.method.as_str(), c.intent.as_str()).then_some((p, c.clone())), + Err(e) => { + last_resolve_err = Some(e); + None + } + } + }); + + resolved_pair.ok_or_else(|| { + if let Some(err) = last_resolve_err { + return err; + } + let offered: Vec<_> = + challenges.iter().map(|c| format!("{}.{}", c.method, c.intent)).collect(); + TransportErrorKind::custom(io::Error::other(format!( + "no supported MPP challenge; server offered [{}]", + offered.join(", "), + ))) + }) + } + + async fn handle_response(resp: reqwest::Response) -> TransportResult { + Self::handle_response_with_funding(resp, None).await + } + + /// Like [`Self::handle_response`] but, when an unsuccessful 402 looks like a + /// fundable error, appends actionable `tempo wallet fund` help that uses + /// the per-request `FundingContext` (so the suggested command includes + /// `--address` and `--network` when known). + async fn handle_response_with_funding( + resp: reqwest::Response, + funding_ctx: Option<&FundingContext>, + ) -> TransportResult { + let status = resp.status(); + debug!(%status, "received response from MPP transport"); + let diagnostics = format_http_diagnostics(resp.headers()); + + let body = resp.bytes().await.map_err(TransportErrorKind::custom)?; + + if tracing::enabled!(tracing::Level::TRACE) { + trace!(body = %String::from_utf8_lossy(&body), "response body"); + } else { + debug!(bytes = body.len(), "retrieved response body"); + } + + if !status.is_success() { + let mut body_text = format!("{}{diagnostics}", String::from_utf8_lossy(&body)); + if should_suggest_tempo_fund(status, &body) { + let default_ctx; + let ctx = match funding_ctx { + Some(c) => c, + None => { + default_ctx = FundingContext::default(); + &default_ctx + } + }; + body_text.push_str(&tempo_wallet_fund_help(ctx)); + } + return Err(TransportErrorKind::http_error(status.as_u16(), body_text)); + } + + serde_json::from_slice(&body) + .map_err(|err| TransportError::deser_err(err, String::from_utf8_lossy(&body))) + } +} + +/// Returns `Some(chain_id)` when a 402 response should trigger the +/// `wallet.tempo.xyz` device-code authorization flow. +/// +/// Conditions: known Tempo endpoint, interactive (TTY, not `CI`), and no +/// offered Tempo challenge resolves against a local key on `(chain, currency)`. +/// The picked chain matches the first unresolved challenge — same iteration +/// order [`MppHttpTransport::select_challenge`] uses. +fn tempo_chain_needing_auth(url: &Url, resp: &reqwest::Response) -> Option { + if !io::stderr().is_terminal() || env::var_os("CI").is_some() { + return None; + } + pick_chain_needing_auth(url, &parse_challenges(resp)) +} + +/// Extract all parseable MPP challenges from a 402 response's `WWW-Authenticate` headers. +fn parse_challenges(resp: &reqwest::Response) -> Vec { + let values: Vec<&str> = resp + .headers() + .get_all(WWW_AUTHENTICATE_HEADER) + .iter() + .filter_map(|v| v.to_str().ok()) + .collect(); + parse_www_authenticate_all(values).into_iter().filter_map(|r| r.ok()).collect() +} + +/// Inner logic of [`tempo_chain_needing_auth`], factored out for testing. +fn pick_chain_needing_auth( + url: &Url, + challenges: &[mpp::protocol::core::PaymentChallenge], +) -> Option { + if !crate::tempo::is_known_tempo_endpoint(url) { + return None; + } + + let tempo_challenges: Vec<_> = + challenges.iter().filter(|c| c.method.as_str() == "tempo").collect(); + + // If any challenge already resolves with a local key, no auth needed. + let any_resolvable = tempo_challenges.iter().any(|c| { + let (chain_id, currency) = extract_challenge_chain_and_currency(c); + let currency = currency.and_then(|s| s.parse().ok()); + super::keys::discover_mpp_config(super::keys::DiscoverOptions { chain_id, currency }) + .is_some() + }); + if any_resolvable { + return None; + } + + tempo_challenges.iter().find_map(|c| extract_challenge_chain_and_currency(c).0) +} + +/// Extract `(chainId, currency)` from a parsed MPP challenge. +pub(super) fn extract_challenge_chain_and_currency( + c: &mpp::protocol::core::PaymentChallenge, +) -> (Option, Option) { + if c.method.as_str() == "tempo" { + let val = c.request.decode_value().ok(); + let chain_id = val.as_ref().and_then(|v| v.get("methodDetails")?.get("chainId")?.as_u64()); + let currency = val.as_ref().and_then(|v| v.get("currency")?.as_str().map(String::from)); + (chain_id, currency) + } else { + (None, None) + } +} + +/// Trait for resolving a concrete `PaymentProvider` from a potentially lazy wrapper. +pub(crate) trait ResolveProvider { + type Provider: PaymentProvider; + fn resolve(&self) -> TransportResult { + self.resolve_for(Default::default()) + } + fn resolve_for(&self, opts: DiscoverOptions) -> TransportResult; + fn set_key_provisioned(&self, _provisioned: bool) {} + fn is_key_provisioned(&self) -> bool { + true + } + fn clear_channels(&self) {} + fn flush_pending(&self) {} + fn rollback_pending(&self) {} + fn commit_topup_and_track_voucher(&self) {} + /// Drop any cached payment provider so the next `resolve_for` re-runs + /// discovery. Called after the device-code flow writes a fresh + /// `keys.toml` entry. + fn invalidate_cached_provider(&self) {} + fn funding_wallet_address(&self) -> Option { + None + } + fn funding_chain_id(&self) -> Option { + None + } + fn funding_context(&self, challenge: &mpp::protocol::core::PaymentChallenge) -> FundingContext { + let (challenge_chain_id, token) = extract_challenge_chain_and_currency(challenge); + FundingContext { + wallet_address: self.funding_wallet_address(), + token, + chain_id: challenge_chain_id.or_else(|| self.funding_chain_id()).map(Chain::from_id), + } + } + /// Acquire the payment serialization lock. The returned guard must be held + /// across the entire 402 → pay → retry → response cycle to prevent + /// concurrent channel opens and colliding expiring-nonce transactions. + fn lock_pay(&self) -> impl Future>> + Send { + async { None } + } +} + +impl ResolveProvider for P { + type Provider = P; + fn resolve_for(&self, _opts: DiscoverOptions) -> TransportResult

{ + Ok(self.clone()) + } +} + +impl ResolveProvider for LazySessionProvider { + type Provider = SessionProvider; + fn resolve_for(&self, opts: DiscoverOptions) -> TransportResult { + let provider = self.get_or_init(opts.clone())?; + // After the first init, get_or_init returns the cached provider + // regardless of opts. Re-check that the provider's key is compatible + // with this challenge's chain/currency. + if !provider.matches_challenge(opts.chain_id, opts.currency) { + return Err(TransportErrorKind::custom(io::Error::other( + "cached provider does not match challenge chain/currency", + ))); + } + Ok(provider) + } + fn set_key_provisioned(&self, provisioned: bool) { + Self::set_key_provisioned(self, provisioned) + } + fn is_key_provisioned(&self) -> bool { + self.inner.lock().unwrap().as_ref().is_none_or(|p| p.is_key_provisioned()) + } + fn clear_channels(&self) { + Self::clear_channels(self) + } + fn flush_pending(&self) { + Self::flush_pending(self) + } + fn rollback_pending(&self) { + Self::rollback_pending(self) + } + fn commit_topup_and_track_voucher(&self) { + Self::commit_topup_and_track_voucher(self) + } + fn invalidate_cached_provider(&self) { + Self::invalidate(self) + } + fn funding_wallet_address(&self) -> Option { + self.inner.lock().unwrap().as_ref().map(|p| p.funding_wallet_address()) + } + fn funding_chain_id(&self) -> Option { + self.inner.lock().unwrap().as_ref().and_then(|p| p.key_chain_id()) + } + fn lock_pay(&self) -> impl Future>> + Send { + let lock = self.pay_lock.clone(); + async move { Some(lock.lock_owned().await) } + } +} + +impl

fmt::Display for MppHttpTransport

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MppHttpTransport({})", self.url) + } +} + +#[allow(private_bounds)] +impl Service + for MppHttpTransport

+where + P::Provider: Send + Sync + 'static, +{ + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + let this = self.clone(); + let span = debug_span!("MppHttpTransport", url = %this.url); + Box::pin(this.do_request(req).instrument(span.or_current())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::provider::runtime_transport::RuntimeTransportBuilder; + use alloy_json_rpc::{Id, Request, RequestMeta}; + use axum::{ + extract::State, http::StatusCode as AxumStatusCode, response::IntoResponse, routing::post, + }; + use mpp::{ + MppError, + protocol::core::{ + Base64UrlJson, IntentName, MethodName, PaymentChallenge, PaymentCredential, + format_www_authenticate, parse_authorization, + }, + }; + + #[derive(Clone, Debug)] + struct MockPaymentProvider; + + impl PaymentProvider for MockPaymentProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + fn pay( + &self, + challenge: &PaymentChallenge, + ) -> impl Future> + Send { + let echo = challenge.to_echo(); + async move { + Ok(PaymentCredential::with_source( + echo, + "test-source".to_string(), + serde_json::json!({"action": "voucher", "channelId": "0xtest", "cumulativeAmount": "1000", "signature": "0xtest"}), + )) + } + } + } + + #[derive(Clone, Debug)] + struct InsufficientBalanceProvider; + + impl PaymentProvider for InsufficientBalanceProvider { + fn supports(&self, method: &str, intent: &str) -> bool { + method == "tempo" && (intent == "session" || intent == "charge") + } + + async fn pay(&self, _challenge: &PaymentChallenge) -> Result { + Err(MppError::InsufficientBalance(Some( + "wallet has 0 pathUSD but needs 100000".to_string(), + ))) + } + } + + fn test_challenge() -> (PaymentChallenge, String) { + let request = Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1000", + "currency": "0x20c0", + "recipient": "0xpayee", + "methodDetails": { + "chainId": 42431 + } + })) + .unwrap(); + + let challenge = PaymentChallenge { + id: "test-id-42".to_string(), + realm: "test-realm".to_string(), + method: MethodName::new("tempo"), + intent: IntentName::new("session"), + request, + expires: None, + description: None, + digest: None, + opaque: None, + }; + + let www_auth = format_www_authenticate(&challenge).unwrap(); + (challenge, www_auth) + } + + fn test_request() -> RequestPacket { + let req: Request = Request { + meta: RequestMeta::new("eth_blockNumber".into(), Id::Number(1)), + params: serde_json::Value::Array(vec![]), + }; + RequestPacket::Single(req.serialize().unwrap()) + } + + async fn spawn_server(app: axum::Router) -> (String, tokio::task::JoinHandle<()>) { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + (format!("http://{addr}"), handle) + } + + #[tokio::test] + async fn test_mpp_transport_no_402() { + let app = axum::Router::new().route( + "/", + post(|| async { + axum::Json(serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x123" + })) + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let resp = tower::Service::call(&mut transport, test_request()).await.unwrap(); + match resp { + ResponsePacket::Single(r) => assert!(r.is_success()), + _ => panic!("expected single response"), + } + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_402_then_success() { + let (_, www_auth) = test_challenge(); + let state = AppState { www_auth }; + + #[derive(Clone)] + struct AppState { + www_auth: String, + } + + let app = + axum::Router::new() + .route( + "/", + post( + |State(state): State, + req: axum::http::Request| async move { + if let Some(auth) = req.headers().get("authorization") { + let auth_str = auth.to_str().unwrap(); + let credential = parse_authorization(auth_str).unwrap(); + assert_eq!(credential.challenge.id, "test-id-42"); + assert_eq!(credential.challenge.method.as_str(), "tempo"); + assert!(credential.source.is_some()); + + ( + AxumStatusCode::OK, + axum::Json(serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0xvalidated" + })), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", state.www_auth)], + "Payment Required", + ) + .into_response() + } + }, + ), + ) + .with_state(state); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let resp = tower::Service::call(&mut transport, test_request()).await.unwrap(); + match resp { + ResponsePacket::Single(r) => assert!(r.is_success()), + _ => panic!("expected single response"), + } + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_402_missing_www_authenticate() { + let app = axum::Router::new() + .route("/", post(|| async { (AxumStatusCode::PAYMENT_REQUIRED, "pay up") })); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + assert!( + err.to_string().contains("WWW-Authenticate"), + "expected WWW-Authenticate error, got: {err}" + ); + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_payment_failure_suggests_tempo_wallet_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move || { + let www_auth = www_auth.clone(); + async move { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required", + ) + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + InsufficientBalanceProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + #[tokio::test] + async fn test_mpp_transport_retry_402_insufficient_balance_suggests_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail( + "Insufficient pathUSD balance: have 0, need 100000", + ), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("InsufficientBalanceError"), "got: {msg}"); + assert!(msg.contains("Tempo wallet payment could not be funded"), "got: {msg}"); + assert!(msg.contains("tempo wallet fund"), "got: {msg}"); + assert!(msg.contains("--no-browser"), "got: {msg}"); + assert!(msg.contains("Requested payment token: 0x20c0"), "got: {msg}"); + + handle.abort(); + } + + /// Generic `verification-failed` has many non-funding causes (bad signature, + /// replay, expired challenge, clock skew, ...). The transport must surface + /// the original error verbatim and must NOT add a "fund your wallet" hint. + #[tokio::test] + async fn test_mpp_transport_final_402_verification_failed_does_not_suggest_fund() { + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move |req: axum::http::Request| { + let www_auth = www_auth.clone(); + async move { + if req.headers().get("authorization").is_some() { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("content-type", "application/problem+json")], + serde_json::to_string( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(), + ) + .into_response() + } else { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required".to_string(), + ) + .into_response() + } + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + let mut transport = MppHttpTransport::new( + reqwest::Client::new(), + Url::parse(&base_url).unwrap(), + MockPaymentProvider, + ); + + let err = tower::Service::call(&mut transport, test_request()).await.unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Verification Failed"), "got: {msg}"); + assert!( + !msg.contains("Tempo wallet payment could not be funded"), + "verification-failed must not be classified as fundable; got: {msg}" + ); + + handle.abort(); + } + + // --- Classifier unit tests -------------------------------------------- + + #[test] + fn classifier_only_triggers_on_explicit_insufficient_balance_problem() { + // explicit insufficient-balance → true + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_title("InsufficientBalanceError") + .with_detail("Insufficient pathUSD balance"), + ) + .unwrap(); + assert!(should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_verification_failed() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::core("verification-failed") + .with_title("Verification Failed") + .with_detail("Payment verification failed."), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, &body)); + } + + #[test] + fn classifier_does_not_trigger_on_unrelated_text_with_balance_words() { + // Free-text 402 body that just happens to mention the word "balance" + // must NOT trigger the fund suggestion (no structured problem details). + let body = + b"402 Payment Required: server could not balance ledger entries; insufficient inputs."; + assert!(!should_suggest_tempo_fund(StatusCode::PAYMENT_REQUIRED, body)); + } + + #[test] + fn classifier_does_not_trigger_outside_402() { + let body = serde_json::to_vec( + &mpp::error::PaymentErrorDetails::session("insufficient-balance") + .with_detail("Insufficient balance"), + ) + .unwrap(); + assert!(!should_suggest_tempo_fund(StatusCode::INTERNAL_SERVER_ERROR, &body)); + assert!(!should_suggest_tempo_fund(StatusCode::OK, &body)); + } + + #[test] + fn fund_help_includes_address_and_network_for_known_chain() { + let ctx = FundingContext { + wallet_address: Some("0x000000000000000000000000000000000000dEaD".parse().unwrap()), + token: Some("0x20c0".to_string()), + chain_id: Some(Chain::from_id(42431)), + }; + let help = tempo_wallet_fund_help(&ctx); + assert!(help.contains("--address 0x"), "missing --address: {help}"); + assert!(help.contains("--network tempo-moderato"), "missing --network: {help}"); + assert!(help.contains("--no-browser"), "missing --no-browser: {help}"); + assert!(help.contains("Requested payment token: 0x20c0"), "missing token: {help}"); + + let mainnet = FundingContext { chain_id: Some(Chain::from_id(4217)), ..ctx }; + let help2 = tempo_wallet_fund_help(&mainnet); + assert!(help2.contains("--network tempo"), "missing tempo network: {help2}"); + } + + #[test] + fn auto_fund_policy_blocks_in_ci_and_non_tty() { + assert!(!interactive_tempo_fund_allowed(Some("1"), true, true, true), "must not run in CI"); + assert!( + interactive_tempo_fund_allowed(Some("0"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=0 must not disable" + ); + assert!( + interactive_tempo_fund_allowed(Some("false"), false, true, true), + "FOUNDRY_MPP_NO_AUTO_FUND=false must not disable" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, false, true), + "stdin must be a terminal" + ); + assert!( + !interactive_tempo_fund_allowed(None, false, true, false), + "stderr must be a terminal" + ); + assert!(!interactive_tempo_fund_allowed(Some("1"), false, true, true)); + assert!(!interactive_tempo_fund_allowed(Some("true"), false, true, true)); + assert!(interactive_tempo_fund_allowed(None, false, true, true)); + } + + #[tokio::test] + async fn test_plain_http_402_shows_mpp_setup_instructions() { + let _g = crate::tempo::test_env_mutex().lock().await; + let (_, www_auth) = test_challenge(); + + let app = axum::Router::new().route( + "/", + post(move || { + let www_auth = www_auth.clone(); + async move { + ( + AxumStatusCode::PAYMENT_REQUIRED, + [("www-authenticate", www_auth)], + "Payment Required", + ) + } + }), + ); + + let (base_url, handle) = spawn_server(app).await; + + unsafe { + std::env::set_var("TEMPO_HOME", "/nonexistent/path"); + std::env::remove_var("TEMPO_PRIVATE_KEY"); + } + + let transport = RuntimeTransportBuilder::new(Url::parse(&base_url).unwrap()).build(); + let err = transport.request(test_request()).await.unwrap_err(); + let msg = err.to_string(); + + assert!( + msg.contains("402 Payment Required") || msg.contains("no supported MPP challenge"), + "expected MPP setup instructions or 'no supported MPP challenge' in error, got: {msg}" + ); + + handle.abort(); + unsafe { std::env::remove_var("TEMPO_HOME") }; + } + + #[test] + fn test_session_provider_supports_charge_and_session() { + let signer = mpp::PrivateKeySigner::random(); + let provider = + super::super::session::SessionProvider::new(signer, "https://rpc.example.com".into()); + + assert!(provider.supports("tempo", "session")); + assert!(provider.supports("tempo", "charge")); + assert!(!provider.supports("stripe", "charge")); + assert!(!provider.supports("tempo", "subscribe")); + } + + #[tokio::test] + async fn test_session_provider_pay_charge_parses_challenge() { + let signer = mpp::PrivateKeySigner::random(); + let provider = + super::super::session::SessionProvider::new(signer, "https://rpc.example.com".into()); + + // Valid charge challenge — pay_charge wires through to TempoCharge, + // which will fail at gas estimation (no RPC), but confirms the path is connected. + let (challenge, _) = test_challenge(); + let err = provider.pay(&challenge).await.unwrap_err(); + // Should fail deeper than "not supported" — proves charge dispatch works + assert!( + !err.to_string().contains("not supported"), + "expected charge path to be wired up, got: {err}" + ); + } + + /// `invalidate_cached_provider` clears the cache so the next + /// `get_or_init` re-runs discovery — the path `do_request` takes after + /// `ensure_access_key` writes a fresh `keys.toml` entry. + #[tokio::test] + async fn lazy_session_provider_invalidate_clears_cache() { + let _g = crate::tempo::test_env_mutex().lock().await; + // TEMPO_PRIVATE_KEY lets discovery succeed without a keys.toml. + let key_hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + unsafe { + std::env::set_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV, key_hex); + std::env::remove_var(crate::tempo::TEMPO_HOME_ENV); + } + + let lazy = LazySessionProvider::new("https://rpc.example.com".into()); + let _ = lazy.get_or_init(Default::default()).expect("discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected provider to be cached"); + + ResolveProvider::invalidate_cached_provider(&lazy); + assert!(lazy.inner.lock().unwrap().is_none(), "expected cache to be cleared"); + + let _ = lazy.get_or_init(Default::default()).expect("re-discovery succeeds"); + assert!(lazy.inner.lock().unwrap().is_some(), "expected re-init to repopulate cache"); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV) }; + } + + #[test] + fn challenge_chain_and_currency_extraction() { + let extract = |headers: Vec<&str>| -> Vec<(Option, Option)> { + let challenges: Vec<_> = + parse_www_authenticate_all(headers).into_iter().filter_map(|r| r.ok()).collect(); + challenges.iter().map(extract_challenge_chain_and_currency).collect() + }; + + let b64 = |v: serde_json::Value| -> String { + Base64UrlJson::from_value(&v).unwrap().raw().to_string() + }; + + // Tempo challenge with chainId + currency + let tempo_header = format!( + r#"Payment id="abc", realm="api", method="tempo", intent="charge", request="{}""#, + b64( + serde_json::json!({"amount":"1000","currency":"0x20c0","methodDetails":{"chainId":42431},"recipient":"0xabc"}) + ) + ); + assert_eq!(extract(vec![&tempo_header]), vec![(Some(42431), Some("0x20c0".into()))]); + + // Non-tempo challenge → (None, None) + let stripe_header = format!( + r#"Payment id="xyz", realm="api", method="stripe", intent="charge", request="{}""#, + b64(serde_json::json!({"amount":"100"})) + ); + assert_eq!(extract(vec![&stripe_header]), vec![(None, None)]); + + // Tempo challenge without methodDetails → chainId None, currency present + let no_details = format!( + r#"Payment id="def", realm="api", method="tempo", intent="charge", request="{}""#, + b64(serde_json::json!({"amount":"1000","currency":"0x20c0","recipient":"0xabc"})) + ); + assert_eq!(extract(vec![&no_details]), vec![(None, Some("0x20c0".into()))]); + } + + /// Auth must trigger when a key matches the chain but not the currency. + #[test] + fn pick_chain_needing_auth_currency_aware() { + let _g = crate::tempo::test_env_mutex().blocking_lock(); + let dir = tempfile::tempdir().unwrap(); + let wallet = dir.path().join("wallet"); + std::fs::create_dir_all(&wallet).unwrap(); + std::fs::write( + wallet.join("keys.toml"), + r#" +[[keys]] +wallet_type = "passkey" +wallet_address = "0x0000000000000000000000000000000000000001" +key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +chain_id = 4217 + +[[keys.limits]] +currency = "0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +limit = "1000" +"#, + ) + .unwrap(); + unsafe { + std::env::set_var(crate::tempo::TEMPO_HOME_ENV, dir.path()); + std::env::remove_var(crate::tempo::TEMPO_PRIVATE_KEY_ENV); + } + + let url = Url::parse("https://rpc.mpp.tempo.xyz").unwrap(); + let mk = |currency: &str| -> PaymentChallenge { + PaymentChallenge { + id: "x".into(), + realm: "api".into(), + method: MethodName::new("tempo"), + intent: IntentName::new("charge"), + request: Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1", + "currency": currency, + "recipient": "0xabc", + "methodDetails": { "chainId": 4217 } + })) + .unwrap(), + expires: None, + description: None, + digest: None, + opaque: None, + } + }; + + // Currency mismatch → auth needed. + let mismatched = mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + assert_eq!(pick_chain_needing_auth(&url, &[mismatched]), Some(4217)); + + // Currency match → no auth. + let matched = mk("0x20c0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert_eq!(pick_chain_needing_auth(&url, &[matched]), None); + + // Non-Tempo host → never triggers, even without a key. + let stripe_url = Url::parse("https://api.stripe.com").unwrap(); + assert_eq!( + pick_chain_needing_auth( + &stripe_url, + &[mk("0x20c0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")] + ), + None, + ); + + unsafe { std::env::remove_var(crate::tempo::TEMPO_HOME_ENV) }; + } +} diff --git a/crates/common/src/provider/mpp/ws.rs b/crates/common/src/provider/mpp/ws.rs new file mode 100644 index 0000000000000..69aef7d4f4cbc --- /dev/null +++ b/crates/common/src/provider/mpp/ws.rs @@ -0,0 +1,496 @@ +//! MPP WebSocket transport. +//! +//! Implements [`PubSubConnect`] with automatic MPP 402 challenge/credential +//! handshake at WebSocket connect time. Non-MPP servers are handled via a +//! timeout-based fallback: if no challenge frame arrives within a short window +//! after connecting, we assume the server is a plain JSON-RPC WebSocket. + +use alloy_json_rpc::PubSubItem; +use alloy_pubsub::{ConnectionHandle, PubSubConnect}; +use alloy_transport::{Authorization, TransportErrorKind, TransportResult, utils::guess_local_url}; +use alloy_transport_ws::WsBackend; +use futures::{SinkExt, StreamExt}; +use mpp::{ + client::{ + PaymentProvider, + ws::{WsClientMessage, WsServerMessage}, + }, + protocol::core::{PaymentChallenge, format_authorization}, +}; +use rustls::crypto::{CryptoProvider, aws_lc_rs}; +use std::{io, time::Duration}; +use tokio::time::timeout; +use tokio_tungstenite::{ + MaybeTlsStream, WebSocketStream, connect_async, + tungstenite::{ + Message, + client::IntoClientRequest, + http::{HeaderValue, header::AUTHORIZATION}, + }, +}; +use tracing::debug; + +use super::{ + keys::DiscoverOptions, + transport::{LazySessionProvider, extract_challenge_chain_and_currency}, +}; + +type TungsteniteStream = WebSocketStream>; + +/// Timeout for waiting on an MPP challenge frame from the server. +/// +/// If no challenge arrives within this window after the WebSocket upgrade +/// completes, we assume it's a plain (non-MPP) JSON-RPC WebSocket. +const MPP_CHALLENGE_TIMEOUT: Duration = Duration::from_millis(500); + +/// Keepalive ping interval (matches alloy-transport-ws default). +const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(10); + +/// WebSocket connector with automatic MPP payment at connect time. +/// +/// Implements [`PubSubConnect`] so it can be used as a drop-in replacement for +/// alloy's `WsConnect`. On connect, it: +/// +/// 1. Opens a WebSocket connection. +/// 2. Waits briefly for an MPP challenge frame from the server. +/// 3. If a challenge arrives, performs the payment handshake using [`LazySessionProvider`] (same +/// payment logic as the HTTP transport). +/// 4. Spawns a backend loop that bridges the authenticated WebSocket to the alloy +/// [`PubSubFrontend`](alloy_pubsub::PubSubFrontend). +/// +/// Non-MPP servers work transparently — the timeout expires and the backend +/// proceeds with normal JSON-RPC message forwarding. +#[derive(Clone, Debug)] +pub struct MppWsConnect { + url: String, + auth: Option, + provider: LazySessionProvider, +} + +impl MppWsConnect { + /// Create a new MPP WebSocket connector for the given URL. + pub fn new(url: String) -> Self { + let origin = url.clone(); + let auth = + url::Url::parse(&url).ok().and_then(|parsed| Authorization::extract_from_url(&parsed)); + Self { url, auth, provider: LazySessionProvider::new(origin) } + } + + /// Set the authorization header (e.g. JWT bearer token). + pub fn with_auth(mut self, auth: Authorization) -> Self { + self.auth = Some(auth); + self + } + + /// Attempt the MPP handshake on an already-connected WebSocket. + /// + /// Waits up to [`MPP_CHALLENGE_TIMEOUT`] for a challenge frame. If one + /// arrives, pays it and sends the credential back. Returns any buffered + /// non-challenge messages that arrived during the handshake window. + async fn try_mpp_handshake( + socket: &mut TungsteniteStream, + provider: &LazySessionProvider, + ) -> TransportResult> { + let mut buffered_messages: Vec = Vec::new(); + + // Wait briefly for a challenge frame. + let first_msg = timeout(MPP_CHALLENGE_TIMEOUT, socket.next()).await; + + let challenge_frame = match first_msg { + Err(_) => { + // Timeout — not an MPP server. + debug!("no MPP challenge within timeout, treating as plain WS"); + return Ok(buffered_messages); + } + Ok(None) => { + return Err(TransportErrorKind::custom(io::Error::other( + "WebSocket closed before any message", + ))); + } + Ok(Some(Err(e))) => { + return Err(TransportErrorKind::custom(e)); + } + Ok(Some(Ok(msg))) => msg, + }; + + let text = match &challenge_frame { + Message::Text(t) => t.as_ref(), + Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => { + return Ok(buffered_messages); + } + Message::Binary(_) | Message::Close(_) => { + return Err(TransportErrorKind::custom(io::Error::other( + "unexpected binary/close frame on WS connect", + ))); + } + }; + + // Try to parse as an MPP server message. + let server_msg: WsServerMessage = match serde_json::from_str(text) { + Ok(m) => m, + Err(_) => { + // Not an MPP message — buffer it for the backend. + buffered_messages.push(text.to_owned()); + return Ok(buffered_messages); + } + }; + + let challenge: PaymentChallenge = match server_msg { + WsServerMessage::Challenge { ref challenge, .. } => { + serde_json::from_value(challenge.clone()).map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "failed to parse MPP WS challenge: {e}" + ))) + })? + } + _ => { + // Non-challenge MPP message — buffer it. + buffered_messages.push(text.to_owned()); + return Ok(buffered_messages); + } + }; + + debug!(id = %challenge.id, method = %challenge.method, intent = %challenge.intent, "received MPP WS challenge, paying"); + + // Resolve the payment provider (lazily discovers keys on first use). + let (chain_id, currency) = extract_challenge_chain_and_currency(&challenge); + let currency = currency.and_then(|s| s.parse().ok()); + let session = + provider.get_or_init(DiscoverOptions { chain_id, currency }).map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "MPP key discovery failed: {e}" + ))) + })?; + + let credential = session.pay(&challenge).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!("MPP WS payment failed: {e}"))) + })?; + + // Everything after pay() must rollback on failure — wrap so we can't + // miss an error path. + let result = async { + let auth_header = format_authorization(&credential).map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "failed to format MPP credential: {e}" + ))) + })?; + + // Send credential as a WS message. + let cred_msg = WsClientMessage::Credential { credential: auth_header }; + let cred_text = serde_json::to_string(&cred_msg).map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "failed to serialize credential message: {e}" + ))) + })?; + + socket.send(Message::Text(cred_text.into())).await.map_err(|e| { + TransportErrorKind::custom(io::Error::other(format!( + "failed to send MPP credential: {e}" + ))) + })?; + + // Wait for server acknowledgement (receipt or data). + let ack = timeout(Duration::from_secs(30), socket.next()).await.map_err(|_| { + TransportErrorKind::custom(io::Error::other( + "timeout waiting for MPP server acknowledgement", + )) + })?; + + match ack { + None => { + return Err(TransportErrorKind::custom(io::Error::other( + "WebSocket closed after sending credential", + ))); + } + Some(Err(e)) => return Err(TransportErrorKind::custom(e)), + Some(Ok(Message::Text(t))) => { + if let Ok(msg) = serde_json::from_str::(t.as_ref()) { + match msg { + WsServerMessage::Receipt { .. } => { + debug!("MPP WS handshake complete (receipt received)"); + } + WsServerMessage::Error { error } => { + return Err(TransportErrorKind::custom(io::Error::other(format!( + "MPP WS server error: {error}" + )))); + } + _ => { + buffered_messages.push(t.to_string()); + } + } + } else { + buffered_messages.push(t.to_string()); + } + } + Some(Ok(Message::Close(_))) => { + return Err(TransportErrorKind::custom(io::Error::other( + "WebSocket closed after sending credential", + ))); + } + Some(Ok(_)) => {} + } + + Ok(buffered_messages) + } + .await; + + match &result { + Ok(_) => provider.flush_pending(), + Err(_) => provider.rollback_pending(), + } + + result + } +} + +impl PubSubConnect for MppWsConnect { + fn is_local(&self) -> bool { + guess_local_url(&self.url) + } + + async fn connect(&self) -> TransportResult { + let mut request = + self.url.as_str().into_client_request().map_err(TransportErrorKind::custom)?; + + if let Some(ref auth) = self.auth { + let mut auth_value = + HeaderValue::from_str(&auth.to_string()).map_err(TransportErrorKind::custom)?; + auth_value.set_sensitive(true); + request.headers_mut().insert(AUTHORIZATION, auth_value); + } + + // Install the default rustls crypto provider (required by rustls 0.23+). + let _ = CryptoProvider::install_default(aws_lc_rs::default_provider()); + + let (mut socket, _) = connect_async(request).await.map_err(TransportErrorKind::custom)?; + + // Attempt MPP handshake (timeout-based fallback for non-MPP servers). + let buffered = Self::try_mpp_handshake(&mut socket, &self.provider).await?; + + let (handle, interface) = ConnectionHandle::new(); + + // Replay any messages that arrived during the handshake window. + for msg in &buffered { + if let Ok(item) = serde_json::from_str::(msg) { + let _ = interface.send_to_frontend(item); + } + } + + // Reuse alloy's WsBackend for the post-handshake JSON-RPC loop. + WsBackend::from_socket(socket, interface, KEEPALIVE_INTERVAL).spawn(); + + Ok(handle) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_json_rpc::{Id, Request, RequestMeta, RequestPacket, ResponsePacket}; + use alloy_primitives::hex; + use mpp::{ + PrivateKeySigner, + protocol::core::{Base64UrlJson, IntentName, MethodName, parse_authorization}, + }; + use tokio::task::JoinHandle; + use tokio_tungstenite::accept_async; + use tower::Service; + + fn test_challenge() -> PaymentChallenge { + let request = Base64UrlJson::from_value(&serde_json::json!({ + "amount": "1000", + "currency": "0x0000000000000000000000000000000000000000", + "recipient": "0x0000000000000000000000000000000000000001", + "methodDetails": { + "chainId": 42431 + } + })) + .unwrap(); + + PaymentChallenge { + id: "ws-test-id".to_string(), + realm: "test-realm".to_string(), + method: MethodName::new("tempo"), + intent: IntentName::new("session"), + request, + expires: None, + description: None, + digest: None, + opaque: None, + } + } + + /// Spawn a WS server on localhost, returns (ws_url, join_handle). + /// `handler` receives the server-side socket for full control. + async fn spawn_ws_server(handler: F) -> (String, JoinHandle<()>) + where + F: FnOnce(WebSocketStream) -> Fut + Send + 'static, + Fut: std::future::Future + Send, + { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let ws = accept_async(stream).await.unwrap(); + handler(ws).await; + }); + (format!("ws://{addr}"), handle) + } + + /// Plain WS server (no MPP) — connect and send a JSON-RPC request. + #[tokio::test] + async fn test_ws_no_mpp_plain_jsonrpc() { + let (url, server) = spawn_ws_server(|mut ws| async move { + // Wait for a JSON-RPC request, echo a response. + while let Some(Ok(msg)) = ws.next().await { + if let Message::Text(text) = msg { + let req: serde_json::Value = serde_json::from_str(&text).unwrap(); + let id = req.get("id").unwrap().clone(); + let resp = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": "0xabc" + }); + ws.send(Message::Text(resp.to_string().into())).await.unwrap(); + } + } + }) + .await; + + let connector = MppWsConnect::new(url); + let mut frontend = connector.into_service().await.unwrap(); + + let req = Request { + meta: RequestMeta::new("eth_blockNumber".into(), Id::Number(1)), + params: serde_json::Value::Array(vec![]), + }; + let packet = RequestPacket::Single(req.serialize().unwrap()); + let resp = frontend.call(packet).await.unwrap(); + + match resp { + ResponsePacket::Single(r) => assert!(r.is_success()), + _ => panic!("expected single response"), + } + + server.abort(); + } + + /// MPP server sends challenge → client pays → server sends receipt. + #[tokio::test] + async fn test_ws_mpp_challenge_credential_receipt() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; + let challenge = test_challenge(); + let challenge_json = serde_json::to_value(&challenge).unwrap(); + + let (url, server) = spawn_ws_server(move |mut ws| async move { + // Send challenge. + let challenge_msg = serde_json::json!({ + "type": "challenge", + "challenge": challenge_json + }); + ws.send(Message::Text(challenge_msg.to_string().into())).await.unwrap(); + + // Receive credential. + let msg = ws.next().await.unwrap().unwrap(); + let text = match msg { + Message::Text(t) => t, + other => panic!("expected text, got {other:?}"), + }; + let parsed: serde_json::Value = serde_json::from_str(&text).unwrap(); + assert_eq!(parsed["type"], "credential"); + // Verify it's a valid MPP credential. + let cred_str = parsed["credential"].as_str().unwrap(); + let cred = parse_authorization(cred_str).unwrap(); + assert_eq!(cred.challenge.id, "ws-test-id"); + + // Send receipt. + let receipt_msg = serde_json::json!({ + "type": "receipt", + "receipt": { "id": "ws-test-id" } + }); + ws.send(Message::Text(receipt_msg.to_string().into())).await.unwrap(); + + // Now serve JSON-RPC. + while let Some(Ok(msg)) = ws.next().await { + if let Message::Text(text) = msg { + let req: serde_json::Value = serde_json::from_str(&text).unwrap(); + let id = req.get("id").unwrap().clone(); + let resp = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": "0xpaid" + }); + ws.send(Message::Text(resp.to_string().into())).await.unwrap(); + } + } + }) + .await; + + // Set a random private key so LazySessionProvider can initialize. + let signer = PrivateKeySigner::random(); + let key_hex = hex::encode(signer.to_bytes()); + unsafe { std::env::set_var("TEMPO_PRIVATE_KEY", &key_hex) }; + + let connector = MppWsConnect::new(url); + let mut frontend = connector.into_service().await.unwrap(); + + let req = Request { + meta: RequestMeta::new("eth_blockNumber".into(), Id::Number(1)), + params: serde_json::Value::Array(vec![]), + }; + let packet = RequestPacket::Single(req.serialize().unwrap()); + let resp = frontend.call(packet).await.unwrap(); + + match resp { + ResponsePacket::Single(r) => assert!(r.is_success()), + _ => panic!("expected single response"), + } + + unsafe { std::env::remove_var("TEMPO_PRIVATE_KEY") }; + server.abort(); + } + + /// MPP server sends challenge, client pays, server closes → rollback. + #[tokio::test] + async fn test_ws_mpp_rollback_on_post_pay_close() { + // Serialize with other tests that mutate TEMPO_PRIVATE_KEY / TEMPO_HOME. + let _g = crate::tempo::test_env_mutex().lock().await; + let challenge = test_challenge(); + let challenge_json = serde_json::to_value(&challenge).unwrap(); + + let (url, server) = spawn_ws_server(move |mut ws| async move { + // Send challenge. + let challenge_msg = serde_json::json!({ + "type": "challenge", + "challenge": challenge_json + }); + ws.send(Message::Text(challenge_msg.to_string().into())).await.unwrap(); + + // Receive credential (client paid). + let _ = ws.next().await; + + // Close without sending receipt — simulates post-pay failure. + ws.close(None).await.ok(); + }) + .await; + + let signer = PrivateKeySigner::random(); + let key_hex = hex::encode(signer.to_bytes()); + unsafe { std::env::set_var("TEMPO_PRIVATE_KEY", &key_hex) }; + + let connector = MppWsConnect::new(url); + let result = connector.connect().await; + + // Connect must fail — server closed after credential was sent. + assert!(result.is_err(), "expected error when server closes after payment, got Ok"); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("closed") || err.contains("WebSocket"), + "expected close-related error, got: {err}" + ); + + unsafe { std::env::remove_var("TEMPO_PRIVATE_KEY") }; + server.abort(); + } +} diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index f4c5e789dd137..f59a2efa75b8e 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -1,14 +1,17 @@ //! Runtime transport that connects on first request, which can take either of an HTTP, -//! WebSocket, or IPC transport and supports retries based on CUPS logic. +//! WebSocket, or IPC transport. Retries are handled by a client layer (e.g., +//! `RetryBackoffLayer`) when used. -use crate::{DEFAULT_USER_AGENT, REQUEST_TIMEOUT}; +use crate::{ + DEFAULT_USER_AGENT, REQUEST_TIMEOUT, + provider::mpp::{keys::discover_mpp_key, transport::LazyMppHttpTransport, ws::MppWsConnect}, +}; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_pubsub::{PubSubConnect, PubSubFrontend}; -use alloy_rpc_types::engine::{Claims, JwtSecret}; +use alloy_rpc_types_engine::{Claims, JwtSecret}; use alloy_transport::{ Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, }; -use alloy_transport_http::Http; use alloy_transport_ipc::IpcConnect; use alloy_transport_ws::WsConnect; use reqwest::header::{HeaderName, HeaderValue}; @@ -18,12 +21,27 @@ use tokio::sync::RwLock; use tower::Service; use url::Url; +/// Known MPP-enabled RPC host suffixes. +/// +/// Endpoints matching these patterns are always connected via [`MppWsConnect`], +/// regardless of whether local MPP keys have been discovered. +const KNOWN_MPP_HOSTS: &[&str] = &[".mpp.tempo.xyz"]; + +/// Returns `true` if `url` points to a known MPP-enabled RPC service. +fn is_known_mpp_endpoint(url: &Url) -> bool { + url.host_str().is_some_and(|host| KNOWN_MPP_HOSTS.iter().any(|suffix| host.ends_with(suffix))) +} + /// An enum representing the different transports that can be used to connect to a runtime. /// Only meant to be used internally by [RuntimeTransport]. #[derive(Clone, Debug)] pub enum InnerTransport { - /// HTTP transport - Http(Http), + /// HTTP transport with lazy MPP 402 handling. + /// + /// For known Tempo endpoints, the MPP layer additionally runs the + /// `wallet.tempo.xyz` device-code flow on a 402 when no local access key + /// is configured (see [`crate::tempo::ensure_access_key`]). + Http(LazyMppHttpTransport), /// WebSocket transport Ws(PubSubFrontend), /// IPC transport @@ -37,10 +55,6 @@ pub enum RuntimeTransportError { #[error("Internal transport error: {0} with {1}")] TransportError(TransportError, String), - /// Failed to lock the transport - #[error("Failed to lock the transport")] - LockError, - /// Invalid URL scheme #[error("URL scheme is not supported: {0}")] BadScheme(String), @@ -67,8 +81,9 @@ pub enum RuntimeTransportError { /// A runtime transport is a custom [`alloy_transport::Transport`] that only connects when the /// *first* request is made. When the first request is made, it will connect to the runtime using /// either an HTTP WebSocket, or IPC transport depending on the URL used. -/// It also supports retries for rate-limiting and timeout-related errors. -#[derive(Clone, Debug, Error)] +/// Retries for rate-limiting and timeout-related errors are handled by an external +/// client layer (e.g., `RetryBackoffLayer`) when configured. +#[derive(Clone, Debug)] pub struct RuntimeTransport { /// The inner actual transport used. inner: Arc>>, @@ -80,6 +95,10 @@ pub struct RuntimeTransport { jwt: Option, /// The timeout for requests. timeout: std::time::Duration, + /// Whether to accept invalid certificates. + accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection. + no_proxy: bool, } /// A builder for [RuntimeTransport]. @@ -89,12 +108,21 @@ pub struct RuntimeTransportBuilder { headers: Vec, jwt: Option, timeout: std::time::Duration, + accept_invalid_certs: bool, + no_proxy: bool, } impl RuntimeTransportBuilder { /// Create a new builder with the given URL. - pub fn new(url: Url) -> Self { - Self { url, headers: vec![], jwt: None, timeout: REQUEST_TIMEOUT } + pub const fn new(url: Url) -> Self { + Self { + url, + headers: vec![], + jwt: None, + timeout: REQUEST_TIMEOUT, + accept_invalid_certs: false, + no_proxy: false, + } } /// Set the URL for the transport. @@ -110,11 +138,26 @@ impl RuntimeTransportBuilder { } /// Set the timeout for the transport. - pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { + pub const fn with_timeout(mut self, timeout: std::time::Duration) -> Self { self.timeout = timeout; self } + /// Set whether to accept invalid certificates. + pub const fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self { + self.accept_invalid_certs = accept_invalid_certs; + self + } + + /// Set whether to disable automatic proxy detection. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub const fn no_proxy(mut self, no_proxy: bool) -> Self { + self.no_proxy = no_proxy; + self + } + /// Builds the [RuntimeTransport] and returns it in a disconnected state. /// The runtime transport will then connect when the first request happens. pub fn build(self) -> RuntimeTransport { @@ -124,6 +167,8 @@ impl RuntimeTransportBuilder { headers: self.headers, jwt: self.jwt, timeout: self.timeout, + accept_invalid_certs: self.accept_invalid_certs, + no_proxy: self.no_proxy, } } } @@ -149,7 +194,15 @@ impl RuntimeTransport { pub fn reqwest_client(&self) -> Result { let mut client_builder = reqwest::Client::builder() .timeout(self.timeout) - .tls_built_in_root_certs(self.url.scheme() == "https"); + .danger_accept_invalid_certs(self.accept_invalid_certs); + + // Disable automatic proxy detection if requested. This helps in sandboxed environments + // (e.g., Cursor IDE sandbox, macOS App Sandbox) where system proxy detection via + // SCDynamicStore causes crashes. See: https://github.com/foundry-rs/foundry/issues/12733 + if self.no_proxy { + client_builder = client_builder.no_proxy(); + } + let mut headers = reqwest::header::HeaderMap::new(); // If there's a JWT, add it to the headers if we can decode it. @@ -166,7 +219,7 @@ impl RuntimeTransport { // Add any custom headers. for header in &self.headers { - let make_err = || RuntimeTransportError::BadHeader(header.to_string()); + let make_err = || RuntimeTransportError::BadHeader(header.clone()); let (key, val) = header.split_once(':').ok_or_else(make_err)?; @@ -184,28 +237,58 @@ impl RuntimeTransport { ); } + // If MPP_API_KEY is set, attach it as x-api-key for gated MPP proxies. + // Does not override an explicit x-api-key header from the user. + if !headers.contains_key(HeaderName::from_static("x-api-key")) + && let Ok(api_key) = std::env::var("MPP_API_KEY") + { + let api_key = api_key.trim(); + if !api_key.is_empty() { + let mut value = HeaderValue::from_str(api_key) + .map_err(|_| RuntimeTransportError::BadHeader("MPP_API_KEY".to_string()))?; + value.set_sensitive(true); + headers.insert(HeaderName::from_static("x-api-key"), value); + } + } + client_builder = client_builder.default_headers(headers); Ok(client_builder.build()?) } - /// Connects to an HTTP [alloy_transport_http::Http] transport. + /// Connects to an HTTP transport with lazy MPP 402 handling. fn connect_http(&self) -> Result { let client = self.reqwest_client()?; - Ok(InnerTransport::Http(Http::with_client(client, self.url.clone()))) + Ok(InnerTransport::Http(LazyMppHttpTransport::lazy(client, self.url.clone()))) } /// Connects to a WS transport. + /// + /// Uses [`MppWsConnect`] (which performs the MPP challenge/credential + /// handshake at connect time) when the endpoint is a known MPP service or + /// when MPP keys are discoverable. Otherwise falls back to alloy's plain + /// [`WsConnect`] with zero overhead. async fn connect_ws(&self) -> Result { let auth = self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); - let mut ws = WsConnect::new(self.url.to_string()); - if let Some(auth) = auth { - ws = ws.with_auth(auth); + + let service = if is_known_mpp_endpoint(&self.url) && discover_mpp_key().is_some() { + let mut ws = MppWsConnect::new(self.url.to_string()); + if let Some(auth) = auth { + ws = ws.with_auth(auth); + } + ws.into_service() + .await + .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))? + } else { + let mut ws = WsConnect::new(self.url.to_string()); + if let Some(auth) = auth { + ws = ws.with_auth(auth); + } + ws.into_service() + .await + .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))? }; - let service = ws - .into_service() - .await - .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))?; + Ok(InnerTransport::Ws(service)) } @@ -222,8 +305,8 @@ impl RuntimeTransport { /// Sends a request using the underlying transport. /// If this is the first request, it will connect to the appropriate transport depending on the - /// URL scheme. When sending the request, retries will be automatically handled depending - /// on the parameters set on the [RuntimeTransport]. + /// URL scheme. Retries are performed by an external client layer (e.g., `RetryBackoffLayer`), + /// if such a layer is configured by the caller. /// For sending the actual request, this action is delegated down to the /// underlying transport through Tower's [tower::Service::call]. See tower's [tower::Service] /// trait for more information. diff --git a/crates/common/src/reports.rs b/crates/common/src/reports.rs deleted file mode 100644 index 0fdf4502eb68b..0000000000000 --- a/crates/common/src/reports.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::shell; - -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum ReportKind { - #[default] - Text, - JSON, -} - -/// Determine the kind of report to generate based on the current shell. -pub fn report_kind() -> ReportKind { - if shell::is_json() { - ReportKind::JSON - } else { - ReportKind::Text - } -} diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index 49eab352b7f09..ec102d3ef0446 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -1,7 +1,7 @@ //! Retry utilities. use eyre::{Error, Report, Result}; -use std::{future::Future, time::Duration}; +use std::time::Duration; /// Error type for Retry. #[derive(Debug, thiserror::Error)] @@ -23,12 +23,12 @@ pub struct Retry { impl Retry { /// Creates a new `Retry` instance. - pub fn new(retries: u32, delay: Duration) -> Self { + pub const fn new(retries: u32, delay: Duration) -> Self { Self { retries, delay } } /// Creates a new `Retry` instance with no delay between retries. - pub fn new_no_delay(retries: u32) -> Self { + pub const fn new_no_delay(retries: u32) -> Self { Self::new(retries, Duration::ZERO) } diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index 9dc20044f2ce5..c8cf736d69e95 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -4,22 +4,22 @@ use crate::{abi::abi_decode_calldata, provider::runtime_transport::RuntimeTransportBuilder}; use alloy_json_abi::JsonAbi; -use alloy_primitives::{map::HashMap, Selector, B256}; +use alloy_primitives::{B256, Selector, map::HashMap}; use eyre::Context; use itertools::Itertools; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::{ fmt, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, + atomic::{AtomicBool, AtomicUsize, Ordering}, }, time::Duration, }; -const BASE_URL: &str = "https://api.openchain.xyz"; -const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup"; -const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; +const BASE_URL: &str = "https://api.4byte.sourcify.dev"; +const SELECTOR_LOOKUP_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/lookup"; +const SELECTOR_IMPORT_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/import"; /// The standard request timeout for API requests. const REQ_TIMEOUT: Duration = Duration::from_secs(15); @@ -106,7 +106,7 @@ impl OpenChainClient { if is_connectivity_err(err) { warn!("spurious network detected for OpenChain"); let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst); - if previous >= self.max_timedout_requests { + if previous + 1 >= self.max_timedout_requests { self.set_spurious(); } } @@ -217,7 +217,7 @@ impl OpenChainClient { ) } - let mut sigs = self.decode_function_selector(calldata[..8].parse().unwrap()).await?; + let mut sigs = self.decode_function_selector(calldata[..8].parse()?).await?; // Retain only signatures that can be decoded. sigs.retain(|sig| abi_decode_calldata(sig, calldata, true, true).is_ok()); Ok(sigs) @@ -264,7 +264,7 @@ impl OpenChainClient { }; let (_, data) = calldata.split_at(8); - if data.len() % 64 != 0 { + if !data.len().is_multiple_of(64) { eyre::bail!("\nInvalid calldata size") } @@ -371,7 +371,7 @@ pub enum SelectorKind { impl SelectorKind { /// Returns the function selector if it is a function OR custom error. - pub fn as_function(&self) -> Option { + pub const fn as_function(&self) -> Option { match *self { Self::Function(selector) | Self::Error(selector) => Some(selector), _ => None, @@ -379,7 +379,7 @@ impl SelectorKind { } /// Returns the event selector if it is an event. - pub fn as_event(&self) -> Option { + pub const fn as_event(&self) -> Option { match *self { Self::Event(hash) => Some(hash), _ => None, @@ -444,7 +444,7 @@ pub struct RawSelectorImportData { } impl RawSelectorImportData { - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.function.is_empty() && self.event.is_empty() && self.error.is_empty() } } @@ -535,6 +535,7 @@ pub fn parse_signatures(tokens: Vec) -> ParsedSignatures { RawSelectorImportData::default(), |mut data, signature| { let mut split = signature.split(' '); + #[allow(clippy::collapsible_match)] match split.next() { Some("function") => { if let Some(sig) = split.next() { @@ -642,4 +643,21 @@ mod tests { ParsedSignatures { signatures: Default::default(), ..Default::default() } ); } + + #[tokio::test] + async fn spurious_marked_on_timeout_threshold() { + // Use an unreachable local port to trigger a quick connect error. + let client = OpenChainClient::new().expect("client must build"); + let url = "http://127.0.0.1:9"; // Discard port; typically closed and fails fast. + + // After MAX_TIMEDOUT_REQ - 1 failures we should NOT be spurious. + for i in 0..(MAX_TIMEDOUT_REQ - 1) { + let _ = client.get_text(url).await; // expect an error and internal counter increment + assert!(!client.is_spurious(), "unexpected spurious after {} failed attempts", i + 1); + } + + // The Nth failure (N == MAX_TIMEDOUT_REQ) should flip the spurious flag. + let _ = client.get_text(url).await; + assert!(client.is_spurious(), "expected spurious after threshold failures"); + } } diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs index 90634de397424..b7f32caf9dd2f 100644 --- a/crates/common/src/serde_helpers.rs +++ b/crates/common/src/serde_helpers.rs @@ -1,7 +1,7 @@ //! Misc Serde helpers for foundry crates. -use alloy_primitives::U256; -use serde::{de, Deserialize, Deserializer}; +use alloy_primitives::{U64, U256}; +use serde::{Deserialize, Deserializer, de}; use std::str::FromStr; /// Helper type to parse both `u64` and `U256` @@ -37,15 +37,22 @@ impl FromStr for Numeric { } } -/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the -/// inner value. -pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - match Option::::deserialize(deserializer)? { - Some(val) => val.try_into_u256().map(Some), - None => Ok(None), +/// Helper type to parse a `u64` from a JSON number or a hex/decimal string. +#[derive(Copy, Clone, Deserialize)] +#[serde(untagged)] +pub enum Numeric64 { + /// A JSON number. + Num(u64), + /// A hex or decimal string. + U64(U64), +} + +impl From for u64 { + fn from(n: Numeric64) -> Self { + match n { + Numeric64::Num(n) => n, + Numeric64::U64(n) => n.to::(), + } } } @@ -63,7 +70,7 @@ impl NumberOrHexU256 { /// Tries to convert this into a [U256]]. pub fn try_into_u256(self) -> Result { match self { - Self::Int(num) => U256::from_str(num.to_string().as_str()).map_err(E::custom), + Self::Int(num) => U256::from_str(&num.to_string()).map_err(E::custom), Self::Hex(val) => Ok(val), } } @@ -90,6 +97,16 @@ pub enum NumericSeq { Num(u64), } +/// Helper type to deserialize a single `u64` from either a direct value or a one-element sequence. +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Numeric64ValueOrSeq { + /// Single parameter sequence (e.g `[1]`). + Seq([Numeric64; 1]), + /// Single value. + Value(Numeric64), +} + /// Deserializes a number from hex or int pub fn deserialize_number<'de, D>(deserializer: D) -> Result where @@ -126,6 +143,35 @@ where Ok(num) } +/// Deserializes single `u64` params: `1, [1], ["0x01"]`. +pub fn deserialize_u64_seq<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = match Numeric64ValueOrSeq::deserialize(deserializer)? { + Numeric64ValueOrSeq::Seq(seq) => seq[0].into(), + Numeric64ValueOrSeq::Value(num) => num.into(), + }; + + Ok(num) +} + +/// Deserializes an optional integer from a single-element params sequence. +/// Accepts `[]`, `[null]`, `[n]`, `["0x.."]`. +pub fn deserialize_u64_seq_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let seq = Vec::>::deserialize(deserializer)?; + if seq.len() > 1 { + return Err(de::Error::custom(format!( + "expected params sequence with length 0 or 1 but got {}", + seq.len() + ))); + } + Ok(seq.into_iter().next().flatten().map(Into::into)) +} + pub mod duration { use serde::{Deserialize, Deserializer}; use std::time::Duration; @@ -147,3 +193,82 @@ pub mod duration { d.try_into().map_err(serde::de::Error::custom) } } + +#[cfg(test)] +mod tests { + use super::*; + use serde::de::IntoDeserializer; + use serde_json::json; + + fn parse_u64_param(value: serde_json::Value) -> Result { + deserialize_u64_seq(value.into_deserializer()) + } + + fn parse_optional_u64_param( + value: serde_json::Value, + ) -> Result, serde_json::Error> { + deserialize_u64_seq_opt(value.into_deserializer()) + } + + #[test] + fn deserialize_u64_seq_accepts_single_param_sequence_and_direct_value() { + let valid_values = [ + json!([100]), + json!(100), + json!(["0x64"]), + json!("0x64"), + json!(["100"]), + json!("100"), + ]; + for value in valid_values { + assert_eq!(parse_u64_param(value).unwrap(), 100); + } + + for value in [json!([u64::MAX]), json!(u64::MAX)] { + assert_eq!(parse_u64_param(value).unwrap(), u64::MAX); + } + } + + #[test] + fn deserialize_u64_seq_rejects_invalid_shape_and_overflow() { + for value in [ + json!([]), + json!([1, 2]), + json!([null]), + json!(null), + json!(["0x10000000000000000"]), + json!("0x10000000000000000"), + json!(["18446744073709551616"]), + json!("18446744073709551616"), + ] { + assert!(parse_u64_param(value).is_err()); + } + } + + #[test] + fn deserialize_u64_seq_opt_accepts_empty_null_and_single_param_sequence() { + for value in [json!([]), json!([null])] { + assert_eq!(parse_optional_u64_param(value).unwrap(), None); + } + + for value in [json!([100]), json!(["0x64"]), json!(["100"])] { + assert_eq!(parse_optional_u64_param(value).unwrap(), Some(100)); + } + + assert_eq!(parse_optional_u64_param(json!([u64::MAX])).unwrap(), Some(u64::MAX)); + } + + #[test] + fn deserialize_u64_seq_opt_rejects_invalid_shape_and_overflow() { + for value in [ + json!([1, 2]), + json!(100), + json!("0x64"), + json!([["0x64"]]), + json!(["0x10000000000000000"]), + json!(["18446744073709551616"]), + ] { + assert!(parse_optional_u64_param(value).is_err()); + } + } +} diff --git a/crates/common/src/slot_identifier.rs b/crates/common/src/slot_identifier.rs new file mode 100644 index 0000000000000..e737bc478bb62 --- /dev/null +++ b/crates/common/src/slot_identifier.rs @@ -0,0 +1,985 @@ +//! Storage slot identification and decoding utilities for Solidity storage layouts. +//! +//! This module provides functionality to identify and decode storage slots based on +//! Solidity storage layout information from the compiler. + +use crate::mapping_slots::MappingSlots; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::{B256, U256, hex, keccak256, map::B256Map}; +use foundry_common_fmt::format_token_raw; +use foundry_compilers::artifacts::{Storage, StorageLayout, StorageType}; +use serde::Serialize; +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; +use tracing::trace; + +/// "inplace" encoding type for variables that fit in one storage slot i.e 32 bytes +pub const ENCODING_INPLACE: &str = "inplace"; +/// "mapping" encoding type for Solidity mappings, which use keccak256 hash-based storage +pub const ENCODING_MAPPING: &str = "mapping"; +/// "bytes" encoding type for bytes and string types, which use either inplace or keccak256 +/// hash-based storage depending on length +pub const ENCODING_BYTES: &str = "bytes"; +/// "dynamic_array" encoding type for dynamic arrays, which uses keccak256 hash-based storage +pub const ENCODING_DYN_ARRAY: &str = "dynamic_array"; + +/// Information about a storage slot including its label, type, and decoded values. +#[derive(Serialize, Debug)] +pub struct SlotInfo { + /// The variable name from the storage layout. + /// + /// For top-level variables: just the variable name (e.g., "myVariable") + /// For struct members: dotted path (e.g., "myStruct.memberName") + /// For array elements: name with indices (e.g., "myArray\[0\]", "matrix\[1\]\[2\]") + /// For nested structures: full path (e.g., "outer.inner.field") + /// For mappings: base name with keys (e.g., "balances\[0x1234...\]")/ex + pub label: String, + /// The Solidity type information + #[serde(rename = "type", serialize_with = "serialize_slot_type")] + pub slot_type: StorageTypeInfo, + /// Offset within the storage slot (for packed storage) + pub offset: i64, + /// The storage slot number as a string + pub slot: String, + /// For struct members, contains nested SlotInfo for each member + /// + /// This is populated when a struct's members / fields are packed in a single slot. + #[serde(skip_serializing_if = "Option::is_none")] + pub members: Option>, + /// Decoded values (if available) - used for struct members + #[serde(skip_serializing_if = "Option::is_none")] + pub decoded: Option, + /// Decoded mapping keys (serialized as "key" for single, "keys" for multiple) + #[serde( + skip_serializing_if = "Option::is_none", + flatten, + serialize_with = "serialize_mapping_keys" + )] + pub keys: Option>, +} + +/// Wrapper type that holds both the original type label and the parsed DynSolType. +/// +/// We need both because: +/// - `label`: Used for serialization to ensure output matches user expectations +/// - `dyn_sol_type`: The parsed type used for actual value decoding +#[derive(Debug)] +pub struct StorageTypeInfo { + /// The original type label from storage layout (e.g., "uint256", "address", "mapping(address + /// => uint256)") + pub label: String, + /// The parsed dynamic Solidity type used for decoding + pub dyn_sol_type: DynSolType, +} + +impl SlotInfo { + /// Decodes a single storage value based on the slot's type information. + /// + /// Note: For decoding [`DynSolType::Bytes`] or [`DynSolType::String`] that span multiple slots, + /// use [`SlotInfo::decode_bytes_or_string`]. + pub fn decode(&self, value: B256) -> Option { + // Storage values are always 32 bytes, stored as a single word + let mut actual_type = &self.slot_type.dyn_sol_type; + // Unwrap nested arrays to get to the base element type. + while let DynSolType::FixedArray(elem_type, _) = actual_type { + actual_type = elem_type.as_ref(); + } + + // Special handling for bytes and string types + match actual_type { + DynSolType::Bytes | DynSolType::String => { + // Decode bytes/string from storage + // The last byte contains the length * 2 for short strings/bytes + // or length * 2 + 1 for long strings/bytes + let length_byte = value.0[31]; + + if length_byte & 1 == 0 { + // Short string/bytes (less than 32 bytes) + let length = (length_byte >> 1) as usize; + // Extract data + let data = if length == 0 { Vec::new() } else { value.0[0..length].to_vec() }; + + // Create the appropriate value based on type + if matches!(actual_type, DynSolType::String) { + let str_val = if data.is_empty() { + String::new() + } else { + String::from_utf8(data).unwrap_or_default() + }; + Some(DynSolValue::String(str_val)) + } else { + Some(DynSolValue::Bytes(data)) + } + } else { + // Long string/bytes (32 bytes or more) + // The actual data is stored at keccak256(slot) + // Return None for long values - they need decode_bytes_or_string() + None + } + } + _ => { + // Decode based on the actual type + actual_type.abi_decode(&value.0).ok() + } + } + } + + /// Slot is of type [`DynSolType::Bytes`] or [`DynSolType::String`] + pub const fn is_bytes_or_string(&self) -> bool { + matches!(self.slot_type.dyn_sol_type, DynSolType::Bytes | DynSolType::String) + } + + /// Decodes a [`DynSolType::Bytes`] or [`DynSolType::String`] value + /// that spans across multiple slots. + pub fn decode_bytes_or_string( + &mut self, + base_slot: &B256, + storage_values: &B256Map, + ) -> Option { + // Only process bytes/string types + if !self.is_bytes_or_string() { + return None; + } + + // Try to handle as long bytes/string + self.aggregate_bytes_or_strings(base_slot, storage_values).map(|data| { + match self.slot_type.dyn_sol_type { + DynSolType::String => { + DynSolValue::String(String::from_utf8(data).unwrap_or_default()) + } + DynSolType::Bytes => DynSolValue::Bytes(data), + _ => unreachable!(), + } + }) + } + + /// Decodes both previous and new [`DynSolType::Bytes`] or [`DynSolType::String`] values + /// that span across multiple slots using state diff data. + /// + /// Accepts a mapping of storage_slot to (previous_value, new_value). + pub fn decode_bytes_or_string_values( + &mut self, + base_slot: &B256, + storage_accesses: &BTreeMap, + ) { + // Only process bytes/string types + if !self.is_bytes_or_string() { + return; + } + + // Get both previous and new values from the storage accesses + if let Some((prev_base_value, new_base_value)) = storage_accesses.get(base_slot) { + // Reusable closure to decode bytes/string based on length encoding + let mut decode_value = |base_value: B256, is_new: bool| { + let length_byte = base_value.0[31]; + if length_byte & 1 == 1 { + // Long bytes/string - aggregate from multiple slots + let value_map = storage_accesses + .iter() + .map(|(slot, (prev, new))| (*slot, if is_new { *new } else { *prev })) + .collect::>(); + self.decode_bytes_or_string(base_slot, &value_map) + } else { + // Short bytes/string - decode directly from base slot + self.decode(base_value) + } + }; + + // Decode previous value + let prev_decoded = decode_value(*prev_base_value, false); + + // Decode new value + let new_decoded = decode_value(*new_base_value, true); + + // Set decoded values if both were successfully decoded + if let (Some(prev), Some(new)) = (prev_decoded, new_decoded) { + self.decoded = Some(DecodedSlotValues { previous_value: prev, new_value: new }); + } + } + } + + /// Aggregates a [`DynSolType::Bytes`] or [`DynSolType::String`] value that spans across + /// multiple slots by looking up the length in the base_slot. + /// + /// Returns the aggregated raw bytes. + fn aggregate_bytes_or_strings( + &mut self, + base_slot: &B256, + storage_values: &B256Map, + ) -> Option> { + if !self.is_bytes_or_string() { + return None; + } + + // Check if it's a long bytes/string by looking at the base value + if let Some(base_value) = storage_values.get(base_slot) { + let length_byte = base_value.0[31]; + + // Check if value is long + if length_byte & 1 == 1 { + // Long bytes/string - populate members + let length: U256 = U256::from_be_bytes(base_value.0) >> 1; + let num_slots = length.to::().div_ceil(32).min(256); + let data_start = U256::from_be_bytes(keccak256(base_slot.0).0); + + let mut members = Vec::new(); + let mut full_data = Vec::with_capacity(length.to::()); + + for i in 0..num_slots { + let data_slot = B256::from(data_start + U256::from(i)); + let data_slot_u256 = data_start + U256::from(i); + + // Create member info for this data slot with indexed label + let member_info = Self { + label: format!("{}[{}]", self.label, i), + slot_type: StorageTypeInfo { + label: self.slot_type.label.clone(), + dyn_sol_type: DynSolType::FixedBytes(32), + }, + offset: 0, + slot: data_slot_u256.to_string(), + members: None, + decoded: None, + keys: None, + }; + + if let Some(value) = storage_values.get(&data_slot) { + // Collect data + let bytes_to_take = + std::cmp::min(32, length.to::() - full_data.len()); + full_data.extend_from_slice(&value.0[..bytes_to_take]); + } + + members.push(member_info); + } + + // Set the members field + if !members.is_empty() { + self.members = Some(members); + } + + return Some(full_data); + } + } + + None + } + + /// Decodes storage values (previous and new) and populates the decoded field. + /// For structs with members, it decodes each member individually. + pub fn decode_values(&mut self, previous_value: B256, new_value: B256) { + // If this is a struct with members, decode each member individually + if let Some(members) = &mut self.members { + for member in members.iter_mut() { + let offset = member.offset as usize; + let size = match &member.slot_type.dyn_sol_type { + DynSolType::Uint(bits) | DynSolType::Int(bits) => bits / 8, + DynSolType::Address => 20, + DynSolType::Bool => 1, + DynSolType::FixedBytes(size) => *size, + _ => 32, // Default to full word + }; + + // Extract and decode member values + let mut prev_bytes = [0u8; 32]; + let mut new_bytes = [0u8; 32]; + + if offset + size <= 32 { + // In Solidity storage, values are right-aligned + // For offset 0, we want the rightmost bytes + // For offset 16 (for a uint128), we want bytes 0-16 + // For packed storage: offset 0 is at the rightmost position + // offset 0, size 16 -> read bytes 16-32 (rightmost) + // offset 16, size 16 -> read bytes 0-16 (leftmost) + let byte_start = 32 - offset - size; + prev_bytes[32 - size..] + .copy_from_slice(&previous_value.0[byte_start..byte_start + size]); + new_bytes[32 - size..] + .copy_from_slice(&new_value.0[byte_start..byte_start + size]); + } + + // Decode the member values + if let (Ok(prev_val), Ok(new_val)) = ( + member.slot_type.dyn_sol_type.abi_decode(&prev_bytes), + member.slot_type.dyn_sol_type.abi_decode(&new_bytes), + ) { + member.decoded = + Some(DecodedSlotValues { previous_value: prev_val, new_value: new_val }); + } + } + // For structs with members, we don't need a top-level decoded value + } else { + // For non-struct types, decode directly + // Note: decode() returns None for long bytes/strings, which will be handled by + // decode_bytes_or_string() + if let (Some(prev), Some(new)) = (self.decode(previous_value), self.decode(new_value)) { + self.decoded = Some(DecodedSlotValues { previous_value: prev, new_value: new }); + } + } + } +} + +/// Custom serializer for StorageTypeInfo that only outputs the label +fn serialize_slot_type(info: &StorageTypeInfo, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&info.label) +} + +/// Custom serializer for mapping keys +fn serialize_mapping_keys(keys: &Option>, serializer: S) -> Result +where + S: serde::Serializer, +{ + use serde::ser::SerializeMap; + + if let Some(keys) = keys { + let len = if keys.is_empty() { 0 } else { 1 }; + let mut map = serializer.serialize_map(Some(len))?; + if keys.len() == 1 { + map.serialize_entry("key", &keys[0])?; + } else if keys.len() > 1 { + map.serialize_entry("keys", keys)?; + } + map.end() + } else { + serializer.serialize_none() + } +} + +/// Decoded storage slot values +#[derive(Debug)] +pub struct DecodedSlotValues { + /// Initial decoded storage value + pub previous_value: DynSolValue, + /// Current decoded storage value + pub new_value: DynSolValue, +} + +impl Serialize for DecodedSlotValues { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut state = serializer.serialize_struct("DecodedSlotValues", 2)?; + state.serialize_field("previousValue", &format_token_raw(&self.previous_value))?; + state.serialize_field("newValue", &format_token_raw(&self.new_value))?; + state.end() + } +} + +/// Storage slot identifier that uses Solidity [`StorageLayout`] to identify storage slots. +pub struct SlotIdentifier { + storage_layout: Arc, +} + +impl SlotIdentifier { + /// Creates a new SlotIdentifier with the given storage layout. + pub const fn new(storage_layout: Arc) -> Self { + Self { storage_layout } + } + + /// Identifies a storage slots type using the [`StorageLayout`]. + /// + /// It can also identify whether a slot belongs to a mapping if provided with [`MappingSlots`]. + pub fn identify(&self, slot: &B256, mapping_slots: Option<&MappingSlots>) -> Option { + trace!(?slot, "identifying slot"); + let slot_u256 = U256::from_be_bytes(slot.0); + let slot_str = slot_u256.to_string(); + + for storage in &self.storage_layout.storage { + let storage_type = self.storage_layout.types.get(&storage.storage_type)?; + let dyn_type = DynSolType::parse(&storage_type.label).ok(); + + // Check if we're able to match on a slot from the layout i.e any of the base slots. + // This will always be the case for primitive types that fit in a single slot. + if storage.slot == slot_str + && let Some(parsed_type) = dyn_type + { + // Successfully parsed - handle arrays or simple types + let label = if let DynSolType::FixedArray(_, _) = &parsed_type { + format!("{}{}", storage.label, get_array_base_indices(&parsed_type)) + } else { + storage.label.clone() + }; + + return Some(SlotInfo { + label, + slot_type: StorageTypeInfo { + label: storage_type.label.clone(), + dyn_sol_type: parsed_type, + }, + offset: storage.offset, + slot: storage.slot.clone(), + members: None, + decoded: None, + keys: None, + }); + } + + // Encoding types: + if storage_type.encoding == ENCODING_INPLACE { + // Can be of type FixedArrays or Structs + // Handles the case where the accessed `slot` is maybe different from the base slot. + let array_start_slot = U256::from_str(&storage.slot).ok()?; + + if let Some(parsed_type) = dyn_type + && let DynSolType::FixedArray(_, _) = parsed_type + && let Some(slot_info) = self.handle_array_slot( + storage, + storage_type, + slot_u256, + array_start_slot, + &slot_str, + ) + { + return Some(slot_info); + } + + // If type parsing fails and the label is a struct + if is_struct(&storage_type.label) { + let struct_start_slot = U256::from_str(&storage.slot).ok()?; + if let Some(slot_info) = self.handle_struct( + &storage.label, + storage_type, + slot_u256, + struct_start_slot, + storage.offset, + &slot_str, + 0, + ) { + return Some(slot_info); + } + } + } else if storage_type.encoding == ENCODING_MAPPING + && let Some(mapping_slots) = mapping_slots + && let Some(info) = + self.handle_mapping(storage, storage_type, slot, &slot_str, mapping_slots) + { + return Some(info); + } + } + + None + } + + /// Identifies a bytes or string storage slot by checking all bytes/string variables + /// in the storage layout and using their base slot values from the provided storage changes. + /// + /// # Arguments + /// * `slot` - The slot being identified + /// * `storage_values` - Map of storage slots to their current values + pub fn identify_bytes_or_string( + &self, + slot: &B256, + storage_values: &B256Map, + ) -> Option { + let slot_u256 = U256::from_be_bytes(slot.0); + let slot_str = slot_u256.to_string(); + + // Search through all bytes/string variables in the storage layout + for storage in &self.storage_layout.storage { + if let Some(storage_type) = self.storage_layout.types.get(&storage.storage_type) + && storage_type.encoding == ENCODING_BYTES + { + let Some(base_slot) = U256::from_str(&storage.slot).map(B256::from).ok() else { + continue; + }; + // Get the base slot value from storage_values + if let Some(base_value) = storage_values.get(&base_slot) + && let Some(info) = self.handle_bytes_string( + storage, + storage_type, + slot_u256, + &slot_str, + base_value, + ) + { + return Some(info); + } + } + } + + None + } + + /// Handles identification of array slots. + /// + /// # Arguments + /// * `storage` - The storage metadata from the layout + /// * `storage_type` - Type information for the storage slot + /// * `slot` - The target slot being identified + /// * `array_start_slot` - The starting slot of the array in storage i.e base_slot + /// * `slot_str` - String representation of the slot for output + fn handle_array_slot( + &self, + storage: &Storage, + storage_type: &StorageType, + slot: U256, + array_start_slot: U256, + slot_str: &str, + ) -> Option { + // Check if slot is within array bounds + let total_bytes = storage_type.number_of_bytes.parse::().ok()?; + let total_slots = total_bytes.div_ceil(32); + + if slot >= array_start_slot && slot < array_start_slot + U256::from(total_slots) { + let parsed_type = DynSolType::parse(&storage_type.label).ok()?; + let index = (slot - array_start_slot).to::(); + // Format the array element label based on array dimensions + let label = match &parsed_type { + DynSolType::FixedArray(inner, _) => { + if let DynSolType::FixedArray(_, inner_size) = inner.as_ref() { + // 2D array: calculate row and column + let row = index / (*inner_size as u64); + let col = index % (*inner_size as u64); + format!("{}[{row}][{col}]", storage.label) + } else { + // 1D array + format!("{}[{index}]", storage.label) + } + } + _ => storage.label.clone(), + }; + + return Some(SlotInfo { + label, + slot_type: StorageTypeInfo { + label: storage_type.label.clone(), + dyn_sol_type: parsed_type, + }, + offset: 0, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: None, + }); + } + + None + } + + /// Handles identification of struct slots. + /// + /// Recursively resolves struct members to find the exact member corresponding + /// to the target slot. Handles both single-slot (packed) and multi-slot structs. + /// + /// # Arguments + /// * `base_label` - The label/name for this struct or member + /// * `storage_type` - Type information for the storage + /// * `target_slot` - The target slot being identified + /// * `struct_start_slot` - The starting slot of this struct + /// * `offset` - Offset within the slot (for packed storage) + /// * `slot_str` - String representation of the slot for output + /// * `depth` - Current recursion depth + #[allow(clippy::too_many_arguments)] + fn handle_struct( + &self, + base_label: &str, + storage_type: &StorageType, + target_slot: U256, + struct_start_slot: U256, + offset: i64, + slot_str: &str, + depth: usize, + ) -> Option { + // Limit recursion depth to prevent stack overflow + const MAX_DEPTH: usize = 10; + if depth > MAX_DEPTH { + return None; + } + + let members = storage_type + .other + .get("members") + .and_then(|v| serde_json::from_value::>(v.clone()).ok())?; + + // If this is the exact slot we're looking for (struct's base slot) + if struct_start_slot == target_slot + // Find the member at slot offset 0 (the member that starts at this slot) + && let Some(first_member) = members.iter().find(|m| m.slot == "0") + { + let member_type_info = self.storage_layout.types.get(&first_member.storage_type)?; + + // Check if we have a single-slot struct (all members have slot "0") + let is_single_slot = members.iter().all(|m| m.slot == "0"); + + if is_single_slot { + // Build member info for single-slot struct + let mut member_infos = Vec::new(); + for member in &members { + if let Some(member_type_info) = + self.storage_layout.types.get(&member.storage_type) + && let Some(member_type) = DynSolType::parse(&member_type_info.label).ok() + { + member_infos.push(SlotInfo { + label: member.label.clone(), + slot_type: StorageTypeInfo { + label: member_type_info.label.clone(), + dyn_sol_type: member_type, + }, + offset: member.offset, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: None, + }); + } + } + + // Build the CustomStruct type + let struct_name = + storage_type.label.strip_prefix("struct ").unwrap_or(&storage_type.label); + let prop_names: Vec = members.iter().map(|m| m.label.clone()).collect(); + let member_types: Vec = + member_infos.iter().map(|info| info.slot_type.dyn_sol_type.clone()).collect(); + + let parsed_type = DynSolType::CustomStruct { + name: struct_name.to_string(), + prop_names, + tuple: member_types, + }; + + return Some(SlotInfo { + label: base_label.to_string(), + slot_type: StorageTypeInfo { + label: storage_type.label.clone(), + dyn_sol_type: parsed_type, + }, + offset, + slot: slot_str.to_string(), + decoded: None, + members: if member_infos.is_empty() { None } else { Some(member_infos) }, + keys: None, + }); + } + + // Multi-slot struct - return the first member. + let member_label = format!("{}.{}", base_label, first_member.label); + + // If the first member is itself a struct, recurse + if is_struct(&member_type_info.label) { + return self.handle_struct( + &member_label, + member_type_info, + target_slot, + struct_start_slot, + first_member.offset, + slot_str, + depth + 1, + ); + } + + // Return the first member as a primitive + return Some(SlotInfo { + label: member_label, + slot_type: StorageTypeInfo { + label: member_type_info.label.clone(), + dyn_sol_type: DynSolType::parse(&member_type_info.label).ok()?, + }, + offset: first_member.offset, + slot: slot_str.to_string(), + decoded: None, + members: None, + keys: None, + }); + } + + // Not the base slot - search through members + for member in &members { + let member_slot_offset = U256::from_str(&member.slot).ok()?; + let member_slot = struct_start_slot + member_slot_offset; + let member_type_info = self.storage_layout.types.get(&member.storage_type)?; + let member_label = format!("{}.{}", base_label, member.label); + + // If this member is a struct, recurse into it + if is_struct(&member_type_info.label) { + let slot_info = self.handle_struct( + &member_label, + member_type_info, + target_slot, + member_slot, + member.offset, + slot_str, + depth + 1, + ); + + if member_slot == target_slot || slot_info.is_some() { + return slot_info; + } + } + + if member_slot == target_slot { + // Found the exact member slot + + // Regular member + let member_type = DynSolType::parse(&member_type_info.label).ok()?; + return Some(SlotInfo { + label: member_label, + slot_type: StorageTypeInfo { + label: member_type_info.label.clone(), + dyn_sol_type: member_type, + }, + offset: member.offset, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: None, + }); + } + } + + None + } + + /// Handles identification of mapping slots. + /// + /// Identifies mapping entries by walking up the parent chain to find the base slot, + /// then decodes the keys and builds the appropriate label. + /// + /// # Arguments + /// * `storage` - The storage metadata from the layout + /// * `storage_type` - Type information for the storage + /// * `slot` - The accessed slot being identified + /// * `slot_str` - String representation of the slot for output + /// * `mapping_slots` - Tracked mapping slot accesses for key resolution + fn handle_mapping( + &self, + storage: &Storage, + storage_type: &StorageType, + slot: &B256, + slot_str: &str, + mapping_slots: &MappingSlots, + ) -> Option { + trace!( + "handle_mapping: storage.slot={}, slot={:?}, has_keys={}, has_parents={}", + storage.slot, + slot, + mapping_slots.keys.contains_key(slot), + mapping_slots.parent_slots.contains_key(slot) + ); + + // Verify it's actually a mapping type + if storage_type.encoding != ENCODING_MAPPING { + return None; + } + + // Check if this slot is a known mapping entry + if !mapping_slots.keys.contains_key(slot) { + return None; + } + + // Convert storage.slot to B256 for comparison + let storage_slot_b256 = B256::from(U256::from_str(&storage.slot).ok()?); + + // Walk up the parent chain to collect keys and validate the base slot + let mut current_slot = *slot; + let mut keys_to_decode = Vec::new(); + let mut found_base = false; + + while let Some((key, parent)) = + mapping_slots.keys.get(¤t_slot).zip(mapping_slots.parent_slots.get(¤t_slot)) + { + keys_to_decode.push(*key); + + // Check if the parent is our base storage slot + if *parent == storage_slot_b256 { + found_base = true; + break; + } + + // Move up to the parent for the next iteration + current_slot = *parent; + } + + if !found_base { + trace!("Mapping slot {} does not match any parent in chain", storage.slot); + return None; + } + + // Resolve the mapping type to get all key types and the final value type + let (key_types, value_type_label, full_type_label) = + self.resolve_mapping_type(&storage.storage_type)?; + + // Reverse keys to process from outermost to innermost + keys_to_decode.reverse(); + + // Build the label with decoded keys and collect decoded key values + let mut label = storage.label.clone(); + let mut decoded_keys = Vec::new(); + + // Decode each key using the corresponding type + for (i, key) in keys_to_decode.iter().enumerate() { + if let Some(key_type_label) = key_types.get(i) + && let Ok(sol_type) = DynSolType::parse(key_type_label) + && let Ok(decoded) = sol_type.abi_decode(&key.0) + { + let decoded_key_str = format_token_raw(&decoded); + decoded_keys.push(decoded_key_str.clone()); + label = format!("{label}[{decoded_key_str}]"); + } else { + let hex_key = hex::encode_prefixed(key.0); + decoded_keys.push(hex_key.clone()); + label = format!("{label}[{hex_key}]"); + } + } + + // Parse the final value type for decoding + let dyn_sol_type = DynSolType::parse(&value_type_label).unwrap_or(DynSolType::Bytes); + + Some(SlotInfo { + label, + slot_type: StorageTypeInfo { label: full_type_label, dyn_sol_type }, + offset: storage.offset, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: Some(decoded_keys), + }) + } + + /// Handles identification of bytes/string storage slots. + /// + /// Bytes and strings in Solidity use a special storage layout: + /// - Short values (<32 bytes): stored in the same slot with length * 2 + /// - Long values (>=32 bytes): length * 2 + 1 in main slot, data at keccak256(slot) + /// + /// This function checks if the given slot is: + /// 1. A main slot for a bytes/string variable + /// 2. A data slot for any long bytes/string variable in the storage layout + /// + /// # Arguments + /// * `slot` - The accessed slot being identified + /// * `slot_str` - String representation of the slot for output + /// * `base_slot_value` - The value at the base slot (used to determine length for long + /// bytes/strings) + fn handle_bytes_string( + &self, + storage: &Storage, + storage_type: &StorageType, + slot: U256, + slot_str: &str, + base_slot_value: &B256, + ) -> Option { + // Only handle bytes/string encoded variables for this specific storage entry + if storage_type.encoding != ENCODING_BYTES { + return None; + } + + // Check if this is the main slot for this variable + let base_slot = U256::from_str(&storage.slot).ok()?; + if slot == base_slot { + // Parse the type to get the correct DynSolType + let dyn_type = if storage_type.label == "string" { + DynSolType::String + } else if storage_type.label == "bytes" { + DynSolType::Bytes + } else { + return None; + }; + + return Some(SlotInfo { + label: storage.label.clone(), + slot_type: StorageTypeInfo { + label: storage_type.label.clone(), + dyn_sol_type: dyn_type, + }, + offset: storage.offset, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: None, + }); + } + + // Check if it could be a data slot for this long bytes/string + // Calculate where data slots would start for this variable + let data_start = + U256::from_be_bytes(alloy_primitives::keccak256(base_slot.to_be_bytes::<32>()).0); + + // Get the length from the base slot value to calculate exact number of slots + // For long bytes/strings, the length is stored as (length * 2 + 1) in the base slot + let length_byte = base_slot_value.0[31]; + if length_byte & 1 == 1 { + // It's a long bytes/string + let length = U256::from_be_bytes(base_slot_value.0) >> 1; + // Calculate number of slots needed (round up) + let num_slots = (length + U256::from(31)) / U256::from(32); + + // Check if our slot is within the data region + if slot >= data_start && slot < data_start + num_slots { + let slot_index = (slot - data_start).to::(); + + return Some(SlotInfo { + label: format!("{}[{}]", storage.label, slot_index), + slot_type: StorageTypeInfo { + label: storage_type.label.clone(), + // Type is assigned as FixedBytes(32) for data slots + dyn_sol_type: DynSolType::FixedBytes(32), + }, + offset: 0, + slot: slot_str.to_string(), + members: None, + decoded: None, + keys: None, + }); + } + } + + None + } + + fn resolve_mapping_type(&self, type_ref: &str) -> Option<(Vec, String, String)> { + let storage_type = self.storage_layout.types.get(type_ref)?; + + if storage_type.encoding != ENCODING_MAPPING { + // Not a mapping, return the type as-is + return Some((vec![], storage_type.label.clone(), storage_type.label.clone())); + } + + // Get key and value type references + let key_type_ref = storage_type.key.as_ref()?; + let value_type_ref = storage_type.value.as_ref()?; + + // Resolve the key type + let key_type = self.storage_layout.types.get(key_type_ref)?; + let mut key_types = vec![key_type.label.clone()]; + + // Check if the value is another mapping (nested case) + if let Some(value_storage_type) = self.storage_layout.types.get(value_type_ref) { + if value_storage_type.encoding == ENCODING_MAPPING { + // Recursively resolve the nested mapping + let (nested_keys, final_value, _) = self.resolve_mapping_type(value_type_ref)?; + key_types.extend(nested_keys); + return Some((key_types, final_value, storage_type.label.clone())); + } + // Value is not a mapping, we're done + return Some((key_types, value_storage_type.label.clone(), storage_type.label.clone())); + } + + None + } +} + +/// Returns the base indices for array types, e.g. "\[0\]\[0\]" for 2D arrays. +fn get_array_base_indices(dyn_type: &DynSolType) -> String { + match dyn_type { + DynSolType::FixedArray(inner, _) => { + if let DynSolType::FixedArray(_, _) = inner.as_ref() { + // Nested array (2D or higher) + format!("[0]{}", get_array_base_indices(inner)) + } else { + // Simple 1D array + "[0]".to_string() + } + } + _ => String::new(), + } +} + +/// Checks if a given type label represents a struct type. +pub fn is_struct(s: &str) -> bool { + s.starts_with("struct ") +} diff --git a/crates/common/src/tempo/auth.rs b/crates/common/src/tempo/auth.rs new file mode 100644 index 0000000000000..d79306cfb74f2 --- /dev/null +++ b/crates/common/src/tempo/auth.rs @@ -0,0 +1,494 @@ +//! Tempo wallet device-code authorization flow. +//! +//! Implements the CLI side of the tempoxyz/accounts `cli-auth` device-code +//! protocol: generates a local secp256k1 access key, creates a PKCE-protected +//! device code, opens `wallet.tempo.xyz/cli-auth?code=` in the browser, +//! polls until the user authorizes the key on their passkey wallet, and writes +//! the resulting `keyAuthorization` to `~/.tempo/wallet/keys.toml`. + +use crate::tempo::{ + KeyEntry, KeyType, StoredTokenLimit, WalletType, decode_key_authorization, upsert_key_entry, +}; +use alloy_primitives::{Address, B256, hex}; +use alloy_signer_local::PrivateKeySigner; +use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +#[cfg(any(unix, windows))] +use std::process::Command; +use std::{ + env, + sync::LazyLock, + time::{Duration, Instant}, +}; +use tempo_primitives::transaction::{SignatureType, SignedKeyAuthorization}; +use tokio::sync::Mutex; + +/// Default device-code service URL (production wallet.tempo.xyz). +const DEFAULT_CLI_AUTH_URL: &str = "https://wallet.tempo.xyz/cli-auth"; + +/// Returns `true` if `url`'s host is `tempo.xyz` or a subdomain of it. +pub(crate) fn is_known_tempo_endpoint(url: &url::Url) -> bool { + url.host_str().is_some_and(|host| host == "tempo.xyz" || host.ends_with(".tempo.xyz")) +} + +/// Env var to override the device-code service URL (for tests / staging). +const TEMPO_CLI_AUTH_URL_ENV: &str = "TEMPO_CLI_AUTH_URL"; + +const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2); +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); + +/// Per-process serialization of concurrent `ensure_access_key` calls. +/// +/// Prevents two `cast` invocations in the same process from racing two browser +/// popups for the same chain. +static AUTH_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Configuration for [`ensure_access_key`]. +#[derive(Clone, Debug)] +pub struct EnsureAccessKeyConfig { + /// Chain ID the access key is being authorized for. + pub chain_id: u64, + /// Device-code service base URL. Defaults to [`DEFAULT_CLI_AUTH_URL`]. + pub(crate) service_url: String, + /// Poll interval. + pub(crate) poll_interval: Duration, + /// Total timeout for the authorization flow. + pub(crate) timeout: Duration, + /// If `true`, print the authorization URL to stderr instead of opening a + /// browser. + pub no_browser: bool, +} + +impl EnsureAccessKeyConfig { + /// Build a config from the environment for the given chain. + /// + /// `no_browser` defaults to `true` under `CI`; callers (e.g. `cast tempo + /// login --no-browser`) may override it. + pub fn from_env(chain_id: u64) -> Self { + Self { + chain_id, + service_url: env::var(TEMPO_CLI_AUTH_URL_ENV) + .unwrap_or_else(|_| DEFAULT_CLI_AUTH_URL.to_string()), + poll_interval: DEFAULT_POLL_INTERVAL, + timeout: DEFAULT_TIMEOUT, + no_browser: env::var_os("CI").is_some(), + } + } +} + +/// Open `url` via the OS default browser handler. On platforms without a known +/// opener, this is a no-op (the URL is still printed by [`ensure_access_key`]). +fn open_browser(_url: &str) { + #[cfg(target_os = "macos")] + let _ = Command::new("open").arg(_url).spawn(); + #[cfg(target_os = "windows")] + let _ = Command::new("cmd").args(["/c", "start", "", _url]).spawn(); + #[cfg(all(unix, not(target_os = "macos")))] + let _ = Command::new("xdg-open").arg(_url).spawn(); +} + +/// Result of [`ensure_access_key`]. +#[derive(Debug, Clone)] +pub struct AccessKeyOutcome { + pub wallet_address: Address, + pub key_address: Address, + pub chain_id: u64, +} + +/// Run the device-code flow, persist the resulting key to `keys.toml`, and +/// return the new entry's identifying fields. +pub async fn ensure_access_key(cfg: EnsureAccessKeyConfig) -> Result { + let _guard = AUTH_LOCK.lock().await; + + let signer = PrivateKeySigner::random(); + let key_address = signer.address(); + // The server requires uncompressed SEC1 (65-byte `0x04 || X || Y`); the + // default `to_sec1_bytes()` would emit the compressed 33-byte form. + let pub_key_hex = format!( + "0x{}", + hex::encode(signer.credential().verifying_key().to_encoded_point(false).as_bytes()), + ); + + let code_verifier = random_code_verifier(); + let client = reqwest::Client::builder().timeout(Duration::from_secs(30)).build()?; + let service = cfg.service_url.trim_end_matches('/'); + + let create_req = CreateCodeRequest { + chain_id: cfg.chain_id, + code_challenge: sha256_b64url(&code_verifier), + key_type: "secp256k1", + pub_key: pub_key_hex, + }; + let code = create_code_with_retry(&client, service, &create_req, cfg.timeout).await?; + + let browser_url = format!("{service}?code={code}"); + if cfg.no_browser { + let _ = crate::sh_eprintln!("Open this URL to authorize: {browser_url}"); + } else { + let _ = crate::sh_eprintln!( + "Opening wallet.tempo to authorize an access key…\n {browser_url}" + ); + open_browser(&browser_url); + } + + let poll = PollRequest { code_verifier }; + let started = Instant::now(); + loop { + // Retry transient network/5xx/429 failures within `cfg.timeout`. + let send_res = client.post(format!("{service}/poll/{code}")).json(&poll).send().await; + + let resp = match send_res { + Ok(r) => r, + Err(e) if is_transient_error(&e) && started.elapsed() < cfg.timeout => { + tracing::debug!(error = %e, "transient error polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + Err(e) => return Err(e.into()), + }; + + let status = resp.status(); + if !status.is_success() { + if is_transient_status(status) && started.elapsed() < cfg.timeout { + tracing::debug!(%status, "transient HTTP status polling device code, retrying"); + tokio::time::sleep(cfg.poll_interval).await; + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code poll failed ({status}): {body}"); + } + + let body: PollResponse = resp.json().await?; + match body { + PollResponse::Pending => { + if started.elapsed() > cfg.timeout { + eyre::bail!("timed out waiting for wallet authorization (code {code})"); + } + tokio::time::sleep(cfg.poll_interval).await; + } + PollResponse::Expired => { + eyre::bail!("device code {code} expired before authorization"); + } + PollResponse::Authorized { account_address, key_authorization } => { + let hex_str = key_authorization.ok_or_else(|| { + eyre::eyre!("wallet authorized response missing key_authorization") + })?; + let signed: SignedKeyAuthorization = decode_key_authorization(&hex_str)?; + // Reject mismatches before persisting — an unusable keys.toml + // entry would silently break the next 402 retry. + if signed.authorization.key_id != key_address { + eyre::bail!( + "wallet authorized key {} but the locally generated key is {}", + signed.authorization.key_id, + key_address, + ); + } + if signed.authorization.chain_id != cfg.chain_id { + eyre::bail!( + "wallet authorized chain {} but {} was requested", + signed.authorization.chain_id, + cfg.chain_id, + ); + } + if signed.authorization.key_type != SignatureType::Secp256k1 { + eyre::bail!( + "wallet returned keyType {:?} but secp256k1 was requested", + signed.authorization.key_type, + ); + } + let chain_id = signed.authorization.chain_id; + let key_authorization = + if hex_str.starts_with("0x") { hex_str } else { format!("0x{hex_str}") }; + let entry = KeyEntry { + wallet_type: WalletType::Passkey, + wallet_address: account_address, + chain_id, + key_type: match signed.authorization.key_type { + SignatureType::P256 => KeyType::P256, + SignatureType::WebAuthn => KeyType::WebAuthn, + _ => KeyType::Secp256k1, + }, + key_address: Some(key_address), + key: Some(format!("0x{}", hex::encode(signer.to_bytes()))), + key_authorization: Some(key_authorization), + expiry: signed.authorization.expiry.map(|n| n.get()), + limits: signed + .authorization + .limits + .unwrap_or_default() + .into_iter() + .map(|l| StoredTokenLimit { currency: l.token, limit: l.limit.to_string() }) + .collect(), + }; + upsert_key_entry(entry)?; + return Ok(AccessKeyOutcome { + wallet_address: account_address, + key_address, + chain_id, + }); + } + } + } +} + +fn is_transient_error(err: &reqwest::Error) -> bool { + err.is_timeout() || err.is_connect() || err.is_request() +} + +fn is_transient_status(status: reqwest::StatusCode) -> bool { + status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS +} + +/// POST `/code` with exponential backoff on transient errors, bounded by `timeout`. +async fn create_code_with_retry( + client: &reqwest::Client, + service: &str, + req: &CreateCodeRequest, + timeout: Duration, +) -> Result { + let started = Instant::now(); + let mut backoff = Duration::from_millis(500); + loop { + let send_res = client.post(format!("{service}/code")).json(req).send().await; + + match send_res { + Ok(resp) => { + let status = resp.status(); + if status.is_success() { + let CreateCodeResponse { code } = resp.json().await?; + return Ok(code); + } + if is_transient_status(status) && started.elapsed() < timeout { + tracing::debug!(%status, "transient HTTP status creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + continue; + } + let body = resp.text().await.unwrap_or_default(); + eyre::bail!("device-code create failed ({status}): {body}"); + } + Err(e) if is_transient_error(&e) && started.elapsed() < timeout => { + tracing::debug!(error = %e, "transient error creating device code, retrying"); + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(Duration::from_secs(5)); + } + Err(e) => return Err(e.into()), + } + } +} + +fn random_code_verifier() -> String { + let bytes = B256::random(); + URL_SAFE_NO_PAD.encode(bytes.as_slice()) +} + +fn sha256_b64url(input: &str) -> String { + let digest = Sha256::digest(input.as_bytes()); + URL_SAFE_NO_PAD.encode(digest) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CreateCodeRequest { + /// `0x`-hex per the SDK schema (server accepts hex string or bigint, not a plain JSON number). + #[serde(serialize_with = "serialize_u64_hex")] + chain_id: u64, + code_challenge: String, + key_type: &'static str, + pub_key: String, +} + +fn serialize_u64_hex(v: &u64, s: S) -> std::result::Result { + s.serialize_str(&format!("0x{v:x}")) +} + +#[derive(Deserialize)] +struct CreateCodeResponse { + code: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PollRequest { + code_verifier: String, +} + +/// Matches `tempoxyz/wallet` poll response shape. +#[derive(Deserialize)] +#[serde(tag = "status", rename_all = "lowercase")] +enum PollResponse { + Pending, + Expired, + Authorized { + account_address: Address, + #[serde(default)] + key_authorization: Option, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tempo::{TEMPO_HOME_ENV, read_tempo_keys_file, test_env_mutex}; + use axum::{Json, Router, extract::State, routing::post}; + use std::sync::{Arc, Mutex}; + + #[test] + fn pkce_challenge_matches_sdk_format() { + // Vector from RFC 7636 §4.2. + let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + let challenge = sha256_b64url(verifier); + assert_eq!(challenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); + } + + /// Recover the EOA from a SEC1-encoded public key (compressed or + /// uncompressed). + fn address_from_sec1_hex(s: &str) -> Address { + let stripped = s.strip_prefix("0x").unwrap_or(s); + let bytes = hex::decode(stripped).expect("valid hex"); + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes).expect("valid SEC1 pubkey"); + Address::from_public_key(&vk) + } + + #[derive(Clone)] + struct MockState { + wallet: Arc>>, + /// Derived from the `pubKey` posted to `/code` so `/poll` can echo + /// back a matching `keyId`, like a real wallet would. + key_id: Arc>>, + /// Chain ID the mock `/poll` returns in `keyAuthorization`. + poll_chain_id: u64, + } + + async fn create_code_handler( + State(state): State, + Json(body): Json, + ) -> Json { + // Sanity: required fields present and chainId is a 0x-hex string, + // matching the SDK wire format the live server enforces. + let pub_key = body + .get("pubKey") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("pubKey missing: {body}")); + assert!(body.get("codeChallenge").is_some(), "codeChallenge missing: {body}"); + let chain_id = body.get("chainId").unwrap_or_else(|| panic!("chainId missing: {body}")); + let chain_str = chain_id + .as_str() + .unwrap_or_else(|| panic!("chainId must be string, got {chain_id}: {body}")); + assert!(chain_str.starts_with("0x"), "chainId must be 0x-hex, got {chain_str}"); + let wallet: Address = "0x0000000000000000000000000000000000000042".parse().unwrap(); + *state.wallet.lock().unwrap() = Some(wallet); + *state.key_id.lock().unwrap() = Some(address_from_sec1_hex(pub_key)); + Json(serde_json::json!({ "code": "ABCDEFGH" })) + } + + /// Build the RLP-hex `SignedKeyAuthorization` blob the live server returns + /// in the `key_authorization` field. + fn signed_key_auth_hex(chain_id: u64, key_id: Address, expiry: u64) -> String { + use alloy_rlp::Encodable; + use tempo_primitives::transaction::{KeyAuthorization, PrimitiveSignature}; + let auth = KeyAuthorization::unrestricted(chain_id, SignatureType::Secp256k1, key_id) + .with_expiry(expiry); + let sig: PrimitiveSignature = serde_json::from_value(serde_json::json!({ + "type": "secp256k1", "r": "0x0", "s": "0x0", "yParity": 0 + })) + .unwrap(); + let signed = auth.into_signed(sig); + let mut buf = Vec::new(); + signed.encode(&mut buf); + format!("0x{}", hex::encode(buf)) + } + + async fn poll_handler(State(state): State) -> Json { + let wallet = state.wallet.lock().unwrap().expect("create_code must be called first"); + let key_id = state.key_id.lock().unwrap().expect("create_code must be called first"); + Json(serde_json::json!({ + "status": "authorized", + "account_address": wallet, + "key_authorization": signed_key_auth_hex(state.poll_chain_id, key_id, 9_999_999_999), + })) + } + + /// Spawn a mock wallet.tempo server whose `/poll` echoes `poll_chain_id`. + async fn spawn_mock_wallet(poll_chain_id: u64) -> (String, tokio::task::JoinHandle<()>) { + let app = Router::new() + .route("/code", post(create_code_handler)) + .route("/poll/{code}", post(poll_handler)) + .with_state(MockState { + wallet: Arc::default(), + key_id: Arc::default(), + poll_chain_id, + }); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + (format!("http://{addr}"), handle) + } + + fn test_cfg(service_url: String) -> EnsureAccessKeyConfig { + EnsureAccessKeyConfig { + chain_id: 4217, + service_url, + poll_interval: Duration::from_millis(10), + timeout: Duration::from_secs(2), + no_browser: true, + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_happy_path_writes_keys_toml() { + // SAFETY: serialized with other tests that mutate TEMPO_HOME. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(4217).await; + let outcome = ensure_access_key(test_cfg(service_url)).await.unwrap(); + + let expected_wallet: Address = + "0x0000000000000000000000000000000000000042".parse().unwrap(); + assert_eq!(outcome.chain_id, 4217); + assert_eq!(outcome.wallet_address, expected_wallet); + + let file = read_tempo_keys_file().expect("keys.toml written"); + assert_eq!(file.keys.len(), 1); + let entry = &file.keys[0]; + assert_eq!(entry.wallet_address, outcome.wallet_address); + assert_eq!(entry.key_address, Some(outcome.key_address)); + assert_eq!(entry.chain_id, 4217); + assert_eq!(entry.expiry, Some(9_999_999_999)); + let decoded: tempo_primitives::transaction::SignedKeyAuthorization = + crate::tempo::decode_key_authorization(entry.key_authorization.as_deref().unwrap()) + .expect("RLP roundtrip"); + assert_eq!(decoded.authorization.chain_id, 4217); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } + + #[tokio::test(flavor = "multi_thread")] + async fn ensure_access_key_rejects_wrong_chain_id() { + // Wallet returns chain 99999 but client requested 4217 → must reject + // and persist nothing, else discovery would later fail to find a key + // for the requested chain. + let _g = test_env_mutex().lock().await; + let tmp = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; + + let (service_url, server) = spawn_mock_wallet(99999).await; + let err = ensure_access_key(test_cfg(service_url)).await.unwrap_err(); + assert!( + err.to_string().contains("wallet authorized chain 99999 but 4217 was requested"), + "expected chain mismatch error, got: {err}" + ); + assert!(read_tempo_keys_file().is_none_or(|f| f.keys.is_empty())); + + server.abort(); + unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; + } +} diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs new file mode 100644 index 0000000000000..eb1e5649092b1 --- /dev/null +++ b/crates/common/src/tempo/keystore.rs @@ -0,0 +1,164 @@ +//! Tempo wallet keystore types and discovery helpers. +//! +//! Shared types for reading keys from the Tempo CLI wallet keystore +//! (`$TEMPO_HOME/wallet/keys.toml`, defaulting to `~/.tempo/wallet/keys.toml`). + +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use serde::Deserialize; +use std::path::{Component, Path, PathBuf}; + +/// Environment variable for an ephemeral Tempo private key. +pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; + +/// Environment variable to override the Tempo home directory. +pub const TEMPO_HOME_ENV: &str = "TEMPO_HOME"; + +/// Default Tempo home directory relative to the user's home. +pub const DEFAULT_TEMPO_HOME: &str = ".tempo"; + +/// Relative path from Tempo home to the wallet keys file. +pub const WALLET_KEYS_PATH: &str = "wallet/keys.toml"; + +/// Wallet type matching `tempo-common`'s `WalletType` enum. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(Debug, Default, Deserialize)] +pub struct StoredTokenLimit { + pub currency: Address, + pub limit: String, +} + +/// A single key entry in `keys.toml`. +/// +/// Mirrors the fields from `tempo-common::keys::model::KeyEntry`. +/// Unknown fields are ignored by serde. +#[derive(Debug, Default, Deserialize)] +pub struct KeyEntry { + /// Wallet type: "local" or "passkey". + #[serde(default)] + pub wallet_type: WalletType, + /// Smart wallet address (the on-chain account). + #[serde(default)] + pub wallet_address: Address, + /// Chain ID. + #[serde(default)] + pub chain_id: u64, + /// Cryptographic key type. + #[serde(default)] + pub key_type: KeyType, + /// Key address (the EOA derived from the private key). + #[serde(default)] + pub key_address: Option

, + /// Key private key, stored inline in keys.toml. + #[serde(default)] + pub key: Option, + /// RLP-encoded signed key authorization (hex string). + /// Used in keychain mode to atomically provision the access key on-chain. + #[serde(default)] + pub key_authorization: Option, + /// Expiry timestamp. + #[serde(default)] + pub expiry: Option, + /// Per-token spending limits. + #[serde(default)] + pub limits: Vec, +} + +impl KeyEntry { + /// Whether this entry has a non-empty inline private key. + pub fn has_inline_key(&self) -> bool { + self.key.as_ref().is_some_and(|k| !k.trim().is_empty()) + } +} + +/// The top-level structure of `keys.toml`. +#[derive(Debug, Default, Deserialize)] +pub struct KeysFile { + #[serde(default)] + pub keys: Vec, +} + +/// Resolve the Tempo home directory. +/// +/// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. +fn is_safe_tempo_home_override(path: &Path) -> bool { + !path + .components() + .any(|component| matches!(component, Component::ParentDir)) +} + +pub fn tempo_home() -> Option { + if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { + let candidate = PathBuf::from(home); + if is_safe_tempo_home_override(&candidate) { + return Some(candidate); + } + tracing::warn!( + env = TEMPO_HOME_ENV, + ?candidate, + "ignoring unsafe TEMPO_HOME override containing parent directory components" + ); + } + dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) +} + +/// Returns the path to the Tempo wallet keys file. +pub fn tempo_keys_path() -> Option { + tempo_home().map(|home| home.join(WALLET_KEYS_PATH)) +} + +/// Read and parse the Tempo wallet keys file. +/// +/// Returns `None` if the file doesn't exist or can't be read/parsed. +/// Errors are logged as warnings. +pub fn read_tempo_keys_file() -> Option { + let keys_path = tempo_keys_path()?; + if !keys_path.exists() { + tracing::trace!(?keys_path, "tempo keys file not found"); + return None; + } + + let contents = match std::fs::read_to_string(&keys_path) { + Ok(c) => c, + Err(e) => { + tracing::warn!(?keys_path, %e, "failed to read tempo keys file"); + return None; + } + }; + + match toml::from_str(&contents) { + Ok(f) => Some(f), + Err(e) => { + tracing::warn!(?keys_path, %e, "failed to parse tempo keys file"); + None + } + } +} + +/// Decodes a hex-encoded, RLP-encoded key authorization. +/// +/// The input should be a hex string (with or without 0x prefix) containing +/// RLP-encoded `SignedKeyAuthorization` data. +pub fn decode_key_authorization(hex_str: &str) -> eyre::Result { + let bytes = hex::decode(hex_str)?; + let auth = T::decode(&mut bytes.as_slice())?; + Ok(auth) +} diff --git a/crates/common/src/tempo/mod.rs b/crates/common/src/tempo/mod.rs new file mode 100644 index 0000000000000..ef8d0212bd453 --- /dev/null +++ b/crates/common/src/tempo/mod.rs @@ -0,0 +1,204 @@ +//! Tempo network utilities. + +pub mod auth; + +use crate::FoundryTransactionBuilder; +use alloy_network::Network; +use alloy_primitives::{Address, B256, Signature}; +use alloy_signer::Signer; +use eyre::{Context, Result}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use std::sync::Arc; + +mod keystore; + +pub(crate) use auth::is_known_tempo_endpoint; +pub use auth::{AccessKeyOutcome, EnsureAccessKeyConfig, ensure_access_key}; +pub use keystore::*; + +#[cfg(test)] +pub(crate) use keystore::test_env_mutex; + +#[cfg(test)] +mod tests; + +/// Conservative gas buffer for browser wallet transactions on Tempo chains. +/// +/// Browser wallets may sign with P256 or WebAuthn instead of secp256k1, which costs more gas +/// for signature verification. Since we can't determine the signature type before signing, +/// we add the worst-case (WebAuthn) overhead: +/// - P256: +5,000 gas (P256 precompile cost minus ecrecover savings) +/// - WebAuthn: ~6,500 gas (P256 cost + calldata for webauthn_data) +/// +/// See +pub const TEMPO_BROWSER_GAS_BUFFER: u64 = 7_000; + +/// Gas sponsor configuration for Tempo fee-payer signatures. +#[derive(Clone, Debug)] +pub struct TempoSponsor { + sponsor: Address, + signer: Option>, + signature: Option, +} + +impl TempoSponsor { + pub const fn new( + sponsor: Address, + signer: Option>, + signature: Option, + ) -> Self { + Self { sponsor, signer, signature } + } + + pub const fn sponsor(&self) -> Address { + self.sponsor + } + + pub async fn attach_and_print( + &self, + tx: &mut N::TransactionRequest, + sender: Address, + ) -> Result + where + N::TransactionRequest: FoundryTransactionBuilder, + { + if self.sponsor == sender { + eyre::bail!( + "invalid Tempo sponsorship: sponsor {} must not equal transaction sender", + self.sponsor + ); + } + + let digest = tx.compute_sponsor_hash(sender).ok_or_else(|| { + eyre::eyre!( + "failed to compute Tempo sponsor digest; make sure this is a complete Tempo AA transaction" + ) + })?; + + let preview = TempoSponsorPreview { + sponsor: self.sponsor, + fee_token: tx.fee_token(), + valid_before: tx.valid_before().map(|v| v.get()), + valid_after: tx.valid_after().map(|v| v.get()), + digest, + }; + preview.print()?; + + let signature = if let Some(signature) = self.signature { + signature + } else if let Some(signer) = &self.signer { + signer.sign_hash(&digest).await.context("failed to sign Tempo sponsor digest")? + } else { + eyre::bail!("missing Tempo sponsor signature or signer") + }; + + let recovered = signature + .recover_address_from_prehash(&digest) + .context("failed to recover Tempo sponsor signature")?; + if recovered != self.sponsor { + eyre::bail!("Tempo sponsor signature recovered {recovered}, expected {}", self.sponsor); + } + if recovered == sender { + eyre::bail!( + "invalid Tempo sponsorship: recovered fee payer {recovered} must not equal transaction sender" + ); + } + + tx.set_fee_payer_signature(signature); + Ok(preview) + } +} + +/// User-visible sponsor digest metadata for a single outgoing Tempo transaction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TempoSponsorPreview { + pub sponsor: Address, + pub fee_token: Option
, + pub valid_before: Option, + pub valid_after: Option, + pub digest: B256, +} + +impl TempoSponsorPreview { + pub fn print(&self) -> Result<()> { + crate::sh_eprintln!("Tempo sponsor: {}", self.sponsor)?; + crate::sh_eprintln!( + "Tempo fee token: {}", + self.fee_token.map_or_else(|| "network default".to_string(), |addr| addr.to_string()) + )?; + crate::sh_eprintln!( + "Tempo validity: after {}, before {}", + self.valid_after.map_or_else(|| "none".to_string(), |v| v.to_string()), + self.valid_before.map_or_else(|| "none".to_string(), |v| v.to_string()) + )?; + crate::sh_eprintln!("Tempo sponsor digest: {:?}", self.digest)?; + Ok(()) + } +} + +/// Resolves a `--tempo.sponsor-signer` URI into a Foundry wallet signer. +pub async fn resolve_tempo_sponsor_signer(spec: &str) -> Result { + let spec = spec.trim(); + let (scheme, value) = spec + .split_once("://") + .map(|(scheme, value)| (scheme.to_ascii_lowercase(), value)) + .unwrap_or_else(|| (spec.to_ascii_lowercase(), "")); + + match scheme.as_str() { + "env" => { + if value.is_empty() { + eyre::bail!("env:// sponsor signer requires an environment variable name"); + } + let private_key = std::env::var(value) + .wrap_err_with(|| format!("{value} environment variable is required"))?; + foundry_wallets::utils::create_private_key_signer(&private_key) + } + "private-key" => { + if value.is_empty() { + eyre::bail!("private-key:// sponsor signer requires a private key"); + } + foundry_wallets::utils::create_private_key_signer(value) + } + "keystore" => { + if value.is_empty() { + eyre::bail!("keystore:// sponsor signer requires a keystore path"); + } + WalletOpts { keystore_path: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "account" => { + if value.is_empty() { + eyre::bail!("account:// sponsor signer requires an account name"); + } + WalletOpts { keystore_account_name: Some(value.to_string()), ..Default::default() } + .signer() + .await + } + "ledger" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { ledger: true, raw, ..Default::default() }.signer().await + } + "trezor" => { + let raw = RawWalletOpts { + hd_path: (!value.is_empty()).then(|| value.to_string()), + ..Default::default() + }; + WalletOpts { trezor: true, raw, ..Default::default() }.signer().await + } + "aws" => WalletOpts { aws: true, ..Default::default() }.signer().await, + "gcp" => WalletOpts { gcp: true, ..Default::default() }.signer().await, + "turnkey" => WalletOpts { turnkey: true, ..Default::default() }.signer().await, + "browser" => { + eyre::bail!( + "browser:// sponsor signing is not supported by the current browser wallet API; use --tempo.sponsor-sig or another sponsor signer" + ) + } + _ => eyre::bail!( + "unsupported Tempo sponsor signer `{spec}`; expected env://VAR, keystore://PATH, account://NAME, ledger://, trezor://, aws://, gcp://, turnkey://, or private-key://KEY" + ), + } +} diff --git a/crates/common/src/tempo/tests.rs b/crates/common/src/tempo/tests.rs new file mode 100644 index 0000000000000..60376732a635a --- /dev/null +++ b/crates/common/src/tempo/tests.rs @@ -0,0 +1,55 @@ +use alloy_provider::{Provider, ProviderBuilder}; +use eyre::WrapErr; +use foundry_evm_hardforks::TempoHardfork; +use serde::Deserialize; +use std::env; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct TempoForkSchedule { + schedule: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ForkInfo { + name: String, +} + +async fn check_fork_schedule(rpc_url: &str) -> eyre::Result<()> { + let provider = ProviderBuilder::new().connect_http(rpc_url.parse()?); + let schedule: TempoForkSchedule = provider.raw_request("tempo_forkSchedule".into(), ()).await?; + for fork in &schedule.schedule { + fork.name.parse::()?; + } + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_schedule_parses_configured_rpcs() -> eyre::Result<()> { + let mut checked_any = false; + + for (network, env_key) in [ + ("mainnet", "TEMPO_MAINNET_RPC_URL"), + ("testnet", "TEMPO_TESTNET_RPC_URL"), + ("devnet", "TEMPO_DEVNET_RPC_URL"), + ] { + let rpc_url = match env::var(env_key) { + Ok(url) if !url.is_empty() => url, + _ => continue, + }; + checked_any = true; + + check_fork_schedule(&rpc_url) + .await + .wrap_err_with(|| format!("[{network}] {env_key}={rpc_url}"))?; + } + + if !checked_any { + let _ = crate::sh_eprintln!( + "Missing Tempo RPC env vars. Skipping Tempo fork schedule compatibility test." + ); + } + + Ok(()) +} diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index 931c29948ff01..6fe1672d55000 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -1,18 +1,17 @@ //! terminal utils use foundry_compilers::{ artifacts::remappings::Remapping, - report::{self, BasicStdoutReporter, Reporter}, + report::{self, Reporter}, }; -use foundry_config::find_project_root; use itertools::Itertools; use semver::Version; use std::{ io, - io::{prelude::*, IsTerminal}, + io::{IsTerminal, prelude::*}, path::{Path, PathBuf}, sync::{ - mpsc::{self, TryRecvError}, LazyLock, + mpsc::{self, TryRecvError}, }, thread, time::Duration, @@ -70,12 +69,12 @@ impl Spinner { pub fn tick(&mut self) { if self.no_progress { - return + return; } let indicator = self.indicator[self.idx % self.indicator.len()].green(); let indicator = Paint::new(format!("[{indicator}]")).bold(); - let _ = sh_print!("\r\x33[2K\r{indicator} {}", self.message); + let _ = sh_print!("\r\x1B[2K\r{indicator} {}", self.message); io::stdout().flush().unwrap(); self.idx = self.idx.wrapping_add(1); @@ -94,6 +93,8 @@ impl Spinner { pub struct SpinnerReporter { /// The sender to the spinner thread. sender: mpsc::Sender, + /// The project root path for trimming file paths in verbose output. + project_root: Option, } impl SpinnerReporter { @@ -102,7 +103,7 @@ impl SpinnerReporter { /// The spinner's message will be updated via the `reporter` events /// /// On drop the channel will disconnect and the thread will terminate - pub fn spawn() -> Self { + pub fn spawn(project_root: Option) -> Self { let (sender, rx) = mpsc::channel::(); std::thread::Builder::new() @@ -121,7 +122,7 @@ impl SpinnerReporter { // end with a newline let _ = sh_println!(); let _ = ack.send(()); - break + break; } Err(TryRecvError::Disconnected) => break, Err(TryRecvError::Empty) => thread::sleep(Duration::from_millis(100)), @@ -130,7 +131,7 @@ impl SpinnerReporter { }) .expect("failed to spawn thread"); - Self { sender } + Self { sender, project_root } } fn send_msg(&self, msg: impl Into) { @@ -157,14 +158,12 @@ impl Reporter for SpinnerReporter { // Verbose message with dirty files displays first to avoid being overlapped // by the spinner in .tick() which prints repeatedly over the same line. if shell::verbosity() >= 5 { - let project_root = find_project_root(None); - self.send_msg(format!( "Files to compile:\n{}", dirty_files .iter() .map(|path| { - let trimmed_path = if let Ok(project_root) = &project_root { + let trimmed_path = if let Some(project_root) = &self.project_root { path.strip_prefix(project_root).unwrap_or(path) } else { path @@ -210,19 +209,6 @@ impl Reporter for SpinnerReporter { } } -/// If the output medium is terminal, this calls `f` within the [`SpinnerReporter`] that displays a -/// spinning cursor to display solc progress. -/// -/// If no terminal is available this falls back to common `println!` in [`BasicStdoutReporter`]. -pub fn with_spinner_reporter(f: impl FnOnce() -> T) -> T { - let reporter = if TERM_SETTINGS.indicate_progress { - report::Report::new(SpinnerReporter::spawn()) - } else { - report::Report::new(BasicStdoutReporter::default()) - }; - report::with_scoped(&reporter, f) -} - #[cfg(test)] mod tests { use super::*; @@ -240,7 +226,7 @@ mod tests { #[test] fn can_format_properly() { - let r = SpinnerReporter::spawn(); + let r = SpinnerReporter::spawn(None); let remappings: Vec = vec![ "library/=library/src/".parse().unwrap(), "weird-erc20/=lib/weird-erc20/src/".parse().unwrap(), diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index f5f3ea14ce460..89d4d4c8f8575 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -8,7 +8,7 @@ use std::{fmt, path::Path}; /// Test filter. pub trait TestFilter: Send + Sync { /// Returns whether the test should be included. - fn matches_test(&self, test_name: &str) -> bool; + fn matches_test(&self, test_signature: &str) -> bool; /// Returns whether the contract should be included. fn matches_contract(&self, contract_name: &str) -> bool; @@ -17,6 +17,30 @@ pub trait TestFilter: Send + Sync { fn matches_path(&self, path: &Path) -> bool; } +impl<'a> dyn TestFilter + 'a { + /// Returns `true` if the function is a test function that matches the given filter. + pub fn matches_test_function(&self, func: &Function) -> bool { + func.is_any_test() && self.matches_test(&func.signature()) + } +} + +/// A test filter that filters out nothing. +#[derive(Clone, Debug, Default)] +pub struct EmptyTestFilter(()); +impl TestFilter for EmptyTestFilter { + fn matches_test(&self, _test_signature: &str) -> bool { + true + } + + fn matches_contract(&self, _contract_name: &str) -> bool { + true + } + + fn matches_path(&self, _path: &Path) -> bool { + true + } +} + /// Extension trait for `Function`. pub trait TestFunctionExt { /// Returns the kind of test function. @@ -69,6 +93,15 @@ pub trait TestFunctionExt { self.test_function_kind().is_fixture() } + /// Returns `true` if this function is test reserved function. + fn is_reserved(&self) -> bool { + self.is_any_test() + || self.is_setup() + || self.is_before_test_setup() + || self.is_after_invariant() + || self.is_fixture() + } + #[doc(hidden)] fn tfe_as_str(&self) -> &str; #[doc(hidden)] @@ -116,6 +149,8 @@ pub enum TestFunctionKind { FuzzTest { should_fail: bool }, /// `invariant*` or `statefulFuzz*`. InvariantTest, + /// `table*`, with arguments. + TableTest, /// `afterInvariant`. AfterInvariant, /// `fixture*`. @@ -126,7 +161,6 @@ pub enum TestFunctionKind { impl TestFunctionKind { /// Classify a function. - #[inline] pub fn classify(name: &str, has_inputs: bool) -> Self { match () { _ if name.starts_with("test") => { @@ -140,7 +174,8 @@ impl TestFunctionKind { _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => { Self::InvariantTest } - _ if name.eq_ignore_ascii_case("setup") => Self::Setup, + _ if name.starts_with("table") => Self::TableTest, + _ if name.eq_ignore_ascii_case("setup") && !has_inputs => Self::Setup, _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant, _ if name.starts_with("fixture") => Self::Fixture, _ => Self::Unknown, @@ -156,6 +191,7 @@ impl TestFunctionKind { Self::FuzzTest { should_fail: false } => "fuzz", Self::FuzzTest { should_fail: true } => "fuzz fail", Self::InvariantTest => "invariant", + Self::TableTest => "table", Self::AfterInvariant => "afterInvariant", Self::Fixture => "fixture", Self::Unknown => "unknown", @@ -171,7 +207,10 @@ impl TestFunctionKind { /// Returns `true` if this function is a unit, fuzz, or invariant test. #[inline] pub const fn is_any_test(&self) -> bool { - matches!(self, Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::InvariantTest) + matches!( + self, + Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::TableTest | Self::InvariantTest + ) } /// Returns `true` if this function is a test that should fail. @@ -182,7 +221,7 @@ impl TestFunctionKind { /// Returns `true` if this function is a unit test. #[inline] - pub fn is_unit_test(&self) -> bool { + pub const fn is_unit_test(&self) -> bool { matches!(self, Self::UnitTest { .. }) } @@ -198,6 +237,12 @@ impl TestFunctionKind { matches!(self, Self::InvariantTest) } + /// Returns `true` if this function is a table test. + #[inline] + pub const fn is_table_test(&self) -> bool { + matches!(self, Self::TableTest) + } + /// Returns `true` if this function is an `afterInvariant` function. #[inline] pub const fn is_after_invariant(&self) -> bool { @@ -240,3 +285,18 @@ impl ErrorExt for T { alloy_sol_types::Revert::from(self.to_string()).abi_encode().into() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_setup_classification() { + // setUp() with no params should be classified as Setup + assert_eq!(TestFunctionKind::classify("setUp", false), TestFunctionKind::Setup); + + // setUp(bytes memory) with params should NOT be classified as Setup + // This is common in Gnosis Safe/Zodiac modules + assert_eq!(TestFunctionKind::classify("setUp", true), TestFunctionKind::Unknown); + } +} diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs deleted file mode 100644 index 0933764b5c6d1..0000000000000 --- a/crates/common/src/transactions.rs +++ /dev/null @@ -1,294 +0,0 @@ -//! Wrappers for transactions. - -use alloy_consensus::{transaction::SignerRecoverable, Transaction, TxEnvelope}; -use alloy_eips::eip7702::SignedAuthorization; -use alloy_network::AnyTransactionReceipt; -use alloy_primitives::{Address, TxKind, U256}; -use alloy_provider::{ - network::{AnyNetwork, ReceiptResponse, TransactionBuilder}, - Provider, -}; -use alloy_rpc_types::{BlockId, TransactionRequest}; -use alloy_serde::WithOtherFields; -use eyre::Result; -use foundry_common_fmt::UIfmt; -use serde::{Deserialize, Serialize}; - -/// Helper type to carry a transaction along with an optional revert reason -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TransactionReceiptWithRevertReason { - /// The underlying transaction receipt - #[serde(flatten)] - pub receipt: AnyTransactionReceipt, - - /// The revert reason string if the transaction status is failed - #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] - pub revert_reason: Option, -} - -impl TransactionReceiptWithRevertReason { - /// Returns if the status of the transaction is 0 (failure) - pub fn is_failure(&self) -> bool { - !self.receipt.inner.inner.inner.receipt.status.coerce_status() - } - - /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert - /// reason was not successfully updated - pub async fn update_revert_reason>( - &mut self, - provider: &P, - ) -> Result<()> { - self.revert_reason = self.fetch_revert_reason(provider).await?; - Ok(()) - } - - async fn fetch_revert_reason>( - &self, - provider: &P, - ) -> Result> { - if !self.is_failure() { - return Ok(None) - } - - let transaction = provider - .get_transaction_by_hash(self.receipt.transaction_hash) - .await - .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? - .ok_or_else(|| eyre::eyre!("transaction not found"))?; - - if let Some(block_hash) = self.receipt.block_hash { - let mut call_request: WithOtherFields = - transaction.inner.inner.clone_inner().into(); - call_request.set_from(transaction.inner.inner.signer()); - match provider.call(call_request).block(BlockId::Hash(block_hash.into())).await { - Err(e) => return Ok(extract_revert_reason(e.to_string())), - Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), - } - } - eyre::bail!("unable to fetch block_hash") - } -} - -impl From for TransactionReceiptWithRevertReason { - fn from(receipt: AnyTransactionReceipt) -> Self { - Self { receipt, revert_reason: None } - } -} - -impl From for AnyTransactionReceipt { - fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { - receipt_with_reason.receipt - } -} - -impl UIfmt for TransactionReceiptWithRevertReason { - fn pretty(&self) -> String { - if let Some(revert_reason) = &self.revert_reason { - format!( - "{} -revertReason {}", - self.receipt.pretty(), - revert_reason - ) - } else { - self.receipt.pretty() - } - } -} - -impl UIfmt for TransactionMaybeSigned { - fn pretty(&self) -> String { - match self { - Self::Signed { tx, .. } => tx.pretty(), - Self::Unsigned(tx) => format!( - " -accessList {} -chainId {} -gasLimit {} -gasPrice {} -input {} -maxFeePerBlobGas {} -maxFeePerGas {} -maxPriorityFeePerGas {} -nonce {} -to {} -type {} -value {}", - tx.access_list - .as_ref() - .map(|a| a.iter().collect::>()) - .unwrap_or_default() - .pretty(), - tx.chain_id.pretty(), - tx.gas_limit().unwrap_or_default(), - tx.gas_price.pretty(), - tx.input.input.pretty(), - tx.max_fee_per_blob_gas.pretty(), - tx.max_fee_per_gas.pretty(), - tx.max_priority_fee_per_gas.pretty(), - tx.nonce.pretty(), - tx.to.as_ref().map(|a| a.to()).unwrap_or_default().pretty(), - tx.transaction_type.unwrap_or_default(), - tx.value.pretty(), - ), - } - } -} - -fn extract_revert_reason>(error_string: S) -> Option { - let message_substr = "execution reverted: "; - error_string - .as_ref() - .find(message_substr) - .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) -} - -/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt -pub fn get_pretty_tx_receipt_attr( - receipt: &TransactionReceiptWithRevertReason, - attr: &str, -) -> Option { - match attr { - "blockHash" | "block_hash" => Some(receipt.receipt.block_hash.pretty()), - "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), - "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), - "cumulativeGasUsed" | "cumulative_gas_used" => { - Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) - } - "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.to_string()) - } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), - "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), - "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root().pretty()), - "status" | "statusCode" | "status_code" => { - Some(receipt.receipt.inner.inner.inner.receipt.status.pretty()) - } - "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), - "transactionIndex" | "transaction_index" => { - Some(receipt.receipt.transaction_index.pretty()) - } - "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), - "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), - _ => None, - } -} - -/// Used for broadcasting transactions -/// A transaction can either be a [`TransactionRequest`] waiting to be signed -/// or a [`TxEnvelope`], already signed -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum TransactionMaybeSigned { - Signed { - #[serde(flatten)] - tx: TxEnvelope, - from: Address, - }, - Unsigned(WithOtherFields), -} - -impl TransactionMaybeSigned { - /// Creates a new (unsigned) transaction for broadcast - pub fn new(tx: WithOtherFields) -> Self { - Self::Unsigned(tx) - } - - /// Creates a new signed transaction for broadcast. - pub fn new_signed( - tx: TxEnvelope, - ) -> core::result::Result { - let from = tx.recover_signer()?; - Ok(Self::Signed { tx, from }) - } - - pub fn is_unsigned(&self) -> bool { - matches!(self, Self::Unsigned(_)) - } - - pub fn as_unsigned_mut(&mut self) -> Option<&mut WithOtherFields> { - match self { - Self::Unsigned(tx) => Some(tx), - _ => None, - } - } - - pub fn from(&self) -> Option
{ - match self { - Self::Signed { from, .. } => Some(*from), - Self::Unsigned(tx) => tx.from, - } - } - - pub fn input(&self) -> Option<&[u8]> { - match self { - Self::Signed { tx, .. } => Some(tx.input()), - Self::Unsigned(tx) => tx.input.input().map(|i| i.as_ref()), - } - } - - pub fn to(&self) -> Option { - match self { - Self::Signed { tx, .. } => Some(tx.kind()), - Self::Unsigned(tx) => tx.to, - } - } - - pub fn value(&self) -> Option { - match self { - Self::Signed { tx, .. } => Some(tx.value()), - Self::Unsigned(tx) => tx.value, - } - } - - pub fn gas(&self) -> Option { - match self { - Self::Signed { tx, .. } => Some(tx.gas_limit() as u128), - Self::Unsigned(tx) => tx.gas_limit().map(|g| g as u128), - } - } - - pub fn nonce(&self) -> Option { - match self { - Self::Signed { tx, .. } => Some(tx.nonce()), - Self::Unsigned(tx) => tx.nonce, - } - } - - pub fn authorization_list(&self) -> Option> { - match self { - Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()), - Self::Unsigned(tx) => tx.authorization_list.as_deref().map(|auths| auths.to_vec()), - } - .filter(|auths| !auths.is_empty()) - } -} - -impl From for TransactionMaybeSigned { - fn from(tx: TransactionRequest) -> Self { - Self::new(WithOtherFields::new(tx)) - } -} - -impl TryFrom for TransactionMaybeSigned { - type Error = alloy_consensus::crypto::RecoveryError; - - fn try_from(tx: TxEnvelope) -> core::result::Result { - Self::new_signed(tx) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_revert_reason() { - let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; - let error_string_2 = "server returned an error response: error code 3: Invalid signature"; - - assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); - assert_eq!(extract_revert_reason(error_string_2), None); - } -} diff --git a/crates/common/src/transactions/broadcast.rs b/crates/common/src/transactions/broadcast.rs new file mode 100644 index 0000000000000..95bfb679c616f --- /dev/null +++ b/crates/common/src/transactions/broadcast.rs @@ -0,0 +1,136 @@ +use alloy_consensus::Transaction; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_network::{Network, NetworkTransactionBuilder, TransactionBuilder}; +use alloy_primitives::{Address, Bytes, U256}; +use foundry_common_fmt::UIfmt; +use serde::{Deserialize, Serialize}; + +use super::FoundryTransactionBuilder; + +/// Used for broadcasting transactions +/// A transaction can either be a `TransactionRequest` waiting to be signed +/// or a `TxEnvelope`, already signed +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TransactionMaybeSigned { + Signed { + #[serde(flatten)] + tx: N::TxEnvelope, + from: Address, + }, + Unsigned(N::TransactionRequest), +} + +impl TransactionMaybeSigned { + /// Creates a new (unsigned) transaction for broadcast + pub const fn new(tx: N::TransactionRequest) -> Self { + Self::Unsigned(tx) + } + + pub const fn is_unsigned(&self) -> bool { + matches!(self, Self::Unsigned(_)) + } + + pub const fn as_unsigned_mut(&mut self) -> Option<&mut N::TransactionRequest> { + match self { + Self::Unsigned(tx) => Some(tx), + _ => None, + } + } + + pub fn from(&self) -> Option
{ + match self { + Self::Signed { from, .. } => Some(*from), + Self::Unsigned(tx) => tx.from(), + } + } + + pub fn input(&self) -> Option<&Bytes> { + match self { + Self::Signed { tx, .. } => Some(tx.input()), + Self::Unsigned(tx) => tx.input(), + } + } + + pub fn to(&self) -> Option
{ + match self { + Self::Signed { tx, .. } => tx.to(), + Self::Unsigned(tx) => tx.to(), + } + } + + pub fn value(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.value()), + Self::Unsigned(tx) => tx.value(), + } + } + + pub fn gas(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.gas_limit() as u128), + Self::Unsigned(tx) => tx.gas_limit().map(|g| g as u128), + } + } + + pub fn nonce(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.nonce()), + Self::Unsigned(tx) => tx.nonce(), + } + } + + pub fn authorization_list(&self) -> Option> + where + N::TransactionRequest: FoundryTransactionBuilder, + { + match self { + Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()), + Self::Unsigned(tx) => tx.authorization_list().cloned(), + } + .filter(|auths| !auths.is_empty()) + } +} + +impl UIfmt for TransactionMaybeSigned +where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, +{ + fn pretty(&self) -> String { + match self { + Self::Signed { tx, .. } => tx.pretty(), + Self::Unsigned(tx) => format!( + " +accessList {} +chainId {} +gasLimit {} +gasPrice {} +input {} +maxFeePerBlobGas {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +to {} +type {} +value {}", + tx.access_list() + .as_ref() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + tx.chain_id().pretty(), + tx.gas_limit().unwrap_or_default(), + tx.gas_price().pretty(), + tx.input().pretty(), + tx.max_fee_per_blob_gas().pretty(), + tx.max_fee_per_gas().pretty(), + tx.max_priority_fee_per_gas().pretty(), + tx.nonce().pretty(), + tx.to().pretty(), + NetworkTransactionBuilder::::output_tx_type(tx), + tx.value().pretty(), + ), + } + } +} diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs new file mode 100644 index 0000000000000..aa4c971680d00 --- /dev/null +++ b/crates/common/src/transactions/builder.rs @@ -0,0 +1,553 @@ +use std::num::NonZeroU64; + +use alloy_consensus::{ + BlobTransactionSidecar, BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant, +}; +use alloy_eips::{Encodable2718, eip7702::SignedAuthorization}; +use alloy_network::{AnyNetwork, Ethereum, Network, NetworkTransactionBuilder}; +use alloy_primitives::{Address, B256, Signature, TxKind, U256}; +use alloy_provider::Provider; +use alloy_signer::Signer; +use eyre::Result; +#[cfg(feature = "optimism")] +use op_alloy_network::Optimism; +#[cfg(feature = "optimism")] +use op_alloy_rpc_types::OpTransactionRequest; +use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; +use tempo_primitives::{ + TempoSignature, + transaction::{Call, KeychainSignature, PrimitiveSignature, SignedKeyAuthorization}, +}; + +/// Composite transaction builder trait for Foundry transactions. +/// +/// This extends the base `TransactionBuilder` trait with the same methods as +/// [`alloy_network::TransactionBuilder4844`] for handling blob transaction sidecars, and +/// [`alloy_network::TransactionBuilder7702`] for handling EIP-7702 authorization lists. +/// +/// By default, all methods have no-op implementations, so this can be implemented for any Network. +/// +/// If the Network supports Eip4844 blob transactions implement these methods: +/// - [`FoundryTransactionBuilder::max_fee_per_blob_gas`] +/// - [`FoundryTransactionBuilder::set_max_fee_per_blob_gas`] +/// - [`FoundryTransactionBuilder::blob_versioned_hashes`] +/// - [`FoundryTransactionBuilder::set_blob_versioned_hashes`] +/// - [`FoundryTransactionBuilder::blob_sidecar`] +/// - [`FoundryTransactionBuilder::set_blob_sidecar`] +/// +/// If the Network supports EIP-7702 authorization lists, implement these methods: +/// - [`FoundryTransactionBuilder::authorization_list`] +/// - [`FoundryTransactionBuilder::set_authorization_list`] +/// +/// If the Network supports Tempo transactions, implement these methods: +/// - [`FoundryTransactionBuilder::set_fee_token`] +/// - [`FoundryTransactionBuilder::set_nonce_key`] +/// - [`FoundryTransactionBuilder::set_key_id`] +/// - [`FoundryTransactionBuilder::set_valid_before`] +/// - [`FoundryTransactionBuilder::set_valid_after`] +/// - [`FoundryTransactionBuilder::set_fee_payer_signature`] +pub trait FoundryTransactionBuilder: NetworkTransactionBuilder { + /// Reset gas limit + fn reset_gas_limit(&mut self); + + /// Get the max fee per blob gas for the transaction. + fn max_fee_per_blob_gas(&self) -> Option { + None + } + + /// Set the max fee per blob gas for the transaction. + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + /// Builder-pattern method for setting max fee per blob gas. + fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self { + self.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + self + } + + /// Gets the EIP-4844 blob versioned hashes of the transaction. + /// + /// These may be set independently of the sidecar, e.g. when the sidecar + /// has been pruned but the hashes are still needed for `eth_call`. + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + None + } + + /// Sets the EIP-4844 blob versioned hashes of the transaction. + fn set_blob_versioned_hashes(&mut self, _hashes: Vec) {} + + /// Builder-pattern method for setting the EIP-4844 blob versioned hashes. + fn with_blob_versioned_hashes(mut self, hashes: Vec) -> Self { + self.set_blob_versioned_hashes(hashes); + self + } + + /// Gets the blob sidecar (either EIP-4844 or EIP-7594 variant) of the transaction. + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> { + None + } + + /// Sets the blob sidecar (either EIP-4844 or EIP-7594 variant) of the transaction. + /// + /// Note: This will also set the versioned blob hashes accordingly: + /// [BlobTransactionSidecarVariant::versioned_hashes] + fn set_blob_sidecar(&mut self, _sidecar: BlobTransactionSidecarVariant) {} + + /// Builder-pattern method for setting the blob sidecar of the transaction. + fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecarVariant) -> Self { + self.set_blob_sidecar(sidecar); + self + } + + /// Gets the EIP-4844 blob sidecar if the current sidecar is of that variant. + fn blob_sidecar_4844(&self) -> Option<&BlobTransactionSidecar> { + self.blob_sidecar().and_then(|s| s.as_eip4844()) + } + + /// Sets the EIP-4844 blob sidecar of the transaction. + fn set_blob_sidecar_4844(&mut self, sidecar: BlobTransactionSidecar) { + self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip4844(sidecar)); + } + + /// Builder-pattern method for setting the EIP-4844 blob sidecar of the transaction. + fn with_blob_sidecar_4844(mut self, sidecar: BlobTransactionSidecar) -> Self { + self.set_blob_sidecar_4844(sidecar); + self + } + + /// Gets the EIP-7594 blob sidecar if the current sidecar is of that variant. + fn blob_sidecar_7594(&self) -> Option<&BlobTransactionSidecarEip7594> { + self.blob_sidecar().and_then(|s| s.as_eip7594()) + } + + /// Sets the EIP-7594 blob sidecar of the transaction. + fn set_blob_sidecar_7594(&mut self, sidecar: BlobTransactionSidecarEip7594) { + self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip7594(sidecar)); + } + + /// Builder-pattern method for setting the EIP-7594 blob sidecar of the transaction. + fn with_blob_sidecar_7594(mut self, sidecar: BlobTransactionSidecarEip7594) -> Self { + self.set_blob_sidecar_7594(sidecar); + self + } + + /// Get the EIP-7702 authorization list for the transaction. + fn authorization_list(&self) -> Option<&Vec> { + None + } + + /// Sets the EIP-7702 authorization list. + fn set_authorization_list(&mut self, _authorization_list: Vec) {} + + /// Builder-pattern method for setting the authorization list. + fn with_authorization_list(mut self, authorization_list: Vec) -> Self { + self.set_authorization_list(authorization_list); + self + } + + /// Get the fee token for a Tempo transaction. + fn fee_token(&self) -> Option
{ + None + } + + /// Set the fee token for a Tempo transaction. + fn set_fee_token(&mut self, _fee_token: Address) {} + + /// Builder-pattern method for setting the Tempo fee token. + fn with_fee_token(mut self, fee_token: Address) -> Self { + self.set_fee_token(fee_token); + self + } + + /// Get the 2D nonce key for a Tempo transaction. + fn nonce_key(&self) -> Option { + None + } + + /// Set the 2D nonce key for the Tempo transaction. + fn set_nonce_key(&mut self, _nonce_key: U256) {} + + /// Builder-pattern method for setting a 2D nonce key for a Tempo transaction. + fn with_nonce_key(mut self, nonce_key: U256) -> Self { + self.set_nonce_key(nonce_key); + self + } + + /// Get the access key ID for a Tempo transaction. + fn key_id(&self) -> Option
{ + None + } + + /// Set the access key ID for a Tempo transaction. + /// + /// Used during gas estimation to override the key_id that would normally be + /// recovered from the signature. + fn set_key_id(&mut self, _key_id: Address) {} + + /// Builder-pattern method for setting the Tempo access key ID. + fn with_key_id(mut self, key_id: Address) -> Self { + self.set_key_id(key_id); + self + } + + /// Get the valid_before timestamp for a Tempo expiring nonce transaction. + fn valid_before(&self) -> Option { + None + } + + /// Set the valid_before timestamp for a Tempo expiring nonce transaction. + fn set_valid_before(&mut self, _valid_before: NonZeroU64) {} + + /// Builder-pattern method for setting the valid_before timestamp. + fn with_valid_before(mut self, valid_before: NonZeroU64) -> Self { + self.set_valid_before(valid_before); + self + } + + /// Get the valid_after timestamp for a Tempo expiring nonce transaction. + fn valid_after(&self) -> Option { + None + } + + /// Set the valid_after timestamp for a Tempo expiring nonce transaction. + fn set_valid_after(&mut self, _valid_after: NonZeroU64) {} + + /// Builder-pattern method for setting the valid_after timestamp. + fn with_valid_after(mut self, valid_after: NonZeroU64) -> Self { + self.set_valid_after(valid_after); + self + } + + /// Get the fee payer (sponsor) signature for a Tempo sponsored transaction. + fn fee_payer_signature(&self) -> Option { + None + } + + /// Set the fee payer (sponsor) signature for a Tempo sponsored transaction. + fn set_fee_payer_signature(&mut self, _signature: Signature) {} + + /// Builder-pattern method for setting the fee payer signature. + fn with_fee_payer_signature(mut self, signature: Signature) -> Self { + self.set_fee_payer_signature(signature); + self + } + + /// Computes the sponsor (fee payer) signature hash for this transaction. + /// + /// This builds an unsigned consensus-level transaction from the request and computes + /// the hash that a sponsor needs to sign. Returns `None` for networks that don't + /// support sponsored transactions. + fn compute_sponsor_hash(&self, _from: Address) -> Option { + None + } + + /// Set the key authorization for a Tempo transaction. + /// + /// Embeds a [`SignedKeyAuthorization`] in the transaction body, provisioning the access key + /// on-chain as part of this transaction. + fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} + + /// Embeds key authorization before gas estimation/signing if the access key is not yet + /// provisioned on-chain. + /// + /// This mirrors the mutation performed by [`Self::sign_with_access_key`], but makes the final + /// transaction body available before fee-payer sponsor digests are computed. + fn prepare_access_key_authorization<'a>( + &'a mut self, + _provider: &'a impl Provider, + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + async { Ok(()) } + } + + /// Converts a CREATE transaction into an AA-compatible call entry. + /// + /// Tempo AA transactions use a `calls` list instead of `to`+`input`. Must be + /// called before gas estimation so the RPC sees the correct tx structure. + /// No-op for non-Tempo networks. + fn convert_create_to_call(&mut self) {} + + /// Clears the `to` and `value` fields for batch transactions that use `calls`. + /// + /// In Tempo AA batch transactions, targets are specified in the `calls` field, not in `to`. + /// If `to` is set, `build_aa()` would add a spurious extra call. Must be called after + /// `prepare()` sets `kind`/`to` but before gas estimation. + /// No-op for non-Tempo networks. + fn clear_batch_to(&mut self) {} + + /// Signs the transaction using an access key (keychain mode). + /// + /// If `key_authorization` is provided and the key is not yet provisioned on-chain, + /// embeds the authorization in the transaction before signing. + /// + /// The default implementation returns an error. Only `TempoNetwork` supports this. + fn sign_with_access_key( + self, + _provider: &impl Provider, + _signer: &(impl Signer + Sync), + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&SignedKeyAuthorization>, + ) -> impl Future>> + Send { + async { eyre::bail!("access key signing is not supported for this network") } + } +} + +impl FoundryTransactionBuilder for ::TransactionRequest { + fn reset_gas_limit(&mut self) { + self.gas = None; + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.max_fee_per_blob_gas + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas); + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.blob_versioned_hashes.as_deref() + } + + fn set_blob_versioned_hashes(&mut self, hashes: Vec) { + self.blob_versioned_hashes = Some(hashes); + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> { + self.sidecar.as_ref() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) { + self.sidecar = Some(sidecar); + self.populate_blob_hashes(); + } + + fn authorization_list(&self) -> Option<&Vec> { + self.authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.authorization_list = Some(authorization_list); + } +} + +impl FoundryTransactionBuilder for ::TransactionRequest { + fn reset_gas_limit(&mut self) { + self.gas = None; + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.max_fee_per_blob_gas + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas); + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.blob_versioned_hashes.as_deref() + } + + fn set_blob_versioned_hashes(&mut self, hashes: Vec) { + self.blob_versioned_hashes = Some(hashes); + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> { + self.sidecar.as_ref() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) { + self.sidecar = Some(sidecar); + self.populate_blob_hashes(); + } + + fn authorization_list(&self) -> Option<&Vec> { + self.authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.authorization_list = Some(authorization_list); + } +} + +#[cfg(feature = "optimism")] +impl FoundryTransactionBuilder for OpTransactionRequest { + fn reset_gas_limit(&mut self) { + self.as_mut().gas = None; + } + + fn authorization_list(&self) -> Option<&Vec> { + self.as_ref().authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.as_mut().authorization_list = Some(authorization_list); + } +} + +impl FoundryTransactionBuilder for ::TransactionRequest { + fn reset_gas_limit(&mut self) { + self.gas = None; + } + + fn authorization_list(&self) -> Option<&Vec> { + self.authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.authorization_list = Some(authorization_list); + } + + fn fee_token(&self) -> Option
{ + self.fee_token + } + + fn set_fee_token(&mut self, fee_token: Address) { + self.fee_token = Some(fee_token); + } + + fn nonce_key(&self) -> Option { + self.nonce_key + } + + fn set_nonce_key(&mut self, nonce_key: U256) { + self.nonce_key = Some(nonce_key); + } + + fn key_id(&self) -> Option
{ + self.key_id + } + + fn set_key_id(&mut self, key_id: Address) { + self.key_id = Some(key_id); + } + + fn valid_before(&self) -> Option { + self.valid_before + } + + fn set_valid_before(&mut self, valid_before: NonZeroU64) { + self.valid_before = Some(valid_before); + } + + fn valid_after(&self) -> Option { + self.valid_after + } + + fn set_valid_after(&mut self, valid_after: NonZeroU64) { + self.valid_after = Some(valid_after); + } + + fn fee_payer_signature(&self) -> Option { + self.fee_payer_signature + } + + fn set_fee_payer_signature(&mut self, signature: Signature) { + self.fee_payer_signature = Some(signature); + } + + fn compute_sponsor_hash(&self, from: Address) -> Option { + let tx = self.clone().build_aa().ok()?; + Some(tx.fee_payer_signature_hash(from)) + } + + fn set_key_authorization(&mut self, key_authorization: SignedKeyAuthorization) { + self.key_authorization = Some(key_authorization); + } + + fn prepare_access_key_authorization<'a>( + &'a mut self, + provider: &'a impl Provider, + wallet_address: Address, + key_address: Address, + key_authorization: Option<&'a SignedKeyAuthorization>, + ) -> impl Future> + Send + 'a + where + Self: Send, + { + let auth = key_authorization.cloned(); + + async move { + if let Some(auth) = auth { + let is_provisioned = provider + .get_keychain_key(wallet_address, key_address) + .await + .map(|info| info.keyId != Address::ZERO) + .unwrap_or(false); + + if !is_provisioned { + self.set_key_authorization(auth); + } + } + + Ok(()) + } + } + + fn convert_create_to_call(&mut self) { + if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) { + let input = self.inner.input.input().cloned().unwrap_or_default(); + let value = self.inner.value.unwrap_or(U256::ZERO); + self.calls.push(Call { to: TxKind::Create, value, input }); + self.inner.input = Default::default(); + self.inner.value = None; + self.inner.to = None; + } + } + + fn clear_batch_to(&mut self) { + if !self.calls.is_empty() { + self.inner.to = None; + self.inner.value = None; + } + } + + fn sign_with_access_key( + mut self, + provider: &impl Provider, + signer: &(impl Signer + Sync), + wallet_address: Address, + key_address: Address, + key_authorization: Option<&SignedKeyAuthorization>, + ) -> impl Future>> + Send { + let auth = key_authorization.cloned(); + let provisioning_fut = provider.get_keychain_key(wallet_address, key_address); + + async move { + if let Some(auth) = auth { + let is_provisioned = + provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false); + + if !is_provisioned && self.key_authorization.is_none() { + if self.fee_payer_signature.is_some() { + eyre::bail!( + "cannot add Tempo key authorization after fee payer signature was attached" + ); + } + self.set_key_authorization(auth); + } + } + + let tempo_tx = self + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + Ok(buf) + } + } +} diff --git a/crates/common/src/transactions/mod.rs b/crates/common/src/transactions/mod.rs new file mode 100644 index 0000000000000..74943c81f44fc --- /dev/null +++ b/crates/common/src/transactions/mod.rs @@ -0,0 +1,9 @@ +//! Wrappers for transactions. + +mod broadcast; +mod builder; +mod receipt; + +pub use broadcast::*; +pub use builder::*; +pub use receipt::*; diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs new file mode 100644 index 0000000000000..c2e34419248c4 --- /dev/null +++ b/crates/common/src/transactions/receipt.rs @@ -0,0 +1,154 @@ +use alloy_network::{AnyNetwork, AnyTransactionReceipt, Network, TransactionResponse}; +use alloy_primitives::Address; +use alloy_provider::{ + Provider, + network::{ReceiptResponse, TransactionBuilder}, +}; +use alloy_rpc_types::{BlockId, TransactionReceipt}; +use eyre::Result; +use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; +#[cfg(feature = "optimism")] +use op_alloy_rpc_types::OpTransactionReceipt; +use serde::{Deserialize, Serialize}; +use tempo_alloy::rpc::TempoTransactionReceipt; + +/// Helper trait providing `contract_address` setter for generic `ReceiptResponse` +pub trait FoundryReceiptResponse { + /// Sets address of the created contract, or `None` if the transaction was not a deployment. + fn set_contract_address(&mut self, contract_address: Address); +} + +impl FoundryReceiptResponse for TransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.contract_address = Some(contract_address); + } +} + +#[cfg(feature = "optimism")] +impl FoundryReceiptResponse for OpTransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.inner.contract_address = Some(contract_address); + } +} + +impl FoundryReceiptResponse for TempoTransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.contract_address = Some(contract_address); + } +} + +/// Helper type to carry a transaction along with an optional revert reason +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TransactionReceiptWithRevertReason { + /// The underlying transaction receipt + #[serde(flatten)] + pub receipt: N::ReceiptResponse, + + /// The revert reason string if the transaction status is failed + #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] + pub revert_reason: Option, +} + +impl TransactionReceiptWithRevertReason +where + N::TxEnvelope: Clone, + N::ReceiptResponse: UIfmtReceiptExt, +{ + /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert + /// reason was not successfully updated + pub async fn update_revert_reason(&mut self, provider: &dyn Provider) -> Result<()> { + self.revert_reason = self.fetch_revert_reason(provider).await?; + Ok(()) + } + + async fn fetch_revert_reason(&self, provider: &dyn Provider) -> Result> { + // If the transaction succeeded, there is no revert reason to fetch + if self.receipt.status() { + return Ok(None); + } + + let transaction = provider + .get_transaction_by_hash(self.receipt.transaction_hash()) + .await + .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? + .ok_or_else(|| eyre::eyre!("transaction not found"))?; + + if let Some(block_hash) = self.receipt.block_hash() { + let mut call_request: N::TransactionRequest = transaction.as_ref().clone().into(); + call_request.set_from(transaction.from()); + match provider.call(call_request).block(BlockId::Hash(block_hash.into())).await { + Err(e) => return Ok(extract_revert_reason(e.to_string())), + Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), + } + } + eyre::bail!("unable to fetch block_hash") + } +} + +impl From for TransactionReceiptWithRevertReason { + fn from(receipt: AnyTransactionReceipt) -> Self { + Self { receipt, revert_reason: None } + } +} + +impl From> for AnyTransactionReceipt { + fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { + receipt_with_reason.receipt + } +} + +impl UIfmt for TransactionReceiptWithRevertReason +where + N::ReceiptResponse: UIfmt, +{ + fn pretty(&self) -> String { + if let Some(revert_reason) = &self.revert_reason { + format!( + "{} +revertReason {}", + self.receipt.pretty(), + revert_reason + ) + } else { + self.receipt.pretty() + } + } +} + +fn extract_revert_reason>(error_string: S) -> Option { + let message_substr = "execution reverted: "; + error_string + .as_ref() + .find(message_substr) + .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) +} + +/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt with revert reason +pub fn get_pretty_receipt_w_reason_attr( + receipt: &TransactionReceiptWithRevertReason, + attr: &str, +) -> Option +where + N: Network, + N::ReceiptResponse: UIfmtReceiptExt, +{ + // Handle revert reason first, then delegate to the receipt formatting function + if matches!(attr, "revertReason" | "revert_reason") { + return Some(receipt.revert_reason.pretty()); + } + get_pretty_receipt_attr::(&receipt.receipt, attr) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_revert_reason() { + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; + + assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); + assert_eq!(extract_revert_reason(error_string_2), None); + } +} diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index a655d1e3d713f..fbf191233c3de 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -1,6 +1,18 @@ //! Uncategorised utilities. -use alloy_primitives::{keccak256, B256, U256}; +use alloy_primitives::{B256, Bytes, U256, hex, keccak256}; +use foundry_compilers::{ + Project, + artifacts::{BytecodeObject, SolcLanguage}, + error::SolcError, + flatten::{Flattener, FlattenerError}, +}; +use regex::Regex; +use std::{path::Path, sync::LazyLock}; + +static BYTECODE_PLACEHOLDER_RE: LazyLock = + LazyLock::new(|| Regex::new(r"__\$.{34}\$__").expect("invalid regex")); + /// Block on a future using the current tokio runtime on the current thread. pub fn block_on(future: F) -> F::Output { block_on_handle(&tokio::runtime::Handle::current(), future) @@ -38,19 +50,60 @@ pub fn erc7201(id: &str) -> B256 { keccak256(x.to_be_bytes::<32>()) & B256::from(!U256::from(0xff)) } -/// Utility function to ignore metadata hash of the given bytecode. +/// Utility function to find the start of the metadata in the bytecode. /// This assumes that the metadata is at the end of the bytecode. -pub fn ignore_metadata_hash(bytecode: &[u8]) -> &[u8] { +pub fn find_metadata_start(bytecode: &[u8]) -> Option { // Get the last two bytes of the bytecode to find the length of CBOR metadata. - let Some((rest, metadata_len_bytes)) = bytecode.split_last_chunk() else { return bytecode }; + let (rest, metadata_len_bytes) = bytecode.split_last_chunk()?; let metadata_len = u16::from_be_bytes(*metadata_len_bytes) as usize; if metadata_len > rest.len() { - return bytecode; + return None; } - let (rest, metadata) = rest.split_at(rest.len() - metadata_len); - if ciborium::from_reader::(metadata).is_ok() { - rest + ciborium::from_reader::(&rest[rest.len() - metadata_len..]) + .is_ok() + .then(|| rest.len() - metadata_len) +} + +/// Utility function to ignore metadata hash of the given bytecode. +/// This assumes that the metadata is at the end of the bytecode. +pub fn ignore_metadata_hash(bytecode: &[u8]) -> &[u8] { + if let Some(metadata) = find_metadata_start(bytecode) { + &bytecode[..metadata] } else { bytecode } } + +/// Strips all __$xxx$__ placeholders from the bytecode if it's an unlinked bytecode. +/// by replacing them with 20 zero bytes. +/// This is useful for matching bytecodes to a contract source, and for the source map, +/// in which the actual address of the placeholder isn't important. +pub fn strip_bytecode_placeholders(bytecode: &BytecodeObject) -> Option { + match &bytecode { + BytecodeObject::Bytecode(bytes) => Some(bytes.clone()), + BytecodeObject::Unlinked(s) => { + // Replace all __$xxx$__ placeholders with 20 zero bytes (40 hex chars) + let s = (*BYTECODE_PLACEHOLDER_RE).replace_all(s, "00".repeat(20)); + let bytes = hex::decode(s.as_bytes()); + Some(bytes.ok()?.into()) + } + } +} + +/// Flattens the given target of the project. Falls back to the old flattening implementation +/// if the target cannot be compiled successfully. This would be the case if the target has invalid +/// syntax. (e.g. Solang) +pub fn flatten(project: Project, target_path: &Path) -> eyre::Result { + // Save paths for fallback before Flattener::new takes ownership + let paths = project.paths.clone(); + let flattened = match Flattener::new(project, target_path) { + Ok(flattener) => Ok(flattener.flatten()), + Err(FlattenerError::Compilation(_)) => { + paths.with_language::().flatten(target_path) + } + Err(FlattenerError::Other(err)) => Err(err), + } + .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?; + + Ok(flattened) +} diff --git a/crates/common/src/version.rs b/crates/common/src/version.rs index f69457bf7c1e3..5d771c8345207 100644 --- a/crates/common/src/version.rs +++ b/crates/common/src/version.rs @@ -21,7 +21,8 @@ pub const LONG_VERSION: &str = concat!( pub const IS_NIGHTLY_VERSION: bool = option_env!("FOUNDRY_IS_NIGHTLY_VERSION").is_some(); /// The warning message for nightly versions. -pub const NIGHTLY_VERSION_WARNING_MESSAGE: &str = - "This is a nightly build of Foundry. It is recommended to use the latest stable version. \ - Visit https://book.getfoundry.sh/announcements for more information. \n\ +pub const NIGHTLY_VERSION_WARNING_MESSAGE: &str = "This is a nightly build of Foundry. It is recommended to use the latest stable version. \ To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment. \n"; + +/// The long SHA of the latest commit. +pub const COMMIT_SHA: &str = env!("FOUNDRY_COMMIT_SHA"); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 3bd2fa44b9e6a..4c6978e0d9750 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -20,10 +20,10 @@ foundry-compilers = { workspace = true, features = ["svm-solc"] } alloy-chains = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["serde"] } -revm.workspace = true +foundry-evm-hardforks.workspace = true +foundry-evm-networks.workspace = true -solar-parse.workspace = true -solar-interface.workspace = true +solar.workspace = true dirs.workspace = true dunce.workspace = true @@ -34,28 +34,33 @@ globset = "0.4" heck.workspace = true itertools.workspace = true mesc.workspace = true -number_prefix = "0.4" +unit-prefix = "0.5.2" +rayon.workspace = true regex.workspace = true -reqwest.workspace = true semver = { workspace = true, features = ["serde"] } serde_json.workspace = true serde.workspace = true soldeer-core.workspace = true thiserror.workspace = true toml = { workspace = true, features = ["preserve_order"] } -toml_edit = "0.22" +toml_edit.workspace = true tracing.workspace = true walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" [dev-dependencies] +snapbox.workspace = true similar-asserts.workspace = true figment = { workspace = true, features = ["test"] } tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/README.md b/crates/config/README.md index be3055f5816f7..ac742cc05db01 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -54,255 +54,15 @@ and merge, at the per-key level: The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, or if it is not set, "default". -### All Options +## Configuration Reference -The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](./src/lib.rs) and [/cli/tests/it/config.rs](../forge/tests/it/config.rs). +For a full list of all configuration options, see the [Foundry Book](https://getfoundry.sh/config/reference/overview): -```toml -## defaults for _all_ profiles -[profile.default] -src = 'src' -test = 'test' -script = 'script' -out = 'out' -libs = ['lib'] -auto_detect_remappings = true # recursive auto-detection of remappings -remappings = [] -# list of libraries to link in the form of `::
`: `"src/MyLib.sol:MyLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6"` -# the supports remappings -libraries = [] -cache = true -cache_path = 'cache' -broadcast = 'broadcast' -# additional solc allow paths -allow_paths = [] -# additional solc include paths -include_paths = [] -force = false -evm_version = 'shanghai' -gas_reports = ['*'] -gas_reports_ignore = [] -## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value -# solc = '0.8.10' -auto_detect_solc = true -offline = false -optimizer = false -optimizer_runs = 200 -model_checker = { contracts = { 'a.sol' = [ - 'A1', - 'A2', -], 'b.sol' = [ - 'B1', - 'B2', -] }, engine = 'chc', targets = [ - 'assert', - 'outOfBounds', -], timeout = 10000 } -verbosity = 0 -eth_rpc_url = "https://example.com/" -# Setting this option enables decoding of error traces from mainnet deployed / verified contracts via etherscan -etherscan_api_key = "YOURETHERSCANAPIKEY" -# ignore solc warnings for missing license and exceeded contract size -# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname", "too-many-warnings", "constructor-visibility", "init-code-size", "missing-receive-ether", "unnamed-return", "transient-storage"] -# additional warnings can be added using their numeric error code: ["license", 1337] -ignored_error_codes = ["license", "code-size"] -ignored_warnings_from = ["path_to_ignore"] -deny_warnings = false -match_test = "Foo" -no_match_test = "Bar" -match_contract = "Foo" -no_match_contract = "Bar" -match_path = "*/Foo*" -no_match_path = "*/Bar*" -no_match_coverage = "Baz" -# Number of threads to use. Specifying 0 defaults to the number of logical cores. -threads = 0 -# whether to show test execution progress -show_progress = true -ffi = false -always_use_create_2_factory = false -prompt_timeout = 120 -# These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` -sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' -initial_balance = '0xffffffffffffffffffffffff' -block_number = 0 -fork_block_number = 0 -chain_id = 1 -# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds 2**63-1 (9223372036854775807). -# `gas_limit = "max"` is equivalent to `gas_limit = "18446744073709551615"`. This is not recommended -# as it will make infinite loops effectively hang during execution. -gas_limit = 1073741824 -gas_price = 0 -block_base_fee_per_gas = 0 -block_coinbase = '0x0000000000000000000000000000000000000000' -block_timestamp = 0 -block_difficulty = 0 -block_prevrandao = '0x0000000000000000000000000000000000000000' -block_gas_limit = 30000000 -memory_limit = 134217728 -extra_output = ["metadata"] -extra_output_files = [] -names = false -sizes = false -via_ir = false -ast = false -# caches storage retrieved locally for certain chains and endpoints -# can also be restricted to `chains = ["optimism", "mainnet"]` -# by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" -# to disable storage caching entirely set `no_storage_caching = true` -rpc_storage_caching = { chains = "all", endpoints = "all" } -# this overrides `rpc_storage_caching` entirely -no_storage_caching = false -# Whether to store the referenced sources in the metadata as literal data. -use_literal_content = false -# use ipfs method to generate the metadata hash, solc's default. -# To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" -bytecode_hash = "ipfs" -# Whether to append the CBOR-encoded metadata file. -cbor_metadata = true -# How to treat revert (and require) reason strings. -# Possible values are: "default", "strip", "debug" and "verboseDebug". -# "default" does not inject compiler-generated revert strings and keeps user-supplied ones. -# "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects -# "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. -# "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) -revert_strings = "default" -# If this option is enabled, Solc is instructed to generate output (bytecode) only for the required contracts -# this can reduce compile time for `forge test` a bit but is considered experimental at this point. -sparse_mode = false -build_info = true -build_info_path = "build-info" -root = "root" -# Configures permissions for cheatcodes that touch the filesystem like `vm.writeFile` -# `access` restricts how the `path` can be accessed via cheatcodes -# `read-write` | `true` => `read` + `write` access allowed (`vm.readFile` + `vm.writeFile`) -# `none`| `false` => no access -# `read` => only read access (`vm.readFile`) -# `write` => only write access (`vm.writeFile`) -# The `allowed_paths` further lists the paths that are considered, e.g. `./` represents the project root directory -# By default, only read access is granted to the project's out dir, so generated artifacts can be read by default -# following example enables read-write access for the project dir : -# `fs_permissions = [{ access = "read-write", path = "./"}]` -fs_permissions = [{ access = "read", path = "./out"}] -# whether failed assertions should revert -# note that this only applies to native (cheatcode) assertions, invoked on Vm contract -assertions_revert = true -# whether `failed()` should be invoked to check if the test have failed -legacy_assertions = false -[fuzz] -runs = 256 -max_test_rejects = 65536 -seed = '0x3e8' -dictionary_weight = 40 -include_storage = true -include_push_bytes = true - -[invariant] -runs = 256 -depth = 500 -fail_on_revert = false -call_override = false -dictionary_weight = 80 -include_storage = true -include_push_bytes = true -shrink_run_limit = 5000 - -[fmt] -line_length = 100 -tab_width = 2 -bracket_spacing = true -``` - -#### Additional Optimizer settings - -Optimizer components can be tweaked with the `OptimizerDetails` object: - -See [Compiler Input Description `settings.optimizer.details`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) - -The `optimizer_details` (`optimizerDetails` also works) settings must be prefixed with the profile they correspond -to: `[profile.default.optimizer_details]` -belongs to the `[profile.default]` profile - -```toml -[profile.default.optimizer_details] -constantOptimizer = true -yul = true -# this sets the `yulDetails` of the `optimizer_details` for the `default` profile -[profile.default.optimizer_details.yulDetails] -stackAllocation = true -optimizerSteps = 'dhfoDgvulfnTUtnIf' -``` - -#### RPC-Endpoints settings - -The `rpc_endpoints` value accepts a list of `alias = ""` pairs. - -The following example declares two pairs: -The alias `optimism` references the endpoint URL directly. -The alias `mainnet` references the environment variable `RPC_MAINNET` which holds the entire URL. -The alias `goerli` references an endpoint that will be interpolated with the value the `GOERLI_API_KEY` holds. - -Environment variables need to be wrapped in `${}` - -```toml -[rpc_endpoints] -optimism = "https://optimism.alchemyapi.io/v2/1234567" -mainnet = "${RPC_MAINNET}" -goerli = "https://eth-goerli.alchemyapi.io/v2/${GOERLI_API_KEY}" -``` - -#### Etherscan API Key settings - -The `etherscan` value accepts a list of `alias = "{key = "", url? ="", chain?= """""}"` items. - -the `key` attribute is always required and should contain the actual API key for that chain or an env var that holds the key in the form `${ENV_VAR}` -The `chain` attribute is optional if the `alias` is the already the `chain` name, such as in `mainnet = { key = "${ETHERSCAN_MAINNET_KEY}"}` -The optional `url` attribute can be used to explicitly set the Etherscan API url, this is the recommended setting for chains not natively supported by name. - -```toml -[etherscan] -mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } -mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576", chain = 42 } -unknownchain = { key = "ABCDEFG", url = "https://" } -``` - -##### Additional Model Checker settings - -[Solidity's built-in model checker](https://docs.soliditylang.org/en/latest/smtchecker.html#tutorial) -is an opt-in module that can be enabled via the `ModelChecker` object. - -See [Compiler Input Description `settings.modelChecker`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) -and [the model checker's options](https://docs.soliditylang.org/en/latest/smtchecker.html#smtchecker-options-and-tuning). - -The module is available in `solc` release binaries for OSX and Linux. -The latter requires the z3 library version [4.8.8, 4.8.14] to be installed -in the system (SO version 4.8). - -Similarly to the optimizer settings above, the `model_checker` settings must be -prefixed with the profile they correspond to: `[profile.default.model_checker]` belongs -to the `[profile.default]` profile. - -```toml -[profile.default.model_checker] -contracts = { 'src/Contract.sol' = [ 'Contract' ] } -engine = 'chc' -timeout = 10000 -targets = [ 'assert' ] -``` - -The fields above are recommended when using the model checker. -Setting which contract should be verified is extremely important, otherwise all -available contracts will be verified which can consume a lot of time. -The recommended engine is `chc`, but `bmc` and `all` (runs both) are also -accepted. -It is also important to set a proper timeout (given in milliseconds), since the -default time given to the underlying solvers may not be enough. -If no verification targets are given, only assertions will be checked. - -The model checker will run when `forge build` is invoked, and will show -findings as warnings if any. +- [Default Configuration](https://getfoundry.sh/config/reference/default-config) - All `[profile.default]` options +- [Testing Configuration](https://getfoundry.sh/config/reference/testing) - Fuzz and invariant testing options +- [Formatter Configuration](https://getfoundry.sh/config/reference/formatter) - Code formatting options +- [Linter Configuration](https://getfoundry.sh/config/reference/linter) - Linting options +- [Doc Generator Configuration](https://getfoundry.sh/config/reference/doc-generator) - Documentation generation options ## Environment Variables @@ -314,4 +74,3 @@ supported, this means that `FOUNDRY_SRC` and `DAPP_SRC` are equivalent. Some exceptions to the above are [explicitly ignored](https://github.com/foundry-rs/foundry/blob/10440422e63aae660104e079dfccd5b0ae5fd720/config/src/lib.rs#L1539-L15522) due to security concerns. Environment variables take precedence over values in `foundry.toml`. Values are parsed as a loose form of TOML syntax. -Consider the following examples: diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..00a381eaf66ff --- /dev/null +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..89d40cf5606ce --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..77a3bb7ab11c2 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,63 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use foundry_config::Config; +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if fs::read_to_string(file).map(|old| normalize_newlines(&old) == normalize_newlines(contents)).unwrap_or(false) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/config/src/cache.rs b/crates/config/src/cache.rs index d087b5e6a2c2e..d67825340ab80 100644 --- a/crates/config/src/cache.rs +++ b/crates/config/src/cache.rs @@ -1,9 +1,9 @@ //! Support types for configuring storage caching use crate::Chain; -use number_prefix::NumberPrefix; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, fmt::Formatter, str::FromStr}; +use unit_prefix::NumberPrefix; /// Settings to configure caching of remote. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -24,7 +24,7 @@ impl StorageCachingConfig { pub fn enable_for_chain_id(&self, chain_id: u64) -> bool { // ignore dev chains if [99, 1337, 31337].contains(&chain_id) { - return false + return false; } self.chains.is_match(chain_id) } @@ -281,7 +281,7 @@ mod tests { block_explorer: 4230000, }, ChainCache { - name: "mumbai".to_string(), + name: "amoy".to_string(), blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)], block_explorer: 0, }, @@ -301,7 +301,7 @@ mod tests { - Block Explorer (4.2 MB)\n\n\t\ - Block 1 (1.0 kB)\n\t\ - Block 2 (2.0 MB)\n\ - - mumbai (3.0 B)\n\t\ + - amoy (3.0 B)\n\t\ - Block Explorer (0.0 B)\n\n\t\ - Block 1 (1.0 B)\n\t\ - Block 2 (2.0 B)\n"; diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs index 8bb48f525bb54..cb93a1f6d7c5c 100644 --- a/crates/config/src/compilation.rs +++ b/crates/config/src/compilation.rs @@ -1,13 +1,13 @@ use crate::{filter::GlobMatcher, serde_helpers}; use foundry_compilers::{ + RestrictionsWithVersion, artifacts::{BytecodeHash, EvmVersion}, multi::{MultiCompilerRestrictions, MultiCompilerSettings}, settings::VyperRestrictions, solc::{Restriction, SolcRestrictions}, - RestrictionsWithVersion, }; use semver::VersionReq; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; /// Keeps possible overrides for default settings which users may configure to construct additional /// settings profile. @@ -69,6 +69,7 @@ pub enum RestrictionsError { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CompilationRestrictions { pub paths: GlobMatcher, + #[serde(default, deserialize_with = "deserialize_version_req")] pub version: Option, pub via_ir: Option, pub bytecode_hash: Option, @@ -85,6 +86,36 @@ pub struct CompilationRestrictions { pub max_evm_version: Option, } +/// Custom deserializer for version field that rejects ambiguous bare version numbers. +fn deserialize_version_req<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt_string: Option = Option::deserialize(deserializer)?; + let Some(opt_string) = opt_string else { + return Ok(None); + }; + + let version = opt_string.trim(); + // Reject bare versions like "0.8.11" that lack an operator prefix + if version.chars().next().is_some_and(|c| c.is_ascii_digit()) { + return Err(serde::de::Error::custom(format!( + "Invalid version format '{opt_string}' in compilation_restrictions. \ + Bare version numbers are ambiguous and default to caret requirements (e.g. '^{version}'). \ + Use an explicit constraint such as '={version}' for an exact version or '>={version}' for a minimum version." + ))); + } + + let req = VersionReq::parse(&opt_string).map_err(|e| { + serde::de::Error::custom(format!( + "Invalid version requirement '{opt_string}': {e}. \ + Examples: '=0.8.11' (exact), '>=0.8.11' (minimum), '>=0.8.11 <0.9.0' (range)." + )) + })?; + + Ok(Some(req)) +} + impl TryFrom for RestrictionsWithVersion { type Error = RestrictionsError; diff --git a/crates/config/src/doc.rs b/crates/config/src/doc.rs index 36e8a12b08df2..90ba6a17ab0db 100644 --- a/crates/config/src/doc.rs +++ b/crates/config/src/doc.rs @@ -20,6 +20,10 @@ pub struct DocConfig { /// The repository url. #[serde(default, skip_serializing_if = "Option::is_none")] pub repository: Option, + /// The path to source code (e.g. `tree/main/packages/contracts`). + /// Useful for monorepos or for projects with source code located in specific directories. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, /// Globs to ignore pub ignore: Vec, } @@ -32,6 +36,7 @@ impl Default for DocConfig { homepage: Some(PathBuf::from("README.md")), title: String::default(), repository: None, + path: None, ignore: Vec::default(), } } diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 1758e6a4870bf..60a15206bc51a 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -1,7 +1,7 @@ //! Support for multiple RPC-endpoints -use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}; -use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; +use crate::resolve::{RE_PLACEHOLDER, UnresolvedEnvVarError, interpolate}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap}; use std::{ collections::BTreeMap, fmt, @@ -63,22 +63,6 @@ pub enum RpcEndpointType { } impl RpcEndpointType { - /// Returns the string variant - pub fn as_endpoint_string(&self) -> Option<&RpcEndpointUrl> { - match self { - Self::String(url) => Some(url), - Self::Config(_) => None, - } - } - - /// Returns the config variant - pub fn as_endpoint_config(&self) -> Option<&RpcEndpoint> { - match self { - Self::Config(config) => Some(config), - Self::String(_) => None, - } - } - /// Returns the url or config this type holds /// /// # Error @@ -137,14 +121,6 @@ impl RpcEndpointUrl { } } - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - Self::Env(val) => Some(val), - Self::Url(_) => None, - } - } - /// Returns the url this type holds /// /// # Error @@ -301,6 +277,11 @@ pub struct RpcEndpoint { /// endpoint url or env pub endpoint: RpcEndpointUrl, + /// Additional fallback endpoints for load-balanced multi-endpoint forking. + /// When set, requests are distributed across all endpoints (primary + extra) + /// with automatic failover. + pub extra_endpoints: Vec, + /// Token to be used as authentication pub auth: Option, @@ -317,6 +298,7 @@ impl RpcEndpoint { pub fn resolve(self) -> ResolvedRpcEndpoint { ResolvedRpcEndpoint { endpoint: self.endpoint.resolve(), + extra_endpoints: self.extra_endpoints.into_iter().map(|e| e.resolve()).collect(), auth: self.auth.map(|auth| auth.resolve()), config: self.config, } @@ -325,7 +307,7 @@ impl RpcEndpoint { impl fmt::Display for RpcEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { endpoint, auth, config } = self; + let Self { endpoint, auth, config, .. } = self; write!(f, "{endpoint}")?; write!(f, "{config}")?; if let Some(auth) = auth { @@ -340,16 +322,24 @@ impl Serialize for RpcEndpoint { where S: Serializer, { - if self.config.retries.is_none() && - self.config.retry_backoff.is_none() && - self.config.compute_units_per_second.is_none() && - self.auth.is_none() - { - // serialize as endpoint if there's no additional config + let has_config = self.config.retries.is_some() + || self.config.retry_backoff.is_some() + || self.config.compute_units_per_second.is_some() + || self.auth.is_some(); + + if !has_config && self.extra_endpoints.is_empty() { + // serialize as plain endpoint string if there's no additional config self.endpoint.serialize(serializer) } else { - let mut map = serializer.serialize_map(Some(4))?; - map.serialize_entry("endpoint", &self.endpoint)?; + let mut map = serializer.serialize_map(None)?; + if self.extra_endpoints.is_empty() { + map.serialize_entry("endpoint", &self.endpoint)?; + } else { + // Serialize all endpoints as an array under "endpoints" + let all: Vec<&RpcEndpointUrl> = + std::iter::once(&self.endpoint).chain(&self.extra_endpoints).collect(); + map.serialize_entry("endpoints", &all)?; + } map.serialize_entry("retries", &self.config.retries)?; map.serialize_entry("retry_backoff", &self.config.retry_backoff)?; map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?; @@ -372,10 +362,13 @@ impl<'de> Deserialize<'de> for RpcEndpoint { }); } + // Support both single "endpoint" and array "endpoints" for backwards compatibility #[derive(Deserialize)] struct RpcEndpointConfigInner { #[serde(alias = "url")] - endpoint: RpcEndpointUrl, + endpoint: Option, + /// Array of endpoint URLs for multi-endpoint load balancing + endpoints: Option>, retries: Option, retry_backoff: Option, compute_units_per_second: Option, @@ -384,14 +377,43 @@ impl<'de> Deserialize<'de> for RpcEndpoint { let RpcEndpointConfigInner { endpoint, + endpoints, retries, retry_backoff, compute_units_per_second, auth, } = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + let (primary, extra) = match (endpoint, endpoints) { + // Single endpoint: endpoint = "..." + (Some(ep), None) => (ep, vec![]), + // Array of endpoints: endpoints = ["...", "..."] + (None, Some(mut eps)) => { + if eps.is_empty() { + return Err(serde::de::Error::custom( + "endpoints array must contain at least one URL", + )); + } + let primary = eps.remove(0); + (primary, eps) + } + // Both provided — error + (Some(_), Some(_)) => { + return Err(serde::de::Error::custom( + "cannot specify both `endpoint` and `endpoints`", + )); + } + // Neither provided — error + (None, None) => { + return Err(serde::de::Error::custom( + "must specify either `endpoint` or `endpoints`", + )); + } + }; + Ok(Self { - endpoint, + endpoint: primary, + extra_endpoints: extra, auth, config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second }, }) @@ -408,6 +430,7 @@ impl Default for RpcEndpoint { fn default() -> Self { Self { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig::default(), auth: None, } @@ -418,31 +441,53 @@ impl Default for RpcEndpoint { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResolvedRpcEndpoint { pub endpoint: Result, + /// Additional resolved endpoints for multi-endpoint load balancing. + pub extra_endpoints: Vec>, pub auth: Option>, pub config: RpcEndpointConfig, } impl ResolvedRpcEndpoint { - /// Returns the url this type holds, see [`RpcEndpoint::resolve`] + /// Returns the primary url this type holds, see [`RpcEndpoint::resolve`] pub fn url(&self) -> Result { self.endpoint.clone() } + /// Returns all resolved URLs (primary + extra) for multi-endpoint configurations. + /// Returns an empty vec if no extra endpoints are configured. + pub fn all_urls(&self) -> Result, UnresolvedEnvVarError> { + let primary = self.endpoint.clone()?; + if self.extra_endpoints.is_empty() { + return Ok(vec![primary]); + } + let mut urls = vec![primary]; + for ep in &self.extra_endpoints { + urls.push(ep.clone()?); + } + Ok(urls) + } + // Returns true if all environment variables are resolved successfully pub fn is_unresolved(&self) -> bool { let endpoint_err = self.endpoint.is_err(); + let extra_err = self.extra_endpoints.iter().any(|e| e.is_err()); let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false); - endpoint_err || auth_err + endpoint_err || extra_err || auth_err } // Attempts to resolve unresolved environment variables into a new instance pub fn try_resolve(mut self) -> Self { if !self.is_unresolved() { - return self + return self; } if let Err(err) = self.endpoint { self.endpoint = err.try_resolve() } + for ep in &mut self.extra_endpoints { + if let Err(err) = std::mem::replace(ep, Ok(String::new())) { + *ep = err.try_resolve(); + } + } if let Some(Err(err)) = self.auth { self.auth = Some(err.try_resolve()) } @@ -477,6 +522,18 @@ impl DerefMut for ResolvedRpcEndpoints { } } +/// Returns the URL for a built-in RPC alias, if one exists. +/// +/// Built-in aliases act as fallbacks: they are only used when the alias has **not** been +/// defined by the user in `[rpc_endpoints]` or resolved via MESC. +pub fn builtin_rpc_url(alias: &str) -> Option<&'static str> { + match alias { + "tempo" => Some("https://rpc.mpp.tempo.xyz"), + "moderato" => Some("https://rpc.mpp.moderato.tempo.xyz"), + _ => None, + } +} + #[cfg(test)] mod tests { use super::*; @@ -495,6 +552,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(5), retry_backoff: Some(250), @@ -510,6 +568,7 @@ mod tests { config, RpcEndpoint { endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: None, retry_backoff: None, @@ -519,4 +578,62 @@ mod tests { } ); } + + #[test] + fn serde_rpc_config_multi_endpoints() { + // Array of endpoints via "endpoints" key + let s = r#"{ + "endpoints": ["https://rpc1.example.com", "https://rpc2.example.com", "https://rpc3.example.com"], + "retries": 5, + "retry_backoff": 1000 + }"#; + let config: RpcEndpoint = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpoint { + endpoint: RpcEndpointUrl::Url("https://rpc1.example.com".to_string()), + extra_endpoints: vec![ + RpcEndpointUrl::Url("https://rpc2.example.com".to_string()), + RpcEndpointUrl::Url("https://rpc3.example.com".to_string()), + ], + config: RpcEndpointConfig { + retries: Some(5), + retry_backoff: Some(1000), + compute_units_per_second: None, + }, + auth: None, + } + ); + + // Resolved URLs + let resolved = config.resolve(); + let all_urls = resolved.all_urls().unwrap(); + assert_eq!( + all_urls, + vec![ + "https://rpc1.example.com".to_string(), + "https://rpc2.example.com".to_string(), + "https://rpc3.example.com".to_string(), + ] + ); + } + + #[test] + fn serde_rpc_config_rejects_both_endpoint_and_endpoints() { + let s = r#"{ + "endpoint": "https://rpc1.example.com", + "endpoints": ["https://rpc2.example.com"] + }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("cannot specify both")); + } + + #[test] + fn serde_rpc_config_rejects_empty_endpoints() { + let s = r#"{ "endpoints": [] }"#; + let result: Result = serde_json::from_str(s); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("at least one URL")); + } } diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index eba84a57da0dc..9881d29bd0aab 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -12,8 +12,8 @@ pub struct ExtractConfigError { } impl ExtractConfigError { - /// Wraps the figment error - pub fn new(error: figment::Error) -> Self { + /// Wraps the figment error. + pub const fn new(error: figment::Error) -> Self { Self { error } } } @@ -22,7 +22,7 @@ impl fmt::Display for ExtractConfigError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut unique_errors = Vec::with_capacity(self.error.count()); let mut unique = HashSet::with_capacity(self.error.count()); - for err in self.error.clone().into_iter() { + for err in self.error.clone() { let err = if err .metadata .as_ref() @@ -63,7 +63,7 @@ impl Error for ExtractConfigError { pub enum FoundryConfigError { /// An error thrown during toml parsing Toml(figment::Error), - /// Any other error thrown when constructing the config's figment + /// Any other error thrown when constructing the config's figment. Other(figment::Error), } @@ -140,6 +140,10 @@ pub enum SolidityErrorCode { TransientStorageUsed, /// There are more than 256 warnings. Ignoring the rest. TooManyWarnings, + /// Warning: 'transfer' is deprecated and scheduled for removal. + TransferDeprecated, + /// Warning: Natspec memory-safe-assembly special comment for inline assembly is deprecated. + NatspecMemorySafeAssemblyDeprecated, /// All other error codes Other(u64), } @@ -148,7 +152,7 @@ impl SolidityErrorCode { /// The textual identifier for this error /// /// Returns `Err(code)` if unknown error - pub fn as_str(&self) -> Result<&'static str, u64> { + pub const fn as_str(&self) -> Result<&'static str, u64> { let s = match self { Self::SpdxLicenseNotProvided => "license", Self::VisibilityForConstructorIsIgnored => "constructor-visibility", @@ -167,6 +171,8 @@ impl SolidityErrorCode { Self::PragmaSolidity => "pragma-solidity", Self::TransientStorageUsed => "transient-storage", Self::TooManyWarnings => "too-many-warnings", + Self::TransferDeprecated => "transfer-deprecated", + Self::NatspecMemorySafeAssemblyDeprecated => "natspec-memory-safe-assembly-deprecated", Self::Other(code) => return Err(*code), }; Ok(s) @@ -193,6 +199,8 @@ impl From for u64 { SolidityErrorCode::PragmaSolidity => 3420, SolidityErrorCode::TransientStorageUsed => 2394, SolidityErrorCode::TooManyWarnings => 4591, + SolidityErrorCode::TransferDeprecated => 9207, + SolidityErrorCode::NatspecMemorySafeAssemblyDeprecated => 2424, SolidityErrorCode::Other(code) => code, } } @@ -229,6 +237,8 @@ impl FromStr for SolidityErrorCode { "pragma-solidity" => Self::PragmaSolidity, "transient-storage" => Self::TransientStorageUsed, "too-many-warnings" => Self::TooManyWarnings, + "transfer-deprecated" => Self::TransferDeprecated, + "natspec-memory-safe-assembly-deprecated" => Self::NatspecMemorySafeAssemblyDeprecated, _ => return Err(format!("Unknown variant {s}")), }; @@ -255,6 +265,9 @@ impl From for SolidityErrorCode { 5740 => Self::Unreachable, 3420 => Self::PragmaSolidity, 2394 => Self::TransientStorageUsed, + 4591 => Self::TooManyWarnings, + 9207 => Self::TransferDeprecated, + 2424 => Self::NatspecMemorySafeAssemblyDeprecated, other => Self::Other(other), } } diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 76d50b09bc934..9632eb425fbde 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -1,15 +1,14 @@ //! Support for multiple Etherscan keys. use crate::{ - resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, Chain, Config, NamedChain, + resolve::{RE_PLACEHOLDER, UnresolvedEnvVarError, interpolate}, }; use figment::{ + Error, Metadata, Profile, Provider, providers::Env, value::{Dict, Map}, - Error, Metadata, Profile, Provider, }; -use foundry_block_explorers::EtherscanApiVersion; use heck::ToKebabCase; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ @@ -19,9 +18,6 @@ use std::{ time::Duration, }; -/// The user agent to use when querying the etherscan API. -pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); - /// A [Provider] that provides Etherscan API key from the environment if it's not empty. /// /// This prevents `ETHERSCAN_API_KEY=""` if it's set but empty @@ -37,10 +33,10 @@ impl Provider for EtherscanEnvProvider { fn data(&self) -> Result, Error> { let mut dict = Dict::default(); let env_provider = Env::raw().only(&["ETHERSCAN_API_KEY"]); - if let Some((key, value)) = env_provider.iter().next() { - if !value.trim().is_empty() { - dict.insert(key.as_str().to_string(), value.into()); - } + if let Some((key, value)) = env_provider.iter().next() + && !value.trim().is_empty() + { + dict.insert(key.as_str().to_string(), value.into()); } Ok(Map::from([(Config::selected_profile(), dict)])) @@ -53,7 +49,11 @@ pub enum EtherscanConfigError { #[error(transparent)] Unresolved(#[from] UnresolvedEnvVarError), - #[error("No known Etherscan API URL for config{0} with chain `{1}`. Please specify a `url`")] + #[error( + "No known Etherscan API URL for chain `{1}`. To fix this, please:\n\ + 1. Specify a `url` {0}\n\ + 2. Verify the chain `{1}` is correct" + )] UnknownChain(String, Chain), #[error("At least one of `url` or `chain` must be present{0}")] @@ -84,13 +84,13 @@ impl EtherscanConfigs { } /// Returns all (alias -> url) pairs - pub fn resolved(self, default_api_version: EtherscanApiVersion) -> ResolvedEtherscanConfigs { + pub fn resolved(self) -> ResolvedEtherscanConfigs { ResolvedEtherscanConfigs { configs: self .configs .into_iter() .map(|(name, e)| { - let resolved = e.resolve(Some(&name), default_api_version); + let resolved = e.resolve(Some(&name)); (name, resolved) }) .collect(), @@ -135,11 +135,11 @@ impl ResolvedEtherscanConfigs { self, chain: Chain, ) -> Option> { - for (_, config) in self.configs.into_iter() { + for (_, config) in self.configs { match config { Ok(c) if c.chain == Some(chain) => return Some(Ok(c)), Err(e) => return Some(Err(e)), - _ => continue, + _ => {} } } None @@ -174,9 +174,6 @@ pub struct EtherscanConfig { /// Etherscan API URL #[serde(default, skip_serializing_if = "Option::is_none")] pub url: Option, - /// Etherscan API Version. Defaults to v2 - #[serde(default, alias = "api-version", skip_serializing_if = "Option::is_none")] - pub api_version: Option, /// The etherscan API KEY that's required to make requests pub key: EtherscanApiKey, } @@ -191,11 +188,8 @@ impl EtherscanConfig { pub fn resolve( self, alias: Option<&str>, - default_api_version: EtherscanApiVersion, ) -> Result { - let Self { chain, mut url, key, api_version } = self; - - let api_version = api_version.unwrap_or(default_api_version); + let Self { chain, mut url, key } = self; if let Some(url) = &mut url { *url = interpolate(url)?; @@ -226,23 +220,17 @@ impl EtherscanConfig { match (chain, url) { (Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig { api_url, - api_version, browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()), key, chain: Some(chain), }), - (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain, api_version) - .ok_or_else(|| { - let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); - EtherscanConfigError::UnknownChain(msg, chain) - }), - (None, Some(api_url)) => Ok(ResolvedEtherscanConfig { - api_url, - browser_url: None, - key, - chain: None, - api_version, + (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| { + let msg = alias.map(|a| format!("for `{a}`")).unwrap_or_default(); + EtherscanConfigError::UnknownChain(msg, chain) }), + (None, Some(api_url)) => { + Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None }) + } (None, None) => { let msg = alias .map(|a| format!(" for Etherscan config with unknown alias `{a}`")) @@ -264,9 +252,6 @@ pub struct ResolvedEtherscanConfig { pub browser_url: Option, /// The resolved API key. pub key: String, - /// Etherscan API Version. - #[serde(default)] - pub api_version: EtherscanApiVersion, /// The chain name or EIP-155 chain ID. #[serde(default, skip_serializing_if = "Option::is_none")] pub chain: Option, @@ -274,16 +259,11 @@ pub struct ResolvedEtherscanConfig { impl ResolvedEtherscanConfig { /// Creates a new instance using the api key and chain - pub fn create( - api_key: impl Into, - chain: impl Into, - api_version: EtherscanApiVersion, - ) -> Option { + pub fn create(api_key: impl Into, chain: impl Into) -> Option { let chain = chain.into(); let (api_url, browser_url) = chain.etherscan_urls()?; Some(Self { api_url: api_url.to_string(), - api_version, browser_url: Some(browser_url.to_string()), key: api_key.into(), chain: Some(chain), @@ -315,7 +295,21 @@ impl ResolvedEtherscanConfig { self, ) -> Result { - let Self { api_url, browser_url, key: api_key, chain, api_version } = self; + self.into_client_with_no_proxy(false) + } + + /// Same as [`Self::into_client`] but optionally disables automatic proxy detection. + /// + /// When `no_proxy` is `true`, calls [`foundry_block_explorers::ClientBuilder::no_proxy`], + /// which prevents system proxy lookups that can crash in sandboxed environments (e.g., + /// Cursor IDE, macOS App Sandbox). + /// See: + pub fn into_client_with_no_proxy( + self, + no_proxy: bool, + ) -> Result + { + let Self { api_url, browser_url, key: api_key, chain } = self; let chain = chain.unwrap_or_default(); let cache = Config::foundry_etherscan_chain_cache_dir(chain); @@ -327,20 +321,26 @@ impl ResolvedEtherscanConfig { } } - let api_url = into_url(&api_url)?; - let client = reqwest::Client::builder() - .user_agent(ETHERSCAN_USER_AGENT) - .tls_built_in_root_certs(api_url.scheme() == "https") - .build()?; + // Disable automatic proxy detection. In sandboxed environments (e.g., Cursor IDE, + // macOS App Sandbox), reqwest's system proxy lookup via SCDynamicStore can crash + // when the API returns NULL. See: https://github.com/foundry-rs/foundry/issues/12733 let mut client_builder = foundry_block_explorers::Client::builder() - .with_client(client) - .with_api_version(api_version) .with_api_key(api_key) .with_cache(cache, Duration::from_secs(24 * 60 * 60)); - if let Some(browser_url) = browser_url { + if no_proxy { + client_builder = client_builder.no_proxy(); + } + if let Some(ref browser_url) = browser_url { client_builder = client_builder.with_url(browser_url)?; } - client_builder.chain(chain)?.build() + + // Use the provided URL (either custom from foundry.toml or chain's default from resolve()) + client_builder = client_builder.with_api_url(&api_url)?; + // Fallback: Use api_url as browser URL if browser_url is not set + if browser_url.is_none() { + client_builder = client_builder.with_url(&api_url)?; + } + client_builder.build() } } @@ -361,22 +361,6 @@ pub enum EtherscanApiKey { } impl EtherscanApiKey { - /// Returns the key variant - pub fn as_key(&self) -> Option<&str> { - match self { - Self::Key(url) => Some(url), - Self::Env(_) => None, - } - } - - /// Returns the env variant - pub fn as_env(&self) -> Option<&str> { - match self { - Self::Env(val) => Some(val), - Self::Key(_) => None, - } - } - /// Returns the key this type holds /// /// # Error @@ -420,13 +404,6 @@ impl fmt::Display for EtherscanApiKey { } } -/// This is a hack to work around `IntoUrl`'s sealed private functions, which can't be called -/// normally. -#[inline] -fn into_url(url: impl reqwest::IntoUrl) -> std::result::Result { - url.into_url() -} - #[cfg(test)] mod tests { use super::*; @@ -441,36 +418,17 @@ mod tests { chain: Some(Mainnet.into()), url: None, key: EtherscanApiKey::Key("ABCDEFG".to_string()), - api_version: None, }, ); - let mut resolved = configs.resolved(EtherscanApiVersion::V2); + let mut resolved = configs.resolved(); let config = resolved.remove("mainnet").unwrap().unwrap(); - // None version = None - assert_eq!(config.api_version, EtherscanApiVersion::V2); - let client = config.into_client().unwrap(); - assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); - } - - #[test] - fn can_create_v1_client_via_chain() { - let mut configs = EtherscanConfigs::default(); - configs.insert( - "mainnet".to_string(), - EtherscanConfig { - chain: Some(Mainnet.into()), - url: None, - api_version: Some(EtherscanApiVersion::V1), - key: EtherscanApiKey::Key("ABCDEG".to_string()), - }, - ); - let mut resolved = configs.resolved(EtherscanApiVersion::V2); - let config = resolved.remove("mainnet").unwrap().unwrap(); - assert_eq!(config.api_version, EtherscanApiVersion::V1); let client = config.into_client().unwrap(); - assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V1); + assert_eq!( + client.etherscan_api_url().as_str(), + "https://api.etherscan.io/v2/api?chainid=1" + ); } #[test] @@ -482,11 +440,10 @@ mod tests { chain: Some(Mainnet.into()), url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), - api_version: None, }, ); - let mut resolved = configs.resolved(EtherscanApiVersion::V2); + let mut resolved = configs.resolved(); let config = resolved.remove("mainnet").unwrap().unwrap(); let _ = config.into_client().unwrap(); } @@ -500,24 +457,28 @@ mod tests { EtherscanConfig { chain: Some(Mainnet.into()), url: Some("https://api.etherscan.io/api".to_string()), - api_version: None, key: EtherscanApiKey::Env(format!("${{{env}}}")), }, ); - let mut resolved = configs.clone().resolved(EtherscanApiVersion::V2); + let mut resolved = configs.clone().resolved(); let config = resolved.remove("mainnet").unwrap(); assert!(config.is_err()); - std::env::set_var(env, "ABCDEFG"); + unsafe { + std::env::set_var(env, "ABCDEFG"); + } - let mut resolved = configs.resolved(EtherscanApiVersion::V2); + let mut resolved = configs.resolved(); let config = resolved.remove("mainnet").unwrap().unwrap(); assert_eq!(config.key, "ABCDEFG"); let client = config.into_client().unwrap(); - assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); + // Custom URL should be used even when chain has a default URL + assert_eq!(client.etherscan_api_url().as_str(), "https://api.etherscan.io/api"); - std::env::remove_var(env); + unsafe { + std::env::remove_var(env); + } } #[test] @@ -529,11 +490,10 @@ mod tests { chain: None, url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), - api_version: None, }, ); - let mut resolved = configs.clone().resolved(EtherscanApiVersion::V2); + let mut resolved = configs.clone().resolved(); let config = resolved.remove("blast_sepolia").unwrap().unwrap(); assert_eq!(config.chain, Some(Chain::blast_sepolia())); } @@ -544,13 +504,57 @@ mod tests { chain: None, url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), - api_version: None, }; - let resolved = - config.clone().resolve(Some("base_sepolia"), EtherscanApiVersion::V2).unwrap(); + let resolved = config.clone().resolve(Some("base_sepolia")).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); - let resolved = config.resolve(Some("base-sepolia"), EtherscanApiVersion::V2).unwrap(); + let resolved = config.resolve(Some("base-sepolia")).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); } + + #[test] + fn can_create_client_with_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should work if a custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: Some("https://custom.api.url/verify/etherscan".to_string()), + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap().unwrap(); + let result = config.into_client(); + assert!( + result.is_ok(), + "Should succeed with custom URL even for chains without default Etherscan URLs" + ); + } + + #[test] + fn fails_without_custom_url_for_chain_without_default_url() { + // Chains without default Etherscan URLs (e.g., Dev, AnvilHardhat networks) + // should fail if no custom URL is provided in foundry.toml. + let mut configs = EtherscanConfigs::default(); + configs.insert( + "dev".to_string(), + EtherscanConfig { + chain: Some(Chain::dev()), + url: None, + key: EtherscanApiKey::Key("test_key".to_string()), + }, + ); + + let mut resolved = configs.resolved(); + let config = resolved.remove("dev").unwrap(); + + assert!( + config.is_err(), + "Should fail: chains without default Etherscan URLs require custom URL" + ); + } } diff --git a/crates/config/src/extend.rs b/crates/config/src/extend.rs new file mode 100644 index 0000000000000..5b4eb23322272 --- /dev/null +++ b/crates/config/src/extend.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +/// Strategy for extending configuration from a base file. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ExtendStrategy { + /// Uses `admerge` figment strategy. + /// Arrays are concatenated (base elements + local elements). + /// Other values are replaced (local values override base values). + #[default] + ExtendArrays, + + /// Uses `merge` figment strategy. + /// Arrays are replaced entirely (local arrays replace base arrays). + /// Other values are replaced (local values override base values). + ReplaceArrays, + + /// Throws an error if any of the keys in the inherited toml file are also in `foundry.toml`. + NoCollision, +} + +/// Configuration for extending from a base file. +/// +/// Supports two formats: +/// - String: `extends = "base.toml"` +/// - Object: `extends = { path = "base.toml", strategy = "no-collision" }` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Extends { + /// Simple string path to base file + Path(String), + /// Detailed configuration with path and strategy + Config(ExtendConfig), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExtendConfig { + pub path: String, + #[serde(default)] + pub strategy: Option, +} + +impl Extends { + /// Get the path to the base file + pub fn path(&self) -> &str { + match self { + Self::Path(path) => path, + Self::Config(config) => &config.path, + } + } + + /// Get the strategy to use for extending + pub fn strategy(&self) -> ExtendStrategy { + match self { + Self::Path(_) => ExtendStrategy::default(), + Self::Config(config) => config.strategy.unwrap_or_default(), + } + } +} + +// -- HELPERS ----------------------------------------------------------------- + +// Helper structs to only extract the 'extends' field and its strategy from the profiles +#[derive(Deserialize, Default)] +pub(crate) struct ExtendsPartialConfig { + #[serde(default)] + pub profile: Option>, +} + +#[derive(Deserialize, Default)] +pub(crate) struct ExtendsHelper { + #[serde(default)] + pub extends: Option, +} diff --git a/crates/config/src/filter.rs b/crates/config/src/filter.rs index e2f542b67f130..d92badb424a06 100644 --- a/crates/config/src/filter.rs +++ b/crates/config/src/filter.rs @@ -46,10 +46,10 @@ impl GlobMatcher { return true; } - if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { - if file_name.contains(self.as_str()) { - return true; - } + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) + && file_name.contains(self.as_str()) + { + return true; } if !path.starts_with("./") && self.as_str().starts_with("./") { @@ -59,9 +59,8 @@ impl GlobMatcher { if path.is_relative() && Path::new(self.glob().glob()).is_absolute() { if let Ok(canonicalized_path) = dunce::canonicalize(path) { return self.matcher.is_match(&canonicalized_path); - } else { - return false; } + return false; } false @@ -139,11 +138,11 @@ impl FileFilter for SkipBuildFilters { /// Only returns a match if _no_ exclusion filter matches fn is_match(&self, file: &Path) -> bool { self.matchers.iter().all(|matcher| { - if !matcher.is_match_exclude(file) { - false - } else { + if matcher.is_match_exclude(file) { file.strip_prefix(&self.project_root) .map_or(true, |stripped| matcher.is_match_exclude(stripped)) + } else { + false } }) } @@ -181,7 +180,7 @@ impl SkipBuildFilter { } /// Returns the pattern to match against a file - pub fn file_pattern(&self) -> &str { + pub const fn file_pattern(&self) -> &str { match self { Self::Tests => ".t.sol", Self::Scripts => ".s.sol", diff --git a/crates/config/src/fix.rs b/crates/config/src/fix.rs index d940fee2b62ce..ef7f1660525a3 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -21,11 +21,11 @@ impl TomlFile { Ok(Self { doc, path }) } - fn doc(&self) -> &toml_edit::DocumentMut { + const fn doc(&self) -> &toml_edit::DocumentMut { &self.doc } - fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { + const fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { &mut self.doc } @@ -79,7 +79,7 @@ impl TomlFile { return Err(InsertProfileError { message: format!("Expected [{profile_str}] to be a Table"), value, - }) + }); } // get or create the profile section let profile_map = if let Some(map) = self.get_mut(Config::PROFILE_SECTION) { @@ -87,7 +87,7 @@ impl TomlFile { } else { // insert profile section at the beginning of the map let mut profile_section = toml_edit::Table::new(); - profile_section.set_position(0); + profile_section.set_position(Some(0)); profile_section.set_implicit(true); self.insert(Config::PROFILE_SECTION, toml_edit::Item::Table(profile_section)); self.get_mut(Config::PROFILE_SECTION).expect("exists per above") @@ -99,7 +99,7 @@ impl TomlFile { return Err(InsertProfileError { message: format!("Expected [{}] to be a Table", Config::PROFILE_SECTION), value, - }) + }); }; // check the profile map for structure and existing keys if let Some(profile) = profile_map.get(profile_str) { @@ -112,7 +112,7 @@ impl TomlFile { profile_str ), value, - }) + }); } } else { return Err(InsertProfileError { @@ -122,7 +122,7 @@ impl TomlFile { profile_str ), value, - }) + }); } } // insert the profile @@ -143,9 +143,7 @@ fn fix_toml_non_strict_profiles( .as_table() .iter() .map(|(k, _)| k.to_string()) - .filter(|k| { - !(k == Config::PROFILE_SECTION || Config::STANDALONE_SECTIONS.contains(&k.as_str())) - }) + .filter(|k| !Config::is_standalone_section(k)) .collect::>(); // remove each profile and insert into [profile] section @@ -187,7 +185,7 @@ pub fn fix_tomls() -> Vec { Ok(toml_file) => toml_file, Err(err) => { warnings.push(Warning::CouldNotReadToml { path: toml, err: err.to_string() }); - continue + continue; } }; @@ -204,13 +202,11 @@ pub fn fix_tomls() -> Vec { }) } - if was_edited { - if let Err(err) = toml_file.save() { - warnings.push(Warning::CouldNotWriteToml { - path: toml_file.path().into(), - err: err.to_string(), - }); - } + if was_edited && let Err(err) = toml_file.save() { + warnings.push(Warning::CouldNotWriteToml { + path: toml_file.path().into(), + err: err.to_string(), + }); } } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 69381171989be..ac5227e1c059c 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -7,8 +7,10 @@ use serde::{Deserialize, Serialize}; pub struct FormatterConfig { /// Maximum line length where formatter will try to wrap the line pub line_length: usize, - /// Number of spaces per indentation level + /// Number of spaces per indentation level. Ignored if style is Tab pub tab_width: usize, + /// Style of indent + pub style: IndentStyle, /// Print spaces between brackets pub bracket_spacing: bool, /// Style of uint/int256 types @@ -27,24 +29,36 @@ pub struct FormatterConfig { pub override_spacing: bool, /// Wrap comments on `line_length` reached pub wrap_comments: bool, + /// Style of doc comments + pub docs_style: DocCommentStyle, /// Globs to ignore pub ignore: Vec, /// Add new line at start and end of contract declarations pub contract_new_lines: bool, /// Sort import statements alphabetically in groups (a group is separated by a newline). pub sort_imports: bool, + /// Choose between `import "a" as name` and `import * as name from "a"` + pub namespace_import_style: NamespaceImportStyle, + /// Whether to suppress spaces around the power operator (`**`). + pub pow_no_space: bool, + /// Style that determines if a broken list, should keep its elements together on their own + /// line, before breaking individually. + pub prefer_compact: PreferCompact, + /// Keep single imports on a single line even if they exceed line length. + pub single_line_imports: bool, } -/// Style of uint/int256 types -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Style of integer types. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum IntTypes { - /// Print the explicit uint256 or int256 + /// Use the type defined in the source code. + Preserve, + /// Print the full length `uint256` or `int256`. + #[default] Long, - /// Print the implicit uint or int + /// Print the alias `uint` or `int`. Short, - /// Use the type defined in the source code - Preserve, } /// Style of underscores in number literals @@ -52,9 +66,9 @@ pub enum IntTypes { #[serde(rename_all = "snake_case")] pub enum NumberUnderscore { /// Use the underscores defined in the source code + #[default] Preserve, /// Remove all underscores - #[default] Remove, /// Add an underscore every thousand, if greater than 9999 /// e.g. 1000 -> 1000 and 10000 -> 10_000 @@ -64,19 +78,19 @@ pub enum NumberUnderscore { impl NumberUnderscore { /// Returns true if the option is `Preserve` #[inline] - pub fn is_preserve(self) -> bool { + pub const fn is_preserve(self) -> bool { matches!(self, Self::Preserve) } /// Returns true if the option is `Remove` #[inline] - pub fn is_remove(self) -> bool { + pub const fn is_remove(self) -> bool { matches!(self, Self::Remove) } /// Returns true if the option is `Remove` #[inline] - pub fn is_thousands(self) -> bool { + pub const fn is_thousands(self) -> bool { matches!(self, Self::Thousands) } } @@ -94,70 +108,67 @@ pub enum HexUnderscore { Bytes, } -impl HexUnderscore { - /// Returns true if the option is `Preserve` - #[inline] - pub fn is_preserve(self) -> bool { - matches!(self, Self::Preserve) - } - - /// Returns true if the option is `Remove` - #[inline] - pub fn is_remove(self) -> bool { - matches!(self, Self::Remove) - } - - /// Returns true if the option is `Remove` - #[inline] - pub fn is_bytes(self) -> bool { - matches!(self, Self::Bytes) - } +/// Style of doc comments +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum DocCommentStyle { + /// Preserve the source code style + #[default] + Preserve, + /// Use single-line style (`///`) + Line, + /// Use block style (`/** .. */`) + Block, } /// Style of string quotes -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum QuoteStyle { - /// Use double quotes where possible + /// Use quotation mark defined in the source code. + Preserve, + /// Use double quotes where possible. + #[default] Double, - /// Use single quotes where possible + /// Use single quotes where possible. Single, - /// Use quotation mark defined in the source code - Preserve, } impl QuoteStyle { - /// Get associated quotation mark with option - pub fn quote(self) -> Option { + /// Returns the associated quotation mark character. + pub const fn quote(self) -> Option { match self { + Self::Preserve => None, Self::Double => Some('"'), Self::Single => Some('\''), - Self::Preserve => None, } } } /// Style of single line blocks in statements -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SingleLineBlockStyle { + /// Preserve the original style + #[default] + Preserve, /// Prefer single line block when possible Single, /// Always use multiline block Multi, - /// Preserve the original style - Preserve, } /// Style of function header in case it doesn't fit -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MultilineFuncHeaderStyle { - /// Write function parameters multiline first. - ParamsFirst, + /// Always write function parameters multiline. + #[serde(alias = "params_first")] // alias for backwards compatibility + ParamsAlways, /// Write function parameters multiline first when there is more than one param. ParamsFirstMulti, /// Write function attributes multiline first. + #[default] AttributesFirst, /// If function params or attrs are multiline. /// split the rest @@ -166,23 +177,98 @@ pub enum MultilineFuncHeaderStyle { AllParams, } +impl MultilineFuncHeaderStyle { + pub const fn all(&self) -> bool { + matches!(self, Self::All | Self::AllParams) + } + + pub const fn params_first(&self) -> bool { + matches!(self, Self::ParamsAlways | Self::ParamsFirstMulti) + } + + pub const fn attrib_first(&self) -> bool { + matches!(self, Self::AttributesFirst) + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NamespaceImportStyle { + /// prefer plain imports: `import "source" as name;` + #[default] + PreferPlain, + /// prefer glob imports: `import * as name from "source";` + PreferGlob, + /// preserve the original style + Preserve, +} + +/// Style that determines if a broken list, should keep its elements together on their own line, +/// before breaking individually. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PreferCompact { + /// All elements are preferred consistent. + None, + /// Calls are preferred compact. Events and errors break consistently. + Calls, + /// Events are preferred compact. Calls and errors break consistently. + Events, + /// Errors are preferred compact. Calls and events break consistently. + Errors, + /// Events and errors are preferred compact. Calls break consistently. + EventsErrors, + /// All elements are preferred compact. + #[default] + All, +} + +impl PreferCompact { + pub const fn calls(&self) -> bool { + matches!(self, Self::All | Self::Calls) + } + + pub const fn events(&self) -> bool { + matches!(self, Self::All | Self::Events | Self::EventsErrors) + } + + pub const fn errors(&self) -> bool { + matches!(self, Self::All | Self::Errors | Self::EventsErrors) + } +} + +/// Style of indent +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum IndentStyle { + #[default] + Space, + Tab, +} + impl Default for FormatterConfig { fn default() -> Self { Self { line_length: 120, tab_width: 4, + style: IndentStyle::Space, bracket_spacing: false, - int_types: IntTypes::Long, - multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst, - quote_style: QuoteStyle::Double, - number_underscore: NumberUnderscore::Preserve, - hex_underscore: HexUnderscore::Remove, - single_line_statement_blocks: SingleLineBlockStyle::Preserve, + int_types: IntTypes::default(), + multiline_func_header: MultilineFuncHeaderStyle::default(), + quote_style: QuoteStyle::default(), + number_underscore: NumberUnderscore::default(), + hex_underscore: HexUnderscore::default(), + single_line_statement_blocks: SingleLineBlockStyle::default(), override_spacing: false, wrap_comments: false, ignore: vec![], contract_new_lines: false, sort_imports: false, + namespace_import_style: NamespaceImportStyle::default(), + pow_no_space: false, + prefer_compact: PreferCompact::default(), + docs_style: DocCommentStyle::default(), + single_line_imports: false, } } } diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index 1d2c35ff33bf9..e38e6040ac902 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -37,33 +37,60 @@ impl FsPermissions { /// Caution: This should be called with normalized paths if the `allowed_paths` are also /// normalized. pub fn is_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool { - self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default() + self.find_permission(path).is_some_and(|perm| perm.is_granted(kind)) } /// Returns the permission for the matching path. /// - /// This finds the longest matching path with resolved sym links, e.g. if we have the following - /// permissions: + /// This finds the longest matching path with resolved sym links and returns the highest + /// privilege permission. The algorithm works as follows: /// - /// `./out` = `read` - /// `./out/contracts` = `read-write` + /// 1. Find all permissions where the path matches (using longest path match) + /// 2. Return the highest privilege permission from those matches /// - /// And we check for `./out/contracts/MyContract.sol` we will get `read-write` as permission. + /// Example scenarios: + /// + /// ```text + /// ./out = read + /// ./out/contracts = read-write + /// ``` + /// Checking `./out/contracts/MyContract.sol` returns `read-write` (longest path match) + /// + /// ```text + /// ./out/contracts = read + /// ./out/contracts = write + /// ``` + /// Checking `./out/contracts/MyContract.sol` returns `write` (highest privilege, which also + /// grants read access) pub fn find_permission(&self, path: &Path) -> Option { - let mut permission: Option<&PathPermission> = None; + let mut max_path_len = 0; + let mut highest_permission = FsAccessPermission::None; + + // Find all matching permissions at the longest matching path for perm in &self.permissions { let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone()); - if path.starts_with(permission_path) { - if let Some(active_perm) = permission.as_ref() { - // the longest path takes precedence - if perm.path < active_perm.path { - continue; + if path.starts_with(&permission_path) { + let path_len = permission_path.components().count(); + if path_len > max_path_len { + // Found a longer matching path, reset to this permission + max_path_len = path_len; + highest_permission = perm.access; + } else if path_len == max_path_len { + // Same path length, keep the highest privilege + highest_permission = match (highest_permission, perm.access) { + (FsAccessPermission::ReadWrite, _) + | (FsAccessPermission::Read, FsAccessPermission::Write) + | (FsAccessPermission::Write, FsAccessPermission::Read) => { + FsAccessPermission::ReadWrite + } + (FsAccessPermission::None, perm) => perm, + (existing_perm, _) => existing_perm, } } - permission = Some(perm); } } - permission.map(|perm| perm.access) + + (max_path_len > 0).then_some(highest_permission) } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries @@ -85,12 +112,12 @@ impl FsPermissions { } /// Returns true if no permissions are configured - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.permissions.is_empty() } /// Returns the number of configured permissions - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.permissions.len() } } @@ -131,7 +158,7 @@ impl PathPermission { } /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { + pub const fn is_granted(&self, kind: FsAccessKind) -> bool { self.access.is_granted(kind) } } @@ -160,22 +187,22 @@ pub enum FsAccessPermission { /// FS access is _not_ allowed #[default] None, - /// FS access is allowed, this includes `read` + `write` - ReadWrite, /// Only reading is allowed Read, /// Only writing is allowed Write, + /// FS access is allowed, this includes `read` + `write` + ReadWrite, } impl FsAccessPermission { /// Returns true if the access is allowed - pub fn is_granted(&self, kind: FsAccessKind) -> bool { + pub const fn is_granted(&self, kind: FsAccessKind) -> bool { match (self, kind) { (Self::ReadWrite, _) => true, - (Self::None, _) => false, - (Self::Read, FsAccessKind::Read) => true, (Self::Write, FsAccessKind::Write) => true, + (Self::Read, FsAccessKind::Read) => true, + (Self::None, _) => false, _ => false, } } @@ -270,4 +297,34 @@ mod tests { let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap(); assert_eq!(FsAccessPermission::Write, permission); } + + #[test] + fn read_write_permission_combination() { + // When multiple permissions are defined for the same path, highest privilege wins + let permissions = FsPermissions::new(vec![ + PathPermission::read("./out/contracts"), + PathPermission::write("./out/contracts"), + ]); + + let permission = + permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::ReadWrite, permission); + } + + #[test] + fn longest_path_takes_precedence() { + let permissions = FsPermissions::new(vec![ + PathPermission::read_write("./out"), + PathPermission::read("./out/contracts"), + ]); + + // More specific path (./out/contracts) takes precedence even with lower privilege + let permission = + permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::Read, permission); + + // Broader path still applies to its own files + let permission = permissions.find_permission(Path::new("./out/other.sol")).unwrap(); + assert_eq!(FsAccessPermission::ReadWrite, permission); + } } diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 26e1c080cbc8d..8f63718e086cd 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -1,6 +1,7 @@ //! Configuration for fuzz testing. use alloy_primitives::U256; +use foundry_compilers::utils::canonicalized; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -9,11 +10,14 @@ use std::path::PathBuf; pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, - /// The maximum number of test case rejections allowed by proptest, to be - /// encountered during usage of `vm.assume` cheatcode. This will be used - /// to set the `max_global_rejects` value in proptest test runner config. - /// `max_local_rejects` option isn't exposed here since we're not using - /// `prop_filter`. + /// Optional 1-based fuzz run to execute. + pub run: Option, + /// Optional fuzz worker ID to pair with `run`. + pub worker: Option, + /// Fails the fuzzed test if a revert occurs. + pub fail_on_revert: bool, + /// The maximum number of test case rejections allowed, + /// encountered during usage of `vm.assume` cheatcode. pub max_test_rejects: u32, /// Optional seed for the fuzzing RNG algorithm pub seed: Option, @@ -22,10 +26,11 @@ pub struct FuzzConfig { pub dictionary: FuzzDictionaryConfig, /// Number of runs to execute and include in the gas report. pub gas_report_samples: u32, + /// The fuzz corpus configuration. + #[serde(flatten)] + pub corpus: FuzzCorpusConfig, /// Path where fuzz failures are recorded and replayed. pub failure_persist_dir: Option, - /// Name of the file to record fuzz failures, defaults to `failures`. - pub failure_persist_file: Option, /// show `console.log` in fuzz test, defaults to `false` pub show_logs: bool, /// Optional timeout (in seconds) for each property test @@ -36,12 +41,15 @@ impl Default for FuzzConfig { fn default() -> Self { Self { runs: 256, + run: None, + worker: None, + fail_on_revert: true, max_test_rejects: 65536, seed: None, dictionary: FuzzDictionaryConfig::default(), gas_report_samples: 256, + corpus: FuzzCorpusConfig::default(), failure_persist_dir: None, - failure_persist_file: None, show_logs: false, timeout: None, } @@ -51,11 +59,7 @@ impl Default for FuzzConfig { impl FuzzConfig { /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. pub fn new(cache_dir: PathBuf) -> Self { - Self { - failure_persist_dir: Some(cache_dir), - failure_persist_file: Some("failures".to_string()), - ..Default::default() - } + Self { failure_persist_dir: Some(cache_dir), ..Default::default() } } } @@ -73,24 +77,119 @@ pub struct FuzzDictionaryConfig { /// Once the fuzzer exceeds this limit, it will start evicting random entries /// /// This limit is put in place to prevent memory blowup. - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] pub max_fuzz_dictionary_addresses: usize, /// How many values to record at most. /// Once the fuzzer exceeds this limit, it will start evicting random entries - #[serde(deserialize_with = "crate::deserialize_usize_or_max")] + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] pub max_fuzz_dictionary_values: usize, + /// How many literal values to seed from the AST, at most. + /// + /// This value is independent from the max amount of addresses and values. + #[serde( + deserialize_with = "crate::deserialize_usize_or_max", + serialize_with = "crate::serialize_usize_or_max" + )] + pub max_fuzz_dictionary_literals: usize, } impl Default for FuzzDictionaryConfig { fn default() -> Self { + const MB: usize = 1024 * 1024; + Self { dictionary_weight: 40, include_storage: true, include_push_bytes: true, - // limit this to 300MB - max_fuzz_dictionary_addresses: (300 * 1024 * 1024) / 20, - // limit this to 200MB - max_fuzz_dictionary_values: (200 * 1024 * 1024) / 32, + max_fuzz_dictionary_addresses: 300 * MB / 20, + max_fuzz_dictionary_values: 300 * MB / 32, + max_fuzz_dictionary_literals: 200 * MB / 32, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct FuzzCorpusConfig { + // Path to corpus directory, enabled coverage guided fuzzing mode. + // If not set then sequences producing new coverage are not persisted and mutated. + pub corpus_dir: Option, + // Whether corpus to use gzip file compression and decompression. + pub corpus_gzip: bool, + // Number of mutations until entry marked as eligible to be flushed from in-memory corpus. + // Mutations will be performed at least `corpus_min_mutations` times. + pub corpus_min_mutations: usize, + // Number of corpus that won't be evicted from memory. + pub corpus_min_size: usize, + /// Whether to collect and display edge coverage metrics. + pub show_edge_coverage: bool, + /// Whether to collect edge coverage from native Rust crates compiled with + /// SanitizerCoverage instrumentation (e.g. precompile implementations). + /// Requires building forge with a `RUSTC_WRAPPER` that injects sancov flags. + pub sancov_edges: bool, + /// Whether to capture comparison operands from sancov-instrumented crates + /// and inject them into the fuzz dictionary. Independent of `sancov_edges`. + pub sancov_trace_cmp: bool, +} + +impl FuzzCorpusConfig { + pub fn with_test(&mut self, contract: &str, test: &str) { + if let Some(corpus_dir) = &self.corpus_dir { + self.corpus_dir = Some(canonicalized(corpus_dir.join(contract).join(test))); + } + } + + /// Whether any edge coverage (EVM or sancov) should be collected. + pub const fn collect_edge_coverage(&self) -> bool { + self.corpus_dir.is_some() || self.show_edge_coverage || self.sancov_edges + } + + /// Whether the EVM `EdgeCovInspector` should be enabled. + /// + /// Disabled when sancov edge coverage is active — sancov provides the + /// coverage signal and EVM hits from the Solidity handler would dilute it. + /// Trace-cmp-only mode keeps EVM edges enabled since trace-cmp only + /// contributes dictionary entries, not edge coverage. + pub const fn collect_evm_edge_coverage(&self) -> bool { + !self.sancov_edges && (self.corpus_dir.is_some() || self.show_edge_coverage) + } + + /// Whether sancov edge coverage collection is enabled. + pub const fn collect_sancov_edges(&self) -> bool { + self.sancov_edges + } + + /// Whether sancov trace-cmp capture is enabled. + pub const fn collect_sancov_trace_cmp(&self) -> bool { + self.sancov_trace_cmp + } + + /// Whether either sancov coverage mode is active. + pub const fn sancov_active(&self) -> bool { + self.sancov_edges || self.sancov_trace_cmp + } + + /// Whether coverage guided fuzzing is enabled. + pub const fn is_coverage_guided(&self) -> bool { + self.corpus_dir.is_some() + } +} + +impl Default for FuzzCorpusConfig { + fn default() -> Self { + Self { + corpus_dir: None, + corpus_gzip: true, + corpus_min_mutations: 5, + corpus_min_size: 0, + show_edge_coverage: false, + sancov_edges: false, + sancov_trace_cmp: false, } } } diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index fa67b2426cf00..000cefc26737a 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,10 +1,13 @@ +use std::collections::BTreeSet; + use crate::Config; use alloy_primitives::map::HashMap; use figment::{ - value::{Dict, Map, Value}, Figment, Profile, Provider, + value::{Dict, Map, Value}, }; use foundry_compilers::ProjectCompileOutput; +use foundry_evm_networks::NetworkVariant; use itertools::Itertools; mod natspec; @@ -97,7 +100,11 @@ impl InlineConfig { /// Returns a [`figment::Provider`] for this [`InlineConfig`] at the given contract and function /// level. - pub fn provide<'a>(&'a self, contract: &'a str, function: &'a str) -> InlineConfigProvider<'a> { + pub const fn provide<'a>( + &'a self, + contract: &'a str, + function: &'a str, + ) -> InlineConfigProvider<'a> { InlineConfigProvider { inline: self, contract, function } } @@ -119,6 +126,42 @@ impl InlineConfig { self.get_function(contract, function).is_some_and(|map| !map.is_empty()) } + /// Returns the configured [`NetworkVariant`] for a given test, checking function-level first + /// then contract-level. Returns `None` if no network annotation is present. + pub fn network_for( + &self, + profile: &Profile, + contract: &str, + function: &str, + ) -> Option { + let data = self.provide(contract, function).data().ok()?; + let dict = data.get(profile).or_else(|| data.get(&Profile::Default))?; + if let Some(Value::Dict(_, networks)) = dict.get("networks") + && let Some(Value::String(_, s)) = networks.get("network") + { + return s.parse().ok(); + } + None + } + + /// Returns all distinct [`NetworkVariant`]s referenced in any inline config annotation. + /// + /// This is used to determine whether a multi-network test pass is needed. + pub fn referenced_override_networks(&self, profile: &Profile) -> Vec { + let mut seen = BTreeSet::new(); + for (contract, function) in self.fn_level.keys() { + if let Some(v) = self.network_for(profile, contract, function) { + seen.insert(v); + } + } + for contract in self.contract_level.keys() { + if let Some(v) = self.network_for(profile, contract, "") { + seen.insert(v); + } + } + seen.into_iter().collect() + } + fn get_contract(&self, contract: &str) -> Option<&DataMap> { self.contract_level.get(contract) } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 92c610081d068..f1d5844101379 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -1,17 +1,14 @@ -use super::{InlineConfigError, InlineConfigErrorKind, INLINE_CONFIG_PREFIX}; +use super::{INLINE_CONFIG_PREFIX, InlineConfigError, InlineConfigErrorKind}; use figment::Profile; use foundry_compilers::{ - artifacts::{ast::NodeType, Node}, ProjectCompileOutput, + artifacts::{Node, ast::NodeType}, }; use itertools::Itertools; use serde_json::Value; -use solar_parse::{ - ast::{ - interface::{self, Session}, - Arena, CommentKind, Item, ItemKind, - }, - Parser, +use solar::{ + ast::{self, Span}, + interface::Session, }; use std::{collections::BTreeMap, path::Path}; @@ -22,7 +19,7 @@ pub struct NatSpec { pub contract: String, /// The function annotated with the natspec. None if the natspec is contract-level. pub function: Option, - /// The line the natspec appears, in the form `row:col:length`, i.e. `10:21:122`. + /// The line the natspec begins, in the form `line:column`, i.e. `10:21`. pub line: String, /// The actual natspec comment, without slashes or block punctuation. pub docs: String, @@ -32,30 +29,47 @@ impl NatSpec { /// Factory function that extracts a vector of [`NatSpec`] instances from /// a solc compiler output. The root path is to express contract base dirs. /// That is essential to match per-test configs at runtime. + #[instrument(name = "NatSpec::parse", skip_all)] pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { let mut natspecs: Vec = vec![]; + let compiler = output.parser().solc().compiler(); + let solar = SolarParser::new(compiler.sess()); let solc = SolcParser::new(); - let solar = SolarParser::new(); for (id, artifact) in output.artifact_ids() { - let abs_path = id.source.as_path(); - let path = abs_path.strip_prefix(root).unwrap_or(abs_path); + let path = id.source.as_path(); + let path = path.strip_prefix(root).unwrap_or(path); + let abs_path = &*root.join(path); let contract_name = id.name.split('.').next().unwrap(); // `id.identifier` but with the stripped path. let contract = format!("{}:{}", path.display(), id.name); - let mut used_solc_ast = false; - if let Some(ast) = &artifact.ast { - if let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { - solc.parse(&mut natspecs, &contract, node, true); - used_solc_ast = true; + let mut used_solar = false; + compiler.enter_sequential(|compiler| { + if let Some((_, source)) = compiler.gcx().get_ast_source(abs_path) + && let Some(ast) = &source.ast + { + solar.parse_ast(&mut natspecs, ast, &contract, contract_name); + used_solar = true; } + }); + + if !used_solar { + warn!(?abs_path, %contract, "could not parse natspec with solar"); } - if !used_solc_ast { - if let Ok(src) = std::fs::read_to_string(abs_path) { - solar.parse(&mut natspecs, &src, &contract, contract_name); - } + let used_solc = if !used_solar + && let Some(ast) = &artifact.ast + && let Some(node) = solc.contract_root_node(&ast.nodes, &contract) + { + solc.parse(&mut natspecs, &contract, node, true); + true + } else { + false + }; + + if !used_solar && !used_solc { + warn!(?abs_path, %contract, "could not parse natspec"); } } @@ -116,7 +130,7 @@ struct SolcParser { } impl SolcParser { - fn new() -> Self { + const fn new() -> Self { Self { _private: () } } @@ -126,10 +140,10 @@ impl SolcParser { for n in nodes { if n.node_type == NodeType::ContractDefinition { let contract_data = &n.other; - if let Value::String(contract_name) = contract_data.get("name")? { - if contract_id.ends_with(contract_name) { - return Some(n) - } + if let Value::String(contract_name) = contract_data.get("name")? + && contract_id.ends_with(contract_name) + { + return Some(n); } } } @@ -140,10 +154,8 @@ impl SolcParser { /// If a natspec is found it is added to `natspecs` fn parse(&self, natspecs: &mut Vec, contract: &str, node: &Node, root: bool) { // If we're at the root contract definition node, try parsing contract-level natspec - if root { - if let Some((docs, line)) = self.get_node_docs(&node.other) { - natspecs.push(NatSpec { contract: contract.into(), function: None, docs, line }) - } + if root && let Some((docs, line)) = self.get_node_docs(&node.other) { + natspecs.push(NatSpec { contract: contract.into(), function: None, docs, line }) } for n in &node.nodes { if let Some((function, docs, line)) = self.get_fn_data(n) { @@ -170,7 +182,7 @@ impl SolcParser { let fn_data = &node.other; let fn_name: String = self.get_fn_name(fn_data)?; let (fn_docs, docs_src_line) = self.get_node_docs(fn_data)?; - return Some((fn_name, fn_docs, docs_src_line)) + return Some((fn_name, fn_docs, docs_src_line)); } None @@ -190,59 +202,55 @@ impl SolcParser { /// "raw:col:length". /// - `None` in case the function has not natspec comments. fn get_node_docs(&self, data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = data.get("documentation")? { - if let Value::String(comment) = fn_docs.get("text")? { - if comment.contains(INLINE_CONFIG_PREFIX) { - let mut src_line = fn_docs - .get("src") - .map(|src| src.to_string()) - .unwrap_or_else(|| String::from("")); - - src_line.retain(|c| c != '"'); - return Some((comment.into(), src_line)) - } - } + if let Value::Object(fn_docs) = data.get("documentation")? + && let Value::String(comment) = fn_docs.get("text")? + && comment.contains(INLINE_CONFIG_PREFIX) + { + let mut src_line = fn_docs + .get("src") + .map(|src| src.to_string()) + .unwrap_or_else(|| String::from("")); + + src_line.retain(|c| c != '"'); + return Some((comment.into(), src_line)); } None } } -struct SolarParser { - _private: (), +struct SolarParser<'a> { + sess: &'a Session, } -impl SolarParser { - fn new() -> Self { - Self { _private: () } +impl<'a> SolarParser<'a> { + const fn new(sess: &'a Session) -> Self { + Self { sess } } - fn parse( + fn parse_ast( &self, natspecs: &mut Vec, - src: &str, + source_unit: &ast::SourceUnit<'_>, contract_id: &str, contract_name: &str, ) { - // Fast path to avoid parsing the file. - if !src.contains(INLINE_CONFIG_PREFIX) { - return; - } - - let mut handle_docs = |item: &Item<'_>| { + let mut handle_docs = |item: &ast::Item<'_>| { if item.docs.is_empty() { return; } + let mut span = Span::DUMMY; let lines = item .docs .iter() .filter_map(|d| { let s = d.symbol.as_str(); if !s.contains(INLINE_CONFIG_PREFIX) { - return None + return None; } + span = if span.is_dummy() { d.span } else { span.to(d.span) }; match d.kind { - CommentKind::Line => Some(s.trim().to_string()), - CommentKind::Block => Some( + ast::CommentKind::Line => Some(s.trim().to_string()), + ast::CommentKind::Block => Some( s.lines() .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) .map(|line| line.trim_start().trim_start_matches('*').trim()) @@ -255,11 +263,9 @@ impl SolarParser { if lines.is_empty() { return; } - let span = - item.docs.iter().map(|doc| doc.span).reduce(|a, b| a.to(b)).unwrap_or_default(); natspecs.push(NatSpec { contract: contract_id.to_string(), - function: if let ItemKind::Function(f) = &item.kind { + function: if let ast::ItemKind::Function(f) = &item.kind { Some( f.header .name @@ -269,14 +275,52 @@ impl SolarParser { } else { None }, - line: format!("{}:{}:0", span.lo().0, span.hi().0), + line: { + let (_, loc) = self.sess.source_map().span_to_location_info(span); + format!("{}:{}", loc.lo.line, loc.lo.col.0 + 1) + }, docs: lines, }); }; + for item in source_unit.items.iter() { + let ast::ItemKind::Contract(c) = &item.kind else { continue }; + if c.name.as_str() != contract_name { + continue; + } + + // Handle contract level doc comments. + handle_docs(item); + + // Handle function level doc comments. + for item in c.body.iter() { + let ast::ItemKind::Function(_) = &item.kind else { continue }; + handle_docs(item); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use snapbox::{assert_data_eq, str}; + use solar::parse::{ + Parser, + ast::{Arena, interface}, + }; + + fn parse(natspecs: &mut Vec, src: &str, contract_id: &str, contract_name: &str) { + // Fast path to avoid parsing the file. + if !src.contains(INLINE_CONFIG_PREFIX) { + return; + } + let sess = Session::builder() .with_silent_emitter(Some("Inline config parsing failed".to_string())) .build(); + let solar = SolarParser::new(&sess); let _ = sess.enter(|| -> interface::Result<()> { let arena = Arena::new(); @@ -289,31 +333,11 @@ impl SolarParser { let source_unit = parser.parse_file().map_err(|e| e.emit())?; - for item in source_unit.items.iter() { - let ItemKind::Contract(c) = &item.kind else { continue }; - if c.name.as_str() != contract_name { - continue; - } - - // Handle contract level doc comments. - handle_docs(item); - - // Handle function level doc comments. - for item in c.body.iter() { - let ItemKind::Function(_) = &item.kind else { continue }; - handle_docs(item); - } - } + solar.parse_ast(natspecs, &source_unit, contract_id, contract_name); Ok(()) }); } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; #[test] fn can_reject_invalid_profiles() { @@ -370,41 +394,45 @@ function f2() {} /** forge-config: default.fuzz.runs = 800 */ function f3() {} } "; let mut natspecs = vec![]; - let id = || "path.sol:C".to_string(); - let solar_parser = SolarParser::new(); - solar_parser.parse(&mut natspecs, src, &id(), "C"); - assert_eq!( - natspecs, - [ - // f1 - NatSpec { - contract: id(), - function: Some("f1".to_string()), - line: "14:134:0".to_string(), - docs: "forge-config: default.fuzz.runs = 600\nforge-config: default.fuzz.runs = 601".to_string(), - }, - // f2 - NatSpec { - contract: id(), - function: Some("f2".to_string()), - line: "164:208:0".to_string(), - docs: "forge-config: default.fuzz.runs = 700".to_string(), - }, - // f3 - NatSpec { - contract: id(), - function: Some("f3".to_string()), - line: "226:270:0".to_string(), - docs: "forge-config: default.fuzz.runs = 800".to_string(), - }, - // f4 - NatSpec { - contract: id(), - function: Some("f4".to_string()), - line: "289:391:0".to_string(), - docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), - }, - ] + parse(&mut natspecs, src, "path.sol:C", "C"); + assert_data_eq!( + format!("{natspecs:#?}"), + str![[r#" +[ + NatSpec { + contract: "path.sol:C", + function: Some( + "f1", + ), + line: "2:14", + docs: "forge-config: default.fuzz.runs = 600/nforge-config: default.fuzz.runs = 601", + }, + NatSpec { + contract: "path.sol:C", + function: Some( + "f2", + ), + line: "7:8", + docs: "forge-config: default.fuzz.runs = 700", + }, + NatSpec { + contract: "path.sol:C", + function: Some( + "f3", + ), + line: "8:18", + docs: "forge-config: default.fuzz.runs = 800", + }, + NatSpec { + contract: "path.sol:C", + function: Some( + "f4", + ), + line: "10:1", + docs: "forge-config: default.fuzz.runs = 1024/nforge-config: default.fuzz.max-test-rejects = 500", + }, +] +"#]] ); } @@ -427,19 +455,21 @@ contract FuzzInlineConf is DSTest { } "#; let mut natspecs = vec![]; - let solar = SolarParser::new(); - let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); - solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); - assert_eq!( - natspecs, - [ - NatSpec { - contract: id(), - function: Some("testInlineConfFuzz".to_string()), - line: "141:255:0".to_string(), - docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), - }, - ] + parse(&mut natspecs, src, "inline/FuzzInlineConf.t.sol:FuzzInlineConf", "FuzzInlineConf"); + assert_data_eq!( + format!("{natspecs:#?}"), + str![[r#" +[ + NatSpec { + contract: "inline/FuzzInlineConf.t.sol:FuzzInlineConf", + function: Some( + "testInlineConfFuzz", + ), + line: "8:5", + docs: "forge-config: default.fuzz.runs = 1024/nforge-config: default.fuzz.max-test-rejects = 500", + }, +] +"#]] ); } @@ -478,8 +508,8 @@ contract FuzzInlineConf is DSTest { fn natspec() -> NatSpec { let conf = r" - forge-config: default.fuzz.runs = 600 - forge-config: ci.fuzz.runs = 500 + forge-config: default.fuzz.runs = 600 + forge-config: ci.fuzz.runs = 500 ========= SOME NOISY TEXT ============= 䩹𧀫Jx닧Ʀ̳盅K擷􅟽Ɂw첊}ꏻk86ᖪk-檻ܴ렝[Dz𐤬oᘓƤ ꣖ۻ%Ƅ㪕ς:(饁΍av/烲ڻ̛߉橞㗡𥺃̹M봓䀖ؿ̄󵼁)𯖛d􂽰񮍃 @@ -516,31 +546,44 @@ contract FuzzInlineConf2 is DSTest { } "#; let mut natspecs = vec![]; - let solar = SolarParser::new(); - let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); - solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); - assert_eq!( - natspecs, - [NatSpec { - contract: id(), - function: Some("testInlineConfFuzz1".to_string()), - line: "142:181:0".to_string(), - docs: "forge-config: default.fuzz.runs = 1".to_string(), - },] + parse(&mut natspecs, src, "inline/FuzzInlineConf.t.sol:FuzzInlineConf", "FuzzInlineConf"); + assert_data_eq!( + format!("{natspecs:#?}"), + str![[r#" +[ + NatSpec { + contract: "inline/FuzzInlineConf.t.sol:FuzzInlineConf", + function: Some( + "testInlineConfFuzz1", + ), + line: "8:6", + docs: "forge-config: default.fuzz.runs = 1", + }, +] +"#]] ); let mut natspecs = vec![]; - let id = || "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2".to_string(); - solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf2"); - assert_eq!( - natspecs, - [NatSpec { - contract: id(), - function: Some("testInlineConfFuzz2".to_string()), - line: "264:303:0".to_string(), - // should not get config from previous contract - docs: "forge-config: default.fuzz.runs = 2".to_string(), - },] + parse( + &mut natspecs, + src, + "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2", + "FuzzInlineConf2", + ); + assert_data_eq!( + format!("{natspecs:#?}"), + str![[r#" +[ + NatSpec { + contract: "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2", + function: Some( + "testInlineConfFuzz2", + ), + line: "13:5", + docs: "forge-config: default.fuzz.runs = 2", + }, +] +"#]] ); } @@ -560,25 +603,27 @@ contract FuzzInlineConf is DSTest { function testInlineConfFuzz2() {} }"#; let mut natspecs = vec![]; - let solar = SolarParser::new(); - let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); - solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); - assert_eq!( - natspecs, - [ - NatSpec { - contract: id(), - function: None, - line: "101:140:0".to_string(), - docs: "forge-config: default.fuzz.runs = 1".to_string(), - }, - NatSpec { - contract: id(), - function: Some("testInlineConfFuzz1".to_string()), - line: "181:220:0".to_string(), - docs: "forge-config: default.fuzz.runs = 3".to_string(), - } - ] + parse(&mut natspecs, src, "inline/FuzzInlineConf.t.sol:FuzzInlineConf", "FuzzInlineConf"); + assert_data_eq!( + format!("{natspecs:#?}"), + str![[r#" +[ + NatSpec { + contract: "inline/FuzzInlineConf.t.sol:FuzzInlineConf", + function: None, + line: "7:1", + docs: "forge-config: default.fuzz.runs = 1", + }, + NatSpec { + contract: "inline/FuzzInlineConf.t.sol:FuzzInlineConf", + function: Some( + "testInlineConfFuzz1", + ), + line: "9:5", + docs: "forge-config: default.fuzz.runs = 3", + }, +] +"#]] ); } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 0c5dbc567d363..15f8d4608ac5f 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -1,6 +1,6 @@ //! Configuration for invariant testing -use crate::fuzz::FuzzDictionaryConfig; +use crate::fuzz::{FuzzCorpusConfig, FuzzDictionaryConfig}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -26,6 +26,9 @@ pub struct InvariantConfig { pub max_assume_rejects: u32, /// Number of runs to execute and include in the gas report. pub gas_report_samples: u32, + /// The fuzz corpus configuration. + #[serde(flatten)] + pub corpus: FuzzCorpusConfig, /// Path where invariant failures are recorded and replayed. pub failure_persist_dir: Option, /// Whether to collect and display fuzzed selectors metrics. @@ -34,6 +37,18 @@ pub struct InvariantConfig { pub timeout: Option, /// Display counterexample as solidity calls. pub show_solidity: bool, + /// Maximum time (in seconds) between generated txs. + pub max_time_delay: Option, + /// Maximum number of blocks elapsed between generated txs. + pub max_block_delay: Option, + /// Number of calls to execute between invariant assertions. + /// + /// - `0`: Only assert on the last call of each run (fastest, but may miss exact breaking call) + /// - `1` (default): Assert after every call (current behavior, most precise) + /// - `N`: Assert every N calls AND always on the last call + /// + /// Example: `check_interval = 10` means assert after calls 10, 20, 30, ... and the last call. + pub check_interval: u32, } impl Default for InvariantConfig { @@ -47,10 +62,14 @@ impl Default for InvariantConfig { shrink_run_limit: 5000, max_assume_rejects: 65536, gas_report_samples: 256, + corpus: FuzzCorpusConfig::default(), failure_persist_dir: None, - show_metrics: false, + show_metrics: true, timeout: None, show_solidity: false, + max_time_delay: None, + max_block_delay: None, + check_interval: 1, } } } @@ -58,19 +77,11 @@ impl Default for InvariantConfig { impl InvariantConfig { /// Creates invariant configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. pub fn new(cache_dir: PathBuf) -> Self { - Self { - runs: 256, - depth: 500, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, - shrink_run_limit: 5000, - max_assume_rejects: 65536, - gas_report_samples: 256, - failure_persist_dir: Some(cache_dir), - show_metrics: false, - timeout: None, - show_solidity: false, - } + Self { failure_persist_dir: Some(cache_dir), ..Default::default() } + } + + /// Returns true if generated invariant calls may advance block time or height. + pub const fn has_delay(&self) -> bool { + self.max_block_delay.is_some() || self.max_time_delay.is_some() } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 8224b6b203155..71eb2a96d7152 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -3,49 +3,49 @@ //! Foundry configuration. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; use crate::cache::StorageCachingConfig; -use alloy_primitives::{address, map::AddressHashMap, Address, FixedBytes, B256, U256}; +use alloy_primitives::{Address, B256, FixedBytes, U256, address, map::AddressHashMap}; use eyre::{ContextCompat, WrapErr}; use figment::{ + Error, Figment, Metadata, Profile, Provider, providers::{Env, Format, Serialized, Toml}, value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, }; use filter::GlobMatcher; use foundry_compilers::{ + ArtifactOutput, ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, + RestrictionsWithVersion, VyperLanguage, artifacts::{ + BytecodeHash, DebuggingSettings, EvmVersion, Libraries, ModelCheckerSettings, + ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, Settings, SettingsMetadata, + Severity, output_selection::{ContractOutputSelection, OutputSelection}, remappings::{RelativeRemapping, Remapping}, - serde_helpers, BytecodeHash, DebuggingSettings, EvmVersion, Libraries, - ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, - Settings, SettingsMetadata, Severity, + serde_helpers, }, cache::SOLIDITY_FILES_CACHE_FILENAME, compilers::{ + Compiler, multi::{MultiCompiler, MultiCompilerSettings}, solc::{Solc, SolcCompiler}, vyper::{Vyper, VyperSettings}, - Compiler, }, error::SolcError, - multi::{MultiCompilerParsedSource, MultiCompilerRestrictions}, - solc::{CliSettings, SolcSettings}, - ArtifactOutput, ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, - RestrictionsWithVersion, VyperLanguage, + multi::{MultiCompilerParser, MultiCompilerRestrictions}, + solc::{CliSettings, SolcLanguage, SolcSettings}, }; use regex::Regex; -use revm::primitives::hardfork::SpecId; use semver::Version; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use std::{ borrow::Cow, collections::BTreeMap, - fs, + fs, io, path::{Path, PathBuf}, str::FromStr, }; @@ -53,19 +53,20 @@ use std::{ mod macros; pub mod utils; +pub use foundry_evm_hardforks::{FoundryHardfork, FromEvmVersion, evm_spec_id}; pub use utils::*; mod endpoints; pub use endpoints::{ ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, RpcEndpoints, + builtin_rpc_url, }; mod etherscan; -use etherscan::{ - EtherscanConfigError, EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig, -}; +pub use etherscan::EtherscanConfigError; +use etherscan::{EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig}; -mod resolve; +pub mod resolve; pub use resolve::UnresolvedEnvVarError; pub mod cache; @@ -99,14 +100,13 @@ pub mod fix; // reexport so cli types can implement `figment::Provider` to easily merge compiler arguments pub use alloy_chains::{Chain, NamedChain}; pub use figment; -use foundry_block_explorers::EtherscanApiVersion; pub mod providers; pub use providers::Remappings; use providers::*; mod fuzz; -pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; +pub use fuzz::{FuzzConfig, FuzzCorpusConfig, FuzzDictionaryConfig}; mod invariant; pub use invariant::InvariantConfig; @@ -118,7 +118,7 @@ pub mod soldeer; use soldeer::{SoldeerConfig, SoldeerDependencyConfig}; mod vyper; -use vyper::VyperConfig; +pub use vyper::VyperConfig; mod bind_json; use bind_json::BindJsonConfig; @@ -126,6 +126,12 @@ use bind_json::BindJsonConfig; mod compilation; pub use compilation::{CompilationRestrictions, SettingsOverrides}; +pub mod extend; +use extend::Extends; + +use foundry_evm_networks::NetworkConfigs; +pub use semver; + /// Foundry configuration /// /// # Defaults @@ -157,7 +163,7 @@ pub use compilation::{CompilationRestrictions, SettingsOverrides}; /// the "default" meta-profile. /// /// Note that these behaviors differ from those of [`Config::figment()`]. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -180,50 +186,61 @@ pub struct Config { #[serde(default = "root_default", skip_serializing)] pub root: PathBuf, - /// path of the source contracts dir, like `src` or `contracts` + /// Configuration for extending from another foundry.toml (base) file. + /// + /// Can be either a string path or an object with path and strategy. + /// Base files cannot extend (inherit) other files. + #[serde(default, skip_serializing)] + pub extends: Option, + + /// Path of the sources directory. + /// + /// Defaults to `src`. pub src: PathBuf, - /// path of the test dir + /// Path of the tests directory. pub test: PathBuf, - /// path of the script dir + /// Path of the scripts directory. pub script: PathBuf, - /// path to where artifacts shut be written to + /// Path to the artifacts directory. pub out: PathBuf, - /// all library folders to include, `lib`, `node_modules` + /// Paths to all library folders, such as `lib`, or `node_modules`. pub libs: Vec, - /// `Remappings` to use for this repo + /// Remappings to use for this repo pub remappings: Vec, - /// Whether to autodetect remappings by scanning the `libs` folders recursively + /// Whether to autodetect remappings. pub auto_detect_remappings: bool, - /// library addresses to link + /// Library addresses to link. pub libraries: Vec, - /// whether to enable cache + /// Whether to enable the build cache. pub cache: bool, - /// whether to dynamically link tests - pub dynamic_test_linking: bool, - /// where the cache is stored if enabled + /// The path to the cache store. pub cache_path: PathBuf, - /// where the gas snapshots are stored + /// Whether to dynamically link tests. + pub dynamic_test_linking: bool, + /// Where the gas snapshots are stored. pub snapshots: PathBuf, - /// whether to check for differences against previously stored gas snapshots + /// Whether to check for differences against previously stored gas snapshots. pub gas_snapshot_check: bool, - /// whether to emit gas snapshots to disk + /// Whether to emit gas snapshots to disk. pub gas_snapshot_emit: bool, - /// where the broadcast logs are stored + /// The path to store broadcast logs at. pub broadcast: PathBuf, - /// additional solc allow paths for `--allow-paths` + /// Additional paths passed to `solc --allow-paths`. pub allow_paths: Vec, - /// additional solc include paths for `--include-path` + /// Additional paths passed to `solc --include-path`. pub include_paths: Vec, - /// glob patterns to skip + /// Glob patterns for file paths to skip when building and executing contracts. pub skip: Vec, - /// whether to force a `project.clean()` + /// Whether to forcefully clean all project artifacts before running commands. pub force: bool, - /// evm version to use + /// The EVM version to use when building contracts. #[serde(with = "from_str_lowercase")] pub evm_version: EvmVersion, - /// list of contracts to report gas of + /// The runtime hardfork to use when executing tests and scripts. + pub hardfork: Option, + /// List of contracts to generate gas reports for. pub gas_reports: Vec, - /// list of contracts to ignore for gas reports + /// List of contracts to ignore for gas reports. pub gas_reports_ignore: Vec, /// Whether to include gas reports for tests. pub gas_reports_include_tests: bool, @@ -270,6 +287,13 @@ pub struct Config { pub verbosity: u8, /// url of the rpc server that should be used for any rpc calls pub eth_rpc_url: Option, + /// Whether to accept invalid certificates for the rpc server. + pub eth_rpc_accept_invalid_certs: bool, + /// Whether to disable automatic proxy detection for the rpc server. + /// + /// This can help in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) + /// where system proxy detection via SCDynamicStore causes crashes. + pub eth_rpc_no_proxy: bool, /// JWT secret that should be used for any rpc calls pub eth_rpc_jwt: Option, /// Timeout that should be used for any rpc calls @@ -283,19 +307,24 @@ pub struct Config { /// You can also the ETH_RPC_HEADERS env variable like so: /// `ETH_RPC_HEADERS="x-custom-header:value x-another-header:another-value"` pub eth_rpc_headers: Option>, + /// Print the equivalent curl command instead of making the RPC request. + pub eth_rpc_curl: bool, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, - /// etherscan API version - pub etherscan_api_version: Option, /// Multiple etherscan api configs and their aliases #[serde(default, skip_serializing_if = "EtherscanConfigs::is_empty")] pub etherscan: EtherscanConfigs, - /// list of solidity error codes to always silence in the compiler output + /// List of solidity error codes to always silence in the compiler output. pub ignored_error_codes: Vec, - /// list of file paths to ignore + /// List of (path prefix, solidity error codes) to silence in the compiler output. + pub ignored_error_codes_from: Vec<(PathBuf, Vec)>, + /// List of file paths to ignore. #[serde(rename = "ignored_warnings_from")] pub ignored_file_paths: Vec, - /// When true, compiler warnings are treated as errors + /// Diagnostic level (minimum) at which the process should finish with a non-zero exit. + pub deny: DenyLevel, + /// DEPRECATED: use `deny` instead. + #[serde(default, skip_serializing)] pub deny_warnings: bool, /// Only run test functions matching the specified regex pattern. #[serde(rename = "match_test")] @@ -330,6 +359,8 @@ pub struct Config { pub invariant: InvariantConfig, /// Whether to allow ffi cheatcodes in test pub ffi: bool, + /// Whether to show `console.log` outputs in realtime during script/test execution + pub live_logs: bool, /// Whether to allow `expectRevert` for internal functions. pub allow_internal_expect_revert: bool, /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. @@ -343,7 +374,11 @@ pub struct Config { /// the initial balance of each deployed test contract pub initial_balance: U256, /// the block.number value during EVM execution - pub block_number: u64, + #[serde( + deserialize_with = "crate::deserialize_u64_to_u256", + serialize_with = "crate::serialize_u64_or_u256" + )] + pub block_number: U256, /// pins the block number for the state fork pub fork_block_number: Option, /// The chain name or EIP-155 chain ID. @@ -363,7 +398,11 @@ pub struct Config { /// The `block.coinbase` value during EVM execution. pub block_coinbase: Address, /// The `block.timestamp` value during EVM execution. - pub block_timestamp: u64, + #[serde( + deserialize_with = "crate::deserialize_u64_to_u256", + serialize_with = "crate::serialize_u64_or_u256" + )] + pub block_timestamp: U256, /// The `block.difficulty` value during EVM execution. pub block_difficulty: u64, /// Before merge the `block.max_hash`, after merge it is `block.prevrandao`. @@ -468,9 +507,12 @@ pub struct Config { /// Useful for more correct gas accounting and EVM behavior in general. pub isolate: bool, - /// Whether to disable the block gas limit. + /// Whether to disable the block gas limit checks. pub disable_block_gas_limit: bool, + /// Whether to enable the tx gas limit checks as imposed by Osaka (EIP-7825). + pub enable_tx_gas_limit: bool, + /// Address labels pub labels: AddressHashMap, @@ -505,9 +547,9 @@ pub struct Config { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub extra_args: Vec, - /// Whether to enable Odyssey features. - #[serde(alias = "alphanet")] - pub odyssey: bool, + /// Networks with enabled features. + #[serde(flatten)] + pub networks: NetworkConfigs, /// Timeout for transactions in seconds. pub transaction_timeout: u64, @@ -540,13 +582,109 @@ pub struct Config { pub _non_exhaustive: (), } +/// Diagnostic level (minimum) at which the process should finish with a non-zero exit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Default, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum DenyLevel { + /// Always exit with zero code. + #[default] + Never, + /// Exit with a non-zero code if any warnings are found. + Warnings, + /// Exit with a non-zero code if any notes or warnings are found. + Notes, +} + +// Custom deserialization to make `DenyLevel` parsing case-insensitive and backwards compatible with +// booleans. +impl<'de> Deserialize<'de> for DenyLevel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DenyLevelVisitor; + + impl<'de> de::Visitor<'de> for DenyLevelVisitor { + type Value = DenyLevel; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("one of the following strings: `never`, `warnings`, `notes`") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(DenyLevel::from(value)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + DenyLevel::from_str(value).map_err(de::Error::custom) + } + } + + deserializer.deserialize_any(DenyLevelVisitor) + } +} + +impl FromStr for DenyLevel { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "warnings" | "warning" | "w" => Ok(Self::Warnings), + "notes" | "note" | "n" => Ok(Self::Notes), + "never" | "false" | "f" => Ok(Self::Never), + _ => Err(format!( + "unknown variant: found `{s}`, expected one of `never`, `warnings`, `notes`" + )), + } + } +} + +impl From for DenyLevel { + fn from(deny: bool) -> Self { + if deny { Self::Warnings } else { Self::Never } + } +} + +impl DenyLevel { + /// Returns `true` if the deny level includes warnings. + pub const fn warnings(&self) -> bool { + match self { + Self::Never => false, + Self::Warnings | Self::Notes => true, + } + } + + /// Returns `true` if the deny level includes notes. + pub const fn notes(&self) -> bool { + match self { + Self::Never | Self::Warnings => false, + Self::Notes => true, + } + } + + /// Returns `true` if the deny level is set to never (only errors). + pub const fn never(&self) -> bool { + match self { + Self::Never => true, + Self::Warnings | Self::Notes => false, + } + } +} + /// Mapping of fallback standalone sections. See [`FallbackProfileProvider`]. pub const STANDALONE_FALLBACK_SECTIONS: &[(&str, &str)] = &[("invariant", "fuzz")]; /// Deprecated keys and their replacements. /// /// See [Warning::DeprecatedKey] -pub const DEPRECATIONS: &[(&str, &str)] = &[("cancun", "evm_version = Cancun")]; +pub const DEPRECATIONS: &[(&str, &str)] = + &[("cancun", "evm_version = Cancun"), ("deny_warnings", "deny = warnings")]; impl Config { /// The default profile: "default" @@ -558,6 +696,9 @@ impl Config { /// TOML section for profiles pub const PROFILE_SECTION: &'static str = "profile"; + /// External config sections, ignored from warnings. + pub const EXTERNAL_SECTION: &'static str = "external"; + /// Standalone sections in the config which get integrated into the selected profile pub const STANDALONE_SECTIONS: &'static [&'static str] = &[ "rpc_endpoints", @@ -574,6 +715,12 @@ impl Config { "bind_json", ]; + pub(crate) fn is_standalone_section>(section: &T) -> bool { + section == Self::PROFILE_SECTION + || section == Self::EXTERNAL_SECTION + || Self::STANDALONE_SECTIONS.iter().any(|s| section == *s) + } + /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; @@ -614,6 +761,18 @@ impl Config { Self::from_provider(Self::figment_with_root(root.as_ref())) } + /// Loads the `Config` from the given root directory, allowing profile fallback. + /// + /// Unlike [`load_with_root`](Self::load_with_root), if the selected profile (via + /// `FOUNDRY_PROFILE`) does not exist in the config, this falls back to the default profile + /// instead of returning an error. This is useful for loading nested lib/dependency configs + /// that may not define all profiles the main project uses. + #[track_caller] + pub fn load_with_root_and_fallback(root: impl AsRef) -> Result { + let figment = Self::figment_with_root(root.as_ref()); + Self::from_figment_fallback(Figment::from(figment)) + } + /// Attempts to extract a `Config` from `provider`, returning the result. /// /// # Example @@ -634,6 +793,18 @@ impl Config { Self::from_figment(Figment::from(provider)) } + /// Applies an inline provider on top of the current config without reloading external + /// providers such as `foundry.toml`, env vars, or remappings. + pub fn merge_inline_provider(&self, provider: T) -> Result { + let mut config = + self.to_figment(FigmentProviders::None).merge(provider).extract::()?; + config.profile = self.profile.clone(); + config.profiles = self.profiles.clone(); + config.normalize_hardfork_settings()?; + + Ok(config) + } + #[doc(hidden)] #[deprecated(note = "use `Config::from_provider` instead")] pub fn try_from(provider: T) -> Result { @@ -641,31 +812,76 @@ impl Config { } fn from_figment(figment: Figment) -> Result { + Self::from_figment_inner(figment, true) + } + + /// Same as `from_figment` but allows unknown profiles, falling back to default profile. + /// Used when loading nested lib configs that may not define all profiles. + fn from_figment_fallback(figment: Figment) -> Result { + Self::from_figment_inner(figment, false) + } + + fn from_figment_inner( + figment: Figment, + strict_profile: bool, + ) -> Result { let mut config = figment.extract::().map_err(ExtractConfigError::new)?; - config.profile = figment.profile().clone(); + let selected_profile = figment.profile().clone(); // The `"profile"` profile contains all the profiles as keys. - let mut add_profile = |profile: &Profile| { - if !config.profiles.contains(profile) { - config.profiles.push(profile.clone()); + fn add_profile(profiles: &mut Vec, profile: &Profile) { + if !profiles.contains(profile) { + profiles.push(profile.clone()); } - }; + } let figment = figment.select(Self::PROFILE_SECTION); - if let Ok(data) = figment.data() { - if let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) { - for profile in profiles.keys() { - add_profile(&Profile::new(profile)); - } + if let Ok(data) = figment.data() + && let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) + { + for profile in profiles.keys() { + add_profile(&mut config.profiles, &Profile::new(profile)); + } + } + add_profile(&mut config.profiles, &Self::DEFAULT_PROFILE); + + // Check if the selected profile exists. + if config.profiles.contains(&selected_profile) { + config.profile = selected_profile; + } else { + // Fall back to the default profile. In strict mode (top-level loads), emit a warning + // so users are informed when an unknown profile (e.g. via `FOUNDRY_PROFILE`) is + // selected; the silent fallback is reserved for nested lib configs. + if strict_profile { + config + .warnings + .push(Warning::UnknownProfile { profile: selected_profile.to_string() }); } + config.profile = Self::DEFAULT_PROFILE; } - add_profile(&Self::DEFAULT_PROFILE); - add_profile(&config.profile); config.normalize_optimizer_settings(); + config.normalize_hardfork_settings().map_err(ExtractConfigError::new)?; + + // Validate optimizer_runs does not exceed u32::MAX (Solidity compiler limit) + if let Some(runs) = config.optimizer_runs + && runs > u32::MAX as usize + { + return Err(ExtractConfigError::new(Error::from(format!( + "`optimizer_runs` value {} exceeds maximum allowed value of {}", + runs, + u32::MAX + )))); + } Ok(config) } + fn normalize_hardfork_settings(&mut self) -> Result<(), Error> { + let Some(hardfork) = self.hardfork else { return Ok(()) }; + self.networks = self.networks.normalize_for_hardfork(hardfork).map_err(Error::from)?; + Ok(()) + } + /// Returns the populated [Figment] using the requested [FigmentProviders] preset. /// /// This will merge various providers, such as env,toml,remappings into the figment if @@ -685,14 +901,14 @@ impl Config { if let Some(global_toml) = Self::foundry_dir_toml().filter(|p| p.exists()) { figment = Self::merge_toml_provider( figment, - TomlFileProvider::new(None, global_toml).cached(), + TomlFileProvider::new(None, global_toml), profile.clone(), ); } // merge local foundry.toml file figment = Self::merge_toml_provider( figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), root.join(Self::FILE_NAME)).cached(), + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), root.join(Self::FILE_NAME)), profile.clone(), ); @@ -794,6 +1010,7 @@ impl Config { self.broadcast = p(&root, &self.broadcast); self.cache_path = p(&root, &self.cache_path); self.snapshots = p(&root, &self.snapshots); + self.test_failures_file = p(&root, &self.test_failures_file); if let Some(build_info_path) = self.build_info_path { self.build_info_path = Some(p(&root, &build_info_path)); @@ -830,7 +1047,7 @@ impl Config { /// Normalizes optimizer settings. /// See - pub fn normalized_optimizer_settings(mut self) -> Self { + pub const fn normalized_optimizer_settings(mut self) -> Self { self.normalize_optimizer_settings(); self } @@ -844,7 +1061,7 @@ impl Config { /// - with default settings, optimizer is set to false and optimizer runs to 200 /// - if optimizer is set and optimizer runs not specified, then optimizer runs is set to 200 /// - enable optimizer if not explicitly set and optimizer runs set to a value greater than 0 - pub fn normalize_optimizer_settings(&mut self) { + pub const fn normalize_optimizer_settings(&mut self) { match (self.optimizer, self.optimizer_runs) { // Default: set the optimizer to false and optimizer runs to 200. (None, None) => { @@ -861,10 +1078,10 @@ impl Config { /// Returns the normalized [EvmVersion] for the current solc version, or the configured one. pub fn get_normalized_evm_version(&self) -> EvmVersion { - if let Some(version) = self.solc_version() { - if let Some(evm_version) = self.evm_version.normalize_version_solc(&version) { - return evm_version; - } + if let Some(version) = self.solc_version() + && let Some(evm_version) = self.evm_version.normalize_version_solc(&version) + { + return evm_version; } self.evm_version } @@ -888,6 +1105,7 @@ impl Config { /// Cleans up any duplicate `Remapping` and sorts them /// /// On windows this will convert any `\` in the remapping path into a `/` + #[allow(clippy::missing_const_for_fn)] pub fn sanitize_remappings(&mut self) { #[cfg(target_os = "windows")] { @@ -934,6 +1152,20 @@ impl Config { self.create_project(false, true) } + /// A cached, in-memory project that does not request any artifacts. + /// + /// Use this when you just want the source graph or the Solar compiler context. + pub fn solar_project(&self) -> Result, SolcError> { + let ui_testing = std::env::var_os("FOUNDRY_LINT_UI_TESTING").is_some(); + let mut project = self.create_project(self.cache && !ui_testing, false)?; + project.update_output_selection(|selection| { + // We have to request something to populate `contracts` in the output and thus + // artifacts. + *selection = OutputSelection::common_output_selection(["abi".into()]); + }); + Ok(project) + } + /// Builds mapping with additional settings profiles. fn additional_settings( &self, @@ -957,12 +1189,13 @@ impl Config { paths: &ProjectPathsConfig, ) -> Result>, SolcError> { - let mut map = BTreeMap::new(); + let mut map: BTreeMap> = + BTreeMap::new(); if self.compilation_restrictions.is_empty() { return Ok(BTreeMap::new()); } - let graph = Graph::::resolve(paths)?; + let graph = Graph::::resolve(paths)?; let (sources, _) = graph.into_sources(); for res in &self.compilation_restrictions { @@ -977,9 +1210,7 @@ impl Config { }) { let res: RestrictionsWithVersion<_> = res.clone().try_into().map_err(SolcError::msg)?; - if !map.contains_key(source) { - map.insert(source.clone(), res); - } else { + if map.contains_key(source) { let value = map.remove(source.as_path()).unwrap(); if let Some(merged) = value.clone().merge(res) { map.insert(source.clone(), merged); @@ -994,6 +1225,8 @@ impl Config { ); map.insert(source.clone(), value); } + } else { + map.insert(source.clone(), res); } } } @@ -1007,6 +1240,10 @@ impl Config { pub fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { let settings = self.compiler_settings()?; let paths = self.project_paths(); + + // Strip "./" prefix for consistent path matching + let parse_path = |path: &PathBuf| path.strip_prefix("./").unwrap_or(path).to_path_buf(); + let mut builder = Project::builder() .artifacts(self.configured_artifacts_handler()) .additional_settings(self.additional_settings(&settings)) @@ -1014,8 +1251,11 @@ impl Config { .settings(settings) .paths(paths) .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) - .ignore_paths(self.ignored_file_paths.clone()) - .set_compiler_severity_filter(if self.deny_warnings { + .ignore_error_codes_from(self.ignored_error_codes_from.iter().map(|(path, codes)| { + (parse_path(path), codes.iter().copied().map(Into::into).collect()) + })) + .ignore_paths(self.ignored_file_paths.iter().map(parse_path).collect()) + .set_compiler_severity_filter(if self.deny.warnings() { Severity::Warning } else { Severity::Error @@ -1033,35 +1273,99 @@ impl Config { let project = builder.build(self.compiler()?)?; if self.force { - self.cleanup(&project)?; + // Warnings are intentionally dropped here because `sh_warn!` is a circular + // dependency. Callers that need warnings should call `cleanup()` directly. + let _ = self.cleanup(&project); } Ok(project) } + /// Disables optimizations and enables viaIR with minimum optimization if `ir_minimum` is true. + pub fn disable_optimizations(&self, project: &mut Project, ir_minimum: bool) { + if ir_minimum { + // Enable viaIR with minimum optimization: https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + // And also in new releases of Solidity: https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 + project.settings.solc.settings = std::mem::take(&mut project.settings.solc.settings) + .with_via_ir_minimum_optimization(); + + // Sanitize settings for solc 0.8.4 if version cannot be detected: https://github.com/foundry-rs/foundry/issues/9322 + // But keep the EVM version: https://github.com/ethereum/solidity/issues/15775 + let evm_version = project.settings.solc.evm_version; + let version = self.solc_version().unwrap_or_else(|| Version::new(0, 8, 4)); + project.settings.solc.settings.sanitize(&version, SolcLanguage::Solidity); + project.settings.solc.evm_version = evm_version; + } else { + project.settings.solc.optimizer.disable(); + project.settings.solc.optimizer.runs = None; + project.settings.solc.optimizer.details = None; + project.settings.solc.via_ir = None; + } + } + /// Cleans the project. + /// + /// Returns a list of warning messages for any non-fatal cleanup failures. Cleanup is + /// best-effort: all steps are attempted even if some fail. pub fn cleanup>( &self, project: &Project, - ) -> Result<(), SolcError> { - project.cleanup()?; + ) -> Result, SolcError> { + let mut warnings = Vec::new(); + + if let Err(err) = project.cleanup() { + warnings.push(format!("failed to clean project artifacts: {err}")); + } // Remove last test run failures file. - let _ = fs::remove_file(&self.test_failures_file); + let test_failures_path = if self.test_failures_file.is_absolute() { + self.test_failures_file.clone() + } else { + project.root().join(&self.test_failures_file) + }; + let root_canon = dunce::canonicalize(project.root()).unwrap_or_else(|_| project.root().to_path_buf()); + let validated_test_failures_path = test_failures_path + .parent() + .and_then(|parent| dunce::canonicalize(parent).ok().map(|p| (p, test_failures_path.file_name()))) + .and_then(|(parent, file_name)| file_name.map(|name| parent.join(name))); + + match validated_test_failures_path { + Some(path) if path.starts_with(&root_canon) => { + if let Err(err) = fs::remove_file(&path) && err.kind() != io::ErrorKind::NotFound { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + path.display() + )); + } + } + _ => { + warnings.push(format!( + "skipped removing test failures file outside project root: {}", + test_failures_path.display() + )); + } + } // Remove fuzz and invariant cache directories. - let remove_test_dir = |test_dir: &Option| { + let mut remove_test_dir = |test_dir: &Option| { if let Some(test_dir) = test_dir { let path = project.root().join(test_dir); - if path.exists() { - let _ = fs::remove_dir_all(&path); + if let Err(err) = fs::remove_dir_all(&path) + && err.kind() != io::ErrorKind::NotFound + { + warnings.push(format!( + "failed to remove test cache directory {}: {err}", + path.display() + )); } } }; remove_test_dir(&self.fuzz.failure_persist_dir); + remove_test_dir(&self.fuzz.corpus.corpus_dir); + remove_test_dir(&self.invariant.corpus.corpus_dir); remove_test_dir(&self.invariant.failure_persist_dir); - Ok(()) + Ok(warnings) } /// Ensures that the configured version is installed if explicitly set @@ -1101,17 +1405,16 @@ impl Config { Ok(None) } - /// Returns the [SpecId] derived from the configured [EvmVersion] - #[inline] - pub fn evm_spec_id(&self) -> SpecId { - evm_spec_id(self.evm_version, self.odyssey) + /// Returns the Spec derived from the configured [EvmVersion] + pub fn evm_spec_id(&self) -> SPEC { + self.hardfork.map(Into::into).unwrap_or_else(|| evm_spec_id(self.evm_version)) } /// Returns whether the compiler version should be auto-detected /// /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of /// `auto_detect_solc` - pub fn is_auto_detect(&self) -> bool { + pub const fn is_auto_detect(&self) -> bool { if self.solc.is_some() { return false; } @@ -1120,9 +1423,9 @@ impl Config { /// Whether caching should be enabled for the given chain id pub fn enable_caching(&self, endpoint: &str, chain_id: impl Into) -> bool { - !self.no_storage_caching && - self.rpc_storage_caching.enable_for_chain_id(chain_id.into()) && - self.rpc_storage_caching.enable_for_endpoint(endpoint) + !self.no_storage_caching + && self.rpc_storage_caching.enable_for_chain_id(chain_id.into()) + && self.rpc_storage_caching.enable_for_endpoint(endpoint) } /// Returns the `ProjectPathsConfig` sub set of the config. @@ -1237,7 +1540,7 @@ impl Config { /// # } /// ``` pub fn get_rpc_url(&self) -> Option, UnresolvedEnvVarError>> { - let maybe_alias = self.eth_rpc_url.as_ref().or(self.etherscan_api_key.as_ref())?; + let maybe_alias = self.eth_rpc_url.as_deref()?; if let Some(alias) = self.get_rpc_url_with_alias(maybe_alias) { Some(alias) } else { @@ -1278,8 +1581,42 @@ impl Config { return Some(endpoint.url().map(Cow::Owned)); } - if let Ok(Some(endpoint)) = mesc::get_endpoint_by_query(maybe_alias, Some("foundry")) { - return Some(Ok(Cow::Owned(endpoint.url))); + if let Some(mesc_url) = self.get_rpc_url_from_mesc(maybe_alias) { + return Some(Ok(Cow::Owned(mesc_url))); + } + + if let Some(builtin) = crate::endpoints::builtin_rpc_url(maybe_alias) { + return Some(Ok(Cow::Borrowed(builtin))); + } + + None + } + + /// Attempts to resolve the URL for the given alias from [`mesc`](https://github.com/paradigmxyz/mesc) + pub fn get_rpc_url_from_mesc(&self, maybe_alias: &str) -> Option { + // Note: mesc requires a MESC_PATH in the env, which the user can configure and is expected + // to be part of the shell profile, default is ~/mesc.json + let mesc_config = mesc::load::load_config_data() + .inspect_err(|err| debug!(%err, "failed to load mesc config")) + .ok()?; + + if let Ok(Some(endpoint)) = + mesc::query::get_endpoint_by_query(&mesc_config, maybe_alias, Some("foundry")) + { + return Some(endpoint.url); + } + + if maybe_alias.chars().all(|c| c.is_numeric()) { + // try to lookup the mesc network by chain id if alias is numeric + // This only succeeds if the chain id has a default: + // "network_defaults": { + // "50104": "sophon_50104" + // } + if let Ok(Some(endpoint)) = + mesc::query::get_endpoint_by_network(&mesc_config, maybe_alias, Some("foundry")) + { + return Some(endpoint.url); + } } None @@ -1300,11 +1637,7 @@ impl Config { &'a self, fallback: impl Into>, ) -> Result, UnresolvedEnvVarError> { - if let Some(url) = self.get_rpc_url() { - url - } else { - Ok(fallback.into()) - } + if let Some(url) = self.get_rpc_url() { url } else { Ok(fallback.into()) } } /// Returns the configured rpc or `"http://localhost:8545"` if no `eth_rpc_url` is set @@ -1357,23 +1690,17 @@ impl Config { &self, chain: Option, ) -> Result, EtherscanConfigError> { - let default_api_version = self.etherscan_api_version.unwrap_or_default(); - - if let Some(maybe_alias) = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref()) { - if self.etherscan.contains_key(maybe_alias) { - return self - .etherscan - .clone() - .resolved(default_api_version) - .remove(maybe_alias) - .transpose(); - } + if let Some(maybe_alias) = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref()) + && self.etherscan.contains_key(maybe_alias) + { + return self.etherscan.clone().resolved().remove(maybe_alias).transpose(); } // try to find by comparing chain IDs after resolving - if let Some(res) = chain.or(self.chain).and_then(|chain| { - self.etherscan.clone().resolved(default_api_version).find_chain(chain) - }) { + if let Some(res) = chain + .or(self.chain) + .and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) + { match (res, self.etherscan_api_key.as_ref()) { (Ok(mut config), Some(key)) => { // we update the key, because if an etherscan_api_key is set, it should take @@ -1394,10 +1721,8 @@ impl Config { return Ok(ResolvedEtherscanConfig::create( key, chain.or(self.chain).unwrap_or_default(), - default_api_version, )); } - Ok(None) } @@ -1406,19 +1731,19 @@ impl Config { /// Optionally updates the config with the given `chain`. /// /// See also [Self::get_etherscan_config_with_chain] + #[expect(clippy::disallowed_macros)] pub fn get_etherscan_api_key(&self, chain: Option) -> Option { - self.get_etherscan_config_with_chain(chain).ok().flatten().map(|c| c.key) - } - - /// Helper function to get the API version. - /// - /// See also [Self::get_etherscan_config_with_chain] - pub fn get_etherscan_api_version(&self, chain: Option) -> EtherscanApiVersion { self.get_etherscan_config_with_chain(chain) + .map_err(|e| { + // `sh_warn!` is a circular dependency, preventing us from using it here. + eprintln!( + "{}: failed getting etherscan config: {e}", + yansi::Paint::yellow("Warning"), + ); + }) .ok() .flatten() - .map(|c| c.api_version) - .unwrap_or_default() + .map(|c| c.key) } /// Returns the remapping for the project's _src_ directory @@ -1433,20 +1758,12 @@ impl Config { /// Returns the remapping for the project's _test_ directory, but only if it exists pub fn get_test_dir_remapping(&self) -> Option { - if self.root.join(&self.test).exists() { - get_dir_remapping(&self.test) - } else { - None - } + if self.root.join(&self.test).exists() { get_dir_remapping(&self.test) } else { None } } /// Returns the remapping for the project's _script_ directory, but only if it exists pub fn get_script_dir_remapping(&self) -> Option { - if self.root.join(&self.script).exists() { - get_dir_remapping(&self.script) - } else { - None - } + if self.root.join(&self.script).exists() { get_dir_remapping(&self.script) } else { None } } /// Returns the `Optimizer` based on the configured settings @@ -1478,7 +1795,7 @@ impl Config { extra_output.push(ContractOutputSelection::Metadata); } - ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().cloned()) + ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().copied()) } /// Parses all libraries in the form of @@ -1502,10 +1819,10 @@ impl Config { // This might be too much here, so only enable assertion checks. // If users wish to enable all options they need to do so explicitly. let mut model_checker = self.model_checker.clone(); - if let Some(model_checker_settings) = &mut model_checker { - if model_checker_settings.targets.is_none() { - model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); - } + if let Some(model_checker_settings) = &mut model_checker + && model_checker_settings.targets.is_none() + { + model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); } let mut settings = Settings { @@ -1530,7 +1847,6 @@ impl Config { remappings: Vec::new(), // Set with `with_extra_output` below. output_selection: Default::default(), - eof_version: None, } .with_extra_output(self.configured_artifacts_handler().output_selection()); @@ -1634,11 +1950,6 @@ impl Config { src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), libs: paths.libraries.into_iter().map(|lib| lib.file_name().unwrap().into()).collect(), - remappings: paths - .remappings - .into_iter() - .map(|r| RelativeRemapping::new(r, root)) - .collect(), fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), ..Self::default() } @@ -1654,16 +1965,6 @@ impl Config { } } - /// Returns the default config that uses dapptools style paths - pub fn dapptools() -> Self { - Self { - chain: Some(Chain::from_id(99)), - block_timestamp: 0, - block_number: 0, - ..Self::default() - } - } - /// Extracts a basic subset of the config, used for initialisations. /// /// # Example @@ -1679,6 +1980,7 @@ impl Config { out: self.out, libs: self.libs, remappings: self.remappings, + network: self.networks.active_network_name().map(String::from), } } @@ -1896,63 +2198,108 @@ impl Config { } /// Clears the foundry cache. - pub fn clean_foundry_cache() -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_cache() -> eyre::Result> { if let Some(cache_dir) = Self::foundry_cache_dir() { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry cache for `chain`. - pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_chain_cache_dir(chain) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache for chain {chain} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_chain_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry cache for `chain` and `block`. - pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_block_cache_dir(chain, block) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry cache for chain {chain} block {block} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_block_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry etherscan cache. - pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_etherscan_cache() -> eyre::Result> { if let Some(cache_dir) = Self::foundry_etherscan_cache_dir() { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry etherscan cache at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_etherscan_cache_dir"); } - Ok(()) + Ok(vec![]) } /// Clears the foundry etherscan cache for `chain`. - pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { + /// + /// Returns warnings for any non-fatal deletion failures. + pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result> { if let Some(cache_dir) = Self::foundry_etherscan_chain_cache_dir(chain) { let path = cache_dir.as_path(); - let _ = fs::remove_dir_all(path); + if let Err(err) = fs::remove_dir_all(path) + && err.kind() != io::ErrorKind::NotFound + { + return Ok(vec![format!( + "failed to remove foundry etherscan cache for chain {chain} at {}: {err}", + path.display() + )]); + } } else { eyre::bail!("failed to get foundry_etherscan_cache_dir for chain: {}", chain); } - Ok(()) + Ok(vec![]) } /// List the data in the foundry cache. @@ -1964,9 +2311,8 @@ impl Config { } if let Ok(entries) = cache_dir.as_path().read_dir() { for entry in entries.flatten().filter(|x| x.path().is_dir()) { - match Chain::from_str(&entry.file_name().to_string_lossy()) { - Ok(chain) => cache.chains.push(Self::list_foundry_chain_cache(chain)?), - Err(_) => continue, + if let Ok(chain) = Chain::from_str(&entry.file_name().to_string_lossy()) { + cache.chains.push(Self::list_foundry_chain_cache(chain)?); } } Ok(cache) @@ -2011,8 +2357,8 @@ impl Config { let file_name = block.file_name(); let filepath = if file_type.is_dir() { block.path().join("storage.json") - } else if file_type.is_file() && - file_name.to_string_lossy().chars().all(char::is_numeric) + } else if file_type.is_file() + && file_name.to_string_lossy().chars().all(char::is_numeric) { block.path() } else { @@ -2095,20 +2441,25 @@ impl Config { /// /// See also fn normalize_defaults(&self, mut figment: Figment) -> Figment { - // TODO: add a warning if evm_version is provided but incompatible if figment.contains("evm_version") { return figment; } // Normalize `evm_version` based on the provided solc version. - if let Ok(solc) = figment.extract_inner::("solc") { - if let Some(version) = solc + if let Ok(solc) = figment.extract_inner::("solc") + && let Some(version) = solc .try_version() .ok() .and_then(|version| self.evm_version.normalize_version_solc(&version)) - { - figment = figment.merge(("evm_version", version)); - } + { + figment = figment.merge(("evm_version", version)); + } + + // Normalize `deny` based on the provided `deny_warnings` value. + if figment.extract_inner::("deny_warnings").unwrap_or(false) + && figment.extract_inner("deny") == Ok(DenyLevel::Never) + { + figment = figment.merge(("deny", DenyLevel::Warnings)); } figment @@ -2300,6 +2651,7 @@ impl Default for Config { fs_permissions: FsPermissions::new([PathPermission::read("out")]), isolate: cfg!(feature = "isolate-by-default"), root: root_default(), + extends: None, src: "src".into(), test: "test".into(), script: "script".into(), @@ -2315,7 +2667,8 @@ impl Default for Config { allow_paths: vec![], include_paths: vec![], force: false, - evm_version: EvmVersion::Cancun, + evm_version: EvmVersion::Osaka, + hardfork: None, gas_reports: vec!["*".to_string()], gas_reports_ignore: vec![], gas_reports_include_tests: false, @@ -2345,12 +2698,13 @@ impl Default for Config { invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, ffi: false, + live_logs: false, allow_internal_expect_revert: false, prompt_timeout: 120, sender: Self::DEFAULT_SENDER, tx_origin: Self::DEFAULT_SENDER, initial_balance: U256::from((1u128 << 96) - 1), - block_number: 1, + block_number: U256::from(1), fork_block_number: None, chain: None, gas_limit: (1u64 << 30).into(), // ~1B @@ -2358,18 +2712,21 @@ impl Default for Config { gas_price: None, block_base_fee_per_gas: 0, block_coinbase: Address::ZERO, - block_timestamp: 1, + block_timestamp: U256::from(1), block_difficulty: 0, block_prevrandao: Default::default(), block_gas_limit: None, disable_block_gas_limit: false, + enable_tx_gas_limit: false, memory_limit: 1 << 27, // 2**27 = 128MiB = 134_217_728 bytes eth_rpc_url: None, + eth_rpc_accept_invalid_certs: false, + eth_rpc_no_proxy: false, eth_rpc_jwt: None, eth_rpc_timeout: None, eth_rpc_headers: None, + eth_rpc_curl: false, etherscan_api_key: None, - etherscan_api_version: None, verbosity: 0, remappings: vec![], auto_detect_remappings: true, @@ -2379,8 +2736,12 @@ impl Default for Config { SolidityErrorCode::ContractExceeds24576Bytes, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, SolidityErrorCode::TransientStorageUsed, + SolidityErrorCode::TransferDeprecated, + SolidityErrorCode::NatspecMemorySafeAssemblyDeprecated, ], + ignored_error_codes_from: vec![], ignored_file_paths: vec![], + deny: DenyLevel::Never, deny_warnings: false, via_ir: false, ast: false, @@ -2411,7 +2772,7 @@ impl Default for Config { legacy_assertions: false, warnings: vec![], extra_args: vec![], - odyssey: false, + networks: Default::default(), transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), @@ -2483,11 +2844,7 @@ impl SolcReq { impl> From for SolcReq { fn from(s: T) -> Self { let s = s.as_ref(); - if let Ok(v) = Version::from_str(s) { - Self::Version(v) - } else { - Self::Local(s.into()) - } + if let Ok(v) = Version::from_str(s) { Self::Version(v) } else { Self::Local(s.into()) } } } @@ -2516,6 +2873,9 @@ pub struct BasicConfig { /// `Remappings` to use for this repo #[serde(default, skip_serializing_if = "Vec::is_empty")] pub remappings: Vec, + /// The active non-Ethereum network (e.g. `"tempo"`). + #[serde(skip)] + pub network: Option, } impl BasicConfig { @@ -2523,13 +2883,35 @@ impl BasicConfig { /// /// This serializes to a table with the name of the profile pub fn to_string_pretty(&self) -> Result { - let s = toml::to_string_pretty(self)?; + let mut profile_body = toml::Value::try_from(self)?; + if let Some(ref network) = self.network + && let toml::Value::Table(ref mut table) = profile_body + { + table.insert("network".to_string(), toml::Value::String(network.clone())); + } + + let mut profile_section = toml::value::Table::new(); + profile_section.insert(self.profile.to_string(), profile_body); + + let mut document = toml::value::Table::new(); + document.insert("profile".to_string(), toml::Value::Table(profile_section)); + + if self.network.as_deref() == Some("tempo") { + let mut endpoints = toml::value::Table::new(); + endpoints.insert( + "tempo".to_string(), + toml::Value::String("https://rpc.tempo.xyz/".to_string()), + ); + endpoints.insert( + "moderato".to_string(), + toml::Value::String("https://rpc.moderato.tempo.xyz/".to_string()), + ); + document.insert("rpc_endpoints".to_string(), toml::Value::Table(endpoints)); + } + + let body = toml::to_string_pretty(&toml::Value::Table(document))?; Ok(format!( - "\ -[profile.{}] -{s} -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n", - self.profile + "{body}\n# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options\n" )) } } @@ -2572,17 +2954,19 @@ mod tests { cache::{CachedChains, CachedEndpoints}, endpoints::RpcEndpointType, etherscan::ResolvedEtherscanConfigs, + fmt::IndentStyle, }; + use NamedChain::Moonbeam; use endpoints::{RpcAuth, RpcEndpointConfig}; use figment::error::Kind::InvalidType; use foundry_compilers::artifacts::{ - vyper::VyperOptimizationMode, ModelCheckerEngine, YulDetails, + ModelCheckerEngine, YulDetails, vyper::VyperOptimizationMode, }; + use foundry_evm_hardforks::TempoHardfork; use similar_asserts::assert_eq; use soldeer_core::remappings::RemappingsLocation; use std::{fs::File, io::Write}; use tempfile::tempdir; - use NamedChain::Moonbeam; // Helper function to clear `__warnings` in config, since it will be populated during loading // from file, causing testing problem when comparing to those created from `default()`, etc. @@ -3015,11 +3399,15 @@ mod tests { )?; let config = Config::load().unwrap(); - assert!(config - .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) - .is_err()); + assert!( + config + .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) + .is_err() + ); - std::env::set_var(env_key, env_value); + unsafe { + std::env::set_var(env_key, env_value); + } assert_eq!( config @@ -3042,7 +3430,9 @@ mod tests { "via etherscan_api_key" ); - std::env::remove_var(env_key); + unsafe { + std::env::remove_var(env_key); + } Ok(()) }); } @@ -3063,11 +3453,11 @@ mod tests { let config = Config::load().unwrap(); - assert!(config.etherscan.clone().resolved(EtherscanApiVersion::V2).has_unresolved()); + assert!(config.etherscan.clone().resolved().has_unresolved()); jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); - let configs = config.etherscan.resolved(EtherscanApiVersion::V2); + let configs = config.etherscan.resolved(); assert!(!configs.has_unresolved()); let mb_urls = Moonbeam.etherscan_urls().unwrap(); @@ -3081,7 +3471,6 @@ mod tests { api_url: mainnet_urls.0.to_string(), chain: Some(NamedChain::Mainnet.into()), browser_url: Some(mainnet_urls.1.to_string()), - api_version: EtherscanApiVersion::V2, key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), } ), @@ -3091,7 +3480,6 @@ mod tests { api_url: mb_urls.0.to_string(), chain: Some(Moonbeam.into()), browser_url: Some(mb_urls.1.to_string()), - api_version: EtherscanApiVersion::V2, key: "123456789".to_string(), } ), @@ -3118,11 +3506,11 @@ mod tests { let config = Config::load().unwrap(); - assert!(config.etherscan.clone().resolved(EtherscanApiVersion::V2).has_unresolved()); + assert!(config.etherscan.clone().resolved().has_unresolved()); jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); - let configs = config.etherscan.resolved(EtherscanApiVersion::V2); + let configs = config.etherscan.resolved(); assert!(!configs.has_unresolved()); let mb_urls = Moonbeam.etherscan_urls().unwrap(); @@ -3136,7 +3524,6 @@ mod tests { api_url: mainnet_urls.0.to_string(), chain: Some(NamedChain::Mainnet.into()), browser_url: Some(mainnet_urls.1.to_string()), - api_version: EtherscanApiVersion::V2, key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), } ), @@ -3146,7 +3533,6 @@ mod tests { api_url: mb_urls.0.to_string(), chain: Some(Moonbeam.into()), browser_url: Some(mb_urls.1.to_string()), - api_version: EtherscanApiVersion::V1, key: "123456789".to_string(), } ), @@ -3180,6 +3566,27 @@ mod tests { }); } + // any invalid entry invalidates whole [etherscan] sections + #[test] + fn test_resolve_etherscan_with_invalid_name() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [etherscan] + mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + an_invalid_name = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + "#, + )?; + + let config = Config::load().unwrap(); + let etherscan_config = config.get_etherscan_config(); + assert!(etherscan_config.is_none()); + + Ok(()) + }); + } + #[test] fn test_resolve_rpc_url() { figment::Jail::expect_with(|jail| { @@ -3238,19 +3645,19 @@ mod tests { r#" [profile.default] [rpc_endpoints] - polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" + polygonAmoy = "https://polygon-amoy.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" "#, )?; let mut config = Config::load().unwrap(); - config.eth_rpc_url = Some("polygonMumbai".to_string()); + config.eth_rpc_url = Some("polygonAmoy".to_string()); assert!(config.get_rpc_url().unwrap().is_err()); jail.set_env("_RESOLVE_RPC_ALIAS", "123455"); let mut config = Config::load().unwrap(); - config.eth_rpc_url = Some("polygonMumbai".to_string()); + config.eth_rpc_url = Some("polygonAmoy".to_string()); assert_eq!( - "https://polygon-mumbai.g.alchemy.com/v2/123455", + "https://polygon-amoy.g.alchemy.com/v2/123455", config.get_rpc_url().unwrap().unwrap() ); @@ -3279,7 +3686,10 @@ mod tests { let config = config.get_etherscan_config_with_chain(Some(NamedChain::Arbitrum.into())); assert!(config.is_err()); - assert_eq!(config.unwrap_err().to_string(), "At least one of `url` or `chain` must be present for Etherscan config with unknown alias `arbitrum_alias`"); + assert_eq!( + config.unwrap_err().to_string(), + "At least one of `url` or `chain` must be present for Etherscan config with unknown alias `arbitrum_alias`" + ); Ok(()) }); @@ -3311,6 +3721,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3336,6 +3747,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3383,6 +3795,7 @@ mod tests { "mainnet", RpcEndpointType::Config(RpcEndpoint { endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3409,6 +3822,7 @@ mod tests { endpoint: RpcEndpointUrl::Url( "https://eth-mainnet.alchemyapi.io/v2/123455".to_string() ), + extra_endpoints: vec![], config: RpcEndpointConfig { retries: Some(3), retry_backoff: Some(1000), @@ -3497,7 +3911,7 @@ mod tests { [etherscan] optimism = { key = "https://etherscan-optimism.com/" } - mumbai = { key = "https://etherscan-mumbai.com/" } + amoy = { key = "https://etherscan-amoy.com/" } "#, )?; @@ -3506,10 +3920,10 @@ mod tests { let optimism = config.get_etherscan_api_key(Some(NamedChain::Optimism.into())); assert_eq!(optimism, Some("https://etherscan-optimism.com/".to_string())); - config.etherscan_api_key = Some("mumbai".to_string()); + config.etherscan_api_key = Some("amoy".to_string()); - let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); - assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); + let amoy = config.get_etherscan_api_key(Some(NamedChain::PolygonAmoy.into())); + assert_eq!(amoy, Some("https://etherscan-amoy.com/".to_string())); Ok(()) }); @@ -3524,17 +3938,17 @@ mod tests { [profile.default] [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 } + amoy = { key = "https://etherscan-amoy.com/", chain = 80002 } "#, )?; let config = Config::load().unwrap(); - let mumbai = config - .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) + let amoy = config + .get_etherscan_config_with_chain(Some(NamedChain::PolygonAmoy.into())) .unwrap() .unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); + assert_eq!(amoy.key, "https://etherscan-amoy.com/".to_string()); Ok(()) }); @@ -3549,18 +3963,18 @@ mod tests { [profile.default] [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/", chain = 80001 , url = "https://verifier-url.com/"} + amoy = { key = "https://etherscan-amoy.com/", chain = 80002 , url = "https://verifier-url.com/"} "#, )?; let config = Config::load().unwrap(); - let mumbai = config - .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) + let amoy = config + .get_etherscan_config_with_chain(Some(NamedChain::PolygonAmoy.into())) .unwrap() .unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); - assert_eq!(mumbai.api_url, "https://verifier-url.com/".to_string()); + assert_eq!(amoy.key, "https://etherscan-amoy.com/".to_string()); + assert_eq!(amoy.api_url, "https://verifier-url.com/".to_string()); Ok(()) }); @@ -3573,23 +3987,23 @@ mod tests { "foundry.toml", r#" [profile.default] - eth_rpc_url = "mumbai" + eth_rpc_url = "amoy" [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/" } + amoy = { key = "https://etherscan-amoy.com/" } [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/mumbai" + amoy = "https://polygon-amoy.g.alchemy.com/v2/amoy" "#, )?; let config = Config::load().unwrap(); - let mumbai = config.get_etherscan_config_with_chain(None).unwrap().unwrap(); - assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); + let amoy = config.get_etherscan_config_with_chain(None).unwrap().unwrap(); + assert_eq!(amoy.key, "https://etherscan-amoy.com/".to_string()); - let mumbai_rpc = config.get_rpc_url().unwrap().unwrap(); - assert_eq!(mumbai_rpc, "https://polygon-mumbai.g.alchemy.com/v2/mumbai"); + let amoy_rpc = config.get_rpc_url().unwrap().unwrap(); + assert_eq!(amoy_rpc, "https://polygon-amoy.g.alchemy.com/v2/amoy"); Ok(()) }); } @@ -3727,7 +4141,7 @@ mod tests { gas_reports = ['*'] ignored_error_codes = [1878] ignored_warnings_from = ["something"] - deny_warnings = false + deny = "never" initial_balance = '0xffffffffffffffffffffffff' libraries = [] libs = ['lib'] @@ -4007,6 +4421,7 @@ mod tests { out: "myout".into(), libs: default.libs.clone(), remappings: default.remappings.clone(), + network: None, } ); jail.set_env("FOUNDRY_PROFILE", r"other"); @@ -4019,6 +4434,7 @@ mod tests { out: "myout".into(), libs: default.libs.clone(), remappings: default.remappings, + network: None, } ); Ok(()) @@ -4148,7 +4564,7 @@ mod tests { let config = Config::load().unwrap(); - assert_eq!(config.block_number, 1337); + assert_eq!(config.block_number, U256::from(1337)); assert_eq!(config.sender, addr); assert_eq!(config.fuzz.runs, 420); assert_eq!(config.invariant.depth, 20); @@ -4170,8 +4586,10 @@ mod tests { let config = Config::load().unwrap(); assert_eq!( config.libraries, - vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string()] + vec![ + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string() + ] ); jail.set_env( @@ -4181,8 +4599,10 @@ mod tests { let config = Config::load().unwrap(); assert_eq!( config.libraries, - vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" - .to_string(),] + vec![ + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" + .to_string(), + ] ); jail.set_env( @@ -4412,6 +4832,48 @@ mod tests { }); } + #[test] + fn test_model_checker_settings_with_bool_flags() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [profile.default] + + [profile.default.model_checker] + engine = 'chc' + show_unproved = true + show_unsupported = true + show_proved_safe = false + div_mod_with_slacks = true + ", + )?; + let mut loaded = Config::load().unwrap(); + clear_warning(&mut loaded); + + let mc = loaded.model_checker.as_ref().unwrap(); + assert_eq!(mc.show_unproved, Some(true)); + assert_eq!(mc.show_unsupported, Some(true)); + assert_eq!(mc.show_proved_safe, Some(false)); + assert_eq!(mc.div_mod_with_slacks, Some(true)); + + // Test round-trip: serialize and reload + let s = loaded.to_string_pretty().unwrap(); + jail.create_file("foundry.toml", &s)?; + + let mut reloaded = Config::load().unwrap(); + clear_warning(&mut reloaded); + + let mc_reloaded = reloaded.model_checker.as_ref().unwrap(); + assert_eq!(mc_reloaded.show_unproved, Some(true)); + assert_eq!(mc_reloaded.show_unsupported, Some(true)); + assert_eq!(mc_reloaded.show_proved_safe, Some(false)); + assert_eq!(mc_reloaded.div_mod_with_slacks, Some(true)); + + Ok(()) + }); + } + #[test] fn test_model_checker_settings_relative_paths() { figment::Jail::expect_with(|jail| { @@ -4472,12 +4934,13 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r" + r#" [fmt] line_length = 100 tab_width = 2 bracket_spacing = true - ", + style = "space" + "#, )?; let loaded = Config::load().unwrap().sanitized(); assert_eq!( @@ -4486,6 +4949,7 @@ mod tests { line_length: 100, tab_width: 2, bracket_spacing: true, + style: IndentStyle::Space, ..Default::default() } ); @@ -4592,7 +5056,8 @@ mod tests { src: "src".into(), out: "out".into(), libs: vec!["lib".into()], - remappings: vec![] + remappings: vec![], + network: None, } ) ); @@ -4624,6 +5089,58 @@ mod tests { }); } + #[test] + fn hardfork_overrides_spec_id() { + let config = Config { + hardfork: Some(FoundryHardfork::Tempo(TempoHardfork::T3)), + ..Config::default() + }; + + assert_eq!(config.evm_spec_id::(), TempoHardfork::T3); + } + + #[test] + fn tempo_hardfork_infers_tempo_network() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + hardfork = "tempo:T3" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.hardfork, Some(FoundryHardfork::Tempo(TempoHardfork::T3))); + assert!(config.networks.is_tempo()); + + Ok(()) + }); + } + + #[test] + fn hardfork_rejects_conflicting_network() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + tempo = true + hardfork = "shanghai" + "#, + )?; + + let err = Config::load().unwrap_err(); + assert!( + err.to_string() + .to_lowercase() + .contains("hardfork `shanghai` conflicts with network config `tempo`") + ); + + Ok(()) + }); + } + #[test] fn test_etherscan_api_key() { figment::Jail::expect_with(|jail| { @@ -4976,4 +5493,1971 @@ mod tests { Ok(()) }); } + + // + #[test] + fn test_resolve_mesc_by_chain_id() { + let s = r#"{ + "mesc_version": "0.2.1", + "default_endpoint": null, + "endpoints": { + "sophon_50104": { + "name": "sophon_50104", + "url": "https://rpc.sophon.xyz", + "chain_id": "50104", + "endpoint_metadata": {} + } + }, + "network_defaults": { + }, + "network_names": {}, + "profiles": { + "foundry": { + "name": "foundry", + "default_endpoint": "local_ethereum", + "network_defaults": { + "50104": "sophon_50104" + }, + "profile_metadata": {}, + "use_mesc": true + } + }, + "global_metadata": {} +}"#; + + let config = serde_json::from_str(s).unwrap(); + let endpoint = mesc::query::get_endpoint_by_network(&config, "50104", Some("foundry")) + .unwrap() + .unwrap(); + assert_eq!(endpoint.url, "https://rpc.sophon.xyz"); + + let s = r#"{ + "mesc_version": "0.2.1", + "default_endpoint": null, + "endpoints": { + "sophon_50104": { + "name": "sophon_50104", + "url": "https://rpc.sophon.xyz", + "chain_id": "50104", + "endpoint_metadata": {} + } + }, + "network_defaults": { + "50104": "sophon_50104" + }, + "network_names": {}, + "profiles": {}, + "global_metadata": {} +}"#; + + let config = serde_json::from_str(s).unwrap(); + let endpoint = mesc::query::get_endpoint_by_network(&config, "50104", Some("foundry")) + .unwrap() + .unwrap(); + assert_eq!(endpoint.url, "https://rpc.sophon.xyz"); + } + + #[test] + fn test_get_etherscan_config_with_unknown_chain() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [etherscan] + mainnet = { chain = 3658348, key = "api-key"} + "#, + )?; + let config = Config::load().unwrap(); + let unknown_chain = Chain::from_id(3658348); + let result = config.get_etherscan_config_with_chain(Some(unknown_chain)); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("No known Etherscan API URL for chain `3658348`")); + assert!(error_msg.contains("Specify a `url`")); + assert!(error_msg.contains("Verify the chain `3658348` is correct")); + + Ok(()) + }); + } + + #[test] + fn test_get_etherscan_config_with_existing_chain_and_url() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [etherscan] + mainnet = { chain = 1, key = "api-key" } + "#, + )?; + let config = Config::load().unwrap(); + let unknown_chain = Chain::from_id(1); + let result = config.get_etherscan_config_with_chain(Some(unknown_chain)); + assert!(result.is_ok()); + Ok(()) + }); + } + + #[test] + fn test_can_inherit_a_base_toml() { + figment::Jail::expect_with(|jail| { + // Create base config file with optimizer_runs = 800 + jail.create_file( + "base-config.toml", + r#" + [profile.default] + optimizer_runs = 800 + + [invariant] + runs = 1000 + + [rpc_endpoints] + mainnet = "https://example.com" + optimism = "https://example-2.com/" + "#, + )?; + + // Create local config that inherits from base-config.toml + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base-config.toml" + + [invariant] + runs = 333 + depth = 15 + + [rpc_endpoints] + mainnet = "https://test.xyz/rpc" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.extends, Some(Extends::Path("base-config.toml".to_string()))); + + // optimizer_runs should be inherited from base-config.toml + assert_eq!(config.optimizer_runs, Some(800)); + + // invariant settings should be overridden by local config + assert_eq!(config.invariant.runs, 333); + assert_eq!(config.invariant.depth, 15); + + // rpc_endpoints.mainnet should be overridden by local config + // optimism should be inherited from base config + let endpoints = config.rpc_endpoints.resolved(); + assert!( + endpoints.get("mainnet").unwrap().url().unwrap().contains("https://test.xyz/rpc") + ); + assert!(endpoints.get("optimism").unwrap().url().unwrap().contains("example-2.com")); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_validation() { + figment::Jail::expect_with(|jail| { + // Test 1: Base file with 'extends' should fail + jail.create_file( + "base-with-inherit.toml", + r#" + [profile.default] + extends = "another.toml" + optimizer_runs = 800 + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base-with-inherit.toml" + "#, + )?; + + // Should fail because base file has 'extends' + let result = Config::load(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Nested inheritance is not allowed")); + + // Test 2: Circular reference should fail + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "foundry.toml" + "#, + )?; + + let result = Config::load(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("cannot inherit from itself")); + + // Test 3: Non-existent base file should fail + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "non-existent.toml" + "#, + )?; + + let result = Config::load(); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("does not exist") + || err_msg.contains("Failed to resolve inherited config path"), + "Error message: {err_msg}" + ); + + Ok(()) + }); + } + + #[test] + fn test_complex_inheritance_merging() { + figment::Jail::expect_with(|jail| { + // Create a comprehensive base config + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer = true + optimizer_runs = 1000 + via_ir = false + solc = "0.8.19" + + [invariant] + runs = 500 + depth = 100 + + [fuzz] + runs = 256 + seed = "0x123" + + [rpc_endpoints] + mainnet = "https://base-mainnet.com" + optimism = "https://base-optimism.com" + arbitrum = "https://base-arbitrum.com" + "#, + )?; + + // Create local config that overrides some values + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + optimizer_runs = 200 # Override + via_ir = true # Override + # optimizer and solc are inherited + + [invariant] + runs = 333 # Override + # depth is inherited + + # fuzz section is fully inherited + + [rpc_endpoints] + mainnet = "https://local-mainnet.com" # Override + # optimism and arbitrum are inherited + polygon = "https://local-polygon.com" # New + "#, + )?; + + let config = Config::load().unwrap(); + + // Check profile.default values + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(200)); + assert_eq!(config.via_ir, true); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 19)))); + + // Check invariant section + assert_eq!(config.invariant.runs, 333); + assert_eq!(config.invariant.depth, 100); + + // Check fuzz section (fully inherited) + assert_eq!(config.fuzz.runs, 256); + assert_eq!(config.fuzz.seed, Some(U256::from(0x123))); + + // Check rpc_endpoints + let endpoints = config.rpc_endpoints.resolved(); + assert!(endpoints.get("mainnet").unwrap().url().unwrap().contains("local-mainnet")); + assert!(endpoints.get("optimism").unwrap().url().unwrap().contains("base-optimism")); + assert!(endpoints.get("arbitrum").unwrap().url().unwrap().contains("base-arbitrum")); + assert!(endpoints.get("polygon").unwrap().url().unwrap().contains("local-polygon")); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_different_profiles() { + figment::Jail::expect_with(|jail| { + // Create base config with multiple profiles + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer = true + optimizer_runs = 200 + + [profile.ci] + optimizer = true + optimizer_runs = 10000 + via_ir = true + + [profile.dev] + optimizer = false + "#, + )?; + + // Local config inherits from base - only for default profile + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + verbosity = 3 + + [profile.ci] + optimizer_runs = 5000 # This doesn't inherit from base.toml's ci profile + "#, + )?; + + // Test default profile + let config = Config::load().unwrap(); + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(200)); + assert_eq!(config.verbosity, 3); + + // Test CI profile (NO 'extends', so doesn't inherit from base) + jail.set_env("FOUNDRY_PROFILE", "ci"); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(5000)); + assert_eq!(config.optimizer, Some(true)); + // via_ir is not set in local ci profile and there's no 'extends', so default + assert_eq!(config.via_ir, false); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_env_vars() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer_runs = 500 + sender = "0x0000000000000000000000000000000000000001" + verbosity = 1 + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + verbosity = 2 + "#, + )?; + + // Environment variables should override both base and local values + jail.set_env("FOUNDRY_OPTIMIZER_RUNS", "999"); + jail.set_env("FOUNDRY_VERBOSITY", "4"); + + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(999)); + assert_eq!(config.verbosity, 4); + assert_eq!( + config.sender, + "0x0000000000000000000000000000000000000001" + .parse::() + .unwrap() + ); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_subdirectories() { + figment::Jail::expect_with(|jail| { + // Create base config in a subdirectory + jail.create_dir("configs")?; + jail.create_file( + "configs/base.toml", + r#" + [profile.default] + optimizer_runs = 800 + src = "contracts" + "#, + )?; + + // Reference it with relative path + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "configs/base.toml" + test = "tests" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(800)); + assert_eq!(config.src, PathBuf::from("contracts")); + assert_eq!(config.test, PathBuf::from("tests")); + + // Test with parent directory reference + jail.create_dir("project")?; + jail.create_file( + "shared-base.toml", + r#" + [profile.default] + optimizer_runs = 1500 + "#, + )?; + + jail.create_file( + "project/foundry.toml", + r#" + [profile.default] + extends = "../shared-base.toml" + "#, + )?; + + std::env::set_current_dir(jail.directory().join("project")).unwrap(); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(1500)); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_empty_files() { + figment::Jail::expect_with(|jail| { + // Empty base file + jail.create_file( + "base.toml", + r#" + [profile.default] + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + optimizer_runs = 300 + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(300)); + + // Empty local file (only 'extends') + jail.create_file( + "base2.toml", + r#" + [profile.default] + optimizer_runs = 400 + via_ir = true + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base2.toml" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.optimizer_runs, Some(400)); + assert!(config.via_ir); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_array_and_table_merging() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "base.toml", + r#" + [profile.default] + libs = ["lib", "node_modules"] + ignored_error_codes = [5667, 1878] + extra_output = ["metadata", "ir"] + + [profile.default.model_checker] + engine = "chc" + timeout = 10000 + targets = ["assert"] + + [profile.default.optimizer_details] + peephole = true + inliner = true + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + libs = ["custom-lib"] # Concatenates with base array + ignored_error_codes = [2018] # Concatenates with base array + + [profile.default.model_checker] + timeout = 5000 # Overrides base value + # engine and targets are inherited + + [profile.default.optimizer_details] + jumpdest_remover = true # Adds new field + # peephole and inliner are inherited + "#, + )?; + + let config = Config::load().unwrap(); + + // Arrays are now concatenated with admerge (base + local) + assert_eq!( + config.libs, + vec![ + PathBuf::from("lib"), + PathBuf::from("node_modules"), + PathBuf::from("custom-lib") + ] + ); + assert_eq!( + config.ignored_error_codes, + vec![ + SolidityErrorCode::UnusedFunctionParameter, // 5667 from base.toml + SolidityErrorCode::SpdxLicenseNotProvided, // 1878 from base.toml + SolidityErrorCode::FunctionStateMutabilityCanBeRestricted // 2018 from local + ] + ); + + // Tables are deep-merged + assert_eq!(config.model_checker.as_ref().unwrap().timeout, Some(5000)); + assert_eq!( + config.model_checker.as_ref().unwrap().engine, + Some(ModelCheckerEngine::CHC) + ); + assert_eq!( + config.model_checker.as_ref().unwrap().targets, + Some(vec![ModelCheckerTarget::Assert]) + ); + + // optimizer_details table is actually merged, not replaced + assert_eq!(config.optimizer_details.as_ref().unwrap().peephole, Some(true)); + assert_eq!(config.optimizer_details.as_ref().unwrap().inliner, Some(true)); + assert_eq!(config.optimizer_details.as_ref().unwrap().jumpdest_remover, None); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_special_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "base.toml", + r#" + [profile.default] + # Base file should not have 'extends' to avoid nested inheritance + + [labels] + "0x0000000000000000000000000000000000000001" = "Alice" + "0x0000000000000000000000000000000000000002" = "Bob" + + [[profile.default.fs_permissions]] + access = "read" + path = "./src" + + [[profile.default.fs_permissions]] + access = "read-write" + path = "./cache" + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + + [labels] + "0x0000000000000000000000000000000000000002" = "Bob Updated" + "0x0000000000000000000000000000000000000003" = "Charlie" + + [[profile.default.fs_permissions]] + access = "read" + path = "./test" + "#, + )?; + + let config = Config::load().unwrap(); + + // Labels should be merged + assert_eq!( + config.labels.get( + &"0x0000000000000000000000000000000000000001" + .parse::() + .unwrap() + ), + Some(&"Alice".to_string()) + ); + assert_eq!( + config.labels.get( + &"0x0000000000000000000000000000000000000002" + .parse::() + .unwrap() + ), + Some(&"Bob Updated".to_string()) + ); + assert_eq!( + config.labels.get( + &"0x0000000000000000000000000000000000000003" + .parse::() + .unwrap() + ), + Some(&"Charlie".to_string()) + ); + + // fs_permissions array is now concatenated with addmerge (base + local) + assert_eq!(config.fs_permissions.permissions.len(), 3); // 2 from base + 1 from local + // Check that all permissions are present + assert!( + config + .fs_permissions + .permissions + .iter() + .any(|p| p.path.to_str().unwrap() == "./src") + ); + assert!( + config + .fs_permissions + .permissions + .iter() + .any(|p| p.path.to_str().unwrap() == "./cache") + ); + assert!( + config + .fs_permissions + .permissions + .iter() + .any(|p| p.path.to_str().unwrap() == "./test") + ); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_compilation_settings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "base.toml", + r#" + [profile.default] + solc = "0.8.19" + evm_version = "paris" + via_ir = false + optimizer = true + optimizer_runs = 200 + + [profile.default.optimizer_details] + peephole = true + inliner = false + jumpdest_remover = true + order_literals = false + deduplicate = true + cse = true + constant_optimizer = true + yul = true + + [profile.default.optimizer_details.yul_details] + stack_allocation = true + optimizer_steps = "dhfoDgvulfnTUtnIf" + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + evm_version = "shanghai" # Override + optimizer_runs = 1000 # Override + + [profile.default.optimizer_details] + inliner = true # Override + # Rest inherited + "#, + )?; + + let config = Config::load().unwrap(); + + // Check compilation settings + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 19)))); + assert_eq!(config.evm_version, EvmVersion::Shanghai); + assert_eq!(config.via_ir, false); + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(1000)); + + // Check optimizer details - the table is actually merged + let details = config.optimizer_details.as_ref().unwrap(); + assert_eq!(details.peephole, Some(true)); + assert_eq!(details.inliner, Some(true)); + assert_eq!(details.jumpdest_remover, None); + assert_eq!(details.order_literals, None); + assert_eq!(details.deduplicate, Some(true)); + assert_eq!(details.cse, Some(true)); + assert_eq!(details.constant_optimizer, None); + assert_eq!(details.yul, Some(true)); + + // Check yul details - inherited from base + if let Some(yul_details) = details.yul_details.as_ref() { + assert_eq!(yul_details.stack_allocation, Some(true)); + assert_eq!(yul_details.optimizer_steps, Some("dhfoDgvulfnTUtnIf".to_string())); + } + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_remappings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "base.toml", + r#" + [profile.default] + remappings = [ + "forge-std/=lib/forge-std/src/", + "@openzeppelin/=lib/openzeppelin-contracts/", + "ds-test/=lib/ds-test/src/" + ] + auto_detect_remappings = false + "#, + )?; + + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + remappings = [ + "@custom/=lib/custom/", + "ds-test/=lib/forge-std/lib/ds-test/src/" # Note: This will be added alongside base remappings + ] + "#, + )?; + + let config = Config::load().unwrap(); + + // Remappings array is now concatenated with admerge (base + local) + assert!(config.remappings.iter().any(|r| r.to_string().contains("@custom/"))); + assert!(config.remappings.iter().any(|r| r.to_string().contains("ds-test/"))); + assert!(config.remappings.iter().any(|r| r.to_string().contains("forge-std/"))); + assert!(config.remappings.iter().any(|r| r.to_string().contains("@openzeppelin/"))); + + // auto_detect_remappings should be inherited + assert!(!config.auto_detect_remappings); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_multiple_profiles_and_single_file() { + figment::Jail::expect_with(|jail| { + // Create base config with prod and test profiles + jail.create_file( + "base.toml", + r#" + [profile.prod] + optimizer = true + optimizer_runs = 10000 + via_ir = true + + [profile.test] + optimizer = false + + [profile.test.fuzz] + runs = 100 + "#, + )?; + + // Local config inherits from base for prod profile + jail.create_file( + "foundry.toml", + r#" + [profile.prod] + extends = "base.toml" + evm_version = "shanghai" # Additional setting + + [profile.test] + extends = "base.toml" + + [profile.test.fuzz] + runs = 500 # Override + "#, + )?; + + // Test prod profile + jail.set_env("FOUNDRY_PROFILE", "prod"); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(10000)); + assert_eq!(config.via_ir, true); + assert_eq!(config.evm_version, EvmVersion::Shanghai); + + // Test test profile + jail.set_env("FOUNDRY_PROFILE", "test"); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer, Some(false)); + assert_eq!(config.fuzz.runs, 500); + + Ok(()) + }); + } + + #[test] + fn test_inheritance_with_multiple_profiles_and_files() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "prod.toml", + r#" + [profile.prod] + optimizer = true + optimizer_runs = 20000 + gas_limit = 50000000 + "#, + )?; + jail.create_file( + "dev.toml", + r#" + [profile.dev] + optimizer = true + optimizer_runs = 333 + gas_limit = 555555 + "#, + )?; + + // Local config with only both profiles + jail.create_file( + "foundry.toml", + r#" + [profile.dev] + extends = "dev.toml" + sender = "0x0000000000000000000000000000000000000001" + + [profile.prod] + extends = "prod.toml" + sender = "0x0000000000000000000000000000000000000002" + "#, + )?; + + // Test that prod profile correctly inherits even without a default profile + jail.set_env("FOUNDRY_PROFILE", "dev"); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(333)); + assert_eq!(config.gas_limit, 555555.into()); + assert_eq!( + config.sender, + "0x0000000000000000000000000000000000000001" + .parse::() + .unwrap() + ); + + // Test that prod profile correctly inherits even without a default profile + jail.set_env("FOUNDRY_PROFILE", "prod"); + let config = Config::load().unwrap(); + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(20000)); + assert_eq!(config.gas_limit, 50000000.into()); + assert_eq!( + config.sender, + "0x0000000000000000000000000000000000000002" + .parse::() + .unwrap() + ); + + Ok(()) + }); + } + + #[test] + fn test_extends_strategy_extend_arrays() { + figment::Jail::expect_with(|jail| { + // Create base config with arrays + jail.create_file( + "base.toml", + r#" + [profile.default] + libs = ["lib", "node_modules"] + ignored_error_codes = [5667, 1878] + optimizer_runs = 200 + "#, + )?; + + // Local config extends with extend-arrays strategy (concatenates arrays) + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + libs = ["mylib", "customlib"] + ignored_error_codes = [1234] + optimizer_runs = 500 + "#, + )?; + + let config = Config::load().unwrap(); + + // Arrays should be concatenated (base + local) + assert_eq!(config.libs.len(), 4); + assert!(config.libs.iter().any(|l| l.to_str() == Some("lib"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("node_modules"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("mylib"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("customlib"))); + + assert_eq!(config.ignored_error_codes.len(), 3); + assert!( + config.ignored_error_codes.contains(&SolidityErrorCode::UnusedFunctionParameter) + ); // 5667 + assert!( + config.ignored_error_codes.contains(&SolidityErrorCode::SpdxLicenseNotProvided) + ); // 1878 + assert!(config.ignored_error_codes.contains(&SolidityErrorCode::from(1234u64))); // 1234 - generic + + // Non-array values should be replaced + assert_eq!(config.optimizer_runs, Some(500)); + + Ok(()) + }); + } + + #[test] + fn test_extends_strategy_replace_arrays() { + figment::Jail::expect_with(|jail| { + // Create base config with arrays + jail.create_file( + "base.toml", + r#" + [profile.default] + libs = ["lib", "node_modules"] + ignored_error_codes = [5667, 1878] + optimizer_runs = 200 + "#, + )?; + + // Local config extends with replace-arrays strategy (replaces arrays entirely) + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = { path = "base.toml", strategy = "replace-arrays" } + libs = ["mylib", "customlib"] + ignored_error_codes = [1234] + optimizer_runs = 500 + "#, + )?; + + let config = Config::load().unwrap(); + + // Arrays should be replaced entirely (only local values) + assert_eq!(config.libs.len(), 2); + assert!(config.libs.iter().any(|l| l.to_str() == Some("mylib"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("customlib"))); + assert!(!config.libs.iter().any(|l| l.to_str() == Some("lib"))); + assert!(!config.libs.iter().any(|l| l.to_str() == Some("node_modules"))); + + assert_eq!(config.ignored_error_codes.len(), 1); + assert!(config.ignored_error_codes.contains(&SolidityErrorCode::from(1234u64))); // 1234 + assert!( + !config.ignored_error_codes.contains(&SolidityErrorCode::UnusedFunctionParameter) + ); // 5667 + + // Non-array values should be replaced + assert_eq!(config.optimizer_runs, Some(500)); + + Ok(()) + }); + } + + #[test] + fn test_extends_strategy_no_collision_success() { + figment::Jail::expect_with(|jail| { + // Create base config + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer = true + optimizer_runs = 200 + src = "src" + "#, + )?; + + // Local config extends with no-collision strategy and no conflicts + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = { path = "base.toml", strategy = "no-collision" } + test = "tests" + libs = ["lib"] + "#, + )?; + + let config = Config::load().unwrap(); + + // Values from base should be present + assert_eq!(config.optimizer, Some(true)); + assert_eq!(config.optimizer_runs, Some(200)); + assert_eq!(config.src, PathBuf::from("src")); + + // Values from local should be present + assert_eq!(config.test, PathBuf::from("tests")); + assert_eq!(config.libs.len(), 1); + assert!(config.libs.iter().any(|l| l.to_str() == Some("lib"))); + + Ok(()) + }); + } + + #[test] + fn test_extends_strategy_no_collision_error() { + figment::Jail::expect_with(|jail| { + // Create base config + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer = true + optimizer_runs = 200 + libs = ["lib", "node_modules"] + "#, + )?; + + // Local config extends with no-collision strategy but has conflicts + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = { path = "base.toml", strategy = "no-collision" } + optimizer_runs = 500 + libs = ["mylib"] + "#, + )?; + + // Loading should fail due to key collision + let result = Config::load(); + + if let Ok(config) = result { + panic!( + "Expected error but got config with optimizer_runs: {:?}, libs: {:?}", + config.optimizer_runs, config.libs + ); + } + + let err = result.unwrap_err(); + let err_str = err.to_string(); + assert!( + err_str.contains("Key collision detected") || err_str.contains("collision"), + "Error message doesn't mention collision: {err_str}" + ); + + Ok(()) + }); + } + + #[test] + fn test_extends_both_syntaxes() { + figment::Jail::expect_with(|jail| { + // Create base config + jail.create_file( + "base.toml", + r#" + [profile.default] + libs = ["lib"] + optimizer = true + "#, + )?; + + // Test 1: Simple string syntax (should use default extend-arrays) + jail.create_file( + "foundry_string.toml", + r#" + [profile.default] + extends = "base.toml" + libs = ["custom"] + "#, + )?; + + // Test 2: Object syntax with explicit strategy + jail.create_file( + "foundry_object.toml", + r#" + [profile.default] + extends = { path = "base.toml", strategy = "replace-arrays" } + libs = ["custom"] + "#, + )?; + + // Test string syntax (default extend-arrays) + jail.set_env("FOUNDRY_CONFIG", "foundry_string.toml"); + let config = Config::load().unwrap(); + assert_eq!(config.libs.len(), 2); // Should concatenate + assert!(config.libs.iter().any(|l| l.to_str() == Some("lib"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("custom"))); + + // Test object syntax (replace-arrays) + jail.set_env("FOUNDRY_CONFIG", "foundry_object.toml"); + let config = Config::load().unwrap(); + assert_eq!(config.libs.len(), 1); // Should replace + assert!(config.libs.iter().any(|l| l.to_str() == Some("custom"))); + assert!(!config.libs.iter().any(|l| l.to_str() == Some("lib"))); + + Ok(()) + }); + } + + #[test] + fn test_extends_strategy_default_is_extend_arrays() { + figment::Jail::expect_with(|jail| { + // Create base config + jail.create_file( + "base.toml", + r#" + [profile.default] + libs = ["lib", "node_modules"] + optimizer = true + "#, + )?; + + // Local config extends without specifying strategy (should default to extend-arrays) + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + libs = ["custom"] + optimizer = false + "#, + )?; + + // Should work with default extend-arrays strategy + let config = Config::load().unwrap(); + + // Arrays should be concatenated by default + assert_eq!(config.libs.len(), 3); + assert!(config.libs.iter().any(|l| l.to_str() == Some("lib"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("node_modules"))); + assert!(config.libs.iter().any(|l| l.to_str() == Some("custom"))); + + // Non-array values should be replaced + assert_eq!(config.optimizer, Some(false)); + + Ok(()) + }); + } + + #[test] + fn test_deprecated_deny_warnings_is_handled() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + deny_warnings = true + "#, + )?; + let config = Config::load().unwrap(); + + // Assert that the deprecated flag is correctly interpreted + assert_eq!(config.deny, DenyLevel::Warnings); + Ok(()) + }); + } + + #[test] + fn warns_on_unknown_keys_in_profile() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + unknown_key_xyz = 123 + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!(cfg.warnings.iter().any( + |w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "unknown_key_xyz") + )); + Ok(()) + }); + } + + #[test] + fn no_unknown_key_warning_for_network_field() { + // Regression test: `network` is a flattened `Option` field of `NetworkConfigs`. It must + // not trigger an unknown-key warning, regardless of whether it is set. + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + network = "tempo" + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + !cfg.warnings.iter().any( + |w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "network") + ), + "did not expect UnknownKey warning for `network`, got: {:?}", + cfg.warnings + ); + Ok(()) + }); + } + + #[test] + fn no_unknown_key_warning_for_legacy_tempo_alias() { + // Regression test: the legacy `tempo = true` alias must keep working without warnings. + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + tempo = true + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + !cfg.warnings + .iter() + .any(|w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "tempo")), + "did not expect UnknownKey warning for `tempo`, got: {:?}", + cfg.warnings + ); + Ok(()) + }); + } + + #[test] + fn fails_on_ambiguous_version_in_compilation_restrictions() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "0.8.11" + "#, + )?; + + let err = Config::load().expect_err("expected bare version to fail"); + let err_msg = err.to_string(); + assert!( + err_msg.contains("Invalid version format '0.8.11'") + && err_msg.contains("Bare version numbers are ambiguous"), + "Expected error about ambiguous version, got: {err_msg}" + ); + + Ok(()) + }); + } + + #[test] + fn accepts_explicit_version_requirements() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + version = "=0.8.11" + + [[profile.default.compilation_restrictions]] + paths = "test/*.sol" + version = ">=0.8.11" + "#, + )?; + + let config = Config::load().expect("should accept explicit version requirements"); + assert_eq!(config.compilation_restrictions.len(), 2); + + Ok(()) + }); + } + + #[test] + fn warns_on_unknown_keys_in_all_config_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + unknown_profile_key = "should_warn" + + # Standalone sections with unknown keys + [fmt] + line_length = 120 + unknown_fmt_key = "should_warn" + + [lint] + severity = ["high"] + unknown_lint_key = "should_warn" + + [doc] + out = "docs" + unknown_doc_key = "should_warn" + + [fuzz] + runs = 256 + unknown_fuzz_key = "should_warn" + + [invariant] + runs = 256 + unknown_invariant_key = "should_warn" + + [vyper] + unknown_vyper_key = "should_warn" + + [bind_json] + out = "bindings.sol" + unknown_bind_json_key = "should_warn" + + # Nested profile sections with unknown keys + [profile.default.fmt] + line_length = 100 + unknown_nested_fmt_key = "should_warn" + + [profile.default.lint] + severity = ["low"] + unknown_nested_lint_key = "should_warn" + + [profile.default.doc] + out = "documentation" + unknown_nested_doc_key = "should_warn" + + [profile.default.fuzz] + runs = 512 + unknown_nested_fuzz_key = "should_warn" + + [profile.default.invariant] + runs = 512 + unknown_nested_invariant_key = "should_warn" + + [profile.default.vyper] + unknown_nested_vyper_key = "should_warn" + + [profile.default.bind_json] + out = "nested_bindings.sol" + unknown_nested_bind_json_key = "should_warn" + + # Array sections with unknown keys + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + unknown_compilation_key = "should_warn" + + [[profile.default.additional_compiler_profiles]] + name = "via-ir" + via_ir = true + unknown_compiler_profile_key = "should_warn" + "#, + )?; + + let cfg = Config::load().unwrap(); + + // Expected warnings for profile-level unknown key + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownKey { key, .. } if key == "unknown_profile_key" + )), + "Expected warning for 'unknown_profile_key' in profile, got: {:?}", + cfg.warnings + ); + + // Expected warnings for standalone sections + let standalone_expected = [ + ("unknown_fmt_key", "fmt"), + ("unknown_lint_key", "lint"), + ("unknown_doc_key", "doc"), + ("unknown_fuzz_key", "fuzz"), + ("unknown_invariant_key", "invariant"), + ("unknown_vyper_key", "vyper"), + ("unknown_bind_json_key", "bind_json"), + ]; + + for (expected_key, expected_section) in standalone_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in standalone section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Expected warnings for nested profile sections + let nested_expected = [ + ("unknown_nested_fmt_key", "fmt"), + ("unknown_nested_lint_key", "lint"), + ("unknown_nested_doc_key", "doc"), + ("unknown_nested_fuzz_key", "fuzz"), + ("unknown_nested_invariant_key", "invariant"), + ("unknown_nested_vyper_key", "vyper"), + ("unknown_nested_bind_json_key", "bind_json"), + ]; + + for (expected_key, expected_section) in nested_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in nested section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Expected warnings for array item sections + let array_expected = [ + ("unknown_compilation_key", "compilation_restrictions"), + ("unknown_compiler_profile_key", "additional_compiler_profiles"), + ]; + + for (expected_key, expected_section) in array_expected { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in array section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Verify total count of unknown key warnings + let unknown_key_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!(w, crate::Warning::UnknownKey { .. }) + || matches!(w, crate::Warning::UnknownSectionKey { .. }) + }) + .collect(); + + // 1 profile key + 7 standalone + 7 nested + 2 array = 17 total + assert_eq!( + unknown_key_warnings.len(), + 17, + "Expected 17 unknown key warnings (1 profile + 7 standalone + 7 nested + 2 array), got {}: {:?}", + unknown_key_warnings.len(), + unknown_key_warnings + ); + + Ok(()) + }); + } + + #[test] + fn warns_on_unknown_keys_in_extended_config() { + figment::Jail::expect_with(|jail| { + // Create base config with unknown keys + jail.create_file( + "base.toml", + r#" + [profile.default] + optimizer_runs = 800 + unknown_base_profile_key = "should_warn" + + [lint] + severity = ["high"] + unknown_base_lint_key = "should_warn" + + [fmt] + line_length = 100 + unknown_base_fmt_key = "should_warn" + "#, + )?; + + // Create local config that extends base with its own unknown keys + jail.create_file( + "foundry.toml", + r#" + [profile.default] + extends = "base.toml" + src = "src" + unknown_local_profile_key = "should_warn" + + [lint] + unknown_local_lint_key = "should_warn" + + [fuzz] + runs = 512 + unknown_local_fuzz_key = "should_warn" + + [[profile.default.compilation_restrictions]] + paths = "src/*.sol" + unknown_local_restriction_key = "should_warn" + "#, + )?; + + let cfg = Config::load().unwrap(); + + // Verify base config values are inherited + assert_eq!(cfg.optimizer_runs, Some(800)); + + // Unknown keys from both base and local configs should be detected. + // Note: Due to how figment merges configs before validation, the source + // will show the local config file for all warnings. This is a known + // limitation - proper source attribution for extended configs would + // require validating each file before the merge. + + // Verify all expected unknown keys are detected + let expected_unknown_keys = ["unknown_base_profile_key", "unknown_local_profile_key"]; + for expected_key in expected_unknown_keys { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownKey { key, .. } if key == expected_key + )), + "Expected warning for '{}', got: {:?}", + expected_key, + cfg.warnings + ); + } + + let expected_section_keys = [ + ("unknown_base_lint_key", "lint"), + ("unknown_base_fmt_key", "fmt"), + ("unknown_local_lint_key", "lint"), + ("unknown_local_fuzz_key", "fuzz"), + ("unknown_local_restriction_key", "compilation_restrictions"), + ]; + for (expected_key, expected_section) in expected_section_keys { + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == expected_key && section == expected_section + )), + "Expected warning for '{}' in section '{}', got: {:?}", + expected_key, + expected_section, + cfg.warnings + ); + } + + // Verify total: 2 profile keys + 5 section keys = 7 warnings + let unknown_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!(w, crate::Warning::UnknownKey { .. }) + || matches!(w, crate::Warning::UnknownSectionKey { .. }) + }) + .collect(); + assert_eq!( + unknown_warnings.len(), + 7, + "Expected 7 unknown key warnings, got {}: {:?}", + unknown_warnings.len(), + unknown_warnings + ); + + Ok(()) + }); + } + + // Test for issue #12844: FOUNDRY_PROFILE=nonexistent should warn and fall back to default. + #[test] + fn warns_on_unknown_profile() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "nonexistent"); + let cfg = Config::load().expect("expected unknown profile to fall back to default"); + assert_eq!(cfg.profile, Config::DEFAULT_PROFILE); + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownProfile { profile } if profile == "nonexistent" + )), + "Expected UnknownProfile warning, got: {:?}", + cfg.warnings + ); + + Ok(()) + }); + } + + // Test for issue #13316: vyper config keys should not trigger unknown key warnings + #[test] + fn no_false_warnings_for_vyper_config_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [vyper] + optimize = "gas" + path = "/usr/bin/vyper" + experimental_codegen = true + "#, + )?; + + let cfg = Config::load().unwrap(); + // None of the valid vyper keys should trigger warnings + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid vyper keys should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: vyper config in profile should not trigger false warnings + #[test] + fn no_false_warnings_for_nested_vyper_config_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.default.vyper] + optimize = "codesize" + path = "/opt/vyper/bin/vyper" + experimental_codegen = false + "#, + )?; + + let cfg = Config::load().unwrap(); + // None of the valid vyper keys should trigger warnings + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid nested vyper keys should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: inline vyper config format should not trigger false warnings + // This matches the exact format used in https://github.com/pcaversaccio/snekmate + #[test] + fn no_false_warnings_for_inline_vyper_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + vyper = { optimize = "gas" } + + [profile.default-venom] + vyper = { experimental_codegen = true } + + [profile.ci-venom] + vyper = { experimental_codegen = true } + "#, + )?; + + let cfg = Config::load().unwrap(); + let vyper_warnings: Vec<_> = cfg + .warnings + .iter() + .filter(|w| { + matches!( + w, + crate::Warning::UnknownSectionKey { section, .. } if section == "vyper" + ) + }) + .collect(); + + assert!( + vyper_warnings.is_empty(), + "Valid inline vyper config should not trigger warnings, got: {vyper_warnings:?}" + ); + + Ok(()) + }); + } + + // Test for issue #13316: unknown vyper keys should still warn + #[test] + fn warns_on_unknown_vyper_keys() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [vyper] + optimize = "gas" + unknown_vyper_option = true + "#, + )?; + + let cfg = Config::load().unwrap(); + assert!( + cfg.warnings.iter().any(|w| matches!( + w, + crate::Warning::UnknownSectionKey { key, section, .. } + if key == "unknown_vyper_option" && section == "vyper" + )), + "Unknown vyper key should trigger warning, got: {:?}", + cfg.warnings + ); + + Ok(()) + }); + } + + // Test for issue #12844: known profile should work + #[test] + fn succeeds_on_known_profile() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci] + src = "src" + fuzz = { runs = 10000 } + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "ci"); + let config = Config::load().expect("known profile should work"); + assert_eq!(config.profile.as_str(), "ci"); + assert_eq!(config.fuzz.runs, 10000); + + Ok(()) + }); + } + + // Test for issue #12963: nested lib configs should fallback to default profile + // when they don't define the requested profile + #[test] + fn nested_lib_config_falls_back_to_default_profile() { + figment::Jail::expect_with(|jail| { + // Create a lib directory with only default profile + let lib_path = jail.directory().join("lib/mylib"); + std::fs::create_dir_all(&lib_path).unwrap(); + jail.create_file( + "lib/mylib/foundry.toml", + r#" + [profile.default] + src = "contracts" + "#, + )?; + + // Set a profile that doesn't exist in the lib + jail.set_env("FOUNDRY_PROFILE", "ci"); + + // load_with_root_and_fallback should succeed and fall back to default + let config = Config::load_with_root_and_fallback(&lib_path) + .expect("lib config should load with fallback"); + assert_eq!(config.profile, Config::DEFAULT_PROFILE); + assert_eq!(config.src.as_os_str(), "contracts"); + + Ok(()) + }); + } + + // Test for issue #12963: nested lib configs should use requested profile if it exists + #[test] + fn nested_lib_config_uses_profile_if_exists() { + figment::Jail::expect_with(|jail| { + // Create a lib directory with both default and ci profiles + let lib_path = jail.directory().join("lib/mylib"); + std::fs::create_dir_all(&lib_path).unwrap(); + jail.create_file( + "lib/mylib/foundry.toml", + r#" + [profile.default] + src = "contracts" + + [profile.ci] + src = "contracts" + fuzz = { runs = 5000 } + "#, + )?; + + // Set a profile that exists in the lib + jail.set_env("FOUNDRY_PROFILE", "ci"); + + // load_with_root_and_fallback should use the ci profile + let config = Config::load_with_root_and_fallback(&lib_path) + .expect("lib config should load with profile"); + assert_eq!(config.profile.as_str(), "ci"); + assert_eq!(config.fuzz.runs, 5000); + + Ok(()) + }); + } + + // Test for issue #13170: profile names with hyphens should work correctly + #[test] + fn succeeds_on_hyphenated_profile_name() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci-venom] + src = "src" + fuzz = { runs = 7500 } + + [profile.default-venom] + src = "src" + fuzz = { runs = 8000 } + "#, + )?; + + // Test ci-venom profile + jail.set_env("FOUNDRY_PROFILE", "ci-venom"); + let config = Config::load().expect("hyphenated profile should work"); + assert_eq!(config.profile.as_str(), "ci-venom"); + assert_eq!(config.fuzz.runs, 7500); + + // Test default-venom profile + jail.set_env("FOUNDRY_PROFILE", "default-venom"); + let config = Config::load().expect("hyphenated profile should work"); + assert_eq!(config.profile.as_str(), "default-venom"); + assert_eq!(config.fuzz.runs, 8000); + + // Verify the profiles list contains hyphenated names + assert!( + config.profiles.iter().any(|p| p.as_str() == "ci-venom"), + "profiles should contain 'ci-venom', got: {:?}", + config.profiles + ); + assert!( + config.profiles.iter().any(|p| p.as_str() == "default-venom"), + "profiles should contain 'default-venom', got: {:?}", + config.profiles + ); + + Ok(()) + }); + } + + // Test for issue #13170: hyphenated profile with nested config keys + #[test] + fn hyphenated_profile_with_nested_sections() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + src = "src" + + [profile.ci-venom] + src = "src" + optimizer_runs = 500 + + [profile.ci-venom.fuzz] + runs = 10000 + max_test_rejects = 350000 + + [profile.ci-venom.invariant] + runs = 375 + depth = 500 + "#, + )?; + + jail.set_env("FOUNDRY_PROFILE", "ci-venom"); + let config = + Config::load().expect("hyphenated profile with nested sections should work"); + assert_eq!(config.profile.as_str(), "ci-venom"); + assert_eq!(config.optimizer_runs, Some(500)); + assert_eq!(config.fuzz.runs, 10000); + assert_eq!(config.fuzz.max_test_rejects, 350000); + assert_eq!(config.invariant.runs, 375); + assert_eq!(config.invariant.depth, 500); + + Ok(()) + }); + } } diff --git a/crates/config/src/lint.rs b/crates/config/src/lint.rs index 10d86c96fb321..9494fe18d9f8b 100644 --- a/crates/config/src/lint.rs +++ b/crates/config/src/lint.rs @@ -3,12 +3,15 @@ use clap::ValueEnum; use core::fmt; use serde::{Deserialize, Deserializer, Serialize}; -use solar_interface::diagnostics::Level; +use solar::{ + ast::{self as ast}, + interface::diagnostics::Level, +}; use std::str::FromStr; use yansi::Paint; -/// Contains the config and rule set -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +/// Contains the config and rule set. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct LinterConfig { /// Specifies which lints to run based on severity. /// @@ -18,21 +21,127 @@ pub struct LinterConfig { /// Deny specific lints based on their ID (e.g. "mixed-case-function"). pub exclude_lints: Vec, - /// Globs to ignore + /// Globs to ignore. pub ignore: Vec, + + /// Whether to run linting during `forge build`. + /// + /// Defaults to true. Set to false to disable automatic linting during builds. + pub lint_on_build: bool, + + /// Configuration specific to individual lints. + pub lint_specific: LintSpecificConfig, +} + +impl Default for LinterConfig { + fn default() -> Self { + Self { + lint_on_build: true, + severity: vec![Severity::High, Severity::Med, Severity::Low], + exclude_lints: Vec::new(), + ignore: Vec::new(), + lint_specific: LintSpecificConfig::default(), + } + } +} + +/// Contract types that can be exempted from the multi-contract-file lint. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ContractException { + Interface, + Library, + AbstractContract, +} + +/// Configuration specific to individual lints. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct LintSpecificConfig { + /// Configurable patterns that should be excluded when performing `mixedCase` lint checks. + /// + /// Defaults to ["ERC", "URI"] to allow common names like `rescueERC20`, `ERC721TokenReceiver` + /// or `tokenURI`. + pub mixed_case_exceptions: Vec, + + /// Contract types that are allowed to appear multiple times in the same file. + /// + /// Valid values: "interface", "library", "abstract_contract" + /// + /// Defaults to an empty array (all contract types are flagged when multiple exist). + /// Note: Regular contracts cannot be exempted and will always be flagged when multiple exist. + pub multi_contract_file_exceptions: Vec, +} + +impl Default for LintSpecificConfig { + fn default() -> Self { + Self { + mixed_case_exceptions: vec![ + "ERC".to_string(), + "URI".to_string(), + "ID".to_string(), + "URL".to_string(), + "API".to_string(), + "JSON".to_string(), + "XML".to_string(), + "HTML".to_string(), + "HTTP".to_string(), + "HTTPS".to_string(), + ], + multi_contract_file_exceptions: Vec::new(), + } + } } -/// Severity of a lint -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize)] +impl LintSpecificConfig { + /// Checks if a given contract kind is included in the list of exceptions + pub fn is_exempted(&self, contract_kind: &ast::ContractKind) -> bool { + let exception_to_check = match contract_kind { + ast::ContractKind::Interface => ContractException::Interface, + ast::ContractKind::Library => ContractException::Library, + ast::ContractKind::AbstractContract => ContractException::AbstractContract, + // Regular contracts are always linted + ast::ContractKind::Contract => return false, + }; + + self.multi_contract_file_exceptions.contains(&exception_to_check) + } +} + +/// Severity of a lint. +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] pub enum Severity { High, Med, Low, Info, Gas, + CodeSize, } impl Severity { + const fn to_str(self) -> &'static str { + match self { + Self::High => "High", + Self::Med => "Med", + Self::Low => "Low", + Self::Info => "Info", + Self::Gas => "Gas", + Self::CodeSize => "CodeSize", + } + } + + const fn to_str_kebab(self) -> &'static str { + match self { + Self::High => "high", + Self::Med => "medium", + Self::Low => "low", + Self::Info => "info", + Self::Gas => "gas", + Self::CodeSize => "code-size", + } + } + pub fn color(&self, message: &str) -> String { match self { Self::High => Paint::red(message).bold().to_string(), @@ -40,6 +149,7 @@ impl Severity { Self::Low => Paint::yellow(message).bold().to_string(), Self::Info => Paint::cyan(message).bold().to_string(), Self::Gas => Paint::green(message).bold().to_string(), + Self::CodeSize => Paint::green(message).bold().to_string(), } } } @@ -48,21 +158,23 @@ impl From for Level { fn from(severity: Severity) -> Self { match severity { Severity::High | Severity::Med | Severity::Low => Self::Warning, - Severity::Info | Severity::Gas => Self::Note, + Severity::Info | Severity::Gas | Severity::CodeSize => Self::Note, } } } impl fmt::Display for Severity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let colored = match self { - Self::High => self.color("High"), - Self::Med => self.color("Med"), - Self::Low => self.color("Low"), - Self::Info => self.color("Info"), - Self::Gas => self.color("Gas"), - }; - write!(f, "{colored}") + write!(f, "{}", self.color(self.to_str())) + } +} + +impl Serialize for Severity { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_str_kebab().serialize(serializer) } } @@ -87,7 +199,10 @@ impl FromStr for Severity { "low" => Ok(Self::Low), "info" => Ok(Self::Info), "gas" => Ok(Self::Gas), - _ => Err(format!("unknown variant: found `{s}`, expected `one of `High`, `Med`, `Low`, `Info`, `Gas``")), + "size" | "codesize" | "code-size" => Ok(Self::CodeSize), + _ => Err(format!( + "unknown variant: found `{s}`, expected `one of `High`, `Med`, `Low`, `Info`, `Gas`, `CodeSize`" + )), } } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index cb5dc9771abc9..12679e7002e38 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -95,8 +95,9 @@ macro_rules! impl_figment_convert { /// /// ```rust /// use foundry_config::{ +/// Config, /// figment::{value::*, *}, -/// impl_figment_convert, merge_impl_figment_convert, Config, +/// impl_figment_convert, merge_impl_figment_convert, /// }; /// use std::path::PathBuf; /// diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs index 64c99c771ba38..6423e37a4f2fa 100644 --- a/crates/config/src/providers/ext.rs +++ b/crates/config/src/providers/ext.rs @@ -1,12 +1,15 @@ -use crate::{utils, Config}; +use crate::{Config, extend, utils}; use figment::{ + Error, Figment, Metadata, Profile, Provider, providers::{Env, Format, Toml}, value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, }; use foundry_compilers::ProjectPathsConfig; use heck::ToSnakeCase; -use std::path::{Path, PathBuf}; +use std::{ + cell::OnceCell, + path::{Path, PathBuf}, +}; pub(crate) trait ProviderExt: Provider + Sized { fn rename( @@ -46,18 +49,19 @@ impl ProviderExt for P {} /// A convenience provider to retrieve a toml file. /// This will return an error if the env var is set but the file does not exist pub(crate) struct TomlFileProvider { - pub env_var: Option<&'static str>, - pub default: PathBuf, - pub cache: Option, Error>>, + env_var: Option<&'static str>, + env_val: OnceCell>, + default: PathBuf, + cache: OnceCell, Error>>, } impl TomlFileProvider { - pub(crate) fn new(env_var: Option<&'static str>, default: impl Into) -> Self { - Self { env_var, default: default.into(), cache: None } + pub(crate) const fn new(env_var: Option<&'static str>, default: PathBuf) -> Self { + Self { env_var, env_val: OnceCell::new(), default, cache: OnceCell::new() } } - fn env_val(&self) -> Option { - self.env_var.and_then(Env::var) + fn env_val(&self) -> Option<&str> { + self.env_val.get_or_init(|| self.env_var.and_then(Env::var)).as_deref() } fn file(&self) -> PathBuf { @@ -74,28 +78,162 @@ impl TomlFileProvider { false } - pub(crate) fn cached(mut self) -> Self { - self.cache = Some(self.read()); - self - } - + /// Reads and processes the TOML configuration file, handling inheritance if configured. fn read(&self) -> Result, Error> { use serde::de::Error as _; - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { + + // Get the config file path and validate it exists + let local_path = self.file(); + if !local_path.exists() { + if let Some(file) = self.env_val() { return Err(Error::custom(format!( "Config file `{}` set in env var `{}` does not exist", file, self.env_var.unwrap() ))); } - Toml::file(file) + return Ok(Map::new()); + } + + // Create a provider for the local config file + let local_provider = Toml::file(local_path.clone()).nested(); + + // Parse the local config to check for extends field + let local_path_str = local_path.to_string_lossy(); + let local_content = std::fs::read_to_string(&local_path) + .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?; + let partial_config: extend::ExtendsPartialConfig = toml::from_str(&local_content) + .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?; + + // Check if the currently active profile has an 'extends' field + let selected_profile = Config::selected_profile(); + let extends_config = partial_config.profile.as_ref().and_then(|profiles| { + let profile_str = selected_profile.to_string(); + profiles.get(&profile_str).and_then(|cfg| cfg.extends.as_ref()) + }); + + // If inheritance is configured, load and merge the base config + if let Some(extends_config) = extends_config { + let extends_path = extends_config.path(); + let extends_strategy = extends_config.strategy(); + let relative_base_path = PathBuf::from(extends_path); + let local_dir = local_path.parent().ok_or_else(|| { + Error::custom(format!( + "Could not determine parent directory of config file: {}", + local_path.display() + )) + })?; + + let base_path = + foundry_compilers::utils::canonicalize(local_dir.join(&relative_base_path)) + .map_err(|e| { + Error::custom(format!( + "Failed to resolve inherited config path: {}: {e}", + relative_base_path.display() + )) + })?; + + // Validate the base config file exists + if !base_path.is_file() { + return Err(Error::custom(format!( + "Inherited config file does not exist or is not a file: {}", + base_path.display() + ))); + } + + // Prevent self-inheritance which would cause infinite recursion + if foundry_compilers::utils::canonicalize(&local_path).ok().as_ref() == Some(&base_path) + { + return Err(Error::custom(format!( + "Config file {} cannot inherit from itself.", + local_path.display() + ))); + } + + // Parse the base config to check for nested inheritance + let base_path_str = base_path.to_string_lossy(); + let base_content = std::fs::read_to_string(&base_path) + .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?; + let base_partial: extend::ExtendsPartialConfig = toml::from_str(&base_content) + .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?; + + // Check if the base file's same profile also has extends (nested inheritance) + let base_extends = base_partial + .profile + .as_ref() + .and_then(|profiles| { + let profile_str = selected_profile.to_string(); + profiles.get(&profile_str) + }) + .and_then(|profile| profile.extends.as_ref()); + + // Prevent nested inheritance to avoid complexity and potential cycles + if base_extends.is_some() { + return Err(Error::custom(format!( + "Nested inheritance is not allowed. Base file '{}' cannot have an 'extends' field in profile '{selected_profile}'.", + base_path.display() + ))); + } + + // Load base configuration as a Figment provider + let base_provider = Toml::file(base_path).nested(); + + // Apply the selected merge strategy + match extends_strategy { + extend::ExtendStrategy::ExtendArrays => { + // Using 'admerge' strategy: + // - Arrays are concatenated (base elements + local elements) + // - Other values are replaced (local values override base values) + // - The extends field is preserved in the final configuration + Figment::new().merge(base_provider).admerge(local_provider).data() + } + extend::ExtendStrategy::ReplaceArrays => { + // Using 'merge' strategy: + // - Arrays are replaced entirely (local arrays replace base arrays) + // - Other values are replaced (local values override base values) + Figment::new().merge(base_provider).merge(local_provider).data() + } + extend::ExtendStrategy::NoCollision => { + // Check for key collisions between base and local configs + let base_data = base_provider.data()?; + let local_data = local_provider.data()?; + + let profile_key = Profile::new("profile"); + if let (Some(local_profiles), Some(base_profiles)) = + (local_data.get(&profile_key), base_data.get(&profile_key)) + { + // Extract dicts for the selected profile + let profile_str = selected_profile.to_string(); + let base_dict = base_profiles.get(&profile_str).and_then(|v| v.as_dict()); + let local_dict = local_profiles.get(&profile_str).and_then(|v| v.as_dict()); + + // Find colliding keys + if let (Some(local_dict), Some(base_dict)) = (local_dict, base_dict) { + let collisions: Vec<&String> = local_dict + .keys() + .filter(|key| { + // Ignore the "extends" key as it's expected + *key != "extends" && base_dict.contains_key(*key) + }) + .collect(); + + if !collisions.is_empty() { + return Err(Error::custom(format!( + "Key collision detected in profile '{profile_str}' when extending '{extends_path}'. \ + Conflicting keys: {collisions:?}. Use 'extends.strategy' or 'extends_strategy' to specify how to handle conflicts." + ))); + } + } + } + + // Safe to merge the configs without collisions + Figment::new().merge(base_provider).merge(local_provider).data() + } + } } else { - Toml::file(&self.default) + // No inheritance - return the local config as-is + local_provider.data() } - .nested() - .data() } } @@ -109,16 +247,15 @@ impl Provider for TomlFileProvider { } fn data(&self) -> Result, Error> { - if let Some(cache) = self.cache.as_ref() { - cache.clone() - } else { - self.read() - } + self.cache.get_or_init(|| self.read()).clone() } } /// A Provider that ensures all keys are snake case if they're not standalone sections, See /// `Config::STANDALONE_SECTIONS` +/// +/// For the `[profile]` section, profile names (like `ci-venom`) are preserved as-is, +/// but the config keys within each profile are still converted to snake_case. pub(crate) struct ForcedSnakeCaseData

(pub(crate) P); impl Provider for ForcedSnakeCaseData

{ @@ -127,17 +264,55 @@ impl Provider for ForcedSnakeCaseData

{ } fn data(&self) -> Result, Error> { - let mut map = Map::new(); - for (profile, dict) in self.0.data()? { + let mut map = self.0.data()?; + for (profile, dict) in &mut map { if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { // don't force snake case for keys in standalone sections - map.insert(profile, dict); continue; } - map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect()); + + if profile.as_str().as_str() == Config::PROFILE_SECTION { + // For the `[profile]` section, we need to preserve profile names (the keys) + // but snake_case the config keys within each profile's dict. + let dict2 = std::mem::take(dict); + *dict = dict2 + .into_iter() + .map(|(profile_name, v)| { + // Keep the profile name exactly as-is (e.g., "ci-venom" stays "ci-venom") + let v = snake_case_value_keys(v); + (profile_name, v) + }) + .collect(); + continue; + } + + let dict2 = std::mem::take(dict); + *dict = dict2.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect(); } Ok(map) } + + fn profile(&self) -> Option { + self.0.profile() + } +} + +/// Recursively converts all keys in a Value (if it's a Dict) to snake_case. +fn snake_case_value_keys(value: Value) -> Value { + match value { + Value::Dict(tag, dict) => { + let new_dict = dict + .into_iter() + .map(|(k, v)| (k.to_snake_case(), snake_case_value_keys(v))) + .collect(); + Value::Dict(tag, new_dict) + } + Value::Array(tag, arr) => { + let new_arr = arr.into_iter().map(snake_case_value_keys).collect(); + Value::Array(tag, new_arr) + } + other => other, + } } /// A Provider that handles breaking changes in toml files @@ -164,14 +339,20 @@ impl Provider for BackwardsCompatTomlProvider

{ dict.insert("solc".to_string(), v); } } - - if let Some(v) = dict.remove("odyssey") { - dict.insert("odyssey".to_string(), v); + if let Some(v) = dict.remove("deny_warnings") + && !dict.contains_key("deny") + { + dict.insert("deny".to_string(), v); } + map.insert(profile, dict); } Ok(map) } + + fn profile(&self) -> Option { + self.0.profile() + } } /// A provider that sets the `src` and `output` path depending on their existence. @@ -339,6 +520,7 @@ impl Provider for RenameProfileProvider

{ fn metadata(&self) -> Metadata { self.provider.metadata() } + fn data(&self) -> Result, Error> { let mut data = self.provider.data()?; if let Some(data) = data.remove(&self.from) { @@ -346,6 +528,7 @@ impl Provider for RenameProfileProvider

{ } Ok(Default::default()) } + fn profile(&self) -> Option { Some(self.to.clone()) } @@ -382,31 +565,32 @@ impl Provider for UnwrapProfileProvider

{ fn metadata(&self) -> Metadata { self.provider.metadata() } + fn data(&self) -> Result, Error> { - self.provider.data().and_then(|mut data| { - if let Some(profiles) = data.remove(&self.wrapping_key) { - for (profile_str, profile_val) in profiles { - let profile = Profile::new(&profile_str); - if profile != self.profile { - continue; - } - match profile_val { - Value::Dict(_, dict) => return Ok(profile.collect(dict)), - bad_val => { - let mut err = Error::from(figment::error::Kind::InvalidType( - bad_val.to_actual(), - "dict".into(), - )); - err.metadata = Some(self.provider.metadata()); - err.profile = Some(self.profile.clone()); - return Err(err); - } + let mut data = self.provider.data()?; + if let Some(profiles) = data.remove(&self.wrapping_key) { + for (profile_str, profile_val) in profiles { + let profile = Profile::new(&profile_str); + if profile != self.profile { + continue; + } + match profile_val { + Value::Dict(_, dict) => return Ok(profile.collect(dict)), + bad_val => { + let mut err = Error::from(figment::error::Kind::InvalidType( + bad_val.to_actual(), + "dict".into(), + )); + err.metadata = Some(self.provider.metadata()); + err.profile = Some(self.profile.clone()); + return Err(err); } } } - Ok(Default::default()) - }) + } + Ok(Default::default()) } + fn profile(&self) -> Option { Some(self.profile.clone()) } @@ -443,15 +627,18 @@ impl Provider for WrapProfileProvider

{ fn metadata(&self) -> Metadata { self.provider.metadata() } + fn data(&self) -> Result, Error> { if let Some(inner) = self.provider.data()?.remove(&self.profile) { let value = Value::from(inner); - let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect(); + let mut dict = Dict::new(); + dict.insert(self.profile.as_str().as_str().to_snake_case(), value); Ok(self.wrapping_key.collect(dict)) } else { Ok(Default::default()) } } + fn profile(&self) -> Option { Some(self.profile.clone()) } @@ -496,6 +683,7 @@ impl Provider for OptionalStrictProfileProvider

{ fn metadata(&self) -> Metadata { self.provider.metadata() } + fn data(&self) -> Result, Error> { let mut figment = Figment::from(&self.provider); for profile in &self.profiles { @@ -516,6 +704,7 @@ impl Provider for OptionalStrictProfileProvider

{ err }) } + fn profile(&self) -> Option { self.profiles.last().cloned() } @@ -542,13 +731,11 @@ impl Provider for FallbackProfileProvider

{ } fn data(&self) -> Result, Error> { - let data = self.provider.data()?; - if let Some(fallback) = data.get(&self.fallback) { - let mut inner = data.get(&self.profile).cloned().unwrap_or_default(); + let mut data = self.provider.data()?; + if let Some(fallback) = data.remove(&self.fallback) { + let mut inner = data.remove(&self.profile).unwrap_or_default(); for (k, v) in fallback { - if !inner.contains_key(k) { - inner.insert(k.to_owned(), v.clone()); - } + inner.entry(k).or_insert(v); } Ok(self.profile.collect(inner)) } else { diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index c6eb55fe99f43..88c0255217908 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -1,12 +1,13 @@ -use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; +use crate::{Config, foundry_toml_dirs, remappings_from_env_var, remappings_from_newline}; use figment::{ - value::{Dict, Map}, Error, Figment, Metadata, Profile, Provider, + value::{Dict, Map}, }; use foundry_compilers::artifacts::remappings::{RelativeRemapping, Remapping}; +use rayon::prelude::*; use std::{ borrow::Cow, - collections::{btree_map::Entry, BTreeMap, HashSet}, + collections::{BTreeMap, HashSet, btree_map::Entry}, fs, path::{Path, PathBuf}, }; @@ -23,27 +24,24 @@ pub struct Remappings { impl Remappings { /// Create a new `Remappings` wrapper with an empty vector. - pub fn new() -> Self { + pub const fn new() -> Self { Self { remappings: Vec::new(), project_paths: Vec::new() } } /// Create a new `Remappings` wrapper with a vector of remappings. - pub fn new_with_remappings(remappings: Vec) -> Self { + pub const fn new_with_remappings(remappings: Vec) -> Self { Self { remappings, project_paths: Vec::new() } } /// Extract project paths that cannot be remapped by dependencies. pub fn with_figment(mut self, figment: &Figment) -> Self { let mut add_project_remapping = |path: &str| { - if let Ok(path) = figment.find_value(path) { - if let Some(path) = path.into_string() { - let remapping = Remapping { - context: None, - name: format!("{path}/"), - path: format!("{path}/"), - }; - self.project_paths.push(remapping); - } + if let Ok(path) = figment.find_value(path) + && let Some(path) = path.into_string() + { + let remapping = + Remapping { context: None, name: format!("{path}/"), path: format!("{path}/") }; + self.project_paths.push(remapping); } }; add_project_remapping("src"); @@ -63,9 +61,7 @@ impl Remappings { /// Consumes the wrapper and returns the inner remappings vector. pub fn into_inner(self) -> Vec { let mut seen = HashSet::new(); - let remappings = - self.remappings.iter().filter(|r| seen.insert(Self::filter_key(r))).cloned().collect(); - remappings + self.remappings.iter().filter(|r| seen.insert(Self::filter_key(r))).cloned().collect() } /// Push an element to the remappings vector, but only if it's not already present. @@ -78,9 +74,9 @@ impl Remappings { if self.remappings.iter().any(|existing| { if remapping.name.ends_with(".sol") { // For .sol files, only prevent duplicate source names in the same context - return existing.name == remapping.name && - existing.context == remapping.context && - existing.path == remapping.path + return existing.name == remapping.name + && existing.context == remapping.context + && existing.path == remapping.path; } // What we're doing here is filtering for ambiguous paths. For example, if we have @@ -97,8 +93,8 @@ impl Remappings { if !existing_name_path.ends_with('/') { existing_name_path.push('/') } - let is_conflicting = remapping.name.starts_with(&existing_name_path) || - existing.name.starts_with(&remapping.name); + let is_conflicting = remapping.name.starts_with(&existing_name_path) + || existing.name.starts_with(&remapping.name); is_conflicting && existing.context == remapping.context }) { return; @@ -170,14 +166,15 @@ impl RemappingsProvider<'_> { ) { let context_mappings = mappings.entry(context).or_default(); match context_mappings.entry(key) { - Entry::Occupied(mut e) => { - if e.get().components().count() > path.components().count() { - e.insert(path); - } + Entry::Occupied(mut e) + if e.get().components().count() > path.components().count() => + { + e.insert(path); } Entry::Vacant(e) => { e.insert(path); } + _ => {} } } @@ -212,23 +209,20 @@ impl RemappingsProvider<'_> { // TODO: if a lib specifies contexts for remappings manually, we need to figure out how to // resolve that if self.auto_detect_remappings { + let (nested_foundry_remappings, auto_detected_remappings) = rayon::join( + || self.find_nested_foundry_remappings(), + || self.auto_detect_remappings(), + ); + let mut lib_remappings = BTreeMap::new(); - // find all remappings of from libs that use a foundry.toml - for r in self.lib_foundry_toml_remappings() { + for r in nested_foundry_remappings { insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); } - // use auto detection for all libs - for r in self - .lib_paths - .iter() - .map(|lib| self.root.join(lib)) - .inspect(|lib| trace!(?lib, "find all remappings")) - .flat_map(|lib| Remapping::find_many(&lib)) - { + for r in auto_detected_remappings { // this is an additional safety check for weird auto-detected remappings if ["lib/", "src/", "contracts/"].contains(&r.name.as_str()) { trace!(target: "forge", "- skipping the remapping"); - continue + continue; } insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); } @@ -251,51 +245,69 @@ impl RemappingsProvider<'_> { } /// Returns all remappings declared in foundry.toml files of libraries - fn lib_foundry_toml_remappings(&self) -> impl Iterator + '_ { + fn find_nested_foundry_remappings(&self) -> impl Iterator + '_ { self.lib_paths - .iter() + .par_iter() .map(|p| if p.is_absolute() { self.root.join("lib") } else { self.root.join(p) }) .flat_map(foundry_toml_dirs) - .inspect(|lib| { - trace!("find all remappings of nested foundry.toml lib: {:?}", lib); + .flat_map_iter(|lib| { + trace!(?lib, "find all remappings of nested foundry.toml"); + self.nested_foundry_remappings(&lib) }) - .flat_map(|lib: PathBuf| { - // load config, of the nested lib if it exists - let Ok(config) = Config::load_with_root(&lib) else { return vec![] }; - let config = config.sanitized(); - - // if the configured _src_ directory is set to something that - // [Remapping::find_many()] doesn't classify as a src directory (src, contracts, - // lib), then we need to manually add a remapping here - let mut src_remapping = None; - if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] - .contains(&config.src.as_path()) - { - if let Some(name) = lib.file_name().and_then(|s| s.to_str()) { - let mut r = Remapping { - context: None, - name: format!("{name}/"), - path: format!("{}", lib.join(&config.src).display()), - }; - if !r.path.ends_with('/') { - r.path.push('/') - } - src_remapping = Some(r); - } - } + .collect::>() + .into_iter() + } - // Eventually, we could set context for remappings at this location, - // taking into account the OS platform. We'll need to be able to handle nested - // contexts depending on dependencies for this to work. - // For now, we just leave the default context (none). - let mut remappings = - config.remappings.into_iter().map(Remapping::from).collect::>(); + fn nested_foundry_remappings(&self, lib: &Path) -> Vec { + // load config of the nested lib if it exists, using fallback mode since libs may not + // define all profiles the main project uses + let Ok(config) = Config::load_with_root_and_fallback(lib) else { return vec![] }; + let config = config.sanitized(); + + // if the configured _src_ directory is set to something that + // `Remapping::find_many` doesn't classify as a src directory (src, contracts, + // lib), then we need to manually add a remapping here + let src_remapping = if ![Path::new("src"), Path::new("contracts"), Path::new("lib")] + .contains(&config.src.as_path()) + && let Some(name) = lib.file_name().and_then(|s| s.to_str()) + { + let mut r = Remapping { + context: None, + name: format!("{name}/"), + path: format!("{}", lib.join(&config.src).display()), + }; + if !r.path.ends_with('/') { + r.path.push('/') + } + Some(r) + } else { + None + }; - if let Some(r) = src_remapping { - remappings.push(r); - } - remappings + // Eventually, we could set context for remappings at this location, + // taking into account the OS platform. We'll need to be able to handle nested + // contexts depending on dependencies for this to work. + // For now, we just leave the default context (none). + let mut remappings = + config.remappings.into_iter().map(Remapping::from).collect::>(); + + if let Some(r) = src_remapping { + remappings.push(r); + } + remappings + } + + /// Auto detect remappings from the lib paths + fn auto_detect_remappings(&self) -> impl Iterator + '_ { + self.lib_paths + .par_iter() + .flat_map_iter(|lib| { + let lib = self.root.join(lib); + trace!(?lib, "find all remappings"); + Remapping::find_many(&lib) }) + .collect::>() + .into_iter() } } @@ -311,7 +323,7 @@ impl Provider for RemappingsProvider<'_> { if let figment::error::Kind::MissingField(_) = err.kind { self.get_remappings(vec![]) } else { - return Err(err.clone()) + return Err(err.clone()); } } }?; @@ -453,11 +465,15 @@ mod tests { let result = remappings.into_inner(); assert_eq!(result.len(), 2, "Should allow same name with different contexts"); - assert!(result - .iter() - .any(|r| r.context == Some("test/".to_string()) && r.path == "test/Contract.sol")); - assert!(result - .iter() - .any(|r| r.context == Some("prod/".to_string()) && r.path == "prod/Contract.sol")); + assert!( + result + .iter() + .any(|r| r.context == Some("test/".to_string()) && r.path == "test/Contract.sol") + ); + assert!( + result + .iter() + .any(|r| r.context == Some("prod/".to_string()) && r.path == "prod/Contract.sol") + ); } } diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs index 944225be18c2c..ff1d0b35def47 100644 --- a/crates/config/src/providers/warnings.rs +++ b/crates/config/src/providers/warnings.rs @@ -1,9 +1,47 @@ -use crate::{Config, Warning, DEPRECATIONS}; +use crate::{Config, DEPRECATIONS, Warning}; use figment::{ - value::{Dict, Map, Value}, Error, Figment, Metadata, Profile, Provider, + value::{Dict, Map, Value}, }; -use std::collections::BTreeMap; +use heck::ToSnakeCase; +use std::collections::{BTreeMap, BTreeSet}; + +/// Allowed keys for CompilationRestrictions. +const COMPILATION_RESTRICTIONS_KEYS: &[&str] = &[ + "paths", + "version", + "via_ir", + "bytecode_hash", + "min_optimizer_runs", + "optimizer_runs", + "max_optimizer_runs", + "min_evm_version", + "evm_version", + "max_evm_version", +]; + +/// Allowed keys for SettingsOverrides. +const SETTINGS_OVERRIDES_KEYS: &[&str] = + &["name", "via_ir", "evm_version", "optimizer", "optimizer_runs", "bytecode_hash"]; + +/// Allowed keys for VyperConfig. +/// Required because VyperConfig uses `skip_serializing_if = "Option::is_none"` on all fields, +/// causing the default serialization to produce an empty dict. +const VYPER_KEYS: &[&str] = &["optimize", "path", "experimental_codegen"]; + +/// Allowed keys for DocConfig. +/// Required because DocConfig uses `skip_serializing_if = "Option::is_none"` on some fields +/// (`repository`, `path`), whose defaults are `None` and thus excluded from serialization. +const DOC_KEYS: &[&str] = &["out", "title", "book", "homepage", "repository", "path", "ignore"]; + +/// Reserved keys that should not trigger unknown key warnings. +const RESERVED_KEYS: &[&str] = &["extends"]; + +/// Keys kept for backward compatibility that should not trigger unknown key warnings. +/// +/// `tempo` and `optimism` are legacy aliases for `network = "tempo"` / `network = "optimism"` — +/// still accepted on input but no longer serialized in the default config. +const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version", "tempo", "optimism"]; /// Generate warnings for unknown sections and deprecated keys pub struct WarningsProvider

{ @@ -44,46 +82,230 @@ impl WarningsProvider

{ let mut out = self.old_warnings.clone()?; // Add warning for unknown sections. - out.extend( - data.keys() - .filter(|k| { - **k != Config::PROFILE_SECTION && - !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) - }) - .map(|unknown_section| { - let source = self.provider.metadata().source.map(|s| s.to_string()); - Warning::UnknownSection { unknown_section: unknown_section.clone(), source } - }), - ); + out.extend(data.keys().filter(|k| !Config::is_standalone_section(k.as_str())).map( + |unknown_section| { + let source = self.provider.metadata().source.map(|s| s.to_string()); + Warning::UnknownSection { unknown_section: unknown_section.clone(), source } + }, + )); // Add warning for deprecated keys. let deprecated_key_warning = |key| { DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { - if key == *deprecated_key { - Some(Warning::DeprecatedKey { - old: deprecated_key.to_string(), - new: new_value.to_string(), - }) - } else { - None - } + (key == *deprecated_key).then(|| Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), + }) }) }; let profiles = data .iter() .filter(|(profile, _)| **profile == Config::PROFILE_SECTION) .map(|(_, dict)| dict); + out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning)); out.extend( profiles + .clone() .filter_map(|dict| dict.get(self.profile.as_str().as_str())) .filter_map(Value::as_dict) .flat_map(BTreeMap::keys) .filter_map(deprecated_key_warning), ); + // Add warning for unknown keys within profiles (root keys only here). + if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data() + && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE) + { + let allowed_keys: BTreeSet = default_dict.keys().cloned().collect(); + for profile_map in profiles.clone() { + for (profile, value) in profile_map { + let Some(profile_dict) = value.as_dict() else { + continue; + }; + + let source = self + .provider + .metadata() + .source + .map(|s| s.to_string()) + .unwrap_or(Config::FILE_NAME.to_string()); + for key in profile_dict.keys() { + let is_not_deprecated = + !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key); + let is_not_allowed = !allowed_keys.contains(key) + && !allowed_keys.contains(&key.to_snake_case()); + let is_not_reserved = + !RESERVED_KEYS.contains(&key.as_str()) && key != Self::WARNINGS_KEY; + let is_not_backward_compatible = + !BACKWARD_COMPATIBLE_KEYS.contains(&key.as_str()); + + if is_not_deprecated + && is_not_allowed + && is_not_reserved + && is_not_backward_compatible + { + out.push(Warning::UnknownKey { + key: key.clone(), + profile: profile.clone(), + source: source.clone(), + }); + } + } + + // Add warning for unknown keys in nested sections within profiles. + self.collect_nested_section_warnings( + profile_dict, + default_dict, + &source, + &mut out, + ); + } + } + + // Add warning for unknown keys in standalone sections. + self.collect_standalone_section_warnings(&data, default_dict, &mut out); + } + Ok(out) } + + /// Collects warnings for unknown keys in standalone sections like `[lint]`, `[fmt]`, etc. + fn collect_standalone_section_warnings( + &self, + data: &Map, + default_dict: &Dict, + out: &mut Vec, + ) { + let source = self + .provider + .metadata() + .source + .map(|s| s.to_string()) + .unwrap_or(Config::FILE_NAME.to_string()); + + for section_name in Config::STANDALONE_SECTIONS { + // Get the section from the parsed data + let section_profile = Profile::new(section_name); + let Some(section_dict) = data.get(§ion_profile) else { + continue; + }; + + // Get allowed keys for this section from the default config + // Special case for vyper: VyperConfig uses skip_serializing_if on all Option fields, + // so the default serialization produces an empty dict. Use explicit keys instead. + let allowed_keys: BTreeSet = if *section_name == "vyper" { + VYPER_KEYS.iter().map(|s| s.to_string()).collect() + } else if *section_name == "doc" { + DOC_KEYS.iter().map(|s| s.to_string()).collect() + } else { + let Some(default_section_value) = default_dict.get(*section_name) else { + continue; + }; + let Some(default_section_dict) = default_section_value.as_dict() else { + continue; + }; + default_section_dict.keys().cloned().collect() + }; + + for key in section_dict.keys() { + let is_not_allowed = + !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: key.clone(), + section: section_name.to_string(), + source: source.clone(), + }); + } + } + } + } + + /// Collects warnings for unknown keys in nested sections within profiles, + /// like `compilation_restrictions`. + fn collect_nested_section_warnings( + &self, + profile_dict: &Dict, + default_dict: &Dict, + source: &str, + out: &mut Vec, + ) { + // Check nested sections that are dicts (like `lint`, `fmt` when defined in profile) + for (key, value) in profile_dict { + let Some(nested_dict) = value.as_dict() else { + // Also check arrays of dicts (like `compilation_restrictions`) + if let Some(arr) = value.as_array() { + // Get allowed keys for known array item types + let allowed_keys = Self::get_array_item_allowed_keys(key); + + if allowed_keys.is_empty() { + continue; + } + + for item in arr { + let Some(item_dict) = item.as_dict() else { + continue; + }; + for item_key in item_dict.keys() { + let is_not_allowed = !allowed_keys.contains(item_key) + && !allowed_keys.contains(&item_key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: item_key.clone(), + section: key.clone(), + source: source.to_string(), + }); + } + } + } + } + continue; + }; + + // Get allowed keys from the default config for this nested section + // Special case for vyper: VyperConfig uses skip_serializing_if on all Option fields, + // so the default serialization produces an empty dict. Use explicit keys instead. + let allowed_keys: BTreeSet = if key == "vyper" { + VYPER_KEYS.iter().map(|s| s.to_string()).collect() + } else if key == "doc" { + DOC_KEYS.iter().map(|s| s.to_string()).collect() + } else { + let Some(default_value) = default_dict.get(key) else { + continue; + }; + let Some(default_nested_dict) = default_value.as_dict() else { + continue; + }; + default_nested_dict.keys().cloned().collect() + }; + + for nested_key in nested_dict.keys() { + let is_not_allowed = !allowed_keys.contains(nested_key) + && !allowed_keys.contains(&nested_key.to_snake_case()); + if is_not_allowed { + out.push(Warning::UnknownSectionKey { + key: nested_key.clone(), + section: key.clone(), + source: source.to_string(), + }); + } + } + } + } + + /// Returns the allowed keys for array item types based on the section name. + fn get_array_item_allowed_keys(section_name: &str) -> BTreeSet { + match section_name { + "compilation_restrictions" => { + COMPILATION_RESTRICTIONS_KEYS.iter().map(|s| s.to_string()).collect() + } + "additional_compiler_profiles" => { + SETTINGS_OVERRIDES_KEYS.iter().map(|s| s.to_string()).collect() + } + _ => BTreeSet::new(), + } + } } impl Provider for WarningsProvider

{ diff --git a/crates/config/src/soldeer.rs b/crates/config/src/soldeer.rs index 24ecce0955f12..872748c8d317e 100644 --- a/crates/config/src/soldeer.rs +++ b/crates/config/src/soldeer.rs @@ -28,11 +28,15 @@ pub struct MapDependency { /// The git tag in case git is used as dependency source #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, + + /// An optional relative path to the project's root within the repository + #[serde(default, skip_serializing_if = "Option::is_none")] + pub project_root: Option, } /// Type for Soldeer configs, under dependencies tag in the foundry.toml #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct SoldeerDependencyConfig(BTreeMap); +pub struct SoldeerDependencyConfig(pub BTreeMap); impl AsRef for SoldeerDependencyConfig { fn as_ref(&self) -> &Self { diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index bcb20ab698afb..f834637848a16 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -3,12 +3,8 @@ use crate::Config; use alloy_primitives::U256; use figment::value::Value; -use foundry_compilers::artifacts::{ - remappings::{Remapping, RemappingError}, - EvmVersion, -}; -use revm::primitives::hardfork::SpecId; -use serde::{de::Error, Deserialize, Deserializer}; +use foundry_compilers::artifacts::remappings::{Remapping, RemappingError}; +use serde::{Deserialize, Deserializer, Serializer, de::Error}; use std::{ io, path::{Path, PathBuf}, @@ -35,7 +31,7 @@ pub fn load_config_with_root(root: Option<&Path>) -> eyre::Result { pub fn find_git_root(relative_to: &Path) -> io::Result> { let root = if relative_to.is_absolute() { relative_to } else { &dunce::canonicalize(relative_to)? }; - Ok(root.ancestors().find(|p| p.join(".git").is_dir()).map(Path::to_path_buf)) + Ok(root.ancestors().find(|p| p.join(".git").exists()).map(Path::to_path_buf)) } /// Returns the root path to set for the project root. @@ -213,6 +209,64 @@ where deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom) } +/// Serialize a `usize` as `"max"` if it equals `usize::MAX`, as a string if it exceeds +/// `i64::MAX` (TOML integer limit), or as a plain number otherwise. +pub(crate) fn serialize_usize_or_max(value: &usize, serializer: S) -> Result +where + S: Serializer, +{ + if *value == usize::MAX { + serializer.serialize_str("max") + } else if *value > i64::MAX as usize { + serializer.serialize_str(&value.to_string()) + } else { + serializer.serialize_u64(*value as u64) + } +} + +/// Deserialize into `U256` from either a `u64`, a `U256` hex string, or a decimal string. +pub fn deserialize_u64_to_u256<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum NumericValue { + U256(U256), + U64(u64), + String(String), + } + + match NumericValue::deserialize(deserializer)? { + NumericValue::U64(n) => Ok(U256::from(n)), + NumericValue::U256(n) => Ok(n), + NumericValue::String(s) => { + // Handle decimal strings (e.g., "18446744073709551615") + U256::from_str(&s).map_err(D::Error::custom) + } + } +} + +/// Serialize `U256` as `u64` if it fits, otherwise as a hex string. +/// If the number fits into a i64, serialize it as number without quotation marks. +/// If the number fits into a u64, serialize it as a stringified number with quotation marks. +/// Otherwise, serialize it as a hex string with quotation marks. +pub fn serialize_u64_or_u256(n: &U256, serializer: S) -> Result +where + S: Serializer, +{ + // The TOML specification handles integers as i64 so the number representation is limited to + // i64. If the number is larger than `i64::MAX` and up to `u64::MAX`, we serialize it as a + // string to avoid losing precision. + if let Ok(n_i64) = i64::try_from(*n) { + serializer.serialize_i64(n_i64) + } else if let Ok(n_u64) = u64::try_from(*n) { + serializer.serialize_str(&n_u64.to_string()) + } else { + serializer.serialize_str(&format!("{n:#x}")) + } +} + /// Helper type to parse both `u64` and `U256` #[derive(Clone, Copy, Deserialize)] #[serde(untagged)] @@ -243,27 +297,3 @@ impl FromStr for Numeric { } } } - -/// Returns the [SpecId] derived from [EvmVersion] -#[inline] -pub fn evm_spec_id(evm_version: EvmVersion, odyssey: bool) -> SpecId { - if odyssey { - return SpecId::OSAKA; - } - match evm_version { - EvmVersion::Homestead => SpecId::HOMESTEAD, - EvmVersion::TangerineWhistle => SpecId::TANGERINE, - EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - EvmVersion::Byzantium => SpecId::BYZANTIUM, - EvmVersion::Constantinople => SpecId::CONSTANTINOPLE, - EvmVersion::Petersburg => SpecId::PETERSBURG, - EvmVersion::Istanbul => SpecId::ISTANBUL, - EvmVersion::Berlin => SpecId::BERLIN, - EvmVersion::London => SpecId::LONDON, - EvmVersion::Paris => SpecId::MERGE, - EvmVersion::Shanghai => SpecId::SHANGHAI, - EvmVersion::Cancun => SpecId::CANCUN, - EvmVersion::Prague => SpecId::PRAGUE, - EvmVersion::Osaka => SpecId::OSAKA, - } -} diff --git a/crates/config/src/warning.rs b/crates/config/src/warning.rs index a19104eaf7b4d..32a1183208892 100644 --- a/crates/config/src/warning.rs +++ b/crates/config/src/warning.rs @@ -46,6 +46,30 @@ pub enum Warning { /// is being removed completely without replacement new: String, }, + /// An unknown key was encountered in a profile in a TOML file + UnknownKey { + /// The unknown key name + key: String, + /// The profile where the key was found, if applicable + profile: String, + /// The config file where the key was found + source: String, + }, + /// An unknown key was encountered in a section in a TOML file + UnknownSectionKey { + /// The unknown key name + key: String, + /// The section where the key was found + section: String, + /// The config file where the key was found + source: String, + }, + /// The selected profile (via `FOUNDRY_PROFILE` or otherwise) does not exist in the config. + /// Falls back to the default profile. + UnknownProfile { + /// The selected profile that does not exist + profile: String, + }, } impl fmt::Display for Warning { @@ -82,7 +106,28 @@ impl fmt::Display for Warning { write!(f, "Key `{old}` is being deprecated and will be removed in future versions.") } Self::DeprecatedKey { old, new } => { - write!(f, "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.") + write!( + f, + "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions." + ) + } + Self::UnknownKey { key, profile, source } => { + write!( + f, + "Found unknown `{key}` config for profile `{profile}` defined in {source}." + ) + } + Self::UnknownSectionKey { key, section, source } => { + write!( + f, + "Found unknown `{key}` config key in section `{section}` defined in {source}." + ) + } + Self::UnknownProfile { profile } => { + write!( + f, + "Selected profile `{profile}` does not exist; falling back to the default profile." + ) } } } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 4cf86e20a5045..cc3dabd32d4bf 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -21,11 +21,19 @@ revm-inspectors.workspace = true alloy-primitives.workspace = true -crossterm = "0.28" +crossterm = "0.29" eyre.workspace = true -ratatui = { version = "0.29", default-features = false, features = [ +ratatui = { version = "0.30", default-features = false, features = [ "crossterm", ] } revm.workspace = true tracing.workspace = true serde.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/debugger/src/builder.rs b/crates/debugger/src/builder.rs index fd2dce3dfe099..832617291dc63 100644 --- a/crates/debugger/src/builder.rs +++ b/crates/debugger/src/builder.rs @@ -1,9 +1,11 @@ //! Debugger builder. -use crate::{node::flatten_call_trace, DebugNode, Debugger}; -use alloy_primitives::{map::AddressHashMap, Address}; -use foundry_common::{evm::Breakpoints, get_contract_name}; -use foundry_evm_traces::{debug::ContractSources, CallTraceArena, CallTraceDecoder, Traces}; +use crate::{DebugNode, Debugger, node::flatten_call_trace}; +use alloy_primitives::{Address, map::AddressHashMap}; +use foundry_common::get_contract_name; +use foundry_evm_core::Breakpoints; +use foundry_evm_traces::{CallTraceArena, CallTraceDecoder, Traces, debug::ContractSources}; + /// Debugger builder. #[derive(Debug, Default)] #[must_use = "builders do nothing unless you call `build` on them"] diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs index 907232cad7e98..95d836ada6856 100644 --- a/crates/debugger/src/debugger.rs +++ b/crates/debugger/src/debugger.rs @@ -1,9 +1,9 @@ //! Debugger implementation. -use crate::{tui::TUI, DebugNode, DebuggerBuilder, ExitReason}; +use crate::{DebugNode, DebuggerBuilder, ExitReason, tui::TUI}; use alloy_primitives::map::AddressHashMap; use eyre::Result; -use foundry_common::evm::Breakpoints; +use foundry_evm_core::Breakpoints; use foundry_evm_traces::debug::ContractSources; use std::path::Path; @@ -27,7 +27,7 @@ impl Debugger { } /// Creates a new debugger. - pub fn new( + pub const fn new( debug_arena: Vec, identified_contracts: AddressHashMap, contracts_sources: ContractSources, diff --git a/crates/debugger/src/dump.rs b/crates/debugger/src/dump.rs index 2d50b4079ed4b..6a910d26f8de9 100644 --- a/crates/debugger/src/dump.rs +++ b/crates/debugger/src/dump.rs @@ -1,4 +1,4 @@ -use crate::{debugger::DebuggerContext, DebugNode}; +use crate::{DebugNode, debugger::DebuggerContext}; use alloy_primitives::map::AddressMap; use foundry_common::fs::write_json_file; use foundry_compilers::{ diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index 1c1bf9614ee2e..322320af3c5fa 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -3,7 +3,7 @@ //! Interactive Solidity TUI debugger and debugger data file dumper #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs index 59e28d553abde..9e84b847c5329 100644 --- a/crates/debugger/src/node.rs +++ b/crates/debugger/src/node.rs @@ -14,19 +14,22 @@ pub struct DebugNode { pub kind: CallKind, /// Calldata of the call. pub calldata: Bytes, + /// The gas limit of the call. + pub gas_limit: u64, /// The debug steps. pub steps: Vec, } impl DebugNode { /// Creates a new debug node. - pub fn new( + pub const fn new( address: Address, kind: CallKind, steps: Vec, calldata: Bytes, + gas_limit: u64, ) -> Self { - Self { address, kind, steps, calldata } + Self { address, kind, steps, calldata, gas_limit } } } @@ -73,12 +76,12 @@ pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec) { // Skip nodes with empty steps as there's nothing to display for them. if steps.is_empty() { - continue + continue; } let call = &arena_nodes[pending.node_idx].trace; let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() }; - let node = DebugNode::new(call.address, call.kind, steps, calldata); + let node = DebugNode::new(call.address, call.kind, steps, calldata, call.gas_limit); out.push(node); } diff --git a/crates/debugger/src/op.rs b/crates/debugger/src/op.rs index 8e2edce964ae9..8abf33d6f533d 100644 --- a/crates/debugger/src/op.rs +++ b/crates/debugger/src/op.rs @@ -1,6 +1,3 @@ -use alloy_primitives::Bytes; -use revm::bytecode::opcode; - /// Named parameter of an EVM opcode. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct OpcodeParam { @@ -14,32 +11,8 @@ impl OpcodeParam { /// Returns the list of named parameters for the given opcode, accounts for special opcodes /// requiring immediate bytes to determine stack items. #[inline] - pub(crate) fn of(op: u8, immediate: Option<&Bytes>) -> Option> { - match op { - // Handle special cases requiring immediate bytes - opcode::DUPN => immediate - .and_then(|i| i.first().copied()) - .map(|i| vec![Self { name: "dup_value", index: i as usize }]), - opcode::SWAPN => immediate.and_then(|i| { - i.first().map(|i| { - vec![ - Self { name: "a", index: 1 }, - Self { name: "swap_value", index: *i as usize }, - ] - }) - }), - opcode::EXCHANGE => immediate.and_then(|i| { - i.first().map(|imm| { - let n = (imm >> 4) + 1; - let m = (imm & 0xf) + 1; - vec![ - Self { name: "value1", index: n as usize }, - Self { name: "value2", index: m as usize }, - ] - }) - }), - _ => Some(MAP[op as usize].to_vec()), - } + pub(crate) fn of(op: u8) -> &'static [Self] { + MAP[op as usize] } } @@ -68,16 +41,21 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { } // https://www.evm.codes - // https://github.com/smlxl/evm.codes - // https://github.com/klkvr/evm.codes - // https://github.com/klkvr/evm.codes/blob/HEAD/opcodes.json - // jq -rf opcodes.jq opcodes.json - /* - def mkargs(input): - input | split(" | ") | to_entries | map("\(.key): \"\(.value)\"") | join(", "); - - to_entries[] | "0x\(.key)(\(mkargs(.value.input)))," - */ + // https://raw.githubusercontent.com/duneanalytics/evm.codes/refs/heads/main/opcodes.json + // + // jq -r ' + // def mkargs(input): + // input + // | split(" | ") + // | to_entries + // | map("\(.key): \"\(.value)\"") + // | join(", "); + // to_entries[] + // | "0x\(.key)(\(mkargs(.value.input)))," + // ' opcodes.json + // + // NOTE: the labels generated for `DUPN` and `SWAPN` have incorrect indices and have been + // manually adjusted in the `map!` macro below. map! { 0x00(), 0x01(0: "a", 1: "b"), @@ -152,7 +130,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x46(), 0x47(), 0x48(), - 0x49(), + 0x49(0: "index"), 0x4a(), 0x4b(), 0x4c(), @@ -171,11 +149,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x59(), 0x5a(), 0x5b(), - 0x5c(), - 0x5d(), - 0x5e(), - - // PUSHN + 0x5c(0: "key"), + 0x5d(0: "key", 1: "value"), + 0x5e(0: "destOffset", 1: "offset", 2: "size"), 0x5f(), 0x60(), 0x61(), @@ -297,7 +273,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xd0(0: "offset"), 0xd1(), 0xd2(), - 0xd3(0: "memOffset", 1: "offset", 2: "size"), + 0xd3(0: "mem_offset", 1: "offset", 2: "size"), 0xd4(), 0xd5(), 0xd6(), @@ -322,9 +298,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xe9(), 0xea(), 0xeb(), - 0xec(0: "value", 1: "salt", 2: "offset", 3: "size"), + 0xec(0: "value", 1: "salt", 2: "input_offset", 3: "input_size"), 0xed(), - 0xee(0: "offset", 1: "size"), + 0xee(0: "aux_data_offset", 1: "aux_data_size"), 0xef(), 0xf0(0: "value", 1: "offset", 2: "size"), 0xf1(0: "gas", 1: "address", 2: "value", 3: "argsOffset", 4: "argsSize", 5: "retOffset", 6: "retSize"), @@ -334,10 +310,10 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xf5(0: "value", 1: "offset", 2: "size", 3: "salt"), 0xf6(), 0xf7(0: "offset"), - 0xf8(0: "address", 1: "argsOffset", 2: "argsSize", 3: "value"), - 0xf9(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xf8(0: "target_address", 1: "input_offset", 2: "input_size", 3: "value"), + 0xf9(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfa(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), - 0xfb(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xfb(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfc(), 0xfd(0: "offset", 1: "size"), 0xfe(), diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index a2a4c987a78d6..c3f38b681aac1 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -1,7 +1,7 @@ //! Debugger context and event handler implementation. -use crate::{debugger::DebuggerContext, DebugNode, ExitReason}; -use alloy_primitives::{hex, Address}; +use crate::{DebugNode, ExitReason, debugger::DebuggerContext}; +use alloy_primitives::{Address, hex}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; use foundry_evm_core::buffer::BufferKind; use revm::bytecode::opcode::OpCode; @@ -124,11 +124,12 @@ impl TUIContext<'_> { fn handle_key_event(&mut self, event: KeyEvent) -> ControlFlow { // Breakpoints - if let KeyCode::Char(c) = event.code { - if c.is_alphabetic() && self.key_buffer.starts_with('\'') { - self.handle_breakpoint(c); - return ControlFlow::Continue(()); - } + if let KeyCode::Char(c) = event.code + && c.is_alphabetic() + && self.key_buffer.starts_with('\'') + { + self.handle_breakpoint(c); + return ControlFlow::Continue(()); } let control = event.modifiers.contains(KeyModifiers::CONTROL); @@ -195,11 +196,11 @@ impl TUIContext<'_> { } // Go to next call - KeyCode::Char('C') => { - if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 { - self.draw_memory.inner_call_index += 1; - self.current_step = 0; - } + KeyCode::Char('C') + if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 => + { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; } // Step forward @@ -262,12 +263,12 @@ impl TUIContext<'_> { // this pc) if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) { for (i, node) in self.debug_arena().iter().enumerate() { - if node.address == *caller { - if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) { - self.draw_memory.inner_call_index = i; - self.current_step = step; - break; - } + if node.address == *caller + && let Some(step) = node.steps.iter().position(|step| step.pc == *pc) + { + self.draw_memory.inner_call_index = i; + self.current_step = step; + break; } } } @@ -330,25 +331,11 @@ fn pretty_opcode(step: &CallTraceStep) -> String { } fn is_jump(step: &CallTraceStep, prev: &CallTraceStep) -> bool { - if !matches!( - prev.op, - OpCode::JUMP | - OpCode::JUMPI | - OpCode::JUMPF | - OpCode::RJUMP | - OpCode::RJUMPI | - OpCode::RJUMPV | - OpCode::CALLF | - OpCode::RETF - ) { - return false + if !matches!(prev.op, OpCode::JUMP | OpCode::JUMPI) { + return false; } let immediate_len = prev.immediate_bytes.as_ref().map_or(0, |b| b.len()); - if step.pc != prev.pc + 1 + immediate_len { - true - } else { - step.code_section_idx != prev.code_section_idx - } + step.pc != prev.pc + 1 + immediate_len } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 580d6f958ac44..5b6dceac4c890 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -3,14 +3,14 @@ use super::context::TUIContext; use crate::op::OpcodeParam; use foundry_compilers::artifacts::sourcemap::SourceElement; -use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; +use foundry_evm_core::buffer::{BufferKind, get_buffer_accesses}; use foundry_evm_traces::debug::SourceData; use ratatui::{ + Frame, layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span, Text}, widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}, - Frame, }; use revm_inspectors::tracing::types::CallKind; use std::{collections::VecDeque, fmt::Write, io}; @@ -21,7 +21,6 @@ impl TUIContext<'_> { terminal.draw(|f| self.draw_layout(f)).map(drop) } - #[inline] fn draw_layout(&self, f: &mut Frame<'_>) { // We need 100 columns to display a 32 byte word in the memory and stack panes. let area = f.area(); @@ -291,11 +290,11 @@ impl TUIContext<'_> { lines.push(u_num, line, u_text); } - let first = if !last_has_nl { + let first = if last_has_nl { + 0 + } else { lines.push_raw(h_num, &[Span::raw(last), Span::styled(actual[0], h_text)]); 1 - } else { - 0 }; // Skip the first line if it has already been handled above. @@ -310,14 +309,12 @@ impl TUIContext<'_> { } // Fill in the rest of the line as unhighlighted. - if let Some(last) = actual.last() { - if !last.ends_with('\n') { - if let Some(post) = after.pop_front() { - if let Some(last) = lines.lines.last_mut() { - last.spans.push(Span::raw(post)); - } - } - } + if let Some(last) = actual.last() + && !last.ends_with('\n') + && let Some(post) = after.pop_front() + && let Some(last) = lines.lines.last_mut() + { + last.spans.push(Span::raw(post)); } // Add after highlighted text. @@ -375,11 +372,11 @@ impl TUIContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {} | Code section: {}", + "Address: {} | PC: {} | Gas used: {} | Gas refund: {}", self.address(), self.current_step().pc, - self.current_step().gas_used, - self.current_step().code_section_idx, + self.debug_call().gas_limit - self.current_step().gas_remaining, + self.current_step().gas_refund_counter ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) @@ -398,7 +395,7 @@ impl TUIContext<'_> { let min_len = decimal_digits(stack_len).max(2); - let params = OpcodeParam::of(step.op.get(), step.immediate_bytes.as_ref()); + let params = OpcodeParam::of(step.op.get()); let text: Vec> = stack .map(|stack| { @@ -408,10 +405,7 @@ impl TUIContext<'_> { .enumerate() .skip(self.draw_memory.current_stack_startline) .map(|(i, stack_item)| { - let param = params - .as_ref() - .and_then(|params| params.iter().find(|param| param.index == i)); - + let param = params.iter().find(|param| param.index == i); let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); // Stack index. @@ -429,11 +423,11 @@ impl TUIContext<'_> { } }); - if self.stack_labels { - if let Some(param) = param { - spans.push(Span::raw("| ")); - spans.push(Span::raw(param.name)); - } + if self.stack_labels + && let Some(param) = param + { + spans.push(Span::raw("| ")); + spans.push(Span::raw(param.name)); } spans.push(Span::raw("\n")); @@ -468,21 +462,20 @@ impl TUIContext<'_> { let mut write_size = None; let mut color = None; let stack_len = step.stack.as_ref().map_or(0, |s| s.len()); - if stack_len > 0 { - if let Some(stack) = step.stack.as_ref() { - if let Some(accesses) = get_buffer_accesses(step.op.get(), stack) { - if let Some(read_access) = accesses.read { - offset = Some(read_access.1.offset); - len = Some(read_access.1.len); - color = Some(Color::Cyan); - } - if let Some(write_access) = accesses.write { - if self.active_buffer == BufferKind::Memory { - write_offset = Some(write_access.offset); - write_size = Some(write_access.len); - } - } - } + if stack_len > 0 + && let Some(stack) = step.stack.as_ref() + && let Some(accesses) = get_buffer_accesses(step.op.get(), stack) + { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + len = Some(read_access.1.len); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write + && self.active_buffer == BufferKind::Memory + { + write_offset = Some(write_access.offset); + write_size = Some(write_access.len); } } @@ -493,16 +486,14 @@ impl TUIContext<'_> { if self.current_step > 0 { let prev_step = self.current_step - 1; let prev_step = &self.debug_steps()[prev_step]; - if let Some(stack) = prev_step.stack.as_ref() { - if let Some(write_access) = + if let Some(stack) = prev_step.stack.as_ref() + && let Some(write_access) = get_buffer_accesses(prev_step.op.get(), stack).and_then(|a| a.write) - { - if self.active_buffer == BufferKind::Memory { - offset = Some(write_access.offset); - len = Some(write_access.len); - color = Some(Color::Green); - } - } + && self.active_buffer == BufferKind::Memory + { + offset = Some(write_access.offset); + len = Some(write_access.len); + color = Some(Color::Green); } } diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index ba27243256705..9f4a445a6fb45 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -1,22 +1,16 @@ -//! The TUI implementation. +//! The debugger TUI. use crossterm::{ - event::{self, DisableMouseCapture, EnableMouseCapture, Event}, + event::{self, DisableMouseCapture, EnableMouseCapture}, execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use eyre::Result; use ratatui::{ - backend::{Backend, CrosstermBackend}, Terminal, + backend::{Backend, CrosstermBackend}, }; -use std::{ - io, - ops::ControlFlow, - sync::{mpsc, Arc}, - thread, - time::{Duration, Instant}, -}; +use std::{io, ops::ControlFlow, sync::Arc}; mod context; use crate::debugger::DebuggerContext; @@ -33,14 +27,14 @@ pub enum ExitReason { CharExit, } -/// The TUI debugger. +/// The debugger TUI. pub struct TUI<'a> { debugger_context: &'a mut DebuggerContext, } impl<'a> TUI<'a> { /// Creates a new debugger. - pub fn new(debugger_context: &'a mut DebuggerContext) -> Self { + pub const fn new(debugger_context: &'a mut DebuggerContext) -> Self { Self { debugger_context } } @@ -48,60 +42,24 @@ impl<'a> TUI<'a> { pub fn try_run(&mut self) -> Result { let backend = CrosstermBackend::new(io::stdout()); let terminal = Terminal::new(backend)?; - TerminalGuard::with(terminal, |terminal| self.try_run_real(terminal)) + TerminalGuard::with(terminal, |terminal| self.run_inner(terminal)) } #[instrument(target = "debugger", name = "run", skip_all, ret)] - fn try_run_real(&mut self, terminal: &mut DebuggerTerminal) -> Result { - // Create the context. + fn run_inner(&mut self, terminal: &mut DebuggerTerminal) -> Result { let mut cx = TUIContext::new(self.debugger_context); - cx.init(); - - // Create an event listener in a different thread. - let (tx, rx) = mpsc::channel(); - thread::Builder::new() - .name("event-listener".into()) - .spawn(move || Self::event_listener(tx)) - .expect("failed to spawn thread"); - - // Start the event loop. loop { cx.draw(terminal)?; - match cx.handle_event(rx.recv()?) { + match cx.handle_event(event::read()?) { ControlFlow::Continue(()) => {} ControlFlow::Break(reason) => return Ok(reason), } } } - - fn event_listener(tx: mpsc::Sender) { - // This is the recommend tick rate from `ratatui`, based on their examples - let tick_rate = Duration::from_millis(200); - - let mut last_tick = Instant::now(); - loop { - // Poll events since last tick - if last tick is greater than tick_rate, we - // demand immediate availability of the event. This may affect interactivity, - // but I'm not sure as it is hard to test. - if event::poll(tick_rate.saturating_sub(last_tick.elapsed())).unwrap() { - let event = event::read().unwrap(); - if tx.send(event).is_err() { - return; - } - } - - // Force update if time has passed - if last_tick.elapsed() > tick_rate { - last_tick = Instant::now(); - } - } - } } -// TODO: Update once on 1.82 -#[expect(deprecated)] -type PanicHandler = Box) + 'static + Sync + Send>; +type PanicHandler = Box) + 'static + Sync + Send>; /// Handles terminal state. #[must_use] diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 95e26df179cc4..814beab402729 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -14,22 +14,25 @@ repository.workspace = true workspace = true [dependencies] -forge-fmt.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +solar.workspace = true alloy-primitives.workspace = true derive_more.workspace = true eyre.workspace = true itertools.workspace = true -mdbook = { version = "0.4", default-features = false, features = ["search"] } +mdbook-driver = { version = "0.5", default-features = false, features = ["search"] } rayon.workspace = true serde_json.workspace = true serde.workspace = true -solang-parser.workspace = true thiserror.workspace = true toml.workspace = true tracing.workspace = true regex.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/doc/README.md b/crates/doc/README.md index 9e9c638aadd9f..0d7bb2d24b733 100644 --- a/crates/doc/README.md +++ b/crates/doc/README.md @@ -14,14 +14,12 @@ In this phase, builder invokes 2 parsers: [solang parser](https://github.com/hyp Then, builder takes the output of the internal `Parser` and creates documents with additional information: the path of the original item, display identity, the target path where this document will be written. - 2. Preprocess The builder accepts an array of preprocessors which can be applied to documents produced in the `Parse` phase. The preprocessors can rearrange and/or change the array as well as modify the separate documents. At the end of this phase, the builder maintains a possibly modified collection of documents. - 3. Write -At this point, builder has all necessary information to generate documentation for the source code. It takes every document, formats the source file contents and writes/copies additional files that are required for building documentation. \ No newline at end of file +At this point, builder has all necessary information to generate documentation for the source code. It takes every document, formats the source file contents and writes/copies additional files that are required for building documentation. diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 6c2bec99d19f8..ae456e5de7e2c 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -1,13 +1,13 @@ use crate::{ - document::DocumentContent, helpers::merge_toml_table, AsDoc, BufWriter, Document, ParseItem, - ParseSource, Parser, Preprocessor, + AsDoc, BufWriter, Document, ParseItem, ParseSource, Parser, Preprocessor, + document::DocumentContent, helpers::merge_toml_table, }; use alloy_primitives::map::HashMap; -use forge_fmt::{FormatterConfig, Visitable}; +use eyre::{Context, Result}; use foundry_compilers::{compilers::solc::SOLC_EXTENSIONS, utils::source_files_iter}; -use foundry_config::{filter::expand_globs, DocConfig}; +use foundry_config::{DocConfig, FormatterConfig, filter::expand_globs}; use itertools::Itertools; -use mdbook::MDBook; +use mdbook_driver::MDBook; use rayon::prelude::*; use std::{ cmp::Ordering, @@ -22,24 +22,23 @@ use toml::value; #[derive(Debug)] pub struct DocBuilder { /// The project root - pub root: PathBuf, + root: PathBuf, /// Path to Solidity source files. - pub sources: PathBuf, + sources: PathBuf, /// Paths to external libraries. - pub libraries: Vec, + libraries: Vec, /// Flag whether to build mdbook. - pub should_build: bool, + should_build: bool, /// Documentation configuration. - pub config: DocConfig, + config: DocConfig, /// The array of preprocessors to apply. - pub preprocessors: Vec>, + preprocessors: Vec>, /// The formatter config. - pub fmt: FormatterConfig, + fmt: FormatterConfig, /// Whether to include libraries to the output. - pub include_libraries: bool, + include_libraries: bool, } -// TODO: consider using `tfio` impl DocBuilder { pub(crate) const SRC: &'static str = "src"; const SOL_EXT: &'static str = "sol"; @@ -66,7 +65,7 @@ impl DocBuilder { } /// Set `should_build` flag on the builder - pub fn with_should_build(mut self, should_build: bool) -> Self { + pub const fn with_should_build(mut self, should_build: bool) -> Self { self.should_build = should_build; self } @@ -90,12 +89,15 @@ impl DocBuilder { } /// Get the output directory - pub fn out_dir(&self) -> PathBuf { - self.root.join(&self.config.out) + pub fn out_dir(&self) -> Result { + Ok(self.root.join(&self.config.out).canonicalize()?) } /// Parse the sources and build the documentation. - pub fn build(self) -> eyre::Result<()> { + pub fn build(self, compiler: &mut solar::sema::Compiler) -> eyre::Result<()> { + fs::create_dir_all(self.root.join(&self.config.out)) + .wrap_err("failed to create output directory")?; + // Expand ignore globs let ignored = expand_globs(&self.root, self.config.ignore.iter())?; @@ -106,7 +108,7 @@ impl DocBuilder { if sources.is_empty() { sh_println!("No sources detected at {}", self.sources.display())?; - return Ok(()) + return Ok(()); } let library_sources = self @@ -121,131 +123,136 @@ impl DocBuilder { .chain(library_sources.iter().map(|path| (path, true))) .collect::>(); - let documents = combined_sources - .par_iter() - .enumerate() - .map(|(i, (path, from_library))| { - let path = *path; - let from_library = *from_library; - - // Read and parse source file - let source = fs::read_to_string(path)?; - - let (mut source_unit, comments) = match solang_parser::parse(&source, i) { - Ok(res) => res, - Err(err) => { - if from_library { - // Ignore failures for library files - return Ok(Vec::new()); - } else { - return Err(eyre::eyre!( - "Failed to parse Solidity code for {}\nDebug info: {:?}", - path.display(), - err - )); + let out_dir = self.out_dir()?; + let out_target_dir = out_dir.clone(); + let documents = compiler.enter_mut(|compiler| -> eyre::Result>> { + let gcx = compiler.gcx(); + let documents = combined_sources + .par_iter() + .map(|(path, from_library)| { + let path = *path; + let from_library = *from_library; + let mut files = vec![]; + + // Read and parse source file + if let Some((_, ast_source)) = gcx.get_ast_source(path) + && let Some(source_unit) = ast_source.ast.as_ref() + { + // Solar uses a global SourceMap: span BytePos values are global + // offsets, not per-file offsets. Subtract file.start_pos so that + // span-based indexing into the per-file source string is correct. + let source = ast_source.file.src.to_string(); + let file_start = ast_source.file.start_pos.to_usize(); + + // Walk the solar AST directly + let doc = Parser::new(source, file_start, self.fmt.tab_width); + let all_items = doc.parse(source_unit); + + // Split the parsed items on top-level constants and rest. + let (items, consts): (Vec, Vec) = all_items + .into_iter() + .partition(|item| !matches!(item.source, ParseSource::Variable(_))); + + // Attempt to group overloaded top-level functions + let mut remaining = Vec::with_capacity(items.len()); + let mut funcs: HashMap> = HashMap::default(); + for item in items { + if matches!(item.source, ParseSource::Function(_)) { + funcs.entry(item.source.ident()).or_default().push(item); + } else { + // Put the item back + remaining.push(item); + } } - } - }; - - // Visit the parse tree - let mut doc = Parser::new(comments, source).with_fmt(self.fmt.clone()); - source_unit - .visit(&mut doc) - .map_err(|err| eyre::eyre!("Failed to parse source: {err}"))?; - - // Split the parsed items on top-level constants and rest. - let (items, consts): (Vec, Vec) = doc - .items() - .into_iter() - .partition(|item| !matches!(item.source, ParseSource::Variable(_))); - - // Attempt to group overloaded top-level functions - let mut remaining = Vec::with_capacity(items.len()); - let mut funcs: HashMap> = HashMap::default(); - for item in items { - if matches!(item.source, ParseSource::Function(_)) { - funcs.entry(item.source.ident()).or_default().push(item); - } else { - // Put the item back - remaining.push(item); - } - } - let (items, overloaded): ( - HashMap>, - HashMap>, - ) = funcs.into_iter().partition(|(_, v)| v.len() == 1); - remaining.extend(items.into_iter().flat_map(|(_, v)| v)); - - // Each regular item will be written into its own file. - let mut files = remaining - .into_iter() - .map(|item| { - let relative_path = path.strip_prefix(&self.root)?.join(item.filename()); - let target_path = self.config.out.join(Self::SRC).join(relative_path); - let ident = item.source.ident(); - Ok(Document::new( - path.clone(), - target_path, - from_library, - self.config.out.clone(), - ) - .with_content(DocumentContent::Single(item), ident)) - }) - .collect::>>()?; - - // If top-level constants exist, they will be written to the same file. - if !consts.is_empty() { - let filestem = path.file_stem().and_then(|stem| stem.to_str()); - - let filename = { - let mut name = "constants".to_owned(); - if let Some(stem) = filestem { - name.push_str(&format!(".{stem}")); + let (items, overloaded): ( + HashMap>, + HashMap>, + ) = funcs.into_iter().partition(|(_, v)| v.len() == 1); + remaining.extend(items.into_values().flatten()); + + // Each regular item will be written into its own file. + files = remaining + .into_iter() + .map(|item| { + let relative_path = + path.strip_prefix(&self.root)?.join(item.filename()); + + let target_path = out_dir.join(Self::SRC).join(relative_path); + let ident = item.source.ident(); + Ok(Document::new( + path.clone(), + target_path, + from_library, + out_target_dir.clone(), + ) + .with_content(DocumentContent::Single(item), ident)) + }) + .collect::>>()?; + + // If top-level constants exist, they will be written to the same file. + if !consts.is_empty() { + let filestem = path.file_stem().and_then(|stem| stem.to_str()); + + let filename = { + let mut name = "constants".to_owned(); + if let Some(stem) = filestem { + name.push_str(&format!(".{stem}")); + } + name.push_str(".md"); + name + }; + let relative_path = path.strip_prefix(&self.root)?.join(filename); + let target_path = out_dir.join(Self::SRC).join(relative_path); + + let identity = match filestem { + Some(stem) if stem.to_lowercase().contains("constants") => { + stem.to_owned() + } + Some(stem) => format!("{stem} constants"), + None => "constants".to_owned(), + }; + + files.push( + Document::new( + path.clone(), + target_path, + from_library, + out_target_dir.clone(), + ) + .with_content(DocumentContent::Constants(consts), identity), + ) } - name.push_str(".md"); - name - }; - let relative_path = path.strip_prefix(&self.root)?.join(filename); - let target_path = self.config.out.join(Self::SRC).join(relative_path); - let identity = match filestem { - Some(stem) if stem.to_lowercase().contains("constants") => stem.to_owned(), - Some(stem) => format!("{stem} constants"), - None => "constants".to_owned(), + // If overloaded functions exist, they will be written to the same file + if !overloaded.is_empty() { + for (ident, funcs) in overloaded { + let filename = + funcs.first().expect("no overloaded functions").filename(); + let relative_path = path.strip_prefix(&self.root)?.join(filename); + + let target_path = out_dir.join(Self::SRC).join(relative_path); + files.push( + Document::new( + path.clone(), + target_path, + from_library, + out_target_dir.clone(), + ) + .with_content( + DocumentContent::OverloadedFunctions(funcs), + ident, + ), + ); + } + } }; - files.push( - Document::new( - path.clone(), - target_path, - from_library, - self.config.out.clone(), - ) - .with_content(DocumentContent::Constants(consts), identity), - ) - } - - // If overloaded functions exist, they will be written to the same file - if !overloaded.is_empty() { - for (ident, funcs) in overloaded { - let filename = funcs.first().expect("no overloaded functions").filename(); - let relative_path = path.strip_prefix(&self.root)?.join(filename); - let target_path = self.config.out.join(Self::SRC).join(relative_path); - files.push( - Document::new( - path.clone(), - target_path, - from_library, - self.config.out.clone(), - ) - .with_content(DocumentContent::OverloadedFunctions(funcs), ident), - ); - } - } + Ok(files) + }) + .collect::>>()?; - Ok(files) - }) - .collect::>>()?; + Ok(documents) + })?; // Flatten results and apply preprocessors to files let documents = self @@ -255,19 +262,21 @@ impl DocBuilder { p.preprocess(docs) })?; - // Sort the results - let documents = documents.into_iter().sorted_by(|doc1, doc2| { - doc1.item_path.display().to_string().cmp(&doc2.item_path.display().to_string()) - }); + // Sort the results and filter libraries. + let documents = documents + .into_iter() + .sorted_by(|doc1, doc2| { + doc1.item_path.display().to_string().cmp(&doc2.item_path.display().to_string()) + }) + .filter(|d| !d.from_library || self.include_libraries) + .collect_vec(); // Write mdbook related files - self.write_mdbook( - documents.filter(|d| !d.from_library || self.include_libraries).collect_vec(), - )?; + self.write_mdbook(documents)?; // Build the book if requested if self.should_build { - MDBook::load(self.out_dir()) + MDBook::load(self.out_dir().wrap_err("failed to construct output directory")?) .and_then(|book| book.build()) .map_err(|err| eyre::eyre!("failed to build book: {err:?}"))?; } @@ -276,7 +285,7 @@ impl DocBuilder { } fn write_mdbook(&self, documents: Vec) -> eyre::Result<()> { - let out_dir = self.out_dir(); + let out_dir = self.out_dir().wrap_err("failed to construct output directory")?; let out_dir_src = out_dir.join(Self::SRC); fs::create_dir_all(&out_dir_src)?; @@ -321,11 +330,11 @@ impl DocBuilder { fs::write(out_dir.join("book.css"), include_str!("../static/book.css"))?; // Write book config - fs::write(self.out_dir().join("book.toml"), self.book_config()?)?; + fs::write(out_dir.join("book.toml"), self.book_config()?)?; // Write .gitignore let gitignore = "book/"; - fs::write(self.out_dir().join(".gitignore"), gitignore)?; + fs::write(out_dir.join(".gitignore"), gitignore)?; // Write doc files for document in documents { @@ -349,10 +358,19 @@ impl DocBuilder { .unwrap() .insert(String::from("title"), self.config.title.clone().into()); if let Some(ref repo) = self.config.repository { + // Create the full repository URL. + let git_repo_url = if let Some(path) = &self.config.path { + // If path is specified, append it to the repository URL. + format!("{}/{}", repo.trim_end_matches('/'), path.trim_start_matches('/')) + } else { + // If no path specified, use repository URL as-is. + repo.clone() + }; + book["output"].as_table_mut().unwrap()["html"] .as_table_mut() .unwrap() - .insert(String::from("git-repository-url"), repo.clone().into()); + .insert(String::from("git-repository-url"), git_repo_url.into()); } // Attempt to find the user provided book path @@ -361,11 +379,7 @@ impl DocBuilder { Some(self.config.book.clone()) } else { let book_path = self.config.book.join("book.toml"); - if book_path.is_file() { - Some(book_path) - } else { - None - } + book_path.is_file().then_some(book_path) } }; @@ -385,7 +399,7 @@ impl DocBuilder { depth: usize, ) -> eyre::Result<()> { if files.is_empty() { - return Ok(()) + return Ok(()); } if let Some(path) = base_path { @@ -422,15 +436,14 @@ impl DocBuilder { } }); + let out_dir = self.out_dir().wrap_err("failed to construct output directory")?; let mut readme = BufWriter::new("\n\n# Contents\n"); for (path, files) in grouped { if path.extension().map(|ext| ext == Self::SOL_EXT).unwrap_or_default() { for file in files { let ident = &file.identity; - let summary_path = file - .target_path - .strip_prefix(self.out_dir().strip_prefix(&self.root)?.join(Self::SRC))?; + let summary_path = &file.target_path.strip_prefix(out_dir.join(Self::SRC))?; summary.write_link_list_item( ident, &summary_path.display().to_string(), @@ -450,12 +463,12 @@ impl DocBuilder { self.write_summary_section(summary, &files, Some(&path), depth + 1)?; } } - if !readme.is_empty() { - if let Some(path) = base_path { - let path = self.out_dir().join(Self::SRC).join(path); - fs::create_dir_all(&path)?; - fs::write(path.join(Self::README), readme.finish())?; - } + if !readme.is_empty() + && let Some(path) = base_path + { + let path = out_dir.join(Self::SRC).join(path); + fs::create_dir_all(&path)?; + fs::write(path.join(Self::README), readme.finish())?; } Ok(()) } diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index 63b81e020083f..bcf80089cfe56 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -89,7 +89,7 @@ pub enum DocumentContent { } impl DocumentContent { - pub(crate) fn len(&self) -> usize { + pub(crate) const fn len(&self) -> usize { match self { Self::Empty => 0, Self::Single(_) => 1, @@ -101,13 +101,7 @@ impl DocumentContent { pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> { match self { Self::Empty => None, - Self::Single(item) => { - if index == 0 { - Some(item) - } else { - None - } - } + Self::Single(item) => (index == 0).then_some(item), Self::Constants(items) => items.get_mut(index), Self::OverloadedFunctions(items) => items.get_mut(index), } @@ -149,10 +143,10 @@ impl<'a> Iterator for ParseItemIter<'a> { fn next(&mut self) -> Option { if let Some(next) = self.next.take() { - return Some(next) + return Some(next); } if let Some(other) = self.other.as_mut() { - return other.next() + return other.next(); } None @@ -170,10 +164,10 @@ impl<'a> Iterator for ParseItemIterMut<'a> { fn next(&mut self) -> Option { if let Some(next) = self.next.take() { - return Some(next) + return Some(next); } if let Some(other) = self.other.as_mut() { - return other.next() + return other.next(); } None @@ -183,7 +177,7 @@ impl<'a> Iterator for ParseItemIterMut<'a> { /// Read the preprocessor output variant from document context. /// Returns [None] if there is no output. macro_rules! read_context { - ($doc: expr, $id: expr, $variant: ident) => { + ($doc:expr, $id:expr, $variant:ident) => { $doc.get_from_context($id).and_then(|out| match out { // Only a single variant is matched. Otherwise the code is invalid. PreprocessorOutput::$variant(inner) => Some(inner), diff --git a/crates/doc/src/helpers.rs b/crates/doc/src/helpers.rs index 013d606c76e36..7b36e2e257b71 100644 --- a/crates/doc/src/helpers.rs +++ b/crates/doc/src/helpers.rs @@ -1,4 +1,4 @@ -use toml::{value::Table, Value}; +use toml::{Value, value::Table}; /// Merge original toml table with the override. pub(crate) fn merge_toml_table(table: &mut Table, override_table: Table) { diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 174c76600d8a0..e50598cff83ac 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -3,7 +3,7 @@ //! See [`DocBuilder`]. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -21,7 +21,11 @@ mod helpers; mod parser; pub use parser::{ - error, Comment, CommentTag, Comments, CommentsRef, ParseItem, ParseSource, Parser, + Comment, CommentTag, Comments, CommentsRef, ParseItem, ParseSource, Parser, error, + source::{ + BaseInfo, ContractKind, ContractSource, EnumSource, ErrorSource, EventSource, + FunctionSource, ParamInfo, StructSource, TypeSource, VariableAttr, VariableSource, + }, }; mod preprocessor; @@ -30,4 +34,4 @@ pub use preprocessor::*; mod writer; pub use writer::{AsDoc, AsDocResult, BufWriter, Markdown}; -pub use mdbook; +pub use mdbook_driver; diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index bf2b0ad7b4f0d..42b91ccc366aa 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -1,6 +1,5 @@ use alloy_primitives::map::HashMap; -use derive_more::{derive::Display, Deref, DerefMut}; -use solang_parser::doccomment::DocCommentTag; +use derive_more::{Deref, DerefMut, derive::Display}; /// The natspec comment tag explaining the purpose of the comment. /// See: . @@ -46,7 +45,7 @@ impl CommentTag { } _ => { warn!(target: "forge::doc", tag=trimmed, "unknown comment tag. custom tags must be preceded by `custom:`"); - return None + return None; } }; Some(tag) @@ -66,14 +65,14 @@ pub struct Comment { impl Comment { /// Create new instance of [Comment]. - pub fn new(tag: CommentTag, value: String) -> Self { + pub const fn new(tag: CommentTag, value: String) -> Self { Self { tag, value } } - /// Create new instance of [Comment] from [DocCommentTag] + /// Create new instance of [Comment] from a tag string and value, /// if it has a valid natspec tag. - pub fn from_doc_comment(value: DocCommentTag) -> Option { - CommentTag::from_str(&value.tag).map(|tag| Self { tag, value: value.value }) + pub fn from_tag_and_value(tag: &str, value: String) -> Option { + CommentTag::from_str(tag).map(|tag| Self { tag, value }) } /// Split the comment at first word. @@ -86,25 +85,17 @@ impl Comment { /// Returns [None] if the word doesn't match. /// Useful for [CommentTag::Param] and [CommentTag::Return] comments. pub fn match_first_word(&self, expected: &str) -> Option<&str> { - self.split_first_word().and_then( - |(word, rest)| { - if word == expected { - Some(rest) - } else { - None - } - }, - ) + self.split_first_word().and_then(|(word, rest)| (word == expected).then_some(rest)) } /// Check if this comment is a custom tag. - pub fn is_custom(&self) -> bool { + pub const fn is_custom(&self) -> bool { matches!(self.tag, CommentTag::Custom(_)) } } /// The collection of natspec [Comment] items. -#[derive(Clone, Debug, Default, PartialEq, Deref, DerefMut)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Deref, DerefMut)] pub struct Comments(Vec); /// Forward the [Comments] function implementation to the [CommentsRef] @@ -125,16 +116,18 @@ impl Comments { ref_fn!(pub fn contains_tag(&self, tag: &Comment) -> bool); ref_fn!(pub fn find_inheritdoc_base(&self) -> Option<&'_ str>); - /// Attempt to lookup + /// Attempts to lookup inherited comments and merge them with the current collection. /// - /// Merges two comments collections by inserting [CommentTag] from the second collection - /// into the first unless they are present. + /// Looks up comments in `inheritdocs` using the key `{base}.{ident}` where `base` is + /// extracted from an `@inheritdoc` tag. Merges the found comments by inserting + /// [CommentTag] from the inherited collection into the current one unless they are + /// already present. pub fn merge_inheritdoc( &self, ident: &str, inheritdocs: Option>, ) -> Self { - let mut result = Self(Vec::from_iter(self.iter().cloned())); + let mut result = self.clone(); if let (Some(inheritdocs), Some(base)) = (inheritdocs, self.find_inheritdoc_base()) { let key = format!("{base}.{ident}"); @@ -151,14 +144,69 @@ impl Comments { } } -impl From> for Comments { - fn from(value: Vec) -> Self { - Self(value.into_iter().flat_map(Comment::from_doc_comment).collect()) +impl Comments { + /// Parse natspec comments from raw doc comment lines. + /// + /// Each line should be the raw text content of a `///` or `/** */` doc comment + /// with the comment delimiters already stripped (as provided by solar's `DocComment::symbol`). + /// + /// Natspec tags start with `@` (e.g. `@notice`, `@dev`, `@param`). + /// Lines without a tag at the start are treated as continuations of the previous tag, + /// or as `@notice` if no previous tag exists. + pub fn from_doc_lines(lines: impl IntoIterator>) -> Self { + let mut comments = Vec::new(); + let mut current_tag: Option = None; + let mut current_value = String::new(); + + let flush = |tag: &Option, value: &str, out: &mut Vec| { + let value = value.trim(); + if value.is_empty() && tag.is_none() { + return; + } + let tag_str = tag.as_deref().unwrap_or("notice"); + // Filter out `@solidity` tags and empty tags + if tag_str.trim() == "solidity" || tag_str.trim().is_empty() { + return; + } + if let Some(c) = Comment::from_tag_and_value(tag_str, value.to_string()) { + out.push(c); + } + }; + + for raw_line in lines { + let raw = raw_line.as_ref(); + // For block comments, process each line individually + for line in raw.lines() { + let trimmed = line.trim().trim_start_matches('*').trim(); + + if let Some(rest) = trimmed.strip_prefix('@') { + // Flush previous + flush(¤t_tag, ¤t_value, &mut comments); + // Parse new tag + let (tag, value) = rest.split_once(char::is_whitespace).unwrap_or((rest, "")); + current_tag = Some(tag.to_string()); + current_value = value.trim().to_string(); + } else if !trimmed.is_empty() { + // Continuation of current tag + if current_value.is_empty() { + current_value = trimmed.to_string(); + } else { + current_value.push('\n'); + current_value.push_str(trimmed); + } + } + } + } + + // Flush last + flush(¤t_tag, ¤t_value, &mut comments); + + Self(comments) } } /// The collection of references to natspec [Comment] items. -#[derive(Debug, Default, PartialEq, Deref)] +#[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); impl<'a> CommentsRef<'a> { @@ -170,13 +218,13 @@ impl<'a> CommentsRef<'a> { /// Filter a collection of comments and return only those that match provided tags. pub fn include_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here - CommentsRef(self.iter().cloned().filter(|c| tags.contains(&c.tag)).collect()) + CommentsRef(self.iter().copied().filter(|c| tags.contains(&c.tag)).collect()) } /// Filter a collection of comments and return only those that do not match provided tags. pub fn exclude_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here - CommentsRef(self.iter().cloned().filter(|c| !tags.contains(&c.tag)).collect()) + CommentsRef(self.iter().copied().filter(|c| !tags.contains(&c.tag)).collect()) } /// Check if the collection contains a target comment. @@ -184,8 +232,8 @@ impl<'a> CommentsRef<'a> { self.iter().any(|c| match (&c.tag, &target.tag) { (CommentTag::Inheritdoc, CommentTag::Inheritdoc) => c.value == target.value, (CommentTag::Param, CommentTag::Param) | (CommentTag::Return, CommentTag::Return) => { - c.split_first_word().map(|(name, _)| name) == - target.split_first_word().map(|(name, _)| name) + c.split_first_word().map(|(name, _)| name) + == target.split_first_word().map(|(name, _)| name) } (tag1, tag2) => tag1 == tag2, }) @@ -200,7 +248,7 @@ impl<'a> CommentsRef<'a> { /// Filter a collection of comments and only return the custom tags. pub fn get_custom_tags(&self) -> Self { - CommentsRef(self.iter().cloned().filter(|c| c.is_custom()).collect()) + CommentsRef(self.iter().copied().filter(|c| c.is_custom()).collect()) } } diff --git a/crates/doc/src/parser/error.rs b/crates/doc/src/parser/error.rs index 770137f99d173..9ea1a4c439b88 100644 --- a/crates/doc/src/parser/error.rs +++ b/crates/doc/src/parser/error.rs @@ -1,4 +1,4 @@ -use forge_fmt::FormatterError; +use solar::interface::diagnostics::EmittedDiagnostics; use thiserror::Error; /// The parser error. @@ -7,7 +7,7 @@ use thiserror::Error; pub enum ParserError { /// Formatter error. #[error(transparent)] - Formatter(#[from] FormatterError), + Formatter(EmittedDiagnostics), /// Internal parser error. #[error(transparent)] Internal(#[from] eyre::Error), diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index 5cc39f3e9aec6..cca36d47301c8 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -1,32 +1,29 @@ -use crate::{error::ParserResult, Comments}; -use forge_fmt::{ - solang_ext::SafeUnwrap, Comments as FmtComments, Formatter, FormatterConfig, InlineConfig, - Visitor, -}; -use solang_parser::pt::{ - ContractDefinition, ContractTy, EnumDefinition, ErrorDefinition, EventDefinition, - FunctionDefinition, StructDefinition, TypeDefinition, VariableDefinition, +use crate::Comments; + +pub use super::source::{ + ContractSource, EnumSource, ErrorSource, EventSource, FunctionSource, ParseSource, + StructSource, VariableSource, }; /// The parsed item. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct ParseItem { /// The parse tree source. pub source: ParseSource, /// Item comments. pub comments: Comments, /// Children items. - pub children: Vec, + pub children: Vec, /// Formatted code string. pub code: String, } -/// Defines a method that filters [ParseItem]'s children and returns the source pt token of the +/// Defines a method that filters [ParseItem]'s children and returns the source data of the /// children matching the target variant as well as its comments. /// Returns [Option::None] if no children matching the variant are found. macro_rules! filter_children_fn { ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => { - /// Filter children items for [ParseSource::$variant] variants. + /// Filter children items for matching variants. $vis fn $name(&self) -> Option> { let items = self.children.iter().filter_map(|item| match item.source { ParseSource::$variant(ref inner) => Some((inner, &item.comments, &item.code)), @@ -42,12 +39,11 @@ macro_rules! filter_children_fn { }; } -/// Defines a method that returns [ParseSource] inner element if it matches -/// the variant +/// Defines a method that returns the inner element if it matches the variant. macro_rules! as_inner_source { ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => { - /// Return inner element if it matches $variant. - /// If the element doesn't match, returns [None] + /// Return inner element if it matches the variant. + /// If the element doesn't match, returns [None]. $vis fn $name(&self) -> Option<&$ret> { match self.source { ParseSource::$variant(ref inner) => Some(inner), @@ -80,48 +76,16 @@ impl ParseItem { self } - /// Set formatted code on the [ParseItem]. - pub fn with_code(mut self, source: &str, config: FormatterConfig) -> ParserResult { - let mut code = String::new(); - let mut fmt = Formatter::new( - &mut code, - source, - FmtComments::default(), - InlineConfig::default(), - config, - ); - - match self.source.clone() { - ParseSource::Contract(mut contract) => { - contract.parts = vec![]; - fmt.visit_contract(&mut contract)? - } - ParseSource::Function(mut func) => { - func.body = None; - fmt.visit_function(&mut func)? - } - ParseSource::Variable(mut var) => fmt.visit_var_definition(&mut var)?, - ParseSource::Event(mut event) => fmt.visit_event(&mut event)?, - ParseSource::Error(mut error) => fmt.visit_error(&mut error)?, - ParseSource::Struct(mut structure) => fmt.visit_struct(&mut structure)?, - ParseSource::Enum(mut enumeration) => fmt.visit_enum(&mut enumeration)?, - ParseSource::Type(mut ty) => fmt.visit_type_definition(&mut ty)?, - }; - + /// Set the code string for this [ParseItem]. + pub fn with_code(mut self, code: String) -> Self { self.code = code; - - Ok(self) + self } /// Format the item's filename. pub fn filename(&self) -> String { - let prefix = match self.source { - ParseSource::Contract(ref c) => match c.ty { - ContractTy::Contract(_) => "contract", - ContractTy::Abstract(_) => "abstract", - ContractTy::Interface(_) => "interface", - ContractTy::Library(_) => "library", - }, + let prefix = match &self.source { + ParseSource::Contract(c) => c.kind.as_str(), ParseSource::Function(_) => "function", ParseSource::Variable(_) => "variable", ParseSource::Event(_) => "event", @@ -134,54 +98,14 @@ impl ParseItem { format!("{prefix}.{ident}.md") } - filter_children_fn!(pub fn variables(&self, Variable) -> VariableDefinition); - filter_children_fn!(pub fn functions(&self, Function) -> FunctionDefinition); - filter_children_fn!(pub fn events(&self, Event) -> EventDefinition); - filter_children_fn!(pub fn errors(&self, Error) -> ErrorDefinition); - filter_children_fn!(pub fn structs(&self, Struct) -> StructDefinition); - filter_children_fn!(pub fn enums(&self, Enum) -> EnumDefinition); - - as_inner_source!(pub fn as_contract(&self, Contract) -> ContractDefinition); - as_inner_source!(pub fn as_variable(&self, Variable) -> VariableDefinition); - as_inner_source!(pub fn as_function(&self, Function) -> FunctionDefinition); -} - -/// A wrapper type around pt token. -#[derive(Clone, Debug, PartialEq, Eq)] -#[allow(clippy::large_enum_variant)] -pub enum ParseSource { - /// Source contract definition. - Contract(Box), - /// Source function definition. - Function(FunctionDefinition), - /// Source variable definition. - Variable(VariableDefinition), - /// Source event definition. - Event(EventDefinition), - /// Source error definition. - Error(ErrorDefinition), - /// Source struct definition. - Struct(StructDefinition), - /// Source enum definition. - Enum(EnumDefinition), - /// Source type definition. - Type(TypeDefinition), -} + filter_children_fn!(pub fn variables(&self, Variable) -> VariableSource); + filter_children_fn!(pub fn functions(&self, Function) -> FunctionSource); + filter_children_fn!(pub fn events(&self, Event) -> EventSource); + filter_children_fn!(pub fn errors(&self, Error) -> ErrorSource); + filter_children_fn!(pub fn structs(&self, Struct) -> StructSource); + filter_children_fn!(pub fn enums(&self, Enum) -> EnumSource); -impl ParseSource { - /// Get the identity of the source - pub fn ident(&self) -> String { - match self { - Self::Contract(contract) => contract.name.safe_unwrap().name.to_owned(), - Self::Variable(var) => var.name.safe_unwrap().name.to_owned(), - Self::Event(event) => event.name.safe_unwrap().name.to_owned(), - Self::Error(error) => error.name.safe_unwrap().name.to_owned(), - Self::Struct(structure) => structure.name.safe_unwrap().name.to_owned(), - Self::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(), - Self::Function(func) => { - func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()) - } - Self::Type(ty) => ty.name.name.to_owned(), - } - } + as_inner_source!(pub fn as_contract(&self, Contract) -> ContractSource); + as_inner_source!(pub fn as_variable(&self, Variable) -> VariableSource); + as_inner_source!(pub fn as_function(&self, Function) -> FunctionSource); } diff --git a/crates/doc/src/parser/mod.rs b/crates/doc/src/parser/mod.rs index 1cf496c22f94b..124d2aa3ed576 100644 --- a/crates/doc/src/parser/mod.rs +++ b/crates/doc/src/parser/mod.rs @@ -1,19 +1,12 @@ //! The parser module. -use forge_fmt::{FormatterConfig, Visitable, Visitor}; -use itertools::Itertools; -use solang_parser::{ - doccomment::{parse_doccomments, DocComment}, - pt::{ - Comment as SolangComment, EnumDefinition, ErrorDefinition, EventDefinition, - FunctionDefinition, Identifier, Loc, SourceUnit, SourceUnitPart, StructDefinition, - TypeDefinition, VariableDefinition, - }, -}; +use solar::parse::ast; /// Parser error. pub mod error; -use error::{ParserError, ParserResult}; + +/// Owned source types. +pub mod source; /// Parser item. mod item; @@ -23,215 +16,274 @@ pub use item::{ParseItem, ParseSource}; mod comment; pub use comment::{Comment, CommentTag, Comments, CommentsRef}; -/// The documentation parser. This type implements a [Visitor] trait. +use source::*; + +/// The documentation parser. /// -/// While walking the parse tree, [Parser] will collect relevant source items and corresponding -/// doc comments. The resulting [ParseItem]s can be accessed by calling [Parser::items]. -#[derive(Debug, Default)] +/// Walks the solar AST and extracts [`ParseItem`]s with owned source data and doc comments. +#[derive(Debug)] pub struct Parser { - /// Initial comments from solang parser. - comments: Vec, - /// Parser context. - context: ParserContext, /// Parsed results. items: Vec, - /// Source file. + /// The source code string of the file being parsed. source: String, - /// The formatter config. - fmt: FormatterConfig, -} - -/// [Parser] context. -#[derive(Debug, Default)] -struct ParserContext { - /// Current visited parent. - parent: Option, - /// Current start pointer for parsing doc comments. - doc_start_loc: usize, + /// The global byte offset of this file's first byte in solar's source map. + /// + /// Solar uses a global `SourceMap` where each file's `BytePos` values start at + /// `file.start_pos` rather than 0. All span `lo`/`hi` values must be offset by this + /// amount before indexing into `self.source`. + file_start: usize, + /// Tab width used to format code. + tab_width: usize, } impl Parser { /// Create a new instance of [Parser]. - pub fn new(comments: Vec, source: String) -> Self { - Self { comments, source, ..Default::default() } + /// + /// `file_start` is `ast_source.file.start_pos.to_usize()` — the offset of the file's + /// first byte in solar's global source map. Pass `0` in tests where you parse directly. + pub const fn new(source: String, file_start: usize, tab_width: usize) -> Self { + Self { items: Vec::new(), source, file_start, tab_width } } - /// Set formatter config on the [Parser] - pub fn with_fmt(mut self, fmt: FormatterConfig) -> Self { - self.fmt = fmt; - self - } - - /// Return the parsed items. Consumes the parser. - pub fn items(self) -> Vec { + /// Parse a solar source unit and return the parsed items. + pub fn parse(mut self, source_unit: &ast::SourceUnit<'_>) -> Vec { + for item in source_unit.items.iter() { + if let Some(parsed) = self.parse_item(item) { + self.items.push(parsed); + } + } self.items } - /// Visit the children elements with parent context. - /// This function memoizes the previous parent, sets the context - /// to a new one and invokes a visit function. The context will be reset - /// to the previous parent at the end of the function. - fn with_parent( - &mut self, - mut parent: ParseItem, - mut visit: impl FnMut(&mut Self) -> ParserResult<()>, - ) -> ParserResult { - let curr = self.context.parent.take(); - self.context.parent = Some(parent); - visit(self)?; - parent = self.context.parent.take().unwrap(); - self.context.parent = curr; - Ok(parent) - } - - /// Adds a child element to the parent item if it exists. - /// Otherwise the element will be added to a top-level items collection. - /// Moves the doc comment pointer to the end location of the child element. - fn add_element_to_parent(&mut self, source: ParseSource, loc: Loc) -> ParserResult<()> { - let child = self.new_item(source, loc.start())?; - if let Some(parent) = self.context.parent.as_mut() { - parent.children.push(child); - } else { - self.items.push(child); + /// Parse a single solar AST item into a [ParseItem]. + fn parse_item(&self, item: &ast::Item<'_>) -> Option { + let docs = Self::parse_docs(&item.docs); + let span = item.span; + + match &item.kind { + ast::ItemKind::Contract(contract) => { + let source = self.parse_contract(contract); + let code = self.extract_code(span); + let mut parse_item = + ParseItem::new(ParseSource::Contract(source)).with_comments(docs); + + // Parse children + let mut children = Vec::new(); + for child in contract.body.iter() { + if let Some(parsed) = self.parse_item(child) { + children.push(parsed); + } + } + parse_item.children = children; + parse_item.code = code; + Some(parse_item) + } + ast::ItemKind::Function(func) => { + let source = self.parse_function(func); + let code = self.extract_prototype_code(func); + Some( + ParseItem::new(ParseSource::Function(source)) + .with_comments(docs) + .with_code(code), + ) + } + ast::ItemKind::Variable(var) => { + let source = self.parse_variable(var); + let code = self.extract_code(span); + Some( + ParseItem::new(ParseSource::Variable(source)) + .with_comments(docs) + .with_code(code), + ) + } + ast::ItemKind::Event(event) => { + let source = self.parse_event(event); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Event(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Error(err) => { + let source = self.parse_error(err); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Error(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Struct(strukt) => { + let source = self.parse_struct(strukt); + let code = self.extract_code(span); + Some( + ParseItem::new(ParseSource::Struct(source)).with_comments(docs).with_code(code), + ) + } + ast::ItemKind::Enum(enm) => { + let source = self.parse_enum(enm); + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Enum(source)).with_comments(docs).with_code(code)) + } + ast::ItemKind::Udvt(udvt) => { + let source = TypeSource { name: udvt.name.to_string() }; + let code = self.extract_code(span); + Some(ParseItem::new(ParseSource::Type(source)).with_comments(docs).with_code(code)) + } + // Skip pragmas, imports, using directives + _ => None, } - self.context.doc_start_loc = loc.end(); - Ok(()) } - /// Create new [ParseItem] with comments and formatted code. - fn new_item(&mut self, source: ParseSource, loc_start: usize) -> ParserResult { - let docs = self.parse_docs(loc_start)?; - ParseItem::new(source).with_comments(docs).with_code(&self.source, self.fmt.clone()) + fn parse_contract(&self, contract: &ast::ItemContract<'_>) -> ContractSource { + let kind = match contract.kind { + ast::ContractKind::Contract => ContractKind::Contract, + ast::ContractKind::AbstractContract => ContractKind::Abstract, + ast::ContractKind::Interface => ContractKind::Interface, + ast::ContractKind::Library => ContractKind::Library, + }; + + let bases = contract + .bases + .iter() + .map(|base| { + let full_name = base.name.to_string(); + let ident = base.name.last().name.to_string(); + BaseInfo { name: full_name, ident } + }) + .collect(); + + ContractSource { name: contract.name.to_string(), kind, bases } } - /// Parse the doc comments from the current start location. - fn parse_docs(&mut self, end: usize) -> ParserResult { - self.parse_docs_range(self.context.doc_start_loc, end) + fn parse_function(&self, func: &ast::ItemFunction<'_>) -> FunctionSource { + let name = func.header.name.map(|n| n.to_string()); + let kind = func.kind.to_string(); + let params = self.parse_var_defs(&func.header.parameters); + let returns = + func.header.returns.as_deref().map(|r| self.parse_var_defs(r)).unwrap_or_default(); + FunctionSource { name, kind, params, returns } } - /// Parse doc comments from the within specified range. - fn parse_docs_range(&mut self, start: usize, end: usize) -> ParserResult { - let mut res = vec![]; - for comment in parse_doccomments(&self.comments, start, end) { - match comment { - DocComment::Line { comment } => res.push(comment), - DocComment::Block { comments } => res.extend(comments), + fn parse_variable(&self, var: &ast::VariableDefinition<'_>) -> VariableSource { + let name = var.name.map(|n| n.to_string()).unwrap_or_default(); + + let mut attrs = Vec::new(); + if let Some(m) = var.mutability { + match m { + ast::VarMut::Constant => attrs.push(VariableAttr::Constant), + ast::VarMut::Immutable => attrs.push(VariableAttr::Immutable), } } - // Filter out `@solidity` and empty tags - // See https://docs.soliditylang.org/en/v0.8.17/assembly.html#memory-safety - let res = res - .into_iter() - .filter(|c| c.tag.trim() != "solidity" && !c.tag.trim().is_empty()) - .collect_vec(); - Ok(res.into()) + VariableSource { name, attrs } } -} -impl Visitor for Parser { - type Error = ParserError; - - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> ParserResult<()> { - for source in &mut source_unit.0 { - match source { - SourceUnitPart::ContractDefinition(def) => { - // Create new contract parse item. - let contract = - self.new_item(ParseSource::Contract(def.clone()), def.loc.start())?; - - // Move the doc pointer to the contract location start. - self.context.doc_start_loc = def.loc.start(); - - // Parse child elements with current contract as parent - let contract = self.with_parent(contract, |doc| { - def.parts - .iter_mut() - .map(|d| d.visit(doc)) - .collect::>>()?; - Ok(()) - })?; - - // Move the doc pointer to the contract location end. - self.context.doc_start_loc = def.loc.end(); - - // Add contract to the parsed items. - self.items.push(contract); - } - SourceUnitPart::FunctionDefinition(func) => self.visit_function(func)?, - SourceUnitPart::EventDefinition(event) => self.visit_event(event)?, - SourceUnitPart::ErrorDefinition(error) => self.visit_error(error)?, - SourceUnitPart::StructDefinition(structure) => self.visit_struct(structure)?, - SourceUnitPart::EnumDefinition(enumerable) => self.visit_enum(enumerable)?, - SourceUnitPart::VariableDefinition(var) => self.visit_var_definition(var)?, - SourceUnitPart::TypeDefinition(ty) => self.visit_type_definition(ty)?, - _ => {} - }; - } + fn parse_event(&self, event: &ast::ItemEvent<'_>) -> EventSource { + let fields = self.parse_var_defs(&event.parameters); + EventSource { name: event.name.to_string(), fields } + } - Ok(()) + fn parse_error(&self, err: &ast::ItemError<'_>) -> ErrorSource { + let fields = self.parse_var_defs(&err.parameters); + ErrorSource { name: err.name.to_string(), fields } } - fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) + fn parse_struct(&self, strukt: &ast::ItemStruct<'_>) -> StructSource { + let fields = self.parse_var_defs(strukt.fields); + StructSource { name: strukt.name.to_string(), fields } } - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + /// Parse a list of variable definitions into [ParamInfo]. + fn parse_var_defs(&self, vars: &[ast::VariableDefinition<'_>]) -> Vec { + vars.iter() + .map(|v| ParamInfo { name: v.name.map(|n| n.to_string()), ty: self.type_string(&v.ty) }) + .collect() } - fn visit_function(&mut self, func: &mut FunctionDefinition) -> ParserResult<()> { - // If the function parameter doesn't have a name, try to set it with - // `@custom:name` tag if any was provided - let mut start_loc = func.loc.start(); - for (loc, param) in &mut func.params { - if let Some(param) = param { - if param.name.is_none() { - let docs = self.parse_docs_range(start_loc, loc.end())?; - let name_tag = - docs.iter().find(|c| c.tag == CommentTag::Custom("name".to_owned())); - if let Some(name_tag) = name_tag { - if let Some(name) = name_tag.value.trim().split(' ').next() { - param.name = - Some(Identifier { loc: Loc::Implicit, name: name.to_owned() }) - } - } - } - } - start_loc = loc.end(); + /// Extract the type as a string from the source code. + fn type_string(&self, ty: &ast::Type<'_>) -> String { + let lo = ty.span.lo().to_usize().saturating_sub(self.file_start); + let hi = ty.span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + self.source[lo..hi].to_string() + } else { + String::new() } + } - self.add_element_to_parent(ParseSource::Function(func.clone()), func.loc) + fn parse_enum(&self, enm: &ast::ItemEnum<'_>) -> EnumSource { + let variants = enm.variants.iter().map(|v| v.to_string()).collect(); + EnumSource { name: enm.name.to_string(), variants } } - fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) + /// Parse doc comments from solar's [ast::DocComments] into our [Comments] type. + fn parse_docs(docs: &ast::DocComments<'_>) -> Comments { + if docs.is_empty() { + return Comments::default(); + } + Comments::from_doc_lines(docs.iter().map(|d| d.symbol.as_str())) } - fn visit_event(&mut self, event: &mut EventDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Event(event.clone()), event.loc) + /// Extract a code snippet from the source for the given span. + fn extract_code(&self, span: ast::Span) -> String { + let lo = span.lo().to_usize().saturating_sub(self.file_start); + let hi = span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + let code = &self.source[lo..hi]; + self.dedent(code) + } else { + String::new() + } } - fn visit_error(&mut self, error: &mut ErrorDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Error(error.clone()), error.loc) + /// Extract only the function prototype (excluding the body) from the source. + fn extract_prototype_code(&self, func: &ast::ItemFunction<'_>) -> String { + let lo = func.header.span.lo().to_usize().saturating_sub(self.file_start); + let hi = func.header.span.hi().to_usize().saturating_sub(self.file_start); + if lo < self.source.len() && hi <= self.source.len() && lo < hi { + let mut code = self.source[lo..hi].to_string(); + code.push(';'); + self.dedent(&code) + } else { + String::new() + } } - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Type(def.clone()), def.loc) + /// Remove one level of indentation from code. + fn dedent(&self, code: &str) -> String { + let prefix = &" ".repeat(self.tab_width); + code.lines() + .map(|line| line.strip_prefix(prefix).unwrap_or(line)) + .collect::>() + .join("\n") } } #[cfg(test)] mod tests { use super::*; - use solang_parser::parse; - #[inline] fn parse_source(src: &str) -> Vec { - let (mut source, comments) = parse(src, 0).expect("failed to parse source"); - let mut doc = Parser::new(comments, src.to_owned()); - source.visit(&mut doc).expect("failed to visit source"); - doc.items() + use solar::parse::{ + Parser as SolarParser, + ast::{Arena, interface}, + interface::Session, + }; + + let sess = + Session::builder().with_silent_emitter(Some("test parse failed".to_string())).build(); + + sess.enter(|| -> Vec { + let arena = Arena::new(); + let mut parser = SolarParser::from_source_code( + &sess, + &arena, + interface::source_map::FileName::Custom("test".to_string()), + src.to_string(), + ) + .expect("failed to create parser"); + + let source_unit = parser.parse_file().map_err(|e| e.emit()).expect("failed to parse"); + + // file_start=0: when parsing directly, solar's BytePos values start at 0. + let doc = Parser::new(src.to_string(), 0, 4); + doc.parse(&source_unit) + }) } macro_rules! test_single_unit { @@ -298,7 +350,7 @@ mod tests { struct ContractStruct { } enum ContractEnum { } - uint256 constant CONTRACT_CONSTANT; + uint256 constant CONTRACT_CONSTANT = 0; bool contractVar; function contractFunction(uint256) external returns (uint256) { @@ -348,23 +400,39 @@ mod tests { assert!(matches!(fallback.source, ParseSource::Function(_))); } + #[test] + fn overloaded_function_signatures() { + let items = parse_source( + r" + interface IFoo { + function process(address addr) external; + function process(address[] calldata addrs) external; + function process(address addr, uint256 value) external; + } + ", + ); + assert_eq!(items.len(), 1); + let contract = items.first().unwrap(); + assert_eq!(contract.children.len(), 3); + let sigs: Vec = contract.children.iter().map(|ch| ch.source.signature()).collect(); + assert_eq!(sigs[0], "process(address)", "first overload"); + assert_eq!(sigs[1], "process(address[])", "second overload (array)"); + assert_eq!(sigs[2], "process(address,uint256)", "third overload"); + } + #[test] fn contract_with_doc_comments() { let items = parse_source( r" pragma solidity ^0.8.19; - /// @name Test - /// no tag - ///@notice Cool contract - /// @ dev This is not a dev tag + /// @notice Cool contract /** * @dev line one * line 2 */ contract Test { - /*** my function - i like whitespace - */ + /// my function + /// i like whitespace function test() {} } ", diff --git a/crates/doc/src/parser/source.rs b/crates/doc/src/parser/source.rs new file mode 100644 index 0000000000000..5a2fbddf43d5a --- /dev/null +++ b/crates/doc/src/parser/source.rs @@ -0,0 +1,172 @@ +//! Owned source types extracted from the parse tree for documentation generation. +//! +//! These types hold only the data needed by the doc writer and preprocessors, +//! avoiding lifetime dependencies on the parser's arena-allocated AST. + +/// Information about a parameter, struct field, event/error parameter, etc. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParamInfo { + /// The parameter name, if any. + pub name: Option, + /// The type rendered as a string (e.g. `"uint256"`, `"address"`). + pub ty: String, +} + +/// A base contract reference (e.g. `is IERC721, Ownable`). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BaseInfo { + /// The full dotted name (e.g. `"IERC721"` or `"SomeLib.Base"`). + pub name: String, + /// The last identifier in the path, used for linking. + pub ident: String, +} + +/// The kind of a contract-like definition. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ContractKind { + Contract, + Abstract, + Interface, + Library, +} + +impl ContractKind { + /// Returns the lowercase keyword string. + pub const fn as_str(&self) -> &'static str { + match self { + Self::Contract => "contract", + Self::Abstract => "abstract", + Self::Interface => "interface", + Self::Library => "library", + } + } +} + +/// Owned contract definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContractSource { + pub name: String, + pub kind: ContractKind, + pub bases: Vec, +} + +/// Owned function definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FunctionSource { + /// The function name, or `None` for unnamed functions (fallback/receive). + pub name: Option, + /// The function kind as a display string (e.g. `"function"`, `"constructor"`, `"fallback"`). + pub kind: String, + /// Function parameters. + pub params: Vec, + /// Return parameters. + pub returns: Vec, +} + +impl FunctionSource { + /// Get the signature of the function, including parameter types. + pub fn signature(&self) -> String { + let name = self.name.as_deref().unwrap_or(&self.kind); + if self.params.is_empty() { + return name.to_string(); + } + format!( + "{}({})", + name, + self.params.iter().map(|p| p.ty.as_str()).collect::>().join(",") + ) + } +} + +/// Variable attribute relevant for doc generation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VariableAttr { + Constant, + Immutable, +} + +/// Owned variable definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VariableSource { + pub name: String, + pub attrs: Vec, +} + +/// Owned event definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EventSource { + pub name: String, + pub fields: Vec, +} + +/// Owned error definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ErrorSource { + pub name: String, + pub fields: Vec, +} + +/// Owned struct definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StructSource { + pub name: String, + pub fields: Vec, +} + +/// Owned enum definition data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EnumSource { + pub name: String, + pub variants: Vec, +} + +/// Owned type definition data (user-defined value types). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TypeSource { + pub name: String, +} + +/// A wrapper type around owned source data extracted from the parse tree. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ParseSource { + /// Source contract definition. + Contract(ContractSource), + /// Source function definition. + Function(FunctionSource), + /// Source variable definition. + Variable(VariableSource), + /// Source event definition. + Event(EventSource), + /// Source error definition. + Error(ErrorSource), + /// Source struct definition. + Struct(StructSource), + /// Source enum definition. + Enum(EnumSource), + /// Source type definition. + Type(TypeSource), +} + +impl ParseSource { + /// Get the identity of the source. + pub fn ident(&self) -> String { + match self { + Self::Contract(c) => c.name.clone(), + Self::Variable(v) => v.name.clone(), + Self::Event(e) => e.name.clone(), + Self::Error(e) => e.name.clone(), + Self::Struct(s) => s.name.clone(), + Self::Enum(e) => e.name.clone(), + Self::Function(f) => f.name.clone().unwrap_or_else(|| f.kind.clone()), + Self::Type(t) => t.name.clone(), + } + } + + /// Get the signature of the source (for functions, includes parameter types). + pub fn signature(&self) -> String { + match self { + Self::Function(f) => f.signature(), + _ => self.ident(), + } + } +} diff --git a/crates/doc/src/preprocessor/contract_inheritance.rs b/crates/doc/src/preprocessor/contract_inheritance.rs index 915105bd5bd72..5df27c12a4e72 100644 --- a/crates/doc/src/preprocessor/contract_inheritance.rs +++ b/crates/doc/src/preprocessor/contract_inheritance.rs @@ -1,7 +1,6 @@ use super::{Preprocessor, PreprocessorId}; -use crate::{document::DocumentContent, Document, ParseSource, PreprocessorOutput}; +use crate::{Document, ParseSource, PreprocessorOutput, document::DocumentContent}; use alloy_primitives::map::HashMap; -use forge_fmt::solang_ext::SafeUnwrap; use std::path::PathBuf; /// [ContractInheritance] preprocessor id. @@ -10,7 +9,7 @@ pub const CONTRACT_INHERITANCE_ID: PreprocessorId = PreprocessorId("contract_inh /// The contract inheritance preprocessor. /// /// It matches the documents with inner [`ParseSource::Contract`](crate::ParseSource) elements, -/// iterates over their [Base](solang_parser::pt::Base)s and attempts +/// iterates over their base contracts and attempts /// to link them with the paths of the other contract documents. /// /// This preprocessor writes to [Document]'s context. @@ -27,23 +26,22 @@ impl Preprocessor for ContractInheritance { fn preprocess(&self, documents: Vec) -> Result, eyre::Error> { for document in &documents { - if let DocumentContent::Single(ref item) = document.content { - if let ParseSource::Contract(ref contract) = item.source { - let mut links = HashMap::default(); + if let DocumentContent::Single(ref item) = document.content + && let ParseSource::Contract(ref contract) = item.source + { + let mut links = HashMap::default(); - // Attempt to match bases to other contracts - for base in &contract.base { - let base_ident = base.name.identifiers.last().unwrap().name.clone(); - if let Some(linked) = self.try_link_base(&base_ident, &documents) { - links.insert(base_ident, linked); - } + // Attempt to match bases to other contracts + for base in &contract.bases { + let base_ident = base.ident.clone(); + if let Some(linked) = self.try_link_base(&base_ident, &documents) { + links.insert(base_ident, linked); } + } - if !links.is_empty() { - // Write to context - document - .add_context(self.id(), PreprocessorOutput::ContractInheritance(links)); - } + if !links.is_empty() { + // Write to context + document.add_context(self.id(), PreprocessorOutput::ContractInheritance(links)); } } } @@ -58,12 +56,11 @@ impl ContractInheritance { if candidate.from_library && !self.include_libraries { continue; } - if let DocumentContent::Single(ref item) = candidate.content { - if let ParseSource::Contract(ref contract) = item.source { - if base == contract.name.safe_unwrap().name { - return Some(candidate.target_path.clone()) - } - } + if let DocumentContent::Single(ref item) = candidate.content + && let ParseSource::Contract(ref contract) = item.source + && base == contract.name + { + return Some(candidate.relative_output_path().to_path_buf()); } } None diff --git a/crates/doc/src/preprocessor/git_source.rs b/crates/doc/src/preprocessor/git_source.rs index fcf910359de63..d8136d27a5ead 100644 --- a/crates/doc/src/preprocessor/git_source.rs +++ b/crates/doc/src/preprocessor/git_source.rs @@ -26,7 +26,7 @@ impl Preprocessor for GitSource { fn preprocess(&self, documents: Vec) -> Result, eyre::Error> { if let Some(ref repo) = self.repository { let repo = repo.trim_end_matches('/'); - let commit = self.commit.clone().unwrap_or("master".to_owned()); + let commit = self.commit.as_deref().unwrap_or("master"); for document in &documents { if document.from_library { continue; diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs index 7f401e6d41dc2..6fdda0e40d290 100644 --- a/crates/doc/src/preprocessor/infer_hyperlinks.rs +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -1,6 +1,5 @@ use super::{Preprocessor, PreprocessorId}; use crate::{Comments, Document, ParseItem, ParseSource}; -use forge_fmt::solang_ext::SafeUnwrap; use regex::{Captures, Match, Regex}; use std::{ borrow::Cow, @@ -85,13 +84,16 @@ impl InferInlineHyperlinks { for item in items { match &item.source { ParseSource::Contract(contract) => { - let name = &contract.name.safe_unwrap().name; + let name = &contract.name; if name == link.identifier { if link.part.is_none() { - return Some(InlineLinkTarget::borrowed(name, target_path.to_path_buf())) + return Some(InlineLinkTarget::borrowed( + name, + target_path.to_path_buf(), + )); } // try to find the referenced item in the contract's children - return Self::find_match(link, target_path, item.children.iter()) + return Self::find_match(link, target_path, item.children.iter()); } } ParseSource::Function(fun) => { @@ -100,39 +102,42 @@ impl InferInlineHyperlinks { // have so we can match the correct one if let Some(id) = &fun.name { // Note: constructors don't have a name - if id.name == link.ref_name() { - return Some(InlineLinkTarget::borrowed( - &id.name, - target_path.to_path_buf(), - )) + if id == link.ref_name() { + return Some(InlineLinkTarget::borrowed(id, target_path.to_path_buf())); } } else if link.ref_name() == "constructor" { return Some(InlineLinkTarget::borrowed( "constructor", target_path.to_path_buf(), - )) + )); } } ParseSource::Variable(_) => {} ParseSource::Event(ev) => { - let ev_name = &ev.name.safe_unwrap().name; + let ev_name = &ev.name; if ev_name == link.ref_name() { - return Some(InlineLinkTarget::borrowed(ev_name, target_path.to_path_buf())) + return Some(InlineLinkTarget::borrowed( + ev_name, + target_path.to_path_buf(), + )); } } ParseSource::Error(err) => { - let err_name = &err.name.safe_unwrap().name; + let err_name = &err.name; if err_name == link.ref_name() { - return Some(InlineLinkTarget::borrowed(err_name, target_path.to_path_buf())) + return Some(InlineLinkTarget::borrowed( + err_name, + target_path.to_path_buf(), + )); } } ParseSource::Struct(structdef) => { - let struct_name = &structdef.name.safe_unwrap().name; + let struct_name = &structdef.name; if struct_name == link.ref_name() { return Some(InlineLinkTarget::borrowed( struct_name, target_path.to_path_buf(), - )) + )); } } ParseSource::Enum(_) => {} @@ -195,7 +200,7 @@ struct InlineLinkTarget<'a> { } impl<'a> InlineLinkTarget<'a> { - fn borrowed(section: &'a str, target_path: PathBuf) -> Self { + const fn borrowed(section: &'a str, target_path: PathBuf) -> Self { Self { section: Cow::Borrowed(section), target_path } } } @@ -227,7 +232,7 @@ impl<'a> InlineLink<'a> { } fn captures(s: &'a str) -> impl Iterator + 'a { - RE_INLINE_LINK.captures(s).map(Self::from_capture).into_iter().flatten() + RE_INLINE_LINK.captures_iter(s).filter_map(Self::from_capture) } /// Parses the first inline link. @@ -253,22 +258,8 @@ impl<'a> InlineLink<'a> { self.exact_identifier().split('-').next().unwrap() } - fn exact_identifier(&self) -> &str { - let mut name = self.identifier; - if let Some(part) = self.part { - name = part; - } - name - } - - /// Returns the name of the referenced item and its arguments, if any. - /// - /// Eg: `safeMint-address-uint256-` returns `("safeMint", ["address", "uint256"])` - #[expect(unused)] - fn ref_name_exact(&self) -> (&str, impl Iterator + '_) { - let identifier = self.exact_identifier(); - let mut iter = identifier.split('-'); - (iter.next().unwrap(), iter.filter(|s| !s.is_empty())) + const fn exact_identifier(&self) -> &str { + if let Some(part) = self.part { part } else { self.identifier } } /// Returns the content of the matched link. @@ -277,7 +268,7 @@ impl<'a> InlineLink<'a> { } /// Returns true if the link is external. - fn is_external(&self) -> bool { + const fn is_external(&self) -> bool { self.part.is_some() } } diff --git a/crates/doc/src/preprocessor/inheritdoc.rs b/crates/doc/src/preprocessor/inheritdoc.rs index 8a3ef7fe338c3..15d190f83d49c 100644 --- a/crates/doc/src/preprocessor/inheritdoc.rs +++ b/crates/doc/src/preprocessor/inheritdoc.rs @@ -1,9 +1,8 @@ use super::{Preprocessor, PreprocessorId}; use crate::{ - document::DocumentContent, Comments, Document, ParseItem, ParseSource, PreprocessorOutput, + Comments, Document, ParseItem, ParseSource, PreprocessorOutput, document::DocumentContent, }; use alloy_primitives::map::HashMap; -use forge_fmt::solang_ext::SafeUnwrap; /// [`Inheritdoc`] preprocessor ID. pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc"); @@ -70,19 +69,18 @@ impl Inheritdoc { documents: &Vec, ) -> Option<(String, Comments)> { for candidate in documents { - if let DocumentContent::Single(ref item) = candidate.content { - if let ParseSource::Contract(ref contract) = item.source { - if base == contract.name.safe_unwrap().name { - // Not matched for the contract because it's a noop - // https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags + if let DocumentContent::Single(ref item) = candidate.content + && let ParseSource::Contract(ref contract) = item.source + && base == contract.name + { + // Not matched for the contract because it's a noop + // https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags - for children in &item.children { - // TODO: improve matching logic - if source.ident() == children.source.ident() { - let key = format!("{}.{}", base, source.ident()); - return Some((key, children.comments.clone())) - } - } + for children in &item.children { + // Match using signature for functions (includes parameter types for overloads) + if source.signature() == children.source.signature() { + let key = format!("{}.{}", base, source.signature()); + return Some((key, children.comments.clone())); } } } diff --git a/crates/doc/src/preprocessor/mod.rs b/crates/doc/src/preprocessor/mod.rs index 5011b59a1b5e8..e51bf3cbba5de 100644 --- a/crates/doc/src/preprocessor/mod.rs +++ b/crates/doc/src/preprocessor/mod.rs @@ -5,19 +5,19 @@ use alloy_primitives::map::HashMap; use std::{fmt::Debug, path::PathBuf}; mod contract_inheritance; -pub use contract_inheritance::{ContractInheritance, CONTRACT_INHERITANCE_ID}; +pub use contract_inheritance::{CONTRACT_INHERITANCE_ID, ContractInheritance}; mod inheritdoc; -pub use inheritdoc::{Inheritdoc, INHERITDOC_ID}; +pub use inheritdoc::{INHERITDOC_ID, Inheritdoc}; mod infer_hyperlinks; -pub use infer_hyperlinks::{InferInlineHyperlinks, INFER_INLINE_HYPERLINKS_ID}; +pub use infer_hyperlinks::{INFER_INLINE_HYPERLINKS_ID, InferInlineHyperlinks}; mod git_source; -pub use git_source::{GitSource, GIT_SOURCE_ID}; +pub use git_source::{GIT_SOURCE_ID, GitSource}; mod deployments; -pub use deployments::{Deployment, Deployments, DEPLOYMENTS_ID}; +pub use deployments::{DEPLOYMENTS_ID, Deployment, Deployments}; /// The preprocessor id. #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index efdf45af25a83..888f6269623a5 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,14 +1,12 @@ use crate::{ - document::{read_context, DocumentContent}, + BaseInfo, CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document, + FunctionSource, GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, VariableAttr, + document::{DocumentContent, read_context}, parser::ParseSource, writer::BufWriter, - CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput, - CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID, }; -use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; -use solang_parser::pt::{Base, FunctionDefinition}; -use std::path::{Path, PathBuf}; +use std::path::Path; /// The result of [`AsDoc::as_doc`]. pub type AsDocResult = Result; @@ -36,6 +34,14 @@ impl AsDoc for CommentsRef<'_> { fn as_doc(&self) -> AsDocResult { let mut writer = BufWriter::default(); + // Write title tag(s) + let titles = self.include_tag(CommentTag::Title); + if !titles.is_empty() { + writer.write_bold(&format!("Title{}:", if titles.len() == 1 { "" } else { "s" }))?; + writer.writeln_raw(titles.iter().map(|t| &t.value).join(", "))?; + writer.writeln()?; + } + // Write author tag(s) let authors = self.include_tag(CommentTag::Author); if !authors.is_empty() { @@ -54,7 +60,7 @@ impl AsDoc for CommentsRef<'_> { // Write dev tags let devs = self.include_tag(CommentTag::Dev); for d in devs.iter() { - writer.write_italic(&d.value)?; + writer.write_dev_content(&d.value)?; writer.writeln()?; } @@ -66,8 +72,8 @@ impl AsDoc for CommentsRef<'_> { writer.writeln_raw(format!( "{}{}: {}", if customs.len() == 1 { "" } else { "- " }, - &c.tag, - &c.value + c.tag, + c.value ))?; writer.writeln()?; } @@ -77,9 +83,9 @@ impl AsDoc for CommentsRef<'_> { } } -impl AsDoc for Base { +impl AsDoc for BaseInfo { fn as_doc(&self) -> AsDocResult { - Ok(self.name.identifiers.iter().map(|ident| ident.name.to_owned()).join(".")) + Ok(self.name.clone()) } } @@ -97,17 +103,7 @@ impl AsDoc for Document { } for item in items { - let func = item.as_function().unwrap(); - let mut heading = item.source.ident(); - if !func.params.is_empty() { - heading.push_str(&format!( - "({})", - func.params - .iter() - .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default()) - .join(", ") - )); - } + let heading = item.source.signature().replace(',', ", "); writer.write_heading(&heading)?; writer.write_section(&item.comments, &item.code)?; } @@ -121,7 +117,7 @@ impl AsDoc for Document { for item in items { let var = item.as_variable().unwrap(); - writer.write_heading(&var.name.safe_unwrap().name)?; + writer.write_heading(&var.name)?; writer.write_section(&item.comments, &item.code)?; } } @@ -138,28 +134,25 @@ impl AsDoc for Document { match &item.source { ParseSource::Contract(contract) => { - if !contract.base.is_empty() { + if !contract.bases.is_empty() { writer.write_bold("Inherits:")?; - // we need this to find the _relative_ paths - let src_target_dir = self.target_src_dir(); - let mut bases = vec![]; let linked = read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance); - for base in &contract.base { + for base in &contract.bases { let base_doc = base.as_doc()?; - let base_ident = &base.name.identifiers.last().unwrap().name; + let base_ident = &base.ident; let link = linked .as_ref() .and_then(|link| { link.get(base_ident).map(|path| { - let path = Path::new("/").join( - path.strip_prefix(&src_target_dir) - .ok() - .unwrap_or(path), - ); + let path = if cfg!(windows) { + Path::new("\\").join(path) + } else { + Path::new("/").join(path) + }; Markdown::Link(&base_doc, &path.display().to_string()) .as_doc() }) @@ -176,18 +169,44 @@ impl AsDoc for Document { writer.writeln_doc(&item.comments)?; - if let Some(state_vars) = item.variables() { - writer.write_subtitle("State Variables")?; - state_vars.into_iter().try_for_each(|(item, comments, code)| { - let comments = comments.merge_inheritdoc( - &item.name.safe_unwrap().name, - read_context!(self, INHERITDOC_ID, Inheritdoc), - ); - - writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(&comments, code)?; - writer.writeln() - })?; + if let Some(all_vars) = item.variables() { + let (constants, state_vars): (Vec<_>, Vec<_>) = + all_vars.into_iter().partition(|(item, _, _)| { + item.attrs.iter().any(|attr| { + matches!( + attr, + VariableAttr::Constant | VariableAttr::Immutable + ) + }) + }); + + if !constants.is_empty() { + writer.write_subtitle("Constants")?; + constants.into_iter().try_for_each(|(item, comments, code)| { + let comments = comments.merge_inheritdoc( + &item.name, + read_context!(self, INHERITDOC_ID, Inheritdoc), + ); + + writer.write_heading(&item.name)?; + writer.write_section(&comments, code)?; + writer.writeln() + })?; + } + + if !state_vars.is_empty() { + writer.write_subtitle("State Variables")?; + state_vars.into_iter().try_for_each(|(item, comments, code)| { + let comments = comments.merge_inheritdoc( + &item.name, + read_context!(self, INHERITDOC_ID, Inheritdoc), + ); + + writer.write_heading(&item.name)?; + writer.write_section(&comments, code)?; + writer.writeln() + })?; + } } if let Some(funcs) = item.functions() { @@ -201,7 +220,7 @@ impl AsDoc for Document { if let Some(events) = item.events() { writer.write_subtitle("Events")?; events.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_events_table(&item.fields, comments) })?; @@ -210,7 +229,7 @@ impl AsDoc for Document { if let Some(errors) = item.errors() { writer.write_subtitle("Errors")?; errors.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_errors_table(&item.fields, comments) })?; @@ -219,7 +238,7 @@ impl AsDoc for Document { if let Some(structs) = item.structs() { writer.write_subtitle("Structs")?; structs.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; + writer.write_heading(&item.name)?; writer.write_section(comments, code)?; writer.try_write_properties_table(&item.fields, comments) })?; @@ -228,8 +247,9 @@ impl AsDoc for Document { if let Some(enums) = item.enums() { writer.write_subtitle("Enums")?; enums.into_iter().try_for_each(|(item, comments, code)| { - writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code) + writer.write_heading(&item.name)?; + writer.write_section(comments, code)?; + writer.try_write_variant_table(item, comments) })?; } } @@ -245,16 +265,16 @@ impl AsDoc for Document { writer.write_code(&item.code)?; // Write function parameter comments in a table - let params = - func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Param, ¶ms, &item.comments)?; + writer.try_write_param_table( + CommentTag::Param, + &func.params, + &item.comments, + )?; // Write function return parameter comments in a table - let returns = - func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); writer.try_write_param_table( CommentTag::Return, - &returns, + &func.returns, &item.comments, )?; @@ -286,22 +306,18 @@ impl AsDoc for Document { } impl Document { - /// Where all the source files are written to - fn target_src_dir(&self) -> PathBuf { - self.out_target_dir.join("src") - } - /// Writes a function to the buffer. fn write_function( &self, writer: &mut BufWriter, - func: &FunctionDefinition, + func: &FunctionSource, comments: &Comments, code: &str, ) -> Result<(), std::fmt::Error> { - let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); + let func_name = func.name.as_deref().unwrap_or(&func.kind).to_string(); + let func_sign = func.signature(); let comments = - comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); + comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc)); // Write function name writer.write_heading(&func_name)?; @@ -315,12 +331,10 @@ impl Document { writer.write_code(code)?; // Write function parameter comments in a table - let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?; + writer.try_write_param_table(CommentTag::Param, &func.params, &comments)?; // Write function return parameter comments in a table - let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); - writer.try_write_param_table(CommentTag::Return, &returns, &comments)?; + writer.try_write_param_table(CommentTag::Return, &func.returns, &comments)?; writer.writeln()?; Ok(()) diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 4bb7f9612db47..c5b7142b57b64 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,6 +1,8 @@ -use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; +use crate::{ + AsDoc, CommentTag, Comments, Deployment, EnumSource, Markdown, ParamInfo, + writer::traits::ParamLike, +}; use itertools::Itertools; -use solang_parser::pt::{ErrorParameter, EventParameter, Parameter, VariableDeclaration}; use std::{ fmt::{self, Display, Write}, sync::LazyLock, @@ -19,6 +21,11 @@ const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"]; static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock = LazyLock::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); +/// Headers and separator for rendering the variants table. +const VARIANTS_TABLE_HEADERS: &[&str] = &["Name", "Description"]; +static VARIANTS_TABLE_SEPARATOR: LazyLock = + LazyLock::new(|| VARIANTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); + /// The buffered writer. /// Writes various display items into the internal buffer. #[derive(Debug, Default)] @@ -33,7 +40,7 @@ impl BufWriter { } /// Returns true if the buffer is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.buf.is_empty() } @@ -82,6 +89,17 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } + /// Writes dev content to the buffer, handling markdown lists properly. + /// If the content contains markdown lists, it formats them correctly. + /// Otherwise, it writes the content in italics. + pub fn write_dev_content(&mut self, text: &str) -> fmt::Result { + for line in text.lines() { + writeln!(self.buf, "{line}")?; + } + + Ok(()) + } + /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) @@ -132,7 +150,7 @@ impl BufWriter { // There is nothing to write. if params.is_empty() || comments.is_empty() { - return Ok(()) + return Ok(()); } self.write_bold(heading)?; @@ -156,7 +174,7 @@ impl BufWriter { let row = [ Markdown::Code(param_name.unwrap_or("")).as_doc()?, - Markdown::Code(¶m.type_name()).as_doc()?, + Markdown::Code(param.type_name()).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; @@ -171,17 +189,56 @@ impl BufWriter { /// Doesn't write anything if either params or comments are empty. pub fn try_write_properties_table( &mut self, - params: &[VariableDeclaration], + params: &[ParamInfo], comments: &Comments, ) -> fmt::Result { self.try_write_table(CommentTag::Param, params, comments, "Properties") } + /// Tries to write the variant table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_variant_table( + &mut self, + params: &EnumSource, + comments: &Comments, + ) -> fmt::Result { + let comments = comments.include_tags(&[CommentTag::Param]); + + // There is nothing to write. + if comments.is_empty() { + return Ok(()); + } + + self.write_bold("Variants")?; + self.writeln()?; + + self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?; + self.write_piped(&VARIANTS_TABLE_SEPARATOR)?; + + for variant in ¶ms.variants { + let param_name = Some(variant.clone()); + + let comment = param_name.as_ref().and_then(|name| { + comments.iter().find_map(|comment| comment.match_first_word(name)) + }); + + let row = [ + Markdown::Code(variant.as_str()).as_doc()?, + comment.unwrap_or_default().replace('\n', " "), + ]; + self.write_piped(&row.join("|"))?; + } + + self.writeln()?; + + Ok(()) + } + /// Tries to write the parameters table to the buffer. /// Doesn't write anything if either params or comments are empty. pub fn try_write_events_table( &mut self, - params: &[EventParameter], + params: &[ParamInfo], comments: &Comments, ) -> fmt::Result { self.try_write_table(CommentTag::Param, params, comments, "Parameters") @@ -191,7 +248,7 @@ impl BufWriter { /// Doesn't write anything if either params or comments are empty. pub fn try_write_errors_table( &mut self, - params: &[ErrorParameter], + params: &[ParamInfo], comments: &Comments, ) -> fmt::Result { self.try_write_table(CommentTag::Param, params, comments, "Parameters") @@ -202,7 +259,7 @@ impl BufWriter { pub fn try_write_param_table( &mut self, tag: CommentTag, - params: &[&Parameter], + params: &[ParamInfo], comments: &Comments, ) -> fmt::Result { let heading = match &tag { diff --git a/crates/doc/src/writer/traits.rs b/crates/doc/src/writer/traits.rs index 0b79718d5102f..7b0fbcc813a2b 100644 --- a/crates/doc/src/writer/traits.rs +++ b/crates/doc/src/writer/traits.rs @@ -1,58 +1,23 @@ //! Helper traits for writing documentation. -use solang_parser::pt::Expression; +use crate::ParamInfo; -/// Helper trait to abstract over a solang type that can be documented as parameter +/// Helper trait to abstract over a type that can be documented as a parameter. pub(crate) trait ParamLike { - /// Returns the type of the parameter. - fn ty(&self) -> &Expression; - /// Returns the type as a string. - fn type_name(&self) -> String { - self.ty().to_string() - } + fn type_name(&self) -> &str; /// Returns the identifier of the parameter. fn name(&self) -> Option<&str>; } -impl ParamLike for solang_parser::pt::Parameter { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::VariableDeclaration { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::EventParameter { - fn ty(&self) -> &Expression { - &self.ty - } - - fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) - } -} - -impl ParamLike for solang_parser::pt::ErrorParameter { - fn ty(&self) -> &Expression { +impl ParamLike for ParamInfo { + fn type_name(&self) -> &str { &self.ty } fn name(&self) -> Option<&str> { - self.name.as_ref().map(|id| id.name.as_str()) + self.name.as_deref() } } @@ -60,8 +25,8 @@ impl ParamLike for &T where T: ParamLike, { - fn ty(&self) -> &Expression { - T::ty(*self) + fn type_name(&self) -> &str { + T::type_name(*self) } fn name(&self) -> Option<&str> { diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json index 54e6d46dff349..f275f471087ee 100644 --- a/crates/evm/abi/src/Console.json +++ b/crates/evm/abi/src/Console.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"name":"table","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index 2e28fece21e42..d0b32ff410941 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -16,12 +16,15 @@ def main(): # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() sig_strings = re.findall( - r'"(log.*?)"', + r'"((?:log|table).*?)"', console_sol, ) raw_sigs = [s.strip().strip('"') for s in sig_strings] sigs = [ - s.replace("string", "string memory").replace("bytes)", "bytes memory)") + re.sub(r"(\w+\[\])", r"\1 memory", s) + .replace("string,", "string memory,") + .replace("string)", "string memory)") + .replace("bytes)", "bytes memory)") for s in raw_sigs ] sigs = list(set(sigs)) @@ -38,6 +41,7 @@ def main(): ) combined = json.loads(r.stdout.strip()) abi = combined["contracts"][":HardhatConsole"]["abi"] + open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) diff --git a/crates/evm/abi/src/lib.rs b/crates/evm/abi/src/lib.rs index 8e9313c2f516e..0887a4a1c2cc6 100644 --- a/crates/evm/abi/src/lib.rs +++ b/crates/evm/abi/src/lib.rs @@ -1,6 +1,6 @@ //! Solidity ABI-related utilities and [`sol!`](alloy_sol_types::sol) definitions. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] pub mod console; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 45097ca47f1fa..801e813026a39 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -18,10 +18,14 @@ foundry-cheatcodes-spec.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm-abi.workspace = true +foundry-evm-hardforks.workspace = true +foundry-evm-networks.workspace = true +alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } -alloy-evm.workspace = true +alloy-evm = { workspace = true, features = ["rpc"] } alloy-genesis.workspace = true +alloy-hardforks.workspace = true alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = [ "serde", @@ -32,8 +36,10 @@ alloy-primitives = { workspace = true, features = [ alloy-provider.workspace = true alloy-network.workspace = true alloy-consensus.workspace = true -alloy-rpc-types.workspace = true +alloy-op-evm = { workspace = true, optional = true } +alloy-rpc-types = { workspace = true, features = ["anvil"] } alloy-sol-types.workspace = true +alloy-rlp.workspace = true foundry-fork-db.workspace = true revm = { workspace = true, features = [ @@ -46,11 +52,17 @@ revm = { workspace = true, features = [ "arbitrary", "c-kzg", "blst", - "secp256r1", ] } revm-inspectors.workspace = true -op-revm.workspace = true -alloy-op-evm.workspace = true +op-alloy-consensus = { workspace = true, features = ["k256"], optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } +tempo-revm.workspace = true +tempo-alloy.workspace = true +tempo-contracts.workspace = true +tempo-evm.workspace = true +tempo-precompiles.workspace = true auto_impl.workspace = true eyre.workspace = true @@ -65,4 +77,19 @@ tracing.workspace = true url.workspace = true [dev-dependencies] +alloy-serde.workspace = true +anvil.workspace = true foundry-test-utils.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", + "foundry-common/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", +] diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 4220150e6536b..87a92e9d97460 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -2,28 +2,32 @@ use super::BackendError; use crate::{ + FoundryInspectorExt, backend::{ - diagnostic::RevertDiagnostic, Backend, DatabaseExt, JournaledState, LocalForkId, - RevertStateSnapshotAction, + Backend, DatabaseExt, JournaledState, LocalForkId, RevertStateSnapshotAction, + diagnostic::RevertDiagnostic, + }, + evm::{ + EvmEnvFor, FoundryContextFor, FoundryEvmFactory, FoundryEvmNetwork, HaltReasonFor, SpecFor, + TxEnvFor, }, fork::{CreateFork, ForkId}, - AsEnvMut, Env, EnvMut, InspectorExt, }; use alloy_evm::Evm; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; -use alloy_rpc_types::TransactionRequest; +use alloy_primitives::{Address, B256, TxKind, U256}; use eyre::WrapErr; use foundry_fork_db::DatabaseError; use revm::{ + Database, DatabaseCommit, bytecode::Bytecode, + context::{ContextTr, Transaction}, context_interface::result::ResultAndState, database::DatabaseRef, - primitives::{hardfork::SpecId, HashMap as Map}, - state::{Account, AccountInfo}, - Database, DatabaseCommit, + primitives::AddressMap, + state::{Account, AccountInfo, EvmState}, }; -use std::{borrow::Cow, collections::BTreeMap}; +use std::{borrow::Cow, collections::BTreeMap, fmt::Debug}; /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. /// @@ -41,22 +45,35 @@ use std::{borrow::Cow, collections::BTreeMap}; /// don't make use of them. Alternatively each test case would require its own `Backend` clone, /// which would add significant overhead for large fuzz sets even if the Database is not big after /// setup. -#[derive(Clone, Debug)] -pub struct CowBackend<'a> { +pub struct CowBackend<'a, FEN: FoundryEvmNetwork> { /// The underlying `Backend`. /// /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. - pub backend: Cow<'a, Backend>, - /// Keeps track of whether the backed is already initialized - is_initialized: bool, - /// The [SpecId] of the current backend. - spec_id: SpecId, + pub backend: Cow<'a, Backend>, + /// Pending initialization params for the backend on first mutable access. + /// `None` means the backend has already been initialized for the current call. + pending_init: Option<(SpecFor, Address, TxKind)>, +} + +impl Clone for CowBackend<'_, FEN> { + fn clone(&self) -> Self { + Self { backend: self.backend.clone(), pending_init: self.pending_init } + } } -impl<'a> CowBackend<'a> { +impl Debug for CowBackend<'_, FEN> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CowBackend") + .field("backend", &self.backend) + .field("pending_init", &self.pending_init) + .finish() + } +} + +impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> { /// Creates a new `CowBackend` with the given `Backend`. - pub fn new_borrowed(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::default() } + pub const fn new_borrowed(backend: &'a Backend) -> Self { + Self { backend: Cow::Borrowed(backend), pending_init: None } } /// Executes the configured transaction of the `env` without committing state changes @@ -64,21 +81,26 @@ impl<'a> CowBackend<'a> { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( + pub fn inspect FoundryInspectorExt>>( &mut self, - env: &mut Env, - inspector: &mut I, - ) -> eyre::Result { + evm_env: &mut EvmEnvFor, + tx_env: &mut TxEnvFor, + inspector: I, + ) -> eyre::Result>> { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state - self.is_initialized = false; - self.spec_id = env.evm_env.cfg_env.spec; + self.pending_init = Some((evm_env.cfg_env.spec, tx_env.caller(), tx_env.kind())); - let mut evm = crate::evm::new_evm_with_inspector(self, env.to_owned(), inspector); + let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector( + self, + evm_env.clone(), + inspector, + ); - let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; + let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; - *env = evm.as_env_mut().to_owned(); + *tx_env = evm.tx().clone(); + *evm_env = evm.finish().1; Ok(res) } @@ -93,46 +115,48 @@ impl<'a> CowBackend<'a> { /// Returns a mutable instance of the Backend. /// /// If this is the first time this is called, the backed is cloned and initialized. - fn backend_mut(&mut self, env: &EnvMut<'_>) -> &mut Backend { - if !self.is_initialized { + fn backend_mut(&mut self) -> &mut Backend { + if let Some((spec_id, caller, tx_kind)) = self.pending_init.take() { let backend = self.backend.to_mut(); - let mut env = env.to_owned(); - env.evm_env.cfg_env.spec = self.spec_id; - backend.initialize(&env); - self.is_initialized = true; - return backend + backend.initialize(spec_id, caller, tx_kind); + return backend; } self.backend.to_mut() } /// Returns a mutable instance of the Backend if it is initialized. - fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { - if self.is_initialized { - return Some(self.backend.to_mut()) + fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { + if self.pending_init.is_none() { + return Some(self.backend.to_mut()); } None } } -impl DatabaseExt for CowBackend<'_> { - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256 { - self.backend_mut(env).snapshot_state(journaled_state, env) +impl DatabaseExt for CowBackend<'_, FEN> { + fn snapshot_state( + &mut self, + journaled_state: &JournaledState, + evm_env: &EvmEnvFor, + ) -> U256 { + self.backend_mut().snapshot_state(journaled_state, evm_env) } fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, - current: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, + caller: Address, action: RevertStateSnapshotAction, ) -> Option { - self.backend_mut(current).revert_state(id, journaled_state, current, action) + self.backend_mut().revert_state(id, journaled_state, evm_env, caller, action) } fn delete_state_snapshot(&mut self, id: U256) -> bool { // delete state snapshot requires a previous snapshot to be initialized if let Some(backend) = self.initialized_backend_mut() { - return backend.delete_state_snapshot(id) + return backend.delete_state_snapshot(id); } false } @@ -158,62 +182,56 @@ impl DatabaseExt for CowBackend<'_> { fn select_fork( &mut self, id: LocalForkId, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, + tx_env: &mut TxEnvFor, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(env).select_fork(id, env, journaled_state) + self.backend_mut().select_fork(id, evm_env, tx_env, journaled_state) } fn roll_fork( &mut self, id: Option, block_number: u64, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) + self.backend_mut().roll_fork(id, block_number, evm_env, journaled_state) } fn roll_fork_to_transaction( &mut self, id: Option, transaction: B256, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) + self.backend_mut().roll_fork_to_transaction(id, transaction, evm_env, journaled_state) } fn transact( &mut self, id: Option, transaction: B256, - mut env: Env, + evm_env: EvmEnvFor, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt< + ::FoundryContext<'db>, + >, ) -> eyre::Result<()> { - self.backend_mut(&env.as_env_mut()).transact( - id, - transaction, - env, - journaled_state, - inspector, - ) + self.backend_mut().transact(id, transaction, evm_env, journaled_state, inspector) } fn transact_from_tx( &mut self, - transaction: &TransactionRequest, - mut env: Env, + tx_env: TxEnvFor, + evm_env: EvmEnvFor, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt< + ::FoundryContext<'db>, + >, ) -> eyre::Result<()> { - self.backend_mut(&env.as_env_mut()).transact_from_tx( - transaction, - env, - journaled_state, - inspector, - ) + self.backend_mut().transact_from_tx(tx_env, evm_env, journaled_state, inspector) } fn active_fork_id(&self) -> Option { @@ -232,12 +250,8 @@ impl DatabaseExt for CowBackend<'_> { self.backend.ensure_fork_id(id) } - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option { - self.backend.diagnose_revert(callee, journaled_state) + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option { + self.backend.diagnose_revert(callee, evm_state) } fn load_allocs( @@ -245,7 +259,7 @@ impl DatabaseExt for CowBackend<'_> { allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default().as_env_mut()).load_allocs(allocs, journaled_state) + self.backend.to_mut().load_allocs(allocs, journaled_state) } fn clone_account( @@ -254,11 +268,7 @@ impl DatabaseExt for CowBackend<'_> { target: &Address, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default().as_env_mut()).clone_account( - source, - target, - journaled_state, - ) + self.backend.to_mut().clone_account(source, target, journaled_state) } fn is_persistent(&self, acc: &Address) -> bool { @@ -290,7 +300,7 @@ impl DatabaseExt for CowBackend<'_> { } } -impl DatabaseRef for CowBackend<'_> { +impl DatabaseRef for CowBackend<'_, FEN> { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -310,7 +320,7 @@ impl DatabaseRef for CowBackend<'_> { } } -impl Database for CowBackend<'_> { +impl Database for CowBackend<'_, FEN> { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -330,8 +340,8 @@ impl Database for CowBackend<'_> { } } -impl DatabaseCommit for CowBackend<'_> { - fn commit(&mut self, changes: Map) { +impl DatabaseCommit for CowBackend<'_, FEN> { + fn commit(&mut self, changes: AddressMap) { self.backend.to_mut().commit(changes) } } diff --git a/crates/evm/core/src/backend/diagnostic.rs b/crates/evm/core/src/backend/diagnostic.rs index df215508da1c0..02ffa2881c781 100644 --- a/crates/evm/core/src/backend/diagnostic.rs +++ b/crates/evm/core/src/backend/diagnostic.rs @@ -1,5 +1,5 @@ use crate::backend::LocalForkId; -use alloy_primitives::{map::AddressHashMap, Address}; +use alloy_primitives::{Address, map::AddressHashMap}; use itertools::Itertools; /// Represents possible diagnostic cases on revert @@ -41,7 +41,9 @@ impl RevertDiagnostic { if *persistent { format!("Contract {contract_label} does not exist") } else { - format!("Contract {contract_label} does not exist and is not marked as persistent, see `vm.makePersistent()`") + format!( + "Contract {contract_label} does not exist and is not marked as persistent, see `vm.makePersistent()`" + ) } } } diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index 42456cceadd16..e9ed62a788284 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -24,8 +24,6 @@ pub enum BackendError { For a test environment, you can use `etch` to place the required bytecode at that address." )] MissingCreate2Deployer, - #[error("{0}")] - Other(String), } impl BackendError { @@ -60,6 +58,7 @@ impl> From> for BackendError { EVMError::Custom(err) => Self::msg(err), EVMError::Header(err) => Self::msg(err.to_string()), EVMError::Transaction(err) => Self::msg(err.to_string()), + EVMError::CustomAny(err) => Self::msg(err.to_string()), } } } diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index 0ffa718fb3671..7ae2002283ded 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -4,11 +4,11 @@ use crate::state_snapshot::StateSnapshots; use alloy_primitives::{Address, B256, U256}; use foundry_fork_db::DatabaseError; use revm::{ + Database, DatabaseCommit, bytecode::Bytecode, database::{CacheDB, DatabaseRef, EmptyDB}, - primitives::HashMap as Map, + primitives::AddressMap, state::{Account, AccountInfo}, - Database, DatabaseCommit, }; /// Type alias for an in-memory database. @@ -73,7 +73,7 @@ impl Database for MemDb { } impl DatabaseCommit for MemDb { - fn commit(&mut self, changes: Map) { + fn commit(&mut self, changes: AddressMap) { DatabaseCommit::commit(&mut self.inner, changes) } } @@ -133,16 +133,16 @@ mod tests { // call `basic` on a non-existing account let info = Database::basic(&mut db, address).unwrap(); assert!(info.is_none()); + let mut info = info.unwrap_or_default(); info.balance = U256::from(500u64); // insert the modified account info db.insert_account_info(address, info); - // when fetching again, the `AccountInfo` is still `None` because the state of the account - // is `AccountState::NotExisting`, see + // now we can call `basic` again and it should return the inserted account info let info = Database::basic(&mut db, address).unwrap(); - assert!(info.is_none()); + assert!(info.is_some()); } /// Demonstrates how to insert a new account but not mark it as non-existing diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 892bf40c075bf..5c5fe29c3154c 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1,35 +1,40 @@ //! Foundry's main executor backend abstraction and implementation. use crate::{ + FoundryBlock, FoundryInspectorExt, FoundryTransaction, FromAnyRpcTransaction, constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, - evm::new_evm_with_inspector, + evm::{ + BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory, + FoundryEvmNetwork, HaltReasonFor, PrecompilesFor, SpecFor, TxEnvFor, + }, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, - utils::{configure_tx_env, configure_tx_req_env}, - AsEnvMut, Env, EnvMut, InspectorExt, + utils::get_blob_base_fee_update_fraction, }; -use alloy_consensus::Typed2718; -use alloy_evm::Evm; +use alloy_consensus::{BlockHeader, Typed2718}; +use alloy_evm::{Evm, EvmEnv, EvmFactory}; use alloy_genesis::GenesisAccount; -use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; -use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256}; -use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; +use alloy_network::{ + AnyNetwork, AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, TransactionResponse, +}; +use alloy_primitives::{Address, B256, TxKind, U256, keccak256, uint}; +use alloy_rpc_types::BlockNumberOrTag; use eyre::Context; -use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; -pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; +use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender}; +pub use foundry_fork_db::{BlockchainDb, ForkBlockEnv, SharedBackend, cache::BlockchainDbMeta}; +use itertools::Itertools; use revm::{ + Database, DatabaseCommit, JournalEntry, bytecode::Bytecode, - context::JournalInner, - context_interface::{block::BlobExcessGasAndPrice, result::ResultAndState}, - database::{CacheDB, DatabaseRef}, - inspector::NoOpInspector, - precompile::{PrecompileSpecId, Precompiles}, - primitives::{hardfork::SpecId, HashMap as Map, Log, KECCAK_EMPTY}, + context::{Block, BlockEnv, CfgEnv, ContextTr, JournalInner, Transaction}, + context_interface::{journaled_state::account::JournaledAccountTr, result::ResultAndState}, + database::{CacheDB, DatabaseRef, EmptyDB}, + primitives::{AddressMap, HashMap as Map, KECCAK_EMPTY, Log}, state::{Account, AccountInfo, EvmState, EvmStorageSlot}, - Database, DatabaseCommit, JournalEntry, }; use std::{ collections::{BTreeMap, HashMap, HashSet}, + fmt::Debug, time::Instant, }; @@ -49,7 +54,7 @@ mod snapshot; pub use snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapshot}; // A `revm::Database` that is used in forking mode -type ForkDB = CacheDB; +type ForkDB = CacheDB>; /// Represents a numeric `ForkId` valid only for the existence of the `Backend`. /// @@ -76,13 +81,19 @@ pub type JournaledState = JournalInner; /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] -pub trait DatabaseExt: Database + DatabaseCommit { +pub trait DatabaseExt: + Database + DatabaseCommit + Debug +{ /// Creates a new state snapshot at the current point of execution. /// /// A state snapshot is associated with a new unique id that's created for the snapshot. /// State snapshots can be reverted: [DatabaseExt::revert_state], however, depending on the /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256; + fn snapshot_state( + &mut self, + journaled_state: &JournaledState, + evm_env: &EvmEnv, + ) -> U256; /// Reverts the snapshot if it exists /// @@ -92,15 +103,16 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// **N.B.** While this reverts the state of the evm to the snapshot, it keeps new logs made /// since the snapshots was created. This way we can show logs that were emitted between /// snapshot and its revert. - /// This will also revert any changes in the `Env` and replace it with the captured `Env` of - /// `Self::snapshot_state`. + /// This will also revert any changes in the `EvmEnv` and `TxEnv` and replace them with the + /// captured values from `Self::snapshot_state`. /// /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, + caller: Address, action: RevertStateSnapshotAction, ) -> Option; @@ -119,11 +131,12 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn create_select_fork( &mut self, fork: CreateFork, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, + tx_env: &mut F::Tx, journaled_state: &mut JournaledState, ) -> eyre::Result { let id = self.create_fork(fork)?; - self.select_fork(id, env, journaled_state)?; + self.select_fork(id, evm_env, tx_env, journaled_state)?; Ok(id) } @@ -133,12 +146,13 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn create_select_fork_at_transaction( &mut self, fork: CreateFork, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, + tx_env: &mut F::Tx, journaled_state: &mut JournaledState, transaction: B256, ) -> eyre::Result { let id = self.create_fork_at_transaction(fork, transaction)?; - self.select_fork(id, env, journaled_state)?; + self.select_fork(id, evm_env, tx_env, journaled_state)?; Ok(id) } @@ -154,7 +168,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// Selects the fork's state /// - /// This will also modify the current `Env`. + /// This will also modify the current `EvmEnv` and `TxEnv`. /// /// **Note**: this does not change the local state, but swaps the remote state /// @@ -164,7 +178,8 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn select_fork( &mut self, id: LocalForkId, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, + tx_env: &mut F::Tx, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -179,7 +194,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: Option, block_number: u64, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -195,7 +210,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: Option, transaction: B256, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnv, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -204,18 +219,18 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: Option, transaction: B256, - env: Env, + evm_env: EvmEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()>; /// Executes a given TransactionRequest, commits the new state to the DB fn transact_from_tx( &mut self, - transaction: &TransactionRequest, - env: Env, + tx_env: F::Tx, + evm_env: EvmEnv, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt>, ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on @@ -270,11 +285,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// the contract is deployed there. /// /// Returns a more useful error message if that's the case - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option; + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option; /// Loads the account allocs from the given `allocs` map into the passed [JournaledState]. /// @@ -376,8 +387,6 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn set_blockhash(&mut self, block_number: U256, block_hash: B256); } -struct _ObjectSafe(dyn DatabaseExt); - /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -410,7 +419,7 @@ struct _ObjectSafe(dyn DatabaseExt); /// Multiple "forks" can be created `Backend::create_fork()`, however only 1 can be used by the /// `db`. However, their state can be hot-swapped by swapping the read half of `db` from one fork to /// another. -/// When swapping forks (`Backend::select_fork()`) we also update the current `Env` of the `EVM` +/// When swapping forks (`Backend::select_fork()`) we also update the current `EvmEnv` of the `EVM` /// accordingly, so that all `block.*` config values match /// /// When another for is selected [`DatabaseExt::select_fork()`] the entire storage, including @@ -430,11 +439,10 @@ struct _ObjectSafe(dyn DatabaseExt); /// **Note:** State snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. -#[derive(Clone, Debug)] #[must_use] -pub struct Backend { +pub struct Backend { /// The access point for managing forks - forks: MultiFork, + forks: MultiFork, BlockEnvFor>, // The default in memory db mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with @@ -459,16 +467,40 @@ pub struct Backend { /// If this is set, then the Backend is currently in forking mode active_fork_ids: Option<(LocalForkId, ForkLookupIndex)>, /// holds additional Backend data - inner: BackendInner, + inner: BackendInner, } -impl Backend { +impl Clone for Backend { + fn clone(&self) -> Self { + Self { + forks: self.forks.clone(), + mem_db: self.mem_db.clone(), + fork_init_journaled_state: self.fork_init_journaled_state.clone(), + active_fork_ids: self.active_fork_ids, + inner: self.inner.clone(), + } + } +} + +impl Debug for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Backend") + .field("forks", &self.forks) + .field("mem_db", &self.mem_db) + .field("fork_init_journaled_state", &self.fork_init_journaled_state) + .field("active_fork_ids", &self.active_fork_ids) + .field("inner", &self.inner) + .finish() + } +} + +impl Backend { /// Creates a new Backend with a spawned multi fork thread. /// /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory /// database. pub fn spawn(fork: Option) -> eyre::Result { - Self::new(MultiFork::spawn(), fork) + Self::new(MultiFork::, BlockEnvFor>::spawn(), fork) } /// Creates a new instance of `Backend` @@ -477,7 +509,10 @@ impl Backend { /// database. /// /// Prefer using [`spawn`](Self::spawn) instead. - pub fn new(forks: MultiFork, fork: Option) -> eyre::Result { + pub fn new( + forks: MultiFork, BlockEnvFor>, + fork: Option, + ) -> eyre::Result { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` let inner = BackendInner { @@ -514,7 +549,7 @@ impl Backend { /// as active pub(crate) fn new_with_fork( id: &ForkId, - fork: Fork, + fork: Fork>, journaled_state: JournaledState, ) -> eyre::Result { let mut backend = Self::spawn(None)?; @@ -567,16 +602,23 @@ impl Backend { storage: Map, ) -> Result<(), DatabaseError> { if let Some(db) = self.active_fork_db_mut() { - db.replace_account_storage(address, storage) + db.replace_account_storage(address, storage.into_iter().collect()) } else { - self.mem_db.replace_account_storage(address, storage) + self.mem_db.replace_account_storage(address, storage.into_iter().collect()) } } /// Returns all snapshots created in this backend - pub fn state_snapshots( + #[allow(clippy::type_complexity)] + pub const fn state_snapshots( &self, - ) -> &StateSnapshots> { + ) -> &StateSnapshots< + BackendStateSnapshot< + BackendDatabaseSnapshot>, + SpecFor, + BlockEnvFor, + >, + > { &self.inner.state_snapshots } @@ -588,8 +630,8 @@ impl Backend { /// This will also grant cheatcode access to the test account pub fn set_test_contract(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting test account"); - self.add_persistent_account(acc); - self.allow_cheatcode_access(acc); + self.inner.persistent_accounts.insert(acc); + self.inner.cheatcode_access_accounts.insert(acc); self } @@ -597,19 +639,18 @@ impl Backend { pub fn set_caller(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting caller account"); self.inner.caller = Some(acc); - self.allow_cheatcode_access(acc); + self.inner.cheatcode_access_accounts.insert(acc); self } /// Sets the current spec id - pub fn set_spec_id(&mut self, spec_id: SpecId) -> &mut Self { - trace!(?spec_id, "setting spec ID"); - self.inner.spec_id = spec_id; + pub fn set_spec_id(&mut self, spec_id: impl Into>) -> &mut Self { + self.inner.spec_id = spec_id.into(); self } /// Returns the set caller address - pub fn caller_address(&self) -> Option

{ + pub const fn caller_address(&self) -> Option
{ self.inner.caller } @@ -618,12 +659,12 @@ impl Backend { /// If an error occurs in a restored state snapshot, the test is considered failed. /// /// This returns whether there was a reverted state snapshot that recorded an error. - pub fn has_state_snapshot_failure(&self) -> bool { + pub const fn has_state_snapshot_failure(&self) -> bool { self.inner.has_state_snapshot_failure } /// Sets the state snapshot failure flag. - pub fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { + pub const fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { self.inner.has_state_snapshot_failure = has_state_snapshot_failure } @@ -631,7 +672,7 @@ impl Backend { pub(crate) fn update_fork_db( &self, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork>, ) { self.update_fork_db_contracts( self.inner.persistent_accounts.iter().copied(), @@ -645,7 +686,7 @@ impl Backend { &self, accounts: impl IntoIterator, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork>, ) { if let Some(db) = self.active_fork_db() { merge_account_data(accounts, db, active_journaled_state, target_fork) @@ -655,7 +696,7 @@ impl Backend { } /// Returns the memory db used if not in forking mode - pub fn mem_db(&self) -> &FoundryEvmInMemoryDB { + pub const fn mem_db(&self) -> &FoundryEvmInMemoryDB { &self.mem_db } @@ -670,27 +711,26 @@ impl Backend { } /// Returns the currently active `Fork`, if any - pub fn active_fork(&self) -> Option<&Fork> { + pub fn active_fork(&self) -> Option<&Fork>> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork(idx)) } /// Returns the currently active `Fork`, if any - pub fn active_fork_mut(&mut self) -> Option<&mut Fork> { + pub fn active_fork_mut(&mut self) -> Option<&mut Fork>> { self.active_fork_ids.map(|(_, idx)| self.inner.get_fork_mut(idx)) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db(&self) -> Option<&ForkDB> { + pub fn active_fork_db(&self) -> Option<&ForkDB>> { self.active_fork().map(|f| &f.db) } /// Returns the currently active `ForkDB`, if any - pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB> { + pub fn active_fork_db_mut(&mut self) -> Option<&mut ForkDB>> { self.active_fork_mut().map(|f| &mut f.db) } /// Returns the current database implementation as a `&dyn` value. - #[inline(always)] pub fn db(&self) -> &dyn Database { match self.active_fork_db() { Some(fork_db) => fork_db, @@ -699,7 +739,6 @@ impl Backend { } /// Returns the current database implementation as a `&mut dyn` value. - #[inline(always)] pub fn db_mut(&mut self) -> &mut dyn Database { match self.active_fork_ids.map(|(_, idx)| &mut self.inner.get_fork_mut(idx).db) { Some(fork_db) => fork_db, @@ -708,7 +747,9 @@ impl Backend { } /// Creates a snapshot of the currently active database - pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { + pub(crate) fn create_db_snapshot( + &self, + ) -> BackendDatabaseSnapshot> { if let Some((id, idx)) = self.active_fork_ids { let fork = self.inner.get_fork(idx).clone(); let fork_id = self.inner.ensure_fork_id(id).cloned().expect("Exists; qed"); @@ -744,18 +785,21 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &Env) { - self.set_caller(env.tx.caller); - self.set_spec_id(env.evm_env.cfg_env.spec); + pub(crate) fn initialize( + &mut self, + spec_id: impl Into>, + caller: Address, + tx_kind: TxKind, + ) { + self.set_caller(caller); + self.set_spec_id(spec_id); - let test_contract = match env.tx.kind { + let test_contract = match tx_kind { TxKind::Call(to) => to, TxKind::Create => { - let nonce = self - .basic_ref(env.tx.caller) - .map(|b| b.unwrap_or_default().nonce) - .unwrap_or_default(); - env.tx.caller.create(nonce) + let nonce = + self.basic_ref(caller).map(|b| b.unwrap_or_default().nonce).unwrap_or_default(); + caller.create(nonce) } }; self.set_test_contract(test_contract); @@ -766,24 +810,29 @@ impl Backend { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect( + pub fn inspect FoundryInspectorExt>>( &mut self, - env: &mut Env, - inspector: &mut I, - ) -> eyre::Result { - self.initialize(env); - let mut evm = crate::evm::new_evm_with_inspector(self, env.to_owned(), inspector); - - let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; + evm_env: &mut EvmEnvFor, + tx_env: &mut TxEnvFor, + inspector: I, + ) -> eyre::Result>> { + self.initialize(evm_env.cfg_env.spec, tx_env.caller(), tx_env.kind()); + let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector( + self, + evm_env.to_owned(), + inspector, + ); + let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?; - *env = evm.as_env_mut().to_owned(); + *tx_env = evm.tx().clone(); + *evm_env = evm.finish().1; Ok(res) } /// Returns true if the address is a precompile pub fn is_existing_precompile(&self, addr: &Address) -> bool { - self.inner.precompiles().contains(addr) + self.inner.precompiles().addresses().contains(addr) } /// Sets the initial journaled state to use when initializing forks @@ -807,7 +856,10 @@ impl Backend { .fork_init_journaled_state .state .iter() - .filter(|(addr, _)| !self.is_existing_precompile(addr) && !self.is_persistent(addr)) + .filter(|(addr, _)| { + !self.is_existing_precompile(addr) + && !self.inner.persistent_accounts.contains(*addr) + }) .map(|(addr, _)| addr) .copied() .collect::>(); @@ -824,7 +876,7 @@ impl Backend { // created account takes precedence: for example contract creation in setups if init_account.is_created() { trace!(?loaded_account, "skipping created account"); - continue + continue; } // otherwise we need to replace the account's info with the one from the fork's @@ -845,20 +897,20 @@ impl Backend { transaction: B256, ) -> eyre::Result<(u64, AnyRpcBlock)> { let fork = self.inner.get_fork_by_id(id)?; - let tx = fork.db.db.get_transaction(transaction)?; + let tx = fork.backend().get_transaction(transaction)?; // get the block number we need to fork - if let Some(tx_block) = tx.block_number { - let block = fork.db.db.get_full_block(tx_block)?; + if let Some(tx_block) = tx.block_number() { + let block = fork.backend().get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; Ok((fork_block, block)) } else { - let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; + let block = fork.backend().get_full_block(BlockNumberOrTag::Latest)?; - let number = block.header.number; + let number = block.header().number(); Ok((number, block)) } @@ -870,56 +922,74 @@ impl Backend { pub fn replay_until( &mut self, id: LocalForkId, - mut env: Env, + evm_env: EvmEnvFor, tx_hash: B256, journaled_state: &mut JournaledState, - ) -> eyre::Result>> { + ) -> eyre::Result> { trace!(?id, ?tx_hash, "replay until transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); - let fork_id = self.ensure_fork_id(id)?.clone(); let fork = self.inner.get_fork_by_id_mut(id)?; - let full_block = fork.db.db.get_full_block(env.evm_env.block_env.number)?; - - for tx in full_block.inner.transactions.txns() { - // System transactions such as on L2s don't contain any pricing info so we skip them - // otherwise this would cause reverts - if is_known_system_sender(tx.inner().inner.signer()) || - tx.ty() == SYSTEM_TRANSACTION_TYPE - { - trace!(tx=?tx.tx_hash(), "skipping system transaction"); - continue; + let full_block = + fork.backend().get_full_block(evm_env.block_env.number().saturating_to::())?; + + // Collect non-system transactions up to and including the target. + let txs = full_block + .transactions() + .txns() + .filter(|tx| !is_known_system_sender(tx.from()) && tx.ty() != SYSTEM_TRANSACTION_TYPE); + + let mut txs_to_replay = Vec::new(); + let mut target_tx = None; + for tx in txs { + if tx.tx_hash() == tx_hash { + target_tx = Some(tx.clone()); + break; } + txs_to_replay.push(tx.clone()); + } - if tx.tx_hash() == tx_hash { - // found the target transaction - return Ok(Some(tx.inner.clone())) + // Replay all preceding transactions using a single EVM + cloned ForkDB. + if !txs_to_replay.is_empty() { + let now = Instant::now(); + + // Clone the fork's CacheDB once. The underlying SharedBackend is Arc-backed, + // so only the local cache layer is actually duplicated. + let replay_db = fork.db.clone(); + let mut evm = FEN::EvmFactory::default().create_evm(replay_db, evm_env); + + for tx in &txs_to_replay { + let tx_env = TxEnvFor::::from_any_rpc_transaction(tx)?; + trace!(tx=?tx.tx_hash(), "committing transaction"); + evm.transact_commit(tx_env).wrap_err("backend: failed committing transaction")?; } - trace!(tx=?tx.tx_hash(), "committing transaction"); - - commit_transaction( - &tx.inner, - &mut env.as_env_mut(), - journaled_state, - fork, - &fork_id, - &persistent_accounts, - &mut NoOpInspector, - )?; + + // Extract the DB back and replace the fork's database with the replayed state. + fork.db = evm.into_db(); + + // Refresh journaled states from the updated database, preserving persistent + // accounts (cheatcode address, CREATE2 deployer, test contract, etc.). + fork.refresh_journaled_states(journaled_state, &persistent_accounts)?; + + trace!(elapsed=?now.elapsed(), count=txs_to_replay.len(), "replayed transactions"); } - Ok(None) + Ok(target_tx) } } -impl DatabaseExt for Backend { - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256 { +impl DatabaseExt for Backend { + fn snapshot_state( + &mut self, + journaled_state: &JournaledState, + evm_env: &EvmEnvFor, + ) -> U256 { trace!("create snapshot"); let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), - env.to_owned(), + evm_env.clone(), )); trace!(target: "backend", "Created new snapshot {}", id); id @@ -929,7 +999,8 @@ impl DatabaseExt for Backend { &mut self, id: U256, current_state: &JournaledState, - current: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, + caller: Address, action: RevertStateSnapshotAction, ) -> Option { trace!(?id, "revert snapshot"); @@ -943,17 +1014,16 @@ impl DatabaseExt for Backend { // Check if an error occurred either during or before the snapshot. // DSTest contracts don't have snapshot functionality, so this slot is enough to check // for failure here. - if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) { - if let Some(slot) = account.storage.get(&GLOBAL_FAIL_SLOT) { - if !slot.present_value.is_zero() { - self.set_state_snapshot_failure(true); - } - } + if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) + && let Some(slot) = account.storage.get(&GLOBAL_FAIL_SLOT) + && !slot.present_value.is_zero() + { + self.set_state_snapshot_failure(true); } // merge additional logs snapshot.merge(current_state); - let BackendStateSnapshot { db, mut journaled_state, env } = snapshot; + let BackendStateSnapshot { db, mut journaled_state, snap_evm_env } = snapshot; match db { BackendDatabaseSnapshot::InMemory(mem_db) => { self.mem_db = mem_db; @@ -962,7 +1032,6 @@ impl DatabaseExt for Backend { // there might be the case where the snapshot was created during `setUp` with // another caller, so we need to ensure the caller account is present in the // journaled state and database - let caller = current.tx.caller; journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = current_state .state @@ -981,7 +1050,7 @@ impl DatabaseExt for Backend { } } - update_current_env_with_fork_env(&mut current.as_env_mut(), env); + *evm_env = snap_evm_env; trace!(target: "backend", "Reverted snapshot {}", id); Some(journaled_state) @@ -1017,19 +1086,20 @@ impl DatabaseExt for Backend { trace!(?transaction, "create fork at transaction"); let id = self.create_fork(fork)?; let fork_id = self.ensure_fork_id(id).cloned()?; - let mut env = self + let mut evm_env = self .forks - .get_env(fork_id)? - .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exit", id))?; + .get_evm_env(fork_id)? + .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exist", id))?; // we still need to roll to the transaction, but we only need an empty dummy state since we // don't need to update the active journaled state yet self.roll_fork_to_transaction( Some(id), transaction, - &mut env.as_env_mut(), + &mut evm_env, &mut self.inner.new_journaled_state(), )?; + Ok(id) } @@ -1038,7 +1108,8 @@ impl DatabaseExt for Backend { fn select_fork( &mut self, id: LocalForkId, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, + tx_env: &mut TxEnvFor, active_journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, "select fork"); @@ -1052,25 +1123,25 @@ impl DatabaseExt for Backend { if let Some(active_fork_id) = self.active_fork_id() { self.forks.update_block( self.ensure_fork_id(active_fork_id).cloned()?, - env.block.number, - env.block.timestamp, + evm_env.block_env.number(), + evm_env.block_env.timestamp(), )?; } let fork_id = self.ensure_fork_id(id).cloned()?; let idx = self.inner.ensure_fork_index(&fork_id)?; - let fork_env = self + let fork_evm_env = self .forks - .get_env(fork_id)? - .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exit", id))?; + .get_evm_env(fork_id)? + .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exist", id))?; // If we're currently in forking mode we need to update the journaled_state to this point, // this ensures the changes performed while the fork was active are recorded if let Some(active) = self.active_fork_mut() { active.journaled_state = active_journaled_state.clone(); - let caller = env.tx.caller; - let caller_account = active.journaled_state.state.get(&env.tx.caller).cloned(); + let caller = tx_env.caller(); + let caller_account = active.journaled_state.state.get(&caller).cloned(); let target_fork = self.inner.get_fork_mut(idx); // depth 0 will be the default value when the fork was created @@ -1115,7 +1186,7 @@ impl DatabaseExt for Backend { let Ok(db_account) = db.load_account(addr) else { continue }; let Some(fork_account) = fork.journaled_state.state.get_mut(&addr) else { - continue + continue; }; for (key, val) in &db_account.storage { @@ -1135,11 +1206,11 @@ impl DatabaseExt for Backend { // another edge case where a fork is created and selected during setup with not // necessarily the same caller as for the test, however we must always // ensure that fork's state contains the current sender - let caller = env.tx.caller; + let caller = tx_env.caller(); fork.journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = active_journaled_state .state - .get(&env.tx.caller) + .get(&caller) .map(|acc| acc.info.clone()) .unwrap_or_default(); @@ -1158,7 +1229,8 @@ impl DatabaseExt for Backend { self.active_fork_ids = Some((id, idx)); // Update current environment with environment of newly selected fork. - update_current_env_with_fork_env(env, fork_env); + tx_env.set_chain_id(Some(fork_evm_env.cfg_env.chain_id)); + *evm_env = fork_evm_env; Ok(()) } @@ -1169,7 +1241,7 @@ impl DatabaseExt for Backend { &mut self, id: Option, block_number: u64, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); @@ -1184,7 +1256,7 @@ impl DatabaseExt for Backend { if active_id == id { // need to update the block's env settings right away, which is otherwise set when // forks are selected `select_fork` - update_current_env_with_fork_env(env, fork_env); + *evm_env = fork_env; // we also need to update the journaled_state right away, this has essentially the // same effect as selecting (`select_fork`) by discarding @@ -1196,8 +1268,8 @@ impl DatabaseExt for Backend { let active = self.inner.get_fork_mut(active_idx); active.journaled_state = self.fork_init_journaled_state.clone(); - active.journaled_state.depth = journaled_state.depth; + for addr in persistent_addrs { merge_journaled_state_data(addr, journaled_state, &mut active.journaled_state); } @@ -1233,7 +1305,7 @@ impl DatabaseExt for Backend { &mut self, id: Option, transaction: B256, - env: &mut EnvMut<'_>, + evm_env: &mut EvmEnvFor, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?transaction, "roll fork to transaction"); @@ -1242,15 +1314,22 @@ impl DatabaseExt for Backend { let (fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; - // roll the fork to the transaction's block or latest if it's pending - self.roll_fork(Some(id), fork_block, env, journaled_state)?; + // roll the fork to the transaction's parent block or latest if it's pending, because we + // need to fork off the parent block's state for tx level forking and then replay the txs + // before the tx in that block to get the state at the tx + self.roll_fork(Some(id), fork_block, evm_env, journaled_state)?; - update_env_block(env, &block); + // we need to update the env to the block + update_env_block(evm_env, block.header()); - let env = env.to_owned(); + // after we forked at the fork block we need to properly update the block env to the block + // env of the tx's block + let _ = self + .forks + .update_block_env(self.inner.ensure_fork_id(id).cloned()?, evm_env.block_env.clone()); // replay all transactions that came before - self.replay_until(id, env, transaction, journaled_state)?; + self.replay_until(id, evm_env.clone(), transaction, journaled_state)?; Ok(()) } @@ -1259,9 +1338,11 @@ impl DatabaseExt for Backend { &mut self, maybe_id: Option, transaction: B256, - mut env: Env, + mut evm_env: EvmEnvFor, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt< + ::FoundryContext<'db>, + >, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1270,8 +1351,9 @@ impl DatabaseExt for Backend { let tx = { let fork = self.inner.get_fork_by_id_mut(id)?; - fork.db.db.get_transaction(transaction)? + fork.backend().get_transaction(transaction)? }; + let tx_env = TxEnvFor::::from_any_rpc_transaction(&tx)?; // This is a bit ambiguous because the user wants to transact an arbitrary transaction in // the current context, but we're assuming the user wants to transact the transaction as it @@ -1281,12 +1363,12 @@ impl DatabaseExt for Backend { // So we modify the env to match the transaction's block. let (_fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; - update_env_block(&mut env.as_env_mut(), &block); + update_env_block(&mut evm_env, block.header()); let fork = self.inner.get_fork_by_id_mut(id)?; - commit_transaction( - &tx.inner, - &mut env.as_env_mut(), + commit_transaction::( + evm_env, + tx_env, journaled_state, fork, &fork_id, @@ -1297,22 +1379,24 @@ impl DatabaseExt for Backend { fn transact_from_tx( &mut self, - tx: &TransactionRequest, - mut env: Env, + tx_env: TxEnvFor, + evm_env: EvmEnvFor, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt< + ::FoundryContext<'db>, + >, ) -> eyre::Result<()> { - trace!(?tx, "execute signed transaction"); + trace!("execute signed transaction"); self.commit(journaled_state.state.clone()); let res = { - configure_tx_req_env(&mut env.as_env_mut(), tx, None)?; - let mut db = self.clone(); - let mut evm = new_evm_with_inspector(&mut db, env.to_owned(), inspector); - evm.journaled_state.depth = journaled_state.depth + 1; - evm.transact(env.tx)? + let depth = journaled_state.depth + 1; + let mut evm = + FEN::EvmFactory::default().create_foundry_nested_evm(&mut db, evm_env, inspector); + evm.journal_inner_mut().depth = depth; + evm.transact_raw(tx_env)? }; self.commit(res.state); @@ -1335,24 +1419,16 @@ impl DatabaseExt for Backend { if self.inner.issued_local_fork_ids.contains_key(&id) { return Ok(id); } - eyre::bail!("Requested fork `{}` does not exit", id) - } - if let Some(id) = self.active_fork_id() { - Ok(id) - } else { - eyre::bail!("No fork active") + eyre::bail!("Requested fork `{}` does not exist", id) } + if let Some(id) = self.active_fork_id() { Ok(id) } else { eyre::bail!("No fork active") } } fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.inner.ensure_fork_id(id) } - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option { + fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option { let active_id = self.active_fork_id()?; let active_fork = self.active_fork()?; @@ -1362,7 +1438,7 @@ impl DatabaseExt for Backend { return None; } - if !active_fork.is_contract(callee) && !is_contract_in_state(journaled_state, callee) { + if !active_fork.is_contract(callee) && !is_contract_in_state(evm_state, callee) { // no contract for `callee` available on current fork, check if available on other forks let mut available_on = Vec::new(); for (id, fork) in self.inner.forks_iter().filter(|(id, _)| *id != active_id) { @@ -1419,38 +1495,37 @@ impl DatabaseExt for Backend { ) -> Result<(), BackendError> { // Fetch the account from the journaled state. Will create a new account if it does // not already exist. - let mut state_acc = journaled_state.load_account(self, *target)?; + let mut state_acc = journaled_state.load_account_mut(self, *target)?; // Set the account's bytecode and code hash, if the `bytecode` field is present. if let Some(bytecode) = source.code.as_ref() { - state_acc.info.code_hash = keccak256(bytecode); + let bytecode_hash = keccak256(bytecode); let bytecode = Bytecode::new_raw(bytecode.0.clone().into()); - state_acc.info.code = Some(bytecode); + state_acc.set_code(bytecode_hash, bytecode); } + // Set the account's balance. + state_acc.set_balance(source.balance); + // Set the account's storage, if the `storage` field is present. - if let Some(storage) = source.storage.as_ref() { - state_acc.storage = storage - .iter() - .map(|(slot, value)| { + if let Some(acc) = journaled_state.state.get_mut(target) { + if let Some(storage) = source.storage.as_ref() { + for (slot, value) in storage { let slot = U256::from_be_bytes(slot.0); - ( + acc.storage.insert( slot, EvmStorageSlot::new_changed( - state_acc - .storage - .get(&slot) - .map(|s| s.present_value) - .unwrap_or_default(), + acc.storage.get(&slot).map(|s| s.present_value).unwrap_or_default(), U256::from_be_bytes(value.0), + 0, ), - ) - }) - .collect(); - } - // Set the account's nonce and balance. - state_acc.info.nonce = source.nonce.unwrap_or_default(); - state_acc.info.balance = source.balance; + ); + } + } + + // Set the account's nonce. + acc.info.nonce = source.nonce.unwrap_or_default(); + }; // Touch the account to ensure the loaded information persists if called in `setUp`. journaled_state.touch(*target); @@ -1495,7 +1570,7 @@ impl DatabaseExt for Backend { } } -impl DatabaseRef for Backend { +impl DatabaseRef for Backend { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -1531,8 +1606,8 @@ impl DatabaseRef for Backend { } } -impl DatabaseCommit for Backend { - fn commit(&mut self, changes: Map) { +impl DatabaseCommit for Backend { + fn commit(&mut self, changes: AddressMap) { if let Some(db) = self.active_fork_db_mut() { db.commit(changes) } else { @@ -1541,7 +1616,7 @@ impl DatabaseCommit for Backend { } } -impl Database for Backend { +impl Database for Backend { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { if let Some(db) = self.active_fork_db_mut() { @@ -1578,35 +1653,51 @@ impl Database for Backend { /// Variants of a [revm::Database] #[derive(Clone, Debug)] -pub enum BackendDatabaseSnapshot { +pub enum BackendDatabaseSnapshot { /// Simple in-memory [revm::Database] InMemory(FoundryEvmInMemoryDB), /// Contains the entire forking mode database - Forked(LocalForkId, ForkId, ForkLookupIndex, Box), + Forked(LocalForkId, ForkId, ForkLookupIndex, Box>), } /// Represents a fork #[derive(Clone, Debug)] -pub struct Fork { - db: ForkDB, +pub struct Fork { + db: ForkDB, journaled_state: JournaledState, } -impl Fork { +impl Fork { + /// Returns a reference to the underlying [`SharedBackend`]. + pub const fn backend(&self) -> &SharedBackend { + &self.db.db + } + /// Returns true if the account is a contract pub fn is_contract(&self, acc: Address) -> bool { - if let Ok(Some(acc)) = self.db.basic_ref(acc) { - if acc.code_hash != KECCAK_EMPTY { - return true; - } + if let Ok(Some(acc)) = self.db.basic_ref(acc) + && acc.code_hash != KECCAK_EMPTY + { + return true; } - is_contract_in_state(&self.journaled_state, acc) + is_contract_in_state(&self.journaled_state.state, acc) + } + + /// Refreshes the given journaled state and the fork's own journaled state from the + /// database, preserving persistent accounts. + fn refresh_journaled_states( + &mut self, + journaled_state: &mut JournaledState, + persistent_accounts: &HashSet
, + ) -> Result<(), BackendError> { + update_state(&mut journaled_state.state, &mut self.db, Some(persistent_accounts))?; + update_state(&mut self.journaled_state.state, &mut self.db, Some(persistent_accounts))?; + Ok(()) } } /// Container type for various Backend related data -#[derive(Clone, Debug)] -pub struct BackendInner { +pub struct BackendInner { /// Stores the `ForkId` of the fork the `Backend` launched with from the start. /// /// In other words if [`Backend::spawn()`] was called with a `CreateFork` command, to launch @@ -1629,9 +1720,16 @@ pub struct BackendInner { pub created_forks: HashMap, /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling - pub forks: Vec>, + pub forks: Vec>>>, /// Contains state snapshots made at a certain point - pub state_snapshots: StateSnapshots>, + #[allow(clippy::type_complexity)] + pub state_snapshots: StateSnapshots< + BackendStateSnapshot< + BackendDatabaseSnapshot>, + SpecFor, + BlockEnvFor, + >, + >, /// Tracks whether there was a failure in a snapshot that was reverted /// /// The Test contract contains a bool variable that is set to true when an `assert` function @@ -1651,12 +1749,48 @@ pub struct BackendInner { /// instead the use only one that's persistent across fork swaps. pub persistent_accounts: HashSet
, /// The configured spec id - pub spec_id: SpecId, + pub spec_id: SpecFor, /// All accounts that are allowed to execute cheatcodes pub cheatcode_access_accounts: HashSet
, } -impl BackendInner { +impl Clone for BackendInner { + fn clone(&self) -> Self { + Self { + launched_with_fork: self.launched_with_fork.clone(), + issued_local_fork_ids: self.issued_local_fork_ids.clone(), + created_forks: self.created_forks.clone(), + forks: self.forks.clone(), + state_snapshots: self.state_snapshots.clone(), + has_state_snapshot_failure: self.has_state_snapshot_failure, + caller: self.caller, + next_fork_id: self.next_fork_id, + persistent_accounts: self.persistent_accounts.clone(), + spec_id: self.spec_id, + cheatcode_access_accounts: self.cheatcode_access_accounts.clone(), + } + } +} + +impl Debug for BackendInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BackendInner") + .field("launched_with_fork", &self.launched_with_fork) + .field("issued_local_fork_ids", &self.issued_local_fork_ids) + .field("created_forks", &self.created_forks) + .field("forks", &self.forks) + .field("state_snapshots", &self.state_snapshots) + .field("has_state_snapshot_failure", &self.has_state_snapshot_failure) + .field("caller", &self.caller) + .field("next_fork_id", &self.next_fork_id) + .field("persistent_accounts", &self.persistent_accounts) + .field("spec_id", &self.spec_id) + .field("cheatcode_access_accounts", &self.cheatcode_access_accounts) + .finish() + } +} + +impl BackendInner { pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids .get(&id) @@ -1676,51 +1810,58 @@ impl BackendInner { /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork(&self, idx: ForkLookupIndex) -> &Fork { + fn get_fork(&self, idx: ForkLookupIndex) -> &Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_ref().unwrap() } /// Returns the underlying fork mapped to the index #[track_caller] - fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork { + fn get_fork_mut(&mut self, idx: ForkLookupIndex) -> &mut Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].as_mut().unwrap() } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id_mut(&mut self, id: LocalForkId) -> eyre::Result<&mut Fork> { + fn get_fork_by_id_mut( + &mut self, + id: LocalForkId, + ) -> eyre::Result<&mut Fork>> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork_mut(idx)) } /// Returns the underlying fork corresponding to the id #[track_caller] - fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork> { + fn get_fork_by_id(&self, id: LocalForkId) -> eyre::Result<&Fork>> { let idx = self.ensure_fork_index_by_local_id(id)?; Ok(self.get_fork(idx)) } /// Removes the fork - fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork { + fn take_fork(&mut self, idx: ForkLookupIndex) -> Fork> { debug_assert!(idx < self.forks.len(), "fork lookup index must exist"); self.forks[idx].take().unwrap() } - fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork) { + fn set_fork(&mut self, idx: ForkLookupIndex, fork: Fork>) { self.forks[idx] = Some(fork) } /// Returns an iterator over Forks - pub fn forks_iter(&self) -> impl Iterator + '_ { + pub fn forks_iter( + &self, + ) -> impl Iterator>)> + '_ { self.issued_local_fork_ids .iter() .map(|(id, fork_id)| (*id, self.get_fork(self.created_forks[fork_id]))) } /// Returns a mutable iterator over all Forks - pub fn forks_iter_mut(&mut self) -> impl Iterator + '_ { + pub fn forks_iter_mut( + &mut self, + ) -> impl Iterator>> + '_ { self.forks.iter_mut().filter_map(|f| f.as_mut()) } @@ -1730,7 +1871,7 @@ impl BackendInner { id: LocalForkId, fork_id: ForkId, idx: ForkLookupIndex, - fork: Fork, + fork: Fork>, ) { self.created_forks.insert(fork_id.clone(), idx); self.issued_local_fork_ids.insert(id, fork_id); @@ -1742,7 +1883,7 @@ impl BackendInner { &mut self, id: LocalForkId, fork_id: ForkId, - db: ForkDB, + db: ForkDB>, journaled_state: JournaledState, ) -> ForkLookupIndex { let idx = self.forks.len(); @@ -1758,7 +1899,7 @@ impl BackendInner { &mut self, id: LocalForkId, new_fork_id: ForkId, - backend: SharedBackend, + backend: SharedBackend>, ) -> eyre::Result { let fork_id = self.ensure_fork_id(id)?; let idx = self.ensure_fork_index(fork_id)?; @@ -1783,7 +1924,7 @@ impl BackendInner { pub fn insert_new_fork( &mut self, fork_id: ForkId, - db: ForkDB, + db: ForkDB>, journaled_state: JournaledState, ) -> (LocalForkId, ForkLookupIndex) { let idx = self.forks.len(); @@ -1811,23 +1952,29 @@ impl BackendInner { self.issued_local_fork_ids.is_empty() } - pub fn precompiles(&self) -> &'static Precompiles { - Precompiles::new(PrecompileSpecId::from_spec_id(self.spec_id)) + pub fn precompiles(&self) -> PrecompilesFor { + let evm = FEN::EvmFactory::default().create_evm( + EmptyDB::default(), + EvmEnv::new(CfgEnv::new_with_spec(self.spec_id), Default::default()), + ); + evm.precompiles().clone() } /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { let mut journal = { let mut journal_inner = JournalInner::new(); - journal_inner.set_spec_id(self.spec_id); + journal_inner.set_spec_id(self.spec_id.into()); journal_inner }; - journal.precompiles.extend(self.precompiles().addresses().copied()); + journal + .warm_addresses + .set_precompile_addresses(self.precompiles().addresses().copied().collect()); journal } } -impl Default for BackendInner { +impl Default for BackendInner { fn default() -> Self { Self { launched_with_fork: None, @@ -1839,7 +1986,7 @@ impl Default for BackendInner { caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - spec_id: SpecId::default(), + spec_id: SpecFor::::default(), // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1851,22 +1998,15 @@ impl Default for BackendInner { } } -/// This updates the currently used env with the fork's environment -pub(crate) fn update_current_env_with_fork_env(current: &mut EnvMut<'_>, fork: Env) { - *current.block = fork.evm_env.block_env; - *current.cfg = fork.evm_env.cfg_env; - current.tx.chain_id = fork.tx.chain_id; -} - /// Clones the data of the given `accounts` from the `active` database into the `fork_db` /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`. -pub(crate) fn merge_account_data( +pub(crate) fn merge_account_data( accounts: impl IntoIterator, active: &CacheDB, active_journaled_state: &mut JournaledState, - target_fork: &mut Fork, + target_fork: &mut Fork, ) { - for addr in accounts.into_iter() { + for addr in accounts { merge_db_account_data(addr, active, &mut target_fork.db); merge_journaled_state_data(addr, active_journaled_state, &mut target_fork.journaled_state); } @@ -1893,10 +2033,10 @@ fn merge_journaled_state_data( } /// Clones the account data from the `active` db into the `ForkDB` -fn merge_db_account_data( +fn merge_db_account_data( addr: Address, active: &CacheDB, - fork_db: &mut ForkDB, + fork_db: &mut ForkDB, ) { trace!(?addr, "merging database data"); @@ -1928,53 +2068,56 @@ fn merge_db_account_data( } /// Returns true of the address is a contract -fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool { - journaled_state - .state - .get(&acc) - .map(|acc| acc.info.code_hash != KECCAK_EMPTY) - .unwrap_or_default() +fn is_contract_in_state(evm_state: &EvmState, acc: Address) -> bool { + evm_state.get(&acc).map(|acc| acc.info.code_hash != KECCAK_EMPTY).unwrap_or_default() } -/// Updates the env's block with the block's data -fn update_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { - env.block.timestamp = block.header.timestamp; - env.block.beneficiary = block.header.beneficiary; - env.block.difficulty = block.header.difficulty; - env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; - env.block.number = block.header.number; - if let Some(excess_blob_gas) = block.header.excess_blob_gas { - env.block.blob_excess_gas_and_price = - Some(BlobExcessGasAndPrice::new(excess_blob_gas, false)); +/// Updates the evm env's block with the block's data +fn update_env_block( + evm_env: &mut EvmEnv, + header: &impl BlockHeader, +) { + let block_env = &mut evm_env.block_env; + block_env.set_timestamp(U256::from(header.timestamp())); + block_env.set_beneficiary(header.beneficiary()); + block_env.set_difficulty(header.difficulty()); + block_env.set_prevrandao(header.mix_hash()); + block_env.set_basefee(header.base_fee_per_gas().unwrap_or_default()); + block_env.set_gas_limit(header.gas_limit()); + block_env.set_number(U256::from(header.number())); + + if let Some(excess_blob_gas) = header.excess_blob_gas() { + evm_env.block_env.set_blob_excess_gas_and_price( + excess_blob_gas, + get_blob_base_fee_update_fraction(evm_env.cfg_env.chain_id, header.timestamp()), + ); } } /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an inspector. -fn commit_transaction( - tx: &Transaction, - env: &mut EnvMut<'_>, +fn commit_transaction( + evm_env: EvmEnvFor, + tx_env: TxEnvFor, journaled_state: &mut JournaledState, - fork: &mut Fork, + fork: &mut Fork>, fork_id: &ForkId, persistent_accounts: &HashSet
, - inspector: &mut dyn InspectorExt, + inspector: &mut dyn for<'db> FoundryInspectorExt< + ::FoundryContext<'db>, + >, ) -> eyre::Result<()> { - configure_tx_env(env, tx); - let now = Instant::now(); let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let mut db = Backend::new_with_fork(fork_id, fork, journaled_state)?; + let mut db: Backend = Backend::new_with_fork(fork_id, fork, journaled_state)?; - let mut evm = crate::evm::new_evm_with_inspector(&mut db as _, env.to_owned(), inspector); - // Adjust inner EVM depth to ensure that inspectors receive accurate data. - evm.journaled_state.depth = depth + 1; - evm.transact(env.tx.clone()).wrap_err("backend: failed committing transaction")? + let mut evm = + FEN::EvmFactory::default().create_foundry_nested_evm(&mut db, evm_env, inspector); + evm.journal_inner_mut().depth = depth + 1; + evm.transact_raw(tx_env).wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); @@ -2003,71 +2146,64 @@ pub fn update_state( /// Applies the changeset of a transaction to the active journaled state and also commits it in the /// forked db -fn apply_state_changeset( - state: Map, +fn apply_state_changeset( + state: EvmState, journaled_state: &mut JournaledState, - fork: &mut Fork, + fork: &mut Fork, persistent_accounts: &HashSet
, ) -> Result<(), BackendError> { // commit the state and update the loaded accounts fork.db.commit(state); - - update_state(&mut journaled_state.state, &mut fork.db, Some(persistent_accounts))?; - update_state(&mut fork.journaled_state.state, &mut fork.db, Some(persistent_accounts))?; - - Ok(()) + fork.refresh_journaled_states(journaled_state, persistent_accounts) } #[cfg(test)] mod tests { - use crate::{backend::Backend, fork::CreateFork, opts::EvmOpts}; - use alloy_primitives::{Address, U256}; + use crate::{backend::Backend, evm::EthEvmNetwork, opts::EvmOpts}; + use alloy_primitives::{U256, address}; use alloy_provider::Provider; use foundry_common::provider::get_http_provider; use foundry_config::{Config, NamedChain}; use foundry_fork_db::cache::{BlockchainDb, BlockchainDbMeta}; - use revm::database::DatabaseRef; - - const ENDPOINT: Option<&str> = option_env!("ETH_RPC_URL"); + use revm::{ + context::{BlockEnv, TxEnv}, + database::DatabaseRef, + primitives::hardfork::SpecId, + }; #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cache() { - let Some(endpoint) = ENDPOINT else { return }; - + let endpoint = &*foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(endpoint); let block_num = provider.get_block_number().await.unwrap(); - let config = Config::figment(); - let mut evm_opts = config.extract::().unwrap(); + let mut evm_opts = Config::figment().extract::().unwrap(); + evm_opts.fork_url = Some(endpoint.to_string()); evm_opts.fork_block_number = Some(block_num); - let (env, _block) = evm_opts.fork_evm_env(endpoint).await.unwrap(); + let (evm_env, _, fork_block) = evm_opts.env::().await.unwrap(); - let fork = CreateFork { - enable_caching: true, - url: endpoint.to_string(), - env: env.clone(), - evm_opts, - }; + let fork = + evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap(); - let backend = Backend::spawn(Some(fork)).unwrap(); + let backend = Backend::::spawn(Some(fork)).unwrap(); // some rng contract from etherscan - let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); + let address = address!("0x63091244180ae240c87d1f528f5f269134cb07b3"); - let idx = U256::from(0u64); - let _value = backend.storage_ref(address, idx); + let num_slots = 5; let _account = backend.basic_ref(address); - - // fill some slots - let num_slots = 10u64; - for idx in 1..num_slots { + for idx in 0..num_slots { let _ = backend.storage_ref(address, U256::from(idx)); } drop(backend); - let meta = BlockchainDbMeta { block_env: env.evm_env.block_env, hosts: Default::default() }; + let meta = BlockchainDbMeta { + chain: None, + block_env: evm_env.block_env, + hosts: Default::default(), + }; let db = BlockchainDb::new( meta, diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs index d26d9a55e750d..915657ccde868 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/snapshot.rs @@ -1,8 +1,8 @@ use super::JournaledState; -use crate::Env; +use alloy_evm::EvmEnv; use alloy_primitives::{ - map::{AddressHashMap, HashMap}, B256, U256, + map::{AddressHashMap, U256Map}, }; use revm::state::AccountInfo; use serde::{Deserialize, Serialize}; @@ -11,24 +11,24 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct StateSnapshot { pub accounts: AddressHashMap, - pub storage: AddressHashMap>, - pub block_hashes: HashMap, + pub storage: AddressHashMap>, + pub block_hashes: U256Map, } /// Represents a state snapshot taken during evm execution #[derive(Clone, Debug)] -pub struct BackendStateSnapshot { +pub struct BackendStateSnapshot { pub db: T, /// The journaled_state state at a specific point pub journaled_state: JournaledState, - /// Contains the env at the time of the snapshot - pub env: Env, + /// Contains the evm env at the time of the snapshot + pub snap_evm_env: EvmEnv, } -impl BackendStateSnapshot { +impl BackendStateSnapshot { /// Takes a new state snapshot. - pub fn new(db: T, journaled_state: JournaledState, env: Env) -> Self { - Self { db, journaled_state, env } + pub const fn new(db: T, journaled_state: JournaledState, evm_env: EvmEnv) -> Self { + Self { db, journaled_state, snap_evm_env: evm_env } } /// Called when this state snapshot is reverted. @@ -57,7 +57,7 @@ pub enum RevertStateSnapshotAction { impl RevertStateSnapshotAction { /// Returns `true` if the action is to keep the state snapshot. - pub fn is_keep(&self) -> bool { + pub const fn is_keep(&self) -> bool { matches!(self, Self::RevertKeep) } } diff --git a/crates/evm/core/src/buffer.rs b/crates/evm/core/src/buffer.rs index 5cce0a91ad97b..16655e382da0f 100644 --- a/crates/evm/core/src/buffer.rs +++ b/crates/evm/core/src/buffer.rs @@ -2,7 +2,7 @@ use alloy_primitives::U256; use revm::bytecode::opcode; /// Used to keep track of which buffer is currently active to be drawn by the debugger. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum BufferKind { Memory, Calldata, @@ -11,7 +11,7 @@ pub enum BufferKind { impl BufferKind { /// Helper to cycle through the active buffers. - pub fn next(&self) -> Self { + pub const fn next(&self) -> Self { match self { Self::Memory => Self::Calldata, Self::Calldata => Self::Returndata, @@ -75,13 +75,6 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))), - opcode::RETURNDATALOAD => (Some((BufferKind::Returndata, 1, -1)), None), - opcode::EOFCREATE => (Some((BufferKind::Memory, 3, 4)), None), - opcode::RETURNCONTRACT => (Some((BufferKind::Memory, 1, 2)), None), - opcode::DATACOPY => (None, Some((1, 3))), - opcode::EXTCALL | opcode::EXTSTATICCALL | opcode::EXTDELEGATECALL => { - (Some((BufferKind::Memory, 2, 3)), None) - } _ => Default::default(), }; @@ -90,17 +83,12 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { -2 => Some(1), -1 => Some(32), 0 => None, - 1.. => { - if (stack_index as usize) <= stack_len { - Some(stack[stack_len - stack_index as usize].saturating_to()) - } else { - None - } - } + 1.. => ((stack_index as usize) <= stack_len) + .then(|| stack[stack_len - stack_index as usize].saturating_to()), _ => panic!("invalid stack index"), }; - if buffer_access.0.is_some() || buffer_access.1.is_some() { + (buffer_access.0.is_some() || buffer_access.1.is_some()).then(|| { let (read, write) = buffer_access; let read_access = read.and_then(|b| { let (buffer, offset, len) = b; @@ -110,8 +98,6 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { let (offset, len) = b; Some(BufferAccess { offset: get_size(offset)?, len: get_size(len)? }) }); - Some(BufferAccesses { read: read_access, write: write_access }) - } else { - None - } + BufferAccesses { read: read_access, write: write_access } + }) } diff --git a/crates/evm/core/src/bytecode.rs b/crates/evm/core/src/bytecode.rs new file mode 100644 index 0000000000000..5e8b350eeb266 --- /dev/null +++ b/crates/evm/core/src/bytecode.rs @@ -0,0 +1,248 @@ +use revm::bytecode::{OpCode, opcode}; +use std::{fmt, slice}; + +/// An iterator that yields opcodes and their immediate data. +/// +/// If the bytecode is not well-formed, the iterator will still yield opcodes, but the immediate +/// data may be incorrect. For example, if the bytecode is `PUSH2 0x69`, the iterator will yield +/// `PUSH2, &[]`. +#[derive(Clone, Debug)] +pub struct InstIter<'a> { + iter: slice::Iter<'a, u8>, +} + +impl fmt::Display for InstIter<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, op) in self.clone().enumerate() { + if i > 0 { + f.write_str(" ")?; + } + write!(f, "{op}")?; + } + Ok(()) + } +} + +impl<'a> InstIter<'a> { + /// Create a new iterator over the given bytecode slice. + #[inline] + pub fn new(slice: &'a [u8]) -> Self { + Self { iter: slice.iter() } + } + + /// Returns a new iterator that also yields the program counter alongside the opcode and + /// immediate data. + #[inline] + pub const fn with_pc(self) -> InstIterWithPc<'a> { + InstIterWithPc { iter: self, pc: 0 } + } + + /// Returns the inner iterator. + #[inline] + pub const fn inner(&self) -> &slice::Iter<'a, u8> { + &self.iter + } + + /// Returns the inner iterator. + #[inline] + pub const fn inner_mut(&mut self) -> &mut slice::Iter<'a, u8> { + &mut self.iter + } + + /// Returns the inner iterator. + #[inline] + pub const fn into_inner(self) -> slice::Iter<'a, u8> { + self.iter + } +} + +impl<'a> Iterator for InstIter<'a> { + type Item = Inst<'a>; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|&opcode| { + // SAFETY: OpCode wraps a u8, unknown opcodes are valid to construct. + let opcode = unsafe { OpCode::new_unchecked(opcode) }; + let len = imm_len(opcode.get()) as usize; + let (immediate, rest) = self.iter.as_slice().split_at_checked(len).unwrap_or_default(); + self.iter = rest.iter(); + Inst { opcode, immediate } + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.iter.len(); + ((len != 0) as usize, Some(len)) + } +} + +impl std::iter::FusedIterator for InstIter<'_> {} + +/// A bytecode iterator that yields opcodes and their immediate data, alongside the program counter. +/// +/// Created by calling [`InstIter::with_pc`]. +#[derive(Debug)] +pub struct InstIterWithPc<'a> { + iter: InstIter<'a>, + pc: usize, +} + +impl<'a> Iterator for InstIterWithPc<'a> { + type Item = (usize, Inst<'a>); + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|inst| { + let pc = self.pc; + self.pc += 1 + inst.immediate.len(); + (pc, inst) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl std::iter::FusedIterator for InstIterWithPc<'_> {} + +/// An opcode and its immediate data. Returned by [`InstIter`]. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Inst<'a> { + /// The opcode. + pub opcode: OpCode, + /// The immediate data, if any. + /// + /// If an opcode is missing immediate data, e.g. malformed or bytecode hash, this will be an + /// empty slice. + pub immediate: &'a [u8], +} + +impl fmt::Debug for Inst<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for Inst<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.opcode)?; + match self.immediate { + [] => Ok(()), + imm => write!(f, " {:#x}", alloy_primitives::hex::display(imm)), + } + } +} + +/// Returns the length of the immediate data for the given opcode, or `0` if none. +#[inline] +const fn imm_len(op: u8) -> u8 { + match op { + opcode::PUSH1..=opcode::PUSH32 => op - opcode::PUSH0, + _ => 0, + } +} + +/// Returns a string representation of the given bytecode. +pub fn format_bytecode(bytecode: &[u8]) -> String { + let mut w = String::new(); + format_bytecode_to(bytecode, &mut w).unwrap(); + w +} + +/// Formats an EVM bytecode to the given writer. +pub fn format_bytecode_to(bytecode: &[u8], w: &mut W) -> fmt::Result { + write!(w, "{}", InstIter::new(bytecode)) +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::bytecode::opcode as op; + + fn o(op: u8) -> OpCode { + unsafe { OpCode::new_unchecked(op) } + } + + #[test] + fn iter_basic() { + let bytecode = [0x01, 0x02, 0x03, 0x04, 0x05]; + let mut iter = InstIter::new(&bytecode); + + assert_eq!(iter.next(), Some(Inst { opcode: o(0x01), immediate: &[] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(0x02), immediate: &[] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(0x03), immediate: &[] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(0x04), immediate: &[] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(0x05), immediate: &[] })); + assert_eq!(iter.next(), None); + } + + #[test] + fn iter_with_imm() { + let bytecode = [op::PUSH0, op::PUSH1, 0x69, op::PUSH2, 0x01, 0x02]; + let mut iter = InstIter::new(&bytecode); + + assert_eq!(iter.next(), Some(Inst { opcode: o(op::PUSH0), immediate: &[] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(op::PUSH1), immediate: &[0x69] })); + assert_eq!(iter.next(), Some(Inst { opcode: o(op::PUSH2), immediate: &[0x01, 0x02] })); + assert_eq!(iter.next(), None); + } + + #[test] + fn iter_with_imm_too_short() { + let bytecode = [op::PUSH2, 0x69]; + let mut iter = InstIter::new(&bytecode); + + assert_eq!(iter.next(), Some(Inst { opcode: o(op::PUSH2), immediate: &[] })); + assert_eq!(iter.next(), None); + } + + #[test] + fn display() { + let bytecode = [op::PUSH0, op::PUSH1, 0x69, op::PUSH2, 0x01, 0x02]; + let s = format_bytecode(&bytecode); + assert_eq!(s, "PUSH0 PUSH1 0x69 PUSH2 0x0102"); + } + + #[test] + fn decode_push2_and_stop() { + // 0x61 0xAA 0xBB = PUSH2 0xAABB + // 0x00 = STOP + let code = vec![0x61, 0xAA, 0xBB, 0x00]; + let insns = InstIter::new(&code).with_pc().collect::>(); + + // PUSH2 then STOP + assert_eq!(insns.len(), 2); + + // PUSH2 at pc = 0 + let i0 = &insns[0]; + assert_eq!(i0.0, 0); + assert_eq!(i0.1.opcode, op::PUSH2); + assert_eq!(i0.1.immediate, &[0xAA, 0xBB]); + + // STOP at pc = 3 + let i1 = &insns[1]; + assert_eq!(i1.0, 3); + assert_eq!(i1.1.opcode, op::STOP); + assert!(i1.1.immediate.is_empty()); + } + + #[test] + fn decode_arithmetic_ops() { + // 0x01 = ADD, 0x02 = MUL, 0x03 = SUB, 0x04 = DIV + let code = vec![0x01, 0x02, 0x03, 0x04]; + let insns = InstIter::new(&code).with_pc().collect::>(); + + assert_eq!(insns.len(), 4); + + let expected = [(0, op::ADD), (1, op::MUL), (2, op::SUB), (3, op::DIV)]; + for ((pc, want_op), insn) in expected.iter().zip(insns.iter()) { + assert_eq!(insn.0, *pc); + assert_eq!(insn.1.opcode, *want_op); + assert!(insn.1.immediate.is_empty()); + } + } +} diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index feb8fc0e667e0..ee3bd818c092e 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{address, b256, hex, Address, B256}; +use alloy_primitives::{Address, B256, address, b256, hex}; /// The cheatcode handler address. /// @@ -17,7 +17,7 @@ pub const CHEATCODE_CONTRACT_HASH: B256 = /// The Hardhat console address. /// -/// See: +/// See: pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("0x000000000000000000636F6e736F6c652e6c6f67"); /// Stores the caller address to be used as *sender* account for: @@ -29,7 +29,9 @@ pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("0x000000000000000000636F6 pub const CALLER: Address = address!("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38"); /// The default test contract address. -pub const TEST_CONTRACT_ADDRESS: Address = address!("0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84"); +/// +/// Derived from `CALLER.create(1)`. +pub const TEST_CONTRACT_ADDRESS: Address = address!("0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496"); /// Magic return value returned by the `assume` cheatcode. pub const MAGIC_ASSUME: &[u8] = b"FOUNDRY::ASSUME"; @@ -37,9 +39,6 @@ pub const MAGIC_ASSUME: &[u8] = b"FOUNDRY::ASSUME"; /// Magic return value returned by the `skip` cheatcode. Optionally appended with a reason. pub const MAGIC_SKIP: &[u8] = b"FOUNDRY::SKIP"; -/// Test timeout return value. -pub const TEST_TIMEOUT: &str = "FOUNDRY::TEST_TIMEOUT"; - /// The address that deploys the default CREATE2 deployer contract. pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = address!("0x3fAB184622Dc19b6109349B94811493BF2a45362"); @@ -47,9 +46,13 @@ pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = pub const DEFAULT_CREATE2_DEPLOYER: Address = address!("0x4e59b44847b379578588920ca78fbf26c0b4956c"); /// The initcode of the default CREATE2 deployer. -pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); +pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!( + "604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" +); /// The runtime code of the default CREATE2 deployer. -pub const DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE: &[u8] = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); +pub const DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE: &[u8] = &hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" +); /// The hash of the default CREATE2 deployer code. /// /// This is calculated as `keccak256([`DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE`])`. diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index c3dab88f49cd5..b836023a968b7 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -1,9 +1,9 @@ //! Various utilities to decode test results. -use crate::abi::{console, Vm}; +use crate::abi::{Vm, console}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Error, JsonAbi}; -use alloy_primitives::{hex, map::HashMap, Log, Selector}; +use alloy_primitives::{Log, Selector, hex, map::HashMap}; use alloy_sol_types::{ ContractError::Revert, RevertReason, RevertReason::ContractError, SolEventInterface, SolInterface, SolValue, @@ -13,6 +13,12 @@ use itertools::Itertools; use revm::interpreter::InstructionResult; use std::{fmt, sync::OnceLock}; +/// Stable user-facing fallback for empty revert payloads. +pub const EMPTY_REVERT_DATA: &str = ""; + +/// Prefix used by Foundry assertion helpers in user-visible failure messages. +pub const ASSERTION_FAILED_PREFIX: &str = "assertion failed"; + /// A skip reason. #[derive(Clone, Debug, PartialEq, Eq)] pub struct SkipReason(pub Option); @@ -62,7 +68,7 @@ pub fn decode_console_log(log: &Log) -> Option { #[derive(Clone, Debug, Default)] pub struct RevertDecoder { /// The custom errors to use for decoding. - pub errors: HashMap>, + errors: HashMap>, } impl Default for &RevertDecoder { @@ -94,25 +100,15 @@ impl RevertDecoder { self } - /// Sets the ABI to use for error decoding, if it is present. - /// - /// Note that this is decently expensive as it will hash all errors for faster indexing. - pub fn with_abi_opt(mut self, abi: Option<&JsonAbi>) -> Self { - if let Some(abi) = abi { - self.extend_from_abi(abi); - } - self - } - /// Extends the decoder with the given ABI's custom errors. - pub fn extend_from_abis<'a>(&mut self, abi: impl IntoIterator) { + fn extend_from_abis<'a>(&mut self, abi: impl IntoIterator) { for abi in abi { self.extend_from_abi(abi); } } /// Extends the decoder with the given ABI's custom errors. - pub fn extend_from_abi(&mut self, abi: &JsonAbi) { + fn extend_from_abi(&mut self, abi: &JsonAbi) { for error in abi.errors() { self.push_error(error.clone()); } @@ -129,11 +125,7 @@ impl RevertDecoder { /// than user output. pub fn decode(&self, err: &[u8], status: Option) -> String { self.maybe_decode(err, status).unwrap_or_else(|| { - if err.is_empty() { - "".to_string() - } else { - trimmed_hex(err) - } + if err.is_empty() { EMPTY_REVERT_DATA.to_string() } else { trimmed_hex(err) } }) } @@ -141,10 +133,6 @@ impl RevertDecoder { /// /// See [`decode`](Self::decode) for more information. pub fn maybe_decode(&self, err: &[u8], status: Option) -> Option { - if let Some(reason) = SkipReason::decode(err) { - return Some(reason.to_string()); - } - // Solidity's `Error(string)` (handled separately in order to strip revert: prefix) if let Some(ContractError(Revert(revert))) = RevertReason::decode(err) { return Some(revert.reason); @@ -174,7 +162,7 @@ impl RevertDecoder { } if string_decoded.is_some() { - return string_decoded + return string_decoded; } // Generic custom error. @@ -188,17 +176,17 @@ impl RevertDecoder { } } s - }) + }); } if string_decoded.is_some() { - return string_decoded + return string_decoded; } - if let Some(status) = status { - if !status.is_ok() { - return Some(format!("EvmError: {status:?}")); - } + if let Some(status) = status + && !status.is_ok() + { + return Some(format!("EvmError: {status:?}")); } if err.is_empty() { None @@ -211,10 +199,10 @@ impl RevertDecoder { /// Helper function that decodes provided error as an ABI encoded or an ASCII string (if not empty). fn decode_as_non_empty_string(err: &[u8]) -> Option { // ABI-encoded `string`. - if let Ok(s) = String::abi_decode(err) { - if !s.is_empty() { - return Some(s); - } + if let Ok(s) = String::abi_decode(err) + && !s.is_empty() + { + return Some(s); } // ASCII string. @@ -235,8 +223,8 @@ fn trimmed_hex(s: &[u8]) -> String { } else { format!( "{}…{} ({} bytes)", - &hex::encode(&s[..n / 2]), - &hex::encode(&s[s.len() - n / 2..]), + hex::encode(&s[..n / 2]), + hex::encode(&s[s.len() - n / 2..]), s.len(), ) } @@ -272,7 +260,10 @@ mod tests { "0xe17594de" "756688fe00000000000000000000000000000000000000000000000000000000" ); - assert_eq!(decoder.decode(data, None), "custom error 0xe17594de: 756688fe00000000000000000000000000000000000000000000000000000000"); + assert_eq!( + decoder.decode(data, None), + "custom error 0xe17594de: 756688fe00000000000000000000000000000000000000000000000000000000" + ); /* abi.encodeWithSelector(ValidationFailed.selector, abi.encodeWithSelector(InvalidNonce.selector)) @@ -285,4 +276,13 @@ mod tests { ); assert_eq!(decoder.decode(data, None), "ValidationFailed(0x756688fe)"); } + + #[test] + fn maybe_decode_magic_skip_is_not_skip_marker() { + let decoder = RevertDecoder::new(); + let reason = decoder.maybe_decode(crate::constants::MAGIC_SKIP, None).unwrap(); + + assert_eq!(reason, "FOUNDRY::SKIP"); + assert!(SkipReason::decode_self(&reason).is_none()); + } } diff --git a/crates/evm/core/src/either_evm.rs b/crates/evm/core/src/either_evm.rs deleted file mode 100644 index 2ef9f894f144b..0000000000000 --- a/crates/evm/core/src/either_evm.rs +++ /dev/null @@ -1,276 +0,0 @@ -use alloy_evm::{eth::EthEvmContext, Database, EthEvm, Evm, EvmEnv}; -use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, Bytes}; -use op_revm::{OpContext, OpHaltReason, OpSpecId, OpTransaction, OpTransactionError}; -use revm::{ - context::{ - result::{EVMError, ExecutionResult, HaltReason, ResultAndState}, - BlockEnv, TxEnv, - }, - handler::PrecompileProvider, - interpreter::InterpreterResult, - primitives::hardfork::SpecId, - DatabaseCommit, Inspector, -}; - -/// Alias for result type returned by [`Evm::transact`] methods. -type EitherEvmResult = - Result, EVMError>; - -/// Alias for result type returned by [`Evm::transact_commit`] methods. -type EitherExecResult = - Result, EVMError>; - -/// [`EitherEvm`] delegates its calls to one of the two evm implementations; either [`EthEvm`] or -/// [`OpEvm`]. -/// -/// Calls are delegated to [`OpEvm`] only if optimism is enabled. -/// -/// The call delegation is handled via its own implementation of the [`Evm`] trait. -/// -/// The [`Evm::transact`] and other such calls work over the [`OpTransaction`] type. -/// -/// However, the [`Evm::HaltReason`] and [`Evm::Error`] leverage the optimism [`OpHaltReason`] and -/// [`OpTransactionError`] as these are supersets of the eth types. This makes it easier to map eth -/// types to op types and also prevents ignoring of any error that maybe thrown by [`OpEvm`]. -#[allow(clippy::large_enum_variant)] -pub enum EitherEvm -where - DB: Database, -{ - /// [`EthEvm`] implementation. - Eth(EthEvm), - /// [`OpEvm`] implementation. - Op(OpEvm), -} - -impl EitherEvm -where - DB: Database, - I: Inspector> + Inspector>, - P: PrecompileProvider, Output = InterpreterResult> - + PrecompileProvider, Output = InterpreterResult>, -{ - /// Converts the [`EthEvm::transact`] result to [`EitherEvmResult`]. - fn map_eth_result( - &self, - result: Result, EVMError>, - ) -> EitherEvmResult { - match result { - Ok(result) => { - // Map the halt reason - Ok(result.map_haltreason(OpHaltReason::Base)) - } - Err(e) => Err(self.map_eth_err(e)), - } - } - - /// Converts the [`EthEvm::transact_commit`] result to [`EitherExecResult`]. - fn map_exec_result( - &self, - result: Result>, - ) -> EitherExecResult { - match result { - Ok(result) => { - // Map the halt reason - Ok(result.map_haltreason(OpHaltReason::Base)) - } - Err(e) => Err(self.map_eth_err(e)), - } - } - - /// Maps [`EVMError`] to [`EVMError`]. - fn map_eth_err(&self, err: EVMError) -> EVMError { - match err { - EVMError::Transaction(invalid_tx) => { - EVMError::Transaction(OpTransactionError::Base(invalid_tx)) - } - EVMError::Database(e) => EVMError::Database(e), - EVMError::Header(e) => EVMError::Header(e), - EVMError::Custom(e) => EVMError::Custom(e), - } - } -} - -impl Evm for EitherEvm -where - DB: Database, - I: Inspector> + Inspector>, - P: PrecompileProvider, Output = InterpreterResult> - + PrecompileProvider, Output = InterpreterResult>, -{ - type DB = DB; - type Error = EVMError; - type HaltReason = OpHaltReason; - type Tx = OpTransaction; - type Inspector = I; - type Precompiles = P; - type Spec = SpecId; - - fn chain_id(&self) -> u64 { - match self { - Self::Eth(evm) => evm.chain_id(), - Self::Op(evm) => evm.chain_id(), - } - } - - fn block(&self) -> &BlockEnv { - match self { - Self::Eth(evm) => evm.block(), - Self::Op(evm) => evm.block(), - } - } - - fn db_mut(&mut self) -> &mut Self::DB { - match self { - Self::Eth(evm) => evm.db_mut(), - Self::Op(evm) => evm.db_mut(), - } - } - - fn into_db(self) -> Self::DB - where - Self: Sized, - { - match self { - Self::Eth(evm) => evm.into_db(), - Self::Op(evm) => evm.into_db(), - } - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - match self { - Self::Eth(evm) => evm.finish(), - Self::Op(evm) => { - let (db, env) = evm.finish(); - (db, map_env(env)) - } - } - } - - fn precompiles(&self) -> &Self::Precompiles { - match self { - Self::Eth(evm) => evm.precompiles(), - Self::Op(evm) => evm.precompiles(), - } - } - - fn precompiles_mut(&mut self) -> &mut Self::Precompiles { - match self { - Self::Eth(evm) => evm.precompiles_mut(), - Self::Op(evm) => evm.precompiles_mut(), - } - } - - fn inspector(&self) -> &Self::Inspector { - match self { - Self::Eth(evm) => evm.inspector(), - Self::Op(evm) => evm.inspector(), - } - } - - fn inspector_mut(&mut self) -> &mut Self::Inspector { - match self { - Self::Eth(evm) => evm.inspector_mut(), - Self::Op(evm) => evm.inspector_mut(), - } - } - - fn enable_inspector(&mut self) { - match self { - Self::Eth(evm) => evm.enable_inspector(), - Self::Op(evm) => evm.enable_inspector(), - } - } - - fn disable_inspector(&mut self) { - match self { - Self::Eth(evm) => evm.disable_inspector(), - Self::Op(evm) => evm.disable_inspector(), - } - } - - fn set_inspector_enabled(&mut self, enabled: bool) { - match self { - Self::Eth(evm) => evm.set_inspector_enabled(enabled), - Self::Op(evm) => evm.set_inspector_enabled(enabled), - } - } - - fn into_env(self) -> EvmEnv - where - Self: Sized, - { - match self { - Self::Eth(evm) => evm.into_env(), - Self::Op(evm) => map_env(evm.into_env()), - } - } - - fn transact( - &mut self, - tx: impl alloy_evm::IntoTxEnv, - ) -> Result, Self::Error> { - match self { - Self::Eth(evm) => { - let eth = evm.transact(tx.into_tx_env().base); - self.map_eth_result(eth) - } - Self::Op(evm) => evm.transact(tx), - } - } - - fn transact_commit( - &mut self, - tx: impl alloy_evm::IntoTxEnv, - ) -> Result, Self::Error> - where - Self::DB: DatabaseCommit, - { - match self { - Self::Eth(evm) => { - let eth = evm.transact_commit(tx.into_tx_env().base); - self.map_exec_result(eth) - } - Self::Op(evm) => evm.transact_commit(tx), - } - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - match self { - Self::Eth(evm) => { - let res = evm.transact_raw(tx.base); - self.map_eth_result(res) - } - Self::Op(evm) => evm.transact_raw(tx), - } - } - - fn transact_system_call( - &mut self, - caller: Address, - contract: Address, - data: Bytes, - ) -> Result, Self::Error> { - match self { - Self::Eth(evm) => { - let eth = evm.transact_system_call(caller, contract, data); - self.map_eth_result(eth) - } - Self::Op(evm) => evm.transact_system_call(caller, contract, data), - } - } -} - -/// Maps [`EvmEnv`] to [`EvmEnv`]. -fn map_env(env: EvmEnv) -> EvmEnv { - let eth_spec_id = env.spec_id().into_eth_spec(); - let cfg = env.cfg_env.with_spec(eth_spec_id); - EvmEnv { cfg_env: cfg, block_env: env.block_env } -} diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index a7708f132380d..76429e4bc78e3 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -1,103 +1,1025 @@ +use std::fmt::Debug; + +use alloy_consensus::Typed2718; pub use alloy_evm::EvmEnv; +use alloy_evm::FromRecoveredTx; +use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; +use alloy_primitives::{Address, B256, Bytes, U256}; +#[cfg(feature = "optimism")] +use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; use revm::{ - context::{BlockEnv, CfgEnv, JournalInner, JournalTr, TxEnv}, - primitives::hardfork::SpecId, - Context, Database, Journal, JournalEntry, + Context, Database, Journal, + context::{Block, BlockEnv, Cfg, CfgEnv, Transaction, TxEnv}, + context_interface::{ + ContextTr, + either::Either, + transaction::{AccessList, RecoveredAuthorization, SignedAuthorization}, + }, + inspector::JournalExt, + primitives::{TxKind, hardfork::SpecId}, }; +use tempo_revm::{TempoBlockEnv, TempoTxEnv}; + +use crate::backend::JournaledState; + +/// Extension of [`Block`] with mutable setters, allowing EVM-agnostic mutation of block fields. +pub trait FoundryBlock: Block { + /// Sets the block number. + fn set_number(&mut self, number: U256); + + /// Sets the beneficiary (coinbase) address. + fn set_beneficiary(&mut self, beneficiary: Address); + + /// Sets the block timestamp. + fn set_timestamp(&mut self, timestamp: U256); + + /// Sets the gas limit. + fn set_gas_limit(&mut self, gas_limit: u64); + + /// Sets the base fee per gas. + fn set_basefee(&mut self, basefee: u64); + + /// Sets the block difficulty. + fn set_difficulty(&mut self, difficulty: U256); + + /// Sets the prevrandao value. + fn set_prevrandao(&mut self, prevrandao: Option); + + /// Sets the excess blob gas and blob gasprice. + fn set_blob_excess_gas_and_price( + &mut self, + _excess_blob_gas: u64, + _base_fee_update_fraction: u64, + ); + + // Tempo methods -/// Helper container type for [`EvmEnv`] and [`TxEnv`]. -#[derive(Clone, Debug, Default)] -pub struct Env { - pub evm_env: EvmEnv, - pub tx: TxEnv, + /// Returns the milliseconds portion of the block timestamp. + fn timestamp_millis_part(&self) -> u64 { + 0 + } + + /// Sets the milliseconds portion of the block timestamp. + fn set_timestamp_millis_part(&mut self, _millis: u64) {} } -/// Helper container type for [`EvmEnv`] and [`TxEnv`]. -impl Env { - pub fn default_with_spec_id(spec_id: SpecId) -> Self { - let mut cfg = CfgEnv::default(); - cfg.spec = spec_id; +impl FoundryBlock for BlockEnv { + fn set_number(&mut self, number: U256) { + self.number = number; + } + + fn set_beneficiary(&mut self, beneficiary: Address) { + self.beneficiary = beneficiary; + } + + fn set_timestamp(&mut self, timestamp: U256) { + self.timestamp = timestamp; + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.gas_limit = gas_limit; + } - Self::from(cfg, BlockEnv::default(), TxEnv::default()) + fn set_basefee(&mut self, basefee: u64) { + self.basefee = basefee; } - pub fn from(cfg: CfgEnv, block: BlockEnv, tx: TxEnv) -> Self { - Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx } + fn set_difficulty(&mut self, difficulty: U256) { + self.difficulty = difficulty; } - pub fn new_with_spec_id(cfg: CfgEnv, block: BlockEnv, tx: TxEnv, spec_id: SpecId) -> Self { - let mut cfg = cfg; - cfg.spec = spec_id; + fn set_prevrandao(&mut self, prevrandao: Option) { + self.prevrandao = prevrandao; + } - Self::from(cfg, block, tx) + fn set_blob_excess_gas_and_price( + &mut self, + excess_blob_gas: u64, + base_fee_update_fraction: u64, + ) { + self.set_blob_excess_gas_and_price(excess_blob_gas, base_fee_update_fraction); } } -/// Helper struct with mutable references to the block and cfg environments. -pub struct EnvMut<'a> { - pub block: &'a mut BlockEnv, - pub cfg: &'a mut CfgEnv, - pub tx: &'a mut TxEnv, +impl FoundryBlock for TempoBlockEnv { + fn set_number(&mut self, number: U256) { + self.inner.set_number(number); + } + + fn set_beneficiary(&mut self, beneficiary: Address) { + self.inner.set_beneficiary(beneficiary); + } + + fn set_timestamp(&mut self, timestamp: U256) { + self.inner.set_timestamp(timestamp); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.inner.set_gas_limit(gas_limit); + } + + fn set_basefee(&mut self, basefee: u64) { + self.inner.set_basefee(basefee); + } + + fn set_difficulty(&mut self, difficulty: U256) { + self.inner.set_difficulty(difficulty); + } + + fn set_prevrandao(&mut self, prevrandao: Option) { + self.inner.set_prevrandao(prevrandao); + } + + fn set_blob_excess_gas_and_price( + &mut self, + _excess_blob_gas: u64, + _base_fee_update_fraction: u64, + ) { + } + + fn timestamp_millis_part(&self) -> u64 { + self.timestamp_millis_part + } + + fn set_timestamp_millis_part(&mut self, millis: u64) { + self.timestamp_millis_part = millis; + } } -impl EnvMut<'_> { - /// Returns a copy of the environment. - pub fn to_owned(&self) -> Env { - Env { - evm_env: EvmEnv { cfg_env: self.cfg.to_owned(), block_env: self.block.to_owned() }, - tx: self.tx.to_owned(), +/// Extension of [`Transaction`] with mutable setters, allowing EVM-agnostic mutation of transaction +/// fields. +pub trait FoundryTransaction: Transaction { + /// Sets the transaction type. + fn set_tx_type(&mut self, tx_type: u8); + + /// Sets the caller (sender) address. + fn set_caller(&mut self, caller: Address); + + /// Sets the gas limit. + fn set_gas_limit(&mut self, gas_limit: u64); + + /// Sets the gas price (or max fee per gas for EIP-1559). + fn set_gas_price(&mut self, gas_price: u128); + + /// Sets the transaction kind (call or create). + fn set_kind(&mut self, kind: TxKind); + + /// Sets the value sent with the transaction. + fn set_value(&mut self, value: U256); + + /// Sets the transaction input data. + fn set_data(&mut self, data: Bytes); + + /// Sets the nonce. + fn set_nonce(&mut self, nonce: u64); + + /// Sets the chain ID. + fn set_chain_id(&mut self, chain_id: Option); + + /// Sets the access list. + fn set_access_list(&mut self, access_list: AccessList); + + /// Returns a mutable reference to the EIP-7702 authorization list. + fn authorization_list_mut( + &mut self, + ) -> &mut Vec>; + + /// Sets the max priority fee per gas. + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option); + + /// Sets the blob versioned hashes. + fn set_blob_hashes(&mut self, blob_hashes: Vec); + + /// Sets the max fee per blob gas. + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128); + + /// Sets the EIP-7702 signed authorization list. + fn set_signed_authorization(&mut self, auth: Vec) { + *self.authorization_list_mut() = auth.into_iter().map(Either::Left).collect(); + } + + // `OpTransaction` methods + + /// Enveloped transaction bytes. + fn enveloped_tx(&self) -> Option<&Bytes> { + None + } + + /// Set Enveloped transaction bytes. + fn set_enveloped_tx(&mut self, _bytes: Bytes) {} + + /// Source hash of the deposit transaction. + fn source_hash(&self) -> Option { + None + } + + /// Sets source hash of the deposit transaction. + fn set_source_hash(&mut self, _source_hash: B256) {} + + /// Mint of the deposit transaction + fn mint(&self) -> Option { + None + } + + /// Sets mint of the deposit transaction. + fn set_mint(&mut self, _mint: u128) {} + + /// Whether the transaction is a system transaction + fn is_system_transaction(&self) -> bool { + false + } + + /// Sets whether the transaction is a system transaction + fn set_system_transaction(&mut self, _is_system_transaction: bool) {} + + /// Returns `true` if transaction is an Optimism deposit transaction. + fn is_deposit(&self) -> bool { + #[cfg(feature = "optimism")] + { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE } + #[cfg(not(feature = "optimism"))] + { + false + } + } + + // Tempo methods + + /// Returns the fee token address for this transaction. + fn fee_token(&self) -> Option
{ + None + } + + /// Sets the fee token address for this transaction. + fn set_fee_token(&mut self, _token: Option
) {} + + /// Returns the fee payer for this transaction. + fn fee_payer(&self) -> Option> { + None } + + /// Sets the fee payer for this transaction. + fn set_fee_payer(&mut self, _payer: Option>) {} } -pub trait AsEnvMut { - fn as_env_mut(&mut self) -> EnvMut<'_>; +impl FoundryTransaction for TxEnv { + fn set_tx_type(&mut self, tx_type: u8) { + self.tx_type = tx_type; + } + + fn set_caller(&mut self, caller: Address) { + self.caller = caller; + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.gas_limit = gas_limit; + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.gas_price = gas_price; + } + + fn set_kind(&mut self, kind: TxKind) { + self.kind = kind; + } + + fn set_value(&mut self, value: U256) { + self.value = value; + } + + fn set_data(&mut self, data: Bytes) { + self.data = data; + } + + fn set_nonce(&mut self, nonce: u64) { + self.nonce = nonce; + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.chain_id = chain_id; + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.access_list = access_list; + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + &mut self.authorization_list + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.gas_priority_fee = gas_priority_fee; + } + + fn set_blob_hashes(&mut self, blob_hashes: Vec) { + self.blob_hashes = blob_hashes; + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.max_fee_per_blob_gas = max_fee_per_blob_gas; + } } -impl AsEnvMut for EnvMut<'_> { - fn as_env_mut(&mut self) -> EnvMut<'_> { - EnvMut { block: self.block, cfg: self.cfg, tx: self.tx } +impl FoundryTransaction for TempoTxEnv { + fn set_tx_type(&mut self, tx_type: u8) { + self.inner.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.inner.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.inner.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.inner.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.inner.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.inner.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.inner.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.inner.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.inner.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.inner.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.inner.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.inner.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn fee_token(&self) -> Option
{ + self.fee_token + } + + fn set_fee_token(&mut self, token: Option
) { + self.fee_token = token; + } + + fn fee_payer(&self) -> Option> { + self.fee_payer + } + + fn set_fee_payer(&mut self, payer: Option>) { + self.fee_payer = payer; } } -impl AsEnvMut for Env { - fn as_env_mut(&mut self) -> EnvMut<'_> { - EnvMut { - block: &mut self.evm_env.block_env, - cfg: &mut self.evm_env.cfg_env, - tx: &mut self.tx, - } +/// Extension trait providing mutable field access to block, tx, and cfg environments. +/// +/// [`ContextTr`] only exposes immutable references for block, tx, and cfg. +/// Cheatcodes like `vm.warp()`, `vm.roll()`, `vm.chainId()` need to mutate these fields. +pub trait FoundryContextExt: + ContextTr< + Block: FoundryBlock + Clone, + Tx: FoundryTransaction + Clone, + Cfg = CfgEnv, + Journal: JournalExt, + > +{ + /// Specification id type + /// + /// Bubbled-up from `ContextTr::Cfg` for convenience and simplified bounds. + type Spec: Into + Copy + Debug; + + /// Mutable reference to the block environment. + fn block_mut(&mut self) -> &mut Self::Block; + + /// Mutable reference to the transaction environment. + fn tx_mut(&mut self) -> &mut Self::Tx; + + /// Mutable reference to the configuration environment. + fn cfg_mut(&mut self) -> &mut Self::Cfg; + + /// Mutable reference to the db and the journal inner. + fn db_journal_inner_mut(&mut self) -> (&mut Self::Db, &mut JournaledState); + + /// Sets block environment. + fn set_block(&mut self, block: Self::Block) { + *self.block_mut() = block; + } + + /// Sets transaction environment. + fn set_tx(&mut self, tx: Self::Tx) { + *self.tx_mut() = tx; + } + + /// Sets configuration environment. + fn set_cfg(&mut self, cfg: Self::Cfg) { + *self.cfg_mut() = cfg; + } + + /// Sets journal inner. + fn set_journal_inner(&mut self, journal_inner: JournaledState) { + *self.db_journal_inner_mut().1 = journal_inner; + } + + /// Sets EVM environment. + fn set_evm(&mut self, evm_env: EvmEnv) { + *self.cfg_mut() = evm_env.cfg_env; + *self.block_mut() = evm_env.block_env; + } + + /// Cloned transaction environment. + fn tx_clone(&self) -> Self::Tx { + self.tx().clone() + } + + /// Cloned EVM environment (Cfg + Block). + fn evm_clone(&self) -> EvmEnv { + EvmEnv::new(self.cfg().clone(), self.block().clone()) } } -impl, C> AsEnvMut - for Context +impl< + BLOCK: FoundryBlock + Clone, + TX: FoundryTransaction + Clone, + SPEC: Into + Copy + Debug, + DB: Database, + C, +> FoundryContextExt for Context, DB, Journal, C> { - fn as_env_mut(&mut self) -> EnvMut<'_> { - EnvMut { block: &mut self.block, cfg: &mut self.cfg, tx: &mut self.tx } + type Spec = ::Spec; + + fn block_mut(&mut self) -> &mut Self::Block { + &mut self.block + } + + fn tx_mut(&mut self) -> &mut Self::Tx { + &mut self.tx + } + + fn cfg_mut(&mut self) -> &mut Self::Cfg { + &mut self.cfg + } + + fn db_journal_inner_mut(&mut self) -> (&mut Self::Db, &mut JournaledState) { + (&mut self.journaled_state.database, &mut self.journaled_state.inner) } } -pub trait ContextExt { - type DB: Database; +/// Trait for converting an [`AnyRpcTransaction`] into a specific `TxEnv`. +/// +/// Implementations extract the inner [`alloy_consensus::TxEnvelope`] via +/// [`as_envelope()`](alloy_network::AnyTxEnvelope::as_envelope) then delegate to +/// [`FromRecoveredTx`]. +pub trait FromAnyRpcTransaction: Sized { + /// Tries to convert an [`AnyRpcTransaction`] into `Self`. + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result; +} - fn as_db_env_and_journal( - &mut self, - ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>); +impl FromAnyRpcTransaction for TxEnv { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + Ok(Self::from_recovered_tx(envelope, tx.from())) + } else { + eyre::bail!("cannot convert unknown transaction type to TxEnv") + } + } } -impl ContextExt - for Context, C> -{ - type DB = DB; +impl FromAnyRpcTransaction for TempoTxEnv { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + use alloy_consensus::Transaction as _; + if let Some(envelope) = tx.as_envelope() { + return Ok(TxEnv::from_recovered_tx(envelope, tx.from()).into()); + } - fn as_db_env_and_journal( - &mut self, - ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>) { - ( - &mut self.journaled_state.database, - &mut self.journaled_state.inner, - EnvMut { block: &mut self.block, cfg: &mut self.cfg, tx: &mut self.tx }, + // Handle Tempo transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == tempo_alloy::primitives::TEMPO_TX_TYPE_ID + { + let base = TxEnv { + tx_type: unknown.ty(), + caller: tx.from(), + gas_limit: unknown.gas_limit(), + gas_price: unknown.max_fee_per_gas(), + gas_priority_fee: unknown.max_priority_fee_per_gas(), + kind: unknown.kind(), + value: unknown.value(), + data: unknown.input().clone(), + nonce: unknown.nonce(), + chain_id: unknown.chain_id(), + access_list: unknown.access_list().cloned().unwrap_or_default(), + ..Default::default() + }; + let fee_token = + unknown.inner.fields.get_deserialized::
("feeToken").and_then(Result::ok); + return Ok(Self { inner: base, fee_token, ..Default::default() }); + } + + eyre::bail!("cannot convert unknown transaction type to TempoTxEnv") + } +} + +#[cfg(feature = "optimism")] +mod optimism { + use super::*; + use alloy_op_evm::OpTx; + use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit}; + use op_revm::{OpTransaction, transaction::OpTxTr}; + + impl FoundryTransaction for OpTransaction { + fn set_tx_type(&mut self, tx_type: u8) { + self.base.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.base.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.base.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.base.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.base.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.base.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.base.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.base.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.base.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + OpTxTr::enveloped_tx(self) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.enveloped_tx = Some(bytes); + } + + fn source_hash(&self) -> Option { + OpTxTr::source_hash(self) + } + + fn set_source_hash(&mut self, source_hash: B256) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.source_hash = source_hash; + } + } + + fn mint(&self) -> Option { + OpTxTr::mint(self) + } + + fn set_mint(&mut self, mint: u128) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.mint = Some(mint); + } + } + + fn is_system_transaction(&self) -> bool { + OpTxTr::is_system_transaction(self) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + self.deposit.is_system_transaction = is_system_transaction; + } + } + } + + impl FoundryTransaction for OpTx { + fn set_tx_type(&mut self, tx_type: u8) { + self.0.set_tx_type(tx_type); + } + + fn set_caller(&mut self, caller: Address) { + self.0.set_caller(caller); + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.set_gas_limit(gas_limit); + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.0.set_gas_price(gas_price); + } + + fn set_kind(&mut self, kind: TxKind) { + self.0.set_kind(kind); + } + + fn set_value(&mut self, value: U256) { + self.0.set_value(value); + } + + fn set_data(&mut self, data: Bytes) { + self.0.set_data(data); + } + + fn set_nonce(&mut self, nonce: u64) { + self.0.set_nonce(nonce); + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.0.set_chain_id(chain_id); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.0.set_access_list(access_list); + } + + fn authorization_list_mut( + &mut self, + ) -> &mut Vec> { + self.0.authorization_list_mut() + } + + fn set_gas_priority_fee(&mut self, gas_priority_fee: Option) { + self.0.set_gas_priority_fee(gas_priority_fee); + } + + fn set_blob_hashes(&mut self, _blob_hashes: Vec) {} + + fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {} + + fn enveloped_tx(&self) -> Option<&Bytes> { + FoundryTransaction::enveloped_tx(&self.0) + } + + fn set_enveloped_tx(&mut self, bytes: Bytes) { + self.0.set_enveloped_tx(bytes); + } + + fn source_hash(&self) -> Option { + FoundryTransaction::source_hash(&self.0) + } + + fn set_source_hash(&mut self, source_hash: B256) { + self.0.set_source_hash(source_hash); + } + + fn mint(&self) -> Option { + FoundryTransaction::mint(&self.0) + } + + fn set_mint(&mut self, mint: u128) { + self.0.set_mint(mint); + } + + fn is_system_transaction(&self) -> bool { + FoundryTransaction::is_system_transaction(&self.0) + } + + fn set_system_transaction(&mut self, is_system_transaction: bool) { + self.0.set_system_transaction(is_system_transaction); + } + } + + impl FromAnyRpcTransaction for OpTx { + fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result { + if let Some(envelope) = tx.as_envelope() { + return Ok(Self(OpTransaction:: { + base: TxEnv::from_recovered_tx(envelope, tx.from()), + enveloped_tx: None, + deposit: Default::default(), + })); + } + + // Handle OP deposit transactions from `Unknown` envelope variant. + if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner + && unknown.ty() == DEPOSIT_TX_TYPE_ID + { + let mut fields = unknown.inner.fields.clone(); + fields.insert("from".to_string(), serde_json::to_value(tx.from())?); + let deposit_tx: TxDeposit = fields + .deserialize_into() + .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?; + return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from)); + } + + eyre::bail!("cannot convert unknown transaction type to OpTransaction") + } + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU64; + + use super::*; + use alloy_consensus::{Signed, TxEip1559, transaction::Recovered}; + use alloy_evm::{EthEvmFactory, EvmFactory}; + use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; + use alloy_primitives::Signature; + use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo}; + use alloy_serde::WithOtherFields; + use foundry_evm_hardforks::TempoHardfork; + use revm::database::EmptyDB; + use tempo_alloy::primitives::{ + AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, + transaction::PrimitiveSignature, + }; + use tempo_evm::TempoEvmFactory; + + #[test] + fn eth_evm_foundry_context_ext_implementation() { + let mut evm = EthEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = SpecId::AMSTERDAM; + assert_eq!(evm.ctx().cfg().spec, SpecId::AMSTERDAM); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + #[test] + fn tempo_evm_foundry_context_ext_implementation() { + let mut evm = TempoEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = TempoHardfork::Genesis; + assert_eq!(evm.ctx().cfg().spec, TempoHardfork::Genesis); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + fn make_signed_eip1559() -> Signed { + Signed::new_unchecked( + TxEip1559 { + chain_id: 1, + nonce: 42, + gas_limit: 21001, + to: TxKind::Call(Address::with_last_byte(0xBB)), + value: U256::from(101), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + B256::ZERO, ) } + + #[test] + fn from_any_rpc_transaction_for_eth() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + + let any_tx = >::from(rpc_tx); + let tx_env = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + assert_eq!(tx_env.caller, from); + assert_eq!(tx_env.nonce, 42); + assert_eq!(tx_env.gas_limit, 21001); + assert_eq!(tx_env.value, U256::from(101)); + assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB))); + } + + #[test] + fn from_any_rpc_transaction_unknown_envelope_errors() { + let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope { + hash: B256::ZERO, + inner: UnknownTypedTransaction { + ty: AnyTxType(0xFF), + fields: Default::default(), + memo: Default::default(), + }, + }); + let from = Address::random(); + let any_tx = AnyRpcTransaction::new(WithOtherFields::new(RpcTransaction { + inner: Recovered::new_unchecked(unknown, from), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + block_timestamp: None, + })); + + let result = TxEnv::from_any_rpc_transaction(&any_tx).unwrap_err(); + assert!(result.to_string().contains("unknown transaction type")); + } + + #[test] + fn from_any_rpc_transaction_for_tempo_eth_envelope() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + + let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(tx_env.inner.caller, from); + assert_eq!(tx_env.inner.nonce, 42); + assert_eq!(tx_env.inner.gas_limit, 21001); + assert_eq!(tx_env.inner.value, U256::from(101)); + assert_eq!(tx_env.fee_token, None); + } + + #[test] + fn from_any_rpc_transaction_for_tempo_aa() { + let from = Address::random(); + let fee_token = Some(Address::random()); + let tempo_tx = TempoTransaction { + chain_id: 42431, + nonce: 42, + gas_limit: 424242, + fee_token, + nonce_key: U256::from(4242), + valid_after: NonZeroU64::new(1800000000), + ..Default::default() + }; + let aa_signed = AASigned::new_unhashed( + tempo_tx, + TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::new( + U256::ZERO, + U256::ZERO, + false, + ))), + ); + + // Build a concrete Tempo RPC transaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(TempoTxEnvelope::AA(aa_signed), from), + TransactionInfo::default(), + ); + let json = serde_json::to_value(&rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(tx_env.inner.caller, from); + assert_eq!(tx_env.inner.nonce, 42); + assert_eq!(tx_env.inner.gas_limit, 424242); + assert_eq!(tx_env.inner.chain_id, Some(42431)); + assert_eq!(tx_env.fee_token, fee_token); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + use alloy_consensus::Sealed; + use alloy_op_evm::{OpEvmFactory, OpTx}; + use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo}; + use op_alloy_rpc_types::Transaction as OpRpcTransaction; + use op_revm::OpSpecId; + + #[test] + fn op_evm_foundry_context_ext_implementation() { + let mut evm = + OpEvmFactory::::default().create_evm(EmptyDB::default(), EvmEnv::default()); + + // Test EVM Context Block mutation + evm.ctx_mut().block_mut().set_number(U256::from(123)); + assert_eq!(evm.ctx().block().number(), U256::from(123)); + + // Test EVM Context Tx mutation + evm.ctx_mut().tx_mut().set_nonce(99); + assert_eq!(evm.ctx().tx().nonce(), 99); + + // Test EVM Context Cfg mutation + evm.ctx_mut().cfg_mut().spec = OpSpecId::JOVIAN; + assert_eq!(evm.ctx().cfg().spec, OpSpecId::JOVIAN); + + // Round-trip test to ensure no issues with cloning and setting tx_env and evm_env + let tx_env = evm.ctx().tx_clone(); + evm.ctx_mut().set_tx(tx_env); + let evm_env = evm.ctx().evm_clone(); + evm.ctx_mut().set_evm(evm_env); + } + + #[test] + fn from_any_rpc_transaction_for_op() { + let from = Address::random(); + let signed_tx = make_signed_eip1559(); + + // Build the eth TxEnv to compare against op base + let rpc_tx = RpcTransaction::from_transaction( + Recovered::new_unchecked(signed_tx.into(), from), + TransactionInfo::default(), + ); + let any_tx = >::from(rpc_tx); + let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base, expected_base); + } + + #[test] + fn from_any_rpc_transaction_for_op_deposit() { + let from = Address::random(); + let source_hash = B256::random(); + let deposit = TxDeposit { + source_hash, + from, + to: TxKind::Call(Address::with_last_byte(0xCC)), + mint: 1111, + value: U256::from(200), + gas_limit: 21000, + is_system_transaction: true, + input: Default::default(), + }; + + // Build a concrete OpRpcTransaction, serialize to JSON, deserialize as + // AnyRpcTransaction. + let op_rpc_tx = OpRpcTransaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from), + OpTransactionInfo::default(), + ); + let json = serde_json::to_value(&op_rpc_tx).unwrap(); + let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap(); + + let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap(); + assert_eq!(op_tx_env.base.caller, from); + assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC))); + assert_eq!(op_tx_env.base.value, U256::from(200)); + assert_eq!(op_tx_env.base.gas_limit, 21000); + assert_eq!(op_tx_env.deposit.source_hash, source_hash); + assert_eq!(op_tx_env.deposit.mint, Some(1111)); + assert!(op_tx_env.deposit.is_system_transaction); + } + } } diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs deleted file mode 100644 index ab216fe4117d2..0000000000000 --- a/crates/evm/core/src/evm.rs +++ /dev/null @@ -1,387 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use crate::{ - backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, Env, InspectorExt, -}; -use alloy_consensus::constants::KECCAK_EMPTY; -use alloy_evm::{ - eth::EthEvmContext, - precompiles::{DynPrecompile, PrecompilesMap}, - Evm, EvmEnv, -}; -use alloy_primitives::{Address, Bytes, U256}; -use foundry_fork_db::DatabaseError; -use revm::{ - context::{ - result::{EVMError, HaltReason, ResultAndState}, - BlockEnv, CfgEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContext, TxEnv, - }, - handler::{ - instructions::EthInstructions, EthFrame, EthPrecompiles, FrameInitOrResult, FrameResult, - Handler, ItemOrResult, MainnetHandler, - }, - inspector::InspectorHandler, - interpreter::{ - interpreter::EthInterpreter, return_ok, CallInput, CallInputs, CallOutcome, CallScheme, - CallValue, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, - InterpreterResult, - }, - precompile::{secp256r1::P256VERIFY, PrecompileSpecId, Precompiles}, - primitives::hardfork::SpecId, - Context, ExecuteEvm, Journal, -}; - -pub fn new_evm_with_inspector<'i, 'db, I: InspectorExt + ?Sized>( - db: &'db mut dyn DatabaseExt, - env: Env, - inspector: &'i mut I, -) -> FoundryEvm<'db, &'i mut I> { - let ctx = EthEvmContext { - journaled_state: { - let mut journal = Journal::new(db); - journal.set_spec_id(env.evm_env.cfg_env.spec); - journal - }, - block: env.evm_env.block_env, - cfg: env.evm_env.cfg_env, - tx: env.tx, - chain: (), - local: LocalContext::default(), - error: Ok(()), - }; - let spec = ctx.cfg.spec; - - let mut evm = FoundryEvm { - inner: RevmEvm::new_with_inspector( - ctx, - inspector, - EthInstructions::default(), - get_precompiles(spec), - ), - }; - - inject_precompiles(&mut evm); - - evm -} - -pub fn new_evm_with_existing_context<'a>( - ctx: EthEvmContext<&'a mut dyn DatabaseExt>, - inspector: &'a mut dyn InspectorExt, -) -> FoundryEvm<'a, &'a mut dyn InspectorExt> { - let spec = ctx.cfg.spec; - - let mut evm = FoundryEvm { - inner: RevmEvm::new_with_inspector( - ctx, - inspector, - EthInstructions::default(), - get_precompiles(spec), - ), - }; - - inject_precompiles(&mut evm); - - evm -} - -/// Conditionally inject additional precompiles into the EVM context. -fn inject_precompiles(evm: &mut FoundryEvm<'_, impl InspectorExt>) { - if evm.inspector().is_odyssey() { - evm.precompiles_mut().apply_precompile(P256VERIFY.address(), |_| { - Some(DynPrecompile::from(P256VERIFY.precompile())) - }); - } -} - -/// Get the precompiles for the given spec. -fn get_precompiles(spec: SpecId) -> PrecompilesMap { - PrecompilesMap::from_static( - EthPrecompiles { - precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), - spec, - } - .precompiles, - ) -} - -/// Get the call inputs for the CREATE2 factory. -fn get_create2_factory_call_inputs( - salt: U256, - inputs: &CreateInputs, - deployer: Address, -) -> CallInputs { - let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); - CallInputs { - caller: inputs.caller, - bytecode_address: deployer, - target_address: deployer, - scheme: CallScheme::Call, - value: CallValue::Transfer(inputs.value), - input: CallInput::Bytes(calldata.into()), - gas_limit: inputs.gas_limit, - is_static: false, - return_memory_offset: 0..0, - is_eof: false, - } -} - -pub struct FoundryEvm<'db, I: InspectorExt> { - #[allow(clippy::type_complexity)] - pub inner: RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - >, -} - -impl FoundryEvm<'_, I> { - pub fn run_execution( - &mut self, - frame: FrameInput, - ) -> Result> { - let mut handler = FoundryHandler::<_>::default(); - - // Create first frame action - let frame = handler.inspect_first_frame_init(&mut self.inner, frame)?; - let frame_result = match frame { - ItemOrResult::Item(frame) => handler.inspect_run_exec_loop(&mut self.inner, frame)?, - ItemOrResult::Result(result) => result, - }; - - Ok(frame_result) - } -} - -impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { - type Precompiles = PrecompilesMap; - type Inspector = I; - type DB = &'db mut dyn DatabaseExt; - type Error = EVMError; - type HaltReason = HaltReason; - type Spec = SpecId; - type Tx = TxEnv; - - fn chain_id(&self) -> u64 { - self.inner.ctx.cfg.chain_id - } - - fn block(&self) -> &BlockEnv { - &self.inner.block - } - - fn db_mut(&mut self) -> &mut Self::DB { - self.inner.db() - } - - fn precompiles(&self) -> &Self::Precompiles { - &self.inner.precompiles - } - - fn precompiles_mut(&mut self) -> &mut Self::Precompiles { - &mut self.inner.precompiles - } - - fn inspector(&self) -> &Self::Inspector { - &self.inner.inspector - } - - fn inspector_mut(&mut self) -> &mut Self::Inspector { - &mut self.inner.inspector - } - - fn set_inspector_enabled(&mut self, _enabled: bool) { - unimplemented!("FoundryEvm is always inspecting") - } - - fn transact_raw( - &mut self, - tx: Self::Tx, - ) -> Result, Self::Error> { - let mut handler = FoundryHandler::<_>::default(); - self.inner.set_tx(tx); - handler.inspect_run(&mut self.inner) - } - - fn transact_system_call( - &mut self, - _caller: Address, - _contract: Address, - _data: Bytes, - ) -> Result, Self::Error> { - unimplemented!() - } - - fn finish(self) -> (Self::DB, EvmEnv) - where - Self: Sized, - { - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; - - (journaled_state.database, EvmEnv { block_env, cfg_env }) - } -} - -impl<'db, I: InspectorExt> Deref for FoundryEvm<'db, I> { - type Target = Context; - - fn deref(&self) -> &Self::Target { - &self.inner.ctx - } -} - -impl DerefMut for FoundryEvm<'_, I> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.ctx - } -} - -pub struct FoundryHandler<'db, I: InspectorExt> { - #[allow(clippy::type_complexity)] - inner: MainnetHandler< - RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - >, - EVMError, - EthFrame< - RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - >, - EVMError, - EthInterpreter, - >, - >, - create2_overrides: Vec<(usize, CallInputs)>, -} - -impl Default for FoundryHandler<'_, I> { - fn default() -> Self { - Self { inner: MainnetHandler::default(), create2_overrides: Vec::new() } - } -} - -impl<'db, I: InspectorExt> Handler for FoundryHandler<'db, I> { - type Evm = RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - >; - type Error = EVMError; - type Frame = EthFrame< - RevmEvm< - EthEvmContext<&'db mut dyn DatabaseExt>, - I, - EthInstructions>, - PrecompilesMap, - >, - EVMError, - EthInterpreter, - >; - type HaltReason = HaltReason; - - fn frame_return_result( - &mut self, - frame: &mut Self::Frame, - evm: &mut Self::Evm, - result: ::FrameResult, - ) -> Result<(), Self::Error> { - let result = if self - .create2_overrides - .last() - .is_some_and(|(depth, _)| *depth == evm.journal().depth) - { - let (_, call_inputs) = self.create2_overrides.pop().unwrap(); - let FrameResult::Call(mut result) = result else { - unreachable!("create2 override should be a call frame"); - }; - - // Decode address from output. - let address = match result.instruction_result() { - return_ok!() => Address::try_from(result.output().as_ref()) - .map_err(|_| { - result.result = InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 factory output".into(), - gas: Gas::new(call_inputs.gas_limit), - }; - }) - .ok(), - _ => None, - }; - - FrameResult::Create(CreateOutcome { result: result.result, address }) - } else { - result - }; - - self.inner.frame_return_result(frame, evm, result) - } -} - -impl InspectorHandler for FoundryHandler<'_, I> { - type IT = EthInterpreter; - - fn inspect_frame_call( - &mut self, - frame: &mut Self::Frame, - evm: &mut Self::Evm, - ) -> Result, Self::Error> { - let frame_or_result = self.inner.inspect_frame_call(frame, evm)?; - - let ItemOrResult::Item(FrameInput::Create(inputs)) = &frame_or_result else { - return Ok(frame_or_result) - }; - - let CreateScheme::Create2 { salt } = inputs.scheme else { return Ok(frame_or_result) }; - - if !evm.inspector.should_use_create2_factory(&mut evm.ctx, inputs) { - return Ok(frame_or_result) - } - - let gas_limit = inputs.gas_limit; - - // Get CREATE2 deployer. - let create2_deployer = evm.inspector.create2_deployer(); - - // Generate call inputs for CREATE2 factory. - let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); - - // Push data about current override to the stack. - self.create2_overrides.push((evm.journal().depth(), call_inputs.clone())); - - // Sanity check that CREATE2 deployer exists. - let code_hash = evm.journal().load_account(create2_deployer)?.info.code_hash; - if code_hash == KECCAK_EMPTY { - return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::copy_from_slice( - format!("missing CREATE2 deployer: {create2_deployer}").as_bytes(), - ), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - }))) - } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { - return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 deployer bytecode".into(), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - }))) - } - - // Return the created CALL frame instead - Ok(ItemOrResult::Item(FrameInput::Call(Box::new(call_inputs)))) - } -} diff --git a/crates/evm/core/src/evm/eth.rs b/crates/evm/core/src/evm/eth.rs new file mode 100644 index 0000000000000..71313e9e34df8 --- /dev/null +++ b/crates/evm/core/src/evm/eth.rs @@ -0,0 +1,103 @@ +use alloy_evm::{ + EthEvm, EthEvmFactory, Evm, EvmEnv, EvmFactory, eth::EthEvmContext, precompiles::PrecompilesMap, +}; +use foundry_fork_db::DatabaseError; +use revm::{ + context::{ + BlockEnv, ContextTr, Evm as RevmEvm, LocalContextTr, TxEnv, + result::{EVMError, ResultAndState}, + }, + handler::{ + EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, instructions::EthInstructions, + }, + inspector::InspectorHandler, + interpreter::{ + FrameInput, SharedMemory, interpreter::EthInterpreter, interpreter_action::FrameInit, + }, + primitives::hardfork::SpecId, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + evm::{FoundryEvmFactory, NestedEvm}, +}; + +type EthEvmHandler<'db, I> = MainnetHandler, EVMError, EthFrame>; + +pub type EthRevmEvm<'db, I> = RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>>, + PrecompilesMap, + EthFrame, +>; + +impl FoundryEvmFactory for EthEvmFactory { + type FoundryContext<'db> = EthEvmContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = + EthEvm<&'db mut dyn DatabaseExt, I, Self::Precompiles>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let mut eth_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + eth_evm.cfg.tx_chain_id_check = true; + eth_evm.inspector().get_networks().inject_precompiles(eth_evm.precompiles_mut()); + eth_evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db> { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner()) + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for EthRevmEvm<'db, I> +{ + type Spec = SpecId; + type Block = BlockEnv; + type Tx = TxEnv; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx_mut().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = EthEvmHandler::::default(); + + // Create first frame + let memory = + SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + // Run execution loop + let mut frame_result = handler.inspect_run_exec_loop(self, first_frame_input)?; + + // Handle last frame result + handler.last_frame_result(self, &mut frame_result)?; + + Ok(frame_result) + } + + fn transact_raw(&mut self, tx: Self::Tx) -> Result> { + self.set_tx(tx); + + let result = EthEvmHandler::::default().inspect_run(self)?; + + Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + self.ctx_ref().evm_clone() + } +} diff --git a/crates/evm/core/src/evm/mod.rs b/crates/evm/core/src/evm/mod.rs new file mode 100644 index 0000000000000..fc9e9e7d2810f --- /dev/null +++ b/crates/evm/core/src/evm/mod.rs @@ -0,0 +1,251 @@ +use std::{fmt::Debug, ops::Deref}; + +use crate::{ + FoundryBlock, FoundryContextExt, FoundryInspectorExt, FoundryTransaction, + FromAnyRpcTransaction, + backend::{DatabaseExt, JournaledState}, +}; +use alloy_consensus::{SignableTransaction, Signed, transaction::SignerRecoverable}; +use alloy_evm::{ + EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap, +}; +use alloy_network::{Ethereum, Network}; +use alloy_primitives::{Address, Signature, U256}; +use alloy_rlp::Decodable; +use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; +use foundry_config::FromEvmVersion; +use foundry_fork_db::{DatabaseError, ForkBlockEnv}; +use revm::{ + Database, + context::{ + JournalTr, + result::{EVMError, HaltReason, ResultAndState}, + }, + handler::FrameResult, + interpreter::{ + CallInput, CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InstructionResult, + }, + primitives::hardfork::SpecId, +}; +use serde::{Deserialize, Serialize}; +use tempo_alloy::TempoNetwork; +use tempo_evm::evm::TempoEvmFactory; +use tempo_revm::TempoHaltReason; + +pub mod eth; +#[cfg(feature = "optimism")] +pub mod op; +pub mod tempo; + +pub use eth::*; +#[cfg(feature = "optimism")] +pub use op::*; +pub use tempo::*; + +/// Foundry's supertrait associating [Network] with [FoundryEvmFactory] +pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static { + type Network: Network< + TxEnvelope: Decodable + + SignerRecoverable + + From::UnsignedTx>> + + for<'d> Deserialize<'d> + + Serialize + + UIfmt, + UnsignedTx: SignableTransaction, + TransactionRequest: FoundryTransactionBuilder + + for<'d> Deserialize<'d> + + Serialize, + ReceiptResponse: FoundryReceiptResponse, + >; + type EvmFactory: FoundryEvmFactory::TxEnvelope>>; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct EthEvmNetwork; +impl FoundryEvmNetwork for EthEvmNetwork { + type Network = Ethereum; + type EvmFactory = EthEvmFactory; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct TempoEvmNetwork; +impl FoundryEvmNetwork for TempoEvmNetwork { + type Network = TempoNetwork; + type EvmFactory = TempoEvmFactory; +} + +/// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`]. +pub type EvmFactoryFor = ::EvmFactory; +pub type FoundryContextFor<'db, FEN> = + as FoundryEvmFactory>::FoundryContext<'db>; +pub type TxEnvFor = as EvmFactory>::Tx; +pub type HaltReasonFor = as EvmFactory>::HaltReason; +pub type SpecFor = as EvmFactory>::Spec; +pub type BlockEnvFor = as EvmFactory>::BlockEnv; +pub type PrecompilesFor = as EvmFactory>::Precompiles; +pub type EvmEnvFor = EvmEnv, BlockEnvFor>; + +pub type NetworkFor = ::Network; +pub type TxEnvelopeFor = as Network>::TxEnvelope; +pub type TransactionRequestFor = as Network>::TransactionRequest; +pub type TransactionResponseFor = as Network>::TransactionResponse; +pub type BlockResponseFor = as Network>::BlockResponse; + +pub trait FoundryEvmFactory: + EvmFactory< + Spec: Into + FromEvmVersion + Default + Copy + Unpin + Send + 'static, + BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin, + Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync, + HaltReason: IntoInstructionResult, + Precompiles = PrecompilesMap, + > + Clone + + Debug + + Default + + 'static +{ + /// Foundry Context abstraction + type FoundryContext<'db>: FoundryContextExt< + Block = Self::BlockEnv, + Tx = Self::Tx, + Spec = Self::Spec, + Db: DatabaseExt, + > + where + Self: 'db; + + /// The Foundry-wrapped EVM type produced by this factory. + type FoundryEvm<'db, I: FoundryInspectorExt>>: Evm< + DB = &'db mut dyn DatabaseExt, + Tx = Self::Tx, + BlockEnv = Self::BlockEnv, + Spec = Self::Spec, + HaltReason = Self::HaltReason, + > + Deref> + where + Self: 'db; + + /// Creates a Foundry-wrapped EVM with the given inspector. + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I>; + + /// Creates a Foundry-wrapped EVM with a dynamic inspector, returning a boxed [`NestedEvm`]. + /// + /// This helper exists because `&mut dyn FoundryInspectorExt` cannot satisfy + /// the generic `I: FoundryInspectorExt>` bound when the context + /// type is only known through an associated type. Each concrete factory implements this + /// directly, side-stepping the higher-kinded lifetime issue. + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db>; +} + +/// Object-safe trait exposing the operations that cheatcode nested EVM closures need. +/// +/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.) +/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type. +pub trait NestedEvm { + /// The spec type. + type Spec; + /// The block environment type. + type Block; + /// The transaction environment type. + type Tx; + + /// Returns a mutable reference to the journal inner state (`JournaledState`). + fn journal_inner_mut(&mut self) -> &mut JournaledState; + + /// Runs a single execution frame (create or call) through the EVM handler loop. + fn run_execution(&mut self, frame: FrameInput) -> Result>; + + /// Executes a full transaction with the given tx env. + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError>; + + fn to_evm_env(&self) -> EvmEnv; +} + +/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations. +pub type NestedEvmClosure<'a, Spec, Block, Tx> = + &'a mut dyn FnMut( + &mut dyn NestedEvm, + ) -> Result<(), EVMError>; + +/// Clones the current context (env + journal), passes the database, cloned env, +/// and cloned journal inner to the callback. The callback builds whatever EVM it +/// needs, runs its operations, and returns `(result, modified_env, modified_journal)`. +/// Modified state is written back after the callback returns. +pub fn with_cloned_context( + ecx: &mut CTX, + f: impl FnOnce( + &mut CTX::Db, + EvmEnv, + JournaledState, + ) + -> Result<(EvmEnv, JournaledState), EVMError>, +) -> Result<(), EVMError> { + let evm_env = ecx.evm_clone(); + + let (db, journal_inner) = ecx.db_journal_inner_mut(); + let journal_inner_clone = journal_inner.clone(); + + let (sub_evm_env, sub_inner) = f(db, evm_env, journal_inner_clone)?; + + // Write back modified state. The db borrow was released when f returned. + ecx.set_journal_inner(sub_inner); + ecx.set_evm(sub_evm_env); + + Ok(()) +} + +/// Get the call inputs for the CREATE2 factory. +pub fn get_create2_factory_call_inputs( + salt: U256, + inputs: &CreateInputs, + deployer: Address, + journal: &mut T, +) -> Result::Error> { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat(); + let account = journal.load_account_with_code(deployer)?; + Ok(CallInputs { + caller: inputs.caller(), + bytecode_address: deployer, + known_bytecode: (account.info.code_hash, account.info.code.clone().unwrap_or_default()), + target_address: deployer, + scheme: CallScheme::Call, + value: CallValue::Transfer(inputs.value()), + input: CallInput::Bytes(calldata.into()), + gas_limit: inputs.gas_limit(), + reservoir: inputs.reservoir(), + is_static: false, + return_memory_offset: 0..0, + }) +} + +/// Converts a network-specific halt reason into an [`InstructionResult`]. +pub trait IntoInstructionResult { + fn into_instruction_result(self) -> InstructionResult; +} + +impl IntoInstructionResult for HaltReason { + fn into_instruction_result(self) -> InstructionResult { + self.into() + } +} + +impl IntoInstructionResult for TempoHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Ethereum(eth) => eth.into(), + _ => InstructionResult::PrecompileError, + } + } +} diff --git a/crates/evm/core/src/evm/op.rs b/crates/evm/core/src/evm/op.rs new file mode 100644 index 0000000000000..efb74ad3abf50 --- /dev/null +++ b/crates/evm/core/src/evm/op.rs @@ -0,0 +1,136 @@ +use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap}; +use alloy_op_evm::{OpEvm, OpEvmContext, OpEvmFactory, OpTx}; +use foundry_fork_db::DatabaseError; +use op_alloy_network::Optimism; +use op_revm::{OpEvm as RevmEvm, OpHaltReason, OpSpecId, OpTransactionError, handler::OpHandler}; +use revm::{ + context::{ + BlockEnv, ContextTr, LocalContextTr, + result::{EVMError, HaltReason, ResultAndState}, + }, + handler::{EthFrame, EvmTr, FrameResult, Handler, instructions::EthInstructions}, + inspector::InspectorHandler, + interpreter::{ + FrameInput, InstructionResult, SharedMemory, interpreter::EthInterpreter, + interpreter_action::FrameInit, + }, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + evm::{FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm}, +}; + +#[derive(Clone, Copy, Debug, Default)] +pub struct OpEvmNetwork; +impl FoundryEvmNetwork for OpEvmNetwork { + type Network = Optimism; + type EvmFactory = OpEvmFactory; +} + +impl IntoInstructionResult for OpHaltReason { + fn into_instruction_result(self) -> InstructionResult { + match self { + Self::Base(eth) => eth.into(), + Self::FailedDeposit => InstructionResult::Stop, + } + } +} + +type OpEvmHandler<'db, I> = + OpHandler, EVMError, EthFrame>; + +pub type OpRevmEvm<'db, I> = RevmEvm< + OpEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>>, + PrecompilesMap, +>; + +impl FoundryEvmFactory for OpEvmFactory { + type FoundryContext<'db> = OpEvmContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = + OpEvm<&'db mut dyn DatabaseExt, I, Self::Precompiles>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let mut op_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + op_evm.cfg.tx_chain_id_check = true; + op_evm.inspector().get_networks().inject_precompiles(op_evm.precompiles_mut()); + op_evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db> { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner()) + } +} + +/// Maps an OP [`EVMError`] to the common `EVMError` used by [`NestedEvm`]. +fn map_op_error(e: EVMError) -> EVMError { + match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::Transaction(t) => EVMError::Custom(format!("op transaction error: {t}")), + EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error), + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for OpRevmEvm<'db, I> +{ + type Spec = OpSpecId; + type Block = BlockEnv; + type Tx = OpTx; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = OpEvmHandler::::new(); + + let memory = + SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + let mut frame_result = + handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_op_error)?; + + handler.last_frame_result(self, &mut frame_result).map_err(map_op_error)?; + + Ok(frame_result) + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, EVMError> { + self.ctx().set_tx(tx); + + let mut handler = OpEvmHandler::::new(); + let result = handler.inspect_run(self).map_err(map_op_error)?; + + let result = result.map_haltreason(|h| match h { + OpHaltReason::Base(eth) => eth, + _ => HaltReason::PrecompileError, + }); + + Ok(ResultAndState::new(result, self.ctx_ref().journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + self.ctx_ref().evm_clone() + } +} diff --git a/crates/evm/core/src/evm/tempo.rs b/crates/evm/core/src/evm/tempo.rs new file mode 100644 index 0000000000000..98c8cef9fac07 --- /dev/null +++ b/crates/evm/core/src/evm/tempo.rs @@ -0,0 +1,167 @@ +use alloy_evm::{Evm, EvmEnv, EvmFactory}; +use alloy_primitives::Bytes; +use foundry_evm_hardforks::TempoHardfork; +use foundry_fork_db::DatabaseError; +use revm::{ + context::{ + ContextTr, LocalContextTr, + result::{EVMError, HaltReason, ResultAndState}, + }, + handler::{EvmTr, FrameResult, Handler}, + inspector::InspectorHandler, + interpreter::{FrameInput, SharedMemory, interpreter_action::FrameInit}, + state::Bytecode, +}; +use tempo_evm::{TempoBlockEnv, TempoEvmFactory, TempoHaltReason, evm::TempoEvm}; +use tempo_precompiles::storage::StorageCtx; +use tempo_revm::{ + TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, gas_params::tempo_gas_params, + handler::TempoEvmHandler, +}; + +use crate::{ + FoundryContextExt, FoundryInspectorExt, + backend::{DatabaseExt, JournaledState}, + constants::{CALLER, TEST_CONTRACT_ADDRESS}, + evm::{FoundryEvmFactory, NestedEvm}, + tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_genesis_inner}, +}; + +// Will be removed when the next revm release includes bluealloy/revm#3518. +pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt, I>; + +/// Initialize Tempo precompiles and contracts for a newly created EVM. +/// +/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode, +/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`]. +/// +/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode +/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles +/// on Tempo nodes (no real EVM bytecode on-chain). +pub(crate) fn initialize_tempo_evm< + 'db, + I: FoundryInspectorExt>>, +>( + evm: &mut TempoEvm<&'db mut dyn DatabaseExt, I>, + is_forked: bool, +) { + let ctx = evm.ctx_mut(); + StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || { + if is_forked { + // In fork mode, warm up precompile accounts to avoid repeated RPC fetches. + let mut sctx = StorageCtx; + let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef])); + for addr in TEMPO_PRECOMPILE_ADDRESSES.iter().chain(TEMPO_TIP20_TOKENS.iter()) { + sctx.set_code(*addr, sentinel.clone()) + .expect("failed to warm tempo precompile address"); + } + } else { + // In non-fork mode, run full genesis initialization. + initialize_tempo_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER) + .expect("tempo genesis initialization failed"); + } + }); +} + +impl FoundryEvmFactory for TempoEvmFactory { + type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt>; + + type FoundryEvm<'db, I: FoundryInspectorExt>> = + TempoEvm<&'db mut dyn DatabaseExt, I>; + + fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt>>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: I, + ) -> Self::FoundryEvm<'db, I> { + let is_forked = db.is_forked_mode(); + let spec = *evm_env.spec_id(); + let mut tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector); + tempo_evm.cfg.gas_params = tempo_gas_params(spec); + tempo_evm.cfg.tx_chain_id_check = true; + if tempo_evm.cfg.tx_gas_limit_cap.is_none() { + tempo_evm.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap(); + } + + let networks = tempo_evm.inspector().get_networks(); + networks.inject_precompiles(tempo_evm.precompiles_mut()); + + initialize_tempo_evm(&mut tempo_evm, is_forked); + tempo_evm + } + + fn create_foundry_nested_evm<'db>( + &self, + db: &'db mut dyn DatabaseExt, + evm_env: EvmEnv, + inspector: &'db mut dyn FoundryInspectorExt>, + ) -> Box + 'db> + { + Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner()) + } +} + +/// Maps a Tempo [`EVMError`] to the common `EVMError` used by [`NestedEvm`]. +/// +/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains +/// an associated `Error` type, this mapping can be removed. +pub(crate) fn map_tempo_error( + e: EVMError, +) -> EVMError { + match e { + EVMError::Database(db) => EVMError::Database(db), + EVMError::Header(h) => EVMError::Header(h), + EVMError::Custom(s) => EVMError::Custom(s), + EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error), + EVMError::Transaction(t) => match t { + TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth), + t => EVMError::Custom(format!("tempo transaction error: {t}")), + }, + } +} + +impl<'db, I: FoundryInspectorExt>>> NestedEvm + for TempoRevmEvm<'db, I> +{ + type Spec = TempoHardfork; + type Block = TempoBlockEnv; + type Tx = TempoTxEnv; + + fn journal_inner_mut(&mut self) -> &mut JournaledState { + &mut self.ctx_mut().journaled_state.inner + } + + fn run_execution(&mut self, frame: FrameInput) -> Result> { + let mut handler = TempoEvmHandler::new(); + + let memory = + SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone()); + let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame }; + + let mut frame_result = + handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?; + + handler.last_frame_result(self, &mut frame_result).map_err(map_tempo_error)?; + + Ok(frame_result) + } + + fn transact_raw(&mut self, tx: Self::Tx) -> Result> { + self.set_tx(tx); + + let mut handler = TempoEvmHandler::new(); + let result = handler.inspect_run(self).map_err(map_tempo_error)?; + + let result = result.map_haltreason(|h| match h { + TempoHaltReason::Ethereum(eth) => eth, + _ => HaltReason::PrecompileError, + }); + + Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone())) + } + + fn to_evm_env(&self) -> EvmEnv { + self.ctx_ref().evm_clone() + } +} diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 30549f028aace..2284823047ca6 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -4,15 +4,18 @@ use crate::{ backend::{RevertStateSnapshotAction, StateSnapshot}, state_snapshot::StateSnapshots, }; -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_network::Network; +use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; -use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; +use foundry_fork_db::{BlockchainDb, DatabaseError, ForkBlockEnv, SharedBackend}; use parking_lot::Mutex; use revm::{ + Database, DatabaseCommit, bytecode::Bytecode, + context::BlockEnv, database::{CacheDB, DatabaseRef}, + primitives::AddressMap, state::{Account, AccountInfo}, - Database, DatabaseCommit, }; use std::sync::Arc; @@ -23,28 +26,28 @@ use std::sync::Arc; /// This database uses the `backend` for read and the `db` for write operations. But note the /// `backend` will also write (missing) data to the `db` in the background #[derive(Clone, Debug)] -pub struct ForkedDatabase { +pub struct ForkedDatabase { /// Responsible for fetching missing data. /// /// This is responsible for getting data. - backend: SharedBackend, + backend: SharedBackend, /// Cached Database layer, ensures that changes are not written to the database that /// exclusively stores the state of the remote client. /// /// This separates Read/Write operations /// - reads from the `SharedBackend as DatabaseRef` writes to the internal cache storage. - cache_db: CacheDB, + cache_db: CacheDB>, /// Contains all the data already fetched. /// /// This exclusively stores the _unchanged_ remote client state. - db: BlockchainDb, + db: BlockchainDb, /// Holds the state snapshots of a blockchain. - state_snapshots: Arc>>, + state_snapshots: Arc>>>, } -impl ForkedDatabase { +impl ForkedDatabase { /// Creates a new instance of this DB - pub fn new(backend: SharedBackend, db: BlockchainDb) -> Self { + pub fn new(backend: SharedBackend, db: BlockchainDb) -> Self { Self { cache_db: CacheDB::new(backend.clone()), backend, @@ -53,22 +56,22 @@ impl ForkedDatabase { } } - pub fn database(&self) -> &CacheDB { + pub const fn database(&self) -> &CacheDB> { &self.cache_db } - pub fn database_mut(&mut self) -> &mut CacheDB { + pub const fn database_mut(&mut self) -> &mut CacheDB> { &mut self.cache_db } - pub fn state_snapshots(&self) -> &Arc>> { + pub const fn state_snapshots(&self) -> &Arc>>> { &self.state_snapshots } /// Reset the fork to a fresh forked state, and optionally update the fork config pub fn reset( &mut self, - _url: Option, + _urls: Vec, block_number: impl Into, ) -> Result<(), String> { self.backend.set_pinned_block(block_number).map_err(|err| err.to_string())?; @@ -89,11 +92,11 @@ impl ForkedDatabase { } /// Returns the database that holds the remote state - pub fn inner(&self) -> &BlockchainDb { + pub const fn inner(&self) -> &BlockchainDb { &self.db } - pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot { + pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot { let db = self.db.db(); let state_snapshot = StateSnapshot { accounts: db.accounts.read().clone(), @@ -150,7 +153,7 @@ impl ForkedDatabase { } } -impl Database for ForkedDatabase { +impl Database for ForkedDatabase { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -173,7 +176,7 @@ impl Database for ForkedDatabase { } } -impl DatabaseRef for ForkedDatabase { +impl DatabaseRef for ForkedDatabase { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -193,8 +196,8 @@ impl DatabaseRef for ForkedDatabase { } } -impl DatabaseCommit for ForkedDatabase { - fn commit(&mut self, changes: HashMap) { +impl DatabaseCommit for ForkedDatabase { + fn commit(&mut self, changes: AddressMap) { self.database_mut().commit(changes) } } @@ -203,26 +206,31 @@ impl DatabaseCommit for ForkedDatabase { /// /// This mimics `revm::CacheDB` #[derive(Clone, Debug)] -pub struct ForkDbStateSnapshot { - pub local: CacheDB, +pub struct ForkDbStateSnapshot { + pub local: CacheDB>, pub state_snapshot: StateSnapshot, } -impl ForkDbStateSnapshot { - fn get_storage(&self, address: Address, index: U256) -> Option { - self.local - .cache - .accounts - .get(&address) - .and_then(|account| account.storage.get(&index)) - .copied() +impl ForkDbStateSnapshot { + /// Lookup storage in `state_snapshot`, then fall back to the backend (remote RPC). + fn storage_from_snapshot_or_backend( + &self, + address: Address, + index: U256, + ) -> Result { + // Check state_snapshot.storage first (data fetched by SharedBackend / disk cache). + if let Some(val) = self.state_snapshot.storage.get(&address).and_then(|s| s.get(&index)) { + return Ok(*val); + } + // Fall back to the underlying backend (SharedBackend → remote RPC). + DatabaseRef::storage_ref(&self.local, address, index) } } // This `DatabaseRef` implementation works similar to `CacheDB` which prioritizes modified elements, // and uses another db as fallback // We prioritize stored changed accounts/storage -impl DatabaseRef for ForkDbStateSnapshot { +impl DatabaseRef for ForkDbStateSnapshot { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -247,15 +255,9 @@ impl DatabaseRef for ForkDbStateSnapshot { match self.local.cache.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), - }, - }, - None => match self.get_storage(address, index) { - None => DatabaseRef::storage_ref(&self.local, address, index), - Some(storage) => Ok(storage), + None => self.storage_from_snapshot_or_backend(address, index), }, + None => self.storage_from_snapshot_or_backend(address, index), } } @@ -272,7 +274,6 @@ mod tests { use super::*; use crate::backend::BlockchainDbMeta; use foundry_common::provider::get_http_provider; - use std::collections::BTreeSet; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always return the /// `AccountInfo` @@ -280,7 +281,8 @@ mod tests { async fn fork_db_insert_basic_default() { let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(rpc.clone()); - let meta = BlockchainDbMeta { block_env: Default::default(), hosts: BTreeSet::from([rpc]) }; + let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc); + let db = BlockchainDb::new(meta, None); let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await; @@ -300,4 +302,28 @@ mod tests { assert!(loaded.is_some()); assert_eq!(loaded.unwrap(), info); } + + /// Verifies that `ForkDbStateSnapshot::storage_ref` reads from `state_snapshot.storage` + /// when the slot is missing from `local.cache.accounts`. Without this lookup the call + /// would fall through to the backend and return the unrelated remote value. + #[tokio::test(flavor = "multi_thread")] + async fn fork_db_state_snapshot_reads_storage_from_snapshot() { + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); + let provider = get_http_provider(rpc.clone()); + let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc); + let db = BlockchainDb::new(meta, None); + let backend = SharedBackend::spawn_backend(Arc::new(provider), db, None).await; + + let address = Address::random(); + let slot = U256::from(42u64); + let expected = U256::from(0xdeadbeefu64); + + let mut state_snapshot = StateSnapshot::default(); + state_snapshot.storage.entry(address).or_default().insert(slot, expected); + + let snapshot = ForkDbStateSnapshot { local: CacheDB::new(backend), state_snapshot }; + + let got = DatabaseRef::storage_ref(&snapshot, address, slot).unwrap(); + assert_eq!(got, expected); + } } diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs deleted file mode 100644 index 8b23ae5b8531a..0000000000000 --- a/crates/evm/core/src/fork/init.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::{utils::apply_chain_and_block_specific_env_changes, AsEnvMut, Env, EvmEnv}; -use alloy_consensus::BlockHeader; -use alloy_primitives::Address; -use alloy_provider::{network::BlockResponse, Network, Provider}; -use alloy_rpc_types::BlockNumberOrTag; -use eyre::WrapErr; -use foundry_common::NON_ARCHIVE_NODE_WARNING; -use revm::context::{BlockEnv, CfgEnv, TxEnv}; - -/// Initializes a REVM block environment based on a forked -/// ethereum provider. -pub async fn environment>( - provider: &P, - memory_limit: u64, - gas_price: Option, - override_chain_id: Option, - pin_block: Option, - origin: Address, - disable_block_gas_limit: bool, -) -> eyre::Result<(Env, N::BlockResponse)> { - let block_number = if let Some(pin_block) = pin_block { - pin_block - } else { - provider.get_block_number().await.wrap_err("failed to get latest block number")? - }; - let (fork_gas_price, rpc_chain_id, block) = tokio::try_join!( - provider.get_gas_price(), - provider.get_chain_id(), - provider.get_block_by_number(BlockNumberOrTag::Number(block_number)) - )?; - let block = if let Some(block) = block { - block - } else { - if let Ok(latest_block) = provider.get_block_number().await { - // If the `eth_getBlockByNumber` call succeeds, but returns null instead of - // the block, and the block number is less than equal the latest block, then - // the user is forking from a non-archive node with an older block number. - if block_number <= latest_block { - error!("{NON_ARCHIVE_NODE_WARNING}"); - } - eyre::bail!( - "failed to get block for block number: {block_number}; \ - latest block number: {latest_block}" - ); - } - eyre::bail!("failed to get block for block number: {block_number}") - }; - - let cfg = configure_env( - override_chain_id.unwrap_or(rpc_chain_id), - memory_limit, - disable_block_gas_limit, - ); - - let mut env = Env { - evm_env: EvmEnv { - cfg_env: cfg, - block_env: BlockEnv { - number: block.header().number(), - timestamp: block.header().timestamp(), - beneficiary: block.header().beneficiary(), - difficulty: block.header().difficulty(), - prevrandao: block.header().mix_hash(), - basefee: block.header().base_fee_per_gas().unwrap_or_default(), - gas_limit: block.header().gas_limit(), - ..Default::default() - }, - }, - tx: TxEnv { - caller: origin, - gas_price: gas_price.unwrap_or(fork_gas_price), - chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), - gas_limit: block.header().gas_limit() as u64, - ..Default::default() - }, - }; - - apply_chain_and_block_specific_env_changes::(env.as_env_mut(), &block); - - Ok((env, block)) -} - -/// Configures the environment for the given chain id and memory limit. -pub fn configure_env(chain_id: u64, memory_limit: u64, disable_block_gas_limit: bool) -> CfgEnv { - let mut cfg = CfgEnv::default(); - cfg.chain_id = chain_id; - cfg.memory_limit = memory_limit; - cfg.limit_contract_code_size = Some(usize::MAX); - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller - // is a contract. So we disable the check by default. - cfg.disable_eip3607 = true; - cfg.disable_block_gas_limit = disable_block_gas_limit; - cfg.disable_nonce_check = true; - cfg -} diff --git a/crates/evm/core/src/fork/mod.rs b/crates/evm/core/src/fork/mod.rs index 3049f9f957a63..2ff7706df6e1f 100644 --- a/crates/evm/core/src/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -1,8 +1,4 @@ use super::opts::EvmOpts; -use crate::Env; - -mod init; -pub use init::{configure_env, environment}; pub mod database; @@ -16,8 +12,6 @@ pub struct CreateFork { pub enable_caching: bool, /// The URL to a node for fetching remote state pub url: String, - /// The env to create this fork, main purpose is to provide some metadata for the fork - pub env: Env, /// All env settings as configured by the user pub evm_opts: EvmOpts, } diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 59a05d67be2f2..dd8b3afd32bf1 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -4,26 +4,28 @@ //! concurrently active pairs at once. use super::CreateFork; -use crate::Env; -use alloy_consensus::BlockHeader; -use alloy_primitives::map::HashMap; -use alloy_provider::network::BlockResponse; -use foundry_common::provider::{ProviderBuilder, RetryProvider}; +use crate::FoundryBlock; +use alloy_evm::EvmEnv; +use alloy_network::{AnyNetwork, Network}; +use alloy_primitives::{U256, map::HashMap}; use foundry_config::Config; -use foundry_fork_db::{cache::BlockchainDbMeta, BackendHandler, BlockchainDb, SharedBackend}; +use foundry_fork_db::{ + BackendHandler, BlockchainDb, ForkBlockEnv, SharedBackend, cache::BlockchainDbMeta, +}; use futures::{ - channel::mpsc::{channel, Receiver, Sender}, - stream::{Fuse, Stream}, + FutureExt, StreamExt, + channel::mpsc::{Receiver, Sender, channel}, + stream::Fuse, task::{Context, Poll}, - Future, FutureExt, StreamExt, }; +use revm::primitives::hardfork::SpecId; use std::{ fmt::{self, Write}, pin::Pin, sync::{ - atomic::AtomicUsize, - mpsc::{channel as oneshot_channel, Sender as OneshotSender}, Arc, + atomic::AtomicUsize, + mpsc::{Sender as OneshotSender, channel as oneshot_channel}, }, time::Duration, }; @@ -67,39 +69,52 @@ impl> From for ForkId { /// Can send requests to the `MultiForkHandler` to create forks. #[derive(Clone, Debug)] #[must_use] -pub struct MultiFork { +pub struct MultiFork { /// Channel to send `Request`s to the handler. - handler: Sender, + handler: Sender>, /// Ensures that all rpc resources get flushed properly. - _shutdown: Arc, + _shutdown: Arc>, } -impl MultiFork { +impl< + N: Network, + SPEC: Into + Default + Copy + Unpin + Send + 'static, + BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin, +> MultiFork +{ /// Creates a new pair and spawns the `MultiForkHandler` on a background thread. pub fn spawn() -> Self { trace!(target: "fork::multi", "spawning multifork"); let (fork, mut handler) = Self::new(); - // Spawn a light-weight thread with a thread-local async runtime just for - // sending and receiving data from the remote client(s). - std::thread::Builder::new() - .name("multi-fork-backend".into()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to build tokio runtime"); - - rt.block_on(async move { - // Flush cache every 60s, this ensures that long-running fork tests get their - // cache flushed from time to time. - // NOTE: we install the interval here because the `tokio::timer::Interval` - // requires a rt. - handler.set_flush_cache_interval(Duration::from_secs(60)); - handler.await - }); - }) - .expect("failed to spawn thread"); + + // Spawn a light-weight thread just for sending and receiving data from the remote + // client(s). + let fut = async move { + // Flush cache every 60s, this ensures that long-running fork tests get their + // cache flushed from time to time. + // NOTE: we install the interval here because the `tokio::timer::Interval` + // requires a rt. + handler.set_flush_cache_interval(Duration::from_secs(60)); + handler.await + }; + match tokio::runtime::Handle::try_current() { + Ok(rt) => _ = rt.spawn(fut), + Err(_) => { + trace!(target: "fork::multi", "spawning multifork backend thread"); + _ = std::thread::Builder::new() + .name("multi-fork-backend".into()) + .spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to build tokio runtime") + .block_on(fut) + }) + .expect("failed to spawn thread") + } + } + trace!(target: "fork::multi", "spawned MultiForkHandler thread"); fork } @@ -108,7 +123,7 @@ impl MultiFork { /// /// Use [`spawn`](Self::spawn) instead. #[doc(hidden)] - pub fn new() -> (Self, MultiForkHandler) { + pub fn new() -> (Self, MultiForkHandler) { let (handler, handler_rx) = channel(1); let _shutdown = Arc::new(ShutDownMultiFork { handler: Some(handler.clone()) }); (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx)) @@ -117,7 +132,11 @@ impl MultiFork { /// Returns a fork backend. /// /// If no matching fork backend exists it will be created. - pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<(ForkId, SharedBackend, Env)> { + #[allow(clippy::type_complexity)] + pub fn create_fork( + &self, + fork: CreateFork, + ) -> eyre::Result<(ForkId, SharedBackend, EvmEnv)> { trace!("Creating new fork, url={}, block={:?}", fork.url, fork.evm_opts.fork_block_number); let (sender, rx) = oneshot_channel(); let req = Request::CreateFork(Box::new(fork), sender); @@ -128,11 +147,12 @@ impl MultiFork { /// Rolls the block of the fork. /// /// If no matching fork backend exists it will be created. + #[allow(clippy::type_complexity)] pub fn roll_fork( &self, fork: ForkId, block: u64, - ) -> eyre::Result<(ForkId, SharedBackend, Env)> { + ) -> eyre::Result<(ForkId, SharedBackend, EvmEnv)> { trace!(?fork, ?block, "rolling fork"); let (sender, rx) = oneshot_channel(); let req = Request::RollFork(fork, block, sender); @@ -140,17 +160,17 @@ impl MultiFork { rx.recv()? } - /// Returns the `Env` of the given fork, if any. - pub fn get_env(&self, fork: ForkId) -> eyre::Result> { + /// Returns the `EvmEnv` of the given fork, if any. + pub fn get_evm_env(&self, fork: ForkId) -> eyre::Result>> { trace!(?fork, "getting env config"); let (sender, rx) = oneshot_channel(); - let req = Request::GetEnv(fork, sender); + let req = Request::GetEvmEnv(fork, sender); self.handler.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e))?; Ok(rx.recv()?) } /// Updates block number and timestamp of given fork with new values. - pub fn update_block(&self, fork: ForkId, number: u64, timestamp: u64) -> eyre::Result<()> { + pub fn update_block(&self, fork: ForkId, number: U256, timestamp: U256) -> eyre::Result<()> { trace!(?fork, ?number, ?timestamp, "update fork block"); self.handler .clone() @@ -158,10 +178,25 @@ impl MultiFork { .map_err(|e| eyre::eyre!("{:?}", e)) } + /// Updates the fork's entire env + /// + /// This is required for tx level forking where we need to fork off the `block - 1` state but + /// still need use env settings for `env`. + pub fn update_block_env(&self, fork: ForkId, env: BLOCK) -> eyre::Result<()> + where + BLOCK: fmt::Debug, + { + trace!(?fork, ?env, "update fork block"); + self.handler + .clone() + .try_send(Request::UpdateEnv(fork, env)) + .map_err(|e| eyre::eyre!("{:?}", e)) + } + /// Returns the corresponding fork if it exists. /// /// Returns `None` if no matching fork backend is available. - pub fn get_fork(&self, id: impl Into) -> eyre::Result> { + pub fn get_fork(&self, id: impl Into) -> eyre::Result>> { let id = id.into(); trace!(?id, "get fork backend"); let (sender, rx) = oneshot_channel(); @@ -181,63 +216,83 @@ impl MultiFork { } } -type Handler = BackendHandler>; - -type CreateFuture = - Pin> + Send>>; -type CreateSender = OneshotSender>; -type GetEnvSender = OneshotSender>; +type CreateFuture = Pin< + Box< + dyn Future< + Output = eyre::Result<( + ForkId, + CreatedFork, + BackendHandler, + )>, + > + Send, + >, +>; +type CreateSender = + OneshotSender, EvmEnv)>>; +type GetEvmEnvSender = OneshotSender>>; /// Request that's send to the handler. #[derive(Debug)] -enum Request { +enum Request { /// Creates a new ForkBackend. - CreateFork(Box, CreateSender), + CreateFork(Box, CreateSender), /// Returns the Fork backend for the `ForkId` if it exists. - GetFork(ForkId, OneshotSender>), + GetFork(ForkId, OneshotSender>>), /// Adjusts the block that's being forked, by creating a new fork at the new block. - RollFork(ForkId, u64, CreateSender), + RollFork(ForkId, u64, CreateSender), /// Returns the environment of the fork. - GetEnv(ForkId, GetEnvSender), + GetEvmEnv(ForkId, GetEvmEnvSender), /// Updates the block number and timestamp of the fork. - UpdateBlock(ForkId, u64, u64), + UpdateBlock(ForkId, U256, U256), + /// Updates the block the entire block env, + UpdateEnv(ForkId, BLOCK), /// Shutdowns the entire `MultiForkHandler`, see `ShutDownMultiFork` ShutDown(OneshotSender<()>), /// Returns the Fork Url for the `ForkId` if it exists. GetForkUrl(ForkId, OneshotSender>), } -enum ForkTask { +enum ForkTask { /// Contains the future that will establish a new fork. - Create(CreateFuture, ForkId, CreateSender, Vec), + Create( + CreateFuture, + ForkId, + CreateSender, + Vec>, + ), } /// The type that manages connections in the background. #[must_use = "futures do nothing unless polled"] -pub struct MultiForkHandler { +pub struct MultiForkHandler { /// Incoming requests from the `MultiFork`. - incoming: Fuse>, + incoming: Fuse>>, /// All active handlers. /// /// It's expected that this list will be rather small (<10). - handlers: Vec<(ForkId, Handler)>, + handlers: Vec<(ForkId, BackendHandler)>, // tasks currently in progress - pending_tasks: Vec, + pending_tasks: Vec>, /// All _unique_ forkids mapped to their corresponding backend. /// /// Note: The backend can be shared by multiple ForkIds if the target the same provider and /// block number. - forks: HashMap, + forks: HashMap>, /// Optional periodic interval to flush rpc cache. flush_cache_interval: Option, } -impl MultiForkHandler { - fn new(incoming: Receiver) -> Self { +impl< + N: Network, + SPEC: Into + Default + Copy + 'static, + BLOCK: FoundryBlock + ForkBlockEnv + Default, +> MultiForkHandler +{ + fn new(incoming: Receiver>) -> Self { Self { incoming: incoming.fuse(), handlers: Default::default(), @@ -255,19 +310,19 @@ impl MultiForkHandler { } /// Returns the list of additional senders of a matching task for the given id, if any. - #[expect(irrefutable_let_patterns)] - fn find_in_progress_task(&mut self, id: &ForkId) -> Option<&mut Vec> { - for task in &mut self.pending_tasks { - if let ForkTask::Create(_, in_progress, _, additional) = task { - if in_progress == id { - return Some(additional); - } + fn find_in_progress_task( + &mut self, + id: &ForkId, + ) -> Option<&mut Vec>> { + for ForkTask::Create(_, in_progress, _, additional) in &mut self.pending_tasks { + if in_progress == id { + return Some(additional); } } None } - fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { + fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { let fork_id = ForkId::new(&fork.url, fork.evm_opts.fork_block_number); trace!(?fork_id, "created new forkId"); @@ -285,31 +340,37 @@ impl MultiForkHandler { fn insert_new_fork( &mut self, fork_id: ForkId, - fork: CreatedFork, - sender: CreateSender, - additional_senders: Vec, + fork: CreatedFork, + sender: CreateSender, + additional_senders: Vec>, ) { self.forks.insert(fork_id.clone(), fork.clone()); - let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.opts.env.clone()))); + let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.evm_env.clone()))); // Notify all additional senders and track unique forkIds. for sender in additional_senders { let next_fork_id = fork.inc_senders(fork_id.clone()); self.forks.insert(next_fork_id.clone(), fork.clone()); - let _ = sender.send(Ok((next_fork_id, fork.backend.clone(), fork.opts.env.clone()))); + let _ = sender.send(Ok((next_fork_id, fork.backend.clone(), fork.evm_env.clone()))); } } + /// Update the fork's block entire env + fn update_env(&mut self, fork_id: ForkId, env: BLOCK) { + if let Some(fork) = self.forks.get_mut(&fork_id) { + fork.evm_env.block_env = env; + } + } /// Update fork block number and timestamp. Used to preserve values set by `roll` and `warp` /// cheatcodes when new fork selected. - fn update_block(&mut self, fork_id: ForkId, block_number: u64, block_timestamp: u64) { + fn update_block(&mut self, fork_id: ForkId, block_number: U256, block_timestamp: U256) { if let Some(fork) = self.forks.get_mut(&fork_id) { - fork.opts.env.evm_env.block_env.number = block_number; - fork.opts.env.evm_env.block_env.timestamp = block_timestamp; + fork.evm_env.block_env.set_number(block_number); + fork.evm_env.block_env.set_timestamp(block_timestamp); } } - fn on_request(&mut self, req: Request) { + fn on_request(&mut self, req: Request) { match req { Request::CreateFork(fork, sender) => self.create_fork(*fork, sender), Request::GetFork(fork_id, sender) => { @@ -323,15 +384,19 @@ impl MultiForkHandler { opts.evm_opts.fork_block_number = Some(block); self.create_fork(opts, sender) } else { - let _ = sender.send(Err(eyre::eyre!("No matching fork exits for {}", fork_id))); + let _ = + sender.send(Err(eyre::eyre!("No matching fork exists for {}", fork_id))); } } - Request::GetEnv(fork_id, sender) => { - let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.opts.env.clone())); + Request::GetEvmEnv(fork_id, sender) => { + let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.evm_env.clone())); } Request::UpdateBlock(fork_id, block_number, block_timestamp) => { self.update_block(fork_id, block_number, block_timestamp); } + Request::UpdateEnv(fork_id, block_env) => { + self.update_env(fork_id, block_env); + } Request::ShutDown(sender) => { trace!(target: "fork::multi", "received shutdown signal"); // We're emptying all fork backends, this way we ensure all caches get flushed. @@ -349,18 +414,21 @@ impl MultiForkHandler { // Drives all handler to completion. // This future will finish once all underlying BackendHandler are completed. -impl Future for MultiForkHandler { +impl< + N: Network, + SPEC: Into + Default + Copy + Unpin + 'static, + BLOCK: FoundryBlock + ForkBlockEnv + Default + Unpin, +> Future for MultiForkHandler +{ type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let pin = self.get_mut(); + let this = self.get_mut(); // Receive new requests. loop { - match Pin::new(&mut pin.incoming).poll_next(cx) { - Poll::Ready(Some(req)) => { - pin.on_request(req); - } + match this.incoming.poll_next_unpin(cx) { + Poll::Ready(Some(req)) => this.on_request(req), Poll::Ready(None) => { // Channel closed, but we still need to drive the fork handlers to completion. trace!(target: "fork::multi", "request channel closed"); @@ -371,23 +439,23 @@ impl Future for MultiForkHandler { } // Advance all tasks. - for n in (0..pin.pending_tasks.len()).rev() { - let task = pin.pending_tasks.swap_remove(n); + for n in (0..this.pending_tasks.len()).rev() { + let task = this.pending_tasks.swap_remove(n); match task { ForkTask::Create(mut fut, id, sender, additional_senders) => { if let Poll::Ready(resp) = fut.poll_unpin(cx) { match resp { Ok((fork_id, fork, handler)) => { - if let Some(fork) = pin.forks.get(&fork_id).cloned() { - pin.insert_new_fork( + if let Some(fork) = this.forks.get(&fork_id).cloned() { + this.insert_new_fork( fork.inc_senders(fork_id), fork, sender, additional_senders, ); } else { - pin.handlers.push((fork_id.clone(), handler)); - pin.insert_new_fork(fork_id, fork, sender, additional_senders); + this.handlers.push((fork_id.clone(), handler)); + this.insert_new_fork(fork_id, fork, sender, additional_senders); } } Err(err) => { @@ -398,7 +466,7 @@ impl Future for MultiForkHandler { } } } else { - pin.pending_tasks.push(ForkTask::Create( + this.pending_tasks.push(ForkTask::Create( fut, id, sender, @@ -410,38 +478,40 @@ impl Future for MultiForkHandler { } // Advance all handlers. - for n in (0..pin.handlers.len()).rev() { - let (id, mut handler) = pin.handlers.swap_remove(n); + for n in (0..this.handlers.len()).rev() { + let (id, mut handler) = this.handlers.swap_remove(n); match handler.poll_unpin(cx) { Poll::Ready(_) => { trace!(target: "fork::multi", "fork {:?} completed", id); } Poll::Pending => { - pin.handlers.push((id, handler)); + this.handlers.push((id, handler)); } } } - if pin.handlers.is_empty() && pin.incoming.is_done() { + if this.handlers.is_empty() && this.incoming.is_done() { trace!(target: "fork::multi", "completed"); return Poll::Ready(()); } // Periodically flush cached RPC state. - if pin + if this .flush_cache_interval .as_mut() .map(|interval| interval.poll_tick(cx).is_ready()) - .unwrap_or_default() && - !pin.forks.is_empty() + .unwrap_or_default() + && !this.forks.is_empty() { trace!(target: "fork::multi", "tick flushing caches"); - let forks = pin.forks.values().map(|f| f.backend.clone()).collect::>(); + let forks = this.forks.values().map(|f| f.backend.clone()).collect::>(); // Flush this on new thread to not block here. std::thread::Builder::new() .name("flusher".into()) .spawn(move || { - forks.into_iter().for_each(|fork| fork.flush_cache()); + for fork in forks { + fork.flush_cache(); + } }) .expect("failed to spawn thread"); } @@ -452,19 +522,25 @@ impl Future for MultiForkHandler { /// Tracks the created Fork #[derive(Debug, Clone)] -struct CreatedFork { +struct CreatedFork { /// How the fork was initially created. opts: CreateFork, + /// The resolved EVM environment (fetched from the provider). + evm_env: EvmEnv, /// Copy of the sender. - backend: SharedBackend, + backend: SharedBackend, /// How many consumers there are, since a `SharedBacked` can be used by multiple /// consumers. num_senders: Arc, } -impl CreatedFork { - pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { - Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } +impl CreatedFork { + pub fn new( + opts: CreateFork, + evm_env: EvmEnv, + backend: SharedBackend, + ) -> Self { + Self { opts, evm_env, backend, num_senders: Arc::new(AtomicUsize::new(1)) } } /// Increment senders and return unique identifier of the fork. @@ -485,20 +561,20 @@ impl CreatedFork { /// This type intentionally does not implement `Clone` since it's intended that there's only once /// instance. #[derive(Debug)] -struct ShutDownMultiFork { - handler: Option>, +struct ShutDownMultiFork { + handler: Option>>, } -impl Drop for ShutDownMultiFork { +impl Drop for ShutDownMultiFork { fn drop(&mut self) { trace!(target: "fork::multi", "initiating shutdown"); let (sender, rx) = oneshot_channel(); let req = Request::ShutDown(sender); - if let Some(mut handler) = self.handler.take() { - if handler.try_send(req).is_ok() { - let _ = rx.recv(); - trace!(target: "fork::cache", "multifork backend shutdown"); - } + if let Some(mut handler) = self.handler.take() + && handler.try_send(req).is_ok() + { + let _ = rx.recv(); + trace!(target: "fork::cache", "multifork backend shutdown"); } } } @@ -506,36 +582,36 @@ impl Drop for ShutDownMultiFork { /// Creates a new fork. /// /// This will establish a new `Provider` to the endpoint and return the Fork Backend. -async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { - let provider = Arc::new( - ProviderBuilder::new(fork.url.as_str()) - .maybe_max_retry(fork.evm_opts.fork_retries) - .maybe_initial_backoff(fork.evm_opts.fork_retry_backoff) - .maybe_headers(fork.evm_opts.fork_headers.clone()) - .compute_units_per_second(fork.evm_opts.get_compute_units_per_second()) - .build()?, - ); +async fn create_fork< + N: Network, + SPEC: Into + Default + Copy, + BLOCK: FoundryBlock + ForkBlockEnv + Default, +>( + mut fork: CreateFork, +) -> eyre::Result<(ForkId, CreatedFork, BackendHandler)> { + // Ensure evm_opts reflects the fork URL (may differ from the resolved CreateFork url when + // created via cheatcodes, where evm_opts is cloned from the base config). + fork.evm_opts.fork_url = Some(fork.url.clone()); // Initialise the fork environment. - let (env, block) = fork.evm_opts.fork_evm_env(&fork.url).await?; - fork.env = env; - let meta = BlockchainDbMeta::new(fork.env.evm_env.block_env.clone(), fork.url.clone()); - - // We need to use the block number from the block because the env's number can be different on - // some L2s (e.g. Arbitrum). - let number = block.header().number(); + // Here we use [`AnyNetwork`] to maximize compatibility with custom chains, aligned with + // `EvmOpts::env` impl. + let any_provider = fork.evm_opts.fork_provider_with_url::(&fork.url)?; + let (evm_env, number) = fork.evm_opts.fork_evm_env::<_, BLOCK, _, _>(&any_provider).await?; + let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), fork.url.clone()); // Determine the cache path if caching is enabled. let cache_path = if fork.enable_caching { - Config::foundry_block_cache_dir(fork.env.evm_env.cfg_env.chain_id, number) + Config::foundry_block_cache_dir(evm_env.cfg_env.chain_id, number) } else { None }; + let provider = fork.evm_opts.fork_provider_with_url::(&fork.url)?; let db = BlockchainDb::new(meta, cache_path); let (backend, handler) = SharedBackend::new(provider, db, Some(number.into())); - let fork = CreatedFork::new(fork, backend); - let fork_id = ForkId::new(&fork.opts.url, number.into()); + let fork_id = ForkId::new(&fork.url, Some(number)); + let fork = CreatedFork::new(fork, evm_env, backend); Ok((fork_id, fork, handler)) } diff --git a/crates/evm/core/src/hardfork.rs b/crates/evm/core/src/hardfork.rs new file mode 100644 index 0000000000000..858624dec5c77 --- /dev/null +++ b/crates/evm/core/src/hardfork.rs @@ -0,0 +1 @@ +pub use foundry_evm_hardforks::*; diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index 7fc64791ace06..0f0a7719fc6bb 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,6 +1,5 @@ +use crate::bytecode::InstIter; use alloy_primitives::map::rustc_hash::FxHashMap; -use eyre::Result; -use revm::bytecode::opcode::{OpCode, PUSH0, PUSH1, PUSH32}; use serde::Serialize; /// Maps from program counter to instruction counter. @@ -9,7 +8,7 @@ use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(transparent)] pub struct PcIcMap { - pub inner: FxHashMap, + inner: FxHashMap, } impl PcIcMap { @@ -38,7 +37,7 @@ impl PcIcMap { /// /// Inverse of [`PcIcMap`]. pub struct IcPcMap { - pub inner: FxHashMap, + inner: FxHashMap, } impl IcPcMap { @@ -61,123 +60,23 @@ impl IcPcMap { pub fn get(&self, ic: u32) -> Option { self.inner.get(&ic).copied() } + + /// Iterate over the IC-PC pairs. + pub fn iter(&self) -> impl Iterator { + self.inner.iter() + } } fn make_map(code: &[u8]) -> FxHashMap { assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); - let mut map = FxHashMap::with_capacity_and_hasher(code.len(), Default::default()); - - let mut pc = 0usize; - let mut cumulative_push_size = 0usize; - while pc < code.len() { - let ic = pc - cumulative_push_size; + for (ic, (pc, _)) in InstIter::new(code).with_pc().enumerate() { if PC_FIRST { map.insert(pc as u32, ic as u32); } else { map.insert(ic as u32, pc as u32); } - - if (PUSH1..=PUSH32).contains(&code[pc]) { - // Skip the push bytes. - let push_size = (code[pc] - PUSH0) as usize; - pc += push_size; - cumulative_push_size += push_size; - } - - pc += 1; } - map.shrink_to_fit(); - map } - -/// Represents a single instruction consisting of the opcode and its immediate data. -pub struct Instruction { - /// OpCode, if it could be decoded. - pub op: Option, - /// Immediate data following the opcode. - pub immediate: Box<[u8]>, - /// Program counter of the opcode. - pub pc: u32, -} - -/// Decodes raw opcode bytes into [`Instruction`]s. -pub fn decode_instructions(code: &[u8]) -> Result> { - assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); - - let mut pc = 0usize; - let mut steps = Vec::new(); - - while pc < code.len() { - let op = OpCode::new(code[pc]); - let next_pc = pc + 1; - let immediate_size = op.map(|op| op.info().immediate_size()).unwrap_or(0) as usize; - let is_normal_push = op.map(|op| op.is_push()).unwrap_or(false); - - if !is_normal_push && next_pc + immediate_size > code.len() { - eyre::bail!("incomplete sequence of bytecode"); - } - - // Ensure immediate is padded if needed. - let immediate_end = (next_pc + immediate_size).min(code.len()); - let mut immediate = vec![0u8; immediate_size]; - let immediate_part = &code[next_pc..immediate_end]; - immediate[..immediate_part.len()].copy_from_slice(immediate_part); - - steps.push(Instruction { op, pc: pc as u32, immediate: immediate.into_boxed_slice() }); - - pc = next_pc + immediate_size; - } - - Ok(steps) -} - -#[cfg(test)] -pub mod tests { - use super::*; - - #[test] - fn decode_push2_and_stop() -> Result<()> { - // 0x61 0xAA 0xBB = PUSH2 0xAABB - // 0x00 = STOP - let code = vec![0x61, 0xAA, 0xBB, 0x00]; - let insns = decode_instructions(&code)?; - - // PUSH2 then STOP - assert_eq!(insns.len(), 2); - - // PUSH2 at pc = 0 - let i0 = &insns[0]; - assert_eq!(i0.pc, 0); - assert_eq!(i0.op, Some(OpCode::PUSH2)); - assert_eq!(i0.immediate.as_ref(), &[0xAA, 0xBB]); - - // STOP at pc = 3 - let i1 = &insns[1]; - assert_eq!(i1.pc, 3); - assert_eq!(i1.op, Some(OpCode::STOP)); - assert!(i1.immediate.is_empty()); - - Ok(()) - } - - #[test] - fn decode_arithmetic_ops() -> Result<()> { - // 0x01 = ADD, 0x02 = MUL, 0x03 = SUB, 0x04 = DIV - let code = vec![0x01, 0x02, 0x03, 0x04]; - let insns = decode_instructions(&code)?; - - assert_eq!(insns.len(), 4); - - let expected = [(0, OpCode::ADD), (1, OpCode::MUL), (2, OpCode::SUB), (3, OpCode::DIV)]; - for ((pc, want_op), insn) in expected.iter().zip(insns.iter()) { - assert_eq!(insn.pc, *pc); - assert_eq!(insn.op, Some(*want_op)); - assert!(insn.immediate.is_empty()); - } - - Ok(()) - } -} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 76d6be35dd6cb..c2edbb9dfd33b 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -3,16 +3,20 @@ //! Core EVM abstractions. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "optimism")] +use op_alloy_rpc_types as _; use crate::constants::DEFAULT_CREATE2_DEPLOYER; -use alloy_evm::eth::EthEvmContext; -use alloy_primitives::Address; +use alloy_primitives::{Address, map::HashMap}; use auto_impl::auto_impl; -use backend::DatabaseExt; -use revm::{inspector::NoOpInspector, interpreter::CreateInputs, Inspector}; +use revm::{Inspector, inspector::NoOpInspector, interpreter::CreateInputs}; use revm_inspectors::access_list::AccessListInspector; +/// Map keyed by breakpoints char to their location (contract address, pc) +pub type Breakpoints = HashMap; + #[macro_use] extern crate tracing; @@ -23,33 +27,35 @@ pub mod abi { pub mod env; pub use env::*; +use foundry_evm_networks::NetworkConfigs; + pub mod backend; pub mod buffer; +pub mod bytecode; pub mod constants; pub mod decode; -pub mod either_evm; pub mod evm; pub mod fork; +pub mod hardfork; pub mod ic; -pub mod opcodes; pub mod opts; pub mod precompiles; pub mod state_snapshot; +pub mod tempo; pub mod utils; -/// An extension trait that allows us to add additional hooks to Inspector for later use in -/// handlers. +/// Foundry-specific inspector methods, decoupled from any particular EVM context type. +/// +/// This trait holds Foundry-specific extensions (create2 factory, console logging, +/// network config, deployer address). It has no `Inspector` supertrait so it can +/// be used in generic code with `I: FoundryInspectorExt + Inspector`. #[auto_impl(&mut, Box)] -pub trait InspectorExt: for<'a> Inspector> { +pub trait InspectorExt { /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. /// /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 /// factory. - fn should_use_create2_factory( - &mut self, - _context: &mut EthEvmContext<&mut dyn DatabaseExt>, - _inputs: &CreateInputs, - ) -> bool { + fn should_use_create2_factory(&mut self, _depth: usize, _inputs: &CreateInputs) -> bool { false } @@ -58,9 +64,9 @@ pub trait InspectorExt: for<'a> Inspector let _ = msg; } - /// Returns `true` if the current network is Odyssey. - fn is_odyssey(&self) -> bool { - false + /// Returns configured networks. + fn get_networks(&self) -> NetworkConfigs { + NetworkConfigs::default() } /// Returns the CREATE2 deployer address. @@ -69,6 +75,14 @@ pub trait InspectorExt: for<'a> Inspector } } +/// A combined inspector trait that integrates revm's [`Inspector`] with Foundry-specific +/// extensions. Automatically implemented for any type that implements both [`Inspector`] +/// and [`InspectorExt`]. +pub trait FoundryInspectorExt: Inspector + InspectorExt {} + +impl FoundryInspectorExt for T where T: Inspector + InspectorExt +{} + impl InspectorExt for NoOpInspector {} impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opcodes.rs b/crates/evm/core/src/opcodes.rs deleted file mode 100644 index 7fad7ca5f3a3e..0000000000000 --- a/crates/evm/core/src/opcodes.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Opcode utils - -use revm::bytecode::opcode::OpCode; - -/// Returns true if the opcode modifies memory. -/// -/// -#[inline] -pub const fn modifies_memory(opcode: OpCode) -> bool { - matches!( - opcode, - OpCode::EXTCODECOPY | - OpCode::MLOAD | - OpCode::MSTORE | - OpCode::MSTORE8 | - OpCode::MCOPY | - OpCode::CODECOPY | - OpCode::CALLDATACOPY | - OpCode::RETURNDATACOPY | - OpCode::CALL | - OpCode::CALLCODE | - OpCode::DELEGATECALL | - OpCode::STATICCALL - ) -} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 11a602aab868f..ab68eb08821e8 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,15 +1,20 @@ -use super::fork::environment; use crate::{ + EvmEnv, FoundryBlock, FoundryTransaction, constants::DEFAULT_CREATE2_DEPLOYER, - fork::{configure_env, CreateFork}, - EvmEnv, + fork::CreateFork, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; -use alloy_primitives::{Address, B256, U256}; -use alloy_provider::{network::AnyRpcBlock, Provider}; +use alloy_chains::NamedChain; +use alloy_consensus::BlockHeader; +use alloy_network::{AnyNetwork, BlockResponse, Network}; +use alloy_primitives::{Address, B256, BlockNumber, ChainId, U256}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_types::{BlockNumberOrTag, anvil::NodeInfo}; use eyre::WrapErr; -use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; +use foundry_common::{ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, provider::ProviderBuilder}; use foundry_config::{Chain, Config, GasLimit}; -use revm::context::{BlockEnv, TxEnv}; +use foundry_evm_networks::NetworkConfigs; +use revm::{context::CfgEnv, primitives::hardfork::SpecId}; use serde::{Deserialize, Serialize}; use std::fmt::Write; use url::Url; @@ -72,8 +77,12 @@ pub struct EvmOpts { /// Whether to disable block gas limit checks. pub disable_block_gas_limit: bool, - /// whether to enable Odyssey features. - pub odyssey: bool, + /// Whether to enable tx gas limit checks as imposed by Osaka (EIP-7825). + pub enable_tx_gas_limit: bool, + + #[serde(flatten)] + /// Networks with enabled features. + pub networks: NetworkConfigs, /// The CREATE2 deployer's address. pub create2_deployer: Address, @@ -99,81 +108,217 @@ impl Default for EvmOpts { memory_limit: 0, isolate: false, disable_block_gas_limit: false, - odyssey: false, + enable_tx_gas_limit: false, + networks: NetworkConfigs::default(), create2_deployer: DEFAULT_CREATE2_DEPLOYER, } } } impl EvmOpts { - /// Configures a new `revm::Env` + /// Returns a `RootProvider` for the given fork URL configured with options in `self` and + /// annotated `Network` type. + pub fn fork_provider_with_url( + &self, + fork_url: &str, + ) -> eyre::Result> { + ProviderBuilder::new(fork_url) + .maybe_max_retry(self.fork_retries) + .maybe_initial_backoff(self.fork_retry_backoff) + .maybe_headers(self.fork_headers.clone()) + .compute_units_per_second(self.get_compute_units_per_second()) + .build() + } + + /// Infers the network configuration from the fork chain ID if not already set. /// - /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain - /// id, ) - pub async fn evm_env(&self) -> eyre::Result { + /// When a fork URL is configured and the network has not been explicitly set, + /// this fetches the chain ID from the remote endpoint and calls + /// [`NetworkConfigs::with_chain_id`] to auto-enable the correct network + /// (e.g. Tempo, OP Stack) based on the chain ID. + pub async fn infer_network_from_fork(&mut self) { + #[cfg(feature = "optimism")] + let already_op = self.networks.is_optimism(); + #[cfg(not(feature = "optimism"))] + let already_op = false; + if !self.networks.is_tempo() + && !already_op + && let Some(ref fork_url) = self.fork_url + && let Ok(provider) = self.fork_provider_with_url::(fork_url) + && let Ok(chain_id) = provider.get_chain_id().await + { + // If Anvil's chain, request anvil_nodeInfo to determine if the network is Tempo. + if chain_id == NamedChain::AnvilHardhat as u64 { + if let Ok(node_info) = + provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()).await + && node_info.network.is_some_and(|network| network == "tempo") + { + self.networks = NetworkConfigs::with_tempo(); + } + } else { + self.networks = self.networks.with_chain_id(chain_id); + } + } + } + + /// Returns a tuple with [`EvmEnv`], `TxEnv`, and the actual fork block number. + /// + /// If a `fork_url` is set, creates a provider and passes it to both `EvmOpts::fork_evm_env` + /// and `EvmOpts::fork_tx_env`. Falls back to local settings when no fork URL is configured. + /// + /// The fork block number is returned separately because on some L2s (e.g., Arbitrum) the + /// `block_env.number` may be remapped (to the L1 block number) and therefore cannot be used + /// to pin the fork. + pub async fn env< + SPEC: Into + Default + Copy, + BLOCK: FoundryBlock + Default, + TX: FoundryTransaction + Default, + >( + &self, + ) -> eyre::Result<(EvmEnv, TX, Option)> { if let Some(ref fork_url) = self.fork_url { - Ok(self.fork_evm_env(fork_url).await?.0) + let provider = self.fork_provider_with_url::(fork_url)?; + let ((evm_env, block_number), tx) = + tokio::try_join!(self.fork_evm_env(&provider), self.fork_tx_env(&provider))?; + Ok((evm_env, tx, Some(block_number))) } else { - Ok(self.local_evm_env()) + Ok((self.local_evm_env(), self.local_tx_env(), None)) } } - /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint. - /// And the block that was used to configure the environment. - pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> { - let provider = ProviderBuilder::new(fork_url) - .compute_units_per_second(self.get_compute_units_per_second()) - .build()?; - environment( - &provider, - self.memory_limit, - self.env.gas_price.map(|v| v as u128), - self.env.chain_id, - self.fork_block_number, - self.sender, - self.disable_block_gas_limit, + /// Returns the [`EvmEnv`] (cfg + block) and [`BlockNumber`] fetched from the fork endpoint via + /// provider + pub async fn fork_evm_env< + SPEC: Into + Default + Copy, + BLOCK: FoundryBlock + Default, + N: Network, + P: Provider, + >( + &self, + provider: &P, + ) -> eyre::Result<(EvmEnv, BlockNumber)> { + trace!( + memory_limit = %self.memory_limit, + override_chain_id = ?self.env.chain_id, + pin_block = ?self.fork_block_number, + origin = %self.sender, + disable_block_gas_limit = %self.disable_block_gas_limit, + enable_tx_gas_limit = %self.enable_tx_gas_limit, + configs = ?self.networks, + "creating fork environment" + ); + + let bn = match self.fork_block_number { + Some(bn) => BlockNumberOrTag::Number(bn), + None => BlockNumberOrTag::Latest, + }; + + let (chain_id, block) = tokio::try_join!( + option_try_or_else(self.env.chain_id, async || provider.get_chain_id().await), + provider.get_block_by_number(bn) ) - .await .wrap_err_with(|| { let mut msg = "could not instantiate forked environment".to_string(); - if let Ok(url) = Url::parse(fork_url) { - if let Some(provider) = url.host() { - write!(msg, " with provider {provider}").unwrap(); - } + if let Some(fork_url) = self.fork_url.as_deref() + && let Ok(url) = Url::parse(fork_url) + && let Some(host) = url.host() + { + write!(msg, " with provider {host}").unwrap(); } msg - }) + })?; + + let Some(block) = block else { + let bn_msg = match bn { + BlockNumberOrTag::Number(bn) => format!("block number: {bn}"), + bn => format!("{bn} block"), + }; + let latest_msg = if let Ok(latest_block) = provider.get_block_number().await { + if let Some(block_number) = self.fork_block_number + && block_number <= latest_block + { + error!("{NON_ARCHIVE_NODE_WARNING}"); + } + format!("; latest block number: {latest_block}") + } else { + Default::default() + }; + eyre::bail!("failed to get {bn_msg}{latest_msg}"); + }; + + let block_number = block.header().number(); + let mut evm_env = EvmEnv { + cfg_env: self.cfg_env(chain_id), + block_env: block_env_from_header(block.header()), + }; + + apply_chain_and_block_specific_env_changes::(&mut evm_env, &block, self.networks); + + Ok((evm_env, block_number)) } - /// Returns the `revm::Env` configured with only local settings - pub fn local_evm_env(&self) -> crate::Env { - let cfg = configure_env( - self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID), - self.memory_limit, - self.disable_block_gas_limit, - ); + /// Returns the [`EvmEnv`] configured with only local settings. + fn local_evm_env + Default + Clone, BLOCK: FoundryBlock + Default>( + &self, + ) -> EvmEnv { + let cfg_env = self.cfg_env(self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID)); + let mut block_env = BLOCK::default(); + block_env.set_number(self.env.block_number); + block_env.set_beneficiary(self.env.block_coinbase); + block_env.set_timestamp(self.env.block_timestamp); + block_env.set_difficulty(U256::from(self.env.block_difficulty)); + block_env.set_prevrandao(Some(self.env.block_prevrandao)); + block_env.set_basefee(self.env.block_base_fee_per_gas); + block_env.set_gas_limit(self.gas_limit()); + EvmEnv::new(cfg_env, block_env) + } + + /// Returns the `TxEnv` with gas price and chain id resolved from provider. + async fn fork_tx_env>( + &self, + provider: &P, + ) -> eyre::Result { + let (gas_price, chain_id) = tokio::try_join!( + option_try_or_else(self.env.gas_price.map(|v| v as u128), async || { + provider.get_gas_price().await + }), + option_try_or_else(self.env.chain_id, async || provider.get_chain_id().await), + )?; + let mut tx_env = TX::default(); + tx_env.set_caller(self.sender); + tx_env.set_chain_id(Some(chain_id)); + tx_env.set_gas_price(gas_price); + tx_env.set_gas_limit(self.gas_limit()); + Ok(tx_env) + } - crate::Env { - evm_env: EvmEnv { - cfg_env: cfg, - block_env: BlockEnv { - number: self.env.block_number, - beneficiary: self.env.block_coinbase, - timestamp: self.env.block_timestamp, - difficulty: U256::from(self.env.block_difficulty), - prevrandao: Some(self.env.block_prevrandao), - basefee: self.env.block_base_fee_per_gas, - gas_limit: self.gas_limit(), - ..Default::default() - }, - }, - tx: TxEnv { - gas_price: self.env.gas_price.unwrap_or_default().into(), - gas_limit: self.gas_limit(), - caller: self.sender, - ..Default::default() - }, + /// Returns the `TxEnv` configured from local settings only. + fn local_tx_env(&self) -> TX { + let mut tx_env = TX::default(); + tx_env.set_caller(self.sender); + tx_env.set_gas_price(self.env.gas_price.unwrap_or_default().into()); + tx_env.set_gas_limit(self.gas_limit()); + tx_env + } + + /// Builds a [`CfgEnv`] from the options, using the provided [`ChainId`]. + fn cfg_env + Default + Clone>(&self, chain_id: ChainId) -> CfgEnv { + let mut cfg = CfgEnv::default(); + cfg.chain_id = chain_id; + cfg.memory_limit = self.memory_limit; + cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); + // EIP-3607 rejects transactions from senders with deployed code. + // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller + // is a contract. So we disable the check by default. + cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = self.disable_block_gas_limit; + cfg.disable_nonce_check = true; + // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825). + // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true. + if !self.enable_tx_gas_limit { + cfg.tx_gas_limit_cap = Some(u64::MAX); } + cfg } /// Helper function that returns the [CreateFork] to use, if any. @@ -189,10 +334,26 @@ impl EvmOpts { /// /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will /// be at `~/.foundry/cache/mainnet/14435000/storage.json`. - pub fn get_fork(&self, config: &Config, env: crate::Env) -> Option { + /// `fork_block_number` is the actual block number to pin the fork to. This must be the + /// real chain block number, not a remapped value. On some L2s (e.g., Arbitrum) + /// `block_env.number` is remapped to the L1 block number, so callers must pass the + /// original block number returned by [`EvmOpts::env`] instead. + pub fn get_fork( + &self, + config: &Config, + chain_id: u64, + fork_block_number: Option, + ) -> Option { let url = self.fork_url.clone()?; - let enable_caching = config.enable_caching(&url, env.evm_env.cfg_env.chain_id); - Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() }) + let enable_caching = config.enable_caching(&url, chain_id); + + // Pin fork_block_number to the block that was already fetched in env, so subsequent + // fork operations use the same block. This prevents inconsistencies when forking at + // "latest" where the chain could advance between calls. + let mut evm_opts = self.clone(); + evm_opts.fork_block_number = evm_opts.fork_block_number.or(fork_block_number); + + Some(CreateFork { url, enable_caching, evm_opts }) } /// Returns the gas limit to use @@ -200,27 +361,15 @@ impl EvmOpts { self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0 } - /// Returns the configured chain id, which will be - /// - the value of `chain_id` if set - /// - mainnet if `fork_url` contains "mainnet" - /// - the chain if `fork_url` is set and the endpoints returned its chain id successfully - /// - mainnet otherwise - pub async fn get_chain_id(&self) -> u64 { - if let Some(id) = self.env.chain_id { - return id; - } - self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id() - } - /// Returns the available compute units per second, which will be /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled) /// - the assigned compute units, if `compute_units_per_second` is set /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise - pub fn get_compute_units_per_second(&self) -> u64 { + const fn get_compute_units_per_second(&self) -> u64 { if self.no_rpc_rate_limit { u64::MAX } else if let Some(cups) = self.compute_units_per_second { - return cups; + cups } else { ALCHEMY_FREE_TIER_CUPS } @@ -228,13 +377,10 @@ impl EvmOpts { /// Returns the chain ID from the RPC, if any. pub async fn get_remote_chain_id(&self) -> Option { - if let Some(ref url) = self.fork_url { + if let Some(url) = &self.fork_url + && let Ok(provider) = self.fork_provider_with_url::(url) + { trace!(?url, "retrieving chain via eth_chainId"); - let provider = ProviderBuilder::new(url.as_str()) - .compute_units_per_second(self.get_compute_units_per_second()) - .build() - .ok() - .unwrap_or_else(|| panic!("Failed to establish provider to {url}")); if let Ok(id) = provider.get_chain_id().await { return Some(Chain::from(id)); @@ -278,10 +424,18 @@ pub struct Env { pub block_coinbase: Address, /// the block.timestamp value during EVM execution - pub block_timestamp: u64, + #[serde( + deserialize_with = "foundry_config::deserialize_u64_to_u256", + serialize_with = "foundry_config::serialize_u64_or_u256" + )] + pub block_timestamp: U256, /// the block.number value during EVM execution" - pub block_number: u64, + #[serde( + deserialize_with = "foundry_config::deserialize_u64_to_u256", + serialize_with = "foundry_config::serialize_u64_or_u256" + )] + pub block_number: U256, /// the block.difficulty value during EVM execution pub block_difficulty: u64, @@ -297,3 +451,168 @@ pub struct Env { #[serde(default, skip_serializing_if = "Option::is_none")] pub code_size_limit: Option, } + +async fn option_try_or_else( + option: Option, + f: impl AsyncFnOnce() -> Result, +) -> Result { + if let Some(value) = option { Ok(value) } else { f().await } +} + +#[cfg(test)] +mod tests { + use revm::context::{BlockEnv, TxEnv}; + + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_default_anvil_selects_ethereum() { + let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await; + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(handle.http_endpoint()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + // Plain anvil (chain id 31337) without tempo flag -> Ethereum (no network flags set). + assert!(!evm_opts.networks.is_tempo()); + #[cfg(feature = "optimism")] + assert!(!evm_opts.networks.is_optimism()); + assert!(!evm_opts.networks.is_celo()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_tempo_anvil_via_node_info() { + let (_api, handle) = anvil::spawn(anvil::NodeConfig::test_tempo()).await; + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(handle.http_endpoint()); + // Networks not set -> should query anvil_nodeInfo to discover tempo. + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + assert!(evm_opts.networks.is_tempo(), "should detect tempo via anvil_nodeInfo"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infer_network_tempo_anvil_skips_rpc_when_already_set() { + // Use a URL that would fail if any RPC call were attempted (connection refused). + // This proves the early-return guard prevents all network requests. + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some("http://127.0.0.1:1".to_string()); + // Explicitly set tempo before calling infer (simulates --tempo CLI flag). + evm_opts.networks = NetworkConfigs::with_tempo(); + + evm_opts.infer_network_from_fork().await; + + // Should still be tempo, the early-return guard skips the RPC call. + assert!(evm_opts.networks.is_tempo()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn flaky_infer_network_tempo_moderato_rpc() { + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some("https://rpc.moderato.tempo.xyz".to_string()); + assert_eq!(evm_opts.networks, NetworkConfigs::default()); + + evm_opts.infer_network_from_fork().await; + + // Tempo Moderato has a known Tempo chain ID -> should be inferred via with_chain_id. + assert!(evm_opts.networks.is_tempo(), "should detect tempo from Moderato chain ID"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn get_fork_pins_block_number_from_env() { + let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint(); + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(endpoint.clone()); + // Explicitly leave fork_block_number as None to simulate --fork-url without --block-number + assert!(evm_opts.fork_block_number.is_none()); + + // Fetch the environment (this resolves "latest" to an actual block number) + let (evm_env, _, fork_block) = evm_opts.env::().await.unwrap(); + assert!(fork_block.is_some(), "should have resolved a fork block number"); + let resolved_block = fork_block.unwrap(); + assert!(resolved_block > 0, "should have resolved to a real block number"); + + // Create the fork - this should pin the block number + let fork = + evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap(); + + // The fork's evm_opts should now have fork_block_number set to the resolved block + assert_eq!( + fork.evm_opts.fork_block_number, + Some(resolved_block), + "get_fork should pin fork_block_number to the block from env" + ); + } + + // Regression test for https://github.com/foundry-rs/foundry/issues/13576 + // On Arbitrum, `block_env.number` is remapped to the L1 block number by + // `apply_chain_and_block_specific_env_changes`. The fork block number returned + // by `env()` must be the actual L2 block number, not the remapped L1 value. + #[tokio::test(flavor = "multi_thread")] + async fn flaky_get_fork_uses_l2_block_number_on_arbitrum() { + let endpoint = + foundry_test_utils::rpc::next_rpc_endpoint(foundry_config::NamedChain::Arbitrum); + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(endpoint.clone()); + assert!(evm_opts.fork_block_number.is_none()); + + let (evm_env, _, fork_block) = evm_opts.env::().await.unwrap(); + let fork_block = fork_block.expect("should have resolved a fork block number"); + + // On Arbitrum, block_env.number is the L1 block number (much smaller). + // The fork_block should be the actual L2 block number (much larger). + let block_env_number: u64 = evm_env.block_env.number.to(); + assert!( + fork_block > block_env_number, + "fork_block ({fork_block}) should be the L2 block, which is larger than \ + block_env.number ({block_env_number}) which is the L1 block on Arbitrum" + ); + + // Verify get_fork pins to the correct L2 block number + let fork = evm_opts + .get_fork(&Config::default(), evm_env.cfg_env.chain_id, Some(fork_block)) + .unwrap(); + assert_eq!( + fork.evm_opts.fork_block_number, + Some(fork_block), + "get_fork should pin to the L2 block number, not the L1 block number" + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn get_fork_preserves_explicit_block_number() { + let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint(); + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_url = Some(endpoint.clone()); + // Set an explicit block number + evm_opts.fork_block_number = Some(12345678); + + let (evm_env, _, fork_block) = evm_opts.env::().await.unwrap(); + + let fork = + evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap(); + + // Should preserve the explicit block number, not override it + assert_eq!( + fork.evm_opts.fork_block_number, + Some(12345678), + "get_fork should preserve explicitly set fork_block_number" + ); + } +} diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs index d265ff20591bd..7ee9c66106de7 100644 --- a/crates/evm/core/src/precompiles.rs +++ b/crates/evm/core/src/precompiles.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{address, Address}; +use alloy_primitives::{Address, address}; /// The ECRecover precompile address. pub const EC_RECOVER: Address = address!("0x0000000000000000000000000000000000000001"); @@ -30,6 +30,35 @@ pub const BLAKE_2F: Address = address!("0x00000000000000000000000000000000000000 /// The PointEvaluation precompile address. pub const POINT_EVALUATION: Address = address!("0x000000000000000000000000000000000000000a"); +/// The BLS12-381 G1ADD precompile address. +pub const BLS12_G1ADD: Address = address!("0x000000000000000000000000000000000000000b"); + +/// The BLS12-381 G1MSM precompile address. +pub const BLS12_G1MSM: Address = address!("0x000000000000000000000000000000000000000c"); + +/// The BLS12-381 G2ADD precompile address. +pub const BLS12_G2ADD: Address = address!("0x000000000000000000000000000000000000000d"); + +/// The BLS12-381 G2MSM precompile address. +pub const BLS12_G2MSM: Address = address!("0x000000000000000000000000000000000000000e"); + +/// The BLS12-381 pairing check precompile address. +pub const BLS12_PAIRING_CHECK: Address = address!("0x000000000000000000000000000000000000000f"); + +/// The BLS12-381 map Fp to G1 precompile address. +pub const BLS12_MAP_FP_TO_G1: Address = address!("0x0000000000000000000000000000000000000010"); + +/// The BLS12-381 map Fp2 to G2 precompile address. +pub const BLS12_MAP_FP2_TO_G2: Address = address!("0x0000000000000000000000000000000000000011"); + +/// The P256VERIFY precompile address. +pub const P256_VERIFY: Address = address!("0x0000000000000000000000000000000000000100"); + +/// The Celo transfer precompile address. +/// +/// See +pub const CELO_TRANSFER: Address = address!("0x00000000000000000000000000000000000000fd"); + /// Precompile addresses. pub const PRECOMPILES: &[Address] = &[ EC_RECOVER, @@ -42,4 +71,12 @@ pub const PRECOMPILES: &[Address] = &[ EC_PAIRING, BLAKE_2F, POINT_EVALUATION, + BLS12_G1ADD, + BLS12_G1MSM, + BLS12_G2ADD, + BLS12_G2MSM, + BLS12_PAIRING_CHECK, + BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, + P256_VERIFY, ]; diff --git a/crates/evm/core/src/state_snapshot.rs b/crates/evm/core/src/state_snapshot.rs index 3be1172aded5d..48a2bebccb7db 100644 --- a/crates/evm/core/src/state_snapshot.rs +++ b/crates/evm/core/src/state_snapshot.rs @@ -1,7 +1,6 @@ //! Support for snapshotting different states -use alloy_primitives::{map::HashMap, U256}; -use std::ops::Add; +use alloy_primitives::{U256, map::HashMap}; /// Represents all state snapshots #[derive(Clone, Debug)] @@ -30,7 +29,7 @@ impl StateSnapshots { let snapshot_state = self.state_snapshots.remove(&id); // Revert all state snapshots taken after the state snapshot with the `id` - let mut to_revert = id.add(U256::from(1)); + let mut to_revert = id + U256::from(1); while to_revert < self.id { self.state_snapshots.remove(&to_revert); to_revert += U256::from(1); @@ -61,9 +60,8 @@ impl StateSnapshots { /// Inserts the new state snapshot at the given `id`. /// /// Does not auto-increment the next `id`. - pub fn insert_at(&mut self, state_snapshot: T, id: U256) -> U256 { + pub fn insert_at(&mut self, state_snapshot: T, id: U256) { self.state_snapshots.insert(id, state_snapshot); - id } } diff --git a/crates/evm/core/src/tempo.rs b/crates/evm/core/src/tempo.rs new file mode 100644 index 0000000000000..eb29696858ff2 --- /dev/null +++ b/crates/evm/core/src/tempo.rs @@ -0,0 +1,197 @@ +//! Tempo precompile and contract initialization for Foundry. +//! +//! This module provides the core initialization logic for Tempo-specific precompiles, +//! fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD), and standard contracts. +//! +//! It includes the shared genesis initialization function used by both anvil and forge. + +use alloy_primitives::{Address, Bytes, U256, address}; +use revm::state::Bytecode; +use tempo_contracts::{ + ARACHNID_CREATE2_FACTORY_ADDRESS, CREATEX_ADDRESS, CreateX, MULTICALL3_ADDRESS, Multicall3, + PERMIT2_ADDRESS, Permit2, SAFE_DEPLOYER_ADDRESS, SafeDeployer, + contracts::ARACHNID_CREATE2_FACTORY_BYTECODE, + precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, NONCE_PRECOMPILE_ADDRESS, + SIGNATURE_VERIFIER_ADDRESS, STABLECOIN_DEX_ADDRESS, TIP_FEE_MANAGER_ADDRESS, + TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, VALIDATOR_CONFIG_ADDRESS, + VALIDATOR_CONFIG_V2_ADDRESS, + }, +}; +use tempo_precompiles::{ + error::TempoPrecompileError, + storage::{PrecompileStorageProvider, StorageCtx}, + tip20::{ISSUER_ROLE, ITIP20, TIP20Token}, + tip20_factory::TIP20Factory, + validator_config, +}; + +pub use tempo_contracts::precompiles::PATH_USD_ADDRESS; + +/// All well-known Tempo precompile addresses. +pub const TEMPO_PRECOMPILE_ADDRESSES: &[Address] = &[ + NONCE_PRECOMPILE_ADDRESS, + STABLECOIN_DEX_ADDRESS, + TIP20_FACTORY_ADDRESS, + TIP403_REGISTRY_ADDRESS, + TIP_FEE_MANAGER_ADDRESS, + VALIDATOR_CONFIG_ADDRESS, + VALIDATOR_CONFIG_V2_ADDRESS, + ACCOUNT_KEYCHAIN_ADDRESS, + SIGNATURE_VERIFIER_ADDRESS, + ADDRESS_REGISTRY_ADDRESS, +]; + +/// All well-known TIP20 fee token addresses on Tempo networks. +pub const TEMPO_TIP20_TOKENS: &[Address] = &[PATH_USD_ADDRESS]; + +/// Initialize Tempo precompiles and contracts using a storage provider. +/// +/// This is the core initialization logic that sets up Tempo-specific precompiles, +/// fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD), and standard contracts. +/// +/// This function should be called during genesis setup when running in Tempo mode. +/// It uses the `StorageCtx` pattern to work with any storage backend that implements +/// `PrecompileStorageProvider`. +/// +/// # Arguments +/// * `storage` - A mutable reference to a storage provider implementing `PrecompileStorageProvider` +/// * `admin` - The admin address that will have control over tokens and config +/// * `recipient` - The address that will receive minted tokens +/// +/// Ref: +pub fn initialize_tempo_genesis( + storage: &mut impl PrecompileStorageProvider, + admin: Address, + recipient: Address, +) -> Result<(), TempoPrecompileError> { + StorageCtx::enter(storage, || initialize_tempo_genesis_inner(admin, recipient)) +} + +/// Inner genesis initialization logic. Must be called within a [`StorageCtx`] scope +/// (either via [`StorageCtx::enter`] or [`StorageCtx::enter_evm`]). +pub fn initialize_tempo_genesis_inner( + admin: Address, + recipient: Address, +) -> Result<(), TempoPrecompileError> { + // Idempotent: PATH_USD is the first token created during genesis; if it already exists, skip. + if TIP20Factory::new().is_tip20(PATH_USD_ADDRESS)? { + return Ok(()); + } + + let mut ctx = StorageCtx; + + // Set sentinel bytecode for precompile addresses + let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef])); + for precompile in TEMPO_PRECOMPILE_ADDRESSES { + ctx.set_code(*precompile, sentinel.clone())?; + } + + // Create PathUSD token: 0x20C0000000000000000000000000000000000000 + let path_usd_token_address = create_and_mint_token( + PATH_USD_ADDRESS, + "PathUSD", + "PathUSD", + "USD", + Address::ZERO, + admin, + recipient, + U256::from(u64::MAX), + )?; + + // Create AlphaUSD token: 0x20C0000000000000000000000000000000000001 + let _alpha_usd_token_address = create_and_mint_token( + address!("20C0000000000000000000000000000000000001"), + "AlphaUSD", + "AlphaUSD", + "USD", + path_usd_token_address, + admin, + recipient, + U256::from(u64::MAX), + )?; + + // Create BetaUSD token: 0x20C0000000000000000000000000000000000002 + let _beta_usd_token_address = create_and_mint_token( + address!("20C0000000000000000000000000000000000002"), + "BetaUSD", + "BetaUSD", + "USD", + path_usd_token_address, + admin, + recipient, + U256::from(u64::MAX), + )?; + + // Create ThetaUSD token: 0x20C0000000000000000000000000000000000003 + let _theta_usd_token_address = create_and_mint_token( + address!("20C0000000000000000000000000000000000003"), + "ThetaUSD", + "ThetaUSD", + "USD", + path_usd_token_address, + admin, + recipient, + U256::from(u64::MAX), + )?; + + // Initialize ValidatorConfig with admin as owner + ctx.sstore(VALIDATOR_CONFIG_ADDRESS, validator_config::slots::OWNER, admin.into_word().into())?; + + // Set bytecode for standard contracts + ctx.set_code( + MULTICALL3_ADDRESS, + Bytecode::new_legacy(Bytes::from_static(&Multicall3::DEPLOYED_BYTECODE)), + )?; + ctx.set_code( + CREATEX_ADDRESS, + Bytecode::new_legacy(Bytes::from_static(&CreateX::DEPLOYED_BYTECODE)), + )?; + ctx.set_code( + SAFE_DEPLOYER_ADDRESS, + Bytecode::new_legacy(Bytes::from_static(&SafeDeployer::DEPLOYED_BYTECODE)), + )?; + ctx.set_code( + PERMIT2_ADDRESS, + Bytecode::new_legacy(Bytes::from_static(&Permit2::DEPLOYED_BYTECODE)), + )?; + ctx.set_code( + ARACHNID_CREATE2_FACTORY_ADDRESS, + Bytecode::new_legacy(ARACHNID_CREATE2_FACTORY_BYTECODE), + )?; + + Ok(()) +} + +/// Helper function to create and mint a TIP20 token. +#[allow(clippy::too_many_arguments)] +fn create_and_mint_token( + address: Address, + symbol: &str, + name: &str, + currency: &str, + quote_token: Address, + admin: Address, + recipient: Address, + mint_amount: U256, +) -> Result { + let mut tip20_factory = TIP20Factory::new(); + + let token_address = tip20_factory.create_token_reserved_address( + address, + name, + symbol, + currency, + quote_token, + admin, + )?; + + let mut token = TIP20Token::from_address(token_address)?; + token.grant_role_internal(admin, *ISSUER_ROLE)?; + token.mint(admin, ITIP20::mintCall { to: recipient, amount: mint_amount })?; + if admin != recipient { + token.mint(admin, ITIP20::mintCall { to: admin, amount: mint_amount })?; + } + + Ok(token_address) +} diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 29518074644d0..76e1df1bd1778 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,42 +1,66 @@ -use alloy_consensus::BlockHeader; +use crate::{EvmEnv, FoundryBlock}; +use alloy_chains::Chain; +use alloy_consensus::{BlockHeader, private::alloy_eips::eip7840::BlobParams}; +use alloy_hardforks::EthereumHardfork; use alloy_json_abi::{Function, JsonAbi}; -use alloy_network::{ - eip2718::{ - EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, - LEGACY_TX_TYPE_ID, - }, - AnyTxEnvelope, TransactionResponse, -}; -use alloy_primitives::{Address, Selector, TxKind, B256, U256}; -use alloy_provider::{network::BlockResponse, Network}; -use alloy_rpc_types::{Transaction, TransactionRequest}; -use foundry_common::is_impersonated_tx; +use alloy_primitives::{B256, ChainId, Selector, U256}; +use alloy_provider::{Network, network::BlockResponse}; use foundry_config::NamedChain; -use revm::primitives::hardfork::SpecId; +use foundry_evm_networks::NetworkConfigs; +use revm::primitives::{ + eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, + hardfork::SpecId, +}; pub use revm::state::EvmState as StateChangeset; -use crate::EnvMut; +/// Hints to the compiler that this is a cold path, i.e. unlikely to be taken. +#[cold] +#[inline(always)] +pub const fn cold_path() { + // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable. +} + +/// Constructs a generic [`FoundryBlock`] from a block header. +pub fn block_env_from_header(header: &impl BlockHeader) -> BLOCK { + let mut block = BLOCK::default(); + block.set_number(U256::from(header.number())); + block.set_beneficiary(header.beneficiary()); + block.set_timestamp(U256::from(header.timestamp())); + block.set_difficulty(header.difficulty()); + block.set_prevrandao(header.mix_hash()); + block.set_basefee(header.base_fee_per_gas().unwrap_or_default()); + block.set_gas_limit(header.gas_limit()); + block +} /// Depending on the configured chain id and block number this should apply any specific changes /// /// - checks for prevrandao mixhash after merge /// - applies chain specifics: on Arbitrum `block.number` is the L1 block /// -/// Should be called with proper chain id (retrieved from provider if not provided). -pub fn apply_chain_and_block_specific_env_changes( - env: EnvMut<'_>, +/// Should be called with proper chain id (retrieved from provider if not provided), works with any +/// [`FoundryBlock`] type. +pub fn apply_chain_and_block_specific_env_changes< + N: Network, + SPEC: Into + Copy, + BLOCK: FoundryBlock, +>( + evm_env: &mut EvmEnv, block: &N::BlockResponse, + configs: NetworkConfigs, ) { - use NamedChain::*; + use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet}; - if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { + if let Ok(chain) = NamedChain::try_from(evm_env.cfg_env.chain_id) { let block_number = block.header().number(); match chain { Mainnet => { // after merge difficulty is supplanted with prevrandao EIP-4399 if block_number >= 15_537_351u64 { - env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); + evm_env + .block_env + .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into()); } return; @@ -48,15 +72,9 @@ pub fn apply_chain_and_block_specific_env_changes( // (`mixHash`) is always zero, even though bsc adopts the newer EVM // specification. This will confuse revm and causes emulation // failure. - env.block.prevrandao = Some(env.block.difficulty.into()); + evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into())); return; } - Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet => { - if env.block.prevrandao.is_none() { - // - env.block.prevrandao = Some(B256::random()); - } - } c if c.is_arbitrum() => { // on arbitrum `block.number` is the L1 block which is included in the // `l1BlockNumber` field @@ -67,16 +85,63 @@ pub fn apply_chain_and_block_specific_env_changes( serde_json::from_value::(l1_block_number).ok() }) { - env.block.number = l1_block_number.to(); + evm_env.block_env.set_number(l1_block_number); } } _ => {} } } + if configs.bypass_prevrandao(evm_env.cfg_env.chain_id) + && evm_env.block_env.prevrandao().is_none() + { + // + evm_env.block_env.set_prevrandao(Some(B256::random())); + } + // if difficulty is `0` we assume it's past merge if block.header().difficulty().is_zero() { - env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); + evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into()); + } +} + +/// Derives the active [`BlobParams`] based on the given timestamp. +/// +/// This falls back to regular ethereum blob params if no hardforks for the given chain id are +/// detected. +pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams { + let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp) + .unwrap_or_default(); + + match hardfork { + EthereumHardfork::Prague => BlobParams::prague(), + EthereumHardfork::Osaka => BlobParams::osaka(), + EthereumHardfork::Bpo1 => BlobParams::bpo1(), + EthereumHardfork::Bpo2 => BlobParams::bpo2(), + + // future hardforks/unknown settings: update once decided + EthereumHardfork::Bpo3 => BlobParams::bpo2(), + EthereumHardfork::Bpo4 => BlobParams::bpo2(), + EthereumHardfork::Bpo5 => BlobParams::bpo2(), + EthereumHardfork::Amsterdam => BlobParams::bpo2(), + + // fallback + _ => BlobParams::cancun(), + } +} + +/// Derive the blob base fee update fraction based on the chain and timestamp by checking the +/// hardfork. +pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 { + get_blob_params(chain_id, timestamp).update_fraction as u64 +} + +/// Returns the blob base fee update fraction based on the spec id. +pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 { + if spec >= SpecId::PRAGUE { + BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE + } else { + BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN } } @@ -90,92 +155,3 @@ pub fn get_function<'a>( .find(|func| func.selector() == selector) .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}")) } - -/// Configures the env for the given RPC transaction. -/// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`. -pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction) { - let impersonated_from = is_impersonated_tx(&tx.inner).then_some(tx.from()); - if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() { - configure_tx_req_env(env, &tx.clone().into(), impersonated_from).expect("cannot fail"); - } -} - -/// Configures the env for the given RPC transaction request. -/// `impersonated_from` is the address of the impersonated account. This helps account for an -/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`. -pub fn configure_tx_req_env( - env: &mut EnvMut<'_>, - tx: &TransactionRequest, - impersonated_from: Option
, -) -> eyre::Result<()> { - let TransactionRequest { - nonce, - from, - to, - value, - gas_price, - gas, - max_fee_per_gas, - max_priority_fee_per_gas, - max_fee_per_blob_gas, - ref input, - chain_id, - ref blob_versioned_hashes, - ref access_list, - transaction_type, - ref authorization_list, - sidecar: _, - } = *tx; - - // If no transaction type is provided, we need to infer it from the other fields. - let tx_type = transaction_type.unwrap_or_else(|| { - if authorization_list.is_some() { - EIP7702_TX_TYPE_ID - } else if blob_versioned_hashes.is_some() { - EIP4844_TX_TYPE_ID - } else if max_fee_per_gas.is_some() || max_priority_fee_per_gas.is_some() { - EIP1559_TX_TYPE_ID - } else if access_list.is_some() { - EIP2930_TX_TYPE_ID - } else { - LEGACY_TX_TYPE_ID - } - }); - env.tx.tx_type = tx_type; - - // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction - env.tx.kind = to.unwrap_or(TxKind::Create); - // If the transaction is impersonated, we need to set the caller to the from - // address Ref: https://github.com/foundry-rs/foundry/issues/9541 - env.tx.caller = - impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?); - env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?; - env.tx.nonce = nonce.unwrap_or_default(); - env.tx.value = value.unwrap_or_default(); - env.tx.data = input.input().cloned().unwrap_or_default(); - env.tx.chain_id = chain_id; - - // Type 1, EIP-2930 - env.tx.access_list = access_list.clone().unwrap_or_default(); - - // Type 2, EIP-1559 - env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default(); - env.tx.gas_priority_fee = max_priority_fee_per_gas; - - // Type 3, EIP-4844 - env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default(); - env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default(); - - // Type 4, EIP-7702 - if let Some(authorization_list) = authorization_list { - env.tx.set_signed_authorization(authorization_list.clone()); - } - - Ok(()) -} - -/// Get the gas used, accounting for refunds -pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { - let refund_quotient = if SpecId::is_enabled_in(spec, SpecId::LONDON) { 5 } else { 2 }; - spent - (refunded).min(spent / refund_quotient) -} diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index e38d33e2a5907..758604564726e 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -24,3 +24,8 @@ revm.workspace = true semver.workspace = true tracing.workspace = true rayon.workspace = true +solar.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 37bd4d381e53a..231da8a4b1d62 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -1,550 +1,453 @@ use super::{CoverageItem, CoverageItemKind, SourceLocation}; use alloy_primitives::map::HashMap; use foundry_common::TestFunctionExt; -use foundry_compilers::artifacts::{ - ast::{self, Ast, Node, NodeType}, - Source, -}; +use foundry_compilers::ProjectCompileOutput; use rayon::prelude::*; -use std::sync::Arc; +use solar::{ + ast::{self, ExprKind, ItemKind, StmtKind, yul}, + data_structures::{Never, map::FxHashSet}, + interface::{BytePos, Span}, + sema::{Gcx, hir}, +}; +use std::{ + ops::{ControlFlow, Range}, + path::PathBuf, + sync::Arc, +}; /// A visitor that walks the AST of a single contract and finds coverage items. -#[derive(Clone, Debug)] -pub struct ContractVisitor<'a> { +#[derive(Clone)] +struct SourceVisitor<'gcx> { /// The source ID of the contract. source_id: u32, - /// The source code that contains the AST being walked. - source: &'a str, + /// The solar session for span resolution. + gcx: Gcx<'gcx>, /// The name of the contract being walked. - contract_name: &'a Arc, + contract_name: Arc, /// The current branch ID branch_id: u32, - /// Stores the last line we put in the items collection to ensure we don't push duplicate lines - last_line: u32, /// Coverage items - pub items: Vec, + items: Vec, + + all_lines: Vec, + function_calls: Vec, + function_calls_set: FxHashSet, } -impl<'a> ContractVisitor<'a> { - pub fn new(source_id: usize, source: &'a str, contract_name: &'a Arc) -> Self { +struct SourceVisitorCheckpoint { + items: usize, + all_lines: usize, + function_calls: usize, +} + +impl<'gcx> SourceVisitor<'gcx> { + fn new(source_id: u32, gcx: Gcx<'gcx>) -> Self { Self { - source_id: source_id.try_into().expect("too many sources"), - source, - contract_name, + source_id, + gcx, + contract_name: Arc::default(), branch_id: 0, - last_line: 0, - items: Vec::new(), + all_lines: Default::default(), + function_calls: Default::default(), + function_calls_set: Default::default(), + items: Default::default(), } } - pub fn visit_contract(&mut self, node: &Node) -> eyre::Result<()> { - // Find all functions and walk their AST - for node in &node.nodes { - match node.node_type { - NodeType::FunctionDefinition => { - self.visit_function_definition(node)?; - } - NodeType::ModifierDefinition => { - self.visit_modifier_or_yul_fn_definition(node)?; + const fn checkpoint(&self) -> SourceVisitorCheckpoint { + SourceVisitorCheckpoint { + items: self.items.len(), + all_lines: self.all_lines.len(), + function_calls: self.function_calls.len(), + } + } + + fn restore_checkpoint(&mut self, checkpoint: SourceVisitorCheckpoint) { + let SourceVisitorCheckpoint { items, all_lines, function_calls } = checkpoint; + self.items.truncate(items); + self.all_lines.truncate(all_lines); + self.function_calls.truncate(function_calls); + } + + fn visit_contract<'ast>(&mut self, contract: &'ast ast::ItemContract<'ast>) { + let _ = ast::Visit::visit_item_contract(self, contract); + } + + /// Returns `true` if the contract has any test functions. + fn has_tests(&self, checkpoint: &SourceVisitorCheckpoint) -> bool { + self.items[checkpoint.items..].iter().any(|item| { + if let CoverageItemKind::Function { name } = &item.kind { + name.is_any_test() + } else { + false + } + }) + } + + /// Disambiguate functions with the same name in the same contract. + fn disambiguate_functions(&mut self) { + let mut dups = HashMap::<_, Vec>::default(); + for (i, item) in self.items.iter().enumerate() { + if let CoverageItemKind::Function { name } = &item.kind { + dups.entry(name.clone()).or_default().push(i); + } + } + for dups in dups.values() { + if dups.len() > 1 { + for (i, &dup) in dups.iter().enumerate() { + let item = &mut self.items[dup]; + if let CoverageItemKind::Function { name } = &item.kind { + item.kind = + CoverageItemKind::Function { name: format!("{name}.{i}").into() }; + } } - _ => {} } } - Ok(()) } - fn visit_function_definition(&mut self, node: &Node) -> eyre::Result<()> { - let Some(body) = &node.body else { return Ok(()) }; + fn resolve_function_calls(&mut self, hir_source_id: hir::SourceId) { + self.function_calls_set = self.function_calls.iter().copied().collect(); + let _ = hir::Visit::visit_nested_source(self, hir_source_id); + } - let name: String = - node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; - let kind: String = - node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; + fn sort(&mut self) { + self.items.sort(); + } - // TODO: We currently can only detect empty bodies in normal functions, not any of the other - // kinds: https://github.com/foundry-rs/foundry/issues/9458 - if kind != "function" && !has_statements(body) { - return Ok(()); + fn push_lines(&mut self) { + self.all_lines.sort_unstable(); + self.all_lines.dedup(); + let mut lines = Vec::new(); + for &line in &self.all_lines { + if let Some(reference_item) = + self.items.iter().find(|item| item.loc.lines.start == line) + { + lines.push(CoverageItem { + kind: CoverageItemKind::Line, + loc: reference_item.loc.clone(), + hits: 0, + }); + } } + self.items.extend(lines); + } + + fn push_stmt(&mut self, span: Span) { + self.push_item_kind(CoverageItemKind::Statement, span); + } - // `fallback`, `receive`, and `constructor` functions have an empty `name`. - // Use the `kind` itself as the name. - let name = if name.is_empty() { kind } else { name }; + /// Creates a coverage item for a given kind and source location. Pushes item to the internal + /// collection (plus additional coverage line if item is a statement). + fn push_item_kind(&mut self, kind: CoverageItemKind, span: Span) { + let item = CoverageItem { kind, loc: self.source_location_for(span), hits: 0 }; + + debug_assert!(!matches!(item.kind, CoverageItemKind::Line)); + self.all_lines.push(item.loc.lines.start); - self.push_item_kind(CoverageItemKind::Function { name }, &node.src); - self.visit_block(body) + self.items.push(item); } - fn visit_modifier_or_yul_fn_definition(&mut self, node: &Node) -> eyre::Result<()> { - let name: String = - node.attribute("name").ok_or_else(|| eyre::eyre!("Modifier has no name"))?; + fn source_location_for(&self, mut span: Span) -> SourceLocation { + // Statements' ranges in the solc source map do not include the semicolon. + if let Ok(snippet) = self.gcx.sess.source_map().span_to_snippet(span) + && let Some(stripped) = snippet.strip_suffix(';') + { + let stripped = stripped.trim_end(); + let skipped = snippet.len() - stripped.len(); + span = span.with_hi(span.hi() - BytePos::from_usize(skipped)); + } - match &node.body { - Some(body) => { - self.push_item_kind(CoverageItemKind::Function { name }, &node.src); - self.visit_block(body) - } - _ => Ok(()), + SourceLocation { + source_id: self.source_id as usize, + contract_name: self.contract_name.clone(), + bytes: self.byte_range(span), + lines: self.line_range(span), } } - fn visit_block(&mut self, node: &Node) -> eyre::Result<()> { - let statements: Vec = node.attribute("statements").unwrap_or_default(); + fn byte_range(&self, span: Span) -> Range { + let bytes_usize = self.gcx.sess.source_map().span_to_source(span).unwrap().data; + bytes_usize.start as u32..bytes_usize.end as u32 + } - for statement in &statements { - self.visit_statement(statement)?; - } + fn line_range(&self, span: Span) -> Range { + let lines = self.gcx.sess.source_map().span_to_lines(span).unwrap().data; + assert!(!lines.is_empty()); + let first = lines.first().unwrap(); + let last = lines.last().unwrap(); + first.line_index as u32 + 1..last.line_index as u32 + 2 + } - Ok(()) + const fn next_branch_id(&mut self) -> u32 { + let id = self.branch_id; + self.branch_id = id + 1; + id } +} - fn visit_statement(&mut self, node: &Node) -> eyre::Result<()> { - match node.node_type { - // Blocks - NodeType::Block | NodeType::UncheckedBlock | NodeType::YulBlock => { - self.visit_block(node) - } - // Inline assembly block - NodeType::InlineAssembly => self.visit_block( - &node - .attribute("AST") - .ok_or_else(|| eyre::eyre!("inline assembly block with no AST attribute"))?, - ), - // Simple statements - NodeType::Break | - NodeType::Continue | - NodeType::EmitStatement | - NodeType::RevertStatement | - NodeType::YulAssignment | - NodeType::YulBreak | - NodeType::YulContinue | - NodeType::YulLeave | - NodeType::YulVariableDeclaration => { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - Ok(()) - } - // Skip placeholder statements as they are never referenced in source maps. - NodeType::PlaceholderStatement => Ok(()), - // Return with eventual subcall - NodeType::Return => { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - if let Some(expr) = node.attribute("expression") { - self.visit_expression(&expr)?; - } - Ok(()) - } - // Variable declaration - NodeType::VariableDeclarationStatement => { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - if let Some(expr) = node.attribute("initialValue") { - self.visit_expression(&expr)?; - } - Ok(()) - } - // While loops - NodeType::DoWhileStatement | NodeType::WhileStatement => { - self.visit_expression( - &node - .attribute("condition") - .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, - )?; - - let body = node - .body - .as_deref() - .ok_or_else(|| eyre::eyre!("while statement had no body node"))?; - self.visit_block_or_statement(body) - } - // For loops - NodeType::ForStatement => { - if let Some(stmt) = node.attribute("initializationExpression") { - self.visit_statement(&stmt)?; - } - if let Some(expr) = node.attribute("condition") { - self.visit_expression(&expr)?; - } - if let Some(stmt) = node.attribute("loopExpression") { - self.visit_statement(&stmt)?; +impl<'ast> ast::Visit<'ast> for SourceVisitor<'_> { + type BreakValue = Never; + + fn visit_item_contract( + &mut self, + contract: &'ast ast::ItemContract<'ast>, + ) -> ControlFlow { + self.contract_name = contract.name.as_str().into(); + self.walk_item_contract(contract) + } + + #[expect(clippy::single_match)] + fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow { + match &item.kind { + ItemKind::Function(func) => { + // TODO: We currently can only detect empty bodies in normal functions, not any of + // the other kinds: https://github.com/foundry-rs/foundry/issues/9458 + if func.kind != ast::FunctionKind::Function && !has_statements(func.body.as_ref()) { + return ControlFlow::Continue(()); } - let body = node - .body - .as_deref() - .ok_or_else(|| eyre::eyre!("for statement had no body node"))?; - self.visit_block_or_statement(body) - } - // Expression statement - NodeType::ExpressionStatement | NodeType::YulExpressionStatement => self - .visit_expression( - &node - .attribute("expression") - .ok_or_else(|| eyre::eyre!("expression statement had no expression"))?, - ), - // If statement - NodeType::IfStatement => { - self.visit_expression( - &node - .attribute("condition") - .ok_or_else(|| eyre::eyre!("if statement had no condition"))?, - )?; - - let true_body: Node = node - .attribute("trueBody") - .ok_or_else(|| eyre::eyre!("if statement had no true body"))?; - - // We need to store the current branch ID here since visiting the body of either of - // the if blocks may increase `self.branch_id` in the case of nested if statements. - let branch_id = self.branch_id; - - // We increase the branch ID here such that nested branches do not use the same - // branch ID as we do. - self.branch_id += 1; - - match node.attribute::("falseBody") { - // Both if/else statements. - Some(false_body) => { - // Add branch coverage items only if one of true/branch bodies contains - // statements. - if has_statements(&true_body) || has_statements(&false_body) { - // The branch instruction is mapped to the first opcode within the true - // body source range. - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id: 0, - is_first_opcode: true, - }, - &true_body.src, - ); - // Add the coverage item for branch 1 (false body). - // The relevant source range for the false branch is the `else` - // statement itself and the false body of the else statement. - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id: 1, - is_first_opcode: false, - }, - &ast::LowFidelitySourceLocation { - start: node.src.start, - length: false_body.src.length.map(|length| { - false_body.src.start - true_body.src.start + length - }), - index: node.src.index, - }, - ); - - // Process the true body. - self.visit_block_or_statement(&true_body)?; - // Process the false body. - self.visit_block_or_statement(&false_body)?; - } - } - None => { - // Add single branch coverage only if it contains statements. - if has_statements(&true_body) { - // Add the coverage item for branch 0 (true body). - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id: 0, - is_first_opcode: true, - }, - &true_body.src, - ); - // Process the true body. - self.visit_block_or_statement(&true_body)?; - } + let name = func.header.name.as_ref().map(|n| n.as_str()).unwrap_or_else(|| { + match func.kind { + ast::FunctionKind::Constructor => "constructor", + ast::FunctionKind::Receive => "receive", + ast::FunctionKind::Fallback => "fallback", + ast::FunctionKind::Function | ast::FunctionKind::Modifier => unreachable!(), } + }); + + // Exclude function from coverage report if it is virtual without implementation. + let exclude_func = func.header.virtual_() && !func.is_implemented(); + if !exclude_func { + self.push_item_kind( + CoverageItemKind::Function { name: name.into() }, + item.span, + ); } - Ok(()) + self.walk_item(item)?; } - NodeType::YulIf => { - self.visit_expression( - &node - .attribute("condition") - .ok_or_else(|| eyre::eyre!("yul if statement had no condition"))?, - )?; - let body = node - .body - .as_deref() - .ok_or_else(|| eyre::eyre!("yul if statement had no body"))?; - - // We need to store the current branch ID here since visiting the body of either of - // the if blocks may increase `self.branch_id` in the case of nested if statements. - let branch_id = self.branch_id; - - // We increase the branch ID here such that nested branches do not use the same - // branch ID as we do - self.branch_id += 1; - - self.push_item_kind( - CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: false }, - &node.src, - ); - self.visit_block(body)?; + _ => {} + } + // Only walk functions. + ControlFlow::Continue(()) + } - Ok(()) + fn visit_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) -> ControlFlow { + match &stmt.kind { + StmtKind::Break | StmtKind::Continue | StmtKind::Emit(..) | StmtKind::Revert(..) => { + self.push_stmt(stmt.span); + // TODO(dani): these probably shouldn't be excluded. + return ControlFlow::Continue(()); } - // Try-catch statement. Coverage is reported as branches for catch clauses with - // statements. - NodeType::TryStatement => { - self.visit_expression( - &node - .attribute("externalCall") - .ok_or_else(|| eyre::eyre!("try statement had no call"))?, - )?; - - let branch_id = self.branch_id; - self.branch_id += 1; - - let mut clauses = node - .attribute::>("clauses") - .ok_or_else(|| eyre::eyre!("try statement had no clauses"))?; - - let try_block = clauses - .remove(0) - .attribute::("block") - .ok_or_else(|| eyre::eyre!("try statement had no block"))?; - // Add branch with path id 0 for try (first clause). - self.push_item_kind( - CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: true }, - &ast::LowFidelitySourceLocation { - start: node.src.start, - length: try_block - .src - .length - .map(|length| try_block.src.start + length - node.src.start), - index: node.src.index, - }, - ); - self.visit_block(&try_block)?; - - let mut path_id = 1; - for clause in clauses { - if let Some(catch_block) = clause.attribute::("block") { - if has_statements(&catch_block) { - // Add catch branch if it has statements. - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id, - is_first_opcode: true, - }, - &catch_block.src, - ); - self.visit_block(&catch_block)?; - // Increment path id for next branch. - path_id += 1; - } else if clause.attribute::("parameters").is_some() { - // Add coverage for clause with parameters and empty statements. - // (`catch (bytes memory reason) {}`). - // Catch all clause without statements is ignored (`catch {}`). - self.push_item_kind(CoverageItemKind::Statement, &clause.src); - self.visit_statement(&clause)?; - } - } - } - - Ok(()) + StmtKind::Return(_) | StmtKind::DeclSingle(_) | StmtKind::DeclMulti(..) => { + self.push_stmt(stmt.span); } - NodeType::YulSwitch => { - // Add coverage for each case statement amd their bodies. - for case in node - .attribute::>("cases") - .ok_or_else(|| eyre::eyre!("yul switch had no case"))? - { - self.push_item_kind(CoverageItemKind::Statement, &case.src); - self.visit_statement(&case)?; - if let Some(body) = case.body { - self.push_item_kind(CoverageItemKind::Statement, &body.src); - self.visit_block(&body)? + StmtKind::If(_cond, then_stmt, else_stmt) => { + let branch_id = self.next_branch_id(); + + // Add branch coverage items only if one of true/branch bodies contains statements. + if stmt_has_statements(then_stmt) + || else_stmt.as_ref().is_some_and(|s| stmt_has_statements(s)) + { + // The branch instruction is mapped to the first opcode within the true + // body source range. + self.push_item_kind( + CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: true }, + then_stmt.span, + ); + if else_stmt.is_some() { + // We use `stmt.span`, which includes `else_stmt.span`, since we need to + // include the condition so that this can be marked as covered. + // Initially implemented in https://github.com/foundry-rs/foundry/pull/3094. + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 1, + is_first_opcode: false, + }, + stmt.span, + ); } } - Ok(()) } - NodeType::YulForLoop => { - if let Some(condition) = node.attribute("condition") { - self.visit_expression(&condition)?; - } - if let Some(pre) = node.attribute::("pre") { - self.visit_block(&pre)? - } - if let Some(post) = node.attribute::("post") { - self.visit_block(&post)? - } - if let Some(body) = &node.body { - self.push_item_kind(CoverageItemKind::Statement, &body.src); - self.visit_block(body)? + StmtKind::Try(ast::StmtTry { expr: _, clauses }) => { + let branch_id = self.next_branch_id(); + + let mut path_id = 0; + for catch in clauses.iter() { + let ast::TryCatchClause { span, name: _, args, block } = catch; + let span = if path_id == 0 { stmt.span.to(*span) } else { *span }; + if path_id == 0 || has_statements(Some(block)) { + self.push_item_kind( + CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: true }, + span, + ); + path_id += 1; + } else if !args.is_empty() { + // Add coverage for clause with parameters and empty statements. + // (`catch (bytes memory reason) {}`). + // Catch all clause without statements is ignored (`catch {}`). + self.push_stmt(span); + } } - Ok(()) - } - NodeType::YulFunctionDefinition => self.visit_modifier_or_yul_fn_definition(node), - _ => { - warn!("unexpected node type, expected a statement: {:?}", node.node_type); - Ok(()) } + + // Skip placeholder statements as they are never referenced in source maps. + StmtKind::Assembly(_) + | StmtKind::Block(_) + | StmtKind::UncheckedBlock(_) + | StmtKind::Placeholder + | StmtKind::Expr(_) + | StmtKind::While(..) + | StmtKind::DoWhile(..) + | StmtKind::For { .. } => {} } + self.walk_stmt(stmt) } - fn visit_expression(&mut self, node: &Node) -> eyre::Result<()> { - match node.node_type { - NodeType::Assignment | - NodeType::UnaryOperation | - NodeType::Conditional | - NodeType::YulFunctionCall => { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - Ok(()) + fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow { + match &expr.kind { + ExprKind::Assign(..) + | ExprKind::Unary(..) + | ExprKind::Binary(..) + | ExprKind::Ternary(..) => { + self.push_stmt(expr.span); + if matches!(expr.kind, ExprKind::Binary(..)) { + return self.walk_expr(expr); + } } - NodeType::FunctionCall => { - // Do not count other kinds of calls towards coverage (like `typeConversion` - // and `structConstructorCall`). - let kind: Option = node.attribute("kind"); - if let Some("functionCall") = kind.as_deref() { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - - let expr: Option = node.attribute("expression"); - if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { - // Might be a require call, add branch coverage. - // Asserts should not be considered branches: . - let name: Option = expr.and_then(|expr| expr.attribute("name")); - if let Some("require") = name.as_deref() { - let branch_id = self.branch_id; - self.branch_id += 1; - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id: 0, - is_first_opcode: false, - }, - &node.src, - ); - self.push_item_kind( - CoverageItemKind::Branch { - branch_id, - path_id: 1, - is_first_opcode: false, - }, - &node.src, - ); - } + ExprKind::Call(callee, _args) => { + // Resolve later. + self.function_calls.push(expr.span); + + if let ExprKind::Ident(ident) = &callee.kind { + // Might be a require call, add branch coverage. + // Asserts should not be considered branches: . + if ident.as_str() == "require" { + let branch_id = self.next_branch_id(); + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 0, + is_first_opcode: false, + }, + expr.span, + ); + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 1, + is_first_opcode: false, + }, + expr.span, + ); } } - - Ok(()) } - NodeType::BinaryOperation => { - self.push_item_kind(CoverageItemKind::Statement, &node.src); - - // visit left and right expressions - // There could possibly a function call in the left or right expression - // e.g: callFunc(a) + callFunc(b) - if let Some(expr) = node.attribute("leftExpression") { - self.visit_expression(&expr)?; - } + _ => {} + } + // Intentionally do not walk all expressions. + ControlFlow::Continue(()) + } - if let Some(expr) = node.attribute("rightExpression") { - self.visit_expression(&expr)?; + fn visit_yul_stmt(&mut self, stmt: &'ast yul::Stmt<'ast>) -> ControlFlow { + use yul::StmtKind; + match &stmt.kind { + StmtKind::VarDecl(..) + | StmtKind::AssignSingle(..) + | StmtKind::AssignMulti(..) + | StmtKind::Leave + | StmtKind::Break + | StmtKind::Continue => { + self.push_stmt(stmt.span); + // Don't walk assignments. + return ControlFlow::Continue(()); + } + StmtKind::If(..) => { + let branch_id = self.next_branch_id(); + self.push_item_kind( + CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: false }, + stmt.span, + ); + } + StmtKind::For(yul::StmtFor { body, .. }) => { + self.push_stmt(body.span); + } + StmtKind::Switch(switch) => { + for case in switch.cases.iter() { + self.push_stmt(case.span); + self.push_stmt(case.body.span); } - - Ok(()) } - // Does not count towards coverage - NodeType::FunctionCallOptions | - NodeType::Identifier | - NodeType::IndexAccess | - NodeType::IndexRangeAccess | - NodeType::Literal | - NodeType::YulLiteralValue | - NodeType::YulIdentifier => Ok(()), - _ => { - warn!("unexpected node type, expected an expression: {:?}", node.node_type); - Ok(()) + StmtKind::FunctionDef(func) => { + let name = func.name.as_str(); + self.push_item_kind(CoverageItemKind::Function { name: name.into() }, stmt.span); } + // TODO(dani): merge with Block below on next solar release: https://github.com/paradigmxyz/solar/pull/496 + StmtKind::Expr(_) => { + self.push_stmt(stmt.span); + return ControlFlow::Continue(()); + } + StmtKind::Block(_) => {} } + self.walk_yul_stmt(stmt) } - fn visit_block_or_statement(&mut self, node: &Node) -> eyre::Result<()> { - match node.node_type { - NodeType::Block => self.visit_block(node), - NodeType::Break | - NodeType::Continue | - NodeType::DoWhileStatement | - NodeType::EmitStatement | - NodeType::ExpressionStatement | - NodeType::ForStatement | - NodeType::IfStatement | - NodeType::InlineAssembly | - NodeType::Return | - NodeType::RevertStatement | - NodeType::TryStatement | - NodeType::VariableDeclarationStatement | - NodeType::YulVariableDeclaration | - NodeType::WhileStatement => self.visit_statement(node), - // Skip placeholder statements as they are never referenced in source maps. - NodeType::PlaceholderStatement => Ok(()), - _ => { - warn!("unexpected node type, expected block or statement: {:?}", node.node_type); - Ok(()) - } + fn visit_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) -> ControlFlow { + use yul::ExprKind; + match &expr.kind { + ExprKind::Path(_) | ExprKind::Lit(_) => {} + ExprKind::Call(_) => self.push_stmt(expr.span), } + // Intentionally do not walk all expressions. + ControlFlow::Continue(()) } +} - /// Creates a coverage item for a given kind and source location. Pushes item to the internal - /// collection (plus additional coverage line if item is a statement). - fn push_item_kind(&mut self, kind: CoverageItemKind, src: &ast::LowFidelitySourceLocation) { - let item = CoverageItem { kind, loc: self.source_location_for(src), hits: 0 }; - - // Push a line item if we haven't already. - debug_assert!(!matches!(item.kind, CoverageItemKind::Line)); - if self.last_line < item.loc.lines.start { - self.items.push(CoverageItem { - kind: CoverageItemKind::Line, - loc: item.loc.clone(), - hits: 0, - }); - self.last_line = item.loc.lines.start; - } +impl<'gcx> hir::Visit<'gcx> for SourceVisitor<'gcx> { + type BreakValue = Never; - self.items.push(item); + fn hir(&self) -> &'gcx hir::Hir<'gcx> { + &self.gcx.hir } - fn source_location_for(&self, loc: &ast::LowFidelitySourceLocation) -> SourceLocation { - let bytes_start = loc.start as u32; - let bytes_end = (loc.start + loc.length.unwrap_or(0)) as u32; - let bytes = bytes_start..bytes_end; - - let start_line = self.source[..bytes.start as usize].lines().count() as u32; - let n_lines = self.source[bytes.start as usize..bytes.end as usize].lines().count() as u32; - let lines = start_line..start_line + n_lines; - SourceLocation { - source_id: self.source_id as usize, - contract_name: self.contract_name.clone(), - bytes, - lines, + fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow { + if let hir::ExprKind::Call(lhs, ..) = &expr.kind + && self.function_calls_set.contains(&expr.span) + && is_regular_call(lhs) + { + self.push_stmt(expr.span); } + self.walk_expr(expr) } } -/// Helper function to check if a given node is or contains any statement. -fn has_statements(node: &Node) -> bool { - match node.node_type { - NodeType::DoWhileStatement | - NodeType::EmitStatement | - NodeType::ExpressionStatement | - NodeType::ForStatement | - NodeType::IfStatement | - NodeType::RevertStatement | - NodeType::TryStatement | - NodeType::VariableDeclarationStatement | - NodeType::WhileStatement => true, - _ => node.attribute::>("statements").is_some_and(|s| !s.is_empty()), +// https://github.com/argotorg/solidity/blob/965166317bbc2b02067eb87f222a2dce9d24e289/libsolidity/ast/ASTAnnotations.h#L336-L341 +// https://github.com/argotorg/solidity/blob/965166317bbc2b02067eb87f222a2dce9d24e289/libsolidity/analysis/TypeChecker.cpp#L2720 +fn is_regular_call(lhs: &hir::Expr<'_>) -> bool { + match lhs.peel_parens().kind { + // StructConstructorCall + hir::ExprKind::Ident([hir::Res::Item(hir::ItemId::Struct(_))]) => false, + // TypeConversion + hir::ExprKind::Type(_) => false, + _ => true, + } +} + +fn has_statements(block: Option<&ast::Block<'_>>) -> bool { + block.is_some_and(|block| !block.is_empty()) +} + +fn stmt_has_statements(stmt: &ast::Stmt<'_>) -> bool { + match &stmt.kind { + StmtKind::Assembly(a) => !a.block.is_empty(), + StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => has_statements(Some(b)), + _ => true, } } @@ -571,57 +474,59 @@ impl SourceAnalysis { /// Note: Source IDs are only unique per compilation job; that is, a code base compiled with /// two different solc versions will produce overlapping source IDs if the compiler version is /// not taken into account. - pub fn new(data: &SourceFiles<'_>) -> eyre::Result { - let mut sourced_items = data - .sources - .par_iter() - .flat_map_iter(|(&source_id, SourceFile { source, ast })| { - let items = ast.nodes.iter().map(move |node| { - if !matches!(node.node_type, NodeType::ContractDefinition) { - return Ok(vec![]); - } + #[instrument(name = "SourceAnalysis::new", skip_all)] + pub fn new(data: &SourceFiles, output: &ProjectCompileOutput) -> eyre::Result { + let mut sourced_items = output.parser().solc().compiler().enter(|compiler| { + data.sources + .par_iter() + .map(|(&source_id, path)| { + let _guard = debug_span!("SourceAnalysis::new::visit", ?path).entered(); + + let (_, source) = compiler.gcx().get_ast_source(path).unwrap(); + let ast = source.ast.as_ref().unwrap(); + let (hir_source_id, _) = compiler.gcx().get_hir_source(path).unwrap(); + + let mut visitor = SourceVisitor::new(source_id, compiler.gcx()); + for item in ast.items.iter() { + // Visit only top-level contracts. + let ItemKind::Contract(contract) = &item.kind else { continue }; + + // Skip interfaces which have no function implementations. + if contract.kind.is_interface() { + continue; + } - // Skip interfaces which have no function implementations. - let contract_kind: String = node - .attribute("contractKind") - .ok_or_else(|| eyre::eyre!("Contract has no kind"))?; - if contract_kind == "interface" { - return Ok(vec![]); + let checkpoint = visitor.checkpoint(); + visitor.visit_contract(contract); + if visitor.has_tests(&checkpoint) { + visitor.restore_checkpoint(checkpoint); + } } - let name = node - .attribute("name") - .ok_or_else(|| eyre::eyre!("Contract has no name"))?; - - let mut visitor = ContractVisitor::new(source_id, &source.content, &name); - visitor.visit_contract(node)?; - let mut items = visitor.items; - - let is_test = items.iter().any(|item| { - if let CoverageItemKind::Function { name } = &item.kind { - name.is_any_test() - } else { - false - } - }); - if is_test { - items.clear(); + if !visitor.function_calls.is_empty() { + visitor.resolve_function_calls(hir_source_id); } - Ok(items) - }); - items.map(move |items| items.map(|items| (source_id, items))) - }) - .collect::)>>>()?; + if !visitor.items.is_empty() { + visitor.disambiguate_functions(); + visitor.sort(); + visitor.push_lines(); + visitor.sort(); + } + (source_id, visitor.items) + }) + .collect::)>>() + }); // Create mapping and merge items. sourced_items.sort_by_key(|(id, items)| (*id, items.first().map(|i| i.loc.bytes.start))); let Some(&(max_idx, _)) = sourced_items.last() else { return Ok(Self::default()) }; let len = max_idx + 1; let mut all_items = Vec::new(); - let mut map = vec![(u32::MAX, 0); len]; + let mut map = vec![(u32::MAX, 0); len as usize]; for (idx, items) in sourced_items { // Assumes that all `idx` items are consecutive, guaranteed by the sort above. + let idx = idx as usize; if map[idx].0 == u32::MAX { map[idx].0 = all_items.len() as u32; } @@ -638,7 +543,7 @@ impl SourceAnalysis { } /// Returns all the mutable coverage items. - pub fn all_items_mut(&mut self) -> &mut Vec { + pub const fn all_items_mut(&mut self) -> &mut Vec { &mut self.all_items } @@ -668,17 +573,8 @@ impl SourceAnalysis { } /// A list of versioned sources and their ASTs. -#[derive(Debug, Default)] -pub struct SourceFiles<'a> { +#[derive(Default)] +pub struct SourceFiles { /// The versioned sources. - pub sources: HashMap>, -} - -/// The source code and AST of a file. -#[derive(Debug)] -pub struct SourceFile<'a> { - /// The source code. - pub source: Source, - /// The AST of the source code. - pub ast: &'a Ast, + pub sources: HashMap, } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index 415ad744b30e8..c2bf424a1ad49 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -3,7 +3,7 @@ use crate::analysis::SourceAnalysis; use alloy_primitives::map::rustc_hash::FxHashSet; use eyre::ensure; use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap}; -use foundry_evm_core::ic::IcPcMap; +use foundry_evm_core::{bytecode::InstIter, ic::IcPcMap}; use revm::bytecode::opcode; /// Attempts to find anchors for the given items using the given source map and bytecode. @@ -93,54 +93,42 @@ pub fn find_anchor_branch( loc: &SourceLocation, ) -> eyre::Result<(ItemAnchor, ItemAnchor)> { let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None; - let mut pc = 0; - let mut cumulative_push_size = 0; - while pc < bytecode.len() { - let op = bytecode[pc]; - + for (ic, (pc, inst)) in InstIter::new(bytecode).with_pc().enumerate() { // We found a push, so we do some PC -> IC translation accounting, but we also check if // this push is coupled with the JUMPI we are interested in. // Check if Opcode is PUSH - if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { - let element = if let Some(element) = source_map.get(pc - cumulative_push_size) { - element - } else { + if (opcode::PUSH1..=opcode::PUSH32).contains(&inst.opcode.get()) { + let Some(element) = source_map.get(ic) else { // NOTE(onbjerg): For some reason the last few bytes of the bytecode do not have // a source map associated, so at that point we just stop searching - break + break; }; - // Do push byte accounting - let push_size = (op - opcode::PUSH1 + 1) as usize; - pc += push_size; - cumulative_push_size += push_size; - // Check if we are in the source range we are interested in, and if the next opcode // is a JUMPI - if is_in_source_range(element, loc) && bytecode[pc + 1] == opcode::JUMPI { - // We do not support program counters bigger than usize. This is also an - // assumption in REVM, so this is just a sanity check. - ensure!(push_size <= 8, "jump destination overflow"); + let next_pc = pc + inst.immediate.len() + 1; + let push_size = inst.immediate.len(); + if bytecode.get(next_pc).copied() == Some(opcode::JUMPI) + && is_in_source_range(element, loc) + { + // We do not support program counters bigger than u32. + ensure!(push_size <= 4, "jump destination overflow"); - // Convert the push bytes for the second branch's PC to a usize - let push_bytes_start = pc - push_size + 1; - let push_bytes = &bytecode[push_bytes_start..push_bytes_start + push_size]; - let mut pc_bytes = [0u8; 8]; - pc_bytes[8 - push_size..].copy_from_slice(push_bytes); - let pc_jump = u64::from_be_bytes(pc_bytes); - let pc_jump = u32::try_from(pc_jump).expect("PC is too big"); + // Convert the push bytes for the second branch's PC to a u32. + let mut pc_bytes = [0u8; 4]; + pc_bytes[4 - push_size..].copy_from_slice(inst.immediate); + let pc_jump = u32::from_be_bytes(pc_bytes); anchors = Some(( ItemAnchor { item_id, // The first branch is the opcode directly after JUMPI - instruction: (pc + 2) as u32, + instruction: (next_pc + 1) as u32, }, ItemAnchor { item_id, instruction: pc_jump }, )); } } - pc += 1; } anchors.ok_or_else(|| eyre::eyre!("Could not detect branches in source: {}", loc)) diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index dfed9589e55fc..ed2ad0af7a11f 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -1,16 +1,14 @@ use crate::{HitMap, HitMaps}; use alloy_primitives::B256; use revm::{ - context::ContextTr, - inspector::JournalExt, - interpreter::{interpreter_types::Jumps, Interpreter}, Inspector, + interpreter::{Interpreter, interpreter_types::Jumps}, }; use std::ptr::NonNull; /// Inspector implementation for collecting coverage information. #[derive(Clone, Debug)] -pub struct CoverageCollector { +pub struct LineCoverageCollector { // NOTE: `current_map` is always a valid reference into `maps`. // It is accessed only through `get_or_insert_map` which guarantees that it's valid. // Both of these fields are unsafe to access directly outside of `*insert_map`. @@ -21,10 +19,10 @@ pub struct CoverageCollector { } // SAFETY: See comments on `current_map`. -unsafe impl Send for CoverageCollector {} -unsafe impl Sync for CoverageCollector {} +unsafe impl Send for LineCoverageCollector {} +unsafe impl Sync for LineCoverageCollector {} -impl Default for CoverageCollector { +impl Default for LineCoverageCollector { fn default() -> Self { Self { current_map: NonNull::dangling(), @@ -34,23 +32,20 @@ impl Default for CoverageCollector { } } -impl Inspector for CoverageCollector -where - CTX: ContextTr, -{ +impl Inspector for LineCoverageCollector { fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { - get_or_insert_contract_hash(interpreter); - self.insert_map(interpreter); + let map = self.get_or_insert_map(interpreter); + // Reserve some space early to avoid reallocating too often. + map.reserve(8192.min(interpreter.bytecode.len())); } - #[inline] fn step(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { let map = self.get_or_insert_map(interpreter); map.hit(interpreter.bytecode.pc() as u32); } } -impl CoverageCollector { +impl LineCoverageCollector { /// Finish collecting coverage information and return the [`HitMaps`]. pub fn finish(self) -> HitMaps { self.maps @@ -62,7 +57,7 @@ impl CoverageCollector { /// See comments on `current_map` for more details. #[inline] fn get_or_insert_map(&mut self, interpreter: &mut Interpreter) -> &mut HitMap { - let hash = get_or_insert_contract_hash(interpreter); + let hash = interpreter.bytecode.get_or_calculate_hash(); if self.current_hash != *hash { self.insert_map(interpreter); } @@ -73,7 +68,7 @@ impl CoverageCollector { #[cold] #[inline(never)] fn insert_map(&mut self, interpreter: &mut Interpreter) { - let hash = interpreter.bytecode.hash().unwrap_or_else(|| eof_panic()); + let hash = interpreter.bytecode.hash().unwrap(); self.current_hash = hash; // Converts the mutable reference to a `NonNull` pointer. self.current_map = self @@ -83,21 +78,3 @@ impl CoverageCollector { .into(); } } - -/// Helper function for extracting contract hash used to record coverage hit map. -/// -/// If the contract hash is zero (contract not yet created but it's going to be created in current -/// tx) then the hash is calculated from the bytecode. -#[inline] -fn get_or_insert_contract_hash(interpreter: &mut Interpreter) -> B256 { - if interpreter.bytecode.hash().is_none_or(|h| h.is_zero()) { - interpreter.bytecode.regenerate_hash(); - } - interpreter.bytecode.hash().unwrap_or_else(|| eof_panic()) -} - -#[cold] -#[inline(never)] -fn eof_panic() -> ! { - panic!("coverage does not support EOF"); -} diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 793e0ee56670c..25ecebed244a8 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -3,14 +3,14 @@ //! EVM bytecode coverage analysis. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; use alloy_primitives::{ - map::{B256HashMap, HashMap}, Bytes, + map::{B256HashMap, HashMap, rustc_hash::FxHashMap}, }; use analysis::SourceAnalysis; use eyre::Result; @@ -18,7 +18,7 @@ use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; use std::{ collections::BTreeMap, - fmt::Display, + fmt, num::NonZeroU32, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, @@ -29,7 +29,7 @@ pub mod analysis; pub mod anchors; mod inspector; -pub use inspector::CoverageCollector; +pub use inspector::LineCoverageCollector; /// A coverage report. /// @@ -44,6 +44,8 @@ pub struct CoverageReport { /// All coverage items for the codebase, keyed by the compiler version. pub analyses: HashMap, /// All item anchors for the codebase, keyed by their contract ID. + /// + /// `(id, (creation, runtime))` pub anchors: HashMap, Vec)>, /// All the bytecode hits for the codebase. pub bytecode_hits: HashMap, @@ -77,6 +79,8 @@ impl CoverageReport { } /// Add anchors to this report. + /// + /// `(id, (creation, runtime))` pub fn add_anchors( &mut self, anchors: impl IntoIterator, Vec))>, @@ -208,8 +212,8 @@ impl DerefMut for HitMaps { /// Contains low-level data about hit counters for the instructions in the bytecode of a contract. #[derive(Clone, Debug)] pub struct HitMap { + hits: FxHashMap, bytecode: Bytes, - hits: HashMap, } impl HitMap { @@ -221,7 +225,7 @@ impl HitMap { /// Returns the bytecode. #[inline] - pub fn bytecode(&self) -> &Bytes { + pub const fn bytecode(&self) -> &Bytes { &self.bytecode } @@ -243,9 +247,15 @@ impl HitMap { *self.hits.entry(pc).or_default() += hits; } + /// Reserve space for additional hits. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.hits.reserve(additional); + } + /// Merge another hitmap into this, assuming the bytecode is consistent pub fn merge(&mut self, other: &Self) { - self.hits.reserve(other.len()); + self.reserve(other.len()); for (pc, hits) in other.iter() { self.hits(pc, hits); } @@ -278,8 +288,8 @@ pub struct ContractId { pub contract_name: Arc, } -impl Display for ContractId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ContractId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Contract \"{}\" (solc {}, source ID {})", @@ -297,8 +307,8 @@ pub struct ItemAnchor { pub item_id: u32, } -impl Display for ItemAnchor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ItemAnchor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IC {} -> Item {}", self.instruction, self.item_id) } } @@ -326,10 +336,41 @@ pub enum CoverageItemKind { /// A function in the code. Function { /// The name of the function. - name: String, + name: Box, }, } +impl PartialEq for CoverageItemKind { + fn eq(&self, other: &Self) -> bool { + self.ord_key() == other.ord_key() + } +} + +impl Eq for CoverageItemKind {} + +impl PartialOrd for CoverageItemKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CoverageItemKind { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.ord_key().cmp(&other.ord_key()) + } +} + +impl CoverageItemKind { + fn ord_key(&self) -> impl Ord + use<> { + match *self { + Self::Line => 0, + Self::Statement => 1, + Self::Branch { .. } => 2, + Self::Function { .. } => 3, + } + } +} + #[derive(Clone, Debug)] pub struct CoverageItem { /// The coverage item kind. @@ -340,23 +381,81 @@ pub struct CoverageItem { pub hits: u32, } -impl Display for CoverageItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.kind { - CoverageItemKind::Line => { - write!(f, "Line")?; - } - CoverageItemKind::Statement => { - write!(f, "Statement")?; - } - CoverageItemKind::Branch { branch_id, path_id, .. } => { - write!(f, "Branch (branch: {branch_id}, path: {path_id})")?; +impl PartialEq for CoverageItem { + fn eq(&self, other: &Self) -> bool { + self.ord_key() == other.ord_key() + } +} + +impl Eq for CoverageItem {} + +impl PartialOrd for CoverageItem { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CoverageItem { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.ord_key().cmp(&other.ord_key()) + } +} + +impl fmt::Display for CoverageItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_with_source(None).fmt(f) + } +} + +impl CoverageItem { + fn ord_key(&self) -> impl Ord + use<> { + ( + self.loc.source_id, + self.loc.lines.start, + self.loc.lines.end, + self.kind.ord_key(), + self.loc.bytes.start, + self.loc.bytes.end, + ) + } + + pub fn fmt_with_source(&self, src: Option<&str>) -> impl fmt::Display { + solar::data_structures::fmt::from_fn(move |f| { + match &self.kind { + CoverageItemKind::Line => { + write!(f, "Line")?; + } + CoverageItemKind::Statement => { + write!(f, "Statement")?; + } + CoverageItemKind::Branch { branch_id, path_id, .. } => { + write!(f, "Branch (branch: {branch_id}, path: {path_id})")?; + } + CoverageItemKind::Function { name } => { + write!(f, r#"Function "{name}""#)?; + } } - CoverageItemKind::Function { name } => { - write!(f, r#"Function "{name}""#)?; + write!(f, " (location: ({}), hits: {})", self.loc, self.hits)?; + + if let Some(src) = src + && let Some(src) = src.get(self.loc.bytes()) + { + write!(f, " -> ")?; + + let max_len = 64; + let max_half = max_len / 2; + + if src.len() > max_len { + write!(f, "\"{}", src[..max_half].escape_debug())?; + write!(f, "...")?; + write!(f, "{}\"", src[src.len() - max_half..].escape_debug())?; + } else { + write!(f, "{src:?}")?; + } } - } - write!(f, " (location: {}, hits: {})", self.loc, self.hits) + + Ok(()) + }) } } @@ -373,13 +472,18 @@ pub struct SourceLocation { pub lines: Range, } -impl Display for SourceLocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "source ID {}, lines {:?}, bytes {:?}", self.source_id, self.lines, self.bytes) +impl fmt::Display for SourceLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "source ID: {}, lines: {:?}, bytes: {:?}", self.source_id, self.lines, self.bytes) } } impl SourceLocation { + /// Returns the byte range as usize. + pub const fn bytes(&self) -> Range { + self.bytes.start as usize..self.bytes.end as usize + } + /// Returns the length of the byte range. pub fn len(&self) -> u32 { self.bytes.len() as u32 @@ -426,7 +530,7 @@ impl CoverageSummary { } /// Adds another coverage summary to this one. - pub fn merge(&mut self, other: &Self) { + pub const fn merge(&mut self, other: &Self) { let Self { line_count, line_hits, @@ -448,7 +552,7 @@ impl CoverageSummary { } /// Adds a coverage item to this summary. - pub fn add_item(&mut self, item: &CoverageItem) { + pub const fn add_item(&mut self, item: &CoverageItem) { match item.kind { CoverageItemKind::Line => { self.line_count += 1; diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 06074e305b630..dd5138f532074 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -21,17 +21,21 @@ foundry-config.workspace = true foundry-evm-core.workspace = true foundry-evm-coverage.workspace = true foundry-evm-fuzz.workspace = true +foundry-evm-sancov.workspace = true +foundry-evm-hardforks.workspace = true +foundry-evm-networks.workspace = true foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } -alloy-evm.workspace = true alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", "arbitrary", "rlp", ] } +alloy-rpc-types.workspace = true alloy-sol-types.workspace = true revm = { workspace = true, default-features = false, features = [ "std", @@ -44,6 +48,8 @@ revm = { workspace = true, default-features = false, features = [ "c-kzg", ] } revm-inspectors.workspace = true +tempo-precompiles.workspace = true +tempo-primitives.workspace = true eyre.workspace = true parking_lot.workspace = true @@ -51,4 +57,21 @@ proptest.workspace = true thiserror.workspace = true tracing.workspace = true indicatif.workspace = true +serde_json.workspace = true serde.workspace = true +uuid.workspace = true +rayon.workspace = true +tokio.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm-core/optimism", + "foundry-evm-hardforks/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-fuzz/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index a60934046efe1..a49716ca4fbc1 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,6 +1,9 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; -use foundry_evm_core::{backend::Backend, Env}; -use revm::primitives::hardfork::SpecId; +use foundry_evm_core::{ + backend::Backend, + evm::{BlockEnvFor, EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, +}; +use revm::context::{Block, Transaction}; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -9,42 +12,38 @@ use revm::primitives::hardfork::SpecId; /// /// [`Cheatcodes`]: super::Cheatcodes /// [`InspectorStack`]: super::InspectorStack -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] #[must_use = "builders do nothing unless you call `build` on them"] -pub struct ExecutorBuilder { +pub struct ExecutorBuilder { /// The configuration used to build an `InspectorStack`. - stack: InspectorStackBuilder, + stack: InspectorStackBuilder>, /// The gas limit. gas_limit: Option, - /// The spec ID. - spec_id: SpecId, + /// The spec override. When `None`, the spec from `EvmEnv::cfg_env` is preserved. + spec: Option>, legacy_assertions: bool, } -impl Default for ExecutorBuilder { +impl Default for ExecutorBuilder { #[inline] fn default() -> Self { Self { stack: InspectorStackBuilder::new(), gas_limit: None, - spec_id: SpecId::default(), + spec: None, legacy_assertions: false, } } } -impl ExecutorBuilder { - /// Create a new executor builder. - #[inline] - pub fn new() -> Self { - Self::default() - } - +impl ExecutorBuilder { /// Modify the inspector stack. #[inline] pub fn inspectors( mut self, - f: impl FnOnce(InspectorStackBuilder) -> InspectorStackBuilder, + f: impl FnOnce( + InspectorStackBuilder>, + ) -> InspectorStackBuilder>, ) -> Self { self.stack = f(self.stack); self @@ -52,42 +51,50 @@ impl ExecutorBuilder { /// Sets the EVM spec to use. #[inline] - pub fn spec_id(mut self, spec: SpecId) -> Self { - self.spec_id = spec; + pub const fn spec_id(mut self, spec: SpecFor) -> Self { + self.spec = Some(spec); self } + /// Optionally sets the EVM spec. When `None`, the spec from `EvmEnv::cfg_env` is preserved. + #[inline] + pub const fn spec_id_opt(self, spec: Option>) -> Self { + if let Some(spec) = spec { self.spec_id(spec) } else { self } + } + /// Sets the executor gas limit. #[inline] - pub fn gas_limit(mut self, gas_limit: u64) -> Self { + pub const fn gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } /// Sets the `legacy_assertions` flag. #[inline] - pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { + pub const fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { self.legacy_assertions = legacy_assertions; self } /// Builds the executor as configured. #[inline] - pub fn build(self, env: Env, db: Backend) -> Executor { - let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; + pub fn build( + self, + mut evm_env: EvmEnvFor, + tx_env: TxEnvFor, + db: Backend, + ) -> Executor { + let Self { mut stack, gas_limit, spec, legacy_assertions, .. } = self; if stack.block.is_none() { - stack.block = Some(env.evm_env.block_env.clone()); + stack.block = Some(evm_env.block_env.clone()); } if stack.gas_price.is_none() { - stack.gas_price = Some(env.tx.gas_price); + stack.gas_price = Some(tx_env.gas_price()); + } + let gas_limit = gas_limit.unwrap_or(evm_env.block_env.gas_limit()); + if let Some(spec) = spec { + evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec); } - let gas_limit = gas_limit.unwrap_or(env.evm_env.block_env.gas_limit); - let env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx, - spec_id, - ); - Executor::new(db, env, stack.build(), gas_limit, legacy_assertions) + Executor::new(db, evm_env, tx_env, stack.build(), gas_limit, legacy_assertions) } } diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs new file mode 100644 index 0000000000000..3c48e30dd239d --- /dev/null +++ b/crates/evm/evm/src/executors/corpus.rs @@ -0,0 +1,1391 @@ +//! Corpus management for parallel fuzzing with coverage-guided mutation. +//! +//! This module implements a corpus-based fuzzing system that stores, mutates, and shares +//! transaction sequences across multiple fuzzing workers. Each corpus entry represents a +//! sequence of transactions that has produced interesting coverage, and can be mutated to +//! discover new execution paths. +//! +//! ## File System Structure +//! +//! The corpus is organized on disk as follows: +//! +//! ```text +//! / +//! ├── worker0/ # Master (worker 0) directory +//! │ ├── corpus/ # Master's corpus entries +//! │ │ ├── -.json # Corpus entry (if small) +//! │ │ ├── -.json.gz # Corpus entry (if large, compressed) +//! │ └── sync/ # Directory where other workers export new findings +//! │ └── -.json # New entries from other workers +//! └── workerN/ # Worker N's directory +//! ├── corpus/ # Worker N's local corpus +//! │ └── ... +//! └── sync/ # Worker 2's sync directory +//! └── ... +//! ``` +//! +//! ## Workflow +//! +//! - Each worker maintains its own local corpus with entries stored as JSON files +//! - Workers export new interesting entries to the master's sync directory via hard links +//! - The master (worker0) imports new entries from its sync directory and exports them to all the +//! other workers +//! - Workers sync with the master to receive new corpus entries from other workers +//! - This all happens periodically, there is no clear order in which workers export or import +//! entries since it doesn't matter as long as the corpus eventually syncs across all workers + +use crate::executors::{Executor, RawCallResult, invariant::execute_tx}; +use alloy_dyn_abi::JsonAbiExt; +use alloy_json_abi::Function; +use alloy_primitives::{Bytes, I256}; +use eyre::{Result, eyre}; +use foundry_common::sh_warn; +use foundry_config::FuzzCorpusConfig; +use foundry_evm_core::evm::FoundryEvmNetwork; +use foundry_evm_fuzz::{ + BasicTxDetails, + invariant::FuzzRunIdentifiedContracts, + strategies::{EvmFuzzState, mutate_param_value}, +}; +use proptest::{ + prelude::{Just, Rng, Strategy}, + prop_oneof, + strategy::{BoxedStrategy, ValueTree}, + test_runner::TestRunner, +}; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + path::{Path, PathBuf}, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + time::{SystemTime, UNIX_EPOCH}, +}; +use uuid::Uuid; + +const WORKER: &str = "worker"; +const CORPUS_DIR: &str = "corpus"; +const SYNC_DIR: &str = "sync"; +const OPTIMIZATION_BEST_FILE: &str = "optimization_best.json"; + +const FAVORABILITY_THRESHOLD: f64 = 0.3; +const COVERAGE_MAP_SIZE: usize = 65536; + +/// Threshold for compressing corpus entries. +/// 4KiB is usually the minimum file size on popular file systems. +const GZIP_THRESHOLD: usize = 4 * 1024; + +/// Possible mutation strategies to apply on a call sequence. +#[derive(Debug, Clone)] +enum MutationType { + /// Splice original call sequence. + Splice, + /// Repeat selected call several times. + Repeat, + /// Interleave calls from two random call sequences. + Interleave, + /// Replace prefix of the original call sequence with new calls. + Prefix, + /// Replace suffix of the original call sequence with new calls. + Suffix, + /// ABI mutate random args of selected call in sequence. + Abi, +} + +/// Persisted optimization state: the best value found and the sequence that produced it. +#[derive(Clone, Serialize, Deserialize)] +struct OptimizationState { + best_value: I256, + best_sequence: Vec, +} + +/// Holds Corpus information. +#[derive(Clone, Serialize)] +struct CorpusEntry { + // Unique corpus identifier. + uuid: Uuid, + // Total mutations of corpus as primary source. + total_mutations: usize, + // New coverage found as a result of mutating this corpus. + new_finds_produced: usize, + // Corpus call sequence. + #[serde(skip_serializing)] + tx_seq: Vec, + // Whether this corpus is favored, i.e. producing new finds more often than + // `FAVORABILITY_THRESHOLD`. + is_favored: bool, + /// Timestamp of when this entry was written to disk in seconds. + #[serde(skip_serializing)] + timestamp: u64, +} + +impl CorpusEntry { + /// Creates a corpus entry with a new UUID. + pub fn new(tx_seq: Vec) -> Self { + Self::new_with(tx_seq, Uuid::new_v4()) + } + + /// Creates a corpus entry with a path. + /// The UUID is parsed from the file name, otherwise a new UUID is generated. + pub fn new_existing(tx_seq: Vec, path: PathBuf) -> Result { + let Some(name) = path.file_name().and_then(|s| s.to_str()) else { + eyre::bail!("invalid corpus file path: {path:?}"); + }; + let uuid = parse_corpus_filename(name)?.0; + Ok(Self::new_with(tx_seq, uuid)) + } + + /// Creates a corpus entry with the given UUID. + pub fn new_with(tx_seq: Vec, uuid: Uuid) -> Self { + Self { + uuid, + total_mutations: 0, + new_finds_produced: 0, + tx_seq, + is_favored: false, + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time went backwards") + .as_secs(), + } + } + + fn write_to_disk_in(&self, dir: &Path, can_gzip: bool) -> foundry_common::fs::Result<()> { + let file_name = self.file_name(can_gzip); + let path = dir.join(file_name); + if self.should_gzip(can_gzip) { + foundry_common::fs::write_json_gzip_file(&path, &self.tx_seq) + } else { + foundry_common::fs::write_json_file(&path, &self.tx_seq) + } + } + + fn file_name(&self, can_gzip: bool) -> String { + let ext = if self.should_gzip(can_gzip) { ".json.gz" } else { ".json" }; + format!("{}-{}{ext}", self.uuid, self.timestamp) + } + + fn should_gzip(&self, can_gzip: bool) -> bool { + if !can_gzip { + return false; + } + let size: usize = self.tx_seq.iter().map(|tx| tx.estimate_serialized_size()).sum(); + size > GZIP_THRESHOLD + } +} + +#[derive(Default)] +pub(crate) struct GlobalCorpusMetrics { + // Number of edges seen during the invariant run. + cumulative_edges_seen: AtomicUsize, + // Number of features (new hitcount bin of previously hit edge) seen during the invariant run. + cumulative_features_seen: AtomicUsize, + // Number of corpus entries. + corpus_count: AtomicUsize, + // Number of corpus entries that are favored. + favored_items: AtomicUsize, +} + +impl fmt::Display for GlobalCorpusMetrics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.load().fmt(f) + } +} + +impl GlobalCorpusMetrics { + pub(crate) fn load(&self) -> CorpusMetrics { + CorpusMetrics { + cumulative_edges_seen: self.cumulative_edges_seen.load(Ordering::Relaxed), + cumulative_features_seen: self.cumulative_features_seen.load(Ordering::Relaxed), + corpus_count: self.corpus_count.load(Ordering::Relaxed), + favored_items: self.favored_items.load(Ordering::Relaxed), + } + } +} + +#[derive(Serialize, Default, Clone)] +pub(crate) struct CorpusMetrics { + // Number of edges seen during the invariant run. + cumulative_edges_seen: usize, + // Number of features (new hitcount bin of previously hit edge) seen during the invariant run. + cumulative_features_seen: usize, + // Number of corpus entries. + corpus_count: usize, + // Number of corpus entries that are favored. + favored_items: usize, +} + +impl fmt::Display for CorpusMetrics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f)?; + writeln!(f, " - cumulative edges seen: {}", self.cumulative_edges_seen)?; + writeln!(f, " - cumulative features seen: {}", self.cumulative_features_seen)?; + writeln!(f, " - corpus count: {}", self.corpus_count)?; + write!(f, " - favored items: {}", self.favored_items)?; + Ok(()) + } +} + +impl CorpusMetrics { + /// Records number of new edges or features explored during the campaign. + pub const fn update_seen(&mut self, is_edge: bool) { + if is_edge { + self.cumulative_edges_seen += 1; + } else { + self.cumulative_features_seen += 1; + } + } + + /// Updates campaign favored items. + pub const fn update_favored(&mut self, is_favored: bool, corpus_favored: bool) { + if is_favored && !corpus_favored { + self.favored_items += 1; + } else if !is_favored && corpus_favored { + self.favored_items -= 1; + } + } +} + +/// Per-worker corpus manager. +pub struct WorkerCorpus { + /// Worker Id + id: usize, + /// In-memory corpus entries populated from the persisted files and + /// runs administered by this worker. + in_memory_corpus: Vec, + /// History of binned hitcount of edges seen during fuzzing + history_map: Vec, + /// History of binned hitcount of sancov (native Rust) edges seen during fuzzing + sancov_history_map: Vec, + /// Number of failed replays from initial corpus + pub(crate) failed_replays: usize, + /// Worker Metrics + pub(crate) metrics: CorpusMetrics, + /// Fuzzed calls generator. + tx_generator: BoxedStrategy, + /// Call sequence mutation strategy type generator used by stateful fuzzing. + mutation_generator: BoxedStrategy, + /// Identifier of current mutated entry for this worker. + current_mutated: Option, + /// Config + config: Arc, + /// Indices of new entries added to [`WorkerCorpus::in_memory_corpus`] since last sync. + new_entry_indices: Vec, + /// Last sync timestamp in seconds. + last_sync_timestamp: u64, + /// Worker Dir + /// corpus_dir/worker1/ + worker_dir: Option, + /// Metrics at last sync - used to calculate deltas while syncing with global metrics + last_sync_metrics: CorpusMetrics, + /// Optimization mode: the best value found so far (loaded from disk or discovered in-run). + optimization_best_value: Option, + /// Optimization mode: the call sequence that produced the best value. + optimization_best_sequence: Vec, +} + +impl WorkerCorpus { + pub fn new( + id: usize, + config: FuzzCorpusConfig, + tx_generator: BoxedStrategy, + // Only required by master worker (id = 0) to replay existing corpus. + executor: Option<&Executor>, + fuzzed_function: Option<&Function>, + fuzzed_contracts: Option<&FuzzRunIdentifiedContracts>, + ) -> Result { + let mutation_generator = prop_oneof![ + Just(MutationType::Splice), + Just(MutationType::Repeat), + Just(MutationType::Interleave), + Just(MutationType::Prefix), + Just(MutationType::Suffix), + Just(MutationType::Abi), + ] + .boxed(); + + let worker_dir = config.corpus_dir.as_ref().map(|corpus_dir| { + let worker_dir = corpus_dir.join(format!("{WORKER}{id}")); + let worker_corpus = worker_dir.join(CORPUS_DIR); + let sync_dir = worker_dir.join(SYNC_DIR); + + // Create the necessary directories for the worker. + let _ = foundry_common::fs::create_dir_all(&worker_corpus); + let _ = foundry_common::fs::create_dir_all(&sync_dir); + + worker_dir + }); + + let mut in_memory_corpus = vec![]; + let mut history_map = vec![0u8; COVERAGE_MAP_SIZE]; + let mut sancov_history_map = vec![0u8; COVERAGE_MAP_SIZE]; + let mut metrics = CorpusMetrics::default(); + let mut failed_replays = 0; + let mut optimization_best_value = None; + let mut optimization_best_sequence = vec![]; + + if id == 0 + && let Some(corpus_dir) = &config.corpus_dir + { + // Load persisted optimization state if it exists. + let opt_path = corpus_dir.join(OPTIMIZATION_BEST_FILE); + if opt_path.is_file() { + match foundry_common::fs::read_json_file::(&opt_path) { + Ok(state) => { + debug!( + target: "corpus", + "loaded optimization best value {} with sequence len {}", + state.best_value, + state.best_sequence.len() + ); + optimization_best_value = Some(state.best_value); + optimization_best_sequence = state.best_sequence; + } + Err(err) => { + let _ = sh_warn!( + "failed to load optimization state from {}: {err}; starting without persisted optimization seed", + opt_path.display() + ); + } + } + } + + // Seed in-memory corpus with the persisted optimization best sequence + // so the mutation engine can build on it in future runs. + if !optimization_best_sequence.is_empty() { + in_memory_corpus.push(CorpusEntry::new(optimization_best_sequence.clone())); + metrics.corpus_count += 1; + } + + // Master worker loads the initial corpus, if it exists. + // Then, [distribute]s it to workers. + let executor = executor.expect("Executor required for master worker"); + 'corpus_replay: for entry in read_corpus_dir(corpus_dir) { + let tx_seq = entry.read_tx_seq()?; + if tx_seq.is_empty() { + continue; + } + // Warm up history map from loaded sequences. + let mut executor = executor.clone(); + for tx in &tx_seq { + if Self::can_replay_tx(tx, fuzzed_function, fuzzed_contracts) { + let mut call_result = execute_tx(&mut executor, tx)?; + let (new_coverage, is_edge) = call_result + .merge_all_coverage(&mut history_map, &mut sancov_history_map); + if new_coverage { + metrics.update_seen(is_edge); + } + + // Commit only when running invariant / stateful tests. + if fuzzed_contracts.is_some() { + executor.commit(&mut call_result); + } + } else { + failed_replays += 1; + + // If the only input for fuzzed function cannot be replied, then move to + // next one without adding it in memory. + if fuzzed_function.is_some() { + continue 'corpus_replay; + } + } + } + + metrics.corpus_count += 1; + + debug!( + target: "corpus", + "load sequence with len {} from corpus file {}", + tx_seq.len(), + entry.path.display() + ); + + // Populate in memory corpus with the sequence from corpus file. + in_memory_corpus.push(CorpusEntry::new_with(tx_seq, entry.uuid)); + } + } + + Ok(Self { + id, + in_memory_corpus, + history_map, + sancov_history_map, + failed_replays, + metrics, + tx_generator, + mutation_generator, + current_mutated: None, + config: config.into(), + new_entry_indices: Default::default(), + last_sync_timestamp: 0, + worker_dir, + last_sync_metrics: Default::default(), + optimization_best_value, + optimization_best_sequence, + }) + } + + /// Updates stats for the given call sequence, if new coverage produced. + /// Persists the call sequence (if corpus directory is configured and new coverage or + /// improved optimization value) and updates in-memory corpus. + #[instrument(skip_all)] + pub fn process_inputs( + &mut self, + inputs: &[BasicTxDetails], + new_coverage: bool, + optimization: Option<(I256, Vec)>, + ) { + let Some(worker_corpus) = &self.worker_dir else { + return; + }; + let worker_corpus = worker_corpus.join(CORPUS_DIR); + + // Check if this run improved the optimization value. + let improved_optimization = optimization.as_ref().is_some_and(|(value, _)| { + self.optimization_best_value.is_none_or(|best| *value > best) + }); + + // Update stats of current mutated primary corpus. + if let Some(uuid) = &self.current_mutated { + let should_credit = new_coverage || improved_optimization; + if let Some(corpus) = + self.in_memory_corpus.iter_mut().find(|corpus| corpus.uuid == *uuid) + { + corpus.total_mutations += 1; + if should_credit { + corpus.new_finds_produced += 1 + } + let is_favored = (corpus.new_finds_produced as f64 / corpus.total_mutations as f64) + > FAVORABILITY_THRESHOLD; + self.metrics.update_favored(is_favored, corpus.is_favored); + corpus.is_favored = is_favored; + + trace!( + target: "corpus", + "updated corpus {}, total mutations: {}, new finds: {}", + corpus.uuid, corpus.total_mutations, corpus.new_finds_produced + ); + } + + self.current_mutated = None; + } + + // Persist optimization state to disk if improved. + if let Some((value, best_seq)) = optimization + && improved_optimization + { + self.optimization_best_value = Some(value); + self.optimization_best_sequence = best_seq; + self.persist_optimization_state(); + } + + // Collect inputs if current run produced new coverage or improved optimization. + if !new_coverage && !improved_optimization { + return; + } + + // When the run is interesting only because of optimization (no new coverage), + // add the best prefix to the corpus instead of the full run — the prefix is + // the sequence that actually achieved the best value. + assert!(!inputs.is_empty()); + let corpus_inputs = if improved_optimization && !new_coverage { + self.optimization_best_sequence.clone() + } else { + inputs.to_vec() + }; + let corpus = CorpusEntry::new(corpus_inputs); + + // Persist to disk. + let write_result = corpus.write_to_disk_in(&worker_corpus, self.config.corpus_gzip); + if let Err(err) = write_result { + debug!(target: "corpus", %err, "failed to record call sequence {:?}", corpus.tx_seq); + } else { + trace!( + target: "corpus", + "persisted {} inputs for new coverage for {} corpus", + corpus.tx_seq.len(), + corpus.uuid, + ); + } + + // Track in-memory corpus changes to update MasterWorker on sync. + let new_index = self.in_memory_corpus.len(); + self.new_entry_indices.push(new_index); + + // This includes reverting txs in the corpus and `can_continue` removes + // them. We want this as it is new coverage and may help reach the other branch. + self.metrics.corpus_count += 1; + self.in_memory_corpus.push(corpus); + } + + /// Returns the previously persisted optimization best value and sequence (if any). + pub fn optimization_initial_state(&self) -> (Option, Vec) { + (self.optimization_best_value, self.optimization_best_sequence.clone()) + } + + /// Persists the current optimization best value and sequence to disk. + fn persist_optimization_state(&self) { + let Some(value) = self.optimization_best_value else { + return; + }; + let Some(corpus_dir) = &self.config.corpus_dir else { + return; + }; + let state = OptimizationState { + best_value: value, + best_sequence: self.optimization_best_sequence.clone(), + }; + let path = corpus_dir.join(OPTIMIZATION_BEST_FILE); + if let Err(err) = foundry_common::fs::write_json_file(&path, &state) { + debug!(target: "corpus", %err, "failed to persist optimization state"); + } else { + trace!( + target: "corpus", + "persisted optimization best value {} with sequence len {}", + value, + self.optimization_best_sequence.len() + ); + } + } + + /// Collects EVM and sancov coverage from call result and updates metrics. + pub fn merge_edge_coverage( + &mut self, + call_result: &mut RawCallResult, + ) -> bool { + if !self.config.collect_edge_coverage() { + return false; + } + + let (new_coverage, is_edge) = + call_result.merge_all_coverage(&mut self.history_map, &mut self.sancov_history_map); + if new_coverage { + self.metrics.update_seen(is_edge); + } + new_coverage + } + + /// Generates new call sequence from in memory corpus. Evicts oldest corpus mutated more than + /// configured max mutations value. Used by invariant test campaigns. + #[instrument(skip_all)] + pub fn new_inputs( + &mut self, + test_runner: &mut TestRunner, + fuzz_state: &EvmFuzzState, + targeted_contracts: &FuzzRunIdentifiedContracts, + ) -> Result> { + let mut new_seq = vec![]; + + // Early return with first_input only if corpus dir / coverage guided fuzzing not + // configured. + if !self.config.is_coverage_guided() { + new_seq.push(self.new_tx(test_runner)?); + return Ok(new_seq); + }; + + if !self.in_memory_corpus.is_empty() { + self.evict_oldest_corpus()?; + + let mutation_type = self + .mutation_generator + .new_tree(test_runner) + .map_err(|err| eyre!("Could not generate mutation type {err}"))? + .current(); + + let rng = test_runner.rng(); + let corpus_len = self.in_memory_corpus.len(); + let primary = &self.in_memory_corpus[rng.random_range(0..corpus_len)]; + let secondary = &self.in_memory_corpus[rng.random_range(0..corpus_len)]; + + match mutation_type { + MutationType::Splice => { + trace!(target: "corpus", "splice {} and {}", primary.uuid, secondary.uuid); + + self.current_mutated = Some(primary.uuid); + + let start1 = rng.random_range(0..primary.tx_seq.len()); + let end1 = rng.random_range(start1..primary.tx_seq.len()); + + let start2 = rng.random_range(0..secondary.tx_seq.len()); + let end2 = rng.random_range(start2..secondary.tx_seq.len()); + + for tx in primary.tx_seq.iter().take(end1).skip(start1) { + new_seq.push(tx.clone()); + } + for tx in secondary.tx_seq.iter().take(end2).skip(start2) { + new_seq.push(tx.clone()); + } + } + MutationType::Repeat => { + let corpus = if rng.random::() { primary } else { secondary }; + trace!(target: "corpus", "repeat {}", corpus.uuid); + + self.current_mutated = Some(corpus.uuid); + + new_seq = corpus.tx_seq.clone(); + let start = rng.random_range(0..corpus.tx_seq.len()); + let end = rng.random_range(start..corpus.tx_seq.len()); + let item_idx = rng.random_range(0..corpus.tx_seq.len()); + let repeated = vec![new_seq[item_idx].clone(); end - start]; + new_seq.splice(start..end, repeated); + } + MutationType::Interleave => { + trace!(target: "corpus", "interleave {} with {}", primary.uuid, secondary.uuid); + + self.current_mutated = Some(primary.uuid); + + for (tx1, tx2) in primary.tx_seq.iter().zip(secondary.tx_seq.iter()) { + // TODO: chunks? + let tx = if rng.random::() { tx1.clone() } else { tx2.clone() }; + new_seq.push(tx); + } + } + MutationType::Prefix => { + let corpus = if rng.random::() { primary } else { secondary }; + trace!(target: "corpus", "overwrite prefix of {}", corpus.uuid); + + self.current_mutated = Some(corpus.uuid); + + new_seq = corpus.tx_seq.clone(); + for i in 0..rng.random_range(0..=new_seq.len()) { + new_seq[i] = self.new_tx(test_runner)?; + } + } + MutationType::Suffix => { + let corpus = if rng.random::() { primary } else { secondary }; + trace!(target: "corpus", "overwrite suffix of {}", corpus.uuid); + + self.current_mutated = Some(corpus.uuid); + + new_seq = corpus.tx_seq.clone(); + for i in new_seq.len() - rng.random_range(0..new_seq.len())..corpus.tx_seq.len() + { + new_seq[i] = self.new_tx(test_runner)?; + } + } + MutationType::Abi => { + let targets = targeted_contracts.targets.lock(); + let corpus = if rng.random::() { primary } else { secondary }; + trace!(target: "corpus", "ABI mutate args of {}", corpus.uuid); + + self.current_mutated = Some(corpus.uuid); + + new_seq = corpus.tx_seq.clone(); + + let idx = rng.random_range(0..new_seq.len()); + let tx = new_seq.get_mut(idx).unwrap(); + if let (_, Some(function)) = targets.fuzzed_artifacts(tx) { + // TODO: add call_value to call details and mutate it as well as sender some + // of the time. + if !function.inputs.is_empty() { + self.abi_mutate(tx, function, test_runner, fuzz_state)?; + } + } + } + } + } + + // Make sure the new sequence contains at least one tx to start fuzzing from. + if new_seq.is_empty() { + new_seq.push(self.new_tx(test_runner)?); + } + trace!(target: "corpus", "new sequence of {} calls generated", new_seq.len()); + + Ok(new_seq) + } + + /// Generates a new input from the shared in memory corpus. Evicts oldest corpus mutated more + /// than configured max mutations value. Used by fuzz (stateless) test campaigns. + #[instrument(skip_all)] + pub fn new_input( + &mut self, + test_runner: &mut TestRunner, + fuzz_state: &EvmFuzzState, + function: &Function, + ) -> Result { + // Early return if not running with coverage guided fuzzing. + if !self.config.is_coverage_guided() { + return Ok(self.new_tx(test_runner)?.call_details.calldata); + } + + self.evict_oldest_corpus()?; + + let tx = if self.in_memory_corpus.is_empty() { + self.new_tx(test_runner)? + } else { + let corpus = &self.in_memory_corpus + [test_runner.rng().random_range(0..self.in_memory_corpus.len())]; + self.current_mutated = Some(corpus.uuid); + let mut tx = corpus.tx_seq.first().unwrap().clone(); + self.abi_mutate(&mut tx, function, test_runner, fuzz_state)?; + tx + }; + + Ok(tx.call_details.calldata) + } + + /// Generates single call from corpus strategy. + pub fn new_tx(&self, test_runner: &mut TestRunner) -> Result { + Ok(self + .tx_generator + .new_tree(test_runner) + .map_err(|_| eyre!("Could not generate case"))? + .current()) + } + + /// Returns the next call to be used in call sequence. + /// If coverage guided fuzzing is not configured or if previous input was discarded then this is + /// a new tx from strategy. + /// If running with coverage guided fuzzing it returns a new call only when sequence + /// does not have enough entries, or randomly. Otherwise, returns the next call from initial + /// sequence. + pub fn generate_next_input( + &mut self, + test_runner: &mut TestRunner, + sequence: &[BasicTxDetails], + discarded: bool, + depth: usize, + ) -> Result { + // Early return with new input if corpus dir / coverage guided fuzzing not configured or if + // call was discarded. + if self.config.corpus_dir.is_none() || discarded { + return self.new_tx(test_runner); + } + + // When running with coverage guided fuzzing enabled then generate new sequence if initial + // sequence's length is less than depth or randomly, to occasionally intermix new txs. + if depth > sequence.len().saturating_sub(1) || test_runner.rng().random_ratio(1, 10) { + return self.new_tx(test_runner); + } + + // Continue with the next call initial sequence. + Ok(sequence[depth].clone()) + } + + /// Flush the oldest corpus mutated more than configured max mutations unless they are + /// favored. + fn evict_oldest_corpus(&mut self) -> Result<()> { + if self.in_memory_corpus.len() > self.config.corpus_min_size.max(1) + && let Some(index) = self.in_memory_corpus.iter().position(|corpus| { + corpus.total_mutations > self.config.corpus_min_mutations && !corpus.is_favored + }) + { + let corpus = &self.in_memory_corpus[index]; + + trace!(target: "corpus", corpus=%serde_json::to_string(&corpus).unwrap(), "evict corpus"); + + // Remove corpus from memory. + self.in_memory_corpus.remove(index); + + // Adjust the tracked indices. + self.new_entry_indices.retain_mut(|i| { + if *i > index { + *i -= 1; // Shift indices down. + true // Keep this index. + } else { + *i != index // Remove if it's the deleted index, keep otherwise. + } + }); + } + Ok(()) + } + + /// Mutates calldata of provided tx by abi decoding current values and randomly selecting the + /// inputs to change. + fn abi_mutate( + &self, + tx: &mut BasicTxDetails, + function: &Function, + test_runner: &mut TestRunner, + fuzz_state: &EvmFuzzState, + ) -> Result<()> { + // let rng = test_runner.rng(); + let mut arg_mutation_rounds = + test_runner.rng().random_range(0..=function.inputs.len()).max(1); + let round_arg_idx: Vec = if function.inputs.len() <= 1 { + vec![0] + } else { + (0..arg_mutation_rounds) + .map(|_| test_runner.rng().random_range(0..function.inputs.len())) + .collect() + }; + let mut prev_inputs = function + .abi_decode_input(&tx.call_details.calldata[4..]) + .map_err(|err| eyre!("failed to load previous inputs: {err}"))?; + + while arg_mutation_rounds > 0 { + let idx = round_arg_idx[arg_mutation_rounds - 1]; + prev_inputs[idx] = mutate_param_value( + &function + .inputs + .get(idx) + .expect("Could not get input to mutate") + .selector_type() + .parse()?, + prev_inputs[idx].clone(), + test_runner, + fuzz_state, + ); + arg_mutation_rounds -= 1; + } + + tx.call_details.calldata = + function.abi_encode_input(&prev_inputs).map_err(|e| eyre!(e.to_string()))?.into(); + Ok(()) + } + + // Sync Methods. + + /// Imports the new corpus entries from the `sync` directory. + /// These contain tx sequences which are replayed and used to update the history map. + fn load_sync_corpus(&self) -> Result)>> { + let Some(worker_dir) = &self.worker_dir else { + return Ok(vec![]); + }; + + let sync_dir = worker_dir.join(SYNC_DIR); + if !sync_dir.is_dir() { + return Ok(vec![]); + } + + let mut imports = vec![]; + for entry in read_corpus_dir(&sync_dir) { + if entry.timestamp <= self.last_sync_timestamp { + continue; + } + let tx_seq = entry.read_tx_seq()?; + if tx_seq.is_empty() { + warn!(target: "corpus", "skipping empty corpus entry: {}", entry.path.display()); + continue; + } + imports.push((entry, tx_seq)); + } + + if !imports.is_empty() { + debug!(target: "corpus", "imported {} new corpus entries", imports.len()); + } + + Ok(imports) + } + + /// Syncs and calibrates the in memory corpus and updates the history_map if new coverage is + /// found from the corpus findings of other workers. + #[instrument(skip_all)] + fn calibrate( + &mut self, + executor: &Executor, + fuzzed_function: Option<&Function>, + fuzzed_contracts: Option<&FuzzRunIdentifiedContracts>, + ) -> Result<()> { + let Some(worker_dir) = &self.worker_dir else { + return Ok(()); + }; + let corpus_dir = worker_dir.join(CORPUS_DIR); + + let mut executor = executor.clone(); + for (entry, tx_seq) in self.load_sync_corpus()? { + let mut new_coverage_on_sync = false; + for tx in &tx_seq { + if !Self::can_replay_tx(tx, fuzzed_function, fuzzed_contracts) { + continue; + } + + let mut call_result = execute_tx(&mut executor, tx)?; + + // Check if this provides new coverage. + let (new_coverage, is_edge) = call_result + .merge_all_coverage(&mut self.history_map, &mut self.sancov_history_map); + + if new_coverage { + self.metrics.update_seen(is_edge); + new_coverage_on_sync = true; + } + + // Commit only for stateful tests. + if fuzzed_contracts.is_some() { + executor.commit(&mut call_result); + } + + trace!( + target: "corpus", + %new_coverage, + ?tx, + "replayed tx for syncing", + ); + } + + let sync_path = &entry.path; + if new_coverage_on_sync { + // Move file from sync/ to corpus/ directory. + let corpus_path = corpus_dir.join(sync_path.components().next_back().unwrap()); + if let Err(err) = std::fs::rename(sync_path, &corpus_path) { + debug!(target: "corpus", %err, "failed to move synced corpus from {sync_path:?} to {corpus_path:?} dir"); + continue; + } + + debug!( + target: "corpus", + name=%entry.name(), + "moved synced corpus to corpus dir", + ); + + let corpus_entry = CorpusEntry::new_existing(tx_seq.clone(), entry.path.clone())?; + self.in_memory_corpus.push(corpus_entry); + } else { + // Remove the file as it did not generate new coverage. + if let Err(err) = std::fs::remove_file(&entry.path) { + debug!(target: "corpus", %err, "failed to remove synced corpus from {sync_path:?}"); + continue; + } + trace!(target: "corpus", "removed synced corpus from {sync_path:?}"); + } + } + + Ok(()) + } + + /// Exports the new corpus entries to the master worker's sync dir. + #[instrument(skip_all)] + fn export_to_master(&self) -> Result<()> { + // Master doesn't export (it only receives from others). + assert_ne!(self.id, 0, "non-master only"); + + // Early return if no new entries or corpus dir not configured. + if self.new_entry_indices.is_empty() || self.worker_dir.is_none() { + return Ok(()); + } + + let worker_dir = self.worker_dir.as_ref().unwrap(); + let Some(master_sync_dir) = self + .config + .corpus_dir + .as_ref() + .map(|dir| dir.join(format!("{WORKER}0")).join(SYNC_DIR)) + else { + return Ok(()); + }; + + let mut exported = 0; + let corpus_dir = worker_dir.join(CORPUS_DIR); + + for &index in &self.new_entry_indices { + let Some(corpus) = self.in_memory_corpus.get(index) else { continue }; + let file_name = corpus.file_name(self.config.corpus_gzip); + let file_path = corpus_dir.join(&file_name); + let sync_path = master_sync_dir.join(&file_name); + if let Err(err) = std::fs::hard_link(&file_path, &sync_path) { + debug!(target: "corpus", %err, "failed to export corpus {}", corpus.uuid); + continue; + } + exported += 1; + } + + debug!(target: "corpus", "exported {exported} new corpus entries"); + + Ok(()) + } + + /// Exports the global corpus to the `sync/` directories of all the non-master workers. + #[instrument(skip_all)] + fn export_to_workers(&mut self, num_workers: usize) -> Result<()> { + assert_eq!(self.id, 0, "master worker only"); + if self.worker_dir.is_none() { + return Ok(()); + } + + let worker_dir = self.worker_dir.as_ref().unwrap(); + let master_corpus_dir = worker_dir.join(CORPUS_DIR); + let filtered_master_corpus = read_corpus_dir(&master_corpus_dir) + .filter(|entry| entry.timestamp > self.last_sync_timestamp) + .collect::>(); + let mut any_distributed = false; + for target_worker in 1..num_workers { + let target_dir = self + .config + .corpus_dir + .as_ref() + .unwrap() + .join(format!("{WORKER}{target_worker}")) + .join(SYNC_DIR); + + if !target_dir.is_dir() { + foundry_common::fs::create_dir_all(&target_dir)?; + } + + for entry in &filtered_master_corpus { + let name = entry.name(); + let sync_path = target_dir.join(name); + if let Err(err) = std::fs::hard_link(&entry.path, &sync_path) { + debug!(target: "corpus", %err, from=?entry.path, to=?sync_path, "failed to distribute corpus"); + continue; + } + any_distributed = true; + trace!(target: "corpus", %name, ?target_dir, "distributed corpus"); + } + } + + debug!(target: "corpus", %any_distributed, "distributed master corpus to all workers"); + + Ok(()) + } + + // TODO(dani): currently only master syncs metrics? + /// Syncs local metrics with global corpus metrics by calculating and applying deltas. + pub(crate) fn sync_metrics(&mut self, global_corpus_metrics: &GlobalCorpusMetrics) { + // Calculate delta metrics since last sync. + let edges_delta = self + .metrics + .cumulative_edges_seen + .saturating_sub(self.last_sync_metrics.cumulative_edges_seen); + let features_delta = self + .metrics + .cumulative_features_seen + .saturating_sub(self.last_sync_metrics.cumulative_features_seen); + // For corpus count and favored items, calculate deltas. + let corpus_count_delta = + self.metrics.corpus_count as isize - self.last_sync_metrics.corpus_count as isize; + let favored_delta = + self.metrics.favored_items as isize - self.last_sync_metrics.favored_items as isize; + + // Add delta values to global metrics. + + if edges_delta > 0 { + global_corpus_metrics.cumulative_edges_seen.fetch_add(edges_delta, Ordering::Relaxed); + } + if features_delta > 0 { + global_corpus_metrics + .cumulative_features_seen + .fetch_add(features_delta, Ordering::Relaxed); + } + + if corpus_count_delta > 0 { + global_corpus_metrics + .corpus_count + .fetch_add(corpus_count_delta as usize, Ordering::Relaxed); + } else if corpus_count_delta < 0 { + global_corpus_metrics + .corpus_count + .fetch_sub((-corpus_count_delta) as usize, Ordering::Relaxed); + } + + if favored_delta > 0 { + global_corpus_metrics + .favored_items + .fetch_add(favored_delta as usize, Ordering::Relaxed); + } else if favored_delta < 0 { + global_corpus_metrics + .favored_items + .fetch_sub((-favored_delta) as usize, Ordering::Relaxed); + } + + // Store current metrics as last sync metrics for next delta calculation. + self.last_sync_metrics = self.metrics.clone(); + } + + /// Syncs the workers in_memory_corpus and history_map with the findings from other workers. + #[instrument(skip_all)] + pub fn sync( + &mut self, + num_workers: usize, + executor: &Executor, + fuzzed_function: Option<&Function>, + fuzzed_contracts: Option<&FuzzRunIdentifiedContracts>, + global_corpus_metrics: &GlobalCorpusMetrics, + ) -> Result<()> { + trace!(target: "corpus", "syncing"); + + self.sync_metrics(global_corpus_metrics); + + self.calibrate(executor, fuzzed_function, fuzzed_contracts)?; + if self.id == 0 { + self.export_to_workers(num_workers)?; + } else { + self.export_to_master()?; + } + + let last_sync = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + self.last_sync_timestamp = last_sync; + + self.new_entry_indices.clear(); + + debug!(target: "corpus", last_sync, "synced"); + + Ok(()) + } + + /// Helper to check if a tx can be replayed. + fn can_replay_tx( + tx: &BasicTxDetails, + fuzzed_function: Option<&Function>, + fuzzed_contracts: Option<&FuzzRunIdentifiedContracts>, + ) -> bool { + fuzzed_contracts.is_some_and(|contracts| contracts.targets.lock().can_replay(tx)) + || fuzzed_function.is_some_and(|function| { + tx.call_details + .calldata + .get(..4) + .is_some_and(|selector| function.selector() == selector) + }) + } +} + +fn read_corpus_dir(path: &Path) -> impl Iterator { + let dir = match std::fs::read_dir(path) { + Ok(dir) => dir, + Err(err) => { + debug!(%err, ?path, "failed to read corpus directory"); + return vec![].into_iter(); + } + }; + dir.filter_map(|res| match res { + Ok(entry) => { + let path = entry.path(); + if !path.is_file() { + return None; + } + let name = if path.is_file() + && let Some(name) = path.file_name() + && let Some(name) = name.to_str() + { + name + } else { + return None; + }; + + if let Ok((uuid, timestamp)) = parse_corpus_filename(name) { + Some(CorpusDirEntry { path, uuid, timestamp }) + } else { + debug!(target: "corpus", ?path, "failed to parse corpus filename"); + None + } + } + Err(err) => { + debug!(%err, "failed to read corpus directory entry"); + None + } + }) + .collect::>() + .into_iter() +} + +struct CorpusDirEntry { + path: PathBuf, + uuid: Uuid, + timestamp: u64, +} + +impl CorpusDirEntry { + fn name(&self) -> &str { + self.path.file_name().unwrap().to_str().unwrap() + } + + fn read_tx_seq(&self) -> foundry_common::fs::Result> { + let path = &self.path; + if path.extension() == Some("gz".as_ref()) { + foundry_common::fs::read_json_gzip_file(path) + } else { + foundry_common::fs::read_json_file(path) + } + } +} + +/// Parses the corpus filename and returns the uuid and timestamp associated with it. +fn parse_corpus_filename(name: &str) -> Result<(Uuid, u64)> { + let name = name.trim_end_matches(".gz").trim_end_matches(".json"); + + let (uuid_str, timestamp_str) = + name.rsplit_once('-').ok_or_else(|| eyre!("invalid corpus filename format: {name}"))?; + + let uuid = Uuid::parse_str(uuid_str)?; + let timestamp = timestamp_str.parse()?; + + Ok((uuid, timestamp)) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::Address; + use std::fs; + + fn basic_tx() -> BasicTxDetails { + BasicTxDetails { + warp: None, + roll: None, + sender: Address::ZERO, + call_details: foundry_evm_fuzz::CallDetails { + target: Address::ZERO, + calldata: Bytes::new(), + }, + } + } + + fn temp_corpus_dir() -> PathBuf { + let dir = std::env::temp_dir().join(format!("foundry-corpus-tests-{}", Uuid::new_v4())); + let _ = fs::create_dir_all(&dir); + dir + } + + fn new_manager_with_single_corpus() -> (WorkerCorpus, Uuid) { + let tx_gen = Just(basic_tx()).boxed(); + let config = FuzzCorpusConfig { + corpus_dir: Some(temp_corpus_dir()), + corpus_gzip: false, + corpus_min_mutations: 0, + corpus_min_size: 0, + ..Default::default() + }; + + let tx_seq = vec![basic_tx()]; + let corpus = CorpusEntry::new(tx_seq); + let seed_uuid = corpus.uuid; + + // Create corpus root dir and worker subdirectory. + let corpus_root = config.corpus_dir.clone().unwrap(); + let worker_subdir = corpus_root.join("worker0"); + let _ = fs::create_dir_all(&worker_subdir); + + let manager = WorkerCorpus { + id: 0, + tx_generator: tx_gen, + mutation_generator: Just(MutationType::Repeat).boxed(), + config: config.into(), + in_memory_corpus: vec![corpus], + current_mutated: Some(seed_uuid), + failed_replays: 0, + history_map: vec![0u8; COVERAGE_MAP_SIZE], + sancov_history_map: vec![0u8; COVERAGE_MAP_SIZE], + metrics: CorpusMetrics::default(), + new_entry_indices: Default::default(), + last_sync_timestamp: 0, + worker_dir: Some(corpus_root), + last_sync_metrics: CorpusMetrics::default(), + optimization_best_value: None, + optimization_best_sequence: vec![], + }; + + (manager, seed_uuid) + } + + #[test] + fn favored_sets_true_and_metrics_increment_when_ratio_gt_threshold() { + let (mut manager, uuid) = new_manager_with_single_corpus(); + let corpus = manager.in_memory_corpus.iter_mut().find(|c| c.uuid == uuid).unwrap(); + corpus.total_mutations = 4; + corpus.new_finds_produced = 2; // ratio currently 0.5 if both increment → 3/5 = 0.6 > 0.3. + corpus.is_favored = false; + + // Ensure metrics start at 0. + assert_eq!(manager.metrics.favored_items, 0); + + // Mark this as the currently mutated corpus and process a run with new coverage. + manager.current_mutated = Some(uuid); + manager.process_inputs(&[basic_tx()], true, None); + + let corpus = manager.in_memory_corpus.iter().find(|c| c.uuid == uuid).unwrap(); + assert!(corpus.is_favored, "expected favored to be true when ratio > threshold"); + assert_eq!( + manager.metrics.favored_items, 1, + "favored_items should increment on false→true" + ); + } + + #[test] + fn favored_sets_false_and_metrics_decrement_when_ratio_lt_threshold() { + let (mut manager, uuid) = new_manager_with_single_corpus(); + let corpus = manager.in_memory_corpus.iter_mut().find(|c| c.uuid == uuid).unwrap(); + corpus.total_mutations = 9; + corpus.new_finds_produced = 3; // 3/9 = 0.333.. > 0.3; after +1: 3/10 = 0.3 => not favored. + corpus.is_favored = true; // Start as favored. + + manager.metrics.favored_items = 1; + + // Next run does NOT produce coverage → only total_mutations increments, ratio drops. + manager.current_mutated = Some(uuid); + manager.process_inputs(&[basic_tx()], false, None); + + let corpus = manager.in_memory_corpus.iter().find(|c| c.uuid == uuid).unwrap(); + assert!(!corpus.is_favored, "expected favored to be false when ratio < threshold"); + assert_eq!( + manager.metrics.favored_items, 0, + "favored_items should decrement on true→false" + ); + } + + #[test] + fn favored_is_false_on_ratio_equal_threshold() { + let (mut manager, uuid) = new_manager_with_single_corpus(); + let corpus = manager.in_memory_corpus.iter_mut().find(|c| c.uuid == uuid).unwrap(); + // After this call with new_coverage=true, totals become 10 and 3 → 0.3. + corpus.total_mutations = 9; + corpus.new_finds_produced = 2; + corpus.is_favored = false; + + manager.current_mutated = Some(uuid); + manager.process_inputs(&[basic_tx()], true, None); + + let corpus = manager.in_memory_corpus.iter().find(|c| c.uuid == uuid).unwrap(); + assert!( + !(corpus.is_favored), + "with strict '>' comparison, favored must be false when ratio == threshold" + ); + } + + #[test] + fn eviction_skips_favored_and_evicts_non_favored() { + // Manager with two corpora. + let tx_gen = Just(basic_tx()).boxed(); + let config = FuzzCorpusConfig { + corpus_dir: Some(temp_corpus_dir()), + corpus_min_mutations: 0, + corpus_min_size: 0, + ..Default::default() + }; + + let mut favored = CorpusEntry::new(vec![basic_tx()]); + favored.total_mutations = 2; + favored.is_favored = true; + + let mut non_favored = CorpusEntry::new(vec![basic_tx()]); + non_favored.total_mutations = 2; + non_favored.is_favored = false; + let non_favored_uuid = non_favored.uuid; + + let corpus_root = temp_corpus_dir(); + let worker_subdir = corpus_root.join("worker0"); + fs::create_dir_all(&worker_subdir).unwrap(); + + let mut manager = WorkerCorpus { + id: 0, + tx_generator: tx_gen, + mutation_generator: Just(MutationType::Repeat).boxed(), + config: config.into(), + in_memory_corpus: vec![favored, non_favored], + current_mutated: None, + failed_replays: 0, + history_map: vec![0u8; COVERAGE_MAP_SIZE], + sancov_history_map: vec![0u8; COVERAGE_MAP_SIZE], + metrics: CorpusMetrics::default(), + new_entry_indices: Default::default(), + last_sync_timestamp: 0, + worker_dir: Some(corpus_root), + last_sync_metrics: CorpusMetrics::default(), + optimization_best_value: None, + optimization_best_sequence: vec![], + }; + + // First eviction should remove the non-favored one. + manager.evict_oldest_corpus().unwrap(); + assert_eq!(manager.in_memory_corpus.len(), 1); + assert!(manager.in_memory_corpus.iter().all(|c| c.is_favored)); + + // Attempt eviction again: only favored remains → should not remove. + manager.evict_oldest_corpus().unwrap(); + assert_eq!(manager.in_memory_corpus.len(), 1, "favored corpus must not be evicted"); + + // Ensure the evicted one was the non-favored uuid. + assert!(manager.in_memory_corpus.iter().all(|c| c.uuid != non_favored_uuid)); + } +} diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index a64b072193587..1932a834ab397 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,48 +1,173 @@ -use crate::executors::{Executor, FuzzTestTimer, RawCallResult}; +use crate::executors::{ + DURATION_BETWEEN_METRICS_REPORT, EarlyExit, Executor, FuzzTestTimer, RawCallResult, + corpus::{GlobalCorpusMetrics, WorkerCorpus}, +}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; -use alloy_primitives::{map::HashMap, Address, Bytes, Log, U256}; +use alloy_primitives::{Address, Bytes, Log, U256, keccak256, map::HashMap}; use eyre::Result; -use foundry_common::evm::Breakpoints; +use foundry_common::sh_println; use foundry_config::FuzzConfig; use foundry_evm_core::{ - constants::{MAGIC_ASSUME, TEST_TIMEOUT}, + Breakpoints, + constants::{CHEATCODE_ADDRESS, MAGIC_ASSUME}, decode::{RevertDecoder, SkipReason}, + evm::FoundryEvmNetwork, }; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ - strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, - BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult, + BaseCounterExample, BasicTxDetails, CallDetails, CounterExample, FuzzCase, FuzzError, + FuzzFixtures, FuzzRunMetadata, FuzzTestResult, + strategies::{EvmFuzzState, fuzz_calldata, fuzz_calldata_from_state}, }; use foundry_evm_traces::SparsedTraceArena; use indicatif::ProgressBar; -use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use std::{cell::RefCell, collections::BTreeMap}; +use proptest::{ + strategy::Strategy, + test_runner::{RngAlgorithm, TestCaseError, TestRng, TestRunner}, +}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use serde_json::json; +use std::{ + sync::{ + Arc, OnceLock, + atomic::{AtomicU32, Ordering}, + }, + time::{Instant, SystemTime, UNIX_EPOCH}, +}; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; -/// Contains data collected during fuzz test runs. -#[derive(Default)] -pub struct FuzzTestData { - // Stores the first fuzz case. - pub first_case: Option, - // Stored gas usage per fuzz case. - pub gas_by_case: Vec<(u64, u64)>, - // Stores the result and calldata of the last failed call, if any. - pub counterexample: (Bytes, RawCallResult), - // Stores up to `max_traces_to_collect` traces. - pub traces: Vec, - // Stores breakpoints for the last fuzz case. - pub breakpoints: Option, - // Stores coverage information for all fuzz cases. - pub coverage: Option, - // Stores logs for all fuzz cases - pub logs: Vec, - // Stores gas snapshots for all fuzz cases - pub gas_snapshots: BTreeMap>, - // Deprecated cheatcodes mapped to their replacements. - pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, +/// Corpus syncs across workers every `SYNC_INTERVAL` runs. +const SYNC_INTERVAL: u32 = 1000; + +/// Minimum number of runs per worker. +/// This is mainly to reduce the overall number of rayon jobs. +const MIN_RUNS_PER_WORKER: u32 = 64; + +struct WorkerState { + /// Worker identifier + id: usize, + /// First fuzz case this worker encountered (with global run number) + first_case: Option<(u32, FuzzCase)>, + /// Gas usage for all cases this worker ran + gas_by_case: Vec<(u64, u64)>, + /// Counterexample if this worker found one + counterexample: (Bytes, RawCallResult), + /// Traces collected by this worker + /// + /// Stores up to `max_traces_to_collect` which is `config.gas_report_samples / num_workers` + traces: Vec, + /// Last breakpoints from this worker + breakpoints: Option, + /// Coverage collected by this worker + coverage: Option, + /// Logs from all cases this worker ran + logs: Vec, + /// Deprecated cheatcodes seen by this worker + deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, + /// Number of runs this worker completed + runs: u32, + /// Failure reason if this worker failed + failure: Option, + /// Fuzz run metadata that produced the failure. + failure_run: Option, + /// Last run timestamp in milliseconds + /// + /// Used to identify which worker ran last and collect its traces and call breakpoints + last_run_timestamp: u128, + /// Failed corpus replays + failed_corpus_replays: usize, +} + +impl WorkerState { + fn new(worker_id: usize) -> Self { + Self { + id: worker_id, + first_case: None, + gas_by_case: Vec::new(), + counterexample: (Bytes::new(), RawCallResult::default()), + traces: Vec::new(), + breakpoints: None, + coverage: None, + logs: Vec::new(), + deprecated_cheatcodes: HashMap::default(), + runs: 0, + failure: None, + failure_run: None, + last_run_timestamp: 0, + failed_corpus_replays: 0, + } + } +} + +/// Shared state for coordinating parallel fuzz workers +struct SharedFuzzState { + state: EvmFuzzState, + /// Total runs across workers + total_runs: Arc, + /// Found failure + /// + /// The worker that found the failure sets it's ID. + /// + /// This ID is then used to correctly extract the failure reason and counterexample. + failed_worker_id: OnceLock, + /// Total rejects across workers + total_rejects: Arc, + /// Fuzz timer + timer: FuzzTestTimer, + /// Global corpus metrics + global_corpus_metrics: GlobalCorpusMetrics, + + /// Global test suite early exit. + global_early_exit: EarlyExit, + /// Local fuzz early exit. + local_early_exit: EarlyExit, +} + +impl SharedFuzzState { + fn new(state: EvmFuzzState, timeout: Option, early_exit: EarlyExit) -> Self { + Self { + state, + total_runs: Arc::new(AtomicU32::new(0)), + failed_worker_id: OnceLock::new(), + total_rejects: Arc::new(AtomicU32::new(0)), + timer: FuzzTestTimer::new(timeout), + global_corpus_metrics: GlobalCorpusMetrics::default(), + global_early_exit: early_exit, + local_early_exit: EarlyExit::new(true), + } + } + + /// Increments the number of runs and returns the new value. + fn increment_runs(&self) -> u32 { + self.total_runs.fetch_add(1, Ordering::Relaxed) + 1 + } + + /// Increments and returns the new value of the number of rejected tests. + fn increment_rejects(&self) -> u32 { + self.total_rejects.fetch_add(1, Ordering::Relaxed) + 1 + } + + /// Returns `true` if the worker should continue running. + fn should_continue(&self) -> bool { + !(self.global_early_exit.should_stop() + || self.local_early_exit.should_stop() + || self.timer.is_timed_out()) + } + + /// Returns true if the worker was able to claim the failure, false if failure was set by + /// another worker + fn try_claim_failure(&self, worker_id: usize) -> bool { + let mut claimed = false; + let _ = self.failed_worker_id.get_or_init(|| { + claimed = true; + self.local_early_exit.record_failure(); + worker_id + }); + claimed + } } /// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`]. @@ -50,238 +175,568 @@ pub struct FuzzTestData { /// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with /// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the /// configuration which can be overridden via [environment variables](proptest::test_runner::Config) -pub struct FuzzedExecutor { - /// The EVM executor - executor: Executor, +pub struct FuzzedExecutor { + /// The EVM executor. + executor_f: Executor, /// The fuzzer runner: TestRunner, - /// The account that calls tests + /// The account that calls tests. sender: Address, - /// The fuzz configuration + /// The fuzz configuration. config: FuzzConfig, + /// The persisted counterexample to be replayed, if any. + persisted_failure: Option, + /// The number of parallel workers. + num_workers: usize, } -impl FuzzedExecutor { +impl FuzzedExecutor { /// Instantiates a fuzzed executor given a testrunner pub fn new( - executor: Executor, + executor: Executor, runner: TestRunner, sender: Address, config: FuzzConfig, + persisted_failure: Option, ) -> Self { - Self { executor, runner, sender, config } + let run_limit = if config.run.is_some() { 1 } else { config.runs }; + let max_workers = if run_limit == 0 { + 0 + } else if config.run.is_some() { + 1 + } else { + Ord::max(1, run_limit / MIN_RUNS_PER_WORKER) + }; + let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); + Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } } /// Fuzzes the provided function, assuming it is available at the contract at `address` /// If `should_fail` is set to `true`, then it will stop only when there's a success /// test case. /// - /// Returns a list of all the consumed gas and calldata of every fuzz case + /// Returns a list of all the consumed gas and calldata of every fuzz case. + #[allow(clippy::too_many_arguments)] pub fn fuzz( - &self, + &mut self, func: &Function, fuzz_fixtures: &FuzzFixtures, - deployed_libs: &[Address], + state: EvmFuzzState, address: Address, rd: &RevertDecoder, progress: Option<&ProgressBar>, - ) -> FuzzTestResult { - // Stores the fuzz test execution data. - let execution_data = RefCell::new(FuzzTestData::default()); - let state = self.build_fuzz_state(deployed_libs); - let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); - let strategy = proptest::prop_oneof![ - 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), - dictionary_weight => fuzz_calldata_from_state(func.clone(), &state), - ]; - // We want to collect at least one trace which will be displayed to user. - let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; - let show_logs = self.config.show_logs; + early_exit: &EarlyExit, + tokio_handle: &tokio::runtime::Handle, + ) -> Result { + let shared_state = SharedFuzzState::new(state, self.config.timeout, early_exit.clone()); - // Start timer for this fuzz test. - let timer = FuzzTestTimer::new(self.config.timeout); + let worker_ids = self.worker_ids(); + debug!(n = worker_ids.len(), "spawning workers"); + let workers = worker_ids + .into_par_iter() + .map(|worker_id| { + let _guard = tokio_handle.enter(); + let _guard = info_span!("fuzz_worker", id = worker_id).entered(); + let timer = Instant::now(); + let r = self.run_worker( + worker_id, + func, + fuzz_fixtures, + address, + rd, + &shared_state, + progress, + ); + debug!("finished in {:?}", timer.elapsed()); + r + }) + .collect::>>()?; - let run_result = self.runner.clone().run(&strategy, |calldata| { - // Check if the timeout has been reached. - if timer.is_timed_out() { - return Err(TestCaseError::fail(TEST_TIMEOUT)); - } + Ok(self.aggregate_results(workers, func, &shared_state)) + } - let fuzz_res = self.single_fuzz(address, calldata)?; + /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` + /// or a `CounterExampleOutcome` + fn single_fuzz( + &self, + executor: &Executor, + address: Address, + calldata: Bytes, + coverage_metrics: &mut WorkerCorpus, + ) -> Result, TestCaseError> { + let mut call = executor + .call_raw(self.sender, address, calldata.clone(), U256::ZERO) + .map_err(|e| TestCaseError::fail(e.to_string()))?; + let new_coverage = coverage_metrics.merge_edge_coverage(&mut call); + coverage_metrics.process_inputs( + &[BasicTxDetails { + warp: None, + roll: None, + sender: self.sender, + call_details: CallDetails { target: address, calldata: calldata.clone() }, + }], + new_coverage, + None, + ); - // If running with progress then increment current run. - if let Some(progress) = progress { - progress.inc(1); - }; + // Handle `vm.assume`. + if call.result.as_ref() == MAGIC_ASSUME { + return Err(TestCaseError::reject(FuzzError::AssumeReject)); + } - match fuzz_res { - FuzzOutcome::Case(case) => { - let mut data = execution_data.borrow_mut(); - data.gas_by_case.push((case.case.gas, case.case.stipend)); + let (breakpoints, deprecated_cheatcodes) = + call.cheatcodes.as_ref().map_or_else(Default::default, |cheats| { + (cheats.breakpoints.clone(), cheats.deprecated.clone()) + }); - if data.first_case.is_none() { - data.first_case.replace(case.case); - } + // Consider call success if test should not fail on reverts and reverter is not the + // cheatcode or test address. + let success = if !self.config.fail_on_revert + && call + .reverter + .is_some_and(|reverter| reverter != address && reverter != CHEATCODE_ADDRESS) + { + true + } else { + executor.is_raw_call_mut_success(address, &mut call, false) + }; - if let Some(call_traces) = case.traces { - if data.traces.len() == max_traces_to_collect { - data.traces.pop(); - } - data.traces.push(call_traces); - data.breakpoints.replace(case.breakpoints); - } + if success { + Ok(FuzzOutcome::Case(CaseOutcome { + case: FuzzCase { gas: call.gas_used, stipend: call.stipend }, + traces: call.traces, + coverage: call.line_coverage, + breakpoints, + logs: call.logs, + deprecated_cheatcodes, + })) + } else { + Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: call.exit_reason, + counterexample: (calldata, call), + breakpoints, + })) + } + } - if show_logs { - data.logs.extend(case.logs); - } + /// Aggregates the results from all workers + fn aggregate_results( + &self, + mut workers: Vec>, + func: &Function, + shared_state: &SharedFuzzState, + ) -> FuzzTestResult { + let mut result = FuzzTestResult::default(); + if workers.is_empty() { + result.success = true; + return result; + } - HitMaps::merge_opt(&mut data.coverage, case.coverage); + // Find first case and last run worker. Set `failed_corpus_replays`. + let mut first_case_candidate = None; + let mut last_run_worker = None; + for (i, worker) in workers.iter().enumerate() { + if let Some((run, ref case)) = worker.first_case + && first_case_candidate.as_ref().is_none_or(|&(r, _)| run < r) + { + first_case_candidate = Some((run, case.clone())); + } - data.deprecated_cheatcodes = case.deprecated_cheatcodes; + if last_run_worker.is_none_or(|(t, _)| worker.last_run_timestamp > t) { + last_run_worker = Some((worker.last_run_timestamp, i)); + } - Ok(()) - } - FuzzOutcome::CounterExample(CounterExampleOutcome { - exit_reason: status, - counterexample: outcome, - .. - }) => { - // We cannot use the calldata returned by the test runner in `TestError::Fail`, - // since that input represents the last run case, which may not correspond with - // our failure - when a fuzz case fails, proptest will try to run at least one - // more case to find a minimal failure case. - let reason = rd.maybe_decode(&outcome.1.result, Some(status)); - execution_data.borrow_mut().logs.extend(outcome.1.logs.clone()); - execution_data.borrow_mut().counterexample = outcome; - // HACK: we have to use an empty string here to denote `None`. - Err(TestCaseError::fail(reason.unwrap_or_default())) - } + // Only set replays from master which is responsible for replaying persisted corpus. + if worker.id == 0 { + result.failed_corpus_replays = worker.failed_corpus_replays; } - }); + } + result.first_case = first_case_candidate.map(|(_, case)| case).unwrap_or_default(); + let (_, last_run_worker_idx) = last_run_worker.expect("at least one worker"); - let fuzz_result = execution_data.into_inner(); - let (calldata, call) = fuzz_result.counterexample; + if let Some(&failed_worker_id) = shared_state.failed_worker_id.get() { + result.success = false; - let mut traces = fuzz_result.traces; - let (last_run_traces, last_run_breakpoints) = if run_result.is_ok() { - (traces.pop(), fuzz_result.breakpoints) - } else { - (call.traces.clone(), call.cheatcodes.map(|c| c.breakpoints)) - }; + let failed_worker_idx = workers.iter().position(|w| w.id == failed_worker_id).unwrap(); + let failed_worker = &mut workers[failed_worker_idx]; - let mut result = FuzzTestResult { - first_case: fuzz_result.first_case.unwrap_or_default(), - gas_by_case: fuzz_result.gas_by_case, - success: run_result.is_ok(), - skipped: false, - reason: None, - counterexample: None, - logs: fuzz_result.logs, - labeled_addresses: call.labels, - traces: last_run_traces, - breakpoints: last_run_breakpoints, - gas_report_traces: traces.into_iter().map(|a| a.arena).collect(), - coverage: fuzz_result.coverage, - deprecated_cheatcodes: fuzz_result.deprecated_cheatcodes, - }; + let (calldata, call) = std::mem::take(&mut failed_worker.counterexample); + result.labels = call.labels; + result.traces = call.traces.clone(); + result.breakpoints = call.cheatcodes.map(|c| c.breakpoints); - match run_result { - Ok(()) => {} - Err(TestError::Abort(reason)) => { - let msg = reason.message(); - // Currently the only operation that can trigger proptest global rejects is the - // `vm.assume` cheatcode, thus we surface this info to the user when the fuzz test - // aborts due to too many global rejects, making the error message more actionable. - result.reason = if msg == "Too many global rejects" { - let error = FuzzError::TooManyRejects(self.runner.config().max_global_rejects); - Some(error.to_string()) - } else { - Some(msg.to_string()) - }; - } - Err(TestError::Fail(reason, _)) => { - let reason = reason.to_string(); - if reason == TEST_TIMEOUT { - // If the reason is a timeout, we consider the fuzz test successful. - result.success = true; - } else { + match &failed_worker.failure { + Some(TestCaseError::Fail(reason)) => { + let reason = reason.to_string(); result.reason = (!reason.is_empty()).then_some(reason); let args = if let Some(data) = calldata.get(4..) { func.abi_decode_input(data).unwrap_or_default() } else { vec![] }; - + let fuzz = failed_worker.failure_run.unwrap_or_default(); result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + BaseCounterExample::from_fuzz_call(calldata, args, call.traces) + .with_fuzz_metadata(FuzzRunMetadata::new( + fuzz.seed.or(self.config.seed), + fuzz.run, + fuzz.worker, + )), )); } + Some(TestCaseError::Reject(reason)) => { + let reason = reason.to_string(); + result.reason = (!reason.is_empty()).then_some(reason); + } + None => {} } + } else { + let last_run_worker = &workers[last_run_worker_idx]; + result.success = true; + result.traces = last_run_worker.traces.last().cloned(); + result.breakpoints = last_run_worker.breakpoints.clone(); + } + + if !self.config.show_logs { + result.logs = workers[last_run_worker_idx].logs.clone(); } - if let Some(reason) = &result.reason { - if let Some(reason) = SkipReason::decode_self(reason) { - result.skipped = true; - result.reason = reason.0; + for mut worker in workers { + result.gas_by_case.append(&mut worker.gas_by_case); + if self.config.show_logs { + result.logs.append(&mut worker.logs); } + result.gas_report_traces.extend(worker.traces.into_iter().map(|t| t.arena)); + HitMaps::merge_opt(&mut result.line_coverage, worker.coverage); + result.deprecated_cheatcodes.extend(worker.deprecated_cheatcodes); } - state.log_stats(); + if let Some(reason) = &result.reason + && let Some(reason) = SkipReason::decode_self(reason) + { + result.skipped = true; + result.reason = reason.0; + } result } - /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` - /// or a `CounterExampleOutcome` - pub fn single_fuzz( + /// Runs a single fuzz worker + #[allow(clippy::too_many_arguments)] + fn run_worker( &self, + worker_id: usize, + func: &Function, + fuzz_fixtures: &FuzzFixtures, address: Address, - calldata: alloy_primitives::Bytes, - ) -> Result { - let mut call = self - .executor - .call_raw(self.sender, address, calldata.clone(), U256::ZERO) - .map_err(|e| TestCaseError::fail(e.to_string()))?; + rd: &RevertDecoder, + shared_state: &SharedFuzzState, + progress: Option<&ProgressBar>, + ) -> Result> { + // Prepare + let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); + let strategy = proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), + dictionary_weight => fuzz_calldata_from_state(func.clone(), &shared_state.state), + ] + .prop_map(move |calldata| BasicTxDetails { + warp: None, + roll: None, + sender: Default::default(), + call_details: CallDetails { target: Default::default(), calldata }, + }); - // Handle `vm.assume`. - if call.result.as_ref() == MAGIC_ASSUME { - return Err(TestCaseError::reject(FuzzError::AssumeReject)) + let mut corpus = WorkerCorpus::new( + worker_id, + self.config.corpus.clone(), + strategy.boxed(), + // Master worker replays the persisted corpus using the executor + (worker_id == 0).then_some(&self.executor_f), + Some(func), + None, // fuzzed_contracts for invariant tests + )?; + let mut executor = self.executor_f.clone(); + + let mut worker = WorkerState::new(worker_id); + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = + std::cmp::max(1, self.config.gas_report_samples / self.num_workers as u32); + + let worker_runs = self.runs_per_worker(worker_id); + debug!(worker_runs); + + let mut runner_config = self.runner.config().clone(); + runner_config.cases = worker_runs; + + let mut runner = if let Some(seed) = self.config.seed { + let worker_seed = Self::fuzz_worker_seed(seed, worker_id); + trace!(target: "forge::test", ?worker_seed, "deterministic seed for worker {worker_id}"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &worker_seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(runner_config, rng) + } else { + TestRunner::new(runner_config) + }; + + if let Some(target_run) = self.config.run { + for _ in 1..target_run { + if let Err(err) = corpus.new_input(&mut runner, &shared_state.state, func) { + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id + ))); + shared_state.try_claim_failure(worker_id); + return Ok(worker); + } + } } - let (breakpoints, deprecated_cheatcodes) = - call.cheatcodes.as_ref().map_or_else(Default::default, |cheats| { - (cheats.breakpoints.clone(), cheats.deprecated.clone()) - }); + let mut persisted_failure = + self.persisted_failure.as_ref().filter(|_| worker_id == 0 && self.config.run.is_none()); - let success = self.executor.is_raw_call_mut_success(address, &mut call, false); - if success { - Ok(FuzzOutcome::Case(CaseOutcome { - case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, - traces: call.traces, - coverage: call.coverage, - breakpoints, - logs: call.logs, - deprecated_cheatcodes, - })) + // Offset to stagger corpus syncs across workers; so that workers don't sync at the same + // time. + let sync_offset = (worker_id as u32).saturating_mul(100); + let sync_threshold = SYNC_INTERVAL + sync_offset; + let mut runs_since_sync = sync_threshold; // Always sync at the start. + let mut last_metrics_report = Instant::now(); + // Continue while: + // 1. Global state allows (not timed out, not at global limit, no failure found) + // 2. Worker hasn't reached its specific run limit + 'stop: while shared_state.should_continue() && worker.runs < worker_runs { + // If counterexample recorded, replay it first, without incrementing runs. + let (input, fuzz_run) = if worker_id == 0 + && let Some(failure) = persisted_failure.take() + && failure.calldata.get(..4).is_some_and(|selector| func.selector() == selector) + { + let seed = failure.fuzz.seed.or(self.config.seed); + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() + && let Some(seed) = seed + { + let run = failure.fuzz.run.unwrap_or(1); + let worker = failure.fuzz.worker.unwrap_or(worker_id as u32) as usize; + cheats.set_seed(Self::fuzz_run_seed(seed, worker, run)); + } + + ( + failure.calldata.clone(), + Some(FuzzRunMetadata::new( + seed, + failure.fuzz.run, + Some(failure.fuzz.worker.unwrap_or(worker_id as u32)), + )), + ) + } else { + runs_since_sync += 1; + if runs_since_sync >= sync_threshold { + let timer = Instant::now(); + corpus.sync( + self.num_workers, + &executor, + Some(func), + None, + &shared_state.global_corpus_metrics, + )?; + trace!("finished corpus sync in {:?}", timer.elapsed()); + runs_since_sync = 0; + } + + let fuzz_run = self.config.run.unwrap_or(worker.runs + 1); + if let Some(cheats) = executor.inspector_mut().cheatcodes.as_mut() + && let Some(seed) = self.config.seed + { + cheats.set_seed(Self::fuzz_run_seed(seed, worker_id, fuzz_run)); + } + + let input = match corpus.new_input(&mut runner, &shared_state.state, func) { + Ok(input) => input, + Err(err) => { + worker.failure = Some(TestCaseError::fail(format!( + "failed to generate fuzzed input in worker {}: {err}", + worker.id + ))); + shared_state.try_claim_failure(worker_id); + break 'stop; + } + }; + + ( + input, + Some(FuzzRunMetadata::new( + self.config.seed, + Some(fuzz_run), + Some(worker_id as u32), + )), + ) + }; + + let mut inc_runs = || { + let total_runs = shared_state.increment_runs(); + debug_assert!( + shared_state.timer.is_enabled() + || total_runs + <= if self.config.run.is_some() { 1 } else { self.config.runs }, + "worker runs were not distributed correctly" + ); + worker.runs += 1; + if let Some(progress) = progress { + progress.inc(1); + } + total_runs + }; + + worker.last_run_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); + match self.single_fuzz(&executor, address, input, &mut corpus) { + Ok(fuzz_outcome) => match fuzz_outcome { + FuzzOutcome::Case(case) => { + let total_runs = inc_runs(); + + if worker_id == 0 && self.config.corpus.collect_edge_coverage() { + if let Some(progress) = progress { + corpus.sync_metrics(&shared_state.global_corpus_metrics); + progress + .set_message(format!("{}", shared_state.global_corpus_metrics)); + } else if last_metrics_report.elapsed() + > DURATION_BETWEEN_METRICS_REPORT + { + corpus.sync_metrics(&shared_state.global_corpus_metrics); + // Display metrics inline. + let metrics = json!({ + "timestamp": SystemTime::now() + .duration_since(UNIX_EPOCH)? + .as_secs(), + "test": func.name, + "metrics": shared_state.global_corpus_metrics.load(), + }); + let _ = sh_println!("{metrics}"); + last_metrics_report = Instant::now(); + } + } + + worker.gas_by_case.push((case.case.gas, case.case.stipend)); + + if worker.first_case.is_none() { + worker.first_case = Some((total_runs, case.case)); + } + + if let Some(call_traces) = case.traces { + if worker.traces.len() == max_traces_to_collect as usize { + worker.traces.pop(); + } + worker.traces.push(call_traces); + worker.breakpoints = Some(case.breakpoints); + } + + // Always store logs from the last run in test_data.logs for display at + // verbosity >= 2. When show_logs is true, + // accumulate all logs. When false, only keep the last run's logs. + if self.config.show_logs { + worker.logs.extend(case.logs); + } else { + worker.logs = case.logs; + } + + HitMaps::merge_opt(&mut worker.coverage, case.coverage); + worker.deprecated_cheatcodes = case.deprecated_cheatcodes; + } + FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: status, + counterexample: outcome, + .. + }) => { + inc_runs(); + worker.failure_run = fuzz_run; + + // Only classify magic skip payloads when the revert originates from the + // cheatcode address. + let reason = if outcome.1.reverter == Some(CHEATCODE_ADDRESS) { + SkipReason::decode(&outcome.1.result) + .map(|reason| reason.to_string()) + .or_else(|| rd.maybe_decode(&outcome.1.result, status)) + } else { + rd.maybe_decode(&outcome.1.result, status) + }; + worker.logs.extend(outcome.1.logs.clone()); + worker.counterexample = outcome; + worker.failure = Some(TestCaseError::fail(reason.unwrap_or_default())); + shared_state.try_claim_failure(worker_id); + break 'stop; + } + }, + Err(err) => match err { + TestCaseError::Fail(_) => { + worker.failure = Some(err); + shared_state.try_claim_failure(worker_id); + break 'stop; + } + TestCaseError::Reject(_) => { + let max = self.config.max_test_rejects; + + let total = shared_state.increment_rejects(); + + // Update progress bar to reflect rejected runs. + // TODO(dani): (pre-existing) conflicts with corpus metrics `set_message` + if !self.config.corpus.collect_edge_coverage() + && let Some(progress) = progress + { + progress.set_message(format!("([{total}] rejected)")); + } + + if max > 0 && total > max { + worker.failure = + Some(TestCaseError::reject(FuzzError::TooManyRejects(max))); + shared_state.try_claim_failure(worker_id); + break 'stop; + } + } + }, + } + } + + if worker_id == 0 { + worker.failed_corpus_replays = corpus.failed_replays; + } + + // Logs stats + trace!("worker {worker_id} fuzz stats"); + shared_state.state.log_stats(); + + Ok(worker) + } + + /// Determines the number of runs per worker. + const fn runs_per_worker(&self, worker_id: usize) -> u32 { + let worker_id = worker_id as u32; + let total_runs = if self.config.run.is_some() { 1 } else { self.config.runs }; + let n = self.num_workers as u32; + let runs = total_runs / n; + let remainder = total_runs % n; + // Distribute the remainder evenly among the first `remainder` workers, + // assuming `worker_id` is in `0..n`. + if worker_id < remainder { runs + 1 } else { runs } + } + + /// Returns the worker IDs to execute. + fn worker_ids(&self) -> Vec { + if self.config.run.is_some() { + vec![self.config.worker.unwrap_or(0) as usize] } else { - Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { - exit_reason: call.exit_reason, - counterexample: (calldata, call), - breakpoints, - })) + (0..self.num_workers).collect() } } - /// Stores fuzz state for use with [fuzz_calldata_from_state] - pub fn build_fuzz_state(&self, deployed_libs: &[Address]) -> EvmFuzzState { - if let Some(fork_db) = self.executor.backend().active_fork_db() { - EvmFuzzState::new(fork_db, self.config.dictionary, deployed_libs) + /// Derives the deterministic RNG seed for a fuzz worker. + fn fuzz_worker_seed(seed: U256, worker_id: usize) -> U256 { + if worker_id == 0 { + seed } else { - EvmFuzzState::new( - self.executor.backend().mem_db(), - self.config.dictionary, - deployed_libs, - ) + let worker_id = worker_id as u32; + let seed_data = [&seed.to_be_bytes::<32>()[..], &worker_id.to_be_bytes()[..]].concat(); + U256::from_be_bytes(keccak256(seed_data).0) } } + + /// Derives the deterministic RNG seed for cheatcode randomness in a worker-local run. + fn fuzz_run_seed(seed: U256, worker_id: usize, run: u32) -> U256 { + Self::fuzz_worker_seed(seed, worker_id).wrapping_add(U256::from(run.saturating_sub(1))) + } } diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs index 1bda778e7c9cf..a25c3eedbe4dd 100644 --- a/crates/evm/evm/src/executors/fuzz/types.rs +++ b/crates/evm/evm/src/executors/fuzz/types.rs @@ -1,6 +1,6 @@ use crate::executors::RawCallResult; -use alloy_primitives::{map::HashMap, Bytes, Log}; -use foundry_common::evm::Breakpoints; +use alloy_primitives::{Bytes, Log, map::HashMap}; +use foundry_evm_core::{Breakpoints, evm::FoundryEvmNetwork}; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::FuzzCase; use foundry_evm_traces::SparsedTraceArena; @@ -25,11 +25,11 @@ pub struct CaseOutcome { /// Returned by a single fuzz when a counterexample has been discovered #[derive(Debug)] -pub struct CounterExampleOutcome { +pub struct CounterExampleOutcome { /// Minimal reproduction test case for failing test. - pub counterexample: (Bytes, RawCallResult), + pub counterexample: (Bytes, RawCallResult), /// The status of the call. - pub exit_reason: InstructionResult, + pub exit_reason: Option, /// Breakpoints char pc map. pub breakpoints: Breakpoints, } @@ -37,7 +37,7 @@ pub struct CounterExampleOutcome { /// Outcome of a single fuzz #[derive(Debug)] #[expect(clippy::large_enum_variant)] -pub enum FuzzOutcome { +pub enum FuzzOutcome { Case(CaseOutcome), - CounterExample(CounterExampleOutcome), + CounterExample(CounterExampleOutcome), } diff --git a/crates/evm/evm/src/executors/invariant/error.rs b/crates/evm/evm/src/executors/invariant/error.rs index 0ff4b4c52f962..4f1cd5ebbfa36 100644 --- a/crates/evm/evm/src/executors/invariant/error.rs +++ b/crates/evm/evm/src/executors/invariant/error.rs @@ -1,9 +1,12 @@ -use super::{BasicTxDetails, InvariantContract}; +use super::InvariantContract; use crate::executors::RawCallResult; use alloy_primitives::{Address, Bytes}; use foundry_config::InvariantConfig; -use foundry_evm_core::decode::RevertDecoder; -use foundry_evm_fuzz::{invariant::FuzzRunIdentifiedContracts, Reason}; +use foundry_evm_core::{ + decode::{ASSERTION_FAILED_PREFIX, EMPTY_REVERT_DATA, RevertDecoder}, + evm::FoundryEvmNetwork, +}; +use foundry_evm_fuzz::{BasicTxDetails, Reason, invariant::FuzzRunIdentifiedContracts}; use proptest::test_runner::TestError; /// Stores information about failures and reverts of the invariant tests. @@ -65,22 +68,32 @@ pub struct FailedInvariantCaseData { pub shrink_run_limit: u32, /// Fail on revert, used to check sequence when shrinking. pub fail_on_revert: bool, + /// Whether this failure originated from a handler assertion. + pub assertion_failure: bool, } impl FailedInvariantCaseData { - pub fn new( + pub fn new( invariant_contract: &InvariantContract<'_>, invariant_config: &InvariantConfig, targeted_contracts: &FuzzRunIdentifiedContracts, calldata: &[BasicTxDetails], - call_result: RawCallResult, + call_result: RawCallResult, inner_sequence: &[Option], ) -> Self { // Collect abis of fuzzed and invariant contracts to decode custom error. let revert_reason = RevertDecoder::new() .with_abis(targeted_contracts.targets.lock().values().map(|c| &c.abi)) .with_abi(invariant_contract.abi) - .decode(call_result.result.as_ref(), Some(call_result.exit_reason)); + .decode(call_result.result.as_ref(), call_result.exit_reason); + // Non-reverting assertion failures surface through Foundry's failure flags instead of + // revert data. Use a stable fallback so invariant output is not blank. + let revert_reason = + if !call_result.reverted && matches!(revert_reason.as_str(), "" | EMPTY_REVERT_DATA) { + ASSERTION_FAILED_PREFIX.to_string() + } else { + revert_reason + }; let func = invariant_contract.invariant_function; debug_assert!(func.inputs.is_empty()); @@ -97,6 +110,17 @@ impl FailedInvariantCaseData { inner_sequence: inner_sequence.to_vec(), shrink_run_limit: invariant_config.shrink_run_limit, fail_on_revert: invariant_config.fail_on_revert, + assertion_failure: false, } } + + /// Marks this case as assertion-originated and normalizes empty decoded revert data from + /// non-reverting assertion paths into a stable user-facing message. + pub fn with_assertion_failure(mut self, assertion_failure: bool) -> Self { + self.assertion_failure = assertion_failure; + if assertion_failure && matches!(self.revert_reason.as_str(), "" | EMPTY_REVERT_DATA) { + self.revert_reason = ASSERTION_FAILED_PREFIX.to_string(); + } + self + } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 4e2914ae79e04..e02cdbc393ee6 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -1,41 +1,47 @@ use crate::{ - executors::{Executor, RawCallResult}, + executors::{ + DURATION_BETWEEN_METRICS_REPORT, EarlyExit, EvmError, Executor, FuzzTestTimer, + RawCallResult, corpus::WorkerCorpus, + }, inspectors::Fuzzer, }; -use alloy_primitives::{map::HashMap, Address, Bytes, FixedBytes, Selector, U256}; -use alloy_sol_types::{sol, SolCall}; -use eyre::{eyre, ContextCompat, Result}; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; +use alloy_primitives::{Address, Bytes, FixedBytes, I256, Selector, U256, map::AddressMap}; +use alloy_sol_types::{SolCall, sol}; +use eyre::{ContextCompat, Result, eyre}; +use foundry_common::{ + TestFunctionExt, + contracts::{ContractsByAddress, ContractsByArtifact}, + sh_eprintln, sh_println, +}; use foundry_config::InvariantConfig; use foundry_evm_core::{ + FoundryBlock, constants::{ CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, - TEST_TIMEOUT, }, + evm::FoundryEvmNetwork, precompiles::PRECOMPILES, }; use foundry_evm_fuzz::{ + BasicTxDetails, FuzzCase, FuzzFixtures, FuzzedCases, invariant::{ - ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract, + ArtifactFilters, FuzzRunIdentifiedContracts, InvariantContract, InvariantSettings, RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts, }, - strategies::{invariant_strat, override_call_strat, EvmFuzzState}, - FuzzCase, FuzzFixtures, FuzzedCases, + strategies::{EvmFuzzState, invariant_strat, override_call_strat}, }; use foundry_evm_traces::{CallTraceArena, SparsedTraceArena}; use indicatif::ProgressBar; use parking_lot::RwLock; -use proptest::{ - strategy::{Strategy, ValueTree}, - test_runner::{TestCaseError, TestRunner}, -}; -use result::{assert_after_invariant, assert_invariants, can_continue}; -use revm::state::Account; -use shrink::shrink_sequence; +use proptest::{strategy::Strategy, test_runner::TestRunner}; +use result::{assert_after_invariant, assert_invariants, can_continue, did_fail_on_assert}; +use revm::{context::Block, state::Account}; +use serde::{Deserialize, Serialize}; +use serde_json::json; use std::{ - cell::RefCell, - collections::{btree_map::Entry, HashMap as Map}, + collections::{HashMap as Map, HashSet, btree_map::Entry}, sync::Arc, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; mod error; @@ -47,11 +53,9 @@ pub use replay::{replay_error, replay_run}; mod result; pub use result::InvariantFuzzTestResult; -use serde::{Deserialize, Serialize}; mod shrink; -use crate::executors::{EvmError, FuzzTestTimer}; -pub use shrink::check_sequence; +pub use shrink::{CheckSequenceOptions, check_sequence, check_sequence_value}; sol! { interface IInvariantTest { @@ -118,104 +122,207 @@ pub struct InvariantMetrics { pub discards: usize, } +/// Campaign-level throughput metrics for invariant progress reporting. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +struct InvariantThroughputMetrics { + total_txs: u64, + total_gas: u64, +} + +impl InvariantThroughputMetrics { + const fn record_call(&mut self, gas_used: u64) { + self.total_txs += 1; + self.total_gas += gas_used; + } + + fn tx_per_sec(self, elapsed: Duration) -> f64 { + rate_per_sec(self.total_txs as f64, elapsed) + } + + fn gas_per_sec(self, elapsed: Duration) -> f64 { + rate_per_sec(self.total_gas as f64, elapsed) + } +} + +/// Converts a cumulative campaign total into an average per-second rate. +/// +/// Returns `0.0` during the initial zero-elapsed startup window to avoid +/// dividing by zero while progress reporting is warming up. +fn rate_per_sec(total: f64, elapsed: Duration) -> f64 { + let elapsed_secs = elapsed.as_secs_f64(); + if elapsed_secs > 0.0 { total / elapsed_secs } else { 0.0 } +} + +/// Tracks invariant failure counts during a campaign. +#[derive(Debug, Default)] +struct InvariantFailureMetrics { + failures: u64, + unique_failures: HashSet, +} + +impl InvariantFailureMetrics { + /// Records a failure and emits a structured JSON `"failure"` event. + fn record_failure(&mut self, invariant_name: &str, target: &str, reason: &str) { + self.failures += 1; + self.unique_failures.insert(invariant_name.to_string()); + + let timestamp = + SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0); + let event = json!({ + "timestamp": timestamp, + "event": "failure", + "invariant": invariant_name, + "target": target, + "reason": reason, + }); + let _ = sh_eprintln!("{}", serde_json::to_string(&event).unwrap_or_default()); + } +} + +/// Builds the machine-readable invariant progress payload emitted during a +/// campaign. +/// +/// This keeps the existing corpus progress metrics together with cumulative and +/// derived throughput fields so downstream benchmark tooling can consume a +/// single JSON event shape. +fn build_invariant_progress_json( + timestamp_secs: u64, + invariant_name: &str, + corpus_metrics: &M, + optimization_best: Option, + throughput: InvariantThroughputMetrics, + failure_metrics: &InvariantFailureMetrics, + elapsed: Duration, +) -> serde_json::Value { + let mut metrics = serde_json::to_value(corpus_metrics).unwrap_or_default(); + if let Some(obj) = metrics.as_object_mut() { + obj.insert("failures".to_string(), json!(failure_metrics.failures)); + obj.insert("unique_failures".to_string(), json!(failure_metrics.unique_failures.len())); + } + + let mut payload = json!({ + "timestamp": timestamp_secs, + "event": "pulse", + "invariant": invariant_name, + "metrics": metrics, + "total_txs": throughput.total_txs, + "total_gas": throughput.total_gas, + "tx_per_sec": throughput.tx_per_sec(elapsed), + "gas_per_sec": throughput.gas_per_sec(elapsed), + }); + + if let Some(best) = optimization_best { + payload["optimization_best"] = json!(best.to_string()); + } + + payload +} + /// Contains data collected during invariant test runs. -pub struct InvariantTestData { +struct InvariantTestData { // Consumed gas and calldata of every successful fuzz call. - pub fuzz_cases: Vec, + fuzz_cases: Vec, // Data related to reverts or failed assertions of the test. - pub failures: InvariantFailures, + failures: InvariantFailures, // Calldata in the last invariant run. - pub last_run_inputs: Vec, + last_run_inputs: Vec, // Additional traces for gas report. - pub gas_report_traces: Vec>, + gas_report_traces: Vec>, // Last call results of the invariant test. - pub last_call_results: Option, - // Coverage information collected from all fuzzed calls. - pub coverage: Option, + last_call_results: Option>, + // Line coverage information collected from all fuzzed calls. + line_coverage: Option, // Metrics for each fuzzed selector. - pub metrics: Map, + metrics: Map, // Proptest runner to query for random values. // The strategy only comes with the first `input`. We fill the rest of the `inputs` // until the desired `depth` so we can use the evolving fuzz dictionary // during the run. - pub branch_runner: TestRunner, + branch_runner: TestRunner, + + // Optimization mode state: tracks the best (maximum) value and the sequence that produced it. + // Only used when invariant function returns int256. + optimization_best_value: Option, + optimization_best_sequence: Vec, } /// Contains invariant test data. -pub struct InvariantTest { +struct InvariantTest { // Fuzz state of invariant test. - pub fuzz_state: EvmFuzzState, + fuzz_state: EvmFuzzState, // Contracts fuzzed by the invariant test. - pub targeted_contracts: FuzzRunIdentifiedContracts, + targeted_contracts: FuzzRunIdentifiedContracts, // Data collected during invariant runs. - pub execution_data: RefCell, + test_data: InvariantTestData, } -impl InvariantTest { +impl InvariantTest { /// Instantiates an invariant test. - pub fn new( + fn new( fuzz_state: EvmFuzzState, targeted_contracts: FuzzRunIdentifiedContracts, failures: InvariantFailures, - last_call_results: Option, + last_call_results: Option>, branch_runner: TestRunner, ) -> Self { let mut fuzz_cases = vec![]; if last_call_results.is_none() { fuzz_cases.push(FuzzedCases::new(vec![])); } - let execution_data = RefCell::new(InvariantTestData { + let test_data = InvariantTestData { fuzz_cases, failures, last_run_inputs: vec![], gas_report_traces: vec![], last_call_results, - coverage: None, + line_coverage: None, metrics: Map::default(), branch_runner, - }); - Self { fuzz_state, targeted_contracts, execution_data } + optimization_best_value: None, + optimization_best_sequence: vec![], + }; + Self { fuzz_state, targeted_contracts, test_data } } /// Returns number of invariant test reverts. - pub fn reverts(&self) -> usize { - self.execution_data.borrow().failures.reverts + const fn reverts(&self) -> usize { + self.test_data.failures.reverts } /// Whether invariant test has errors or not. - pub fn has_errors(&self) -> bool { - self.execution_data.borrow().failures.error.is_some() + const fn has_errors(&self) -> bool { + self.test_data.failures.error.is_some() } /// Set invariant test error. - pub fn set_error(&self, error: InvariantFuzzError) { - self.execution_data.borrow_mut().failures.error = Some(error); + fn set_error(&mut self, error: InvariantFuzzError) { + self.test_data.failures.error = Some(error); } /// Set last invariant test call results. - pub fn set_last_call_results(&self, call_result: Option) { - self.execution_data.borrow_mut().last_call_results = call_result; + fn set_last_call_results(&mut self, call_result: Option>) { + self.test_data.last_call_results = call_result; } /// Set last invariant run call sequence. - pub fn set_last_run_inputs(&self, inputs: &Vec) { - self.execution_data.borrow_mut().last_run_inputs.clone_from(inputs); + fn set_last_run_inputs(&mut self, inputs: &Vec) { + self.test_data.last_run_inputs.clone_from(inputs); } - /// Merge current collected coverage with the new coverage from last fuzzed call. - pub fn merge_coverage(&self, new_coverage: Option) { - HitMaps::merge_opt(&mut self.execution_data.borrow_mut().coverage, new_coverage); + /// Merge current collected line coverage with the new coverage from last fuzzed call. + fn merge_line_coverage(&mut self, new_coverage: Option) { + HitMaps::merge_opt(&mut self.test_data.line_coverage, new_coverage); } /// Update metrics for a fuzzed selector, extracted from tx details. /// Always increments number of calls; discarded runs (through assume cheatcodes) are tracked /// separated from reverts. - pub fn record_metrics(&self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) { + fn record_metrics(&mut self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) { if let Some(metric_key) = self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details) { - let test_metrics = &mut self.execution_data.borrow_mut().metrics; + let test_metrics = &mut self.test_data.metrics; let invariant_metrics = test_metrics.entry(metric_key).or_default(); invariant_metrics.calls += 1; if discarded { @@ -228,44 +335,57 @@ impl InvariantTest { /// End invariant test run by collecting results, cleaning collected artifacts and reverting /// created fuzz state. - pub fn end_run(&self, run: InvariantTestRun, gas_samples: usize) { + fn end_run(&mut self, run: InvariantTestRun, gas_samples: usize) { // We clear all the targeted contracts created during this run. self.targeted_contracts.clear_created_contracts(run.created_contracts); - let mut invariant_data = self.execution_data.borrow_mut(); - if invariant_data.gas_report_traces.len() < gas_samples { - invariant_data + if self.test_data.gas_report_traces.len() < gas_samples { + self.test_data .gas_report_traces .push(run.run_traces.into_iter().map(|arena| arena.arena).collect()); } - invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs)); + self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs)); // Revert state to not persist values between runs. self.fuzz_state.revert(); } + + /// Updates the optimization state if the new value is better (higher) than the current best. + fn update_optimization_value(&mut self, value: I256, sequence: &[BasicTxDetails]) { + if self.test_data.optimization_best_value.is_none_or(|best| value > best) { + self.test_data.optimization_best_value = Some(value); + self.test_data.optimization_best_sequence = sequence.to_vec(); + } + } } /// Contains data for an invariant test run. -pub struct InvariantTestRun { +struct InvariantTestRun { // Invariant run call sequence. - pub inputs: Vec, + inputs: Vec, // Current invariant run executor. - pub executor: Executor, + executor: Executor, // Invariant run stat reports (eg. gas usage). - pub fuzz_runs: Vec, + fuzz_runs: Vec, // Contracts created during current invariant run. - pub created_contracts: Vec
, + created_contracts: Vec
, // Traces of each call of the invariant run call sequence. - pub run_traces: Vec, + run_traces: Vec, // Current depth of invariant run. - pub depth: u32, + depth: u32, // Current assume rejects of the invariant run. - pub assume_rejects_counter: u32, + rejects: u32, + // Whether new coverage was discovered during this run. + new_coverage: bool, + // For optimization mode: the best value found during this run (if any). + optimization_value: Option, + // For optimization mode: the length of the input prefix that produced the best value. + optimization_prefix_len: usize, } -impl InvariantTestRun { +impl InvariantTestRun { /// Instantiates an invariant test run. - pub fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self { + fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self { Self { inputs: vec![first_input], executor, @@ -273,7 +393,10 @@ impl InvariantTestRun { created_contracts: vec![], run_traces: vec![], depth: 0, - assume_rejects_counter: 0, + rejects: 0, + new_coverage: false, + optimization_value: None, + optimization_prefix_len: 0, } } } @@ -284,8 +407,8 @@ impl InvariantTestRun { /// contracts with inputs, until it finds a counterexample sequence. The provided [`TestRunner`] /// contains all the configuration which can be overridden via [environment /// variables](proptest::test_runner::Config) -pub struct InvariantExecutor<'a> { - pub executor: Executor, +pub struct InvariantExecutor<'a, FEN: FoundryEvmNetwork> { + pub executor: Executor, /// Proptest runner. runner: TestRunner, /// The invariant configuration @@ -299,10 +422,10 @@ pub struct InvariantExecutor<'a> { artifact_filters: ArtifactFilters, } -impl<'a> InvariantExecutor<'a> { +impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { /// Instantiates a fuzzed executor EVM given a testrunner pub fn new( - executor: Executor, + executor: Executor, runner: TestRunner, config: InvariantConfig, setup_contracts: &'a ContractsByAddress, @@ -318,29 +441,55 @@ impl<'a> InvariantExecutor<'a> { } } + pub fn config(self) -> InvariantConfig { + self.config + } + /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`. pub fn invariant_fuzz( &mut self, invariant_contract: InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, - deployed_libs: &[Address], + fuzz_state: EvmFuzzState, progress: Option<&ProgressBar>, + early_exit: &EarlyExit, ) -> Result { // Throw an error to abort test run if the invariant function accepts input params if !invariant_contract.invariant_function.inputs.is_empty() { - return Err(eyre!("Invariant test function should have no inputs")) + return Err(eyre!("Invariant test function should have no inputs")); } - let (invariant_test, invariant_strategy) = - self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?; + let (mut invariant_test, mut corpus_manager) = + self.prepare_test(&invariant_contract, fuzz_fixtures, fuzz_state)?; // Start timer for this invariant test. + let mut runs = 0; let timer = FuzzTestTimer::new(self.config.timeout); + let mut last_metrics_report = Instant::now(); + let campaign_start = Instant::now(); + let mut throughput = InvariantThroughputMetrics::default(); + let mut failure_metrics = InvariantFailureMetrics::default(); + let continue_campaign = |runs: u32| { + if early_exit.should_stop() { + return false; + } + + if timer.is_enabled() { !timer.is_timed_out() } else { runs < self.config.runs } + }; + + // Invariant runs with edge coverage if corpus dir is set or showing edge coverage. + let edge_coverage_enabled = self.config.corpus.collect_edge_coverage(); + + 'stop: while continue_campaign(runs) { + let initial_seq = corpus_manager.new_inputs( + &mut invariant_test.test_data.branch_runner, + &invariant_test.fuzz_state, + &invariant_test.targeted_contracts, + )?; - let _ = self.runner.run(&invariant_strategy, |first_input| { // Create current invariant run data. let mut current_run = InvariantTestRun::new( - first_input, + initial_seq[0].clone(), // Before each run, we must reset the backend state. self.executor.clone(), self.config.depth as usize, @@ -348,7 +497,7 @@ impl<'a> InvariantExecutor<'a> { // We stop the run immediately if we have reverted, and `fail_on_revert` is set. if self.config.fail_on_revert && invariant_test.reverts() > 0 { - return Err(TestCaseError::fail("call reverted")) + return Err(eyre!("call reverted")); } while current_run.depth < self.config.depth { @@ -358,45 +507,42 @@ impl<'a> InvariantExecutor<'a> { // successful even though it timed out. We *want* // this behavior for now, so that's ok, but // future developers should be aware of this. - return Err(TestCaseError::fail(TEST_TIMEOUT)); + break 'stop; } - let tx = current_run.inputs.last().ok_or_else(|| { - TestCaseError::fail("no input generated to called fuzz target") - })?; + let tx = current_run + .inputs + .last() + .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?; // Execute call from the randomly generated sequence without committing state. // State is committed only if call is not a magic assume. - let mut call_result = current_run - .executor - .call_raw( - tx.sender, - tx.call_details.target, - tx.call_details.calldata.clone(), - U256::ZERO, - ) - .map_err(|e| TestCaseError::fail(e.to_string()))?; - + let mut call_result = execute_tx(&mut current_run.executor, tx)?; let discarded = call_result.result.as_ref() == MAGIC_ASSUME; if self.config.show_metrics { invariant_test.record_metrics(tx, call_result.reverted, discarded); } - // Collect coverage from last fuzzed call. - invariant_test.merge_coverage(call_result.coverage.clone()); + // Collect line coverage from last fuzzed call. + invariant_test.merge_line_coverage(call_result.line_coverage.clone()); + // Collect edge coverage and set the flag in the current run. + if corpus_manager.merge_edge_coverage(&mut call_result) { + current_run.new_coverage = true; + } if discarded { current_run.inputs.pop(); - current_run.assume_rejects_counter += 1; - if current_run.assume_rejects_counter > self.config.max_assume_rejects { + current_run.rejects += 1; + if current_run.rejects > self.config.max_assume_rejects { invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects( self.config.max_assume_rejects, )); - return Err(TestCaseError::fail( - "reached maximum number of `vm.assume` rejects", - )); + break 'stop; } } else { + let assertion_failure = + did_fail_on_assert(&call_result, &call_result.state_changeset); + // Commit executed call result. current_run.executor.commit(&mut call_result); @@ -407,7 +553,7 @@ impl<'a> InvariantExecutor<'a> { // inconsistencies whenever proptest tries to use the input case after test // execution. // See . - let mut state_changeset = call_result.state_changeset.clone(); + let mut state_changeset = std::mem::take(&mut call_result.state_changeset); if !call_result.reverted { collect_data( &invariant_test, @@ -431,133 +577,248 @@ impl<'a> InvariantExecutor<'a> { { warn!(target: "forge::test", "{error}"); } - current_run.fuzz_runs.push(FuzzCase { - calldata: tx.call_details.calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); + current_run + .fuzz_runs + .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend }); + throughput.record_call(call_result.gas_used); // Determine if test can continue or should exit. - let result = can_continue( - &invariant_contract, - &invariant_test, - &mut current_run, - &self.config, - call_result, - &state_changeset, - ) - .map_err(|e| TestCaseError::fail(e.to_string()))?; + // Check invariants based on check_interval to improve deep run performance. + // - check_interval=0: only assert on the last call + // - check_interval=1 (default): assert after every call + // - check_interval=N: assert every N calls AND always on the last call + let is_last_call = current_run.depth == self.config.depth - 1; + // In optimization mode, always evaluate the invariant to track + // the best value at every prefix — check_interval only gates + // boolean invariant assertions. + let is_optimization = invariant_contract.is_optimization(); + let should_check_invariant = is_optimization + || if self.config.check_interval == 0 { + is_last_call + } else { + self.config.check_interval == 1 + || (current_run.depth + 1) + .is_multiple_of(self.config.check_interval) + || is_last_call + }; + + let result = if should_check_invariant { + can_continue( + &invariant_contract, + &mut invariant_test, + &mut current_run, + &self.config, + call_result, + &state_changeset, + ) + .map_err(|e| eyre!(e.to_string()))? + } else { + // Skip invariant check but still track reverts + if call_result.reverted { + invariant_test.test_data.failures.reverts += 1; + } + if assertion_failure || (call_result.reverted && self.config.fail_on_revert) + { + let case_data = error::FailedInvariantCaseData::new( + &invariant_contract, + &self.config, + &invariant_test.targeted_contracts, + ¤t_run.inputs, + call_result, + &[], + ) + .with_assertion_failure(assertion_failure); + invariant_test.test_data.failures.revert_reason = + Some(case_data.revert_reason.clone()); + invariant_test.test_data.failures.error = Some(if assertion_failure { + InvariantFuzzError::BrokenInvariant(case_data) + } else { + InvariantFuzzError::Revert(case_data) + }); + result::RichInvariantResults::new(false, None) + } else if call_result.reverted + && !invariant_contract.is_optimization() + && !self.config.has_delay() + { + // Delay-enabled campaigns keep reverted calls so shrinking can + // preserve their warp/roll contribution when building the final + // counterexample. + current_run.inputs.pop(); + result::RichInvariantResults::new(true, None) + } else { + result::RichInvariantResults::new(true, None) + } + }; + if !result.can_continue || current_run.depth == self.config.depth - 1 { invariant_test.set_last_run_inputs(¤t_run.inputs); } // If test cannot continue then stop current run and exit test suite. if !result.can_continue { - return Err(TestCaseError::fail("test cannot continue")) + let reason = invariant_test + .test_data + .failures + .error + .as_ref() + .and_then(|e| e.revert_reason()) + .unwrap_or_default(); + failure_metrics.record_failure( + &invariant_contract.invariant_function.name, + invariant_contract.name, + &reason, + ); + break 'stop; } invariant_test.set_last_call_results(result.call_result); current_run.depth += 1; } - // Generates the next call from the run using the recently updated - // dictionary. - current_run.inputs.push( - invariant_strategy - .new_tree(&mut invariant_test.execution_data.borrow_mut().branch_runner) - .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? - .current(), - ); + current_run.inputs.push(corpus_manager.generate_next_input( + &mut invariant_test.test_data.branch_runner, + &initial_seq, + discarded, + current_run.depth as usize, + )?); } + // Extend corpus with current run data. + // Materialize the optimization best prefix once at run end (avoids + // cloning inputs on every new in-run max). + let optimization = current_run.optimization_value.map(|v| { + let prefix = current_run.inputs[..current_run.optimization_prefix_len].to_vec(); + (v, prefix) + }); + corpus_manager.process_inputs( + ¤t_run.inputs, + current_run.new_coverage, + optimization, + ); + // Call `afterInvariant` only if it is declared and test didn't fail already. if invariant_contract.call_after_invariant && !invariant_test.has_errors() { - assert_after_invariant( + let success = assert_after_invariant( &invariant_contract, - &invariant_test, + &mut invariant_test, ¤t_run, &self.config, ) - .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?; + .map_err(|_| eyre!("Failed to call afterInvariant"))?; + if !success { + let reason = invariant_test + .test_data + .failures + .error + .as_ref() + .and_then(|e| e.revert_reason()) + .unwrap_or_default(); + failure_metrics.record_failure( + &invariant_contract.invariant_function.name, + invariant_contract.name, + &reason, + ); + } } // End current invariant test run. invariant_test.end_run(current_run, self.config.gas_report_samples as usize); - - // If running with progress then increment completed runs. if let Some(progress) = progress { + // If running with progress then increment completed runs. progress.inc(1); + // Display current best value and/or corpus metrics in progress bar. + let best = invariant_test.test_data.optimization_best_value; + if edge_coverage_enabled || best.is_some() { + let mut msg = String::new(); + if let Some(best) = best { + msg.push_str(&format!("best: {best}")); + } + if edge_coverage_enabled { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!("{}", corpus_manager.metrics)); + } + progress.set_message(msg); + } + } else if edge_coverage_enabled + && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT + { + // Display corpus metrics inline as JSON. + let metrics = build_invariant_progress_json( + SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(), + &invariant_contract.invariant_function.name, + &corpus_manager.metrics, + invariant_test.test_data.optimization_best_value, + throughput, + &failure_metrics, + campaign_start.elapsed(), + ); + let _ = sh_println!("{}", serde_json::to_string(&metrics)?); + last_metrics_report = Instant::now(); } - Ok(()) - }); + runs += 1; + } trace!(?fuzz_fixtures); invariant_test.fuzz_state.log_stats(); - let result = invariant_test.execution_data.into_inner(); + let result = invariant_test.test_data; Ok(InvariantFuzzTestResult { error: result.failures.error, cases: result.fuzz_cases, reverts: result.failures.reverts, last_run_inputs: result.last_run_inputs, gas_report_traces: result.gas_report_traces, - coverage: result.coverage, + line_coverage: result.line_coverage, metrics: result.metrics, + failed_corpus_replays: corpus_manager.failed_replays, + optimization_best_value: result.optimization_best_value, + optimization_best_sequence: result.optimization_best_sequence, }) } /// Prepares certain structures to execute the invariant tests: /// * Invariant Fuzz Test. - /// * Invariant Strategy + /// * Invariant Corpus Manager. fn prepare_test( &mut self, invariant_contract: &InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, - deployed_libs: &[Address], - ) -> Result<(InvariantTest, impl Strategy)> { + fuzz_state: EvmFuzzState, + ) -> Result<(InvariantTest, WorkerCorpus)> { // Finds out the chosen deployed contracts and/or senders. self.select_contract_artifacts(invariant_contract.address)?; let (targeted_senders, targeted_contracts) = self.select_contracts_and_senders(invariant_contract.address)?; - // Stores fuzz state for use with [fuzz_calldata_from_state]. - let fuzz_state = EvmFuzzState::new( - self.executor.backend().mem_db(), - self.config.dictionary, - deployed_libs, - ); - // Creates the invariant strategy. let strategy = invariant_strat( fuzz_state.clone(), targeted_senders, targeted_contracts.clone(), - self.config.dictionary.dictionary_weight, + self.config.clone(), fuzz_fixtures.clone(), ) .no_shrink(); - // Allows `override_call_strat` to use the address given by the Fuzzer inspector during - // EVM execution. - let mut call_generator = None; - if self.config.call_override { - let target_contract_ref = Arc::new(RwLock::new(Address::ZERO)); - - call_generator = Some(RandomCallGenerator::new( - invariant_contract.address, - self.runner.clone(), - override_call_strat( - fuzz_state.clone(), - targeted_contracts.clone(), - target_contract_ref.clone(), - fuzz_fixtures.clone(), - ), - target_contract_ref, - )); - } - - self.executor.inspector_mut().fuzzer = - Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true }); + // If any of the targeted contracts have the storage layout enabled then we can sample + // mapping values. To accomplish, we need to record the mapping storage slots and keys. + let fuzz_state = + if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) { + fuzz_state.with_mapping_slots(AddressMap::default()) + } else { + fuzz_state + }; + + // Set up fuzzer WITHOUT call_generator initially. + // We defer call_override until after the initial invariant check to avoid + // injecting random calls during setup which would break the invariant assertion. + self.executor.inspector_mut().set_fuzzer(Fuzzer { + call_generator: None, + fuzz_state: fuzz_state.clone(), + collect: true, + }); // Let's make sure the invariant is sound before actually starting the run: // We'll assert the invariant in its initial state, and if it fails, we'll @@ -573,19 +834,64 @@ impl<'a> InvariantExecutor<'a> { &mut failures, )?; if let Some(error) = failures.error { - return Err(eyre!(error.revert_reason().unwrap_or_default())) + return Err(eyre!(error.revert_reason().unwrap_or_default())); } - Ok(( - InvariantTest::new( - fuzz_state, - targeted_contracts, - failures, - last_call_results, + // NOW enable call_override after the initial invariant check has passed. + // This allows `override_call_strat` to inject calls during actual fuzz runs + // for reentrancy vulnerability detection. + if self.config.call_override { + let target_contract_ref = Arc::new(RwLock::new(Address::ZERO)); + + // Collect handler addresses - these are the contracts we want to inject + // reentrancy into (simulating malicious receive() functions). + let handler_addresses: std::collections::HashSet
= + targeted_contracts.targets.lock().keys().copied().collect(); + + let call_generator = RandomCallGenerator::new( + invariant_contract.address, + handler_addresses, self.runner.clone(), - ), - strategy, - )) + override_call_strat( + fuzz_state.clone(), + targeted_contracts.clone(), + target_contract_ref.clone(), + fuzz_fixtures.clone(), + ), + target_contract_ref, + ); + + if let Some(fuzzer) = self.executor.inspector_mut().fuzzer.as_mut() { + fuzzer.call_generator = Some(call_generator); + } + } + + let worker = WorkerCorpus::new( + 0, + self.config.corpus.clone(), + strategy.boxed(), + Some(&self.executor), + None, + Some(&targeted_contracts), + )?; + + let mut invariant_test = InvariantTest::new( + fuzz_state, + targeted_contracts, + failures, + last_call_results, + self.runner.clone(), + ); + + // Seed invariant test with previously persisted optimization state, + // but only if the current invariant is in optimization mode. + if invariant_contract.is_optimization() { + let (opt_best_value, opt_best_sequence) = worker.optimization_initial_state(); + invariant_test.test_data.optimization_best_value = opt_best_value; + invariant_test.test_data.optimization_best_sequence = opt_best_sequence; + } + + Ok((invariant_test, worker)) } /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string @@ -634,13 +940,13 @@ impl<'a> InvariantExecutor<'a> { .filter(|func| { !matches!( func.state_mutability, - alloy_json_abi::StateMutability::Pure | - alloy_json_abi::StateMutability::View + alloy_json_abi::StateMutability::Pure + | alloy_json_abi::StateMutability::View ) }) - .count() == - 0 && - !self.artifact_filters.excluded.contains(&artifact.identifier()) + .count() + == 0 + && !self.artifact_filters.excluded.contains(&artifact.identifier()) { self.artifact_filters.excluded.push(artifact.identifier()); } @@ -651,8 +957,8 @@ impl<'a> InvariantExecutor<'a> { for contract in targeted_artifacts { let identifier = self.validate_selected_contract(contract, &[])?; - if !self.artifact_filters.targeted.contains_key(&identifier) && - !self.artifact_filters.excluded.contains(&identifier) + if !self.artifact_filters.targeted.contains_key(&identifier) + && !self.artifact_filters.excluded.contains(&identifier) { self.artifact_filters.targeted.insert(identifier, vec![]); } @@ -679,9 +985,11 @@ impl<'a> InvariantExecutor<'a> { .wrap_err(format!("{contract} does not have the selector {selector:?}"))?; } - return Ok(artifact.identifier()) + return Ok(artifact.identifier()); } - eyre::bail!("{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."); + eyre::bail!( + "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`." + ); } /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`, @@ -716,15 +1024,19 @@ impl<'a> InvariantExecutor<'a> { return true; } - *addr != to && - *addr != CHEATCODE_ADDRESS && - *addr != HARDHAT_CONSOLE_ADDRESS && - (selected.is_empty() || selected.contains(addr)) && - (excluded.is_empty() || !excluded.contains(addr)) && - self.artifact_filters.matches(identifier) + *addr != to + && *addr != CHEATCODE_ADDRESS + && *addr != HARDHAT_CONSOLE_ADDRESS + && (selected.is_empty() || selected.contains(addr)) + && (excluded.is_empty() || !excluded.contains(addr)) + && self.artifact_filters.matches(identifier) }) .map(|(addr, (identifier, abi))| { - (*addr, TargetedContract::new(identifier.clone(), abi.clone())) + ( + *addr, + TargetedContract::new(identifier.clone(), abi.clone()) + .with_project_contracts(self.project_contracts), + ) }) .collect(); let mut contracts = TargetedContracts { inner: contracts }; @@ -767,8 +1079,12 @@ impl<'a> InvariantExecutor<'a> { // Identifiers are specified as an array, so we loop through them. for identifier in artifacts { // Try to find the contract by name or identifier in the project's contracts. - if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier) + if let Some((_, contract_data)) = + self.project_contracts.iter().find(|(artifact, _)| { + &artifact.name == identifier || &artifact.identifier() == identifier + }) { + let abi = &contract_data.abi; combined // Check if there's an entry for the given key in the 'combined' map. .entry(*addr) @@ -778,7 +1094,13 @@ impl<'a> InvariantExecutor<'a> { entry.abi.functions.extend(abi.functions.clone()); }) // Otherwise insert it into the map. - .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi)); + .or_insert_with(|| { + let mut contract = + TargetedContract::new(identifier.clone(), abi.clone()); + contract.storage_layout = + contract_data.storage_layout.as_ref().map(Arc::clone); + contract + }); } } } @@ -801,10 +1123,16 @@ impl<'a> InvariantExecutor<'a> { } } + let mut target_test_selectors = vec![]; + let mut excluded_test_selectors = vec![]; + // Collect contract functions marked as target for fuzzing campaign. let selectors = self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {}); for IInvariantTest::FuzzSelector { addr, selectors } in selectors { + if addr == address { + target_test_selectors = selectors.clone(); + } self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?; } @@ -812,9 +1140,40 @@ impl<'a> InvariantExecutor<'a> { let excluded_selectors = self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {}); for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors { + if addr == address { + // If fuzz selector address is the test contract, then record selectors to be + // later excluded if needed. + excluded_test_selectors = selectors.clone(); + } self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?; } + if target_test_selectors.is_empty() + && let Some(target) = targeted_contracts.get(&address) + { + // If test contract is marked as a target and no target selector explicitly set, then + // include only state-changing functions that are not reserved and selectors that are + // not explicitly excluded. + let selectors: Vec<_> = target + .abi + .functions() + .filter_map(|func| { + if matches!( + func.state_mutability, + alloy_json_abi::StateMutability::Pure + | alloy_json_abi::StateMutability::View + ) || func.is_reserved() + || excluded_test_selectors.contains(&func.selector()) + { + None + } else { + Some(func.selector()) + } + }) + .collect(); + self.add_address_with_functions(address, &selectors, false, targeted_contracts)?; + } + Ok(()) } @@ -828,7 +1187,7 @@ impl<'a> InvariantExecutor<'a> { ) -> eyre::Result<()> { // Do not add address in target contracts if no function selected. if selectors.is_empty() { - return Ok(()) + return Ok(()); } let contract = match targeted_contracts.entry(address) { @@ -841,37 +1200,50 @@ impl<'a> InvariantExecutor<'a> { address ) })?; - entry.insert(TargetedContract::new(identifier.clone(), abi.clone())) + entry.insert( + TargetedContract::new(identifier.clone(), abi.clone()) + .with_project_contracts(self.project_contracts), + ) } }; contract.add_selectors(selectors.iter().copied(), should_exclude)?; Ok(()) } + + /// Computes the current invariant settings for the given invariant contract address. + /// + /// This extracts the target contracts, selectors, senders, and failure settings + /// that are used to determine if a persisted counterexample is still valid. + pub fn compute_settings(&mut self, invariant_address: Address) -> Result { + self.select_contract_artifacts(invariant_address)?; + let (sender_filters, targeted_contracts) = + self.select_contracts_and_senders(invariant_address)?; + let targets = targeted_contracts.targets.lock(); + Ok(InvariantSettings::new(&targets, &sender_filters, self.config.fail_on_revert)) + } } /// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA /// before inserting it into the dictionary. Otherwise, we flood the dictionary with /// randomly generated addresses. -fn collect_data( - invariant_test: &InvariantTest, - state_changeset: &mut HashMap, +fn collect_data( + invariant_test: &InvariantTest, + state_changeset: &mut AddressMap, tx: &BasicTxDetails, - call_result: &RawCallResult, + call_result: &RawCallResult, run_depth: u32, ) { // Verify it has no code. - let mut has_code = false; - if let Some(Some(code)) = + let has_code = if let Some(Some(code)) = state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref()) { - has_code = !code.is_empty(); - } + !code.is_empty() + } else { + false + }; // We keep the nonce changes to apply later. - let mut sender_changeset = None; - if !has_code { - sender_changeset = state_changeset.remove(&tx.sender); - } + let sender_changeset = if has_code { None } else { state_changeset.remove(&tx.sender) }; // Collect values from fuzzed call result and add them to fuzz dictionary. invariant_test.fuzz_state.collect_values_from_call( @@ -883,6 +1255,13 @@ fn collect_data( run_depth, ); + // Inject typed sancov trace-cmp operands into the fuzz dictionary. + if let Some(cmp_values) = &call_result.sancov_cmp_values { + invariant_test.fuzz_state.collect_typed_cmp_values( + cmp_values.iter().map(|s| (s.width, alloy_primitives::B256::from(s.value))), + ); + } + // Re-add changes if let Some(changed) = sender_changeset { state_changeset.insert(tx.sender, changed); @@ -892,10 +1271,10 @@ fn collect_data( /// Calls the `afterInvariant()` function on a contract. /// Returns call result and if call succeeded. /// The state after the call is not persisted. -pub(crate) fn call_after_invariant_function( - executor: &Executor, +pub(crate) fn call_after_invariant_function( + executor: &Executor, to: Address, -) -> std::result::Result<(RawCallResult, bool), EvmError> { +) -> Result<(RawCallResult, bool), EvmError> { let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR); let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?; let success = executor.is_raw_call_mut_success(to, &mut call_result, false); @@ -903,12 +1282,142 @@ pub(crate) fn call_after_invariant_function( } /// Calls the invariant function and returns call result and if succeeded. -pub(crate) fn call_invariant_function( - executor: &Executor, +pub(crate) fn call_invariant_function( + executor: &Executor, address: Address, calldata: Bytes, -) -> Result<(RawCallResult, bool)> { +) -> Result<(RawCallResult, bool)> { let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?; let success = executor.is_raw_call_mut_success(address, &mut call_result, false); Ok((call_result, success)) } + +/// Executes a fuzz call and returns the result. +/// Applies any block timestamp (warp) and block number (roll) adjustments before the call. +pub(crate) fn execute_tx( + executor: &mut Executor, + tx: &BasicTxDetails, +) -> Result> { + let warp = tx.warp.unwrap_or_default(); + let roll = tx.roll.unwrap_or_default(); + + if warp > 0 || roll > 0 { + // Apply pre-call block adjustments to the executor's env. + let ts = executor.evm_env().block_env.timestamp(); + let num = executor.evm_env().block_env.number(); + executor.evm_env_mut().block_env.set_timestamp(ts + warp); + executor.evm_env_mut().block_env.set_number(num + roll); + + // Also update the inspector's cheatcodes.block if set. + // The inspector's block may override the env during interpreter initialization, + // so we need to add our warp/roll on top of any existing cheatcode-set values. + let block_env = executor.evm_env().block_env.clone(); + if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { + if let Some(block) = cheatcodes.block.as_mut() { + let bts = block.timestamp(); + let bnum = block.number(); + block.set_timestamp(bts + warp); + block.set_number(bnum + roll); + } else { + cheatcodes.block = Some(block_env); + } + } + } + + executor + .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), U256::ZERO) + .map_err(|e| eyre!(format!("Could not make raw evm call: {e}"))) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn invariant_progress_json_includes_throughput_fields() { + let mut throughput = InvariantThroughputMetrics::default(); + throughput.record_call(20); + throughput.record_call(30); + + let payload = build_invariant_progress_json( + 123, + "invariant_balance", + &json!({ "corpus_count": 7 }), + Some(I256::try_from(42).unwrap()), + throughput, + &InvariantFailureMetrics::default(), + Duration::from_secs(10), + ); + + assert_eq!(payload["timestamp"], json!(123)); + assert_eq!(payload["invariant"], json!("invariant_balance")); + assert_eq!(payload["metrics"]["corpus_count"], json!(7)); + assert_eq!(payload["total_txs"], json!(2)); + assert_eq!(payload["total_gas"], json!(50)); + assert!((payload["tx_per_sec"].as_f64().unwrap() - 0.2).abs() < 1e-12); + assert!((payload["gas_per_sec"].as_f64().unwrap() - 5.0).abs() < 1e-12); + assert_eq!(payload["optimization_best"], json!("42")); + } + + #[test] + fn invariant_progress_json_zero_elapsed_reports_zero_rates() { + let mut throughput = InvariantThroughputMetrics::default(); + throughput.record_call(21_000); + + let payload = build_invariant_progress_json( + 456, + "invariant_zero_elapsed", + &json!({ "corpus_count": 1 }), + None, + throughput, + &InvariantFailureMetrics::default(), + Duration::ZERO, + ); + + assert_eq!(payload["tx_per_sec"], json!(0.0)); + assert_eq!(payload["gas_per_sec"], json!(0.0)); + assert!(payload.get("optimization_best").is_none()); + } + + #[test] + fn invariant_progress_json_includes_failure_counts() { + let mut failure_metrics = InvariantFailureMetrics::default(); + failure_metrics.record_failure("invariant_a", "TestContract", "revert"); + failure_metrics.record_failure("invariant_a", "TestContract", "revert"); + failure_metrics.record_failure("invariant_b", "TestContract", "assertion failed"); + + let payload = build_invariant_progress_json( + 789, + "invariant_a", + &json!({ "corpus_count": 5 }), + None, + InvariantThroughputMetrics::default(), + &failure_metrics, + Duration::from_secs(1), + ); + + assert_eq!(payload["metrics"]["failures"], json!(3)); + assert_eq!(payload["metrics"]["unique_failures"], json!(2)); + } + + #[test] + fn failure_metrics_tracks_total_and_unique_failures() { + let mut metrics = InvariantFailureMetrics::default(); + metrics.record_failure("invariant_a", "TestContract", "revert"); + metrics.record_failure("invariant_a", "TestContract", "revert"); + metrics.record_failure("invariant_b", "TestContract", "assertion failed"); + + assert_eq!(metrics.failures, 3); + assert_eq!(metrics.unique_failures.len(), 2); + assert!(metrics.unique_failures.contains("invariant_a")); + assert!(metrics.unique_failures.contains("invariant_b")); + } + + #[test] + fn failure_metrics_default_is_zero() { + let metrics = InvariantFailureMetrics::default(); + assert_eq!(metrics.failures, 0); + assert!(metrics.unique_failures.is_empty()); + } +} diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index ab11f3728ae99..19fc7cdc7e45d 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -1,34 +1,32 @@ -use super::{ - call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, - shrink_sequence, +use super::{call_after_invariant_function, call_invariant_function, execute_tx}; +use crate::executors::{ + EarlyExit, Executor, + invariant::shrink::{shrink_sequence, shrink_sequence_value}, }; -use crate::executors::Executor; use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{map::HashMap, Log, U256}; +use alloy_primitives::{I256, Log, map::HashMap}; use eyre::Result; use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_config::InvariantConfig; +use foundry_evm_core::evm::FoundryEvmNetwork; use foundry_evm_coverage::HitMaps; -use foundry_evm_fuzz::{ - invariant::{BasicTxDetails, InvariantContract}, - BaseCounterExample, -}; -use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces}; +use foundry_evm_fuzz::{BaseCounterExample, BasicTxDetails, invariant::InvariantContract}; +use foundry_evm_traces::{TraceKind, TraceMode, Traces, load_contracts}; use indicatif::ProgressBar; use parking_lot::RwLock; -use proptest::test_runner::TestError; use std::sync::Arc; /// Replays a call sequence for collecting logs and traces. /// Returns counterexample to be used when the call sequence is a failed scenario. #[expect(clippy::too_many_arguments)] -pub fn replay_run( +pub fn replay_run( invariant_contract: &InvariantContract<'_>, - mut executor: Executor, + mut executor: Executor, known_contracts: &ContractsByArtifact, mut ided_contracts: ContractsByAddress, logs: &mut Vec, traces: &mut Traces, - coverage: &mut Option, + line_coverage: &mut Option, deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>, inputs: &[BasicTxDetails], show_solidity: bool, @@ -42,16 +40,13 @@ pub fn replay_run( // Replay each call from the sequence, collect logs, traces and coverage. for tx in inputs { - let call_result = executor.transact_raw( - tx.sender, - tx.call_details.target, - tx.call_details.calldata.clone(), - U256::ZERO, - )?; - - logs.extend(call_result.logs); + let mut call_result = execute_tx(&mut executor, tx)?; + logs.extend(call_result.logs.clone()); traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - HitMaps::merge_opt(coverage, call_result.coverage); + HitMaps::merge_opt(line_coverage, call_result.line_coverage.clone()); + + // Commit state changes to persist across calls in the sequence. + executor.commit(&mut call_result); // Identify newly generated contracts, if they exist. ided_contracts @@ -59,9 +54,7 @@ pub fn replay_run( // Create counter example to be used in failed case. counterexample_sequence.push(BaseCounterExample::from_invariant_call( - tx.sender, - tx.call_details.target, - &tx.call_details.calldata, + tx, &ided_contracts, call_result.traces, show_solidity, @@ -98,59 +91,77 @@ pub fn replay_run( Ok(counterexample_sequence) } -/// Replays the error case, shrinks the failing sequence and collects all necessary traces. +/// Replays and shrinks a call sequence, collecting logs and traces. +/// +/// For check mode (target_value=None): shrinks to find shortest failing sequence. +/// For optimization mode (target_value=Some): shrinks to find shortest sequence producing target. #[expect(clippy::too_many_arguments)] -pub fn replay_error( - failed_case: &FailedInvariantCaseData, +pub fn replay_error( + config: InvariantConfig, + mut executor: Executor, + calls: &[BasicTxDetails], + inner_sequence: Option>>, + expect_assertion_failure: bool, + target_value: Option, invariant_contract: &InvariantContract<'_>, - mut executor: Executor, known_contracts: &ContractsByArtifact, ided_contracts: ContractsByAddress, logs: &mut Vec, traces: &mut Traces, - coverage: &mut Option, + line_coverage: &mut Option, deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>, progress: Option<&ProgressBar>, - show_solidity: bool, + early_exit: &EarlyExit, ) -> Result> { - match failed_case.test_error { - // Don't use at the moment. - TestError::Abort(_) => Ok(vec![]), - TestError::Fail(_, ref calls) => { - // Shrink sequence of failed calls. - let calls = shrink_sequence( - failed_case, - calls, - &executor, - invariant_contract.call_after_invariant, - progress, - )?; - - set_up_inner_replay(&mut executor, &failed_case.inner_sequence); + let calls = if let Some(target) = target_value { + shrink_sequence_value( + &config, + invariant_contract, + calls, + &executor, + target, + progress, + early_exit, + )? + } else { + shrink_sequence( + &config, + invariant_contract, + calls, + expect_assertion_failure, + &executor, + progress, + early_exit, + )? + }; - // Replay calls to get the counterexample and to collect logs, traces and coverage. - replay_run( - invariant_contract, - executor, - known_contracts, - ided_contracts, - logs, - traces, - coverage, - deprecated_cheatcodes, - &calls, - show_solidity, - ) - } + if let Some(sequence) = inner_sequence { + set_up_inner_replay(&mut executor, &sequence); } + + replay_run( + invariant_contract, + executor, + known_contracts, + ided_contracts, + logs, + traces, + line_coverage, + deprecated_cheatcodes, + &calls, + config.show_solidity, + ) } /// Sets up the calls generated by the internal fuzzer, if they exist. -fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { - if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer { - if let Some(call_generator) = &mut fuzzer.call_generator { - call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); - call_generator.set_replay(true); - } +fn set_up_inner_replay( + executor: &mut Executor, + inner_sequence: &[Option], +) { + if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer + && let Some(call_generator) = &mut fuzzer.call_generator + { + call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); + call_generator.set_replay(true); } } diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs index 8920a1209342a..0c14ae1641998 100644 --- a/crates/evm/evm/src/executors/invariant/result.rs +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -1,17 +1,26 @@ use super::{ - call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, InvariantFailures, InvariantFuzzError, InvariantMetrics, InvariantTest, InvariantTestRun, + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, }; use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::I256; +use alloy_sol_types::{Panic, PanicKind, Revert, SolError, SolInterface}; use eyre::Result; use foundry_config::InvariantConfig; -use foundry_evm_core::utils::StateChangeset; +use foundry_evm_core::{ + abi::Vm, + constants::CHEATCODE_ADDRESS, + decode::{ASSERTION_FAILED_PREFIX, decode_console_log}, + evm::FoundryEvmNetwork, + utils::StateChangeset, +}; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ - invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract}, - FuzzedCases, + BasicTxDetails, FuzzedCases, + invariant::{FuzzRunIdentifiedContracts, InvariantContract}, }; +use revm::interpreter::InstructionResult; use revm_inspectors::tracing::CallTraceArena; use std::{borrow::Cow, collections::HashMap}; @@ -29,42 +38,105 @@ pub struct InvariantFuzzTestResult { /// Additional traces used for gas report construction. pub gas_report_traces: Vec>, /// The coverage info collected during the invariant test runs. - pub coverage: Option, + pub line_coverage: Option, /// Fuzzed selectors metrics collected during the invariant test runs. pub metrics: HashMap, + /// Number of failed replays from persisted corpus. + pub failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best (maximum) value achieved. + /// None means standard invariant check mode. + pub optimization_best_value: Option, + /// For optimization mode: the call sequence that produced the best value. + pub optimization_best_sequence: Vec, } /// Enriched results of an invariant run check. /// /// Contains the success condition and call results of the last run -pub(crate) struct RichInvariantResults { +pub(crate) struct RichInvariantResults { pub(crate) can_continue: bool, - pub(crate) call_result: Option, + pub(crate) call_result: Option>, } -impl RichInvariantResults { - fn new(can_continue: bool, call_result: Option) -> Self { +impl RichInvariantResults { + pub(crate) const fn new(can_continue: bool, call_result: Option>) -> Self { Self { can_continue, call_result } } } +/// Returns true if this call failed due to a Solidity assertion: +/// - `Panic(0x01)`, or +/// - legacy invalid opcode assert behavior. +pub(crate) fn is_assertion_failure( + call_result: &RawCallResult, +) -> bool { + if !call_result.reverted { + return false; + } + + is_assert_panic(call_result.result.as_ref()) + || matches!(call_result.exit_reason, Some(InstructionResult::InvalidFEOpcode)) + || is_revert_assertion_failure(call_result.result.as_ref()) + || is_cheatcode_assert_revert(call_result) +} + +fn is_assert_panic(data: &[u8]) -> bool { + Panic::abi_decode(data).is_ok_and(|panic| panic == PanicKind::Assert.into()) +} + +fn is_revert_assertion_failure(data: &[u8]) -> bool { + Revert::abi_decode(data).is_ok_and(|revert| revert.reason.contains(ASSERTION_FAILED_PREFIX)) +} + +fn is_cheatcode_assert_revert(call_result: &RawCallResult) -> bool { + fn decoded_cheatcode_message(data: &[u8]) -> Option { + Vm::VmErrors::abi_decode(data).ok().map(|error| error.to_string()) + } + + call_result.reverter == Some(CHEATCODE_ADDRESS) + && decoded_cheatcode_message(call_result.result.as_ref()) + .is_some_and(|message| message.starts_with(ASSERTION_FAILED_PREFIX)) +} + +fn logged_assertion_failure(call_result: &RawCallResult) -> bool { + call_result + .logs + .iter() + .filter_map(decode_console_log) + .any(|msg| msg.starts_with(ASSERTION_FAILED_PREFIX)) +} + +/// Returns whether the current fuzz call should be treated as an assertion failure. +/// +/// This covers Solidity `assert`, legacy invalid-opcode assertions, `vm.assert*` reverts, and the +/// non-reverting `GLOBAL_FAIL_SLOT` path used when `assertions_revert = false`. +pub(crate) fn did_fail_on_assert( + call_result: &RawCallResult, + state_changeset: &StateChangeset, +) -> bool { + is_assertion_failure(call_result) + || call_result.has_state_snapshot_failure + || Executor::::has_pending_global_failure(state_changeset) + || logged_assertion_failure(call_result) +} + /// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the /// external `invariant_failures.failed_invariant` map and returns a generic error. /// Either returns the call result if successful, or nothing if there was an error. -pub(crate) fn assert_invariants( +pub(crate) fn assert_invariants( invariant_contract: &InvariantContract<'_>, invariant_config: &InvariantConfig, targeted_contracts: &FuzzRunIdentifiedContracts, - executor: &Executor, + executor: &Executor, calldata: &[BasicTxDetails], invariant_failures: &mut InvariantFailures, -) -> Result> { +) -> Result>> { let mut inner_sequence = vec![]; - if let Some(fuzzer) = &executor.inspector().fuzzer { - if let Some(call_generator) = &fuzzer.call_generator { - inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); - } + if let Some(fuzzer) = &executor.inspector().fuzzer + && let Some(call_generator) = &fuzzer.call_generator + { + inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); } let (call_result, success) = call_invariant_function( @@ -93,15 +165,19 @@ pub(crate) fn assert_invariants( /// Returns if invariant test can continue and last successful call result of the invariant test /// function (if it can continue). -pub(crate) fn can_continue( +/// +/// For optimization mode (int256 return), tracks the max value but never fails on invariant. +/// For check mode, asserts the invariant and fails if broken. +pub(crate) fn can_continue( invariant_contract: &InvariantContract<'_>, - invariant_test: &InvariantTest, - invariant_run: &mut InvariantTestRun, + invariant_test: &mut InvariantTest, + invariant_run: &mut InvariantTestRun, invariant_config: &InvariantConfig, - call_result: RawCallResult, + call_result: RawCallResult, state_changeset: &StateChangeset, -) -> Result { +) -> Result> { let mut call_results = None; + let is_optimization = invariant_contract.is_optimization(); let handlers_succeeded = || { invariant_test.targeted_contracts.targets.lock().keys().all(|address| { @@ -114,29 +190,54 @@ pub(crate) fn can_continue( }) }; - // Assert invariants if the call did not revert and the handlers did not fail. if !call_result.reverted && handlers_succeeded() { if let Some(traces) = call_result.traces { invariant_run.run_traces.push(traces); } - call_results = assert_invariants( - invariant_contract, - invariant_config, - &invariant_test.targeted_contracts, - &invariant_run.executor, - &invariant_run.inputs, - &mut invariant_test.execution_data.borrow_mut().failures, - )?; - if call_results.is_none() { - return Ok(RichInvariantResults::new(false, None)); + if is_optimization { + // Optimization mode: call invariant and track max value, never fail. + let (inv_result, success) = call_invariant_function( + &invariant_run.executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + if success + && inv_result.result.len() >= 32 + && let Some(value) = I256::try_from_be_slice(&inv_result.result[..32]) + { + invariant_test.update_optimization_value(value, &invariant_run.inputs); + // Track the best value and its prefix length for this run + // (used for corpus persistence — materialized once at run end). + if invariant_run.optimization_value.is_none_or(|prev| value > prev) { + invariant_run.optimization_value = Some(value); + invariant_run.optimization_prefix_len = invariant_run.inputs.len(); + } + } + call_results = Some(inv_result); + } else { + // Check mode: assert invariants and fail if broken. + call_results = assert_invariants( + invariant_contract, + invariant_config, + &invariant_test.targeted_contracts, + &invariant_run.executor, + &invariant_run.inputs, + &mut invariant_test.test_data.failures, + )?; + if call_results.is_none() { + return Ok(RichInvariantResults::new(false, None)); + } } } else { - // Increase the amount of reverts. - let mut invariant_data = invariant_test.execution_data.borrow_mut(); - invariant_data.failures.reverts += 1; - // If fail on revert is set, we must return immediately. - if invariant_config.fail_on_revert { + let invariant_data = &mut invariant_test.test_data; + let is_assert_failure = did_fail_on_assert(&call_result, state_changeset); + + if call_result.reverted { + invariant_data.failures.reverts += 1; + } + + if is_assert_failure || (call_result.reverted && invariant_config.fail_on_revert) { let case_data = FailedInvariantCaseData::new( invariant_contract, invariant_config, @@ -144,14 +245,20 @@ pub(crate) fn can_continue( &invariant_run.inputs, call_result, &[], - ); + ) + .with_assertion_failure(is_assert_failure); invariant_data.failures.revert_reason = Some(case_data.revert_reason.clone()); - invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data)); + invariant_data.failures.error = Some(if is_assert_failure { + InvariantFuzzError::BrokenInvariant(case_data) + } else { + InvariantFuzzError::Revert(case_data) + }); return Ok(RichInvariantResults::new(false, None)); - } else if call_result.reverted { - // If we don't fail test on revert then remove last reverted call from inputs. - // This improves shrinking performance as irrelevant calls won't be checked again. + } else if call_result.reverted && !is_optimization && !invariant_config.has_delay() { + // If we don't fail test on revert then remove the reverted call from inputs. + // Delay-enabled campaigns keep reverted calls so shrinking can preserve their + // warp/roll contribution when building the final counterexample. invariant_run.inputs.pop(); } } @@ -160,10 +267,10 @@ pub(crate) fn can_continue( /// Given the executor state, asserts conditions within `afterInvariant` function. /// If call fails then the invariant test is considered failed. -pub(crate) fn assert_after_invariant( +pub(crate) fn assert_after_invariant( invariant_contract: &InvariantContract<'_>, - invariant_test: &InvariantTest, - invariant_run: &InvariantTestRun, + invariant_test: &mut InvariantTest, + invariant_run: &InvariantTestRun, invariant_config: &InvariantConfig, ) -> Result { let (call_result, success) = @@ -182,3 +289,83 @@ pub(crate) fn assert_after_invariant( } Ok(success) } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::Bytes; + use foundry_evm_core::evm::EthEvmNetwork; + + fn panic_payload(code: u8) -> Bytes { + let mut payload = vec![0_u8; 36]; + payload[..4].copy_from_slice(&[0x4e, 0x48, 0x7b, 0x71]); + payload[35] = code; + payload.into() + } + + #[test] + fn detects_assert_panic_code() { + let call_result = RawCallResult:: { + reverted: true, + result: panic_payload(0x01), + ..Default::default() + }; + assert!(is_assertion_failure(&call_result)); + } + + #[test] + fn ignores_non_assert_panic_code() { + let call_result = RawCallResult:: { + reverted: true, + result: panic_payload(0x11), + ..Default::default() + }; + assert!(!is_assertion_failure(&call_result)); + } + + #[test] + fn detects_legacy_invalid_opcode_assert() { + let call_result = RawCallResult:: { + reverted: true, + exit_reason: Some(InstructionResult::InvalidFEOpcode), + ..Default::default() + }; + assert!(is_assertion_failure(&call_result)); + } + + #[test] + fn detects_vm_assert_revert() { + let call_result = RawCallResult:: { + reverted: true, + result: Vm::CheatcodeError { message: format!("{ASSERTION_FAILED_PREFIX}: 1 != 2") } + .abi_encode() + .into(), + reverter: Some(CHEATCODE_ADDRESS), + ..Default::default() + }; + assert!(is_assertion_failure(&call_result)); + } + + #[test] + fn detects_assertion_failure_revert_reason() { + let call_result = RawCallResult:: { + reverted: true, + result: Revert { reason: format!("{ASSERTION_FAILED_PREFIX}: expected") } + .abi_encode() + .into(), + ..Default::default() + }; + assert!(is_assertion_failure(&call_result)); + } + + #[test] + fn ignores_empty_cheatcode_revert() { + let call_result = RawCallResult:: { + reverted: true, + result: Bytes::new(), + reverter: Some(CHEATCODE_ADDRESS), + ..Default::default() + }; + assert!(!is_assertion_failure(&call_result)); + } +} diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs index c468c58eefa63..2096e5edc27f2 100644 --- a/crates/evm/evm/src/executors/invariant/shrink.rs +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -1,20 +1,19 @@ use crate::executors::{ + EarlyExit, EvmError, Executor, RawCallResult, invariant::{ - call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + call_after_invariant_function, call_invariant_function, execute_tx, + result::did_fail_on_assert, }, - Executor, }; -use alloy_primitives::{Address, Bytes, U256}; -use foundry_evm_core::constants::MAGIC_ASSUME; -use foundry_evm_fuzz::invariant::BasicTxDetails; +use alloy_primitives::{Address, Bytes, I256, U256}; +use foundry_config::InvariantConfig; +use foundry_evm_core::{ + FoundryBlock, constants::MAGIC_ASSUME, decode::RevertDecoder, evm::FoundryEvmNetwork, +}; +use foundry_evm_fuzz::{BasicTxDetails, invariant::InvariantContract}; use indicatif::ProgressBar; use proptest::bits::{BitSetLike, VarBitSet}; -use std::cmp::min; - -#[derive(Clone, Copy, Debug)] -struct Shrink { - call_index: usize, -} +use revm::context::Block; /// Shrinker for a call sequence failure. /// Iterates sequence call sequence top down and removes calls one by one. @@ -26,20 +25,11 @@ struct CallSequenceShrinker { call_sequence_len: usize, /// Call ids contained in current shrunk sequence. included_calls: VarBitSet, - /// Current shrunk call id. - shrink: Shrink, - /// Previous shrunk call id. - prev_shrink: Option, } impl CallSequenceShrinker { fn new(call_sequence_len: usize) -> Self { - Self { - call_sequence_len, - included_calls: VarBitSet::saturated(call_sequence_len), - shrink: Shrink { call_index: 0 }, - prev_shrink: None, - } + Self { call_sequence_len, included_calls: VarBitSet::saturated(call_sequence_len) } } /// Return candidate shrink sequence to be tested, by removing ids from original sequence. @@ -47,94 +37,152 @@ impl CallSequenceShrinker { (0..self.call_sequence_len).filter(|&call_id| self.included_calls.test(call_id)) } - /// Removes next call from sequence. - fn simplify(&mut self) -> bool { - if self.shrink.call_index >= self.call_sequence_len { - // We reached the end of call sequence, nothing left to simplify. - false - } else { - // Remove current call. - self.included_calls.clear(self.shrink.call_index); - // Record current call as previous call. - self.prev_shrink = Some(self.shrink); - // Remove next call index - self.shrink = Shrink { call_index: self.shrink.call_index + 1 }; - true - } - } - - /// Reverts removed call from sequence and tries to simplify next call. - fn complicate(&mut self) -> bool { - match self.prev_shrink { - Some(shrink) => { - // Undo the last call removed. - self.included_calls.set(shrink.call_index); - self.prev_shrink = None; - // Try to simplify next call. - self.simplify() + /// Advance to the next call index, wrapping around to 0 at the end. + const fn next_index(&self, call_idx: usize) -> usize { + if call_idx + 1 == self.call_sequence_len { 0 } else { call_idx + 1 } + } +} + +/// Resets the progress bar for shrinking. +fn reset_shrink_progress(config: &InvariantConfig, progress: Option<&ProgressBar>) { + if let Some(progress) = progress { + progress.set_length(config.shrink_run_limit as u64); + progress.reset(); + progress.set_message(" Shrink"); + } +} + +/// Applies accumulated warp/roll to a call, returning a modified copy. +fn apply_warp_roll(call: &BasicTxDetails, warp: U256, roll: U256) -> BasicTxDetails { + let mut result = call.clone(); + if warp > U256::ZERO { + result.warp = Some(warp); + } + if roll > U256::ZERO { + result.roll = Some(roll); + } + result +} + +/// Applies warp/roll adjustments directly to the executor's environment. +fn apply_warp_roll_to_env( + executor: &mut Executor, + warp: U256, + roll: U256, +) { + if warp > U256::ZERO || roll > U256::ZERO { + let ts = executor.evm_env().block_env.timestamp(); + let num = executor.evm_env().block_env.number(); + executor.evm_env_mut().block_env.set_timestamp(ts + warp); + executor.evm_env_mut().block_env.set_number(num + roll); + + let block_env = executor.evm_env().block_env.clone(); + if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() { + if let Some(block) = cheatcodes.block.as_mut() { + let bts = block.timestamp(); + let bnum = block.number(); + block.set_timestamp(bts + warp); + block.set_number(bnum + roll); + } else { + cheatcodes.block = Some(block_env); } - None => false, } } } -/// Shrinks the failure case to its smallest sequence of calls. -/// -/// Maximal shrinkage is guaranteed if the shrink_run_limit is not set to a value lower than the -/// length of failed call sequence. +/// Builds the final shrunk sequence from the shrinker state. /// -/// The shrunk call sequence always respect the order failure is reproduced as it is tested -/// top-down. -pub(crate) fn shrink_sequence( - failed_case: &FailedInvariantCaseData, +/// When `accumulate_warp_roll` is enabled, warp/roll from removed calls is folded into the next +/// kept call so the final sequence remains reproducible. +fn build_shrunk_sequence( calls: &[BasicTxDetails], - executor: &Executor, - call_after_invariant: bool, + shrinker: &CallSequenceShrinker, + accumulate_warp_roll: bool, +) -> Vec { + if !accumulate_warp_roll { + return shrinker.current().map(|idx| calls[idx].clone()).collect(); + } + + let mut result = Vec::new(); + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + + for (idx, call) in calls.iter().enumerate() { + accumulated_warp += call.warp.unwrap_or(U256::ZERO); + accumulated_roll += call.roll.unwrap_or(U256::ZERO); + + if shrinker.included_calls.test(idx) { + result.push(apply_warp_roll(call, accumulated_warp, accumulated_roll)); + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + } + + result +} + +pub(crate) fn shrink_sequence( + config: &InvariantConfig, + invariant_contract: &InvariantContract<'_>, + calls: &[BasicTxDetails], + expect_assertion_failure: bool, + executor: &Executor, progress: Option<&ProgressBar>, + early_exit: &EarlyExit, ) -> eyre::Result> { trace!(target: "forge::test", "Shrinking sequence of {} calls.", calls.len()); - // Reset run count and display shrinking message. - if let Some(progress) = progress { - progress.set_length(min(calls.len(), failed_case.shrink_run_limit as usize) as u64); - progress.reset(); - progress.set_message(" Shrink"); - } + reset_shrink_progress(config, progress); + let target_address = invariant_contract.address; + let calldata: Bytes = invariant_contract.invariant_function.selector().to_vec().into(); // Special case test: the invariant is *unsatisfiable* - it took 0 calls to // break the invariant -- consider emitting a warning. - let (_, success) = - call_invariant_function(executor, failed_case.addr, failed_case.calldata.clone())?; + let (_, success) = call_invariant_function(executor, target_address, calldata.clone())?; if !success { return Ok(vec![]); } + let accumulate_warp_roll = config.has_delay(); + let mut call_idx = 0; let mut shrinker = CallSequenceShrinker::new(calls.len()); - for _ in 0..failed_case.shrink_run_limit { - // Check candidate sequence result. + + for _ in 0..config.shrink_run_limit { + if early_exit.should_stop() { + break; + } + + shrinker.included_calls.clear(call_idx); + match check_sequence( executor.clone(), calls, shrinker.current().collect(), - failed_case.addr, - failed_case.calldata.clone(), - failed_case.fail_on_revert, - call_after_invariant, + target_address, + calldata.clone(), + CheckSequenceOptions { + accumulate_warp_roll, + fail_on_revert: config.fail_on_revert, + expect_assertion_failure, + call_after_invariant: invariant_contract.call_after_invariant, + rd: None, + }, ) { - // If candidate sequence still fails then shrink more if possible. - Ok((false, _)) if !shrinker.simplify() => break, - // If candidate sequence pass then restore last removed call and shrink other - // calls if possible. - Ok((true, _)) if !shrinker.complicate() => break, + // If candidate sequence still fails, shrink until shortest possible. + Ok((false, _, _)) if shrinker.included_calls.count() == 1 => break, + // Restore last removed call as it caused sequence to pass invariant. + Ok((true, _, _)) => shrinker.included_calls.set(call_idx), _ => {} } if let Some(progress) = progress { progress.inc(1); } + + call_idx = shrinker.next_index(call_idx); } - Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) + Ok(build_shrunk_sequence(calls, &shrinker, accumulate_warp_roll)) } /// Checks if the given call sequence breaks the invariant. @@ -143,41 +191,339 @@ pub(crate) fn shrink_sequence( /// persisted failures. /// Returns the result of invariant check (and afterInvariant call if needed) and if sequence was /// entirely applied. -pub fn check_sequence( - mut executor: Executor, +/// +/// When `options.accumulate_warp_roll` is enabled, warp/roll from removed calls is folded into the +/// next kept call so the candidate sequence stays representable as a concrete counterexample. +pub fn check_sequence( + executor: Executor, calls: &[BasicTxDetails], sequence: Vec, test_address: Address, calldata: Bytes, - fail_on_revert: bool, - call_after_invariant: bool, -) -> eyre::Result<(bool, bool)> { + options: CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { + if options.accumulate_warp_roll { + check_sequence_with_accumulation(executor, calls, sequence, test_address, calldata, options) + } else { + check_sequence_simple(executor, calls, sequence, test_address, calldata, options) + } +} + +fn check_sequence_simple( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + options: CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { // Apply the call sequence. for call_index in sequence { let tx = &calls[call_index]; - let call_result = executor.transact_raw( - tx.sender, - tx.call_details.target, - tx.call_details.calldata.clone(), - U256::ZERO, - )?; + let mut call_result = execute_tx(&mut executor, tx)?; + let assertion_failure = did_fail_on_assert(&call_result, &call_result.state_changeset); // Ignore calls reverted with `MAGIC_ASSUME`. This is needed to handle failed scenarios that // are replayed with a modified version of test driver (that use new `vm.assume` // cheatcodes). - if call_result.reverted && fail_on_revert && call_result.result.as_ref() != MAGIC_ASSUME { - // Candidate sequence fails test. - // We don't have to apply remaining calls to check sequence. - return Ok((false, false)); + if call_result.result.as_ref() != MAGIC_ASSUME { + if assertion_failure { + return Ok((false, false, assertion_failure_reason(call_result, options.rd))); + } + + if call_result.reverted && options.fail_on_revert { + if options.expect_assertion_failure { + return Ok((true, false, None)); + } + return Ok((false, false, call_failure_reason(call_result, options.rd))); + } + } + + if !call_result.reverted { + executor.commit(&mut call_result); + } + } + + finish_sequence_check(&executor, test_address, calldata, &options) +} + +fn check_sequence_with_accumulation( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + options: CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + let mut seq_iter = sequence.iter().peekable(); + + for (idx, tx) in calls.iter().enumerate() { + accumulated_warp += tx.warp.unwrap_or(U256::ZERO); + accumulated_roll += tx.roll.unwrap_or(U256::ZERO); + + if seq_iter.peek() != Some(&&idx) { + continue; + } + + seq_iter.next(); + + let tx_with_accumulated = apply_warp_roll(tx, accumulated_warp, accumulated_roll); + let mut call_result = execute_tx(&mut executor, &tx_with_accumulated)?; + let assertion_failure = did_fail_on_assert(&call_result, &call_result.state_changeset); + + if call_result.result.as_ref() != MAGIC_ASSUME { + if assertion_failure { + return Ok((false, false, assertion_failure_reason(call_result, options.rd))); + } + + if call_result.reverted && options.fail_on_revert { + if options.expect_assertion_failure { + return Ok((true, false, None)); + } + return Ok((false, false, call_failure_reason(call_result, options.rd))); + } + } + + if !call_result.reverted { + executor.commit(&mut call_result); + } + + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + + // Unlike optimization mode we intentionally do not apply trailing warp/roll before the + // invariant call: those delays would not be representable in the final shrunk sequence. + finish_sequence_check(&executor, test_address, calldata, &options) +} + +fn finish_sequence_check( + executor: &Executor, + test_address: Address, + calldata: Bytes, + options: &CheckSequenceOptions<'_>, +) -> eyre::Result<(bool, bool, Option)> { + let handle_terminal_failure = |call_result: RawCallResult| { + let should_ignore_failure = options.expect_assertion_failure + && !executor.has_global_failure(&call_result.state_changeset) + && !did_fail_on_assert(&call_result, &call_result.state_changeset); + + if should_ignore_failure { + return (true, true, None); } + + let reason = if options.expect_assertion_failure { + assertion_failure_reason(call_result, options.rd) + } else { + call_failure_reason(call_result, options.rd) + }; + + (false, true, reason) + }; + + let (invariant_result, mut success) = + call_invariant_function(executor, test_address, calldata)?; + if !success { + return Ok(handle_terminal_failure(invariant_result)); } - // Check the invariant for call sequence. - let (_, mut success) = call_invariant_function(&executor, test_address, calldata)?; // Check after invariant result if invariant is success and `afterInvariant` function is // declared. - if success && call_after_invariant { - (_, success) = call_after_invariant_function(&executor, test_address)?; + if success && options.call_after_invariant { + let (after_invariant_result, after_invariant_success) = + call_after_invariant_function(executor, test_address)?; + success = after_invariant_success; + if !success { + return Ok(handle_terminal_failure(after_invariant_result)); + } + } + + Ok((success, true, None)) +} + +pub struct CheckSequenceOptions<'a> { + pub accumulate_warp_roll: bool, + pub fail_on_revert: bool, + pub expect_assertion_failure: bool, + pub call_after_invariant: bool, + pub rd: Option<&'a RevertDecoder>, +} + +fn call_failure_reason( + call_result: RawCallResult, + rd: Option<&RevertDecoder>, +) -> Option { + match call_result.into_evm_error(rd) { + EvmError::Execution(err) => Some(err.reason), + _ => None, + } +} + +fn assertion_failure_reason( + call_result: RawCallResult, + rd: Option<&RevertDecoder>, +) -> Option { + call_failure_reason(call_result, rd).or_else(|| Some("assertion failed".to_string())) +} + +/// Shrinks a call sequence to the shortest sequence that still produces the target optimization +/// value. This is specifically for optimization mode where we want to find the minimal sequence +/// that achieves the maximum value. +/// +/// Unlike `shrink_sequence` (for check mode), this function: +/// - Accumulates warp/roll values from removed calls into the next kept call +/// - Checks for target value equality rather than invariant failure +pub(crate) fn shrink_sequence_value( + config: &InvariantConfig, + invariant_contract: &InvariantContract<'_>, + calls: &[BasicTxDetails], + executor: &Executor, + target_value: I256, + progress: Option<&ProgressBar>, + early_exit: &EarlyExit, +) -> eyre::Result> { + trace!(target: "forge::test", "Shrinking optimization sequence of {} calls for target value {}.", calls.len(), target_value); + + reset_shrink_progress(config, progress); + + let target_address = invariant_contract.address; + let calldata: Bytes = invariant_contract.invariant_function.selector().to_vec().into(); + + // Special case: check if target value is achieved with 0 calls. + if check_sequence_value(executor.clone(), calls, vec![], target_address, calldata.clone())? + == Some(target_value) + { + return Ok(vec![]); } - Ok((success, true)) + let mut call_idx = 0; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + + for _ in 0..config.shrink_run_limit { + if early_exit.should_stop() { + break; + } + + shrinker.included_calls.clear(call_idx); + + let keeps_target = check_sequence_value( + executor.clone(), + calls, + shrinker.current().collect(), + target_address, + calldata.clone(), + )? == Some(target_value); + + if keeps_target { + if shrinker.included_calls.count() == 1 { + break; + } + } else { + shrinker.included_calls.set(call_idx); + } + + if let Some(progress) = progress { + progress.inc(1); + } + + call_idx = shrinker.next_index(call_idx); + } + + Ok(build_shrunk_sequence(calls, &shrinker, true)) +} + +/// Executes a call sequence and returns the optimization value (int256) from the invariant +/// function. Used during shrinking for optimization mode. +/// +/// Returns `None` if the invariant call fails or doesn't return a valid int256. +/// Unlike `check_sequence`, this applies warp/roll from ALL calls (including removed ones). +pub fn check_sequence_value( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, +) -> eyre::Result> { + let mut accumulated_warp = U256::ZERO; + let mut accumulated_roll = U256::ZERO; + let mut seq_iter = sequence.iter().peekable(); + + for (idx, tx) in calls.iter().enumerate() { + accumulated_warp += tx.warp.unwrap_or(U256::ZERO); + accumulated_roll += tx.roll.unwrap_or(U256::ZERO); + + if seq_iter.peek() == Some(&&idx) { + seq_iter.next(); + + let tx_with_accumulated = apply_warp_roll(tx, accumulated_warp, accumulated_roll); + let mut call_result = execute_tx(&mut executor, &tx_with_accumulated)?; + + if !call_result.reverted { + executor.commit(&mut call_result); + } + + accumulated_warp = U256::ZERO; + accumulated_roll = U256::ZERO; + } + } + + // Apply any remaining accumulated warp/roll before calling invariant. + apply_warp_roll_to_env(&mut executor, accumulated_warp, accumulated_roll); + + let (inv_result, success) = call_invariant_function(&executor, test_address, calldata)?; + + if success + && inv_result.result.len() >= 32 + && let Some(value) = I256::try_from_be_slice(&inv_result.result[..32]) + { + return Ok(Some(value)); + } + + Ok(None) +} + +#[cfg(test)] +mod tests { + use super::{CallSequenceShrinker, build_shrunk_sequence}; + use alloy_primitives::{Address, Bytes, U256}; + use foundry_evm_fuzz::{BasicTxDetails, CallDetails}; + use proptest::bits::BitSetLike; + + fn tx(warp: Option, roll: Option) -> BasicTxDetails { + BasicTxDetails { + warp: warp.map(U256::from), + roll: roll.map(U256::from), + sender: Address::ZERO, + call_details: CallDetails { target: Address::ZERO, calldata: Bytes::new() }, + } + } + + #[test] + fn build_shrunk_sequence_accumulates_removed_delay_into_next_kept_call() { + let calls = vec![tx(Some(3), Some(5)), tx(Some(7), Some(11)), tx(Some(13), Some(17))]; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + shrinker.included_calls.clear(0); + + let shrunk = build_shrunk_sequence(&calls, &shrinker, true); + + assert_eq!(shrunk.len(), 2); + assert_eq!(shrunk[0].warp, Some(U256::from(10))); + assert_eq!(shrunk[0].roll, Some(U256::from(16))); + assert_eq!(shrunk[1].warp, Some(U256::from(13))); + assert_eq!(shrunk[1].roll, Some(U256::from(17))); + } + + #[test] + fn build_shrunk_sequence_does_not_move_trailing_delay_backward() { + let calls = vec![tx(Some(3), Some(5)), tx(Some(7), Some(11))]; + let mut shrinker = CallSequenceShrinker::new(calls.len()); + shrinker.included_calls.clear(1); + + let shrunk = build_shrunk_sequence(&calls, &shrinker, true); + + assert_eq!(shrunk.len(), 1); + assert_eq!(shrunk[0].warp, Some(U256::from(3))); + assert_eq!(shrunk[0].roll, Some(U256::from(5))); + } } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index bfd57a4a0ccee..3ffa92b459905 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -6,44 +6,49 @@ // `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of // the concrete `Executor` type. -use crate::{ - inspectors::{ - cheatcodes::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStack, - }, - Env, +use crate::inspectors::{ + Cheatcodes, InspectorData, InspectorStack, cheatcodes::BroadcastableTransactions, }; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{ + Address, Bytes, Log, TxKind, U256, keccak256, map::{AddressHashMap, HashMap}, - Address, Bytes, Log, TxKind, U256, }; -use alloy_sol_types::{sol, SolCall}; +use alloy_sol_types::{SolCall, sol}; use foundry_evm_core::{ + EvmEnv, FoundryBlock, FoundryTransaction, backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt, GLOBAL_FAIL_SLOT}, constants::{ CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER, }, decode::{RevertDecoder, SkipReason}, + evm::{ + EthEvmNetwork, EvmEnvFor, FoundryEvmNetwork, HaltReasonFor, IntoInstructionResult, SpecFor, + TxEnvFor, + }, utils::StateChangeset, - EvmEnv, InspectorExt, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ bytecode::Bytecode, - context::{BlockEnv, TxEnv}, + context::Transaction, context_interface::{ result::{ExecutionResult, Output, ResultAndState}, transaction::SignedAuthorization, }, database::{DatabaseCommit, DatabaseRef}, - interpreter::{return_ok, InstructionResult}, - primitives::hardfork::SpecId, + interpreter::{InstructionResult, return_ok}, }; +use sancov::SancovGuard; use std::{ borrow::Cow, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, time::{Duration, Instant}, }; @@ -56,9 +61,14 @@ pub use fuzz::FuzzedExecutor; pub mod invariant; pub use invariant::InvariantExecutor; +mod corpus; +mod sancov; mod trace; + pub use trace::TracingExecutor; +const DURATION_BETWEEN_METRICS_REPORT: Duration = Duration::from_secs(5); + sol! { interface ITest { function setUp() external; @@ -81,36 +91,36 @@ sol! { /// deployment /// - `setup`: a special case of `transact`, used to set up the environment for a test #[derive(Clone, Debug)] -pub struct Executor { +pub struct Executor { /// The underlying `revm::Database` that contains the EVM storage. + /// + /// Wrapped in `Arc` for efficient cloning during parallel fuzzing. Use [`Arc::make_mut`] + /// for copy-on-write semantics when mutation is needed. // Note: We do not store an EVM here, since we are really // only interested in the database. REVM's `EVM` is a thin // wrapper around spawning a new EVM on every call anyway, // so the performance difference should be negligible. - backend: Backend, - /// The EVM environment. - env: Env, + backend: Arc>, + /// The EVM environment (block and cfg). + evm_env: EvmEnvFor, + /// The transaction environment. + tx_env: TxEnvFor, /// The Revm inspector stack. - inspector: InspectorStack, + inspector: InspectorStack, /// The gas limit for calls and deployments. gas_limit: u64, /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, } -impl Executor { - /// Creates a new `ExecutorBuilder`. - #[inline] - pub fn builder() -> ExecutorBuilder { - ExecutorBuilder::new() - } - +impl Executor { /// Creates a new `Executor` with the given arguments. #[inline] pub fn new( - mut backend: Backend, - env: Env, - inspector: InspectorStack, + mut backend: Backend, + evm_env: EvmEnvFor, + tx_env: TxEnvFor, + inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, ) -> Self { @@ -127,81 +137,103 @@ impl Executor { }, ); - Self { backend, env, inspector, gas_limit, legacy_assertions } + Self { + backend: Arc::new(backend), + evm_env, + tx_env, + inspector, + gas_limit, + legacy_assertions, + } } - fn clone_with_backend(&self, backend: Backend) -> Self { - let env = Env::new_with_spec_id( - self.env.evm_env.cfg_env.clone(), - self.env.evm_env.block_env.clone(), - self.env.tx.clone(), - self.spec_id(), - ); - Self::new(backend, env, self.inspector().clone(), self.gas_limit, self.legacy_assertions) + fn clone_with_backend(&self, backend: Backend) -> Self { + let evm_env = self.evm_env.clone(); + Self { + backend: Arc::new(backend), + evm_env, + tx_env: self.tx_env.clone(), + inspector: self.inspector().clone(), + gas_limit: self.gas_limit, + legacy_assertions: self.legacy_assertions, + } } /// Returns a reference to the EVM backend. - pub fn backend(&self) -> &Backend { + pub fn backend(&self) -> &Backend { &self.backend } /// Returns a mutable reference to the EVM backend. - pub fn backend_mut(&mut self) -> &mut Backend { - &mut self.backend + /// + /// Uses copy-on-write semantics: if other clones of this executor share the backend, + /// this will clone the backend first. + pub fn backend_mut(&mut self) -> &mut Backend { + Arc::make_mut(&mut self.backend) + } + + /// Returns a reference to the EVM environment (block and cfg). + pub const fn evm_env(&self) -> &EvmEnvFor { + &self.evm_env + } + + /// Returns a mutable reference to the EVM environment (block and cfg). + pub const fn evm_env_mut(&mut self) -> &mut EvmEnvFor { + &mut self.evm_env } - /// Returns a reference to the EVM environment. - pub fn env(&self) -> &Env { - &self.env + /// Returns a reference to the transaction environment. + pub const fn tx_env(&self) -> &TxEnvFor { + &self.tx_env } - /// Returns a mutable reference to the EVM environment. - pub fn env_mut(&mut self) -> &mut Env { - &mut self.env + /// Returns a mutable reference to the transaction environment. + pub const fn tx_env_mut(&mut self) -> &mut TxEnvFor { + &mut self.tx_env } /// Returns a reference to the EVM inspector. - pub fn inspector(&self) -> &InspectorStack { + pub const fn inspector(&self) -> &InspectorStack { &self.inspector } /// Returns a mutable reference to the EVM inspector. - pub fn inspector_mut(&mut self) -> &mut InspectorStack { + pub const fn inspector_mut(&mut self) -> &mut InspectorStack { &mut self.inspector } - /// Returns the EVM spec ID. - pub fn spec_id(&self) -> SpecId { - self.env.evm_env.cfg_env.spec + /// Returns the EVM spec. + pub const fn spec_id(&self) -> SpecFor { + self.evm_env.cfg_env.spec } - /// Sets the EVM spec ID. - pub fn set_spec_id(&mut self, spec_id: SpecId) { - self.env.evm_env.cfg_env.spec = spec_id; + /// Sets the EVM spec and updates spec-dependent gas parameters. + pub fn set_spec_id(&mut self, spec_id: SpecFor) { + self.evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id); } /// Returns the gas limit for calls and deployments. /// /// This is different from the gas limit imposed by the passed in environment, as those limits /// are used by the EVM for certain opcodes like `gaslimit`. - pub fn gas_limit(&self) -> u64 { + pub const fn gas_limit(&self) -> u64 { self.gas_limit } /// Sets the gas limit for calls and deployments. - pub fn set_gas_limit(&mut self, gas_limit: u64) { + pub const fn set_gas_limit(&mut self, gas_limit: u64) { self.gas_limit = gas_limit; } /// Returns whether `failed()` should be called on the test contract to determine if the test /// failed. - pub fn legacy_assertions(&self) -> bool { + pub const fn legacy_assertions(&self) -> bool { self.legacy_assertions } /// Sets whether `failed()` should be called on the test contract to determine if the test /// failed. - pub fn set_legacy_assertions(&mut self, legacy_assertions: bool) { + pub const fn set_legacy_assertions(&mut self, legacy_assertions: bool) { self.legacy_assertions = legacy_assertions; } @@ -249,7 +281,7 @@ impl Executor { let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; self.backend_mut().insert_account_info(address, account); - self.env_mut().tx.nonce = nonce; + self.tx_env_mut().set_nonce(nonce); Ok(()) } @@ -258,6 +290,36 @@ impl Executor { Ok(self.backend().basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default()) } + /// Set the code of an account. + pub fn set_code(&mut self, address: Address, code: Bytecode) -> BackendResult<()> { + let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); + account.code_hash = keccak256(code.original_byte_slice()); + account.code = Some(code); + self.backend_mut().insert_account_info(address, account); + Ok(()) + } + + /// Set the storage of an account. + pub fn set_storage( + &mut self, + address: Address, + storage: HashMap, + ) -> BackendResult<()> { + self.backend_mut().replace_account_storage(address, storage)?; + Ok(()) + } + + /// Set a storage slot of an account. + pub fn set_storage_slot( + &mut self, + address: Address, + slot: U256, + value: U256, + ) -> BackendResult<()> { + self.backend_mut().insert_account_storage(address, slot, value)?; + Ok(()) + } + /// Returns `true` if the account has no code. pub fn is_empty_code(&self, address: Address) -> BackendResult { Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true)) @@ -282,7 +344,7 @@ impl Executor { #[inline] pub fn create2_deployer(&self) -> Address { - self.inspector().create2_deployer() + self.inspector().create2_deployer } /// Deploys a contract and commits the new state to the underlying database. @@ -295,9 +357,9 @@ impl Executor { code: Bytes, value: U256, rd: Option<&RevertDecoder>, - ) -> Result { - let env = self.build_test_env(from, TxKind::Create, code, value); - self.deploy_with_env(env, rd) + ) -> Result, EvmError> { + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value); + self.deploy_with_env(evm_env, tx_env, rd) } /// Deploys a contract using the given `env` and commits the new state to the underlying @@ -305,21 +367,22 @@ impl Executor { /// /// # Panics /// - /// Panics if `env.tx.kind` is not `TxKind::Create(_)`. + /// Panics if `tx_env.kind` is not `TxKind::Create(_)`. #[instrument(name = "deploy", level = "debug", skip_all)] pub fn deploy_with_env( &mut self, - env: Env, + evm_env: EvmEnvFor, + tx_env: TxEnvFor, rd: Option<&RevertDecoder>, - ) -> Result { + ) -> Result, EvmError> { assert!( - matches!(env.tx.kind, TxKind::Create), + matches!(tx_env.kind(), TxKind::Create), "Expected create transaction, got {:?}", - env.tx.kind + tx_env.kind() ); - trace!(sender=%env.tx.caller, "deploying contract"); + trace!(sender=%tx_env.caller(), "deploying contract"); - let mut result = self.transact_with_env(env)?; + let mut result = self.transact_with_env(evm_env, tx_env)?; result = result.into_result(rd)?; let Some(Output::Create(_, Some(address))) = result.out else { panic!("Deployment succeeded, but no address was returned: {result:#?}"); @@ -329,7 +392,7 @@ impl Executor { // persistent across fork swaps in forking mode self.backend_mut().add_persistent_account(address); - debug!(%address, "deployed contract"); + trace!(%address, "deployed contract"); Ok(DeployResult { raw: result, address }) } @@ -346,7 +409,7 @@ impl Executor { from: Option
, to: Address, rd: Option<&RevertDecoder>, - ) -> Result { + ) -> Result, EvmError> { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); @@ -356,9 +419,9 @@ impl Executor { res = res.into_result(rd)?; // record any changes made to the block's environment during setup - self.env_mut().evm_env.block_env = res.env.evm_env.block_env.clone(); + self.evm_env_mut().block_env = res.evm_env.block_env.clone(); // and also the chainid, which can be set manually - self.env_mut().evm_env.cfg_env.chain_id = res.env.evm_env.cfg_env.chain_id; + self.evm_env_mut().cfg_env.chain_id = res.evm_env.cfg_env.chain_id; let success = self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false); @@ -378,7 +441,7 @@ impl Executor { args: &[DynSolValue], value: U256, rd: Option<&RevertDecoder>, - ) -> Result { + ) -> Result, EvmError> { let calldata = Bytes::from(func.abi_encode_input(args)?); let result = self.call_raw(from, to, calldata, value)?; result.into_decoded_result(func, rd) @@ -392,7 +455,7 @@ impl Executor { args: &C, value: U256, rd: Option<&RevertDecoder>, - ) -> Result, EvmError> { + ) -> Result, EvmError> { let calldata = Bytes::from(args.abi_encode()); let mut raw = self.call_raw(from, to, calldata, value)?; raw = raw.into_result(rd)?; @@ -408,7 +471,7 @@ impl Executor { args: &[DynSolValue], value: U256, rd: Option<&RevertDecoder>, - ) -> Result { + ) -> Result, EvmError> { let calldata = Bytes::from(func.abi_encode_input(args)?); let result = self.transact_raw(from, to, calldata, value)?; result.into_decoded_result(func, rd) @@ -421,9 +484,9 @@ impl Executor { to: Address, calldata: Bytes, value: U256, - ) -> eyre::Result { - let env = self.build_test_env(from, TxKind::Call(to), calldata, value); - self.call_with_env(env) + ) -> eyre::Result> { + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.call_with_env(evm_env, tx_env) } /// Performs a raw call to an account on the current state of the VM with an EIP-7702 @@ -435,11 +498,11 @@ impl Executor { calldata: Bytes, value: U256, authorization_list: Vec, - ) -> eyre::Result { - let mut env = self.build_test_env(from, to.into(), calldata, value); - env.tx.set_signed_authorization(authorization_list); - env.tx.tx_type = 4; - self.call_with_env(env) + ) -> eyre::Result> { + let (evm_env, mut tx_env) = self.build_test_env(from, to.into(), calldata, value); + tx_env.set_signed_authorization(authorization_list); + tx_env.set_tx_type(4); + self.call_with_env(evm_env, tx_env) } /// Performs a raw call to an account on the current state of the VM. @@ -449,30 +512,90 @@ impl Executor { to: Address, calldata: Bytes, value: U256, - ) -> eyre::Result { - let env = self.build_test_env(from, TxKind::Call(to), calldata, value); - self.transact_with_env(env) + ) -> eyre::Result> { + let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.transact_with_env(evm_env, tx_env) } - /// Execute the transaction configured in `env.tx`. + /// Performs a raw call to an account on the current state of the VM with an EIP-7702 + /// authorization last. + pub fn transact_raw_with_authorization( + &mut self, + from: Address, + to: Address, + calldata: Bytes, + value: U256, + authorization_list: Vec, + ) -> eyre::Result> { + let (evm_env, mut tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value); + tx_env.set_signed_authorization(authorization_list); + tx_env.set_tx_type(4); + self.transact_with_env(evm_env, tx_env) + } + + /// Execute the transaction configured in `tx_env`. /// /// The state after the call is **not** persisted. #[instrument(name = "call", level = "debug", skip_all)] - pub fn call_with_env(&self, mut env: Env) -> eyre::Result { - let mut inspector = self.inspector().clone(); + pub fn call_with_env( + &self, + mut evm_env: EvmEnvFor, + mut tx_env: TxEnvFor, + ) -> eyre::Result> { + let mut stack = self.inspector().clone(); + let sancov_edges = stack.inner.sancov_edges; + let sancov_trace_cmp = stack.inner.sancov_trace_cmp; + let sancov_active = sancov_edges || sancov_trace_cmp; let mut backend = CowBackend::new_borrowed(self.backend()); - let result = backend.inspect(&mut env, &mut inspector)?; - convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) + let result = { + let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp)); + backend.inspect(&mut evm_env, &mut tx_env, &mut stack)? + }; + let mut result = convert_executed_result( + evm_env, + tx_env, + stack, + result, + backend.has_state_snapshot_failure(), + )?; + if sancov_edges { + SancovGuard::append_edges_into(&mut result); + } + if sancov_trace_cmp { + SancovGuard::drain_cmp_into(&mut result); + } + Ok(result) } - /// Execute the transaction configured in `env.tx`. + /// Execute the transaction configured in `tx_env`. #[instrument(name = "transact", level = "debug", skip_all)] - pub fn transact_with_env(&mut self, mut env: Env) -> eyre::Result { - let mut inspector = self.inspector().clone(); + pub fn transact_with_env( + &mut self, + mut evm_env: EvmEnvFor, + mut tx_env: TxEnvFor, + ) -> eyre::Result> { + let mut stack = self.inspector().clone(); + let sancov_edges = stack.inner.sancov_edges; + let sancov_trace_cmp = stack.inner.sancov_trace_cmp; + let sancov_active = sancov_edges || sancov_trace_cmp; let backend = self.backend_mut(); - let result = backend.inspect(&mut env, &mut inspector)?; - let mut result = - convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure())?; + let result = { + let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp)); + backend.inspect(&mut evm_env, &mut tx_env, &mut stack)? + }; + let mut result = convert_executed_result( + evm_env, + tx_env, + stack, + result, + backend.has_state_snapshot_failure(), + )?; + if sancov_edges { + SancovGuard::append_edges_into(&mut result); + } + if sancov_trace_cmp { + SancovGuard::drain_cmp_into(&mut result); + } self.commit(&mut result); Ok(result) } @@ -482,7 +605,7 @@ impl Executor { /// /// This should not be exposed to the user, as it should be called only by `transact*`. #[instrument(name = "commit", level = "debug", skip_all)] - fn commit(&mut self, result: &mut RawCallResult) { + fn commit(&mut self, result: &mut RawCallResult) { // Persist changes to db. self.backend_mut().commit(result.state_changeset.clone()); @@ -501,7 +624,8 @@ impl Executor { } // Persist the changed environment. - self.inspector_mut().set_env(&result.env); + self.inspector_mut().set_block(result.evm_env.block_env.clone()); + self.inspector_mut().set_gas_price(result.tx_env.gas_price()); } /// Returns `true` if a test can be considered successful. @@ -511,7 +635,7 @@ impl Executor { pub fn is_raw_call_mut_success( &self, address: Address, - call_result: &mut RawCallResult, + call_result: &mut RawCallResult, should_fail: bool, ) -> bool { self.is_raw_call_success( @@ -529,7 +653,7 @@ impl Executor { &self, address: Address, state_changeset: Cow<'_, StateChangeset>, - call_result: &RawCallResult, + call_result: &RawCallResult, should_fail: bool, ) -> bool { if call_result.has_state_snapshot_failure { @@ -589,17 +713,8 @@ impl Executor { } // Check the global failure slot. - if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS) { - if let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT) { - if !failed_slot.present_value().is_zero() { - return false; - } - } - } - if let Ok(failed_slot) = self.backend().storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT) { - if !failed_slot.is_zero() { - return false; - } + if self.has_global_failure(&state_changeset) { + return false; } if !self.legacy_assertions { @@ -640,40 +755,80 @@ impl Executor { } } + /// Returns whether the in-flight state changeset for the current call sets the global + /// assertion failure flag. + pub fn has_pending_global_failure(state_changeset: &StateChangeset) -> bool { + if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS) + && let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT) + && !failed_slot.present_value().is_zero() + { + return true; + } + + false + } + + /// Returns whether the global assertion failure flag is set either in the in-flight state + /// changeset or in the committed backend state. + pub fn has_global_failure(&self, state_changeset: &StateChangeset) -> bool { + if Self::has_pending_global_failure(state_changeset) { + return true; + } + + self.backend() + .storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT) + .is_ok_and(|failed_slot| !failed_slot.is_zero()) + } + + /// Clears the global assertion failure flag from both the committed backend state and, when + /// provided, the in-flight state changeset for the current call. + pub fn clear_global_failure( + &mut self, + state_changeset: Option<&mut StateChangeset>, + ) -> BackendResult<()> { + if let Some(state_changeset) = state_changeset + && let Some(acc) = state_changeset.get_mut(&CHEATCODE_ADDRESS) + && let Some(failed_slot) = acc.storage.get_mut(&GLOBAL_FAIL_SLOT) + { + failed_slot.present_value = U256::ZERO; + } + + self.set_storage_slot(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::ZERO) + } + /// Creates the environment to use when executing a transaction in a test context /// /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by /// the cheatcode state in between calls. - fn build_test_env(&self, caller: Address, kind: TxKind, data: Bytes, value: U256) -> Env { - Env { - evm_env: EvmEnv { - cfg_env: { - let mut cfg = self.env().evm_env.cfg_env.clone(); - cfg.spec = self.spec_id(); - cfg - }, - // We always set the gas price to 0 so we can execute the transaction regardless of - // network conditions - the actual gas price is kept in `self.block` and is applied - // by the cheatcode handler if it is enabled - block_env: BlockEnv { - basefee: 0, - gas_limit: self.gas_limit, - ..self.env().evm_env.block_env.clone() - }, - }, - tx: TxEnv { - caller, - kind, - data, - value, - // As above, we set the gas price to 0. - gas_price: 0, - gas_priority_fee: None, - gas_limit: self.gas_limit, - chain_id: Some(self.env().evm_env.cfg_env.chain_id), - ..self.env().tx.clone() - }, - } + fn build_test_env( + &self, + caller: Address, + kind: TxKind, + data: Bytes, + value: U256, + ) -> (EvmEnvFor, TxEnvFor) { + let mut cfg_env = self.evm_env.cfg_env.clone(); + cfg_env.spec = self.spec_id(); + + // We always set the gas price to 0 so we can execute the transaction regardless of + // network conditions - the actual gas price is kept in `self.block` and is applied + // by the cheatcode handler if it is enabled + let mut block_env = self.evm_env.block_env.clone(); + block_env.set_basefee(0); + block_env.set_gas_limit(self.gas_limit); + + let mut tx_env = self.tx_env.clone(); + tx_env.set_caller(caller); + tx_env.set_kind(kind); + tx_env.set_data(data); + tx_env.set_value(value); + // As above, we set the gas price to 0. + tx_env.set_gas_price(0); + tx_env.set_gas_priority_fee(None); + tx_env.set_gas_limit(self.gas_limit); + tx_env.set_chain_id(Some(self.evm_env.cfg_env.chain_id)); + + (EvmEnv { cfg_env, block_env }, tx_env) } pub fn call_sol_default(&self, to: Address, args: &C) -> C::Return @@ -690,15 +845,15 @@ impl Executor { /// Represents the context after an execution error occurred. #[derive(Debug, thiserror::Error)] #[error("execution reverted: {reason} (gas: {})", raw.gas_used)] -pub struct ExecutionErr { +pub struct ExecutionErr { /// The raw result of the call. - pub raw: RawCallResult, + pub raw: RawCallResult, /// The revert reason. pub reason: String, } -impl std::ops::Deref for ExecutionErr { - type Target = RawCallResult; +impl std::ops::Deref for ExecutionErr { + type Target = RawCallResult; #[inline] fn deref(&self) -> &Self::Target { @@ -706,7 +861,7 @@ impl std::ops::Deref for ExecutionErr { } } -impl std::ops::DerefMut for ExecutionErr { +impl std::ops::DerefMut for ExecutionErr { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.raw @@ -714,10 +869,10 @@ impl std::ops::DerefMut for ExecutionErr { } #[derive(Debug, thiserror::Error)] -pub enum EvmError { +pub enum EvmError { /// Error which occurred during execution of a transaction. #[error(transparent)] - Execution(#[from] Box), + Execution(Box>), /// Error which occurred during ABI encoding/decoding. #[error(transparent)] Abi(#[from] alloy_dyn_abi::Error), @@ -733,13 +888,13 @@ pub enum EvmError { ), } -impl From for EvmError { - fn from(err: ExecutionErr) -> Self { +impl From> for EvmError { + fn from(err: ExecutionErr) -> Self { Self::Execution(Box::new(err)) } } -impl From for EvmError { +impl From for EvmError { fn from(err: alloy_sol_types::Error) -> Self { Self::Abi(err.into()) } @@ -747,15 +902,15 @@ impl From for EvmError { /// The result of a deployment. #[derive(Debug)] -pub struct DeployResult { +pub struct DeployResult { /// The raw result of the deployment. - pub raw: RawCallResult, + pub raw: RawCallResult, /// The address of the deployed contract pub address: Address, } -impl std::ops::Deref for DeployResult { - type Target = RawCallResult; +impl std::ops::Deref for DeployResult { + type Target = RawCallResult; #[inline] fn deref(&self) -> &Self::Target { @@ -763,24 +918,24 @@ impl std::ops::Deref for DeployResult { } } -impl std::ops::DerefMut for DeployResult { +impl std::ops::DerefMut for DeployResult { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.raw } } -impl From for RawCallResult { - fn from(d: DeployResult) -> Self { +impl From> for RawCallResult { + fn from(d: DeployResult) -> Self { d.raw } } /// The result of a raw call. #[derive(Debug)] -pub struct RawCallResult { +pub struct RawCallResult { /// The status of the call - pub exit_reason: InstructionResult, + pub exit_reason: Option, /// Whether the call reverted or not pub reverted: bool, /// Whether the call includes a snapshot failure @@ -802,26 +957,36 @@ pub struct RawCallResult { pub labels: AddressHashMap, /// The traces of the call pub traces: Option, - /// The coverage info collected during the call - pub coverage: Option, + /// The line coverage info collected during the call + pub line_coverage: Option, + /// The edge coverage info collected during the call + pub edge_coverage: Option>, + /// Sancov edge coverage from instrumented native Rust crates (e.g. precompiles). + /// Tracked separately from EVM edge coverage to avoid ID-space collisions. + pub sancov_coverage: Option>, + /// Comparison operands captured via sancov trace-cmp callbacks. + pub sancov_cmp_values: Option>, /// Scripted transactions generated from this call - pub transactions: Option, + pub transactions: Option>, /// The changeset of the state. pub state_changeset: StateChangeset, - /// The `revm::Env` after the call - pub env: Env, + /// The `EvmEnv` after the call + pub evm_env: EvmEnvFor, + /// The `TxEnv` after the call + pub tx_env: TxEnvFor, /// The cheatcode states after execution - pub cheatcodes: Option, + pub cheatcodes: Option>>, /// The raw output of the execution pub out: Option, /// The chisel state - pub chisel_state: Option<(Vec, Vec, InstructionResult)>, + pub chisel_state: Option<(Vec, Vec)>, + pub reverter: Option
, } -impl Default for RawCallResult { +impl Default for RawCallResult { fn default() -> Self { Self { - exit_reason: InstructionResult::Continue, + exit_reason: None, reverted: false, has_state_snapshot_failure: false, result: Bytes::new(), @@ -831,20 +996,25 @@ impl Default for RawCallResult { logs: Vec::new(), labels: HashMap::default(), traces: None, - coverage: None, + line_coverage: None, + edge_coverage: None, + sancov_coverage: None, + sancov_cmp_values: None, transactions: None, state_changeset: HashMap::default(), - env: Env::default(), + evm_env: EvmEnv::default(), + tx_env: TxEnvFor::::default(), cheatcodes: Default::default(), out: None, chisel_state: None, + reverter: None, } } } -impl RawCallResult { +impl RawCallResult { /// Unpacks an EVM result. - pub fn from_evm_result(r: Result) -> eyre::Result<(Self, Option)> { + pub fn from_evm_result(r: Result>) -> eyre::Result<(Self, Option)> { match r { Ok(r) => Ok((r, None)), Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))), @@ -852,31 +1022,27 @@ impl RawCallResult { } } - /// Unpacks an execution result. - pub fn from_execution_result(r: Result) -> (Self, Option) { - match r { - Ok(r) => (r, None), - Err(e) => (e.raw, Some(e.reason)), - } - } - /// Converts the result of the call into an `EvmError`. - pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { - if let Some(reason) = SkipReason::decode(&self.result) { + pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { + if self.reverter == Some(CHEATCODE_ADDRESS) + && let Some(reason) = SkipReason::decode(&self.result) + { return EvmError::Skip(reason); } - let reason = rd.unwrap_or_default().decode(&self.result, Some(self.exit_reason)); + let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason); EvmError::Execution(Box::new(self.into_execution_error(reason))) } /// Converts the result of the call into an `ExecutionErr`. - pub fn into_execution_error(self, reason: String) -> ExecutionErr { + pub const fn into_execution_error(self, reason: String) -> ExecutionErr { ExecutionErr { raw: self, reason } } /// Returns an `EvmError` if the call failed, otherwise returns `self`. - pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result { - if self.exit_reason.is_ok() { + pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result> { + if let Some(reason) = self.exit_reason + && reason.is_ok() + { Ok(self) } else { Err(self.into_evm_error(rd)) @@ -888,34 +1054,120 @@ impl RawCallResult { mut self, func: &Function, rd: Option<&RevertDecoder>, - ) -> Result { + ) -> Result, EvmError> { self = self.into_result(rd)?; let mut result = func.abi_decode_output(&self.result)?; - let decoded_result = if result.len() == 1 { - result.pop().unwrap() - } else { - // combine results into a tuple - DynSolValue::Tuple(result) - }; + let decoded_result = + if result.len() == 1 { result.pop().unwrap() } else { DynSolValue::Tuple(result) }; Ok(CallResult { raw: self, decoded_result }) } /// Returns the transactions generated from this call. - pub fn transactions(&self) -> Option<&BroadcastableTransactions> { + pub fn transactions(&self) -> Option<&BroadcastableTransactions> { self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions) } + + /// Update provided history map with edge coverage info collected during this call. + /// Uses AFL binning algo + pub fn merge_edge_coverage(&mut self, history_map: &mut [u8]) -> (bool, bool) { + let mut new_coverage = false; + let mut is_edge = false; + if let Some(x) = &mut self.edge_coverage { + // Iterate over the current map and the history map together and update + // the history map, if we discover some new coverage, report true + for (curr, hist) in std::iter::zip(x, history_map) { + // If we got a hitcount of at least 1 + if *curr > 0 { + // Convert hitcount into bucket count + let bucket = match *curr { + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 4, + 4..=7 => 8, + 8..=15 => 16, + 16..=31 => 32, + 32..=127 => 64, + 128..=255 => 128, + }; + + // If the old record for this edge pair is lower, update + if *hist < bucket { + if *hist == 0 { + // Counts as an edge the first time we see it, otherwise it's a feature. + is_edge = true; + } + *hist = bucket; + new_coverage = true; + } + + // Zero out the current map for next iteration. + *curr = 0; + } + } + } + (new_coverage, is_edge) + } + + /// Update provided history map with sancov coverage info collected during this call. + /// Same AFL binning algo as [`Self::merge_edge_coverage`]. + pub fn merge_sancov_coverage(&mut self, history_map: &mut Vec) -> (bool, bool) { + let mut new_coverage = false; + let mut is_edge = false; + if let Some(x) = &mut self.sancov_coverage { + if history_map.len() < x.len() { + history_map.resize(x.len(), 0); + } + for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) { + if *curr > 0 { + let bucket = match *curr { + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 4, + 4..=7 => 8, + 8..=15 => 16, + 16..=31 => 32, + 32..=127 => 64, + 128..=255 => 128, + }; + if *hist < bucket { + if *hist == 0 { + is_edge = true; + } + *hist = bucket; + new_coverage = true; + } + *curr = 0; + } + } + } + (new_coverage, is_edge) + } + + /// Merge both EVM and sancov coverage into their respective history maps. + /// Returns `(new_coverage, is_edge)` — true if either domain produced new coverage. + pub fn merge_all_coverage( + &mut self, + evm_history: &mut [u8], + sancov_history: &mut Vec, + ) -> (bool, bool) { + let (new_evm, edge_evm) = self.merge_edge_coverage(evm_history); + let (new_san, edge_san) = self.merge_sancov_coverage(sancov_history); + (new_evm || new_san, edge_evm || edge_san) + } } /// The result of a call. -pub struct CallResult { +pub struct CallResult { /// The raw result of the call. - pub raw: RawCallResult, + pub raw: RawCallResult, /// The decoded result of the call. pub decoded_result: T, } -impl std::ops::Deref for CallResult { - type Target = RawCallResult; +impl std::ops::Deref for CallResult { + type Target = RawCallResult; #[inline] fn deref(&self) -> &Self::Target { @@ -923,7 +1175,7 @@ impl std::ops::Deref for CallResult { } } -impl std::ops::DerefMut for CallResult { +impl std::ops::DerefMut for CallResult { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.raw @@ -931,31 +1183,27 @@ impl std::ops::DerefMut for CallResult { } /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` -fn convert_executed_result( - env: Env, - inspector: InspectorStack, - ResultAndState { result, state: state_changeset }: ResultAndState, +fn convert_executed_result( + evm_env: EvmEnvFor, + tx_env: TxEnvFor, + inspector: InspectorStack, + ResultAndState { result, state: state_changeset }: ResultAndState>, has_state_snapshot_failure: bool, -) -> eyre::Result { +) -> eyre::Result> { let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result { - ExecutionResult::Success { reason, gas_used, gas_refunded, output, logs, .. } => { - (reason.into(), gas_refunded, gas_used, Some(output), logs) + ExecutionResult::Success { reason, gas, output, logs } => { + (reason.into(), gas.final_refunded(), gas.tx_gas_used(), Some(output), logs) } - ExecutionResult::Revert { gas_used, output } => { - // Need to fetch the unused gas - (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output)), vec![]) + ExecutionResult::Revert { gas, output, logs } => { + (InstructionResult::Revert, 0_u64, gas.tx_gas_used(), Some(Output::Call(output)), logs) } - ExecutionResult::Halt { reason, gas_used } => { - (reason.into(), 0_u64, gas_used, None, vec![]) + ExecutionResult::Halt { reason, gas, logs } => { + (reason.into_instruction_result(), 0_u64, gas.tx_gas_used(), None, logs) } }; - let gas = revm::interpreter::gas::calculate_initial_tx_gas( - env.evm_env.cfg_env.spec, - &env.tx.data, - env.tx.kind.is_create(), - env.tx.access_list.len().try_into()?, - 0, - 0, + let gas = revm::interpreter::gas::calculate_initial_tx_gas_for_tx( + &tx_env, + evm_env.cfg_env.spec.into(), ); let result = match &out { @@ -963,8 +1211,16 @@ fn convert_executed_result( _ => Bytes::new(), }; - let InspectorData { mut logs, labels, traces, coverage, cheatcodes, chisel_state } = - inspector.collect(); + let InspectorData { + mut logs, + labels, + traces, + line_coverage, + edge_coverage, + cheatcodes, + chisel_state, + reverter, + } = inspector.collect(); if logs.is_empty() { logs = exec_logs; @@ -976,23 +1232,28 @@ fn convert_executed_result( .filter(|txs| !txs.is_empty()); Ok(RawCallResult { - exit_reason, + exit_reason: Some(exit_reason), reverted: !matches!(exit_reason, return_ok!()), has_state_snapshot_failure, result, gas_used, gas_refunded, - stipend: gas.initial_gas, + stipend: gas.initial_total_gas, logs, labels, traces, - coverage, + line_coverage, + edge_coverage, + sancov_coverage: None, + sancov_cmp_values: None, transactions, state_changeset, - env, + evm_env, + tx_env, cheatcodes, out, chisel_state, + reverter, }) } @@ -1007,8 +1268,115 @@ impl FuzzTestTimer { Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) } } + /// Whether the fuzz test timer is enabled. + pub const fn is_enabled(&self) -> bool { + self.inner.is_some() + } + /// Whether the current fuzz test timed out and should be stopped. pub fn is_timed_out(&self) -> bool { self.inner.is_some_and(|(start, duration)| start.elapsed() > duration) } } + +/// Helper struct to enable early exit behavior: when one test fails or run is interrupted, +/// all other tests stop early. +#[derive(Clone, Debug)] +pub struct EarlyExit { + /// Shared atomic flag set to `true` when a failure occurs or ctrl-c received. + inner: Arc, + /// Whether to exit early on test failure (fail-fast mode). + fail_fast: bool, +} + +impl EarlyExit { + pub fn new(fail_fast: bool) -> Self { + Self { inner: Arc::new(AtomicBool::new(false)), fail_fast } + } + + /// Records a test failure. Only triggers early exit if fail-fast mode is enabled. + pub fn record_failure(&self) { + if self.fail_fast { + self.inner.store(true, Ordering::Relaxed); + } + } + + /// Records a Ctrl-C interrupt. Always triggers early exit. + pub fn record_ctrl_c(&self) { + self.inner.store(true, Ordering::Relaxed); + } + + /// Whether tests should stop and exit early. + pub fn should_stop(&self) -> bool { + self.inner.load(Ordering::Relaxed) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_evm_core::constants::MAGIC_SKIP; + use revm::{context::Cfg, primitives::hardfork::SpecId}; + + #[test] + fn cheatcode_skip_payload_is_classified_as_skip() { + let raw = RawCallResult:: { + result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"), + reverter: Some(CHEATCODE_ADDRESS), + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Skip(_))); + } + + #[test] + fn forged_skip_payload_from_non_cheatcode_is_execution_error() { + let raw = RawCallResult:: { + result: Bytes::from_static(MAGIC_SKIP), + reverter: Some(CALLER), + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Execution(_))); + } + + #[test] + fn skip_payload_without_reverter_is_execution_error() { + let raw = RawCallResult:: { + result: Bytes::from_static(MAGIC_SKIP), + reverter: None, + ..Default::default() + }; + + let err = raw.into_evm_error(None); + assert!(matches!(err, EvmError::Execution(_))); + } + + #[test] + fn set_spec_id_updates_spec_dependent_cfg_state() { + let backend = Backend::::spawn(None).unwrap(); + let mut executor = ExecutorBuilder::default().build( + EvmEnvFor::::default(), + TxEnvFor::::default(), + backend, + ); + + executor.evm_env_mut().cfg_env.set_spec_and_mainnet_gas_params(SpecId::HOMESTEAD); + assert_eq!( + executor.evm_env().cfg_env.gas_params(), + &revm::context_interface::cfg::GasParams::new_spec(SpecId::HOMESTEAD), + ); + assert!(!executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled()); + + executor.set_spec_id(SpecId::AMSTERDAM); + + assert_eq!(executor.spec_id(), SpecId::AMSTERDAM); + assert_eq!( + executor.evm_env().cfg_env.gas_params(), + &revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM), + ); + assert!(executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled()); + } +} diff --git a/crates/evm/evm/src/executors/sancov.rs b/crates/evm/evm/src/executors/sancov.rs new file mode 100644 index 0000000000000..660069b5563d6 --- /dev/null +++ b/crates/evm/evm/src/executors/sancov.rs @@ -0,0 +1,70 @@ +use super::RawCallResult; +use foundry_evm_core::evm::FoundryEvmNetwork; + +const SANCOV_BUFFER_CAPACITY: usize = 65536; + +/// RAII guard that activates sancov coverage collection for the duration of an EVM call. +/// +/// Allocates a thread-local scratch buffer for sancov hits and sets it as the active coverage map. +/// After execution, sancov hits are appended to the call result separately from EVM edge coverage. +pub(super) struct SancovGuard { + collect_edges: bool, +} + +thread_local! { + static SANCOV_BUFFER: std::cell::RefCell> = + std::cell::RefCell::new(vec![0u8; SANCOV_BUFFER_CAPACITY]); +} + +impl SancovGuard { + pub(super) fn new(collect_edges: bool, collect_trace_cmp: bool) -> Self { + if collect_edges { + SANCOV_BUFFER.with(|buf| { + let mut buf = buf.borrow_mut(); + buf.fill(0); + let ptr = buf.as_mut_ptr(); + let len = buf.len(); + foundry_evm_sancov::set_coverage_map(ptr, len); + }); + } + if collect_trace_cmp { + foundry_evm_sancov::clear_cmp_operands(); + } + Self { collect_edges } + } + + /// Populate the result's sancov coverage buffer with edge hits. + pub(super) fn append_edges_into(result: &mut RawCallResult) { + let sancov_used = foundry_evm_sancov::sancov_edge_count(); + if sancov_used == 0 { + return; + } + + SANCOV_BUFFER.with(|buf| { + let buf = buf.borrow(); + let sancov_slice = &buf[..sancov_used.min(buf.len())]; + + if !sancov_slice.iter().any(|&b| b > 0) { + return; + } + + result.sancov_coverage = Some(sancov_slice.to_vec()); + }); + } + + /// Drain captured comparison operands and attach them to the result for dictionary injection. + pub(super) fn drain_cmp_into(result: &mut RawCallResult) { + let cmp_values = foundry_evm_sancov::drain_cmp_operands(); + if !cmp_values.is_empty() { + result.sancov_cmp_values = Some(cmp_values); + } + } +} + +impl Drop for SancovGuard { + fn drop(&mut self) { + if self.collect_edges { + foundry_evm_sancov::clear_coverage_map(); + } + } +} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index c58ee8ddd6e59..93e02cdba870f 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,72 +1,111 @@ -use crate::{ - executors::{Executor, ExecutorBuilder}, - Env, -}; -use alloy_primitives::Address; +use crate::executors::{Executor, ExecutorBuilder}; +use alloy_primitives::{Address, U256, map::HashMap}; +use alloy_rpc_types::state::StateOverride; +use eyre::Context; use foundry_compilers::artifacts::EvmVersion; -use foundry_config::{utils::evm_spec_id, Chain, Config}; -use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; +use foundry_config::{Chain, Config, evm_spec_id}; +use foundry_evm_core::{ + backend::Backend, + evm::{BlockEnvFor, EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, + fork::CreateFork, + opts::EvmOpts, +}; +use foundry_evm_networks::NetworkConfigs; use foundry_evm_traces::TraceMode; -use revm::primitives::hardfork::SpecId; +use revm::{context::Transaction, state::Bytecode}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled -pub struct TracingExecutor { - executor: Executor, +pub struct TracingExecutor { + executor: Executor, } -impl TracingExecutor { +impl TracingExecutor { pub fn new( - env: Env, - fork: Option, + env: (EvmEnvFor, TxEnvFor), + fork: CreateFork, version: Option, trace_mode: TraceMode, - odyssey: bool, + networks: NetworkConfigs, create2_deployer: Address, + state_overrides: Option, ) -> eyre::Result { - let db = Backend::spawn(fork)?; - Ok(Self { - // configures a bare version of the evm executor: no cheatcode inspector is enabled, - // tracing will be enabled only for the targeted transaction - executor: ExecutorBuilder::new() - .inspectors(|stack| { - stack.trace_mode(trace_mode).odyssey(odyssey).create2_deployer(create2_deployer) - }) - .spec_id(evm_spec_id(version.unwrap_or_default(), odyssey)) - .build(env, db), - }) + let db = Backend::spawn(Some(fork))?; + // configures a bare version of the evm executor: no cheatcode and log_collector inspector + // is enabled, tracing will be enabled only for the targeted transaction + let mut executor = ExecutorBuilder::default() + .inspectors(|stack| { + stack.trace_mode(trace_mode).networks(networks).create2_deployer(create2_deployer) + }) + .spec_id_opt(version.map(evm_spec_id::>)) + .build(env.0, env.1, db); + + // Apply the state overrides. + if let Some(state_overrides) = state_overrides { + for (address, overrides) in state_overrides { + if let Some(balance) = overrides.balance { + executor.set_balance(address, balance)?; + } + if let Some(nonce) = overrides.nonce { + executor.set_nonce(address, nonce)?; + } + if let Some(code) = overrides.code { + let bytecode = Bytecode::new_raw_checked(code) + .wrap_err("invalid bytecode in state override")?; + executor.set_code(address, bytecode)?; + } + if let Some(state) = overrides.state { + let state: HashMap = state + .into_iter() + .map(|(slot, value)| (slot.into(), value.into())) + .collect(); + executor.set_storage(address, state)?; + } + if let Some(state_diff) = overrides.state_diff { + for (slot, value) in state_diff { + executor.set_storage_slot(address, slot.into(), value.into())?; + } + } + } + } + + Ok(Self { executor }) } /// Returns the spec id of the executor - pub fn spec_id(&self) -> SpecId { + pub const fn spec_id(&self) -> SpecFor { self.executor.spec_id() } /// uses the fork block number from the config pub async fn get_fork_material( - config: &Config, + config: &mut Config, mut evm_opts: EvmOpts, - ) -> eyre::Result<(Env, Option, Option, bool)> { + ) -> eyre::Result<(EvmEnvFor, TxEnvFor, CreateFork, Chain, NetworkConfigs)> { evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); evm_opts.fork_block_number = config.fork_block_number; - let env = evm_opts.evm_env().await?; + let (evm_env, tx_env, fork_block) = + evm_opts.env::, BlockEnvFor, TxEnvFor>().await?; - let fork = evm_opts.get_fork(config, env.clone()); + let fork = evm_opts.get_fork(config, evm_env.cfg_env.chain_id, fork_block).unwrap(); + let networks = evm_opts.networks.with_chain_id(evm_env.cfg_env.chain_id); + config.labels.extend(networks.precompiles_label()); - Ok((env, fork, evm_opts.get_remote_chain_id().await, evm_opts.odyssey)) + let chain = tx_env.chain_id().unwrap().into(); + Ok((evm_env, tx_env, fork, chain, networks)) } } -impl Deref for TracingExecutor { - type Target = Executor; +impl Deref for TracingExecutor { + type Target = Executor; fn deref(&self) -> &Self::Target { &self.executor } } -impl DerefMut for TracingExecutor { +impl DerefMut for TracingExecutor { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.executor } diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs index 67ff8255a261e..1388f3c18140f 100644 --- a/crates/evm/evm/src/inspectors/chisel_state.rs +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -1,12 +1,7 @@ use alloy_primitives::U256; -use foundry_evm_core::backend::DatabaseError; use revm::{ - context::ContextTr, - inspector::JournalExt, - interpreter::{ - interpreter::EthInterpreter, interpreter_types::Jumps, InstructionResult, Interpreter, - }, - Database, Inspector, + Inspector, + interpreter::{Interpreter, interpreter_types::Jumps}, }; /// An inspector for Chisel @@ -15,32 +10,26 @@ pub struct ChiselState { /// The PC of the final instruction pub final_pc: usize, /// The final state of the REPL contract call - pub state: Option<(Vec, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec)>, } impl ChiselState { /// Create a new Chisel state inspector. #[inline] - pub fn new(final_pc: usize) -> Self { + pub const fn new(final_pc: usize) -> Self { Self { final_pc, state: None } } } -impl Inspector for ChiselState -where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, -{ +impl Inspector for ChiselState { #[cold] - fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) { + fn step_end(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { // If we are at the final pc of the REPL contract execution, set the state. // Subtraction can't overflow because `pc` is always at least 1 in `step_end`. - if self.final_pc == interp.bytecode.pc() - 1 { + if self.final_pc == interpreter.bytecode.pc() - 1 { self.state = Some(( - interp.stack.data().clone(), - interp.memory.context_memory().to_vec(), - interp.control.instruction_result, + interpreter.stack.data().clone(), + interpreter.memory.context_memory().to_vec(), )) } } diff --git a/crates/evm/evm/src/inspectors/custom_printer.rs b/crates/evm/evm/src/inspectors/custom_printer.rs index a35958ee550d0..c2471425b493c 100644 --- a/crates/evm/evm/src/inspectors/custom_printer.rs +++ b/crates/evm/evm/src/inspectors/custom_printer.rs @@ -2,18 +2,16 @@ //! It is a great tool if some debugging is needed. use foundry_common::sh_println; -use foundry_evm_core::backend::DatabaseError; use revm::{ + Inspector, bytecode::opcode::OpCode, context::{ContextTr, JournalTr}, - inspector::{inspectors::GasInspector, JournalExt}, + inspector::inspectors::GasInspector, interpreter::{ - interpreter::EthInterpreter, - interpreter_types::{Jumps, LoopControl, MemoryTr}, CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, + interpreter_types::{Jumps, MemoryTr}, }, primitives::{Address, U256}, - Database, Inspector, }; /// Custom print [Inspector], it has step level information of execution. @@ -24,14 +22,9 @@ pub struct CustomPrintTracer { gas_inspector: GasInspector, } -impl Inspector for CustomPrintTracer -where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, -{ +impl Inspector for CustomPrintTracer { fn initialize_interp(&mut self, interp: &mut Interpreter, _context: &mut CTX) { - self.gas_inspector.initialize_interp(&interp.control.gas); + self.gas_inspector.initialize_interp(&interp.gas); } // get opcode by calling `interp.contract.opcode(interp.program_counter())`. @@ -52,17 +45,17 @@ where gas_remaining, name, opcode, - interp.control.gas.refunded(), - interp.control.gas.refunded(), + interp.gas.refunded(), + interp.gas.refunded(), interp.stack.data(), memory_size, ); - self.gas_inspector.step(&interp.control.gas); + self.gas_inspector.step(&interp.gas); } - fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) { - self.gas_inspector.step_end(interp.control.gas_mut()); + fn step_end(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { + self.gas_inspector.step_end(&interpreter.gas); } fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, outcome: &mut CallOutcome) { @@ -94,11 +87,11 @@ where fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option { let _ = sh_println!( "CREATE CALL: caller:{:?}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", - inputs.caller, - inputs.scheme, - inputs.value, - inputs.init_code, - inputs.gas_limit + inputs.caller(), + inputs.scheme(), + inputs.value(), + inputs.init_code(), + inputs.gas_limit() ); None } diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 32af7bdbce6c8..c591b9426bf9e 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,60 +1,88 @@ use alloy_primitives::Log; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; -use foundry_common::{fmt::ConsoleFmt, ErrorExt}; +use foundry_common::{ErrorExt, fmt::ConsoleFmt, sh_println}; use foundry_evm_core::{ - abi::console, backend::DatabaseError, constants::HARDHAT_CONSOLE_ADDRESS, InspectorExt, + InspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS, decode::decode_console_log, }; use revm::{ + Inspector, context::ContextTr, - inspector::JournalExt, - interpreter::{ - interpreter::EthInterpreter, CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, - InterpreterResult, - }, - Database, Inspector, + interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult}, }; /// An inspector that collects logs during execution. /// /// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs. -#[derive(Clone, Debug, Default)] -pub struct LogCollector { +#[derive(Clone, Debug)] +pub enum LogCollector { /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs. - pub logs: Vec, + Capture { logs: Vec }, + /// Print logs directly to stdout. + LiveLogs, } impl LogCollector { + pub fn into_captured_logs(self) -> Option> { + match self { + Self::Capture { logs } => Some(logs), + Self::LiveLogs => None, + } + } + #[cold] - fn do_hardhat_log(&mut self, context: &mut CTX, inputs: &CallInputs) -> Option - where - CTX: ContextTr, Journal: JournalExt>, - { + fn do_hardhat_log( + &mut self, + context: &mut CTX, + inputs: &CallInputs, + ) -> Option { if let Err(err) = self.hardhat_log(&inputs.input.bytes(context)) { let result = InstructionResult::Revert; let output = err.abi_encode_revert(); return Some(CallOutcome { result: InterpreterResult { result, output, gas: Gas::new(inputs.gas_limit) }, memory_offset: inputs.return_memory_offset.clone(), - }) + was_precompile_called: true, + precompile_call_logs: vec![], + }); } None } fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { let decoded = console::hh::ConsoleCalls::abi_decode(data)?; - self.logs.push(hh_to_ds(&decoded)); + for line in decoded.fmt(Default::default()).lines() { + self.push_msg(line); + } Ok(()) } + + fn push_raw_log(&mut self, log: Log) { + match self { + Self::Capture { logs } => logs.push(log), + Self::LiveLogs => { + if let Some(msg) = decode_console_log(&log) { + sh_println!("{msg}").expect("fail printing to stdout"); + } else { + // This case should not happen if the users call through forge-std. + // We print the log data for the user nonetheless. + sh_println!("console.log({:?}, {})", log.data.topics(), log.data.data) + .expect("fail printing to stdout"); + } + } + } + } + + fn push_msg(&mut self, msg: &str) { + match self { + Self::Capture { logs } => logs.push(new_console_log(msg)), + Self::LiveLogs => sh_println!("{msg}").expect("fail printing to stdout"), + } + } } -impl Inspector for LogCollector -where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, -{ - fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { - self.logs.push(log); +impl Inspector for LogCollector { + fn log(&mut self, _context: &mut CTX, log: Log) { + self.push_raw_log(log); } fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option { @@ -67,17 +95,10 @@ where impl InspectorExt for LogCollector { fn console_log(&mut self, msg: &str) { - self.logs.push(new_console_log(msg)); + self.push_msg(msg); } } -/// Converts a Hardhat `console.log` call to a DSTest `log(string)` event. -fn hh_to_ds(call: &console::hh::ConsoleCalls) -> Log { - // Convert the parameters of the call to their string representation using `ConsoleFmt`. - let msg = call.fmt(Default::default()); - new_console_log(&msg) -} - /// Creates a `console.log(string)` event. fn new_console_log(msg: &str) -> Log { Log::new_unchecked( diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index 85ed1354ea11a..839c037426d45 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -1,7 +1,7 @@ //! EVM inspectors. pub use foundry_cheatcodes::{self as cheatcodes, Cheatcodes, CheatsConfig}; -pub use foundry_evm_coverage::CoverageCollector; +pub use foundry_evm_coverage::LineCoverageCollector; pub use foundry_evm_fuzz::Fuzzer; pub use foundry_evm_traces::{StackSnapshotType, TracingInspector, TracingInspectorConfig}; @@ -24,3 +24,6 @@ pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; mod revert_diagnostic; pub use revert_diagnostic::RevertDiagnostic; + +mod tempo_labels; +pub(crate) use tempo_labels::TempoLabels; diff --git a/crates/evm/evm/src/inspectors/revert_diagnostic.rs b/crates/evm/evm/src/inspectors/revert_diagnostic.rs index c5da1b5b9c1b9..e26a4ee9aaa9a 100644 --- a/crates/evm/evm/src/inspectors/revert_diagnostic.rs +++ b/crates/evm/evm/src/inspectors/revert_diagnostic.rs @@ -1,26 +1,22 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; -use foundry_evm_core::{ - backend::DatabaseError, - constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, -}; +use foundry_evm_core::constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}; use revm::{ + Inspector, bytecode::opcode, context::{ContextTr, JournalTr}, - inspector::JournalExt, interpreter::{ - interpreter::EthInterpreter, interpreter_types::Jumps, CallInputs, CallOutcome, CallScheme, - InstructionResult, Interpreter, InterpreterAction, InterpreterResult, + CallInputs, CallOutcome, CallScheme, InstructionResult, Interpreter, InterpreterAction, + interpreter_types::{Jumps, LoopControl}, }, - Database, Inspector, }; use std::fmt; const IGNORE: [Address; 2] = [HARDHAT_CONSOLE_ADDRESS, CHEATCODE_ADDRESS]; /// Checks if the call scheme corresponds to any sort of delegate call -pub fn is_delegatecall(scheme: CallScheme) -> bool { - matches!(scheme, CallScheme::DelegateCall | CallScheme::ExtDelegateCall | CallScheme::CallCode) +pub const fn is_delegatecall(scheme: CallScheme) -> bool { + matches!(scheme, CallScheme::DelegateCall | CallScheme::CallCode) } #[derive(Debug, Clone, Copy)] @@ -62,26 +58,22 @@ impl fmt::Display for DetailedRevertReason { #[derive(Clone, Debug, Default)] pub struct RevertDiagnostic { /// Tracks calls with calldata that target an address without executable code. - pub non_contract_call: Option<(Address, CallScheme, usize)>, + non_contract_call: Option<(Address, CallScheme, usize)>, /// Tracks EXTCODESIZE checks that target an address without executable code. - pub non_contract_size_check: Option<(Address, usize)>, + non_contract_size_check: Option<(Address, usize)>, /// Whether the step opcode is EXTCODESIZE or not. - pub is_extcodesize_step: bool, + is_extcodesize_step: bool, } impl RevertDiagnostic { /// Returns the effective target address whose code would be executed. /// For delegate calls, this is the `bytecode_address`. Otherwise, it's the `target_address`. - fn code_target_address(&self, inputs: &mut CallInputs) -> Address { - if is_delegatecall(inputs.scheme) { - inputs.bytecode_address - } else { - inputs.target_address - } + const fn code_target_address(&self, inputs: &mut CallInputs) -> Address { + if is_delegatecall(inputs.scheme) { inputs.bytecode_address } else { inputs.target_address } } /// Derives the revert reason based on the cached data. Should only be called after a revert. - fn reason(&self) -> Option { + const fn reason(&self) -> Option { if let Some((addr, scheme, _)) = self.non_contract_call { let reason = if is_delegatecall(scheme) { DetailedRevertReason::DelegateCallToNonContract(addr) @@ -101,16 +93,13 @@ impl RevertDiagnostic { } /// Injects the revert diagnostic into the debug traces. Should only be called after a revert. - fn broadcast_diagnostic(&self, interp: &mut Interpreter) { + fn broadcast_diagnostic(&self, interpreter: &mut Interpreter) { if let Some(reason) = self.reason() { - interp.control.instruction_result = InstructionResult::Revert; - interp.control.next_action = InterpreterAction::Return { - result: InterpreterResult { - output: reason.to_string().abi_encode().into(), - gas: interp.control.gas, - result: InstructionResult::Revert, - }, - }; + interpreter.bytecode.set_action(InterpreterAction::new_return( + InstructionResult::Revert, + reason.to_string().abi_encode().into(), + interpreter.gas, + )); } } @@ -120,32 +109,27 @@ impl RevertDiagnostic { /// - if `non_contract_size_check` was set at the current depth, `broadcast_diagnostic` is /// called. Otherwise, it is cleared. #[cold] - fn handle_revert(&mut self, interp: &mut Interpreter, ctx: &mut CTX) - where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, - { + fn handle_revert(&mut self, interp: &mut Interpreter, ctx: &mut CTX) { // REVERT (offset, size) - if let Ok(size) = interp.stack.peek(1) { - if size.is_zero() { - // Check empty revert with same depth as a non-contract call - if let Some((_, _, depth)) = self.non_contract_call { - if ctx.journal_ref().depth() == depth { - self.broadcast_diagnostic(interp); - } else { - self.non_contract_call = None; - } - return; + if let Ok(size) = interp.stack.peek(1) + && size.is_zero() + { + // Check empty revert with same depth as a non-contract call + if let Some((_, _, depth)) = self.non_contract_call { + if ctx.journal_ref().depth() == depth { + self.broadcast_diagnostic(interp); + } else { + self.non_contract_call = None; } + return; + } - // Check empty revert with same depth as a non-contract size check - if let Some((_, depth)) = self.non_contract_size_check { - if depth == ctx.journal_ref().depth() { - self.broadcast_diagnostic(interp); - } else { - self.non_contract_size_check = None; - } + // Check empty revert with same depth as a non-contract size check + if let Some((_, depth)) = self.non_contract_size_check { + if depth == ctx.journal_ref().depth() { + self.broadcast_diagnostic(interp); + } else { + self.non_contract_size_check = None; } } } @@ -155,12 +139,7 @@ impl RevertDiagnostic { /// - Optimistically caches the target address and current depth in `non_contract_size_check`, /// pending later validation. #[cold] - fn handle_extcodesize(&mut self, interp: &mut Interpreter, ctx: &mut CTX) - where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, - { + fn handle_extcodesize(&mut self, interp: &mut Interpreter, ctx: &mut CTX) { // EXTCODESIZE (address) if let Ok(word) = interp.stack.peek(0) { let addr = Address::from_word(word.into()); @@ -178,22 +157,17 @@ impl RevertDiagnostic { /// Tracks `EXTCODESIZE` output. If the bytecode size is NOT 0, clears the cache. #[cold] fn handle_extcodesize_output(&mut self, interp: &mut Interpreter) { - if let Ok(size) = interp.stack.peek(0) { - if size != U256::ZERO { - self.non_contract_size_check = None; - } + if let Ok(size) = interp.stack.peek(0) + && size != U256::ZERO + { + self.non_contract_size_check = None; } self.is_extcodesize_step = false; } } -impl Inspector for RevertDiagnostic -where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, -{ +impl Inspector for RevertDiagnostic { /// Tracks the first call with non-zero calldata that targets a non-contract address. Excludes /// precompiles and test addresses. fn call(&mut self, ctx: &mut CTX, inputs: &mut CallInputs) -> Option { @@ -203,10 +177,11 @@ where return None; } - if let Ok(state) = ctx.journal().code(target) { - if state.is_empty() && !inputs.input.is_empty() { - self.non_contract_call = Some((target, inputs.scheme, ctx.journal_ref().depth())); - } + if let Ok(state) = ctx.journal_mut().code(target) + && state.is_empty() + && !inputs.input.is_empty() + { + self.non_contract_call = Some((target, inputs.scheme, ctx.journal_ref().depth())); } None } diff --git a/crates/evm/evm/src/inspectors/script.rs b/crates/evm/evm/src/inspectors/script.rs index 205215f08db08..7e9ad84d2ffb3 100644 --- a/crates/evm/evm/src/inspectors/script.rs +++ b/crates/evm/evm/src/inspectors/script.rs @@ -1,15 +1,11 @@ -use alloy_evm::Database; -use alloy_primitives::Address; -use foundry_common::sh_err; -use foundry_evm_core::backend::DatabaseError; +use alloy_primitives::{Address, Bytes}; use revm::{ + Inspector, bytecode::opcode::ADDRESS, - context::ContextTr, - inspector::JournalExt, interpreter::{ - interpreter::EthInterpreter, interpreter_types::Jumps, InstructionResult, Interpreter, + InstructionResult, Interpreter, InterpreterAction, + interpreter_types::{Jumps, LoopControl}, }, - Inspector, }; /// An inspector that enforces certain rules during script execution. @@ -21,29 +17,19 @@ pub struct ScriptExecutionInspector { pub script_address: Address, } -impl Inspector for ScriptExecutionInspector -where - D: Database, - CTX: ContextTr, - CTX::Journal: JournalExt, -{ - #[inline] +impl Inspector for ScriptExecutionInspector { fn step(&mut self, interpreter: &mut Interpreter, _ecx: &mut CTX) { // Check if both target and bytecode address are the same as script contract address // (allow calling external libraries when bytecode address is different). - if interpreter.bytecode.opcode() == ADDRESS && - interpreter.input.target_address == self.script_address && - interpreter.input.bytecode_address == Some(self.script_address) + if interpreter.bytecode.opcode() == ADDRESS + && interpreter.input.target_address == self.script_address + && interpreter.input.bytecode_address == Some(self.script_address) { - // Log the reason for revert - let _ = sh_err!( - "Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon." - ); - // Set the instruction result to Revert to stop execution - interpreter.control.instruction_result = InstructionResult::Revert; + interpreter.bytecode.set_action(InterpreterAction::new_return( + InstructionResult::Revert, + Bytes::from("Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon."), + interpreter.gas, + )); } - // Note: We don't return anything here as step returns void. - // The original check returned InstructionResult::Continue, but that's the default - // behavior. } } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 520de5f45e2aa..2a3bce3dac89e 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,46 +1,59 @@ use super::{ - Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, CustomPrintTracer, Fuzzer, - LogCollector, RevertDiagnostic, ScriptExecutionInspector, TracingInspector, + Cheatcodes, CheatsConfig, ChiselState, CustomPrintTracer, Fuzzer, LineCoverageCollector, + LogCollector, RevertDiagnostic, ScriptExecutionInspector, TempoLabels, TracingInspector, }; -use alloy_evm::{eth::EthEvmContext, Evm}; use alloy_primitives::{ - map::{AddressHashMap, HashMap}, - Address, Bytes, Log, TxKind, U256, + Address, B256, Bytes, Log, TxKind, U256, + map::{AddressHashMap, AddressMap}, }; -use foundry_cheatcodes::{CheatcodesExecutor, Wallets}; + +use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosure, Wallets}; +use foundry_common::compile::Analysis; use foundry_evm_core::{ - backend::{DatabaseExt, JournaledState}, - evm::new_evm_with_inspector, - ContextExt, Env, InspectorExt, + FoundryBlock, FoundryTransaction, InspectorExt, + backend::{DatabaseError, DatabaseExt, JournaledState}, + constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, + env::FoundryContextExt, + evm::{ + BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory, + FoundryEvmNetwork, SpecFor, TxEnvFor, get_create2_factory_call_inputs, with_cloned_context, + }, }; use foundry_evm_coverage::HitMaps; +use foundry_evm_networks::NetworkConfigs; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ + Inspector, context::{ - result::{ExecutionResult, Output}, - BlockEnv, + Block, Cfg, ContextTr, JournalTr, Transaction, + result::{EVMError, ExecutionResult, Output}, }, context_interface::CreateScheme, + handler::FrameResult, + inspector::JournalExt, interpreter::{ - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, - EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, + InstructionResult, Interpreter, InterpreterResult, return_ok, }, + primitives::KECCAK_EMPTY, state::{Account, AccountStatus}, - Inspector, }; +use revm_inspectors::edge_cov::EdgeCovInspector; use std::{ ops::{Deref, DerefMut}, sync::Arc, }; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] #[must_use = "builders do nothing unless you call `build` on them"] -pub struct InspectorStackBuilder { +pub struct InspectorStackBuilder { + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities. + pub analysis: Option, /// The block environment. /// /// Used in the cheatcode handler to overwrite the block environment separately from the /// execution block environment. - pub block: Option, + pub block: Option, /// The gas price. /// /// Used in the cheatcode handler to overwrite the gas price separately from the gas price @@ -53,9 +66,12 @@ pub struct InspectorStackBuilder { /// Whether to enable tracing and revert diagnostics. pub trace_mode: TraceMode, /// Whether logs should be collected. + /// - None for no log collection. + /// - Some(true) for realtime console.log-ing. + /// - Some(false) for log collection. pub logs: Option, - /// Whether coverage info should be collected. - pub coverage: Option, + /// Whether line coverage info should be collected. + pub line_coverage: Option, /// Whether to print all opcode traces into the console. Useful for debugging the EVM. pub print: Option, /// The chisel state inspector. @@ -64,31 +80,59 @@ pub struct InspectorStackBuilder { /// In isolation mode all top-level calls are executed as a separate transaction in a separate /// EVM context, enabling more precise gas accounting and transaction state changes. pub enable_isolation: bool, - /// Whether to enable Odyssey features. - pub odyssey: bool, + /// Networks with enabled features. + pub networks: NetworkConfigs, /// The wallets to set in the cheatcodes context. pub wallets: Option, /// The CREATE2 deployer address. pub create2_deployer: Address, } -impl InspectorStackBuilder { +impl Default for InspectorStackBuilder { + fn default() -> Self { + Self { + analysis: None, + block: None, + gas_price: None, + cheatcodes: None, + fuzzer: None, + trace_mode: TraceMode::None, + logs: None, + line_coverage: None, + print: None, + chisel_state: None, + enable_isolation: false, + networks: NetworkConfigs::default(), + wallets: None, + create2_deployer: Default::default(), + } + } +} + +impl InspectorStackBuilder { /// Create a new inspector stack builder. #[inline] pub fn new() -> Self { Self::default() } + /// Set the solar compiler instance that grants syntactic and semantic analysis capabilities + #[inline] + pub fn set_analysis(mut self, analysis: Analysis) -> Self { + self.analysis = Some(analysis); + self + } + /// Set the block environment. #[inline] - pub fn block(mut self, block: BlockEnv) -> Self { + pub fn block(mut self, block: BLOCK) -> Self { self.block = Some(block); self } /// Set the gas price. #[inline] - pub fn gas_price(mut self, gas_price: u128) -> Self { + pub const fn gas_price(mut self, gas_price: u128) -> Self { self.gas_price = Some(gas_price); self } @@ -116,28 +160,28 @@ impl InspectorStackBuilder { /// Set the Chisel inspector. #[inline] - pub fn chisel_state(mut self, final_pc: usize) -> Self { + pub const fn chisel_state(mut self, final_pc: usize) -> Self { self.chisel_state = Some(final_pc); self } - /// Set whether to collect logs. + /// Set the log collector, and whether to print the logs directly to stdout. #[inline] - pub fn logs(mut self, yes: bool) -> Self { - self.logs = Some(yes); + pub const fn logs(mut self, live_logs: bool) -> Self { + self.logs = Some(live_logs); self } - /// Set whether to collect coverage information. + /// Set whether to collect line coverage information. #[inline] - pub fn coverage(mut self, yes: bool) -> Self { - self.coverage = Some(yes); + pub const fn line_coverage(mut self, yes: bool) -> Self { + self.line_coverage = Some(yes); self } /// Set whether to enable the trace printer. #[inline] - pub fn print(mut self, yes: bool) -> Self { + pub const fn print(mut self, yes: bool) -> Self { self.print = Some(yes); self } @@ -155,39 +199,41 @@ impl InspectorStackBuilder { /// Set whether to enable the call isolation. /// For description of call isolation, see [`InspectorStack::enable_isolation`]. #[inline] - pub fn enable_isolation(mut self, yes: bool) -> Self { + pub const fn enable_isolation(mut self, yes: bool) -> Self { self.enable_isolation = yes; self } - /// Set whether to enable Odyssey features. - /// For description of call isolation, see [`InspectorStack::enable_isolation`]. + /// Set networks with enabled features. #[inline] - pub fn odyssey(mut self, yes: bool) -> Self { - self.odyssey = yes; + pub const fn networks(mut self, networks: NetworkConfigs) -> Self { + self.networks = networks; self } #[inline] - pub fn create2_deployer(mut self, create2_deployer: Address) -> Self { + pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self { self.create2_deployer = create2_deployer; self } /// Builds the stack of inspectors to use when transacting/committing on the EVM. - pub fn build(self) -> InspectorStack { + pub fn build>>( + self, + ) -> InspectorStack { let Self { + analysis, block, gas_price, cheatcodes, fuzzer, trace_mode, logs, - coverage, + line_coverage, print, chisel_state, enable_isolation, - odyssey, + networks, wallets, create2_deployer, } = self; @@ -196,6 +242,11 @@ impl InspectorStackBuilder { // inspectors if let Some(config) = cheatcodes { let mut cheatcodes = Cheatcodes::new(config); + // Set analysis capabilities if they are provided + if let Some(analysis) = analysis { + stack.set_analysis(analysis.clone()); + cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis)); + } // Set wallets if they are provided if let Some(wallets) = wallets { cheatcodes.set_wallets(wallets); @@ -209,19 +260,22 @@ impl InspectorStackBuilder { if let Some(chisel_state) = chisel_state { stack.set_chisel(chisel_state); } - - stack.collect_coverage(coverage.unwrap_or(false)); - stack.collect_logs(logs.unwrap_or(true)); + stack.collect_line_coverage(line_coverage.unwrap_or(false)); + stack.collect_logs(logs); stack.print(print.unwrap_or(false)); stack.tracing(trace_mode); stack.enable_isolation(enable_isolation); - stack.odyssey(odyssey); + stack.networks(networks); stack.set_create2_deployer(create2_deployer); + if networks.is_tempo() { + stack.inner.tempo_labels = Some(Box::default()); + } + // environment, must come after all of the inspectors if let Some(block) = block { - stack.set_block(&block); + stack.set_block(block); } if let Some(gas_price) = gas_price { stack.set_gas_price(gas_price); @@ -235,17 +289,19 @@ impl InspectorStackBuilder { /// dispatch. #[macro_export] macro_rules! call_inspectors { - ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => { $( if let Some($id) = $inspector { - ({ #[inline(always)] #[cold] || $call })(); + $crate::utils::cold_path(); + $body; } )+ }; - (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => {{ + (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{ $( if let Some($id) = $inspector { - if let Some(result) = ({ #[inline(always)] #[cold] || $call })() { + $crate::utils::cold_path(); + if let Some(result) = $body { return result; } } @@ -254,13 +310,15 @@ macro_rules! call_inspectors { } /// The collected results of [`InspectorStack`]. -pub struct InspectorData { +pub struct InspectorData { pub logs: Vec, pub labels: AddressHashMap, pub traces: Option, - pub coverage: Option, - pub cheatcodes: Option, - pub chisel_state: Option<(Vec, Vec, InstructionResult)>, + pub line_coverage: Option, + pub edge_coverage: Option>, + pub cheatcodes: Option>>, + pub chisel_state: Option<(Vec, Vec)>, + pub reverter: Option
, } /// Contains data about the state of outer/main EVM which created and invoked the inner EVM context. @@ -276,17 +334,18 @@ pub struct InnerContextData { /// An inspector that calls multiple inspectors in sequence. /// -/// If a call to an inspector returns a value other than [InstructionResult::Continue] (or -/// equivalent) the remaining inspectors are not called. +/// If a call to an inspector returns a value (indicating a stop or revert) the remaining inspectors +/// are not called. /// /// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling /// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives /// us ability to create and execute separate EVM frames from inside cheatcodes while still having /// access to entire stack of inspectors and correctly handling traces, logs, debugging info /// collection, etc. -#[derive(Clone, Debug, Default)] -pub struct InspectorStack { - pub cheatcodes: Option, +#[derive(Clone, Debug)] +pub struct InspectorStack { + #[allow(clippy::type_complexity)] + pub cheatcodes: Option>>, pub inner: InspectorStackInner, } @@ -295,43 +354,135 @@ pub struct InspectorStack { /// See [`InspectorStack`]. #[derive(Default, Clone, Debug)] pub struct InspectorStackInner { - pub chisel_state: Option, - pub coverage: Option, - pub fuzzer: Option, - pub log_collector: Option, - pub printer: Option, - pub tracer: Option, - pub script_execution_inspector: Option, + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities. + pub analysis: Option, + + // Inspectors. + // These are boxed to reduce the size of the struct and slightly improve performance of the + // `if let Some` checks. + pub chisel_state: Option>, + pub edge_coverage: Option>, + pub fuzzer: Option>, + pub line_coverage: Option>, + pub log_collector: Option>, + pub printer: Option>, + pub revert_diag: Option>, + pub script_execution_inspector: Option>, + pub tempo_labels: Option>, + pub tracer: Option>, + + // FoundryInspectorExt and other internal data. + /// Whether to collect sancov edge coverage from instrumented native crates. + pub sancov_edges: bool, + /// Whether to capture sancov trace-cmp operands for dictionary injection. + pub sancov_trace_cmp: bool, pub enable_isolation: bool, - pub odyssey: bool, + pub networks: NetworkConfigs, pub create2_deployer: Address, - pub revert_diag: Option, - /// Flag marking if we are in the inner EVM context. pub in_inner_context: bool, pub inner_context_data: Option, - pub top_frame_journal: HashMap, + pub top_frame_journal: AddressMap, + /// Address that reverted the call, if any. + pub reverter: Option
, + /// LIFO stack tracking CREATE2 frames that were redirected to the CREATE2 factory. + /// Each entry records the journal depth at which the redirect occurred. + pub pending_create2_redirects: Vec, + /// Pending CREATE2 deployer validation error, deferred from `frame_start` to `create` so + /// it goes through the normal inspector lifecycle (tracing, etc.). + pub pending_create2_error: Option, } /// Struct keeping mutable references to both parts of [InspectorStack] and implementing -/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut] or via -/// [CheatcodesExecutor::get_inspector] method implemented for [InspectorStackInner]. -pub struct InspectorStackRefMut<'a> { - pub cheatcodes: Option<&'a mut Cheatcodes>, +/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut]. +pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> { + pub cheatcodes: Option<&'a mut Cheatcodes>, pub inner: &'a mut InspectorStackInner, } -impl CheatcodesExecutor for InspectorStackInner { - fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box { - Box::new(InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }) +impl CheatcodesExecutor for InspectorStackInner { + fn with_nested_evm( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result<(), EVMError> { + let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; + with_cloned_context(ecx, |db, evm_env, journal_inner| { + let mut evm = + FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector); + *evm.journal_inner_mut() = journal_inner; + f(&mut *evm)?; + let sub_inner = evm.journal_inner_mut().clone(); + let sub_evm_env = evm.to_evm_env(); + Ok((sub_evm_env, sub_inner)) + }) + } + + fn with_fresh_nested_evm( + &mut self, + cheats: &mut Cheatcodes, + db: &mut as ContextTr>::Db, + evm_env: EvmEnvFor, + f: NestedEvmClosure<'_, SpecFor, BlockEnvFor, TxEnvFor>, + ) -> Result, EVMError> { + let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; + let mut evm = + FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector); + f(&mut *evm)?; + Ok(evm.to_evm_env()) + } + + fn transact_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + fork_id: Option, + transaction: B256, + ) -> eyre::Result<()> { + let evm_env = ecx.evm_clone(); + let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact(fork_id, transaction, evm_env, inner, &mut inspector) + } + + fn transact_from_tx_on_db( + &mut self, + cheats: &mut Cheatcodes, + ecx: &mut FoundryContextFor<'_, FEN>, + tx_env: TxEnvFor, + ) -> eyre::Result<()> { + let evm_env = ecx.evm_clone(); + let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }; + let (db, inner) = ecx.db_journal_inner_mut(); + db.transact_from_tx(tx_env, evm_env, inner, &mut inspector) } - fn tracing_inspector(&mut self) -> Option<&mut Option> { - Some(&mut self.tracer) + fn console_log(&mut self, msg: &str) { + if let Some(ref mut collector) = self.log_collector { + InspectorExt::console_log(&mut **collector, msg); + } + } + + fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { + self.tracer.as_deref_mut() + } + + fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option
) { + self.in_inner_context = enabled; + self.inner_context_data = enabled.then(|| InnerContextData { + original_origin: original_origin.expect("origin required when enabling inner ctx"), + }); } } -impl InspectorStack { +impl Default for InspectorStack { + fn default() -> Self { + Self::new() + } +} + +impl InspectorStack { /// Creates a new inspector stack. /// /// Note that the stack is empty by default, and you must add inspectors to it. @@ -339,42 +490,20 @@ impl InspectorStack { /// with [`InspectorStack`]. #[inline] pub fn new() -> Self { - Self::default() + Self { cheatcodes: None, inner: InspectorStackInner::default() } } - /// Logs the status of the inspectors. - pub fn log_status(&self) { - trace!(enabled=%{ - let mut enabled = Vec::with_capacity(16); - macro_rules! push { - ($($id:ident),* $(,)?) => { - $( - if self.$id.is_some() { - enabled.push(stringify!($id)); - } - )* - }; - } - push!(cheatcodes, chisel_state, coverage, fuzzer, log_collector, printer, tracer); - if self.enable_isolation { - enabled.push("isolation"); - } - format!("[{}]", enabled.join(", ")) - }); - } - - /// Set variables from an environment for the relevant inspectors. + /// Set the solar compiler instance. #[inline] - pub fn set_env(&mut self, env: &Env) { - self.set_block(&env.evm_env.block_env); - self.set_gas_price(env.tx.gas_price); + pub fn set_analysis(&mut self, analysis: Analysis) { + self.analysis = Some(analysis); } /// Sets the block for the relevant inspectors. #[inline] - pub fn set_block(&mut self, block: &BlockEnv) { + pub fn set_block(&mut self, block: BlockEnvFor) { if let Some(cheatcodes) = &mut self.cheatcodes { - cheatcodes.block = Some(block.clone()); + cheatcodes.block = Some(block); } } @@ -388,38 +517,57 @@ impl InspectorStack { /// Set the cheatcodes inspector. #[inline] - pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes) { - self.cheatcodes = Some(cheatcodes); + pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes) { + self.cheatcodes = Some(cheatcodes.into()); } /// Set the fuzzer inspector. #[inline] pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) { - self.fuzzer = Some(fuzzer); + self.fuzzer = Some(fuzzer.into()); } /// Set the Chisel inspector. #[inline] pub fn set_chisel(&mut self, final_pc: usize) { - self.chisel_state = Some(ChiselState::new(final_pc)); + self.chisel_state = Some(ChiselState::new(final_pc).into()); } - /// Set whether to enable the coverage collector. + /// Set whether to enable the line coverage collector. #[inline] - pub fn collect_coverage(&mut self, yes: bool) { - self.coverage = yes.then(Default::default); + pub fn collect_line_coverage(&mut self, yes: bool) { + self.line_coverage = yes.then(Default::default); } - /// Set whether to enable call isolation. + /// Set whether to enable the edge coverage collector. #[inline] - pub fn enable_isolation(&mut self, yes: bool) { - self.enable_isolation = yes; + pub fn collect_edge_coverage(&mut self, yes: bool) { + // TODO: configurable edge size? + self.edge_coverage = yes.then(EdgeCovInspector::new).map(Into::into); + } + + /// Set whether to collect sancov edge coverage from instrumented native crates. + #[inline] + pub const fn collect_sancov_edges(&mut self, yes: bool) { + self.inner.sancov_edges = yes; + } + + /// Set whether to capture sancov trace-cmp operands for dictionary injection. + #[inline] + pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) { + self.inner.sancov_trace_cmp = yes; } /// Set whether to enable call isolation. #[inline] - pub fn odyssey(&mut self, yes: bool) { - self.odyssey = yes; + pub const fn enable_isolation(&mut self, yes: bool) { + self.inner.enable_isolation = yes; + } + + /// Set networks with enabled features. + #[inline] + pub const fn networks(&mut self, networks: NetworkConfigs) { + self.inner.networks = networks; } /// Set the CREATE2 deployer address. @@ -429,9 +577,18 @@ impl InspectorStack { } /// Set whether to enable the log collector. + /// - None for no log collection. + /// - Some(true) for realtime console.log-ing. + /// - Some(false) for log collection. #[inline] - pub fn collect_logs(&mut self, yes: bool) { - self.log_collector = yes.then(Default::default); + pub fn collect_logs(&mut self, live_logs: Option) { + self.log_collector = live_logs.map(|live_logs| { + Box::new(if live_logs { + LogCollector::LiveLogs + } else { + LogCollector::Capture { logs: Vec::new() } + }) + }); } /// Set whether to enable the trace printer. @@ -444,11 +601,7 @@ impl InspectorStack { /// Revert diagnostic inspector is activated when `mode != TraceMode::None` #[inline] pub fn tracing(&mut self, mode: TraceMode) { - if mode.is_none() { - self.revert_diag = None; - } else { - self.revert_diag = Some(RevertDiagnostic::default()); - } + self.revert_diag = (!mode.is_none()).then(RevertDiagnostic::default).map(Into::into); if let Some(config) = mode.into_config() { *self.tracer.get_or_insert_with(Default::default).config_mut() = config; @@ -464,12 +617,26 @@ impl InspectorStack { script_address; } + #[inline(always)] + fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> { + InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner } + } + /// Collects all the data gathered during inspection into a single struct. - #[inline] - pub fn collect(self) -> InspectorData { + pub fn collect(self) -> InspectorData { let Self { mut cheatcodes, - inner: InspectorStackInner { chisel_state, coverage, log_collector, tracer, .. }, + inner: + InspectorStackInner { + chisel_state, + line_coverage, + edge_coverage, + log_collector, + tempo_labels, + tracer, + reverter, + .. + }, } = self; let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| { @@ -491,41 +658,41 @@ impl InspectorStack { }); InspectorData { - logs: log_collector.map(|logs| logs.logs).unwrap_or_default(), - labels: cheatcodes - .as_ref() - .map(|cheatcodes| cheatcodes.labels.clone()) - .unwrap_or_default(), + logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(), + labels: { + let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default(); + if let Some(tempo_labels) = tempo_labels { + labels.extend(tempo_labels.labels); + } + labels + }, traces, - coverage: coverage.map(|coverage| coverage.finish()), + line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()), + edge_coverage: edge_coverage.map(|edge_coverage| edge_coverage.into_hitcount()), cheatcodes, chisel_state: chisel_state.and_then(|state| state.state), + reverter, } } - - #[inline(always)] - fn as_mut(&mut self) -> InspectorStackRefMut<'_> { - InspectorStackRefMut { cheatcodes: self.cheatcodes.as_mut(), inner: &mut self.inner } - } } -impl InspectorStackRefMut<'_> { +impl InspectorStackRefMut<'_, FEN> { /// Adjusts the EVM data for the inner EVM context. /// Should be called on the top-level call of inner context (depth == 0 && /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>) { + fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut CTX) { let inner_context_data = self.inner_context_data.as_ref().expect("should be called in inner context"); - ecx.tx.caller = inner_context_data.original_origin; + ecx.tx_mut().set_caller(inner_context_data.original_origin); } fn do_call_end( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, inputs: &CallInputs, outcome: &mut CallOutcome, - ) -> CallOutcome { + ) { let result = outcome.result.result; call_inspectors!( #[ret] @@ -542,22 +709,25 @@ impl InspectorStackRefMut<'_> { // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - let different = outcome.result.result != result || - (outcome.result.result == InstructionResult::Revert && - outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) + let different = outcome.result.result != result + || (outcome.result.result == InstructionResult::Revert + && outcome.output() != previous_outcome.output()); + different.then_some(()) }, ); - outcome.clone() + // Record first address that reverted the call. + if result.is_revert() && self.reverter.is_none() { + self.reverter = Some(inputs.target_address); + } } fn do_create_end( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, call: &CreateInputs, outcome: &mut CreateOutcome, - ) -> CreateOutcome { + ) { let result = outcome.result.result; call_inspectors!( #[ret] @@ -568,106 +738,94 @@ impl InspectorStackRefMut<'_> { // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - let different = outcome.result.result != result || - (outcome.result.result == InstructionResult::Revert && - outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) - }, - ); - - outcome.clone() - } - - fn do_eofcreate_end( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: &mut CreateOutcome, - ) -> CreateOutcome { - let result = outcome.result.result; - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], - |inspector| { - let previous_outcome = outcome.clone(); - inspector.eofcreate_end(ecx, call, outcome); - - // If the inspector returns a different status or a revert with a non-empty message, - // we assume it wants to tell us something - let different = outcome.result.result != result || - (outcome.result.result == InstructionResult::Revert && - outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) + let different = outcome.result.result != result + || (outcome.result.result == InstructionResult::Revert + && outcome.output() != previous_outcome.output()); + different.then_some(()) }, ); - - outcome.clone() } fn transact_inner( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, kind: TxKind, caller: Address, input: Bytes, gas_limit: u64, value: U256, ) -> (InterpreterResult, Option
) { - let cached_env = Env::from(ecx.cfg.clone(), ecx.block.clone(), ecx.tx.clone()); - - ecx.block.basefee = 0; - ecx.tx.chain_id = Some(ecx.cfg.chain_id); - ecx.tx.caller = caller; - ecx.tx.kind = kind; - ecx.tx.data = input; - ecx.tx.value = value; + let cached_evm_env = ecx.evm_clone(); + let cached_tx_env = ecx.tx_clone(); + + ecx.block_mut().set_basefee(0); + + let chain_id = ecx.cfg().chain_id(); + ecx.tx_mut().set_chain_id(Some(chain_id)); + ecx.tx_mut().set_caller(caller); + ecx.tx_mut().set_kind(kind); + ecx.tx_mut().set_data(input); + ecx.tx_mut().set_value(value); // Add 21000 to the gas limit to account for the base cost of transaction. - ecx.tx.gas_limit = gas_limit + 21000; + ecx.tx_mut().set_gas_limit(gas_limit + 21000); // If we haven't disabled gas limit checks, ensure that transaction gas limit will not // exceed block gas limit. - if !ecx.cfg.disable_block_gas_limit { - ecx.tx.gas_limit = std::cmp::min(ecx.tx.gas_limit, ecx.block.gas_limit); + if !ecx.cfg().is_block_gas_limit_disabled() { + let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit()); + ecx.tx_mut().set_gas_limit(gas_limit); } - ecx.tx.gas_price = 0; + ecx.tx_mut().set_gas_price(0); - self.inner_context_data = Some(InnerContextData { original_origin: cached_env.tx.caller }); + self.inner_context_data = + Some(InnerContextData { original_origin: cached_tx_env.caller() }); self.in_inner_context = true; - let res = self.with_stack(|inspector| { - let (db, journal, env) = ecx.as_db_env_and_journal(); - let mut evm = new_evm_with_inspector(db, env.to_owned(), inspector); + let evm_env = ecx.evm_clone(); + let tx_env = ecx.tx_clone(); - evm.journaled_state.state = { - let mut state = journal.state.clone(); + let res = self.with_inspector(|mut inspector| { + let (res, nested_env) = { + let (db, journal) = ecx.db_journal_inner_mut(); + let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm( + db, + evm_env, + &mut inspector, + ); - for (addr, acc_mut) in &mut state { - // mark all accounts cold, besides preloaded addresses - if !journal.warm_preloaded_addresses.contains(addr) { - acc_mut.mark_cold(); - } + evm.journal_inner_mut().state = { + let mut state = journal.state.clone(); - // mark all slots cold - for slot_mut in acc_mut.storage.values_mut() { - slot_mut.is_cold = true; - slot_mut.original_value = slot_mut.present_value; - } - } + for (addr, acc_mut) in &mut state { + // mark all accounts cold, besides preloaded addresses + if journal.warm_addresses.is_cold(addr) { + acc_mut.mark_cold(); + } - state - }; + // mark all slots cold + for slot_mut in acc_mut.storage.values_mut() { + slot_mut.is_cold = true; + slot_mut.original_value = slot_mut.present_value; + } + } - // set depth to 1 to make sure traces are collected correctly - evm.journaled_state.depth = 1; + state + }; - let res = evm.transact(env.tx.clone()); + // set depth to 1 to make sure traces are collected correctly + evm.journal_inner_mut().depth = 1; - // need to reset the env in case it was modified via cheatcodes during execution - *env.cfg = evm.cfg.clone(); - *env.block = evm.block.clone(); + let res = evm.transact_raw(tx_env); + let nested_evm_env = evm.to_evm_env(); + (res, nested_evm_env) + }; - *env.tx = cached_env.tx; - env.block.basefee = cached_env.evm_env.block_env.basefee; + // Restore env, preserving cheatcode cfg/block changes from the nested EVM + // but restoring the original tx and basefee (which we zeroed for the nested call). + let mut restored_evm_env = nested_env; + restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee()); + ecx.set_evm(restored_evm_env); + ecx.set_tx(cached_tx_env); res }); @@ -685,14 +843,14 @@ impl InspectorStackRefMut<'_> { }; for (addr, mut acc) in res.state { - let Some(acc_mut) = ecx.journaled_state.state.get_mut(&addr) else { - ecx.journaled_state.state.insert(addr, acc); - continue + let Some(acc_mut) = ecx.journal_mut().evm_state_mut().get_mut(&addr) else { + ecx.journal_mut().evm_state_mut().insert(addr, acc); + continue; }; // make sure accounts that were warmed earlier do not become cold - if acc.status.contains(AccountStatus::Cold) && - !acc_mut.status.contains(AccountStatus::Cold) + if acc.status.contains(AccountStatus::Cold) + && !acc_mut.status.contains(AccountStatus::Cold) { acc.status -= AccountStatus::Cold; } @@ -702,7 +860,7 @@ impl InspectorStackRefMut<'_> { for (key, val) in acc.storage { let Some(slot_mut) = acc_mut.storage.get_mut(&key) else { acc_mut.storage.insert(key, val); - continue + continue; }; slot_mut.present_value = val.present_value; slot_mut.is_cold &= val.is_cold; @@ -710,62 +868,65 @@ impl InspectorStackRefMut<'_> { } let (result, address, output) = match res.result { - ExecutionResult::Success { reason, gas_used, gas_refunded, logs: _, output } => { - gas.set_refund(gas_refunded as i64); - let _ = gas.record_cost(gas_used); + ExecutionResult::Success { reason, gas: result_gas, logs: _, output } => { + gas.set_refund(result_gas.final_refunded() as i64); + let _ = gas.record_regular_cost(result_gas.tx_gas_used()); let address = match output { Output::Create(_, address) => address, Output::Call(_) => None, }; (reason.into(), address, output.into_data()) } - ExecutionResult::Halt { reason, gas_used } => { - let _ = gas.record_cost(gas_used); - (reason.into(), None, Bytes::new()) + ExecutionResult::Halt { reason, gas: result_gas, .. } => { + let _ = gas.record_regular_cost(result_gas.tx_gas_used()); + (InstructionResult::from(reason), None, Bytes::new()) } - ExecutionResult::Revert { gas_used, output } => { - let _ = gas.record_cost(gas_used); + ExecutionResult::Revert { gas: result_gas, output, .. } => { + let _ = gas.record_regular_cost(result_gas.tx_gas_used()); (InstructionResult::Revert, None, output) } }; (InterpreterResult { result, output, gas }, address) } - /// Moves out of references, constructs an [`InspectorStack`] and runs the given closure with - /// it. - fn with_stack(&mut self, f: impl FnOnce(&mut InspectorStack) -> O) -> O { - let mut stack = InspectorStack { - cheatcodes: self - .cheatcodes - .as_deref_mut() - .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone()))), - inner: std::mem::take(self.inner), - }; + /// Moves out of references, constructs a new [`InspectorStackRefMut`] and runs the given + /// closure with it. + fn with_inspector(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O { + let mut cheatcodes = self + .cheatcodes + .as_deref_mut() + .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone()))); + let mut inner = std::mem::take(self.inner); + + // Save pending CREATE2 redirects so frame_end in the nested EVM doesn't consume them. + // These belong to the outer EVM's frame lifecycle and must be restored after. + let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects); - let out = f(&mut stack); + let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner }); if let Some(cheats) = self.cheatcodes.as_deref_mut() { - *cheats = stack.cheatcodes.take().unwrap(); + *cheats = cheatcodes.unwrap(); } - *self.inner = stack.inner; + inner.pending_create2_redirects = saved_create2_redirects; + *self.inner = inner; out } /// Invoked at the beginning of a new top-level (0 depth) frame. - fn top_level_frame_start(&mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>) { + fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) { if self.enable_isolation { // If we're in isolation mode, we need to keep track of the state at the beginning of // the frame to be able to roll back on revert - self.top_frame_journal = ecx.journaled_state.state.clone(); + self.top_frame_journal.clone_from(ecx.journal().evm_state()); } } /// Invoked at the end of root frame. fn top_level_frame_end( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, result: InstructionResult, ) { if !result.is_revert() { @@ -782,88 +943,207 @@ impl InspectorStackRefMut<'_> { // created We can't rely on revm's journal because it doesn't account for changes // made by isolated calls if self.enable_isolation { - ecx.journaled_state.state = std::mem::take(&mut self.top_frame_journal); + *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal); } } -} -impl Inspector> for InspectorStackRefMut<'_> { - fn initialize_interp( + // We take extra care in optimizing `step` and `step_end`, as they're are likely the most + // hot functions in all of Foundry. + // We want to `#[inline(always)]` these functions so that `InspectorStack` does not + // delegate to `InspectorStackRefMut` in this case. + + #[inline(always)] + fn step_inlined( &mut self, interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, ) { call_inspectors!( [ - &mut self.coverage, + // These are sorted in definition order. + &mut self.edge_coverage, + &mut self.fuzzer, + &mut self.line_coverage, + &mut self.printer, + &mut self.revert_diag, + &mut self.script_execution_inspector, &mut self.tracer, + // Keep `cheatcodes` last to make use of the tail call. &mut self.cheatcodes, - &mut self.script_execution_inspector, - &mut self.printer ], - |inspector| inspector.initialize_interp(interpreter, ecx), + |inspector| (**inspector).step(interpreter, ecx), ); } - fn step( + #[inline(always)] + fn step_end_inlined( &mut self, interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, ) { call_inspectors!( [ - &mut self.fuzzer, + // These are sorted in definition order. + &mut self.chisel_state, + &mut self.printer, + &mut self.revert_diag, &mut self.tracer, - &mut self.coverage, + // Keep `cheatcodes` last to make use of the tail call. &mut self.cheatcodes, - &mut self.script_execution_inspector, - &mut self.printer, - &mut self.revert_diag ], - |inspector| inspector.step(interpreter, ecx), + |inspector| (**inspector).step_end(interpreter, ecx), ); } +} - fn step_end( +impl Inspector> + for InspectorStackRefMut<'_, FEN> +{ + fn initialize_interp( &mut self, interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, ) { call_inspectors!( [ + &mut self.line_coverage, &mut self.tracer, &mut self.cheatcodes, - &mut self.chisel_state, - &mut self.printer, - &mut self.revert_diag + &mut self.script_execution_inspector, + &mut self.printer ], - |inspector| inspector.step_end(interpreter, ecx), + |inspector| inspector.initialize_interp(interpreter, ecx), + ); + } + + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { + self.step_inlined(interpreter, ecx); + } + + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { + self.step_end_inlined(interpreter, ecx); + } + + #[allow(clippy::redundant_clone)] + fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) { + call_inspectors!( + [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.log(ecx, log.clone()), ); } - fn log( + #[allow(clippy::redundant_clone)] + fn log_full( &mut self, interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, log: Log, ) { call_inspectors!( [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - |inspector| inspector.log(interpreter, ecx, log.clone()), + |inspector| inspector.log_full(interpreter, ecx, log.clone()), ); } + fn frame_start( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + frame_input: &mut FrameInput, + ) -> Option { + if let FrameInput::Create(inputs) = frame_input + && let CreateScheme::Create2 { salt } = inputs.scheme() + && self.should_use_create2_factory(ecx.journal().depth(), inputs) + { + let gas_limit = inputs.gas_limit(); + let create2_deployer = self.create2_deployer(); + + // Validate deployer before rewriting. + let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash; + if code_hash == KECCAK_EMPTY { + // Store the revert so `create` can return it inside the normal inspector + // lifecycle (avoids tracing mismatch from short-circuiting in frame_start). + self.inner.pending_create2_error = Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from( + format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(), + ), + gas: Gas::new(gas_limit), + }, + address: None, + }); + return None; + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + self.inner.pending_create2_error = Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + address: None, + }); + return None; + } + + let call_inputs = + get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut()) + .ok()?; + + // Record the redirect depth *after* validation succeeds. + self.inner.pending_create2_redirects.push(ecx.journal().depth()); + + // Rewrite the frame input from Create to Call. + *frame_input = FrameInput::Call(Box::new(call_inputs)); + } + + None + } + + fn frame_end( + &mut self, + ecx: &mut FoundryContextFor<'_, FEN>, + _frame_input: &FrameInput, + frame_result: &mut FrameResult, + ) { + let depth = ecx.journal().depth(); + if self.inner.pending_create2_redirects.last().copied() != Some(depth) { + return; + } + + self.inner.pending_create2_redirects.pop(); + + let FrameResult::Call(call) = frame_result else { + debug_assert!(false, "pending CREATE2 redirect ended with non-call result"); + return; + }; + + let address = match call.instruction_result() { + return_ok!() => Address::try_from(call.output().as_ref()) + .map_err(|_| { + call.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call.result.gas.limit()), + }; + }) + .ok(), + _ => None, + }; + + *frame_result = FrameResult::Create(CreateOutcome { result: call.result.clone(), address }); + } + fn call( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, call: &mut CallInputs, ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { self.adjust_evm_data_for_inner_context(ecx); return None; } - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_start(ecx); } @@ -874,14 +1154,13 @@ impl Inspector> for InspectorStackRefMut<'_> &mut self.tracer, &mut self.log_collector, &mut self.printer, - &mut self.revert_diag + &mut self.revert_diag, + &mut self.tempo_labels ], |inspector| { let mut out = None; if let Some(output) = inspector.call(ecx, call) { - if output.result.result != InstructionResult::Continue { - out = Some(Some(output)); - } + out = Some(Some(output)); } out }, @@ -889,27 +1168,34 @@ impl Inspector> for InspectorStackRefMut<'_> if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { // Handle mocked functions, replace bytecode address with mock if matched. - if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) { + if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) { + let input_bytes = call.input.bytes(ecx); // Check if any mock function set for call data or if catch-all mock function set // for selector. - if let Some(target) = mocks.get(&call.input.bytes(ecx)).or_else(|| { - call.input.bytes(ecx).get(..4).and_then(|selector| mocks.get(selector)) - }) { + if let Some(target) = mocks + .get(&input_bytes) + .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector))) + { call.bytecode_address = *target; + + let target = ecx + .journal_mut() + .load_account_with_code(*target) + .expect("failed to load account"); + call.known_bytecode = + (target.info.code_hash, target.info.code.clone().unwrap_or_default()); } } if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) { - if output.result.result != InstructionResult::Continue { - return Some(output); - } + return Some(output); } } - if self.enable_isolation && !self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1 { match call.scheme { // Isolate CALLs - CallScheme::Call | CallScheme::ExtCall => { + CallScheme::Call => { let input = call.input.bytes(ecx); let (result, _) = self.transact_inner( ecx, @@ -922,21 +1208,23 @@ impl Inspector> for InspectorStackRefMut<'_> return Some(CallOutcome { result, memory_offset: call.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: vec![], }); } // Mark accounts and storage cold before STATICCALLs - CallScheme::StaticCall | CallScheme::ExtStaticCall => { - let JournaledState { state, warm_preloaded_addresses, .. } = - &mut ecx.journaled_state.inner; + CallScheme::StaticCall => { + let (_, journal_inner) = ecx.db_journal_inner_mut(); + let JournaledState { state, warm_addresses, .. } = journal_inner; for (addr, acc_mut) in state { // Do not mark accounts and storage cold accounts with arbitrary storage. - if let Some(cheatcodes) = &self.cheatcodes { - if cheatcodes.has_arbitrary_storage(addr) { - continue; - } + if let Some(cheatcodes) = &self.cheatcodes + && cheatcodes.has_arbitrary_storage(addr) + { + continue; } - if !warm_preloaded_addresses.contains(addr) { + if warm_addresses.is_cold(addr) { acc_mut.mark_cold(); } @@ -946,7 +1234,7 @@ impl Inspector> for InspectorStackRefMut<'_> } } // Process other variants as usual - CallScheme::CallCode | CallScheme::DelegateCall | CallScheme::ExtDelegateCall => {} + CallScheme::CallCode | CallScheme::DelegateCall => {} } } @@ -955,163 +1243,113 @@ impl Inspector> for InspectorStackRefMut<'_> fn call_end( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, inputs: &CallInputs, outcome: &mut CallOutcome, ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { return; } self.do_call_end(ecx, inputs, outcome); - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_end(ecx, outcome.result.result); } } fn create( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, create: &mut CreateInputs, ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { self.adjust_evm_data_for_inner_context(ecx); return None; } - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_start(ecx); } call_inspectors!( #[ret] - [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + [&mut self.tracer, &mut self.line_coverage, &mut self.cheatcodes], |inspector| inspector.create(ecx, create).map(Some), ); - if !matches!(create.scheme, CreateScheme::Create2 { .. }) && - self.enable_isolation && - !self.in_inner_context && - ecx.journaled_state.depth == 1 - { - let (result, address) = self.transact_inner( - ecx, - TxKind::Create, - create.caller, - create.init_code.clone(), - create.gas_limit, - create.value, - ); - return Some(CreateOutcome { result, address }); + // If frame_start detected an invalid CREATE2 deployer, return the error here + // (after sub-inspectors have been notified) so tracing stays balanced. + if let Some(error) = self.inner.pending_create2_error.take() { + return Some(error); } - None - } - - fn create_end( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &CreateInputs, - outcome: &mut CreateOutcome, - ) { - // We are processing inner context outputs in the outer context, so need to avoid processing - // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { - return; - } - - self.do_create_end(ecx, call, outcome); - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_end(ecx, outcome.result.result); - } - } - - fn eofcreate( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { - self.adjust_evm_data_for_inner_context(ecx); - return None; - } - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_start(ecx); - } - - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], - |inspector| inspector.eofcreate(ecx, create).map(Some), - ); - - if matches!(create.kind, EOFCreateKind::Tx { .. }) && - self.enable_isolation && - !self.in_inner_context && - ecx.journaled_state.depth == 1 + if !matches!(create.scheme(), CreateScheme::Create2 { .. }) + && self.enable_isolation + && !self.in_inner_context + && ecx.journal().depth() == 1 { - let init_code = match &mut create.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { .. } => unreachable!(), - }; + // In isolation mode, transact_inner returns None for the address on revert; pre-compute + // the would-be deployed address so create_end can enforce expected_revert reverter + // checks. + let precomputed_address = ecx + .journal() + .evm_state() + .get(&create.caller()) + .map(|acc| create.caller().create(acc.info.nonce)); let (result, address) = self.transact_inner( ecx, TxKind::Create, - create.caller, - init_code, - create.gas_limit, - create.value, + create.caller(), + create.init_code().clone(), + create.gas_limit(), + create.value(), ); + let address = + address.or_else(|| if result.is_revert() { precomputed_address } else { None }); return Some(CreateOutcome { result, address }); } None } - fn eofcreate_end( + fn create_end( &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, + ecx: &mut FoundryContextFor<'_, FEN>, + call: &CreateInputs, outcome: &mut CreateOutcome, ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { + if self.in_inner_context && ecx.journal().depth() == 1 { return; } - self.do_eofcreate_end(ecx, call, outcome); + self.do_create_end(ecx, call, outcome); - if ecx.journaled_state.depth == 0 { + if ecx.journal().depth() == 0 { self.top_level_frame_end(ecx, outcome.result.result); } } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { - Inspector::>::selfdestruct( + call_inspectors!([&mut self.printer], |inspector| { + Inspector::>::selfdestruct( inspector, contract, target, value, ) }); } } -impl InspectorExt for InspectorStackRefMut<'_> { - fn should_use_create2_factory( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - inputs: &CreateInputs, - ) -> bool { +impl InspectorExt for InspectorStackRefMut<'_, FEN> { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { call_inspectors!( #[ret] [&mut self.cheatcodes], - |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, + |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) }, ); false @@ -1123,8 +1361,8 @@ impl InspectorExt for InspectorStackRefMut<'_> { )); } - fn is_odyssey(&self) -> bool { - self.inner.odyssey + fn get_networks(&self) -> NetworkConfigs { + self.inner.networks } fn create2_deployer(&self) -> Address { @@ -1132,28 +1370,18 @@ impl InspectorExt for InspectorStackRefMut<'_> { } } -impl Inspector> for InspectorStack { - #[inline] - fn step( - &mut self, - interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - ) { - self.as_mut().step(interpreter, ecx) +impl Inspector> for InspectorStack { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { + self.as_mut().step_inlined(interpreter, ecx) } - #[inline] - fn step_end( - &mut self, - interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - ) { - self.as_mut().step_end(interpreter, ecx) + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) { + self.as_mut().step_end_inlined(interpreter, ecx) } fn call( &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, + context: &mut FoundryContextFor<'_, FEN>, inputs: &mut CallInputs, ) -> Option { self.as_mut().call(context, inputs) @@ -1161,7 +1389,7 @@ impl Inspector> for InspectorStack { fn call_end( &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, + context: &mut FoundryContextFor<'_, FEN>, inputs: &CallInputs, outcome: &mut CallOutcome, ) { @@ -1170,7 +1398,7 @@ impl Inspector> for InspectorStack { fn create( &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, + context: &mut FoundryContextFor<'_, FEN>, create: &mut CreateInputs, ) -> Option { self.as_mut().create(context, create) @@ -1178,63 +1406,67 @@ impl Inspector> for InspectorStack { fn create_end( &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, + context: &mut FoundryContextFor<'_, FEN>, call: &CreateInputs, outcome: &mut CreateOutcome, ) { self.as_mut().create_end(context, call, outcome) } - fn eofcreate( + fn initialize_interp( &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - self.as_mut().eofcreate(context, create) + interpreter: &mut Interpreter, + ecx: &mut FoundryContextFor<'_, FEN>, + ) { + self.as_mut().initialize_interp(interpreter, ecx) } - fn eofcreate_end( - &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: &mut CreateOutcome, - ) { - self.as_mut().eofcreate_end(context, call, outcome) + fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) { + self.as_mut().log(ecx, log) } - fn initialize_interp( + fn log_full( &mut self, interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ecx: &mut FoundryContextFor<'_, FEN>, + log: Log, ) { - self.as_mut().initialize_interp(interpreter, ecx) + self.as_mut().log_full(interpreter, ecx, log) } - fn log( + fn frame_start( &mut self, - interpreter: &mut Interpreter, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - log: Log, + context: &mut FoundryContextFor<'_, FEN>, + frame_input: &mut FrameInput, + ) -> Option { + self.as_mut().frame_start(context, frame_input) + } + + fn frame_end( + &mut self, + context: &mut FoundryContextFor<'_, FEN>, + frame_input: &FrameInput, + frame_result: &mut FrameResult, ) { - self.as_mut().log(interpreter, ecx, log) + self.as_mut().frame_end(context, frame_input, frame_result) } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - self.as_mut().selfdestruct(contract, target, value); + call_inspectors!([&mut self.inner.printer], |inspector| { + Inspector::>::selfdestruct( + inspector, contract, target, value, + ) + }); } } -impl InspectorExt for InspectorStack { - fn should_use_create2_factory( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - inputs: &CreateInputs, - ) -> bool { - self.as_mut().should_use_create2_factory(ecx, inputs) +impl InspectorExt for InspectorStack { + fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { + self.as_mut().should_use_create2_factory(depth, inputs) } - fn is_odyssey(&self) -> bool { - self.odyssey + fn get_networks(&self) -> NetworkConfigs { + self.networks } fn create2_deployer(&self) -> Address { @@ -1242,7 +1474,7 @@ impl InspectorExt for InspectorStack { } } -impl<'a> Deref for InspectorStackRefMut<'a> { +impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> { type Target = &'a mut InspectorStackInner; fn deref(&self) -> &Self::Target { @@ -1250,13 +1482,13 @@ impl<'a> Deref for InspectorStackRefMut<'a> { } } -impl DerefMut for InspectorStackRefMut<'_> { +impl DerefMut for InspectorStackRefMut<'_, FEN> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl Deref for InspectorStack { +impl Deref for InspectorStack { type Target = InspectorStackInner; fn deref(&self) -> &Self::Target { @@ -1264,7 +1496,7 @@ impl Deref for InspectorStack { } } -impl DerefMut for InspectorStack { +impl DerefMut for InspectorStack { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } diff --git a/crates/evm/evm/src/inspectors/tempo_labels.rs b/crates/evm/evm/src/inspectors/tempo_labels.rs new file mode 100644 index 0000000000000..f0e2e2279fb17 --- /dev/null +++ b/crates/evm/evm/src/inspectors/tempo_labels.rs @@ -0,0 +1,45 @@ +use alloy_primitives::map::AddressMap; +use foundry_evm_core::backend::DatabaseError; +use revm::{ + Database, Inspector, + context::ContextTr, + inspector::JournalExt, + interpreter::{CallInputs, CallOutcome, interpreter::EthInterpreter}, +}; +use tempo_primitives::TempoAddressExt; + +/// Inspector that labels TIP20 token precompile addresses with their on-chain names. +/// +/// During execution, when a call targets a TIP20 address, this inspector reads the token's +/// name from storage and records the `address -> name` mapping. These labels are later merged +/// into trace output for better readability. +#[derive(Default, Clone, Debug)] +pub struct TempoLabels { + pub(crate) labels: AddressMap, +} + +impl Inspector for TempoLabels +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ + fn call(&mut self, ctx: &mut CTX, inputs: &mut CallInputs) -> Option { + if inputs.target_address.is_tip20() && !self.labels.contains_key(&inputs.target_address) { + let bytes = ctx + .db_mut() + .storage(inputs.target_address, tempo_precompiles::tip20::slots::NAME) + .unwrap_or_default() + .to_be_bytes::<32>(); + let len = bytes[31] as usize / 2; // Last byte stores length * 2 for short strings + let name = if len == 0 { + "TIP20".to_string() + } else { + String::from_utf8_lossy(&bytes[..len]).to_string() + }; + self.labels.insert(inputs.target_address, name); + } + + None + } +} diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 92d39026aa677..660b10ea41d4b 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -3,7 +3,7 @@ //! Main Foundry EVM backend abstractions. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; @@ -11,11 +11,14 @@ extern crate tracing; pub mod executors; pub mod inspectors; +pub use foundry_evm_core as core; pub use foundry_evm_core::{ - backend, constants, decode, fork, opts, utils, Env, EnvMut, EvmEnv, InspectorExt, + EvmEnv, FoundryInspectorExt, InspectorExt, backend, constants, decode, fork, hardfork, opts, + utils, }; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; +pub use foundry_evm_hardforks as hardforks; pub use foundry_evm_traces as traces; // TODO: We should probably remove these, but it's a pretty big breaking change. diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 452eb49790f9e..5629b17d936da 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -21,6 +21,8 @@ foundry-evm-core.workspace = true foundry-evm-coverage.workspace = true foundry-evm-traces.workspace = true +solar.workspace = true + alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = [ @@ -48,3 +50,12 @@ rand.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm-core/optimism", + "foundry-evm-coverage/optimism", + "foundry-evm-traces/optimism", +] diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs index 279f0878382ac..691989c8d31cf 100644 --- a/crates/evm/fuzz/src/inspector.rs +++ b/crates/evm/fuzz/src/inspector.rs @@ -1,40 +1,39 @@ use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; +use foundry_common::mapping_slots::step as mapping_step; +use foundry_evm_core::constants::CHEATCODE_ADDRESS; use revm::{ - context::{ContextTr, Transaction}, - inspector::JournalExt, - interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, Interpreter}, Inspector, + context::{ContextTr, JournalTr, Transaction}, + interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, CallValue, Interpreter}, }; /// An inspector that can fuzz and collect data for that effect. #[derive(Clone, Debug)] pub struct Fuzzer { - /// Given a strategy, it generates a random call. - pub call_generator: Option, /// If set, it collects `stack` and `memory` values for fuzzing purposes. pub collect: bool, + /// Given a strategy, it generates a random call. + pub call_generator: Option, /// If `collect` is set, we store the collected values in this fuzz dictionary. pub fuzz_state: EvmFuzzState, } -impl Inspector for Fuzzer -where - CTX: ContextTr, -{ +impl Inspector for Fuzzer { #[inline] fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) { // We only collect `stack` and `memory` data before and after calls. if self.collect { self.collect_data(interp); - self.collect = false; + if let Some(mapping_slots) = &mut self.fuzz_state.mapping_slots { + mapping_step(mapping_slots, interp); + } } } - #[inline] fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { // We don't want to override the very first call made to the test contract. if self.call_generator.is_some() && ecx.tx().caller() != inputs.caller { - self.override_call(inputs); + self.override_call(ecx, inputs); } // We only collect `stack` and `memory` data before and after calls. @@ -44,10 +43,12 @@ where None } - #[inline] fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) { if let Some(ref mut call_generator) = self.call_generator { - call_generator.used = false; + // Decrement depth when any call ends while inside an override + if call_generator.override_depth > 0 { + call_generator.override_depth -= 1; + } } // We only collect `stack` and `memory` data before and after calls. @@ -58,6 +59,7 @@ where impl Fuzzer { /// Collects `stack` and `memory` values into the fuzz dictionary. + #[cold] fn collect_data(&mut self, interpreter: &Interpreter) { self.fuzz_state.collect_values(interpreter.stack.data().iter().copied().map(Into::into)); @@ -68,27 +70,80 @@ impl Fuzzer { // state.insert(slot); // } + + self.collect = false; } - /// Overrides an external call and tries to call any method of msg.sender. - fn override_call(&mut self, call: &mut CallInputs) { - if let Some(ref mut call_generator) = self.call_generator { - // We only override external calls which are not coming from the test contract. - if call.caller != call_generator.test_address && - call.scheme == CallScheme::Call && - !call_generator.used - { - // There's only a 30% chance that an override happens. - if let Some(tx) = call_generator.next(call.caller, call.target_address) { - call.input = CallInput::Bytes(tx.call_details.calldata.0.into()); - call.caller = tx.sender; - call.target_address = tx.call_details.target; - - // TODO: in what scenarios can the following be problematic - call.bytecode_address = tx.call_details.target; - call_generator.used = true; - } - } + /// Overrides an external call to simulate reentrancy attacks. + /// + /// This function detects reentrancy vulnerabilities by replacing external calls + /// with callbacks that reenter the caller contract. + /// + /// For calls with value (ETH transfers): + /// 1. Performs the ETH transfer via the journal first + /// 2. Replaces the call with a reentrant callback (value = 0) + /// + /// For calls without value: + /// - Replaces the call entirely with a reentrant callback + /// + /// This simulates malicious contracts that immediately reenter when called. + fn override_call(&mut self, ecx: &mut CTX, call: &mut CallInputs) { + let Some(ref mut call_generator) = self.call_generator else { + return; + }; + + // Skip if: + // - Caller is test contract (don't override the initial calls from the test) + // - Not a CALL scheme (only override CALLs, not STATICCALLs, DELEGATECALLs, etc.) + // - Inside an override (prevent recursive overrides) + // - Target is cheatcode address + // - Neither caller nor target is a handler contract + // + // We override calls when either the caller OR target is a handler. This covers: + // 1. EtherStore pattern: handler sends ETH out, attacker reenters handler + // 2. Rari pattern: external protocol sends ETH to handler, handler reenters protocol + let caller_is_handler = call_generator.is_handler(call.caller); + let target_is_handler = call_generator.is_handler(call.target_address); + if call.caller == call_generator.test_address + || call.scheme != CallScheme::Call + || call_generator.override_depth > 0 + || call.target_address == CHEATCODE_ADDRESS + || (!caller_is_handler && !target_is_handler) + { + return; + } + + // There's only a ~27% chance that an override happens (90% * 30% from strategy). + let Some(tx) = call_generator.next(call.caller, call.target_address) else { + return; + }; + + // For value transfers, perform the ETH transfer before injecting the callback. + // This simulates a malicious receive() that gets the ETH and then reenters. + let value = call.transfer_value().unwrap_or_default(); + let has_value = !value.is_zero() && call.gas_limit > 2300; + if has_value && ecx.journal_mut().transfer(call.caller, call.target_address, value).is_err() + { + return; } + + // Replace the call with a reentrant callback + call.input = CallInput::Bytes(tx.call_details.calldata.0.into()); + call.caller = tx.sender; + call.target_address = tx.call_details.target; + call.bytecode_address = tx.call_details.target; + let target = ecx + .journal_mut() + .load_account_with_code(tx.call_details.target) + .expect("failed to load account"); + // Clear known_bytecode to force REVM to load bytecode from the new target. + // Without this, REVM uses cached bytecode from the original target (e.g., empty + // bytecode for EOA), causing the call to short-circuit before executing any code. + call.known_bytecode = (target.info.code_hash, target.info.code.clone().unwrap_or_default()); + // Clear value since ETH was already transferred above + call.value = CallValue::Transfer(alloy_primitives::U256::ZERO); + + // Track that we're inside an overridden call to avoid recursive overrides + call_generator.override_depth = 1; } } diff --git a/crates/evm/fuzz/src/invariant/call_override.rs b/crates/evm/fuzz/src/invariant/call_override.rs index dddf591526f08..a9fdb1fc36bfc 100644 --- a/crates/evm/fuzz/src/invariant/call_override.rs +++ b/crates/evm/fuzz/src/invariant/call_override.rs @@ -1,4 +1,4 @@ -use super::{BasicTxDetails, CallDetails}; +use crate::{BasicTxDetails, CallDetails}; use alloy_primitives::Address; use parking_lot::{Mutex, RwLock}; use proptest::{ @@ -6,22 +6,30 @@ use proptest::{ strategy::{SBoxedStrategy, Strategy, ValueTree}, test_runner::TestRunner, }; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; /// Given a TestRunner and a strategy, it generates calls. Used inside the Fuzzer inspector to -/// override external calls to test for potential reentrancy vulnerabilities.. +/// override external calls to test for potential reentrancy vulnerabilities. +/// +/// The key insight is that we only override calls TO handler contracts (targeted contracts). +/// This simulates a malicious contract that reenters when receiving ETH via its receive() function. #[derive(Clone, Debug)] pub struct RandomCallGenerator { /// Address of the test contract. pub test_address: Address, + /// Addresses of handler contracts that can be reentered. + /// We only inject callbacks when the call target is one of these. + pub handler_addresses: Arc>>, /// Runner that will generate the call from the strategy. pub runner: Arc>, /// Strategy to be used to generate calls from `target_reference`. pub strategy: SBoxedStrategy>, /// Reference to which contract we want a fuzzed calldata from. pub target_reference: Arc>, - /// Flag to know if a call has been overridden. Don't allow nesting for now. - pub used: bool, + /// Tracks the call depth when an override is active. When > 0, we're inside an overridden + /// call and should not override nested calls. Incremented when we override a call, + /// decremented when any call ends while inside an override. + pub override_depth: usize, /// If set to `true`, consumes the next call from `last_sequence`, otherwise queries it from /// the strategy. pub replay: bool, @@ -32,21 +40,28 @@ pub struct RandomCallGenerator { impl RandomCallGenerator { pub fn new( test_address: Address, + handler_addresses: HashSet
, runner: TestRunner, strategy: impl Strategy + Send + Sync + 'static, target_reference: Arc>, ) -> Self { Self { test_address, + handler_addresses: Arc::new(RwLock::new(handler_addresses)), runner: Arc::new(Mutex::new(runner)), strategy: weighted(0.9, strategy).sboxed(), target_reference, last_sequence: Arc::default(), replay: false, - used: false, + override_depth: 0, } } + /// Check if the given address is a handler that can be reentered. + pub fn is_handler(&self, address: Address) -> bool { + self.handler_addresses.read().contains(&address) + } + /// All `self.next()` calls will now pop `self.last_sequence`. Used to replay an invariant /// failure. pub fn set_replay(&mut self, status: bool) { @@ -75,12 +90,9 @@ impl RandomCallGenerator { *self.target_reference.write() = original_caller; // `original_caller` has a 80% chance of being the `new_target`. - let choice = self - .strategy - .new_tree(&mut self.runner.lock()) - .unwrap() - .current() - .map(|call_details| BasicTxDetails { sender, call_details }); + let choice = self.strategy.new_tree(&mut self.runner.lock()).unwrap().current().map( + |call_details| BasicTxDetails { warp: None, roll: None, sender, call_details }, + ); self.last_sequence.write().push(choice.clone()); choice diff --git a/crates/evm/fuzz/src/invariant/filters.rs b/crates/evm/fuzz/src/invariant/filters.rs index 520e5b5afcd6e..f9f068161f5e6 100644 --- a/crates/evm/fuzz/src/invariant/filters.rs +++ b/crates/evm/fuzz/src/invariant/filters.rs @@ -18,8 +18,8 @@ pub struct ArtifactFilters { impl ArtifactFilters { /// Returns `true` if the given identifier matches this filter. pub fn matches(&self, identifier: &str) -> bool { - (self.targeted.is_empty() || self.targeted.contains_key(identifier)) && - (self.excluded.is_empty() || !self.excluded.iter().any(|id| id == identifier)) + (self.targeted.is_empty() || self.targeted.contains_key(identifier)) + && (self.excluded.is_empty() || !self.excluded.iter().any(|id| id == identifier)) } /// Gets all the targeted functions from `artifact`. Returns error, if selectors do not match @@ -38,14 +38,14 @@ impl ArtifactFilters { .collect::>>()?; // targetArtifactSelectors > excludeArtifacts > targetArtifacts if functions.is_empty() && self.excluded.contains(&artifact.identifier()) { - return Ok(None) + return Ok(None); } - return Ok(Some(functions)) + return Ok(Some(functions)); } // If no contract is specifically targeted, and this contract is not excluded, then accept // all functions. if self.targeted.is_empty() && !self.excluded.contains(&artifact.identifier()) { - return Ok(Some(vec![])) + return Ok(Some(vec![])); } Ok(None) } diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index c681512de1e60..d23066061b6e0 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -1,16 +1,25 @@ use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes, Selector}; +use alloy_primitives::{Address, Selector, map::HashMap}; +use foundry_compilers::artifacts::StorageLayout; use itertools::Either; use parking_lot::Mutex; -use std::{collections::BTreeMap, sync::Arc}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt, sync::Arc}; mod call_override; pub use call_override::RandomCallGenerator; mod filters; +use crate::BasicTxDetails; pub use filters::{ArtifactFilters, SenderFilters}; use foundry_common::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::utils::{get_function, StateChangeset}; +use foundry_evm_core::utils::{StateChangeset, get_function}; + +/// Returns true if the function returns `int256`, indicating optimization mode. +/// In optimization mode, the fuzzer maximizes the return value instead of checking invariants. +pub fn is_optimization_invariant(func: &Function) -> bool { + func.outputs.len() == 1 && func.outputs[0].ty == "int256" +} /// Contracts identified as targets during a fuzz run. /// @@ -74,6 +83,7 @@ impl FuzzRunIdentifiedContracts { abi: contract.abi.clone(), targeted_functions: functions, excluded_functions: Vec::new(), + storage_layout: contract.storage_layout.as_ref().map(Arc::clone), }; targets.insert(*address, contract); } @@ -126,6 +136,14 @@ impl TargetedContracts { .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f))) } + /// Returns whether the given transaction can be replayed or not with known contracts. + pub fn can_replay(&self, tx: &BasicTxDetails) -> bool { + match self.inner.get(&tx.call_details.target) { + Some(c) => c.abi.functions().any(|f| f.selector() == tx.call_details.calldata[..4]), + None => false, + } + } + /// Identifies fuzzed contract and function based on given tx details and returns unique metric /// key composed from contract identifier and function name. pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option { @@ -137,6 +155,16 @@ impl TargetedContracts { .map(|function| format!("{}.{}", contract.identifier.clone(), function.name)) }) } + + /// Returns a map of contract addresses to their storage layouts. + pub fn get_storage_layouts(&self) -> HashMap> { + self.inner + .iter() + .filter_map(|(addr, c)| { + c.storage_layout.as_ref().map(|layout| (*addr, Arc::clone(layout))) + }) + .collect() + } } impl std::ops::Deref for TargetedContracts { @@ -164,27 +192,48 @@ pub struct TargetedContract { pub targeted_functions: Vec, /// The excluded functions of the contract. pub excluded_functions: Vec, + /// The contract's storage layout, if available. + pub storage_layout: Option>, } impl TargetedContract { /// Returns a new `TargetedContract` instance. - pub fn new(identifier: String, abi: JsonAbi) -> Self { - Self { identifier, abi, targeted_functions: Vec::new(), excluded_functions: Vec::new() } + pub const fn new(identifier: String, abi: JsonAbi) -> Self { + Self { + identifier, + abi, + targeted_functions: Vec::new(), + excluded_functions: Vec::new(), + storage_layout: None, + } + } + + /// Determines contract storage layout from project contracts. Needs `storageLayout` to be + /// enabled as extra output in project configuration. + pub fn with_project_contracts(mut self, project_contracts: &ContractsByArtifact) -> Self { + if let Some((src, name)) = self.identifier.split_once(':') + && let Some((_, contract_data)) = project_contracts.iter().find(|(artifact, _)| { + artifact.name == name && artifact.source.as_path().ends_with(src) + }) + { + self.storage_layout = contract_data.storage_layout.as_ref().map(Arc::clone); + } + self } /// Helper to retrieve functions to fuzz for specified abi. /// Returns specified targeted functions if any, else mutable abi functions that are not /// marked as excluded. pub fn abi_fuzzed_functions(&self) -> impl Iterator { - if !self.targeted_functions.is_empty() { - Either::Left(self.targeted_functions.iter()) - } else { + if self.targeted_functions.is_empty() { Either::Right(self.abi.functions().filter(|&func| { !matches!( func.state_mutability, alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View ) && !self.excluded_functions.contains(func) })) + } else { + Either::Left(self.targeted_functions.iter()) } } @@ -210,29 +259,13 @@ impl TargetedContract { } } -/// Details of a transaction generated by invariant strategy for fuzzing a target. -#[derive(Clone, Debug)] -pub struct BasicTxDetails { - // Transaction sender address. - pub sender: Address, - // Transaction call details. - pub call_details: CallDetails, -} - -/// Call details of a transaction generated to fuzz invariant target. -#[derive(Clone, Debug)] -pub struct CallDetails { - // Address of target contract. - pub target: Address, - // The data of the transaction. - pub calldata: Bytes, -} - /// Test contract which is testing its invariants. #[derive(Clone, Debug)] pub struct InvariantContract<'a> { /// Address of the test contract. pub address: Address, + /// Name of the test contract. + pub name: &'a str, /// Invariant function present in the test contract. pub invariant_function: &'a Function, /// If true, `afterInvariant` function is called after each invariant run. @@ -240,3 +273,142 @@ pub struct InvariantContract<'a> { /// ABI of the test contract. pub abi: &'a JsonAbi, } + +impl<'a> InvariantContract<'a> { + /// Creates a new invariant contract. + pub const fn new( + address: Address, + name: &'a str, + invariant_function: &'a Function, + call_after_invariant: bool, + abi: &'a JsonAbi, + ) -> Self { + Self { address, name, invariant_function, call_after_invariant, abi } + } + + /// Returns true if this is an optimization mode invariant (returns int256). + pub fn is_optimization(&self) -> bool { + is_optimization_invariant(self.invariant_function) + } +} + +/// Settings that determine the validity of a persisted invariant counterexample. +/// +/// When a counterexample is replayed, it's only valid if the same contracts, selectors, +/// senders, and fail_on_revert settings are used. Changes to unrelated code (e.g., adding +/// a log statement) should not invalidate the counterexample. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct InvariantSettings { + /// Target contracts with their addresses and identifiers. + pub target_contracts: BTreeMap, + /// Target selectors per contract address. + pub target_selectors: BTreeMap>, + /// Target senders for the invariant test. + pub target_senders: Vec
, + /// Excluded senders for the invariant test. + pub excluded_senders: Vec
, + /// Whether the test should fail on any revert. + pub fail_on_revert: bool, +} + +impl InvariantSettings { + /// Creates new invariant settings from the given components. + pub fn new( + targeted_contracts: &TargetedContracts, + sender_filters: &SenderFilters, + fail_on_revert: bool, + ) -> Self { + let target_contracts = targeted_contracts + .inner + .iter() + .map(|(addr, contract)| (*addr, contract.identifier.clone())) + .collect(); + + let target_selectors = targeted_contracts + .inner + .iter() + .map(|(addr, contract)| { + let selectors: Vec = + contract.abi_fuzzed_functions().map(|f| f.selector()).collect(); + (*addr, selectors) + }) + .collect(); + + let mut target_senders = sender_filters.targeted.clone(); + target_senders.sort(); + + let mut excluded_senders = sender_filters.excluded.clone(); + excluded_senders.sort(); + + Self { + target_contracts, + target_selectors, + target_senders, + excluded_senders, + fail_on_revert, + } + } + + /// Compares these settings with another and returns a description of what changed. + /// Returns `None` if the settings are equivalent. + pub fn diff(&self, other: &Self) -> Option { + let mut changes = Vec::new(); + + if self.target_contracts != other.target_contracts { + let added: Vec<_> = other + .target_contracts + .iter() + .filter(|(addr, _)| !self.target_contracts.contains_key(*addr)) + .map(|(_, name)| name.as_str()) + .collect(); + let removed: Vec<_> = self + .target_contracts + .iter() + .filter(|(addr, _)| !other.target_contracts.contains_key(*addr)) + .map(|(_, name)| name.as_str()) + .collect(); + + if !added.is_empty() { + changes.push(format!("added target contracts: {}", added.join(", "))); + } + if !removed.is_empty() { + changes.push(format!("removed target contracts: {}", removed.join(", "))); + } + } + + if self.target_selectors != other.target_selectors { + changes.push("target selectors changed".to_string()); + } + + if self.target_senders != other.target_senders { + changes.push("target senders changed".to_string()); + } + + if self.excluded_senders != other.excluded_senders { + changes.push("excluded senders changed".to_string()); + } + + if self.fail_on_revert != other.fail_on_revert { + changes.push(format!( + "fail_on_revert changed from {} to {}", + self.fail_on_revert, other.fail_on_revert + )); + } + + if changes.is_empty() { None } else { Some(changes.join(", ")) } + } +} + +impl fmt::Display for InvariantSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "targets: {}, selectors: {}, senders: {}, excluded: {}, fail_on_revert: {}", + self.target_contracts.len(), + self.target_selectors.values().map(|v| v.len()).sum::(), + self.target_senders.len(), + self.excluded_senders.len(), + self.fail_on_revert, + ) + } +} diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index bce67dfe320fa..9c3e7d179c7f6 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -3,17 +3,18 @@ //! EVM fuzzing implementation using [`proptest`]. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_primitives::{ + Address, Bytes, Log, U256, map::{AddressHashMap, HashMap}, - Address, Bytes, Log, }; -use foundry_common::{calc, contracts::ContractsByAddress, evm::Breakpoints}; +use foundry_common::{calc, contracts::ContractsByAddress}; +use foundry_evm_core::Breakpoints; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{CallTraceArena, SparsedTraceArena}; use itertools::Itertools; @@ -27,10 +28,64 @@ pub use error::FuzzError; pub mod invariant; pub mod strategies; +pub use strategies::LiteralMaps; mod inspector; pub use inspector::Fuzzer; +/// Metadata needed to reproduce a fuzz run. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct FuzzRunMetadata { + /// Seed used for the worker's input stream. + #[serde(default, rename = "fuzz_seed", skip_serializing_if = "Option::is_none")] + pub seed: Option, + /// 1-based run inside the worker's input stream. + #[serde(default, rename = "fuzz_run", skip_serializing_if = "Option::is_none")] + pub run: Option, + /// Worker that generated the input stream. + #[serde(default, rename = "fuzz_worker", skip_serializing_if = "Option::is_none")] + pub worker: Option, +} + +impl FuzzRunMetadata { + /// Creates metadata for reproducing a fuzz run. + pub const fn new(seed: Option, run: Option, worker: Option) -> Self { + Self { seed, run, worker } + } +} + +/// Details of a transaction generated by fuzz strategy for fuzzing a target. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BasicTxDetails { + /// Time (in seconds) to increase block timestamp before executing the tx. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub warp: Option, + /// Number to increase block number before executing the tx. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub roll: Option, + /// Transaction sender address. + pub sender: Address, + /// Transaction call details. + #[serde(flatten)] + pub call_details: CallDetails, +} + +/// Call details of a transaction generated to fuzz. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CallDetails { + /// Address of target contract. + pub target: Address, + /// The data of the transaction. + pub calldata: Bytes, +} + +impl BasicTxDetails { + /// Returns an estimate of the serialized (JSON) size in bytes. + pub fn estimate_serialized_size(&self) -> usize { + size_of::() + self.call_details.calldata.len() * 2 + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[expect(clippy::large_enum_variant)] pub enum CounterExample { @@ -42,6 +97,10 @@ pub enum CounterExample { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BaseCounterExample { + // Amount to increase block timestamp. + pub warp: Option, + // Amount to increase block number. + pub roll: Option, /// Address which makes the call. pub sender: Option
, /// Address to which to call to. @@ -64,45 +123,54 @@ pub struct BaseCounterExample { /// Whether to display sequence as solidity. #[serde(skip)] pub show_solidity: bool, + /// Fuzz metadata needed to reproduce this counterexample. + #[serde(flatten)] + pub fuzz: FuzzRunMetadata, } impl BaseCounterExample { /// Creates counter example representing a step from invariant call sequence. pub fn from_invariant_call( - sender: Address, - addr: Address, - bytes: &Bytes, + tx: &BasicTxDetails, contracts: &ContractsByAddress, traces: Option, show_solidity: bool, ) -> Self { - if let Some((name, abi)) = &contracts.get(&addr) { - if let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) { - // skip the function selector when decoding - if let Ok(args) = func.abi_decode_input(&bytes[4..]) { - return Self { - sender: Some(sender), - addr: Some(addr), - calldata: bytes.clone(), - contract_name: Some(name.clone()), - func_name: Some(func.name.clone()), - signature: Some(func.signature()), - args: Some( - foundry_common::fmt::format_tokens(&args).format(", ").to_string(), - ), - raw_args: Some( - foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string(), - ), - traces, - show_solidity, - }; - } + let sender = tx.sender; + let target = tx.call_details.target; + let bytes = &tx.call_details.calldata; + let warp = tx.warp; + let roll = tx.roll; + if let Some((name, abi)) = &contracts.get(&target) + && let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) + { + // skip the function selector when decoding + if let Ok(args) = func.abi_decode_input(&bytes[4..]) { + return Self { + warp, + roll, + sender: Some(sender), + addr: Some(target), + calldata: bytes.clone(), + contract_name: Some(name.clone()), + func_name: Some(func.name.clone()), + signature: Some(func.signature()), + args: Some(foundry_common::fmt::format_tokens(&args).format(", ").to_string()), + raw_args: Some( + foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string(), + ), + traces, + show_solidity, + fuzz: FuzzRunMetadata::default(), + }; } } Self { + warp, + roll, sender: Some(sender), - addr: Some(addr), + addr: Some(target), calldata: bytes.clone(), contract_name: None, func_name: None, @@ -111,6 +179,7 @@ impl BaseCounterExample { raw_args: None, traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } @@ -121,6 +190,8 @@ impl BaseCounterExample { traces: Option, ) -> Self { Self { + warp: None, + roll: None, sender: None, addr: None, calldata: bytes, @@ -131,29 +202,41 @@ impl BaseCounterExample { raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()), traces, show_solidity: false, + fuzz: FuzzRunMetadata::default(), } } + + /// Sets fuzz metadata for reproducing this counterexample. + pub const fn with_fuzz_metadata(mut self, fuzz: FuzzRunMetadata) -> Self { + self.fuzz = fuzz; + self + } } impl fmt::Display for BaseCounterExample { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Display counterexample as solidity. - if self.show_solidity { - if let (Some(sender), Some(contract), Some(address), Some(func_name), Some(args)) = + if self.show_solidity + && let (Some(sender), Some(contract), Some(address), Some(func_name), Some(args)) = (&self.sender, &self.contract_name, &self.addr, &self.func_name, &self.raw_args) - { - writeln!(f, "\t\tvm.prank({sender});")?; - write!( - f, - "\t\t{}({}).{}({});", - contract.split_once(':').map_or(contract.as_str(), |(_, contract)| contract), - address, - func_name, - args - )?; - - return Ok(()) + { + if let Some(warp) = &self.warp { + writeln!(f, "\t\tvm.warp(block.timestamp + {warp});")?; + } + if let Some(roll) = &self.roll { + writeln!(f, "\t\tvm.roll(block.number + {roll});")?; } + writeln!(f, "\t\tvm.prank({sender});")?; + write!( + f, + "\t\t{}({}).{}({});", + contract.split_once(':').map_or(contract.as_str(), |(_, contract)| contract), + address, + func_name, + args + )?; + + return Ok(()); } // Regular counterexample display. @@ -169,10 +252,17 @@ impl fmt::Display for BaseCounterExample { write!(f, "{addr} ")? } + if let Some(warp) = &self.warp { + write!(f, "warp={warp} ")?; + } + if let Some(roll) = &self.roll { + write!(f, "roll={roll} ")?; + } + if let Some(sig) = &self.signature { write!(f, "calldata={sig}")? } else { - write!(f, "calldata={}", &self.calldata)? + write!(f, "calldata={}", self.calldata)? } if let Some(args) = &self.args { @@ -184,7 +274,7 @@ impl fmt::Display for BaseCounterExample { } /// The outcome of a fuzz test -#[derive(Debug)] +#[derive(Debug, Default)] pub struct FuzzTestResult { /// we keep this for the debugger pub first_case: FuzzCase, @@ -209,7 +299,7 @@ pub struct FuzzTestResult { pub logs: Vec, /// Labeled addresses - pub labeled_addresses: AddressHashMap, + pub labels: AddressHashMap, /// Exemplary traces for a fuzz run of the test function /// @@ -221,14 +311,17 @@ pub struct FuzzTestResult { /// Those traces should not be displayed. pub gas_report_traces: Vec, - /// Raw coverage info - pub coverage: Option, + /// Raw line coverage info + pub line_coverage: Option, /// Breakpoints for debugger. Correspond to the same fuzz case as `traces`. pub breakpoints: Option, // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, + + /// Number of failed replays from persisted corpus. + pub failed_corpus_replays: usize, } impl FuzzTestResult { @@ -257,8 +350,6 @@ impl FuzzTestResult { /// Data of a single fuzz test case #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct FuzzCase { - /// The calldata used for this fuzz test - pub calldata: Bytes, /// Consumed gas pub gas: u64, /// The initial gas stipend for the transaction @@ -273,30 +364,25 @@ pub struct FuzzedCases { } impl FuzzedCases { - #[inline] pub fn new(mut cases: Vec) -> Self { cases.sort_by_key(|c| c.gas); Self { cases } } - #[inline] pub fn cases(&self) -> &[FuzzCase] { &self.cases } - #[inline] pub fn into_cases(self) -> Vec { self.cases } /// Get the last [FuzzCase] - #[inline] pub fn last(&self) -> Option<&FuzzCase> { self.cases.last() } /// Returns the median gas of all test cases - #[inline] pub fn median_gas(&self, with_stipend: bool) -> u64 { let mut values = self.gas_values(with_stipend); values.sort_unstable(); @@ -304,14 +390,12 @@ impl FuzzedCases { } /// Returns the average gas use of all test cases - #[inline] pub fn mean_gas(&self, with_stipend: bool) -> u64 { let mut values = self.gas_values(with_stipend); values.sort_unstable(); calc::mean(&values) } - #[inline] fn gas_values(&self, with_stipend: bool) -> Vec { self.cases .iter() @@ -320,19 +404,16 @@ impl FuzzedCases { } /// Returns the case with the highest gas usage - #[inline] pub fn highest(&self) -> Option<&FuzzCase> { self.cases.last() } /// Returns the case with the lowest gas usage - #[inline] pub fn lowest(&self) -> Option<&FuzzCase> { self.cases.first() } /// Returns the highest amount of gas spent on a fuzz case - #[inline] pub fn highest_gas(&self, with_stipend: bool) -> u64 { self.highest() .map(|c| if with_stipend { c.gas } else { c.gas - c.stipend }) @@ -340,7 +421,6 @@ impl FuzzedCases { } /// Returns the lowest amount of gas spent on a fuzz case - #[inline] pub fn lowest_gas(&self) -> u64 { self.lowest().map(|c| c.gas).unwrap_or_default() } diff --git a/crates/evm/fuzz/src/strategies/calldata.rs b/crates/evm/fuzz/src/strategies/calldata.rs index 6d9c0340bdca9..c792af57f5b4e 100644 --- a/crates/evm/fuzz/src/strategies/calldata.rs +++ b/crates/evm/fuzz/src/strategies/calldata.rs @@ -1,6 +1,6 @@ use crate::{ - strategies::{fuzz_param_from_state, fuzz_param_with_fixtures, EvmFuzzState}, FuzzFixtures, + strategies::{EvmFuzzState, fuzz_param_from_state, fuzz_param_with_fixtures}, }; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; @@ -9,7 +9,10 @@ use proptest::prelude::Strategy; /// Given a function, it returns a strategy which generates valid calldata /// for that function's input types, following declared test fixtures. -pub fn fuzz_calldata(func: Function, fuzz_fixtures: &FuzzFixtures) -> impl Strategy { +pub fn fuzz_calldata( + func: Function, + fuzz_fixtures: &FuzzFixtures, +) -> impl Strategy + use<> { // We need to compose all the strategies generated for each parameter in all // possible combinations, accounting any parameter declared fixture let strats = func @@ -40,7 +43,7 @@ pub fn fuzz_calldata(func: Function, fuzz_fixtures: &FuzzFixtures) -> impl Strat pub fn fuzz_calldata_from_state( func: Function, state: &EvmFuzzState, -) -> impl Strategy { +) -> impl Strategy + use<> { let strats = func .inputs .iter() @@ -62,10 +65,10 @@ pub fn fuzz_calldata_from_state( #[cfg(test)] mod tests { - use crate::{strategies::fuzz_calldata, FuzzFixtures}; + use crate::{FuzzFixtures, strategies::fuzz_calldata}; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_json_abi::Function; - use alloy_primitives::{map::HashMap, Address}; + use alloy_primitives::{Address, map::HashMap}; use proptest::prelude::Strategy; #[test] diff --git a/crates/evm/fuzz/src/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs index 53d63ce03f923..c96d7c2da6420 100644 --- a/crates/evm/fuzz/src/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -1,5 +1,5 @@ use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{Sign, I256, U256}; +use alloy_primitives::{I256, Sign, U256}; use proptest::{ prelude::Rng, strategy::{NewTree, Strategy, ValueTree}, @@ -23,7 +23,7 @@ impl IntValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: I256, fixed: bool) -> Self { + const fn new(start: I256, fixed: bool) -> Self { Self { lo: I256::ZERO, curr: start, hi: start, fixed } } @@ -41,7 +41,7 @@ impl IntValueTree { fn magnitude_greater(lhs: I256, rhs: I256) -> bool { if lhs.is_zero() { - return false + return false; } (lhs > rhs) ^ (lhs.is_negative()) } @@ -56,7 +56,7 @@ impl ValueTree for IntValueTree { fn simplify(&mut self) -> bool { if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { - return false + return false; } self.hi = self.curr; self.reposition() @@ -64,7 +64,7 @@ impl ValueTree for IntValueTree { fn complicate(&mut self) -> bool { if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { - return false + return false; } self.lo = if self.curr != I256::MIN && self.curr != I256::MAX { @@ -123,10 +123,10 @@ impl IntStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - let offset = I256::from_raw(U256::from(rng.gen_range(0..4))); + let offset = I256::from_raw(U256::from(rng.random_range(0..4))); let umax: U256 = (U256::from(1) << (self.bits - 1)) - U256::from(1); // Choose if we want values around min, -0, +0, or max - let kind = rng.gen_range(0..4); + let kind = rng.random_range(0..4); let start = match kind { 0 => { I256::overflowing_from_sign_and_abs(Sign::Negative, umax + U256::from(1)).0 + offset @@ -142,15 +142,15 @@ impl IntStrategy { fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_random_tree(runner) + return self.generate_random_tree(runner); } // Generate value tree from fixture. - let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; - if let Some(int_fixture) = fixture.as_int() { - if int_fixture.1 == self.bits { - return Ok(IntValueTree::new(int_fixture.0, false)); - } + let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())]; + if let Some(int_fixture) = fixture.as_int() + && int_fixture.1 == self.bits + { + return Ok(IntValueTree::new(int_fixture.0, false)); } // If fixture is not a valid type, raise error and generate random value. @@ -162,15 +162,15 @@ impl IntStrategy { let rng = runner.rng(); // generate random number of bits uniformly - let bits = rng.gen_range(0..=self.bits); + let bits = rng.random_range(0..=self.bits); if bits == 0 { - return Ok(IntValueTree::new(I256::ZERO, false)) + return Ok(IntValueTree::new(I256::ZERO, false)); } // init 2 128-bit randoms - let mut higher: u128 = rng.gen_range(0..=u128::MAX); - let mut lower: u128 = rng.gen_range(0..=u128::MAX); + let mut higher: u128 = rng.random_range(0..=u128::MAX); + let mut lower: u128 = rng.random_range(0..=u128::MAX); // cut 2 randoms according to bits size match bits - 1 { @@ -184,15 +184,14 @@ impl IntStrategy { // init I256 from 2 randoms let mut inner: [u64; 4] = [0; 4]; - let mask64 = (1 << 65) - 1; - inner[0] = (lower & mask64) as u64; + inner[0] = lower as u64; inner[1] = (lower >> 64) as u64; - inner[2] = (higher & mask64) as u64; + inner[2] = higher as u64; inner[3] = (higher >> 64) as u64; // we have a small bias here, i.e. intN::min will never be generated // but it's ok since it's generated in `fn generate_edge_tree(...)` - let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; + let sign = if rng.random::() { Sign::Positive } else { Sign::Negative }; let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner)); Ok(IntValueTree::new(start, false)) @@ -205,7 +204,7 @@ impl Strategy for IntStrategy { fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; - let bias = runner.rng().gen_range(0..total_weight); + let bias = runner.rng().random_range(0..total_weight); // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 3222529099d28..8ff473d4ebc93 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -1,11 +1,12 @@ use super::{fuzz_calldata, fuzz_param_from_state}; use crate::{ - invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters}, - strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, - FuzzFixtures, + BasicTxDetails, CallDetails, FuzzFixtures, + invariant::{FuzzRunIdentifiedContracts, SenderFilters}, + strategies::{EvmFuzzState, fuzz_calldata_from_state, fuzz_param}, }; use alloy_json_abi::Function; -use alloy_primitives::Address; +use alloy_primitives::{Address, U256}; +use foundry_config::InvariantConfig; use parking_lot::RwLock; use proptest::prelude::*; use rand::seq::IteratorRandom; @@ -28,20 +29,30 @@ pub fn override_call_strat( let fuzz_state = fuzz_state.clone(); let fuzz_fixtures = fuzz_fixtures.clone(); - let func = { + let (actual_target, func) = { let contracts = contracts.targets.lock(); - let contract = contracts.get(&target_address).unwrap_or_else(|| { - // Choose a random contract if target selected by lazy strategy is not in fuzz run - // identified contracts. This can happen when contract is created in `setUp` call - // but is not included in targetContracts. - contracts.values().choose(&mut rand::rng()).unwrap() - }); + // If the target address is in the contracts map, use it directly. + // Otherwise, fall back to a random contract from the targeted contracts. + // This can happen when call_override sets target_reference to a contract + // that is not in targetContracts (e.g., the protocol contract during reentrancy). + let (actual_target, contract) = + contracts.get(&target_address).map(|c| (target_address, c)).unwrap_or_else(|| { + let entry = contracts + .iter() + .choose(&mut rand::rng()) + .expect("at least one target contract"); + (*entry.0, entry.1) + }); let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect(); - any::().prop_map(move |index| index.get(&fuzzed_functions).clone()) + ( + actual_target, + any::() + .prop_map(move |index| index.get(&fuzzed_functions).clone()), + ) }; func.prop_flat_map(move |func| { - fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func) + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, actual_target, func) }) }) } @@ -60,25 +71,44 @@ pub fn invariant_strat( fuzz_state: EvmFuzzState, senders: SenderFilters, contracts: FuzzRunIdentifiedContracts, - dictionary_weight: u32, + config: InvariantConfig, fuzz_fixtures: FuzzFixtures, ) -> impl Strategy { let senders = Rc::new(senders); + let dictionary_weight = config.dictionary.dictionary_weight; + + // Strategy to generate values for tx warp and roll. + let warp_roll_strat = |cond: bool| { + if cond { any::().prop_map(Some).boxed() } else { Just(None).boxed() } + }; + any::() .prop_flat_map(move |selector| { let contracts = contracts.targets.lock(); let functions = contracts.fuzzed_functions(); let (target_address, target_function) = selector.select(functions); + let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight); + let call_details = fuzz_contract_with_calldata( &fuzz_state, &fuzz_fixtures, *target_address, target_function.clone(), ); - (sender, call_details) + + let warp = warp_roll_strat(config.max_time_delay.is_some()); + let roll = warp_roll_strat(config.max_block_delay.is_some()); + + (warp, roll, sender, call_details) + }) + .prop_map(move |(warp, roll, sender, call_details)| { + let warp = + warp.map(|time| time % U256::from(config.max_time_delay.unwrap_or_default())); + let roll = + roll.map(|block| block % U256::from(config.max_block_delay.unwrap_or_default())); + BasicTxDetails { warp, roll, sender, call_details } }) - .prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details }) } /// Strategy to select a sender address: @@ -88,19 +118,30 @@ fn select_random_sender( fuzz_state: &EvmFuzzState, senders: Rc, dictionary_weight: u32, -) -> impl Strategy { - if !senders.targeted.is_empty() { - any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() - } else { +) -> impl Strategy + use<> { + if senders.targeted.is_empty() { assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100"); proptest::prop_oneof![ 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address), dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state), ] - .prop_map(move |addr| addr.as_address().unwrap()) - // Too many exclusions can slow down testing. - .prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr)) + .prop_map(move |addr| { + let mut addr = addr.as_address().unwrap(); + // Make sure the selected address is not in the list of excluded senders. + // We don't use proptest's filter to avoid reaching the `PROPTEST_MAX_LOCAL_REJECTS` + // max rejects and exiting test before all runs completes. + // See . + loop { + if !senders.excluded.contains(&addr) { + break; + } + addr = Address::random(); + } + addr + }) .boxed() + } else { + any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() } } @@ -111,7 +152,7 @@ pub fn fuzz_contract_with_calldata( fuzz_fixtures: &FuzzFixtures, target: Address, func: Function, -) -> impl Strategy { +) -> impl Strategy + use<> { // We need to compose all the strategies generated for each parameter in all possible // combinations. // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning. diff --git a/crates/evm/fuzz/src/strategies/literals.rs b/crates/evm/fuzz/src/strategies/literals.rs new file mode 100644 index 0000000000000..6d767a8309e7b --- /dev/null +++ b/crates/evm/fuzz/src/strategies/literals.rs @@ -0,0 +1,357 @@ +use alloy_dyn_abi::DynSolType; +use alloy_primitives::{ + B256, Bytes, I256, U256, keccak256, + map::{B256IndexSet, HashMap, IndexSet}, +}; +use foundry_common::Analysis; +use foundry_compilers::ProjectPathsConfig; +use solar::{ + ast::{self, Visit}, + interface::source_map::FileName, +}; +use std::{ + ops::ControlFlow, + sync::{Arc, OnceLock}, +}; + +#[derive(Clone, Debug)] +pub struct LiteralsDictionary { + maps: Arc>, +} + +impl Default for LiteralsDictionary { + fn default() -> Self { + Self::new(None, None, usize::MAX) + } +} + +impl LiteralsDictionary { + pub fn new( + analysis: Option, + paths_config: Option, + max_values: usize, + ) -> Self { + let maps = Arc::new(OnceLock::::new()); + if let Some(analysis) = analysis + && max_values > 0 + { + let maps = maps.clone(); + // This can't be done in a rayon task (including inside of `get`) because it can cause a + // deadlock, since internally `solar` also uses rayon. + let _ = std::thread::Builder::new().name("literal-collector".into()).spawn(move || { + let _ = maps.get_or_init(|| { + let literals = + LiteralsCollector::process(&analysis, paths_config.as_ref(), max_values); + debug!( + words = literals.words.values().map(|set| set.len()).sum::(), + strings = literals.strings.len(), + bytes = literals.bytes.len(), + "collected source code literals for fuzz dictionary" + ); + literals + }); + }); + } else { + maps.set(Default::default()).unwrap(); + } + Self { maps } + } + + /// Returns a reference to the `LiteralMaps`. + pub fn get(&self) -> &LiteralMaps { + self.maps.wait() + } + + /// Test-only helper to seed the dictionary with literal values. + #[cfg(test)] + pub(crate) fn set(&mut self, map: super::LiteralMaps) { + self.maps = Arc::new(OnceLock::new()); + self.maps.set(map).unwrap(); + } +} + +#[derive(Debug, Default)] +pub struct LiteralMaps { + pub words: HashMap, + pub strings: IndexSet, + pub bytes: IndexSet, +} + +#[derive(Debug, Default)] +pub struct LiteralsCollector { + max_values: usize, + total_values: usize, + output: LiteralMaps, +} + +impl LiteralsCollector { + fn new(max_values: usize) -> Self { + Self { max_values, ..Default::default() } + } + + pub fn process( + analysis: &Analysis, + paths_config: Option<&ProjectPathsConfig>, + max_values: usize, + ) -> LiteralMaps { + analysis.enter(|compiler| { + let mut literals_collector = Self::new(max_values); + for source in compiler.sources().iter() { + // Ignore scripts, and libs + if let Some(paths) = paths_config + && let FileName::Real(source_path) = &source.file.name + && !(source_path.starts_with(&paths.sources) || paths.is_test(source_path)) + { + continue; + } + + if let Some(ast) = &source.ast + && literals_collector.visit_source_unit(ast).is_break() + { + break; + } + } + + literals_collector.output + }) + } +} + +impl<'ast> ast::Visit<'ast> for LiteralsCollector { + type BreakValue = (); + + fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<()> { + // Stop early if we've hit the limit + if self.total_values >= self.max_values { + return ControlFlow::Break(()); + } + + // Handle unary negation of number literals + if let ast::ExprKind::Unary(un_op, inner_expr) = &expr.kind + && un_op.kind == ast::UnOpKind::Neg + && let ast::ExprKind::Lit(lit, _) = &inner_expr.kind + && let ast::LitKind::Number(n) = &lit.kind + { + // Compute the negative I256 value + if let Ok(pos_i256) = I256::try_from(*n) { + let neg_value = -pos_i256; + let neg_b256 = B256::from(neg_value.into_raw()); + + // Store under all intN sizes that can represent this value + for bits in [16, 32, 64, 128, 256] { + if can_fit_int(neg_value, bits) + && self + .output + .words + .entry(DynSolType::Int(bits)) + .or_default() + .insert(neg_b256) + { + self.total_values += 1; + } + } + } + + // Continue walking the expression + return self.walk_expr(expr); + } + + // Handle literals + if let ast::ExprKind::Lit(lit, _) = &expr.kind { + let is_new = match &lit.kind { + ast::LitKind::Number(n) => { + let pos_value = U256::from(*n); + let pos_b256 = B256::from(pos_value); + + // Store under all uintN sizes that can represent this value + for bits in [8, 16, 32, 64, 128, 256] { + if can_fit_uint(pos_value, bits) + && self + .output + .words + .entry(DynSolType::Uint(bits)) + .or_default() + .insert(pos_b256) + { + self.total_values += 1; + } + } + false // already handled inserts individually + } + ast::LitKind::Address(addr) => self + .output + .words + .entry(DynSolType::Address) + .or_default() + .insert(addr.into_word()), + ast::LitKind::Str(ast::StrKind::Hex, sym, _) => { + self.output.bytes.insert(Bytes::copy_from_slice(sym.as_byte_str())) + } + ast::LitKind::Str(_, sym, _) => { + let s = String::from_utf8_lossy(sym.as_byte_str()).into_owned(); + // For strings, also store the hashed version + let hash = keccak256(s.as_bytes()); + if self.output.words.entry(DynSolType::FixedBytes(32)).or_default().insert(hash) + { + self.total_values += 1; + } + // And the right-padded version if it fits. + if s.len() <= 32 { + let padded = B256::right_padding_from(s.as_bytes()); + if self + .output + .words + .entry(DynSolType::FixedBytes(32)) + .or_default() + .insert(padded) + { + self.total_values += 1; + } + } + self.output.strings.insert(s) + } + ast::LitKind::Bool(..) | ast::LitKind::Rational(..) | ast::LitKind::Err(..) => { + false // ignore + } + }; + + if is_new { + self.total_values += 1; + } + } + + self.walk_expr(expr) + } +} + +/// Checks if a signed integer value can fit in intN type. +fn can_fit_int(value: I256, bits: usize) -> bool { + // Calculate the maximum positive value for intN: 2^(N-1) - 1 + let max_val = I256::try_from((U256::from(1) << (bits - 1)) - U256::from(1)) + .expect("max value should fit in I256"); + // Calculate the minimum negative value for intN: -2^(N-1) + let min_val = -max_val - I256::ONE; + + value >= min_val && value <= max_val +} + +/// Checks if an unsigned integer value can fit in uintN type. +fn can_fit_uint(value: U256, bits: usize) -> bool { + if bits == 256 { + return true; + } + // Calculate the maximum value for uintN: 2^N - 1 + let max_val = (U256::from(1) << bits) - U256::from(1); + value <= max_val +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + use solar::interface::{Session, source_map}; + + const SOURCE: &str = r#" + contract Magic { + // plain literals + address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + uint64 constant MAGIC_NUMBER = 1122334455; + int32 constant MAGIC_INT = -777; + bytes32 constant MAGIC_WORD = "abcd1234"; + bytes constant MAGIC_BYTES = hex"deadbeef"; + string constant MAGIC_STRING = "xyzzy"; + + // constant exprs with folding + uint256 constant NEG_FOLDING = uint(-2); + uint256 constant BIN_FOLDING = 2 * 2 ether; + bytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); + }"#; + + #[test] + fn test_literals_collector_coverage() { + let map = process_source_literals(SOURCE); + + // Expected values from the SOURCE contract + let addr = address!("0x6B175474E89094C44Da98b954EedeAC495271d0F").into_word(); + let num = B256::from(U256::from(1122334455u64)); + let int = B256::from(I256::try_from(-777i32).unwrap().into_raw()); + let word = B256::right_padding_from(b"abcd1234"); + let dyn_bytes = Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]); + + assert_word(&map, DynSolType::Address, addr, "Expected DAI in address set"); + assert_word(&map, DynSolType::Uint(64), num, "Expected MAGIC_NUMBER in uint64 set"); + assert_word(&map, DynSolType::Int(32), int, "Expected MAGIC_INT in int32 set"); + assert_word(&map, DynSolType::FixedBytes(32), word, "Expected MAGIC_WORD in bytes32 set"); + assert!(map.strings.contains("xyzzy"), "Expected MAGIC_STRING to be collected"); + assert!( + map.strings.contains("eip1967.proxy.implementation"), + "Expected IMPLEMENTATION_SLOT in string set" + ); + assert!(map.bytes.contains(&dyn_bytes), "Expected MAGIC_BYTES in bytes set"); + } + + #[test] + fn test_literals_collector_size() { + let literals = process_source_literals(SOURCE); + + // Helper to get count for a type, returns 0 if not present + let count = |ty: DynSolType| literals.words.get(&ty).map_or(0, |set| set.len()); + + assert_eq!(count(DynSolType::Address), 1, "Address literal count mismatch"); + assert_eq!(literals.strings.len(), 3, "String literals count mismatch"); + assert_eq!(literals.bytes.len(), 1, "Byte literals count mismatch"); + + // Unsigned integers - MAGIC_NUMBER (1122334455) appears in multiple sizes + assert_eq!(count(DynSolType::Uint(8)), 2, "Uint(8) count mismatch"); + assert_eq!(count(DynSolType::Uint(16)), 3, "Uint(16) count mismatch"); + assert_eq!(count(DynSolType::Uint(32)), 4, "Uint(32) count mismatch"); + assert_eq!(count(DynSolType::Uint(64)), 5, "Uint(64) count mismatch"); + assert_eq!(count(DynSolType::Uint(128)), 5, "Uint(128) count mismatch"); + assert_eq!(count(DynSolType::Uint(256)), 5, "Uint(256) count mismatch"); + + // Signed integers - MAGIC_INT (-777) appears in multiple sizes + assert_eq!(count(DynSolType::Int(16)), 2, "Int(16) count mismatch"); + assert_eq!(count(DynSolType::Int(32)), 2, "Int(32) count mismatch"); + assert_eq!(count(DynSolType::Int(64)), 2, "Int(64) count mismatch"); + assert_eq!(count(DynSolType::Int(128)), 2, "Int(128) count mismatch"); + assert_eq!(count(DynSolType::Int(256)), 2, "Int(256) count mismatch"); + + // FixedBytes(32) includes: + // - MAGIC_WORD + // - String literals (hashed and right-padded versions) + assert_eq!(count(DynSolType::FixedBytes(32)), 6, "FixedBytes(32) count mismatch"); + + // Total count check + assert_eq!( + literals.words.values().map(|set| set.len()).sum::(), + 41, + "Total word values count mismatch" + ); + } + + // -- TEST HELPERS --------------------------------------------------------- + + fn process_source_literals(source: &str) -> LiteralMaps { + let mut compiler = + solar::sema::Compiler::new(Session::builder().with_stderr_emitter().build()); + compiler + .enter_mut(|c| -> std::io::Result<()> { + let mut pcx = c.parse(); + pcx.set_resolve_imports(false); + + pcx.add_file( + c.sess().source_map().new_source_file(source_map::FileName::Stdin, source)?, + ); + pcx.parse(); + let _ = c.lower_asts(); + Ok(()) + }) + .expect("Failed to compile test source"); + + LiteralsCollector::process(&std::sync::Arc::new(compiler), None, usize::MAX) + } + + fn assert_word(literals: &LiteralMaps, ty: DynSolType, value: B256, msg: &str) { + assert!(literals.words.get(&ty).is_some_and(|set| set.contains(&value)), "{}", msg); + } +} diff --git a/crates/evm/fuzz/src/strategies/mod.rs b/crates/evm/fuzz/src/strategies/mod.rs index 1d8e647a52f3f..ceed0df29f701 100644 --- a/crates/evm/fuzz/src/strategies/mod.rs +++ b/crates/evm/fuzz/src/strategies/mod.rs @@ -5,7 +5,10 @@ mod uint; pub use uint::UintStrategy; mod param; -pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures}; +pub use param::{ + fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures, mutate_param_value, + mutate_param_value_with_senders, +}; mod calldata; pub use calldata::{fuzz_calldata, fuzz_calldata_from_state}; @@ -15,3 +18,9 @@ pub use state::EvmFuzzState; mod invariants; pub use invariants::{fuzz_contract_with_calldata, invariant_strat, override_call_strat}; + +mod mutators; +pub use mutators::BoundMutator; + +mod literals; +pub use literals::{LiteralMaps, LiteralsCollector, LiteralsDictionary}; diff --git a/crates/evm/fuzz/src/strategies/mutators.rs b/crates/evm/fuzz/src/strategies/mutators.rs new file mode 100644 index 0000000000000..db944dedb9a95 --- /dev/null +++ b/crates/evm/fuzz/src/strategies/mutators.rs @@ -0,0 +1,729 @@ +use alloy_dyn_abi::Word; +use alloy_primitives::{Address, I256, Sign, U256}; +use proptest::{prelude::*, test_runner::TestRunner}; +use rand::seq::IndexedRandom; +use std::fmt::Debug; + +// Interesting 8-bit values to inject. +static INTERESTING_8: &[i8] = &[-128, -1, 0, 1, 16, 32, 64, 100, 127]; + +/// Interesting 16-bit values to inject. +static INTERESTING_16: &[i16] = &[ + -128, -1, 0, 1, 16, 32, 64, 100, 127, -32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, +]; + +/// Interesting 32-bit values to inject. +static INTERESTING_32: &[i32] = &[ + -128, + -1, + 0, + 1, + 16, + 32, + 64, + 100, + 127, + -32768, + -129, + 128, + 255, + 256, + 512, + 1000, + 1024, + 4096, + 32767, + -2147483648, + -100663046, + -32769, + 32768, + 65535, + 65536, + 100663045, + 2147483647, +]; + +/// Multipliers used to define the 3 standard deviation range of a Gaussian-like curve. +/// For example, a multiplier of 0.25 means the +/-3 standard deviation bounds are +/-25% of the +/// original value. +static THREE_SIGMA_MULTIPLIERS: &[f64] = &[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]; + +/// Mutator that randomly increments or decrements an uint or int. +pub(crate) trait IncrementDecrementMutator: Sized + Copy + Debug { + fn validate(old: Self, new: Self, size: usize) -> Option; + + #[instrument( + name = "mutator::increment_decrement", + level = "trace", + skip(size, test_runner), + ret + )] + fn increment_decrement(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mutated = if test_runner.rng().random::() { + self.wrapping_add(Self::ONE) + } else { + self.wrapping_sub(Self::ONE) + }; + Self::validate(self, mutated, size) + } + + fn wrapping_add(self, rhs: Self) -> Self; + fn wrapping_sub(self, rhs: Self) -> Self; + const ONE: Self; +} + +macro_rules! impl_increment_decrement_mutator { + ($ty:ty, $validate_fn:path) => { + impl IncrementDecrementMutator for $ty { + fn validate(old: Self, new: Self, size: usize) -> Option { + $validate_fn(old, new, size) + } + + fn wrapping_add(self, rhs: Self) -> Self { + Self::wrapping_add(self, rhs) + } + + fn wrapping_sub(self, rhs: Self) -> Self { + Self::wrapping_sub(self, rhs) + } + + const ONE: Self = Self::ONE; + } + }; +} + +impl_increment_decrement_mutator!(U256, validate_uint_mutation); +impl_increment_decrement_mutator!(I256, validate_int_mutation); + +/// Mutator that changes the current value of an uint or int by applying gaussian noise. +pub(crate) trait GaussianNoiseMutator: Sized + Copy + Debug { + fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option; +} + +impl GaussianNoiseMutator for U256 { + #[instrument( + name = "U256::mutate_with_gaussian_noise", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option { + let scale_factor = sample_gaussian_scale(&mut test_runner.rng())?; + let mut bytes: [u8; 32] = self.to_be_bytes(); + apply_scale_to_bytes(&mut bytes[32 - size / 8..], scale_factor)?; + validate_uint_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +impl GaussianNoiseMutator for I256 { + #[instrument( + name = "I256::mutate_with_gaussian_noise", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option { + let scale_factor = sample_gaussian_scale(&mut test_runner.rng())?; + let mut bytes: [u8; 32] = self.to_be_bytes(); + apply_scale_to_bytes(&mut bytes[32 - size / 8..], scale_factor)?; + validate_int_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +/// Mutator that bounds the current value of an uint or int in the given range. +/// The mutated value is always different from the current value. +pub trait BoundMutator: Sized + Copy + Debug { + fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option; +} + +impl BoundMutator for U256 { + #[instrument(name = "U256::bound", level = "trace", skip(test_runner), ret)] + fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option { + if min > max || self < min || self > max || min == max { + return None; + } + + let rng = test_runner.rng(); + + loop { + let bits = rng.random_range(8..=256); + let mask = (Self::ONE << bits) - Self::ONE; + let candidate = Self::from(rng.random::()) & mask; + + // Map to range. + let candidate = min + (candidate % ((max - min).saturating_add(Self::ONE))); + + if candidate != self { + return Some(candidate); + } + } + } +} + +impl BoundMutator for I256 { + #[instrument(name = "I256::bound", level = "trace", skip(test_runner), ret)] + fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option { + if min > max || self < min || self > max || min == max { + return None; + } + + let rng = test_runner.rng(); + + loop { + let bits = rng.random_range(8..=255); + let mask = (U256::ONE << bits) - U256::ONE; + let rand_u = U256::from(rng.next_u64()) | (U256::from(rng.next_u64()) << 64); + let unsigned_candidate = rand_u & mask; + + let signed_candidate = { + let midpoint = U256::ONE << (bits - 1); + if unsigned_candidate < midpoint { + Self::from_raw(unsigned_candidate) + } else { + Self::from_raw(unsigned_candidate) - Self::from_raw(U256::ONE << bits) + } + }; + + // Map to range. + let range = max.saturating_sub(min).saturating_add(Self::ONE).unsigned_abs(); + let wrapped = Self::from_raw(U256::from(signed_candidate.unsigned_abs()) % range); + let candidate = + if signed_candidate.is_negative() { max - wrapped } else { min + wrapped }; + + if candidate != self { + return Some(candidate); + } + } + } +} + +/// Mutator that changes the current value by flipping a random bit. +pub(crate) trait BitMutator: Sized + Copy + Debug { + fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option; +} + +impl BitMutator for U256 { + #[instrument(name = "U256::flip_random_bit", level = "trace", skip(size, test_runner), ret)] + fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + flip_random_bit_in_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_uint_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +impl BitMutator for I256 { + #[instrument(name = "I256::flip_random_bit", level = "trace", skip(size, test_runner), ret)] + fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + flip_random_bit_in_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_int_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +impl BitMutator for Address { + #[instrument(name = "Address::flip_random_bit", level = "trace", skip(_size, test_runner), ret)] + fn flip_random_bit(self, _size: usize, test_runner: &mut TestRunner) -> Option { + let mut mutated = self; + flip_random_bit_in_slice(mutated.as_mut_slice(), test_runner)?; + (self != mutated).then_some(mutated) + } +} + +impl BitMutator for Word { + #[instrument(name = "Word::flip_random_bit", level = "trace", skip(size, test_runner), ret)] + fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes = self; + let slice = &mut bytes[..size]; + flip_random_bit_in_slice(slice, test_runner)?; + (self != bytes).then_some(bytes) + } +} + +/// Mutator that changes the current value by randomly injecting interesting words (for uint, int, +/// address and fixed bytes) - see . +pub(crate) trait InterestingWordMutator: Sized + Copy + Debug { + fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option; + fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option; + fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option; +} + +impl InterestingWordMutator for U256 { + #[instrument( + name = "U256::mutate_interesting_byte", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_byte_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_uint_mutation(self, Self::from_be_bytes(bytes), size) + } + + #[instrument( + name = "U256::mutate_interesting_word", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_word_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_uint_mutation(self, Self::from_be_bytes(bytes), size) + } + + #[instrument( + name = "U256::mutate_interesting_dword", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_dword_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_uint_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +impl InterestingWordMutator for I256 { + #[instrument( + name = "I256::mutate_interesting_byte", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_byte_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_int_mutation(self, Self::from_be_bytes(bytes), size) + } + + #[instrument( + name = "I256::mutate_interesting_word", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_word_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_int_mutation(self, Self::from_be_bytes(bytes), size) + } + + #[instrument( + name = "I256::mutate_interesting_dword", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes: [u8; 32] = self.to_be_bytes(); + mutate_interesting_dword_slice(&mut bytes[32 - size / 8..], test_runner)?; + validate_int_mutation(self, Self::from_be_bytes(bytes), size) + } +} + +impl InterestingWordMutator for Address { + #[instrument( + name = "Address::mutate_interesting_byte", + level = "trace", + skip(_size, test_runner), + ret + )] + fn mutate_interesting_byte(self, _size: usize, test_runner: &mut TestRunner) -> Option { + let mut mutated = self; + mutate_interesting_byte_slice(mutated.as_mut_slice(), test_runner)?; + (self != mutated).then_some(mutated) + } + + #[instrument( + name = "Address::mutate_interesting_word", + level = "trace", + skip(_size, test_runner), + ret + )] + fn mutate_interesting_word(self, _size: usize, test_runner: &mut TestRunner) -> Option { + let mut mutated = self; + mutate_interesting_word_slice(mutated.as_mut_slice(), test_runner)?; + (self != mutated).then_some(mutated) + } + + #[instrument( + name = "Address::mutate_interesting_dword", + level = "trace", + skip(_size, test_runner), + ret + )] + fn mutate_interesting_dword(self, _size: usize, test_runner: &mut TestRunner) -> Option { + let mut mutated = self; + mutate_interesting_dword_slice(mutated.as_mut_slice(), test_runner)?; + (self != mutated).then_some(mutated) + } +} + +impl InterestingWordMutator for Word { + #[instrument( + name = "Word::mutate_interesting_byte", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes = self; + let slice = &mut bytes[..size]; + mutate_interesting_byte_slice(slice, test_runner)?; + (self != bytes).then_some(bytes) + } + + #[instrument( + name = "Word::mutate_interesting_word", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes = self; + let slice = &mut bytes[..size]; + mutate_interesting_word_slice(slice, test_runner)?; + (self != bytes).then_some(bytes) + } + + #[instrument( + name = "Word::mutate_interesting_dword", + level = "trace", + skip(size, test_runner), + ret + )] + fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option { + let mut bytes = self; + let slice = &mut bytes[..size]; + mutate_interesting_dword_slice(slice, test_runner)?; + (self != bytes).then_some(bytes) + } +} + +/// Flips a random bit in the given mutable byte slice. +fn flip_random_bit_in_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> { + if bytes.is_empty() { + return None; + } + let bit_index = test_runner.rng().random_range(0..(bytes.len() * 8)); + bytes[bit_index / 8] ^= 1 << (bit_index % 8); + Some(()) +} + +/// Mutates a random byte in the given byte slice by replacing it with a randomly chosen +/// interesting 8-bit value. +fn mutate_interesting_byte_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> { + let index = test_runner.rng().random_range(0..bytes.len()); + let val = *INTERESTING_8.choose(&mut test_runner.rng())? as u8; + bytes[index] = val; + Some(()) +} + +/// Mutates a random 2-byte (16-bit) region in the byte slice with a randomly chosen interesting +/// 16-bit value. +fn mutate_interesting_word_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> { + if bytes.len() < 2 { + return None; + } + let index = test_runner.rng().random_range(0..=bytes.len() - 2); + let val = *INTERESTING_16.choose(&mut test_runner.rng())? as u16; + bytes[index..index + 2].copy_from_slice(&val.to_be_bytes()); + Some(()) +} + +/// Mutates a random 4-byte (32-bit) region in the byte slice with a randomly chosen interesting +/// 32-bit value. +fn mutate_interesting_dword_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> { + if bytes.len() < 4 { + return None; + } + let index = test_runner.rng().random_range(0..=bytes.len() - 4); + let val = *INTERESTING_32.choose(&mut test_runner.rng())? as u32; + bytes[index..index + 4].copy_from_slice(&val.to_be_bytes()); + Some(()) +} + +/// Samples a scale factor from a pseudo-Gaussian distribution centered around 1.0. +/// +/// - Select a random standard deviation multiplier from a predefined set. +/// - Approximates a standard normal distribution using the Irwin-Hall method (sum of uniform +/// samples). +/// - Scales the normal value by the chosen standard deviation multiplier, divided by 3 to get +/// standard deviation. +/// - Adds 1.0 to center the scale factor around 1.0 (no mutation). +/// +/// Returns a scale factor that, when applied to a number, mimics Gaussian noise. +fn sample_gaussian_scale(rng: &mut R) -> Option { + let num_samples = 8; + let chosen_3rd_sigma = *THREE_SIGMA_MULTIPLIERS.choose(rng).unwrap_or(&1.0); + + let mut sum = 0.0; + for _ in 0..num_samples { + sum += rng.random::(); + } + + let standard_normal = sum - (num_samples as f64 / 2.0); + let mut scale_factor = (chosen_3rd_sigma / 3.0) * standard_normal; + scale_factor += 1.0; + + if scale_factor < 0.0 || (scale_factor - 1.0).abs() < f64::EPSILON { + None + } else { + Some(scale_factor) + } +} + +/// Applies a floating-point scale factor to a byte slice representing an unsigned or signed +/// integer. +fn apply_scale_to_bytes(bytes: &mut [u8], scale_factor: f64) -> Option<()> { + let mut carry_down = 0.0; + + for i in (0..bytes.len()).rev() { + let byte_val = bytes[i] as f64; + let scaled = (byte_val + carry_down * 256.0) * scale_factor; + + if i == 0 && scaled >= 256.0 { + for b in bytes.iter_mut() { + *b = 0xFF; + } + return Some(()); + } + + bytes[i] = (scaled % 256.0).floor() as u8; + + let mut carry_up = (scaled / 256.0).floor(); + carry_down = (scaled % 1.0) / scale_factor; + + let mut j = i; + // Propagate carry_up until it is zero or no more bytes left + while carry_up > 0.0 && j > 0 { + j -= 1; + let new_val = bytes[j] as f64 + carry_up; + if j == 0 && new_val >= 256.0 { + for b in bytes.iter_mut() { + *b = 0xFF; + } + return Some(()); + } + bytes[j] = (new_val % 256.0).floor() as u8; + carry_up = (new_val / 256.0).floor(); + } + } + + Some(()) +} + +/// Returns mutated uint value if different from the original value and if it fits in the given +/// size, otherwise None. +fn validate_uint_mutation(original: U256, mutated: U256, size: usize) -> Option { + // Early return if mutated value is the same as original value. + if mutated == original { + return None; + } + + // Check if mutated value fits the given size. + let max = if size < 256 { (U256::from(1) << size) - U256::from(1) } else { U256::MAX }; + (mutated <= max).then_some(mutated) +} + +/// Returns mutated int value if different from the original value and if it fits in the given size, +/// otherwise None. +fn validate_int_mutation(original: I256, mutated: I256, size: usize) -> Option { + // Early return if mutated value is the same as original value. + if mutated == original { + return None; + } + + // Check if mutated value fits the given size. + let max_abs = (U256::from(1) << (size - 1)) - U256::from(1); + match mutated.sign() { + Sign::Positive => mutated < I256::overflowing_from_sign_and_abs(Sign::Positive, max_abs).0, + Sign::Negative => mutated > I256::overflowing_from_sign_and_abs(Sign::Negative, max_abs).0, + } + .then_some(mutated) +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::test_runner::Config; + + #[test] + fn validate_uint_mutation_accepts_inclusive_max() { + // For width `size`, valid unsigned values are 0..=max where max = 2^size - 1. + // Regression: `< max` incorrectly rejected `mutated == max`; must use `<= max`. + for size in [8usize, 16, 32, 64, 128] { + let max = (U256::from(1) << size) - U256::from(1); + assert_eq!( + super::validate_uint_mutation(U256::ZERO, max, size), + Some(max), + "size={size}" + ); + } + let original = U256::from(1); + assert_eq!(super::validate_uint_mutation(original, U256::MAX, 256), Some(U256::MAX)); + } + + #[test] + fn test_mutate_uint() { + let mut runner = TestRunner::new(Config::default()); + let size = 32; + + let test_values = + vec![U256::ZERO, U256::ONE, U256::from(12345u64), U256::from(255), U256::MAX]; + + #[track_caller] + fn validate_mutation(value: U256, mutated: Option) { + assert!( + mutated.is_none() || mutated.is_some_and(|m| m != value), + "Mutation failed: value = {value:?}, mutated = {mutated:?}" + ); + } + + for value in test_values { + for _ in 0..100 { + validate_mutation(value, U256::increment_decrement(value, size, &mut runner)); + validate_mutation(value, U256::flip_random_bit(value, size, &mut runner)); + validate_mutation(value, U256::mutate_interesting_byte(value, size, &mut runner)); + validate_mutation(value, U256::mutate_interesting_word(value, size, &mut runner)); + validate_mutation(value, U256::mutate_interesting_dword(value, size, &mut runner)); + } + } + } + + #[test] + fn test_mutate_int() { + let mut runner = TestRunner::new(Config::default()); + let size = 32; + + let test_values = vec![ + I256::ZERO, + I256::ONE, + I256::MINUS_ONE, + I256::from_dec_str("12345").unwrap(), + I256::from_dec_str("-54321").unwrap(), + I256::from_dec_str("340282366920938463463374607431768211455").unwrap(), + I256::from_dec_str("-340282366920938463463374607431768211455").unwrap(), + ]; + + #[track_caller] + fn validate_mutation(value: I256, mutated: Option) { + assert!( + mutated.is_none() || mutated.is_some_and(|m| m != value), + "Mutation failed: value = {value:?}, mutated = {mutated:?}" + ); + } + + for value in test_values { + for _ in 0..100 { + validate_mutation(value, I256::increment_decrement(value, size, &mut runner)); + validate_mutation(value, I256::flip_random_bit(value, size, &mut runner)); + validate_mutation(value, I256::mutate_interesting_byte(value, size, &mut runner)); + validate_mutation(value, I256::mutate_interesting_word(value, size, &mut runner)); + validate_mutation(value, I256::mutate_interesting_dword(value, size, &mut runner)); + } + } + } + + #[test] + fn test_mutate_address() { + let mut runner = TestRunner::new(Config::default()); + let value = Address::random(); + + #[track_caller] + fn validate_mutation(value: Address, mutated: Option
) { + assert!( + mutated.is_none() || mutated.is_some_and(|mutated| mutated != value), + "Mutation failed for value: {value:?}, result: {mutated:?}" + ); + } + + for _ in 0..100 { + validate_mutation(value, Address::flip_random_bit(value, 20, &mut runner)); + validate_mutation(value, Address::mutate_interesting_byte(value, 20, &mut runner)); + validate_mutation(value, Address::mutate_interesting_word(value, 20, &mut runner)); + validate_mutation(value, Address::mutate_interesting_dword(value, 20, &mut runner)); + } + } + + #[test] + fn test_mutate_word() { + let mut runner = TestRunner::new(Config::default()); + let value = Word::random(); + + #[track_caller] + fn validate_mutation(value: Word, mutated: Option) { + assert!( + mutated.is_none() || mutated.is_some_and(|mutated| mutated != value), + "Mutation failed for value: {value:?}, result: {mutated:?}" + ); + } + + for _ in 0..100 { + validate_mutation(value, Word::flip_random_bit(value, 32, &mut runner)); + validate_mutation(value, Word::mutate_interesting_byte(value, 32, &mut runner)); + validate_mutation(value, Word::mutate_interesting_word(value, 32, &mut runner)); + validate_mutation(value, Word::mutate_interesting_dword(value, 32, &mut runner)); + } + } + + #[test] + fn test_mutate_interesting_word_too_small_returns_none() { + let mut runner = TestRunner::new(Config::default()); + let value = U256::from(123); + assert!(U256::mutate_interesting_word(value, 8, &mut runner).is_none()); + } + + #[test] + fn test_mutate_interesting_dword_too_small_returns_none() { + let mut runner = TestRunner::new(Config::default()); + let value = I256::from_dec_str("123").unwrap(); + assert!(I256::mutate_interesting_dword(value, 16, &mut runner).is_none()); + } + + #[test] + fn test_u256_bound() { + let mut runner = TestRunner::new(Config::default()); + let min = U256::from(0u64); + let max = U256::from(200u64); + let original = U256::from(100u64); + + for _ in 0..50 { + let result = original.bound(min, max, &mut runner); + assert!(result.is_some(), "Mutation should occur"); + + let mutated = result.unwrap(); + assert!(mutated >= min, "Mutated value >= min"); + assert!(mutated <= max, "Mutated value <= max"); + assert_ne!(mutated, original, "mutated value should differ from original"); + } + + // Test bound in [min, max] range. + let result = original.bound(U256::MIN, U256::MAX, &mut runner); + assert!(result.is_some(), "Mutation should occur"); + } + + #[test] + fn test_i256_bound() { + let mut runner = TestRunner::new(Config::default()); + let min = I256::from_dec_str("-100").unwrap(); + let max = I256::from_dec_str("100").unwrap(); + let original = I256::from_dec_str("10").unwrap(); + + for _ in 0..50 { + let result = original.bound(min, max, &mut runner); + assert!(result.is_some(), "Mutation should occur"); + + let mutated = result.unwrap(); + assert!(mutated >= min, "Mutated value >= min"); + assert!(mutated <= max, "Mutated value <= max"); + assert_ne!(mutated, original, "Mutated value should not equal current"); + } + + // Test bound in [min, max] range. + let result = original.bound(I256::MIN, I256::MAX, &mut runner); + assert!(result.is_some(), "Mutation should occur"); + } +} diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index a8834bcef9942..54bc341d767e7 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -1,8 +1,15 @@ use super::state::EvmFuzzState; -use alloy_dyn_abi::{DynSolType, DynSolValue}; +use crate::{ + invariant::SenderFilters, + strategies::mutators::{ + BitMutator, GaussianNoiseMutator, IncrementDecrementMutator, InterestingWordMutator, + }, +}; +use alloy_dyn_abi::{DynSolType, DynSolValue, Word}; use alloy_primitives::{Address, B256, I256, U256}; -use proptest::prelude::*; -use rand::{rngs::StdRng, SeedableRng}; +use proptest::{prelude::*, test_runner::TestRunner}; +use rand::{SeedableRng, prelude::IndexedMutRandom, rngs::StdRng}; +use std::mem::replace; /// The max length of arrays we fuzz for is 256. const MAX_ARRAY_LEN: usize = 256; @@ -41,11 +48,11 @@ fn fuzz_param_inner( param: &DynSolType, mut fuzz_fixtures: Option<(&[DynSolValue], &str)>, ) -> BoxedStrategy { - if let Some((fixtures, name)) = fuzz_fixtures { - if !fixtures.iter().all(|f| f.matches(param)) { - error!("fixtures for {name:?} do not match type {param}"); - fuzz_fixtures = None; - } + if let Some((fixtures, name)) = fuzz_fixtures + && !fixtures.iter().all(|f| f.matches(param)) + { + error!("fixtures for {name:?} do not match type {param}"); + fuzz_fixtures = None; } let fuzz_fixtures = fuzz_fixtures.map(|(f, _)| f); @@ -135,9 +142,7 @@ pub fn fuzz_param_from_state( value() .prop_map(move |value| { let mut fuzzed_addr = Address::from_word(value); - if !deployed_libs.contains(&fuzzed_addr) { - DynSolValue::Address(fuzzed_addr) - } else { + if deployed_libs.contains(&fuzzed_addr) { let mut rng = StdRng::seed_from_u64(0x1337); // use deterministic rng // Do not use addresses of deployed libraries as fuzz input, instead return @@ -151,9 +156,8 @@ pub fn fuzz_param_from_state( break; } } - - DynSolValue::Address(fuzzed_addr) } + DynSolValue::Address(fuzzed_addr) }) .boxed() } @@ -169,15 +173,59 @@ pub fn fuzz_param_from_state( }) .boxed(), DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), - DynSolType::String => DynSolValue::type_strategy(param) - .prop_map(move |value| { - DynSolValue::String( - value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), - ) - }) - .boxed(), + DynSolType::String => { + let state = state.clone(); + (proptest::bool::weighted(0.3), any::()) + .prop_flat_map(move |(use_ast, select_index)| { + let dict = state.dictionary_read(); + + // AST string literals available: 30% probability + let ast_strings = dict.ast_strings(); + if use_ast && !ast_strings.is_empty() { + let s = &ast_strings.as_slice()[select_index.index(ast_strings.len())]; + return Just(DynSolValue::String(s.clone())).boxed(); + } + + // Fallback to random string generation + DynSolValue::type_strategy(&DynSolType::String) + .prop_map(|value| { + DynSolValue::String( + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), + ) + }) + .boxed() + }) + .boxed() + } DynSolType::Bytes => { - value().prop_map(move |value| DynSolValue::Bytes(value.0.into())).boxed() + let state_clone = state.clone(); + ( + value(), + proptest::bool::weighted(0.1), + proptest::bool::weighted(0.2), + any::(), + ) + .prop_map(move |(word, use_ast_string, use_ast_bytes, select_index)| { + let dict = state_clone.dictionary_read(); + + // Try string literals as bytes: 10% chance + let ast_strings = dict.ast_strings(); + if use_ast_string && !ast_strings.is_empty() { + let s = &ast_strings.as_slice()[select_index.index(ast_strings.len())]; + return DynSolValue::Bytes(s.as_bytes().to_vec()); + } + + // Try hex literals: 20% chance + let ast_bytes = dict.ast_bytes(); + if use_ast_bytes && !ast_bytes.is_empty() { + let bytes = &ast_bytes.as_slice()[select_index.index(ast_bytes.len())]; + return DynSolValue::Bytes(bytes.to_vec()); + } + + // Fallback to the generated word from the dictionary: 70% chance + DynSolValue::Bytes(word.0.into()) + }) + .boxed() } DynSolType::Int(n @ 8..=256) => match n / 8 { 32 => value() @@ -185,11 +233,19 @@ pub fn fuzz_param_from_state( .boxed(), 1..=31 => value() .prop_map(move |value| { - // Generate a uintN in the correct range, then shift it to the range of intN - // by subtracting 2^(N-1) - let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); - let max_int_plus1 = U256::from(1).wrapping_shl(n - 1); - let num = I256::from_raw(uint.wrapping_sub(max_int_plus1)); + // Extract lower N bits + let uint_n = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); + // Interpret as signed int (two's complement) --> check sign bit (bit N-1). + let sign_bit = U256::from(1) << (n - 1); + let num = if uint_n >= sign_bit { + // Negative number in two's complement + let modulus = U256::from(1) << n; + I256::from_raw(uint_n.wrapping_sub(modulus)) + } else { + // Positive number + I256::from_raw(uint_n) + }; + DynSolValue::Int(num, n) }) .boxed(), @@ -227,22 +283,250 @@ pub fn fuzz_param_from_state( } } +/// Selects a random address for mutation, respecting sender filters if provided. +/// +/// Priority: +/// 1. If `senders` has targeted addresses, pick randomly from those +/// 2. Otherwise, pick from the dictionary state values (excluding any in `senders.excluded`) +/// 3. Returns `None` if no suitable address is found or if the selected address equals `current` +fn select_random_address( + current: Address, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, +) -> Option
{ + if let Some(senders) = senders { + if !senders.targeted.is_empty() { + // Pick from targeted senders + let index = test_runner.rng().random_range(0..senders.targeted.len()); + let addr = senders.targeted[index]; + return (addr != current).then_some(addr); + } + + // Pick from dictionary state values, excluding addresses in the exclusion list + let dict = state.dictionary_read(); + let values = dict.values(); + if values.is_empty() { + return None; + } + + // Try a few times to find a non-excluded address + for _ in 0..10 { + let index = test_runner.rng().random_range(0..values.len()); + let addr = Address::from_word(values[index]); + if addr != current && !senders.excluded.contains(&addr) { + return Some(addr); + } + } + None + } else { + // No sender filters, just pick from dictionary state values + let dict = state.dictionary_read(); + let values = dict.values(); + if values.is_empty() { + None + } else { + let index = test_runner.rng().random_range(0..values.len()); + let addr = Address::from_word(values[index]); + (addr != current).then_some(addr) + } + } +} + +/// Mutates the current value of the given parameter type and value. +pub fn mutate_param_value( + param: &DynSolType, + value: DynSolValue, + test_runner: &mut TestRunner, + state: &EvmFuzzState, +) -> DynSolValue { + mutate_param_value_inner(param, value, test_runner, state, None) +} + +/// Mutates the current value of the given parameter type and value, with optional sender filters. +/// +/// When `senders` is provided and has targeted addresses, address mutations will prefer +/// selecting from those targeted addresses (similar to `select_random_sender` behavior). +pub fn mutate_param_value_with_senders( + param: &DynSolType, + value: DynSolValue, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: &SenderFilters, +) -> DynSolValue { + mutate_param_value_inner(param, value, test_runner, state, Some(senders)) +} + +fn mutate_param_value_inner( + param: &DynSolType, + value: DynSolValue, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, +) -> DynSolValue { + let new_value = |param: &DynSolType, test_runner: &mut TestRunner| { + fuzz_param_from_state(param, state) + .new_tree(test_runner) + .expect("Could not generate case") + .current() + }; + + match value { + DynSolValue::Bool(val) => { + // flip boolean value + trace!(target: "mutator", "Bool flip {val}"); + Some(DynSolValue::Bool(!val)) + } + DynSolValue::Uint(val, size) => match test_runner.rng().random_range(0..=6) { + 0 => U256::increment_decrement(val, size, test_runner), + 1 => U256::flip_random_bit(val, size, test_runner), + 2 => U256::mutate_interesting_byte(val, size, test_runner), + 3 => U256::mutate_interesting_word(val, size, test_runner), + 4 => U256::mutate_interesting_dword(val, size, test_runner), + 5 => U256::mutate_with_gaussian_noise(val, size, test_runner), + 6 => None, + _ => unreachable!(), + } + .map(|v| DynSolValue::Uint(v, size)), + DynSolValue::Int(val, size) => match test_runner.rng().random_range(0..=6) { + 0 => I256::increment_decrement(val, size, test_runner), + 1 => I256::flip_random_bit(val, size, test_runner), + 2 => I256::mutate_interesting_byte(val, size, test_runner), + 3 => I256::mutate_interesting_word(val, size, test_runner), + 4 => I256::mutate_interesting_dword(val, size, test_runner), + 5 => I256::mutate_with_gaussian_noise(val, size, test_runner), + 6 => None, + _ => unreachable!(), + } + .map(|v| DynSolValue::Int(v, size)), + DynSolValue::Address(val) => match test_runner.rng().random_range(0..=5) { + 0 => Address::flip_random_bit(val, 20, test_runner), + 1 => Address::mutate_interesting_byte(val, 20, test_runner), + 2 => Address::mutate_interesting_word(val, 20, test_runner), + 3 => Address::mutate_interesting_dword(val, 20, test_runner), + // Replace with a random address from targeted senders or dictionary. + 4 => select_random_address(val, test_runner, state, senders), + 5 => None, + _ => unreachable!(), + } + .map(DynSolValue::Address), + DynSolValue::Array(mut values) => { + if let DynSolType::Array(param_type) = param + && !values.is_empty() + { + match test_runner.rng().random_range(0..=2) { + // Decrease array size by removing a random element. + 0 => { + values.remove(test_runner.rng().random_range(0..values.len())); + } + // Increase array size. + 1 => values.push(new_value(param_type, test_runner)), + // Mutate random array element. + 2 => mutate_random_array_value( + &mut values, + param_type, + test_runner, + state, + senders, + ), + _ => unreachable!(), + } + Some(DynSolValue::Array(values)) + } else { + None + } + } + DynSolValue::FixedArray(mut values) => { + if let DynSolType::FixedArray(param_type, _size) = param + && !values.is_empty() + { + mutate_random_array_value(&mut values, param_type, test_runner, state, senders); + Some(DynSolValue::FixedArray(values)) + } else { + None + } + } + DynSolValue::FixedBytes(word, size) => match test_runner.rng().random_range(0..=4) { + 0 => Word::flip_random_bit(word, size, test_runner), + 1 => Word::mutate_interesting_byte(word, size, test_runner), + 2 => Word::mutate_interesting_word(word, size, test_runner), + 3 => Word::mutate_interesting_dword(word, size, test_runner), + 4 => None, + _ => unreachable!(), + } + .map(|word| DynSolValue::FixedBytes(word, size)), + DynSolValue::CustomStruct { name, prop_names, tuple: mut values } => { + if let DynSolType::CustomStruct { name: _, prop_names: _, tuple: tuple_types } + | DynSolType::Tuple(tuple_types) = param + && !values.is_empty() + { + // Mutate random struct element. + mutate_random_tuple_value(&mut values, tuple_types, test_runner, state, senders); + Some(DynSolValue::CustomStruct { name, prop_names, tuple: values }) + } else { + None + } + } + DynSolValue::Tuple(mut values) => { + if let DynSolType::Tuple(tuple_types) = param + && !values.is_empty() + { + // Mutate random tuple element. + mutate_random_tuple_value(&mut values, tuple_types, test_runner, state, senders); + Some(DynSolValue::Tuple(values)) + } else { + None + } + } + _ => None, + } + .unwrap_or_else(|| new_value(param, test_runner)) +} + +/// Mutates random value from given tuples. +fn mutate_random_tuple_value( + tuple_values: &mut [DynSolValue], + tuple_types: &[DynSolType], + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, +) { + let id = test_runner.rng().random_range(0..tuple_values.len()); + let param_type = &tuple_types[id]; + let old_val = replace(&mut tuple_values[id], DynSolValue::Bool(false)); + let new_val = mutate_param_value_inner(param_type, old_val, test_runner, state, senders); + tuple_values[id] = new_val; +} + +/// Mutates random value from given array. +fn mutate_random_array_value( + array_values: &mut [DynSolValue], + element_type: &DynSolType, + test_runner: &mut TestRunner, + state: &EvmFuzzState, + senders: Option<&SenderFilters>, +) { + let elem = array_values.choose_mut(&mut test_runner.rng()).unwrap(); + let old_val = replace(elem, DynSolValue::Bool(false)); + let new_val = mutate_param_value_inner(element_type, old_val, test_runner, state, senders); + *elem = new_val; +} + #[cfg(test)] mod tests { use crate::{ - strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, FuzzFixtures, + strategies::{EvmFuzzState, fuzz_calldata, fuzz_calldata_from_state}, }; + use alloy_primitives::B256; use foundry_common::abi::get_func; - use foundry_config::FuzzDictionaryConfig; - use revm::database::{CacheDB, EmptyDB}; + use std::collections::HashSet; #[test] fn can_fuzz_array() { let f = "testArray(uint64[2] calldata values)"; let func = get_func(f).unwrap(); - let db = CacheDB::new(EmptyDB::default()); - let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]); + let state = EvmFuzzState::test(); let strategy = proptest::prop_oneof![ 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()), 40 => fuzz_calldata_from_state(func, &state), @@ -251,4 +535,209 @@ mod tests { let mut runner = proptest::test_runner::TestRunner::new(cfg); let _ = runner.run(&strategy, |_| Ok(())); } + + #[test] + fn can_fuzz_string_and_bytes_with_ast_literals_and_hashes() { + use super::fuzz_param_from_state; + use crate::strategies::LiteralMaps; + use alloy_dyn_abi::DynSolType; + use alloy_primitives::keccak256; + use proptest::strategy::Strategy; + + // Seed dict with string values and their hashes --> mimic `CheatcodeAnalysis` behavior. + let mut literals = LiteralMaps::default(); + literals.strings.insert("hello".to_string()); + literals.strings.insert("world".to_string()); + literals.words.entry(DynSolType::FixedBytes(32)).or_default().insert(keccak256("hello")); + literals.words.entry(DynSolType::FixedBytes(32)).or_default().insert(keccak256("world")); + + let state = EvmFuzzState::test(); + state.seed_literals(literals); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Verify strategies generates the seeded AST literals + let mut generated_bytes = HashSet::new(); + let mut generated_hashes = HashSet::new(); + let mut generated_strings = HashSet::new(); + let bytes_strategy = fuzz_param_from_state(&DynSolType::Bytes, &state); + let string_strategy = fuzz_param_from_state(&DynSolType::String, &state); + let bytes32_strategy = fuzz_param_from_state(&DynSolType::FixedBytes(32), &state); + + for _ in 0..256 { + let tree = bytes_strategy.new_tree(&mut runner).unwrap(); + if let Some(bytes) = tree.current().as_bytes() + && let Ok(s) = std::str::from_utf8(bytes) + { + generated_bytes.insert(s.to_string()); + } + + let tree = string_strategy.new_tree(&mut runner).unwrap(); + if let Some(s) = tree.current().as_str() { + generated_strings.insert(s.to_string()); + } + + let tree = bytes32_strategy.new_tree(&mut runner).unwrap(); + if let Some((bytes, size)) = tree.current().as_fixed_bytes() + && size == 32 + { + generated_hashes.insert(B256::from_slice(bytes)); + } + } + + assert!(generated_bytes.contains("hello")); + assert!(generated_bytes.contains("world")); + assert!(generated_strings.contains("hello")); + assert!(generated_strings.contains("world")); + assert!(generated_hashes.contains(&keccak256("hello"))); + assert!(generated_hashes.contains(&keccak256("world"))); + } + + #[test] + fn mutate_address_can_select_from_dictionary() { + use super::mutate_param_value; + use alloy_dyn_abi::{DynSolType, DynSolValue}; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary via state values. + let addr1 = Address::repeat_byte(0x11); + let addr2 = Address::repeat_byte(0x22); + let addr3 = Address::repeat_byte(0x33); + state.collect_values([addr1.into_word(), addr2.into_word(), addr3.into_word()]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Mutate an address many times and verify we can get addresses from the dictionary. + let original = Address::repeat_byte(0xff); + let mut got_addr1 = false; + let mut got_addr2 = false; + let mut got_addr3 = false; + + for _ in 0..1000 { + let mutated = mutate_param_value( + &DynSolType::Address, + DynSolValue::Address(original), + &mut runner, + &state, + ); + if let DynSolValue::Address(addr) = mutated { + if addr == addr1 { + got_addr1 = true; + } + if addr == addr2 { + got_addr2 = true; + } + if addr == addr3 { + got_addr3 = true; + } + } + if got_addr1 && got_addr2 && got_addr3 { + break; + } + } + + // We should have seen at least one dictionary address in 1000 iterations. + assert!( + got_addr1 || got_addr2 || got_addr3, + "Address mutation should select addresses from dictionary" + ); + } + + #[test] + fn mutate_address_prefers_targeted_senders() { + use super::select_random_address; + use crate::invariant::SenderFilters; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary (these should NOT be selected when targeted is set). + let dict_addr = Address::repeat_byte(0xdd); + state.collect_values([dict_addr.into_word()]); + + // Set up targeted senders. + let targeted1 = Address::repeat_byte(0x11); + let targeted2 = Address::repeat_byte(0x22); + let senders = SenderFilters::new(vec![targeted1, targeted2], vec![]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Call select_random_address directly to verify it uses targeted senders. + let original = Address::repeat_byte(0xff); + let mut got_targeted1 = false; + let mut got_targeted2 = false; + let mut got_dict = false; + + for _ in 0..100 { + if let Some(addr) = select_random_address(original, &mut runner, &state, Some(&senders)) + { + if addr == targeted1 { + got_targeted1 = true; + } + if addr == targeted2 { + got_targeted2 = true; + } + if addr == dict_addr { + got_dict = true; + } + } + } + + // Should see targeted addresses, never dictionary address. + assert!( + got_targeted1 || got_targeted2, + "select_random_address should select from targeted senders" + ); + assert!( + !got_dict, + "select_random_address should not select from dictionary when targeted senders are set" + ); + } + + #[test] + fn mutate_address_respects_excluded_senders() { + use super::select_random_address; + use crate::invariant::SenderFilters; + use alloy_primitives::Address; + + let state = EvmFuzzState::test(); + + // Add addresses to dictionary. + let addr1 = Address::repeat_byte(0x11); + let addr2 = Address::repeat_byte(0x22); + let excluded_addr = Address::repeat_byte(0xee); + state.collect_values([addr1.into_word(), addr2.into_word(), excluded_addr.into_word()]); + + // Exclude one address. + let senders = SenderFilters::new(vec![], vec![excluded_addr]); + + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + + // Call select_random_address directly to verify it respects excluded senders. + let original = Address::repeat_byte(0xff); + let mut got_excluded = false; + let mut got_valid = false; + + for _ in 0..100 { + if let Some(addr) = select_random_address(original, &mut runner, &state, Some(&senders)) + { + if addr == excluded_addr { + got_excluded = true; + break; + } + if addr == addr1 || addr == addr2 { + got_valid = true; + } + } + } + + assert!(!got_excluded, "select_random_address should not select excluded addresses"); + assert!(got_valid, "select_random_address should select valid (non-excluded) addresses"); + } } diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index 80c0c029865d2..a62106999bd8b 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -1,16 +1,20 @@ -use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts}; +use crate::{ + BasicTxDetails, invariant::FuzzRunIdentifiedContracts, strategies::literals::LiteralsDictionary, +}; use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt}; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{ - map::{AddressIndexSet, B256IndexSet, HashMap}, - Address, Bytes, Log, B256, U256, + Address, B256, Bytes, Log, U256, + map::{AddressIndexSet, AddressMap, B256IndexSet, HashMap, IndexSet}, +}; +use foundry_common::{ + ignore_metadata_hash, mapping_slots::MappingSlots, slot_identifier::SlotIdentifier, }; -use foundry_common::ignore_metadata_hash; +use foundry_compilers::artifacts::StorageLayout; use foundry_config::FuzzDictionaryConfig; -use foundry_evm_core::utils::StateChangeset; -use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; +use foundry_evm_core::{bytecode::InstIter, utils::StateChangeset}; +use parking_lot::{RawRwLock, RwLock, lock_api::RwLockReadGuard}; use revm::{ - bytecode::opcode, database::{CacheDB, DatabaseRef, DbAccount}, state::AccountInfo, }; @@ -30,13 +34,29 @@ pub struct EvmFuzzState { inner: Arc>, /// Addresses of external libraries deployed in test setup, excluded from fuzz test inputs. pub deployed_libs: Vec
, + /// Records mapping accesses. Used to identify storage slots belonging to mappings and sampling + /// the values in the [`FuzzDictionary`]. + /// + /// Only needed when [`StorageLayout`] is available. + pub(crate) mapping_slots: Option>, } impl EvmFuzzState { + #[cfg(test)] + pub(crate) fn test() -> Self { + Self::new( + &[], + &CacheDB::::default(), + FuzzDictionaryConfig::default(), + None, + ) + } + pub fn new( + deployed_libs: &[Address], db: &CacheDB, config: FuzzDictionaryConfig, - deployed_libs: &[Address], + literals: Option<&LiteralsDictionary>, ) -> Self { // Sort accounts to ensure deterministic dictionary generation from the same setUp state. let mut accs = db.cache.accounts.iter().collect::>(); @@ -45,7 +65,20 @@ impl EvmFuzzState { // Create fuzz dictionary and insert values from db state. let mut dictionary = FuzzDictionary::new(config); dictionary.insert_db_values(accs); - Self { inner: Arc::new(RwLock::new(dictionary)), deployed_libs: deployed_libs.to_vec() } + if let Some(literals) = literals { + dictionary.literal_values = literals.clone(); + } + + Self { + inner: Arc::new(RwLock::new(dictionary)), + deployed_libs: deployed_libs.to_vec(), + mapping_slots: None, + } + } + + pub fn with_mapping_slots(mut self, mapping_slots: AddressMap) -> Self { + self.mapping_slots = Some(mapping_slots); + self } pub fn collect_values(&self, values: impl IntoIterator) { @@ -72,8 +105,25 @@ impl EvmFuzzState { let (target_abi, target_function) = targets.fuzzed_artifacts(tx); dict.insert_logs_values(target_abi, logs, run_depth); dict.insert_result_values(target_function, result, run_depth); + // Get storage layouts for contracts in the state changeset + let storage_layouts = targets.get_storage_layouts(); + dict.insert_new_state_values( + state_changeset, + &storage_layouts, + self.mapping_slots.as_ref(), + ); + } + } + + /// Collects typed trace-cmp operands from sancov-instrumented code. + /// Values are inserted into both persistent state values (survive reverts) and typed + /// sample buckets (for ABI-aware mutation). + pub fn collect_typed_cmp_values(&self, values: impl IntoIterator) { + let mut dict = self.inner.write(); + for (width, value) in values { + dict.insert_persistent_value(value); + dict.insert_typed_cmp_value(width, value); } - dict.insert_new_state_values(state_changeset); } /// Removes all newly added entries from the dictionary. @@ -92,11 +142,19 @@ impl EvmFuzzState { pub fn log_stats(&self) { self.inner.read().log_stats(); } + + /// Test-only helper to seed the dictionary with literal values. + #[cfg(test)] + pub(crate) fn seed_literals(&self, map: super::LiteralMaps) { + self.inner.write().seed_literals(map); + } } // We're using `IndexSet` to have a stable element order when restoring persisted state, as well as // for performance when iterating over the sets. -#[derive(Default)] +/// Maximum number of persistent values from sancov trace-cmp. +const MAX_PERSISTENT_VALUES: usize = 2048; + pub struct FuzzDictionary { /// Collected state values. state_values: B256IndexSet, @@ -110,8 +168,18 @@ pub struct FuzzDictionary { /// Number of address values initially collected from db. /// Used to revert new collected addresses at the end of each run. db_addresses: usize, - /// Sample typed values that are collected from call result and used across invariant runs. + /// Typed runtime sample values persisted across invariant runs. + /// Initially seeded with literal values collected from the source code. sample_values: HashMap, + /// Lazily initialized dictionary of literal values collected from the source code. + literal_values: LiteralsDictionary, + /// Tracks whether literals from `literal_values` have been merged into `sample_values`. + /// + /// Set to `true` on first call to `seed_samples()`. Before seeding, `samples()` checks both + /// maps separately. After seeding, literals are merged in, so only `sample_values` is checked. + samples_seeded: bool, + /// Persistent values from sancov trace-cmp that survive `revert()` across runs. + persistent_values: B256IndexSet, misses: usize, hits: usize, @@ -122,13 +190,33 @@ impl fmt::Debug for FuzzDictionary { f.debug_struct("FuzzDictionary") .field("state_values", &self.state_values.len()) .field("addresses", &self.addresses) + .field("persistent_values", &self.persistent_values.len()) .finish() } } +impl Default for FuzzDictionary { + fn default() -> Self { + Self::new(Default::default()) + } +} + impl FuzzDictionary { pub fn new(config: FuzzDictionaryConfig) -> Self { - let mut dictionary = Self { config, ..Default::default() }; + let mut dictionary = Self { + config, + samples_seeded: false, + + state_values: Default::default(), + addresses: Default::default(), + db_state_values: Default::default(), + db_addresses: Default::default(), + sample_values: Default::default(), + literal_values: Default::default(), + persistent_values: Default::default(), + misses: Default::default(), + hits: Default::default(), + }; dictionary.prefill(); dictionary } @@ -138,6 +226,16 @@ impl FuzzDictionary { self.insert_value(B256::ZERO); } + /// Seeds `sample_values` with all words from the [`LiteralsDictionary`]. + /// Should only be called once per dictionary lifetime. + #[cold] + fn seed_samples(&mut self) { + trace!("seeding `sample_values` from literal dictionary"); + self.sample_values + .extend(self.literal_values.get().words.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.samples_seeded = true; + } + /// Insert values from initial db state into fuzz dictionary. /// These values are persisted across invariant runs. fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) { @@ -151,7 +249,7 @@ impl FuzzDictionary { // Sort storage values before inserting to ensure deterministic dictionary. let values = account.storage.iter().collect::>(); for (slot, value) in values { - self.insert_storage_value(slot, value); + self.insert_storage_value(slot, value, None, None); } } } @@ -176,12 +274,12 @@ impl FuzzDictionary { result: &Bytes, run_depth: u32, ) { - if let Some(function) = function { - if !function.outputs.is_empty() { - // Decode result and collect samples to be used in subsequent fuzz runs. - if let Ok(decoded_result) = function.abi_decode_output(result) { - self.insert_sample_values(decoded_result, run_depth); - } + if let Some(function) = function + && !function.outputs.is_empty() + { + // Decode result and collect samples to be used in subsequent fuzz runs. + if let Ok(decoded_result) = function.abi_decode_output(result) { + self.insert_sample_values(decoded_result, run_depth); } } } @@ -226,7 +324,12 @@ impl FuzzDictionary { /// Insert values from call state changeset into fuzz dictionary. /// These values are removed at the end of current run. - fn insert_new_state_values(&mut self, state_changeset: &StateChangeset) { + fn insert_new_state_values( + &mut self, + state_changeset: &StateChangeset, + storage_layouts: &HashMap>, + mapping_slots: Option<&AddressMap>, + ) { for (address, account) in state_changeset { // Insert basic account information. self.insert_value(address.into_word()); @@ -234,8 +337,20 @@ impl FuzzDictionary { self.insert_push_bytes_values(address, &account.info); // Insert storage values. if self.config.include_storage { + let slot_identifier = + storage_layouts.get(address).map(|layout| SlotIdentifier::new(layout.clone())); + trace!( + "{address:?} has mapping_slots {}", + mapping_slots.is_some_and(|m| m.contains_key(address)) + ); + let mapping_slots = mapping_slots.and_then(|m| m.get(address)); for (slot, value) in &account.storage { - self.insert_storage_value(slot, &value.present_value); + self.insert_storage_value( + slot, + &value.present_value, + slot_identifier.as_ref(), + mapping_slots, + ); } } } @@ -245,62 +360,59 @@ impl FuzzDictionary { /// Values are collected only once for a given address. /// If values are newly collected then they are removed at the end of current run. fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) { - if self.config.include_push_bytes && !self.addresses.contains(address) { - // Insert push bytes - if let Some(code) = &account_info.code { - self.insert_address(*address); + if self.config.include_push_bytes + && !self.addresses.contains(address) + && let Some(code) = &account_info.code + { + self.insert_address(*address); + if !self.values_full() { self.collect_push_bytes(ignore_metadata_hash(code.original_byte_slice())); } } } fn collect_push_bytes(&mut self, code: &[u8]) { - let mut i = 0; let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT); - while i < len { - let op = code[i]; - if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { - let push_size = (op - opcode::PUSH1 + 1) as usize; - let push_start = i + 1; - let push_end = push_start + push_size; - // As a precaution, if a fuzz test deploys malformed bytecode (such as using - // `CREATE2`) this will terminate the loop early. - if push_start > code.len() || push_end > code.len() { - break; - } - - let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap(); - if push_value != U256::ZERO { - // Never add 0 to the dictionary as it's always present. - self.insert_value(push_value.into()); - - // Also add the value below and above the push value to the dictionary. - self.insert_value((push_value - U256::from(1)).into()); - - if push_value != U256::MAX { - self.insert_value((push_value + U256::from(1)).into()); - } - } - - i += push_size; + let code = &code[..len]; + for inst in InstIter::new(code) { + // Don't add 0 to the dictionary as it's already present. + if !inst.immediate.is_empty() + && let Some(push_value) = U256::try_from_be_slice(inst.immediate) + && push_value != U256::ZERO + { + self.insert_value_u256(push_value); } - i += 1; } } /// Insert values from single storage slot and storage value into fuzz dictionary. - /// If storage values are newly collected then they are removed at the end of current run. - fn insert_storage_value(&mut self, storage_slot: &U256, storage_value: &U256) { - self.insert_value(B256::from(*storage_slot)); - self.insert_value(B256::from(*storage_value)); - // also add the value below and above the storage value to the dictionary. - if *storage_value != U256::ZERO { - let below_value = storage_value - U256::from(1); - self.insert_value(B256::from(below_value)); - } - if *storage_value != U256::MAX { - let above_value = storage_value + U256::from(1); - self.insert_value(B256::from(above_value)); + /// Uses [`SlotIdentifier`] to identify storage slots types. + fn insert_storage_value( + &mut self, + slot: &U256, + value: &U256, + slot_identifier: Option<&SlotIdentifier>, + mapping_slots: Option<&MappingSlots>, + ) { + let slot = B256::from(*slot); + let value = B256::from(*value); + + // Always insert the slot itself + self.insert_value(slot); + + // If we have a storage layout, use SlotIdentifier for better type identification. + if let Some(slot_identifier) = slot_identifier + // Identify slot type. + && let Some(slot_info) = slot_identifier.identify(&slot, mapping_slots) + && slot_info.decode(value).is_some() + { + trace!(?slot_info, "inserting typed storage value"); + if !self.samples_seeded { + self.seed_samples(); + } + self.sample_values.entry(slot_info.slot_type.dyn_sol_type).or_default().insert(value); + } else { + self.insert_value_u256(value.into()); } } @@ -313,13 +425,74 @@ impl FuzzDictionary { } /// Insert raw value into fuzz dictionary. + /// /// If value is newly collected then it is removed by index at the end of current run. - fn insert_value(&mut self, value: B256) { - if self.state_values.len() < self.config.max_fuzz_dictionary_values { + /// + /// Returns true if the value was inserted. + fn insert_value(&mut self, value: B256) -> bool { + let insert = !self.values_full(); + if insert { let new_value = self.state_values.insert(value); let counter = if new_value { &mut self.misses } else { &mut self.hits }; *counter += 1; } + insert + } + + /// Insert a persistent value that survives `revert()` across invariant runs. + /// Used for trace-cmp operands that should compound over time. + fn insert_persistent_value(&mut self, value: B256) { + if self.persistent_values.len() >= MAX_PERSISTENT_VALUES { + return; + } + if self.persistent_values.insert(value) && self.state_values.insert(value) { + self.db_state_values += 1; + } + } + + /// Insert a typed trace-cmp value into the `sample_values` map. + /// Maps sancov width to `DynSolType` buckets and promotes to larger types. + fn insert_typed_cmp_value(&mut self, width: u8, value: B256) { + if !self.samples_seeded { + self.seed_samples(); + } + + const MAX_TYPED_CMP_PER_BUCKET: usize = 1024; + + let native_type = match width { + 8 => DynSolType::Uint(8), + 16 => DynSolType::Uint(16), + 32 => DynSolType::Uint(32), + 64 => DynSolType::Uint(64), + _ => DynSolType::Uint(256), + }; + + let insert = |map: &mut HashMap, ty: DynSolType, val: B256| { + let bucket = map.entry(ty).or_default(); + if bucket.len() < MAX_TYPED_CMP_PER_BUCKET { + bucket.insert(val); + } + }; + + insert(&mut self.sample_values, native_type, value); + + if width <= 64 { + insert(&mut self.sample_values, DynSolType::Uint(128), value); + insert(&mut self.sample_values, DynSolType::Uint(256), value); + insert(&mut self.sample_values, DynSolType::Int(256), value); + } + } + + fn insert_value_u256(&mut self, value: U256) -> bool { + // Also add the value below and above the push value to the dictionary. + let one = U256::from(1); + self.insert_value(value.into()) + | self.insert_value((value.wrapping_sub(one)).into()) + | self.insert_value((value.wrapping_add(one)).into()) + } + + fn values_full(&self) -> bool { + self.state_values.len() >= self.config.max_fuzz_dictionary_values } /// Insert sample values that are reused across multiple runs. @@ -330,6 +503,9 @@ impl FuzzDictionary { sample_values: impl IntoIterator, limit: u32, ) { + if !self.samples_seeded { + self.seed_samples(); + } for sample in sample_values { if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) { if let Some(values) = self.sample_values.get_mut(&sample_type) { @@ -346,7 +522,7 @@ impl FuzzDictionary { } } - pub fn values(&self) -> &B256IndexSet { + pub const fn values(&self) -> &B256IndexSet { &self.state_values } @@ -358,13 +534,34 @@ impl FuzzDictionary { self.state_values.is_empty() } + /// Returns sample values for a given type, checking both runtime samples and literals. + /// + /// Before `seed_samples()` is called, checks both `literal_values` and `sample_values` + /// separately. After seeding, all literal values are merged into `sample_values`. #[inline] pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> { + // If not seeded yet, return literals + if !self.samples_seeded { + return self.literal_values.get().words.get(param_type); + } + self.sample_values.get(param_type) } + /// Returns the collected literal strings, triggering initialization if needed. #[inline] - pub fn addresses(&self) -> &AddressIndexSet { + pub fn ast_strings(&self) -> &IndexSet { + &self.literal_values.get().strings + } + + /// Returns the collected literal bytes (hex strings), triggering initialization if needed. + #[inline] + pub fn ast_bytes(&self) -> &IndexSet { + &self.literal_values.get().bytes + } + + #[inline] + pub const fn addresses(&self) -> &AddressIndexSet { &self.addresses } @@ -384,4 +581,10 @@ impl FuzzDictionary { "FuzzDictionary stats", ); } + + #[cfg(test)] + /// Test-only helper to seed the dictionary with literal values. + pub(crate) fn seed_literals(&mut self, map: super::LiteralMaps) { + self.literal_values.set(map); + } } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 4a9dfe955020c..ebf21ad0d1687 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -23,7 +23,7 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool) -> Self { + const fn new(start: U256, fixed: bool) -> Self { Self { lo: U256::ZERO, curr: start, hi: start, fixed } } @@ -49,7 +49,7 @@ impl ValueTree for UintValueTree { fn simplify(&mut self) -> bool { if self.fixed || (self.hi <= self.lo) { - return false + return false; } self.hi = self.curr; self.reposition() @@ -57,7 +57,7 @@ impl ValueTree for UintValueTree { fn complicate(&mut self) -> bool { if self.fixed || (self.hi <= self.lo) { - return false + return false; } self.lo = self.curr + U256::from(1); @@ -111,8 +111,8 @@ impl UintStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); // Choose if we want values around 0 or max - let is_min = rng.gen_bool(0.5); - let offset = U256::from(rng.gen_range(0..4)); + let is_min = rng.random::(); + let offset = U256::from(rng.random_range(0..4)); let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; Ok(UintValueTree::new(start, false)) } @@ -120,15 +120,15 @@ impl UintStrategy { fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_random_tree(runner) + return self.generate_random_tree(runner); } // Generate value tree from fixture. - let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; - if let Some(uint_fixture) = fixture.as_uint() { - if uint_fixture.1 == self.bits { - return Ok(UintValueTree::new(uint_fixture.0, false)); - } + let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())]; + if let Some(uint_fixture) = fixture.as_uint() + && uint_fixture.1 == self.bits + { + return Ok(UintValueTree::new(uint_fixture.0, false)); } // If fixture is not a valid type, raise error and generate random value. @@ -140,11 +140,11 @@ impl UintStrategy { let rng = runner.rng(); // generate random number of bits uniformly - let bits = rng.gen_range(0..=self.bits); + let bits = rng.random_range(0..=self.bits); // init 2 128-bit randoms - let mut higher: u128 = rng.gen_range(0..=u128::MAX); - let mut lower: u128 = rng.gen_range(0..=u128::MAX); + let mut higher: u128 = rng.random_range(0..=u128::MAX); + let mut lower: u128 = rng.random_range(0..=u128::MAX); // cut 2 randoms according to bits size match bits { @@ -158,10 +158,9 @@ impl UintStrategy { // init U256 from 2 randoms let mut inner: [u64; 4] = [0; 4]; - let mask64 = (1 << 65) - 1; - inner[0] = (lower & mask64) as u64; + inner[0] = lower as u64; inner[1] = (lower >> 64) as u64; - inner[2] = (higher & mask64) as u64; + inner[2] = higher as u64; inner[3] = (higher >> 64) as u64; let start: U256 = U256::from_limbs(inner); @@ -169,11 +168,7 @@ impl UintStrategy { } fn type_max(&self) -> U256 { - if self.bits < 256 { - (U256::from(1) << self.bits) - U256::from(1) - } else { - U256::MAX - } + if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { U256::MAX } } } @@ -182,7 +177,7 @@ impl Strategy for UintStrategy { type Value = U256; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; - let bias = runner.rng().gen_range(0..total_weight); + let bias = runner.rng().random_range(0..total_weight); // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), diff --git a/crates/evm/hardforks/Cargo.toml b/crates/evm/hardforks/Cargo.toml new file mode 100644 index 0000000000000..68f6fb23bab07 --- /dev/null +++ b/crates/evm/hardforks/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "foundry-evm-hardforks" +description = "EVM hardfork definitions for Foundry" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-chains.workspace = true +alloy-hardforks = { workspace = true, features = ["serde"] } +alloy-op-hardforks = { workspace = true, features = ["serde"], optional = true } +alloy-rpc-types.workspace = true +op-revm = { workspace = true, optional = true } +revm.workspace = true +serde = { workspace = true, features = ["derive"] } +tempo-chainspec.workspace = true +foundry-compilers.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "dep:op-revm"] diff --git a/crates/evm/hardforks/src/lib.rs b/crates/evm/hardforks/src/lib.rs new file mode 100644 index 0000000000000..a8e0d51738263 --- /dev/null +++ b/crates/evm/hardforks/src/lib.rs @@ -0,0 +1,415 @@ +//! EVM hardfork definitions for Foundry. +//! +//! Provides [`FoundryHardfork`], a unified enum over Ethereum, Optimism, and Tempo hardforks +//! with `FromStr`/`Serialize`/`Deserialize` support for CLI and config usage. + +use std::str::FromStr; + +use alloy_chains::Chain; +use alloy_rpc_types::BlockNumberOrTag; +use foundry_compilers::artifacts::EvmVersion; +#[cfg(feature = "optimism")] +use op_revm::OpSpecId; +use revm::primitives::hardfork::SpecId; +use serde::{Deserialize, Serialize}; + +pub use alloy_hardforks::EthereumHardfork; +#[cfg(feature = "optimism")] +pub use alloy_op_hardforks::OpHardfork; +pub use tempo_chainspec::hardfork::TempoHardfork; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(into = "String")] +pub enum FoundryHardfork { + Ethereum(EthereumHardfork), + #[cfg(feature = "optimism")] + Optimism(OpHardfork), + Tempo(TempoHardfork), +} + +impl From for String { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] + FoundryHardfork::Optimism(h) => format!("optimism:{h}"), + FoundryHardfork::Tempo(h) => format!("tempo:{h}"), + } + } +} + +impl<'de> Deserialize<'de> for FoundryHardfork { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl FromStr for FoundryHardfork { + type Err = String; + + fn from_str(s: &str) -> Result { + let raw = s.trim(); + + let Some((ns, fork_raw)) = raw.split_once(':') else { + return EthereumHardfork::from_str(raw) + .map(Self::Ethereum) + .map_err(|_| format!("unknown ethereum hardfork '{raw}'")); + }; + + let ns = ns.trim().to_ascii_lowercase(); + let fork = fork_raw.trim().to_ascii_lowercase().replace(['-', ' '], "_"); + + match ns.as_str() { + "eth" | "ethereum" => EthereumHardfork::from_str(&fork) + .map(Self::Ethereum) + .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")), + + #[cfg(feature = "optimism")] + "op" | "optimism" => OpHardfork::from_str(&fork) + .map(Self::Optimism) + .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")), + + "t" | "tempo" => TempoHardfork::from_str(&fork) + .map(Self::Tempo) + .map_err(|_| format!("unknown tempo hardfork '{fork_raw}'")), + _ => EthereumHardfork::from_str(&fork) + .map(Self::Ethereum) + .map_err(|_| format!("unknown hardfork '{raw}'")), + } + } +} + +impl FoundryHardfork { + pub const fn ethereum(h: EthereumHardfork) -> Self { + Self::Ethereum(h) + } + + #[cfg(feature = "optimism")] + pub const fn optimism(h: OpHardfork) -> Self { + Self::Optimism(h) + } + + pub const fn tempo(h: TempoHardfork) -> Self { + Self::Tempo(h) + } + + /// Returns the hardfork name without a network namespace prefix. + pub fn name(&self) -> String { + match self { + Self::Ethereum(h) => format!("{h}"), + #[cfg(feature = "optimism")] + Self::Optimism(h) => format!("{h}"), + Self::Tempo(h) => format!("{h}"), + } + } + + /// Returns the network namespace for this hardfork, or `None` for plain Ethereum. + /// + /// Mirrors the namespace prefix used in the `"network:hardfork"` serialization format. + pub const fn namespace(&self) -> Option<&'static str> { + match self { + Self::Ethereum(_) => None, + #[cfg(feature = "optimism")] + Self::Optimism(_) => Some("optimism"), + Self::Tempo(_) => Some("tempo"), + } + } + + /// Auto-detect the active hardfork for a given chain at a specific timestamp. + /// + /// Tries Ethereum, then Optimism. Returns `None` for unknown chains. + pub fn from_chain_and_timestamp(chain_id: u64, timestamp: u64) -> Option { + let chain = Chain::from_id(chain_id); + if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) { + return Some(Self::Ethereum(fork)); + } + #[cfg(feature = "optimism")] + if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) { + return Some(Self::Optimism(fork)); + } + // TODO: add tempo support after https://github.com/tempoxyz/tempo/pull/3514 release + // providing TempoHardfork::from_chain_and_timestamp + None + } +} + +impl From for FoundryHardfork { + fn from(value: EthereumHardfork) -> Self { + Self::Ethereum(value) + } +} + +impl From for EthereumHardfork { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Ethereum(hardfork) => hardfork, + _ => Self::default(), + } + } +} + +#[cfg(feature = "optimism")] +impl From for FoundryHardfork { + fn from(value: OpHardfork) -> Self { + Self::Optimism(value) + } +} + +#[cfg(feature = "optimism")] +impl From for OpHardfork { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Optimism(hardfork) => hardfork, + _ => Self::default(), + } + } +} + +impl From for FoundryHardfork { + fn from(value: TempoHardfork) -> Self { + Self::Tempo(value) + } +} + +impl From for TempoHardfork { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Tempo(hardfork) => hardfork, + _ => Self::default(), + } + } +} + +impl From for SpecId { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + #[cfg(feature = "optimism")] + FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), + FoundryHardfork::Tempo(hardfork) => hardfork.into(), + } + } +} + +#[cfg(feature = "optimism")] +impl From for OpSpecId { + fn from(fork: FoundryHardfork) -> Self { + match fork { + FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork), + _ => Self::default(), + } + } +} + +/// Map an `EthereumHardfork` enum into its corresponding `SpecId`. +pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { + match hardfork { + EthereumHardfork::Frontier => SpecId::FRONTIER, + EthereumHardfork::Homestead => SpecId::HOMESTEAD, + EthereumHardfork::Dao => SpecId::DAO_FORK, + EthereumHardfork::Tangerine => SpecId::TANGERINE, + EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, + EthereumHardfork::Byzantium => SpecId::BYZANTIUM, + EthereumHardfork::Constantinople => SpecId::CONSTANTINOPLE, + EthereumHardfork::Petersburg => SpecId::PETERSBURG, + EthereumHardfork::Istanbul => SpecId::ISTANBUL, + EthereumHardfork::MuirGlacier => SpecId::MUIR_GLACIER, + EthereumHardfork::Berlin => SpecId::BERLIN, + EthereumHardfork::London => SpecId::LONDON, + EthereumHardfork::ArrowGlacier => SpecId::ARROW_GLACIER, + EthereumHardfork::GrayGlacier => SpecId::GRAY_GLACIER, + EthereumHardfork::Paris => SpecId::MERGE, + EthereumHardfork::Shanghai => SpecId::SHANGHAI, + EthereumHardfork::Cancun => SpecId::CANCUN, + EthereumHardfork::Prague => SpecId::PRAGUE, + EthereumHardfork::Osaka => SpecId::OSAKA, + EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2 => SpecId::OSAKA, + EthereumHardfork::Bpo3 | EthereumHardfork::Bpo4 | EthereumHardfork::Bpo5 => { + unimplemented!() + } + f => unreachable!("unimplemented {}", f), + } +} + +/// Map an `OptimismHardfork` enum into its corresponding `OpSpecId`. +#[cfg(feature = "optimism")] +pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { + match hardfork { + OpHardfork::Bedrock => OpSpecId::BEDROCK, + OpHardfork::Regolith => OpSpecId::REGOLITH, + OpHardfork::Canyon => OpSpecId::CANYON, + OpHardfork::Ecotone => OpSpecId::ECOTONE, + OpHardfork::Fjord => OpSpecId::FJORD, + OpHardfork::Granite => OpSpecId::GRANITE, + OpHardfork::Holocene => OpSpecId::HOLOCENE, + OpHardfork::Isthmus => OpSpecId::ISTHMUS, + OpHardfork::Interop => OpSpecId::INTEROP, + OpHardfork::Jovian => OpSpecId::JOVIAN, + f => unreachable!("unimplemented {}", f), + } +} + +/// Trait for converting an [`EvmVersion`] into a network-specific spec type. +pub trait FromEvmVersion: From { + fn from_evm_version(version: EvmVersion) -> Self; +} + +impl FromEvmVersion for SpecId { + fn from_evm_version(version: EvmVersion) -> Self { + match version { + EvmVersion::Homestead => Self::HOMESTEAD, + EvmVersion::TangerineWhistle => Self::TANGERINE, + EvmVersion::SpuriousDragon => Self::SPURIOUS_DRAGON, + EvmVersion::Byzantium => Self::BYZANTIUM, + EvmVersion::Constantinople => Self::CONSTANTINOPLE, + EvmVersion::Petersburg => Self::PETERSBURG, + EvmVersion::Istanbul => Self::ISTANBUL, + EvmVersion::Berlin => Self::BERLIN, + EvmVersion::London => Self::LONDON, + EvmVersion::Paris => Self::MERGE, + EvmVersion::Shanghai => Self::SHANGHAI, + EvmVersion::Cancun => Self::CANCUN, + EvmVersion::Prague => Self::PRAGUE, + EvmVersion::Osaka => Self::OSAKA, + } + } +} + +#[cfg(feature = "optimism")] +impl FromEvmVersion for OpSpecId { + fn from_evm_version(version: EvmVersion) -> Self { + match version { + EvmVersion::Homestead + | EvmVersion::TangerineWhistle + | EvmVersion::SpuriousDragon + | EvmVersion::Byzantium + | EvmVersion::Constantinople + | EvmVersion::Petersburg + | EvmVersion::Istanbul + | EvmVersion::Berlin + | EvmVersion::London + | EvmVersion::Paris => Self::BEDROCK, + EvmVersion::Shanghai => Self::CANYON, + EvmVersion::Cancun => Self::ECOTONE, + EvmVersion::Prague => Self::ISTHMUS, + EvmVersion::Osaka => Self::JOVIAN, + } + } +} + +impl FromEvmVersion for TempoHardfork { + fn from_evm_version(_: EvmVersion) -> Self { + Self::default() + } +} + +/// Returns the spec id derived from [`EvmVersion`] for a given spec type. +pub fn evm_spec_id(evm_version: EvmVersion) -> SPEC { + SPEC::from_evm_version(evm_version) +} + +/// Convert a `BlockNumberOrTag` into an `EthereumHardfork`. +pub fn ethereum_hardfork_from_block_tag(block: impl Into) -> EthereumHardfork { + let num = match block.into() { + BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Number(num) => num, + _ => u64::MAX, + }; + + EthereumHardfork::from_mainnet_block_number(num) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_hardforks::ethereum::mainnet::*; + + #[test] + fn test_ethereum_spec_id_mapping() { + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD); + + // Test latest hardforks + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA); + } + + #[test] + fn test_tempo_spec_id_mapping() { + assert_eq!(SpecId::from(TempoHardfork::Genesis), SpecId::OSAKA); + } + + #[test] + fn test_hardfork_from_block_tag_numbers() { + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1), + EthereumHardfork::Frontier + ); + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1), + EthereumHardfork::London + ); + } + + #[test] + fn test_from_chain_and_timestamp_ethereum_mainnet() { + assert_eq!( + FoundryHardfork::from_chain_and_timestamp(1, 0), + Some(FoundryHardfork::Ethereum(EthereumHardfork::Frontier)) + ); + // Shanghai activated at timestamp 1681338455 on mainnet + assert_eq!( + FoundryHardfork::from_chain_and_timestamp(1, 1_681_338_455), + Some(FoundryHardfork::Ethereum(EthereumHardfork::Shanghai)) + ); + } + + #[test] + fn test_from_chain_and_timestamp_sepolia() { + let sepolia_chain_id = 11155111; + assert!(FoundryHardfork::from_chain_and_timestamp(sepolia_chain_id, u64::MAX).is_some()); + } + + #[test] + fn test_from_chain_and_timestamp_unknown_chain() { + assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + + #[test] + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); + + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } + + #[test] + fn test_from_chain_and_timestamp_op_mainnet() { + let op_chain_id = 10; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + + #[test] + fn test_from_chain_and_timestamp_base() { + let base_chain_id = 8453; + assert!(matches!( + FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX), + Some(FoundryHardfork::Optimism(_)) + )); + } + } +} diff --git a/crates/evm/networks/Cargo.toml b/crates/evm/networks/Cargo.toml new file mode 100644 index 0000000000000..00c9abf0f90f7 --- /dev/null +++ b/crates/evm/networks/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "foundry-evm-networks" +description = "Custom network features, like precompiles and custom tx types" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-evm-hardforks.workspace = true + +alloy-chains.workspace = true +alloy-eips.workspace = true +alloy-evm.workspace = true +alloy-op-hardforks = { workspace = true, optional = true } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } + +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "optional_eip3607", + "optional_block_gas_limit", + "optional_no_base_fee", + "arbitrary", + "c-kzg", + "blst", +] } + +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +serde.workspace = true + +[dev-dependencies] +serde_json.workspace = true + +[features] +default = ["optimism"] +optimism = ["dep:alloy-op-hardforks", "foundry-evm-hardforks/optimism"] diff --git a/crates/evm/networks/README.md b/crates/evm/networks/README.md new file mode 100644 index 0000000000000..00a6a35a1dc2e --- /dev/null +++ b/crates/evm/networks/README.md @@ -0,0 +1,58 @@ +# Custom EVM Networks + +The evm-networks crate defines custom network features that are shared across Foundry's tooling (`anvil`, `forge` and +`cast`). Currently, it supports custom precompiles, with planned support for custom transaction types. + +## Adding a Custom Network +To add configuration support for a custom network (e.g. `my_network`), add a new field to the `NetworkConfigs` struct: + +```rust + /// Enable my custom network features. + #[arg(help_heading = "Networks", long)] + #[serde(default)] + pub my_network: bool, +``` + +This automatically enables: +- `my_network = true` in foundry.toml +- `--my-network` anvil CLI flag +``` +Networks: + --my-network + Enable my custom network features + +``` + +If you'd like network features to be enabled automatically based on the chain ID, update `NetworkConfigs::with_chain_id`: + +```rust +impl NetworkConfigs { + pub fn with_chain_id(chain_id: u64) -> Self { + // Enable custom network features here + } +} +``` + +## Adding a custom precompile + +- Create a module for your network-specific logic, e.g., `my_network/transfer`. +- Implement the precompile logic as a function that accepts a `PrecompileInput` containing execution context and hooks for +interacting with EVM state, and returns a `PrecompileResult`: + +```rust +pub fn custom_precompile( + input: alloy_evm::precompiles::PrecompileInput<'_> +) -> revm::precompile::PrecompileResult { + // Your logic here +} +``` + +- Enable the precompile in the `NetworkConfigs` implementation by conditionally applying it to an address: + +```rust +if self.my_network { + precompiles.apply_precompile(&MY_NETWORK_TRANSFER_ADDRESS, move |_| { + Some(my_network::transfer::custom_precompile()) + }); +} +``` \ No newline at end of file diff --git a/crates/evm/networks/src/celo/mod.rs b/crates/evm/networks/src/celo/mod.rs new file mode 100644 index 0000000000000..014e52f2771e2 --- /dev/null +++ b/crates/evm/networks/src/celo/mod.rs @@ -0,0 +1 @@ +pub mod transfer; diff --git a/crates/evm/networks/src/celo/transfer.rs b/crates/evm/networks/src/celo/transfer.rs new file mode 100644 index 0000000000000..2455f9f36f98f --- /dev/null +++ b/crates/evm/networks/src/celo/transfer.rs @@ -0,0 +1,124 @@ +//! Celo precompile implementation for token transfers. +//! +//! This module implements the Celo transfer precompile that enables native token transfers from an +//! EVM contract. The precompile is part of Celo's token duality system, allowing transfer of +//! native tokens via ERC20. +//! +//! For more details, see: +//! +//! The transfer precompile is deployed at address 0xfd and accepts 96 bytes of input: +//! - from address (32 bytes, left-padded) +//! - to address (32 bytes, left-padded) +//! - value (32 bytes, big-endian U256) + +use std::borrow::Cow; + +use alloy_evm::precompiles::{DynPrecompile, PrecompileInput}; +use alloy_primitives::{Address, U256, address}; +use revm::precompile::{ + PrecompileError, PrecompileHalt, PrecompileId, PrecompileOutput, PrecompileResult, +}; + +/// Label of the Celo transfer precompile to display in traces. +pub const CELO_TRANSFER_LABEL: &str = "CELO_TRANSFER_PRECOMPILE"; + +/// Address of the Celo transfer precompile. +pub const CELO_TRANSFER_ADDRESS: Address = address!("0x00000000000000000000000000000000000000fd"); + +/// ID for the [Celo transfer precompile](CELO_TRANSFER_ADDRESS). +pub static PRECOMPILE_ID_CELO_TRANSFER: PrecompileId = + PrecompileId::Custom(Cow::Borrowed("celo transfer")); + +/// Gas cost for Celo transfer precompile. +const CELO_TRANSFER_GAS_COST: u64 = 9000; + +/// Returns the Celo native transfer. +pub fn precompile() -> DynPrecompile { + DynPrecompile::new_stateful(PRECOMPILE_ID_CELO_TRANSFER.clone(), celo_transfer_precompile) +} + +/// Celo transfer precompile implementation. +/// +/// Uses load_account to modify balances directly, making it compatible with PrecompilesMap. +pub fn celo_transfer_precompile(mut input: PrecompileInput<'_>) -> PrecompileResult { + // Check minimum gas requirement + if input.gas < CELO_TRANSFER_GAS_COST { + return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, input.reservoir)); + } + + // Validate input length (must be exactly 96 bytes: 32 + 32 + 32) + if input.data.len() != 96 { + return Ok(PrecompileOutput::halt( + PrecompileHalt::Other( + format!( + "Invalid input length for Celo transfer precompile: expected 96 bytes, got {}", + input.data.len() + ) + .into(), + ), + input.reservoir, + )); + } + + // Parse input: from (bytes 12-32), to (bytes 44-64), value (bytes 64-96) + let from_bytes = &input.data[12..32]; + let to_bytes = &input.data[44..64]; + let value_bytes = &input.data[64..96]; + + let from_address = Address::from_slice(from_bytes); + let to_address = Address::from_slice(to_bytes); + let value = U256::from_be_slice(value_bytes); + + // Perform the transfer using load_account to modify balances directly + let internals = input.internals_mut(); + + // Load and check the from account balance first + + let from_account = match internals.load_account(from_address) { + Ok(account) => account, + Err(e) => { + return Ok(PrecompileOutput::halt( + PrecompileHalt::Other(format!("Failed to load sender account: {e:?}").into()), + input.reservoir, + )); + } + }; + + // Check if from account has sufficient balance + if from_account.data.info.balance < value { + return Ok(PrecompileOutput::halt( + PrecompileHalt::Other("Insufficient balance".into()), + input.reservoir, + )); + } + + let to_account = match internals.load_account(to_address) { + Ok(account) => account, + Err(e) => { + return Ok(PrecompileOutput::halt( + PrecompileHalt::Other(format!("Failed to load recipient account: {e:?}").into()), + input.reservoir, + )); + } + }; + + // Check for overflow in to account + if to_account.data.info.balance.checked_add(value).is_none() { + return Ok(PrecompileOutput::halt( + PrecompileHalt::Other("Balance overflow in to account".into()), + input.reservoir, + )); + } + + // Transfer the value between accounts + internals + .transfer(from_address, to_address, value) + .map_err(|e| PrecompileError::Fatal(format!("Failed to perform transfer: {e:?}")))?; + + // No output data for successful transfer + Ok(PrecompileOutput::new( + CELO_TRANSFER_GAS_COST, + alloy_primitives::Bytes::new(), + input.reservoir, + )) +} diff --git a/crates/evm/networks/src/lib.rs b/crates/evm/networks/src/lib.rs new file mode 100644 index 0000000000000..384cee5a7bed3 --- /dev/null +++ b/crates/evm/networks/src/lib.rs @@ -0,0 +1,404 @@ +//! # foundry-evm-networks +//! +//! Foundry EVM network configuration. + +use crate::celo::transfer::{ + CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL, PRECOMPILE_ID_CELO_TRANSFER, +}; +use alloy_chains::{ + Chain, NamedChain, + NamedChain::{Chiado, Gnosis, Moonbase, Moonbeam, MoonbeamDev, Moonriver, Rsk, RskTestnet}, +}; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_evm::precompiles::PrecompilesMap; +use alloy_primitives::{Address, ChainId, map::AddressHashMap}; +use clap::Parser; +use foundry_evm_hardforks::FoundryHardfork; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +pub mod celo; + +#[cfg(feature = "optimism")] +mod optimism; + +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + clap::ValueEnum, +)] +#[serde(rename_all = "lowercase")] +#[clap(rename_all = "lowercase")] +pub enum NetworkVariant { + #[default] + Ethereum, + #[cfg(feature = "optimism")] + Optimism, + Tempo, +} + +impl std::str::FromStr for NetworkVariant { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ethereum" => Ok(Self::Ethereum), + #[cfg(feature = "optimism")] + "optimism" => Ok(Self::Optimism), + "tempo" => Ok(Self::Tempo), + _ => Err(format!("unknown network variant: {s}")), + } + } +} + +impl NetworkVariant { + pub const fn name(&self) -> &'static str { + match self { + Self::Ethereum => "ethereum", + #[cfg(feature = "optimism")] + Self::Optimism => "optimism", + Self::Tempo => "tempo", + } + } +} + +impl std::fmt::Display for NetworkVariant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.name()) + } +} + +impl From for NetworkVariant { + fn from(chain_id: ChainId) -> Self { + let chain = Chain::from_id(chain_id); + if chain.is_tempo() { + return Self::Tempo; + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::Optimism; + } + Self::Ethereum + } +} + +#[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq)] +pub struct NetworkConfigs { + /// Enable a specific network family. + #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] + #[serde(default)] + pub(crate) network: Option, + /// Enable Celo network features. + #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] + celo: bool, + /// Enable Optimism network features (deprecated: use --network optimism). + #[cfg(feature = "optimism")] + #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])] + // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the + // canonical form is `network = "optimism"`. + #[serde(default)] + pub(crate) optimism: bool, + /// Enable Tempo network features (deprecated: use --network tempo). + #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])] + #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))] + // Deserialize-only legacy alias: accepted in foundry.toml but never serialized — the + // canonical form is `network = "tempo"`. + #[serde(default)] + tempo: bool, + /// Whether to bypass prevrandao. + #[arg(skip)] + #[serde(default)] + bypass_prevrandao: bool, +} + +// Custom `Serialize` impl: always emits the *resolved* network as the canonical +// `network = "..."` field, and never emits the legacy `tempo` / `optimism` aliases. This avoids +// confusing output like `network = "tempo"` next to `tempo = false`, and ensures `tempo = true` +// in foundry.toml round-trips as `network = "tempo"`. +impl Serialize for NetworkConfigs { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeStruct; + let mut s = serializer.serialize_struct("NetworkConfigs", 3)?; + s.serialize_field("network", &self.resolved_network())?; + s.serialize_field("celo", &self.celo)?; + s.serialize_field("bypass_prevrandao", &self.bypass_prevrandao)?; + s.end() + } +} + +impl NetworkConfigs { + pub fn with_celo() -> Self { + Self { celo: true, ..Default::default() } + } + + pub fn with_tempo() -> Self { + Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() } + } + + pub const fn is_tempo(&self) -> bool { + matches!(self.resolved_network(), Some(NetworkVariant::Tempo)) + } + + pub const fn is_celo(&self) -> bool { + self.celo + } + + /// Returns the resolved network variant, folding legacy flags. + const fn resolved_network(&self) -> Option { + if let Some(n) = self.network { + return Some(n); + } + #[cfg(feature = "optimism")] + if self.optimism { + return Some(NetworkVariant::Optimism); + } + if self.tempo { + return Some(NetworkVariant::Tempo); + } + None + } + + /// Returns the name of the currently active non-Ethereum network, or `None` for plain Ethereum. + pub fn active_network_name(&self) -> Option<&'static str> { + self.resolved_network().and_then(|n| match n { + NetworkVariant::Ethereum => None, + _ => Some(n.name()), + }) + } + + /// Returns the base fee parameters for the configured network. + /// + /// For Optimism networks, returns Canyon parameters if the Canyon hardfork is active + /// at the given timestamp, otherwise returns pre-Canyon parameters. + pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + #[cfg(feature = "optimism")] + if self.is_optimism() { + return self.op_base_fee_params(timestamp); + } + let _ = timestamp; + BaseFeeParams::ethereum() + } + + pub fn bypass_prevrandao(&self, chain_id: u64) -> bool { + if let Ok( + Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet | Gnosis | Chiado, + ) = NamedChain::try_from(chain_id) + { + return true; + } + self.bypass_prevrandao + } + + pub fn with_chain_id(self, chain_id: u64) -> Self { + let chain = Chain::from_id(chain_id); + if self.resolved_network().is_some() { + return if !self.celo + && matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + { + Self::with_celo() + } else { + self + }; + } + if chain.is_tempo() { + return Self::with_tempo(); + } + #[cfg(feature = "optimism")] + if chain.is_optimism() { + return Self::with_optimism(); + } + self + } + + /// Validates `hardfork` against the current `NetworkConfigs` and, if consistent, returns an + /// updated instance with the network implied by the enabled hardfork. + /// + /// Returns `Err` when the hardfork's network family conflicts with the configured one. + pub fn normalize_for_hardfork(self, hardfork: FoundryHardfork) -> Result { + if let Some(configured) = + self.active_network_name().filter(|&n| Some(n) != hardfork.namespace()) + { + return Err(format!( + "hardfork `{}` conflicts with network config `{configured}`", + String::from(hardfork), + )); + } + + let network = match hardfork { + FoundryHardfork::Ethereum(_) => self, + FoundryHardfork::Tempo(_) => Self::with_tempo(), + #[cfg(feature = "optimism")] + FoundryHardfork::Optimism(_) => Self::with_optimism(), + }; + + Ok(network) + } + + /// Inject precompiles for configured networks. + pub fn inject_precompiles(self, precompiles: &mut PrecompilesMap) { + if self.celo { + precompiles.apply_precompile(&CELO_TRANSFER_ADDRESS, move |_| { + Some(celo::transfer::precompile()) + }); + } + } + + /// Returns precompiles label for configured networks, to be used in traces. + pub fn precompiles_label(self) -> AddressHashMap { + let mut labels = AddressHashMap::default(); + if self.celo { + labels.insert(CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL.to_string()); + } + labels + } + + /// Returns precompiles for configured networks. + pub fn precompiles(self) -> BTreeMap { + let mut precompiles = BTreeMap::new(); + if self.celo { + precompiles + .insert(PRECOMPILE_ID_CELO_TRANSFER.name().to_string(), CELO_TRANSFER_ADDRESS); + } + precompiles + } +} + +impl From for NetworkConfigs { + fn from(network: NetworkVariant) -> Self { + match network { + NetworkVariant::Ethereum => Self::default(), + NetworkVariant::Tempo => { + Self { network: Some(network), tempo: true, ..Default::default() } + } + #[cfg(feature = "optimism")] + NetworkVariant::Optimism => { + Self { network: Some(network), optimism: true, ..Default::default() } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // --- Equivalence: new flag == legacy flag --- + + #[test] + fn new_tempo_flag_equivalent_to_legacy() { + let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() }; + let via_old = NetworkConfigs { tempo: true, ..Default::default() }; + assert_eq!(via_new.is_tempo(), via_old.is_tempo()); + assert_eq!(via_new.active_network_name(), via_old.active_network_name()); + } + + // --- resolved() / active_network_name --- + + #[test] + fn active_network_name_tempo() { + let cfg = NetworkConfigs::with_tempo(); + assert_eq!(cfg.active_network_name(), Some("tempo")); + } + + #[test] + fn active_network_name_default_is_none() { + assert_eq!(NetworkConfigs::default().active_network_name(), None); + } + + // --- Serde round-trip --- + + #[test] + fn serde_roundtrip_tempo() { + let original = NetworkConfigs::with_tempo(); + let json = serde_json::to_string(&original).unwrap(); + let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); + assert!(restored.is_tempo()); + } + + #[test] + fn serde_legacy_tempo_bool_deserialized() { + // Old foundry.toml format: `tempo = true` + let json = r#"{"tempo": true, "celo": false, "bypass_prevrandao": false}"#; + let cfg: NetworkConfigs = serde_json::from_str(json).unwrap(); + assert!(cfg.is_tempo()); + } + + #[test] + fn serde_serializes_legacy_alias_as_canonical_network() { + // Legacy `tempo = true` should serialize as the canonical `network = "tempo"`, + // and the legacy `tempo` / `optimism` keys must not appear in the output. + let cfg = NetworkConfigs { tempo: true, ..Default::default() }; + let json = serde_json::to_value(cfg).unwrap(); + assert_eq!(json["network"], serde_json::json!("tempo")); + assert!(json.get("tempo").is_none(), "legacy `tempo` key should not be serialized"); + assert!(json.get("optimism").is_none(), "legacy `optimism` key should not be serialized"); + } + + #[test] + fn serde_new_network_field_deserialized() { + let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#; + let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap(); + assert!(cfg_tempo.is_tempo()); + } + + #[cfg(feature = "optimism")] + mod optimism { + use super::*; + + #[test] + fn new_optimism_flag_equivalent_to_legacy() { + let via_new = + NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() }; + let via_old = NetworkConfigs { optimism: true, ..Default::default() }; + assert_eq!(via_new.is_optimism(), via_old.is_optimism()); + assert_eq!(via_new.is_tempo(), via_old.is_tempo()); + assert_eq!(via_new.active_network_name(), via_old.active_network_name()); + } + + #[test] + fn active_network_name_optimism() { + let cfg = NetworkConfigs::with_optimism(); + assert_eq!(cfg.active_network_name(), Some("optimism")); + } + + #[test] + fn new_flag_wins_over_legacy_when_both_set() { + // --network optimism --tempo: network field wins + let cfg = NetworkConfigs { + network: Some(NetworkVariant::Optimism), + tempo: true, + ..Default::default() + }; + assert!(cfg.is_optimism()); + assert!(!cfg.is_tempo()); + } + + #[test] + fn serde_roundtrip_optimism() { + let original = NetworkConfigs::with_optimism(); + let json = serde_json::to_string(&original).unwrap(); + let restored: NetworkConfigs = serde_json::from_str(&json).unwrap(); + assert!(restored.is_optimism()); + assert!(!restored.is_tempo()); + } + + #[test] + fn serde_optimism_field_deserialized() { + let json_optimism = + r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#; + let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap(); + assert!(cfg_optimism.is_optimism()); + } + } +} diff --git a/crates/evm/networks/src/optimism.rs b/crates/evm/networks/src/optimism.rs new file mode 100644 index 0000000000000..5fffa38a333c7 --- /dev/null +++ b/crates/evm/networks/src/optimism.rs @@ -0,0 +1,25 @@ +//! Optimism-specific extensions for [`NetworkConfigs`] and related helpers. + +use crate::{NetworkConfigs, NetworkVariant}; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; + +impl NetworkConfigs { + pub fn with_optimism() -> Self { + Self { network: Some(NetworkVariant::Optimism), optimism: true, ..Default::default() } + } + + pub const fn is_optimism(&self) -> bool { + matches!(self.resolved_network(), Some(NetworkVariant::Optimism)) + } + + /// Optimism-specific base fee parameters, picking Canyon vs pre-Canyon based on `timestamp`. + pub(crate) fn op_base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + let op_hardforks = OpChainHardforks::op_mainnet(); + if op_hardforks.is_canyon_active_at_timestamp(timestamp) { + BaseFeeParams::optimism_canyon() + } else { + BaseFeeParams::optimism() + } + } +} diff --git a/crates/evm/sancov/Cargo.toml b/crates/evm/sancov/Cargo.toml new file mode 100644 index 0000000000000..4d65a79b6429f --- /dev/null +++ b/crates/evm/sancov/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "foundry-evm-sancov" +description = "SanitizerCoverage callbacks for coverage-guided fuzzing of native Rust code" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true diff --git a/crates/evm/sancov/README.md b/crates/evm/sancov/README.md new file mode 100644 index 0000000000000..6105fba807da1 --- /dev/null +++ b/crates/evm/sancov/README.md @@ -0,0 +1,48 @@ +# foundry-evm-sancov + +SanitizerCoverage callbacks for coverage-guided fuzzing of native Rust code (precompiles, revm internals, etc.). + +When forge is built with a `RUSTC_WRAPPER` that injects sancov flags for target crates, the fuzzer uses native code edge coverage to guide mutation. Comparison operands from instrumented code are also captured and injected into the fuzz dictionary. + +## Config + +```toml +[invariant] +sancov_edges = true +sancov_trace_cmp = true +corpus_dir = "corpus/invariant" +``` + +When `sancov_edges` is enabled, the EVM `EdgeCovInspector` is automatically disabled — sancov replaces EVM bytecode coverage as the guidance signal. + +## Build + +Create a `RUSTC_WRAPPER` that injects sancov flags for the crate(s) you want to instrument: + +```bash +#!/usr/bin/env bash +RUSTC="$1"; shift +CRATE_NAME="" +PREV="" +for arg in "$@"; do + [ "$PREV" = "--crate-name" ] && CRATE_NAME="$arg" && break + PREV="$arg" +done + +if [ "$CRATE_NAME" = "your_target_crate" ]; then + exec "$RUSTC" "$@" \ + -Cpasses=sancov-module \ + -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-pc-guard \ + -Cllvm-args=-sanitizer-coverage-trace-compares +else + exec "$RUSTC" "$@" +fi +``` + +Then build: + +```bash +RUSTC_WRAPPER=./sancov-wrapper.sh cargo build --profile fuzz --bin forge +``` + diff --git a/crates/evm/sancov/src/lib.rs b/crates/evm/sancov/src/lib.rs new file mode 100644 index 0000000000000..b192cf8f5d2bb --- /dev/null +++ b/crates/evm/sancov/src/lib.rs @@ -0,0 +1,258 @@ +//! SanitizerCoverage callbacks for coverage-guided fuzzing of native Rust code. +//! +//! Provides LLVM SanitizerCoverage callbacks and a coverage map that can be set +//! by the fuzzing executor to collect edge coverage from instrumented Rust +//! crates (e.g. precompile implementations compiled with `-Cpasses=sancov-module`). +//! +//! Additionally provides trace-cmp callbacks that capture comparison operands +//! and surface them to the fuzzer's dictionary, enabling it to solve comparison +//! guards (balance checks, overflow guards, etc.). +//! +//! Only crates compiled with sancov instrumentation (via a `RUSTC_WRAPPER`) +//! will trigger these callbacks — no runtime filtering needed. + +use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering}; + +static COVERAGE_MAP_PTR: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +static COVERAGE_MAP_LEN: AtomicUsize = AtomicUsize::new(0); + +/// Point the coverage map at the given buffer. Subsequent `__sanitizer_cov_trace_pc_guard` +/// calls will record hits into this buffer. +pub fn set_coverage_map(ptr: *mut u8, len: usize) { + COVERAGE_MAP_PTR.store(ptr, Ordering::Release); + COVERAGE_MAP_LEN.store(len, Ordering::Release); +} + +/// Deactivate the coverage map. +pub fn clear_coverage_map() { + COVERAGE_MAP_PTR.store(std::ptr::null_mut(), Ordering::Release); + COVERAGE_MAP_LEN.store(0, Ordering::Release); +} + +/// Whether a coverage map is currently active. +pub fn is_active() -> bool { + !COVERAGE_MAP_PTR.load(Ordering::Relaxed).is_null() +} + +static NEXT_SANCOV_IDX: AtomicUsize = AtomicUsize::new(0); + +static GUARD_LOOKUP: std::sync::RwLock> = std::sync::RwLock::new(Vec::new()); + +const UNASSIGNED: usize = usize::MAX; + +/// Record a hit for the given guard ID into the active coverage map. +#[inline(always)] +pub fn record_hit(guard_id: u32) { + let ptr = COVERAGE_MAP_PTR.load(Ordering::Relaxed); + if ptr.is_null() { + return; + } + let len = COVERAGE_MAP_LEN.load(Ordering::Relaxed); + if len == 0 { + return; + } + + let gid = guard_id as usize; + + // Fast path: read lock, check if already assigned. + let idx = { + let lookup = GUARD_LOOKUP.read().unwrap(); + (gid < lookup.len() && lookup[gid] != UNASSIGNED).then(|| lookup[gid]) + }; + + let idx = idx.unwrap_or_else(|| { + // Slow path: write lock, assign new index (double-check after acquiring). + let mut lookup = GUARD_LOOKUP.write().unwrap(); + if gid >= lookup.len() { + lookup.resize(gid + 1, UNASSIGNED); + } + if lookup[gid] == UNASSIGNED { + lookup[gid] = NEXT_SANCOV_IDX.fetch_add(1, Ordering::Relaxed); + } + lookup[gid] + }); + + if idx >= len { + return; + } + unsafe { + let slot = ptr.add(idx); + *slot = (*slot).wrapping_add(1); + } +} + +/// Number of unique sancov edges discovered so far. +pub fn sancov_edge_count() -> usize { + NEXT_SANCOV_IDX.load(Ordering::Relaxed) +} + +static GUARD_COUNTER: AtomicU32 = AtomicU32::new(1); + +/// # Safety +/// +/// Called by the LLVM SanitizerCoverage runtime at startup. `[start, stop)` must be a valid +/// range of mutable `u32` guard slots allocated by the compiler for the current DSO. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32, stop: *mut u32) { + while start < stop { + let id = GUARD_COUNTER.fetch_add(1, Ordering::Relaxed); + unsafe { + *start = id; + start = start.add(1); + } + } +} + +/// # Safety +/// +/// Called by the LLVM SanitizerCoverage runtime at every instrumented CFG edge. +/// `guard` must point to a valid `u32` guard slot initialized by +/// `__sanitizer_cov_trace_pc_guard_init`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) { + let id = unsafe { *guard }; + if id == 0 { + return; + } + record_hit(id); +} + +// --------------------------------------------------------------------------- +// Trace-cmp: capture comparison operands from instrumented code +// --------------------------------------------------------------------------- + +const MAX_CMP_OPERANDS: usize = 512; + +/// A single comparison operand captured by a trace-cmp callback. +#[derive(Clone, Copy, Debug)] +pub struct CmpSample { + /// Bit-width of the original comparison (8, 16, 32, or 64). + pub width: u8, + /// The operand value, right-aligned in a 32-byte buffer. + pub value: [u8; 32], +} + +thread_local! { + static CMP_OPERANDS: std::cell::RefCell> = + const { std::cell::RefCell::new(Vec::new()) }; +} + +#[inline(always)] +fn record_cmp(width: u8, arg1: u64, arg2: u64) { + if !is_active() { + return; + } + if arg1 == 0 && arg2 == 0 { + return; + } + CMP_OPERANDS.with(|ops| { + let mut ops = ops.borrow_mut(); + if ops.len() >= MAX_CMP_OPERANDS { + return; + } + if arg1 != 0 { + let mut buf = [0u8; 32]; + buf[24..].copy_from_slice(&arg1.to_be_bytes()); + ops.push(CmpSample { width, value: buf }); + } + if arg2 != 0 && arg2 != arg1 { + let mut buf = [0u8; 32]; + buf[24..].copy_from_slice(&arg2.to_be_bytes()); + ops.push(CmpSample { width, value: buf }); + } + }); +} + +/// Drain all captured comparison operands from the current thread. +pub fn drain_cmp_operands() -> Vec { + CMP_OPERANDS.with(|ops| { + let mut ops = ops.borrow_mut(); + std::mem::take(&mut *ops) + }) +} + +/// Clear all captured comparison operands on the current thread. +pub fn clear_cmp_operands() { + CMP_OPERANDS.with(|ops| ops.borrow_mut().clear()); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 1-byte comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) { + record_cmp(8, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 2-byte comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_cmp2(arg1: u16, arg2: u16) { + record_cmp(16, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 4-byte comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_cmp4(arg1: u32, arg2: u32) { + record_cmp(32, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 8-byte comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64) { + record_cmp(64, arg1, arg2); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 1-byte constant comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) { + record_cmp(8, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 2-byte constant comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_const_cmp2(arg1: u16, arg2: u16) { + record_cmp(16, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 4-byte constant comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_const_cmp4(arg1: u32, arg2: u32) { + record_cmp(32, arg1 as u64, arg2 as u64); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage at 8-byte constant comparison instructions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_const_cmp8(arg1: u64, arg2: u64) { + record_cmp(64, arg1, arg2); +} + +/// # Safety +/// +/// Called by LLVM SanitizerCoverage before switch statements. +/// `cases[0]` is the number of cases, `cases[1]` is bit-width of `val`, +/// `cases[2..]` are the case constants. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __sanitizer_cov_trace_switch(val: u64, cases: *const u64) { + if !is_active() || cases.is_null() { + return; + } + let n = unsafe { *cases } as usize; + for i in 0..n.min(16) { + let case_val = unsafe { *cases.add(2 + i) }; + record_cmp(64, val, case_val); + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index f555d619fa228..73d64d3ab5d07 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -32,17 +32,25 @@ alloy-primitives = { workspace = true, features = [ alloy-sol-types.workspace = true revm-inspectors.workspace = true +tempo-contracts.workspace = true +tempo-precompiles.workspace = true + +async-trait.workspace = true eyre.workspace = true futures.workspace = true itertools.workspace = true +memchr.workspace = true +rayon.workspace = true +reqwest.workspace = true +revm.workspace = true +serde_json = { workspace = true, features = ["raw_value"] } serde.workspace = true -serde_json.workspace = true +solar.workspace = true +tempfile.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true -tempfile.workspace = true -rayon.workspace = true -solar-parse.workspace = true -revm.workspace = true +yansi.workspace = true -[dev-dependencies] -tempfile.workspace = true +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism", "foundry-evm-core/optimism"] diff --git a/crates/evm/traces/src/backtrace/mod.rs b/crates/evm/traces/src/backtrace/mod.rs new file mode 100644 index 0000000000000..1b479954b8d59 --- /dev/null +++ b/crates/evm/traces/src/backtrace/mod.rs @@ -0,0 +1,419 @@ +//! Solidity stack trace support for test failures. + +use crate::{CallTrace, SparsedTraceArena}; +use alloy_primitives::{Address, Bytes, map::HashMap}; +use foundry_compilers::{ + Artifact, ArtifactId, ProjectCompileOutput, + artifacts::{ConfigurableContractArtifact, Libraries, sourcemap::SourceMap}, +}; +use std::{fmt, path::PathBuf}; +use yansi::Paint; + +mod source_map; +use source_map::load_build_sources; +pub use source_map::{PcSourceMapper, SourceData}; + +/// Linked library information for backtrace resolution. +/// +/// Contains the path, name, and deployed address of a linked library +/// to enable proper frame resolution in backtraces. +#[derive(Debug, Clone)] +struct LinkedLib { + /// The source file path of the library + path: PathBuf, + /// The name of the library contract + name: String, + /// The deployed address of the library + address: Address, +} + +/// Holds a reference to [`ProjectCompileOutput`] to fetch artifacts and sources for backtrace +/// generation. +pub struct BacktraceBuilder<'a> { + /// Linked libraries from configuration + linked_libraries: Vec, + /// Reference to project output for on-demand source loading + output: &'a ProjectCompileOutput, + /// Project root + root: PathBuf, + /// Disable source locations + /// + /// Source locations will be inaccurately reported if the files have been compiled with via-ir + disable_source_locs: bool, + /// Sources grouped by [`ArtifactId::build_id`] to avoid re-reading files for artifacts from + /// the same build + /// + /// The source [`Vec`] is indexed by the compiler source ID, and contains the source path and + /// source content. + build_sources_cache: HashMap>, +} + +impl<'a> BacktraceBuilder<'a> { + /// Instantiates a backtrace builder from a [`ProjectCompileOutput`]. + pub fn new( + output: &'a ProjectCompileOutput, + root: PathBuf, + linked_libraries: Option, + disable_source_locs: bool, + ) -> Self { + let linked_libs = linked_libraries + .map(|libs| { + libs.libs + .iter() + .flat_map(|(path, libs_map)| { + libs_map.iter().map(move |(name, addr_str)| (path, name, addr_str)) + }) + .filter_map(|(path, name, addr_str)| { + addr_str.parse().ok().map(|address| LinkedLib { + path: path.clone(), + name: name.clone(), + address, + }) + }) + .collect() + }) + .unwrap_or_default(); + + Self { + linked_libraries: linked_libs, + output, + root, + disable_source_locs, + build_sources_cache: HashMap::default(), + } + } + + /// Generates a backtrace from a [`SparsedTraceArena`]. + pub fn from_traces(&mut self, arena: &SparsedTraceArena) -> Backtrace<'_> { + // Resolve addresses to artifacts using trace labels and linked libraries + let artifacts_by_address = self.resolve_addresses(arena); + for (artifact_id, _) in artifacts_by_address.values() { + let build_id = &artifact_id.build_id; + if !self.build_sources_cache.contains_key(build_id) + && let Some(sources) = load_build_sources(build_id, self.output, &self.root) + { + self.build_sources_cache.insert(build_id.clone(), sources); + } + } + + Backtrace::new( + artifacts_by_address, + &self.build_sources_cache, + self.linked_libraries.clone(), + self.disable_source_locs, + arena, + ) + } + + /// Resolves contract addresses to [`ArtifactId`] and their [`SourceData`] from trace labels and + /// linked libraries. + fn resolve_addresses( + &self, + arena: &SparsedTraceArena, + ) -> HashMap { + let mut artifacts_by_address = HashMap::default(); + + // Collect all labels from traces first + let label_to_address = arena + .nodes() + .iter() + .filter_map(|node| { + if let Some(decoded) = &node.trace.decoded + && let Some(label) = &decoded.label + { + return Some((label.as_str(), node.trace.address)); + } + None + }) + .collect::>(); + + // Build linked library target IDs + let linked_lib_targets = self + .linked_libraries + .iter() + .map(|lib| (format!("{}:{}", lib.path.display(), lib.name), lib.address)) + .collect::>(); + + let get_source = |artifact: &ConfigurableContractArtifact| -> Option<(SourceMap, Bytes)> { + let source_map = artifact.get_source_map_deployed()?.ok()?; + let deployed_bytecode = artifact.get_deployed_bytecode_bytes()?.into_owned(); + + if deployed_bytecode.is_empty() { + return None; + } + + Some((source_map, deployed_bytecode)) + }; + + for (artifact_id, artifact) in self.output.artifact_ids() { + // Match and insert artifacts using trace labels + if let Some(address) = label_to_address.get(artifact_id.name.as_str()) + && let Some((source_map, bytecode)) = get_source(artifact) + { + // Match and insert artifacts using trace labels + artifacts_by_address + .insert(*address, (artifact_id.clone(), SourceData { source_map, bytecode })); + } else if let Some(&lib_address) = + // Match and insert the linked library artifacts + linked_lib_targets.get(&artifact_id.identifier()).or_else(|| { + let id = artifact_id + .clone() + .with_stripped_file_prefixes(&self.root) + .identifier(); + linked_lib_targets.get(&id) + }) + && let Some((source_map, bytecode)) = get_source(artifact) + { + // Insert linked libraries + artifacts_by_address + .insert(lib_address, (artifact_id, SourceData { source_map, bytecode })); + } + } + + artifacts_by_address + } +} + +/// A Solidity stack trace for a test failure. +/// +/// Generates a backtrace from a [`SparsedTraceArena`] by leveraging source maps and bytecode. +/// +/// It uses the program counter (PC) from the traces to map to a specific source location for the +/// call. +/// +/// Each step/call in the backtrace is classified as a BacktraceFrame +#[non_exhaustive] +pub struct Backtrace<'a> { + /// The frames of the backtrace, from innermost (where the revert happened) to outermost. + frames: Vec, + /// Map from address to PcSourceMapper + pc_mappers: HashMap>, + /// Linked libraries from configuration + linked_libraries: Vec, + /// Disable pinpointing source locations in files + /// + /// Should be disabled when via-ir is enabled + disable_source_locs: bool, +} + +impl<'a> Backtrace<'a> { + /// Creates a backtrace from collected artifacts and sources. + fn new( + artifacts_by_address: HashMap, + build_sources: &'a HashMap>, + linked_libraries: Vec, + disable_source_locs: bool, + arena: &SparsedTraceArena, + ) -> Self { + let mut pc_mappers = HashMap::default(); + + // Build PC source mappers for each contract + if !disable_source_locs { + for (addr, (artifact_id, source_data)) in artifacts_by_address { + if let Some(sources) = build_sources.get(&artifact_id.build_id) { + let mapper = PcSourceMapper::new(source_data, sources); + pc_mappers.insert(addr, mapper); + } + } + } + + let mut backtrace = + Self { frames: Vec::new(), pc_mappers, linked_libraries, disable_source_locs }; + + backtrace.extract_frames(arena); + + backtrace + } + + /// Extracts backtrace frames from a trace arena. + fn extract_frames(&mut self, arena: &SparsedTraceArena) { + let resolved_arena = &arena.arena; + + if resolved_arena.nodes().is_empty() { + return; + } + + // Find the deepest failed node (where the actual revert happened) + let mut current_idx = None; + let mut max_depth = 0; + + for (idx, node) in resolved_arena.nodes().iter().enumerate() { + if !node.trace.success && node.trace.depth >= max_depth { + max_depth = node.trace.depth; + current_idx = Some(idx); + } + } + + if current_idx.is_none() { + return; + } + + // Build the call stack by walking from the deepest node back to root + while let Some(idx) = current_idx { + let node = &resolved_arena.nodes()[idx]; + let trace = &node.trace; + + if let Some(frame) = self.create_frame(trace) { + self.frames.push(frame); + } + + current_idx = node.parent; + } + } + + /// Creates a frame from a call trace. + fn create_frame(&self, trace: &CallTrace) -> Option { + let contract_address = trace.address; + let mut frame = BacktraceFrame::new(contract_address); + + // Try to get source location from PC mapper + if !self.disable_source_locs + && let Some(source_location) = trace.steps.last().and_then(|last_step| { + self.pc_mappers.get(&contract_address).and_then(|m| m.map_pc(last_step.pc)) + }) + { + frame = frame + .with_source_location( + source_location.file, + source_location.line, + source_location.column, + ) + .with_byte_offset(source_location.offset); + } + + if let Some(decoded) = &trace.decoded { + if let Some(label) = &decoded.label { + frame = frame.with_contract_name(label.clone()); + } else if let Some(lib) = + self.linked_libraries.iter().find(|l| l.address == contract_address) + { + frame = frame.with_contract_name(lib.name.clone()); + } + + if let Some(call_data) = &decoded.call_data { + let sig = &call_data.signature; + let func_name = + if let Some(paren_pos) = sig.find('(') { &sig[..paren_pos] } else { sig }; + frame = frame.with_function_name(func_name.to_string()); + } + } + + Some(frame) + } + + /// Returns true if the backtrace is empty. + pub const fn is_empty(&self) -> bool { + self.frames.is_empty() + } +} + +impl fmt::Display for Backtrace<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.frames.is_empty() { + return Ok(()); + } + + writeln!(f, "{}", Paint::yellow("Backtrace:"))?; + + for frame in &self.frames { + write!(f, " ")?; + write!(f, "at ")?; + writeln!(f, "{frame}")?; + } + + Ok(()) + } +} + +/// A single frame in a backtrace. +#[derive(Debug, Clone)] +struct BacktraceFrame { + /// The contract address where this frame is executing. + pub contract_address: Address, + /// The contract name, if known. + pub contract_name: Option, + /// The function name, if known. + pub function_name: Option, + /// The source file path. + pub file: Option, + /// The line number in the source file. + pub line: Option, + /// The column number in the source file. + pub column: Option, + /// The byte offset in the source file. + pub byte_offset: Option, +} + +impl BacktraceFrame { + /// Creates a new backtrace frame. + const fn new(contract_address: Address) -> Self { + Self { + contract_address, + contract_name: None, + function_name: None, + file: None, + line: None, + column: None, + byte_offset: None, + } + } + + /// Sets the contract name. + fn with_contract_name(mut self, name: String) -> Self { + self.contract_name = Some(name); + self + } + + /// Sets the function name. + fn with_function_name(mut self, name: String) -> Self { + self.function_name = Some(name); + self + } + + /// Sets the source location. + fn with_source_location(mut self, file: PathBuf, line: usize, column: usize) -> Self { + self.file = Some(file); + self.line = Some(line); + self.column = Some(column); + self + } + + /// Sets the byte offset. + const fn with_byte_offset(mut self, offset: usize) -> Self { + self.byte_offset = Some(offset); + self + } +} + +// Format: . (FILE:LINE:COL) +impl fmt::Display for BacktraceFrame { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut result = String::new(); + + // No contract name, show address + result.push_str(self.contract_name.as_ref().unwrap_or(&self.contract_address.to_string())); + + // Add function name if available + result.push_str(&self.function_name.as_ref().map_or(String::new(), |f| format!(".{f}"))); + + if let Some(file) = &self.file { + result.push_str(" ("); + result.push_str(&file.display().to_string()); + } + + if let Some(line) = self.line { + result.push(':'); + result.push_str(&line.to_string()); + + result.push(':'); + result.push_str(&self.column.as_ref().map_or("0".to_string(), |c| c.to_string())); + } + + // Add location in parentheses if available + if self.file.is_some() || self.line.is_some() { + result.push(')'); + } + + write!(f, "{result}") + } +} diff --git a/crates/evm/traces/src/backtrace/source_map.rs b/crates/evm/traces/src/backtrace/source_map.rs new file mode 100644 index 0000000000000..b5f11e5a20030 --- /dev/null +++ b/crates/evm/traces/src/backtrace/source_map.rs @@ -0,0 +1,177 @@ +//! Source map decoding and PC mapping utilities. + +use alloy_primitives::Bytes; +use foundry_compilers::{ProjectCompileOutput, artifacts::sourcemap::SourceMap}; +use foundry_evm_core::ic::IcPcMap; +use std::path::{Path, PathBuf}; + +/// Source data for a single contract. +#[derive(Debug, Clone)] +pub struct SourceData { + /// Runtime source map for the contract + pub source_map: SourceMap, + /// Deployed bytecode for accurate PC mapping + pub bytecode: Bytes, +} + +/// Maps program counters to source locations. +pub struct PcSourceMapper<'a> { + /// Mapping from instruction counter to program counter. + ic_pc_map: IcPcMap, + /// Source data consists of the source_map and the deployed bytecode + source_data: SourceData, + /// Source files i.e source path and content (indexed by source_id) + sources: &'a [(PathBuf, String)], + /// Cached line offset mappings for each source file. + line_offsets: Vec>, +} + +impl<'a> PcSourceMapper<'a> { + /// Creates a new PC to source mapper. + pub fn new(source_data: SourceData, sources: &'a [(PathBuf, String)]) -> Self { + // Build instruction counter to program counter mapping + let ic_pc_map = IcPcMap::new(source_data.bytecode.as_ref()); + + // Pre-calculate line offsets for each source file + let line_offsets = + sources.iter().map(|(_, content)| compute_line_offsets(content)).collect(); + + Self { ic_pc_map, source_data, sources, line_offsets } + } + + /// Maps a program counter to source location. + pub fn map_pc(&self, pc: usize) -> Option { + // Find the instruction counter for this PC + let ic = self.find_instruction_counter(pc)?; + + // Get the source element for this instruction + let element = self.source_data.source_map.get(ic)?; + + // Get the source file index - returns None if index is -1 + let source_idx_opt = element.index(); + + let source_idx = source_idx_opt? as usize; + if source_idx >= self.sources.len() { + return None; + } + + // Get the source file info + let (file_path, content) = &self.sources[source_idx]; + + // Convert byte offset to line and column + let offset = element.offset() as usize; + + // Check if offset is valid for this source file + if offset >= content.len() { + return None; + } + + let (line, column) = self.offset_to_line_column(source_idx, offset)?; + + trace!( + file = ?file_path, + line = line, + column = column, + offset = offset, + "Mapped PC to source location" + ); + + Some(SourceLocation { + file: file_path.clone(), + line, + column, + length: element.length() as usize, + offset, + }) + } + + /// Finds the instruction counter for a given program counter. + fn find_instruction_counter(&self, pc: usize) -> Option { + // The IcPcMap maps IC -> PC, we need the reverse + // We find the highest IC that has a PC <= our target PC + let mut best_ic = None; + let mut best_pc = 0; + + for (ic, mapped_pc) in self.ic_pc_map.iter() { + let mapped_pc = *mapped_pc as usize; + if mapped_pc <= pc && mapped_pc >= best_pc { + best_pc = mapped_pc; + best_ic = Some(*ic as usize); + } + } + + best_ic + } + + /// Converts a byte offset to line and column numbers. + /// + /// Returned lines and column numbers are 1-indexed. + fn offset_to_line_column(&self, source_idx: usize, offset: usize) -> Option<(usize, usize)> { + let line_offsets = self.line_offsets.get(source_idx)?; + + // Find the line containing this offset + let line = line_offsets.binary_search(&offset).unwrap_or_else(|i| i.saturating_sub(1)); + + // Calculate column within the line + let line_start = if line == 0 { 0 } else { line_offsets[line - 1] + 1 }; + let column = offset.saturating_sub(line_start); + + // Lines and columns are 1-indexed + Some((line + 1, column + 1)) + } +} +/// Represents a location in source code. +#[derive(Debug, Clone)] +pub struct SourceLocation { + pub file: PathBuf, + pub line: usize, + pub column: usize, + pub length: usize, + /// Byte offset in the source file + /// This specifically useful when one source file contains multiple contracts / libraries. + pub offset: usize, +} + +/// Computes line offset positions in source content. +fn compute_line_offsets(content: &str) -> Vec { + let mut offsets = vec![0]; + offsets.extend(memchr::memchr_iter(b'\n', content.as_bytes())); + offsets +} + +/// Loads sources for a specific ArtifactId.build_id +pub fn load_build_sources( + build_id: &str, + output: &ProjectCompileOutput, + root: &Path, +) -> Option> { + let build_ctx = output.builds().find(|(bid, _)| *bid == build_id).map(|(_, ctx)| ctx)?; + + // Determine the size needed for sources vector + // Highest source_id + let max_source_id = build_ctx.source_id_to_path.keys().max().map_or(0, |id| *id) as usize; + + // Vec of source path and it's content + let mut sources = vec![(PathBuf::new(), String::new()); max_source_id + 1]; + + // Populate sources at their correct indices + for (source_id, source_path) in &build_ctx.source_id_to_path { + let idx = *source_id as usize; + + let full_path = + if source_path.is_absolute() { source_path.clone() } else { root.join(source_path) }; + let mut source_content = foundry_common::fs::read_to_string(&full_path).unwrap_or_default(); + + // Normalize line endings for windows + if source_content.contains('\r') { + source_content = source_content.replace("\r\n", "\n"); + } + + // Convert path to relative PathBuf + let path_buf = source_path.strip_prefix(root).unwrap_or(source_path).to_path_buf(); + + sources[idx] = (path_buf, source_content); + } + + Some(sources) +} diff --git a/crates/evm/traces/src/debug/mod.rs b/crates/evm/traces/src/debug/mod.rs index 0e07124597aa3..04934ac49026e 100644 --- a/crates/evm/traces/src/debug/mod.rs +++ b/crates/evm/traces/src/debug/mod.rs @@ -1,8 +1,8 @@ mod sources; use crate::CallTraceNode; use alloy_dyn_abi::{ - parser::{Parameters, Storage}, DynSolType, DynSolValue, Specifier, + parser::{Parameters, Storage}, }; use alloy_primitives::U256; use foundry_common::fmt::format_token; @@ -18,7 +18,7 @@ pub struct DebugTraceIdentifier { } impl DebugTraceIdentifier { - pub fn new(contracts_sources: ContractSources) -> Self { + pub const fn new(contracts_sources: ContractSources) -> Self { Self { contracts_sources } } @@ -65,7 +65,7 @@ struct DebugStepsWalker<'a> { } impl<'a> DebugStepsWalker<'a> { - pub fn new( + pub const fn new( node: &'a mut CallTraceNode, sources: &'a ContractSources, contract_name: &'a str, @@ -105,9 +105,9 @@ impl<'a> DebugStepsWalker<'a> { return false; }; - loc.offset() == other_loc.offset() && - loc.length() == other_loc.length() && - loc.index() == other_loc.index() + loc.offset() == other_loc.offset() + && loc.length() == other_loc.length() + && loc.index() == other_loc.index() } /// Invoked when current step is a JUMPDEST preceded by a JUMP marked as [Jump::In]. @@ -131,10 +131,10 @@ impl<'a> DebugStepsWalker<'a> { /// Invoked when current step is a JUMPDEST preceded by a JUMP marked as [Jump::Out]. fn jump_out(&mut self) { let Some((i, _)) = self.stack.iter().enumerate().rfind(|(_, (_, step_idx))| { - self.is_same_loc(*step_idx, self.current_step) || - self.is_same_loc(step_idx + 1, self.current_step - 1) + self.is_same_loc(*step_idx, self.current_step) + || self.is_same_loc(step_idx + 1, self.current_step - 1) }) else { - return + return; }; // We've found a match, remove all records between start and end, those // are considered invalid. @@ -143,25 +143,26 @@ impl<'a> DebugStepsWalker<'a> { // Try to decode function inputs and outputs from the stack and memory. let (inputs, outputs) = self .src_map(start_idx + 1) - .map(|(source_element, source)| { + .and_then(|(source_element, source)| { let start = source_element.offset() as usize; - let end = start + source_element.length() as usize; - let fn_definition = source.source[start..end].replace('\n', ""); + let (fn_definition, _) = + source_span(&source.source, start, source_element.length() as usize)?; + let fn_definition = fn_definition.replace('\n', ""); let (inputs, outputs) = parse_types(&fn_definition); - ( + Some(( inputs.and_then(|t| { try_decode_args_from_step(&t, &self.node.trace.steps[start_idx + 1]) }), outputs.and_then(|t| try_decode_args_from_step(&t, self.current_step())), - ) + )) }) .unwrap_or_default(); - self.node.trace.steps[start_idx].decoded = Some(DecodedTraceStep::InternalCall( + self.node.trace.steps[start_idx].decoded = Some(Box::new(DecodedTraceStep::InternalCall( DecodedInternalCall { func_name, args: inputs, return_data: outputs }, self.current_step, - )); + ))); } fn process(&mut self) { @@ -199,15 +200,8 @@ impl<'a> DebugStepsWalker<'a> { /// Returns string in the format `Contract::function`. fn parse_function_from_loc(source: &SourceData, loc: &SourceElement) -> Option { let start = loc.offset() as usize; - let end = start + loc.length() as usize; - let src_len = source.source.len(); + let (source_part, end) = source_span(&source.source, start, loc.length() as usize)?; - // Handle special case of preprocessed test sources. - if start > src_len || end > src_len { - return None; - } - - let source_part = &source.source[start..end]; if !source_part.starts_with("function") { return None; } @@ -217,6 +211,12 @@ fn parse_function_from_loc(source: &SourceData, loc: &SourceElement) -> Option Option<(&str, usize)> { + let end = start.checked_add(len)?; + + Some((source.get(start..end)?, end)) +} + /// Parses function input and output types into [Parameters]. fn parse_types(source: &str) -> (Option>, Option>) { let inputs = source.find('(').and_then(|params_start| { @@ -329,3 +329,15 @@ fn decode_from_memory(ty: &DynSolType, memory: &[u8], location: usize) -> Option _ => ty.abi_decode(first_word).ok(), } } + +#[cfg(test)] +mod tests { + use super::source_span; + + #[test] + fn source_span_returns_none_for_invalid_ranges() { + assert_eq!(source_span("abcdef", 2, 3), Some(("cde", 5))); + assert_eq!(source_span("abcdef", 7, 1), None); + assert_eq!(source_span("abcdef", usize::MAX, 1), None); + } +} diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index cfd7056e5a8b6..18ed9e0f4ef9d 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -1,17 +1,16 @@ use eyre::{Context, Result}; -use foundry_common::compact_to_contract; +use foundry_common::{compact_to_contract, strip_bytecode_placeholders}; use foundry_compilers::{ + Artifact, ProjectCompileOutput, artifacts::{ + Bytecode, ContractBytecodeSome, Libraries, Source, sourcemap::{SourceElement, SourceMap}, - Bytecode, Contract, ContractBytecodeSome, Libraries, Source, }, multi::MultiCompilerLanguage, - Artifact, Compiler, ProjectCompileOutput, }; use foundry_evm_core::ic::PcIcMap; use foundry_linking::Linker; use rayon::prelude::*; -use solar_parse::{interface::Session, Parser}; use std::{ collections::{BTreeMap, HashMap, HashSet}, fmt::Write, @@ -27,11 +26,17 @@ pub struct SourceData { pub path: PathBuf, /// Maps contract name to (start, end) of the contract definition in the source code. /// This is useful for determining which contract contains given function definition. - contract_definitions: Vec<(String, Range)>, + pub contract_definitions: Vec<(String, Range)>, } impl SourceData { - pub fn new(source: Arc, language: MultiCompilerLanguage, path: PathBuf) -> Self { + pub fn new( + output: &ProjectCompileOutput, + source: Arc, + language: MultiCompilerLanguage, + path: PathBuf, + root: &Path, + ) -> Self { let mut contract_definitions = Vec::new(); match language { @@ -42,21 +47,21 @@ impl SourceData { } } MultiCompilerLanguage::Solc(_) => { - let sess = Session::builder().with_silent_emitter(None).build(); - let _ = sess.enter(|| -> solar_parse::interface::Result<()> { - let arena = solar_parse::ast::Arena::new(); - let filename = path.clone().into(); - let mut parser = - Parser::from_source_code(&sess, &arena, filename, source.to_string())?; - let ast = parser.parse_file().map_err(|e| e.emit())?; - for item in ast.items { - if let solar_parse::ast::ItemKind::Contract(contract) = &item.kind { - let range = item.span.lo().to_usize()..item.span.hi().to_usize(); - contract_definitions.push((contract.name.to_string(), range)); + let r = output.parser().solc().compiler().enter(|compiler| -> Option<()> { + let (_, source) = compiler.gcx().get_ast_source(root.join(&path))?; + for item in source.ast.as_ref()?.items.iter() { + if let solar::ast::ItemKind::Contract(contract) = &item.kind { + contract_definitions.push(( + contract.name.to_string(), + compiler.sess().source_map().span_to_range(item.span).unwrap(), + )); } } - Ok(()) + Some(()) }); + if r.is_none() { + warn!("failed to parse contract definitions for {}", path.display()); + } } } @@ -94,9 +99,9 @@ impl ArtifactData { }) }; - // Only parse bytecode if it's not empty. - let pc_ic_map = if let Some(bytes) = b.bytes() { - (!bytes.is_empty()).then(|| PcIcMap::new(bytes)) + // Only parse bytecode if it's not empty, stripping placeholders if necessary. + let pc_ic_map = if let Some(bytes) = strip_bytecode_placeholders(&b.object) { + (!bytes.is_empty()).then(|| PcIcMap::new(bytes.as_ref())) } else { None }; @@ -135,15 +140,12 @@ impl ContractSources { Ok(sources) } - pub fn insert>( + pub fn insert( &mut self, - output: &ProjectCompileOutput, + output: &ProjectCompileOutput, root: &Path, libraries: Option<&Libraries>, - ) -> Result<()> - where - C::Language: Into, - { + ) -> Result<()> { let link_data = libraries.map(|libraries| { let linker = Linker::new(root, output.artifact_ids().collect()); (linker, libraries) @@ -197,9 +199,11 @@ impl ContractSources { })?; let stripped = path.strip_prefix(root).unwrap_or(path).to_path_buf(); let source_data = Arc::new(SourceData::new( + output, source.content.clone(), - build.language.into(), + build.language, stripped, + root, )); entry.insert(source_data.clone()); source_data @@ -287,7 +291,7 @@ impl ContractSources { source_map.get(pc as usize) }?; // if the source element has an index, find the sourcemap for that index - let res = source_element + source_element .index() // if index matches current file_id, return current source code .and_then(|index| { @@ -299,9 +303,7 @@ impl ContractSources { .get(&artifact.build_id)? .get(&source_element.index()?) .map(|source| (source_element.clone(), source.as_ref())) - }); - - res + }) }) } } diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 44b9c9cd729b6..a47c90c116d0b 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -1,33 +1,39 @@ use crate::{ + CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, debug::DebugTraceIdentifier, identifier::{IdentifiedAddress, LocalTraceIdentifier, SignaturesIdentifier, TraceIdentifier}, - CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, }; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Error, Event, Function, JsonAbi}; use alloy_primitives::{ - map::{hash_map::Entry, HashMap, HashSet}, - Address, LogData, Selector, B256, + Address, B256, LogData, Selector, + map::{HashMap, HashSet, hash_map::Entry}, }; use foundry_common::{ - abi::get_indexed_event, fmt::format_token, get_contract_name, selectors::SelectorKind, - ContractsByArtifact, SELECTOR_LEN, + ContractsByArtifact, SELECTOR_LEN, abi::get_indexed_event, fmt::format_token, + get_contract_name, selectors::SelectorKind, }; use foundry_evm_core::{ - abi::{console, Vm}, - constants::{ - CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, - TEST_CONTRACT_ADDRESS, - }, + abi::{Vm, console}, + constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS}, decode::RevertDecoder, precompiles::{ - BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, - RIPEMD_160, SHA_256, + BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, + MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, }, }; use itertools::Itertools; use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; use std::{collections::BTreeMap, sync::OnceLock}; +use tempo_contracts::precompiles::{ + IAccountKeychain, IFeeManager, IStablecoinDEX, ITIP20Factory, ITIP403Registry, IValidatorConfig, +}; +use tempo_precompiles::{ + ACCOUNT_KEYCHAIN_ADDRESS, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS, STABLECOIN_DEX_ADDRESS, + TIP_FEE_MANAGER_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, + VALIDATOR_CONFIG_ADDRESS, nonce::INonce, tip20::ITIP20, +}; mod precompiles; @@ -77,7 +83,7 @@ impl CallTraceDecoderBuilder { /// Sets the verbosity level of the decoder. #[inline] - pub fn with_verbosity(mut self, level: u8) -> Self { + pub const fn with_verbosity(mut self, level: u8) -> Self { self.decoder.verbosity = level; self } @@ -89,6 +95,20 @@ impl CallTraceDecoderBuilder { self } + /// Sets the signature identifier for events and functions. + #[inline] + pub const fn with_label_disabled(mut self, disable_alias: bool) -> Self { + self.decoder.disable_labels = disable_alias; + self + } + + /// Sets the chain ID for network-specific precompile detection. + #[inline] + pub const fn with_chain_id(mut self, chain_id: Option) -> Self { + self.decoder.chain_id = chain_id; + self + } + /// Sets the debug identifier for the decoder. #[inline] pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self { @@ -143,6 +163,12 @@ pub struct CallTraceDecoder { /// Optional identifier of individual trace steps. pub debug_identifier: Option, + + /// Disable showing of labels. + pub disable_labels: bool, + + /// The chain ID, used to determine network-specific precompiles. + pub chain_id: Option, } impl CallTraceDecoder { @@ -157,6 +183,7 @@ impl CallTraceDecoder { INIT.get_or_init(Self::init) } + #[instrument(name = "CallTraceDecoder::init", level = "debug")] fn init() -> Self { Self { contracts: Default::default(), @@ -165,7 +192,6 @@ impl CallTraceDecoder { (HARDHAT_CONSOLE_ADDRESS, "console".to_string()), (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()), (CALLER, "DefaultSender".to_string()), - (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()), (EC_RECOVER, "ECRecover".to_string()), (SHA_256, "SHA-256".to_string()), (RIPEMD_160, "RIPEMD-160".to_string()), @@ -176,6 +202,23 @@ impl CallTraceDecoder { (EC_PAIRING, "ECPairing".to_string()), (BLAKE_2F, "Blake2F".to_string()), (POINT_EVALUATION, "PointEvaluation".to_string()), + (BLS12_G1ADD, "BLS12_G1ADD".to_string()), + (BLS12_G1MSM, "BLS12_G1MSM".to_string()), + (BLS12_G2ADD, "BLS12_G2ADD".to_string()), + (BLS12_G2MSM, "BLS12_G2MSM".to_string()), + (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()), + (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()), + (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()), + (P256_VERIFY, "P256VERIFY".to_string()), + // Tempo + (TIP_FEE_MANAGER_ADDRESS, "FeeManager".to_string()), + (TIP403_REGISTRY_ADDRESS, "TIP403Registry".to_string()), + (TIP20_FACTORY_ADDRESS, "TIP20Factory".to_string()), + (STABLECOIN_DEX_ADDRESS, "StablecoinDex".to_string()), + (NONCE_PRECOMPILE_ADDRESS, "Nonce".to_string()), + (VALIDATOR_CONFIG_ADDRESS, "ValidatorConfig".to_string()), + (ACCOUNT_KEYCHAIN_ADDRESS, "AccountKeychain".to_string()), + (PATH_USD_ADDRESS, "PathUSD".to_string()), ]), receive_contracts: Default::default(), fallback_contracts: Default::default(), @@ -184,11 +227,29 @@ impl CallTraceDecoder { functions: console::hh::abi::functions() .into_values() .chain(Vm::abi::functions().into_values()) + // Tempo + .chain(IFeeManager::abi::functions().into_values()) + .chain(ITIP20::abi::functions().into_values()) + .chain(ITIP403Registry::abi::functions().into_values()) + .chain(ITIP20Factory::abi::functions().into_values()) + .chain(IStablecoinDEX::abi::functions().into_values()) + .chain(INonce::abi::functions().into_values()) + .chain(IValidatorConfig::abi::functions().into_values()) + .chain(IAccountKeychain::abi::functions().into_values()) .flatten() .map(|func| (func.selector(), vec![func])) .collect(), events: console::ds::abi::events() .into_values() + // Tempo + .chain(IFeeManager::abi::events().into_values()) + .chain(ITIP20::abi::events().into_values()) + .chain(ITIP403Registry::abi::events().into_values()) + .chain(ITIP20Factory::abi::events().into_values()) + .chain(IStablecoinDEX::abi::events().into_values()) + .chain(INonce::abi::events().into_values()) + .chain(IValidatorConfig::abi::events().into_values()) + .chain(IAccountKeychain::abi::events().into_values()) .flatten() .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) .collect(), @@ -198,6 +259,10 @@ impl CallTraceDecoder { verbosity: 0, debug_identifier: None, + + disable_labels: false, + + chain_id: None, } } @@ -212,6 +277,7 @@ impl CallTraceDecoder { self.receive_contracts.clear(); self.fallback_contracts.clear(); + self.non_fallback_contracts.clear(); } /// Identify unknown addresses in the specified call trace using the specified identifier. @@ -230,6 +296,12 @@ impl CallTraceDecoder { identifier: &'a mut impl TraceIdentifier, ) -> Vec> { let nodes = arena.nodes().iter().filter(|node| { + // Skip precompile addresses, they will never resolve externally. + if node.is_precompile() + || precompiles::is_known_precompile(node.trace.address, self.chain_id) + { + return false; + } let address = &node.trace.address; !self.labels.contains_key(address) || !self.contracts.contains_key(address) }); @@ -258,11 +330,38 @@ impl CallTraceDecoder { } } + /// Selects the appropriate function from a list of functions with the same selector + /// by checking which one belongs to the contract being called, this avoids collisions + /// where multiple different functions across different contracts have the same selector. + fn select_contract_function<'a>( + &self, + functions: &'a [Function], + trace: &CallTrace, + ) -> &'a [Function] { + // When there are selector collisions, try to decode the calldata with each function + // to determine which one is actually being called. The correct function should + // decode successfully while the wrong ones will fail due to parameter type mismatches. + if functions.len() > 1 { + for (i, func) in functions.iter().enumerate() { + if trace.data.len() >= SELECTOR_LEN + && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok() + { + return &functions[i..i + 1]; + } + } + } + functions + } + /// Adds a single error to the decoder. pub fn push_error(&mut self, error: Error) { self.revert_decoder.push_error(error); } + pub const fn without_label(&mut self, disable: bool) { + self.disable_labels = disable; + } + fn collect_identified_addresses(&mut self, mut addrs: Vec>) { addrs.sort_by_key(|identity| identity.address); addrs.dedup_by_key(|identity| identity.address); @@ -278,7 +377,7 @@ impl CallTraceDecoder { self.contracts.entry(address).or_insert(contract); } - if let Some(label) = label { + if let Some(label) = label.filter(|s| !s.is_empty()) { self.labels.entry(address).or_insert(label); } @@ -323,28 +422,29 @@ impl CallTraceDecoder { /// [CallTraceDecoder::decode_event] for more details. pub async fn populate_traces(&self, traces: &mut Vec) { for node in traces { - node.trace.decoded = self.decode_function(&node.trace).await; + node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await)); for log in &mut node.logs { - log.decoded = self.decode_event(&log.raw_log).await; + log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await)); } - if let Some(debug) = self.debug_identifier.as_ref() { - if let Some(identified) = self.contracts.get(&node.trace.address) { - debug.identify_node_steps(node, get_contract_name(identified)) - } + if let Some(debug) = self.debug_identifier.as_ref() + && let Some(identified) = self.contracts.get(&node.trace.address) + { + debug.identify_node_steps(node, get_contract_name(identified)) } } } /// Decodes a call trace. pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace { - let label = self.labels.get(&trace.address).cloned(); + let label = + if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() }; if trace.kind.is_any_create() { return DecodedCallTrace { label, ..Default::default() }; } - if let Some(trace) = precompiles::decode(trace, 1) { + if let Some(trace) = precompiles::decode(trace, self.chain_id) { return trace; } @@ -363,10 +463,10 @@ impl CallTraceDecoder { let functions = match self.functions.get(&selector) { Some(fs) => fs, None => { - if let Some(identifier) = &self.signature_identifier { - if let Some(function) = identifier.identify_function(selector).await { - functions.push(function); - } + if let Some(identifier) = &self.signature_identifier + && let Some(function) = identifier.identify_function(selector).await + { + functions.push(function); } &functions } @@ -374,43 +474,42 @@ impl CallTraceDecoder { // Check if unsupported fn selector: calldata dooes NOT point to one of its selectors + // non-fallback contract + no receive - if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address) { - if !contract_selectors.contains(&selector) && - (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address)) - { - let return_data = if !trace.success { - let revert_msg = - self.revert_decoder.decode(&trace.output, Some(trace.status)); - - if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") { - Some(format!( - "unrecognized function selector {} for contract {}, which has no fallback function.", - selector, trace.address - )) - } else { - Some(revert_msg) - } - } else { - None - }; + if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address) + && !contract_selectors.contains(&selector) + && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address)) + { + let return_data = if trace.success { + None + } else { + let revert_msg = self.revert_decoder.decode(&trace.output, trace.status); - if let Some(func) = functions.first() { - return DecodedCallTrace { - label, - call_data: Some(self.decode_function_input(trace, func)), - return_data, - }; + if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") { + Some(format!( + "unrecognized function selector {} for contract {}, which has no fallback function.", + selector, trace.address + )) } else { - return DecodedCallTrace { - label, - call_data: self.fallback_call_data(trace), - return_data, - }; - }; - } + Some(revert_msg) + } + }; + + return if let Some(func) = functions.first() { + DecodedCallTrace { + label, + call_data: Some(self.decode_function_input(trace, func)), + return_data, + } + } else { + DecodedCallTrace { + label, + call_data: self.fallback_call_data(trace), + return_data, + } + }; } - let [func, ..] = &functions[..] else { + let contract_functions = self.select_contract_function(functions, trace); + let [func, ..] = contract_functions else { return DecodedCallTrace { label, call_data: self.fallback_call_data(trace), @@ -421,18 +520,17 @@ impl CallTraceDecoder { // If traced contract is a fallback contract, check if it has the decoded function. // If not, then replace call data signature with `fallback`. let mut call_data = self.decode_function_input(trace, func); - if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) { - if !fallback_functions.contains(&selector) { - if let Some(cd) = self.fallback_call_data(trace) { - call_data.signature = cd.signature; - } - } + if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) + && !fallback_functions.contains(&selector) + && let Some(cd) = self.fallback_call_data(trace) + { + call_data.signature = cd.signature; } DecodedCallTrace { label, call_data: Some(call_data), - return_data: self.decode_function_output(trace, functions), + return_data: self.decode_function_output(trace, contract_functions), } } else { DecodedCallTrace { @@ -454,10 +552,10 @@ impl CallTraceDecoder { } } - if args.is_none() { - if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..]) { - args = Some(v.iter().map(|value| self.format_value(value)).collect()); - } + if args.is_none() + && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..]) + { + args = Some(v.iter().map(|value| self.format_value(value)).collect()); } } @@ -467,7 +565,28 @@ impl CallTraceDecoder { /// Custom decoding for cheatcode inputs. fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option> { match func.name.as_str() { - "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]), + "expectRevert" => { + let decoded = match data.get(SELECTOR_LEN..) { + Some(data) => func.abi_decode_input(data).ok(), + None => None, + }; + let Some(decoded) = decoded else { + return Some(vec![self.revert_decoder.decode(data, None)]); + }; + let Some(first) = decoded.first() else { + return Some(vec![self.revert_decoder.decode(data, None)]); + }; + let expected_revert = match first { + DynSolValue::Bytes(bytes) => bytes.as_slice(), + DynSolValue::FixedBytes(word, size) => &word[..*size], + _ => return None, + }; + Some( + std::iter::once(self.revert_decoder.decode(expected_revert, None)) + .chain(decoded.iter().skip(1).map(|value| self.format_value(value))) + .collect(), + ) + } "addr" | "createWallet" | "deriveKey" | "rememberKey" => { // Redact private key in all cases Some(vec!["".to_string()]) @@ -475,20 +594,12 @@ impl CallTraceDecoder { "broadcast" | "startBroadcast" => { // Redact private key if defined // broadcast(uint256) / startBroadcast(uint256) - if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" { - Some(vec!["".to_string()]) - } else { - None - } + (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["".to_string()]) } "getNonce" => { // Redact private key if defined // getNonce(Wallet) - if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" { - Some(vec!["".to_string()]) - } else { - None - } + (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["".to_string()]) } "sign" | "signP256" => { let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; @@ -599,11 +710,10 @@ impl CallTraceDecoder { return self.default_return_data(trace); } - if trace.address == CHEATCODE_ADDRESS { - if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) - { - return Some(decoded); - } + if trace.address == CHEATCODE_ADDRESS + && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) + { + return Some(decoded); } if let Some(values) = @@ -654,7 +764,14 @@ impl CallTraceDecoder { /// The default decoded return data for a trace. fn default_return_data(&self, trace: &CallTrace) -> Option { - (!trace.success).then(|| self.revert_decoder.decode(&trace.output, Some(trace.status))) + // For calls with status None or successful status, don't decode revert data + // This is due to trace.status is derived from the revm_interpreter::InstructionResult in + // revm-inspectors status will `None` post revm 27, as `InstructionResult::Continue` does + // not exists anymore. + if trace.status.is_none() || trace.status.is_some_and(|s| s.is_ok()) { + return None; + } + (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status)) } /// Decodes an event. @@ -665,10 +782,10 @@ impl CallTraceDecoder { let events = match self.events.get(&(t0, log.topics().len() - 1)) { Some(es) => es, None => { - if let Some(identifier) = &self.signature_identifier { - if let Some(event) = identifier.identify_event(t0).await { - events.push(get_indexed_event(event, log)); - } + if let Some(identifier) = &self.signature_identifier + && let Some(event) = identifier.identify_event(t0).await + { + events.push(get_indexed_event(event, log)); } &events } @@ -706,10 +823,10 @@ impl CallTraceDecoder { .iter() .map(|log| log.raw_log.topics()) .filter(|&topics| { - if let Some(&first) = topics.first() { - if self.events.contains_key(&(first, topics.len() - 1)) { - return false; - } + if let Some(&first) = topics.first() + && self.events.contains_key(&(first, topics.len() - 1)) + { + return false; } true }) @@ -720,9 +837,9 @@ impl CallTraceDecoder { .iter() .filter(|&n| { // Ignore known addresses. - if n.trace.address == DEFAULT_CREATE2_DEPLOYER || - n.is_precompile() || - precompiles::is_known_precompile(n.trace.address, 1) + if n.trace.address == DEFAULT_CREATE2_DEPLOYER + || n.is_precompile() + || precompiles::is_known_precompile(n.trace.address, self.chain_id) { return false; } @@ -744,10 +861,10 @@ impl CallTraceDecoder { /// Pretty-prints a value. fn format_value(&self, value: &DynSolValue) -> String { - if let DynSolValue::Address(addr) = value { - if let Some(label) = self.labels.get(addr) { - return format!("{label}: [{addr}]"); - } + if let DynSolValue::Address(addr) = value + && let Some(label) = self.labels.get(addr) + { + return format!("{label}: [{addr}]"); } format_token(value) } @@ -807,12 +924,174 @@ mod tests { use super::*; use alloy_primitives::hex; + #[test] + fn test_selector_collision_resolution() { + use alloy_json_abi::Function; + use alloy_primitives::Address; + + // Create two functions with the same selector but different signatures + let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap(); + let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap(); + + // Verify they have the same selector (this is the collision) + assert_eq!(func1.selector(), func2.selector()); + + let functions = vec![func1, func2]; + + // Create a mock trace with calldata that matches func1 + let trace = CallTrace { + address: Address::from([0x12; 20]), + data: hex!("23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064").to_vec().into(), + ..Default::default() + }; + + let decoder = CallTraceDecoder::new(); + let result = decoder.select_contract_function(&functions, &trace); + + // Should return only the function that can decode the calldata (func1) + assert_eq!(result.len(), 1); + assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)"); + } + + #[test] + fn test_selector_collision_resolution_second_function() { + use alloy_json_abi::Function; + use alloy_primitives::Address; + + // Create two functions with the same selector but different signatures + let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap(); + let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap(); + + let functions = vec![func1, func2]; + + // Create a mock trace with calldata that matches func2 + let trace = CallTrace { + address: Address::from([0x12; 20]), + data: hex!("23b872dd0000000000000000000000000000000000000000000000000000000000000064") + .to_vec() + .into(), + ..Default::default() + }; + + let decoder = CallTraceDecoder::new(); + let result = decoder.select_contract_function(&functions, &trace); + + // Should return only the function that can decode the calldata (func2) + assert_eq!(result.len(), 1); + assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)"); + } + #[test] fn test_should_redact() { let decoder = CallTraceDecoder::new(); + let expected_revert_bytes4 = vec![0xde, 0xad, 0xbe, 0xef]; + let expect_revert_bytes4_data = Function::parse("expectRevert(bytes4)") + .unwrap() + .abi_encode_input(&[DynSolValue::FixedBytes( + B256::right_padding_from(expected_revert_bytes4.as_slice()), + 4, + )]) + .unwrap(); + + let expected_revert_bytes = hex!( + "08c379a000000000000000000000000000000000000000000000000000000000\ + 0000002000000000000000000000000000000000000000000000000000000000\ + 00000004626f6f6d000000000000000000000000000000000000000000000000" + ) + .to_vec(); + let expect_revert_bytes_data = Function::parse("expectRevert(bytes)") + .unwrap() + .abi_encode_input(&[DynSolValue::Bytes(expected_revert_bytes.clone())]) + .unwrap(); + + let reverter = Address::from([0x11; 20]); + let expect_revert_bytes4_address_data = Function::parse("expectRevert(bytes4,address)") + .unwrap() + .abi_encode_input(&[ + DynSolValue::FixedBytes( + B256::right_padding_from(expected_revert_bytes4.as_slice()), + 4, + ), + DynSolValue::Address(reverter), + ]) + .unwrap(); + + let count = 42_u64; + let expect_revert_bytes_count_data = Function::parse("expectRevert(bytes,uint64)") + .unwrap() + .abi_encode_input(&[ + DynSolValue::Bytes(expected_revert_bytes.clone()), + DynSolValue::Uint(alloy_primitives::U256::from(count), 64), + ]) + .unwrap(); + + let expect_revert_bytes_address_count_data = + Function::parse("expectRevert(bytes,address,uint64)") + .unwrap() + .abi_encode_input(&[ + DynSolValue::Bytes(expected_revert_bytes.clone()), + DynSolValue::Address(reverter), + DynSolValue::Uint(alloy_primitives::U256::from(count), 64), + ]) + .unwrap(); + + let expect_revert_runtime_data = expected_revert_bytes4.clone(); + // [function_signature, data, expected] let cheatcode_input_test_cases = vec![ + // Should decode the expected revert payload, not full cheatcode calldata: + ( + "expectRevert(bytes4)", + expect_revert_bytes4_data, + Some(vec![decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None)]), + ), + ( + "expectRevert(bytes)", + expect_revert_bytes_data, + Some(vec![decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None)]), + ), + ( + "expectRevert(bytes4)", + expect_revert_runtime_data.clone(), + Some(vec![ + decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None), + ]), + ), + ( + "expectRevert(bytes4,address)", + expect_revert_bytes4_address_data, + Some(vec![ + decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None), + decoder.format_value(&DynSolValue::Address(reverter)), + ]), + ), + ( + "expectRevert(bytes,uint64)", + expect_revert_bytes_count_data, + Some(vec![ + decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None), + decoder + .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)), + ]), + ), + ( + "expectRevert(bytes,address,uint64)", + expect_revert_bytes_address_count_data, + Some(vec![ + decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None), + decoder.format_value(&DynSolValue::Address(reverter)), + decoder + .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)), + ]), + ), + ( + "expectRevert()", + expect_revert_runtime_data.clone(), + Some(vec![ + decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None), + ]), + ), // Should redact private key from traces in all cases: ("addr(uint256)", vec![], Some(vec!["".to_string()])), ("createWallet(string)", vec![], Some(vec!["".to_string()])), @@ -1158,4 +1437,119 @@ mod tests { assert_eq!(result, expected, "Output case failed for: {function_signature}"); } } + + // A mock identifier that records which addresses it was asked to identify. + struct RecordingIdentifier { + queried: Vec
, + } + impl TraceIdentifier for RecordingIdentifier { + fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { + self.queried.extend(nodes.iter().map(|n| n.trace.address)); + Vec::new() + } + } + + #[test] + fn test_identify_addresses_skips_evm_precompiles() { + use foundry_evm_core::precompiles::SHA_256; + + let decoder = CallTraceDecoder::new(); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + // Standard EVM precompile flagged by the inspector. + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: SHA_256, + depth: 1, + maybe_precompile: Some(true), + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + // Standard EVM precompile NOT flagged, caught by is_known_precompile. + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: SHA_256, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 2, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + assert_eq!(identifier.queried, vec![regular_addr]); + } + + #[test] + fn test_identify_addresses_skips_tempo_precompiles() { + use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES; + + // Decoder with Tempo chain ID (4217). + let mut decoder = CallTraceDecoder::new().clone(); + decoder.chain_id = Some(4217); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + // Tempo precompile — not flagged by inspector, caught by is_known_precompile + // only when chain_id is a Tempo chain. + let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0]; + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: tempo_precompile, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + // On a Tempo chain, the Tempo precompile should be filtered out. + assert_eq!(identifier.queried, vec![regular_addr]); + } + + #[test] + fn test_identify_addresses_does_not_skip_tempo_precompiles_on_other_chains() { + use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES; + + // Decoder with Ethereum mainnet chain ID (1). + let mut decoder = CallTraceDecoder::new().clone(); + decoder.chain_id = Some(1); + + let mut arena = CallTraceArena::default(); + let regular_addr = Address::from([0x42; 20]); + arena.nodes_mut()[0].trace.address = regular_addr; + + let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0]; + arena.nodes_mut().push(CallTraceNode { + trace: CallTrace { + address: tempo_precompile, + depth: 1, + maybe_precompile: None, + ..Default::default() + }, + idx: 1, + ..Default::default() + }); + + let mut identifier = RecordingIdentifier { queried: Vec::new() }; + decoder.identify_addresses(&arena, &mut identifier); + + // On Ethereum, Tempo precompile addresses are regular contracts — should NOT be filtered. + assert_eq!(identifier.queried, vec![regular_addr, tempo_precompile]); + } } diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index 245c70e10ec57..5b0a320477fad 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -1,9 +1,14 @@ use crate::{CallTrace, DecodedCallData}; -use alloy_primitives::{hex, Address, B256, U256}; -use alloy_sol_types::{abi, sol, SolCall}; -use foundry_evm_core::precompiles::{ - BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, - RIPEMD_160, SHA_256, +use alloy_primitives::{Address, B256, U256, hex}; +use alloy_sol_types::{SolCall, abi, sol}; +use foundry_config::{Chain, NamedChain}; +use foundry_evm_core::{ + precompiles::{ + BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1, + BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, CELO_TRANSFER, EC_ADD, EC_MUL, EC_PAIRING, + EC_RECOVER, IDENTITY, MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256, + }, + tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS}, }; use itertools::Itertools; use revm_inspectors::tracing::types::DecodedCallTrace; @@ -33,108 +38,314 @@ interface Precompiles { /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success); /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h); /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value); + + // Prague BLS12-381 precompiles (EIP-2537) + /* 0x0b */ function bls12G1Add(bytes p1, bytes p2) returns (bytes result); + /* 0x0c */ function bls12G1Msm(bytes[] scalarsAndPoints) returns (bytes result); + /* 0x0d */ function bls12G2Add(bytes p1, bytes p2) returns (bytes result); + /* 0x0e */ function bls12G2Msm(bytes[] scalarsAndPoints) returns (bytes result); + /* 0x0f */ function bls12PairingCheck(bytes[] pairs) returns (bool success); + /* 0x10 */ function bls12MapFpToG1(bytes fp) returns (bytes result); + /* 0x11 */ function bls12MapFp2ToG2(bytes fp2) returns (bytes result); + + // Osaka precompiles (EIP-7212) + /* 0x100 */ function p256Verify(bytes32 hash, uint256 r, uint256 s, uint256 qx, uint256 qy) returns (bool success); } } use Precompiles::*; -macro_rules! tri { - ($e:expr) => { - match $e { - Ok(x) => x, - Err(_) => return None, - } - }; -} - -pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool { - address[..19].iter().all(|&x| x == 0) && - matches!( +pub(super) fn is_known_precompile(address: Address, chain_id: Option) -> bool { + // Standard EVM precompiles (all chains). + let is_standard = address[..19].iter().all(|&x| x == 0) + && matches!( address, - EC_RECOVER | - SHA_256 | - RIPEMD_160 | - IDENTITY | - MOD_EXP | - EC_ADD | - EC_MUL | - EC_PAIRING | - BLAKE_2F | - POINT_EVALUATION - ) + EC_RECOVER + | SHA_256 + | RIPEMD_160 + | IDENTITY + | MOD_EXP + | EC_ADD + | EC_MUL + | EC_PAIRING + | BLAKE_2F + | POINT_EVALUATION + | BLS12_G1ADD + | BLS12_G1MSM + | BLS12_G2ADD + | BLS12_G2MSM + | BLS12_PAIRING_CHECK + | BLS12_MAP_FP_TO_G1 + | BLS12_MAP_FP2_TO_G2 + | P256_VERIFY + ); + if is_standard { + return true; + } + // Tempo precompiles and TIP20 fee tokens (only on Tempo chains). + if chain_id.is_some_and(|id| Chain::from_id(id).is_tempo()) + && (TEMPO_PRECOMPILE_ADDRESSES.contains(&address) || TEMPO_TIP20_TOKENS.contains(&address)) + { + return true; + } + // Celo transfer precompile (only on Celo chains). + if chain_id.is_some_and(|id| { + matches!(Chain::from_id(id).named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) + }) && address == CELO_TRANSFER + { + return true; + } + false } /// Tries to decode a precompile call. Returns `Some` if successful. -pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option { - if !is_known_precompile(trace.address, _chain_id) { +pub(super) fn decode(trace: &CallTrace, chain_id: Option) -> Option { + if !is_known_precompile(trace.address, chain_id) { return None; } - let data = &trace.data; + for &precompile in PRECOMPILES { + if trace.address == precompile.address() { + let signature = precompile.signature(&trace.data); - let (signature, args) = match trace.address { - EC_RECOVER => { - let (sig, ecrecoverCall { hash, v, r, s }) = tri!(abi_decode_call(data)); - (sig, vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()]) - } - SHA_256 => (sha256Call::SIGNATURE, vec![data.to_string()]), - RIPEMD_160 => (ripemdCall::SIGNATURE, vec![data.to_string()]), - IDENTITY => (identityCall::SIGNATURE, vec![data.to_string()]), - MOD_EXP => (modexpCall::SIGNATURE, tri!(decode_modexp(data))), - EC_ADD => { - let (sig, ecaddCall { x1, y1, x2, y2 }) = tri!(abi_decode_call(data)); - (sig, vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()]) - } - EC_MUL => { - let (sig, ecmulCall { x1, y1, s }) = tri!(abi_decode_call(data)); - (sig, vec![x1.to_string(), y1.to_string(), s.to_string()]) + let args = precompile + .decode_call(&trace.data) + .unwrap_or_else(|_| vec![trace.data.to_string()]); + + let return_data = precompile + .decode_return(&trace.output) + .unwrap_or_else(|_| vec![trace.output.to_string()]); + let return_data = if return_data.len() == 1 { + return_data.into_iter().next().unwrap() + } else { + format!("({})", return_data.join(", ")) + }; + + return Some(DecodedCallTrace { + label: Some("PRECOMPILES".to_string()), + call_data: Some(DecodedCallData { signature: signature.to_string(), args }), + return_data: Some(return_data), + }); } - EC_PAIRING => (ecpairingCall::SIGNATURE, tri!(decode_ecpairing(data))), - BLAKE_2F => (blake2fCall::SIGNATURE, tri!(decode_blake2f(data))), - POINT_EVALUATION => (pointEvaluationCall::SIGNATURE, tri!(decode_kzg(data))), - _ => return None, - }; + } + + None +} + +pub(super) trait Precompile { + fn address(&self) -> Address; + fn signature(&self, data: &[u8]) -> &'static str; - Some(DecodedCallTrace { - label: Some("PRECOMPILES".to_string()), - call_data: Some(DecodedCallData { signature: signature.to_string(), args }), - // TODO: Decode return data too. - return_data: None, - }) + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + Ok(vec![hex::encode_prefixed(data)]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + Ok(vec![hex::encode_prefixed(data)]) + } } // Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a // convenient way to decode the data. -fn decode_modexp(data: &[u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data); - let b_size = decoder.take_offset()?; - let e_size = decoder.take_offset()?; - let m_size = decoder.take_offset()?; - let b = decoder.take_slice(b_size)?; - let e = decoder.take_slice(e_size)?; - let m = decoder.take_slice(m_size)?; - Ok(vec![ - b_size.to_string(), - e_size.to_string(), - m_size.to_string(), - hex::encode_prefixed(b), - hex::encode_prefixed(e), - hex::encode_prefixed(m), - ]) +const PRECOMPILES: &[&dyn Precompile] = &[ + &Ecrecover, + &Sha256, + &Ripemd160, + &Identity, + &ModExp, + &EcAdd, + &Ecmul, + &Ecpairing, + &Blake2f, + &PointEvaluation, + &Bls12G1Add, + &Bls12G1Msm, + &Bls12G2Add, + &Bls12G2Msm, + &Bls12PairingCheck, + &Bls12MapFpToG1, + &Bls12MapFp2ToG2, + &P256Verify, +]; + +struct Ecrecover; +impl Precompile for Ecrecover { + fn address(&self) -> Address { + EC_RECOVER + } + + fn signature(&self, _: &[u8]) -> &'static str { + ecrecoverCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ecrecoverCall { hash, v, r, s } = ecrecoverCall::abi_decode_raw(data)?; + Ok(vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = ecrecoverCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } } -fn decode_ecpairing(data: &[u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data); - let mut values = Vec::new(); - // input must be either empty or a multiple of 6 32-byte values - let mut tmp = <[&B256; 6]>::default(); - while !decoder.is_empty() { - for tmp in &mut tmp { - *tmp = decoder.take_word()?; +struct Sha256; +impl Precompile for Sha256 { + fn address(&self) -> Address { + SHA_256 + } + + fn signature(&self, _: &[u8]) -> &'static str { + sha256Call::SIGNATURE + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = sha256Call::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +struct Ripemd160; +impl Precompile for Ripemd160 { + fn address(&self) -> Address { + RIPEMD_160 + } + + fn signature(&self, _: &[u8]) -> &'static str { + ripemdCall::SIGNATURE + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = ripemdCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +struct Identity; +impl Precompile for Identity { + fn address(&self) -> Address { + IDENTITY + } + + fn signature(&self, _: &[u8]) -> &'static str { + identityCall::SIGNATURE + } +} + +struct ModExp; +impl Precompile for ModExp { + fn address(&self) -> Address { + MOD_EXP + } + + fn signature(&self, _: &[u8]) -> &'static str { + modexpCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data); + let b_size = decoder.take_offset()?; + let e_size = decoder.take_offset()?; + let m_size = decoder.take_offset()?; + let b = decoder.take_slice(b_size)?; + let e = decoder.take_slice(e_size)?; + let m = decoder.take_slice(m_size)?; + Ok(vec![ + b_size.to_string(), + e_size.to_string(), + m_size.to_string(), + hex::encode_prefixed(b), + hex::encode_prefixed(e), + hex::encode_prefixed(m), + ]) + } +} + +struct EcAdd; +impl Precompile for EcAdd { + fn address(&self) -> Address { + EC_ADD + } + + fn signature(&self, _: &[u8]) -> &'static str { + ecaddCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ecaddCall { x1, y1, x2, y2 } = ecaddCall::abi_decode_raw(data)?; + Ok(vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ecaddReturn { x, y } = ecaddCall::abi_decode_returns(data)?; + Ok(vec![x.to_string(), y.to_string()]) + } +} + +struct Ecmul; +impl Precompile for Ecmul { + fn address(&self) -> Address { + EC_MUL + } + + fn signature(&self, _: &[u8]) -> &'static str { + ecmulCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ecmulCall { x1, y1, s } = ecmulCall::abi_decode_raw(data)?; + Ok(vec![x1.to_string(), y1.to_string(), s.to_string()]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ecmulReturn { x, y } = ecmulCall::abi_decode_returns(data)?; + Ok(vec![x.to_string(), y.to_string()]) + } +} + +struct Ecpairing; +impl Precompile for Ecpairing { + fn address(&self) -> Address { + EC_PAIRING + } + + fn signature(&self, _: &[u8]) -> &'static str { + ecpairingCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data); + let mut values = Vec::new(); + // input must be either empty or a multiple of 6 32-byte values + let mut tmp = <[&B256; 6]>::default(); + while !decoder.is_empty() { + for tmp in &mut tmp { + *tmp = decoder.take_word()?; + } + values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0)))); } - values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0)))); + Ok(values) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = ecpairingCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +struct Blake2f; +impl Precompile for Blake2f { + fn address(&self) -> Address { + BLAKE_2F + } + + fn signature(&self, _: &[u8]) -> &'static str { + blake2fCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + decode_blake2f(data) } - Ok(values) } fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result> { @@ -155,31 +366,187 @@ fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result> { ]) } -fn decode_kzg(data: &[u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data); - let versioned_hash = decoder.take_word()?; - let z = decoder.take_word()?; - let y = decoder.take_word()?; - let commitment = decoder.take_slice(48)?; - let proof = decoder.take_slice(48)?; - Ok(vec![ - versioned_hash.to_string(), - z.to_string(), - y.to_string(), - hex::encode_prefixed(commitment), - hex::encode_prefixed(proof), - ]) -} +struct PointEvaluation; +impl Precompile for PointEvaluation { + fn address(&self) -> Address { + POINT_EVALUATION + } + + fn signature(&self, _: &[u8]) -> &'static str { + pointEvaluationCall::SIGNATURE + } -fn abi_decode_call(data: &[u8]) -> alloy_sol_types::Result<(&'static str, T)> { - // raw because there are no selectors here - Ok((T::SIGNATURE, T::abi_decode_raw(data)?)) + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data); + let versioned_hash = decoder.take_word()?; + let z = decoder.take_word()?; + let y = decoder.take_word()?; + let commitment = decoder.take_slice(48)?; + let proof = decoder.take_slice(48)?; + Ok(vec![ + versioned_hash.to_string(), + z.to_string(), + y.to_string(), + hex::encode_prefixed(commitment), + hex::encode_prefixed(proof), + ]) + } } fn iter_to_string, T: std::fmt::Display>(iter: I) -> String { format!("[{}]", iter.format(", ")) } +const G1_POINT_SIZE: usize = 128; +const G2_POINT_SIZE: usize = 256; +const SCALAR_SIZE: usize = 32; +const FP_SIZE: usize = 64; + +struct Bls12G1Add; +impl Precompile for Bls12G1Add { + fn address(&self) -> Address { + BLS12_G1ADD + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G1AddCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (p1, rest) = take_at_most(data, G1_POINT_SIZE); + let (p2, _) = take_at_most(rest, G1_POINT_SIZE); + Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)]) + } +} + +struct Bls12G1Msm; +impl Precompile for Bls12G1Msm { + fn address(&self) -> Address { + BLS12_G1MSM + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G1MsmCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G1_POINT_SIZE + SCALAR_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } +} + +struct Bls12G2Add; +impl Precompile for Bls12G2Add { + fn address(&self) -> Address { + BLS12_G2ADD + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G2AddCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (p1, rest) = take_at_most(data, G2_POINT_SIZE); + let (p2, _) = take_at_most(rest, G2_POINT_SIZE); + Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)]) + } +} + +struct Bls12G2Msm; +impl Precompile for Bls12G2Msm { + fn address(&self) -> Address { + BLS12_G2MSM + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12G2MsmCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G2_POINT_SIZE + SCALAR_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } +} + +struct Bls12PairingCheck; +impl Precompile for Bls12PairingCheck { + fn address(&self) -> Address { + BLS12_PAIRING_CHECK + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12PairingCheckCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let pair_size = G1_POINT_SIZE + G2_POINT_SIZE; + Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect()) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = bls12PairingCheckCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +struct Bls12MapFpToG1; +impl Precompile for Bls12MapFpToG1 { + fn address(&self) -> Address { + BLS12_MAP_FP_TO_G1 + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12MapFpToG1Call::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (fp, _) = take_at_most(data, FP_SIZE); + Ok(vec![hex::encode_prefixed(fp)]) + } +} + +struct Bls12MapFp2ToG2; +impl Precompile for Bls12MapFp2ToG2 { + fn address(&self) -> Address { + BLS12_MAP_FP2_TO_G2 + } + + fn signature(&self, _: &[u8]) -> &'static str { + bls12MapFp2ToG2Call::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let (fp2, _) = take_at_most(data, G1_POINT_SIZE); + Ok(vec![hex::encode_prefixed(fp2)]) + } +} + +struct P256Verify; +impl Precompile for P256Verify { + fn address(&self) -> Address { + P256_VERIFY + } + + fn signature(&self, _: &[u8]) -> &'static str { + p256VerifyCall::SIGNATURE + } + + fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { + let p256VerifyCall { hash, r, s, qx, qy } = p256VerifyCall::abi_decode_raw(data)?; + Ok(vec![hash.to_string(), r.to_string(), s.to_string(), qx.to_string(), qy.to_string()]) + } + + fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result> { + let ret = p256VerifyCall::abi_decode_returns(data)?; + Ok(vec![ret.to_string()]) + } +} + +fn take_at_most(data: &[u8], n: usize) -> (&[u8], &[u8]) { + let n = n.min(data.len()); + data.split_at(n) +} + #[cfg(test)] mod tests { use super::*; @@ -216,7 +583,7 @@ mod tests { 30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5 " ); - let decoded = decode_ecpairing(&data).unwrap(); + let decoded = Ecpairing.decode_call(&data).unwrap(); // 4 arrays of 6 32-byte values assert_eq!(decoded.len(), 4); } diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs index 603ff96a7e3d9..4fc43594e1b39 100644 --- a/crates/evm/traces/src/folded_stack_trace.rs +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -1,24 +1,30 @@ use alloy_primitives::hex::ToHexExt; use revm_inspectors::tracing::{ - types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder}, CallTraceArena, + types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder}, }; /// Builds a folded stack trace from a call trace arena. -pub fn build(arena: &CallTraceArena) -> Vec { - let mut fst = EvmFoldedStackTraceBuilder::default(); +pub fn build(arena: &CallTraceArena, isolate: bool) -> Vec { + let mut fst = EvmFoldedStackTraceBuilder::new(isolate); fst.process_call_node(arena.nodes(), 0); fst.build() } /// Wrapper for building a folded stack trace using EVM call trace node. -#[derive(Default)] pub struct EvmFoldedStackTraceBuilder { + /// Trace produced in isolate mode, meaning refund needs to be reversed at the depth=1 + /// frame for consistent gas values. + isolate: bool, /// Raw folded stack trace builder. fst: FoldedStackTraceBuilder, } impl EvmFoldedStackTraceBuilder { + pub fn new(isolate: bool) -> Self { + Self { isolate, fst: FoldedStackTraceBuilder::default() } + } + /// Returns the folded stack trace. pub fn build(self) -> Vec { self.fst.build() @@ -30,24 +36,40 @@ impl EvmFoldedStackTraceBuilder { let node = &nodes[idx]; let func_name = if node.trace.kind.is_any_create() { - let contract_name = node.trace.decoded.label.as_deref().unwrap_or("Contract"); + let contract_name = node + .trace + .decoded + .as_ref() + .and_then(|dc| dc.label.as_deref()) + .unwrap_or("Contract"); format!("new {contract_name}") } else { let selector = node .selector() .map(|selector| selector.encode_hex_with_prefix()) .unwrap_or_else(|| "fallback".to_string()); - let signature = - node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector); + let signature = node + .trace + .decoded + .as_ref() + .and_then(|dc| dc.call_data.as_ref()) + .map(|dc| &dc.signature) + .unwrap_or(&selector); - if let Some(label) = &node.trace.decoded.label { + if let Some(label) = node.trace.decoded.as_ref().and_then(|dc| dc.label.as_ref()) { format!("{label}.{signature}") } else { signature.clone() } }; - self.fst.enter(func_name, node.trace.gas_used as i64); + let mut gas_used = node.trace.gas_used; + let max_refund_adjust_depth = if self.isolate { 1 } else { 0 }; + if node.trace.depth <= max_refund_adjust_depth { + gas_used += node.trace.gas_refund_counter; + } + + self.fst.enter(func_name, gas_used); // Track internal function step exits to do in this call context. let mut step_exits = vec![]; @@ -87,10 +109,10 @@ impl EvmFoldedStackTraceBuilder { ) { let step = &steps[step_idx]; if let Some(decoded_step) = &step.decoded { - match decoded_step { + match decoded_step.as_ref() { DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { - let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); - self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64); + let gas_used = step.gas_remaining - steps[*step_end_idx].gas_remaining; + self.fst.enter(decoded_internal_call.func_name.clone(), gas_used); step_exits.push(*step_end_idx); } DecodedTraceStep::Line(_) => {} @@ -148,13 +170,13 @@ pub struct FoldedStackTraceBuilder { struct TraceEntry { /// Names of all functions in the call stack of this trace. names: Vec, - /// Gas consumed by this function, allowed to be negative due to refunds. - gas: i64, + /// Gas consumed by this function, not including refunds. + gas: u64, } impl FoldedStackTraceBuilder { /// Enter execution of a function call that consumes `gas`. - pub fn enter(&mut self, label: String, gas: i64) { + pub fn enter(&mut self, label: String, gas: u64) { let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default(); while self.exits > 0 { @@ -167,7 +189,7 @@ impl FoldedStackTraceBuilder { } /// Exit execution of a function call. - pub fn exit(&mut self) { + pub const fn exit(&mut self) { self.exits += 1; } @@ -179,7 +201,7 @@ impl FoldedStackTraceBuilder { /// Internal method to build the folded stack trace without subtracting gas consumed by /// the children function calls. - fn build_without_subtraction(&mut self) -> Vec { + pub fn build_without_subtraction(&mut self) -> Vec { let mut lines = Vec::new(); for TraceEntry { names, gas } in &self.traces { lines.push(format!("{} {}", names.join(";"), gas)); diff --git a/crates/evm/traces/src/identifier/etherscan.rs b/crates/evm/traces/src/identifier/etherscan.rs deleted file mode 100644 index 64b34e2363912..0000000000000 --- a/crates/evm/traces/src/identifier/etherscan.rs +++ /dev/null @@ -1,269 +0,0 @@ -use super::{IdentifiedAddress, TraceIdentifier}; -use crate::debug::ContractSources; -use alloy_primitives::Address; -use foundry_block_explorers::{ - contract::{ContractMetadata, Metadata}, - errors::EtherscanError, -}; -use foundry_common::compile::etherscan_project; -use foundry_config::{Chain, Config}; -use futures::{ - future::{join_all, Future}, - stream::{FuturesUnordered, Stream, StreamExt}, - task::{Context, Poll}, -}; -use revm_inspectors::tracing::types::CallTraceNode; -use std::{ - borrow::Cow, - collections::BTreeMap, - pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; -use tokio::time::{Duration, Interval}; - -/// A trace identifier that tries to identify addresses using Etherscan. -pub struct EtherscanIdentifier { - /// The Etherscan client - client: Arc, - /// Tracks whether the API key provides was marked as invalid - /// - /// After the first [EtherscanError::InvalidApiKey] this will get set to true, so we can - /// prevent any further attempts - invalid_api_key: Arc, - pub contracts: BTreeMap, - pub sources: BTreeMap, -} - -impl EtherscanIdentifier { - /// Creates a new Etherscan identifier with the given client - pub fn new(config: &Config, chain: Option) -> eyre::Result> { - // In offline mode, don't use Etherscan. - if config.offline { - return Ok(None); - } - let Some(config) = config.get_etherscan_config_with_chain(chain)? else { - return Ok(None); - }; - trace!(target: "traces::etherscan", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); - Ok(Some(Self { - client: Arc::new(config.into_client()?), - invalid_api_key: Arc::new(AtomicBool::new(false)), - contracts: BTreeMap::new(), - sources: BTreeMap::new(), - })) - } - - /// Goes over the list of contracts we have pulled from the traces, clones their source from - /// Etherscan and compiles them locally, for usage in the debugger. - pub async fn get_compiled_contracts(&self) -> eyre::Result { - // TODO: Add caching so we dont double-fetch contracts. - let outputs_fut = self - .contracts - .iter() - // filter out vyper files - .filter(|(_, metadata)| !metadata.is_vyper()) - .map(|(address, metadata)| async move { - sh_println!("Compiling: {} {address}", metadata.contract_name)?; - let root = tempfile::tempdir()?; - let root_path = root.path(); - let project = etherscan_project(metadata, root_path)?; - let output = project.compile()?; - - if output.has_compiler_errors() { - eyre::bail!("{output}") - } - - Ok((project, output, root)) - }) - .collect::>(); - - // poll all the futures concurrently - let outputs = join_all(outputs_fut).await; - - let mut sources: ContractSources = Default::default(); - - // construct the map - for res in outputs { - let (project, output, _root) = res?; - sources.insert(&output, project.root(), None)?; - } - - Ok(sources) - } - - fn identify_from_metadata( - &self, - address: Address, - metadata: &Metadata, - ) -> IdentifiedAddress<'static> { - let label = metadata.contract_name.clone(); - let abi = metadata.abi().ok().map(Cow::Owned); - IdentifiedAddress { - address, - label: Some(label.clone()), - contract: Some(label), - abi, - artifact_id: None, - } - } -} - -impl TraceIdentifier for EtherscanIdentifier { - fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { - if self.invalid_api_key.load(Ordering::Relaxed) || nodes.is_empty() { - return Vec::new() - } - - trace!(target: "evm::traces::etherscan", "identify {} addresses", nodes.len()); - - let mut identities = Vec::new(); - let mut fetcher = EtherscanFetcher::new( - self.client.clone(), - Duration::from_secs(1), - 5, - Arc::clone(&self.invalid_api_key), - ); - - for &node in nodes { - let address = node.trace.address; - if let Some(metadata) = self.contracts.get(&address) { - identities.push(self.identify_from_metadata(address, metadata)); - } else { - fetcher.push(address); - } - } - - let fetched_identities = foundry_common::block_on( - fetcher - .map(|(address, metadata)| { - let addr = self.identify_from_metadata(address, &metadata); - self.contracts.insert(address, metadata); - addr - }) - .collect::>>(), - ); - - identities.extend(fetched_identities); - identities - } -} - -type EtherscanFuture = - Pin)>>>; - -/// A rate limit aware Etherscan client. -/// -/// Fetches information about multiple addresses concurrently, while respecting rate limits. -struct EtherscanFetcher { - /// The Etherscan client - client: Arc, - /// The time we wait if we hit the rate limit - timeout: Duration, - /// The interval we are currently waiting for before making a new request - backoff: Option, - /// The maximum amount of requests to send concurrently - concurrency: usize, - /// The addresses we have yet to make requests for - queue: Vec
, - /// The in progress requests - in_progress: FuturesUnordered, - /// tracks whether the API key provides was marked as invalid - invalid_api_key: Arc, -} - -impl EtherscanFetcher { - fn new( - client: Arc, - timeout: Duration, - concurrency: usize, - invalid_api_key: Arc, - ) -> Self { - Self { - client, - timeout, - backoff: None, - concurrency, - queue: Vec::new(), - in_progress: FuturesUnordered::new(), - invalid_api_key, - } - } - - fn push(&mut self, address: Address) { - self.queue.push(address); - } - - fn queue_next_reqs(&mut self) { - while self.in_progress.len() < self.concurrency { - let Some(addr) = self.queue.pop() else { break }; - let client = Arc::clone(&self.client); - self.in_progress.push(Box::pin(async move { - trace!(target: "traces::etherscan", ?addr, "fetching info"); - let res = client.contract_source_code(addr).await; - (addr, res) - })); - } - } -} - -impl Stream for EtherscanFetcher { - type Item = (Address, Metadata); - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let pin = self.get_mut(); - - loop { - if let Some(mut backoff) = pin.backoff.take() { - if backoff.poll_tick(cx).is_pending() { - pin.backoff = Some(backoff); - return Poll::Pending - } - } - - pin.queue_next_reqs(); - - let mut made_progress_this_iter = false; - match pin.in_progress.poll_next_unpin(cx) { - Poll::Pending => {} - Poll::Ready(None) => return Poll::Ready(None), - Poll::Ready(Some((addr, res))) => { - made_progress_this_iter = true; - match res { - Ok(mut metadata) => { - if let Some(item) = metadata.items.pop() { - return Poll::Ready(Some((addr, item))) - } - } - Err(EtherscanError::RateLimitExceeded) => { - warn!(target: "traces::etherscan", "rate limit exceeded on attempt"); - pin.backoff = Some(tokio::time::interval(pin.timeout)); - pin.queue.push(addr); - } - Err(EtherscanError::InvalidApiKey) => { - warn!(target: "traces::etherscan", "invalid api key"); - // mark key as invalid - pin.invalid_api_key.store(true, Ordering::Relaxed); - return Poll::Ready(None) - } - Err(EtherscanError::BlockedByCloudflare) => { - warn!(target: "traces::etherscan", "blocked by cloudflare"); - // mark key as invalid - pin.invalid_api_key.store(true, Ordering::Relaxed); - return Poll::Ready(None) - } - Err(err) => { - warn!(target: "traces::etherscan", "could not get etherscan info: {:?}", err); - } - } - } - } - - if !made_progress_this_iter { - return Poll::Pending - } - } - } -} diff --git a/crates/evm/traces/src/identifier/external.rs b/crates/evm/traces/src/identifier/external.rs new file mode 100644 index 0000000000000..ee91e9e733c81 --- /dev/null +++ b/crates/evm/traces/src/identifier/external.rs @@ -0,0 +1,512 @@ +use super::{IdentifiedAddress, TraceIdentifier}; +use crate::debug::ContractSources; +use alloy_primitives::{ + Address, + map::{Entry, HashMap, HashSet}, +}; +use eyre::WrapErr; +use foundry_block_explorers::{contract::Metadata, errors::EtherscanError}; +use foundry_common::compile::etherscan_project; +use foundry_config::{Chain, Config}; +use futures::{ + future::join_all, + stream::{FuturesUnordered, Stream, StreamExt}, + task::{Context, Poll}, +}; +use revm_inspectors::tracing::types::CallTraceNode; +use serde::Deserialize; +use std::{ + borrow::Cow, + pin::Pin, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; +use tokio::time::{Duration, Interval}; + +/// A trace identifier that tries to identify addresses using Etherscan. +pub struct ExternalIdentifier { + fetchers: Vec>, + /// Cached contracts. + contracts: HashMap)>, +} + +impl ExternalIdentifier { + /// Creates a new external identifier with the given client + pub fn new(config: &Config, mut chain: Option) -> eyre::Result> { + if config.offline { + return Ok(None); + } + + let no_proxy = config.eth_rpc_no_proxy; + let config = match config.get_etherscan_config_with_chain(chain) { + Ok(Some(config)) => { + chain = config.chain; + Some(config) + } + Ok(None) => { + warn!(target: "evm::traces::external", "etherscan config not found"); + None + } + Err(err) => { + warn!(target: "evm::traces::external", ?err, "failed to get etherscan config"); + None + } + }; + + let mut fetchers = Vec::>::new(); + if let Some(chain) = chain { + debug!(target: "evm::traces::external", ?chain, "using sourcify identifier"); + fetchers.push(Arc::new(SourcifyFetcher::new(chain))); + } + if let Some(config) = config { + debug!(target: "evm::traces::external", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); + match config.into_client_with_no_proxy(no_proxy) { + Ok(client) => { + fetchers.push(Arc::new(EtherscanFetcher::new(client))); + } + Err(err) => { + warn!(target: "evm::traces::external", ?err, "failed to create etherscan client"); + } + } + } + if fetchers.is_empty() { + debug!(target: "evm::traces::external", "no fetchers enabled"); + return Ok(None); + } + + Ok(Some(Self { fetchers, contracts: Default::default() })) + } + + /// Goes over the list of contracts we have pulled from the traces, clones their source from + /// Etherscan and compiles them locally, for usage in the debugger. + pub async fn get_compiled_contracts(&self) -> eyre::Result { + // Collect contract info upfront so we can reference it in error messages + let contracts_info: Vec<_> = self + .contracts + .iter() + // filter out vyper files and contracts without metadata + .filter_map(|(addr, (_, metadata))| { + if let Some(metadata) = metadata.as_ref() + && !metadata.is_vyper() + { + Some((*addr, metadata)) + } else { + None + } + }) + .collect(); + + let outputs_fut = contracts_info + .iter() + .map(|(addr, metadata)| async move { + sh_println!("Compiling: {} {addr}", metadata.contract_name)?; + let root = tempfile::tempdir()?; + let root_path = root.path(); + let project = etherscan_project(metadata, root_path)?; + let output = project.compile()?; + if output.has_compiler_errors() { + eyre::bail!("{output}") + } + + Ok((project, output, root)) + }) + .collect::>(); + + // poll all the futures concurrently + let outputs = join_all(outputs_fut).await; + + let mut sources: ContractSources = Default::default(); + + // construct the map + for (idx, res) in outputs.into_iter().enumerate() { + let (addr, metadata) = &contracts_info[idx]; + let name = &metadata.contract_name; + let (project, output, _) = + res.wrap_err_with(|| format!("Failed to compile contract {name} at {addr}"))?; + sources + .insert(&output, project.root(), None) + .wrap_err_with(|| format!("Failed to insert contract {name} at {addr}"))?; + } + + Ok(sources) + } + + fn identify_from_metadata( + &self, + address: Address, + metadata: &Metadata, + ) -> IdentifiedAddress<'static> { + let label = metadata.contract_name.clone(); + let abi = metadata.abi().ok().map(Cow::Owned); + IdentifiedAddress { + address, + label: Some(label.clone()), + contract: Some(label), + abi, + artifact_id: None, + } + } +} + +impl TraceIdentifier for ExternalIdentifier { + fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { + if nodes.is_empty() { + return Vec::new(); + } + + trace!(target: "evm::traces::external", "identify {} addresses", nodes.len()); + + let mut identities = Vec::new(); + let mut to_fetch = HashSet::new(); + + // Check cache first. + for &node in nodes { + let address = node.trace.address; + if let Some((_, metadata)) = self.contracts.get(&address) { + if let Some(metadata) = metadata { + identities.push(self.identify_from_metadata(address, metadata)); + } else { + // Do nothing. We know that this contract was not verified. + } + } else { + to_fetch.insert(address); + } + } + + if to_fetch.is_empty() { + return identities; + } + trace!(target: "evm::traces::external", "fetching {} addresses", to_fetch.len()); + + let to_fetch = to_fetch.into_iter().collect::>(); + let fetchers = + self.fetchers.iter().map(|fetcher| ExternalFetcher::new(fetcher.clone(), &to_fetch)); + let fetched_identities = foundry_common::block_on( + futures::stream::select_all(fetchers) + .filter_map(|(address, value)| { + let addr = value + .1 + .as_ref() + .map(|metadata| self.identify_from_metadata(address, metadata)); + match self.contracts.entry(address) { + Entry::Occupied(mut occupied_entry) => { + let old = occupied_entry.get(); + // Only override when the new result is strictly better: + // - new has metadata and old doesn't, OR + // - both have metadata but new is from Etherscan and old is not. + // Never downgrade a successful lookup to None. + let should_replace = match (&old.1, &value.1) { + (None, Some(_)) => true, + (Some(_), None) => false, + _ => { + matches!(value.0, FetcherKind::Etherscan) + && !matches!(old.0, FetcherKind::Etherscan) + } + }; + if should_replace { + occupied_entry.insert(value); + } + } + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(value); + } + } + async move { addr } + }) + .collect::>>(), + ); + trace!(target: "evm::traces::external", "fetched {} addresses: {fetched_identities:#?}", fetched_identities.len()); + + identities.extend(fetched_identities); + identities + } +} + +type FetchFuture = + Pin, EtherscanError>)>>>; + +/// A rate limit aware fetcher. +/// +/// Fetches information about multiple addresses concurrently, while respecting rate limits. +struct ExternalFetcher { + /// The fetcher + fetcher: Arc, + /// The time we wait if we hit the rate limit + timeout: Duration, + /// The interval we are currently waiting for before making a new request + backoff: Option, + /// The maximum amount of requests to send concurrently + concurrency: usize, + /// The addresses we have yet to make requests for + queue: Vec
, + /// The in progress requests + in_progress: FuturesUnordered, +} + +impl ExternalFetcher { + fn new(fetcher: Arc, to_fetch: &[Address]) -> Self { + Self { + timeout: fetcher.timeout(), + backoff: None, + concurrency: fetcher.concurrency(), + fetcher, + queue: to_fetch.to_vec(), + in_progress: FuturesUnordered::new(), + } + } + + fn queue_next_reqs(&mut self) { + while self.in_progress.len() < self.concurrency { + let Some(addr) = self.queue.pop() else { break }; + let fetcher = Arc::clone(&self.fetcher); + self.in_progress.push(Box::pin(async move { + trace!(target: "evm::traces::external", ?addr, "fetching info"); + let res = fetcher.fetch(addr).await; + (addr, res) + })); + } + } +} + +impl Stream for ExternalFetcher { + type Item = (Address, (FetcherKind, Option)); + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let pin = self.get_mut(); + + let _guard = + info_span!("evm::traces::external", kind=?pin.fetcher.kind(), "ExternalFetcher") + .entered(); + + if pin.fetcher.invalid_api_key().load(Ordering::Relaxed) { + return Poll::Ready(None); + } + + loop { + if let Some(mut backoff) = pin.backoff.take() + && backoff.poll_tick(cx).is_pending() + { + pin.backoff = Some(backoff); + return Poll::Pending; + } + + pin.queue_next_reqs(); + + let mut made_progress_this_iter = false; + match pin.in_progress.poll_next_unpin(cx) { + Poll::Pending => {} + Poll::Ready(None) => return Poll::Ready(None), + Poll::Ready(Some((addr, res))) => { + made_progress_this_iter = true; + match res { + Ok(metadata) => { + return Poll::Ready(Some((addr, (pin.fetcher.kind(), metadata)))); + } + Err(EtherscanError::ContractCodeNotVerified(_)) => { + return Poll::Ready(Some((addr, (pin.fetcher.kind(), None)))); + } + Err(EtherscanError::RateLimitExceeded) => { + warn!(target: "evm::traces::external", "rate limit exceeded on attempt"); + pin.backoff = Some(tokio::time::interval(pin.timeout)); + pin.queue.push(addr); + } + Err(EtherscanError::InvalidApiKey) => { + warn!(target: "evm::traces::external", "invalid api key"); + // mark key as invalid + pin.fetcher.invalid_api_key().store(true, Ordering::Relaxed); + return Poll::Ready(None); + } + Err(EtherscanError::BlockedByCloudflare) => { + warn!(target: "evm::traces::external", "blocked by cloudflare"); + // mark key as invalid + pin.fetcher.invalid_api_key().store(true, Ordering::Relaxed); + return Poll::Ready(None); + } + Err(err) => { + warn!(target: "evm::traces::external", ?err, "could not get info"); + // Cache the failure so we don't re-fetch on subsequent arenas. + return Poll::Ready(Some((addr, (pin.fetcher.kind(), None)))); + } + } + } + } + + if !made_progress_this_iter { + return Poll::Pending; + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FetcherKind { + Etherscan, + Sourcify, +} + +#[async_trait::async_trait] +trait ExternalFetcherT: Send + Sync { + fn kind(&self) -> FetcherKind; + fn timeout(&self) -> Duration; + fn concurrency(&self) -> usize; + fn invalid_api_key(&self) -> &AtomicBool; + async fn fetch(&self, address: Address) -> Result, EtherscanError>; +} + +struct EtherscanFetcher { + client: foundry_block_explorers::Client, + invalid_api_key: AtomicBool, +} + +impl EtherscanFetcher { + const fn new(client: foundry_block_explorers::Client) -> Self { + Self { client, invalid_api_key: AtomicBool::new(false) } + } +} + +#[async_trait::async_trait] +impl ExternalFetcherT for EtherscanFetcher { + fn kind(&self) -> FetcherKind { + FetcherKind::Etherscan + } + + fn timeout(&self) -> Duration { + Duration::from_secs(1) + } + + fn concurrency(&self) -> usize { + 5 + } + + fn invalid_api_key(&self) -> &AtomicBool { + &self.invalid_api_key + } + + async fn fetch(&self, address: Address) -> Result, EtherscanError> { + self.client.contract_source_code(address).await.map(|mut metadata| metadata.items.pop()) + } +} + +struct SourcifyFetcher { + client: reqwest::Client, + url: String, + invalid_api_key: AtomicBool, +} + +impl SourcifyFetcher { + fn new(chain: Chain) -> Self { + Self { + client: reqwest::Client::new(), + url: format!("https://sourcify.dev/server/v2/contract/{}", chain.id()), + invalid_api_key: AtomicBool::new(false), + } + } +} + +#[async_trait::async_trait] +impl ExternalFetcherT for SourcifyFetcher { + fn kind(&self) -> FetcherKind { + FetcherKind::Sourcify + } + + fn timeout(&self) -> Duration { + Duration::from_secs(1) + } + + fn concurrency(&self) -> usize { + 5 + } + + fn invalid_api_key(&self) -> &AtomicBool { + &self.invalid_api_key + } + + async fn fetch(&self, address: Address) -> Result, EtherscanError> { + let url = format!("{url}/{address}?fields=abi,compilation", url = self.url); + let response = self + .client + .get(url) + .send() + .await + .map_err(|e| EtherscanError::Unknown(e.to_string()))?; + let code = response.status(); + match code.as_u16() { + // Not verified. + 404 => return Err(EtherscanError::ContractCodeNotVerified(address)), + // Too many requests. + 429 => return Err(EtherscanError::RateLimitExceeded), + _ => {} + } + let response: SourcifyResponse = + response.json().await.map_err(|e| EtherscanError::Unknown(e.to_string()))?; + trace!(target: "evm::traces::external", "Sourcify response for {address}: {response:#?}"); + match response { + SourcifyResponse::Success(metadata) => Ok(Some(metadata.into())), + SourcifyResponse::Error(error) => Err(EtherscanError::Unknown(format!("{error:#?}"))), + } + } +} + +/// Sourcify API response for `/v2/contract/{chainId}/{address}`. +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +enum SourcifyResponse { + Success(SourcifyMetadata), + Error(SourcifyError), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +#[expect(dead_code)] // Used in Debug. +struct SourcifyError { + custom_code: String, + message: String, + error_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyMetadata { + #[serde(default)] + abi: Option>, + #[serde(default)] + compilation: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Compilation { + #[serde(default)] + compiler_version: String, + #[serde(default)] + name: String, +} + +impl From for Metadata { + fn from(metadata: SourcifyMetadata) -> Self { + let SourcifyMetadata { abi, compilation } = metadata; + let (contract_name, compiler_version) = compilation + .map(|c| (c.name, c.compiler_version)) + .unwrap_or_else(|| (String::new(), String::new())); + // Defaulted fields may be fetched from sourcify but we don't make use of them. + Self { + source_code: foundry_block_explorers::contract::SourceCodeMetadata::Sources( + Default::default(), + ), + abi: Box::::from(abi.unwrap_or_default()).into(), + contract_name, + compiler_version, + optimization_used: 0, + runs: 0, + constructor_arguments: Default::default(), + evm_version: String::new(), + library: String::new(), + license_type: String::new(), + proxy: 0, + implementation: None, + swarm_source: String::new(), + } + } +} diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index f0bb8cd9d7d76..05dab2ba44edd 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -1,7 +1,8 @@ use super::{IdentifiedAddress, TraceIdentifier}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::JsonAbi; -use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact}; +use alloy_primitives::{Address, Bytes, map::HashMap}; +use foundry_common::contracts::{ContractsByArtifact, bytecode_diff_score}; use foundry_compilers::ArtifactId; use revm_inspectors::tracing::types::CallTraceNode; use std::borrow::Cow; @@ -12,11 +13,12 @@ pub struct LocalTraceIdentifier<'a> { known_contracts: &'a ContractsByArtifact, /// Vector of pairs of artifact ID and the runtime code length of the given artifact. ordered_ids: Vec<(&'a ArtifactId, usize)>, + /// The contracts bytecode. + contracts_bytecode: Option<&'a HashMap>, } impl<'a> LocalTraceIdentifier<'a> { /// Creates a new local trace identifier. - #[inline] pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { let mut ordered_ids = known_contracts .iter() @@ -24,12 +26,17 @@ impl<'a> LocalTraceIdentifier<'a> { .map(|(id, bytecode)| (id, bytecode.len())) .collect::>(); ordered_ids.sort_by_key(|(_, len)| *len); - Self { known_contracts, ordered_ids } + Self { known_contracts, ordered_ids, contracts_bytecode: None } + } + + pub const fn with_bytecodes(mut self, contracts_bytecode: &'a HashMap) -> Self { + self.contracts_bytecode = Some(contracts_bytecode); + self } /// Returns the known contracts. #[inline] - pub fn contracts(&self) -> &'a ContractsByArtifact { + pub const fn contracts(&self) -> &'a ContractsByArtifact { self.known_contracts } @@ -48,9 +55,9 @@ impl<'a> LocalTraceIdentifier<'a> { let contract = self.known_contracts.get(id)?; // Select bytecodes to compare based on `is_creation` flag. let (contract_bytecode, current_bytecode) = if is_creation { - (contract.bytecode(), creation_code) + (contract.bytecode_without_placeholders(), creation_code) } else { - (contract.deployed_bytecode(), runtime_code) + (contract.deployed_bytecode_without_placeholders(), runtime_code) }; if let Some(bytecode) = contract_bytecode { @@ -67,7 +74,7 @@ impl<'a> LocalTraceIdentifier<'a> { } } - let score = bytecode_diff_score(bytecode, current_bytecode); + let score = bytecode_diff_score(&bytecode, current_bytecode); if score == 0.0 { trace!(target: "evm::traces::local", "found exact match"); return Some((id, &contract.abi)); @@ -118,11 +125,7 @@ impl<'a> LocalTraceIdentifier<'a> { // Note: the diff score can be inaccurate for small contracts so we're using a relatively // high threshold here to avoid filtering out too many contracts. - if min_score < 0.85 { - min_score_id - } else { - None - } + if min_score < 0.85 { min_score_id } else { None } } /// Returns the index of the artifact with the given code length, or the index of the first @@ -161,7 +164,18 @@ impl TraceIdentifier for LocalTraceIdentifier<'_> { let _span = trace_span!(target: "evm::traces::local", "identify", %address).entered(); - let (id, abi) = self.identify_code(runtime_code?, creation_code?)?; + // In order to identify the addresses, we need at least the runtime code. It can be + // obtained from the trace itself (if it's a CREATE* call), or from the fetched + // bytecodes. + let (runtime_code, creation_code) = match (runtime_code, creation_code) { + (Some(runtime_code), Some(creation_code)) => (runtime_code, creation_code), + (Some(runtime_code), _) => (runtime_code, &[] as &[u8]), + _ => { + let code = self.contracts_bytecode?.get(&address)?; + (code.as_ref(), &[] as &[u8]) + } + }; + let (id, abi) = self.identify_code(runtime_code, creation_code)?; trace!(target: "evm::traces::local", id=%id.identifier(), "identified"); Some(IdentifiedAddress { diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 0654b5087764f..40dd968527d68 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -1,5 +1,5 @@ use alloy_json_abi::JsonAbi; -use alloy_primitives::Address; +use alloy_primitives::{Address, Bytes, map::HashMap}; use foundry_common::ContractsByArtifact; use foundry_compilers::ArtifactId; use foundry_config::{Chain, Config}; @@ -9,13 +9,14 @@ use std::borrow::Cow; mod local; pub use local::LocalTraceIdentifier; -mod etherscan; -pub use etherscan::EtherscanIdentifier; +mod external; +pub use external::ExternalIdentifier; mod signatures; pub use signatures::{SignaturesCache, SignaturesIdentifier}; /// An address identified by a [`TraceIdentifier`]. +#[derive(Debug)] pub struct IdentifiedAddress<'a> { /// The address. pub address: Address, @@ -41,8 +42,8 @@ pub trait TraceIdentifier { pub struct TraceIdentifiers<'a> { /// The local trace identifier. pub local: Option>, - /// The optional Etherscan trace identifier. - pub etherscan: Option, + /// The optional external trace identifier. + pub external: Option, } impl Default for TraceIdentifiers<'_> { @@ -53,6 +54,10 @@ impl Default for TraceIdentifiers<'_> { impl TraceIdentifier for TraceIdentifiers<'_> { fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { + if nodes.is_empty() { + return Vec::new(); + } + let mut identities = Vec::with_capacity(nodes.len()); if let Some(local) = &mut self.local { identities.extend(local.identify_addresses(nodes)); @@ -60,8 +65,8 @@ impl TraceIdentifier for TraceIdentifiers<'_> { return identities; } } - if let Some(etherscan) = &mut self.etherscan { - identities.extend(etherscan.identify_addresses(nodes)); + if let Some(external) = &mut self.external { + identities.extend(external.identify_addresses(nodes)); } identities } @@ -70,7 +75,7 @@ impl TraceIdentifier for TraceIdentifiers<'_> { impl<'a> TraceIdentifiers<'a> { /// Creates a new, empty instance. pub const fn new() -> Self { - Self { local: None, etherscan: None } + Self { local: None, external: None } } /// Sets the local identifier. @@ -79,14 +84,25 @@ impl<'a> TraceIdentifiers<'a> { self } - /// Sets the etherscan identifier. - pub fn with_etherscan(mut self, config: &Config, chain: Option) -> eyre::Result { - self.etherscan = EtherscanIdentifier::new(config, chain)?; + /// Sets the local identifier. + pub fn with_local_and_bytecodes( + mut self, + known_contracts: &'a ContractsByArtifact, + contracts_bytecode: &'a HashMap, + ) -> Self { + self.local = + Some(LocalTraceIdentifier::new(known_contracts).with_bytecodes(contracts_bytecode)); + self + } + + /// Sets the external identifier. + pub fn with_external(mut self, config: &Config, chain: Option) -> eyre::Result { + self.external = ExternalIdentifier::new(config, chain)?; Ok(self) } /// Returns `true` if there are no set identifiers. - pub fn is_empty(&self) -> bool { - self.local.is_none() && self.etherscan.is_none() + pub const fn is_empty(&self) -> bool { + self.local.is_none() && self.external.is_none() } } diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 549430cf67df4..3481620fa7c75 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -1,5 +1,5 @@ use alloy_json_abi::{Error, Event, Function, JsonAbi}; -use alloy_primitives::{map::HashMap, Selector, B256}; +use alloy_primitives::{B256, Selector, map::HashMap}; use eyre::Result; use foundry_common::{ abi::{get_error, get_event, get_func}, @@ -59,11 +59,15 @@ impl From<&SignaturesCache> for SignaturesDiskCache { let (functions, errors, events) = value.signatures.iter().fold( (BTreeMap::new(), BTreeMap::new(), BTreeMap::new()), |mut acc, (kind, signature)| { - let value = signature.clone().unwrap_or_default(); - match *kind { - SelectorKind::Function(selector) => _ = acc.0.insert(selector, value), - SelectorKind::Error(selector) => _ = acc.1.insert(selector, value), - SelectorKind::Event(selector) => _ = acc.2.insert(selector, value), + // Only persist resolved signatures. Unknown selectors (None) are kept + // in-memory for session dedup but not written to disk, so they can be + // re-queried in future sessions once the signature database is updated. + if let Some(value) = signature.clone() { + match *kind { + SelectorKind::Function(selector) => _ = acc.0.insert(selector, value), + SelectorKind::Error(selector) => _ = acc.1.insert(selector, value), + SelectorKind::Event(selector) => _ = acc.2.insert(selector, value), + } } acc }, @@ -83,7 +87,7 @@ impl Serialize for SignaturesCache { impl SignaturesCache { /// Loads the cache from a file. - #[instrument(target = "evm::traces")] + #[instrument(target = "evm::traces", name = "SignaturesCache::load")] pub fn load(path: &Path) -> Self { trace!(target: "evm::traces", ?path, "reading signature cache"); fs::read_json_file(path) @@ -94,12 +98,12 @@ impl SignaturesCache { } /// Saves the cache to a file. - #[instrument(target = "evm::traces", skip(self))] + #[instrument(target = "evm::traces", name = "SignaturesCache::save", skip(self))] pub fn save(&self, path: &Path) { - if let Some(parent) = path.parent() { - if let Err(err) = std::fs::create_dir_all(parent) { - warn!(target: "evm::traces", ?parent, %err, "failed to create cache"); - } + if let Some(parent) = path.parent() + && let Err(err) = std::fs::create_dir_all(parent) + { + warn!(target: "evm::traces", ?parent, %err, "failed to create cache"); } if let Err(err) = fs::write_json_file(path, self) { warn!(target: "evm::traces", %err, "failed to flush signature cache"); @@ -149,9 +153,12 @@ impl SignaturesCache { /// An identifier that tries to identify functions and events using signatures found at /// `https://openchain.xyz` or a local cache. #[derive(Clone, Debug)] -pub struct SignaturesIdentifier { +pub struct SignaturesIdentifier(Arc); + +#[derive(Debug)] +struct SignaturesIdentifierInner { /// Cached selectors for functions, events and custom errors. - cache: Arc>, + cache: RwLock, /// Location where to save the signature cache. cache_path: Option, /// The OpenChain client to fetch signatures from. `None` if disabled on construction. @@ -174,7 +181,7 @@ impl SignaturesIdentifier { /// - `cache_dir` is the cache directory to store the signatures. /// - `offline` disables the OpenChain client. pub fn new_with(cache_dir: Option<&Path>, offline: bool) -> Result { - let client = if !offline { Some(OpenChainClient::new()?) } else { None }; + let client = if offline { None } else { Some(OpenChainClient::new()?) }; let (cache, cache_path) = if let Some(cache_dir) = cache_dir { let path = cache_dir.join("signatures"); let cache = SignaturesCache::load(&path); @@ -182,14 +189,16 @@ impl SignaturesIdentifier { } else { Default::default() }; - Ok(Self { cache: Arc::new(RwLock::new(cache)), cache_path, client }) + Ok(Self(Arc::new(SignaturesIdentifierInner { + cache: RwLock::new(cache), + cache_path, + client, + }))) } /// Saves the cache to the file system. pub fn save(&self) { - if let Some(path) = &self.cache_path { - foundry_compilers::utils::RuntimeOrHandle::new().block_on(self.cache.read()).save(path); - } + self.0.save(); } /// Identifies `Function`s. @@ -238,20 +247,20 @@ impl SignaturesIdentifier { } trace!(target: "evm::traces", ?selectors, "identifying selectors"); - let mut cache_r = self.cache.read().await; - if let Some(client) = &self.client { + let mut cache_r = self.0.cache.read().await; + if let Some(client) = &self.0.client { let query = selectors.iter().copied().filter(|v| !cache_r.contains_key(v)).collect::>(); if !query.is_empty() { drop(cache_r); - let mut cache_w = self.cache.write().await; + let mut cache_w = self.0.cache.write().await; if let Ok(res) = client.decode_selectors(&query).await { for (selector, signatures) in std::iter::zip(query, res) { cache_w.signatures.insert(selector, signatures.into_iter().next()); } } drop(cache_w); - cache_r = self.cache.read().await; + cache_r = self.0.cache.read().await; } } selectors.iter().map(|selector| cache_r.get(selector).unwrap_or_default()).collect() @@ -267,8 +276,51 @@ impl SignaturesIdentifier { } } -impl Drop for SignaturesIdentifier { +impl SignaturesIdentifierInner { + fn save(&self) { + // We only identify new signatures if the client is enabled. + if let Some(path) = &self.cache_path + && self.client.is_some() + { + self.cache + .try_read() + .expect("SignaturesIdentifier cache is locked while attempting to save") + .save(path); + } + } +} + +impl Drop for SignaturesIdentifierInner { fn drop(&mut self) { self.save(); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unknown_signatures_not_persisted_to_disk() { + let known_selector = SelectorKind::Function(Selector::from([0xaa, 0xbb, 0xcc, 0xdd])); + let unknown_selector = SelectorKind::Error(Selector::from([0x11, 0x22, 0x33, 0x44])); + + let mut cache = SignaturesCache::default(); + cache.signatures.insert(known_selector, Some("transfer(address,uint256)".into())); + cache.signatures.insert(unknown_selector, None); + + // Verify both are in memory. + assert!(cache.contains_key(&known_selector)); + assert!(cache.contains_key(&unknown_selector)); + + // Round-trip through the disk format. + let disk: SignaturesDiskCache = (&cache).into(); + let reloaded = SignaturesCache::from(disk); + + // Known signature survives the round-trip. + assert_eq!(reloaded.get(&known_selector), Some(Some("transfer(address,uint256)".into()))); + // Unknown signature is gone — it will be re-queried next session. + assert_eq!(reloaded.get(&unknown_selector), None); + assert!(!reloaded.contains_key(&unknown_selector)); + } +} diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index ab024a2c1f530..f9bf9c5e8471a 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -3,7 +3,7 @@ //! EVM trace identifying and decoding. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -17,8 +17,8 @@ use foundry_common::{ }; use revm::bytecode::opcode::OpCode; use revm_inspectors::tracing::{ - types::{DecodedTraceStep, TraceMemberOrder}, OpcodeFilter, + types::{DecodedTraceStep, TraceMemberOrder}, }; use serde::{Deserialize, Serialize}; use std::{ @@ -30,12 +30,12 @@ use std::{ use alloy_primitives::map::HashMap; pub use revm_inspectors::tracing::{ + CallTraceArena, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, StackSnapshotType, + TraceWriter, TracingInspector, TracingInspectorConfig, types::{ CallKind, CallLog, CallTrace, CallTraceNode, DecodedCallData, DecodedCallLog, DecodedCallTrace, }, - CallTraceArena, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, StackSnapshotType, - TraceWriter, TracingInspector, TracingInspectorConfig, }; /// Call trace address identifiers. @@ -52,6 +52,8 @@ pub use debug::DebugTraceIdentifier; pub mod folded_stack_trace; +pub mod backtrace; + pub type Traces = Vec<(TraceKind, SparsedTraceArena)>; /// Trace arena keeping track of ignored trace items. @@ -86,7 +88,7 @@ impl SparsedTraceArena { .chain(nodes[node_idx].ordering.clone().into_iter().map(Some)) .enumerate(); - let mut iternal_calls = Vec::new(); + let mut internal_calls = Vec::new(); let mut items_to_remove = BTreeSet::new(); for (item_idx, item) in items { if let Some(end_node) = ignored.get(&(node_idx, item_idx)) { @@ -108,17 +110,17 @@ impl SparsedTraceArena { } // we only remove decoded internal calls if they did not start/pause tracing Some(TraceMemberOrder::Step(step_idx)) => { - // If this is an internal call beginning, track it in `iternal_calls` - if let Some(DecodedTraceStep::InternalCall(_, end_step_idx)) = - &nodes[node_idx].trace.steps[step_idx].decoded + // If this is an internal call beginning, track it in `internal_calls` + if let Some(decoded) = &nodes[node_idx].trace.steps[step_idx].decoded + && let DecodedTraceStep::InternalCall(_, end_step_idx) = &**decoded { - iternal_calls.push((item_idx, remove, *end_step_idx)); + internal_calls.push((item_idx, remove, *end_step_idx)); // we decide if we should remove it later remove = false; } // Handle ends of internal calls - iternal_calls.retain(|(start_item_idx, remove_start, end_step_idx)| { - if *end_step_idx != step_idx { + internal_calls.retain(|(start_item_idx, remove_start, end_idx)| { + if *end_idx != step_idx { return true; } // only remove start if end should be removed as well @@ -138,10 +140,11 @@ impl SparsedTraceArena { items_to_remove.insert(item_idx); } - if let Some((end_node, end_step_idx)) = cur_ignore_end { - if node_idx == *end_node && item_idx == *end_step_idx { - *cur_ignore_end = None; - } + if let Some((end_node, end_step_idx)) = cur_ignore_end + && node_idx == *end_node + && item_idx == *end_step_idx + { + *cur_ignore_end = None; } } @@ -184,6 +187,15 @@ pub fn render_trace_arena(arena: &SparsedTraceArena) -> String { render_trace_arena_inner(arena, false, false) } +/// Prunes trace depth if depth is provided as an argument +pub fn prune_trace_depth(arena: &mut CallTraceArena, depth: usize) { + for node in arena.nodes_mut() { + if node.trace.depth >= depth { + node.ordering.clear(); + } + } +} + /// Render a collection of call traces to a string optionally including contract creation bytecodes /// and in JSON format. pub fn render_trace_arena_inner( @@ -192,7 +204,7 @@ pub fn render_trace_arena_inner( with_storage_changes: bool, ) -> String { if shell::is_json() { - return serde_json::to_string(&arena.resolve_arena()).expect("Failed to write traces"); + return serde_json::to_string(&arena.resolve_arena()).expect("Failed to serialize traces"); } let mut w = TraceWriter::new(Vec::::new()) @@ -204,7 +216,7 @@ pub fn render_trace_arena_inner( String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } -fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { +const fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { match choice { shell::ColorChoice::Auto => revm_inspectors::ColorChoice::Auto, shell::ColorChoice::Always => revm_inspectors::ColorChoice::Always, @@ -225,7 +237,7 @@ impl TraceKind { /// /// [`Deployment`]: TraceKind::Deployment #[must_use] - pub fn is_deployment(self) -> bool { + pub const fn is_deployment(self) -> bool { matches!(self, Self::Deployment) } @@ -233,7 +245,7 @@ impl TraceKind { /// /// [`Setup`]: TraceKind::Setup #[must_use] - pub fn is_setup(self) -> bool { + pub const fn is_setup(self) -> bool { matches!(self, Self::Setup) } @@ -241,7 +253,7 @@ impl TraceKind { /// /// [`Execution`]: TraceKind::Execution #[must_use] - pub fn is_execution(self) -> bool { + pub const fn is_execution(self) -> bool { matches!(self, Self::Execution) } } @@ -293,6 +305,10 @@ pub enum TraceMode { None, /// Simple call trace, no steps tracing required. Call, + /// Call trace with steps tracing for JUMP and JUMPDEST opcodes. + /// + /// Does not enable tracking memory or stack snapshots. + Steps, /// Call trace with tracing for JUMP and JUMPDEST opcode steps. /// /// Used for internal functions identification. Does not track memory snapshots. @@ -305,7 +321,10 @@ pub enum TraceMode { /// /// Used by debugger. Debug, - /// Debug trace with storage changes. + /// Step trace with storage change recording. + /// + /// Records JUMP/JUMPDEST steps (like `Steps`) plus storage diffs on SLOAD/SSTORE. + /// Does not enable memory/stack snapshots or unfiltered opcode recording. RecordStateDiff, } @@ -318,6 +337,10 @@ impl TraceMode { matches!(self, Self::Call) } + pub const fn is_steps(self) -> bool { + matches!(self, Self::Steps) + } + pub const fn is_jump_simple(self) -> bool { matches!(self, Self::JumpSimple) } @@ -335,11 +358,7 @@ impl TraceMode { } pub fn with_debug(self, yes: bool) -> Self { - if yes { - std::cmp::max(self, Self::Debug) - } else { - self - } + if yes { std::cmp::max(self, Self::Debug) } else { self } } pub fn with_decode_internal(self, mode: InternalTraceMode) -> Self { @@ -347,18 +366,16 @@ impl TraceMode { } pub fn with_state_changes(self, yes: bool) -> Self { - if yes { - std::cmp::max(self, Self::RecordStateDiff) - } else { - self - } + if yes { std::cmp::max(self, Self::RecordStateDiff) } else { self } } pub fn with_verbosity(self, verbosity: u8) -> Self { - if verbosity >= 3 { - std::cmp::max(self, Self::Call) - } else { - self + match verbosity { + 0..3 => self, + 3..=4 => std::cmp::max(self, Self::Call), + // Enable step recording and state diff recording when verbosity is 5 or higher. + // This includes backtraces (JUMP/JUMPDEST steps) and storage changes. + _ => std::cmp::max(self, Self::RecordStateDiff), } } @@ -366,23 +383,128 @@ impl TraceMode { if self.is_none() { None } else { + // RecordStateDiff is Steps + state diff recording, not Debug + state diff. + // It should not enable memory/stack snapshots. + // State diff recording requires all opcodes (no filter) since it needs + // SLOAD/SSTORE steps, not just JUMP/JUMPDEST. + let effective = if self.record_state_diff() { Self::Steps } else { self }; TracingInspectorConfig { - record_steps: self >= Self::JumpSimple, - record_memory_snapshots: self >= Self::Jump, - record_stack_snapshots: if self >= Self::JumpSimple { + record_steps: self >= Self::Steps, + record_memory_snapshots: effective >= Self::Jump, + record_stack_snapshots: if effective > Self::Steps { StackSnapshotType::Full } else { StackSnapshotType::None }, record_logs: true, record_state_diff: self.record_state_diff(), - record_returndata_snapshots: self.is_debug(), - record_opcodes_filter: (self.is_jump() || self.is_jump_simple()) - .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), + record_returndata_snapshots: effective.is_debug(), + // State diff needs all opcodes recorded to capture SLOAD/SSTORE. + record_opcodes_filter: if self.record_state_diff() { + None + } else { + (effective.is_steps() || effective.is_jump() || effective.is_jump_simple()) + .then(|| { + OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST) + }) + }, exclude_precompile_calls: false, - record_immediate_bytes: self.is_debug(), + record_immediate_bytes: effective.is_debug(), } .into() } } } + +#[cfg(test)] +mod tests { + use super::*; + + // -- TraceMode::with_verbosity level tests -- + + #[test] + fn verbosity_0_through_2_is_noop() { + for v in 0..=2 { + assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::None, "v={v}"); + assert_eq!(TraceMode::Call.with_verbosity(v), TraceMode::Call, "v={v}"); + assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); + } + } + + #[test] + fn verbosity_3_and_4_raises_to_call() { + for v in 3..=4 { + assert_eq!(TraceMode::None.with_verbosity(v), TraceMode::Call, "v={v}"); + // Already above Call — must not downgrade. + assert_eq!(TraceMode::Debug.with_verbosity(v), TraceMode::Debug, "v={v}"); + assert_eq!( + TraceMode::RecordStateDiff.with_verbosity(v), + TraceMode::RecordStateDiff, + "v={v}" + ); + } + } + + #[test] + fn verbosity_5_raises_to_record_state_diff() { + assert_eq!(TraceMode::None.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Call.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Steps.with_verbosity(5), TraceMode::RecordStateDiff); + assert_eq!(TraceMode::Debug.with_verbosity(5), TraceMode::RecordStateDiff); + // Already at the top — stays the same. + assert_eq!(TraceMode::RecordStateDiff.with_verbosity(5), TraceMode::RecordStateDiff); + } + + // -- into_config at each verbosity level -- + + #[test] + fn config_at_verbosity_0_is_none() { + let mode = TraceMode::None.with_verbosity(0); + assert!(mode.into_config().is_none()); + } + + #[test] + fn config_at_verbosity_3_records_calls_only() { + let cfg = TraceMode::None.with_verbosity(3).into_config().unwrap(); + assert!(!cfg.record_steps, "verbosity 3 should not record steps"); + assert!(!cfg.record_state_diff, "verbosity 3 should not record state diff"); + assert!(cfg.record_logs, "verbosity 3 should record logs"); + } + + #[test] + fn config_at_verbosity_5_records_steps_and_state_diff() { + let cfg = TraceMode::None.with_verbosity(5).into_config().unwrap(); + assert!(cfg.record_steps, "verbosity 5 must record steps for backtraces"); + assert!(cfg.record_state_diff, "verbosity 5 must record state diff"); + assert!(cfg.record_logs, "verbosity 5 must record logs"); + // RecordStateDiff should NOT enable expensive debug-level features. + assert!(!cfg.record_memory_snapshots, "verbosity 5 should not record memory snapshots"); + assert_eq!( + cfg.record_stack_snapshots, + StackSnapshotType::None, + "verbosity 5 should not record stack snapshots" + ); + // State diff requires all opcodes to capture SLOAD/SSTORE, so no filter. + assert!( + cfg.record_opcodes_filter.is_none(), + "verbosity 5 needs unfiltered opcodes for state diff" + ); + } + + #[test] + fn config_debug_mode_unchanged() { + // Debug mode must still enable full recording for the debugger. + let cfg = TraceMode::Debug.into_config().unwrap(); + assert!(cfg.record_steps); + assert!(cfg.record_memory_snapshots, "Debug must record memory snapshots"); + assert_eq!( + cfg.record_stack_snapshots, + StackSnapshotType::Full, + "Debug must record full stack snapshots" + ); + assert!(cfg.record_returndata_snapshots, "Debug must record returndata"); + assert!(cfg.record_immediate_bytes, "Debug must record immediate bytes"); + assert!(cfg.record_opcodes_filter.is_none(), "Debug must record all opcodes (no filter)"); + assert!(!cfg.record_state_diff, "Debug alone should not record state diff"); + } +} diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index bc1f44fdc5e03..b6f11772620f9 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -14,17 +14,19 @@ workspace = true [dependencies] foundry-config.workspace = true +foundry-common.workspace = true -alloy-primitives.workspace = true +solar.workspace = true -ariadne = "0.5" itertools.workspace = true -solang-parser.workspace = true -thiserror.workspace = true -tracing.workspace = true +similar = { version = "2", features = ["inline"] } [dev-dependencies] -itertools.workspace = true -similar-asserts.workspace = true +foundry-test-utils.workspace = true + toml.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter"] } +snapbox.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/fmt/README.md b/crates/fmt/README.md index f38eed245eaf9..2a151009b1019 100644 --- a/crates/fmt/README.md +++ b/crates/fmt/README.md @@ -6,71 +6,51 @@ is tested on the [Prettier Solidity Plugin](https://github.com/prettier-solidity ## Architecture -The formatter works in two steps: +The formatter is built on top of [Solar](https://github.com/paradigmxyz/solar), and the architecture is based on a Wadler-style pretty-printing engine. The formatting process consists of two main steps: -1. Parse Solidity source code with [solang](https://github.com/hyperledger-labs/solang) into the PT (Parse Tree) - (not the same as Abstract Syntax Tree, [see difference](https://stackoverflow.com/a/9864571)). -2. Walk the PT and output new source code that's compliant with provided config and rule set. +1. **Parsing**: The Solidity source code is parsed using **`solar`** into an **Abstract Syntax Tree (AST)**. The AST is a tree representation of the code's syntactic structure. +2. **Printing**: The AST is traversed by a visitor, which generates a stream of abstract tokens that are then processed by a pretty-printing engine to produce the final formatted code. -The technique for walking the tree is based on [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern) -and works as following: +### The Pretty Printer (`pp`) -1. Implement `Formatter` callback functions for each PT node type. - Every callback function should write formatted output for the current node - and call `Visitable::visit` function for child nodes delegating the output writing. -1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call - corresponding `Formatter`'s callback function. +The core of the formatter is a pretty-printing engine inspired by Philip Wadler's algorithm, and adapted from the implementations in `rustc_ast_pretty` and `prettyplease`. Its goal is to produce an optimal and readable layout by making intelligent decisions about line breaks. -### Output +The process works like this: -The formatted output is written into the output buffer in _chunks_. The `Chunk` struct holds the content to be written & -metadata for it. This includes the comments surrounding the content as well as the `needs_space` flag specifying whether -this _chunk_ needs a space. The flag overrides the default behavior of `Formatter::next_char_needs_space` method. +1. **AST to Abstract Tokens**: The formatter's `State` object walks the `solar` AST. Instead of directly writing strings, it translates the AST nodes into a stream of abstract formatting "commands" called `Token`s. This decouples the code's structure from the final text output. The primary tokens are: + * **`String`**: An atomic, unbreakable piece of text, like a keyword (`function`), an identifier (`myVar`), or a literal (`42`). + * **`Break`**: A potential line break. This is the core of the engine's flexibility. The `Printer` later decides whether to render a `Break` as a single space or as a newline with appropriate indentation. + * **`Begin`/`End`**: These tokens define a logical group of tokens that should be formatted as a single unit. This allows the printer to decide how to format the entire group at once. -The content gets written into the `FormatBuffer` which contains the information about the current indentation level, -indentation length, current state as well as the other data determining the rules for writing the content. -`FormatBuffer` implements the `std::fmt::Write` trait where it evaluates the current information and decides how the -content should be written to the destination. +2. **Grouping and Breaking Strategy**: The `Begin` and `End` tokens create formatting "boxes" that guide the breaking strategy. There are two main types of boxes: + * **Consistent Box (`cbox`)**: If *any* `Break` inside this box becomes a newline, then *all* `Break`s inside it must also become newlines. This is ideal for lists like function parameters or struct fields, ensuring they are either all on one line or neatly arranged with one item per line. + * **Inconsistent Box (`ibox`)**: `Break`s within this box are independent. The printer can wrap a long line at any `Break` point without forcing other breaks in the same box to become newlines. This is useful for formatting long expressions or comments. -### Comments +3. **The `Printer` Engine**: The `Printer` consumes this stream of tokens and makes the final decisions: + * It maintains a buffer of tokens and tracks the remaining space on the current line based on the configured `line_length`. + * When it encounters a `Begin` token for a group, it calculates whether the entire group could fit on the current line if all its `Break`s were spaces. + * **If it fits**, all `Break`s in that group are rendered as spaces. + * **If it doesn't fit**, `Break`s are rendered as newlines, and the indentation level is adjusted accordingly based on the box's rules (consistent or inconsistent). -The solang parser does not output comments as a type of parse tree node, but rather -in a list alongside the parse tree with location information. It is therefore necessary -to infer where to insert the comments and how to format them while traversing the parse tree. +Crucially, this entire process is deterministic. Because the formatter completely rebuilds the code from the AST, it discards all original whitespace, line breaks, and other stylistic variations. This means that for a given AST and configuration, the output will always be identical. No matter how inconsistently the input code is formatted, the result is a single, canonical representation, ensuring predictability and consistency across any codebase. -To handle this, the formatter pre-parses the comments and puts them into two categories: -Prefix and Postfix comments. Prefix comments refer to the node directly after them, and -postfix comments refer to the node before them. As an illustration: +> **Debug Mode**: To visualize the debug output, and understand how the pretty-printer makes its decisions about boxes and breaks, see the [Debug](#debug) section in Testing. -```solidity -// This is a prefix comment -/* This is also a prefix comment */ -uint variable = 1 + 2; /* this is postfix */ // this is postfix too - // and this is a postfix comment on the next line -``` +### Comments -To insert the comments into the appropriate areas, strings get converted to chunks -before being written to the buffer. A chunk is any string that cannot be split by -whitespace. A chunk also carries with it the surrounding comment information. Thereby -when writing the chunk the comments can be added before and after the chunk as well -as any whitespace surrounding. +Comment handling is a critical aspect of the formatter, designed to preserve developer intent while restructuring the code. -To construct a chunk, the string and the location of the string is given to the -Formatter and the pre-parsed comments before the start and end of the string are -associated with that string. The source code can then further be chunked before the -chunks are written to the buffer. +1. **Categorization**: Comments are parsed and categorized by their position and style: `Isolated` (on its own line), `Mixed` (on a line with code), and `Trailing` (at the end of a line). -To write the chunk, first the comments associated with the start of the chunk get -written to the buffer. Then the Formatter checks if any whitespace is needed between -what's been written to the buffer and what's in the chunk and inserts it where appropriate. -If the chunk content fits on the same line, it will be written directly to the buffer, -otherwise it will be written on the next line. Finally, any associated postfix -comments also get written. +2. **Blank Line Handling**: Blank lines in the source code are treated as a special `BlankLine` comment type, allowing the formatter to preserve vertical spacing that separates logical blocks of code. However, to maintain a clean and consistent vertical rhythm, any sequence of multiple blank lines is collapsed into a single blank line. This prevents excessive empty space in the formatted output. -### Example +3. **Integration with Printing**: During the AST traversal, the formatter queries for comments that appear before the current code element. These comments, including blank lines, are then strategically inserted into the `Printer`'s token stream. The formatter inserts `Break` tokens around comments to ensure they are correctly spaced from the surrounding code, and emits one or two `hardbreak`s for blank lines to maintain the original vertical rhythm. -Source code +This approach allows the formatter to respect both the syntactic structure of the code and the developer's textual annotations and spacing, producing a clean, readable, and intentional layout. +### Example + +**Source Code** ```solidity pragma solidity ^0.8.10 ; contract HelloWorld { @@ -79,23 +59,37 @@ contract HelloWorld { } + event Greet( string indexed name) ; ``` -Parse Tree (simplified) - +**Abstract Syntax Tree (AST) (simplified)** ```text SourceUnit - | PragmaDirective("solidity", "^0.8.10") - | ContractDefinition("HelloWorld") - | VariableDefinition("string", "message", null, ["public"]) - | FunctionDefinition("constructor") - | Parameter("string", "initMessage", ["memory"]) - | EventDefinition("string", "Greet", ["indexed"], ["name"]) + ├─ PragmaDirective("solidity", "^0.8.10") + ├─ ItemContract("HelloWorld") + │ ├─ VariableDefinition { name: "message", ty: "string", visibility: "public" } + │ └─ ItemFunction { + │ kind: Constructor, + │ header: FunctionHeader { + │ parameters: [ + │ VariableDefinition { name: "initMessage", ty: "string", data_location: "memory" } + │ ] + │ }, + │ body: Block { + │ stmts: [ + │ Stmt { kind: Expr(Assign {lhs: Ident("message"), rhs: Ident("initMessage")}) } + │ ] + │ } + │ } + └─ ItemEvent { name: "Greet", parameters: [ + VariableDefinition { name: "name", ty: "string", indexed: true } + ] } ``` -Formatted source code that was reconstructed from the Parse Tree +**Formatted Source Code** +The code is reconstructed from the AST using the pretty-printer. ```solidity pragma solidity ^0.8.10; @@ -112,111 +106,139 @@ event Greet(string indexed name); ### Configuration -The formatter supports multiple configuration options defined in `FormatterConfig`. - -| Option | Default | Description | -|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| line_length | 120 | Maximum line length where formatter will try to wrap the line | -| tab_width | 4 | Number of spaces per indentation level | -| bracket_spacing | false | Print spaces between brackets | -| int_types | long | Style of uint/int256 types. Available options: `long`, `short`, `preserve` | -| multiline_func_header | attributes_first | Style of multiline function header in case it doesn't fit. Available options: `params_first`, `params_first_multi`, `attributes_first`, `all`, `all_params` | -| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | -| number_underscore | preserve | Style of underscores in number literals. Available options: `preserve`, `remove`, `thousands` | -| hex_underscore | remove | Style of underscores in hex literals. Available options: `preserve`, `remove`, `bytes` | -| single_line_statement_blocks | preserve | Style of single line blocks in statements. Available options: `single`, `multi`, `preserve` | -| override_spacing | false | Print space in state variable, function and modifier `override` attribute | -| wrap_comments | false | Wrap comments on `line_length` reached | -| ignore | [] | Globs to ignore | -| contract_new_lines | false | Add new line at start and end of contract declarations | -| sort_imports | false | Sort import statements alphabetically in groups | - -### Disable Line - -The formatter can be disabled on specific lines by adding a comment `// forgefmt: disable-next-line`, like this: +The formatter supports multiple configuration options defined in `foundry.toml`. + +| Option | Default | Description | +| :--- | :--- | :--- | +| `line_length` | `120` | Maximum line length where the formatter will try to wrap the line. | +| `tab_width` | `4` | Number of spaces per indentation level. Ignored if `style` is `tab`. | +| `style` | `space` | The style of indentation. Options: `space`, `tab`. | +| `bracket_spacing` | `false` | Print spaces between brackets. | +| `int_types` | `long` | Style for `uint256`/`int256` types. Options: `long`, `short`, `preserve`. | +| `multiline_func_header` | `attributes_first` | The style of multiline function headers. Options: `attributes_first`, `params_always`, `params_first_multi`, `all`, `all_params`. | +| `prefer_compact` | `all` | Style that determines if a broken list, should keep its elements together on their own line, before breaking individually. Options: `none`, `calls`, `events`, `errors`, `events_errors`, `all`. | +| `quote_style` | `double` | The style of quotation marks. Options: `double`, `single`, `preserve`. | +| `number_underscore` | `preserve` | The style of underscores in number literals. Options: `preserve`, `remove`, `thousands`. | +| `hex_underscore` | `remove` | The style of underscores in hex literals. Options: `preserve`, `remove`, `bytes`. | +| `single_line_statement_blocks` | `preserve` | The style of single-line blocks in statements. Options: `preserve`, `single`, `multi`. | +| `override_spacing` | `false` | Print a space in the `override` attribute. | +| `wrap_comments` | `false` | Wrap comments when `line_length` is reached. | +| `docs_style` | `preserve` | Enforces the style of doc (natspec) comments. Options: `preserve`, `line`, `block`. | +| `ignore` | `[]` | Globs to ignore. | +| `contract_new_lines` | `false` | Add a new line at the start and end of contract declarations. | +| `sort_imports` | `false` | Sort import statements alphabetically in groups. A group is a set of imports separated by a newline. | +| `namespace_import_style` | `prefer_plain` | Style for namespace imports. Options: `prefer_plain` (`import "foo" as foo;`), `prefer_glob` (`import * as foo from "foo";`), `preserve`. | +| `pow_no_space` | `false` | Suppress spaces around the power operator (`**`). | +| `single_line_imports` | `false` | Keep single imports on a single line, even if they exceed the line length limit. | + +> Check [`FormatterConfig`](../config/src/fmt.rs) for a more detailed explanation. +### Inline Configuration + +The formatter can be instructed to skip specific sections of code using inline comments. While the tool supports fine-grained control, it is generally more robust and efficient to disable formatting for entire AST items or statements. + +This approach is preferred because it allows the formatter to treat the entire disabled item as a single, opaque unit. It can simply copy the original source text for that item's span instead of partially formatting a line, switching to copy mode, and then resuming formatting. This leads to more predictable output and avoids potential edge cases with complex, partially-disabled statements. + +#### Disable Line + +These directives are best used when they apply to a complete, self-contained AST statement, as shown below. In this case, `uint x = 100;` is a full statement, making it a good candidate for a line-based disable. + +To disable the next line: ```solidity // forgefmt: disable-next-line uint x = 100; ``` -Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` -instead: - +To disable the current line: ```solidity uint x = 100; // forgefmt: disable-line ``` +#### Disable Block -### Disable Block - -The formatter can be disabled for a section of code by adding a comment `// forgefmt: disable-start` before and a -comment `// forgefmt: disable-end` after, like this: +This is the recommended approach for complex, multi-line constructs where you want to preserve specific formatting. In the example below, the entire `function` definition is disabled. This is preferable to trying to disable individual lines within the signature, because lines like `uint256 b /* a comment that goes inside the comma */,` do not correspond to a complete AST item or statement on their own. Disabling the whole item is cleaner and more aligned with the code's structure. ```solidity // forgefmt: disable-start -uint x = 100; -uint y = 101; +function fnWithManyArguments( + uint a, + uint256 b /* a comment that goes inside the comma */, + uint256 c +) external returns (bool) { // forgefmt: disable-end ``` -### Testing +## Contributing -Tests reside under the `fmt/testdata` folder and specify the malformatted & expected Solidity code. The source code file -is named `original.sol` and expected file(s) are named in a format `({prefix}.)?fmt.sol`. Multiple expected files are -needed for tests covering available configuration options. +Check out the [foundry contribution guide](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md). + +Guidelines for contributing to `forge fmt`: + +### Opening an issue + +1. Create a short, concise title describing the issue. + * **Bad**: `Forge fmt does not work` + * **Good**: `bug(forge-fmt): misplaces postfix comment on if-statement` +2. Fill in the issue template fields, including Foundry version, platform, and component info. +3. Provide code snippets showing the current and expected behaviors. +4. If it's a feature request, explain why the feature is needed. +5. Add the `C-forge` and `Cmd-forge-fmt` labels. + +### Fixing a Bug or Developing a Feature + +1. Specify the issue being addressed in the PR description. +2. Add a note on your solution in the PR description. +3. Ensure the PR includes comprehensive acceptance tests under `fmt/testdata/`, covering: + * The specific case being fixed/added. + * Behavior with different kinds of comments (isolated, mixed, trailing). + * If it's a new config value, tests covering all available options. + +### Testing -The default configuration values can be overridden from within the expected file by adding a comment in the format -`// config: {config_entry} = {config_value}`. For example: +Tests are located in the `fmt/testdata` folder. Each test consists of an `original.sol` file and one or more expected output files, named `*.fmt.sol`. +The default configuration can be overridden from within an expected output file by adding a comment in the format `// config: {config_key} = {config_value}`. For example: ```solidity // config: line_length = 160 ``` -The `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the -following process: +The testing process for each test suite is as follows: +1. Read `original.sol` and the corresponding `*.fmt.sol` expected output. +2. Parse any `// config:` comments from the expected file to create a test-specific configuration. +3. Format `original.sol` and assert that the output matches the content of `*.fmt.sol`. +4. To ensure **idempotency**, format the content of `*.fmt.sol` again and assert that the output does not change. -1. Preparse comments with config values -2. Parse and compare the AST for source & expected files. - - The `AstEq` trait defines the comparison rules for the AST nodes -3. Format the source file and assert the equality of the output with the expected file. -4. Format the expected files and assert the idempotency of the formatting operation. +### Debug -## Contributing +The formatter includes a debug mode that provides visual insight into the pretty-printer's decision-making process. This is invaluable for troubleshooting complex formatting issues and understanding how the boxes and breaks described in [The Pretty Printer](#the-pretty-printer-pp) section work. -Check out the [foundry contribution guide](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md). +To enable it, run the formatter with the `FMT_DEBUG` environment variable set: +```sh +FMT_DEBUG=1 cargo test -p forge-fmt --test formatter Repros +``` -Guidelines for contributing to `forge fmt`: +When enabled, the output will be annotated with special characters representing the printer's internal state: -### Opening an issue +* **Boxes**: + * `«` and `»`: Mark the start and end of a **consistent** box (`cbox`). + * `‹` and `›`: Mark the start and end of an **inconsistent** box (`ibox`). + +* **Breaks**: + * `·`: Represents a `Break` token, which could be a space or a newline. + +For example, running debug mode on the `HelloWorld` contract from earlier would produce an output like this: + +```text +pragma solidity ^0.8.10;· +· +«‹«contract HelloWorld »{›· +‹‹ string· public· message››;· +· +« constructor«(«‹‹string memory initMessage››»)» {»· +«‹ message = ·initMessage›·;· +» }· +»}· +· +event Greet(««‹‹string indexed name››»»);· +``` -1. Create a short concise title describing an issue. - - Bad Title Examples - ```text - Forge fmt does not work - Forge fmt breaks - Forge fmt unexpected behavior - ``` - - Good Title Examples - ```text - Forge fmt postfix comment misplaced - Forge fmt does not inline short yul blocks - ``` -2. Fill in the issue template fields that include foundry version, platform & component info. -3. Provide the code snippets showing the current & expected behaviors. -4. If it's a feature request, specify why this feature is needed. -5. Besides the default label (`T-Bug` for bugs or `T-feature` for features), add `C-forge` and `Cmd-forge-fmt` labels. - -### Fixing A Bug - -1. Specify an issue that is being addressed in the PR description. -2. Add a note on the solution in the PR description. -3. Make sure the PR includes the acceptance test(s). - -### Developing A Feature - -1. Specify an issue that is being addressed in the PR description. -2. Add a note on the solution in the PR description. -3. Provide the test coverage for the new feature. These should include: - - Adding malformatted & expected solidity code under `fmt/testdata/$dir/` - - Testing the behavior of pre and postfix comments - - If it's a new config value, tests covering **all** available options \ No newline at end of file +This annotated output allows you to see exactly how the printer is grouping tokens and where it considers inserting a space or a newline. This makes it much easier to diagnose why a certain layout is being produced. diff --git a/crates/fmt/src/buffer.rs b/crates/fmt/src/buffer.rs deleted file mode 100644 index 18db4fea0459b..0000000000000 --- a/crates/fmt/src/buffer.rs +++ /dev/null @@ -1,442 +0,0 @@ -//! Format buffer. - -use crate::{ - comments::{CommentState, CommentStringExt}, - string::{QuoteState, QuotedStringExt}, -}; -use std::fmt::Write; - -/// An indent group. The group may optionally skip the first line -#[derive(Clone, Debug, Default)] -struct IndentGroup { - skip_line: bool, -} - -#[derive(Clone, Copy, Debug)] -enum WriteState { - LineStart(CommentState), - WriteTokens(CommentState), - WriteString(char), -} - -impl WriteState { - fn comment_state(&self) -> CommentState { - match self { - Self::LineStart(state) => *state, - Self::WriteTokens(state) => *state, - Self::WriteString(_) => CommentState::None, - } - } -} - -impl Default for WriteState { - fn default() -> Self { - Self::LineStart(CommentState::default()) - } -} - -/// A wrapper around a `std::fmt::Write` interface. The wrapper keeps track of indentation as well -/// as information about the last `write_str` command if available. The formatter may also be -/// restricted to a single line, in which case it will throw an error on a newline -#[derive(Clone, Debug)] -pub struct FormatBuffer { - pub w: W, - indents: Vec, - base_indent_len: usize, - tab_width: usize, - last_char: Option, - current_line_len: usize, - restrict_to_single_line: bool, - state: WriteState, -} - -impl FormatBuffer { - pub fn new(w: W, tab_width: usize) -> Self { - Self { - w, - tab_width, - base_indent_len: 0, - indents: vec![], - current_line_len: 0, - last_char: None, - restrict_to_single_line: false, - state: WriteState::default(), - } - } - - /// Create a new temporary buffer based on an existing buffer which retains information about - /// the buffer state, but has a blank String as its underlying `Write` interface - pub fn create_temp_buf(&self) -> FormatBuffer { - let mut new = FormatBuffer::new(String::new(), self.tab_width); - new.base_indent_len = self.total_indent_len(); - new.current_line_len = self.current_line_len(); - new.last_char = self.last_char; - new.restrict_to_single_line = self.restrict_to_single_line; - new.state = match self.state { - WriteState::WriteTokens(state) | WriteState::LineStart(state) => { - WriteState::LineStart(state) - } - WriteState::WriteString(ch) => WriteState::WriteString(ch), - }; - new - } - - /// Restrict the buffer to a single line - pub fn restrict_to_single_line(&mut self, restricted: bool) { - self.restrict_to_single_line = restricted; - } - - /// Indent the buffer by delta - pub fn indent(&mut self, delta: usize) { - self.indents.extend(std::iter::repeat_n(IndentGroup::default(), delta)); - } - - /// Dedent the buffer by delta - pub fn dedent(&mut self, delta: usize) { - self.indents.truncate(self.indents.len() - delta); - } - - /// Get the current level of the indent. This is multiplied by the tab width to get the - /// resulting indent - fn level(&self) -> usize { - self.indents.iter().filter(|i| !i.skip_line).count() - } - - /// Check if the last indent group is being skipped - pub fn last_indent_group_skipped(&self) -> bool { - self.indents.last().map(|i| i.skip_line).unwrap_or(false) - } - - /// Set whether the last indent group should be skipped - pub fn set_last_indent_group_skipped(&mut self, skip_line: bool) { - if let Some(i) = self.indents.last_mut() { - i.skip_line = skip_line - } - } - - /// Get the current indent size (level * tab_width) - pub fn current_indent_len(&self) -> usize { - self.level() * self.tab_width - } - - /// Get the total indent size - pub fn total_indent_len(&self) -> usize { - self.current_indent_len() + self.base_indent_len - } - - /// Get the current written position (this does not include the indent size) - pub fn current_line_len(&self) -> usize { - self.current_line_len - } - - /// Check if the buffer is at the beginning of a new line - pub fn is_beginning_of_line(&self) -> bool { - matches!(self.state, WriteState::LineStart(_)) - } - - /// Start a new indent group (skips first indent) - pub fn start_group(&mut self) { - self.indents.push(IndentGroup { skip_line: true }); - } - - /// End the last indent group - pub fn end_group(&mut self) { - self.indents.pop(); - } - - /// Get the last char written to the buffer - pub fn last_char(&self) -> Option { - self.last_char - } - - /// When writing a newline apply state changes - fn handle_newline(&mut self, mut comment_state: CommentState) { - if comment_state == CommentState::Line { - comment_state = CommentState::None; - } - self.current_line_len = 0; - self.set_last_indent_group_skipped(false); - self.last_char = Some('\n'); - self.state = WriteState::LineStart(comment_state); - } -} - -impl FormatBuffer { - /// Write a raw string to the buffer. This will ignore indents and remove the indents of the - /// written string to match the current base indent of this buffer if it is a temp buffer - pub fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result { - self._write_raw(s.as_ref()) - } - - fn _write_raw(&mut self, s: &str) -> std::fmt::Result { - let mut lines = s.lines().peekable(); - let mut comment_state = self.state.comment_state(); - while let Some(line) = lines.next() { - // remove the whitespace that covered by the base indent length (this is normally the - // case with temporary buffers as this will be re-added by the underlying IndentWriter - // later on - let (new_comment_state, line_start) = line - .comment_state_char_indices() - .with_state(comment_state) - .take(self.base_indent_len) - .take_while(|(_, _, ch)| ch.is_whitespace()) - .last() - .map(|(state, idx, ch)| (state, idx + ch.len_utf8())) - .unwrap_or((comment_state, 0)); - comment_state = new_comment_state; - let trimmed_line = &line[line_start..]; - if !trimmed_line.is_empty() { - self.w.write_str(trimmed_line)?; - self.current_line_len += trimmed_line.len(); - self.last_char = trimmed_line.chars().next_back(); - self.state = WriteState::WriteTokens(comment_state); - } - if lines.peek().is_some() || s.ends_with('\n') { - if self.restrict_to_single_line { - return Err(std::fmt::Error) - } - self.w.write_char('\n')?; - self.handle_newline(comment_state); - } - } - Ok(()) - } -} - -impl Write for FormatBuffer { - fn write_str(&mut self, mut s: &str) -> std::fmt::Result { - if s.is_empty() { - return Ok(()) - } - - let mut indent = " ".repeat(self.current_indent_len()); - - loop { - match self.state { - WriteState::LineStart(mut comment_state) => { - match s.find(|b| b != '\n') { - // No non-empty lines in input, write the entire string (only newlines) - None => { - if !s.is_empty() { - self.w.write_str(s)?; - self.handle_newline(comment_state); - } - break - } - - // We can see the next non-empty line. Write up to the - // beginning of that line, then insert an indent, then - // continue. - Some(len) => { - let (head, tail) = s.split_at(len); - self.w.write_str(head)?; - self.w.write_str(&indent)?; - self.current_line_len = 0; - self.last_char = Some(' '); - // a newline has been inserted - if len > 0 { - if self.last_indent_group_skipped() { - indent = " ".repeat(self.current_indent_len() + self.tab_width); - self.set_last_indent_group_skipped(false); - } - if comment_state == CommentState::Line { - comment_state = CommentState::None; - } - } - s = tail; - self.state = WriteState::WriteTokens(comment_state); - } - } - } - WriteState::WriteTokens(comment_state) => { - if s.is_empty() { - break - } - - // find the next newline or non-comment string separator (e.g. ' or ") - let mut len = 0; - let mut new_state = WriteState::WriteTokens(comment_state); - for (state, idx, ch) in s.comment_state_char_indices().with_state(comment_state) - { - len = idx; - if ch == '\n' { - if self.restrict_to_single_line { - return Err(std::fmt::Error) - } - new_state = WriteState::LineStart(state); - break - } else if state == CommentState::None && (ch == '\'' || ch == '"') { - new_state = WriteState::WriteString(ch); - break - } else { - new_state = WriteState::WriteTokens(state); - } - } - - if matches!(new_state, WriteState::WriteTokens(_)) { - // No newlines or strings found, write the entire string - self.w.write_str(s)?; - self.current_line_len += s.len(); - self.last_char = s.chars().next_back(); - self.state = new_state; - break - } else { - // A newline or string has been found. Write up to that character and - // continue on the tail - let (head, tail) = s.split_at(len + 1); - self.w.write_str(head)?; - s = tail; - match new_state { - WriteState::LineStart(comment_state) => { - self.handle_newline(comment_state) - } - new_state => { - self.current_line_len += head.len(); - self.last_char = head.chars().next_back(); - self.state = new_state; - } - } - } - } - WriteState::WriteString(quote) => { - match s.quoted_ranges().with_state(QuoteState::String(quote)).next() { - // No end found, write the rest of the string - None => { - self.w.write_str(s)?; - self.current_line_len += s.len(); - self.last_char = s.chars().next_back(); - break - } - // String end found, write the string and continue to add tokens after - Some((_, _, len)) => { - let (head, tail) = s.split_at(len + 1); - self.w.write_str(head)?; - if let Some((_, last)) = head.rsplit_once('\n') { - self.set_last_indent_group_skipped(false); - self.current_line_len = last.len(); - } else { - self.current_line_len += head.len(); - } - self.last_char = Some(quote); - s = tail; - self.state = WriteState::WriteTokens(CommentState::None); - } - } - } - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const TAB_WIDTH: usize = 4; - - #[test] - fn test_buffer_indents() -> std::fmt::Result { - let delta = 1; - - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - assert_eq!(buf.indents.len(), 0); - assert_eq!(buf.level(), 0); - assert_eq!(buf.current_indent_len(), 0); - - buf.indent(delta); - assert_eq!(buf.indents.len(), delta); - assert_eq!(buf.level(), delta); - assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); - - buf.indent(delta); - buf.set_last_indent_group_skipped(true); - assert!(buf.last_indent_group_skipped()); - assert_eq!(buf.indents.len(), delta * 2); - assert_eq!(buf.level(), delta); - assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH); - buf.dedent(delta); - - buf.dedent(delta); - assert_eq!(buf.indents.len(), 0); - assert_eq!(buf.level(), 0); - assert_eq!(buf.current_indent_len(), 0); - - // panics on extra dedent - let res = std::panic::catch_unwind(|| buf.clone().dedent(delta)); - assert!(res.is_err()); - - Ok(()) - } - - #[test] - fn test_identical_temp_buf() -> std::fmt::Result { - let content = "test string"; - let multiline_content = "test\nmultiline\nmultiple"; - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - - // create identical temp buf - let mut temp = buf.create_temp_buf(); - writeln!(buf, "{content}")?; - writeln!(temp, "{content}")?; - assert_eq!(buf.w, format!("{content}\n")); - assert_eq!(temp.w, buf.w); - assert_eq!(temp.current_line_len, buf.current_line_len); - assert_eq!(temp.base_indent_len, buf.total_indent_len()); - - let delta = 1; - buf.indent(delta); - - let mut temp_indented = buf.create_temp_buf(); - assert!(temp_indented.w.is_empty()); - assert_eq!(temp_indented.base_indent_len, buf.total_indent_len()); - assert_eq!(temp_indented.level() + delta, buf.level()); - - let indent = " ".repeat(delta * TAB_WIDTH); - - let mut original_buf = buf.clone(); - write!(buf, "{multiline_content}")?; - let expected_content = format!( - "{}\n{}{}", - content, - indent, - multiline_content.lines().collect::>().join(&format!("\n{indent}")) - ); - assert_eq!(buf.w, expected_content); - - write!(temp_indented, "{multiline_content}")?; - - // write temp buf to original and assert the result - write!(original_buf, "{}", temp_indented.w)?; - assert_eq!(buf.w, original_buf.w); - - Ok(()) - } - - #[test] - fn test_preserves_original_content_with_default_settings() -> std::fmt::Result { - let contents = [ - "simple line", - r" - some - multiline - content", - "// comment", - "/* comment */", - r"mutliline - content - // comment1 - with comments - /* comment2 */ ", - ]; - - for content in &contents { - let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH); - write!(buf, "{content}")?; - assert_eq!(&buf.w, content); - } - - Ok(()) - } -} diff --git a/crates/fmt/src/chunk.rs b/crates/fmt/src/chunk.rs deleted file mode 100644 index 7d9ce25c7fbd0..0000000000000 --- a/crates/fmt/src/chunk.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::comments::CommentWithMetadata; - -/// Holds information about a non-whitespace-splittable string, and the surrounding comments -#[derive(Clone, Debug, Default)] -pub struct Chunk { - pub postfixes_before: Vec, - pub prefixes: Vec, - pub content: String, - pub postfixes: Vec, - pub needs_space: Option, -} - -impl From for Chunk { - fn from(string: String) -> Self { - Self { content: string, ..Default::default() } - } -} - -impl From<&str> for Chunk { - fn from(string: &str) -> Self { - Self { content: string.to_owned(), ..Default::default() } - } -} - -// The struct with information about chunks used in the [Formatter::surrounded] method -#[derive(Debug)] -pub struct SurroundingChunk { - pub before: Option, - pub next: Option, - pub spaced: Option, - pub content: String, -} - -impl SurroundingChunk { - pub fn new( - content: impl std::fmt::Display, - before: Option, - next: Option, - ) -> Self { - Self { before, next, content: format!("{content}"), spaced: None } - } - - pub fn spaced(mut self) -> Self { - self.spaced = Some(true); - self - } - - pub fn non_spaced(mut self) -> Self { - self.spaced = Some(false); - self - } - - pub fn loc_before(&self) -> usize { - self.before.unwrap_or_default() - } - - pub fn loc_next(&self) -> Option { - self.next - } -} diff --git a/crates/fmt/src/comments.rs b/crates/fmt/src/comments.rs deleted file mode 100644 index eafdb998910d9..0000000000000 --- a/crates/fmt/src/comments.rs +++ /dev/null @@ -1,456 +0,0 @@ -use crate::inline_config::{InlineConfigItem, InvalidInlineConfigItem}; -use itertools::Itertools; -use solang_parser::pt::*; -use std::collections::VecDeque; - -/// The type of a Comment -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CommentType { - /// A Line comment (e.g. `// ...`) - Line, - /// A Block comment (e.g. `/* ... */`) - Block, - /// A Doc Line comment (e.g. `/// ...`) - DocLine, - /// A Doc Block comment (e.g. `/** ... */`) - DocBlock, -} - -/// The comment position -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CommentPosition { - /// Comes before the code it describes - Prefix, - /// Comes after the code it describes - Postfix, -} - -/// Comment with additional metadata -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CommentWithMetadata { - pub ty: CommentType, - pub loc: Loc, - pub has_newline_before: bool, - pub indent_len: usize, - pub comment: String, - pub position: CommentPosition, -} - -impl PartialOrd for CommentWithMetadata { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for CommentWithMetadata { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.loc.cmp(&other.loc) - } -} - -impl CommentWithMetadata { - fn new( - comment: Comment, - position: CommentPosition, - has_newline_before: bool, - indent_len: usize, - ) -> Self { - let (ty, loc, comment) = match comment { - Comment::Line(loc, comment) => (CommentType::Line, loc, comment), - Comment::Block(loc, comment) => (CommentType::Block, loc, comment), - Comment::DocLine(loc, comment) => (CommentType::DocLine, loc, comment), - Comment::DocBlock(loc, comment) => (CommentType::DocBlock, loc, comment), - }; - Self { - comment: comment.trim_end().to_string(), - ty, - loc, - position, - has_newline_before, - indent_len, - } - } - - /// Construct a comment with metadata by analyzing its surrounding source code - fn from_comment_and_src(comment: Comment, src: &str, last_comment: Option<&Self>) -> Self { - let src_before = &src[..comment.loc().start()]; - if src_before.is_empty() { - return Self::new(comment, CommentPosition::Prefix, false, 0) - } - - let mut lines_before = src_before.lines().rev(); - let this_line = - if src_before.ends_with('\n') { "" } else { lines_before.next().unwrap_or_default() }; - let indent_len = this_line.chars().take_while(|c| c.is_whitespace()).count(); - let last_line = lines_before.next().map(str::trim_start); - - if matches!(comment, Comment::DocLine(..) | Comment::DocBlock(..)) { - return Self::new( - comment, - CommentPosition::Prefix, - last_line.is_none_or(str::is_empty), - indent_len, - ) - } - - // TODO: this loop takes almost the entirety of the time spent in parsing, which is up to - // 80% of `crate::fmt` - let mut code_end = 0; - for (state, idx, ch) in src_before.comment_state_char_indices() { - if matches!(state, CommentState::None) && !ch.is_whitespace() { - code_end = idx; - } - } - - let (position, has_newline_before) = if src_before[code_end..].contains('\n') { - // comment sits on a line without code - if let Some(last_line) = last_line { - if last_line.is_empty() { - // line before is empty - (CommentPosition::Prefix, true) - } else { - // line has something - // check if the last comment after code was a postfix comment - if last_comment - .is_some_and(|last| last.loc.end() > code_end && !last.is_prefix()) - { - // get the indent size of the next item of code - let next_indent_len = src[comment.loc().end()..] - .non_comment_chars() - .take_while(|ch| ch.is_whitespace()) - .fold(indent_len, |indent, ch| if ch == '\n' { 0 } else { indent + 1 }); - if indent_len > next_indent_len { - // the comment indent is bigger than the next code indent - (CommentPosition::Postfix, false) - } else { - // the comment indent is equal to or less than the next code - // indent - (CommentPosition::Prefix, false) - } - } else { - // if there is no postfix comment after the piece of code - (CommentPosition::Prefix, false) - } - } - } else { - // beginning of file - (CommentPosition::Prefix, false) - } - } else { - // comment is after some code - (CommentPosition::Postfix, false) - }; - - Self::new(comment, position, has_newline_before, indent_len) - } - - pub fn is_line(&self) -> bool { - matches!(self.ty, CommentType::Line | CommentType::DocLine) - } - - pub fn is_doc_block(&self) -> bool { - matches!(self.ty, CommentType::DocBlock) - } - - pub fn is_prefix(&self) -> bool { - matches!(self.position, CommentPosition::Prefix) - } - - pub fn is_before(&self, byte: usize) -> bool { - self.loc.start() < byte - } - - /// Returns the contents of the comment without the start and end tokens - pub fn contents(&self) -> &str { - let mut s = self.comment.as_str(); - if let Some(stripped) = s.strip_prefix(self.start_token()) { - s = stripped; - } - if let Some(end_token) = self.end_token() { - if let Some(stripped) = s.strip_suffix(end_token) { - s = stripped; - } - } - s - } - - /// The start token of the comment - #[inline] - pub const fn start_token(&self) -> &'static str { - match self.ty { - CommentType::Line => "//", - CommentType::Block => "/*", - CommentType::DocLine => "///", - CommentType::DocBlock => "/**", - } - } - - /// The token that gets written on the newline when the - /// comment is wrapped - #[inline] - pub const fn wrap_token(&self) -> &'static str { - match self.ty { - CommentType::Line => "// ", - CommentType::DocLine => "/// ", - CommentType::Block => "", - CommentType::DocBlock => " * ", - } - } - - /// The end token of the comment - #[inline] - pub const fn end_token(&self) -> Option<&'static str> { - match self.ty { - CommentType::Line | CommentType::DocLine => None, - CommentType::Block | CommentType::DocBlock => Some("*/"), - } - } -} - -/// A list of comments -#[derive(Clone, Debug, Default)] -pub struct Comments { - prefixes: VecDeque, - postfixes: VecDeque, -} - -impl Comments { - pub fn new(mut comments: Vec, src: &str) -> Self { - let mut prefixes = VecDeque::with_capacity(comments.len()); - let mut postfixes = VecDeque::with_capacity(comments.len()); - let mut last_comment = None; - - comments.sort_by_key(|comment| comment.loc()); - for comment in comments { - let comment = CommentWithMetadata::from_comment_and_src(comment, src, last_comment); - let vec = if comment.is_prefix() { &mut prefixes } else { &mut postfixes }; - vec.push_back(comment); - last_comment = Some(vec.back().unwrap()); - } - Self { prefixes, postfixes } - } - - /// Helper for removing comments before a byte offset - fn remove_comments_before( - comments: &mut VecDeque, - byte: usize, - ) -> Vec { - let pos = comments - .iter() - .find_position(|comment| !comment.is_before(byte)) - .map(|(idx, _)| idx) - .unwrap_or_else(|| comments.len()); - if pos == 0 { - return Vec::new() - } - comments.rotate_left(pos); - comments.split_off(comments.len() - pos).into() - } - - /// Remove any prefix comments that occur before the byte offset in the src - pub(crate) fn remove_prefixes_before(&mut self, byte: usize) -> Vec { - Self::remove_comments_before(&mut self.prefixes, byte) - } - - /// Remove any postfix comments that occur before the byte offset in the src - pub(crate) fn remove_postfixes_before(&mut self, byte: usize) -> Vec { - Self::remove_comments_before(&mut self.postfixes, byte) - } - - /// Remove any comments that occur before the byte offset in the src - pub(crate) fn remove_all_comments_before(&mut self, byte: usize) -> Vec { - self.remove_prefixes_before(byte) - .into_iter() - .merge(self.remove_postfixes_before(byte)) - .collect() - } - - pub(crate) fn pop(&mut self) -> Option { - if self.iter().next()?.is_prefix() { - self.prefixes.pop_front() - } else { - self.postfixes.pop_front() - } - } - - pub(crate) fn iter(&self) -> impl Iterator { - self.prefixes.iter().merge(self.postfixes.iter()) - } - - /// Parse all comments to return a list of inline config items. This will return an iterator of - /// results of parsing comments which start with `forgefmt:` - pub fn parse_inline_config_items( - &self, - ) -> impl Iterator> + '_ - { - self.iter() - .filter_map(|comment| { - Some((comment, comment.contents().trim_start().strip_prefix("forgefmt:")?.trim())) - }) - .map(|(comment, item)| { - let loc = comment.loc; - item.parse().map(|out| (loc, out)).map_err(|out| (loc, out)) - }) - } -} - -/// The state of a character in a string with possible comments -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum CommentState { - /// character not in a comment - #[default] - None, - /// First `/` in line comment start `"//"` - LineStart1, - /// Second `/` in line comment start `"//"` - LineStart2, - /// Character in a line comment - Line, - /// `/` in block comment start `"/*"` - BlockStart1, - /// `*` in block comment start `"/*"` - BlockStart2, - /// Character in a block comment - Block, - /// `*` in block comment end `"*/"` - BlockEnd1, - /// `/` in block comment end `"*/"` - BlockEnd2, -} - -/// An Iterator over characters and indices in a string slice with information about the state of -/// comments -pub struct CommentStateCharIndices<'a> { - iter: std::str::CharIndices<'a>, - state: CommentState, -} - -impl<'a> CommentStateCharIndices<'a> { - #[inline] - fn new(string: &'a str) -> Self { - Self { iter: string.char_indices(), state: CommentState::None } - } - - #[inline] - pub fn with_state(mut self, state: CommentState) -> Self { - self.state = state; - self - } - - #[inline] - pub fn peek(&mut self) -> Option<(usize, char)> { - self.iter.clone().next() - } -} - -impl Iterator for CommentStateCharIndices<'_> { - type Item = (CommentState, usize, char); - - #[inline] - fn next(&mut self) -> Option { - let (idx, ch) = self.iter.next()?; - match self.state { - CommentState::None => { - if ch == '/' { - self.state = match self.peek() { - Some((_, '/')) => CommentState::LineStart1, - Some((_, '*')) => CommentState::BlockStart1, - _ => CommentState::None, - }; - } - } - CommentState::LineStart1 => { - self.state = CommentState::LineStart2; - } - CommentState::LineStart2 => { - self.state = CommentState::Line; - } - CommentState::Line => { - if ch == '\n' { - self.state = CommentState::None; - } - } - CommentState::BlockStart1 => { - self.state = CommentState::BlockStart2; - } - CommentState::BlockStart2 => { - self.state = CommentState::Block; - } - CommentState::Block => { - if ch == '*' { - if let Some((_, '/')) = self.peek() { - self.state = CommentState::BlockEnd1; - } - } - } - CommentState::BlockEnd1 => { - self.state = CommentState::BlockEnd2; - } - CommentState::BlockEnd2 => { - self.state = CommentState::None; - } - } - Some((self.state, idx, ch)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - #[inline] - fn count(self) -> usize { - self.iter.count() - } -} - -impl std::iter::FusedIterator for CommentStateCharIndices<'_> {} - -/// An Iterator over characters in a string slice which are not a apart of comments -pub struct NonCommentChars<'a>(CommentStateCharIndices<'a>); - -impl Iterator for NonCommentChars<'_> { - type Item = char; - - #[inline] - fn next(&mut self) -> Option { - for (state, _, ch) in self.0.by_ref() { - if state == CommentState::None { - return Some(ch) - } - } - None - } -} - -/// Helpers for iterating over comment containing strings -pub trait CommentStringExt { - fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_>; - - #[inline] - fn non_comment_chars(&self) -> NonCommentChars<'_> { - NonCommentChars(self.comment_state_char_indices()) - } - - #[inline] - fn trim_comments(&self) -> String { - self.non_comment_chars().collect() - } -} - -impl CommentStringExt for T -where - T: AsRef, -{ - #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { - CommentStateCharIndices::new(self.as_ref()) - } -} - -impl CommentStringExt for str { - #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { - CommentStateCharIndices::new(self) - } -} diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs deleted file mode 100644 index 641be5943012a..0000000000000 --- a/crates/fmt/src/formatter.rs +++ /dev/null @@ -1,3902 +0,0 @@ -//! A Solidity formatter - -use crate::{ - buffer::*, - chunk::*, - comments::{ - CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, - }, - format_diagnostics_report, - helpers::import_path_string, - macros::*, - solang_ext::{pt::*, *}, - string::{QuoteState, QuotedStringExt}, - visit::{Visitable, Visitor}, - FormatterConfig, InlineConfig, IntTypes, -}; -use alloy_primitives::Address; -use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle}; -use itertools::{Either, Itertools}; -use solang_parser::diagnostics::Diagnostic; -use std::{fmt::Write, path::PathBuf, str::FromStr}; -use thiserror::Error; - -type Result = std::result::Result; - -/// A custom Error thrown by the Formatter -#[derive(Debug, Error)] -pub enum FormatterError { - /// Error thrown by `std::fmt::Write` interfaces - #[error(transparent)] - Fmt(#[from] std::fmt::Error), - /// Encountered invalid parse tree item. - #[error("encountered invalid parse tree item at {0:?}")] - InvalidParsedItem(Loc), - /// Failed to parse the source code - #[error("failed to parse file:\n{}", format_diagnostics_report(_0, _1.as_deref(), _2))] - Parse(String, Option, Vec), - /// All other errors - #[error(transparent)] - Custom(Box), -} - -impl FormatterError { - fn fmt() -> Self { - Self::Fmt(std::fmt::Error) - } - - fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self { - Self::Custom(Box::new(err)) - } -} - -#[expect(unused_macros)] -macro_rules! format_err { - ($msg:literal $(,)?) => { - $crate::formatter::FormatterError::custom($msg.to_string()) - }; - ($err:expr $(,)?) => { - $crate::formatter::FormatterError::custom($err) - }; - ($fmt:expr, $($arg:tt)*) => { - $crate::formatter::FormatterError::custom(format!($fmt, $($arg)*)) - }; -} - -macro_rules! bail { - ($msg:literal $(,)?) => { - return Err($crate::formatter::format_err!($msg)) - }; - ($err:expr $(,)?) => { - return Err($err) - }; - ($fmt:expr, $($arg:tt)*) => { - return Err($crate::formatter::format_err!($fmt, $(arg)*)) - }; -} - -// TODO: store context entities as references without copying -/// Current context of the Formatter (e.g. inside Contract or Function definition) -#[derive(Debug, Default)] -struct Context { - contract: Option, - function: Option, - if_stmt_single_line: Option, -} - -impl Context { - /// Returns true if the current function context is the constructor - pub(crate) fn is_constructor_function(&self) -> bool { - self.function.as_ref().is_some_and(|f| matches!(f.ty, FunctionTy::Constructor)) - } -} - -/// A Solidity formatter -#[derive(Debug)] -pub struct Formatter<'a, W> { - buf: FormatBuffer, - source: &'a str, - config: FormatterConfig, - temp_bufs: Vec>, - context: Context, - comments: Comments, - inline_config: InlineConfig, -} - -impl<'a, W: Write> Formatter<'a, W> { - pub fn new( - w: W, - source: &'a str, - comments: Comments, - inline_config: InlineConfig, - config: FormatterConfig, - ) -> Self { - Self { - buf: FormatBuffer::new(w, config.tab_width), - source, - config, - temp_bufs: Vec::new(), - context: Context::default(), - comments, - inline_config, - } - } - - /// Get the Write interface of the current temp buffer or the underlying Write - fn buf(&mut self) -> &mut dyn Write { - match &mut self.temp_bufs[..] { - [] => &mut self.buf as &mut dyn Write, - [.., buf] => buf as &mut dyn Write, - } - } - - /// Casts the current writer `w` as a `String` reference. Should only be used for debugging. - unsafe fn buf_contents(&self) -> &String { - *(&self.buf.w as *const W as *const &mut String) - } - - /// Casts the current `W` writer or the current temp buffer as a `String` reference. - /// Should only be used for debugging. - #[expect(dead_code)] - unsafe fn temp_buf_contents(&self) -> &String { - match &self.temp_bufs[..] { - [] => self.buf_contents(), - [.., buf] => &buf.w, - } - } - - buf_fn! { fn indent(&mut self, delta: usize) } - buf_fn! { fn dedent(&mut self, delta: usize) } - buf_fn! { fn start_group(&mut self) } - buf_fn! { fn end_group(&mut self) } - buf_fn! { fn create_temp_buf(&self) -> FormatBuffer } - buf_fn! { fn restrict_to_single_line(&mut self, restricted: bool) } - buf_fn! { fn current_line_len(&self) -> usize } - buf_fn! { fn total_indent_len(&self) -> usize } - buf_fn! { fn is_beginning_of_line(&self) -> bool } - buf_fn! { fn last_char(&self) -> Option } - buf_fn! { fn last_indent_group_skipped(&self) -> bool } - buf_fn! { fn set_last_indent_group_skipped(&mut self, skip: bool) } - buf_fn! { fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result } - - /// Do the callback within the context of a temp buffer - fn with_temp_buf( - &mut self, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - self.temp_bufs.push(self.create_temp_buf()); - let res = fun(self); - let out = self.temp_bufs.pop().unwrap(); - res?; - Ok(out) - } - - /// Does the next written character require whitespace before - fn next_char_needs_space(&self, next_char: char) -> bool { - if self.is_beginning_of_line() { - return false - } - let last_char = - if let Some(last_char) = self.last_char() { last_char } else { return false }; - if last_char.is_whitespace() || next_char.is_whitespace() { - return false - } - match last_char { - '{' => match next_char { - '{' | '[' | '(' => false, - '/' => true, - _ => self.config.bracket_spacing, - }, - '(' | '.' | '[' => matches!(next_char, '/'), - '/' => true, - _ => match next_char { - '}' => self.config.bracket_spacing, - ')' | ',' | '.' | ';' | ']' => false, - _ => true, - }, - } - } - - /// Is length of the `text` with respect to already written line <= `config.line_length` - fn will_it_fit(&self, text: impl AsRef) -> bool { - let text = text.as_ref(); - if text.is_empty() { - return true - } - if text.contains('\n') { - return false - } - let space: usize = self.next_char_needs_space(text.chars().next().unwrap()).into(); - self.config.line_length >= - self.total_indent_len() - .saturating_add(self.current_line_len()) - .saturating_add(text.chars().count() + space) - } - - /// Write empty brackets with respect to `config.bracket_spacing` setting: - /// `"{ }"` if `true`, `"{}"` if `false` - fn write_empty_brackets(&mut self) -> Result<()> { - let brackets = if self.config.bracket_spacing { "{ }" } else { "{}" }; - write_chunk!(self, "{brackets}")?; - Ok(()) - } - - /// Write semicolon to the buffer - fn write_semicolon(&mut self) -> Result<()> { - write!(self.buf(), ";")?; - Ok(()) - } - - /// Write whitespace separator to the buffer - /// `"\n"` if `multiline` is `true`, `" "` if `false` - fn write_whitespace_separator(&mut self, multiline: bool) -> Result<()> { - if !self.is_beginning_of_line() { - write!(self.buf(), "{}", if multiline { "\n" } else { " " })?; - } - Ok(()) - } - - /// Write new line with preserved `last_indent_group_skipped` flag - fn write_preserved_line(&mut self) -> Result<()> { - let last_indent_group_skipped = self.last_indent_group_skipped(); - writeln!(self.buf())?; - self.set_last_indent_group_skipped(last_indent_group_skipped); - Ok(()) - } - - /// Write unformatted src and comments for given location. - fn write_raw_src(&mut self, loc: Loc) -> Result<()> { - let disabled_stmts_src = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec()) - .map_err(FormatterError::custom)?; - self.write_raw(disabled_stmts_src.trim_end())?; - self.write_whitespace_separator(true)?; - // Remove comments as they're already included in disabled src. - let _ = self.comments.remove_all_comments_before(loc.end()); - Ok(()) - } - - /// Returns number of blank lines in source between two byte indexes - fn blank_lines(&self, start: usize, end: usize) -> usize { - // because of sorting import statements, start can be greater than end - if start > end { - return 0 - } - self.source[start..end].trim_comments().matches('\n').count() - } - - /// Get the byte offset of the next line - fn find_next_line(&self, byte_offset: usize) -> Option { - let mut iter = self.source[byte_offset..].char_indices(); - while let Some((_, ch)) = iter.next() { - match ch { - '\n' => return iter.next().map(|(idx, _)| byte_offset + idx), - '\r' => { - return iter.next().and_then(|(idx, ch)| match ch { - '\n' => iter.next().map(|(idx, _)| byte_offset + idx), - _ => Some(byte_offset + idx), - }) - } - _ => {} - } - } - None - } - - /// Find the next instance of the character in source excluding comments - fn find_next_in_src(&self, byte_offset: usize, needle: char) -> Option { - self.source[byte_offset..] - .comment_state_char_indices() - .position(|(state, _, ch)| needle == ch && state == CommentState::None) - .map(|p| byte_offset + p) - } - - /// Find the start of the next instance of a slice in source - fn find_next_str_in_src(&self, byte_offset: usize, needle: &str) -> Option { - let subset = &self.source[byte_offset..]; - needle.chars().next().and_then(|first_char| { - subset - .comment_state_char_indices() - .position(|(state, idx, ch)| { - first_char == ch && - state == CommentState::None && - idx + needle.len() <= subset.len() && - subset[idx..idx + needle.len()] == *needle - }) - .map(|p| byte_offset + p) - }) - } - - /// Extends the location to the next instance of a character. Returns true if the loc was - /// extended - fn extend_loc_until(&self, loc: &mut Loc, needle: char) -> bool { - if let Some(end) = self.find_next_in_src(loc.end(), needle).map(|offset| offset + 1) { - *loc = loc.with_end(end); - true - } else { - false - } - } - - /// Return the flag whether the attempt should be made - /// to write the block on a single line. - /// If the block style is configured to [SingleLineBlockStyle::Preserve], - /// lookup whether there was a newline introduced in `[start_from, end_at]` range - /// where `end_at` is the start of the block. - fn should_attempt_block_single_line( - &mut self, - stmt: &mut Statement, - start_from: usize, - ) -> bool { - match self.config.single_line_statement_blocks { - SingleLineBlockStyle::Single => true, - SingleLineBlockStyle::Multi => false, - SingleLineBlockStyle::Preserve => { - let end_at = match stmt { - Statement::Block { statements, .. } if !statements.is_empty() => { - statements.first().as_ref().unwrap().loc().start() - } - Statement::Expression(loc, _) => loc.start(), - _ => stmt.loc().start(), - }; - - self.find_next_line(start_from).is_some_and(|loc| loc >= end_at) - } - } - } - - /// Create a chunk given a string and the location information - fn chunk_at( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - needs_space: Option, - content: impl std::fmt::Display, - ) -> Chunk { - Chunk { - postfixes_before: self.comments.remove_postfixes_before(byte_offset), - prefixes: self.comments.remove_prefixes_before(byte_offset), - content: content.to_string(), - postfixes: next_byte_offset - .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) - .unwrap_or_default(), - needs_space, - } - } - - /// Create a chunk given a callback - fn chunked( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result { - self.chunked_mono(byte_offset, next_byte_offset, &mut fun) - } - - fn chunked_mono( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - fun: &mut dyn FnMut(&mut Self) -> Result<()>, - ) -> Result { - let postfixes_before = self.comments.remove_postfixes_before(byte_offset); - let prefixes = self.comments.remove_prefixes_before(byte_offset); - let content = self.with_temp_buf(fun)?.w; - let postfixes = next_byte_offset - .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset)) - .unwrap_or_default(); - Ok(Chunk { postfixes_before, prefixes, content, postfixes, needs_space: None }) - } - - /// Create a chunk given a [Visitable] item - fn visit_to_chunk( - &mut self, - byte_offset: usize, - next_byte_offset: Option, - visitable: &mut impl Visitable, - ) -> Result { - self.chunked(byte_offset, next_byte_offset, |fmt| { - visitable.visit(fmt)?; - Ok(()) - }) - } - - /// Transform [Visitable] items to the list of chunks - fn items_to_chunks<'b>( - &mut self, - next_byte_offset: Option, - items: impl Iterator + 'b, - ) -> Result> { - let mut items = items.peekable(); - let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); - while let Some((loc, item)) = items.next() { - let chunk_next_byte_offset = - items.peek().map(|(loc, _)| loc.start()).or(next_byte_offset); - - let chunk = if self.inline_config.is_disabled(loc) { - // If item format is disabled, we determine last disabled line from item and create - // chunk with raw src. - let mut disabled_loc = loc; - self.chunked(disabled_loc.start(), chunk_next_byte_offset, |fmt| { - while fmt.inline_config.is_disabled(disabled_loc) { - if let Some(next_line) = fmt.find_next_line(disabled_loc.end()) { - disabled_loc = disabled_loc.with_end(next_line); - } else { - break; - } - } - fmt.write_raw_src(disabled_loc)?; - Ok(()) - })? - } else { - self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)? - }; - out.push(chunk); - } - Ok(out) - } - - /// Transform [Visitable] items to a list of chunks and then sort those chunks. - fn items_to_chunks_sorted<'b>( - &mut self, - next_byte_offset: Option, - items: impl Iterator + 'b, - ) -> Result> { - let mut items = items.peekable(); - let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0)); - while let Some(item) = items.next() { - let chunk_next_byte_offset = - items.peek().map(|next| next.loc().start()).or(next_byte_offset); - let chunk = self.visit_to_chunk(item.loc().start(), chunk_next_byte_offset, item)?; - out.push((item, chunk)); - } - out.sort_by(|(a, _), (b, _)| a.cmp(b)); - Ok(out.into_iter().map(|(_, c)| c).collect()) - } - - /// Write a comment to the buffer formatted. - /// WARNING: This may introduce a newline if the comment is a Line comment - /// or if the comment are wrapped - fn write_comment(&mut self, comment: &CommentWithMetadata, is_first: bool) -> Result<()> { - if self.inline_config.is_disabled(comment.loc) { - return self.write_raw_comment(comment) - } - - match comment.position { - CommentPosition::Prefix => self.write_prefix_comment(comment, is_first), - CommentPosition::Postfix => self.write_postfix_comment(comment), - } - } - - /// Write a comment with position [CommentPosition::Prefix] - fn write_prefix_comment( - &mut self, - comment: &CommentWithMetadata, - is_first: bool, - ) -> Result<()> { - if !self.is_beginning_of_line() { - self.write_preserved_line()?; - } - if !is_first && comment.has_newline_before { - self.write_preserved_line()?; - } - - if matches!(comment.ty, CommentType::DocBlock) { - let mut lines = comment.contents().trim().lines(); - writeln!(self.buf(), "{}", comment.start_token())?; - lines.try_for_each(|l| self.write_doc_block_line(comment, l))?; - write!(self.buf(), " {}", comment.end_token().unwrap())?; - self.write_preserved_line()?; - return Ok(()) - } - - write!(self.buf(), "{}", comment.start_token())?; - - let mut wrapped = false; - let contents = comment.contents(); - let mut lines = contents.lines().peekable(); - while let Some(line) = lines.next() { - wrapped |= self.write_comment_line(comment, line)?; - if lines.peek().is_some() { - self.write_preserved_line()?; - } - } - - if let Some(end) = comment.end_token() { - // Check if the end token in the original comment was on the separate line - if !wrapped && comment.comment.lines().count() > contents.lines().count() { - self.write_preserved_line()?; - } - write!(self.buf(), "{end}")?; - } - if self.find_next_line(comment.loc.end()).is_some() { - self.write_preserved_line()?; - } - - Ok(()) - } - - /// Write a comment with position [CommentPosition::Postfix] - fn write_postfix_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { - let indented = self.is_beginning_of_line(); - self.indented_if(indented, 1, |fmt| { - if !indented && fmt.next_char_needs_space('/') { - fmt.write_whitespace_separator(false)?; - } - - write!(fmt.buf(), "{}", comment.start_token())?; - let start_token_pos = fmt.current_line_len(); - - let mut lines = comment.contents().lines().peekable(); - fmt.grouped(|fmt| { - while let Some(line) = lines.next() { - fmt.write_comment_line(comment, line)?; - if lines.peek().is_some() { - fmt.write_whitespace_separator(true)?; - } - } - Ok(()) - })?; - - if let Some(end) = comment.end_token() { - // If comment is not multiline, end token has to be aligned with the start - if fmt.is_beginning_of_line() { - write!(fmt.buf(), "{}{end}", " ".repeat(start_token_pos))?; - } else { - write!(fmt.buf(), "{end}")?; - } - } - - if comment.is_line() { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }) - } - - /// Write the line of a doc block comment line - fn write_doc_block_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<()> { - if line.trim().starts_with('*') { - let line = line.trim().trim_start_matches('*'); - let needs_space = line.chars().next().is_some_and(|ch| !ch.is_whitespace()); - write!(self.buf(), " *{}", if needs_space { " " } else { "" })?; - self.write_comment_line(comment, line)?; - self.write_whitespace_separator(true)?; - return Ok(()) - } - - let indent_whitespace_count = line - .char_indices() - .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len()) - .count(); - let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width; - write!(self.buf(), " *")?; - let content = &line[to_skip..]; - if !content.trim().is_empty() { - write!(self.buf(), " ")?; - self.write_comment_line(comment, &line[to_skip..])?; - } - self.write_whitespace_separator(true)?; - Ok(()) - } - - /// Write a comment line that might potentially overflow the maximum line length - /// and, if configured, will be wrapped to the next line. - fn write_comment_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result { - if self.will_it_fit(line) || !self.config.wrap_comments { - let start_with_ws = - line.chars().next().map(|ch| ch.is_whitespace()).unwrap_or_default(); - if !self.is_beginning_of_line() || !start_with_ws { - write!(self.buf(), "{line}")?; - return Ok(false) - } - - // if this is the beginning of the line, - // the comment should start with at least an indent - let indent = self.buf.current_indent_len(); - let mut chars = line - .char_indices() - .skip_while(|(idx, ch)| ch.is_whitespace() && *idx < indent) - .map(|(_, ch)| ch); - let padded = format!("{}{}", " ".repeat(indent), chars.join("")); - self.write_raw(padded)?; - return Ok(false) - } - - let mut words = line.split(' ').peekable(); - while let Some(word) = words.next() { - if self.is_beginning_of_line() { - write!(self.buf(), "{}", word.trim_start())?; - } else { - self.write_raw(word)?; - } - - if let Some(next) = words.peek() { - if !word.is_empty() && !self.will_it_fit(next) { - // the next word doesn't fit on this line, - // write remaining words on the next - self.write_whitespace_separator(true)?; - // write newline wrap token - write!(self.buf(), "{}", comment.wrap_token())?; - self.write_comment_line(comment, &words.join(" "))?; - return Ok(true) - } - - self.write_whitespace_separator(false)?; - } - } - Ok(false) - } - - /// Write a raw comment. This is like [`write_comment`](Self::write_comment) but won't do any - /// formatting or worry about whitespace behind the comment. - fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { - self.write_raw(&comment.comment)?; - if comment.is_line() { - self.write_preserved_line()?; - } - Ok(()) - } - - // TODO handle whitespace between comments for disabled sections - /// Write multiple comments - fn write_comments<'b>( - &mut self, - comments: impl IntoIterator, - ) -> Result<()> { - let mut comments = comments.into_iter().peekable(); - let mut last_byte_written = match comments.peek() { - Some(comment) => comment.loc.start(), - None => return Ok(()), - }; - let mut is_first = true; - for comment in comments { - let unwritten_whitespace_loc = - Loc::File(comment.loc.file_no(), last_byte_written, comment.loc.start()); - if self.inline_config.is_disabled(unwritten_whitespace_loc) { - self.write_raw(&self.source[unwritten_whitespace_loc.range()])?; - self.write_raw_comment(comment)?; - last_byte_written = if comment.is_line() { - self.find_next_line(comment.loc.end()).unwrap_or_else(|| comment.loc.end()) - } else { - comment.loc.end() - }; - } else { - self.write_comment(comment, is_first)?; - } - is_first = false; - } - Ok(()) - } - - /// Write a postfix comments before a given location - fn write_postfix_comments_before(&mut self, byte_end: usize) -> Result<()> { - let comments = self.comments.remove_postfixes_before(byte_end); - self.write_comments(&comments) - } - - /// Write all prefix comments before a given location - fn write_prefix_comments_before(&mut self, byte_end: usize) -> Result<()> { - let comments = self.comments.remove_prefixes_before(byte_end); - self.write_comments(&comments) - } - - /// Check if a chunk will fit on the current line - fn will_chunk_fit(&mut self, format_string: &str, chunk: &Chunk) -> Result { - if let Some(chunk_str) = self.simulate_to_single_line(|fmt| fmt.write_chunk(chunk))? { - Ok(self.will_it_fit(format_string.replacen("{}", &chunk_str, 1))) - } else { - Ok(false) - } - } - - /// Check if a separated list of chunks will fit on the current line - fn are_chunks_separated_multiline<'b>( - &mut self, - format_string: &str, - items: impl IntoIterator, - separator: &str, - ) -> Result { - let items = items.into_iter().collect_vec(); - if let Some(chunks) = self.simulate_to_single_line(|fmt| { - fmt.write_chunks_separated(items.iter().copied(), separator, false) - })? { - Ok(!self.will_it_fit(format_string.replacen("{}", &chunks, 1))) - } else { - Ok(true) - } - } - - /// Write the chunk and any surrounding comments into the buffer - /// This will automatically add whitespace before the chunk given the rule set in - /// `next_char_needs_space`. If the chunk does not fit on the current line it will be put on - /// to the next line - fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> { - // handle comments before chunk - self.write_comments(&chunk.postfixes_before)?; - self.write_comments(&chunk.prefixes)?; - - // trim chunk start - let content = if chunk.content.starts_with('\n') { - let mut chunk = chunk.content.trim_start().to_string(); - chunk.insert(0, '\n'); - chunk - } else if chunk.content.starts_with(' ') { - let mut chunk = chunk.content.trim_start().to_string(); - chunk.insert(0, ' '); - chunk - } else { - chunk.content.clone() - }; - - if !content.is_empty() { - // add whitespace if necessary - let needs_space = chunk - .needs_space - .unwrap_or_else(|| self.next_char_needs_space(content.chars().next().unwrap())); - if needs_space { - if self.will_it_fit(&content) { - write!(self.buf(), " ")?; - } else { - writeln!(self.buf())?; - } - } - - // write chunk - write!(self.buf(), "{content}")?; - } - - // write any postfix comments - self.write_comments(&chunk.postfixes)?; - - Ok(()) - } - - /// Write chunks separated by a separator. If `multiline`, each chunk will be written to a - /// separate line - fn write_chunks_separated<'b>( - &mut self, - chunks: impl IntoIterator, - separator: &str, - multiline: bool, - ) -> Result<()> { - let mut chunks = chunks.into_iter().peekable(); - while let Some(chunk) = chunks.next() { - let mut chunk = chunk.clone(); - - // handle postfixes before and add newline if necessary - self.write_comments(&std::mem::take(&mut chunk.postfixes_before))?; - if multiline && !self.is_beginning_of_line() { - writeln!(self.buf())?; - } - - // remove postfixes so we can add separator between - let postfixes = std::mem::take(&mut chunk.postfixes); - - self.write_chunk(&chunk)?; - - // add separator - if chunks.peek().is_some() { - write!(self.buf(), "{separator}")?; - self.write_comments(&postfixes)?; - if multiline && !self.is_beginning_of_line() { - writeln!(self.buf())?; - } - } else { - self.write_comments(&postfixes)?; - } - } - Ok(()) - } - - /// Apply the callback indented by the indent size - fn indented(&mut self, delta: usize, fun: impl FnMut(&mut Self) -> Result<()>) -> Result<()> { - self.indented_if(true, delta, fun) - } - - /// Apply the callback indented by the indent size if the condition is true - fn indented_if( - &mut self, - condition: bool, - delta: usize, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - if condition { - self.indent(delta); - } - let res = fun(self); - if condition { - self.dedent(delta); - } - res?; - Ok(()) - } - - /// Apply the callback into an indent group. The first line of the indent group is not - /// indented but lines thereafter are - fn grouped(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - self.start_group(); - let res = fun(self); - let indented = !self.last_indent_group_skipped(); - self.end_group(); - res?; - Ok(indented) - } - - /// Add a function context around a procedure and revert the context at the end of the procedure - /// regardless of the response - fn with_function_context( - &mut self, - context: FunctionDefinition, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - self.context.function = Some(context); - let res = fun(self); - self.context.function = None; - res - } - - /// Add a contract context around a procedure and revert the context at the end of the procedure - /// regardless of the response - fn with_contract_context( - &mut self, - context: ContractDefinition, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { - self.context.contract = Some(context); - let res = fun(self); - self.context.contract = None; - res - } - - /// Create a transaction. The result of the transaction is not applied to the buffer unless - /// `Transacton::commit` is called - fn transact<'b>( - &'b mut self, - fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - Transaction::new(self, fun) - } - - /// Do the callback and return the result on the buffer as a string - fn simulate_to_string(&mut self, fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - Ok(self.transact(fun)?.buffer) - } - - /// Turn a chunk and its surrounding comments into a string - fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { - self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) - } - - /// Try to create a string based on a callback. If the string does not fit on a single line - /// this will return `None` - fn simulate_to_single_line( - &mut self, - mut fun: impl FnMut(&mut Self) -> Result<()>, - ) -> Result> { - let mut single_line = false; - let tx = self.transact(|fmt| { - fmt.restrict_to_single_line(true); - single_line = match fun(fmt) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - Ok(if single_line && tx.will_it_fit(&tx.buffer) { Some(tx.buffer) } else { None }) - } - - /// Try to apply a callback to a single line. If the callback cannot be applied to a single - /// line the callback will not be applied to the buffer and `false` will be returned. Otherwise - /// `true` will be returned - fn try_on_single_line(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result { - let mut single_line = false; - let tx = self.transact(|fmt| { - fmt.restrict_to_single_line(true); - single_line = match fun(fmt) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - Ok(if single_line && tx.will_it_fit(&tx.buffer) { - tx.commit()?; - true - } else { - false - }) - } - - /// Surrounds a callback with parentheses. The callback will try to be applied to a single - /// line. If the callback cannot be applied to a single line the callback will applied to the - /// nextline indented. The callback receives a `multiline` hint as the second argument which - /// receives `true` in the latter case - fn surrounded( - &mut self, - first: SurroundingChunk, - last: SurroundingChunk, - mut fun: impl FnMut(&mut Self, bool) -> Result<()>, - ) -> Result<()> { - let first_chunk = - self.chunk_at(first.loc_before(), first.loc_next(), first.spaced, first.content); - self.write_chunk(&first_chunk)?; - - let multiline = !self.try_on_single_line(|fmt| { - fun(fmt, false)?; - let last_chunk = - fmt.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content); - fmt.write_chunk(&last_chunk)?; - Ok(()) - })?; - - if multiline { - self.indented(1, |fmt| { - fmt.write_whitespace_separator(true)?; - let stringified = fmt.with_temp_buf(|fmt| fun(fmt, true))?.w; - write_chunk!(fmt, "{}", stringified.trim_start()) - })?; - if !last.content.trim_start().is_empty() { - self.indented(1, |fmt| fmt.write_whitespace_separator(true))?; - } - let last_chunk = - self.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content); - self.write_chunk(&last_chunk)?; - } - - Ok(()) - } - - /// Write each [Visitable] item on a separate line. The function will check if there are any - /// blank lines between each visitable statement and will apply a single blank line if there - /// exists any. The `needs_space` callback can force a newline and is given the last_item if - /// any and the next item as arguments - fn write_lined_visitable<'b, I, V, F>( - &mut self, - loc: Loc, - items: I, - needs_space_fn: F, - ) -> Result<()> - where - I: Iterator + 'b, - V: Visitable + CodeLocation + 'b, - F: Fn(&V, &V) -> bool, - { - let mut items = items.collect::>(); - items.reverse(); - // get next item - let pop_next = |fmt: &mut Self, items: &mut Vec<&'b mut V>| { - let comment = - fmt.comments.iter().next().filter(|comment| comment.loc.end() < loc.end()); - let item = items.last(); - if let (Some(comment), Some(item)) = (comment, item) { - if comment.loc < item.loc() { - Some(Either::Left(fmt.comments.pop().unwrap())) - } else { - Some(Either::Right(items.pop().unwrap())) - } - } else if comment.is_some() { - Some(Either::Left(fmt.comments.pop().unwrap())) - } else if item.is_some() { - Some(Either::Right(items.pop().unwrap())) - } else { - None - } - }; - // get whitespace between to offsets. this needs to account for possible left over - // semicolons which are not included in the `Loc` - let unwritten_whitespace = |from: usize, to: usize| { - let to = to.max(from); - let mut loc = Loc::File(loc.file_no(), from, to); - let src = &self.source[from..to]; - if let Some(semi) = src.find(';') { - loc = loc.with_start(from + semi + 1); - } - (loc, &self.source[loc.range()]) - }; - - let mut last_byte_written = match ( - self.comments.iter().next().filter(|comment| comment.loc.end() < loc.end()), - items.last(), - ) { - (Some(comment), Some(item)) => comment.loc.min(item.loc()), - (None, Some(item)) => item.loc(), - (Some(comment), None) => comment.loc, - (None, None) => return Ok(()), - } - .start(); - - let mut last_loc: Option = None; - let mut visited_locs: Vec = Vec::new(); - - // marker for whether the next item needs additional space - let mut needs_space = false; - let mut last_comment = None; - - while let Some(mut line_item) = pop_next(self, &mut items) { - let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc()); - let (unwritten_whitespace_loc, unwritten_whitespace) = - unwritten_whitespace(last_byte_written, loc.start()); - let ignore_whitespace = if self.inline_config.is_disabled(unwritten_whitespace_loc) { - trace!("Unwritten whitespace: {unwritten_whitespace:?}"); - self.write_raw(unwritten_whitespace)?; - true - } else { - false - }; - match line_item.as_mut() { - Either::Left(comment) => { - if ignore_whitespace { - self.write_raw_comment(comment)?; - if unwritten_whitespace.contains('\n') { - needs_space = false; - } - } else { - self.write_comment(comment, last_loc.is_none())?; - if last_loc.is_some() && comment.has_newline_before { - needs_space = false; - } - } - } - Either::Right(item) => { - if !ignore_whitespace { - self.write_whitespace_separator(true)?; - if let Some(mut last_loc) = last_loc { - // here's an edge case when we reordered items so the last_loc isn't - // necessarily the item that directly precedes the current item because - // the order might have changed, so we need to find the last item that - // is before the current item by checking the recorded locations - if let Some(last_item) = visited_locs - .iter() - .rev() - .find(|prev_item| prev_item.start() > last_loc.end()) - { - last_loc = *last_item; - } - - // The blank lines check is susceptible additional trailing new lines - // because the block docs can contain - // multiple lines, but the function def should follow directly after the - // block comment - let is_last_doc_comment = matches!( - last_comment, - Some(CommentWithMetadata { ty: CommentType::DocBlock, .. }) - ); - - if needs_space || - (!is_last_doc_comment && - self.blank_lines(last_loc.end(), loc.start()) > 1) - { - writeln!(self.buf())?; - } - } - } - if let Some(next_item) = items.last() { - needs_space = needs_space_fn(item, next_item); - } - trace!("Visiting {}", { - let n = std::any::type_name::(); - n.strip_prefix("solang_parser::pt::").unwrap_or(n) - }); - item.visit(self)?; - } - } - - last_loc = Some(loc); - visited_locs.push(loc); - - last_comment = None; - - last_byte_written = loc.end(); - if let Some(comment) = line_item.left() { - if comment.is_line() { - last_byte_written = - self.find_next_line(last_byte_written).unwrap_or(last_byte_written); - } - last_comment = Some(comment); - } - } - - // write manually to avoid eof comment being detected as first - let comments = self.comments.remove_prefixes_before(loc.end()); - for comment in comments { - self.write_comment(&comment, false)?; - } - - let (unwritten_src_loc, mut unwritten_whitespace) = - unwritten_whitespace(last_byte_written, loc.end()); - if self.inline_config.is_disabled(unwritten_src_loc) { - if unwritten_src_loc.end() == self.source.len() { - // remove EOF line ending - unwritten_whitespace = unwritten_whitespace - .strip_suffix('\n') - .map(|w| w.strip_suffix('\r').unwrap_or(w)) - .unwrap_or(unwritten_whitespace); - } - trace!("Unwritten whitespace: {unwritten_whitespace:?}"); - self.write_raw(unwritten_whitespace)?; - } - - Ok(()) - } - - /// Visit the right side of an assignment. The function will try to write the assignment on a - /// single line or indented on the next line. If it can't do this it resorts to letting the - /// expression decide how to split itself on multiple lines - fn visit_assignment(&mut self, expr: &mut Expression) -> Result<()> { - if self.try_on_single_line(|fmt| expr.visit(fmt))? { - return Ok(()) - } - - self.write_postfix_comments_before(expr.loc().start())?; - self.write_prefix_comments_before(expr.loc().start())?; - - if self.try_on_single_line(|fmt| fmt.indented(1, |fmt| expr.visit(fmt)))? { - return Ok(()) - } - - let mut fit_on_next_line = false; - self.indented(1, |fmt| { - let tx = fmt.transact(|fmt| { - writeln!(fmt.buf())?; - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - } - Ok(()) - })?; - - if !fit_on_next_line { - self.indented_if(expr.is_unsplittable(), 1, |fmt| expr.visit(fmt))?; - } - - Ok(()) - } - - /// Visit the list of comma separated items. - /// If the prefix is not empty, then the function will write - /// the whitespace before the parentheses (if they are required). - fn visit_list( - &mut self, - prefix: &str, - items: &mut [T], - start_offset: Option, - end_offset: Option, - paren_required: bool, - ) -> Result<()> - where - T: Visitable + CodeLocation, - { - write_chunk!(self, "{}", prefix)?; - let whitespace = if !prefix.is_empty() { " " } else { "" }; - let next_after_start_offset = items.first().map(|item| item.loc().start()); - let first_surrounding = SurroundingChunk::new("", start_offset, next_after_start_offset); - let last_surronding = SurroundingChunk::new(")", None, end_offset); - if items.is_empty() { - if paren_required { - write!(self.buf(), "{whitespace}(")?; - self.surrounded(first_surrounding, last_surronding, |fmt, _| { - // write comments before the list end - write_chunk!(fmt, end_offset.unwrap_or_default(), "")?; - Ok(()) - })?; - } - } else { - write!(self.buf(), "{whitespace}(")?; - self.surrounded(first_surrounding, last_surronding, |fmt, multiline| { - let args = - fmt.items_to_chunks(end_offset, items.iter_mut().map(|arg| (arg.loc(), arg)))?; - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - })?; - } - Ok(()) - } - - /// Visit the block item. Attempt to write it on the single - /// line if requested. Surround by curly braces and indent - /// each line otherwise. Returns `true` if the block fit - /// on a single line - fn visit_block( - &mut self, - loc: Loc, - statements: &mut [T], - attempt_single_line: bool, - attempt_omit_braces: bool, - ) -> Result - where - T: Visitable + CodeLocation, - { - if attempt_single_line && statements.len() == 1 { - let fits_on_single = self.try_on_single_line(|fmt| { - if !attempt_omit_braces { - write!(fmt.buf(), "{{ ")?; - } - statements.first_mut().unwrap().visit(fmt)?; - if !attempt_omit_braces { - write!(fmt.buf(), " }}")?; - } - Ok(()) - })?; - - if fits_on_single { - return Ok(true) - } - } - - // Determine if any of start / end of the block is disabled and block lines boundaries. - let is_start_disabled = self.inline_config.is_disabled(loc.with_end(loc.start())); - let is_end_disabled = self.inline_config.is_disabled(loc.with_start(loc.end())); - let end_of_first_line = self.find_next_line(loc.start()).unwrap_or_default(); - let end_of_last_line = self.find_next_line(loc.end()).unwrap_or_default(); - - // Write first line of the block: - // - as it is until the end of line, if format disabled - // - start block if line formatted - if is_start_disabled { - self.write_raw_src(loc.with_end(end_of_first_line))?; - } else { - write_chunk!(self, "{{")?; - } - - // Write comments and close block if no statement. - if statements.is_empty() { - self.indented(1, |fmt| { - fmt.write_prefix_comments_before(loc.end())?; - fmt.write_postfix_comments_before(loc.end())?; - Ok(()) - })?; - - write_chunk!(self, "}}")?; - return Ok(false) - } - - // Determine writable statements by excluding statements from disabled start / end lines. - // We check the position of last statement from first line (if disabled) and position of - // first statement from last line (if disabled) and slice accordingly. - let writable_statments = match ( - statements.iter().rposition(|stmt| { - is_start_disabled && - self.find_next_line(stmt.loc().end()).unwrap_or_default() == - end_of_first_line - }), - statements.iter().position(|stmt| { - is_end_disabled && - self.find_next_line(stmt.loc().end()).unwrap_or_default() == end_of_last_line - }), - ) { - // We have statements on both disabled start / end lines. - (Some(start), Some(end)) => { - if start == end || start + 1 == end { - None - } else { - Some(&mut statements[start + 1..end]) - } - } - // We have statements only on disabled start line. - (Some(start), None) => { - if start + 1 == statements.len() { - None - } else { - Some(&mut statements[start + 1..]) - } - } - // We have statements only on disabled end line. - (None, Some(end)) => { - if end == 0 { - None - } else { - Some(&mut statements[..end]) - } - } - // No statements on disabled start / end line. - (None, None) => Some(statements), - }; - - // Write statements that are not on any disabled first / last block line. - let mut statements_loc = loc; - if let Some(writable_statements) = writable_statments { - if let Some(first_statement) = writable_statements.first() { - statements_loc = statements_loc.with_start(first_statement.loc().start()); - self.write_whitespace_separator(true)?; - self.write_postfix_comments_before(statements_loc.start())?; - } - // If last line is disabled then statements location ends where last block line starts. - if is_end_disabled { - if let Some(last_statement) = writable_statements.last() { - statements_loc = statements_loc.with_end( - self.find_next_line(last_statement.loc().end()).unwrap_or_default(), - ); - } - } - self.indented(1, |fmt| { - fmt.write_lined_visitable( - statements_loc, - writable_statements.iter_mut(), - |_, _| false, - )?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - } - - // Write last line of the block: - // - as it is from where statements location ends until the end of last line, if format - // disabled - // - close block if line formatted - if is_end_disabled { - self.write_raw_src(loc.with_start(statements_loc.end()).with_end(end_of_last_line))?; - } else { - if end_of_first_line != end_of_last_line { - self.write_whitespace_separator(true)?; - } - write_chunk!(self, loc.end(), "}}")?; - } - - Ok(false) - } - - /// Visit statement as `Statement::Block`. - fn visit_stmt_as_block( - &mut self, - stmt: &mut Statement, - attempt_single_line: bool, - ) -> Result { - match stmt { - Statement::Block { loc, statements, .. } => { - self.visit_block(*loc, statements, attempt_single_line, true) - } - _ => self.visit_block(stmt.loc(), &mut [stmt], attempt_single_line, true), - } - } - - /// Visit the generic member access expression and - /// attempt flatten it by checking if the inner expression - /// matches a given member access variant. - fn visit_member_access<'b, T, M>( - &mut self, - expr: &'b mut Box, - ident: &mut Identifier, - mut matcher: M, - ) -> Result<()> - where - T: CodeLocation + Visitable, - M: FnMut(&mut Self, &'b mut Box) -> Result, &'b mut Identifier)>>, - { - let chunk_member_access = |fmt: &mut Self, ident: &mut Identifier, expr: &mut Box| { - fmt.chunked(ident.loc.start(), Some(expr.loc().start()), |fmt| ident.visit(fmt)) - }; - - let mut chunks: Vec = vec![chunk_member_access(self, ident, expr)?]; - let mut remaining = expr; - while let Some((inner_expr, inner_ident)) = matcher(self, remaining)? { - chunks.push(chunk_member_access(self, inner_ident, inner_expr)?); - remaining = inner_expr; - } - - chunks.reverse(); - chunks.iter_mut().for_each(|chunk| chunk.content.insert(0, '.')); - - if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { - self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; - } - Ok(()) - } - - /// Visit the yul string with an optional identifier. - /// If the identifier is present, write the value in the format `:`. - /// - /// Ref: - fn visit_yul_string_with_ident( - &mut self, - loc: Loc, - val: &str, - ident: &mut Option, - ) -> Result<()> { - let ident = - if let Some(ident) = ident { format!(":{}", ident.name) } else { String::new() }; - write_chunk!(self, loc.start(), loc.end(), "{val}{ident}")?; - Ok(()) - } - - /// Format a quoted string as `prefix"string"` where the quote character is handled - /// by the configuration `quote_style` - fn quote_str(&self, loc: Loc, prefix: Option<&str>, string: &str) -> String { - let get_og_quote = || { - self.source[loc.range()] - .quote_state_char_indices() - .find_map( - |(state, _, ch)| { - if matches!(state, QuoteState::Opening(_)) { - Some(ch) - } else { - None - } - }, - ) - .expect("Could not find quote character for quoted string") - }; - let mut quote = self.config.quote_style.quote().unwrap_or_else(get_og_quote); - let mut quoted = format!("{quote}{string}{quote}"); - if !quoted.is_quoted() { - quote = get_og_quote(); - quoted = format!("{quote}{string}{quote}"); - } - let prefix = prefix.unwrap_or(""); - format!("{prefix}{quoted}") - } - - /// Write a quoted string. See `Formatter::quote_str` for more information - fn write_quoted_str(&mut self, loc: Loc, prefix: Option<&str>, string: &str) -> Result<()> { - write_chunk!(self, loc.start(), loc.end(), "{}", self.quote_str(loc, prefix, string)) - } - - /// Write and format numbers. This will fix underscores as well as remove unnecessary 0's and - /// exponents - fn write_num_literal( - &mut self, - loc: Loc, - value: &str, - fractional: Option<&str>, - exponent: &str, - unit: &mut Option, - ) -> Result<()> { - let config = self.config.number_underscore; - - // get source if we preserve underscores - let (value, fractional, exponent) = if config.is_preserve() { - let source = &self.source[loc.start()..loc.end()]; - // Strip unit - let (source, _) = source.split_once(' ').unwrap_or((source, "")); - let (val, exp) = source.split_once(['e', 'E']).unwrap_or((source, "")); - let (val, fract) = - val.split_once('.').map(|(val, fract)| (val, Some(fract))).unwrap_or((val, None)); - ( - val.trim().to_string(), - fract.map(|fract| fract.trim().to_string()), - exp.trim().to_string(), - ) - } else { - // otherwise strip underscores - ( - value.trim().replace('_', ""), - fractional.map(|fract| fract.trim().replace('_', "")), - exponent.trim().replace('_', ""), - ) - }; - - // strip any padded 0's - let val = value.trim_start_matches('0'); - let fract = fractional.as_ref().map(|fract| fract.trim_end_matches('0')); - let (exp_sign, mut exp) = if let Some(exp) = exponent.strip_prefix('-') { - ("-", exp) - } else { - ("", exponent.as_str()) - }; - exp = exp.trim().trim_start_matches('0'); - - let add_underscores = |string: &str, reversed: bool| -> String { - if !config.is_thousands() || string.len() < 5 { - return string.to_string() - } - if reversed { - Box::new(string.as_bytes().chunks(3)) as Box> - } else { - Box::new(string.as_bytes().rchunks(3).rev()) as Box> - } - .map(|chunk| std::str::from_utf8(chunk).expect("valid utf8 content.")) - .collect::>() - .join("_") - }; - - let mut out = String::new(); - if val.is_empty() { - out.push('0'); - } else { - out.push_str(&add_underscores(val, false)); - } - if let Some(fract) = fract { - out.push('.'); - if fract.is_empty() { - out.push('0'); - } else { - // TODO re-enable me on the next solang-parser v0.1.18 - // currently disabled because of the following bug - // https://github.com/hyperledger-labs/solang/pull/954 - // out.push_str(&add_underscores(fract, true)); - out.push_str(fract) - } - } - if !exp.is_empty() { - out.push('e'); - out.push_str(exp_sign); - out.push_str(&add_underscores(exp, false)); - } - - write_chunk!(self, loc.start(), loc.end(), "{out}")?; - self.write_unit(unit) - } - - /// Write and hex literals according to the configuration. - fn write_hex_literal(&mut self, lit: &HexLiteral) -> Result<()> { - let HexLiteral { loc, hex } = lit; - match self.config.hex_underscore { - HexUnderscore::Remove => self.write_quoted_str(*loc, Some("hex"), hex), - HexUnderscore::Preserve => { - let quote = &self.source[loc.start()..loc.end()].trim_start_matches("hex"); - // source is always quoted so we remove the quotes first so we can adhere to the - // configured quoting style - let hex = "e[1..quote.len() - 1]; - self.write_quoted_str(*loc, Some("hex"), hex) - } - HexUnderscore::Bytes => { - // split all bytes - let hex = hex - .chars() - .chunks(2) - .into_iter() - .map(|chunk| chunk.collect::()) - .collect::>() - .join("_"); - self.write_quoted_str(*loc, Some("hex"), &hex) - } - } - } - - /// Write built-in unit. - fn write_unit(&mut self, unit: &mut Option) -> Result<()> { - if let Some(unit) = unit { - write_chunk!(self, unit.loc.start(), unit.loc.end(), "{}", unit.name)?; - } - Ok(()) - } - - /// Write the function header - fn write_function_header( - &mut self, - func: &mut FunctionDefinition, - body_loc: Option, - header_multiline: bool, - ) -> Result { - let func_name = if let Some(ident) = &func.name { - format!("{} {}", func.ty, ident.name) - } else { - func.ty.to_string() - }; - - // calculate locations of chunk groups - let attrs_loc = func.attributes.first().map(|attr| attr.loc()); - let returns_loc = func.returns.first().map(|param| param.0); - - let params_next_offset = attrs_loc - .as_ref() - .or(returns_loc.as_ref()) - .or(body_loc.as_ref()) - .map(|loc| loc.start()); - let attrs_end = returns_loc.as_ref().or(body_loc.as_ref()).map(|loc| loc.start()); - let returns_end = body_loc.as_ref().map(|loc| loc.start()); - - let mut params_multiline = false; - - let params_loc = { - let mut loc = func.loc.with_end(func.loc.start()); - self.extend_loc_until(&mut loc, ')'); - loc - }; - let params_disabled = self.inline_config.is_disabled(params_loc); - if params_disabled { - let chunk = self.chunked(func.loc.start(), None, |fmt| fmt.visit_source(params_loc))?; - params_multiline = chunk.content.contains('\n'); - self.write_chunk(&chunk)?; - } else { - let first_surrounding = SurroundingChunk::new( - format!("{func_name}("), - Some(func.loc.start()), - Some( - func.params - .first() - .map(|param| param.0.start()) - .unwrap_or_else(|| params_loc.end()), - ), - ); - self.surrounded( - first_surrounding, - SurroundingChunk::new(")", None, params_next_offset), - |fmt, multiline| { - let params = fmt.items_to_chunks( - params_next_offset, - func.params - .iter_mut() - .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), - )?; - let after_params = if !func.attributes.is_empty() || !func.returns.is_empty() { - "" - } else if func.body.is_some() { - " {" - } else { - ";" - }; - let should_multiline = header_multiline && - matches!( - fmt.config.multiline_func_header, - MultilineFuncHeaderStyle::ParamsFirst | - MultilineFuncHeaderStyle::ParamsFirstMulti | - MultilineFuncHeaderStyle::All | - MultilineFuncHeaderStyle::AllParams - ); - params_multiline = should_multiline || - multiline || - fmt.are_chunks_separated_multiline( - &format!("{{}}){after_params}"), - ¶ms, - ",", - )?; - // Write new line if we have only one parameter and params first set, - // or if the function definition is multiline and all params set. - let single_param_multiline = matches!( - fmt.config.multiline_func_header, - MultilineFuncHeaderStyle::ParamsFirst - ) || params_multiline && - matches!( - fmt.config.multiline_func_header, - MultilineFuncHeaderStyle::AllParams - ); - if params.len() == 1 && single_param_multiline { - writeln!(fmt.buf())?; - } - fmt.write_chunks_separated(¶ms, ",", params_multiline)?; - Ok(()) - }, - )?; - } - - let mut write_attributes = |fmt: &mut Self, multiline: bool| -> Result<()> { - // write attributes - if !func.attributes.is_empty() { - let attrs_loc = func - .attributes - .first() - .unwrap() - .loc() - .with_end_from(&func.attributes.last().unwrap().loc()); - if fmt.inline_config.is_disabled(attrs_loc) { - // If params are also disabled then write functions attributes on the same line. - if params_disabled { - fmt.write_whitespace_separator(false)?; - let attrs_src = - String::from_utf8(self.source.as_bytes()[attrs_loc.range()].to_vec()) - .map_err(FormatterError::custom)?; - fmt.write_raw(attrs_src)?; - } else { - fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?; - } - } else { - fmt.write_postfix_comments_before(attrs_loc.start())?; - fmt.write_whitespace_separator(multiline)?; - let attributes = - fmt.items_to_chunks_sorted(attrs_end, func.attributes.iter_mut())?; - fmt.indented(1, |fmt| { - fmt.write_chunks_separated(&attributes, "", multiline)?; - Ok(()) - })?; - } - } - - // write returns - if !func.returns.is_empty() { - let returns_start_loc = func.returns.first().unwrap().0; - let returns_loc = returns_start_loc.with_end_from(&func.returns.last().unwrap().0); - if fmt.inline_config.is_disabled(returns_loc) { - fmt.write_whitespace_separator(false)?; - let returns_src = - String::from_utf8(self.source.as_bytes()[returns_loc.range()].to_vec()) - .map_err(FormatterError::custom)?; - fmt.write_raw(format!("returns ({returns_src})"))?; - } else { - let mut returns = fmt.items_to_chunks( - returns_end, - func.returns - .iter_mut() - .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), - )?; - - // there's an issue with function return value that would lead to indent issues because those can be formatted with line breaks - for function_chunk in - returns.iter_mut().filter(|chunk| chunk.content.starts_with("function(")) - { - // this will bypass the recursive indent that was applied when the function - // content was formatted in the chunk - function_chunk.content = function_chunk - .content - .split('\n') - .map(|s| s.trim_start()) - .collect::>() - .join("\n"); - } - - fmt.write_postfix_comments_before(returns_loc.start())?; - fmt.write_whitespace_separator(multiline)?; - fmt.indented(1, |fmt| { - fmt.surrounded( - SurroundingChunk::new("returns (", Some(returns_loc.start()), None), - SurroundingChunk::new(")", None, returns_end), - |fmt, multiline_hint| { - fmt.write_chunks_separated(&returns, ",", multiline_hint)?; - Ok(()) - }, - )?; - Ok(()) - })?; - } - } - Ok(()) - }; - - let should_multiline = header_multiline && - if params_multiline { - matches!( - self.config.multiline_func_header, - MultilineFuncHeaderStyle::All | MultilineFuncHeaderStyle::AllParams - ) - } else { - matches!( - self.config.multiline_func_header, - MultilineFuncHeaderStyle::AttributesFirst - ) - }; - let attrs_multiline = should_multiline || - !self.try_on_single_line(|fmt| { - write_attributes(fmt, false)?; - if !fmt.will_it_fit(if func.body.is_some() { " {" } else { ";" }) { - bail!(FormatterError::fmt()) - } - Ok(()) - })?; - if attrs_multiline { - write_attributes(self, true)?; - } - Ok(attrs_multiline) - } - - /// Write potentially nested `if statements` - fn write_if_stmt( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - ) -> Result<(), FormatterError> { - let single_line_stmt_wide = self.context.if_stmt_single_line.unwrap_or_default(); - - visit_source_if_disabled_else!(self, loc.with_end(if_branch.loc().start()), { - self.surrounded( - SurroundingChunk::new("if (", Some(loc.start()), Some(cond.loc().start())), - SurroundingChunk::new(")", None, Some(if_branch.loc().start())), - |fmt, _| { - fmt.write_prefix_comments_before(cond.loc().end())?; - cond.visit(fmt)?; - fmt.write_postfix_comments_before(if_branch.loc().start()) - }, - )?; - }); - - let cond_close_paren_loc = - self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = single_line_stmt_wide && - self.should_attempt_block_single_line(if_branch.as_mut(), cond_close_paren_loc); - let if_branch_is_single_line = self.visit_stmt_as_block(if_branch, attempt_single_line)?; - if single_line_stmt_wide && !if_branch_is_single_line { - bail!(FormatterError::fmt()) - } - - if let Some(else_branch) = else_branch { - self.write_postfix_comments_before(else_branch.loc().start())?; - if if_branch_is_single_line { - writeln!(self.buf())?; - } - write_chunk!(self, else_branch.loc().start(), "else")?; - if let Statement::If(loc, cond, if_branch, else_branch) = else_branch.as_mut() { - self.visit_if(*loc, cond, if_branch, else_branch, false)?; - } else { - let else_branch_is_single_line = - self.visit_stmt_as_block(else_branch, attempt_single_line)?; - if single_line_stmt_wide && !else_branch_is_single_line { - bail!(FormatterError::fmt()) - } - } - } - Ok(()) - } - - /// Sorts grouped import statement alphabetically. - fn sort_imports(&self, source_unit: &mut SourceUnit) { - // first we need to find the grouped import statements - // A group is defined as a set of import statements that are separated by a blank line - let mut import_groups = Vec::new(); - let mut current_group = Vec::new(); - let mut source_unit_parts = source_unit.0.iter().enumerate().peekable(); - while let Some((i, part)) = source_unit_parts.next() { - if let SourceUnitPart::ImportDirective(_) = part { - current_group.push(i); - let current_loc = part.loc(); - if let Some((_, next_part)) = source_unit_parts.peek() { - let next_loc = next_part.loc(); - // import statements are followed by a new line, so if there are more than one - // we have a group - if self.blank_lines(current_loc.end(), next_loc.start()) > 1 { - import_groups.push(std::mem::take(&mut current_group)); - } - } - } else if !current_group.is_empty() { - import_groups.push(std::mem::take(&mut current_group)); - } - } - - if !current_group.is_empty() { - import_groups.push(current_group); - } - - if import_groups.is_empty() { - // nothing to sort - return - } - - // order all groups alphabetically - for group in &import_groups { - // SAFETY: group is not empty - let first = group[0]; - let last = group.last().copied().expect("group is not empty"); - let import_directives = &mut source_unit.0[first..=last]; - - // sort rename style imports alphabetically based on the actual import and not the - // rename - for source_unit_part in import_directives.iter_mut() { - if let SourceUnitPart::ImportDirective(Import::Rename(_, renames, _)) = - source_unit_part - { - renames.sort_by_cached_key(|(og_ident, _)| og_ident.name.clone()); - } - } - - import_directives.sort_by_cached_key(|item| match item { - SourceUnitPart::ImportDirective(import) => match import { - Import::Plain(path, _) => path.to_string(), - Import::GlobalSymbol(path, _, _) => path.to_string(), - Import::Rename(path, _, _) => path.to_string(), - }, - _ => { - unreachable!("import group contains non-import statement") - } - }); - } - } -} - -// Traverse the Solidity Parse Tree and write to the code formatter -impl Visitor for Formatter<'_, W> { - type Error = FormatterError; - - #[instrument(name = "source", skip(self))] - fn visit_source(&mut self, loc: Loc) -> Result<()> { - let source = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec()) - .map_err(FormatterError::custom)?; - let mut lines = source.splitn(2, '\n'); - - write_chunk!(self, loc.start(), "{}", lines.next().unwrap())?; - if let Some(remainder) = lines.next() { - // Call with `self.write_str` and not `write!`, so we can have `\n` at the beginning - // without triggering an indentation - self.write_raw(format!("\n{remainder}"))?; - } - - let _ = self.comments.remove_all_comments_before(loc.end()); - - Ok(()) - } - - #[instrument(name = "SU", skip_all)] - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> { - if self.config.sort_imports { - self.sort_imports(source_unit); - } - // TODO: do we need to put pragma and import directives at the top of the file? - // source_unit.0.sort_by_key(|item| match item { - // SourceUnitPart::PragmaDirective(_, _, _) => 0, - // SourceUnitPart::ImportDirective(_, _) => 1, - // _ => usize::MAX, - // }); - let loc = Loc::File( - source_unit - .loc_opt() - .or_else(|| self.comments.iter().next().map(|comment| comment.loc)) - .map(|loc| loc.file_no()) - .unwrap_or_default(), - 0, - self.source.len(), - ); - - self.write_lined_visitable( - loc, - source_unit.0.iter_mut(), - |last_unit, unit| match last_unit { - SourceUnitPart::PragmaDirective(..) => { - !matches!(unit, SourceUnitPart::PragmaDirective(..)) - } - SourceUnitPart::ImportDirective(_) => { - !matches!(unit, SourceUnitPart::ImportDirective(_)) - } - SourceUnitPart::ErrorDefinition(_) => { - !matches!(unit, SourceUnitPart::ErrorDefinition(_)) - } - SourceUnitPart::Using(_) => !matches!(unit, SourceUnitPart::Using(_)), - SourceUnitPart::VariableDefinition(_) => { - !matches!(unit, SourceUnitPart::VariableDefinition(_)) - } - SourceUnitPart::Annotation(_) => false, - _ => true, - }, - )?; - - // EOF newline - if self.last_char() != Some('\n') { - writeln!(self.buf())?; - } - - Ok(()) - } - - #[instrument(name = "contract", skip_all)] - fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<()> { - return_source_if_disabled!(self, contract.loc); - - self.with_contract_context(contract.clone(), |fmt| { - let contract_name = contract.name.safe_unwrap(); - - visit_source_if_disabled_else!( - fmt, - contract.loc.with_end_from( - &contract.base.first().map(|b| b.loc).unwrap_or(contract_name.loc) - ), - { - fmt.grouped(|fmt| { - write_chunk!(fmt, contract.loc.start(), "{}", contract.ty)?; - write_chunk!(fmt, contract_name.loc.end(), "{}", contract_name.name)?; - if !contract.base.is_empty() { - write_chunk!( - fmt, - contract_name.loc.end(), - contract.base.first().unwrap().loc.start(), - "is" - )?; - } - Ok(()) - })?; - } - ); - - if !contract.base.is_empty() { - visit_source_if_disabled_else!( - fmt, - contract - .base - .first() - .unwrap() - .loc - .with_end_from(&contract.base.last().unwrap().loc), - { - fmt.indented(1, |fmt| { - let base_end = contract.parts.first().map(|part| part.loc().start()); - let bases = fmt.items_to_chunks( - base_end, - contract.base.iter_mut().map(|base| (base.loc, base)), - )?; - let multiline = - fmt.are_chunks_separated_multiline("{}", &bases, ",")?; - fmt.write_chunks_separated(&bases, ",", multiline)?; - fmt.write_whitespace_separator(multiline)?; - Ok(()) - })?; - } - ); - } - - if let Some(layout) = &mut contract.layout { - write_chunk!(fmt, "layout at ")?; - fmt.visit_expr(layout.loc(), layout)?; - write_chunk!(fmt, " ")?; - } - - write_chunk!(fmt, "{{")?; - - fmt.indented(1, |fmt| { - if let Some(first) = contract.parts.first() { - fmt.write_postfix_comments_before(first.loc().start())?; - fmt.write_whitespace_separator(true)?; - } else { - return Ok(()) - } - - if fmt.config.contract_new_lines { - write_chunk!(fmt, "\n")?; - } - - fmt.write_lined_visitable( - contract.loc, - contract.parts.iter_mut(), - |last_part, part| match last_part { - ContractPart::ErrorDefinition(_) => { - !matches!(part, ContractPart::ErrorDefinition(_)) - } - ContractPart::EventDefinition(_) => { - !matches!(part, ContractPart::EventDefinition(_)) - } - ContractPart::VariableDefinition(_) => { - !matches!(part, ContractPart::VariableDefinition(_)) - } - ContractPart::TypeDefinition(_) => { - !matches!(part, ContractPart::TypeDefinition(_)) - } - ContractPart::EnumDefinition(_) => { - !matches!(part, ContractPart::EnumDefinition(_)) - } - ContractPart::Using(_) => !matches!(part, ContractPart::Using(_)), - ContractPart::FunctionDefinition(last_def) => { - if last_def.is_empty() { - match part { - ContractPart::FunctionDefinition(def) => !def.is_empty(), - _ => true, - } - } else { - true - } - } - ContractPart::Annotation(_) => false, - _ => true, - }, - ) - })?; - - if !contract.parts.is_empty() { - fmt.write_whitespace_separator(true)?; - - if fmt.config.contract_new_lines { - write_chunk!(fmt, "\n")?; - } - } - - write_chunk!(fmt, contract.loc.end(), "}}")?; - - Ok(()) - })?; - - Ok(()) - } - - // Support extension for Solana/Substrate - #[instrument(name = "annotation", skip_all)] - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { - return_source_if_disabled!(self, annotation.loc); - let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; - write!(self.buf(), "@{id}")?; - write!(self.buf(), "(")?; - annotation.value.visit(self)?; - write!(self.buf(), ")")?; - Ok(()) - } - - fn visit_pragma( - &mut self, - pragma: &mut PragmaDirective, - ) -> std::result::Result<(), Self::Error> { - let loc = pragma.loc(); - return_source_if_disabled!(self, loc, ';'); - - match pragma { - PragmaDirective::Identifier(loc, id1, id2) => { - write_chunk!( - self, - loc.start(), - loc.end(), - "pragma {}{}{};", - id1.as_ref().map(|id| id.name.to_string()).unwrap_or_default(), - if id1.is_some() && id2.is_some() { " " } else { "" }, - id2.as_ref().map(|id| id.name.to_string()).unwrap_or_default(), - )?; - } - PragmaDirective::StringLiteral(_loc, id, lit) => { - write_chunk!(self, "pragma {} ", id.name)?; - let StringLiteral { loc, string, .. } = lit; - write_chunk!(self, loc.start(), loc.end(), "\"{string}\";")?; - } - PragmaDirective::Version(loc, id, version) => { - write_chunk!(self, loc.start(), id.loc().end(), "pragma {}", id.name)?; - let version_loc = loc.with_start(version[0].loc().start()); - self.visit_source(version_loc)?; - self.write_semicolon()?; - } - } - Ok(()) - } - - #[instrument(name = "import_plain", skip_all)] - fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), import.loc().start(), "import")?; - fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "import_global", skip_all)] - fn visit_import_global( - &mut self, - loc: Loc, - global: &mut ImportPath, - alias: &mut Identifier, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), global.loc().start(), "import")?; - fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?; - write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?; - alias.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "import_renames", skip_all)] - fn visit_import_renames( - &mut self, - loc: Loc, - imports: &mut [(Identifier, Option)], - from: &mut ImportPath, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - if imports.is_empty() { - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), "import")?; - fmt.write_empty_brackets()?; - write_chunk!(fmt, loc.start(), from.loc().start(), "from")?; - fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; - fmt.write_semicolon()?; - Ok(()) - })?; - return Ok(()) - } - - let imports_start = imports.first().unwrap().0.loc.start(); - - write_chunk!(self, loc.start(), imports_start, "import")?; - - self.surrounded( - SurroundingChunk::new("{", Some(imports_start), None), - SurroundingChunk::new("}", None, Some(from.loc().start())), - |fmt, _multiline| { - let mut imports = imports.iter_mut().peekable(); - let mut import_chunks = Vec::new(); - while let Some((ident, alias)) = imports.next() { - import_chunks.push(fmt.chunked( - ident.loc.start(), - imports.peek().map(|(ident, _)| ident.loc.start()), - |fmt| { - fmt.grouped(|fmt| { - ident.visit(fmt)?; - if let Some(alias) = alias { - write_chunk!(fmt, ident.loc.end(), alias.loc.start(), "as")?; - alias.visit(fmt)?; - } - Ok(()) - })?; - Ok(()) - }, - )?); - } - - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{}} }} from \"{}\";", import_path_string(from)), - &import_chunks, - ",", - )?; - fmt.write_chunks_separated(&import_chunks, ",", multiline)?; - Ok(()) - }, - )?; - - self.grouped(|fmt| { - write_chunk!(fmt, imports_start, from.loc().start(), "from")?; - fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; - fmt.write_semicolon()?; - Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "enum", skip_all)] - fn visit_enum(&mut self, enumeration: &mut EnumDefinition) -> Result<()> { - return_source_if_disabled!(self, enumeration.loc); - - let enum_name = enumeration.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?; - name.content = format!("enum {} ", name.content); - if enumeration.values.is_empty() { - self.write_chunk(&name)?; - self.write_empty_brackets()?; - } else { - name.content.push('{'); - self.write_chunk(&name)?; - - self.indented(1, |fmt| { - let values = fmt.items_to_chunks( - Some(enumeration.loc.end()), - enumeration.values.iter_mut().map(|ident| { - let ident = ident.safe_unwrap_mut(); - (ident.loc, ident) - }), - )?; - fmt.write_chunks_separated(&values, ",", true)?; - writeln!(fmt.buf())?; - Ok(()) - })?; - write_chunk!(self, "}}")?; - } - - Ok(()) - } - - #[instrument(name = "assembly", skip_all)] - fn visit_assembly( - &mut self, - loc: Loc, - dialect: &mut Option, - block: &mut YulBlock, - flags: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - write_chunk!(self, loc.start(), "assembly")?; - if let Some(StringLiteral { loc, string, .. }) = dialect { - write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; - } - if let Some(flags) = flags { - if !flags.is_empty() { - let loc_start = flags.first().unwrap().loc.start(); - self.surrounded( - SurroundingChunk::new("(", Some(loc_start), None), - SurroundingChunk::new(")", None, Some(block.loc.start())), - |fmt, _| { - let mut flags = flags.iter_mut().peekable(); - let mut chunks = vec![]; - while let Some(flag) = flags.next() { - let next_byte_offset = - flags.peek().map(|next_flag| next_flag.loc.start()); - chunks.push(fmt.chunked( - flag.loc.start(), - next_byte_offset, - |fmt| { - write!(fmt.buf(), "\"{}\"", flag.string)?; - Ok(()) - }, - )?); - } - fmt.write_chunks_separated(&chunks, ",", false)?; - Ok(()) - }, - )?; - } - } - - block.visit(self) - } - - #[instrument(name = "block", skip_all)] - fn visit_block( - &mut self, - loc: Loc, - unchecked: bool, - statements: &mut Vec, - ) -> Result<()> { - return_source_if_disabled!(self, loc); - if unchecked { - write_chunk!(self, loc.start(), "unchecked ")?; - } - - self.visit_block(loc, statements, false, false)?; - Ok(()) - } - - #[instrument(name = "args", skip_all)] - fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - write!(self.buf(), "{{")?; - - let mut args_iter = args.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { - let next_byte_offset = args_iter - .peek() - .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) - .unwrap_or_else(|| loc.end()); - chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { - fmt.grouped(|fmt| { - write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; - expr.visit(fmt) - })?; - Ok(()) - })?); - } - - if let Some(first) = chunks.first_mut() { - if first.prefixes.is_empty() && - first.postfixes_before.is_empty() && - !self.config.bracket_spacing - { - first.needs_space = Some(false); - } - } - let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; - - let prefix = if multiline && !self.is_beginning_of_line() { - "\n" - } else if self.config.bracket_spacing { - " " - } else { - "" - }; - let closing_bracket = format!("{prefix}{}", "}"); - if let Some(arg) = args.last() { - write_chunk!(self, arg.loc.end(), "{closing_bracket}")?; - } else { - write_chunk!(self, "{closing_bracket}")?; - } - - Ok(()) - } - - #[instrument(name = "expr", skip_all)] - fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { - return_source_if_disabled!(self, loc); - - match expr { - Expression::Type(loc, typ) => match typ { - Type::Address => write_chunk!(self, loc.start(), "address")?, - Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?, - Type::Payable => write_chunk!(self, loc.start(), "payable")?, - Type::Bool => write_chunk!(self, loc.start(), "bool")?, - Type::String => write_chunk!(self, loc.start(), "string")?, - Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?, - Type::Rational => write_chunk!(self, loc.start(), "rational")?, - Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?, - Type::Int(ref n) | Type::Uint(ref n) => { - let int = if matches!(typ, Type::Int(_)) { "int" } else { "uint" }; - match n { - 256 => match self.config.int_types { - IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?, - IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?, - IntTypes::Preserve => self.visit_source(*loc)?, - }, - _ => write_chunk!(self, loc.start(), "{int}{n}")?, - } - } - Type::Mapping { loc, key, key_name, value, value_name } => { - let arrow_loc = self.find_next_str_in_src(loc.start(), "=>"); - let close_paren_loc = - self.find_next_in_src(value.loc().end(), ')').unwrap_or(loc.end()); - let first = SurroundingChunk::new( - "mapping(", - Some(loc.start()), - Some(key.loc().start()), - ); - let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end())) - .non_spaced(); - self.surrounded(first, last, |fmt, multiline| { - fmt.grouped(|fmt| { - key.visit(fmt)?; - - if let Some(name) = key_name { - let end_loc = arrow_loc.unwrap_or(value.loc().start()); - write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?; - } else if let Some(arrow_loc) = arrow_loc { - fmt.write_postfix_comments_before(arrow_loc)?; - } - - let mut write_arrow_and_value = |fmt: &mut Self| { - write!(fmt.buf(), "=> ")?; - value.visit(fmt)?; - if let Some(name) = value_name { - write_chunk!(fmt, name.loc.start(), " {}", name)?; - } - Ok(()) - }; - - let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?; - let multiline = multiline && !fmt.will_it_fit(rest_str); - fmt.write_whitespace_separator(multiline)?; - - write_arrow_and_value(fmt)?; - - fmt.write_postfix_comments_before(close_paren_loc)?; - fmt.write_prefix_comments_before(close_paren_loc) - })?; - Ok(()) - })?; - } - Type::Function { .. } => self.visit_source(*loc)?, - }, - Expression::BoolLiteral(loc, val) => { - write_chunk!(self, loc.start(), loc.end(), "{val}")?; - } - Expression::NumberLiteral(loc, val, exp, unit) => { - self.write_num_literal(*loc, val, None, exp, unit)?; - } - Expression::HexNumberLiteral(loc, val, unit) => { - // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals - let val = if val.len() == 42 { - Address::from_str(val).expect("").to_string() - } else { - val.to_owned() - }; - write_chunk!(self, loc.start(), loc.end(), "{val}")?; - self.write_unit(unit)?; - } - Expression::RationalNumberLiteral(loc, val, fraction, exp, unit) => { - self.write_num_literal(*loc, val, Some(fraction), exp, unit)?; - } - Expression::StringLiteral(vals) => { - for StringLiteral { loc, string, unicode } in vals { - let prefix = if *unicode { Some("unicode") } else { None }; - self.write_quoted_str(*loc, prefix, string)?; - } - } - Expression::HexLiteral(vals) => { - for val in vals { - self.write_hex_literal(val)?; - } - } - Expression::AddressLiteral(loc, val) => { - // support of solana/substrate address literals - self.write_quoted_str(*loc, Some("address"), val)?; - } - Expression::Parenthesis(loc, expr) => { - self.surrounded( - SurroundingChunk::new("(", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(loc.end())), - |fmt, _| expr.visit(fmt), - )?; - } - Expression::ArraySubscript(_, ty_exp, index_expr) => { - ty_exp.visit(self)?; - write!(self.buf(), "[")?; - index_expr.as_mut().map(|index| index.visit(self)).transpose()?; - write!(self.buf(), "]")?; - } - Expression::ArraySlice(loc, expr, start, end) => { - expr.visit(self)?; - write!(self.buf(), "[")?; - let mut write_slice = |fmt: &mut Self, multiline| -> Result<()> { - if multiline { - fmt.write_whitespace_separator(true)?; - } - fmt.grouped(|fmt| { - start.as_mut().map(|start| start.visit(fmt)).transpose()?; - write!(fmt.buf(), ":")?; - if let Some(end) = end { - let mut chunk = - fmt.chunked(end.loc().start(), Some(loc.end()), |fmt| { - end.visit(fmt) - })?; - if chunk.prefixes.is_empty() && - chunk.postfixes_before.is_empty() && - (start.is_none() || fmt.will_it_fit(&chunk.content)) - { - chunk.needs_space = Some(false); - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }; - - if !self.try_on_single_line(|fmt| write_slice(fmt, false))? { - self.indented(1, |fmt| write_slice(fmt, true))?; - } - - write!(self.buf(), "]")?; - } - Expression::ArrayLiteral(loc, exprs) => { - write_chunk!(self, loc.start(), "[")?; - let chunks = self.items_to_chunks( - Some(loc.end()), - exprs.iter_mut().map(|expr| (expr.loc(), expr)), - )?; - let multiline = self.are_chunks_separated_multiline("{}]", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| { - fmt.write_chunks_separated(&chunks, ",", multiline)?; - if multiline { - fmt.write_postfix_comments_before(loc.end())?; - fmt.write_prefix_comments_before(loc.end())?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - })?; - write_chunk!(self, loc.end(), "]")?; - } - Expression::PreIncrement(..) | - Expression::PostIncrement(..) | - Expression::PreDecrement(..) | - Expression::PostDecrement(..) | - Expression::Not(..) | - Expression::UnaryPlus(..) | - Expression::Add(..) | - Expression::Negate(..) | - Expression::Subtract(..) | - Expression::Power(..) | - Expression::Multiply(..) | - Expression::Divide(..) | - Expression::Modulo(..) | - Expression::ShiftLeft(..) | - Expression::ShiftRight(..) | - Expression::BitwiseNot(..) | - Expression::BitwiseAnd(..) | - Expression::BitwiseXor(..) | - Expression::BitwiseOr(..) | - Expression::Less(..) | - Expression::More(..) | - Expression::LessEqual(..) | - Expression::MoreEqual(..) | - Expression::And(..) | - Expression::Or(..) | - Expression::Equal(..) | - Expression::NotEqual(..) => { - let spaced = expr.has_space_around(); - let op = expr.operator().unwrap(); - - match expr.components_mut() { - (Some(left), Some(right)) => { - left.visit(self)?; - - let right_chunk = - self.chunked(right.loc().start(), Some(loc.end()), |fmt| { - write_chunk!(fmt, right.loc().start(), "{op}")?; - right.visit(fmt)?; - Ok(()) - })?; - - self.grouped(|fmt| fmt.write_chunk(&right_chunk))?; - } - (Some(left), None) => { - left.visit(self)?; - write_chunk_spaced!(self, loc.end(), Some(spaced), "{op}")?; - } - (None, Some(right)) => { - write_chunk!(self, right.loc().start(), "{op}")?; - let mut right_chunk = - self.visit_to_chunk(right.loc().end(), Some(loc.end()), right)?; - right_chunk.needs_space = Some(spaced); - self.write_chunk(&right_chunk)?; - } - (None, None) => {} - } - } - Expression::Assign(..) | - Expression::AssignOr(..) | - Expression::AssignAnd(..) | - Expression::AssignXor(..) | - Expression::AssignShiftLeft(..) | - Expression::AssignShiftRight(..) | - Expression::AssignAdd(..) | - Expression::AssignSubtract(..) | - Expression::AssignMultiply(..) | - Expression::AssignDivide(..) | - Expression::AssignModulo(..) => { - let op = expr.operator().unwrap(); - let (left, right) = expr.components_mut(); - let (left, right) = (left.unwrap(), right.unwrap()); - - left.visit(self)?; - write_chunk!(self, "{op}")?; - self.visit_assignment(right)?; - } - Expression::ConditionalOperator(loc, cond, first_expr, second_expr) => { - cond.visit(self)?; - - let first_expr = self.chunked( - first_expr.loc().start(), - Some(second_expr.loc().start()), - |fmt| { - write_chunk!(fmt, "?")?; - first_expr.visit(fmt) - }, - )?; - let second_expr = - self.chunked(second_expr.loc().start(), Some(loc.end()), |fmt| { - write_chunk!(fmt, ":")?; - second_expr.visit(fmt) - })?; - - let chunks = vec![first_expr, second_expr]; - if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? { - self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?; - } - } - Expression::Variable(ident) => { - write_chunk!(self, loc.end(), "{}", ident.name)?; - } - Expression::MemberAccess(_, expr, ident) => { - self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { - Expression::MemberAccess(_, inner_expr, inner_ident) => { - Ok(Some((inner_expr, inner_ident))) - } - expr => { - expr.visit(fmt)?; - Ok(None) - } - })?; - } - Expression::List(loc, items) => { - self.surrounded( - SurroundingChunk::new( - "(", - Some(loc.start()), - items.first().map(|item| item.0.start()), - ), - SurroundingChunk::new(")", None, Some(loc.end())), - |fmt, _| { - let items = fmt.items_to_chunks( - Some(loc.end()), - items.iter_mut().map(|(loc, item)| (*loc, item)), - )?; - let write_items = |fmt: &mut Self, multiline| { - fmt.write_chunks_separated(&items, ",", multiline) - }; - if !fmt.try_on_single_line(|fmt| write_items(fmt, false))? { - write_items(fmt, true)?; - } - Ok(()) - }, - )?; - } - Expression::FunctionCall(loc, expr, exprs) => { - self.visit_expr(expr.loc(), expr)?; - self.visit_list("", exprs, Some(expr.loc().end()), Some(loc.end()), true)?; - } - Expression::NamedFunctionCall(loc, expr, args) => { - self.visit_expr(expr.loc(), expr)?; - write!(self.buf(), "(")?; - self.visit_args(*loc, args)?; - write!(self.buf(), ")")?; - } - Expression::FunctionCallBlock(_, expr, stmt) => { - expr.visit(self)?; - stmt.visit(self)?; - } - Expression::New(_, expr) => { - write_chunk!(self, "new ")?; - self.visit_expr(expr.loc(), expr)?; - } - _ => self.visit_source(loc)?, - }; - - Ok(()) - } - - #[instrument(name = "ident", skip_all)] - fn visit_ident(&mut self, loc: Loc, ident: &mut Identifier) -> Result<()> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.end(), "{}", ident.name)?; - Ok(()) - } - - #[instrument(name = "ident_path", skip_all)] - fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { - if idents.identifiers.is_empty() { - return Ok(()) - } - return_source_if_disabled!(self, idents.loc); - - idents.identifiers.iter_mut().skip(1).for_each(|chunk| { - if !chunk.name.starts_with('.') { - chunk.name.insert(0, '.') - } - }); - let chunks = self.items_to_chunks( - Some(idents.loc.end()), - idents.identifiers.iter_mut().map(|ident| (ident.loc, ident)), - )?; - self.grouped(|fmt| { - let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, "")?; - fmt.write_chunks_separated(&chunks, "", multiline) - })?; - Ok(()) - } - - #[instrument(name = "emit", skip_all)] - fn visit_emit(&mut self, loc: Loc, event: &mut Expression) -> Result<()> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), "emit")?; - event.visit(self)?; - self.write_semicolon()?; - Ok(()) - } - - #[instrument(name = "var_definition", skip_all)] - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { - return_source_if_disabled!(self, var.loc, ';'); - - var.ty.visit(self)?; - - let multiline = self.grouped(|fmt| { - let var_name = var.name.safe_unwrap_mut(); - let name_start = var_name.loc.start(); - - let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; - if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { - fmt.write_chunks_separated(&attrs, "", true)?; - } - - let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; - if var.initializer.is_some() { - name.content.push_str(" ="); - } - fmt.write_chunk(&name)?; - - Ok(()) - })?; - - var.initializer - .as_mut() - .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) - .transpose()?; - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "var_definition_stmt", skip_all)] - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - let declaration = self - .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; - let multiline = declaration.content.contains('\n'); - self.write_chunk(&declaration)?; - - if let Some(expr) = expr { - write!(self.buf(), " =")?; - self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; - } - - self.write_semicolon() - } - - #[instrument(name = "var_declaration", skip_all)] - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { - return_source_if_disabled!(self, var.loc); - self.grouped(|fmt| { - var.ty.visit(fmt)?; - if let Some(storage) = &var.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - let var_name = var.name.safe_unwrap(); - write_chunk!(fmt, var_name.loc.end(), "{var_name}") - })?; - Ok(()) - } - - #[instrument(name = "return", skip_all)] - fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - self.write_postfix_comments_before(loc.start())?; - self.write_prefix_comments_before(loc.start())?; - - if expr.is_none() { - write_chunk!(self, loc.end(), "return;")?; - return Ok(()) - } - - let expr = expr.as_mut().unwrap(); - let expr_loc_start = expr.loc().start(); - let write_return = |fmt: &mut Self| -> Result<()> { - write_chunk!(fmt, loc.start(), "return")?; - fmt.write_postfix_comments_before(expr_loc_start)?; - Ok(()) - }; - - let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { - let fits_on_single = fmt.try_on_single_line(|fmt| { - write_return(fmt)?; - expr.visit(fmt) - })?; - if fits_on_single { - return Ok(()) - } - - let mut fit_on_next_line = false; - let tx = fmt.transact(|fmt| { - fmt.grouped(|fmt| { - write_return(fmt)?; - if !fmt.is_beginning_of_line() { - fmt.write_whitespace_separator(true)?; - } - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - return Ok(()) - } - - write_return(fmt)?; - expr.visit(fmt)?; - Ok(()) - }; - - write_return_with_expr(self)?; - write_chunk!(self, loc.end(), ";")?; - Ok(()) - } - - #[instrument(name = "revert", skip_all)] - fn visit_revert( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "revert")?; - if let Some(error) = error { - error.visit(self)?; - } - self.visit_list("", args, None, Some(loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "revert_named_args", skip_all)] - fn visit_revert_named_args( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - write_chunk!(self, loc.start(), "revert")?; - let mut error_indented = false; - if let Some(error) = error { - if !self.try_on_single_line(|fmt| error.visit(fmt))? { - error.visit(self)?; - error_indented = true; - } - } - - if args.is_empty() { - write!(self.buf(), "({{}});")?; - return Ok(()) - } - - write!(self.buf(), "(")?; - self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; - write!(self.buf(), ")")?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "break", skip_all)] - fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) - } - - #[instrument(name = "continue", skip_all)] - fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) - } - - #[instrument(name = "try", skip_all)] - fn visit_try( - &mut self, - loc: Loc, - expr: &mut Expression, - returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - clauses: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let try_next_byte = clauses.first().map(|c| match c { - CatchClause::Simple(loc, ..) => loc.start(), - CatchClause::Named(loc, ..) => loc.start(), - }); - let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { - write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; - expr.visit(fmt)?; - if let Some((params, stmt)) = returns { - let mut params = - params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); - let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); - fmt.surrounded( - SurroundingChunk::new("returns (", Some(byte_offset), None), - SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), - |fmt, _| { - let chunks = fmt.items_to_chunks( - Some(stmt.loc().start()), - params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - Ok(()) - }, - )?; - stmt.visit(fmt)?; - } - Ok(()) - })?; - - let mut chunks = vec![try_chunk]; - for clause in clauses { - let (loc, ident, mut param, stmt) = match clause { - CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), - CatchClause::Named(loc, ident, param, stmt) => { - (loc, Some(ident), Some(param), stmt) - } - }; - - let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { - write_chunk!(fmt, "catch")?; - if let Some(ident) = ident.as_ref() { - fmt.write_postfix_comments_before( - param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), - )?; - write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; - } - if let Some(param) = param.as_mut() { - write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; - fmt.surrounded( - SurroundingChunk::new("", Some(param.loc.start()), None), - SurroundingChunk::new(")", None, Some(stmt.loc().start())), - |fmt, _| param.visit(fmt), - )?; - } - - stmt.visit(fmt)?; - Ok(()) - })?; - - chunks.push(chunk); - } - - let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; - if !multiline { - self.write_chunks_separated(&chunks, "", false)?; - return Ok(()) - } - - let mut chunks = chunks.iter_mut().peekable(); - let mut prev_multiline = false; - - // write try chunk first - if let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - write!(self.buf(), "{chunk_str}")?; - prev_multiline = chunk_str.contains('\n'); - } - - while let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - let multiline = chunk_str.contains('\n'); - self.indented_if(!multiline, 1, |fmt| { - chunk.needs_space = Some(false); - let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); - let prefix = if fmt.is_beginning_of_line() { - "" - } else if on_same_line { - " " - } else { - "\n" - }; - let chunk_str = format!("{prefix}{chunk_str}"); - write!(fmt.buf(), "{chunk_str}")?; - Ok(()) - })?; - prev_multiline = multiline; - } - Ok(()) - } - - #[instrument(name = "if", skip_all)] - fn visit_if( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - is_first_stmt: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - if !is_first_stmt { - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - return Ok(()) - } - - self.context.if_stmt_single_line = Some(true); - let mut stmt_fits_on_single = false; - let tx = self.transact(|fmt| { - stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; - - if stmt_fits_on_single { - tx.commit()?; - } else { - self.context.if_stmt_single_line = Some(false); - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - } - self.context.if_stmt_single_line = None; - - Ok(()) - } - - #[instrument(name = "do_while", skip_all)] - fn visit_do_while( - &mut self, - loc: Loc, - body: &mut Statement, - cond: &mut Expression, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "do ")?; - self.visit_stmt_as_block(body, false)?; - visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { - self.surrounded( - SurroundingChunk::new("while (", Some(cond.loc().start()), None), - SurroundingChunk::new(");", None, Some(loc.end())), - |fmt, _| cond.visit(fmt), - )?; - }); - Ok(()) - } - - #[instrument(name = "while", skip_all)] - fn visit_while( - &mut self, - loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.surrounded( - SurroundingChunk::new("while (", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(cond.loc().end())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(body.loc().start()) - }, - )?; - - let cond_close_paren_loc = - self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); - self.visit_stmt_as_block(body, attempt_single_line)?; - Ok(()) - } - - #[instrument(name = "for", skip_all)] - fn visit_for( - &mut self, - loc: Loc, - init: &mut Option>, - cond: &mut Option>, - update: &mut Option>, - body: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let next_byte_end = update.as_ref().map(|u| u.loc().end()); - self.surrounded( - SurroundingChunk::new("for (", Some(loc.start()), None), - SurroundingChunk::new(")", None, next_byte_end), - |fmt, _| { - let mut write_for_loop_header = |fmt: &mut Self, multiline: bool| -> Result<()> { - match init { - Some(stmt) => stmt.visit(fmt), - None => fmt.write_semicolon(), - }?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - - cond.visit(fmt)?; - fmt.write_semicolon()?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - - match update { - Some(expr) => expr.visit(fmt), - None => Ok(()), - } - }; - let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; - if multiline { - write_for_loop_header(fmt, true)?; - } - Ok(()) - }, - )?; - match body { - Some(body) => { - self.visit_stmt_as_block(body, false)?; - } - None => { - self.write_empty_brackets()?; - } - }; - Ok(()) - } - - #[instrument(name = "function", skip_all)] - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { - if func.body.is_some() { - return_source_if_disabled!(self, func.loc()); - } else { - return_source_if_disabled!(self, func.loc(), ';'); - } - - self.with_function_context(func.clone(), |fmt| { - fmt.write_postfix_comments_before(func.loc.start())?; - fmt.write_prefix_comments_before(func.loc.start())?; - - let body_loc = func.body.as_ref().map(CodeLocation::loc); - let mut attrs_multiline = false; - let fits_on_single = fmt.try_on_single_line(|fmt| { - fmt.write_function_header(func, body_loc, false)?; - Ok(()) - })?; - if !fits_on_single { - attrs_multiline = fmt.write_function_header(func, body_loc, true)?; - } - - // write function body - match &mut func.body { - Some(body) => { - let body_loc = body.loc(); - // Handle case where block / statements starts on disabled line. - if fmt.inline_config.is_disabled(body_loc.with_end(body_loc.start())) { - match body { - Statement::Block { statements, .. } if !statements.is_empty() => { - fmt.write_whitespace_separator(false)?; - fmt.visit_block(body_loc, statements, false, false)?; - return Ok(()) - } - _ => { - // Attrs should be written on same line if first line is disabled - // and there's no statement. - attrs_multiline = false - } - } - } - - let byte_offset = body_loc.start(); - let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; - fmt.write_whitespace_separator( - attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), - )?; - fmt.write_chunk(&body)?; - } - None => fmt.write_semicolon()?, - } - Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "function_attribute", skip_all)] - fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - match attribute { - FunctionAttribute::Mutability(mutability) => { - write_chunk!(self, mutability.loc().end(), "{mutability}")? - } - FunctionAttribute::Visibility(visibility) => { - // Visibility will always have a location in a Function attribute - write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? - } - FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, - FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, - FunctionAttribute::Override(loc, args) => { - write_chunk!(self, loc.start(), "override")?; - if !args.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", args, None, Some(loc.end()), false)? - } - FunctionAttribute::BaseOrModifier(loc, base) => { - // here we need to find out if this attribute belongs to the constructor because the - // modifier need to include the trailing parenthesis - // This is very ambiguous because the modifier can either by an inherited contract - // or a modifier here: e.g.: This is valid constructor: - // `constructor() public Ownable() OnlyOwner {}` - let is_constructor = self.context.is_constructor_function(); - // we can't make any decisions here regarding trailing `()` because we'd need to - // find out if the `base` is a solidity modifier or an - // interface/contract therefore we use its raw content. - - // we can however check if the contract `is` the `base`, this however also does - // not cover all cases - let is_contract_base = self.context.contract.as_ref().is_some_and(|contract| { - contract.base.iter().any(|contract_base| { - contract_base - .name - .identifiers - .iter() - .zip(&base.name.identifiers) - .all(|(l, r)| l.name == r.name) - }) - }); - - if is_contract_base { - base.visit(self)?; - } else if is_constructor { - // This is ambiguous because the modifier can either by an inherited - // contract modifiers with empty parenthesis are - // valid, but not required so we make the assumption - // here that modifiers are lowercase - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - let is_lowercase = - base_or_modifier.content.chars().next().is_some_and(|c| c.is_lowercase()); - if is_lowercase && base_or_modifier.content.ends_with("()") { - base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); - } - - self.write_chunk(&base_or_modifier)?; - } else { - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - if base_or_modifier.content.ends_with("()") { - base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); - } - self.write_chunk(&base_or_modifier)?; - } - } - FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, - }; - - Ok(()) - } - - #[instrument(name = "var_attribute", skip_all)] - fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - let token = match attribute { - VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), - VariableAttribute::Constant(_) => Some("constant".to_string()), - VariableAttribute::Immutable(_) => Some("immutable".to_string()), - VariableAttribute::StorageType(_) => None, // Unsupported - VariableAttribute::Override(loc, idents) => { - write_chunk!(self, loc.start(), "override")?; - if !idents.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; - None - } - VariableAttribute::StorageLocation(storage) => Some(storage.to_string()), - }; - if let Some(token) = token { - let loc = attribute.loc(); - write_chunk!(self, loc.start(), loc.end(), "{}", token)?; - } - Ok(()) - } - - #[instrument(name = "base", skip_all)] - fn visit_base(&mut self, base: &mut Base) -> Result<()> { - return_source_if_disabled!(self, base.loc); - - let name_loc = &base.name.loc; - let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { - fmt.visit_ident_path(&mut base.name)?; - Ok(()) - })?; - - if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { - // This is ambiguous because the modifier can either by an inherited contract or a - // modifier - if self.context.function.is_some() { - name.content.push_str("()"); - } - self.write_chunk(&name)?; - return Ok(()) - } - - let args = base.args.as_mut().unwrap(); - let args_start = CodeLocation::loc(args.first().unwrap()).start(); - - name.content.push('('); - let formatted_name = self.chunk_to_string(&name)?; - - let multiline = !self.will_it_fit(&formatted_name); - - self.surrounded( - SurroundingChunk::new(&formatted_name, Some(args_start), None), - SurroundingChunk::new(")", None, Some(base.loc.end())), - |fmt, multiline_hint| { - let args = fmt.items_to_chunks( - Some(base.loc.end()), - args.iter_mut().map(|arg| (arg.loc(), arg)), - )?; - let multiline = multiline || - multiline_hint || - fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - }, - )?; - - Ok(()) - } - - #[instrument(name = "parameter", skip_all)] - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { - return_source_if_disabled!(self, parameter.loc); - self.grouped(|fmt| { - parameter.ty.visit(fmt)?; - if let Some(storage) = ¶meter.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - if let Some(name) = ¶meter.name { - write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "struct", skip_all)] - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { - return_source_if_disabled!(self, structure.loc); - self.grouped(|fmt| { - let struct_name = structure.name.safe_unwrap_mut(); - write_chunk!(fmt, struct_name.loc.start(), "struct")?; - struct_name.visit(fmt)?; - if structure.fields.is_empty() { - return fmt.write_empty_brackets() - } - - write!(fmt.buf(), " {{")?; - fmt.surrounded( - SurroundingChunk::new("", Some(struct_name.loc.end()), None), - SurroundingChunk::new("}", None, Some(structure.loc.end())), - |fmt, _multiline| { - let chunks = fmt.items_to_chunks( - Some(structure.loc.end()), - structure.fields.iter_mut().map(|ident| (ident.loc, ident)), - )?; - for mut chunk in chunks { - chunk.content.push(';'); - fmt.write_chunk(&chunk)?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }, - ) - })?; - - Ok(()) - } - - #[instrument(name = "event", skip_all)] - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { - return_source_if_disabled!(self, event.loc, ';'); - - let event_name = event.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; - name.content = format!("event {}(", name.content); - - let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; - if event.fields.is_empty() { - name.content.push_str(last_chunk); - self.write_chunk(&name)?; - } else { - let byte_offset = event.fields.first().unwrap().loc.start(); - let first_chunk = self.chunk_to_string(&name)?; - self.surrounded( - SurroundingChunk::new(first_chunk, Some(byte_offset), None), - SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), - |fmt, multiline| { - let params = fmt - .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; - - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; - fmt.write_chunks_separated(¶ms, ",", multiline) - }, - )?; - } - - Ok(()) - } - - #[instrument(name = "event_parameter", skip_all)] - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if param.indexed { - write_chunk!(fmt, param.loc.start(), "indexed")?; - } - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "error", skip_all)] - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { - return_source_if_disabled!(self, error.loc, ';'); - - let error_name = error.name.safe_unwrap_mut(); - let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; - name.content = format!("error {}", name.content); - - let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{formatted_name}")?; - let start_offset = error.fields.first().map(|f| f.loc.start()); - self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "error_parameter", skip_all)] - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "type_definition", skip_all)] - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { - return_source_if_disabled!(self, def.loc, ';'); - self.grouped(|fmt| { - write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; - def.name.visit(fmt)?; - write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; - def.ty.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "stray_semicolon", skip_all)] - fn visit_stray_semicolon(&mut self) -> Result<()> { - self.write_semicolon() - } - - #[instrument(name = "opening_paren", skip_all)] - fn visit_opening_paren(&mut self) -> Result<()> { - write_chunk!(self, "(")?; - Ok(()) - } - - #[instrument(name = "closing_paren", skip_all)] - fn visit_closing_paren(&mut self) -> Result<()> { - write_chunk!(self, ")")?; - Ok(()) - } - - #[instrument(name = "newline", skip_all)] - fn visit_newline(&mut self) -> Result<()> { - writeln_chunk!(self)?; - Ok(()) - } - - #[instrument(name = "using", skip_all)] - fn visit_using(&mut self, using: &mut Using) -> Result<()> { - return_source_if_disabled!(self, using.loc, ';'); - - write_chunk!(self, using.loc.start(), "using")?; - - let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); - let global_start = using.global.as_mut().map(|global| global.loc.start()); - let loc_end = using.loc.end(); - - let (is_library, mut list_chunks) = match &mut using.list { - UsingList::Library(library) => { - (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) - } - UsingList::Functions(funcs) => { - let mut funcs = funcs.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(func) = funcs.next() { - let next_byte_end = funcs.peek().map(|func| func.loc.start()); - chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { - fmt.visit_ident_path(&mut func.path)?; - if let Some(op) = func.oper { - write!(fmt.buf(), " as {op}")?; - } - Ok(()) - })?); - } - (false, chunks) - } - UsingList::Error => return self.visit_parser_error(using.loc), - }; - - let for_chunk = self.chunk_at( - using.loc.start(), - Some(ty_start.or(global_start).unwrap_or(loc_end)), - None, - "for", - ); - let ty_chunk = if let Some(ty) = &mut using.ty { - self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? - } else { - self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") - }; - let global_chunk = using - .global - .as_mut() - .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) - .transpose()?; - - let write_for_def = |fmt: &mut Self| { - fmt.grouped(|fmt| { - fmt.write_chunk(&for_chunk)?; - fmt.write_chunk(&ty_chunk)?; - if let Some(global_chunk) = global_chunk.as_ref() { - fmt.write_chunk(global_chunk)?; - } - Ok(()) - })?; - Ok(()) - }; - - let simulated_for_def = self.simulate_to_string(write_for_def)?; - - if is_library { - let chunk = list_chunks.pop().unwrap(); - if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { - self.write_chunk(&chunk)?; - write_for_def(self)?; - } else { - self.write_whitespace_separator(true)?; - self.grouped(|fmt| { - fmt.write_chunk(&chunk)?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - write_for_def(self)?; - } - } else { - self.surrounded( - SurroundingChunk::new("{", Some(using.loc.start()), None), - SurroundingChunk::new( - "}", - None, - Some(ty_start.or(global_start).unwrap_or(loc_end)), - ), - |fmt, _multiline| { - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{ {{}} }} {simulated_for_def};"), - &list_chunks, - ",", - )?; - fmt.write_chunks_separated(&list_chunks, ",", multiline)?; - Ok(()) - }, - )?; - write_for_def(self)?; - } - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "yul_block", skip_all)] - fn visit_yul_block( - &mut self, - loc: Loc, - statements: &mut Vec, - attempt_single_line: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.visit_block(loc, statements, attempt_single_line, false)?; - Ok(()) - } - - #[instrument(name = "yul_expr", skip_all)] - fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { - return_source_if_disabled!(self, expr.loc()); - - match expr { - YulExpression::BoolLiteral(loc, val, ident) => { - let val = if *val { "true" } else { "false" }; - self.visit_yul_string_with_ident(*loc, val, ident) - } - YulExpression::FunctionCall(expr) => self.visit_yul_function_call(expr), - YulExpression::HexNumberLiteral(loc, val, ident) => { - self.visit_yul_string_with_ident(*loc, val, ident) - } - YulExpression::HexStringLiteral(val, ident) => self.visit_yul_string_with_ident( - val.loc, - &self.quote_str(val.loc, Some("hex"), &val.hex), - ident, - ), - YulExpression::NumberLiteral(loc, val, expr, ident) => { - let val = if expr.is_empty() { val.to_owned() } else { format!("{val}e{expr}") }; - self.visit_yul_string_with_ident(*loc, &val, ident) - } - YulExpression::StringLiteral(val, ident) => self.visit_yul_string_with_ident( - val.loc, - &self.quote_str(val.loc, None, &val.string), - ident, - ), - YulExpression::SuffixAccess(_, expr, ident) => { - self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() { - YulExpression::SuffixAccess(_, inner_expr, inner_ident) => { - Ok(Some((inner_expr, inner_ident))) - } - expr => { - expr.visit(fmt)?; - Ok(None) - } - }) - } - YulExpression::Variable(ident) => { - write_chunk!(self, ident.loc.start(), ident.loc.end(), "{}", ident.name) - } - } - } - - #[instrument(name = "yul_assignment", skip_all)] - fn visit_yul_assignment( - &mut self, - loc: Loc, - exprs: &mut Vec, - expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - return_source_if_disabled!(self, loc); - - self.grouped(|fmt| { - let chunks = - fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; - - let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - - if let Some(expr) = expr { - write_chunk!(fmt, expr.loc().start(), ":=")?; - let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; - if !fmt.will_chunk_fit("{}", &chunk)? { - fmt.write_whitespace_separator(true)?; - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "yul_for", skip_all)] - fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - write_chunk!(self, stmt.loc.start(), "for")?; - self.visit_yul_block(stmt.init_block.loc, &mut stmt.init_block.statements, true)?; - stmt.condition.visit(self)?; - self.visit_yul_block(stmt.post_block.loc, &mut stmt.post_block.statements, true)?; - self.visit_yul_block(stmt.execution_block.loc, &mut stmt.execution_block.statements, true)?; - Ok(()) - } - - #[instrument(name = "yul_function_call", skip_all)] - fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - write_chunk!(self, stmt.loc.start(), "{}", stmt.id.name)?; - self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) - } - - #[instrument(name = "yul_fun_def", skip_all)] - fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - - write_chunk!(self, stmt.loc.start(), "function {}", stmt.id.name)?; - - self.visit_list("", &mut stmt.params, None, None, true)?; - - if !stmt.returns.is_empty() { - self.grouped(|fmt| { - write_chunk!(fmt, "->")?; - - let chunks = fmt.items_to_chunks( - Some(stmt.body.loc.start()), - stmt.returns.iter_mut().map(|param| (param.loc, param)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - if multiline { - fmt.write_whitespace_separator(true)?; - } - Ok(()) - })?; - } - - stmt.body.visit(self)?; - - Ok(()) - } - - #[instrument(name = "yul_if", skip_all)] - fn visit_yul_if( - &mut self, - loc: Loc, - expr: &mut YulExpression, - block: &mut YulBlock, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), "if")?; - expr.visit(self)?; - self.visit_yul_block(block.loc, &mut block.statements, true) - } - - #[instrument(name = "yul_leave", skip_all)] - fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - write_chunk!(self, loc.start(), loc.end(), "leave") - } - - #[instrument(name = "yul_switch", skip_all)] - fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { - return_source_if_disabled!(self, stmt.loc); - - write_chunk!(self, stmt.loc.start(), "switch")?; - stmt.condition.visit(self)?; - writeln_chunk!(self)?; - let mut cases = stmt.cases.iter_mut().peekable(); - while let Some(YulSwitchOptions::Case(loc, expr, block)) = cases.next() { - write_chunk!(self, loc.start(), "case")?; - expr.visit(self)?; - self.visit_yul_block(block.loc, &mut block.statements, true)?; - let is_last = cases.peek().is_none(); - if !is_last || stmt.default.is_some() { - writeln_chunk!(self)?; - } - } - if let Some(YulSwitchOptions::Default(loc, ref mut block)) = stmt.default { - write_chunk!(self, loc.start(), "default")?; - self.visit_yul_block(block.loc, &mut block.statements, true)?; - } - Ok(()) - } - - #[instrument(name = "yul_var_declaration", skip_all)] - fn visit_yul_var_declaration( - &mut self, - loc: Loc, - idents: &mut Vec, - expr: &mut Option, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), "let")?; - fmt.visit_yul_assignment(loc, idents, &mut expr.as_mut()) - })?; - Ok(()) - } - - #[instrument(name = "yul_typed_ident", skip_all)] - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - return_source_if_disabled!(self, ident.loc); - self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) - } - - #[instrument(name = "parser_error", skip_all)] - fn visit_parser_error(&mut self, loc: Loc) -> Result<()> { - Err(FormatterError::InvalidParsedItem(loc)) - } -} - -/// An action which may be committed to a Formatter -struct Transaction<'f, 'a, W> { - fmt: &'f mut Formatter<'a, W>, - buffer: String, - comments: Comments, -} - -impl<'a, W> std::ops::Deref for Transaction<'_, 'a, W> { - type Target = Formatter<'a, W>; - fn deref(&self) -> &Self::Target { - self.fmt - } -} - -impl std::ops::DerefMut for Transaction<'_, '_, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.fmt - } -} - -impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { - /// Create a new transaction from a callback - fn new( - fmt: &'f mut Formatter<'a, W>, - fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, - ) -> Result { - let mut comments = fmt.comments.clone(); - let buffer = fmt.with_temp_buf(fun)?.w; - comments = std::mem::replace(&mut fmt.comments, comments); - Ok(Self { fmt, buffer, comments }) - } - - /// Commit the transaction to the Formatter - fn commit(self) -> Result { - self.fmt.comments = self.comments; - write_chunk!(self.fmt, "{}", self.buffer)?; - Ok(self.buffer) - } -} diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs deleted file mode 100644 index 1d036ba6b66d0..0000000000000 --- a/crates/fmt/src/helpers.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{ - inline_config::{InlineConfig, InvalidInlineConfigItem}, - Comments, Formatter, FormatterConfig, FormatterError, Visitable, -}; -use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; -use itertools::Itertools; -use solang_parser::{diagnostics::Diagnostic, pt::*}; -use std::{fmt::Write, path::Path}; - -/// Result of parsing the source code -#[derive(Debug)] -pub struct Parsed<'a> { - /// The original source code. - pub src: &'a str, - /// The parse tree. - pub pt: SourceUnit, - /// Parsed comments. - pub comments: Comments, - /// Parsed inline config. - pub inline_config: InlineConfig, - /// Invalid inline config items parsed. - pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>, -} - -/// Parse source code. -pub fn parse(src: &str) -> Result, FormatterError> { - parse_raw(src).map_err(|diag| FormatterError::Parse(src.to_string(), None, diag)) -} - -/// Parse source code with a path for diagnostics. -pub fn parse2<'s>(src: &'s str, path: Option<&Path>) -> Result, FormatterError> { - parse_raw(src) - .map_err(|diag| FormatterError::Parse(src.to_string(), path.map(ToOwned::to_owned), diag)) -} - -/// Parse source code, returning a list of diagnostics on failure. -pub fn parse_raw(src: &str) -> Result, Vec> { - let (pt, comments) = solang_parser::parse(src, 0)?; - let comments = Comments::new(comments, src); - let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) = - comments.parse_inline_config_items().partition_result(); - let inline_config = InlineConfig::new(inline_config_items, src); - Ok(Parsed { src, pt, comments, inline_config, invalid_inline_config_items }) -} - -/// Format parsed code -pub fn format_to( - writer: W, - mut parsed: Parsed<'_>, - config: FormatterConfig, -) -> Result<(), FormatterError> { - trace!(?parsed, ?config, "Formatting"); - let mut formatter = - Formatter::new(writer, parsed.src, parsed.comments, parsed.inline_config, config); - parsed.pt.visit(&mut formatter) -} - -/// Parse and format a string with default settings -pub fn format(src: &str) -> Result { - let parsed = parse(src)?; - - let mut output = String::new(); - format_to(&mut output, parsed, FormatterConfig::default())?; - - Ok(output) -} - -/// Converts the start offset of a `Loc` to `(line, col)` -pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { - debug_assert!(content.len() > start); - - // first line is `1` - let mut line_counter = 1; - for (offset, c) in content.chars().enumerate() { - if c == '\n' { - line_counter += 1; - } - if offset > start { - return (line_counter, offset - start) - } - } - - unreachable!("content.len() > start") -} - -/// Formats parser diagnostics -pub fn format_diagnostics_report( - content: &str, - path: Option<&Path>, - diagnostics: &[Diagnostic], -) -> String { - if diagnostics.is_empty() { - return String::new(); - } - - let filename = - path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default(); - let mut s = Vec::new(); - for diag in diagnostics { - let span = (filename.as_str(), diag.loc.start()..diag.loc.end()); - let mut report = Report::build(ReportKind::Error, span.clone()) - .with_message(format!("{:?}", diag.ty)) - .with_label( - Label::new(span) - .with_color(Color::Red) - .with_message(diag.message.as_str().fg(Color::Red)), - ); - - for note in &diag.notes { - report = report.with_note(¬e.message); - } - - report.finish().write((filename.as_str(), Source::from(content)), &mut s).unwrap(); - } - String::from_utf8(s).unwrap() -} - -pub fn import_path_string(path: &ImportPath) -> String { - match path { - ImportPath::Filename(s) => s.string.clone(), - ImportPath::Path(p) => p.to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // - #[test] - fn test_interface_format() { - let s = "interface I {\n function increment() external;\n function number() external view returns (uint256);\n function setNumber(uint256 newNumber) external;\n}"; - let _formatted = format(s).unwrap(); - } -} diff --git a/crates/fmt/src/inline_config.rs b/crates/fmt/src/inline_config.rs deleted file mode 100644 index fb0a3dfb0977e..0000000000000 --- a/crates/fmt/src/inline_config.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::comments::{CommentState, CommentStringExt}; -use itertools::Itertools; -use solang_parser::pt::Loc; -use std::{fmt, str::FromStr}; - -/// An inline config item -#[derive(Clone, Copy, Debug)] -pub enum InlineConfigItem { - /// Disables the next code item regardless of newlines - DisableNextItem, - /// Disables formatting on the current line - DisableLine, - /// Disables formatting between the next newline and the newline after - DisableNextLine, - /// Disables formatting for any code that follows this and before the next "disable-end" - DisableStart, - /// Disables formatting for any code that precedes this and after the previous "disable-start" - DisableEnd, -} - -impl FromStr for InlineConfigItem { - type Err = InvalidInlineConfigItem; - fn from_str(s: &str) -> Result { - Ok(match s { - "disable-next-item" => Self::DisableNextItem, - "disable-line" => Self::DisableLine, - "disable-next-line" => Self::DisableNextLine, - "disable-start" => Self::DisableStart, - "disable-end" => Self::DisableEnd, - s => return Err(InvalidInlineConfigItem(s.into())), - }) - } -} - -#[derive(Debug)] -pub struct InvalidInlineConfigItem(String); - -impl fmt::Display for InvalidInlineConfigItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("Invalid inline config item: {}", self.0)) - } -} - -/// A disabled formatting range. `loose` designates that the range includes any loc which -/// may start in between start and end, whereas the strict version requires that -/// `range.start >= loc.start <=> loc.end <= range.end` -#[derive(Debug)] -struct DisabledRange { - start: usize, - end: usize, - loose: bool, -} - -impl DisabledRange { - fn includes(&self, loc: Loc) -> bool { - loc.start() >= self.start && (if self.loose { loc.start() } else { loc.end() } <= self.end) - } -} - -/// An inline config. Keeps track of disabled ranges. -/// -/// This is a list of Inline Config items for locations in a source file. This is -/// usually acquired by parsing the comments for an `forgefmt:` items. -/// -/// See [`Comments::parse_inline_config_items`](crate::Comments::parse_inline_config_items) for -/// details. -#[derive(Debug, Default)] -pub struct InlineConfig { - disabled_ranges: Vec, -} - -impl InlineConfig { - /// Build a new inline config with an iterator of inline config items and their locations in a - /// source file - pub fn new(items: impl IntoIterator, src: &str) -> Self { - let mut disabled_ranges = vec![]; - let mut disabled_range_start = None; - let mut disabled_depth = 0usize; - for (loc, item) in items.into_iter().sorted_by_key(|(loc, _)| loc.start()) { - match item { - InlineConfigItem::DisableNextItem => { - let offset = loc.end(); - let mut char_indices = src[offset..] - .comment_state_char_indices() - .filter_map(|(state, idx, ch)| match state { - CommentState::None => Some((idx, ch)), - _ => None, - }) - .skip_while(|(_, ch)| ch.is_whitespace()); - if let Some((mut start, _)) = char_indices.next() { - start += offset; - let end = char_indices - .find(|(_, ch)| !ch.is_whitespace()) - .map(|(idx, _)| offset + idx) - .unwrap_or(src.len()); - disabled_ranges.push(DisabledRange { start, end, loose: true }); - } - } - InlineConfigItem::DisableLine => { - let mut prev_newline = - src[..loc.start()].char_indices().rev().skip_while(|(_, ch)| *ch != '\n'); - let start = prev_newline.next().map(|(idx, _)| idx).unwrap_or_default(); - - let end_offset = loc.end(); - let mut next_newline = - src[end_offset..].char_indices().skip_while(|(_, ch)| *ch != '\n'); - let end = - end_offset + next_newline.next().map(|(idx, _)| idx).unwrap_or_default(); - - disabled_ranges.push(DisabledRange { start, end, loose: false }); - } - InlineConfigItem::DisableNextLine => { - let offset = loc.end(); - let mut char_indices = - src[offset..].char_indices().skip_while(|(_, ch)| *ch != '\n').skip(1); - if let Some((mut start, _)) = char_indices.next() { - start += offset; - let end = char_indices - .find(|(_, ch)| *ch == '\n') - .map(|(idx, _)| offset + idx + 1) - .unwrap_or(src.len()); - disabled_ranges.push(DisabledRange { start, end, loose: false }); - } - } - InlineConfigItem::DisableStart => { - if disabled_depth == 0 { - disabled_range_start = Some(loc.end()); - } - disabled_depth += 1; - } - InlineConfigItem::DisableEnd => { - disabled_depth = disabled_depth.saturating_sub(1); - if disabled_depth == 0 { - if let Some(start) = disabled_range_start.take() { - disabled_ranges.push(DisabledRange { - start, - end: loc.start(), - loose: false, - }) - } - } - } - } - } - if let Some(start) = disabled_range_start.take() { - disabled_ranges.push(DisabledRange { start, end: src.len(), loose: false }) - } - Self { disabled_ranges } - } - - /// Check if the location is in a disabled range - pub fn is_disabled(&self, loc: Loc) -> bool { - self.disabled_ranges.iter().any(|range| range.includes(loc)) - } -} diff --git a/crates/fmt/src/lib.rs b/crates/fmt/src/lib.rs index 006b4db02abe8..cf973d4d99dff 100644 --- a/crates/fmt/src/lib.rs +++ b/crates/fmt/src/lib.rs @@ -1,27 +1,288 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -#[macro_use] -extern crate tracing; - -mod buffer; -pub mod chunk; -mod comments; -mod formatter; -mod helpers; -pub mod inline_config; -mod macros; -pub mod solang_ext; -mod string; -pub mod visit; +#![cfg_attr(docsrs, feature(doc_cfg))] -pub use foundry_config::fmt::*; +const DEBUG: bool = false || option_env!("FMT_DEBUG").is_some(); +const DEBUG_INDENT: bool = false || option_env!("FMT_DEBUG").is_some(); + +use foundry_common::comments::{ + Comment, Comments, + inline_config::{InlineConfig, InlineConfigItem}, +}; + +mod state; + +mod pp; -pub use comments::Comments; -pub use formatter::{Formatter, FormatterError}; -pub use helpers::{ - format, format_diagnostics_report, format_to, offset_to_line_column, parse, parse2, Parsed, +use solar::{ + parse::{ + ast::{SourceUnit, Span}, + interface::{Session, diagnostics::EmittedDiagnostics, source_map::SourceFile}, + }, + sema::{Compiler, Gcx, Source}, }; -pub use inline_config::InlineConfig; -pub use visit::{Visitable, Visitor}; + +use std::{path::Path, sync::Arc}; + +pub use foundry_config::fmt::*; + +/// The result of the formatter. +pub type FormatterResult = DiagnosticsResult; + +/// The result of the formatter. +#[derive(Debug)] +pub enum DiagnosticsResult { + /// Everything went well. + Ok(T), + /// No errors encountered, but warnings or other non-error diagnostics were emitted. + OkWithDiagnostics(T, E), + /// Errors encountered, but a result was produced anyway. + ErrRecovered(T, E), + /// Fatal errors encountered. + Err(E), +} + +impl DiagnosticsResult { + /// Converts the formatter result into a standard result. + /// + /// This ignores any non-error diagnostics if `Ok`, and any valid result if `Err`. + pub fn into_result(self) -> Result { + match self { + Self::Ok(s) | Self::OkWithDiagnostics(s, _) => Ok(s), + Self::ErrRecovered(_, d) | Self::Err(d) => Err(d), + } + } + + /// Returns the result, even if it was produced with errors. + pub fn into_ok(self) -> Result { + match self { + Self::Ok(s) | Self::OkWithDiagnostics(s, _) | Self::ErrRecovered(s, _) => Ok(s), + Self::Err(e) => Err(e), + } + } + + /// Returns any result produced. + pub const fn ok_ref(&self) -> Option<&T> { + match self { + Self::Ok(s) | Self::OkWithDiagnostics(s, _) | Self::ErrRecovered(s, _) => Some(s), + Self::Err(_) => None, + } + } + + /// Returns any diagnostics emitted. + pub const fn err_ref(&self) -> Option<&E> { + match self { + Self::Ok(_) => None, + Self::OkWithDiagnostics(_, d) | Self::ErrRecovered(_, d) | Self::Err(d) => Some(d), + } + } + + /// Returns `true` if the result is `Ok`. + pub const fn is_ok(&self) -> bool { + matches!(self, Self::Ok(_) | Self::OkWithDiagnostics(_, _)) + } + + /// Returns `true` if the result is `Err`. + pub const fn is_err(&self) -> bool { + !self.is_ok() + } +} + +pub fn format_file( + path: &Path, + config: Arc, + compiler: &mut Compiler, +) -> FormatterResult { + format_inner(config, compiler, &|sess| { + sess.source_map().load_file(path).map_err(|e| sess.dcx.err(e.to_string()).emit()) + }) +} + +pub fn format_source( + source: &str, + path: Option<&Path>, + config: Arc, + compiler: &mut Compiler, +) -> FormatterResult { + format_inner(config, compiler, &|sess| { + let name = match path { + Some(path) => solar::parse::interface::source_map::FileName::Real(path.to_path_buf()), + None => solar::parse::interface::source_map::FileName::Stdin, + }; + sess.source_map() + .new_source_file(name, source) + .map_err(|e| sess.dcx.err(e.to_string()).emit()) + }) +} + +/// Format a string input with the default compiler. +pub fn format(source: &str, config: FormatterConfig) -> FormatterResult { + let mut compiler = Compiler::new( + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(), + ); + + format_source(source, None, Arc::new(config), &mut compiler) +} + +fn format_inner( + config: Arc, + compiler: &mut Compiler, + mk_file: &(dyn Fn(&Session) -> solar::parse::interface::Result> + Sync + Send), +) -> FormatterResult { + // First pass formatting + let first_result = format_once(config.clone(), compiler, mk_file); + + // If first pass was not successful, return the result + if first_result.is_err() { + return first_result; + } + let Some(first_formatted) = first_result.ok_ref() else { return first_result }; + + // Second pass formatting + let second_result = format_once(config, compiler, &|sess| { + // Need a new name since we can't overwrite the original file. + let prev_sf = mk_file(sess)?; + let new_name = match &prev_sf.name { + solar::interface::source_map::FileName::Real(path) => { + path.with_extension("again.sol").into() + } + solar::interface::source_map::FileName::Stdin => { + solar::interface::source_map::FileName::Custom("stdin-again".to_string()) + } + solar::interface::source_map::FileName::Custom(name) => { + solar::interface::source_map::FileName::Custom(format!("{name}-again")) + } + }; + sess.source_map() + .new_source_file(new_name, first_formatted) + .map_err(|e| sess.dcx.err(e.to_string()).emit()) + }); + + // Check if the two passes produce the same output (idempotency) + match (first_result.ok_ref(), second_result.ok_ref()) { + (Some(first), Some(second)) if first != second => { + panic!("formatter is not idempotent:\n{}", diff(first, second)); + } + _ => {} + } + + if first_result.is_ok() && second_result.is_err() && !DEBUG { + panic!( + "failed to format a second time:\nfirst_result={first_result:#?}\nsecond_result={second_result:#?}" + ); + // second_result + } else { + first_result + } +} + +fn diff(first: &str, second: &str) -> impl std::fmt::Display { + use std::fmt::Write; + let diff = similar::TextDiff::from_lines(first, second); + let mut s = String::new(); + for change in diff.iter_all_changes() { + let tag = match change.tag() { + similar::ChangeTag::Delete => "-", + similar::ChangeTag::Insert => "+", + similar::ChangeTag::Equal => " ", + }; + write!(s, "{tag}{change}").unwrap(); + } + s +} + +fn format_once( + config: Arc, + compiler: &mut Compiler, + mk_file: &( + dyn Fn(&solar::interface::Session) -> solar::interface::Result> + + Send + + Sync + ), +) -> FormatterResult { + let res = compiler.enter_mut(|c| -> solar::interface::Result { + let mut pcx = c.parse(); + pcx.set_resolve_imports(false); + let file = mk_file(c.sess())?; + pcx.add_file(file.clone()); + pcx.parse(); + c.dcx().has_errors()?; + + let gcx = c.gcx(); + let (_, source) = gcx.get_ast_source(&file.name).unwrap(); + Ok(format_ast(gcx, source, config).expect("unable to format AST")) + }); + + let diagnostics = compiler.sess().dcx.emitted_diagnostics().unwrap(); + match (res, compiler.sess().dcx.has_errors()) { + (Ok(s), Ok(())) if diagnostics.is_empty() => FormatterResult::Ok(s), + (Ok(s), Ok(())) => FormatterResult::OkWithDiagnostics(s, diagnostics), + (Ok(s), Err(_)) => FormatterResult::ErrRecovered(s, diagnostics), + (Err(_), Ok(_)) => unreachable!(), + (Err(_), Err(_)) => FormatterResult::Err(diagnostics), + } +} + +// A parallel-safe "worker" function. +pub fn format_ast<'ast>( + gcx: Gcx<'ast>, + source: &'ast Source<'ast>, + config: Arc, +) -> Option { + let comments = Comments::new( + &source.file, + gcx.sess.source_map(), + true, + config.wrap_comments, + matches!(config.style, IndentStyle::Tab).then(|| config.tab_width), + ); + let ast = source.ast.as_ref()?; + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + + let mut state = state::State::new(gcx.sess.source_map(), config, inline_config, comments); + state.print_source_unit(ast); + Some(state.s.eof()) +} + +fn parse_inline_config<'ast>( + sess: &Session, + comments: &Comments, + ast: &'ast SourceUnit<'ast>, +) -> InlineConfig<()> { + let parse_item = |mut item: &str, cmnt: &Comment| -> Option<(Span, InlineConfigItem<()>)> { + if let Some(prefix) = cmnt.prefix() { + item = item.strip_prefix(prefix).unwrap_or(item); + } + if let Some(suffix) = cmnt.suffix() { + item = item.strip_suffix(suffix).unwrap_or(item); + } + let item = item.trim_start().strip_prefix("forgefmt:")?.trim(); + match item.parse::>() { + Ok(item) => Some((cmnt.span, item)), + Err(e) => { + sess.dcx.warn(e.to_string()).span(cmnt.span).emit(); + None + } + } + }; + + let items = comments.iter().flat_map(|cmnt| { + let mut found_items = Vec::with_capacity(2); + // Always process the first line. + if let Some(line) = cmnt.lines.first() + && let Some(item) = parse_item(line, cmnt) + { + found_items.push(item); + } + // If the comment has more than one line, process the last line. + if cmnt.lines.len() > 1 + && let Some(line) = cmnt.lines.last() + && let Some(item) = parse_item(line, cmnt) + { + found_items.push(item); + } + found_items + }); + + InlineConfig::from_ast(items, ast, sess.source_map()) +} diff --git a/crates/fmt/src/macros.rs b/crates/fmt/src/macros.rs deleted file mode 100644 index c5c9d31a7d0e0..0000000000000 --- a/crates/fmt/src/macros.rs +++ /dev/null @@ -1,125 +0,0 @@ -macro_rules! write_chunk { - ($self:expr, $format_str:literal) => {{ - write_chunk!($self, $format_str,) - }}; - ($self:expr, $format_str:literal, $($arg:tt)*) => {{ - $self.write_chunk(&format!($format_str, $($arg)*).into()) - }}; - ($self:expr, $loc:expr) => {{ - write_chunk!($self, $loc, "") - }}; - ($self:expr, $loc:expr, $format_str:literal) => {{ - write_chunk!($self, $loc, $format_str,) - }}; - ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, None, None, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ - write_chunk!($self, $loc, $end_loc, $format_str,) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, Some($end_loc), None, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, Some($end_loc), Some($needs_space), format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; -} - -macro_rules! writeln_chunk { - ($self:expr) => {{ - writeln_chunk!($self, "") - }}; - ($self:expr, $format_str:literal) => {{ - writeln_chunk!($self, $format_str,) - }}; - ($self:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, "{}\n", format_args!($format_str, $($arg)*)) - }}; - ($self:expr, $loc:expr) => {{ - writeln_chunk!($self, $loc, "") - }}; - ($self:expr, $loc:expr, $format_str:literal) => {{ - writeln_chunk!($self, $loc, $format_str,) - }}; - ($self:expr, $loc:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, $loc, "{}\n", format_args!($format_str, $($arg)*)) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal) => {{ - writeln_chunk!($self, $loc, $end_loc, $format_str,) - }}; - ($self:expr, $loc:expr, $end_loc:expr, $format_str:literal, $($arg:tt)*) => {{ - write_chunk!($self, $loc, $end_loc, "{}\n", format_args!($format_str, $($arg)*)) - }}; -} - -macro_rules! write_chunk_spaced { - ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal) => {{ - write_chunk_spaced!($self, $loc, $needs_space, $format_str,) - }}; - ($self:expr, $loc:expr, $needs_space:expr, $format_str:literal, $($arg:tt)*) => {{ - let chunk = $self.chunk_at($loc, None, $needs_space, format_args!($format_str, $($arg)*),); - $self.write_chunk(&chunk) - }}; -} - -macro_rules! buf_fn { - ($vis:vis fn $name:ident(&self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { - $vis fn $name(&self, $($arg_name : $arg_ty),*) $(-> $ret)? { - if self.temp_bufs.is_empty() { - self.buf.$name($($arg_name),*) - } else { - self.temp_bufs.last().unwrap().$name($($arg_name),*) - } - } - }; - ($vis:vis fn $name:ident(&mut self $(,)? $($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { - $vis fn $name(&mut self, $($arg_name : $arg_ty),*) $(-> $ret)? { - if self.temp_bufs.is_empty() { - self.buf.$name($($arg_name),*) - } else { - self.temp_bufs.last_mut().unwrap().$name($($arg_name),*) - } - } - }; -} - -macro_rules! return_source_if_disabled { - ($self:expr, $loc:expr) => {{ - let loc = $loc; - if $self.inline_config.is_disabled(loc) { - trace!("Returning because disabled: {loc:?}"); - return $self.visit_source(loc) - } - }}; - ($self:expr, $loc:expr, $suffix:literal) => {{ - let mut loc = $loc; - let has_suffix = $self.extend_loc_until(&mut loc, $suffix); - if $self.inline_config.is_disabled(loc) { - $self.visit_source(loc)?; - trace!("Returning because disabled: {loc:?}"); - if !has_suffix { - write!($self.buf(), "{}", $suffix)?; - } - return Ok(()) - } - }}; -} - -macro_rules! visit_source_if_disabled_else { - ($self:expr, $loc:expr, $block:block) => {{ - let loc = $loc; - if $self.inline_config.is_disabled(loc) { - $self.visit_source(loc)?; - } else $block - }}; -} - -pub(crate) use buf_fn; -pub(crate) use return_source_if_disabled; -pub(crate) use visit_source_if_disabled_else; -pub(crate) use write_chunk; -pub(crate) use write_chunk_spaced; -pub(crate) use writeln_chunk; diff --git a/crates/fmt/src/pp/convenience.rs b/crates/fmt/src/pp/convenience.rs new file mode 100644 index 0000000000000..a4a2d693c6149 --- /dev/null +++ b/crates/fmt/src/pp/convenience.rs @@ -0,0 +1,164 @@ +use super::{BeginToken, BreakToken, Breaks, IndentStyle, Printer, SIZE_INFINITY, Token}; +use std::borrow::Cow; + +impl Printer { + /// "raw box" + pub fn rbox(&mut self, indent: isize, breaks: Breaks) { + self.scan_begin(BeginToken { indent: IndentStyle::Block { offset: indent }, breaks }); + } + + /// Inconsistent breaking box + pub fn ibox(&mut self, indent: isize) { + self.rbox(indent, Breaks::Inconsistent); + } + + /// Consistent breaking box + pub fn cbox(&mut self, indent: isize) { + self.rbox(indent, Breaks::Consistent); + } + + pub fn visual_align(&mut self) { + self.scan_begin(BeginToken { indent: IndentStyle::Visual, breaks: Breaks::Consistent }); + } + + pub fn break_offset(&mut self, n: usize, off: isize) { + self.scan_break(BreakToken { offset: off, blank_space: n, ..BreakToken::default() }); + } + + pub fn end(&mut self) { + self.scan_end(); + } + + pub fn eof(mut self) -> String { + self.scan_eof(); + self.out + } + + pub fn word(&mut self, w: impl Into>) { + self.scan_string(w.into()); + } + + fn spaces(&mut self, n: usize) { + self.break_offset(n, 0); + } + + pub fn zerobreak(&mut self) { + self.spaces(0); + } + + pub fn space(&mut self) { + self.spaces(1); + } + + pub fn hardbreak(&mut self) { + self.spaces(SIZE_INFINITY as usize); + } + + pub fn last_token_is_neverbreak(&self) -> bool { + if let Some(token) = self.last_token() { + return token.is_neverbreak(); + } + + false + } + + pub fn last_token_is_break(&self) -> bool { + if let Some(token) = self.last_token() { + return matches!(token, Token::Break(_)); + } + false + } + + pub fn last_token_is_space(&self) -> bool { + if let Some(token) = self.last_token() + && token.is_space() + { + return true; + } + + self.out.ends_with(' ') + } + + pub fn is_beginning_of_line(&self) -> bool { + match self.last_token() { + Some(last_token) => last_token.is_hardbreak(), + None => self.out.is_empty() || self.out.ends_with('\n'), + } + } + + /// Attempts to identify whether the current position is: + /// 1. the beginning of a line (empty) + /// 2. a line with only indentation (just whitespaces) + /// + /// NOTE: this is still an educated guess, based on a heuristic. + pub fn is_bol_or_only_ind(&self) -> bool { + for i in self.buf.index_range().rev() { + let token = &self.buf[i].token; + if token.is_hardbreak() { + return true; + } + if Self::token_has_non_whitespace_content(token) { + return false; + } + } + + let last_line = + if let Some(pos) = self.out.rfind('\n') { &self.out[pos + 1..] } else { &self.out[..] }; + + last_line.trim().is_empty() + } + + fn token_has_non_whitespace_content(token: &Token) -> bool { + match token { + Token::String(s) => !s.trim().is_empty(), + Token::Break(BreakToken { pre_break: Some(s), .. }) => !s.trim().is_empty(), + _ => false, + } + } + + pub(crate) fn hardbreak_tok_offset(offset: isize) -> Token { + Token::Break(BreakToken { + offset, + blank_space: SIZE_INFINITY as usize, + ..BreakToken::default() + }) + } + + pub fn hardbreak_if_nonempty(&mut self) { + self.scan_break(BreakToken { + blank_space: SIZE_INFINITY as usize, + if_nonempty: true, + ..BreakToken::default() + }); + } + + pub fn neverbreak(&mut self) { + self.scan_break(BreakToken { never_break: true, ..BreakToken::default() }); + } +} + +impl Token { + pub(crate) const fn is_neverbreak(&self) -> bool { + if let Self::Break(BreakToken { never_break, .. }) = *self { + return never_break; + } + false + } + + pub(crate) const fn is_hardbreak(&self) -> bool { + if let Self::Break(BreakToken { blank_space, never_break, .. }) = *self { + return blank_space == SIZE_INFINITY as usize && !never_break; + } + false + } + + pub(crate) fn is_space(&self) -> bool { + match self { + Self::Break(BreakToken { offset, blank_space, .. }) => { + *offset == 0 && *blank_space == 1 + } + Self::String(s) => s.ends_with(' '), + _ => false, + } + } +} diff --git a/crates/fmt/src/pp/helpers.rs b/crates/fmt/src/pp/helpers.rs new file mode 100644 index 0000000000000..9b1a5aa91582e --- /dev/null +++ b/crates/fmt/src/pp/helpers.rs @@ -0,0 +1,46 @@ +use super::{Printer, Token}; +use std::borrow::Cow; + +impl Printer { + pub fn word_space(&mut self, w: impl Into>) { + self.word(w); + self.space(); + } + + /// Adds a new hardbreak if not at the beginning of the line. + /// If there was a buffered break token, replaces it (ensures hardbreak) keeping the offset. + pub fn hardbreak_if_not_bol(&mut self) { + if !self.is_bol_or_only_ind() { + if let Some(Token::Break(last)) = self.last_token_still_buffered() + && last.offset != 0 + { + self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)); + return; + } + self.hardbreak(); + } + } + + pub fn space_if_not_bol(&mut self) { + if !self.is_bol_or_only_ind() { + self.space(); + } + } + + pub fn nbsp(&mut self) { + self.word(" "); + } + + pub fn space_or_nbsp(&mut self, breaks: bool) { + if breaks { + self.space(); + } else { + self.nbsp(); + } + } + + pub fn word_nbsp(&mut self, w: impl Into>) { + self.word(w); + self.nbsp(); + } +} diff --git a/crates/fmt/src/pp/mod.rs b/crates/fmt/src/pp/mod.rs new file mode 100644 index 0000000000000..89783f05f5bb8 --- /dev/null +++ b/crates/fmt/src/pp/mod.rs @@ -0,0 +1,488 @@ +//! Adapted from [`rustc_ast_pretty`](https://github.com/rust-lang/rust/blob/07d3fd1d9b9c1f07475b96a9d168564bf528db68/compiler/rustc_ast_pretty/src/pp.rs) +//! and [`prettyplease`](https://github.com/dtolnay/prettyplease/blob/8eb8c14649aea32e810732bd4d64fe519e6b752a/src/algorithm.rs). + +use crate::{DEBUG, DEBUG_INDENT}; +use ring::RingBuffer; +use std::{borrow::Cow, cmp, collections::VecDeque, iter}; + +mod convenience; +mod helpers; +mod ring; + +// Every line is allowed at least this much space, even if highly indented. +const MIN_SPACE: isize = 40; + +/// How to break. Described in more detail in the module docs. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Breaks { + Consistent, + Inconsistent, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum IndentStyle { + /// Vertically aligned under whatever column this block begins at. + /// ```ignore + /// fn demo(arg1: usize, + /// arg2: usize) {} + /// ``` + Visual, + /// Indented relative to the indentation level of the previous line. + /// ```ignore + /// fn demo( + /// arg1: usize, + /// arg2: usize, + /// ) {} + /// ``` + Block { offset: isize }, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) struct BreakToken { + pub(crate) offset: isize, + pub(crate) blank_space: usize, + pub(crate) pre_break: Option<&'static str>, + pub(crate) post_break: Option<&'static str>, + pub(crate) if_nonempty: bool, + pub(crate) never_break: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct BeginToken { + indent: IndentStyle, + breaks: Breaks, +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum Token { + // In practice a string token contains either a `&'static str` or a + // `String`. `Cow` is overkill for this because we never modify the data, + // but it's more convenient than rolling our own more specialized type. + String(Cow<'static, str>), + Break(BreakToken), + Begin(BeginToken), + End, +} + +#[derive(Copy, Clone, Debug)] +enum PrintFrame { + Fits(Breaks), + Broken(usize, Breaks), +} + +pub(crate) const SIZE_INFINITY: isize = 0xffff; + +#[derive(Debug)] +pub struct Printer { + out: String, + /// Number of spaces left on line. + space: isize, + /// Ring-buffer of tokens and calculated sizes. + buf: RingBuffer, + /// Running size of stream "...left". + left_total: isize, + /// Running size of stream "...right". + right_total: isize, + /// Pseudo-stack, really a ring too. Holds the + /// primary-ring-buffers index of the Begin that started the + /// current block, possibly with the most recent Break after that + /// Begin (if there is any) on top of it. Stuff is flushed off the + /// bottom as it becomes irrelevant due to the primary ring-buffer + /// advancing. + scan_stack: VecDeque, + /// Stack of blocks-in-progress being flushed by print. + print_stack: Vec, + /// Level of indentation of current line. + indent: usize, + /// Buffered indentation to avoid writing trailing whitespace. + pending_indentation: usize, + /// The token most recently popped from the left boundary of the + /// ring-buffer for printing. + last_printed: Option, + + /// Target line width. + margin: isize, + /// If `Some(tab_width)` the printer will use tabs for indentation. + indent_config: Option, +} + +#[derive(Debug)] +pub struct BufEntry { + token: Token, + size: isize, +} + +impl Printer { + pub fn new(margin: usize, use_tab_with_size: Option) -> Self { + let margin = (margin as isize).clamp(MIN_SPACE, SIZE_INFINITY - 1); + Self { + out: String::new(), + space: margin, + buf: RingBuffer::new(), + left_total: 0, + right_total: 0, + scan_stack: VecDeque::new(), + print_stack: Vec::new(), + indent: 0, + pending_indentation: 0, + last_printed: None, + + margin, + indent_config: use_tab_with_size, + } + } + + /// Predicts available space on the current or next line based on pending breaks. + /// + /// This function provides a heuristic for estimating available space by checking if + /// an unconditional hard break is pending in the buffer. The printer's internal + /// `self.space` value may not accurately reflect pending formatting decisions. + /// + /// # Returns + /// + /// - The full `margin` if an unconditional hard break is pending, signaling that a new line + /// will be created. Callers should apply their own indentation logic as they have more + /// semantic context about the code structure. + /// - The current space left (`self.space`) if no hard break is found, which can be trusted when + /// no line breaks are imminent. + /// + /// # Trade-offs + /// + /// This heuristic may overestimate available space, + /// but provides a reliable signal for hard breaks while keeping the implementation + /// simple. + pub(crate) fn space_left(&self) -> usize { + // Scan backwards through the buffer for the last unconditional hard break. + for i in self.buf.index_range().rev() { + let token = &self.buf[i].token; + + if let Token::Break(break_token) = token + && break_token.blank_space as isize >= SIZE_INFINITY + && !break_token.never_break + { + return self.margin as usize; + } + + // Stop at first non-end token. + if !matches!(token, Token::End) { + break; + } + } + + // If no hard break pending, return actual space on current line or the full margin if space + // is negative. + (if self.space < 0 { self.margin } else { self.space }) as usize + } + + pub(crate) fn last_token(&self) -> Option<&Token> { + self.last_token_still_buffered().or(self.last_printed.as_ref()) + } + + pub(crate) fn last_token_still_buffered(&self) -> Option<&Token> { + if self.buf.is_empty() { + return None; + } + Some(&self.buf.last().token) + } + + /// Be very careful with this! + pub(crate) fn replace_last_token_still_buffered(&mut self, token: Token) { + self.buf.last_mut().token = token; + } + + /// WARNING: Be very careful with this! + /// + /// Searches backwards through the buffer to find and replace the last token + /// that satisfies a predicate. This is a specialized and sensitive operation. + /// + /// This function's traversal logic is specifically designed to handle cases + /// where formatting boxes have been closed (e.g., after a multi-line + /// comment). It will automatically skip over any trailing `Token::End` + /// tokens to find the substantive token before them. + /// + /// The search stops as soon as it encounters any token other than `End` + /// (i.e., a `String`, `Break`, or `Begin`). The provided predicate is then + /// called on that token. If the predicate returns `true`, the token is + /// replaced. + /// + /// This function will only ever evaluate the predicate on **one** token. + pub(crate) fn find_and_replace_last_token_still_buffered( + &mut self, + new_token: Token, + predicate: F, + ) where + F: FnOnce(&Token) -> bool, + { + for i in self.buf.index_range().rev() { + let token = &self.buf[i].token; + if matches!(token, Token::End) { + // It's safe to skip the end of a box. + continue; + } + + // Apply the predicate and return after the first non-end token. + if predicate(token) { + self.buf[i].token = new_token; + } + break; + } + } + + fn scan_eof(&mut self) { + if !self.scan_stack.is_empty() { + self.check_stack(0); + self.advance_left(); + } + } + + fn scan_begin(&mut self, token: BeginToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } + let right = self.buf.push(BufEntry { token: Token::Begin(token), size: -self.right_total }); + self.scan_stack.push_back(right); + } + + fn scan_end(&mut self) { + if self.scan_stack.is_empty() { + self.print_end(); + } else { + if !self.buf.is_empty() + && let Token::Break(break_token) = self.buf.last().token + { + if self.buf.len() >= 2 + && let Token::Begin(_) = self.buf.second_last().token + { + self.buf.pop_last(); + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + return; + } + if break_token.if_nonempty { + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + } + } + let right = self.buf.push(BufEntry { token: Token::End, size: -1 }); + self.scan_stack.push_back(right); + } + } + + pub(crate) fn scan_break(&mut self, token: BreakToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } else { + self.check_stack(0); + } + let right = self.buf.push(BufEntry { token: Token::Break(token), size: -self.right_total }); + self.scan_stack.push_back(right); + self.right_total += token.blank_space as isize; + } + + fn scan_string(&mut self, string: Cow<'static, str>) { + if self.scan_stack.is_empty() { + self.print_string(&string); + } else { + let len = string.len() as isize; + self.buf.push(BufEntry { token: Token::String(string), size: len }); + self.right_total += len; + self.check_stream(); + } + } + + #[track_caller] + pub(crate) fn offset(&mut self, offset: isize) { + match &mut self.buf.last_mut().token { + Token::Break(token) => token.offset += offset, + Token::Begin(_) => {} + Token::String(_) | Token::End => unreachable!(), + } + } + + pub(crate) fn ends_with(&self, ch: char) -> bool { + for i in self.buf.index_range().rev() { + if let Token::String(token) = &self.buf[i].token { + return token.ends_with(ch); + } + } + self.out.ends_with(ch) + } + + fn check_stream(&mut self) { + while self.right_total - self.left_total > self.space { + if *self.scan_stack.front().unwrap() == self.buf.index_range().start { + self.scan_stack.pop_front().unwrap(); + self.buf.first_mut().size = SIZE_INFINITY; + } + + self.advance_left(); + + if self.buf.is_empty() { + break; + } + } + } + + fn advance_left(&mut self) { + while self.buf.first().size >= 0 { + let left = self.buf.pop_first(); + + match &left.token { + Token::String(string) => { + self.left_total += left.size; + self.print_string(string); + } + Token::Break(token) => { + self.left_total += token.blank_space as isize; + self.print_break(*token, left.size); + } + Token::Begin(token) => self.print_begin(*token, left.size), + Token::End => self.print_end(), + } + + self.last_printed = Some(left.token); + + if self.buf.is_empty() { + break; + } + } + } + + fn check_stack(&mut self, mut depth: usize) { + while let Some(&index) = self.scan_stack.back() { + let entry = &mut self.buf[index]; + match entry.token { + Token::Begin(_) => { + if depth == 0 { + break; + } + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + depth -= 1; + } + Token::End => { + // paper says + not =, but that makes no sense. + self.scan_stack.pop_back().unwrap(); + entry.size = 1; + depth += 1; + } + _ => { + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + if depth == 0 { + break; + } + } + } + } + } + + fn get_top(&self) -> PrintFrame { + self.print_stack.last().copied().unwrap_or(PrintFrame::Broken(0, Breaks::Inconsistent)) + } + + fn print_begin(&mut self, token: BeginToken, size: isize) { + if DEBUG { + self.out.push(match token.breaks { + Breaks::Consistent => '«', + Breaks::Inconsistent => '‹', + }); + if DEBUG_INDENT && let IndentStyle::Block { offset } = token.indent { + self.out.extend(offset.to_string().chars().map(|ch| match ch { + '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] + [(ch as u8 - b'0') as usize], + '-' => '₋', + _ => unreachable!(), + })); + } + } + + if size > self.space { + self.print_stack.push(PrintFrame::Broken(self.indent, token.breaks)); + self.indent = match token.indent { + IndentStyle::Block { offset } => { + usize::try_from(self.indent as isize + offset).unwrap() + } + IndentStyle::Visual => (self.margin - self.space) as usize, + }; + } else { + self.print_stack.push(PrintFrame::Fits(token.breaks)); + } + } + + fn print_end(&mut self) { + let breaks = match self.print_stack.pop().unwrap() { + PrintFrame::Broken(indent, breaks) => { + self.indent = indent; + breaks + } + PrintFrame::Fits(breaks) => breaks, + }; + if DEBUG { + self.out.push(match breaks { + Breaks::Consistent => '»', + Breaks::Inconsistent => '›', + }); + } + } + + fn print_break(&mut self, token: BreakToken, size: isize) { + let fits = token.never_break + || match self.get_top() { + PrintFrame::Fits(..) => true, + PrintFrame::Broken(.., Breaks::Consistent) => false, + PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, + }; + if fits { + self.pending_indentation += token.blank_space; + self.space -= token.blank_space as isize; + if DEBUG { + self.out.push('·'); + } + } else { + if let Some(pre_break) = token.pre_break { + self.print_indent(); + self.out.push_str(pre_break); + } + if DEBUG { + self.out.push('·'); + } + self.out.push('\n'); + let indent = self.indent as isize + token.offset; + self.pending_indentation = usize::try_from(indent).expect("negative indentation"); + self.space = cmp::max(self.margin - indent, MIN_SPACE); + if let Some(post_break) = token.post_break { + self.print_indent(); + self.out.push_str(post_break); + self.space -= post_break.len() as isize; + } + } + } + + fn print_string(&mut self, string: &str) { + self.print_indent(); + self.out.push_str(string); + self.space -= string.len() as isize; + } + + fn print_indent(&mut self) { + self.out.reserve(self.pending_indentation); + if let Some(tab_width) = self.indent_config { + let num_tabs = self.pending_indentation / tab_width; + self.out.extend(iter::repeat_n('\t', num_tabs)); + + let remainder = self.pending_indentation % tab_width; + self.out.extend(iter::repeat_n(' ', remainder)); + } else { + self.out.extend(iter::repeat_n(' ', self.pending_indentation)); + } + self.pending_indentation = 0; + } +} diff --git a/crates/fmt/src/pp/ring.rs b/crates/fmt/src/pp/ring.rs new file mode 100644 index 0000000000000..198f3c0a1baee --- /dev/null +++ b/crates/fmt/src/pp/ring.rs @@ -0,0 +1,95 @@ +use std::{ + collections::VecDeque, + ops::{Index, IndexMut, Range}, +}; + +#[derive(Debug)] +pub(crate) struct RingBuffer { + data: VecDeque, + // Abstract index of data[0] in the infinitely sized queue. + offset: usize, +} + +impl RingBuffer { + pub(crate) const fn new() -> Self { + Self { data: VecDeque::new(), offset: 0 } + } + + pub(crate) fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub(crate) fn len(&self) -> usize { + self.data.len() + } + + pub(crate) fn push(&mut self, value: T) -> usize { + let index = self.offset + self.data.len(); + self.data.push_back(value); + index + } + + pub(crate) fn clear(&mut self) { + self.data.clear(); + } + + pub(crate) fn index_range(&self) -> Range { + self.offset..self.offset + self.data.len() + } + + #[inline] + #[track_caller] + pub(crate) fn first(&self) -> &T { + &self.data[0] + } + + #[inline] + #[track_caller] + pub(crate) fn first_mut(&mut self) -> &mut T { + &mut self.data[0] + } + + #[inline] + #[track_caller] + pub(crate) fn pop_first(&mut self) -> T { + self.offset += 1; + self.data.pop_front().unwrap() + } + + #[inline] + #[track_caller] + pub(crate) fn last(&self) -> &T { + self.data.back().unwrap() + } + + #[inline] + #[track_caller] + pub(crate) fn last_mut(&mut self) -> &mut T { + self.data.back_mut().unwrap() + } + + #[inline] + #[track_caller] + pub(crate) fn second_last(&self) -> &T { + &self.data[self.data.len() - 2] + } + + #[inline] + #[track_caller] + pub(crate) fn pop_last(&mut self) { + self.data.pop_back().unwrap(); + } +} + +impl Index for RingBuffer { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + &self.data[index.checked_sub(self.offset).unwrap()] + } +} + +impl IndexMut for RingBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[index.checked_sub(self.offset).unwrap()] + } +} diff --git a/crates/fmt/src/solang_ext/ast_eq.rs b/crates/fmt/src/solang_ext/ast_eq.rs deleted file mode 100644 index 1ba1748d84644..0000000000000 --- a/crates/fmt/src/solang_ext/ast_eq.rs +++ /dev/null @@ -1,708 +0,0 @@ -use alloy_primitives::{Address, I256, U256}; -use solang_parser::pt::*; -use std::str::FromStr; - -/// Helper to convert a string number into a comparable one -fn to_num(string: &str) -> I256 { - if string.is_empty() { - return I256::ZERO - } - string.replace('_', "").trim().parse().unwrap() -} - -/// Helper to convert the fractional part of a number into a comparable one. -/// This will reverse the number so that 0's can be ignored -fn to_num_reversed(string: &str) -> U256 { - if string.is_empty() { - return U256::from(0) - } - string.replace('_', "").trim().chars().rev().collect::().parse().unwrap() -} - -/// Helper to filter [ParameterList] to omit empty -/// parameters -fn filter_params(list: &ParameterList) -> ParameterList { - list.iter().filter(|(_, param)| param.is_some()).cloned().collect::>() -} - -/// Check if two ParseTrees are equal ignoring location information or ordering if ordering does -/// not matter -pub trait AstEq { - fn ast_eq(&self, other: &Self) -> bool; -} - -impl AstEq for Loc { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for IdentifierPath { - fn ast_eq(&self, other: &Self) -> bool { - self.identifiers.ast_eq(&other.identifiers) - } -} - -impl AstEq for SourceUnit { - fn ast_eq(&self, other: &Self) -> bool { - self.0.ast_eq(&other.0) - } -} - -impl AstEq for VariableDefinition { - fn ast_eq(&self, other: &Self) -> bool { - let sorted_attrs = |def: &Self| { - let mut attrs = def.attrs.clone(); - attrs.sort(); - attrs - }; - self.ty.ast_eq(&other.ty) && - self.name.ast_eq(&other.name) && - self.initializer.ast_eq(&other.initializer) && - sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for FunctionDefinition { - fn ast_eq(&self, other: &Self) -> bool { - // attributes - let sorted_attrs = |def: &Self| { - let mut attrs = def.attributes.clone(); - attrs.sort(); - attrs - }; - - // params - let left_params = filter_params(&self.params); - let right_params = filter_params(&other.params); - let left_returns = filter_params(&self.returns); - let right_returns = filter_params(&other.returns); - - self.ty.ast_eq(&other.ty) && - self.name.ast_eq(&other.name) && - left_params.ast_eq(&right_params) && - self.return_not_returns.ast_eq(&other.return_not_returns) && - left_returns.ast_eq(&right_returns) && - self.body.ast_eq(&other.body) && - sorted_attrs(self).ast_eq(&sorted_attrs(other)) - } -} - -impl AstEq for Base { - fn ast_eq(&self, other: &Self) -> bool { - self.name.ast_eq(&other.name) && - self.args.clone().unwrap_or_default().ast_eq(&other.args.clone().unwrap_or_default()) - } -} - -impl AstEq for Vec -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - if self.len() != other.len() { - false - } else { - self.iter().zip(other.iter()).all(|(left, right)| left.ast_eq(right)) - } - } -} - -impl AstEq for Option -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - match (self, other) { - (Some(left), Some(right)) => left.ast_eq(right), - (None, None) => true, - _ => false, - } - } -} - -impl AstEq for Box -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for () { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for &T -where - T: AstEq, -{ - fn ast_eq(&self, other: &Self) -> bool { - T::ast_eq(self, other) - } -} - -impl AstEq for String { - fn ast_eq(&self, other: &Self) -> bool { - match (Address::from_str(self), Address::from_str(other)) { - (Ok(left), Ok(right)) => left == right, - _ => self == other, - } - } -} - -macro_rules! ast_eq_field { - (#[ast_eq_use($convert_func:ident)] $field:ident) => { - $convert_func($field) - }; - ($field:ident) => { - $field - }; -} - -macro_rules! gen_ast_eq_enum { - ($self:expr, $other:expr, $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - match $self { - $($name::$unit_variant => gen_ast_eq_enum!($other, $name, $unit_variant),)* - $($name::$tuple_variant($($tuple_field),*) => - gen_ast_eq_enum!($other, $name, $tuple_variant ($($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),*)),)* - $($name::$struct_variant { $($struct_field),* } => - gen_ast_eq_enum!($other, $name, $struct_variant {$($(#[ast_eq_use($struct_convert_func)])? $struct_field),*}),)* - } - }; - ($other:expr, $name:ident, $unit_variant:ident) => { - { - matches!($other, $name::$unit_variant) - } - }; - ($other:expr, $name:ident, $tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? ) ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - if let $name::$tuple_variant($($tuple_field),*) = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($tuple_convert_func)])? $tuple_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; - ($other:expr, $name:ident, $struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? } ) => { - { - let left = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - if let $name::$struct_variant { $($struct_field),* } = $other { - let right = ($(ast_eq_field!($(#[ast_eq_use($struct_convert_func)])? $struct_field)),*); - left.ast_eq(&right) - } else { - false - } - } - }; -} - -macro_rules! wrap_in_box { - ($stmt:expr, $loc:expr) => { - if !matches!(**$stmt, Statement::Block { .. }) { - Box::new(Statement::Block { - loc: $loc, - unchecked: false, - statements: vec![*$stmt.clone()], - }) - } else { - $stmt.clone() - } - }; -} - -impl AstEq for Statement { - fn ast_eq(&self, other: &Self) -> bool { - match self { - Self::If(loc, expr, stmt1, stmt2) => { - #[expect(clippy::borrowed_box)] - let wrap_if = |stmt1: &Box, stmt2: &Option>| { - ( - wrap_in_box!(stmt1, *loc), - stmt2.as_ref().map(|stmt2| { - if matches!(**stmt2, Self::If(..)) { - stmt2.clone() - } else { - wrap_in_box!(stmt2, *loc) - } - }), - ) - }; - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let left = (loc, expr, &stmt1, &stmt2); - if let Self::If(loc, expr, stmt1, stmt2) = other { - let (stmt1, stmt2) = wrap_if(stmt1, stmt2); - let right = (loc, expr, &stmt1, &stmt2); - left.ast_eq(&right) - } else { - false - } - } - Self::While(loc, expr, stmt1) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, expr, &stmt1); - if let Self::While(loc, expr, stmt1) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, expr, &stmt1); - left.ast_eq(&right) - } else { - false - } - } - Self::DoWhile(loc, stmt1, expr) => { - let stmt1 = wrap_in_box!(stmt1, *loc); - let left = (loc, &stmt1, expr); - if let Self::DoWhile(loc, stmt1, expr) = other { - let stmt1 = wrap_in_box!(stmt1, *loc); - let right = (loc, &stmt1, expr); - left.ast_eq(&right) - } else { - false - } - } - Self::For(loc, stmt1, expr, stmt2, stmt3) => { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let left = (loc, stmt1, expr, stmt2, &stmt3); - if let Self::For(loc, stmt1, expr, stmt2, stmt3) = other { - let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); - let right = (loc, stmt1, expr, stmt2, &stmt3); - left.ast_eq(&right) - } else { - false - } - } - Self::Try(loc, expr, returns, catch) => { - let left_returns = - returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); - let left = (loc, expr, left_returns, catch); - if let Self::Try(loc, expr, returns, catch) = other { - let right_returns = - returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); - let right = (loc, expr, right_returns, catch); - left.ast_eq(&right) - } else { - false - } - } - _ => gen_ast_eq_enum!(self, other, Statement { - _ - Args(loc, args), - Expression(loc, expr), - VariableDefinition(loc, decl, expr), - Continue(loc, ), - Break(loc, ), - Return(loc, expr), - Revert(loc, expr, expr2), - RevertNamedArgs(loc, expr, args), - Emit(loc, expr), - // provide overridden variants regardless - If(loc, expr, stmt1, stmt2), - While(loc, expr, stmt1), - DoWhile(loc, stmt1, expr), - For(loc, stmt1, expr, stmt2, stmt3), - Try(loc, expr, params, clause), - Error(loc) - _ - Block { - loc, - unchecked, - statements, - }, - Assembly { - loc, - dialect, - block, - flags, - }, - }), - } - } -} - -macro_rules! derive_ast_eq { - ($name:ident) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - self == other - } - } - }; - (($($index:tt $gen:tt),*)) => { - impl < $( $gen ),* > AstEq for ($($gen,)*) where $($gen: AstEq),* { - fn ast_eq(&self, other: &Self) -> bool { - $( - if !self.$index.ast_eq(&other.$index) { - return false - } - )* - true - } - } - }; - (struct $name:ident { $($field:ident),* $(,)? }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - let $name { $($field),* } = self; - let left = ($($field),*); - let $name { $($field),* } = other; - let right = ($($field),*); - left.ast_eq(&right) - } - } - }; - (enum $name:ident { - $($unit_variant:ident),* $(,)? - _ - $($tuple_variant:ident ( $($(#[ast_eq_use($tuple_convert_func:ident)])? $tuple_field:ident),* $(,)? )),* $(,)? - _ - $($struct_variant:ident { $($(#[ast_eq_use($struct_convert_func:ident)])? $struct_field:ident),* $(,)? }),* $(,)? - }) => { - impl AstEq for $name { - fn ast_eq(&self, other: &Self) -> bool { - gen_ast_eq_enum!(self, other, $name { - $($unit_variant),* - _ - $($tuple_variant ( $($(#[ast_eq_use($tuple_convert_func)])? $tuple_field),* )),* - _ - $($struct_variant { $($(#[ast_eq_use($struct_convert_func)])? $struct_field),* }),* - }) - } - } - } -} - -derive_ast_eq! { (0 A) } -derive_ast_eq! { (0 A, 1 B) } -derive_ast_eq! { (0 A, 1 B, 2 C) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F) } -derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G) } -derive_ast_eq! { bool } -derive_ast_eq! { u8 } -derive_ast_eq! { u16 } -derive_ast_eq! { I256 } -derive_ast_eq! { U256 } -derive_ast_eq! { struct Identifier { loc, name } } -derive_ast_eq! { struct HexLiteral { loc, hex } } -derive_ast_eq! { struct StringLiteral { loc, unicode, string } } -derive_ast_eq! { struct Parameter { loc, annotation, ty, storage, name } } -derive_ast_eq! { struct NamedArgument { loc, name, expr } } -derive_ast_eq! { struct YulBlock { loc, statements } } -derive_ast_eq! { struct YulFunctionCall { loc, id, arguments } } -derive_ast_eq! { struct YulFunctionDefinition { loc, id, params, returns, body } } -derive_ast_eq! { struct YulSwitch { loc, condition, cases, default } } -derive_ast_eq! { struct YulFor { - loc, - init_block, - condition, - post_block, - execution_block, -}} -derive_ast_eq! { struct YulTypedIdentifier { loc, id, ty } } -derive_ast_eq! { struct VariableDeclaration { loc, ty, storage, name } } -derive_ast_eq! { struct Using { loc, list, ty, global } } -derive_ast_eq! { struct UsingFunction { loc, path, oper } } -derive_ast_eq! { struct TypeDefinition { loc, name, ty } } -derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, layout, parts } } -derive_ast_eq! { struct EventParameter { loc, ty, indexed, name } } -derive_ast_eq! { struct ErrorParameter { loc, ty, name } } -derive_ast_eq! { struct EventDefinition { loc, name, fields, anonymous } } -derive_ast_eq! { struct ErrorDefinition { loc, keyword, name, fields } } -derive_ast_eq! { struct StructDefinition { loc, name, fields } } -derive_ast_eq! { struct EnumDefinition { loc, name, values } } -derive_ast_eq! { struct Annotation { loc, id, value } } -derive_ast_eq! { enum PragmaDirective { - _ - Identifier(loc, id1, id2), - StringLiteral(loc, id, lit), - Version(loc, id, version), - _ -}} -derive_ast_eq! { enum UsingList { - Error, - _ - Library(expr), - Functions(exprs), - _ -}} -derive_ast_eq! { enum UserDefinedOperator { - BitwiseAnd, - BitwiseNot, - Negate, - BitwiseOr, - BitwiseXor, - Add, - Divide, - Modulo, - Multiply, - Subtract, - Equal, - More, - MoreEqual, - Less, - LessEqual, - NotEqual, - _ - _ -}} -derive_ast_eq! { enum Visibility { - _ - External(loc), - Public(loc), - Internal(loc), - Private(loc), - _ -}} -derive_ast_eq! { enum Mutability { - _ - Pure(loc), - View(loc), - Constant(loc), - Payable(loc), - _ -}} -derive_ast_eq! { enum FunctionAttribute { - _ - Mutability(muta), - Visibility(visi), - Virtual(loc), - Immutable(loc), - Override(loc, idents), - BaseOrModifier(loc, base), - Error(loc), - _ -}} -derive_ast_eq! { enum StorageLocation { - _ - Memory(loc), - Storage(loc), - Calldata(loc), - Transient(loc), - _ -}} -derive_ast_eq! { enum Type { - Address, - AddressPayable, - Payable, - Bool, - Rational, - DynamicBytes, - String, - _ - Int(int), - Uint(int), - Bytes(int), - _ - Mapping{ loc, key, key_name, value, value_name }, - Function { params, attributes, returns }, -}} -derive_ast_eq! { enum Expression { - _ - PostIncrement(loc, expr1), - PostDecrement(loc, expr1), - New(loc, expr1), - ArraySubscript(loc, expr1, expr2), - ArraySlice( - loc, - expr1, - expr2, - expr3, - ), - MemberAccess(loc, expr1, ident1), - FunctionCall(loc, expr1, exprs1), - FunctionCallBlock(loc, expr1, stmt), - NamedFunctionCall(loc, expr1, args), - Not(loc, expr1), - BitwiseNot(loc, expr1), - Delete(loc, expr1), - PreIncrement(loc, expr1), - PreDecrement(loc, expr1), - UnaryPlus(loc, expr1), - Negate(loc, expr1), - Power(loc, expr1, expr2), - Multiply(loc, expr1, expr2), - Divide(loc, expr1, expr2), - Modulo(loc, expr1, expr2), - Add(loc, expr1, expr2), - Subtract(loc, expr1, expr2), - ShiftLeft(loc, expr1, expr2), - ShiftRight(loc, expr1, expr2), - BitwiseAnd(loc, expr1, expr2), - BitwiseXor(loc, expr1, expr2), - BitwiseOr(loc, expr1, expr2), - Less(loc, expr1, expr2), - More(loc, expr1, expr2), - LessEqual(loc, expr1, expr2), - MoreEqual(loc, expr1, expr2), - Equal(loc, expr1, expr2), - NotEqual(loc, expr1, expr2), - And(loc, expr1, expr2), - Or(loc, expr1, expr2), - ConditionalOperator(loc, expr1, expr2, expr3), - Assign(loc, expr1, expr2), - AssignOr(loc, expr1, expr2), - AssignAnd(loc, expr1, expr2), - AssignXor(loc, expr1, expr2), - AssignShiftLeft(loc, expr1, expr2), - AssignShiftRight(loc, expr1, expr2), - AssignAdd(loc, expr1, expr2), - AssignSubtract(loc, expr1, expr2), - AssignMultiply(loc, expr1, expr2), - AssignDivide(loc, expr1, expr2), - AssignModulo(loc, expr1, expr2), - BoolLiteral(loc, bool1), - NumberLiteral(loc, #[ast_eq_use(to_num)] str1, #[ast_eq_use(to_num)] str2, unit), - RationalNumberLiteral( - loc, - #[ast_eq_use(to_num)] str1, - #[ast_eq_use(to_num_reversed)] str2, - #[ast_eq_use(to_num)] str3, - unit - ), - HexNumberLiteral(loc, str1, unit), - StringLiteral(strs1), - Type(loc, ty1), - HexLiteral(hexs1), - AddressLiteral(loc, str1), - Variable(ident1), - List(loc, params1), - ArrayLiteral(loc, exprs1), - Parenthesis(loc, expr) - _ -}} -derive_ast_eq! { enum CatchClause { - _ - Simple(param, ident, stmt), - Named(loc, ident, param, stmt), - _ -}} -derive_ast_eq! { enum YulStatement { - _ - Assign(loc, exprs, expr), - VariableDeclaration(loc, idents, expr), - If(loc, expr, block), - For(yul_for), - Switch(switch), - Leave(loc), - Break(loc), - Continue(loc), - Block(block), - FunctionDefinition(def), - FunctionCall(func), - Error(loc), - _ -}} -derive_ast_eq! { enum YulExpression { - _ - BoolLiteral(loc, boo, ident), - NumberLiteral(loc, string1, string2, ident), - HexNumberLiteral(loc, string, ident), - HexStringLiteral(hex, ident), - StringLiteral(string, ident), - Variable(ident), - FunctionCall(func), - SuffixAccess(loc, expr, ident), - _ -}} -derive_ast_eq! { enum YulSwitchOptions { - _ - Case(loc, expr, block), - Default(loc, block), - _ -}} -derive_ast_eq! { enum SourceUnitPart { - _ - ContractDefinition(def), - PragmaDirective(pragma), - ImportDirective(import), - EnumDefinition(def), - StructDefinition(def), - EventDefinition(def), - ErrorDefinition(def), - FunctionDefinition(def), - VariableDefinition(def), - TypeDefinition(def), - Using(using), - StraySemicolon(loc), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ImportPath { - _ - Filename(lit), - Path(path), - _ -}} -derive_ast_eq! { enum Import { - _ - Plain(string, loc), - GlobalSymbol(string, ident, loc), - Rename(string, idents, loc), - _ -}} -derive_ast_eq! { enum FunctionTy { - Constructor, - Function, - Fallback, - Receive, - Modifier, - _ - _ -}} -derive_ast_eq! { enum ContractPart { - _ - StructDefinition(def), - EventDefinition(def), - EnumDefinition(def), - ErrorDefinition(def), - VariableDefinition(def), - FunctionDefinition(def), - TypeDefinition(def), - StraySemicolon(loc), - Using(using), - Annotation(annotation), - _ -}} -derive_ast_eq! { enum ContractTy { - _ - Abstract(loc), - Contract(loc), - Interface(loc), - Library(loc), - _ -}} -derive_ast_eq! { enum VariableAttribute { - _ - Visibility(visi), - Constant(loc), - Immutable(loc), - Override(loc, idents), - StorageType(st), - StorageLocation(st), - _ -}} - -// Who cares -impl AstEq for StorageType { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} - -impl AstEq for VersionComparator { - fn ast_eq(&self, _other: &Self) -> bool { - true - } -} diff --git a/crates/fmt/src/solang_ext/loc.rs b/crates/fmt/src/solang_ext/loc.rs deleted file mode 100644 index 6261e08c637ee..0000000000000 --- a/crates/fmt/src/solang_ext/loc.rs +++ /dev/null @@ -1,168 +0,0 @@ -use solang_parser::pt; -use std::{borrow::Cow, rc::Rc, sync::Arc}; - -/// Returns the code location. -/// -/// Patched version of [`pt::CodeLocation`]: includes the block of a [`pt::FunctionDefinition`] in -/// its `loc`. -pub trait CodeLocationExt { - /// Returns the code location of `self`. - fn loc(&self) -> pt::Loc; -} - -impl CodeLocationExt for &T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for &mut T { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Cow<'_, T> { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Box { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Rc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -impl CodeLocationExt for Arc { - fn loc(&self) -> pt::Loc { - (**self).loc() - } -} - -// FunctionDefinition patch -impl CodeLocationExt for pt::FunctionDefinition { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - let mut loc = self.loc; - if let Some(ref body) = self.body { - loc.use_end_from(&pt::CodeLocation::loc(body)); - } - loc - } -} - -impl CodeLocationExt for pt::ContractPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::SourceUnitPart { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - match self { - Self::FunctionDefinition(f) => f.loc(), - _ => pt::CodeLocation::loc(self), - } - } -} - -impl CodeLocationExt for pt::ImportPath { - fn loc(&self) -> pt::Loc { - match self { - Self::Filename(s) => s.loc(), - Self::Path(i) => i.loc(), - } - } -} - -impl CodeLocationExt for pt::VersionComparator { - fn loc(&self) -> pt::Loc { - match self { - Self::Plain { loc, .. } | - Self::Operator { loc, .. } | - Self::Or { loc, .. } | - Self::Range { loc, .. } => *loc, - } - } -} - -macro_rules! impl_delegate { - ($($t:ty),+ $(,)?) => {$( - impl CodeLocationExt for $t { - #[inline] - #[track_caller] - fn loc(&self) -> pt::Loc { - pt::CodeLocation::loc(self) - } - } - )+}; -} - -impl_delegate! { - pt::Annotation, - pt::Base, - pt::ContractDefinition, - pt::EnumDefinition, - pt::ErrorDefinition, - pt::ErrorParameter, - pt::EventDefinition, - pt::EventParameter, - pt::PragmaDirective, - // pt::FunctionDefinition, - pt::HexLiteral, - pt::Identifier, - pt::IdentifierPath, - pt::NamedArgument, - pt::Parameter, - // pt::SourceUnit, - pt::StringLiteral, - pt::StructDefinition, - pt::TypeDefinition, - pt::Using, - pt::UsingFunction, - pt::VariableDeclaration, - pt::VariableDefinition, - pt::YulBlock, - pt::YulFor, - pt::YulFunctionCall, - pt::YulFunctionDefinition, - pt::YulSwitch, - pt::YulTypedIdentifier, - - pt::CatchClause, - pt::Comment, - // pt::ContractPart, - pt::ContractTy, - pt::Expression, - pt::FunctionAttribute, - // pt::FunctionTy, - pt::Import, - pt::Loc, - pt::Mutability, - // pt::SourceUnitPart, - pt::Statement, - pt::StorageLocation, - // pt::Type, - // pt::UserDefinedOperator, - pt::UsingList, - pt::VariableAttribute, - // pt::Visibility, - pt::YulExpression, - pt::YulStatement, - pt::YulSwitchOptions, -} diff --git a/crates/fmt/src/solang_ext/mod.rs b/crates/fmt/src/solang_ext/mod.rs deleted file mode 100644 index 3aa7c526c5ba1..0000000000000 --- a/crates/fmt/src/solang_ext/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Extension traits and modules to the [`solang_parser`] crate. - -/// Same as [`solang_parser::pt`], but with the patched `CodeLocation`. -pub mod pt { - #[doc(no_inline)] - pub use super::loc::CodeLocationExt as CodeLocation; - - #[doc(no_inline)] - pub use solang_parser::pt::{ - Annotation, Base, CatchClause, Comment, ContractDefinition, ContractPart, ContractTy, - EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, - Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, - IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, - Parameter, ParameterList, PragmaDirective, SourceUnit, SourceUnitPart, Statement, - StorageLocation, StringLiteral, StructDefinition, Type, TypeDefinition, - UserDefinedOperator, Using, UsingFunction, UsingList, VariableAttribute, - VariableDeclaration, VariableDefinition, Visibility, YulBlock, YulExpression, YulFor, - YulFunctionCall, YulFunctionDefinition, YulStatement, YulSwitch, YulSwitchOptions, - YulTypedIdentifier, - }; -} - -mod ast_eq; -mod loc; -mod safe_unwrap; - -pub use ast_eq::AstEq; -pub use loc::CodeLocationExt; -pub use safe_unwrap::SafeUnwrap; diff --git a/crates/fmt/src/solang_ext/safe_unwrap.rs b/crates/fmt/src/solang_ext/safe_unwrap.rs deleted file mode 100644 index fe2810ad9705a..0000000000000 --- a/crates/fmt/src/solang_ext/safe_unwrap.rs +++ /dev/null @@ -1,52 +0,0 @@ -use solang_parser::pt; - -/// Trait implemented to unwrap optional parse tree items initially introduced in -/// [hyperledger/solang#1068]. -/// -/// Note that the methods of this trait should only be used on parse tree items' fields, like -/// [pt::VariableDefinition] or [pt::EventDefinition], where the `name` field is `None` only when an -/// error occurred during parsing. -/// -/// [hyperledger/solang#1068]: https://github.com/hyperledger/solang/pull/1068 -pub trait SafeUnwrap { - /// See [SafeUnwrap]. - fn safe_unwrap(&self) -> &T; - - /// See [SafeUnwrap]. - fn safe_unwrap_mut(&mut self) -> &mut T; -} - -#[inline(never)] -#[cold] -#[track_caller] -fn invalid() -> ! { - panic!("invalid parse tree") -} - -macro_rules! impl_ { - ($($t:ty),+ $(,)?) => { - $( - impl SafeUnwrap<$t> for Option<$t> { - #[inline] - #[track_caller] - fn safe_unwrap(&self) -> &$t { - match *self { - Some(ref x) => x, - None => invalid(), - } - } - - #[inline] - #[track_caller] - fn safe_unwrap_mut(&mut self) -> &mut $t { - match *self { - Some(ref mut x) => x, - None => invalid(), - } - } - } - )+ - }; -} - -impl_!(pt::Identifier, pt::StringLiteral); diff --git a/crates/fmt/src/state/common.rs b/crates/fmt/src/state/common.rs new file mode 100644 index 0000000000000..b2b2767ad1e9a --- /dev/null +++ b/crates/fmt/src/state/common.rs @@ -0,0 +1,960 @@ +use super::{CommentConfig, Separator, State}; +use crate::pp::{BreakToken, Printer, SIZE_INFINITY}; +use foundry_common::iter::IterDelimited; +use foundry_config::fmt as config; +use itertools::{Either, Itertools}; +use solar::parse::{ + Cursor, + ast::{self, Span}, + interface::BytePos, +}; +use std::{borrow::Cow, fmt::Debug}; + +pub(crate) trait LitExt<'ast> { + fn is_str_concatenation(&self) -> bool; +} + +impl<'ast> LitExt<'ast> for ast::Lit<'ast> { + /// Checks if a the input literal is a string literal with multiple parts. + fn is_str_concatenation(&self) -> bool { + if let ast::LitKind::Str(_, _, parts) = &self.kind { !parts.is_empty() } else { false } + } +} + +/// Language-specific pretty printing. Common for both: Solidity + Yul. +impl<'ast> State<'_, 'ast> { + pub(super) fn print_lit_inner(&mut self, lit: &'ast ast::Lit<'ast>, is_yul: bool) { + let ast::Lit { span, symbol, ref kind } = *lit; + if self.handle_span(span, false) { + return; + } + + match *kind { + ast::LitKind::Str(kind, ..) => { + self.s.ibox(0); + for (pos, (span, symbol)) in lit.literals().delimited() { + if !self.handle_span(span, false) { + let quote_pos = span.lo() + kind.prefix().len() as u32; + self.print_str_lit(kind, quote_pos, symbol.as_str()); + } + if pos.is_last { + self.neverbreak(); + } else { + if !self.print_trailing_comment(span.hi(), None) { + self.space_if_not_bol(); + } + } + } + self.end(); + } + ast::LitKind::Number(_) | ast::LitKind::Rational(_) => { + self.print_num_literal(symbol.as_str(), is_yul); + } + ast::LitKind::Address(value) => self.word(value.to_string()), + ast::LitKind::Bool(value) => self.word(if value { "true" } else { "false" }), + ast::LitKind::Err(_) => self.word(symbol.to_string()), + } + } + + fn print_num_literal(&mut self, source: &str, is_yul: bool) { + fn strip_underscores_if(b: bool, s: &str) -> Cow<'_, str> { + if b && s.contains('_') { Cow::Owned(s.replace('_', "")) } else { Cow::Borrowed(s) } + } + + fn add_underscores( + out: &mut String, + config: config::NumberUnderscore, + string: &str, + is_dec: bool, + is_yul: bool, + reversed: bool, + ) { + // The underscore thousand separator is only valid in Solidity decimal numbers. + // It is not supported by hex numbers, nor Yul literals. + // https://github.com/foundry-rs/foundry/issues/12111 + if !config.is_thousands() || !is_dec || is_yul || string.len() < 5 { + out.push_str(string); + return; + } + + let chunks = if reversed { + Either::Left(string.as_bytes().chunks(3)) + } else { + Either::Right(string.as_bytes().rchunks(3).rev()) + } + .map(|chunk| std::str::from_utf8(chunk).unwrap()); + for chunk in Itertools::intersperse(chunks, "_") { + out.push_str(chunk); + } + } + + debug_assert!(source.is_ascii(), "{source:?}"); + + let config = self.config.number_underscore; + let is_dec = !["0x", "0b", "0o"].iter().any(|prefix| source.starts_with(prefix)); + + let (val, exp) = if is_dec { + source.split_once(['e', 'E']).unwrap_or((source, "")) + } else { + (source, "") + }; + let (val, fract) = val.split_once('.').unwrap_or((val, "")); + + let strip_underscores = !config.is_preserve() || is_yul; + let mut val = &strip_underscores_if(strip_underscores, val)[..]; + let mut exp = &strip_underscores_if(strip_underscores, exp)[..]; + let mut fract = &strip_underscores_if(strip_underscores, fract)[..]; + + // strip any padded 0's + let mut exp_sign = ""; + if is_dec { + val = val.trim_start_matches('0'); + fract = fract.trim_end_matches('0'); + (exp_sign, exp) = + if let Some(exp) = exp.strip_prefix('-') { ("-", exp) } else { ("", exp) }; + exp = exp.trim_start_matches('0'); + } + + let mut out = String::with_capacity(source.len() * 2); + if val.is_empty() { + out.push('0'); + } else { + add_underscores(&mut out, config, val, is_dec, is_yul, false); + } + if source.contains('.') { + out.push('.'); + match (fract.is_empty(), exp.is_empty()) { + // `X.YeZ`: keep as is + (false, false) => out.push_str(fract), + // `X.Y` + (false, true) => add_underscores(&mut out, config, fract, is_dec, is_yul, true), + // `X.` -> `X.0` + (true, _) => out.push('0'), + }; + } + if !exp.is_empty() { + out.push('e'); + out.push_str(exp_sign); + add_underscores(&mut out, config, exp, is_dec, is_yul, false); + } + + self.word(out); + } + + /// `s` should be the *unescaped contents of the string literal*. + pub(super) fn print_str_lit(&mut self, kind: ast::StrKind, quote_pos: BytePos, s: &str) { + self.print_comments(quote_pos, CommentConfig::default()); + let s = self.str_lit_to_string(kind, quote_pos, s); + self.word(s); + } + + /// `s` should be the *unescaped contents of the string literal*. + fn str_lit_to_string(&self, kind: ast::StrKind, quote_pos: BytePos, s: &str) -> String { + let prefix = kind.prefix(); + let quote = match self.config.quote_style { + config::QuoteStyle::Double => '\"', + config::QuoteStyle::Single => '\'', + config::QuoteStyle::Preserve => self.char_at(quote_pos).unwrap_or_default(), + }; + debug_assert!(matches!(quote, '\"' | '\''), "{quote:?}"); + let s = solar::parse::interface::data_structures::fmt::from_fn(move |f| { + if matches!(kind, ast::StrKind::Hex) { + match self.config.hex_underscore { + config::HexUnderscore::Preserve => {} + config::HexUnderscore::Remove | config::HexUnderscore::Bytes => { + let mut clean = s.to_string().replace('_', ""); + if matches!(self.config.hex_underscore, config::HexUnderscore::Bytes) { + clean = + clean.chars().chunks(2).into_iter().map(|c| c.format("")).join("_"); + } + return f.write_str(&clean); + } + }; + } + f.write_str(s) + }); + let mut s = format!("{prefix}{quote}{s}{quote}"); + + // If the output is not a single token then revert to the original quote. + #[allow(unstable_name_collisions)] + if Cursor::new(&s).exactly_one().is_err() { + let other_quote = if quote == '\"' { '\'' } else { '\"' }; + { + let s = unsafe { s.as_bytes_mut() }; + s[prefix.len()] = other_quote as u8; + s[s.len() - 1] = other_quote as u8; + } + debug_assert!(Cursor::new(&s).exactly_one().map(|_| true).unwrap()); + } + + s + } + + pub(super) fn print_tuple_empty(&mut self, pos_lo: BytePos, pos_hi: BytePos) { + if self.handle_span(Span::new(pos_lo, pos_hi), true) { + return; + } + + self.print_inside_parens(|state| { + state.s.cbox(state.ind); + if let Some(cmnt) = + state.print_comments(pos_hi, CommentConfig::skip_ws().mixed_prev_space()) + { + if cmnt.is_mixed() { + state.s.offset(-state.ind); + } else { + state.break_offset_if_not_bol(0, -state.ind, false); + } + } + state.end(); + }); + } + + pub(super) fn print_tuple<'a, T, P, S>( + &mut self, + values: &'a [T], + pos_lo: BytePos, + pos_hi: BytePos, + mut print: P, + mut get_span: S, + format: ListFormat, + ) where + P: FnMut(&mut Self, &'a T), + S: FnMut(&T) -> Span, + { + if self.handle_span(Span::new(pos_lo, pos_hi), true) { + return; + } + + if values.is_empty() { + self.print_tuple_empty(pos_lo, pos_hi); + return; + } + + if !(values.len() == 1 && format.is_inline()) { + // Use commasep + self.print_inside_parens(|state| { + state.commasep(values, pos_lo, pos_hi, print, get_span, format) + }); + return; + } + + // Format single-item inline lists directly without boxes + self.print_inside_parens(|state| { + let span = get_span(&values[0]); + state.s.cbox(state.ind); + let skip_break = if state.peek_comment_before(span.hi()).is_some() { + state.hardbreak(); + false + } else { + true + }; + + state.print_comments(span.lo(), CommentConfig::skip_ws().mixed_prev_space()); + print(state, &values[0]); + + if !state.print_trailing_comment(span.hi(), None) && skip_break { + state.neverbreak(); + } else { + state.break_offset_if_not_bol(0, -state.ind, false); + } + state.end(); + }); + } + + pub(super) fn print_array<'a, T, P, S>( + &mut self, + values: &'a [T], + span: Span, + print: P, + get_span: S, + ) where + P: FnMut(&mut Self, &'a T), + S: FnMut(&T) -> Span, + { + if self.handle_span(span, false) { + return; + } + + self.print_word("["); + self.commasep(values, span.lo(), span.hi(), print, get_span, ListFormat::compact()); + self.print_word("]"); + } + + pub(super) fn commasep_opening_logic( + &mut self, + values: &[T], + mut get_span: S, + format: ListFormat, + manual_opening: bool, + ) -> bool + where + S: FnMut(&T) -> Span, + { + let Some(span) = values.first().map(&mut get_span) else { + return false; + }; + + // If first item is uninformed (just a comma), and it has its own comment, skip it. + // It will be dealt with when printing the item in the main loop of `commasep`. + if span.is_dummy() + && let Some(next_pos) = values.get(1).map(|v| get_span(v).lo()) + && self.peek_comment_before(next_pos).is_some() + { + return true; + } + + // Check for comments before the first item. + if let Some((cmnt_span, cmnt_style)) = + self.peek_comment_before(span.lo()).map(|c| (c.span, c.style)) + { + let cmnt_disabled = self.inline_config.is_disabled(cmnt_span); + // Handle special formatting for disabled code with isolated comments. + if self.cursor.enabled && cmnt_disabled && cmnt_style.is_isolated() { + self.print_sep(Separator::Hardbreak); + if !format.with_delimiters { + self.s.offset(self.ind); + } + }; + + // If manual opening flag is passed, we simply force the break, and skip the comment. + // It will be dealt with when printing the item in the main loop of `commasep`. + if manual_opening { + self.hardbreak(); + self.s.offset(self.ind); + return true; + } + + let cmnt_config = if format.with_delimiters { + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space() + } else { + CommentConfig::skip_ws().no_breaks().mixed_prev_space().offset(self.ind) + }; + // Apply spacing based on comment styles. + if let Some(last_style) = self.print_comments(span.lo(), cmnt_config) { + match (cmnt_style.is_mixed(), last_style.is_mixed()) { + (true, true) => { + if format.breaks_cmnts { + self.hardbreak(); + } else { + self.space(); + } + if !format.with_delimiters && !cmnt_disabled { + self.s.offset(self.ind); + } + } + (false, true) => { + self.nbsp(); + } + (false, false) if !format.with_delimiters && !cmnt_disabled => { + self.hardbreak(); + self.s.offset(self.ind); + } + _ => {} + } + } + if self.cursor.enabled { + self.cursor.advance_to(span.lo(), true); + } + return true; + } + + if self.cursor.enabled { + self.cursor.advance_to(span.lo(), true); + } + + if !values.is_empty() && !format.with_delimiters { + format.print_break(true, values.len(), &mut self.s); + self.s.offset(self.ind); + return true; + } + + false + } + + pub(super) fn commasep<'a, T, P, S>( + &mut self, + values: &'a [T], + _pos_lo: BytePos, + pos_hi: BytePos, + mut print: P, + mut get_span: S, + format: ListFormat, + ) where + P: FnMut(&mut Self, &'a T), + S: FnMut(&T) -> Span, + { + if values.is_empty() { + return; + } + + // We can't simply check `peek_comment_before(pos_hi)` cause we would also account for + // comments in the child expression, and those don't matter. + let has_comments = + // check for comments before the first element + self.peek_comment_before(get_span(&values[0]).lo()).is_some() || + // check for comments between elements + values.windows(2).any(|w| self.peek_comment_between(get_span(&w[0]).hi(), get_span(&w[1]).lo()).is_some()) || + // check for comments after the last element + self.peek_comment_between(get_span(values.last().unwrap()).hi(), pos_hi).is_some(); + + // For calls with opts and args, which should break consistently, we need to skip the + // wrapping cbox to prioritize call args breaking before the call opts. Because of that, we + // must manually offset the breaks between args, so that they are properly indented. + let manual_opening = + format.is_consistent() && !format.with_delimiters && self.call_with_opts_and_args; + // When there are comments, we can preserve the cbox, as they will make it break + let manual_offset = !has_comments && manual_opening; + + let is_single_without_cmnts = values.len() == 1 && !format.break_single && !has_comments; + let skip_first_break = if format.with_delimiters || format.is_inline() { + self.s.cbox(if format.no_ind { 0 } else { self.ind }); + if is_single_without_cmnts { + true + } else { + self.commasep_opening_logic(values, &mut get_span, format, manual_opening) + } + } else { + let res = self.commasep_opening_logic(values, &mut get_span, format, manual_opening); + if !manual_offset { + self.s.cbox(if format.no_ind { 0 } else { self.ind }); + } + res + }; + + if let Some(sym) = format.prev_symbol() { + self.word_space(sym); + } else if is_single_without_cmnts && format.with_space { + self.nbsp(); + } else if !skip_first_break && !format.is_inline() { + format.print_break(true, values.len(), &mut self.s); + if manual_offset { + self.s.offset(self.ind); + } + } + + if format.is_compact() && !(format.breaks_with_comments() && has_comments) { + self.s.cbox(0); + } + + let mut last_delimiter_break = !format.with_delimiters; + let mut skip_last_break = + is_single_without_cmnts || !format.with_delimiters || format.is_inline(); + for (i, value) in values.iter().enumerate() { + let is_last = i == values.len() - 1; + if self + .print_comments(get_span(value).lo(), CommentConfig::skip_ws().mixed_prev_space()) + .is_some_and(|cmnt| cmnt.is_mixed()) + && format.breaks_cmnts + { + self.hardbreak(); // trailing and isolated comments already hardbreak + } + + // Avoid printing the last uninformed item, so that we can handle line breaks. + if !(is_last && get_span(value).is_dummy()) { + print(self, value); + } + + let next_span = if is_last { None } else { Some(get_span(&values[i + 1])) }; + let next_pos = next_span.map(Span::lo).unwrap_or(pos_hi); + let cmnt_before_next = + self.peek_comment_before(next_pos).map(|cmnt| (cmnt.span, cmnt.style)); + + if !is_last { + // Handle disabled lines with comments after the value, but before the comma. + if cmnt_before_next.is_some_and(|(cmnt_span, _)| { + let span = self.cursor.span(cmnt_span.lo()); + self.inline_config.is_disabled(span) + // NOTE: necessary workaround to patch this edgecase due to lack of spans for the commas. + && self.sm.span_to_snippet(span).is_ok_and(|snip| !snip.contains(',')) + }) { + self.print_comments( + next_pos, + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ); + } + self.print_word(","); + } + + if !is_last + && format.breaks_cmnts + && cmnt_before_next.is_some_and(|(cmnt_span, cmnt_style)| { + let disabled = self.inline_config.is_disabled(cmnt_span); + (cmnt_style.is_mixed() && !disabled) || (cmnt_style.is_isolated() && disabled) + }) + { + self.hardbreak(); // trailing and isolated comments already hardbreak + } + + // Print trailing comments. + let comment_config = if !is_last || format.with_delimiters { + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space() + } else { + CommentConfig::skip_ws().no_breaks().mixed_prev_space() + }; + let with_trailing = self.print_comments(next_pos, comment_config).is_some(); + + if is_last && with_trailing { + if self.is_bol_or_only_ind() { + // if a trailing comment is printed at the very end, we have to manually adjust + // the offset to avoid having a double break. + self.break_offset_if_not_bol(0, -self.ind, false); + } else { + self.s.break_offset(SIZE_INFINITY as usize, -self.ind); + } + skip_last_break = true; + last_delimiter_break = false; + } + + // Final break if needed before the next value. + if let Some(next_span) = next_span + && !self.is_bol_or_only_ind() + && !self.inline_config.is_disabled(next_span) + && !next_span.is_dummy() + { + format.print_break(false, values.len(), &mut self.s); + if manual_offset { + self.s.offset(self.ind); + } + } + } + + if format.is_compact() && !(format.breaks_with_comments() && has_comments) { + self.end(); + } + if !skip_last_break { + if let Some(sym) = format.post_symbol() { + format.print_break(false, values.len(), &mut self.s); + self.s.offset(-self.ind); + self.word(sym); + } else { + format.print_break(true, values.len(), &mut self.s); + self.s.offset(-self.ind); + } + } else if is_single_without_cmnts && format.with_space { + self.nbsp(); + } else if let Some(sym) = format.post_symbol() { + self.nbsp(); + self.word(sym); + } + + if !manual_offset { + self.end(); + } + self.cursor.advance_to(pos_hi, true); + + if last_delimiter_break { + format.print_break(true, values.len(), &mut self.s); + } + } + + pub(super) fn print_path(&mut self, path: &'ast ast::PathSlice, consistent_break: bool) { + if consistent_break { + self.s.cbox(self.ind); + } else { + self.s.ibox(self.ind); + } + for (pos, ident) in path.segments().iter().delimited() { + self.print_ident(ident); + if !pos.is_last { + if !self.emit_or_revert { + self.zerobreak(); + } + self.word("."); + } + } + self.end(); + } + + pub(super) fn print_block_inner( + &mut self, + block: &'ast [T], + block_format: BlockFormat, + mut print: impl FnMut(&mut Self, &'ast T), + mut get_block_span: impl FnMut(&'ast T) -> Span, + pos_hi: BytePos, + ) { + // Attempt to print in a single line. + if block_format.attempt_single_line() && block.len() == 1 { + self.print_single_line_block(block, block_format, print, get_block_span); + return; + } + + // Empty blocks with comments require special attention. + if block.is_empty() { + self.print_empty_block(block_format, pos_hi); + return; + } + + // update block depth + self.block_depth += 1; + + // Print multiline block comments. + let block_lo = get_block_span(&block[0]).lo(); + match block_format { + BlockFormat::NoBraces(None) => { + if !self.handle_span(self.cursor.span(block_lo), false) { + self.print_comments(block_lo, CommentConfig::default()); + } + self.s.cbox(0); + } + BlockFormat::NoBraces(Some(offset)) => { + let enabled = + !self.inline_config.is_disabled(Span::new(block_lo, block_lo + BytePos(1))) + && !self.handle_span(self.cursor.span(block_lo), true); + match self + .peek_comment() + .and_then(|cmnt| (cmnt.span.hi() < block_lo).then_some((cmnt.span, cmnt.style))) + { + Some((span, style)) => { + if enabled { + // Inline config is not disabled and span not handled + if !self.inline_config.is_disabled(span) || style.is_isolated() { + self.cursor.advance_to(span.lo(), true); + self.break_offset(SIZE_INFINITY as usize, offset); + } + if let Some(cmnt) = self.print_comments( + block_lo, + CommentConfig::skip_leading_ws(false).offset(offset), + ) && !cmnt.is_mixed() + && !cmnt.is_blank() + { + self.s.offset(offset); + } + } else if style.is_isolated() { + self.print_sep_unhandled(Separator::Hardbreak); + self.s.offset(offset); + } + } + None => { + if enabled { + self.zerobreak(); + self.s.offset(offset); + } else if self.cursor.enabled { + self.print_sep_unhandled(Separator::Space); + self.s.offset(offset); + self.cursor.advance_to(block_lo, true); + } + } + } + self.s.cbox(self.ind); + } + _ => { + self.print_word("{"); + self.s.cbox(self.ind); + if !self.handle_span(self.cursor.span(block_lo), false) + && self + .print_comments(block_lo, CommentConfig::default()) + .is_none_or(|cmnt| cmnt.is_mixed()) + { + self.hardbreak_if_nonempty(); + } + } + } + + // Print multiline block statements. + for (i, stmt) in block.iter().enumerate() { + let is_last = i == block.len() - 1; + print(self, stmt); + + let is_disabled = self.inline_config.is_disabled(get_block_span(stmt)); + let (next_enabled, next_lo) = if is_last { + (false, None) + } else { + let next_span = get_block_span(&block[i + 1]); + ( + !self.inline_config.is_disabled(next_span), + self.peek_comment_before(next_span.lo()).is_none().then_some(next_span.lo()), + ) + }; + + // when this stmt and the next one are enabled, break normally (except if last stmt) + if !is_disabled + && next_enabled + && (!is_last + || self.peek_comment_before(pos_hi).is_some_and(|cmnt| cmnt.style.is_mixed())) + { + self.hardbreak_if_not_bol(); + continue; + } + // when this stmt is disabled and the next one is enabled, break if there is no + // enabled preceding comment. Otherwise the breakpoint is handled by the comment. + if is_disabled + && next_enabled + && let Some(next_lo) = next_lo + && self + .peek_comment_before(next_lo) + .is_none_or(|cmnt| self.inline_config.is_disabled(cmnt.span)) + { + self.hardbreak_if_not_bol() + } + } + + self.print_comments( + pos_hi, + CommentConfig::skip_trailing_ws().mixed_no_break().mixed_prev_space(), + ); + if !block_format.breaks() { + if !self.last_token_is_break() { + self.hardbreak(); + } + self.s.offset(-self.ind); + } + self.end(); + if block_format.with_braces() { + self.print_word("}"); + } + + // restore block depth + self.block_depth -= 1; + } + + fn print_single_line_block( + &mut self, + block: &'ast [T], + block_format: BlockFormat, + mut print: impl FnMut(&mut Self, &'ast T), + mut get_block_span: impl FnMut(&'ast T) -> Span, + ) { + self.s.cbox(self.ind); + + match block_format { + BlockFormat::Compact(true) => { + self.scan_break(BreakToken { pre_break: Some("{"), ..Default::default() }); + print(self, &block[0]); + self.print_comments(get_block_span(&block[0]).hi(), CommentConfig::default()); + self.s.scan_break(BreakToken { post_break: Some("}"), ..Default::default() }); + self.s.offset(-self.ind); + } + _ => { + self.word("{"); + self.space(); + print(self, &block[0]); + self.print_comments(get_block_span(&block[0]).hi(), CommentConfig::default()); + self.space_if_not_bol(); + self.s.offset(-self.ind); + self.word("}"); + } + } + + self.end(); + } + + fn print_empty_block(&mut self, block_format: BlockFormat, pos_hi: BytePos) { + let has_braces = block_format.with_braces(); + + // Trailing comments are printed after the block + if self.peek_comment_before(pos_hi).is_none_or(|c| c.style.is_trailing()) { + if self.config.bracket_spacing { + if has_braces { + self.word("{ }"); + } else { + self.nbsp(); + } + } else if has_braces { + self.word("{}"); + } + self.print_comments(pos_hi, CommentConfig::skip_ws()); + return; + } + + // Non-trailing or mixed comments - print inside block + if has_braces { + self.word("{"); + } + let offset = if let BlockFormat::NoBraces(Some(off)) = block_format { off } else { 0 }; + self.print_comments( + pos_hi, + self.cmnt_config().offset(offset).mixed_no_break().mixed_prev_space().mixed_post_nbsp(), + ); + self.print_comments( + pos_hi, + CommentConfig::default().mixed_no_break().mixed_prev_space().mixed_post_nbsp(), + ); + if has_braces { + self.word("}"); + } + } +} + +/// Formatting style for comma-separated lists. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ListFormat { + /// The core formatting strategy. + kind: ListFormatKind, + /// If `true`, it means that the list already carries indentation. + no_ind: bool, + /// If `true`, a single-element list may break. + break_single: bool, + /// If `true`, a comment within the list forces a break. + breaks_cmnts: bool, + /// If `true`, a space is added after the opening delimiter and before the closing one. + with_space: bool, + /// If `true`, the list is enclosed in delimiters. + with_delimiters: bool, +} + +/// The kind of formatting style for a list. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ListFormatKind { + /// Always breaks for multiple elements. + AlwaysBreak, + /// Breaks all elements if any break. + Consistent, + /// Attempts to fit all elements in one line, before breaking consistently. + Compact, + /// The list is printed inline, without breaks. + Inline, + /// Special formatting for Yul return values. + Yul { sym_prev: Option<&'static str>, sym_post: Option<&'static str> }, +} + +impl Default for ListFormat { + fn default() -> Self { + Self { + kind: ListFormatKind::Consistent, + no_ind: false, + break_single: false, + breaks_cmnts: false, + with_space: false, + with_delimiters: true, + } + } +} + +impl ListFormat { + // -- GETTER METHODS ------------------------------------------------------- + pub(crate) const fn prev_symbol(&self) -> Option<&'static str> { + if let ListFormatKind::Yul { sym_prev, .. } = self.kind { sym_prev } else { None } + } + + pub(crate) const fn post_symbol(&self) -> Option<&'static str> { + if let ListFormatKind::Yul { sym_post, .. } = self.kind { sym_post } else { None } + } + + pub(crate) const fn is_consistent(&self) -> bool { + matches!(self.kind, ListFormatKind::Consistent) + } + + pub(crate) const fn is_compact(&self) -> bool { + matches!(self.kind, ListFormatKind::Compact) + } + + pub(crate) const fn is_inline(&self) -> bool { + matches!(self.kind, ListFormatKind::Inline) + } + + pub(crate) const fn breaks_with_comments(&self) -> bool { + self.breaks_cmnts + } + + // -- BUILDER METHODS ------------------------------------------------------ + pub(crate) fn inline() -> Self { + Self { kind: ListFormatKind::Inline, ..Default::default() } + } + + pub(crate) fn consistent() -> Self { + Self { kind: ListFormatKind::Consistent, ..Default::default() } + } + + pub(crate) fn compact() -> Self { + Self { kind: ListFormatKind::Compact, ..Default::default() } + } + + pub(crate) fn always_break() -> Self { + Self { + kind: ListFormatKind::AlwaysBreak, + breaks_cmnts: true, + break_single: true, + with_delimiters: true, + ..Default::default() + } + } + + pub(crate) fn yul(sym_prev: Option<&'static str>, sym_post: Option<&'static str>) -> Self { + Self { + kind: ListFormatKind::Yul { sym_prev, sym_post }, + breaks_cmnts: true, + with_delimiters: true, + ..Default::default() + } + } + + pub(crate) const fn without_ind(mut self, without: bool) -> Self { + if !matches!(self.kind, ListFormatKind::Inline) { + self.no_ind = without; + } + self + } + + pub(crate) const fn break_single(mut self, value: bool) -> Self { + if !matches!(self.kind, ListFormatKind::Inline) { + self.break_single = value; + } + self + } + + pub(crate) const fn break_cmnts(mut self) -> Self { + if !matches!(self.kind, ListFormatKind::Inline) { + self.breaks_cmnts = true; + } + self + } + + pub(crate) const fn with_space(mut self) -> Self { + if !matches!(self.kind, ListFormatKind::Inline) { + self.with_space = true; + } + self + } + + pub(crate) const fn with_delimiters(mut self, with: bool) -> Self { + if matches!(self.kind, ListFormatKind::Compact | ListFormatKind::Consistent) { + self.with_delimiters = with; + } + self + } + + // -- PRINTER METHODS ------------------------------------------------------ + pub(crate) fn print_break(&self, soft: bool, elems: usize, p: &mut Printer) { + match self.kind { + ListFormatKind::Inline => p.nbsp(), // CAREFUL: we can't use `pp.offset()` afterwards + ListFormatKind::AlwaysBreak if elems > 1 || (self.break_single && elems == 1) => { + p.hardbreak() + } + _ => { + if soft && !self.with_space { + p.zerobreak(); + } else { + p.space(); + } + } + } + } +} + +/// Formatting style for code blocks +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[expect(dead_code)] +pub(crate) enum BlockFormat { + Regular, + /// Attempts to fit all elements in one line, before breaking consistently. Flags whether to + /// use braces or not. + Compact(bool), + /// Doesn't print braces. Flags the offset that should be applied before opening the block box. + /// Useful when the caller needs to manually handle the braces. + NoBraces(Option), +} + +impl BlockFormat { + pub(crate) const fn with_braces(&self) -> bool { + !matches!(self, Self::NoBraces(_)) + } + pub(crate) const fn breaks(&self) -> bool { + matches!(self, Self::NoBraces(None)) + } + + pub(crate) const fn attempt_single_line(&self) -> bool { + matches!(self, Self::Compact(_)) + } +} diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs new file mode 100644 index 0000000000000..4b986017b71dd --- /dev/null +++ b/crates/fmt/src/state/mod.rs @@ -0,0 +1,1191 @@ +#![allow(clippy::too_many_arguments)] +use crate::{ + FormatterConfig, InlineConfig, + pp::{self, BreakToken, SIZE_INFINITY, Token}, + state::sol::BinOpGroup, +}; +use foundry_common::{ + comments::{Comment, CommentStyle, Comments, estimate_line_width, line_with_tabs}, + iter::IterDelimited, +}; +use foundry_config::fmt::{DocCommentStyle, IndentStyle}; +use solar::parse::{ + ast::{self, Span}, + interface::{BytePos, SourceMap}, + token, +}; +use std::{borrow::Cow, ops::Deref, sync::Arc}; + +mod common; +mod sol; +mod yul; + +/// Specifies the nature of a complex call. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum CallContextKind { + /// A chained method call, `a().b()`. + Chained, + + /// A nested function call, `a(b())`. + Nested, +} + +/// Formatting context for a call expression. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct CallContext { + /// The kind call. + pub(super) kind: CallContextKind, + + /// The size of the callee's head, excluding its arguments. + pub(super) size: usize, + + /// Whether this chain context added its own indentation box. + pub(super) has_indent: bool, +} + +impl CallContext { + pub(super) const fn nested(size: usize) -> Self { + Self { kind: CallContextKind::Nested, size, has_indent: false } + } + + pub(super) const fn chained(size: usize, has_indent: bool) -> Self { + Self { kind: CallContextKind::Chained, size, has_indent } + } + + pub(super) const fn is_nested(&self) -> bool { + matches!(self.kind, CallContextKind::Nested) + } + + pub(super) const fn is_chained(&self) -> bool { + matches!(self.kind, CallContextKind::Chained) + } +} + +#[derive(Debug, Default)] +pub(super) struct CallStack { + stack: Vec, +} + +impl Deref for CallStack { + type Target = [CallContext]; + fn deref(&self) -> &Self::Target { + &self.stack + } +} + +impl CallStack { + pub(crate) fn push(&mut self, call: CallContext) { + self.stack.push(call); + } + + pub(crate) fn pop(&mut self) -> Option { + self.stack.pop() + } + + pub(crate) fn is_nested(&self) -> bool { + self.last().is_some_and(|call| call.is_nested()) + } + + /// Returns true if any chained call in the stack has its own indentation. + /// Used to determine if commasep should skip its own indentation (to avoid double indent). + pub(crate) fn has_chain_with_indent(&self) -> bool { + self.stack.iter().any(|call| call.is_chained() && call.has_indent) + } +} + +pub(super) struct State<'sess, 'ast> { + // CORE COMPONENTS + pub(super) s: pp::Printer, + ind: isize, + + sm: &'sess SourceMap, + pub(super) comments: Comments, + config: Arc, + inline_config: InlineConfig<()>, + cursor: SourcePos, + + // FORMATTING CONTEXT: + // Whether the source file uses CRLF (`\r\n`) line endings. + has_crlf: bool, + // The current contract being formatted, if inside a contract definition. + contract: Option<&'ast ast::ItemContract<'ast>>, + // Current block nesting depth (incremented for each `{...}` block entered). + block_depth: usize, + // Stack tracking nested and chained function calls. + call_stack: CallStack, + + // Whether the current statement should be formatted as a single line, or not. + single_line_stmt: Option, + // The current binary expression chain context, if inside one. + binary_expr: Option, + // Whether inside a `return` statement that contains a binary expression, or not. + return_bin_expr: bool, + // Whether inside a call with call options and at least one argument. + call_with_opts_and_args: bool, + // Whether to skip the index soft breaks because the callee fits inline. + skip_index_break: bool, + // Whether inside an `emit` or `revert` call with a qualified path, or not. + emit_or_revert: bool, + // Whether inside a variable initialization expression, or not. + var_init: bool, +} + +impl std::ops::Deref for State<'_, '_> { + type Target = pp::Printer; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.s + } +} + +impl std::ops::DerefMut for State<'_, '_> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.s + } +} + +struct SourcePos { + pos: BytePos, + enabled: bool, +} + +impl SourcePos { + pub(super) fn advance(&mut self, bytes: u32) { + self.pos += BytePos(bytes); + } + + pub(super) fn advance_to(&mut self, pos: BytePos, enabled: bool) { + self.pos = std::cmp::max(pos, self.pos); + self.enabled = enabled; + } + + pub(super) fn next_line(&mut self, is_at_crlf: bool) { + self.pos += if is_at_crlf { 2 } else { 1 }; + } + + pub(super) fn span(&self, to: BytePos) -> Span { + Span::new(self.pos, to) + } +} + +pub(super) enum Separator { + Nbsp, + Space, + Hardbreak, + SpaceOrNbsp(bool), +} + +impl Separator { + fn print(&self, p: &mut pp::Printer, cursor: &mut SourcePos, is_at_crlf: bool) { + match self { + Self::Nbsp => p.nbsp(), + Self::Space => p.space(), + Self::Hardbreak => p.hardbreak(), + Self::SpaceOrNbsp(breaks) => p.space_or_nbsp(*breaks), + } + + cursor.next_line(is_at_crlf); + } +} + +/// Generic methods +impl<'sess> State<'sess, '_> { + pub(super) fn new( + sm: &'sess SourceMap, + config: Arc, + inline_config: InlineConfig<()>, + comments: Comments, + ) -> Self { + Self { + s: pp::Printer::new( + config.line_length, + matches!(config.style, IndentStyle::Tab).then(|| config.tab_width), + ), + ind: config.tab_width as isize, + sm, + comments, + config, + inline_config, + cursor: SourcePos { pos: BytePos::from_u32(0), enabled: true }, + has_crlf: false, + contract: None, + single_line_stmt: None, + call_with_opts_and_args: false, + skip_index_break: false, + binary_expr: None, + return_bin_expr: false, + emit_or_revert: false, + var_init: false, + block_depth: 0, + call_stack: CallStack::default(), + } + } + + /// Checks a span of the source for a carriage return (`\r`) to determine if the file + /// uses CRLF line endings. + /// + /// If a `\r` is found, `self.has_crlf` is set to `true`. This is intended to be + /// called once at the beginning of the formatting process for efficiency. + fn check_crlf(&mut self, span: Span) { + if let Ok(snip) = self.sm.span_to_snippet(span) + && snip.contains('\r') + { + self.has_crlf = true; + } + } + + /// Checks if the cursor is currently positioned at the start of a CRLF sequence (`\r\n`). + /// The check is only meaningful if `self.has_crlf` is true. + fn is_at_crlf(&self) -> bool { + self.has_crlf && self.char_at(self.cursor.pos) == Some('\r') + } + + /// Computes the space left, bounded by the max space left. + fn space_left(&self) -> usize { + std::cmp::min(self.s.space_left(), self.max_space_left(0)) + } + + /// Computes the maximum space left given the context information available: + /// `block_depth`, `tab_width`, and a user-defined unavailable size `prefix_len`. + fn max_space_left(&self, prefix_len: usize) -> usize { + self.config + .line_length + .saturating_sub(self.block_depth * self.config.tab_width + prefix_len) + } + + fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) { + // When searching, the break token is expected to be inside a closed box. Thus, we will + // traverse the buffer and evaluate the first non-end token. + if search { + // We do something pretty sketchy here: tuck the nonzero offset-adjustment we + // were going to deposit along with the break into the previous hardbreak. + self.find_and_replace_last_token_still_buffered( + pp::Printer::hardbreak_tok_offset(off), + |token| token.is_hardbreak(), + ); + return; + } + + // When not explicitly searching, the break token is expected to be the last token. + if !self.is_beginning_of_line() { + self.break_offset(n, off) + } else if off != 0 + && let Some(last_token) = self.last_token_still_buffered() + && last_token.is_hardbreak() + { + // We do something pretty sketchy here: tuck the nonzero offset-adjustment we + // were going to deposit along with the break into the previous hardbreak. + self.replace_last_token_still_buffered(pp::Printer::hardbreak_tok_offset(off)); + } + } + + fn braces_break(&mut self) { + if self.config.bracket_spacing { + self.space(); + } else { + self.zerobreak(); + } + } +} + +/// Span to source. +impl State<'_, '_> { + fn char_at(&self, pos: BytePos) -> Option { + let res = self.sm.lookup_byte_offset(pos); + res.sf.src.get(res.pos.to_usize()..)?.chars().next() + } + + fn print_span(&mut self, span: Span) { + match self.sm.span_to_snippet(span) { + Ok(s) => self.s.word(if matches!(self.config.style, IndentStyle::Tab) { + snippet_with_tabs(s, self.config.tab_width) + } else { + s + }), + Err(e) => panic!("failed to print {span:?}: {e:#?}"), + } + // Drop comments that are included in the span. + while let Some(cmnt) = self.peek_comment() { + if cmnt.pos() >= span.hi() { + break; + } + let _ = self.next_comment().unwrap(); + } + // Update cursor + self.cursor.advance_to(span.hi(), false); + } + + /// Returns `true` if the span is disabled and has been printed as-is. + #[must_use] + fn handle_span(&mut self, span: Span, skip_prev_cmnts: bool) -> bool { + if !skip_prev_cmnts { + self.print_comments(span.lo(), CommentConfig::default()); + } + self.print_span_if_disabled(span) + } + + /// Returns `true` if the span is disabled and has been printed as-is. + #[inline] + #[must_use] + fn print_span_if_disabled(&mut self, span: Span) -> bool { + let cursor_span = self.cursor.span(span.hi()); + if self.inline_config.is_disabled(cursor_span) { + self.print_span_cold(cursor_span); + return true; + } + if self.inline_config.is_disabled(span) { + self.print_span_cold(span); + return true; + } + false + } + + #[cold] + fn print_span_cold(&mut self, span: Span) { + self.print_span(span); + } + + fn print_tokens(&mut self, tokens: &[token::Token]) { + // Leave unchanged. + let span = Span::join_first_last(tokens.iter().map(|t| t.span)); + self.print_span(span); + } + + fn print_word(&mut self, w: impl Into>) { + let cow = w.into(); + self.cursor.advance(cow.len() as u32); + self.word(cow); + } + + fn print_sep(&mut self, sep: Separator) { + if self.handle_span( + self.cursor.span(self.cursor.pos + if self.is_at_crlf() { 2 } else { 1 }), + true, + ) { + return; + } + + self.print_sep_unhandled(sep); + } + + fn print_sep_unhandled(&mut self, sep: Separator) { + let is_at_crlf = self.is_at_crlf(); + sep.print(&mut self.s, &mut self.cursor, is_at_crlf); + } + + fn print_ident(&mut self, ident: &ast::Ident) { + if self.handle_span(ident.span, true) { + return; + } + + self.print_comments(ident.span.lo(), CommentConfig::skip_ws()); + self.word(ident.to_string()); + } + + fn print_inside_parens(&mut self, f: F) + where + F: FnOnce(&mut Self), + { + self.print_word("("); + f(self); + self.print_word(")"); + } + + fn estimate_size(&self, span: Span) -> usize { + if let Ok(snip) = self.sm.span_to_snippet(span) { + let (mut size, mut first, mut prev_needs_space) = (0, true, false); + + for line in snip.lines() { + let line = line.trim(); + + if prev_needs_space { + size += 1; + } else if !first && let Some(char) = line.chars().next() { + // A line break or a space are required if this line: + // - starts with an operator. + // - starts with one of the ternary operators + // - starts with a bracket and fmt config forces bracket spacing. + match char { + '&' | '|' | '=' | '>' | '<' | '+' | '-' | '*' | '/' | '%' | '^' | '?' + | ':' => size += 1, + '}' | ')' | ']' if self.config.bracket_spacing => size += 1, + _ => (), + } + } + first = false; + + // trim spaces before and after mixed comments + let mut search = line; + loop { + if let Some((lhs, comment)) = search.split_once(r#"/*"#) { + size += lhs.trim_end().len() + 2; + search = comment; + } else if let Some((comment, rhs)) = search.split_once(r#"*/"#) { + size += comment.len() + 2; + search = rhs; + } else { + size += search.trim().len(); + break; + } + } + + // Next line requires a line break if this one: + // - ends with a bracket and fmt config forces bracket spacing. + // - ends with ',' a line break or a space are required. + // - ends with ';' a line break is required. + prev_needs_space = match line.chars().next_back() { + Some('[' | '(' | '{') => self.config.bracket_spacing, + Some(',' | ';') => true, + _ => false, + }; + } + return size; + } + + span.to_range().len() + } + + fn same_source_line(&self, a: BytePos, b: BytePos) -> bool { + self.sm.lookup_char_pos(a).line == self.sm.lookup_char_pos(b).line + } +} + +/// Comment-related methods. +impl<'sess> State<'sess, '_> { + /// Returns `None` if the span is disabled and has been printed as-is. + #[must_use] + fn handle_comment(&mut self, cmnt: Comment, skip_break: bool) -> Option { + if self.cursor.enabled { + if self.inline_config.is_disabled(cmnt.span) { + if cmnt.style.is_trailing() && !self.last_token_is_space() { + self.nbsp(); + } + self.print_span_cold(cmnt.span); + if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) { + self.print_sep(Separator::Hardbreak); + } + return None; + } + } else if self.print_span_if_disabled(cmnt.span) { + if !skip_break && (cmnt.style.is_isolated() || cmnt.style.is_trailing()) { + self.print_sep(Separator::Hardbreak); + } + return None; + } + Some(cmnt) + } + + fn cmnt_config(&self) -> CommentConfig { + Default::default() + } + + const fn print_docs(&mut self, docs: &'_ ast::DocComments<'_>) { + // Intetionally no-op. Handled with `self.comments`. + let _ = docs; + } + + /// Prints comments that are before the given position. + /// + /// Returns `Some` with the style of the last comment printed, or `None` if no comment was + /// printed. + fn print_comments(&mut self, pos: BytePos, mut config: CommentConfig) -> Option { + let mut last_style: Option = None; + let mut is_leading = true; + let config_cache = config; + let mut buffered_blank = None; + while self.peek_comment().is_some_and(|c| c.pos() < pos) { + let mut cmnt = self.next_comment().unwrap(); + let style_cache = cmnt.style; + + // Merge consecutive line doc comments when converting to block style + if self.config.docs_style == foundry_config::fmt::DocCommentStyle::Block + && cmnt.is_doc + && cmnt.kind == ast::CommentKind::Line + { + let mut ref_line = self.sm.lookup_char_pos(cmnt.span.hi()).line; + while let Some(next_cmnt) = self.peek_comment() { + if !next_cmnt.is_doc + || next_cmnt.kind != ast::CommentKind::Line + || ref_line + 1 != self.sm.lookup_char_pos(next_cmnt.span.lo()).line + { + break; + } + + let next_to_merge = self.next_comment().unwrap(); + cmnt.lines.extend(next_to_merge.lines); + cmnt.span = cmnt.span.to(next_to_merge.span); + ref_line += 1; + } + } + + // Ensure breaks are never skipped when there are multiple comments + if self.peek_comment_before(pos).is_some() { + config.iso_no_break = false; + config.trailing_no_break = false; + } + + // Handle disabled comments + let Some(cmnt) = self.handle_comment( + cmnt, + if style_cache.is_isolated() { + config.iso_no_break + } else { + config.trailing_no_break + }, + ) else { + last_style = Some(style_cache); + continue; + }; + + if cmnt.style.is_blank() { + match config.skip_blanks { + Some(Skip::All) => continue, + Some(Skip::Leading { resettable: true }) if is_leading => continue, + Some(Skip::Leading { resettable: false }) if last_style.is_none() => continue, + Some(Skip::Trailing) => { + buffered_blank = Some(cmnt); + continue; + } + _ => (), + } + // Never print blank lines after docs comments + } else if !cmnt.is_doc { + is_leading = false; + } + + if let Some(blank) = buffered_blank.take() { + self.print_comment(blank, config); + } + + // Handle mixed with follow-up comment + if cmnt.style.is_mixed() { + if let Some(cmnt) = self.peek_comment_before(pos) { + config.mixed_no_break_prev = true; + config.mixed_no_break_post = true; + config.mixed_post_nbsp = cmnt.style.is_mixed(); + } + + // Ensure consecutive mixed comments don't have a double-space + if last_style.is_some_and(|s| s.is_mixed()) { + config.mixed_no_break_prev = true; + config.mixed_no_break_post = true; + config.mixed_prev_space = false; + } + } else if config.offset != 0 + && cmnt.style.is_isolated() + && last_style.is_some_and(|s| s.is_isolated()) + { + self.offset(config.offset); + } + + last_style = Some(cmnt.style); + self.print_comment(cmnt, config); + config = config_cache; + } + last_style + } + + /// Prints a line, wrapping it if it starts with the given prefix. + fn print_wrapped_line( + &mut self, + line: &str, + prefix: &'static str, + break_offset: isize, + is_doc: bool, + ) { + if !line.starts_with(prefix) { + self.word(line.to_owned()); + return; + } + + fn post_break_prefix(prefix: &'static str, has_content: bool) -> &'static str { + if !has_content { + return prefix; + } + match prefix { + "///" => "/// ", + "//" => "// ", + "/*" => "/* ", + " *" => " * ", + _ => prefix, + } + } + + self.ibox(0); + self.word(prefix); + + let content = &line[prefix.len()..]; + let content = if is_doc { + // Doc comments preserve leading whitespaces (right after the prefix) as nbps. + let ws_len = content + .char_indices() + .take_while(|(_, c)| c.is_whitespace()) + .last() + .map_or(0, |(idx, c)| idx + c.len_utf8()); + let (leading_ws, rest) = content.split_at(ws_len); + if !leading_ws.is_empty() { + self.word(leading_ws.to_owned()); + } + rest + } else { + // Non-doc comments: replace first whitespace with nbsp, rest of content continues + if let Some(first_char) = content.chars().next() { + if first_char.is_whitespace() { + self.nbsp(); + &content[first_char.len_utf8()..] + } else { + content + } + } else { + "" + } + }; + + let post_break = post_break_prefix(prefix, !content.is_empty()); + + // Process content character by character to preserve consecutive whitespaces + let (mut chars, mut current_word) = (content.chars().peekable(), String::new()); + while let Some(ch) = chars.next() { + if ch.is_whitespace() { + // Print current word + if !current_word.is_empty() { + self.word(std::mem::take(&mut current_word)); + } + + // Preserve multiple spaces while adding a single break + let mut ws_count = 1; + while chars.peek().is_some_and(|c| c.is_whitespace()) { + ws_count += 1; + chars.next(); + } + self.s.scan_break(BreakToken { + offset: break_offset, + blank_space: ws_count, + post_break: if post_break.starts_with("/*") { None } else { Some(post_break) }, + ..Default::default() + }); + continue; + } + + current_word.push(ch); + } + + // Print final word + if !current_word.is_empty() { + self.word(current_word); + } + + self.end(); + } + + /// Merges consecutive line comments to avoid orphan words. + fn merge_comment_lines(&self, lines: &[String], prefix: &str) -> Vec { + // Do not apply smart merging to block comments + if lines.is_empty() || lines.len() < 2 || !prefix.starts_with("//") { + return lines.to_vec(); + } + + let mut result = Vec::new(); + let mut i = 0; + + while i < lines.len() { + let current_line = &lines[i]; + + // Keep empty lines, and non-prefixed lines, untouched + if current_line.trim().is_empty() || !current_line.starts_with(prefix) { + result.push(current_line.clone()); + i += 1; + continue; + } + + if i + 1 < lines.len() { + let next_line = &lines[i + 1]; + + // Check if next line is has the same prefix and is not empty + if next_line.starts_with(prefix) && !next_line.trim().is_empty() { + // Only merge if the current line doesn't fit within available width + if estimate_line_width(current_line, self.config.tab_width) > self.space_left() + { + // Merge the lines and let the wrapper handle breaking if needed + let merged_line = format!( + "{current_line} {next_content}", + next_content = next_line[prefix.len()..].trim_start() + ); + result.push(merged_line); + + // Skip both lines since they are merged + i += 2; + continue; + } + } + } + + // No merge possible, keep the line as-is + result.push(current_line.clone()); + i += 1; + } + + result + } + + fn print_comment(&mut self, mut cmnt: Comment, mut config: CommentConfig) { + self.cursor.advance_to(cmnt.span.hi(), true); + + if cmnt.is_doc { + cmnt = style_doc_comment(self.config.docs_style, cmnt); + } + + match cmnt.style { + CommentStyle::Mixed => { + let Some(prefix) = cmnt.prefix() else { return }; + let never_break = self.last_token_is_neverbreak(); + if !self.is_bol_or_only_ind() { + match (never_break || config.mixed_no_break_prev, config.mixed_prev_space) { + (false, true) => config.space(&mut self.s), + (false, false) => config.zerobreak(&mut self.s), + (true, true) => self.nbsp(), + (true, false) => (), + }; + } + if self.config.wrap_comments { + // Merge and wrap comments + let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix); + for (pos, line) in merged_lines.into_iter().delimited() { + self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc); + if !pos.is_last { + self.hardbreak(); + } + } + } else { + // No wrapping, print as-is + for (pos, line) in cmnt.lines.into_iter().delimited() { + self.word(line); + if !pos.is_last { + self.hardbreak(); + } + } + } + if config.mixed_post_nbsp { + config.nbsp_or_space(self.config.wrap_comments, &mut self.s); + self.cursor.advance(1); + } else if !config.mixed_no_break_post { + config.space(&mut self.s); + self.cursor.advance(1); + } + } + CommentStyle::Isolated => { + let Some(mut prefix) = cmnt.prefix() else { return }; + if !config.iso_no_break { + config.hardbreak_if_not_bol(self.is_bol_or_only_ind(), &mut self.s); + } + + if self.config.wrap_comments { + // Merge and wrap comments + let merged_lines = self.merge_comment_lines(&cmnt.lines, prefix); + for (pos, line) in merged_lines.into_iter().delimited() { + let hb = |this: &mut Self| { + this.hardbreak(); + if pos.is_last { + this.cursor.next_line(this.is_at_crlf()); + } + }; + if line.is_empty() { + hb(self); + continue; + } + if pos.is_first { + self.ibox(config.offset); + if cmnt.is_doc && matches!(prefix, "/**") { + self.word(prefix); + hb(self); + prefix = " * "; + continue; + } + } + + self.print_wrapped_line(&line, prefix, 0, cmnt.is_doc); + + if pos.is_last { + self.end(); + if !config.iso_no_break { + hb(self); + } + } else { + hb(self); + } + } + } else { + // No wrapping, print as-is + for (pos, line) in cmnt.lines.into_iter().delimited() { + let hb = |this: &mut Self| { + this.hardbreak(); + if pos.is_last { + this.cursor.next_line(this.is_at_crlf()); + } + }; + if line.is_empty() { + hb(self); + continue; + } + if pos.is_first { + self.ibox(config.offset); + if cmnt.is_doc && matches!(prefix, "/**") { + self.word(prefix); + hb(self); + prefix = " * "; + continue; + } + } + + self.word(line); + + if pos.is_last { + self.end(); + if !config.iso_no_break { + hb(self); + } + } else { + hb(self); + } + } + } + } + CommentStyle::Trailing => { + let Some(prefix) = cmnt.prefix() else { return }; + self.neverbreak(); + if !self.is_bol_or_only_ind() { + self.nbsp(); + } + + if !self.config.wrap_comments && cmnt.lines.len() == 1 { + self.word(cmnt.lines.pop().unwrap()); + } else if self.config.wrap_comments { + if cmnt.is_doc || matches!(cmnt.kind, ast::CommentKind::Line) { + config.offset = 0; + } else { + config.offset = self.ind; + } + for (lpos, line) in cmnt.lines.into_iter().delimited() { + if !line.is_empty() { + self.print_wrapped_line(&line, prefix, config.offset, cmnt.is_doc); + } + if !lpos.is_last { + config.hardbreak(&mut self.s); + } + } + } else { + self.visual_align(); + for (pos, line) in cmnt.lines.into_iter().delimited() { + if !line.is_empty() { + self.word(line); + if !pos.is_last { + self.hardbreak(); + } + } + } + self.end(); + } + + if !config.trailing_no_break { + self.print_sep(Separator::Hardbreak); + } + } + + CommentStyle::BlankLine => { + // Pre-requisite: ensure that blank links are printed at the beginning of new line. + if !self.last_token_is_break() && !self.is_bol_or_only_ind() { + config.hardbreak(&mut self.s); + self.cursor.next_line(self.is_at_crlf()); + } + + // We need to do at least one, possibly two hardbreaks. + let twice = match self.last_token() { + Some(Token::String(s)) => ";" == s, + Some(Token::Begin(_)) => true, + Some(Token::End) => true, + _ => false, + }; + if twice { + config.hardbreak(&mut self.s); + self.cursor.next_line(self.is_at_crlf()); + } + config.hardbreak(&mut self.s); + self.cursor.next_line(self.is_at_crlf()); + } + } + } + + fn peek_comment<'b>(&'b self) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments.peek() + } + + fn peek_comment_before<'b>(&'b self, pos: BytePos) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments.iter().take_while(|c| c.pos() < pos).find(|c| !c.style.is_blank()) + } + + fn has_comment_before_with(&self, pos: BytePos, f: F) -> bool + where + F: FnMut(&Comment) -> bool, + { + self.comments.iter().take_while(|c| c.pos() < pos).any(f) + } + + fn peek_comment_between<'b>(&'b self, pos_lo: BytePos, pos_hi: BytePos) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments + .iter() + .take_while(|c| pos_lo < c.pos() && c.pos() < pos_hi) + .find(|c| !c.style.is_blank()) + } + + fn has_comment_between(&self, start_pos: BytePos, end_pos: BytePos) -> bool { + self.comments.iter().filter(|c| c.pos() > start_pos && c.pos() < end_pos).any(|_| true) + } + + pub(crate) fn next_comment(&mut self) -> Option { + self.comments.next() + } + + fn peek_trailing_comment<'b>( + &'b self, + span_pos: BytePos, + next_pos: Option, + ) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments.peek_trailing(self.sm, span_pos, next_pos).map(|(cmnt, _)| cmnt) + } + + fn print_trailing_comment_inner( + &mut self, + span_pos: BytePos, + next_pos: Option, + config: Option, + ) -> bool { + let mut printed = 0; + if let Some((_, n)) = self.comments.peek_trailing(self.sm, span_pos, next_pos) { + let config = + config.unwrap_or(CommentConfig::skip_ws().mixed_no_break().mixed_prev_space()); + while printed <= n { + let cmnt = self.comments.next().unwrap(); + if let Some(cmnt) = self.handle_comment(cmnt, config.trailing_no_break) { + self.print_comment(cmnt, config); + }; + printed += 1; + } + } + printed != 0 + } + + fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option) -> bool { + self.print_trailing_comment_inner(span_pos, next_pos, None) + } + + fn print_trailing_comment_no_break(&mut self, span_pos: BytePos, next_pos: Option) { + self.print_trailing_comment_inner( + span_pos, + next_pos, + Some(CommentConfig::skip_ws().trailing_no_break().mixed_no_break().mixed_prev_space()), + ); + } + + fn print_remaining_comments(&mut self, skip_leading_ws: bool) { + // If there aren't any remaining comments, then we need to manually + // make sure there is a line break at the end. + if self.peek_comment().is_none() && !self.is_bol_or_only_ind() { + self.hardbreak(); + return; + } + + let mut is_leading = true; + while let Some(cmnt) = self.next_comment() { + if cmnt.style.is_blank() && skip_leading_ws && is_leading { + continue; + } + + is_leading = false; + if let Some(cmnt) = self.handle_comment(cmnt, false) { + self.print_comment(cmnt, CommentConfig::default()); + } else if self.peek_comment().is_none() && !self.is_bol_or_only_ind() { + self.hardbreak(); + } + } + } +} + +#[derive(Clone, Copy)] +enum Skip { + All, + Leading { resettable: bool }, + Trailing, +} + +#[derive(Default, Clone, Copy)] +pub(crate) struct CommentConfig { + // Config: all + skip_blanks: Option, + offset: isize, + + // Config: isolated comments + iso_no_break: bool, + // Config: trailing comments + trailing_no_break: bool, + // Config: mixed comments + mixed_prev_space: bool, + mixed_post_nbsp: bool, + mixed_no_break_prev: bool, + mixed_no_break_post: bool, +} + +impl CommentConfig { + pub(crate) fn skip_ws() -> Self { + Self { skip_blanks: Some(Skip::All), ..Default::default() } + } + + pub(crate) fn skip_leading_ws(resettable: bool) -> Self { + Self { skip_blanks: Some(Skip::Leading { resettable }), ..Default::default() } + } + + pub(crate) fn skip_trailing_ws() -> Self { + Self { skip_blanks: Some(Skip::Trailing), ..Default::default() } + } + + pub(crate) const fn offset(mut self, off: isize) -> Self { + self.offset = off; + self + } + + pub(crate) const fn no_breaks(mut self) -> Self { + self.iso_no_break = true; + self.trailing_no_break = true; + self.mixed_no_break_prev = true; + self.mixed_no_break_post = true; + self + } + + pub(crate) const fn trailing_no_break(mut self) -> Self { + self.trailing_no_break = true; + self + } + + pub(crate) const fn mixed_no_break(mut self) -> Self { + self.mixed_no_break_prev = true; + self.mixed_no_break_post = true; + self + } + + pub(crate) const fn mixed_no_break_post(mut self) -> Self { + self.mixed_no_break_post = true; + self + } + + pub(crate) const fn mixed_prev_space(mut self) -> Self { + self.mixed_prev_space = true; + self + } + + pub(crate) const fn mixed_post_nbsp(mut self) -> Self { + self.mixed_post_nbsp = true; + self + } + + pub(crate) fn hardbreak_if_not_bol(&self, is_bol: bool, p: &mut pp::Printer) { + if self.offset != 0 && !is_bol { + self.hardbreak(p); + } else { + p.hardbreak_if_not_bol(); + } + } + + pub(crate) fn hardbreak(&self, p: &mut pp::Printer) { + p.break_offset(SIZE_INFINITY as usize, self.offset); + } + + pub(crate) fn space(&self, p: &mut pp::Printer) { + p.break_offset(1, self.offset); + } + + pub(crate) fn nbsp_or_space(&self, breaks: bool, p: &mut pp::Printer) { + if breaks { + self.space(p); + } else { + p.nbsp(); + } + } + + pub(crate) fn zerobreak(&self, p: &mut pp::Printer) { + p.break_offset(0, self.offset); + } +} + +fn snippet_with_tabs(s: String, tab_width: usize) -> String { + // process leading breaks + let trimmed = s.trim_start_matches('\n'); + let num_breaks = s.len() - trimmed.len(); + let mut formatted = std::iter::repeat_n('\n', num_breaks).collect::(); + + // process lines + for (pos, line) in trimmed.lines().delimited() { + line_with_tabs(&mut formatted, line, tab_width, None); + if !pos.is_last { + formatted.push('\n'); + } + } + + formatted +} + +/// Formats a doc comment with the requested style. +/// +/// NOTE: assumes comments have already been normalized. +fn style_doc_comment(style: DocCommentStyle, mut cmnt: Comment) -> Comment { + match style { + DocCommentStyle::Line if cmnt.kind == ast::CommentKind::Block => { + let mut new_lines = Vec::new(); + for (pos, line) in cmnt.lines.iter().delimited() { + if pos.is_first || pos.is_last { + // Skip the opening '/**' and closing '*/' lines + continue; + } + + // Convert ' * {content}' to '/// {content}' + let trimmed = line.trim_start(); + if let Some(content) = trimmed.strip_prefix('*') { + new_lines.push(format!("///{content}")); + } else if !trimmed.is_empty() { + new_lines.push(format!("/// {trimmed}")); + } + } + + cmnt.lines = new_lines; + cmnt.kind = ast::CommentKind::Line; + cmnt + } + DocCommentStyle::Block if cmnt.kind == ast::CommentKind::Line => { + let mut new_lines = vec!["/**".to_string()]; + + for line in &cmnt.lines { + // Convert '/// {content}' to ' * {content}' + new_lines.push(format!(" *{content}", content = &line[3..])) + } + + new_lines.push(" */".to_string()); + cmnt.lines = new_lines; + cmnt.kind = ast::CommentKind::Block; + cmnt + } + // Otherwise, no conversion needed. + _ => cmnt, + } +} diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs new file mode 100644 index 0000000000000..dc53558a20f33 --- /dev/null +++ b/crates/fmt/src/state/sol.rs @@ -0,0 +1,3129 @@ +#![allow(clippy::too_many_arguments)] + +use super::{ + CommentConfig, Separator, State, + common::{BlockFormat, ListFormat}, +}; +use crate::{ + pp::SIZE_INFINITY, + state::{CallContext, common::LitExt}, +}; +use foundry_common::{comments::Comment, iter::IterDelimited}; +use foundry_config::fmt::{self as config, MultilineFuncHeaderStyle}; +use solar::{ + ast::BoxSlice, + interface::SpannedOption, + parse::{ + ast::{self, Span}, + interface::BytePos, + }, +}; +use std::{collections::HashMap, fmt::Debug}; + +#[rustfmt::skip] +macro_rules! get_span { + () => { |value| value.span }; + (()) => { |value| value.span() }; +} + +/// Language-specific pretty printing: Solidity. +impl<'ast> State<'_, 'ast> { + pub(crate) fn print_source_unit(&mut self, source_unit: &'ast ast::SourceUnit<'ast>) { + // Figure out if the cursor needs to check for CR (`\r`). + if let Some(item) = source_unit.items.first() { + self.check_crlf(item.span.to(source_unit.items.last().unwrap().span)); + } + + let mut items = source_unit.items.iter().peekable(); + let mut is_first = true; + while let Some(item) = items.next() { + // If imports shouldn't be sorted, or if the item is not an import, print it directly. + if !self.config.sort_imports || !matches!(item.kind, ast::ItemKind::Import(_)) { + self.print_item(item, is_first); + is_first = false; + if let Some(next_item) = items.peek() { + self.separate_items(next_item, false); + } + continue; + } + + // Otherwise, collect a group of consecutive imports and sort them before printing. + let mut import_group = vec![item]; + while let Some(next_item) = items.peek() { + // Groups end when the next item is not an import or when there is a blank line. + if !matches!(next_item.kind, ast::ItemKind::Import(_)) + || self.has_comment_between(item.span.hi(), next_item.span.lo()) + { + break; + } + import_group.push(items.next().unwrap()); + } + + import_group.sort_by_key(|item| { + if let ast::ItemKind::Import(import) = &item.kind { + import.path.value.as_str() + } else { + unreachable!("Expected an import item") + } + }); + + for (pos, group_item) in import_group.iter().delimited() { + self.print_item(group_item, is_first); + is_first = false; + + if !pos.is_last { + self.hardbreak_if_not_bol(); + } + } + if let Some(next_item) = items.peek() { + self.separate_items(next_item, false); + } + } + + self.print_remaining_comments(is_first); + } + + /// Prints a hardbreak if the item needs an isolated line break. + fn separate_items(&mut self, next_item: &'ast ast::Item<'ast>, advance: bool) { + if !item_needs_iso(&next_item.kind) { + return; + } + let span = next_item.span; + + let cmnts = self + .comments + .iter() + .filter_map(|c| (c.pos() < span.lo()).then_some(c.style)) + .collect::>(); + + if let Some(first) = cmnts.first() + && let Some(last) = cmnts.last() + { + if !(first.is_blank() || last.is_blank()) { + self.hardbreak(); + return; + } + if advance { + if self.peek_comment_before(span.lo()).is_some() { + self.print_comments(span.lo(), CommentConfig::default()); + } else if self.inline_config.is_disabled(span.shrink_to_lo()) { + self.hardbreak(); + self.cursor.advance_to(span.lo(), true); + } + } + } else { + self.hardbreak(); + } + } + + fn print_item(&mut self, item: &'ast ast::Item<'ast>, skip_ws: bool) { + let ast::Item { ref docs, span, ref kind } = *item; + self.print_docs(docs); + + if self.handle_span(item.span, skip_ws) { + if !self.print_trailing_comment(span.hi(), None) { + self.print_sep(Separator::Hardbreak); + } + return; + } + + if self + .print_comments( + span.lo(), + if skip_ws { + CommentConfig::skip_leading_ws(false) + } else { + CommentConfig::default() + }, + ) + .is_some_and(|cmnt| cmnt.is_mixed()) + { + self.zerobreak(); + } + + match kind { + ast::ItemKind::Pragma(pragma) => self.print_pragma(pragma), + ast::ItemKind::Import(import) => self.print_import(import), + ast::ItemKind::Using(using) => self.print_using(using), + ast::ItemKind::Contract(contract) => self.print_contract(contract, span), + ast::ItemKind::Function(func) => self.print_function(func), + ast::ItemKind::Variable(var) => self.print_var_def(var), + ast::ItemKind::Struct(strukt) => self.print_struct(strukt, span), + ast::ItemKind::Enum(enm) => self.print_enum(enm, span), + ast::ItemKind::Udvt(udvt) => self.print_udvt(udvt), + ast::ItemKind::Error(err) => self.print_error(err), + ast::ItemKind::Event(event) => self.print_event(event), + } + + self.cursor.advance_to(span.hi(), true); + self.print_comments(span.hi(), CommentConfig::default()); + self.print_trailing_comment(span.hi(), None); + self.hardbreak_if_not_bol(); + self.cursor.next_line(self.is_at_crlf()); + } + + fn print_pragma(&mut self, pragma: &'ast ast::PragmaDirective<'ast>) { + self.word("pragma "); + match &pragma.tokens { + ast::PragmaTokens::Version(ident, semver_req) => { + self.print_ident(ident); + self.nbsp(); + self.word(semver_req.to_string()); + } + ast::PragmaTokens::Custom(a, b) => { + self.print_ident_or_strlit(a); + if let Some(b) = b { + self.nbsp(); + self.print_ident_or_strlit(b); + } + } + ast::PragmaTokens::Verbatim(tokens) => { + self.print_tokens(tokens); + } + } + self.word(";"); + } + + fn print_commasep_aliases<'a, I>(&mut self, aliases: I) + where + I: Iterator)>, + 'ast: 'a, + { + for (pos, (ident, alias)) in aliases.delimited() { + self.print_ident(ident); + if let Some(alias) = alias { + self.word(" as "); + self.print_ident(alias); + } + if !pos.is_last { + self.word(","); + self.space(); + } + } + } + + fn print_import(&mut self, import: &'ast ast::ImportDirective<'ast>) { + let ast::ImportDirective { path, items } = import; + self.word("import "); + + use ast::ImportItems; + use config::NamespaceImportStyle as NIStyle; + + match (items, self.config.namespace_import_style) { + (ImportItems::Plain(None), _) => { + self.print_ast_str_lit(path); + } + + (ImportItems::Plain(Some(source_alias)), NIStyle::Preserve | NIStyle::PreferPlain) + | (ImportItems::Glob(source_alias), NIStyle::PreferPlain) => { + self.print_ast_str_lit(path); + self.word(" as "); + self.print_ident(source_alias); + } + + (ImportItems::Glob(source_alias), NIStyle::Preserve | NIStyle::PreferGlob) + | (ImportItems::Plain(Some(source_alias)), NIStyle::PreferGlob) => { + self.word("*"); + self.word(" as "); + self.print_ident(source_alias); + self.word(" from "); + self.print_ast_str_lit(path); + } + + (ImportItems::Aliases(aliases), _) => { + // Check if we should keep single imports on one line + let use_single_line = self.config.single_line_imports && aliases.len() == 1; + + if use_single_line { + self.word("{"); + if self.config.bracket_spacing { + self.nbsp(); + } + } else { + self.s.cbox(self.ind); + self.word("{"); + self.braces_break(); + } + + if self.config.sort_imports { + let mut sorted: Vec<_> = aliases.iter().collect(); + sorted.sort_by_key(|(ident, _alias)| ident.name.as_str()); + self.print_commasep_aliases(sorted.into_iter()); + } else { + self.print_commasep_aliases(aliases.iter()); + }; + + if use_single_line { + if self.config.bracket_spacing { + self.nbsp(); + } + self.word("}"); + } else { + self.braces_break(); + self.s.offset(-self.ind); + self.word("}"); + self.end(); + } + self.word(" from "); + self.print_ast_str_lit(path); + } + } + self.word(";"); + } + + fn print_using(&mut self, using: &'ast ast::UsingDirective<'ast>) { + let ast::UsingDirective { list, ty, global } = using; + self.word("using "); + match list { + ast::UsingList::Single(path) => self.print_path(path, true), + ast::UsingList::Multiple(items) => { + self.s.cbox(self.ind); + self.word("{"); + self.braces_break(); + for (pos, (path, op)) in items.iter().delimited() { + self.print_path(path, true); + if let Some(op) = op { + self.word(" as "); + self.word(op.to_str()); + } + if !pos.is_last { + self.word(","); + self.space(); + } + } + self.braces_break(); + self.s.offset(-self.ind); + self.word("}"); + self.end(); + } + } + self.word(" for "); + if let Some(ty) = ty { + self.print_ty(ty); + } else { + self.word("*"); + } + if *global { + self.word(" global"); + } + self.word(";"); + } + + fn print_contract(&mut self, c: &'ast ast::ItemContract<'ast>, span: Span) { + let ast::ItemContract { kind, name, layout, bases, body } = c; + self.contract = Some(c); + self.cursor.advance_to(span.lo(), true); + + self.s.cbox(self.ind); + self.ibox(0); + self.cbox(0); + self.word_nbsp(kind.to_str()); + self.print_ident(name); + self.nbsp(); + + if let Some(layout) = layout + && !self.handle_span(layout.span, false) + { + self.word("layout at "); + self.print_expr(layout.slot); + self.print_sep(Separator::Space); + } + + if let Some(first) = bases.first().map(|base| base.span()) + && let Some(last) = bases.last().map(|base| base.span()) + && self.inline_config.is_disabled(first.to(last)) + { + _ = self.handle_span(first.until(last), false); + } else if !bases.is_empty() { + self.word("is"); + self.space(); + let last = bases.len() - 1; + for (i, base) in bases.iter().enumerate() { + if !self.handle_span(base.span(), false) { + self.print_modifier_call(base, false); + if i != last { + self.word(","); + if self + .print_comments( + bases[i + 1].span().lo(), + CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(), + ) + .is_none() + { + self.space(); + } + } + } + } + if !self.print_trailing_comment(bases.last().unwrap().span().hi(), None) { + self.space(); + } + self.s.offset(-self.ind); + } + self.end(); + + self.print_word("{"); + self.end(); + if body.is_empty() { + if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() { + // Adjust the offset of the trailing break from comment printing + // so the closing brace is not indented + self.s.offset(-self.ind); + } else if self.config.bracket_spacing { + self.nbsp(); + }; + self.end(); + } else { + // update block depth + self.block_depth += 1; + + self.print_sep(Separator::Hardbreak); + if self.config.contract_new_lines { + self.hardbreak(); + } + let body_lo = body[0].span.lo(); + if self.peek_comment_before(body_lo).is_some() { + self.print_comments(body_lo, CommentConfig::skip_leading_ws(true)); + } + + let mut is_first = true; + let mut items = body.iter().peekable(); + while let Some(item) = items.next() { + self.print_item(item, is_first); + is_first = false; + if let Some(next_item) = items.peek() { + if self.inline_config.is_disabled(next_item.span) { + _ = self.handle_span(next_item.span, false); + } else { + self.separate_items(next_item, true); + } + } + } + + if let Some(cmnt) = self.print_comments(span.hi(), CommentConfig::skip_trailing_ws()) + && self.config.contract_new_lines + && !cmnt.is_blank() + { + self.print_sep(Separator::Hardbreak); + } + self.s.offset(-self.ind); + self.end(); + if self.config.contract_new_lines { + self.hardbreak_if_nonempty(); + } + + // restore block depth + self.block_depth -= 1; + } + self.print_word("}"); + + self.cursor.advance_to(span.hi(), true); + self.contract = None; + } + + fn print_struct(&mut self, strukt: &'ast ast::ItemStruct<'ast>, span: Span) { + let ast::ItemStruct { name, fields } = strukt; + let ind = if self.estimate_size(name.span) + 8 >= self.space_left() { self.ind } else { 0 }; + self.s.ibox(self.ind); + self.word("struct"); + self.space(); + self.print_ident(name); + self.word(" {"); + if !fields.is_empty() { + self.break_offset(SIZE_INFINITY as usize, ind); + } + self.s.ibox(0); + for var in fields.iter() { + self.print_var_def(var); + if !self.print_trailing_comment(var.span.hi(), None) { + self.hardbreak(); + } + } + self.print_comments(span.hi(), CommentConfig::skip_ws()); + if ind == 0 { + self.s.offset(-self.ind); + } + self.end(); + self.end(); + self.word("}"); + } + + fn print_enum(&mut self, enm: &'ast ast::ItemEnum<'ast>, span: Span) { + let ast::ItemEnum { name, variants } = enm; + self.s.cbox(self.ind); + self.word("enum "); + self.print_ident(name); + self.word(" {"); + self.hardbreak_if_nonempty(); + for (pos, ident) in variants.iter().delimited() { + self.print_comments(ident.span.lo(), CommentConfig::default()); + self.print_ident(ident); + if !pos.is_last { + self.word(","); + } + if !self.print_trailing_comment(ident.span.hi(), None) { + self.hardbreak(); + } + } + self.print_comments(span.hi(), CommentConfig::skip_ws()); + self.s.offset(-self.ind); + self.end(); + self.word("}"); + } + + fn print_udvt(&mut self, udvt: &'ast ast::ItemUdvt<'ast>) { + let ast::ItemUdvt { name, ty } = udvt; + self.word("type "); + self.print_ident(name); + self.word(" is "); + self.print_ty(ty); + self.word(";"); + } + + // NOTE(rusowsky): Functions are the only source unit item that handle inline (disabled) format + fn print_function(&mut self, func: &'ast ast::ItemFunction<'ast>) { + let ast::ItemFunction { kind, ref header, ref body, body_span } = *func; + let ast::FunctionHeader { + name, + ref parameters, + visibility, + state_mutability: sm, + virtual_, + ref override_, + ref returns, + .. + } = *header; + + self.s.cbox(self.ind); + + // Print fn name and params + _ = self.handle_span(self.cursor.span(header.span.lo()), false); + self.print_word(kind.to_str()); + if let Some(name) = name { + self.print_sep(Separator::Nbsp); + self.print_ident(&name); + self.cursor.advance_to(name.span.hi(), true); + } + self.s.cbox(-self.ind); + let header_style = self.config.multiline_func_header; + let params_format = match header_style { + MultilineFuncHeaderStyle::ParamsAlways => ListFormat::always_break(), + MultilineFuncHeaderStyle::All + if header.parameters.len() > 1 && !self.can_header_be_inlined(func) => + { + ListFormat::always_break() + } + MultilineFuncHeaderStyle::AllParams + if !header.parameters.is_empty() && !self.can_header_be_inlined(func) => + { + ListFormat::always_break() + } + _ => ListFormat::consistent().break_cmnts().break_single( + // ensure fn params are always breakable when there is a single `Contract.Struct` + parameters.len() == 1 + && matches!( + ¶meters[0].ty, + ast::Type { kind: ast::TypeKind::Custom(ty), .. } if ty.segments().len() > 1 + ), + ), + }; + self.print_parameter_list(parameters, parameters.span, params_format); + self.end(); + + // Map attributes to their corresponding comments + let (mut map, attributes, first_attrib_pos) = + AttributeCommentMapper::new(returns.as_ref(), body_span.lo()).build(self, header); + + let mut handle_pre_cmnts = |this: &mut Self, span: Span| -> bool { + if this.inline_config.is_disabled(span) + // Note: `map` is still captured from the outer scope, which is fine. + && let Some((pre_cmnts, ..)) = map.remove(&span.lo()) + { + for (pos, cmnt) in pre_cmnts.into_iter().delimited() { + if pos.is_first && cmnt.style.is_isolated() && !this.is_bol_or_only_ind() { + this.print_sep(Separator::Hardbreak); + } + if let Some(cmnt) = this.handle_comment(cmnt, false) { + this.print_comment(cmnt, CommentConfig::skip_ws().mixed_post_nbsp()); + } + if pos.is_last { + return true; + } + } + } + false + }; + + let skip_attribs = returns.as_ref().is_some_and(|ret| { + let attrib_span = Span::new(first_attrib_pos, ret.span.lo()); + handle_pre_cmnts(self, attrib_span); + self.handle_span(attrib_span, false) + }); + let skip_returns = { + let pos = if skip_attribs { self.cursor.pos } else { first_attrib_pos }; + let ret_span = Span::new(pos, body_span.lo()); + handle_pre_cmnts(self, ret_span); + self.handle_span(ret_span, false) + }; + + let attrib_box = self.config.multiline_func_header.params_first() + || (self.config.multiline_func_header.attrib_first() + && !self.can_header_params_be_inlined(func)); + if attrib_box { + self.s.cbox(0); + } + if !(skip_attribs || skip_returns) { + // Print fn attributes in correct order + if let Some(v) = visibility { + self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str())); + } + if let Some(sm) = sm + && !matches!(*sm, ast::StateMutability::NonPayable) + { + self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str())); + } + if let Some(v) = virtual_ { + self.print_fn_attribute(v, &mut map, &mut |s| s.word("virtual")); + } + if let Some(o) = override_ { + self.print_fn_attribute(o.span, &mut map, &mut |s| s.print_override(o)); + } + for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) { + if let AttributeKind::Modifier(modifier) = m.kind { + let is_base = self.is_modifier_a_base_contract(kind, modifier); + self.print_fn_attribute(m.span, &mut map, &mut |s| { + s.print_modifier_call(modifier, is_base) + }); + } + } + } + if !skip_returns + && let Some(ret) = returns + && !ret.is_empty() + { + if !self.handle_span(self.cursor.span(ret.span.lo()), false) { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.print_sep(Separator::Space); + } + self.cursor.advance_to(ret.span.lo(), true); + self.print_word("returns "); + } + self.print_parameter_list( + ret, + ret.span, + ListFormat::consistent(), // .with_cmnts_break(false), + ); + } + + // Print fn body + if let Some(body) = body { + if self.handle_span(self.cursor.span(body_span.lo()), false) { + // Print spacing if necessary. Updates cursor. + } else { + if let Some(cmnt) = self.peek_comment_before(body_span.lo()) { + if cmnt.style.is_mixed() { + // These shouldn't update the cursor, as we've already dealt with it above + self.space(); + self.s.offset(-self.ind); + self.print_comments(body_span.lo(), CommentConfig::skip_ws()); + } else { + self.zerobreak(); + self.s.offset(-self.ind); + self.print_comments(body_span.lo(), CommentConfig::skip_ws()); + self.s.offset(-self.ind); + } + } else { + // If there are no modifiers, overrides, nor returns never break + if header.modifiers.is_empty() + && header.override_.is_none() + && returns.as_ref().is_none_or(|r| r.is_empty()) + && (header.visibility().is_none() || body.is_empty()) + { + self.nbsp(); + } else { + self.space(); + self.s.offset(-self.ind); + } + } + self.cursor.advance_to(body_span.lo(), true); + } + self.print_word("{"); + self.end(); + if attrib_box { + self.end(); + } + + self.print_block_without_braces(body, body_span.hi(), Some(self.ind)); + if self.cursor.enabled || self.cursor.pos < body_span.hi() { + self.print_word("}"); + self.cursor.advance_to(body_span.hi(), true); + } + } else { + self.print_comments(body_span.lo(), CommentConfig::skip_ws().mixed_prev_space()); + self.end(); + if attrib_box { + self.end(); + } + self.neverbreak(); + self.print_word(";"); + } + + if let Some(cmnt) = self.peek_trailing_comment(body_span.hi(), None) { + if cmnt.is_doc { + // trailing doc comments after the fn body are isolated + // these shouldn't update the cursor, as this is our own formatting + self.hardbreak(); + self.hardbreak(); + } + self.print_trailing_comment(body_span.hi(), None); + } + } + + fn print_fn_attribute( + &mut self, + span: Span, + map: &mut AttributeCommentMap, + print_fn: &mut dyn FnMut(&mut Self), + ) { + match map.remove(&span.lo()) { + Some((pre_cmnts, inner_cmnts, post_cmnts)) => { + // Print preceding comments. + for cmnt in pre_cmnts { + let Some(cmnt) = self.handle_comment(cmnt, false) else { + continue; + }; + self.print_comment(cmnt, CommentConfig::default()); + } + // Push the inner comments back to the queue, so that they are printed in their + // intended place. + for cmnt in inner_cmnts.into_iter().rev() { + self.comments.push_front(cmnt); + } + let enabled = if self.handle_span(span, false) { + false + } else { + if !self.is_bol_or_only_ind() { + self.space(); + } + self.ibox(0); + print_fn(self); + self.cursor.advance_to(span.hi(), true); + true + }; + // Print subsequent comments. + for cmnt in post_cmnts { + let Some(cmnt) = self.handle_comment(cmnt, false) else { + continue; + }; + self.print_comment(cmnt, CommentConfig::default().mixed_prev_space()); + } + if enabled { + self.end(); + } + } + // Fallback for attributes not in the map (should never happen) + None => { + if !self.is_bol_or_only_ind() { + self.space(); + } + print_fn(self); + self.cursor.advance_to(span.hi(), true); + } + } + } + + fn is_modifier_a_base_contract( + &self, + kind: ast::FunctionKind, + modifier: &'ast ast::Modifier<'ast>, + ) -> bool { + // Add `()` in functions when the modifier is a base contract. + // HACK: heuristics: + // 1. exactly matches the name of a base contract as declared in the `contract is`; + // this does not account for inheritance; + let is_contract_base = self.contract.is_some_and(|contract| { + contract + .bases + .iter() + .any(|contract_base| contract_base.name.to_string() == modifier.name.to_string()) + }); + // 2. assume that title case names in constructors are bases. + // LEGACY: constructors used to also be `function NameOfContract...`; not checked. + let is_constructor = matches!(kind, ast::FunctionKind::Constructor); + // LEGACY: we are checking the beginning of the path, not the last segment. + is_contract_base + || (is_constructor + && modifier.name.first().name.as_str().starts_with(char::is_uppercase)) + } + + fn print_error(&mut self, err: &'ast ast::ItemError<'ast>) { + let ast::ItemError { name, parameters } = err; + self.word("error "); + self.print_ident(name); + self.print_parameter_list( + parameters, + parameters.span, + if self.config.prefer_compact.errors() { + ListFormat::compact() + } else { + ListFormat::consistent() + }, + ); + self.word(";"); + } + + fn print_event(&mut self, event: &'ast ast::ItemEvent<'ast>) { + let ast::ItemEvent { name, parameters, anonymous } = event; + self.word("event "); + self.print_ident(name); + self.print_parameter_list( + parameters, + parameters.span, + if self.config.prefer_compact.events() { + ListFormat::compact().break_cmnts() + } else { + ListFormat::consistent().break_cmnts() + }, + ); + if *anonymous { + self.word(" anonymous"); + } + self.word(";"); + } + + fn print_var_def(&mut self, var: &'ast ast::VariableDefinition<'ast>) { + self.print_var(var, true); + self.word(";"); + } + + /// Prints the RHS of an assignment or variable initializer. + fn print_assign_rhs( + &mut self, + rhs: &'ast ast::Expr<'ast>, + lhs_size: usize, + space_left: usize, + ty: Option<&ast::TypeKind<'ast>>, + cache: bool, + ) { + // Check if the total expression overflows but the RHS would fit alone on a new line. + // This helps keep the RHS together on a single line when possible. + let rhs_size = self.estimate_size(rhs.span); + let overflows = lhs_size + rhs_size >= space_left; + let fits_alone = rhs_size + self.config.tab_width < space_left; + let fits_alone_no_cmnts = + fits_alone && !self.has_comment_between(rhs.span.lo(), rhs.span.hi()); + let force_break = overflows && fits_alone_no_cmnts; + + if lhs_size <= space_left { + self.neverbreak(); + } + + // Handle comments before the RHS expression + if let Some(cmnt) = self.peek_comment_before(rhs.span.lo()) + && self.inline_config.is_disabled(cmnt.span) + { + self.print_sep(Separator::Nbsp); + } + if self + .print_comments( + rhs.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ) + .is_some_and(|cmnt| cmnt.is_trailing()) + { + self.break_offset_if_not_bol(SIZE_INFINITY as usize, self.ind, false); + } + + // Match on expression kind to determine formatting strategy + match &rhs.kind { + ast::ExprKind::Lit(lit, ..) if lit.is_str_concatenation() => { + // String concatenations stay on the same line with nbsp + self.print_sep(Separator::Nbsp); + self.neverbreak(); + self.s.ibox(self.ind); + self.print_expr(rhs); + self.end(); + } + ast::ExprKind::Lit(..) if ty.is_none() && !fits_alone => { + // Long string in assign expr goes on its own line + self.print_sep(Separator::Space); + self.s.offset(self.ind); + self.print_expr(rhs); + } + ast::ExprKind::Binary(lhs, op, _) => { + let print_inline = |this: &mut Self| { + this.print_sep(Separator::Nbsp); + this.neverbreak(); + this.print_expr(rhs); + }; + let print_with_break = |this: &mut Self, force_break: bool| { + if !this.is_bol_or_only_ind() { + if force_break { + this.print_sep(Separator::Hardbreak); + } else { + this.print_sep(Separator::Space); + } + } + this.s.offset(this.ind); + this.s.ibox(this.ind); + this.print_expr(rhs); + this.end(); + }; + + // Binary expressions: check if we need to break and indent + if force_break { + print_with_break(self, true); + } else if self.estimate_lhs_size(rhs, op) + lhs_size > space_left { + if has_complex_successor(&rhs.kind, true) + && get_callee_head_size(lhs) + lhs_size <= space_left + { + // Keep complex exprs (where callee fits) inline, as they will have breaks + if matches!(lhs.kind, ast::ExprKind::Call(..)) { + self.s.ibox(-self.ind); + print_inline(self); + self.end(); + } else { + print_inline(self); + } + } else { + print_with_break(self, false); + } + } + // Otherwise, if expr fits, ensure no breaks + else { + print_inline(self); + } + } + _ => { + // General case: handle calls, complex successors, and other expressions + let callee_doesnt_fit = if let ast::ExprKind::Call(call_expr, ..) = &rhs.kind { + let callee_size = get_callee_head_size(call_expr); + callee_size + lhs_size > space_left + && callee_size + self.config.tab_width < space_left + } else { + false + }; + + if (lhs_size + 1 >= space_left && !is_call_chain(&rhs.kind, false)) + || callee_doesnt_fit + { + self.s.ibox(self.ind); + } else { + self.s.ibox(0); + }; + + if has_complex_successor(&rhs.kind, true) + && !matches!(&rhs.kind, ast::ExprKind::Member(..)) + { + // delegate breakpoints to `self.commasep(..)` for complex successors + if !self.is_bol_or_only_ind() { + let needs_offset = !callee_doesnt_fit + && rhs_size + lhs_size + 1 >= space_left + && fits_alone_no_cmnts; + let separator = if callee_doesnt_fit || needs_offset { + Separator::Space + } else { + Separator::Nbsp + }; + self.print_sep(separator); + if needs_offset { + self.s.offset(self.ind); + } + } + } else { + if !self.is_bol_or_only_ind() { + self.print_sep_unhandled(Separator::Space); + } + // apply type-dependent indentation if type info is available + if let Some(ty) = ty + && matches!(ty, ast::TypeKind::Elementary(..) | ast::TypeKind::Mapping(..)) + { + self.s.offset(self.ind); + } + } + self.print_expr(rhs); + self.end(); + } + } + + self.var_init = cache; + } + + fn print_var(&mut self, var: &'ast ast::VariableDefinition<'ast>, is_var_def: bool) { + let ast::VariableDefinition { + span, + ty, + visibility, + mutability, + data_location, + override_, + indexed, + name, + initializer, + } = var; + + if self.handle_span(*span, false) { + return; + } + + // NOTE(rusowsky): this is hacky but necessary to properly estimate if we figure out if we + // have double breaks (which should have double indentation) or not. + // Alternatively, we could achieve the same behavior with a new box group that supports + // "continuation" which would only increase indentation if its parent box broke. + let init_space_left = self.space_left(); + let mut pre_init_size = self.estimate_size(ty.span); + + // Non-elementary types use commasep which has its own padding. + self.s.ibox(0); + if override_.is_some() { + self.s.cbox(self.ind); + } else { + self.s.ibox(self.ind); + } + self.print_ty(ty); + + self.print_attribute(visibility.map(|v| v.to_str()), is_var_def, &mut pre_init_size); + self.print_attribute(mutability.map(|m| m.to_str()), is_var_def, &mut pre_init_size); + self.print_attribute(data_location.map(|d| d.to_str()), is_var_def, &mut pre_init_size); + + if let Some(override_) = override_ { + if self + .print_comments(override_.span.lo(), CommentConfig::skip_ws().mixed_prev_space()) + .is_none() + { + self.print_sep(Separator::SpaceOrNbsp(is_var_def)); + } + self.ibox(0); + self.print_override(override_); + pre_init_size += self.estimate_size(override_.span) + 1; + } + + if *indexed { + self.print_attribute(indexed.then_some("indexed"), is_var_def, &mut pre_init_size); + } + + if let Some(ident) = name { + self.print_sep(Separator::SpaceOrNbsp(is_var_def && override_.is_none())); + self.print_comments( + ident.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(), + ); + self.print_ident(ident); + pre_init_size += self.estimate_size(ident.span) + 1; + } + if let Some(init) = initializer { + let cache = self.var_init; + self.var_init = true; + + pre_init_size += 2; + self.print_word(" ="); + if override_.is_some() { + self.end(); + } + self.end(); + + self.print_assign_rhs(init, pre_init_size, init_space_left, Some(&ty.kind), cache); + } else { + self.end(); + } + self.end(); + } + + fn print_attribute( + &mut self, + attribute: Option<&'static str>, + is_var_def: bool, + size: &mut usize, + ) { + if let Some(s) = attribute { + self.print_sep(Separator::SpaceOrNbsp(is_var_def)); + self.print_word(s); + *size += s.len() + 1; + } + } + + fn print_parameter_list( + &mut self, + parameters: &'ast [ast::VariableDefinition<'ast>], + span: Span, + format: ListFormat, + ) { + if self.handle_span(span, false) { + return; + } + + self.print_tuple( + parameters, + span.lo(), + span.hi(), + |fmt, var| fmt.print_var(var, false), + get_span!(), + format, + ); + } + + fn print_ident_or_strlit(&mut self, value: &'ast ast::IdentOrStrLit) { + match value { + ast::IdentOrStrLit::Ident(ident) => self.print_ident(ident), + ast::IdentOrStrLit::StrLit(strlit) => self.print_ast_str_lit(strlit), + } + } + + /// Prints a raw AST string literal, which is unescaped. + fn print_ast_str_lit(&mut self, strlit: &'ast ast::StrLit) { + self.print_str_lit(ast::StrKind::Str, strlit.span.lo(), strlit.value.as_str()); + } + + fn print_lit(&mut self, lit: &'ast ast::Lit<'ast>) { + self.print_lit_inner(lit, false); + } + + fn print_ty(&mut self, ty: &'ast ast::Type<'ast>) { + if self.handle_span(ty.span, false) { + return; + } + + match &ty.kind { + &ast::TypeKind::Elementary(ty) => 'b: { + match ty { + // `address payable` is normalized to `address`. + ast::ElementaryType::Address(true) => { + self.word("address payable"); + break 'b; + } + // Integers are normalized to long form. + ast::ElementaryType::Int(size) | ast::ElementaryType::UInt(size) => { + match (self.config.int_types, size.bits_raw()) { + (config::IntTypes::Short, 0 | 256) + | (config::IntTypes::Preserve, 0) => { + let short = match ty { + ast::ElementaryType::Int(_) => "int", + ast::ElementaryType::UInt(_) => "uint", + _ => unreachable!(), + }; + self.word(short); + break 'b; + } + _ => {} + } + } + _ => {} + } + self.word(ty.to_abi_str()); + } + ast::TypeKind::Array(ast::TypeArray { element, size }) => { + self.print_ty(element); + if let Some(size) = size { + self.word("["); + self.print_expr(size); + self.word("]"); + } else { + self.word("[]"); + } + } + ast::TypeKind::Function(ast::TypeFunction { + parameters, + visibility, + state_mutability, + returns, + }) => { + self.cbox(0); + self.word("function"); + self.print_parameter_list(parameters, parameters.span, ListFormat::inline()); + + if let Some(v) = visibility { + self.space(); + self.word(v.to_str()); + } + if let Some(sm) = state_mutability + && !matches!(**sm, ast::StateMutability::NonPayable) + { + self.space(); + self.word(sm.to_str()); + } + if let Some(ret) = returns + && !ret.is_empty() + { + self.nbsp(); + self.word("returns"); + self.nbsp(); + self.print_parameter_list( + ret, + ret.span, + ListFormat::consistent(), // .with_cmnts_break(false), + ); + } + self.end(); + } + ast::TypeKind::Mapping(ast::TypeMapping { key, key_name, value, value_name }) => { + self.word("mapping("); + self.s.cbox(0); + if let Some(cmnt) = self.peek_comment_before(key.span.lo()) { + if cmnt.style.is_mixed() { + self.print_comments( + key.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ); + self.break_offset_if_not_bol(SIZE_INFINITY as usize, 0, false); + } else { + self.print_comments(key.span.lo(), CommentConfig::skip_ws()); + } + } + // Fitting a mapping in one line takes, at least, 16 chars (one-char var name): + // 'mapping(' + {key} + ' => ' {value} ') ' + {name} + ';' + // To be more conservative, we use 18 to decide whether to force a break or not. + else if 18 + + self.estimate_size(key.span) + + key_name.map(|k| self.estimate_size(k.span)).unwrap_or(0) + + self.estimate_size(value.span) + + value_name.map(|v| self.estimate_size(v.span)).unwrap_or(0) + >= self.space_left() + { + self.hardbreak(); + } else { + self.zerobreak(); + } + self.s.cbox(0); + self.print_ty(key); + if let Some(ident) = key_name { + if self + .print_comments( + ident.span.lo(), + CommentConfig::skip_ws() + .mixed_no_break() + .mixed_prev_space() + .mixed_post_nbsp(), + ) + .is_none() + { + self.nbsp(); + } + self.print_ident(ident); + } + // NOTE(rusowsky): unless we add more spans to solar, using `value.span.lo()` + // consumes "comment6" of which should be printed after the `=>` + self.print_comments( + value.span.lo(), + CommentConfig::skip_ws() + .trailing_no_break() + .mixed_no_break() + .mixed_prev_space(), + ); + self.space(); + self.s.offset(self.ind); + self.word("=> "); + self.s.ibox(self.ind); + self.print_ty(value); + if let Some(ident) = value_name { + self.neverbreak(); + if self + .print_comments( + ident.span.lo(), + CommentConfig::skip_ws() + .mixed_no_break() + .mixed_prev_space() + .mixed_post_nbsp(), + ) + .is_none() + { + self.nbsp(); + } + self.print_ident(ident); + if self + .peek_comment_before(ty.span.hi()) + .is_some_and(|cmnt| cmnt.style.is_mixed()) + { + self.neverbreak(); + self.print_comments( + value.span.lo(), + CommentConfig::skip_ws().mixed_no_break(), + ); + } + } + self.end(); + self.end(); + if self + .print_comments( + ty.span.hi(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ) + .is_some_and(|cmnt| !cmnt.is_mixed()) + { + self.break_offset_if_not_bol(0, -self.ind, false); + } else { + self.zerobreak(); + self.s.offset(-self.ind); + } + self.end(); + self.word(")"); + } + ast::TypeKind::Custom(path) => self.print_path(path, false), + } + } + + fn print_override(&mut self, override_: &'ast ast::Override<'ast>) { + let ast::Override { span, paths } = override_; + if self.handle_span(*span, false) { + return; + } + self.word("override"); + if !paths.is_empty() { + if self.config.override_spacing { + self.nbsp(); + } + self.print_tuple( + paths, + span.lo(), + span.hi(), + |this, path| this.print_path(path, false), + get_span!(()), + ListFormat::consistent(), // .with_cmnts_break(false), + ); + } + } + + /* --- Expressions --- */ + /// Prints an expression by matching on its variant and delegating to the appropriate + /// printer method, handling all Solidity expression kinds. + fn print_expr(&mut self, expr: &'ast ast::Expr<'ast>) { + let ast::Expr { span, ref kind } = *expr; + if self.handle_span(span, false) { + return; + } + + match kind { + ast::ExprKind::Array(exprs) => { + self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!()) + } + ast::ExprKind::Assign(lhs, None, rhs) => self.print_assign_expr(lhs, rhs), + ast::ExprKind::Assign(lhs, Some(op), rhs) => self.print_bin_expr(lhs, op, rhs, true), + ast::ExprKind::Binary(lhs, op, rhs) => self.print_bin_expr(lhs, op, rhs, false), + ast::ExprKind::Call(call_expr, call_args) => { + let cache = self.call_with_opts_and_args; + self.call_with_opts_and_args = is_call_with_opts_and_args(&expr.kind); + self.print_member_or_call_chain( + call_expr, + MemberOrCallArgs::CallArgs( + self.estimate_size(call_args.span), + self.has_comments_between_elements(call_args.span, call_args.exprs()), + ), + |s| { + s.print_call_args( + call_args, + ListFormat::compact() + .break_cmnts() + .break_single(true) + .without_ind(s.return_bin_expr) + .with_delimiters(!s.call_with_opts_and_args), + get_callee_head_size(call_expr), + ); + }, + ); + self.call_with_opts_and_args = cache; + } + ast::ExprKind::CallOptions(expr, named_args) => { + // the flag is only meant to be used to format the call args + let cache = self.call_with_opts_and_args; + self.call_with_opts_and_args = false; + + self.print_expr(expr); + self.print_named_args(named_args, span.hi()); + + // restore cached value + self.call_with_opts_and_args = cache; + } + ast::ExprKind::Delete(expr) => { + self.word("delete "); + self.print_expr(expr); + } + ast::ExprKind::Ident(ident) => self.print_ident(ident), + ast::ExprKind::Index(expr, kind) => self.print_index_expr(span, expr, kind), + ast::ExprKind::Lit(lit, unit) => { + self.print_lit(lit); + if let Some(unit) = unit { + self.nbsp(); + self.word(unit.to_str()); + } + } + ast::ExprKind::Member(member_expr, ident) => { + self.print_member_or_call_chain( + member_expr, + MemberOrCallArgs::Member(self.estimate_size(ident.span)), + |s| { + s.print_trailing_comment(member_expr.span.hi(), Some(ident.span.lo())); + match member_expr.kind { + ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) => (), + ast::ExprKind::Index(..) if s.skip_index_break => (), + // Don't add break when accessing a field after a call with named args. + // e.g., `_lzSend({_dstEid: x, ...}).guid` should keep `.guid` + // on the same line as the closing `})`. + // See: https://github.com/foundry-rs/foundry/issues/12399 + _ if is_call_with_named_args(&member_expr.kind) => (), + _ => s.zerobreak(), + } + s.word("."); + s.print_ident(ident); + }, + ); + } + ast::ExprKind::New(ty) => { + self.word("new "); + self.print_ty(ty); + } + ast::ExprKind::Payable(args) => { + self.word("payable"); + self.print_call_args(args, ListFormat::compact().break_cmnts(), 7); + } + ast::ExprKind::Ternary(cond, then, els) => self.print_ternary_expr(cond, then, els), + ast::ExprKind::Tuple(exprs) => self.print_tuple( + exprs, + span.lo(), + span.hi(), + |this, expr| match expr.as_ref() { + SpannedOption::Some(expr) => this.print_expr(expr), + SpannedOption::None(span) => { + this.print_comments(span.hi(), CommentConfig::skip_ws().no_breaks()); + } + }, + |expr| match expr.as_ref() { + SpannedOption::Some(expr) => expr.span, + // Manually handled by printing the comment when `None` + SpannedOption::None(..) => Span::DUMMY, + }, + ListFormat::compact().break_single(is_binary_expr(&expr.kind)), + ), + ast::ExprKind::TypeCall(ty) => { + self.word("type"); + self.print_tuple( + std::slice::from_ref(ty), + span.lo(), + span.hi(), + Self::print_ty, + get_span!(), + ListFormat::consistent(), + ); + } + ast::ExprKind::Type(ty) => self.print_ty(ty), + ast::ExprKind::Unary(un_op, expr) => { + let prefix = un_op.kind.is_prefix(); + let op = un_op.kind.to_str(); + if prefix { + self.word(op); + } + self.print_expr(expr); + if !prefix { + debug_assert!(un_op.kind.is_postfix()); + self.word(op); + } + } + } + self.cursor.advance_to(span.hi(), true); + } + + /// Prints a simple assignment expression of the form `lhs = rhs`. + fn print_assign_expr(&mut self, lhs: &'ast ast::Expr<'ast>, rhs: &'ast ast::Expr<'ast>) { + let cache = self.var_init; + self.var_init = true; + + let space_left = self.space_left(); + let lhs_size = self.estimate_size(lhs.span); + self.print_expr(lhs); + self.word(" ="); + self.print_assign_rhs(rhs, lhs_size + 2, space_left, None, cache); + } + + /// Prints a binary operator expression. Handles operator chains and formatting. + fn print_bin_expr( + &mut self, + lhs: &'ast ast::Expr<'ast>, + bin_op: &ast::BinOp, + rhs: &'ast ast::Expr<'ast>, + is_assign: bool, + ) { + let prev_chain = self.binary_expr; + let is_chain = prev_chain.is_some_and(|prev| prev == bin_op.kind.group()); + + // Opening box if starting a new operator chain. + if !is_chain { + self.binary_expr = Some(bin_op.kind.group()); + + let indent = if (is_assign && has_complex_successor(&rhs.kind, true)) + || self.call_stack.is_nested() + && is_call_chain(&lhs.kind, false) + && self.estimate_size(lhs.span) >= self.space_left() + { + 0 + } else { + self.ind + }; + self.s.ibox(indent); + } + + // Print LHS. + self.print_expr(lhs); + + // Handle assignment (`+=`, etc.) vs binary ops (`+`, `*`, etc.). + let no_trailing_comment = !self.print_trailing_comment(lhs.span.hi(), Some(rhs.span.lo())); + if is_assign { + if no_trailing_comment { + self.nbsp(); + } + self.word(bin_op.kind.to_str()); + self.word("= "); + } else { + if no_trailing_comment + && self + .print_comments( + bin_op.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ) + .is_none_or(|cmnt| cmnt.is_mixed()) + { + if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) { + self.space_if_not_bol(); + } else if !self.is_bol_or_only_ind() && !self.last_token_is_break() { + self.zerobreak(); + } + } + + self.word(bin_op.kind.to_str()); + + if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) { + self.nbsp(); + } + } + + // Print RHS with optional ibox if mixed comment precedes. + let rhs_has_mixed_comment = + self.peek_comment_before(rhs.span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed()); + if rhs_has_mixed_comment { + self.ibox(0); + self.print_expr(rhs); + self.end(); + } else { + self.print_expr(rhs); + } + + // End current box if this was top-level in the chain. + if !is_chain { + self.binary_expr = prev_chain; + self.end(); + } + } + + /// Prints an indexing expression. + fn print_index_expr( + &mut self, + span: Span, + expr: &'ast ast::Expr<'ast>, + kind: &'ast ast::IndexKind<'ast>, + ) { + self.print_expr(expr); + self.word("["); + self.s.cbox(self.ind); + + let mut skip_break = false; + let mut zerobreak = |this: &mut Self| { + if this.skip_index_break { + skip_break = true; + } else { + this.zerobreak(); + } + }; + match kind { + ast::IndexKind::Index(Some(inner_expr)) => { + zerobreak(self); + self.print_expr(inner_expr); + } + ast::IndexKind::Index(None) => {} + ast::IndexKind::Range(start, end) => { + if let Some(start_expr) = start { + if self + .print_comments(start_expr.span.lo(), CommentConfig::skip_ws()) + .is_none_or(|s| s.is_mixed()) + { + zerobreak(self); + } + self.print_expr(start_expr); + } else { + zerobreak(self); + } + + self.word(":"); + + if let Some(end_expr) = end { + self.s.ibox(self.ind); + if start.is_some() { + zerobreak(self); + } + self.print_comments( + end_expr.span.lo(), + CommentConfig::skip_ws() + .mixed_prev_space() + .mixed_no_break() + .mixed_post_nbsp(), + ); + self.print_expr(end_expr); + } + + // Trailing comment handling. + let is_trailing = if let Some(style) = self.print_comments( + span.hi(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(), + ) { + skip_break = true; + style.is_trailing() + } else { + false + }; + + // Adjust indentation and line breaks. + match (skip_break, end.is_some()) { + (true, true) => { + self.break_offset_if_not_bol(0, -2 * self.ind, false); + self.end(); + if !is_trailing { + self.break_offset_if_not_bol(0, -self.ind, false); + } + } + (true, false) => { + self.break_offset_if_not_bol(0, -self.ind, false); + } + (false, true) => { + self.end(); + } + _ => {} + } + } + } + + if !skip_break { + self.zerobreak(); + self.s.offset(-self.ind); + } + + self.end(); + self.word("]"); + } + + /// Prints a ternary expression of the form `cond ? then : else`. + fn print_ternary_expr( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Expr<'ast>, + els: &'ast ast::Expr<'ast>, + ) { + self.s.cbox(self.ind); + self.s.ibox(0); + + let print_sub_expr = |this: &mut Self, span_lo, prefix, expr: &'ast ast::Expr<'ast>| { + match prefix { + Some(prefix) => { + if this.peek_comment_before(span_lo).is_some() { + this.space(); + } + this.print_comments(span_lo, CommentConfig::skip_ws()); + this.end(); + if !this.is_bol_or_only_ind() { + this.space(); + } + this.s.ibox(0); + this.word(prefix); + } + None => { + this.print_comments(expr.span.lo(), CommentConfig::skip_ws()); + } + }; + this.print_expr(expr); + }; + + // conditional expression + self.s.ibox(-self.ind); + print_sub_expr(self, then.span.lo(), None, cond); + self.end(); + // then expression + print_sub_expr(self, then.span.lo(), Some("? "), then); + // else expression + print_sub_expr(self, els.span.lo(), Some(": "), els); + + self.end(); + self.neverbreak(); + self.s.offset(-self.ind); + self.end(); + } + + // If `add_parens_if_empty` is true, then add parentheses `()` even if there are no arguments. + fn print_modifier_call( + &mut self, + modifier: &'ast ast::Modifier<'ast>, + add_parens_if_empty: bool, + ) { + let ast::Modifier { name, arguments } = modifier; + self.print_path(name, false); + if !arguments.is_empty() || add_parens_if_empty { + self.print_call_args( + arguments, + ListFormat::compact().break_cmnts(), + name.to_string().len(), + ); + } + } + + fn print_member_or_call_chain( + &mut self, + child_expr: &'ast ast::Expr<'ast>, + member_or_args: MemberOrCallArgs, + print_suffix: F, + ) where + F: FnOnce(&mut Self), + { + fn member_depth(depth: usize, expr: &ast::Expr<'_>) -> usize { + if let ast::ExprKind::Member(child, ..) = &expr.kind { + member_depth(depth + 1, child) + } else { + depth + } + } + + let (mut extra_box, skip_cache) = (false, self.skip_index_break); + let parent_is_chain = self.call_stack.last().copied().is_some_and(|call| call.is_chained()); + if !parent_is_chain { + // Estimate sizes of callee and optional member + let callee_size = get_callee_head_size(child_expr) + member_or_args.member_size(); + let expr_size = self.estimate_size(child_expr.span); + + let callee_fits_line = self.space_left() > callee_size + 1; + let total_fits_line = self.space_left() > expr_size + member_or_args.size() + 2; + let no_cmnt_or_mixed = + self.peek_comment_before(child_expr.span.hi()).is_none_or(|c| c.style.is_mixed()); + + // If call with options, add an extra box to prioritize breaking the call args + if self.call_with_opts_and_args { + self.cbox(0); + extra_box = true; + } + + // Determine if this chain will add its own indentation + let chain_has_indent = is_call_chain(&child_expr.kind, true) + || !(no_cmnt_or_mixed + || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..))) + || !callee_fits_line + || (member_depth(0, child_expr) >= 2 + && (!total_fits_line || member_or_args.has_comments())); + + // Start a new chain if needed + if is_call_chain(&child_expr.kind, false) { + self.call_stack.push(CallContext::chained(callee_size, chain_has_indent)); + } + + if chain_has_indent { + self.s.ibox(self.ind); + } else { + self.skip_index_break = true; + self.cbox(0); + } + } + + // Recursively print the child/prefix expression. + self.print_expr(child_expr); + + // If an extra box was opened, close it + if extra_box { + self.end(); + } + + // Call the closure to print the suffix for the current link, with the calculated position. + print_suffix(self); + + // If a chain was started, clean up the state and end the box. + if !parent_is_chain { + if is_call_chain(&child_expr.kind, false) { + self.call_stack.pop(); + } + self.end(); + } + + // Restore cache + if self.skip_index_break { + self.skip_index_break = skip_cache; + } + } + + fn print_call_args( + &mut self, + args: &'ast ast::CallArgs<'ast>, + format: ListFormat, + callee_size: usize, + ) { + let ast::CallArgs { span, ref kind } = *args; + if self.handle_span(span, true) { + return; + } + + self.call_stack.push(CallContext::nested(callee_size)); + + // Clear the binary expression cache before the call. + let cache = self.binary_expr.take(); + + match kind { + ast::CallArgsKind::Unnamed(exprs) => { + self.print_tuple( + exprs, + span.lo(), + span.hi(), + |this, e| this.print_expr(e), + get_span!(), + format, + ); + } + ast::CallArgsKind::Named(named_args) => { + self.print_inside_parens(|state| state.print_named_args(named_args, span.hi())); + } + } + + // Restore the cache to continue with the current chain. + self.binary_expr = cache; + self.call_stack.pop(); + } + + fn print_named_args(&mut self, args: &'ast [ast::NamedArg<'ast>], pos_hi: BytePos) { + let list_format = match (self.config.bracket_spacing, self.config.prefer_compact.calls()) { + (false, true) => ListFormat::compact(), + (false, false) => ListFormat::consistent(), + (true, true) => ListFormat::compact().with_space(), + (true, false) => ListFormat::consistent().with_space(), + }; + + self.word("{"); + // Use the start position of the first argument's name for comment processing. + if let Some(first_arg) = args.first() { + let list_lo = first_arg.name.span.lo(); + self.commasep( + args, + list_lo, + pos_hi, + // Closure to print a single named argument (`name: value`) + |s, arg| { + s.cbox(0); + s.print_ident(&arg.name); + s.word(":"); + if s.same_source_line(arg.name.span.hi(), arg.value.span.hi()) + || !s.print_trailing_comment(arg.name.span.hi(), None) + { + s.nbsp(); + } + s.print_comments( + arg.value.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(), + ); + s.print_expr(arg.value); + s.end(); + }, + |arg| arg.name.span.until(arg.value.span), + list_format + .break_cmnts() + .break_single(true) + .without_ind(self.call_stack.has_chain_with_indent()) + .with_delimiters(!self.call_with_opts_and_args), + ); + } else if self.config.bracket_spacing { + self.nbsp(); + } + self.word("}"); + } + + /* --- Statements --- */ + /// Prints the given statement in the source code, handling formatting, inline documentation, + /// trailing comments and layout logic for various statement kinds. + fn print_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) { + let ast::Stmt { ref docs, span, ref kind } = *stmt; + self.print_docs(docs); + + // Handle disabled statements. + if self.handle_span(span, false) { + self.print_trailing_comment_no_break(stmt.span.hi(), None); + return; + } + + // return statements can't have a preceding comment in the same line. + let force_break = matches!(kind, ast::StmtKind::Return(..)) + && self.peek_comment_before(span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed()); + + match kind { + ast::StmtKind::Assembly(ast::StmtAssembly { dialect, flags, block }) => { + self.print_assembly_stmt(span, dialect, flags, block) + } + ast::StmtKind::DeclSingle(var) => self.print_var(var, true), + ast::StmtKind::DeclMulti(vars, init_expr) => { + self.print_multi_decl_stmt(span, vars, init_expr) + } + ast::StmtKind::Block(stmts) => self.print_block(stmts, span), + ast::StmtKind::Break => self.word("break"), + ast::StmtKind::Continue => self.word("continue"), + ast::StmtKind::DoWhile(stmt, cond) => { + self.word("do "); + self.print_stmt_as_block(stmt, cond.span.lo(), false); + self.nbsp(); + self.print_if_cond("while", cond, cond.span.hi()); + } + ast::StmtKind::Emit(path, args) => self.print_emit_or_revert("emit", path, args), + ast::StmtKind::Expr(expr) => self.print_expr(expr), + ast::StmtKind::For { init, cond, next, body } => { + self.print_for_stmt(span, init, cond, next, body) + } + ast::StmtKind::If(cond, then, els_opt) => self.print_if_stmt(span, cond, then, els_opt), + ast::StmtKind::Return(expr) => self.print_return_stmt(force_break, expr), + ast::StmtKind::Revert(path, args) => self.print_emit_or_revert("revert", path, args), + ast::StmtKind::Try(ast::StmtTry { expr, clauses }) => { + self.print_try_stmt(expr, clauses) + } + ast::StmtKind::UncheckedBlock(block) => { + self.word("unchecked "); + self.print_block(block, stmt.span); + } + ast::StmtKind::While(cond, stmt) => { + // Check if blocks should be inlined and update cache if necessary + let inline = self.is_single_line_block(cond, stmt, None); + if !inline.is_cached && self.single_line_stmt.is_none() { + self.single_line_stmt = Some(inline.outcome); + } + + // Print while cond and its statement + self.print_if_cond("while", cond, stmt.span.lo()); + self.nbsp(); + self.print_stmt_as_block(stmt, stmt.span.hi(), inline.outcome); + + // Clear cache if necessary + if !inline.is_cached && self.single_line_stmt.is_some() { + self.single_line_stmt = None; + } + } + ast::StmtKind::Placeholder => self.word("_"), + } + if stmt_needs_semi(kind) { + self.neverbreak(); // semicolon shouldn't account for linebreaks + self.word(";"); + self.cursor.advance_to(span.hi(), true); + } + // print comments without breaks, as those are handled by the caller. + self.print_comments( + stmt.span.hi(), + CommentConfig::default().trailing_no_break().mixed_no_break().mixed_prev_space(), + ); + self.print_trailing_comment_no_break(stmt.span.hi(), None); + } + + /// Prints an `assembly` statement, including optional dialect and flags, + /// followed by its Yul block. + fn print_assembly_stmt( + &mut self, + span: Span, + dialect: &'ast Option, + flags: &'ast [ast::StrLit], + block: &'ast ast::yul::Block<'ast>, + ) { + _ = self.handle_span(self.cursor.span(span.lo()), false); + if !self.handle_span(span.until(block.span), false) { + self.cursor.advance_to(span.lo(), true); + self.print_word("assembly "); // 9 chars + if let Some(dialect) = dialect { + self.print_ast_str_lit(dialect); + self.print_sep(Separator::Nbsp); + } + if !flags.is_empty() { + self.print_tuple( + flags, + span.lo(), + block.span.lo(), + Self::print_ast_str_lit, + get_span!(), + ListFormat::consistent(), + ); + self.print_sep(Separator::Nbsp); + } + } + self.print_yul_block(block, block.span, false, 9); + } + + /// Prints a multiple-variable declaration with a single initializer expression, + /// formatted as a tuple-style assignment (e.g., `(a, b) = foo();`). + fn print_multi_decl_stmt( + &mut self, + span: Span, + vars: &'ast BoxSlice<'ast, SpannedOption>>, + init_expr: &'ast ast::Expr<'ast>, + ) { + let space_left = self.space_left(); + + self.s.ibox(self.ind); + self.s.ibox(-self.ind); + self.print_tuple( + vars, + span.lo(), + init_expr.span.lo(), + |this, var| match var { + SpannedOption::Some(var) => this.print_var(var, true), + SpannedOption::None(span) => { + this.print_comments(span.hi(), CommentConfig::skip_ws().mixed_no_break_post()); + } + }, + |var| match var { + SpannedOption::Some(var) => var.span, + // Manually handled by printing the comment when `None` + SpannedOption::None(..) => Span::DUMMY, + }, + ListFormat::consistent(), + ); + self.end(); + self.word(" ="); + + if self.estimate_size(init_expr.span) + self.config.tab_width + <= std::cmp::max(space_left, self.space_left()) + { + self.print_sep(Separator::Space); + self.ibox(0); + } else { + self.print_sep(Separator::Nbsp); + self.neverbreak(); + self.s.ibox(-self.ind); + } + self.print_expr(init_expr); + self.end(); + self.end(); + } + + /// Prints a `for` loop statement, including its initializer, condition, + /// increment expression, and loop body, with formatting and spacing. + fn print_for_stmt( + &mut self, + span: Span, + init: &'ast Option<&mut ast::Stmt<'ast>>, + cond: &'ast Option<&mut ast::Expr<'ast>>, + next: &'ast Option<&mut ast::Expr<'ast>>, + body: &'ast ast::Stmt<'ast>, + ) { + self.cbox(0); + self.s.ibox(self.ind); + self.print_word("for ("); + self.zerobreak(); + + // Print init. + self.s.cbox(0); + match init { + Some(init_stmt) => self.print_stmt(init_stmt), + None => self.print_word(";"), + } + + // Print condition. + match cond { + Some(cond_expr) => { + self.print_sep(Separator::Space); + self.print_expr(cond_expr); + } + None => self.zerobreak(), + } + self.print_word(";"); + + // Print next clause. + match next { + Some(next_expr) => { + self.space(); + self.print_expr(next_expr); + } + None => self.zerobreak(), + } + + // Close head. + self.break_offset_if_not_bol(0, -self.ind, false); + self.end(); + self.print_word(") "); + self.neverbreak(); + self.end(); + + // Print comments and body. + self.print_comments(body.span.lo(), CommentConfig::skip_ws()); + self.print_stmt_as_block(body, span.hi(), false); + self.end(); + } + + /// Prints an `if` statement, including its condition, `then` block, and any chained + /// `else` or `else if` branches, handling inline formatting decisions and comments. + fn print_if_stmt( + &mut self, + span: Span, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: &'ast Option<&mut ast::Stmt<'ast>>, + ) { + // Check if blocks should be inlined and update cache if necessary + let inline = self.is_single_line_block(cond, then, els_opt.as_ref()); + let set_inline_cache = !inline.is_cached && self.single_line_stmt.is_none(); + if set_inline_cache { + self.single_line_stmt = Some(inline.outcome); + } + + self.cbox(0); + self.ibox(0); + // Print if stmt + self.print_if_no_else(cond, then, inline.outcome); + + // Print else (if) stmts, if any + let mut current_else = els_opt.as_deref(); + while let Some(els) = current_else { + if self.ends_with('}') { + // If there are comments with line breaks, don't add spaces to mixed comments + if self.has_comment_before_with(els.span.lo(), |cmnt| !cmnt.style.is_mixed()) { + // If last comment is miced, ensure line break + if self + .print_comments(els.span.lo(), CommentConfig::skip_ws().mixed_no_break()) + .is_some_and(|cmnt| cmnt.is_mixed()) + { + self.hardbreak(); + } + } + // Otherwise, ensure a non-breaking space is added + else if self + .print_comments( + els.span.lo(), + CommentConfig::skip_ws() + .mixed_no_break() + .mixed_prev_space() + .mixed_post_nbsp(), + ) + .is_none() + { + self.nbsp(); + } + } else { + self.hardbreak_if_not_bol(); + if self + .print_comments(els.span.lo(), CommentConfig::skip_ws()) + .is_some_and(|cmnt| cmnt.is_mixed()) + { + self.hardbreak(); + }; + } + + self.ibox(0); + self.print_word("else "); + match &els.kind { + ast::StmtKind::If(cond, then, next_else) => { + self.print_if_no_else(cond, then, inline.outcome); + current_else = next_else.as_deref(); + } + _ => { + self.print_stmt_as_block(els, span.hi(), inline.outcome); + self.end(); // end ibox for final else + break; + } + } + } + self.end(); + + // Clear inline cache if we set it earlier. + if set_inline_cache { + self.single_line_stmt = None; + } + } + + /// Prints a `return` statement, optionally including a return expression. + /// Handles spacing, line breaking, and formatting. + fn print_return_stmt(&mut self, force_break: bool, expr: &'ast Option<&mut ast::Expr<'ast>>) { + if force_break { + self.hardbreak_if_not_bol(); + } + + let space_left = self.space_left(); + let expr_size = expr.as_ref().map_or(0, |expr| self.estimate_size(expr.span)); + + // `return ' + expr + ';' + let overflows = space_left < 8 + expr_size; + let fits_alone = space_left > expr_size; + + if let Some(expr) = expr { + let is_simple = matches!(expr.kind, ast::ExprKind::Lit(..) | ast::ExprKind::Ident(..)); + let allow_break = overflows && fits_alone; + + self.return_bin_expr = matches!(expr.kind, ast::ExprKind::Binary(..)); + self.s.ibox(if is_simple || allow_break { self.ind } else { 0 }); + + self.print_word("return"); + + match self.print_comments( + expr.span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(), + ) { + Some(cmnt) if cmnt.is_trailing() && !is_simple => self.s.offset(self.ind), + None => self.print_sep(Separator::SpaceOrNbsp(allow_break)), + _ => {} + } + + self.print_expr(expr); + self.end(); + self.return_bin_expr = false; + } else { + self.print_word("return"); + } + } + + /// Prints a `try` statement along with its associated `catch` clauses, + /// following Solidity's `try ... returns (...) { ... } catch (...) { ... }` syntax. + fn print_try_stmt( + &mut self, + expr: &'ast ast::Expr<'ast>, + clauses: &'ast [ast::TryCatchClause<'ast>], + ) { + self.cbox(0); + if let Some((first, other)) = clauses.split_first() { + // Print the 'try' clause + let ast::TryCatchClause { args, block, span: try_span, .. } = first; + self.cbox(0); + self.ibox(0); + self.print_word("try "); + self.print_comments(expr.span.lo(), CommentConfig::skip_ws()); + self.print_expr(expr); + + // Print comments. + self.print_comments( + args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()), + CommentConfig::skip_ws(), + ); + if !self.is_beginning_of_line() { + self.nbsp(); + } + + if args.is_empty() { + self.end(); + } else { + self.print_word("returns "); + self.print_word("("); + self.zerobreak(); + self.end(); + let span = args.span.with_hi(block.span.lo()); + self.commasep( + args, + span.lo(), + span.hi(), + |fmt, var| fmt.print_var(var, false), + get_span!(), + ListFormat::compact().with_delimiters(false), + ); + self.print_word(")"); + self.nbsp(); + } + if block.is_empty() { + self.print_block(block, *try_span); + self.end(); + } else { + self.print_word("{"); + self.end(); + self.neverbreak(); + self.print_trailing_comment_no_break(try_span.lo(), None); + self.print_block_without_braces(block, try_span.hi(), Some(self.ind)); + if self.cursor.enabled || self.cursor.pos < try_span.hi() { + self.print_word("}"); + self.cursor.advance_to(try_span.hi(), true); + } + } + + let mut skip_ind = false; + if self.print_trailing_comment(try_span.hi(), other.first().map(|c| c.span.lo())) { + // if a trailing comment is printed at the very end, we have to manually + // adjust the offset to avoid having a double break. + self.break_offset_if_not_bol(0, self.ind, false); + skip_ind = true; + }; + + let mut prev_block_multiline = self.is_multiline_block(block, false); + + // Handle 'catch' clauses + for (pos, ast::TryCatchClause { name, args, block, span: catch_span }) in + other.iter().delimited() + { + let current_block_multiline = self.is_multiline_block(block, false); + if !pos.is_first || !skip_ind { + if prev_block_multiline && (current_block_multiline || pos.is_last) { + self.nbsp(); + } else { + self.space(); + if !current_block_multiline { + self.s.offset(self.ind); + } + } + } + self.s.ibox(self.ind); + self.print_comments( + catch_span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(), + ); + + self.print_word("catch "); + if !args.is_empty() { + self.print_comments( + args[0].span.lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(), + ); + if let Some(name) = name { + self.print_ident(name); + } + self.print_parameter_list( + args, + args.span.with_hi(block.span.lo()), + ListFormat::inline(), + ); + self.nbsp(); + } + self.print_word("{"); + self.end(); + if !block.is_empty() { + self.print_trailing_comment_no_break(catch_span.lo(), None); + } + self.print_block_without_braces(block, catch_span.hi(), Some(self.ind)); + if self.cursor.enabled || self.cursor.pos < try_span.hi() { + self.print_word("}"); + self.cursor.advance_to(catch_span.hi(), true); + } + + prev_block_multiline = current_block_multiline; + } + } + self.end(); + } + + fn print_if_no_else( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + inline: bool, + ) { + if !self.handle_span(cond.span.until(then.span), true) { + self.print_if_cond("if", cond, then.span.lo()); + // if empty block without comments, ensure braces are inlined + if let ast::StmtKind::Block(block) = &then.kind + && block.is_empty() + && self.peek_comment_before(then.span.hi()).is_none() + { + self.neverbreak(); + self.print_sep(Separator::Nbsp); + } else { + self.print_sep(Separator::Space); + } + } + self.end(); + self.print_stmt_as_block(then, then.span.hi(), inline); + self.cursor.advance_to(then.span.hi(), true); + } + + fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { + self.print_word(kw); + self.print_sep_unhandled(Separator::Nbsp); + self.print_tuple( + std::slice::from_ref(cond), + cond.span.lo(), + pos_hi, + Self::print_expr, + get_span!(), + ListFormat::compact().break_cmnts().break_single(is_binary_expr(&cond.kind)), + ); + } + + fn print_emit_or_revert( + &mut self, + kw: &'static str, + path: &'ast ast::PathSlice, + args: &'ast ast::CallArgs<'ast>, + ) { + self.word(kw); + if self + .print_comments( + path.span().lo(), + CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(), + ) + .is_none() + { + self.nbsp(); + }; + self.s.cbox(0); + self.emit_or_revert = path.segments().len() > 1; + self.print_path(path, false); + let format = if self.config.prefer_compact.calls() { + ListFormat::compact() + } else { + ListFormat::consistent() + }; + self.print_call_args(args, format.break_cmnts(), path.to_string().len()); + self.emit_or_revert = false; + self.end(); + } + + fn print_block(&mut self, block: &'ast [ast::Stmt<'ast>], span: Span) { + self.print_block_inner( + block, + BlockFormat::Regular, + Self::print_stmt, + |b| b.span, + span.hi(), + ); + } + + fn print_block_without_braces( + &mut self, + block: &'ast [ast::Stmt<'ast>], + pos_hi: BytePos, + offset: Option, + ) { + self.print_block_inner( + block, + BlockFormat::NoBraces(offset), + Self::print_stmt, + |b| b.span, + pos_hi, + ); + } + + // Body of a if/loop. + fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, pos_hi: BytePos, inline: bool) { + if self.handle_span(stmt.span, false) { + return; + } + + let stmts = if let ast::StmtKind::Block(stmts) = &stmt.kind { + stmts + } else { + std::slice::from_ref(stmt) + }; + + if inline && !stmts.is_empty() { + self.neverbreak(); + self.print_block_without_braces(stmts, pos_hi, None); + } else { + // Reset cache for nested (child) stmts within this (parent) block. + let inline_parent = self.single_line_stmt.take(); + + self.print_word("{"); + self.print_block_without_braces(stmts, pos_hi, Some(self.ind)); + self.print_word("}"); + + // Restore cache for the rest of stmts within the same height. + self.single_line_stmt = inline_parent; + } + } + + /// Determines if an `if/else` block should be inlined. + /// Also returns if the value was cached, so that it can be cleaned afterwards. + /// + /// # Returns + /// + /// A tuple `(should_inline, was_cached)`. The second boolean is `true` if the + /// decision was retrieved from the cache or is a final decision based on config, + /// preventing the caller from clearing a cache value that was never set. + fn is_single_line_block( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, + ) -> Decision { + // If a decision is already cached from a parent, use it directly. + if let Some(cached_decision) = self.single_line_stmt { + return Decision { outcome: cached_decision, is_cached: true }; + } + + // Empty statements are always printed as blocks. + if std::slice::from_ref(then).is_empty() { + return Decision { outcome: false, is_cached: false }; + } + + // If possible, take an early decision based on the block style configuration. + match self.config.single_line_statement_blocks { + config::SingleLineBlockStyle::Preserve + if self.is_stmt_in_new_line(cond, then) + || self.is_multiline_block_stmt(then, true) => + { + return Decision { outcome: false, is_cached: false }; + } + config::SingleLineBlockStyle::Single if self.is_multiline_block_stmt(then, true) => { + return Decision { outcome: false, is_cached: false }; + } + config::SingleLineBlockStyle::Multi => { + return Decision { outcome: false, is_cached: false }; + } + _ => {} + }; + + // If no decision was made, estimate the length to be formatted. + // NOTE: conservative check -> worst-case scenario is formatting as multi-line block. + if !self.can_stmts_be_inlined(cond, then, els_opt) { + return Decision { outcome: false, is_cached: false }; + } + + // If the parent would fit, check all of its children. + if let Some(stmt) = els_opt { + if let ast::StmtKind::If(child_cond, child_then, child_els_opt) = &stmt.kind { + return self.is_single_line_block(child_cond, child_then, child_els_opt.as_ref()); + } else if self.is_multiline_block_stmt(stmt, true) { + return Decision { outcome: false, is_cached: false }; + } + } + + // If all children can also fit, allow single-line block. + Decision { outcome: true, is_cached: false } + } + + fn is_inline_stmt(&self, stmt: &'ast ast::Stmt<'ast>, cond_len: usize) -> bool { + if let ast::StmtKind::If(cond, then, els_opt) = &stmt.kind { + let if_span = cond.span.to(then.span); + if self.sm.is_multiline(if_span) + && matches!( + self.config.single_line_statement_blocks, + config::SingleLineBlockStyle::Preserve + ) + { + return false; + } + if cond_len + self.estimate_size(if_span) >= self.space_left() { + return false; + } + if let Some(els) = els_opt + && !self.is_inline_stmt(els, 6) + { + return false; + } + } else { + if matches!( + self.config.single_line_statement_blocks, + config::SingleLineBlockStyle::Preserve + ) && self.sm.is_multiline(stmt.span) + { + return false; + } + if cond_len + self.estimate_size(stmt.span) >= self.space_left() { + return false; + } + } + true + } + + /// Checks if a statement was explicitly written in a new line. + fn is_stmt_in_new_line( + &self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + ) -> bool { + let span_between = cond.span.between(then.span); + if let Ok(snip) = self.sm.span_to_snippet(span_between) { + // Check for newlines after the closing parenthesis of the `if (...)`. + if let Some((_, after_paren)) = snip.split_once(')') { + return after_paren.lines().count() > 1; + } + } + false + } + + /// Checks if a block statement `{ ... }` contains more than one line of actual code. + fn is_multiline_block_stmt( + &self, + stmt: &'ast ast::Stmt<'ast>, + empty_as_multiline: bool, + ) -> bool { + if let ast::StmtKind::Block(block) = &stmt.kind { + return self.is_multiline_block(block, empty_as_multiline); + } + false + } + + /// Checks if a block statement `{ ... }` should be treated as multiline, + /// either because it spans multiple lines or contains multiple statements. + fn is_multiline_block(&self, block: &'ast ast::Block<'ast>, empty_as_multiline: bool) -> bool { + if block.stmts.is_empty() { + return empty_as_multiline; + } + // A block with multiple statements should never be inlined, regardless of + // whether it was written on a single line in the source. + if block.stmts.len() > 1 { + return true; + } + if self.sm.is_multiline(block.span) + && let Ok(snip) = self.sm.span_to_snippet(block.span) + { + let code_lines = snip.lines().filter(|line| { + let trimmed = line.trim(); + // Ignore empty lines and lines with only '{' or '}' + if empty_as_multiline { + !trimmed.is_empty() && trimmed != "{" && trimmed != "}" + } else { + !trimmed.is_empty() + } + }); + return code_lines.count() > 1; + } + false + } + + /// Performs a size estimation to see if the if/else can fit on one line. + fn can_stmts_be_inlined( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, + ) -> bool { + let cond_len = self.estimate_size(cond.span); + + // If the condition fits in one line, 6 chars: 'if (' + {cond} + ') ' + {then} + // Otherwise chars: ') ' + {then} + let then_margin = if 6 + cond_len < self.space_left() { 6 + cond_len } else { 2 }; + + if !self.is_inline_stmt(then, then_margin) { + return false; + } + + // Always 6 chars for the else: 'else ' + els_opt.is_none_or(|els| self.is_inline_stmt(els, 6)) + } + + fn can_header_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool { + self.estimate_header_size(func) <= self.space_left() + } + + fn can_header_params_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool { + self.estimate_header_params_size(func) <= self.space_left() + } + + fn estimate_header_size(&mut self, func: &ast::ItemFunction<'_>) -> usize { + let ast::ItemFunction { kind: _, ref header, ref body, body_span: _ } = *func; + + // ' ' + visibility + let visibility = header.visibility.map_or(0, |v| self.estimate_size(v.span) + 1); + // ' ' + state mutability + let mutability = header.state_mutability.map_or(0, |sm| self.estimate_size(sm.span) + 1); + // ' ' + modifier + (' ' + modifier) + let m = header.modifiers.iter().fold(0, |len, m| len + self.estimate_size(m.span())); + let modifiers = if m != 0 { m + 1 } else { 0 }; + // ' ' + override + let override_ = header.override_.as_ref().map_or(0, |o| self.estimate_size(o.span) + 1); + // ' ' + virtual + let virtual_ = if header.virtual_.is_none() { 0 } else { 8 }; + // ' returns(' + var + (', ' + var) + ')' + let returns = header.returns.as_ref().map_or(0, |ret| { + ret.vars + .iter() + .fold(0, |len, p| if len != 0 { len + 2 } else { 10 } + self.estimate_size(p.span)) + }); + // ' {' or ';' + let end = if body.is_some() { 2 } else { 1 }; + + self.estimate_header_params_size(func) + + visibility + + mutability + + modifiers + + override_ + + virtual_ + + returns + + end + } + + fn estimate_header_params_size(&mut self, func: &ast::ItemFunction<'_>) -> usize { + let ast::ItemFunction { kind, ref header, body: _, body_span: _ } = *func; + + let kw = match kind { + ast::FunctionKind::Constructor => 11, // 'constructor' + ast::FunctionKind::Function => 9, // 'function ' + ast::FunctionKind::Modifier => 9, // 'modifier ' + ast::FunctionKind::Fallback => 8, // 'fallback' + ast::FunctionKind::Receive => 7, // 'receive' + }; + + // '(' + param + (', ' + param) + ')' + let params = header + .parameters + .vars + .iter() + .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span)); + + kw + header.name.map_or(0, |name| self.estimate_size(name.span)) + std::cmp::max(2, params) + } + + fn estimate_lhs_size(&self, expr: &ast::Expr<'_>, parent_op: &ast::BinOp) -> usize { + match &expr.kind { + ast::ExprKind::Binary(lhs, op, _) if op.kind.group() == parent_op.kind.group() => { + self.estimate_lhs_size(lhs, op) + } + _ => self.estimate_size(expr.span), + } + } + + fn has_comments_between_elements(&self, limits: Span, elements: I) -> bool + where + I: IntoIterator>, + { + let mut last_span_end = limits.lo(); + for expr in elements { + if self.has_comment_between(last_span_end, expr.span.lo()) { + return true; + } + last_span_end = expr.span.hi(); + } + + if self.has_comment_between(last_span_end, limits.hi()) { + return true; + } + + false + } +} + +// -- HELPERS (language-specific) ---------------------------------------------- + +#[derive(Debug)] +enum MemberOrCallArgs { + Member(usize), + CallArgs(usize, bool), +} + +impl MemberOrCallArgs { + const fn size(&self) -> usize { + match self { + Self::CallArgs(size, ..) | Self::Member(size) => *size, + } + } + + const fn member_size(&self) -> usize { + match self { + Self::CallArgs(..) => 0, + Self::Member(size) => *size, + } + } + + const fn has_comments(&self) -> bool { + matches!(self, Self::CallArgs(.., true)) + } +} + +#[derive(Debug, Clone)] +#[expect(dead_code)] +enum AttributeKind<'ast> { + Visibility(ast::Visibility), + StateMutability(ast::StateMutability), + Virtual, + Override(&'ast ast::Override<'ast>), + Modifier(&'ast ast::Modifier<'ast>), +} + +type AttributeCommentMap = HashMap, Vec, Vec)>; + +#[derive(Debug, Clone)] +struct AttributeInfo<'ast> { + kind: AttributeKind<'ast>, + span: Span, +} + +/// Helper struct to map attributes to their associated comments in function headers. +struct AttributeCommentMapper<'ast> { + limit_pos: BytePos, + comments: Vec, + attributes: Vec>, +} + +impl<'ast> AttributeCommentMapper<'ast> { + fn new(returns: Option<&'ast ast::ParameterList<'ast>>, body_pos: BytePos) -> Self { + Self { + comments: Vec::new(), + attributes: Vec::new(), + limit_pos: returns.as_ref().map_or(body_pos, |ret| ret.span.lo()), + } + } + + #[allow(clippy::type_complexity)] + fn build( + mut self, + state: &mut State<'_, 'ast>, + header: &'ast ast::FunctionHeader<'ast>, + ) -> (AttributeCommentMap, Vec>, BytePos) { + let first_attr = self.collect_attributes(header); + self.cache_comments(state); + (self.map(), self.attributes, first_attr) + } + + fn map(&mut self) -> AttributeCommentMap { + let mut map = HashMap::new(); + for a in 0..self.attributes.len() { + let is_last = a == self.attributes.len() - 1; + let (mut before, mut inner, mut after) = (Vec::new(), Vec::new(), Vec::new()); + + let before_limit = self.attributes[a].span.lo(); + let inner_limit = self.attributes[a].span.hi(); + let after_limit = + if is_last { self.limit_pos } else { self.attributes[a + 1].span.lo() }; + + let mut c = 0; + while c < self.comments.len() { + if self.comments[c].pos() <= before_limit { + before.push(self.comments.remove(c)); + } else if self.comments[c].pos() <= inner_limit { + inner.push(self.comments.remove(c)); + } else if (after.is_empty() || is_last) && self.comments[c].pos() <= after_limit { + after.push(self.comments.remove(c)); + } else { + c += 1; + } + } + map.insert(before_limit, (before, inner, after)); + } + map + } + + fn collect_attributes(&mut self, header: &'ast ast::FunctionHeader<'ast>) -> BytePos { + let mut first_pos = BytePos(u32::MAX); + if let Some(v) = header.visibility { + if v.span.lo() < first_pos { + first_pos = v.span.lo() + } + self.attributes + .push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span }); + } + if let Some(sm) = header.state_mutability { + if sm.span.lo() < first_pos { + first_pos = sm.span.lo() + } + self.attributes + .push(AttributeInfo { kind: AttributeKind::StateMutability(*sm), span: sm.span }); + } + if let Some(span) = header.virtual_ { + if span.lo() < first_pos { + first_pos = span.lo() + } + self.attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span }); + } + if let Some(ref o) = header.override_ { + if o.span.lo() < first_pos { + first_pos = o.span.lo() + } + self.attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span }); + } + for m in header.modifiers.iter() { + if m.span().lo() < first_pos { + first_pos = m.span().lo() + } + self.attributes + .push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() }); + } + self.attributes.sort_by_key(|attr| attr.span.lo()); + first_pos + } + + fn cache_comments(&mut self, state: &mut State<'_, 'ast>) { + let mut pending = None; + for cmnt in state.comments.iter() { + if cmnt.pos() >= self.limit_pos { + break; + } + match pending { + Some(ref p) => pending = Some(p + 1), + None => pending = Some(0), + } + } + while let Some(p) = pending { + if p == 0 { + pending = None; + } else { + pending = Some(p - 1); + } + let cmnt = state.next_comment().unwrap(); + if cmnt.style.is_blank() { + continue; + } + self.comments.push(cmnt); + } + } +} + +const fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool { + match stmt { + ast::StmtKind::Assembly { .. } + | ast::StmtKind::Block { .. } + | ast::StmtKind::For { .. } + | ast::StmtKind::If { .. } + | ast::StmtKind::Try { .. } + | ast::StmtKind::UncheckedBlock { .. } + | ast::StmtKind::While { .. } => false, + + ast::StmtKind::DeclSingle { .. } + | ast::StmtKind::DeclMulti { .. } + | ast::StmtKind::Break { .. } + | ast::StmtKind::Continue { .. } + | ast::StmtKind::DoWhile { .. } + | ast::StmtKind::Emit { .. } + | ast::StmtKind::Expr { .. } + | ast::StmtKind::Return { .. } + | ast::StmtKind::Revert { .. } + | ast::StmtKind::Placeholder { .. } => true, + } +} + +/// Returns `true` if the item needs an isolated line break. +fn item_needs_iso(item: &ast::ItemKind<'_>) -> bool { + match item { + ast::ItemKind::Pragma(..) + | ast::ItemKind::Import(..) + | ast::ItemKind::Using(..) + | ast::ItemKind::Variable(..) + | ast::ItemKind::Udvt(..) + | ast::ItemKind::Enum(..) + | ast::ItemKind::Error(..) + | ast::ItemKind::Event(..) => false, + + ast::ItemKind::Contract(..) => true, + + ast::ItemKind::Struct(strukt) => !strukt.fields.is_empty(), + ast::ItemKind::Function(func) => { + func.body.as_ref().is_some_and(|b| !b.is_empty()) + && !matches!(func.kind, ast::FunctionKind::Modifier) + } + } +} + +const fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool { + matches!(expr_kind, ast::ExprKind::Binary(..)) +} + +fn has_complex_successor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool { + match expr_kind { + ast::ExprKind::Binary(lhs, _, rhs) => { + if left { + has_complex_successor(&lhs.kind, left) + } else { + has_complex_successor(&rhs.kind, left) + } + } + ast::ExprKind::Unary(_, expr) => has_complex_successor(&expr.kind, left), + ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false, + ast::ExprKind::Tuple(..) => false, + _ => true, + } +} + +const fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool { + matches!(expr_kind, ast::ExprKind::Call(..)) +} + +/// Returns true if this is a call with named arguments (struct-style syntax). +/// Used to determine if `.field` after such a call should avoid breaking. +/// E.g., `_lzSend({_dstEid: x, ...}).guid` → true (named args call) +/// E.g., `someFunc(a, b).field` → false (positional args) +const fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool { + if let ast::ExprKind::Call(_, args) = expr_kind { + matches!(args.kind, ast::CallArgsKind::Named(_)) + } else { + false + } +} + +fn is_call_chain(expr_kind: &ast::ExprKind<'_>, must_have_child: bool) -> bool { + if let ast::ExprKind::Member(child, ..) = expr_kind { + is_call_chain(&child.kind, false) + } else { + !must_have_child && is_call(expr_kind) + } +} + +fn is_call_with_opts_and_args(expr_kind: &ast::ExprKind<'_>) -> bool { + if let ast::ExprKind::Call(call_expr, call_args) = expr_kind { + matches!(call_expr.kind, ast::ExprKind::CallOptions(..)) && !call_args.is_empty() + } else { + false + } +} + +#[derive(Debug)] +struct Decision { + outcome: bool, + is_cached: bool, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum BinOpGroup { + Arithmetic, + Bitwise, + Comparison, + Logical, +} + +trait BinOpExt { + fn group(&self) -> BinOpGroup; +} + +impl BinOpExt for ast::BinOpKind { + fn group(&self) -> BinOpGroup { + match self { + Self::Or | Self::And => BinOpGroup::Logical, + Self::Eq | Self::Ne | Self::Lt | Self::Le | Self::Gt | Self::Ge => { + BinOpGroup::Comparison + } + Self::BitOr | Self::BitXor | Self::BitAnd | Self::Shl | Self::Shr | Self::Sar => { + BinOpGroup::Bitwise + } + Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Rem | Self::Pow => { + BinOpGroup::Arithmetic + } + } + } +} + +/// Calculates the size the callee's "head," excluding its arguments. +/// +/// # Examples +/// +/// - `myFunction(..)`: 8 (length of `myFunction`) +/// - `uint256(..)`: 7 (length of `uint256`) +/// - `abi.encode(..)`: 10 (length of `abi.encode`) +/// - `foo(..).bar(..)`: 3 (length of `foo`) +pub(super) fn get_callee_head_size(callee: &ast::Expr<'_>) -> usize { + match &callee.kind { + ast::ExprKind::Ident(id) => id.as_str().len(), + ast::ExprKind::Type(ast::Type { kind: ast::TypeKind::Elementary(ty), .. }) => { + ty.to_abi_str().len() + } + ast::ExprKind::Index(base, idx) => { + let idx_len = match idx { + ast::IndexKind::Index(expr) => expr.as_ref().map_or(0, |e| get_callee_head_size(e)), + ast::IndexKind::Range(e1, e2) => { + 1 + e1.as_ref().map_or(0, |e| get_callee_head_size(e)) + + e2.as_ref().map_or(0, |e| get_callee_head_size(e)) + } + }; + get_callee_head_size(base) + 2 + idx_len + } + ast::ExprKind::Member(base, member_ident) => { + match &base.kind { + ast::ExprKind::Ident(..) | ast::ExprKind::Type(..) => { + get_callee_head_size(base) + 1 + member_ident.as_str().len() + } + + // Chainned calls are not traversed, and instead just the member identifier is used + ast::ExprKind::Member(child, ..) + if !matches!(&child.kind, ast::ExprKind::Call(..)) => + { + get_callee_head_size(base) + 1 + member_ident.as_str().len() + } + _ => member_ident.as_str().len(), + } + } + ast::ExprKind::Binary(lhs, _, _) => get_callee_head_size(lhs), + + // If the callee is not an identifier or member access, it has no "head" + _ => 0, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{FormatterConfig, InlineConfig}; + use foundry_common::comments::Comments; + use solar::{ + interface::{Session, source_map::FileName}, + sema::Compiler, + }; + use std::sync::Arc; + + /// This helper extracts function headers from the AST and passes them to the test function. + fn parse_and_test(source: &str, test_fn: F) + where + F: FnOnce(&mut State<'_, '_>, &ast::ItemFunction<'_>) + Send, + { + let session = Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(session); + + compiler + .enter_mut(|c| -> solar::interface::Result<()> { + let mut pcx = c.parse(); + pcx.set_resolve_imports(false); + + // Create a source file using stdin as the filename + let file = c + .sess() + .source_map() + .new_source_file(FileName::Stdin, source) + .map_err(|e| c.sess().dcx.err(e.to_string()).emit())?; + + pcx.add_file(file.clone()); + pcx.parse(); + c.dcx().has_errors()?; + + // Get AST from parsed source and setup the formatter + let gcx = c.gcx(); + let (_, source_obj) = gcx.get_ast_source(&file.name).expect("Failed to get AST"); + let ast = source_obj.ast.as_ref().expect("No AST found"); + let comments = + Comments::new(&source_obj.file, gcx.sess.source_map(), true, false, None); + let config = Arc::new(FormatterConfig::default()); + let inline_config = InlineConfig::default(); + let mut state = State::new(gcx.sess.source_map(), config, inline_config, comments); + + // Extract the first function header (either top-level or inside a contract) + let func = ast + .items + .iter() + .find_map(|item| match &item.kind { + ast::ItemKind::Function(func) => Some(func), + ast::ItemKind::Contract(contract) => { + contract.body.iter().find_map(|contract_item| { + match &contract_item.kind { + ast::ItemKind::Function(func) => Some(func), + _ => None, + } + }) + } + _ => None, + }) + .expect("No function found in source"); + + // Run the closure + test_fn(&mut state, func); + + Ok(()) + }) + .expect("Test failed"); + } + + #[test] + fn test_estimate_header_sizes() { + let test_cases = [ + ("function foo();", 14, 15), + ("function foo() {}", 14, 16), + ("function foo() public {}", 14, 23), + ("function foo(uint256 a) public {}", 23, 32), + ("function foo(uint256 a, address b, bool c) public {}", 42, 51), + ("function foo() public pure {}", 14, 28), + ("function foo() public virtual {}", 14, 31), + ("function foo() public override {}", 14, 32), + ("function foo() public onlyOwner {}", 14, 33), + ("function foo() public returns(uint256) {}", 14, 40), + ("function foo() public returns(uint256, address) {}", 14, 49), + ("function foo(uint256 a) public virtual override returns(uint256) {}", 23, 66), + ("function foo() external payable {}", 14, 33), + // other function types + ("contract C { constructor() {} }", 13, 15), + ("contract C { constructor(uint256 a) {} }", 22, 24), + ("contract C { modifier onlyOwner() {} }", 20, 22), + ("contract C { modifier onlyRole(bytes32 role) {} }", 31, 33), + ("contract C { fallback() external payable {} }", 10, 29), + ("contract C { receive() external payable {} }", 9, 28), + ]; + + for (source, expected_params, expected_header) in &test_cases { + parse_and_test(source, |state, func| { + let params_size = state.estimate_header_params_size(func); + assert_eq!( + params_size, *expected_params, + "Failed params size: expected {expected_params}, got {params_size} for source: {source}", + ); + + let header_size = state.estimate_header_size(func); + assert_eq!( + header_size, *expected_header, + "Failed header size: expected {expected_header}, got {header_size} for source: {source}", + ); + }); + } + } +} diff --git a/crates/fmt/src/state/yul.rs b/crates/fmt/src/state/yul.rs new file mode 100644 index 0000000000000..b67fdd5a26263 --- /dev/null +++ b/crates/fmt/src/state/yul.rs @@ -0,0 +1,317 @@ +#![allow(clippy::too_many_arguments)] + +use super::{ + CommentConfig, State, + common::{BlockFormat, ListFormat}, +}; +use solar::parse::ast::{self, Span, yul}; + +#[rustfmt::skip] +macro_rules! get_span { + () => { |value| value.span }; + (()) => { |value| value.span() }; +} + +/// Language-specific pretty printing: Yul. +impl<'ast> State<'_, 'ast> { + fn print_lit_yul(&mut self, lit: &'ast ast::Lit<'ast>) { + self.print_lit_inner(lit, true); + } + + pub(crate) fn print_yul_stmt(&mut self, stmt: &'ast yul::Stmt<'ast>) { + let yul::Stmt { ref docs, span, ref kind } = *stmt; + self.print_docs(docs); + if self.handle_span(span, false) { + return; + } + + match kind { + yul::StmtKind::Block(stmts) => self.print_yul_block(stmts, span, false, 0), + yul::StmtKind::AssignSingle(path, expr) => { + self.print_path(path, false); + self.word(" := "); + self.neverbreak(); + self.cursor.advance_to(expr.span.lo(), self.cursor.enabled); + self.print_yul_expr(expr); + } + yul::StmtKind::AssignMulti(paths, expr_call) => { + self.ibox(0); + self.commasep( + paths, + stmt.span.lo(), + stmt.span.hi(), + |this, path| this.print_path(path, false), + get_span!(()), + ListFormat::consistent(), + ); + self.word(" :="); + self.space(); + self.s.offset(self.ind); + self.ibox(0); + self.print_yul_expr(expr_call); + self.end(); + self.end(); + } + yul::StmtKind::Expr(expr_call) => self.print_yul_expr(expr_call), + yul::StmtKind::If(expr, stmts) => { + self.print_word("if "); // 3 chars + self.print_yul_expr(expr); + self.nbsp(); // 1 char + self.print_yul_block(stmts, span, false, 4 + self.estimate_size(expr.span)); + } + yul::StmtKind::For(yul::StmtFor { init, cond, step, body }) => { + self.ibox(0); + + self.print_word("for "); // 4 chars + self.print_yul_block(init, init.span, false, 4); + + self.space(); + self.print_yul_expr(cond); + + self.space(); + self.print_yul_block(step, step.span, false, 0); + + self.space(); + self.print_yul_block(body, body.span, false, 0); + + self.end(); + } + yul::StmtKind::Switch(yul::StmtSwitch { selector, cases }) => { + self.print_word("switch "); + self.print_yul_expr(selector); + + self.print_trailing_comment(selector.span.hi(), None); + + for yul::StmtSwitchCase { constant, body, span } in cases.iter() { + self.hardbreak_if_not_bol(); + if let Some(constant) = constant { + self.print_comments( + constant.span.lo(), + CommentConfig::default().mixed_prev_space(), + ); + self.print_word("case "); + self.print_lit_yul(constant); + self.nbsp(); + } else { + self.print_comments( + body.span.lo(), + CommentConfig::default().mixed_prev_space(), + ); + self.print_word("default "); + } + self.print_yul_block(body, *span, false, 0); + + self.print_trailing_comment(selector.span.hi(), None); + } + } + yul::StmtKind::Leave => self.print_word("leave"), + yul::StmtKind::Break => self.print_word("break"), + yul::StmtKind::Continue => self.print_word("continue"), + yul::StmtKind::FunctionDef(func) => { + let yul::Function { name, parameters, returns, body } = func; + let params_hi = parameters + .last() + .map_or(returns.first().map_or(body.span.lo(), |r| r.span.lo()), |p| { + p.span.hi() + }); + + self.cbox(0); + self.s.ibox(0); + self.print_word("function "); + self.print_ident(name); + self.print_tuple( + parameters, + span.lo(), + params_hi, + Self::print_ident, + get_span!(), + ListFormat::consistent(), + ); + self.nbsp(); + let has_returns = !returns.is_empty(); + let skip_opening_brace = has_returns; + if self.can_yul_header_params_be_inlined(func) { + self.neverbreak(); + } + if has_returns { + self.commasep( + returns, + returns.first().map_or(params_hi, |ret| ret.span.lo()), + returns.last().map_or(body.span.lo(), |ret| ret.span.hi()), + Self::print_ident, + get_span!(), + ListFormat::yul(Some("->"), Some("{")), + ); + } + self.end(); + self.print_yul_block(body, span, skip_opening_brace, 0); + self.end(); + } + yul::StmtKind::VarDecl(idents, expr) => { + self.s.ibox(self.ind); + self.print_word("let "); + self.commasep( + idents, + stmt.span.lo(), + idents.last().map_or(stmt.span.lo(), |i| i.span.hi()), + Self::print_ident, + get_span!(), + ListFormat::consistent(), + ); + if let Some(expr) = expr { + self.print_word(" :="); + self.space(); + self.print_yul_expr(expr); + } + self.end(); + } + } + } + + fn print_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) { + let yul::Expr { span, ref kind } = *expr; + if self.handle_span(span, false) { + return; + } + + match kind { + yul::ExprKind::Path(path) => self.print_path(path, false), + yul::ExprKind::Call(yul::ExprCall { name, arguments }) => { + self.print_ident(name); + self.print_tuple( + arguments, + span.lo(), + span.hi(), + |s, arg| s.print_yul_expr(arg), + get_span!(), + ListFormat::consistent().break_single(true), + ); + } + yul::ExprKind::Lit(lit) => { + if matches!(&lit.kind, ast::LitKind::Address(_)) { + self.print_span_cold(lit.span); + } else { + self.print_lit_yul(lit); + } + } + } + } + + pub(super) fn print_yul_block( + &mut self, + block: &'ast yul::Block<'ast>, + span: Span, + skip_opening_brace: bool, + prefix_len: usize, + ) { + if self.handle_span(span, false) { + return; + } + + if !skip_opening_brace { + self.print_word("{"); + } + + let can_inline_block = if block.len() <= 1 && !self.is_multiline_yul_block(block) { + if self.max_space_left(prefix_len) == 0 { + self.estimate_size(block.span) + self.config.tab_width < self.space_left() + } else { + self.estimate_size(block.span) + prefix_len < self.space_left() + } + } else { + false + }; + if can_inline_block { + self.neverbreak(); + self.print_block_inner( + block, + BlockFormat::NoBraces(None), + |s, stmt| { + s.nbsp(); + s.print_yul_stmt(stmt); + if s.peek_comment_before(stmt.span.hi()).is_none() + && s.peek_trailing_comment(stmt.span.hi(), None).is_none() + { + s.nbsp(); + } + s.print_comments( + stmt.span.hi(), + CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(), + ); + if !s.last_token_is_space() { + s.nbsp(); + } + }, + |b| b.span, + span.hi(), + ); + } else { + let (mut i, n_args) = (0, block.len().saturating_sub(1)); + self.print_block_inner( + block, + BlockFormat::NoBraces(Some(self.ind)), + |s, stmt| { + s.print_yul_stmt(stmt); + s.print_comments(stmt.span.hi(), CommentConfig::default()); + if i == n_args { + s.print_trailing_comment(stmt.span.hi(), Some(span.hi())); + } else { + let next_span = block[i + 1].span; + s.print_trailing_comment(stmt.span.hi(), Some(next_span.lo())); + if !s.is_bol_or_only_ind() && !s.inline_config.is_disabled(stmt.span) { + // when disabling a single line, manually add a nonbreaking line jump so + // that the indentation of the disabled line is maintained. + if s.inline_config.is_disabled(next_span) + && s.peek_comment_before(next_span.lo()) + .is_none_or(|cmnt| !cmnt.style.is_isolated()) + { + s.word("\n"); + // otherwise, use a regular hardbreak + } else { + s.hardbreak_if_not_bol(); + } + } + i += 1; + } + }, + |b| b.span, + span.hi(), + ); + } + self.print_word("}"); + self.print_trailing_comment(span.hi(), None); + } + + /// Checks if a block statement `{ ... }` contains more than one line of actual code. + fn is_multiline_yul_block(&self, block: &'ast yul::Block<'ast>) -> bool { + if block.stmts.is_empty() { + return false; + } + if self.sm.is_multiline(block.span) + && let Ok(snip) = self.sm.span_to_snippet(block.span) + { + let code_lines = snip.lines().filter(|line| { + let trimmed = line.trim(); + // Ignore empty lines and lines with only '{' or '}' + !trimmed.is_empty() + }); + return code_lines.count() > 1; + } + false + } + + fn estimate_yul_header_params_size(&mut self, func: &yul::Function<'_>) -> usize { + // '(' + param + (', ' + param) + ')' + let params = func + .parameters + .iter() + .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span)); + + // 'function ' + name + ' ' + params + ' ->' + 9 + self.estimate_size(func.name.span) + 1 + params + 3 + } + + fn can_yul_header_params_be_inlined(&mut self, func: &yul::Function<'_>) -> bool { + self.estimate_yul_header_params_size(func) <= self.space_left() + } +} diff --git a/crates/fmt/src/string.rs b/crates/fmt/src/string.rs deleted file mode 100644 index ae570a39b827a..0000000000000 --- a/crates/fmt/src/string.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Helpers for dealing with quoted strings - -/// The state of a character in a string with quotable components -/// This is a simplified version of the -/// [actual parser](https://docs.soliditylang.org/en/v0.8.15/grammar.html#a4.SolidityLexer.EscapeSequence) -/// as we don't care about hex or other character meanings -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum QuoteState { - /// Not currently in quoted string - #[default] - None, - /// The opening character of a quoted string - Opening(char), - /// A character in a quoted string - String(char), - /// The `\` in an escape sequence `"\n"` - Escaping(char), - /// The escaped character e.g. `n` in `"\n"` - Escaped(char), - /// The closing character - Closing(char), -} - -/// An iterator over characters and indices in a string slice with information about quoted string -/// states -pub struct QuoteStateCharIndices<'a> { - iter: std::str::CharIndices<'a>, - state: QuoteState, -} - -impl<'a> QuoteStateCharIndices<'a> { - fn new(string: &'a str) -> Self { - Self { iter: string.char_indices(), state: QuoteState::None } - } - pub fn with_state(mut self, state: QuoteState) -> Self { - self.state = state; - self - } -} - -impl Iterator for QuoteStateCharIndices<'_> { - type Item = (QuoteState, usize, char); - fn next(&mut self) -> Option { - let (idx, ch) = self.iter.next()?; - match self.state { - QuoteState::None | QuoteState::Closing(_) => { - if ch == '\'' || ch == '"' { - self.state = QuoteState::Opening(ch); - } else { - self.state = QuoteState::None - } - } - QuoteState::String(quote) | QuoteState::Opening(quote) | QuoteState::Escaped(quote) => { - if ch == quote { - self.state = QuoteState::Closing(quote) - } else if ch == '\\' { - self.state = QuoteState::Escaping(quote) - } else { - self.state = QuoteState::String(quote) - } - } - QuoteState::Escaping(quote) => self.state = QuoteState::Escaped(quote), - } - Some((self.state, idx, ch)) - } -} - -/// An iterator over the indices of quoted string locations -pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); - -impl QuotedRanges<'_> { - pub fn with_state(mut self, state: QuoteState) -> Self { - self.0 = self.0.with_state(state); - self - } -} - -impl Iterator for QuotedRanges<'_> { - type Item = (char, usize, usize); - fn next(&mut self) -> Option { - let (quote, start) = loop { - let (state, idx, _) = self.0.next()?; - match state { - QuoteState::Opening(quote) | - QuoteState::Escaping(quote) | - QuoteState::Escaped(quote) | - QuoteState::String(quote) => break (quote, idx), - QuoteState::Closing(quote) => return Some((quote, idx, idx)), - QuoteState::None => {} - } - }; - for (state, idx, _) in self.0.by_ref() { - if matches!(state, QuoteState::Closing(_)) { - return Some((quote, start, idx)) - } - } - None - } -} - -/// Helpers for iterating over quoted strings -pub trait QuotedStringExt { - /// Returns an iterator of characters, indices and their quoted string state. - fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_>; - - /// Returns an iterator of quoted string ranges. - fn quoted_ranges(&self) -> QuotedRanges<'_> { - QuotedRanges(self.quote_state_char_indices()) - } - - /// Check to see if a string is quoted. This will return true if the first character - /// is a quote and the last character is a quote with no non-quoted sections in between. - fn is_quoted(&self) -> bool { - let mut iter = self.quote_state_char_indices(); - if !matches!(iter.next(), Some((QuoteState::Opening(_), _, _))) { - return false - } - while let Some((state, _, _)) = iter.next() { - if matches!(state, QuoteState::Closing(_)) { - return iter.next().is_none() - } - } - false - } -} - -impl QuotedStringExt for T -where - T: AsRef, -{ - fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { - QuoteStateCharIndices::new(self.as_ref()) - } -} - -impl QuotedStringExt for str { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { - QuoteStateCharIndices::new(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use similar_asserts::assert_eq; - - #[test] - fn quote_state_char_indices() { - assert_eq!( - r#"a'a"\'\"\n\\'a"#.quote_state_char_indices().collect::>(), - vec![ - (QuoteState::None, 0, 'a'), - (QuoteState::Opening('\''), 1, '\''), - (QuoteState::String('\''), 2, 'a'), - (QuoteState::String('\''), 3, '"'), - (QuoteState::Escaping('\''), 4, '\\'), - (QuoteState::Escaped('\''), 5, '\''), - (QuoteState::Escaping('\''), 6, '\\'), - (QuoteState::Escaped('\''), 7, '"'), - (QuoteState::Escaping('\''), 8, '\\'), - (QuoteState::Escaped('\''), 9, 'n'), - (QuoteState::Escaping('\''), 10, '\\'), - (QuoteState::Escaped('\''), 11, '\\'), - (QuoteState::Closing('\''), 12, '\''), - (QuoteState::None, 13, 'a'), - ] - ); - } - - #[test] - fn quoted_ranges() { - let string = r#"testing "double quoted" and 'single quoted' strings"#; - assert_eq!( - string - .quoted_ranges() - .map(|(quote, start, end)| (quote, &string[start..=end])) - .collect::>(), - vec![('"', r#""double quoted""#), ('\'', "'single quoted'")] - ); - } -} diff --git a/crates/fmt/src/visit.rs b/crates/fmt/src/visit.rs deleted file mode 100644 index b0d93ce0e84c6..0000000000000 --- a/crates/fmt/src/visit.rs +++ /dev/null @@ -1,637 +0,0 @@ -//! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). - -use crate::solang_ext::{pt::*, CodeLocationExt}; - -/// A trait that is invoked while traversing the Solidity Parse Tree. -/// Each method of the [Visitor] trait is a hook that can be potentially overridden. -/// -/// Currently the main implementer of this trait is the [`Formatter`](crate::Formatter<'_>) struct. -pub trait Visitor { - type Error: std::error::Error; - - fn visit_source(&mut self, _loc: Loc) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_source_unit(&mut self, _source_unit: &mut SourceUnit) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_contract(&mut self, _contract: &mut ContractDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<(), Self::Error> { - self.visit_source(annotation.loc) - } - - fn visit_pragma(&mut self, pragma: &mut PragmaDirective) -> Result<(), Self::Error> { - self.visit_source(pragma.loc()) - } - - fn visit_import_plain( - &mut self, - _loc: Loc, - _import: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_global( - &mut self, - _loc: Loc, - _global: &mut ImportPath, - _alias: &mut Identifier, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_import_renames( - &mut self, - _loc: Loc, - _imports: &mut [(Identifier, Option)], - _from: &mut ImportPath, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_enum(&mut self, _enum: &mut EnumDefinition) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_assembly( - &mut self, - loc: Loc, - _dialect: &mut Option, - _block: &mut YulBlock, - _flags: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_block( - &mut self, - loc: Loc, - _unchecked: bool, - _statements: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_args(&mut self, loc: Loc, _args: &mut Vec) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - /// Don't write semicolon at the end because expressions can appear as both - /// part of other node and a statement in the function body - fn visit_expr(&mut self, loc: Loc, _expr: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident(&mut self, loc: Loc, _ident: &mut Identifier) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> { - self.visit_source(idents.loc) - } - - fn visit_emit(&mut self, loc: Loc, _event: &mut Expression) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { - self.visit_source(var.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - _declaration: &mut VariableDeclaration, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon() - } - - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { - self.visit_source(var.loc) - } - - fn visit_return( - &mut self, - loc: Loc, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_revert_named_args( - &mut self, - loc: Loc, - _error: &mut Option, - _args: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_break(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_continue(&mut self, loc: Loc, _semicolon: bool) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - #[expect(clippy::type_complexity)] - fn visit_try( - &mut self, - loc: Loc, - _expr: &mut Expression, - _returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - _clauses: &mut Vec, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_if( - &mut self, - loc: Loc, - _cond: &mut Expression, - _if_branch: &mut Box, - _else_branch: &mut Option>, - _is_first_stmt: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_do_while( - &mut self, - loc: Loc, - _body: &mut Statement, - _cond: &mut Expression, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_while( - &mut self, - loc: Loc, - _cond: &mut Expression, - _body: &mut Statement, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_for( - &mut self, - loc: Loc, - _init: &mut Option>, - _cond: &mut Option>, - _update: &mut Option>, - _body: &mut Option>, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(func.loc())?; - if func.body.is_none() { - self.visit_stray_semicolon()?; - } - - Ok(()) - } - - fn visit_function_attribute( - &mut self, - attribute: &mut FunctionAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_var_attribute( - &mut self, - attribute: &mut VariableAttribute, - ) -> Result<(), Self::Error> { - self.visit_source(attribute.loc())?; - Ok(()) - } - - fn visit_base(&mut self, base: &mut Base) -> Result<(), Self::Error> { - self.visit_source(base.loc) - } - - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { - self.visit_source(parameter.loc) - } - - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<(), Self::Error> { - self.visit_source(structure.loc)?; - - Ok(()) - } - - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<(), Self::Error> { - self.visit_source(event.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<(), Self::Error> { - self.visit_source(error.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<(), Self::Error> { - self.visit_source(param.loc) - } - - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { - self.visit_source(def.loc) - } - - fn visit_stray_semicolon(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_opening_paren(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_closing_paren(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_newline(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn visit_using(&mut self, using: &mut Using) -> Result<(), Self::Error> { - self.visit_source(using.loc)?; - self.visit_stray_semicolon()?; - - Ok(()) - } - - fn visit_yul_block( - &mut self, - loc: Loc, - _stmts: &mut Vec, - _attempt_single_line: bool, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { - self.visit_source(expr.loc()) - } - - fn visit_yul_assignment( - &mut self, - loc: Loc, - _exprs: &mut Vec, - _expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocationExt, - { - self.visit_source(loc) - } - - fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_if( - &mut self, - loc: Loc, - _expr: &mut YulExpression, - _block: &mut YulBlock, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> { - self.visit_source(stmt.loc) - } - - fn visit_yul_var_declaration( - &mut self, - loc: Loc, - _idents: &mut Vec, - _expr: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc) - } - - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - self.visit_source(ident.loc) - } - - fn visit_parser_error(&mut self, loc: Loc) -> Result<(), Self::Error> { - self.visit_source(loc) - } -} - -/// Visitable trait for [`solang_parser::pt`] types. -/// -/// All [`solang_parser::pt`] types, such as [Statement], should implement the [Visitable] trait -/// that accepts a trait [Visitor] implementation, which has various callback handles for Solidity -/// Parse Tree nodes. -/// -/// We want to take a `&mut self` to be able to implement some advanced features in the future such -/// as modifying the Parse Tree before formatting it. -pub trait Visitable { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor; -} - -impl Visitable for &mut T -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Option -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - if let Some(inner) = self.as_mut() { - inner.visit(v) - } else { - Ok(()) - } - } -} - -impl Visitable for Box -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - T::visit(self, v) - } -} - -impl Visitable for Vec -where - T: Visitable, -{ - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - for item in self.iter_mut() { - item.visit(v)?; - } - Ok(()) - } -} - -impl Visitable for SourceUnitPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::ContractDefinition(contract) => v.visit_contract(contract), - Self::PragmaDirective(pragma) => v.visit_pragma(pragma), - Self::ImportDirective(import) => import.visit(v), - Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), - Self::StructDefinition(structure) => v.visit_struct(structure), - Self::EventDefinition(event) => v.visit_event(event), - Self::ErrorDefinition(error) => v.visit_error(error), - Self::FunctionDefinition(function) => v.visit_function(function), - Self::VariableDefinition(variable) => v.visit_var_definition(variable), - Self::TypeDefinition(def) => v.visit_type_definition(def), - Self::StraySemicolon(_) => v.visit_stray_semicolon(), - Self::Using(using) => v.visit_using(using), - Self::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Import { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Plain(import, loc) => v.visit_import_plain(*loc, import), - Self::GlobalSymbol(global, import_as, loc) => { - v.visit_import_global(*loc, global, import_as) - } - Self::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), - } - } -} - -impl Visitable for ContractPart { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::StructDefinition(structure) => v.visit_struct(structure), - Self::EventDefinition(event) => v.visit_event(event), - Self::ErrorDefinition(error) => v.visit_error(error), - Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), - Self::VariableDefinition(variable) => v.visit_var_definition(variable), - Self::FunctionDefinition(function) => v.visit_function(function), - Self::TypeDefinition(def) => v.visit_type_definition(def), - Self::StraySemicolon(_) => v.visit_stray_semicolon(), - Self::Using(using) => v.visit_using(using), - Self::Annotation(annotation) => v.visit_annotation(annotation), - } - } -} - -impl Visitable for Statement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Block { loc, unchecked, statements } => { - v.visit_block(*loc, *unchecked, statements) - } - Self::Assembly { loc, dialect, block, flags } => { - v.visit_assembly(*loc, dialect, block, flags) - } - Self::Args(loc, args) => v.visit_args(*loc, args), - Self::If(loc, cond, if_branch, else_branch) => { - v.visit_if(*loc, cond, if_branch, else_branch, true) - } - Self::While(loc, cond, body) => v.visit_while(*loc, cond, body), - Self::Expression(loc, expr) => { - v.visit_expr(*loc, expr)?; - v.visit_stray_semicolon() - } - Self::VariableDefinition(loc, declaration, expr) => { - v.visit_var_definition_stmt(*loc, declaration, expr) - } - Self::For(loc, init, cond, update, body) => v.visit_for(*loc, init, cond, update, body), - Self::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), - Self::Continue(loc) => v.visit_continue(*loc, true), - Self::Break(loc) => v.visit_break(*loc, true), - Self::Return(loc, expr) => v.visit_return(*loc, expr), - Self::Revert(loc, error, args) => v.visit_revert(*loc, error, args), - Self::RevertNamedArgs(loc, error, args) => v.visit_revert_named_args(*loc, error, args), - Self::Emit(loc, event) => v.visit_emit(*loc, event), - Self::Try(loc, expr, returns, clauses) => v.visit_try(*loc, expr, returns, clauses), - Self::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -impl Visitable for Loc { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_source(*self) - } -} - -impl Visitable for Expression { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_expr(self.loc(), self) - } -} - -impl Visitable for Identifier { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_ident(self.loc, self) - } -} - -impl Visitable for VariableDeclaration { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_var_declaration(self) - } -} - -impl Visitable for YulBlock { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.visit_yul_block(self.loc, self.statements.as_mut(), false) - } -} - -impl Visitable for YulStatement { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - match self { - Self::Assign(loc, exprs, expr) => v.visit_yul_assignment(*loc, exprs, &mut Some(expr)), - Self::Block(block) => v.visit_yul_block(block.loc, block.statements.as_mut(), false), - Self::Break(loc) => v.visit_break(*loc, false), - Self::Continue(loc) => v.visit_continue(*loc, false), - Self::For(stmt) => v.visit_yul_for(stmt), - Self::FunctionCall(stmt) => v.visit_yul_function_call(stmt), - Self::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), - Self::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), - Self::Leave(loc) => v.visit_yul_leave(*loc), - Self::Switch(stmt) => v.visit_yul_switch(stmt), - Self::VariableDeclaration(loc, idents, expr) => { - v.visit_yul_var_declaration(*loc, idents, expr) - } - Self::Error(loc) => v.visit_parser_error(*loc), - } - } -} - -macro_rules! impl_visitable { - ($type:ty, $func:ident) => { - impl Visitable for $type { - fn visit(&mut self, v: &mut V) -> Result<(), V::Error> - where - V: Visitor, - { - v.$func(self) - } - } - }; -} - -impl_visitable!(SourceUnit, visit_source_unit); -impl_visitable!(FunctionAttribute, visit_function_attribute); -impl_visitable!(VariableAttribute, visit_var_attribute); -impl_visitable!(Parameter, visit_parameter); -impl_visitable!(Base, visit_base); -impl_visitable!(EventParameter, visit_event_parameter); -impl_visitable!(ErrorParameter, visit_error_parameter); -impl_visitable!(IdentifierPath, visit_ident_path); -impl_visitable!(YulExpression, visit_yul_expr); -impl_visitable!(YulTypedIdentifier, visit_yul_typed_ident); diff --git a/crates/fmt/testdata/ArrayExpressions/fmt.sol b/crates/fmt/testdata/ArrayExpressions/fmt.sol index adda7a30e098d..ccb73a3166881 100644 --- a/crates/fmt/testdata/ArrayExpressions/fmt.sol +++ b/crates/fmt/testdata/ArrayExpressions/fmt.sol @@ -6,7 +6,7 @@ contract ArrayExpressions { uint256 length = 10; uint256[] memory sample2 = new uint256[](length); - uint256[] /* comment1 */ memory /* comment2 */ sample3; // comment3 + uint256[] memory /* comment1 */ /* comment2 */ sample3; // comment3 /* ARRAY SLICE */ msg.data[4:]; @@ -63,7 +63,8 @@ contract ArrayExpressions { 2, /* comment9 */ 3 // comment10 ]; - uint256[1] memory literal3 = - [ /* comment11 */ someVeryVeryLongVariableName /* comment13 */ ]; + uint256[1] memory literal3 = [ /* comment11 */ + someVeryVeryLongVariableName /* comment13 */ + ]; } } diff --git a/crates/fmt/testdata/BlockComments/tab.fmt.sol b/crates/fmt/testdata/BlockComments/tab.fmt.sol new file mode 100644 index 0000000000000..527f372abd489 --- /dev/null +++ b/crates/fmt/testdata/BlockComments/tab.fmt.sol @@ -0,0 +1,26 @@ +// config: style = "tab" +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } +} diff --git a/crates/fmt/testdata/BlockCommentsFunction/tab.fmt.sol b/crates/fmt/testdata/BlockCommentsFunction/tab.fmt.sol new file mode 100644 index 0000000000000..40eae6c9069ce --- /dev/null +++ b/crates/fmt/testdata/BlockCommentsFunction/tab.fmt.sol @@ -0,0 +1,21 @@ +// config: style = "tab" +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + * function testGetFuzz(bytes[2][] memory kvs) public { + * for (uint256 i = 0; i < kvs.length; i++) { + * bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + * console.logBytes32(root); + * } + * + * for (uint256 i = 0; i < kvs.length; i++) { + * (bool exist, bytes memory value) = trie.get(kvs[i][0]); + * console.logBool(exist); + * console.logBytes(value); + * require(exist); + * require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + * } + * } + */ +} diff --git a/crates/fmt/testdata/CommentEmptyLine/fmt.sol b/crates/fmt/testdata/CommentEmptyLine/fmt.sol new file mode 100644 index 0000000000000..a2407017f4be9 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/fmt.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} diff --git a/crates/fmt/testdata/CommentEmptyLine/original.sol b/crates/fmt/testdata/CommentEmptyLine/original.sol new file mode 100644 index 0000000000000..9c7f35770e9f3 --- /dev/null +++ b/crates/fmt/testdata/CommentEmptyLine/original.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} diff --git a/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol index 88694860aded2..be61b28170c73 100644 --- a/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol +++ b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol @@ -9,5 +9,7 @@ import {IAchievements} from "./interfaces/IAchievements.sol"; import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; contract Achievements is IAchievements, SoulBound1155, Ownable { - constructor(address owner) Ownable() ERC1155() {} + constructor(address owner) my_modifier Ownable() ERC1155() {} + + function f() my_modifier MyModifier my_modifier MyModifier {} } diff --git a/crates/fmt/testdata/ConstructorModifierStyle/original.sol b/crates/fmt/testdata/ConstructorModifierStyle/original.sol index 88694860aded2..eb418ba126178 100644 --- a/crates/fmt/testdata/ConstructorModifierStyle/original.sol +++ b/crates/fmt/testdata/ConstructorModifierStyle/original.sol @@ -9,5 +9,7 @@ import {IAchievements} from "./interfaces/IAchievements.sol"; import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; contract Achievements is IAchievements, SoulBound1155, Ownable { - constructor(address owner) Ownable() ERC1155() {} + constructor(address owner) my_modifier Ownable() ERC1155() {} + + function f() my_modifier MyModifier() my_modifier() MyModifier {} } diff --git a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol index dca4e325d39c9..26809a9418cf0 100644 --- a/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/bracket-spacing.fmt.sol @@ -13,7 +13,11 @@ contract SampleContract { constructor() { /* comment 9 */ } // comment 10 // comment 11 - function max( /* comment 13 */ uint256 arg1, uint256 /* comment 14 */ arg2, uint256 /* comment 15 */ ) + function max( /* comment 13 */ + uint256 arg1, + uint256 /* comment 14 */ arg2, + uint256 /* comment 15 */ + ) // comment 16 external /* comment 17 */ pure @@ -35,3 +39,21 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{ } + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{ } + +contract WithLayoutAndBase layout at 69 is Base { } + +contract ERC7201Short layout at erc7201("s") is Base { } + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base { } + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") is Base { } diff --git a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol index 2e9661f956dcf..9dca04b11e25c 100644 --- a/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/contract-new-lines.fmt.sol @@ -50,3 +50,24 @@ contract ERC20DecimalsMock is ERC20 { } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{} + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{} + +contract WithLayoutAndBase layout at 69 is Base {} + +contract ERC7201Short layout at erc7201("s") is Base {} + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") + is + Base +{} diff --git a/crates/fmt/testdata/ContractDefinition/fmt.sol b/crates/fmt/testdata/ContractDefinition/fmt.sol index 551e84decfc5b..afe196dbf3da9 100644 --- a/crates/fmt/testdata/ContractDefinition/fmt.sol +++ b/crates/fmt/testdata/ContractDefinition/fmt.sol @@ -45,3 +45,24 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{} + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{} + +contract WithLayoutAndBase layout at 69 is Base {} + +contract ERC7201Short layout at erc7201("s") is Base {} + +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} + +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") + is + Base +{} diff --git a/crates/fmt/testdata/ContractDefinition/original.sol b/crates/fmt/testdata/ContractDefinition/original.sol index 4c671985bda7b..ed66f2c5728d2 100644 --- a/crates/fmt/testdata/ContractDefinition/original.sol +++ b/crates/fmt/testdata/ContractDefinition/original.sol @@ -17,7 +17,7 @@ contract SampleContract { // comment 16 external /* comment 17 */ pure - returns(uint256) + returns(uint256) // comment 18 { // comment 19 return arg1 > arg2 ? arg1 : arg2; @@ -38,3 +38,18 @@ contract ERC20DecimalsMock is ERC20 { _decimals = decimals_; } } + +contract SomeContract is + ERC165Upgradeable, // 1 inherited component + ISomeContract // 4 inherited components +{ } + +contract AnotherContract is + Adminable, /* 1 inherited components */ + UUPSUpgradeable /* 1 inherited component */ +{ } + +contract WithLayoutAndBase layout at 69 is Base {} +contract ERC7201Short layout at erc7201("s") is Base {} +contract ERC7201Mid layout at erc7201("openzeppelin.med") is Base {} +contract ERC7201OverMax layout at erc7201("openzeppelin.storage.exceeds.max") is Base {} diff --git a/crates/fmt/testdata/DocComments/block.fmt.sol b/crates/fmt/testdata/DocComments/block.fmt.sol new file mode 100644 index 0000000000000..f216134747684 --- /dev/null +++ b/crates/fmt/testdata/DocComments/block.fmt.sol @@ -0,0 +1,117 @@ +// config: docs_style = "block" +pragma solidity ^0.8.13; + +/** + * @title A Hello world example + */ +contract HelloWorld { + /** + * Some example struct + */ + struct Person { + uint256 age; + address wallet; + } + + /** + * Here's a more double asterix comment + */ + Person public theDude; + + /** + * Will this long comment be wrapped leaving + * orphan words? + */ + Person public anotherDude; + + /** + * Constructs the dude + * @param age The dude's age + */ + constructor(uint256 age) { + theDude = Person({age: age, wallet: msg.sender}); + } + + /** + * @dev does nothing + */ + function example() public { + /** + * Does this add a whitespace error? + * + * Let's find out. + */ + } + + /** + * @dev Calculates a rectangle's surface and perimeter. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @return s The calculated surface. + * @return p The calculated perimeter. + */ + function rectangle(uint256 w, uint256 h) + public + pure + returns (uint256 s, uint256 p) + { + s = w * h; + p = 2 * (w + h); + } + + /** + * A long doc line comment that will be wrapped + */ + function docLineOverflow() external {} + + function docLinePostfixOverflow() external {} + + /** + * A long doc line comment that will be wrapped + */ + + /** + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ + function anotherExample() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineIndent() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineMalformedIndent() external {} + + /** + * contract A { + * function withALongNameThatWillCauseCommentWrap() public { + * // does nothing. + * } + * } + */ + function malformedIndentOverflow() external {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/crates/fmt/testdata/DocComments/fmt.sol b/crates/fmt/testdata/DocComments/fmt.sol index 4248f0fe587da..0e830871e040a 100644 --- a/crates/fmt/testdata/DocComments/fmt.sol +++ b/crates/fmt/testdata/DocComments/fmt.sol @@ -13,6 +13,10 @@ contract HelloWorld { */ Person public theDude; + /// Will this long comment be wrapped leaving + /// orphan words? + Person public anotherDude; + /// Constructs the dude /// @param age The dude's age constructor(uint256 age) { diff --git a/crates/fmt/testdata/DocComments/line.fmt.sol b/crates/fmt/testdata/DocComments/line.fmt.sol new file mode 100644 index 0000000000000..bf484db9e948d --- /dev/null +++ b/crates/fmt/testdata/DocComments/line.fmt.sol @@ -0,0 +1,87 @@ +// config: docs_style = "line" +pragma solidity ^0.8.13; + +/// @title A Hello world example +contract HelloWorld { + /// Some example struct + struct Person { + uint256 age; + address wallet; + } + + /// Here's a more double asterix comment + Person public theDude; + + /// Will this long comment be wrapped leaving + /// orphan words? + Person public anotherDude; + + /// Constructs the dude + /// @param age The dude's age + constructor(uint256 age) { + theDude = Person({age: age, wallet: msg.sender}); + } + + /// @dev does nothing + function example() public { + /// Does this add a whitespace error? + /// + /// Let's find out. + } + + /// @dev Calculates a rectangle's surface and perimeter. + /// @param w Width of the rectangle. + /// @param h Height of the rectangle. + /// @return s The calculated surface. + /// @return p The calculated perimeter. + function rectangle(uint256 w, uint256 h) + public + pure + returns (uint256 s, uint256 p) + { + s = w * h; + p = 2 * (w + h); + } + + /// A long doc line comment that will be wrapped + function docLineOverflow() external {} + + function docLinePostfixOverflow() external {} + + /// A long doc line comment that will be wrapped + + /// @notice Here is my comment + /// - item 1 + /// - item 2 + /// Some equations: + /// y = mx + b + function anotherExample() external {} + + /// contract A { + /// function foo() public { + /// // does nothing. + /// } + /// } + function multilineIndent() external {} + + /// contract A { + /// function foo() public { + /// // does nothing. + /// } + /// } + function multilineMalformedIndent() external {} + + /// contract A { + /// function withALongNameThatWillCauseCommentWrap() public { + /// // does nothing. + /// } + /// } + function malformedIndentOverflow() external {} +} + +/// contract A { +/// function foo() public { +/// // does nothing. +/// } +/// } +function freeFloatingMultilineIndent() {} diff --git a/crates/fmt/testdata/DocComments/original.sol b/crates/fmt/testdata/DocComments/original.sol index 28f654b57903d..ca6383dda9510 100644 --- a/crates/fmt/testdata/DocComments/original.sol +++ b/crates/fmt/testdata/DocComments/original.sol @@ -14,6 +14,10 @@ contract HelloWorld { */ Person public theDude; + /// Will this long comment be wrapped leaving + /// orphan words? + Person public anotherDude; + /// Constructs the dude /// @param age The dude's age constructor(uint256 age) { @@ -27,7 +31,7 @@ contract HelloWorld { function example() public { /** * Does this add a whitespace error? - * + * * Let's find out. */ } @@ -43,10 +47,10 @@ contract HelloWorld { p = 2 * (w + h); } - /// A long doc line comment that will be wrapped + /// A long doc line comment that will be wrapped function docLineOverflow() external {} - function docLinePostfixOverflow() external {} /// A long doc line comment that will be wrapped + function docLinePostfixOverflow() external {} /// A long doc line comment that will be wrapped /** * @notice Here is my comment diff --git a/crates/fmt/testdata/DocComments/tab.fmt.sol b/crates/fmt/testdata/DocComments/tab.fmt.sol new file mode 100644 index 0000000000000..b97a0066e9b4b --- /dev/null +++ b/crates/fmt/testdata/DocComments/tab.fmt.sol @@ -0,0 +1,105 @@ +// config: style = "tab" +pragma solidity ^0.8.13; + +/// @title A Hello world example +contract HelloWorld { + /// Some example struct + struct Person { + uint256 age; + address wallet; + } + + /** + * Here's a more double asterix comment + */ + Person public theDude; + + /// Will this long comment be wrapped leaving + /// orphan words? + Person public anotherDude; + + /// Constructs the dude + /// @param age The dude's age + constructor(uint256 age) { + theDude = Person({age: age, wallet: msg.sender}); + } + + /** + * @dev does nothing + */ + function example() public { + /** + * Does this add a whitespace error? + * + * Let's find out. + */ + } + + /** + * @dev Calculates a rectangle's surface and perimeter. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @return s The calculated surface. + * @return p The calculated perimeter. + */ + function rectangle(uint256 w, uint256 h) + public + pure + returns (uint256 s, uint256 p) + { + s = w * h; + p = 2 * (w + h); + } + + /// A long doc line comment that will be wrapped + function docLineOverflow() external {} + + function docLinePostfixOverflow() external {} + + /// A long doc line comment that will be wrapped + + /** + * @notice Here is my comment + * - item 1 + * - item 2 + * Some equations: + * y = mx + b + */ + function anotherExample() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineIndent() external {} + + /** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ + function multilineMalformedIndent() external {} + + /** + * contract A { + * function withALongNameThatWillCauseCommentWrap() public { + * // does nothing. + * } + * } + */ + function malformedIndentOverflow() external {} +} + +/** + * contract A { + * function foo() public { + * // does nothing. + * } + * } + */ +function freeFloatingMultilineIndent() {} diff --git a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol index c3c7fe00c9180..d1ff4e9b1410c 100644 --- a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol +++ b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol @@ -11,17 +11,19 @@ contract HelloWorld { } /** - * Here's a more double asterix - * comment + * Here's a more double asterix comment */ Person public theDude; + /// Will this long comment be wrapped + /// leaving orphan words? + Person public anotherDude; + /// Constructs the dude /// @param age The dude's age constructor(uint256 age) { theDude = Person({ - age: age, - wallet: msg.sender + age: age, wallet: msg.sender }); } @@ -30,21 +32,19 @@ contract HelloWorld { */ function example() public { /** - * Does this add a whitespace - * error? + * Does this add a whitespace error? * * Let's find out. */ } /** - * @dev Calculates a rectangle's - * surface and perimeter. + * @dev Calculates a rectangle's surface + * and perimeter. * @param w Width of the rectangle. * @param h Height of the rectangle. * @return s The calculated surface. - * @return p The calculated - * perimeter. + * @return p The calculated perimeter. */ function rectangle( uint256 w, @@ -58,18 +58,15 @@ contract HelloWorld { p = 2 * (w + h); } - /// A long doc line comment that - /// will be wrapped - function docLineOverflow() - external - {} + /// A long doc line comment that will be + /// wrapped + function docLineOverflow() external {} function docLinePostfixOverflow() - external - {} + external {} - /// A long doc line comment that - /// will be wrapped + /// A long doc line comment that will be + /// wrapped /** * @notice Here is my comment @@ -78,9 +75,7 @@ contract HelloWorld { * Some equations: * y = mx + b */ - function anotherExample() - external - {} + function anotherExample() external {} /** * contract A { @@ -89,9 +84,7 @@ contract HelloWorld { * } * } */ - function multilineIndent() - external - {} + function multilineIndent() external {} /** * contract A { @@ -101,8 +94,7 @@ contract HelloWorld { * } */ function multilineMalformedIndent() - external - {} + external {} /** * contract A { @@ -114,8 +106,7 @@ contract HelloWorld { * } */ function malformedIndentOverflow() - external - {} + external {} } /** diff --git a/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol b/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol new file mode 100644 index 0000000000000..99b1446b894d8 --- /dev/null +++ b/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol @@ -0,0 +1,48 @@ +// config: line_length = 120 +event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); + +function emitEvent() { + emit NewEvent(beneficiary, _vestingBeneficiaries.length - 1, uint64(block.timestamp), endTimestamp); + + emit NewEvent( /* beneficiary */ + beneficiary, + /* index */ + _vestingBeneficiaries.length - 1, + /* timestamp */ + uint64(block.timestamp), + /* end timestamp */ + endTimestamp + ); + + emit NewEvent( + beneficiary, // beneficiary + _vestingBeneficiaries.length - 1, // index + uint64(block.timestamp), // timestamp + endTimestamp // end timestamp + ); + + // https://github.com/foundry-rs/foundry/issues/12029 + emit OperatorSharesDecreased( + defaultOperator, + address(0), + strategyMock, + depositAmount / 6 // 1 withdrawal not queued so decreased + ); + + // https://github.com/foundry-rs/foundry/issues/12146 + emit ISablierComptroller.DisableCustomFeeUSD( + protocol_protocol, caller_caller, user_users.sender, previousMinFeeUSD_0, newMinFeeUSD_feeUSD + ); + emit ISablierComptroller.DisableCustomFeeUSD({ + protocol: protocol, caller: caller, user: users.sender, previousMinFeeUSD: 0, newMinFeeUSD: feeUSD + }); + + emit ISablierLockupLinear.CreateLockupLinearStream({ + streamId: streamId, + commonParams: Lockup.CreateEventCommon({ + funder: msg.sender, sender: sender, recipient: recipient, depositAmount: depositAmount + }), + cliffTime: cliffTime, + unlockAmounts: unlockAmounts + }); +} diff --git a/crates/fmt/testdata/EmitStatement/120.fmt.sol b/crates/fmt/testdata/EmitStatement/120.fmt.sol new file mode 100644 index 0000000000000..8e12d544bacf9 --- /dev/null +++ b/crates/fmt/testdata/EmitStatement/120.fmt.sol @@ -0,0 +1,60 @@ +// config: line_length = 120 +// config: prefer_compact = "none" +event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); + +function emitEvent() { + emit NewEvent(beneficiary, _vestingBeneficiaries.length - 1, uint64(block.timestamp), endTimestamp); + + emit NewEvent( /* beneficiary */ + beneficiary, + /* index */ + _vestingBeneficiaries.length - 1, + /* timestamp */ + uint64(block.timestamp), + /* end timestamp */ + endTimestamp + ); + + emit NewEvent( + beneficiary, // beneficiary + _vestingBeneficiaries.length - 1, // index + uint64(block.timestamp), // timestamp + endTimestamp // end timestamp + ); + + // https://github.com/foundry-rs/foundry/issues/12029 + emit OperatorSharesDecreased( + defaultOperator, + address(0), + strategyMock, + depositAmount / 6 // 1 withdrawal not queued so decreased + ); + + // https://github.com/foundry-rs/foundry/issues/12146 + emit ISablierComptroller.DisableCustomFeeUSD( + protocol_protocol, + caller_caller, + user_users.sender, + previousMinFeeUSD_0, + newMinFeeUSD_feeUSD + ); + emit ISablierComptroller.DisableCustomFeeUSD({ + protocol: protocol, + caller: caller, + user: users.sender, + previousMinFeeUSD: 0, + newMinFeeUSD: feeUSD + }); + + emit ISablierLockupLinear.CreateLockupLinearStream({ + streamId: streamId, + commonParams: Lockup.CreateEventCommon({ + funder: msg.sender, + sender: sender, + recipient: recipient, + depositAmount: depositAmount + }), + cliffTime: cliffTime, + unlockAmounts: unlockAmounts + }); +} diff --git a/crates/fmt/testdata/EmitStatement/fmt.sol b/crates/fmt/testdata/EmitStatement/fmt.sol index 0fac66b9b2b80..b31df04a7818e 100644 --- a/crates/fmt/testdata/EmitStatement/fmt.sol +++ b/crates/fmt/testdata/EmitStatement/fmt.sol @@ -11,8 +11,7 @@ function emitEvent() { endTimestamp ); - emit NewEvent( - /* beneficiary */ + emit NewEvent( /* beneficiary */ beneficiary, /* index */ _vestingBeneficiaries.length - 1, @@ -28,4 +27,40 @@ function emitEvent() { uint64(block.timestamp), // timestamp endTimestamp // end timestamp ); + + // https://github.com/foundry-rs/foundry/issues/12029 + emit OperatorSharesDecreased( + defaultOperator, + address(0), + strategyMock, + depositAmount / 6 // 1 withdrawal not queued so decreased + ); + + // https://github.com/foundry-rs/foundry/issues/12146 + emit ISablierComptroller.DisableCustomFeeUSD( + protocol_protocol, + caller_caller, + user_users.sender, + previousMinFeeUSD_0, + newMinFeeUSD_feeUSD + ); + emit ISablierComptroller.DisableCustomFeeUSD({ + protocol: protocol, + caller: caller, + user: users.sender, + previousMinFeeUSD: 0, + newMinFeeUSD: feeUSD + }); + + emit ISablierLockupLinear.CreateLockupLinearStream({ + streamId: streamId, + commonParams: Lockup.CreateEventCommon({ + funder: msg.sender, + sender: sender, + recipient: recipient, + depositAmount: depositAmount + }), + cliffTime: cliffTime, + unlockAmounts: unlockAmounts + }); } diff --git a/crates/fmt/testdata/EmitStatement/original.sol b/crates/fmt/testdata/EmitStatement/original.sol index 661abb78261d7..bb554fa974ebd 100644 --- a/crates/fmt/testdata/EmitStatement/original.sol +++ b/crates/fmt/testdata/EmitStatement/original.sol @@ -20,5 +20,29 @@ function emitEvent() { _vestingBeneficiaries.length - 1, // index uint64(block.timestamp), // timestamp endTimestamp // end timestamp - ); + ); + + // https://github.com/foundry-rs/foundry/issues/12029 + emit OperatorSharesDecreased( + defaultOperator, + address(0), + strategyMock, + depositAmount / 6 // 1 withdrawal not queued so decreased + ); + + // https://github.com/foundry-rs/foundry/issues/12146 + emit ISablierComptroller.DisableCustomFeeUSD(protocol_protocol, caller_caller, user_users.sender, previousMinFeeUSD_0, newMinFeeUSD_feeUSD); + emit ISablierComptroller.DisableCustomFeeUSD({ protocol: protocol, caller: caller, user: users.sender, previousMinFeeUSD: 0, newMinFeeUSD: feeUSD }); + + emit ISablierLockupLinear.CreateLockupLinearStream({ + streamId: streamId, + commonParams: Lockup.CreateEventCommon({ + funder: msg.sender, + sender: sender, + recipient: recipient, + depositAmount: depositAmount + }), + cliffTime: cliffTime, + unlockAmounts: unlockAmounts + }); } diff --git a/crates/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol index a4ae0f0192051..10483822a86b8 100644 --- a/crates/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/EnumDefinition/bracket-spacing.fmt.sol @@ -1,6 +1,6 @@ // config: bracket_spacing = true contract EnumDefinitions { - enum Empty { } + enum Empty {} enum ActionChoices { GoLeft, GoRight, diff --git a/crates/fmt/testdata/EnumVariants/fmt.sol b/crates/fmt/testdata/EnumVariants/fmt.sol index b33b8846984d2..3f1c59b8f2595 100644 --- a/crates/fmt/testdata/EnumVariants/fmt.sol +++ b/crates/fmt/testdata/EnumVariants/fmt.sol @@ -10,8 +10,8 @@ interface I { /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. enum CallerMode2 { /// No caller modification is currently active. - None, - /// No caller modification is currently active2. + None, /// No caller modification is currently active2. + Some } diff --git a/crates/fmt/testdata/ForStatement/fmt.sol b/crates/fmt/testdata/ForStatement/fmt.sol index a1bb4b2e6a28c..c6dbe34f17e20 100644 --- a/crates/fmt/testdata/ForStatement/fmt.sol +++ b/crates/fmt/testdata/ForStatement/fmt.sol @@ -12,7 +12,16 @@ contract ForStatement { uint256 veryLongVariableName = 1000; for ( uint256 i3; - i3 < 10 && veryLongVariableName > 999 && veryLongVariableName < 1001; + i3 < 10 && veryLongVariableName > 999 + && veryLongVariableName < 1001; + i3++ + ) { + i3++; + } + + for ( + uint256 i3; + i3 < 10 && veryLongVariableName > 900 && veryLongVariableName < 999; i3++ ) { i3++; diff --git a/crates/fmt/testdata/ForStatement/original.sol b/crates/fmt/testdata/ForStatement/original.sol index e98288dd1cbce..7cb4711192b5d 100644 --- a/crates/fmt/testdata/ForStatement/original.sol +++ b/crates/fmt/testdata/ForStatement/original.sol @@ -11,7 +11,7 @@ contract ForStatement { uint256 i2; for(++i2;i2<10;i2++) - + {} uint256 veryLongVariableName = 1000; @@ -20,6 +20,12 @@ contract ForStatement { ; i3++) { i3 ++ ; } + for ( uint256 i3; i3 < 10 + && veryLongVariableName>900 && veryLongVariableName< 999 + ; i3++) + { i3 ++ ; } + + for (type(uint256).min;;) {} for (;;) { "test" ; } @@ -30,4 +36,4 @@ contract ForStatement { for (uint256 i6 = 10; i6 > i5; i6--) i5++; } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol b/crates/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol index 93e5eb1a2e793..3e7513c854d47 100644 --- a/crates/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/FunctionCallArgsStatement/bracket-spacing.fmt.sol @@ -1,3 +1,4 @@ +// config: line_length = 120 // config: bracket_spacing = true interface ITarget { function run() external payable; @@ -11,10 +12,7 @@ contract FunctionCallArgsStatement { gas = 1 gwei; } - function veryAndVeryLongNameOfSomeGasEstimateFunction() - public - returns (uint256) - { + function veryAndVeryLongNameOfSomeGasEstimateFunction() public returns (uint256) { return gasleft(); } @@ -31,12 +29,11 @@ contract FunctionCallArgsStatement { target.run{ gas: estimate(), value: value(1) }(); - target.run{ - value: value(1 ether), - gas: veryAndVeryLongNameOfSomeGasEstimateFunction() - }(); + target.run{ value: value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() }(); - target.run{ /* comment 1 */ value: /* comment2 */ 1 }; + target.run{ /* comment 1 */ + value: /* comment2 */ 1 + }; target.run{ /* comment3 */ value: 1, // comment4 @@ -51,5 +48,10 @@ contract FunctionCallArgsStatement { }; vm.expectEmit({ checkTopic1: false, checkTopic2: false }); + + lockup.withdraw{ value: LOCKUP_MIN_FEE_WEI }({ + streamId: streamId, to: users.recipient, amount: withdrawAmount + }); + portal.xcall{ value: msg.value }(id, ConfLevel.Finalized, Predeploys.NominaBridgeNative, xcalldata); } } diff --git a/crates/fmt/testdata/FunctionCallArgsStatement/fmt.sol b/crates/fmt/testdata/FunctionCallArgsStatement/fmt.sol index 5a5cc5f634281..927f9040755f7 100644 --- a/crates/fmt/testdata/FunctionCallArgsStatement/fmt.sol +++ b/crates/fmt/testdata/FunctionCallArgsStatement/fmt.sol @@ -35,7 +35,9 @@ contract FunctionCallArgsStatement { gas: veryAndVeryLongNameOfSomeGasEstimateFunction() }(); - target.run{ /* comment 1 */ value: /* comment2 */ 1}; + target.run{ /* comment 1 */ + value: /* comment2 */ 1 + }; target.run{ /* comment3 */ value: 1, // comment4 @@ -50,5 +52,12 @@ contract FunctionCallArgsStatement { }; vm.expectEmit({checkTopic1: false, checkTopic2: false}); + + lockup.withdraw{value: LOCKUP_MIN_FEE_WEI}({ + streamId: streamId, to: users.recipient, amount: withdrawAmount + }); + portal.xcall{value: msg.value}( + id, ConfLevel.Finalized, Predeploys.NominaBridgeNative, xcalldata + ); } } diff --git a/crates/fmt/testdata/FunctionCallArgsStatement/original.sol b/crates/fmt/testdata/FunctionCallArgsStatement/original.sol index b2cfaa2f28f9a..2a25b90726287 100644 --- a/crates/fmt/testdata/FunctionCallArgsStatement/original.sol +++ b/crates/fmt/testdata/FunctionCallArgsStatement/original.sol @@ -23,18 +23,18 @@ contract FunctionCallArgsStatement { target.run{gas:1,value:0x00}(); - target.run{ - gas : 1000, - value: 1 ether + target.run{ + gas : 1000, + value: 1 ether } (); target.run{ gas: estimate(), - value: value(1) }(); + value: value(1) }(); target.run { value: value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - target.run /* comment 1 */ { value: /* comment2 */ 1 }; + target.run /* comment 1 */ { value: /* comment2 */ 1 }; target.run { /* comment3 */ value: 1, // comment4 gas: gasleft()}; @@ -46,5 +46,8 @@ contract FunctionCallArgsStatement { gas: gasleft()}; vm.expectEmit({ checkTopic1: false, checkTopic2: false }); + + lockup.withdraw{ value: LOCKUP_MIN_FEE_WEI }({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); + portal.xcall{ value: msg.value }(id, ConfLevel.Finalized, Predeploys.NominaBridgeNative, xcalldata); } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol b/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol index db7164d284a54..f7ff46fd7cd2c 100644 --- a/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol @@ -15,7 +15,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -39,9 +39,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -332,10 +331,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -729,4 +728,13 @@ contract FunctionOverrides is { a = 1; } + + function simple( + address _target, + bytes memory _payload + ) + internal + { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinition/all.fmt.sol b/crates/fmt/testdata/FunctionDefinition/all.fmt.sol index 6d90880679199..10272e81a04ec 100644 --- a/crates/fmt/testdata/FunctionDefinition/all.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/all.fmt.sol @@ -15,7 +15,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -39,9 +39,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -332,10 +331,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -727,4 +726,13 @@ contract FunctionOverrides is { a = 1; } + + function simple( + address _target, + bytes memory _payload + ) + internal + { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinition/fmt.sol b/crates/fmt/testdata/FunctionDefinition/fmt.sol index 9e34a8bea2682..3d8ecaed032cd 100644 --- a/crates/fmt/testdata/FunctionDefinition/fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/fmt.sol @@ -14,7 +14,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -38,9 +38,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -323,10 +322,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -706,4 +705,10 @@ contract FunctionOverrides is { a = 1; } + + function simple(address _target, bytes memory _payload) + internal + { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinition/original.sol b/crates/fmt/testdata/FunctionDefinition/original.sol index 97db649d55660..02eb64e8e6fb0 100644 --- a/crates/fmt/testdata/FunctionDefinition/original.sol +++ b/crates/fmt/testdata/FunctionDefinition/original.sol @@ -91,10 +91,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function () external {} + function f() external {} fallback () external {} - function () external payable {} + function f() external payable {} fallback () external payable {} receive () external payable {} @@ -214,5 +214,11 @@ contract FunctionOverrides is FunctionInterfaces, FunctionDefinitions { function oneParam(uint256 x) override(FunctionInterfaces, FunctionDefinitions, SomeOtherFunctionContract, SomeImport.AndAnotherFunctionContract) { a = 1; } -} + function simple(address _target, bytes memory _payload) + internal + { + a = 1; + } + +} diff --git a/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol b/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol index 516e5c2fd42ed..5d62b4411da9e 100644 --- a/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol @@ -15,7 +15,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -39,9 +39,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -324,10 +323,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -707,4 +706,10 @@ contract FunctionOverrides is { a = 1; } + + function simple(address _target, bytes memory _payload) + internal + { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-always.fmt.sol similarity index 97% rename from crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol rename to crates/fmt/testdata/FunctionDefinition/params-always.fmt.sol index 3e7ebfff6b3aa..0afc214dd6fd8 100644 --- a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/params-always.fmt.sol @@ -1,5 +1,5 @@ // config: line_length = 60 -// config: multiline_func_header = "params_first" +// config: multiline_func_header = "params_always" interface FunctionInterfaces { function noParamsNoModifiersNoReturns(); @@ -17,7 +17,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -41,9 +41,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -326,10 +325,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -713,4 +712,11 @@ contract FunctionOverrides is { a = 1; } + + function simple( + address _target, + bytes memory _payload + ) internal { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol index cd2015c9e050e..24052f8066b75 100644 --- a/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol @@ -15,7 +15,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ @@ -39,9 +39,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// TEST @@ -324,10 +323,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} @@ -707,4 +706,11 @@ contract FunctionOverrides is { a = 1; } + + function simple( + address _target, + bytes memory _payload + ) internal { + a = 1; + } } diff --git a/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol index 7b751e22ec26a..f961e7bd9601c 100644 --- a/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol +++ b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol @@ -5,10 +5,7 @@ contract ReturnFnFormat { function returnsFunction() internal pure - returns ( - function() - internal pure returns (uint256) - ) + returns (function() internal pure returns (uint256)) {} } diff --git a/crates/fmt/testdata/FunctionType/fmt.sol b/crates/fmt/testdata/FunctionType/fmt.sol index 39053d816058f..f2cb30800a72d 100644 --- a/crates/fmt/testdata/FunctionType/fmt.sol +++ b/crates/fmt/testdata/FunctionType/fmt.sol @@ -1,6 +1,6 @@ -// config: line_length = 90 +// config: line_length = 100 library ArrayUtils { - function map(uint256[] memory self, function (uint) pure returns (uint) f) + function map(uint256[] memory self, function(uint256) pure returns (uint256) f) internal pure returns (uint256[] memory r) @@ -11,7 +11,7 @@ library ArrayUtils { } } - function reduce(uint256[] memory self, function (uint, uint) pure returns (uint) f) + function reduce(uint256[] memory self, function(uint256, uint256) pure returns (uint256) f) internal pure returns (uint256 r) @@ -28,4 +28,12 @@ library ArrayUtils { r[i] = i; } } + + function _castToPure(function(bytes memory) internal view fnIn) + returns (function(bytes memory) pure fnOut) + { + assembly { + fnOut := fnIn + } + } } diff --git a/crates/fmt/testdata/FunctionType/original.sol b/crates/fmt/testdata/FunctionType/original.sol index 27b402d85ef5f..e3c408a79e653 100644 --- a/crates/fmt/testdata/FunctionType/original.sol +++ b/crates/fmt/testdata/FunctionType/original.sol @@ -28,4 +28,12 @@ library ArrayUtils { r[i] = i; } } + + function _castToPure(function(bytes memory) internal view fnIn) +returns (function(bytes memory) pure fnOut) + { + assembly { + fnOut := fnIn + } + } } diff --git a/crates/fmt/testdata/IfStatement/block-multi.fmt.sol b/crates/fmt/testdata/IfStatement/block-multi.fmt.sol index dcd8bb83eaa8f..e69a4fc613541 100644 --- a/crates/fmt/testdata/IfStatement/block-multi.fmt.sol +++ b/crates/fmt/testdata/IfStatement/block-multi.fmt.sol @@ -50,8 +50,8 @@ contract IfStatement { /* comment9 */ else if ( /* comment10 */ anotherLongCondition // comment11 - ) { /* comment12 */ + ) { execute(); } // comment13 /* comment14 */ @@ -93,8 +93,8 @@ contract IfStatement { } if (condition) { - execute(); - } // comment18 + execute(); // comment18 + } if (condition) { executeWithMultipleParameters(condition, anotherLongCondition); @@ -168,4 +168,33 @@ contract IfStatement { executeElse(); } } + + function test_nestedBkocks() public { + if (accesses[i].account == address(simpleStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + bytes32 slot = accesses[i].storageAccesses[j].slot; + if (slot == bytes32(uint256(0))) { + foundValueSlot = true; + } + if (slot == bytes32(uint256(1))) { + foundOwnerSlot = true; + } + if (slot == bytes32(uint256(2))) { + foundValuesSlot0 = true; + } + if (slot == bytes32(uint256(3))) { + foundValuesSlot1 = true; + } + if (slot == bytes32(uint256(4))) { + foundValuesSlot2 = true; + } + } + } + } + + function test_emptyIfBlock() external { + if (block.number < 10) {} else { + revert(); + } + } } diff --git a/crates/fmt/testdata/IfStatement/block-single.fmt.sol b/crates/fmt/testdata/IfStatement/block-single.fmt.sol index ba2b9998b184c..92a2067bd98f8 100644 --- a/crates/fmt/testdata/IfStatement/block-single.fmt.sol +++ b/crates/fmt/testdata/IfStatement/block-single.fmt.sol @@ -43,8 +43,8 @@ contract IfStatement { /* comment9 */ else if ( /* comment10 */ anotherLongCondition // comment11 - ) { /* comment12 */ + ) { execute(); } // comment13 /* comment14 */ @@ -120,4 +120,23 @@ contract IfStatement { else if (condition) execute(); else executeElse(); } + + function test_nestedBkocks() public { + if (accesses[i].account == address(simpleStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + bytes32 slot = accesses[i].storageAccesses[j].slot; + if (slot == bytes32(uint256(0))) foundValueSlot = true; + if (slot == bytes32(uint256(1))) foundOwnerSlot = true; + if (slot == bytes32(uint256(2))) foundValuesSlot0 = true; + if (slot == bytes32(uint256(3))) foundValuesSlot1 = true; + if (slot == bytes32(uint256(4))) foundValuesSlot2 = true; + } + } + } + + function test_emptyIfBlock() external { + if (block.number < 10) {} else { + revert(); + } + } } diff --git a/crates/fmt/testdata/IfStatement/fmt.sol b/crates/fmt/testdata/IfStatement/fmt.sol index cb2f8874f83d5..03117f0883773 100644 --- a/crates/fmt/testdata/IfStatement/fmt.sol +++ b/crates/fmt/testdata/IfStatement/fmt.sol @@ -49,8 +49,8 @@ contract IfStatement { /* comment9 */ else if ( /* comment10 */ anotherLongCondition // comment11 - ) { /* comment12 */ + ) { execute(); } // comment13 /* comment14 */ @@ -142,4 +142,23 @@ contract IfStatement { executeElse(); } } + + function test_nestedBkocks() public { + if (accesses[i].account == address(simpleStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + bytes32 slot = accesses[i].storageAccesses[j].slot; + if (slot == bytes32(uint256(0))) foundValueSlot = true; + if (slot == bytes32(uint256(1))) foundOwnerSlot = true; + if (slot == bytes32(uint256(2))) foundValuesSlot0 = true; + if (slot == bytes32(uint256(3))) foundValuesSlot1 = true; + if (slot == bytes32(uint256(4))) foundValuesSlot2 = true; + } + } + } + + function test_emptyIfBlock() external { + if (block.number < 10) {} else { + revert(); + } + } } diff --git a/crates/fmt/testdata/IfStatement/original.sol b/crates/fmt/testdata/IfStatement/original.sol index b36829bbbf6bf..cef566fda3c8b 100644 --- a/crates/fmt/testdata/IfStatement/original.sol +++ b/crates/fmt/testdata/IfStatement/original.sol @@ -15,9 +15,9 @@ function executeWithVeryVeryVeryLongNameAndSomeParameter(bool parameter) {} contract IfStatement { function test() external { - if( true) + if( true) { - execute() ; + execute() ; } bool condition; bool anotherLongCondition; bool andAnotherVeryVeryLongCondition ; @@ -51,7 +51,7 @@ contract IfStatement { /* comment14 */ else { } // comment15 if ( - // comment16 + // comment16 condition /* comment17 */ ) { @@ -116,4 +116,23 @@ contract IfStatement { else executeElse(); } -} \ No newline at end of file + + function test_nestedBkocks() public { + if (accesses[i].account == address(simpleStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + bytes32 slot = accesses[i].storageAccesses[j].slot; + if (slot == bytes32(uint256(0))) foundValueSlot = true; + if (slot == bytes32(uint256(1))) foundOwnerSlot = true; + if (slot == bytes32(uint256(2))) foundValuesSlot0 = true; + if (slot == bytes32(uint256(3))) foundValuesSlot1 = true; + if (slot == bytes32(uint256(4))) foundValuesSlot2 = true; + } + } + } + + function test_emptyIfBlock() external { + if (block.number < 10) {} else { + revert(); + } + } +} diff --git a/crates/fmt/testdata/IfStatement2/120.fmt.sol b/crates/fmt/testdata/IfStatement2/120.fmt.sol new file mode 100644 index 0000000000000..beb31cec549b9 --- /dev/null +++ b/crates/fmt/testdata/IfStatement2/120.fmt.sol @@ -0,0 +1,28 @@ +// config: line_length = 120 +contract IfStatement { + function test() external { + bool anotherLongCondition; + + if (condition && ((condition || anotherLongCondition))) execute(); + } + + // https://github.com/foundry-rs/foundry/issues/12102 + function repro() external { + for (uint256 i; i < len; ++i) { + proportions[i] = totalDepositedTvl == 0 + ? 0 + : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); + proportions[i] = totalDepositedTvl == 0 + ? 0 + : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); + } + } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars.expectedSnapshotTime = withdrawAmount + <= getDescaledAmount(flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId)) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp(); + } +} diff --git a/crates/fmt/testdata/IfStatement2/fmt.sol b/crates/fmt/testdata/IfStatement2/fmt.sol index 10ae43601d4ad..7dc3ce088bb5c 100644 --- a/crates/fmt/testdata/IfStatement2/fmt.sol +++ b/crates/fmt/testdata/IfStatement2/fmt.sol @@ -4,4 +4,37 @@ contract IfStatement { if (condition && ((condition || anotherLongCondition))) execute(); } + + // https://github.com/foundry-rs/foundry/issues/12102 + function repro() external { + for (uint256 i; i < len; ++i) { + proportions[i] = totalDepositedTvl == 0 + ? 0 + : Math.mulDiv( + vaultUsdValue[i], + 1e18, + totalDepositedTvl, + Math.Rounding.Floor + ); + proportions[i] = totalDepositedTvl == 0 + ? 0 + : Math.mulDiv( + vaultUsdValue[i], + 1e18, + totalDepositedTvl, + Math.Rounding.Floor + ); + } + } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars.expectedSnapshotTime = withdrawAmount + <= getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp(); + } } diff --git a/crates/fmt/testdata/IfStatement2/original.sol b/crates/fmt/testdata/IfStatement2/original.sol index df020c04bbe33..1dac9109a458f 100644 --- a/crates/fmt/testdata/IfStatement2/original.sol +++ b/crates/fmt/testdata/IfStatement2/original.sol @@ -7,4 +7,23 @@ contract IfStatement { ) ) execute(); } -} \ No newline at end of file + + // https://github.com/foundry-rs/foundry/issues/12102 + function repro() external { + for (uint i; i < len; ++i) { + proportions[i] = + totalDepositedTvl == 0 ? 0 : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); + proportions[i] = totalDepositedTvl == 0 + ? 0 + : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); + } + } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars. expectedSnapshotTime = withdrawAmount + <= getDescaledAmount(flow.getSnapshotDebtScaled (streamId), flow.getTokenDecimals(streamId)) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp (); + } +} diff --git a/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol b/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol index 1db94929ab7c7..b0f4fd067d465 100644 --- a/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol @@ -5,8 +5,8 @@ import "SomeFile.sol" as SomeOtherFile; import "SomeFile.sol" as SomeOtherFile; import "AnotherFile.sol" as SomeSymbol; import "AnotherFile.sol" as SomeSymbol; -import { symbol1 as alias, symbol2 } from "File.sol"; -import { symbol1 as alias, symbol2 } from "File.sol"; +import { symbol1 as alias0, symbol2 } from "File.sol"; +import { symbol1 as alias0, symbol2 } from "File.sol"; import { symbol1 as alias1, symbol2 as alias2, @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/fmt.sol b/crates/fmt/testdata/ImportDirective/fmt.sol index 4915b8ab203c8..c4ce5f3166750 100644 --- a/crates/fmt/testdata/ImportDirective/fmt.sol +++ b/crates/fmt/testdata/ImportDirective/fmt.sol @@ -4,8 +4,8 @@ import "SomeFile.sol" as SomeOtherFile; import "SomeFile.sol" as SomeOtherFile; import "AnotherFile.sol" as SomeSymbol; import "AnotherFile.sol" as SomeSymbol; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; import { symbol1 as alias1, symbol2 as alias2, @@ -18,3 +18,8 @@ import { symbol3 as alias3, symbol4 } from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol new file mode 100644 index 0000000000000..e930ceee0754d --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-glob.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "prefer_glob" +import "SomeFile.sol"; +import "SomeFile.sol"; +import * as SomeOtherFile from "SomeFile.sol"; +import * as SomeOtherFile from "SomeFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol new file mode 100644 index 0000000000000..6ea4cabeeb1ec --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-prefer-plain.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "prefer_plain" +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol b/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol new file mode 100644 index 0000000000000..607817b9eadfb --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/namespace-import-preserve.fmt.sol @@ -0,0 +1,26 @@ +// config: namespace_import_style = "preserve" +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/original.sol b/crates/fmt/testdata/ImportDirective/original.sol index 0e18e10c14dab..2a18f88f0fa33 100644 --- a/crates/fmt/testdata/ImportDirective/original.sol +++ b/crates/fmt/testdata/ImportDirective/original.sol @@ -4,7 +4,10 @@ import "SomeFile.sol" as SomeOtherFile; import 'SomeFile.sol' as SomeOtherFile; import * as SomeSymbol from "AnotherFile.sol"; import * as SomeSymbol from 'AnotherFile.sol'; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from 'File.sol'; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from 'File.sol'; import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from "File2.sol"; import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol b/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol index d1bf9852c02e5..c759965928ec2 100644 --- a/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol @@ -5,8 +5,8 @@ import "SomeFile.sol" as SomeOtherFile; import 'SomeFile.sol' as SomeOtherFile; import "AnotherFile.sol" as SomeSymbol; import 'AnotherFile.sol' as SomeSymbol; -import {symbol1 as alias, symbol2} from "File.sol"; -import {symbol1 as alias, symbol2} from 'File.sol'; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from 'File.sol'; import { symbol1 as alias1, symbol2 as alias2, @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol b/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol index 10449e079ae81..1820bd5166841 100644 --- a/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol @@ -5,8 +5,8 @@ import 'SomeFile.sol' as SomeOtherFile; import 'SomeFile.sol' as SomeOtherFile; import 'AnotherFile.sol' as SomeSymbol; import 'AnotherFile.sol' as SomeSymbol; -import {symbol1 as alias, symbol2} from 'File.sol'; -import {symbol1 as alias, symbol2} from 'File.sol'; +import {symbol1 as alias0, symbol2} from 'File.sol'; +import {symbol1 as alias0, symbol2} from 'File.sol'; import { symbol1 as alias1, symbol2 as alias2, @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'; diff --git a/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol b/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol new file mode 100644 index 0000000000000..5644e336dbb97 --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol @@ -0,0 +1,24 @@ +// config: single_line_imports = true +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/InlineDisable/fmt.sol b/crates/fmt/testdata/InlineDisable/fmt.sol index d7adea60b32da..dae3f1a6ff02c 100644 --- a/crates/fmt/testdata/InlineDisable/fmt.sol +++ b/crates/fmt/testdata/InlineDisable/fmt.sol @@ -29,7 +29,7 @@ enum States { enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } // forgefmt: disable-next-line -bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; +bytes32 constant BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; // forgefmt: disable-start @@ -37,7 +37,7 @@ bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b6 // comment2 -/* comment 3 */ /* +/* comment 3 */ /* comment4 */ // comment 5 @@ -105,7 +105,7 @@ function testDoWhile() external { uint256 i; do { "test"; } while (i != 0); - do + do {} while ( @@ -113,9 +113,9 @@ i != 0); bool someVeryVeryLongCondition; do { "test"; } while( - someVeryVeryLongCondition && !someVeryVeryLongCondition && + someVeryVeryLongCondition && !someVeryVeryLongCondition && !someVeryVeryLongCondition && -someVeryVeryLongCondition); +someVeryVeryLongCondition); do i++; while(i < 10); @@ -134,7 +134,7 @@ function forStatement() { uint256 i2; for(++i2;i2<10;i2++) - + {} uint256 veryLongVariableName = 1000; @@ -161,18 +161,18 @@ function callArgTest() { target.run{gas:1,value:0x00}(); - target.run{ - gas : 1000, - value: 1 ether + target.run{ + gas : 1000, + value: 1 ether } (); target.run{ gas: estimate(), - value: value(1) }(); + value: value(1) }(); target.run { value: value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - target.run /* comment 1 */ { value: /* comment2 */ 1 }; + target.run /* comment 1 */ { value: /* comment2 */ 1 }; target.run { /* comment3 */ value: 1, // comment4 gas: gasleft()}; @@ -240,7 +240,7 @@ function returnTest() { 0x00; } - if (val == 1) { return + if (val == 1) { return 1; } if (val == 2) { @@ -270,11 +270,11 @@ function namedFuncCall() { ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - + SimpleStruct memory simple2 = SimpleStruct( - { // comment1 + { // comment1 /* comment2 */ val : /* comment3 */ 0 - + } ); // forgefmt: disable-end @@ -294,10 +294,10 @@ function revertTest() { ts: block.timestamp, message: "some reason" }); - + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - revert // comment1 + revert // comment1 ({}); // forgefmt: disable-end } @@ -329,7 +329,7 @@ function thisTest() { this // comment1 .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); address(this).balance; - + address thisAddress = address( // comment2 /* comment3 */ this // comment 4 @@ -365,10 +365,10 @@ function tryTest() { } try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { unknown.doSomething(); - } + } catch Error(string memory) { unknown.handleError(); } @@ -385,7 +385,7 @@ function testArray() { : /* comment2 */ msg.data.length // comment3 ]; msg.data[ - // comment4 + // comment4 4 // comment5 :msg.data.length /* comment6 */]; // forgefmt: disable-end @@ -457,7 +457,7 @@ function testWhile() { i3 < 10 ) { i3++; } - uint256 i4; while (i4 < 10) + uint256 i4; while (i4 < 10) { i4 ++ ;} @@ -469,11 +469,8 @@ function testWhile() { } function testLine() {} - function /* forgefmt: disable-line */ testLine( ) { } - function testLine() {} - function testLine( ) { } // forgefmt: disable-line // forgefmt: disable-start @@ -487,7 +484,7 @@ error TopLevelCustomErrorArgWithoutName (string); event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); -// forgefmt: disable-stop +// forgefmt: disable-end function setNumber(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line number = newNumber; diff --git a/crates/fmt/testdata/InlineDisable/original.sol b/crates/fmt/testdata/InlineDisable/original.sol index 7731678940cbf..8a51a8f786c0f 100644 --- a/crates/fmt/testdata/InlineDisable/original.sol +++ b/crates/fmt/testdata/InlineDisable/original.sol @@ -14,7 +14,7 @@ enum States { State1, State2, State3, State4, State5, State6, State7, State8, St enum States { State1, State2, State3, State4, State5, State6, State7, State8, State9 } // forgefmt: disable-next-line -bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; +bytes32 constant BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; // forgefmt: disable-start @@ -22,7 +22,7 @@ bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b6 // comment2 -/* comment 3 */ /* +/* comment 3 */ /* comment4 */ // comment 5 @@ -87,7 +87,7 @@ function testDoWhile() external { uint256 i; do { "test"; } while (i != 0); - do + do {} while ( @@ -95,9 +95,9 @@ i != 0); bool someVeryVeryLongCondition; do { "test"; } while( - someVeryVeryLongCondition && !someVeryVeryLongCondition && + someVeryVeryLongCondition && !someVeryVeryLongCondition && !someVeryVeryLongCondition && -someVeryVeryLongCondition); +someVeryVeryLongCondition); do i++; while(i < 10); @@ -116,7 +116,7 @@ function forStatement() { uint256 i2; for(++i2;i2<10;i2++) - + {} uint256 veryLongVariableName = 1000; @@ -143,18 +143,18 @@ function callArgTest() { target.run{gas:1,value:0x00}(); - target.run{ - gas : 1000, - value: 1 ether + target.run{ + gas : 1000, + value: 1 ether } (); target.run{ gas: estimate(), - value: value(1) }(); + value: value(1) }(); target.run { value: value(1 ether), gas: veryAndVeryLongNameOfSomeGasEstimateFunction() } (); - target.run /* comment 1 */ { value: /* comment2 */ 1 }; + target.run /* comment 1 */ { value: /* comment2 */ 1 }; target.run { /* comment3 */ value: 1, // comment4 gas: gasleft()}; @@ -211,7 +211,7 @@ function literalTest() { // forgefmt: disable-end // forgefmt: disable-next-line - bytes memory bytecode = + bytes memory bytecode = hex"ff"; } @@ -222,7 +222,7 @@ function returnTest() { 0x00; } - if (val == 1) { return + if (val == 1) { return 1; } if (val == 2) { @@ -252,11 +252,11 @@ function namedFuncCall() { ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - + SimpleStruct memory simple2 = SimpleStruct( - { // comment1 + { // comment1 /* comment2 */ val : /* comment3 */ 0 - + } ); // forgefmt: disable-end @@ -276,10 +276,10 @@ function revertTest() { ts: block.timestamp, message: "some reason" }); - + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - revert // comment1 + revert // comment1 ({}); // forgefmt: disable-end } @@ -311,7 +311,7 @@ function thisTest() { this // comment1 .someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword(); address(this).balance; - + address thisAddress = address( // comment2 /* comment3 */ this // comment 4 @@ -347,10 +347,10 @@ function tryTest() { } try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { unknown.doSomething(); - } + } catch Error(string memory) { unknown.handleError(); } @@ -367,7 +367,7 @@ function testArray() { : /* comment2 */ msg.data.length // comment3 ]; msg.data[ - // comment4 + // comment4 4 // comment5 :msg.data.length /* comment6 */]; // forgefmt: disable-end @@ -439,7 +439,7 @@ function testWhile() { i3 < 10 ) { i3++; } - uint256 i4; while (i4 < 10) + uint256 i4; while (i4 < 10) { i4 ++ ;} @@ -450,9 +450,9 @@ function testWhile() { // forgefmt: disable-end } -function testLine( ) { } -function /* forgefmt: disable-line */ testLine( ) { } -function testLine( ) { } +function testLine( ) { } +function /* forgefmt: disable-line */ testLine( ) { } +function testLine( ) { } function testLine( ) { } // forgefmt: disable-line // forgefmt: disable-start @@ -466,7 +466,7 @@ error TopLevelCustomErrorArgWithoutName (string); event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); -// forgefmt: disable-stop +// forgefmt: disable-end function setNumber(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line number = newNumber; diff --git a/crates/fmt/testdata/LiteralExpression/fmt.sol b/crates/fmt/testdata/LiteralExpression/fmt.sol index 7fa6878e5586d..1551b4a9cb316 100644 --- a/crates/fmt/testdata/LiteralExpression/fmt.sol +++ b/crates/fmt/testdata/LiteralExpression/fmt.sol @@ -11,7 +11,7 @@ contract LiteralExpressions { // number literals 1; 123_000; - 1_2e345_678; + // 1_2e345_678; -1; 2e-10; // comment5 @@ -22,6 +22,8 @@ contract LiteralExpressions { 0x00; 0x123_456; 0x2eff_abde; + address(0xCAFE); + address(0xBEEF); // rational number literals 0.1; diff --git a/crates/fmt/testdata/LiteralExpression/original.sol b/crates/fmt/testdata/LiteralExpression/original.sol index 5c559531de955..916e927da1ff0 100644 --- a/crates/fmt/testdata/LiteralExpression/original.sol +++ b/crates/fmt/testdata/LiteralExpression/original.sol @@ -10,7 +10,7 @@ contract LiteralExpressions { // number literals 1; 123_000; - 1_2e345_678; + // 1_2e345_678; -1; 2e-10; // comment5 @@ -20,6 +20,8 @@ contract LiteralExpressions { 0x00; 0x123_456; 0x2eff_abde; + address(0xCAFE); + address(0xBEEF); // rational number literals .1; diff --git a/crates/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol b/crates/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol index 3d9490804f231..7e9f6e0b2ec79 100644 --- a/crates/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol +++ b/crates/fmt/testdata/LiteralExpression/preserve-quote.fmt.sol @@ -12,7 +12,7 @@ contract LiteralExpressions { // number literals 1; 123_000; - 1_2e345_678; + // 1_2e345_678; -1; 2e-10; // comment5 @@ -23,6 +23,8 @@ contract LiteralExpressions { 0x00; 0x123_456; 0x2eff_abde; + address(0xCAFE); + address(0xBEEF); // rational number literals 0.1; diff --git a/crates/fmt/testdata/LiteralExpression/single-quote.fmt.sol b/crates/fmt/testdata/LiteralExpression/single-quote.fmt.sol index cdc67a2c6c814..67244e687fe03 100644 --- a/crates/fmt/testdata/LiteralExpression/single-quote.fmt.sol +++ b/crates/fmt/testdata/LiteralExpression/single-quote.fmt.sol @@ -12,7 +12,7 @@ contract LiteralExpressions { // number literals 1; 123_000; - 1_2e345_678; + // 1_2e345_678; -1; 2e-10; // comment5 @@ -23,6 +23,8 @@ contract LiteralExpressions { 0x00; 0x123_456; 0x2eff_abde; + address(0xCAFE); + address(0xBEEF); // rational number literals 0.1; diff --git a/crates/fmt/testdata/MappingType/fmt.sol b/crates/fmt/testdata/MappingType/fmt.sol index 7f6297cff9455..61d5cd02fb300 100644 --- a/crates/fmt/testdata/MappingType/fmt.sol +++ b/crates/fmt/testdata/MappingType/fmt.sol @@ -29,7 +29,7 @@ contract Mapping { // comment2 ) mapping6; mapping( /* comment3 */ - uint256 /* comment4 */ key /* comment5 */ - => /* comment6 */ uint256 /* comment7 */ value /* comment8 */ /* comment9 */ + uint256 /* comment4 */ key /* comment5 */ /* comment6 */ + => uint256 /* comment7 */ value /* comment8 */ /* comment9 */ ) /* comment10 */ mapping7; } diff --git a/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol b/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol index 14a24c9003888..e8f832e3e299f 100644 --- a/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol +++ b/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol @@ -1,3 +1,4 @@ +// config: prefer_compact = "events_errors" contract NamedFunctionCallExpression { struct SimpleStruct { uint256 val; @@ -26,22 +27,20 @@ contract NamedFunctionCallExpression { }); StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting - memory long = - StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ + memory + long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); SimpleStruct memory simple2 = SimpleStruct({ // comment1 - /* comment2 */ - val: /* comment3 */ 0 + /* comment2 */ val: /* comment3 */ 0 }); - SimpleStruct memory simple3 = SimpleStruct({ - /* comment4 */ + SimpleStruct memory simple3 = SimpleStruct({ /* comment4 */ // comment5 val: // comment6 - 0 // comment7 - // comment8 + 0 // comment7 + // comment8 }); } } diff --git a/crates/fmt/testdata/NamedFunctionCallExpression/original.sol b/crates/fmt/testdata/NamedFunctionCallExpression/original.sol index 8b34474a7134f..5546c60d6bc40 100644 --- a/crates/fmt/testdata/NamedFunctionCallExpression/original.sol +++ b/crates/fmt/testdata/NamedFunctionCallExpression/original.sol @@ -9,20 +9,20 @@ contract NamedFunctionCallExpression { ComplexStruct memory complex = ComplexStruct({ val: 1, anotherVal: 2, flag: true, timestamp: block.timestamp }); StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting memory long = StructWithAVeryLongNameThatExceedsMaximumLengthThatIsAllowedForFormatting({ whyNameSoLong: "dunno" }); - + SimpleStruct memory simple2 = SimpleStruct( - { // comment1 + { // comment1 /* comment2 */ val : /* comment3 */ 0 - + } ); SimpleStruct memory simple3 = SimpleStruct( - /* comment4 */ { + /* comment4 */ { // comment5 val: // comment6 0 // comment7 // comment8 }); } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/NonKeywords/fmt.sol b/crates/fmt/testdata/NonKeywords/fmt.sol index 9a6a31539d8de..2786450ba571e 100644 --- a/crates/fmt/testdata/NonKeywords/fmt.sol +++ b/crates/fmt/testdata/NonKeywords/fmt.sol @@ -1,13 +1,13 @@ struct S { uint256 error; - // uint256 layout; + uint256 layout; uint256 at; + // uint256 transient; } -// uint256 transient; function f() { uint256 error = 0; - // uint256 layout = 0; + uint256 layout = 0; uint256 at = 0; // uint256 transient = 0; @@ -17,25 +17,26 @@ function f() { // transient = 0; S memory x = S({ + // format error: 0, - // layout: 0, + layout: 0, at: 0 + // transient: 0 }); - // transient: 0 x.error = 0; - // x.layout = 0; + x.layout = 0; x.at = 0; // x.transient = 0; assembly { let error := 0 - // let layout := 0 + let layout := 0 let at := 0 // let transient := 0 error := 0 - // layout := 0 + layout := 0 at := 0 // transient := 0 } diff --git a/crates/fmt/testdata/NonKeywords/original.sol b/crates/fmt/testdata/NonKeywords/original.sol index 010bd3296e406..2786450ba571e 100644 --- a/crates/fmt/testdata/NonKeywords/original.sol +++ b/crates/fmt/testdata/NonKeywords/original.sol @@ -1,13 +1,13 @@ struct S { uint256 error; - // uint256 layout; + uint256 layout; uint256 at; // uint256 transient; } function f() { uint256 error = 0; - // uint256 layout = 0; + uint256 layout = 0; uint256 at = 0; // uint256 transient = 0; @@ -17,25 +17,26 @@ function f() { // transient = 0; S memory x = S({ + // format error: 0, - // layout: 0, + layout: 0, at: 0 // transient: 0 }); x.error = 0; - // x.layout = 0; + x.layout = 0; x.at = 0; // x.transient = 0; assembly { let error := 0 - // let layout := 0 + let layout := 0 let at := 0 // let transient := 0 error := 0 - // layout := 0 + layout := 0 at := 0 // transient := 0 } diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/fmt.sol index 7c9f5740de76a..aef6b27d769cf 100644 --- a/crates/fmt/testdata/NumberLiteralUnderscore/fmt.sol +++ b/crates/fmt/testdata/NumberLiteralUnderscore/fmt.sol @@ -1,25 +1,30 @@ contract NumberLiteral { + bytes4 internal constant HEX_NUM = 0x01ffc9a7; + function test() external { 1; 123_000; - 1_2e345_678; + // 1_2e345_678; // solar error: exponent too large -1; 2e-10; 0.1; 1.3; 2.5e1; 1.23454; - 1.2e34_5_678; - 134411.2e34_5_678; - 13431.134112e34_135_678; + // 1.2e34_5_678; // solar error: exponent too large + // 134411.2e34_5_678; // solar error: exponent too large + // 13431.134112e34_135_678; // solar error: exponent too large 13431.0134112; - 13431.134112e-139_3141340; - 134411.2e34_5_6780; - 13431.134112e34_135_6780; - 0.134112; + // 13431.134112e-139_3141340; // solar error: exponent too large + // 00134411.200e0034_5_6780; // solar error: leading zeros are not allowed in integers + // 013431.13411200e34_135_6780; // solar error: leading zeros are not allowed in integers + // 00.1341120000; // solar error: leading zeros are not allowed in integers 1.0; - 13431.134112e-139_3141340; - 123e456; + // 0013431.13411200e-00139_3141340; // solar error: leading zeros are not allowed in integers + 10_234e56; + 1234e56; + 10000; 1_000; + 5_267.8268764263694426e18; } } diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/original.sol b/crates/fmt/testdata/NumberLiteralUnderscore/original.sol index 8e88fc6d29472..fad579d1316ec 100644 --- a/crates/fmt/testdata/NumberLiteralUnderscore/original.sol +++ b/crates/fmt/testdata/NumberLiteralUnderscore/original.sol @@ -1,25 +1,30 @@ contract NumberLiteral { + bytes4 internal constant HEX_NUM = 0x01ffc9a7; + function test() external { 1; 123_000; - 1_2e345_678; + // 1_2e345_678; // solar error: exponent too large -1; 2e-10; .1; 1.3; 2.5e1; 1.23454e0; - 1.2e34_5_678; - 134411.2e34_5_678; - 13431.134112e34_135_678; + // 1.2e34_5_678; // solar error: exponent too large + // 134411.2e34_5_678; // solar error: exponent too large + // 13431.134112e34_135_678; // solar error: exponent too large 13431.0134112; - 13431.134112e-139_3141340; - 00134411.200e0034_5_6780; - 013431.13411200e34_135_6780; - 00.1341120000; + // 13431.134112e-139_3141340; // solar error: exponent too large + // 00134411.200e0034_5_6780; // solar error: leading zeros are not allowed in integers + // 013431.13411200e34_135_6780; // solar error: leading zeros are not allowed in integers + // 00.1341120000; // solar error: leading zeros are not allowed in integers 1.0000; - 0013431.13411200e-00139_3141340; - 123E456; + // 0013431.13411200e-00139_3141340; // solar error: leading zeros are not allowed in integers + 10_234E56; + 1234E56; + 10000; 1_000; + 5_267.8268764263694426e18; } } diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol index d87dc99d9653d..77dde9162e28d 100644 --- a/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol +++ b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol @@ -1,26 +1,31 @@ // config: number_underscore = "preserve" contract NumberLiteral { + bytes4 internal constant HEX_NUM = 0x01ffc9a7; + function test() external { 1; 123_000; - 1_2e345_678; + // 1_2e345_678; // solar error: exponent too large -1; 2e-10; 0.1; 1.3; 2.5e1; 1.23454; - 1.2e34_5_678; - 134411.2e34_5_678; - 13431.134112e34_135_678; + // 1.2e34_5_678; // solar error: exponent too large + // 134411.2e34_5_678; // solar error: exponent too large + // 13431.134112e34_135_678; // solar error: exponent too large 13431.0134112; - 13431.134112e-139_3141340; - 134411.2e34_5_6780; - 13431.134112e34_135_6780; - 0.134112; + // 13431.134112e-139_3141340; // solar error: exponent too large + // 00134411.200e0034_5_6780; // solar error: leading zeros are not allowed in integers + // 013431.13411200e34_135_6780; // solar error: leading zeros are not allowed in integers + // 00.1341120000; // solar error: leading zeros are not allowed in integers 1.0; - 13431.134112e-139_3141340; - 123e456; + // 0013431.13411200e-00139_3141340; // solar error: leading zeros are not allowed in integers + 10_234e56; + 1234e56; + 10000; 1_000; + 5_267.8268764263694426e18; } } diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol index cbde2e9b9fe61..61b2ce2bd0c3e 100644 --- a/crates/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol +++ b/crates/fmt/testdata/NumberLiteralUnderscore/remove.fmt.sol @@ -1,26 +1,31 @@ // config: number_underscore = "remove" contract NumberLiteral { + bytes4 internal constant HEX_NUM = 0x01ffc9a7; + function test() external { 1; 123000; - 12e345678; + // 1_2e345_678; // solar error: exponent too large -1; 2e-10; 0.1; 1.3; 2.5e1; 1.23454; - 1.2e345678; - 134411.2e345678; - 13431.134112e34135678; + // 1.2e34_5_678; // solar error: exponent too large + // 134411.2e34_5_678; // solar error: exponent too large + // 13431.134112e34_135_678; // solar error: exponent too large 13431.0134112; - 13431.134112e-1393141340; - 134411.2e3456780; - 13431.134112e341356780; - 0.134112; + // 13431.134112e-139_3141340; // solar error: exponent too large + // 00134411.200e0034_5_6780; // solar error: leading zeros are not allowed in integers + // 013431.13411200e34_135_6780; // solar error: leading zeros are not allowed in integers + // 00.1341120000; // solar error: leading zeros are not allowed in integers 1.0; - 13431.134112e-1393141340; - 123e456; + // 0013431.13411200e-00139_3141340; // solar error: leading zeros are not allowed in integers + 10234e56; + 1234e56; + 10000; 1000; + 5267.8268764263694426e18; } } diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol index a9fc8a69ae6fa..261d49b5021a5 100644 --- a/crates/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol +++ b/crates/fmt/testdata/NumberLiteralUnderscore/thousands.fmt.sol @@ -1,26 +1,31 @@ // config: number_underscore = "thousands" contract NumberLiteral { + bytes4 internal constant HEX_NUM = 0x01ffc9a7; + function test() external { 1; 123_000; - 12e345_678; + // 1_2e345_678; // solar error: exponent too large -1; 2e-10; 0.1; 1.3; 2.5e1; - 1.23454; - 1.2e345_678; - 134_411.2e345_678; - 13_431.134112e34_135_678; - 13_431.0134112; - 13_431.134112e-1_393_141_340; - 134_411.2e3_456_780; - 13_431.134112e341_356_780; - 0.134112; + 1.234_54; + // 1.2e34_5_678; // solar error: exponent too large + // 134411.2e34_5_678; // solar error: exponent too large + // 13431.134112e34_135_678; // solar error: exponent too large + 13_431.013_411_2; + // 13431.134112e-139_3141340; // solar error: exponent too large + // 00134411.200e0034_5_6780; // solar error: leading zeros are not allowed in integers + // 013431.13411200e34_135_6780; // solar error: leading zeros are not allowed in integers + // 00.1341120000; // solar error: leading zeros are not allowed in integers 1.0; - 13_431.134112e-1_393_141_340; - 123e456; + // 0013431.13411200e-00139_3141340; // solar error: leading zeros are not allowed in integers + 10_234e56; + 1234e56; + 10_000; 1000; + 5267.8268764263694426e18; } } diff --git a/crates/fmt/testdata/OperatorExpressions/120.fmt.sol b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol new file mode 100644 index 0000000000000..0a6d58cef8698 --- /dev/null +++ b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol @@ -0,0 +1,89 @@ +// config: line_length = 120 +function test() { + uint256 expr001 = (1 + 2) + 3; + uint256 expr002 = 1 + (2 + 3); + uint256 expr003 = 1 * 2 + 3; + uint256 expr004 = (1 * 2) + 3; + uint256 expr005 = 1 * (2 + 3); + uint256 expr006 = 1 + 2 * 3; + uint256 expr007 = (1 + 2) * 3; + uint256 expr008 = 1 + (2 * 3); + uint256 expr009 = 1 ** 2 ** 3; + uint256 expr010 = 1 ** (2 ** 3); + uint256 expr011 = (1 ** 2) ** 3; + uint256 expr012 = ++expr011 + 1; + bool expr013 = ++expr012 == expr011 - 1; + bool expr014 = ++(++expr013)--; + if (++batch.movesPerformed == drivers.length) createNewBatch(); + sum += getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ) / 1e18; + other += 1e18 + / getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ); + if ( + op == 0x54 // SLOAD + || op == 0x55 // SSTORE + || op == 0xF0 // CREATE + || op == 0xF1 // CALL + || op == 0xF2 // CALLCODE + || op == 0xF4 // DELEGATECALL + || op == 0xF5 // CREATE2 + || op == 0xFA // STATICCALL + || op == 0xFF // SELFDESTRUCT + ) return false; +} + +function test_nested() { + require( + keccak256(abi.encodePacked("some long string")) == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + state.zeroForOne = + IERC20(Currency.unwrap(state.poolKey1.currency0)) == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); + + coreAddresses.evc == address(0) && coreAddresses.protocolConfig == address(0) + && coreAddresses.sequenceRegistry == address(0) && coreAddresses.balanceTracker == address(0) + && coreAddresses.permit2 == address(0); + + return spender == ownerOf(tokenId) || getApproved[tokenId] == spender || isApprovedForAll[ownerOf(tokenId)][spender]; +} + +function new_y(uint256 x, uint256 dx, uint256 x_basis, uint256 y, uint256 y_basis) external pure returns (uint256) { + return _get_y( + x * _VELODROME_TOKEN_BASIS / x_basis, + dx * _VELODROME_TOKEN_BASIS / x_basis, + y * _VELODROME_TOKEN_BASIS / y_basis + ) * y_basis / _VELODROME_TOKEN_BASIS * aReallyLongIdentifierThatMakesTheOperatorExpressionBreak; +} + +contract Repro { + bytes4 public constant MINIMAL_INTERFACE_ID = this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector + ^ this.execute.selector ^ this.getMinFeeUSDFor.selector; + bool isTestnet = chainId == ARBITRUM_SEPOLIA || chainId == BASE_SEPOLIA || chainId == MODE_SEPOLIA + || chainId == OPTIMISM_SEPOLIA || chainId == SEPOLIA; + + function test() { + assign = this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector ^ this.execute.selector + ^ this.getMinFeeUSDFor.selector; + isMainnet = chainId == ABSTRACT || chainId == ARBITRUM || chainId == AVALANCHE || chainId == BASE + || chainId == BERACHAIN || chainId == BLAST || chainId == BSC || chainId == CHILIZ || chainId == COREDAO + || chainId == ETHEREUM || chainId == GNOSIS || chainId == HYPEREVM || chainId == LIGHTLINK + || chainId == LINEA || chainId == MODE || chainId == MORPH || chainId == OPTIMISM || chainId == POLYGON + || chainId == SCROLL || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC + || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); + } +} diff --git a/crates/fmt/testdata/OperatorExpressions/fmt.sol b/crates/fmt/testdata/OperatorExpressions/fmt.sol index be0cec9173d55..c913b9859f066 100644 --- a/crates/fmt/testdata/OperatorExpressions/fmt.sol +++ b/crates/fmt/testdata/OperatorExpressions/fmt.sol @@ -41,3 +41,65 @@ function test() { || op == 0xFF // SELFDESTRUCT ) return false; } + +function test_nested() { + require( + keccak256(abi.encodePacked("some long string")) + == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + state.zeroForOne = IERC20(Currency.unwrap(state.poolKey1.currency0)) + == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); + + coreAddresses.evc == address(0) + && coreAddresses.protocolConfig == address(0) + && coreAddresses.sequenceRegistry == address(0) + && coreAddresses.balanceTracker == address(0) + && coreAddresses.permit2 == address(0); + + return spender == ownerOf(tokenId) || getApproved[tokenId] == spender + || isApprovedForAll[ownerOf(tokenId)][spender]; +} + +function new_y( + uint256 x, + uint256 dx, + uint256 x_basis, + uint256 y, + uint256 y_basis +) external pure returns (uint256) { + return _get_y( + x * _VELODROME_TOKEN_BASIS / x_basis, + dx * _VELODROME_TOKEN_BASIS / x_basis, + y * _VELODROME_TOKEN_BASIS / y_basis + ) * y_basis / _VELODROME_TOKEN_BASIS + * aReallyLongIdentifierThatMakesTheOperatorExpressionBreak; +} + +contract Repro { + bytes4 public constant MINIMAL_INTERFACE_ID = + this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector + ^ this.execute.selector ^ this.getMinFeeUSDFor.selector; + bool isTestnet = chainId == ARBITRUM_SEPOLIA || chainId == BASE_SEPOLIA + || chainId == MODE_SEPOLIA || chainId == OPTIMISM_SEPOLIA + || chainId == SEPOLIA; + + function test() { + assign = this.calculateMinFeeWeiFor.selector + ^ this.convertUSDFeeToWei.selector ^ this.execute.selector + ^ this.getMinFeeUSDFor.selector; + isMainnet = chainId == ABSTRACT || chainId == ARBITRUM + || chainId == AVALANCHE || chainId == BASE || chainId == BERACHAIN + || chainId == BLAST || chainId == BSC || chainId == CHILIZ + || chainId == COREDAO || chainId == ETHEREUM || chainId == GNOSIS + || chainId == HYPEREVM || chainId == LIGHTLINK || chainId == LINEA + || chainId == MODE || chainId == MORPH || chainId == OPTIMISM + || chainId == POLYGON || chainId == SCROLL || chainId == SEI + || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC + || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); + } +} diff --git a/crates/fmt/testdata/OperatorExpressions/original.sol b/crates/fmt/testdata/OperatorExpressions/original.sol index c3e788d0a9385..0de13f2149dd0 100644 --- a/crates/fmt/testdata/OperatorExpressions/original.sol +++ b/crates/fmt/testdata/OperatorExpressions/original.sol @@ -7,9 +7,9 @@ function test() { uint256 expr006 = 1 + 2 * 3; uint256 expr007 = (1 + 2) * 3; uint256 expr008 = 1 + (2 * 3); - uint256 expr009 = 1 ** 2 ** 3; - uint256 expr010 = 1 ** (2 ** 3); - uint256 expr011 = (1 ** 2) ** 3; + uint256 expr009 = 1**2 ** 3; + uint256 expr010 = 1**(2 ** 3); + uint256 expr011 = (1**2) ** 3; uint256 expr012 = ++expr011 + 1; bool expr013 = ++expr012 == expr011 - 1; bool expr014 = ++(++expr013)--; @@ -28,3 +28,50 @@ function test() { || op == 0xFF // SELFDESTRUCT ) return false; } + +function test_nested() { + require( + keccak256(abi.encodePacked("some long string")) + == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + state.zeroForOne = IERC20(Currency.unwrap(state.poolKey1.currency0)) + == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); + + coreAddresses.evc == address(0) && coreAddresses.protocolConfig == address(0) + && coreAddresses.sequenceRegistry == address(0) && coreAddresses.balanceTracker == address(0) + && coreAddresses.permit2 == address(0); + + return spender == ownerOf(tokenId) || getApproved[tokenId] == spender + || isApprovedForAll[ownerOf(tokenId)][spender]; +} + +function new_y(uint256 x, uint256 dx, uint256 x_basis, uint256 y, uint256 y_basis) + external + pure + returns (uint256) +{ + return _get_y( + x * _VELODROME_TOKEN_BASIS / x_basis, + dx * _VELODROME_TOKEN_BASIS / x_basis, + y * _VELODROME_TOKEN_BASIS / y_basis + ) * y_basis / _VELODROME_TOKEN_BASIS * aReallyLongIdentifierThatMakesTheOperatorExpressionBreak; +} + +contract Repro { + bytes4 public constant MINIMAL_INTERFACE_ID = this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector ^ this.execute.selector ^ this.getMinFeeUSDFor.selector; + bool isTestnet = chainId == ARBITRUM_SEPOLIA || chainId == BASE_SEPOLIA || chainId == MODE_SEPOLIA || chainId == OPTIMISM_SEPOLIA || chainId == SEPOLIA; + + function test() { + assign = this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector ^ this.execute.selector ^ this.getMinFeeUSDFor.selector; + isMainnet = chainId == ABSTRACT || chainId == ARBITRUM || chainId == AVALANCHE || chainId == BASE + || chainId == BERACHAIN || chainId == BLAST || chainId == BSC || chainId == CHILIZ || chainId == COREDAO + || chainId == ETHEREUM || chainId == GNOSIS || chainId == HYPEREVM || chainId == LIGHTLINK || chainId == LINEA + || chainId == MODE || chainId == MORPH || chainId == OPTIMISM || chainId == POLYGON || chainId == SCROLL + || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC || chainId == UNICHAIN + || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); + } +} diff --git a/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol b/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol new file mode 100644 index 0000000000000..f03f3a382f3de --- /dev/null +++ b/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol @@ -0,0 +1,106 @@ +// config: pow_no_space = true +function test() { + uint256 expr001 = (1 + 2) + 3; + uint256 expr002 = 1 + (2 + 3); + uint256 expr003 = 1 * 2 + 3; + uint256 expr004 = (1 * 2) + 3; + uint256 expr005 = 1 * (2 + 3); + uint256 expr006 = 1 + 2 * 3; + uint256 expr007 = (1 + 2) * 3; + uint256 expr008 = 1 + (2 * 3); + uint256 expr009 = 1**2**3; + uint256 expr010 = 1**(2**3); + uint256 expr011 = (1**2)**3; + uint256 expr012 = ++expr011 + 1; + bool expr013 = ++expr012 == expr011 - 1; + bool expr014 = ++(++expr013)--; + if (++batch.movesPerformed == drivers.length) createNewBatch(); + sum += getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ) / 1e18; + other += 1e18 + / getPrice( + ACCELERATE_STARTING_PRICE, + ACCELERATE_PER_PERIOD_DECREASE, + idleTicks, + actionsSold[ActionType.ACCELERATE] + i, + ACCELERATE_SELL_PER_TICK + ); + if ( + op == 0x54 // SLOAD + || op == 0x55 // SSTORE + || op == 0xF0 // CREATE + || op == 0xF1 // CALL + || op == 0xF2 // CALLCODE + || op == 0xF4 // DELEGATECALL + || op == 0xF5 // CREATE2 + || op == 0xFA // STATICCALL + || op == 0xFF // SELFDESTRUCT + ) return false; +} + +function test_nested() { + require( + keccak256(abi.encodePacked("some long string")) + == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + state.zeroForOne = IERC20(Currency.unwrap(state.poolKey1.currency0)) + == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); + + coreAddresses.evc == address(0) + && coreAddresses.protocolConfig == address(0) + && coreAddresses.sequenceRegistry == address(0) + && coreAddresses.balanceTracker == address(0) + && coreAddresses.permit2 == address(0); + + return spender == ownerOf(tokenId) || getApproved[tokenId] == spender + || isApprovedForAll[ownerOf(tokenId)][spender]; +} + +function new_y( + uint256 x, + uint256 dx, + uint256 x_basis, + uint256 y, + uint256 y_basis +) external pure returns (uint256) { + return _get_y( + x * _VELODROME_TOKEN_BASIS / x_basis, + dx * _VELODROME_TOKEN_BASIS / x_basis, + y * _VELODROME_TOKEN_BASIS / y_basis + ) * y_basis / _VELODROME_TOKEN_BASIS + * aReallyLongIdentifierThatMakesTheOperatorExpressionBreak; +} + +contract Repro { + bytes4 public constant MINIMAL_INTERFACE_ID = + this.calculateMinFeeWeiFor.selector ^ this.convertUSDFeeToWei.selector + ^ this.execute.selector ^ this.getMinFeeUSDFor.selector; + bool isTestnet = chainId == ARBITRUM_SEPOLIA || chainId == BASE_SEPOLIA + || chainId == MODE_SEPOLIA || chainId == OPTIMISM_SEPOLIA + || chainId == SEPOLIA; + + function test() { + assign = this.calculateMinFeeWeiFor.selector + ^ this.convertUSDFeeToWei.selector ^ this.execute.selector + ^ this.getMinFeeUSDFor.selector; + isMainnet = chainId == ABSTRACT || chainId == ARBITRUM + || chainId == AVALANCHE || chainId == BASE || chainId == BERACHAIN + || chainId == BLAST || chainId == BSC || chainId == CHILIZ + || chainId == COREDAO || chainId == ETHEREUM || chainId == GNOSIS + || chainId == HYPEREVM || chainId == LIGHTLINK || chainId == LINEA + || chainId == MODE || chainId == MORPH || chainId == OPTIMISM + || chainId == POLYGON || chainId == SCROLL || chainId == SEI + || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC + || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); + } +} diff --git a/crates/fmt/testdata/Repros/fmt.sol b/crates/fmt/testdata/Repros/fmt.sol index 0a480c0b02bdf..771c86e6a641e 100644 --- a/crates/fmt/testdata/Repros/fmt.sol +++ b/crates/fmt/testdata/Repros/fmt.sol @@ -1,5 +1,13 @@ // Repros of fmt issues +// https://github.com/foundry-rs/foundry/issues/7944 +import {ERC20} from "@contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import {ERC20Burnable} from "@contracts/token/ERC20/ext/ERC20Burnable.sol"; +import {IERC20} from "@contracts/token/ERC20/IERC20.sol"; +import {IERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import {AccessControl} from "@contracts/access/AccessControl.sol"; + // https://github.com/foundry-rs/foundry/issues/4403 function errorIdentifier() { bytes memory error = bytes(""); @@ -11,9 +19,7 @@ function one() external { this.other({ data: abi.encodeCall( this.other, - ( - "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" - ) + ("bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla") ) }); } @@ -48,15 +54,15 @@ contract TestContract { } function test1() public { - assembly{ sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line + assembly { sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line sstore(2, 2) } } function test2() public { - assembly{ sstore( 1, 1) // forgefmt: disable-line + assembly { sstore( 1, 1) // forgefmt: disable-line sstore(2, 2) - sstore(3, 3)// forgefmt: disable-line + sstore(3, 3) // forgefmt: disable-line sstore(4, 4) } } @@ -65,19 +71,19 @@ contract TestContract { // forgefmt: disable-next-line assembly{ sstore( 1, 1) sstore(2, 2) - sstore(3, 3)// forgefmt: disable-line + sstore(3, 3) // forgefmt: disable-line sstore(4, 4) - }// forgefmt: disable-line + } // forgefmt: disable-line } function test4() public { // forgefmt: disable-next-line - assembly{ + assembly { sstore(1, 1) sstore(2, 2) - sstore(3, 3)// forgefmt: disable-line + sstore(3, 3) // forgefmt: disable-line sstore(4, 4) - }// forgefmt: disable-line + } // forgefmt: disable-line if (condition) execute(); // comment7 } @@ -159,3 +165,265 @@ contract DbgFmtTest is Test { return 0; } } + +// https://github.com/foundry-rs/foundry/issues/8557 +// https://github.com/foundry-rs/foundry/issues/11249 +function argListRepro(address tokenIn, uint256 amountIn, bool data) { + maverickV2SwapCallback( + tokenIn, + amountIn, // forgefmt: disable-line + // forgefmt: disable-next-line + 0 /* we didn't bother loading `amountOut` because we don't use it */, + data + ); +} + +// https://github.com/foundry-rs/foundry/issues/11905 +function noBlanksLinesBeforeIdentifiers() public { + timelockController.grantRole(keccak256("EXECUTOR_ROLE"), address(0)); +} + +// https://github.com/foundry-rs/foundry/issues/11913 +function rustfmtBlankLinesInStmtBlocks() public { + if (someCondition) { + bar = true; + + emit Foo(bar); + } +} + +contract NestedCallsTest is Test { + string constant errMsg = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_nestedCalls() public { + vm._expectCheatcodeRevert( + bytes(string.concat(errMsg, ": ", left, " != ", right)) + ); + } + + function test_assemblyFnComments() public { + assembly { + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + } + } + + function test_binOpsInsideNestedBlocks() public { + for (uint256 i = 0; i < steps.length; i++) { + if ( + step.opcode == 0x52 + && /*MSTORE*/ step.stack[0] == testContract.memPtr() // MSTORE offset + && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val + ) { + mstoreCalled = true; + } + } + } +} + +contract ERC1967Factory { + /// @dev Returns a pointer to the initialization code of a proxy created via this factory. + function _initCode() internal view returns (bytes32 m) { + assembly { + /** + * -------------------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * -------------------------------------------------------------------------------------| + * RUNTIME (127 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * | + * ::: check if caller is factory ::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 33 | CALLER | c 0 0 | | + * 73 factory | PUSH20 factory | f c 0 0 | | + * 14 | EQ | isf 0 0 | | + * 60 0x57 | PUSH1 0x57 | dest isf 0 0 | | + * 57 | JUMPI | 0 0 | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: set new implementation (caller is factory) ::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 35 | CALLDATALOAD | impl 0 0 | | + * 60 0x20 | PUSH1 0x20 | w impl 0 0 | | + * 35 | CALLDATALOAD | slot impl 0 0 | | + * 55 | SSTORE | 0 0 | | + * | + * ::: no extra calldata, return :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x40 | PUSH1 0x40 | 2w 0 0 | | + * 80 | DUP1 | 2w 2w 0 0 | | + * 36 | CALLDATASIZE | cds 2w 2w 0 0 | | + * 11 | GT | gt 2w 0 0 | | + * 15 | ISZERO | lte 2w 0 0 | | + * 60 0x52 | PUSH1 0x52 | dest lte 2w 0 0 | | + * 57 | JUMPI | 2w 0 0 | | + * | + * ::: copy extra calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 2w 0 0 | | + * 03 | SUB | t 0 0 | | + * 80 | DUP1 | t t 0 0 | | + * 60 0x40 | PUSH1 0x40 | 2w t t 0 0 | | + * 3d | RETURNDATASIZE | 0 2w t t 0 0 | | + * 37 | CALLDATACOPY | t 0 0 | [0..t): extra calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 t 0 0 | [0..t): extra calldata | + * 3d | RETURNDATASIZE | 0 0 t 0 0 | [0..t): extra calldata | + * 35 | CALLDATALOAD | i 0 t 0 0 | [0..t): extra calldata | + * 5a | GAS | g i 0 t 0 0 | [0..t): extra calldata | + * f4 | DELEGATECALL | succ | [0..t): extra calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..t): extra calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..t): extra calldata | + * 80 | DUP1 | 0 0 rds succ | [0..t): extra calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * -------------------------------------------------------------------------------------+ + */ + m := mload(0x40) + // forgefmt: disable-start + switch shr(112, address()) + case 0 { + // If the factory's address has six or more leading zero bytes. + mstore(add(m, 0x75), 0x604c573d6000fd) // 7 + mstore(add(m, 0x6e), 0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x4e), 0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x2e), 0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x0e), address()) // 14 + mstore(m, 0x60793d8160093d39f33d3d336d) // 9 + 4 + } + default { + mstore(add(m, 0x7b), 0x6052573d6000fd) // 7 + mstore(add(m, 0x74), 0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x54), 0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x34), 0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x14), address()) // 20 + mstore(m, 0x607f3d8160093d39f33d3d3373) // 9 + 4 + } + // forgefmt: disable-end + } + } +} + +/// @title Wrapped Ether Hook +/// @notice Hook for wrapping/unwrapping ETH in Uniswap V4 pools +/// @dev Implements 1:1 wrapping/unwrapping of ETH to WETH +contract WETHHook is BaseTokenWrapperHook { + /// @notice The WETH9 contract + WETH public immutable weth; + + /// @notice Creates a new WETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _weth The WETH9 contract address + constructor(IPoolManager _manager, address payable _weth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(_weth), // wrapper token is WETH + CurrencyLibrary.ADDRESS_ZERO // underlying token is ETH (address(0)) + ) + { + weth = WETH(payable(_weth)); + } +} + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/original.sol b/crates/fmt/testdata/Repros/original.sol index 6f18784d39dea..0a37ed6f9807d 100644 --- a/crates/fmt/testdata/Repros/original.sol +++ b/crates/fmt/testdata/Repros/original.sol @@ -1,5 +1,13 @@ // Repros of fmt issues +// https://github.com/foundry-rs/foundry/issues/7944 +import { ERC20 } from "@contracts/token/ERC20/ERC20.sol"; +import { ERC20Permit } from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import { ERC20Burnable } from "@contracts/token/ERC20/ext/ERC20Burnable.sol"; +import { IERC20 } from "@contracts/token/ERC20/IERC20.sol"; +import { IERC20Permit } from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import { AccessControl } from "@contracts/access/AccessControl.sol"; + // https://github.com/foundry-rs/foundry/issues/4403 function errorIdentifier() { bytes memory error = bytes(""); @@ -158,3 +166,261 @@ contract DbgFmtTest is Test { return 0; } } + +// https://github.com/foundry-rs/foundry/issues/8557 +// https://github.com/foundry-rs/foundry/issues/11249 +function argListRepro(address tokenIn, uint256 amountIn, bool data) { + maverickV2SwapCallback( + tokenIn, + amountIn, // forgefmt: disable-line + // forgefmt: disable-next-line + 0 /* we didn't bother loading `amountOut` because we don't use it */, + data + ); +} + +// https://github.com/foundry-rs/foundry/issues/11905 +function noBlanksLinesBeforeIdentifiers() public { + timelockController + + + + .grantRole(keccak256("EXECUTOR_ROLE"), address(0)); +} + +// https://github.com/foundry-rs/foundry/issues/11913 +function rustfmtBlankLinesInStmtBlocks() public { + if (someCondition) { + + + + bar = true; + + + + emit Foo(bar); + + + } +} + +contract NestedCallsTest is Test { + string constant errMsg = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_nestedCalls() public { + vm._expectCheatcodeRevert( + bytes(string.concat(errMsg, ": ", left, " != ", right)) + ); + } + + function test_assemblyFnComments() public { + assembly { + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + } + } + + function test_binOpsInsideNestedBlocks() public { + for (uint256 i = 0; i < steps.length; i++) { + if ( + step.opcode == 0x52 + && /*MSTORE*/ step.stack[0] == testContract.memPtr() // MSTORE offset + && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val + ) { + mstoreCalled = true; + } + } + } +} + +contract ERC1967Factory { + /// @dev Returns a pointer to the initialization code of a proxy created via this factory. + function _initCode() internal view returns (bytes32 m) { + assembly { + /** + * -------------------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * -------------------------------------------------------------------------------------| + * RUNTIME (127 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * | + * ::: check if caller is factory ::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 33 | CALLER | c 0 0 | | + * 73 factory | PUSH20 factory | f c 0 0 | | + * 14 | EQ | isf 0 0 | | + * 60 0x57 | PUSH1 0x57 | dest isf 0 0 | | + * 57 | JUMPI | 0 0 | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: set new implementation (caller is factory) ::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 35 | CALLDATALOAD | impl 0 0 | | + * 60 0x20 | PUSH1 0x20 | w impl 0 0 | | + * 35 | CALLDATALOAD | slot impl 0 0 | | + * 55 | SSTORE | 0 0 | | + * | + * ::: no extra calldata, return :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x40 | PUSH1 0x40 | 2w 0 0 | | + * 80 | DUP1 | 2w 2w 0 0 | | + * 36 | CALLDATASIZE | cds 2w 2w 0 0 | | + * 11 | GT | gt 2w 0 0 | | + * 15 | ISZERO | lte 2w 0 0 | | + * 60 0x52 | PUSH1 0x52 | dest lte 2w 0 0 | | + * 57 | JUMPI | 2w 0 0 | | + * | + * ::: copy extra calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 2w 0 0 | | + * 03 | SUB | t 0 0 | | + * 80 | DUP1 | t t 0 0 | | + * 60 0x40 | PUSH1 0x40 | 2w t t 0 0 | | + * 3d | RETURNDATASIZE | 0 2w t t 0 0 | | + * 37 | CALLDATACOPY | t 0 0 | [0..t): extra calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 t 0 0 | [0..t): extra calldata | + * 3d | RETURNDATASIZE | 0 0 t 0 0 | [0..t): extra calldata | + * 35 | CALLDATALOAD | i 0 t 0 0 | [0..t): extra calldata | + * 5a | GAS | g i 0 t 0 0 | [0..t): extra calldata | + * f4 | DELEGATECALL | succ | [0..t): extra calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..t): extra calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..t): extra calldata | + * 80 | DUP1 | 0 0 rds succ | [0..t): extra calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * -------------------------------------------------------------------------------------+ + */ + m := mload(0x40) + // forgefmt: disable-start + switch shr(112, address()) + case 0 { + // If the factory's address has six or more leading zero bytes. + mstore(add(m, 0x75), 0x604c573d6000fd) // 7 + mstore(add(m, 0x6e), 0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x4e), 0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x2e), 0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x0e), address()) // 14 + mstore(m, 0x60793d8160093d39f33d3d336d) // 9 + 4 + } + default { + mstore(add(m, 0x7b), 0x6052573d6000fd) // 7 + mstore(add(m, 0x74), 0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x54), 0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x34), 0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x14), address()) // 20 + mstore(m, 0x607f3d8160093d39f33d3d3373) // 9 + 4 + } + // forgefmt: disable-end + } + } +} + +/// @title Wrapped Ether Hook +/// @notice Hook for wrapping/unwrapping ETH in Uniswap V4 pools +/// @dev Implements 1:1 wrapping/unwrapping of ETH to WETH +contract WETHHook is BaseTokenWrapperHook { + /// @notice The WETH9 contract + WETH public immutable weth; + + /// @notice Creates a new WETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _weth The WETH9 contract address + constructor(IPoolManager _manager, address payable _weth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(_weth), // wrapper token is WETH + CurrencyLibrary.ADDRESS_ZERO // underlying token is ETH (address(0)) + ) + { + weth = WETH(payable(_weth)); + } +} + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { bytes32 sender; address receiver; address token; uint256 amount; } + function pack(TransferMessage memory m) internal pure returns (bytes memory) { return ""; } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, receiver: someAddressValue, token: someAddressValue, amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/sorted.fmt.sol b/crates/fmt/testdata/Repros/sorted.fmt.sol new file mode 100644 index 0000000000000..0d56601d6c098 --- /dev/null +++ b/crates/fmt/testdata/Repros/sorted.fmt.sol @@ -0,0 +1,430 @@ +// config: sort_imports = true +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/7944 +import {AccessControl} from "@contracts/access/AccessControl.sol"; +import {ERC20} from "@contracts/token/ERC20/ERC20.sol"; +import {IERC20} from "@contracts/token/ERC20/IERC20.sol"; +import {ERC20Burnable} from "@contracts/token/ERC20/ext/ERC20Burnable.sol"; +import {ERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import {IERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ("bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla") + ) + }); +} + +// https://github.com/foundry-rs/foundry/issues/3979 +contract Format { + bool public test; + + function testing(uint256 amount) public payable { + if ( + // This is a comment + msg.value == amount + ) { + test = true; + } else { + test = false; + } + + if ( + // Another one + block.timestamp >= amount + ) {} + } +} + +// https://github.com/foundry-rs/foundry/issues/3830 +contract TestContract { + function test(uint256 a) public { + if (a > 1) { + a = 2; + } // forgefmt: disable-line + } + + function test1() public { + assembly { sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line + sstore(2, 2) + } + } + + function test2() public { + assembly { sstore( 1, 1) // forgefmt: disable-line + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } + } + + function test3() public { + // forgefmt: disable-next-line + assembly{ sstore( 1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } // forgefmt: disable-line + } + + function test4() public { + // forgefmt: disable-next-line + assembly { + sstore(1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } // forgefmt: disable-line + if (condition) execute(); // comment7 + } + + function test5() public { + assembly { sstore(0, 0) }// forgefmt: disable-line + } + + function test6() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + } + return true ; } // forgefmt: disable-line + + function test7() returns (bool) { // forgefmt: disable-line + if (true) { // forgefmt: disable-line + uint256 a = 1; // forgefmt: disable-line + } + return true; + } + + function test8() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + uint256 a = 1; + } else { + uint256 b = 1; // forgefmt: disable-line + } + return true; + } +} + +// https://github.com/foundry-rs/foundry/issues/5825 +library MyLib { + bytes32 private constant TYPE_HASH = keccak256( + // forgefmt: disable-start + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + // forgefmt: disable-end + ); + + bytes32 private constant TYPE_HASH_1 = keccak256( + "MyStruct(" "uint8 myEnum," "address myAddress" ")" // forgefmt: disable-line + ); + + // forgefmt: disable-start + bytes32 private constant TYPE_HASH_2 = keccak256( + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + ); + // forgefmt: disable-end +} + +contract IfElseTest { + function setNumber(uint256 newNumber) public { + number = newNumber; + if (newNumber = 1) { + number = 1; + } else if (newNumber = 2) { + // number = 2; + } else { + newNumber = 3; + } + } +} + +contract DbgFmtTest is Test { + function test_argsList() public { + uint256 result1 = internalNoArgs({}); + result2 = add({a: 1, b: 2}); + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function internalNoArgs() internal pure returns (uint256) { + return 0; + } +} + +// https://github.com/foundry-rs/foundry/issues/8557 +// https://github.com/foundry-rs/foundry/issues/11249 +function argListRepro(address tokenIn, uint256 amountIn, bool data) { + maverickV2SwapCallback( + tokenIn, + amountIn, // forgefmt: disable-line + // forgefmt: disable-next-line + 0 /* we didn't bother loading `amountOut` because we don't use it */, + data + ); +} + +// https://github.com/foundry-rs/foundry/issues/11905 +function noBlanksLinesBeforeIdentifiers() public { + timelockController.grantRole(keccak256("EXECUTOR_ROLE"), address(0)); +} + +// https://github.com/foundry-rs/foundry/issues/11913 +function rustfmtBlankLinesInStmtBlocks() public { + if (someCondition) { + bar = true; + + emit Foo(bar); + } +} + +contract NestedCallsTest is Test { + string constant errMsg = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_nestedCalls() public { + vm._expectCheatcodeRevert( + bytes(string.concat(errMsg, ": ", left, " != ", right)) + ); + } + + function test_assemblyFnComments() public { + assembly { + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + } + } + + function test_binOpsInsideNestedBlocks() public { + for (uint256 i = 0; i < steps.length; i++) { + if ( + step.opcode == 0x52 + && /*MSTORE*/ step.stack[0] == testContract.memPtr() // MSTORE offset + && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val + ) { + mstoreCalled = true; + } + } + } +} + +contract ERC1967Factory { + /// @dev Returns a pointer to the initialization code of a proxy created via this factory. + function _initCode() internal view returns (bytes32 m) { + assembly { + /** + * -------------------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * -------------------------------------------------------------------------------------| + * RUNTIME (127 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * | + * ::: check if caller is factory ::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 33 | CALLER | c 0 0 | | + * 73 factory | PUSH20 factory | f c 0 0 | | + * 14 | EQ | isf 0 0 | | + * 60 0x57 | PUSH1 0x57 | dest isf 0 0 | | + * 57 | JUMPI | 0 0 | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: set new implementation (caller is factory) ::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 35 | CALLDATALOAD | impl 0 0 | | + * 60 0x20 | PUSH1 0x20 | w impl 0 0 | | + * 35 | CALLDATALOAD | slot impl 0 0 | | + * 55 | SSTORE | 0 0 | | + * | + * ::: no extra calldata, return :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x40 | PUSH1 0x40 | 2w 0 0 | | + * 80 | DUP1 | 2w 2w 0 0 | | + * 36 | CALLDATASIZE | cds 2w 2w 0 0 | | + * 11 | GT | gt 2w 0 0 | | + * 15 | ISZERO | lte 2w 0 0 | | + * 60 0x52 | PUSH1 0x52 | dest lte 2w 0 0 | | + * 57 | JUMPI | 2w 0 0 | | + * | + * ::: copy extra calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 2w 0 0 | | + * 03 | SUB | t 0 0 | | + * 80 | DUP1 | t t 0 0 | | + * 60 0x40 | PUSH1 0x40 | 2w t t 0 0 | | + * 3d | RETURNDATASIZE | 0 2w t t 0 0 | | + * 37 | CALLDATACOPY | t 0 0 | [0..t): extra calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 t 0 0 | [0..t): extra calldata | + * 3d | RETURNDATASIZE | 0 0 t 0 0 | [0..t): extra calldata | + * 35 | CALLDATALOAD | i 0 t 0 0 | [0..t): extra calldata | + * 5a | GAS | g i 0 t 0 0 | [0..t): extra calldata | + * f4 | DELEGATECALL | succ | [0..t): extra calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..t): extra calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..t): extra calldata | + * 80 | DUP1 | 0 0 rds succ | [0..t): extra calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * -------------------------------------------------------------------------------------+ + */ + m := mload(0x40) + // forgefmt: disable-start + switch shr(112, address()) + case 0 { + // If the factory's address has six or more leading zero bytes. + mstore(add(m, 0x75), 0x604c573d6000fd) // 7 + mstore(add(m, 0x6e), 0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x4e), 0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x2e), 0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x0e), address()) // 14 + mstore(m, 0x60793d8160093d39f33d3d336d) // 9 + 4 + } + default { + mstore(add(m, 0x7b), 0x6052573d6000fd) // 7 + mstore(add(m, 0x74), 0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x54), 0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x34), 0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x14), address()) // 20 + mstore(m, 0x607f3d8160093d39f33d3d3373) // 9 + 4 + } + // forgefmt: disable-end + } + } +} + +/// @title Wrapped Ether Hook +/// @notice Hook for wrapping/unwrapping ETH in Uniswap V4 pools +/// @dev Implements 1:1 wrapping/unwrapping of ETH to WETH +contract WETHHook is BaseTokenWrapperHook { + /// @notice The WETH9 contract + WETH public immutable weth; + + /// @notice Creates a new WETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _weth The WETH9 contract address + constructor(IPoolManager _manager, address payable _weth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(_weth), // wrapper token is WETH + CurrencyLibrary.ADDRESS_ZERO // underlying token is ETH (address(0)) + ) + { + weth = WETH(payable(_weth)); + } +} + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/Repros/tab.fmt.sol b/crates/fmt/testdata/Repros/tab.fmt.sol new file mode 100644 index 0000000000000..b423e45507513 --- /dev/null +++ b/crates/fmt/testdata/Repros/tab.fmt.sol @@ -0,0 +1,430 @@ +// config: style = "tab" +// Repros of fmt issues + +// https://github.com/foundry-rs/foundry/issues/7944 +import {ERC20} from "@contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import {ERC20Burnable} from "@contracts/token/ERC20/ext/ERC20Burnable.sol"; +import {IERC20} from "@contracts/token/ERC20/IERC20.sol"; +import {IERC20Permit} from "@contracts/token/ERC20/ext/ERC20Permit.sol"; +import {AccessControl} from "@contracts/access/AccessControl.sol"; + +// https://github.com/foundry-rs/foundry/issues/4403 +function errorIdentifier() { + bytes memory error = bytes(""); + if (error.length > 0) {} +} + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ("bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla") + ) + }); +} + +// https://github.com/foundry-rs/foundry/issues/3979 +contract Format { + bool public test; + + function testing(uint256 amount) public payable { + if ( + // This is a comment + msg.value == amount + ) { + test = true; + } else { + test = false; + } + + if ( + // Another one + block.timestamp >= amount + ) {} + } +} + +// https://github.com/foundry-rs/foundry/issues/3830 +contract TestContract { + function test(uint256 a) public { + if (a > 1) { + a = 2; + } // forgefmt: disable-line + } + + function test1() public { + assembly { sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line + sstore(2, 2) + } + } + + function test2() public { + assembly { sstore( 1, 1) // forgefmt: disable-line + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } + } + + function test3() public { + // forgefmt: disable-next-line + assembly{ sstore( 1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } // forgefmt: disable-line + } + + function test4() public { + // forgefmt: disable-next-line + assembly { + sstore(1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } // forgefmt: disable-line + if (condition) execute(); // comment7 + } + + function test5() public { + assembly { sstore(0, 0) }// forgefmt: disable-line + } + + function test6() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + } + return true ; } // forgefmt: disable-line + + function test7() returns (bool) { // forgefmt: disable-line + if (true) { // forgefmt: disable-line + uint256 a = 1; // forgefmt: disable-line + } + return true; + } + + function test8() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + uint256 a = 1; + } else { + uint256 b = 1; // forgefmt: disable-line + } + return true; + } +} + +// https://github.com/foundry-rs/foundry/issues/5825 +library MyLib { + bytes32 private constant TYPE_HASH = keccak256( + // forgefmt: disable-start + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + // forgefmt: disable-end + ); + + bytes32 private constant TYPE_HASH_1 = keccak256( + "MyStruct(" "uint8 myEnum," "address myAddress" ")" // forgefmt: disable-line + ); + + // forgefmt: disable-start + bytes32 private constant TYPE_HASH_2 = keccak256( + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + ); + // forgefmt: disable-end +} + +contract IfElseTest { + function setNumber(uint256 newNumber) public { + number = newNumber; + if (newNumber = 1) { + number = 1; + } else if (newNumber = 2) { + // number = 2; + } else { + newNumber = 3; + } + } +} + +contract DbgFmtTest is Test { + function test_argsList() public { + uint256 result1 = internalNoArgs({}); + result2 = add({a: 1, b: 2}); + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function internalNoArgs() internal pure returns (uint256) { + return 0; + } +} + +// https://github.com/foundry-rs/foundry/issues/8557 +// https://github.com/foundry-rs/foundry/issues/11249 +function argListRepro(address tokenIn, uint256 amountIn, bool data) { + maverickV2SwapCallback( + tokenIn, + amountIn, // forgefmt: disable-line + // forgefmt: disable-next-line + 0 /* we didn't bother loading `amountOut` because we don't use it */, + data + ); +} + +// https://github.com/foundry-rs/foundry/issues/11905 +function noBlanksLinesBeforeIdentifiers() public { + timelockController.grantRole(keccak256("EXECUTOR_ROLE"), address(0)); +} + +// https://github.com/foundry-rs/foundry/issues/11913 +function rustfmtBlankLinesInStmtBlocks() public { + if (someCondition) { + bar = true; + + emit Foo(bar); + } +} + +contract NestedCallsTest is Test { + string constant errMsg = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_nestedCalls() public { + vm._expectCheatcodeRevert( + bytes(string.concat(errMsg, ": ", left, " != ", right)) + ); + } + + function test_assemblyFnComments() public { + assembly { + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + } + } + + function test_binOpsInsideNestedBlocks() public { + for (uint256 i = 0; i < steps.length; i++) { + if ( + step.opcode == 0x52 + && /*MSTORE*/ step.stack[0] == testContract.memPtr() // MSTORE offset + && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val + ) { + mstoreCalled = true; + } + } + } +} + +contract ERC1967Factory { + /// @dev Returns a pointer to the initialization code of a proxy created via this factory. + function _initCode() internal view returns (bytes32 m) { + assembly { + /** + * -------------------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * -------------------------------------------------------------------------------------| + * RUNTIME (127 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * | + * ::: check if caller is factory ::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 33 | CALLER | c 0 0 | | + * 73 factory | PUSH20 factory | f c 0 0 | | + * 14 | EQ | isf 0 0 | | + * 60 0x57 | PUSH1 0x57 | dest isf 0 0 | | + * 57 | JUMPI | 0 0 | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: set new implementation (caller is factory) ::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 35 | CALLDATALOAD | impl 0 0 | | + * 60 0x20 | PUSH1 0x20 | w impl 0 0 | | + * 35 | CALLDATALOAD | slot impl 0 0 | | + * 55 | SSTORE | 0 0 | | + * | + * ::: no extra calldata, return :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x40 | PUSH1 0x40 | 2w 0 0 | | + * 80 | DUP1 | 2w 2w 0 0 | | + * 36 | CALLDATASIZE | cds 2w 2w 0 0 | | + * 11 | GT | gt 2w 0 0 | | + * 15 | ISZERO | lte 2w 0 0 | | + * 60 0x52 | PUSH1 0x52 | dest lte 2w 0 0 | | + * 57 | JUMPI | 2w 0 0 | | + * | + * ::: copy extra calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 2w 0 0 | | + * 03 | SUB | t 0 0 | | + * 80 | DUP1 | t t 0 0 | | + * 60 0x40 | PUSH1 0x40 | 2w t t 0 0 | | + * 3d | RETURNDATASIZE | 0 2w t t 0 0 | | + * 37 | CALLDATACOPY | t 0 0 | [0..t): extra calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 t 0 0 | [0..t): extra calldata | + * 3d | RETURNDATASIZE | 0 0 t 0 0 | [0..t): extra calldata | + * 35 | CALLDATALOAD | i 0 t 0 0 | [0..t): extra calldata | + * 5a | GAS | g i 0 t 0 0 | [0..t): extra calldata | + * f4 | DELEGATECALL | succ | [0..t): extra calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..t): extra calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..t): extra calldata | + * 80 | DUP1 | 0 0 rds succ | [0..t): extra calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * -------------------------------------------------------------------------------------+ + */ + m := mload(0x40) + // forgefmt: disable-start + switch shr(112, address()) + case 0 { + // If the factory's address has six or more leading zero bytes. + mstore(add(m, 0x75), 0x604c573d6000fd) // 7 + mstore(add(m, 0x6e), 0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x4e), 0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x2e), 0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x0e), address()) // 14 + mstore(m, 0x60793d8160093d39f33d3d336d) // 9 + 4 + } + default { + mstore(add(m, 0x7b), 0x6052573d6000fd) // 7 + mstore(add(m, 0x74), 0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x54), 0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x34), 0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x14), address()) // 20 + mstore(m, 0x607f3d8160093d39f33d3d3373) // 9 + 4 + } + // forgefmt: disable-end + } + } +} + +/// @title Wrapped Ether Hook +/// @notice Hook for wrapping/unwrapping ETH in Uniswap V4 pools +/// @dev Implements 1:1 wrapping/unwrapping of ETH to WETH +contract WETHHook is BaseTokenWrapperHook { + /// @notice The WETH9 contract + WETH public immutable weth; + + /// @notice Creates a new WETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _weth The WETH9 contract address + constructor(IPoolManager _manager, address payable _weth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(_weth), // wrapper token is WETH + CurrencyLibrary.ADDRESS_ZERO // underlying token is ETH (address(0)) + ) + { + weth = WETH(payable(_weth)); + } +} + +// https://github.com/foundry-rs/foundry/issues/12529 +library TransferMessageLib { + struct TransferMessage { + bytes32 sender; + address receiver; + address token; + uint256 amount; + } + + function pack(TransferMessage memory m) + internal + pure + returns (bytes memory) + { + return ""; + } +} + +contract ChainedStructCall { + using TransferMessageLib for TransferMessageLib.TransferMessage; + bytes32 someBytes32Value; + address someAddressValue; + uint256 someUint256Value; + + function test_chainedStructIndentation() public { + bytes memory payload = TransferMessageLib.TransferMessage({ + sender: someBytes32Value, + receiver: someAddressValue, + token: someAddressValue, + amount: someUint256Value + }).pack(); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/110.fmt.sol b/crates/fmt/testdata/ReprosCalls/110.fmt.sol new file mode 100644 index 0000000000000..47ba38b2fa656 --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/110.fmt.sol @@ -0,0 +1,166 @@ +// config: line_length = 110 +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) + == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = + LerpFactoryLike(lerpFab()).newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = execute( + oracleRouterFactory, deployRouterForOracle, eVaultFactory, upgradable, asset, oracle, unitOfAccount + ); + + if (eVault == address(0)) { + eVault = address( + GenericFactory(eVaultFactory) + .createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0))) + ); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = abi.encode( + LidoOracleInfo({base: IOracle(oracleAddress).WSTETH(), quote: IOracle(oracleAddress).STETH()}) + ); + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry) + .add(adapter, LidoFundamentalOracle(adapter).WSTETH(), LidoFundamentalOracle(adapter).WETH()); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory).implementation() + .staticcall(abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0))); + + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall(IERC4626.withdraw, (1500e18, address(swapper), user)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall( + swapVerifier.verifyDebtMax, (address(eSTETH), user, exactOutTolerance, type(uint256).max) + ) + }); + + uint256 fork = vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = new ConstructorVictim(sender, "msg.sender", "not set during prank"); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert("failed parsing as `uint256`: missing hex prefix for hex string"); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks.willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer(Create3.predict(_salt, address(_deployer)), address(o), id); + + return _verifyDeploymentRootHash(_getMerkleRoot(proof, hash), originalOwner) + .ternary(IERC1271.isValidSignature.selector, bytes4(0xffffffff)); +} + +function returnLongBinaryOp() returns (bytes32) { + return bytes32( + uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce)) + ); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = + Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + uint256 globalBuyAmount = + Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + + { + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), buyToken: IERC20(address(0)), minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), buyToken: IERC20(address(0)), minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector + .revertWith(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)); + + nestedStruct.withCalls.thatCause + .aBreak( + param1, + param2, + param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = LpPosition({ + tickLower: posTickLower, tickUpper: posTickUpper, liquidity: lpTailLiquidity, id: uint16(id) + }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool(IPoolManager.ModifyLiquidityParams memory paramSeed) + public + { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/120.fmt.sol b/crates/fmt/testdata/ReprosCalls/120.fmt.sol new file mode 100644 index 0000000000000..7615c199ccc54 --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/120.fmt.sol @@ -0,0 +1,152 @@ +// config: line_length = 120 +// config: bracket_spacing = true +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = LerpFactoryLike(lerpFab()).newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = + execute(oracleRouterFactory, deployRouterForOracle, eVaultFactory, upgradable, asset, oracle, unitOfAccount); + + if (eVault == address(0)) { + eVault = address( + GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0))) + ); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = + abi.encode(LidoOracleInfo({ base: IOracle(oracleAddress).WSTETH(), quote: IOracle(oracleAddress).STETH() })); + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry) + .add(adapter, LidoFundamentalOracle(adapter).WSTETH(), LidoFundamentalOracle(adapter).WETH()); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory).implementation() + .staticcall(abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0))); + + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall(IERC4626.withdraw, (1500e18, address(swapper), user)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall(swapVerifier.verifyDebtMax, (address(eSTETH), user, exactOutTolerance, type(uint256).max)) + }); + + uint256 fork = vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = new ConstructorVictim(sender, "msg.sender", "not set during prank"); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert("failed parsing as `uint256`: missing hex prefix for hex string"); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks.willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer(Create3.predict(_salt, address(_deployer)), address(o), id); + + return _verifyDeploymentRootHash(_getMerkleRoot(proof, hash), originalOwner) + .ternary(IERC1271.isValidSignature.selector, bytes4(0xffffffff)); +} + +function returnLongBinaryOp() returns (bytes32) { + return + bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + + { + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), buyToken: IERC20(address(0)), minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), buyToken: IERC20(address(0)), minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector.revertWith(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)); + + nestedStruct.withCalls.thatCause + .aBreak( + param1, + param2, + param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = LpPosition({ + tickLower: posTickLower, tickUpper: posTickUpper, liquidity: lpTailLiquidity, id: uint16(id) + }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool(IPoolManager.ModifyLiquidityParams memory paramSeed) public { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/80.fmt.sol b/crates/fmt/testdata/ReprosCalls/80.fmt.sol new file mode 100644 index 0000000000000..47b8e1644d9ad --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/80.fmt.sol @@ -0,0 +1,226 @@ +// config: line_length = 80 +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) + == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = LerpFactoryLike(lerpFab()) + .newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = execute( + oracleRouterFactory, + deployRouterForOracle, + eVaultFactory, + upgradable, + asset, + oracle, + unitOfAccount + ); + + if (eVault == address(0)) { + eVault = address( + GenericFactory(eVaultFactory) + .createProxy( + address(0), + true, + abi.encodePacked(asset, address(0), address(0)) + ) + ); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = abi.encode( + LidoOracleInfo({ + base: IOracle(oracleAddress).WSTETH(), + quote: IOracle(oracleAddress).STETH() + }) + ); + + return someFunction().getValue().modifyValue().negate() + .scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry) + .add( + adapter, + LidoFundamentalOracle(adapter).WSTETH(), + LidoFundamentalOracle(adapter).WETH() + ); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory) + .implementation() + .staticcall( + abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0)) + ); + + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall( + IERC4626.withdraw, (1500e18, address(swapper), user) + ) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall( + swapVerifier.verifyDebtMax, + (address(eSTETH), user, exactOutTolerance, type(uint256).max) + ) + }); + + uint256 fork = + vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = + new ConstructorVictim(sender, "msg.sender", "not set during prank"); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert( + "failed parsing as `uint256`: missing hex prefix for hex string" + ); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks + .willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = + FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer( + Create3.predict(_salt, address(_deployer)), address(o), id + ); + + return _verifyDeploymentRootHash(_getMerkleRoot(proof, hash), originalOwner) + .ternary(IERC1271.isValidSignature.selector, bytes4(0xffffffff)); +} + +function returnLongBinaryOp() returns (bytes32) { + return bytes32( + uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 + | uint256(Nonce.unwrap(nonce)) + ); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = Take.take( + state, + notes, + uint32(IPoolManager.take.selector), + recipient, + minBuyAmount + ); + uint256 globalBuyAmount = Take.take( + state, + notes, + uint32(IPoolManager.take.selector), + recipient, + minBuyAmount + ); + + { + u.executionData = _transferExecution( + address(paymentToken), address(0xabcd), 1 ether + ); + u.executionData = _transferExecution( + address(paymentToken), address(0xabcd), 1 ether + ); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = + ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = + ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = + defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + ISignatureTransfer.PermitTransferFrom memory permit = + defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector + .revertWith( + Currency.unwrap(key.currency0), Currency.unwrap(key.currency1) + ); + + nestedStruct.withCalls.thatCause + .aBreak( + param1, + param2, + param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = LpPosition({ + tickLower: posTickLower, + tickUpper: posTickUpper, + liquidity: lpTailLiquidity, + id: uint16(id) + }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool( + IPoolManager.ModifyLiquidityParams memory paramSeed + ) public { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol b/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol new file mode 100644 index 0000000000000..a51f55ab6b440 --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol @@ -0,0 +1,165 @@ +// config: line_length = 120 +// config: bracket_spacing = true +// config: prefer_compact = "none" +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = LerpFactoryLike(lerpFab()).newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = + execute(oracleRouterFactory, deployRouterForOracle, eVaultFactory, upgradable, asset, oracle, unitOfAccount); + + if (eVault == address(0)) { + eVault = address( + GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0))) + ); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = + abi.encode(LidoOracleInfo({ base: IOracle(oracleAddress).WSTETH(), quote: IOracle(oracleAddress).STETH() })); + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry) + .add(adapter, LidoFundamentalOracle(adapter).WSTETH(), LidoFundamentalOracle(adapter).WETH()); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory).implementation() + .staticcall(abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0))); + + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall(IERC4626.withdraw, (1500e18, address(swapper), user)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall(swapVerifier.verifyDebtMax, (address(eSTETH), user, exactOutTolerance, type(uint256).max)) + }); + + uint256 fork = vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = new ConstructorVictim(sender, "msg.sender", "not set during prank"); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert("failed parsing as `uint256`: missing hex prefix for hex string"); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks.willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer(Create3.predict(_salt, address(_deployer)), address(o), id); + + return _verifyDeploymentRootHash(_getMerkleRoot(proof, hash), originalOwner) + .ternary(IERC1271.isValidSignature.selector, bytes4(0xffffffff)); +} + +function returnLongBinaryOp() returns (bytes32) { + return + bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + + { + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector.revertWith(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)); + + nestedStruct.withCalls.thatCause + .aBreak( + param1, + param2, + param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = LpPosition({ + tickLower: posTickLower, + tickUpper: posTickUpper, + liquidity: lpTailLiquidity, + id: uint16(id) + }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool(IPoolManager.ModifyLiquidityParams memory paramSeed) public { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + streamId: defaultStreamId, + to: users.eve, + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + // cmnt + streamId: defaultStreamId, + to: users.eve, + amount: WITHDRAW_AMOUNT_6D + }); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/original.sol b/crates/fmt/testdata/ReprosCalls/original.sol new file mode 100644 index 0000000000000..cb92b2a0c5b13 --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/original.sol @@ -0,0 +1,148 @@ +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = LerpFactoryLike(lerpFab()).newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = + execute(oracleRouterFactory, deployRouterForOracle, eVaultFactory, upgradable, asset, oracle, unitOfAccount); + + if (eVault == address(0)) { + eVault = address(GenericFactory(eVaultFactory) + .createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0)))); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = + abi.encode(LidoOracleInfo({base: IOracle(oracleAddress).WSTETH(), quote: IOracle(oracleAddress).STETH()})); + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry).add( + adapter, LidoFundamentalOracle(adapter).WSTETH(), LidoFundamentalOracle(adapter).WETH() + ); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory).implementation().staticcall( + abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0)) + ); + +IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall(IERC4626.withdraw, (1500e18, address(swapper), user)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall(swapVerifier.verifyDebtMax, (address(eSTETH), user, exactOutTolerance, type(uint256).max)) + }); + + uint256 fork = vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = new ConstructorVictim( sender, "msg.sender", "not set during prank" ); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert( "failed parsing as `uint256`: missing hex prefix for hex string" ); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks.willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer( Create3.predict(_salt, address(_deployer)), address(o), id ); + + return _verifyDeploymentRootHash( +_getMerkleRoot(proof, hash), originalOwner +).ternary( +IERC1271.isValidSignature.selector, bytes4(0xffffffff) +); +} + +function returnLongBinaryOp() returns (bytes32) { + return + bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = + Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + uint256 globalBuyAmount = Take.take( + state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount + ); + + { + u.executionData = + _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + u.executionData = _transferExecution( + address(paymentToken), address(0xabcd), 1 ether + ); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), buyToken: IERC20(address(0)), minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = + defaultERC20PermitTransfer(address(fromToken()), amount(), 0 /* nonce */); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), amount(), 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector.revertWith( + Currency.unwrap(key.currency0), Currency.unwrap(key.currency1) + ); + + nestedStruct.withCalls.thatCause.aBreak(param1, param2, param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = + LpPosition({ tickLower: posTickLower, tickUpper: posTickUpper, liquidity: lpTailLiquidity, id: uint16(id) }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool( + IPoolManager.ModifyLiquidityParams memory paramSeed + ) public { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI /* cmnt */ }({ streamId: defaultStreamId, to: users.eve, /* cmnt */ amount: WITHDRAW_AMOUNT_6D }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D }); + } +} diff --git a/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol b/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol new file mode 100644 index 0000000000000..ef580620d8486 --- /dev/null +++ b/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol @@ -0,0 +1,17 @@ +// config: line_length = 120 +// config: multiline_func_header = "all" +contract Repros { + // https://github.com/foundry-rs/foundry/issues/12109 + function createDefaultStream(UD21x18 ratePerSecond, uint40 startTime, IERC20 token_) internal returns (uint256); + + function calculateStreamedPercentage( + uint128 streamedAmount, + uint128 depositedAmount + ) + internal + pure + returns (uint256) + { + a = 1; + } +} diff --git a/crates/fmt/testdata/ReprosFunctionDefs/original.sol b/crates/fmt/testdata/ReprosFunctionDefs/original.sol new file mode 100644 index 0000000000000..19c97e95f0c6c --- /dev/null +++ b/crates/fmt/testdata/ReprosFunctionDefs/original.sol @@ -0,0 +1,5 @@ +contract Repros { + // https://github.com/foundry-rs/foundry/issues/12109 + function createDefaultStream(UD21x18 ratePerSecond, uint40 startTime, IERC20 token_) internal returns (uint256); + function calculateStreamedPercentage(uint128 streamedAmount, uint128 depositedAmount) internal pure returns (uint256) { a = 1; } +} diff --git a/crates/fmt/testdata/ReturnStatement/fmt.sol b/crates/fmt/testdata/ReturnStatement/fmt.sol index d628d6097233d..d40330f73e805 100644 --- a/crates/fmt/testdata/ReturnStatement/fmt.sol +++ b/crates/fmt/testdata/ReturnStatement/fmt.sol @@ -23,7 +23,9 @@ contract ReturnStatement { 0x00; } - if (val == 1) return 1; + if (val == 1) { + return 1; + } if (val == 2) { return 3 - 1; diff --git a/crates/fmt/testdata/ReturnStatement/original.sol b/crates/fmt/testdata/ReturnStatement/original.sol index 9cfaa82d6c524..f6305661ff2d4 100644 --- a/crates/fmt/testdata/ReturnStatement/original.sol +++ b/crates/fmt/testdata/ReturnStatement/original.sol @@ -23,7 +23,7 @@ contract ReturnStatement { 0x00; } - if (val == 1) { return + if (val == 1) { return 1; } if (val == 2) { @@ -45,14 +45,14 @@ contract ReturnStatement { function returnMultipleValues(uint256 val) external returns (uint256, uint256, bool) { if (val == 0) { return /* return mul 1 */ (0, 1,/* return mul 2 */ false); } - if (val == 1) { + if (val == 1) { // return mul 3 return /* return mul 4 */ ( 987654321, 1234567890,/* return mul 5 */ false); } if (val == 2) { - return /* return mul 6 */ ( 1234567890 + 987654321 + 87654123536, 987654321 + 1234567890 + 124245235235, true); + return /* return mul 6 */ ( 1234567890 + 987654321 + 87654123536, 987654321 + 1234567890 + 124245235235, true); } return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); diff --git a/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol b/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol new file mode 100644 index 0000000000000..0ba90b1060973 --- /dev/null +++ b/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol @@ -0,0 +1,38 @@ +// config: prefer_compact = "events_errors" +// config: bracket_spacing = true +contract RevertNamedArgsStatement { + error EmptyError(); + error SimpleError(uint256 val); + error ComplexError(uint256 val, uint256 ts, string message); + error SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength( + uint256 val, uint256 ts, string message + ); + + function test() external { + revert({ }); + + revert EmptyError({ }); + + revert SimpleError({ val: 0 }); + + revert ComplexError({ + val: 0, + ts: block.timestamp, + message: "some reason" + }); + + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ + val: 0, + ts: 0x00, + message: "something unpredictable happened that caused execution to revert" + }); + + revert({ }); // comment1 + + revert /* comment2 */ SimpleError({ /* comment3 */ // comment4 + val: 0 // comment 5 + }); + + revert Errors.Unauthorized({ caller: msg.sender, neededRole: role }); + } +} diff --git a/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol b/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol index 9ad6b042b731a..11c8069fe521c 100644 --- a/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol +++ b/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol @@ -1,3 +1,4 @@ +// config: prefer_compact = "events_errors" contract RevertNamedArgsStatement { error EmptyError(); error SimpleError(uint256 val); @@ -19,17 +20,18 @@ contract RevertNamedArgsStatement { message: "some reason" }); - revert - SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ - val: 0, - ts: 0x00, - message: "something unpredictable happened that caused execution to revert" - }); + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ + val: 0, + ts: 0x00, + message: "something unpredictable happened that caused execution to revert" + }); revert({}); // comment1 revert /* comment2 */ SimpleError({ /* comment3 */ // comment4 val: 0 // comment 5 }); + + revert Errors.Unauthorized({caller: msg.sender, neededRole: role}); } } diff --git a/crates/fmt/testdata/RevertNamedArgsStatement/original.sol b/crates/fmt/testdata/RevertNamedArgsStatement/original.sol index 2c9e35ba3d16c..1713aec13b47c 100644 --- a/crates/fmt/testdata/RevertNamedArgsStatement/original.sol +++ b/crates/fmt/testdata/RevertNamedArgsStatement/original.sol @@ -19,14 +19,16 @@ contract RevertNamedArgsStatement { ts: block.timestamp, message: "some reason" }); - + revert SomeVeryVeryVeryLongErrorNameWithNamedArgumentsThatExceedsMaximumLength({ val: 0, ts: 0x00, message: "something unpredictable happened that caused execution to revert"}); - revert // comment1 + revert // comment1 ({}); - revert /* comment2 */ SimpleError /* comment3 */ ({ // comment4 + revert /* comment2 */ SimpleError /* comment3 */ ({ // comment4 val:0 // comment 5 }); + + revert Errors.Unauthorized({ caller: msg.sender, neededRole: role }); } } diff --git a/crates/fmt/testdata/RevertStatement/fmt.sol b/crates/fmt/testdata/RevertStatement/fmt.sol index 3a677a5cadfdb..4ed9eb348d39d 100644 --- a/crates/fmt/testdata/RevertStatement/fmt.sol +++ b/crates/fmt/testdata/RevertStatement/fmt.sol @@ -1,3 +1,4 @@ +// config: prefer_compact = "none" contract RevertStatement { error TestError(uint256, bool, string); @@ -27,7 +28,9 @@ contract RevertStatement { message // comment5 /* comment6 */ ); - revert( /* comment7 */ /* comment8 */ message /* comment9 */ ); /* comment10 */ // comment11 + revert( /* comment7 */ /* comment8 */ + message /* comment9 */ + ); /* comment10 */ // comment11 revert( string.concat( @@ -40,11 +43,15 @@ contract RevertStatement { revert TestError(0, false, message); revert TestError( - 0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString() + 0, + false, + someVeryLongFunctionNameToGetDynamicErrorMessageString() ); revert /* comment13 */ /* comment14 */ TestError( /* comment15 */ - 1234567890, false, message + 1234567890, + false, + message ); revert TestError( /* comment16 */ diff --git a/crates/fmt/testdata/RevertStatement/original.sol b/crates/fmt/testdata/RevertStatement/original.sol index 3426fdb1e5ad5..0c64dfeb25159 100644 --- a/crates/fmt/testdata/RevertStatement/original.sol +++ b/crates/fmt/testdata/RevertStatement/original.sol @@ -6,17 +6,17 @@ contract RevertStatement { } function test(string memory message) external { - revert ( ) ; + revert ( ) ; revert ( /* comment1 */ ); - - revert + + revert ( ) ; - // comment2 + // comment2 revert ( // comment3 ); @@ -24,7 +24,7 @@ contract RevertStatement { revert ( message ); - revert ( + revert ( // comment4 message // comment5 /* comment6 */ ); @@ -35,10 +35,10 @@ contract RevertStatement { revert TestError(0, false, message); revert TestError(0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString()); - + revert /* comment13 */ /* comment14 */ TestError /* comment15 */(1234567890, false, message); revert TestError ( /* comment16 */ 1, true, someVeryLongFunctionNameToGetDynamicErrorMessageString() /* comment17 */); } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/SimpleComments/fmt.sol b/crates/fmt/testdata/SimpleComments/fmt.sol index 6e8d5195bd143..1520db43d4162 100644 --- a/crates/fmt/testdata/SimpleComments/fmt.sol +++ b/crates/fmt/testdata/SimpleComments/fmt.sol @@ -1,6 +1,18 @@ contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: + // VARIABLES + //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• + mapping(address /* asset */ => address /* router */) public router; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + constructor() { // TODO: do this and that @@ -49,6 +61,35 @@ contract SimpleComments { return // a line comment that exceeds line width value; } + + // https://github.com/foundry-rs/foundry/issues/11836 + function test5() public { + ( + /* poolIndex */, + uint256 sellAmount1, + uint256 buyAmount1, + /* poolKey1 */, + /* sellToken */, + /* buyToken */, + /* sellTokenBalanceBefore */, + uint256 buyTokenBalanceBefore1, + /* hashMul */, + /* hashMod */ + ) = _swapPre(2, TOTAL_SUPPLY / 1_000, false, zeroForOne1); + } + + // https://github.com/foundry-rs/foundry/issues/12045 + function test6() { + ( + // uint80 roundID + , + int256 dataFeedAnswer, + // uint startedAt + , + uint256 updatedAt, + // uint80 answeredInRound + ) = dataFeedContract.latestRoundData(); + } } /* diff --git a/crates/fmt/testdata/SimpleComments/original.sol b/crates/fmt/testdata/SimpleComments/original.sol index d41c686b2f783..7020527b43671 100644 --- a/crates/fmt/testdata/SimpleComments/original.sol +++ b/crates/fmt/testdata/SimpleComments/original.sol @@ -1,6 +1,17 @@ contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: + // VARIABLES + //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• + mapping(address /* asset */ => address /* router */) public router; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ constructor() { // TODO: do this and that @@ -50,6 +61,35 @@ contract SimpleComments { return // a line comment that exceeds line width value; } + + // https://github.com/foundry-rs/foundry/issues/11836 + function test5() public { + ( + /* poolIndex */, + uint256 sellAmount1, + uint256 buyAmount1, + /* poolKey1 */, + /* sellToken */, + /* buyToken */, + /* sellTokenBalanceBefore */, + uint256 buyTokenBalanceBefore1, + /* hashMul */, + /* hashMod */ + ) = _swapPre(2, TOTAL_SUPPLY / 1_000, false, zeroForOne1); + } + + // https://github.com/foundry-rs/foundry/issues/12045 + function test6() { + ( + // uint80 roundID + , + int256 dataFeedAnswer, + // uint startedAt + , + uint256 updatedAt, + // uint80 answeredInRound + ) = dataFeedContract.latestRoundData(); + } } /* @@ -80,4 +120,4 @@ function test() {} // some comment // another comment -// eof comment \ No newline at end of file +// eof comment diff --git a/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol b/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol index 06ddc1cc19669..d41b8efa99f08 100644 --- a/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol +++ b/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol @@ -1,9 +1,22 @@ // config: line_length = 60 // config: wrap_comments = true contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: + // VARIABLES + //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• + mapping(address /* asset */ => address /* router */) public router; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS + */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + constructor() { // TODO: do this and that @@ -37,8 +50,8 @@ contract SimpleComments { function test4() public view returns (uint256) { uint256 abc; // long postfix comment that exceeds - // line width. the comment should be split and - // carried over to the next line + // line width. the comment should be split and + // carried over to the next line uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline // long prefix comment that exceeds line width. the @@ -54,13 +67,43 @@ contract SimpleComments { uint256 value; return /* a long block comment that exceeds line - width */ - value; + width */ value; return /* a block comment that exceeds line width */ value; return // a line comment that exceeds line width value; } + + // https://github.com/foundry-rs/foundry/issues/11836 + function test5() public { + ( + /* poolIndex */, + uint256 sellAmount1, + uint256 buyAmount1, + /* poolKey1 */, + /* sellToken */, + /* buyToken */, + /* sellTokenBalanceBefore */, + uint256 buyTokenBalanceBefore1, + /* hashMul */, + /* hashMod */ + ) = _swapPre( + 2, TOTAL_SUPPLY / 1_000, false, zeroForOne1 + ); + } + + // https://github.com/foundry-rs/foundry/issues/12045 + function test6() { + ( + // uint80 roundID + , + int256 dataFeedAnswer, + // uint startedAt + , + uint256 updatedAt, + // uint80 answeredInRound + ) = dataFeedContract.latestRoundData(); + } } /* diff --git a/crates/fmt/testdata/SortedImports/fmt.sol b/crates/fmt/testdata/SortedImports/fmt.sol index f9b2c0ee2a9c3..0c821d365fbf5 100644 --- a/crates/fmt/testdata/SortedImports/fmt.sol +++ b/crates/fmt/testdata/SortedImports/fmt.sol @@ -13,8 +13,8 @@ import { symbol3 as alias1, symbol4 } from "File0.sol"; -import {symbol1 as alias, symbol2} from "File2.sol"; -import {symbol1 as alias, symbol2} from "File3.sol"; +import {symbol1 as alias0, symbol2} from "File2.sol"; +import {symbol1 as alias0, symbol2} from "File3.sol"; import { symbol1 as alias1, symbol2 as alias2, @@ -29,6 +29,6 @@ import {Something2, Something3} from "someFile.sol"; // This is a comment import {Something2, Something3} from "someFile.sol"; -import {symbol1 as alias, symbol2} from "File3.sol"; +import {symbol1 as alias0, symbol2} from "File3.sol"; // comment inside group is treated as a separator for now -import {symbol1 as alias, symbol2} from "File2.sol"; +import {symbol1 as alias0, symbol2} from "File2.sol"; diff --git a/crates/fmt/testdata/SortedImports/original.sol b/crates/fmt/testdata/SortedImports/original.sol index 54b3ca3b59cfb..b52bbda3ad097 100644 --- a/crates/fmt/testdata/SortedImports/original.sol +++ b/crates/fmt/testdata/SortedImports/original.sol @@ -6,8 +6,8 @@ import "SomeFile0.sol" as SomeOtherFile; import "AnotherFile2.sol" as SomeSymbol; import "AnotherFile1.sol" as SomeSymbol; -import {symbol2, symbol1 as alias} from "File3.sol"; -import {symbol2, symbol1 as alias} from "File2.sol"; +import {symbol2, symbol1 as alias0} from "File3.sol"; +import {symbol2, symbol1 as alias0} from "File2.sol"; import {symbol2 as alias2, symbol1 as alias1, symbol3 as alias3, symbol4} from "File6.sol"; import {symbol3 as alias1, symbol2 as alias2, symbol1 as alias3, symbol4} from "File0.sol"; @@ -18,6 +18,6 @@ import {Something3, Something2} from "someFile.sol"; // This is a comment import {Something3, Something2} from "someFile.sol"; -import {symbol2, symbol1 as alias} from "File3.sol"; +import {symbol2, symbol1 as alias0} from "File3.sol"; // comment inside group is treated as a separator for now -import {symbol2, symbol1 as alias} from "File2.sol"; \ No newline at end of file +import {symbol2, symbol1 as alias0} from "File2.sol"; \ No newline at end of file diff --git a/crates/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol b/crates/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol index 3e1c8ea4e3ff1..72e5fa307ded2 100644 --- a/crates/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/StructDefinition/bracket-spacing.fmt.sol @@ -1,5 +1,5 @@ // config: bracket_spacing = true -struct Foo { } +struct Foo {} struct Bar { uint256 foo; diff --git a/crates/fmt/testdata/StructDefinition/original.sol b/crates/fmt/testdata/StructDefinition/original.sol index a82d7a92e7d7a..cb3d3e11dbb0b 100644 --- a/crates/fmt/testdata/StructDefinition/original.sol +++ b/crates/fmt/testdata/StructDefinition/original.sol @@ -1,4 +1,5 @@ struct Foo { + } struct Bar { uint foo ;string bar ; } struct MyStruct { diff --git a/crates/fmt/testdata/StructFieldAccess/fmt.sol b/crates/fmt/testdata/StructFieldAccess/fmt.sol new file mode 100644 index 0000000000000..22b16bde3f77c --- /dev/null +++ b/crates/fmt/testdata/StructFieldAccess/fmt.sol @@ -0,0 +1,46 @@ +// config: line_length = 120 +// https://github.com/foundry-rs/foundry/issues/12399 +contract StructFieldAccess { + function a() external { + bytes32 guid = + _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).guid; + } + + function b() external view returns (uint256) { + return _quote({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _payInLzToken: false + }).nativeFee; + } + + // Simple cases + function c() external { + uint256 val = getData().value; + bool flag = getStruct({param: 1}).isActive; + } + + // Nested struct field access + function d() external { + uint256 nested = getOuter().inner.value; + } + + // Chained calls with named args + function e() external { + bytes32 guid = + _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).wrap({wrapper: wrapperAddress, extraData: bytes("")}).guid; + } +} diff --git a/crates/fmt/testdata/StructFieldAccess/original.sol b/crates/fmt/testdata/StructFieldAccess/original.sol new file mode 100644 index 0000000000000..7671273c028be --- /dev/null +++ b/crates/fmt/testdata/StructFieldAccess/original.sol @@ -0,0 +1,43 @@ +// https://github.com/foundry-rs/foundry/issues/12399 +contract StructFieldAccess { + function a() external { + bytes32 guid = _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).guid; + } + + function b() external view returns (uint256) { + return _quote({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _payInLzToken: false + }).nativeFee; + } + + // Simple cases + function c() external { + uint256 val = getData().value; + bool flag = getStruct({param: 1}).isActive; + } + + // Nested struct field access + function d() external { + uint256 nested = getOuter().inner.value; + } + + // Chained calls with named args + function e() external { + bytes32 guid = _lzSend({ + _dstEid: dstEid, + _message: message, + _options: OptionsBuilder.newOptions().addExecutorLzReceiveOption({_gas: gasLimit, _value: 0}), + _fee: MessagingFee({nativeFee: msg.value, lzTokenFee: 0}), + _refundAddress: msg.sender + }).wrap({wrapper: wrapperAddress, extraData: bytes("")}).guid; + } +} diff --git a/crates/fmt/testdata/ThisExpression/fmt.sol b/crates/fmt/testdata/ThisExpression/fmt.sol index 239a6073eae39..458f8f6b859e6 100644 --- a/crates/fmt/testdata/ThisExpression/fmt.sol +++ b/crates/fmt/testdata/ThisExpression/fmt.sol @@ -1,8 +1,7 @@ contract ThisExpression { function someFunc() public {} function someVeryVeryVeryLongVariableNameThatWillBeAccessedByThisKeyword() - public - {} + public {} function test() external { this.someFunc(); @@ -13,8 +12,7 @@ contract ThisExpression { address thisAddress = address( // comment2 - /* comment3 */ - this // comment 4 + /* comment3 */ this // comment 4 ); } } diff --git a/crates/fmt/testdata/TrailingComma/fmt.sol b/crates/fmt/testdata/TrailingComma/fmt.sol index 034ac5d33088d..c69a6562fce9a 100644 --- a/crates/fmt/testdata/TrailingComma/fmt.sol +++ b/crates/fmt/testdata/TrailingComma/fmt.sol @@ -1,8 +1,4 @@ contract C is Contract { - modifier m(uint256) {} - // invalid solidity code, but valid pt - modifier m2(uint256) returns (uint256) {} - function f(uint256 a) external {} function f2(uint256 a, bytes32 b) external returns (uint256) {} diff --git a/crates/fmt/testdata/TrailingComma/original.sol b/crates/fmt/testdata/TrailingComma/original.sol index c06460f250aa8..644eb064c9ba6 100644 --- a/crates/fmt/testdata/TrailingComma/original.sol +++ b/crates/fmt/testdata/TrailingComma/original.sol @@ -1,12 +1,8 @@ contract C is Contract { - modifier m(uint256, ,,, ) {} - // invalid solidity code, but valid pt - modifier m2(uint256) returns (uint256,,,) {} - function f(uint256 a, ) external {} - function f2(uint256 a, , , ,bytes32 b) external returns (uint256,,,,) {} + function f2(uint256 a, bytes32 b,) external returns (uint256,) {} function f3() external { - try some.invoke() returns (uint256,,,uint256) {} catch {} + try some.invoke() returns (uint256,uint256,) {} catch {} } } diff --git a/crates/fmt/testdata/TryStatement/fmt.sol b/crates/fmt/testdata/TryStatement/fmt.sol index d49687eb1285a..cff134e646038 100644 --- a/crates/fmt/testdata/TryStatement/fmt.sol +++ b/crates/fmt/testdata/TryStatement/fmt.sol @@ -62,8 +62,7 @@ contract TryStatement { catch { /* comment6 */ } // comment7 - try unknown.empty() { - // comment8 + try unknown.empty() { // comment8 unknown.doSomething(); } /* comment9 */ catch /* comment10 */ Error(string memory) { unknown.handleError(); @@ -71,4 +70,35 @@ contract TryStatement { unknown.handleError(); } catch {} } + + function test_multiParam() { + Mock mock = new Mock(); + + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function test_multiComment() { + try vm.envString("API_KEY") returns (string memory) { + console2.log("Forked Ethereum mainnet"); + // Fork mainnet at a specific block for consistency + vm.createSelectFork(vm.rpcUrl("mainnet"), 21_900_000); + // do something + } catch { + /* sadness */ + // more sadness + revert(); + } + } + + function try_reallyLongCall() { + try AggregatorV3Interface(oracle).latestRoundData() returns ( + uint80, int256 _price, uint256, uint256 _updatedAt, uint80 + ) { + return true; + } catch {} // https://github.com/foundry-rs/foundry/issues/12240 + } } diff --git a/crates/fmt/testdata/TryStatement/original.sol b/crates/fmt/testdata/TryStatement/original.sol index 9fc158b20195a..288ad81293738 100644 --- a/crates/fmt/testdata/TryStatement/original.sol +++ b/crates/fmt/testdata/TryStatement/original.sol @@ -39,10 +39,10 @@ contract TryStatement { } try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { unknown.doSomething(); - } + } catch Error(string memory) { unknown.handleError(); } @@ -55,7 +55,7 @@ contract TryStatement { catch /* comment6 */ {} // comment7 - try unknown.empty() { // comment8 + try unknown.empty() { // comment8 unknown.doSomething(); } /* comment9 */ catch /* comment10 */ Error(string memory) { unknown.handleError(); @@ -63,4 +63,32 @@ contract TryStatement { unknown.handleError(); } catch {} } -} \ No newline at end of file + + function test_multiParam() { + Mock mock = new Mock(); + + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function test_multiComment() { + try vm.envString("API_KEY") returns (string memory) { + console2.log("Forked Ethereum mainnet"); + // Fork mainnet at a specific block for consistency + vm.createSelectFork(vm.rpcUrl("mainnet"), 21_900_000); + // do something + } catch /* sadness */ { + // more sadness + revert(); + } + } + + function try_reallyLongCall() { + try AggregatorV3Interface(oracle).latestRoundData() returns (uint80, int256 _price, uint256, uint256 _updatedAt, uint80) { + return true; + } catch {} // https://github.com/foundry-rs/foundry/issues/12240 + } +} diff --git a/crates/fmt/testdata/UnitExpression/fmt.sol b/crates/fmt/testdata/UnitExpression/fmt.sol index ceb16c86c7813..1f1fa4d727e71 100644 --- a/crates/fmt/testdata/UnitExpression/fmt.sol +++ b/crates/fmt/testdata/UnitExpression/fmt.sol @@ -15,10 +15,12 @@ contract UnitExpression { uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue - * 1 /* comment1 */ ether; // comment2 + * 1 ether; + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue + * 1 ether; /* comment1 */ // comment2 - value = 1 // comment3 - // comment4 - ether; // comment5 + value = 1 ether; // comment3 + // comment4 + // comment5 } } diff --git a/crates/fmt/testdata/UnitExpression/original.sol b/crates/fmt/testdata/UnitExpression/original.sol index f85af34fe7770..1a252bf5b3e5c 100644 --- a/crates/fmt/testdata/UnitExpression/original.sol +++ b/crates/fmt/testdata/UnitExpression/original.sol @@ -14,10 +14,11 @@ contract UnitExpression { uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 ether; value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 value = 1 // comment3 // comment4 ether; // comment5 } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol b/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol index 8896668d1e43c..07b4866e4d6cb 100644 --- a/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol @@ -24,4 +24,49 @@ contract TestContract { uint256 allowed = allowance[from][msg.sender]; allowance[from][msg.sender] = allowed; } + + function test_longAssignements() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = + "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + } + + function test_stringConcatenation() public { + string memory strConcat = "0," "11579208923731619542357098500868790785," + "0x0000000000000000000000000000000000000000000000000000000000000000," + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() + .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/VariableAssignment/fmt.sol b/crates/fmt/testdata/VariableAssignment/fmt.sol index 07480d873c21e..080992fe050b0 100644 --- a/crates/fmt/testdata/VariableAssignment/fmt.sol +++ b/crates/fmt/testdata/VariableAssignment/fmt.sol @@ -23,4 +23,49 @@ contract TestContract { uint256 allowed = allowance[from][msg.sender]; allowance[from][msg.sender] = allowed; } + + function test_longAssignements() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = + "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + } + + function test_stringConcatenation() public { + string memory strConcat = "0," "11579208923731619542357098500868790785," + "0x0000000000000000000000000000000000000000000000000000000000000000," + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() + .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/VariableAssignment/original.sol b/crates/fmt/testdata/VariableAssignment/original.sol index 07480d873c21e..2ccc26c97df29 100644 --- a/crates/fmt/testdata/VariableAssignment/original.sol +++ b/crates/fmt/testdata/VariableAssignment/original.sol @@ -23,4 +23,33 @@ contract TestContract { uint256 allowed = allowance[from][msg.sender]; allowance[from][msg.sender] = allowed; } + + function test_longAssignements() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + } + + function test_stringConcatenation() public { + string memory strConcat = "0," "11579208923731619542357098500868790785," + "0x0000000000000000000000000000000000000000000000000000000000000000," + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage(originDomain, bytes32(0), address(inbox).toBytes32(), abi.encode(orderId, bytes32(0), address(0))); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage(originDomain, bytes32(0), address(inbox).toBytes32(), abi.encode(orderId, bytes32(0), address(0))); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount(flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId)) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/VariableDefinition/fmt.sol b/crates/fmt/testdata/VariableDefinition/fmt.sol index 85a88e5326de8..d783890c8b0e2 100644 --- a/crates/fmt/testdata/VariableDefinition/fmt.sol +++ b/crates/fmt/testdata/VariableDefinition/fmt.sol @@ -2,26 +2,23 @@ contract Contract layout at 69 { bytes32 transient a; - bytes32 private constant BYTES; + bytes32 private constant BYTES = 0; bytes32 private constant - override(Base1) BYTES; + override(Base1) BYTES = 0; bytes32 private constant - override(Base1, Base2) BYTES; + override(Base1, Base2) BYTES = 0; bytes32 private constant - immutable - override BYTES; + override BYTES = 0; bytes32 private constant - immutable - override - BYTES_VERY_VERY_VERY_LONG; + override BYTES_VERY_VERY_VERY_LONG = 0; bytes32 private constant @@ -31,22 +28,19 @@ contract Contract layout at 69 { SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract - ) BYTES_OVERRIDDEN; + ) BYTES_OVERRIDDEN = 0; bytes32 private constant BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant - immutable override BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant - immutable - override - BYTES_VERY_VERY_VERY_LONG = + override BYTES_VERY_VERY_VERY_LONG = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant BYTES_VERY_VERY_LONG = diff --git a/crates/fmt/testdata/VariableDefinition/original.sol b/crates/fmt/testdata/VariableDefinition/original.sol index 279c62e55b30c..eccd61018e24b 100644 --- a/crates/fmt/testdata/VariableDefinition/original.sol +++ b/crates/fmt/testdata/VariableDefinition/original.sol @@ -1,18 +1,18 @@ contract Contract layout at 69 { bytes32 transient a; - bytes32 constant private BYTES; - bytes32 private constant override (Base1) BYTES; - bytes32 private constant override (Base1, Base2) BYTES; - bytes32 private constant override immutable BYTES; - bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG; - bytes32 private constant override(Base1, Base2, SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract) BYTES_OVERRIDDEN; + bytes32 constant private BYTES = 0; + bytes32 private constant override (Base1) BYTES = 0; + bytes32 private constant override (Base1, Base2) BYTES = 0; + bytes32 private constant override BYTES = 0; + bytes32 private constant override BYTES_VERY_VERY_VERY_LONG = 0; + bytes32 private constant override(Base1, Base2, SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract) BYTES_OVERRIDDEN = 0; bytes32 constant private BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant override immutable BYTES = + bytes32 private constant override BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; - bytes32 private constant override immutable BYTES_VERY_VERY_VERY_LONG = + bytes32 private constant override BYTES_VERY_VERY_VERY_LONG = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant BYTES_VERY_VERY_LONG = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; diff --git a/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol b/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol index 41ef397f65156..3b78c06f2368e 100644 --- a/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol +++ b/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol @@ -3,26 +3,23 @@ contract Contract layout at 69 { bytes32 transient a; - bytes32 private constant BYTES; + bytes32 private constant BYTES = 0; bytes32 private constant - override (Base1) BYTES; + override (Base1) BYTES = 0; bytes32 private constant - override (Base1, Base2) BYTES; + override (Base1, Base2) BYTES = 0; bytes32 private constant - immutable - override BYTES; + override BYTES = 0; bytes32 private constant - immutable - override - BYTES_VERY_VERY_VERY_LONG; + override BYTES_VERY_VERY_VERY_LONG = 0; bytes32 private constant @@ -32,22 +29,19 @@ contract Contract layout at 69 { SomeLongBaseContract, AndAnotherVeryLongBaseContract, Imported.Contract - ) BYTES_OVERRIDDEN; + ) BYTES_OVERRIDDEN = 0; bytes32 private constant BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant - immutable override BYTES = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant - immutable - override - BYTES_VERY_VERY_VERY_LONG = + override BYTES_VERY_VERY_VERY_LONG = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; bytes32 private constant BYTES_VERY_VERY_LONG = diff --git a/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol b/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol index cff7ac40b3d29..5fddf215b8980 100644 --- a/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol +++ b/crates/fmt/testdata/WhileStatement/block-multi.fmt.sol @@ -55,10 +55,14 @@ contract WhileStatement { while (condition) { doIt(); + doIt(); } - while ( - // comment1 + while (condition) { + doIt(); + } + + while ( // comment1 condition ) { doIt(); diff --git a/crates/fmt/testdata/WhileStatement/block-single.fmt.sol b/crates/fmt/testdata/WhileStatement/block-single.fmt.sol index ee5c48b7d911f..58f2c08463e3e 100644 --- a/crates/fmt/testdata/WhileStatement/block-single.fmt.sol +++ b/crates/fmt/testdata/WhileStatement/block-single.fmt.sol @@ -33,10 +33,14 @@ contract WhileStatement { while (condition) doIt(); + while (condition) { + doIt(); + doIt(); + } + while (condition) doIt(); - while ( - // comment1 + while ( // comment1 condition ) doIt(); diff --git a/crates/fmt/testdata/WhileStatement/fmt.sol b/crates/fmt/testdata/WhileStatement/fmt.sol index 131c4eaedb799..7e366786f0816 100644 --- a/crates/fmt/testdata/WhileStatement/fmt.sol +++ b/crates/fmt/testdata/WhileStatement/fmt.sol @@ -40,10 +40,14 @@ contract WhileStatement { while (condition) doIt(); + while (condition) { + doIt(); + doIt(); + } + while (condition) doIt(); - while ( - // comment1 + while ( // comment1 condition ) doIt(); diff --git a/crates/fmt/testdata/WhileStatement/original.sol b/crates/fmt/testdata/WhileStatement/original.sol index 8b245b0cfa0f8..12fe15fb600a4 100644 --- a/crates/fmt/testdata/WhileStatement/original.sol +++ b/crates/fmt/testdata/WhileStatement/original.sol @@ -36,6 +36,8 @@ contract WhileStatement { while(condition) { doIt(); } + while(condition) { doIt(); doIt(); } + while (condition) doIt(); diff --git a/crates/fmt/testdata/Yul/fmt.sol b/crates/fmt/testdata/Yul/fmt.sol index 2f37eb2f290fe..26ae8f2eb884d 100644 --- a/crates/fmt/testdata/Yul/fmt.sol +++ b/crates/fmt/testdata/Yul/fmt.sol @@ -1,3 +1,4 @@ +// config: number_underscore = "thousands" contract Yul { function test() external { // https://github.com/euler-xyz/euler-contracts/blob/d4f207a4ac5a6e8ab7447a0f09d1399150c41ef4/contracts/vendor/MerkleProof.sol#L54 @@ -69,7 +70,7 @@ contract Yul { } // ************ - /* + /* calls pair.swap( tokenOutNo == 0 ? amountOut : 0, tokenOutNo == 1 ? amountOut : 0, @@ -96,7 +97,9 @@ contract Yul { mstore(0xe0, 0x80) let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) - if iszero(s2) { revert(3, 3) } + if iszero(s2) { + revert(3, 3) + } } // https://github.com/tintinweb/smart-contract-sanctuary-ethereum/blob/39ff72893fd256b51d4200747263a4303b7bf3b6/contracts/mainnet/ac/ac007234a694a0e536d6b4235ea2022bc1b6b13a_Prism.sol#L147 @@ -172,8 +175,8 @@ contract Yul { v7 {} - let zero:u32 := 0:u32 - let v:u256, t:u32 := sample(1, 2) + let zero := 0 + let v, t := sample(1, 2) let x, y := sample2(2, 1) let val1, val2, val3, val4, val5, val6, val7 @@ -184,5 +187,70 @@ contract Yul { assembly { a := 1 /* some really really really long comment that should not fit in one line */ } + + assembly ("memory-safe") { + let fmp := mload(0x40) + // do something + } + + assembly { + let addrSlot := + or( + mul(eq(0x8f283970, fnSel), adminSlot), // `changeAdmin(address)`. + mul(eq(0x0900f010, fnSel), adminSlot) // `upgrade(address)`. + ) + + for {} 1 {} { + if iszero(not(mload(i))) { break } // Break if all limbs are zero. + } + + switch mode + // Get value. + case 0 { + result := getStr( + input, + _BITPOS_VALUE, + _BITPOS_VALUE_LENGTH, + _VALUE_INITED + ) + } + // Get children. + case 3 { result := children(input) } + // Parse. + default { + let p := add(input, 0x20) + let e := add(p, mload(input)) + if iszero(eq(p, e)) { + let c := chr(e) + mstore8(e, 34) // Place a '"' at the end to speed up parsing. + // The `34 << 248` makes `mallocItem` preserve '"' at the end. + mstore(0x00, setP(shl(248, 34), _BITPOS_STRING, input)) + result, p := parseValue(input, 0, p, e) + mstore8(e, c) // Restore the original char at the end. + } + } + } + + assembly { + function parseNumber(s_, packed_, pIn_, end_) -> _item, _pOut { + _pOut := pIn_ + if eq(chr(_pOut), 45) { _pOut := add(_pOut, 1) } // '-'. + if iszero(lt(sub(chr(_pOut), 48), 10)) { fail() } // Not '0'..'9'. + let c_ := chr(_pOut) + _pOut := add(_pOut, 1) + if iszero(eq(c_, 48)) { + _pOut := skip0To9s(_pOut, end_, 0) + } // Not '0'. + if eq(chr(_pOut), 46) { + _pOut := skip0To9s(add(_pOut, 1), end_, 1) + } // '.'. + let t_ := mload(_pOut) + if eq(or(0x20, byte(0, t_)), 101) { + // forgefmt: disable-next-item + _pOut := skip0To9s(add(byte(sub(byte(1, t_), 14), 0x010001), // '+', '-'. + add(_pOut, 1)), end_, 1) + } + } + } } } diff --git a/crates/fmt/testdata/Yul/original.sol b/crates/fmt/testdata/Yul/original.sol index 5bd47c8dd9796..1fba402759726 100644 --- a/crates/fmt/testdata/Yul/original.sol +++ b/crates/fmt/testdata/Yul/original.sol @@ -67,7 +67,7 @@ contract Yul { } // ************ - /* + /* calls pair.swap( tokenOutNo == 0 ? amountOut : 0, tokenOutNo == 1 ? amountOut : 0, @@ -93,7 +93,7 @@ contract Yul { // empty bytes mstore(0xe0, 0x80) - let s2 := call(sub(gas(), 5000), pair, 0, 0x7c, 0xa4, 0, 0) + let s2 := call(sub(gas(), 5_000), pair, 0, 0x7c, 0xa4, 0, 0) if iszero(s2) { revert(3, 3) } @@ -128,8 +128,8 @@ contract Yul { function functionThatReturnsSevenValuesAndCanBeUsedInAssignment() -> v1, v2, v3, v4, v5, v6, v7 {} - let zero:u32 := 0:u32 - let v:u256, t:u32 := sample(1, 2) + let zero := 0 + let v, t := sample(1, 2) let x, y := sample2(2, 1) let val1, val2, val3, val4, val5, val6, val7 @@ -137,5 +137,59 @@ contract Yul { } assembly { a := 1 /* some really really really long comment that should not fit in one line */ } + + assembly ("memory-safe") { + let fmp := mload(0x40) + // do something + } + + assembly { + let addrSlot := + or( + mul(eq(0x8f283970, fnSel), adminSlot), // `changeAdmin(address)`. + mul(eq(0x0900f010, fnSel), adminSlot) // `upgrade(address)`. + ) + + for {} 1 {} { + if iszero(not(mload(i))) { break } // Break if all limbs are zero. + } + + switch mode + // Get value. + case 0 { result := getStr(input, _BITPOS_VALUE, _BITPOS_VALUE_LENGTH, _VALUE_INITED) } + // Get children. + case 3 { result := children(input) } + // Parse. + default { + let p := add(input, 0x20) + let e := add(p, mload(input)) + if iszero(eq(p, e)) { + let c := chr(e) + mstore8(e, 34) // Place a '"' at the end to speed up parsing. + // The `34 << 248` makes `mallocItem` preserve '"' at the end. + mstore(0x00, setP(shl(248, 34), _BITPOS_STRING, input)) + result, p := parseValue(input, 0, p, e) + mstore8(e, c) // Restore the original char at the end. + } + } + } + + assembly { + function parseNumber(s_, packed_, pIn_, end_) -> _item, _pOut { + _pOut := pIn_ + if eq(chr(_pOut), 45) { _pOut := add(_pOut, 1) } // '-'. + if iszero(lt(sub(chr(_pOut), 48), 10)) { fail() } // Not '0'..'9'. + let c_ := chr(_pOut) + _pOut := add(_pOut, 1) + if iszero(eq(c_, 48)) { _pOut := skip0To9s(_pOut, end_, 0) } // Not '0'. + if eq(chr(_pOut), 46) { _pOut := skip0To9s(add(_pOut, 1), end_, 1) } // '.'. + let t_ := mload(_pOut) + if eq(or(0x20, byte(0, t_)), 101) { + // forgefmt: disable-next-item + _pOut := skip0To9s(add(byte(sub(byte(1, t_), 14), 0x010001), // '+', '-'. + add(_pOut, 1)), end_, 1) + } + } + } } } diff --git a/crates/fmt/testdata/YulStrings/fmt.sol b/crates/fmt/testdata/YulStrings/fmt.sol index d05caeb26692a..6b440daed24da 100644 --- a/crates/fmt/testdata/YulStrings/fmt.sol +++ b/crates/fmt/testdata/YulStrings/fmt.sol @@ -3,12 +3,9 @@ contract Yul { assembly { let a := "abc" let b := "abc" - let c := "abc":u32 - let d := "abc":u32 - let e := hex"deadbeef" - let f := hex"deadbeef" - let g := hex"deadbeef":u32 - let h := hex"deadbeef":u32 + let c := hex"deadbeef" + let d := hex"deadbeef" + let e := 0xffffffffffffffffffffffffffffffffffffffff datacopy(0, dataoffset("runtime"), datasize("runtime")) return(0, datasize("runtime")) } diff --git a/crates/fmt/testdata/YulStrings/original.sol b/crates/fmt/testdata/YulStrings/original.sol index fb3d5d20f4b76..ce066ef5c11e1 100644 --- a/crates/fmt/testdata/YulStrings/original.sol +++ b/crates/fmt/testdata/YulStrings/original.sol @@ -3,12 +3,9 @@ contract Yul { assembly { let a := "abc" let b := 'abc' - let c := "abc":u32 - let d := 'abc':u32 - let e := hex"deadbeef" - let f := hex'deadbeef' - let g := hex"deadbeef":u32 - let h := hex'deadbeef':u32 + let c := hex"deadbeef" + let d := hex'deadbeef' + let e := 0xffffffffffffffffffffffffffffffffffffffff datacopy(0, dataoffset('runtime'), datasize("runtime")) return(0, datasize("runtime")) } diff --git a/crates/fmt/testdata/YulStrings/preserve-quote.fmt.sol b/crates/fmt/testdata/YulStrings/preserve-quote.fmt.sol index dff9435396706..cb309029bbb87 100644 --- a/crates/fmt/testdata/YulStrings/preserve-quote.fmt.sol +++ b/crates/fmt/testdata/YulStrings/preserve-quote.fmt.sol @@ -4,12 +4,9 @@ contract Yul { assembly { let a := "abc" let b := 'abc' - let c := "abc":u32 - let d := 'abc':u32 - let e := hex"deadbeef" - let f := hex'deadbeef' - let g := hex"deadbeef":u32 - let h := hex'deadbeef':u32 + let c := hex"deadbeef" + let d := hex'deadbeef' + let e := 0xffffffffffffffffffffffffffffffffffffffff datacopy(0, dataoffset('runtime'), datasize("runtime")) return(0, datasize("runtime")) } diff --git a/crates/fmt/testdata/YulStrings/single-quote.fmt.sol b/crates/fmt/testdata/YulStrings/single-quote.fmt.sol index f1fc7fb8b514a..21ee6bc956e7d 100644 --- a/crates/fmt/testdata/YulStrings/single-quote.fmt.sol +++ b/crates/fmt/testdata/YulStrings/single-quote.fmt.sol @@ -4,12 +4,9 @@ contract Yul { assembly { let a := 'abc' let b := 'abc' - let c := 'abc':u32 - let d := 'abc':u32 - let e := hex'deadbeef' - let f := hex'deadbeef' - let g := hex'deadbeef':u32 - let h := hex'deadbeef':u32 + let c := hex'deadbeef' + let d := hex'deadbeef' + let e := 0xffffffffffffffffffffffffffffffffffffffff datacopy(0, dataoffset('runtime'), datasize('runtime')) return(0, datasize('runtime')) } diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 454b26c46ccc6..abb70f2ec842a 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -1,244 +1,251 @@ -use forge_fmt::{format_to, parse, solang_ext::AstEq, FormatterConfig}; -use itertools::Itertools; -use std::{fs, path::PathBuf}; -use tracing_subscriber::{EnvFilter, FmtSubscriber}; - -fn tracing() { - let subscriber = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .with_test_writer() - .finish(); - let _ = tracing::subscriber::set_global_default(subscriber); -} +use forge_fmt::FormatterConfig; +use foundry_test_utils::init_tracing; +use snapbox::{Data, assert_data_eq}; +use solar::sema::Compiler; +use std::{ + fs, + path::{Path, PathBuf}, + sync::Arc, +}; + +#[track_caller] +fn format(source: &str, path: &Path, fmt_config: Arc) -> String { + let mut compiler = Compiler::new( + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(), + ); -fn test_directory(base_name: &str, test_config: TestConfig) { - tracing(); - let mut original = None; - - let tests = - fs::read_dir(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata").join(base_name)) - .unwrap() - .filter_map(|path| { - let path = path.unwrap().path(); - let source = fs::read_to_string(&path).unwrap(); - - if let Some(filename) = path.file_name().and_then(|name| name.to_str()) { - if filename == "original.sol" { - original = Some(source); - } else if filename - .strip_suffix("fmt.sol") - .map(|filename| filename.strip_suffix('.')) - .is_some() - { - // The majority of the tests were written with the assumption - // that the default value for max line length is `80`. - // Preserve that to avoid rewriting test logic. - let default_config = - FormatterConfig { line_length: 80, ..Default::default() }; - - let mut config = toml::Value::try_from(default_config).unwrap(); - let config_table = config.as_table_mut().unwrap(); - let mut lines = source.split('\n').peekable(); - let mut line_num = 1; - while let Some(line) = lines.peek() { - let entry = line - .strip_prefix("//") - .and_then(|line| line.trim().strip_prefix("config:")) - .map(str::trim); - let entry = if let Some(entry) = entry { entry } else { break }; - - let values = match toml::from_str::(entry) { - Ok(toml::Value::Table(table)) => table, - _ => panic!("Invalid config item in {filename} at {line_num}"), - }; - config_table.extend(values); - - line_num += 1; - lines.next(); - } - let config = config - .try_into() - .unwrap_or_else(|err| panic!("Invalid config for {filename}: {err}")); - - return Some((filename.to_string(), config, lines.join("\n"))) - } - } - - None - }) - .collect::>(); - - for (filename, config, formatted) in tests { - test_formatter( - &filename, - config, - original.as_ref().expect("original.sol not found"), - &formatted, - test_config, - ); + match forge_fmt::format_source(source, Some(path), fmt_config, &mut compiler).into_result() { + Ok(formatted) => formatted, + Err(e) => panic!("failed to format {path:?}: {e}"), } } +#[track_caller] fn assert_eof(content: &str) { - assert!(content.ends_with('\n') && !content.ends_with("\n\n")); + assert!(content.ends_with('\n'), "missing trailing newline"); + assert!(!content.ends_with("\n\n"), "extra trailing newline"); } -fn test_formatter( - filename: &str, - config: FormatterConfig, - source: &str, - expected_source: &str, - test_config: TestConfig, -) { - #[derive(Eq)] - struct PrettyString(String); +fn tests_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata") +} - impl PartialEq for PrettyString { - fn eq(&self, other: &Self) -> bool { - self.0.lines().eq(other.0.lines()) - } +fn test_directory(base_name: &str) { + init_tracing(); + let dir = tests_dir().join(base_name); + let mut original = fs::read_to_string(dir.join("original.sol")).unwrap(); + if cfg!(windows) { + original = original.replace("\r\n", "\n"); } - - impl std::fmt::Debug for PrettyString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) + let mut handles = vec![]; + for res in dir.read_dir().unwrap() { + let entry = res.unwrap(); + let path = entry.path(); + + let filename = path.file_name().and_then(|name| name.to_str()).unwrap(); + if filename == "original.sol" { + continue; + } + assert!(path.is_file(), "expected file: {path:?}"); + assert!(filename.ends_with("fmt.sol"), "unknown file: {path:?}"); + + let mut expected = fs::read_to_string(&path).unwrap(); + if cfg!(windows) { + expected = expected + .replace("\r\n", "\n") + .replace(r"\'", r"/'") + .replace(r#"\""#, r#"/""#) + .replace("\\\n", "/\n"); } - } - - assert_eof(expected_source); - - let source_parsed = match parse(source) { - Ok(p) => p, - Err(e) => panic!("{e}"), - }; - let expected_parsed = match parse(expected_source) { - Ok(p) => p, - Err(e) => panic!("{e}"), - }; - if !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { - similar_asserts::assert_eq!( - source_parsed.pt, - expected_parsed.pt, - "(formatted Parse Tree == expected Parse Tree) in {}", - filename + // The majority of the tests were written with the assumption that the default value for max + // line length is `80`. Preserve that to avoid rewriting test logic. + let default_config = FormatterConfig { line_length: 80, ..Default::default() }; + + let mut config = toml::Value::try_from(default_config).unwrap(); + let config_table = config.as_table_mut().unwrap(); + let mut comments_end = 0; + for (i, line) in expected.lines().enumerate() { + let line_num = i + 1; + let Some(entry) = line + .strip_prefix("//") + .and_then(|line| line.trim().strip_prefix("config:")) + .map(str::trim) + else { + break; + }; + + let values = match toml::from_str::(entry) { + Ok(toml::Value::Table(table)) => table, + r => panic!("invalid fmt config item in {filename} at {line_num}: {r:?}"), + }; + config_table.extend(values); + + comments_end += line.len() + 1; + } + let config = Arc::new( + config + .try_into::() + .unwrap_or_else(|err| panic!("invalid test config for {filename}: {err}")), ); + + let original = original.clone(); + let tname = format!("{base_name}/{filename}"); + let spawn = move || { + test_formatter(&path, config.clone(), &original, &expected, comments_end); + }; + handles.push(std::thread::Builder::new().name(tname).spawn(spawn).unwrap()); } + let results = handles.into_iter().map(|h| h.join()).collect::>(); + for result in results { + result.unwrap(); + } +} - let expected = PrettyString(expected_source.to_string()); +fn test_formatter( + expected_path: &Path, + config: Arc, + source: &str, + expected_source: &str, + comments_end: usize, +) { + let path = &*expected_path.with_file_name("original.sol"); + let expected_data = || Data::read_from(expected_path, None).raw(); - let mut source_formatted = String::new(); - format_to(&mut source_formatted, source_parsed, config.clone()).unwrap(); + let mut source_formatted = format(source, path, config.clone()); + // Inject `expected`'s comments, if any, so we can use the expected file as a snapshot. + source_formatted.insert_str(0, &expected_source[..comments_end]); + assert_data_eq!(&source_formatted, expected_data()); assert_eof(&source_formatted); - let source_formatted = PrettyString(source_formatted); - - similar_asserts::assert_eq!( - source_formatted, - expected, - "(formatted == expected) in {}", - filename - ); - - let mut expected_formatted = String::new(); - format_to(&mut expected_formatted, expected_parsed, config).unwrap(); + let mut expected_content = std::fs::read_to_string(expected_path).unwrap(); + if cfg!(windows) { + expected_content = expected_content.replace("\r\n", "\n"); + } + let expected_formatted = format(&expected_content, expected_path, config); + assert_data_eq!(&expected_formatted, expected_data()); + assert_eof(expected_source); assert_eof(&expected_formatted); - - let expected_formatted = PrettyString(expected_formatted); - - similar_asserts::assert_eq!( - expected_formatted, - expected, - "(formatted == expected) in {}", - filename - ); } -#[derive(Clone, Copy, Default)] -struct TestConfig { - /// Whether to compare the formatted source code AST with the original AST - skip_compare_ast_eq: bool, -} - -impl TestConfig { - fn skip_compare_ast_eq() -> Self { - Self { skip_compare_ast_eq: true } +fn test_all_dirs_are_declared(dirs: &[&str]) { + let mut undeclared = vec![]; + for actual_dir in tests_dir().read_dir().unwrap().filter_map(Result::ok) { + let path = actual_dir.path(); + assert!(path.is_dir(), "expected directory: {path:?}"); + let actual_dir_name = path.file_name().unwrap().to_str().unwrap(); + if !dirs.contains(&actual_dir_name) { + undeclared.push(actual_dir_name.to_string()); + } } + assert!( + undeclared.is_empty(), + "the following test directories are not declared in the test suite macro call: {undeclared:#?}" + ) } -macro_rules! test_dir { - ($dir:ident $(,)?) => { - test_dir!($dir, Default::default()); - }; - ($dir:ident, $config:expr $(,)?) => { - #[expect(non_snake_case)] +macro_rules! fmt_tests { + ($($(#[$attr:meta])* $dir:ident),+ $(,)?) => { #[test] - fn $dir() { - test_directory(stringify!($dir), $config); + fn all_dirs_are_declared() { + test_all_dirs_are_declared(&[$(stringify!($dir)),*]); } - }; -} -macro_rules! test_directories { - ($($dir:ident),+ $(,)?) => {$( - test_dir!($dir); - )+}; + $( + #[allow(non_snake_case)] + #[test] + $(#[$attr])* + fn $dir() { + test_directory(stringify!($dir)); + } + )+ + }; } -test_directories! { +fmt_tests! { + #[ignore = "annotations are not valid Solidity"] + Annotation, + ArrayExpressions, + BlockComments, + BlockCommentsFunction, + CommentEmptyLine, + ConditionalOperatorExpression, ConstructorDefinition, ConstructorModifierStyle, ContractDefinition, DocComments, + DoWhileStatement, + EmitStatement, EnumDefinition, + EnumVariants, ErrorDefinition, EventDefinition, + ForStatement, + FunctionCall, + FunctionCallArgsStatement, FunctionDefinition, FunctionDefinitionWithFunctionReturns, FunctionType, + HexUnderscore, + IfStatement, + IfStatement2, ImportDirective, + InlineDisable, + IntTypes, + LiteralExpression, + MappingType, ModifierDefinition, + NamedFunctionCallExpression, + NonKeywords, + NumberLiteralUnderscore, + OperatorExpressions, + PragmaDirective, + Repros, + ReprosCalls, + ReprosFunctionDefs, + ReturnStatement, + RevertNamedArgsStatement, + RevertStatement, + SimpleComments, + SortedImports, StatementBlock, StructDefinition, + StructFieldAccess, + ThisExpression, + #[ignore = "Solar errors when parsing inputs with trailing commas"] + TrailingComma, + TryStatement, TypeDefinition, + UnitExpression, UsingDirective, + VariableAssignment, VariableDefinition, - OperatorExpressions, WhileStatement, - DoWhileStatement, - ForStatement, - IfStatement, - IfStatement2, - VariableAssignment, - FunctionCallArgsStatement, - RevertStatement, - RevertNamedArgsStatement, - ReturnStatement, - TryStatement, - ConditionalOperatorExpression, - NamedFunctionCallExpression, - NonKeywords, - ArrayExpressions, - UnitExpression, - ThisExpression, - SimpleComments, - LiteralExpression, Yul, YulStrings, - IntTypes, - InlineDisable, - NumberLiteralUnderscore, - HexUnderscore, - FunctionCall, - TrailingComma, - PragmaDirective, - Annotation, - MappingType, - EmitStatement, - Repros, - BlockComments, - BlockCommentsFunction, - EnumVariants, } -test_dir!(SortedImports, TestConfig::skip_compare_ast_eq()); +#[test] +fn test_comment_empty_line_bug() { + init_tracing(); + let source = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment + +} +"#; + + let expected = r#"pragma solidity ^0.8.0; + +contract ProofOfConcept { + // some comment +} +"#; + + let fmt_config = Arc::new(FormatterConfig::default()); + let path = Path::new("test.sol"); + let formatted = format(source, path, fmt_config); + + assert_eq!(formatted, expected, "Formatting mismatch"); +} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index d823ca3285140..4e83eb168abca 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -26,17 +26,16 @@ harness = false # lib foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common.workspace = true -foundry-compilers = { workspace = true, features = ["full"] } +foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm.workspace = true -foundry-evm-core.workspace = true -foundry-wallets.workspace = true +foundry-evm-networks.workspace = true foundry-linking.workspace = true -forge-script-sequence.workspace = true comfy-table.workspace = true eyre.workspace = true proptest.workspace = true +rand.workspace = true rayon.workspace = true serde.workspace = true tracing.workspace = true @@ -52,80 +51,91 @@ forge-script.workspace = true forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } alloy-chains.workspace = true +alloy-consensus.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-network.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } -alloy-rpc-types.workspace = true -alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true alloy-hardforks.workspace = true +tempo-alloy.workspace = true + revm.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -clap_complete = "4" -clap_complete_fig = "4" +clap_complete.workspace = true dunce.workspace = true -futures.workspace = true indicatif.workspace = true inferno = { version = "0.12", default-features = false } itertools.workspace = true parking_lot.workspace = true regex = { workspace = true, default-features = false } -reqwest = { workspace = true, features = ["json"] } semver.workspace = true serde_json.workspace = true similar = { version = "2", features = ["inline"] } -solang-parser.workspace = true -solar-parse.workspace = true -solar-interface.workspace = true +solar.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } -toml = { workspace = true, features = ["preserve_order"] } -toml_edit = "0.22" +toml_edit.workspace = true watchexec = "8.0" watchexec-events = "6.0" watchexec-signals = "5.0" clearscreen = "4.0" evm-disassembler.workspace = true +path-slash.workspace = true +reqwest = { workspace = true, features = ["json"] } +url.workspace = true # doc server axum = { workspace = true, features = ["ws"] } tower-http = { workspace = true, features = ["fs"] } -opener = "0.7" +opener = "0.8" # soldeer soldeer-commands.workspace = true -quick-junit = "0.5.0" +soldeer-core.workspace = true +quick-junit = "0.5.2" [dev-dependencies] +alloy-hardforks.workspace = true anvil.workspace = true +forge-script-sequence.workspace = true foundry-test-utils.workspace = true -mockall = "0.13" +mockall = "0.14" globset = "0.4" -paste = "1.0" -path-slash = "0.2" similar-asserts.workspace = true -svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ - "rustls", -] } +svm.workspace = true tempfile.workspace = true alloy-signer-local.workspace = true [features] -default = ["jemalloc"] -asm-keccak = ["alloy-primitives/asm-keccak"] +default = ["jemalloc", "asm-keccak", "optimism"] +asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] +turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cli/optimism", + "forge-script/optimism", + "forge-verify/optimism", + "forge-doc/optimism", + "forge-fmt/optimism", + "forge-lint/optimism", + "forge-sol-macro-gen/optimism", +] diff --git a/crates/forge/assets/.gitignoreTemplate b/crates/forge/assets/.gitignoreTemplate index 85198aaa55b84..c8e4586809297 100644 --- a/crates/forge/assets/.gitignoreTemplate +++ b/crates/forge/assets/.gitignoreTemplate @@ -2,6 +2,10 @@ cache/ out/ +# Commonly ignored directories +.logs/ +.ignore/ + # Ignores development broadcast logs !/broadcast /broadcast/*/31337/ @@ -12,3 +16,5 @@ docs/ # Dotenv file .env +.env.* +!.env.example diff --git a/crates/forge/assets/README.md b/crates/forge/assets/README.md index 9265b4558406a..8817d6ab7b2a9 100644 --- a/crates/forge/assets/README.md +++ b/crates/forge/assets/README.md @@ -4,10 +4,10 @@ Foundry consists of: -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. ## Documentation diff --git a/crates/forge/assets/solidity/CounterTemplate.s.sol b/crates/forge/assets/solidity/CounterTemplate.s.sol new file mode 100644 index 0000000000000..f01d69c399c32 --- /dev/null +++ b/crates/forge/assets/solidity/CounterTemplate.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/crates/forge/assets/solidity/CounterTemplate.sol b/crates/forge/assets/solidity/CounterTemplate.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/crates/forge/assets/solidity/CounterTemplate.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/crates/forge/assets/solidity/CounterTemplate.t.sol b/crates/forge/assets/solidity/CounterTemplate.t.sol new file mode 100644 index 0000000000000..48319108366c0 --- /dev/null +++ b/crates/forge/assets/solidity/CounterTemplate.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/forge/assets/solidity/workflowTemplate.yml b/crates/forge/assets/solidity/workflowTemplate.yml new file mode 100644 index 0000000000000..8aeae90e03e63 --- /dev/null +++ b/crates/forge/assets/solidity/workflowTemplate.yml @@ -0,0 +1,35 @@ +name: CI + +permissions: {} + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: forge --version + + - name: Run Forge fmt + run: forge fmt --check + + - name: Run Forge build + run: forge build --sizes + + - name: Run Forge tests + run: forge test -vvv diff --git a/crates/forge/assets/tempo/MailTemplate.s.sol b/crates/forge/assets/tempo/MailTemplate.s.sol new file mode 100644 index 0000000000000..45006f7cd0e06 --- /dev/null +++ b/crates/forge/assets/tempo/MailTemplate.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; +import {ITIP20RolesAuth} from "tempo-std/interfaces/ITIP20RolesAuth.sol"; +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; +import {StdTokens} from "tempo-std/StdTokens.sol"; +import {Mail} from "../src/Mail.sol"; + +contract MailScript is Script { + function setUp() public {} + + function run(string memory salt) public { + vm.startBroadcast(); + + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); + StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); + + ITIP20 token = ITIP20( + StdPrecompiles.TIP20_FACTORY + .createToken("testUSD", "tUSD", "USD", StdTokens.PATH_USD, msg.sender, keccak256(bytes(salt))) + ); + + ITIP20RolesAuth(address(token)).grantRole(token.ISSUER_ROLE(), msg.sender); + + token.mint(msg.sender, 1_000_000 * 10 ** token.decimals()); + + new Mail(token); + + vm.stopBroadcast(); + } +} diff --git a/crates/forge/assets/tempo/MailTemplate.sol b/crates/forge/assets/tempo/MailTemplate.sol new file mode 100644 index 0000000000000..2caed92d47517 --- /dev/null +++ b/crates/forge/assets/tempo/MailTemplate.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; + +/// @title Mail +/// @notice Send mail with TIP-20 token attachments on Tempo. +/// +/// Supports two modes: +/// 1. Direct — call `sendMail()` yourself (uses `msg.sender`). +/// 2. Relayed — sign a mail off-chain and let anyone deliver it on-chain. +/// +/// Relayed mode uses the [TIP-1020] `SignatureVerifier` precompile to verify the +/// sender's Tempo signature. Unlike Ethereum's `ecrecover`, this precompile: +/// - Supports secp256k1, P256, and WebAuthn signature types +/// - Reverts on invalid signatures instead of returning `address(0)` +/// - Maintains forward compatibility with future Tempo account types +/// +/// [TIP-1020]: +contract Mail { + /// @notice Emitted when a mail is sent, either directly or via a relayer. + event MailSent(address indexed from, address indexed to, string message, Attachment attachment); + + /// @notice A TIP-20 token transfer bundled with a mail. + struct Attachment { + uint256 amount; + bytes32 memo; + } + + /// @notice The TIP-20 token used for mail attachments. + ITIP20 public token; + + /// @notice Per-sender nonce to prevent signature replay on relayed mails (requires T3). + mapping(address => uint256) public nonces; + + constructor(ITIP20 token_) { + token = token_; + } + + /// @notice Send mail directly (sender = msg.sender). + function sendMail(address to, string memory message, Attachment memory attachment) external { + token.transferFromWithMemo(msg.sender, to, attachment.amount, attachment.memo); + emit MailSent(msg.sender, to, message, attachment); + } + + /// @notice Send mail on behalf of `from` using their off-chain Tempo signature (requires T3). + /// @dev The sender must have pre-approved this contract to spend their tokens. + function sendMail( + address from, + address to, + string memory message, + Attachment memory attachment, + bytes calldata signature + ) external { + bytes32 hash = getDigest(from, to, message, attachment); + + // `verify()` returns `false` on signer mismatch, reverts on malformed signatures. + require(StdPrecompiles.SIGNATURE_VERIFIER.verify(from, hash, signature), "invalid signature"); + + // `recover()` returns the signer address directly, reverts on malformed signatures. + require(StdPrecompiles.SIGNATURE_VERIFIER.recover(hash, signature) == from, "invalid signature"); + + nonces[from]++; + token.transferFromWithMemo(from, to, attachment.amount, attachment.memo); + emit MailSent(from, to, message, attachment); + } + + /// @notice Compute the digest a sender must sign to authorize a relayed mail. + function getDigest(address from, address to, string memory message, Attachment memory attachment) + public + view + returns (bytes32) + { + return keccak256(abi.encode(address(this), block.chainid, from, to, message, attachment, nonces[from])); + } +} diff --git a/crates/forge/assets/tempo/MailTemplate.t.sol b/crates/forge/assets/tempo/MailTemplate.t.sol new file mode 100644 index 0000000000000..19760303860a1 --- /dev/null +++ b/crates/forge/assets/tempo/MailTemplate.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; +import {ITIP20RolesAuth} from "tempo-std/interfaces/ITIP20RolesAuth.sol"; +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; +import {StdTokens} from "tempo-std/StdTokens.sol"; +import {Mail} from "../src/Mail.sol"; + +/// @notice Tests for direct mail sending (no signature verification). +contract MailTest is Test { + ITIP20 public token; + Mail public mail; + + address public constant ALICE = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + address public constant BOB = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + + function setUp() public virtual { + address feeToken = vm.envOr("TEMPO_FEE_TOKEN", StdTokens.PATH_USD_ADDRESS); + StdPrecompiles.TIP_FEE_MANAGER.setUserToken(feeToken); + + token = ITIP20( + StdPrecompiles.TIP20_FACTORY + .createToken("testUSD", "tUSD", "USD", StdTokens.PATH_USD, address(this), bytes32(0)) + ); + + ITIP20RolesAuth(address(token)).grantRole(token.ISSUER_ROLE(), address(this)); + + mail = new Mail(token); + } + + function test_SendMail() public { + token.mint(ALICE, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = + Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); + + vm.startPrank(ALICE); + token.approve(address(mail), attachment.amount); + mail.sendMail(BOB, "Hello Bob, here is your invoice.", attachment); + vm.stopPrank(); + + assertEq(token.balanceOf(BOB), attachment.amount); + assertEq(token.balanceOf(ALICE), 100_000 * 10 ** token.decimals() - attachment.amount); + } + + function testFuzz_SendMail(uint128 mintAmount, uint128 sendAmount, string memory message, bytes32 memo) public { + mintAmount = uint128(bound(mintAmount, 0, type(uint128).max)); + sendAmount = uint128(bound(sendAmount, 0, mintAmount)); + + token.mint(ALICE, mintAmount); + + Mail.Attachment memory attachment = Mail.Attachment({amount: sendAmount, memo: memo}); + + vm.startPrank(ALICE); + token.approve(address(mail), sendAmount); + mail.sendMail(BOB, message, attachment); + vm.stopPrank(); + + assertEq(token.balanceOf(BOB), sendAmount); + assertEq(token.balanceOf(ALICE), mintAmount - sendAmount); + } +} + +/// @notice Tests for relayed mail using the TIP-1020 SignatureVerifier precompile (requires T3). +/// forge-config: default.hardfork = "tempo:T3" +contract MailRelayTest is MailTest { + // secp256k1 keys (used by vm.sign / vm.addr) + uint256 internal constant ALICE_PK = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + uint256 internal constant BOB_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + + // P256 key (used by vm.signP256 / vm.publicKeyP256) + uint256 internal constant CAROL_P256_PK = 0x1; + address internal CAROL; + bytes32 internal carolPubX; + bytes32 internal carolPubY; + + uint256 internal constant P256_ORDER = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + uint256 internal constant P256N_HALF = 0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8; + + function setUp() public override { + super.setUp(); + + // Derive P256 public key and Tempo address for Carol + (uint256 x, uint256 y) = vm.publicKeyP256(CAROL_P256_PK); + carolPubX = bytes32(x); + carolPubY = bytes32(y); + CAROL = address(uint160(uint256(keccak256(abi.encodePacked(x, y))))); + } + + /// @notice Relayed send with a secp256k1 signature — Alice signs, Bob delivers. + function test_SendMailWithSecp256k1Signature() public { + token.mint(ALICE, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = + Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); + + vm.prank(ALICE); + token.approve(address(mail), attachment.amount); + + string memory message = "Hello Bob, here is your invoice."; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, digest); + + vm.prank(BOB); + mail.sendMail(ALICE, BOB, message, attachment, abi.encodePacked(r, s, v)); + + assertEq(token.balanceOf(BOB), attachment.amount); + assertEq(mail.nonces(ALICE), 1); + } + + /// @notice Relayed send with a P256 signature — Carol signs, Bob delivers. + function test_SendMailWithP256Signature() public { + token.mint(CAROL, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = + Mail.Attachment({amount: 100 * 10 ** token.decimals(), memo: "Invoice #1234"}); + + vm.prank(CAROL); + token.approve(address(mail), attachment.amount); + + string memory message = "Hello Bob, signed with P256."; + bytes32 digest = mail.getDigest(CAROL, BOB, message, attachment); + (bytes32 r, bytes32 s) = vm.signP256(CAROL_P256_PK, digest); + s = _normalizeP256S(s); + + bytes memory sig = abi.encodePacked(uint8(0x01), r, s, carolPubX, carolPubY, uint8(0)); + + vm.prank(BOB); + mail.sendMail(CAROL, BOB, message, attachment, sig); + + assertEq(token.balanceOf(BOB), attachment.amount); + assertEq(mail.nonces(CAROL), 1); + } + + /// @notice Replaying the same signature fails (nonce incremented). + function test_ReplayReverts() public { + token.mint(ALICE, 100_000 * 10 ** token.decimals()); + + Mail.Attachment memory attachment = Mail.Attachment({amount: 50 * 10 ** token.decimals(), memo: "tip"}); + + vm.prank(ALICE); + token.approve(address(mail), attachment.amount * 2); + + string memory message = "tip"; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, digest); + bytes memory sig = abi.encodePacked(r, s, v); + + mail.sendMail(ALICE, BOB, message, attachment, sig); + + vm.expectRevert(); + mail.sendMail(ALICE, BOB, message, attachment, sig); + } + + /// @notice Submitting Bob's signature as Alice's fails. + function test_WrongSignerReverts() public { + Mail.Attachment memory attachment = Mail.Attachment({amount: 100, memo: "fake"}); + + string memory message = "spoofed"; + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(BOB_PK, digest); + + vm.expectRevert("invalid signature"); + mail.sendMail(ALICE, BOB, message, attachment, abi.encodePacked(r, s, v)); + } + + /// @dev Normalize P256 s to low-s form (required by the precompile). + function _normalizeP256S(bytes32 s) internal pure returns (bytes32) { + uint256 sVal = uint256(s); + if (sVal > P256N_HALF) return bytes32(P256_ORDER - sVal); + return s; + } +} diff --git a/crates/forge/assets/tempo/README.md b/crates/forge/assets/tempo/README.md new file mode 100644 index 0000000000000..603f4a3d97d85 --- /dev/null +++ b/crates/forge/assets/tempo/README.md @@ -0,0 +1,57 @@ +## Tempo Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Tempo's fork of Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Deploy + +```shell +$ forge script script/Mail.s.sol:MailScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ cast --help +``` diff --git a/crates/forge/assets/tempo/workflowTemplate.yml b/crates/forge/assets/tempo/workflowTemplate.yml new file mode 100644 index 0000000000000..1eb2d2bcf9af1 --- /dev/null +++ b/crates/forge/assets/tempo/workflowTemplate.yml @@ -0,0 +1,37 @@ +name: CI + +permissions: {} + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + check: + name: Tempo Foundry project + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: forge --version + + - name: Run Forge fmt + run: forge fmt --check + + - name: Run Forge build + run: forge build --sizes + + - name: Run Forge tests + run: forge test -vvv diff --git a/crates/forge/assets/vyper/CounterTemplate.s.sol b/crates/forge/assets/vyper/CounterTemplate.s.sol new file mode 100644 index 0000000000000..aab0faa29c80c --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {ICounter} from "../src/ICounter.sol"; + +contract CounterScript is Script { + ICounter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = ICounter(deployCode("src/Counter.vy")); + + vm.stopBroadcast(); + } +} diff --git a/crates/forge/assets/vyper/CounterTemplate.t.sol b/crates/forge/assets/vyper/CounterTemplate.t.sol new file mode 100644 index 0000000000000..b492089b190ea --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {ICounter} from "../src/ICounter.sol"; + +contract CounterTest is Test { + ICounter public counter; + + function setUp() public { + counter = ICounter(deployCode("src/Counter.vy")); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/forge/assets/vyper/CounterTemplate.vy b/crates/forge/assets/vyper/CounterTemplate.vy new file mode 100644 index 0000000000000..f0a00db3d6209 --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.vy @@ -0,0 +1,11 @@ +# pragma version ~=0.4.3 + +number: public(uint256) + +@external +def setNumber(newNumber: uint256): + self.number = newNumber + +@external +def increment(): + self.number += 1 \ No newline at end of file diff --git a/crates/forge/assets/vyper/ICounterTemplate.sol b/crates/forge/assets/vyper/ICounterTemplate.sol new file mode 100644 index 0000000000000..a3219a951776f --- /dev/null +++ b/crates/forge/assets/vyper/ICounterTemplate.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +interface ICounter { + function number() external view returns (uint256); + function setNumber(uint256 newNumber) external; + function increment() external; +} diff --git a/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml new file mode 100644 index 0000000000000..7a5fc54a1b084 --- /dev/null +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -0,0 +1,46 @@ +name: CI + +permissions: {} + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: forge --version + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Install Vyper + run: pip install git+https://github.com/vyperlang/vyper.git@v0.4.3 + + - name: Show the Vyper version + run: vyper --version + + - name: Run Forge fmt + run: forge fmt --check + + - name: Run Forge build + run: forge build --sizes + + - name: Run Forge tests + run: forge test -vvv diff --git a/crates/forge/src/args.rs b/crates/forge/src/args.rs index 2d411d7c30bc8..c5779aebbcbc7 100644 --- a/crates/forge/src/args.rs +++ b/crates/forge/src/args.rs @@ -5,14 +5,16 @@ use crate::{ use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; -use foundry_cli::{handler, utils}; -use foundry_common::shell; -use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext}; +use foundry_cli::utils; +use foundry_common::{sh_warn, shell}; +use foundry_evm::inspectors::cheatcodes::{ForgeContext, set_execution_context}; /// Run the `forge` command line interface. pub fn run() -> Result<()> { setup()?; + foundry_cli::opts::GlobalArgs::check_markdown_help::(); + let args = Forge::parse(); args.global.init()?; @@ -21,11 +23,8 @@ pub fn run() -> Result<()> { /// Setup the global logger and other utilities. pub fn setup() -> Result<()> { - utils::install_crypto_provider(); - handler::install(); - utils::load_dotenv(); + utils::common_setup(); utils::subscriber(); - utils::enable_paint(); Ok(()) } @@ -50,76 +49,71 @@ pub fn run_command(args: Forge) -> Result<()> { }; set_execution_context(context); + let global = &args.global; + // Run the subcommand. match args.cmd { ForgeSubcommand::Test(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_test(cmd)) + global.block_on(watch::watch_test(cmd)) } else { let silent = cmd.junit || shell::is_json(); - let outcome = utils::block_on(cmd.run())?; + let outcome = global.block_on(cmd.run())?; outcome.ensure_ok(silent) } } - ForgeSubcommand::Script(cmd) => utils::block_on(cmd.run_script()), + ForgeSubcommand::Script(cmd) => global.block_on(cmd.run_script()), ForgeSubcommand::Coverage(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_coverage(cmd)) + global.block_on(watch::watch_coverage(cmd)) } else { - utils::block_on(cmd.run()) + global.block_on(cmd.run()) } } ForgeSubcommand::Bind(cmd) => cmd.run(), ForgeSubcommand::Build(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_build(cmd)) + global.block_on(watch::watch_build(cmd)) } else { - cmd.run().map(drop) + global.block_on(cmd.run()).map(drop) } } - ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()), - ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()), - ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()), - ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::VerifyContract(args) => global.block_on(args.run()), + ForgeSubcommand::VerifyCheck(args) => global.block_on(args.run()), + ForgeSubcommand::VerifyBytecode(cmd) => global.block_on(cmd.run()), + ForgeSubcommand::Clone(cmd) => global.block_on(cmd.run()), ForgeSubcommand::Cache(cmd) => match cmd.sub { CacheSubcommands::Clean(cmd) => cmd.run(), CacheSubcommands::Ls(cmd) => cmd.run(), }, - ForgeSubcommand::Create(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Create(cmd) => global.block_on(cmd.run()), ForgeSubcommand::Update(cmd) => cmd.run(), - ForgeSubcommand::Install(cmd) => cmd.run(), + ForgeSubcommand::Install(cmd) => global.block_on(cmd.run()), ForgeSubcommand::Remove(cmd) => cmd.run(), ForgeSubcommand::Remappings(cmd) => cmd.run(), - ForgeSubcommand::Init(cmd) => cmd.run(), + ForgeSubcommand::Init(cmd) => global.block_on(cmd.run()), ForgeSubcommand::Completions { shell } => { generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout()); Ok(()) } - ForgeSubcommand::GenerateFigSpec => { - clap_complete::generate( - clap_complete_fig::Fig, - &mut Forge::command(), - "forge", - &mut std::io::stdout(), - ); - Ok(()) - } ForgeSubcommand::Clean { root } => { let config = utils::load_config_with_root(root.as_deref())?; let project = config.project()?; - config.cleanup(&project)?; + for warning in config.cleanup(&project)? { + let _ = sh_warn!("{warning}"); + } Ok(()) } ForgeSubcommand::Snapshot(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_gas_snapshot(cmd)) + global.block_on(watch::watch_gas_snapshot(cmd)) } else { - utils::block_on(cmd.run()) + global.block_on(cmd.run()) } } ForgeSubcommand::Fmt(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_fmt(cmd)) + global.block_on(watch::watch_fmt(cmd)) } else { cmd.run() } @@ -128,27 +122,21 @@ pub fn run_command(args: Forge) -> Result<()> { ForgeSubcommand::Flatten(cmd) => cmd.run(), ForgeSubcommand::Inspect(cmd) => cmd.run(), ForgeSubcommand::Tree(cmd) => cmd.run(), - ForgeSubcommand::Geiger(cmd) => { - let n = cmd.run()?; - if n > 0 { - std::process::exit(n as i32); - } - Ok(()) - } + ForgeSubcommand::Geiger(cmd) => cmd.run(), ForgeSubcommand::Doc(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_doc(cmd)) + global.block_on(watch::watch_doc(cmd)) } else { - utils::block_on(cmd.run())?; + global.block_on(cmd.run())?; Ok(()) } } - ForgeSubcommand::Selectors { command } => utils::block_on(command.run()), + ForgeSubcommand::Selectors { command } => global.block_on(command.run()), ForgeSubcommand::Generate(cmd) => match cmd.sub { GenerateSubcommands::Test(cmd) => cmd.run(), }, ForgeSubcommand::Compiler(cmd) => cmd.run(), - ForgeSubcommand::Soldeer(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Soldeer(cmd) => global.block_on(cmd.run()), ForgeSubcommand::Eip712(cmd) => cmd.run(), ForgeSubcommand::BindJson(cmd) => cmd.run(), ForgeSubcommand::Lint(cmd) => cmd.run(), diff --git a/crates/forge/src/cmd/bind.rs b/crates/forge/src/cmd/bind.rs index b1f2aef0e5d31..e356dde7a2ead 100644 --- a/crates/forge/src/cmd/bind.rs +++ b/crates/forge/src/cmd/bind.rs @@ -202,15 +202,11 @@ impl BindArgs { .get_json_files(artifacts)? .filter_map(|(name, path)| { trace!(?path, "parsing SolMacroGen from file"); - if dup.insert(name.clone()) { - Some(SolMacroGen::new(path, name)) - } else { - None - } + dup.insert(name.clone()).then(|| SolMacroGen::new(path, name)) }) .collect::>(); - let multi = MultiSolMacroGen::new(artifacts, instances); + let multi = MultiSolMacroGen::new(instances); eyre::ensure!(!multi.instances.is_empty(), "No contract artifacts found"); Ok(multi) } @@ -239,7 +235,14 @@ impl BindArgs { let mut solmacrogen = self.get_solmacrogen(artifacts)?; sh_println!("Generating bindings for {} contracts", solmacrogen.instances.len())?; - if !self.module { + if self.module { + trace!(single_file = self.single_file, "generating module"); + solmacrogen.write_to_module( + bindings_root, + self.single_file, + !self.skip_extra_derives, + )?; + } else { trace!(single_file = self.single_file, "generating crate"); solmacrogen.write_to_crate( &self.crate_name, @@ -252,13 +255,6 @@ impl BindArgs { self.alloy_rev.clone(), !self.skip_extra_derives, )?; - } else { - trace!(single_file = self.single_file, "generating module"); - solmacrogen.write_to_module( - bindings_root, - self.single_file, - !self.skip_extra_derives, - )?; } Ok(()) diff --git a/crates/forge/src/cmd/bind_json.rs b/crates/forge/src/cmd/bind_json.rs index 7c6bfaa52ad74..113a32ba5fb7e 100644 --- a/crates/forge/src/cmd/bind_json.rs +++ b/crates/forge/src/cmd/bind_json.rs @@ -1,36 +1,39 @@ use super::eip712::Resolver; use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; -use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_cli::{ + opts::{BuildOpts, configure_pcx_from_solc}, + utils::LoadConfig, +}; +use foundry_common::{TYPE_BINDING_PREFIX, fs}; use foundry_compilers::{ - artifacts::{ - output_selection::OutputSelection, ContractDefinitionPart, Source, SourceUnit, - SourceUnitPart, Sources, - }, - multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, - project::ProjectCompiler, - solc::SolcLanguage, - Graph, Project, + CompilerInput, Graph, Project, + artifacts::{Source, Sources}, + multi::{MultiCompilerLanguage, MultiCompilerParser}, + solc::{SolcLanguage, SolcVersionedInput}, }; use foundry_config::Config; use itertools::Itertools; +use path_slash::PathExt; use rayon::prelude::*; -use solar_parse::{ - ast::{self, interface::source_map::FileName, visit::Visit, Arena, FunctionKind, Span, VarMut}, - interface::Session, +use semver::Version; +use solar::parse::{ Parser as SolarParser, + ast::{self, Arena, FunctionKind, Span, VarMut, interface::source_map::FileName, visit::Visit}, + interface::Session, }; use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::{self, Write}, + collections::{BTreeMap, BTreeSet, HashSet}, + fmt::Write, ops::ControlFlow, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, }; foundry_config::impl_figment_convert!(BindJsonArgs, build); +const JSON_BINDINGS_PLACEHOLDER: &str = "library JsonBindings {}"; + /// CLI arguments for `forge bind-json`. #[derive(Clone, Debug, Parser)] pub struct BindJsonArgs { @@ -44,37 +47,16 @@ pub struct BindJsonArgs { impl BindJsonArgs { pub fn run(self) -> Result<()> { - self.preprocess()?.compile()?.find_structs()?.resolve_imports_and_aliases().write()?; - - Ok(()) - } - - /// In cases when user moves/renames/deletes structs, compiler will start failing because - /// generated bindings will be referencing non-existing structs or importing non-existing - /// files. - /// - /// Because of that, we need a little bit of preprocessing to make sure that bindings will still - /// be valid. - /// - /// The strategy is: - /// 1. Replace bindings file with an empty one to get rid of potentially invalid imports. - /// 2. Remove all function bodies to get rid of `serialize`/`deserialize` invocations. - /// 3. Remove all `immutable` attributes to avoid errors because of erased constructors - /// initializing them. - /// - /// After that we'll still have enough information for bindings but compilation should succeed - /// in most of the cases. - fn preprocess(self) -> Result { let config = self.load_config()?; let project = config.ephemeral_project()?; - let target_path = config.root.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); + // Step 1: Read and preprocess sources let sources = project.paths.read_input_files()?; - let graph = Graph::::resolve_sources(&project.paths, sources)?; + let graph = Graph::::resolve_sources(&project.paths, sources)?; // We only generate bindings for a single Solidity version to avoid conflicts. - let mut sources = graph + let (version, mut sources, _) = graph // resolve graph into mapping language -> version -> sources .into_sources_by_version(&project)? .sources @@ -86,11 +68,42 @@ impl BindJsonArgs { .into_iter() // For now, we are always picking the latest version. .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) - .unwrap() - .1; + .unwrap(); + + // Step 2: Preprocess sources to handle potentially invalid bindings + self.preprocess_sources(&mut sources)?; + + // Insert empty bindings file. + sources.insert(target_path.clone(), Source::new(JSON_BINDINGS_PLACEHOLDER)); + + // Step 3: Find structs and generate bindings + let structs_to_write = + self.find_and_resolve_structs(&config, &project, version, sources, &target_path)?; + + // Step 4: Write bindings + self.write_bindings(&structs_to_write, &target_path)?; + + Ok(()) + } + /// In cases when user moves/renames/deletes structs, compiler will start failing because + /// generated bindings will be referencing non-existing structs or importing non-existing + /// files. + /// + /// Because of that, we need a little bit of preprocessing to make sure that bindings will still + /// be valid. + /// + /// The strategy is: + /// 1. Replace bindings file with an empty one to get rid of potentially invalid imports. + /// 2. Remove all function bodies to get rid of `serialize`/`deserialize` invocations. + /// 3. Remove all `immutable` attributes to avoid errors because of erased constructors + /// initializing them. + /// + /// After that we'll still have enough information for bindings but compilation should succeed + /// in most of the cases. + fn preprocess_sources(&self, sources: &mut Sources) -> Result<()> { let sess = Session::builder().with_stderr_emitter().build(); - let result = sess.enter_parallel(|| -> solar_parse::interface::Result<()> { + let result = sess.enter(|| -> solar::interface::Result<()> { sources.0.par_iter_mut().try_for_each(|(path, source)| { let mut content = Arc::try_unwrap(std::mem::take(&mut source.content)).unwrap(); @@ -99,7 +112,7 @@ impl BindJsonArgs { &sess, &arena, FileName::Real(path.clone()), - content.to_string(), + content.clone(), )?; let ast = parser.parse_file().map_err(|e| e.emit())?; @@ -112,277 +125,116 @@ impl BindJsonArgs { }) }); eyre::ensure!(result.is_ok(), "failed parsing"); - - // Insert empty bindings file. - sources.insert(target_path.clone(), Source::new("library JsonBindings {}")); - - Ok(PreprocessedState { sources, target_path, project, config }) - } -} - -struct PreprocessorVisitor { - updates: Vec<(Span, &'static str)>, -} - -impl PreprocessorVisitor { - fn new() -> Self { - Self { updates: Vec::new() } - } - - fn update(mut self, sess: &Session, content: &mut String) { - if self.updates.is_empty() { - return; - } - - let sf = sess.source_map().lookup_source_file(self.updates[0].0.lo()); - let base = sf.start_pos.0; - - self.updates.sort_by_key(|(span, _)| span.lo()); - let mut shift = 0_i64; - for (span, new) in self.updates { - let lo = span.lo() - base; - let hi = span.hi() - base; - let start = ((lo.0 as i64) - shift) as usize; - let end = ((hi.0 as i64) - shift) as usize; - - content.replace_range(start..end, new); - shift += (end - start) as i64; - shift -= new.len() as i64; - } - } -} - -impl<'ast> Visit<'ast> for PreprocessorVisitor { - type BreakValue = solar_parse::interface::data_structures::Never; - - fn visit_item_function( - &mut self, - func: &'ast ast::ItemFunction<'ast>, - ) -> ControlFlow { - // Replace function bodies with a noop statement. - if let Some(block) = &func.body { - if !block.is_empty() { - let span = block.first().unwrap().span.to(block.last().unwrap().span); - let new_body = match func.kind { - FunctionKind::Modifier => "_;", - _ => "revert();", - }; - self.updates.push((span, new_body)); - } - } - - self.walk_item_function(func) - } - - fn visit_variable_definition( - &mut self, - var: &'ast ast::VariableDefinition<'ast>, - ) -> ControlFlow { - // Remove `immutable` attributes. - if let Some(VarMut::Immutable) = var.mutability { - self.updates.push((var.span, "")); - } - - self.walk_variable_definition(var) - } -} - -/// A single struct definition for which we need to generate bindings. -#[derive(Debug, Clone)] -struct StructToWrite { - /// Name of the struct definition. - name: String, - /// Name of the contract containing the struct definition. None if the struct is defined at the - /// file level. - contract_name: Option, - /// Import alias for the contract or struct, depending on whether the struct is imported - /// directly, or via a contract. - import_alias: Option, - /// Path to the file containing the struct definition. - path: PathBuf, - /// EIP712 schema for the struct. - schema: String, - /// Name of the struct definition used in function names and schema_* variables. - name_in_fns: String, -} - -impl StructToWrite { - /// Returns the name of the imported item. If struct is defined at the file level, returns the - /// struct name, otherwise returns the parent contract name. - fn struct_or_contract_name(&self) -> &str { - self.contract_name.as_deref().unwrap_or(&self.name) - } - - /// Same as [StructToWrite::struct_or_contract_name] but with alias applied. - fn struct_or_contract_name_with_alias(&self) -> &str { - self.import_alias.as_deref().unwrap_or(self.struct_or_contract_name()) - } - - /// Path which can be used to reference this struct in input/output parameters. Either - /// StructName or ParantName.StructName - fn full_path(&self) -> String { - if self.contract_name.is_some() { - format!("{}.{}", self.struct_or_contract_name_with_alias(), self.name) - } else { - self.struct_or_contract_name_with_alias().to_string() - } - } - - fn import_item(&self) -> String { - if let Some(alias) = &self.import_alias { - format!("{} as {}", self.struct_or_contract_name(), alias) - } else { - self.struct_or_contract_name().to_string() - } + Ok(()) } -} -#[derive(Debug)] -struct PreprocessedState { - sources: Sources, - target_path: PathBuf, - project: Project, - config: Config, -} + /// Find structs, resolve conflicts, and prepare them for writing + fn find_and_resolve_structs( + &self, + config: &Config, + project: &Project, + version: Version, + sources: Sources, + _target_path: &Path, + ) -> Result> { + let settings = config.solc_settings()?; + let include = &config.bind_json.include; + let exclude = &config.bind_json.exclude; + let root = &config.root; + + let input = SolcVersionedInput::build(sources, settings, SolcLanguage::Solidity, version); + + let mut sess = Session::builder().with_stderr_emitter().build(); + sess.dcx.set_flags_mut(|flags| flags.track_diagnostics = false); + let mut compiler = solar::sema::Compiler::new(sess); -impl PreprocessedState { - fn compile(self) -> Result { - let Self { sources, target_path, mut project, config } = self; - - project.update_output_selection(|selection| { - *selection = OutputSelection::ast_output_selection(); - }); - - let output = with_compilation_reporter(false, || { - ProjectCompiler::with_sources(&project, sources)?.compile() - })?; - - if output.has_compiler_errors() { - eyre::bail!("{output}"); - } - - // Collect ASTs by getting them from sources and converting into strongly typed - // `SourceUnit`s. Also strips root from paths. - let asts = output - .into_output() - .sources - .into_iter() - .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) - .map(|(path, ast)| { - Ok(( - path.strip_prefix(project.root()).unwrap_or(&path).to_path_buf(), - serde_json::from_str::(&serde_json::to_string(&ast)?)?, - )) - }) - .collect::>>()?; - - Ok(CompiledState { asts, target_path, config, project }) - } -} - -#[derive(Debug, Clone)] -struct CompiledState { - asts: BTreeMap, - target_path: PathBuf, - config: Config, - project: Project, -} + let mut structs_to_write = Vec::new(); -impl CompiledState { - fn find_structs(self) -> Result { - let Self { asts, target_path, config, project } = self; - - // construct mapping (file, id) -> (struct definition, optional parent contract name) - let structs = asts - .iter() - .flat_map(|(path, ast)| { - let mut structs = Vec::new(); - // we walk AST directly instead of using visitors because we need to distinguish - // between file-level and contract-level struct definitions - for node in &ast.nodes { - match node { - SourceUnitPart::StructDefinition(def) => { - structs.push((def, None)); - } - SourceUnitPart::ContractDefinition(contract) => { - for node in &contract.nodes { - if let ContractDefinitionPart::StructDefinition(def) = node { - structs.push((def, Some(contract.name.clone()))); - } - } - } - _ => {} + compiler.enter_mut(|compiler| -> Result<()> { + // Set up the parsing context with the project paths, without adding the source files + let mut pcx = compiler.parse(); + configure_pcx_from_solc(&mut pcx, &project.paths, &input, false); + + let mut target_files = HashSet::new(); + for (path, source) in &input.input.sources { + if include.is_empty() { + // Exclude library files by default + if project.paths.has_library_ancestor(path) { + continue; + } + } else { + if !include.iter().any(|matcher| matcher.is_match(path)) { + continue; } } - structs.into_iter().map(|(def, parent)| ((path.as_path(), def.id), (def, parent))) - }) - .collect::>(); - - // Resolver for EIP712 schemas - let resolver = Resolver::new(&asts); - - let mut structs_to_write = Vec::new(); - let include = config.bind_json.include; - let exclude = config.bind_json.exclude; - - for ((path, id), (def, contract_name)) in structs { - // For some structs there's no schema (e.g. if they contain a mapping), so we just skip - // those. - let Some(schema) = resolver.resolve_struct_eip712(id)? else { continue }; - - if !include.is_empty() { - if !include.iter().any(|matcher| matcher.is_match(path)) { + if exclude.iter().any(|matcher| matcher.is_match(path)) { continue; } - } else { - // Exclude library files by default - if project.paths.has_library_ancestor(path) { - continue; + + if let Ok(src_file) = compiler + .sess() + .source_map() + .new_source_file(path.clone(), source.content.as_str()) + { + target_files.insert(Arc::clone(&src_file)); + pcx.add_file(src_file); } } - if exclude.iter().any(|matcher| matcher.is_match(path)) { - continue; + // Parse and resolve + pcx.parse(); + let Ok(ControlFlow::Continue(())) = compiler.lower_asts() else { return Ok(()) }; + let gcx = compiler.gcx(); + let hir = &gcx.hir; + let resolver = Resolver::new(gcx); + for id in resolver.struct_ids() { + if let Some(schema) = resolver.resolve_struct_eip712(id) { + let def = hir.strukt(id); + let source = hir.source(def.source); + + if !target_files.contains(&source.file) { + continue; + } + + if let FileName::Real(path) = &source.file.name { + structs_to_write.push(StructToWrite { + name: def.name.as_str().into(), + contract_name: def + .contract + .map(|id| hir.contract(id).name.as_str().into()), + path: path.strip_prefix(root).unwrap_or(path).to_path_buf(), + schema, + // will be filled later + import_alias: None, + name_in_fns: String::new(), + }); + } + } } + Ok(()) + })?; - structs_to_write.push(StructToWrite { - name: def.name.clone(), - contract_name, - path: path.to_path_buf(), - schema, + eyre::ensure!(compiler.sess().dcx.has_errors().is_ok(), "errors occurred"); - // will be filled later - import_alias: None, - name_in_fns: String::new(), - }) - } + // Resolve import aliases and function names + self.resolve_conflicts(&mut structs_to_write); - Ok(StructsState { structs_to_write, target_path }) + Ok(structs_to_write) } -} -#[derive(Debug)] -struct StructsState { - structs_to_write: Vec, - target_path: PathBuf, -} - -impl StructsState { - /// We manage 2 namespsaces for JSON bindings: + /// We manage 2 namespaces for JSON bindings: /// - Namespace of imported items. This includes imports of contracts containing structs and /// structs defined at the file level. /// - Namespace of struct names used in function names and schema_* variables. /// /// Both of those might contain conflicts, so we need to resolve them. - fn resolve_imports_and_aliases(self) -> ResolvedState { - let Self { mut structs_to_write, target_path } = self; - + fn resolve_conflicts(&self, structs_to_write: &mut [StructToWrite]) { // firstly, we resolve imported names conflicts // construct mapping name -> paths from which items with such name are imported let mut names_to_paths = BTreeMap::new(); - for s in &structs_to_write { + for s in structs_to_write.iter() { names_to_paths .entry(s.struct_or_contract_name()) .or_insert_with(BTreeSet::new) @@ -394,8 +246,7 @@ impl StructsState { for (name, paths) in names_to_paths { if paths.len() <= 1 { - // no alias needed - continue + continue; // no alias needed } for (i, path) in paths.into_iter().enumerate() { @@ -406,7 +257,7 @@ impl StructsState { } } - for s in &mut structs_to_write { + for s in structs_to_write.iter_mut() { let name = s.struct_or_contract_name(); if aliases.contains_key(name) { s.import_alias = Some(aliases[name][&s.path].clone()); @@ -434,40 +285,22 @@ impl StructsState { } } - for (s, fn_name) in structs_to_write.iter_mut().zip(fn_names.into_iter()) { + for (s, fn_name) in structs_to_write.iter_mut().zip(fn_names) { s.name_in_fns = fn_name.unwrap_or(s.name.clone()); } - - ResolvedState { structs_to_write, target_path } } -} -struct ResolvedState { - structs_to_write: Vec, - target_path: PathBuf, -} - -impl ResolvedState { - fn write(self) -> Result { + /// Write the final bindings file + fn write_bindings( + &self, + structs_to_write: &[StructToWrite], + target_path: &PathBuf, + ) -> Result<()> { let mut result = String::new(); - self.write_imports(&mut result)?; - self.write_vm(&mut result); - self.write_library(&mut result)?; - - if let Some(parent) = self.target_path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(&self.target_path, &result)?; - - sh_println!("Bindings written to {}", self.target_path.display())?; - - Ok(result) - } - fn write_imports(&self, result: &mut String) -> fmt::Result { + // Write imports let mut grouped_imports = BTreeMap::new(); - - for struct_to_write in &self.structs_to_write { + for struct_to_write in structs_to_write { let item = struct_to_write.import_item(); grouped_imports .entry(struct_to_write.path.as_path()) @@ -479,18 +312,15 @@ impl ResolvedState { for (path, names) in grouped_imports { writeln!( - result, + &mut result, "import {{{}}} from \"{}\";", names.iter().join(", "), - path.display() + path.to_slash_lossy() )?; } - Ok(()) - } - - /// Writes minimal VM interface to not depend on forge-std version - fn write_vm(&self, result: &mut String) { + // Write VM interface + // Writes minimal VM interface to not depend on forge-std version result.push_str(r#" interface Vm { function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); @@ -500,9 +330,8 @@ interface Vm { function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json); } "#); - } - fn write_library(&self, result: &mut String) -> fmt::Result { + // Write library result.push_str( r#" library JsonBindings { @@ -510,19 +339,20 @@ library JsonBindings { "#, ); + // write schema constants - for struct_to_write in &self.structs_to_write { + for struct_to_write in structs_to_write { writeln!( - result, - " string constant schema_{} = \"{}\";", - struct_to_write.name_in_fns, struct_to_write.schema + &mut result, + " {}{} = \"{}\";", + TYPE_BINDING_PREFIX, struct_to_write.name_in_fns, struct_to_write.schema )?; } // write serialization functions - for struct_to_write in &self.structs_to_write { + for struct_to_write in structs_to_write { write!( - result, + &mut result, r#" function serialize({path} memory value) internal pure returns (string memory) {{ return vm.serializeJsonType(schema_{name_in_fns}, abi.encode(value)); @@ -551,6 +381,131 @@ library JsonBindings { result.push_str("}\n"); + // Write to file + if let Some(parent) = target_path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(target_path, &result)?; + + sh_println!("Bindings written to {}", target_path.display())?; + Ok(()) } } + +struct PreprocessorVisitor { + updates: Vec<(Span, &'static str)>, +} + +impl PreprocessorVisitor { + const fn new() -> Self { + Self { updates: Vec::new() } + } + + fn update(mut self, sess: &Session, content: &mut String) { + if self.updates.is_empty() { + return; + } + + let sf = sess.source_map().lookup_source_file(self.updates[0].0.lo()); + let base = sf.start_pos.0; + + self.updates.sort_by_key(|(span, _)| span.lo()); + let mut shift = 0_i64; + for (span, new) in self.updates { + let lo = span.lo() - base; + let hi = span.hi() - base; + let start = ((lo.0 as i64) - shift) as usize; + let end = ((hi.0 as i64) - shift) as usize; + + content.replace_range(start..end, new); + shift += (end - start) as i64; + shift -= new.len() as i64; + } + } +} + +impl<'ast> Visit<'ast> for PreprocessorVisitor { + type BreakValue = solar::interface::data_structures::Never; + + fn visit_item_function( + &mut self, + func: &'ast ast::ItemFunction<'ast>, + ) -> ControlFlow { + // Replace function bodies with a noop statement. + if let Some(block) = &func.body + && !block.is_empty() + { + let span = block.first().unwrap().span.to(block.last().unwrap().span); + let new_body = match func.kind { + FunctionKind::Modifier => "_;", + _ => "revert();", + }; + self.updates.push((span, new_body)); + } + + self.walk_item_function(func) + } + + fn visit_variable_definition( + &mut self, + var: &'ast ast::VariableDefinition<'ast>, + ) -> ControlFlow { + // Remove `immutable` attributes. + if var.mutability == Some(VarMut::Immutable) { + self.updates.push((var.span, "")); + } + + self.walk_variable_definition(var) + } +} + +/// A single struct definition for which we need to generate bindings. +#[derive(Debug, Clone)] +struct StructToWrite { + /// Name of the struct definition. + name: String, + /// Name of the contract containing the struct definition. None if the struct is defined at the + /// file level. + contract_name: Option, + /// Import alias for the contract or struct, depending on whether the struct is imported + /// directly, or via a contract. + import_alias: Option, + /// Path to the file containing the struct definition. + path: PathBuf, + /// EIP712 schema for the struct. + schema: String, + /// Name of the struct definition used in function names and schema_* variables. + name_in_fns: String, +} + +impl StructToWrite { + /// Returns the name of the imported item. If struct is defined at the file level, returns the + /// struct name, otherwise returns the parent contract name. + fn struct_or_contract_name(&self) -> &str { + self.contract_name.as_deref().unwrap_or(&self.name) + } + + /// Same as [StructToWrite::struct_or_contract_name] but with alias applied. + fn struct_or_contract_name_with_alias(&self) -> &str { + self.import_alias.as_deref().unwrap_or(self.struct_or_contract_name()) + } + + /// Path which can be used to reference this struct in input/output parameters. Either + /// StructName or ParentName.StructName + fn full_path(&self) -> String { + if self.contract_name.is_some() { + format!("{}.{}", self.struct_or_contract_name_with_alias(), self.name) + } else { + self.struct_or_contract_name_with_alias().to_string() + } + } + + fn import_item(&self) -> String { + if let Some(alias) = &self.import_alias { + format!("{} as {}", self.struct_or_contract_name(), alias) + } else { + self.struct_or_contract_name().to_string() + } + } +} diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index e64569206775b..2aea7fd0830b6 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -1,21 +1,26 @@ use super::{install, watch::WatchArgs}; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use forge_lint::{linter::Linter, sol::SolidityLinter}; +use foundry_cli::{ + opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, + utils::{Git, LoadConfig, cache_local_signatures}, +}; use foundry_common::{compile::ProjectCompiler, shell}; use foundry_compilers::{ - compilers::{multi::MultiCompilerLanguage, Language}, + CompilationError, FileFilter, Project, ProjectCompileOutput, + compilers::{Language, multi::MultiCompilerLanguage}, + solc::SolcLanguage, utils::source_files_iter, - Project, ProjectCompileOutput, }; use foundry_config::{ + Config, SkipBuildFilters, figment::{ - self, + self, Metadata, Profile, Provider, error::Kind::InvalidType, value::{Dict, Map, Value}, - Metadata, Profile, Provider, }, - Config, + filter::expand_globs, }; use serde::Serialize; use std::path::PathBuf; @@ -56,6 +61,14 @@ pub struct BuildArgs { #[serde(skip)] pub ignore_eip_3860: bool, + /// Skip the post-build lint step for this invocation. + /// + /// Equivalent to setting `lint_on_build = false` under `[lint]` in foundry.toml, + /// but only for the current command. + #[arg(long, visible_alias = "skip-lint")] + #[serde(skip)] + pub no_lint: bool, + #[command(flatten)] #[serde(flatten)] pub build: BuildOpts, @@ -66,14 +79,18 @@ pub struct BuildArgs { } impl BuildArgs { - pub fn run(self) -> Result { + pub async fn run(self) -> Result { let mut config = self.load_config()?; - if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings + { // need to re-configure here to also catch additional remappings config = self.load_config()?; } + self.check_soldeer_lock_consistency(&config).await; + self.check_foundry_lock_consistency(&config); + let project = config.project()?; // Collect sources to compile if build subdirectories specified. @@ -98,15 +115,127 @@ impl BuildArgs { .ignore_eip_3860(self.ignore_eip_3860) .bail(!format_json); - let output = compiler.compile(&project)?; + let mut output = compiler.compile(&project)?; + + // Cache project selectors. + cache_local_signatures(&output)?; if format_json && !self.names && !self.sizes { sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?; } + // Only run the `SolidityLinter` if lint on build and no compilation errors. + if !self.no_lint + && config.lint.lint_on_build + && !output.output().errors.iter().any(|e| e.is_error()) + && let Err(err) = self.lint(&project, &config, self.paths.as_deref(), &mut output) + { + emit_lint_failure_notice(); + return Err(err.wrap_err( + "post-build lint step failed; rerun with --no-lint or set \ + `lint_on_build = false` under `[lint]` in foundry.toml to bypass", + )); + } + Ok(output) } + fn lint( + &self, + project: &Project, + config: &Config, + files: Option<&[PathBuf]>, + output: &mut ProjectCompileOutput, + ) -> Result<()> { + let format_json = shell::is_json(); + if project.compiler.solc.is_some() && !shell::is_quiet() { + let linter = SolidityLinter::new(config.project_paths()) + .with_json_emitter(format_json) + .with_description(!format_json) + .with_severity(if config.lint.severity.is_empty() { + None + } else { + Some(config.lint.severity.clone()) + }) + .without_lints(if config.lint.exclude_lints.is_empty() { + None + } else { + Some( + config + .lint + .exclude_lints + .iter() + .filter_map(|s| forge_lint::sol::SolLint::try_from(s.as_str()).ok()) + .collect(), + ) + }) + .with_lint_specific(&config.lint.lint_specific); + + // Expand ignore globs and canonicalize from the get go + let ignored = expand_globs(&config.root, config.lint.ignore.iter())? + .iter() + .flat_map(foundry_common::fs::canonicalize_path) + .collect::>(); + + let skip = SkipBuildFilters::new(config.skip.clone(), config.root.clone()); + let curr_dir = std::env::current_dir()?; + let input_files = config + .project_paths::() + .input_files_iter() + .filter(|p| { + // Lint only specified build files, if any. + if let Some(files) = files { + return files.iter().any(|file| &curr_dir.join(file) == p); + } + skip.is_match(p) + && !(ignored.contains(p) || ignored.contains(&curr_dir.join(p))) + }) + .collect::>(); + + let solar_sources = + get_solar_sources_from_compile_output(config, output, Some(&input_files), None)?; + if solar_sources.input.sources.is_empty() { + if !input_files.is_empty() { + sh_warn!("unable to lint. Solar only supports Solidity versions >=0.8.0")?; + } + return Ok(()); + } + + // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new + // compiler, we should reuse the parser from the project output. + // + // Buffer emitter so parse-phase errors surface verbatim in `convert_solar_errors`. + let mut compiler = solar::sema::Compiler::new( + solar::interface::Session::builder() + .with_buffer_emitter(Default::default()) + .build(), + ); + + // Load the solar-compatible sources to the pcx before linting + compiler.enter_mut(|compiler| { + let mut pcx = compiler.parse(); + configure_pcx_from_solc(&mut pcx, &config.project_paths(), &solar_sources, true); + pcx.set_resolve_imports(true); + pcx.parse(); + }); + + // Flush buffered parse-phase warnings; on error, `convert_solar_errors` surfaces + // them in the returned error instead, so skip to avoid duplicates. + if compiler.sess().dcx.has_errors().is_ok() + && let Some(diags) = compiler.sess().emitted_diagnostics() + { + let s = diags.to_string(); + if !s.is_empty() { + let _ = sh_eprint!("{s}"); + } + } + + linter.lint(&input_files, config.deny, &mut compiler)?; + } + + Ok(()) + } + /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see @@ -117,7 +246,7 @@ impl BuildArgs { } /// Returns whether `BuildArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } @@ -131,6 +260,112 @@ impl BuildArgs { Ok([config.src, config.test, config.script, foundry_toml]) }) } + + /// Check soldeer.lock file consistency using soldeer_core APIs + async fn check_soldeer_lock_consistency(&self, config: &Config) { + let soldeer_lock_path = config.root.join("soldeer.lock"); + if !soldeer_lock_path.exists() { + return; + } + + // Note: read_lockfile returns Ok with empty entries for malformed files + let Ok(lockfile) = soldeer_core::lock::read_lockfile(&soldeer_lock_path) else { + return; + }; + + let deps_dir = config.root.join("dependencies"); + for entry in &lockfile.entries { + let dep_name = entry.name(); + + // Use soldeer_core's integrity check + match soldeer_core::install::check_dependency_integrity(entry, &deps_dir).await { + Ok(status) => { + use soldeer_core::install::DependencyStatus; + // Check if status indicates a problem + if matches!( + status, + DependencyStatus::Missing | DependencyStatus::FailedIntegrity + ) { + sh_warn!("Dependency '{}' integrity check failed: {:?}", dep_name, status) + .ok(); + } + } + Err(e) => { + sh_warn!("Dependency '{}' integrity check error: {}", dep_name, e).ok(); + } + } + } + } + + /// Check foundry.lock file consistency with git submodules + fn check_foundry_lock_consistency(&self, config: &Config) { + use crate::lockfile::{DepIdentifier, FOUNDRY_LOCK, Lockfile}; + + let foundry_lock_path = config.root.join(FOUNDRY_LOCK); + if !foundry_lock_path.exists() { + return; + } + + let git = Git::new(&config.root); + + let mut lockfile = Lockfile::new(&config.root).with_git(&git); + if let Err(e) = lockfile.read() { + if !e.to_string().contains("Lockfile not found") { + sh_warn!("Failed to parse foundry.lock: {}", e).ok(); + } + return; + } + + for (dep_path, dep_identifier) in lockfile.iter() { + let full_path = config.root.join(dep_path); + + if !full_path.exists() { + sh_warn!("Dependency '{}' not found at expected path", dep_path.display()).ok(); + continue; + } + + let actual_rev = match git.get_rev("HEAD", &full_path) { + Ok(rev) => rev, + Err(_) => { + sh_warn!("Failed to get git revision for dependency '{}'", dep_path.display()) + .ok(); + continue; + } + }; + + // Compare with the expected revision from lockfile + let expected_rev = match dep_identifier { + DepIdentifier::Branch { rev, .. } + | DepIdentifier::Tag { rev, .. } + | DepIdentifier::Rev { rev, .. } => rev.clone(), + }; + + if actual_rev != expected_rev { + sh_warn!( + "Dependency '{}' revision mismatch: expected '{}', found '{}'", + dep_path.display(), + expected_rev, + actual_rev + ) + .ok(); + } + } + } +} + +/// Notice shown on lint-on-build failure; printed separately so it survives single-line +/// cause-chain rendering. +const LINT_FAILURE_NOTICE: &str = "\ +note: post-build lint failed, but compilation succeeded. +bypass with `--no-lint` or set `lint_on_build = false` under `[lint]` in foundry.toml +docs: https://getfoundry.sh/forge/linting#disable-linting-on-build +"; + +fn emit_lint_failure_notice() { + if shell::is_json() { + return; + } + let _ = sh_eprintln!("\n{LINT_FAILURE_NOTICE}"); } // Make this args a `figment::Provider` so that it can be merged into the `Config` diff --git a/crates/forge/src/cmd/cache.rs b/crates/forge/src/cmd/cache.rs index efbdde5cb6981..0c1f22f2e6de6 100644 --- a/crates/forge/src/cmd/cache.rs +++ b/crates/forge/src/cmd/cache.rs @@ -1,10 +1,11 @@ use cache::Cache; use clap::{ - builder::{PossibleValuesParser, TypedValueParser}, Arg, Command, Parser, Subcommand, + builder::{PossibleValuesParser, TypedValueParser}, }; use eyre::Result; -use foundry_config::{cache, Chain, Config, NamedChain}; +use foundry_common::sh_warn; +use foundry_config::{Chain, Config, NamedChain, cache}; use std::{ffi::OsStr, str::FromStr}; use strum::VariantNames; @@ -60,13 +61,16 @@ impl CleanArgs { for chain_or_all in chains { match chain_or_all { ChainOrAll::NamedChain(chain) => { - clean_chain_cache(chain, blocks.to_vec(), etherscan)? + clean_chain_cache(chain, blocks.clone(), etherscan)? } ChainOrAll::All => { - if etherscan { - Config::clean_foundry_etherscan_cache()?; + let warnings = if etherscan { + Config::clean_foundry_etherscan_cache()? } else { Config::clean_foundry_cache()? + }; + for warning in warnings { + let _ = sh_warn!("{warning}"); } } } @@ -128,17 +132,24 @@ impl FromStr for ChainOrAll { fn clean_chain_cache(chain: impl Into, blocks: Vec, etherscan: bool) -> Result<()> { let chain = chain.into(); + let mut warnings = Vec::new(); if blocks.is_empty() { - Config::clean_foundry_etherscan_chain_cache(chain)?; + warnings.extend(Config::clean_foundry_etherscan_chain_cache(chain)?); if etherscan { - return Ok(()) + for warning in warnings { + let _ = sh_warn!("{warning}"); + } + return Ok(()); } - Config::clean_foundry_chain_cache(chain)?; + warnings.extend(Config::clean_foundry_chain_cache(chain)?); } else { for block in blocks { - Config::clean_foundry_block_cache(chain, block)?; + warnings.extend(Config::clean_foundry_block_cache(chain, block)?); } } + for warning in warnings { + let _ = sh_warn!("{warning}"); + } Ok(()) } diff --git a/crates/forge/src/cmd/clone.rs b/crates/forge/src/cmd/clone.rs index a5bc4b2aa4676..28b5df47ec1ad 100644 --- a/crates/forge/src/cmd/clone.rs +++ b/crates/forge/src/cmd/clone.rs @@ -1,11 +1,14 @@ use super::{init::InitArgs, install::DependencyInstallOpts}; use alloy_primitives::{Address, Bytes, ChainId, TxHash}; -use clap::{Parser, ValueHint}; +use clap::{Parser, ValueEnum, ValueHint}; use eyre::Result; +use forge_verify::sourcify::SOURCIFY_URL; use foundry_block_explorers::{ - contract::{ContractCreationData, ContractMetadata, Metadata}, - errors::EtherscanError, Client, + contract::{ + ContractCreationData, ContractMetadata, Metadata, SourceCodeEntry, SourceCodeMetadata, + }, + errors::EtherscanError, }; use foundry_cli::{ opts::EtherscanOpts, @@ -13,20 +16,25 @@ use foundry_cli::{ }; use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ + ProjectCompileOutput, ProjectPathsConfig, artifacts::{ + ConfigurableContractArtifact, Settings, StorageLayout, output_selection::ContractOutputSelection, remappings::{RelativeRemapping, Remapping}, - ConfigurableContractArtifact, Settings, StorageLayout, }, compilers::solc::Solc, - ProjectCompileOutput, ProjectPathsConfig, }; use foundry_config::{Chain, Config}; +use reqwest::StatusCode; +use serde::Deserialize; use std::{ + collections::{BTreeMap, HashMap}, fs::read_dir, path::{Path, PathBuf}, time::Duration, }; +use tracing::trace; +use url::Url; /// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a /// cloned contract. The metadata can be serialized to a metadata file in the cloned project root. @@ -52,14 +60,25 @@ pub struct CloneMetadata { pub storage_layout: StorageLayout, } +/// Source explorer type for `forge clone`. +#[derive(Clone, Copy, Debug, ValueEnum, Default)] +pub enum SourceExplorer { + /// Use Etherscan API (default). + #[default] + Etherscan, + /// Use Sourcify API. + Sourcify, +} + /// CLI arguments for `forge clone`. /// -/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan) in the -/// following steps: +/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan, Sourcify) in +/// the following steps: /// 1. Fetch the contract source code from the block explorer. /// 2. Initialize a empty foundry project at the `root` directory specified in `CloneArgs`. /// 3. Dump the contract sources to the source directory. -/// 4. Update the `foundry.toml` configuration file with the compiler settings from Etherscan. +/// 4. Update the `foundry.toml` configuration file with the compiler settings from the block +/// explorer. /// 5. Try compile the cloned contract, so that we can get the original storage layout. This /// original storage layout is preserved in the `CloneMetadata` so that if the user later /// modifies the contract, it is possible to quickly check the storage layout compatibility with @@ -78,7 +97,7 @@ pub struct CloneArgs { #[arg(long)] pub no_remappings_txt: bool, - /// Keep the original directory structure collected from Etherscan. + /// Keep the original directory structure collected from the block explorer. /// /// If this flag is set, the directory structure of the cloned project will be kept as is. /// By default, the directory structure is re-orgnized to increase the readability, but may @@ -86,6 +105,18 @@ pub struct CloneArgs { #[arg(long)] pub keep_directory_structure: bool, + /// Source explorer to use for fetching contract data. + /// + /// Can be either "etherscan" (default) or "sourcify". + #[arg(long, default_value = "etherscan", value_name = "EXPLORER")] + pub source: SourceExplorer, + + /// Custom Sourcify API URL. + /// + /// Implies `--source sourcify`. + #[arg(long, value_name = "URL")] + pub sourcify_url: Option, + #[command(flatten)] pub etherscan: EtherscanOpts, @@ -95,24 +126,47 @@ pub struct CloneArgs { impl CloneArgs { pub async fn run(self) -> Result<()> { - let Self { address, root, install, etherscan, no_remappings_txt, keep_directory_structure } = - self; + let Self { + address, + root, + install, + etherscan, + no_remappings_txt, + keep_directory_structure, + source, + sourcify_url, + } = self; // step 0. get the chain and api key from the config let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); - let etherscan_api_version = config.get_etherscan_api_version(Some(chain)); - let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = - Client::new_with_api_version(chain, etherscan_api_key.clone(), etherscan_api_version)?; - - // step 1. get the metadata from client - sh_println!("Downloading the source code of {address} from Etherscan...")?; - let meta = Self::collect_metadata_from_client(address, &client).await?; + // If sourcify_url is specified, use Sourcify as the source + let source = if sourcify_url.is_some() { SourceExplorer::Sourcify } else { source }; + + // step 1. get the metadata from client based on source type + let (meta, explorer_name, sourcify_client) = match source { + SourceExplorer::Etherscan => { + let client = config + .get_etherscan_config_with_chain(Some(chain))? + .ok_or_else(|| { + eyre::eyre!("No Etherscan API key configured for chain {chain}") + })? + .into_client_with_no_proxy(config.eth_rpc_no_proxy)?; + sh_println!("Downloading the source code of {address} from Etherscan...")?; + let meta = Self::collect_metadata_from_client(address, &client).await?; + (meta, "Etherscan", None) + } + SourceExplorer::Sourcify => { + let client = SourcifyClient::with_url(chain, sourcify_url.as_deref()); + sh_println!("Downloading the source code of {address} from Sourcify...")?; + let meta = Self::collect_metadata_from_client(address, &client).await?; + (meta, "Sourcify", Some(client)) + } + }; // step 2. initialize an empty project - Self::init_an_empty_project(&root, install)?; + Self::init_an_empty_project(&root, install).await?; // canonicalize the root path // note that at this point, the root directory must have been created let root = dunce::canonicalize(&root)?; @@ -122,20 +176,33 @@ impl CloneArgs { .await?; // step 4. collect the compilation metadata - // if the etherscan api key is not set, we need to wait for 3 seconds between calls - sh_println!("Collecting the creation information of {address} from Etherscan...")?; - - if etherscan_api_key.is_empty() { - sh_warn!("Waiting for 5 seconds to avoid rate limit...")?; - tokio::time::sleep(Duration::from_secs(5)).await; + sh_println!("Collecting the creation information of {address} from {explorer_name}...")?; + + match source { + SourceExplorer::Etherscan => { + let etherscan_config = + config.get_etherscan_config_with_chain(Some(chain))?.ok_or_else(|| { + eyre::eyre!("No Etherscan API key configured for chain {chain}") + })?; + if etherscan_config.key.is_empty() { + sh_warn!("Waiting for 5 seconds to avoid rate limit...")?; + tokio::time::sleep(Duration::from_secs(5)).await; + } + let client = etherscan_config.into_client_with_no_proxy(config.eth_rpc_no_proxy)?; + Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; + } + SourceExplorer::Sourcify => { + // Reuse the client from step 1 to benefit from cached creation data + let client = sourcify_client.expect("Sourcify client should exist"); + Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; + } } - Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; // step 5. git add and commit the changes if needed if install.commit { let git = Git::new(&root); git.add(Some("--all"))?; - let msg = format!("chore: forge clone {address}"); + let msg = format!("chore: forge clone {address} from {explorer_name}"); git.commit(&msg)?; } @@ -146,7 +213,7 @@ impl CloneArgs { /// /// * `address` - the address of the contract to be cloned. /// * `client` - the client of the block explorer. - pub(crate) async fn collect_metadata_from_client( + pub(crate) async fn collect_metadata_from_client( address: Address, client: &C, ) -> Result { @@ -162,17 +229,14 @@ impl CloneArgs { /// * `root` - the root directory of the project. /// * `enable_git` - whether to enable git for the project. /// * `quiet` - whether to print messages. - pub(crate) fn init_an_empty_project(root: &Path, install: DependencyInstallOpts) -> Result<()> { - // let's try to init the project with default init args - let init_args = InitArgs { root: root.to_path_buf(), install, ..Default::default() }; - init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; - - // remove the unnecessary example contracts - // XXX (ZZ): this is a temporary solution until we have a proper way to remove contracts, - // e.g., add a field in the InitArgs to control the example contract generation - fs::remove_file(root.join("src/Counter.sol"))?; - fs::remove_file(root.join("test/Counter.t.sol"))?; - fs::remove_file(root.join("script/Counter.s.sol"))?; + pub(crate) async fn init_an_empty_project( + root: &Path, + install: DependencyInstallOpts, + ) -> Result<()> { + // Initialize the project with empty set to true to avoid creating example contracts + let init_args = + InitArgs { root: root.to_path_buf(), install, empty: true, ..Default::default() }; + init_args.run().await.map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; Ok(()) } @@ -180,12 +244,12 @@ impl CloneArgs { /// Collect the compilation metadata of the cloned contract. /// This function compiles the cloned contract and collects the compilation metadata. /// - /// * `meta` - the metadata of the contract (from Etherscan). + /// * `meta` - the metadata of the contract (from block explorer). /// * `chain` - the chain where the contract to be cloned locates. /// * `address` - the address of the contract to be cloned. /// * `root` - the root directory of the cloned project. /// * `client` - the client of the block explorer. - pub(crate) async fn collect_compilation_metadata( + pub(crate) async fn collect_compilation_metadata( meta: &Metadata, chain: Chain, address: Address, @@ -197,7 +261,7 @@ impl CloneArgs { let (main_file, main_artifact) = find_main_contract(&compile_output, &meta.contract_name)?; let main_file = main_file.strip_prefix(root)?.to_path_buf(); let storage_layout = - main_artifact.storage_layout.to_owned().expect("storage layout not found"); + main_artifact.storage_layout.clone().expect("storage layout not found"); // dump the metadata to the root directory let creation_tx = client.contract_creation_data(address).await?; @@ -449,17 +513,17 @@ fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result Result Result Result( rv.ok_or_else(|| eyre::eyre!("contract not found")) } -/// EtherscanClient is a trait that defines the methods to interact with Etherscan. +/// ExplorerClient is a trait that defines the methods to interact with block explorers. /// It is defined as a wrapper of the `foundry_block_explorers::Client` to allow mocking. #[cfg_attr(test, mockall::automock)] -pub(crate) trait EtherscanClient { +pub(crate) trait ExplorerClient { async fn contract_source_code( &self, address: Address, @@ -609,8 +671,7 @@ pub(crate) trait EtherscanClient { ) -> std::result::Result; } -impl EtherscanClient for Client { - #[inline] +impl ExplorerClient for Client { async fn contract_source_code( &self, address: Address, @@ -618,7 +679,6 @@ impl EtherscanClient for Client { self.contract_source_code(address).await } - #[inline] async fn contract_creation_data( &self, address: Address, @@ -627,13 +687,328 @@ impl EtherscanClient for Client { } } +/// SourcifyClient is a client for interacting with Sourcify API. +pub(crate) struct SourcifyClient { + client: reqwest::Client, + chain: Chain, + base_url: String, + /// Whether the base_url already contains the full path (v2/contract/chain) + is_full_path: bool, + /// Cached creation data from the first API call + cached_creation_data: std::sync::Arc>>, +} + +impl SourcifyClient { + pub fn with_url(chain: Chain, verifier_url: Option<&str>) -> Self { + let (base_url, is_full_path) = match verifier_url { + Some(url) => ( + Url::parse(url).unwrap_or_else(|_| Url::parse(SOURCIFY_URL).unwrap()), + true, // Custom URL contains full path + ), + None => (Url::parse(SOURCIFY_URL).unwrap(), false), + }; + Self { + client: reqwest::Client::new(), + chain, + base_url: base_url.to_string().trim_end_matches('/').to_string(), + is_full_path, + cached_creation_data: std::sync::Arc::new(std::sync::Mutex::new(None)), + } + } + + fn get_contract_url(&self, address: Address, fields: &str) -> String { + if self.is_full_path { + // Custom URL already contains v2/contract/chain, just append address and fields + format!("{}/{}?fields={}", self.base_url, address, fields) + } else { + // Default URL, need to build full path + format!( + "{}/v2/contract/{}/{}?fields={}", + self.base_url, + self.chain.id(), + address, + fields + ) + } + } +} + +/// Sourcify API response for contract files. +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +#[allow(dead_code, clippy::large_enum_variant)] +enum SourcifyContractResponse { + Success(SourcifyContractData), + Error(SourcifyErrorResponse), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyContractData { + #[serde(default)] + sources: Option>, + #[serde(default)] + abi: Option, + #[serde(default)] + compilation: Option, + #[serde(default)] + #[allow(dead_code)] + creation_code: Option, + #[serde(default)] + #[allow(dead_code)] + deployed_bytecode: Option, + #[serde(default)] + #[allow(dead_code)] + runtime_bytecode: Option, + #[serde(default)] + deployment: Option, + // Additional fields that may be present in the response + #[serde(default)] + #[allow(dead_code)] + match_id: Option, + #[serde(default)] + #[allow(dead_code)] + creation_match: Option, + #[serde(default)] + #[allow(dead_code)] + runtime_match: Option, + #[serde(default)] + #[allow(dead_code)] + verified_at: Option, + #[serde(default)] + #[allow(dead_code)] + r#match: Option, + #[serde(default)] + #[allow(dead_code)] + chain_id: Option, + #[serde(default)] + #[allow(dead_code)] + address: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifySourceFile { + #[serde(default)] + content: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyCompilation { + #[serde(default)] + compiler_version: String, + #[serde(default)] + name: String, + #[serde(default)] + compiler_settings: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyDeployment { + #[serde(default)] + transaction_hash: Option, + #[serde(default)] + deployer: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourcifyErrorResponse { + #[serde(default)] + custom_code: String, + #[serde(default)] + message: String, + #[serde(default)] + #[allow(dead_code)] + error_id: String, + // Error responses should not have sources field + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[allow(dead_code)] + sources: Option<()>, +} + +impl ExplorerClient for SourcifyClient { + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result { + // Request all fields including creation data to cache them + let url = self.get_contract_url(address, "sources,abi,compilation,deployment"); + let response = self + .client + .get(&url) + .send() + .await + .map_err(|e| EtherscanError::Unknown(e.to_string()))?; + + let status = response.status(); + trace!("Sourcify API response: status={:?}, url={}", status, url); + + match status { + StatusCode::NOT_FOUND => return Err(EtherscanError::ContractCodeNotVerified(address)), + StatusCode::TOO_MANY_REQUESTS => return Err(EtherscanError::RateLimitExceeded), + _ => {} + } + + // Read response body once + let response_text = + response.text().await.map_err(|e| EtherscanError::Unknown(e.to_string()))?; + trace!("Sourcify API response body: {}", response_text); + + if !status.is_success() { + return Err(EtherscanError::Unknown(format!( + "Sourcify API error (status {status}): {response_text}" + ))); + } + + // Use the untagged enum to properly handle both success and error responses + let response: SourcifyContractResponse = + serde_json::from_str(&response_text).map_err(|e| { + // Truncate response for error message to avoid huge output + let truncated = if response_text.len() > 500 { + format!("{}... (truncated)", &response_text[..500]) + } else { + response_text.clone() + }; + EtherscanError::Unknown(format!( + "Failed to parse Sourcify response: {e}. Response: {truncated}" + )) + })?; + + let data = match response { + SourcifyContractResponse::Success(data) => data, + SourcifyContractResponse::Error(error) => { + let error_msg = if error.custom_code.is_empty() && error.message.is_empty() { + "Unknown Sourcify API error".to_string() + } else { + format!("Sourcify API error: {} - {}", error.custom_code, error.message) + }; + return Err(EtherscanError::Unknown(error_msg)); + } + }; + + let sources_map = data.sources.ok_or_else(|| { + EtherscanError::Unknown("Sourcify response missing sources field".to_string()) + })?; + + // Convert sources map to SourceCodeMetadata::Sources format + let sources: HashMap = sources_map + .into_iter() + .map(|(path, source_file)| (path, SourceCodeEntry { content: source_file.content })) + .collect(); + + let source_code = SourceCodeMetadata::Sources(sources); + + let contract_name = data + .compilation + .as_ref() + .map(|c| c.name.clone()) + .unwrap_or_else(|| "Contract".to_string()); + + let compiler_version = + data.compilation.as_ref().map(|c| c.compiler_version.clone()).unwrap_or_default(); + + let abi = data.abi.map(|a| a.to_string()).unwrap_or_default(); + + // Cache creation data for later use in contract_creation_data + let tx_hash = data + .deployment + .as_ref() + .and_then(|d| d.transaction_hash.as_ref()) + .and_then(|h| h.parse().ok()) + .unwrap_or(TxHash::ZERO); + let creator = data + .deployment + .as_ref() + .and_then(|d| d.deployer.as_ref()) + .and_then(|a| a.parse().ok()) + .unwrap_or(Address::ZERO); + let creation_data = ContractCreationData { + contract_address: address, + contract_creator: creator, + transaction_hash: tx_hash, + }; + if let Ok(mut cache) = self.cached_creation_data.lock() { + *cache = Some(creation_data); + } + + // Extract compiler_settings from compilation if available + let constructor_arguments = Bytes::default(); + let optimization_used = data + .compilation + .as_ref() + .and_then(|c| c.compiler_settings.as_ref()) + .and_then(|s| s.get("optimizer")) + .and_then(|o| o.get("enabled")) + .and_then(|e| e.as_bool()) + .map(|b| if b { 1 } else { 0 }) + .unwrap_or(0); + + let runs = data + .compilation + .as_ref() + .and_then(|c| c.compiler_settings.as_ref()) + .and_then(|s| s.get("optimizer")) + .and_then(|o| o.get("runs")) + .and_then(|r| r.as_u64()) + .unwrap_or(0); + + Ok(ContractMetadata { + items: vec![Metadata { + source_code, + abi, + contract_name, + compiler_version, + optimization_used, + runs, + constructor_arguments, + evm_version: String::new(), + library: String::new(), + license_type: String::new(), + proxy: 0, + implementation: None, + swarm_source: String::new(), + }], + }) + } + + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result { + // Check cache first + if let Ok(cache) = self.cached_creation_data.lock() + && let Some(ref cached_data) = *cache + && cached_data.contract_address == address + { + return Ok(*cached_data); + } + + // If cache is empty or address doesn't match, use fallback values + // This should rarely happen since we cache in contract_source_code + trace!("Creation data not in cache for address {address}, using fallback values"); + let creation_data = ContractCreationData { + contract_address: address, + contract_creator: Address::ZERO, + transaction_hash: TxHash::ZERO, + }; + if let Ok(mut cache) = self.cached_creation_data.lock() { + *cache = Some(creation_data); + } + + Ok(creation_data) + } +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::hex; use foundry_compilers::CompilerContract; use foundry_test_utils::rpc::next_etherscan_api_key; - use std::collections::BTreeMap; #[expect(clippy::disallowed_macros)] fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { @@ -646,8 +1021,8 @@ mod tests { contract_name: &str, stripped_creation_code: &str, ) { - compiled.compiled_contracts_by_compiler_version().iter().for_each(|(_, contracts)| { - contracts.iter().for_each(|(name, contract)| { + for contracts in compiled.compiled_contracts_by_compiler_version().values() { + for (name, contract) in contracts { if name == contract_name { let compiled_creation_code = contract.bin_ref().expect("creation code not found"); @@ -657,11 +1032,11 @@ mod tests { "inconsistent creation code" ); } - }); - }); + } + } } - fn mock_etherscan(address: Address) -> impl super::EtherscanClient { + fn mock_etherscan(address: Address) -> impl super::ExplorerClient { // load mock data let mut mocked_data = BTreeMap::new(); let data_folder = @@ -688,7 +1063,7 @@ mod tests { let (metadata, creation_data) = mocked_data.get(&address).unwrap(); let metadata = metadata.clone(); let creation_data = *creation_data; - let mut mocked_client = super::MockEtherscanClient::new(); + let mut mocked_client = super::MockExplorerClient::new(); mocked_client .expect_contract_source_code() .times(1) @@ -726,10 +1101,13 @@ mod tests { /// Run the clone command with the specified contract address and assert the compilation. async fn one_test_case(address: Address, check_compilation_result: bool) { - let mut project_root = tempfile::tempdir().unwrap().path().to_path_buf(); + let temp_dir = tempfile::tempdir().unwrap(); + let mut project_root = temp_dir.path().to_path_buf(); let client = mock_etherscan(address); let meta = CloneArgs::collect_metadata_from_client(address, &client).await.unwrap(); - CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()).unwrap(); + CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()) + .await + .unwrap(); project_root = dunce::canonicalize(&project_root).unwrap(); CloneArgs::parse_metadata(&meta, Chain::mainnet(), &project_root, false, false) .await @@ -749,7 +1127,6 @@ mod tests { pick_creation_info(&address.to_string()).expect("creation code not found"); assert_compilation_result(rv, contract_name, stripped_creation_code); } - std::fs::remove_dir_all(project_root).unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -777,7 +1154,7 @@ mod tests { } #[tokio::test(flavor = "multi_thread")] - async fn test_clone_contract_with_relative_import() { + async fn flaky_test_clone_contract_with_relative_import() { let address = "0x3a23F943181408EAC424116Af7b7790c94Cb97a5".parse().unwrap(); one_test_case(address, false).await } @@ -812,9 +1189,25 @@ mod tests { // remember to remove CBOR metadata from the creation code const CREATION_ARRAY: [(&str, &str, &str); 4] = [ - ("0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193", "BearXNFTStaking", "608060405234801561001057600080fd5b50613000806100206000396000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80638129fc1c11610130578063bca35a71116100b8578063dada55011161007c578063dada550114610458578063f2fde38b1461046b578063f83d08ba1461047e578063fbb0022714610486578063fccd7f721461048e57600080fd5b8063bca35a71146103fa578063bf9befb11461040d578063c89d5b8b14610416578063d5d423001461041e578063d976e09f1461042657600080fd5b8063b1c92f95116100ff578063b1c92f95146103c5578063b549445c146103ce578063b81f8e89146103d6578063b9ade5b7146103de578063ba0848db146103e757600080fd5b80638129fc1c146103905780638da5cb5b14610398578063aaed083b146103a9578063b10dcc93146103b257600080fd5b8063367c164e116101b35780635923489b116101825780635923489b146103245780636e2751211461034f578063706ce3e114610362578063715018a614610375578063760a2e8a1461037d57600080fd5b8063367c164e146102bd57806338ff8a85146102d05780633a17f4f0146102f1578063426233601461030457600080fd5b8063206635e7116101fa578063206635e71461026d5780632afe761a146102805780632bd30f1114610289578063305f839a146102ab57806333ddacd1146102b457600080fd5b8062944f621461022b5780630d00368b146102405780630e8feed41461025c578063120957fd14610264575b600080fd5b61023e610239366004612aa4565b6104bc565b005b61024960735481565b6040519081526020015b60405180910390f35b61023e61053a565b610249606d5481565b61023e61027b366004612b2c565b61057e565b610249606f5481565b60785461029b90610100900460ff1681565b6040519015158152602001610253565b61024960715481565b61024960765481565b61023e6102cb366004612bc2565b6105d1565b6102e36102de366004612aa4565b610829565b604051610253929190612c16565b61023e6102ff366004612aa4565b6109e1565b610317610312366004612aa4565b610a56565b6040516102539190612c2f565b606a54610337906001600160a01b031681565b6040516001600160a01b039091168152602001610253565b6102e361035d366004612aa4565b610b4c565b606b54610337906001600160a01b031681565b61023e610cf8565b61029b61038b366004612aa4565b610d2e565b61023e610dc2565b6033546001600160a01b0316610337565b61024960705481565b61023e6103c0366004612b2c565b610fc0565b610249606e5481565b61023e611236565b61023e6112bb565b61024960725481565b6102e36103f5366004612aa4565b6112ef565b61023e610408366004612aa4565b61149b565b610249606c5481565b610249611510565b606f54610249565b610439610434366004612aa4565b611594565b6040805192151583526001600160a01b03909116602083015201610253565b606954610337906001600160a01b031681565b61023e610479366004612aa4565b6115cf565b61023e611667565b61023e6116a0565b6104a161049c366004612aa4565b6116e1565b60408051938452602084019290925290820152606001610253565b6033546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690612c42565b60405180910390fd5b606b546001600160a01b03163b6105185760405162461bcd60e51b81526004016104e690612c77565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061054533611594565b509050806105655760405162461bcd60e51b81526004016104e690612cae565b61056e33610d2e565b61057b5761057b33611c73565b50565b610587336116e1565b505060765560005b81518110156105cd576105bb8282815181106105ad576105ad612cda565b602002602001015133611d7b565b806105c581612d06565b91505061058f565b5050565b606a54604051636eb1769f60e11b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b602482015282916001600160a01b03169063dd62ed3e90604401602060405180830381865afa158015610633573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106579190612d21565b10156106bb5760405162461bcd60e51b815260206004820152602d60248201527f596f75206861766520746f20617070726f766520726f6f747820746f2073746160448201526c1ada5b99c818dbdb9d1c9858dd609a1b60648201526084016104e6565b606a546040516323b872dd60e01b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b6024820152604481018390526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190612d3a565b50606a546040516326c7e79d60e21b8152600481018390526001600160a01b0390911690639b1f9e7490602401600060405180830381600087803b15801561079157600080fd5b505af11580156107a5573d6000803e3d6000fd5b5050606b546001600160a01b031691506379c650689050336107c88460056120a1565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561080e57600080fd5b505af1158015610822573d6000803e3d6000fd5b5050505050565b600060606000805b6001600160a01b0385166000908152607460205260409020548110156108bc576001600160a01b0385166000908152607460205260409020805461089791908390811061088057610880612cda565b906000526020600020906005020160000154612129565b156108aa576108a7600183612d5c565b91505b806108b481612d06565b915050610831565b5060008167ffffffffffffffff8111156108d8576108d8612ac1565b604051908082528060200260200182016040528015610901578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461095791908390811061088057610880612cda565b156109c3576001600160a01b038716600090815260746020526040902080548290811061098657610986612cda565b9060005260206000209060050201600001548383815181106109aa576109aa612cda565b60209081029190910101526109c0826001612154565b91505b806109cd81612d06565b915050610908565b50919590945092505050565b6033546001600160a01b03163314610a0b5760405162461bcd60e51b81526004016104e690612c42565b6069546001600160a01b03163b610a345760405162461bcd60e51b81526004016104e690612c77565b606980546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0381166000908152607460205260408120546060919067ffffffffffffffff811115610a8b57610a8b612ac1565b604051908082528060200260200182016040528015610ab4578160200160208202803683370190505b50905060005b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b0384166000908152607460205260409020805482908110610b0457610b04612cda565b906000526020600020906005020160000154828281518110610b2857610b28612cda565b602090810291909101015280610b3d81612d06565b915050610aba565b5092915050565b600060606000805b6001600160a01b038516600090815260746020526040902054811015610bdf576001600160a01b03851660009081526074602052604090208054610bba919083908110610ba357610ba3612cda565b9060005260206000209060050201600001546121b3565b15610bcd57610bca826001612154565b91505b80610bd781612d06565b915050610b54565b5060008167ffffffffffffffff811115610bfb57610bfb612ac1565b604051908082528060200260200182016040528015610c24578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b03871660009081526074602052604090208054610c7a919083908110610ba357610ba3612cda565b15610ce6576001600160a01b0387166000908152607460205260409020805482908110610ca957610ca9612cda565b906000526020600020906005020160000154838381518110610ccd57610ccd612cda565b6020908102919091010152610ce3826001612154565b91505b80610cf081612d06565b915050610c2b565b6033546001600160a01b03163314610d225760405162461bcd60e51b81526004016104e690612c42565b610d2c60006121d0565b565b60006001815b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b03841660009081526074602052604081208054610d9a919084908110610d8357610d83612cda565b906000526020600020906005020160010154612222565b9050603c8111610daa5750610db0565b60009250505b80610dba81612d06565b915050610d34565b600054610100900460ff16610ddd5760005460ff1615610de1565b303b155b610e445760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104e6565b600054610100900460ff16158015610e66576000805461ffff19166101011790555b610e6e61223c565b606580546001600160a01b0319908116737a250d5630b4cf539739df2c5dacb4c659f2488d1790915560668054821673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2179055620151806067556312cc030060685560698054821673e22e1e620dffb03065cd77db0162249c0c91bf01179055606a8054821673d718ad25285d65ef4d79262a6cd3aea6a8e01023179055606b80549091167399cfdf48d0ba4885a73786148a2f89d86c7021701790556000606c5568056bc75e2d63100000606d556802b5e3af16b1880000606e55690257058e269742680000606f819055681b1ae4d6e2ef5000006070819055610bb8607181905591610f709190612d74565b610f7a9190612d8b565b607255607154606e54606d54610f909190612d74565b610f9a9190612d8b565b60735560006076556078805460ff19169055801561057b576000805461ff001916905550565b6000610fcb33611594565b50905080610feb5760405162461bcd60e51b81526004016104e690612cae565b607854610100900460ff161561102c5760405162461bcd60e51b8152602060048201526006602482015265131bd8dad95960d21b60448201526064016104e6565b600061103733610a56565b90508051835111156110775760405162461bcd60e51b81526020600482015260096024820152684964206572726f727360b81b60448201526064016104e6565b6000805b84518110156110fd5760005b83518110156110ea578381815181106110a2576110a2612cda565b60200260200101518683815181106110bc576110bc612cda565b602002602001015114156110d8576110d5836001612154565b92505b806110e281612d06565b915050611087565b50806110f581612d06565b91505061107b565b50835181141561123057835161112761111e82678ac7230489e800006120a1565b606f5490612154565b606f55611132612273565b600060768190555b855181101561122d5760695486516001600160a01b03909116906323b872dd90309033908a908690811061117057611170612cda565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b5050606c80549250905060006111f383612dad565b919050555061121b3387838151811061120e5761120e612cda565b602002602001015161229b565b8061122581612d06565b91505061113a565b50505b50505050565b60785460ff166112ac5760005b60755481101561057b576001607760006075848154811061126657611266612cda565b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055806112a481612d06565b915050611243565b6078805460ff19166001179055565b60006112c633611594565b509050806112e65760405162461bcd60e51b81526004016104e690612cae565b61057b33612470565b600060606000805b6001600160a01b038516600090815260746020526040902054811015611382576001600160a01b0385166000908152607460205260409020805461135d91908390811061134657611346612cda565b906000526020600020906005020160000154612574565b156113705761136d600183612d5c565b91505b8061137a81612d06565b9150506112f7565b5060008167ffffffffffffffff81111561139e5761139e612ac1565b6040519080825280602002602001820160405280156113c7578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461141d91908390811061134657611346612cda565b15611489576001600160a01b038716600090815260746020526040902080548290811061144c5761144c612cda565b90600052602060002090600502016000015483838151811061147057611470612cda565b6020908102919091010152611486826001612154565b91505b8061149381612d06565b9150506113ce565b6033546001600160a01b031633146114c55760405162461bcd60e51b81526004016104e690612c42565b606a546001600160a01b03163b6114ee5760405162461bcd60e51b81526004016104e690612c77565b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000806064606f5460016115249190612dc4565b61152e9190612d8b565b611539906001612d5c565b606d546115469190612dc4565b90506000606d54826115589190612d74565b905060006001606d548361156c9190612d8b565b6115769190612d8b565b611581906001612dc4565b61158c906064612dc4565b949350505050565b6001600160a01b038116600090815260776020526040812054819060ff161515600114156115c457506001929050565b506000928392509050565b6033546001600160a01b031633146115f95760405162461bcd60e51b81526004016104e690612c42565b6001600160a01b03811661165e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016104e6565b61057b816121d0565b73d0d725208fd36be1561050fc1dd6a651d7ea7c89331415610d2c576078805461ff001981166101009182900460ff1615909102179055565b60006116ab33611594565b509050806116cb5760405162461bcd60e51b81526004016104e690612cae565b6116d433610d2e565b61057b5761057b336125aa565b600080808080808087816116f482610829565b509050600061170283610b4c565b50905060058110611a3b57600160005b6001600160a01b038516600090815260746020526040902054811015611a0e576001600160a01b0385166000908152607460205260408120805461177891908490811061176157611761612cda565b906000526020600020906005020160040154612222565b6001600160a01b038716600090815260746020526040902080549192506117a99184908110610ba357610ba3612cda565b806117de57506001600160a01b038616600090815260746020526040902080546117de91908490811061088057610880612cda565b80156117ea5750600181105b156117f457600092505b82801561181857506001600160a01b03861660009081526074602052604090205415155b156118715761186a8561182c83600a612dc4565b6118369190612dc4565b6118648661184585600a612dc4565b61184f9190612dc4565b60765461186490670de0b6b3a7640000612798565b90612154565b995061188e565b8261188e5760765461188b90670de0b6b3a7640000612798565b99505b6001600160a01b038616600090815260746020526040902080546118bd91908490811061088057610880612cda565b15611950576001600160a01b038616600090815260746020526040812080546119089190859081106118f1576118f1612cda565b906000526020600020906005020160020154612222565b90508061192861192182680ad78ebc5ac6200000612dc4565b8c90612154565b9a5061194761194082680ad78ebc5ac6200000612dc4565b8b90612154565b995050506119fb565b6001600160a01b0386166000908152607460205260409020805461197f919084908110610ba357610ba3612cda565b156119fb576001600160a01b038616600090815260746020526040812080546119b39190859081106118f1576118f1612cda565b905060008190506119d76002606d54846119cd9190612dc4565b6119219190612d8b565b9a506119f66002606d54836119ec9190612dc4565b6119409190612d8b565b995050505b5080611a0681612d06565b915050611712565b508515611a3557606b54606654611a32916001600160a01b039081169116886127da565b94505b50611c4f565b60005b6001600160a01b038416600090815260746020526040902054811015611c28576001600160a01b03841660009081526074602052604090208054611a8d91908390811061088057610880612cda565b15611b9f576001600160a01b03841660009081526074602052604081208054611ac191908490811061176157611761612cda565b9050611ad8611ad182600a612dc4565b8a90612154565b98506000611b1560746000886001600160a01b03166001600160a01b0316815260200190815260200160002084815481106118f1576118f1612cda565b9050611b2d611ad182680ad78ebc5ac6200000612dc4565b98506000611b8160746000896001600160a01b03166001600160a01b031681526020019081526020016000208581548110611b6a57611b6a612cda565b906000526020600020906005020160030154612222565b9050611b99611ad182680ad78ebc5ac6200000612dc4565b98505050505b6001600160a01b03841660009081526074602052604090208054611bce919083908110610ba357610ba3612cda565b15611c16576001600160a01b03841660009081526074602052604081208054611c0291908490811061176157611761612cda565b9050611c12611ad182600a612dc4565b9850505b80611c2081612d06565b915050611a3e565b508415611c4f57606b54606654611c4c916001600160a01b039081169116876127da565b93505b611c6187670de0b6b3a7640000612dc4565b9b959a50929850939650505050505050565b6000611c7e826116e1565b5091505080156105cd57606b5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af1158015611cdb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cff9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b0383166000908152607460205260409020805442919083908110611d5057611d50612cda565b600091825260209091206002600590920201015580611d6e81612d06565b915050611d03565b505050565b607054606f5410611db857607354606d6000828254611d9a9190612d74565b9091555050606f54611db490678ac7230489e80000612905565b606f555b6069546040516331a9108f60e11b8152600481018490526001600160a01b03838116921690636352211e90602401602060405180830381865afa158015611e03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e279190612de3565b6001600160a01b031614611e7d5760405162461bcd60e51b815260206004820152601e60248201527f596f7520617265206e6f742061206f776e6572206f6620746865206e6674000060448201526064016104e6565b60695460405163e985e9c560e01b81523360048201523060248201526001600160a01b039091169063e985e9c590604401602060405180830381865afa158015611ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eef9190612d3a565b1515600114611f575760405162461bcd60e51b815260206004820152602e60248201527f596f752073686f756c6420617070726f7665206e667420746f2074686520737460448201526d185ada5b99c818dbdb9d1c9858dd60921b60648201526084016104e6565b6069546040516323b872dd60e01b81526001600160a01b03838116600483015230602483015260448201859052909116906323b872dd90606401600060405180830381600087803b158015611fab57600080fd5b505af1158015611fbf573d6000803e3d6000fd5b505050506000611fce82611594565b50905060006040518060a001604052808581526020014281526020014281526020014281526020014281525090506120126001606c5461215490919063ffffffff16565b606c556001600160a01b03831660009081526074602090815260408083208054600181810183559185529383902085516005909502019384559184015191830191909155820151600282015560608201516003820155608082015160049091015581611230576001600160a01b0383166000908152607760205260409020805460ff1916600117905550505050565b6000826120b057506000612123565b60006120bc8385612dc4565b9050826120c98583612d8b565b146121205760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b90505b92915050565b60008064e8d4a510008310158015612146575064e8d4a510058311155b156121235750600192915050565b6000806121618385612d5c565b9050838110156121205760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b600080610e7483116121c757506001612123565b50600092915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6067546000906122328342612d74565b6121239190612d8b565b600054610100900460ff166122635760405162461bcd60e51b81526004016104e690612e00565b61226b612947565b610d2c61296e565b61227c33612470565b61228533610d2e565b610d2c5761229233611c73565b610d2c336125aa565b60005b6001600160a01b038316600090815260746020526040902054811015612428576001600160a01b03831660009081526074602052604090208054839190839081106122eb576122eb612cda565b9060005260206000209060050201600001541415612416576001600160a01b0383166000908152607460205260409020805461232990600190612d74565b8154811061233957612339612cda565b906000526020600020906005020160746000856001600160a01b03166001600160a01b03168152602001908152602001600020828154811061237d5761237d612cda565b60009182526020808320845460059093020191825560018085015490830155600280850154908301556003808501549083015560049384015493909101929092556001600160a01b03851681526074909152604090208054806123e2576123e2612e4b565b6000828152602081206005600019909301928302018181556001810182905560028101829055600381018290556004015590555b8061242081612d06565b91505061229e565b506001600160a01b0382166000908152607460205260409020546105cd576001600160a01b038216600090815260746020526040812061246791612a3f565b6105cd8261299e565b600061247b826116e1565b509091505080156105cd57606a5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af11580156124d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fd9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b038316600090815260746020526040902080544291908390811061254e5761254e612cda565b60009182526020909120600460059092020101558061256c81612d06565b915050612501565b60006509184e72a00682101561258c57506000919050565b6509184e72b4b38211156125a257506000919050565b506001919050565b60006125b5826116e1565b5091505080156105cd57606b5460655460405163095ea7b360e01b81526001600160a01b0391821660048201526024810184905291169063095ea7b3906044016020604051808303816000875af1158015612614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126389190612d3a565b5060408051600280825260608083018452926020830190803683375050606b5482519293506001600160a01b03169183915060009061267957612679612cda565b6001600160a01b0392831660209182029290920101526066548251911690829060019081106126aa576126aa612cda565b6001600160a01b03928316602091820292909201015260655460405163791ac94760e01b815291169063791ac947906126f0908590600090869089904290600401612e9a565b600060405180830381600087803b15801561270a57600080fd5b505af115801561271e573d6000803e3d6000fd5b5050505060005b6001600160a01b038416600090815260746020526040902054811015611230576001600160a01b038416600090815260746020526040902080544291908390811061277257612772612cda565b60009182526020909120600360059092020101558061279081612d06565b915050612725565b600061212083836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506129d7565b6040805160028082526060808301845260009390929190602083019080368337019050509050848160008151811061281457612814612cda565b60200260200101906001600160a01b031690816001600160a01b031681525050838160018151811061284857612848612cda565b6001600160a01b03928316602091820292909201015260655460405163d06ca61f60e01b8152600092919091169063d06ca61f9061288c9087908690600401612ed6565b600060405180830381865afa1580156128a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128d19190810190612eef565b905080600183516128e29190612d74565b815181106128f2576128f2612cda565b6020026020010151925050509392505050565b600061212083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612a0e565b600054610100900460ff16610d2c5760405162461bcd60e51b81526004016104e690612e00565b600054610100900460ff166129955760405162461bcd60e51b81526004016104e690612e00565b610d2c336121d0565b6000806129aa83611594565b915091508115611d76576001600160a01b03166000908152607760205260409020805460ff191690555050565b600081836129f85760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d8b565b95945050505050565b60008184841115612a325760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d74565b508054600082556005029060005260206000209081019061057b91905b80821115612a8b5760008082556001820181905560028201819055600382018190556004820155600501612a5c565b5090565b6001600160a01b038116811461057b57600080fd5b600060208284031215612ab657600080fd5b813561212081612a8f565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612b0057612b00612ac1565b604052919050565b600067ffffffffffffffff821115612b2257612b22612ac1565b5060051b60200190565b60006020808385031215612b3f57600080fd5b823567ffffffffffffffff811115612b5657600080fd5b8301601f81018513612b6757600080fd5b8035612b7a612b7582612b08565b612ad7565b81815260059190911b82018301908381019087831115612b9957600080fd5b928401925b82841015612bb757833582529284019290840190612b9e565b979650505050505050565b600060208284031215612bd457600080fd5b5035919050565b600081518084526020808501945080840160005b83811015612c0b57815187529582019590820190600101612bef565b509495945050505050565b82815260406020820152600061158c6040830184612bdb565b6020815260006121206020830184612bdb565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526017908201527f41646472657373206973206e6f7420636f6e7472616374000000000000000000604082015260600190565b6020808252601290820152712cb7ba9030b932903737ba1039ba30b5b2b960711b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d1a57612d1a612cf0565b5060010190565b600060208284031215612d3357600080fd5b5051919050565b600060208284031215612d4c57600080fd5b8151801515811461212057600080fd5b60008219821115612d6f57612d6f612cf0565b500190565b600082821015612d8657612d86612cf0565b500390565b600082612da857634e487b7160e01b600052601260045260246000fd5b500490565b600081612dbc57612dbc612cf0565b506000190190565b6000816000190483118215151615612dde57612dde612cf0565b500290565b600060208284031215612df557600080fd5b815161212081612a8f565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b600081518084526020808501945080840160005b83811015612c0b5781516001600160a01b031687529582019590820190600101612e75565b85815284602082015260a060408201526000612eb960a0830186612e61565b6001600160a01b0394909416606083015250608001529392505050565b82815260406020820152600061158c6040830184612e61565b60006020808385031215612f0257600080fd5b825167ffffffffffffffff811115612f1957600080fd5b8301601f81018513612f2a57600080fd5b8051612f38612b7582612b08565b81815260059190911b82018301908381019087831115612f5757600080fd5b928401925b82841015612bb757835182529284019290840190612f5c565b600060208083528351808285015260005b81811015612fa257858101830151858201604001528201612f86565b81811115612fb4576000604083870101525b50601f01601f191692909201604001939250505056fe"), - ("0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545", "GovernorCharlieDelegate", "608060405234801561001057600080fd5b50613e45806100206000396000f3fe60806040526004361061031a5760003560e01c80637b3c71d3116101ab578063d50572ee116100f7578063f0843ba811610095578063fc176c041161006f578063fc176c0414610b82578063fc4eee4214610ba2578063fc66ff1414610bb8578063fe0d94c114610bd857600080fd5b8063f0843ba814610b12578063f2b0653714610b32578063f682e04c14610b6257600080fd5b8063de7bc127116100d1578063de7bc127146109ec578063deaaa7cc14610a02578063e23a9a5214610a36578063e837159c14610afc57600080fd5b8063d50572ee146109a0578063da35c664146109b6578063ddf0b009146109cc57600080fd5b8063a6d8784a11610164578063c1a287e21161013e578063c1a287e214610933578063c4d66de81461094a578063c5a8425d1461096a578063c9fb9e871461098a57600080fd5b8063a6d8784a146108e7578063abaac6a8146108fd578063b58131b01461091d57600080fd5b80637b3c71d31461083c5780637bdbe4d01461085c5780637cae57bb14610871578063806bd5811461088757806386d37e8b146108a757806399533365146108c757600080fd5b80632fedff591161026a5780633e4f49e61161022357806350442098116101fd578063504420981461074657806356781388146107665780635c60da1b1461078657806366176743146107be57600080fd5b80633e4f49e6146106d957806340e58ee5146107065780634d6733d21461072657600080fd5b80632fedff59146105ee578063328dd9821461060e57806338bd0dda1461063e5780633932abb11461066b5780633af32abf146106815780633bccf4fd146106b957600080fd5b8063158ef93e116102d757806318b62629116102b157806318b626291461056e5780631dfb1b5a1461058457806320606b70146105a457806324bc1a64146105d857600080fd5b8063158ef93e146104f757806317977c611461052157806317ba1b8b1461054e57600080fd5b8063013cf08b1461031f57806302a251a31461042857806306fdde031461044c5780630825f38f146104a25780630ea2d98c146104b7578063140499ea146104d7575b600080fd5b34801561032b57600080fd5b506103b361033a3660046132ee565b60096020819052600091825260409091208054600182015460028301546007840154600885015495850154600a860154600b870154600c880154600d890154600e9099015497996001600160a01b0390971698959794969593949293919260ff808316936101008404821693620100009004909116918d565b604080519d8e526001600160a01b03909c1660208e01529a8c019990995260608b019790975260808a019590955260a089019390935260c088019190915260e08701521515610100860152151561012085015215156101408401526101608301526101808201526101a0015b60405180910390f35b34801561043457600080fd5b5061043e60045481565b60405190815260200161041f565b34801561045857600080fd5b506104956040518060400160405280601a81526020017f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000081525081565b60405161041f9190613363565b6104b56104b0366004613450565b610beb565b005b3480156104c357600080fd5b506104b56104d23660046132ee565b610e61565b3480156104e357600080fd5b506104b56104f23660046134d6565b610ec6565b34801561050357600080fd5b506012546105119060ff1681565b604051901515815260200161041f565b34801561052d57600080fd5b5061043e61053c3660046134d6565b600a6020526000908152604090205481565b34801561055a57600080fd5b506104b56105693660046132ee565b610f07565b34801561057a57600080fd5b5061043e600f5481565b34801561059057600080fd5b506104b561059f3660046132ee565b610f64565b3480156105b057600080fd5b5061043e7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b3480156105e457600080fd5b5061043e60015481565b3480156105fa57600080fd5b506104b56106093660046132ee565b610fc1565b34801561061a57600080fd5b5061062e6106293660046132ee565b61101e565b60405161041f94939291906135ba565b34801561064a57600080fd5b5061043e6106593660046134d6565b600d6020526000908152604090205481565b34801561067757600080fd5b5061043e60035481565b34801561068d57600080fd5b5061051161069c3660046134d6565b6001600160a01b03166000908152600d6020526040902054421090565b3480156106c557600080fd5b506104b56106d4366004613623565b6112af565b3480156106e557600080fd5b506106f96106f43660046132ee565b611516565b60405161041f9190613687565b34801561071257600080fd5b506104b56107213660046132ee565b61169e565b34801561073257600080fd5b506104b56107413660046136af565b611b80565b34801561075257600080fd5b506104b56107613660046132ee565b611c45565b34801561077257600080fd5b506104b56107813660046136d9565b611ca2565b34801561079257600080fd5b506000546107a6906001600160a01b031681565b6040516001600160a01b03909116815260200161041f565b3480156107ca57600080fd5b506108146107d9366004613705565b601160209081526000928352604080842090915290825290205460ff808216916101008104909116906201000090046001600160601b031683565b60408051931515845260ff90921660208401526001600160601b03169082015260600161041f565b34801561084857600080fd5b506104b5610857366004613728565b611d09565b34801561086857600080fd5b5061043e600a81565b34801561087d57600080fd5b5061043e600c5481565b34801561089357600080fd5b506104b56108a23660046132ee565b611d58565b3480156108b357600080fd5b506104b56108c23660046132ee565b611db5565b3480156108d357600080fd5b506104b56108e23660046134d6565b611e12565b3480156108f357600080fd5b5061043e60155481565b34801561090957600080fd5b506104b56109183660046132ee565b611e8b565b34801561092957600080fd5b5061043e60055481565b34801561093f57600080fd5b5061043e6212750081565b34801561095657600080fd5b506104b56109653660046134d6565b611ee8565b34801561097657600080fd5b50600e546107a6906001600160a01b031681565b34801561099657600080fd5b5061043e60135481565b3480156109ac57600080fd5b5061043e60025481565b3480156109c257600080fd5b5061043e60075481565b3480156109d857600080fd5b506104b56109e73660046132ee565b611fd9565b3480156109f857600080fd5b5061043e60105481565b348015610a0e57600080fd5b5061043e7f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f81565b348015610a4257600080fd5b50610acc610a51366004613705565b60408051606081018252600080825260208201819052918101919091525060009182526011602090815260408084206001600160a01b03939093168452918152918190208151606081018352905460ff8082161515835261010082041693820193909352620100009092046001600160601b03169082015290565b6040805182511515815260208084015160ff1690820152918101516001600160601b03169082015260600161041f565b348015610b0857600080fd5b5061043e60145481565b348015610b1e57600080fd5b506104b5610b2d3660046132ee565b61238f565b348015610b3e57600080fd5b50610511610b4d3660046132ee565b600b6020526000908152604090205460ff1681565b348015610b6e57600080fd5b5061043e610b7d3660046139b0565b6123ec565b348015610b8e57600080fd5b506104b5610b9d3660046132ee565b612a48565b348015610bae57600080fd5b5061043e60065481565b348015610bc457600080fd5b506008546107a6906001600160a01b031681565b6104b5610be63660046132ee565b612aa5565b60008585858585604051602001610c06959493929190613a91565b60408051601f1981840301815291815281516020928301206000818152600b90935291205490915060ff16610c7b5760405162461bcd60e51b81526020600482015260166024820152753a3c103430b9b713ba103132b2b71038bab2bab2b21760511b60448201526064015b60405180910390fd5b81421015610ccb5760405162461bcd60e51b815260206004820152601d60248201527f7478206861736e2774207375727061737365642074696d656c6f636b2e0000006044820152606401610c72565b610cd86212750083613af3565b421115610d165760405162461bcd60e51b815260206004820152600c60248201526b3a3c1034b99039ba30b6329760a11b6044820152606401610c72565b6000818152600b60205260409020805460ff191690558351606090610d3c575082610d68565b848051906020012084604051602001610d56929190613b0b565b60405160208183030381529060405290505b6000876001600160a01b03168783604051610d839190613b3c565b60006040518083038185875af1925050503d8060008114610dc0576040519150601f19603f3d011682016040523d82523d6000602084013e610dc5565b606091505b5050905080610e0f5760405162461bcd60e51b81526020600482015260166024820152753a3c1032bc32b1baba34b7b7103932bb32b93a32b21760511b6044820152606401610c72565b876001600160a01b0316837fa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e789898989604051610e4f9493929190613b58565b60405180910390a35050505050505050565b333014610e805760405162461bcd60e51b8152600401610c7290613b95565b600480549082905560408051828152602081018490527f7e3f7f0708a84de9203036abaa450dccc85ad5ff52f78c170f3edb55cf5e882891015b60405180910390a15050565b333014610ee55760405162461bcd60e51b8152600401610c7290613b95565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610f265760405162461bcd60e51b8152600401610c7290613b95565b600580549082905560408051828152602081018490527fccb45da8d5717e6c4544694297c4ba5cf151d455c9bb0ed4fc7a38411bc054619101610eba565b333014610f835760405162461bcd60e51b8152600401610c7290613b95565b600380549082905560408051828152602081018490527fc565b045403dc03c2eea82b81a0465edad9e2e7fc4d97e11421c209da93d7a939101610eba565b333014610fe05760405162461bcd60e51b8152600401610c7290613b95565b601480549082905560408051828152602081018490527f519a192fe8db9e38785eb494c69f530ddb21b9e34322f8d08fe29bd3849749889101610eba565b606080606080600060096000878152602001908152602001600020905080600301816004018260050183600601838054806020026020016040519081016040528092919081815260200182805480156110a057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611082575b50505050509350828054806020026020016040519081016040528092919081815260200182805480156110f257602002820191906000526020600020905b8154815260200190600101908083116110de575b5050505050925081805480602002602001604051908101604052809291908181526020016000905b828210156111c657838290600052602060002001805461113990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461116590613bcc565b80156111b25780601f10611187576101008083540402835291602001916111b2565b820191906000526020600020905b81548152906001019060200180831161119557829003601f168201915b50505050508152602001906001019061111a565b50505050915080805480602002602001604051908101604052809291908181526020016000905b8282101561129957838290600052602060002001805461120c90613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461123890613bcc565b80156112855780601f1061125a57610100808354040283529160200191611285565b820191906000526020600020905b81548152906001019060200180831161126857829003601f168201915b5050505050815260200190600101906111ed565b5050505090509450945094509450509193509193565b604080518082018252601a81527f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000060209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f75a838dcd8ee5903cc7f4a5799344d0080864f57a6e9911f8bdfb4c8ddce9b5481840152466060820152306080808301919091528351808303909101815260a0820184528051908301207f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f60c083015260e0820189905260ff8816610100808401919091528451808403909101815261012083019094528351939092019290922061190160f01b6101408401526101428301829052610162830181905290916000906101820160408051601f198184030181528282528051602091820120600080855291840180845281905260ff8a169284019290925260608301889052608083018790529092509060019060a0016020604051602081039080840390855afa15801561143c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661149f5760405162461bcd60e51b815260206004820181905260248201527f63617374566f746542795369673a20696e76616c6964207369676e61747572656044820152606401610c72565b88816001600160a01b03167fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda48a6114d7858e8e612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a3505050505050505050565b6000816007541015801561152b575060065482115b6115775760405162461bcd60e51b815260206004820152601a60248201527f73746174653a20696e76616c69642070726f706f73616c2069640000000000006044820152606401610c72565b600082815260096020908152604080832060018101546001600160a01b03168452600d90925290912054600c82015442919091109060ff16156115be575060029392505050565b816007015443116115d3575060009392505050565b816008015443116115e8575060019392505050565b8080156115fc575081600d015482600a0154115b80611618575080158015611618575081600a0154826009015411155b80611633575080158015611633575081600d01548260090154105b15611642575060039392505050565b6002820154611655575060049392505050565b600c820154610100900460ff1615611671575060079392505050565b6212750082600201546116849190613af3565b4210611694575060069392505050565b5060059392505050565b60076116a982611516565b60078111156116ba576116ba613671565b14156117085760405162461bcd60e51b815260206004820152601d60248201527f63616e742063616e63656c2065786563757465642070726f706f73616c0000006044820152606401610c72565b600081815260096020526040902060018101546001600160a01b0316336001600160a01b0316146119755760018101546001600160a01b03166000908152600d6020526040902054421015611878576005546008546001838101546001600160a01b039283169263782d6fe1929116906117829043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613c1e565b6001600160601b03161080156118275750600e546001600160a01b0316336001600160a01b0316145b6118735760405162461bcd60e51b815260206004820152601c60248201527f63616e63656c3a2077686974656c69737465642070726f706f736572000000006044820152606401610c72565b611975565b6005546008546001838101546001600160a01b039283169263782d6fe1929116906118a39043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156118e757600080fd5b505afa1580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190613c1e565b6001600160601b0316106119755760405162461bcd60e51b815260206004820181905260248201527f63616e63656c3a2070726f706f7365722061626f7665207468726573686f6c646044820152606401610c72565b600c8101805460ff1916600117905560005b6003820154811015611b5057611b3e8260030182815481106119ab576119ab613c47565b6000918252602090912001546004840180546001600160a01b0390921691849081106119d9576119d9613c47565b90600052602060002001548460050184815481106119f9576119f9613c47565b906000526020600020018054611a0e90613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611a3a90613bcc565b8015611a875780601f10611a5c57610100808354040283529160200191611a87565b820191906000526020600020905b815481529060010190602001808311611a6a57829003601f168201915b5050505050856006018581548110611aa157611aa1613c47565b906000526020600020018054611ab690613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611ae290613bcc565b8015611b2f5780601f10611b0457610100808354040283529160200191611b2f565b820191906000526020600020905b815481529060010190602001808311611b1257829003601f168201915b50505050508660020154612f12565b80611b4881613c5d565b915050611987565b5060405182907f789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c90600090a25050565b333014611b9f5760405162461bcd60e51b8152600401610c7290613b95565b42601554611bad9190613af3565b8110611bf45760405162461bcd60e51b81526020600482015260166024820152750caf0e0d2e4c2e8d2dedc40caf0c6cacac8e640dac2f60531b6044820152606401610c72565b6001600160a01b0382166000818152600d6020908152604091829020849055815192835282018390527f4e7b7545bc5744d0e30425959f4687475774b6c7edad77d24cb51c7d967d45159101610eba565b333014611c645760405162461bcd60e51b8152600401610c7290613b95565b601080549082905560408051828152602081018490527f2a61b867418a359864adca8bb250ea65ee8bd41dbfd0279198d8e7552d4a27c29101610eba565b81337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda483611cd1838583612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a35050565b83337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda485611d38838583612c90565b8686604051611d4a9493929190613c78565b60405180910390a350505050565b333014611d775760405162461bcd60e51b8152600401610c7290613b95565b601380549082905560408051828152602081018490527f8cb5451eee8feb516cec9cd600201bbc31a30886d70c841a085a3fa69a4294d19101610eba565b333014611dd45760405162461bcd60e51b8152600401610c7290613b95565b600180549082905560408051828152602081018490527fa74554b0f53da47d07ec571d712428b3720460f54f81375fbcf78f6b5f72e7ed9101610eba565b333014611e315760405162461bcd60e51b8152600401610c7290613b95565b600e80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f80a07e73e552148844a9c216d9724212d609cfa54e9c1a2e97203bdd2c4ad3419101610eba565b333014611eaa5760405162461bcd60e51b8152600401610c7290613b95565b600f80549082905560408051828152602081018490527f80a384652af83fc00bfd40ef94edda7ede83e7db39931b2c889821573f314e239101610eba565b60125460ff1615611f3b5760405162461bcd60e51b815260206004820152601860248201527f616c7265616479206265656e20696e697469616c697a656400000000000000006044820152606401610c72565b600880546001600160a01b0319166001600160a01b0392909216919091179055619d8060045561335460035569d3c21bcecceda10000006005556202a300600c5560006007556a084595161401484a00000060019081556a21165458500521280000006002556119aa600f5561a8c06010556a01a784379d99db420000006013556146506014556301e133806015556012805460ff19169091179055565b6004611fe482611516565b6007811115611ff557611ff5613671565b146120425760405162461bcd60e51b815260206004820152601f60248201527f63616e206f6e6c792062652071756575656420696620737563636565646564006044820152606401610c72565b6000818152600960205260408120600e8101549091906120629042613af3565b905060005b600383015481101561234d57600b600084600301838154811061208c5761208c613c47565b6000918252602090912001546004860180546001600160a01b0390921691859081106120ba576120ba613c47565b90600052602060002001548660050185815481106120da576120da613c47565b906000526020600020018760060186815481106120f9576120f9613c47565b9060005260206000200187604051602001612118959493929190613d62565b60408051601f198184030181529181528151602092830120835290820192909252016000205460ff161561218e5760405162461bcd60e51b815260206004820152601760248201527f70726f706f73616c20616c7265616479207175657565640000000000000000006044820152606401610c72565b61233a8360030182815481106121a6576121a6613c47565b6000918252602090912001546004850180546001600160a01b0390921691849081106121d4576121d4613c47565b90600052602060002001548560050184815481106121f4576121f4613c47565b90600052602060002001805461220990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461223590613bcc565b80156122825780601f1061225757610100808354040283529160200191612282565b820191906000526020600020905b81548152906001019060200180831161226557829003601f168201915b505050505086600601858154811061229c5761229c613c47565b9060005260206000200180546122b190613bcc565b80601f01602080910402602001604051908101604052809291908181526020018280546122dd90613bcc565b801561232a5780601f106122ff5761010080835404028352916020019161232a565b820191906000526020600020905b81548152906001019060200180831161230d57829003601f168201915b50505050508688600e0154612fac565b508061234581613c5d565b915050612067565b506002820181905560405181815283907f9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda28929060200160405180910390a2505050565b3330146123ae5760405162461bcd60e51b8152600401610c7290613b95565b600280549082905560408051828152602081018490527fc2adf06da6765dba7faaccde4c0ce3f91c35dd3390e7f0b6bc2844202c9fa9529101610eba565b6000600154600014156124365760405162461bcd60e51b8152602060048201526012602482015271436861726c6965206e6f742061637469766560701b6044820152606401610c72565b6005546008546001600160a01b031663782d6fe133612456600143613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b15801561249a57600080fd5b505afa1580156124ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d29190613c1e565b6001600160601b03161015806124ec57506124ec3361069c565b6125385760405162461bcd60e51b815260206004820152601e60248201527f766f7465732062656c6f772070726f706f73616c207468726573686f6c6400006044820152606401610c72565b8551875114801561254a575084518751145b8015612557575083518751145b6125a35760405162461bcd60e51b815260206004820152601a60248201527f696e666f726d6174696f6e206172697479206d69736d617463680000000000006044820152606401610c72565b86516125e85760405162461bcd60e51b81526020600482015260146024820152736d7573742070726f7669646520616374696f6e7360601b6044820152606401610c72565b600a8751111561262d5760405162461bcd60e51b815260206004820152601060248201526f746f6f206d616e7920616374696f6e7360801b6044820152606401610c72565b336000908152600a6020526040902054801561271657600061264e82611516565b9050600181600781111561266457612664613671565b14156126b25760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b60008160078111156126c6576126c6613671565b14156127145760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b505b6007805490600061272683613c5d565b9190505550600060405180610220016040528060075481526020016127483390565b6001600160a01b03168152602001600081526020018a8152602001898152602001888152602001878152602001600354436127839190613af3565b8152602001600454600354436127999190613af3565b6127a39190613af3565b815260200160008152602001600081526020016000815260200160001515815260200160001515815260200185151581526020016001548152602001600c5481525090508380156127fa57506127f83361069c565b155b1561282c574360e08201819052600f5461281391613af3565b6101008201526002546101e08201526010546102008201525b6128353361069c565b15612876576013546101e08201526014546128509043613af3565b60e08201526004546014546128659043613af3565b61286f9190613af3565b6101008201525b805160009081526009602090815260409182902083518155818401516001820180546001600160a01b0319166001600160a01b03909216919091179055918301516002830155606083015180518493926128d792600385019291019061309d565b50608082015180516128f3916004840191602090910190613102565b5060a0820151805161290f91600584019160209091019061313d565b5060c0820151805161292b916006840191602090910190613196565b5060e08281015160078301556101008084015160088401556101208401516009840155610140840151600a80850191909155610160850151600b850155610180850151600c850180546101a08801516101c089015161ffff1990921693151561ff0019169390931792151585029290921762ff0000191662010000921515929092029190911790556101e0850151600d85015561020090940151600e9093019290925583516020808601516001600160a01b0316600090815294905260409384902055830151835191840151925190923392917f7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e091612a33918f918f918f918f918f90613d9b565b60405180910390a45198975050505050505050565b333014612a675760405162461bcd60e51b8152600401610c7290613b95565b600c80549082905560408051828152602081018490527fed0229422af39d4d7d33f7a27d31d6f5cb20ec628293da58dd6e8a528ed466be9101610eba565b6005612ab082611516565b6007811115612ac157612ac1613671565b14612b0e5760405162461bcd60e51b815260206004820152601c60248201527f63616e206f6e6c792062652065786563276420696620717565756564000000006044820152606401610c72565b6000818152600960205260408120600c8101805461ff001916610100179055905b6003820154811015612c6057306001600160a01b0316630825f38f836004018381548110612b5f57612b5f613c47565b9060005260206000200154846003018481548110612b7f57612b7f613c47565b6000918252602090912001546004860180546001600160a01b039092169186908110612bad57612bad613c47565b9060005260206000200154866005018681548110612bcd57612bcd613c47565b90600052602060002001876006018781548110612bec57612bec613c47565b9060005260206000200188600201546040518763ffffffff1660e01b8152600401612c1b959493929190613d62565b6000604051808303818588803b158015612c3457600080fd5b505af1158015612c48573d6000803e3d6000fd5b50505050508080612c5890613c5d565b915050612b2f565b5060405182907f712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f90600090a25050565b60006001612c9d84611516565b6007811115612cae57612cae613671565b14612cee5760405162461bcd60e51b815260206004820152601060248201526f1d9bdd1a5b99c81a5cc818db1bdcd95960821b6044820152606401610c72565b60028260ff161115612d365760405162461bcd60e51b8152602060048201526011602482015270696e76616c696420766f7465207479706560781b6044820152606401610c72565b6000838152600960209081526040808320601183528184206001600160a01b0389168552909252909120805460ff1615612da85760405162461bcd60e51b81526020600482015260136024820152721d9bdd195c88185b1c9958591e481d9bdd1959606a1b6044820152606401610c72565b600854600783015460405163782d6fe160e01b81526000926001600160a01b03169163782d6fe191612df2918b916004016001600160a01b03929092168252602082015260400190565b60206040518083038186803b158015612e0a57600080fd5b505afa158015612e1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e429190613c1e565b905060ff8516612e6f57806001600160601b031683600a0154612e659190613af3565b600a840155612ec9565b8460ff1660011415612e9e57806001600160601b03168360090154612e949190613af3565b6009840155612ec9565b8460ff1660021415612ec957806001600160601b031683600b0154612ec39190613af3565b600b8401555b81546001600160601b03821662010000026dffffffffffffffffffffffff00001960ff88166101000261ffff199093169290921760011791909116179091559150509392505050565b60008585858585604051602001612f2d959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916905591506001600160a01b0387169082907f2fffc091a501fd91bfbff27141450d3acb40fb8e6d8382b243ec7a812a3aaf8790612f9c908990899089908990613b58565b60405180910390a3505050505050565b6000612fb88242613af3565b831015612ffd5760405162461bcd60e51b815260206004820152601360248201527236bab9ba1039b0ba34b9b33c903232b630bc9760691b6044820152606401610c72565b60008787878787604051602001613018959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916600117905591506001600160a01b0389169082907f76e2796dc3a81d57b0e8504b647febcbeeb5f4af818e164f11eef8131a6a763f9061308a908b908b908b908b90613b58565b60405180910390a3979650505050505050565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906130bd565b506130fe9291506131ef565b5090565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f2578251825591602001919060010190613122565b82805482825590600052602060002090810192821561318a579160200282015b8281111561318a578251805161317a918491602090910190613204565b509160200191906001019061315d565b506130fe929150613277565b8280548282559060005260206000209081019282156131e3579160200282015b828111156131e357825180516131d3918491602090910190613204565b50916020019190600101906131b6565b506130fe929150613294565b5b808211156130fe57600081556001016131f0565b82805461321090613bcc565b90600052602060002090601f01602090048101928261323257600085556130f2565b82601f1061324b57805160ff19168380011785556130f2565b828001600101855582156130f257918201828111156130f2578251825591602001919060010190613122565b808211156130fe57600061328b82826132b1565b50600101613277565b808211156130fe5760006132a882826132b1565b50600101613294565b5080546132bd90613bcc565b6000825580601f106132cd575050565b601f0160209004906000526020600020908101906132eb91906131ef565b50565b60006020828403121561330057600080fd5b5035919050565b60005b8381101561332257818101518382015260200161330a565b83811115613331576000848401525b50505050565b6000815180845261334f816020860160208601613307565b601f01601f19169290920160200192915050565b6020815260006133766020830184613337565b9392505050565b80356001600160a01b038116811461339457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156133d8576133d8613399565b604052919050565b600082601f8301126133f157600080fd5b813567ffffffffffffffff81111561340b5761340b613399565b61341e601f8201601f19166020016133af565b81815284602083860101111561343357600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561346857600080fd5b6134718661337d565b945060208601359350604086013567ffffffffffffffff8082111561349557600080fd5b6134a189838a016133e0565b945060608801359150808211156134b757600080fd5b506134c4888289016133e0565b95989497509295608001359392505050565b6000602082840312156134e857600080fd5b6133768261337d565b600081518084526020808501945080840160005b8381101561352a5781516001600160a01b031687529582019590820190600101613505565b509495945050505050565b600081518084526020808501945080840160005b8381101561352a57815187529582019590820190600101613549565b600081518084526020808501808196508360051b8101915082860160005b858110156135ad57828403895261359b848351613337565b98850198935090840190600101613583565b5091979650505050505050565b6080815260006135cd60808301876134f1565b82810360208401526135df8187613535565b905082810360408401526135f38186613565565b905082810360608401526136078185613565565b979650505050505050565b803560ff8116811461339457600080fd5b600080600080600060a0868803121561363b57600080fd5b8535945061364b60208701613612565b935061365960408701613612565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052602160045260246000fd5b60208101600883106136a957634e487b7160e01b600052602160045260246000fd5b91905290565b600080604083850312156136c257600080fd5b6136cb8361337d565b946020939093013593505050565b600080604083850312156136ec57600080fd5b823591506136fc60208401613612565b90509250929050565b6000806040838503121561371857600080fd5b823591506136fc6020840161337d565b6000806000806060858703121561373e57600080fd5b8435935061374e60208601613612565b9250604085013567ffffffffffffffff8082111561376b57600080fd5b818701915087601f83011261377f57600080fd5b81358181111561378e57600080fd5b8860208285010111156137a057600080fd5b95989497505060200194505050565b600067ffffffffffffffff8211156137c9576137c9613399565b5060051b60200190565b600082601f8301126137e457600080fd5b813560206137f96137f4836137af565b6133af565b82815260059290921b8401810191818101908684111561381857600080fd5b8286015b8481101561383a5761382d8161337d565b835291830191830161381c565b509695505050505050565b600082601f83011261385657600080fd5b813560206138666137f4836137af565b82815260059290921b8401810191818101908684111561388557600080fd5b8286015b8481101561383a5780358352918301918301613889565b600082601f8301126138b157600080fd5b813560206138c16137f4836137af565b82815260059290921b840181019181810190868411156138e057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139045760008081fd5b6139128986838b01016133e0565b8452509183019183016138e4565b600082601f83011261393157600080fd5b813560206139416137f4836137af565b82815260059290921b8401810191818101908684111561396057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139845760008081fd5b6139928986838b01016133e0565b845250918301918301613964565b8035801515811461339457600080fd5b60008060008060008060c087890312156139c957600080fd5b863567ffffffffffffffff808211156139e157600080fd5b6139ed8a838b016137d3565b97506020890135915080821115613a0357600080fd5b613a0f8a838b01613845565b96506040890135915080821115613a2557600080fd5b613a318a838b016138a0565b95506060890135915080821115613a4757600080fd5b613a538a838b01613920565b94506080890135915080821115613a6957600080fd5b50613a7689828a016133e0565b925050613a8560a088016139a0565b90509295509295509295565b60018060a01b038616815284602082015260a060408201526000613ab860a0830186613337565b8281036060840152613aca8186613337565b9150508260808301529695505050505050565b634e487b7160e01b600052601160045260246000fd5b60008219821115613b0657613b06613add565b500190565b6001600160e01b0319831681528151600090613b2e816004850160208701613307565b919091016004019392505050565b60008251613b4e818460208701613307565b9190910192915050565b848152608060208201526000613b716080830186613337565b8281036040840152613b838186613337565b91505082606083015295945050505050565b60208082526017908201527f6d75737420636f6d652066726f6d2074686520676f762e000000000000000000604082015260600190565b600181811c90821680613be057607f821691505b60208210811415613c0157634e487b7160e01b600052602260045260246000fd5b50919050565b600082821015613c1957613c19613add565b500390565b600060208284031215613c3057600080fd5b81516001600160601b038116811461337657600080fd5b634e487b7160e01b600052603260045260246000fd5b6000600019821415613c7157613c71613add565b5060010190565b60ff851681526001600160601b038416602082015260606040820152816060820152818360808301376000818301608090810191909152601f909201601f191601019392505050565b8054600090600181811c9080831680613cdb57607f831692505b6020808410821415613cfd57634e487b7160e01b600052602260045260246000fd5b838852818015613d145760018114613d2857613d56565b60ff19861689830152604089019650613d56565b876000528160002060005b86811015613d4e5781548b8201850152908501908301613d33565b8a0183019750505b50505050505092915050565b60018060a01b038616815284602082015260a060408201526000613d8960a0830186613cc1565b8281036060840152613aca8186613cc1565b60c081526000613dae60c08301896134f1565b8281036020840152613dc08189613535565b90508281036040840152613dd48188613565565b90508281036060840152613de88187613565565b905084608084015282810360a0840152613e028185613337565b999850505050505050505056fe"), - ("0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", "PoolExercise", "6101c06040523480156200001257600080fd5b50604051620030713803806200307183398101604081905262000035916200016a565b6001600160a01b038681166101005285811660805284811660a05283811660c052821660e052600f81900b61012052858585858585620000846000808062000101602090811b6200011917901c565b6101408181525050620000a660016000806200010160201b620001191760201c565b6101608181525050620000c860026000806200010160201b620001191760201c565b6101808181525050620000ea60036000806200010160201b620001191760201c565b6101a05250620002319a5050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200012c576200012c620001f4565b6200013992911b6200020a565b6200014591906200020a565b949350505050565b80516001600160a01b03811681146200016557600080fd5b919050565b60008060008060008060c087890312156200018457600080fd5b6200018f876200014d565b95506200019f602088016200014d565b9450620001af604088016200014d565b9350620001bf606088016200014d565b9250620001cf608088016200014d565b915060a087015180600f0b8114620001e657600080fd5b809150509295509295509295565b634e487b7160e01b600052602160045260246000fd5b600082198211156200022c57634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051612d6f6200030260003960008181610e1c015261137b015260008181610e420152818161135101526113f4015260008181611327015281816114b801526122c90152600081816112fe015281816113cb0152818161148f015281816114e101526122ef0152600081816103a8015281816107ed0152610cda0152600050506000818161176601526117b201526000610485015260008181611964015261212c015260005050612d6f6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063477130981461003b578063b50e7ee314610050575b600080fd5b61004e610049366004612986565b610063565b005b61004e61005e3660046129c7565b610109565b336001600160a01b038416146100f9576001600160a01b03831660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff166100f95760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b61010483838361015f565b505050565b6101156000838361015f565b5050565b600081600f0b60808467ffffffffffffffff16901b60f8866007811115610142576101426129e9565b61014d92911b612a15565b6101579190612a15565b949350505050565b608082901c8260006001600160a01b0386161560f883901c600481600781111561018b5761018b6129e9565b14806101a8575060068160078111156101a6576101a66129e9565b145b6101e35760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964207479706560a01b60448201526064016100f0565b8115806101f95750428567ffffffffffffffff16105b6102335760405162461bcd60e51b815260206004820152600b60248201526a1b9bdd08195e1c1a5c995960aa1b60448201526064016100f0565b6004816007811115610247576102476129e9565b149250506000610262600080516020612d1a83398151915290565b9050600061026f826104c0565b9050428667ffffffffffffffff16101561029a576102978267ffffffffffffffff8816610526565b90505b82806102be5750836102b45784600f0b81600f0b126102be565b84600f0b81600f0b135b6102f45760405162461bcd60e51b81526020600482015260076024820152666e6f742049544d60c81b60448201526064016100f0565b6000841561033a5785600f0b82600f0b1315610335576103328861032984610320600f82900b8b61061e565b600f0b90610659565b600f0b906106b1565b90505b610367565b85600f0b82600f0b12156103675761036461035d89610329600f8a900b8661061e565b8490610719565b90505b6000841561038c5761037b89838c89610754565b6103859082612a15565b9050610454565b6103978b8b8b6108cb565b600082156103ff576103d58c6103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b866106b1565b610a5d565b90506103e18183612a15565b91506103ff8c6103f089610a8c565b6103fa8487612a2d565b610ae1565b604080518c8152602081018c9052908101849052606081018290526001600160a01b038d16907f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca19060800160405180910390a2505b610474898361046e6104678a6000610bb1565b8c8c610119565b89610be4565b61047e9082612a15565b90506104b37f00000000000000000000000000000000000000000000000000000000000000006104ad88610e13565b83610e67565b5050505050505050505050565b60004282600c015414156104de576104d88242610e82565b92915050565b6104e782610eb0565b90506104f38242610e82565b600f0b61050557610505824283610fd3565b42600c830155610516826001611051565b610521826000611051565b919050565b600080610535610e1084612a5a565b600881901c6000818152601287016020526040812054929350909160ff84169190821b821c90610568620e100042612a5a565b90505b811580156105795750808411155b156105a65760128801600061058d86612a7c565b955085815260200190815260200160002054915061056b565b600060805b80156105d25783811c156105ca576105c38183612a15565b93811c9391505b60011c6105ab565b5060118901600060018360086105e88a84612a15565b6105f392911b612a2d565b6105fd9190612a2d565b8152602081019190915260400160002054600f0b9998505050505050505050565b6000600f82810b9084900b0360016001607f1b03198112801590610649575060016001607f1b038113155b61065257600080fd5b9392505050565b600081600f0b6000141561066c57600080fd5b600082600f0b604085600f0b901b8161068757610687612a44565b05905060016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000816106c0575060006104d8565b600083600f0b12156106d157600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b0381111561070057600080fd5b60401b811981111561071157600080fd5b019392505050565b600080610737838560030160149054906101000a900460ff166110e1565b9050610157818560030160159054906101000a900460ff166110f7565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb602052604081205b85156108c25760006107a9600161079884611112565b6107a29190612a2d565b839061111c565b905060006107b78287611128565b9050878111156107c45750865b600080881561084657896107d8848b612a97565b6107e29190612a5a565b9150610815846103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b90506108218187612a15565b955061082d828a612a2d565b98506108468461083c89610a8c565b6103fa8486612a2d565b610850838b612a2d565b99506001600160a01b0384167f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca189856108898587612a2d565b604080519384526020840192909252908201526060810184905260800160405180910390a26108b98489856108cb565b50505050610782565b50949350505050565b6001600160a01b03831661092d5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016100f0565b61095b3384600061093d866111db565b610946866111db565b60405180602001604052806000815250611226565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156109fc5760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b60648201526084016100f0565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600080610a6984611762565b9050612710610a788285612a97565b610a829190612a5a565b6101579084612a2d565b600081610ab157600080516020612d1a833981519152546001600160a01b03166104d8565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b80610aeb57505050565b60405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb90604401602060405180830381600087803b158015610b3557600080fd5b505af1158015610b49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6d9190612ab6565b6101045760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016100f0565b60008215610bcf5781610bc5576005610bc8565b60045b90506104d8565b81610bdb576007610652565b60069392505050565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb60205260408120835b8615610e09576000610c3a6001610c2985611112565b610c339190612a2d565b849061111c565b90506000610c488288611128565b905088811115610c555750875b600089610c62838b612a97565b610c6c9190612a5a565b9050610c78818a612a2d565b9850610c84828b612a2d565b9950600087610cc35781610cb4610c9f600f88900b866106b1565b600080516020612d1a83398151915290610719565b610cbe9190612a2d565b610ccd565b610ccd8284612a2d565b90506000610d02856103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b9050610d0e8189612a15565b975082610d2a600080516020612d1a833981519152878c61182c565b15610d5457610d4386610d3d8486612a2d565b8c611869565b610d4d8282612a15565b9050610d7d565b610d7086610d618c610e13565b610d6b8587612a2d565b610e67565b610d7a8382612a15565b90505b610d97600080516020612d1a833981519152878c8461192c565b610da2868c876108cb565b6001600160a01b0386167f69a2ef6bf9e7ff92cbf1b71963ba1751b1abe8f99e3b3aae2ab99e416df614938c610dd88587612a2d565b60408051928352602083019190915281018890526060810185905260800160405180910390a2505050505050610c13565b5050949350505050565b600081610e40577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b61010483838360405180602001604052806000815250611a6c565b60006011830181610e95610e1085612a5a565b8152602081019190915260400160002054600f0b9392505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0357600080fd5b505afa158015610f17573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f3b9190612ad8565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f8f57600080fd5b505afa158015610fa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc79190612ad8565b90506101578282611b93565b6000610fe1610e1084612a5a565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061101a60ff80831690612a2d565b6001901b846012016000600884901c815260200190815260200160002060008282546110469190612a15565b909155505050505050565b80151560009081526013830160205260409020805415806110725750805442105b1561107c57505050565b60006110888484611c2e565b90506110c384826110bd6110b286600101546110ad898b611c9890919063ffffffff16565b6110e1565b600f86900b90611cc6565b86611cf9565b50501515600090815260139091016020526040812081815560010155565b6000610652836110f284600a612bd5565b611d76565b600061065261110783600a612bd5565b600f85900b906106b1565b60006104d8825490565b60006106528383611dad565b60006001600160a01b0383166111945760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b60648201526084016100f0565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6040805160018082528183019092526060916000919060208083019080368337019050509050828160008151811061121557611215612be4565b602090810291909101015292915050565b611234868686868686611e33565b600080516020612d1a83398151915260005b845181101561175857600085828151811061126357611263612be4565b60200260200101519050600085838151811061128157611281612be4565b60200260200101519050806000141561129b575050611746565b6001600160a01b0389166112b8576112b66015850183612011565b505b6001600160a01b0388161580156112e857506000828152600080516020612cfa8339815191526020526040902054155b156112fc576112fa601585018361201d565b505b7f000000000000000000000000000000000000000000000000000000000000000082148061134957507f000000000000000000000000000000000000000000000000000000000000000082145b8061137357507f000000000000000000000000000000000000000000000000000000000000000082145b8061139d57507f000000000000000000000000000000000000000000000000000000000000000082145b1561148d576001600160a01b038916158015906113c257506001600160a01b03881615155b1561148d5760007f000000000000000000000000000000000000000000000000000000000000000083148061141657507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d870160209081526040808320841515845290915290205490915042906114509062015180612a15565b1061148b5760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b60448201526064016100f0565b505b7f00000000000000000000000000000000000000000000000000000000000000008214806114da57507f000000000000000000000000000000000000000000000000000000000000000082145b15611682577f00000000000000000000000000000000000000000000000000000000000000008214600061150e8683612029565b90506001600160a01b038b161561163857600061152b8c86611128565b9050818111801561154557506115418285612a15565b8111155b156115dd576001600160a01b038c166000908152601488016020908152604080832086151580855260138c01845282852054855290835281842090845290915290205484906115949083612a2d565b10156115d25760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b60448201526064016100f0565b6115dd878d85612043565b6001600160a01b038b161561163657611611878d858c8a8151811061160457611604612be4565b602002602001015161192c565b611636878c858c8a8151811061162957611629612be4565b60200260200101516120f4565b505b6001600160a01b038a161561167f5760006116538b86611128565b905081811115801561166d57508161166b8583612a15565b115b1561167d5761167d878c85612218565b505b50505b60f882901c826001600160a01b038b16158015906116a857506001600160a01b038a1615155b80156116e0575060058260078111156116c3576116c36129e9565b14806116e0575060078260078111156116de576116de6129e9565b145b1561174157600060058360078111156116fb576116fb6129e9565b1490506000816117225761171d611716600f85900b876106b1565b8990610719565b611724565b845b9050611732888e848461192c565b61173e888d84846120f4565b50505b505050505b8061175081612a7c565b915050611246565b5050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610521576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b1580156117f457600080fd5b505afa158015611808573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d89190612ad8565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061186057504281115b95945050505050565b600080516020612d1a83398151915261188b84611885846122c0565b85610e67565b60006101048061189b8142612a5a565b6118a59190612a97565b6118af9190612a15565b6001600160a01b03861660009081526014840160209081526040808320848452825280832087151584529091528120805492935086929091906118f3908490612a15565b90915550508215156000908152601383016020526040812060018101805491928792611920908490612a15565b90915550505550505050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b863087866119978982612a2d565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156119fa57600080fd5b505af1158015611a0e573d6000803e3d6000fd5b505050508282611a1e9190612a2d565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a2d565b9315156000908152601890960160205250506040909320555050565b6001600160a01b038416611acc5760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b60648201526084016100f0565b611aeb33600086611adc876111db565b611ae5876111db565b86611226565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611b3e908490612a15565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b600081611b9f57600080fd5b600080841215611bb457836000039350600190505b6000831215611bc65760009290920391155b6000611bd28585612314565b90508115611c00576001607f1b816001600160801b03161115611bf457600080fd5b60000391506104d89050565b60016001607f1b03816001600160801b03161115611c1d57600080fd5b91506104d89050565b505092915050565b600080611c4b83611c40576001611c43565b60005b600080610119565b831515600090815260138601602052604090206001015490915061015790600080516020612cfa83398151915260008481526020919091526040902054611c929190612a2d565b6110ad86865b600081611cb3576003830154600160a81b900460ff16610652565b505060030154600160a01b900460ff1690565b6000600f83810b9083900b0160016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000611d058583612476565b90506000611d16868387878761248f565b9050611d23868285612595565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b600081611d8257600080fd5b6000611d8e8484612314565b905060016001607f1b036001600160801b038216111561065257600080fd5b81546000908210611e0b5760405162461bcd60e51b815260206004820152602260248201527f456e756d657261626c655365743a20696e646578206f7574206f6620626f756e604482015261647360f01b60648201526084016100f0565b826000018281548110611e2057611e20612be4565b9060005260206000200154905092915050565b836001600160a01b0316856001600160a01b031614612009576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020612cfa833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b87518110156104b3576000878281518110611edf57611edf612be4565b602002602001015190506000811115611ff6576000898381518110611f0657611f06612be4565b6020026020010151905060006001600160a01b03168c6001600160a01b03161415611f545760008181526020889052604081208054849290611f49908490612a15565b90915550611f8a9050565b81611f5f8d83611128565b1415611f8a576000818152602087905260409020611f7d908d6125ec565b50611f88858261201d565b505b6001600160a01b038b16611fc15760008181526020889052604081208054849290611fb6908490612a2d565b90915550611ff49050565b611fcb8b82611128565b611ff4576000818152602087905260409020611fe7908c612601565b50611ff28482612011565b505b505b508061200181612a7c565b915050611ec2565b505050505050565b60006106528383612612565b60006106528383612661565b60008161203a578260040154610652565b50506005015490565b6001600160a01b03821661205657600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061208184838361274c565b61208c575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b8630878661215f8982612a15565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156121c257600080fd5b505af11580156121d6573d6000803e3d6000fd5b5050505082826121e69190612a15565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a15565b6001600160a01b03821661222b57600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061225684838361274c565b15612262575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6000816122ed577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008161232057600080fd5b60006001600160c01b03841161234b5782604085901b8161234357612343612a44565b049050612462565b60c084811c6401000000008110612364576020918201911c5b620100008110612376576010918201911c5b6101008110612387576008918201911c5b60108110612397576004918201911c5b600481106123a7576002918201911c5b600281106123b6576001820191505b60bf820360018603901c6001018260ff0387901b816123d7576123d7612a44565b0492506001600160801b038311156123ee57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b8281101561241a576001820391505b608084901b92900382811015612431576001820391505b829003608084901c821461244757612447612bfa565b88818161245657612456612a44565b04870196505050505050505b6001600160801b0381111561065257600080fd5b60006124828383612798565b90506106528382846127bf565b600080826124a4576019870154600f0b6124b4565b6019870154600160801b9004600f0b5b905080600f0b600014156124cc57506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b6064820152730f6e8ef18fb5bb61d545fee60f779d8aed60408f9063e101a89b9060840160206040518083038186803b15801561253257600080fd5b505af4158015612546573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256a9190612c10565b915067b33333333333333382600f0b121561258b5767b33333333333333391505b5095945050505050565b80156125c5576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b6000610652836001600160a01b038416612661565b6000610652836001600160a01b0384165b6000818152600183016020526040812054612659575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104d8565b5060006104d8565b60008181526001830160205260408120548015612742576000612685600183612a2d565b8554909150600090869061269b90600190612a2d565b815481106126ab576126ab612be4565b90600052602060002001549050808660000183815481106126ce576126ce612be4565b6000918252602090912001556126e5826001612a15565b6000828152600188016020526040902055855486908061270757612707612c33565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104d8565b60009150506104d8565b6001600160a01b0383811660009081526020849052604081205490911615158061015757506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6000816127b3576008830154600160801b9004600f0b610652565b505060090154600f0b90565b600080826127d15784600a01546127d7565b84600b01545b6127e19042612a2d565b905061a8c0811115612800576127f961a8c082612a2d565b9050612809565b83915050610652565b600061281782613840611d76565b9050600061282a85611c40576001611c43565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020612cfa83398151915290935290832054939450926128889161287891612a2d565b6128829084612a2d565b83611d76565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e666666666666666608082018190526801000000000000000060a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b8152919250730f6e8ef18fb5bb61d545fee60f779d8aed60408f91634916d70d9161292991600401612c49565b60206040518083038186803b15801561294157600080fd5b505af4158015612955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129799190612c10565b9998505050505050505050565b60008060006060848603121561299b57600080fd5b83356001600160a01b03811681146129b257600080fd5b95602085013595506040909401359392505050565b600080604083850312156129da57600080fd5b50508035926020909101359150565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115612a2857612a286129ff565b500190565b600082821015612a3f57612a3f6129ff565b500390565b634e487b7160e01b600052601260045260246000fd5b600082612a7757634e487b7160e01b600052601260045260246000fd5b500490565b6000600019821415612a9057612a906129ff565b5060010190565b6000816000190483118215151615612ab157612ab16129ff565b500290565b600060208284031215612ac857600080fd5b8151801515811461065257600080fd5b600060208284031215612aea57600080fd5b5051919050565b600181815b80851115612b2c578160001904821115612b1257612b126129ff565b80851615612b1f57918102915b93841c9390800290612af6565b509250929050565b600082612b43575060016104d8565b81612b50575060006104d8565b8160018114612b665760028114612b7057612b8c565b60019150506104d8565b60ff841115612b8157612b816129ff565b50506001821b6104d8565b5060208310610133831016604e8410600b8410161715612baf575081810a6104d8565b612bb98383612af1565b8060001904821115612bcd57612bcd6129ff565b029392505050565b600061065260ff841683612b34565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052600160045260246000fd5b600060208284031215612c2257600080fd5b815180600f0b811461065257600080fd5b634e487b7160e01b600052603160045260246000fd5b6000610120820190508251600f0b82526020830151600f0b60208301526040830151612c7a6040840182600f0b9052565b506060830151612c8f6060840182600f0b9052565b506080830151612ca46080840182600f0b9052565b5060a0830151612cb960a0840182600f0b9052565b5060c0830151612cce60c0840182600f0b9052565b5060e0830151612ce360e0840182600f0b9052565b5061010080840151611c2682850182600f0b905256feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eb"), - ("0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05", "MainchainGatewayV2", "608060405234801561001057600080fd5b506000805460ff1916905561582e806200002b6000396000f3fe60806040526004361061032d5760003560e01c80639157921c116101a5578063b2975794116100ec578063d547741f11610095578063dafae4081161006f578063dafae4081461096e578063dff525e11461098e578063e400327c146109ae578063e75235b8146109ce5761033c565b8063d547741f14610901578063d55ed10314610921578063d64af2a61461094e5761033c565b8063cdb67444116100c6578063cdb674441461089c578063cdf64a76146108b4578063d19773d2146108d45761033c565b8063b29757941461082f578063b9c362091461085c578063ca15c8731461087c5761033c565b8063a3912ec81161014e578063affed0e011610128578063affed0e0146107cc578063b1a2567e146107e2578063b1d08a03146108025761033c565b8063a3912ec81461033a578063ab7965661461077f578063ac78dfe8146107ac5761033c565b8063994390891161017f57806399439089146107155780639dcc4da314610735578063a217fddf1461076a5761033c565b80639157921c1461068f57806391d14854146106af57806393c5678f146106f55761033c565b806336568abe116102745780635c975abb1161021d5780637de5dedd116101f75780637de5dedd146106115780638456cb59146106265780638f34e3471461063b5780639010d07c1461066f5761033c565b80635c975abb146105ac5780636932be98146105c45780636c1ce670146105f15761033c565b80634d0d66731161024e5780634d0d66731461052f5780634d493f4e1461054f57806359122f6b1461057f5761033c565b806336568abe146104e75780633f4ba83a146105075780634b14557e1461051c5761033c565b80631d4a7210116102d65780632f2ff15d116102b05780632f2ff15d1461049b578063302d12db146104bb5780633644e515146104d25761033c565b80631d4a721014610428578063248a9ca3146104555780632dfdf0b5146104855761033c565b8063180ff1e911610307578063180ff1e9146103d55780631a8e55b0146103e85780631b6e7594146104085761033c565b806301ffc9a71461034457806317ce2dd41461037957806317fcb39b1461039d5761033c565b3661033c5761033a6109e6565b005b61033a6109e6565b34801561035057600080fd5b5061036461035f366004614843565b610a69565b60405190151581526020015b60405180910390f35b34801561038557600080fd5b5061038f60755481565b604051908152602001610370565b3480156103a957600080fd5b506074546103bd906001600160a01b031681565b6040516001600160a01b039091168152602001610370565b61033a6103e33660046148f4565b610aad565b3480156103f457600080fd5b5061033a6104033660046149e6565b610dbd565b34801561041457600080fd5b5061033a610423366004614a52565b610e8f565b34801561043457600080fd5b5061038f610443366004614aec565b603e6020526000908152604090205481565b34801561046157600080fd5b5061038f610470366004614b09565b60009081526072602052604090206001015490565b34801561049157600080fd5b5061038f60765481565b3480156104a757600080fd5b5061033a6104b6366004614b22565b610f64565b3480156104c757600080fd5b5061038f620f424081565b3480156104de57600080fd5b5060775461038f565b3480156104f357600080fd5b5061033a610502366004614b22565b610f8f565b34801561051357600080fd5b5061033a61101b565b61033a61052a366004614b52565b611083565b34801561053b57600080fd5b5061036461054a366004614b7d565b6110e1565b34801561055b57600080fd5b5061036461056a366004614b09565b607a6020526000908152604090205460ff1681565b34801561058b57600080fd5b5061038f61059a366004614aec565b603a6020526000908152604090205481565b3480156105b857600080fd5b5060005460ff16610364565b3480156105d057600080fd5b5061038f6105df366004614b09565b60796020526000908152604090205481565b3480156105fd57600080fd5b5061036461060c366004614c06565b61118c565b34801561061d57600080fd5b5061038f61119f565b34801561063257600080fd5b5061033a611234565b34801561064757600080fd5b5061038f7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e481565b34801561067b57600080fd5b506103bd61068a366004614c32565b61129c565b34801561069b57600080fd5b5061033a6106aa366004614c54565b6112b4565b3480156106bb57600080fd5b506103646106ca366004614b22565b60009182526072602090815260408084206001600160a01b0393909316845291905290205460ff1690565b34801561070157600080fd5b5061033a6107103660046149e6565b6115ca565b34801561072157600080fd5b506003546103bd906001600160a01b031681565b34801561074157600080fd5b50610755610750366004614c32565b611696565b60408051928352602083019190915201610370565b34801561077657600080fd5b5061038f600081565b34801561078b57600080fd5b5061038f61079a366004614aec565b603c6020526000908152604090205481565b3480156107b857600080fd5b506103646107c7366004614b09565b61172f565b3480156107d857600080fd5b5061038f60045481565b3480156107ee57600080fd5b5061033a6107fd3660046149e6565b6117ce565b34801561080e57600080fd5b5061038f61081d366004614aec565b60396020526000908152604090205481565b34801561083b57600080fd5b5061084f61084a366004614aec565b61189a565b6040516103709190614ca5565b34801561086857600080fd5b50610755610877366004614c32565b611992565b34801561088857600080fd5b5061038f610897366004614b09565b611a17565b3480156108a857600080fd5b50603754603854610755565b3480156108c057600080fd5b5061033a6108cf366004614aec565b611a2e565b3480156108e057600080fd5b5061038f6108ef366004614aec565b603b6020526000908152604090205481565b34801561090d57600080fd5b5061033a61091c366004614b22565b611a97565b34801561092d57600080fd5b5061038f61093c366004614aec565b603d6020526000908152604090205481565b34801561095a57600080fd5b5061033a610969366004614aec565b611abd565b34801561097a57600080fd5b50610364610989366004614b09565b611b26565b34801561099a57600080fd5b5061033a6109a9366004614cd2565b611bbd565b3480156109ba57600080fd5b5061033a6109c93660046149e6565b611cc7565b3480156109da57600080fd5b50600154600254610755565b60005460ff1615610a315760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b60448201526064015b60405180910390fd5b6074546001600160a01b03163314610a6757610a4b614802565b338152604080820151349101528051610a65908290611d93565b505b565b60006001600160e01b031982167f5a05180f000000000000000000000000000000000000000000000000000000001480610aa75750610aa78261210a565b92915050565b607154610100900460ff16610ac85760715460ff1615610acc565b303b155b610b3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a28565b607154610100900460ff16158015610b60576071805461ffff19166101011790555b610b6b60008d612171565b6075899055610b798b61217b565b610b828a6121dd565b610c29604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f159f52c1e3a2b6a6aad3950adf713516211484e0516dad685ea662a094b7c43b918101919091527fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a560608201524660808201523060a082015260c00160408051601f198184030181529190528051602090910120607755565b610c338887612238565b5050610c3f87876122f8565b5050610c496123d3565b6000610c558680614da6565b90501115610d1657610c7e610c6a8680614da6565b610c776020890189614da6565b8787612467565b610ca4610c8b8680614da6565b8660005b602002810190610c9f9190614da6565b612666565b610cca610cb18680614da6565b8660015b602002810190610cc59190614da6565b612779565b610cf0610cd78680614da6565b8660025b602002810190610ceb9190614da6565b61288c565b610d16610cfd8680614da6565b8660035b602002810190610d119190614da6565b612a30565b60005b610d266040870187614da6565b9050811015610d9c57610d8a7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e4610d606040890189614da6565b84818110610d7057610d70614d90565b9050602002016020810190610d859190614aec565b612b43565b80610d9481614e06565b915050610d19565b508015610daf576071805461ff00191690555b505050505050505050505050565b6000805160206157b9833981519152546001600160a01b03163314610e1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82610e7d5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612779565b50505050565b6000805160206157b9833981519152546001600160a01b03163314610eef5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b84610f4e5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b610f5c868686868686612467565b505050505050565b600082815260726020526040902060010154610f808133612b65565b610f8a8383612b43565b505050565b6001600160a01b038116331461100d5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c6600000000000000000000000000000000006064820152608401610a28565b6110178282612be5565b5050565b6000805160206157b9833981519152546001600160a01b0316331461107b5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a67612c07565b60005460ff16156110c95760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b610a656110db36839003830183614ec0565b33611d93565b6000805460ff16156111285760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b611184848484808060200260200160405190810160405280939291908181526020016000905b8282101561117a5761116b60608302860136819003810190614f13565b8152602001906001019061114e565b5050505050612ca3565b949350505050565b600061119883836133bc565b9392505050565b600061122f600360009054906101000a90046001600160a01b03166001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b1580156111f257600080fd5b505afa158015611206573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061122a9190614f89565b613480565b905090565b6000805160206157b9833981519152546001600160a01b031633146112945760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a676134b6565b60008281526073602052604081206111989083613531565b7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e46112df8133612b65565b60006112f86112f336859003850185614ff0565b61353d565b905061130c6112f336859003850185614ff0565b8335600090815260796020526040902054146113765760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b82356000908152607a602052604090205460ff166113fc5760405162461bcd60e51b815260206004820152603160248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220617060448201527f70726f766564207769746864726177616c0000000000000000000000000000006064820152608401610a28565b82356000908152607a602052604090819020805460ff19169055517fd639511b37b3b002cca6cfe6bca0d833945a5af5a045578a0627fc43b79b26309061144690839086906150c4565b60405180910390a160006114606080850160608601614aec565b9050600061147661012086016101008701615151565b600181111561148757611487614c71565b141561154f5760006114a2368690038601610100870161516e565b6001600160a01b0383166000908152603b60205260409020549091506114ce90610140870135906135c6565b604082015260006114e8368790038701610100880161516e565b60408301519091506114ff9061014088013561518a565b604082015260745461151f908390339086906001600160a01b03166135e0565b6115486115326060880160408901614aec565b60745483919086906001600160a01b03166135e0565b505061158b565b61158b6115626060860160408701614aec565b60745483906001600160a01b03166115833689900389016101008a0161516e565b9291906135e0565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d82856040516115bc9291906150c4565b60405180910390a150505050565b6000805160206157b9833981519152546001600160a01b0316331461162a5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261168a5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612666565b6000806116b86000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146117115760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b84846122f8565b90925090506117286123d3565b9250929050565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190614f89565b6037546117b991906151a1565b6038546117c690846151a1565b101592915050565b6000805160206157b9833981519152546001600160a01b0316331461182e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261188e5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e898484848461288c565b60408051808201909152600080825260208201526001600160a01b0382166000908152607860205260409081902081518083019092528054829060ff1660018111156118e8576118e8614c71565b60018111156118f9576118f9614c71565b815290546001600160a01b036101009091048116602092830152908201519192501661198d5760405162461bcd60e51b815260206004820152602560248201527f4d61696e636861696e4761746577617956323a20756e737570706f727465642060448201527f746f6b656e0000000000000000000000000000000000000000000000000000006064820152608401610a28565b919050565b6000806119b46000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b031614611a0d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b8484612238565b6000818152607360205260408120610aa790613a13565b6000805160206157b9833981519152546001600160a01b03163314611a8e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a65816121dd565b600082815260726020526040902060010154611ab38133612b65565b610f8a8383612be5565b6000805160206157b9833981519152546001600160a01b03163314611b1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a658161217b565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b158015611b6b57600080fd5b505afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba39190614f89565b600154611bb091906151a1565b6002546117c690846151a1565b6000805160206157b9833981519152546001600160a01b03163314611c1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b85611c7c5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b611c8a878787878787612467565b611c978787836000610c8f565b611ca48787836001610cb5565b611cb18787836002610cdb565b611cbe8787836003610d01565b50505050505050565b6000805160206157b9833981519152546001600160a01b03163314611d275760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82611d875760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612a30565b604080518082018252600080825260208201526074549184015190916001600160a01b031690611dc290613a1d565b60208401516001600160a01b0316611ee1573484604001516040015114611e375760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611e408161189a565b6040850151519092506001811115611e5a57611e5a614c71565b82516001811115611e6d57611e6d614c71565b14611ecd5760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b6001600160a01b0381166020850152612087565b3415611f3b5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611f48846020015161189a565b6040850151519092506001811115611f6257611f62614c71565b82516001811115611f7557611f75614c71565b14611fd55760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b60208401516040850151611fec9185903090613ac7565b83602001516001600160a01b0316816001600160a01b031614156120875760408481015181015190517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b15801561206e57600080fd5b505af1158015612082573d6000803e3d6000fd5b505050505b607680546000918261209883614e06565b91905055905060006120bf858386602001516075548a613ce190949392919063ffffffff16565b90507fd7b25068d9dc8d00765254cfb7f5070f98d263c8d68931d937c7362fa738048b6120eb8261353d565b826040516120fa9291906151c0565b60405180910390a1505050505050565b60006001600160e01b031982167f7965db0b000000000000000000000000000000000000000000000000000000001480610aa757507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614610aa7565b6110178282612b43565b6074805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f9d2334c23be647e994f27a72c5eee42a43d5bdcfe15bb88e939103c2b114cbaf906020015b60405180910390a150565b6003805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fef40dc07567635f84f5edbd2f8dbc16b40d9d282dd8e7e6f4ff58236b6836169906020016121d2565b6000808284111561228b5760405162461bcd60e51b815260206004820152601c60248201527f4761746577617956323a20696e76616c6964207468726573686f6c64000000006044820152606401610a28565b505060018054600280549285905583905560048054919291849186919060006122b383614e06565b9091555060408051868152602081018690527f976f8a9c5bdf8248dec172376d6e2b80a8e3df2f0328e381c6db8e1cf138c0f891015b60405180910390a49250929050565b600080828411156123715760405162461bcd60e51b815260206004820152602760248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64000000000000000000000000000000000000000000000000006064820152608401610a28565b5050603780546038805492859055839055600480549192918491869190600061239983614e06565b9091555060408051868152602081018690527f31312c97b89cc751b832d98fd459b967a2c3eef3b49757d1cf5ebaa12bb6eee191016122e9565b6002546037546123e391906151a1565b6038546001546123f391906151a1565b1115610a675760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64730000000000000000000000000000000000000000000000006064820152608401610a28565b848314801561247557508481145b6124e75760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206172726160448201527f79206c656e6774680000000000000000000000000000000000000000000000006064820152608401610a28565b60005b8581101561262c5784848281811061250457612504614d90565b90506020020160208101906125199190614aec565b6078600089898581811061252f5761252f614d90565b90506020020160208101906125449190614aec565b6001600160a01b039081168252602082019290925260400160002080547fffffffffffffffffffffff0000000000000000000000000000000000000000ff1661010093909216929092021790558282828181106125a3576125a3614d90565b90506020020160208101906125b89190615151565b607860008989858181106125ce576125ce614d90565b90506020020160208101906125e39190614aec565b6001600160a01b031681526020810191909152604001600020805460ff19166001838181111561261557612615614c71565b02179055508061262481614e06565b9150506124ea565b507fa4f03cc9c0e0aeb5b71b4ec800702753f65748c2cf3064695ba8e8b46be704448686868686866040516120fa969594939291906152c1565b8281146126c85760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612743578282828181106126e5576126e5614d90565b905060200201356039600087878581811061270257612702614d90565b90506020020160208101906127179190614aec565b6001600160a01b031681526020810191909152604001600020558061273b81614e06565b9150506126cb565b507f80bc635c452ae67f12f9b6f12ad4daa6dbbc04eeb9ebb87d354ce10c0e210dc0848484846040516115bc9493929190615339565b8281146127db5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612856578282828181106127f8576127f8614d90565b90506020020135603a600087878581811061281557612815614d90565b905060200201602081019061282a9190614aec565b6001600160a01b031681526020810191909152604001600020558061284e81614e06565b9150506127de565b507f64557254143204d91ba2d95acb9fda1e5fea55f77efd028685765bc1e94dd4b5848484846040516115bc9493929190615339565b8281146128ee5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b838110156129fa57620f424083838381811061290f5761290f614d90565b90506020020135111561298a5760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420706560448201527f7263656e746167650000000000000000000000000000000000000000000000006064820152608401610a28565b82828281811061299c5761299c614d90565b90506020020135603b60008787858181106129b9576129b9614d90565b90506020020160208101906129ce9190614aec565b6001600160a01b03168152602081019190915260400160002055806129f281614e06565b9150506128f1565b507fb05f5de88ae0294ebb6f67c5af2fcbbd593cc6bdfe543e2869794a4c8ce3ea50848484846040516115bc9493929190615339565b828114612a925760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612b0d57828282818110612aaf57612aaf614d90565b90506020020135603c6000878785818110612acc57612acc614d90565b9050602002016020810190612ae19190614aec565b6001600160a01b0316815260208101919091526040016000205580612b0581614e06565b915050612a95565b507fb5d2963614d72181b4df1f993d45b83edf42fa19710f0204217ba1b3e183bb73848484846040516115bc9493929190615339565b612b4d8282613db6565b6000828152607360205260409020610f8a9082613e58565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff1661101757612ba3816001600160a01b03166014613e6d565b612bae836020613e6d565b604051602001612bbf9291906153d0565b60408051601f198184030181529082905262461bcd60e51b8252610a2891600401615451565b612bef828261404e565b6000828152607360205260409020610f8a90826140d1565b60005460ff16612c595760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152606401610a28565b6000805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000823561014084013582612cbe6080870160608801614aec565b9050612cdb612cd6368890038801610100890161516e565b613a1d565b6001612ced6040880160208901615151565b6001811115612cfe57612cfe614c71565b14612d715760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964207265636560448201527f697074206b696e640000000000000000000000000000000000000000000000006064820152608401610a28565b60808601354614612de95760405162461bcd60e51b8152602060048201526024808201527f4d61696e636861696e4761746577617956323a20696e76616c6964206368616960448201527f6e206964000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6000612dfe61084a6080890160608a01614aec565b9050612e1261012088016101008901615151565b6001811115612e2357612e23614c71565b81516001811115612e3657612e36614c71565b148015612e675750612e4e60e0880160c08901614aec565b6001600160a01b031681602001516001600160a01b0316145b612ebf5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b60008481526079602052604090205415612f415760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220707260448201527f6f636573736564207769746864726177616c00000000000000000000000000006064820152608401610a28565b6001612f5561012089016101008a01615151565b6001811115612f6657612f66614c71565b1480612f795750612f7782846133bc565b155b612feb5760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a2072656163686564206461696c60448201527f79207769746864726177616c206c696d697400000000000000000000000000006064820152608401610a28565b6000612fff6112f3368a90038a018a614ff0565b9050600061300f607754836140e6565b6003549091506001600160a01b0316600061303d6130356101208d016101008e01615151565b878985614142565b60408051606081018252600080825260208201819052918101829052919b50919250819081906000805b8f5181101561323c578f818151811061308257613082614d90565b6020908102919091018101518051818301516040808401518151600081529586018083528f905260ff9093169085015260608401526080830152935060019060a0016020604051602081039080840390855afa1580156130e6573d6000803e3d6000fd5b505050602060405103519450846001600160a01b0316846001600160a01b0316106131795760405162461bcd60e51b815260206004820152602160248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206f72646560448201527f72000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6040517f953865650000000000000000000000000000000000000000000000000000000081526001600160a01b03808716600483015286955089169063953865659060240160206040518083038186803b1580156131d657600080fd5b505afa1580156131ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320e9190614f89565b6132189083615484565b915086821061322a576001955061323c565b8061323481614e06565b915050613067565b50846132b05760405162461bcd60e51b815260206004820152603660248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220696e60448201527f73756666696369656e7420766f746520776569676874000000000000000000006064820152608401610a28565b50505060008a81526079602052604090208690555050881561332c576000888152607a602052604090819020805460ff19166001179055517f89e52969465b1f1866fc5d46fd62de953962e9cb33552443cd999eba05bd20dc906133179086908e906150c4565b60405180910390a15050505050505050610aa7565b6133368688614233565b61337561334960608d0160408e01614aec565b87607460009054906101000a90046001600160a01b03168e61010001803603810190611583919061516e565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d848c6040516133a69291906150c4565b60405180910390a1505050505050505092915050565b6001600160a01b0382166000908152603a602052604081205482106133e357506000610aa7565b60006133f2620151804261549c565b6001600160a01b0385166000908152603e60205260409020549091508111156134385750506001600160a01b0382166000908152603c6020526040902054811015610aa7565b6001600160a01b0384166000908152603d602052604090205461345c908490615484565b6001600160a01b0385166000908152603c602052604090205411159150610aa79050565b600060025460016002548460015461349891906151a1565b6134a29190615484565b6134ac919061518a565b610aa7919061549c565b60005460ff16156134fc5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b6000805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612c863390565b600061119883836142c3565b60007fb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea60001b8260000151836020015161357a85604001516142ed565b61358786606001516142ed565b6135948760800151614350565b6040516020016135a9969594939291906154be565b604051602081830303815290604052805190602001209050919050565b6000620f42406135d683856151a1565b611198919061549c565b6000816001600160a01b0316836001600160a01b031614156136905760408086015190516001600160a01b0386169180156108fc02916000818181858888f1935050505061368b57816001600160a01b031663d0e30db086604001516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561366757600080fd5b505af115801561367b573d6000803e3d6000fd5b505050505061368b858585614393565b613a0c565b6000855160018111156136a5576136a5614c71565b1415613866576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000906001600160a01b038516906370a082319060240160206040518083038186803b15801561370657600080fd5b505afa15801561371a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061373e9190614f89565b9050856040015181101561385557836001600160a01b03166340c10f193083896040015161376c919061518a565b6040516001600160a01b03909216602483015260448201526064016040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516137c091906154f8565b6000604051808303816000865af19150503d80600081146137fd576040519150601f19603f3d011682016040523d82523d6000602084013e613802565b606091505b505080925050816138555760405162461bcd60e51b815260206004820152601b60248201527f546f6b656e3a204552433230206d696e74696e67206661696c656400000000006044820152606401610a28565b613860868686614393565b50613a0c565b60018551600181111561387b5761387b614c71565b141561399e5761389083858760200151614437565b61368b57602085810151604080516001600160a01b038881166024830152604480830194909452825180830390940184526064909101825292820180516001600160e01b03167f40c10f1900000000000000000000000000000000000000000000000000000000179052519185169161390991906154f8565b6000604051808303816000865af19150503d8060008114613946576040519150601f19603f3d011682016040523d82523d6000602084013e61394b565b606091505b5050809150508061368b5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e3a20455243373231206d696e74696e67206661696c6564000000006044820152606401610a28565b60405162461bcd60e51b815260206004820152602160248201527f546f6b656e3a20756e737570706f7274656420746f6b656e207374616e64617260448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b5050505050565b6000610aa7825490565b600081516001811115613a3257613a32614c71565b148015613a43575060008160400151115b8015613a5157506020810151155b80613a7b5750600181516001811115613a6c57613a6c614c71565b148015613a7b57506040810151155b610a655760405162461bcd60e51b815260206004820152601360248201527f546f6b656e3a20696e76616c696420696e666f000000000000000000000000006044820152606401610a28565b600060608186516001811115613adf57613adf614c71565b1415613bbd5760408681015181516001600160a01b038881166024830152878116604483015260648083019390935283518083039093018352608490910183526020820180516001600160e01b03166323b872dd60e01b179052915191851691613b4991906154f8565b6000604051808303816000865af19150503d8060008114613b86576040519150601f19603f3d011682016040523d82523d6000602084013e613b8b565b606091505b509092509050818015613bb6575080511580613bb6575080806020019051810190613bb69190615514565b9150613c84565b600186516001811115613bd257613bd2614c71565b141561399e57602086810151604080516001600160a01b0389811660248301528881166044830152606480830194909452825180830390940184526084909101825292820180516001600160e01b03166323b872dd60e01b1790525191851691613c3c91906154f8565b6000604051808303816000865af19150503d8060008114613c79576040519150601f19603f3d011682016040523d82523d6000602084013e613c7e565b606091505b50909250505b81610f5c57613c92866144e2565b613ca6866001600160a01b03166014613e6d565b613cba866001600160a01b03166014613e6d565b613cce866001600160a01b03166014613e6d565b604051602001612bbf9493929190615536565b613d516040805160a08101825260008082526020808301829052835160608082018652838252818301849052818601849052848601919091528451808201865283815280830184905280860184905281850152845190810185528281529081018290529283015290608082015290565b83815260006020820181905250604080820180516001600160a01b039788169052602080890151825190891690820152905146908301528751606084018051918916909152805195909716940193909352935182015292909201516080820152919050565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff166110175760008281526072602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613e143390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000611198836001600160a01b03841661454f565b60606000613e7c8360026151a1565b613e87906002615484565b67ffffffffffffffff811115613e9f57613e9f614e21565b6040519080825280601f01601f191660200182016040528015613ec9576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110613f0057613f00614d90565b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110613f4b57613f4b614d90565b60200101906001600160f81b031916908160001a9053506000613f6f8460026151a1565b613f7a906001615484565b90505b6001811115613fff577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110613fbb57613fbb614d90565b1a60f81b828281518110613fd157613fd1614d90565b60200101906001600160f81b031916908160001a90535060049490941c93613ff881615606565b9050613f7d565b5083156111985760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610a28565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff16156110175760008281526072602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000611198836001600160a01b03841661459e565b604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820185905260428083018590528351808403909101815260629092019092528051910120600090611198565b6000806000836001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b15801561418057600080fd5b505afa158015614194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141b89190614f89565b90506141c381613480565b925060008760018111156141d9576141d9614c71565b1415614229576001600160a01b038616600090815260396020526040902054851061420a5761420781614691565b92505b6001600160a01b0386166000908152603a602052604090205485101591505b5094509492505050565b6000614242620151804261549c565b6001600160a01b0384166000908152603e6020526040902054909150811115614291576001600160a01b03929092166000908152603e6020908152604080832094909455603d90529190912055565b6001600160a01b0383166000908152603d6020526040812080548492906142b9908490615484565b9091555050505050565b60008260000182815481106142da576142da614d90565b9060005260206000200154905092915050565b805160208083015160408085015190516000946135a9947f353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e87649491939192019384526001600160a01b03928316602085015291166040830152606082015260800190565b805160208083015160408085015190516000946135a9947f1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d94919391920161561d565b600080845160018111156143a9576143a9614c71565b14156143c5576143be828486604001516146a9565b90506143ef565b6001845160018111156143da576143da614c71565b141561399e576143be82848660200151614437565b80610e89576143fd846144e2565b614411846001600160a01b03166014613e6d565b614425846001600160a01b03166014613e6d565b604051602001612bbf93929190615648565b604080513060248201526001600160a01b038481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b1790529151600092861691614495916154f8565b6000604051808303816000865af19150503d80600081146144d2576040519150601f19603f3d011682016040523d82523d6000602084013e6144d7565b606091505b509095945050505050565b606061450d826000015160018111156144fd576144fd614c71565b6001600160a01b03166001613e6d565b61451a8360200151614795565b6145278460400151614795565b604051602001614539939291906156d9565b6040516020818303038152906040529050919050565b600081815260018301602052604081205461459657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aa7565b506000610aa7565b600081815260018301602052604081205480156146875760006145c260018361518a565b85549091506000906145d69060019061518a565b905081811461463b5760008660000182815481106145f6576145f6614d90565b906000526020600020015490508087600001848154811061461957614619614d90565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061464c5761464c6157a2565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610aa7565b6000915050610aa7565b600060385460016038548460375461349891906151a1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b03167fa9059cbb0000000000000000000000000000000000000000000000000000000017905291516000926060929087169161471f91906154f8565b6000604051808303816000865af19150503d806000811461475c576040519150601f19603f3d011682016040523d82523d6000602084013e614761565b606091505b50909250905081801561478c57508051158061478c57508080602001905181019061478c9190615514565b95945050505050565b6060816147d557505060408051808201909152600481527f3078303000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156147f857806147e981614e06565b915050600882901c91506147d9565b6111848482613e6d565b604080516060810182526000808252602082015290810161483e6040805160608101909152806000815260200160008152602001600081525090565b905290565b60006020828403121561485557600080fd5b81356001600160e01b03198116811461119857600080fd5b6001600160a01b0381168114610a6557600080fd5b803561198d8161486d565b8060608101831015610aa757600080fd5b8060808101831015610aa757600080fd5b60008083601f8401126148c157600080fd5b50813567ffffffffffffffff8111156148d957600080fd5b6020830191508360208260051b850101111561172857600080fd5b60008060008060008060008060008060006101408c8e03121561491657600080fd5b61491f8c614882565b9a5061492d60208d01614882565b995061493b60408d01614882565b985060608c0135975060808c0135965060a08c0135955060c08c0135945067ffffffffffffffff8060e08e0135111561497357600080fd5b6149838e60e08f01358f0161488d565b9450806101008e0135111561499757600080fd5b6149a88e6101008f01358f0161489e565b9350806101208e013511156149bc57600080fd5b506149ce8d6101208e01358e016148af565b81935080925050509295989b509295989b9093969950565b600080600080604085870312156149fc57600080fd5b843567ffffffffffffffff80821115614a1457600080fd5b614a20888389016148af565b90965094506020870135915080821115614a3957600080fd5b50614a46878288016148af565b95989497509550505050565b60008060008060008060608789031215614a6b57600080fd5b863567ffffffffffffffff80821115614a8357600080fd5b614a8f8a838b016148af565b90985096506020890135915080821115614aa857600080fd5b614ab48a838b016148af565b90965094506040890135915080821115614acd57600080fd5b50614ada89828a016148af565b979a9699509497509295939492505050565b600060208284031215614afe57600080fd5b81356111988161486d565b600060208284031215614b1b57600080fd5b5035919050565b60008060408385031215614b3557600080fd5b823591506020830135614b478161486d565b809150509250929050565b600060a08284031215614b6457600080fd5b50919050565b60006101608284031215614b6457600080fd5b60008060006101808486031215614b9357600080fd5b614b9d8585614b6a565b925061016084013567ffffffffffffffff80821115614bbb57600080fd5b818601915086601f830112614bcf57600080fd5b813581811115614bde57600080fd5b876020606083028501011115614bf357600080fd5b6020830194508093505050509250925092565b60008060408385031215614c1957600080fd5b8235614c248161486d565b946020939093013593505050565b60008060408385031215614c4557600080fd5b50508035926020909101359150565b60006101608284031215614c6757600080fd5b6111988383614b6a565b634e487b7160e01b600052602160045260246000fd5b60028110610a6557634e487b7160e01b600052602160045260246000fd5b81516040820190614cb581614c87565b808352506001600160a01b03602084015116602083015292915050565b60008060008060008060006080888a031215614ced57600080fd5b873567ffffffffffffffff80821115614d0557600080fd5b614d118b838c016148af565b909950975060208a0135915080821115614d2a57600080fd5b614d368b838c016148af565b909750955060408a0135915080821115614d4f57600080fd5b614d5b8b838c016148af565b909550935060608a0135915080821115614d7457600080fd5b50614d818a828b0161489e565b91505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112614dbd57600080fd5b83018035915067ffffffffffffffff821115614dd857600080fd5b6020019150600581901b360382131561172857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415614e1a57614e1a614df0565b5060010190565b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715614e6857634e487b7160e01b600052604160045260246000fd5b60405290565b60028110610a6557600080fd5b600060608284031215614e8d57600080fd5b614e95614e37565b90508135614ea281614e6e565b80825250602082013560208201526040820135604082015292915050565b600060a08284031215614ed257600080fd5b614eda614e37565b8235614ee58161486d565b81526020830135614ef58161486d565b6020820152614f078460408501614e7b565b60408201529392505050565b600060608284031215614f2557600080fd5b6040516060810181811067ffffffffffffffff82111715614f5657634e487b7160e01b600052604160045260246000fd5b604052823560ff81168114614f6a57600080fd5b8152602083810135908201526040928301359281019290925250919050565b600060208284031215614f9b57600080fd5b5051919050565b600060608284031215614fb457600080fd5b614fbc614e37565b90508135614fc98161486d565b81526020820135614fd98161486d565b806020830152506040820135604082015292915050565b6000610160828403121561500357600080fd5b60405160a0810181811067ffffffffffffffff8211171561503457634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561504981614e6e565b602082015261505b8460408501614fa2565b604082015261506d8460a08501614fa2565b6060820152615080846101008501614e7b565b60808201529392505050565b80356150978161486d565b6001600160a01b0390811683526020820135906150b38261486d565b166020830152604090810135910152565b6000610180820190508382528235602083015260208301356150e581614e6e565b6150ee81614c87565b80604084015250615105606083016040850161508c565b61511560c0830160a0850161508c565b61012061010084013561512781614e6e565b61513081614c87565b81840152830135610140808401919091529092013561016090910152919050565b60006020828403121561516357600080fd5b813561119881614e6e565b60006060828403121561518057600080fd5b6111988383614e7b565b60008282101561519c5761519c614df0565b500390565b60008160001904831182151516156151bb576151bb614df0565b500290565b6000610180820190508382528251602083015260208301516151e181614c87565b6040838101919091528381015180516001600160a01b03908116606086015260208201511660808501529081015160a084015250606083015180516001600160a01b0390811660c085015260208201511660e08401526040810151610100840152506080830151805161525381614c87565b6101208401526020810151610140840152604001516101609092019190915292915050565b8183526000602080850194508260005b858110156152b657813561529b8161486d565b6001600160a01b031687529582019590820190600101615288565b509495945050505050565b6060815260006152d560608301888a615278565b6020838203818501526152e982888a615278565b8481036040860152858152869250810160005b8681101561532a57833561530f81614e6e565b61531881614c87565b825292820192908201906001016152fc565b509a9950505050505050505050565b60408152600061534d604083018688615278565b82810360208401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84111561538557600080fd5b8360051b80866020840137600091016020019081529695505050505050565b60005b838110156153bf5781810151838201526020016153a7565b83811115610e895750506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516154088160178501602088016153a4565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516154458160288401602088016153a4565b01602801949350505050565b60208152600082518060208401526154708160408501602087016153a4565b601f01601f19169190910160400192915050565b6000821982111561549757615497614df0565b500190565b6000826154b957634e487b7160e01b600052601260045260246000fd5b500490565b8681526020810186905260c081016154d586614c87565b8560408301528460608301528360808301528260a0830152979650505050505050565b6000825161550a8184602087016153a4565b9190910192915050565b60006020828403121561552657600080fd5b8151801515811461119857600080fd5b7f546f6b656e3a20636f756c64206e6f74207472616e7366657220000000000000815260008551602061556f82601a8601838b016153a4565b7f2066726f6d200000000000000000000000000000000000000000000000000000601a9285019283015286516155aa81838501848b016153a4565b630103a37960e51b92018181019290925285516155cd81602485018985016153a4565b660103a37b5b2b7160cd1b6024939091019283015284516155f481602b85018489016153a4565b91909101602b01979650505050505050565b60008161561557615615614df0565b506000190190565b8481526080810161562d85614c87565b84602083015283604083015282606083015295945050505050565b7f546f6b656e3a20636f756c64206e6f74207472616e736665722000000000000081526000845161568081601a8501602089016153a4565b630103a37960e51b601a9184019182015284516156a481601e8401602089016153a4565b660103a37b5b2b7160cd1b601e929091019182015283516156cc8160258401602088016153a4565b0160250195945050505050565b7f546f6b656e496e666f280000000000000000000000000000000000000000000081526000845161571181600a8501602089016153a4565b80830190507f2c0000000000000000000000000000000000000000000000000000000000000080600a830152855161575081600b850160208a016153a4565b600b920191820152835161576b81600c8401602088016153a4565b7f2900000000000000000000000000000000000000000000000000000000000000600c9290910191820152600d0195945050505050565b634e487b7160e01b600052603160045260246000fdfeb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610348617350726f787941646d696e3a20756e617574686f72697a65642073656e64") + ( + "0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193", + "BearXNFTStaking", + "608060405234801561001057600080fd5b50613000806100206000396000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80638129fc1c11610130578063bca35a71116100b8578063dada55011161007c578063dada550114610458578063f2fde38b1461046b578063f83d08ba1461047e578063fbb0022714610486578063fccd7f721461048e57600080fd5b8063bca35a71146103fa578063bf9befb11461040d578063c89d5b8b14610416578063d5d423001461041e578063d976e09f1461042657600080fd5b8063b1c92f95116100ff578063b1c92f95146103c5578063b549445c146103ce578063b81f8e89146103d6578063b9ade5b7146103de578063ba0848db146103e757600080fd5b80638129fc1c146103905780638da5cb5b14610398578063aaed083b146103a9578063b10dcc93146103b257600080fd5b8063367c164e116101b35780635923489b116101825780635923489b146103245780636e2751211461034f578063706ce3e114610362578063715018a614610375578063760a2e8a1461037d57600080fd5b8063367c164e146102bd57806338ff8a85146102d05780633a17f4f0146102f1578063426233601461030457600080fd5b8063206635e7116101fa578063206635e71461026d5780632afe761a146102805780632bd30f1114610289578063305f839a146102ab57806333ddacd1146102b457600080fd5b8062944f621461022b5780630d00368b146102405780630e8feed41461025c578063120957fd14610264575b600080fd5b61023e610239366004612aa4565b6104bc565b005b61024960735481565b6040519081526020015b60405180910390f35b61023e61053a565b610249606d5481565b61023e61027b366004612b2c565b61057e565b610249606f5481565b60785461029b90610100900460ff1681565b6040519015158152602001610253565b61024960715481565b61024960765481565b61023e6102cb366004612bc2565b6105d1565b6102e36102de366004612aa4565b610829565b604051610253929190612c16565b61023e6102ff366004612aa4565b6109e1565b610317610312366004612aa4565b610a56565b6040516102539190612c2f565b606a54610337906001600160a01b031681565b6040516001600160a01b039091168152602001610253565b6102e361035d366004612aa4565b610b4c565b606b54610337906001600160a01b031681565b61023e610cf8565b61029b61038b366004612aa4565b610d2e565b61023e610dc2565b6033546001600160a01b0316610337565b61024960705481565b61023e6103c0366004612b2c565b610fc0565b610249606e5481565b61023e611236565b61023e6112bb565b61024960725481565b6102e36103f5366004612aa4565b6112ef565b61023e610408366004612aa4565b61149b565b610249606c5481565b610249611510565b606f54610249565b610439610434366004612aa4565b611594565b6040805192151583526001600160a01b03909116602083015201610253565b606954610337906001600160a01b031681565b61023e610479366004612aa4565b6115cf565b61023e611667565b61023e6116a0565b6104a161049c366004612aa4565b6116e1565b60408051938452602084019290925290820152606001610253565b6033546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690612c42565b60405180910390fd5b606b546001600160a01b03163b6105185760405162461bcd60e51b81526004016104e690612c77565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061054533611594565b509050806105655760405162461bcd60e51b81526004016104e690612cae565b61056e33610d2e565b61057b5761057b33611c73565b50565b610587336116e1565b505060765560005b81518110156105cd576105bb8282815181106105ad576105ad612cda565b602002602001015133611d7b565b806105c581612d06565b91505061058f565b5050565b606a54604051636eb1769f60e11b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b602482015282916001600160a01b03169063dd62ed3e90604401602060405180830381865afa158015610633573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106579190612d21565b10156106bb5760405162461bcd60e51b815260206004820152602d60248201527f596f75206861766520746f20617070726f766520726f6f747820746f2073746160448201526c1ada5b99c818dbdb9d1c9858dd609a1b60648201526084016104e6565b606a546040516323b872dd60e01b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b6024820152604481018390526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190612d3a565b50606a546040516326c7e79d60e21b8152600481018390526001600160a01b0390911690639b1f9e7490602401600060405180830381600087803b15801561079157600080fd5b505af11580156107a5573d6000803e3d6000fd5b5050606b546001600160a01b031691506379c650689050336107c88460056120a1565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561080e57600080fd5b505af1158015610822573d6000803e3d6000fd5b5050505050565b600060606000805b6001600160a01b0385166000908152607460205260409020548110156108bc576001600160a01b0385166000908152607460205260409020805461089791908390811061088057610880612cda565b906000526020600020906005020160000154612129565b156108aa576108a7600183612d5c565b91505b806108b481612d06565b915050610831565b5060008167ffffffffffffffff8111156108d8576108d8612ac1565b604051908082528060200260200182016040528015610901578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461095791908390811061088057610880612cda565b156109c3576001600160a01b038716600090815260746020526040902080548290811061098657610986612cda565b9060005260206000209060050201600001548383815181106109aa576109aa612cda565b60209081029190910101526109c0826001612154565b91505b806109cd81612d06565b915050610908565b50919590945092505050565b6033546001600160a01b03163314610a0b5760405162461bcd60e51b81526004016104e690612c42565b6069546001600160a01b03163b610a345760405162461bcd60e51b81526004016104e690612c77565b606980546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0381166000908152607460205260408120546060919067ffffffffffffffff811115610a8b57610a8b612ac1565b604051908082528060200260200182016040528015610ab4578160200160208202803683370190505b50905060005b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b0384166000908152607460205260409020805482908110610b0457610b04612cda565b906000526020600020906005020160000154828281518110610b2857610b28612cda565b602090810291909101015280610b3d81612d06565b915050610aba565b5092915050565b600060606000805b6001600160a01b038516600090815260746020526040902054811015610bdf576001600160a01b03851660009081526074602052604090208054610bba919083908110610ba357610ba3612cda565b9060005260206000209060050201600001546121b3565b15610bcd57610bca826001612154565b91505b80610bd781612d06565b915050610b54565b5060008167ffffffffffffffff811115610bfb57610bfb612ac1565b604051908082528060200260200182016040528015610c24578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b03871660009081526074602052604090208054610c7a919083908110610ba357610ba3612cda565b15610ce6576001600160a01b0387166000908152607460205260409020805482908110610ca957610ca9612cda565b906000526020600020906005020160000154838381518110610ccd57610ccd612cda565b6020908102919091010152610ce3826001612154565b91505b80610cf081612d06565b915050610c2b565b6033546001600160a01b03163314610d225760405162461bcd60e51b81526004016104e690612c42565b610d2c60006121d0565b565b60006001815b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b03841660009081526074602052604081208054610d9a919084908110610d8357610d83612cda565b906000526020600020906005020160010154612222565b9050603c8111610daa5750610db0565b60009250505b80610dba81612d06565b915050610d34565b600054610100900460ff16610ddd5760005460ff1615610de1565b303b155b610e445760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104e6565b600054610100900460ff16158015610e66576000805461ffff19166101011790555b610e6e61223c565b606580546001600160a01b0319908116737a250d5630b4cf539739df2c5dacb4c659f2488d1790915560668054821673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2179055620151806067556312cc030060685560698054821673e22e1e620dffb03065cd77db0162249c0c91bf01179055606a8054821673d718ad25285d65ef4d79262a6cd3aea6a8e01023179055606b80549091167399cfdf48d0ba4885a73786148a2f89d86c7021701790556000606c5568056bc75e2d63100000606d556802b5e3af16b1880000606e55690257058e269742680000606f819055681b1ae4d6e2ef5000006070819055610bb8607181905591610f709190612d74565b610f7a9190612d8b565b607255607154606e54606d54610f909190612d74565b610f9a9190612d8b565b60735560006076556078805460ff19169055801561057b576000805461ff001916905550565b6000610fcb33611594565b50905080610feb5760405162461bcd60e51b81526004016104e690612cae565b607854610100900460ff161561102c5760405162461bcd60e51b8152602060048201526006602482015265131bd8dad95960d21b60448201526064016104e6565b600061103733610a56565b90508051835111156110775760405162461bcd60e51b81526020600482015260096024820152684964206572726f727360b81b60448201526064016104e6565b6000805b84518110156110fd5760005b83518110156110ea578381815181106110a2576110a2612cda565b60200260200101518683815181106110bc576110bc612cda565b602002602001015114156110d8576110d5836001612154565b92505b806110e281612d06565b915050611087565b50806110f581612d06565b91505061107b565b50835181141561123057835161112761111e82678ac7230489e800006120a1565b606f5490612154565b606f55611132612273565b600060768190555b855181101561122d5760695486516001600160a01b03909116906323b872dd90309033908a908690811061117057611170612cda565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b5050606c80549250905060006111f383612dad565b919050555061121b3387838151811061120e5761120e612cda565b602002602001015161229b565b8061122581612d06565b91505061113a565b50505b50505050565b60785460ff166112ac5760005b60755481101561057b576001607760006075848154811061126657611266612cda565b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055806112a481612d06565b915050611243565b6078805460ff19166001179055565b60006112c633611594565b509050806112e65760405162461bcd60e51b81526004016104e690612cae565b61057b33612470565b600060606000805b6001600160a01b038516600090815260746020526040902054811015611382576001600160a01b0385166000908152607460205260409020805461135d91908390811061134657611346612cda565b906000526020600020906005020160000154612574565b156113705761136d600183612d5c565b91505b8061137a81612d06565b9150506112f7565b5060008167ffffffffffffffff81111561139e5761139e612ac1565b6040519080825280602002602001820160405280156113c7578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461141d91908390811061134657611346612cda565b15611489576001600160a01b038716600090815260746020526040902080548290811061144c5761144c612cda565b90600052602060002090600502016000015483838151811061147057611470612cda565b6020908102919091010152611486826001612154565b91505b8061149381612d06565b9150506113ce565b6033546001600160a01b031633146114c55760405162461bcd60e51b81526004016104e690612c42565b606a546001600160a01b03163b6114ee5760405162461bcd60e51b81526004016104e690612c77565b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000806064606f5460016115249190612dc4565b61152e9190612d8b565b611539906001612d5c565b606d546115469190612dc4565b90506000606d54826115589190612d74565b905060006001606d548361156c9190612d8b565b6115769190612d8b565b611581906001612dc4565b61158c906064612dc4565b949350505050565b6001600160a01b038116600090815260776020526040812054819060ff161515600114156115c457506001929050565b506000928392509050565b6033546001600160a01b031633146115f95760405162461bcd60e51b81526004016104e690612c42565b6001600160a01b03811661165e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016104e6565b61057b816121d0565b73d0d725208fd36be1561050fc1dd6a651d7ea7c89331415610d2c576078805461ff001981166101009182900460ff1615909102179055565b60006116ab33611594565b509050806116cb5760405162461bcd60e51b81526004016104e690612cae565b6116d433610d2e565b61057b5761057b336125aa565b600080808080808087816116f482610829565b509050600061170283610b4c565b50905060058110611a3b57600160005b6001600160a01b038516600090815260746020526040902054811015611a0e576001600160a01b0385166000908152607460205260408120805461177891908490811061176157611761612cda565b906000526020600020906005020160040154612222565b6001600160a01b038716600090815260746020526040902080549192506117a99184908110610ba357610ba3612cda565b806117de57506001600160a01b038616600090815260746020526040902080546117de91908490811061088057610880612cda565b80156117ea5750600181105b156117f457600092505b82801561181857506001600160a01b03861660009081526074602052604090205415155b156118715761186a8561182c83600a612dc4565b6118369190612dc4565b6118648661184585600a612dc4565b61184f9190612dc4565b60765461186490670de0b6b3a7640000612798565b90612154565b995061188e565b8261188e5760765461188b90670de0b6b3a7640000612798565b99505b6001600160a01b038616600090815260746020526040902080546118bd91908490811061088057610880612cda565b15611950576001600160a01b038616600090815260746020526040812080546119089190859081106118f1576118f1612cda565b906000526020600020906005020160020154612222565b90508061192861192182680ad78ebc5ac6200000612dc4565b8c90612154565b9a5061194761194082680ad78ebc5ac6200000612dc4565b8b90612154565b995050506119fb565b6001600160a01b0386166000908152607460205260409020805461197f919084908110610ba357610ba3612cda565b156119fb576001600160a01b038616600090815260746020526040812080546119b39190859081106118f1576118f1612cda565b905060008190506119d76002606d54846119cd9190612dc4565b6119219190612d8b565b9a506119f66002606d54836119ec9190612dc4565b6119409190612d8b565b995050505b5080611a0681612d06565b915050611712565b508515611a3557606b54606654611a32916001600160a01b039081169116886127da565b94505b50611c4f565b60005b6001600160a01b038416600090815260746020526040902054811015611c28576001600160a01b03841660009081526074602052604090208054611a8d91908390811061088057610880612cda565b15611b9f576001600160a01b03841660009081526074602052604081208054611ac191908490811061176157611761612cda565b9050611ad8611ad182600a612dc4565b8a90612154565b98506000611b1560746000886001600160a01b03166001600160a01b0316815260200190815260200160002084815481106118f1576118f1612cda565b9050611b2d611ad182680ad78ebc5ac6200000612dc4565b98506000611b8160746000896001600160a01b03166001600160a01b031681526020019081526020016000208581548110611b6a57611b6a612cda565b906000526020600020906005020160030154612222565b9050611b99611ad182680ad78ebc5ac6200000612dc4565b98505050505b6001600160a01b03841660009081526074602052604090208054611bce919083908110610ba357610ba3612cda565b15611c16576001600160a01b03841660009081526074602052604081208054611c0291908490811061176157611761612cda565b9050611c12611ad182600a612dc4565b9850505b80611c2081612d06565b915050611a3e565b508415611c4f57606b54606654611c4c916001600160a01b039081169116876127da565b93505b611c6187670de0b6b3a7640000612dc4565b9b959a50929850939650505050505050565b6000611c7e826116e1565b5091505080156105cd57606b5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af1158015611cdb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cff9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b0383166000908152607460205260409020805442919083908110611d5057611d50612cda565b600091825260209091206002600590920201015580611d6e81612d06565b915050611d03565b505050565b607054606f5410611db857607354606d6000828254611d9a9190612d74565b9091555050606f54611db490678ac7230489e80000612905565b606f555b6069546040516331a9108f60e11b8152600481018490526001600160a01b03838116921690636352211e90602401602060405180830381865afa158015611e03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e279190612de3565b6001600160a01b031614611e7d5760405162461bcd60e51b815260206004820152601e60248201527f596f7520617265206e6f742061206f776e6572206f6620746865206e6674000060448201526064016104e6565b60695460405163e985e9c560e01b81523360048201523060248201526001600160a01b039091169063e985e9c590604401602060405180830381865afa158015611ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eef9190612d3a565b1515600114611f575760405162461bcd60e51b815260206004820152602e60248201527f596f752073686f756c6420617070726f7665206e667420746f2074686520737460448201526d185ada5b99c818dbdb9d1c9858dd60921b60648201526084016104e6565b6069546040516323b872dd60e01b81526001600160a01b03838116600483015230602483015260448201859052909116906323b872dd90606401600060405180830381600087803b158015611fab57600080fd5b505af1158015611fbf573d6000803e3d6000fd5b505050506000611fce82611594565b50905060006040518060a001604052808581526020014281526020014281526020014281526020014281525090506120126001606c5461215490919063ffffffff16565b606c556001600160a01b03831660009081526074602090815260408083208054600181810183559185529383902085516005909502019384559184015191830191909155820151600282015560608201516003820155608082015160049091015581611230576001600160a01b0383166000908152607760205260409020805460ff1916600117905550505050565b6000826120b057506000612123565b60006120bc8385612dc4565b9050826120c98583612d8b565b146121205760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b90505b92915050565b60008064e8d4a510008310158015612146575064e8d4a510058311155b156121235750600192915050565b6000806121618385612d5c565b9050838110156121205760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b600080610e7483116121c757506001612123565b50600092915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6067546000906122328342612d74565b6121239190612d8b565b600054610100900460ff166122635760405162461bcd60e51b81526004016104e690612e00565b61226b612947565b610d2c61296e565b61227c33612470565b61228533610d2e565b610d2c5761229233611c73565b610d2c336125aa565b60005b6001600160a01b038316600090815260746020526040902054811015612428576001600160a01b03831660009081526074602052604090208054839190839081106122eb576122eb612cda565b9060005260206000209060050201600001541415612416576001600160a01b0383166000908152607460205260409020805461232990600190612d74565b8154811061233957612339612cda565b906000526020600020906005020160746000856001600160a01b03166001600160a01b03168152602001908152602001600020828154811061237d5761237d612cda565b60009182526020808320845460059093020191825560018085015490830155600280850154908301556003808501549083015560049384015493909101929092556001600160a01b03851681526074909152604090208054806123e2576123e2612e4b565b6000828152602081206005600019909301928302018181556001810182905560028101829055600381018290556004015590555b8061242081612d06565b91505061229e565b506001600160a01b0382166000908152607460205260409020546105cd576001600160a01b038216600090815260746020526040812061246791612a3f565b6105cd8261299e565b600061247b826116e1565b509091505080156105cd57606a5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af11580156124d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fd9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b038316600090815260746020526040902080544291908390811061254e5761254e612cda565b60009182526020909120600460059092020101558061256c81612d06565b915050612501565b60006509184e72a00682101561258c57506000919050565b6509184e72b4b38211156125a257506000919050565b506001919050565b60006125b5826116e1565b5091505080156105cd57606b5460655460405163095ea7b360e01b81526001600160a01b0391821660048201526024810184905291169063095ea7b3906044016020604051808303816000875af1158015612614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126389190612d3a565b5060408051600280825260608083018452926020830190803683375050606b5482519293506001600160a01b03169183915060009061267957612679612cda565b6001600160a01b0392831660209182029290920101526066548251911690829060019081106126aa576126aa612cda565b6001600160a01b03928316602091820292909201015260655460405163791ac94760e01b815291169063791ac947906126f0908590600090869089904290600401612e9a565b600060405180830381600087803b15801561270a57600080fd5b505af115801561271e573d6000803e3d6000fd5b5050505060005b6001600160a01b038416600090815260746020526040902054811015611230576001600160a01b038416600090815260746020526040902080544291908390811061277257612772612cda565b60009182526020909120600360059092020101558061279081612d06565b915050612725565b600061212083836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506129d7565b6040805160028082526060808301845260009390929190602083019080368337019050509050848160008151811061281457612814612cda565b60200260200101906001600160a01b031690816001600160a01b031681525050838160018151811061284857612848612cda565b6001600160a01b03928316602091820292909201015260655460405163d06ca61f60e01b8152600092919091169063d06ca61f9061288c9087908690600401612ed6565b600060405180830381865afa1580156128a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128d19190810190612eef565b905080600183516128e29190612d74565b815181106128f2576128f2612cda565b6020026020010151925050509392505050565b600061212083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612a0e565b600054610100900460ff16610d2c5760405162461bcd60e51b81526004016104e690612e00565b600054610100900460ff166129955760405162461bcd60e51b81526004016104e690612e00565b610d2c336121d0565b6000806129aa83611594565b915091508115611d76576001600160a01b03166000908152607760205260409020805460ff191690555050565b600081836129f85760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d8b565b95945050505050565b60008184841115612a325760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d74565b508054600082556005029060005260206000209081019061057b91905b80821115612a8b5760008082556001820181905560028201819055600382018190556004820155600501612a5c565b5090565b6001600160a01b038116811461057b57600080fd5b600060208284031215612ab657600080fd5b813561212081612a8f565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612b0057612b00612ac1565b604052919050565b600067ffffffffffffffff821115612b2257612b22612ac1565b5060051b60200190565b60006020808385031215612b3f57600080fd5b823567ffffffffffffffff811115612b5657600080fd5b8301601f81018513612b6757600080fd5b8035612b7a612b7582612b08565b612ad7565b81815260059190911b82018301908381019087831115612b9957600080fd5b928401925b82841015612bb757833582529284019290840190612b9e565b979650505050505050565b600060208284031215612bd457600080fd5b5035919050565b600081518084526020808501945080840160005b83811015612c0b57815187529582019590820190600101612bef565b509495945050505050565b82815260406020820152600061158c6040830184612bdb565b6020815260006121206020830184612bdb565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526017908201527f41646472657373206973206e6f7420636f6e7472616374000000000000000000604082015260600190565b6020808252601290820152712cb7ba9030b932903737ba1039ba30b5b2b960711b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d1a57612d1a612cf0565b5060010190565b600060208284031215612d3357600080fd5b5051919050565b600060208284031215612d4c57600080fd5b8151801515811461212057600080fd5b60008219821115612d6f57612d6f612cf0565b500190565b600082821015612d8657612d86612cf0565b500390565b600082612da857634e487b7160e01b600052601260045260246000fd5b500490565b600081612dbc57612dbc612cf0565b506000190190565b6000816000190483118215151615612dde57612dde612cf0565b500290565b600060208284031215612df557600080fd5b815161212081612a8f565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b600081518084526020808501945080840160005b83811015612c0b5781516001600160a01b031687529582019590820190600101612e75565b85815284602082015260a060408201526000612eb960a0830186612e61565b6001600160a01b0394909416606083015250608001529392505050565b82815260406020820152600061158c6040830184612e61565b60006020808385031215612f0257600080fd5b825167ffffffffffffffff811115612f1957600080fd5b8301601f81018513612f2a57600080fd5b8051612f38612b7582612b08565b81815260059190911b82018301908381019087831115612f5757600080fd5b928401925b82841015612bb757835182529284019290840190612f5c565b600060208083528351808285015260005b81811015612fa257858101830151858201604001528201612f86565b81811115612fb4576000604083870101525b50601f01601f191692909201604001939250505056fe", + ), + ( + "0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545", + "GovernorCharlieDelegate", + "608060405234801561001057600080fd5b50613e45806100206000396000f3fe60806040526004361061031a5760003560e01c80637b3c71d3116101ab578063d50572ee116100f7578063f0843ba811610095578063fc176c041161006f578063fc176c0414610b82578063fc4eee4214610ba2578063fc66ff1414610bb8578063fe0d94c114610bd857600080fd5b8063f0843ba814610b12578063f2b0653714610b32578063f682e04c14610b6257600080fd5b8063de7bc127116100d1578063de7bc127146109ec578063deaaa7cc14610a02578063e23a9a5214610a36578063e837159c14610afc57600080fd5b8063d50572ee146109a0578063da35c664146109b6578063ddf0b009146109cc57600080fd5b8063a6d8784a11610164578063c1a287e21161013e578063c1a287e214610933578063c4d66de81461094a578063c5a8425d1461096a578063c9fb9e871461098a57600080fd5b8063a6d8784a146108e7578063abaac6a8146108fd578063b58131b01461091d57600080fd5b80637b3c71d31461083c5780637bdbe4d01461085c5780637cae57bb14610871578063806bd5811461088757806386d37e8b146108a757806399533365146108c757600080fd5b80632fedff591161026a5780633e4f49e61161022357806350442098116101fd578063504420981461074657806356781388146107665780635c60da1b1461078657806366176743146107be57600080fd5b80633e4f49e6146106d957806340e58ee5146107065780634d6733d21461072657600080fd5b80632fedff59146105ee578063328dd9821461060e57806338bd0dda1461063e5780633932abb11461066b5780633af32abf146106815780633bccf4fd146106b957600080fd5b8063158ef93e116102d757806318b62629116102b157806318b626291461056e5780631dfb1b5a1461058457806320606b70146105a457806324bc1a64146105d857600080fd5b8063158ef93e146104f757806317977c611461052157806317ba1b8b1461054e57600080fd5b8063013cf08b1461031f57806302a251a31461042857806306fdde031461044c5780630825f38f146104a25780630ea2d98c146104b7578063140499ea146104d7575b600080fd5b34801561032b57600080fd5b506103b361033a3660046132ee565b60096020819052600091825260409091208054600182015460028301546007840154600885015495850154600a860154600b870154600c880154600d890154600e9099015497996001600160a01b0390971698959794969593949293919260ff808316936101008404821693620100009004909116918d565b604080519d8e526001600160a01b03909c1660208e01529a8c019990995260608b019790975260808a019590955260a089019390935260c088019190915260e08701521515610100860152151561012085015215156101408401526101608301526101808201526101a0015b60405180910390f35b34801561043457600080fd5b5061043e60045481565b60405190815260200161041f565b34801561045857600080fd5b506104956040518060400160405280601a81526020017f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000081525081565b60405161041f9190613363565b6104b56104b0366004613450565b610beb565b005b3480156104c357600080fd5b506104b56104d23660046132ee565b610e61565b3480156104e357600080fd5b506104b56104f23660046134d6565b610ec6565b34801561050357600080fd5b506012546105119060ff1681565b604051901515815260200161041f565b34801561052d57600080fd5b5061043e61053c3660046134d6565b600a6020526000908152604090205481565b34801561055a57600080fd5b506104b56105693660046132ee565b610f07565b34801561057a57600080fd5b5061043e600f5481565b34801561059057600080fd5b506104b561059f3660046132ee565b610f64565b3480156105b057600080fd5b5061043e7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b3480156105e457600080fd5b5061043e60015481565b3480156105fa57600080fd5b506104b56106093660046132ee565b610fc1565b34801561061a57600080fd5b5061062e6106293660046132ee565b61101e565b60405161041f94939291906135ba565b34801561064a57600080fd5b5061043e6106593660046134d6565b600d6020526000908152604090205481565b34801561067757600080fd5b5061043e60035481565b34801561068d57600080fd5b5061051161069c3660046134d6565b6001600160a01b03166000908152600d6020526040902054421090565b3480156106c557600080fd5b506104b56106d4366004613623565b6112af565b3480156106e557600080fd5b506106f96106f43660046132ee565b611516565b60405161041f9190613687565b34801561071257600080fd5b506104b56107213660046132ee565b61169e565b34801561073257600080fd5b506104b56107413660046136af565b611b80565b34801561075257600080fd5b506104b56107613660046132ee565b611c45565b34801561077257600080fd5b506104b56107813660046136d9565b611ca2565b34801561079257600080fd5b506000546107a6906001600160a01b031681565b6040516001600160a01b03909116815260200161041f565b3480156107ca57600080fd5b506108146107d9366004613705565b601160209081526000928352604080842090915290825290205460ff808216916101008104909116906201000090046001600160601b031683565b60408051931515845260ff90921660208401526001600160601b03169082015260600161041f565b34801561084857600080fd5b506104b5610857366004613728565b611d09565b34801561086857600080fd5b5061043e600a81565b34801561087d57600080fd5b5061043e600c5481565b34801561089357600080fd5b506104b56108a23660046132ee565b611d58565b3480156108b357600080fd5b506104b56108c23660046132ee565b611db5565b3480156108d357600080fd5b506104b56108e23660046134d6565b611e12565b3480156108f357600080fd5b5061043e60155481565b34801561090957600080fd5b506104b56109183660046132ee565b611e8b565b34801561092957600080fd5b5061043e60055481565b34801561093f57600080fd5b5061043e6212750081565b34801561095657600080fd5b506104b56109653660046134d6565b611ee8565b34801561097657600080fd5b50600e546107a6906001600160a01b031681565b34801561099657600080fd5b5061043e60135481565b3480156109ac57600080fd5b5061043e60025481565b3480156109c257600080fd5b5061043e60075481565b3480156109d857600080fd5b506104b56109e73660046132ee565b611fd9565b3480156109f857600080fd5b5061043e60105481565b348015610a0e57600080fd5b5061043e7f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f81565b348015610a4257600080fd5b50610acc610a51366004613705565b60408051606081018252600080825260208201819052918101919091525060009182526011602090815260408084206001600160a01b03939093168452918152918190208151606081018352905460ff8082161515835261010082041693820193909352620100009092046001600160601b03169082015290565b6040805182511515815260208084015160ff1690820152918101516001600160601b03169082015260600161041f565b348015610b0857600080fd5b5061043e60145481565b348015610b1e57600080fd5b506104b5610b2d3660046132ee565b61238f565b348015610b3e57600080fd5b50610511610b4d3660046132ee565b600b6020526000908152604090205460ff1681565b348015610b6e57600080fd5b5061043e610b7d3660046139b0565b6123ec565b348015610b8e57600080fd5b506104b5610b9d3660046132ee565b612a48565b348015610bae57600080fd5b5061043e60065481565b348015610bc457600080fd5b506008546107a6906001600160a01b031681565b6104b5610be63660046132ee565b612aa5565b60008585858585604051602001610c06959493929190613a91565b60408051601f1981840301815291815281516020928301206000818152600b90935291205490915060ff16610c7b5760405162461bcd60e51b81526020600482015260166024820152753a3c103430b9b713ba103132b2b71038bab2bab2b21760511b60448201526064015b60405180910390fd5b81421015610ccb5760405162461bcd60e51b815260206004820152601d60248201527f7478206861736e2774207375727061737365642074696d656c6f636b2e0000006044820152606401610c72565b610cd86212750083613af3565b421115610d165760405162461bcd60e51b815260206004820152600c60248201526b3a3c1034b99039ba30b6329760a11b6044820152606401610c72565b6000818152600b60205260409020805460ff191690558351606090610d3c575082610d68565b848051906020012084604051602001610d56929190613b0b565b60405160208183030381529060405290505b6000876001600160a01b03168783604051610d839190613b3c565b60006040518083038185875af1925050503d8060008114610dc0576040519150601f19603f3d011682016040523d82523d6000602084013e610dc5565b606091505b5050905080610e0f5760405162461bcd60e51b81526020600482015260166024820152753a3c1032bc32b1baba34b7b7103932bb32b93a32b21760511b6044820152606401610c72565b876001600160a01b0316837fa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e789898989604051610e4f9493929190613b58565b60405180910390a35050505050505050565b333014610e805760405162461bcd60e51b8152600401610c7290613b95565b600480549082905560408051828152602081018490527f7e3f7f0708a84de9203036abaa450dccc85ad5ff52f78c170f3edb55cf5e882891015b60405180910390a15050565b333014610ee55760405162461bcd60e51b8152600401610c7290613b95565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610f265760405162461bcd60e51b8152600401610c7290613b95565b600580549082905560408051828152602081018490527fccb45da8d5717e6c4544694297c4ba5cf151d455c9bb0ed4fc7a38411bc054619101610eba565b333014610f835760405162461bcd60e51b8152600401610c7290613b95565b600380549082905560408051828152602081018490527fc565b045403dc03c2eea82b81a0465edad9e2e7fc4d97e11421c209da93d7a939101610eba565b333014610fe05760405162461bcd60e51b8152600401610c7290613b95565b601480549082905560408051828152602081018490527f519a192fe8db9e38785eb494c69f530ddb21b9e34322f8d08fe29bd3849749889101610eba565b606080606080600060096000878152602001908152602001600020905080600301816004018260050183600601838054806020026020016040519081016040528092919081815260200182805480156110a057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611082575b50505050509350828054806020026020016040519081016040528092919081815260200182805480156110f257602002820191906000526020600020905b8154815260200190600101908083116110de575b5050505050925081805480602002602001604051908101604052809291908181526020016000905b828210156111c657838290600052602060002001805461113990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461116590613bcc565b80156111b25780601f10611187576101008083540402835291602001916111b2565b820191906000526020600020905b81548152906001019060200180831161119557829003601f168201915b50505050508152602001906001019061111a565b50505050915080805480602002602001604051908101604052809291908181526020016000905b8282101561129957838290600052602060002001805461120c90613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461123890613bcc565b80156112855780601f1061125a57610100808354040283529160200191611285565b820191906000526020600020905b81548152906001019060200180831161126857829003601f168201915b5050505050815260200190600101906111ed565b5050505090509450945094509450509193509193565b604080518082018252601a81527f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000060209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f75a838dcd8ee5903cc7f4a5799344d0080864f57a6e9911f8bdfb4c8ddce9b5481840152466060820152306080808301919091528351808303909101815260a0820184528051908301207f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f60c083015260e0820189905260ff8816610100808401919091528451808403909101815261012083019094528351939092019290922061190160f01b6101408401526101428301829052610162830181905290916000906101820160408051601f198184030181528282528051602091820120600080855291840180845281905260ff8a169284019290925260608301889052608083018790529092509060019060a0016020604051602081039080840390855afa15801561143c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661149f5760405162461bcd60e51b815260206004820181905260248201527f63617374566f746542795369673a20696e76616c6964207369676e61747572656044820152606401610c72565b88816001600160a01b03167fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda48a6114d7858e8e612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a3505050505050505050565b6000816007541015801561152b575060065482115b6115775760405162461bcd60e51b815260206004820152601a60248201527f73746174653a20696e76616c69642070726f706f73616c2069640000000000006044820152606401610c72565b600082815260096020908152604080832060018101546001600160a01b03168452600d90925290912054600c82015442919091109060ff16156115be575060029392505050565b816007015443116115d3575060009392505050565b816008015443116115e8575060019392505050565b8080156115fc575081600d015482600a0154115b80611618575080158015611618575081600a0154826009015411155b80611633575080158015611633575081600d01548260090154105b15611642575060039392505050565b6002820154611655575060049392505050565b600c820154610100900460ff1615611671575060079392505050565b6212750082600201546116849190613af3565b4210611694575060069392505050565b5060059392505050565b60076116a982611516565b60078111156116ba576116ba613671565b14156117085760405162461bcd60e51b815260206004820152601d60248201527f63616e742063616e63656c2065786563757465642070726f706f73616c0000006044820152606401610c72565b600081815260096020526040902060018101546001600160a01b0316336001600160a01b0316146119755760018101546001600160a01b03166000908152600d6020526040902054421015611878576005546008546001838101546001600160a01b039283169263782d6fe1929116906117829043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613c1e565b6001600160601b03161080156118275750600e546001600160a01b0316336001600160a01b0316145b6118735760405162461bcd60e51b815260206004820152601c60248201527f63616e63656c3a2077686974656c69737465642070726f706f736572000000006044820152606401610c72565b611975565b6005546008546001838101546001600160a01b039283169263782d6fe1929116906118a39043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156118e757600080fd5b505afa1580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190613c1e565b6001600160601b0316106119755760405162461bcd60e51b815260206004820181905260248201527f63616e63656c3a2070726f706f7365722061626f7665207468726573686f6c646044820152606401610c72565b600c8101805460ff1916600117905560005b6003820154811015611b5057611b3e8260030182815481106119ab576119ab613c47565b6000918252602090912001546004840180546001600160a01b0390921691849081106119d9576119d9613c47565b90600052602060002001548460050184815481106119f9576119f9613c47565b906000526020600020018054611a0e90613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611a3a90613bcc565b8015611a875780601f10611a5c57610100808354040283529160200191611a87565b820191906000526020600020905b815481529060010190602001808311611a6a57829003601f168201915b5050505050856006018581548110611aa157611aa1613c47565b906000526020600020018054611ab690613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611ae290613bcc565b8015611b2f5780601f10611b0457610100808354040283529160200191611b2f565b820191906000526020600020905b815481529060010190602001808311611b1257829003601f168201915b50505050508660020154612f12565b80611b4881613c5d565b915050611987565b5060405182907f789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c90600090a25050565b333014611b9f5760405162461bcd60e51b8152600401610c7290613b95565b42601554611bad9190613af3565b8110611bf45760405162461bcd60e51b81526020600482015260166024820152750caf0e0d2e4c2e8d2dedc40caf0c6cacac8e640dac2f60531b6044820152606401610c72565b6001600160a01b0382166000818152600d6020908152604091829020849055815192835282018390527f4e7b7545bc5744d0e30425959f4687475774b6c7edad77d24cb51c7d967d45159101610eba565b333014611c645760405162461bcd60e51b8152600401610c7290613b95565b601080549082905560408051828152602081018490527f2a61b867418a359864adca8bb250ea65ee8bd41dbfd0279198d8e7552d4a27c29101610eba565b81337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda483611cd1838583612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a35050565b83337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda485611d38838583612c90565b8686604051611d4a9493929190613c78565b60405180910390a350505050565b333014611d775760405162461bcd60e51b8152600401610c7290613b95565b601380549082905560408051828152602081018490527f8cb5451eee8feb516cec9cd600201bbc31a30886d70c841a085a3fa69a4294d19101610eba565b333014611dd45760405162461bcd60e51b8152600401610c7290613b95565b600180549082905560408051828152602081018490527fa74554b0f53da47d07ec571d712428b3720460f54f81375fbcf78f6b5f72e7ed9101610eba565b333014611e315760405162461bcd60e51b8152600401610c7290613b95565b600e80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f80a07e73e552148844a9c216d9724212d609cfa54e9c1a2e97203bdd2c4ad3419101610eba565b333014611eaa5760405162461bcd60e51b8152600401610c7290613b95565b600f80549082905560408051828152602081018490527f80a384652af83fc00bfd40ef94edda7ede83e7db39931b2c889821573f314e239101610eba565b60125460ff1615611f3b5760405162461bcd60e51b815260206004820152601860248201527f616c7265616479206265656e20696e697469616c697a656400000000000000006044820152606401610c72565b600880546001600160a01b0319166001600160a01b0392909216919091179055619d8060045561335460035569d3c21bcecceda10000006005556202a300600c5560006007556a084595161401484a00000060019081556a21165458500521280000006002556119aa600f5561a8c06010556a01a784379d99db420000006013556146506014556301e133806015556012805460ff19169091179055565b6004611fe482611516565b6007811115611ff557611ff5613671565b146120425760405162461bcd60e51b815260206004820152601f60248201527f63616e206f6e6c792062652071756575656420696620737563636565646564006044820152606401610c72565b6000818152600960205260408120600e8101549091906120629042613af3565b905060005b600383015481101561234d57600b600084600301838154811061208c5761208c613c47565b6000918252602090912001546004860180546001600160a01b0390921691859081106120ba576120ba613c47565b90600052602060002001548660050185815481106120da576120da613c47565b906000526020600020018760060186815481106120f9576120f9613c47565b9060005260206000200187604051602001612118959493929190613d62565b60408051601f198184030181529181528151602092830120835290820192909252016000205460ff161561218e5760405162461bcd60e51b815260206004820152601760248201527f70726f706f73616c20616c7265616479207175657565640000000000000000006044820152606401610c72565b61233a8360030182815481106121a6576121a6613c47565b6000918252602090912001546004850180546001600160a01b0390921691849081106121d4576121d4613c47565b90600052602060002001548560050184815481106121f4576121f4613c47565b90600052602060002001805461220990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461223590613bcc565b80156122825780601f1061225757610100808354040283529160200191612282565b820191906000526020600020905b81548152906001019060200180831161226557829003601f168201915b505050505086600601858154811061229c5761229c613c47565b9060005260206000200180546122b190613bcc565b80601f01602080910402602001604051908101604052809291908181526020018280546122dd90613bcc565b801561232a5780601f106122ff5761010080835404028352916020019161232a565b820191906000526020600020905b81548152906001019060200180831161230d57829003601f168201915b50505050508688600e0154612fac565b508061234581613c5d565b915050612067565b506002820181905560405181815283907f9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda28929060200160405180910390a2505050565b3330146123ae5760405162461bcd60e51b8152600401610c7290613b95565b600280549082905560408051828152602081018490527fc2adf06da6765dba7faaccde4c0ce3f91c35dd3390e7f0b6bc2844202c9fa9529101610eba565b6000600154600014156124365760405162461bcd60e51b8152602060048201526012602482015271436861726c6965206e6f742061637469766560701b6044820152606401610c72565b6005546008546001600160a01b031663782d6fe133612456600143613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b15801561249a57600080fd5b505afa1580156124ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d29190613c1e565b6001600160601b03161015806124ec57506124ec3361069c565b6125385760405162461bcd60e51b815260206004820152601e60248201527f766f7465732062656c6f772070726f706f73616c207468726573686f6c6400006044820152606401610c72565b8551875114801561254a575084518751145b8015612557575083518751145b6125a35760405162461bcd60e51b815260206004820152601a60248201527f696e666f726d6174696f6e206172697479206d69736d617463680000000000006044820152606401610c72565b86516125e85760405162461bcd60e51b81526020600482015260146024820152736d7573742070726f7669646520616374696f6e7360601b6044820152606401610c72565b600a8751111561262d5760405162461bcd60e51b815260206004820152601060248201526f746f6f206d616e7920616374696f6e7360801b6044820152606401610c72565b336000908152600a6020526040902054801561271657600061264e82611516565b9050600181600781111561266457612664613671565b14156126b25760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b60008160078111156126c6576126c6613671565b14156127145760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b505b6007805490600061272683613c5d565b9190505550600060405180610220016040528060075481526020016127483390565b6001600160a01b03168152602001600081526020018a8152602001898152602001888152602001878152602001600354436127839190613af3565b8152602001600454600354436127999190613af3565b6127a39190613af3565b815260200160008152602001600081526020016000815260200160001515815260200160001515815260200185151581526020016001548152602001600c5481525090508380156127fa57506127f83361069c565b155b1561282c574360e08201819052600f5461281391613af3565b6101008201526002546101e08201526010546102008201525b6128353361069c565b15612876576013546101e08201526014546128509043613af3565b60e08201526004546014546128659043613af3565b61286f9190613af3565b6101008201525b805160009081526009602090815260409182902083518155818401516001820180546001600160a01b0319166001600160a01b03909216919091179055918301516002830155606083015180518493926128d792600385019291019061309d565b50608082015180516128f3916004840191602090910190613102565b5060a0820151805161290f91600584019160209091019061313d565b5060c0820151805161292b916006840191602090910190613196565b5060e08281015160078301556101008084015160088401556101208401516009840155610140840151600a80850191909155610160850151600b850155610180850151600c850180546101a08801516101c089015161ffff1990921693151561ff0019169390931792151585029290921762ff0000191662010000921515929092029190911790556101e0850151600d85015561020090940151600e9093019290925583516020808601516001600160a01b0316600090815294905260409384902055830151835191840151925190923392917f7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e091612a33918f918f918f918f918f90613d9b565b60405180910390a45198975050505050505050565b333014612a675760405162461bcd60e51b8152600401610c7290613b95565b600c80549082905560408051828152602081018490527fed0229422af39d4d7d33f7a27d31d6f5cb20ec628293da58dd6e8a528ed466be9101610eba565b6005612ab082611516565b6007811115612ac157612ac1613671565b14612b0e5760405162461bcd60e51b815260206004820152601c60248201527f63616e206f6e6c792062652065786563276420696620717565756564000000006044820152606401610c72565b6000818152600960205260408120600c8101805461ff001916610100179055905b6003820154811015612c6057306001600160a01b0316630825f38f836004018381548110612b5f57612b5f613c47565b9060005260206000200154846003018481548110612b7f57612b7f613c47565b6000918252602090912001546004860180546001600160a01b039092169186908110612bad57612bad613c47565b9060005260206000200154866005018681548110612bcd57612bcd613c47565b90600052602060002001876006018781548110612bec57612bec613c47565b9060005260206000200188600201546040518763ffffffff1660e01b8152600401612c1b959493929190613d62565b6000604051808303818588803b158015612c3457600080fd5b505af1158015612c48573d6000803e3d6000fd5b50505050508080612c5890613c5d565b915050612b2f565b5060405182907f712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f90600090a25050565b60006001612c9d84611516565b6007811115612cae57612cae613671565b14612cee5760405162461bcd60e51b815260206004820152601060248201526f1d9bdd1a5b99c81a5cc818db1bdcd95960821b6044820152606401610c72565b60028260ff161115612d365760405162461bcd60e51b8152602060048201526011602482015270696e76616c696420766f7465207479706560781b6044820152606401610c72565b6000838152600960209081526040808320601183528184206001600160a01b0389168552909252909120805460ff1615612da85760405162461bcd60e51b81526020600482015260136024820152721d9bdd195c88185b1c9958591e481d9bdd1959606a1b6044820152606401610c72565b600854600783015460405163782d6fe160e01b81526000926001600160a01b03169163782d6fe191612df2918b916004016001600160a01b03929092168252602082015260400190565b60206040518083038186803b158015612e0a57600080fd5b505afa158015612e1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e429190613c1e565b905060ff8516612e6f57806001600160601b031683600a0154612e659190613af3565b600a840155612ec9565b8460ff1660011415612e9e57806001600160601b03168360090154612e949190613af3565b6009840155612ec9565b8460ff1660021415612ec957806001600160601b031683600b0154612ec39190613af3565b600b8401555b81546001600160601b03821662010000026dffffffffffffffffffffffff00001960ff88166101000261ffff199093169290921760011791909116179091559150509392505050565b60008585858585604051602001612f2d959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916905591506001600160a01b0387169082907f2fffc091a501fd91bfbff27141450d3acb40fb8e6d8382b243ec7a812a3aaf8790612f9c908990899089908990613b58565b60405180910390a3505050505050565b6000612fb88242613af3565b831015612ffd5760405162461bcd60e51b815260206004820152601360248201527236bab9ba1039b0ba34b9b33c903232b630bc9760691b6044820152606401610c72565b60008787878787604051602001613018959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916600117905591506001600160a01b0389169082907f76e2796dc3a81d57b0e8504b647febcbeeb5f4af818e164f11eef8131a6a763f9061308a908b908b908b908b90613b58565b60405180910390a3979650505050505050565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906130bd565b506130fe9291506131ef565b5090565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f2578251825591602001919060010190613122565b82805482825590600052602060002090810192821561318a579160200282015b8281111561318a578251805161317a918491602090910190613204565b509160200191906001019061315d565b506130fe929150613277565b8280548282559060005260206000209081019282156131e3579160200282015b828111156131e357825180516131d3918491602090910190613204565b50916020019190600101906131b6565b506130fe929150613294565b5b808211156130fe57600081556001016131f0565b82805461321090613bcc565b90600052602060002090601f01602090048101928261323257600085556130f2565b82601f1061324b57805160ff19168380011785556130f2565b828001600101855582156130f257918201828111156130f2578251825591602001919060010190613122565b808211156130fe57600061328b82826132b1565b50600101613277565b808211156130fe5760006132a882826132b1565b50600101613294565b5080546132bd90613bcc565b6000825580601f106132cd575050565b601f0160209004906000526020600020908101906132eb91906131ef565b50565b60006020828403121561330057600080fd5b5035919050565b60005b8381101561332257818101518382015260200161330a565b83811115613331576000848401525b50505050565b6000815180845261334f816020860160208601613307565b601f01601f19169290920160200192915050565b6020815260006133766020830184613337565b9392505050565b80356001600160a01b038116811461339457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156133d8576133d8613399565b604052919050565b600082601f8301126133f157600080fd5b813567ffffffffffffffff81111561340b5761340b613399565b61341e601f8201601f19166020016133af565b81815284602083860101111561343357600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561346857600080fd5b6134718661337d565b945060208601359350604086013567ffffffffffffffff8082111561349557600080fd5b6134a189838a016133e0565b945060608801359150808211156134b757600080fd5b506134c4888289016133e0565b95989497509295608001359392505050565b6000602082840312156134e857600080fd5b6133768261337d565b600081518084526020808501945080840160005b8381101561352a5781516001600160a01b031687529582019590820190600101613505565b509495945050505050565b600081518084526020808501945080840160005b8381101561352a57815187529582019590820190600101613549565b600081518084526020808501808196508360051b8101915082860160005b858110156135ad57828403895261359b848351613337565b98850198935090840190600101613583565b5091979650505050505050565b6080815260006135cd60808301876134f1565b82810360208401526135df8187613535565b905082810360408401526135f38186613565565b905082810360608401526136078185613565565b979650505050505050565b803560ff8116811461339457600080fd5b600080600080600060a0868803121561363b57600080fd5b8535945061364b60208701613612565b935061365960408701613612565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052602160045260246000fd5b60208101600883106136a957634e487b7160e01b600052602160045260246000fd5b91905290565b600080604083850312156136c257600080fd5b6136cb8361337d565b946020939093013593505050565b600080604083850312156136ec57600080fd5b823591506136fc60208401613612565b90509250929050565b6000806040838503121561371857600080fd5b823591506136fc6020840161337d565b6000806000806060858703121561373e57600080fd5b8435935061374e60208601613612565b9250604085013567ffffffffffffffff8082111561376b57600080fd5b818701915087601f83011261377f57600080fd5b81358181111561378e57600080fd5b8860208285010111156137a057600080fd5b95989497505060200194505050565b600067ffffffffffffffff8211156137c9576137c9613399565b5060051b60200190565b600082601f8301126137e457600080fd5b813560206137f96137f4836137af565b6133af565b82815260059290921b8401810191818101908684111561381857600080fd5b8286015b8481101561383a5761382d8161337d565b835291830191830161381c565b509695505050505050565b600082601f83011261385657600080fd5b813560206138666137f4836137af565b82815260059290921b8401810191818101908684111561388557600080fd5b8286015b8481101561383a5780358352918301918301613889565b600082601f8301126138b157600080fd5b813560206138c16137f4836137af565b82815260059290921b840181019181810190868411156138e057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139045760008081fd5b6139128986838b01016133e0565b8452509183019183016138e4565b600082601f83011261393157600080fd5b813560206139416137f4836137af565b82815260059290921b8401810191818101908684111561396057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139845760008081fd5b6139928986838b01016133e0565b845250918301918301613964565b8035801515811461339457600080fd5b60008060008060008060c087890312156139c957600080fd5b863567ffffffffffffffff808211156139e157600080fd5b6139ed8a838b016137d3565b97506020890135915080821115613a0357600080fd5b613a0f8a838b01613845565b96506040890135915080821115613a2557600080fd5b613a318a838b016138a0565b95506060890135915080821115613a4757600080fd5b613a538a838b01613920565b94506080890135915080821115613a6957600080fd5b50613a7689828a016133e0565b925050613a8560a088016139a0565b90509295509295509295565b60018060a01b038616815284602082015260a060408201526000613ab860a0830186613337565b8281036060840152613aca8186613337565b9150508260808301529695505050505050565b634e487b7160e01b600052601160045260246000fd5b60008219821115613b0657613b06613add565b500190565b6001600160e01b0319831681528151600090613b2e816004850160208701613307565b919091016004019392505050565b60008251613b4e818460208701613307565b9190910192915050565b848152608060208201526000613b716080830186613337565b8281036040840152613b838186613337565b91505082606083015295945050505050565b60208082526017908201527f6d75737420636f6d652066726f6d2074686520676f762e000000000000000000604082015260600190565b600181811c90821680613be057607f821691505b60208210811415613c0157634e487b7160e01b600052602260045260246000fd5b50919050565b600082821015613c1957613c19613add565b500390565b600060208284031215613c3057600080fd5b81516001600160601b038116811461337657600080fd5b634e487b7160e01b600052603260045260246000fd5b6000600019821415613c7157613c71613add565b5060010190565b60ff851681526001600160601b038416602082015260606040820152816060820152818360808301376000818301608090810191909152601f909201601f191601019392505050565b8054600090600181811c9080831680613cdb57607f831692505b6020808410821415613cfd57634e487b7160e01b600052602260045260246000fd5b838852818015613d145760018114613d2857613d56565b60ff19861689830152604089019650613d56565b876000528160002060005b86811015613d4e5781548b8201850152908501908301613d33565b8a0183019750505b50505050505092915050565b60018060a01b038616815284602082015260a060408201526000613d8960a0830186613cc1565b8281036060840152613aca8186613cc1565b60c081526000613dae60c08301896134f1565b8281036020840152613dc08189613535565b90508281036040840152613dd48188613565565b90508281036060840152613de88187613565565b905084608084015282810360a0840152613e028185613337565b999850505050505050505056fe", + ), + ( + "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", + "PoolExercise", + "6101c06040523480156200001257600080fd5b50604051620030713803806200307183398101604081905262000035916200016a565b6001600160a01b038681166101005285811660805284811660a05283811660c052821660e052600f81900b61012052858585858585620000846000808062000101602090811b6200011917901c565b6101408181525050620000a660016000806200010160201b620001191760201c565b6101608181525050620000c860026000806200010160201b620001191760201c565b6101808181525050620000ea60036000806200010160201b620001191760201c565b6101a05250620002319a5050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200012c576200012c620001f4565b6200013992911b6200020a565b6200014591906200020a565b949350505050565b80516001600160a01b03811681146200016557600080fd5b919050565b60008060008060008060c087890312156200018457600080fd5b6200018f876200014d565b95506200019f602088016200014d565b9450620001af604088016200014d565b9350620001bf606088016200014d565b9250620001cf608088016200014d565b915060a087015180600f0b8114620001e657600080fd5b809150509295509295509295565b634e487b7160e01b600052602160045260246000fd5b600082198211156200022c57634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051612d6f6200030260003960008181610e1c015261137b015260008181610e420152818161135101526113f4015260008181611327015281816114b801526122c90152600081816112fe015281816113cb0152818161148f015281816114e101526122ef0152600081816103a8015281816107ed0152610cda0152600050506000818161176601526117b201526000610485015260008181611964015261212c015260005050612d6f6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063477130981461003b578063b50e7ee314610050575b600080fd5b61004e610049366004612986565b610063565b005b61004e61005e3660046129c7565b610109565b336001600160a01b038416146100f9576001600160a01b03831660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff166100f95760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b61010483838361015f565b505050565b6101156000838361015f565b5050565b600081600f0b60808467ffffffffffffffff16901b60f8866007811115610142576101426129e9565b61014d92911b612a15565b6101579190612a15565b949350505050565b608082901c8260006001600160a01b0386161560f883901c600481600781111561018b5761018b6129e9565b14806101a8575060068160078111156101a6576101a66129e9565b145b6101e35760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964207479706560a01b60448201526064016100f0565b8115806101f95750428567ffffffffffffffff16105b6102335760405162461bcd60e51b815260206004820152600b60248201526a1b9bdd08195e1c1a5c995960aa1b60448201526064016100f0565b6004816007811115610247576102476129e9565b149250506000610262600080516020612d1a83398151915290565b9050600061026f826104c0565b9050428667ffffffffffffffff16101561029a576102978267ffffffffffffffff8816610526565b90505b82806102be5750836102b45784600f0b81600f0b126102be565b84600f0b81600f0b135b6102f45760405162461bcd60e51b81526020600482015260076024820152666e6f742049544d60c81b60448201526064016100f0565b6000841561033a5785600f0b82600f0b1315610335576103328861032984610320600f82900b8b61061e565b600f0b90610659565b600f0b906106b1565b90505b610367565b85600f0b82600f0b12156103675761036461035d89610329600f8a900b8661061e565b8490610719565b90505b6000841561038c5761037b89838c89610754565b6103859082612a15565b9050610454565b6103978b8b8b6108cb565b600082156103ff576103d58c6103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b866106b1565b610a5d565b90506103e18183612a15565b91506103ff8c6103f089610a8c565b6103fa8487612a2d565b610ae1565b604080518c8152602081018c9052908101849052606081018290526001600160a01b038d16907f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca19060800160405180910390a2505b610474898361046e6104678a6000610bb1565b8c8c610119565b89610be4565b61047e9082612a15565b90506104b37f00000000000000000000000000000000000000000000000000000000000000006104ad88610e13565b83610e67565b5050505050505050505050565b60004282600c015414156104de576104d88242610e82565b92915050565b6104e782610eb0565b90506104f38242610e82565b600f0b61050557610505824283610fd3565b42600c830155610516826001611051565b610521826000611051565b919050565b600080610535610e1084612a5a565b600881901c6000818152601287016020526040812054929350909160ff84169190821b821c90610568620e100042612a5a565b90505b811580156105795750808411155b156105a65760128801600061058d86612a7c565b955085815260200190815260200160002054915061056b565b600060805b80156105d25783811c156105ca576105c38183612a15565b93811c9391505b60011c6105ab565b5060118901600060018360086105e88a84612a15565b6105f392911b612a2d565b6105fd9190612a2d565b8152602081019190915260400160002054600f0b9998505050505050505050565b6000600f82810b9084900b0360016001607f1b03198112801590610649575060016001607f1b038113155b61065257600080fd5b9392505050565b600081600f0b6000141561066c57600080fd5b600082600f0b604085600f0b901b8161068757610687612a44565b05905060016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000816106c0575060006104d8565b600083600f0b12156106d157600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b0381111561070057600080fd5b60401b811981111561071157600080fd5b019392505050565b600080610737838560030160149054906101000a900460ff166110e1565b9050610157818560030160159054906101000a900460ff166110f7565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb602052604081205b85156108c25760006107a9600161079884611112565b6107a29190612a2d565b839061111c565b905060006107b78287611128565b9050878111156107c45750865b600080881561084657896107d8848b612a97565b6107e29190612a5a565b9150610815846103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b90506108218187612a15565b955061082d828a612a2d565b98506108468461083c89610a8c565b6103fa8486612a2d565b610850838b612a2d565b99506001600160a01b0384167f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca189856108898587612a2d565b604080519384526020840192909252908201526060810184905260800160405180910390a26108b98489856108cb565b50505050610782565b50949350505050565b6001600160a01b03831661092d5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016100f0565b61095b3384600061093d866111db565b610946866111db565b60405180602001604052806000815250611226565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156109fc5760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b60648201526084016100f0565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600080610a6984611762565b9050612710610a788285612a97565b610a829190612a5a565b6101579084612a2d565b600081610ab157600080516020612d1a833981519152546001600160a01b03166104d8565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b80610aeb57505050565b60405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb90604401602060405180830381600087803b158015610b3557600080fd5b505af1158015610b49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6d9190612ab6565b6101045760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016100f0565b60008215610bcf5781610bc5576005610bc8565b60045b90506104d8565b81610bdb576007610652565b60069392505050565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb60205260408120835b8615610e09576000610c3a6001610c2985611112565b610c339190612a2d565b849061111c565b90506000610c488288611128565b905088811115610c555750875b600089610c62838b612a97565b610c6c9190612a5a565b9050610c78818a612a2d565b9850610c84828b612a2d565b9950600087610cc35781610cb4610c9f600f88900b866106b1565b600080516020612d1a83398151915290610719565b610cbe9190612a2d565b610ccd565b610ccd8284612a2d565b90506000610d02856103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b9050610d0e8189612a15565b975082610d2a600080516020612d1a833981519152878c61182c565b15610d5457610d4386610d3d8486612a2d565b8c611869565b610d4d8282612a15565b9050610d7d565b610d7086610d618c610e13565b610d6b8587612a2d565b610e67565b610d7a8382612a15565b90505b610d97600080516020612d1a833981519152878c8461192c565b610da2868c876108cb565b6001600160a01b0386167f69a2ef6bf9e7ff92cbf1b71963ba1751b1abe8f99e3b3aae2ab99e416df614938c610dd88587612a2d565b60408051928352602083019190915281018890526060810185905260800160405180910390a2505050505050610c13565b5050949350505050565b600081610e40577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b61010483838360405180602001604052806000815250611a6c565b60006011830181610e95610e1085612a5a565b8152602081019190915260400160002054600f0b9392505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0357600080fd5b505afa158015610f17573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f3b9190612ad8565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f8f57600080fd5b505afa158015610fa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc79190612ad8565b90506101578282611b93565b6000610fe1610e1084612a5a565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061101a60ff80831690612a2d565b6001901b846012016000600884901c815260200190815260200160002060008282546110469190612a15565b909155505050505050565b80151560009081526013830160205260409020805415806110725750805442105b1561107c57505050565b60006110888484611c2e565b90506110c384826110bd6110b286600101546110ad898b611c9890919063ffffffff16565b6110e1565b600f86900b90611cc6565b86611cf9565b50501515600090815260139091016020526040812081815560010155565b6000610652836110f284600a612bd5565b611d76565b600061065261110783600a612bd5565b600f85900b906106b1565b60006104d8825490565b60006106528383611dad565b60006001600160a01b0383166111945760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b60648201526084016100f0565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6040805160018082528183019092526060916000919060208083019080368337019050509050828160008151811061121557611215612be4565b602090810291909101015292915050565b611234868686868686611e33565b600080516020612d1a83398151915260005b845181101561175857600085828151811061126357611263612be4565b60200260200101519050600085838151811061128157611281612be4565b60200260200101519050806000141561129b575050611746565b6001600160a01b0389166112b8576112b66015850183612011565b505b6001600160a01b0388161580156112e857506000828152600080516020612cfa8339815191526020526040902054155b156112fc576112fa601585018361201d565b505b7f000000000000000000000000000000000000000000000000000000000000000082148061134957507f000000000000000000000000000000000000000000000000000000000000000082145b8061137357507f000000000000000000000000000000000000000000000000000000000000000082145b8061139d57507f000000000000000000000000000000000000000000000000000000000000000082145b1561148d576001600160a01b038916158015906113c257506001600160a01b03881615155b1561148d5760007f000000000000000000000000000000000000000000000000000000000000000083148061141657507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d870160209081526040808320841515845290915290205490915042906114509062015180612a15565b1061148b5760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b60448201526064016100f0565b505b7f00000000000000000000000000000000000000000000000000000000000000008214806114da57507f000000000000000000000000000000000000000000000000000000000000000082145b15611682577f00000000000000000000000000000000000000000000000000000000000000008214600061150e8683612029565b90506001600160a01b038b161561163857600061152b8c86611128565b9050818111801561154557506115418285612a15565b8111155b156115dd576001600160a01b038c166000908152601488016020908152604080832086151580855260138c01845282852054855290835281842090845290915290205484906115949083612a2d565b10156115d25760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b60448201526064016100f0565b6115dd878d85612043565b6001600160a01b038b161561163657611611878d858c8a8151811061160457611604612be4565b602002602001015161192c565b611636878c858c8a8151811061162957611629612be4565b60200260200101516120f4565b505b6001600160a01b038a161561167f5760006116538b86611128565b905081811115801561166d57508161166b8583612a15565b115b1561167d5761167d878c85612218565b505b50505b60f882901c826001600160a01b038b16158015906116a857506001600160a01b038a1615155b80156116e0575060058260078111156116c3576116c36129e9565b14806116e0575060078260078111156116de576116de6129e9565b145b1561174157600060058360078111156116fb576116fb6129e9565b1490506000816117225761171d611716600f85900b876106b1565b8990610719565b611724565b845b9050611732888e848461192c565b61173e888d84846120f4565b50505b505050505b8061175081612a7c565b915050611246565b5050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610521576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b1580156117f457600080fd5b505afa158015611808573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d89190612ad8565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061186057504281115b95945050505050565b600080516020612d1a83398151915261188b84611885846122c0565b85610e67565b60006101048061189b8142612a5a565b6118a59190612a97565b6118af9190612a15565b6001600160a01b03861660009081526014840160209081526040808320848452825280832087151584529091528120805492935086929091906118f3908490612a15565b90915550508215156000908152601383016020526040812060018101805491928792611920908490612a15565b90915550505550505050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b863087866119978982612a2d565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156119fa57600080fd5b505af1158015611a0e573d6000803e3d6000fd5b505050508282611a1e9190612a2d565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a2d565b9315156000908152601890960160205250506040909320555050565b6001600160a01b038416611acc5760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b60648201526084016100f0565b611aeb33600086611adc876111db565b611ae5876111db565b86611226565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611b3e908490612a15565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b600081611b9f57600080fd5b600080841215611bb457836000039350600190505b6000831215611bc65760009290920391155b6000611bd28585612314565b90508115611c00576001607f1b816001600160801b03161115611bf457600080fd5b60000391506104d89050565b60016001607f1b03816001600160801b03161115611c1d57600080fd5b91506104d89050565b505092915050565b600080611c4b83611c40576001611c43565b60005b600080610119565b831515600090815260138601602052604090206001015490915061015790600080516020612cfa83398151915260008481526020919091526040902054611c929190612a2d565b6110ad86865b600081611cb3576003830154600160a81b900460ff16610652565b505060030154600160a01b900460ff1690565b6000600f83810b9083900b0160016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000611d058583612476565b90506000611d16868387878761248f565b9050611d23868285612595565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b600081611d8257600080fd5b6000611d8e8484612314565b905060016001607f1b036001600160801b038216111561065257600080fd5b81546000908210611e0b5760405162461bcd60e51b815260206004820152602260248201527f456e756d657261626c655365743a20696e646578206f7574206f6620626f756e604482015261647360f01b60648201526084016100f0565b826000018281548110611e2057611e20612be4565b9060005260206000200154905092915050565b836001600160a01b0316856001600160a01b031614612009576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020612cfa833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b87518110156104b3576000878281518110611edf57611edf612be4565b602002602001015190506000811115611ff6576000898381518110611f0657611f06612be4565b6020026020010151905060006001600160a01b03168c6001600160a01b03161415611f545760008181526020889052604081208054849290611f49908490612a15565b90915550611f8a9050565b81611f5f8d83611128565b1415611f8a576000818152602087905260409020611f7d908d6125ec565b50611f88858261201d565b505b6001600160a01b038b16611fc15760008181526020889052604081208054849290611fb6908490612a2d565b90915550611ff49050565b611fcb8b82611128565b611ff4576000818152602087905260409020611fe7908c612601565b50611ff28482612011565b505b505b508061200181612a7c565b915050611ec2565b505050505050565b60006106528383612612565b60006106528383612661565b60008161203a578260040154610652565b50506005015490565b6001600160a01b03821661205657600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061208184838361274c565b61208c575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b8630878661215f8982612a15565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156121c257600080fd5b505af11580156121d6573d6000803e3d6000fd5b5050505082826121e69190612a15565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a15565b6001600160a01b03821661222b57600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061225684838361274c565b15612262575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6000816122ed577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008161232057600080fd5b60006001600160c01b03841161234b5782604085901b8161234357612343612a44565b049050612462565b60c084811c6401000000008110612364576020918201911c5b620100008110612376576010918201911c5b6101008110612387576008918201911c5b60108110612397576004918201911c5b600481106123a7576002918201911c5b600281106123b6576001820191505b60bf820360018603901c6001018260ff0387901b816123d7576123d7612a44565b0492506001600160801b038311156123ee57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b8281101561241a576001820391505b608084901b92900382811015612431576001820391505b829003608084901c821461244757612447612bfa565b88818161245657612456612a44565b04870196505050505050505b6001600160801b0381111561065257600080fd5b60006124828383612798565b90506106528382846127bf565b600080826124a4576019870154600f0b6124b4565b6019870154600160801b9004600f0b5b905080600f0b600014156124cc57506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b6064820152730f6e8ef18fb5bb61d545fee60f779d8aed60408f9063e101a89b9060840160206040518083038186803b15801561253257600080fd5b505af4158015612546573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256a9190612c10565b915067b33333333333333382600f0b121561258b5767b33333333333333391505b5095945050505050565b80156125c5576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b6000610652836001600160a01b038416612661565b6000610652836001600160a01b0384165b6000818152600183016020526040812054612659575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104d8565b5060006104d8565b60008181526001830160205260408120548015612742576000612685600183612a2d565b8554909150600090869061269b90600190612a2d565b815481106126ab576126ab612be4565b90600052602060002001549050808660000183815481106126ce576126ce612be4565b6000918252602090912001556126e5826001612a15565b6000828152600188016020526040902055855486908061270757612707612c33565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104d8565b60009150506104d8565b6001600160a01b0383811660009081526020849052604081205490911615158061015757506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6000816127b3576008830154600160801b9004600f0b610652565b505060090154600f0b90565b600080826127d15784600a01546127d7565b84600b01545b6127e19042612a2d565b905061a8c0811115612800576127f961a8c082612a2d565b9050612809565b83915050610652565b600061281782613840611d76565b9050600061282a85611c40576001611c43565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020612cfa83398151915290935290832054939450926128889161287891612a2d565b6128829084612a2d565b83611d76565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e666666666666666608082018190526801000000000000000060a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b8152919250730f6e8ef18fb5bb61d545fee60f779d8aed60408f91634916d70d9161292991600401612c49565b60206040518083038186803b15801561294157600080fd5b505af4158015612955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129799190612c10565b9998505050505050505050565b60008060006060848603121561299b57600080fd5b83356001600160a01b03811681146129b257600080fd5b95602085013595506040909401359392505050565b600080604083850312156129da57600080fd5b50508035926020909101359150565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115612a2857612a286129ff565b500190565b600082821015612a3f57612a3f6129ff565b500390565b634e487b7160e01b600052601260045260246000fd5b600082612a7757634e487b7160e01b600052601260045260246000fd5b500490565b6000600019821415612a9057612a906129ff565b5060010190565b6000816000190483118215151615612ab157612ab16129ff565b500290565b600060208284031215612ac857600080fd5b8151801515811461065257600080fd5b600060208284031215612aea57600080fd5b5051919050565b600181815b80851115612b2c578160001904821115612b1257612b126129ff565b80851615612b1f57918102915b93841c9390800290612af6565b509250929050565b600082612b43575060016104d8565b81612b50575060006104d8565b8160018114612b665760028114612b7057612b8c565b60019150506104d8565b60ff841115612b8157612b816129ff565b50506001821b6104d8565b5060208310610133831016604e8410600b8410161715612baf575081810a6104d8565b612bb98383612af1565b8060001904821115612bcd57612bcd6129ff565b029392505050565b600061065260ff841683612b34565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052600160045260246000fd5b600060208284031215612c2257600080fd5b815180600f0b811461065257600080fd5b634e487b7160e01b600052603160045260246000fd5b6000610120820190508251600f0b82526020830151600f0b60208301526040830151612c7a6040840182600f0b9052565b506060830151612c8f6060840182600f0b9052565b506080830151612ca46080840182600f0b9052565b5060a0830151612cb960a0840182600f0b9052565b5060c0830151612cce60c0840182600f0b9052565b5060e0830151612ce360e0840182600f0b9052565b5061010080840151611c2682850182600f0b905256feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eb", + ), + ( + "0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05", + "MainchainGatewayV2", + "608060405234801561001057600080fd5b506000805460ff1916905561582e806200002b6000396000f3fe60806040526004361061032d5760003560e01c80639157921c116101a5578063b2975794116100ec578063d547741f11610095578063dafae4081161006f578063dafae4081461096e578063dff525e11461098e578063e400327c146109ae578063e75235b8146109ce5761033c565b8063d547741f14610901578063d55ed10314610921578063d64af2a61461094e5761033c565b8063cdb67444116100c6578063cdb674441461089c578063cdf64a76146108b4578063d19773d2146108d45761033c565b8063b29757941461082f578063b9c362091461085c578063ca15c8731461087c5761033c565b8063a3912ec81161014e578063affed0e011610128578063affed0e0146107cc578063b1a2567e146107e2578063b1d08a03146108025761033c565b8063a3912ec81461033a578063ab7965661461077f578063ac78dfe8146107ac5761033c565b8063994390891161017f57806399439089146107155780639dcc4da314610735578063a217fddf1461076a5761033c565b80639157921c1461068f57806391d14854146106af57806393c5678f146106f55761033c565b806336568abe116102745780635c975abb1161021d5780637de5dedd116101f75780637de5dedd146106115780638456cb59146106265780638f34e3471461063b5780639010d07c1461066f5761033c565b80635c975abb146105ac5780636932be98146105c45780636c1ce670146105f15761033c565b80634d0d66731161024e5780634d0d66731461052f5780634d493f4e1461054f57806359122f6b1461057f5761033c565b806336568abe146104e75780633f4ba83a146105075780634b14557e1461051c5761033c565b80631d4a7210116102d65780632f2ff15d116102b05780632f2ff15d1461049b578063302d12db146104bb5780633644e515146104d25761033c565b80631d4a721014610428578063248a9ca3146104555780632dfdf0b5146104855761033c565b8063180ff1e911610307578063180ff1e9146103d55780631a8e55b0146103e85780631b6e7594146104085761033c565b806301ffc9a71461034457806317ce2dd41461037957806317fcb39b1461039d5761033c565b3661033c5761033a6109e6565b005b61033a6109e6565b34801561035057600080fd5b5061036461035f366004614843565b610a69565b60405190151581526020015b60405180910390f35b34801561038557600080fd5b5061038f60755481565b604051908152602001610370565b3480156103a957600080fd5b506074546103bd906001600160a01b031681565b6040516001600160a01b039091168152602001610370565b61033a6103e33660046148f4565b610aad565b3480156103f457600080fd5b5061033a6104033660046149e6565b610dbd565b34801561041457600080fd5b5061033a610423366004614a52565b610e8f565b34801561043457600080fd5b5061038f610443366004614aec565b603e6020526000908152604090205481565b34801561046157600080fd5b5061038f610470366004614b09565b60009081526072602052604090206001015490565b34801561049157600080fd5b5061038f60765481565b3480156104a757600080fd5b5061033a6104b6366004614b22565b610f64565b3480156104c757600080fd5b5061038f620f424081565b3480156104de57600080fd5b5060775461038f565b3480156104f357600080fd5b5061033a610502366004614b22565b610f8f565b34801561051357600080fd5b5061033a61101b565b61033a61052a366004614b52565b611083565b34801561053b57600080fd5b5061036461054a366004614b7d565b6110e1565b34801561055b57600080fd5b5061036461056a366004614b09565b607a6020526000908152604090205460ff1681565b34801561058b57600080fd5b5061038f61059a366004614aec565b603a6020526000908152604090205481565b3480156105b857600080fd5b5060005460ff16610364565b3480156105d057600080fd5b5061038f6105df366004614b09565b60796020526000908152604090205481565b3480156105fd57600080fd5b5061036461060c366004614c06565b61118c565b34801561061d57600080fd5b5061038f61119f565b34801561063257600080fd5b5061033a611234565b34801561064757600080fd5b5061038f7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e481565b34801561067b57600080fd5b506103bd61068a366004614c32565b61129c565b34801561069b57600080fd5b5061033a6106aa366004614c54565b6112b4565b3480156106bb57600080fd5b506103646106ca366004614b22565b60009182526072602090815260408084206001600160a01b0393909316845291905290205460ff1690565b34801561070157600080fd5b5061033a6107103660046149e6565b6115ca565b34801561072157600080fd5b506003546103bd906001600160a01b031681565b34801561074157600080fd5b50610755610750366004614c32565b611696565b60408051928352602083019190915201610370565b34801561077657600080fd5b5061038f600081565b34801561078b57600080fd5b5061038f61079a366004614aec565b603c6020526000908152604090205481565b3480156107b857600080fd5b506103646107c7366004614b09565b61172f565b3480156107d857600080fd5b5061038f60045481565b3480156107ee57600080fd5b5061033a6107fd3660046149e6565b6117ce565b34801561080e57600080fd5b5061038f61081d366004614aec565b60396020526000908152604090205481565b34801561083b57600080fd5b5061084f61084a366004614aec565b61189a565b6040516103709190614ca5565b34801561086857600080fd5b50610755610877366004614c32565b611992565b34801561088857600080fd5b5061038f610897366004614b09565b611a17565b3480156108a857600080fd5b50603754603854610755565b3480156108c057600080fd5b5061033a6108cf366004614aec565b611a2e565b3480156108e057600080fd5b5061038f6108ef366004614aec565b603b6020526000908152604090205481565b34801561090d57600080fd5b5061033a61091c366004614b22565b611a97565b34801561092d57600080fd5b5061038f61093c366004614aec565b603d6020526000908152604090205481565b34801561095a57600080fd5b5061033a610969366004614aec565b611abd565b34801561097a57600080fd5b50610364610989366004614b09565b611b26565b34801561099a57600080fd5b5061033a6109a9366004614cd2565b611bbd565b3480156109ba57600080fd5b5061033a6109c93660046149e6565b611cc7565b3480156109da57600080fd5b50600154600254610755565b60005460ff1615610a315760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b60448201526064015b60405180910390fd5b6074546001600160a01b03163314610a6757610a4b614802565b338152604080820151349101528051610a65908290611d93565b505b565b60006001600160e01b031982167f5a05180f000000000000000000000000000000000000000000000000000000001480610aa75750610aa78261210a565b92915050565b607154610100900460ff16610ac85760715460ff1615610acc565b303b155b610b3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a28565b607154610100900460ff16158015610b60576071805461ffff19166101011790555b610b6b60008d612171565b6075899055610b798b61217b565b610b828a6121dd565b610c29604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f159f52c1e3a2b6a6aad3950adf713516211484e0516dad685ea662a094b7c43b918101919091527fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a560608201524660808201523060a082015260c00160408051601f198184030181529190528051602090910120607755565b610c338887612238565b5050610c3f87876122f8565b5050610c496123d3565b6000610c558680614da6565b90501115610d1657610c7e610c6a8680614da6565b610c776020890189614da6565b8787612467565b610ca4610c8b8680614da6565b8660005b602002810190610c9f9190614da6565b612666565b610cca610cb18680614da6565b8660015b602002810190610cc59190614da6565b612779565b610cf0610cd78680614da6565b8660025b602002810190610ceb9190614da6565b61288c565b610d16610cfd8680614da6565b8660035b602002810190610d119190614da6565b612a30565b60005b610d266040870187614da6565b9050811015610d9c57610d8a7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e4610d606040890189614da6565b84818110610d7057610d70614d90565b9050602002016020810190610d859190614aec565b612b43565b80610d9481614e06565b915050610d19565b508015610daf576071805461ff00191690555b505050505050505050505050565b6000805160206157b9833981519152546001600160a01b03163314610e1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82610e7d5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612779565b50505050565b6000805160206157b9833981519152546001600160a01b03163314610eef5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b84610f4e5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b610f5c868686868686612467565b505050505050565b600082815260726020526040902060010154610f808133612b65565b610f8a8383612b43565b505050565b6001600160a01b038116331461100d5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c6600000000000000000000000000000000006064820152608401610a28565b6110178282612be5565b5050565b6000805160206157b9833981519152546001600160a01b0316331461107b5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a67612c07565b60005460ff16156110c95760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b610a656110db36839003830183614ec0565b33611d93565b6000805460ff16156111285760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b611184848484808060200260200160405190810160405280939291908181526020016000905b8282101561117a5761116b60608302860136819003810190614f13565b8152602001906001019061114e565b5050505050612ca3565b949350505050565b600061119883836133bc565b9392505050565b600061122f600360009054906101000a90046001600160a01b03166001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b1580156111f257600080fd5b505afa158015611206573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061122a9190614f89565b613480565b905090565b6000805160206157b9833981519152546001600160a01b031633146112945760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a676134b6565b60008281526073602052604081206111989083613531565b7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e46112df8133612b65565b60006112f86112f336859003850185614ff0565b61353d565b905061130c6112f336859003850185614ff0565b8335600090815260796020526040902054146113765760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b82356000908152607a602052604090205460ff166113fc5760405162461bcd60e51b815260206004820152603160248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220617060448201527f70726f766564207769746864726177616c0000000000000000000000000000006064820152608401610a28565b82356000908152607a602052604090819020805460ff19169055517fd639511b37b3b002cca6cfe6bca0d833945a5af5a045578a0627fc43b79b26309061144690839086906150c4565b60405180910390a160006114606080850160608601614aec565b9050600061147661012086016101008701615151565b600181111561148757611487614c71565b141561154f5760006114a2368690038601610100870161516e565b6001600160a01b0383166000908152603b60205260409020549091506114ce90610140870135906135c6565b604082015260006114e8368790038701610100880161516e565b60408301519091506114ff9061014088013561518a565b604082015260745461151f908390339086906001600160a01b03166135e0565b6115486115326060880160408901614aec565b60745483919086906001600160a01b03166135e0565b505061158b565b61158b6115626060860160408701614aec565b60745483906001600160a01b03166115833689900389016101008a0161516e565b9291906135e0565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d82856040516115bc9291906150c4565b60405180910390a150505050565b6000805160206157b9833981519152546001600160a01b0316331461162a5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261168a5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612666565b6000806116b86000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146117115760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b84846122f8565b90925090506117286123d3565b9250929050565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190614f89565b6037546117b991906151a1565b6038546117c690846151a1565b101592915050565b6000805160206157b9833981519152546001600160a01b0316331461182e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261188e5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e898484848461288c565b60408051808201909152600080825260208201526001600160a01b0382166000908152607860205260409081902081518083019092528054829060ff1660018111156118e8576118e8614c71565b60018111156118f9576118f9614c71565b815290546001600160a01b036101009091048116602092830152908201519192501661198d5760405162461bcd60e51b815260206004820152602560248201527f4d61696e636861696e4761746577617956323a20756e737570706f727465642060448201527f746f6b656e0000000000000000000000000000000000000000000000000000006064820152608401610a28565b919050565b6000806119b46000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b031614611a0d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b8484612238565b6000818152607360205260408120610aa790613a13565b6000805160206157b9833981519152546001600160a01b03163314611a8e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a65816121dd565b600082815260726020526040902060010154611ab38133612b65565b610f8a8383612be5565b6000805160206157b9833981519152546001600160a01b03163314611b1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a658161217b565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b158015611b6b57600080fd5b505afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba39190614f89565b600154611bb091906151a1565b6002546117c690846151a1565b6000805160206157b9833981519152546001600160a01b03163314611c1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b85611c7c5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b611c8a878787878787612467565b611c978787836000610c8f565b611ca48787836001610cb5565b611cb18787836002610cdb565b611cbe8787836003610d01565b50505050505050565b6000805160206157b9833981519152546001600160a01b03163314611d275760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82611d875760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612a30565b604080518082018252600080825260208201526074549184015190916001600160a01b031690611dc290613a1d565b60208401516001600160a01b0316611ee1573484604001516040015114611e375760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611e408161189a565b6040850151519092506001811115611e5a57611e5a614c71565b82516001811115611e6d57611e6d614c71565b14611ecd5760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b6001600160a01b0381166020850152612087565b3415611f3b5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611f48846020015161189a565b6040850151519092506001811115611f6257611f62614c71565b82516001811115611f7557611f75614c71565b14611fd55760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b60208401516040850151611fec9185903090613ac7565b83602001516001600160a01b0316816001600160a01b031614156120875760408481015181015190517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b15801561206e57600080fd5b505af1158015612082573d6000803e3d6000fd5b505050505b607680546000918261209883614e06565b91905055905060006120bf858386602001516075548a613ce190949392919063ffffffff16565b90507fd7b25068d9dc8d00765254cfb7f5070f98d263c8d68931d937c7362fa738048b6120eb8261353d565b826040516120fa9291906151c0565b60405180910390a1505050505050565b60006001600160e01b031982167f7965db0b000000000000000000000000000000000000000000000000000000001480610aa757507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614610aa7565b6110178282612b43565b6074805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f9d2334c23be647e994f27a72c5eee42a43d5bdcfe15bb88e939103c2b114cbaf906020015b60405180910390a150565b6003805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fef40dc07567635f84f5edbd2f8dbc16b40d9d282dd8e7e6f4ff58236b6836169906020016121d2565b6000808284111561228b5760405162461bcd60e51b815260206004820152601c60248201527f4761746577617956323a20696e76616c6964207468726573686f6c64000000006044820152606401610a28565b505060018054600280549285905583905560048054919291849186919060006122b383614e06565b9091555060408051868152602081018690527f976f8a9c5bdf8248dec172376d6e2b80a8e3df2f0328e381c6db8e1cf138c0f891015b60405180910390a49250929050565b600080828411156123715760405162461bcd60e51b815260206004820152602760248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64000000000000000000000000000000000000000000000000006064820152608401610a28565b5050603780546038805492859055839055600480549192918491869190600061239983614e06565b9091555060408051868152602081018690527f31312c97b89cc751b832d98fd459b967a2c3eef3b49757d1cf5ebaa12bb6eee191016122e9565b6002546037546123e391906151a1565b6038546001546123f391906151a1565b1115610a675760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64730000000000000000000000000000000000000000000000006064820152608401610a28565b848314801561247557508481145b6124e75760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206172726160448201527f79206c656e6774680000000000000000000000000000000000000000000000006064820152608401610a28565b60005b8581101561262c5784848281811061250457612504614d90565b90506020020160208101906125199190614aec565b6078600089898581811061252f5761252f614d90565b90506020020160208101906125449190614aec565b6001600160a01b039081168252602082019290925260400160002080547fffffffffffffffffffffff0000000000000000000000000000000000000000ff1661010093909216929092021790558282828181106125a3576125a3614d90565b90506020020160208101906125b89190615151565b607860008989858181106125ce576125ce614d90565b90506020020160208101906125e39190614aec565b6001600160a01b031681526020810191909152604001600020805460ff19166001838181111561261557612615614c71565b02179055508061262481614e06565b9150506124ea565b507fa4f03cc9c0e0aeb5b71b4ec800702753f65748c2cf3064695ba8e8b46be704448686868686866040516120fa969594939291906152c1565b8281146126c85760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612743578282828181106126e5576126e5614d90565b905060200201356039600087878581811061270257612702614d90565b90506020020160208101906127179190614aec565b6001600160a01b031681526020810191909152604001600020558061273b81614e06565b9150506126cb565b507f80bc635c452ae67f12f9b6f12ad4daa6dbbc04eeb9ebb87d354ce10c0e210dc0848484846040516115bc9493929190615339565b8281146127db5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612856578282828181106127f8576127f8614d90565b90506020020135603a600087878581811061281557612815614d90565b905060200201602081019061282a9190614aec565b6001600160a01b031681526020810191909152604001600020558061284e81614e06565b9150506127de565b507f64557254143204d91ba2d95acb9fda1e5fea55f77efd028685765bc1e94dd4b5848484846040516115bc9493929190615339565b8281146128ee5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b838110156129fa57620f424083838381811061290f5761290f614d90565b90506020020135111561298a5760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420706560448201527f7263656e746167650000000000000000000000000000000000000000000000006064820152608401610a28565b82828281811061299c5761299c614d90565b90506020020135603b60008787858181106129b9576129b9614d90565b90506020020160208101906129ce9190614aec565b6001600160a01b03168152602081019190915260400160002055806129f281614e06565b9150506128f1565b507fb05f5de88ae0294ebb6f67c5af2fcbbd593cc6bdfe543e2869794a4c8ce3ea50848484846040516115bc9493929190615339565b828114612a925760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612b0d57828282818110612aaf57612aaf614d90565b90506020020135603c6000878785818110612acc57612acc614d90565b9050602002016020810190612ae19190614aec565b6001600160a01b0316815260208101919091526040016000205580612b0581614e06565b915050612a95565b507fb5d2963614d72181b4df1f993d45b83edf42fa19710f0204217ba1b3e183bb73848484846040516115bc9493929190615339565b612b4d8282613db6565b6000828152607360205260409020610f8a9082613e58565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff1661101757612ba3816001600160a01b03166014613e6d565b612bae836020613e6d565b604051602001612bbf9291906153d0565b60408051601f198184030181529082905262461bcd60e51b8252610a2891600401615451565b612bef828261404e565b6000828152607360205260409020610f8a90826140d1565b60005460ff16612c595760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152606401610a28565b6000805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000823561014084013582612cbe6080870160608801614aec565b9050612cdb612cd6368890038801610100890161516e565b613a1d565b6001612ced6040880160208901615151565b6001811115612cfe57612cfe614c71565b14612d715760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964207265636560448201527f697074206b696e640000000000000000000000000000000000000000000000006064820152608401610a28565b60808601354614612de95760405162461bcd60e51b8152602060048201526024808201527f4d61696e636861696e4761746577617956323a20696e76616c6964206368616960448201527f6e206964000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6000612dfe61084a6080890160608a01614aec565b9050612e1261012088016101008901615151565b6001811115612e2357612e23614c71565b81516001811115612e3657612e36614c71565b148015612e675750612e4e60e0880160c08901614aec565b6001600160a01b031681602001516001600160a01b0316145b612ebf5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b60008481526079602052604090205415612f415760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220707260448201527f6f636573736564207769746864726177616c00000000000000000000000000006064820152608401610a28565b6001612f5561012089016101008a01615151565b6001811115612f6657612f66614c71565b1480612f795750612f7782846133bc565b155b612feb5760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a2072656163686564206461696c60448201527f79207769746864726177616c206c696d697400000000000000000000000000006064820152608401610a28565b6000612fff6112f3368a90038a018a614ff0565b9050600061300f607754836140e6565b6003549091506001600160a01b0316600061303d6130356101208d016101008e01615151565b878985614142565b60408051606081018252600080825260208201819052918101829052919b50919250819081906000805b8f5181101561323c578f818151811061308257613082614d90565b6020908102919091018101518051818301516040808401518151600081529586018083528f905260ff9093169085015260608401526080830152935060019060a0016020604051602081039080840390855afa1580156130e6573d6000803e3d6000fd5b505050602060405103519450846001600160a01b0316846001600160a01b0316106131795760405162461bcd60e51b815260206004820152602160248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206f72646560448201527f72000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6040517f953865650000000000000000000000000000000000000000000000000000000081526001600160a01b03808716600483015286955089169063953865659060240160206040518083038186803b1580156131d657600080fd5b505afa1580156131ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320e9190614f89565b6132189083615484565b915086821061322a576001955061323c565b8061323481614e06565b915050613067565b50846132b05760405162461bcd60e51b815260206004820152603660248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220696e60448201527f73756666696369656e7420766f746520776569676874000000000000000000006064820152608401610a28565b50505060008a81526079602052604090208690555050881561332c576000888152607a602052604090819020805460ff19166001179055517f89e52969465b1f1866fc5d46fd62de953962e9cb33552443cd999eba05bd20dc906133179086908e906150c4565b60405180910390a15050505050505050610aa7565b6133368688614233565b61337561334960608d0160408e01614aec565b87607460009054906101000a90046001600160a01b03168e61010001803603810190611583919061516e565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d848c6040516133a69291906150c4565b60405180910390a1505050505050505092915050565b6001600160a01b0382166000908152603a602052604081205482106133e357506000610aa7565b60006133f2620151804261549c565b6001600160a01b0385166000908152603e60205260409020549091508111156134385750506001600160a01b0382166000908152603c6020526040902054811015610aa7565b6001600160a01b0384166000908152603d602052604090205461345c908490615484565b6001600160a01b0385166000908152603c602052604090205411159150610aa79050565b600060025460016002548460015461349891906151a1565b6134a29190615484565b6134ac919061518a565b610aa7919061549c565b60005460ff16156134fc5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b6000805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612c863390565b600061119883836142c3565b60007fb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea60001b8260000151836020015161357a85604001516142ed565b61358786606001516142ed565b6135948760800151614350565b6040516020016135a9969594939291906154be565b604051602081830303815290604052805190602001209050919050565b6000620f42406135d683856151a1565b611198919061549c565b6000816001600160a01b0316836001600160a01b031614156136905760408086015190516001600160a01b0386169180156108fc02916000818181858888f1935050505061368b57816001600160a01b031663d0e30db086604001516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561366757600080fd5b505af115801561367b573d6000803e3d6000fd5b505050505061368b858585614393565b613a0c565b6000855160018111156136a5576136a5614c71565b1415613866576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000906001600160a01b038516906370a082319060240160206040518083038186803b15801561370657600080fd5b505afa15801561371a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061373e9190614f89565b9050856040015181101561385557836001600160a01b03166340c10f193083896040015161376c919061518a565b6040516001600160a01b03909216602483015260448201526064016040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516137c091906154f8565b6000604051808303816000865af19150503d80600081146137fd576040519150601f19603f3d011682016040523d82523d6000602084013e613802565b606091505b505080925050816138555760405162461bcd60e51b815260206004820152601b60248201527f546f6b656e3a204552433230206d696e74696e67206661696c656400000000006044820152606401610a28565b613860868686614393565b50613a0c565b60018551600181111561387b5761387b614c71565b141561399e5761389083858760200151614437565b61368b57602085810151604080516001600160a01b038881166024830152604480830194909452825180830390940184526064909101825292820180516001600160e01b03167f40c10f1900000000000000000000000000000000000000000000000000000000179052519185169161390991906154f8565b6000604051808303816000865af19150503d8060008114613946576040519150601f19603f3d011682016040523d82523d6000602084013e61394b565b606091505b5050809150508061368b5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e3a20455243373231206d696e74696e67206661696c6564000000006044820152606401610a28565b60405162461bcd60e51b815260206004820152602160248201527f546f6b656e3a20756e737570706f7274656420746f6b656e207374616e64617260448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b5050505050565b6000610aa7825490565b600081516001811115613a3257613a32614c71565b148015613a43575060008160400151115b8015613a5157506020810151155b80613a7b5750600181516001811115613a6c57613a6c614c71565b148015613a7b57506040810151155b610a655760405162461bcd60e51b815260206004820152601360248201527f546f6b656e3a20696e76616c696420696e666f000000000000000000000000006044820152606401610a28565b600060608186516001811115613adf57613adf614c71565b1415613bbd5760408681015181516001600160a01b038881166024830152878116604483015260648083019390935283518083039093018352608490910183526020820180516001600160e01b03166323b872dd60e01b179052915191851691613b4991906154f8565b6000604051808303816000865af19150503d8060008114613b86576040519150601f19603f3d011682016040523d82523d6000602084013e613b8b565b606091505b509092509050818015613bb6575080511580613bb6575080806020019051810190613bb69190615514565b9150613c84565b600186516001811115613bd257613bd2614c71565b141561399e57602086810151604080516001600160a01b0389811660248301528881166044830152606480830194909452825180830390940184526084909101825292820180516001600160e01b03166323b872dd60e01b1790525191851691613c3c91906154f8565b6000604051808303816000865af19150503d8060008114613c79576040519150601f19603f3d011682016040523d82523d6000602084013e613c7e565b606091505b50909250505b81610f5c57613c92866144e2565b613ca6866001600160a01b03166014613e6d565b613cba866001600160a01b03166014613e6d565b613cce866001600160a01b03166014613e6d565b604051602001612bbf9493929190615536565b613d516040805160a08101825260008082526020808301829052835160608082018652838252818301849052818601849052848601919091528451808201865283815280830184905280860184905281850152845190810185528281529081018290529283015290608082015290565b83815260006020820181905250604080820180516001600160a01b039788169052602080890151825190891690820152905146908301528751606084018051918916909152805195909716940193909352935182015292909201516080820152919050565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff166110175760008281526072602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613e143390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000611198836001600160a01b03841661454f565b60606000613e7c8360026151a1565b613e87906002615484565b67ffffffffffffffff811115613e9f57613e9f614e21565b6040519080825280601f01601f191660200182016040528015613ec9576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110613f0057613f00614d90565b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110613f4b57613f4b614d90565b60200101906001600160f81b031916908160001a9053506000613f6f8460026151a1565b613f7a906001615484565b90505b6001811115613fff577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110613fbb57613fbb614d90565b1a60f81b828281518110613fd157613fd1614d90565b60200101906001600160f81b031916908160001a90535060049490941c93613ff881615606565b9050613f7d565b5083156111985760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610a28565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff16156110175760008281526072602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000611198836001600160a01b03841661459e565b604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820185905260428083018590528351808403909101815260629092019092528051910120600090611198565b6000806000836001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b15801561418057600080fd5b505afa158015614194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141b89190614f89565b90506141c381613480565b925060008760018111156141d9576141d9614c71565b1415614229576001600160a01b038616600090815260396020526040902054851061420a5761420781614691565b92505b6001600160a01b0386166000908152603a602052604090205485101591505b5094509492505050565b6000614242620151804261549c565b6001600160a01b0384166000908152603e6020526040902054909150811115614291576001600160a01b03929092166000908152603e6020908152604080832094909455603d90529190912055565b6001600160a01b0383166000908152603d6020526040812080548492906142b9908490615484565b9091555050505050565b60008260000182815481106142da576142da614d90565b9060005260206000200154905092915050565b805160208083015160408085015190516000946135a9947f353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e87649491939192019384526001600160a01b03928316602085015291166040830152606082015260800190565b805160208083015160408085015190516000946135a9947f1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d94919391920161561d565b600080845160018111156143a9576143a9614c71565b14156143c5576143be828486604001516146a9565b90506143ef565b6001845160018111156143da576143da614c71565b141561399e576143be82848660200151614437565b80610e89576143fd846144e2565b614411846001600160a01b03166014613e6d565b614425846001600160a01b03166014613e6d565b604051602001612bbf93929190615648565b604080513060248201526001600160a01b038481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b1790529151600092861691614495916154f8565b6000604051808303816000865af19150503d80600081146144d2576040519150601f19603f3d011682016040523d82523d6000602084013e6144d7565b606091505b509095945050505050565b606061450d826000015160018111156144fd576144fd614c71565b6001600160a01b03166001613e6d565b61451a8360200151614795565b6145278460400151614795565b604051602001614539939291906156d9565b6040516020818303038152906040529050919050565b600081815260018301602052604081205461459657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aa7565b506000610aa7565b600081815260018301602052604081205480156146875760006145c260018361518a565b85549091506000906145d69060019061518a565b905081811461463b5760008660000182815481106145f6576145f6614d90565b906000526020600020015490508087600001848154811061461957614619614d90565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061464c5761464c6157a2565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610aa7565b6000915050610aa7565b600060385460016038548460375461349891906151a1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b03167fa9059cbb0000000000000000000000000000000000000000000000000000000017905291516000926060929087169161471f91906154f8565b6000604051808303816000865af19150503d806000811461475c576040519150601f19603f3d011682016040523d82523d6000602084013e614761565b606091505b50909250905081801561478c57508051158061478c57508080602001905181019061478c9190615514565b95945050505050565b6060816147d557505060408051808201909152600481527f3078303000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156147f857806147e981614e06565b915050600882901c91506147d9565b6111848482613e6d565b604080516060810182526000808252602082015290810161483e6040805160608101909152806000815260200160008152602001600081525090565b905290565b60006020828403121561485557600080fd5b81356001600160e01b03198116811461119857600080fd5b6001600160a01b0381168114610a6557600080fd5b803561198d8161486d565b8060608101831015610aa757600080fd5b8060808101831015610aa757600080fd5b60008083601f8401126148c157600080fd5b50813567ffffffffffffffff8111156148d957600080fd5b6020830191508360208260051b850101111561172857600080fd5b60008060008060008060008060008060006101408c8e03121561491657600080fd5b61491f8c614882565b9a5061492d60208d01614882565b995061493b60408d01614882565b985060608c0135975060808c0135965060a08c0135955060c08c0135945067ffffffffffffffff8060e08e0135111561497357600080fd5b6149838e60e08f01358f0161488d565b9450806101008e0135111561499757600080fd5b6149a88e6101008f01358f0161489e565b9350806101208e013511156149bc57600080fd5b506149ce8d6101208e01358e016148af565b81935080925050509295989b509295989b9093969950565b600080600080604085870312156149fc57600080fd5b843567ffffffffffffffff80821115614a1457600080fd5b614a20888389016148af565b90965094506020870135915080821115614a3957600080fd5b50614a46878288016148af565b95989497509550505050565b60008060008060008060608789031215614a6b57600080fd5b863567ffffffffffffffff80821115614a8357600080fd5b614a8f8a838b016148af565b90985096506020890135915080821115614aa857600080fd5b614ab48a838b016148af565b90965094506040890135915080821115614acd57600080fd5b50614ada89828a016148af565b979a9699509497509295939492505050565b600060208284031215614afe57600080fd5b81356111988161486d565b600060208284031215614b1b57600080fd5b5035919050565b60008060408385031215614b3557600080fd5b823591506020830135614b478161486d565b809150509250929050565b600060a08284031215614b6457600080fd5b50919050565b60006101608284031215614b6457600080fd5b60008060006101808486031215614b9357600080fd5b614b9d8585614b6a565b925061016084013567ffffffffffffffff80821115614bbb57600080fd5b818601915086601f830112614bcf57600080fd5b813581811115614bde57600080fd5b876020606083028501011115614bf357600080fd5b6020830194508093505050509250925092565b60008060408385031215614c1957600080fd5b8235614c248161486d565b946020939093013593505050565b60008060408385031215614c4557600080fd5b50508035926020909101359150565b60006101608284031215614c6757600080fd5b6111988383614b6a565b634e487b7160e01b600052602160045260246000fd5b60028110610a6557634e487b7160e01b600052602160045260246000fd5b81516040820190614cb581614c87565b808352506001600160a01b03602084015116602083015292915050565b60008060008060008060006080888a031215614ced57600080fd5b873567ffffffffffffffff80821115614d0557600080fd5b614d118b838c016148af565b909950975060208a0135915080821115614d2a57600080fd5b614d368b838c016148af565b909750955060408a0135915080821115614d4f57600080fd5b614d5b8b838c016148af565b909550935060608a0135915080821115614d7457600080fd5b50614d818a828b0161489e565b91505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112614dbd57600080fd5b83018035915067ffffffffffffffff821115614dd857600080fd5b6020019150600581901b360382131561172857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415614e1a57614e1a614df0565b5060010190565b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715614e6857634e487b7160e01b600052604160045260246000fd5b60405290565b60028110610a6557600080fd5b600060608284031215614e8d57600080fd5b614e95614e37565b90508135614ea281614e6e565b80825250602082013560208201526040820135604082015292915050565b600060a08284031215614ed257600080fd5b614eda614e37565b8235614ee58161486d565b81526020830135614ef58161486d565b6020820152614f078460408501614e7b565b60408201529392505050565b600060608284031215614f2557600080fd5b6040516060810181811067ffffffffffffffff82111715614f5657634e487b7160e01b600052604160045260246000fd5b604052823560ff81168114614f6a57600080fd5b8152602083810135908201526040928301359281019290925250919050565b600060208284031215614f9b57600080fd5b5051919050565b600060608284031215614fb457600080fd5b614fbc614e37565b90508135614fc98161486d565b81526020820135614fd98161486d565b806020830152506040820135604082015292915050565b6000610160828403121561500357600080fd5b60405160a0810181811067ffffffffffffffff8211171561503457634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561504981614e6e565b602082015261505b8460408501614fa2565b604082015261506d8460a08501614fa2565b6060820152615080846101008501614e7b565b60808201529392505050565b80356150978161486d565b6001600160a01b0390811683526020820135906150b38261486d565b166020830152604090810135910152565b6000610180820190508382528235602083015260208301356150e581614e6e565b6150ee81614c87565b80604084015250615105606083016040850161508c565b61511560c0830160a0850161508c565b61012061010084013561512781614e6e565b61513081614c87565b81840152830135610140808401919091529092013561016090910152919050565b60006020828403121561516357600080fd5b813561119881614e6e565b60006060828403121561518057600080fd5b6111988383614e7b565b60008282101561519c5761519c614df0565b500390565b60008160001904831182151516156151bb576151bb614df0565b500290565b6000610180820190508382528251602083015260208301516151e181614c87565b6040838101919091528381015180516001600160a01b03908116606086015260208201511660808501529081015160a084015250606083015180516001600160a01b0390811660c085015260208201511660e08401526040810151610100840152506080830151805161525381614c87565b6101208401526020810151610140840152604001516101609092019190915292915050565b8183526000602080850194508260005b858110156152b657813561529b8161486d565b6001600160a01b031687529582019590820190600101615288565b509495945050505050565b6060815260006152d560608301888a615278565b6020838203818501526152e982888a615278565b8481036040860152858152869250810160005b8681101561532a57833561530f81614e6e565b61531881614c87565b825292820192908201906001016152fc565b509a9950505050505050505050565b60408152600061534d604083018688615278565b82810360208401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84111561538557600080fd5b8360051b80866020840137600091016020019081529695505050505050565b60005b838110156153bf5781810151838201526020016153a7565b83811115610e895750506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516154088160178501602088016153a4565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516154458160288401602088016153a4565b01602801949350505050565b60208152600082518060208401526154708160408501602087016153a4565b601f01601f19169190910160400192915050565b6000821982111561549757615497614df0565b500190565b6000826154b957634e487b7160e01b600052601260045260246000fd5b500490565b8681526020810186905260c081016154d586614c87565b8560408301528460608301528360808301528260a0830152979650505050505050565b6000825161550a8184602087016153a4565b9190910192915050565b60006020828403121561552657600080fd5b8151801515811461119857600080fd5b7f546f6b656e3a20636f756c64206e6f74207472616e7366657220000000000000815260008551602061556f82601a8601838b016153a4565b7f2066726f6d200000000000000000000000000000000000000000000000000000601a9285019283015286516155aa81838501848b016153a4565b630103a37960e51b92018181019290925285516155cd81602485018985016153a4565b660103a37b5b2b7160cd1b6024939091019283015284516155f481602b85018489016153a4565b91909101602b01979650505050505050565b60008161561557615615614df0565b506000190190565b8481526080810161562d85614c87565b84602083015283604083015282606083015295945050505050565b7f546f6b656e3a20636f756c64206e6f74207472616e736665722000000000000081526000845161568081601a8501602089016153a4565b630103a37960e51b601a9184019182015284516156a481601e8401602089016153a4565b660103a37b5b2b7160cd1b601e929091019182015283516156cc8160258401602088016153a4565b0160250195945050505050565b7f546f6b656e496e666f280000000000000000000000000000000000000000000081526000845161571181600a8501602089016153a4565b80830190507f2c0000000000000000000000000000000000000000000000000000000000000080600a830152855161575081600b850160208a016153a4565b600b920191820152835161576b81600c8401602088016153a4565b7f2900000000000000000000000000000000000000000000000000000000000000600c9290910191820152600d0195945050505050565b634e487b7160e01b600052603160045260246000fdfeb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610348617350726f787941646d696e3a20756e617574686f72697a65642073656e64", + ), ]; } diff --git a/crates/forge/src/cmd/compiler.rs b/crates/forge/src/cmd/compiler.rs index 19e4354ca9cb4..ee8d14e4bc1b2 100644 --- a/crates/forge/src/cmd/compiler.rs +++ b/crates/forge/src/cmd/compiler.rs @@ -1,7 +1,7 @@ use clap::{Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_common::shell; -use foundry_compilers::{artifacts::EvmVersion, Graph}; +use foundry_compilers::{Graph, artifacts::EvmVersion}; use foundry_config::Config; use semver::Version; use serde::Serialize; @@ -72,8 +72,8 @@ impl ResolveArgs { .iter() .map(|(version, sources, _)| { let paths: Vec = sources - .iter() - .filter_map(|(path_file, _)| { + .keys() + .filter_map(|path_file| { let path_str = path_file .strip_prefix(&project.paths.root) .unwrap_or(path_file) @@ -82,25 +82,19 @@ impl ResolveArgs { .to_string(); // Skip files that match the given regex pattern. - if let Some(ref regex) = skip { - if regex.is_match(&path_str) { - return None; - } + if let Some(ref regex) = skip + && regex.is_match(&path_str) + { + return None; } Some(path_str) }) .collect(); - let evm_version = if shell::verbosity() > 1 { - Some( - EvmVersion::default() - .normalize_version_solc(version) - .unwrap_or_default(), - ) - } else { - None - }; + let evm_version = (shell::verbosity() > 1).then(|| { + EvmVersion::default().normalize_version_solc(version).unwrap_or_default() + }); ResolvedCompiler { version: version.clone(), evm_version, paths } }) @@ -115,7 +109,9 @@ impl ResolveArgs { // Clear paths if verbosity is 0, performed only after filtering to avoid being // skipped. if shell::verbosity() == 0 { - versions_with_paths.iter_mut().for_each(|version| version.paths.clear()); + for version in &mut versions_with_paths { + version.paths.clear(); + } } output.insert(language.to_string(), versions_with_paths); diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index a85e2391346da..fac66727c9d99 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -1,8 +1,8 @@ use super::build::BuildArgs; use clap::Parser; use eyre::Result; -use foundry_cli::utils::LoadConfig; -use foundry_common::{evm::EvmArgs, shell}; +use foundry_cli::{opts::EvmArgs, utils::LoadConfig}; +use foundry_common::shell; use foundry_config::fix::fix_tomls; foundry_config::impl_figment_convert!(ConfigArgs, build, evm); @@ -32,7 +32,7 @@ impl ConfigArgs { for warning in fix_tomls() { sh_warn!("{warning}")?; } - return Ok(()) + return Ok(()); } let config = self @@ -53,8 +53,8 @@ impl ConfigArgs { } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index 3b2e04eb88b26..b8ce2a9b945b1 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -1,34 +1,24 @@ use super::{install, test::TestArgs, watch::WatchArgs}; -use crate::{ - coverage::{ - analysis::{SourceAnalysis, SourceFile, SourceFiles}, - anchors::find_anchors, - BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, - DebugReporter, ItemAnchor, LcovReporter, - }, - MultiContractRunnerBuilder, +use crate::coverage::{ + BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, + DebugReporter, ItemAnchor, LcovReporter, + analysis::{SourceAnalysis, SourceFiles}, + anchors::find_anchors, }; -use alloy_primitives::{map::HashMap, Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, U256, map::HashMap}; use clap::{Parser, ValueEnum, ValueHint}; -use eyre::{Context, Result}; +use eyre::Result; use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED}; -use foundry_common::compile::ProjectCompiler; +use foundry_common::{compile::ProjectCompiler, errors::convert_solar_errors}; use foundry_compilers::{ - artifacts::{ - sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage, Source, - }, - compilers::multi::MultiCompiler, - Artifact, ArtifactId, Project, ProjectCompileOutput, ProjectPathsConfig, + Artifact, ArtifactId, Project, ProjectCompileOutput, ProjectPathsConfig, VYPER_EXTENSIONS, + artifacts::{CompactBytecode, CompactDeployedBytecode, sourcemap::SourceMap}, }; use foundry_config::Config; -use foundry_evm::opts::EvmOpts; -use foundry_evm_core::ic::IcPcMap; +use foundry_evm::{core::ic::IcPcMap, opts::EvmOpts}; use rayon::prelude::*; use semver::{Version, VersionReq}; -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; +use std::path::{Path, PathBuf}; // Loads project's figment and merges the build cli arguments into it foundry_config::impl_figment_convert!(CoverageArgs, test); @@ -65,7 +55,6 @@ pub struct CoverageArgs { /// If not specified, the report will be stored in the root of the project. #[arg( long, - short, value_hint = ValueHint::FilePath, value_name = "PATH" )] @@ -75,6 +64,10 @@ pub struct CoverageArgs { #[arg(long)] include_libs: bool, + /// Whether to exclude tests from the coverage report. + #[arg(long)] + exclude_tests: bool, + /// The coverage reporters to use. Constructed from the other fields. #[arg(skip)] reporters: Vec>, @@ -88,18 +81,19 @@ impl CoverageArgs { let (mut config, evm_opts) = self.load_config_and_evm_opts()?; // install missing dependencies - if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings + { // need to re-configure here to also catch additional remappings config = self.load_config()?; } - // Set fuzz seed so coverage reports are deterministic - config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); - - // Coverage analysis requires the Solc AST output. - config.ast = true; + // Default to a static fuzz seed so coverage reports are deterministic, + // but allow the user to override it via `--fuzz-seed` or `[fuzz] seed` in config. + if config.fuzz.seed.is_none() { + config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } - let (paths, output) = { + let (paths, mut output) = { let (project, output) = self.build(&config)?; (project.paths, output) }; @@ -107,10 +101,10 @@ impl CoverageArgs { self.populate_reporters(&paths.root); sh_println!("Analysing contracts...")?; - let report = self.prepare(&paths, &output)?; + let report = self.prepare(&paths, &mut output)?; sh_println!("Running tests...")?; - self.collect(&paths.root, &output, report, Arc::new(config), evm_opts).await + self.collect(&paths.root, &output, report, config, evm_opts).await } fn populate_reporters(&mut self, root: &Path) { @@ -140,39 +134,24 @@ impl CoverageArgs { let mut project = config.ephemeral_project()?; if self.ir_minimum { - // print warning message sh_warn!( "`--ir-minimum` enables `viaIR` with minimum optimization, \ which can result in inaccurate source mappings.\n\ Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n\ Note that `viaIR` is production ready since Solidity 0.8.13 and above.\n\ - See more: https://github.com/foundry-rs/foundry/issues/3357" + See more: https://book.getfoundry.sh/guides/best-practices/stack-too-deep" )?; - - // Enable viaIR with minimum optimization: https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 - // And also in new releases of Solidity: https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 - project.settings.solc.settings = - project.settings.solc.settings.with_via_ir_minimum_optimization(); - - // Sanitize settings for solc 0.8.4 if version cannot be detected: https://github.com/foundry-rs/foundry/issues/9322 - // But keep the EVM version: https://github.com/ethereum/solidity/issues/15775 - let evm_version = project.settings.solc.evm_version; - let version = config.solc_version().unwrap_or_else(|| Version::new(0, 8, 4)); - project.settings.solc.settings.sanitize(&version, SolcLanguage::Solidity); - project.settings.solc.evm_version = evm_version; } else { sh_warn!( "optimizer settings and `viaIR` have been disabled for accurate coverage reports.\n\ If you encounter \"stack too deep\" errors, consider using `--ir-minimum` which \ - enables `viaIR` with minimum optimization resolving most of the errors" + enables `viaIR` with minimum optimization resolving most of the errors.\n\ + See more: https://book.getfoundry.sh/guides/best-practices/stack-too-deep" )?; - - project.settings.solc.optimizer.disable(); - project.settings.solc.optimizer.runs = None; - project.settings.solc.optimizer.details = None; - project.settings.solc.via_ir = None; } + config.disable_optimizations(&mut project, self.ir_minimum); + let output = ProjectCompiler::default() .compile(&project)? .with_stripped_file_prefixes(project.root()); @@ -181,39 +160,49 @@ impl CoverageArgs { } /// Builds the coverage report. - #[instrument(name = "prepare", skip_all)] + #[instrument(name = "Coverage::prepare", skip_all)] fn prepare( &self, project_paths: &ProjectPathsConfig, - output: &ProjectCompileOutput, + output: &mut ProjectCompileOutput, ) -> Result { let mut report = CoverageReport::default(); + output.parser_mut().solc_mut().compiler_mut().enter_mut(|compiler| { + if compiler.gcx().stage() < Some(solar::config::CompilerStage::Lowering) { + let _ = compiler.lower_asts(); + } + convert_solar_errors(compiler.dcx()) + })?; + let output = &*output; + // Collect source files. - let mut versioned_sources = HashMap::>::default(); + let mut versioned_sources = HashMap::::default(); for (path, source_file, version) in output.output().sources.sources_with_version() { + // Filter out vyper sources. + if path + .extension() + .and_then(|s| s.to_str()) + .is_some_and(|ext| VYPER_EXTENSIONS.contains(&ext)) + { + continue; + } + report.add_source(version.clone(), source_file.id as usize, path.clone()); - // Filter out dependencies. - if !self.include_libs && project_paths.has_library_ancestor(path) { + // Filter out libs dependencies and tests. + if (!self.include_libs && project_paths.has_library_ancestor(path)) + || (self.exclude_tests && project_paths.is_test(path)) + { continue; } - if let Some(ast) = &source_file.ast { - let file = project_paths.root.join(path); - trace!(root=?project_paths.root, ?file, "reading source file"); - - let source = SourceFile { - ast, - source: Source::read(&file) - .wrap_err("Could not read source code for analysis")?, - }; - versioned_sources - .entry(version.clone()) - .or_default() - .sources - .insert(source_file.id as usize, source); - } + let path = project_paths.root.join(path); + versioned_sources + .entry(version.clone()) + .or_default() + .sources + .insert(source_file.id, path); } // Get source maps and bytecodes. @@ -228,7 +217,7 @@ impl CoverageArgs { // Add coverage items. for (version, sources) in &versioned_sources { - let source_analysis = SourceAnalysis::new(sources)?; + let source_analysis = SourceAnalysis::new(sources, output)?; let anchors = artifacts .par_iter() .filter(|artifact| artifact.contract_id.version == *version) @@ -252,38 +241,26 @@ impl CoverageArgs { } /// Runs tests, collects coverage data and generates the final report. + #[instrument(name = "Coverage::collect", skip_all)] async fn collect( mut self, - root: &Path, + project_root: &Path, output: &ProjectCompileOutput, mut report: CoverageReport, - config: Arc, + config: Config, evm_opts: EvmOpts, ) -> Result<()> { - let verbosity = evm_opts.verbosity; - - // Build the contract runner - let env = evm_opts.evm_env().await?; - let runner = MultiContractRunnerBuilder::new(config.clone()) - .initial_balance(evm_opts.initial_balance) - .evm_spec(config.evm_spec_id()) - .sender(evm_opts.sender) - .with_fork(evm_opts.get_fork(&config, env.clone())) - .set_coverage(true) - .build::(root, output, env, evm_opts)?; - - let known_contracts = runner.known_contracts.clone(); - let filter = self.test.filter(&config)?; - let outcome = self.test.run_tests(runner, config, verbosity, &filter, output).await?; + let outcome = + self.test.run_tests(project_root, config, evm_opts, output, &filter, true).await?; - outcome.ensure_ok(false)?; + let known_contracts = outcome.known_contracts.as_ref().unwrap().clone(); // Add hit data to the coverage report - let data = outcome.results.iter().flat_map(|(_, suite)| { + let data = outcome.results.values().flat_map(|suite| { let mut hits = Vec::new(); for result in suite.test_results.values() { - let Some(hit_maps) = result.coverage.as_ref() else { continue }; + let Some(hit_maps) = result.line_coverage.as_ref() else { continue }; for map in hit_maps.0.values() { if let Some((id, _)) = known_contracts.find_by_deployed_code(map.bytecode()) { hits.push((id, map, true)); @@ -323,18 +300,29 @@ impl CoverageArgs { } // Output final reports. + self.report(&report)?; + + // Check for test failures after generating coverage report. + // This ensures coverage data is written even when tests fail. + outcome.ensure_ok(false)?; + + Ok(()) + } + + #[instrument(name = "Coverage::report", skip_all)] + fn report(&mut self, report: &CoverageReport) -> Result<()> { for reporter in &mut self.reporters { - reporter.report(&report)?; + let _guard = debug_span!("reporter.report", kind=%reporter.name()).entered(); + reporter.report(report)?; } - Ok(()) } - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.test.is_watch() } - pub fn watch(&self) -> &WatchArgs { + pub const fn watch(&self) -> &WatchArgs { &self.test.watch } } @@ -409,7 +397,7 @@ pub struct BytecodeData { /// The source maps are indexed by *instruction counters*, which are the indexes of /// instructions in the bytecode *minus any push bytes*. /// - /// Since our coverage inspector collects hit data using program counters, the anchors + /// Since our line coverage inspector collects hit data using program counters, the anchors /// also need to be based on program counters. ic_pc_map: IcPcMap, } diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 6768b43829836..9b26c7465fcc9 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -1,39 +1,48 @@ use crate::cmd::install; use alloy_chains::Chain; +use alloy_consensus::{SignableTransaction, Signed}; use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; -use alloy_network::{AnyNetwork, AnyTransactionReceipt, EthereumWallet, TransactionBuilder}; -use alloy_primitives::{hex, Address, Bytes}; -use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder}; -use alloy_rpc_types::TransactionRequest; -use alloy_serde::WithOtherFields; -use alloy_signer::Signer; +use alloy_network::{Ethereum, EthereumWallet, Network, ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{Address, Bytes, U256, hex}; +use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as AlloyProviderBuilder}; +use alloy_signer::{Signature, Signer}; use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; +use eyre::{Context, ContextCompat, Result}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, - utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, + utils::{ + LoadConfig, ResolvedLane, find_contract_artifacts, maybe_print_resolved_lane, + read_constructor_args_file, resolve_lane, + }, }; use foundry_common::{ + FoundryTransactionBuilder, compile::{self}, fmt::parse_tokens, + provider::ProviderBuilder, shell, + tempo::TEMPO_BROWSER_GAS_BUFFER, }; use foundry_compilers::{ - artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, ArtifactId, + ArtifactId, artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, }; use foundry_config::{ + Config, figment::{ - self, + self, Metadata, Profile, value::{Dict, Map}, - Metadata, Profile, }, - merge_impl_figment_convert, Config, + merge_impl_figment_convert, +}; +use foundry_wallets::{ + BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner, }; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; +use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN}; merge_impl_figment_convert!(CreateArgs, build, eth); @@ -98,15 +107,51 @@ pub struct CreateArgs { #[command(flatten)] retry: RetryArgs, + + /// Browser wallet options + #[command(flatten)] + browser: BrowserWalletOpts, } impl CreateArgs { /// Executes the command to create a contract pub async fn run(mut self) -> Result<()> { + let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?; + + // Resolve chain early so we can dispatch to the correct network type. + if self.chain_id().is_none() { + let config = self.load_config()?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; + let chain_id = provider.get_chain_id().await?; + self.eth.etherscan.chain = Some(chain_id.into()); + } + + if tempo_access_key.is_some() + || self.tx.tempo.is_tempo() + || self.chain_id().is_some_and(|c| c.is_tempo()) + { + self.run_generic::(signer, tempo_access_key).await + } else { + self.run_generic::(signer, None).await + } + } + + async fn run_generic( + mut self, + pre_resolved_signer: Option, + access_key: Option, + ) -> Result<()> + where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, + N::ReceiptResponse: serde::Serialize, + { let mut config = self.load_config()?; // Install missing dependencies. - if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings + { // need to re-configure here to also catch additional remappings config = self.load_config()?; } @@ -122,7 +167,7 @@ impl CreateArgs { let output = compile::compile_target(&target_path, &project, shell::is_json())?; - let (abi, bin, id) = remove_contract(output, &target_path, &self.contract.name)?; + let (abi, bin, id) = find_contract_artifacts(output, &target_path, &self.contract.name)?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, @@ -135,7 +180,10 @@ impl CreateArgs { }) .collect::>() .join("\n"); - eyre::bail!("Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}", link_refs) + eyre::bail!( + "Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}", + link_refs + ) } }; @@ -148,22 +196,51 @@ impl CreateArgs { constructor_args.as_deref().unwrap_or(&self.constructor_args), )? } else { + if !self.constructor_args.is_empty() || self.constructor_args_path.is_some() { + sh_warn!( + "`{}` has no constructor; ignoring provided constructor arguments", + self.contract.name + )?; + } vec![] }; - let provider = utils::get_provider(&config)?; + let provider = ProviderBuilder::::from_config(&config)?.build()?; - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chain_id().await? - }; + // Inject access key ID into TempoOpts so it's set before gas estimation. + if let Some(ref ak) = access_key { + self.tx.tempo.key_id = Some(ak.key_address); + } + + // Resolve `--tempo.lane ` against the lanes file (default + // `/tempo.lanes.toml`) and populate `self.tx.tempo.nonce_key` from the lane. + // Must happen before `self.deploy(...)` so `TempoOpts::apply` picks up the nonce_key. + let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?; // Whether to broadcast the transaction or not let dry_run = !self.broadcast; - if self.unlocked { + // Launch browser signer if `--browser` flag is set + let browser = self.browser.run::().await?; + + if let Some(browser) = browser { + // Deploy with browser wallet + let deployer_address = browser.address(); + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + None, + Some(browser), + resolved_lane, + ) + .await + } else if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( @@ -171,18 +248,44 @@ impl CreateArgs { bin, params, provider, - chain_id, sender, config.transaction_timeout, id, dry_run, + None, + None, + resolved_lane, + ) + .await + } else if let Some(ak) = access_key { + // Tempo keychain mode: sign with access key and send raw + let signer = match pre_resolved_signer { + Some(s) => s, + None => self.eth.wallet.signer().await?, + }; + let deployer_address = ak.wallet_address; + self.deploy( + abi, + bin, + params, + provider, + deployer_address, + config.transaction_timeout, + id, + dry_run, + Some((signer, ak)), + None, + resolved_lane, ) .await } else { // Deploy with signer - let signer = self.eth.wallet.signer().await?; + let signer = match pre_resolved_signer { + Some(s) => s, + None => self.eth.wallet.signer().await?, + }; let deployer = signer.address(); - let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + let provider = AlloyProviderBuilder::<_, _, N>::default() .wallet(EthereumWallet::new(signer)) .connect_provider(provider); self.deploy( @@ -190,19 +293,21 @@ impl CreateArgs { bin, params, provider, - chain_id, deployer, config.transaction_timeout, id, dry_run, + None, + None, + resolved_lane, ) .await } } - /// Returns the provided chain id, if any. - fn chain_id(&self) -> Option { - self.eth.etherscan.chain.map(|chain| chain.id()) + /// Returns the resolved chain, if any. + const fn chain_id(&self) -> Option { + self.eth.etherscan.chain } /// Ensures the verify command can be executed. @@ -214,7 +319,6 @@ impl CreateArgs { async fn verify_preflight_check( &self, constructor_args: Option, - chain: u64, id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, @@ -225,11 +329,12 @@ impl CreateArgs { compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, + no_auto_detect: false, + use_solc: None, num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), - api_version: self.eth.etherscan.api_version, - chain: Some(chain.into()), + chain: self.chain_id(), }, rpc: Default::default(), flatten: false, @@ -244,14 +349,16 @@ impl CreateArgs { evm_version: self.build.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, guess_constructor_args: false, - compilation_profile: Some(id.profile.to_string()), + compilation_profile: Some(id.profile.clone()), + language: None, + creation_transaction_hash: None, }; // Check config for Etherscan API Keys to avoid preflight check failing if no // ETHERSCAN_API_KEY value set. let config = verify.load_config()?; verify.etherscan.key = - config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key); let context = verify.resolve_context().await?; @@ -261,25 +368,34 @@ impl CreateArgs { /// Deploys the contract #[expect(clippy::too_many_arguments)] - async fn deploy>( + async fn deploy>( self, abi: JsonAbi, bin: BytecodeObject, args: Vec, provider: P, - chain: u64, deployer_address: Address, timeout: u64, id: ArtifactId, dry_run: bool, - ) -> Result<()> { + tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>, + browser_signer: Option>, + resolved_lane: Option, + ) -> Result<()> + where + N::TransactionRequest: FoundryTransactionBuilder + serde::Serialize, + N::ReceiptResponse: serde::Serialize, + { + let chain = self.chain_id().context("chain ID not resolved")?; + let bin = bin.into_bytes().unwrap_or_default(); if bin.is_empty() { eyre::bail!("no bytecode found in bin object for {}", self.contract.name) } let provider = Arc::new(provider); - let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone(), timeout); + let factory = + ContractFactory::::new(abi.clone(), bin.clone(), provider.clone(), timeout); let is_args_empty = args.is_empty(); let mut deployer = @@ -290,53 +406,94 @@ impl CreateArgs { e } })?; - let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + let is_legacy = self.tx.legacy || chain.is_legacy(); deployer.tx.set_from(deployer_address); - deployer.tx.set_chain_id(chain); + deployer.tx.set_chain_id(chain.id()); // `to` field must be set explicitly, cannot be None. - if deployer.tx.to.is_none() { + if deployer.tx.to().is_none() { deployer.tx.set_create(); } - deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce { - Ok(nonce.to()) - } else { - provider.get_transaction_count(deployer_address).await - }?); - // set tx value if specified - if let Some(value) = self.tx.value { - deployer.tx.set_value(value); + // If Tempo chain fee token must be set + if chain.is_tempo() { + if let Some(fee_token) = self.tx.tempo.fee_token { + deployer.tx.set_fee_token(fee_token); + } else { + deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN); + } } - deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { - Ok(gas_limit.to()) - } else { - provider.estimate_gas(deployer.tx.clone()).await - }?); + // Apply user-provided gas, fee, nonce, and Tempo options. + self.tx.apply::(&mut deployer.tx, is_legacy); + + // Convert the CREATE into an AA-compatible call entry since Tempo AA + // transactions use a `calls` list instead of `to`+`input`. + if chain.is_tempo() { + deployer.tx.convert_create_to_call(); + } + + // For keychain mode, set key_id and nonce_key before gas estimation. + if let Some((_, ref ak)) = tempo_keychain { + deployer.tx.set_key_id(ak.key_address); + if deployer.tx.nonce_key().is_none() { + deployer.tx.set_nonce_key(U256::ZERO); + } + } + + // Fetch defaults from provider for values not specified by user. + if self.tx.nonce.is_none() && !self.tx.tempo.expiring_nonce { + deployer.tx.set_nonce(provider.get_transaction_count(deployer_address).await?); + } + + maybe_print_resolved_lane(resolved_lane.as_ref(), deployer.tx.nonce().unwrap_or_default())?; + + if let Some((_, ref ak)) = tempo_keychain { + deployer + .tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } + + // set access list if specified + if let Some(access_list) = match self.tx.access_list { + None => None, + Some(None) => Some(provider.create_access_list(&deployer.tx).await?.access_list), + Some(Some(ref access_list)) => Some(access_list.clone()), + } { + deployer.tx.set_access_list(access_list); + } + + if self.tx.gas_limit.is_none() { + let mut estimated = provider.estimate_gas(deployer.tx.clone()).await?; + + // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which + // costs more gas for signature verification on Tempo chains. Add a + // conservative buffer since we can't determine the signature type beforehand. + if browser_signer.is_some() && chain.is_tempo() { + estimated += TEMPO_BROWSER_GAS_BUFFER; + } + + deployer.tx.set_gas_limit(estimated); + } if is_legacy { - let gas_price = if let Some(gas_price) = self.tx.gas_price { - gas_price.to() - } else { - provider.get_gas_price().await? - }; - deployer.tx.set_gas_price(gas_price); - } else { + if self.tx.gas_price.is_none() { + deployer.tx.set_gas_price(provider.get_gas_price().await?); + } + } else if self.tx.gas_price.is_none() || self.tx.priority_gas_price.is_none() { let estimate = provider.estimate_eip1559_fees().await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - let priority_fee = if let Some(priority_fee) = self.tx.priority_gas_price { - priority_fee.to() - } else { - estimate.max_priority_fee_per_gas - }; - let max_fee = if let Some(max_fee) = self.tx.gas_price { - max_fee.to() - } else { - estimate.max_fee_per_gas - }; - - deployer.tx.set_max_fee_per_gas(max_fee); - deployer.tx.set_max_priority_fee_per_gas(priority_fee); + if self.tx.priority_gas_price.is_none() { + deployer.tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + if self.tx.gas_price.is_none() { + deployer.tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + } } // Before we actually deploy the contract we try check if the verify settings are valid @@ -350,11 +507,18 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + self.verify_preflight_check(constructor_args.clone(), &id).await?; } if dry_run { - if !shell::is_json() { + if shell::is_json() { + let output = json!({ + "contract": self.contract.name, + "transaction": &deployer.tx, + "abi":&abi + }); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + } else { sh_warn!("Dry run enabled, not broadcasting transaction\n")?; sh_println!("Contract: {}", self.contract.name)?; @@ -364,34 +528,87 @@ impl CreateArgs { )?; sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?; - sh_warn!("To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more.")?; - } else { - let output = json!({ - "contract": self.contract.name, - "transaction": &deployer.tx, - "abi":&abi - }); - sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + sh_warn!( + "To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more." + )?; } return Ok(()); } + let tempo_sponsor = self.tx.tempo.sponsor_config().await?; + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut deployer.tx, deployer_address).await?; + } + // Deploy the actual contract - let (deployed_contract, receipt) = deployer.send_with_receipt().await?; + let (deployed_contract, receipt) = if let Some(browser) = browser_signer { + // Browser wallet signs and sends the transaction + let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?; + + // Wait for the transaction to be confirmed, then fetch the receipt. + provider + .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash)) + .await? + .await?; + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?; + + if !receipt.status() { + eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}"); + } + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else if let Some((signer, ak)) = tempo_keychain { + // Tempo keychain mode: sign with access key provisioning and send raw + let raw_tx = deployer + .tx + .sign_with_access_key( + &provider, + &signer, + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + + let receipt = provider + .send_raw_transaction(&raw_tx) + .await? + .with_required_confirmations(1) + .with_timeout(Some(Duration::from_secs(timeout))) + .get_receipt() + .await?; + + let address = receipt + .contract_address() + .ok_or_else(|| eyre::eyre!("contract was not deployed"))?; + + (address, receipt) + } else { + deployer.send_with_receipt().await? + }; let address = deployed_contract; + let tx_hash = receipt.transaction_hash(); if shell::is_json() { let output = json!({ "deployer": deployer_address.to_string(), "deployedTo": address.to_string(), - "transactionHash": receipt.transaction_hash + "transactionHash": tx_hash }); sh_println!("{}", serde_json::to_string_pretty(&output)?)?; } else { sh_println!("Deployer: {deployer_address}")?; sh_println!("Deployed to: {address}")?; - sh_println!("Transaction hash: {:?}", receipt.transaction_hash)?; + sh_println!("Transaction hash: {tx_hash:?}")?; }; if !self.verify { @@ -401,11 +618,7 @@ impl CreateArgs { sh_println!("Starting contract verification...")?; let num_of_optimizations = if let Some(optimizer) = self.build.compiler.optimize { - if optimizer { - Some(self.build.compiler.optimizer_runs.unwrap_or(200)) - } else { - None - } + optimizer.then(|| self.build.compiler.optimizer_runs.unwrap_or(200)) } else { self.build.compiler.optimizer_runs }; @@ -416,12 +629,10 @@ impl CreateArgs { compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, + no_auto_detect: false, + use_solc: None, num_of_optimizations, - etherscan: EtherscanOpts { - key: self.eth.etherscan.key(), - api_version: self.eth.etherscan.api_version, - chain: Some(chain.into()), - }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) }, rpc: Default::default(), flatten: false, force: false, @@ -435,7 +646,9 @@ impl CreateArgs { evm_version: self.build.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, guess_constructor_args: false, - compilation_profile: Some(id.profile.to_string()), + compilation_profile: Some(id.profile.clone()), + language: None, + creation_transaction_hash: Some(tx_hash), }; sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?; verify.run().await @@ -450,6 +663,14 @@ impl CreateArgs { constructor: &Constructor, constructor_args: &[String], ) -> Result> { + if constructor.inputs.len() != constructor_args.len() { + eyre::bail!( + "Constructor argument count mismatch: expected {} but got {}", + constructor.inputs.len(), + constructor_args.len() + ); + } + let mut params = Vec::with_capacity(constructor.inputs.len()); for (input, arg) in constructor.inputs.iter().zip(constructor_args) { // resolve the input type directly @@ -482,30 +703,30 @@ impl figment::Provider for CreateArgs { /// compatibility with less-abstract Contracts. /// /// For full usage docs, see [`DeploymentTxFactory`]. -pub type ContractFactory

= DeploymentTxFactory

; +pub type ContractFactory = DeploymentTxFactory; /// Helper which manages the deployment transaction of a smart contract. It /// wraps a deployment transaction, and retrieves the contract address output /// by it. #[derive(Debug)] #[must_use = "ContractDeploymentTx does nothing unless you `send` it"] -pub struct ContractDeploymentTx { +pub struct ContractDeploymentTx { /// the actual deployer, exposed for overriding the defaults - pub deployer: Deployer

, + pub deployer: Deployer, /// marker for the `Contract` type to create afterwards /// /// this type will be used to construct it via `From::from(Contract)` _contract: PhantomData, } -impl Clone for ContractDeploymentTx { +impl Clone for ContractDeploymentTx { fn clone(&self) -> Self { Self { deployer: self.deployer.clone(), _contract: self._contract } } } -impl From> for ContractDeploymentTx { - fn from(deployer: Deployer

) -> Self { +impl From> for ContractDeploymentTx { + fn from(deployer: Deployer) -> Self { Self { deployer, _contract: PhantomData } } } @@ -513,21 +734,21 @@ impl From> for ContractDeploymentTx { /// Helper which manages the deployment transaction of a smart contract #[derive(Clone, Debug)] #[must_use = "Deployer does nothing unless you `send` it"] -pub struct Deployer

{ +pub struct Deployer { /// The deployer's transaction, exposed for overriding the defaults - pub tx: WithOtherFields, + pub tx: N::TransactionRequest, client: P, confs: usize, timeout: u64, } -impl> Deployer

{ +impl> Deployer { /// Broadcasts the contract deployment transaction and after waiting for it to /// be sufficiently confirmed (default: 1), it returns a tuple with the [`Address`] at the - /// deployed contract's address and the corresponding [`AnyTransactionReceipt`]. + /// deployed contract's address and the corresponding receipt. pub async fn send_with_receipt( self, - ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> { + ) -> Result<(Address, N::ReceiptResponse), ContractDeploymentError> { let receipt = self .client .borrow() @@ -538,8 +759,12 @@ impl> Deployer

{ .get_receipt() .await?; + if !receipt.status() { + return Err(ContractDeploymentError::DeploymentFailed(receipt.transaction_hash())); + } + let address = - receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?; + receipt.contract_address().ok_or(ContractDeploymentError::ContractNotDeployed)?; Ok((address, receipt)) } @@ -549,19 +774,20 @@ impl> Deployer

{ /// created which manages the Contract bytecode and Application Binary Interface /// (ABI), usually generated from the Solidity compiler. #[derive(Clone, Debug)] -pub struct DeploymentTxFactory

{ +pub struct DeploymentTxFactory { client: P, abi: JsonAbi, bytecode: Bytes, timeout: u64, + _network: PhantomData, } -impl + Clone> DeploymentTxFactory

{ +impl + Clone> DeploymentTxFactory { /// Creates a factory for deployment of the Contract with bytecode, and the /// constructor defined in the abi. The client will be used to send any deployment /// transaction. - pub fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self { - Self { client, abi, bytecode, timeout } + pub const fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self { + Self { client, abi, bytecode, timeout, _network: PhantomData } } /// Create a deployment tx using the provided tokens as constructor @@ -569,7 +795,10 @@ impl + Clone> DeploymentTxFactory

{ pub fn deploy_tokens( self, params: Vec, - ) -> Result, ContractDeploymentError> { + ) -> Result, ContractDeploymentError> + where + N::TransactionRequest: FoundryTransactionBuilder, + { // Encode the constructor args & concatenate with the bytecode if necessary let data: Bytes = match (self.abi.constructor(), params.is_empty()) { (None, false) => return Err(ContractDeploymentError::ConstructorError), @@ -585,8 +814,8 @@ impl + Clone> DeploymentTxFactory

{ }; // create the tx object. Since we're deploying a contract, `to` is `None` - let tx = WithOtherFields::new(TransactionRequest::default().input(data.into())); - + let mut tx = N::TransactionRequest::default(); + tx.set_input(data); Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) } } @@ -600,6 +829,8 @@ pub enum ContractDeploymentError { DetokenizationError(#[from] alloy_dyn_abi::Error), #[error("contract was not deployed")] ContractNotDeployed, + #[error("deployment transaction failed (receipt status 0): {0}")] + DeploymentFailed(alloy_primitives::TxHash), #[error(transparent)] RpcError(#[from] TransportError), } @@ -642,7 +873,7 @@ mod tests { "--chain-id", "9999", ]); - assert_eq!(args.chain_id(), Some(9999)); + assert_eq!(args.chain_id().map(|c| c.id()), Some(9999)); } #[test] diff --git a/crates/forge/src/cmd/doc/mod.rs b/crates/forge/src/cmd/doc/mod.rs index 73b78618cbb0e..2f0a4a51659b3 100644 --- a/crates/forge/src/cmd/doc/mod.rs +++ b/crates/forge/src/cmd/doc/mod.rs @@ -4,10 +4,10 @@ use eyre::Result; use forge_doc::{ ContractInheritance, Deployments, DocBuilder, GitSource, InferInlineHyperlinks, Inheritdoc, }; -use foundry_cli::opts::GH_REPO_PREFIX_REGEX; +use foundry_cli::{opts::GH_REPO_PREFIX_REGEX, utils::Git}; use foundry_common::compile::ProjectCompiler; -use foundry_config::{load_config_with_root, Config}; -use std::{path::PathBuf, process::Command}; +use foundry_config::{Config, load_config_with_root}; +use std::path::PathBuf; mod server; use server::Server; @@ -71,31 +71,26 @@ impl DocArgs { let root = &config.root; let project = config.project()?; let compiler = ProjectCompiler::new().quiet(true); - let _output = compiler.compile(&project)?; + let mut output = compiler.compile(&project)?; + let compiler = output.parser_mut().solc_mut().compiler_mut(); let mut doc_config = config.doc; if let Some(out) = self.out { doc_config.out = out; } - if doc_config.repository.is_none() { - // Attempt to read repo from git - if let Ok(output) = Command::new("git").args(["remote", "get-url", "origin"]).output() { - if !output.stdout.is_empty() { - let remote = String::from_utf8(output.stdout)?.trim().to_owned(); - if let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote) { - let brand = captures.name("brand").unwrap().as_str(); - let tld = captures.name("tld").unwrap().as_str(); - let project = GH_REPO_PREFIX_REGEX.replace(&remote, ""); - doc_config.repository = Some(format!( - "https://{brand}.{tld}/{}", - project.trim_end_matches(".git") - )); - } - } - } + // Attempt to read repo URL from git + if doc_config.repository.is_none() + && let Some(remote) = Git::new(root).remote_url("origin") + && let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote) + { + let brand = captures.name("brand").unwrap().as_str(); + let tld = captures.name("tld").unwrap().as_str(); + let project = GH_REPO_PREFIX_REGEX.replace(&remote, ""); + doc_config.repository = + Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))); } - let commit = foundry_cli::utils::Git::new(root).commit_hash(false, "HEAD").ok(); + let commit = Git::new(root).commit_hash(false, "HEAD").ok(); let mut builder = DocBuilder::new( root.clone(), @@ -120,7 +115,7 @@ impl DocArgs { builder = builder.with_preprocessor(Deployments { root: root.clone(), deployments }); } - builder.build()?; + builder.build(compiler)?; if self.serve { Server::new(doc_config.out) @@ -134,7 +129,7 @@ impl DocArgs { } /// Returns whether watch mode is enabled - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } diff --git a/crates/forge/src/cmd/doc/server.rs b/crates/forge/src/cmd/doc/server.rs index 09662270a4514..d9a83adda8721 100644 --- a/crates/forge/src/cmd/doc/server.rs +++ b/crates/forge/src/cmd/doc/server.rs @@ -1,5 +1,5 @@ -use axum::{routing::get_service, Router}; -use forge_doc::mdbook::{utils::fs::get_404_output_file, MDBook}; +use axum::{Router, routing::get_service}; +use forge_doc::mdbook_driver::MDBook; use std::{ io, net::{SocketAddr, ToSocketAddrs}, @@ -38,13 +38,13 @@ impl Server { } /// Set the port to serve on. - pub fn with_port(mut self, port: usize) -> Self { + pub const fn with_port(mut self, port: usize) -> Self { self.port = port; self } /// Set whether to open the browser after serving. - pub fn open(mut self, open: bool) -> Self { + pub const fn open(mut self, open: bool) -> Self { self.open = open; self } @@ -67,12 +67,11 @@ impl Server { .next() .ok_or_else(|| eyre::eyre!("no address found for {}", address))?; let build_dir = book.build_dir_for("html"); - let input_404 = book + let file_404 = book .config - .get("output.html.input-404") - .and_then(|v| v.as_str()) - .map(ToString::to_string); - let file_404 = get_404_output_file(&input_404); + .html_config() + .map(|c| c.get_404_output_file()) + .unwrap_or_else(|| "404.html".to_string()); let serving_url = format!("http://{address}"); sh_println!("Serving on: {serving_url}")?; @@ -94,7 +93,7 @@ impl Server { async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) -> io::Result<()> { let file_404 = build_dir.join(file_404); let svc = ServeDir::new(build_dir).not_found_service(ServeFile::new(file_404)); - let app = Router::new().nest_service("/", get_service(svc)); + let app = Router::new().fallback_service(get_service(svc)); let tcp_listener = tokio::net::TcpListener::bind(address).await?; axum::serve(tcp_listener, app.into_make_service()).await } diff --git a/crates/forge/src/cmd/eip712.rs b/crates/forge/src/cmd/eip712.rs index 3c85840673d6a..a635820e66a9c 100644 --- a/crates/forge/src/cmd/eip712.rs +++ b/crates/forge/src/cmd/eip712.rs @@ -1,13 +1,20 @@ +use alloy_primitives::{B256, keccak256}; use clap::{Parser, ValueHint}; -use eyre::{Ok, OptionExt, Result}; +use eyre::Result; use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; -use foundry_common::compile::ProjectCompiler; -use foundry_compilers::artifacts::{ - output_selection::OutputSelection, - visitor::{Visitor, Walk}, - ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, TypeName, +use foundry_common::{compile::ProjectCompiler, shell}; +use serde::Serialize; +use solar::sema::{ + Gcx, Hir, + hir::StructId, + ty::{Ty, TyKind}, +}; +use std::{ + collections::BTreeMap, + fmt::{Display, Formatter, Result as FmtResult, Write}, + ops::ControlFlow, + path::{Path, PathBuf}, }; -use std::{collections::BTreeMap, fmt::Write, path::PathBuf}; foundry_config::impl_figment_convert!(Eip712Args, build); @@ -22,234 +29,212 @@ pub struct Eip712Args { build: BuildOpts, } +#[derive(Debug, Serialize)] +struct Eip712Output { + path: String, + #[serde(rename = "type")] + ty: String, + hash: B256, +} + +impl Display for Eip712Output { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + writeln!(f, "{}:", self.path)?; + writeln!(f, " - type: {}", self.ty)?; + writeln!(f, " - hash: {}", self.hash) + } +} + impl Eip712Args { pub fn run(self) -> Result<()> { - let config = self.load_config()?; - let mut project = config.ephemeral_project()?; - let target_path = dunce::canonicalize(self.target_path)?; - project.update_output_selection(|selection| { - *selection = OutputSelection::ast_output_selection(); - }); - - let output = ProjectCompiler::new().files([target_path.clone()]).compile(&project)?; - - // Collect ASTs by getting them from sources and converting into strongly typed - // `SourceUnit`s. - let asts = output - .into_output() - .sources - .into_iter() - .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) - .map(|(path, ast)| { - Ok((path, serde_json::from_str::(&serde_json::to_string(&ast)?)?)) - }) - .collect::>>()?; - - let resolver = Resolver::new(&asts); - - let target_ast = asts - .get(&target_path) - .ok_or_else(|| eyre::eyre!("Could not find AST for target file {target_path:?}"))?; - - let structs_in_target = { - let mut collector = StructCollector::default(); - target_ast.walk(&mut collector); - collector.0 - }; - - for id in structs_in_target.keys() { - if let Some(resolved) = resolver.resolve_struct_eip712(*id)? { - sh_println!("{resolved}\n")?; + let config = self.build.load_config()?; + let project = config.solar_project()?; + let mut output = ProjectCompiler::new().files([self.target_path]).compile(&project)?; + let compiler = output.parser_mut().solc_mut().compiler_mut(); + compiler.enter_mut(|compiler| -> Result<()> { + let Ok(ControlFlow::Continue(())) = compiler.lower_asts() else { return Ok(()) }; + let gcx = compiler.gcx(); + let resolver = Resolver::new(gcx); + + let outputs = resolver + .struct_ids() + .filter_map(|id| { + let resolved = resolver.resolve_struct_eip712(id)?; + Some(Eip712Output { + path: resolver.get_struct_path(id), + hash: keccak256(resolved.as_bytes()), + ty: resolved, + }) + }) + .collect::>(); + + if shell::is_json() { + sh_println!("{json}", json = serde_json::to_string_pretty(&outputs)?)?; + } else { + for output in &outputs { + sh_println!("{output}")?; + } } - } - Ok(()) - } -} + Ok(()) + })?; -/// AST [Visitor] used for collecting struct definitions. -#[derive(Debug, Clone, Default)] -pub struct StructCollector(pub BTreeMap); + // `compiler.sess()` inside of `ProjectCompileOutput` is built with `with_buffer_emitter`. + let diags = compiler.sess().dcx.emitted_diagnostics().unwrap(); + if compiler.sess().dcx.has_errors().is_err() { + eyre::bail!("{diags}"); + } else { + let _ = sh_eprint!("{diags}"); + } -impl Visitor for StructCollector { - fn visit_struct_definition(&mut self, def: &StructDefinition) { - self.0.insert(def.id, def.clone()); + Ok(()) } } -/// Collects mapping from AST id of type definition to representation of this type for EIP-712 -/// encoding. +/// Generates the EIP-712 `encodeType` string for a given struct. /// -/// For now, maps contract definitions to `address` and enums to `uint8`. -#[derive(Debug, Clone, Default)] -struct SimpleCustomTypesCollector(BTreeMap); +/// Requires a reference to the source HIR. +pub struct Resolver<'gcx> { + gcx: Gcx<'gcx>, +} -impl Visitor for SimpleCustomTypesCollector { - fn visit_contract_definition(&mut self, def: &ContractDefinition) { - self.0.insert(def.id, "address".to_string()); +impl<'gcx> Resolver<'gcx> { + /// Constructs a new [`Resolver`] for the supplied [`Hir`] instance. + pub const fn new(gcx: Gcx<'gcx>) -> Self { + Self { gcx } } - fn visit_enum_definition(&mut self, def: &EnumDefinition) { - self.0.insert(def.id, "uint8".to_string()); + #[inline] + fn hir(&self) -> &'gcx Hir<'gcx> { + &self.gcx.hir } -} - -pub struct Resolver { - simple_types: BTreeMap, - structs: BTreeMap, -} - -impl Resolver { - pub fn new(asts: &BTreeMap) -> Self { - let simple_types = { - let mut collector = SimpleCustomTypesCollector::default(); - asts.values().for_each(|ast| ast.walk(&mut collector)); - - collector.0 - }; - let structs = { - let mut collector = StructCollector::default(); - asts.values().for_each(|ast| ast.walk(&mut collector)); - collector.0 - }; + /// Returns the [`StructId`]s of every user-defined struct in source order. + pub fn struct_ids(&self) -> impl Iterator { + self.hir().strukt_ids() + } - Self { simple_types, structs } + /// Returns the path for a struct, with the format: `file.sol > MyContract > MyStruct` + pub fn get_struct_path(&self, id: StructId) -> String { + let strukt = self.hir().strukt(id).name.as_str(); + match self.hir().strukt(id).contract { + Some(cid) => { + let full_name = self.gcx.contract_fully_qualified_name(cid).to_string(); + let relevant = Path::new(&full_name) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(&full_name); + + if let Some((file, contract)) = relevant.rsplit_once(':') { + format!("{file} > {contract} > {strukt}") + } else { + format!("{relevant} > {strukt}") + } + } + None => strukt.to_string(), + } } - /// Converts a given struct definition into EIP-712 `encodeType` representation. + /// Converts a given struct into its EIP-712 `encodeType` representation. /// - /// Returns `None` if struct contains any fields that are not supported by EIP-712 (e.g. - /// mappings or function pointers). - pub fn resolve_struct_eip712(&self, id: usize) -> Result> { + /// Returns `None` if the struct, or any of its fields, contains constructs + /// not supported by EIP-712 (mappings, function types, errors, etc). + pub fn resolve_struct_eip712(&self, id: StructId) -> Option { let mut subtypes = BTreeMap::new(); - subtypes.insert(self.structs[&id].name.clone(), id); + subtypes.insert(self.hir().strukt(id).name.as_str().into(), id); self.resolve_eip712_inner(id, &mut subtypes, true, None) } fn resolve_eip712_inner( &self, - id: usize, - subtypes: &mut BTreeMap, + id: StructId, + subtypes: &mut BTreeMap, append_subtypes: bool, rename: Option<&str>, - ) -> Result> { - let def = &self.structs[&id]; - let mut result = format!("{}(", rename.unwrap_or(&def.name)); - - for (idx, member) in def.members.iter().enumerate() { - let Some(ty) = self.resolve_type( - member.type_name.as_ref().ok_or_eyre("missing type name")?, - subtypes, - )? - else { - return Ok(None) - }; - - write!(result, "{ty} {name}", name = member.name)?; - - if idx < def.members.len() - 1 { + ) -> Option { + let def = self.hir().strukt(id); + let mut result = format!("{}(", rename.unwrap_or(def.name.as_str())); + + for (idx, field_id) in def.fields.iter().enumerate() { + let field = self.hir().variable(*field_id); + let ty = self.resolve_type(self.gcx.type_of_hir_ty(&field.ty), subtypes)?; + + write!(result, "{ty} {name}", name = field.name?.as_str()).ok()?; + + if idx < def.fields.len() - 1 { result.push(','); } } result.push(')'); - if !append_subtypes { - return Ok(Some(result)) - } + if append_subtypes { + for (subtype_name, subtype_id) in + subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::>() + { + if subtype_id == id { + continue; + } + let encoded_subtype = + self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))?; - for (subtype_name, subtype_id) in - subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::>() - { - if subtype_id == id { - continue + result.push_str(&encoded_subtype); } - let Some(encoded_subtype) = - self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))? - else { - return Ok(None) - }; - result.push_str(&encoded_subtype); } - Ok(Some(result)) + Some(result) } - /// Converts given [TypeName] into a type which can be converted to - /// [`alloy_dyn_abi::DynSolType`]. - /// - /// Returns `None` if the type is not supported for EIP712 encoding. - pub fn resolve_type( + fn resolve_type( &self, - type_name: &TypeName, - subtypes: &mut BTreeMap, - ) -> Result> { - match type_name { - TypeName::FunctionTypeName(_) | TypeName::Mapping(_) => Ok(None), - TypeName::ElementaryTypeName(ty) => Ok(Some(ty.name.clone())), - TypeName::ArrayTypeName(ty) => { - let Some(inner) = self.resolve_type(&ty.base_type, subtypes)? else { - return Ok(None) + ty: Ty<'gcx>, + subtypes: &mut BTreeMap, + ) -> Option { + let ty = ty.peel_refs(); + match ty.kind { + TyKind::Elementary(elem_ty) => Some(elem_ty.to_abi_str().to_string()), + TyKind::Array(element_ty, size) => { + let inner_type = self.resolve_type(element_ty, subtypes)?; + let size = size.to_string(); + Some(format!("{inner_type}[{size}]")) + } + TyKind::DynArray(element_ty) => { + let inner_type = self.resolve_type(element_ty, subtypes)?; + Some(format!("{inner_type}[]")) + } + TyKind::Udvt(ty, _) => self.resolve_type(ty, subtypes), + TyKind::Struct(id) => { + let def = self.hir().strukt(id); + let name = match subtypes.iter().find(|(_, cached_id)| id == **cached_id) { + Some((name, _)) => name.clone(), + None => { + // Otherwise, assign new name + let mut i = 0; + let mut name = def.name.as_str().into(); + while subtypes.contains_key(&name) { + i += 1; + name = format!("{}_{i}", def.name.as_str()); + } + + subtypes.insert(name.clone(), id); + + // Recursively resolve fields to populate subtypes + for &field_id in def.fields { + let field_ty = self.gcx.type_of_item(field_id.into()); + self.resolve_type(field_ty, subtypes)?; + } + name + } }; - let len = parse_array_length(&ty.type_descriptions)?; - Ok(Some(format!("{inner}[{}]", len.unwrap_or("")))) - } - TypeName::UserDefinedTypeName(ty) => { - if let Some(name) = self.simple_types.get(&(ty.referenced_declaration as usize)) { - Ok(Some(name.clone())) - } else if let Some(def) = self.structs.get(&(ty.referenced_declaration as usize)) { - let name = - // If we've already seen struct with this ID, just use assigned name. - if let Some((name, _)) = subtypes.iter().find(|(_, id)| **id == def.id) { - name.clone() - } else { - // Otherwise, assign new name. - let mut i = 0; - let mut name = def.name.clone(); - while subtypes.contains_key(&name) { - i += 1; - name = format!("{}_{i}", def.name); - } - - subtypes.insert(name.clone(), def.id); - - // iterate over members to check if they are resolvable and to populate subtypes - for member in &def.members { - if self.resolve_type( - member.type_name.as_ref().ok_or_eyre("missing type name")?, - subtypes, - )? - .is_none() - { - return Ok(None) - } - } - name - }; - - return Ok(Some(name)) - } else { - return Ok(None) - } + Some(name) } + // For now, map enums to `uint8` + TyKind::Enum(_) => Some("uint8".to_string()), + // For now, map contracts to `address` + TyKind::Contract(_) => Some("address".to_string()), + // EIP-712 doesn't support tuples (should use structs), functions, mappings, nor errors + _ => None, } } } - -fn parse_array_length(type_description: &TypeDescriptions) -> Result> { - let type_string = - type_description.type_string.as_ref().ok_or_eyre("missing typeString for array type")?; - let Some(inside_brackets) = - type_string.rsplit_once("[").and_then(|(_, right)| right.split("]").next()) - else { - eyre::bail!("failed to parse array type string: {type_string}") - }; - - if inside_brackets.is_empty() { - Ok(None) - } else { - Ok(Some(inside_brackets)) - } -} diff --git a/crates/forge/src/cmd/flatten.rs b/crates/forge/src/cmd/flatten.rs index 8bc5d44724619..3dc508d577cf9 100644 --- a/crates/forge/src/cmd/flatten.rs +++ b/crates/forge/src/cmd/flatten.rs @@ -4,12 +4,7 @@ use foundry_cli::{ opts::{BuildOpts, ProjectPathOpts}, utils::LoadConfig, }; -use foundry_common::{compile::with_compilation_reporter, fs}; -use foundry_compilers::{ - compilers::solc::SolcLanguage, - error::SolcError, - flatten::{Flattener, FlattenerError}, -}; +use foundry_common::{flatten, fs}; use std::path::PathBuf; /// CLI arguments for `forge flatten`. @@ -44,21 +39,7 @@ impl FlattenArgs { let project = config.ephemeral_project()?; let target_path = dunce::canonicalize(target_path)?; - - let flattener = - with_compilation_reporter(true, || Flattener::new(project.clone(), &target_path)); - - let flattened = match flattener { - Ok(flattener) => Ok(flattener.flatten()), - Err(FlattenerError::Compilation(_)) => { - // Fallback to the old flattening implementation if we couldn't compile the target - // successfully. This would be the case if the target has invalid - // syntax. (e.g. Solang) - project.paths.with_language::().flatten(&target_path) - } - Err(FlattenerError::Other(err)) => Err(err), - } - .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?; + let flattened = flatten(project, &target_path)?; match output { Some(output) => { diff --git a/crates/forge/src/cmd/fmt.rs b/crates/forge/src/cmd/fmt.rs index 104c3224829c5..b476cf97a130e 100644 --- a/crates/forge/src/cmd/fmt.rs +++ b/crates/forge/src/cmd/fmt.rs @@ -1,18 +1,19 @@ use super::watch::WatchArgs; use clap::{Parser, ValueHint}; -use eyre::{Context, Result}; -use forge_fmt::{format_to, parse}; +use eyre::Result; use foundry_cli::utils::{FoundryPathExt, LoadConfig}; -use foundry_common::fs; +use foundry_common::{errors::convert_solar_errors, fs}; use foundry_compilers::{compilers::solc::SolcLanguage, solc::SOLC_EXTENSIONS}; use foundry_config::{filter::expand_globs, impl_figment_convert_basic}; use rayon::prelude::*; use similar::{ChangeTag, TextDiff}; +use solar::sema::Compiler; use std::{ fmt::{self, Write}, io, - io::{Read, Write as _}, + io::Write as _, path::{Path, PathBuf}, + sync::Arc, }; use yansi::{Color, Paint, Style}; @@ -50,46 +51,72 @@ impl_figment_convert_basic!(FmtArgs); impl FmtArgs { pub fn run(self) -> Result<()> { let config = self.load_config()?; + let cwd = std::env::current_dir()?; // Expand ignore globs and canonicalize from the get go let ignored = expand_globs(&config.root, config.fmt.ignore.iter())? .iter() - .flat_map(foundry_common::fs::canonicalize_path) + .flat_map(fs::canonicalize_path) .collect::>(); - let cwd = std::env::current_dir()?; + // Expand lib globs separately - we only exclude these during discovery, not explicit paths + let libs = expand_globs(&config.root, config.libs.iter().filter_map(|p| p.to_str()))? + .iter() + .flat_map(fs::canonicalize_path) + .collect::>(); + + // Helper to check if a file path is under any ignored or lib directory + let is_under_ignored_dir = |file_path: &Path, include_libs: bool| -> bool { + let check_against_dir = |dir: &PathBuf| { + file_path.starts_with(dir) + || cwd.join(file_path).starts_with(dir) + || fs::canonicalize_path(file_path).is_ok_and(|p| p.starts_with(dir)) + }; + + ignored.iter().any(&check_against_dir) + || (include_libs && libs.iter().any(&check_against_dir)) + }; + let input = match &self.paths[..] { [] => { - // Retrieve the project paths, and filter out the ignored ones. + // Retrieve the project paths, and filter out the ignored ones and libs. let project_paths: Vec = config .project_paths::() .input_files_iter() - .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p)))) + .filter(|p| { + !(ignored.contains(p) + || ignored.contains(&cwd.join(p)) + || is_under_ignored_dir(p, true)) + }) .collect(); Input::Paths(project_paths) } - [one] if one == Path::new("-") => { - let mut s = String::new(); - io::stdin().read_to_string(&mut s).expect("Failed to read from stdin"); - Input::Stdin(s) - } + [one] if one == Path::new("-") => Input::Stdin, paths => { let mut inputs = Vec::with_capacity(paths.len()); for path in paths { - if !ignored.is_empty() && - ((path.is_absolute() && ignored.contains(path)) || - ignored.contains(&cwd.join(path))) + // Check if path is in ignored directories + if !ignored.is_empty() + && ((path.is_absolute() && ignored.contains(path)) + || ignored.contains(&cwd.join(path))) { - continue + continue; } if path.is_dir() { - inputs.extend(foundry_compilers::utils::source_files_iter( - path, - SOLC_EXTENSIONS, - )); + // If the input directory is not a lib directory, make sure to ignore libs. + let exclude_libs = !is_under_ignored_dir(path, true); + inputs.extend( + foundry_compilers::utils::source_files_iter(path, SOLC_EXTENSIONS) + .filter(|p| { + !(ignored.contains(p) + || ignored.contains(&cwd.join(p)) + || is_under_ignored_dir(p, exclude_libs)) + }), + ); } else if path.is_sol() { - inputs.push(path.to_path_buf()); + // Explicit file paths are always included, even if in a lib + inputs.push(path.clone()); } else { warn!("Cannot process path {}", path.display()); } @@ -98,114 +125,110 @@ impl FmtArgs { } }; - let format = |source: String, path: Option<&Path>| -> Result<_> { - let name = match path { - Some(path) => path.strip_prefix(&config.root).unwrap_or(path).display().to_string(), - None => "stdin".to_string(), - }; - - let parsed = parse(&source).wrap_err_with(|| { - format!("Failed to parse Solidity code for {name}. Leaving source unchanged.") - })?; - - if !parsed.invalid_inline_config_items.is_empty() { - for (loc, warning) in &parsed.invalid_inline_config_items { - let mut lines = source[..loc.start().min(source.len())].split('\n'); - let col = lines.next_back().unwrap().len() + 1; - let row = lines.count() + 1; - sh_warn!("[{}:{}:{}] {}", name, row, col, warning)?; - } - } - - let mut output = String::new(); - format_to(&mut output, parsed, config.fmt.clone()).unwrap(); - - solang_parser::parse(&output, 0).map_err(|diags| { - eyre::eyre!( - "Failed to construct valid Solidity code for {name}. Leaving source unchanged.\n\ - Debug info: {diags:?}\n\ - Formatted output:\n\n{output}" - ) - })?; - - let diff = TextDiff::from_lines(&source, &output); - let new_format = diff.ratio() < 1.0; - if self.check || path.is_none() { - if self.raw { - sh_print!("{output}")?; - } + let mut compiler = Compiler::new( + solar::interface::Session::builder().with_buffer_emitter(Default::default()).build(), + ); - // If new format then compute diff summary. - if new_format { - return Ok(Some(format_diff_summary(&name, &diff))) - } - } else if let Some(path) = path { - // If new format then write it on disk. - if new_format { - fs::write(path, output)?; - } - } - Ok(None) - }; - - let diffs = match input { - Input::Stdin(source) => format(source, None).map(|diff| vec![diff]), - Input::Paths(paths) => { - if paths.is_empty() { + // Parse, format, and check the diffs. + compiler.enter_mut(|compiler| { + let mut pcx = compiler.parse(); + pcx.set_resolve_imports(false); + match input { + Input::Paths(paths) if paths.is_empty() => { sh_warn!( "Nothing to format.\n\ HINT: If you are working outside of the project, \ try providing paths to your source files: `forge fmt `" )?; - return Ok(()) + return Ok(()); } - paths - .par_iter() - .map(|path| { - let source = fs::read_to_string(path)?; - format(source, Some(path)) - }) - .collect() + Input::Paths(paths) => _ = pcx.par_load_files(paths), + Input::Stdin => _ = pcx.load_stdin(), } - }?; + pcx.parse(); + + let gcx = compiler.gcx(); + let fmt_config = Arc::new(config.fmt); + let diffs: Vec = gcx + .sources + .raw + .par_iter() + .filter_map(|source_unit| { + let path = source_unit.file.name.as_real(); + let original = source_unit.file.src.as_str(); + let formatted = forge_fmt::format_ast(gcx, source_unit, fmt_config.clone())?; + let from_stdin = path.is_none(); + + // Return formatted code when read from stdin and raw enabled. + // + if from_stdin && self.raw { + return Some(Ok(formatted)); + } - let mut diffs = diffs.iter().flatten(); - if let Some(first) = diffs.next() { - // This branch is only reachable with stdin or --check + if original == formatted { + return None; + } - if !self.raw { + if self.check || from_stdin { + let summary = if self.raw { + formatted + } else { + let name = match path { + Some(path) => path + .strip_prefix(&config.root) + .unwrap_or(path) + .display() + .to_string(), + None => "stdin".to_string(), + }; + format_diff_summary(&name, &TextDiff::from_lines(original, &formatted)) + }; + Some(Ok(summary)) + } else if let Some(path) = path { + match fs::write(path, formatted) { + Ok(()) => {} + Err(e) => return Some(Err(e.into())), + } + let _ = sh_println!("Formatted {}", path.display()); + None + } else { + unreachable!() + } + }) + .collect::>()?; + + if !diffs.is_empty() { + // This block is only reached in --check mode when files need formatting. let mut stdout = io::stdout().lock(); - let first = std::iter::once(first); - for (i, diff) in first.chain(diffs).enumerate() { + for (i, diff) in diffs.iter().enumerate() { if i > 0 { let _ = stdout.write_all(b"\n"); } let _ = stdout.write_all(diff.as_bytes()); } + if self.check { + std::process::exit(1); + } } - if self.check { - std::process::exit(1); - } - } - - Ok(()) + convert_solar_errors(compiler.dcx()) + }) } /// Returns whether `FmtArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } } -struct Line(Option); - #[derive(Debug)] enum Input { - Stdin(String), + Stdin, Paths(Vec), } +struct Line(Option); + impl fmt::Display for Line { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { diff --git a/crates/forge/src/cmd/geiger.rs b/crates/forge/src/cmd/geiger.rs index 68694a3688ecf..dfc5733036dc8 100644 --- a/crates/forge/src/cmd/geiger.rs +++ b/crates/forge/src/cmd/geiger.rs @@ -1,89 +1,38 @@ use clap::{Parser, ValueHint}; -use eyre::{Result, WrapErr}; -use foundry_cli::utils::LoadConfig; -use foundry_compilers::{resolver::parse::SolData, Graph}; -use foundry_config::{impl_figment_convert_basic, Config}; -use itertools::Itertools; -use solar_parse::{ast, ast::visit::Visit, interface::Session}; -use std::{ - ops::ControlFlow, - path::{Path, PathBuf}, -}; +use eyre::Result; +use foundry_cli::opts::BuildOpts; +use foundry_config::{DenyLevel, impl_figment_convert}; +use std::path::PathBuf; /// CLI arguments for `forge geiger`. +/// +/// This command is an alias for `forge lint --only-lint unsafe-cheatcode` +/// and detects usage of unsafe cheat codes in a project and its dependencies. #[derive(Clone, Debug, Parser)] pub struct GeigerArgs { /// Paths to files or directories to detect. #[arg( - conflicts_with = "root", value_hint = ValueHint::FilePath, value_name = "PATH", - num_args(1..), + num_args(0..) )] paths: Vec, - /// The project's root path. - /// - /// By default root of the Git repository, if in one, - /// or the current working directory. - #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] - root: Option, - - /// Globs to ignore. - #[arg( - long, - value_hint = ValueHint::FilePath, - value_name = "PATH", - num_args(1..), - )] - ignore: Vec, - #[arg(long, hide = true)] check: bool, + #[arg(long, hide = true)] full: bool, + + #[command(flatten)] + build: BuildOpts, } -impl_figment_convert_basic!(GeigerArgs); +impl_figment_convert!(GeigerArgs, build); impl GeigerArgs { - pub fn sources(&self, config: &Config) -> Result> { - let cwd = std::env::current_dir()?; - - let mut sources: Vec = { - if self.paths.is_empty() { - let paths = config.project_paths(); - Graph::::resolve(&paths)? - .files() - .keys() - .filter(|f| !paths.has_library_ancestor(f)) - .cloned() - .collect() - } else { - self.paths - .iter() - .flat_map(|path| foundry_common::fs::files_with_ext(path, "sol")) - .unique() - .collect() - } - }; - - sources.retain_mut(|path| { - let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(&path) }; - *path = abs_path.strip_prefix(&cwd).unwrap_or(&abs_path).to_path_buf(); - !self.ignore.iter().any(|ignore| { - if ignore.is_absolute() { - abs_path.starts_with(ignore) - } else { - abs_path.starts_with(cwd.join(ignore)) - } - }) - }); - - Ok(sources) - } - - pub fn run(self) -> Result { + pub fn run(self) -> Result<()> { + // Deprecated flags warnings if self.check { sh_warn!("`--check` is deprecated as it's now the default behavior\n")?; } @@ -91,73 +40,20 @@ impl GeigerArgs { sh_warn!("`--full` is deprecated as reports are not generated anymore\n")?; } - let config = self.load_config()?; - let sources = self.sources(&config).wrap_err("Failed to resolve files")?; - - if config.ffi { - sh_warn!("FFI enabled\n")?; - } - - let mut sess = Session::builder().with_stderr_emitter().build(); - sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); - let unsafe_cheatcodes = &[ - "ffi".to_string(), - "readFile".to_string(), - "readLine".to_string(), - "writeFile".to_string(), - "writeLine".to_string(), - "removeFile".to_string(), - "closeFile".to_string(), - "setEnv".to_string(), - "deriveKey".to_string(), - ]; - Ok(sess - .enter(|| sources.iter().map(|file| lint_file(&sess, unsafe_cheatcodes, file)).sum())) - } -} - -fn lint_file(sess: &Session, unsafe_cheatcodes: &[String], path: &Path) -> usize { - try_lint_file(sess, unsafe_cheatcodes, path).unwrap_or(0) -} - -fn try_lint_file( - sess: &Session, - unsafe_cheatcodes: &[String], - path: &Path, -) -> solar_parse::interface::Result { - let arena = solar_parse::ast::Arena::new(); - let mut parser = solar_parse::Parser::from_file(sess, &arena, path)?; - let ast = parser.parse_file().map_err(|e| e.emit())?; - let mut visitor = Visitor::new(sess, unsafe_cheatcodes); - let _ = visitor.visit_source_unit(&ast); - Ok(visitor.count) -} + sh_warn!( + "`forge geiger` is deprecated, as it is just an alias for `forge lint --only-lint unsafe-cheatcode`\n" + )?; -struct Visitor<'a> { - sess: &'a Session, - count: usize, - unsafe_cheatcodes: &'a [String], -} - -impl<'a> Visitor<'a> { - fn new(sess: &'a Session, unsafe_cheatcodes: &'a [String]) -> Self { - Self { sess, count: 0, unsafe_cheatcodes } - } -} - -impl<'ast> Visit<'ast> for Visitor<'_> { - type BreakValue = solar_parse::interface::data_structures::Never; + // Convert geiger command to lint command with specific lint filter + let mut lint_args = crate::cmd::lint::LintArgs { + paths: self.paths, + severity: None, + lint: Some(vec!["unsafe-cheatcode".to_string()]), + build: self.build, + }; + lint_args.build.deny = Some(DenyLevel::Notes); - fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow { - if let ast::ExprKind::Call(lhs, _args) = &expr.kind { - if let ast::ExprKind::Member(_lhs, member) = &lhs.kind { - if self.unsafe_cheatcodes.iter().any(|c| c.as_str() == member.as_str()) { - let msg = format!("usage of unsafe cheatcode `vm.{member}`"); - self.sess.dcx.err(msg).span(member.span).emit(); - self.count += 1; - } - } - } - self.walk_expr(expr) + // Run the lint command with the geiger-specific configuration + lint_args.run() } } diff --git a/crates/forge/src/cmd/generate/mod.rs b/crates/forge/src/cmd/generate/mod.rs index f1f69d8385618..cc7a67102a1b3 100644 --- a/crates/forge/src/cmd/generate/mod.rs +++ b/crates/forge/src/cmd/generate/mod.rs @@ -26,6 +26,8 @@ pub struct GenerateTestArgs { impl GenerateTestArgs { pub fn run(self) -> Result<()> { + sh_warn!("`forge generate` is deprecated and will be removed in a future version")?; + let contract_name = format_identifier(&self.contract_name, true); let instance_name = format_identifier(&self.contract_name, false); diff --git a/crates/forge/src/cmd/init.rs b/crates/forge/src/cmd/init.rs index 7866f14d63908..e75f8a356f91b 100644 --- a/crates/forge/src/cmd/init.rs +++ b/crates/forge/src/cmd/init.rs @@ -5,6 +5,7 @@ use foundry_cli::utils::Git; use foundry_common::fs; use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::Config; +use foundry_evm_networks::{NetworkConfigs, NetworkVariant}; use std::path::{Path, PathBuf}; use yansi::Paint; @@ -37,15 +38,54 @@ pub struct InitArgs { #[arg(long, conflicts_with = "template")] pub vscode: bool, + /// Initialize a Vyper project template. + #[arg(long, conflicts_with = "template")] + pub vyper: bool, + + /// Initialize a project template for the specified network in Foundry. + #[arg(long, short, num_args = 1, value_name = "NETWORK", conflicts_with_all = &["vyper", "template"])] + pub network: Option, + + /// Use the parent git repository instead of initializing a new one. + /// Only valid if the target is in a git repository. + #[arg(long, conflicts_with = "template")] + pub use_parent_git: bool, + + /// Do not create example contracts (Counter.sol, Counter.t.sol, Counter.s.sol). + #[arg(long, conflicts_with = "template")] + pub empty: bool, + + /// Do not create an initial commit. + /// + /// This is a noop flag kept for backwards compatibility, as `forge init` no longer commits by + /// default. Use `--commit` to opt into creating a commit. + #[arg(long, hide = true)] + pub no_commit: bool, + #[command(flatten)] pub install: DependencyInstallOpts, } impl InitArgs { - pub fn run(self) -> Result<()> { - let Self { root, template, branch, install, offline, force, vscode } = self; + pub async fn run(self) -> Result<()> { + let Self { + root, + template, + branch, + install, + offline, + force, + vscode, + use_parent_git, + vyper, + network, + empty, + no_commit: _, + } = self; let DependencyInstallOpts { shallow, no_git, commit } = install; + let tempo = matches!(network, Some(NetworkVariant::Tempo)); + // create the root dir if it does not exist if !root.exists() { fs::create_dir_all(&root)?; @@ -118,22 +158,90 @@ impl InitArgs { let script = root.join("script"); fs::create_dir_all(&script)?; - // write the contract file - let contract_path = src.join("Counter.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.sol"))?; - // write the tests - let contract_path = test.join("Counter.t.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.t.sol"))?; - // write the script - let contract_path = script.join("Counter.s.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.s.sol"))?; - // Write the default README file + // Only create example contracts if not disabled + if !empty { + if vyper { + // write the contract file + let contract_path = src.join("Counter.vy"); + fs::write( + contract_path, + include_str!("../../assets/vyper/CounterTemplate.vy"), + )?; + let interface_path = src.join("ICounter.sol"); + fs::write( + interface_path, + include_str!("../../assets/vyper/ICounterTemplate.sol"), + )?; + + // write the tests + let contract_path = test.join("Counter.t.sol"); + fs::write( + contract_path, + include_str!("../../assets/vyper/CounterTemplate.t.sol"), + )?; + + // write the script + let contract_path = script.join("Counter.s.sol"); + fs::write( + contract_path, + include_str!("../../assets/vyper/CounterTemplate.s.sol"), + )?; + } else if tempo { + // write the contract file + let contract_path = src.join("Mail.sol"); + fs::write(contract_path, include_str!("../../assets/tempo/MailTemplate.sol"))?; + + // write the tests + let contract_path = test.join("Mail.t.sol"); + fs::write( + contract_path, + include_str!("../../assets/tempo/MailTemplate.t.sol"), + )?; + + // write the script + let contract_path = script.join("Mail.s.sol"); + fs::write( + contract_path, + include_str!("../../assets/tempo/MailTemplate.s.sol"), + )?; + } else { + // write the contract file + let contract_path = src.join("Counter.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.sol"), + )?; + + // write the tests + let contract_path = test.join("Counter.t.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.t.sol"), + )?; + + // write the script + let contract_path = script.join("Counter.s.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.s.sol"), + )?; + } + } + + // Write the README file let readme_path = root.join("README.md"); - fs::write(readme_path, include_str!("../../assets/README.md"))?; + if tempo { + fs::write(readme_path, include_str!("../../assets/tempo/README.md"))?; + } else { + fs::write(readme_path, include_str!("../../assets/README.md"))?; + } // write foundry.toml, if it doesn't exist already let dest = root.join(Config::FILE_NAME); let mut config = Config::load_with_root(&root)?; + if tempo { + config.networks = NetworkConfigs::with_tempo(); + } if !dest.exists() { fs::write(dest, config.clone().into_basic().to_string_pretty()?)?; } @@ -141,17 +249,28 @@ impl InitArgs { // set up the repo if !no_git { - init_git_repo(git, commit)?; + init_git_repo(git, commit, use_parent_git, vyper, tempo)?; } // install forge-std if !offline { if root.join("lib/forge-std").exists() { sh_warn!("\"lib/forge-std\" already exists, skipping install...")?; - self.install.install(&mut config, vec![])?; + self.install.install(&mut config, vec![]).await?; } else { let dep = "https://github.com/foundry-rs/forge-std".parse()?; - self.install.install(&mut config, vec![dep])?; + self.install.install(&mut config, vec![dep]).await?; + } + + // install tempo-std + if tempo { + if root.join("lib/tempo-std").exists() { + sh_warn!("\"lib/tempo-std\" already exists, skipping install...")?; + self.install.install(&mut config, vec![]).await?; + } else { + let dep = "https://github.com/tempoxyz/tempo-std".parse()?; + self.install.install(&mut config, vec![dep]).await?; + } } } @@ -166,14 +285,21 @@ impl InitArgs { } } -/// Initialises `root` as a git repository, if it isn't one already. +/// Initialises `root` as a git repository, if it isn't one already, unless 'use_parent_git' is +/// true. /// /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already. /// /// Commits everything in `root` if `commit` is true. -fn init_git_repo(git: Git<'_>, commit: bool) -> Result<()> { - // git init - if !git.is_in_repo()? { +fn init_git_repo( + git: Git<'_>, + commit: bool, + use_parent_git: bool, + vyper: bool, + tempo: bool, +) -> Result<()> { + // `git init` + if !git.is_in_repo()? || (!use_parent_git && !git.is_repo_root()?) { git.init()?; } @@ -187,7 +313,14 @@ fn init_git_repo(git: Git<'_>, commit: bool) -> Result<()> { let workflow = git.root.join(".github/workflows/test.yml"); if !workflow.exists() { fs::create_dir_all(workflow.parent().unwrap())?; - fs::write(workflow, include_str!("../../assets/workflowTemplate.yml"))?; + + if vyper { + fs::write(workflow, include_str!("../../assets/vyper/workflowTemplate.yml"))?; + } else if tempo { + fs::write(workflow, include_str!("../../assets/tempo/workflowTemplate.yml"))?; + } else { + fs::write(workflow, include_str!("../../assets/solidity/workflowTemplate.yml"))?; + } } // commit everything diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index af0b10a66b5f0..4eedd0f8f6b08 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -1,26 +1,29 @@ use alloy_json_abi::{EventParam, InternalType, JsonAbi, Param}; use alloy_primitives::{hex, keccak256}; use clap::Parser; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; -use eyre::{eyre, Result}; +use comfy_table::{Cell, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; +use eyre::{Result, eyre}; use foundry_cli::opts::{BuildOpts, CompilerOpts}; use foundry_common::{ compile::{PathOrContractInfo, ProjectCompiler}, find_matching_contract_artifact, find_target_path, shell, }; use foundry_compilers::{ + ProjectCompileOutput, artifacts::{ + StorageLayout, output_selection::{ BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, EvmOutputSelection, EwasmOutputSelection, }, - StorageLayout, }, solc::SolcLanguage, }; +use path_slash::PathExt; use regex::Regex; use serde_json::{Map, Value}; -use std::{collections::BTreeMap, fmt, str::FromStr, sync::LazyLock}; +use solar::sema::interface::source_map::FileName; +use std::{collections::BTreeMap, fmt, ops::ControlFlow, path::Path, str::FromStr, sync::LazyLock}; /// CLI arguments for `forge inspect`. #[derive(Clone, Debug, Parser)] @@ -40,11 +43,15 @@ pub struct InspectArgs { /// Whether to remove comments when inspecting `ir` and `irOptimized` artifact fields. #[arg(long, short, help_heading = "Display options")] pub strip_yul_comments: bool, + + /// Whether to wrap the table to the terminal width. + #[arg(long, short, help_heading = "Display options")] + pub wrap: bool, } impl InspectArgs { pub fn run(self) -> Result<()> { - let Self { contract, field, build, strip_yul_comments } = self; + let Self { contract, field, build, strip_yul_comments, wrap } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); @@ -72,8 +79,13 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let compiler = ProjectCompiler::new().quiet(true); let target_path = find_target_path(&project, &contract)?; + if field == ContractArtifactField::Linearization && !is_solidity_source(&target_path) { + eyre::bail!( + "linearization inspection is only supported for Solidity contracts (.sol targets)" + ); + } + let compiler = ProjectCompiler::new().quiet(true); let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact @@ -82,11 +94,8 @@ impl InspectArgs { // Match on ContractArtifactFields and pretty-print match field { ContractArtifactField::Abi => { - let abi = artifact - .abi - .as_ref() - .ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; - print_abi(abi)?; + let abi = artifact.abi.as_ref().ok_or_else(|| missing_error("ABI"))?; + print_abi(abi, wrap)?; } ContractArtifactField::Bytecode => { print_json_str(&artifact.bytecode, Some("object"))?; @@ -101,13 +110,13 @@ impl InspectArgs { print_json_str(&artifact.legacy_assembly, None)?; } ContractArtifactField::MethodIdentifiers => { - print_method_identifiers(&artifact.method_identifiers)?; + print_method_identifiers(&artifact.method_identifiers, wrap)?; } ContractArtifactField::GasEstimates => { print_json(&artifact.gas_estimates)?; } ContractArtifactField::StorageLayout => { - print_storage_layout(artifact.storage_layout.as_ref())?; + print_storage_layout(artifact.storage_layout.as_ref(), wrap)?; } ContractArtifactField::DevDoc => { print_json(&artifact.devdoc)?; @@ -129,11 +138,11 @@ impl InspectArgs { } ContractArtifactField::Errors => { let out = artifact.abi.as_ref().map_or(Map::new(), parse_errors); - print_errors_events(&out, true)?; + print_errors_events(&out, true, wrap)?; } ContractArtifactField::Events => { let out = artifact.abi.as_ref().map_or(Map::new(), parse_events); - print_errors_events(&out, false)?; + print_errors_events(&out, false, wrap)?; } ContractArtifactField::StandardJson => { let standard_json = if let Some(version) = solc_version { @@ -147,6 +156,31 @@ impl InspectArgs { }; print_json(&standard_json)?; } + ContractArtifactField::Libraries => { + let all_libs: Vec = artifact + .all_link_references() + .into_iter() + .flat_map(|(path, libs)| { + libs.into_keys().map(move |lib| format!("{path}:{lib}")) + }) + .collect(); + if shell::is_json() { + return print_json(&all_libs); + } + sh_println!( + "Dynamically linked libraries:\n{}", + all_libs.iter().map(|v| format!(" {v}")).collect::>().join("\n") + )?; + } + ContractArtifactField::Linearization => { + print_linearization( + &mut output, + project.root(), + &target_path, + contract.name(), + wrap, + )?; + } }; Ok(()) @@ -155,7 +189,7 @@ impl InspectArgs { fn parse_errors(abi: &JsonAbi) -> Map { let mut out = serde_json::Map::new(); - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { + for er in abi.errors.values().flatten() { let types = get_ty_sig(&er.inputs); let sig = format!("{:x}", er.selector()); let sig_trimmed = &sig[0..8]; @@ -166,7 +200,7 @@ fn parse_errors(abi: &JsonAbi) -> Map { fn parse_events(abi: &JsonAbi) -> Map { let mut out = serde_json::Map::new(); - for ev in abi.events.iter().flat_map(|(_, events)| events) { + for ev in abi.events.values().flatten() { let types = parse_event_params(&ev.inputs); let topic = hex::encode(keccak256(ev.signature())); out.insert(format!("{}({})", ev.name, types), format!("0x{topic}").into()); @@ -179,7 +213,7 @@ fn parse_event_params(ev_params: &[EventParam]) -> String { .iter() .map(|p| { if let Some(ty) = p.internal_type() { - return internal_ty(ty) + return internal_ty(ty); } p.ty.clone() }) @@ -187,66 +221,70 @@ fn parse_event_params(ev_params: &[EventParam]) -> String { .join(",") } -fn print_abi(abi: &JsonAbi) -> Result<()> { +fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> { if shell::is_json() { - return print_json(abi) + return print_json(abi); } let headers = vec![Cell::new("Type"), Cell::new("Signature"), Cell::new("Selector")]; - print_table(headers, |table| { - // Print events - for ev in abi.events.iter().flat_map(|(_, events)| events) { - let types = parse_event_params(&ev.inputs); - let selector = ev.selector().to_string(); - table.add_row(["event", &format!("{}({})", ev.name, types), &selector]); - } + print_table( + headers, + |table| { + // Print events + for ev in abi.events.values().flatten() { + let types = parse_event_params(&ev.inputs); + let selector = ev.selector().to_string(); + table.add_row(["event", &format!("{}({})", ev.name, types), &selector]); + } - // Print errors - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { - let selector = er.selector().to_string(); - table.add_row([ - "error", - &format!("{}({})", er.name, get_ty_sig(&er.inputs)), - &selector, - ]); - } + // Print errors + for er in abi.errors.values().flatten() { + let selector = er.selector().to_string(); + table.add_row([ + "error", + &format!("{}({})", er.name, get_ty_sig(&er.inputs)), + &selector, + ]); + } - // Print functions - for func in abi.functions.iter().flat_map(|(_, f)| f) { - let selector = func.selector().to_string(); - let state_mut = func.state_mutability.as_json_str(); - let func_sig = if !func.outputs.is_empty() { - format!( - "{}({}) {state_mut} returns ({})", - func.name, - get_ty_sig(&func.inputs), - get_ty_sig(&func.outputs) - ) - } else { - format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) - }; - table.add_row(["function", &func_sig, &selector]); - } + // Print functions + for func in abi.functions.values().flatten() { + let selector = func.selector().to_string(); + let state_mut = func.state_mutability.as_json_str(); + let func_sig = if func.outputs.is_empty() { + format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) + } else { + format!( + "{}({}) {state_mut} returns ({})", + func.name, + get_ty_sig(&func.inputs), + get_ty_sig(&func.outputs) + ) + }; + table.add_row(["function", &func_sig, &selector]); + } - if let Some(constructor) = abi.constructor() { - let state_mut = constructor.state_mutability.as_json_str(); - table.add_row([ - "constructor", - &format!("constructor({}) {state_mut}", get_ty_sig(&constructor.inputs)), - "", - ]); - } + if let Some(constructor) = abi.constructor() { + let state_mut = constructor.state_mutability.as_json_str(); + table.add_row([ + "constructor", + &format!("constructor({}) {state_mut}", get_ty_sig(&constructor.inputs)), + "", + ]); + } - if let Some(fallback) = &abi.fallback { - let state_mut = fallback.state_mutability.as_json_str(); - table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]); - } + if let Some(fallback) = &abi.fallback { + let state_mut = fallback.state_mutability.as_json_str(); + table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]); + } - if let Some(receive) = &abi.receive { - let state_mut = receive.state_mutability.as_json_str(); - table.add_row(["receive", &format!("receive() {state_mut}"), ""]); - } - }) + if let Some(receive) = &abi.receive { + let state_mut = receive.state_mutability.as_json_str(); + table.add_row(["receive", &format!("receive() {state_mut}"), ""]); + } + }, + should_wrap, + ) } fn get_ty_sig(inputs: &[Param]) -> String { @@ -274,13 +312,16 @@ fn internal_ty(ty: &InternalType) -> String { } } -pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> { +pub fn print_storage_layout( + storage_layout: Option<&StorageLayout>, + should_wrap: bool, +) -> Result<()> { let Some(storage_layout) = storage_layout else { - eyre::bail!("Could not get storage layout"); + return Err(missing_error("storage layout")); }; if shell::is_json() { - return print_json(&storage_layout) + return print_json(&storage_layout); } let headers = vec![ @@ -292,40 +333,51 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<() Cell::new("Contract"), ]; - print_table(headers, |table| { - for slot in &storage_layout.storage { - let storage_type = storage_layout.types.get(&slot.storage_type); - table.add_row([ - slot.label.as_str(), - storage_type.map_or("?", |t| &t.label), - &slot.slot, - &slot.offset.to_string(), - storage_type.map_or("?", |t| &t.number_of_bytes), - &slot.contract, - ]); - } - }) + print_table( + headers, + |table| { + for slot in &storage_layout.storage { + let storage_type = storage_layout.types.get(&slot.storage_type); + table.add_row([ + slot.label.as_str(), + storage_type.map_or("?", |t| &t.label), + &slot.slot, + &slot.offset.to_string(), + storage_type.map_or("?", |t| &t.number_of_bytes), + &slot.contract, + ]); + } + }, + should_wrap, + ) } -fn print_method_identifiers(method_identifiers: &Option>) -> Result<()> { +fn print_method_identifiers( + method_identifiers: &Option>, + should_wrap: bool, +) -> Result<()> { let Some(method_identifiers) = method_identifiers else { - eyre::bail!("Could not get method identifiers"); + return Err(missing_error("method identifiers")); }; if shell::is_json() { - return print_json(method_identifiers) + return print_json(method_identifiers); } let headers = vec![Cell::new("Method"), Cell::new("Identifier")]; - print_table(headers, |table| { - for (method, identifier) in method_identifiers { - table.add_row([method, identifier]); - } - }) + print_table( + headers, + |table| { + for (method, identifier) in method_identifiers { + table.add_row([method.as_str(), identifier.as_str()]); + } + }, + should_wrap, + ) } -fn print_errors_events(map: &Map, is_err: bool) -> Result<()> { +fn print_errors_events(map: &Map, is_err: bool, should_wrap: bool) -> Result<()> { if shell::is_json() { return print_json(map); } @@ -335,22 +387,142 @@ fn print_errors_events(map: &Map, is_err: bool) -> Result<()> { } else { vec![Cell::new("Event"), Cell::new("Topic")] }; - print_table(headers, |table| { - for (method, selector) in map { - table.add_row([method, selector.as_str().unwrap()]); - } - }) + print_table( + headers, + |table| { + for (method, selector) in map { + table.add_row([method, selector.as_str().unwrap()]); + } + }, + should_wrap, + ) } -fn print_table(headers: Vec, add_rows: impl FnOnce(&mut Table)) -> Result<()> { +fn print_table( + headers: Vec, + add_rows: impl FnOnce(&mut Table), + should_wrap: bool, +) -> Result<()> { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(headers); + if should_wrap { + table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); + } add_rows(&mut table); sh_println!("\n{table}\n")?; Ok(()) } +fn print_linearization( + output: &mut ProjectCompileOutput, + root: &Path, + target_path: &Path, + target_name: Option<&str>, + should_wrap: bool, +) -> Result<()> { + let mut chain = Vec::new(); + let mut lowered = false; + let compiler = output.parser_mut().solc_mut().compiler_mut(); + compiler.enter_mut(|compiler| -> Result<()> { + let Ok(ControlFlow::Continue(())) = compiler.lower_asts() else { return Ok(()) }; + lowered = true; + + let hir = &compiler.gcx().hir; + let matching_contracts = hir + .contract_ids() + .filter(|id| { + let contract = hir.contract(*id); + if let Some(target_name) = target_name + && contract.name.as_str() != target_name + { + return false; + } + + matches!( + &hir.source(contract.source).file.name, + FileName::Real(path) if path == target_path + ) + }) + .collect::>(); + + let target_contract = match matching_contracts.as_slice() { + [id] => *id, + [] => { + if let Some(target_name) = target_name { + eyre::bail!( + "Could not find contract `{target_name}` in `{}`", + target_path.display() + ); + } + eyre::bail!("Could not find contract in `{}`", target_path.display()); + } + _ => { + eyre::bail!( + "Multiple contracts found in the same file, please specify the target : or " + ); + } + }; + + for (order, base_id) in hir.contract(target_contract).linearized_bases.iter().enumerate() { + let contract = hir.contract(*base_id); + let source = hir.source(contract.source); + let FileName::Real(path) = &source.file.name else { continue }; + let path = path.strip_prefix(root).unwrap_or(path); + chain.push(( + order, + path.to_slash_lossy().into_owned(), + contract.name.as_str().to_string(), + )); + } + + Ok(()) + })?; + + // `compiler.sess()` inside of `ProjectCompileOutput` is built with `with_buffer_emitter`. + let diags = compiler.sess().dcx.emitted_diagnostics().unwrap(); + if compiler.sess().dcx.has_errors().is_err() { + eyre::bail!("{diags}"); + } else { + let _ = sh_eprint!("{diags}"); + } + if !lowered { + eyre::bail!( + "unable to inspect linearization: failed to lower Solidity ASTs for `{}`", + target_path.display() + ); + } + + if shell::is_json() { + let contracts = chain + .into_iter() + .map(|(order, source, contract)| { + serde_json::json!({ + "order": order, + "source": source, + "contract": contract, + }) + }) + .collect::>(); + return print_json(&contracts); + } + + let headers = vec![Cell::new("Order"), Cell::new("Source"), Cell::new("Contract")]; + print_table( + headers, + |table| { + for (order, source, contract) in &chain { + table.add_row([order.to_string(), source.clone(), contract.clone()]); + } + }, + should_wrap, + ) +} + /// Contract level output selection #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ContractArtifactField { @@ -372,6 +544,8 @@ pub enum ContractArtifactField { Errors, Events, StandardJson, + Libraries, + Linearization, } macro_rules! impl_value_enum { @@ -381,7 +555,6 @@ macro_rules! impl_value_enum { pub const ALL: &'static [Self] = &[$(Self::$field),+]; /// Returns the string representation of `self`. - #[inline] pub const fn as_str(&self) -> &'static str { match self { $( @@ -391,7 +564,6 @@ macro_rules! impl_value_enum { } /// Returns all the aliases of `self`. - #[inline] pub const fn aliases(&self) -> &'static [&'static str] { match self { $( @@ -402,17 +574,14 @@ macro_rules! impl_value_enum { } impl ::clap::ValueEnum for $name { - #[inline] fn value_variants<'a>() -> &'a [Self] { Self::ALL } - #[inline] fn to_possible_value(&self) -> Option<::clap::builder::PossibleValue> { Some(::clap::builder::PossibleValue::new(Self::as_str(self)).aliases(Self::aliases(self))) } - #[inline] fn from_str(input: &str, ignore_case: bool) -> Result { let _ = ignore_case; ::from_str(input) @@ -460,6 +629,10 @@ impl_value_enum! { Errors => "errors" | "er", Events => "events" | "ev", StandardJson => "standardJson" | "standard-json" | "standard_json", + Libraries => "libraries" | "lib" | "libs", + Linearization => "linearization" | "linearizedInheritance" + | "linearized-inheritance" | "linearized_inheritance" + | "linearizedBases" | "linearized-bases" | "linearized_bases", } } @@ -492,6 +665,10 @@ impl TryFrom for ContractOutputSelection { Caf::StandardJson => { Err(eyre!("StandardJson is not supported for ContractOutputSelection")) } + Caf::Libraries => Err(eyre!("Libraries is not supported for ContractOutputSelection")), + Caf::Linearization => { + Err(eyre!("Linearization is not supported for ContractOutputSelection")) + } } } } @@ -502,21 +679,20 @@ impl PartialEq for ContractArtifactField { type Eos = EvmOutputSelection; matches!( (self, other), - (Self::Abi | Self::Events, Cos::Abi) | - (Self::Errors, Cos::Abi) | - (Self::Bytecode, Cos::Evm(Eos::ByteCode(_))) | - (Self::DeployedBytecode, Cos::Evm(Eos::DeployedByteCode(_))) | - (Self::Assembly | Self::AssemblyOptimized, Cos::Evm(Eos::Assembly)) | - (Self::LegacyAssembly, Cos::Evm(Eos::LegacyAssembly)) | - (Self::MethodIdentifiers, Cos::Evm(Eos::MethodIdentifiers)) | - (Self::GasEstimates, Cos::Evm(Eos::GasEstimates)) | - (Self::StorageLayout, Cos::StorageLayout) | - (Self::DevDoc, Cos::DevDoc) | - (Self::Ir, Cos::Ir) | - (Self::IrOptimized, Cos::IrOptimized) | - (Self::Metadata, Cos::Metadata) | - (Self::UserDoc, Cos::UserDoc) | - (Self::Ewasm, Cos::Ewasm(_)) + (Self::Abi | Self::Events | Self::Errors, Cos::Abi) + | (Self::Bytecode, Cos::Evm(Eos::ByteCode(_))) + | (Self::DeployedBytecode, Cos::Evm(Eos::DeployedByteCode(_))) + | (Self::Assembly | Self::AssemblyOptimized, Cos::Evm(Eos::Assembly)) + | (Self::LegacyAssembly, Cos::Evm(Eos::LegacyAssembly)) + | (Self::MethodIdentifiers, Cos::Evm(Eos::MethodIdentifiers)) + | (Self::GasEstimates, Cos::Evm(Eos::GasEstimates)) + | (Self::StorageLayout, Cos::StorageLayout) + | (Self::DevDoc, Cos::DevDoc) + | (Self::Ir, Cos::Ir) + | (Self::IrOptimized, Cos::IrOptimized) + | (Self::Metadata, Cos::Metadata) + | (Self::UserDoc, Cos::UserDoc) + | (Self::Ewasm, Cos::Ewasm(_)) ) } } @@ -530,7 +706,14 @@ impl fmt::Display for ContractArtifactField { impl ContractArtifactField { /// Returns true if this field does not need to be passed to the compiler. pub const fn can_skip_field(&self) -> bool { - matches!(self, Self::Bytecode | Self::DeployedBytecode | Self::StandardJson) + matches!( + self, + Self::Bytecode + | Self::DeployedBytecode + | Self::StandardJson + | Self::Libraries + | Self::Linearization + ) } } @@ -546,7 +729,7 @@ fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> fn print_yul(yul: Option<&str>, strip_comments: bool) -> Result<()> { let Some(yul) = yul else { - eyre::bail!("Could not get IR output"); + return Err(missing_error("IR output")); }; static YUL_COMMENTS: LazyLock = @@ -563,17 +746,28 @@ fn print_yul(yul: Option<&str>, strip_comments: bool) -> Result<()> { fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result { let value = serde_json::to_value(obj)?; - let mut value_ref = &value; - if let Some(key) = key { - if let Some(value2) = value.get(key) { - value_ref = value2; - } - } - let s = match value_ref.as_str() { - Some(s) => s.to_string(), - None => format!("{value_ref:#}"), + let value = if let Some(key) = key + && let Some(value) = value.get(key) + { + value + } else { + &value }; - Ok(s) + Ok(match value.as_str() { + Some(s) => s.to_string(), + None => format!("{value:#}"), + }) +} + +fn is_solidity_source(path: &Path) -> bool { + path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| ext.eq_ignore_ascii_case("sol")) +} + +fn missing_error(field: &str) -> eyre::Error { + eyre!( + "{field} missing from artifact; \ + this could be a spurious caching issue, consider running `forge clean`" + ) } #[cfg(test)] @@ -585,10 +779,28 @@ mod tests { for &field in ContractArtifactField::ALL { if field == ContractArtifactField::StandardJson { let selection: Result = field.try_into(); - assert!(selection - .unwrap_err() - .to_string() - .eq("StandardJson is not supported for ContractOutputSelection")); + assert!( + selection + .unwrap_err() + .to_string() + .eq("StandardJson is not supported for ContractOutputSelection") + ); + } else if field == ContractArtifactField::Libraries { + let selection: Result = field.try_into(); + assert!( + selection + .unwrap_err() + .to_string() + .eq("Libraries is not supported for ContractOutputSelection") + ); + } else if field == ContractArtifactField::Linearization { + let selection: Result = field.try_into(); + assert!( + selection + .unwrap_err() + .to_string() + .eq("Linearization is not supported for ContractOutputSelection") + ); } else { let selection: ContractOutputSelection = field.try_into().unwrap(); assert_eq!(field, selection); diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index 0a88c6eca7ab1..e76b7d6b755cf 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -1,13 +1,15 @@ +use crate::{DepIdentifier, FOUNDRY_LOCK, Lockfile}; use clap::{Parser, ValueHint}; use eyre::{Context, Result}; use foundry_cli::{ opts::Dependency, - utils::{CommandUtils, Git, LoadConfig}, + utils::{Git, LoadConfig}, }; use foundry_common::fs; -use foundry_config::{impl_figment_convert_basic, Config}; +use foundry_config::{Config, impl_figment_convert_basic}; use regex::Regex; use semver::Version; +use soldeer_commands::{Command, Verbosity, commands::install::Install}; use std::{ io::IsTerminal, path::{Path, PathBuf}, @@ -58,9 +60,9 @@ pub struct InstallArgs { impl_figment_convert_basic!(InstallArgs); impl InstallArgs { - pub fn run(self) -> Result<()> { + pub async fn run(self) -> Result<()> { let mut config = self.load_config()?; - self.opts.install(&mut config, self.dependencies) + self.opts.install(&mut config, self.dependencies).await } } @@ -91,12 +93,13 @@ impl DependencyInstallOpts { /// See also [`Self::install`]. /// /// Returns true if any dependency was installed. - pub fn install_missing_dependencies(self, config: &mut Config) -> bool { + pub async fn install_missing_dependencies(self, config: &mut Config) -> bool { let lib = config.install_lib_dir(); if self.git(config).has_missing_dependencies(Some(lib)).unwrap_or(false) { // The extra newline is needed, otherwise the compiler output will overwrite the message let _ = sh_println!("Missing dependencies found. Installing now...\n"); - if self.install(config, Vec::new()).is_err() { + + if self.install(config, Vec::new()).await.is_err() { let _ = sh_warn!("Your project has missing dependencies that could not be installed."); } @@ -107,7 +110,7 @@ impl DependencyInstallOpts { } /// Installs all dependencies - pub fn install(self, config: &mut Config, dependencies: Vec) -> Result<()> { + pub async fn install(self, config: &mut Config, dependencies: Vec) -> Result<()> { let Self { no_git, commit, .. } = self; let git = self.git(config); @@ -115,7 +118,22 @@ impl DependencyInstallOpts { let install_lib_dir = config.install_lib_dir(); let libs = git.root.join(install_lib_dir); - if dependencies.is_empty() && !self.no_git { + let mut lockfile = Lockfile::new(&config.root); + if !no_git { + lockfile = lockfile.with_git(&git); + + // Check if submodules are uninitialized, if so, we need to fetch all submodules + // This is to ensure that foundry.lock syncs successfully and doesn't error out, when + // looking for commits/tags in submodules + if git.submodules_uninitialized()? { + trace!(lib = %libs.display(), "submodules uninitialized"); + git.submodule_update(false, false, false, true, Some(&libs))?; + } + } + + let out_of_sync_deps = lockfile.sync(config.install_lib_dir())?; + + if dependencies.is_empty() && !no_git { // Use the root of the git repository to look for submodules. let root = Git::root_of(git.root)?; match git.has_submodules(Some(&root)) { @@ -124,10 +142,10 @@ impl DependencyInstallOpts { // recursively fetch all submodules (without fetching latest) git.submodule_update(false, false, false, true, Some(&libs))?; + lockfile.write()?; } - Err(err) => { - warn!(?err, "Failed to check for submodules"); + sh_err!("Failed to check for submodules: {err}")?; } _ => { // no submodules, nothing to do @@ -144,15 +162,16 @@ impl DependencyInstallOpts { .strip_prefix(git.root) .wrap_err("Library directory is not relative to the repository root")?; sh_println!( - "Installing {} in {} (url: {:?}, tag: {:?})", + "Installing {} in {} (url: {}, tag: {})", dep.name, path.display(), - dep.url, - dep.tag + dep.url.as_deref().unwrap_or("None"), + dep.tag.as_deref().unwrap_or("None") )?; // this tracks the actual installed tag let installed_tag; + let mut dep_id = None; if no_git { installed_tag = installer.install_as_folder(&dep, &path)?; } else { @@ -161,15 +180,30 @@ impl DependencyInstallOpts { } installed_tag = installer.install_as_submodule(&dep, &path)?; + let mut new_insertion = false; // Pin branch to submodule if branch is used - if let Some(branch) = &installed_tag { + if let Some(tag_or_branch) = &installed_tag { // First, check if this tag has a branch - if git.has_branch(branch, &path)? { + dep_id = Some(DepIdentifier::resolve_type(&git, &path, tag_or_branch)?); + if git.has_branch(tag_or_branch, &path)? + && dep_id.as_ref().is_some_and(|id| id.is_branch()) + { // always work with relative paths when directly modifying submodules - git.cmd() - .args(["submodule", "set-branch", "-b", branch]) - .arg(rel_path) - .exec()?; + git.set_submodule_branch(rel_path, tag_or_branch)?; + + let rev = git.get_rev(tag_or_branch, &path)?; + + dep_id = Some(DepIdentifier::Branch { + name: tag_or_branch.clone(), + rev, + r#override: false, + }); + } + + trace!(?dep_id, ?tag_or_branch, "resolved dep id"); + if let Some(dep_id) = &dep_id { + new_insertion = true; + lockfile.insert(rel_path.to_path_buf(), dep_id.clone()); } if commit { @@ -180,14 +214,31 @@ impl DependencyInstallOpts { } } + if new_insertion + || out_of_sync_deps.as_ref().is_some_and(|o| !o.is_empty()) + || !lockfile.exists() + { + lockfile.write()?; + } + // commit the installation if commit { let mut msg = String::with_capacity(128); msg.push_str("forge install: "); msg.push_str(dep.name()); + if let Some(tag) = &installed_tag { msg.push_str("\n\n"); - msg.push_str(tag); + + if let Some(dep_id) = &dep_id { + msg.push_str(&dep_id.to_string()); + } else { + msg.push_str(tag); + } + } + + if !lockfile.is_empty() { + git.root(&config.root).add(Some(FOUNDRY_LOCK))?; } git.commit(&msg)?; } @@ -196,9 +247,19 @@ impl DependencyInstallOpts { let mut msg = format!(" {} {}", "Installed".green(), dep.name); if let Some(tag) = dep.tag.or(installed_tag) { msg.push(' '); - msg.push_str(tag.as_str()); + + if let Some(dep_id) = dep_id { + msg.push_str(&dep_id.to_string()); + } else { + msg.push_str(tag.as_str()); + } } sh_println!("{msg}")?; + + // Check if the dependency has soldeer.lock and install soldeer dependencies + if let Err(e) = install_soldeer_deps_if_needed(&path).await { + sh_warn!("Failed to install soldeer dependencies for {}: {e}", dep.name)?; + } } // update `libs` in config if not included yet @@ -206,12 +267,43 @@ impl DependencyInstallOpts { config.libs.push(install_lib_dir.to_path_buf()); config.update_libs()?; } + Ok(()) } } -pub fn install_missing_dependencies(config: &mut Config) -> bool { - DependencyInstallOpts::default().install_missing_dependencies(config) +pub async fn install_missing_dependencies(config: &mut Config) -> bool { + DependencyInstallOpts::default().install_missing_dependencies(config).await +} + +/// Checks if a dependency has soldeer.lock and installs soldeer dependencies if needed. +async fn install_soldeer_deps_if_needed(dep_path: &Path) -> Result<()> { + let soldeer_lock = dep_path.join("soldeer.lock"); + + if soldeer_lock.exists() { + sh_println!(" Found soldeer.lock, installing soldeer dependencies...")?; + + // Change to the dependency directory and run soldeer install + let original_dir = std::env::current_dir()?; + std::env::set_current_dir(dep_path)?; + + let result = soldeer_commands::run( + Command::Install(Install::default()), + Verbosity::new( + foundry_common::shell::verbosity(), + if foundry_common::shell::is_quiet() { 1 } else { 0 }, + ), + ) + .await; + + // Change back to original directory + std::env::set_current_dir(original_dir)?; + + result.map_err(|e| eyre::eyre!("Failed to run soldeer install: {e}"))?; + sh_println!(" Soldeer dependencies installed successfully")?; + } + + Ok(()) } #[derive(Clone, Copy, Debug)] @@ -232,8 +324,10 @@ impl Installer<'_> { dep.tag = self.last_tag(path); } - // checkout the tag if necessary - self.git_checkout(&dep, path, false)?; + // checkout the tag if necessary, using recursive checkout to properly clean up + // nested submodules that may exist on the default branch but not on the target tag. + // See: https://github.com/foundry-rs/foundry/issues/13688 + self.git_checkout(&dep, path, true)?; trace!("updating dependency submodules recursively"); self.git.root(path).submodule_update( @@ -244,12 +338,51 @@ impl Installer<'_> { std::iter::empty::(), )?; + // remove nested .git directories from submodules before removing the top-level .git + Self::remove_nested_git_dirs(path)?; + // remove git artifacts fs::remove_dir_all(path.join(".git"))?; Ok(dep.tag) } + /// Recursively removes `.git` files/directories from nested submodules within `root`. + /// + /// Submodules typically have a `.git` file (not a directory) pointing to the parent's + /// `.git/modules/` directory. This cleans those up so the result is a plain folder tree. + fn remove_nested_git_dirs(root: &Path) -> Result<()> { + Self::remove_nested_git_dirs_inner(root, root) + } + + fn remove_nested_git_dirs_inner(root: &Path, dir: &Path) -> Result<()> { + let entries = match std::fs::read_dir(dir) { + Ok(entries) => entries, + Err(_) => return Ok(()), + }; + for entry in entries { + let entry = entry?; + let ft = entry.file_type()?; + + // never follow symlinks + if ft.is_symlink() { + continue; + } + + let path = entry.path(); + if path.file_name() == Some(".git".as_ref()) && path.parent() != Some(root) { + if ft.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } else if ft.is_dir() { + Self::remove_nested_git_dirs_inner(root, &path)?; + } + } + Ok(()) + } + /// Installs the dependency as new submodule. /// /// This will add the git submodule to the given dir, initialize it and checkout the tag if @@ -306,7 +439,7 @@ impl Installer<'_> { for &prefix in common_prefixes { if let Some(rem) = tag.strip_prefix(prefix) { maybe_semver = rem; - break + break; } } match Version::parse(maybe_semver) { @@ -362,21 +495,17 @@ impl Installer<'_> { if e.to_string().contains("did not match any file(s) known to git") { e = eyre::eyre!("Tag: \"{tag}\" not found for repo \"{url}\"!") } - return Err(e) + return Err(e); } - if is_branch { - Ok(tag) - } else { - Ok(String::new()) - } + if is_branch { Ok(tag) } else { Ok(String::new()) } } /// disambiguate tag if it is a version tag fn match_tag(self, tag: &str, path: &Path) -> Result { // only try to match if it looks like a version tag if !DEPENDENCY_VERSION_TAG_REGEX.is_match(tag) { - return Ok(tag.into()) + return Ok(tag.into()); } // generate candidate list by filtering `git tag` output, valid ones are those "starting @@ -394,13 +523,13 @@ impl Installer<'_> { // no match found, fall back to the user-provided tag if candidates.is_empty() { - return Ok(tag.into()) + return Ok(tag.into()); } // have exact match for candidate in &candidates { if candidate == tag { - return Ok(tag.into()) + return Ok(tag.into()); } } @@ -410,7 +539,7 @@ impl Installer<'_> { let input = prompt!( "Found a similar version tag: {matched_tag}, do you want to use this instead? [Y/n] " )?; - return if match_yn(input) { Ok(matched_tag.clone()) } else { Ok(tag.into()) } + return if match_yn(input) { Ok(matched_tag.clone()) } else { Ok(tag.into()) }; } // multiple candidates, ask the user to choose one or skip @@ -433,16 +562,16 @@ impl Installer<'_> { Ok(i) if (1..=n_candidates).contains(&i) => { let c = &candidates[i]; sh_println!("[{i}] {c} selected")?; - return Ok(c.clone()) + return Ok(c.clone()); } - _ => continue, + _ => {} } } } fn match_branch(self, tag: &str, path: &Path) -> Result> { // fetch remote branches and check for tag - let output = self.git.root(path).cmd().args(["branch", "-r"]).get_stdout_lossy()?; + let output = self.git.root(path).remote_branches()?; let mut candidates = output .lines() @@ -456,13 +585,13 @@ impl Installer<'_> { // no match found, fall back to the user-provided tag if candidates.is_empty() { - return Ok(None) + return Ok(None); } // have exact match for candidate in &candidates { if candidate == tag { - return Ok(Some(tag.to_string())) + return Ok(Some(tag.to_string())); } } @@ -472,7 +601,7 @@ impl Installer<'_> { let input = prompt!( "Found a similar branch: {matched_tag}, do you want to use this instead? [Y/n] " )?; - return if match_yn(input) { Ok(Some(matched_tag.clone())) } else { Ok(None) } + return if match_yn(input) { Ok(Some(matched_tag.clone())) } else { Ok(None) }; } // multiple candidates, ask the user to choose one or skip @@ -492,7 +621,7 @@ impl Installer<'_> { // default selection, return None if input.is_empty() { sh_println!("Canceled branch matching")?; - return Ok(None) + return Ok(None); } // match user input, 0 indicates skipping and use original tag diff --git a/crates/forge/src/cmd/lint.rs b/crates/forge/src/cmd/lint.rs index f81c252c40fdb..333f629931a08 100644 --- a/crates/forge/src/cmd/lint.rs +++ b/crates/forge/src/cmd/lint.rs @@ -1,12 +1,16 @@ use clap::{Parser, ValueHint}; -use eyre::{eyre, Result}; +use eyre::{Result, eyre}; use forge_lint::{ linter::Linter, sol::{SolLint, SolLintError, SolidityLinter}, }; -use foundry_cli::utils::{FoundryPathExt, LoadConfig}; +use foundry_cli::{ + opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, + utils::{FoundryPathExt, LoadConfig}, +}; +use foundry_common::{compile::ProjectCompiler, shell}; use foundry_compilers::{solc::SolcLanguage, utils::SOLC_EXTENSIONS}; -use foundry_config::{filter::expand_globs, impl_figment_convert_basic, lint::Severity}; +use foundry_config::{filter::expand_globs, lint::Severity}; use std::path::PathBuf; /// CLI arguments for `forge lint`. @@ -14,37 +18,30 @@ use std::path::PathBuf; pub struct LintArgs { /// Path to the file to be checked. Overrides the `ignore` project config. #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] - paths: Vec, - - /// The project's root path. - /// - /// By default root of the Git repository, if in one, - /// or the current working directory. - #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] - root: Option, + pub(crate) paths: Vec, /// Specifies which lints to run based on severity. Overrides the `severity` project config. /// /// Supported values: `high`, `med`, `low`, `info`, `gas`. #[arg(long, value_name = "SEVERITY", num_args(1..))] - severity: Option>, + pub(crate) severity: Option>, /// Specifies which lints to run based on their ID (e.g., "incorrect-shift"). Overrides the /// `exclude_lints` project config. #[arg(long = "only-lint", value_name = "LINT_ID", num_args(1..))] - lint: Option>, + pub(crate) lint: Option>, - /// Activates the linter's JSON formatter (rustc-compatible). - #[arg(long)] - json: bool, + #[command(flatten)] + pub(crate) build: BuildOpts, } -impl_figment_convert_basic!(LintArgs); +foundry_config::impl_figment_convert!(LintArgs, build); impl LintArgs { pub fn run(self) -> Result<()> { let config = self.load_config()?; - let project = config.project()?; + let project = config.solar_project()?; + let path_config = config.project_paths(); // Expand ignore globs and canonicalize from the get go let ignored = expand_globs(&config.root, config.lint.ignore.iter())? @@ -56,12 +53,11 @@ impl LintArgs { let input = match &self.paths[..] { [] => { // Retrieve the project paths, and filter out the ignored ones. - let project_paths = config + config .project_paths::() .input_files_iter() .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p)))) - .collect(); - project_paths + .collect() } paths => { // Override default excluded paths and only lint the input files. @@ -71,9 +67,9 @@ impl LintArgs { inputs .extend(foundry_compilers::utils::source_files(path, SOLC_EXTENSIONS)); } else if path.is_sol() { - inputs.push(path.to_path_buf()); + inputs.push(path.clone()); } else { - warn!("Cannot process path {}", path.display()); + warn!("cannot process path {}", path.display()); } } inputs @@ -81,7 +77,7 @@ impl LintArgs { }; if input.is_empty() { - sh_println!("Nothing to lint")?; + sh_println!("nothing to lint")?; return Ok(()); } @@ -90,29 +86,48 @@ impl LintArgs { }; // Override default lint config with user-defined lints - let (include, exclude) = match &self.lint { - Some(cli_lints) => (Some(parse_lints(cli_lints)?), None), - None => (None, Some(parse_lints(&config.lint.exclude_lints)?)), - }; - - // Override default severity config with user-defined severity - let severity = match self.severity { - Some(target) => target, - None => config.lint.severity, + // When --only-lint is used, bypass the severity filter by setting it to None + let (include, exclude, severity) = match &self.lint { + Some(cli_lints) => (Some(parse_lints(cli_lints)?), None, vec![]), + None => { + let severity = self.severity.clone().unwrap_or(config.lint.severity.clone()); + (None, Some(parse_lints(&config.lint.exclude_lints)?), severity) + } }; if project.compiler.solc.is_none() { - return Err(eyre!("Linting not supported for this language")); + return Err(eyre!("linting not supported for this language")); } - let linter = SolidityLinter::new() - .with_json_emitter(self.json) + let linter = SolidityLinter::new(path_config) + .with_json_emitter(shell::is_json()) .with_description(true) .with_lints(include) .without_lints(exclude) - .with_severity(if severity.is_empty() { None } else { Some(severity) }); + .with_severity(if severity.is_empty() { None } else { Some(severity) }) + .with_lint_specific(&config.lint.lint_specific); + + let output = ProjectCompiler::new().files(input.iter().cloned()).compile(&project)?; + let solar_sources = + get_solar_sources_from_compile_output(&config, &output, Some(&input), Some(&ignored))?; + if solar_sources.input.sources.is_empty() { + return Err(eyre!("unable to lint. Solar only supports Solidity versions >=0.8.0")); + } - linter.lint(&input); + // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new + // compiler, we should reuse the parser from the project output. + let mut compiler = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + + // Load the solar-compatible sources to the pcx before linting + compiler.enter_mut(|compiler| { + let mut pcx = compiler.parse(); + pcx.set_resolve_imports(true); + configure_pcx_from_solc(&mut pcx, &config.project_paths(), &solar_sources, true); + pcx.parse(); + }); + linter.lint(&input, config.deny, &mut compiler)?; Ok(()) } diff --git a/crates/forge/src/cmd/remove.rs b/crates/forge/src/cmd/remove.rs index 2033ad3a7c4ad..8bd97fe828c33 100644 --- a/crates/forge/src/cmd/remove.rs +++ b/crates/forge/src/cmd/remove.rs @@ -1,3 +1,4 @@ +use crate::Lockfile; use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::{ @@ -30,18 +31,31 @@ impl_figment_convert_basic!(RemoveArgs); impl RemoveArgs { pub fn run(self) -> Result<()> { let config = self.load_config()?; - let (root, paths) = super::update::dependencies_paths(&self.dependencies, &config)?; + let (root, paths, _) = super::update::dependencies_paths(&self.dependencies, &config)?; let git_modules = root.join(".git/modules"); + let git = Git::new(&root); + let mut lockfile = Lockfile::new(&config.root).with_git(&git); + let _synced = lockfile.sync(config.install_lib_dir())?; // remove all the dependencies by invoking `git rm` only once with all the paths - Git::new(&root).rm(self.force, &paths)?; + git.rm(self.force, &paths)?; // remove all the dependencies from .git/modules - for (Dependency { name, url, tag, .. }, path) in self.dependencies.iter().zip(&paths) { - sh_println!("Removing '{name}' in {}, (url: {url:?}, tag: {tag:?})", path.display())?; + for (Dependency { name, tag, .. }, path) in self.dependencies.iter().zip(&paths) { + // Get the URL from git submodule config instead of using the parsed dependency URL + let url = git.submodule_url(path).unwrap_or(None); + sh_println!( + "Removing '{name}' in {}, (url: {}, tag: {})", + path.display(), + url.as_deref().unwrap_or("None"), + tag.as_deref().unwrap_or("None") + )?; + let _ = lockfile.remove(path); std::fs::remove_dir_all(git_modules.join(path))?; } + lockfile.write()?; + Ok(()) } } diff --git a/crates/forge/src/cmd/selectors.rs b/crates/forge/src/cmd/selectors.rs index 5e858c9da9c58..252544e7ea5e8 100644 --- a/crates/forge/src/cmd/selectors.rs +++ b/crates/forge/src/cmd/selectors.rs @@ -1,18 +1,18 @@ use alloy_primitives::hex; use clap::Parser; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Table}; +use comfy_table::{Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; use eyre::Result; use foundry_cli::{ opts::{BuildOpts, CompilerOpts, ProjectPathOpts}, - utils::{cache_local_signatures, FoundryPathExt}, + utils::{FoundryPathExt, cache_local_signatures, cache_signatures_from_abis}, }; use foundry_common::{ - compile::{compile_target, PathOrContractInfo, ProjectCompiler}, - selectors::{import_selectors, SelectorImportData}, + compile::{PathOrContractInfo, ProjectCompiler, compile_target}, + selectors::{SelectorImportData, import_selectors}, + shell, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; -use foundry_config::Config; -use std::fs::canonicalize; +use std::{collections::BTreeMap, fs::canonicalize}; /// CLI arguments for `forge selectors`. #[derive(Clone, Debug, Parser)] @@ -57,6 +57,9 @@ pub enum SelectorsSubcommands { #[command(flatten)] project_paths: ProjectPathOpts, + + #[arg(long, help = "Do not group the selectors by contract in separate tables.")] + no_group: bool, }, /// Find if a selector is present in the project @@ -73,6 +76,8 @@ pub enum SelectorsSubcommands { /// Cache project selectors (enables trace with local contracts functions and events). #[command(visible_alias = "c")] Cache { + #[arg(long, help = "Path to a folder containing additional abis to include in the cache")] + extra_abis_path: Option, #[command(flatten)] project_paths: ProjectPathOpts, }, @@ -81,7 +86,12 @@ pub enum SelectorsSubcommands { impl SelectorsSubcommands { pub async fn run(self) -> Result<()> { match self { - Self::Cache { project_paths } => { + Self::Cache { project_paths, extra_abis_path } => { + if let Some(extra_abis_path) = extra_abis_path { + sh_println!("Caching selectors for ABIs at {extra_abis_path}")?; + cache_signatures_from_abis(extra_abis_path)?; + } + sh_println!("Caching selectors for contracts in the project...")?; let build_args = BuildOpts { project_paths, @@ -95,7 +105,7 @@ impl SelectorsSubcommands { // compile the project to get the artifacts/abis let project = build_args.project()?; let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; - cache_local_signatures(&outcome, &Config::foundry_cache_dir().unwrap())? + cache_local_signatures(&outcome)?; } Self::Upload { contract, all, project_paths } => { let build_args = BuildOpts { @@ -156,7 +166,7 @@ impl SelectorsSubcommands { while let Some((contract, artifact)) = artifacts.next() { let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; if abi.functions.is_empty() && abi.events.is_empty() && abi.errors.is_empty() { - continue + continue; } sh_println!("Uploading selectors for {contract}...")?; @@ -204,7 +214,7 @@ impl SelectorsSubcommands { .filter_map(|(k1, v1)| { second_method_map .iter() - .find_map(|(k2, v2)| if **v2 == *v1 { Some((k2, v2)) } else { None }) + .find_map(|(k2, v2)| (**v2 == *v1).then_some((k2, v2))) .map(|(k2, v2)| (v2, k1, k2)) }) .collect(); @@ -213,7 +223,11 @@ impl SelectorsSubcommands { sh_println!("No colliding method selectors between the two contracts.")?; } else { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header([ String::from("Selector"), first_contract.name, @@ -226,7 +240,7 @@ impl SelectorsSubcommands { sh_println!("\n{table}\n")?; } } - Self::List { contract, project_paths } => { + Self::List { contract, project_paths, no_group } => { sh_println!("Listing selectors for contracts in the project...")?; let build_args = BuildOpts { project_paths, @@ -272,43 +286,100 @@ impl SelectorsSubcommands { .collect() }; - let mut artifacts = artifacts.into_iter().peekable(); + let mut artifacts = artifacts.into_iter(); - while let Some((contract, artifact)) = artifacts.next() { - let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - if abi.functions.is_empty() && abi.events.is_empty() && abi.errors.is_empty() { - continue + #[derive(PartialEq, PartialOrd, Eq, Ord)] + enum SelectorType { + Function, + Event, + Error, + } + impl std::fmt::Display for SelectorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Function => write!(f, "Function"), + Self::Event => write!(f, "Event"), + Self::Error => write!(f, "Error"), + } } + } - sh_println!("{contract}")?; + let mut selectors = + BTreeMap::>>::new(); - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + for (contract, artifact) in artifacts.by_ref() { + let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - table.set_header(["Type", "Signature", "Selector"]); + let contract_selectors = selectors.entry(contract.clone()).or_default(); for func in abi.functions() { let sig = func.signature(); let selector = func.selector(); - table.add_row(["Function", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Function) + .or_default() + .push((hex::encode_prefixed(selector), sig)); } for event in abi.events() { let sig = event.signature(); let selector = event.selector(); - table.add_row(["Event", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Event) + .or_default() + .push((hex::encode_prefixed(selector), sig)); } for error in abi.errors() { let sig = error.signature(); let selector = error.selector(); - table.add_row(["Error", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Error) + .or_default() + .push((hex::encode_prefixed(selector), sig)); + } + } + + if no_group { + let mut table = Table::new(); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } + table.set_header(["Type", "Signature", "Selector", "Contract"]); + + for (contract, contract_selectors) in selectors { + for (selector_type, selectors) in contract_selectors { + for (selector, sig) in selectors { + table.add_row([ + selector_type.to_string(), + sig, + selector, + contract.clone(), + ]); + } + } } - sh_println!("\n{table}\n")?; + sh_println!("\n{table}")?; + } else { + for (idx, (contract, contract_selectors)) in selectors.into_iter().enumerate() { + sh_println!("{}{contract}", if idx == 0 { "" } else { "\n" })?; + let mut table = Table::new(); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } + table.set_header(["Type", "Signature", "Selector"]); - if artifacts.peek().is_some() { - sh_println!()? + for (selector_type, selectors) in contract_selectors { + for (selector, sig) in selectors { + table.add_row([selector_type.to_string(), sig, selector]); + } + } + sh_println!("\n{table}")?; } } } @@ -337,16 +408,20 @@ impl SelectorsSubcommands { .collect::>(); let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(["Type", "Signature", "Selector", "Contract"]); + let selector_str = selector.strip_prefix("0x").unwrap_or(selector.as_str()); + let selector_bytes = hex::decode(selector_str)?; + for (_file, contract, artifact) in artifacts { let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - let selector_bytes = - hex::decode(selector.strip_prefix("0x").unwrap_or(&selector))?; - for func in abi.functions() { if func.selector().as_slice().starts_with(selector_bytes.as_slice()) { table.add_row([ diff --git a/crates/forge/src/cmd/snapshot.rs b/crates/forge/src/cmd/snapshot.rs index da642bd1241be..7c6fb51ce3266 100644 --- a/crates/forge/src/cmd/snapshot.rs +++ b/crates/forge/src/cmd/snapshot.rs @@ -1,9 +1,13 @@ use super::test; use crate::result::{SuiteTestResult, TestKindReport, TestOutcome}; -use alloy_primitives::{map::HashMap, U256}; -use clap::{builder::RangedU64ValueParser, Parser, ValueHint}; +use alloy_primitives::{U256, map::HashMap}; +use clap::{Parser, ValueHint, builder::RangedU64ValueParser}; +use comfy_table::{ + Cell, Color, Row, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, +}; use eyre::{Context, Result}; use foundry_cli::utils::STATIC_FUZZ_SEED; +use foundry_common::shell; use regex::Regex; use std::{ cmp::Ordering, @@ -70,6 +74,10 @@ pub struct GasSnapshotArgs { )] tolerance: Option, + /// How to sort diff results. + #[arg(long, value_name = "ORDER")] + diff_sort: Option, + /// All test arguments are supported #[command(flatten)] pub(crate) test: test::TestArgs, @@ -81,7 +89,7 @@ pub struct GasSnapshotArgs { impl GasSnapshotArgs { /// Returns whether `GasSnapshotArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.test.is_watch() } @@ -91,17 +99,20 @@ impl GasSnapshotArgs { } pub async fn run(mut self) -> Result<()> { - // Set fuzz seed so gas snapshots are deterministic - self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Default to a static fuzz seed so gas snapshots are deterministic, + // but allow the user to override it via `--fuzz-seed`. + if self.test.fuzz_seed.is_none() { + self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + } - let outcome = self.test.execute_tests().await?; + let outcome = self.test.compile_and_run().await?; outcome.ensure_ok(false)?; let tests = self.config.apply(outcome); if let Some(path) = self.diff { let snap = path.as_ref().unwrap_or(&self.snap); let snaps = read_gas_snapshot(snap)?; - diff(tests, snaps)?; + diff(tests, snaps, self.diff_sort.unwrap_or_default())?; } else if let Some(path) = self.check { let snap = path.as_ref().unwrap_or(&self.snap); let snaps = read_gas_snapshot(snap)?; @@ -111,13 +122,17 @@ impl GasSnapshotArgs { std::process::exit(1) } } else { + if matches!(self.format, Some(Format::Table)) { + let table = build_gas_snapshot_table(&tests); + sh_println!("\n{}", table)?; + } write_to_gas_snapshot_file(&tests, self.snap, self.format)?; } Ok(()) } } -// TODO implement pretty tables +// Gas report format on stdout. #[derive(Clone, Debug)] pub enum Format { Table, @@ -154,17 +169,31 @@ struct GasSnapshotConfig { max: Option, } +/// Sort order for diff output +#[derive(Clone, Debug, Default, clap::ValueEnum)] +enum DiffSortOrder { + /// Sort by percentage change (smallest to largest) - default behavior + #[default] + Percentage, + /// Sort by percentage change (largest to smallest) + PercentageDesc, + /// Sort by absolute gas change (smallest to largest) + Absolute, + /// Sort by absolute gas change (largest to smallest) + AbsoluteDesc, +} + impl GasSnapshotConfig { - fn is_in_gas_range(&self, gas_used: u64) -> bool { - if let Some(min) = self.min { - if gas_used < min { - return false - } + const fn is_in_gas_range(&self, gas_used: u64) -> bool { + if let Some(min) = self.min + && gas_used < min + { + return false; } - if let Some(max) = self.max { - if gas_used > max { - return false - } + if let Some(max) = self.max + && gas_used > max + { + return false; } true } @@ -225,6 +254,7 @@ impl FromStr for GasSnapshotEntry { runs: runs.as_str().parse().unwrap(), median_gas: med.as_str().parse().unwrap(), mean_gas: avg.as_str().parse().unwrap(), + failed_corpus_replays: 0, }, }) } else { @@ -242,6 +272,8 @@ impl FromStr for GasSnapshotEntry { calls: calls.as_str().parse().unwrap(), reverts: reverts.as_str().parse().unwrap(), metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, }, }) } @@ -288,6 +320,31 @@ fn write_to_gas_snapshot_file( Ok(fs::write(path, content)?) } +fn build_gas_snapshot_table(tests: &[SuiteTestResult]) -> Table { + let mut table = Table::new(); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } + + table.set_header(vec![ + Cell::new("Contract").fg(Color::Cyan), + Cell::new("Signature").fg(Color::Cyan), + Cell::new("Report").fg(Color::Cyan), + ]); + + for test in tests { + let mut row = Row::new(); + row.add_cell(Cell::new(test.contract_name())); + row.add_cell(Cell::new(&test.signature)); + row.add_cell(Cell::new(test.result.kind.report())); + table.add_row(row); + } + + table +} + /// A Gas snapshot entry diff. #[derive(Clone, Debug, PartialEq, Eq)] pub struct GasSnapshotDiff { @@ -301,7 +358,7 @@ impl GasSnapshotDiff { /// /// `> 0` if the source used more gas /// `< 0` if the target used more gas - fn gas_change(&self) -> i128 { + const fn gas_change(&self) -> i128 { self.source_gas_used.gas() as i128 - self.target_gas_used.gas() as i128 } @@ -352,42 +409,121 @@ fn check( } /// Compare the set of tests with an existing gas snapshot. -fn diff(tests: Vec, snaps: Vec) -> Result<()> { +fn diff( + tests: Vec, + snaps: Vec, + sort_order: DiffSortOrder, +) -> Result<()> { let snaps = snaps .into_iter() .map(|s| ((s.contract_name, s.signature), s.gas_used)) .collect::>(); let mut diffs = Vec::with_capacity(tests.len()); - for test in tests.into_iter() { + let mut new_tests = Vec::new(); + + for test in tests { if let Some(target_gas_used) = snaps.get(&(test.contract_name().to_string(), test.signature.clone())).cloned() { diffs.push(GasSnapshotDiff { source_gas_used: test.result.kind.report(), - signature: test.signature, + signature: format!("{}::{}", test.contract_name(), test.signature), target_gas_used, }); + } else { + // Track new tests + new_tests.push(format!("{}::{}", test.contract_name(), test.signature)); } } + + let mut increased = 0; + let mut decreased = 0; + let mut unchanged = 0; let mut overall_gas_change = 0i128; let mut overall_gas_used = 0i128; - diffs.sort_by(|a, b| a.gas_diff().abs().total_cmp(&b.gas_diff().abs())); + // Sort based on user preference + match sort_order { + DiffSortOrder::Percentage => { + // Default: sort by percentage change (smallest to largest) + diffs.sort_by(|a, b| a.gas_diff().abs().total_cmp(&b.gas_diff().abs())); + } + DiffSortOrder::PercentageDesc => { + // Sort by percentage change (largest to smallest) + diffs.sort_by(|a, b| b.gas_diff().abs().total_cmp(&a.gas_diff().abs())); + } + DiffSortOrder::Absolute => { + // Sort by absolute gas change (smallest to largest) + diffs.sort_by_key(|d| d.gas_change().abs()); + } + DiffSortOrder::AbsoluteDesc => { + // Sort by absolute gas change (largest to smallest) + diffs.sort_by_key(|d| std::cmp::Reverse(d.gas_change().abs())); + } + } - for diff in diffs { + for diff in &diffs { let gas_change = diff.gas_change(); overall_gas_change += gas_change; overall_gas_used += diff.target_gas_used.gas() as i128; let gas_diff = diff.gas_diff(); + + // Classify changes + if gas_change > 0 { + increased += 1; + } else if gas_change < 0 { + decreased += 1; + } else { + unchanged += 1; + } + + // Display with icon and before/after values + let icon = if gas_change > 0 { + "↑".red().to_string() + } else if gas_change < 0 { + "↓".green().to_string() + } else { + "━".to_string() + }; + sh_println!( - "{} (gas: {} ({})) ", + "{} {} (gas: {} → {} | {} {})", + icon, diff.signature, + diff.target_gas_used.gas(), + diff.source_gas_used.gas(), fmt_change(gas_change), fmt_pct_change(gas_diff) )?; } - let overall_gas_diff = overall_gas_change as f64 / overall_gas_used as f64; + // Display new tests if any + if !new_tests.is_empty() { + sh_println!("\n{}", "New tests:".yellow())?; + for test in new_tests { + sh_println!(" {} {}", "+".green(), test)?; + } + } + + // Summary separator + sh_println!("\n{}", "-".repeat(80))?; + + let overall_gas_diff = if overall_gas_used > 0 { + overall_gas_change as f64 / overall_gas_used as f64 + } else { + 0.0 + }; + + sh_println!( + "Total tests: {}, {} {}, {} {}, {} {}", + diffs.len(), + "↑".red().to_string(), + increased, + "↓".green().to_string(), + decreased, + "━", + unchanged + )?; sh_println!( "Overall gas change: {} ({})", fmt_change(overall_gas_change), @@ -410,9 +546,7 @@ fn fmt_pct_change(change: f64) -> String { fn fmt_change(change: i128) -> String { match change.cmp(&0) { Ordering::Less => format!("{change}").green().to_string(), - Ordering::Equal => { - format!("{change}") - } + Ordering::Equal => change.to_string(), Ordering::Greater => format!("{change}").red().to_string(), } } @@ -470,7 +604,12 @@ mod tests { GasSnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), - gas_used: TestKindReport::Fuzz { runs: 256, median_gas: 200, mean_gas: 100 } + gas_used: TestKindReport::Fuzz { + runs: 256, + median_gas: 200, + mean_gas: 100, + failed_corpus_replays: 0 + } } ); } @@ -488,7 +627,9 @@ mod tests { runs: 256, calls: 100, reverts: 200, - metrics: HashMap::default() + metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, } } ); @@ -507,7 +648,9 @@ mod tests { runs: 256, calls: 3840, reverts: 2388, - metrics: HashMap::default() + metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, } } ); diff --git a/crates/forge/src/cmd/soldeer.rs b/crates/forge/src/cmd/soldeer.rs index 6ca70758ce7f6..fd7cf2d0505de 100644 --- a/crates/forge/src/cmd/soldeer.rs +++ b/crates/forge/src/cmd/soldeer.rs @@ -27,7 +27,7 @@ impl SoldeerArgs { #[cfg(test)] mod tests { - use soldeer_commands::{commands::Version, Command, Verbosity}; + use soldeer_commands::{Command, Verbosity, commands::Version}; #[tokio::test] async fn test_soldeer_version() { diff --git a/crates/forge/src/cmd/test/filter.rs b/crates/forge/src/cmd/test/filter.rs index ec2e9b01b50e8..c9219ec504d6a 100644 --- a/crates/forge/src/cmd/test/filter.rs +++ b/crates/forge/src/cmd/test/filter.rs @@ -1,7 +1,7 @@ use clap::Parser; use foundry_common::TestFilter; use foundry_compilers::{FileFilter, ProjectPathsConfig}; -use foundry_config::{filter::GlobMatcher, Config}; +use foundry_config::{Config, filter::GlobMatcher}; use std::{fmt, path::Path}; /// The filter to use during testing. @@ -46,13 +46,13 @@ pub struct FilterArgs { impl FilterArgs { /// Returns true if the filter is empty. - pub fn is_empty(&self) -> bool { - self.test_pattern.is_none() && - self.test_pattern_inverse.is_none() && - self.contract_pattern.is_none() && - self.contract_pattern_inverse.is_none() && - self.path_pattern.is_none() && - self.path_pattern_inverse.is_none() + pub const fn is_empty(&self) -> bool { + self.test_pattern.is_none() + && self.test_pattern_inverse.is_none() + && self.contract_pattern.is_none() + && self.contract_pattern_inverse.is_none() + && self.path_pattern.is_none() + && self.path_pattern_inverse.is_none() } /// Merges the set filter globs with the config's values @@ -106,13 +106,13 @@ impl FileFilter for FilterArgs { } impl TestFilter for FilterArgs { - fn matches_test(&self, test_name: &str) -> bool { + fn matches_test(&self, test_signature: &str) -> bool { let mut ok = true; if let Some(re) = &self.test_pattern { - ok = ok && re.is_match(test_name); + ok = ok && re.is_match(test_signature); } if let Some(re) = &self.test_pattern_inverse { - ok = ok && !re.is_match(test_name); + ok = ok && !re.is_match(test_signature); } ok } @@ -176,22 +176,22 @@ pub struct ProjectPathsAwareFilter { impl ProjectPathsAwareFilter { /// Returns true if the filter is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.args_filter.is_empty() } /// Returns the CLI arguments. - pub fn args(&self) -> &FilterArgs { + pub const fn args(&self) -> &FilterArgs { &self.args_filter } /// Returns the CLI arguments mutably. - pub fn args_mut(&mut self) -> &mut FilterArgs { + pub const fn args_mut(&mut self) -> &mut FilterArgs { &mut self.args_filter } /// Returns the project paths. - pub fn paths(&self) -> &ProjectPathsConfig { + pub const fn paths(&self) -> &ProjectPathsConfig { &self.paths } } @@ -207,8 +207,8 @@ impl FileFilter for ProjectPathsAwareFilter { } impl TestFilter for ProjectPathsAwareFilter { - fn matches_test(&self, test_name: &str) -> bool { - self.args_filter.matches_test(test_name) + fn matches_test(&self, test_signature: &str) -> bool { + self.args_filter.matches_test(test_signature) } fn matches_contract(&self, contract_name: &str) -> bool { diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index 9e8a4b2d26643..938e9674c0cbe 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -1,53 +1,61 @@ use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; use crate::{ + MultiContractRunner, MultiContractRunnerBuilder, decode::decode_console_logs, gas_report::GasReport, - multi_runner::matches_contract, + multi_runner::{MultiNetworkConfig, matches_artifact}, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ + CallTraceDecoderBuilder, InternalTraceMode, TraceKind, debug::{ContractSources, DebugTraceIdentifier}, decode_trace_arena, folded_stack_trace, identifier::SignaturesIdentifier, - CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, - MultiContractRunner, MultiContractRunnerBuilder, TestFilter, }; use alloy_primitives::U256; use chrono::Utc; use clap::{Parser, ValueHint}; -use eyre::{bail, Context, OptionExt, Result}; -use foundry_block_explorers::EtherscanApiVersion; +use eyre::{Context, OptionExt, Result, bail}; use foundry_cli::{ - opts::{BuildOpts, GlobalArgs}, + opts::{BuildOpts, EvmArgs, GlobalArgs}, utils::{self, LoadConfig}, }; -use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell, TestFunctionExt}; +use foundry_common::{EmptyTestFilter, TestFunctionExt, compile::ProjectCompiler, fs, shell}; use foundry_compilers::{ - artifacts::output_selection::OutputSelection, + ProjectCompileOutput, + artifacts::{Libraries, output_selection::OutputSelection}, compilers::{ - multi::{MultiCompiler, MultiCompilerLanguage}, Language, + multi::{MultiCompiler, MultiCompilerLanguage}, }, utils::source_files_iter, - ProjectCompileOutput, }; use foundry_config::{ - figment, + Config, InlineConfig, figment, figment::{ - value::{Dict, Map}, Metadata, Profile, Provider, + value::{Dict, Map}, }, filter::GlobMatcher, - Config, }; use foundry_debugger::Debugger; -use foundry_evm::traces::identifier::TraceIdentifiers; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; +use foundry_evm::{ + core::evm::{ + BlockEnvFor, EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor, + }, + opts::EvmOpts, + traces::{backtrace::BacktraceBuilder, identifier::TraceIdentifiers, prune_trace_depth}, +}; +use rand::Rng; use regex::Regex; +use revm::context::Transaction; use std::{ collections::{BTreeMap, BTreeSet}, fmt::Write, - path::PathBuf, - sync::{mpsc::channel, Arc}, + path::{Path, PathBuf}, + sync::{Arc, mpsc::channel}, time::{Duration, Instant}, }; use yansi::Paint; @@ -57,7 +65,7 @@ mod summary; use crate::{result::TestKind, traces::render_trace_arena_inner}; pub use filter::FilterArgs; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; -use summary::{format_invariant_metrics_table, TestSummaryReport}; +use summary::{TestSummaryReport, format_invariant_metrics_table}; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, build, evm); @@ -135,6 +143,10 @@ pub struct TestArgs { #[arg(long, short, env = "FORGE_SUPPRESS_SUCCESSFUL_TRACES", help_heading = "Display options")] suppress_successful_traces: bool, + /// Defines the depth of a trace + #[arg(long)] + trace_depth: Option, + /// Output test results as JUnit XML report. #[arg(long, conflicts_with_all = ["quiet", "json", "gas_report", "summary", "list", "show_progress"], help_heading = "Display options")] pub junit: bool, @@ -147,10 +159,6 @@ pub struct TestArgs { #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] etherscan_api_key: Option, - /// The Etherscan API version. - #[arg(long, env = "ETHERSCAN_API_VERSION", value_name = "VERSION")] - etherscan_api_version: Option, - /// List tests instead of running them. #[arg(long, short, conflicts_with_all = ["show_progress", "decode_internal", "summary"], help_heading = "Display options")] list: bool, @@ -162,6 +170,14 @@ pub struct TestArgs { #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, + /// Run only the fuzz case at the given 1-based run index. + #[arg(long, env = "FOUNDRY_FUZZ_RUN", value_name = "RUN")] + pub fuzz_run: Option, + + /// Run the fuzz case from the given worker. Requires `--fuzz-run`. + #[arg(long, env = "FOUNDRY_FUZZ_WORKER", value_name = "WORKER", requires = "fuzz_run")] + pub fuzz_worker: Option, + /// Timeout for each fuzz run in seconds. #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] pub fuzz_timeout: Option, @@ -187,6 +203,10 @@ pub struct TestArgs { #[arg(long, help_heading = "Display options", requires = "summary")] pub detailed: bool, + /// Disables the labels in the traces. + #[arg(long, help_heading = "Display options")] + pub disable_labels: bool, + #[command(flatten)] filter: FilterArgs, @@ -201,80 +221,49 @@ pub struct TestArgs { } impl TestArgs { - pub async fn run(self) -> Result { + pub async fn run(mut self) -> Result { trace!(target: "forge::test", "executing test command"); - self.execute_tests().await + self.compile_and_run().await } - /// Returns sources which include any tests to be executed. - /// If no filters are provided, sources are filtered by existence of test/invariant methods in - /// them, If filters are provided, sources are additionally filtered by them. + /// Returns a list of files that need to be compiled in order to run all the tests that match + /// the given filter. + /// + /// This means that it will return all sources that are not test contracts or that match the + /// filter. We want to compile all non-test sources always because tests might depend on them + /// dynamically through cheatcodes. + #[instrument(target = "forge::test", skip_all)] pub fn get_sources_to_compile( &self, config: &Config, - filter: &ProjectPathsAwareFilter, + test_filter: &ProjectPathsAwareFilter, ) -> Result> { + // An empty filter doesn't filter out anything. + // We can still optimize slightly by excluding scripts. + if test_filter.is_empty() { + return Ok(source_files_iter(&config.src, MultiCompilerLanguage::FILE_EXTENSIONS) + .chain(source_files_iter(&config.test, MultiCompilerLanguage::FILE_EXTENSIONS)) + .collect()); + } + let mut project = config.create_project(true, true)?; project.update_output_selection(|selection| { *selection = OutputSelection::common_output_selection(["abi".to_string()]); }); - let output = project.compile()?; - if output.has_compiler_errors() { sh_println!("{output}")?; eyre::bail!("Compilation failed"); } - // ABIs of all sources - let abis = output - .into_artifacts() - .filter_map(|(id, artifact)| artifact.abi.map(|abi| (id, abi))) - .collect::>(); - - // Filter sources by their abis and contract names. - let mut test_sources = abis - .iter() - .filter(|(id, abi)| matches_contract(id, abi, filter)) - .map(|(id, _)| id.source.clone()) - .collect::>(); - - if test_sources.is_empty() { - if filter.is_empty() { - sh_println!( - "No tests found in project! \ - Forge looks for functions that starts with `test`." - )?; - } else { - sh_println!("No tests match the provided pattern:")?; - sh_print!("{filter}")?; - - // Try to suggest a test when there's no match - if let Some(test_pattern) = &filter.args().test_pattern { - let test_name = test_pattern.as_str(); - let candidates = abis - .into_iter() - .filter(|(id, _)| { - filter.matches_path(&id.source) && filter.matches_contract(&id.name) - }) - .flat_map(|(_, abi)| abi.functions.into_keys()) - .collect::>(); - if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { - sh_println!("\nDid you mean `{suggestion}`?")?; - } - } - } - - eyre::bail!("No tests to run"); - } - - // Always recompile all sources to ensure that `getCode` cheatcode can use any artifact. - test_sources.extend(source_files_iter( - &project.paths.sources, - MultiCompilerLanguage::FILE_EXTENSIONS, - )); - - Ok(test_sources) + Ok(output + .artifact_ids() + .filter_map(|(id, artifact)| artifact.abi.as_ref().map(|abi| (id, abi))) + .filter(|(id, abi)| { + id.source.starts_with(&config.src) || matches_artifact(test_filter, id, abi) + }) + .map(|(id, _)| id.source) + .collect()) } /// Executes all the tests in the project. @@ -283,21 +272,13 @@ impl TestArgs { /// configured filter will be executed /// /// Returns the test results for all matching tests. - pub async fn execute_tests(mut self) -> Result { + pub async fn compile_and_run(&mut self) -> Result { // Merge all configs. - let (mut config, mut evm_opts) = self.load_config_and_evm_opts()?; - - // Explicitly enable isolation for gas reports for more correct gas accounting. - if self.gas_report { - evm_opts.isolate = true; - } else { - // Do not collect gas report traces if gas report is not enabled. - config.fuzz.gas_report_samples = 0; - config.invariant.gas_report_samples = 0; - } + let (mut config, evm_opts) = self.load_config_and_evm_opts()?; // Install missing dependencies. - if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings + { // need to re-configure here to also catch additional remappings config = self.load_config()?; } @@ -308,29 +289,55 @@ impl TestArgs { let filter = self.filter(&config)?; trace!(target: "forge::test", ?filter, "using filter"); - let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; - let compiler = ProjectCompiler::new() .dynamic_test_linking(config.dynamic_test_linking) .quiet(shell::is_json() || self.junit) - .files(sources_to_compile); - + .files(self.get_sources_to_compile(&config, &filter)?); let output = compiler.compile(&project)?; - // Create test options from general project settings and compiler output. - let project_root = &project.paths.root; + self.run_tests(&project.paths.root, config, evm_opts, &output, &filter, false).await + } + + /// Executes all the tests in the project. + /// + /// See [`Self::compile_and_run`] for more details. + pub async fn run_tests( + &mut self, + project_root: &Path, + mut config: Config, + mut evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + ) -> Result { + if config.fuzz.run == Some(0) { + bail!("`fuzz.run` must be greater than 0"); + } + // Explicitly enable isolation for gas reports for more correct gas accounting. + if self.gas_report { + evm_opts.isolate = true; + } else { + // Do not collect gas report traces if gas report is not enabled. + config.fuzz.gas_report_samples = 0; + config.invariant.gas_report_samples = 0; + } + + // Generate a random fuzz seed if none provided, for reproducibility. + config.fuzz.seed = config + .fuzz + .seed + .or_else(|| Some(U256::from_be_bytes(rand::rng().random::<[u8; 32]>()))); + + // Create test options from general project settings and compiler output. let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; - // Determine print verbosity and executor verbosity. - let verbosity = evm_opts.verbosity; + // Determine executor verbosity. if (self.gas_report && evm_opts.verbosity < 3) || self.flamegraph || self.flamechart { evm_opts.verbosity = 3; } - let env = evm_opts.evm_env().await?; - // Enable internal tracing for more informative flamegraph. if should_draw && !self.decode_internal { self.decode_internal = true; @@ -345,21 +352,84 @@ impl TestArgs { InternalTraceMode::None }; - // Prepare the test builder. - let config = Arc::new(config); - let runner = MultiContractRunnerBuilder::new(config.clone()) - .set_debug(should_debug) - .set_decode_internal(decode_internal) - .initial_balance(evm_opts.initial_balance) - .evm_spec(config.evm_spec_id()) - .sender(evm_opts.sender) - .with_fork(evm_opts.get_fork(&config, env.clone())) - .enable_isolation(evm_opts.isolate) - .odyssey(evm_opts.odyssey) - .build::(project_root, &output, env, evm_opts)?; + // Auto-detect network from fork chain ID when not explicitly configured. + evm_opts.infer_network_from_fork().await; + + // Parse inline config early to detect per-test network annotations. + let inline_config = InlineConfig::new_parsed(output, &config)?; + let override_networks = inline_config.referenced_override_networks(&config.profile); + + let (libraries, mut outcome) = if override_networks.is_empty() { + // Single-pass: no per-test network overrides, use global network setting. + self.dispatch_network( + &evm_opts, + config, + evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig::default(), + ) + .await? + } else { + // Multi-pass: run each distinct network separately and merge results. + let all_override_networks = override_networks.clone(); + let multi_pass_timer = Instant::now(); + + // Default pass: global network, runs tests without an explicit network annotation. + let (libraries, mut outcome) = self + .dispatch_network( + &evm_opts, + config.clone(), + evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: None, + }, + ) + .await?; + + // Override passes: one per annotated network. + for &network in &override_networks { + let mut pass_evm_opts = evm_opts.clone(); + pass_evm_opts.networks = network.into(); + let (_, pass_outcome) = self + .dispatch_network( + &pass_evm_opts, + config.clone(), + pass_evm_opts.clone(), + output, + filter, + coverage, + should_debug, + decode_internal, + MultiNetworkConfig { + all_override_networks: all_override_networks.clone(), + pass_network: Some(network), + }, + ) + .await?; + merge_outcomes(&mut outcome, pass_outcome); + } - let libraries = runner.libraries.clone(); - let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; + // Print the merged summary (per-pass summaries are suppressed in `run_tests_inner`). + if !self.summary && !shell::is_json() { + sh_println!("{}", outcome.summary(multi_pass_timer.elapsed()))?; + } + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; + } + + (libraries, outcome) + }; if should_draw { let (suite_name, test_name, mut test_result) = @@ -374,7 +444,7 @@ impl TestArgs { // Decode traces. let decoder = outcome.last_run_decoder.as_ref().unwrap(); decode_trace_arena(arena, decoder).await; - let mut fst = folded_stack_trace::build(arena); + let mut fst = folded_stack_trace::build(arena, self.evm.isolate); let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; let contract = suite_name.split(':').next_back().unwrap(); @@ -408,7 +478,7 @@ impl TestArgs { outcome.remove_first().ok_or_eyre("no tests were executed")?; let sources = - ContractSources::from_project_output(&output, project.root(), Some(&libraries))?; + ContractSources::from_project_output(output, project_root, Some(&libraries))?; // Run the debugger. let mut builder = Debugger::builder() @@ -423,8 +493,8 @@ impl TestArgs { } let mut debugger = builder.build(); - if let Some(dump_path) = self.dump { - debugger.dump_to_file(&dump_path)?; + if let Some(dump_path) = &self.dump { + debugger.dump_to_file(dump_path)?; } else { debugger.try_run_tui()?; } @@ -433,15 +503,107 @@ impl TestArgs { Ok(outcome) } + /// Build the test runner and execute tests for a specific network type. + #[allow(clippy::too_many_arguments)] + async fn build_and_run_tests( + &self, + config: Config, + evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + should_debug: bool, + decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, + ) -> eyre::Result<(Libraries, TestOutcome)> { + let verbosity = evm_opts.verbosity; + let (evm_env, tx_env, fork_block) = + evm_opts.env::, BlockEnvFor, TxEnvFor>().await?; + + let config = Arc::new(config); + let runner = MultiContractRunnerBuilder::new(config.clone()) + .set_debug(should_debug) + .set_decode_internal(decode_internal) + .initial_balance(evm_opts.initial_balance) + .sender(evm_opts.sender) + .with_fork(evm_opts.get_fork(&config, evm_env.cfg_env.chain_id, fork_block)) + .enable_isolation(evm_opts.isolate) + .fail_fast(self.fail_fast) + .set_coverage(coverage) + .with_multi_network(multi_network) + .build::(output, evm_env, tx_env, evm_opts)?; + + let libraries = runner.libraries.clone(); + let outcome = self.run_tests_inner(runner, config, verbosity, filter, output).await?; + Ok((libraries, outcome)) + } + + /// Dispatches `build_and_run_tests` to the correct network type based on `evm_opts.networks`. + #[allow(clippy::too_many_arguments)] + async fn dispatch_network( + &self, + dispatch_opts: &EvmOpts, + config: Config, + evm_opts: EvmOpts, + output: &ProjectCompileOutput, + filter: &ProjectPathsAwareFilter, + coverage: bool, + should_debug: bool, + decode_internal: InternalTraceMode, + multi_network: MultiNetworkConfig, + ) -> eyre::Result<(Libraries, TestOutcome)> { + if dispatch_opts.networks.is_tempo() { + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } else { + #[cfg(feature = "optimism")] + if dispatch_opts.networks.is_optimism() { + return self + .build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await; + } + self.build_and_run_tests::( + config, + evm_opts, + output, + filter, + coverage, + should_debug, + decode_internal, + multi_network, + ) + .await + } + } + /// Run all tests that matches the filter predicate from a test runner - pub async fn run_tests( + async fn run_tests_inner( &self, - mut runner: MultiContractRunner, + mut runner: MultiContractRunner, config: Arc, verbosity: u8, filter: &ProjectPathsAwareFilter, output: &ProjectCompileOutput, ) -> eyre::Result { + let fuzz_seed = config.fuzz.seed; if self.list { return list(runner, filter); } @@ -452,6 +614,33 @@ impl TestArgs { let silent = self.gas_report && shell::is_json() || self.summary && shell::is_json(); let num_filtered = runner.matching_test_functions(filter).count(); + + if num_filtered == 0 { + let total_tests = if filter.is_empty() { + num_filtered + } else { + runner.matching_test_functions(&EmptyTestFilter::default()).count() + }; + if total_tests == 0 { + sh_println!( + "No tests found in project! Forge looks for functions that start with `test`" + )?; + } else { + let mut msg = format!("no tests match the provided pattern:\n{filter}"); + // Try to suggest a test when there's no match. + if let Some(test_pattern) = &filter.args().test_pattern { + let test_name = test_pattern.as_str(); + // Filter contracts but not test functions. + let candidates = runner.all_test_functions(filter).map(|f| &f.name); + if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { + write!(msg, "\nDid you mean `{suggestion}`?")?; + } + } + sh_warn!("{msg}")?; + } + return Ok(TestOutcome::empty(Some(runner.known_contracts.clone()), false)); + } + if num_filtered != 1 && (self.debug || self.flamegraph || self.flamechart) { let action = if self.flamegraph { "generate a flamegraph" @@ -479,7 +668,7 @@ impl TestArgs { // Run tests in a non-streaming fashion and collect results for serialization. if !self.gas_report && !self.summary && shell::is_json() { let mut results = runner.test_collect(filter)?; - results.values_mut().for_each(|suite_result| { + for suite_result in results.values_mut() { for test_result in suite_result.test_results.values_mut() { if verbosity >= 2 { // Decode logs at level 2 and above. @@ -489,44 +678,55 @@ impl TestArgs { test_result.logs = vec![]; } } - }); + } sh_println!("{}", serde_json::to_string(&results)?)?; - return Ok(TestOutcome::new(results, self.allow_failure)); + let kc = runner.known_contracts.clone(); + return Ok(TestOutcome::new(Some(kc), results, self.allow_failure, fuzz_seed)); } if self.junit { let results = runner.test_collect(filter)?; sh_println!("{}", junit_xml_report(&results, verbosity).to_string()?)?; - return Ok(TestOutcome::new(results, self.allow_failure)); + let kc = runner.known_contracts.clone(); + return Ok(TestOutcome::new(Some(kc), results, self.allow_failure, fuzz_seed)); } - let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; + let remote_chain = + if runner.fork.is_some() { runner.tx_env.chain_id().map(Into::into) } else { None }; let known_contracts = runner.known_contracts.clone(); let libraries = runner.libraries.clone(); + // Capture multi-pass state before moving `runner` into the spawn task. + // In multi-pass mode the per-pass summary is suppressed; the merged summary is + // printed once by the caller after all passes complete. + let is_multi_pass = !runner.tcfg.multi_network.all_override_networks.is_empty(); + // Run tests in a streaming fashion. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); let show_progress = config.show_progress; let handle = tokio::task::spawn_blocking({ let filter = filter.clone(); - move || runner.test(&filter, tx, show_progress) + move || runner.test(&filter, tx, show_progress).map(|()| runner) }); // Set up trace identifiers. let mut identifier = TraceIdentifiers::new().with_local(&known_contracts); - // Avoid using etherscan for gas report as we decode more traces and this will be - // expensive. - if !self.gas_report { - identifier = identifier.with_etherscan(&config, remote_chain_id)?; + // Avoid using external identifiers for gas report as we decode more traces and this will be + // expensive. Also skip external identifiers for local tests (no remote chain) to avoid + // unnecessary Etherscan API calls that significantly slow down test execution. + if !self.gas_report && remote_chain.is_some() { + identifier = identifier.with_external(&config, remote_chain)?; } // Build the trace decoder. let mut builder = CallTraceDecoderBuilder::new() .with_known_contracts(&known_contracts) - .with_verbosity(verbosity); + .with_label_disabled(self.disable_labels) + .with_verbosity(verbosity) + .with_chain_id(remote_chain.map(|c| c.id())); // Signatures are of no value for gas reports. if !self.gas_report { builder = @@ -550,21 +750,31 @@ impl TestArgs { let mut gas_snapshots = BTreeMap::>::new(); - let mut outcome = TestOutcome::empty(self.allow_failure); + let mut outcome = TestOutcome::empty(None, self.allow_failure); + outcome.fuzz_seed = fuzz_seed; let mut any_test_failed = false; - for (contract_name, suite_result) in rx { - let tests = &suite_result.test_results; + let mut backtrace_builder = None; + for (contract_name, mut suite_result) in rx { + let tests = &mut suite_result.test_results; + let has_tests = !tests.is_empty(); + + // In multi-pass (per-test network override) mode, skip suites that contributed no + // tests to this pass so we don't emit a stray blank line in the suite header or + // pollute the outcome with empty entries. + if is_multi_pass && !has_tests && suite_result.warnings.is_empty() { + continue; + } // Clear the addresses and labels from previous test. decoder.clear_addresses(); // We identify addresses if we're going to print *any* trace or gas report. - let identify_addresses = verbosity >= 3 || - self.gas_report || - self.debug || - self.flamegraph || - self.flamechart; + let identify_addresses = verbosity >= 3 + || self.gas_report + || self.debug + || self.flamegraph + || self.flamechart; // Print suite header. if !silent { @@ -572,7 +782,7 @@ impl TestArgs { for warning in &suite_result.warnings { sh_warn!("{warning}")?; } - if !tests.is_empty() { + if has_tests { let len = tests.len(); let tests = if len > 1 { "tests" } else { "test" }; sh_println!("Ran {len} {tests} for {contract_name}")?; @@ -587,10 +797,10 @@ impl TestArgs { sh_println!("{}", result.short_result(name))?; // Display invariant metrics if invariant kind. - if let TestKind::Invariant { metrics, .. } = &result.kind { - if !metrics.is_empty() { - let _ = sh_println!("\n{}\n", format_invariant_metrics_table(metrics)); - } + if let TestKind::Invariant { metrics, .. } = &result.kind + && !metrics.is_empty() + { + let _ = sh_println!("\n{}\n", format_invariant_metrics_table(metrics)); } // We only display logs at level 2 and above @@ -613,13 +823,11 @@ impl TestArgs { // Clear the addresses and labels from previous runs. decoder.clear_addresses(); - decoder - .labels - .extend(result.labeled_addresses.iter().map(|(k, v)| (*k, v.clone()))); + decoder.labels.extend(result.labels.iter().map(|(k, v)| (*k, v.clone()))); // Identify addresses and decode traces. let mut decoded_traces = Vec::with_capacity(result.traces.len()); - for (kind, arena) in &mut result.traces.clone() { + for (kind, arena) in &mut result.traces { if identify_addresses { decoder.identify(arena, &mut identifier); } @@ -641,6 +849,11 @@ impl TestArgs { if should_include { decode_trace_arena(arena, &decoder).await; + + if let Some(trace_depth) = self.trace_depth { + prune_trace_depth(arena, trace_depth); + } + decoded_traces.push(render_trace_arena_inner(arena, false, verbosity > 4)); } } @@ -652,6 +865,33 @@ impl TestArgs { } } + // Extract and display backtrace for failed tests when verbosity >= 3. + // At verbosity 3-4 backtraces show contract/function names only. + // At verbosity 5 backtraces include source file locations. + if !silent + && result.status.is_failure() + && verbosity >= 3 + && !result.traces.is_empty() + && let Some((_, arena)) = + result.traces.iter().find(|(kind, _)| matches!(kind, TraceKind::Execution)) + { + // Lazily initialize the backtrace builder on first failure + let builder = backtrace_builder.get_or_insert_with(|| { + BacktraceBuilder::new( + output, + config.root.clone(), + config.parsed_libraries().ok(), + config.via_ir, + ) + }); + + let backtrace = builder.from_traces(arena); + + if !backtrace.is_empty() { + sh_println!("{}", backtrace)?; + } + } + if let Some(gas_report) = &mut gas_report { gas_report.analyze(result.traces.iter().map(|(_, a)| &a.arena), &decoder).await; @@ -672,6 +912,8 @@ impl TestArgs { } } } + // Clear memory. + result.gas_report_traces = Default::default(); // Collect and merge gas snapshots. for (group, new_snapshots) in &result.gas_snapshots { @@ -693,12 +935,11 @@ impl TestArgs { // // Exiting early with code 1 if differences are found. if self.gas_snapshot_check.unwrap_or(config.gas_snapshot_check) { - let differences_found = gas_snapshots.clone().into_iter().fold( - false, - |mut found, (group, snapshots)| { + let differences_found = + gas_snapshots.iter().fold(false, |mut found, (group, snapshots)| { // If the snapshot file doesn't exist, we can't compare so we skip. if !&config.snapshots.join(format!("{group}.json")).exists() { - return false; + return found; } let previous_snapshots: BTreeMap = @@ -709,14 +950,9 @@ impl TestArgs { .iter() .filter_map(|(k, v)| { previous_snapshots.get(k).and_then(|previous_snapshot| { - if previous_snapshot != v { - Some(( - k.clone(), - (previous_snapshot.clone(), v.clone()), - )) - } else { - None - } + (previous_snapshot != v).then(|| { + (k.clone(), (previous_snapshot.clone(), v.clone())) + }) }) }) .collect(); @@ -738,8 +974,7 @@ impl TestArgs { } found - }, - ); + }); if differences_found { sh_eprintln!()?; @@ -761,18 +996,18 @@ impl TestArgs { fs::create_dir_all(&config.snapshots)?; // Write gas snapshots to disk per group. - gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + for (group, snapshots) in &gas_snapshots { fs::write_pretty_json_file( &config.snapshots.join(format!("{group}.json")), &snapshots, ) .expect("Failed to write gas snapshots to disk"); - }); + } } } // Print suite summary. - if !silent { + if !silent && has_tests { sh_println!("{}", suite_result.summary())?; } @@ -791,25 +1026,29 @@ impl TestArgs { if let Some(gas_report) = gas_report { let finalized = gas_report.finalize(); - sh_println!("{}", &finalized)?; + sh_println!("{finalized}")?; outcome.gas_report = Some(finalized); } - if !self.summary && !shell::is_json() { + if !is_multi_pass && !self.summary && !shell::is_json() { sh_println!("{}", outcome.summary(duration))?; } - if self.summary && !outcome.results.is_empty() { + if !is_multi_pass && self.summary && !outcome.results.is_empty() { let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); - sh_println!("{}", &summary_report)?; + sh_println!("{summary_report}")?; } // Reattach the task. - if let Err(e) = handle.await { - match e.try_into_panic() { + match handle.await { + Ok(result) => { + let runner = result?; + outcome.known_contracts = Some(runner.known_contracts); + } + Err(e) => match e.try_into_panic() { Ok(payload) => std::panic::resume_unwind(payload), Err(e) => return Err(e.into()), - } + }, } // Persist test run failures to enable replaying. @@ -836,7 +1075,7 @@ impl TestArgs { } /// Returns whether `BuildArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { + pub const fn is_watch(&self) -> bool { self.watch.watch.is_some() } @@ -864,6 +1103,12 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_run) = self.fuzz_run { + fuzz_dict.insert("run".to_string(), fuzz_run.into()); + } + if let Some(fuzz_worker) = self.fuzz_worker { + fuzz_dict.insert("worker".to_string(), fuzz_worker.into()); + } if let Some(fuzz_timeout) = self.fuzz_timeout { fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); } @@ -875,11 +1120,7 @@ impl Provider for TestArgs { if let Some(etherscan_api_key) = self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) { - dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); - } - - if let Some(api_version) = &self.etherscan_api_version { - dict.insert("etherscan_api_version".to_string(), api_version.to_string().into()); + dict.insert("etherscan_api_key".to_string(), etherscan_api_key.clone().into()); } if self.show_progress { @@ -891,7 +1132,10 @@ impl Provider for TestArgs { } /// Lists all matching tests -fn list(runner: MultiContractRunner, filter: &ProjectPathsAwareFilter) -> Result { +fn list( + runner: MultiContractRunner, + filter: &ProjectPathsAwareFilter, +) -> Result { let results = runner.list(filter); if shell::is_json() { @@ -905,13 +1149,43 @@ fn list(runner: MultiContractRunner, filter: &ProjectPathsAwareFilter) -> Result } } } - Ok(TestOutcome::empty(false)) + Ok(TestOutcome::empty(Some(runner.known_contracts), false)) +} + +/// Merges `other` into `base` by extending suite results. +/// +/// For suites that appear in both, test results are combined (function-level pass routing ensures +/// each function appears in exactly one pass, so there are no key conflicts in practice). +fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { + for (suite_id, other_suite) in other.results { + match base.results.entry(suite_id) { + std::collections::btree_map::Entry::Vacant(e) => { + e.insert(other_suite); + } + std::collections::btree_map::Entry::Occupied(mut e) => { + let base_suite = e.get_mut(); + base_suite.test_results.extend(other_suite.test_results); + base_suite.warnings.extend(other_suite.warnings); + base_suite.duration += other_suite.duration; + } + } + } + if let Some(decoder) = other.last_run_decoder { + base.last_run_decoder = Some(decoder); + } } /// Load persisted filter (with last test run failures) from file. fn last_run_failures(config: &Config) -> Option { match fs::read_to_string(&config.test_failures_file) { - Ok(filter) => Some(Regex::new(&filter).unwrap()), + Ok(filter) => Regex::new(&filter) + .inspect_err(|e| { + _ = sh_warn!( + "failed to parse test filter from {:?}: {e}", + config.test_failures_file + ) + }) + .ok(), Err(_) => None, } } @@ -922,12 +1196,12 @@ fn persist_run_failures(config: &Config, outcome: &TestOutcome) { let mut filter = String::new(); let mut failures = outcome.failures().peekable(); while let Some((test_name, _)) = failures.next() { - if test_name.is_any_test() { - if let Some(test_match) = test_name.split("(").next() { - filter.push_str(test_match); - if failures.peek().is_some() { - filter.push('|'); - } + if test_name.is_any_test() + && let Some(test_match) = test_name.split('(').next() + { + filter.push_str(test_match); + if failures.peek().is_some() { + filter.push('|'); } } } @@ -995,6 +1269,12 @@ mod tests { assert!(args.fuzz_seed.is_some()); } + #[test] + fn depth_trace() { + let args: TestArgs = TestArgs::parse_from(["foundry-cli", "--trace-depth", "2"]); + assert!(args.trace_depth.is_some()); + } + // #[test] fn fuzz_seed_exists() { @@ -1003,6 +1283,14 @@ mod tests { assert!(args.fuzz_seed.is_some()); } + #[test] + fn fuzz_run() { + let args: TestArgs = + TestArgs::parse_from(["foundry-cli", "--fuzz-run", "10", "--fuzz-worker", "2"]); + assert_eq!(args.fuzz_run, Some(10)); + assert_eq!(args.fuzz_worker, Some(2)); + } + #[test] fn extract_chain() { let test = |arg: &str, expected: Chain| { diff --git a/crates/forge/src/cmd/test/summary.rs b/crates/forge/src/cmd/test/summary.rs index 68ab3f4590b05..a0123e896d0bf 100644 --- a/crates/forge/src/cmd/test/summary.rs +++ b/crates/forge/src/cmd/test/summary.rs @@ -1,6 +1,8 @@ use crate::cmd::test::TestOutcome; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Row, Table}; -use foundry_common::reports::{report_kind, ReportKind}; +use comfy_table::{ + Cell, Color, Row, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, +}; +use foundry_common::shell; use foundry_evm::executors::invariant::InvariantMetrics; use itertools::Itertools; use serde_json::json; @@ -8,8 +10,6 @@ use std::{collections::HashMap, fmt::Display}; /// Represents a test summary report. pub struct TestSummaryReport { - /// The kind of report to generate. - report_kind: ReportKind, /// Whether the report should be detailed. is_detailed: bool, /// The test outcome to report. @@ -17,22 +17,18 @@ pub struct TestSummaryReport { } impl TestSummaryReport { - pub fn new(is_detailed: bool, outcome: TestOutcome) -> Self { - Self { report_kind: report_kind(), is_detailed, outcome } + pub const fn new(is_detailed: bool, outcome: TestOutcome) -> Self { + Self { is_detailed, outcome } } } impl Display for TestSummaryReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self.report_kind { - ReportKind::Text => { - writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; - } - ReportKind::JSON => { - writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; - } + if shell::is_json() { + writeln!(f, "{}", self.format_json_output(&self.is_detailed, &self.outcome))?; + } else { + writeln!(f, "\n{}", self.format_table_output(&self.is_detailed, &self.outcome))?; } - Ok(()) } } @@ -67,7 +63,11 @@ impl TestSummaryReport { fn format_table_output(&self, is_detailed: &bool, outcome: &TestOutcome) -> Table { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } let mut row = Row::from(vec![ Cell::new("Test Suite"), @@ -114,7 +114,7 @@ impl TestSummaryReport { if self.is_detailed { row.add_cell(Cell::new(suite_path)); - row.add_cell(Cell::new(format!("{:.2?}", suite.duration).to_string())); + row.add_cell(Cell::new(format!("{:.2?}", suite.duration))); } table.add_row(row); @@ -141,7 +141,11 @@ pub(crate) fn format_invariant_metrics_table( test_metrics: &HashMap, ) -> Table { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(vec![ Cell::new("Contract"), diff --git a/crates/forge/src/cmd/tree.rs b/crates/forge/src/cmd/tree.rs index b97d7c8d98654..84c4ea7b36d54 100644 --- a/crates/forge/src/cmd/tree.rs +++ b/crates/forge/src/cmd/tree.rs @@ -2,8 +2,8 @@ use clap::Parser; use eyre::Result; use foundry_cli::{opts::ProjectPathOpts, utils::LoadConfig}; use foundry_compilers::{ - resolver::{parse::SolData, Charset, TreeOptions}, Graph, + resolver::{Charset, TreeOptions}, }; /// CLI arguments for `forge tree`. @@ -28,7 +28,7 @@ foundry_config::impl_figment_convert!(TreeArgs, project_paths); impl TreeArgs { pub fn run(self) -> Result<()> { let config = self.load_config()?; - let graph = Graph::::resolve(&config.project_paths())?; + let graph = ::resolve(&config.project_paths())?; let opts = TreeOptions { charset: self.charset, no_dedupe: self.no_dedupe }; graph.print_with_options(opts); diff --git a/crates/forge/src/cmd/update.rs b/crates/forge/src/cmd/update.rs index 5e965c34a9999..9bdd9089df570 100644 --- a/crates/forge/src/cmd/update.rs +++ b/crates/forge/src/cmd/update.rs @@ -1,11 +1,14 @@ +use crate::{DepIdentifier, DepMap, Lockfile}; +use alloy_primitives::map::HashMap; use clap::{Parser, ValueHint}; use eyre::{Context, Result}; use foundry_cli::{ opts::Dependency, utils::{Git, LoadConfig}, }; -use foundry_config::{impl_figment_convert_basic, Config}; +use foundry_config::{Config, impl_figment_convert_basic}; use std::path::PathBuf; +use yansi::Paint; /// CLI arguments for `forge update`. #[derive(Clone, Debug, Parser)] @@ -33,39 +36,197 @@ impl_figment_convert_basic!(UpdateArgs); impl UpdateArgs { pub fn run(self) -> Result<()> { let config = self.load_config()?; - let (root, paths) = dependencies_paths(&self.dependencies, &config)?; + // dep_overrides consists of absolute paths of dependencies and their tags + let (root, _paths, dep_overrides) = dependencies_paths(&self.dependencies, &config)?; + // Mapping of relative path of lib to its tag type + // e.g "lib/forge-std" -> DepIdentifier::Tag { name: "v0.1.0", rev: "1234567" } + let git = Git::new(&root); + + let mut foundry_lock = Lockfile::new(&config.root).with_git(&git); + let out_of_sync_deps = foundry_lock.sync(config.install_lib_dir())?; + + // update the submodules' tags if any overrides are present + let mut prev_dep_ids: DepMap = HashMap::default(); + if dep_overrides.is_empty() { + // running `forge update`, update all deps + foundry_lock.iter_mut().for_each(|(_path, dep_id)| { + // Set r#override flag to true if the dep is a branch + if let DepIdentifier::Branch { .. } = dep_id { + dep_id.mark_override(); + } + }); + } else { + for (dep_path, override_tag) in &dep_overrides { + let rel_path = dep_path + .strip_prefix(&root) + .wrap_err("Dependency path is not relative to the repository root")?; + + if let Ok(mut dep_id) = DepIdentifier::resolve_type(&git, dep_path, override_tag) { + // Store the previous state before overriding + let prev = foundry_lock.get(rel_path).cloned(); + + // If it's a branch, mark it as overridden so it gets updated below + if let DepIdentifier::Branch { .. } = dep_id { + dep_id.mark_override(); + } + + // Update the lockfile + foundry_lock.override_dep(rel_path, dep_id)?; + + // Only track as updated if there was a previous dependency + if let Some(prev) = prev { + prev_dep_ids.insert(rel_path.to_owned(), prev); + } + } else { + sh_warn!( + "Could not r#override submodule at {} with tag {}, try using forge install", + rel_path.display(), + override_tag + )?; + } + } + } + // fetch the latest changes for each submodule (recursively if flag is set) let git = Git::new(&root); + let update_paths = self.update_dep_paths(&foundry_lock); + trace!(?update_paths, "updating deps at"); + if self.recursive { // update submodules recursively - git.submodule_update(self.force, true, false, true, paths) + git.submodule_update(self.force, true, false, true, update_paths)?; } else { - // update root submodules - git.submodule_update(self.force, true, false, false, paths)?; - // initialize submodules of each submodule recursively (otherwise direct submodule - // dependencies will revert to last commit) - git.submodule_foreach(false, "git submodule update --init --progress --recursive") + let is_empty = update_paths.is_empty(); + + // update submodules + git.submodule_update(self.force, true, false, false, update_paths)?; + + if !is_empty { + // initialize submodules of each submodule recursively (otherwise direct submodule + // dependencies will revert to last commit) + git.submodule_foreach(false, "git submodule update --init --progress --recursive")?; + } + } + + // Update branches to their latest commit from origin + // This handles both explicit updates (forge update dep@branch) and + // general updates (forge update) for branch-tracked dependencies + let branch_overrides = foundry_lock + .iter_mut() + .filter_map(|(path, dep_id)| { + if dep_id.is_branch() && dep_id.overridden() { + return Some((path, dep_id)); + } + None + }) + .collect::>(); + + for (path, dep_id) in branch_overrides { + let submodule_path = root.join(path); + let name = dep_id.name(); + + // Fetch and checkout the latest commit from the remote branch + git.fetch_and_checkout_branch(&submodule_path, name)?; + + // Now get the updated revision after syncing with origin + let (updated_rev, _) = git.current_rev_branch(&submodule_path)?; + + // Update the lockfile entry to reflect the latest commit + let prev = std::mem::replace( + dep_id, + DepIdentifier::Branch { + name: name.to_string(), + rev: updated_rev, + r#override: true, + }, + ); + + // Only insert if we don't already have a previous state for this path + // (e.g., from explicit overrides where we converted tag to branch) + if !prev_dep_ids.contains_key(path) { + prev_dep_ids.insert(path.to_owned(), prev); + } + } + + // checkout the submodules at the correct tags + // Skip branches that were already updated above to avoid reverting to local branch + for (path, dep_id) in foundry_lock.iter() { + // Ignore other dependencies if single update. + if !dep_overrides.is_empty() && !dep_overrides.contains_key(path) { + continue; + } + + // Skip branches that were already updated + if dep_id.is_branch() && dep_id.overridden() { + continue; + } + git.checkout_at(dep_id.checkout_id(), &root.join(path))?; + } + + if out_of_sync_deps.is_some_and(|o| !o.is_empty()) + || foundry_lock.iter().any(|(_, dep_id)| dep_id.overridden()) + { + foundry_lock.write()?; + } + + // Print updates from => to + for (path, prev) in prev_dep_ids { + let curr = foundry_lock.get(&path).unwrap(); + sh_println!( + "Updated dep at '{}', (from: {prev}, to: {curr})", + path.display().green(), + prev = prev, + curr = curr.yellow() + )?; } + + Ok(()) + } + + /// Returns the `lib/paths` of the dependencies that have been updated/overridden. + fn update_dep_paths(&self, foundry_lock: &Lockfile<'_>) -> Vec { + foundry_lock + .iter() + .filter_map(|(path, dep_id)| { + if dep_id.overridden() { + return Some(path.clone()); + } + None + }) + .collect() } } -/// Returns `(root, paths)` where `root` is the root of the Git repository and `paths` are the -/// relative paths of the dependencies. -pub fn dependencies_paths(deps: &[Dependency], config: &Config) -> Result<(PathBuf, Vec)> { +/// Returns `(root, paths, overridden_deps_with_abosolute_paths)` where `root` is the root of the +/// Git repository and `paths` are the relative paths of the dependencies. +#[allow(clippy::type_complexity)] +pub fn dependencies_paths( + deps: &[Dependency], + config: &Config, +) -> Result<(PathBuf, Vec, HashMap)> { let git_root = Git::root_of(&config.root)?; let libs = config.install_lib_dir(); + if deps.is_empty() { + return Ok((git_root, Vec::new(), HashMap::default())); + } + let mut paths = Vec::with_capacity(deps.len()); + let mut overrides = HashMap::with_capacity_and_hasher(deps.len(), Default::default()); for dep in deps { let name = dep.name(); let dep_path = libs.join(name); + if !dep_path.exists() { + eyre::bail!("Could not find dependency {name:?} in {}", dep_path.display()); + } let rel_path = dep_path .strip_prefix(&git_root) .wrap_err("Library directory is not relative to the repository root")?; - if !dep_path.exists() { - eyre::bail!("Could not find dependency {name:?} in {}", dep_path.display()); + + if let Some(tag) = &dep.tag { + overrides.insert(dep_path.clone(), tag.to_owned()); } paths.push(rel_path.to_owned()); } - Ok((git_root, paths)) + Ok((git_root, paths, overrides)) } diff --git a/crates/forge/src/cmd/watch.rs b/crates/forge/src/cmd/watch.rs index 077604d2d8000..7fce12fe719a6 100644 --- a/crates/forge/src/cmd/watch.rs +++ b/crates/forge/src/cmd/watch.rs @@ -11,20 +11,23 @@ use parking_lot::Mutex; use std::{ path::PathBuf, sync::{ - atomic::{AtomicU8, Ordering}, Arc, + atomic::{AtomicU8, Ordering}, }, time::Duration, }; use tokio::process::Command as TokioCommand; use watchexec::{ + Watchexec, action::ActionHandler, command::{Command, Program}, job::{CommandState, Job}, paths::summarise_events_to_env, - Watchexec, }; -use watchexec_events::{Event, Priority, ProcessEnd}; +use watchexec_events::{ + Event, Priority, ProcessEnd, Tag, + filekind::{AccessKind, FileEventKind}, +}; use watchexec_signals::Signal; use yansi::{Color, Paint}; @@ -49,6 +52,13 @@ pub struct WatchArgs { #[arg(long)] pub run_all: bool, + /// Re-run only previously failed tests first when a change is made. + /// + /// If all previously failed tests pass, the full test suite will be run automatically. + /// This is particularly useful for TDD workflows where you want fast feedback on failures. + #[arg(long, alias = "rerun-failures")] + pub rerun_failed: bool, + /// File update debounce delay. /// /// During the delay, incoming change events are accumulated and @@ -179,6 +189,33 @@ impl WatchArgs { return action; } + if cfg!(target_os = "linux") { + // Reading a file now triggers `Access(Open)` events on Linux due to: + // https://github.com/notify-rs/notify/pull/612 + // This causes an infinite rebuild loop: the build reads a file, + // which triggers a notification, which restarts the build, and so on. + // To prevent this, we ignore `Access(Open)` events during event processing. + let mut has_file_events = false; + let mut has_synthetic_events = false; + 'outer: for e in action.events.iter() { + if e.is_empty() { + has_synthetic_events = true; + break; + } + for tag in &e.tags { + if let Tag::FileEventKind(kind) = tag + && !matches!(kind, FileEventKind::Access(AccessKind::Open(_))) { + has_file_events = true; + break 'outer; + } + } + } + if !has_file_events && !has_synthetic_events { + debug!("no filesystem events (other than Access(Open)) or synthetic events, skip without doing more"); + return action; + } + } + job.run({ let job = job.clone(); move |context| { @@ -265,16 +302,30 @@ pub async fn watch_test(args: TestArgs) -> Result<()> { let config: Config = args.build.load_config()?; let filter = args.filter(&config)?; // Marker to check whether to override the command. - let no_reconfigure = filter.args().test_pattern.is_some() || - filter.args().path_pattern.is_some() || - filter.args().contract_pattern.is_some() || - args.watch.run_all; + let no_reconfigure = filter.args().test_pattern.is_some() + || filter.args().path_pattern.is_some() + || filter.args().contract_pattern.is_some() + || args.watch.run_all; let last_test_files = Mutex::new(HashSet::::default()); let project_root = config.root.to_string_lossy().into_owned(); + let test_failures_file = config.test_failures_file.clone(); + let rerun_failed = args.watch.rerun_failed; + let config = args.watch.watchexec_config_with_override( || Ok([&config.test, &config.src]), move |events, command| { + // Check if we should prioritize rerunning failed tests + let has_failures = rerun_failed && test_failures_file.exists(); + + if has_failures { + // Smart mode: rerun failed tests first + trace!("Smart watch mode: will rerun failed tests first"); + command.arg("--rerun"); + // Don't add file-specific filters when rerunning failures + return; + } + let mut changed_sol_test_files: HashSet<_> = events .iter() .flat_map(|e| e.paths()) @@ -373,11 +424,11 @@ fn clean_cmd_args(num: usize, mut cmd_args: Vec) -> Vec { fn contains_w_in_short(arg: &str) -> Option { let mut iter = arg.chars().peekable(); if *iter.peek()? != '-' { - return None + return None; } iter.next(); if *iter.peek()? == '-' { - return None + return None; } Some(iter.any(|c| c == 'w')) } diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index b446fccc94b79..f7a20443b2dd3 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -1,9 +1,11 @@ //! Coverage reports. use alloy_primitives::map::{HashMap, HashSet}; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Attribute, Cell, Color, Row, Table}; +use comfy_table::{ + Attribute, Cell, Color, Row, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, +}; use evm_disassembler::disassemble_bytes; -use foundry_common::fs; +use foundry_common::{fs, shell}; use semver::Version; use std::{ collections::hash_map, @@ -15,6 +17,9 @@ pub use foundry_evm::coverage::*; /// A coverage reporter. pub trait CoverageReporter { + /// Returns a debug string for the reporter. + fn name(&self) -> &'static str; + /// Returns `true` if the reporter needs source maps for the final report. fn needs_source_maps(&self) -> bool { false @@ -35,7 +40,11 @@ pub struct CoverageSummaryReporter { impl Default for CoverageSummaryReporter { fn default() -> Self { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(vec![ Cell::new("File"), @@ -62,6 +71,10 @@ impl CoverageSummaryReporter { } impl CoverageReporter for CoverageSummaryReporter { + fn name(&self) -> &'static str { + "summary" + } + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { self.total.merge(&summary); @@ -102,12 +115,16 @@ pub struct LcovReporter { impl LcovReporter { /// Create a new LCOV reporter. - pub fn new(path: PathBuf, version: Version) -> Self { + pub const fn new(path: PathBuf, version: Version) -> Self { Self { path, version } } } impl CoverageReporter for LcovReporter { + fn name(&self) -> &'static str { + "lcov" + } + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { let mut out = std::io::BufWriter::new(fs::create_file(&self.path)?); @@ -118,6 +135,19 @@ impl CoverageReporter for LcovReporter { writeln!(out, "TN:")?; writeln!(out, "SF:{}", path.display())?; + // First pass: collect line hits for DA records. + // Track both which lines have been recorded and the max hits per line. + let mut line_hits: HashMap = HashMap::default(); + for item in &items { + if matches!(item.kind, CoverageItemKind::Line | CoverageItemKind::Statement) { + let line = item.loc.lines.start; + line_hits + .entry(line) + .and_modify(|h| *h = (*h).max(item.hits)) + .or_insert(item.hits); + } + } + let mut recorded_lines = HashSet::new(); for item in items { @@ -143,18 +173,26 @@ impl CoverageReporter for LcovReporter { } } // Add lines / statement hits only once. - CoverageItemKind::Line | CoverageItemKind::Statement => { - if recorded_lines.insert(line) { - writeln!(out, "DA:{line},{hits}")?; - } + CoverageItemKind::Line | CoverageItemKind::Statement + if recorded_lines.insert(line) => + { + writeln!(out, "DA:{line},{hits}")?; } CoverageItemKind::Branch { branch_id, path_id, .. } => { - writeln!( - out, - "BRDA:{line},{branch_id},{path_id},{}", - if hits == 0 { "-".to_string() } else { hits.to_string() } - )?; + // Per LCOV spec: "-" means the expression was never evaluated (line not + // executed), "0" means branch exists but was never taken. + // Check if the line containing this branch was hit. + let line_was_hit = line_hits.get(&line).is_some_and(|&h| h > 0); + let hits_str = if hits > 0 { + hits.to_string() + } else if line_was_hit { + "0".to_string() + } else { + "-".to_string() + }; + writeln!(out, "BRDA:{line},{branch_id},{path_id},{hits_str}")?; } + _ => {} } } @@ -184,33 +222,34 @@ impl CoverageReporter for LcovReporter { pub struct DebugReporter; impl CoverageReporter for DebugReporter { + fn name(&self) -> &'static str { + "debug" + } + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, items) in report.items_by_file() { - sh_println!("Uncovered for {}:", path.display())?; + let src = fs::read_to_string(path)?; + sh_println!("{}:", path.display())?; for item in items { - if item.hits == 0 { - sh_println!("- {item}")?; - } + sh_println!("- {}", item.fmt_with_source(Some(&src)))?; } sh_println!()?; } - for (contract_id, anchors) in &report.anchors { + for (contract_id, (cta, rta)) in &report.anchors { + if cta.is_empty() && rta.is_empty() { + continue; + } + sh_println!("Anchors for {contract_id}:")?; - let anchors = anchors - .0 + let anchors = cta .iter() .map(|anchor| (false, anchor)) - .chain(anchors.1.iter().map(|anchor| (true, anchor))); - for (is_deployed, anchor) in anchors { - sh_println!("- {anchor}")?; - if is_deployed { - sh_println!("- Creation code")?; - } else { - sh_println!("- Runtime code")?; - } + .chain(rta.iter().map(|anchor| (true, anchor))); + for (is_runtime, anchor) in anchors { + let kind = if is_runtime { " runtime" } else { "creation" }; sh_println!( - " - Refers to item: {}", + "- {kind} {anchor}: {}", report .analyses .get(&contract_id.version) @@ -231,12 +270,16 @@ pub struct BytecodeReporter { } impl BytecodeReporter { - pub fn new(root: PathBuf, destdir: PathBuf) -> Self { + pub const fn new(root: PathBuf, destdir: PathBuf) -> Self { Self { root, destdir } } } impl CoverageReporter for BytecodeReporter { + fn name(&self) -> &'static str { + "bytecode" + } + fn needs_source_maps(&self) -> bool { true } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index dc8527d5e3565..58b11d98874ed 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -5,12 +5,10 @@ use crate::{ traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use alloy_primitives::map::HashSet; -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; -use foundry_common::{ - calc, - reports::{report_kind, ReportKind}, - TestFunctionExt, +use comfy_table::{ + Cell, CellAlignment, Color, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, }; +use foundry_common::{TestFunctionExt, calc, shell}; use foundry_evm::traces::CallKind; use serde::{Deserialize, Serialize}; @@ -22,8 +20,6 @@ use std::{collections::BTreeMap, fmt::Display}; pub struct GasReport { /// Whether to report any contracts. report_any: bool, - /// What kind of report to generate. - report_kind: ReportKind, /// Contracts to generate the report for. report_for: HashSet, /// Contracts to ignore when generating the report. @@ -44,14 +40,7 @@ impl GasReport { let report_for = report_for.into_iter().collect::>(); let ignore = ignore.into_iter().collect::>(); let report_any = report_for.is_empty() || report_for.contains("*"); - Self { - report_any, - report_kind: report_kind(), - report_for, - ignore, - include_tests, - ..Default::default() - } + Self { report_any, report_for, ignore, include_tests, ..Default::default() } } /// Whether the given contract should be reported. @@ -98,7 +87,7 @@ impl GasReport { if !self.should_report(contract_name) { return; } - let contract_info = self.contracts.entry(name.to_string()).or_default(); + let contract_info = self.contracts.entry(name.clone()).or_default(); let is_create_call = trace.kind.is_any_create(); // Record contract deployment size. @@ -156,20 +145,17 @@ impl GasReport { impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self.report_kind { - ReportKind::Text => { - for (name, contract) in &self.contracts { - if contract.functions.is_empty() { - trace!(name, "gas report contract without functions"); - continue; - } - - let table = self.format_table_output(contract, name); - writeln!(f, "\n{table}")?; + if shell::is_json() { + writeln!(f, "{}", self.format_json_output())?; + } else { + for (name, contract) in &self.contracts { + if contract.functions.is_empty() { + trace!(name, "gas report contract without functions"); + continue; } - } - ReportKind::JSON => { - writeln!(f, "{}", &self.format_json_output())?; + + let table = self.format_table_output(contract, name); + writeln!(f, "\n{table}")?; } } @@ -191,8 +177,8 @@ impl GasReport { let functions = contract .functions - .iter() - .flat_map(|(_, sigs)| { + .values() + .flat_map(|sigs| { sigs.iter().map(|(sig, gas_info)| { let display_name = sig.replace(':', ""); (display_name, gas_info) @@ -216,7 +202,11 @@ impl GasReport { fn format_table_output(&self, contract: &ContractInfo, name: &str) -> Table { let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + if shell::is_markdown() { + table.load_preset(ASCII_MARKDOWN); + } else { + table.apply_modifier(UTF8_ROUND_CORNERS); + } table.set_header(vec![Cell::new(format!("{name} Contract")).fg(Color::Magenta)]); @@ -225,8 +215,8 @@ impl GasReport { Cell::new("Deployment Size").fg(Color::Cyan), ]); table.add_row(vec![ - Cell::new(contract.gas.to_string()), - Cell::new(contract.size.to_string()), + Cell::new(contract.gas.to_string()).set_alignment(CellAlignment::Right), + Cell::new(contract.size.to_string()).set_alignment(CellAlignment::Right), ]); // Add a blank row to separate deployment info from function info. @@ -241,22 +231,30 @@ impl GasReport { Cell::new("# Calls").fg(Color::Cyan), ]); - contract.functions.iter().for_each(|(fname, sigs)| { - sigs.iter().for_each(|(sig, gas_info)| { + for (fname, sigs) in &contract.functions { + for (sig, gas_info) in sigs { // Show function signature if overloaded else display function name. let display_name = - if sigs.len() == 1 { fname.to_string() } else { sig.replace(':', "") }; + if sigs.len() == 1 { fname.clone() } else { sig.replace(':', "") }; table.add_row(vec![ Cell::new(display_name), - Cell::new(gas_info.min.to_string()).fg(Color::Green), - Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), - Cell::new(gas_info.median.to_string()).fg(Color::Yellow), - Cell::new(gas_info.max.to_string()).fg(Color::Red), - Cell::new(gas_info.calls.to_string()), + Cell::new(gas_info.min.to_string()) + .fg(Color::Green) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.mean.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.median.to_string()) + .fg(Color::Yellow) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.max.to_string()) + .fg(Color::Red) + .set_alignment(CellAlignment::Right), + Cell::new(gas_info.calls.to_string()).set_alignment(CellAlignment::Right), ]); - }) - }); + } + } table } diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 4c7d135767719..d898032a78fd9 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,6 +1,7 @@ //! Forge is a fast and flexible Ethereum testing framework. -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -8,6 +9,10 @@ extern crate foundry_common; #[macro_use] extern crate tracing; +// Required for optional features (aws-kms, gcp-kms, turnkey) +#[cfg(any(feature = "aws-kms", feature = "gcp-kms", feature = "turnkey"))] +use foundry_wallets as _; + pub mod args; pub mod cmd; pub mod opts; @@ -28,3 +33,6 @@ pub mod result; // TODO: remove pub use foundry_common::traits::TestFilter; pub use foundry_evm::*; + +mod lockfile; +pub use lockfile::{DepIdentifier, DepMap, FOUNDRY_LOCK, Lockfile}; diff --git a/crates/forge/src/lockfile.rs b/crates/forge/src/lockfile.rs new file mode 100644 index 0000000000000..4f0f091076bb9 --- /dev/null +++ b/crates/forge/src/lockfile.rs @@ -0,0 +1,441 @@ +//! foundry.lock handler type. + +use alloy_primitives::map::HashMap; +use eyre::{OptionExt, Result}; +use foundry_cli::utils::Git; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, hash_map::Entry}, + path::{Path, PathBuf}, +}; + +pub const FOUNDRY_LOCK: &str = "foundry.lock"; + +/// A type alias for a HashMap of dependencies keyed by relative path to the submodule dir. +pub type DepMap = HashMap; + +/// A lockfile handler that keeps track of the dependencies and their current state. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Lockfile<'a> { + /// A map of the dependencies keyed by relative path to the submodule dir. + #[serde(flatten)] + deps: DepMap, + /// This is optional to handle no-git scencarios. + #[serde(skip)] + git: Option<&'a Git<'a>>, + /// Absolute path to the lockfile. + #[serde(skip)] + lockfile_path: PathBuf, +} + +impl<'a> Lockfile<'a> { + /// Create a new [`Lockfile`] instance. + /// + /// `project_root` is the absolute path to the project root. + /// + /// You will need to call [`Lockfile::read`] or [`Lockfile::sync`] to load the lockfile. + pub fn new(project_root: &Path) -> Self { + Self { deps: HashMap::default(), git: None, lockfile_path: project_root.join(FOUNDRY_LOCK) } + } + + /// Set the git instance to be used for submodule operations. + pub const fn with_git(mut self, git: &'a Git<'_>) -> Self { + self.git = Some(git); + self + } + + /// Sync the foundry.lock file with the current state of `git submodules`. + /// + /// If the lockfile and git submodules are out of sync, it returns a [`DepMap`] consisting of + /// _only_ the out-of-sync dependencies. + /// + /// This method writes the lockfile to project root if: + /// - The lockfile does not exist. + /// - The lockfile is out of sync with the git submodules. + pub fn sync(&mut self, lib: &Path) -> Result> { + match self.read() { + Ok(_) => {} + Err(e) if !e.to_string().contains("Lockfile not found") => { + return Err(e); + } + _ => {} + } + + if let Some(git) = &self.git { + let submodules = git.submodules()?; + + if submodules.is_empty() { + trace!("No submodules found. Skipping sync."); + return Ok(None); + } + + let modules_with_branch = git + .read_submodules_with_branch(&Git::root_of(git.root)?, lib.file_name().unwrap())?; + + let mut out_of_sync: DepMap = HashMap::default(); + for sub in &submodules { + let rel_path = sub.path(); + let rev = sub.rev(); + + let entry = self.deps.entry(rel_path.clone()); + + match entry { + Entry::Occupied(e) if e.get().rev() != rev => { + out_of_sync.insert(rel_path.clone(), e.get().clone()); + } + Entry::Vacant(e) => { + // Check if there is branch specified for the submodule at rel_path in + // .gitmodules + let maybe_branch = modules_with_branch.get(rel_path).cloned(); + + trace!(?maybe_branch, submodule = ?rel_path, "submodule branch"); + if let Some(branch) = maybe_branch { + let dep_id = DepIdentifier::Branch { + name: branch, + rev: rev.to_string(), + r#override: false, + }; + e.insert(dep_id.clone()); + out_of_sync.insert(rel_path.clone(), dep_id); + continue; + } + + let dep_id = DepIdentifier::Rev { rev: rev.to_string(), r#override: false }; + trace!(submodule=?rel_path, ?dep_id, "submodule dep_id"); + e.insert(dep_id.clone()); + out_of_sync.insert(rel_path.clone(), dep_id); + } + _ => {} + } + } + + return Ok(if out_of_sync.is_empty() { None } else { Some(out_of_sync) }); + } + + Ok(None) + } + + /// Loads the lockfile from the project root. + /// + /// Throws an error if the lockfile does not exist. + pub fn read(&mut self) -> Result<()> { + if !self.lockfile_path.exists() { + return Err(eyre::eyre!("Lockfile not found at {}", self.lockfile_path.display())); + } + + let lockfile_str = foundry_common::fs::read_to_string(&self.lockfile_path)?; + + self.deps = serde_json::from_str(&lockfile_str)?; + + trace!(lockfile = ?self.deps, "loaded lockfile"); + + Ok(()) + } + + /// Writes the lockfile to the project root. + pub fn write(&self) -> Result<()> { + let ordered_deps: BTreeMap<_, _> = self.deps.clone().into_iter().collect(); + foundry_common::fs::write_pretty_json_file(&self.lockfile_path, &ordered_deps)?; + trace!(at= ?self.lockfile_path, "wrote lockfile"); + + Ok(()) + } + + /// Insert a dependency into the lockfile. + /// If the dependency already exists, it will be updated. + /// + /// Note: This does not write the updated lockfile to disk, only inserts the dep in-memory. + pub fn insert(&mut self, path: PathBuf, dep_id: DepIdentifier) { + self.deps.insert(path, dep_id); + } + + /// Get the [`DepIdentifier`] for a submodule at a given path. + pub fn get(&self, path: &Path) -> Option<&DepIdentifier> { + self.deps.get(path) + } + + /// Removes a dependency from the lockfile. + /// + /// Note: This does not write the updated lockfile to disk, only removes the dep in-memory. + pub fn remove(&mut self, path: &Path) -> Option { + self.deps.remove(path) + } + + /// Override a dependency in the lockfile. + /// + /// Returns the overridden/previous [`DepIdentifier`]. + /// This is used in `forge update` to decide whether a dep's tag/branch/rev should be updated. + /// + /// Throws an error if the dependency is not found in the lockfile. + pub fn override_dep( + &mut self, + dep: &Path, + mut new_dep_id: DepIdentifier, + ) -> Result { + let prev = self + .deps + .get_mut(dep) + .map(|d| { + new_dep_id.mark_override(); + std::mem::replace(d, new_dep_id) + }) + .ok_or_eyre(format!("Dependency not found in lockfile: {}", dep.display()))?; + + Ok(prev) + } + + /// Returns the num of dependencies in the lockfile. + pub fn len(&self) -> usize { + self.deps.len() + } + + /// Returns whether the lockfile is empty. + pub fn is_empty(&self) -> bool { + self.deps.is_empty() + } + + /// Returns an iterator over the lockfile. + pub fn iter(&self) -> impl Iterator { + self.deps.iter() + } + + /// Returns an mutable iterator over the lockfile. + pub fn iter_mut(&mut self) -> impl Iterator { + self.deps.iter_mut() + } + + pub fn exists(&self) -> bool { + self.lockfile_path.exists() + } +} + +// Implement .iter() for &LockFile + +/// Identifies whether a dependency (submodule) is referenced by a branch, +/// tag or rev (commit hash). +/// +/// Each enum variant consists of an `r#override` flag which is used in `forge update` to decide +/// whether to update a dep or not. This flag is skipped during serialization. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum DepIdentifier { + /// `name` of the branch and the `rev` it is currently pointing to. + /// Running `forge update`, will update the `name` branch to the latest `rev`. + #[serde(rename = "branch")] + Branch { + name: String, + rev: String, + #[serde(skip)] + r#override: bool, + }, + /// Release tag `name` and the `rev` it is currently pointing to. + /// Running `forge update` does not update the tag/rev. + /// Dependency will remain pinned to the existing tag/rev unless r#override like so `forge + /// update owner/dep@tag=different_tag`. + #[serde(rename = "tag")] + Tag { + name: String, + rev: String, + #[serde(skip)] + r#override: bool, + }, + /// Commit hash `rev` the submodule is currently pointing to. + /// Running `forge update` does not update the rev. + /// Dependency will remain pinned to the existing rev unless r#override. + #[serde(rename = "rev", untagged)] + Rev { + rev: String, + #[serde(skip)] + r#override: bool, + }, +} + +impl DepIdentifier { + /// Resolves the [`DepIdentifier`] for a submodule at a given path. + /// `lib_path` is the absolute path to the submodule. + pub fn resolve_type(git: &Git<'_>, lib_path: &Path, s: &str) -> Result { + trace!(lib_path = ?lib_path, resolving_type = ?s, "resolving submodule identifier"); + // Get the tags for the submodule + if git.has_tag(s, lib_path)? { + let rev = git.get_rev(s, lib_path)?; + return Ok(Self::Tag { name: String::from(s), rev, r#override: false }); + } + + if git.has_branch(s, lib_path)? { + let rev = git.get_rev(s, lib_path)?; + return Ok(Self::Branch { name: String::from(s), rev, r#override: false }); + } + + if git.has_rev(s, lib_path)? { + return Ok(Self::Rev { rev: String::from(s), r#override: false }); + } + + Err(eyre::eyre!("Could not resolve tag type for submodule at path {}", lib_path.display())) + } + + /// Get the commit hash of the dependency. + pub fn rev(&self) -> &str { + match self { + Self::Branch { rev, .. } => rev, + Self::Tag { rev, .. } => rev, + Self::Rev { rev, .. } => rev, + } + } + + /// Get the name of the dependency. + /// + /// In case of a Rev, this will return the commit hash. + pub fn name(&self) -> &str { + match self { + Self::Branch { name, .. } => name, + Self::Tag { name, .. } => name, + Self::Rev { rev, .. } => rev, + } + } + + /// Get the name/rev to checkout at. + pub fn checkout_id(&self) -> &str { + match self { + Self::Branch { name, .. } => name, + Self::Tag { name, .. } => name, + Self::Rev { rev, .. } => rev, + } + } + + /// Marks as dependency as overridden. + pub const fn mark_override(&mut self) { + match self { + Self::Branch { r#override, .. } => *r#override = true, + Self::Tag { r#override, .. } => *r#override = true, + Self::Rev { r#override, .. } => *r#override = true, + } + } + + /// Returns whether the dependency has been overridden. + pub const fn overridden(&self) -> bool { + match self { + Self::Branch { r#override, .. } => *r#override, + Self::Tag { r#override, .. } => *r#override, + Self::Rev { r#override, .. } => *r#override, + } + } + + /// Returns whether the dependency is a branch. + pub const fn is_branch(&self) -> bool { + matches!(self, Self::Branch { .. }) + } +} + +impl std::fmt::Display for DepIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Branch { name, rev, .. } => write!(f, "branch={name}@{rev}"), + Self::Tag { name, rev, .. } => write!(f, "tag={name}@{rev}"), + Self::Rev { rev, .. } => write!(f, "rev={rev}"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn serde_dep_identifier() { + let branch = DepIdentifier::Branch { + name: "main".to_string(), + rev: "b7954c3e9ce1d487b49489f5800f52f4b77b7351".to_string(), + r#override: false, + }; + + let tag = DepIdentifier::Tag { + name: "v0.1.0".to_string(), + rev: "b7954c3e9ce1d487b49489f5800f52f4b77b7351".to_string(), + r#override: false, + }; + + let rev = DepIdentifier::Rev { + rev: "b7954c3e9ce1d487b49489f5800f52f4b77b7351".to_string(), + r#override: false, + }; + + let branch_str = serde_json::to_string(&branch).unwrap(); + let tag_str = serde_json::to_string(&tag).unwrap(); + let rev_str = serde_json::to_string(&rev).unwrap(); + + assert_eq!( + branch_str, + r#"{"branch":{"name":"main","rev":"b7954c3e9ce1d487b49489f5800f52f4b77b7351"}}"# + ); + assert_eq!( + tag_str, + r#"{"tag":{"name":"v0.1.0","rev":"b7954c3e9ce1d487b49489f5800f52f4b77b7351"}}"# + ); + assert_eq!(rev_str, r#"{"rev":"b7954c3e9ce1d487b49489f5800f52f4b77b7351"}"#); + + let branch_de: DepIdentifier = serde_json::from_str(&branch_str).unwrap(); + let tag_de: DepIdentifier = serde_json::from_str(&tag_str).unwrap(); + let rev_de: DepIdentifier = serde_json::from_str(&rev_str).unwrap(); + + assert_eq!(branch, branch_de); + assert_eq!(tag, tag_de); + assert_eq!(rev, rev_de); + } + + #[test] + fn test_write_ordered_deps() { + let dir = tempdir().unwrap(); + let mut lockfile = Lockfile::new(dir.path()); + lockfile.insert( + PathBuf::from("z_dep"), + DepIdentifier::Rev { rev: "3".to_string(), r#override: false }, + ); + lockfile.insert( + PathBuf::from("a_dep"), + DepIdentifier::Rev { rev: "1".to_string(), r#override: false }, + ); + lockfile.insert( + PathBuf::from("c_dep"), + DepIdentifier::Rev { rev: "2".to_string(), r#override: false }, + ); + let _ = lockfile.write(); + let contents = fs::read_to_string(lockfile.lockfile_path).unwrap(); + let expected = r#"{ + "a_dep": { + "rev": "1" + }, + "c_dep": { + "rev": "2" + }, + "z_dep": { + "rev": "3" + } +}"#; + assert_eq!(contents.trim(), expected.trim()); + + let mut lockfile = Lockfile::new(dir.path()); + lockfile.read().unwrap(); + lockfile.insert( + PathBuf::from("x_dep"), + DepIdentifier::Rev { rev: "4".to_string(), r#override: false }, + ); + let _ = lockfile.write(); + let contents = fs::read_to_string(lockfile.lockfile_path).unwrap(); + let expected = r#"{ + "a_dep": { + "rev": "1" + }, + "c_dep": { + "rev": "2" + }, + "x_dep": { + "rev": "4" + }, + "z_dep": { + "rev": "3" + } +}"#; + assert_eq!(contents.trim(), expected.trim()); + } +} diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 7b10328d5908e..675f0c3e6c99c 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,38 +1,42 @@ //! Forge test runner for multiple contracts. use crate::{ - progress::TestsProgress, result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, - TestFilter, + ContractRunner, TestFilter, progress::TestsProgress, result::SuiteResult, + runner::LIBRARY_DEPLOYER, }; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; -use foundry_common::{get_contract_name, shell::verbosity, ContractsByArtifact, TestFunctionExt}; +use foundry_cli::opts::configure_pcx_from_compile_output; +use foundry_common::{ + ContractsByArtifact, ContractsByArtifactBuilder, TestFunctionExt, get_contract_name, +}; use foundry_compilers::{ + Artifact, ArtifactId, Compiler, ProjectCompileOutput, artifacts::{Contract, Libraries}, - compilers::Compiler, - Artifact, ArtifactId, ProjectCompileOutput, }; use foundry_config::{Config, InlineConfig}; use foundry_evm::{ backend::Backend, + core::evm::{EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor}, decode::RevertDecoder, - executors::{Executor, ExecutorBuilder}, + executors::{EarlyExit, Executor, ExecutorBuilder}, fork::CreateFork, + fuzz::strategies::LiteralsDictionary, inspectors::CheatsConfig, opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, - Env, }; +use foundry_evm_networks::NetworkVariant; + use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; -use revm::primitives::hardfork::SpecId; use std::{ borrow::Borrow, collections::BTreeMap, - fmt::Debug, + ops::{Deref, DerefMut}, path::Path, - sync::{mpsc, Arc}, + sync::{Arc, mpsc}, time::Instant, }; @@ -46,7 +50,8 @@ pub type DeployableContracts = BTreeMap; /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. -pub struct MultiContractRunner { +#[derive(Clone, Debug)] +pub struct MultiContractRunner { /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, @@ -58,35 +63,39 @@ pub struct MultiContractRunner { pub libs_to_deploy: Vec, /// Library addresses used to link contracts. pub libraries: Libraries, + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities + pub analysis: Arc, + /// Literals dictionary for fuzzing. + pub fuzz_literals: LiteralsDictionary, /// The fork to use at launch pub fork: Option, /// The base configuration for the test runner. - pub tcfg: TestRunnerConfig, + pub tcfg: TestRunnerConfig, } -impl std::ops::Deref for MultiContractRunner { - type Target = TestRunnerConfig; +impl Deref for MultiContractRunner { + type Target = TestRunnerConfig; fn deref(&self) -> &Self::Target { &self.tcfg } } -impl std::ops::DerefMut for MultiContractRunner { +impl DerefMut for MultiContractRunner { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.tcfg } } -impl MultiContractRunner { +impl MultiContractRunner { /// Returns an iterator over all contracts that match the filter. pub fn matching_contracts<'a: 'b, 'b>( &'a self, filter: &'b dyn TestFilter, ) -> impl Iterator + 'b { - self.contracts.iter().filter(|&(id, c)| matches_contract(id, &c.abi, filter)) + self.contracts.iter().filter(|&(id, c)| matches_artifact(filter, id, &c.abi)) } /// Returns an iterator over all test functions that match the filter. @@ -96,7 +105,7 @@ impl MultiContractRunner { ) -> impl Iterator + 'b { self.matching_contracts(filter) .flat_map(|(_, c)| c.abi.functions()) - .filter(|func| is_matching_test(func, filter)) + .filter(|func| filter.matches_test_function(func)) } /// Returns an iterator over all test functions in contracts that match the filter. @@ -120,7 +129,7 @@ impl MultiContractRunner { let tests = c .abi .functions() - .filter(|func| is_matching_test(func, filter)) + .filter(|func| filter.matches_test_function(func)) .map(|func| func.name.clone()) .collect::>(); (source, name, tests) @@ -214,9 +223,9 @@ impl MultiContractRunner { tests_progress.inner.lock().clear(); - results.iter().for_each(|result| { + for result in &results { let _ = tx.send(result.to_owned()); - }); + } } else { contracts.par_iter().for_each(|&(id, contract)| { let _guard = tokio_handle.enter(); @@ -232,24 +241,29 @@ impl MultiContractRunner { &self, artifact_id: &ArtifactId, contract: &TestContract, - db: &Backend, + db: &Backend, filter: &dyn TestFilter, tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, ) -> SuiteResult { let identifier = artifact_id.identifier(); - let mut span_name = identifier.as_str(); - - if !enabled!(tracing::Level::TRACE) { - span_name = get_contract_name(&identifier); - } + let span_name = if enabled!(tracing::Level::TRACE) { + identifier.as_str() + } else { + get_contract_name(&identifier) + }; let span = debug_span!("suite", name = %span_name); let span_local = span.clone(); let _guard = span_local.enter(); debug!("start executing all tests in contract"); - let executor = self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()); + let executor = self.tcfg.executor( + self.known_contracts.clone(), + self.analysis.clone(), + artifact_id, + db.clone(), + ); let runner = ContractRunner::new( &identifier, contract, @@ -267,11 +281,30 @@ impl MultiContractRunner { } } +/// Tracks network assignment across a multi-network test run. +/// +/// When inline config specifies different networks for different tests, the runner performs one +/// pass per distinct network. This struct encodes which pass we're in so each `ContractRunner` +/// can skip tests that belong to a different pass. +/// +/// Default (empty `all_override_networks`, `None` pass) = single-pass mode, every test runs. +#[derive(Clone, Debug, Default)] +pub struct MultiNetworkConfig { + /// All networks explicitly referenced in inline config annotations across the whole suite. + /// Empty means single-pass mode (no per-test network overrides present). + pub all_override_networks: Vec, + /// The network this pass is responsible for. + /// `None` = default pass: runs tests *without* an explicit network annotation (or annotated + /// with a network not in `all_override_networks`). + /// `Some(v)` = override pass: runs only tests annotated with exactly `v`. + pub pass_network: Option, +} + /// Configuration for the test runner. /// /// This is modified after instantiation through inline config. -#[derive(Clone)] -pub struct TestRunnerConfig { +#[derive(Clone, Debug)] +pub struct TestRunnerConfig { /// Project config. pub config: Arc, /// Inline configuration. @@ -280,25 +313,30 @@ pub struct TestRunnerConfig { /// EVM configuration. pub evm_opts: EvmOpts, /// EVM environment. - pub env: Env, + pub evm_env: EvmEnvFor, + /// Transaction environment. + pub tx_env: TxEnvFor, /// EVM version. - pub spec_id: SpecId, + pub spec_id: SpecFor, /// The address which will be used to deploy the initial contracts and send all transactions. pub sender: Address, - /// Whether to collect coverage info - pub coverage: bool, + /// Whether to collect line coverage info + pub line_coverage: bool, /// Whether to collect debug info pub debug: bool, /// Whether to enable steps tracking in the tracer. pub decode_internal: InternalTraceMode, /// Whether to enable call isolation. pub isolation: bool, - /// Whether to enable Odyssey features. - pub odyssey: bool, + /// Whether to exit early on test failure or if test run interrupted. + pub early_exit: EarlyExit, + + /// Multi-network pass configuration. Default = single-pass mode. + pub multi_network: MultiNetworkConfig, } -impl TestRunnerConfig { +impl TestRunnerConfig { /// Reconfigures all fields using the given `config`. /// This is for example used to override the configuration with inline config. pub fn reconfigure_with(&mut self, config: Arc) { @@ -306,21 +344,24 @@ impl TestRunnerConfig { self.spec_id = config.evm_spec_id(); self.sender = config.sender; - self.odyssey = config.odyssey; + self.evm_opts.networks = config.networks; self.isolation = config.isolate; // Specific to Forge, not present in config. - // TODO: self.evm_opts - // TODO: self.env - // self.coverage = N/A; + // self.line_coverage = N/A; // self.debug = N/A; // self.decode_internal = N/A; + // TODO: self.evm_opts + self.evm_opts.always_use_create_2_factory = config.always_use_create_2_factory; + + // TODO: self.env + self.config = config; } /// Configures the given executor with this configuration. - pub fn configure_executor(&self, executor: &mut Executor) { + pub fn configure_executor(&self, executor: &mut Executor) { // TODO: See above let inspector = executor.inspector_mut(); @@ -330,9 +371,9 @@ impl TestRunnerConfig { Arc::new(cheatcodes.config.clone_with(&self.config, self.evm_opts.clone())); } inspector.tracing(self.trace_mode()); - inspector.collect_coverage(self.coverage); + inspector.collect_line_coverage(self.line_coverage); inspector.enable_isolation(self.isolation); - inspector.odyssey(self.odyssey); + inspector.networks(self.evm_opts.networks); // inspector.set_create2_deployer(self.evm_opts.create2_deployer); // executor.env_mut().clone_from(&self.env); @@ -345,29 +386,33 @@ impl TestRunnerConfig { pub fn executor( &self, known_contracts: ContractsByArtifact, + analysis: Arc, artifact_id: &ArtifactId, - db: Backend, - ) -> Executor { + db: Backend, + ) -> Executor { let cheats_config = Arc::new(CheatsConfig::new( &self.config, self.evm_opts.clone(), Some(known_contracts), Some(artifact_id.clone()), + None, )); - ExecutorBuilder::new() + ExecutorBuilder::default() .inspectors(|stack| { stack + .logs(self.config.live_logs) .cheatcodes(cheats_config) .trace_mode(self.trace_mode()) - .coverage(self.coverage) + .line_coverage(self.line_coverage) .enable_isolation(self.isolation) - .odyssey(self.odyssey) + .networks(self.evm_opts.networks) .create2_deployer(self.evm_opts.create2_deployer) + .set_analysis(analysis) }) .spec_id(self.spec_id) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db) + .build(self.evm_env.clone(), self.tx_env.clone(), db) } fn trace_mode(&self) -> TraceMode { @@ -375,12 +420,11 @@ impl TestRunnerConfig { .with_debug(self.debug) .with_decode_internal(self.decode_internal) .with_verbosity(self.evm_opts.verbosity) - .with_state_changes(verbosity() > 4) } } /// Builder used for instantiating the multi-contract runner -#[derive(Clone, Debug)] +#[derive(Clone)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all @@ -388,22 +432,22 @@ pub struct MultiContractRunnerBuilder { pub sender: Option

, /// The initial balance for each one of the deployed smart contracts pub initial_balance: U256, - /// The EVM spec to use - pub evm_spec: Option, /// The fork to use at launch pub fork: Option, /// Project config. pub config: Arc, - /// Whether or not to collect coverage info - pub coverage: bool, + /// Whether or not to collect line coverage info + pub line_coverage: bool, /// Whether or not to collect debug info pub debug: bool, /// Whether to enable steps tracking in the tracer. pub decode_internal: InternalTraceMode, /// Whether to enable call isolation pub isolation: bool, - /// Whether to enable Odyssey features. - pub odyssey: bool, + /// Whether to exit early on test failure. + pub fail_fast: bool, + /// Multi-network pass configuration. + pub multi_network: MultiNetworkConfig, } impl MultiContractRunnerBuilder { @@ -412,70 +456,71 @@ impl MultiContractRunnerBuilder { config, sender: Default::default(), initial_balance: Default::default(), - evm_spec: Default::default(), fork: Default::default(), - coverage: Default::default(), + line_coverage: Default::default(), debug: Default::default(), isolation: Default::default(), decode_internal: Default::default(), - odyssey: Default::default(), + fail_fast: false, + multi_network: Default::default(), } } - pub fn sender(mut self, sender: Address) -> Self { + pub const fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self } - pub fn initial_balance(mut self, initial_balance: U256) -> Self { + pub const fn initial_balance(mut self, initial_balance: U256) -> Self { self.initial_balance = initial_balance; self } - pub fn evm_spec(mut self, spec: SpecId) -> Self { - self.evm_spec = Some(spec); - self - } - pub fn with_fork(mut self, fork: Option) -> Self { self.fork = fork; self } - pub fn set_coverage(mut self, enable: bool) -> Self { - self.coverage = enable; + pub const fn set_coverage(mut self, enable: bool) -> Self { + self.line_coverage = enable; self } - pub fn set_debug(mut self, enable: bool) -> Self { + pub const fn set_debug(mut self, enable: bool) -> Self { self.debug = enable; self } - pub fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { + pub const fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { self.decode_internal = mode; self } - pub fn enable_isolation(mut self, enable: bool) -> Self { - self.isolation = enable; + pub fn with_multi_network(mut self, multi_network: MultiNetworkConfig) -> Self { + self.multi_network = multi_network; self } - pub fn odyssey(mut self, enable: bool) -> Self { - self.odyssey = enable; + pub const fn fail_fast(mut self, fail_fast: bool) -> Self { + self.fail_fast = fail_fast; + self + } + + pub const fn enable_isolation(mut self, enable: bool) -> Self { + self.isolation = enable; self } /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm - pub fn build>( + pub fn build>( self, - root: &Path, output: &ProjectCompileOutput, - env: Env, + evm_env: EvmEnvFor, + tx_env: TxEnvFor, evm_opts: EvmOpts, - ) -> Result { + ) -> Result> { + let root = &self.config.root; let contracts = output .artifact_ids() .map(|(id, v)| (id.with_stripped_file_prefixes(root), v)) @@ -485,8 +530,8 @@ impl MultiContractRunnerBuilder { // Build revert decoder from ABIs of all artifacts. let abis = linker .contracts - .iter() - .filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow())); + .values() + .filter_map(|contract| contract.abi.as_ref().map(|abi| abi.borrow())); let revert_decoder = RevertDecoder::new().with_abis(abis); let LinkOutput { libraries, libs_to_deploy } = linker.link_with_nonce_or_address( @@ -496,18 +541,20 @@ impl MultiContractRunnerBuilder { linker.contracts.keys(), )?; - let linked_contracts = linker.get_linked_artifacts(&libraries)?; + let linked_contracts = linker.get_linked_artifacts_cow(&libraries)?; // Create a mapping of name => (abi, deployment code, Vec) let mut deployable_contracts = DeployableContracts::default(); for (id, contract) in linked_contracts.iter() { - let Some(abi) = &contract.abi else { continue }; + let Some(abi) = contract.abi.as_ref() else { continue }; // if it's a test, link it and add to deployable contracts - if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && - abi.functions().any(|func| func.name.is_any_test()) + if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) + && abi.functions().any(|func| func.name.is_any_test()) { + linker.ensure_linked(contract, id)?; + let Some(bytecode) = contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty()) else { @@ -515,11 +562,47 @@ impl MultiContractRunnerBuilder { }; deployable_contracts - .insert(id.clone(), TestContract { abi: abi.clone(), bytecode }); + .insert(id.clone(), TestContract { abi: abi.clone().into_owned(), bytecode }); } } - let known_contracts = ContractsByArtifact::new(linked_contracts); + // Create known contracts from linked contracts and storage layout information (if any). + let known_contracts = + ContractsByArtifactBuilder::new(linked_contracts).with_output(output, root).build(); + + // Initialize and configure the solar compiler. + let mut analysis = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + let dcx = analysis.dcx_mut(); + dcx.set_emitter(Box::new( + solar::interface::diagnostics::HumanEmitter::stderr(Default::default()) + .source_map(Some(dcx.source_map().unwrap())), + )); + dcx.set_flags_mut(|f| f.track_diagnostics = false); + + // Populate solar's global context by parsing and lowering the sources. + let files: Vec<_> = output.output().sources.as_ref().keys().cloned().collect(); + + analysis.enter_mut(|compiler| -> Result<()> { + let mut pcx = compiler.parse(); + configure_pcx_from_compile_output( + &mut pcx, + &self.config, + output, + if files.is_empty() { None } else { Some(&files) }, + )?; + pcx.parse(); + let _ = compiler.lower_asts(); + Ok(()) + })?; + + let analysis = Arc::new(analysis); + let fuzz_literals = LiteralsDictionary::new( + Some(analysis.clone()), + Some(self.config.project_paths()), + self.config.fuzz.dictionary.max_fuzz_dictionary_literals, + ); Ok(MultiContractRunner { contracts: deployable_contracts, @@ -527,34 +610,40 @@ impl MultiContractRunnerBuilder { known_contracts, libs_to_deploy, libraries, - - fork: self.fork, + analysis, + fuzz_literals, tcfg: TestRunnerConfig { evm_opts, - env, - spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), + evm_env, + tx_env, + spec_id: self.config.evm_spec_id(), sender: self.sender.unwrap_or(self.config.sender), - - coverage: self.coverage, + line_coverage: self.line_coverage, debug: self.debug, decode_internal: self.decode_internal, inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, - odyssey: self.odyssey, - + early_exit: EarlyExit::new(self.fail_fast), + multi_network: self.multi_network, config: self.config, }, + + fork: self.fork, }) } } -pub fn matches_contract(id: &ArtifactId, abi: &JsonAbi, filter: &dyn TestFilter) -> bool { - (filter.matches_path(&id.source) && filter.matches_contract(&id.name)) && - abi.functions().any(|func| is_matching_test(func, filter)) +pub fn matches_artifact(filter: &dyn TestFilter, id: &ArtifactId, abi: &JsonAbi) -> bool { + matches_contract(filter, &id.source, &id.name, abi.functions()) } -/// Returns `true` if the function is a test function that matches the given filter. -pub(crate) fn is_matching_test(func: &Function, filter: &dyn TestFilter) -> bool { - func.is_any_test() && filter.matches_test(&func.signature()) +pub(crate) fn matches_contract( + filter: &dyn TestFilter, + path: &Path, + contract_name: &str, + functions: impl IntoIterator>, +) -> bool { + (filter.matches_path(path) && filter.matches_contract(contract_name)) + && functions.into_iter().any(|func| filter.matches_test_function(func.borrow())) } diff --git a/crates/forge/src/opts.rs b/crates/forge/src/opts.rs index a50de6c7be59e..f025cba73eb8d 100644 --- a/crates/forge/src/opts.rs +++ b/crates/forge/src/opts.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; name = "forge", version = SHORT_VERSION, long_version = LONG_VERSION, - after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html", + after_help = "Find more information in the book: https://getfoundry.sh/forge/overview", next_display_order = None, )] pub struct Forge { @@ -62,7 +62,7 @@ pub enum ForgeSubcommand { /// Install one or multiple dependencies. /// /// If no arguments are provided, then existing dependencies will be installed. - #[command(visible_alias = "i")] + #[command(visible_aliases = ["i", "add"])] Install(InstallArgs), /// Remove one or multiple dependencies. @@ -96,13 +96,9 @@ pub enum ForgeSubcommand { #[command(visible_alias = "com")] Completions { #[arg(value_enum)] - shell: clap_complete::Shell, + shell: foundry_cli::clap::Shell, }, - /// Generate Fig autocompletion spec. - #[command(visible_alias = "fig")] - GenerateFigSpec, - /// Remove the build artifacts and cache directories. #[command(visible_alias = "cl")] Clean { @@ -144,7 +140,9 @@ pub enum ForgeSubcommand { #[command(visible_alias = "tr")] Tree(tree::TreeArgs), - /// Detects usage of unsafe cheat codes in a project and its dependencies. + /// DEPRECATED: Detects usage of unsafe cheat codes in a project and its dependencies. + /// + /// This is an alias for `forge lint --only-lint unsafe-cheatcode`. Geiger(geiger::GeigerArgs), /// Generate documentation for the project. @@ -158,6 +156,7 @@ pub enum ForgeSubcommand { }, /// Generate scaffold files. + #[command(hide = true)] Generate(generate::GenerateArgs), /// Compiler utilities. diff --git a/crates/forge/src/progress.rs b/crates/forge/src/progress.rs index 9ca182f769d50..e8a5d7e049682 100644 --- a/crates/forge/src/progress.rs +++ b/crates/forge/src/progress.rs @@ -1,7 +1,9 @@ use alloy_primitives::map::HashMap; +use chrono::Utc; use indicatif::{MultiProgress, ProgressBar}; use parking_lot::Mutex; use std::{sync::Arc, time::Duration}; + /// State of [ProgressBar]s displayed for the given test run. /// Shows progress of all test suites matching filter. /// For each test within the test suite an individual progress bar is displayed. @@ -63,20 +65,25 @@ impl TestsProgressState { pub fn start_fuzz_progress( &mut self, suite_name: &str, - test_name: &String, + test_name: &str, + timeout: Option, runs: u32, ) -> Option { if let Some(suite_progress) = self.suites_progress.get(suite_name) { let fuzz_progress = self.multi.insert_after(suite_progress, ProgressBar::new(runs as u64)); + let template = if let Some(timeout) = timeout { + let ends_at = (Utc::now() + chrono::Duration::seconds(timeout.into())) + .format("%H:%M:%S %Y-%m-%d") + .to_string(); + format!(" ↪ {{prefix:.bold.dim}}: [{{pos}}] Runs, ends at {ends_at} UTC {{msg}}") + } else { + " ↪ {prefix:.bold.dim}: [{pos}/{len}] Runs {msg}".to_string() + }; fuzz_progress.set_style( - indicatif::ProgressStyle::with_template( - " ↪ {prefix:.bold.dim}: [{pos}/{len}]{msg} Runs", - ) - .unwrap() - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), + indicatif::ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), ); - fuzz_progress.set_prefix(test_name.to_string()); + fuzz_progress.set_prefix(test_name.to_owned()); Some(fuzz_progress) } else { None @@ -89,7 +96,7 @@ impl TestsProgressState { } } -/// Clonable wrapper around [TestsProgressState]. +/// Cloneable wrapper around [TestsProgressState]. #[derive(Debug, Clone)] pub struct TestsProgress { pub inner: Arc>, @@ -105,11 +112,12 @@ impl TestsProgress { pub fn start_fuzz_progress( tests_progress: Option<&TestsProgress>, suite_name: &str, - test_name: &String, + test_name: &str, + timeout: Option, runs: u32, ) -> Option { if let Some(progress) = tests_progress { - progress.inner.lock().start_fuzz_progress(suite_name, test_name, runs) + progress.inner.lock().start_fuzz_progress(suite_name, test_name, timeout, runs) } else { None } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 90447fc26bfa0..66be289ef252d 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -5,15 +5,16 @@ use crate::{ gas_report::GasReport, }; use alloy_primitives::{ + Address, I256, Log, U256, map::{AddressHashMap, HashMap}, - Address, Log, }; use eyre::Report; -use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; +use foundry_common::{ContractsByArtifact, get_contract_name, get_file_name, shell}; use foundry_evm::{ + core::{Breakpoints, evm::FoundryEvmNetwork}, coverage::HitMaps, decode::SkipReason, - executors::{invariant::InvariantMetrics, RawCallResult}, + executors::{RawCallResult, invariant::InvariantMetrics}, fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult}, traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; @@ -42,17 +43,33 @@ pub struct TestOutcome { pub last_run_decoder: Option, /// The gas report, if requested. pub gas_report: Option, + /// Known contracts from the test run (used for coverage). + pub known_contracts: Option, + /// The fuzz seed used for the test run. + pub fuzz_seed: Option, } impl TestOutcome { /// Creates a new test outcome with the given results. - pub fn new(results: BTreeMap, allow_failure: bool) -> Self { - Self { results, allow_failure, last_run_decoder: None, gas_report: None } + pub const fn new( + known_contracts: Option, + results: BTreeMap, + allow_failure: bool, + fuzz_seed: Option, + ) -> Self { + Self { + results, + allow_failure, + last_run_decoder: None, + gas_report: None, + known_contracts, + fuzz_seed, + } } /// Creates a new empty test outcome. - pub fn empty(allow_failure: bool) -> Self { - Self::new(BTreeMap::new(), allow_failure) + pub const fn empty(known_contracts: Option, allow_failure: bool) -> Self { + Self::new(known_contracts, BTreeMap::new(), allow_failure, None) } /// Returns an iterator over all individual succeeding tests and their names. @@ -122,6 +139,11 @@ impl TestOutcome { self.failures().count() } + /// Returns `true` if any fuzz or invariant test failed. + pub fn has_fuzz_failures(&self) -> bool { + self.failures().any(|(_, t)| t.kind.is_fuzz() || t.kind.is_invariant()) + } + /// Sums up all the durations of all individual test suites. /// /// Note that this is not necessarily the wall clock time of the entire test run. @@ -159,7 +181,6 @@ impl TestOutcome { } if shell::is_quiet() || silent { - // TODO: Avoid process::exit std::process::exit(1); } @@ -184,7 +205,26 @@ impl TestOutcome { successes.to_string().green() )?; - // TODO: Avoid process::exit + // Show helpful hint for rerunning failed tests + let test_word = if failures == 1 { "test" } else { "tests" }; + sh_println!( + "\nTip: Run {} to retry only the {} failed {}", + "`forge test --rerun`".cyan(), + failures, + test_word + )?; + + // Print seed for fuzz/invariant test failures to enable reproduction. + if let Some(seed) = self.fuzz_seed + && outcome.has_fuzz_failures() + { + sh_println!( + "\nFuzz seed: {} (use {} to reproduce)", + format!("{seed:#x}").cyan(), + "`--fuzz-seed`".cyan() + )?; + } + std::process::exit(1); } @@ -351,19 +391,19 @@ pub enum TestStatus { impl TestStatus { /// Returns `true` if the test was successful. #[inline] - pub fn is_success(self) -> bool { + pub const fn is_success(self) -> bool { matches!(self, Self::Success) } /// Returns `true` if the test failed. #[inline] - pub fn is_failure(self) -> bool { + pub const fn is_failure(self) -> bool { matches!(self, Self::Failure) } /// Returns `true` if the test was skipped. #[inline] - pub fn is_skipped(self) -> bool { + pub const fn is_skipped(self) -> bool { matches!(self, Self::Skipped) } } @@ -399,15 +439,18 @@ pub struct TestResult { pub traces: Traces, /// Additional traces to use for gas report. + /// + /// These are cleared after the gas report is analyzed. #[serde(skip)] pub gas_report_traces: Vec>, - /// Raw coverage info + /// Raw line coverage info #[serde(skip)] - pub coverage: Option, + pub line_coverage: Option, /// Labeled addresses - pub labeled_addresses: AddressHashMap, + #[serde(rename = "labeled_addresses")] // Backwards compatibility. + pub labels: AddressHashMap, #[serde(with = "foundry_common::serde_helpers::duration")] pub duration: Duration, @@ -426,7 +469,25 @@ pub struct TestResult { impl fmt::Display for TestResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.status { - TestStatus::Success => "[PASS]".green().fmt(f), + TestStatus::Success => { + // For optimization mode, show the best example sequence in green. + if let Some(CounterExample::Sequence(original, sequence)) = &self.counterexample { + let mut s = String::from("[PASS]"); + s.push_str( + format!( + "\n\t[Best sequence] (original: {original}, shrunk: {})\n", + sequence.len() + ) + .as_str(), + ); + for ex in sequence { + writeln!(s, "{ex}").unwrap(); + } + s.green().wrap().fmt(f) + } else { + "[PASS]".green().fmt(f) + } + } TestStatus::Skipped => { let mut s = String::from("[SKIP"); if let Some(reason) = &self.reason { @@ -466,20 +527,29 @@ impl fmt::Display for TestResult { } else { s.push(']'); } - s.red().fmt(f) + s.red().wrap().fmt(f) } } } } +macro_rules! extend { + ($a:expr, $b:expr, $trace_kind:expr) => { + $a.logs.extend($b.logs); + $a.labels.extend($b.labels); + $a.traces.extend($b.traces.map(|traces| ($trace_kind, traces))); + $a.merge_coverages($b.line_coverage); + }; +} + impl TestResult { /// Creates a new test result starting from test setup results. pub fn new(setup: &TestSetup) -> Self { Self { - labeled_addresses: setup.labels.clone(), + labels: setup.labels.clone(), logs: setup.logs.clone(), traces: setup.traces.clone(), - coverage: setup.coverage.clone(), + line_coverage: setup.coverage.clone(), ..Default::default() } } @@ -491,13 +561,25 @@ impl TestResult { /// Creates a test setup result. pub fn setup_result(setup: TestSetup) -> Self { + let TestSetup { + address: _, + fuzz_fixtures: _, + logs, + labels, + traces, + coverage, + deployed_libs: _, + reason, + skipped, + deployment_failure: _, + } = setup; Self { - status: if setup.skipped { TestStatus::Skipped } else { TestStatus::Failure }, - reason: setup.reason, - logs: setup.logs, - traces: setup.traces, - coverage: setup.coverage, - labeled_addresses: setup.labels, + status: if skipped { TestStatus::Skipped } else { TestStatus::Failure }, + reason, + logs, + traces, + line_coverage: coverage, + labels, ..Default::default() } } @@ -516,20 +598,17 @@ impl TestResult { /// Returns the result for single test. Merges execution results (logs, labeled addresses, /// traces and coverages) in initial setup results. - pub fn single_result( + pub fn single_result( &mut self, success: bool, reason: Option, - raw_call_result: RawCallResult, + raw_call_result: RawCallResult, ) { - self.kind = - TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) }; + self.kind = TestKind::Unit { + gas: raw_call_result.gas_used.saturating_sub(raw_call_result.stipend), + }; - // Record logs, labels, traces and merge coverages. - self.logs.extend(raw_call_result.logs); - self.labeled_addresses.extend(raw_call_result.labels); - self.traces.extend(raw_call_result.traces.map(|traces| (TraceKind::Execution, traces))); - self.merge_coverages(raw_call_result.coverage); + extend!(self, raw_call_result, TraceKind::Execution); self.status = match success { true => TestStatus::Success, @@ -554,13 +633,11 @@ impl TestResult { mean_gas: result.mean_gas(false), first_case: result.first_case, runs: result.gas_by_case.len(), + failed_corpus_replays: result.failed_corpus_replays, }; // Record logs, labels, traces and merge coverages. - self.logs.extend(result.logs); - self.labeled_addresses.extend(result.labeled_addresses); - self.traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); - self.merge_coverages(result.coverage); + extend!(self, result, TraceKind::Execution); self.status = if result.skipped { TestStatus::Skipped @@ -577,10 +654,30 @@ impl TestResult { self.deprecated_cheatcodes = result.deprecated_cheatcodes; } + /// Returns the fail result for fuzz test setup. + pub fn fuzz_setup_fail(&mut self, e: Report) { + self.kind = TestKind::Fuzz { + first_case: Default::default(), + runs: 0, + mean_gas: 0, + median_gas: 0, + failed_corpus_replays: 0, + }; + self.status = TestStatus::Failure; + debug!(?e, "failed to set up fuzz testing environment"); + self.reason = Some(format!("failed to set up fuzz testing environment: {e}")); + } + /// Returns the skipped result for invariant test. pub fn invariant_skip(&mut self, reason: SkipReason) { - self.kind = - TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; + self.kind = TestKind::Invariant { + runs: 1, + calls: 1, + reverts: 1, + metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, + }; self.status = TestStatus::Skipped; self.reason = reason.0; } @@ -590,23 +687,38 @@ impl TestResult { &mut self, replayed_entirely: bool, invariant_name: &String, + replay_reason: Option, call_sequence: Vec, ) { - self.kind = - TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; - self.status = TestStatus::Failure; - self.reason = if replayed_entirely { - Some(format!("{invariant_name} replay failure")) - } else { - Some(format!("{invariant_name} persisted failure revert")) + self.kind = TestKind::Invariant { + runs: 1, + calls: 1, + reverts: 1, + metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, }; + self.status = TestStatus::Failure; + self.reason = replay_reason.or_else(|| { + if replayed_entirely { + Some(format!("{invariant_name} replay failure")) + } else { + Some(format!("{invariant_name} persisted failure revert")) + } + }); self.counterexample = Some(CounterExample::Sequence(call_sequence.len(), call_sequence)); } /// Returns the fail result for invariant test setup. pub fn invariant_setup_fail(&mut self, e: Report) { - self.kind = - TestKind::Invariant { runs: 0, calls: 0, reverts: 0, metrics: HashMap::default() }; + self.kind = TestKind::Invariant { + runs: 0, + calls: 0, + reverts: 0, + metrics: HashMap::default(), + failed_corpus_replays: 0, + optimization_best_value: None, + }; self.status = TestStatus::Failure; self.reason = Some(format!("failed to set up invariant testing environment: {e}")); } @@ -622,24 +734,57 @@ impl TestResult { cases: Vec, reverts: usize, metrics: Map, + failed_corpus_replays: usize, + optimization_best_value: Option, ) { self.kind = TestKind::Invariant { runs: cases.len(), calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), reverts, metrics, + failed_corpus_replays, + optimization_best_value, }; - self.status = match success { - true => TestStatus::Success, - false => TestStatus::Failure, + // For optimization mode (Some value), always succeed. For check mode (None), use success. + self.status = if optimization_best_value.is_some() || success { + TestStatus::Success + } else { + TestStatus::Failure }; self.reason = reason; self.counterexample = counterexample; self.gas_report_traces = gas_report_traces; } + /// Returns the result for a table test. Merges table test execution results (logs, labeled + /// addresses, traces and coverages) in initial setup results. + pub fn table_result(&mut self, result: FuzzTestResult) { + self.kind = TestKind::Table { + median_gas: result.median_gas(false), + mean_gas: result.mean_gas(false), + runs: result.gas_by_case.len(), + }; + + // Record logs, labels, traces and merge coverages. + extend!(self, result, TraceKind::Execution); + + self.status = if result.skipped { + TestStatus::Skipped + } else if result.success { + TestStatus::Success + } else { + TestStatus::Failure + }; + self.reason = result.reason; + self.counterexample = result.counterexample; + self.duration = Duration::default(); + self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); + self.breakpoints = result.breakpoints.unwrap_or_default(); + self.deprecated_cheatcodes = result.deprecated_cheatcodes; + } + /// Returns `true` if this is the result of a fuzz test - pub fn is_fuzz(&self) -> bool { + pub const fn is_fuzz(&self) -> bool { matches!(self.kind, TestKind::Fuzz { .. }) } @@ -649,25 +794,42 @@ impl TestResult { } /// Merges the given raw call result into `self`. - pub fn extend(&mut self, call_result: RawCallResult) { - self.logs.extend(call_result.logs); - self.labeled_addresses.extend(call_result.labels); - self.traces.extend(call_result.traces.map(|traces| (TraceKind::Execution, traces))); - self.merge_coverages(call_result.coverage); + pub fn extend(&mut self, call_result: RawCallResult) { + extend!(self, call_result, TraceKind::Execution); } /// Merges the given coverage result into `self`. pub fn merge_coverages(&mut self, other_coverage: Option) { - HitMaps::merge_opt(&mut self.coverage, other_coverage); + HitMaps::merge_opt(&mut self.line_coverage, other_coverage); } } /// Data report by a test. #[derive(Clone, Debug, PartialEq, Eq)] pub enum TestKindReport { - Unit { gas: u64 }, - Fuzz { runs: usize, mean_gas: u64, median_gas: u64 }, - Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, + Unit { + gas: u64, + }, + Fuzz { + runs: usize, + mean_gas: u64, + median_gas: u64, + failed_corpus_replays: usize, + }, + Invariant { + runs: usize, + calls: usize, + reverts: usize, + metrics: Map, + failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best value achieved. None = check mode. + optimization_best_value: Option, + }, + Table { + runs: usize, + mean_gas: u64, + median_gas: u64, + }, } impl fmt::Display for TestKindReport { @@ -676,11 +838,38 @@ impl fmt::Display for TestKindReport { Self::Unit { gas } => { write!(f, "(gas: {gas})") } - Self::Fuzz { runs, mean_gas, median_gas } => { - write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") + Self::Fuzz { runs, mean_gas, median_gas, failed_corpus_replays } => { + if *failed_corpus_replays != 0 { + write!( + f, + "(runs: {runs}, μ: {mean_gas}, ~: {median_gas}, failed corpus replays: {failed_corpus_replays})" + ) + } else { + write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") + } } - Self::Invariant { runs, calls, reverts, metrics: _ } => { - write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})") + Self::Invariant { + runs, + calls, + reverts, + metrics: _, + failed_corpus_replays, + optimization_best_value, + } => { + // If optimization_best_value is Some, this is optimization mode. + if let Some(best_value) = optimization_best_value { + write!(f, "(best: {best_value}, runs: {runs}, calls: {calls})") + } else if *failed_corpus_replays != 0 { + write!( + f, + "(runs: {runs}, calls: {calls}, reverts: {reverts}, failed corpus replays: {failed_corpus_replays})" + ) + } else { + write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})") + } + } + Self::Table { runs, mean_gas, median_gas } => { + write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") } } } @@ -688,11 +877,11 @@ impl fmt::Display for TestKindReport { impl TestKindReport { /// Returns the main gas value to compare against - pub fn gas(&self) -> u64 { + pub const fn gas(&self) -> u64 { match *self { Self::Unit { gas } => gas, // We use the median for comparisons - Self::Fuzz { median_gas, .. } => median_gas, + Self::Fuzz { median_gas, .. } | Self::Table { median_gas, .. } => median_gas, // We return 0 since it's not applicable Self::Invariant { .. } => 0, } @@ -711,9 +900,20 @@ pub enum TestKind { runs: usize, mean_gas: u64, median_gas: u64, + failed_corpus_replays: usize, }, /// An invariant test. - Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, + Invariant { + runs: usize, + calls: usize, + reverts: usize, + metrics: Map, + failed_corpus_replays: usize, + /// For optimization mode (int256 return): the best value achieved. None = check mode. + optimization_best_value: Option, + }, + /// A table test. + Table { runs: usize, mean_gas: u64, median_gas: u64 }, } impl Default for TestKind { @@ -723,19 +923,46 @@ impl Default for TestKind { } impl TestKind { + /// Returns `true` if this is a fuzz test. + pub const fn is_fuzz(&self) -> bool { + matches!(self, Self::Fuzz { .. }) + } + + /// Returns `true` if this is an invariant test. + pub const fn is_invariant(&self) -> bool { + matches!(self, Self::Invariant { .. }) + } + /// The gas consumed by this test pub fn report(&self) -> TestKindReport { match self { Self::Unit { gas } => TestKindReport::Unit { gas: *gas }, - Self::Fuzz { first_case: _, runs, mean_gas, median_gas } => { - TestKindReport::Fuzz { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } + Self::Fuzz { first_case: _, runs, mean_gas, median_gas, failed_corpus_replays } => { + TestKindReport::Fuzz { + runs: *runs, + mean_gas: *mean_gas, + median_gas: *median_gas, + failed_corpus_replays: *failed_corpus_replays, + } } - Self::Invariant { runs, calls, reverts, metrics: _ } => TestKindReport::Invariant { + Self::Invariant { + runs, + calls, + reverts, + metrics: _, + failed_corpus_replays, + optimization_best_value, + } => TestKindReport::Invariant { runs: *runs, calls: *calls, reverts: *reverts, metrics: HashMap::default(), + failed_corpus_replays: *failed_corpus_replays, + optimization_best_value: *optimization_best_value, }, + Self::Table { runs, mean_gas, median_gas } => { + TestKindReport::Table { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } + } } } } @@ -779,10 +1006,15 @@ impl TestSetup { Self { reason: Some(reason), skipped: true, ..Default::default() } } - pub fn extend(&mut self, raw: RawCallResult, trace_kind: TraceKind) { - self.logs.extend(raw.logs); - self.labels.extend(raw.labels); - self.traces.extend(raw.traces.map(|traces| (trace_kind, traces))); - HitMaps::merge_opt(&mut self.coverage, raw.coverage); + pub fn extend( + &mut self, + raw: RawCallResult, + trace_kind: TraceKind, + ) { + extend!(self, raw, trace_kind); + } + + pub fn merge_coverages(&mut self, other_coverage: Option) { + HitMaps::merge_opt(&mut self.coverage, other_coverage); } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 3e2f65fde7f8e..7feaf35254636 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -1,49 +1,54 @@ //! The Forge test runner. use crate::{ - fuzz::{invariant::BasicTxDetails, BaseCounterExample}, - multi_runner::{is_matching_test, TestContract, TestRunnerConfig}, - progress::{start_fuzz_progress, TestsProgress}, - result::{SuiteResult, TestResult, TestSetup}, MultiContractRunner, TestFilter, + coverage::HitMaps, + fuzz::{BaseCounterExample, FuzzTestResult}, + multi_runner::{TestContract, TestRunnerConfig}, + progress::{TestsProgress, start_fuzz_progress}, + result::{SuiteResult, TestResult, TestSetup}, }; -use alloy_dyn_abi::DynSolValue; +use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_json_abi::Function; -use alloy_primitives::{address, map::HashMap, Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, U256, address, map::HashMap}; use eyre::Result; -use foundry_common::{contracts::ContractsByAddress, TestFunctionExt, TestFunctionKind}; +use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress}; use foundry_compilers::utils::canonicalized; -use foundry_config::{Config, InvariantConfig}; +use foundry_config::{Config, FuzzCorpusConfig}; use foundry_evm::{ constants::CALLER, + core::evm::FoundryEvmNetwork, decode::RevertDecoder, executors::{ + CallResult, EvmError, Executor, ITest, RawCallResult, fuzz::FuzzedExecutor, invariant::{ - check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError, + CheckSequenceOptions, InvariantExecutor, InvariantFuzzError, check_sequence, + replay_error, replay_run, }, - CallResult, EvmError, Executor, ITest, RawCallResult, }, fuzz::{ - fixture_name, - invariant::{CallDetails, InvariantContract}, - CounterExample, FuzzFixtures, + BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name, + invariant::{InvariantContract, InvariantSettings}, + strategies::EvmFuzzState, }, - traces::{load_contracts, TraceKind, TraceMode}, -}; -use proptest::test_runner::{ - FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner, + revm::primitives::hardfork::SpecId, + traces::{TraceKind, TraceMode, load_contracts}, }; +use itertools::Itertools; +use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, cmp::min, collections::BTreeMap, + ops::Deref, path::{Path, PathBuf}, sync::Arc, time::Instant, }; +use tokio::signal; use tracing::Span; /// When running tests, we deploy all external libraries present in the project. To avoid additional @@ -54,13 +59,13 @@ use tracing::Span; pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353"); /// A type that executes all tests of a contract -pub struct ContractRunner<'a> { +pub struct ContractRunner<'a, FEN: FoundryEvmNetwork> { /// The name of the contract. name: &'a str, /// The data of the contract. contract: &'a TestContract, /// The EVM executor. - executor: Executor, + executor: Executor, /// Overall test run progress. progress: Option<&'a TestsProgress>, /// The handle to the tokio runtime. @@ -68,13 +73,13 @@ pub struct ContractRunner<'a> { /// The span of the contract. span: tracing::Span, /// The contract-level configuration. - tcfg: Cow<'a, TestRunnerConfig>, + tcfg: Cow<'a, TestRunnerConfig>, /// The parent runner. - mcr: &'a MultiContractRunner, + mcr: &'a MultiContractRunner, } -impl<'a> std::ops::Deref for ContractRunner<'a> { - type Target = Cow<'a, TestRunnerConfig>; +impl<'a, FEN: FoundryEvmNetwork> Deref for ContractRunner<'a, FEN> { + type Target = Cow<'a, TestRunnerConfig>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -82,15 +87,15 @@ impl<'a> std::ops::Deref for ContractRunner<'a> { } } -impl<'a> ContractRunner<'a> { - pub fn new( +impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> { + pub const fn new( name: &'a str, contract: &'a TestContract, - executor: Executor, + executor: Executor, progress: Option<&'a TestsProgress>, tokio_handle: &'a tokio::runtime::Handle, span: Span, - mcr: &'a MultiContractRunner, + mcr: &'a MultiContractRunner, ) -> Self { Self { name, @@ -104,6 +109,25 @@ impl<'a> ContractRunner<'a> { } } + /// Returns `true` if `func` should run in the current multi-network pass. + /// + /// In single-pass mode (no inline network overrides) every function passes. + /// In multi-pass mode: + /// - Default pass (`pass_network = None`): includes functions *without* an override annotation. + /// - Override pass (`pass_network = Some(v)`): includes only functions annotated with `v`. + fn function_matches_network_pass(&self, func: &Function) -> bool { + let multi = &self.mcr.tcfg.multi_network; + if multi.all_override_networks.is_empty() { + return true; + } + let profile = &self.tcfg.config.profile; + let func_network = self.mcr.inline_config.network_for(profile, self.name, &func.name); + match &multi.pass_network { + None => func_network.is_none_or(|n| !multi.all_override_networks.contains(&n)), + Some(target) => func_network.as_ref() == Some(target), + } + } + /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { @@ -148,6 +172,7 @@ impl<'a> ContractRunner<'a> { let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; result.extend(raw, TraceKind::Deployment); if reason.is_some() { + debug!(?reason, "deployment of library failed"); result.reason = reason; return Ok(result); } @@ -176,6 +201,7 @@ impl<'a> ContractRunner<'a> { let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; result.extend(raw, TraceKind::Deployment); if reason.is_some() { + debug!(?reason, "deployment of test contract failed"); result.reason = reason; return Ok(result); } @@ -221,8 +247,9 @@ impl<'a> ContractRunner<'a> { /// Returns the configuration for a contract or function. fn inline_config(&self, func: Option<&Function>) -> Result { let function = func.map(|f| f.name.as_str()).unwrap_or(""); - let config = - self.mcr.inline_config.merge(self.name, function, &self.config).extract::()?; + let config = self + .config + .merge_inline_provider(self.mcr.inline_config.provide(self.name, function))?; Ok(config) } @@ -306,7 +333,7 @@ impl<'a> ContractRunner<'a> { [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))] .into(), warnings, - ) + ); } // Check if `afterInvariant` function with valid signature declared. @@ -322,7 +349,7 @@ impl<'a> ContractRunner<'a> { )] .into(), warnings, - ) + ); } let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| { let match_sig = after_invariant_fn.name == "afterInvariant"; @@ -352,16 +379,16 @@ impl<'a> ContractRunner<'a> { if setup.reason.is_some() { // The setup failed, so we return a single test result for `setUp` - let fail_msg = if !setup.deployment_failure { - "setUp()".to_string() - } else { + let fail_msg = if setup.deployment_failure { "constructor()".to_string() + } else { + "setUp()".to_string() }; return SuiteResult::new( start.elapsed(), [(fail_msg, TestResult::setup_result(setup))].into(), warnings, - ) + ); } // Filter out functions sequentially since it's very fast and there is no need to do it @@ -371,7 +398,8 @@ impl<'a> ContractRunner<'a> { .contract .abi .functions() - .filter(|func| is_matching_test(func, filter)) + .filter(|func| filter.matches_test_function(func)) + .filter(|func| self.function_matches_network_pass(func)) .collect::>(); debug!( "Found {} test functions out of {} in {:?}", @@ -384,28 +412,34 @@ impl<'a> ContractRunner<'a> { load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts) }); - let test_fail_instances = functions - .iter() - .filter_map(|func| { - TestFunctionKind::classify(&func.name, !func.inputs.is_empty()) - .is_any_test_fail() - .then_some(func.name.clone()) - }) - .collect::>(); + let test_fail_functions = + functions.iter().filter(|func| func.test_function_kind().is_any_test_fail()); + if test_fail_functions.clone().next().is_some() { + let fail = || { + TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string()) + }; + let test_results = test_fail_functions.map(|func| (func.signature(), fail())).collect(); + return SuiteResult::new(start.elapsed(), test_results, warnings); + } - if !test_fail_instances.is_empty() { - let instances = format!( - "Found {} instances: {}", - test_fail_instances.len(), - test_fail_instances.join(", ") - ); - let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string()); - return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings) + let early_exit = &self.tcfg.early_exit; + + if self.progress.is_some() { + let interrupt = early_exit.clone(); + self.tokio_handle.spawn(async move { + signal::ctrl_c().await.expect("Failed to listen for Ctrl+C"); + interrupt.record_ctrl_c(); + }); } let test_results = functions .par_iter() - .map(|&func| { + .filter_map(|&func| { + // Early exit if we're running with fail-fast and a test already failed. + if early_exit.should_stop() { + return None; + } + let start = Instant::now(); let _guard = self.tokio_handle.enter(); @@ -434,7 +468,12 @@ impl<'a> ContractRunner<'a> { ); res.duration = start.elapsed(); - (sig, res) + // Record test failure for early exit (only triggers if fail-fast is enabled). + if res.status.is_failure() { + early_exit.record_failure(); + } + + Some((sig, res)) }) .collect::>(); @@ -444,13 +483,13 @@ impl<'a> ContractRunner<'a> { } /// Executes a single test function, returning a [`TestResult`]. -struct FunctionRunner<'a> { +struct FunctionRunner<'a, FEN: FoundryEvmNetwork> { /// The function-level configuration. - tcfg: Cow<'a, TestRunnerConfig>, + tcfg: Cow<'a, TestRunnerConfig>, /// The EVM executor. - executor: Cow<'a, Executor>, + executor: Cow<'a, Executor>, /// The parent runner. - cr: &'a ContractRunner<'a>, + cr: &'a ContractRunner<'a, FEN>, /// The address of the test contract. address: Address, /// The test setup result. @@ -459,8 +498,8 @@ struct FunctionRunner<'a> { result: TestResult, } -impl<'a> std::ops::Deref for FunctionRunner<'a> { - type Target = Cow<'a, TestRunnerConfig>; +impl<'a, FEN: FoundryEvmNetwork> Deref for FunctionRunner<'a, FEN> { + type Target = Cow<'a, TestRunnerConfig>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -468,8 +507,8 @@ impl<'a> std::ops::Deref for FunctionRunner<'a> { } } -impl<'a> FunctionRunner<'a> { - fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self { +impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { + fn new(cr: &'a ContractRunner<'a, FEN>, setup: &'a TestSetup) -> Self { Self { tcfg: match &cr.tcfg { Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg), @@ -483,7 +522,7 @@ impl<'a> FunctionRunner<'a> { } } - fn revert_decoder(&self) -> &'a RevertDecoder { + const fn revert_decoder(&self) -> &'a RevertDecoder { &self.cr.mcr.revert_decoder } @@ -512,14 +551,9 @@ impl<'a> FunctionRunner<'a> { match kind { TestFunctionKind::UnitTest { .. } => self.run_unit_test(func), TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func), + TestFunctionKind::TableTest => self.run_table_test(func), TestFunctionKind::InvariantTest => { - let test_bytecode = &self.cr.contract.bytecode; - self.run_invariant_test( - func, - call_after_invariant, - identified_contracts.unwrap(), - test_bytecode, - ) + self.run_invariant_test(func, call_after_invariant, identified_contracts.unwrap()) } _ => unreachable!(), } @@ -566,12 +600,141 @@ impl<'a> FunctionRunner<'a> { self.result } + /// Runs a table test. + /// The parameters dataset (table) is created from defined parameter fixtures, therefore each + /// test table parameter should have the same number of fixtures defined. + /// E.g. for table test + /// - `table_test(uint256 amount, bool swap)` fixtures are defined as + /// - `uint256[] public fixtureAmount = [2, 5]` + /// - `bool[] public fixtureSwap = [true, false]` The `table_test` is then called with the pair + /// of args `(2, true)` and `(5, false)`. + fn run_table_test(mut self, func: &Function) -> TestResult { + // Prepare unit test execution. + if self.prepare_test(func).is_err() { + return self.result; + } + + // Extract and validate fixtures for the first table test parameter. + let Some(first_param) = func.inputs.first() else { + self.result.single_fail(Some("Table test should have at least one parameter".into())); + return self.result; + }; + + let Some(first_param_fixtures) = + &self.setup.fuzz_fixtures.param_fixtures(first_param.name()) + else { + self.result.single_fail(Some("Table test should have fixtures defined".into())); + return self.result; + }; + + if first_param_fixtures.is_empty() { + self.result.single_fail(Some("Table test should have at least one fixture".into())); + return self.result; + } + + let fixtures_len = first_param_fixtures.len(); + let mut table_fixtures = vec![&first_param_fixtures[..]]; + + // Collect fixtures for remaining parameters. + for param in &func.inputs[1..] { + let param_name = param.name(); + let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else { + self.result.single_fail(Some(format!("No fixture defined for param {param_name}"))); + return self.result; + }; + + if fixtures.len() != fixtures_len { + self.result.single_fail(Some(format!( + "{} fixtures defined for {param_name} (expected {})", + fixtures.len(), + fixtures_len + ))); + return self.result; + } + + table_fixtures.push(&fixtures[..]); + } + + let progress = start_fuzz_progress( + self.cr.progress, + self.cr.name, + &func.name, + None, + fixtures_len as u32, + ); + + let mut result = FuzzTestResult::default(); + + for i in 0..fixtures_len { + if self.tcfg.early_exit.should_stop() { + return self.result; + } + + // Increment progress bar. + if let Some(progress) = progress.as_ref() { + progress.inc(1); + } + + let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec(); + let (mut raw_call_result, reason) = match self.executor.call( + self.sender, + self.address, + func, + &args, + U256::ZERO, + Some(self.revert_decoder()), + ) { + Ok(res) => (res.raw, None), + Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), + Err(EvmError::Skip(reason)) => { + self.result.single_skip(reason); + return self.result; + } + Err(err) => { + self.result.single_fail(Some(err.to_string())); + return self.result; + } + }; + + result.gas_by_case.push((raw_call_result.gas_used, raw_call_result.stipend)); + result.logs.extend(raw_call_result.logs.clone()); + result.labels.extend(raw_call_result.labels.clone()); + HitMaps::merge_opt(&mut result.line_coverage, raw_call_result.line_coverage.clone()); + + let is_success = + self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false); + // Record counterexample if test fails. + if !is_success { + result.counterexample = + Some(CounterExample::Single(BaseCounterExample::from_fuzz_call( + Bytes::from(func.abi_encode_input(&args).unwrap()), + args, + raw_call_result.traces.clone(), + ))); + result.reason = reason; + result.traces = raw_call_result.traces; + self.result.table_result(result); + return self.result; + } + + // If it's the last iteration and all other runs succeeded, then use last call result + // for logs and traces. + if i == fixtures_len - 1 { + result.success = true; + result.traces = raw_call_result.traces; + self.result.table_result(result); + return self.result; + } + } + + self.result + } + fn run_invariant_test( mut self, func: &Function, call_after_invariant: bool, identified_contracts: &ContractsByAddress, - test_bytecode: &Bytes, ) -> TestResult { // First, run the test normally to see if it needs to be skipped. if let Err(EvmError::Skip(reason)) = self.executor.call( @@ -589,30 +752,62 @@ impl<'a> FunctionRunner<'a> { let runner = self.invariant_runner(); let invariant_config = &self.config.invariant; + let mut executor = self.clone_executor(); + // Enable edge coverage if running with coverage guided fuzzing or with edge coverage + // metrics (useful for benchmarking the fuzzer). + executor + .inspector_mut() + .collect_edge_coverage(invariant_config.corpus.collect_evm_edge_coverage()); + executor + .inspector_mut() + .collect_sancov_edges(invariant_config.corpus.collect_sancov_edges()); + executor + .inspector_mut() + .collect_sancov_trace_cmp(invariant_config.corpus.collect_sancov_trace_cmp()); + let mut config = invariant_config.clone(); + let (failure_dir, failure_file) = test_paths( + &mut config.corpus, + invariant_config.failure_persist_dir.clone().unwrap(), + self.cr.name, + &func.name, + ); + let mut evm = InvariantExecutor::new( - self.clone_executor(), + executor, runner, - invariant_config.clone(), + config, identified_contracts, &self.cr.mcr.known_contracts, ); - let invariant_contract = InvariantContract { - address: self.address, - invariant_function: func, + let invariant_contract = InvariantContract::new( + self.address, + self.cr.name, + func, call_after_invariant, - abi: &self.cr.contract.abi, + &self.cr.contract.abi, + ); + let show_solidity = invariant_config.show_solidity; + + // Compute current invariant settings for failure validation. + let current_settings = match evm.compute_settings(self.address) { + Ok(s) => s, + Err(e) => { + self.result.invariant_setup_fail(e); + return self.result; + } }; - let (failure_dir, failure_file) = invariant_failure_paths( - invariant_config, + let progress = start_fuzz_progress( + self.cr.progress, self.cr.name, - &invariant_contract.invariant_function.name, + &func.name, + invariant_config.timeout, + invariant_config.runs, ); - let show_solidity = invariant_config.show_solidity; // Try to replay recorded failure if any. - if let Some(mut call_sequence) = - persisted_call_sequence(failure_file.as_path(), test_bytecode) + if let Some(InvariantPersistedFailure { mut call_sequence, assertion_failure, .. }) = + persisted_call_sequence(failure_file.as_path(), ¤t_settings) { // Create calls from failed sequence and check if invariant still broken. let txes = call_sequence @@ -620,6 +815,8 @@ impl<'a> FunctionRunner<'a> { .map(|seq| { seq.show_solidity = show_solidity; BasicTxDetails { + warp: seq.warp, + roll: seq.roll, sender: seq.sender.unwrap_or_default(), call_details: CallDetails { target: seq.addr.unwrap_or_default(), @@ -628,52 +825,84 @@ impl<'a> FunctionRunner<'a> { } }) .collect::>(); - if let Ok((success, replayed_entirely)) = check_sequence( + if let Ok((success, replayed_entirely, replay_reason)) = check_sequence( self.clone_executor(), &txes, (0..min(txes.len(), invariant_config.depth as usize)).collect(), invariant_contract.address, invariant_contract.invariant_function.selector().to_vec().into(), - invariant_config.fail_on_revert, - invariant_contract.call_after_invariant, - ) { - if !success { - let _= sh_warn!("\ - Replayed invariant failure from {:?} file. \ - Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.", - failure_file.as_path() - ); - // If sequence still fails then replay error to collect traces and - // exit without executing new runs. - let _ = replay_run( - &invariant_contract, - self.clone_executor(), - &self.cr.mcr.known_contracts, - identified_contracts.clone(), - &mut self.result.logs, - &mut self.result.traces, - &mut self.result.coverage, - &mut self.result.deprecated_cheatcodes, - &txes, - show_solidity, - ); - self.result.invariant_replay_fail( - replayed_entirely, - &invariant_contract.invariant_function.name, - call_sequence, - ); - return self.result; + CheckSequenceOptions { + accumulate_warp_roll: invariant_config.has_delay(), + fail_on_revert: invariant_config.fail_on_revert, + expect_assertion_failure: assertion_failure, + call_after_invariant: invariant_contract.call_after_invariant, + rd: Some(self.revert_decoder()), + }, + ) && !success + { + let warn = format!( + "Replayed invariant failure from {:?} file. \nRun `forge clean` or remove file to ignore failure and to continue invariant test campaign.", + failure_file.as_path() + ); + + if let Some(ref progress) = progress { + progress.set_prefix(format!("{}\n{warn}\n", func.name)); + } else { + let _ = sh_warn!("{warn}"); + } + + // If sequence still fails then replay error to collect traces and exit without + // executing new runs. + match replay_error( + evm.config(), + self.clone_executor(), + &txes, + None, + assertion_failure, + None, // check mode + &invariant_contract, + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.line_coverage, + &mut self.result.deprecated_cheatcodes, + progress.as_ref(), + &self.tcfg.early_exit, + ) { + Ok(replayed_call_sequence) if !replayed_call_sequence.is_empty() => { + call_sequence = replayed_call_sequence; + // Persist error in invariant failure dir. + record_invariant_failure( + failure_dir.as_path(), + failure_file.as_path(), + &call_sequence, + ¤t_settings, + assertion_failure, + ); + } + Ok(_) => {} + Err(err) => { + error!(%err, "Failed to replay invariant error"); + } } + + self.result.invariant_replay_fail( + replayed_entirely, + &invariant_contract.invariant_function.name, + replay_reason, + call_sequence, + ); + return self.result; } } - let progress = - start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs); let invariant_result = match evm.invariant_fuzz( invariant_contract.clone(), &self.setup.fuzz_fixtures, - &self.setup.deployed_libs, + self.build_fuzz_state(true), progress.as_ref(), + &self.tcfg.early_exit, ) { Ok(x) => x, Err(e) => { @@ -682,7 +911,7 @@ impl<'a> FunctionRunner<'a> { } }; // Merge coverage collected during invariant run with test setup coverage. - self.result.merge_coverages(invariant_result.coverage); + self.result.merge_coverages(invariant_result.line_coverage); let mut counterexample = None; let success = invariant_result.error.is_none(); @@ -691,73 +920,111 @@ impl<'a> FunctionRunner<'a> { match invariant_result.error { // If invariants were broken, replay the error to collect logs and traces Some(error) => match error { - InvariantFuzzError::BrokenInvariant(case_data) | - InvariantFuzzError::Revert(case_data) => { + InvariantFuzzError::BrokenInvariant(case_data) + | InvariantFuzzError::Revert(case_data) => { // Replay error to create counterexample and to collect logs, traces and // coverage. + match case_data.test_error { + TestError::Abort(_) => {} + TestError::Fail(_, ref calls) => { + match replay_error( + evm.config(), + self.clone_executor(), + calls, + Some(case_data.inner_sequence), + case_data.assertion_failure, + None, // check mode + &invariant_contract, + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.line_coverage, + &mut self.result.deprecated_cheatcodes, + progress.as_ref(), + &self.tcfg.early_exit, + ) { + Ok(call_sequence) if !call_sequence.is_empty() => { + // Persist error in invariant failure dir. + record_invariant_failure( + failure_dir.as_path(), + failure_file.as_path(), + &call_sequence, + ¤t_settings, + case_data.assertion_failure, + ); + + let original_seq_len = + if let TestError::Fail(_, calls) = &case_data.test_error { + calls.len() + } else { + call_sequence.len() + }; + + counterexample = Some(CounterExample::Sequence( + original_seq_len, + call_sequence, + )) + } + Ok(_) => {} + Err(err) => { + error!(%err, "Failed to replay invariant error"); + } + } + } + }; + } + InvariantFuzzError::MaxAssumeRejects(_) => {} + }, + + // If invariants ran successfully, replay the last run to collect logs and traces. + _ => { + if let Some(best_value) = invariant_result.optimization_best_value { + // Optimization mode: replay and shrink to find shortest best sequence. match replay_error( - &case_data, - &invariant_contract, + evm.config(), self.clone_executor(), + &invariant_result.optimization_best_sequence, + None, + false, + Some(best_value), + &invariant_contract, &self.cr.mcr.known_contracts, identified_contracts.clone(), &mut self.result.logs, &mut self.result.traces, - &mut self.result.coverage, + &mut self.result.line_coverage, &mut self.result.deprecated_cheatcodes, progress.as_ref(), - show_solidity, + &self.tcfg.early_exit, ) { - Ok(call_sequence) => { - if !call_sequence.is_empty() { - // Persist error in invariant failure dir. - if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) { - error!(%err, "Failed to create invariant failure dir"); - } else if let Err(err) = foundry_common::fs::write_json_file( - failure_file.as_path(), - &InvariantPersistedFailure { - call_sequence: call_sequence.clone(), - driver_bytecode: Some(test_bytecode.clone()), - }, - ) { - error!(%err, "Failed to record call sequence"); - } - - let original_seq_len = - if let TestError::Fail(_, calls) = &case_data.test_error { - calls.len() - } else { - call_sequence.len() - }; - - counterexample = - Some(CounterExample::Sequence(original_seq_len, call_sequence)) - } + Ok(best_sequence) if !best_sequence.is_empty() => { + counterexample = Some(CounterExample::Sequence( + invariant_result.optimization_best_sequence.len(), + best_sequence, + )); } Err(err) => { - error!(%err, "Failed to replay invariant error"); + error!(%err, "Failed to replay optimization best sequence"); } - }; - } - InvariantFuzzError::MaxAssumeRejects(_) => {} - }, - - // If invariants ran successfully, replay the last run to collect logs and - // traces. - _ => { - if let Err(err) = replay_run( - &invariant_contract, - self.clone_executor(), - &self.cr.mcr.known_contracts, - identified_contracts.clone(), - &mut self.result.logs, - &mut self.result.traces, - &mut self.result.coverage, - &mut self.result.deprecated_cheatcodes, - &invariant_result.last_run_inputs, - show_solidity, - ) { - error!(%err, "Failed to replay last invariant run"); + _ => {} + } + } else { + // Standard check mode: replay last run for traces. + if let Err(err) = replay_run( + &invariant_contract, + self.clone_executor(), + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.line_coverage, + &mut self.result.deprecated_cheatcodes, + &invariant_result.last_run_inputs, + show_solidity, + ) { + error!(%err, "Failed to replay last invariant run"); + } } } } @@ -770,6 +1037,8 @@ impl<'a> FunctionRunner<'a> { invariant_result.cases, invariant_result.reverts, invariant_result.metrics, + invariant_result.failed_corpus_replays, + invariant_result.optimization_best_value, ); self.result } @@ -790,22 +1059,67 @@ impl<'a> FunctionRunner<'a> { } let runner = self.fuzz_runner(); - let fuzz_config = self.config.fuzz.clone(); + let mut fuzz_config = self.config.fuzz.clone(); + let (failure_dir, failure_file) = test_paths( + &mut fuzz_config.corpus, + fuzz_config.failure_persist_dir.clone().unwrap(), + self.cr.name, + &func.name, + ); - let progress = - start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs); + let progress = start_fuzz_progress( + self.cr.progress, + self.cr.name, + &func.name, + fuzz_config.timeout, + if fuzz_config.run.is_some() { 1 } else { fuzz_config.runs }, + ); + let state = self.build_fuzz_state(false); + let mut executor = self.executor.into_owned(); + // Enable edge coverage if running with coverage guided fuzzing or with edge coverage + // metrics (useful for benchmarking the fuzzer). + executor + .inspector_mut() + .collect_edge_coverage(fuzz_config.corpus.collect_evm_edge_coverage()); + executor.inspector_mut().collect_sancov_edges(fuzz_config.corpus.collect_sancov_edges()); + executor + .inspector_mut() + .collect_sancov_trace_cmp(fuzz_config.corpus.collect_sancov_trace_cmp()); + // Load persisted counterexample, if any. + let persisted_failure = + foundry_common::fs::read_json_file::(failure_file.as_path()).ok(); // Run fuzz test. - let fuzzed_executor = - FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config); - let result = fuzzed_executor.fuzz( + let mut fuzzed_executor = + FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure); + let result = match fuzzed_executor.fuzz( func, &self.setup.fuzz_fixtures, - &self.setup.deployed_libs, + state, self.address, &self.cr.mcr.revert_decoder, progress.as_ref(), - ); + &self.tcfg.early_exit, + self.cr.tokio_handle, + ) { + Ok(x) => x, + Err(e) => { + self.result.fuzz_setup_fail(e); + return self.result; + } + }; + + // Record counterexample. + if let Some(CounterExample::Single(counterexample)) = &result.counterexample { + if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) { + error!(%err, "Failed to create fuzz failure dir"); + } else if let Err(err) = + foundry_common::fs::write_json_file(failure_file.as_path(), counterexample) + { + error!(%err, "Failed to record call sequence"); + } + } + self.result.fuzz_result(result); self.result } @@ -823,13 +1137,13 @@ impl<'a> FunctionRunner<'a> { let address = self.setup.address; // Apply before test configured functions (if any). - if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() == - 1 - { + if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) { for calldata in self.executor.call_sol_default( address, &ITest::beforeTestSetupCall { testSelector: func.selector() }, ) { + let spec_id: SpecId = self.executor.spec_id().into(); + debug!(?calldata, spec=%spec_id, "applying before_test_setup"); // Apply before test configured calldata. match self.executor.to_mut().transact_raw( self.tcfg.sender, @@ -861,40 +1175,42 @@ impl<'a> FunctionRunner<'a> { fn fuzz_runner(&self) -> TestRunner { let config = &self.config.fuzz; - let failure_persist_path = config - .failure_persist_dir - .as_ref() - .unwrap() - .join(config.failure_persist_file.as_ref().unwrap()) - .into_os_string() - .into_string() - .unwrap(); - fuzzer_with_cases( - config.seed, - config.runs, - config.max_test_rejects, - Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), - ) + fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects) } fn invariant_runner(&self) -> TestRunner { let config = &self.config.invariant; - fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None) + fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects) } - fn clone_executor(&self) -> Executor { + fn clone_executor(&self) -> Executor { self.executor.clone().into_owned() } + + fn build_fuzz_state(&self, invariant: bool) -> EvmFuzzState { + let config = + if invariant { self.config.invariant.dictionary } else { self.config.fuzz.dictionary }; + if let Some(db) = self.executor.backend().active_fork_db() { + EvmFuzzState::new( + &self.setup.deployed_libs, + db, + config, + Some(&self.cr.mcr.fuzz_literals), + ) + } else { + let db = self.executor.backend().mem_db(); + EvmFuzzState::new( + &self.setup.deployed_libs, + db, + config, + Some(&self.cr.mcr.fuzz_literals), + ) + } + } } -fn fuzzer_with_cases( - seed: Option, - cases: u32, - max_global_rejects: u32, - file_failure_persistence: Option>, -) -> TestRunner { +fn fuzzer_with_cases(seed: Option, cases: u32, max_global_rejects: u32) -> TestRunner { let config = proptest::test_runner::Config { - failure_persistence: file_failure_persistence, cases, max_global_rejects, // Disable proptest shrink: for fuzz tests we provide single counterexample, @@ -918,44 +1234,72 @@ fn fuzzer_with_cases( struct InvariantPersistedFailure { /// Recorded counterexample. call_sequence: Vec, - /// Bytecode of the test contract that generated the counterexample. - #[serde(skip_serializing_if = "Option::is_none")] - driver_bytecode: Option, + /// Invariant settings when the counterexample was generated. + /// Used to determine if the counterexample is still valid. + settings: InvariantSettings, + /// Whether the persisted failure came from a handler assertion instead of the invariant body. + #[serde(default)] + assertion_failure: bool, } /// Helper function to load failed call sequence from file. -/// Ignores failure if generated with different test contract than the current one. -fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option> { +/// Ignores failure if generated with different invariant settings than the current ones. +fn persisted_call_sequence( + path: &Path, + current_settings: &InvariantSettings, +) -> Option { foundry_common::fs::read_json_file::(path).ok().and_then( |persisted_failure| { - if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode { - // Ignore persisted sequence if test bytecode doesn't match. - if !bytecode.eq(persisted_bytecode) { - let _= sh_warn!("\ - Failure from {:?} file was ignored because test contract bytecode has changed.", - path - ); - return None; - } - }; - Some(persisted_failure.call_sequence) + if let Some(diff) = persisted_failure.settings.diff(current_settings) { + let _ = sh_warn!( + "Failure from {:?} file was ignored because invariant test settings have changed: {}", + path, + diff + ); + return None; + } + Some(persisted_failure) }, ) } -/// Helper functions to return canonicalized invariant failure paths. -fn invariant_failure_paths( - config: &InvariantConfig, +/// Helper function to set test corpus dir and to compose persisted failure paths. +fn test_paths( + corpus_config: &mut FuzzCorpusConfig, + persist_dir: PathBuf, contract_name: &str, - invariant_name: &str, + test_name: &str, ) -> (PathBuf, PathBuf) { - let dir = config - .failure_persist_dir - .clone() - .unwrap() - .join("failures") - .join(contract_name.split(':').next_back().unwrap()); - let dir = canonicalized(dir); - let file = canonicalized(dir.join(invariant_name)); - (dir, file) + let contract = contract_name.split(':').next_back().unwrap(); + // Update config with corpus dir for current test. + corpus_config.with_test(contract, test_name); + + let failures_dir = canonicalized(persist_dir.join("failures").join(contract)); + let failure_file = canonicalized(failures_dir.join(test_name)); + (failures_dir, failure_file) +} + +/// Helper function to persist invariant failure. +fn record_invariant_failure( + failure_dir: &Path, + failure_file: &Path, + call_sequence: &[BaseCounterExample], + settings: &InvariantSettings, + assertion_failure: bool, +) { + if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) { + error!(%err, "Failed to create invariant failure dir"); + return; + } + + if let Err(err) = foundry_common::fs::write_json_file( + failure_file, + &InvariantPersistedFailure { + call_sequence: call_sequence.to_owned(), + settings: settings.clone(), + assertion_failure, + }, + ) { + error!(%err, "Failed to record call sequence"); + } } diff --git a/crates/forge/tests/cli/backtrace.rs b/crates/forge/tests/cli/backtrace.rs new file mode 100644 index 0000000000000..434fa4cb289ba --- /dev/null +++ b/crates/forge/tests/cli/backtrace.rs @@ -0,0 +1,688 @@ +//! Tests for backtrace functionality + +use foundry_test_utils::rpc::{next_etherscan_api_key, next_http_rpc_endpoint}; + +forgetest!(test_backtraces, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.add_source("SimpleRevert.sol", include_str!("../fixtures/backtraces/SimpleRevert.sol")); + prj.add_source("StaticCall.sol", include_str!("../fixtures/backtraces/StaticCall.sol")); + prj.add_source("DelegateCall.sol", include_str!("../fixtures/backtraces/DelegateCall.sol")); + prj.add_source("NestedCalls.sol", include_str!("../fixtures/backtraces/NestedCalls.sol")); + + prj.add_test("Backtrace.t.sol", include_str!("../fixtures/backtraces/Backtrace.t.sol")); + + let output = cmd.args(["test", "-vvvvv"]).assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +... +Ran 11 tests for test/Backtrace.t.sol:BacktraceTest +[FAIL: panic: assertion failed (0x01)] testAssertFail() ([GAS]) +... +Backtrace: + at SimpleRevert.doAssert + at BacktraceTest.testAssertFail (test/Backtrace.t.sol:40:48) + +[FAIL: CustomError(42, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496)] testCustomError() ([GAS]) +... +Backtrace: + at SimpleRevert.doCustomError (src/SimpleRevert.sol:21:59) + at BacktraceTest.testCustomError (test/Backtrace.t.sol:45:49) + +[FAIL: Delegate compute failed] testDelegateCallRequire() ([GAS]) +... +Backtrace: + at DelegateTarget.compute (src/DelegateCall.sol:11:84) + at DelegateCaller.delegateCompute (src/DelegateCall.sol:32:101) + at BacktraceTest.testDelegateCallRequire (test/Backtrace.t.sol:82:57) + +[FAIL: Delegate call failed] testDelegateCallRevert() ([GAS]) +... +Backtrace: + at DelegateTarget.fail (src/DelegateCall.sol:7:43) + at DelegateCaller.delegateFail (src/DelegateCall.sol:26:91) + at BacktraceTest.testDelegateCallRevert (test/Backtrace.t.sol:77:56) + +[FAIL: Failed at internal level 3] testInternalCallChain() ([GAS]) +... +Backtrace: + at BacktraceTest.testInternalCallChain (test/Backtrace.t.sol:72:54) + +[FAIL: Failed at chain level 3] testInternalCallsSameSource() ([GAS]) +... +Backtrace: + at NestedCalls.callChain1 (src/NestedCalls.sol:25:51) + at BacktraceTest.testInternalCallsSameSource (test/Backtrace.t.sol:55:61) + +[FAIL: Maximum depth reached] testNestedCalls() ([GAS]) +... +Backtrace: + at NestedCalls.nestedCall (src/NestedCalls.sol:11:46) + at NestedCalls.nestedCall (src/NestedCalls.sol:13:19) + at NestedCalls.nestedCall (src/NestedCalls.sol:13:19) + at NestedCalls.nestedCall (src/NestedCalls.sol:13:19) + at NestedCalls.nestedCall (src/NestedCalls.sol:13:19) + at BacktraceTest.testNestedCalls (test/Backtrace.t.sol:50:49) + +[FAIL: Value must be greater than zero] testRequireFail() ([GAS]) +... +Backtrace: + at SimpleRevert.doRequire (src/SimpleRevert.sol:11:61) + at BacktraceTest.testRequireFail (test/Backtrace.t.sol:35:49) + +[FAIL: Simple revert message] testSimpleRevert() ([GAS]) +... +Backtrace: + at SimpleRevert.doRevert (src/SimpleRevert.sol:7:67) + at BacktraceTest.testSimpleRevert (test/Backtrace.t.sol:30:50) + +[FAIL: Static compute failed] testStaticCallRequire() ([GAS]) +... +Backtrace: + at StaticTarget.compute (src/StaticCall.sol:11:77) + at StaticCaller.staticCompute (src/StaticCall.sol:30:124) + at BacktraceTest.testStaticCallRequire (test/Backtrace.t.sol:92:60) + +[FAIL: Static call reverted] testStaticCallRevert() ([GAS]) +... +Backtrace: + at StaticTarget.viewFail (src/StaticCall.sol:7:47) + at StaticCaller.staticCallFail (src/StaticCall.sol:25:93) + at BacktraceTest.testStaticCallRevert (test/Backtrace.t.sol:87:59) + +Suite result: FAILED. 0 passed; 11 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +forgetest!(test_backtrace_with_mixed_compilation, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + prj.add_source( + "SimpleRevert.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SimpleRevert { + function doRevert(string memory reason) public pure { + revert(reason); + } +} +"#, + ); + + // Add another source file that won't be modified + prj.add_source( + "HelperContract.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract HelperContract { + function getValue() public pure returns (uint256) { + return 42; + } + + function doRevert() public pure { + revert("Helper revert"); + } +} +"#, + ); + + prj.add_test( + "BacktraceTest.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/SimpleRevert.sol"; +import "../src/HelperContract.sol"; + +contract BacktraceTest is DSTest { + SimpleRevert simpleRevert; + HelperContract helper; + + function setUp() public { + simpleRevert = new SimpleRevert(); + helper = new HelperContract(); + } + + function testSimpleRevert() public { + simpleRevert.doRevert("Test failure"); + } + + function testHelperRevert() public { + helper.doRevert(); + } +} +"#, + ); + + let output = cmd.args(["test", "-vvvvv"]).assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 2 tests for test/BacktraceTest.t.sol:BacktraceTest +[FAIL: Helper revert] testHelperRevert() ([GAS]) +... +Backtrace: + at HelperContract.doRevert (src/HelperContract.sol:11:47) + at BacktraceTest.testHelperRevert (test/BacktraceTest.t.sol:23:50) + +[FAIL: Test failure] testSimpleRevert() ([GAS]) +... +Backtrace: + at SimpleRevert.doRevert (src/SimpleRevert.sol:7:67) + at BacktraceTest.testSimpleRevert (test/BacktraceTest.t.sol:19:50) + +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // Modify the source file - add a comment to change line numbers + prj.add_source( + "SimpleRevert.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SimpleRevert { + function doRevert(string memory reason) public pure { + // Added comment to shift line numbers + revert(reason); + } +} +"#, + ); + + // Modify the test file as well + prj.add_test( + "BacktraceTest.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/SimpleRevert.sol"; +import "../src/HelperContract.sol"; + +contract BacktraceTest is DSTest { + SimpleRevert simpleRevert; + HelperContract helper; + + function setUp() public { + simpleRevert = new SimpleRevert(); + helper = new HelperContract(); + } + + function testSimpleRevert() public { + // Added some comments + // to change line numbers + simpleRevert.doRevert("Test failure"); + } + + function testHelperRevert() public { + helper.doRevert(); + } +} +"#, + ); + + // Second run - mixed compilation (SimpleRevert fresh, BacktraceTest fresh, HelperContract + // cached) + let output = cmd.forge_fuse().args(["test", "-vvvvv"]).assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 2 tests for test/BacktraceTest.t.sol:BacktraceTest +[FAIL: Helper revert] testHelperRevert() ([GAS]) +... +Backtrace: + at HelperContract.doRevert (src/HelperContract.sol:11:47) + at BacktraceTest.testHelperRevert (test/BacktraceTest.t.sol:25:50) + +[FAIL: Test failure] testSimpleRevert() ([GAS]) +... +Backtrace: + at SimpleRevert.doRevert (src/SimpleRevert.sol:8:56) + at BacktraceTest.testSimpleRevert (test/BacktraceTest.t.sol:21:43) + +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +forgetest!(test_library_backtrace, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + // Add library source files + prj.add_source( + "libraries/InternalMathLib.sol", + include_str!("../fixtures/backtraces/libraries/InternalMathLib.sol"), + ); + prj.add_source( + "libraries/ExternalMathLib.sol", + include_str!("../fixtures/backtraces/libraries/ExternalMathLib.sol"), + ); + prj.add_source( + "LibraryConsumer.sol", + include_str!("../fixtures/backtraces/LibraryConsumer.sol"), + ); + + // Add test file + prj.add_test( + "LibraryBacktrace.t.sol", + include_str!("../fixtures/backtraces/LibraryBacktrace.t.sol"), + ); + + // Add foundry.toml configuration for linked library + let config = foundry_config::Config { + libraries: vec!["src/libraries/ExternalMathLib.sol:ExternalMathLib:0x1234567890123456789012345678901234567890".to_string()], + ..Default::default() + }; + prj.write_config(config); + + let output = + cmd.args(["test", "-vvv", "--ast", "--mc", "LibraryBacktraceTest"]).assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 9 tests for test/LibraryBacktrace.t.sol:LibraryBacktraceTest +[FAIL: DivisionByZero()] testExternalDivisionByZero() ([GAS]) +... +Backtrace: + at ExternalMathLib.div + at LibraryConsumer.externalDivide + at LibraryBacktraceTest.testExternalDivisionByZero + +[FAIL: panic: arithmetic underflow or overflow (0x11)] testExternalOverflow() ([GAS]) +... +Backtrace: + at ExternalMathLib.mul + at LibraryConsumer.externalMultiply + at LibraryBacktraceTest.testExternalOverflow + +[FAIL: ExternalMathLib: value must be positive] testExternalRequire() ([GAS]) +... +Backtrace: + at ExternalMathLib.requirePositive + at LibraryConsumer.externalCheckPositive + at LibraryBacktraceTest.testExternalRequire + +[FAIL: Underflow()] testExternalUnderflow() ([GAS]) +... +Backtrace: + at ExternalMathLib.sub + at LibraryConsumer.externalSubtract + at LibraryBacktraceTest.testExternalUnderflow + +[FAIL: DivisionByZero()] testInternalDivisionByZero() ([GAS]) +... +Backtrace: + at LibraryConsumer.internalDivide + at LibraryBacktraceTest.testInternalDivisionByZero + +[FAIL: panic: arithmetic underflow or overflow (0x11)] testInternalOverflow() ([GAS]) +Traces: +... +Backtrace: + at LibraryConsumer.internalMultiply + at LibraryBacktraceTest.testInternalOverflow + +[FAIL: InternalMathLib: value must be positive] testInternalRequire() ([GAS]) +Traces: +... +Backtrace: + at LibraryConsumer.internalCheckPositive + at LibraryBacktraceTest.testInternalRequire + +[FAIL: Underflow()] testInternalUnderflow() ([GAS]) +Traces: +... +Backtrace: + at LibraryConsumer.internalSubtract + at LibraryBacktraceTest.testInternalUnderflow + +[FAIL: DivisionByZero()] testMixedLibraryFailure() ([GAS]) +Traces: +... +Backtrace: + at ExternalMathLib.div + at LibraryConsumer.mixedCalculation + at LibraryBacktraceTest.testMixedLibraryFailure + +Suite result: FAILED. 0 passed; 9 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +forgetest!(test_multiple_libraries_same_file, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "libraries/MultipleLibraries.sol", + include_str!("../fixtures/backtraces/libraries/MultipleLibraries.sol"), + ); + prj.add_source( + "MultipleLibraryConsumer.sol", + include_str!("../fixtures/backtraces/MultipleLibraryConsumer.sol"), + ); + + prj.add_test( + "MultipleLibraryBacktrace.t.sol", + include_str!("../fixtures/backtraces/MultipleLibraryBacktrace.t.sol"), + ); + + let output = cmd + .args(["test", "-vvvvv", "--ast", "--mc", "MultipleLibraryBacktraceTest"]) + .assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 4 tests for test/MultipleLibraryBacktrace.t.sol:MultipleLibraryBacktraceTest +[FAIL: FirstLibError()] testAllLibrariesFirstFails() ([GAS]) +... +Backtrace: + at MultipleLibraryConsumer.useAllLibraries (src/libraries/MultipleLibraries.sol:10:42) + at MultipleLibraryBacktraceTest.testAllLibrariesFirstFails (test/MultipleLibraryBacktrace.t.sol:31:60) + +[FAIL: FirstLibError()] testFirstLibraryError() ([GAS]) +Traces: +... +Backtrace: + at MultipleLibraryConsumer.useFirstLib (src/libraries/MultipleLibraries.sol:10:42) + at MultipleLibraryBacktraceTest.testFirstLibraryError (test/MultipleLibraryBacktrace.t.sol:16:55) + +[FAIL: SecondLibError()] testSecondLibraryError() ([GAS]) +Traces: +... +Backtrace: + at MultipleLibraryConsumer.useSecondLib (src/libraries/MultipleLibraries.sol:26:41) + at MultipleLibraryBacktraceTest.testSecondLibraryError (test/MultipleLibraryBacktrace.t.sol:21:56) + +[FAIL: ThirdLibError()] testThirdLibraryError() ([GAS]) +Traces: +... +Backtrace: + at MultipleLibraryConsumer.useThirdLib (src/libraries/MultipleLibraries.sol:42:42) + at MultipleLibraryBacktraceTest.testThirdLibraryError (test/MultipleLibraryBacktrace.t.sol:26:55) + +Suite result: FAILED. 0 passed; 4 failed; 0 skipped; [ELAPSED] + +... +"#]]); +}); + +forgetest!(test_fork_backtrace, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + let etherscan_api_key = next_etherscan_api_key(); + let fork_url = next_http_rpc_endpoint(); + + prj.add_source( + "ForkedERC20Wrapper.sol", + include_str!("../fixtures/backtraces/ForkedERC20Wrapper.sol"), + ); + + prj.add_test("ForkBacktrace.t.sol", include_str!("../fixtures/backtraces/ForkBacktrace.t.sol")); + + let output = cmd + .args(["test", "-vvvvv", "--fork-url", &fork_url, "--match-contract", "ForkBacktraceTest"]) + .assert_failure(); + + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 5 tests for test/ForkBacktrace.t.sol:ForkBacktraceTest +[FAIL: USDC transfer failed] testDirectOnChainRevert() ([GAS]) +... +Backtrace: + at 0x43506849D7C04F9138D1A2050bbF3A0c054402dd.transfer + at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.transfer + at ForkBacktraceTest.testDirectOnChainRevert (test/ForkBacktrace.t.sol:36:126) + +[FAIL: ERC20: transfer amount exceeds balance] testNestedFailure() ([GAS]) +... +Backtrace: + at 0x43506849D7C04F9138D1A2050bbF3A0c054402dd.transfer + at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.transfer + at ForkedERC20Wrapper.nestedFailure (src/ForkedERC20Wrapper.sol:14:89) + at ForkBacktraceTest.testNestedFailure (test/ForkBacktrace.t.sol:30:51) + +[FAIL: Account has zero USDC balance] testRequireNonZeroBalance() ([GAS]) +... +Backtrace: + at ForkedERC20Wrapper.requireNonZeroBalance (src/ForkedERC20Wrapper.sol:23:68) + at ForkBacktraceTest.testRequireNonZeroBalance (test/ForkBacktrace.t.sol:26:64) + +[FAIL: ERC20: transfer amount exceeds allowance] testTransferFromWithoutApproval() ([GAS]) +... +Backtrace: + at 0x43506849D7C04F9138D1A2050bbF3A0c054402dd.transferFrom + at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.transferFrom + at ForkedERC20Wrapper.transferFromWithoutApproval (src/ForkedERC20Wrapper.sol:18:101) + at ForkBacktraceTest.testTransferFromWithoutApproval (test/ForkBacktrace.t.sol:22:65) + +[FAIL: ERC20: transfer amount exceeds balance] testTransferWithoutBalance() ([GAS]) +... +Backtrace: + at 0x43506849D7C04F9138D1A2050bbF3A0c054402dd.transfer + at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.transfer + at ForkedERC20Wrapper.transferWithoutBalance (src/ForkedERC20Wrapper.sol:14:89) + at ForkBacktraceTest.testTransferWithoutBalance (test/ForkBacktrace.t.sol:18:60) + +Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] +... +"#]]); + + cmd.forge_fuse() + .args([ + "test", + "--mt", + "testTransferFromWithoutApproval", + "-vvvvv", + "--fork-url", + &fork_url, + "--etherscan-api-key", + ðerscan_api_key, + ]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped +... +Ran 1 test for test/ForkBacktrace.t.sol:ForkBacktraceTest +[FAIL: ERC20: transfer amount exceeds allowance] testTransferFromWithoutApproval() ([GAS]) +... +Backtrace: + at FiatTokenV2_2.transferFrom + at FiatTokenProxy.fallback + at ForkedERC20Wrapper.transferFromWithoutApproval (src/ForkedERC20Wrapper.sol:18:101) + at ForkBacktraceTest.testTransferFromWithoutApproval (test/ForkBacktrace.t.sol:22:65) +... +"#]]); +}); + +forgetest!(test_backtrace_via_ir_disables_source_lines, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.add_source("SimpleRevert.sol", include_str!("../fixtures/backtraces/SimpleRevert.sol")); + prj.add_source("StaticCall.sol", include_str!("../fixtures/backtraces/StaticCall.sol")); + prj.add_source("DelegateCall.sol", include_str!("../fixtures/backtraces/DelegateCall.sol")); + prj.add_source("NestedCalls.sol", include_str!("../fixtures/backtraces/NestedCalls.sol")); + + prj.add_test("Backtrace.t.sol", include_str!("../fixtures/backtraces/Backtrace.t.sol")); + + prj.update_config(|c| c.via_ir = true); + + let output = cmd.args(["test", "-vvvvv"]).assert_failure(); + output.stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: Static compute failed] testStaticCallRequire() ([GAS]) +... +Backtrace: + at StaticTarget.compute + at StaticCaller.staticCompute + at BacktraceTest.testStaticCallRequire +... +"#]]); +}); + +// Test that backtraces only appear at verbosity 5 (-vvvvv). +// Runs the same failing test at every verbosity level to assert correct output. +#[cfg(not(feature = "isolate-by-default"))] +forgetest!(test_backtrace_verbosity_levels, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + prj.add_source( + "SimpleRevert.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SimpleRevert { + function doRevert() public pure { + revert("Simple revert message"); + } +} +"#, + ); + + prj.add_test( + "BacktraceVerbosity.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/SimpleRevert.sol"; + +contract BacktraceVerbosityTest is DSTest { + SimpleRevert simpleRevert; + + function setUp() public { + simpleRevert = new SimpleRevert(); + } + + function testRevert() public { + simpleRevert.doRevert(); + } +} +"#, + ); + + // -v (verbosity 1): no traces, no backtrace. + cmd.args(["test", "--mc", "BacktraceVerbosityTest", "-v"]).assert_failure().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"# + ]]); + + // -vvv (verbosity 3): traces and backtrace WITHOUT source locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert + at BacktraceVerbosityTest.testRevert + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // -vvvv (verbosity 4): traces with setup and backtrace WITHOUT source locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::setUp() + ├─ [..] → new SimpleRevert@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] + └─ ← [Stop] + + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert + at BacktraceVerbosityTest.testRevert + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // -vvvvv (verbosity 5): traces with setup, storage changes, and backtrace WITH source + // locations. + cmd.forge_fuse() + .args(["test", "--mc", "BacktraceVerbosityTest", "-vvvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/BacktraceVerbosity.t.sol:BacktraceVerbosityTest +[FAIL: Simple revert message] testRevert() ([GAS]) +Traces: + [..] BacktraceVerbosityTest::setUp() + ├─ [..] → new SimpleRevert@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] + └─ ← [Stop] + + [..] BacktraceVerbosityTest::testRevert() + ├─ [..] SimpleRevert::doRevert() [staticcall] + │ └─ ← [Revert] Simple revert message + └─ ← [Revert] Simple revert message + +Backtrace: + at SimpleRevert.doRevert (src/SimpleRevert.sol:[..]:[..]) + at BacktraceVerbosityTest.testRevert (test/BacktraceVerbosity.t.sol:[..]:[..]) + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); diff --git a/crates/forge/tests/cli/bind.rs b/crates/forge/tests/cli/bind.rs new file mode 100644 index 0000000000000..fa26c1afdf838 --- /dev/null +++ b/crates/forge/tests/cli/bind.rs @@ -0,0 +1,26 @@ +// +forgetest!(bind_unlinked_bytecode, |prj, cmd| { + prj.add_source( + "SomeLibContract.sol", + r#" +library SomeLib { + function add(uint256 a, uint256 b) external pure returns (uint256) { + return a + b; + } +} + +contract SomeLibContract { + function add(uint256 a, uint256 b) public pure returns (uint256) { + return SomeLib.add(a, b); + } +} + "#, + ); + cmd.args(["bind", "--select", "SomeLibContract"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Generating bindings for 1 contracts +Bindings have been generated to [..] +"#]]); +}); diff --git a/crates/forge/tests/cli/bind_json.rs b/crates/forge/tests/cli/bind_json.rs index fcc081f6b6f06..43c515497acfb 100644 --- a/crates/forge/tests/cli/bind_json.rs +++ b/crates/forge/tests/cli/bind_json.rs @@ -23,7 +23,7 @@ contract BindJsonTest is Test { address addrParam; } - function testTopLevel() public { + function testTopLevel() public pure { string memory json = '{"param1": 1, "param2": -1}'; TopLevelStruct memory topLevel = json.deserializeTopLevelStruct(); assertEq(topLevel.param1, 1); @@ -34,7 +34,7 @@ contract BindJsonTest is Test { assertEq(keccak256(abi.encode(deserialized)), keccak256(abi.encode(topLevel))); } - function testContractLevel() public { + function testContractLevel() public pure { ContractLevelStruct memory contractLevel = ContractLevelStruct({ param1: new address[][](2), addrParam: address(0xBEEF) @@ -48,8 +48,7 @@ contract BindJsonTest is Test { } } "#, - ) - .unwrap(); + ); cmd.arg("bind-json").assert_success(); @@ -70,7 +69,7 @@ interface Vm { function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json); } - +... library JsonBindings { Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 26db462134e1d..8753a7afa5387 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,8 +1,10 @@ use crate::utils::generate_large_init_contract; -use foundry_test_utils::{forgetest, snapbox::IntoData, str}; +use foundry_test_utils::{forgetest, forgetest_init, snapbox::IntoData, str}; use globset::Glob; +use std::fs; forgetest_init!(can_parse_build_filters, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.args(["build", "--names", "--skip", "tests", "scripts"]).assert_success().stdout_eq(str![ @@ -42,11 +44,10 @@ contract Dummy { } } ", - ) - .unwrap(); + ); // set up command - cmd.args(["compile", "--format-json"]).assert_success().stdout_eq(str![[r#" + cmd.args(["compile", "--format-json"]).assert_success().stderr_eq("").stdout_eq(str![[r#" { "errors": [ { @@ -71,7 +72,7 @@ contract Dummy { }); forgetest!(initcode_size_exceeds_limit, |prj, cmd| { - prj.add_source("LargeContract.sol", generate_large_init_contract(50_000).as_str()).unwrap(); + prj.add_source("LargeContract.sol", generate_large_init_contract(50_000).as_str()); cmd.args(["build", "--sizes"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -100,6 +101,16 @@ Compiler run successful! .is_json(), ); + cmd.forge_fuse().args(["build", "--sizes", "--md"]).assert_failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | +|---------------|------------------|-------------------|--------------------|---------------------| +| LargeContract | 62 | 50,125 | 24,514 | -973 | + + +"#]]); + // Ignore EIP-3860 cmd.forge_fuse().args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq( @@ -132,10 +143,24 @@ No files changed, compilation skipped "#]] .is_json(), ); + + cmd.forge_fuse() + .args(["build", "--sizes", "--ignore-eip-3860", "--md"]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | +|---------------|------------------|-------------------|--------------------|---------------------| +| LargeContract | 62 | 50,125 | 24,514 | -973 | + + +"#]]); }); // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -146,6 +171,7 @@ Compiler run successful! // tests build output is as expected forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.solc = Some(foundry_config::SolcReq::Version(semver::Version::new(0, 8, 27))); }); @@ -175,18 +201,28 @@ forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { "#]] .is_json(), ); + + cmd.forge_fuse().args(["build", "--sizes", "--md"]).assert_success().stdout_eq(str![[r#" +... + +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | +|----------|------------------|-------------------|--------------------|---------------------| +| Counter | 481 | 509 | 24,095 | 48,643 | + + +"#]]); }); // tests build output --sizes handles multiple contracts with the same name forgetest_init!(build_sizes_multiple_contracts, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "Foo", r" contract Foo { } ", - ) - .unwrap(); + ); prj.add_source( "a/Counter", @@ -198,8 +234,7 @@ contract Counter { } } ", - ) - .unwrap(); + ); prj.add_source( "b/Counter", @@ -211,8 +246,7 @@ contract Counter { } } ", - ) - .unwrap(); + ); cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" ... @@ -230,19 +264,32 @@ contract Counter { ╰-----------------------------+------------------+-------------------+--------------------+---------------------╯ +"#]]); + + cmd.forge_fuse().args(["build", "--sizes", "--md"]).assert_success().stdout_eq(str![[r#" +... + +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | +|-----------------------------|------------------|-------------------|--------------------|---------------------| +| Counter (src/Counter.sol) | 481 | 509 | 24,095 | 48,643 | +| Counter (src/a/Counter.sol) | 344 | 372 | 24,232 | 48,780 | +| Counter (src/b/Counter.sol) | 291 | 319 | 24,285 | 48,833 | +| Foo | 62 | 88 | 24,514 | 49,064 | + + "#]]); }); // tests build output --sizes --json handles multiple contracts with the same name forgetest_init!(build_sizes_multiple_contracts_json, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "Foo", r" contract Foo { } ", - ) - .unwrap(); + ); prj.add_source( "a/Counter", @@ -254,8 +301,7 @@ contract Counter { } } ", - ) - .unwrap(); + ); prj.add_source( "b/Counter", @@ -267,8 +313,7 @@ contract Counter { } } ", - ) - .unwrap(); + ); cmd.args(["build", "--sizes", "--json"]).assert_success().stdout_eq( str![[r#" @@ -312,16 +357,14 @@ contract InvalidContract { some_invalid_syntax } ", - ) - .unwrap(); + ); prj.add_source( "ValidContract", r" contract ValidContract {} ", - ) - .unwrap(); + ); prj.update_config(|config| { config.skip = vec![Glob::new("src/InvalidContract.sol").unwrap().into()]; @@ -329,3 +372,141 @@ contract ValidContract {} cmd.args(["build"]).assert_success(); }); + +// +forgetest_init!(test_consistent_build_output, |prj, cmd| { + prj.add_source( + "AContract.sol", + r#" +import {B} from "/badpath/B.sol"; + +contract A is B {} + "#, + ); + + prj.add_source( + "CContract.sol", + r#" +import {B} from "badpath/B.sol"; + +contract C is B {} + "#, + ); + + cmd.args(["build", "src/AContract.sol"]).assert_failure().stdout_eq(str![[r#" +... +Unable to resolve imports: + "/badpath/B.sol" in "[..]" +with remappings: + forge-std/=[..] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] + +"#]]); + cmd.forge_fuse().args(["build", "src/CContract.sol"]).assert_failure().stdout_eq(str![[r#" +Unable to resolve imports: + "badpath/B.sol" in "[..]" +with remappings: + forge-std/=[..] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] + +"#]]); +}); + +// +// +forgetest!(build_with_invalid_natspec, |prj, cmd| { + prj.add_source( + "ContractWithInvalidNatspec.sol", + r#" +contract ContractA { + /// @deprecated quoteExactOutputSingle and exactOutput. Use QuoterV2 instead. +} + +/// Some editors highlight `@note` or `@todo` +/// @note foo bar + +/// @title ContractB +contract ContractB { + /** + some example code in a comment: + import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + */ +} + "#, + ); + + cmd.args(["build", "src/ContractWithInvalidNatspec.sol"]).assert_success().stderr_eq(str![[ + r#" +warning: invalid natspec tag '@deprecated', custom tags must use format '@custom:name' + [FILE]:5:5 + │ +5 │ /// @deprecated quoteExactOutputSingle and exactOutput. Use QuoterV2 instead. + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ +... + +warning: invalid natspec tag '@note', custom tags must use format '@custom:name' + [FILE]:9:1 + │ +9 │ /// @note foo bar + │ ━━━━━━━━━━━━━━━━━ + │ +... + +"# + ]]); +}); + +// tests that build succeeds without warning when no soldeer.lock exists +forgetest_init!(build_no_warning_without_soldeer_lock, |prj, cmd| { + let soldeer_lock = prj.root().join("soldeer.lock"); + // soldeer.lock should not exist in a fresh project + assert!(!soldeer_lock.exists()); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +"#]]); +}); + +// tests that malformed foundry.lock triggers a warning during build +forgetest_init!(build_warns_on_malformed_foundry_lock, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + fs::write(&foundry_lock, "this is not valid toml { [ }").unwrap(); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +Warning: Failed to parse foundry.lock: [..] +... +"#]]); +}); + +// tests that build succeeds without warning when no foundry.lock exists +forgetest_init!(build_no_warning_without_foundry_lock, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + // Remove foundry.lock if it exists from template + let _ = fs::remove_file(&foundry_lock); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +"#]]); +}); + +// tests that build warns when foundry.lock revision differs from actual submodule revision +forgetest_init!(build_warns_on_foundry_lock_revision_mismatch, |prj, cmd| { + let foundry_lock = prj.root().join("foundry.lock"); + + // Write a foundry.lock with a fake/old revision for forge-std that differs from the actual + let lockfile_content = r#"{ + "lib/forge-std": { + "tag": { + "name": "v1.9.7", + "rev": "0000000000000000000000000000000000000000" + } + } +}"#; + fs::write(&foundry_lock, lockfile_content).unwrap(); + + cmd.args(["build"]).assert_success().stderr_eq(str![[r#" +Warning: Dependency 'lib/forge-std' revision mismatch: expected '0000000000000000000000000000000000000000', found '[..]' + +"#]]); +}); diff --git a/crates/forge/tests/cli/cache.rs b/crates/forge/tests/cli/cache.rs index b2f6484262bdf..da67af2eecea7 100644 --- a/crates/forge/tests/cli/cache.rs +++ b/crates/forge/tests/cli/cache.rs @@ -16,6 +16,7 @@ forgetest!(can_list_specific_chain, |_prj, cmd| { }); forgetest_init!(can_test_no_cache, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["test", "--no-cache"]).assert_success(); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 3f76a179a0994..35368fbc7e2e0 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1,17 +1,17 @@ //! Contains various tests for checking forge's commands use crate::constants::*; -use foundry_compilers::artifacts::{remappings::Remapping, ConfigurableContractArtifact, Metadata}; +use foundry_compilers::artifacts::{ConfigurableContractArtifact, Metadata, remappings::Remapping}; use foundry_config::{ - parse_with_profile, BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, + BasicConfig, Chain, Config, DenyLevel, FuzzConfig, InvariantConfig, SolidityErrorCode, + parse_with_profile, }; use foundry_test_utils::{ foundry_compilers::PathStyle, rpc::next_etherscan_api_key, snapbox::IntoData, - util::{pretty_err, read_string, OutputExt, TestCommand}, + util::{OutputExt, read_string}, }; -use semver::Version; use std::{ fs, path::Path, @@ -53,6 +53,9 @@ Display options: --json Format log messages as JSON + --md + Format log messages as Markdown + -q, --quiet Do not print log messages @@ -67,9 +70,11 @@ Display options: - 2 (-vv): Print logs for all tests. - 3 (-vvv): Print execution traces for failing tests. - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes + and + backtraces with line numbers. -Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html +Find more information in the book: https://getfoundry.sh/forge/overview "#]]); }); @@ -81,6 +86,40 @@ forgetest!(can_clean_non_existing, |prj, cmd| { prj.assert_cleaned(); }); +// checks that `clean` doesn't output warnings +forgetest_init!(can_clean_without_warnings, |prj, cmd| { + prj.add_source( + "Simple.sol", + r#" +pragma solidity ^0.8.5; + +contract Simple { + uint public value = 42; +} +"#, + ); + + prj.create_file( + "foundry.toml", + r#" +[default] +evm_version = "cancun" +solc = "0.8.5" +"#, + ); + // `forge build` warns + cmd.forge_fuse().arg("build").assert_success().stderr_eq(str![[r#" +Warning: Found unknown config section in foundry.toml: [default] +This notation for profiles has been deprecated and may result in the profile not being registered in future versions. +Please use [profile.default] instead or run `forge config --fix`. + +"#]]); + // `forge clear` should not warn + cmd.forge_fuse().arg("clean").assert_success().stderr_eq(str![[r#" + +"#]]); +}); + // checks that `cache ls` can be invoked and displays the foundry cache forgetest!( #[ignore] @@ -266,7 +305,7 @@ forgetest!(can_init_repo_with_config, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -315,7 +354,7 @@ forgetest!(can_init_no_git, |prj, cmd| { cmd.arg("init").arg(prj.root()).arg("--no-git").assert_success().stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -327,6 +366,20 @@ Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std assert!(!prj.root().join("lib/forge-std/.git").exists()); }); +// Checks that `--no-commit` is accepted as a noop backwards-compatibility flag +forgetest!(can_init_with_no_commit, |prj, cmd| { + prj.wipe(); + + cmd.arg("init").arg(prj.root()).arg("--no-commit").assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); + prj.assert_config_exists(); +}); + // Checks that quiet mode does not print anything forgetest!(can_init_quiet, |prj, cmd| { prj.wipe(); @@ -400,7 +453,7 @@ Run with the `--force` flag to initialize regardless. .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -440,7 +493,7 @@ Run with the `--force` flag to initialize regardless. .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -482,7 +535,7 @@ Run with the `--force` flag to initialize regardless. .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -501,13 +554,60 @@ Warning: Target directory is not empty, but `--force` was specified assert_eq!(gitignore, "not foundry .gitignore"); }); +// `forge init --use-parent-git` works on already initialized git repository +forgetest!(can_init_using_parent_repo, |prj, cmd| { + let root = prj.root(); + + // initialize new git repo + let status = Command::new("git") + .arg("init") + .current_dir(root) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("could not run git init"); + assert!(status.success()); + assert!(root.join(".git").exists()); + + prj.create_file("README.md", "non-empty dir"); + prj.create_file(".gitignore", "not foundry .gitignore"); + + let folder = "foundry-folder"; + cmd.arg("init").arg(folder).arg("--force").arg("--use-parent-git").assert_success().stdout_eq( + str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project + +"#]], + ); + + assert!(root.join(folder).join("lib/forge-std").exists()); + + // not overwritten + let gitignore = root.join(".gitignore"); + let gitignore = fs::read_to_string(gitignore).unwrap(); + assert_eq!(gitignore, "not foundry .gitignore"); + + // submodules are registered at root + let gitmodules = root.join(".gitmodules"); + let gitmodules = fs::read_to_string(gitmodules).unwrap(); + assert!(gitmodules.contains( + " + path = foundry-folder/lib/forge-std + url = https://github.com/foundry-rs/forge-std +" + )); +}); + // Checks that remappings.txt and .vscode/settings.json is generated forgetest!(can_init_vscode, |prj, cmd| { prj.wipe(); cmd.arg("init").arg(prj.root()).arg("--vscode").assert_success().stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -610,7 +710,7 @@ Initializing [..] from https://github.com/foundry-rs/forge-template... }); // checks that clone works -forgetest!(can_clone, |prj, cmd| { +forgetest!(flaky_can_clone, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -627,7 +727,7 @@ forgetest!(can_clone, |prj, cmd| { .stdout_eq(str![[r#" Downloading the source code of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project Collecting the creation information of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... @@ -642,7 +742,7 @@ Compiler run successful! }); // Checks that quiet mode does not print anything for clone -forgetest!(can_clone_quiet, |prj, cmd| { +forgetest!(flaky_can_clone_quiet, |prj, cmd| { prj.wipe(); cmd.args([ @@ -656,8 +756,35 @@ forgetest!(can_clone_quiet, |prj, cmd| { .assert_empty_stdout(); }); +// checks that clone works with sourcify +forgetest!(flaky_can_clone_sourcify, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args(["clone", "--source", "sourcify", "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Downloading the source code of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify... +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project +Collecting the creation information of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + // checks that clone works with --no-remappings-txt -forgetest!(can_clone_no_remappings_txt, |prj, cmd| { +forgetest!(flaky_can_clone_no_remappings_txt, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -675,7 +802,7 @@ forgetest!(can_clone_no_remappings_txt, |prj, cmd| { .stdout_eq(str![[r#" Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project Collecting the creation information of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... @@ -690,7 +817,7 @@ Compiler run successful! }); // checks that clone works with --keep-directory-structure -forgetest!(can_clone_keep_directory_structure, |prj, cmd| { +forgetest!(flaky_can_clone_keep_directory_structure, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -728,9 +855,156 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); +// checks that `forge init` works. +forgetest!(can_init_project, |prj, cmd| { + prj.wipe(); + + cmd.args(["init"]).arg(prj.root()).assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); + + assert!(prj.root().join("foundry.toml").exists()); + assert!(prj.root().join("lib/forge-std").exists()); + + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("src").join("Counter.sol").exists()); + + assert!(prj.root().join("test").exists()); + assert!(prj.root().join("test").join("Counter.t.sol").exists()); + + assert!(prj.root().join("script").exists()); + assert!(prj.root().join("script").join("Counter.s.sol").exists()); + + assert!(prj.root().join(".github").join("workflows").exists()); + assert!(prj.root().join(".github").join("workflows").join("test.yml").exists()); +}); + +// checks that `forge init --vyper` works. +forgetest!(can_init_vyper_project, |prj, cmd| { + prj.wipe(); + + cmd.args(["init", "--vyper"]).arg(prj.root()).assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); + + assert!(prj.root().join("foundry.toml").exists()); + assert!(prj.root().join("lib/forge-std").exists()); + + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("src").join("Counter.vy").exists()); + assert!(prj.root().join("src").join("ICounter.sol").exists()); + + assert!(prj.root().join("test").exists()); + assert!(prj.root().join("test").join("Counter.t.sol").exists()); + + assert!(prj.root().join("script").exists()); + assert!(prj.root().join("script").join("Counter.s.sol").exists()); + + assert!(prj.root().join(".github").join("workflows").exists()); + assert!(prj.root().join(".github").join("workflows").join("test.yml").exists()); +}); + +// checks that `forge init --network tempo` works. +forgetest!(can_init_tempo_project, |prj, cmd| { + prj.wipe(); + + cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success().stdout_eq(str![[ + r#" +Initializing [..]... +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] +Installing tempo-std in [..] (url: https://github.com/tempoxyz/tempo-std, tag: None) + Installed tempo-std[..] + Initialized forge project + +"# + ]]); + + assert!(prj.root().join("foundry.toml").exists()); + + // Verify foundry.toml contains `network = "tempo"` so subsequent commands auto-detect the + // network. + let foundry_toml = std::fs::read_to_string(prj.root().join("foundry.toml")).unwrap(); + assert!( + foundry_toml.contains("network = \"tempo\""), + "foundry.toml should contain `network = \"tempo\"`, got:\n{foundry_toml}" + ); + assert!( + foundry_toml.contains("[rpc_endpoints]") + && foundry_toml.contains("tempo = \"https://rpc.tempo.xyz/\"") + && foundry_toml.contains("moderato = \"https://rpc.moderato.tempo.xyz/\""), + "foundry.toml should contain tempo rpc_endpoints, got:\n{foundry_toml}" + ); + + assert!(prj.root().join("lib/forge-std").exists()); + assert!(prj.root().join("lib/tempo-std").exists()); + + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("src").join("Mail.sol").exists()); + + assert!(prj.root().join("test").exists()); + assert!(prj.root().join("test").join("Mail.t.sol").exists()); + + assert!(prj.root().join("script").exists()); + assert!(prj.root().join("script").join("Mail.s.sol").exists()); + + assert!(prj.root().join(".github").join("workflows").exists()); + assert!(prj.root().join(".github").join("workflows").join("test.yml").exists()); + + assert!(prj.root().join("README.md").exists()); +}); + +// checks that `forge init --network tempo` correctly setup network key in config +forgetest!(can_execute_test_and_script_with_default_tempo_config, |prj, cmd| { + prj.wipe(); + + // Initialize a Tempo project. + cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success(); + + // Run tests, Tempo EVM selection is made by reading foundry.toml config + cmd.forge_fuse().arg("test").arg("--root").arg(prj.root()).assert_success(); + + // Same for script + cmd.forge_fuse() + .arg("script") + .arg("script/Mail.s.sol") + .arg("temposalt") + .arg("--tempo") + .arg("--root") + .arg(prj.root()) + .assert_success(); +}); + +// checks that `forge test --tempo` and `forge test -n tempo` are equivalent +forgetest!(network_flag_tempo_equivalent_to_legacy_tempo, |prj, cmd| { + prj.wipe(); + cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success(); + + // --network tempo (new flag) + cmd.forge_fuse() + .args(["test", "--network", "tempo"]) + .arg("--root") + .arg(prj.root()) + .assert_success(); + + // -n tempo (short form) + cmd.forge_fuse().args(["test", "-n", "tempo"]).arg("--root").arg(prj.root()).assert_success(); + + // --tempo (legacy flag) + cmd.forge_fuse().args(["test", "--tempo"]).arg("--root").arg(prj.root()).assert_success(); +}); + // checks that clone works with raw src containing `node_modules` // -forgetest!(can_clone_with_node_modules, |prj, cmd| { +forgetest!(flaky_can_clone_with_node_modules, |prj, cmd| { prj.wipe(); let foundry_toml = prj.root().join(Config::FILE_NAME); @@ -747,7 +1021,7 @@ forgetest!(can_clone_with_node_modules, |prj, cmd| { .stdout_eq(str![[r#" Downloading the source code of 0xA3E217869460bEf59A1CfD0637e2875F9331e823 from Etherscan... Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project Collecting the creation information of 0xA3E217869460bEf59A1CfD0637e2875F9331e823 from Etherscan... @@ -778,6 +1052,7 @@ forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| { // checks that `clean` also works with the "out" value set in Config forgetest_init!(can_clean_config, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| config.out = "custom-out".into()); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -796,6 +1071,7 @@ Compiler run successful! // checks that `clean` removes fuzz and invariant cache dirs forgetest_init!(can_clean_test_cache, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.fuzz = FuzzConfig::new("cache/fuzz".into()); config.invariant = InvariantConfig::new("cache/invariant".into()); @@ -816,6 +1092,7 @@ forgetest_init!(can_clean_test_cache, |prj, cmd| { // checks that extra output works forgetest_init!(can_emit_extra_output, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.args(["build", "--extra-output", "metadata"]).assert_success().stdout_eq(str![[r#" @@ -848,6 +1125,7 @@ Compiler run successful! // checks that extra output works forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args([ "build", "--extra-output", @@ -920,8 +1198,7 @@ contract Greeter { } } ", - ) - .unwrap(); + ); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -969,8 +1246,7 @@ contract Foo { } } "#, - ) - .unwrap(); + ); prj.add_source( "FooLib", @@ -981,8 +1257,7 @@ library FooLib { function check2(Foo f) public {} } "#, - ) - .unwrap(); + ); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -995,10 +1270,9 @@ Compiler run successful! // tests that the `inspect` command works correctly forgetest!(can_execute_inspect_command, |prj, cmd| { let contract_name = "Foo"; - let path = prj - .add_source( - contract_name, - r#" + let path = prj.add_source( + contract_name, + r#" contract Foo { event log_string(string); function run() external { @@ -1006,8 +1280,7 @@ contract Foo { } } "#, - ) - .unwrap(); + ); cmd.arg("inspect").arg(contract_name).arg("bytecode").assert_success().stdout_eq(str![[r#" 0x60806040[..] @@ -1023,6 +1296,99 @@ contract Foo { ]]); }); +forgetest!(can_inspect_linearization_markdown, |prj, cmd| { + prj.add_source("A.sol", "contract A {}"); + prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#); + prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#); + + cmd.forge_fuse().args(["inspect", "C", "linearization", "--md"]).assert_success().stdout_eq( + str![[r#" + +| Order | Source | Contract | +|-------|-----------|----------| +| 0 | src/C.sol | C | +| 1 | src/B.sol | B | +| 2 | src/A.sol | A | + + +"#]], + ); +}); + +forgetest!(can_inspect_linearization_json, |prj, cmd| { + prj.add_source("A.sol", "contract A {}"); + prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#); + prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#); + + cmd.forge_fuse().args(["inspect", "C", "linearization", "--json"]).assert_success().stdout_eq( + str![[r#" +[ + { + "order": 0, + "source": "src/C.sol", + "contract": "C" + }, + { + "order": 1, + "source": "src/B.sol", + "contract": "B" + }, + { + "order": 2, + "source": "src/A.sol", + "contract": "A" + } +] + +"#]], + ); +}); + +forgetest!(can_inspect_linearization_path_qualified_contract, |prj, cmd| { + prj.add_source("one/Base.sol", "contract Base {}"); + prj.add_source( + "one/Target.sol", + r#"import {Base} from "./Base.sol"; contract Target is Base {}"#, + ); + + prj.add_source("two/Base.sol", "contract Base {}"); + prj.add_source( + "two/Target.sol", + r#"import {Base} from "./Base.sol"; contract Target is Base {}"#, + ); + + cmd.forge_fuse() + .args(["inspect", "src/two/Target.sol:Target", "linearization", "--json"]) + .assert_success() + .stdout_eq(str![[r#" +[ + { + "order": 0, + "source": "src/two/Target.sol", + "contract": "Target" + }, + { + "order": 1, + "source": "src/two/Base.sol", + "contract": "Base" + } +] + +"#]]); +}); + +forgetest!(cannot_inspect_linearization_non_solidity_target, |prj, cmd| { + prj.create_file("src/NotSol.vy", "x: uint256"); + + cmd.forge_fuse() + .args(["inspect", "src/NotSol.vy", "linearization"]) + .assert_failure() + .stderr_eq(str![[r#" +Error: linearization inspection is only supported for Solidity contracts (.sol targets) + +"#]]); +}); + // test that `forge snapshot` commands work forgetest!(can_check_snapshot, |prj, cmd| { prj.insert_ds_test(); @@ -1037,8 +1403,7 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["snapshot"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1067,7 +1432,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // test that `forge build` does not print `(with warnings)` if file path is ignored forgetest!(can_compile_without_warnings_ignored_file_paths, |prj, cmd| { - // Ignoring path and setting empty error_codes as default would set would set some error codes + // Ignoring path and setting empty error_codes as default would set some error codes prj.update_config(|config| { config.ignored_file_paths = vec![Path::new("src").to_path_buf()]; config.ignored_error_codes = vec![]; @@ -1081,8 +1446,7 @@ contract A { function testExample() public {} } ", - ) - .unwrap(); + ); cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1119,8 +1483,7 @@ contract A { function testExample() public {} } ", - ) - .unwrap(); + ); cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1151,7 +1514,7 @@ Warning: SPDX license identifier not provided in source file. Before publishing, forgetest!(can_fail_compile_with_warnings, |prj, cmd| { prj.update_config(|config| { config.ignored_error_codes = vec![]; - config.deny_warnings = false; + config.deny = DenyLevel::Never; }); prj.add_raw_source( "A", @@ -1161,8 +1524,7 @@ contract A { function testExample() public {} } ", - ) - .unwrap(); + ); // there are no errors cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" @@ -1179,7 +1541,7 @@ Warning: SPDX license identifier not provided in source file. Before publishing, // warning fails to compile prj.update_config(|config| { config.ignored_error_codes = vec![]; - config.deny_warnings = true; + config.deny = DenyLevel::Warnings; }); cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#" @@ -1193,7 +1555,7 @@ Warning: SPDX license identifier not provided in source file. Before publishing, // ignores error code and compiles prj.update_config(|config| { config.ignored_error_codes = vec![SolidityErrorCode::SpdxLicenseNotProvided]; - config.deny_warnings = true; + config.deny = DenyLevel::Warnings; }); cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" @@ -1201,6 +1563,51 @@ Warning: SPDX license identifier not provided in source file. Before publishing, [SOLC_VERSION] [ELAPSED] Compiler run successful! +"#]]); +}); + +// test that `forge build` ignores error codes only from matching path prefixes +forgetest!(can_compile_without_warnings_ignored_error_codes_from, |prj, cmd| { + let contract = r" +pragma solidity *; +contract A {} + "; + prj.add_raw_source("A", contract); + prj.add_raw_test("A", contract); + + // suppressed for both src and test + prj.update_config(|config| { + config.ignored_error_codes = vec![]; + config.ignored_error_codes_from = vec![ + (std::path::PathBuf::from("src"), vec![SolidityErrorCode::SpdxLicenseNotProvided]), + (std::path::PathBuf::from("test"), vec![SolidityErrorCode::SpdxLicenseNotProvided]), + ]; + }); + + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + // suppressed only for test, src warning still shows + prj.update_config(|config| { + config.ignored_error_codes_from = vec![( + std::path::PathBuf::from("test"), + vec![SolidityErrorCode::SpdxLicenseNotProvided], + )]; + }); + + cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +[FILE] + + "#]]); }); @@ -1208,6 +1615,11 @@ Compiler run successful! forgetest!(can_build_after_failure, |prj, cmd| { prj.insert_ds_test(); + // Disable linting during build to avoid linting output interfering with test assertions + prj.update_config(|config| { + config.lint.lint_on_build = false; + }); + prj.add_source( "ATest.t.sol", r#" @@ -1218,8 +1630,7 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); prj.add_source( "BTest.t.sol", r#" @@ -1230,8 +1641,7 @@ contract BTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1254,7 +1664,7 @@ contract CTest is DSTest { "#; // introduce contract with syntax error - prj.add_source("CTest.t.sol", syntax_err).unwrap(); + prj.add_source("CTest.t.sol", syntax_err); // `forge build --force` which should fail cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#" @@ -1293,8 +1703,7 @@ contract CTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1310,7 +1719,7 @@ Compiler run successful! let cache = fs::read_to_string(prj.cache()).unwrap(); // introduce the error again but building without force - prj.add_source("CTest.t.sol", syntax_err).unwrap(); + prj.add_source("CTest.t.sol", syntax_err); cmd.forge_fuse().arg("build").assert_failure().stderr_eq(str![[r#" Error: Compiler run failed: Error (2314): Expected ';' but got identifier @@ -1326,209 +1735,6 @@ Error (2314): Expected ';' but got identifier assert_eq!(cache, cache_after); }); -// test to check that install/remove works properly -forgetest!(can_install_and_remove, |prj, cmd| { - cmd.git_init(); - - let libs = prj.root().join("lib"); - let git_mod = prj.root().join(".git/modules/lib"); - let git_mod_file = prj.root().join(".gitmodules"); - - let forge_std = libs.join("forge-std"); - let forge_std_mod = git_mod.join("forge-std"); - - let install = |cmd: &mut TestCommand| { - cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( - str![[r#" -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) - Installed forge-std[..] - -"#]], - ); - - assert!(forge_std.exists()); - assert!(forge_std_mod.exists()); - - let submods = read_string(&git_mod_file); - assert!(submods.contains("https://github.com/foundry-rs/forge-std")); - }; - - let remove = |cmd: &mut TestCommand, target: &str| { - // TODO: flaky behavior with URL, sometimes it is None, sometimes it is Some("https://github.com/lib/forge-std") - cmd.forge_fuse().args(["remove", "--force", target]).assert_success().stdout_eq(str![[ - r#" -Removing 'forge-std' in [..], (url: [..], tag: None) - -"# - ]]); - - assert!(!forge_std.exists()); - assert!(!forge_std_mod.exists()); - let submods = read_string(&git_mod_file); - assert!(!submods.contains("https://github.com/foundry-rs/forge-std")); - }; - - install(&mut cmd); - remove(&mut cmd, "forge-std"); - - // install again and remove via relative path - install(&mut cmd); - remove(&mut cmd, "lib/forge-std"); -}); - -// test to check we can run `forge install` in an empty dir -forgetest!(can_install_empty, |prj, cmd| { - // create - cmd.git_init(); - cmd.forge_fuse().args(["install"]); - cmd.assert_empty_stdout(); - - // create initial commit - fs::write(prj.root().join("README.md"), "Initial commit").unwrap(); - - cmd.git_add(); - cmd.git_commit("Initial commit"); - - cmd.forge_fuse().args(["install"]); - cmd.assert_empty_stdout(); -}); - -// test to check that package can be reinstalled after manually removing the directory -forgetest!(can_reinstall_after_manual_remove, |prj, cmd| { - cmd.git_init(); - - let libs = prj.root().join("lib"); - let git_mod = prj.root().join(".git/modules/lib"); - let git_mod_file = prj.root().join(".gitmodules"); - - let forge_std = libs.join("forge-std"); - let forge_std_mod = git_mod.join("forge-std"); - - let install = |cmd: &mut TestCommand| { - cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( - str![[r#" -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) - Installed forge-std[..] - -"#]], - ); - - assert!(forge_std.exists()); - assert!(forge_std_mod.exists()); - - let submods = read_string(&git_mod_file); - assert!(submods.contains("https://github.com/foundry-rs/forge-std")); - }; - - install(&mut cmd); - fs::remove_dir_all(forge_std.clone()).expect("Failed to remove forge-std"); - - // install again - install(&mut cmd); -}); - -// test that we can repeatedly install the same dependency without changes -forgetest!(can_install_repeatedly, |_prj, cmd| { - cmd.git_init(); - - cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]); - for _ in 0..3 { - cmd.assert_success(); - } -}); - -// test that by default we install the latest semver release tag -// -forgetest!(can_install_latest_release_tag, |prj, cmd| { - cmd.git_init(); - cmd.forge_fuse().args(["install", "openzeppelin/openzeppelin-contracts"]); - cmd.assert_success(); - - let dep = prj.paths().libraries[0].join("openzeppelin-contracts"); - assert!(dep.exists()); - - // the latest release at the time this test was written - let version: Version = "4.8.0".parse().unwrap(); - let out = Command::new("git").current_dir(&dep).args(["describe", "--tags"]).output().unwrap(); - let tag = String::from_utf8_lossy(&out.stdout); - let current: Version = tag.as_ref().trim_start_matches('v').trim().parse().unwrap(); - - assert!(current >= version); -}); - -// Tests that forge update doesn't break a working dependency by recursively updating nested -// dependencies -forgetest!( - #[cfg_attr(windows, ignore = "weird git fail")] - can_update_library_with_outdated_nested_dependency, - |prj, cmd| { - cmd.git_init(); - - let libs = prj.root().join("lib"); - let git_mod = prj.root().join(".git/modules/lib"); - let git_mod_file = prj.root().join(".gitmodules"); - - // get paths to check inside install fn - let package = libs.join("forge-5980-test"); - let package_mod = git_mod.join("forge-5980-test"); - - // install main dependency - cmd.forge_fuse() - .args(["install", "evalir/forge-5980-test"]) - .assert_success() - .stdout_eq(str![[r#" -Installing forge-5980-test in [..] (url: Some("https://github.com/evalir/forge-5980-test"), tag: None) - Installed forge-5980-test - -"#]]); - - // assert paths exist - assert!(package.exists()); - assert!(package_mod.exists()); - - let submods = read_string(git_mod_file); - assert!(submods.contains("https://github.com/evalir/forge-5980-test")); - - // try to update the top-level dependency; there should be no update for this dependency, - // but its sub-dependency has upstream (breaking) changes; forge should not attempt to - // update the sub-dependency - cmd.forge_fuse().args(["update", "lib/forge-5980-test"]).assert_empty_stdout(); - - // add explicit remappings for test file - prj.update_config(|config| { - config.remappings = vec![ - Remapping::from_str("forge-5980-test/=lib/forge-5980-test/src/").unwrap().into(), - // explicit remapping for sub-dependendy seems necessary for some reason - Remapping::from_str( - "forge-5980-test-dep/=lib/forge-5980-test/lib/forge-5980-test-dep/src/", - ) - .unwrap() - .into(), - ]; - }); - - // create test file that uses the top-level dependency; if the sub-dependency is updated, - // compilation will fail - prj.add_source( - "CounterCopy", - r#" -import "forge-5980-test/Counter.sol"; -contract CounterCopy is Counter { -} - "#, - ) - .unwrap(); - - // build and check output - cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -"#]]); - } -); - const GAS_REPORT_CONTRACTS: &str = r#" //SPDX-license-identifier: MIT @@ -1616,7 +1822,7 @@ contract ContractThreeTest is DSTest { forgetest!(gas_report_all_contracts, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS); // report for all prj.update_config(|config| { @@ -1631,13 +1837,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1645,13 +1851,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1659,13 +1865,13 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1678,7 +1884,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1694,7 +1900,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -1710,7 +1916,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1736,13 +1942,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1750,13 +1956,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1764,13 +1970,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1783,7 +1989,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1799,7 +2005,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -1815,7 +2021,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1841,13 +2047,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1855,13 +2061,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1869,13 +2075,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1888,7 +2094,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1904,7 +2110,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -1920,7 +2126,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -1949,13 +2155,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -1963,13 +2169,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1977,13 +2183,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -1996,7 +2202,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2012,7 +2218,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2028,7 +2234,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2049,7 +2255,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) forgetest!(gas_report_some_contracts, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS); // report for One prj.update_config(|config| config.gas_reports = vec!["ContractOne".to_string()]); @@ -2061,13 +2267,13 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2080,7 +2286,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2108,13 +2314,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2127,7 +2333,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2155,13 +2361,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2174,7 +2380,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2195,7 +2401,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS); // ignore ContractOne prj.update_config(|config| { @@ -2210,13 +2416,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2224,13 +2430,13 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2243,7 +2449,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2259,7 +2465,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2291,13 +2497,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2305,13 +2511,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ @@ -2324,7 +2530,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2340,7 +2546,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2380,13 +2586,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| foo | 45656 | 45656 | 45656 | 45656 | 1 | +| foo | 45656 | 45656 | 45656 | 45656 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -2394,13 +2600,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| -| 133243 | 395 | | | | | +| 133219 | 395 | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+--------+--------+--------+---------| -| baz | 287711 | 287711 | 287711 | 287711 | 1 | +| baz | 287711 | 287711 | 287711 | 287711 | 1 | ╰------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -2408,13 +2614,13 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| -| 133027 | 394 | | | | | +| 133015 | 394 | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+-------+--------+-------+---------| -| bar | 67683 | 67683 | 67683 | 67683 | 1 | +| bar | 67683 | 67683 | 67683 | 67683 | 1 | ╰----------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2437,7 +2643,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractOne", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2453,7 +2659,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractThree", "deployment": { - "gas": 133243, + "gas": 133219, "size": 395 }, "functions": { @@ -2469,7 +2675,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. { "contract": "src/Contracts.sol:ContractTwo", "deployment": { - "gas": 133027, + "gas": 133015, "size": 394 }, "functions": { @@ -2511,8 +2717,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterTest.t.sol", @@ -2535,8 +2740,7 @@ contract CounterTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... @@ -2545,19 +2749,19 @@ contract CounterTest is DSTest { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 172107 | 578 | | | | | +| 172107 | 578 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| a | 2402 | 2402 | 2402 | 2402 | 1 | +| a | 2402 | 2402 | 2402 | 2402 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| b | 2447 | 2447 | 2447 | 2447 | 1 | +| b | 2447 | 2447 | 2447 | 2447 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | +| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | +| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2612,6 +2816,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(gas_report_with_fallback, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "DelegateProxyTest.sol", r#" @@ -2660,8 +2865,7 @@ contract GasReportFallbackTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_fallback_gas_report", "-vvvv", "--gas-report"]) .assert_success() @@ -2672,15 +2876,15 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| 117171 | 471 | | | | | +| 117171 | 471 | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |---------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| deposit | 21185 | 21185 | 21185 | 21185 | 1 | +| deposit | 21185 | 21185 | 21185 | 21185 | 1 | |---------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 29758 | 29758 | 29758 | 29758 | 1 | +| fallback | 29758 | 29758 | 29758 | 29758 | 1 | ╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2688,13 +2892,13 @@ contract GasReportFallbackTest is Test { +========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| 153531 | 494 | | | | | +| 153531 | 494 | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |-----------------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------+-----------------+------+--------+------+---------| -| deposit | 3661 | 3661 | 3661 | 3661 | 1 | +| deposit | 3661 | 3661 | 3661 | 3661 | 1 | ╰-----------------------------------------------------+-----------------+------+--------+------+---------╯ @@ -2754,7 +2958,8 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); // -forgetest_init!(gas_report_fallback_with_calldata, |prj, cmd| { +forgetest_init!(flaky_gas_report_fallback_with_calldata, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "FallbackWithCalldataTest.sol", r#" @@ -2785,8 +2990,7 @@ contract CounterWithFallbackTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_fallback_with_calldata", "-vvvv", "--gas-report"]) .assert_success() @@ -2810,13 +3014,13 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +=====================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 132471 | 396 | | | | | +| 132471 | 396 | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| fallback | 43461 | 43461 | 43461 | 43461 | 1 | +| fallback | 43461 | 43461 | 43461 | 43461 | 1 | ╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯ @@ -2854,6 +3058,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(gas_report_size_for_nested_create, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "NestedDeployTest.sol", r#" @@ -2883,8 +3088,7 @@ contract NestedDeploy is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_nested_create_gas_report", "--gas-report"]) .assert_success() @@ -2895,13 +3099,13 @@ contract NestedDeploy is Test { +======================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| 0 | 132 | | | | | +| 0 | 132 | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |-------------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------------+-----------------+-------+--------+-------+---------| -| w | 21185 | 21185 | 21185 | 21185 | 1 | +| w | 21185 | 21185 | 21185 | 21185 | 1 | ╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭------------------------------------------+-----------------+------+--------+------+---------╮ @@ -2909,13 +3113,13 @@ contract NestedDeploy is Test { +=============================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------+-----------------+------+--------+------+---------| -| 0 | 731 | | | | | +| 0 | 731 | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | | | | | | | |------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |------------------------------------------+-----------------+------+--------+------+---------| -| child | 2681 | 2681 | 2681 | 2681 | 1 | +| child | 2681 | 2681 | 2681 | 2681 | 1 | ╰------------------------------------------+-----------------+------+--------+------+---------╯ ╭-------------------------------------------+-----------------+-----+--------+-----+---------╮ @@ -2923,13 +3127,13 @@ contract NestedDeploy is Test { +============================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| 328961 | 1163 | | | | | +| 328949 | 1163 | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | | | | | | | |-------------------------------------------+-----------------+-----+--------+-----+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------+-----------------+-----+--------+-----+---------| -| child | 525 | 525 | 525 | 525 | 1 | +| child | 525 | 525 | 525 | 525 | 1 | ╰-------------------------------------------+-----------------+-----+--------+-----+---------╯ @@ -2978,7 +3182,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/NestedDeployTest.sol:Parent", "deployment": { - "gas": 328961, + "gas": 328949, "size": 1163 }, "functions": { @@ -2998,12 +3202,12 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); forgetest_init!(can_use_absolute_imports, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { let remapping = prj.paths().libraries[0].join("myDependency"); - config.remappings = - vec![Remapping::from_str(&format!("myDependency/={}", remapping.display())) - .unwrap() - .into()]; + config.remappings = vec![ + Remapping::from_str(&format!("myDependency/={}", remapping.display())).unwrap().into(), + ]; }); prj.add_lib( @@ -3012,28 +3216,27 @@ forgetest_init!(can_use_absolute_imports, |prj, cmd| { interface IConfig {} ", - ) - .unwrap(); + ); prj.add_lib( "myDependency/src/Config.sol", r#" - import "src/interfaces/IConfig.sol"; + import {IConfig} from "myDependency/src/interfaces/IConfig.sol"; - contract Config {} + contract Config is IConfig {} "#, - ) - .unwrap(); + ); prj.add_source( "Greeter", r#" - import "myDependency/src/Config.sol"; + import {Config} from "myDependency/src/Config.sol"; - contract Greeter {} + contract Greeter { + Config config; + } "#, - ) - .unwrap(); + ); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3045,13 +3248,13 @@ Compiler run successful! // forgetest_init!(can_use_absolute_imports_from_test_and_script, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_script( "IMyScript.sol", r" interface IMyScript {} ", - ) - .unwrap(); + ); prj.add_script( "MyScript.sol", @@ -3060,16 +3263,14 @@ import "script/IMyScript.sol"; contract MyScript is IMyScript {} "#, - ) - .unwrap(); + ); prj.add_test( "IMyTest.sol", r" interface IMyTest {} ", - ) - .unwrap(); + ); prj.add_test( "MyTest.sol", @@ -3078,8 +3279,7 @@ import "test/IMyTest.sol"; contract MyTest is IMyTest {} "#, - ) - .unwrap(); + ); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3090,7 +3290,8 @@ Compiler run successful! }); // checks `forge inspect irOptimized works -forgetest_init!(can_inspect_ir_optimized, |_prj, cmd| { +forgetest_init!(can_inspect_ir_optimized, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", TEMPLATE_CONTRACT, "irOptimized"]); cmd.assert_success().stdout_eq(str![[r#" /// @use-src 0:"src/Counter.sol" @@ -3115,7 +3316,8 @@ object "Counter_21" { }); // checks `forge inspect irOptimized works -forgetest_init!(can_inspect_ir, |_prj, cmd| { +forgetest_init!(can_inspect_ir, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", TEMPLATE_CONTRACT, "ir"]); cmd.assert_success().stdout_eq(str![[r#" @@ -3141,6 +3343,7 @@ object "Counter_21" { // checks forge bind works correctly on the default project forgetest_init!(can_bind, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.arg("bind").assert_success().stdout_eq(str![[r#" @@ -3153,60 +3356,9 @@ Bindings have been generated to [..] "#]]); }); -// checks missing dependencies are auto installed -forgetest_init!(can_install_missing_deps_test, |prj, cmd| { - prj.clear(); - - // wipe forge-std - let forge_std_dir = prj.root().join("lib/forge-std"); - pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); - - cmd.arg("test").assert_success().stdout_eq(str![[r#" -Missing dependencies found. Installing now... - -[UPDATING_DEPENDENCIES] -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 2 tests for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) -[PASS] test_Increment() ([GAS]) -Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) - -"#]]); -}); - -// checks missing dependencies are auto installed -forgetest_init!(can_install_missing_deps_build, |prj, cmd| { - prj.clear(); - - // wipe forge-std - let forge_std_dir = prj.root().join("lib/forge-std"); - pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); - - // Build the project - cmd.arg("build").assert_success().stdout_eq(str![[r#" -Missing dependencies found. Installing now... - -[UPDATING_DEPENDENCIES] -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -"#]]); - - // Expect compilation to be skipped as no files have changed - cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -"#]]); -}); - // checks that extra output works forgetest_init!(can_build_skip_contracts, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); // Only builds the single template contract `src/*` @@ -3227,14 +3379,14 @@ No files changed, compilation skipped }); forgetest_init!(can_build_skip_glob, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "Foo", r" contract TestDemo { function test_run() external {} }", - ) - .unwrap(); + ); // only builds the single template contract `src/*` even if `*.t.sol` or `.s.sol` is absent prj.clear(); @@ -3258,32 +3410,28 @@ Compiler run successful! "#]]); }); -forgetest_init!(can_build_specific_paths, |prj, cmd| { - prj.wipe(); +forgetest!(can_build_specific_paths, |prj, cmd| { prj.add_source( "Counter.sol", r" contract Counter { function count() external {} }", - ) - .unwrap(); + ); prj.add_test( "Foo.sol", r" contract Foo { function test_foo() external {} }", - ) - .unwrap(); + ); prj.add_test( "Bar.sol", r" contract Bar { function test_bar() external {} }", - ) - .unwrap(); + ); // Build 2 files within test dir prj.clear(); @@ -3335,6 +3483,7 @@ Error: No source files found in specified build paths. // checks that build --sizes includes all contracts even if unchanged forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" @@ -3346,6 +3495,15 @@ forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { ╰----------+------------------+-------------------+--------------------+---------------------╯ +"#]]); + + cmd.forge_fuse().args(["build", "--sizes", "--md"]).assert_success().stdout_eq(str![[r#" +... +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | +|----------|------------------|-------------------|--------------------|---------------------| +| Counter | 481 | 509 | 24,095 | 48,643 | + + "#]]); cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_success().stdout_eq( @@ -3365,6 +3523,7 @@ forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { // checks that build --names includes all contracts even if unchanged forgetest_init!(can_build_names_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["build", "--names"]).assert_success().stdout_eq(str![[r#" @@ -3384,6 +3543,7 @@ Compiler run successful! }); forgetest_init!(can_inspect_counter_pretty, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#" ╭----------+---------------------------------+------------╮ @@ -3461,7 +3621,7 @@ const ANOTHER_COUNTER: &str = r#" } "#; forgetest!(inspect_custom_counter_abi, |prj, cmd| { - prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + prj.add_source("Counter.sol", CUSTOM_COUNTER); cmd.args(["inspect", "Counter", "abi"]).assert_success().stdout_eq(str![[r#" @@ -3502,7 +3662,7 @@ forgetest!(inspect_custom_counter_abi, |prj, cmd| { }); forgetest!(inspect_custom_counter_events, |prj, cmd| { - prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + prj.add_source("Counter.sol", CUSTOM_COUNTER); cmd.args(["inspect", "Counter", "events"]).assert_success().stdout_eq(str![[r#" @@ -3519,7 +3679,7 @@ forgetest!(inspect_custom_counter_events, |prj, cmd| { }); forgetest!(inspect_custom_counter_errors, |prj, cmd| { - prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + prj.add_source("Counter.sol", CUSTOM_COUNTER); cmd.args(["inspect", "Counter", "errors"]).assert_success().stdout_eq(str![[r#" @@ -3536,7 +3696,7 @@ forgetest!(inspect_custom_counter_errors, |prj, cmd| { }); forgetest!(inspect_path_only_identifier, |prj, cmd| { - prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + prj.add_source("Counter.sol", CUSTOM_COUNTER); cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_success().stdout_eq(str![[r#" @@ -3554,7 +3714,7 @@ forgetest!(inspect_path_only_identifier, |prj, cmd| { forgetest!(test_inspect_contract_with_same_name, |prj, cmd| { let source = format!("{CUSTOM_COUNTER}\n{ANOTHER_COUNTER}"); - prj.add_source("Counter.sol", &source).unwrap(); + prj.add_source("Counter.sol", &source); cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_failure().stderr_eq(str![[r#"Error: Multiple contracts found in the same file, please specify the target : or [..]"#]]); @@ -3579,10 +3739,9 @@ forgetest!(inspect_multiple_contracts_with_different_paths, |prj, cmd| { r#" contract Source { function foo() public {} - } + } "#, - ) - .unwrap(); + ); prj.add_source( "another/Source.sol", @@ -3591,8 +3750,7 @@ forgetest!(inspect_multiple_contracts_with_different_paths, |prj, cmd| { function bar() public {} } "#, - ) - .unwrap(); + ); cmd.args(["inspect", "src/another/Source.sol:Source", "methodIdentifiers"]) .assert_success() @@ -3609,7 +3767,7 @@ forgetest!(inspect_multiple_contracts_with_different_paths, |prj, cmd| { }); forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| { - prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + prj.add_source("Counter.sol", CUSTOM_COUNTER); cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#" @@ -3632,10 +3790,56 @@ forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| { ╰----------------------------+------------╯ +"#]]); +}); + +const CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS: &str = r#" +contract Counter { + struct BigStruct { + uint256 a; + uint256 b; + uint256 c; + uint256 d; + uint256 e; + uint256 f; + } + + struct NestedBigStruct { + BigStruct a; + BigStruct b; + BigStruct c; + } + + function hugeIdentifier(NestedBigStruct[] calldata _bigStructs, NestedBigStruct calldata _bigStruct) external {} +} +"#; + +forgetest!(inspect_custom_counter_very_huge_method_identifiers_unwrapped, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS); + + cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#" + +╭-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╮ +| Method | Identifier | ++================================================================================================================================================================================================================================================================================================================================================+ +| hugeIdentifier(((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))[],((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))) | f38dafbb | +╰-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╯ + + +"#]]); + + cmd.forge_fuse().args(["inspect", "Counter", "method-identifiers", "--md"]).assert_success().stdout_eq(str![[r#" + +| Method | Identifier | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| hugeIdentifier(((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))[],((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))) | f38dafbb | + + "#]]); }); forgetest_init!(can_inspect_standard_json, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", "src/Counter.sol:Counter", "standard-json"]).assert_success().stdout_eq(str![[r#" { "language": "Solidity", @@ -3673,7 +3877,7 @@ forgetest_init!(can_inspect_standard_json, |prj, cmd| { ] } }, - "evmVersion": "cancun", + "evmVersion": "osaka", "viaIR": false, "libraries": {} } @@ -3682,8 +3886,45 @@ forgetest_init!(can_inspect_standard_json, |prj, cmd| { "#]]); }); +forgetest_init!(can_inspect_libraries, |prj, cmd| { + prj.initialize_default_contracts(); + prj.add_source( + "Source.sol", + r#" + import "./Lib.sol"; + + library Lib2 { + function foo() public {} + } + + contract Source { + function foo() public { + Lib.foo(); + Lib2.foo(); + } + }"#, + ); + + prj.add_source( + "Lib.sol", + r#" + library Lib { + function foo() public {} + } + "#, + ); + + cmd.args(["inspect", "Source", "libraries"]).assert_success().stdout_eq(str![[r#" +Dynamically linked libraries: + src/Lib.sol:Lib + src/Source.sol:Lib2 + +"#]]); +}); + // checks that `clean` also works with the "out" value set in Config forgetest_init!(gas_report_include_tests, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.gas_reports_include_tests = true; config.fuzz.runs = 1; @@ -3698,17 +3939,17 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| -| 156813 | 509 | | | | | +| 156801 | 509 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| -| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| number | 2424 | 2424 | 2424 | 2424 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| -| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -3716,18 +3957,47 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { +================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| 1545498 | 7578 | | | | | +| 1544498 | 7573 | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |-----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +| setUp | 218890 | 218890 | 218890 | 218890 | 1 | |-----------------------------------------+-----------------+--------+--------+--------+---------| -| test_Increment | 54915 | 54915 | 54915 | 54915 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | ╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse() + .args(["test", "--match-test", "test_Increment", "--gas-report", "--md"]) + .assert_success() + .stdout_eq(str![[r#" +... +| src/Counter.sol:Counter Contract | | | | | | +|----------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 156801 | 509 | | | | | +| | | | | | | +| Function Name | Min | Avg | Median | Max | # Calls | +| increment | 43482 | 43482 | 43482 | 43482 | 1 | +| number | 2424 | 2424 | 2424 | 2424 | 1 | +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | + +| test/Counter.t.sol:CounterTest Contract | | | | | | +|-----------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 1544498 | 7573 | | | | | +| | | | | | | +| Function Name | Min | Avg | Median | Max | # Calls | +| setUp | 218890 | 218890 | 218890 | 218890 | 1 | +| test_Increment | 51847 | 51847 | 51847 | 51847 | 1 | + + Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); @@ -3741,7 +4011,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "src/Counter.sol:Counter", "deployment": { - "gas": 156813, + "gas": 156801, "size": 509 }, "functions": { @@ -3771,23 +4041,23 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) { "contract": "test/Counter.t.sol:CounterTest", "deployment": { - "gas": 1545498, - "size": 7578 + "gas": 1544498, + "size": 7573 }, "functions": { "setUp()": { "calls": 1, - "min": 218902, - "mean": 218902, - "median": 218902, - "max": 218902 + "min": 218890, + "mean": 218890, + "median": 218890, + "max": 218890 }, "test_Increment()": { "calls": 1, - "min": 54915, - "mean": 54915, - "median": 54915, - "max": 54915 + "min": 51847, + "mean": 51847, + "median": 51847, + "max": 51847 } } } @@ -3831,14 +4101,14 @@ contract FooBarTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--gas-report"]).assert_success(); }); // forgetest_init!(can_bind_enum_modules, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); prj.add_source( @@ -3848,8 +4118,7 @@ forgetest_init!(can_bind_enum_modules, |prj, cmd| { enum MyEnum { A, B, C } } "#, - ) - .unwrap(); + ); prj.add_source( "UseEnum.sol", @@ -3858,8 +4127,7 @@ forgetest_init!(can_bind_enum_modules, |prj, cmd| { contract UseEnum { Enum.MyEnum public myEnum; }"#, - ) - .unwrap(); + ); cmd.args(["bind", "--select", "^Enum$"]).assert_success().stdout_eq(str![[ r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -3869,3 +4137,24 @@ Generating bindings for 1 contracts Bindings have been generated to [..]"# ]]); }); + +// forge bind e2e +forgetest_init!(can_bind_e2e, |prj, cmd| { + prj.initialize_default_contracts(); + cmd.args(["bind"]).assert_success().stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Generating bindings for 2 contracts +Bindings have been generated to [..]"#]]); + + let bindings_path = prj.root().join("out/bindings"); + + assert!(bindings_path.exists(), "Bindings directory should exist"); + let out = Command::new("cargo") + .arg("build") + .current_dir(&bindings_path) + .output() + .expect("Failed to run cargo build"); + + assert!(out.status.success(), "Cargo build should succeed"); +}); diff --git a/crates/forge/tests/cli/compiler.rs b/crates/forge/tests/cli/compiler.rs index 0b58221f2a1d0..eec4873b0ae32 100644 --- a/crates/forge/tests/cli/compiler.rs +++ b/crates/forge/tests/cli/compiler.rs @@ -18,14 +18,14 @@ contract ContractB {} const CONTRACT_C: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.27; +pragma solidity 0.8.33; contract ContractC {} "#; const CONTRACT_D: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.27; +pragma solidity 0.8.33; contract ContractD {} "#; @@ -64,7 +64,7 @@ def increment() -> uint256: "#; forgetest!(can_resolve_path, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractA", CONTRACT_A); cmd.args(["compiler", "resolve", "--root", prj.root().to_str().unwrap()]) .assert_success() @@ -77,7 +77,7 @@ Solidity: }); forgetest!(can_list_resolved_compiler_versions, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractA", CONTRACT_A); cmd.args(["compiler", "resolve"]).assert_success().stdout_eq(str![[r#" Solidity: @@ -88,7 +88,7 @@ Solidity: }); forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractA", CONTRACT_A); cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq( str![[r#" @@ -105,13 +105,13 @@ forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| { }); forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| { - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#" Solidity: -0.8.27: +0.8.33: ├── src/ContractC.sol └── src/ContractD.sol @@ -120,15 +120,15 @@ Solidity: }); forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| { - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); cmd.args(["compiler", "resolve", "--json", "-v"]).assert_success().stdout_eq( str![[r#" { "Solidity": [ { - "version": "0.8.27", + "version": "0.8.33", "paths": [ "src/ContractC.sol", "src/ContractD.sol" @@ -142,39 +142,39 @@ forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| { }); forgetest!(can_list_resolved_multiple_compiler_versions, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); - prj.add_source("ContractB", CONTRACT_B).unwrap(); - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); - prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); - prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + prj.add_source("ContractA", CONTRACT_A); + prj.add_source("ContractB", CONTRACT_B); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT); cmd.args(["compiler", "resolve"]).assert_success().stdout_eq(str![[r#" Solidity: - 0.8.4 - 0.8.11 -- 0.8.27 +- 0.8.33 Vyper: -- 0.4.0 +- 0.4.3 "#]]); }); forgetest!(can_list_resolved_multiple_compiler_versions_skipped, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); - prj.add_source("ContractB", CONTRACT_B).unwrap(); - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); - prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); - prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + prj.add_source("ContractA", CONTRACT_A); + prj.add_source("ContractB", CONTRACT_B); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT); cmd.args(["compiler", "resolve", "--skip", ".sol", "-v"]).assert_success().stdout_eq(str![[ r#" Vyper: -0.4.0: +0.4.3: ├── src/Counter.vy └── src/ICounter.vyi @@ -184,12 +184,12 @@ Vyper: }); forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); - prj.add_source("ContractB", CONTRACT_B).unwrap(); - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); - prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); - prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + prj.add_source("ContractA", CONTRACT_A); + prj.add_source("ContractB", CONTRACT_B); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT); cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json", "-v"]) .assert_success() @@ -198,7 +198,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| { "Solidity": [ { - "version": "0.8.27", + "version": "0.8.33", "paths": [ "src/ContractD.sol" ] @@ -206,7 +206,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| ], "Vyper": [ { - "version": "0.4.0", + "version": "0.4.3", "paths": [ "src/Counter.vy", "src/ICounter.vyi" @@ -220,12 +220,12 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| }); forgetest!(can_list_resolved_multiple_compiler_versions_verbose, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); - prj.add_source("ContractB", CONTRACT_B).unwrap(); - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); - prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); - prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + prj.add_source("ContractA", CONTRACT_A); + prj.add_source("ContractB", CONTRACT_B); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT); cmd.args(["compiler", "resolve", "-vv"]).assert_success().stdout_eq(str![[r#" Solidity: @@ -236,13 +236,13 @@ Solidity: 0.8.11 (<= london): └── src/ContractB.sol -0.8.27 (<= [..]): +0.8.33 (<= prague): ├── src/ContractC.sol └── src/ContractD.sol Vyper: -0.4.0 (<= cancun): +0.4.3 (<= prague): ├── src/Counter.vy └── src/ICounter.vyi @@ -251,12 +251,12 @@ Vyper: }); forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| { - prj.add_source("ContractA", CONTRACT_A).unwrap(); - prj.add_source("ContractB", CONTRACT_B).unwrap(); - prj.add_source("ContractC", CONTRACT_C).unwrap(); - prj.add_source("ContractD", CONTRACT_D).unwrap(); - prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); - prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + prj.add_source("ContractA", CONTRACT_A); + prj.add_source("ContractB", CONTRACT_B); + prj.add_source("ContractC", CONTRACT_C); + prj.add_source("ContractD", CONTRACT_D); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT); cmd.args(["compiler", "resolve", "--json", "-vv"]).assert_success().stdout_eq( str![[r#" @@ -277,7 +277,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| ] }, { - "version": "0.8.27", + "version": "0.8.33", "evm_version": "[..]", "paths": [ "src/ContractC.sol", @@ -287,7 +287,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| ], "Vyper": [ { - "version": "0.4.0", + "version": "0.4.3", "evm_version": "[..]", "paths": [ "src/Counter.vy", diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 522fb2c01c786..3eebca475a781 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -7,16 +7,16 @@ use foundry_compilers::{ solc::Solc, }; use foundry_config::{ + CompilationRestrictions, Config, FsPermissions, FuzzConfig, FuzzCorpusConfig, InvariantConfig, + SettingsOverrides, SolcReq, cache::{CachedChains, CachedEndpoints, StorageCachingConfig}, filter::GlobMatcher, fs_permissions::{FsAccessPermission, PathPermission}, - CompilationRestrictions, Config, FsPermissions, FuzzConfig, InvariantConfig, SettingsOverrides, - SolcReq, }; use foundry_evm::opts::EvmOpts; use foundry_test_utils::{ - foundry_compilers::artifacts::{remappings::Remapping, EvmVersion}, - util::{pretty_err, OutputExt, TestCommand, OTHER_SOLC_VERSION}, + foundry_compilers::artifacts::{EvmVersion, remappings::Remapping}, + util::{OTHER_SOLC_VERSION, OutputExt, TestCommand, pretty_err}, }; use path_slash::PathBufExt; use semver::VersionReq; @@ -26,8 +26,216 @@ use std::{ fs, path::{Path, PathBuf}, str::FromStr, + thread, }; +const DEFAULT_CONFIG: &str = r#"[profile.default] +src = "src" +test = "test" +script = "script" +out = "out" +libs = ["lib"] +remappings = ["forge-std/=lib/forge-std/src/"] +auto_detect_remappings = true +libraries = [] +cache = true +cache_path = "cache" +dynamic_test_linking = false +snapshots = "snapshots" +gas_snapshot_check = false +gas_snapshot_emit = true +broadcast = "broadcast" +allow_paths = [] +include_paths = [] +skip = [] +force = false +evm_version = "osaka" +gas_reports = ["*"] +gas_reports_ignore = [] +gas_reports_include_tests = false +auto_detect_solc = true +offline = false +optimizer = false +optimizer_runs = 200 +verbosity = 0 +eth_rpc_accept_invalid_certs = false +eth_rpc_no_proxy = false +eth_rpc_curl = false +ignored_error_codes = [ + "license", + "code-size", + "init-code-size", + "transient-storage", + "transfer-deprecated", + "natspec-memory-safe-assembly-deprecated", +] +ignored_error_codes_from = [] +ignored_warnings_from = [] +deny = "never" +test_failures_file = "cache/test-failures" +show_progress = false +ffi = false +live_logs = false +allow_internal_expect_revert = false +always_use_create_2_factory = false +prompt_timeout = 120 +sender = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" +tx_origin = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" +initial_balance = "0xffffffffffffffffffffffff" +block_number = 1 +gas_limit = 1073741824 +block_base_fee_per_gas = 0 +block_coinbase = "0x0000000000000000000000000000000000000000" +block_timestamp = 1 +block_difficulty = 0 +block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000" +memory_limit = 134217728 +extra_output = [] +extra_output_files = [] +names = false +sizes = false +via_ir = false +ast = false +no_storage_caching = false +no_rpc_rate_limit = false +use_literal_content = false +bytecode_hash = "ipfs" +cbor_metadata = true +sparse_mode = false +build_info = false +isolate = false +disable_block_gas_limit = false +enable_tx_gas_limit = false +unchecked_cheatcode_artifacts = false +create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" +create2_deployer = "0x4e59b44847b379578588920ca78fbf26c0b4956c" +assertions_revert = true +legacy_assertions = false +celo = false +bypass_prevrandao = false +transaction_timeout = 120 +additional_compiler_profiles = [] +compilation_restrictions = [] +script_execution_protection = true + +[profile.default.rpc_storage_caching] +chains = "all" +endpoints = "all" + +[[profile.default.fs_permissions]] +access = "read" +path = "out" + +[fmt] +line_length = 120 +tab_width = 4 +style = "space" +bracket_spacing = false +int_types = "long" +multiline_func_header = "attributes_first" +quote_style = "double" +number_underscore = "preserve" +hex_underscore = "remove" +single_line_statement_blocks = "preserve" +override_spacing = false +wrap_comments = false +docs_style = "preserve" +ignore = [] +contract_new_lines = false +sort_imports = false +namespace_import_style = "prefer_plain" +pow_no_space = false +prefer_compact = "all" +single_line_imports = false + +[lint] +severity = [ + "high", + "medium", + "low", +] +exclude_lints = [] +ignore = [] +lint_on_build = true + +[lint.lint_specific] +mixed_case_exceptions = [ + "ERC", + "URI", + "ID", + "URL", + "API", + "JSON", + "XML", + "HTML", + "HTTP", + "HTTPS", +] +multi_contract_file_exceptions = [] + +[doc] +out = "docs" +title = "" +book = "book.toml" +homepage = "README.md" +ignore = [] + +[fuzz] +runs = 256 +fail_on_revert = true +max_test_rejects = 65536 +dictionary_weight = 40 +include_storage = true +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 9830400 +max_fuzz_dictionary_literals = 6553600 +gas_report_samples = 256 +corpus_gzip = true +corpus_min_mutations = 5 +corpus_min_size = 0 +show_edge_coverage = false +sancov_edges = false +sancov_trace_cmp = false +failure_persist_dir = "cache/fuzz" +show_logs = false + +[invariant] +runs = 256 +depth = 500 +fail_on_revert = false +call_override = false +dictionary_weight = 80 +include_storage = true +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 9830400 +max_fuzz_dictionary_literals = 6553600 +shrink_run_limit = 5000 +max_assume_rejects = 65536 +gas_report_samples = 256 +corpus_gzip = true +corpus_min_mutations = 5 +corpus_min_size = 0 +show_edge_coverage = false +sancov_edges = false +sancov_trace_cmp = false +failure_persist_dir = "cache/invariant" +show_metrics = true +show_solidity = false +check_interval = 1 + +[labels] + +[vyper] + +[bind_json] +out = "utils/JsonBindings.sol" +include = [] +exclude = [] + +"#; + // tests all config values that are in use forgetest!(can_extract_config_values, |prj, cmd| { // explicitly set all values @@ -36,6 +244,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { // `profiles` is not serialized. profiles: vec![], root: ".".into(), + extends: None, src: "test-src".into(), test: "test-test".into(), script: "test-script".into(), @@ -50,6 +259,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, + hardfork: None, gas_reports: vec!["Contract".to_string()], gas_reports_ignore: vec![], gas_reports_include_tests: false, @@ -84,23 +294,27 @@ forgetest!(can_extract_config_values, |prj, cmd| { max_test_rejects: 100203, seed: Some(U256::from(1000)), failure_persist_dir: Some("test-cache/fuzz".into()), - failure_persist_file: Some("failures".to_string()), show_logs: false, ..Default::default() }, invariant: InvariantConfig { runs: 256, failure_persist_dir: Some("test-cache/fuzz".into()), + corpus: FuzzCorpusConfig { + corpus_dir: Some("cache/invariant/corpus".into()), + ..Default::default() + }, ..Default::default() }, ffi: true, + live_logs: true, allow_internal_expect_revert: false, always_use_create_2_factory: false, prompt_timeout: 0, sender: "00a329c0648769A73afAc7F9381D08FB43dBEA72".parse().unwrap(), tx_origin: "00a329c0648769A73afAc7F9F81E08FB43dBEA72".parse().unwrap(), initial_balance: U256::from(0xffffffffffffffffffffffffu128), - block_number: 10, + block_number: U256::from(10), fork_block_number: Some(200), chain: Some(9999.into()), gas_limit: 99_000_000u64.into(), @@ -108,26 +322,31 @@ forgetest!(can_extract_config_values, |prj, cmd| { gas_price: Some(999), block_base_fee_per_gas: 10, block_coinbase: Address::random(), - block_timestamp: 10, + block_timestamp: U256::from(10), block_difficulty: 10, block_prevrandao: B256::random(), block_gas_limit: Some(100u64.into()), disable_block_gas_limit: false, + enable_tx_gas_limit: false, memory_limit: 1 << 27, eth_rpc_url: Some("localhost".to_string()), + eth_rpc_accept_invalid_certs: false, + eth_rpc_no_proxy: false, eth_rpc_jwt: None, eth_rpc_timeout: None, eth_rpc_headers: None, + eth_rpc_curl: false, etherscan_api_key: None, - etherscan_api_version: None, etherscan: Default::default(), verbosity: 4, remappings: vec![Remapping::from_str("forge-std/=lib/forge-std/").unwrap().into()], libraries: vec![ - "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6".to_string() + "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6".to_string(), ], ignored_error_codes: vec![], + ignored_error_codes_from: vec![], ignored_file_paths: vec![], + deny: foundry_config::DenyLevel::Never, deny_warnings: false, via_ir: true, ast: false, @@ -165,7 +384,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { assertions_revert: true, legacy_assertions: false, extra_args: vec![], - odyssey: false, + networks: Default::default(), transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), @@ -190,6 +409,7 @@ forgetest!(can_show_config, |prj, cmd| { // - paths are resolved properly // - config supports overrides from env, and cli forgetest_init!(can_override_config, |prj, cmd| { + prj.initialize_default_contracts(); cmd.set_current_dir(prj.root()); let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); @@ -256,6 +476,7 @@ forgetest_init!(can_override_config, |prj, cmd| { }); forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { + prj.initialize_default_contracts(); cmd.set_current_dir(prj.root()); let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); @@ -275,7 +496,7 @@ forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { let install = |cmd: &mut TestCommand, dep: &str| { cmd.forge_fuse().args(["install", dep]).assert_success().stdout_eq(str![[r#" -Installing solmate in [..] (url: Some("https://github.com/transmissions11/solmate"), tag: None) +Installing solmate in [..] (url: https://github.com/transmissions11/solmate, tag: None) Installed solmate[..] "#]]); @@ -318,6 +539,7 @@ Installing solmate in [..] (url: Some("https://github.com/transmissions11/solmat }); forgetest_init!(can_detect_config_vals, |prj, _cmd| { + prj.initialize_default_contracts(); let url = "http://127.0.0.1:8545"; let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); assert!(!config.auto_detect_solc); @@ -338,20 +560,52 @@ forgetest_init!(can_detect_config_vals, |prj, _cmd| { // checks that `clean` removes dapptools style paths forgetest_init!(can_get_evm_opts, |prj, _cmd| { + prj.initialize_default_contracts(); let url = "http://127.0.0.1:8545"; let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); assert_eq!(config.eth_rpc_url, Some(url.to_string())); assert!(config.ffi); - std::env::set_var("FOUNDRY_ETH_RPC_URL", url); + unsafe { + std::env::set_var("FOUNDRY_ETH_RPC_URL", url); + } let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); let evm_opts: EvmOpts = figment.extract().unwrap(); assert_eq!(evm_opts.fork_url, Some(url.to_string())); - std::env::remove_var("FOUNDRY_ETH_RPC_URL"); + unsafe { + std::env::remove_var("FOUNDRY_ETH_RPC_URL"); + } +}); + +// Regression test for : +// the bare `ETH_RPC_URL` env var must NOT cause `forge` commands to set +// `eth_rpc_url` (which would silently fork all `forge test` runs). +// Only `--rpc-url`, `foundry.toml`, the `FOUNDRY_ETH_RPC_URL` env var, or +// cheatcodes should configure forking. +forgetest_init!(eth_rpc_url_env_does_not_set_fork_url, |prj, _cmd| { + prj.initialize_default_contracts(); + let url = "http://127.0.0.1:8545"; + + let mut cmd = prj.forge_bin(); + cmd.arg("config") + .arg("--root") + .arg(prj.root()) + .arg("--json") + .env("ETH_RPC_URL", url) + // Make sure the figment-style env var is not set in the test environment. + .env_remove("FOUNDRY_ETH_RPC_URL"); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let config: Config = serde_json::from_str(stdout.as_ref()).unwrap(); + assert_eq!( + config.eth_rpc_url, None, + "bare ETH_RPC_URL must not propagate to forge config (regression #14538)" + ); }); // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { + prj.initialize_default_contracts(); let config = prj.config_from_output(["--via-ir", "--no-metadata"]); assert!(config.via_ir); assert_eq!(config.cbor_metadata, false); @@ -366,8 +620,7 @@ forgetest!(can_set_solc_explicitly, |prj, cmd| { pragma solidity *; contract Greeter {} ", - ) - .unwrap(); + ); prj.update_config(|config| { config.solc = Some(OTHER_SOLC_VERSION.into()); @@ -389,8 +642,7 @@ forgetest!(can_use_solc, |prj, cmd| { pragma solidity *; contract Foo {} ", - ) - .unwrap(); + ); cmd.args(["build", "--use", OTHER_SOLC_VERSION]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -446,8 +698,7 @@ contract Foo { } } ", - ) - .unwrap(); + ); cmd.arg("build").assert_failure().stderr_eq(str![[r#" Error: Compiler run failed: @@ -533,6 +784,7 @@ forgetest!(can_set_gas_price, |prj, cmd| { // test that we can detect remappings from foundry.toml forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { + prj.initialize_default_contracts(); let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); similar_asserts::assert_eq!( @@ -631,6 +883,7 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // test remappings with closer paths are prioritised // so that `dep/=lib/a/src` will take precedent over `dep/=lib/a/lib/b/src` forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { + prj.initialize_default_contracts(); let config = cmd.config(); // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` @@ -657,15 +910,19 @@ forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { // remapping. // See // Test that +// - single file remapping is properly added, see +// and // - project defined `@openzeppelin/contracts` remapping is added // - library defined `@openzeppelin/contracts-upgradeable` remapping is added // - library defined `@openzeppelin/contracts/upgradeable` remapping is not added as it conflicts // with project defined `@openzeppelin/contracts` remapping // See forgetest_init!(can_prioritise_project_remappings, |prj, cmd| { + prj.initialize_default_contracts(); let mut config = cmd.config(); // Add `@utils/` remapping in project config. config.remappings = vec![ + Remapping::from_str("@utils/libraries/Contract.sol=src/Contract.sol").unwrap().into(), Remapping::from_str("@utils/=src/").unwrap().into(), Remapping::from_str("@openzeppelin/contracts=lib/openzeppelin-contracts/").unwrap().into(), ]; @@ -693,6 +950,7 @@ forgetest_init!(can_prioritise_project_remappings, |prj, cmd| { cmd.args(["remappings", "--pretty"]).assert_success().stdout_eq(str![[r#" Global: +- @utils/libraries/Contract.sol=src/Contract.sol - @utils/=src/ - @openzeppelin/contracts/=lib/openzeppelin-contracts/ - @openzeppelin/contracts-upgradeable/=lib/dep1/lib/openzeppelin-upgradeable/ @@ -711,7 +969,7 @@ forgetest!(can_update_libs_section, |prj, cmd| { prj.update_config(|config| config.libs = vec!["node_modules".into()]); cmd.args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq(str![[r#" -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] "#]]); @@ -723,7 +981,7 @@ Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std // additional install don't edit `libs` cmd.forge_fuse().args(["install", "dapphub/ds-test"]).assert_success().stdout_eq(str![[r#" -Installing ds-test in [..] (url: Some("https://github.com/dapphub/ds-test"), tag: None) +Installing ds-test in [..] (url: https://github.com/dapphub/ds-test, tag: None) Installed ds-test "#]]); @@ -738,7 +996,7 @@ forgetest!(config_emit_warnings, |prj, cmd| { cmd.git_init(); cmd.args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq(str![[r#" -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] "#]]); @@ -760,6 +1018,7 @@ Please use [profile.default] instead or run `forge config --fix`. }); forgetest_init!(can_skip_remappings_auto_detection, |prj, cmd| { + prj.initialize_default_contracts(); // explicitly set remapping and libraries prj.update_config(|config| { config.remappings = vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()]; @@ -783,6 +1042,7 @@ forgetest_init!(can_parse_default_fs_permissions, |_prj, cmd| { }); forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { + prj.initialize_default_contracts(); // explicitly set fs permissions prj.update_config(|config| { config.fs_permissions = FsPermissions::new(vec![ @@ -817,6 +1077,7 @@ forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { #[cfg(not(target_os = "windows"))] forgetest_init!(can_resolve_symlink_fs_permissions, |prj, cmd| { + prj.initialize_default_contracts(); // write config in packages/files/config.json let config_path = prj.root().join("packages").join("files"); fs::create_dir_all(&config_path).unwrap(); @@ -896,6 +1157,7 @@ forgetest!(normalize_config_evm_version, |_prj, cmd| { // Tests that root paths are properly resolved even if submodule specifies remappings for them. // See forgetest_init!(test_submodule_root_path_remappings, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_script( "BaseScript.sol", r#" @@ -904,8 +1166,7 @@ import "forge-std/Script.sol"; contract BaseScript is Script { } "#, - ) - .unwrap(); + ); prj.add_script( "MyScript.sol", r#" @@ -914,8 +1175,7 @@ import "script/BaseScript.sol"; contract MyScript is BaseScript { } "#, - ) - .unwrap(); + ); let nested = prj.paths().libraries[0].join("another-dep"); pretty_err(&nested, fs::create_dir_all(&nested)); @@ -934,6 +1194,7 @@ contract MyScript is BaseScript { // For `src=src` config, remapping should be `src/ = src/`. // forgetest_init!(test_project_remappings, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.src = "src/contracts".into(); config.remappings = vec![Remapping::from_str("contracts/=src/contracts/").unwrap().into()]; @@ -954,172 +1215,14 @@ import "contracts/Counter.sol"; contract CounterTest { } "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["build"]).assert_success(); }); #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(test_default_config, |prj, cmd| { prj.write_config(Config::default()); - cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" -[profile.default] -src = "src" -test = "test" -script = "script" -out = "out" -libs = ["lib"] -remappings = ["forge-std/=lib/forge-std/src/"] -auto_detect_remappings = true -libraries = [] -cache = true -dynamic_test_linking = false -cache_path = "cache" -snapshots = "snapshots" -gas_snapshot_check = false -gas_snapshot_emit = true -broadcast = "broadcast" -allow_paths = [] -include_paths = [] -skip = [] -force = false -evm_version = "cancun" -gas_reports = ["*"] -gas_reports_ignore = [] -gas_reports_include_tests = false -auto_detect_solc = true -offline = false -optimizer = false -optimizer_runs = 200 -verbosity = 0 -ignored_error_codes = [ - "license", - "code-size", - "init-code-size", - "transient-storage", -] -ignored_warnings_from = [] -deny_warnings = false -test_failures_file = "cache/test-failures" -show_progress = false -ffi = false -allow_internal_expect_revert = false -always_use_create_2_factory = false -prompt_timeout = 120 -sender = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" -tx_origin = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" -initial_balance = "0xffffffffffffffffffffffff" -block_number = 1 -gas_limit = 1073741824 -block_base_fee_per_gas = 0 -block_coinbase = "0x0000000000000000000000000000000000000000" -block_timestamp = 1 -block_difficulty = 0 -block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000" -memory_limit = 134217728 -extra_output = [] -extra_output_files = [] -names = false -sizes = false -via_ir = false -ast = false -no_storage_caching = false -no_rpc_rate_limit = false -use_literal_content = false -bytecode_hash = "ipfs" -cbor_metadata = true -sparse_mode = false -build_info = false -isolate = false -disable_block_gas_limit = false -unchecked_cheatcode_artifacts = false -create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" -create2_deployer = "0x4e59b44847b379578588920ca78fbf26c0b4956c" -assertions_revert = true -legacy_assertions = false -odyssey = false -transaction_timeout = 120 -additional_compiler_profiles = [] -compilation_restrictions = [] -script_execution_protection = true - -[profile.default.rpc_storage_caching] -chains = "all" -endpoints = "all" - -[[profile.default.fs_permissions]] -access = "read" -path = "out" - -[fmt] -line_length = 120 -tab_width = 4 -bracket_spacing = false -int_types = "long" -multiline_func_header = "attributes_first" -quote_style = "double" -number_underscore = "preserve" -hex_underscore = "remove" -single_line_statement_blocks = "preserve" -override_spacing = false -wrap_comments = false -ignore = [] -contract_new_lines = false -sort_imports = false - -[lint] -severity = [] -exclude_lints = [] -ignore = [] - -[doc] -out = "docs" -title = "" -book = "book.toml" -homepage = "README.md" -ignore = [] - -[fuzz] -runs = 256 -max_test_rejects = 65536 -dictionary_weight = 40 -include_storage = true -include_push_bytes = true -max_fuzz_dictionary_addresses = 15728640 -max_fuzz_dictionary_values = 6553600 -gas_report_samples = 256 -failure_persist_dir = "cache/fuzz" -failure_persist_file = "failures" -show_logs = false - -[invariant] -runs = 256 -depth = 500 -fail_on_revert = false -call_override = false -dictionary_weight = 80 -include_storage = true -include_push_bytes = true -max_fuzz_dictionary_addresses = 15728640 -max_fuzz_dictionary_values = 6553600 -shrink_run_limit = 5000 -max_assume_rejects = 65536 -gas_report_samples = 256 -failure_persist_dir = "cache/invariant" -show_metrics = false -show_solidity = false - -[labels] - -[vyper] - -[bind_json] -out = "utils/JsonBindings.sol" -include = [] -exclude = [] - - -"#]]); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(DEFAULT_CONFIG); cmd.forge_fuse().args(["config", "--json"]).assert_success().stdout_eq(str![[r#" { @@ -1136,8 +1239,8 @@ exclude = [] "auto_detect_remappings": true, "libraries": [], "cache": true, - "dynamic_test_linking": false, "cache_path": "cache", + "dynamic_test_linking": false, "snapshots": "snapshots", "gas_snapshot_check": false, "gas_snapshot_emit": true, @@ -1146,7 +1249,8 @@ exclude = [] "include_paths": [], "skip": [], "force": false, - "evm_version": "cancun", + "evm_version": "osaka", + "hardfork": null, "gas_reports": [ "*" ], @@ -1161,19 +1265,24 @@ exclude = [] "model_checker": null, "verbosity": 0, "eth_rpc_url": null, + "eth_rpc_accept_invalid_certs": false, + "eth_rpc_no_proxy": false, "eth_rpc_jwt": null, "eth_rpc_timeout": null, "eth_rpc_headers": null, + "eth_rpc_curl": false, "etherscan_api_key": null, - "etherscan_api_version": null, "ignored_error_codes": [ "license", "code-size", "init-code-size", - "transient-storage" + "transient-storage", + "transfer-deprecated", + "natspec-memory-safe-assembly-deprecated" ], + "ignored_error_codes_from": [], "ignored_warnings_from": [], - "deny_warnings": false, + "deny": "never", "match_test": null, "no_match_test": null, "match_contract": null, @@ -1186,16 +1295,26 @@ exclude = [] "show_progress": false, "fuzz": { "runs": 256, + "run": null, + "worker": null, + "fail_on_revert": true, "max_test_rejects": 65536, "seed": null, "dictionary_weight": 40, "include_storage": true, "include_push_bytes": true, "max_fuzz_dictionary_addresses": 15728640, - "max_fuzz_dictionary_values": 6553600, + "max_fuzz_dictionary_values": 9830400, + "max_fuzz_dictionary_literals": 6553600, "gas_report_samples": 256, + "corpus_dir": null, + "corpus_gzip": true, + "corpus_min_mutations": 5, + "corpus_min_size": 0, + "show_edge_coverage": false, + "sancov_edges": false, + "sancov_trace_cmp": false, "failure_persist_dir": "cache/fuzz", - "failure_persist_file": "failures", "show_logs": false, "timeout": null }, @@ -1208,16 +1327,28 @@ exclude = [] "include_storage": true, "include_push_bytes": true, "max_fuzz_dictionary_addresses": 15728640, - "max_fuzz_dictionary_values": 6553600, + "max_fuzz_dictionary_values": 9830400, + "max_fuzz_dictionary_literals": 6553600, "shrink_run_limit": 5000, "max_assume_rejects": 65536, "gas_report_samples": 256, + "corpus_dir": null, + "corpus_gzip": true, + "corpus_min_mutations": 5, + "corpus_min_size": 0, + "show_edge_coverage": false, + "sancov_edges": false, + "sancov_trace_cmp": false, "failure_persist_dir": "cache/invariant", - "show_metrics": false, + "show_metrics": true, "timeout": null, - "show_solidity": false + "show_solidity": false, + "max_time_delay": null, + "max_block_delay": null, + "check_interval": 1 }, "ffi": false, + "live_logs": false, "allow_internal_expect_revert": false, "always_use_create_2_factory": false, "prompt_timeout": 120, @@ -1259,6 +1390,7 @@ exclude = [] "fmt": { "line_length": 120, "tab_width": 4, + "style": "space", "bracket_spacing": false, "int_types": "long", "multiline_func_header": "attributes_first", @@ -1268,14 +1400,39 @@ exclude = [] "single_line_statement_blocks": "preserve", "override_spacing": false, "wrap_comments": false, + "docs_style": "preserve", "ignore": [], "contract_new_lines": false, - "sort_imports": false + "sort_imports": false, + "namespace_import_style": "prefer_plain", + "pow_no_space": false, + "prefer_compact": "all", + "single_line_imports": false }, "lint": { - "severity": [], + "severity": [ + "high", + "medium", + "low" + ], "exclude_lints": [], - "ignore": [] + "ignore": [], + "lint_on_build": true, + "lint_specific": { + "mixed_case_exceptions": [ + "ERC", + "URI", + "ID", + "URL", + "API", + "JSON", + "XML", + "HTML", + "HTTP", + "HTTPS" + ], + "multi_contract_file_exceptions": [] + } }, "doc": { "out": "docs", @@ -1297,6 +1454,7 @@ exclude = [] ], "isolate": false, "disable_block_gas_limit": false, + "enable_tx_gas_limit": false, "labels": {}, "unchecked_cheatcode_artifacts": false, "create2_library_salt": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -1306,7 +1464,9 @@ exclude = [] "soldeer": null, "assertions_revert": true, "legacy_assertions": false, - "odyssey": false, + "network": null, + "celo": false, + "bypass_prevrandao": false, "transaction_timeout": 120, "additional_compiler_profiles": [], "compilation_restrictions": [], @@ -1317,6 +1477,7 @@ exclude = [] }); forgetest_init!(test_optimizer_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: optimizer disabled, optimizer runs 200. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1390,6 +1551,7 @@ optimizer_runs = 0 }); forgetest_init!(test_gas_snapshot_check_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: gas_snapshot_check disabled. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1413,8 +1575,7 @@ contract Flare { } } "#, - ) - .unwrap(); + ); let test_contract = |n: u32| { format!( @@ -1447,7 +1608,7 @@ contract GasSnapshotCheckTest is DSTest {{ }; // Assert that gas_snapshot_check is disabled by default. - prj.add_source("GasSnapshotCheckTest.sol", &test_contract(1)).unwrap(); + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(1)); cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" ... Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest @@ -1466,7 +1627,7 @@ gas_snapshot_check = true "#]]); // Replace the test contract with a new one that will fail the gas snapshot check. - prj.add_source("GasSnapshotCheckTest.sol", &test_contract(2)).unwrap(); + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(2)); cmd.forge_fuse().args(["test"]).assert_failure().stderr_eq(str![[r#" ... [GasSnapshotCheckTest] Failed to match snapshots: @@ -1498,7 +1659,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] "#]]); // Replace the test contract with a new one that will fail the gas_snapshot_check. - prj.add_source("GasSnapshotCheckTest.sol", &test_contract(3)).unwrap(); + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(3)); cmd.forge_fuse().args(["test"]).assert_failure().stderr_eq(str![[r#" ... [GasSnapshotCheckTest] Failed to match snapshots: @@ -1523,7 +1684,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] // Enable using `FORGE_SNAPSHOT_CHECK` environment variable. // Assert that this will override the config file value. prj.update_config(|config| config.gas_snapshot_check = false); - prj.add_source("GasSnapshotCheckTest.sol", &test_contract(4)).unwrap(); + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(4)); cmd.forge_fuse(); cmd.env("FORGE_SNAPSHOT_CHECK", "true"); cmd.args(["test"]).assert_failure().stderr_eq(str![[r#" @@ -1562,6 +1723,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] }); forgetest_init!(test_gas_snapshot_emit_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: gas_snapshot_emit enabled. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1591,8 +1753,7 @@ contract GasSnapshotEmitTest is DSTest { } } "#, - ) - .unwrap(); + ); // Assert that gas_snapshot_emit is enabled by default. cmd.forge_fuse().args(["test"]).assert_success(); @@ -1665,14 +1826,14 @@ contract GasSnapshotEmitTest is DSTest { // Tests compilation restrictions enables optimizer if optimizer runs set to a value higher than 0. forgetest_init!(test_additional_compiler_profiles, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "v1/Counter.sol", r#" contract Counter { } "#, - ) - .unwrap(); + ); prj.add_source( "v2/Counter.sol", @@ -1680,8 +1841,7 @@ contract Counter { contract Counter { } "#, - ) - .unwrap(); + ); prj.add_source( "v3/Counter.sol", @@ -1689,8 +1849,7 @@ contract Counter { contract Counter { } "#, - ) - .unwrap(); + ); // Additional profiles are defined with optimizer runs but without explicitly enabling // optimizer @@ -1712,7 +1871,7 @@ contract Counter { let v1_profile = SettingsOverrides { name: "v1".to_string(), via_ir: Some(true), - evm_version: Some(EvmVersion::Cancun), + evm_version: Some(EvmVersion::Osaka), optimizer: None, optimizer_runs: Some(44444444), bytecode_hash: None, @@ -1798,19 +1957,19 @@ contract Counter { let (via_ir, evm_version, enabled, runs) = artifact_settings("Counter.sol/Counter.json"); assert_eq!(None, via_ir); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("false", enabled.unwrap().to_string()); assert_eq!("200", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v1/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("44444444", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v2/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"osaka\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("111", runs.unwrap().to_string()); @@ -1820,3 +1979,209 @@ contract Counter { assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("800", runs.unwrap().to_string()); }); + +// +forgetest_init!(test_exclude_lints_config, |prj, cmd| { + prj.initialize_default_contracts(); + prj.update_config(|config| { + config.lint.exclude_lints = vec![ + "asm-keccak256".to_string(), + "incorrect-shift".to_string(), + "divide-before-multiply".to_string(), + "mixed-case-variable".to_string(), + "mixed-case-function".to_string(), + "screaming-snake-case-const".to_string(), + "screaming-snake-case-immutable".to_string(), + "unwrapped-modifier-logic".to_string(), + ] + }); + cmd.args(["lint"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); +}); + +// +forgetest_init!(test_fail_fast_config, |prj, cmd| { + // Skip if we don't have at least 2 CPUs to run both tests in parallel. + if thread::available_parallelism().map_or(1, |n| n.get()) < 2 { + return; + } + + prj.update_config(|config| { + // Set large timeout for fuzzed tests so test campaign won't stop if fail fast not passed. + config.fuzz.timeout = Some(3600); + }); + prj.add_test( + "AnotherCounterTest.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract AnotherCounterTest is Test { + // This failure should stop all other tests. + function test_Failure() public pure { + require(false); + } + + function testFuzz_SetNumber(uint256 x) public { + } +} +"#, + ); + cmd.args(["test", "--fail-fast"]).assert_failure(); +}); + +forgetest!(config_deny_warnings_is_deprecated, |prj, cmd| { + cmd.git_init(); + + let faulty_toml = DEFAULT_CONFIG.replace(r#"deny = "never""#, "deny_warnings = true"); + + fs::write(prj.root().join("foundry.toml"), faulty_toml).unwrap(); + cmd.forge_fuse().args(["config"]).assert_success().stderr_eq(str![[r#" +Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. It will be removed in future versions. + +"#]]); +}); + +// +forgetest!(no_warnings_on_external_sections, |prj, cmd| { + cmd.git_init(); + + let toml = r"[profile.default] + src = 'src' + out = 'out' + + # Custom sections for other tools + [external.scopelint] + some_flag = 1 + + [external.forge_deploy] + another_setting = 123"; + + fs::write(prj.root().join("foundry.toml"), toml).unwrap(); + cmd.forge_fuse().args(["config"]).assert_success().stderr_eq(str![[r#" + +"#]]); +}); + +// +forgetest!(config_warnings_on_unknown_keys, |prj, cmd| { + cmd.git_init(); + + let faulty_toml = r"[profile.default] + src = 'src' + out = 'out' + solc_version = '0.8.18' + foo = 'unknown' + + [profile.another] + src = 'src' + out = 'out' + bar = 'another_unknown'"; + + fs::write(prj.root().join("foundry.toml"), faulty_toml).unwrap(); + cmd.forge_fuse().args(["config"]).assert_success().stderr_eq(str![[r#" +Warning: Found unknown `bar` config for profile `another` defined in foundry.toml. +Warning: Found unknown `foo` config for profile `default` defined in foundry.toml. + +"#]]); +}); + +forgetest_init!(test_ignored_file_paths_normalization, |prj, cmd| { + fn gen_contract(name: &str) -> String { + let fn_name = name.chars().next().unwrap().to_lowercase().to_string() + &name[1..]; + format!( + r#" +contract {name} {{ + function {fn_name}() public returns (bool) {{ return true; }} +}} +"# + ) + } + + // Update config to ignore warnings from specific files with various path formats + prj.update_config(|config| { + config.ignored_file_paths = vec![ + PathBuf::from("./test/IgnoredWithPrefix.sol"), // With "./" prefix + PathBuf::from("src/IgnoredNoPrefix.sol"), // Without "./" prefix + PathBuf::from("./src/nested/IgnoredNested.sol"), // Nested path with prefix + ]; + }); + + // Create contracts that will generate warnings + prj.add_source("IgnoredNoPrefix.sol", &gen_contract("IgnoredNoPrefix")); + prj.add_test("IgnoredWithPrefix.sol", &gen_contract("IgnoredWithPrefix")); + + fs::create_dir_all(prj.root().join("src/nested")).unwrap(); + fs::write(prj.root().join("src/nested/IgnoredNested.sol"), gen_contract("IgnoredNested")) + .unwrap(); + + prj.add_source("NotIgnored.sol", &gen_contract("NotIgnored")); + + // Verify the config loads paths as specified (before normalization) + let config = cmd.config(); + let raw_paths = vec![ + PathBuf::from("./test/IgnoredWithPrefix.sol"), + PathBuf::from("src/IgnoredNoPrefix.sol"), + PathBuf::from("./src/nested/IgnoredNested.sol"), + ]; + assert_eq!(config.ignored_file_paths, raw_paths); + + // Build and verify compilation succeeds with just 1 warning: + cmd.forge_fuse().args(["build"]).assert_success().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to pure + [FILE]:5:5: + | +5 | function notIgnored() public returns (bool) { return true; } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +"#, + ); +}); + +forgetest_init!(test_failures_file_normalization, |prj, cmd| { + // Update config with custom path containing "./" prefix + prj.update_config(|config| { + config.test_failures_file = PathBuf::from("./my-custom-failures"); + }); + + prj.add_test( + "MixedTests.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract MixedTests is Test { + function testPass() public pure { + require(1 == 1); + } + + function testFail() public pure { + require(1 == 2, "testFail failed"); + } +} +"#, + ); + + // Run test and verify test_failures_file is created at the correct location + cmd.args(["test"]).assert_failure(); + let failures_file = prj.root().join("my-custom-failures"); + assert!(failures_file.exists()); + assert!(fs::read_to_string(&failures_file).unwrap().contains("testFail")); + + // Verify --rerun works from subdirectory + let rerun_output = cmd + .forge_fuse() + .current_dir(prj.root().join("src")) + .args(["test", "--rerun"]) + .assert_failure() + .get_output() + .stdout_lossy(); + assert!(rerun_output.contains("Ran 1 test")); + assert!(rerun_output.contains("testFail()")); + assert!(!rerun_output.contains("[PASS] testPass()")); +}); diff --git a/crates/forge/tests/cli/context.rs b/crates/forge/tests/cli/context.rs index 34a7598a37cd9..6f00d325c872e 100644 --- a/crates/forge/tests/cli/context.rs +++ b/crates/forge/tests/cli/context.rs @@ -46,36 +46,34 @@ contract ForgeContextTest is DSTest { // tests that context properly set for `forge test` command forgetest!(can_set_forge_test_standard_context, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT); cmd.args(["test", "--match-test", "testForgeTestContext"]).assert_success(); }); // tests that context properly set for `forge snapshot` command forgetest!(can_set_forge_test_snapshot_context, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT); cmd.args(["snapshot", "--match-test", "testForgeSnapshotContext"]).assert_success(); }); // tests that context properly set for `forge coverage` command forgetest!(can_set_forge_test_coverage_context, |prj, cmd| { prj.insert_ds_test(); - prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT); cmd.args(["coverage", "--match-test", "testForgeCoverageContext"]).assert_success(); }); // tests that context properly set for `forge script` command forgetest!(can_set_forge_script_dry_run_context, |prj, cmd| { prj.insert_ds_test(); - let script = - prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + let script = prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT); cmd.arg("script").arg(script).args(["--sig", "runDryRun()"]).assert_success(); }); // tests that context properly set for `forge script --broadcast` command forgetest!(can_set_forge_script_broadcast_context, |prj, cmd| { prj.insert_ds_test(); - let script = - prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + let script = prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT); cmd.arg("script").arg(script).args(["--broadcast", "--sig", "runBroadcast()"]).assert_success(); }); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 43896c7c3623b..a8805ff9c4c34 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -1,10 +1,15 @@ use foundry_common::fs::{self, files_with_ext}; use foundry_test_utils::{ - snapbox::{Data, IntoData}, TestCommand, TestProject, + snapbox::{Data, IntoData}, }; use std::path::Path; +#[track_caller] +fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { + cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data()); +} + fn basic_base(prj: TestProject, mut cmd: TestCommand) { cmd.args(["coverage", "--report=lcov", "--report=summary"]).assert_success().stdout_eq(str![[ r#" @@ -167,10 +172,12 @@ end_of_record } forgetest_init!(basic, |prj, cmd| { + prj.initialize_default_contracts(); basic_base(prj, cmd); }); forgetest_init!(basic_crlf, |prj, cmd| { + prj.initialize_default_contracts(); // Manually replace `\n` with `\r\n` in the source file. let make_crlf = |path: &Path| { fs::write(path, fs::read_to_string(path).unwrap().replace('\n', "\r\n")).unwrap() @@ -199,8 +206,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -221,8 +227,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); // Assert 100% coverage (init function coverage called in setUp is accounted). cmd.arg("coverage").assert_success().stdout_eq(str![[r#" @@ -238,6 +243,57 @@ contract AContractTest is DSTest { "#]]); }); +forgetest!(setup_md, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + int public i; + + function init() public { + i = 0; + } + + function foo() public { + i = 1; + } +} + "#, + ); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + AContract a; + + function setUp() public { + a = new AContract(); + a.init(); + } + + function testFoo() public { + a.foo(); + } +} + "#, + ); + + // Assert 100% coverage (init function coverage called in setUp is accounted). + cmd.arg("coverage").args(["--md"]).assert_success().stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | + +"#]]); +}); + forgetest!(no_match, |prj, cmd| { prj.insert_ds_test(); prj.add_source( @@ -255,8 +311,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -277,8 +332,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); prj.add_source( "BContract.sol", @@ -295,8 +349,7 @@ contract BContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "BContractTest.sol", @@ -317,8 +370,7 @@ contract BContractTest is DSTest { } } "#, - ) - .unwrap(); + ); // Assert AContract is not included in report. cmd.arg("coverage").arg("--no-match-coverage=AContract").assert_success().stdout_eq(str![[ @@ -348,8 +400,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -363,21 +414,20 @@ interface Vm { contract AContractTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + AContract a = new AContract(); + function testAssertBranch() external { - AContract a = new AContract(); bool result = a.checkA(10); assertTrue(result); } function testAssertRevertBranch() external { - AContract a = new AContract(); vm.expectRevert(); a.checkA(1); } } "#, - ) - .unwrap(); + ); // Assert 50% statement coverage for assert failure (assert not considered a branch). cmd.arg("coverage").args(["--mt", "testAssertRevertBranch"]).assert_success().stdout_eq(str![ @@ -421,8 +471,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -436,20 +485,19 @@ interface Vm { contract AContractTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + AContract a = new AContract(); + function testRequireRevert() external { - AContract a = new AContract(); vm.expectRevert(abi.encodePacked("reverted")); a.checkRequire(false); } function testRequireNoRevert() external { - AContract a = new AContract(); a.checkRequire(true); } } "#, - ) - .unwrap(); + ); // Assert 50% branch coverage if only revert tested. cmd.arg("coverage").args(["--mt", "testRequireRevert"]).assert_success().stdout_eq(str![[r#" @@ -508,8 +556,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -518,14 +565,14 @@ import "./test.sol"; import {AContract} from "./AContract.sol"; contract AContractTest is DSTest { + AContract a = new AContract(); + function testFoo() public { - AContract a = new AContract(); a.foo(); } } "#, - ) - .unwrap(); + ); // We want to make sure DA:8,1 is added only once so line hit is not doubled. assert_lcov( @@ -644,8 +691,7 @@ contract Foo { } } "#, - ) - .unwrap(); + ); prj.add_source( "FooTest.sol", @@ -760,8 +806,7 @@ contract FooTest is DSTest { } } "#, - ) - .unwrap(); + ); // Assert no coverage for single path branch. 2 branches (parent and child) not covered. cmd.arg("coverage") @@ -851,8 +896,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -861,14 +905,14 @@ import "./test.sol"; import {AContract} from "./AContract.sol"; contract AContractTest is DSTest { + AContract a = new AContract(); + function testTypeConversionCoverage() external { - AContract a = new AContract(); a.coverMe(); } } "#, - ) - .unwrap(); + ); // Assert 100% coverage. cmd.arg("coverage").assert_success().stdout_eq(str![[r#" @@ -935,8 +979,7 @@ contract Bar { } } "#, - ) - .unwrap(); + ); prj.add_source( "FooTest.sol", @@ -975,8 +1018,7 @@ contract FooTest is DSTest { } } "#, - ) - .unwrap(); + ); // Assert coverage not 100% for happy paths only. cmd.arg("coverage").args(["--mt", "happy"]).assert_success().stdout_eq(str![[r#" @@ -1073,8 +1115,7 @@ contract Foo { } } "#, - ) - .unwrap(); + ); prj.add_source( "FooTest.sol", @@ -1095,8 +1136,7 @@ contract FooTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1137,7 +1177,7 @@ contract B { contract C { function create() public { B b = new B{value: 1}(2); - b = (new B{value: 1})(2); + b = new B{value: 1}(2); b = (new B){value: 1}(2); } } @@ -1152,8 +1192,7 @@ contract D { } } "#, - ) - .unwrap(); + ); prj.add_source( "FooTest.sol", @@ -1188,8 +1227,7 @@ contract FooTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1214,17 +1252,20 @@ contract AContract { event IsTrue(bool isTrue); event IsFalse(bool isFalse); - function ifElseStatementIgnored(bool flag) external { + function ifElseStatementIgnored(bool flag) external returns (bool) { if (flag) emit IsTrue(true); else emit IsFalse(false); - if (flag) flag = true; - else flag = false; + bool flag2; + if (flag) flag2 = true; + else flag2 = false; + + if (flag2) return true; + else return false; } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1233,19 +1274,18 @@ import "./test.sol"; import {AContract} from "./AContract.sol"; contract AContractTest is DSTest { + AContract a = new AContract(); + function testTrueCoverage() external { - AContract a = new AContract(); a.ifElseStatementIgnored(true); } function testFalseCoverage() external { - AContract a = new AContract(); a.ifElseStatementIgnored(false); } } "#, - ) - .unwrap(); + ); // Assert 50% coverage for true branches. cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" @@ -1253,9 +1293,9 @@ contract AContractTest is DSTest { ╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | +================================================================================+ -| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| src/AContract.sol | 62.50% (5/8) | 57.14% (4/7) | 50.00% (3/6) | 100.00% (1/1) | |-------------------+--------------+--------------+--------------+---------------| -| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| Total | 62.50% (5/8) | 57.14% (4/7) | 50.00% (3/6) | 100.00% (1/1) | ╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); @@ -1270,9 +1310,9 @@ contract AContractTest is DSTest { ╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | +================================================================================+ -| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| src/AContract.sol | 62.50% (5/8) | 57.14% (4/7) | 50.00% (3/6) | 100.00% (1/1) | |-------------------+--------------+--------------+--------------+---------------| -| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| Total | 62.50% (5/8) | 57.14% (4/7) | 50.00% (3/6) | 100.00% (1/1) | ╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); @@ -1283,14 +1323,100 @@ contract AContractTest is DSTest { ╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | +===================================================================================+ -| src/AContract.sol | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (8/8) | 100.00% (7/7) | 100.00% (6/6) | 100.00% (1/1) | |-------------------+---------------+---------------+---------------+---------------| -| Total | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +| Total | 100.00% (8/8) | 100.00% (7/7) | 100.00% (6/6) | 100.00% (1/1) | ╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); +forgetest!(single_statement_loop, |prj, cmd| { + // TODO(dani): the specific case of `if (x) continue/break` is not properly covered. + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + function ifBreakContinueIgnored(bool flag) external returns (uint256 sum) { + for (uint256 i = 0; i < 5; i++) { + if (flag) continue; + sum += i; + } + + for (uint256 i = 0; i < 5; i++) { + if (flag) break; + sum += i; + } + } +} + "#, + ); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + AContract a = new AContract(); + + function testTrueCoverage() external { + a.ifBreakContinueIgnored(true); + } + + function testFalseCoverage() external { + a.ifBreakContinueIgnored(false); + } +} + "#, + ); + + // Assert 50% coverage for true branches. + cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" +... +╭-------------------+--------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++==================================================================================+ +| src/AContract.sol | 71.43% (5/7) | 70.00% (7/10) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+--------------+---------------+---------------+---------------| +| Total | 71.43% (5/7) | 70.00% (7/10) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+--------------+---------------+---------------+---------------╯ + +"#]]); + + // Assert 50% coverage for false branches. + cmd.forge_fuse() + .arg("coverage") + .args(["--mt", "testFalseCoverage"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------------+---------------+-----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=====================================================================================+ +| src/AContract.sol | 100.00% (7/7) | 100.00% (10/10) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+-----------------+---------------+---------------| +| Total | 100.00% (7/7) | 100.00% (10/10) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+-----------------+---------------+---------------╯ + +"#]]); + + // Assert 100% coverage (true/false branches properly covered). + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+-----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=====================================================================================+ +| src/AContract.sol | 100.00% (7/7) | 100.00% (10/10) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+-----------------+---------------+---------------| +| Total | 100.00% (7/7) | 100.00% (10/10) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+-----------------+---------------+---------------╯ + +"#]]); +}); + // https://github.com/foundry-rs/foundry/issues/8604 forgetest!(branch_with_calldata_reads, |prj, cmd| { prj.insert_ds_test(); @@ -1312,8 +1438,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1322,23 +1447,22 @@ import "./test.sol"; import {AContract} from "./AContract.sol"; contract AContractTest is DSTest { + AContract a = new AContract(); + function testTrueCoverage() external { - AContract a = new AContract(); bool[] memory isTrue = new bool[](1); isTrue[0] = true; a.execute(isTrue); } function testFalseCoverage() external { - AContract a = new AContract(); bool[] memory isFalse = new bool[](1); isFalse[0] = false; a.execute(isFalse); } } "#, - ) - .unwrap(); + ); // Assert 50% coverage for true branches. cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" @@ -1417,8 +1541,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1440,8 +1563,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1477,8 +1599,7 @@ contract BContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1493,8 +1614,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1526,8 +1646,7 @@ contract AContract { function increment() public {} } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1544,8 +1663,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); assert_lcov( cmd.arg("coverage"), @@ -1598,8 +1716,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1608,15 +1725,15 @@ import "./test.sol"; import "./AContract.sol"; contract AContractTest is DSTest { + AContract a = new AContract(); + function test_constructors() public { - AContract a = new AContract(); address(a).call{value: 5}(""); require(a.counter() == 5); } } "#, - ) - .unwrap(); + ); // Assert both constructor and receive functions coverage reported and appear in LCOV. assert_lcov( @@ -1676,8 +1793,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); // Assert coverage doesn't fail with `Error: Unknown key "inliner"`. cmd.arg("coverage").arg("--ir-minimum").assert_success().stdout_eq(str![[r#" @@ -1710,8 +1826,7 @@ contract AContract { } } "#, - ) - .unwrap(); + ); prj.add_source( "AContractTest.sol", @@ -1732,8 +1847,7 @@ contract AContractTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1785,8 +1899,7 @@ contract ArrayCondition { } } "#, - ) - .unwrap(); + ); prj.add_source( "ArrayConditionTest.sol", @@ -1829,8 +1942,7 @@ contract ArrayConditionTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... @@ -1845,6 +1957,58 @@ contract ArrayConditionTest is DSTest { "#]]); }); +// https://github.com/foundry-rs/foundry/issues/11432 +// Test coverage for linked libraries. +forgetest!(linked_library, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +library LibCounter { + function increment(uint256 number) external returns (uint256) { + return number + 1; + } +} + +contract Counter { + uint256 public number; + + function increment() public { + number = LibCounter.increment(number); + } +} + "#, + ); + + prj.add_source( + "CounterTest.sol", + r#" +import "./test.sol"; +import {Counter} from "./Counter.sol"; + +contract CounterTest is DSTest { + function testIncrement() public { + Counter counter = new Counter(); + counter.increment(); + } +} + "#, + ); + + // Assert 100% coverage for linked libraries. + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-----------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/Counter.sol | 100.00% (4/4) | 100.00% (3/3) | 100.00% (0/0) | 100.00% (2/2) | +|-----------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (3/3) | 100.00% (0/0) | 100.00% (2/2) | +╰-----------------+---------------+---------------+---------------+---------------╯ +... +"#]]); +}); + // // Test that line hits are properly recorded in lcov report. forgetest!(do_while_lcov, |prj, cmd| { @@ -1866,8 +2030,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_source( "Counter.t.sol", @@ -1882,8 +2045,7 @@ contract CounterTest is DSTest { } } "#, - ) - .unwrap(); + ); assert_lcov( cmd.arg("coverage"), @@ -1894,15 +2056,15 @@ DA:7,1 FN:7,Counter.increment FNDA:1,Counter.increment DA:8,1 -DA:14,10 DA:10,10 DA:11,10 BRDA:11,0,0,6 DA:12,6 +DA:14,10 FNF:1 FNH:1 -LF:3 -LH:3 +LF:6 +LH:6 BRF:1 BRH:1 end_of_record @@ -1911,7 +2073,280 @@ end_of_record ); }); -#[track_caller] -fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { - cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data()); +// +// Test that overridden functions are disambiguated in the LCOV report. +forgetest!(disambiguate_functions, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function increment() public { + number++; + } + function increment(uint256 amount) public { + number += amount; + } +} + "#, + ); + + prj.add_source( + "Counter.t.sol", + r#" +import "./test.sol"; +import "./Counter.sol"; + +contract CounterTest is DSTest { + function test_overridden() public { + Counter counter = new Counter(); + counter.increment(); + counter.increment(1); + counter.increment(2); + counter.increment(3); + assertEq(counter.number(), 7); + } +} + "#, + ); + + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/Counter.sol +DA:7,1 +FN:7,Counter.increment.0 +FNDA:1,Counter.increment.0 +DA:8,1 +DA:10,3 +FN:10,Counter.increment.1 +FNDA:3,Counter.increment.1 +DA:11,3 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); +}); + +// Test that functions of abstract contracts and interfaces should not count in coverage report. +forgetest!(abstract_contract_and_interface, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +interface ContractIf { + function setNumber(uint256 newNumber) external; +} + +abstract contract AbstractCounter { + function _setNumber(uint256 newNumber) internal virtual; + + function _incrementNumber(uint256 newNumber) internal virtual returns (uint256 inc) { + inc = newNumber + 1; + } } + +contract Counter is AbstractCounter, ContractIf { + uint256 public number; + + function setNumber(uint256 newNumber) public { + _setNumber(newNumber); + } + + function _setNumber(uint256 newNumber) internal override { + number = _incrementNumber(newNumber); + } + + function _incrementNumber(uint256 newNumber) internal override returns (uint256 inc) { + inc = super._incrementNumber(newNumber); + } +} + "#, + ); + prj.add_source( + "CounterTest.sol", + r#" +import "./test.sol"; +import {Counter} from "./Counter.sol"; + +contract CounterTest is DSTest { + function testCounter() public { + Counter counter = new Counter(); + counter.setNumber(0); + } +} + "#, + ); + + // Test there are 4 functions reported: + // - `setNumber`, `_setNumber` and `_incrementNumber` from `Counter` contract + // - `_incrementNumber` from `AbstractCounter` (virtual with implementation). `_setNumber` is + // excluded as it is not implemented. + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-----------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/Counter.sol | 100.00% (8/8) | 100.00% (4/4) | 100.00% (0/0) | 100.00% (4/4) | +|-----------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (8/8) | 100.00% (4/4) | 100.00% (0/0) | 100.00% (4/4) | +╰-----------------+---------------+---------------+---------------+---------------╯ +... +"#]]); +}); + +// +// Test BRDA hit values follow LCOV spec: "-" when line never executed, "0" when line hit but +// branch not taken. This ensures `genhtml` consistency. +forgetest!(brda_lcov_consistency, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setPositive(uint256 newNumber) public { + if (newNumber > 0) { + number = newNumber; + } else { + number = 1; + } + } + + function neverCalled(uint256 x) public { + if (x > 100) { + number = x; + } else { + number = 100; + } + } +} + "#, + ); + + prj.add_source( + "Counter.t.sol", + r#" +import "./test.sol"; +import "./Counter.sol"; + +contract CounterTest is DSTest { + function test_only_positive_branch() public { + Counter counter = new Counter(); + counter.setPositive(42); + counter.setPositive(100); + } +} + "#, + ); + + // Verify BRDA values: + // - BRDA:8,0,0,2 - if branch taken 2 times + // - BRDA:8,0,1,0 - else branch NOT taken but line was hit (outputs "0", not "-") + // - BRDA:16,1,0,- - if branch NOT taken AND line never executed (outputs "-") + // - BRDA:16,1,1,- - else branch NOT taken AND line never executed (outputs "-") + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/Counter.sol +DA:7,2 +FN:7,Counter.setPositive +FNDA:2,Counter.setPositive +DA:8,2 +BRDA:8,0,0,2 +BRDA:8,0,1,0 +DA:9,2 +DA:11,0 +DA:15,0 +FN:15,Counter.neverCalled +FNDA:0,Counter.neverCalled +DA:16,0 +BRDA:16,1,0,- +BRDA:16,1,1,- +DA:17,0 +DA:19,0 +FNF:2 +FNH:1 +LF:8 +LH:3 +BRF:4 +BRH:1 +end_of_record + +"#]], + ); +}); + +// Test that coverage files are written even when tests fail. +forgetest!(coverage_with_failing_tests, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + "#, + ); + + prj.add_source( + "CounterTest.sol", + r#" +import "./test.sol"; +import {Counter} from "./Counter.sol"; + +contract CounterTest is DSTest { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function test_FailingTest() public { + counter.increment(); + // This assertion will fail + assertEq(counter.number(), 999); + } +} + "#, + ); + + // Run coverage - this should exit with error code 1 due to failing test, + // but the lcov file should still be written. + cmd.arg("coverage").args(["--report=lcov"]).assert_failure(); + + // Verify that the lcov.info file was created despite test failure + let lcov = prj.root().join("lcov.info"); + assert!(lcov.exists(), "lcov.info should be created even when tests fail"); + + // Verify the coverage data is valid and includes the counter contract + let lcov_content = std::fs::read_to_string(&lcov).unwrap(); + assert!(lcov_content.contains("SF:src/Counter.sol"), "Coverage should include Counter.sol"); + assert!(lcov_content.contains("FN:"), "Coverage should include function data"); + assert!(lcov_content.contains("DA:"), "Coverage should include line hit data"); +}); diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 01bb1f01b31a0..053d29454bbd4 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -4,9 +4,9 @@ use crate::{ constants::*, utils::{self, EnvExternalities}, }; -use alloy_primitives::{hex, Address}; -use anvil::{spawn, NodeConfig}; -use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; +use alloy_primitives::{Address, hex}; +use anvil::{NodeConfig, spawn}; +use foundry_compilers::artifacts::{BytecodeHash, remappings::Remapping}; use foundry_test_utils::{ forgetest, forgetest_async, snapbox::IntoData, @@ -40,8 +40,7 @@ contract LinkTest { } } "#, - ) - .unwrap(); + ); prj.add_lib( "remapping/MyLib", @@ -52,8 +51,7 @@ library MyLib { } } ", - ) - .unwrap(); + ); "src/LinkTest.sol:LinkTest".to_string() } @@ -76,8 +74,7 @@ contract Contract { } } "#, - ) - .unwrap(); + ); prj.add_source( "libraries/ChainlinkTWAP", @@ -88,8 +85,7 @@ library ChainlinkTWAP { } } ", - ) - .unwrap(); + ); "src/Contract.sol:Contract".to_string() } @@ -124,14 +120,15 @@ forgetest!(can_create_oracle_on_goerli, |prj, cmd| { create_on_chain(EnvExternalities::goerli(), prj, cmd, setup_oracle); }); -// tests `forge` create on mumbai if correct env vars are set -forgetest!(can_create_oracle_on_mumbai, |prj, cmd| { - create_on_chain(EnvExternalities::mumbai(), prj, cmd, setup_oracle); +// tests `forge` create on amoy if correct env vars are set +forgetest!(can_create_oracle_on_amoy, |prj, cmd| { + create_on_chain(EnvExternalities::amoy(), prj, cmd, setup_oracle); }); // tests that we can deploy the template contract forgetest_async!(can_create_template_contract, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); @@ -284,6 +281,7 @@ Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 // tests that we can deploy the template contract forgetest_async!(can_create_using_unlocked, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); @@ -345,8 +343,7 @@ contract ConstructorContract { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse() .args([ @@ -383,8 +380,7 @@ contract TupleArrayConstructorContract { constructor(Point[] memory _points) {} } "#, - ) - .unwrap(); + ); cmd.forge_fuse() .args([ @@ -433,8 +429,7 @@ contract UniswapV2Swap { } "#, - ) - .unwrap(); + ); cmd.forge_fuse() .args([ @@ -484,8 +479,7 @@ abstract contract AbstractCounter { } } "#, - ) - .unwrap(); + ); cmd.args([ "create", @@ -502,3 +496,42 @@ Error: no bytecode found in bin object for AbstractCounter "#]]); }); + +// Tests that `forge create` fails when the deployment transaction reverts +// +forgetest_async!(flaky_should_fail_on_reverted_deployment, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.credential().to_bytes()); + + prj.add_source( + "RevertingContract.sol", + r#" +contract RevertingContract { + constructor() { + revert("deployment failed"); + } +} + "#, + ); + + // Use --gas-limit to bypass eth_estimateGas, which would reject the tx early. + // This simulates chains that mine reverted txs (e.g. when gas is manually specified). + cmd.args([ + "create", + "./src/RevertingContract.sol:RevertingContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--broadcast", + "--gas-limit", + "1000000", + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: deployment transaction failed (receipt status 0): [..] + +"#]]); +}); diff --git a/crates/forge/tests/cli/debug.rs b/crates/forge/tests/cli/debug.rs index c217beeb501ff..e58b8c1c69216 100644 --- a/crates/forge/tests/cli/debug.rs +++ b/crates/forge/tests/cli/debug.rs @@ -12,7 +12,7 @@ forgetest!( .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -51,8 +51,7 @@ contract A { a = _a; } }"#, - ) - .unwrap(); + ); let script = prj.add_script("Counter.s.sol", r#" import "../src/Counter2.sol"; @@ -84,8 +83,7 @@ contract Script0 is Script, Test { bytes32 _d2 = b.other().d(); } }"#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["build"]).assert_success(); diff --git a/crates/forge/tests/cli/doc.rs b/crates/forge/tests/cli/doc.rs index 699b023d0b26e..947c7a6a9acec 100644 --- a/crates/forge/tests/cli/doc.rs +++ b/crates/forge/tests/cli/doc.rs @@ -1,4 +1,4 @@ -use foundry_test_utils::util::{setup_forge_remote, RemoteProject}; +use foundry_test_utils::util::{RemoteProject, setup_forge_remote}; #[test] fn can_generate_solmate_docs() { @@ -6,3 +6,284 @@ fn can_generate_solmate_docs() { setup_forge_remote(RemoteProject::new("transmissions11/solmate").set_build(false)); prj.forge_command().args(["doc", "--build"]).assert_success(); } + +// Test that overloaded functions in interfaces inherit the correct NatSpec comments +// fixes +forgetest_init!(can_generate_docs_for_overloaded_functions, |prj, cmd| { + prj.add_source( + "IExample.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IExample { + /// @notice Process a single address + /// @param addr The address to process + function process(address addr) external; + + /// @notice Process multiple addresses + /// @param addrs The addresses to process + function process(address[] calldata addrs) external; + + /// @notice Process an address with a value + /// @param addr The address to process + /// @param value The value to use + function process(address addr, uint256 value) external; +} +"#, + ); + + prj.add_source( + "Example.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IExample.sol"; + +contract Example is IExample { + /// @inheritdoc IExample + function process(address addr) external { + // Implementation for single address + } + + /// @inheritdoc IExample + function process(address[] calldata addrs) external { + // Implementation for multiple addresses + } + + /// @inheritdoc IExample + function process(address addr, uint256 value) external { + // Implementation for address with value + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = prj.root().join("docs/src/src/Example.sol/contract.Example.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + assert!(content.contains("Process a single address")); + assert!(content.contains("Process multiple addresses")); + assert!(content.contains("Process an address with a value")); +}); + +// Test that hyperlinks use relative paths, not absolute paths +// fixes +forgetest_init!(hyperlinks_use_relative_paths, |prj, cmd| { + prj.add_source( + "IBase.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IBase { + function baseFunction() external; +} +"#, + ); + + prj.add_source( + "Derived.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IBase.sol"; + +/// @dev Inherits: {IBase} +contract Derived is IBase { + function baseFunction() external override {} +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = prj.root().join("docs/src/src/Derived.sol/contract.Derived.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + assert!( + content.contains("[IBase](/src/IBase.sol/interface.IBase.md") + || content.contains("[IBase](\\src\\IBase.sol\\interface.IBase.md"), + "Hyperlink should use relative path but found: {:?}", + content.lines().find(|line| line.contains("[IBase]")).unwrap_or("not found") + ); +}); + +// Test that constants and immutables are documented under "Constants" section when only constants +// are present fixes +forgetest_init!(constants_and_immutables_are_documented_under_constants_section, |prj, cmd| { + prj.add_source( + "CounterConstants.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterConstants { + uint256 public constant FOO = 1; + uint256 public immutable BAR; + + constructor() { + BAR = 2; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = + prj.root().join("docs/src/src/CounterConstants.sol/contract.CounterConstants.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section exists + assert!(content.contains("## Constants"), "Should have Constants section"); + // Check that State Variables section does not exist + assert!(!content.contains("## State Variables"), "Should not have State Variables section"); + + // Get the position of the Constants section and of the Functions section + let constants_section_pos = content.find("## Constants").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Check that Constants section contains the constant + assert!(content.contains("### FOO"), "Should have FOO constant"); + let foo_constant_pos = content.find("## FOO").unwrap(); + assert!( + foo_constant_pos > constants_section_pos && foo_constant_pos < functions_section_pos, + "FOO constant should be after Constants section and before Functions section" + ); + + // Check that Constants section contains the immutable + let bar_immutable_pos = content.find("## BAR").unwrap(); + assert!(content.contains("### BAR"), "Should have BAR immutable"); + assert!( + bar_immutable_pos > constants_section_pos && bar_immutable_pos < functions_section_pos, + "BAR immutable should be after Constants section and before Functions section" + ); +}); + +// Test that state variables are documented under "State Variables" section when only state +// variables are present fixes +forgetest_init!(state_variables_are_documented_under_state_variables_section, |prj, cmd| { + prj.add_source( + "CounterStateVariables.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterStateVariables { + uint256 public baz; + + function increment() public { + baz++; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = + prj.root().join("docs/src/src/CounterStateVariables.sol/contract.CounterStateVariables.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section does not exist + assert!(!content.contains("## Constants"), "Should not have Constants section"); + // Check that State Variables section exists + assert!(content.contains("## State Variables"), "Should have State Variables section"); + + // Get the position of the State Variables section and of the Functions section + let state_variables_section_pos = content.find("## State Variables").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Check that State Variables section contains the state variable + assert!(content.contains("### baz"), "Should have baz state variable"); + let baz_state_variable_pos = content.find("## baz").unwrap(); + assert!( + baz_state_variable_pos > state_variables_section_pos + && baz_state_variable_pos < functions_section_pos, + "baz state variable should be after State Variables section and before Functions section" + ); +}); + +// Test that constants/immutables and state-variables are documented under separate sections when +// both are present fixes +forgetest_init!( + constants_and_immutables_and_state_variables_are_documented_under_separate_sections, + |prj, cmd| { + prj.add_source( + "CounterMixedVariables.sol", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +contract CounterMixedVariables { + uint256 public constant FOO = 1; + uint256 public immutable BAR; + uint256 public baz; + + constructor() { + BAR = 2; + } + + function increment() public { + baz++; + } +} +"#, + ); + + cmd.args(["doc", "--build"]).assert_success(); + + let doc_path = prj + .root() + .join("docs/src/src/CounterMixedVariables.sol/contract.CounterMixedVariables.md"); + let content = std::fs::read_to_string(&doc_path).unwrap(); + + // Check that Constants section and the State Variables section exist + assert!(content.contains("## Constants"), "Should have Constants section"); + assert!(content.contains("## State Variables"), "Should have State Variables section"); + + // Get the position of the Constants, State Variables, and Functions sections + let constants_section_pos = content.find("## Constants").unwrap(); + let state_variables_section_pos = content.find("## State Variables").unwrap(); + let functions_section_pos = content.find("## Functions").unwrap(); + + // Validate that the sections are in the correct order + assert!( + constants_section_pos < state_variables_section_pos + && state_variables_section_pos < functions_section_pos, + "Constants section should be before State Variables section and before Functions section" + ); + + // Check that Constants section contains the constant + assert!(content.contains("### FOO"), "Should have FOO constant"); + let foo_constant_pos = content.find("## FOO").unwrap(); + assert!( + foo_constant_pos > constants_section_pos + && foo_constant_pos < state_variables_section_pos, + "FOO constant should be after Constants section and before State Variables section" + ); + + // Check that Constants section contains the immutable + assert!(content.contains("### BAR"), "Should have BAR immutable"); + let bar_immutable_pos = content.find("## BAR").unwrap(); + assert!( + bar_immutable_pos > constants_section_pos + && bar_immutable_pos < state_variables_section_pos, + "BAR immutable should be after Constants section and before State Variables section" + ); + + // Check that State Variables section contains the state variable + assert!(content.contains("### baz"), "Should have baz state variable"); + let baz_state_variable_pos = content.find("## baz").unwrap(); + assert!( + baz_state_variable_pos > state_variables_section_pos + && baz_state_variable_pos < functions_section_pos, + "baz state variable should be after State Variables section and before Functions section" + ); + } +); diff --git a/crates/forge/tests/cli/eip712.rs b/crates/forge/tests/cli/eip712.rs index 9ec944631d9db..919f93366b23b 100644 --- a/crates/forge/tests/cli/eip712.rs +++ b/crates/forge/tests/cli/eip712.rs @@ -1,8 +1,9 @@ +use foundry_config::fs_permissions::PathPermission; + forgetest!(test_eip712, |prj, cmd| { - let path = prj - .add_source( - "Structs", - r#" + let path = prj.add_test( + "Structs.sol", + r#" library Structs { struct Foo { Bar bar; @@ -49,34 +50,832 @@ library Structs2 { Structs.Rec rec; } } + +contract DummyTest { + function testDummy() public pure { + revert("test"); + } +} "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref()]).assert_success().stdout_eq( str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] -No files changed, compilation skipped -Foo(Bar bar)Art(uint256 id)Bar(Art art) +Compiler run successful! +Structs.sol > Structs > Foo: + - type: Foo(Bar bar)Art(uint256 id)Bar(Art art) + - hash: 0x6d9b732373bd999fde4072274c752e03f7437067dd75521eb406d8edf1d30f7d + +Structs.sol > Structs > Bar: + - type: Bar(Art art)Art(uint256 id) + - hash: 0xadeb03f4f98fb57c05c9a79d8dd2348220e9bd9fd332ec2fbd92479e5695a596 + +Structs.sol > Structs > Art: + - type: Art(uint256 id) + - hash: 0xbfeb9da97f9dbc2403e9d5ec3853f36414cae141d772601f24e0097d159d302b + +Structs.sol > Structs > Complex: + - type: Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec) + - hash: 0xfb0a234a82efcade7c031ebb4c58afd7f5f242ca67ed06f4050c60044dcee425 + +Structs.sol > Structs > Rec: + - type: Rec(Rec[] rec) + - hash: 0x5f060eb740f5aee93a910587a100458c724479d189f6dd67ac39048bf312102e + +Structs.sol > Structs2 > Foo: + - type: Foo(uint256 id) + - hash: 0xb93d8bb2877cd5cc51979d9fe85339ab570714a6fd974225e2a763851092497e + +Structs.sol > Structs2 > Rec: + - type: Rec(Bar[] bar)Bar(Rec rec) + - hash: 0xe9dded72c72648f27772620cb4e10b773ce31a3ea26ef980c0b39d1834242cda + +Structs.sol > Structs2 > Bar: + - type: Bar(Rec rec)Rec(Bar[] bar) + - hash: 0x164eba932ecde04ec75feba228664d08f29c88d6a67e531757e023e6063c3b2c + +Structs.sol > Structs2 > FooBar: + - type: FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec) + - hash: 0xce88f333fe5b5d4901ceb2569922ffe741cda3afc383a63d34ed2c3d565e42d8 + + +"#]], + ); + + cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref(), "--json"]).assert_success().stdout_eq( + str![[r#" +[ + { + "path": "Structs.sol > Structs > Foo", + "type": "Foo(Bar bar)Art(uint256 id)Bar(Art art)", + "hash": "0x6d9b732373bd999fde4072274c752e03f7437067dd75521eb406d8edf1d30f7d" + }, + { + "path": "Structs.sol > Structs > Bar", + "type": "Bar(Art art)Art(uint256 id)", + "hash": "0xadeb03f4f98fb57c05c9a79d8dd2348220e9bd9fd332ec2fbd92479e5695a596" + }, + { + "path": "Structs.sol > Structs > Art", + "type": "Art(uint256 id)", + "hash": "0xbfeb9da97f9dbc2403e9d5ec3853f36414cae141d772601f24e0097d159d302b" + }, + { + "path": "Structs.sol > Structs > Complex", + "type": "Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec)", + "hash": "0xfb0a234a82efcade7c031ebb4c58afd7f5f242ca67ed06f4050c60044dcee425" + }, + { + "path": "Structs.sol > Structs > Rec", + "type": "Rec(Rec[] rec)", + "hash": "0x5f060eb740f5aee93a910587a100458c724479d189f6dd67ac39048bf312102e" + }, + { + "path": "Structs.sol > Structs2 > Foo", + "type": "Foo(uint256 id)", + "hash": "0xb93d8bb2877cd5cc51979d9fe85339ab570714a6fd974225e2a763851092497e" + }, + { + "path": "Structs.sol > Structs2 > Rec", + "type": "Rec(Bar[] bar)Bar(Rec rec)", + "hash": "0xe9dded72c72648f27772620cb4e10b773ce31a3ea26ef980c0b39d1834242cda" + }, + { + "path": "Structs.sol > Structs2 > Bar", + "type": "Bar(Rec rec)Rec(Bar[] bar)", + "hash": "0x164eba932ecde04ec75feba228664d08f29c88d6a67e531757e023e6063c3b2c" + }, + { + "path": "Structs.sol > Structs2 > FooBar", + "type": "FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec)", + "hash": "0xce88f333fe5b5d4901ceb2569922ffe741cda3afc383a63d34ed2c3d565e42d8" + } +] + +"#]], + ); + + // Testing `solar_project` doesn't mess up cache. + cmd.forge_fuse().arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Structs.sol:DummyTest +[FAIL: test] testDummy() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Structs.sol:DummyTest +[FAIL: test] testDummy() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded -Bar(Art art)Art(uint256 id) +Tip: Run `forge test --rerun` to retry only the 1 failed test -Art(uint256 id) +"#]]); +}); -Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec) +forgetest!(test_eip712_free_standing_structs, |prj, cmd| { + let path = prj.add_source( + "FreeStandingStructs.sol", + r#" +// free-standing struct (outside a contract and lib) +struct FreeStanding { + uint256 id; + string name; +} -Rec(Rec[] rec) +contract InsideContract { + struct ContractStruct { + uint256 value; + } +} -Foo(uint256 id) +library InsideLibrary { + struct LibraryStruct { + bytes32 hash; + } +} +"#, + ); -Rec(Bar[] bar)Bar(Rec rec) + cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref()]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +FreeStanding: + - type: FreeStanding(uint256 id,string name) + - hash: 0xfb3c934b2382873277133498bde6eb3914ab323e3bef8b373ebcd423969bf1a2 -Bar(Rec rec)Rec(Bar[] bar) +FreeStandingStructs.sol > InsideContract > ContractStruct: + - type: ContractStruct(uint256 value) + - hash: 0xfb63263e7cf823ff50385a991cb1bd5c1ff46b58011119984d52f8736331e3fe -FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec) +FreeStandingStructs.sol > InsideLibrary > LibraryStruct: + - type: LibraryStruct(bytes32 hash) + - hash: 0x81d6d25f4d37549244d76a68f23ecdcbf3ae81e5a361ed6c492b6a2e126a2843 "#]], ); }); + +forgetest!(test_eip712_cheatcode_simple, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712", + r#" +contract Eip712Structs { + struct EIP712Domain { + string name; + string version; + uint256 chainId; + address verifyingContract; + } +} + "#, + ); + + prj.add_source("Eip712Cheat.sol", r#" +import "./test.sol"; +import "./Vm.sol"; +import "./console.sol"; + +string constant CANONICAL = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; + +contract Eip712Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEip712HashType() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + console.logBytes32(canonicalHash); + + // Can figure out the canonical type from a messy string representation of the type, + // with an invalid order and extra whitespaces + bytes32 fromTypeDef = vm.eip712HashType( + "EIP712Domain(string name, string version, uint256 chainId, address verifyingContract)" + ); + assertEq(fromTypeDef, canonicalHash); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("EIP712Domain"); + assertEq(fromTypeName, canonicalHash); + } +} +"#, + ); + + cmd.forge_fuse().args(["bind-json"]).assert_success(); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + assert!(bindings.exists(), "'JsonBindings.sol' was not generated at {bindings:?}"); + + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(bindings))); + cmd.forge_fuse().args(["test", "--mc", "Eip712Test", "-vv"]).assert_success().stdout_eq(str![ + [r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[PASS] testEip712HashType() ([GAS]) +Logs: + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#] + ]); +}); + +forgetest!(test_eip712_cheatcode_nested, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712", + r#" +contract Eip712Structs { + struct Transaction { + Person from; + Person to; + Asset tx; + } + struct Person { + address wallet; + string name; + } + struct Asset { + address token; + uint256 amount; + } +} + "#, + ); + + prj.add_source("Eip712Cheat.sol", r#" +import "./test.sol"; +import "./Vm.sol"; + +string constant CANONICAL = "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)"; + +contract Eip712Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEip712HashType_byDefinition() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from a messy string representation of the type, + // with an invalid order and extra whitespaces + bytes32 fromTypeDef = vm.eip712HashType( + "Person(address wallet, string name) Asset(address token, uint256 amount) Transaction(Person from, Person to, Asset tx)" + ); + assertEq(fromTypeDef, canonicalHash); + } + + function testEip712HashType_byTypeName() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("Transaction"); + assertEq(fromTypeName, canonicalHash); + } + + function testReverts_Eip712HashType_invalidName() public { + // Reverts if the input type is not found in the bindings + vm._expectCheatcodeRevert(); + bytes32 fromTypeName = vm.eip712HashType("InvalidTypeName"); + } + + function testEip712HashType_byCustomPathAndTypeName() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("utils/CustomJsonBindings.sol", "Transaction"); + assertEq(fromTypeName, canonicalHash); + } +} +"#, + ); + + // cheatcode by type definition can run without bindings + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byDefinition"]) + .assert_success(); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(&bindings))); + + // cheatcode by type name fails if bindings haven't been generated + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byTypeName"]) + .assert_failure() + .stdout_eq(str![[r#" +... +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byTypeName() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byTypeName() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); + + cmd.forge_fuse().args(["bind-json"]).assert_success(); + assert!(bindings.exists(), "'JsonBindings.sol' was not generated at {bindings:?}"); + + // with generated bindings, cheatcode by type name works + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byTypeName"]) + .assert_success(); + + // even with generated bindings, cheatcode by type name fails if name is not present + cmd.forge_fuse() + .args([ + "test", + "--mc", + "Eip712Test", + "--match-test", + "testReverts_Eip712HashType_invalidName", + ]) + .assert_success(); + + let bindings_2 = prj.root().join("utils").join("CustomJsonBindings.sol"); + prj.update_config(|config| { + config.fs_permissions.add(PathPermission::read(&bindings_2)); + }); + + // cheatcode by custom path and type name fails if bindings haven't been generated for that path + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byCustomPathAndTypeName"]) + .assert_failure() + .stdout_eq(str![[r#" +... +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byCustomPathAndTypeName() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byCustomPathAndTypeName() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); + + cmd.forge_fuse().args(["bind-json", "utils/CustomJsonBindings.sol"]).assert_success(); + assert!(bindings_2.exists(), "'CustomJsonBindings.sol' was not generated at {bindings_2:?}"); + + // with generated bindings, cheatcode by custom path and type name works + cmd.forge_fuse() + .args([ + "test", + "--mc", + "Eip712Test", + "--match-test", + "testEip712HashType_byCustomPathAndTypeName", + ]) + .assert_success(); +}); + +forgetest!(test_eip712_hash_struct_simple, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712HashStructDomainTest.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; + +struct EIP712Domain { + string name; + string version; + uint256 chainId; + address verifyingContract; +} + +string constant _EIP712_DOMAIN_TYPE_DEF = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; +bytes32 constant _EIP712_DOMAIN_TYPE_HASH = keccak256(bytes(_EIP712_DOMAIN_TYPE_DEF)); + +contract Eip712HashStructDomainTest is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function testHashEIP712Domain() public { + EIP712Domain memory domain = EIP712Domain({ + name: "Foo", + version: "Bar", + chainId: 1, + verifyingContract: 0xdEADBEeF00000000000000000000000000000000 + }); + + // simulate user-computed domain hash + bytes memory encodedData = abi.encode( + keccak256(bytes(domain.name)), + keccak256(bytes(domain.version)), + bytes32(domain.chainId), + bytes32(uint256(uint160(domain.verifyingContract))) + ); + bytes32 userStructHash = keccak256(abi.encodePacked(_EIP712_DOMAIN_TYPE_HASH, encodedData)); + + // cheatcode-computed domain hash + bytes32 cheatStructHash = vm.eip712HashStruct(_EIP712_DOMAIN_TYPE_DEF, abi.encode(domain)); + console.log("EIP712Domain struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "EIP712Domain struct hash mismatch"); + } +} +"#, + ); + + cmd.forge_fuse().args(["test", "--mc", "Eip712HashStructDomainTest", "-vvvv"]).assert_success(); +}); + +forgetest!(test_eip712_hash_struct_complex, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712Permit.sol", + r#" +struct PermitDetails { + address token; + uint160 amount; + uint48 expiration; + uint48 nonce; +} + +bytes32 constant _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" +); + +struct PermitSingle { + PermitDetails details; + address spender; + uint256 sigDeadline; +} + +bytes32 constant _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" +); + +// borrowed from https://github.com/Uniswap/permit2/blob/main/src/libraries/PermitHash.sol +library PermitHash { + function hash(PermitSingle memory permitSingle) internal pure returns (bytes32) { + bytes32 permitHash = _hashDetails(permitSingle.details); + return + keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); + } + + function _hashDetails(PermitDetails memory details) internal pure returns (bytes32) { + return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); + } +} +"#, + ); + + prj.add_source( + "Eip712Transaction.sol", + r#" +struct Asset { + address token; + uint256 amount; +} + +bytes32 constant _ASSET_TYPEHASH = keccak256( + "Asset(address token,uint256 amount)" +); + +struct Person { + address wallet; + string name; +} + +bytes32 constant _PERSON_TYPEHASH = keccak256( + "Person(address wallet,string name)" +); + +struct Transaction { + Person from; + Person to; + Asset tx; +} + +bytes32 constant _TRANSACTION_TYPEHASH = keccak256( + "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)" +); + + +library TransactionHash { + function hash(Transaction memory t) internal pure returns (bytes32) { + bytes32 fromHash = _hashPerson(t.from); + bytes32 toHash = _hashPerson(t.to); + bytes32 assetHash = _hashAsset(t.tx); + return + keccak256(abi.encode(_TRANSACTION_TYPEHASH, fromHash, toHash, assetHash)); + } + + function _hashPerson(Person memory person) internal pure returns (bytes32) { + return keccak256( + abi.encode(_PERSON_TYPEHASH, person.wallet, keccak256(bytes(person.name))) + ); + + } + + function _hashAsset(Asset memory asset) internal pure returns (bytes32) { + return keccak256(abi.encode(_ASSET_TYPEHASH, asset)); + } +} + "#, + ); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(&bindings))); + cmd.forge_fuse().args(["bind-json"]).assert_success(); + + prj.add_source( + "Eip712HashStructTest.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; +import "./Eip712Permit.sol"; +import "./Eip712Transaction.sol"; + +contract Eip712HashStructTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testHashPermitSingle_withTypeName() public { + PermitDetails memory details = PermitDetails({ + token: 0x1111111111111111111111111111111111111111, + amount: 1000 ether, + expiration: 12345, + nonce: 1 + }); + + // user-computed permit (using uniswap hash library) + bytes32 userStructHash = PermitHash._hashDetails(details); + + // cheatcode-computed permit + bytes32 cheatStructHash = vm.eip712HashStruct("PermitDetails", abi.encode(details)); + + assertEq(cheatStructHash, userStructHash, "details struct hash mismatch"); + + PermitSingle memory permit = PermitSingle({ + details: details, + spender: 0x2222222222222222222222222222222222222222, + sigDeadline: 12345 + }); + + // user-computed permit (using uniswap hash library) + userStructHash = PermitHash.hash(permit); + + // cheatcode-computed permit + cheatStructHash = vm.eip712HashStruct("PermitSingle", abi.encode(permit)); + console.log("PermitSingle struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch"); + } + + function testHashPermitSingle_withTypeDefinition() public { + PermitDetails memory details = PermitDetails({ + token: 0x1111111111111111111111111111111111111111, + amount: 1000 ether, + expiration: 12345, + nonce: 1 + }); + + // user-computed permit (using uniswap hash library) + bytes32 userStructHash = PermitHash._hashDetails(details); + + // cheatcode-computed permit + bytes32 cheatStructHash = vm.eip712HashStruct("PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce)", abi.encode(details)); + + assertEq(cheatStructHash, userStructHash, "details struct hash mismatch"); + + PermitSingle memory permit = PermitSingle({ + details: details, + spender: 0x2222222222222222222222222222222222222222, + sigDeadline: 12345 + }); + + // user-computed permit (using uniswap hash library) + userStructHash = PermitHash.hash(permit); + + // cheatcode-computed permit (previously encoding) + cheatStructHash = vm.eip712HashStruct("PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce) PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)", abi.encode(permit)); + console.log("PermitSingle struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch"); + } + + function testHashTransaction_withTypeName() public { + Asset memory asset = Asset ({ token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, amount: 100 ether }); + + bytes32 user = TransactionHash._hashAsset(asset); + bytes32 cheat = vm.eip712HashStruct("Asset", abi.encode(asset)); + assertEq(user, cheat, "asset struct hash mismatch"); + + Person memory from = Person ({ wallet: 0x0000000000000000000000000000000000000001, name: "alice" }); + Person memory to = Person ({ wallet: 0x0000000000000000000000000000000000000002, name: "bob" }); + + user = TransactionHash._hashPerson(from); + cheat = vm.eip712HashStruct("Person", abi.encode(from)); + assertEq(user, cheat, "person struct hash mismatch"); + + Transaction memory t = Transaction ({ from: from, to: to, tx: asset }); + + user = TransactionHash.hash(t); + cheat = vm.eip712HashStruct("Transaction", abi.encode(t)); + assertEq(user, cheat, "transaction struct hash mismatch"); + } + + function testHashTransaction_withTypeDefinition() public { + Asset memory asset = Asset ({ token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, amount: 100 ether }); + + bytes32 user = TransactionHash._hashAsset(asset); + bytes32 cheat = vm.eip712HashStruct("Asset(address token, uint256 amount)", abi.encode(asset)); + assertEq(user, cheat, "asset struct hash mismatch"); + + Person memory from = Person ({ wallet: 0x0000000000000000000000000000000000000001, name: "alice" }); + Person memory to = Person ({ wallet: 0x0000000000000000000000000000000000000002, name: "bob" }); + + user = TransactionHash._hashPerson(from); + cheat = vm.eip712HashStruct("Person(address wallet, string name)", abi.encode(from)); + assertEq(user, cheat, "person struct hash mismatch"); + + Transaction memory t = Transaction ({ from: from, to: to, tx: asset }); + + user = TransactionHash.hash(t); + cheat = vm.eip712HashStruct("Person(address wallet, string name) Asset(address token, uint256 amount) Transaction(Person from, Person to, Asset tx)", abi.encode(t)); + assertEq(user, cheat, "transaction struct hash mismatch"); + } +} +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "Eip712HashStructTest", "-vv"]) + .assert_success() + .stdout_eq(str![[r#" +... +[PASS] testHashPermitSingle_withTypeDefinition() ([GAS]) +Logs: + PermitSingle struct hash from cheatcode: + 0x3ed744fdcea02b6b9ad45a9db6e648bf6f18c221909f9ee425191f2a02f9e4a8 + +[PASS] testHashPermitSingle_withTypeName() ([GAS]) +Logs: + PermitSingle struct hash from cheatcode: + 0x3ed744fdcea02b6b9ad45a9db6e648bf6f18c221909f9ee425191f2a02f9e4a8 +... +"#]]); +}); + +forgetest!(test_eip712_hash_typed_data, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712HashTypedData.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; +contract Eip712HashTypedDataTest is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function testHashEIP712Message() public { + string memory jsonData = + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"},{"name":"salt","type":"bytes32"}]},"primaryType":"EIP712Domain","domain":{"name":"example.metamask.io","version":"1","chainId":1,"verifyingContract":"0x0000000000000000000000000000000000000000"},"message":{}}'; + + // since this cheatcode simply exposes an alloy fn, the test has been borrowed from: + // + bytes32 expectedHash = hex"122d1c8ef94b76dad44dcb03fa772361e20855c63311a15d5afe02d1b38f6077"; + assertEq(vm.eip712HashTypedData(jsonData), expectedHash, "EIP712Domain struct hash mismatch"); + } +} +"#, + ); + + cmd.forge_fuse().args(["test", "--mc", "Eip712HashTypedDataTest"]).assert_success(); +}); + +// repro: +forgetest!(test_eip712_hash_typed_data_repro, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712HashTypedData.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; +contract CounterStrike { + bytes32 public constant ATTACK_TYPEHASH = keccak256("Attack(address player,uint128 x,uint128 y,uint40 shootTime)"); + bytes32 public constant DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + string public constant PROTOCOL_NAME = "CounterStrike"; +} + +contract CounterStrike_Test is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + struct EIP712Domain { + string name; + uint256 chainId; + address verifyingContract; + } + + struct Attack { + address player; + uint128 x; + uint128 y; + uint40 shootTime; + } + + string constant SCHEMA_EIP712_DOMAIN = "EIP712Domain(string name,uint256 chainId,address verifyingContract)"; + string constant SCHEMA_ATTACK = "Attack(address player,uint128 x,uint128 y,uint40 shootTime)"; + + CounterStrike public counterStrike; + address public player; + uint256 public playerPrivateKey; + uint128 public x = 10_000e18; + uint128 public y = 20_000e18; + uint40 public shootTime = 12_345_678; + + function setUp() public { + counterStrike = new CounterStrike(); + } + + function test_Attack() public view { + string memory domainJson = vm.serializeJsonType( + SCHEMA_EIP712_DOMAIN, + abi.encode( + EIP712Domain({ + name: counterStrike.PROTOCOL_NAME(), + chainId: block.chainid, + verifyingContract: address(counterStrike) + }) + ) + ); + string memory messageJson = vm.serializeJsonType( + SCHEMA_ATTACK, abi.encode(Attack({ player: player, x: x, y: y, shootTime: shootTime })) + ); + + string memory typesJson = string.concat( + '{"EIP712Domain":[{"name":"name","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Attack":[{"name":"player","type":"address"},{"name":"x","type":"uint128"},{"name":"y","type":"uint128"},{"name":"shootTime","type":"uint40"}]}' + ); + string memory primaryType = '"Attack"'; + string memory typedDataJson = string.concat( + '{"types":', + typesJson, + ',"primaryType":', + primaryType, + ',"domain":', + domainJson, + ',"message":', + messageJson, + "}" + ); + + bytes32 digest = vm.eip712HashTypedData(typedDataJson); + console.logBytes32(digest); + } +} +"#, + ); + + cmd.forge_fuse().args(["test", "-vvv"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 5dcdf4be132b4..f792f5b4a7887 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -1,13 +1,15 @@ use foundry_test_utils::util::ExtTester; // Actively maintained tests +// Last updated: April 29th 2026 // #[test] fn forge_std() { - ExtTester::new("foundry-rs", "forge-std", "464587138602dd194ed0eb5aab15b4721859d422") + ExtTester::new("foundry-rs", "forge-std", "8987040ede9553cea20c95ad40d0455930f9c8e0") // Skip fork tests. .args(["--nmc", "Fork"]) + .verbosity(2) .run(); } @@ -15,7 +17,7 @@ fn forge_std() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn prb_math() { - ExtTester::new("PaulRBerg", "prb-math", "b03f814a03558ed5b62f89a57bcc8d720a393f67") + ExtTester::new("PaulRBerg", "prb-math", "82e5ed5561d0a1c43a3a59edbf4291c8de26479e") .install_command(&["bun", "install", "--prefer-offline"]) // Try npm if bun fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) @@ -38,16 +40,17 @@ fn prb_proxy() { #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn sablier_v2_core() { let mut tester = - ExtTester::new("sablier-labs", "v2-core", "43cf7c9d968e61a5a03e9237a71a27165b125414") + ExtTester::new("sablier-labs", "v2-core", "8b6823c019ff7556ac9ad24cbb5ac62821854d2f") // Skip fork tests. .args(["--nmc", "Fork"]) // Increase the gas limit: https://github.com/sablier-labs/v2-core/issues/956 - .args(["--gas-limit", u64::MAX.to_string().as_str()]) + .args(["--gas-limit", &u64::MAX.to_string()]) // Run tests without optimizations. .env("FOUNDRY_PROFILE", "lite") .install_command(&["bun", "install", "--prefer-offline"]) // Try npm if bun fails / is not installed. - .install_command(&["npm", "install", "--prefer-offline"]); + .install_command(&["npm", "install", "--prefer-offline"]) + .verbosity(2); // This test reverts due to memory limit without isolation. This revert is not reached with // isolation because memory is divided between separate EVMs created by inner calls. @@ -61,7 +64,17 @@ fn sablier_v2_core() { // #[test] fn solady() { - ExtTester::new("Vectorized", "solady", "66162801e022c268a2a0f621ac5eb0df4986f6eb").run(); + let mut tester = + ExtTester::new("Vectorized", "solady", "90db92ce173856605d24a554969f2c67cadbc7e9"); + + // This test expects the mover contract created via CREATE2 to be selfdestructed within the + // same transaction. In isolation mode, each top-level call runs as a separate transaction + // context, so the selfdestruct doesn't clear the code as expected by the test. + if cfg!(feature = "isolate-by-default") { + tester = tester.args(["--nmt", "testSafeMoveETHViaMover"]); + } + + tester.run(); } // @@ -69,8 +82,7 @@ fn solady() { #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] #[cfg(not(feature = "isolate-by-default"))] fn snekmate() { - ExtTester::new("pcaversaccio", "snekmate", "df226f4a45e86c8f8c3ff1f9fa3443d260002050") - .args(["--nmc", "ERC4626VaultTest"]) + ExtTester::new("pcaversaccio", "snekmate", "1a54931129f2814cbbd7ddbafb4005707f8a5bf8") .install_command(&["pnpm", "install", "--prefer-offline"]) // Try npm if pnpm fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) @@ -80,7 +92,7 @@ fn snekmate() { // #[test] fn mds1_multicall3() { - ExtTester::new("mds1", "multicall", "f534fbc9f98386a217eaaf9b29d3d4f6f920d5ec").run(); + ExtTester::new("mds1", "multicall", "b667d67ecfa5361a81e8f110234ce242613b0012").run(); } // Legacy tests diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index 3543fd1d5d87e..77d5a5e84cfbb 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -17,18 +17,25 @@ forgetest!(test_fail_deprecation, |prj, cmd| { } } "#, - ) - .unwrap(); + ); - cmd.forge_fuse().args(["test", "--mc", "DeprecationTestFail"]).assert_failure().stdout_eq( - r#"[COMPILING_FILES] with [SOLC_VERSION] + cmd.forge_fuse() + .args(["test", "--mc", "DeprecationTestFail"]) + .assert_failure() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] ... -[FAIL: `testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert] Found 2 instances: testFail_deprecated, testFail_deprecated2 ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] -... -"#, - ); +Failing tests: +Encountered 2 failing tests in src/DeprecationTestFail.t.sol:DeprecationTestFail +[FAIL: `testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert] testFail_deprecated() ([GAS]) +[FAIL: `testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert] testFail_deprecated2() ([GAS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); }); forgetest!(expect_revert_tests_should_fail, |prj, cmd| { @@ -36,7 +43,7 @@ forgetest!(expect_revert_tests_should_fail, |prj, cmd| { prj.insert_vm(); let expect_revert_failure_tests = include_str!("../fixtures/ExpectRevertFailures.t.sol"); - prj.add_source("ExpectRevertFailures.sol", expect_revert_failure_tests).unwrap(); + prj.add_source("ExpectRevertFailures.sol", expect_revert_failure_tests); cmd.forge_fuse() .args(["test", "--mc", "ExpectRevertFailureTest"]) @@ -63,8 +70,13 @@ Suite result: FAILED. 0 passed; 7 failed; 0 skipped; [ELAPSED] .stdout_eq( r#"No files changed, compilation skipped ... +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != [..]] testShouldFailExpectRevertNestedCreateInnerAddress() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterNestedCreate() ([GAS]) +[FAIL: Reverter != expected reverter: [..] != 0x000000000000000000000000000000000000dEaD] testShouldFailExpectRevertWrongReverterTopLevelCreate() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailExpectRevertsNotOnImmediateNextCall() ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#, ); @@ -75,12 +87,13 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] .stdout_eq( r#"No files changed, compilation skipped ... +[FAIL: call reverted with 'my cool error' when it was expected not to revert] testShouldFailIfExpectRevertWrongString() ([GAS]) [FAIL: call reverted when it was expected not to revert] testShouldFailNoRevert() ([GAS]) [FAIL: expected 0 reverts with reason: revert, but got one] testShouldFailNoRevertSpecific() ([GAS]) -[FAIL: Error != expected error: second-revert != revert] testShouldFailReverCountSpecifc() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailRevertCountAny() ([GAS]) [FAIL: Error != expected error: wrong revert != called a function and then reverted] testShouldFailRevertCountCallsThenReverts() ([GAS]) -Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] +[FAIL: Error != expected error: second-revert != revert] testShouldFailRevertCountSpecific() ([GAS]) +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#, ); @@ -90,11 +103,13 @@ Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] .assert_failure() .stdout_eq(r#"No files changed, compilation skipped ... +[FAIL: call reverted with 'revert' from 0x2e234DAe75C793f67A35089C9d99245E1C58470b, but expected 0 reverts from 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testNoRevertWithWrongReverter() ([GAS]) +[FAIL: call reverted with 'revert2' from 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, but expected 0 reverts with reason 'revert' from 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testNoReverterCountWithData() ([GAS]) [FAIL: expected 0 reverts from address: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, but got one] testShouldFailNoRevertWithReverter() ([GAS]) [FAIL: Reverter != expected reverter: 0x2e234DAe75C793f67A35089C9d99245E1C58470b != 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testShouldFailRevertCountWithReverter() ([GAS]) [FAIL: Error != expected error: wrong revert != revert] testShouldFailReverterCountWithWrongData() ([GAS]) [FAIL: Reverter != expected reverter: 0x2e234DAe75C793f67A35089C9d99245E1C58470b != 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testShouldFailWrongReverterCountWithData() ([GAS]) -Suite result: FAILED. 0 passed; 4 failed; 0 skipped; [ELAPSED] +Suite result: FAILED. 0 passed; 6 failed; 0 skipped; [ELAPSED] ... "#); }); @@ -105,7 +120,7 @@ forgetest!(expect_call_tests_should_fail, |prj, cmd| { let expect_call_failure_tests = include_str!("../fixtures/ExpectCallFailures.t.sol"); - prj.add_source("ExpectCallFailures.sol", expect_call_failure_tests).unwrap(); + prj.add_source("ExpectCallFailures.sol", expect_call_failure_tests); cmd.forge_fuse().args(["test", "--mc", "ExpectCallFailureTest"]).assert_failure().stdout_eq( r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -162,7 +177,7 @@ forgetest!(expect_create_tests_should_fail, |prj, cmd| { let expect_create_failures = include_str!("../fixtures/ExpectCreateFailures.t.sol"); - prj.add_source("ExpectCreateFailures.t.sol", expect_create_failures).unwrap(); + prj.add_source("ExpectCreateFailures.t.sol", expect_create_failures); cmd.forge_fuse().args(["test", "--mc", "ExpectCreateFailureTest"]).assert_failure().stdout_eq(str![[r#" ... @@ -180,28 +195,31 @@ Suite result: FAILED. 0 passed; 8 failed; 0 skipped; [ELAPSED] "#]]); }); -forgetest!(expect_emit_tests_should_fail, |prj, cmd| { +forgetest!(flaky_expect_emit_tests_should_fail, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); let expect_emit_failure_tests = include_str!("../fixtures/ExpectEmitFailures.t.sol"); - prj.add_source("ExpectEmitFailures.sol", expect_emit_failure_tests).unwrap(); + prj.add_source("ExpectEmitFailures.sol", expect_emit_failure_tests); - cmd.forge_fuse().args(["test", "--mc", "ExpectEmitFailureTest"]).assert_failure().stdout_eq(str![[r#" + cmd.forge_fuse().arg("build").assert_success(); + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + cmd.forge_fuse().args(["test", "--mc", "ExpectEmitFailureTest"]).assert_failure().stdout_eq(str![[r#"No files changed, compilation skipped ... -[FAIL: log != expected log] testShouldFailCanMatchConsecutiveEvents() ([GAS]) -[FAIL: log != expected log] testShouldFailDifferentIndexedParameters() ([GAS]) +[FAIL: E != expected A] testShouldFailCanMatchConsecutiveEvents() ([GAS]) +[FAIL: log != expected SomethingElse] testShouldFailDifferentIndexedParameters() ([GAS]) [FAIL: log != expected log] testShouldFailEmitOnlyAppliesToNextCall() ([GAS]) [FAIL: next call did not revert as expected] testShouldFailEmitWindowWithRevertDisallowed() ([GAS]) -[FAIL: log != expected log] testShouldFailEventsOnTwoCalls() ([GAS]) -[FAIL: log != expected log; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmit(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) -[FAIL: log != expected log] testShouldFailExpectEmitAddress() ([GAS]) -[FAIL: log != expected log] testShouldFailExpectEmitAddressWithArgs() ([GAS]) -[FAIL: log != expected log] testShouldFailExpectEmitCanMatchWithoutExactOrder() ([GAS]) +[FAIL: E != expected A] testShouldFailEventsOnTwoCalls() ([GAS]) +[FAIL: Something param mismatch at [..]: expected=[..], got=[..]; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmit(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) +[FAIL: log emitter mismatch: expected=[..], got=[..]] testShouldFailExpectEmitAddress() ([GAS]) +[FAIL: log emitter mismatch: expected=[..], got=[..]] testShouldFailExpectEmitAddressWithArgs() ([GAS]) +[FAIL: Something != expected SomethingElse] testShouldFailExpectEmitCanMatchWithoutExactOrder() ([GAS]) [FAIL: expected an emit, but no logs were emitted afterwards. you might have mismatched events or not enough events were emitted] testShouldFailExpectEmitDanglingNoReference() ([GAS]) [FAIL: expected an emit, but no logs were emitted afterwards. you might have mismatched events or not enough events were emitted] testShouldFailExpectEmitDanglingWithReference() ([GAS]) -[FAIL: log != expected log; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmitNested(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) +[FAIL: Something param mismatch at [..]: expected=[..], got=[..]; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmitNested(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) [FAIL: log != expected log] testShouldFailLowLevelWithoutEmit() ([GAS]) [FAIL: log != expected log] testShouldFailMatchRepeatedEventsOutOfOrder() ([GAS]) [FAIL: log != expected log] testShouldFailNoEmitDirectlyOnNextCall() ([GAS]) @@ -217,48 +235,84 @@ Suite result: FAILED. 0 passed; 15 failed; 0 skipped; [ELAPSED] ... [FAIL: log != expected log] testShouldFailCountEmitsFromAddress() ([GAS]) [FAIL: log != expected log] testShouldFailCountLessEmits() ([GAS]) -[FAIL: log != expected log] testShouldFailEmitSomethingElse() ([GAS]) -[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmit() ([GAS]) -[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmitFromAddress() ([GAS]) +[FAIL: log != expected Something] testShouldFailEmitSomethingElse() ([GAS]) +[FAIL: log emitted but expected 0 times] testShouldFailNoEmit() ([GAS]) +[FAIL: log emitted but expected 0 times] testShouldFailNoEmitFromAddress() ([GAS]) Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] ... "#, ); }); +forgetest!(flaky_expect_emit_params_tests_should_fail, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.update_config(|config| { + config.fuzz.dictionary.max_fuzz_dictionary_literals = 0; + }); + + let expect_emit_failure_src = include_str!("../fixtures/ExpectEmitParamHarness.sol"); + let expect_emit_failure_tests = include_str!("../fixtures/ExpectEmitParamFailures.t.sol"); + + prj.add_source("ExpectEmitParamHarness.sol", expect_emit_failure_src); + prj.add_source("ExpectEmitParamFailures.sol", expect_emit_failure_tests); + + cmd.forge_fuse().arg("build").assert_success(); + + cmd.forge_fuse().args(["test", "--mc", "ExpectEmitParamFailures"]).assert_failure().stdout_eq( + r#"No files changed, compilation skipped +... +[PASS] testSelectiveChecks() ([GAS]) +Suite result: FAILED. 1 passed; 8 failed; 0 skipped; [ELAPSED] +... +[FAIL: anonymous log mismatch at param 0: expected=0x0000000000000000000000000000000000000000000000000000000000000064, got=0x00000000000000000000000000000000000000000000000000000000000003e7] testAnonymousEventMismatch() ([GAS]) +[FAIL: ComplexEvent != expected SimpleEvent] testCompletelyDifferentEvent() ([GAS]) +[FAIL: SimpleEvent param mismatch at b: expected=200, got=999] testIndexedParamMismatch() ([GAS]) +[FAIL: ManyParams param mismatch at a: expected=100, got=111, b: expected=200, got=222, c: expected=300, got=333, d: expected=400, got=444, e: expected=500, got=555] testManyParameterMismatches() ([GAS]) +[FAIL: SimpleEvent param mismatch at c: expected=300, got=999] testMixedEventNonIndexedMismatch() ([GAS]) +[FAIL: SimpleEvent param mismatch at a: expected=100, got=999, b: expected=200, got=888, c: expected=300, got=777] testMultipleMismatches() ([GAS]) +[FAIL: SimpleEvent param mismatch at c: expected=300, got=999] testNonIndexedParamMismatch() ([GAS]) +[FAIL: MixedEventNumbering param mismatch at param2: expected=300, got=999] testParameterNumbering() ([GAS]) + +Encountered a total of 8 failing tests, 1 tests succeeded +... +"#, + ); +}); + forgetest!(mem_safety_test_should_fail, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); let mem_safety_failure_tests = include_str!("../fixtures/MemSafetyFailures.t.sol"); - prj.add_source("MemSafetyFailures.sol", mem_safety_failure_tests).unwrap(); + prj.add_source("MemSafetyFailures.sol", mem_safety_failure_tests); cmd.forge_fuse().args(["test", "--mc", "MemSafetyFailureTest"]).assert_failure().stdout_eq( r#"[COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] ... [FAIL: Expected call to fail] testShouldFailExpectSafeMemoryCall() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CALL() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CALLCODE() ([GAS]) -[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]; counterexample: calldata=[..] args=[..]] testShouldFailExpectSafeMemory_CALLDATACOPY(uint256) (runs: 0, [AVG_GAS]) -[FAIL: memory write at offset 0x80 of size [..] not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_CODECOPY() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CREATE() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CREATE2() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_DELEGATECALL() ([GAS]) -[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_EXTCODECOPY() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_LOG0() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_MLOAD() ([GAS]) -[FAIL: memory write at offset 0x81 of size 0x01 not allowed; safe range: (0x00, 0x60] U (0x80, 0x81]] testShouldFailExpectSafeMemory_MSTORE8_High() ([GAS]) -[FAIL: memory write at offset 0x60 of size 0x01 not allowed; safe range: (0x00, 0x60] U (0x80, 0x81]] testShouldFailExpectSafeMemory_MSTORE8_Low() ([GAS]) -[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_MSTORE_High() ([GAS]) -[FAIL: memory write at offset 0x60 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_MSTORE_Low() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_RETURN() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_RETURNDATACOPY() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_CALL() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_CALLCODE() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0); counterexample: calldata=[..] args=[..]] testShouldFailExpectSafeMemory_CALLDATACOPY(uint256) (runs: 0, [AVG_GAS]) +[FAIL: memory write at offset 0x80 of size [..] not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0)] testShouldFailExpectSafeMemory_CODECOPY() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_CREATE() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_CREATE2() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_DELEGATECALL() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0)] testShouldFailExpectSafeMemory_EXTCODECOPY() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_LOG0() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_MLOAD() ([GAS]) +[FAIL: memory write at offset 0x81 of size 0x01 not allowed; safe range: [0x00, 0x60) U [0x80, 0x81)] testShouldFailExpectSafeMemory_MSTORE8_High() ([GAS]) +[FAIL: memory write at offset 0x60 of size 0x01 not allowed; safe range: [0x00, 0x60) U [0x80, 0x81)] testShouldFailExpectSafeMemory_MSTORE8_Low() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0)] testShouldFailExpectSafeMemory_MSTORE_High() ([GAS]) +[FAIL: memory write at offset 0x60 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0)] testShouldFailExpectSafeMemory_MSTORE_Low() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_RETURN() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_RETURNDATACOPY() ([GAS]) [FAIL: EvmError: Revert] testShouldFailExpectSafeMemory_REVERT() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_SHA3() ([GAS]) -[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_STATICCALL() ([GAS]) -[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailStopExpectSafeMemory() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_SHA3() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: [0x00, 0x60) U [0x80, 0x100)] testShouldFailExpectSafeMemory_STATICCALL() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: [0x00, 0x60) U [0x80, 0xA0)] testShouldFailStopExpectSafeMemory() ([GAS]) Suite result: FAILED. 0 passed; 21 failed; 0 skipped; [ELAPSED] ... "#, @@ -283,8 +337,7 @@ forgetest!(ds_style_test_failing, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["test", "--mc", "DSStyleTest", "-vv"]).assert_failure().stdout_eq( r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -329,8 +382,7 @@ contract FailingSetupTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mc", "FailingSetupTest"]).assert_failure().stdout_eq(str![[ r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -364,8 +416,7 @@ contract MultipleAfterInvariant is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mc", "MultipleAfterInvariant"]).assert_failure().stdout_eq(str![[ r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -384,7 +435,7 @@ forgetest!(multiple_setups, |prj, cmd| { prj.add_source( "MultipleSetupsTest.t.sol", r#" - + import "./test.sol"; contract MultipleSetup is DSTest { @@ -398,8 +449,7 @@ contract MultipleSetup is DSTest { } "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["test", "--mc", "MultipleSetup"]).assert_failure().stdout_eq(str![[ r#"[COMPILING_FILES] with [SOLC_VERSION] @@ -444,8 +494,7 @@ forgetest!(emit_diff_anonymous, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().args(["test", "--mc", "EmitDiffAnonymousTest"]).assert_failure().stdout_eq( str![[r#"[COMPILING_FILES] with [SOLC_VERSION] diff --git a/crates/forge/tests/cli/fmt.rs b/crates/forge/tests/cli/fmt.rs new file mode 100644 index 0000000000000..0865f381854de --- /dev/null +++ b/crates/forge/tests/cli/fmt.rs @@ -0,0 +1,164 @@ +//! Integration tests for `forge fmt` command + +use foundry_test_utils::{forgetest, forgetest_init}; + +const UNFORMATTED: &str = r#"// SPDX-License-Identifier: MIT +pragma solidity =0.8.33 ; + +contract Test { + uint256 public value ; + function setValue ( uint256 _value ) public { + value = _value ; + } +}"#; + +const FORMATTED: &str = r#"// SPDX-License-Identifier: MIT +pragma solidity =0.8.33; + +contract Test { + uint256 public value; + + function setValue(uint256 _value) public { + value = _value; + } +} +"#; + +forgetest_init!(fmt_exclude_libs_in_recursion, |prj, cmd| { + prj.update_config(|config| config.fmt.ignore = vec!["src/ignore/".to_string()]); + + prj.add_lib("SomeLib.sol", UNFORMATTED); + prj.add_raw_source("ignore/IgnoredContract.sol", UNFORMATTED); + cmd.args(["fmt", ".", "--check"]); + cmd.assert_success(); + + cmd.forge_fuse().args(["fmt", "lib/SomeLib.sol", "--check"]); + cmd.assert_failure(); +}); + +// Test that fmt can format a simple contract file +forgetest_init!(fmt_file, |prj, cmd| { + prj.add_raw_source("FmtTest.sol", UNFORMATTED); + cmd.arg("fmt").arg("src/FmtTest.sol"); + cmd.assert_success().stdout_eq(str![[r#" +Formatted [..]/src/FmtTest.sol + +"#]]); + assert_data_eq!( + std::fs::read_to_string(prj.root().join("src/FmtTest.sol")).unwrap(), + FORMATTED, + ); +}); + +// Test that fmt can format from stdin +forgetest!(fmt_stdin, |_prj, cmd| { + cmd.args(["fmt", "-", "--raw"]); + cmd.stdin(UNFORMATTED.as_bytes()); + cmd.assert_success().stdout_eq(FORMATTED); + + // stdin with `--raw` returns formatted code + cmd.stdin(FORMATTED.as_bytes()); + cmd.assert_success().stdout_eq(FORMATTED); + + // stdin with `--check` and without `--raw`returns diff + cmd.forge_fuse().args(["fmt", "-", "--check"]); + cmd.assert_success().stdout_eq(""); +}); + +forgetest_init!(fmt_check_mode, |prj, cmd| { + // Run fmt --check on a well-formatted file + prj.add_raw_source("Test.sol", FORMATTED); + cmd.arg("fmt").arg("--check").arg("src/Test.sol"); + cmd.assert_success().stderr_eq("").stdout_eq(""); + + // Run fmt --check on a mal-formatted file + prj.add_raw_source("Test2.sol", UNFORMATTED); + cmd.forge_fuse().arg("fmt").arg("--check").arg("src/Test2.sol"); + cmd.assert_failure(); +}); + +forgetest!(fmt_check_mode_stdin, |_prj, cmd| { + // Run fmt --check with well-formatted stdin input + cmd.arg("fmt").arg("-").arg("--check"); + cmd.stdin(FORMATTED.as_bytes()); + cmd.assert_success().stderr_eq("").stdout_eq(""); + + // Run fmt --check with mal-formatted stdin input + cmd.stdin(UNFORMATTED.as_bytes()); + cmd.assert_failure().stderr_eq("").stdout_eq(str![[r#" +Diff in stdin: +1 1 | // SPDX-License-Identifier: MIT +2 |-pragma solidity =0.8.33 ; + 2 |+pragma solidity =0.8.33; +... +4 |-contract Test { +5 |- uint256 public value ; +6 |- function setValue ( uint256 _value ) public { +7 |- value = _value ; + 4 |+contract Test { + 5 |+ uint256 public value; +... + 7 |+ function setValue(uint256 _value) public { + 8 |+ value = _value; +8 9 | } +9 |-} + 10 |+} + +"#]]); +}); + +// Test that original is returned if read from stdin and no diff. +// +forgetest!(fmt_stdin_original, |_prj, cmd| { + cmd.args(["fmt", "-", "--raw"]); + + cmd.stdin(FORMATTED.as_bytes()); + cmd.assert_success().stdout_eq(FORMATTED.as_bytes()); +}); + +// Test that fmt can format a simple contract file +forgetest_init!(fmt_file_config_parms_first, |prj, cmd| { + prj.create_file( + "foundry.toml", + r#" +[fmt] +multiline_func_header = 'params_first' +"#, + ); + prj.add_raw_source("FmtTest.sol", FORMATTED); + cmd.forge_fuse().args(["fmt", "--check"]).arg("src/FmtTest.sol"); + cmd.assert_failure().stdout_eq(str![[r#" +Diff in src/FmtTest.sol: +... +7 |- function setValue(uint256 _value) public { + 7 |+ function setValue( + 8 |+ uint256 _value + 9 |+ ) public { +... + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/12000 +forgetest_init!(fmt_only_cmnts_file, |prj, cmd| { + // Only line breaks + prj.add_raw_source("FmtTest.sol", "\n\n"); + + cmd.forge_fuse().args(["fmt", "src/FmtTest.sol"]); + cmd.assert_success(); + assert_data_eq!(std::fs::read_to_string(prj.root().join("src/FmtTest.sol")).unwrap(), "",); + cmd.forge_fuse().args(["fmt", "--check", "src/FmtTest.sol"]); + cmd.assert_success(); + + // Only cmnts + prj.add_raw_source("FmtTest.sol", "\n\n// this is a cmnt"); + + cmd.forge_fuse().args(["fmt", "src/FmtTest.sol"]); + cmd.assert_success(); + assert_data_eq!( + std::fs::read_to_string(prj.root().join("src/FmtTest.sol")).unwrap(), + "// this is a cmnt\n", + ); + cmd.forge_fuse().args(["fmt", "--check", "src/FmtTest.sol"]); + cmd.assert_success(); +}); diff --git a/crates/forge/tests/cli/fmt_integration.rs b/crates/forge/tests/cli/fmt_integration.rs new file mode 100644 index 0000000000000..92c133263d955 --- /dev/null +++ b/crates/forge/tests/cli/fmt_integration.rs @@ -0,0 +1,27 @@ +use foundry_test_utils::util::ExtTester; + +/// Test `forge fmt` immutability. +/// TODO: make sure original fmt is not changed after projects format and rev available. +macro_rules! fmt_test { + ($name:ident, $org:expr, $repo:expr, $commit:expr) => { + #[test] + fn $name() { + let (_, mut cmd) = ExtTester::new($org, $repo, $commit).setup_forge_prj(false); + cmd.arg("fmt").assert_success(); + cmd.arg("--check").assert_success(); + } + }; +} + +fmt_test!(fmt_ithaca_account, "ithacaxyz", "account", "213c04ee1808784c18609607d85feba7730538fd"); + +fmt_test!(fmt_univ4_core, "Uniswap", "v4-core", "59d3ecf53afa9264a16bba0e38f4c5d2231f80bc"); + +fmt_test!( + fmt_evk_periphery, + "euler-xyz", + "evk-periphery", + "e41f2b9b7ed677ca03ff7bd7221a4e2fdd55504f" +); + +fmt_test!(fmt_0x_settler, "0xProject", "0x-settler", "a388c8251ab6c4bedce1641b31027d7b1136daef"); diff --git a/crates/forge/tests/cli/geiger.rs b/crates/forge/tests/cli/geiger.rs deleted file mode 100644 index fd21656284744..0000000000000 --- a/crates/forge/tests/cli/geiger.rs +++ /dev/null @@ -1,92 +0,0 @@ -forgetest!(call, |prj, cmd| { - prj.add_source( - "call.sol", - r#" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - vm.ffi(inputs); - } - } - "#, - ) - .unwrap(); - - cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" -error: usage of unsafe cheatcode `vm.ffi` - [FILE]:7:20 - | -7 | vm.ffi(inputs); - | ^^^ - | - - -"#]]); -}); - -forgetest!(assignment, |prj, cmd| { - prj.add_source( - "assignment.sol", - r#" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - bytes stuff = vm.ffi(inputs); - } - } - "#, - ) - .unwrap(); - - cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" -error: usage of unsafe cheatcode `vm.ffi` - [FILE]:7:34 - | -7 | bytes stuff = vm.ffi(inputs); - | ^^^ - | - - -"#]]); -}); - -forgetest!(exit_code, |prj, cmd| { - prj.add_source( - "multiple.sol", - r#" - contract A is Test { - function do_ffi() public { - vm.ffi(inputs); - vm.ffi(inputs); - vm.ffi(inputs); - } - } - "#, - ) - .unwrap(); - - cmd.arg("geiger").assert_code(3).stderr_eq(str![[r#" -error: usage of unsafe cheatcode `vm.ffi` - [FILE]:6:20 - | -6 | vm.ffi(inputs); - | ^^^ - | - -error: usage of unsafe cheatcode `vm.ffi` - [FILE]:7:20 - | -7 | vm.ffi(inputs); - | ^^^ - | - -error: usage of unsafe cheatcode `vm.ffi` - [FILE]:8:20 - | -8 | vm.ffi(inputs); - | ^^^ - | - - -"#]]); -}); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index cbcfb20b11a3d..ba01767d58b26 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -11,8 +11,7 @@ forgetest!(runs, |prj, cmd| { function test2(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -61,11 +60,10 @@ forgetest!(invalid_profile, |prj, cmd| { function test(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_failure().stderr_eq(str![[r#" -Error: Inline config error at test/inline.sol:80:123:0: invalid profile `unknown.fuzz.runs = 2`; valid profiles: default +Error: Inline config error at test/inline.sol:4:9: invalid profile `unknown.fuzz.runs = 2`; valid profiles: default "#]]); }); @@ -81,8 +79,7 @@ forgetest!(invalid_key, |prj, cmd| { function test(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -113,8 +110,7 @@ forgetest!(invalid_key_2, |prj, cmd| { function test(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -146,8 +142,7 @@ forgetest!(invalid_value, |prj, cmd| { function test(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -166,6 +161,8 @@ Encountered 1 failing test in test/inline.sol:Inline Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); @@ -178,8 +175,7 @@ forgetest!(invalid_value_2, |prj, cmd| { function test(bool) public {} } ", - ) - .unwrap(); + ); cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -198,6 +194,8 @@ Encountered 1 failing test in test/inline.sol:Inline Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); @@ -206,7 +204,6 @@ forgetest_init!(config_inline_isolate, |prj, cmd| { use serde::{Deserialize, Deserializer}; use std::{fs, path::Path}; - prj.wipe_contracts(); prj.add_test( "inline.sol", r#" @@ -256,8 +253,7 @@ forgetest_init!(config_inline_isolate, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-j1"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -333,7 +329,6 @@ Ran 2 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) }); forgetest_init!(config_inline_evm_version, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "inline.sol", r#" @@ -382,8 +377,7 @@ forgetest_init!(config_inline_evm_version, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--evm-version=cancun", "-j1"]).assert_success().stdout_eq(str![[r#" ... @@ -401,3 +395,137 @@ Ran 2 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) "#]]); }); + +forgetest_init!(config_inline_hardfork_same_network_family, |prj, cmd| { + prj.write_config(foundry_config::Config { + hardfork: Some("tempo:T2".parse::().unwrap()), + ..foundry_config::Config::default() + }); + prj.add_test( + "inline.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract InlineHardfork is Test { + /// forge-config: default.hardfork = "tempo:T3" + function test_inline_hardfork() public { + assertTrue(true); + } + } + "#, + ); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/inline.sol:InlineHardfork +[PASS] test_inline_hardfork() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Checks that tests annotated with `forge-config: default.networks.network` run on the correct +// EVM network, and that unannotated tests run on the globally configured network. +// +// Each test makes a real call to the Tempo `TipFeeManager` precompile at +// `0xfeec000000000000000000000000000000000000` (a Tempo-only contract that exists on the +// Moderato testnet and is auto-injected by the in-memory Tempo EVM): +// +// * The default-network test asserts the precompile has no code (it does not exist on Ethereum). +// * The Tempo-network test asserts the precompile has code and `userTokens(address)` returns the +// unset zero-address sentinel, proving the Tempo network was actually selected for that test and +// the Tempo genesis state was loaded. +forgetest!(per_test_network_routing, |prj, cmd| { + prj.add_test( + "inline.sol", + r#" + address constant TIP_FEE_MANAGER = 0xfeEC000000000000000000000000000000000000; + + contract DefaultNetwork { + // No annotation -> runs on the globally selected network (Ethereum by default). + // The Tempo FeeManager precompile must NOT exist here. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + } + + contract TempoNetwork { + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + // Sentinel bytecode (0xef) is injected at every Tempo precompile address. + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + // Call a Tempo-only method: `userTokens(address)` returns the user's preferred + // fee token, or the zero address when none is set. + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + + // Mixed contract: one function annotated with Tempo, one unannotated (runs on Ethereum). + contract MixedNetwork { + // No annotation -> runs on Ethereum; precompile must be absent. + function test_fee_manager_absent_on_ethereum() public view { + require( + TIP_FEE_MANAGER.code.length == 0, + "TipFeeManager should not exist on Ethereum" + ); + } + + /// forge-config: default.networks.network = "tempo" + function test_fee_manager_callable_on_tempo() public view { + require( + TIP_FEE_MANAGER.code.length > 0, + "TipFeeManager must be deployed on Tempo" + ); + + (bool ok, bytes memory ret) = TIP_FEE_MANAGER.staticcall( + abi.encodeWithSignature("userTokens(address)", address(0)) + ); + require(ok, "userTokens call to TipFeeManager failed"); + require(ret.length == 32, "unexpected return data length"); + address token = abi.decode(ret, (address)); + require(token == address(0), "expected unset user fee token"); + } + } + "#, + ); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_absent_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/inline.sol:[..]Network +[PASS] test_fee_manager_callable_on_[..]() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 3 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs new file mode 100644 index 0000000000000..35b785cb111de --- /dev/null +++ b/crates/forge/tests/cli/install.rs @@ -0,0 +1,704 @@ +//! forge install and update tests + +use forge::{DepIdentifier, FOUNDRY_LOCK, Lockfile}; +use foundry_cli::utils::{Git, Submodules}; +use foundry_compilers::artifacts::Remapping; +use foundry_config::Config; +use foundry_test_utils::util::{ + ExtTester, FORGE_STD_REVISION, TestCommand, pretty_err, read_string, +}; +use semver::Version; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, + str::FromStr, +}; + +fn lockfile_get(root: &Path, dep_path: &Path) -> Option { + let mut l = Lockfile::new(root); + l.read().unwrap(); + l.get(dep_path).cloned() +} + +// checks missing dependencies are auto installed +forgetest_init!(can_install_missing_deps_build, |prj, cmd| { + prj.initialize_default_contracts(); + prj.clear(); + + // wipe forge-std + let forge_std_dir = prj.root().join("lib/forge-std"); + pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); + + // Build the project + cmd.arg("build").assert_success().stdout_eq(str![[r#" +Missing dependencies found. Installing now... + +[UPDATING_DEPENDENCIES] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + // assert lockfile + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert_eq!(forge_std.rev(), FORGE_STD_REVISION); + + // Expect compilation to be skipped as no files have changed + cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +"#]]); +}); + +// checks missing dependencies are auto installed +forgetest_init!(can_install_missing_deps_test, |prj, cmd| { + prj.initialize_default_contracts(); + prj.clear(); + + // wipe forge-std + let forge_std_dir = prj.root().join("lib/forge-std"); + pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +Missing dependencies found. Installing now... + +[UPDATING_DEPENDENCIES] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + + // assert lockfile + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert_eq!(forge_std.rev(), FORGE_STD_REVISION); +}); + +// test to check that install/remove works properly +forgetest!(can_install_and_remove, |prj, cmd| { + cmd.git_init(); + + let libs = prj.root().join("lib"); + let git_mod = prj.root().join(".git/modules/lib"); + let git_mod_file = prj.root().join(".gitmodules"); + + let forge_std = libs.join("forge-std"); + let forge_std_mod = git_mod.join("forge-std"); + + let install = |cmd: &mut TestCommand| { + cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( + str![[r#" +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std[..] + +"#]], + ); + + assert!(forge_std.exists()); + assert!(forge_std_mod.exists()); + + let submods = read_string(&git_mod_file); + assert!(submods.contains("https://github.com/foundry-rs/forge-std")); + }; + + let remove = |cmd: &mut TestCommand, target: &str| { + cmd.forge_fuse().args(["remove", "--force", target]).assert_success().stdout_eq(str![[ + r#" +Removing 'forge-std' in [..], (url: https://github.com/foundry-rs/forge-std, tag: None) + +"# + ]]); + + assert!(!forge_std.exists()); + assert!(!forge_std_mod.exists()); + let submods = read_string(&git_mod_file); + assert!(!submods.contains("https://github.com/foundry-rs/forge-std")); + }; + + install(&mut cmd); + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Tag { .. })); + remove(&mut cmd, "forge-std"); + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")); + assert!(forge_std.is_none()); + + // install again and remove via relative path + install(&mut cmd); + remove(&mut cmd, "lib/forge-std"); +}); + +// test to check we can run `forge install` in an empty dir +forgetest!(can_install_empty, |prj, cmd| { + // create + cmd.git_init(); + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); + + // create initial commit + fs::write(prj.root().join("README.md"), "Initial commit").unwrap(); + + cmd.git_add(); + cmd.git_commit("Initial commit"); + + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); +}); + +// test to check that package can be reinstalled after manually removing the directory +forgetest!(can_reinstall_after_manual_remove, |prj, cmd| { + cmd.git_init(); + + let libs = prj.root().join("lib"); + let git_mod = prj.root().join(".git/modules/lib"); + let git_mod_file = prj.root().join(".gitmodules"); + + let forge_std = libs.join("forge-std"); + let forge_std_mod = git_mod.join("forge-std"); + + let install = |cmd: &mut TestCommand| { + cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( + str![[r#" +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) + Installed forge-std tag=[..]"#]], + ); + + assert!(forge_std.exists()); + assert!(forge_std_mod.exists()); + + let submods = read_string(&git_mod_file); + assert!(submods.contains("https://github.com/foundry-rs/forge-std")); + }; + + install(&mut cmd); + let forge_std_lock = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std_lock, DepIdentifier::Tag { .. })); + fs::remove_dir_all(forge_std.clone()).expect("Failed to remove forge-std"); + + // install again with tag + install(&mut cmd); + let forge_std_lock = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std_lock, DepIdentifier::Tag { .. })); +}); + +// test that we can repeatedly install the same dependency without changes +forgetest!(can_install_repeatedly, |_prj, cmd| { + cmd.git_init(); + + cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]); + for _ in 0..3 { + cmd.assert_success(); + } +}); + +// test that by default we install the latest semver release tag +// +forgetest!(can_install_latest_release_tag, |prj, cmd| { + cmd.git_init(); + cmd.forge_fuse().args(["install", "openzeppelin/openzeppelin-contracts"]); + cmd.assert_success(); + + let dep = prj.paths().libraries[0].join("openzeppelin-contracts"); + assert!(dep.exists()); + + let oz_lock = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + assert!(matches!(oz_lock, DepIdentifier::Tag { .. })); + + // the latest release at the time this test was written + let version: Version = "4.8.0".parse().unwrap(); + let out = Command::new("git").current_dir(&dep).args(["describe", "--tags"]).output().unwrap(); + let tag = String::from_utf8_lossy(&out.stdout); + let current: Version = tag.as_ref().trim_start_matches('v').trim().parse().unwrap(); + + assert!(current >= version); +}); + +forgetest!(can_update_and_retain_tag_revs, |prj, cmd| { + cmd.git_init(); + + // Installs oz at release tag + cmd.forge_fuse() + .args(["install", "openzeppelin/openzeppelin-contracts@v5.1.0"]) + .assert_success(); + + // Install solady pinned to rev i.e https://github.com/Vectorized/solady/commit/513f581675374706dbe947284d6b12d19ce35a2a + cmd.forge_fuse().args(["install", "vectorized/solady@513f581"]).assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + let oz_init = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + let solady_init = lockfile_get(prj.root(), &PathBuf::from("lib/solady")).unwrap(); + assert_eq!(oz_init.name(), "v5.1.0"); + assert_eq!(solady_init.rev(), "513f581"); + let submodules_init: Submodules = status.parse().unwrap(); + + cmd.forge_fuse().arg("update").assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + let submodules_update: Submodules = status.parse().unwrap(); + assert_eq!(submodules_init, submodules_update); + + let oz_update = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + let solady_update = lockfile_get(prj.root(), &PathBuf::from("lib/solady")).unwrap(); + assert_eq!(oz_init, oz_update); + assert_eq!(solady_init, solady_update); +}); + +forgetest!(can_override_tag_in_update, |prj, cmd| { + cmd.git_init(); + + // Installs oz at release tag + cmd.forge_fuse() + .args(["install", "openzeppelin/openzeppelin-contracts@v5.0.2"]) + .assert_success(); + + cmd.forge_fuse().args(["install", "vectorized/solady@513f581"]).assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + + let submodules_init: Submodules = status.parse().unwrap(); + + let oz_init_lock = + lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + assert_eq!(oz_init_lock.name(), "v5.0.2"); + let solady_init_lock = lockfile_get(prj.root(), &PathBuf::from("lib/solady")).unwrap(); + assert_eq!(solady_init_lock.rev(), "513f581"); + + // Update oz to a different release tag + cmd.forge_fuse() + .args(["update", "openzeppelin/openzeppelin-contracts@v5.1.0"]) + .assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + + let submodules_update: Submodules = status.parse().unwrap(); + + assert_ne!(submodules_init.0[0], submodules_update.0[0]); + assert_eq!(submodules_init.0[1], submodules_update.0[1]); + + let oz_update_lock = + lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + let solady_update_lock = lockfile_get(prj.root(), &PathBuf::from("lib/solady")).unwrap(); + + assert_ne!(oz_init_lock, oz_update_lock); + assert_eq!(oz_update_lock.name(), "v5.1.0"); + assert_eq!(solady_init_lock, solady_update_lock); +}); + +// Ref: https://github.com/foundry-rs/foundry/pull/9522#pullrequestreview-2494431518 +forgetest!(should_not_update_tagged_deps, |prj, cmd| { + cmd.git_init(); + + // Installs oz at release tag + cmd.forge_fuse() + .args(["install", "openzeppelin/openzeppelin-contracts@tag=v4.9.4"]) + .assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + let submodules_init: Submodules = status.parse().unwrap(); + + let oz_init = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + + cmd.forge_fuse().arg("update").assert_success(); + + let out = cmd.git_submodule_status(); + let status = String::from_utf8_lossy(&out.stdout); + let submodules_update: Submodules = status.parse().unwrap(); + + assert_eq!(submodules_init, submodules_update); + + let oz_update = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + + assert_eq!(oz_init, oz_update); + // Check that halmos-cheatcodes dep is not added to oz deps + let halmos_path = prj.paths().libraries[0].join("openzeppelin-contracts/lib/halmos-cheatcodes"); + + assert!(!halmos_path.exists()); +}); + +forgetest!(can_remove_dep_from_foundry_lock, |prj, cmd| { + cmd.git_init(); + + cmd.forge_fuse() + .args(["install", "openzeppelin/openzeppelin-contracts@tag=v4.9.4"]) + .assert_success(); + + cmd.forge_fuse().args(["install", "vectorized/solady@513f581"]).assert_success(); + cmd.forge_fuse().args(["remove", "openzeppelin-contracts", "--force"]).assert_success(); + + let mut lock = Lockfile::new(prj.root()); + + lock.read().unwrap(); + + assert!(lock.get(&PathBuf::from("lib/openzeppelin-contracts")).is_none()); +}); + +forgetest!( + #[cfg_attr(windows, ignore = "weird git fail")] + can_sync_foundry_lock, + |prj, cmd| { + cmd.git_init(); + + cmd.forge_fuse().args(["install", "foundry-rs/forge-std@master"]).assert_success(); + + cmd.forge_fuse().args(["install", "vectorized/solady"]).assert_success(); + + fs::remove_file(prj.root().join("foundry.lock")).unwrap(); + + // sync submodules and write foundry.lock + cmd.forge_fuse().arg("install").assert_success(); + + let mut lock = forge::Lockfile::new(prj.root()); + lock.read().unwrap(); + + assert!(matches!( + lock.get(&PathBuf::from("lib/forge-std")).unwrap(), + &DepIdentifier::Branch { .. } + )); + assert!(matches!( + lock.get(&PathBuf::from("lib/solady")).unwrap(), + &DepIdentifier::Rev { .. } + )); + } +); + +// Tests that forge update doesn't break a working dependency by recursively updating nested +// dependencies +forgetest!( + #[cfg_attr(windows, ignore = "weird git fail")] + can_update_library_with_outdated_nested_dependency, + |prj, cmd| { + cmd.git_init(); + + let libs = prj.root().join("lib"); + let git_mod = prj.root().join(".git/modules/lib"); + let git_mod_file = prj.root().join(".gitmodules"); + + // get paths to check inside install fn + let package = libs.join("forge-5980-test"); + let package_mod = git_mod.join("forge-5980-test"); + + // install main dependency + cmd.forge_fuse().args(["install", "evalir/forge-5980-test"]).assert_success().stdout_eq( + str![[r#" +Installing forge-5980-test in [..] (url: https://github.com/evalir/forge-5980-test, tag: None) + Installed forge-5980-test + +"#]], + ); + + // assert paths exist + assert!(package.exists()); + assert!(package_mod.exists()); + + let submods = read_string(git_mod_file); + assert!(submods.contains("https://github.com/evalir/forge-5980-test")); + + // try to update the top-level dependency; there should be no update for this dependency, + // but its sub-dependency has upstream (breaking) changes; forge should not attempt to + // update the sub-dependency + cmd.forge_fuse().args(["update", "lib/forge-5980-test"]).assert_empty_stdout(); + + // add explicit remappings for test file + let config = Config { + remappings: vec![ + Remapping::from_str("forge-5980-test/=lib/forge-5980-test/src/").unwrap().into(), + // explicit remapping for sub-dependency seems necessary for some reason + Remapping::from_str( + "forge-5980-test-dep/=lib/forge-5980-test/lib/forge-5980-test-dep/src/", + ) + .unwrap() + .into(), + ], + ..Default::default() + }; + prj.write_config(config); + + // create test file that uses the top-level dependency; if the sub-dependency is updated, + // compilation will fail + prj.add_source( + "CounterCopy", + r#" +import "forge-5980-test/Counter.sol"; +contract CounterCopy is Counter { +} + "#, + ); + + // build and check output + cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + } +); + +#[tokio::test] +async fn uni_v4_core_sync_foundry_lock() { + let (prj, mut cmd) = + ExtTester::new("Uniswap", "v4-core", "e50237c43811bd9b526eff40f26772152a42daba") + .setup_forge_prj(true); + + assert!(!prj.root().join(FOUNDRY_LOCK).exists()); + + let git = Git::new(prj.root()); + + let submodules = git.submodules().unwrap(); + + let submod_forge_std = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/forge-std")).unwrap(); + let submod_oz = submodules + .into_iter() + .find(|s| s.path() == &PathBuf::from("lib/openzeppelin-contracts")) + .unwrap(); + let submod_solmate = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/solmate")).unwrap(); + + cmd.arg("install").assert_success(); + + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Rev { .. })); + assert_eq!(forge_std.rev(), submod_forge_std.rev()); + let solmate = lockfile_get(prj.root(), &PathBuf::from("lib/solmate")).unwrap(); + assert!(matches!(solmate, DepIdentifier::Rev { .. })); + assert_eq!(solmate.rev(), submod_solmate.rev()); + let oz = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + assert!(matches!(oz, DepIdentifier::Rev { .. })); + assert_eq!(oz.rev(), submod_oz.rev()); + + // Commit the lockfile + git.add(&PathBuf::from(FOUNDRY_LOCK)).unwrap(); + git.commit("Foundry lock").unwrap(); + + // Try update. Nothing should get updated everything is pinned tag/rev. + cmd.forge_fuse().arg("update").assert_success(); + + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Rev { .. })); + assert_eq!(forge_std.rev(), submod_forge_std.rev()); + let solmate = lockfile_get(prj.root(), &PathBuf::from("lib/solmate")).unwrap(); + assert!(matches!(solmate, DepIdentifier::Rev { .. })); + assert_eq!(solmate.rev(), submod_solmate.rev()); + let oz = lockfile_get(prj.root(), &PathBuf::from("lib/openzeppelin-contracts")).unwrap(); + assert!(matches!(oz, DepIdentifier::Rev { .. })); + assert_eq!(oz.rev(), submod_oz.rev()); +} + +#[tokio::test] +async fn oz_contracts_sync_foundry_lock() { + let (prj, mut cmd) = ExtTester::new( + "OpenZeppelin", + "openzeppelin-contracts", + "840c974028316f3c8172c1b8e5ed67ad95e255ca", + ) + .setup_forge_prj(true); + + assert!(!prj.root().join(FOUNDRY_LOCK).exists()); + + let git = Git::new(prj.root()); + + let submodules = git.submodules().unwrap(); + + let submod_forge_std = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/forge-std")).unwrap(); + let submod_erc4626_tests = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/erc4626-tests")).unwrap(); + let submod_halmos = submodules + .into_iter() + .find(|s| s.path() == &PathBuf::from("lib/halmos-cheatcodes")) + .unwrap(); + + cmd.arg("install").assert_success(); + + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Branch { .. })); + assert_eq!(forge_std.rev(), submod_forge_std.rev()); + assert_eq!(forge_std.name(), "v1"); + let erc4626_tests = lockfile_get(prj.root(), &PathBuf::from("lib/erc4626-tests")).unwrap(); + assert!(matches!(erc4626_tests, DepIdentifier::Rev { .. })); + assert_eq!(erc4626_tests.rev(), submod_erc4626_tests.rev()); + let halmos = lockfile_get(prj.root(), &PathBuf::from("lib/halmos-cheatcodes")).unwrap(); + assert!(matches!(halmos, DepIdentifier::Rev { .. })); + assert_eq!(halmos.rev(), submod_halmos.rev()); + + // Commit the lockfile + git.add(&PathBuf::from(FOUNDRY_LOCK)).unwrap(); + git.commit("Foundry lock").unwrap(); + + // Try update. forge-std should get updated, rest should remain the same. + cmd.forge_fuse().arg("update").assert_success(); + + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Branch { .. })); + // assert_eq!(forge_std.rev(), submod_forge_std.rev()); // This can fail, as forge-std will get + // updated to the latest commit on master. + assert_eq!(forge_std.name(), "v1"); // But it stays locked on the same master + let erc4626_tests = lockfile_get(prj.root(), &PathBuf::from("lib/erc4626-tests")).unwrap(); + assert!(matches!(erc4626_tests, DepIdentifier::Rev { .. })); + assert_eq!(erc4626_tests.rev(), submod_erc4626_tests.rev()); + let halmos = lockfile_get(prj.root(), &PathBuf::from("lib/halmos-cheatcodes")).unwrap(); + assert!(matches!(halmos, DepIdentifier::Rev { .. })); + assert_eq!(halmos.rev(), submod_halmos.rev()); +} + +#[tokio::test] +async fn correctly_sync_dep_with_multiple_version() { + let (prj, mut cmd) = ExtTester::new( + "yash-atreya", + "sync-lockfile-multi-version-dep", + "1ca47e73a168e54f8f7761862dbd0c603856c5c8", + ) + .setup_forge_prj(true); + + assert!(!prj.root().join(FOUNDRY_LOCK).exists()); + + let git = Git::new(prj.root()); + + let submodules = git.submodules().unwrap(); + let submod_forge_std = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/forge-std")).unwrap(); + let submod_solady = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/solady")).unwrap(); + let submod_solday_v_245 = + submodules.into_iter().find(|s| s.path() == &PathBuf::from("lib/solady-v0.0.245")).unwrap(); + + cmd.arg("install").assert_success(); + + let forge_std = lockfile_get(prj.root(), &PathBuf::from("lib/forge-std")).unwrap(); + assert!(matches!(forge_std, DepIdentifier::Rev { .. })); + assert_eq!(forge_std.rev(), submod_forge_std.rev()); + + let solady = lockfile_get(prj.root(), &PathBuf::from("lib/solady")).unwrap(); + assert!(matches!(solady, DepIdentifier::Rev { .. })); + assert_eq!(solady.rev(), submod_solady.rev()); + + let solday_v_245 = lockfile_get(prj.root(), &PathBuf::from("lib/solady-v0.0.245")).unwrap(); + assert!(matches!(solday_v_245, DepIdentifier::Rev { .. })); + assert_eq!(solday_v_245.rev(), submod_solday_v_245.rev()); +} + +// Regression test: `forge install --no-git` should clean up nested submodule contents +// when installing a tag that does not use submodules for its dependencies. +// https://github.com/foundry-rs/foundry/issues/13688 +forgetest!(flaky_install_no_git_cleans_nested_submodules, |prj, cmd| { + cmd.git_init(); + + // Install openzeppelin-contracts-upgradeable at v4.7.3 with --no-git. + // The default branch has submodules in lib/ (e.g. openzeppelin-contracts, erc4626-tests), + // but v4.7.3 does not use submodules for dependencies. + cmd.forge_fuse() + .args(["install", "--no-git", "OpenZeppelin/openzeppelin-contracts-upgradeable@v4.7.3"]) + .assert_success(); + + let dep_dir = prj.root().join("lib").join("openzeppelin-contracts-upgradeable"); + assert!(dep_dir.exists(), "dependency should be installed"); + + // The nested lib/ directory should either not exist or be empty — v4.7.3 does not use + // submodules so there should be no leftover submodule contents from the default branch. + let nested_lib = dep_dir.join("lib"); + if nested_lib.exists() { + let entries: Vec<_> = fs::read_dir(&nested_lib).unwrap().collect(); + assert!( + entries.is_empty(), + "nested lib/ should be empty after --no-git install at v4.7.3, found: {entries:?}" + ); + } + + // There should be no .git file or directory anywhere under the installed dependency. + fn assert_no_git(dir: &Path) { + for entry in fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + assert!( + path.file_name() != Some(".git".as_ref()), + "found leftover .git at {}", + path.display() + ); + if path.is_dir() { + assert_no_git(&path); + } + } + } + assert_no_git(&dep_dir); +}); + +forgetest_init!(sync_on_forge_update, |prj, cmd| { + let git = Git::new(prj.root()); + + let submodules = git.submodules().unwrap(); + assert!(submodules.0.iter().any(|s| s.rev() == FORGE_STD_REVISION)); + + let mut lockfile = Lockfile::new(prj.root()); + lockfile.read().unwrap(); + + let forge_std = lockfile.get(&PathBuf::from("lib/forge-std")).unwrap(); + assert!(forge_std.rev() == FORGE_STD_REVISION); + + // cd into the forge-std submodule + let forge_std_path = prj.root().join("lib/forge-std"); + let git = Git::new(&forge_std_path); + + // Ensure we're on the release tag first (known starting point) + git.checkout(false, forge_std.name()).unwrap(); + assert_eq!(git.head().unwrap(), forge_std.rev(), "Forge std should be at the release tag"); + + // Make sure origin/master is up to date, then resolve its commit hash deterministically. + git.fetch(false, "origin", Some("master")).unwrap(); + let origin_master_head = git.get_rev("refs/remotes/origin/master", &forge_std_path).unwrap(); + + // Run update and assert the output matches the dynamically resolved hash. + let expected_output = format!( + "Updated dep at 'lib/forge-std', (from: tag={}@{}, to: branch=master@{})\n", + forge_std.name(), + forge_std.rev(), + origin_master_head + ); + + cmd.forge_fuse() + .args(["update", "foundry-rs/forge-std@master"]) + .assert_success() + .stdout_eq(expected_output); + + let git = Git::new(&forge_std_path); + assert_eq!( + git.head().unwrap(), + origin_master_head, + "Submodule HEAD should match resolved origin/master after update" + ); + + let root_git = Git::new(prj.root()); + let submodules_after = root_git.submodules().unwrap(); + let forge_sm = submodules_after + .0 + .iter() + .find(|s| s.path().as_path() == Path::new("lib/forge-std")) + .expect("forge-std submodule should exist"); + assert_eq!( + forge_sm.rev(), + origin_master_head, + "Root submodule status should match resolved origin/master after update" + ); + + let mut lockfile = Lockfile::new(prj.root()); + lockfile.read().unwrap(); + let forge_std_after = lockfile.get(&PathBuf::from("lib/forge-std")).unwrap(); + assert_eq!( + forge_std_after.rev(), + origin_master_head, + "Lockfile rev should match resolved origin/master after update" + ); +}); diff --git a/crates/forge/tests/cli/json.rs b/crates/forge/tests/cli/json.rs new file mode 100644 index 0000000000000..1ca2736d999b4 --- /dev/null +++ b/crates/forge/tests/cli/json.rs @@ -0,0 +1,66 @@ +// tests enhanced `vm.parseJson` and `vm.serializeJson` cheatcodes, which are not constrained to +// alphabetical ordering of struct keys, but rather respect the Solidity struct definition. +forgetest_init!(test_parse_json, |prj, cmd| { + prj.add_test( + "JsonCheats", + r#" +import {Test} from "forge-std/Test.sol"; + +// Definition order: color, sweetness, sourness +// Alphabetical order: color, sourness, sweetness +struct Apple { + string color; + uint8 sweetness; + uint8 sourness; +} + +// Definition order: name, apples +// Alphabetical order: apples, name +struct FruitStall { + string name; + Apple[] apples; +} + +contract SimpleJsonCheatsTest is Test { + function testJsonParseAndSerialize() public { + // Initial JSON has keys in a custom order, different from definition and alphabetical. + string memory originalJson = + '{"name":"Fresh Fruit","apples":[{"sweetness":7,"sourness":3,"color":"Red"},{"sweetness":5,"sourness":5,"color":"Green"}]}'; + + // Parse the original JSON. The parser should correctly handle the unordered keys. + bytes memory decoded = vm.parseJson(originalJson); + FruitStall memory originalType = abi.decode(decoded, (FruitStall)); + + // Assert initial parsing is correct + assertEq(originalType.name, "Fresh Fruit"); + assertEq(originalType.apples[0].color, "Red"); + assertEq(originalType.apples[0].sweetness, 7); + assertEq(originalType.apples[1].sourness, 5); + + // Serialize the struct back to JSON. `vm.serializeJson` should respect the order for all keys. + string memory serializedJson = vm.serializeJsonType( + "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)", + abi.encode(originalType) + ); + + // The expected JSON should have keys ordered according to the struct definitions. + string memory expectedJson = + '{"name":"Fresh Fruit","apples":[{"color":"Red","sweetness":7,"sourness":3},{"color":"Green","sweetness":5,"sourness":5}]}'; + assertEq(serializedJson, expectedJson); + + // Parse the newly serialized JSON to complete the cycle. + bytes memory redecoded = vm.parseJson(serializedJson); + FruitStall memory finalType = abi.decode(redecoded, (FruitStall)); + + // Assert that the struct from the full cycle is identical to the original parsed struct. + assertEq(keccak256(abi.encode(finalType)), keccak256(abi.encode(originalType))); + } +} +"#, + ); + + // Directly run the test. No `bind-json` or type schemas are needed. + cmd.forge_fuse().args(["test"]).assert_success(); + // Should still work when the project is not compiled. + cmd.forge_fuse().args(["test"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index 2223d1cf11fce..dcf30e34827b5 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -1,5 +1,12 @@ -use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; -use foundry_config::{LintSeverity, LinterConfig}; +use forge_lint::{ + linter::Lint, + sol::{self, SolLint}, +}; +use foundry_config::{ + DenyLevel, LintSeverity, LinterConfig, SolidityErrorCode, lint::LintSpecificConfig, +}; + +mod geiger; const CONTRACT: &str = r#" // SPDX-License-Identifier: MIT @@ -13,7 +20,7 @@ contract ContractWithLints { function incorrectShiftHigh() public { uint256 localValue = 50; - result = 8 >> localValue; + uint256 result = 8 >> localValue; } function divideBeforeMultiplyMedium() public { (1 / 2) * 3; @@ -23,61 +30,210 @@ contract ContractWithLints { } function FUNCTION_MIXED_CASE_INFO() public {} } - "#; +"#; const OTHER_CONTRACT: &str = r#" - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// forge-lint: disable-next-line +import { ContractWithLints } from "./ContractWithLints.sol"; + +contract OtherContractWithLints { + function functionMIXEDCaseInfo() public {} +} +"#; + +const ONLY_IMPORTS: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// forge-lint: disable-next-line +import { ContractWithLints } from "./ContractWithLints.sol"; + +import { _PascalCaseInfo } from "./ContractWithLints.sol"; +import "./ContractWithLints.sol"; + +contract Dummy { + bool foo; +} +"#; + +const COUNTER_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract CounterA { + uint256 public CounterA_Fail_Lint; +} +"#; + +const COUNTER_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract CounterB { + uint256 public CounterB_Fail_Lint; +} +"#; + +const COUNTER_WITH_CONST: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +uint256 constant MAX = 1000000; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} +"#; + +const COUNTER_TEST_WITH_CONST: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { Counter, MAX } from "../src/Counter.sol"; + +contract CounterTest { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testFuzz_setNumber(uint256[MAX] calldata numbers) public { + for (uint256 i = 0; i < numbers.length; ++i) { + counter.setNumber(numbers[i]); + } + } +} +"#; + +const MULTI_CONTRACT_FILE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; - contract ContractWithLints { - uint256 VARIABLE_MIXED_CASE_INFO; +interface IToken { + function transfer(address to, uint256 amount) external returns (bool); +} + +library MathLib { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; } - "#; +} + +contract FirstContract { + uint256 public value; + + function setValue(uint256 _value) public { + value = _value; + } +} + +abstract contract BaseContract { + function baseFunction() public virtual; +} + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +contract SecondContract { + address public owner; + + constructor() { + owner = msg.sender; + } +} + +library StringLib { + function toUpperCase(string memory str) internal pure returns (string memory) { + return str; + } +} + +abstract contract AbstractStorage { + mapping(address => uint256) internal balances; + + function getBalance(address account) public view virtual returns (uint256); +} + +interface Token { + function transfer(address to, uint256 amount) external returns (bool); +} +"#; + +const SOLO_INTERFACES: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ERC20 { + function balanceOf(address account) external view returns (uint256); +} + +interface IToken { + function transfer(address to, uint256 amount) external returns (bool); +} +"#; forgetest!(can_use_config, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("ContractWithLints", CONTRACT).unwrap(); - prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); // Check config for `severity` and `exclude` prj.update_config(|config| { config.lint = LinterConfig { severity: vec![LintSeverity::High, LintSeverity::Med], exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, ..Default::default() }; }); cmd.arg("lint").assert_success().stderr_eq(str![[r#" warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - [FILE]:16:9 - | -16 | (1 / 2) * 3; - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); }); forgetest!(can_use_config_ignore, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("ContractWithLints", CONTRACT).unwrap(); - prj.add_source("OtherContract", OTHER_CONTRACT).unwrap(); + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContract", OTHER_CONTRACT); // Check config for `ignore` prj.update_config(|config| { - config.lint = - LinterConfig { ignore: vec!["src/ContractWithLints.sol".into()], ..Default::default() }; + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + ..Default::default() + }; }); cmd.arg("lint").assert_success().stderr_eq(str![[r#" -note[mixed-case-variable]: mutable variables should use mixedCase - [FILE]:6:17 - | -6 | uint256 VARIABLE_MIXED_CASE_INFO; - | ------------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable +note[mixed-case-function]: function names should use mixedCase + [FILE]:9:14 + │ +9 │ function functionMIXEDCaseInfo() public {} + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); @@ -85,43 +241,387 @@ note[mixed-case-variable]: mutable variables should use mixedCase // Check config again, ignoring all files prj.update_config(|config| { config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], ignore: vec!["src/ContractWithLints.sol".into(), "src/OtherContract.sol".into()], + lint_on_build: true, ..Default::default() }; }); cmd.arg("lint").assert_success().stderr_eq(str![[""]]); }); +forgetest!(can_use_config_mixed_case_exception, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContract", OTHER_CONTRACT); + + // Check config for `ignore` + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + lint_specific: LintSpecificConfig { + mixed_case_exceptions: vec!["MIXED".to_string()], + ..Default::default() + }, + }; + }); + cmd.arg("lint").assert_success().stderr_eq(str![[""]]); +}); + +forgetest!(multi_contract_file_no_exceptions, |prj, cmd| { + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // Without exceptions, should flag all 8 contract-like items + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 9 instances of multi-contract-file lint + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 9); + assert!(stderr.contains("IToken")); + assert!(stderr.contains("IERC20")); + assert!(stderr.contains("Token")); + assert!(stderr.contains("MathLib")); + assert!(stderr.contains("StringLib")); + assert!(stderr.contains("BaseContract")); + assert!(stderr.contains("AbstractStorage")); + assert!(stderr.contains("FirstContract")); + assert!(stderr.contains("SecondContract")); +}); + +forgetest!(multi_contract_file_interface_exception, |prj, cmd| { + use foundry_config::lint::ContractException; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // With interface exception, should flag 6 items + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + exclude_lints: vec!["interface-naming".into()], + lint_specific: LintSpecificConfig { + multi_contract_file_exceptions: vec![ContractException::Interface], + ..Default::default() + }, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 6 instances (3 interfaces excluded: IToken, IERC20, Token) + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 6); + assert!(!stderr.contains("IToken")); + assert!(!stderr.contains("IERC20")); + assert!(!stderr.contains("Token")); + assert!(stderr.contains("MathLib")); + assert!(stderr.contains("FirstContract")); +}); + +forgetest!(multi_contract_file_library_exception, |prj, cmd| { + use foundry_config::lint::ContractException; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // With library exception, should flag 7 items + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + lint_specific: LintSpecificConfig { + multi_contract_file_exceptions: vec![ContractException::Library], + ..Default::default() + }, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 7 instances (2 libraries excluded) + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 7); + assert!(stderr.contains("IToken")); + assert!(!stderr.contains("MathLib")); + assert!(!stderr.contains("StringLib")); + assert!(stderr.contains("FirstContract")); +}); + +forgetest!(multi_contract_file_abstract_exception, |prj, cmd| { + use foundry_config::lint::ContractException; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // With abstract contract exception, should flag 7 items + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + lint_specific: LintSpecificConfig { + multi_contract_file_exceptions: vec![ContractException::AbstractContract], + ..Default::default() + }, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 7 instances (2 abstract contracts excluded) + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 7); + assert!(stderr.contains("IToken")); + assert!(stderr.contains("MathLib")); + assert!(stderr.contains("FirstContract")); + assert!(!stderr.contains("BaseContract")); + assert!(!stderr.contains("AbstractStorage")); +}); + +forgetest!(multi_contract_file_multiple_exceptions, |prj, cmd| { + use foundry_config::lint::ContractException; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // With interface + library exceptions, should flag 4 items + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + exclude_lints: vec!["interface-naming".into()], + lint_specific: LintSpecificConfig { + multi_contract_file_exceptions: vec![ + ContractException::Interface, + ContractException::Library, + ], + ..Default::default() + }, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 4 instances (3 interfaces + 2 libraries excluded) + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 4); + assert!(!stderr.contains("IToken")); + assert!(!stderr.contains("IERC20")); + assert!(!stderr.contains("Token")); + assert!(!stderr.contains("MathLib")); + assert!(stderr.contains("BaseContract")); + assert!(stderr.contains("FirstContract")); +}); + +forgetest!(multi_contract_file_all_exceptions, |prj, cmd| { + use foundry_config::lint::ContractException; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // With all exceptions, should still flag 2 regular contracts + prj.update_config(|config| { + config.lint = LinterConfig { + lint_on_build: true, + severity: vec![ + LintSeverity::High, + LintSeverity::Med, + LintSeverity::Low, + LintSeverity::Info, + ], + lint_specific: LintSpecificConfig { + multi_contract_file_exceptions: vec![ + ContractException::Interface, + ContractException::Library, + ContractException::AbstractContract, + ], + ..Default::default() + }, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should see 2 instances (only the 2 regular contracts) + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 2); + assert!(!stderr.contains("IToken")); + assert!(!stderr.contains("MathLib")); + assert!(!stderr.contains("BaseContract")); + assert!(stderr.contains("FirstContract")); + assert!(stderr.contains("SecondContract")); +}); + +forgetest!(multi_contract_file_invalid_toml_value, |prj, cmd| { + use std::fs; + + prj.add_source("Simple", "contract Simple {}"); + + // Write invalid TOML config with invalid enum value + let config_path = prj.root().join("foundry.toml"); + let invalid_config = r#" +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[profile.default.lint.lint_specific] +multi_contract_file_exceptions = ["interface", "bad_contract_type", "library"] +"#; + + fs::write(&config_path, invalid_config).unwrap(); + + // Should fail with deserialization error + let output = cmd.arg("lint").assert_failure(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Assert specific error message for invalid enum variant + assert!(stderr.contains("unknown variant")); + assert!(stderr.contains("expected `one of `interface`, `library`, `abstract_contract`")); +}); + +forgetest!(multi_contract_file_valid_toml_values, |prj, cmd| { + use std::fs; + + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + // Write valid TOML config with all valid enum values + let config_path = prj.root().join("foundry.toml"); + let valid_config = r#" +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[profile.default.lint] +lint_on_build = true +severity = ["high", "medium", "low", "info"] + +[profile.default.lint.lint_specific] +multi_contract_file_exceptions = ["interface", "library", "abstract_contract"] +"#; + + fs::write(&config_path, valid_config).unwrap(); + + // Should succeed and only flag the 2 regular contracts + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + assert_eq!(stderr.matches("note[multi-contract-file]").count(), 2); + assert!(stderr.contains("FirstContract")); + assert!(stderr.contains("SecondContract")); +}); + +forgetest!(interface_naming_fails_for_non_prefixed, |prj, cmd| { + prj.add_source("MixedFile", MULTI_CONTRACT_FILE); + + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec!["multi-contract-file".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // Should flag only the interface that doesn't start with 'I': Token + assert_eq!(stderr.matches("note[interface-naming]").count(), 1); + assert!(stderr.contains("Token")); +}); + +forgetest!(interface_file_naming_fails_for_non_prefixed_file, |prj, cmd| { + prj.add_source("SoloInterfaces", SOLO_INTERFACES); + + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec!["multi-contract-file".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + let output = cmd.arg("lint").assert_success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // File name "SoloInterfaces" doesn't start with 'I', so interface-file-naming should trigger + assert_eq!(stderr.matches("note[interface-file-naming]").count(), 1); + // ERC20 is not prefixed with 'I', so interface-naming should trigger + assert_eq!(stderr.matches("note[interface-naming]").count(), 1); + assert!(stderr.contains("ERC20")); +}); + forgetest!(can_override_config_severity, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("ContractWithLints", CONTRACT).unwrap(); - prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); // Override severity prj.update_config(|config| { config.lint = LinterConfig { severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec![], ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, ..Default::default() }; }); cmd.arg("lint").args(["--severity", "info"]).assert_success().stderr_eq(str![[r#" -note[mixed-case-variable]: mutable variables should use mixedCase - [FILE]:6:17 - | -6 | uint256 VARIABLE_MIXED_CASE_INFO; - | ------------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable +note[mixed-case-function]: function names should use mixedCase + [FILE]:9:14 + │ +9 │ function functionMIXEDCaseInfo() public {} + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCaseInfo` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function "#]]); }); forgetest!(can_override_config_path, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("ContractWithLints", CONTRACT).unwrap(); - prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); // Override excluded files prj.update_config(|config| { @@ -129,92 +629,922 @@ forgetest!(can_override_config_path, |prj, cmd| { severity: vec![LintSeverity::High, LintSeverity::Med], exclude_lints: vec!["incorrect-shift".into()], ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + ..Default::default() }; }); cmd.arg("lint").arg("src/ContractWithLints.sol").assert_success().stderr_eq(str![[r#" warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - [FILE]:16:9 - | -16 | (1 / 2) * 3; - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply "#]]); }); forgetest!(can_override_config_lint, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("ContractWithLints", CONTRACT).unwrap(); - prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); // Override excluded lints prj.update_config(|config| { config.lint = LinterConfig { severity: vec![LintSeverity::High, LintSeverity::Med], exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, ..Default::default() }; }); cmd.arg("lint").args(["--only-lint", "incorrect-shift"]).assert_success().stderr_eq(str![[ r#" warning[incorrect-shift]: the order of args in a shift operation is incorrect - [FILE]:13:18 + [FILE]:13:26 + │ +13 │ uint256 result = 8 >> localValue; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift + + +"# + ]]); +}); + +forgetest!(build_runs_linter_by_default, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter to show only medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // Run forge build and expect linting output before compilation + cmd.arg("build").assert_success().stderr_eq(str![[r#" +warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision + [FILE]:16:9 + │ +16 │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply + + +"#]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: + | +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: + | +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"#]]); +}); + +forgetest!(build_respects_quiet_flag_for_linting, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter to show medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // Run forge build with --quiet flag - should not show linting output + cmd.arg("build").arg("--quiet").assert_success().stderr_eq(str![[""]]).stdout_eq(str![[""]]); +}); + +forgetest!(build_with_json_uses_json_linter_output, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter to show medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // Run forge build with --json flag - should use JSON formatter for linting + let output = cmd.arg("build").arg("--json").assert_success(); + + // Should contain JSON linting output + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + assert!(stderr.contains("\"code\"")); + assert!(stderr.contains("divide-before-multiply")); + + // Should also contain JSON compilation output + let stdout = String::from_utf8_lossy(&output.get_output().stdout); + assert!(stdout.contains("\"errors\"")); + assert!(stdout.contains("\"sources\"")); +}); + +forgetest!(build_respects_lint_on_build_false, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter with medium severity lints but disable lint_on_build + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: false, + ..Default::default() + }; + }); + + // Run forge build - should NOT show linting output because lint_on_build is false + cmd.arg("build").assert_success().stderr_eq(str![[""]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: + | +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: + | +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"#]]); +}); + +// Regression test: after switching the lint session to a buffer emitter, lint diagnostics +// produced during `forge build` must still stream to stderr (they are emitted through a +// separate emitter installed inside `SolidityLinter::lint`, not the session emitter). +forgetest!(build_emits_lint_diagnostics, |prj, cmd| { + prj.add_source("CounterAWithLints", COUNTER_A); + + prj.update_config(|config| { + config.lint.severity = vec![LintSeverity::Info]; + }); + + cmd.arg("build").assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase + [FILE]:6:20 + │ +6 │ uint256 public CounterA_Fail_Lint; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterAFailLint` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + + +"#]]); +}); + +forgetest!(build_no_lint_flag_skips_lint, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // Configure linter with medium severity lints and ensure lint_on_build is enabled + // so the only thing skipping the lint step is the `--no-lint` flag. + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + cmd.args(["build", "--no-lint"]).assert_success().stderr_eq(str![[r#""#]]).stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: | -13 | result = 8 >> localValue; - | --------------- +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). "# ]]); }); -#[tokio::test] -async fn ensure_lint_rule_docs() { - const FOUNDRY_BOOK_LINT_PAGE_URL: &str = - "https://book.getfoundry.sh/reference/forge/forge-lint"; - - // Fetch the content of the lint reference - let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { - Ok(resp) => { - if !resp.status().is_success() { - panic!( - "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", - status = resp.status() - ); +forgetest!(can_process_inline_config_regardless_of_input_order, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); + cmd.arg("lint").assert_success(); + + prj.add_source("OtherContractWithLints", OTHER_CONTRACT); + prj.add_source("ContractWithLints", CONTRACT); + cmd.arg("lint").assert_success(); +}); + +// +forgetest!(can_use_only_lint_with_multilint_passes, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + prj.add_source("OnlyImports", ONLY_IMPORTS); + cmd.arg("lint").args(["--only-lint", "unused-import"]).assert_success().stderr_eq(str![[r#" +note[unused-import]: unused imports should be removed + [FILE]:8:10 + │ +8 │ import { _PascalCaseInfo } from "./ContractWithLints.sol"; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + + +"#]]); +}); + +// +forgetest!(can_lint_only_built_files, |prj, cmd| { + prj.add_source("CounterAWithLints", COUNTER_A); + prj.add_source("CounterBWithLints", COUNTER_B); + + prj.update_config(|config| { + config.lint.severity = vec![LintSeverity::Info]; + }); + + // Both contracts should be linted on build. Redact contract as order is not guaranteed. + cmd.forge_fuse().args(["build"]).assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase +... +note[mixed-case-variable]: mutable variables should use mixedCase +... +"#]]); + + // Only contract CounterBWithLints that we build should be linted. + let args = ["build", "src/CounterBWithLints.sol"]; + cmd.forge_fuse().args(args).assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase + [FILE]:6:20 + │ +6 │ uint256 public CounterB_Fail_Lint; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterBFailLint` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + + +"#]]); +}); + +// +forgetest!(can_lint_param_constants, |prj, cmd| { + prj.add_source("Counter", COUNTER_WITH_CONST); + prj.add_test("CounterTest", COUNTER_TEST_WITH_CONST); + + cmd.forge_fuse().args(["build"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); +}); + +// +forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { + prj.add_source( + "UnwrappedModifierTest", + r#" + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + contract UnwrappedModifierTest { + mapping(address => bool) isOwner; + + modifier onlyOwner() { + require(isOwner[msg.sender], "Not owner"); + require(msg.sender != address(0), "Zero address"); + _; } - match resp.text().await { - Ok(text) => text, - Err(e) => { - panic!("Failed to read response text: {e}"); - } + + function doSomething() public onlyOwner {} + } + "#, + ); + + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::CodeSize], + exclude_lints: vec![], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // should produce clean JSON without ANSI escape sequences (for the url nor the snippets) + cmd.arg("lint").arg("--json").assert_json_stderr(true, + str![[r#" +{ + "$message_type": "diagnostic", + "message": "wrap modifier logic to reduce code size", + "code": { + "code": "unwrapped-modifier-logic", + "explanation": null + }, + "level": "note", + "spans": [ + { + "file_name": "src/UnwrappedModifierTest.sol", + "byte_start": 174, + "byte_end": 355, + "line_start": 8, + "line_end": 12, + "column_start": 13, + "column_end": 14, + "is_primary": true, + "text": [ + { + "text": " modifier onlyOwner() {", + "highlight_start": 13, + "highlight_end": 35 + }, + { + "text": " require(isOwner[msg.sender], \"Not owner\");", + "highlight_start": 1, + "highlight_end": 59 + }, + { + "text": " require(msg.sender != address(0), \"Zero address\");", + "highlight_start": 1, + "highlight_end": 67 + }, + { + "text": " _;", + "highlight_start": 1, + "highlight_end": 19 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 14 + } + ], + "label": null, + "suggested_replacement": null + } + ], + "children": [ + { + "message": "https://getfoundry.sh/forge/linting/unwrapped-modifier-logic", + "code": null, + "level": "help", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "wrap modifier logic to reduce code size", + "code": null, + "level": "help", + "spans": [ + { + "file_name": "src/UnwrappedModifierTest.sol", + "byte_start": 174, + "byte_end": 355, + "line_start": 8, + "line_end": 12, + "column_start": 13, + "column_end": 14, + "is_primary": true, + "text": [ + { + "text": " modifier onlyOwner() {", + "highlight_start": 13, + "highlight_end": 35 + }, + { + "text": " require(isOwner[msg.sender], \"Not owner\");", + "highlight_start": 1, + "highlight_end": 59 + }, + { + "text": " require(msg.sender != address(0), \"Zero address\");", + "highlight_start": 1, + "highlight_end": 67 + }, + { + "text": " _;", + "highlight_start": 1, + "highlight_end": 19 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 14 } + ], + "label": null, + "suggested_replacement": "modifier onlyOwner() {\n _onlyOwner();\n _;\n }\n\n function _onlyOwner() internal {\n require(isOwner[msg.sender], \"Not owner\");\n require(msg.sender != address(0), \"Zero address\");\n }" } - Err(e) => { - panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); + ], + "children": [], + "rendered": null + } + ], + "rendered": "note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size\n\nhelp: wrap modifier logic to reduce code size\n 9 + _onlyOwner();\n10 + _;\n11 + }\n12 + \n13 + function _onlyOwner() internal {\n14 + require(isOwner[msg.sender], \"Not owner\");\n15 + require(msg.sender != address(0), \"Zero address\");\n16 + }\n ╭▸ src/UnwrappedModifierTest.sol:8:13\n │\n 8 │ ┏ modifier onlyOwner() {\n 9 │ ┃ require(isOwner[msg.sender], \"Not owner\");\n10 │ ┃ require(msg.sender != address(0), \"Zero address\");\n11 │ ┃ _;\n12 │ ┃ }\n │ ┗━━━━━━━━━━━━━┛\n │\n ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic\n ╭╴\n 8 ± modifier onlyOwner() {\n ╰╴\n" +} +"#]], +); +}); + +forgetest!(can_fail_on_lints, |prj, cmd| { + prj.add_source("ContractWithLints", CONTRACT); + + // -- LINT ALL SEVERITIES [OUTPUT: WARN + NOTE] ---------------------------- + + cmd.forge_fuse().arg("lint").assert_success(); // DenyLevel::Never (default) + + prj.update_config(|config| { + config.deny = DenyLevel::Warnings; + }); + cmd.forge_fuse().arg("lint").assert_failure(); + + prj.update_config(|config| { + config.deny = DenyLevel::Notes; + }); + cmd.forge_fuse().arg("lint").assert_failure(); + + // cmd flags can override config + prj.update_config(|config| { + config.deny = DenyLevel::Never; + }); + cmd.forge_fuse().args(["lint", "--deny warnings"]).assert_failure(); + cmd.forge_fuse().args(["lint", "--deny notes"]).assert_failure(); + + // usage of `--deny-warnings` flag works, but emits a warning + cmd.forge_fuse().args(["lint", "--deny-warnings"]).assert_failure().stderr_eq(str![[r#" +Warning: `--deny-warnings` is being deprecated in favor of `--deny warnings`. +... + +"#]]); + + // usage of `deny_warnings` config works, but emits a warning + prj.create_file( + "foundry.toml", + r#" +[profile.default] +deny_warnings = true +"#, + ); + cmd.forge_fuse().arg("lint").assert_failure().stderr_eq(str![[r#" +Warning: Key `deny_warnings` is being deprecated in favor of `deny = warnings`. It will be removed in future versions. +... + +"#]]); + + // -- ONLY LINT LOW SEVERITIES [OUTPUT: NOTE] ------------------------------ + + prj.update_config(|config| { + config.deny_warnings = false; + config.deny = DenyLevel::Never; + config.lint.severity = vec![LintSeverity::Info, LintSeverity::Gas, LintSeverity::CodeSize]; + }); + cmd.forge_fuse().arg("lint").assert_success(); + + prj.update_config(|config| { + config.deny = DenyLevel::Warnings; + }); + cmd.forge_fuse().arg("lint").assert_success(); + + prj.update_config(|config| { + config.deny = DenyLevel::Notes; + }); + cmd.forge_fuse().arg("lint").assert_failure(); + + // cmd flags can override config + prj.update_config(|config| { + config.deny = DenyLevel::Never; + }); + cmd.forge_fuse().args(["lint", "--deny notes"]).assert_failure(); +}); + +// ------------------------------------------------------------------------------------------------ + +#[tokio::test] +async fn ensure_lint_rule_docs() { + let client = reqwest::Client::new(); + let mut failures = Vec::new(); + + for lint in registered_lints() { + let url = lint.help(); + let response = match client.get(url).send().await { + Ok(response) => response, + Err(err) => { + failures.push(format!("{} ({url}) could not be fetched: {err}", lint.id())); + continue; + } + }; + + if !response.status().is_success() { + failures.push(format!("{} ({url}) returned HTTP {}", lint.id(), response.status())); + continue; } - }; - - // Ensure no missing lints - let mut missing_lints = Vec::new(); - for lint in REGISTERED_LINTS { - let selector = format!("#{}", lint.id()); - if !content.contains(&selector) { - missing_lints.push(lint.id()); + + let content = match response.text().await { + Ok(content) => content.to_lowercase(), + Err(err) => { + failures + .push(format!("{} ({url}) response body could not be read: {err}", lint.id())); + continue; + } + }; + + let selector = lint.id().to_lowercase(); + let selector_with_space = selector.replace('-', " "); + if !content.contains(&selector) && !content.contains(&selector_with_space) { + failures.push(format!("{} ({url}) did not mention the lint id", lint.id())); } } - if !missing_lints.is_empty() { + if !failures.is_empty() { let mut msg = String::from( - "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + "Foundry Book lint validation failed. The following lint pages are missing or invalid:\n", ); - for lint in missing_lints { - msg.push_str(&format!(" - {lint}\n")); + for failure in failures { + msg.push_str(&format!(" - {failure}\n")); } msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); panic!("{msg}"); } } + +#[test] +fn ensure_no_privileged_lint_id() { + for lint in registered_lints() { + assert_ne!(lint.id(), "all", "lint-id 'all' is reserved. Please use a different id"); + } +} + +fn registered_lints() -> impl Iterator { + sol::high::REGISTERED_LINTS + .iter() + .chain(sol::med::REGISTERED_LINTS) + .chain(sol::low::REGISTERED_LINTS) + .chain(sol::info::REGISTERED_LINTS) + .chain(sol::gas::REGISTERED_LINTS) + .chain(sol::codesize::REGISTERED_LINTS) +} + +// +forgetest!(dependency_warnings_do_not_affect_lint_exit_code, |prj, cmd| { + // Library with code that triggers a solc warning (unused local variable) + const LIB_WITH_WARNING: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library LibWithWarning { + function foo() internal pure returns (uint256) { + uint256 unusedVar = 42; + return 1; + } +} +"#; + + // Clean contract that imports the library but has no lint issues + const CLEAN_CONTRACT: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibWithWarning } from "../lib/LibWithWarning.sol"; + +contract CleanContract { + function bar() public pure returns (uint256) { + return LibWithWarning.foo(); + } +} +"#; + + prj.add_lib("LibWithWarning", LIB_WITH_WARNING); + prj.add_source("CleanContract", CLEAN_CONTRACT); + + // Ignore the solc warning so compilation succeeds, but it still gets counted in diagnostics + prj.update_config(|config| { + config.ignored_error_codes = vec![SolidityErrorCode::UnusedLocalVariable]; + }); + + // Clear cache to force recompilation during lint + prj.clear_cache(); + + // Lint with deny = notes via CLI flag. + // Should succeed because the linter only counts lint diagnostics, not build-phase warnings. + cmd.args(["lint", "-D", "notes"]).assert_success(); +}); + +forgetest!(skips_linting_for_old_solidity_versions, |prj, cmd| { + const OLD_CONTRACT: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract OldContract { + uint256 VARIABLE_MIXED_CASE_INFO; + + function FUNCTION_MIXED_CASE_INFO() public {} +} +"#; + + // Add a contract with Solidity 0.7.x which has lint issues + prj.add_source("OldContract", OLD_CONTRACT); + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // Run forge build - should SUCCEED without linting + cmd.arg("build").assert_success().stderr_eq(str![[ + r#"Warning: unable to lint. Solar only supports Solidity versions >=0.8.0 + +"# + ]]); + + // Run forge lint - should FAIL + cmd.forge_fuse().arg("lint").assert_failure().stderr_eq(str![[ + r#"Error: unable to lint. Solar only supports Solidity versions >=0.8.0 + +"# + ]]); +}); + +const PRAGMA_INCONSISTENT_ALPHA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Alpha {} +"#; + +const PRAGMA_INCONSISTENT_BETA: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract Beta {} +"#; + +forgetest!(pragma_inconsistent_cross_file, |prj, cmd| { + prj.add_source("Alpha", PRAGMA_INCONSISTENT_ALPHA); + prj.add_source("Beta", PRAGMA_INCONSISTENT_BETA); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +const PRAGMA_EXACT_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract A {} +"#; + +const PRAGMA_EXACT_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract B {} +"#; + +const PRAGMA_EXACT_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract C {} +"#; + +const PRAGMA_CARET_A: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract A {} +"#; + +const PRAGMA_CARET_B: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract B {} +"#; + +const PRAGMA_CARET_C: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract C {} +"#; + +const NO_PRAGMA_C: &str = r#" +// SPDX-License-Identifier: MIT + +contract C {} +"#; + +// Multiple files all using the exact same pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_exact_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_EXACT_C); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Multiple files all using the exact same caret pragma must NOT warn. +forgetest!(pragma_inconsistent_consistent_caret_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + prj.add_source("B", PRAGMA_CARET_B); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// A single file in the project cannot conflict with itself. +forgetest!(pragma_inconsistent_single_file_no_warning, |prj, cmd| { + prj.add_source("A", PRAGMA_CARET_A); + + cmd.arg("lint") + .args(["--only-lint", "pragma-inconsistent"]) + .assert_success() + .stderr_eq(str![[r#""#]]); +}); + +// Even files that share a requirement still emit when ANY other variant exists. +// Two files with `0.8.20` plus one file with `^0.8.20` => 3 emits total. +forgetest!(pragma_inconsistent_duplicates_among_conflict, |prj, cmd| { + prj.add_source("A", PRAGMA_EXACT_A); + prj.add_source("B", PRAGMA_EXACT_B); + prj.add_source("C", PRAGMA_CARET_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); + +// Files without a `pragma solidity` directive must not affect the conflict computation. +// Note: `add_raw_source` is used here to bypass the helper that would otherwise inject a default +// `pragma solidity =;` for files that omit one. +forgetest!(pragma_inconsistent_files_without_pragma, |prj, cmd| { + prj.add_raw_source("A", PRAGMA_EXACT_A); + prj.add_raw_source("B", PRAGMA_CARET_B); + // C has no pragma at all; should be ignored by the cross-file check. + prj.add_raw_source("C", NO_PRAGMA_C); + + cmd.arg("lint").args(["--only-lint", "pragma-inconsistent"]).assert_success().stderr_eq(str![ + [r#" +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + [FILE]:3:1 + │ +3 │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + + +"#] + ]); +}); diff --git a/crates/forge/tests/cli/lint/geiger.rs b/crates/forge/tests/cli/lint/geiger.rs new file mode 100644 index 0000000000000..202866e83e35f --- /dev/null +++ b/crates/forge/tests/cli/lint/geiger.rs @@ -0,0 +1,108 @@ +forgetest_init!(call, |prj, cmd| { + prj.add_test( + "call.t.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + vm.ffi(inputs); + } + } + "#, + ); + + cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" +... +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + [FILE]:9:20 + │ +9 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +Error: aborting due to 1 linter note(s) +... +"#]]); +}); + +forgetest_init!(assignment, |prj, cmd| { + prj.add_test( + "assignment.t.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract A is Test { + function do_ffi() public returns (bytes memory) { + string[] memory inputs = new string[](1); + bytes memory stuff = vm.ffi(inputs); + return stuff; + } + } + "#, + ); + + cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" +... +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + [FILE]:9:41 + │ +9 │ bytes memory stuff = vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +Error: aborting due to 1 linter note(s) +... +"#]]); +}); + +forgetest_init!(exit_code, |prj, cmd| { + prj.add_test( + "multiple.t.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + vm.ffi(inputs); + vm.ffi(inputs); + vm.ffi(inputs); + } + } + "#, + ); + + cmd.arg("geiger").assert_failure().stderr_eq(str![[r#" +... +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + [FILE]:9:20 + │ +9 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + [FILE]:10:20 + │ +10 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + [FILE]:11:20 + │ +11 │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +Error: aborting due to 3 linter note(s) +... +"#]]); +}); diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index 40a8c9f8b5d5a..51bfd680f3e8e 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -4,6 +4,8 @@ extern crate foundry_test_utils; pub mod constants; pub mod utils; +mod backtrace; +mod bind; mod bind_json; mod build; mod cache; @@ -17,10 +19,13 @@ mod debug; mod doc; mod eip712; mod failure_assertions; -mod geiger; mod inline_config; +mod install; + +mod json; mod lint; mod multi_script; +mod precompiles; mod script; mod soldeer; mod svm; @@ -30,4 +35,6 @@ mod verify_bytecode; mod version; mod ext_integration; +mod fmt; +mod fmt_integration; mod test_optimizer; diff --git a/crates/forge/tests/cli/multi_script.rs b/crates/forge/tests/cli/multi_script.rs index d6f7628da1694..22c35282c5a08 100644 --- a/crates/forge/tests/cli/multi_script.rs +++ b/crates/forge/tests/cli/multi_script.rs @@ -1,5 +1,5 @@ //! Contains various tests related to forge script -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use foundry_test_utils::{ScriptOutcome, ScriptTester}; diff --git a/crates/forge/tests/cli/precompiles.rs b/crates/forge/tests/cli/precompiles.rs new file mode 100644 index 0000000000000..505a6f7b04800 --- /dev/null +++ b/crates/forge/tests/cli/precompiles.rs @@ -0,0 +1,234 @@ +//! Contains various tests for `forge test` with precompiles. + +use foundry_evm_networks::NetworkConfigs; +use foundry_test_utils::str; + +forgetest_init!(precompile_trace_decoding, |prj, cmd| { + prj.add_test( + "PrecompileTrace.t.sol", + r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +contract PrecompileCaller { + constructor() { + // 0x01 - ECRECOVER + { + bytes32 hash = keccak256("test message"); + uint8 v = 27; + bytes32 r = bytes32(uint256(1)); + bytes32 s = bytes32(uint256(2)); + address(0x01).staticcall(abi.encode(hash, v, r, s)); + } + + // 0x02 - SHA256 + address(0x02).staticcall(abi.encodePacked("hello")); + + // 0x03 - RIPEMD160 + address(0x03).staticcall(abi.encodePacked("hello")); + + // 0x04 - IDENTITY (datacopy) + address(0x04).staticcall(abi.encodePacked("hello")); + + // 0x05 - MODEXP: compute 2^3 mod 5 = 3 + { + bytes memory modexpInput = abi.encodePacked( + uint256(1), // base length + uint256(1), // exponent length + uint256(1), // modulus length + uint8(2), // base = 2 + uint8(3), // exponent = 3 + uint8(5) // modulus = 5 + ); + address(0x05).staticcall(modexpInput); + } + + // 0x06 - BN254 ADD (ecadd): P + O = P + { + uint256 g1x = 1; + uint256 g1y = 2; + uint256 zerox = 0; + uint256 zeroy = 0; + address(0x06).staticcall(abi.encode(g1x, g1y, zerox, zeroy)); + } + + // 0x07 - BN254 MUL (ecmul): 1 * G = G + { + uint256 g1x = 1; + uint256 g1y = 2; + uint256 scalar = 1; + address(0x07).staticcall(abi.encode(g1x, g1y, scalar)); + } + + // 0x08 - BN254 PAIRING: empty input returns success (1) + address(0x08).staticcall(""); + + // 0x09 - BLAKE2F + { + bytes memory blake2fInput = new bytes(213); + blake2fInput[3] = 0x0c; // 12 rounds + bytes8[8] memory iv = [ + bytes8(0x6a09e667f3bcc908), + bytes8(0xbb67ae8584caa73b), + bytes8(0x3c6ef372fe94f82b), + bytes8(0xa54ff53a5f1d36f1), + bytes8(0x510e527fade682d1), + bytes8(0x9b05688c2b3e6c1f), + bytes8(0x1f83d9abfb41bd6b), + bytes8(0x5be0cd19137e2179) + ]; + for (uint256 i = 0; i < 8; i++) { + for (uint256 j = 0; j < 8; j++) { + blake2fInput[4 + i * 8 + j] = iv[i][j]; + } + } + blake2fInput[212] = 0x01; + address(0x09).staticcall(blake2fInput); + } + + // 0x0B - BLS12-381 G1 ADD (two points at infinity) + address(0x0B).staticcall(new bytes(256)); + + // 0x0C - BLS12-381 G1 MSM + address(0x0C).staticcall(new bytes(160)); + + // 0x0D - BLS12-381 G2 ADD (two points at infinity) + address(0x0D).staticcall(new bytes(512)); + + // 0x0E - BLS12-381 G2 MSM + address(0x0E).staticcall(new bytes(288)); + + // 0x0F - BLS12-381 PAIRING (G1 + G2 infinity points) + address(0x0F).staticcall(new bytes(384)); + + // 0x10 - BLS12-381 MAP FP TO G1 + address(0x10).staticcall(new bytes(64)); + + // 0x11 - BLS12-381 MAP FP2 TO G2 + address(0x11).staticcall(new bytes(128)); + + // 0x100 - P256VERIFY (secp256r1) + address(0x100).staticcall(new bytes(160)); + } +} + +contract PrecompileTraceTest is Test { + function test_precompile_traces() public { + new PrecompileCaller(); + } +} + "#, + ); + + cmd.args(["test", "--mt", "test_precompile_traces", "-vvvv", "--evm-version", "osaka"]) + .assert_success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/PrecompileTrace.t.sol:PrecompileTraceTest +[PASS] test_precompile_traces() ([GAS]) +Traces: + [..] PrecompileTraceTest::test_precompile_traces() + ├─ [..] → new PrecompileCaller@[..] + │ ├─ [..] PRECOMPILES::ecrecover(0xea83cdcdd06bf61e414054115a551e23133711d0507dcbc07a4bab7dc4581935, 27, 1, 2) [staticcall] + │ │ └─ ← [Return] 0xBe038042508C42Df7b2A529cd4Cc0a9447c7D2b6 + │ ├─ [..] PRECOMPILES::sha256(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 + │ ├─ [..] PRECOMPILES::ripemd(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x000000000000000000000000108f07b838241261 + │ ├─ [..] PRECOMPILES::identity(0x68656c6c6f) [staticcall] + │ │ └─ ← [Return] 0x68656c6c6f + │ ├─ [..] PRECOMPILES::modexp(1, 1, 1, 0x02, 0x03, 0x05) [staticcall] + │ │ └─ ← [Return] 0x03 + │ ├─ [..] PRECOMPILES::ecadd(1, 2, 0, 0) [staticcall] + │ │ └─ ← [Return] (1, 2) + │ ├─ [..] PRECOMPILES::ecmul(1, 2, 1) [staticcall] + │ │ └─ ← [Return] (1, 2) + │ ├─ [..] PRECOMPILES::ecpairing() [staticcall] + │ │ └─ ← [Return] true + │ ├─ [..] PRECOMPILES::blake2f(12, [633244976228469098, 4298627039875721147, 3168446158426304060, 17381112106731261861, 15096882533739138641, 2264253069420660123, 7763433881832358687, 8728396173323133019], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0], 1) [staticcall] + │ │ └─ ← [Return] 0x1a48bfec594a1b13bb024be345656b8af895d662ccbc3f39fb5ecf2ef05942b5acace594cb81cdff6044b5bfaabfea105168676ce5753f6bb559ce3f92ad4850 + │ ├─ [..] PRECOMPILES::bls12G1Add(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G1Msm(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G2Add(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12G2Msm(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + │ ├─ [..] PRECOMPILES::bls12PairingCheck(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] true + │ ├─ [..] PRECOMPILES::bls12MapFpToG1(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x0000000000000000000000000000000011a9a0372b8f332d5c30de9ad14e50372a73fa4c45d5f2fa5097f2d6fb93bcac592f2e1711ac43db0519870c7d0ea41500000000000000000000000000000000092c0f994164a0719f51c24ba3788de240ff926b55f58c445116e8bc6a47cd63392fd4e8e22bdf9feaa96ee773222133 + │ ├─ [..] PRECOMPILES::bls12MapFp2ToG2(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ │ └─ ← [Return] 0x00000000000000000000000000000000018320896ec9eef9d5e619848dc29ce266f413d02dd31d9b9d44ec0c79cd61f18b075ddba6d7bd20b7ff27a4b324bfce000000000000000000000000000000000a67d12118b5a35bb02d2e86b3ebfa7e23410db93de39fb06d7025fa95e96ffa428a7a27c3ae4dd4b40bd251ac658892000000000000000000000000000000000260e03644d1a2c321256b3246bad2b895cad13890cbe6f85df55106a0d334604fb143c7a042d878006271865bc359410000000000000000000000000000000004c69777a43f0bda07679d5805e63f18cf4e0e7c6112ac7f70266d199b4f76ae27c6269a3ceebdae30806e9a76aadf5c + │ ├─ [..] P256VERIFY::fulfillBasicOrder_efficient_6GL6yc() [staticcall] + │ │ └─ ← [Return] + │ └─ ← [Return] 62 bytes of code + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests transfer using celo precompile. +// +forgetest_init!(celo_transfer, |prj, cmd| { + prj.update_config(|config| { + config.networks = NetworkConfigs::with_celo(); + }); + + prj.add_test( + "CeloTransfer.t.sol", + r#" +import "forge-std/Test.sol"; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); +} + +contract CeloTransferTest is Test { + IERC20 celo = IERC20(0x471EcE3750Da237f93B8E339c536989b8978a438); + IERC20 usdc = IERC20(0xcebA9300f2b948710d2653dD7B07f33A8B32118C); + IERC20 usdt = IERC20(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e); + + address binanceAccount = 0xf6436829Cf96EA0f8BC49d300c536FCC4f84C4ED; + address recipient = makeAddr("recipient"); + + function setUp() public { + vm.createSelectFork("https://forno.celo.org"); + } + + function testCeloBalance() external { + console2.log("recipient balance before", celo.balanceOf(recipient)); + vm.prank(binanceAccount); + celo.transfer(recipient, 100); + console2.log("recipient balance after", celo.balanceOf(recipient)); + assertEq(celo.balanceOf(recipient), 100); + } +} + "#, + ); + + cmd.args(["test", "--mt", "testCeloBalance", "-vvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/CeloTransfer.t.sol:CeloTransferTest +[PASS] testCeloBalance() ([GAS]) +Logs: + recipient balance before 0 + recipient balance after 100 + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 1d6f415aef1d4..031d80f0cf071 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2,14 +2,15 @@ use crate::constants::TEMPLATE_CONTRACT; use alloy_hardforks::EthereumHardfork; -use alloy_primitives::{address, hex, Address, Bytes}; -use anvil::{spawn, NodeConfig}; +use alloy_network::Ethereum; +use alloy_primitives::{Address, Bytes, address, hex}; +use anvil::{NodeConfig, spawn}; use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ + ScriptOutcome, ScriptTester, rpc::{self, next_http_archive_rpc_url}, snapbox::IntoData, util::{OTHER_SOLC_VERSION, SOLC_VERSION}, - ScriptOutcome, ScriptTester, }; use regex::Regex; use serde_json::Value; @@ -20,10 +21,9 @@ forgetest_init!( #[ignore] can_use_fork_cheat_codes_in_script, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" import "forge-std/Script.sol"; contract ContractScript is Script { @@ -35,8 +35,7 @@ contract ContractScript is Script { } } "#, - ) - .unwrap(); + ); let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); @@ -46,10 +45,9 @@ contract ContractScript is Script { // Tests that the `run` command works correctly forgetest!(can_execute_script_command2, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); function run() external { @@ -57,8 +55,7 @@ contract Demo { } } "#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -75,10 +72,9 @@ Script ran successfully. // Tests that the `run` command works correctly when path *and* script name is specified forgetest!(can_execute_script_command_fqn, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); function run() external { @@ -86,8 +82,7 @@ contract Demo { } } "#, - ) - .unwrap(); + ); cmd.arg("script").arg(format!("{}:Demo", script.display())).assert_success().stdout_eq(str![[ r#" @@ -104,10 +99,9 @@ Script ran successfully. // Tests that the run command can run arbitrary functions forgetest!(can_execute_script_command_with_sig, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); function myFunction() external { @@ -115,8 +109,7 @@ contract Demo { } } "#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).arg("--sig").arg("myFunction()").assert_success().stdout_eq( str![[r#" @@ -143,10 +136,25 @@ contract FailingScript is Script { } "#; +static OUT_OF_GAS_SCRIPT: &str = r#" +import "forge-std/Script.sol"; + +contract OutOfGasScript is Script { + function run() external { + uint256 i; + while (true) { + unchecked { + ++i; + } + } + } +} +"#; + // Tests that execution throws upon encountering a revert in the script. forgetest_async!(assert_exit_code_error_on_failure_script, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); - let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + let script = prj.add_source("FailingScript", FAILING_SCRIPT); // set up command cmd.arg("script").arg(script); @@ -162,7 +170,7 @@ Error: script failed: failed // forgetest_async!(assert_exit_code_error_on_failure_script_with_json, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); - let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + let script = prj.add_source("FailingScript", FAILING_SCRIPT); // set up command cmd.arg("script").arg(script).arg("--json"); @@ -174,13 +182,38 @@ Error: script failed: failed "#]]); }); +// Tests that script failures surface halt reasons for empty revert data. +forgetest_async!(assert_exit_code_error_on_out_of_gas_script, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("OutOfGasScript", OUT_OF_GAS_SCRIPT); + + cmd.arg("script").arg(script); + + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: EvmError: OutOfGas + +"#]]); +}); + +// Tests that --json script failures also surface halt reasons for empty revert data. +forgetest_async!(assert_exit_code_error_on_out_of_gas_script_with_json, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("OutOfGasScript", OUT_OF_GAS_SCRIPT); + + cmd.arg("script").arg(script).arg("--json"); + + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: EvmError: OutOfGas + +"#]]); +}); + // Tests that the manually specified gas limit is used when using the --unlocked option forgetest_async!(can_execute_script_command_with_manual_gas_limit_unlocked, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); - let deploy_script = prj - .add_source( - "Foo", - r#" + let deploy_script = prj.add_source( + "Foo", + r#" import "forge-std/Script.sol"; contract GasWaster { @@ -196,8 +229,7 @@ contract DeployScript is Script { } } "#, - ) - .unwrap(); + ); let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; @@ -278,10 +310,9 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. // Tests that the manually specified gas limit is used. forgetest_async!(can_execute_script_command_with_manual_gas_limit, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); - let deploy_script = prj - .add_source( - "Foo", - r#" + let deploy_script = prj.add_source( + "Foo", + r#" import "forge-std/Script.sol"; contract GasWaster { @@ -297,8 +328,7 @@ contract DeployScript is Script { } } "#, - ) - .unwrap(); + ); let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; @@ -383,10 +413,9 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. // Tests that the run command can run functions with arguments forgetest!(can_execute_script_command_with_args, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); event log_uint(uint); @@ -397,8 +426,7 @@ contract Demo { } } "#, - ) - .unwrap(); + ); cmd.arg("script") .arg(script) @@ -422,12 +450,44 @@ Script ran successfully. "#]]); }); +// Tests that the run command can run functions with arguments without specifying the signature +// +forgetest!(can_execute_script_command_with_args_no_sig, |prj, cmd| { + let script = prj.add_source( + "Foo", + r#" +contract Demo { + event log_string(string); + event log_uint(uint); + function run(uint256 a, uint256 b) external { + emit log_string("script ran"); + emit log_uint(a); + emit log_uint(b); + } +} + "#, + ); + + cmd.arg("script").arg(script).arg("1").arg("2").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + script ran + 1 + 2 + +"#]]); +}); + // Tests that the run command can run functions with return values forgetest!(can_execute_script_command_with_returned, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); function run() external returns (uint256 result, uint8) { @@ -435,8 +495,7 @@ contract Demo { return (255, 3); } }"#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -458,10 +517,9 @@ result: uint256 255 forgetest_async!(can_broadcast_script_skipping_simulation, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); // This example script would fail in on-chain simulation - let deploy_script = prj - .add_source( - "DeployScript", - r#" + let deploy_script = prj.add_source( + "DeployScript", + r#" import "forge-std/Script.sol"; contract HashChecker { @@ -486,8 +544,7 @@ contract DeployScript is Script { hashChecker = new HashChecker(); } }"#, - ) - .unwrap(); + ); let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; @@ -571,7 +628,7 @@ contract RunScript is Script { }"# .replace("CONTRACT_ADDRESS", contract_address); - let run_script = prj.add_source("RunScript", &run_code).unwrap(); + let run_script = prj.add_source("RunScript", &run_code); let run_contract = run_script.display().to_string() + ":RunScript"; cmd.forge_fuse() @@ -902,7 +959,7 @@ forgetest_async!(can_deploy_with_custom_create2_notmatched_bytecode, |prj, cmd| .broadcast(ScriptOutcome::ScriptFailed); }); -forgetest_async!(canot_deploy_with_nonexist_create2, |prj, cmd| { +forgetest_async!(cannot_deploy_with_nonexist_create2, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); let create2 = address!("0x0000000000000000000000000000000000b4956c"); @@ -1034,9 +1091,8 @@ forgetest_async!(check_broadcast_log, |prj, cmd| { let run_log = re.replace_all(&run_log, ""); // Clean up carriage return OS differences - let re = Regex::new(r"\r\n").unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, "\n"); - let run_log = re.replace_all(&run_log, "\n"); + let fixtures_log = fixtures_log.replace("\r\n", "\n"); + let run_log = run_log.replace("\r\n", "\n"); similar_asserts::assert_eq!(fixtures_log, run_log); }); @@ -1079,7 +1135,7 @@ forgetest_async!(can_execute_script_with_arguments, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -1131,8 +1187,7 @@ contract Script0 is Script { } } "#, - ) - .unwrap(); + ); cmd .forge_fuse() @@ -1208,7 +1263,7 @@ forgetest_async!(can_execute_script_with_arguments_nested_deploy, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -1220,10 +1275,9 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); let (_api, handle) = spawn(NodeConfig::test()).await; - let script = prj - .add_script( - "Counter.s.sol", - r#" + let script = prj.add_script( + "Counter.s.sol", + r#" import "forge-std/Script.sol"; contract A { @@ -1259,8 +1313,7 @@ contract Script0 is Script { } } "#, - ) - .unwrap(); + ); cmd .forge_fuse() @@ -1330,10 +1383,9 @@ SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet // checks that skipping build forgetest_init!(can_execute_script_and_skip_contracts, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" contract Demo { event log_string(string); function run() external returns (uint256 result, uint8) { @@ -1341,8 +1393,7 @@ contract Demo { return (255, 3); } }"#, - ) - .unwrap(); + ); cmd.arg("script") .arg(script) .args(["--skip", "tests", "--skip", TEMPLATE_CONTRACT]) @@ -1376,13 +1427,13 @@ forgetest_async!(does_script_override_correctly, |prj, cmd| { tester.add_sig("CheckOverrides", "run()").simulate(ScriptOutcome::OkNoEndpoint); }); -forgetest_async!(assert_tx_origin_is_not_overritten, |prj, cmd| { +forgetest_async!(assert_tx_origin_is_not_overwritten, |prj, cmd| { cmd.args(["init", "--force"]) .arg(prj.root()) .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -1393,10 +1444,9 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); - let script = prj - .add_script( - "ScriptTxOrigin.s.sol", - r#" + let script = prj.add_script( + "ScriptTxOrigin.s.sol", + r#" import { Script } from "forge-std/Script.sol"; contract ScriptTxOrigin is Script { @@ -1444,8 +1494,7 @@ contract ContractC { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse() .arg("script") @@ -1470,7 +1519,7 @@ forgetest_async!(assert_can_create_multiple_contracts_with_correct_nonce, |prj, .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -1481,10 +1530,9 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); - let script = prj - .add_script( - "ScriptTxOrigin.s.sol", - r#" + let script = prj.add_script( + "ScriptTxOrigin.s.sol", + r#" import {Script, console} from "forge-std/Script.sol"; contract Contract { @@ -1517,8 +1565,7 @@ contract NestedCreate is Script { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse() .arg("script") @@ -1543,18 +1590,16 @@ If you wish to simulate on-chain transactions pass a RPC URL. }); forgetest_async!(assert_can_detect_target_contract_with_interfaces, |prj, cmd| { - let script = prj - .add_script( - "ScriptWithInterface.s.sol", - r#" + let script = prj.add_script( + "ScriptWithInterface.s.sol", + r#" contract Script { function run() external {} } interface Interface {} "#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1567,10 +1612,9 @@ Script ran successfully. }); forgetest_async!(assert_can_detect_unlinked_target_with_libraries, |prj, cmd| { - let script = prj - .add_script( - "ScriptWithExtLib.s.sol", - r#" + let script = prj.add_script( + "ScriptWithExtLib.s.sol", + r#" library Lib { function f() public {} } @@ -1581,8 +1625,7 @@ contract Script { } } "#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1622,8 +1665,7 @@ import "./B.sol"; contract ScriptA {{}} "# ), - ) - .unwrap(); + ); prj.add_script( "B.sol", @@ -1640,8 +1682,7 @@ contract ScriptB is Script {{ }} "# ), - ) - .unwrap(); + ); prj.add_script( "C.sol", @@ -1653,8 +1694,7 @@ import "./B.sol"; contract ScriptC {{}} "# ), - ) - .unwrap(); + ); let mut tester = ScriptTester::new(cmd, None, prj.root(), "script/B.sol"); tester.cmd.forge_fuse().args(["script", "script/B.sol"]); @@ -1684,18 +1724,16 @@ forgetest_async!(can_sign_with_script_wallet_multiple, |prj, cmd| { }); forgetest_async!(fails_with_function_name_and_overloads, |prj, cmd| { - let script = prj - .add_script( - "Script.s.sol", - r#" + let script = prj.add_script( + "Script.s.sol", + r#" contract Script { function run() external {} function run(address,uint256) external {} } "#, - ) - .unwrap(); + ); cmd.arg("script").args([&script.to_string_lossy(), "--sig", "run"]); cmd.assert_failure().stderr_eq(str![[r#" @@ -1710,7 +1748,7 @@ forgetest_async!(can_decode_custom_errors, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" Initializing [..]... -Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) +Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) Installed forge-std[..] Initialized forge project @@ -1721,10 +1759,9 @@ Warning: Target directory is not empty, but `--force` was specified "#]]); - let script = prj - .add_script( - "CustomErrorScript.s.sol", - r#" + let script = prj.add_script( + "CustomErrorScript.s.sol", + r#" import { Script } from "forge-std/Script.sol"; contract ContractWithCustomError { @@ -1743,8 +1780,7 @@ contract CustomErrorScript is Script { } } "#, - ) - .unwrap(); + ); cmd.forge_fuse().arg("script").arg(script).args(["--tc", "CustomErrorScript"]); cmd.assert_failure().stderr_eq(str![[r#" @@ -1768,8 +1804,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); let node_config = NodeConfig::test().with_base_fee(Some(0)); let (_api, handle) = spawn(node_config).await; @@ -1899,8 +1934,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); let (_api, handle) = spawn(NodeConfig::test()).await; @@ -1934,8 +1968,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); let (_api, handle) = spawn(NodeConfig::test()).await; @@ -1953,7 +1986,7 @@ contract SimpleScript is Script { ]) .assert_success() .stdout_eq(str![[r#" -{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} {"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} @@ -1976,8 +2009,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); let (_api, handle) = spawn(NodeConfig::test()).await; @@ -2051,8 +2083,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2094,8 +2125,7 @@ contract SimpleScript is Script { } "# .replace("", &url), - ) - .unwrap(); + ); cmd.args([ "script", @@ -2199,8 +2229,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2245,32 +2274,31 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. "#]]); - assert!(!api - .get_code(address!("0x4e59b44847b379578588920cA78FbF26c0B4956C"), Default::default()) - .await - .unwrap() - .is_empty()); + assert!( + !api.get_code(address!("0x4e59b44847b379578588920cA78FbF26c0B4956C"), Default::default()) + .await + .unwrap() + .is_empty() + ); }); forgetest_init!(can_get_script_wallets, |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" + let script = prj.add_source( + "Foo", + r#" import "forge-std/Script.sol"; interface Vm { - function getWallets() external returns (address[] memory wallets); + function getWallets() external view returns (address[] memory wallets); } contract WalletScript is Script { - function run() public { + function run() public view { address[] memory wallets = Vm(address(vm)).getWallets(); console.log(wallets[0]); } }"#, - ) - .unwrap(); + ); cmd.arg("script") .arg(script) .args([ @@ -2292,7 +2320,7 @@ Script ran successfully. "#]]); }); -forgetest_init!(can_remeber_keys, |prj, cmd| { +forgetest_init!(can_remember_keys, |prj, cmd| { let script = prj .add_source( "Foo", @@ -2313,8 +2341,7 @@ contract WalletScript is Script { } } }"#, - ) - .unwrap(); + ); cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -2356,8 +2383,7 @@ contract SimpleScript is Script { } } "#, - ) - .unwrap(); + ); cmd.arg("script").args(["SimpleScript", "--fork-url", &handle.http_endpoint(), "-vvvv"]); cmd.assert_success().stdout_eq(str![[r#" @@ -2419,8 +2445,7 @@ contract ContractScript is Script { } } "#, - ) - .unwrap(); + ); cmd.arg("script") .args([ "ContractScript", @@ -2435,7 +2460,8 @@ contract ContractScript is Script { .find(|file| file.ends_with("run-latest.json")) .expect("No broadcast artifacts"); - let sequence: ScriptSequence = foundry_common::fs::read_json_file(&run_latest).unwrap(); + let sequence: ScriptSequence = + foundry_common::fs::read_json_file(&run_latest).unwrap(); assert_eq!(sequence.transactions.len(), 2); assert_eq!(sequence.transactions[1].additional_contracts.len(), 1); @@ -2455,8 +2481,7 @@ forgetest_async!(should_set_correct_sender_nonce_via_cli, |prj, cmd| { } } "#, - ) - .unwrap(); + ); let rpc_url = next_http_archive_rpc_url(); @@ -2509,8 +2534,7 @@ contract DryRunTest is Script { } } "#, - ) - .unwrap(); + ); cmd.arg("script") .args([ @@ -2576,7 +2600,7 @@ Chain 31337 accessList [] chainId 31337 -gasLimit 228231 +gasLimit [..] gasPrice input [..] maxFeePerBlobGas @@ -2584,7 +2608,7 @@ maxFeePerGas maxPriorityFeePerGas nonce 0 to -type 0 +type EIP-1559 value 0 ### Transaction 2 ### @@ -2599,7 +2623,7 @@ maxFeePerGas maxPriorityFeePerGas nonce 1 to 0x5FbDB2315678afecb367f032d93F642f64180aa3 -type 0 +type EIP-1559 value 0 contract: Called(0x5FbDB2315678afecb367f032d93F642f64180aa3) data (decoded): run(uint256,uint256)( @@ -2621,6 +2645,7 @@ SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet // Tests warn when artifact source file no longer exists. // forgetest_init!(should_warn_if_artifact_source_no_longer_exists, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["script", "script/Counter.s.sol"]).assert_success().stdout_eq(str![[r#" ... Script ran successfully. @@ -2660,14 +2685,10 @@ forgetest_init!(should_revert_on_address_opcode, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.arg("script").arg("ScriptWithAddress").assert_failure().stderr_eq(str![[r#" -... -Error: Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon. -Error: script failed: -... +Error: script failed: Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon. "#]]); @@ -2701,8 +2722,7 @@ forgetest_async!(warns_if_no_transactions_to_broadcast, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.args([ "script", @@ -2725,6 +2745,7 @@ Warning: No transactions to broadcast. // Tests EIP-7702 broadcast forgetest_async!(can_broadcast_txes_with_signed_auth, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_script( "EIP7702Script.s.sol", r#" @@ -2751,8 +2772,7 @@ contract EIP7702Script is Script { } } "#, - ) - .unwrap(); + ); let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (_api, handle) = spawn(node_config).await; @@ -2821,16 +2841,16 @@ Simulated On-chain Traces: [..] → new Counter@0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 └─ ← [Return] 481 bytes of code - [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() └─ ← [Stop] - [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() └─ ← [Stop] - [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) └─ ← [Stop] - [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) └─ ← [Stop] @@ -2858,3 +2878,715 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. "#]]); }); + +// Tests EIP-7702 with multiple auth +// Alice sends 5 ETH from Bob to Receiver1 and 1 ETH to Receiver2 +forgetest_async!(can_broadcast_txes_with_multiple_auth, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "BatchCallDelegation.sol", + r#" +contract BatchCallDelegation { + event CallExecuted(address indexed to, uint256 indexed value, bytes data, bool success); + + struct Call { + bytes data; + address to; + uint256 value; + } + + function execute(Call[] calldata calls) external payable { + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + (bool success,) = call.to.call{value: call.value}(call.data); + require(success, "call reverted"); + emit CallExecuted(call.to, call.value, call.data, success); + } + } +} + "#, + ); + + prj.add_script( + "BatchCallDelegationScript.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {BatchCallDelegation} from "../src/BatchCallDelegation.sol"; + +contract BatchCallDelegationScript is Script { + // Alice's address and private key (EOA with no initial contract code). + address payable ALICE_ADDRESS = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + uint256 constant ALICE_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + + // Bob's address and private key (Bob will execute transactions on Alice's behalf). + address constant BOB_ADDRESS = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + uint256 constant BOB_PK = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; + + address constant RECEIVER_1 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955; + address constant RECEIVER_2 = 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc; + + uint256 constant DEPLOYER_PK = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6; + + function run() public { + BatchCallDelegation.Call[] memory aliceCalls = new BatchCallDelegation.Call[](1); + aliceCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""}); + + BatchCallDelegation.Call[] memory bobCalls = new BatchCallDelegation.Call[](2); + bobCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""}); + bobCalls[1] = BatchCallDelegation.Call({to: RECEIVER_2, value: 1 ether, data: ""}); + + vm.startBroadcast(DEPLOYER_PK); + BatchCallDelegation batcher = new BatchCallDelegation(); + vm.stopBroadcast(); + + vm.startBroadcast(ALICE_PK); + vm.signAndAttachDelegation(address(batcher), ALICE_PK); + vm.signAndAttachDelegation(address(batcher), BOB_PK); + vm.signAndAttachDelegation(address(batcher), BOB_PK); + + BatchCallDelegation(BOB_ADDRESS).execute(bobCalls); + + vm.stopBroadcast(); + } +} + "#, + ); + + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + + cmd.args([ + "script", + "script/BatchCallDelegationScript.s.sol", + "--rpc-url", + &handle.http_endpoint(), + "--non-interactive", + "--slow", + "--broadcast", + "--evm-version", + "prague", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + // Alice nonce should be 2 (tx sender and one auth) + let alice_acc = api + .get_account(address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), None) + .await + .unwrap(); + assert_eq!(alice_acc.nonce, 2); + + // Bob nonce should be 2 (two auths) and balance reduced by 6 ETH. + let bob_acc = api + .get_account(address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), None) + .await + .unwrap(); + assert_eq!(bob_acc.nonce, 2); + assert_eq!(bob_acc.balance.to_string(), "94000000000000000000"); + + // Receiver balances should be updated with 5 ETH and 1 ETH. + let receiver1 = api + .get_account(address!("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"), None) + .await + .unwrap(); + assert_eq!(receiver1.nonce, 0); + assert_eq!(receiver1.balance.to_string(), "105000000000000000000"); + let receiver2 = api + .get_account(address!("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"), None) + .await + .unwrap(); + assert_eq!(receiver2.nonce, 0); + assert_eq!(receiver2.balance.to_string(), "101000000000000000000"); +}); + +// +forgetest_async!(check_broadcast_log_with_additional_contracts, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + "#, + ); + prj.add_source( + "Factory.sol", + r#" +import {Counter} from "./Counter.sol"; + +contract Factory { + function deployCounter() public returns (Counter) { + return new Counter(); + } +} + "#, + ); + let deploy_script = prj.add_script( + "Factory.s.sol", + r#" +import "forge-std/Script.sol"; +import {Factory} from "../src/Factory.sol"; +import {Counter} from "../src/Counter.sol"; + +contract FactoryScript is Script { + Factory public factory; + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + factory = new Factory(); + counter = factory.deployCounter(); + + vm.stopBroadcast(); + } +} + "#, + ); + + let deploy_contract = deploy_script.display().to_string() + ":FactoryScript"; + let (_api, handle) = spawn(NodeConfig::test()).await; + cmd.args([ + "script", + &deploy_contract, + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "--slow", + "--broadcast", + "--private-key", + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_success(); + + let broadcast_log = prj.root().join("broadcast/Factory.s.sol/31337/run-latest.json"); + let script_sequence: ScriptSequence = serde_json::from_reader( + fs::File::open(prj.artifacts().join(broadcast_log)).expect("no broadcast log"), + ) + .expect("no script sequence"); + + let counter_contract = script_sequence + .transactions + .get(1) + .expect("no tx") + .additional_contracts + .first() + .expect("no Counter contract"); + assert_eq!(counter_contract.contract_name, Some("Counter".to_string())); +}); + +// +forgetest_async!(call_to_non_contract_address_does_not_panic, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + "#, + ); + + let deploy_script = prj.add_script( + "Counter.s.sol", + &r#" +import "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + function setUp() public {} + function run() public { + vm.createSelectFork(""); + vm.startBroadcast(); + counter = new Counter(); + vm.stopBroadcast(); + + vm.createSelectFork(""); + vm.startBroadcast(); + counter.increment(); + vm.stopBroadcast(); + } +} + "# + .replace("", &endpoint), + ); + + let (_api, handle) = spawn(NodeConfig::test()).await; + cmd.args([ + "script", + &deploy_script.display().to_string(), + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "--slow", + "--broadcast", + "--private-key", + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_failure() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] → new CounterScript@[..] + └─ ← [Return] 2162 bytes of code + + [..] CounterScript::setUp() + └─ ← [Stop] + + [..] CounterScript::run() + ├─ [..] VM::createSelectFork("") + │ └─ ← [Return] 1 + ├─ [..] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new Counter@[..] + │ └─ ← [Return] 481 bytes of code + ├─ [..] VM::stopBroadcast() + │ └─ ← [Return] + ├─ [..] VM::createSelectFork("") + │ └─ ← [Return] 2 + ├─ [..] VM::startBroadcast() + │ └─ ← [Return] + └─ ← [Revert] call to non-contract address [..] + + + +"#]]) + .stderr_eq(str![[r#" +Error: script failed: call to non-contract address [..] +"#]]); +}); + +// Test that --verify without --broadcast fails with a clear error message +forgetest!(verify_without_broadcast_fails, |prj, cmd| { + let script = prj.add_source( + "Counter", + r#" +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function run() external { + // Simple script that does nothing + } +} + "#, + ); + + cmd.args([ + "script", + script.to_str().unwrap(), + "--verify", + "--rpc-url", + "https://sepolia.infura.io/v3/test", + ]) + .assert_failure() + .stderr_eq(str![[r#" +error: the following required arguments were not provided: + --broadcast + +Usage: [..] script --broadcast --verify --rpc-url [ARGS]... + +For more information, try '--help'. + +"#]]); +}); + +// +forgetest_async!(can_broadcast_from_deploy_code_cheatcode, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); + prj.add_script( + "Counter.s.sol", + r#" +import "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Counter} from "../src/Counter.sol"; +contract CounterScript is Script { + function run() public { + vm.startBroadcast(); + address addr1 = vm.deployCode("src/Counter.sol:Counter"); + Counter(addr1).increment(); + vm.stopBroadcast(); + } +} + "#, + ); + + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + + cmd.args([ + "script", + "script/Counter.s.sol:CounterScript", + "--rpc-url", + &handle.http_endpoint(), + "-vvvv", + "--broadcast", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] CounterScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [0] VM::deployCode("src/Counter.sol:Counter") + │ ├─ [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3 + │ │ └─ ← [Return] 481 bytes of code + │ └─ ← [Return] Counter: [0x5FbDB2315678afecb367f032d93F642f64180aa3] + ├─ [..] Counter::increment() + │ └─ ← [Stop] + ├─ [0] VM::stopBroadcast() + │ └─ ← [Return] + └─ ← [Stop] + + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3 + └─ ← [Return] 481 bytes of code + + [..] Counter::increment() + └─ ← [Stop] + + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); + +forgetest_async!(flaky_can_deploy_with_broadcast_in_setup, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Deploy.s.sol", + r#" +import "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +contract DeployScript is Script { + function setUp() public { + vm.startBroadcast(); + } + + function run() public { + payable(address(0)).transfer(1 ether); + + vm.stopBroadcast(); + } +} + "#, + ); + + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + + cmd.args([ + "script", + "script/Deploy.s.sol:DeployScript", + "--rpc-url", + &handle.http_endpoint(), + "-vvvv", + "--broadcast", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [9882] DeployScript::run() + ├─ [0] 0x0000000000000000000000000000000000000000::fallback{value: 1000000000000000000}() + │ └─ ← [Stop] + ├─ [0] VM::stopBroadcast() + │ └─ ← [Return] + └─ ← [Stop] + + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [0] 0x0000000000000000000000000000000000000000::fallback{value: 1000000000000000000}() + └─ ← [Stop] + + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); + +// +forgetest_async!(can_execute_script_with_createx_and_via_ir, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.update_config(|config| { + config.optimizer = Some(true); + config.via_ir = true; + }); + prj.add_script("CreateXScript.s.sol", include_str!("../fixtures/CreateXScript.sol")); + + let (_api, handle) = spawn(NodeConfig::test().with_auto_impersonate(true)).await; + cmd.cast_fuse() + .args([ + "send", + "0xeD456e05CaAb11d66C4c797dD6c1D6f9A7F352b5", + "--value", + "1000000000000000000", + "--from", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--unlocked", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + cmd.cast_fuse() + .args(["publish", "0xf92f698085174876e800832dc6c08080b92f1660a06040523060805234801561001457600080fd5b50608051612e3e6100d860003960008181610603015281816107050152818161082b015281816108d50152818161127f01528181611375015281816113e00152818161141f015281816114a7015281816115b3015281816117d20152818161183d0152818161187c0152818161190401528181611ac501528181611c7801528181611ce301528181611d2201528181611daa01528181611fe901528181612206015281816122f20152818161244d015281816124a601526125820152612e3e6000f3fe60806040526004361061018a5760003560e01c806381503da1116100d6578063d323826a1161007f578063e96deee411610059578063e96deee414610395578063f5745aba146103a8578063f9664498146103bb57600080fd5b8063d323826a1461034f578063ddda0acb1461036f578063e437252a1461038257600080fd5b80639c36a286116100b05780639c36a28614610316578063a7db93f214610329578063c3fe107b1461033c57600080fd5b806381503da1146102d0578063890c283b146102e357806398e810771461030357600080fd5b80632f990e3f116101385780636cec2536116101125780636cec25361461027d57806374637a7a1461029d5780637f565360146102bd57600080fd5b80632f990e3f1461023757806331a7c8c81461024a57806342d654fc1461025d57600080fd5b806327fe18221161016957806327fe1822146101f15780632852527a1461020457806328ddd0461461021757600080fd5b8062d84acb1461018f57806326307668146101cb57806326a32fc7146101de575b600080fd5b6101a261019d366004612915565b6103ce565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6101a26101d9366004612994565b6103e6565b6101a26101ec3660046129db565b610452565b6101a26101ff3660046129db565b6104de565b6101a2610212366004612a39565b610539565b34801561022357600080fd5b506101a2610232366004612a90565b6106fe565b6101a2610245366004612aa9565b61072a565b6101a2610258366004612aa9565b6107bb565b34801561026957600080fd5b506101a2610278366004612b1e565b6107c9565b34801561028957600080fd5b506101a2610298366004612a90565b610823565b3480156102a957600080fd5b506101a26102b8366004612b4a565b61084f565b6101a26102cb3660046129db565b611162565b6101a26102de366004612b74565b6111e8565b3480156102ef57600080fd5b506101a26102fe366004612bac565b611276565b6101a2610311366004612bce565b6112a3565b6101a2610324366004612994565b611505565b6101a2610337366004612c49565b6116f1565b6101a261034a366004612aa9565b611964565b34801561035b57600080fd5b506101a261036a366004612cd9565b6119ed565b6101a261037d366004612c49565b611a17565b6101a2610390366004612bce565b611e0c565b6101a26103a3366004612915565b611e95565b6101a26103b6366004612bce565b611ea4565b6101a26103c9366004612b74565b611f2d565b60006103dd8585858533611a17565b95945050505050565b6000806103f2846120db565b90508083516020850134f59150610408826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a35092915050565b60006104d86104d260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b836103e6565b92915050565b600081516020830134f090506104f3816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a2919050565b600080610545856120db565b905060008460601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006028820152826037826000f593505073ffffffffffffffffffffffffffffffffffffffff8316610635576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660048201526024015b60405180910390fd5b604051829073ffffffffffffffffffffffffffffffffffffffff8516907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808473ffffffffffffffffffffffffffffffffffffffff1634876040516106a19190612d29565b60006040518083038185875af1925050503d80600081146106de576040519150601f19603f3d011682016040523d82523d6000602084013e6106e3565b606091505b50915091506106f382828961247d565b505050509392505050565b60006104d87f00000000000000000000000000000000000000000000000000000000000000008361084f565b60006107b36107aa60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b85858533611a17565b949350505050565b60006107b3848484336112a3565b60006040518260005260ff600b53836020527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f6040526055600b20601452806040525061d694600052600160345350506017601e20919050565b60006104d8827f00000000000000000000000000000000000000000000000000000000000000006107c9565b600060607f9400000000000000000000000000000000000000000000000000000000000000610887600167ffffffffffffffff612d45565b67ffffffffffffffff16841115610902576040517f3c55ab3b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b836000036109c7576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f800000000000000000000000000000000000000000000000000000000000000060368201526037015b6040516020818303038152906040529150611152565b607f8411610a60576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b16602283015260f886901b1660368201526037016109b1565b60ff8411610b1f576040517fd70000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b1660228301527f8100000000000000000000000000000000000000000000000000000000000000603683015260f886901b1660378201526038016109b1565b61ffff8411610bff576040517fd80000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f820000000000000000000000000000000000000000000000000000000000000060368201527fffff00000000000000000000000000000000000000000000000000000000000060f086901b1660378201526039016109b1565b62ffffff8411610ce0576040517fd90000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f830000000000000000000000000000000000000000000000000000000000000060368201527fffffff000000000000000000000000000000000000000000000000000000000060e886901b166037820152603a016109b1565b63ffffffff8411610dc2576040517fda0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f840000000000000000000000000000000000000000000000000000000000000060368201527fffffffff0000000000000000000000000000000000000000000000000000000060e086901b166037820152603b016109b1565b64ffffffffff8411610ea5576040517fdb0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f850000000000000000000000000000000000000000000000000000000000000060368201527fffffffffff00000000000000000000000000000000000000000000000000000060d886901b166037820152603c016109b1565b65ffffffffffff8411610f89576040517fdc0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f860000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffff000000000000000000000000000000000000000000000000000060d086901b166037820152603d016109b1565b66ffffffffffffff841161106e576040517fdd0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f870000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffff0000000000000000000000000000000000000000000000000060c886901b166037820152603e016109b1565b6040517fde0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f880000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffffff00000000000000000000000000000000000000000000000060c086901b166037820152603f0160405160208183030381529060405291505b5080516020909101209392505050565b60006104d86111e260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b83611505565b600061126f61126860408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b8484610539565b9392505050565b600061126f83837f00000000000000000000000000000000000000000000000000000000000000006119ed565b60008451602086018451f090506112b9816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808273ffffffffffffffffffffffffffffffffffffffff168560200151876040516113279190612d29565b60006040518083038185875af1925050503d8060008114611364576040519150601f19603f3d011682016040523d82523d6000602084013e611369565b606091505b5091509150816113c9577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001631156114fb578373ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611495576040519150601f19603f3d011682016040523d82523d6000602084013e61149a565b606091505b509092509050816114fb577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b5050949350505050565b600080611511846120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff81166115e0576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a361162c83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1634876040516116569190612d29565b60006040518083038185875af1925050503d8060008114611693576040519150601f19603f3d011682016040523d82523d6000602084013e611698565b606091505b505090506116a681866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a25050505092915050565b6000806116fd876120db565b9050808651602088018651f59150611714826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808373ffffffffffffffffffffffffffffffffffffffff168660200151886040516117849190612d29565b60006040518083038185875af1925050503d80600081146117c1576040519150601f19603f3d011682016040523d82523d6000602084013e6117c6565b606091505b509150915081611826577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163115611958578473ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d80600081146118f2576040519150601f19603f3d011682016040523d82523d6000602084013e6118f7565b606091505b50909250905081611958577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b50505095945050505050565b60006107b36119e460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b858585336116f1565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b600080611a23876120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff8116611af2576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a3611b3e83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1687600001518a604051611b6c9190612d29565b60006040518083038185875af1925050503d8060008114611ba9576040519150601f19603f3d011682016040523d82523d6000602084013e611bae565b606091505b50509050611bbc81866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a260608573ffffffffffffffffffffffffffffffffffffffff1688602001518a604051611c299190612d29565b60006040518083038185875af1925050503d8060008114611c66576040519150601f19603f3d011682016040523d82523d6000602084013e611c6b565b606091505b50909250905081611ccc577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163115611dfe578673ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611d98576040519150601f19603f3d011682016040523d82523d6000602084013e611d9d565b606091505b50909250905081611dfe577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050505095945050505050565b60006103dd611e8c60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b868686866116f1565b60006103dd85858585336116f1565b60006103dd611f2460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b86868686611a17565b6000808360601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf3000000000000000000000000000000000060288201526037816000f092505073ffffffffffffffffffffffffffffffffffffffff8216612016576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b60405173ffffffffffffffffffffffffffffffffffffffff8316907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808373ffffffffffffffffffffffffffffffffffffffff1634866040516120809190612d29565b60006040518083038185875af1925050503d80600081146120bd576040519150601f19603f3d011682016040523d82523d6000602084013e6120c2565b606091505b50915091506120d282828861247d565b50505092915050565b60008060006120e9846125b3565b9092509050600082600281111561210257612102612e02565b1480156121205750600081600281111561211e5761211e612e02565b145b1561215e57604080513360208201524691810191909152606081018590526080016040516020818303038152906040528051906020012092506123cc565b600082600281111561217257612172612e02565b1480156121905750600181600281111561218e5761218e612e02565b145b156121b0576121a9338560009182526020526040902090565b92506123cc565b60008260028111156121c4576121c4612e02565b03612233576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b600182600281111561224757612247612e02565b1480156122655750600081600281111561226357612263612e02565b145b1561227e576121a9468560009182526020526040902090565b600182600281111561229257612292612e02565b1480156122b0575060028160028111156122ae576122ae612e02565b145b1561231f576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b61239a60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b84036123a657836123c9565b604080516020810186905201604051602081830303815290604052805190602001205b92505b5050919050565b73ffffffffffffffffffffffffffffffffffffffff8116158061240b575073ffffffffffffffffffffffffffffffffffffffff81163b155b1561247a576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b50565b82158061249f575073ffffffffffffffffffffffffffffffffffffffff81163b155b156124fa577f0000000000000000000000000000000000000000000000000000000000000000826040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050565b811580612520575073ffffffffffffffffffffffffffffffffffffffff8116155b80612540575073ffffffffffffffffffffffffffffffffffffffff81163b155b156125af576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b5050565b600080606083901c3314801561261057508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b1561262057506000905080915091565b606083901c3314801561265a57507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561266b5750600090506001915091565b33606084901c036126825750600090506002915091565b606083901c1580156126db57508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b156126ec5750600190506000915091565b606083901c15801561272557507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561273557506001905080915091565b606083901c61274a5750600190506002915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000036127a55750600290506000915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166000036127e15750600290506001915091565b506002905080915091565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261282c57600080fd5b813567ffffffffffffffff80821115612847576128476127ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561288d5761288d6127ec565b816040528381528660208588010111156128a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000604082840312156128d857600080fd5b6040516040810181811067ffffffffffffffff821117156128fb576128fb6127ec565b604052823581526020928301359281019290925250919050565b60008060008060a0858703121561292b57600080fd5b84359350602085013567ffffffffffffffff8082111561294a57600080fd5b6129568883890161281b565b9450604087013591508082111561296c57600080fd5b506129798782880161281b565b92505061298986606087016128c6565b905092959194509250565b600080604083850312156129a757600080fd5b82359150602083013567ffffffffffffffff8111156129c557600080fd5b6129d18582860161281b565b9150509250929050565b6000602082840312156129ed57600080fd5b813567ffffffffffffffff811115612a0457600080fd5b6107b38482850161281b565b803573ffffffffffffffffffffffffffffffffffffffff81168114612a3457600080fd5b919050565b600080600060608486031215612a4e57600080fd5b83359250612a5e60208501612a10565b9150604084013567ffffffffffffffff811115612a7a57600080fd5b612a868682870161281b565b9150509250925092565b600060208284031215612aa257600080fd5b5035919050565b600080600060808486031215612abe57600080fd5b833567ffffffffffffffff80821115612ad657600080fd5b612ae28783880161281b565b94506020860135915080821115612af857600080fd5b50612b058682870161281b565b925050612b1585604086016128c6565b90509250925092565b60008060408385031215612b3157600080fd5b82359150612b4160208401612a10565b90509250929050565b60008060408385031215612b5d57600080fd5b612b6683612a10565b946020939093013593505050565b60008060408385031215612b8757600080fd5b612b9083612a10565b9150602083013567ffffffffffffffff8111156129c557600080fd5b60008060408385031215612bbf57600080fd5b50508035926020909101359150565b60008060008060a08587031215612be457600080fd5b843567ffffffffffffffff80821115612bfc57600080fd5b612c088883890161281b565b95506020870135915080821115612c1e57600080fd5b50612c2b8782880161281b565b935050612c3b86604087016128c6565b915061298960808601612a10565b600080600080600060c08688031215612c6157600080fd5b85359450602086013567ffffffffffffffff80821115612c8057600080fd5b612c8c89838a0161281b565b95506040880135915080821115612ca257600080fd5b50612caf8882890161281b565b935050612cbf87606088016128c6565b9150612ccd60a08701612a10565b90509295509295909350565b600080600060608486031215612cee57600080fd5b8335925060208401359150612b1560408501612a10565b60005b83811015612d20578181015183820152602001612d08565b50506000910152565b60008251612d3b818460208701612d05565b9190910192915050565b67ffffffffffffffff828116828216039080821115612d8d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260008251806040840152612dcf816060850160208701612d05565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016060019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c6343000817000a1ca005f70bf8a1493291468f36ef23b05eb3a4f1807f6b4022942a4104b7537bfc36a029528c0c29546c81e7d78b0277ef87031541bdc96427b246ecedb6d74cd3ed62", "--rpc-url", &handle.http_endpoint()]) + .assert_success(); + cmd.forge_fuse() + .args([ + "script", + "script/CreateXScript.s.sol:CreateXScript", + "--rpc-url", + &handle.http_endpoint(), + "--slow", + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--broadcast", + ]) + .assert_success(); +}); + +forgetest_async!(script_can_run_with_live_logs_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; + +contract Foo is Script { + function setUp() pure public { + console.log("Setup"); + } + + function run() pure public { + console.log("Run %d", uint256(1)); + } +} + "#, + ); + + cmd.forge_fuse() + .args(["script", "script/Foo.s.sol", "--live-logs"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Run 1 +Script ran successfully. +[GAS] + +"#]]); +}); + +forgetest_async!(script_can_run_with_live_logs_config, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.update_config(|config| { + config.live_logs = true; + }); + + prj.add_script( + "Foo.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; + +contract Foo is Script { + function setUp() pure public { + console.log("Setup"); + } + + function run() pure public { + console.log("Run %d", uint256(1)); + } +} + "#, + ); + + cmd.forge_fuse().args(["script", "script/Foo.s.sol"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Run 1 +Script ran successfully. +[GAS] + +"#]]); +}); + +// Regression test for https://github.com/foundry-rs/foundry/issues/13576 +// On Arbitrum, `block.number` is remapped to the L1 block number. Previously, +// fork block pinning used the remapped L1 block number, causing the fork to +// fetch state from an ancient block where contracts did not exist. +forgetest_init!( + #[ignore] + flaky_can_call_arbitrum_contract_in_script, + |prj, cmd| { + let script = prj.add_source( + "ArbScript", + r#" +import "forge-std/Script.sol"; + +interface IERC20 { + function name() external view returns (string memory); +} + +contract ArbScript is Script { + function run() external view { + // USDC on Arbitrum — a contract with zero ETH balance. + // Before the fix, the fork pinned to the L1 block number, fetching state from + // an ancient block (Sept 2022) where USDC did not exist, causing + // "call to non-contract address". + IERC20 usdc = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831); + string memory n = usdc.name(); + require(bytes(n).length > 0, "name should not be empty"); + } +} + "#, + ); + + let rpc = foundry_test_utils::rpc::next_rpc_endpoint(alloy_chains::NamedChain::Arbitrum); + + cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvv"]).assert_success(); + } +); + +// Tests that `forge script` works in Tempo mode without CreateCollision. +// Tempo genesis pre-deploys the Arachnid CREATE2 factory at the same address as the default +// CREATE2 deployer, so `deploy_create2_deployer` must be skipped to avoid a collision. +forgetest!(can_execute_script_command_with_tempo, |prj, cmd| { + prj.wipe(); + + // Initialize a Tempo project (installs forge-std, tempo-std, generates Mail template). + cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success(); + + // Run the generated Mail.s.sol script with a salt argument. + cmd.forge_fuse() + .arg("script") + .arg("script/Mail.s.sol") + .arg("temposalt") + .arg("--tempo") + .arg("--root") + .arg(prj.root()) + .assert_success(); +}); diff --git a/crates/forge/tests/cli/soldeer.rs b/crates/forge/tests/cli/soldeer.rs index 7245f7d696f1f..6176c1614578f 100644 --- a/crates/forge/tests/cli/soldeer.rs +++ b/crates/forge/tests/cli/soldeer.rs @@ -3,6 +3,7 @@ use std::{fs, path::Path}; use foundry_test_utils::forgesoldeer; + forgesoldeer!(install_dependency, |prj, cmd| { let command = "install"; let dependency = "forge-std~1.8.1"; @@ -355,6 +356,60 @@ forgesoldeer!(login, |prj, cmd| { let _ = cmd.arg("soldeer").arg(command).assert_failure(); }); +forgesoldeer!(clean, |prj, cmd| { + let dependency = "forge-std~1.8.1"; + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] + +[dependencies] +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_contents).unwrap(); + + cmd.args(["soldeer", "install", dependency]).assert_success(); + cmd.forge_fuse(); // reset command + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + let command = "clean"; + cmd.arg("soldeer").args([command]).assert_success(); + // Dependencies should have been removed from disk + assert!(!prj.root().join("dependencies").exists()); +}); + +forgesoldeer!(detect_project_root, |prj, cmd| { + let command = "update"; + + let foundry_updates = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = "1.8.1" +"#; + let foundry_file = prj.root().join("foundry.toml"); + + fs::write(&foundry_file, foundry_updates).unwrap(); + + // run command from sub-directory + cmd.set_current_dir(prj.root().join("src")); + cmd.arg("soldeer").arg(command).assert_success(); + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); +}); + fn read_file_to_string(path: &Path) -> String { let contents: String = fs::read_to_string(path).unwrap_or_default(); contents diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index 5fe82f5670cc2..b80805cbecfc4 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 30); +const LATEST_SOLC: Version = Version::new(0, 8, 34); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( @@ -43,8 +43,11 @@ ensure_svm_releases!( // Ensures we can always test with the latest solc build forgetest_init!(can_test_with_latest_solc, |prj, cmd| { - let src = format!( - r#" + prj.initialize_default_contracts(); + prj.add_test( + "Counter.2.t.sol", + &format!( + r#" pragma solidity ={LATEST_SOLC}; import "forge-std/Test.sol"; @@ -55,8 +58,8 @@ contract CounterTest is Test {{ }} }} "# + ), ); - prj.add_test("Counter", &src).unwrap(); // we need to remove the pinned solc version for this prj.update_config(|c| { @@ -65,7 +68,7 @@ contract CounterTest is Test {{ cmd.arg("test").assert_success().stdout_eq(str![[r#" ... -Ran 1 test for test/Counter.sol:CounterTest +Ran 1 test for test/Counter.2.t.sol:CounterTest [PASS] testAssert() ([GAS]) Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] ... diff --git a/crates/forge/tests/cli/test_cmd/core.rs b/crates/forge/tests/cli/test_cmd/core.rs new file mode 100644 index 0000000000000..29dabe80a9dda --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/core.rs @@ -0,0 +1,139 @@ +//! Core test functionality tests + +use foundry_test_utils::str; + +forgetest_init!(failing_test_after_failed_setup, |prj, cmd| { + prj.add_test( + "FailingTestAfterFailedSetup.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FailingTestAfterFailedSetupTest is Test { + function setUp() public { + assertTrue(false); + } + + function testAssertSuccess() public { + assertTrue(true); + } + + function testAssertFailure() public { + assertTrue(false); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest +[FAIL: assertion failed] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest +[FAIL: assertion failed] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(legacy_assertions, |prj, cmd| { + prj.add_test( + "LegacyAssertions.t.sol", + r#" +import "forge-std/Test.sol"; + +contract NoAssertionsRevertTest is Test { + function testMultipleAssertFailures() public { + vm.assertEq(uint256(1), uint256(2)); + vm.assertLt(uint256(5), uint256(4)); + } +} + +/// forge-config: default.legacy_assertions = true +contract LegacyAssertionsTest { + bool public failed; + + function testFlagNotSetSuccess() public {} + + function testFlagSetFailure() public { + failed = true; + } +} +"#, + ); + + cmd.args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/LegacyAssertions.t.sol:LegacyAssertionsTest +[PASS] testFlagNotSetSuccess() ([GAS]) +[FAIL] testFlagSetFailure() ([GAS]) +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/LegacyAssertions.t.sol:NoAssertionsRevertTest +[FAIL: assertion failed: 1 != 2] testMultipleAssertFailures() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 1 failing test in test/LegacyAssertions.t.sol:LegacyAssertionsTest +[FAIL] testFlagSetFailure() ([GAS]) + +Encountered 1 failing test in test/LegacyAssertions.t.sol:NoAssertionsRevertTest +[FAIL: assertion failed: 1 != 2] testMultipleAssertFailures() ([GAS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +forgetest_init!(payment_failure, |prj, cmd| { + prj.add_test( + "PaymentFailure.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Payable { + function pay() public payable {} +} + +contract PaymentFailureTest is Test { + function testCantPay() public { + Payable target = new Payable(); + vm.prank(address(1)); + target.pay{value: 1}(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/PaymentFailure.t.sol:PaymentFailureTest +[FAIL: EvmError: Revert] testCantPay() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/PaymentFailure.t.sol:PaymentFailureTest +[FAIL: EvmError: Revert] testCantPay() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs new file mode 100644 index 0000000000000..454b014a6e1bc --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -0,0 +1,1015 @@ +use alloy_primitives::U256; +use foundry_evm::fuzz::BaseCounterExample; +use foundry_test_utils::{TestCommand, forgetest_init, str}; +use regex::Regex; + +forgetest_init!(test_can_scrape_bytecode, |prj, cmd| { + prj.update_config(|config| config.optimizer = Some(true)); + prj.add_source( + "FuzzerDict.sol", + r#" +// https://github.com/foundry-rs/foundry/issues/1168 +contract FuzzerDict { + // Immutables should get added to the dictionary. + address public immutable immutableOwner; + // Regular storage variables should also get added to the dictionary. + address public storageOwner; + + constructor(address _immutableOwner, address _storageOwner) { + immutableOwner = _immutableOwner; + storageOwner = _storageOwner; + } +} + "#, + ); + + prj.add_test( + "FuzzerDictTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/FuzzerDict.sol"; + +contract FuzzerDictTest is Test { + FuzzerDict fuzzerDict; + + function setUp() public { + fuzzerDict = new FuzzerDict(address(100), address(200)); + } + + /// forge-config: default.fuzz.runs = 2000 + function testImmutableOwner(address who) public { + assertTrue(who != fuzzerDict.immutableOwner()); + } + + /// forge-config: default.fuzz.runs = 2000 + function testStorageOwner(address who) public { + assertTrue(who != fuzzerDict.storageOwner()); + } +} + "#, + ); + + // Test that immutable address is used as fuzzed input, causing test to fail. + cmd.args(["test", "--fuzz-seed", "119", "--mt", "testImmutableOwner"]).assert_failure(); + // Test that storage address is used as fuzzed input, causing test to fail. + cmd.forge_fuse() + .args(["test", "--fuzz-seed", "119", "--mt", "testStorageOwner"]) + .assert_failure(); +}); + +// tests that inline max-test-rejects config is properly applied +forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InlineMaxRejectsTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 1 + function test_fuzz_bound(uint256 a) public { + vm.assume(false); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] test_fuzz_bound(uint256) (runs: 0, [AVG_GAS]) +... +"#]]); +}); + +// Tests that test timeout config is properly applied. +// If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. +forgetest_init!(test_fuzz_timeout, |prj, cmd| { + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzTimeoutTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 0 + /// forge-config: default.fuzz.timeout = 1 + function test_fuzz_bound(uint256 a) public pure { + vm.assume(a == 0); + } +} + "#, + ); + + cmd.args(["test", "-j2"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:FuzzTimeoutTest +[PASS] test_fuzz_bound(uint256) (runs: [..], [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(test_fuzz_fail_on_revert, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.fail_on_revert = false; + config.fuzz.seed = Some(U256::from(100u32)); + }); + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + require(number > 10000000000, "low number"); + number = newNumber; + } +} + "#, + ); + + prj.add_test( + "CounterTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testFuzz_SetNumberRequire(uint256 x) public { + counter.setNumber(x); + require(counter.number() == 1); + } + + function testFuzz_SetNumberAssert(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), 1); + } +} + "#, + ); + + // Tests should not fail as revert happens in Counter contract. + cmd.args(["test", "--mc", "CounterTest"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/CounterTest.t.sol:CounterTest +[PASS] testFuzz_SetNumberAssert(uint256) (runs: 256, [AVG_GAS]) +[PASS] testFuzz_SetNumberRequire(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + + // Tested contract does not revert. + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + "#, + ); + + // Tests should fail as revert happens in cheatcode (assert) and test (require) contract. + cmd.args(["-j1"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/CounterTest.t.sol:CounterTest +[FAIL: assertion failed: [..]] testFuzz_SetNumberAssert(uint256) (runs: 0, [AVG_GAS]) +[FAIL: EvmError: Revert; [..]] testFuzz_SetNumberRequire(uint256) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] +... + +"#]]); +}); + +// Test 256 runs regardless number of test rejects. +// +forgetest_init!(test_fuzz_runs_with_rejects, |prj, cmd| { + prj.add_test( + "FuzzWithRejectsTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzWithRejectsTest is Test { + function testFuzzWithRejects(uint256 x) public pure { + vm.assume(x < 1_000_000); + } +} + "#, + ); + + // Tests should not fail as revert happens in Counter contract. + cmd.args(["test", "--mc", "FuzzWithRejectsTest"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/FuzzWithRejectsTest.t.sol:FuzzWithRejectsTest +[PASS] testFuzzWithRejects(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Test that counterexample is not replayed if test changes. +// +forgetest_init!(test_fuzz_replay_with_changed_test, |prj, cmd| { + prj.update_config(|config| config.fuzz.seed = Some(U256::from(100u32))); + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + require(x > 200); + } +} + "#, + ); + // Tests should fail and record counterexample with value 200. + cmd.args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d700000000000000000000000000000000000000000000000000000000000000c8 args=[200]] testFuzz_SetNumber(uint256) (runs: 6, [AVG_GAS]) +... + +"#]]); + + // Change test to assume counterexample 2 is discarded. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + vm.assume(x != 200); + } +} + "#, + ); + // Test should pass when replay failure with changed assume logic. + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Change test signature. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint8 x) public pure { + } +} + "#, + ); + // Test should pass when replay failure with changed function signature. + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint8) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Change test back to the original one that produced the counterexample. + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function testFuzz_SetNumber(uint256 x) public pure { + require(x > 200); + } +} + "#, + ); + // Test should fail with replayed counterexample 200 (0 runs). + cmd.forge_fuse().args(["test", "-j1"]).assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert; counterexample: calldata=0x5c7f60d700000000000000000000000000000000000000000000000000000000000000c8 args=[200]] testFuzz_SetNumber(uint256) (runs: 0, [AVG_GAS]) +... + +"#]]); +}); + +forgetest_init!(fuzz_basic, |prj, cmd| { + prj.add_test( + "Fuzz.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzTest is Test { + constructor() { + emit log("constructor"); + } + + function setUp() public { + emit log("setUp"); + } + + function testShouldFailFuzz(uint8 x) public { + emit log("testFailFuzz"); + require(x > 128, "should revert"); + } + + function testSuccessfulFuzz(uint128 a, uint128 b) public { + emit log("testSuccessfulFuzz"); + assertEq(uint256(a) + uint256(b), uint256(a) + uint256(b)); + } + + function testToStringFuzz(bytes32 data) public { + vm.toString(data); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Fuzz.t.sol:FuzzTest +[FAIL: should revert; counterexample: calldata=[..] args=[..]] testShouldFailFuzz(uint8) (runs: [..], [AVG_GAS]) +[PASS] testSuccessfulFuzz(uint128,uint128) (runs: 256, [AVG_GAS]) +[PASS] testToStringFuzz(bytes32) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 2 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 1 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 1 failing test in test/Fuzz.t.sol:FuzzTest +[FAIL: should revert; counterexample: calldata=[..] args=[..]] testShouldFailFuzz(uint8) (runs: [..], [AVG_GAS]) + +Encountered a total of 1 failing tests, 2 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// Test that showcases PUSH collection on normal fuzzing. +// Ignored until we collect them in a smarter way. +forgetest_init!( + #[ignore] + fuzz_collection, + |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 100; + config.invariant.runs = 1000; + config.fuzz.runs = 1000; + config.fuzz.seed = Some(U256::from(6u32)); + }); + prj.add_test( + "FuzzCollection.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SampleContract { + uint256 public counter; + uint256 public counterX2; + address public owner = address(0xBEEF); + bool public found_needle; + + event Incremented(uint256 counter); + + modifier onlyOwner() { + require(msg.sender == owner, "ONLY_OWNER"); + _; + } + + function compare(uint256 val) public { + if (val == 0x4446) { + found_needle = true; + } + } + + function incrementBy(uint256 numToIncrement) public onlyOwner { + counter += numToIncrement; + counterX2 += numToIncrement * 2; + + emit Incremented(counter); + } + + function breakTheInvariant(uint256 x) public { + if (x == 0x5556) { + counterX2 = 0; + } + } +} + +contract SampleContractTest is Test { + event Incremented(uint256 counter); + + SampleContract public sample; + + function setUp() public { + sample = new SampleContract(); + } + + function testIncrement(address caller) public { + vm.startPrank(address(caller)); + + vm.expectRevert("ONLY_OWNER"); + sample.incrementBy(1); + } + + function testNeedle(uint256 needle) public { + sample.compare(needle); + require(!sample.found_needle(), "needle found."); + } + + function invariantCounter() public { + require(sample.counter() * 2 == sample.counterX2(), "broken counter."); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#""#]]); + } +); + +forgetest_init!(fuzz_failure_persist, |prj, cmd| { + let persist_dir = prj.cache().parent().unwrap().join("persist"); + assert!(!persist_dir.exists()); + prj.update_config(|config| { + config.fuzz.failure_persist_dir = Some(persist_dir.clone()); + }); + + prj.add_test( + "FuzzFailurePersist.t.sol", + r#" +import "forge-std/Test.sol"; + +struct TestTuple { + address user; + uint256 amount; +} + +contract FuzzFailurePersistTest is Test { + function test_persist_fuzzed_failure( + uint256 x, + int256 y, + address addr, + bool cond, + string calldata test, + TestTuple calldata tuple, + address[] calldata addresses + ) public { + // dummy assume to trigger runs + vm.assume(x > 1 && x < 1111111111111111111111111111); + vm.assume(y > 1 && y < 1111111111111111111111111111); + require(false); + } +} + "#, + ); + + let mut calldata = None; + let expected = str![[r#" +... +Ran 1 test for test/FuzzFailurePersist.t.sol:FuzzFailurePersistTest +[FAIL: EvmError: Revert; counterexample: calldata=[..] args=[..]] test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[]) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + let mut check = |cmd: &mut TestCommand, same: bool| { + let assert = cmd.assert_failure(); + let output = assert.get_output(); + let stdout = String::from_utf8_lossy(&output.stdout); + let calldata = calldata.get_or_insert_with(|| { + let re = Regex::new(r"calldata=(0x[0-9a-fA-F]+)").unwrap(); + re.captures(&stdout).unwrap().get(1).unwrap().as_str().to_string() + }); + assert_eq!(stdout.contains(calldata.as_str()), same, "\n{stdout}"); + assert.stdout_eq(expected.clone()); + }; + + cmd.args(["test", "-j1"]); + + // Run several times, asserting that the failure persists and is the same. + for _ in 0..3 { + check(&mut cmd, true); + assert!(persist_dir.exists()); + } + + // Change dir and run again, asserting that the failure persists. It should be a new failure. + let new_persist_dir = prj.cache().parent().unwrap().join("persist2"); + assert!(!new_persist_dir.exists()); + prj.update_config(|config| { + config.fuzz.failure_persist_dir = Some(new_persist_dir.clone()); + }); + check(&mut cmd, false); + assert!(new_persist_dir.exists()); +}); + +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined +forgetest_init!(fuzz_int, |prj, cmd| { + prj.add_test( + "FuzzInt.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzNumbersTest is Test { + function testPositive(int256) public { + assertTrue(true); + } + + function testNegativeHalf(int256 val) public { + assertTrue(val < 2 ** 128 - 1); + } + + function testNegative0(int256 val) public { + assertTrue(val == 0); + } + + function testNegative1(int256 val) public { + assertTrue(val == -1); + } + + function testNegative2(int128 val) public { + assertTrue(val == 1); + } + + function testNegativeMax0(int256 val) public { + assertTrue(val == type(int256).max); + } + + function testNegativeMax1(int256 val) public { + assertTrue(val == type(int256).max - 2); + } + + function testNegativeMin0(int256 val) public { + assertTrue(val == type(int256).min); + } + + function testNegativeMin1(int256 val) public { + assertTrue(val == type(int256).min + 2); + } + + function testEquality(int256 x, int256 y) public { + int256 xy; + + unchecked { + xy = x * y; + } + + if ((x != 0 && xy / x != y)) { + return; + } + + assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 10 tests for test/FuzzInt.t.sol:FuzzNumbersTest +[FAIL: assertion failed[..]] testEquality(int256,int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative1(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2(int128) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeHalf(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax1(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMin0(int256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMin1(int256) (runs: [..], [AVG_GAS]) +[PASS] testPositive(int256) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 1 passed; 9 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 9 failed, 0 skipped (10 total tests) +... +"#]]); +}); + +forgetest_init!(fuzz_positive, |prj, cmd| { + prj.add_test( + "FuzzPositive.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzPositive is Test { + function testSuccessChecker(uint256 val) public { + assertTrue(true); + } + + function testSuccessChecker2(int256 val) public { + assert(val == val); + } + + function testSuccessChecker3(uint32 val) public { + assert(val + 0 == val); + } +} + "#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 tests for test/FuzzPositive.t.sol:FuzzPositive +[PASS] testSuccessChecker(uint256) (runs: 256, [AVG_GAS]) +[PASS] testSuccessChecker2(int256) (runs: 256, [AVG_GAS]) +[PASS] testSuccessChecker3(uint32) (runs: 256, [AVG_GAS]) +Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined +forgetest_init!(fuzz_uint, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + }); + prj.add_test( + "FuzzUint.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FuzzNumbersTest is Test { + function testPositive(uint256) public { + assertTrue(true); + } + + function testNegativeHalf(uint256 val) public { + assertTrue(val < 2 ** 128 - 1); + } + + function testNegative0(uint256 val) public { + assertTrue(val == 0); + } + + function testNegative2(uint256 val) public { + assertTrue(val == 2); + } + + function testNegative2Max(uint256 val) public { + assertTrue(val == type(uint256).max - 2); + } + + function testNegativeMax(uint256 val) public { + assertTrue(val == type(uint256).max); + } + + function testEquality(uint256 x, uint256 y) public { + uint256 xy; + + unchecked { + xy = x * y; + } + + if ((x != 0 && xy / x != y)) { + return; + } + + assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); + } +} + "#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 7 tests for test/FuzzUint.t.sol:FuzzNumbersTest +[FAIL: assertion failed[..]] testEquality(uint256,uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative0(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegative2Max(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeHalf(uint256) (runs: [..], [AVG_GAS]) +[FAIL: assertion failed[..]] testNegativeMax(uint256) (runs: [..], [AVG_GAS]) +[PASS] testPositive(uint256) (runs: 256, [AVG_GAS]) +Suite result: FAILED. 1 passed; 6 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +forgetest_init!(should_fuzz_literals, |prj, cmd| { + // Add a source with magic (literal) values + prj.add_source( + "Magic.sol", + r#" + contract Magic { + // plain literals + address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + uint64 constant MAGIC_NUMBER = 1122334455; + int32 constant MAGIC_INT = -777; + bytes32 constant MAGIC_WORD = "abcd1234"; + bytes constant MAGIC_BYTES = hex"deadbeef"; + string constant MAGIC_STRING = "xyzzy"; + + function checkAddr(address v) external pure { assert(v != DAI); } + function checkWord(bytes32 v) external pure { assert(v != MAGIC_WORD); } + function checkNumber(uint64 v) external pure { assert(v != MAGIC_NUMBER); } + function checkInteger(int32 v) external pure { assert(v != MAGIC_INT); } + function checkString(string memory v) external pure { assert(keccak256(abi.encodePacked(v)) != keccak256(abi.encodePacked(MAGIC_STRING))); } + function checkBytesFromHex(bytes memory v) external pure { assert(keccak256(v) != keccak256(MAGIC_BYTES)); } + function checkBytesFromString(bytes memory v) external pure { assert(keccak256(v) != keccak256(abi.encodePacked(MAGIC_STRING))); } + } + "#, + ); + + prj.add_test( + "MagicFuzz.t.sol", + r#" + import {Test} from "forge-std/Test.sol"; + import {Magic} from "src/Magic.sol"; + + contract MagicTest is Test { + Magic public magic; + function setUp() public { magic = new Magic(); } + + function testFuzz_Addr(address v) public view { magic.checkAddr(v); } + function testFuzz_Number(uint64 v) public view { magic.checkNumber(v); } + function testFuzz_Integer(int32 v) public view { magic.checkInteger(v); } + function testFuzz_Word(bytes32 v) public view { magic.checkWord(v); } + function testFuzz_String(string memory v) public view { magic.checkString(v); } + function testFuzz_BytesFromHex(bytes memory v) public view { magic.checkBytesFromHex(v); } + function testFuzz_BytesFromString(bytes memory v) public view { magic.checkBytesFromString(v); } + } + "#, + ); + + // Helper to create expected output for a test failure + let expected_fail = |test_name: &str, type_sig: &str, value: &str| -> String { + format!( + r#"No files changed, compilation skipped + +Ran 1 test for test/MagicFuzz.t.sol:MagicTest +[FAIL: panic: assertion failed (0x01); counterexample: calldata=[..] args=[{value}]] {test_name}({type_sig}) (runs: [..], [AVG_GAS]) +[..] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +... +Encountered a total of 1 failing tests, 0 tests succeeded +... +"# + ) + }; + + // Test address literal fuzzing + let mut test_literal = |seed: u32, + test_name: &'static str, + type_sig: &'static str, + expected_value: &'static str| { + // the fuzzer is UNABLE to find a breaking input (fast) when NOT seeding from the AST + prj.update_config(|config| { + config.fuzz.runs = 100; + config.fuzz.dictionary.max_fuzz_dictionary_literals = 0; + config.fuzz.seed = Some(U256::from(seed)); + }); + cmd.forge_fuse().args(["test", "--match-test", test_name, "-j1"]).assert_success(); + + // the fuzzer is ABLE to find a breaking input when seeding from the AST + prj.update_config(|config| { + config.fuzz.dictionary.max_fuzz_dictionary_literals = 10_000; + }); + + let expected_output = expected_fail(test_name, type_sig, expected_value); + cmd.forge_fuse() + .args(["test", "--match-test", test_name, "-j1"]) + .assert_failure() + .stdout_eq(expected_output); + }; + + test_literal(100, "testFuzz_Addr", "address", "0x6B175474E89094C44Da98b954EedeAC495271d0F"); + test_literal(200, "testFuzz_Number", "uint64", "1122334455 [1.122e9]"); + test_literal(300, "testFuzz_Integer", "int32", "-777"); + test_literal( + 400, + "testFuzz_Word", + "bytes32", + "0x6162636431323334000000000000000000000000000000000000000000000000", /* bytes32("abcd1234") */ + ); + test_literal(500, "testFuzz_BytesFromHex", "bytes", "0xdeadbeef"); + test_literal(600, "testFuzz_String", "string", "\"xyzzy\""); + test_literal(999, "testFuzz_BytesFromString", "bytes", "0x78797a7a79"); // abi.encodePacked("xyzzy") +}); + +// Tests that `vm.randomUint()` produces different values across fuzz runs. +// Regression test for https://github.com/foundry-rs/foundry/issues/12817 +// +// The issue was that `vm.randomUint()` would produce the same sequence of values +// in every fuzz run because the RNG was seeded identically for each run. +// This test verifies that with many fuzz runs and a small range, we eventually +// hit value 0, which proves the RNG varies across runs. +forgetest_init!(test_fuzz_random_uint_varies_across_runs, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + function testFuzz_randomUint_shouldFail(uint256) public { + uint256 rand = vm.randomUint(0, 4); + assertTrue(rand != 0, "hit value 0"); + } +} + "#, + ); + + cmd.args(["test", "--fuzz-seed", "1", "--mt", "testFuzz_randomUint_shouldFail"]) + .assert_failure() + .stdout_eq(str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: hit value 0; counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +forgetest_init!(test_fuzz_run_replays_random_uint_failure, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + function testFuzz_randomUint_shouldFail(uint256) public { + uint256 rand = vm.randomUint(0, 4); + assertTrue(rand != 0, "hit value 0"); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: hit value 0; counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]]; + + cmd.args(["test", "--fuzz-seed", "1", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + assert_eq!(persisted_failure.fuzz.seed, Some(U256::from(1))); + assert_eq!(persisted_failure.fuzz.worker, Some(0)); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + cmd.forge_fuse() + .args([ + "test", + "--fuzz-seed", + "1", + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + + cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure().stdout_eq(expected_output); +}); + +forgetest_init!(test_fuzz_rerun_replays_random_uint_failure_without_seed, |prj, cmd| { + prj.add_test( + "RandomFuzzTest.t.sol", + r#" +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract RandomFuzzTest is Test { + error Random(uint256 value); + + function testFuzz_randomUint_shouldFail(uint256) public { + revert Random(vm.randomUint()); + } +} + "#, + ); + + let expected_output = str![[r#" +... +Ran 1 test for test/RandomFuzzTest.t.sol:RandomFuzzTest +[FAIL: Random([..]); counterexample: [..]] testFuzz_randomUint_shouldFail(uint256) (runs: [..], [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]; + + let assert = cmd + .args(["test", "--mt", "testFuzz_randomUint_shouldFail", "-j1"]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + let reason = random_failure_reason(&stdout); + + let failure_file = + prj.root().join("cache/fuzz/failures/RandomFuzzTest/testFuzz_randomUint_shouldFail"); + let persisted_failure: BaseCounterExample = + serde_json::from_slice(&std::fs::read(&failure_file).unwrap()).unwrap(); + let fuzz_seed = format!("{:#x}", persisted_failure.fuzz.seed.unwrap()); + let fuzz_run = persisted_failure.fuzz.run.unwrap().to_string(); + let fuzz_worker = persisted_failure.fuzz.worker.unwrap().to_string(); + + let assert = cmd + .forge_fuse() + .args([ + "test", + "--fuzz-seed", + &fuzz_seed, + "--fuzz-run", + &fuzz_run, + "--fuzz-worker", + &fuzz_worker, + "--mt", + "testFuzz_randomUint_shouldFail", + "-j1", + ]) + .assert_failure() + .stdout_eq(expected_output.clone()); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd + .forge_fuse() + .args(["test", "--rerun", "-j1"]) + .assert_failure() + .stdout_eq(expected_output); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); + + let assert = cmd.forge_fuse().args(["test", "--rerun", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&assert.get_output().stdout); + assert_eq!(random_failure_reason(&stdout), reason, "{stdout}"); +}); + +fn random_failure_reason(stdout: &str) -> String { + Regex::new(r"\[FAIL: (Random\([^)]+\))") + .unwrap() + .captures(stdout) + .unwrap_or_else(|| panic!("{stdout}"))[1] + .to_string() +} diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs new file mode 100644 index 0000000000000..107b387bb0ec5 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -0,0 +1,2680 @@ +use super::*; + +forgetest!(invariant_after_invariant, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantAfterInvariant.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract AfterInvariantHandler { + uint256 public count; + + function inc() external { + count += 1; + } +} + +contract InvariantAfterInvariantTest is Test { + AfterInvariantHandler handler; + + function setUp() public { + handler = new AfterInvariantHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.inc.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function afterInvariant() public { + require(handler.count() < 10, "afterInvariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_after_invariant_failure() public view { + require(handler.count() < 20, "invariant after invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_failure() public view { + require(handler.count() < 9, "invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + function invariant_success() public view { + require(handler.count() < 11, "invariant should not fail"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest +[FAIL: afterInvariant failure] + [SEQUENCE] + invariant_after_invariant_failure() ([RUNS]) + +[STATS] + +[FAIL: invariant failure] + [SEQUENCE] + invariant_failure() ([RUNS]) + +[STATS] + +[PASS] invariant_success() ([RUNS]) + +[STATS] + +Suite result: FAILED. 1 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 2 failing tests in test/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest +[FAIL: afterInvariant failure] + [SEQUENCE] + invariant_after_invariant_failure() ([RUNS]) +[FAIL: invariant failure] + [SEQUENCE] + invariant_failure() ([RUNS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_assume, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + // Should not treat vm.assume as revert. + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantAssume.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Handler is Test { + uint256 public count; + function doSomething(uint256 param) public { + vm.assume(param == 0); + count++; + } +} + +contract InvariantAssume is Test { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function invariant_dummy() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantAssume.t.sol:InvariantAssume +[PASS] invariant_dummy() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test that max_assume_rejects is respected. + prj.update_config(|config| { + config.invariant.max_assume_rejects = 1; + }); + + assert_invariant(&mut cmd).failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantAssume.t.sol:InvariantAssume +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] invariant_dummy() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantAssume.t.sol:InvariantAssume +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] invariant_dummy() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/5868 +forgetest!(invariant_calldata_dictionary, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(1)); + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantCalldataDictionary.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Owned { + address public owner; + address private ownerCandidate; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier onlyOwnerCandidate() { + require(msg.sender == ownerCandidate); + _; + } + + function transferOwnership(address candidate) external onlyOwner { + ownerCandidate = candidate; + } + + function acceptOwnership() external onlyOwnerCandidate { + owner = ownerCandidate; + } +} + +contract Handler is Test { + Owned owned; + + constructor(Owned _owned) { + owned = _owned; + } + + function transferOwnership(address sender, address candidate) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.transferOwnership(candidate); + } + + function acceptOwnership(address sender) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.acceptOwnership(); + } +} + +contract InvariantCalldataDictionary is Test { + address owner; + Owned owned; + Handler handler; + address[] actors; + + function setUp() public { + owner = address(this); + owned = new Owned(); + handler = new Handler(owned); + actors.push(owner); + actors.push(address(777)); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.transferOwnership.selector; + selectors[1] = handler.acceptOwnership.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function fixtureSender() external returns (address[] memory) { + return actors; + } + + function fixtureCandidate() external returns (address[] memory) { + return actors; + } + + function invariant_owner_never_changes() public { + assertEq(owned.owner(), owner); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary +[FAIL: assertion failed] + [SEQUENCE] + invariant_owner_never_changes() ([RUNS]) + +... + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary +[FAIL: assertion failed] + [SEQUENCE] + invariant_owner_never_changes() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_custom_error, |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantCustomError.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ContractWithCustomError { + error InvariantCustomError(uint256, string); + + function revertWithInvariantCustomError() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract Handler is Test { + ContractWithCustomError target; + + constructor() { + target = new ContractWithCustomError(); + } + + function revertTarget() external { + target.revertWithInvariantCustomError(); + } +} + +contract InvariantCustomError is Test { + Handler handler; + + function setUp() external { + handler = new Handler(); + } + + function invariant_decode_error() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantCustomError.t.sol:InvariantCustomError +[FAIL: InvariantCustomError(111, "custom")] + [SEQUENCE] + invariant_decode_error() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantCustomError.t.sol:InvariantCustomError +[FAIL: InvariantCustomError(111, "custom")] + [SEQUENCE] + invariant_decode_error() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_excluded_senders, |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantExcludedSenders.t.sol", + r#" +import "forge-std/Test.sol"; + +contract InvariantSenders { + uint256 public count; + function checkSender() external { + require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); + require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); + require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); + count++; + } +} + +contract InvariantExcludedSendersTest is Test { + InvariantSenders target; + + function setUp() public { + target = new InvariantSenders(); + } + + function invariant_check_sender() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest +[PASS] invariant_check_sender() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(invariant_fixtures, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 100; + // disable literals to test fixtures + config.invariant.dictionary.max_fuzz_dictionary_literals = 0; + config.fuzz.dictionary.max_fuzz_dictionary_literals = 0; + }); + + prj.add_test( + "InvariantFixtures.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values using fixtures. +contract InvariantFixtures is Test { + Target target; + address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; + uint256[] public fixture_amount = [1, 2, 1122334455]; + + function setUp() public { + target = new Target(); + } + + function fixtureMagic() external returns (int32[2] memory) { + int32[2] memory magic; + magic[0] = -777; + magic[1] = 777; + return magic; + } + + function fixtureKey() external pure returns (bytes32[] memory) { + bytes32[] memory keyFixture = new bytes32[](1); + keyFixture[0] = "abcd1234"; + return keyFixture; + } + + function fixtureBackup() external pure returns (bytes[] memory) { + bytes[] memory backupFixture = new bytes[](1); + backupFixture[0] = "qwerty1234"; + return backupFixture; + } + + function fixtureExtra() external pure returns (string[] memory) { + string[] memory extraFixture = new string[](1); + extraFixture[0] = "112233aabbccdd"; + return extraFixture; + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantFixtures.t.sol:InvariantFixtures +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantFixtures.t.sol:InvariantFixtures +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_breaks_without_fixtures, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(1)); + config.invariant.runs = 1; + config.invariant.depth = 100; + }); + + prj.add_test( + "InvariantLiterals.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithoutFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values without using fixtures. +contract InvariantLiterals is Test { + Target target; + + function setUp() public { + target = new Target(); + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantLiterals.t.sol:InvariantLiterals +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantLiterals.t.sol:InvariantLiterals +[FAIL: assertion failed: true != false] + [SEQUENCE] + invariant_target_not_compromised() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest!(invariant_handler_failure, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantHandlerFailure.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Handler is Test { + function doSomething() public { + require(false, "failed on revert"); + } +} + +contract InvariantHandlerFailure is Test { + bytes4[] internal selectors; + + Handler handler; + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.doSomething.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function setUp() public { + handler = new Handler(); + } + + function statefulFuzz_BrokenInvariant() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantHandlerFailure.t.sol:InvariantHandlerFailure +[FAIL: failed on revert] + [SEQUENCE] + statefulFuzz_BrokenInvariant() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantHandlerFailure.t.sol:InvariantHandlerFailure +[FAIL: failed on revert] + [SEQUENCE] + statefulFuzz_BrokenInvariant() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// Here we test that the fuzz engine can include a contract created during the fuzz +// in its fuzz dictionary and eventually break the invariant. +// Specifically, can Judas, a created contract from Jesus, break Jesus contract +// by revealing his identity. +forgetest_init!( + #[cfg_attr(windows, ignore = "for some reason there's different rng")] + invariant_inner_contract, + |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantInnerContract.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Jesus { + address fren; + bool public identity_revealed; + + function create_fren() public { + fren = address(new Judas()); + } + + function kiss() public { + require(msg.sender == fren); + identity_revealed = true; + } +} + +contract Judas { + Jesus jesus; + + constructor() { + jesus = Jesus(msg.sender); + } + + function betray() public { + jesus.kiss(); + } +} + +contract InvariantInnerContract is Test { + Jesus jesus; + + function setUp() public { + jesus = new Jesus(); + } + + function invariantHideJesus() public { + require(jesus.identity_revealed() == false, "jesus betrayed"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [SEQUENCE] + invariantHideJesus() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [SEQUENCE] + invariantHideJesus() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); + + // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + // Disable persisted failures for rerunning the test. + config.invariant.failure_persist_dir = Some( + config + .invariant + .failure_persist_dir + .as_ref() + .unwrap() + .parent() + .unwrap() + .join("persistence2"), + ); + }); + cmd.assert_failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantInnerContract.t.sol:InvariantInnerContract +[FAIL: jesus betrayed] + [Sequence] (original: 2, shrunk: 2) + sender=[..] addr=[test/InvariantInnerContract.t.sol:Jesus][..] calldata=create_fren() args=[] + sender=[..] addr=[test/InvariantInnerContract.t.sol:Judas][..] calldata=betray() args=[] + invariantHideJesus() (runs: 0, calls: 0, reverts: 1) +... +"#]]); + } +); + +// https://github.com/foundry-rs/foundry/issues/7219 +forgetest!(invariant_preserve_state, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "InvariantPreserveState.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Handler is Test { + function thisFunctionReverts() external { + if (block.number < 10) {} else { + revert(); + } + } + + function advanceTime(uint256 blocks) external { + blocks = blocks % 10; + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 12); + } +} + +contract InvariantPreserveState is Test { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.thisFunctionReverts.selector; + selectors[1] = handler.advanceTime.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_preserve_state() public { + assertTrue(true); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantPreserveState.t.sol:InvariantPreserveState +[FAIL: EvmError: Revert] + [SEQUENCE] + invariant_preserve_state() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantPreserveState.t.sol:InvariantPreserveState +[FAIL: EvmError: Revert] + [SEQUENCE] + invariant_preserve_state() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// add code so contract is accounted as valid sender +// see https://github.com/foundry-rs/foundry/issues/4245 +forgetest!(invariant_reentrancy, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); + + prj.add_test( + "InvariantReentrancy.t.sol", + r#" +import "./utils/Test.sol"; + +contract Malicious { + function world() public { + payable(msg.sender).call(""); + } +} + +contract Vulnerable { + bool public open_door = false; + bool public stolen = false; + Malicious mal; + + constructor(address _mal) { + mal = Malicious(_mal); + } + + function hello() public { + open_door = true; + mal.world(); + open_door = false; + } + + function backdoor() public { + require(open_door, ""); + stolen = true; + } +} + +contract InvariantReentrancy is Test { + Vulnerable vuln; + Malicious mal; + + function setUp() public { + mal = new Malicious(); + vuln = new Vulnerable(address(mal)); + } + + // do not include `mal` in identified contracts + // see https://github.com/foundry-rs/foundry/issues/4245 + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(vuln); + return targets; + } + + function invariantNotStolen() public { + require(vuln.stolen() == false, "stolen"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantReentrancy.t.sol:InvariantReentrancy +[FAIL: stolen] + [SEQUENCE] + invariantNotStolen() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantReentrancy.t.sol:InvariantReentrancy +[FAIL: stolen] + [SEQUENCE] + invariantNotStolen() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// Tests that call_override detects the classic DAO-style reentrancy vulnerability +// in EtherStore where balances are updated AFTER the external call. +forgetest!(invariant_reentrancy_ether_store, |prj, cmd| { + prj.insert_utils(); + prj.update_config(|config| { + config.invariant.depth = 15; + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); + + prj.add_test( + "InvariantReentrancyEtherStore.t.sol", + r#" +import "./utils/Test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// Classic reentrancy-vulnerable contract +contract EtherStore { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw() public { + uint256 bal = balances[msg.sender]; + require(bal > 0); + // BUG: External call before state update + (bool sent,) = msg.sender.call{value: bal}(""); + require(sent, "Failed to send Ether"); + balances[msg.sender] = 0; + } +} + +contract InvariantReentrancyEtherStore is Test { + EtherStore store; + address attacker; + + function setUp() public { + store = new EtherStore(); + attacker = address(0x1337); + + vm.deal(address(this), 10 ether); + store.deposit{value: 5 ether}(); + + // Attacker gets 2 ether, deposits 1 ether, keeps 1 ether in wallet. + // After withdrawing their deposit, attacker wallet balance cannot exceed 2 ether. + vm.deal(attacker, 2 ether); + vm.prank(attacker); + store.deposit{value: 1 ether}(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(store); + return targets; + } + + function targetSenders() public view returns (address[] memory) { + address[] memory senders = new address[](1); + senders[0] = attacker; + return senders; + } + + function targetSelectors() public view returns (FuzzSelector[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = EtherStore.withdraw.selector; + FuzzSelector[] memory targets = new FuzzSelector[](1); + targets[0] = FuzzSelector(address(store), selectors); + return targets; + } + + // Attacker should never have more than 2 ether (1 kept + 1 withdrawn) + function invariantSolvency() public view { + require(attacker.balance <= 2 ether, "reentrancy: attacker wallet > 2 ether"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +Ran 1 test for test/InvariantReentrancyEtherStore.t.sol:InvariantReentrancyEtherStore +[FAIL: reentrancy: attacker wallet > 2 ether] + [SEQUENCE] + invariantSolvency() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantReentrancyEtherStore.t.sol:InvariantReentrancyEtherStore +[FAIL: reentrancy: attacker wallet > 2 ether] + [SEQUENCE] + invariantSolvency() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_roll_fork, |prj, cmd| { + prj.add_rpc_endpoints(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.shrink_run_limit = 0; + }); + + prj.add_test( + "InvariantRollFork.t.sol", + r#" +import "forge-std/Test.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract RollForkHandler is Test { + uint256 public totalSupply; + + function work() external { + vm.rollFork(block.number + 1); + totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); + } +} + +contract InvariantRollForkBlockTest is Test { + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 2 + /// forge-config: default.invariant.depth = 4 + function invariant_fork_handler_block() public view { + require(block.number < 19812634, "too many blocks mined"); + } +} + +contract InvariantRollForkStateTest is Test { + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 1 + function invariant_fork_handler_state() public view { + require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkBlockTest +[FAIL: too many blocks mined] + [SEQUENCE] + invariant_fork_handler_block() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkStateTest +[FAIL: wrong supply] + [SEQUENCE] + invariant_fork_handler_state() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkBlockTest +[FAIL: too many blocks mined] + [SEQUENCE] + invariant_fork_handler_block() ([RUNS]) + +Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkStateTest +[FAIL: wrong supply] + [SEQUENCE] + invariant_fork_handler_state() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_scrape_values, |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 10; + config.fuzz.seed = Some(U256::from(100u32)); + }); + + prj.add_test( + "InvariantScrapeValues.t.sol", + r#" +import "forge-std/Test.sol"; + +contract FindFromReturnValue { + bool public found = false; + + function seed() public returns (int256) { + int256 mystery = 13337; + return (1337 + mystery); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromReturnValueTest is Test { + FindFromReturnValue target; + + function setUp() public { + target = new FindFromReturnValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from return found"); + } +} + +contract FindFromLogValue { + event FindFromLog(int256 indexed mystery, bytes32 rand); + + bool public found = false; + + function seed() public { + int256 mystery = 13337; + emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromLogValueTest is Test { + FindFromLogValue target; + + function setUp() public { + target = new FindFromLogValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from logs found"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/InvariantScrapeValues.t.sol:FindFromLogValueTest +[FAIL: value from logs found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +Encountered 1 failing test in test/InvariantScrapeValues.t.sol:FindFromReturnValueTest +[FAIL: value from return found] + [SEQUENCE] + invariant_value_not_found() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_sequence_no_reverts, |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 15; + config.invariant.fail_on_revert = false; + // Use original counterexample to test sequence len. + config.invariant.shrink_run_limit = 0; + }); + + prj.add_test( + "InvariantSequenceNoReverts.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SequenceNoReverts { + uint256 public count; + + function work(uint256 x) public { + require(x % 2 != 0); + count++; + } +} + +contract SequenceNoRevertsTest is Test { + SequenceNoReverts target; + + function setUp() public { + target = new SequenceNoReverts(); + } + + function invariant_no_reverts() public view { + require(target.count() < 10, "condition met"); + } +} +"#, + ); + + // ensure original counterexample len is 10 (even without shrinking) + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantSequenceNoReverts.t.sol:SequenceNoRevertsTest +[FAIL: condition met] + [Sequence] (original: 10, shrunk: 10) +... + invariant_no_reverts() ([..]) +... +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +forgetest_init!( + #[cfg_attr(windows, ignore = "for some reason there's different rng")] + invariant_shrink_big_sequence, + |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 1000; + config.invariant.shrink_run_limit = 425; + }); + + prj.add_test( + "InvariantShrinkBigSequence.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ShrinkBigSequence { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + } + + function checkCond() public view { + require(cond < 77, "condition met"); + } +} + +contract ShrinkBigSequenceTest is Test { + ShrinkBigSequence target; + + function setUp() public { + target = new ShrinkBigSequence(); + } + + function invariant_shrink_big_sequence() public view { + target.checkCond(); + } +} +"#, + ); + + // ensure shrinks to same sequence of 77 + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest +[FAIL: condition met] + [Sequence] (original: [..], shrunk: 77) +... +"#]]); + cmd.assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest +[FAIL: condition met] + [Sequence] (original: [..], shrunk: 77) +... +"#]]); + } +); + +forgetest_init!(invariant_shrink_fail_on_revert, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 200; + }); + + prj.add_test( + "InvariantShrinkFailOnRevert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ShrinkFailOnRevert { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + require(cond < 10, "condition met"); + } +} + +contract ShrinkFailOnRevertTest is Test { + ShrinkFailOnRevert target; + + function setUp() public { + target = new ShrinkFailOnRevert(); + } + + function invariant_shrink_fail_on_revert() public view {} +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantShrinkFailOnRevert.t.sol:ShrinkFailOnRevertTest +[FAIL: condition met] + [Sequence] (original: [..], shrunk: 10) +... +"#]]); +}); + +forgetest_init!(invariant_fail_on_assert_panic, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantFailOnAssertPanic.t.sol", + r#" +import "forge-std/Test.sol"; + +contract AssertHandler { + uint256 public calls; + + function alwaysAssert() external { + calls++; + assert(false); + } +} + +contract InvariantFailOnAssertPanic is Test { + AssertHandler handler; + + function setUp() public { + handler = new AssertHandler(); + targetContract(address(handler)); + } + + function invariant_fail_on_assert_panic() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantFailOnAssertPanic.t.sol:InvariantFailOnAssertPanic +[FAIL: panic: assertion failed (0x01)] +... + invariant_fail_on_assert_panic() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_ignore_assert_panic_when_flag_off, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantIgnoreAssertWhenFlagOff.t.sol", + r#" +import "forge-std/Test.sol"; + +contract AssertHandler { + uint256 public calls; + + function alwaysAssert() external { + calls++; + assert(false); + } +} + +contract InvariantIgnoreAssertWhenFlagOff is Test { + AssertHandler handler; + + function setUp() public { + handler = new AssertHandler(); + targetContract(address(handler)); + } + + function invariant_assert_discarded() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantIgnoreAssertWhenFlagOff.t.sol:InvariantIgnoreAssertWhenFlagOff +[FAIL: panic: assertion failed (0x01)] +... + invariant_assert_discarded() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_fail_on_assert_ignores_non_assert_panic, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantIgnoreNonAssertPanic.t.sol", + r#" +import "forge-std/Test.sol"; + +contract OverflowHandler { + uint256 public calls; + + function alwaysOverflow() external { + calls++; + uint256 x = type(uint256).max; + x = x + 1; + } +} + +contract InvariantIgnoreNonAssertPanic is Test { + OverflowHandler handler; + + function setUp() public { + handler = new OverflowHandler(); + targetContract(address(handler)); + } + + function invariant_non_assert_panic_discarded() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +... +[PASS] invariant_non_assert_panic_discarded() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_fail_on_assert_ignores_require_revert, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantIgnoreRequireRevert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract RequireHandler { + uint256 public calls; + + function alwaysRequire() external { + calls++; + require(false, "require failed"); + } +} + +contract InvariantIgnoreRequireRevert is Test { + RequireHandler handler; + + function setUp() public { + handler = new RequireHandler(); + targetContract(address(handler)); + } + + function invariant_require_revert_discarded() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).success().stdout_eq(str![[r#" +... +[PASS] invariant_require_revert_discarded() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_replay_fail_on_assert, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.fail_on_revert = false; + config.invariant.runs = 1; + config.invariant.depth = 200; + }); + + prj.add_test( + "InvariantReplayFailOnAssert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ReplayAssertHandler { + uint256 public calls; + + function alwaysAssert() external { + calls++; + assert(false); + } +} + +contract ReplayFailOnAssertTest is Test { + ReplayAssertHandler handler; + + function setUp() public { + handler = new ReplayAssertHandler(); + targetContract(address(handler)); + } + + function invariant_replay_fail_on_assert() public view {} +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantReplayFailOnAssert.t.sol:ReplayFailOnAssertTest +[FAIL: panic: assertion failed (0x01)] +... +"#]]); + + cmd.assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantReplayFailOnAssert.t.sol:ReplayFailOnAssertTest +[FAIL: panic: assertion failed (0x01)] +... +"#]]); +}); + +forgetest_init!(invariant_fail_on_vm_assert_revert, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantFailOnVmAssertRevert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract VmAssertHandler { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint256 public calls; + + function alwaysVmAssert() external { + calls++; + vm.assertEq(uint256(1), uint256(2)); + } +} + +contract InvariantFailOnVmAssertRevert is Test { + VmAssertHandler handler; + + function setUp() public { + handler = new VmAssertHandler(); + targetContract(address(handler)); + } + + function invariant_fail_on_vm_assert_revert() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantFailOnVmAssertRevert.t.sol:InvariantFailOnVmAssertRevert +[FAIL: assertion failed: 1 != 2] +... + invariant_fail_on_vm_assert_revert() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_ignore_vm_assert_when_flag_off, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + }); + + prj.add_test( + "InvariantIgnoreVmAssertWhenFlagOff.t.sol", + r#" +import "forge-std/Test.sol"; + +contract VmAssertHandler { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint256 public calls; + + function alwaysVmAssert() external { + calls++; + vm.assertEq(uint256(1), uint256(2)); + } +} + +contract InvariantIgnoreVmAssertWhenFlagOff is Test { + VmAssertHandler handler; + + function setUp() public { + handler = new VmAssertHandler(); + targetContract(address(handler)); + } + + function invariant_vm_assert_discarded() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantIgnoreVmAssertWhenFlagOff.t.sol:InvariantIgnoreVmAssertWhenFlagOff +[FAIL: assertion failed: 1 != 2] +... + invariant_vm_assert_discarded() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_fail_on_vm_assert_global_flag, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + config.assertions_revert = false; + }); + + prj.add_test( + "InvariantFailOnVmAssertGlobalFlag.t.sol", + r#" +import "forge-std/Test.sol"; + +contract VmAssertHandler { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint256 public calls; + + function alwaysVmAssert() external { + calls++; + vm.assertEq(uint256(1), uint256(2)); + } +} + +contract InvariantFailOnVmAssertGlobalFlag is Test { + VmAssertHandler handler; + + function setUp() public { + handler = new VmAssertHandler(); + targetContract(address(handler)); + } + + function invariant_fail_on_vm_assert_global_flag() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantFailOnVmAssertGlobalFlag.t.sol:InvariantFailOnVmAssertGlobalFlag +[FAIL: assertion failed] +... + invariant_fail_on_vm_assert_global_flag() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_ignore_vm_assert_global_flag_when_flag_off, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.fail_on_revert = false; + config.assertions_revert = false; + }); + + prj.add_test( + "InvariantIgnoreVmAssertGlobalFlagWhenFlagOff.t.sol", + r#" +import "forge-std/Test.sol"; + +contract VmAssertHandler { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint256 public calls; + + function alwaysVmAssert() external { + calls++; + vm.assertEq(uint256(1), uint256(2)); + } +} + +contract InvariantIgnoreVmAssertGlobalFlagWhenFlagOff is Test { + VmAssertHandler handler; + + function setUp() public { + handler = new VmAssertHandler(); + targetContract(address(handler)); + } + + function invariant_vm_assert_global_flag_discarded() public view {} +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantIgnoreVmAssertGlobalFlagWhenFlagOff.t.sol:InvariantIgnoreVmAssertGlobalFlagWhenFlagOff +[FAIL: assertion failed] +... + invariant_vm_assert_global_flag_discarded() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_shrink_with_assert, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 1; + config.invariant.depth = 15; + }); + + prj.add_test( + "InvariantShrinkWithAssert.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Counter { + uint256 public number; + + function increment() public { + number++; + } + + function decrement() public { + number--; + } +} + +contract InvariantShrinkWithAssert is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_with_assert() public { + assertTrue(counter.number() < 2, "wrong counter assert"); + } + + function invariant_with_require() public { + require(counter.number() < 2, "wrong counter require"); + } +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithAssert +[FAIL: wrong counter assert] + [Sequence] (original: 2, shrunk: 2) +... + invariant_with_assert() ([..]) +... +[FAIL: wrong counter require] + [Sequence] (original: 2, shrunk: 2) +... + invariant_with_require() ([..]) +... +"#]]); +}); + +forgetest_init!(invariant_replay_keeps_assertion_failure_from_invariant_function, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 1; + config.invariant.depth = 15; + }); + + prj.add_test( + "InvariantReplayKeepsInvariantAssertion.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Counter { + uint256 public number; + + function increment() public { + number++; + } + + function decrement() public { + number--; + } +} + +contract InvariantReplayKeepsInvariantAssertion is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_with_assert() public view { + assertTrue(counter.number() < 2, "wrong counter assert"); + } +} +"#, + ); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantReplayKeepsInvariantAssertion.t.sol:InvariantReplayKeepsInvariantAssertion +[FAIL: wrong counter assert] +... + invariant_with_assert() ([..]) +... +"#]]); + + cmd.assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/InvariantReplayKeepsInvariantAssertion.t.sol:InvariantReplayKeepsInvariantAssertion +[FAIL: wrong counter assert] + [Sequence] (original: 2, shrunk: 2) +... + invariant_with_assert() ([..]) +... +"#]]); +}); + +forgetest_init!(invariant_replay_keeps_assertion_failure_from_after_invariant, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 2; + }); + + prj.add_test( + "InvariantReplayKeepsAfterInvariantAssertion.t.sol", + r#" +import "forge-std/Test.sol"; + +contract AfterInvariantAssertHandler { + uint256 public count; + + function inc() external { + count += 1; + } +} + +contract InvariantReplayKeepsAfterInvariantAssertion is Test { + AfterInvariantAssertHandler handler; + + function setUp() public { + handler = new AfterInvariantAssertHandler(); + targetContract(address(handler)); + } + + function afterInvariant() public view { + assertTrue(handler.count() < 2, "afterInvariant assertion"); + } + + function invariant_success() public view { + require(handler.count() < 10, "invariant should not fail"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantReplayKeepsAfterInvariantAssertion.t.sol:InvariantReplayKeepsAfterInvariantAssertion +[FAIL: afterInvariant assertion] + [SEQUENCE] + invariant_success() ([RUNS]) +... +"#]]); + + assert_invariant(&mut cmd).failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantReplayKeepsAfterInvariantAssertion.t.sol:InvariantReplayKeepsAfterInvariantAssertion +[FAIL: afterInvariant assertion] + [SEQUENCE] + invariant_success() ([RUNS]) +... +"#]]); +}); + +forgetest_init!(invariant_test1, |prj, cmd| { + prj.update_config(|config| { + config.invariant.depth = 10; + }); + + prj.add_test( + "InvariantTest1.t.sol", + r#" +import "forge-std/Test.sol"; + +contract InvariantBreaker { + bool public flag0 = true; + bool public flag1 = true; + + function set0(int256 val) public returns (bool) { + if (val % 100 == 0) { + flag0 = false; + } + return flag0; + } + + function set1(int256 val) public returns (bool) { + if (val % 10 == 0 && !flag0) { + flag1 = false; + } + return flag1; + } +} + +contract InvariantTest is Test { + InvariantBreaker inv; + + function setUp() public { + inv = new InvariantBreaker(); + } + + function invariant_neverFalse() public { + require(inv.flag1(), "false"); + } + + function statefulFuzz_neverFalseWithInvariantAlias() public { + require(inv.flag1(), "false"); + } +} +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/InvariantTest1.t.sol:InvariantTest +[FAIL: false] + [SEQUENCE] + invariant_neverFalse() ([RUNS]) + +[STATS] + +[FAIL: false] + [SEQUENCE] + statefulFuzz_neverFalseWithInvariantAlias() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 2 failing tests in test/InvariantTest1.t.sol:InvariantTest +[FAIL: false] + [SEQUENCE] + invariant_neverFalse() ([RUNS]) +[FAIL: false] + [SEQUENCE] + statefulFuzz_neverFalseWithInvariantAlias() ([RUNS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +forgetest_init!(invariant_warp_and_roll, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.max_time_delay = Some(604800); + config.invariant.max_block_delay = Some(60480); + config.invariant.shrink_run_limit = 0; + }); + + prj.add_test( + "InvariantWarpAndRoll.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + +contract InvariantWarpAndRoll { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_warp() public view { + require(block.timestamp < 500000, "max timestamp"); + } + + /// forge-config: default.invariant.show_solidity = true + function invariant_roll() public view { + require(block.number < 200000, "max block"); + } +} +"#, + ); + + cmd.args(["test", "--mt", "invariant_warp"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: max timestamp] + [Sequence] (original: 5, shrunk: 5) + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=6280 roll=21461 calldata=setNumber(uint256) args=[500000 [5e5]] + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=92060 roll=51816 calldata=setNumber(uint256) args=[0] + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=198040 roll=60259 calldata=increment() args=[] + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=20609 roll=27086 calldata=setNumber(uint256) args=[26717227324157985679793128079000084308648530834088529513797156275625002 [2.671e70]] + sender=[..] addr=[test/InvariantWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=409368 roll=24864 calldata=increment() args=[] + invariant_warp() (runs: 0, calls: 0, reverts: 0) +... + +"#]]); + + cmd.forge_fuse().args(["test", "--mt", "invariant_roll"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: max block] + [Sequence] (original: 6, shrunk: 6) + vm.warp(block.timestamp + 6280); + vm.roll(block.number + 21461); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(500000); + vm.warp(block.timestamp + 92060); + vm.roll(block.number + 51816); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(0); + vm.warp(block.timestamp + 198040); + vm.roll(block.number + 60259); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.warp(block.timestamp + 20609); + vm.roll(block.number + 27086); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(26717227324157985679793128079000084308648530834088529513797156275625002); + vm.warp(block.timestamp + 409368); + vm.roll(block.number + 24864); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.warp(block.timestamp + 218105); + vm.roll(block.number + 17834); + vm.prank([..]); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(24752675372815722001736610830); + invariant_roll() (runs: 0, calls: 0, reverts: 0) +... + +"#]]); + + // Test that time and block advance in target contract as well. + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + }); + prj.add_test( + "HandlerWarpAndRoll.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Counter { + uint256 public number; + function setNumber(uint256 newNumber) public { + require(block.number < 200000, "max block"); + number = newNumber; + } + + function increment() public { + require(block.timestamp < 500000, "max timestamp"); + number++; + } +} + +contract HandlerWarpAndRoll { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_handler() public view { + } +} +"#, + ); + + cmd.forge_fuse().args(["test", "--mt", "invariant_handler"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/HandlerWarpAndRoll.t.sol:HandlerWarpAndRoll +[FAIL: max timestamp] + [Sequence] (original: 5, shrunk: 5) + sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=6280 roll=21461 calldata=setNumber(uint256) args=[200000 [2e5]] + sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=92060 roll=51816 calldata=setNumber(uint256) args=[0] + sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=198040 roll=60259 calldata=increment() args=[] + sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=20609 roll=27086 calldata=setNumber(uint256) args=[26717227324157985679793128079000084308648530834088529513797156275625002 [2.671e70]] + sender=[..] addr=[test/HandlerWarpAndRoll.t.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f warp=409368 roll=24864 calldata=increment() args=[] + invariant_handler() (runs: 0, calls: 0, reverts: 1) + +... + +"#]]); +}); + +// Test that state is preserved across calls during invariant replay. +// Regression test for commit 0584a581b which changed replay_run to use execute_tx +// (which uses call_raw) instead of transact_raw, but forgot to add the commit() call. +forgetest_init!(invariant_replay_state_preserved, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 5; + }); + + prj.add_test( + "InvariantReplayState.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Handler is Test { + uint256 public counter; + + function increment(uint256 amount) public { + uint256 before = counter; + counter += 1; + console.log("before:", before, "after:", counter); + } +} + +contract InvariantReplayStateTest is Test { + Handler handler; + + function setUp() public { + handler = new Handler(); + targetContract(address(handler)); + } + + function invariant_counter_increases() public view { + assertTrue(true); + } +} +"#, + ); + + // With -vvv we see logs from replay. The "before" value of each call should + // match the "after" value from the previous call, proving state persists. + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter_increases() (runs: 1, calls: 5, reverts: 0) +... +Logs: + before: 0 after: 1 + before: 1 after: 2 + before: 2 after: 3 + before: 3 after: 4 + before: 4 after: 5 +... +"#]]); +}); + +// Test optimization mode for invariant testing. +// When an invariant function returns int256, it becomes an optimization target. +// The fuzzer maximizes the return value instead of checking for failures. +forgetest!(invariant_optimization_mode, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantOptimize.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract OptimizationHandler { + int256 public value; + + // Each call adds exactly 10 to value + function increment() external { + value += 10; + } +} + +contract InvariantOptimizeTest is Test { + OptimizationHandler handler; + + function setUp() public { + handler = new OptimizationHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.increment.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + /// @notice Optimization mode: returns int256 to maximize. + /// With depth=5 and only increment(), max value should be 50. + function invariant_optimize_value() public view returns (int256) { + return handler.value(); + } +} +"#, + ); + + // Optimization mode: best value shown first, should reach 50 (5 calls * 10 each) + // Shows [Best sequence] with calls in output + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] [..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] +[..]calldata=increment()[..] + invariant_optimize_value() (best: 50, runs: 1, calls: 5) +... +"#]]); +}); + +// Test that optimization mode works with negative values (finding max of negative range). +forgetest!(invariant_optimization_negative_values, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + + prj.add_test( + "InvariantOptimizeNegative.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract NegativeHandler { + int256 public value = -100; + + // Each call adds exactly 25 to value + function increase() external { + value += 25; + } +} + +contract InvariantOptimizeNegativeTest is Test { + NegativeHandler handler; + + function setUp() public { + handler = new NegativeHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.increase.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 4 + /// Starting at -100, 4 calls of +25 each = -100 + 100 = 0 + function invariant_optimize_negative() public view returns (int256) { + return handler.value(); + } +} +"#, + ); + + // Optimization should reach 0: starting at -100, 4 calls * +25 = 0 + // Shows [Best sequence] with calls in output + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] [..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] +[..]calldata=increase()[..] + invariant_optimize_negative() (best: 0, runs: 1, calls: 4) +... +"#]]); +}); + +// Test that optimization mode: +// 1. Evaluates at every prefix regardless of check_interval (finds true max, not just last-call +// value) +// 2. Persists the best value across runs via corpus directory +forgetest_init!(invariant_optimization_check_interval_and_persistence, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 5; + config.invariant.check_interval = 0; + config.invariant.corpus.corpus_dir = Some("opt_corpus".into()); + }); + prj.add_test( + "InvariantOptimize.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract PeakHandler { + int256 public value; + + // Value peaks at call 3, then drops. + function step() external { + value++; + if (value > 3) { + value = -100; + } + } +} + +contract InvariantOptimizeTest is Test { + PeakHandler handler; + + function setUp() public { + handler = new PeakHandler(); + targetContract(address(handler)); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + /// forge-config: default.invariant.check_interval = 0 + function invariant_optimize_peak() public view returns (int256) { + return handler.value(); + } +} +"#, + ); + + // Run 1: best should be 3 (prefix len 3), NOT -100 (depth 5). + // Validates check_interval=0 doesn't skip optimization sampling. + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] [..] +[..]calldata=step()[..] +[..]calldata=step()[..] +[..]calldata=step()[..] + invariant_optimize_peak() (best: 3, runs: 1, calls: 5) +... +"#]]); + + // Run 2: persisted best should survive across runs. + cmd.forge_fuse().args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +... + invariant_optimize_peak() (best: 3, runs: 1, calls: 5) +... +"#]]); +}); + +// Test optimization mode with time-dependent logic using warp and fixed seed for reproducibility. +// This test ensures warp values are correctly accumulated during shrinking. +forgetest_init!(invariant_optimization_with_warp, |prj, cmd| { + prj.add_test( + "InvariantOptimizeWarp.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantOptimizeWarpTest is Test { + int256 public maxValue; + + function setUp() public { + targetContract(address(this)); + } + + // Simulates time-dependent pricing with warp. Higher timestamp = higher value. + function updateValue(uint256 multiplier) public { + if (multiplier == 0 || multiplier > 100) revert(); + // Value depends on block.timestamp which can be warped + int256 newValue = int256(block.timestamp * multiplier / 1000); + if (newValue > maxValue) { + maxValue = newValue; + } + } + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.max_time_delay = 604800 + function invariant_optimize_max_value() public view returns (int256) { + return maxValue; + } +} +"#, + ); + + // Use fixed seed for deterministic output. The optimizer finds sequences that + // maximize value through time manipulation (warp). Shrinking reduces to 1 call. + cmd.args(["test", "-vvv", "--fuzz-seed", "12345"]).assert_success().stdout_eq(str![[r#" +... +[PASS] + [Best sequence] (original: 9, shrunk: 1) + sender=0x0000000000000000000000000000000000000637 addr=[test/InvariantOptimizeWarp.t.sol:InvariantOptimizeWarpTest]0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 warp=3249628 calldata=updateValue(uint256) args=[100] + invariant_optimize_max_value() (best: 324962, runs: 10, calls: 150) +... +"#]]); +}); + +// Regression test for delay-aware shrinking in check mode. +// Removed calls may still contribute warp/roll, so the final shrunk sequence must preserve those +// values in the remaining call. +forgetest_init!(invariant_shrink_preserves_warp_roll, |prj, cmd| { + prj.add_test( + "InvariantRollWarpShrink.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Roll { + uint256 public number; + + function increment() public { + require(block.number > 50000, "wrong block"); + number++; + } +} + +contract Warp { + uint256 public number; + + function increment() public { + require(block.timestamp > 500000, "wrong timestamp"); + number++; + } +} + +contract InvariantRoll is Test { + Roll public roll; + + function setUp() public { + roll = new Roll(); + } + + /// forge-config: default.fuzz.seed = "119" + /// forge-config: default.invariant.max_block_delay = 60480 + /// forge-config: default.invariant.show_solidity = true + function invariant_roll() public view { + require(roll.number() == 0, "number is not zero"); + } +} + +contract InvariantWarp is Test { + Warp public warp; + + function setUp() public { + warp = new Warp(); + } + + /// forge-config: default.fuzz.seed = "119" + /// forge-config: default.invariant.max_time_delay = 604800 + /// forge-config: default.invariant.show_solidity = true + function invariant_warp() public view { + require(warp.number() == 0, "max time"); + } +} +"#, + ); + + cmd.args(["test", "--mt", "invariant_roll"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: number is not zero] + [Sequence] (original: 3, shrunk: 1) + vm.roll(block.number + 52068); + vm.prank([..]); + Roll(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + invariant_roll() (runs: 0, calls: 0, reverts: 2) +... + +"#]]); + + cmd.forge_fuse().args(["test", "--mt", "invariant_warp"]).assert_failure().stdout_eq(str![[ + r#" +... +[FAIL: max time] + [Sequence] (original: 3, shrunk: 1) + vm.warp(block.timestamp + 656868); + vm.prank(0x00000000000000000000000000000000000012d1); + Warp(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + invariant_warp() (runs: 0, calls: 0, reverts: 2) +... + +"# + ]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/invariant/mod.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs new file mode 100644 index 0000000000000..bbe65f2f2f2fa --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -0,0 +1,1319 @@ +use alloy_primitives::U256; +use foundry_test_utils::{TestCommand, forgetest_init, snapbox::cmd::OutputAssert, str}; + +mod common; +mod storage; +mod target; + +fn assert_invariant(cmd: &mut TestCommand) -> OutputAssert { + cmd.assert_with(&[ + ("[RUNS]", r"runs: \d+, calls: \d+, reverts: \d+"), + ("[SEQUENCE]", r"\[Sequence\].*(\n\t\t.*)*"), + ("[STATS]", r"╭[\s\S]*?╰.*"), + ]) +} + +// Tests that a persisted failure doesn't fail due to assume revert if test driver is changed. +forgetest_init!(should_not_fail_replay_assume, |prj, cmd| { + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.max_assume_rejects = 10; + }); + + // Add initial test that breaks invariant. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract AssumeHandler is Test { + function fuzzMe(uint256 a) public { + require(false, "Invariant failure"); + } +} + +contract AssumeTest is Test { + function setUp() public { + AssumeHandler handler = new AssumeHandler(); + } + function invariant_assume() public {} +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_assume"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: Invariant failure] +... +"#]]); + + // Change test to use assume instead require. Same test should fail with too many inputs + // rejected message instead persisted failure revert. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract AssumeHandler is Test { + function fuzzMe(uint256 a) public { + vm.assume(false); + } +} + +contract AssumeTest is Test { + function setUp() public { + AssumeHandler handler = new AssumeHandler(); + } + function invariant_assume() public {} +} + "#, + ); + + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (10 allowed)] invariant_assume() (runs: 0, calls: 0, reverts: 0) +... +"#]]); +}); + +// Test too many inputs rejected for `assumePrecompile`/`assumeForgeAddress`. +// +forgetest_init!(should_revert_with_assume_code, |prj, cmd| { + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.max_assume_rejects = 10; + config.fuzz.seed = Some(U256::from(100u32)); + }); + + // Add initial test that breaks invariant. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract BalanceTestHandler is Test { + address public ref = address(1412323); + address alice; + + constructor(address _alice) { + alice = _alice; + } + + function increment(uint256 amount_, address addr) public { + assumeNotPrecompile(addr); + assumeNotForgeAddress(addr); + assertEq(alice.balance, 100_000 ether); + } +} + +contract BalanceAssumeTest is Test { + function setUp() public { + address alice = makeAddr("alice"); + vm.deal(alice, 100_000 ether); + targetSender(alice); + BalanceTestHandler handler = new BalanceTestHandler(alice); + targetContract(address(handler)); + } + + function invariant_balance() public {} +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_balance"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (10 allowed)] invariant_balance() (runs: 2, calls: 1000, reverts: 0) +... +"#]]); +}); + +// Test proper message displayed if `targetSelector`/`excludeSelector` called with empty selectors. +// +forgetest_init!(should_not_panic_if_no_selectors, |prj, cmd| { + prj.add_test( + "NoSelectorTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TestHandler is Test {} + +contract NoSelectorTest is Test { + bytes4[] selectors; + + function setUp() public { + TestHandler handler = new TestHandler(); + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + excludeSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + } + + function invariant_panic() public {} +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_panic"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: No contracts to fuzz.] invariant_panic() (runs: 0, calls: 0, reverts: 0) +... +"#]]); +}); + +// +forgetest_init!(should_show_invariant_metrics, |prj, cmd| { + prj.add_test( + "SelectorMetricsTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function setUp() public { + CounterHandler handler = new CounterHandler(); + AnotherCounterHandler handler1 = new AnotherCounterHandler(); + // targetContract(address(handler1)); + } + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true + function invariant_counter() public {} + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true + function invariant_counter2() public {} +} + +contract CounterHandler is Test { + function doSomething(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doAnotherThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + +contract AnotherCounterHandler is Test { + function doWork(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doWorkThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ + +[PASS] invariant_counter2() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); + +// Tests that invariant exists with success after configured timeout. +forgetest_init!(should_apply_configured_timeout, |prj, cmd| { + // Add initial test that breaks invariant. + prj.add_test( + "TimeoutTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TimeoutHandler is Test { + uint256 public count; + + function increment() public { + count++; + } +} + +contract TimeoutTest is Test { + TimeoutHandler handler; + + function setUp() public { + handler = new TimeoutHandler(); + } + + /// forge-config: default.invariant.runs = 10000 + /// forge-config: default.invariant.depth = 20000 + /// forge-config: default.invariant.timeout = 1 + function invariant_counter_timeout() public view { + // Invariant will fail if more than 10000 increments. + // Make sure test timeouts after one second and remaining runs are canceled. + require(handler.count() < 10000); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_counter_timeout"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/TimeoutTest.t.sol:TimeoutTest +[PASS] invariant_counter_timeout() (runs: 0, calls: 0, reverts: 0) + +╭----------------+-----------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=========================================================+ +| TimeoutHandler | increment | [..] | [..] | [..] | +╰----------------+-----------+-------+---------+----------╯ + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Tests that selector hits are uniformly distributed +// +forgetest_init!(invariant_selectors_weight, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + }); + prj.add_source( + "InvariantHandlers.sol", + r#" +contract HandlerOne { + uint256 public hit1; + + function selector1() external { + hit1 += 1; + } +} + +contract HandlerTwo { + uint256 public hit2; + uint256 public hit3; + uint256 public hit4; + uint256 public hit5; + + function selector2() external { + hit2 += 1; + } + + function selector3() external { + hit3 += 1; + } + + function selector4() external { + hit4 += 1; + } + + function selector5() external { + hit5 += 1; + } +} + "#, + ); + + prj.add_test( + "InvariantSelectorsWeightTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/InvariantHandlers.sol"; + +contract InvariantSelectorsWeightTest is Test { + HandlerOne handlerOne; + HandlerTwo handlerTwo; + + function setUp() public { + handlerOne = new HandlerOne(); + handlerTwo = new HandlerTwo(); + } + + function afterInvariant() public { + assertEq(handlerOne.hit1(), 2); + assertEq(handlerTwo.hit2(), 2); + assertEq(handlerTwo.hit3(), 2); + assertEq(handlerTwo.hit4(), 1); + assertEq(handlerTwo.hit5(), 3); + } + + function invariant_selectors_weight() public view {} +} + "#, + ); + + cmd.args(["test", "--fuzz-seed", "119", "--mt", "invariant_selectors_weight"]).assert_success(); +}); + +// Tests original and new counterexample lengths are displayed on failure. +// Tests switch from regular sequence output to solidity. +forgetest_init!(invariant_sequence_len, |prj, cmd| { + prj.initialize_default_contracts(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(10u32)); + }); + + prj.add_test( + "InvariantSequenceLenTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Counter.sol"; + +contract InvariantSequenceLenTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + targetContract(address(counter)); + } + + function invariant_increment() public { + require(counter.number() / 2 < 100000000000000000000000000000000, "invariant increment failure"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: invariant increment failure] + [Sequence] (original: 3, shrunk: 1) +... +"#]]); + + // Check regular sequence output. Shrink disabled to show several lines. + cmd.forge_fuse().arg("clean").assert_success(); + prj.update_config(|config| { + config.invariant.shrink_run_limit = 0; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: invariant increment failure] + [Sequence] (original: 3, shrunk: 3) + sender=0x0000000000000000000000000000000000001490 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x8ef7F804bAd9183981A366EA618d9D47D3124649 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x00000000000000000000000000000000000016C5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] + invariant_increment() (runs: 0, calls: 0, reverts: 0) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]], + ); + + // Check solidity sequence output on same failure. + cmd.forge_fuse().arg("clean").assert_success(); + prj.update_config(|config| { + config.invariant.show_solidity = true; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: invariant increment failure] + [Sequence] (original: 3, shrunk: 3) + vm.prank(0x0000000000000000000000000000000000001490); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.prank(0x8ef7F804bAd9183981A366EA618d9D47D3124649); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.prank(0x00000000000000000000000000000000000016C5); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(284406551521730736391345481857560031052359183671404042152984097777); + invariant_increment() (runs: 0, calls: 0, reverts: 0) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]], + ); + + // Persisted failures should be able to switch output. + prj.update_config(|config| { + config.invariant.show_solidity = false; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: invariant increment failure] + [Sequence] (original: 3, shrunk: 3) + sender=0x0000000000000000000000000000000000001490 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x8ef7F804bAd9183981A366EA618d9D47D3124649 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x00000000000000000000000000000000000016C5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[284406551521730736391345481857560031052359183671404042152984097777 [2.844e65]] + invariant_increment() (runs: 1, calls: 1, reverts: 1) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]], + ); +}); + +// Tests that persisted failure is discarded if test contract was modified. +// +forgetest_init!(invariant_replay_with_different_bytecode, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 5; + config.invariant.depth = 5; + }); + prj.add_source( + "Ownable.sol", + r#" +contract Ownable { + address public owner = address(777); + + function backdoor(address _owner) external { + owner = address(888); + } + + function changeOwner(address _owner) external { + } +} + "#, + ); + prj.add_test( + "OwnableTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Ownable.sol"; + +contract OwnableTest is Test { + Ownable ownable; + + function setUp() public { + ownable = new Ownable(); + } + + function invariant_never_owner() public { + require(ownable.owner() != address(888), "never owner"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_never_owner"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: never owner] +... +"#]]); + + // Should replay failure if same test. + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: never owner] +... +"#]]); + + // Different test driver that should not fail the invariant. + prj.add_test( + "OwnableTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Ownable.sol"; + +contract OwnableTest is Test { + Ownable ownable; + + function setUp() public { + ownable = new Ownable(); + // Ignore selector that fails invariant. + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Ownable.changeOwner.selector; + targetSelector(FuzzSelector({addr: address(ownable), selectors: selectors})); + } + + function invariant_never_owner() public { + require(ownable.owner() != address(888), "never owner"); + } +} + "#, + ); + cmd.assert_success().stderr_eq(str![[r#" +... +Warning: Failure from "[..]/invariant/failures/OwnableTest/invariant_never_owner" file was ignored because invariant test settings have changed: target selectors changed +... +"#]]) + .stdout_eq(str![[r#" +... +[PASS] invariant_never_owner() (runs: 5, calls: 25, reverts: 0) +... +"#]]); +}); + +forgetest_init!(invariant_replay_preserves_fail_reason, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 1; + }); + prj.add_test( + "InvariantReplayFailReason.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantReplayFailReason is Test { + function setUp() public { + targetContract(address(this)); + } + + function callTarget(uint256) external {} + + function invariant_fail_reason() public { + fail(); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_fail_reason"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: assertion failed][..] +... +"#]]); + + // Replay should preserve failure reason instead of generic replay message. + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: assertion failed][..] +... +"#]]); +}); + +forgetest_init!(invariant_replay_preserves_custom_error_reason, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 1; + config.invariant.fail_on_revert = true; + }); + prj.add_test( + "InvariantReplayCustomError.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CustomErrorTarget { + error InvariantCustomError(uint256, string); + + function breakInvariant() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract CustomErrorHandler is Test { + CustomErrorTarget target; + + constructor() { + target = new CustomErrorTarget(); + } + + function callTarget() external { + target.breakInvariant(); + } +} + +contract InvariantReplayCustomError is Test { + CustomErrorHandler handler; + + function setUp() public { + handler = new CustomErrorHandler(); + targetContract(address(handler)); + } + + function invariant_custom_error_reason() public view {} +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_custom_error_reason"]).assert_failure().stdout_eq(str![[ + r#" +... +[FAIL: [..]custom[..]][..] +... +"# + ]]); + + // Replay should preserve custom error string too. + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: [..]custom[..]][..] +... +"#]]); +}); + +forgetest_init!(invariant_replay_preserves_invariant_custom_error_reason, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 1; + }); + prj.add_test( + "InvariantReplayInvariantCustomError.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantReplayInvariantCustomError is Test { + error InvariantCustomError(uint256, string); + + function setUp() public { + targetContract(address(this)); + } + + function touch(uint256) external {} + + function invariant_custom_error_reason_from_invariant() public pure { + revert InvariantCustomError(222, "invariant custom"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_custom_error_reason_from_invariant"]) + .assert_failure() + .stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: InvariantCustomError(222, "invariant custom")][..] +... +"#]]); + + // Replay should preserve invariant-level custom error string too. + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: InvariantCustomError(222, "invariant custom")][..] +... +"#]]); +}); + +// +forgetest_init!(invariant_test_target, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 5; + config.invariant.depth = 5; + }); + prj.add_test( + "InvariantTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantTest is Test { + uint256 count; + + function setCount(uint256 _count) public { + count = _count; + } + + function setUp() public { + } + + function invariant_check_count() public { + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_check_count"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: No contracts to fuzz.] invariant_check_count() (runs: 0, calls: 0, reverts: 0) +... +"#]]); + + prj.add_test( + "InvariantTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantTest is Test { + uint256 count; + + function setCount(uint256 _count) public { + count = _count; + } + + function setUp() public { + targetContract(address(this)); + } + + function invariant_check_count() public { + } +} + "#, + ); + + cmd.forge_fuse().args(["test", "--mt", "invariant_check_count"]).assert_success().stdout_eq( + str![[r#" +... +[PASS] invariant_check_count() (runs: 5, calls: 25, reverts: 0) +... +"#]], + ); +}); + +// Tests that reserved test functions are not fuzzed when test is set as target. +// +forgetest_init!(invariant_target_test_contract_selectors, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 10; + config.invariant.depth = 100; + }); + prj.add_test( + "InvariantTargetTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantTargetTest is Test { + bool fooCalled; + bool testSanityCalled; + bool testTableCalled; + uint256 invariantCalledNum; + uint256 setUpCalledNum; + + function setUp() public { + targetContract(address(this)); + } + + function beforeTestSetup() public { + } + + // Only this selector should be targeted. + function foo() public { + fooCalled = true; + } + + function fixtureCalled() public returns (bool[] memory) { + } + + function table_sanity(bool called) public { + testTableCalled = called; + } + + function test_sanity() public { + testSanityCalled = true; + } + + function afterInvariant() public { + } + + function invariant_foo_called() public view { + } + + function invariant_testSanity_considered_target() public { + } + + function invariant_setUp_considered_target() public { + setUpCalledNum++; + } + + function invariant_considered_target() public { + invariantCalledNum++; + } +} + "#, + ); + + cmd.args(["test", "--mc", "InvariantTargetTest", "--mt", "invariant"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 4 tests for test/InvariantTargetTest.t.sol:InvariantTargetTest +[PASS] invariant_considered_target() (runs: 10, calls: 1000, reverts: 0) + +╭---------------------+----------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=============================================================+ +| InvariantTargetTest | foo | 1000 | 0 | 0 | +╰---------------------+----------+-------+---------+----------╯ + +[PASS] invariant_foo_called() (runs: 10, calls: 1000, reverts: 0) + +╭---------------------+----------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=============================================================+ +| InvariantTargetTest | foo | 1000 | 0 | 0 | +╰---------------------+----------+-------+---------+----------╯ + +[PASS] invariant_setUp_considered_target() (runs: 10, calls: 1000, reverts: 0) + +╭---------------------+----------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=============================================================+ +| InvariantTargetTest | foo | 1000 | 0 | 0 | +╰---------------------+----------+-------+---------+----------╯ + +[PASS] invariant_testSanity_considered_target() (runs: 10, calls: 1000, reverts: 0) + +╭---------------------+----------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=============================================================+ +| InvariantTargetTest | foo | 1000 | 0 | 0 | +╰---------------------+----------+-------+---------+----------╯ + +Suite result: ok. 4 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); + +// Tests that `targetSelector` and `excludeSelector` applied on test contract selectors are +// applied. +// +forgetest_init!(invariant_target_test_include_exclude_selectors, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 10; + config.invariant.depth = 100; + }); + prj.add_test( + "InvariantTargetTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantTargetIncludeTest is Test { + bool include = true; + function setUp() public { + targetContract(address(this)); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = this.shouldInclude1.selector; + selectors[1] = this.shouldInclude2.selector; + targetSelector(FuzzSelector({addr: address(this), selectors: selectors})); + } + + function shouldExclude1() public { + include = false; + } + + function shouldInclude1() public { + include = true; + } + + function shouldExclude2() public { + include = false; + } + + function shouldInclude2() public { + include = true; + } + + function invariant_include() public view { + require(include, "does not include"); + } +} + +contract InvariantTargetExcludeTest is Test { + bool include = true; + function setUp() public { + targetContract(address(this)); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = this.shouldExclude1.selector; + selectors[1] = this.shouldExclude2.selector; + excludeSelector(FuzzSelector({addr: address(this), selectors: selectors})); + } + + function shouldExclude1() public { + include = false; + } + + function shouldInclude1() public { + include = true; + } + + function shouldExclude2() public { + include = false; + } + + function shouldInclude2() public { + include = true; + } + + function invariant_exclude() public view { + require(include, "does not include"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_include"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/InvariantTargetTest.t.sol:InvariantTargetIncludeTest +[PASS] invariant_include() (runs: 10, calls: 1000, reverts: 0) + +╭----------------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++==========================================================================+ +| InvariantTargetIncludeTest | shouldInclude1 | [..] | 0 | 0 | +|----------------------------+----------------+-------+---------+----------| +| InvariantTargetIncludeTest | shouldInclude2 | [..] | 0 | 0 | +╰----------------------------+----------------+-------+---------+----------╯ + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse().args(["test", "--mt", "invariant_exclude"]).assert_success().stdout_eq(str![ + [r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantTargetTest.t.sol:InvariantTargetExcludeTest +[PASS] invariant_exclude() (runs: 10, calls: 1000, reverts: 0) + +╭----------------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++==========================================================================+ +| InvariantTargetExcludeTest | shouldInclude1 | [..] | 0 | 0 | +|----------------------------+----------------+-------+---------+----------| +| InvariantTargetExcludeTest | shouldInclude2 | [..] | 0 | 0 | +╰----------------------------+----------------+-------+---------+----------╯ + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#] + ]); + + cmd.forge_fuse() + .args(["test", "--mt", "invariant_include", "--md"]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantTargetTest.t.sol:InvariantTargetIncludeTest +[PASS] invariant_include() (runs: 10, calls: 1000, reverts: 0) + +| Contract | Selector | Calls | Reverts | Discards | +|----------------------------|----------------|-------|---------|----------| +| InvariantTargetIncludeTest | shouldInclude1 | [..] | 0 | 0 | +| InvariantTargetIncludeTest | shouldInclude2 | [..] | 0 | 0 | + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse() + .args(["test", "--mt", "invariant_exclude", "--md"]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for test/InvariantTargetTest.t.sol:InvariantTargetExcludeTest +[PASS] invariant_exclude() (runs: 10, calls: 1000, reverts: 0) + +| Contract | Selector | Calls | Reverts | Discards | +|----------------------------|----------------|-------|---------|----------| +| InvariantTargetExcludeTest | shouldInclude1 | [..] | 0 | 0 | +| InvariantTargetExcludeTest | shouldInclude2 | [..] | 0 | 0 | + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// +forgetest_init!(corpus_dir, |prj, cmd| { + prj.initialize_default_contracts(); + prj.update_config(|config| { + config.invariant.runs = 10; + config.invariant.depth = 10; + config.invariant.corpus.corpus_dir = Some("invariant_corpus".into()); + + config.fuzz.runs = 10; + config.fuzz.corpus.corpus_dir = Some("fuzz_corpus".into()); + }); + prj.add_test( + "CounterTests.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract Counter1Test is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + function invariant_counter_called() public view { + } +} + +contract Counter2Test is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + function invariant_counter_called() public view { + } +} + "#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 test suites [ELAPSED]: 6 tests passed, 0 failed, 0 skipped (6 total tests) + +"#]]); + + assert!( + prj.root() + .join("invariant_corpus") + .join("Counter1Test") + .join("invariant_counter_called") + .exists() + ); + assert!( + prj.root() + .join("invariant_corpus") + .join("Counter2Test") + .join("invariant_counter_called") + .exists() + ); + assert!( + prj.root().join("fuzz_corpus").join("Counter1Test").join("testFuzz_SetNumber").exists() + ); + assert!( + prj.root().join("fuzz_corpus").join("Counter2Test").join("testFuzz_SetNumber").exists() + ); +}); + +// Tests that check_interval=0 only asserts on the last call of each run. +forgetest_init!(check_interval_zero_only_checks_last_call, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 5; + config.invariant.depth = 10; + config.invariant.check_interval = 0; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // This invariant would fail on intermediate calls (counter 1-9) but passes on call 10 + // With check_interval=0, only the last call is checked, so if depth=10 and counter=10 + // at the end, this should pass even though intermediate states violated the invariant. + function invariant_counter_multiple_of_depth() public view { + // Only passes when counter is 0 or 10 (depth). Fails for 1-9. + require(handler.counter() == 0 || handler.counter() == 10, "not multiple of depth"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_counter"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter_multiple_of_depth() (runs: 5, calls: 50, reverts: 0) +... +"#]]); +}); + +// Tests that check_interval=1 (default) asserts after every call. +forgetest_init!(check_interval_one_checks_every_call, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.check_interval = 1; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // This invariant fails as soon as counter > 5. + // With check_interval=1, it should fail on call 6. + function invariant_counter_le_five() public view { + require(handler.counter() <= 5, "counter > 5"); + } +} + "#, + ); + + assert_invariant(cmd.args(["test", "--mt", "invariant_counter"])).failure().stdout_eq(str![[ + r#" +... +[FAIL: counter > 5] + [SEQUENCE] +... +"# + ]]); +}); + +// Tests that check_interval=N checks every N calls AND always on the last call. +forgetest_init!(check_interval_n_checks_every_n_calls, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 20; + config.invariant.check_interval = 5; + }); + prj.add_test( + "CheckIntervalTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + // With check_interval=5 and depth=20, invariant is checked at calls 5,10,15,20. + // This passes because 5,10,15,20 are all multiples of 5. + function invariant_counter_multiple_of_five() public view { + require(handler.counter() % 5 == 0, "not multiple of 5"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_counter"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter_multiple_of_five() (runs: 1, calls: 20, reverts: 0) +... +"#]]); +}); + +// Tests check_interval via inline config annotation. +forgetest_init!(check_interval_inline_config, |prj, cmd| { + prj.add_test( + "CheckIntervalInlineTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterHandler { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract CheckIntervalInlineTest is Test { + CounterHandler handler; + + function setUp() public { + handler = new CounterHandler(); + targetContract(address(handler)); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.check_interval = 0 + function invariant_only_last_checked() public view { + // Only passes when counter is 0 or 10. With check_interval=0, only last call is checked. + require(handler.counter() == 0 || handler.counter() == 10, "not at boundary"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_only_last_checked"]).assert_success().stdout_eq(str![[ + r#" +... +[PASS] invariant_only_last_checked() (runs: 1, calls: 10, reverts: 0) +... +"# + ]]); +}); diff --git a/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol b/crates/forge/tests/cli/test_cmd/invariant/storage.rs similarity index 59% rename from testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol rename to crates/forge/tests/cli/test_cmd/invariant/storage.rs index 890c495c310bb..dd98e920e20fb 100644 --- a/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol +++ b/crates/forge/tests/cli/test_cmd/invariant/storage.rs @@ -1,6 +1,10 @@ -pragma solidity >0.8.13; +use super::*; -import "ds-test/test.sol"; +forgetest_init!(storage, |prj, cmd| { + prj.add_test( + "name", + r#" +import "forge-std/Test.sol"; contract Contract { address public addr = address(0xbeef); @@ -33,26 +37,40 @@ contract Contract { } } -contract InvariantStorageTest is DSTest { +contract InvariantStorageTest is Test { Contract c; function setUp() public { c = new Contract(); } - function invariantChangeAddress() public { + function invariantChangeAddress() public view { require(c.addr() == address(0xbeef), "changedAddr"); } - function invariantChangeString() public { + function invariantChangeString() public view { require(keccak256(bytes(c.str())) == keccak256(bytes("hello")), "changedStr"); } - function invariantChangeUint() public { + function invariantChangeUint() public view { require(c.num() == 1337, "changedUint"); } - function invariantPush() public { + function invariantPush() public view { require(c.pushNum() == 0, "pushUint"); } } +"#, + ); + + assert_invariant(cmd.args(["test"])).failure().stdout_eq(str![[r#" +... +Suite result: FAILED. 0 passed; 4 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 4 failed, 0 skipped (4 total tests) + +Failing tests: +Encountered 4 failing tests in test/name.sol:InvariantStorageTest +... +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/invariant/target.rs b/crates/forge/tests/cli/test_cmd/invariant/target.rs new file mode 100644 index 0000000000000..a2b766926bb09 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/invariant/target.rs @@ -0,0 +1,861 @@ +use super::*; + +forgetest!(filters, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + prj.update_config(|config| { + config.invariant.runs = 50; + config.invariant.depth = 10; + }); + + prj.add_test( + "ExcludeContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract ExcludeContracts is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + new Hello(); + } + + function excludeContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(hello); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = false; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract ExcludeSelectors is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSelectors() public view returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantFalseWorld() public { + require(hello.world() == false, "true world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeSenders.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + address seed_address = address(0xdeadbeef); + bool public world = true; + + function changeBeef() public { + require(msg.sender == address(0xdeadbeef)); + world = false; + } + + // address(0) should be automatically excluded + function change0() public { + require(msg.sender == address(0)); + world = false; + } +} + +contract ExcludeSenders is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + // Tests clashing. Exclusion takes priority. + function targetSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract TargetContracts is Test { + Hello hello1; + Hello hello2; + + function setUp() public { + hello1 = new Hello(); + hello2 = new Hello(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(hello1); + return addrs; + } + + function invariantTrueWorld() public { + require(hello2.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetInterfaces.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzInterface { + address target; + string[] artifacts; +} + +contract Hello { + bool public world; + + function changeWorld() external { + world = true; + } +} + +interface IHello { + function world() external view returns (bool); + function changeWorld() external; +} + +contract HelloProxy { + address internal immutable _implementation; + + constructor(address implementation_) { + _implementation = implementation_; + } + + function _delegate(address implementation) internal { + assembly { + calldatacopy(0, 0, calldatasize()) + + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + fallback() external payable { + _delegate(_implementation); + } +} + +contract TargetWorldInterfaces is Test { + IHello proxy; + + function setUp() public { + Hello hello = new Hello(); + proxy = IHello(address(new HelloProxy(address(hello)))); + } + + function targetInterfaces() public view returns (FuzzInterface[] memory) { + FuzzInterface[] memory targets = new FuzzInterface[](1); + + string[] memory artifacts = new string[](1); + artifacts[0] = "IHello"; + + targets[0] = FuzzInterface(address(proxy), artifacts); + + return targets; + } + + function invariantTrueWorld() public { + require(proxy.world() == false, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = true; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract TargetSelectors is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function targetSelectors() public view returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetSenders.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Hello { + bool public world = true; + + function change() public { + require(msg.sender == address(0xdeadbeef)); + world = false; + } +} + +contract TargetSenders is Test { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function targetSenders() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(0xdeadbeef); + return addrs; + } + + function invariantTrueWorld() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "ExcludeArtifacts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +// Will get automatically excluded. Otherwise it would throw error. +contract NoMutFunctions { + function no_change() public pure {} +} + +contract Excluded { + bool public world = true; + + function change() public { + world = false; + } +} + +contract Hello { + bool public world = true; + + function change() public { + world = false; + } +} + +contract ExcludeArtifacts is Test { + Excluded excluded; + + function setUp() public { + excluded = new Excluded(); + new Hello(); + new NoMutFunctions(); + } + + function excludeArtifacts() public returns (string[] memory) { + string[] memory abis = new string[](1); + abis[0] = "test/ExcludeArtifacts.t.sol:Excluded"; + return abis; + } + + function invariantShouldPass() public { + require(excluded.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifactSelectors2.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; +} + +contract Parent { + bool public should_be_true = true; + address public child; + + function change() public { + child = msg.sender; + should_be_true = false; + } + + function create() public { + new Child(); + } +} + +contract Child { + Parent parent; + bool public changed = false; + + constructor() { + parent = Parent(msg.sender); + } + + function change_parent() public { + parent.change(); + } + + function tracked_change_parent() public { + parent.change(); + } +} + +contract TargetArtifactSelectors2 is Test { + Parent parent; + + function setUp() public { + parent = new Parent(); + } + + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); + bytes4[] memory selectors_child = new bytes4[](1); + + selectors_child[0] = Child.change_parent.selector; + targets[0] = FuzzArtifactSelector( + "test/TargetArtifactSelectors2.t.sol:Child", selectors_child + ); + + bytes4[] memory selectors_parent = new bytes4[](1); + selectors_parent[0] = Parent.create.selector; + targets[1] = FuzzArtifactSelector( + "test/TargetArtifactSelectors2.t.sol:Parent", selectors_parent + ); + return targets; + } + + function invariantShouldFail() public { + if (!parent.should_be_true()) { + require(!Child(address(parent.child())).changed(), "should have not happened"); + } + require(parent.should_be_true() == true, "it's false"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifactSelectors.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; +} + +contract Hi { + bool public world = true; + + function no_change() public { + world = true; + } + + function change() public { + world = false; + } +} + +contract TargetArtifactSelectors is Test { + Hi hello; + + function setUp() public { + hello = new Hi(); + } + + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hi.no_change.selector; + targets[0] = + FuzzArtifactSelector("test/TargetArtifactSelectors.t.sol:Hi", selectors); + return targets; + } + + function invariantShouldPass() public { + require(hello.world() == true, "false world"); + } +} +"#, + ); + + prj.add_test( + "TargetArtifacts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; + +contract Targeted { + bool public world = true; + + function change() public { + world = false; + } +} + +contract Hello { + bool public world = true; + + function no_change() public {} +} + +contract TargetArtifacts is Test { + Targeted target1; + Targeted target2; + Hello hello; + + function setUp() public { + target1 = new Targeted(); + target2 = new Targeted(); + hello = new Hello(); + } + + function targetArtifacts() public returns (string[] memory) { + string[] memory abis = new string[](1); + abis[0] = "test/TargetArtifacts.t.sol:Targeted"; + return abis; + } + + function invariantShouldPass() public { + require(target2.world() == true || target1.world() == true || hello.world() == true, "false world"); + } + + function invariantShouldFail() public { + require(target2.world() == true || target1.world() == true, "false world"); + } +} +"#, + ); + + // Test ExcludeContracts + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "ExcludeContracts"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/ExcludeContracts.t.sol:ExcludeContracts +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test ExcludeSelectors + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "ExcludeSelectors"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/ExcludeSelectors.t.sol:ExcludeSelectors +[PASS] invariantFalseWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test ExcludeSenders + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "ExcludeSenders"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/ExcludeSenders.t.sol:ExcludeSenders +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test TargetContracts + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "TargetContracts"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/TargetContracts.t.sol:TargetContracts +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test TargetInterfaces (should fail) + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "TargetWorldInterfaces"])) + .failure() + .stdout_eq(str![[r#" +... +Ran 1 test for test/TargetInterfaces.t.sol:TargetWorldInterfaces +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/TargetInterfaces.t.sol:TargetWorldInterfaces +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); + + // Test TargetSelectors + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "TargetSelectors"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/TargetSelectors.t.sol:TargetSelectors +[PASS] invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test TargetSenders (should fail) + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "TargetSenders"])).failure().stdout_eq( + str![[r#" +... +Ran 1 test for test/TargetSenders.t.sol:TargetSenders +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/TargetSenders.t.sol:TargetSenders +[FAIL: false world] + [SEQUENCE] + invariantTrueWorld() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]], + ); + + // Test ExcludeArtifacts + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "ExcludeArtifacts"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/ExcludeArtifacts.t.sol:ExcludeArtifacts +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test TargetArtifactSelectors2 (should fail) + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "TargetArtifactSelectors2"])) + .failure() + .stdout_eq(str![[r#" +... +Ran 1 test for test/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2 +[FAIL: it's false] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +[STATS] + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2 +[FAIL: it's false] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); + + // Test TargetArtifactSelectors + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "^TargetArtifactSelectors$"])) + .success() + .stdout_eq(str![[r#" +... +Ran 1 test for test/TargetArtifactSelectors.t.sol:TargetArtifactSelectors +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test TargetArtifacts + assert_invariant(cmd.forge_fuse().args(["test", "--mc", "^TargetArtifacts$"])) + .failure() + .stdout_eq(str![[r#" +... +Ran 2 tests for test/TargetArtifacts.t.sol:TargetArtifacts +[FAIL: false world] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +[STATS] + +[PASS] invariantShouldPass() ([RUNS]) + +[STATS] + +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/TargetArtifacts.t.sol:TargetArtifacts +[FAIL: false world] + [SEQUENCE] + invariantShouldFail() ([RUNS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/5625 +// https://github.com/foundry-rs/foundry/issues/6166 +// `Target.wrongSelector` is not called when handler added as `targetContract` +// `Target.wrongSelector` is called (and test fails) when no `targetContract` set +forgetest!(fuzzed_selected_targets, |prj, cmd| { + prj.insert_vm(); + prj.insert_ds_test(); + prj.update_config(|config| { + config.invariant.depth = 10; + config.invariant.fail_on_revert = true; + }); + + prj.add_test( + "FuzzedTargetContracts.t.sol", + r#" +import { DSTest as Test } from "src/test.sol"; +import "src/Vm.sol"; + +contract Target { + uint256 count; + + function wrongSelector() external { + revert("wrong target selector called"); + } + + function goodSelector() external { + count++; + } +} + +contract Handler is Test { + function increment() public { + Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); + } +} + +contract ExplicitTargetContract is Test { + Vm constant vm = Vm(HEVM_ADDRESS); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function targetContracts() public view returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(handler); + return addrs; + } + + function invariant_explicit_target() public {} +} + +contract DynamicTargetContract is Test { + Vm constant vm = Vm(HEVM_ADDRESS); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function invariant_dynamic_targets() public {} +} +"#, + ); + + assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" +... +[PASS] invariant_explicit_target() ([RUNS]) +... +[FAIL: wrong target selector called] + [SEQUENCE] + invariant_dynamic_targets() ([RUNS]) +... + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/logs.rs b/crates/forge/tests/cli/test_cmd/logs.rs new file mode 100644 index 0000000000000..0dba7fa8baa7c --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/logs.rs @@ -0,0 +1,896 @@ +//! Tests for various logging functionality + +use foundry_test_utils::str; + +forgetest_init!(debug_logs, |prj, cmd| { + prj.add_test( + "DebugLogs.t.sol", + r#" +import "forge-std/Test.sol"; + +contract DebugLogsTest is Test { + constructor() { + emit log_uint(0); + } + + function setUp() public { + emit log_uint(1); + } + + function test1() public { + emit log_uint(2); + } + + function test2() public { + emit log_uint(3); + } + + function testRevertIfWithRevert() public { + Fails fails = new Fails(); + emit log_uint(4); + vm.expectRevert(); + fails.failure(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfWithRequire() public { + emit log_uint(5); + vm.expectRevert(); + require(false); + } + + function testLog() public { + emit log("Error: Assertion Failed"); + } + + function testLogs() public { + emit logs(bytes("abcd")); + } + + function testLogAddress() public { + emit log_address(address(1)); + } + + function testLogBytes32() public { + emit log_bytes32(bytes32("abcd")); + } + + function testLogInt() public { + emit log_int(int256(-31337)); + } + + function testLogBytes() public { + emit log_bytes(bytes("abcd")); + } + + function testLogString() public { + emit log_string("here"); + } + + function testLogNamedAddress() public { + emit log_named_address("address", address(1)); + } + + function testLogNamedBytes32() public { + emit log_named_bytes32("abcd", bytes32("abcd")); + } + + function testLogNamedDecimalInt() public { + emit log_named_decimal_int("amount", int256(-31337), uint256(18)); + } + + function testLogNamedDecimalUint() public { + emit log_named_decimal_uint("amount", uint256(1 ether), uint256(18)); + } + + function testLogNamedInt() public { + emit log_named_int("amount", int256(-31337)); + } + + function testLogNamedUint() public { + emit log_named_uint("amount", uint256(1 ether)); + } + + function testLogNamedBytes() public { + emit log_named_bytes("abcd", bytes("abcd")); + } + + function testLogNamedString() public { + emit log_named_string("key", "val"); + } +} + +contract Fails is Test { + function failure() public { + emit log_uint(100); + revert(); + } +} +"#, + ); + + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 19 tests for test/DebugLogs.t.sol:DebugLogsTest +[PASS] test1() ([GAS]) +Logs: + 0 + 1 + 2 + +[PASS] test2() ([GAS]) +Logs: + 0 + 1 + 3 + +[PASS] testLog() ([GAS]) +Logs: + 0 + 1 + Error: Assertion Failed + +[PASS] testLogAddress() ([GAS]) +Logs: + 0 + 1 + 0x0000000000000000000000000000000000000001 + +[PASS] testLogBytes() ([GAS]) +Logs: + 0 + 1 + 0x61626364 + +[PASS] testLogBytes32() ([GAS]) +Logs: + 0 + 1 + 0x6162636400000000000000000000000000000000000000000000000000000000 + +[PASS] testLogInt() ([GAS]) +Logs: + 0 + 1 + -31337 + +[PASS] testLogNamedAddress() ([GAS]) +Logs: + 0 + 1 + address: 0x0000000000000000000000000000000000000001 + +[PASS] testLogNamedBytes() ([GAS]) +Logs: + 0 + 1 + abcd: 0x61626364 + +[PASS] testLogNamedBytes32() ([GAS]) +Logs: + 0 + 1 + abcd: 0x6162636400000000000000000000000000000000000000000000000000000000 + +[PASS] testLogNamedDecimalInt() ([GAS]) +Logs: + 0 + 1 + amount: -0.000000000000031337 + +[PASS] testLogNamedDecimalUint() ([GAS]) +Logs: + 0 + 1 + amount: 1.000000000000000000 + +[PASS] testLogNamedInt() ([GAS]) +Logs: + 0 + 1 + amount: -31337 + +[PASS] testLogNamedString() ([GAS]) +Logs: + 0 + 1 + key: val + +[PASS] testLogNamedUint() ([GAS]) +Logs: + 0 + 1 + amount: 1000000000000000000 + +[PASS] testLogString() ([GAS]) +Logs: + 0 + 1 + here + +[PASS] testLogs() ([GAS]) +Logs: + 0 + 1 + 0x61626364 + +[PASS] testRevertIfWithRequire() ([GAS]) +Logs: + 0 + 1 + 5 + +[PASS] testRevertIfWithRevert() ([GAS]) +Logs: + 0 + 1 + 4 + 100 + +Suite result: ok. 19 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 19 tests passed, 0 failed, 0 skipped (19 total tests) + +"#]]); +}); + +forgetest_init!(hardhat_logs, |prj, cmd| { + prj.add_test( + "HardhatLogs.t.sol", + r#" +import "forge-std/console.sol"; + +contract HardhatLogsTest { + constructor() { + console.log("constructor"); + } + + string testStr; + int256 testInt; + uint256 testUint; + bool testBool; + address testAddr; + bytes testBytes; + + function setUp() public { + testStr = "test"; + testInt = -31337; + testUint = 1; + testBool = false; + testAddr = 0x0000000000000000000000000000000000000001; + testBytes = "a"; + } + + function testInts() public view { + console.log(uint256(0)); + console.log(uint256(1)); + console.log(uint256(2)); + console.log(uint256(3)); + } + + function testStrings() public view { + console.log("testStrings"); + } + + function testMisc() public view { + console.log("testMisc", address(1)); + console.log("testMisc", uint256(42)); + } + + function testConsoleLog() public view { + console.log(testStr); + } + + function testLogInt() public view { + console.logInt(testInt); + } + + function testLogUint() public view { + console.logUint(testUint); + } + + function testLogString() public view { + console.logString(testStr); + } + + function testLogBool() public view { + console.logBool(testBool); + } + + function testLogAddress() public view { + console.logAddress(testAddr); + } + + function testLogBytes() public view { + console.logBytes(testBytes); + } + + function testLogBytes1() public view { + console.logBytes1(bytes1(testBytes)); + } + + function testLogBytes2() public view { + console.logBytes2(bytes2(testBytes)); + } + + function testLogBytes3() public view { + console.logBytes3(bytes3(testBytes)); + } + + function testLogBytes4() public view { + console.logBytes4(bytes4(testBytes)); + } + + function testLogBytes5() public view { + console.logBytes5(bytes5(testBytes)); + } + + function testLogBytes6() public view { + console.logBytes6(bytes6(testBytes)); + } + + function testLogBytes7() public view { + console.logBytes7(bytes7(testBytes)); + } + + function testLogBytes8() public view { + console.logBytes8(bytes8(testBytes)); + } + + function testLogBytes9() public view { + console.logBytes9(bytes9(testBytes)); + } + + function testLogBytes10() public view { + console.logBytes10(bytes10(testBytes)); + } + + function testLogBytes11() public view { + console.logBytes11(bytes11(testBytes)); + } + + function testLogBytes12() public view { + console.logBytes12(bytes12(testBytes)); + } + + function testLogBytes13() public view { + console.logBytes13(bytes13(testBytes)); + } + + function testLogBytes14() public view { + console.logBytes14(bytes14(testBytes)); + } + + function testLogBytes15() public view { + console.logBytes15(bytes15(testBytes)); + } + + function testLogBytes16() public view { + console.logBytes16(bytes16(testBytes)); + } + + function testLogBytes17() public view { + console.logBytes17(bytes17(testBytes)); + } + + function testLogBytes18() public view { + console.logBytes18(bytes18(testBytes)); + } + + function testLogBytes19() public view { + console.logBytes19(bytes19(testBytes)); + } + + function testLogBytes20() public view { + console.logBytes20(bytes20(testBytes)); + } + + function testLogBytes21() public view { + console.logBytes21(bytes21(testBytes)); + } + + function testLogBytes22() public view { + console.logBytes22(bytes22(testBytes)); + } + + function testLogBytes23() public view { + console.logBytes23(bytes23(testBytes)); + } + + function testLogBytes24() public view { + console.logBytes24(bytes24(testBytes)); + } + + function testLogBytes25() public view { + console.logBytes25(bytes25(testBytes)); + } + + function testLogBytes26() public view { + console.logBytes26(bytes26(testBytes)); + } + + function testLogBytes27() public view { + console.logBytes27(bytes27(testBytes)); + } + + function testLogBytes28() public view { + console.logBytes28(bytes28(testBytes)); + } + + function testLogBytes29() public view { + console.logBytes29(bytes29(testBytes)); + } + + function testLogBytes30() public view { + console.logBytes30(bytes30(testBytes)); + } + + function testLogBytes31() public view { + console.logBytes31(bytes31(testBytes)); + } + + function testLogBytes32() public view { + console.logBytes32(bytes32(testBytes)); + } + + function testConsoleLogUint() public view { + console.log(testUint); + } + + function testConsoleLogString() public view { + console.log(testStr); + } + + function testConsoleLogBool() public view { + console.log(testBool); + } + + function testConsoleLogAddress() public view { + console.log(testAddr); + } + + function testConsoleLogFormatString() public view { + console.log("formatted log str=%s", testStr); + } + + function testConsoleLogFormatUint() public view { + console.log("formatted log uint=%s", testUint); + } + + function testConsoleLogFormatAddress() public view { + console.log("formatted log addr=%s", testAddr); + } + + function testConsoleLogFormatMulti() public view { + console.log("formatted log str=%s uint=%d", testStr, testUint); + } + + function testConsoleLogFormatEscape() public view { + console.log("formatted log %% %s", testStr); + } + + function testConsoleLogFormatSpill() public view { + console.log("formatted log %s", testStr, testUint); + } +} +"#, + ); + + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +... +Ran 52 tests for test/HardhatLogs.t.sol:HardhatLogsTest +[PASS] testConsoleLog() ([GAS]) +Logs: + constructor + test + +[PASS] testConsoleLogAddress() ([GAS]) +Logs: + constructor + 0x0000000000000000000000000000000000000001 + +[PASS] testConsoleLogBool() ([GAS]) +Logs: + constructor + false + +[PASS] testConsoleLogFormatAddress() ([GAS]) +Logs: + constructor + formatted log addr=0x0000000000000000000000000000000000000001 + +[PASS] testConsoleLogFormatEscape() ([GAS]) +Logs: + constructor + formatted log % test + +[PASS] testConsoleLogFormatMulti() ([GAS]) +Logs: + constructor + formatted log str=test uint=1 + +[PASS] testConsoleLogFormatSpill() ([GAS]) +Logs: + constructor + formatted log test 1 + +[PASS] testConsoleLogFormatString() ([GAS]) +Logs: + constructor + formatted log str=test + +[PASS] testConsoleLogFormatUint() ([GAS]) +Logs: + constructor + formatted log uint=1 + +[PASS] testConsoleLogString() ([GAS]) +Logs: + constructor + test + +[PASS] testConsoleLogUint() ([GAS]) +Logs: + constructor + 1 + +[PASS] testInts() ([GAS]) +Logs: + constructor + 0 + 1 + 2 + 3 + +[PASS] testLogAddress() ([GAS]) +Logs: + constructor + 0x0000000000000000000000000000000000000001 + +[PASS] testLogBool() ([GAS]) +Logs: + constructor + false + +[PASS] testLogBytes() ([GAS]) +Logs: + constructor + 0x61 + +[PASS] testLogBytes1() ([GAS]) +Logs: + constructor + 0x61 + +[PASS] testLogBytes10() ([GAS]) +Logs: + constructor + 0x61000000000000000000 + +[PASS] testLogBytes11() ([GAS]) +Logs: + constructor + 0x6100000000000000000000 + +[PASS] testLogBytes12() ([GAS]) +Logs: + constructor + 0x610000000000000000000000 + +[PASS] testLogBytes13() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000 + +[PASS] testLogBytes14() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000 + +[PASS] testLogBytes15() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000 + +[PASS] testLogBytes16() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000 + +[PASS] testLogBytes17() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000 + +[PASS] testLogBytes18() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000 + +[PASS] testLogBytes19() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000 + +[PASS] testLogBytes2() ([GAS]) +Logs: + constructor + 0x6100 + +[PASS] testLogBytes20() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000 + +[PASS] testLogBytes21() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000 + +[PASS] testLogBytes22() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000 + +[PASS] testLogBytes23() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000 + +[PASS] testLogBytes24() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000 + +[PASS] testLogBytes25() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes26() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes27() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes28() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes29() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes3() ([GAS]) +Logs: + constructor + 0x610000 + +[PASS] testLogBytes30() ([GAS]) +Logs: + constructor + 0x610000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes31() ([GAS]) +Logs: + constructor + 0x61000000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes32() ([GAS]) +Logs: + constructor + 0x6100000000000000000000000000000000000000000000000000000000000000 + +[PASS] testLogBytes4() ([GAS]) +Logs: + constructor + 0x61000000 + +[PASS] testLogBytes5() ([GAS]) +Logs: + constructor + 0x6100000000 + +[PASS] testLogBytes6() ([GAS]) +Logs: + constructor + 0x610000000000 + +[PASS] testLogBytes7() ([GAS]) +Logs: + constructor + 0x61000000000000 + +[PASS] testLogBytes8() ([GAS]) +Logs: + constructor + 0x6100000000000000 + +[PASS] testLogBytes9() ([GAS]) +Logs: + constructor + 0x610000000000000000 + +[PASS] testLogInt() ([GAS]) +Logs: + constructor + -31337 + +[PASS] testLogString() ([GAS]) +Logs: + constructor + test + +[PASS] testLogUint() ([GAS]) +Logs: + constructor + 1 + +[PASS] testMisc() ([GAS]) +Logs: + constructor + testMisc 0x0000000000000000000000000000000000000001 + testMisc 42 + +[PASS] testStrings() ([GAS]) +Logs: + constructor + testStrings + +Suite result: ok. 52 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 52 tests passed, 0 failed, 0 skipped (52 total tests) + +"#]]); +}); + +forgetest_init!(test_can_run_with_live_logs_flag, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } +} + "#, + ); + + cmd.forge_fuse() + .args(["test", "--live-logs", "--match-test", "test1"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test 1 + +Ran 1 test for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(test_can_run_with_live_logs_config, |prj, cmd| { + prj.update_config(|config| { + config.live_logs = true; + }); + + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } +} + "#, + ); + + cmd.forge_fuse().args(["test", "--match-test", "test1"]).assert_success().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test 1 + +Ran 1 test for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"# + ]]); +}); + +forgetest_init!(test_can_run_with_live_logs_flag_race_condition, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract Foo is Test { + function setUp() pure public { + console.log("Setup"); + } + + function test1() pure public { + console.log("Test 1"); + } + + function test2() pure public { + console.log("Test 2"); + } +} + "#, + ); + + // Two threads. Inconsistent printing order. + cmd.forge_fuse().args(["test", "--live-logs", "--threads", "2"]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Setup +Test [..] +Test [..] + +Ran 2 tests for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +[PASS] test2() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]], + ); + + // Single thread. Deterministic printing order. + + for _ in 0..10 { + cmd.forge_fuse() + .args(["test", "--live-logs", "--threads", "1"]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped +Setup +Test 1 +Test 2 + +Ran 2 tests for test/Foo.t.sol:Foo +[PASS] test1() ([GAS]) +[PASS] test2() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + } +}); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd/mod.rs similarity index 76% rename from crates/forge/tests/cli/test_cmd.rs rename to crates/forge/tests/cli/test_cmd/mod.rs index af4b2aed7d0e7..14f49863815cd 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -1,13 +1,92 @@ //! Contains various tests for `forge test`. use alloy_primitives::U256; -use anvil::{spawn, NodeConfig}; +use anvil::{NodeConfig, spawn}; use foundry_test_utils::{ - rpc, str, - util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}, + TestCommand, + rpc::{self, rpc_endpoints}, + str, + util::{OTHER_SOLC_VERSION, OutputExt, SOLC_VERSION}, }; use similar_asserts::assert_eq; -use std::{path::PathBuf, str::FromStr}; +use std::{io::Write, path::PathBuf, str::FromStr}; + +mod core; +mod fuzz; +mod invariant; +mod logs; +mod repros; +mod spec; +mod table; +mod trace; + +/// Sets up a [`TestCommand`] to run `forge test` on the `/testdata` directory with RPC +/// endpoints written to a `.env` file. +fn setup_testdata_cmd(cmd: &mut TestCommand) { + let testdata = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap(); + cmd.current_dir(&testdata); + + let mut dotenv = std::fs::File::create(testdata.join(".env")).unwrap(); + for (name, endpoint) in rpc_endpoints().iter() { + if let Some(url) = endpoint.endpoint.as_url() { + let key = format!("RPC_{}", name.to_uppercase()); + writeln!(dotenv, "{key}={url}").unwrap(); + } + } + drop(dotenv); +} + +/// Contracts excluded from the main `testdata` run because they depend on flaky external RPCs. +/// These are run separately by the `flaky_testdata` test below. +/// Format: pipe-separated regex alternation, e.g. `"Foo|Bar|Baz"`. +const FLAKY_TESTDATA_CONTRACTS: &str = "Issue4640Test"; + +// Run `forge test` on `/testdata`. +forgetest!(testdata, |_prj, cmd| { + setup_testdata_cmd(&mut cmd); + + let mut args = vec!["test"]; + let nmc_isolate = format!( + "--nmc=(LastCallGasDefaultTest|MockFunctionTest|WithSeed|StateDiff|GetStorageSlotsTest|RecordAccount|{FLAKY_TESTDATA_CONTRACTS})", + ); + let nmc_default = format!("--nmc=({FLAKY_TESTDATA_CONTRACTS})"); + if cfg!(feature = "isolate-by-default") { + args.push(&nmc_isolate); + } else { + args.push(&nmc_default); + } + + let orig_assert = cmd.args(args).assert(); + if orig_assert.get_output().status.success() { + return; + } + let stdout = orig_assert.get_output().stdout_lossy(); + if let Some(i) = stdout.rfind("Suite result:") { + test_debug!("--- short stdout ---\n\n{}\n\n---", &stdout[i..]); + } + + // Retry failed tests. + cmd.args(["--rerun"]); + let n = 3; + for i in 1..=n { + test_debug!("retrying failed tests... ({i}/{n})"); + let assert = cmd.assert(); + if assert.get_output().status.success() { + return; + } + } + + orig_assert.success(); +}); + +// Run flaky testdata contracts excluded from the main `testdata` test above. +// Picked up by the nightly `test-flaky` workflow via `cargo nextest run --profile flaky`. +forgetest!(flaky_testdata, |_prj, cmd| { + setup_testdata_cmd(&mut cmd); + let mc = format!("--mc=({FLAKY_TESTDATA_CONTRACTS})"); + cmd.args(["test", &mc]).assert_success(); +}); // tests that test filters are handled correctly forgetest!(can_set_filter_values, |prj, cmd| { @@ -36,56 +115,39 @@ forgetest!(can_set_filter_values, |prj, cmd| { assert_eq!(config.coverage_pattern_inverse, None); }); -// tests that warning is displayed when there are no tests in project +fn dummy_test_filter(cmd: &mut TestCommand) { + cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]); + cmd.args(["--match-contract", "TestC.*", "--no-match-contract", "TestD.*"]); + cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]); +} + +// tests that a warning is displayed when there are no tests in project, regardless of filters forgetest!(warn_no_tests, |prj, cmd| { + // Must add at least one source to not fail earlier. prj.add_source( "dummy", r" contract Dummy {} ", - ) - .unwrap(); - // set up command - cmd.args(["test"]); + ); - // run command and assert - cmd.assert_failure().stdout_eq(str![[r#" -No tests found in project! Forge looks for functions that starts with `test`. + cmd.arg("test").assert_success().stdout_eq(str![[r#" +... +No tests found in project! Forge looks for functions that start with `test` "#]]); -}); - -// tests that warning is displayed with pattern when no tests match -forgetest!(warn_no_tests_match, |prj, cmd| { - prj.add_source( - "dummy", - r" -contract Dummy {} -", - ) - .unwrap(); - - // set up command - cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]); - cmd.args(["--match-contract", "TestC.*", "--no-match-contract", "TestD.*"]); - cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]); - // run command and assert - cmd.assert_failure().stdout_eq(str![[r#" -No tests match the provided pattern: - match-test: `testA.*` - no-match-test: `testB.*` - match-contract: `TestC.*` - no-match-contract: `TestD.*` - match-path: `*TestE*` - no-match-path: `*TestF*` + cmd.forge_fuse(); + dummy_test_filter(&mut cmd); + cmd.assert_success().stdout_eq(str![[r#" +... +No tests found in project! Forge looks for functions that start with `test` "#]]); }); -// tests that suggestion is provided with pattern when no tests match +// tests that a warning is displayed if there are tests but none match a non-empty filter forgetest!(suggest_when_no_tests_match, |prj, cmd| { - // set up project prj.add_source( "TestE.t.sol", r" @@ -94,17 +156,11 @@ contract TestC { } } ", - ) - .unwrap(); - - // set up command - cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]); - cmd.args(["--match-contract", "TestC.*", "--no-match-contract", "TestD.*"]); - cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]); + ); - // run command and assert - cmd.assert_failure().stdout_eq(str![[r#" -No tests match the provided pattern: + dummy_test_filter(&mut cmd); + cmd.assert_success().stderr_eq(str![[r#" +Warning: no tests match the provided pattern: match-test: `testA.*` no-match-test: `testB.*` match-contract: `TestC.*` @@ -131,8 +187,7 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -164,12 +219,10 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] +... Compiler run successful! Ran 1 test for src/ATest.t.sol:ATest @@ -195,8 +248,7 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); prj.add_source( "FailTest.t.sol", @@ -208,8 +260,7 @@ contract FailTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--match-path", "*src/ATest.t.sol"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -239,8 +290,7 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ); prj.add_source( "FailTest.t.sol", @@ -252,8 +302,7 @@ contract FailTest is DSTest { } } "#, - ) - .unwrap(); + ); let test_path = prj.root().join("src/ATest.t.sol"); let test_path = test_path.to_string_lossy(); @@ -293,28 +342,29 @@ contract SimpleContractTest is DSTest { } "#; +#[cfg(not(feature = "isolate-by-default"))] forgetest!(can_run_test_with_json_output_verbose, |prj, cmd| { prj.insert_ds_test(); prj.insert_console(); - prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT); // Assert that with verbose output the json output includes the traces - cmd.args(["test", "-vvv", "--json"]) + cmd.args(["test", "-vvvvv", "--json"]) .assert_success() - .stdout_eq(file!["../fixtures/SimpleContractTestVerbose.json": Json]); + .stdout_eq(file!["../../fixtures/SimpleContractTestVerbose.json": Json]); }); forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| { prj.insert_ds_test(); prj.insert_console(); - prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT); // Assert that without verbose output the json output does not include the traces cmd.args(["test", "--json"]) .assert_success() - .stdout_eq(file!["../fixtures/SimpleContractTestNonVerbose.json": Json]); + .stdout_eq(file!["../../fixtures/SimpleContractTestNonVerbose.json": Json]); }); // tests that `forge test` will pick up tests that are stored in the `test = ` config value @@ -337,8 +387,7 @@ contract MyTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -357,6 +406,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // checks that forge test repeatedly produces the same output #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_test_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.arg("test").assert_success().stdout_eq(str![[r#" @@ -407,8 +457,7 @@ contract ContractTest is DSTest { } } "#, - ) - .unwrap(); + ); // pin version prj.update_config(|config| { @@ -450,8 +499,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that libraries are handled correctly in multiforking mode #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source( "Contract.sol", r" @@ -469,8 +516,7 @@ contract Contract { } } ", - ) - .unwrap(); + ); let endpoint = rpc::next_http_archive_rpc_url(); @@ -491,8 +537,7 @@ contract ContractTest is Test { } "# .replace("", &endpoint), - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -519,31 +564,99 @@ contract FailingTest is Test { "#; forgetest_init!(exit_code_error_on_fail_fast, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("failing_test", FAILING_TEST).unwrap(); + prj.add_source("failing_test", FAILING_TEST); - // set up command cmd.args(["test", "--fail-fast"]); - // run command and assert error exit code cmd.assert_empty_stderr(); }); forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { - prj.wipe_contracts(); - - prj.add_source("failing_test", FAILING_TEST).unwrap(); - // set up command + prj.add_source("failing_test", FAILING_TEST); cmd.args(["test", "--fail-fast", "--json"]); - // run command and assert error exit code cmd.assert_empty_stderr(); }); +// Verify that --show-progress doesn't stop tests after first failure +forgetest_init!(show_progress_runs_all_tests, |prj, cmd| { + prj.add_test( + "MultiTest.t.sol", + r#" +import "forge-std/Test.sol"; + +contract MultiTest is Test { + function test_1_Fail() public { + assertTrue(false); + } + + function test_2_Pass() public { + assertTrue(true); + } + + function test_3_Pass() public { + assertTrue(true); + } +} +"#, + ); + + // With --show-progress, all 3 tests should run despite first one failing + let output = cmd.args(["test", "--show-progress", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&output.get_output().stdout); + + // Verify all 3 tests were executed + assert!(stdout.contains("test_1_Fail"), "test_1_Fail should run"); + assert!(stdout.contains("test_2_Pass"), "test_2_Pass should run"); + assert!(stdout.contains("test_3_Pass"), "test_3_Pass should run"); + assert!(stdout.contains("2 passed; 1 failed"), "Should show 2 passed and 1 failed"); +}); + +// Verify that --show-progress with --fail-fast DOES stop after first failure +forgetest_init!(show_progress_with_fail_fast_exits_early, |prj, cmd| { + prj.add_test( + "MultiTest.t.sol", + r#" +import "forge-std/Test.sol"; + +contract MultiTest is Test { + function test_1_Fail() public { + assertTrue(false); + } + + function test_2_SlowPass() public { + vm.sleep(60000); // Sleep for 60 seconds to ensure fail-fast stops before this completes + assertTrue(true); + } + + function test_3_SlowPass() public { + vm.sleep(60000); // Sleep for 60 seconds to ensure fail-fast stops before this completes + assertTrue(true); + } +} +"#, + ); + + // With both --show-progress and --fail-fast, should stop after first failure + let output = cmd.args(["test", "--show-progress", "--fail-fast", "-j1"]).assert_failure(); + let stdout = String::from_utf8_lossy(&output.get_output().stdout); + + // Verify first test ran and failed + assert!(stdout.contains("test_1_Fail"), "test_1_Fail should run"); + + // With -j1 (sequential execution) and fail-fast, the slow tests should not run + // since test_1_Fail will fail first + let slow_tests_count = (if stdout.contains("test_2_SlowPass") { 1 } else { 0 }) + + (if stdout.contains("test_3_SlowPass") { 1 } else { 0 }); + + assert!( + slow_tests_count < 2, + "With --fail-fast and sequential execution, not all slow tests should run after first failure" + ); +}); + // https://github.com/foundry-rs/foundry/pull/6531 forgetest_init!(fork_traces, |prj, cmd| { - prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( @@ -563,8 +676,7 @@ contract USDTCallingTest is Test { } "# .replace("", &endpoint), - ) - .unwrap(); + ); cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -588,10 +700,35 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +// Validates BPO1 blob gas price calculation during fork transaction replay. +// Block 24127158 has a blob tx at index 0, target tx at index 1. +// Forking at the target tx replays the blob tx with correct BPO1 blob base fee calculation. +forgetest_init!(fork_tx_replay_bpo1_blob_base_fee, |prj, cmd| { + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "BlobFork.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +contract BlobForkTest is Test { + function test_fork_with_blob_replay() public { + // Fork at tx index 1 in block 24127158, which replays blob tx at index 0 + bytes32 txHash = 0xa0f349b16e0f338ee760a9954ff5dbf2a402cff3320f3fe2c3755aee8babc335; + vm.createSelectFork("", txHash); + // If we get here, blob tx replay succeeded + assertTrue(true); + } +} + "# + .replace("", &endpoint), + ); + + cmd.args(["test", "-vvvv"]).assert_success(); +}); + // https://github.com/foundry-rs/foundry/issues/6579 forgetest_init!(include_custom_types_in_traces, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -609,10 +746,9 @@ contract CustomTypesTest is Test { } } "#, - ) - .unwrap(); + ); - cmd.args(["test", "-vvvv"]).assert_failure().stdout_eq(str![[r#" + cmd.args(["test", "-vvvvv"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! @@ -623,6 +759,9 @@ Traces: [247] CustomTypesTest::testErr() └─ ← [Revert] PoolNotInitialized() +Backtrace: + at CustomTypesTest.testErr (test/Contract.t.sol:[..]:[..]) + [PASS] testEvent() ([GAS]) Traces: [1524] CustomTypesTest::testEvent() @@ -639,12 +778,12 @@ Encountered 1 failing test in test/Contract.t.sol:CustomTypesTest Encountered a total of 1 failing tests, 1 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -688,8 +827,7 @@ contract TransientTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success(); }); @@ -698,8 +836,6 @@ forgetest_init!( #[ignore = "Too slow"] can_disable_block_gas_limit, |prj, cmd| { - prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( @@ -727,8 +863,7 @@ contract GasLimitTest is Test { } "# .replace("", &endpoint), - ) - .unwrap(); + ); cmd.args(["test", "-vvvv", "--isolate", "--disable-block-gas-limit"]).assert_success(); } @@ -742,16 +877,13 @@ contract Dummy { function testDummy() public {} } ", - ) - .unwrap(); + ); cmd.args(["test", "--match-path", "src/dummy.sol"]); cmd.assert_success(); }); forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { - prj.wipe_contracts(); - // deterministic test so we always have 54 runs until test fails with overflow prj.update_config(|config| { config.fuzz.runs = 256; @@ -783,8 +915,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs) cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" @@ -793,22 +924,25 @@ contract CounterTest is Test { Compiler run successful! Ran 1 test for test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS]) Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); forgetest_init!(should_exit_early_on_invariant_failure, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "CounterInvariant.t.sol", r#" @@ -834,8 +968,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // make sure invariant test exit early with 0 runs cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" @@ -855,11 +988,14 @@ Encountered 1 failing test in test/CounterInvariant.t.sol:CounterTest Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + +[SEED] (use `--fuzz-seed` to reproduce) + "#]]); }); forgetest_init!(should_replay_failures_only, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "ReplayFailures.t.sol", r#" @@ -883,8 +1019,7 @@ contract ReplayFailuresTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -907,6 +1042,8 @@ Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest Encountered a total of 2 failing tests, 2 tests succeeded +Tip: Run `forge test --rerun` to retry only the 2 failed tests + "#]]); // Test failure filter should be persisted. @@ -930,6 +1067,8 @@ Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest Encountered a total of 2 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 2 failed tests + "#]]); }); @@ -949,8 +1088,7 @@ contract SetupFailureTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).assert_success(); // Test failure filter should not be persisted if `setUp` failed. @@ -959,8 +1097,6 @@ contract SetupFailureTest is Test { // https://github.com/foundry-rs/foundry/issues/7530 forgetest_init!(should_show_precompile_labels, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -972,7 +1108,7 @@ contract PrecompileLabelsTest is Test { vm.deal(address(0x000000000000000000636F6e736F6c652e6c6f67), 1 ether); vm.deal(address(0x4e59b44847b379578588920cA78FbF26c0B4956C), 1 ether); vm.deal(address(0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38), 1 ether); - vm.deal(address(0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84), 1 ether); + vm.deal(address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496), 1 ether); vm.deal(address(1), 1 ether); vm.deal(address(2), 1 ether); vm.deal(address(3), 1 ether); @@ -986,8 +1122,7 @@ contract PrecompileLabelsTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1006,7 +1141,7 @@ Traces: │ └─ ← [Return] ├─ [0] VM::deal(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], 1000000000000000000 [1e18]) │ └─ ← [Return] - ├─ [0] VM::deal(DefaultTestContract: [0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84], 1000000000000000000 [1e18]) + ├─ [0] VM::deal(PrecompileLabelsTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1000000000000000000 [1e18]) │ └─ ← [Return] ├─ [0] VM::deal(ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000 [1e18]) │ └─ ← [Return] @@ -1040,8 +1175,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with config `show_logs: true` for fuzz tests will // display `console.log` info forgetest_init!(should_show_logs_when_fuzz_test, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1061,8 +1194,7 @@ contract ContractFuzz is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1085,8 +1217,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with inline config `show_logs = true` for fuzz tests will // display `console.log` info forgetest_init!(should_show_logs_when_fuzz_test_inline_config, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1106,8 +1236,7 @@ contract ContractFuzz is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1127,11 +1256,9 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); -// tests that `forge test` with config `show_logs: false` for fuzz tests will not display -// `console.log` info +// tests that `forge test` with config `show_logs: false` for fuzz tests will +// still display `console.log` from the last run at verbosity >= 2 (issue #11039) forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1151,8 +1278,8 @@ forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| { } } "#, - ) - .unwrap(); + ); + // At verbosity >= 2, logs from the last run should be shown even when show_logs is false cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1160,6 +1287,9 @@ Compiler run successful! Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz [PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Logs: + inside fuzz test, x is: [..] + Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) @@ -1167,11 +1297,9 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); -// tests that `forge test` with inline config `show_logs = false` for fuzz tests will not -// display `console.log` info +// tests that `forge test` with inline config `show_logs = false` for fuzz tests will +// still display `console.log` from the last run at verbosity >= 2 (issue #11039) forgetest_init!(should_not_show_logs_when_fuzz_test_inline_config, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1191,8 +1319,8 @@ contract ContractFuzz is Test { } } "#, - ) - .unwrap(); + ); + // At verbosity >= 2, logs from the last run should be shown even when show_logs is false cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1200,6 +1328,9 @@ Compiler run successful! Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz [PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Logs: + inside fuzz test, x is: [..] + Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) @@ -1210,7 +1341,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests internal functions trace #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(internal_functions_trace, |prj, cmd| { - prj.wipe_contracts(); prj.clear(); prj.add_test( @@ -1250,8 +1380,7 @@ contract SimpleContractTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -1285,7 +1414,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests internal functions trace with memory decoding #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(internal_functions_trace_memory, |prj, cmd| { - prj.wipe_contracts(); prj.clear(); prj.add_test( @@ -1313,16 +1441,15 @@ contract SimpleContractTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#" ... Traces: [..] SimpleContractTest::test() - ├─ [370554] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f - │ └─ ← [Return] 1737 bytes of code - ├─ [2511] SimpleContract::setStr("new value") - │ ├─ [1588] SimpleContract::_setStr("new value") + ├─ [..] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] [..] bytes of code + ├─ [..] SimpleContract::setStr("new value") + │ ├─ [..] SimpleContract::_setStr("new value") │ │ └─ ← "initial value" │ └─ ← [Stop] └─ ← [Stop] @@ -1332,7 +1459,6 @@ Traces: // tests that `forge test` with a seed produces deterministic random values for uint and addresses. forgetest_init!(deterministic_randomness_with_seed, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "DeterministicRandomnessTest.t.sol", r#" @@ -1361,8 +1487,7 @@ contract DeterministicRandomnessTest is Test { } } "#, - ) - .unwrap(); + ); // Extracts the test result section from the DeterministicRandomnessTest contract output. fn extract_test_result(out: &str) -> &str { @@ -1414,8 +1539,6 @@ contract DeterministicRandomnessTest is Test { // Tests that `pauseGasMetering` used at the end of test does not produce meaningless values. // https://github.com/foundry-rs/foundry/issues/5491 forgetest_init!(gas_metering_pause_last_call, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1446,8 +1569,7 @@ contract ATest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -1461,8 +1583,6 @@ contract ATest is Test { // https://github.com/foundry-rs/foundry/issues/5564 forgetest_init!(gas_metering_expect_revert, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1480,8 +1600,7 @@ contract ATest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1499,8 +1618,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/4523 forgetest_init!(gas_metering_gasleft, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1532,8 +1649,7 @@ contract ATest is Test { } } "#, - ) - .unwrap(); + ); // Log and test gas cost should be similar. cmd.args(["test", "-vvvv"]).with_no_redact().assert_success().stdout_eq(str![[r#" @@ -1561,8 +1677,6 @@ Traces: // https://github.com/foundry-rs/foundry/issues/4370 forgetest_init!(pause_gas_metering_with_delete, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1577,12 +1691,11 @@ contract ATest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... -[PASS] test_negativeGas() (gas: 0) +[PASS] test_negativeGas() (gas: 96) ... "#]]); }); @@ -1590,7 +1703,6 @@ contract ATest is Test { // tests `pauseTracing` and `resumeTracing` functions #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(pause_tracing, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1634,8 +1746,7 @@ contract PauseTracingTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" ... Traces: @@ -1675,7 +1786,6 @@ Traces: }); forgetest_init!(gas_metering_reset, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1772,32 +1882,38 @@ contract ATest is DSTest { b = new B(); vm.resetGasMetering(); } + + // https://github.com/foundry-rs/foundry/issues/12474 + function testMemoryOnReset(uint8[1] memory x) public { + uint8[1] memory z; + z[0] = x[0]; + assertEq(z[0], x[0]); + vm.resetGasMetering(); + assertEq(x[0], z[0]); + } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... -[PASS] testResetGas() (gas: 40) -[PASS] testResetGas1() (gas: 40) -[PASS] testResetGas2() (gas: 40) +[PASS] testResetGas() (gas: 96) +[PASS] testResetGas1() (gas: 96) +[PASS] testResetGas2() (gas: 96) [PASS] testResetGas3() (gas: [..]) [PASS] testResetGas4() (gas: [..]) -[PASS] testResetGas5() (gas: 40) -[PASS] testResetGas6() (gas: 40) -[PASS] testResetGas7() (gas: 49) +[PASS] testResetGas5() (gas: 96) +[PASS] testResetGas6() (gas: 96) +[PASS] testResetGas7() (gas: 96) [PASS] testResetGas8() (gas: [..]) -[PASS] testResetGas9() (gas: 40) -[PASS] testResetNegativeGas() (gas: 0) +[PASS] testResetGas9() (gas: 96) +[PASS] testResetNegativeGas() (gas: 96) ... "#]]); }); // https://github.com/foundry-rs/foundry/issues/8705 forgetest_init!(test_expect_revert_decode, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Counter.t.sol", r#" @@ -1829,8 +1945,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" ... @@ -1842,7 +1957,6 @@ contract CounterTest is Test { // Tests that `expectPartialRevert` cheatcode partially matches revert data. forgetest_init!(test_expect_partial_revert, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1877,8 +1991,7 @@ contract CounterTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" ... @@ -1890,7 +2003,6 @@ contract CounterTest is DSTest { }); forgetest_init!(test_assume_no_revert, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1960,8 +2072,7 @@ contract CounterRevertTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1974,7 +2085,6 @@ contract CounterRevertTest is DSTest { }); forgetest_init!(skip_output, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -2010,8 +2120,7 @@ forgetest_init!(skip_output, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.arg("test").assert_success().stdout_eq(str![[r#" ... @@ -2054,8 +2163,7 @@ contract SkipCounterSetup is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mc", "SkipCounterSetup"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -2072,7 +2180,6 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 1 skipped (1 total tests) }); forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -2111,8 +2218,7 @@ forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--junit"]).assert_failure().stdout_eq(str![[r#" @@ -2152,7 +2258,6 @@ forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { }); forgetest_init!(should_generate_junit_xml_report_with_logs, |prj, cmd| { - prj.wipe_contracts(); prj.add_source( "JunitReportTest.t.sol", r#" @@ -2166,8 +2271,7 @@ contract JunitReportTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--junit", "-vvvv"]).assert_success().stdout_eq(str![[r#" @@ -2225,8 +2329,7 @@ forgetest_init!( } } "#, - ) - .unwrap(); + ); // Tests deprecated cheatcode warning for unit tests. cmd.args(["test", "--mc", "DeprecatedCheatcodeTest"]).assert_success().stderr_eq(str![[ @@ -2260,6 +2363,7 @@ Warning: the following cheatcode(s) are deprecated and will be removed in future ); forgetest_init!(requires_single_test, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--debug"]).assert_failure().stderr_eq(str![[r#" Error: 2 tests matched your criteria, but exactly 1 test must match in order to run the debugger. @@ -2317,8 +2421,7 @@ contract FooTest { } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "testWalletScript", "-vvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -2337,6 +2440,7 @@ Logs: // forgetest_init!(metadata_bytecode_traces, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "ParentProxy.sol", r#" @@ -2352,8 +2456,7 @@ abstract contract ParentProxy { } } "#, - ) - .unwrap(); + ); prj.add_source( "Proxy.sol", r#" @@ -2366,8 +2469,7 @@ contract Proxy is ParentProxy { {} } "#, - ) - .unwrap(); + ); prj.add_test( "MetadataTraceTest.t.sol", @@ -2384,8 +2486,7 @@ contract MetadataTraceTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_proxy_trace", "-vvvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -2443,8 +2544,7 @@ contract Dummy { function testDummy() public {} } ", - ) - .unwrap(); + ); let dump_path = prj.root().join("dump.json"); @@ -2456,8 +2556,8 @@ contract Dummy { forgetest_init!(test_assume_no_revert_with_data, |prj, cmd| { prj.update_config(|config| { - config.fuzz.runs = 60; - config.fuzz.seed = Some(U256::from(100)); + config.fuzz.seed = Some(U256::from(111)); + config.fuzz.dictionary.max_fuzz_dictionary_literals = 0; }); prj.add_source( @@ -2476,6 +2576,7 @@ interface Vm { function assumeNoRevert(PotentialRevert calldata revertData) external pure; function assumeNoRevert(PotentialRevert[] calldata revertData) external pure; function expectRevert(bytes4 revertData, uint64 count) external; + function assume(bool condition) external pure; } contract ReverterB { @@ -2582,6 +2683,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` assumptions are cleared after the first non-cheatcode external call function testMultipleAssumesClearAfterCall_fails(uint256 x) public view { + _vm.assume(x != 3); Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(0)}); revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)}); @@ -2623,19 +2725,18 @@ contract ReverterTest is Test { } }"#, - ) - .unwrap(); + ); cmd.args(["test", "--mc", "ReverterTest"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! Ran 8 tests for src/AssumeNoRevertTest.t.sol:ReverterTest -[FAIL: expected 0 reverts with reason: 0x92fa317b, but got one; counterexample: [..]] testAssumeThenExpectCountZeroFails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: call reverted with 'FOUNDRY::ASSUME' when it was expected not to revert; counterexample: [..] testAssumeThenExpectCountZeroFails(uint256) (runs: [..], [AVG_GAS]) [FAIL: MyRevert(); counterexample: calldata=[..]] testAssumeWithReverter_fails(uint256) (runs: [..], [AVG_GAS]) [FAIL: RevertWithData(2); counterexample: [..]] testAssume_wrongData_fails(uint256) (runs: [..], [AVG_GAS]) [FAIL: MyRevert(); counterexample: [..]] testAssume_wrongSelector_fails(uint256) (runs: [..], [AVG_GAS]) -[FAIL: expected 0 reverts with reason: 0x92fa317b, but got one; counterexample: [..]] testExpectCountZeroThenAssumeFails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: call reverted with 'FOUNDRY::ASSUME' when it was expected not to revert; counterexample: [..]] testExpectCountZeroThenAssumeFails(uint256) (runs: [..], [AVG_GAS]) [FAIL: MyRevert(); counterexample: [..]] testMultipleAssumesClearAfterCall_fails(uint256) (runs: 0, [AVG_GAS]) [FAIL: RevertWithData(3); counterexample: [..]] testMultipleAssumes_OneWrong_fails(uint256) (runs: [..], [AVG_GAS]) [FAIL: vm.assumeNoRevert: you must make another external call prior to calling assumeNoRevert again; counterexample: [..]] testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4) (runs: [..], [AVG_GAS]) @@ -2644,7 +2745,7 @@ Ran 8 tests for src/AssumeNoRevertTest.t.sol:ReverterTest "#]]); }); -forgetest_async!(can_get_broadcast_txs, |prj, cmd| { +forgetest_async!(flaky_can_get_broadcast_txs, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); let (_api, handle) = spawn(NodeConfig::test().silent()).await; @@ -2668,8 +2769,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { } } "#, - ) - .unwrap(); + ); prj.add_script( "DeployCounter", @@ -2691,8 +2791,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { } } "#, - ) - .unwrap(); + ); prj.add_script( "DeployCounterWithCreate2", @@ -2715,8 +2814,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { } } "#, - ) - .unwrap(); + ); let test = r#" import {Vm} from "../src/Vm.sol"; @@ -2726,7 +2824,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { contract GetBroadcastTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function test_getLatestBroacast() external { + function test_getLatestBroadcast() external { // Gets the latest create Vm.BroadcastTxSummary memory broadcast = vm.getBroadcast( "Counter", @@ -2788,7 +2886,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { 31337 ); - assertEq(deployedAddress, address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); + assertGt(uint160(deployedAddress), 0); } function test_getDeployments() public { @@ -2798,13 +2896,15 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { ); assertEq(deployments.length, 2); - assertEq(deployments[0], address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); // Create2 address - latest deployment - assertEq(deployments[1], address(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Create address - oldest deployment + // Verify valid addresses returned and they're different (CREATE vs CREATE2) + assertGt(uint160(deployments[0]), 0); + assertGt(uint160(deployments[1]), 0); + assertTrue(deployments[0] != deployments[1]); } } "#; - prj.add_test("GetBroadcast", test).unwrap(); + prj.add_test("GetBroadcast", test); let sender = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; @@ -2844,10 +2944,13 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { }); // See -forgetest_init!(test_roll_scroll_fork_with_cancun, |prj, cmd| { - prj.add_test( - "ScrollForkTest.t.sol", - r#" +forgetest_init!( + #[ignore = "RPC Service Unavailable"] + test_roll_scroll_fork_with_cancun, + |prj, cmd| { + prj.add_test( + "ScrollForkTest.t.sol", + r#" import {Test} from "forge-std/Test.sol"; @@ -2859,14 +2962,14 @@ contract ScrollForkTest is Test { } } "#, - ) - .unwrap(); + ); - cmd.args(["test", "--mt", "test_roll_scroll_fork_to_tx", "--evm-version", "cancun"]) - .assert_success(); -}); + cmd.args(["test", "--mt", "test_roll_scroll_fork_to_tx", "--evm-version", "cancun"]) + .assert_success(); + } +); -// Test that only provider is included in failed fork error. +// Test that failed fork errors still surface the provider hostname. forgetest_init!(test_display_provider_on_error, |prj, cmd| { prj.add_test( "ForkTest.t.sol", @@ -2879,14 +2982,13 @@ contract ForkTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_fork_err_message"]).assert_failure().stdout_eq(str![[r#" ... Ran 1 test for test/ForkTest.t.sol:ForkTest -[FAIL: vm.createSelectFork: could not instantiate forked environment with provider eth-mainnet.g.alchemy.com; failed to get latest block number; [..]] test_fork_err_message() ([GAS]) -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +[FAIL: vm.createSelectFork: could not instantiate forked environment with provider eth-mainnet.g.alchemy.com; HTTP error 401 with body: Must be authenticated! + ... "#]]); @@ -2895,6 +2997,7 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] // Tests that test traces display state changes when running with verbosity. #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(should_show_state_changes, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" ... Ran 1 test for test/Counter.t.sol:CounterTest @@ -2907,15 +3010,13 @@ Traces: │ └─ ← [Stop] └─ ← [Stop] - [31851] CounterTest::test_Increment() + [28783] CounterTest::test_Increment() ├─ [22418] Counter::increment() │ ├─ storage changes: │ │ @ 0: 0 → 1 │ └─ ← [Stop] ├─ [424] Counter::number() [staticcall] │ └─ ← [Return] 1 - ├─ [0] VM::assertEq(1, 1) [staticcall] - │ └─ ← [Return] └─ ← [Stop] Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] @@ -2935,8 +3036,7 @@ contract ContractTest { function test_anything(uint) public {} } "#, - ) - .unwrap(); + ); cmd.arg("test").arg("--gas-limit=100").assert_failure().stdout_eq(str![[r#" ... @@ -2946,12 +3046,15 @@ Encountered 1 failing test in test/Foo.t.sol:ContractTest Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); // Tests that `start/stopAndReturn` debugTraceRecording does not panic when running with // verbosity > 3. forgetest_init!(should_not_panic_on_debug_trace_verbose, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "DebugTraceRecordingTest.t.sol", r#" @@ -2967,8 +3070,7 @@ contract DebugTraceRecordingTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "test_start_stop_recording", "-vvvv"]).assert_success().stdout_eq( str![[r#" @@ -2980,6 +3082,7 @@ Ran 1 test for test/DebugTraceRecordingTest.t.sol:DebugTraceRecordingTest [PASS] test_start_stop_recording() ([GAS]) Traces: [..] DebugTraceRecordingTest::test_start_stop_recording() +... └─ ← [Stop] Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] @@ -2992,15 +3095,17 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(colored_traces, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--mt", "test_Increment", "--color", "always", "-vvvvv"]) .assert_success() - .stdout_eq(file!["../fixtures/colored_traces.svg": TermSvg]); + .stdout_eq(file!["../../fixtures/colored_traces.svg": TermSvg]); }); // Tests that traces for successful tests can be suppressed by using `-s` flag. // #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(should_only_show_failed_tests_trace, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "SuppressTracesTest.t.sol", r#" @@ -3028,11 +3133,10 @@ contract SuppressTracesTest is Test { } } "#, - ) - .unwrap(); + ); // Show traces and logs for failed test only. - cmd.args(["test", "--mc", "SuppressTracesTest", "-vvvv", "-s"]).assert_failure().stdout_eq( + cmd.args(["test", "--mc", "SuppressTracesTest", "-vvvvv", "-s"]).assert_failure().stdout_eq( str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -3051,10 +3155,12 @@ Traces: │ └─ ← [Stop] └─ ← [Stop] - [35178] SuppressTracesTest::test_increment_failure() + [35200] SuppressTracesTest::test_increment_failure() ├─ [0] console::log("test increment failure") [staticcall] │ └─ ← [Stop] ├─ [22418] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 │ └─ ← [Stop] ├─ [424] Counter::number() [staticcall] │ └─ ← [Return] 1 @@ -3062,6 +3168,10 @@ Traces: │ └─ ← [Revert] assertion failed: 1 != 100 └─ ← [Revert] assertion failed: 1 != 100 +Backtrace: + at VM.assertEq + at SuppressTracesTest.test_increment_failure (lib/forge-std/src/StdAssertions.sol:[..]:[..]) + [PASS] test_increment_success() ([GAS]) Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] @@ -3073,6 +3183,8 @@ Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest Encountered a total of 1 failing tests, 1 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]], ); @@ -3096,7 +3208,7 @@ Traces: │ └─ ← [Stop] └─ ← [Stop] - [35178] SuppressTracesTest::test_increment_failure() + [35200] SuppressTracesTest::test_increment_failure() ├─ [0] console::log("test increment failure") [staticcall] │ └─ ← [Stop] ├─ [22418] Counter::increment() @@ -3107,20 +3219,22 @@ Traces: │ └─ ← [Revert] assertion failed: 1 != 100 └─ ← [Revert] assertion failed: 1 != 100 +Backtrace: + at VM.assertEq + at SuppressTracesTest.test_increment_failure + [PASS] test_increment_success() ([GAS]) Logs: test increment success Traces: - [35229] SuppressTracesTest::test_increment_success() + [32164] SuppressTracesTest::test_increment_success() ├─ [0] console::log("test increment success") [staticcall] │ └─ ← [Stop] ├─ [22418] Counter::increment() │ └─ ← [Stop] ├─ [424] Counter::number() [staticcall] │ └─ ← [Return] 1 - ├─ [0] VM::assertEq(1, 1) [staticcall] - │ └─ ← [Return] └─ ← [Stop] Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] @@ -3133,6 +3247,8 @@ Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest Encountered a total of 1 failing tests, 1 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); @@ -3156,8 +3272,7 @@ contract TestDeploymentFailure is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["t", "--mt", "test_something"]).assert_failure().stdout_eq(str![[r#" ... @@ -3187,8 +3302,7 @@ contract CounterTestA is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["t", "--mt", "test_something"]).assert_failure(); }); @@ -3230,8 +3344,7 @@ contract SenderLogger { } } "#, - ) - .unwrap(); + ); // Emits // Log(: player: [], : player: []) instead // Log(: ContractTest: [], : player: []) @@ -3292,8 +3405,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mt", "testCheckDelegation", "-vvvv"]).assert_success().stdout_eq(str![[r#" ... @@ -3306,9 +3418,9 @@ Traces: ├─ [0] VM::label(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], "alice") │ └─ ← [Return] ├─ [0] VM::signDelegation(0x0000000000000000000000000000000000000000, "") - │ └─ ← [Return] (0, 0x38db2a0ada75402af7cd5bdb8248a1a5b4fec65fdafea4f935084f00dc2ff3c5, 0x29ce7b1c82f9ceaec21f12d690ba8fe6ecba65869caf6ab2d85d79890dc42df2, 1, 0x0000000000000000000000000000000000000000) + │ └─ ← [Return] (0, 0x3d6ad67cc3dc94101a049f85f96937513a05485ae0f8b27545d25c4f71b12cf9, 0x3c0f2d62834f59d6ef0209e8a935f80a891a236eb18ac0e3700dd8f7ac8ae279, 0, 0x0000000000000000000000000000000000000000) ├─ [0] VM::signAndAttachDelegation(0x0000000000000000000000000000000000000000, "") - │ └─ ← [Return] (0, 0x38db2a0ada75402af7cd5bdb8248a1a5b4fec65fdafea4f935084f00dc2ff3c5, 0x29ce7b1c82f9ceaec21f12d690ba8fe6ecba65869caf6ab2d85d79890dc42df2, 1, 0x0000000000000000000000000000000000000000) + │ └─ ← [Return] (0, 0x3d6ad67cc3dc94101a049f85f96937513a05485ae0f8b27545d25c4f71b12cf9, 0x3c0f2d62834f59d6ef0209e8a935f80a891a236eb18ac0e3700dd8f7ac8ae279, 0, 0x0000000000000000000000000000000000000000) └─ ← [Stop] ... @@ -3316,7 +3428,8 @@ Traces: }); // -forgetest_init!(can_upload_selectors_with_path, |prj, cmd| { +forgetest_init!(flaky_can_upload_selectors_with_path, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "CounterV1.sol", r#" @@ -3332,8 +3445,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterV2.sol", @@ -3350,8 +3462,7 @@ contract CounterV2 { } } "#, - ) - .unwrap(); + ); // Upload Counter without path fails as there are multiple contracts with same name. cmd.args(["selectors", "upload", "Counter"]).assert_failure().stderr_eq(str![[r#" @@ -3410,10 +3521,186 @@ Selectors successfully uploaded to OpenChain "#]]); }); -// tests `interceptInitcode` function -forgetest_init!(intercept_initcode, |prj, cmd| { - prj.wipe_contracts(); - prj.insert_ds_test(); +forgetest_init!(selectors_list_cmd, |prj, cmd| { + prj.add_source( + "Counter.sol", + r" +contract Counter { + uint256 public number; + event Incremented(uint256 newNumber); + error IncrementError(); + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + ", + ); + + prj.add_source( + "CounterV2.sol", + r" +contract CounterV2 { + uint256 public number; + + function setNumberV2(uint256 newNumber) public { + number = newNumber; + } + + function incrementV2() public { + number++; + } +} + ", + ); + + cmd.args(["selectors", "list"]).assert_success().stdout_eq(str![[r#" +Listing selectors for contracts in the project... +Counter + +╭----------+----------------------+--------------------------------------------------------------------╮ +| Type | Signature | Selector | ++======================================================================================================+ +| Function | increment() | 0xd09de08a | +|----------+----------------------+--------------------------------------------------------------------| +| Function | number() | 0x8381f58a | +|----------+----------------------+--------------------------------------------------------------------| +| Function | setNumber(uint256) | 0x3fb5c1cb | +|----------+----------------------+--------------------------------------------------------------------| +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +|----------+----------------------+--------------------------------------------------------------------| +| Error | IncrementError() | 0x46544c04 | +╰----------+----------------------+--------------------------------------------------------------------╯ + +CounterV2 + +╭----------+----------------------+------------╮ +| Type | Signature | Selector | ++==============================================+ +| Function | incrementV2() | 0x49365a69 | +|----------+----------------------+------------| +| Function | number() | 0x8381f58a | +|----------+----------------------+------------| +| Function | setNumberV2(uint256) | 0xb525b68c | +╰----------+----------------------+------------╯ + +"#]]); + + cmd.forge_fuse() + .args(["selectors", "list", "--no-group"]) + .assert_success() + .stdout_eq(str![[r#" +Listing selectors for contracts in the project... + +╭----------+----------------------+--------------------------------------------------------------------+-----------╮ +| Type | Signature | Selector | Contract | ++==================================================================================================================+ +| Function | increment() | 0xd09de08a | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | number() | 0x8381f58a | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | setNumber(uint256) | 0x3fb5c1cb | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Error | IncrementError() | 0x46544c04 | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | incrementV2() | 0x49365a69 | CounterV2 | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | number() | 0x8381f58a | CounterV2 | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | setNumberV2(uint256) | 0xb525b68c | CounterV2 | +╰----------+----------------------+--------------------------------------------------------------------+-----------╯ + +"#]]); +}); + +forgetest_init!(selectors_list_cmd_md, |prj, cmd| { + prj.add_source( + "Counter.sol", + r" +contract Counter { + uint256 public number; + event Incremented(uint256 newNumber); + error IncrementError(); + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + ", + ); + + prj.add_source( + "CounterV2.sol", + r" +contract CounterV2 { + uint256 public number; + + function setNumberV2(uint256 newNumber) public { + number = newNumber; + } + + function incrementV2() public { + number++; + } +} + ", + ); + + cmd.args(["selectors", "list", "--md"]).assert_success().stdout_eq(str![[r#" +Listing selectors for contracts in the project... +Counter + +| Type | Signature | Selector | +|----------|----------------------|--------------------------------------------------------------------| +| Function | increment() | 0xd09de08a | +| Function | number() | 0x8381f58a | +| Function | setNumber(uint256) | 0x3fb5c1cb | +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +| Error | IncrementError() | 0x46544c04 | + +CounterV2 + +| Type | Signature | Selector | +|----------|----------------------|------------| +| Function | incrementV2() | 0x49365a69 | +| Function | number() | 0x8381f58a | +| Function | setNumberV2(uint256) | 0xb525b68c | + +"#]]); + + cmd.forge_fuse() + .args(["selectors", "list", "--no-group", "--md"]) + .assert_success() + .stdout_eq(str![[r#" +Listing selectors for contracts in the project... + +| Type | Signature | Selector | Contract | +|----------|----------------------|--------------------------------------------------------------------|-----------| +| Function | increment() | 0xd09de08a | Counter | +| Function | number() | 0x8381f58a | Counter | +| Function | setNumber(uint256) | 0x3fb5c1cb | Counter | +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | Counter | +| Error | IncrementError() | 0x46544c04 | Counter | +| Function | incrementV2() | 0x49365a69 | CounterV2 | +| Function | number() | 0x8381f58a | CounterV2 | +| Function | setNumberV2(uint256) | 0xb525b68c | CounterV2 | + +"#]]); +}); + +// tests `interceptInitcode` function +forgetest_init!(intercept_initcode, |prj, cmd| { + prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -3515,18 +3802,16 @@ contract InterceptInitcodeTest is DSTest { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "-vvvvv"]).assert_success(); }); // // forgetest_init!(should_preserve_fork_state_setup, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Counter.t.sol", - r#" + &r#" import "forge-std/Test.sol"; import {StdChains} from "forge-std/StdChains.sol"; @@ -3553,7 +3838,7 @@ contract CounterTest is Test { // Temporary workaround for `https://eth.llamarpc.com/` being down setChain("mainnet", ChainData({ name: "mainnet", - rpcUrl: "https://reth-ethereum.ithaca.xyz/rpc", + rpcUrl: "", chainId: 1 })); @@ -3588,9 +3873,9 @@ contract CounterTest is Test { assertEq(data[3].bridges.length, 2); } } - "#, - ) - .unwrap(); + "# + .replace("", &rpc::next_http_archive_rpc_url()), + ); cmd.args(["test", "--mc", "CounterTest"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3608,7 +3893,8 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) }); // -forgetest_init!(should_not_panic_on_cool, |prj, cmd| { +forgetest_init!(flaky_should_not_panic_on_cool, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "Counter.t.sol", r#" @@ -3628,8 +3914,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--mc", "CounterTest"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3648,11 +3933,14 @@ Encountered 1 failing test in test/Counter.t.sol:CounterTest Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + "#]]); }); #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(detailed_revert_when_calling_non_contract_address, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "NonContractCallRevertTest.t.sol", r#" @@ -3690,10 +3978,9 @@ contract NonContractCallRevertTest is Test { } } "#, - ) - .unwrap(); + ); - cmd.args(["test", "--mc", "NonContractCallRevertTest", "-vvv"]) + cmd.args(["test", "--mc", "NonContractCallRevertTest", "-vvvvv"]) .assert_failure() .stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3706,6 +3993,13 @@ Logs: test non contract call failure Traces: + [157143] NonContractCallRevertTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [22492] Counter::setNumber(1) + │ └─ ← [Stop] + └─ ← [Stop] + [6350] NonContractCallRevertTest::test_non_contract_call_failure() ├─ [0] console::log("test non contract call failure") [staticcall] │ └─ ← [Stop] @@ -3713,21 +4007,41 @@ Traces: │ └─ ← [Stop] └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000 +Backtrace: + at NonContractCallRevertTest.test_non_contract_call_failure + [FAIL: call to non-contract address 0xdEADBEeF00000000000000000000000000000000] test_non_contract_void_call_failure() ([GAS]) Logs: test non contract (void) call failure Traces: + [157143] NonContractCallRevertTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [22492] Counter::setNumber(1) + │ └─ ← [Stop] + └─ ← [Stop] + [6215] NonContractCallRevertTest::test_non_contract_void_call_failure() ├─ [0] console::log("test non contract (void) call failure") [staticcall] │ └─ ← [Stop] └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000 +Backtrace: + at NonContractCallRevertTest.test_non_contract_void_call_failure (test/NonContractCallRevertTest.t.sol:[..]:[..]) + [FAIL: EvmError: Revert] test_non_supported_selector_call_failure() ([GAS]) Logs: test non supported fn selector call failure Traces: + [157143] NonContractCallRevertTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [22492] Counter::setNumber(1) + │ └─ ← [Stop] + └─ ← [Stop] + [8620] NonContractCallRevertTest::test_non_supported_selector_call_failure() ├─ [0] console::log("test non supported fn selector call failure") [staticcall] │ └─ ← [Stop] @@ -3735,6 +4049,10 @@ Traces: │ └─ ← [Revert] unrecognized function selector 0x5ec01e4d for contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, which has no fallback function. └─ ← [Revert] EvmError: Revert +Backtrace: + at Counter.random (src/Counter.sol:[..]:[..]) + at NonContractCallRevertTest.test_non_supported_selector_call_failure (test/NonContractCallRevertTest.t.sol:[..]:[..]) + Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests) @@ -3747,6 +4065,8 @@ Encountered 3 failing tests in test/NonContractCallRevertTest.t.sol:NonContractC Encountered a total of 3 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 3 failed tests + "#]]); }); @@ -3789,10 +4109,9 @@ contract NonContractDelegateCallRevertTest is Test { } } "#, - ) - .unwrap(); + ); - cmd.args(["test", "--mc", "NonContractDelegateCallRevertTest", "-vvv"]) + cmd.args(["test", "--mc", "NonContractDelegateCallRevertTest", "-vvvvv"]) .assert_failure() .stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -3809,6 +4128,8 @@ Traces: ├─ [0] console::log("Test: Simulating call to unlinked library") [staticcall] │ └─ ← [Stop] ├─ [214746] → new LibraryCaller@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ ├─ storage changes: + │ │ @ 0: 0 → 0x000000000000000000000000deadbeef00000000000000000000000000000000 │ └─ ← [Return] 960 bytes of code ├─ [3896] LibraryCaller::foobar(10) │ ├─ [0] 0xdEADBEeF00000000000000000000000000000000::foo(10) [delegatecall] @@ -3816,6 +4137,10 @@ Traces: │ └─ ← [Revert] delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library) └─ ← [Revert] delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library) +Backtrace: + at LibraryCaller.foobar + at NonContractDelegateCallRevertTest.test_unlinked_library_call_failure (test/NonContractDelegateCallRevertTest.t.sol:[..]:[..]) + Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) @@ -3826,5 +4151,334 @@ Encountered 1 failing test in test/NonContractDelegateCallRevertTest.t.sol:NonCo Encountered a total of 1 failing tests, 0 tests succeeded +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// This test is a copy of `error_event_decode_with_cache` in cast/tests/cli/selectors.rs +// but it uses `forge build` to check that the project selectors are cached by default. +forgetest_init!(flaky_build_with_selectors_cache, |prj, cmd| { + prj.initialize_default_contracts(); + prj.add_source( + "LocalProjectContract", + r#" +contract ContractWithCustomError { + error AnotherValueTooHigh(uint256, address); + event MyUniqueEventWithinLocalProject(uint256 a, address b); +} + "#, + ); + // Build and cache project selectors. + cmd.forge_fuse().args(["build", "--force"]).assert_success(); + + // Assert cast can decode custom error with local cache. + cmd.cast_fuse() + .args(["decode-error", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]) + .assert_success() + .stdout_eq(str![[r#" +AnotherValueTooHigh(uint256,address) +101 +0x0000000000000000000000000000000000D0004F + +"#]]); + // Assert cast can decode event with local cache. + cmd.cast_fuse() + .args(["decode-event", "0xbd3699995dcc867b64dbb607be2c33be38df9134bef1178df13bfb9446e73104000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]) + .assert_success() + .stdout_eq(str![[r#" +MyUniqueEventWithinLocalProject(uint256,address) +78 +0x00000000000000000000000000000DD00000004e + +"#]]); +}); + +// +forgetest_init!(revm_27_prank_bug_fix, |prj, cmd| { + prj.initialize_default_contracts(); + prj.add_test( + "PrankBug.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract PrankTest is Test { + Counter public counter; + + function setUp() public { + vm.startPrank(address(0x123)); + counter = new Counter(); + vm.stopPrank(); + } + + function test_Increment() public { + vm.startPrank(address(0x123)); + counter = new Counter(); + vm.stopPrank(); + + counter.increment(); + assertEq(counter.number(), 1); + } +} +"#, + ); + + cmd.args(["test", "--mc", "PrankTest", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/PrankBug.t.sol:PrankTest +[PASS] test_Increment() ([GAS]) +Traces: + [..] PrankTest::setUp() + ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123) + │ └─ ← [Return] + ├─ [..] → new Counter@0x6cdBd1b486b8FBD4140e8cd6daAED05bE13eD914 + │ └─ ← [Return] 481 bytes of code + ├─ [0] VM::stopPrank() + │ └─ ← [Return] + └─ ← [Stop] + + [..] PrankTest::test_Increment() + ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123) + │ └─ ← [Return] + ├─ [..] → new Counter@0xc4B957Cd61beB9b9afD76204b30683EDAaaB51Ec + │ └─ ← [Return] 481 bytes of code + ├─ [0] VM::stopPrank() + │ └─ ← [Return] + ├─ [..] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 + │ └─ ← [Stop] + ├─ [..] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ storage changes: + │ @ 31: 0x00000000000000000000006cdbd1b486b8fbd4140e8cd6daaed05be13ed91401 → 0x0000000000000000000000c4b957cd61beb9b9afd76204b30683edaaab51ec01 + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests proper reverts in fork mode for contracts with non-existent linked libraries. +// +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(can_fork_test_with_non_existent_linked_library, |prj, cmd| { + prj.update_config(|config| { + config.libraries = + vec!["src/Counter.sol:LibCounter:0x530008d2b058137d9c475b1b7d83984f1fcf1dd0".into()]; + }); + prj.add_source( + "Counter.sol", + r" +library LibCounter { + function dummy() external pure returns (uint) { + return 1; + } +} + +contract Counter { + uint256 public number; + + constructor() { + LibCounter.dummy(); + } + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + + function dummy() external pure returns (uint) { + return LibCounter.dummy(); + } +} + ", + ); + + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "Counter.t.sol", + &r#" +import "forge-std/Test.sol"; +import "src/Counter.sol"; + +contract CounterTest is Test { + function test_select_fork() public { + vm.createSelectFork(""); + new Counter(); + } + + function test_roll_fork() public { + vm.rollFork(block.number - 100); + new Counter(); + } +} + "# + .replace("", &endpoint), + ); + + cmd.args(["test", "--fork-url", &endpoint]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert] test_roll_fork() ([GAS]) +[FAIL: Contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f does not exist and is not marked as persistent, see `vm.makePersistent()`] test_select_fork() ([GAS]) +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 2 failing tests in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert] test_roll_fork() ([GAS]) +[FAIL: Contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f does not exist and is not marked as persistent, see `vm.makePersistent()`] test_select_fork() ([GAS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +// +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(invariant_consistent_output, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 10; + config.invariant.depth = 100; + config.invariant.show_metrics = false; + }); + prj.add_test( + "InvariantOutputTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantOutputTest is Test { + uint256 count; + + function setCond(uint256 cond) public { + if (cond > type(uint256).max / 2) { + count++; + } + } + + function setUp() public { + targetContract(address(this)); + } + + function invariant_check_count() public view { + require(count < 2, "failed invariant"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_check_count", "--color", "always"]) + .assert_failure() + .stdout_eq(file!["../../fixtures/invariant_traces.svg": TermSvg]); +}); + +forgetest_init!(memory_limit, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.memory_limit = 500 * 32; + }); + prj.add_test( + "MemoryLimit.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Memory { + function allocate(uint256 n) external pure returns (uint256[] memory) { + return new uint256[](n); + } +} + +contract MemoryLimitTest is Test { + Memory public m = new Memory(); + + function test_inBounds() public { + m.allocate(100); + } + + function test_oom() public { + m.allocate(1000); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 2 tests for test/MemoryLimit.t.sol:MemoryLimitTest +[PASS] test_inBounds() ([GAS]) +[FAIL: EvmError: Revert] test_oom() ([GAS]) +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/MemoryLimit.t.sol:MemoryLimitTest +[FAIL: EvmError: Revert] test_oom() ([GAS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +forgetest_init!(zero_runs, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "ZeroRuns.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Handler is Test { + function doSomething(uint256 param) public { + revert("unreachable"); + } +} + +contract ZeroRuns is Test { + Handler handler = new Handler(); + + /// forge-config: default.fuzz.runs = 0 + function test_fuzzZeroRuns(uint256 x) public { + revert("unreachable"); + } + + /// forge-config: default.invariant.runs = 0 + function invariant_zeroRuns() public {} + + /// forge-config: default.invariant.depth = 0 + function invariant_zeroDepth() public {} +} +"#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 tests for test/ZeroRuns.t.sol:ZeroRuns +[PASS] invariant_zeroDepth() (runs: 256, calls: 0, reverts: 0) +[PASS] invariant_zeroRuns() (runs: 0, calls: 0, reverts: 0) +[PASS] test_fuzzZeroRuns(uint256) (runs: 0, [AVG_GAS]) +Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs new file mode 100644 index 0000000000000..3803385b496ab --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -0,0 +1,970 @@ +//! Regression tests for specific GitHub issues + +use foundry_test_utils::str; + +// https://github.com/foundry-rs/foundry/issues/3055 +forgetest_init!(issue_3055, |prj, cmd| { + prj.add_test( + "Issue3055.t.sol", + r#" +import "forge-std/Test.sol"; + +/// forge-config: default.assertions_revert = false +contract Issue3055Test is Test { + function test_snapshot() external { + uint256 snapshotId = vm.snapshotState(); + assertEq(uint256(0), uint256(1)); + vm.revertToState(snapshotId); + } + + function test_snapshot2() public { + uint256 snapshotId = vm.snapshotState(); + assertTrue(false); + vm.revertToState(snapshotId); + assertTrue(true); + } + + function test_snapshot3(uint256) public { + vm.expectRevert(); + // Call exposed_snapshot3() using this to perform an external call, + // so we can properly test for reverts. + this.exposed_snapshot3(); + } + + function exposed_snapshot3() public { + uint256 snapshotId = vm.snapshotState(); + assertTrue(false); + vm.revertToState(snapshotId); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 3 tests for test/Issue3055.t.sol:Issue3055Test +[FAIL] test_snapshot() ([GAS]) +[FAIL] test_snapshot2() ([GAS]) +[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 3 failing tests in test/Issue3055.t.sol:Issue3055Test +[FAIL] test_snapshot() ([GAS]) +[FAIL] test_snapshot2() ([GAS]) +[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS]) + +Encountered a total of 3 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 3 failed tests + +[SEED] (use `--fuzz-seed` to reproduce) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3189 +forgetest_init!(issue_3189, |prj, cmd| { + prj.add_test( + "Issue3189.t.sol", + r#" +import "forge-std/Test.sol"; + +contract MyContract { + function foo(uint256 arg) public returns (uint256) { + return arg + 2; + } +} + +contract MyContractUser is Test { + MyContract immutable myContract; + + constructor() { + myContract = new MyContract(); + } + + function foo(uint256 arg) public returns (uint256 ret) { + ret = myContract.foo(arg); + assertEq(ret, arg + 1, "Invariant failed"); + } +} + +contract Issue3189Test is Test { + function testFoo() public { + MyContractUser user = new MyContractUser(); + user.foo(123); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue3189.t.sol:Issue3189Test +[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue3189.t.sol:Issue3189Test +[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3596 +forgetest_init!(issue_3596, |prj, cmd| { + prj.add_test( + "Issue3596.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue3596Test is Test { + function testDealTransfer() public { + address addr = vm.addr(1337); + vm.startPrank(addr); + vm.deal(addr, 20000001 ether); + payable(address(this)).transfer(20000000 ether); + + Nested nested = new Nested(); + nested.doStuff(); + vm.stopPrank(); + } +} + +contract Nested { + function doStuff() public { + doRevert(); + } + + function doRevert() public { + revert("This fails"); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue3596.t.sol:Issue3596Test +[FAIL: EvmError: Revert] testDealTransfer() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue3596.t.sol:Issue3596Test +[FAIL: EvmError: Revert] testDealTransfer() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/2851 +forgetest_init!(issue_2851, |prj, cmd| { + prj.add_test( + "Issue2851.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Backdoor { + uint256 public number = 1; + + function backdoor(uint256 newNumber) public payable { + uint256 x = newNumber - 1; + if (x == 6912213124124531) { + number = 0; + } + } +} + +contract Issue2851Test is Test { + Backdoor back; + + function setUp() public { + back = new Backdoor(); + } + + /// forge-config: default.fuzz.seed = "111" + function invariantNotZero() public { + assertEq(back.number(), 1); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue2851.t.sol:Issue2851Test +[FAIL: assertion failed: 0 != 1] +... + invariantNotZero() ([..]) +... +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6170 +forgetest_init!(issue_6170, |prj, cmd| { + prj.add_test( + "Issue6170.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Emitter { + event Values(uint256 indexed a, uint256 indexed b); + + function plsEmit(uint256 a, uint256 b) external { + emit Values(a, b); + } +} + +contract Issue6170Test is Test { + event Values(uint256 indexed a, uint256 b); + + Emitter e = new Emitter(); + + function test() public { + vm.expectEmit(true, true, false, true); + emit Values(69, 420); + e.plsEmit(69, 420); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Issue6170.t.sol:Issue6170Test +[FAIL: log != expected log] test() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Issue6170.t.sol:Issue6170Test +[FAIL: log != expected log] test() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 1 failed test + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6355 +forgetest_init!(issue_6355, |prj, cmd| { + prj.add_test( + "Issue6355.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue6355Test is Test { + uint256 snapshotId; + Target targ; + + function setUp() public { + snapshotId = vm.snapshotState(); + targ = new Target(); + } + + // this non-deterministically fails sometimes and passes sometimes + function test_shouldPass() public { + assertEq(2, targ.num()); + } + + // always fails + function test_shouldFailWithRevertToState() public { + assertEq(3, targ.num()); + vm.revertToState(snapshotId); + } + + // always fails + function test_shouldFail() public { + assertEq(3, targ.num()); + } +} + +contract Target { + function num() public pure returns (uint256) { + return 2; + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue6355.t.sol:Issue6355Test +[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS]) +[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS]) +[PASS] test_shouldPass() ([GAS]) +Suite result: FAILED. 1 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 2 failing tests in test/Issue6355.t.sol:Issue6355Test +[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS]) +[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS]) + +Encountered a total of 2 failing tests, 1 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 2 failed tests + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/3347 +forgetest_init!(issue_3347, |prj, cmd| { + prj.add_test( + "Issue3347.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue3347Test is Test { + event log2(uint256, uint256); + + function test() public { + emit log2(1, 2); + } +} +"#, + ); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Issue3347.t.sol:Issue3347Test +[PASS] test() ([GAS]) +Traces: + [..] Issue3347Test::test() + ├─ emit log2(: 1, : 2) + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6501 +// Make sure we decode Hardhat-style `console.log`s correctly, in both logs and traces. +forgetest_init!(issue_6501, |prj, cmd| { + prj.add_test( + "Issue6501.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue6501Test is Test { + function test_hhLogs() public { + console.log("a"); + console.log(uint256(1)); + console.log("b", uint256(2)); + } +} +"#, + ); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue6501.t.sol:Issue6501Test +[PASS] test_hhLogs() ([GAS]) +Logs: + a + 1 + b 2 + +Traces: + [..] Issue6501Test::test_hhLogs() + ├─ [0] console::log("a") [staticcall] + │ └─ ← [Stop] + ├─ [0] console::log(1) [staticcall] + │ └─ ← [Stop] + ├─ [0] console::log("b", 2) [staticcall] + │ └─ ← [Stop] + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/8383 +forgetest_init!(issue_8383, |prj, cmd| { + prj.update_config(|config| { + config.optimizer = Some(true); + config.optimizer_runs = Some(200); + }); + prj.add_test( + "Issue8383.t.sol", + r#" +import "forge-std/Test.sol"; + +contract Issue8383Test is Test { + address internal _verifier; + + mapping(bytes32 => bool) internal _vectorTested; + mapping(bytes32 => bool) internal _vectorResult; + + function setUp() public { + _verifier = address(new P256Verifier()); + } + + function _verifyViaVerifier(bytes32 hash, uint256 r, uint256 s, uint256 x, uint256 y) internal returns (bool) { + return _verifyViaVerifier(hash, bytes32(r), bytes32(s), bytes32(x), bytes32(y)); + } + + function _verifyViaVerifier(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) internal returns (bool) { + bytes memory payload = abi.encode(hash, r, s, x, y); + if (uint256(y) & 0xff == 0) { + bytes memory truncatedPayload = abi.encodePacked(hash, r, s, x, bytes31(y)); + _verifierCall(truncatedPayload); + } + if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) { + uint256 r = uint256(keccak256(abi.encode(payload, "2"))); + payload = abi.encodePacked(payload, new bytes(r & 0xff)); + } + bytes32 payloadHash = keccak256(payload); + if (_vectorTested[payloadHash]) return _vectorResult[payloadHash]; + _vectorTested[payloadHash] = true; + return (_vectorResult[payloadHash] = _verifierCall(payload)); + } + + function _verifierCall(bytes memory payload) internal returns (bool) { + (bool success, bytes memory result) = _verifier.call(payload); + return abi.decode(result, (bool)); + } + + function testP256VerifyOutOfBounds() public { + vm.pauseGasMetering(); + uint256 p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + _verifyViaVerifier(bytes32(0), 1, 1, 1, 1); + _verifyViaVerifier(bytes32(0), 1, 1, 0, 1); + _verifyViaVerifier(bytes32(0), 1, 1, 1, 0); + _verifyViaVerifier(bytes32(0), 1, 1, 1, p); + _verifyViaVerifier(bytes32(0), 1, 1, p, 1); + _verifyViaVerifier(bytes32(0), 1, 1, p - 1, 1); + vm.resumeGasMetering(); + } +} + +contract P256Verifier { + uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + uint256 private constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; // `A = P - 3`. + uint256 private constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + uint256 private constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + + fallback() external payable { + assembly { + // For this implementation, we will use the memory without caring about + // the free memory pointer or zero pointer. + // The slots `0x00`, `0x20`, `0x40`, `0x60`, will not be accessed for the `Points[16]` array, + // and can be used for storing other variables. + + mstore(0x40, P) // Set `0x40` to `P`. + + function jAdd(x1, y1, z1, x2, y2, z2) -> x3, y3, z3 { + if iszero(z1) { + x3 := x2 + y3 := y2 + z3 := z2 + leave + } + if iszero(z2) { + x3 := x1 + y3 := y1 + z3 := z1 + leave + } + let p := mload(0x40) + let zz1 := mulmod(z1, z1, p) + let zz2 := mulmod(z2, z2, p) + let u1 := mulmod(x1, zz2, p) + let u2 := mulmod(x2, zz1, p) + let s1 := mulmod(y1, mulmod(zz2, z2, p), p) + let s2 := mulmod(y2, mulmod(zz1, z1, p), p) + let h := addmod(u2, sub(p, u1), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r := addmod(s2, sub(p, s1), p) + x3 := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) + y3 := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, x3), p), p), sub(p, mulmod(s1, hhh, p)), p) + z3 := mulmod(h, mulmod(z1, z2, p), p) + } + + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + + function setJPointDouble(i, j) { + j := shl(7, j) + let x := mload(j) + let y := mload(add(j, returndatasize())) + let z := mload(add(j, 0x40)) + let p := mload(0x40) + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + setJPoint(i, x2, y2, z2) + } + + function setJPointAdd(i, j, k) { + j := shl(7, j) + k := shl(7, k) + let x, y, z := + jAdd( + mload(j), + mload(add(j, returndatasize())), + mload(add(j, 0x40)), + mload(k), + mload(add(k, returndatasize())), + mload(add(k, 0x40)) + ) + setJPoint(i, x, y, z) + } + + let r := calldataload(0x20) + let n := N + + { + let s := calldataload(0x40) + if lt(shr(1, n), s) { s := sub(n, s) } + + // Perform `modExp(s, N - 2, N)`. + // After which, we can abuse `returndatasize()` to get `0x20`. + mstore(0x800, 0x20) + mstore(0x820, 0x20) + mstore(0x840, 0x20) + mstore(0x860, s) + mstore(0x880, sub(n, 2)) + mstore(0x8a0, n) + + let p := mload(0x40) + mstore(0x20, xor(3, p)) // Set `0x20` to `A`. + let Qx := calldataload(0x60) + let Qy := calldataload(0x80) + + if iszero( + and( // The arguments of `and` are evaluated last to first. + and( + and(gt(calldatasize(), 0x9f), and(lt(iszero(r), lt(r, n)), lt(iszero(s), lt(s, n)))), + eq( + mulmod(Qy, Qy, p), + addmod(mulmod(addmod(mulmod(Qx, Qx, p), mload(returndatasize()), p), Qx, p), B, p) + ) + ), + and( + // We need to check that the `returndatasize` is indeed 32, + // so that we can return false if the chain does not have the modexp precompile. + eq(returndatasize(), 0x20), + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), 0x20) + ) + ) + ) { + // POC Note: + // Changing this to `return(0x80, 0x20)` fixes it. + // Alternatively, adding `if mload(0x8c0) { invalid() }` just before the return also fixes it. + return(0x8c0, 0x20) + } + + setJPoint(0x01, Qx, Qy, 1) + setJPoint(0x04, GX, GY, 1) + setJPointDouble(0x02, 0x01) + setJPointDouble(0x08, 0x04) + setJPointAdd(0x03, 0x01, 0x02) + setJPointAdd(0x05, 0x01, 0x04) + setJPointAdd(0x06, 0x02, 0x04) + setJPointAdd(0x07, 0x03, 0x04) + setJPointAdd(0x09, 0x01, 0x08) + setJPointAdd(0x0a, 0x02, 0x08) + setJPointAdd(0x0b, 0x03, 0x08) + setJPointAdd(0x0c, 0x04, 0x08) + setJPointAdd(0x0d, 0x01, 0x0c) + setJPointAdd(0x0e, 0x02, 0x0c) + setJPointAdd(0x0f, 0x03, 0x0c) + } + + let i := 0 + let u1 := mulmod(calldataload(0x00), mload(0x00), n) + let u2 := mulmod(r, mload(0x00), n) + let y := 0 + let z := 0 + let x := 0 + let p := mload(0x40) + for {} 1 {} { + if z { + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + yy := mulmod(y2, y2, p) + zz := mulmod(z2, z2, p) + s := mulmod(4, mulmod(x2, yy, p), p) + m := addmod( + mulmod(3, mulmod(x2, x2, p), p), + mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), + p + ) + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + z := mulmod(2, mulmod(y2, z2, p), p) + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + } + for { let o := or(and(shr(245, shl(i, u1)), 0x600), and(shr(247, shl(i, u2)), 0x180)) } o {} { + let z2 := mload(add(o, 0x40)) + if iszero(z2) { break } + if iszero(z) { + x := mload(o) + y := mload(add(o, returndatasize())) + z := z2 + break + } + let zz1 := mulmod(z, z, p) + let zz2 := mulmod(z2, z2, p) + let u1_ := mulmod(x, zz2, p) + let s1 := mulmod(y, mulmod(zz2, z2, p), p) + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) + z := mulmod(h, mulmod(z, z2, p), p) + break + } + // Just unroll twice. Fully unrolling will only save around 1% to 2% gas, but make the + // bytecode very bloated, which may incur more runtime costs after Verkle. + // See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip + // It's very unlikely that Verkle will come before the P256 precompile. But who knows? + if z { + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + yy := mulmod(y2, y2, p) + zz := mulmod(z2, z2, p) + s := mulmod(4, mulmod(x2, yy, p), p) + m := addmod( + mulmod(3, mulmod(x2, x2, p), p), + mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), + p + ) + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + z := mulmod(2, mulmod(y2, z2, p), p) + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + } + for { let o := or(and(shr(243, shl(i, u1)), 0x600), and(shr(245, shl(i, u2)), 0x180)) } o {} { + let z2 := mload(add(o, 0x40)) + if iszero(z2) { break } + if iszero(z) { + x := mload(o) + y := mload(add(o, returndatasize())) + z := z2 + break + } + let zz1 := mulmod(z, z, p) + let zz2 := mulmod(z2, z2, p) + let u1_ := mulmod(x, zz2, p) + let s1 := mulmod(y, mulmod(zz2, z2, p), p) + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) + z := mulmod(h, mulmod(z, z2, p), p) + break + } + i := add(i, 4) + if eq(i, 256) { break } + } + + if iszero(z) { + mstore(returndatasize(), iszero(r)) + return(returndatasize(), 0x20) + } + + // Perform `modExp(z, P - 2, P)`. + // `0x800`, `0x820, `0x840` are still set to `0x20`. + mstore(0x860, z) + mstore(0x880, sub(p, 2)) + mstore(0x8a0, p) + + mstore( + returndatasize(), + and( // The arguments of `and` are evaluated last to first. + eq(mod(mulmod(x, mulmod(mload(returndatasize()), mload(returndatasize()), p), p), n), r), + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), returndatasize()) + ) + ) + return(returndatasize(), returndatasize()) + } + } +} +"#, + ); + + cmd.arg("test").with_no_redact().assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Issue8383.t.sol:Issue8383Test +[PASS] testP256VerifyOutOfBounds() (gas: 3139) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/9272 +forgetest_init!(issue_9272, |prj, cmd| { + prj.update_config(|config| { + config.allow_paths.push("..".into()); + }); + + prj.add_source( + "Contract.sol", + r#" +pragma solidity ^0.8.0; +import '../Missing.sol'; +contract Contract {} +"#, + ); + + // We expect a compilation error due to the missing import + cmd.arg("build").assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Error (6275): Source "Missing.sol" not found: File not found. Searched the following locations: [..] +ParserError: Source "Missing.sol" not found: File not found. Searched the following locations: [..] + [FILE]:4:1: + | +4 | import '../Missing.sol'; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/10463 +forgetest_init!(issue_10463, |prj, cmd| { + prj.add_test( + "Issue10463.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue10463Test is Test { + event Foo(); + + error CustomError(uint256 code); + + function revertingBefore(bool shouldRevert) external { + if (shouldRevert) revert(); + emit Foo(); + } + + function revertingWithReason() external pure { + revert("revert reason"); + } + + function revertingWithCustomError() external pure { + revert CustomError(42); + } + + function testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() public { + vm.expectEmit(); + emit Foo(); + + this.revertingBefore(true); + } + + function testExpectEmitPreservesRevertReason() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithReason(); + } + + function testExpectEmitPreservesCustomError() public { + vm.expectEmit(); + emit Foo(); + + this.revertingWithCustomError(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +Ran 3 tests for test/Issue10463.t.sol:Issue10463Test +[FAIL: CustomError(42)] testExpectEmitPreservesCustomError() ([GAS]) +[FAIL: revert reason] testExpectEmitPreservesRevertReason() ([GAS]) +[FAIL: EvmError: Revert] testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() ([GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test gas underflow prevention on Cancun (no EIP-7702 gas floor) +forgetest_init!(issue_12803_cancun, |prj, cmd| { + prj.add_test( + "Issue12803.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803Test is Test { + uint a; + function test_negativeGas() public { + vm.pauseGasMetering(); + a = 100; + vm.resumeGasMetering(); + delete a; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[ + r#" +... +Ran 1 test for test/Issue12803.t.sol:Issue12803Test +[PASS] test_negativeGas() (gas: 0) +... +"# + ]]); +}); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test gas underflow prevention on Shanghai (also no EIP-7702 gas floor) +forgetest_init!(issue_12803_shanghai, |prj, cmd| { + prj.add_test( + "Issue12803.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803Test is Test { + uint a; + function test_negativeGas() public { + vm.pauseGasMetering(); + a = 100; + vm.resumeGasMetering(); + delete a; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=shanghai"]).with_no_redact().assert_success().stdout_eq(str![ + [r#" +... +Ran 1 test for test/Issue12803.t.sol:Issue12803Test +[PASS] test_negativeGas() (gas: 0) +... +"#] + ]); +}); + +// https://github.com/foundry-rs/foundry/issues/13766 +// vm.expectRevert(bytes("")) should not panic when actual revert has data +forgetest_init!(issue_13766, |prj, cmd| { + prj.add_test( + "Issue13766.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Reverter { + error CustomError(); + function revertWithData() public pure { revert CustomError(); } +} + +contract Issue13766Test is Test { + function test_expectRevertEmptyBytes() public { + Reverter r = new Reverter(); + vm.expectRevert(bytes("")); + r.revertWithData(); + } +} +"#, + ); + + cmd.arg("test").assert_failure().stdout_eq(str![[r#" +... +[FAIL: Error != expected error: CustomError() != EvmError: Revert] test_expectRevertEmptyBytes() ([GAS]) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/12803 +// Test multiple storage deletions (higher refund) don't cause underflow +forgetest_init!(issue_12803_multiple_deletes, |prj, cmd| { + prj.add_test( + "Issue12803Multi.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Issue12803MultiTest is Test { + uint a; + uint b; + uint c; + function test_multipleDeletes() public { + vm.pauseGasMetering(); + a = 100; + b = 200; + c = 300; + vm.resumeGasMetering(); + delete a; + delete b; + delete c; + } +} +"#, + ); + + cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[ + r#" +... +Ran 1 test for test/Issue12803Multi.t.sol:Issue12803MultiTest +[PASS] test_multipleDeletes() (gas: 0) +... +"# + ]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/spec.rs b/crates/forge/tests/cli/test_cmd/spec.rs new file mode 100644 index 0000000000000..5afb264d33fff --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/spec.rs @@ -0,0 +1,180 @@ +use foundry_test_utils::rpc; + +// Test evm version switch during tests / scripts. +// +// +forgetest_init!(test_set_evm_version, |prj, cmd| { + let endpoint = rpc::next_http_archive_rpc_url(); + prj.add_test( + "TestEvmVersion.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface EvmVm { + function getEvmVersion() external pure returns (string memory evm); + function setEvmVersion(string calldata evm) external; +} + +interface ICreate2Deployer { + function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address); +} + +contract TestEvmVersion is Test { + function test_evm_version() public { + EvmVm evm = EvmVm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + vm.createSelectFork(""); + + evm.setEvmVersion("istanbul"); + evm.getEvmVersion(); + + // revert with NotActivated for istanbul + vm.expectRevert(); + compute(); + + evm.setEvmVersion("shanghai"); + evm.getEvmVersion(); + compute(); + + // switch to Paris, expect revert with NotActivated + evm.setEvmVersion("paris"); + vm.expectRevert(); + compute(); + } + + function compute() internal view { + ICreate2Deployer(0x35Da41c476fA5c6De066f20556069096A1F39364).computeAddress(bytes32(0), bytes32(0)); + } +} + "#.replace("", &endpoint), + ); + + cmd.args(["test", "--mc", "TestEvmVersion", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/TestEvmVersion.t.sol:TestEvmVersion +[PASS] test_evm_version() ([GAS]) +Traces: + [..] TestEvmVersion::test_evm_version() + ├─ [0] VM::createSelectFork("") + │ └─ ← [Return] 0 + ├─ [0] VM::setEvmVersion("istanbul") + │ └─ ← [Return] + ├─ [0] VM::getEvmVersion() [staticcall] + │ └─ ← [Return] "istanbul" + ├─ [0] VM::expectRevert(custom error 0xf4844814) + │ └─ ← [Return] + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [NotActivated] EvmError: NotActivated + ├─ [0] VM::setEvmVersion("shanghai") + │ └─ ← [Return] + ├─ [0] VM::getEvmVersion() [staticcall] + │ └─ ← [Return] "shanghai" + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [Return] 0x0f40d7B7669e3a6683EaB25358318fd42a9F2342 + ├─ [0] VM::setEvmVersion("paris") + │ └─ ← [Return] + ├─ [0] VM::expectRevert(custom error 0xf4844814) + │ └─ ← [Return] + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [NotActivated] EvmError: NotActivated + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Test evm version set in `setUp` is accounted in test. + prj.add_test( + "TestSetupEvmVersion.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface EvmVm { + function getEvmVersion() external pure returns (string memory evm); + function setEvmVersion(string calldata evm) external; +} + +interface ICreate2Deployer { + function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address); +} + +EvmVm constant evm = EvmVm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + +contract TestSetupEvmVersion is Test { + function setUp() public { + evm.setEvmVersion("istanbul"); + } + + function test_evm_version_in_setup() public { + vm.createSelectFork(""); + // revert with NotActivated for istanbul + ICreate2Deployer(0x35Da41c476fA5c6De066f20556069096A1F39364).computeAddress(bytes32(0), bytes32(0)); + } +} + "#.replace("", &endpoint), + ); + cmd.forge_fuse() + .args(["test", "--mc", "TestSetupEvmVersion", "-vvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +... +[FAIL: EvmError: NotActivated] test_evm_version_in_setup() ([GAS]) +Traces: + [..] TestSetupEvmVersion::setUp() + ├─ [0] VM::setEvmVersion("istanbul") + │ └─ ← [Return] + └─ ← [Stop] + + [..] TestSetupEvmVersion::test_evm_version_in_setup() + └─ ← [NotActivated] EvmError: NotActivated +... + +"#]]); + + // Test evm version set in constructor is accounted in test. + prj.add_test( + "TestConstructorEvmVersion.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface EvmVm { + function getEvmVersion() external pure returns (string memory evm); + function setEvmVersion(string calldata evm) external; +} + +interface ICreate2Deployer { + function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address); +} + +EvmVm constant evm = EvmVm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + +contract TestConstructorEvmVersion is Test { + constructor() { + evm.setEvmVersion("istanbul"); + } + + function test_evm_version_in_constructor() public { + vm.createSelectFork(""); + // revert with NotActivated for istanbul + ICreate2Deployer(0x35Da41c476fA5c6De066f20556069096A1F39364).computeAddress(bytes32(0), bytes32(0)); + } +} + "#.replace("", &endpoint), + ); + cmd.forge_fuse() + .args(["test", "--mc", "TestConstructorEvmVersion", "-vvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +... +[FAIL: EvmError: NotActivated] test_evm_version_in_constructor() ([GAS]) +Traces: + [..] TestConstructorEvmVersion::test_evm_version_in_constructor() + └─ ← [NotActivated] EvmError: NotActivated +... + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/table.rs b/crates/forge/tests/cli/test_cmd/table.rs new file mode 100644 index 0000000000000..055d8fe3677a8 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/table.rs @@ -0,0 +1,231 @@ +//! Table tests. + +use foundry_test_utils::{forgetest_init, str}; + +forgetest_init!(should_run_table_tests, |prj, cmd| { + prj.initialize_default_contracts(); + prj.add_test( + "CounterTable.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTableTest is Test { + Counter counter = new Counter(); + + uint256[] public fixtureAmount = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + bool[] public fixtureSwap = [true, true, false, true, false, true, false, true, false, true]; + bool[] public fixtureDiffSwap = [true, false]; + function fixtureNoFixture() public returns (address[] memory) { + } + + function tableWithNoParamFail() public { + counter.increment(); + } + + function tableWithParamNoFixtureFail(uint256 noFixture) public { + require(noFixture != 100); + counter.increment(); + } + + function tableSingleParamPass(uint256 amount) public { + require(amount != 100, "Amount cannot be 100"); + counter.increment(); + } + + function tableSingleParamFail(uint256 amount) public { + require(amount != 10, "Amount cannot be 10"); + counter.increment(); + } + + function tableMultipleParamsNoParamFail(uint256 amount, bool noSwap) public { + require(amount != 100 && noSwap, "Amount cannot be 100"); + counter.increment(); + } + + function tableMultipleParamsDifferentFixturesFail(uint256 amount, bool diffSwap) public { + require(amount != 100 && diffSwap, "Amount cannot be 100"); + counter.increment(); + } + + function tableMultipleParamsFail(uint256 amount, bool swap) public { + require(amount == 3 && swap, "Cannot swap"); + counter.increment(); + } + + function tableMultipleParamsPass(uint256 amount, bool swap) public { + if (amount == 3 && swap) { + revert(); + } + counter.increment(); + } +} + "#, + ); + + cmd.args(["test", "--mc", "CounterTable", "-vvvvv"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 8 tests for test/CounterTable.t.sol:CounterTableTest +[FAIL: 2 fixtures defined for diffSwap (expected 10)] tableMultipleParamsDifferentFixturesFail(uint256,bool) ([GAS]) +[FAIL: Cannot swap; counterexample: calldata=0x717892ca00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001 args=[1, true]] tableMultipleParamsFail(uint256,bool) (runs: 1, [AVG_GAS]) +Traces: + [..] CounterTableTest::tableMultipleParamsFail(1, true) + └─ ← [Revert] Cannot swap + +Backtrace: + at CounterTableTest.tableMultipleParamsFail (test/CounterTable.t.sol:[..]:[..]) + +[FAIL: No fixture defined for param noSwap] tableMultipleParamsNoParamFail(uint256,bool) ([GAS]) +[PASS] tableMultipleParamsPass(uint256,bool) (runs: 10, [AVG_GAS]) +Traces: + [..] CounterTableTest::tableMultipleParamsPass(10, true) + ├─ [..] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 + │ └─ ← [Stop] + └─ ← [Stop] + +[FAIL: Amount cannot be 10; counterexample: calldata=0x44fa2375000000000000000000000000000000000000000000000000000000000000000a args=[10]] tableSingleParamFail(uint256) (runs: 10, [AVG_GAS]) +Traces: + [..] CounterTableTest::tableSingleParamFail(10) + └─ ← [Revert] Amount cannot be 10 + +Backtrace: + at CounterTableTest.tableSingleParamFail (test/CounterTable.t.sol:[..]:[..]) + +[PASS] tableSingleParamPass(uint256) (runs: 10, [AVG_GAS]) +Traces: + [..] CounterTableTest::tableSingleParamPass(10) + ├─ [..] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 + │ └─ ← [Stop] + └─ ← [Stop] + +[FAIL: Table test should have at least one parameter] tableWithNoParamFail() ([GAS]) +[FAIL: Table test should have at least one fixture] tableWithParamNoFixtureFail(uint256) ([GAS]) +Suite result: FAILED. 2 passed; 6 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 6 failed, 0 skipped (8 total tests) + +Failing tests: +Encountered 6 failing tests in test/CounterTable.t.sol:CounterTableTest +[FAIL: 2 fixtures defined for diffSwap (expected 10)] tableMultipleParamsDifferentFixturesFail(uint256,bool) ([GAS]) +[FAIL: Cannot swap; counterexample: calldata=0x717892ca00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001 args=[1, true]] tableMultipleParamsFail(uint256,bool) (runs: 1, [AVG_GAS]) +[FAIL: No fixture defined for param noSwap] tableMultipleParamsNoParamFail(uint256,bool) ([GAS]) +[FAIL: Amount cannot be 10; counterexample: calldata=0x44fa2375000000000000000000000000000000000000000000000000000000000000000a args=[10]] tableSingleParamFail(uint256) (runs: 10, [AVG_GAS]) +[FAIL: Table test should have at least one parameter] tableWithNoParamFail() ([GAS]) +[FAIL: Table test should have at least one fixture] tableWithParamNoFixtureFail(uint256) ([GAS]) + +Encountered a total of 6 failing tests, 2 tests succeeded + +Tip: Run `forge test --rerun` to retry only the 6 failed tests + +"#]]); +}); + +// Table tests should show logs and contribute to coverage. +// +forgetest_init!(should_show_logs_and_add_coverage, |prj, cmd| { + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + + function setNumber(uint256 a, uint256 b) public { + if (a == 1) { + number = b + 1; + } else if (a == 2) { + number = b + 2; + } else if (a == 3) { + number = b + 3; + } else { + number = a + b; + } + } +} + "#, + ); + prj.add_test( + "CounterTest.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + struct TestCase { + uint256 a; + uint256 b; + uint256 expected; + } + + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function fixtureNumbers() public pure returns (TestCase[] memory) { + TestCase[] memory entries = new TestCase[](4); + entries[0] = TestCase(1, 5, 6); + entries[1] = TestCase(2, 10, 12); + entries[2] = TestCase(3, 11, 14); + entries[3] = TestCase(4, 11, 15); + return entries; + } + + function tableSetNumberTest(TestCase memory numbers) public { + console.log("expected", numbers.expected); + counter.setNumber(numbers.a, numbers.b); + require(counter.number() == numbers.expected, "test failed"); + } +} + "#, + ); + + cmd.args(["test", "-vvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/CounterTest.t.sol:CounterTest +[PASS] tableSetNumberTest((uint256,uint256,uint256)) (runs: 4, [AVG_GAS]) +Logs: + expected 6 + expected 12 + expected 14 + expected 15 + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse().args(["coverage"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Analysing contracts... +Running tests... + +Ran 1 test for test/CounterTest.t.sol:CounterTest +[PASS] tableSetNumberTest((uint256,uint256,uint256)) (runs: 4, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +╭-----------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/Counter.sol | 100.00% (8/8) | 100.00% (7/7) | 100.00% (6/6) | 100.00% (1/1) | +|-----------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (8/8) | 100.00% (7/7) | 100.00% (6/6) | 100.00% (1/1) | +╰-----------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd/trace.rs b/crates/forge/tests/cli/test_cmd/trace.rs new file mode 100644 index 0000000000000..d68c9a71b3187 --- /dev/null +++ b/crates/forge/tests/cli/test_cmd/trace.rs @@ -0,0 +1,576 @@ +//! Tests for tracing functionality + +use foundry_test_utils::str; + +forgetest_init!(conflicting_signatures, |prj, cmd| { + prj.add_test( + "ConflictingSignatures.t.sol", + r#" +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; + +contract ReturnsNothing { + function func() public pure {} +} + +contract ReturnsString { + function func() public pure returns (string memory) { + return "string"; + } +} + +contract ReturnsUint { + function func() public pure returns (uint256) { + return 1; + } +} + +contract ConflictingSignaturesTest is Test { + ReturnsNothing retsNothing; + ReturnsString retsString; + ReturnsUint retsUint; + + function setUp() public { + retsNothing = new ReturnsNothing(); + retsString = new ReturnsString(); + retsUint = new ReturnsUint(); + } + + /// Tests that traces are decoded properly when multiple + /// functions have the same 4byte signature, but different + /// return values. + function testTraceWithConflictingSignatures() public { + retsNothing.func(); + retsString.func(); + retsUint.func(); + } +} +"#, + ); + + cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/ConflictingSignatures.t.sol:ConflictingSignaturesTest +[PASS] testTraceWithConflictingSignatures() ([GAS]) +Traces: + [..] ConflictingSignaturesTest::setUp() + ├─ [..] → new ReturnsNothing@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 106 bytes of code + ├─ [..] → new ReturnsString@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ └─ ← [Return] 334 bytes of code + ├─ [..] → new ReturnsUint@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a + │ └─ ← [Return] 175 bytes of code + └─ ← [Stop] + + [..] ConflictingSignaturesTest::testTraceWithConflictingSignatures() + ├─ [..] ReturnsNothing::func() [staticcall] + │ └─ ← [Stop] + ├─ [..] ReturnsString::func() [staticcall] + │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006737472696e670000000000000000000000000000000000000000000000000000 + ├─ [..] ReturnsUint::func() [staticcall] + │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(trace_test, |prj, cmd| { + prj.add_test( + "Trace.t.sol", + r#" +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; + +contract RecursiveCall { + TraceTest factory; + + event Depth(uint256 depth); + event ChildDepth(uint256 childDepth); + event CreatedChild(uint256 childDepth); + + constructor(address _factory) { + factory = TraceTest(_factory); + } + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + this.negativeNum(); + return neededDepth; + } + + uint256 childDepth = this.recurseCall(neededDepth, depth + 1); + emit ChildDepth(childDepth); + + this.someCall(); + emit Depth(depth); + + return depth; + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + + RecursiveCall child = factory.create(); + emit CreatedChild(depth + 1); + + uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); + emit ChildDepth(childDepth); + emit Depth(depth); + + return depth; + } + + function someCall() public pure {} + + function negativeNum() public pure returns (int256) { + return -1000000000; + } +} + +contract TraceTest is Test { + uint256 nodeId = 0; + RecursiveCall first; + + function setUp() public { + first = this.create(); + } + + function create() public returns (RecursiveCall) { + RecursiveCall node = new RecursiveCall(address(this)); + vm.label(address(node), string(abi.encodePacked("Node ", uintToString(nodeId++)))); + + return node; + } + + function testRecurseCall() public { + first.recurseCall(8, 0); + } + + function testRecurseCreate() public { + first.recurseCreate(8, 0); + } +} + +function uintToString(uint256 value) pure returns (string memory) { + // Taken from OpenZeppelin + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); +} +"#, + ); + + cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/Trace.t.sol:TraceTest +[PASS] testRecurseCall() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCall() + ├─ [..] Node 0::recurseCall(8, 0) + │ ├─ [..] Node 0::recurseCall(8, 1) + │ │ ├─ [..] Node 0::recurseCall(8, 2) + │ │ │ ├─ [..] Node 0::recurseCall(8, 3) + │ │ │ │ ├─ [..] Node 0::recurseCall(8, 4) + │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 5) + │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 6) + │ │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 7) + │ │ │ │ │ │ │ │ ├─ [..] Node 0::recurseCall(8, 8) + │ │ │ │ │ │ │ │ │ ├─ [..] Node 0::negativeNum() [staticcall] + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] -1000000000 [-1e9] + │ │ │ │ │ │ │ │ │ └─ ← [Return] 8 + │ │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ │ │ ├─ emit Depth(depth: 7) + │ │ │ │ │ │ │ │ └─ ← [Return] 7 + │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 7) + │ │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ │ ├─ emit Depth(depth: 6) + │ │ │ │ │ │ │ └─ ← [Return] 6 + │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 6) + │ │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ │ ├─ emit Depth(depth: 5) + │ │ │ │ │ │ └─ ← [Return] 5 + │ │ │ │ │ ├─ emit ChildDepth(childDepth: 5) + │ │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ ├─ emit Depth(depth: 4) + │ │ │ │ │ └─ ← [Return] 4 + │ │ │ │ ├─ emit ChildDepth(childDepth: 4) + │ │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ │ └─ ← [Stop] + │ │ │ │ ├─ emit Depth(depth: 3) + │ │ │ │ └─ ← [Return] 3 + │ │ │ ├─ emit ChildDepth(childDepth: 3) + │ │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ │ └─ ← [Stop] + │ │ │ ├─ emit Depth(depth: 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ └─ ← [Stop] + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ [..] Node 0::someCall() [staticcall] + │ │ └─ ← [Stop] + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +[PASS] testRecurseCreate() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCreate() + ├─ [..] Node 0::recurseCreate(8, 0) + │ ├─ [..] TraceTest::create() + │ │ ├─ [..] → new Node 1@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ │ │ ├─ storage changes: + │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ └─ ← [Return] 1911 bytes of code + │ │ ├─ [0] VM::label(Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], "Node 1") + │ │ │ └─ ← [Return] + │ │ ├─ storage changes: + │ │ │ @ 32: 1 → 2 + │ │ └─ ← [Return] Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b] + │ ├─ emit CreatedChild(childDepth: 1) + │ ├─ [..] Node 1::recurseCreate(8, 1) + │ │ ├─ [..] TraceTest::create() + │ │ │ ├─ [..] → new Node 2@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a + │ │ │ │ ├─ storage changes: + │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ ├─ [0] VM::label(Node 2: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], "Node 2") + │ │ │ │ └─ ← [Return] + │ │ │ ├─ storage changes: + │ │ │ │ @ 32: 2 → 3 + │ │ │ └─ ← [Return] Node 2: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a] + │ │ ├─ emit CreatedChild(childDepth: 2) + │ │ ├─ [..] Node 2::recurseCreate(8, 2) + │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ ├─ [..] → new Node 3@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 + │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ ├─ [0] VM::label(Node 3: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], "Node 3") + │ │ │ │ │ └─ ← [Return] + │ │ │ │ ├─ storage changes: + │ │ │ │ │ @ 32: 3 → 4 + │ │ │ │ └─ ← [Return] Node 3: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9] + │ │ │ ├─ emit CreatedChild(childDepth: 3) + │ │ │ ├─ [..] Node 3::recurseCreate(8, 3) + │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ ├─ [..] → new Node 4@0xc7183455a4C133Ae270771860664b6B7ec320bB1 + │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ ├─ [0] VM::label(Node 4: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], "Node 4") + │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ @ 32: 4 → 5 + │ │ │ │ │ └─ ← [Return] Node 4: [0xc7183455a4C133Ae270771860664b6B7ec320bB1] + │ │ │ │ ├─ emit CreatedChild(childDepth: 4) + │ │ │ │ ├─ [..] Node 4::recurseCreate(8, 4) + │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ ├─ [..] → new Node 5@0xa0Cb889707d426A7A386870A03bc70d1b0697598 + │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ ├─ [0] VM::label(Node 5: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], "Node 5") + │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ @ 32: 5 → 6 + │ │ │ │ │ │ └─ ← [Return] Node 5: [0xa0Cb889707d426A7A386870A03bc70d1b0697598] + │ │ │ │ │ ├─ emit CreatedChild(childDepth: 5) + │ │ │ │ │ ├─ [..] Node 5::recurseCreate(8, 5) + │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ ├─ [..] → new Node 6@0x1d1499e622D69689cdf9004d05Ec547d650Ff211 + │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ ├─ [0] VM::label(Node 6: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], "Node 6") + │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ @ 32: 6 → 7 + │ │ │ │ │ │ │ └─ ← [Return] Node 6: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211] + │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 6) + │ │ │ │ │ │ ├─ [..] Node 6::recurseCreate(8, 6) + │ │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ │ ├─ [..] → new Node 7@0xA4AD4f68d0b91CFD19687c881e50f3A00242828c + │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ │ ├─ [0] VM::label(Node 7: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], "Node 7") + │ │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ @ 32: 7 → 8 + │ │ │ │ │ │ │ │ └─ ← [Return] Node 7: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c] + │ │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 7) + │ │ │ │ │ │ │ ├─ [..] Node 7::recurseCreate(8, 7) + │ │ │ │ │ │ │ │ ├─ [..] TraceTest::create() + │ │ │ │ │ │ │ │ │ ├─ [..] → new Node 8@0x03A6a84cD762D9707A21605b548aaaB891562aAb + │ │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] 1911 bytes of code + │ │ │ │ │ │ │ │ │ ├─ [0] VM::label(Node 8: [0x03A6a84cD762D9707A21605b548aaaB891562aAb], "Node 8") + │ │ │ │ │ │ │ │ │ │ └─ ← [Return] + │ │ │ │ │ │ │ │ │ ├─ storage changes: + │ │ │ │ │ │ │ │ │ │ @ 32: 8 → 9 + │ │ │ │ │ │ │ │ │ └─ ← [Return] Node 8: [0x03A6a84cD762D9707A21605b548aaaB891562aAb] + │ │ │ │ │ │ │ │ ├─ emit CreatedChild(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ [..] Node 8::recurseCreate(8, 8) + │ │ │ │ │ │ │ │ │ └─ ← [Return] 8 + │ │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 8) + │ │ │ │ │ │ │ │ ├─ emit Depth(depth: 7) + │ │ │ │ │ │ │ │ └─ ← [Return] 7 + │ │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 7) + │ │ │ │ │ │ │ ├─ emit Depth(depth: 6) + │ │ │ │ │ │ │ └─ ← [Return] 6 + │ │ │ │ │ │ ├─ emit ChildDepth(childDepth: 6) + │ │ │ │ │ │ ├─ emit Depth(depth: 5) + │ │ │ │ │ │ └─ ← [Return] 5 + │ │ │ │ │ ├─ emit ChildDepth(childDepth: 5) + │ │ │ │ │ ├─ emit Depth(depth: 4) + │ │ │ │ │ └─ ← [Return] 4 + │ │ │ │ ├─ emit ChildDepth(childDepth: 4) + │ │ │ │ ├─ emit Depth(depth: 3) + │ │ │ │ └─ ← [Return] 3 + │ │ │ ├─ emit ChildDepth(childDepth: 3) + │ │ │ ├─ emit Depth(depth: 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(trace_test_detph, |prj, cmd| { + prj.add_test( + "Trace.t.sol", + r#" +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; + +contract RecursiveCall { + TraceTest factory; + + event Depth(uint256 depth); + event ChildDepth(uint256 childDepth); + event CreatedChild(uint256 childDepth); + + constructor(address _factory) { + factory = TraceTest(_factory); + } + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + this.negativeNum(); + return neededDepth; + } + + uint256 childDepth = this.recurseCall(neededDepth, depth + 1); + emit ChildDepth(childDepth); + + this.someCall(); + emit Depth(depth); + + return depth; + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + + RecursiveCall child = factory.create(); + emit CreatedChild(depth + 1); + + uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); + emit ChildDepth(childDepth); + emit Depth(depth); + + return depth; + } + + function someCall() public pure {} + + function negativeNum() public pure returns (int256) { + return -1000000000; + } +} + +contract TraceTest is Test { + uint256 nodeId = 0; + RecursiveCall first; + + function setUp() public { + first = this.create(); + } + + function create() public returns (RecursiveCall) { + RecursiveCall node = new RecursiveCall(address(this)); + vm.label(address(node), string(abi.encodePacked("Node ", uintToString(nodeId++)))); + + return node; + } + + function testRecurseCall() public { + first.recurseCall(8, 0); + } + + function testRecurseCreate() public { + first.recurseCreate(8, 0); + } +} + +function uintToString(uint256 value) pure returns (string memory) { + // Taken from OpenZeppelin + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); +} +"#, + ); + + cmd.args(["test", "-vvvvv", "--trace-depth", "3"]).assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/Trace.t.sol:TraceTest +[PASS] testRecurseCall() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCall() + ├─ [..] Node 0::recurseCall(8, 0) + │ ├─ [..] Node 0::recurseCall(8, 1) + │ │ ├─ [..] Node 0::recurseCall(8, 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ [..] Node 0::someCall() [staticcall] + │ │ │ └─ ← [Stop] + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ [..] Node 0::someCall() [staticcall] + │ │ └─ ← [Stop] + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +[PASS] testRecurseCreate() ([GAS]) +Traces: + [..] TraceTest::setUp() + ├─ [..] TraceTest::create() + │ ├─ [..] → new Node 0@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ │ └─ ← [Return] 1911 bytes of code + │ ├─ [0] VM::label(Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "Node 0") + │ │ └─ ← [Return] + │ └─ ← [Return] Node 0: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] + └─ ← [Stop] + + [..] TraceTest::testRecurseCreate() + ├─ [..] Node 0::recurseCreate(8, 0) + │ ├─ [..] TraceTest::create() + │ │ ├─ [405132] → new Node 1@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ │ │ ├─ storage changes: + │ │ │ │ @ 0: 0 → 0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496 + │ │ │ └─ ← [Return] 1911 bytes of code + │ │ ├─ [0] VM::label(Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], "Node 1") + │ │ │ └─ ← [Return] + │ │ ├─ storage changes: + │ │ │ @ 32: 1 → 2 + │ │ └─ ← [Return] Node 1: [0x2e234DAe75C793f67A35089C9d99245E1C58470b] + │ ├─ emit CreatedChild(childDepth: 1) + │ ├─ [..] Node 1::recurseCreate(8, 1) + │ │ ├─ [..] TraceTest::create() + │ │ │ ├─ storage changes: + │ │ │ │ @ 32: 2 → 3 + │ │ │ └─ ← [Return] Node 2: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a] + │ │ ├─ emit CreatedChild(childDepth: 2) + │ │ ├─ [..] Node 2::recurseCreate(8, 2) + │ │ │ └─ ← [Return] 2 + │ │ ├─ emit ChildDepth(childDepth: 2) + │ │ ├─ emit Depth(depth: 1) + │ │ └─ ← [Return] 1 + │ ├─ emit ChildDepth(childDepth: 1) + │ ├─ emit Depth(depth: 0) + │ └─ ← [Return] 0 + └─ ← [Stop] + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index 944abed57ff30..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -2,6 +2,7 @@ // Test cache is invalidated when `forge build` if optimize test option toggled. forgetest_init!(toggle_invalidate_cache_on_build, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -35,6 +36,7 @@ Compiling 23 files with [..] // Test cache is invalidated when `forge test` if optimize test option toggled. forgetest_init!(toggle_invalidate_cache_on_test, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -73,7 +75,6 @@ Compiling 21 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_contract_with_no_interface, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -93,8 +94,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -121,9 +121,8 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); - // All 20 files are compiled on first run. + ); + // All files are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... Compiling 21 files with [..] @@ -148,8 +147,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and both tests fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -177,8 +175,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and only one test fails. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -200,7 +197,6 @@ Compiling 1 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_contract_with_interface, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -216,8 +212,7 @@ interface CounterIf { function increment() external; } "#, - ) - .unwrap(); + ); prj.add_source( "Counter.sol", r#" @@ -235,8 +230,7 @@ contract Counter is CounterIf { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -263,8 +257,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // All 21 files are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -287,8 +280,7 @@ interface CounterIf { function increment() external; } "#, - ) - .unwrap(); + ); // All 3 files (interface, implementation and test) are compiled. cmd.with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -316,8 +308,7 @@ contract Counter is CounterIf { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and both tests fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -340,7 +331,6 @@ Compiling 1 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_without_inheritance, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -360,8 +350,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "mock/CounterMock.sol", @@ -385,8 +374,7 @@ contract CounterMock { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", r#" @@ -412,8 +400,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // 20 files plus one mock file are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -439,8 +426,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and both tests fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -473,8 +459,7 @@ contract CounterMock { } } "#, - ) - .unwrap(); + ); // Assert that mock and test files are compiled and no test fails. cmd.with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -497,7 +482,6 @@ Compiling 2 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_with_inheritance, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -517,8 +501,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "mock/CounterMock.sol", @@ -528,8 +511,7 @@ import {Counter} from "src/Counter.sol"; contract CounterMock is Counter { } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", r#" @@ -555,8 +537,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // 20 files plus one mock file are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -572,7 +553,7 @@ Compiling 22 files with [..] contract Counter { uint256 public number; - function setNumber(uint256 newNumber) public virtual { + function setNumber(uint256) public virtual { number = 12345; } @@ -582,13 +563,12 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert Counter source contract and CounterTest test contract (as it imports mock) are // compiled and both tests fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... -Compiling 2 files with [..] +Compiling 3 files with [..] ... [FAIL: assertion failed: 12347 != 1] test_Increment() (gas: [..]) [FAIL: assertion failed: 12345 != 1] test_SetNumber() (gas: [..]) @@ -612,8 +592,7 @@ contract CounterMock is Counter { } } "#, - ) - .unwrap(); + ); // Assert that CounterMock and CounterTest files are compiled and no test fails. cmd.with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -636,7 +615,6 @@ Compiling 2 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_to_non_mock, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -656,8 +634,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "mock/CounterMock.sol", @@ -667,8 +644,7 @@ import {Counter} from "src/Counter.sol"; contract CounterMock is Counter { } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", r#" @@ -694,8 +670,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // 20 files plus one mock file are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -728,8 +703,7 @@ contract CounterMock { } } "#, - ) - .unwrap(); + ); // Assert that CounterMock and CounterTest files are compiled and tests fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -742,6 +716,82 @@ Compiling 2 files with [..] "#]]); }); +// +// - CounterMock contract is Counter contract +// - CounterMock declared in CounterTest +// +// ├── src +// │ └── Counter.sol +// └── test +// ├── Counter.t.sol +forgetest_init!(preprocess_mock_declared_in_test_contract, |prj, cmd| { + prj.update_config(|config| { + config.dynamic_test_linking = true; + }); + + prj.add_source( + "Counter.sol", + r#" +contract Counter { + function add(uint256 x, uint256 y) public pure returns (uint256) { + return x + y; + } +} + "#, + ); + + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "src/Counter.sol"; + +contract CounterMock is Counter {} + +contract CounterTest is Test { + function test_add() public { + CounterMock impl = new CounterMock(); + assertEq(impl.add(2, 2), 4); + } +} + "#, + ); + // 20 files plus one mock file are compiled on first run. + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +Compiling 21 files with [..] +... + +"#]]); + cmd.with_no_redact().assert_success().stdout_eq(str![[r#" +... +No files changed, compilation skipped +... + +"#]]); + + // Change Counter implementation to fail tests. + prj.add_source( + "Counter.sol", + r#" +contract Counter { + function add(uint256 x, uint256 y) public pure returns (uint256) { + return x + y + 1; + } +} + "#, + ); + // Assert that Counter and CounterTest files are compiled and tests fail. + cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" +... +Compiling 2 files with [..] +... +[FAIL: assertion failed: 5 != 4] test_add() (gas: [..]) +... + +"#]]); +}); + // ├── src // │ ├── CounterA.sol // │ ├── CounterB.sol @@ -751,7 +801,6 @@ Compiling 2 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_multiple_contracts_with_constructors, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -771,8 +820,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterA.sol", r#" @@ -790,8 +838,7 @@ contract CounterA { } } "#, - ) - .unwrap(); + ); // Contract with constructor args without name. prj.add_source( "CounterB.sol", @@ -808,8 +855,7 @@ contract CounterB { } } "#, - ) - .unwrap(); + ); prj.add_source( "v1/Counter.sol", r#" @@ -825,8 +871,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -869,8 +914,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // 22 files plus one mock file are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -901,8 +945,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Only v1/Counter should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -935,8 +978,7 @@ contract CounterA { } } "#, - ) - .unwrap(); + ); // Only CounterA should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -967,8 +1009,7 @@ contract CounterB { } } "#, - ) - .unwrap(); + ); // Only CounterB should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -999,8 +1040,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Only Counter should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1017,8 +1057,7 @@ Compiling 1 files with [..] }); // Test preprocessing contracts with payable constructor, value and salt named args. -forgetest_init!(preprocess_contracts_with_payable_constructor_and_salt, |prj, cmd| { - prj.wipe_contracts(); +forgetest_init!(flaky_preprocess_contracts_with_payable_constructor_and_salt, |prj, cmd| { prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1042,8 +1081,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterWithSalt.sol", r#" @@ -1063,8 +1101,7 @@ contract CounterWithSalt { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -1082,12 +1119,13 @@ contract CounterTest is Test { function test_Increment_In_Counter_With_Salt() public { CounterWithSalt counter = new CounterWithSalt{value: 111, salt: bytes32("preprocess_counter_with_salt")}(1); - assertEq(address(counter), 0x3Efe9ecFc73fB3baB7ECafBB40D3e134260Be6AB); + assertGt(uint160(address(counter)), 0); + counter.increment(); + assertEq(counter.number(), 112); } } "#, - ) - .unwrap(); + ); cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -1119,8 +1157,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Only Counter should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1152,15 +1189,14 @@ contract CounterWithSalt { } } "#, - ) - .unwrap(); + ); // Only Counter should be compiled and test should fail. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... Compiling 1 files with [..] ... [FAIL: assertion failed: 113 != 112] test_Increment_In_Counter() (gas: [..]) -[FAIL: assertion failed: 0x6cDcb015cFcAd0C23560322EdEE8f324520E4b93 != 0x3Efe9ecFc73fB3baB7ECafBB40D3e134260Be6AB] test_Increment_In_Counter_With_Salt() (gas: [..]) +[FAIL: assertion failed: 113 != 112] test_Increment_In_Counter_With_Salt() (gas: [..]) ... "#]]); @@ -1168,7 +1204,6 @@ Compiling 1 files with [..] // Counter contract with constructor reverts and emitted events. forgetest_init!(preprocess_contract_with_require_and_emit, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1186,8 +1221,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -1209,8 +1243,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // All 20 files are compiled on first run. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -1233,8 +1266,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and revert test fails. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1260,8 +1292,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and revert test fails. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1287,8 +1318,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); // Assert that only 1 file is compiled (Counter source contract) and emit test fails. cmd.with_no_redact().assert_failure().stdout_eq(str![[r#" ... @@ -1303,7 +1333,6 @@ Compiling 1 files with [..] // forgetest_init!(preprocess_contract_with_constructor_args_struct, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1320,8 +1349,7 @@ contract Counter { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -1335,8 +1363,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // All 20 files should properly compile. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -1348,6 +1375,7 @@ Compiling 21 files with [..] // Test preprocessed contracts with decode internal fns. forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1377,8 +1405,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); cmd.args(["test", "--decode-internal", "-vvvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -1400,8 +1427,6 @@ Traces: ├─ [..] Counter::number() [staticcall] │ └─ ← [Return] 1 ├─ [..] StdAssertions::assertEq(1, 1) - │ ├─ [0] VM::assertEq(1, 1) [staticcall] - │ │ └─ ← [Return] │ └─ ← └─ ← [Stop] @@ -1415,7 +1440,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // // Preprocess test contracts with try constructor statements. forgetest_init!(preprocess_contract_with_try_ctor_stmt, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1427,8 +1451,7 @@ contract CounterA { uint256 number; } "#, - ) - .unwrap(); + ); prj.add_source( "CounterB.sol", r#" @@ -1440,8 +1463,7 @@ contract CounterB { } } "#, - ) - .unwrap(); + ); prj.add_source( "CounterC.sol", r#" @@ -1453,8 +1475,7 @@ contract CounterC { } } "#, - ) - .unwrap(); + ); prj.add_test( "Counter.t.sol", @@ -1492,8 +1513,7 @@ contract CounterTest is Test { } } "#, - ) - .unwrap(); + ); // All 23 files should properly compile, tests pass. cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... @@ -1519,8 +1539,7 @@ contract CounterB { } } "#, - ) - .unwrap(); + ); // Only CounterB should compile. cmd.assert_failure().stdout_eq(str![[r#" ... @@ -1546,8 +1565,7 @@ contract CounterC { } } "#, - ) - .unwrap(); + ); // Only CounterC should compile. cmd.assert_failure().stdout_eq(str![[r#" ... @@ -1573,8 +1591,7 @@ contract CounterC { } } "#, - ) - .unwrap(); + ); // Only CounterC should compile and revert. cmd.assert_failure().stdout_eq(str![[r#" ... @@ -1588,3 +1605,169 @@ Compiling 1 files with [..] "#]]); }); + +// +// Preprocess test contracts when active prank. +forgetest_init!(preprocess_contract_with_active_prank, |prj, cmd| { + prj.update_config(|config| { + config.dynamic_test_linking = true; + }); + + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public number; + address public deployer; + + constructor() { + deployer = msg.sender; + } +} + "#, + ); + + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + function test_deployer() public { + address deployer = makeAddr("deployer"); + vm.startPrank(deployer); + Counter counter = new Counter{salt: 0}(); + assertEq(counter.deployer(), deployer); + } +} + "#, + ); + // Test should pass. + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] test_deployer() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Preprocess test contracts with try constructor statements that bind return type. +forgetest_init!(preprocess_contract_with_try_ctor_stmt_and_returns, |prj, cmd| { + prj.update_config(|config| { + config.dynamic_test_linking = true; + }); + + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 number; + constructor(uint256 a) payable { + require(a > 0, "ctor failure"); + number = a; + } +} + "#, + ); + prj.add_test( + "CounterReturns.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterReturnsTest is Test { + function test_try_counter_creation_returns_custom_type() public { + try new Counter(1) returns (Counter c) { + c; + } catch { + revert(); + } + } +} + "#, + ); + + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +Compiling 21 files with [..] +... +[PASS] test_try_counter_creation_returns_custom_type() (gas: [..]) +... + +"#]]); + + // Change Counter to fail test in try statement, only Counter contract should be compiled. + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 number; + constructor(uint256 a) payable { + require(a == 0, "ctor failure"); + number = a; + } +} + "#, + ); + cmd.assert_failure().stdout_eq(str![[r#" +... +Compiling 1 files with [..] +... +[FAIL: ctor failure] test_try_counter_creation_returns_custom_type() (gas: [..]) +... + +"#]]); +}); + +// Test that `type(Contract).creationCode` can be used in view functions. +// https://github.com/foundry-rs/foundry/issues/13086 +forgetest_init!(preprocess_creation_code_in_view_function, |prj, cmd| { + prj.update_config(|config| { + config.dynamic_test_linking = true; + }); + + prj.add_source( + "Target.sol", + r#" +contract Target { + uint256 public immutable value; + constructor(uint256 _value) { value = _value; } +} + "#, + ); + + prj.add_test( + "Target.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Target} from "../src/Target.sol"; + +contract TargetTest is Test { + function computeAddress(address factory, uint256 salt, uint256 value) internal view returns (address) { + bytes32 hash = keccak256( + abi.encodePacked( + bytes1(0xff), + factory, + salt, + keccak256(abi.encodePacked(type(Target).creationCode, abi.encode(value))) + ) + ); + return address(uint160(uint256(hash))); + } + + function testComputeAddress() public view { + computeAddress(address(this), 1, 100); + } +} + "#, + ); + + cmd.args(["build"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/utils.rs b/crates/forge/tests/cli/utils.rs index 35b1bf73a7bfd..55b6e74cbfa59 100644 --- a/crates/forge/tests/cli/utils.rs +++ b/crates/forge/tests/cli/utils.rs @@ -89,12 +89,12 @@ impl EnvExternalities { }) } - pub fn mumbai() -> Option { + pub fn amoy() -> Option { Some(Self { - chain: NamedChain::PolygonMumbai, - rpc: network_rpc_key("mumbai")?, - pk: network_private_key("mumbai")?, - etherscan: etherscan_key(NamedChain::PolygonMumbai)?, + chain: NamedChain::PolygonAmoy, + rpc: network_rpc_key("amoy")?, + pk: network_private_key("amoy")?, + etherscan: etherscan_key(NamedChain::PolygonAmoy)?, verifier: "etherscan".to_string(), }) } diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index a6828140debef..c5f013b71c296 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -22,8 +22,7 @@ contract Unique {{ }} "# ), - ) - .unwrap(); + ); } fn add_verify_target(prj: &TestProject) { @@ -35,8 +34,7 @@ contract Verify is Unique { function doStuff() external {} } "#, - ) - .unwrap(); + ); } fn add_single_verify_target_file(prj: &TestProject) { @@ -52,7 +50,7 @@ function doStuff() external {{}} "# ); - prj.add_source("Verify.sol", &contract).unwrap(); + prj.add_source("Verify.sol", &contract); } fn add_verify_target_with_constructor(prj: &TestProject) { @@ -69,19 +67,17 @@ contract Verify is Unique { constructor(SomeStruct memory st, address owner) {} } "#, - ) - .unwrap(); + ); } -#[expect(clippy::disallowed_macros)] fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { // Give Etherscan some time to verify the contract. Retry::new(retries, Duration::from_secs(30)).run(|| -> eyre::Result<()> { let output = cmd.execute(); let out = String::from_utf8_lossy(&output.stdout); - println!("{out}"); + test_debug!("{out}"); if out.contains("Contract successfully verified") { - return Ok(()) + return Ok(()); } eyre::bail!( "Failed to get verification, stdout: {}, stderr: {}", @@ -158,11 +154,10 @@ fn deploy_contract( .unwrap_or_else(|| panic!("Failed to parse deployer {output}")) } -#[expect(clippy::disallowed_macros)] fn verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { - println!("verifying on {}", info.chain); + test_debug!("verifying on {}", info.chain); let contract_path = "src/Verify.sol:Verify"; let address = deploy_contract(&info, contract_path, prj, &mut cmd); @@ -189,11 +184,10 @@ fn verify_on_chain(info: Option, prj: TestProject, mut cmd: Te } } -#[expect(clippy::disallowed_macros)] fn guess_constructor_args(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { - println!("verifying on {}", info.chain); + test_debug!("verifying on {}", info.chain); add_unique(&prj); add_verify_target_with_constructor(&prj); @@ -216,13 +210,13 @@ fn guess_constructor_args(info: Option, prj: TestProject, mut cmd.forge_fuse().arg("verify-contract").root_arg().args([ "--rpc-url".to_string(), - info.rpc.to_string(), + info.rpc.clone(), address, contract_path.to_string(), "--etherscan-api-key".to_string(), - info.etherscan.to_string(), + info.etherscan.clone(), "--verifier".to_string(), - info.verifier.to_string(), + info.verifier.clone(), "--guess-constructor-args".to_string(), ]); @@ -230,12 +224,11 @@ fn guess_constructor_args(info: Option, prj: TestProject, mut } } -#[expect(clippy::disallowed_macros)] /// Executes create --verify on the given chain fn create_verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { - println!("verifying on {}", info.chain); + test_debug!("verifying on {}", info.chain); add_single_verify_target_file(&prj); let contract_path = "src/Verify.sol:Verify"; @@ -317,3 +310,53 @@ forgetest!(can_guess_constructor_args, |prj, cmd| { forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| { verify_on_chain(EnvExternalities::sepolia_empty_verifier(), prj, cmd); }); + +// Tests that verify properly validates verifier arguments. +// +forgetest_init!(can_validate_verifier_settings, |prj, cmd| { + prj.initialize_default_contracts(); + // Build the project to create the cache. + cmd.forge_fuse().arg("build").assert_success(); + // No verifier URL. + cmd.forge_fuse() + .args([ + "verify-contract", + "--rpc-url", + "https://rpc.sepolia-api.lisk.com", + "--verifier", + "blockscout", + "0x19b248616E4964f43F611b5871CE1250f360E9d3", + "src/Counter.sol:Counter", + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: No verifier URL specified for verifier blockscout + +"#]]); + + // Unknown Etherscan chain. + cmd.forge_fuse() + .args([ + "verify-contract", + "--rpc-url", + "https://rpc.sepolia-api.lisk.com", + "--verifier", + "etherscan", + "0x19b248616E4964f43F611b5871CE1250f360E9d3", + "src/Counter.sol:Counter", + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: No known Etherscan API URL for chain `4202`. To fix this, please: +1. Specify a `url` when using Etherscan verifier +2. Verify the chain `4202` is correct + +"#]]); + + cmd.forge_fuse().args(["verify-contract", "--rpc-url", "https://rpc.sepolia-api.lisk.com", "--verifier", "blockscout", "--verifier-url", "https://sepolia-blockscout.lisk.com/api", "0x19b248616E4964f43F611b5871CE1250f360E9d3", "src/Counter.sol:Counter"]).assert_success().stdout_eq(str![[r#" +Start verifying contract `0x19b248616E4964f43F611b5871CE1250f360E9d3` deployed on 4202 + +Contract [src/Counter.sol:Counter] "0x19b248616E4964f43F611b5871CE1250f360E9d3" is already verified. Skipping verification. + +"#]]); +}); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 9ef4d6ddf4606..c8368f324f2c8 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -1,14 +1,16 @@ +use alloy_chains::Chain; use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; use foundry_config::Config; use foundry_test_utils::{ + TestCommand, TestProject, + etherscan::fetch_etherscan_source_flattened, forgetest_async, rpc::{next_etherscan_api_key, next_http_archive_rpc_url}, util::OutputExt, - TestCommand, TestProject, }; #[expect(clippy::too_many_arguments)] -fn test_verify_bytecode( +async fn test_verify_bytecode( prj: TestProject, mut cmd: TestCommand, addr: &str, @@ -18,19 +20,17 @@ fn test_verify_bytecode( verifier: &str, verifier_url: &str, expected_matches: (&str, &str), + chain: Chain, ) { let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); - // fetch and flatten source code - let source_code = cmd - .cast_fuse() - .args(["source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) - .assert_success() - .get_output() - .stdout_lossy(); + // fetch and flatten source code using the library directly + let source_code = fetch_etherscan_source_flattened(addr, ðerscan_key, chain) + .await + .expect("failed to fetch source code from etherscan"); - prj.add_source(contract_name, &source_code).unwrap(); + prj.add_source(contract_name, &source_code); prj.write_config(config); let etherscan_key = next_etherscan_api_key(); @@ -55,14 +55,18 @@ fn test_verify_bytecode( let output = cmd.forge_fuse().args(args).assert_success().get_output().stdout_lossy(); - assert!(output - .contains(format!("Creation code matched with status {}", expected_matches.0).as_str())); - assert!(output - .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); + assert!( + output + .contains(format!("Creation code matched with status {}", expected_matches.0).as_str()) + ); + assert!( + output + .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str()) + ); } #[expect(clippy::too_many_arguments)] -fn test_verify_bytecode_with_ignore( +async fn test_verify_bytecode_with_ignore( prj: TestProject, mut cmd: TestCommand, addr: &str, @@ -72,28 +76,17 @@ fn test_verify_bytecode_with_ignore( verifier_url: &str, expected_matches: (&str, &str), ignore: &str, - chain: &str, + chain: Chain, ) { let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); - // fetch and flatten source code - let source_code = cmd - .cast_fuse() - .args([ - "source", - addr, - "--flatten", - "--etherscan-api-key", - ðerscan_key, - "--chain", - chain, - ]) - .assert_success() - .get_output() - .stdout_lossy(); + // fetch and flatten source code using the library directly + let source_code = fetch_etherscan_source_flattened(addr, ðerscan_key, chain) + .await + .expect("failed to fetch source code from etherscan"); - prj.add_source(contract_name, &source_code).unwrap(); + prj.add_source(contract_name, &source_code); prj.write_config(config); let output = cmd @@ -128,14 +121,21 @@ fn test_verify_bytecode_with_ignore( } if ignore == "runtime" { - assert!(!output - .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); + assert!( + !output.contains( + format!("Runtime code matched with status {}", expected_matches.1).as_str() + ) + ); } else { - assert!(output - .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); + assert!( + output.contains( + format!("Runtime code matched with status {}", expected_matches.1).as_str() + ) + ); } } -forgetest_async!(can_verify_bytecode_no_metadata, |prj, cmd| { + +forgetest_async!(flaky_verify_bytecode_no_metadata, |prj, cmd| { test_verify_bytecode( prj, cmd, @@ -151,12 +151,14 @@ forgetest_async!(can_verify_bytecode_no_metadata, |prj, cmd| { ..Default::default() }, "etherscan", - "https://api.etherscan.io/api", + "https://api.etherscan.io/v2/api?chainid=1", ("partial", "partial"), - ); + Chain::mainnet(), + ) + .await; }); -forgetest_async!(can_verify_bytecode_with_metadata, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_with_metadata, |prj, cmd| { test_verify_bytecode( prj, cmd, @@ -170,13 +172,15 @@ forgetest_async!(can_verify_bytecode_with_metadata, |prj, cmd| { ..Default::default() }, "etherscan", - "https://api.etherscan.io/api", + "https://api.etherscan.io/v2/api?chainid=1", ("partial", "partial"), - ); + Chain::mainnet(), + ) + .await; }); // Test non-CREATE2 deployed contract with blockscout -forgetest_async!(can_verify_bytecode_with_blockscout, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_with_blockscout, |prj, cmd| { test_verify_bytecode( prj, cmd, @@ -192,11 +196,13 @@ forgetest_async!(can_verify_bytecode_with_blockscout, |prj, cmd| { "blockscout", "https://eth.blockscout.com/api", ("partial", "partial"), - ); + Chain::mainnet(), + ) + .await; }); // Test CREATE2 deployed contract with blockscout -forgetest_async!(can_vb_create2_with_blockscout, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_create2_with_blockscout, |prj, cmd| { test_verify_bytecode( prj, cmd, @@ -214,11 +220,13 @@ forgetest_async!(can_vb_create2_with_blockscout, |prj, cmd| { "blockscout", "https://eth.blockscout.com/api", ("partial", "partial"), - ); + Chain::mainnet(), + ) + .await; }); // Test `--constructor-args` -forgetest_async!(can_verify_bytecode_with_constructor_args, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_with_constructor_args, |prj, cmd| { let constructor_args = vec![ "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338", @@ -237,13 +245,15 @@ forgetest_async!(can_verify_bytecode_with_constructor_args, |prj, cmd| { ..Default::default() }, "etherscan", - "https://api.etherscan.io/api", + "https://api.etherscan.io/v2/api?chainid=1", ("partial", "partial"), - ); + Chain::mainnet(), + ) + .await; }); // `--ignore` tests -forgetest_async!(can_ignore_creation, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_can_ignore_creation, |prj, cmd| { test_verify_bytecode_with_ignore( prj, cmd, @@ -258,14 +268,15 @@ forgetest_async!(can_ignore_creation, |prj, cmd| { ..Default::default() }, "etherscan", - "https://api.etherscan.io/api", + "https://api.etherscan.io/v2/api?chainid=1", ("ignored", "partial"), "creation", - "1", - ); + Chain::mainnet(), + ) + .await; }); -forgetest_async!(can_ignore_runtime, |prj, cmd| { +forgetest_async!(flaky_verify_bytecode_can_ignore_runtime, |prj, cmd| { test_verify_bytecode_with_ignore( prj, cmd, @@ -280,11 +291,71 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| { ..Default::default() }, "etherscan", - "https://api.etherscan.io/api", + "https://api.etherscan.io/v2/api?chainid=1", ("partial", "ignored"), "runtime", - "1", - ); + Chain::mainnet(), + ) + .await; +}); + +// Test that verification fails when source code doesn't match deployed bytecode +forgetest_async!(flaky_can_verify_bytecode_fails_on_source_mismatch, |prj, cmd| { + let etherscan_key = next_etherscan_api_key(); + let rpc_url = next_http_archive_rpc_url(); + + // Fetch real source code using the library directly + let real_source = fetch_etherscan_source_flattened( + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + ðerscan_key, + Chain::mainnet(), + ) + .await + .expect("failed to fetch source code from etherscan"); + + prj.add_source("SystemConfig", &real_source); + prj.write_config(Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }); + // Build once with correct source (creates cache) + cmd.forge_fuse().arg("build").assert_success(); + + let source_code = r#" + contract SystemConfig { + uint256 public constant MODIFIED_VALUE = 999; + + function someFunction() public pure returns (uint256) { + return MODIFIED_VALUE; + } + } + "#; + + // Now replace with different incorrect source code + prj.add_source("SystemConfig", source_code); + let etherscan_key = next_etherscan_api_key(); + let args = vec![ + "verify-bytecode", + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + "--etherscan-api-key", + ðerscan_key, + "--verifier", + "etherscan", + "--verifier-url", + "https://api.etherscan.io/v2/api?chainid=1", + "--rpc-url", + &rpc_url, + ]; + let output = cmd.forge_fuse().args(args).assert_success().get_output().stderr_lossy(); + + // Verify that bytecode does NOT match (recompiled with incorrect source) + assert!(output.contains("Error: Creation code did not match".to_string().as_str())); + assert!(output.contains("Error: Runtime code did not match".to_string().as_str())); }); // Test predeploy contracts @@ -308,6 +379,6 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| { // "https://api.basescan.org/api", // ("ignored", "partial"), // "creation", -// "base", -// ); +// Chain::base_mainnet(), +// ).await; // }); diff --git a/crates/forge/tests/fixtures/CreateXScript.sol b/crates/forge/tests/fixtures/CreateXScript.sol new file mode 100644 index 0000000000000..94180d39247e5 --- /dev/null +++ b/crates/forge/tests/fixtures/CreateXScript.sol @@ -0,0 +1,165 @@ +import "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +contract CreateXGuardSaltMinimal { + enum SenderBytes { + MsgSender, + ZeroAddress, + Random + } + + enum RedeployProtectionFlag { + True, + False, + Unspecified + } + + error InvalidSalt(address emitter); + address internal constant _SELF = address(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + + function _guard(bytes32 salt) internal view returns (bytes32 guardedSalt) { + (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) = _parseSalt({salt: salt}); + + if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.True) { + guardedSalt = keccak256(abi.encode(msg.sender, block.chainid, salt)); + } else if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.False) { + guardedSalt = _efficientHash({a: bytes32(uint256(uint160(msg.sender))), b: salt}); + } else if (senderBytes == SenderBytes.MsgSender) { + revert InvalidSalt({emitter: _SELF}); + } else if (senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.True) { + guardedSalt = _efficientHash({a: bytes32(block.chainid), b: salt}); + } else if ( + senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.Unspecified + ) { + revert InvalidSalt({emitter: _SELF}); + } else { + guardedSalt = (salt != _generateSalt()) ? keccak256(abi.encode(salt)) : salt; + } + } + + function _parseSalt( + bytes32 salt + ) internal view returns (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) { + if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.True); + } else if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.False); + } else if (address(bytes20(salt)) == msg.sender) { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.Unspecified); + } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.True); + } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.False); + } else if (address(bytes20(salt)) == address(0)) { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.Unspecified); + } else if (bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.True); + } else if (bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.False); + } else { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.Unspecified); + } + } + + + function _efficientHash(bytes32 a, bytes32 b) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + mstore(0x00, a) + mstore(0x20, b) + hash := keccak256(0x00, 0x40) + } + } + + +function _generateSalt() internal view returns (bytes32 salt) { + unchecked { + salt = keccak256( + abi.encode( + blockhash(block.number - 32), + block.coinbase, + block.number, + block.timestamp, + block.prevrandao, + block.chainid, + msg.sender + ) + ); + } + } +} + +interface CreateX { + function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract); + function computeCreate3Address(bytes32 salt) external view returns (address computedAddress); +} + +struct UNISWAP_ADDRESSES { + address payable UNISWAP_POSITION_MANAGER; + address UNISWAP_PERMIT2; + address UNISWAP_POOL_MANAGER; + address UNISWAP_STATE_VIEW; +} + +contract CreateXScript is Script, CreateXGuardSaltMinimal { + mapping(uint256 => UNISWAP_ADDRESSES) uniswapAddresses; + + function run() public { + bytes32 SALT = keccak256(hex"f39Fd6e51aad88F6F4ce6aB8827279cffFb922660077e9ad43da87c100f02196"); + + // // Local test, addresses mirror Base + uniswapAddresses[1616161] = UNISWAP_ADDRESSES({ + UNISWAP_POSITION_MANAGER: payable( + 0x7C5f5A4bBd8fD63184577525326123B519429bDc + ), + UNISWAP_PERMIT2: 0x000000000022D473030F116dDEE9F6B43aC78BA3, + UNISWAP_POOL_MANAGER: 0x498581fF718922c3f8e6A244956aF099B2652b2b, + UNISWAP_STATE_VIEW: 0xA3c0c9b65baD0b08107Aa264b0f3dB444b867A71 + }); + + + CreateX createx = CreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + + address calculatedC1Address = createx.computeCreate3Address(_guard(bytes32(uint256(SALT) + 2))); + address calculatedC2Address = createx.computeCreate3Address(_guard(bytes32(uint256(SALT) + 1))); + + vm.startBroadcast(); + + bytes memory c0CreationCode = hex"60803461011457601f610f2b38819003918201601f19168301916001600160401b038311848410176101185780849260e094604052833981010312610114576100478161012c565b60208201519091906001600160a01b038116036101145760c081610070604061009e940161012c565b5061007d6060820161012c565b5061008a6080820161012c565b5061009760a0820161012c565b500161012c565b506001600160a01b03168015610101575f80546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3610dea90816101418239f35b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036101145756fe6080806040526004361015610012575f80fd5b5f905f3560e01c9081630abd5fb9146109a457508063188b3c0f14610987578063222d23501461095f578063335abd45146109375780633c0299e51461090f57806346ae0668146108e75780634c25cf26146108ca57806351f18899146108a2578063527203e31461081357806359ac19c1146107c7578063691a0f1e146106555780636c3dd13e14610637578063715018a6146105dd578063787a08a6146105bf5780637c85a183146105a15780638a14117a146105835780638b211c02146105655780638da5cb5b1461053e578063a0beb08414610512578063aaba5271146104aa578063ad55af4b14610481578063b531d7f3146102d8578063ba405a0c146102af578063d2ca211514610291578063d6fbf20214610273578063dd7b883814610255578063e13cbe3814610237578063e2c6505614610219578063ea515b87146101f05763f2fde38b14610168575f80fd5b346101ed5760203660031901126101ed576101816109be565b610189610b6d565b6001600160a01b031680156101d95781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b80fd5b50346101ed57806003193601126101ed576012546040516001600160a01b039091168152602090f35b50346101ed57806003193601126101ed576020600c54604051908152f35b50346101ed57806003193601126101ed576020601054604051908152f35b50346101ed57806003193601126101ed576020600854604051908152f35b50346101ed57806003193601126101ed576020600a54604051908152f35b50346101ed57806003193601126101ed576020600554604051908152f35b50346101ed57806003193601126101ed57600f546040516001600160a01b039091168152602090f35b50346101ed576101803660031901126101ed5760a435610124356001600160a01b03811690610104359060c4359083900361047d57610144356001600160a01b03811694908590036104795761032c610b6d565b8260011b8381046002148415171561043d57826b033b2e3c9fd0803ce8000000036b033b2e3c9fd0803ce800000081116104655761037381670de0b6b3a764000084610d02565b91846b033b2e3c9fd0803ce80000001461045157670de0b6b3a764000090091515810180911161043d57811061042e57600435600555602435600655604435600755606435600855608435600955600a55600d5560e435600b55600c556bffffffffffffffffffffffff60a01b600e541617600e556bffffffffffffffffffffffff60a01b600f541617600f55610164356010557fd9d41def84794db66ae5ab5d041caacb905afc49e9ca3604aa41bb1ea8a582e28180a180f35b635a2ee95360e11b8652600486fd5b634e487b7160e01b87526011600452602487fd5b634e487b7160e01b89526012600452602489fd5b634e487b7160e01b88526011600452602488fd5b8580fd5b8480fd5b50346101ed57806003193601126101ed576004546040516001600160a01b039091168152602090f35b50346101ed5760603660031901126101ed576044356104c7610b6d565b8015610503576004356014556024356013556015557f4bbf648868a599cc55656d8bca2c460f8c2136d467ea4c033a8aaaee493cdfbf8180a180f35b63166cb78960e01b8252600482fd5b50346101ed5760203660031901126101ed5760206105366105316109be565b610a1a565b604051908152f35b50346101ed57806003193601126101ed57546040516001600160a01b039091168152602090f35b50346101ed57806003193601126101ed576020600d54604051908152f35b50346101ed57806003193601126101ed576020600654604051908152f35b50346101ed57806003193601126101ed576020600b54604051908152f35b50346101ed57806003193601126101ed576020600754604051908152f35b50346101ed57806003193601126101ed576105f6610b6d565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346101ed57806003193601126101ed576020600954604051908152f35b503461075b57606036600319011261075b5761066f6109be565b600e5460243590604435906001600160a01b03163303610796576011546001600160a01b031692831561075f57833b1561075b575f80916064604051809481936324d5fda360e01b835260018060a01b0316988960048401528860248401528760448401525af1801561075057610715575b507f20dbc16160364bc8d282b93f41c769d3b024f0f8844f96fae2d74b2b71d844719160409182519182526020820152a280f35b6040919450916107465f7f20dbc16160364bc8d282b93f41c769d3b024f0f8844f96fae2d74b2b71d84471946109d4565b5f949150916106e1565b6040513d5f823e3d90fd5b5f80fd5b60405162461bcd60e51b815260206004820152600f60248201526e6e6f2d63756c742d666163746f727960881b6044820152606490fd5b60405162461bcd60e51b81526020600482015260096024820152686f6e6c792d686f6f6b60b81b6044820152606490fd5b3461075b575f36600319011261075b57600154600254600354600454604080516001600160a01b0395861681529385166020850152918416918301919091529091166060820152608090f35b3461075b575f36600319011261075b57610180600554600654600754600854600954600a54600d54600b5491600c549360018060a01b03600e54169560018060a01b03600f541697601054996040519b8c5260208c015260408b015260608a0152608089015260a088015260c087015260e0860152610100850152610120840152610140830152610160820152f35b3461075b575f36600319011261075b576001546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576020601554604051908152f35b3461075b575f36600319011261075b57600e546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576011546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576002546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576003546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576020601454604051908152f35b3461075b575f36600319011261075b576020906013548152f35b600435906001600160a01b038216820361075b57565b90601f8019910116810190811067ffffffffffffffff8211176109f657604052565b634e487b7160e01b5f52604160045260245ffd5b519062ffffff8216820361075b57565b600f546001600160a01b03908116908216808214610b5e57819282819210610b53575b5060018060a01b03600454169260405160a0810181811067ffffffffffffffff8211176109f65760809160a091604052600180831b0384168152600180831b0385166020820152610bb86040820152603c60608201525f8382015220602460405180978193633205590760e21b835260048301525afa938415610750575f94610ae5575b506001600160a01b03841615610add57610ada93610c51565b90565b505050505f90565b9093506080813d608011610b4b575b81610b01608093836109d4565b8101031261075b578051906001600160a01b038216820361075b5760208101518060020b0361075b57606081610b3c6040610b439401610a0a565b5001610a0a565b50925f610ac1565b3d9150610af4565b92508190505f610a3d565b505050670de0b6b3a764000090565b5f546001600160a01b03163303610b8057565b63118cdaa760e01b5f523360045260245ffd5b5f908015610c4b578080600114610c4357600214610c3c5760016101338210166001600b83101617610c2e579060019060025b60018111610bf25750825f19048211610bde57500290565b634e487b7160e01b81526011600452602490fd5b92805f19048111610c1a5760018416610c11575b80029260011c610bc6565b80920291610c06565b634e487b7160e01b82526011600452602482fd5b6002900a919080610bde5750565b5050600490565b505050600190565b50505f90565b5f9390926001600160a01b039182169291168203610cb75750610c7e916001600160a01b03169050610b93565b90610c96600160c01b670de0b6b3a764000084610d02565b91600160c01b90670de0b6b3a7640000900915158201809211610bde575090565b9192506001600160a01b0390911603610cf357610ada90610ce0906001600160a01b0316610b93565b670de0b6b3a7640000600160c01b610d02565b63e6f2de8760e01b5f5260045ffd5b91818302915f1981850993838086109503948086039514610d925784831115610d7a5790829109815f0382168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b82634e487b715f52156003026011186020526024601cfd5b505080925015610da0570490565b634e487b7160e01b5f52601260045260245ffdfea26469706673582212204c94689568baebf5fff57a2f3e072a1fb7067e2c83f098eac9e838fbfdded18664736f6c634300081a0033"; + + bytes memory c0DeploymentCode = abi.encodePacked( + c0CreationCode, + abi.encode( + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, + uniswapAddresses[block.chainid].UNISWAP_POSITION_MANAGER, + uniswapAddresses[block.chainid].UNISWAP_PERMIT2, + uniswapAddresses[block.chainid].UNISWAP_POOL_MANAGER, + uniswapAddresses[block.chainid].UNISWAP_STATE_VIEW, + calculatedC1Address, + calculatedC2Address + ) + ); + + address c0Address = createx.deployCreate3( + SALT, + c0DeploymentCode + ); + + + bytes memory c2CreationCode = hex"60803460b857601f6112a038819003918201601f19168301916001600160401b0383118484101760bc5780849260209460405283398101031260b857516001600160a01b0381169081900360b857801560a5575f80546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36111cf90816100d18239f35b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c9081630d31612614610f52575080631f94e50314610e4757806324d5fda314610ccb57806343fe85ba14610c8e5780634fb6df17146109c35780634ff013711461099a5780635eeb1bbc1461095b578063646ec81d1461086b5780636f8c57831461080a578063712232d5146107d1578063715018a61461077757806373c376581461073e5780637734d07614610685578063862a8eff146106445780638c730f2a146106185780638da5cb5b146105f1578063a05ff6b8146105ae578063aed19359146101bb578063d48418a1146101825763f2fde38b146100fa575f80fd5b3461017f57602036600319011261017f57610113610f86565b61011b6110de565b6001600160a01b0316801561016b5781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b80fd5b503461017f57602036600319011261017f576020906040906001600160a01b036101aa610f86565b168152600883522054604051908152f35b503461017f57608036600319011261017f576101d5610f86565b9060243567ffffffffffffffff81116105aa576101f6903690600401610fd4565b60443567ffffffffffffffff81116105a657610216903690600401610fd4565b926064356001600160a01b038116908190036105a257835460405163ea515b8760e01b81526001600160a01b0390911695906020816004818a5afa908115610597578691610559575b506001600160a01b031693841561054a578560409461031d946102e360016020809b9a976102b9600583809a8e519b818d9251918291018484015e8101641021b7b4b760d91b838201520301601a1981018b520189610fb2565b8a519c818e9251918291018484015e8101604360f81b838201520301601e1981018c52018a610fb2565b61032f8751998a9788968795637b17ab9560e01b875260018060a01b0316600487015286602487015260c0604487015260c48601906110ba565b848103600319016064860152906110ba565b9060848301528260a483015203925af19182156104835781908293610502575b506001600160a01b0316808252600360209081526040808420805460ff191660011790555163188b3c0f60e01b8152939192919084600481885afa9384156104c35782946104ce575b5060018060a01b03169283825260066020526040822055604051630abd5fb960e01b8152602081600481885afa9081156104c3578291610490575b5060049460209185845260088352604084205560405195868092632612e79360e11b82525afa93841561048357819461044a575b50602083928195604084867fad5759c1cafdae1264943f6fa2482923381aa1c5fe5325f77329f99db30053ed9652600785522055604051908152a2604051908152f35b93506020843d60201161047b575b8161046560209383610fb2565b81010312610477579251926020610407565b5f80fd5b3d9150610458565b50604051903d90823e3d90fd5b90506020813d6020116104bb575b816104ab60209383610fb2565b81010312610477575160046103d3565b3d915061049e565b6040513d84823e3d90fd5b9093506020813d6020116104fa575b816104ea60209383610fb2565b810103126104775751925f610398565b3d91506104dd565b9250506040823d604011610542575b8161051e60409383610fb2565b8101031261017f5761053b6020610534846110a6565b93016110a6565b915f61034f565b3d9150610511565b6370b649dd60e01b8652600486fd5b90506020813d60201161058f575b8161057460209383610fb2565b8101031261058b57610585906110a6565b5f61025f565b8580fd5b3d9150610567565b6040513d88823e3d90fd5b8380fd5b8280fd5b5080fd5b503461017f57604036600319011261017f576020906040906001600160a01b036105d6610f86565b168152600a8352818120602435825283522054604051908152f35b503461017f578060031936011261017f57546040516001600160a01b039091168152602090f35b503461017f57602036600319011261017f57602061063c610637610f86565b61105b565b604051908152f35b503461017f57602036600319011261017f576020906001600160a01b03610669610f86565b16815260018252604060018060a01b0391205416604051908152f35b503461017f57606036600319011261017f5761069f610f86565b906106a8610f9c565b916106b16110de565b6001600160a01b03168082526002602052604082205490929060ff161561072f578282933b1561072b576040516340c10f1960e01b81526001600160a01b039290921660048301526044803560248401528391839190829084905af180156104c35761071a5750f35b8161072491610fb2565b61017f5780f35b5050fd5b638c5d908560e01b8252600482fd5b503461017f57602036600319011261017f576020906040906001600160a01b03610766610f86565b168152600783522054604051908152f35b503461017f578060031936011261017f576107906110de565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461017f57602036600319011261017f576020906040906001600160a01b036107f9610f86565b168152600683522054604051908152f35b503461017f57602036600319011261017f576080906040906001600160a01b03610832610f86565b16815260096020522060ff8154169060018101549060036002820154910154916040519315158452602084015260408301526060820152f35b503461017f57602036600319011261017f57610885610f86565b6004546001600160a01b0316330361094c576001600160a01b0316808252600260209081526040808420805460ff191660011790558284526005909152822054156108f2575b7fa4d13a5bef1890eddfd4d435735b46ed074a5e4b8f0a421cd526aeeef8141d3a8280a280f35b80825260056020524260408320558082526005602052807faed14053ca40d8865c112ef238ff360fe2189d5730000f57a6b88caf3198650e60408085205483865260076020528186205482519182526020820152a26108cb565b635651e6f560e11b8252600482fd5b503461017f57602036600319011261017f5760209060ff906040906001600160a01b03610986610f86565b168152600384522054166040519015158152f35b503461017f578060031936011261017f576004546040516001600160a01b039091168152602090f35b5034610477576040366003190112610477576109dd610f86565b602435908115610c7f576001600160a01b03165f818152600960205260409081902090516080810167ffffffffffffffff811182821017610c6b5760405260ff82541615801582526001830154916020810192835260606003600286015495604084019687520154910152610c5d57825f52600160205260018060a01b0360405f20541691825f52600260205260ff60405f20541615610c4e57815190835f52600a60205260405f20825f5260205260405f2054918215610c3f57845f52600660205260405f20548015610c305790879291865f52600860205260405f2054670de0b6b3a76400009290815b610bf0575050610af292610ae5610af7969593610aed93611104565b90519061102a565b61102a565b61103d565b8015610be157833b156104775760405163079cc67960e41b8152336004820152602481018690525f8160448183895af18015610bd657610bc1575b50823b1561058b576040516340c10f1960e01b8152336004820152602481018290528690818160448183895af180156104c357610bac575b505090516040805195865260208601929092529084015233927f142894e15d18cf7c1106a97eefe825087bf328fa8730129e3f89134321b3a23490606090a480f35b81610bb691610fb2565b61058b57855f610b6a565b610bce9196505f90610fb2565b5f945f610b32565b6040513d5f823e3d90fd5b639a57326560e01b5f5260045ffd5b909192939450600180831614610c1a575b80610c0b91611104565b899493929160011c9081610ac9565b92610c2884610c0b92611104565b939050610c01565b63374c934360e11b5f5260045ffd5b632959df1160e21b5f5260045ffd5b638c5d908560e01b5f5260045ffd5b62ad587d60e81b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b6313aab74360e01b5f5260045ffd5b34610477576020366003190112610477576001600160a01b03610caf610f86565b165f526002602052602060ff60405f2054166040519015158152f35b3461047757606036600319011261047757610ce4610f86565b60243560443591610cf36110de565b8115610e38578215610e29576001600160a01b039081165f8181526001602052604090205490939116918215610e1a57825f52600560205260405f205415610e0b57835f52600960205260405f2080549060ff8216610dfc57600384916001610d5b8861105b565b9460ff19161781558360018201558460028201550155610d7b838361102a565b92845f52600a60205260405f20825f5260205260405f20908154948501809511610de8577fc8ed84018dd91b7f9ef452fe95948d9dad8e47361ad1dd5886c54398e838e24094610de39255604051938493846040919493926060820195825260208201520152565b0390a3005b634e487b7160e01b5f52601160045260245ffd5b630931f96360e41b5f5260045ffd5b63277e703d60e21b5f5260045ffd5b63325ddddd60e01b5f5260045ffd5b639fbba64f60e01b5f5260045ffd5b637294708f60e11b5f5260045ffd5b3461047757604036600319011261047757610e60610f86565b610e68610f9c565b6004546001600160a01b03163303610f43576001600160a01b03169081158015610f32575b610f23576001600160a01b03165f8181526002602052604090205490919060ff1615610c4e575f818152600160205260409020546001600160a01b0316610f11575f81815260016020526040812080546001600160a01b031916841790557f0cb63fc6bb9f844b450c8052989a65f162ecac87757ca2c3921e0e9b7c4ccff69080a3005b631535136360e31b5f5260045260245ffd5b63e6c4247b60e01b5f5260045ffd5b506001600160a01b03811615610e8d565b635651e6f560e11b5f5260045ffd5b34610477576020366003190112610477576020906001600160a01b03610f76610f86565b165f526005825260405f20548152f35b600435906001600160a01b038216820361047757565b602435906001600160a01b038216820361047757565b90601f8019910116810190811067ffffffffffffffff821117610c6b57604052565b81601f820112156104775780359067ffffffffffffffff8211610c6b5760405192611009601f8401601f191660200185610fb2565b8284526020838301011161047757815f926020809301838601378301015290565b81810292918115918404141715610de857565b8115611047570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b03165f8181526005602052604090205415610e0b57805f52600560205260405f20544203428111610de8576110a3915f52600760205260405f20549061103d565b90565b51906001600160a01b038216820361047757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b5f546001600160a01b031633036110f157565b63118cdaa760e01b5f523360045260245ffd5b9190915f838202915f19858209918380841093039280840393146111865782670de0b6b3a7640000111561117457507faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b634e487b71905260116020526024601cfd5b505050670de0b6b3a7640000919250049056fea2646970667358221220e8dac8dec112cb258e7bd8c159fd1da98ea741bb7b01073a1f31609d4cf729a064736f6c634300081a0033"; + bytes memory c2DeploymentCode = abi.encodePacked( + c2CreationCode, + abi.encode(c0Address) + ); + + address c2Address = createx.deployCreate3( + bytes32(uint256(SALT) + 1), + c2DeploymentCode + ); + + // Commenting out this check causes script to pass + if (c2Address != calculatedC2Address) { + console.log("address mismatch"); + } + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol b/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol index 8d7918e364766..75b141241f9fd 100644 --- a/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol +++ b/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol @@ -109,7 +109,8 @@ contract Emitter { /// Emulates `Emitter` in #760 contract LowLevelCaller { function f() external { - address(this).call(abi.encodeWithSignature("g()")); + (bool success,) = address(this).call(abi.encodeWithSignature("g()")); + require(success, "call failed"); } function g() public {} diff --git a/crates/forge/tests/fixtures/ExpectEmitParamFailures.t.sol b/crates/forge/tests/fixtures/ExpectEmitParamFailures.t.sol new file mode 100644 index 0000000000000..c34944996cb5a --- /dev/null +++ b/crates/forge/tests/fixtures/ExpectEmitParamFailures.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "./test.sol"; +import "./Vm.sol"; + +import {EventEmitter, SelectiveEmitter, ParamNumberingEmitter, AnonymousEmitter, ManyParamsEmitter} from "./ExpectEmitParamHarness.sol"; + +contract ExpectEmitParamFailures is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + // Contract instances + EventEmitter public eventEmitter; + SelectiveEmitter public selectiveEmitter; + ParamNumberingEmitter public paramNumberingEmitter; + AnonymousEmitter public anonymousEmitter; + ManyParamsEmitter public manyParamsEmitter; + + // Event declarations for tests + event SimpleEvent(uint256 indexed a, uint256 indexed b, uint256 c); + event ComplexEvent(address indexed sender, uint256 indexed id, bytes data); + event TestEvent(uint256 indexed a, uint256 indexed b, uint256 c); + + event MixedEventNumbering( + uint256 indexed param0, + address indexed param1, + uint256 param2, + uint256 param3, + address param4 + ); + + // Anonymous event for tests + event AnonymousIndexed(uint256 indexed a, uint256 b, address c) anonymous; + + // Event with many parameters + event ManyParams(uint256 a, uint256 b, uint256 c, uint256 d, uint256 e); + + function setUp() public { + eventEmitter = new EventEmitter(); + selectiveEmitter = new SelectiveEmitter(); + paramNumberingEmitter = new ParamNumberingEmitter(); + anonymousEmitter = new AnonymousEmitter(); + manyParamsEmitter = new ManyParamsEmitter(); + } + + function testIndexedParamMismatch() public { + vm.expectEmit(true, true, true, true); + emit SimpleEvent(100, 200, 300); + eventEmitter.emitSimple(100, 999, 300); // Second indexed param (b) mismatch + } + + function testNonIndexedParamMismatch() public { + vm.expectEmit(true, true, true, true); + emit SimpleEvent(100, 200, 300); + eventEmitter.emitSimple(100, 200, 999); // Non-indexed param (c) mismatch + } + + function testMultipleMismatches() public { + vm.expectEmit(true, true, true, true); + emit SimpleEvent(100, 200, 300); + eventEmitter.emitSimple(999, 888, 777); // All params mismatch + } + + function testSelectiveChecks() public { + vm.expectEmit(true, false, true, true); // checkTopic2=false + emit TestEvent(100, 200, 300); + selectiveEmitter.emitEvent(100, 999, 300); // Topic2 different but not checked + } + + function testParameterNumbering() public { + vm.expectEmit(true, true, true, true); + emit MixedEventNumbering( + 100, + address(0x1234), + 300, + 400, + address(0x5678) + ); + paramNumberingEmitter.emitEvent( + 100, + address(0x1234), + 999, + 400, + address(0x5678) + ); // param2 mismatch + } + + function testCompletelyDifferentEvent() public { + vm.expectEmit(true, true, true, true); + emit SimpleEvent(100, 200, 300); + eventEmitter.emitComplex(address(this), 42, hex"deadbeef"); // Different event type + } + + function testAnonymousEventMismatch() public { + vm.expectEmitAnonymous(true, false, false, false, true); // Check topic0 and data + emit AnonymousIndexed(100, 200, address(0x1234)); + anonymousEmitter.emitAnonymousIndexed(999, 200, address(0x1234)); // param0 mismatch + } + + function testManyParameterMismatches() public { + vm.expectEmit(true, true, true, true); + // Event with 5 non-indexed parameters + emit ManyParams(100, 200, 300, 400, 500); + // All 5 parameters differ - should show each one individually + manyParamsEmitter.emitManyParams(111, 222, 333, 444, 555); + } + + function testMixedEventNonIndexedMismatch() public { + // For SimpleEvent: 'a' and 'b' are indexed, 'c' is non-indexed + vm.expectEmit(true, true, true, true); + emit SimpleEvent(100, 200, 300); + // Same indexed params (100, 200) but different non-indexed param + eventEmitter.emitSimple(100, 200, 999); + } +} diff --git a/crates/forge/tests/fixtures/ExpectEmitParamHarness.sol b/crates/forge/tests/fixtures/ExpectEmitParamHarness.sol new file mode 100644 index 0000000000000..95d8faa07f029 --- /dev/null +++ b/crates/forge/tests/fixtures/ExpectEmitParamHarness.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: UNLICENSED + +// Harness contract are in a separate file so that the selectors cache can be populated on `forge b`. +contract EventEmitter { + event SimpleEvent(uint256 indexed a, uint256 indexed b, uint256 c); + event ComplexEvent(address indexed sender, uint256 indexed id, bytes data); + + function emitSimple(uint256 a, uint256 b, uint256 c) public { + emit SimpleEvent(a, b, c); + } + + function emitComplex(address sender, uint256 id, bytes memory data) public { + emit ComplexEvent(sender, id, data); + } + + function emitSimpleMultipleTimes( + uint256 a, + uint256 b, + uint256 c, + uint256 times + ) public { + for (uint256 i = 0; i < times; i++) { + emit SimpleEvent(a, b, c); + } + } +} + +contract SelectiveEmitter { + event TestEvent(uint256 indexed a, uint256 indexed b, uint256 c); + + function emitEvent(uint256 a, uint256 b, uint256 c) public { + emit TestEvent(a, b, c); + } +} + +contract ParamNumberingEmitter { + // Event with 2 indexed and 3 non-indexed parameters + event MixedEventNumbering( + uint256 indexed param0, // param 0 (indexed) + address indexed param1, // param 1 (indexed) + uint256 param2, // param 2 (non-indexed) + uint256 param3, // param 3 (non-indexed) + address param4 // param 4 (non-indexed) + ); + + function emitEvent( + uint256 p0, + address p1, + uint256 p2, + uint256 p3, + address p4 + ) public { + emit MixedEventNumbering(p0, p1, p2, p3, p4); + } +} + +contract AnonymousEmitter { + // Anonymous event with indexed parameter + event AnonymousIndexed(uint256 indexed a, uint256 b, address c) anonymous; + + function emitAnonymousIndexed(uint256 a, uint256 b, address c) public { + emit AnonymousIndexed(a, b, c); + } +} + +contract ManyParamsEmitter { + // Event with many non-indexed parameters to trigger raw data display + event ManyParams(uint256 a, uint256 b, uint256 c, uint256 d, uint256 e); + + function emitManyParams( + uint256 a, + uint256 b, + uint256 c, + uint256 d, + uint256 e + ) public { + emit ManyParams(a, b, c, d, e); + } +} diff --git a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol index 4eca811f0c465..838183a1b0b5e 100644 --- a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol +++ b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol @@ -233,6 +233,63 @@ contract ExpectRevertWithReverterFailureTest is DSTest { aContract.doNotRevert(); aContract.callAndRevert(); } + + // + // Regression: must fail because 0xdead is not the actual reverter when a + // top-level CREATE constructor reverts directly. + function testShouldFailExpectRevertWrongReverterTopLevelCreate() public { + vm.expectRevert(address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // even when an exact-bytes pattern is also supplied for a top-level CREATE. + function testShouldFailExpectRevertWithBytesWrongReverterTopLevelCreate() public { + vm.expectRevert(abi.encodePacked("Reverted by DContract"), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail because the reverter address argument is enforced + // for `expectPartialRevert(bytes4, address)` against a top-level CREATE. + function testShouldFailExpectPartialRevertWrongReverterTopLevelCreate() public { + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), address(0xdead)); + new DContract(); + } + + // + // Regression: must fail when the innermost reverting frame is a nested + // CREATE and the reverter address argument does not match the would-be + // deployed address of the failed deployment. + function testShouldFailExpectRevertWrongReverterNestedCreate() public { + vm.expectRevert(address(0xdead)); + new NestedDContractCreator(); + } + + // + // Regression: documents the intended semantics for nested CREATEs — the + // matched reverter is the *outer* would-be-deployed address (the contract + // whose deployment failed), NOT the innermost reverting CREATE's address. + // Supplying the inner address must fail. + function testShouldFailExpectRevertNestedCreateInnerAddress() public { + // Outer = NestedDContractCreator at this contract's next nonce. + // Inner = DContract created from inside the outer constructor (deployer + // is the outer, nonce 1). + address outer = + vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + address inner = vm.computeCreateAddress(outer, 1); + vm.expectRevert(inner); + new NestedDContractCreator(); + } +} + +// Used by `testShouldFailExpectRevertWrongReverterNestedCreate`: a contract whose +// constructor directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } } contract ExpectRevertCountFailureTest is DSTest { @@ -253,7 +310,7 @@ contract ExpectRevertCountFailureTest is DSTest { reverter.revertWithMessage("revert"); } - function testShouldFailReverCountSpecifc() public { + function testShouldFailRevertCountSpecific() public { uint64 count = 2; Reverter reverter = new Reverter(); vm.expectRevert("revert", count); @@ -277,6 +334,13 @@ contract ExpectRevertCountFailureTest is DSTest { reverter.callThenRevert(dummy, "called a function and then reverted"); reverter.callThenRevert(dummy, "wrong revert"); } + + // + function testShouldFailIfExpectRevertWrongString() public { + Reverter reverter = new Reverter(); + vm.expectRevert("my not so cool error", 0); + reverter.revertWithMessage("my cool error"); + } } contract ExpectRevertCountWithReverterFailures is DSTest { @@ -314,4 +378,24 @@ contract ExpectRevertCountWithReverterFailures is DSTest { reverter.revertWithMessage("revert"); reverter2.revertWithMessage("revert"); } + + // + function testNoReverterCountWithData() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.doNotRevert(); + + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert2"); + } + + // + function testNoRevertWithWrongReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter2.revertWithMessage("revert"); // revert from wrong reverter + } } diff --git a/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json index e9d992751b868..d8e517219b74f 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json @@ -1,25 +1,25 @@ { - "src/Simple.t.sol:SimpleContractTest": { - "duration": "{...}", - "test_results": { - "test()": { - "status": "Success", - "reason": null, - "counterexample": null, - "logs": [], - "decoded_logs": [], - "kind": { - "Unit": { - "gas": "{...}" - } - }, - "traces": [], - "labeled_addresses": {}, - "duration": "{...}", - "breakpoints": {}, - "gas_snapshots": {} - } + "src/Simple.t.sol:SimpleContractTest": { + "duration": "{...}", + "test_results": { + "test()": { + "status": "Success", + "reason": null, + "counterexample": null, + "logs": [], + "decoded_logs": [], + "kind": { + "Unit": { + "gas": "{...}" + } }, - "warnings": [] - } -} \ No newline at end of file + "traces": [], + "labeled_addresses": {}, + "duration": "{...}", + "breakpoints": {}, + "gas_snapshots": {} + } + }, + "warnings": [] + } +} diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json index 8659312887de6..39544b6ccbbe9 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -47,13 +47,10 @@ "output": "{...}", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Return", "steps": [], - "decoded": { - "label": null, - "return_data": null, - "call_data": null - } + "decoded": null }, "logs": [], "ordering": [] @@ -88,56 +85,6969 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", - "steps": [], - "decoded": { - "label": null, - "return_data": null, - "call_data": null - } + "steps": [ + { + "pc": 0, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 2, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 4, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 12, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 5, + "op": 52, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 6, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 7, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 8, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 11, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 15, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 16, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 17, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 19, + "op": 54, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 20, + "op": 16, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 21, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 24, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 25, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 26, + "op": 53, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 27, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 29, + "op": 28, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 30, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 31, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 36, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 37, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 40, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 41, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 42, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 47, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 48, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 51, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 52, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 53, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 58, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 59, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 62, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 138, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 139, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 142, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 145, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 602, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 603, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 604, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 606, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 607, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 610, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 611, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 614, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1035, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1036, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1039, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1040, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1043, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1044, + "op": 57, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 72, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1045, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1046, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1047, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 615, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 616, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 618, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 619, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 620, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 621, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 622, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 623, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 624, + "op": 240, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1056944078, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 625, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 626, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629525, + "gas_refund_counter": 0, + "gas_used": 91235, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 627, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629522, + "gas_refund_counter": 0, + "gas_used": 91238, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 628, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629519, + "gas_refund_counter": 0, + "gas_used": 91241, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 629, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629516, + "gas_refund_counter": 0, + "gas_used": 91244, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 632, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629513, + "gas_refund_counter": 0, + "gas_used": 91247, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 640, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629503, + "gas_refund_counter": 0, + "gas_used": 91257, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 641, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629502, + "gas_refund_counter": 0, + "gas_used": 91258, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 642, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629500, + "gas_refund_counter": 0, + "gas_used": 91260, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 643, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629497, + "gas_refund_counter": 0, + "gas_used": 91263, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 644, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629495, + "gas_refund_counter": 0, + "gas_used": 91265, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 645, + "op": 115, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629492, + "gas_refund_counter": 0, + "gas_used": 91268, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 666, + "op": 22, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629489, + "gas_refund_counter": 0, + "gas_used": 91271, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 667, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629486, + "gas_refund_counter": 0, + "gas_used": 91274, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 672, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629483, + "gas_refund_counter": 0, + "gas_used": 91277, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 674, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629480, + "gas_refund_counter": 0, + "gas_used": 91280, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 676, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629477, + "gas_refund_counter": 0, + "gas_used": 91283, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 677, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629474, + "gas_refund_counter": 0, + "gas_used": 91286, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 678, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629471, + "gas_refund_counter": 0, + "gas_used": 91289, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 683, + "op": 22, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629468, + "gas_refund_counter": 0, + "gas_used": 91292, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 684, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629465, + "gas_refund_counter": 0, + "gas_used": 91295, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 686, + "op": 27, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629462, + "gas_refund_counter": 0, + "gas_used": 91298, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 687, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629459, + "gas_refund_counter": 0, + "gas_used": 91301, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 688, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629456, + "gas_refund_counter": 0, + "gas_used": 91304, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 689, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629453, + "gas_refund_counter": 0, + "gas_used": 91307, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 691, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629450, + "gas_refund_counter": 0, + "gas_used": 91310, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 692, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629447, + "gas_refund_counter": 0, + "gas_used": 91313, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 695, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629444, + "gas_refund_counter": 0, + "gas_used": 91316, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 696, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629441, + "gas_refund_counter": 0, + "gas_used": 91319, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 697, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629438, + "gas_refund_counter": 0, + "gas_used": 91322, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 700, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629435, + "gas_refund_counter": 0, + "gas_used": 91325, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1617, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629427, + "gas_refund_counter": 0, + "gas_used": 91333, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1618, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629426, + "gas_refund_counter": 0, + "gas_used": 91334, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1619, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629424, + "gas_refund_counter": 0, + "gas_used": 91336, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1621, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629421, + "gas_refund_counter": 0, + "gas_used": 91339, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1622, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629418, + "gas_refund_counter": 0, + "gas_used": 91342, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1623, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629415, + "gas_refund_counter": 0, + "gas_used": 91345, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1624, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629412, + "gas_refund_counter": 0, + "gas_used": 91348, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1625, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629410, + "gas_refund_counter": 0, + "gas_used": 91350, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1628, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629407, + "gas_refund_counter": 0, + "gas_used": 91353, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1629, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629405, + "gas_refund_counter": 0, + "gas_used": 91355, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1630, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629402, + "gas_refund_counter": 0, + "gas_used": 91358, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1631, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629399, + "gas_refund_counter": 0, + "gas_used": 91361, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1632, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629396, + "gas_refund_counter": 0, + "gas_used": 91364, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1635, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629393, + "gas_refund_counter": 0, + "gas_used": 91367, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1602, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629385, + "gas_refund_counter": 0, + "gas_used": 91375, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1603, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629384, + "gas_refund_counter": 0, + "gas_used": 91376, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1606, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629381, + "gas_refund_counter": 0, + "gas_used": 91379, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1607, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629378, + "gas_refund_counter": 0, + "gas_used": 91382, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1610, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629375, + "gas_refund_counter": 0, + "gas_used": 91385, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1569, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629367, + "gas_refund_counter": 0, + "gas_used": 91393, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1570, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629366, + "gas_refund_counter": 0, + "gas_used": 91394, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1571, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629364, + "gas_refund_counter": 0, + "gas_used": 91396, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1574, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629361, + "gas_refund_counter": 0, + "gas_used": 91399, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1577, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629358, + "gas_refund_counter": 0, + "gas_used": 91402, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1580, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629355, + "gas_refund_counter": 0, + "gas_used": 91405, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1581, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629352, + "gas_refund_counter": 0, + "gas_used": 91408, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1584, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629349, + "gas_refund_counter": 0, + "gas_used": 91411, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1542, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629341, + "gas_refund_counter": 0, + "gas_used": 91419, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1543, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629340, + "gas_refund_counter": 0, + "gas_used": 91420, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1544, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629338, + "gas_refund_counter": 0, + "gas_used": 91422, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1545, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629335, + "gas_refund_counter": 0, + "gas_used": 91425, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1546, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629332, + "gas_refund_counter": 0, + "gas_used": 91428, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1547, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629330, + "gas_refund_counter": 0, + "gas_used": 91430, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1548, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629327, + "gas_refund_counter": 0, + "gas_used": 91433, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1549, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629324, + "gas_refund_counter": 0, + "gas_used": 91436, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1550, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629322, + "gas_refund_counter": 0, + "gas_used": 91438, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1585, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629314, + "gas_refund_counter": 0, + "gas_used": 91446, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1586, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629313, + "gas_refund_counter": 0, + "gas_used": 91447, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1589, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629310, + "gas_refund_counter": 0, + "gas_used": 91450, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1560, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629302, + "gas_refund_counter": 0, + "gas_used": 91458, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1561, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629301, + "gas_refund_counter": 0, + "gas_used": 91459, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1562, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629299, + "gas_refund_counter": 0, + "gas_used": 91461, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1563, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629296, + "gas_refund_counter": 0, + "gas_used": 91464, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1564, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629293, + "gas_refund_counter": 0, + "gas_used": 91467, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1565, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629291, + "gas_refund_counter": 0, + "gas_used": 91469, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1566, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629288, + "gas_refund_counter": 0, + "gas_used": 91472, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1567, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629285, + "gas_refund_counter": 0, + "gas_used": 91475, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1568, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629283, + "gas_refund_counter": 0, + "gas_used": 91477, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1590, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629275, + "gas_refund_counter": 0, + "gas_used": 91485, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1591, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629274, + "gas_refund_counter": 0, + "gas_used": 91486, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1594, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629271, + "gas_refund_counter": 0, + "gas_used": 91489, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1551, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629263, + "gas_refund_counter": 0, + "gas_used": 91497, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1552, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629262, + "gas_refund_counter": 0, + "gas_used": 91498, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1553, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629260, + "gas_refund_counter": 0, + "gas_used": 91500, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1554, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629257, + "gas_refund_counter": 0, + "gas_used": 91503, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1555, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629254, + "gas_refund_counter": 0, + "gas_used": 91506, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1556, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629252, + "gas_refund_counter": 0, + "gas_used": 91508, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1557, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629249, + "gas_refund_counter": 0, + "gas_used": 91511, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1558, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629246, + "gas_refund_counter": 0, + "gas_used": 91514, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1559, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629244, + "gas_refund_counter": 0, + "gas_used": 91516, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1595, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629236, + "gas_refund_counter": 0, + "gas_used": 91524, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1596, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629235, + "gas_refund_counter": 0, + "gas_used": 91525, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1597, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629232, + "gas_refund_counter": 0, + "gas_used": 91528, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1598, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629230, + "gas_refund_counter": 0, + "gas_used": 91530, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1599, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629227, + "gas_refund_counter": 0, + "gas_used": 91533, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1600, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629224, + "gas_refund_counter": 0, + "gas_used": 91536, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1601, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629222, + "gas_refund_counter": 0, + "gas_used": 91538, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1611, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629214, + "gas_refund_counter": 0, + "gas_used": 91546, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1612, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629213, + "gas_refund_counter": 0, + "gas_used": 91547, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1613, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629210, + "gas_refund_counter": 0, + "gas_used": 91550, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1614, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629207, + "gas_refund_counter": 0, + "gas_used": 91553, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1615, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629205, + "gas_refund_counter": 0, + "gas_used": 91555, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1616, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629203, + "gas_refund_counter": 0, + "gas_used": 91557, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1636, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629195, + "gas_refund_counter": 0, + "gas_used": 91565, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1637, + "op": 146, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629194, + "gas_refund_counter": 0, + "gas_used": 91566, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1638, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629191, + "gas_refund_counter": 0, + "gas_used": 91569, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1639, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629188, + "gas_refund_counter": 0, + "gas_used": 91572, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1640, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629186, + "gas_refund_counter": 0, + "gas_used": 91574, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1641, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629184, + "gas_refund_counter": 0, + "gas_used": 91576, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 701, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629176, + "gas_refund_counter": 0, + "gas_used": 91584, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 702, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629175, + "gas_refund_counter": 0, + "gas_used": 91585, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 703, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629173, + "gas_refund_counter": 0, + "gas_used": 91587, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 705, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629170, + "gas_refund_counter": 0, + "gas_used": 91590, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 706, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629167, + "gas_refund_counter": 0, + "gas_used": 91593, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 707, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629164, + "gas_refund_counter": 0, + "gas_used": 91596, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 708, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629161, + "gas_refund_counter": 0, + "gas_used": 91599, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 709, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629158, + "gas_refund_counter": 0, + "gas_used": 91602, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 710, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629155, + "gas_refund_counter": 0, + "gas_used": 91605, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 711, + "op": 135, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629153, + "gas_refund_counter": 0, + "gas_used": 91607, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 712, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629150, + "gas_refund_counter": 0, + "gas_used": 91610, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 713, + "op": 59, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629147, + "gas_refund_counter": 0, + "gas_used": 91613, + "gas_cost": 100, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 714, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629047, + "gas_refund_counter": 0, + "gas_used": 91713, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 715, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629044, + "gas_refund_counter": 0, + "gas_used": 91716, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 716, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629041, + "gas_refund_counter": 0, + "gas_used": 91719, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 717, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629038, + "gas_refund_counter": 0, + "gas_used": 91722, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 720, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629035, + "gas_refund_counter": 0, + "gas_used": 91725, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 724, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629025, + "gas_refund_counter": 0, + "gas_used": 91735, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 725, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629024, + "gas_refund_counter": 0, + "gas_used": 91736, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 726, + "op": 90, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629022, + "gas_refund_counter": 0, + "gas_used": 91738, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 727, + "op": 241, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073629020, + "gas_refund_counter": 0, + "gas_used": 91740, + "gas_cost": 1056853569, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 728, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606406, + "gas_refund_counter": 0, + "gas_used": 114354, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 729, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606403, + "gas_refund_counter": 0, + "gas_used": 114357, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 730, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606400, + "gas_refund_counter": 0, + "gas_used": 114360, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 731, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606397, + "gas_refund_counter": 0, + "gas_used": 114363, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 734, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606394, + "gas_refund_counter": 0, + "gas_used": 114366, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 742, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606384, + "gas_refund_counter": 0, + "gas_used": 114376, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 743, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606383, + "gas_refund_counter": 0, + "gas_used": 114377, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 744, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606381, + "gas_refund_counter": 0, + "gas_used": 114379, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 745, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606379, + "gas_refund_counter": 0, + "gas_used": 114381, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 746, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606377, + "gas_refund_counter": 0, + "gas_used": 114383, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 747, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606375, + "gas_refund_counter": 0, + "gas_used": 114385, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 750, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606372, + "gas_refund_counter": 0, + "gas_used": 114388, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 752, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606369, + "gas_refund_counter": 0, + "gas_used": 114391, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 755, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606366, + "gas_refund_counter": 0, + "gas_used": 114394, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 814, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606358, + "gas_refund_counter": 0, + "gas_used": 114402, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 815, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606357, + "gas_refund_counter": 0, + "gas_used": 114403, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 818, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606354, + "gas_refund_counter": 0, + "gas_used": 114406, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 819, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606351, + "gas_refund_counter": 0, + "gas_used": 114409, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 821, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606348, + "gas_refund_counter": 0, + "gas_used": 114412, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 822, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606345, + "gas_refund_counter": 0, + "gas_used": 114415, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 824, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606342, + "gas_refund_counter": 0, + "gas_used": 114418, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 825, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606339, + "gas_refund_counter": 0, + "gas_used": 114421, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 828, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606336, + "gas_refund_counter": 0, + "gas_used": 114424, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 829, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606333, + "gas_refund_counter": 0, + "gas_used": 114427, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 830, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606330, + "gas_refund_counter": 0, + "gas_used": 114430, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 833, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606327, + "gas_refund_counter": 0, + "gas_used": 114433, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1657, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606319, + "gas_refund_counter": 0, + "gas_used": 114441, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1658, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606318, + "gas_refund_counter": 0, + "gas_used": 114442, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1659, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606316, + "gas_refund_counter": 0, + "gas_used": 114444, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1661, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606313, + "gas_refund_counter": 0, + "gas_used": 114447, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1662, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606310, + "gas_refund_counter": 0, + "gas_used": 114450, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1663, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606307, + "gas_refund_counter": 0, + "gas_used": 114453, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1664, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606304, + "gas_refund_counter": 0, + "gas_used": 114456, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1665, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606302, + "gas_refund_counter": 0, + "gas_used": 114458, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1668, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606299, + "gas_refund_counter": 0, + "gas_used": 114461, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1669, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606297, + "gas_refund_counter": 0, + "gas_used": 114463, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1670, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606294, + "gas_refund_counter": 0, + "gas_used": 114466, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1671, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606291, + "gas_refund_counter": 0, + "gas_used": 114469, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1672, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606288, + "gas_refund_counter": 0, + "gas_used": 114472, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1675, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606285, + "gas_refund_counter": 0, + "gas_used": 114475, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1642, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606277, + "gas_refund_counter": 0, + "gas_used": 114483, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1643, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606276, + "gas_refund_counter": 0, + "gas_used": 114484, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1646, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606273, + "gas_refund_counter": 0, + "gas_used": 114487, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1647, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606270, + "gas_refund_counter": 0, + "gas_used": 114490, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1650, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606267, + "gas_refund_counter": 0, + "gas_used": 114493, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1551, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606259, + "gas_refund_counter": 0, + "gas_used": 114501, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1552, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606258, + "gas_refund_counter": 0, + "gas_used": 114502, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1553, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606256, + "gas_refund_counter": 0, + "gas_used": 114504, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1554, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606253, + "gas_refund_counter": 0, + "gas_used": 114507, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1555, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606250, + "gas_refund_counter": 0, + "gas_used": 114510, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1556, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606248, + "gas_refund_counter": 0, + "gas_used": 114512, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1557, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606245, + "gas_refund_counter": 0, + "gas_used": 114515, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1558, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606242, + "gas_refund_counter": 0, + "gas_used": 114518, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1559, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606240, + "gas_refund_counter": 0, + "gas_used": 114520, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1651, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606232, + "gas_refund_counter": 0, + "gas_used": 114528, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1652, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606231, + "gas_refund_counter": 0, + "gas_used": 114529, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1653, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606228, + "gas_refund_counter": 0, + "gas_used": 114532, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1654, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606225, + "gas_refund_counter": 0, + "gas_used": 114535, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1655, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606223, + "gas_refund_counter": 0, + "gas_used": 114537, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1656, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606221, + "gas_refund_counter": 0, + "gas_used": 114539, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1676, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606213, + "gas_refund_counter": 0, + "gas_used": 114547, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1677, + "op": 146, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606212, + "gas_refund_counter": 0, + "gas_used": 114548, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1678, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606209, + "gas_refund_counter": 0, + "gas_used": 114551, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1679, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606206, + "gas_refund_counter": 0, + "gas_used": 114554, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1680, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606204, + "gas_refund_counter": 0, + "gas_used": 114556, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1681, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606202, + "gas_refund_counter": 0, + "gas_used": 114558, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 834, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606194, + "gas_refund_counter": 0, + "gas_used": 114566, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 835, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606193, + "gas_refund_counter": 0, + "gas_used": 114567, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 837, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606190, + "gas_refund_counter": 0, + "gas_used": 114570, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 838, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606187, + "gas_refund_counter": 0, + "gas_used": 114573, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 840, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606184, + "gas_refund_counter": 0, + "gas_used": 114576, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 841, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606181, + "gas_refund_counter": 0, + "gas_used": 114579, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 842, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606178, + "gas_refund_counter": 0, + "gas_used": 114582, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 843, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606175, + "gas_refund_counter": 0, + "gas_used": 114585, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 844, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606172, + "gas_refund_counter": 0, + "gas_used": 114588, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 845, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606169, + "gas_refund_counter": 0, + "gas_used": 114591, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 846, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606166, + "gas_refund_counter": 0, + "gas_used": 114594, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 847, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606163, + "gas_refund_counter": 0, + "gas_used": 114597, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 849, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606160, + "gas_refund_counter": 0, + "gas_used": 114600, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 850, + "op": 127, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606157, + "gas_refund_counter": 0, + "gas_used": 114603, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 883, + "op": 123, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606154, + "gas_refund_counter": 0, + "gas_used": 114606, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 912, + "op": 25, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606151, + "gas_refund_counter": 0, + "gas_used": 114609, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 913, + "op": 22, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606148, + "gas_refund_counter": 0, + "gas_used": 114612, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 914, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606145, + "gas_refund_counter": 0, + "gas_used": 114615, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 916, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606142, + "gas_refund_counter": 0, + "gas_used": 114618, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 917, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606139, + "gas_refund_counter": 0, + "gas_used": 114621, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 918, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606136, + "gas_refund_counter": 0, + "gas_used": 114624, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 919, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606133, + "gas_refund_counter": 0, + "gas_used": 114627, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 920, + "op": 123, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606130, + "gas_refund_counter": 0, + "gas_used": 114630, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 949, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606127, + "gas_refund_counter": 0, + "gas_used": 114633, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 950, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606124, + "gas_refund_counter": 0, + "gas_used": 114636, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 951, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606121, + "gas_refund_counter": 0, + "gas_used": 114639, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 952, + "op": 22, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606118, + "gas_refund_counter": 0, + "gas_used": 114642, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 953, + "op": 23, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606115, + "gas_refund_counter": 0, + "gas_used": 114645, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 954, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606112, + "gas_refund_counter": 0, + "gas_used": 114648, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 955, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606109, + "gas_refund_counter": 0, + "gas_used": 114651, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 956, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606106, + "gas_refund_counter": 0, + "gas_used": 114654, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 957, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606104, + "gas_refund_counter": 0, + "gas_used": 114656, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 958, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606102, + "gas_refund_counter": 0, + "gas_used": 114658, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 959, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606100, + "gas_refund_counter": 0, + "gas_used": 114660, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 960, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606098, + "gas_refund_counter": 0, + "gas_used": 114662, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 963, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606095, + "gas_refund_counter": 0, + "gas_used": 114665, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 967, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606087, + "gas_refund_counter": 0, + "gas_used": 114673, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 968, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606086, + "gas_refund_counter": 0, + "gas_used": 114674, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 971, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606083, + "gas_refund_counter": 0, + "gas_used": 114677, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 972, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606080, + "gas_refund_counter": 0, + "gas_used": 114680, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 975, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606077, + "gas_refund_counter": 0, + "gas_used": 114683, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 978, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606074, + "gas_refund_counter": 0, + "gas_used": 114686, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 981, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606071, + "gas_refund_counter": 0, + "gas_used": 114689, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1024, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606063, + "gas_refund_counter": 0, + "gas_used": 114697, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1025, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606062, + "gas_refund_counter": 0, + "gas_used": 114698, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1028, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606059, + "gas_refund_counter": 0, + "gas_used": 114701, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1029, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606056, + "gas_refund_counter": 0, + "gas_used": 114704, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1030, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606053, + "gas_refund_counter": 0, + "gas_used": 114707, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1031, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606051, + "gas_refund_counter": 0, + "gas_used": 114709, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1032, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606048, + "gas_refund_counter": 0, + "gas_used": 114712, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1033, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606045, + "gas_refund_counter": 0, + "gas_used": 114715, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1034, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606043, + "gas_refund_counter": 0, + "gas_used": 114717, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 982, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606035, + "gas_refund_counter": 0, + "gas_used": 114725, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 983, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606034, + "gas_refund_counter": 0, + "gas_used": 114726, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 988, + "op": 22, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606031, + "gas_refund_counter": 0, + "gas_used": 114729, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 989, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606028, + "gas_refund_counter": 0, + "gas_used": 114732, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 993, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606020, + "gas_refund_counter": 0, + "gas_used": 114740, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 994, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606019, + "gas_refund_counter": 0, + "gas_used": 114741, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 995, + "op": 106, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606017, + "gas_refund_counter": 0, + "gas_used": 114743, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1007, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606014, + "gas_refund_counter": 0, + "gas_used": 114746, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1008, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606011, + "gas_refund_counter": 0, + "gas_used": 114749, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1009, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606009, + "gas_refund_counter": 0, + "gas_used": 114751, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1010, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606007, + "gas_refund_counter": 0, + "gas_used": 114753, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1011, + "op": 131, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606005, + "gas_refund_counter": 0, + "gas_used": 114755, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1012, + "op": 81, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073606002, + "gas_refund_counter": 0, + "gas_used": 114758, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1013, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605999, + "gas_refund_counter": 0, + "gas_used": 114761, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1015, + "op": 133, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605996, + "gas_refund_counter": 0, + "gas_used": 114764, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1016, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605993, + "gas_refund_counter": 0, + "gas_used": 114767, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1017, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605990, + "gas_refund_counter": 0, + "gas_used": 114770, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1018, + "op": 90, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605987, + "gas_refund_counter": 0, + "gas_used": 114773, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1019, + "op": 250, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073605985, + "gas_refund_counter": 0, + "gas_used": 114775, + "gas_cost": 1056830933, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1020, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603385, + "gas_refund_counter": 0, + "gas_used": 117375, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1021, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603383, + "gas_refund_counter": 0, + "gas_used": 117377, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1022, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603381, + "gas_refund_counter": 0, + "gas_used": 117379, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 1023, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603379, + "gas_refund_counter": 0, + "gas_used": 117381, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 990, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603371, + "gas_refund_counter": 0, + "gas_used": 117389, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 991, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603370, + "gas_refund_counter": 0, + "gas_used": 117390, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 992, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603368, + "gas_refund_counter": 0, + "gas_used": 117392, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 964, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603360, + "gas_refund_counter": 0, + "gas_used": 117400, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 965, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603359, + "gas_refund_counter": 0, + "gas_used": 117401, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 966, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603357, + "gas_refund_counter": 0, + "gas_used": 117403, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 756, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603349, + "gas_refund_counter": 0, + "gas_used": 117411, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 757, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603348, + "gas_refund_counter": 0, + "gas_used": 117412, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 758, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603346, + "gas_refund_counter": 0, + "gas_used": 117414, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 146, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603338, + "gas_refund_counter": 0, + "gas_used": 117422, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 147, + "op": 0, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1073603337, + "gas_refund_counter": 0, + "gas_used": 117423, + "gas_cost": 0, + "storage_change": null, + "status": "Stop", + "immediate_bytes": null, + "decoded": null + } + ], + "decoded": null }, "logs": [], "ordering": [ + { + "Step": 0 + }, + { + "Step": 1 + }, + { + "Step": 2 + }, + { + "Step": 3 + }, + { + "Step": 4 + }, + { + "Step": 5 + }, + { + "Step": 6 + }, + { + "Step": 7 + }, + { + "Step": 8 + }, + { + "Step": 9 + }, + { + "Step": 10 + }, + { + "Step": 11 + }, + { + "Step": 12 + }, + { + "Step": 13 + }, + { + "Step": 14 + }, + { + "Step": 15 + }, + { + "Step": 16 + }, + { + "Step": 17 + }, + { + "Step": 18 + }, + { + "Step": 19 + }, + { + "Step": 20 + }, + { + "Step": 21 + }, + { + "Step": 22 + }, + { + "Step": 23 + }, + { + "Step": 24 + }, + { + "Step": 25 + }, + { + "Step": 26 + }, + { + "Step": 27 + }, + { + "Step": 28 + }, + { + "Step": 29 + }, + { + "Step": 30 + }, + { + "Step": 31 + }, + { + "Step": 32 + }, + { + "Step": 33 + }, + { + "Step": 34 + }, + { + "Step": 35 + }, + { + "Step": 36 + }, + { + "Step": 37 + }, + { + "Step": 38 + }, + { + "Step": 39 + }, + { + "Step": 40 + }, + { + "Step": 41 + }, + { + "Step": 42 + }, + { + "Step": 43 + }, + { + "Step": 44 + }, + { + "Step": 45 + }, + { + "Step": 46 + }, + { + "Step": 47 + }, + { + "Step": 48 + }, + { + "Step": 49 + }, + { + "Step": 50 + }, + { + "Step": 51 + }, + { + "Step": 52 + }, + { + "Step": 53 + }, + { + "Step": 54 + }, + { + "Step": 55 + }, + { + "Step": 56 + }, + { + "Step": 57 + }, + { + "Step": 58 + }, + { + "Step": 59 + }, + { + "Step": 60 + }, + { + "Step": 61 + }, + { + "Step": 62 + }, + { + "Step": 63 + }, { "Call": 0 }, { - "Call": 1 + "Step": 64 }, { - "Call": 2 - } - ] - }, - { - "parent": 0, - "children": [], - "idx": 1, - "trace": { - "depth": 1, - "success": true, - "caller": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", - "address": "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", - "maybe_precompile": false, - "selfdestruct_address": null, - "selfdestruct_refund_target": null, - "selfdestruct_transferred_value": null, - "kind": "CREATE", - "value": "0x0", - "data": "{...}", - "output": "{...}", - "gas_used": "{...}", - "gas_limit": "{...}", - "status": "Return", - "steps": [], - "decoded": { - "label": null, - "return_data": null, - "call_data": null + "Step": 65 + }, + { + "Step": 66 + }, + { + "Step": 67 + }, + { + "Step": 68 + }, + { + "Step": 69 + }, + { + "Step": 70 + }, + { + "Step": 71 + }, + { + "Step": 72 + }, + { + "Step": 73 + }, + { + "Step": 74 + }, + { + "Step": 75 + }, + { + "Step": 76 + }, + { + "Step": 77 + }, + { + "Step": 78 + }, + { + "Step": 79 + }, + { + "Step": 80 + }, + { + "Step": 81 + }, + { + "Step": 82 + }, + { + "Step": 83 + }, + { + "Step": 84 + }, + { + "Step": 85 + }, + { + "Step": 86 + }, + { + "Step": 87 + }, + { + "Step": 88 + }, + { + "Step": 89 + }, + { + "Step": 90 + }, + { + "Step": 91 + }, + { + "Step": 92 + }, + { + "Step": 93 + }, + { + "Step": 94 + }, + { + "Step": 95 + }, + { + "Step": 96 + }, + { + "Step": 97 + }, + { + "Step": 98 + }, + { + "Step": 99 + }, + { + "Step": 100 + }, + { + "Step": 101 + }, + { + "Step": 102 + }, + { + "Step": 103 + }, + { + "Step": 104 + }, + { + "Step": 105 + }, + { + "Step": 106 + }, + { + "Step": 107 + }, + { + "Step": 108 + }, + { + "Step": 109 + }, + { + "Step": 110 + }, + { + "Step": 111 + }, + { + "Step": 112 + }, + { + "Step": 113 + }, + { + "Step": 114 + }, + { + "Step": 115 + }, + { + "Step": 116 + }, + { + "Step": 117 + }, + { + "Step": 118 + }, + { + "Step": 119 + }, + { + "Step": 120 + }, + { + "Step": 121 + }, + { + "Step": 122 + }, + { + "Step": 123 + }, + { + "Step": 124 + }, + { + "Step": 125 + }, + { + "Step": 126 + }, + { + "Step": 127 + }, + { + "Step": 128 + }, + { + "Step": 129 + }, + { + "Step": 130 + }, + { + "Step": 131 + }, + { + "Step": 132 + }, + { + "Step": 133 + }, + { + "Step": 134 + }, + { + "Step": 135 + }, + { + "Step": 136 + }, + { + "Step": 137 + }, + { + "Step": 138 + }, + { + "Step": 139 + }, + { + "Step": 140 + }, + { + "Step": 141 + }, + { + "Step": 142 + }, + { + "Step": 143 + }, + { + "Step": 144 + }, + { + "Step": 145 + }, + { + "Step": 146 + }, + { + "Step": 147 + }, + { + "Step": 148 + }, + { + "Step": 149 + }, + { + "Step": 150 + }, + { + "Step": 151 + }, + { + "Step": 152 + }, + { + "Step": 153 + }, + { + "Step": 154 + }, + { + "Step": 155 + }, + { + "Step": 156 + }, + { + "Step": 157 + }, + { + "Step": 158 + }, + { + "Step": 159 + }, + { + "Step": 160 + }, + { + "Step": 161 + }, + { + "Step": 162 + }, + { + "Step": 163 + }, + { + "Step": 164 + }, + { + "Step": 165 + }, + { + "Step": 166 + }, + { + "Step": 167 + }, + { + "Step": 168 + }, + { + "Step": 169 + }, + { + "Step": 170 + }, + { + "Step": 171 + }, + { + "Step": 172 + }, + { + "Step": 173 + }, + { + "Step": 174 + }, + { + "Step": 175 + }, + { + "Step": 176 + }, + { + "Step": 177 + }, + { + "Step": 178 + }, + { + "Step": 179 + }, + { + "Step": 180 + }, + { + "Step": 181 + }, + { + "Step": 182 + }, + { + "Step": 183 + }, + { + "Step": 184 + }, + { + "Step": 185 + }, + { + "Step": 186 + }, + { + "Step": 187 + }, + { + "Step": 188 + }, + { + "Step": 189 + }, + { + "Step": 190 + }, + { + "Step": 191 + }, + { + "Step": 192 + }, + { + "Step": 193 + }, + { + "Step": 194 + }, + { + "Call": 1 + }, + { + "Step": 195 + }, + { + "Step": 196 + }, + { + "Step": 197 + }, + { + "Step": 198 + }, + { + "Step": 199 + }, + { + "Step": 200 + }, + { + "Step": 201 + }, + { + "Step": 202 + }, + { + "Step": 203 + }, + { + "Step": 204 + }, + { + "Step": 205 + }, + { + "Step": 206 + }, + { + "Step": 207 + }, + { + "Step": 208 + }, + { + "Step": 209 + }, + { + "Step": 210 + }, + { + "Step": 211 + }, + { + "Step": 212 + }, + { + "Step": 213 + }, + { + "Step": 214 + }, + { + "Step": 215 + }, + { + "Step": 216 + }, + { + "Step": 217 + }, + { + "Step": 218 + }, + { + "Step": 219 + }, + { + "Step": 220 + }, + { + "Step": 221 + }, + { + "Step": 222 + }, + { + "Step": 223 + }, + { + "Step": 224 + }, + { + "Step": 225 + }, + { + "Step": 226 + }, + { + "Step": 227 + }, + { + "Step": 228 + }, + { + "Step": 229 + }, + { + "Step": 230 + }, + { + "Step": 231 + }, + { + "Step": 232 + }, + { + "Step": 233 + }, + { + "Step": 234 + }, + { + "Step": 235 + }, + { + "Step": 236 + }, + { + "Step": 237 + }, + { + "Step": 238 + }, + { + "Step": 239 + }, + { + "Step": 240 + }, + { + "Step": 241 + }, + { + "Step": 242 + }, + { + "Step": 243 + }, + { + "Step": 244 + }, + { + "Step": 245 + }, + { + "Step": 246 + }, + { + "Step": 247 + }, + { + "Step": 248 + }, + { + "Step": 249 + }, + { + "Step": 250 + }, + { + "Step": 251 + }, + { + "Step": 252 + }, + { + "Step": 253 + }, + { + "Step": 254 + }, + { + "Step": 255 + }, + { + "Step": 256 + }, + { + "Step": 257 + }, + { + "Step": 258 + }, + { + "Step": 259 + }, + { + "Step": 260 + }, + { + "Step": 261 + }, + { + "Step": 262 + }, + { + "Step": 263 + }, + { + "Step": 264 + }, + { + "Step": 265 + }, + { + "Step": 266 + }, + { + "Step": 267 + }, + { + "Step": 268 + }, + { + "Step": 269 + }, + { + "Step": 270 + }, + { + "Step": 271 + }, + { + "Step": 272 + }, + { + "Step": 273 + }, + { + "Step": 274 + }, + { + "Step": 275 + }, + { + "Step": 276 + }, + { + "Step": 277 + }, + { + "Step": 278 + }, + { + "Step": 279 + }, + { + "Step": 280 + }, + { + "Step": 281 + }, + { + "Step": 282 + }, + { + "Step": 283 + }, + { + "Step": 284 + }, + { + "Step": 285 + }, + { + "Step": 286 + }, + { + "Step": 287 + }, + { + "Step": 288 + }, + { + "Step": 289 + }, + { + "Step": 290 + }, + { + "Step": 291 + }, + { + "Step": 292 + }, + { + "Step": 293 + }, + { + "Step": 294 + }, + { + "Step": 295 + }, + { + "Step": 296 + }, + { + "Step": 297 + }, + { + "Step": 298 + }, + { + "Step": 299 + }, + { + "Step": 300 + }, + { + "Step": 301 + }, + { + "Step": 302 + }, + { + "Step": 303 + }, + { + "Step": 304 + }, + { + "Step": 305 + }, + { + "Step": 306 + }, + { + "Step": 307 + }, + { + "Step": 308 + }, + { + "Step": 309 + }, + { + "Step": 310 + }, + { + "Step": 311 + }, + { + "Step": 312 + }, + { + "Step": 313 + }, + { + "Step": 314 + }, + { + "Step": 315 + }, + { + "Step": 316 + }, + { + "Step": 317 + }, + { + "Step": 318 + }, + { + "Step": 319 + }, + { + "Step": 320 + }, + { + "Step": 321 + }, + { + "Step": 322 + }, + { + "Step": 323 + }, + { + "Step": 324 + }, + { + "Step": 325 + }, + { + "Step": 326 + }, + { + "Step": 327 + }, + { + "Step": 328 + }, + { + "Step": 329 + }, + { + "Step": 330 + }, + { + "Step": 331 + }, + { + "Call": 2 + }, + { + "Step": 332 + }, + { + "Step": 333 + }, + { + "Step": 334 + }, + { + "Step": 335 + }, + { + "Step": 336 + }, + { + "Step": 337 + }, + { + "Step": 338 + }, + { + "Step": 339 + }, + { + "Step": 340 + }, + { + "Step": 341 + }, + { + "Step": 342 + }, + { + "Step": 343 + }, + { + "Step": 344 + }, + { + "Step": 345 + }, + { + "Step": 346 + } + ] + }, + { + "parent": 0, + "children": [], + "idx": 1, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "address": "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "{...}", + "output": "{...}", + "gas_used": "{...}", + "gas_limit": "{...}", + "gas_refund_counter": 0, + "status": "Return", + "steps": [ + { + "pc": 0, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 2, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912053, + "gas_refund_counter": 0, + "gas_used": 3, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 4, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912050, + "gas_refund_counter": 0, + "gas_used": 6, + "gas_cost": 12, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 5, + "op": 52, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912038, + "gas_refund_counter": 0, + "gas_used": 18, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 6, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912036, + "gas_refund_counter": 0, + "gas_used": 20, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 7, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912033, + "gas_refund_counter": 0, + "gas_used": 23, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 8, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912030, + "gas_refund_counter": 0, + "gas_used": 26, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 10, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912027, + "gas_refund_counter": 0, + "gas_used": 29, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 14, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912017, + "gas_refund_counter": 0, + "gas_used": 39, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 15, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912016, + "gas_refund_counter": 0, + "gas_used": 40, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 16, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912014, + "gas_refund_counter": 0, + "gas_used": 42, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 19, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912011, + "gas_refund_counter": 0, + "gas_used": 45, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 20, + "op": 97, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912008, + "gas_refund_counter": 0, + "gas_used": 48, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 23, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912005, + "gas_refund_counter": 0, + "gas_used": 51, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 24, + "op": 57, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056912003, + "gas_refund_counter": 0, + "gas_used": 53, + "gas_cost": 54, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 25, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056911949, + "gas_refund_counter": 0, + "gas_used": 107, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 26, + "op": 243, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056911947, + "gas_refund_counter": 0, + "gas_used": 109, + "gas_cost": 0, + "storage_change": null, + "status": "Return", + "immediate_bytes": null, + "decoded": null + } + ], + "decoded": null + }, + "logs": [], + "ordering": [ + { + "Step": 0 + }, + { + "Step": 1 + }, + { + "Step": 2 + }, + { + "Step": 3 + }, + { + "Step": 4 + }, + { + "Step": 5 + }, + { + "Step": 6 + }, + { + "Step": 7 + }, + { + "Step": 8 + }, + { + "Step": 9 + }, + { + "Step": 10 + }, + { + "Step": 11 + }, + { + "Step": 12 + }, + { + "Step": 13 + }, + { + "Step": 14 + }, + { + "Step": 15 + }, + { + "Step": 16 } - }, - "logs": [], - "ordering": [] + ] }, { "parent": 0, @@ -158,16 +7068,2357 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", - "steps": [], - "decoded": { - "label": null, - "return_data": null, - "call_data": null - } + "steps": [ + { + "pc": 0, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 2, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 4, + "op": 82, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 12, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 5, + "op": 52, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 6, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 7, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 8, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 10, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 14, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 15, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 16, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 18, + "op": 54, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 19, + "op": 16, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 20, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 22, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 23, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 24, + "op": 53, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 25, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 27, + "op": 28, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 28, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 29, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 34, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 35, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 37, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": "{...}", + "gas_refund_counter": 0, + "gas_used": "{...}", + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 38, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853373, + "gas_refund_counter": 0, + "gas_used": 96, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 39, + "op": 99, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853370, + "gas_refund_counter": 0, + "gas_used": 99, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 44, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853367, + "gas_refund_counter": 0, + "gas_used": 102, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 45, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853364, + "gas_refund_counter": 0, + "gas_used": 105, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 47, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853361, + "gas_refund_counter": 0, + "gas_used": 108, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 78, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853351, + "gas_refund_counter": 0, + "gas_used": 118, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 79, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853350, + "gas_refund_counter": 0, + "gas_used": 119, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 81, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853347, + "gas_refund_counter": 0, + "gas_used": 122, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 83, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853344, + "gas_refund_counter": 0, + "gas_used": 125, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 84, + "op": 54, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853341, + "gas_refund_counter": 0, + "gas_used": 128, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 85, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853339, + "gas_refund_counter": 0, + "gas_used": 130, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 86, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853336, + "gas_refund_counter": 0, + "gas_used": 133, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 87, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853333, + "gas_refund_counter": 0, + "gas_used": 136, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 88, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853330, + "gas_refund_counter": 0, + "gas_used": 139, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 89, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853327, + "gas_refund_counter": 0, + "gas_used": 142, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 91, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853324, + "gas_refund_counter": 0, + "gas_used": 145, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 92, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853321, + "gas_refund_counter": 0, + "gas_used": 148, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 93, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853318, + "gas_refund_counter": 0, + "gas_used": 151, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 95, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853315, + "gas_refund_counter": 0, + "gas_used": 154, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 202, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853307, + "gas_refund_counter": 0, + "gas_used": 162, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 203, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853306, + "gas_refund_counter": 0, + "gas_used": 163, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 204, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853304, + "gas_refund_counter": 0, + "gas_used": 165, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 206, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853301, + "gas_refund_counter": 0, + "gas_used": 168, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 207, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853298, + "gas_refund_counter": 0, + "gas_used": 171, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 208, + "op": 3, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853295, + "gas_refund_counter": 0, + "gas_used": 174, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 209, + "op": 18, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853292, + "gas_refund_counter": 0, + "gas_used": 177, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 210, + "op": 21, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853289, + "gas_refund_counter": 0, + "gas_used": 180, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 211, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853286, + "gas_refund_counter": 0, + "gas_used": 183, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 213, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853283, + "gas_refund_counter": 0, + "gas_used": 186, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 220, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853273, + "gas_refund_counter": 0, + "gas_used": 196, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 221, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853272, + "gas_refund_counter": 0, + "gas_used": 197, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 222, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853270, + "gas_refund_counter": 0, + "gas_used": 199, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 224, + "op": 132, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853267, + "gas_refund_counter": 0, + "gas_used": 202, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 225, + "op": 130, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853264, + "gas_refund_counter": 0, + "gas_used": 205, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 226, + "op": 133, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853261, + "gas_refund_counter": 0, + "gas_used": 208, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 227, + "op": 1, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853258, + "gas_refund_counter": 0, + "gas_used": 211, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 228, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853255, + "gas_refund_counter": 0, + "gas_used": 214, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 230, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853252, + "gas_refund_counter": 0, + "gas_used": 217, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 184, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853244, + "gas_refund_counter": 0, + "gas_used": 225, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 185, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853243, + "gas_refund_counter": 0, + "gas_used": 226, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 186, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853241, + "gas_refund_counter": 0, + "gas_used": 228, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 187, + "op": 53, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853238, + "gas_refund_counter": 0, + "gas_used": 231, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 188, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853235, + "gas_refund_counter": 0, + "gas_used": 234, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 189, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853232, + "gas_refund_counter": 0, + "gas_used": 237, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 190, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853230, + "gas_refund_counter": 0, + "gas_used": 239, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 192, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853227, + "gas_refund_counter": 0, + "gas_used": 242, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 193, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853224, + "gas_refund_counter": 0, + "gas_used": 245, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 195, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853221, + "gas_refund_counter": 0, + "gas_used": 248, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 165, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853213, + "gas_refund_counter": 0, + "gas_used": 256, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 166, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853212, + "gas_refund_counter": 0, + "gas_used": 257, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 168, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853209, + "gas_refund_counter": 0, + "gas_used": 260, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 169, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853206, + "gas_refund_counter": 0, + "gas_used": 263, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 171, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853203, + "gas_refund_counter": 0, + "gas_used": 266, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 116, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853195, + "gas_refund_counter": 0, + "gas_used": 274, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 117, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853194, + "gas_refund_counter": 0, + "gas_used": 275, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 118, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853192, + "gas_refund_counter": 0, + "gas_used": 277, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 119, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853189, + "gas_refund_counter": 0, + "gas_used": 280, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 120, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853186, + "gas_refund_counter": 0, + "gas_used": 283, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 121, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853184, + "gas_refund_counter": 0, + "gas_used": 285, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 122, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853181, + "gas_refund_counter": 0, + "gas_used": 288, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 123, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853178, + "gas_refund_counter": 0, + "gas_used": 291, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 124, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853176, + "gas_refund_counter": 0, + "gas_used": 293, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 172, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853168, + "gas_refund_counter": 0, + "gas_used": 301, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 173, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853167, + "gas_refund_counter": 0, + "gas_used": 302, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 174, + "op": 20, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853164, + "gas_refund_counter": 0, + "gas_used": 305, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 175, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853161, + "gas_refund_counter": 0, + "gas_used": 308, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 177, + "op": 87, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853158, + "gas_refund_counter": 0, + "gas_used": 311, + "gas_cost": 10, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 181, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853148, + "gas_refund_counter": 0, + "gas_used": 321, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 182, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853147, + "gas_refund_counter": 0, + "gas_used": 322, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 183, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853145, + "gas_refund_counter": 0, + "gas_used": 324, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 196, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853137, + "gas_refund_counter": 0, + "gas_used": 332, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 197, + "op": 146, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853136, + "gas_refund_counter": 0, + "gas_used": 333, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 198, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853133, + "gas_refund_counter": 0, + "gas_used": 336, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 199, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853130, + "gas_refund_counter": 0, + "gas_used": 339, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 200, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853128, + "gas_refund_counter": 0, + "gas_used": 341, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 201, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853126, + "gas_refund_counter": 0, + "gas_used": 343, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 231, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853118, + "gas_refund_counter": 0, + "gas_used": 351, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 232, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853117, + "gas_refund_counter": 0, + "gas_used": 352, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 233, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853114, + "gas_refund_counter": 0, + "gas_used": 355, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 234, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853112, + "gas_refund_counter": 0, + "gas_used": 357, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 235, + "op": 146, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853110, + "gas_refund_counter": 0, + "gas_used": 359, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 236, + "op": 145, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853107, + "gas_refund_counter": 0, + "gas_used": 362, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 237, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853104, + "gas_refund_counter": 0, + "gas_used": 365, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 238, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853102, + "gas_refund_counter": 0, + "gas_used": 367, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 239, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853100, + "gas_refund_counter": 0, + "gas_used": 369, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 96, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853092, + "gas_refund_counter": 0, + "gas_used": 377, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 97, + "op": 96, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853091, + "gas_refund_counter": 0, + "gas_used": 378, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 99, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853088, + "gas_refund_counter": 0, + "gas_used": 381, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 107, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853080, + "gas_refund_counter": 0, + "gas_used": 389, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 108, + "op": 128, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853079, + "gas_refund_counter": 0, + "gas_used": 390, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 109, + "op": 95, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853076, + "gas_refund_counter": 0, + "gas_used": 393, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 110, + "op": 129, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853074, + "gas_refund_counter": 0, + "gas_used": 395, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 111, + "op": 144, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853071, + "gas_refund_counter": 0, + "gas_used": 398, + "gas_cost": 3, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 112, + "op": 85, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056853068, + "gas_refund_counter": 0, + "gas_used": 401, + "gas_cost": 22100, + "storage_change": { + "key": "0x0", + "value": "0x64", + "had_value": "0x0", + "reason": "SSTORE" + }, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 113, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056830968, + "gas_refund_counter": 0, + "gas_used": 22501, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 114, + "op": 80, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056830966, + "gas_refund_counter": 0, + "gas_used": 22503, + "gas_cost": 2, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 115, + "op": 86, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056830964, + "gas_refund_counter": 0, + "gas_used": 22505, + "gas_cost": 8, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 100, + "op": 91, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056830956, + "gas_refund_counter": 0, + "gas_used": 22513, + "gas_cost": 1, + "storage_change": null, + "status": null, + "immediate_bytes": null, + "decoded": null + }, + { + "pc": 101, + "op": 0, + "stack": null, + "push_stack": null, + "memory": null, + "returndata": "0x", + "gas_remaining": 1056830955, + "gas_refund_counter": 0, + "gas_used": 22514, + "gas_cost": 0, + "storage_change": null, + "status": "Stop", + "immediate_bytes": null, + "decoded": null + } + ], + "decoded": null }, "logs": [], - "ordering": [] + "ordering": [ + { + "Step": 0 + }, + { + "Step": 1 + }, + { + "Step": 2 + }, + { + "Step": 3 + }, + { + "Step": 4 + }, + { + "Step": 5 + }, + { + "Step": 6 + }, + { + "Step": 7 + }, + { + "Step": 8 + }, + { + "Step": 9 + }, + { + "Step": 10 + }, + { + "Step": 11 + }, + { + "Step": 12 + }, + { + "Step": 13 + }, + { + "Step": 14 + }, + { + "Step": 15 + }, + { + "Step": 16 + }, + { + "Step": 17 + }, + { + "Step": 18 + }, + { + "Step": 19 + }, + { + "Step": 20 + }, + { + "Step": 21 + }, + { + "Step": 22 + }, + { + "Step": 23 + }, + { + "Step": 24 + }, + { + "Step": 25 + }, + { + "Step": 26 + }, + { + "Step": 27 + }, + { + "Step": 28 + }, + { + "Step": 29 + }, + { + "Step": 30 + }, + { + "Step": 31 + }, + { + "Step": 32 + }, + { + "Step": 33 + }, + { + "Step": 34 + }, + { + "Step": 35 + }, + { + "Step": 36 + }, + { + "Step": 37 + }, + { + "Step": 38 + }, + { + "Step": 39 + }, + { + "Step": 40 + }, + { + "Step": 41 + }, + { + "Step": 42 + }, + { + "Step": 43 + }, + { + "Step": 44 + }, + { + "Step": 45 + }, + { + "Step": 46 + }, + { + "Step": 47 + }, + { + "Step": 48 + }, + { + "Step": 49 + }, + { + "Step": 50 + }, + { + "Step": 51 + }, + { + "Step": 52 + }, + { + "Step": 53 + }, + { + "Step": 54 + }, + { + "Step": 55 + }, + { + "Step": 56 + }, + { + "Step": 57 + }, + { + "Step": 58 + }, + { + "Step": 59 + }, + { + "Step": 60 + }, + { + "Step": 61 + }, + { + "Step": 62 + }, + { + "Step": 63 + }, + { + "Step": 64 + }, + { + "Step": 65 + }, + { + "Step": 66 + }, + { + "Step": 67 + }, + { + "Step": 68 + }, + { + "Step": 69 + }, + { + "Step": 70 + }, + { + "Step": 71 + }, + { + "Step": 72 + }, + { + "Step": 73 + }, + { + "Step": 74 + }, + { + "Step": 75 + }, + { + "Step": 76 + }, + { + "Step": 77 + }, + { + "Step": 78 + }, + { + "Step": 79 + }, + { + "Step": 80 + }, + { + "Step": 81 + }, + { + "Step": 82 + }, + { + "Step": 83 + }, + { + "Step": 84 + }, + { + "Step": 85 + }, + { + "Step": 86 + }, + { + "Step": 87 + }, + { + "Step": 88 + }, + { + "Step": 89 + }, + { + "Step": 90 + }, + { + "Step": 91 + }, + { + "Step": 92 + }, + { + "Step": 93 + }, + { + "Step": 94 + }, + { + "Step": 95 + }, + { + "Step": 96 + }, + { + "Step": 97 + }, + { + "Step": 98 + }, + { + "Step": 99 + }, + { + "Step": 100 + }, + { + "Step": 101 + }, + { + "Step": 102 + }, + { + "Step": 103 + }, + { + "Step": 104 + }, + { + "Step": 105 + }, + { + "Step": 106 + }, + { + "Step": 107 + }, + { + "Step": 108 + }, + { + "Step": 109 + }, + { + "Step": 110 + }, + { + "Step": 111 + }, + { + "Step": 112 + }, + { + "Step": 113 + }, + { + "Step": 114 + }, + { + "Step": 115 + }, + { + "Step": 116 + }, + { + "Step": 117 + }, + { + "Step": 118 + }, + { + "Step": 119 + }, + { + "Step": 120 + }, + { + "Step": 121 + }, + { + "Step": 122 + } + ] }, { "parent": 0, @@ -188,13 +9439,10 @@ "output": "0x", "gas_used": "{...}", "gas_limit": "{...}", + "gas_refund_counter": 0, "status": "Stop", "steps": [], - "decoded": { - "label": null, - "return_data": null, - "call_data": null - } + "decoded": null }, "logs": [], "ordering": [] @@ -211,4 +9459,4 @@ }, "warnings": [] } -} \ No newline at end of file +} diff --git a/crates/forge/tests/fixtures/backtraces/Backtrace.t.sol b/crates/forge/tests/fixtures/backtraces/Backtrace.t.sol new file mode 100644 index 0000000000000..c6e8d6d72504f --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/Backtrace.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/Vm.sol"; +import "../src/SimpleRevert.sol"; +import "../src/NestedCalls.sol"; +import "../src/DelegateCall.sol"; +import "../src/StaticCall.sol"; + +contract BacktraceTest is DSTest { + SimpleRevert simpleRevert; + NestedCalls nestedCalls; + DelegateTarget delegateTarget; + DelegateCaller delegateCaller; + StaticTarget staticTarget; + StaticCaller staticCaller; + + function setUp() public { + simpleRevert = new SimpleRevert(); + nestedCalls = new NestedCalls(); + delegateTarget = new DelegateTarget(); + delegateCaller = new DelegateCaller(address(delegateTarget)); + staticTarget = new StaticTarget(); + staticCaller = new StaticCaller(address(staticTarget)); + } + + // Simple revert test + function testSimpleRevert() public { + simpleRevert.doRevert("Simple revert message"); + } + + // Require failure test + function testRequireFail() public { + simpleRevert.doRequire(0); + } + + // Assert failure test + function testAssertFail() public { + simpleRevert.doAssert(); + } + + // Custom error test + function testCustomError() public { + simpleRevert.doCustomError(); + } + + // Nested calls test + function testNestedCalls() public { + nestedCalls.nestedCall(5); + } + + // Internal call chain test + function testInternalCallsSameSource() public { + nestedCalls.callChain1(); + } + + // Test internal calls within test contract + function testInternalCallChain() public { + internalCall1(); + } + + function internalCall1() internal { + internalCall2(); + } + + function internalCall2() internal { + internalCall3(); + } + + function internalCall3() internal pure { + revert("Failed at internal level 3"); + } + + // Delegate call revert test + function testDelegateCallRevert() public { + delegateCaller.delegateFail(); + } + + // Delegate call require test + function testDelegateCallRequire() public { + delegateCaller.delegateCompute(0, 5); + } + + // Static call revert test + function testStaticCallRevert() public view { + staticCaller.staticCallFail(); + } + + // Static call require test + function testStaticCallRequire() public view { + staticCaller.staticCompute(0); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/DelegateCall.sol b/crates/forge/tests/fixtures/backtraces/DelegateCall.sol new file mode 100644 index 0000000000000..6946e527c1a1a --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/DelegateCall.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title DelegateCall - Testing delegate call traces +contract DelegateTarget { + function fail() public pure { + revert("Delegate target failed"); + } + + function compute(uint256 a, uint256 b) public pure returns (uint256) { + require(a > 0, "a must be positive"); + require(b > 0, "b must be positive"); + return a + b; + } +} + +contract DelegateCaller { + address public target; + + constructor(address _target) { + target = _target; + } + + function delegateFail() public { + (bool success,) = target.delegatecall(abi.encodeWithSignature("fail()")); + require(success, "Delegate call failed"); + } + + function delegateCompute(uint256 a, uint256 b) public returns (uint256) { + (bool success, bytes memory data) = + target.delegatecall(abi.encodeWithSignature("compute(uint256,uint256)", a, b)); + require(success, "Delegate compute failed"); + return abi.decode(data, (uint256)); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/ForkBacktrace.t.sol b/crates/forge/tests/fixtures/backtraces/ForkBacktrace.t.sol new file mode 100644 index 0000000000000..45149ffc72e86 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/ForkBacktrace.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/ForkedERC20Wrapper.sol"; + +contract ForkBacktraceTest is DSTest { + ForkedERC20Wrapper wrapper; + + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant CIRCLE = 0x55FE002aefF02F77364de339a1292923A15844B8; + + function setUp() public { + wrapper = new ForkedERC20Wrapper(); + } + + function testTransferWithoutBalance() public { + wrapper.transferWithoutBalance(address(0xdead), 1000000); + } + + function testTransferFromWithoutApproval() public { + wrapper.transferFromWithoutApproval(CIRCLE, address(0xdead), 1000000); + } + + function testRequireNonZeroBalance() public view { + wrapper.requireNonZeroBalance(address(wrapper)); + } + + function testNestedFailure() public { + wrapper.nestedFailure(); + } + + function testDirectOnChainRevert() public { + // Try to call transfer directly on USDC without having balance + (bool success,) = USDC.call(abi.encodeWithSignature("transfer(address,uint256)", address(0xdead), 1000000)); + require(success, "USDC transfer failed"); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/ForkedERC20Wrapper.sol b/crates/forge/tests/fixtures/backtraces/ForkedERC20Wrapper.sol new file mode 100644 index 0000000000000..e146e05d8ea00 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/ForkedERC20Wrapper.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); +} + +contract ForkedERC20Wrapper { + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + function transferWithoutBalance(address recipient, uint256 amount) public { + IERC20(USDC).transfer(recipient, amount); + } + + function transferFromWithoutApproval(address from, address to, uint256 amount) public { + IERC20(USDC).transferFrom(from, to, amount); + } + + function requireNonZeroBalance(address account) public view { + uint256 balance = IERC20(USDC).balanceOf(account); + require(balance > 0, "Account has zero USDC balance"); + } + + function nestedFailure() public { + internalCall(); + } + + function internalCall() internal { + transferWithoutBalance(address(0xdead), 1000000); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/LibraryBacktrace.t.sol b/crates/forge/tests/fixtures/backtraces/LibraryBacktrace.t.sol new file mode 100644 index 0000000000000..13fcd841cc13f --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/LibraryBacktrace.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/Vm.sol"; +import "../src/LibraryConsumer.sol"; +import "../src/libraries/ExternalMathLib.sol"; + +contract LibraryBacktraceTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + LibraryConsumer consumer; + address constant EXTERNAL_LIB_ADDRESS = 0x1234567890123456789012345678901234567890; + + function setUp() public { + // Deploy the external library at the configured address + bytes memory libraryBytecode = type(ExternalMathLib).runtimeCode; + vm.etch(EXTERNAL_LIB_ADDRESS, libraryBytecode); + + // Deploy consumer contract + consumer = new LibraryConsumer(); + } + + // Internal library tests (should show inlined source locations) + + /// @notice Test division by zero in internal library + function testInternalDivisionByZero() public { + consumer.internalDivide(100, 0); + } + + /// @notice Test underflow in internal library + function testInternalUnderflow() public { + consumer.internalSubtract(10, 20); + } + + /// @notice Test overflow in internal library + function testInternalOverflow() public { + consumer.internalMultiply(type(uint256).max, 2); + } + + /// @notice Test require in internal library + function testInternalRequire() public { + consumer.internalCheckPositive(0); + } + + // External library tests (should show delegatecall to library address) + + /// @notice Test division by zero in external library + function testExternalDivisionByZero() public { + consumer.externalDivide(100, 0); + } + + /// @notice Test underflow in external library + function testExternalUnderflow() public { + consumer.externalSubtract(10, 20); + } + + /// @notice Test overflow in external library + function testExternalOverflow() public { + consumer.externalMultiply(type(uint256).max, 2); + } + + /// @notice Test require in external library + function testExternalRequire() public { + consumer.externalCheckPositive(0); + } + + // Mixed library usage test + + /// @notice Test mixed library usage with failure in external library + function testMixedLibraryFailure() public { + // This will fail at the external library division step (50 - 50 = 0, then divide by 0) + consumer.mixedCalculation(50, 50, 0); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/LibraryConsumer.sol b/crates/forge/tests/fixtures/backtraces/LibraryConsumer.sol new file mode 100644 index 0000000000000..e62124915cd3e --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/LibraryConsumer.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./libraries/InternalMathLib.sol"; +import "./libraries/ExternalMathLib.sol"; + +/// @title LibraryConsumer - A contract that uses both internal and external libraries +contract LibraryConsumer { + using InternalMathLib for uint256; + + uint256 public result; + + // Internal library functions (inlined into contract bytecode) + + /// @notice Perform division using internal library + function internalDivide(uint256 a, uint256 b) public returns (uint256) { + result = a.div(b); + return result; + } + + /// @notice Perform multiplication using internal library + function internalMultiply(uint256 a, uint256 b) public returns (uint256) { + result = a.mul(b); + return result; + } + + /// @notice Perform subtraction using internal library + function internalSubtract(uint256 a, uint256 b) public returns (uint256) { + result = a.sub(b); + return result; + } + + /// @notice Check positive value using internal library + function internalCheckPositive(uint256 value) public returns (uint256) { + result = InternalMathLib.requirePositive(value); + return result; + } + + // External library functions (delegatecall to deployed library) + + /// @notice Perform division using external library + function externalDivide(uint256 a, uint256 b) public returns (uint256) { + result = ExternalMathLib.div(a, b); + return result; + } + + /// @notice Perform multiplication using external library + function externalMultiply(uint256 a, uint256 b) public returns (uint256) { + result = ExternalMathLib.mul(a, b); + return result; + } + + /// @notice Perform subtraction using external library + function externalSubtract(uint256 a, uint256 b) public returns (uint256) { + result = ExternalMathLib.sub(a, b); + return result; + } + + /// @notice Check positive value using external library + function externalCheckPositive(uint256 value) public returns (uint256) { + result = ExternalMathLib.requirePositive(value); + return result; + } + + // Mixed usage example + + /// @notice Complex calculation using both libraries + function mixedCalculation(uint256 a, uint256 b, uint256 c) public returns (uint256) { + // First use internal library + uint256 step1 = a.sub(b); + // Then use external library + uint256 step2 = ExternalMathLib.div(step1, c); + // Back to internal + result = step2.mul(10); + return result; + } +} diff --git a/crates/forge/tests/fixtures/backtraces/MultipleLibraryBacktrace.t.sol b/crates/forge/tests/fixtures/backtraces/MultipleLibraryBacktrace.t.sol new file mode 100644 index 0000000000000..de8864f8c408b --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/MultipleLibraryBacktrace.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../src/test.sol"; +import "../src/MultipleLibraryConsumer.sol"; + +contract MultipleLibraryBacktraceTest is DSTest { + MultipleLibraryConsumer consumer; + + function setUp() public { + consumer = new MultipleLibraryConsumer(); + } + + /// @notice Test that FirstMathLib shows correctly in backtrace + function testFirstLibraryError() public { + consumer.useFirstLib(10, 0); // Division by zero in FirstMathLib + } + + /// @notice Test that SecondMathLib shows correctly in backtrace + function testSecondLibraryError() public { + consumer.useSecondLib(5, 10); // Underflow in SecondMathLib + } + + /// @notice Test that ThirdMathLib shows correctly in backtrace + function testThirdLibraryError() public { + consumer.useThirdLib(10, 0); // Modulo by zero in ThirdMathLib + } + + /// @notice Test complex failure in the first library + function testAllLibrariesFirstFails() public { + consumer.useAllLibraries(10, 0, 5); // Division by zero in FirstMathLib + } +} diff --git a/crates/forge/tests/fixtures/backtraces/MultipleLibraryConsumer.sol b/crates/forge/tests/fixtures/backtraces/MultipleLibraryConsumer.sol new file mode 100644 index 0000000000000..fffdbc5475f7e --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/MultipleLibraryConsumer.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./libraries/MultipleLibraries.sol"; + +/// @title MultipleLibraryConsumer - A contract that uses multiple libraries from the same file +contract MultipleLibraryConsumer { + using FirstMathLib for uint256; + using SecondMathLib for uint256; + using ThirdMathLib for uint256; + + uint256 public result; + + /// @notice Test division from FirstMathLib + function useFirstLib(uint256 a, uint256 b) public returns (uint256) { + result = a.divide(b); // Should show FirstMathLib in backtrace + return result; + } + + /// @notice Test subtraction from SecondMathLib + function useSecondLib(uint256 a, uint256 b) public returns (uint256) { + result = a.subtract(b); // Should show SecondMathLib in backtrace + return result; + } + + /// @notice Test modulo from ThirdMathLib + function useThirdLib(uint256 a, uint256 b) public returns (uint256) { + result = a.modulo(b); // Should show ThirdMathLib in backtrace + return result; + } + + /// @notice Complex calculation using all three libraries + function useAllLibraries(uint256 a, uint256 b, uint256 c) public returns (uint256) { + uint256 step1 = a.divide(b); // FirstMathLib + uint256 step2 = step1.add(c); // SecondMathLib + result = step2.modulo(10); // ThirdMathLib + return result; + } +} diff --git a/crates/forge/tests/fixtures/backtraces/NestedCalls.sol b/crates/forge/tests/fixtures/backtraces/NestedCalls.sol new file mode 100644 index 0000000000000..67a4546a46f6b --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/NestedCalls.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title NestedCalls - Testing nested call stack traces +contract NestedCalls { + uint256 public depth; + + function nestedCall(uint256 maxDepth) public { + depth++; + if (depth >= maxDepth) { + revert("Maximum depth reached"); + } + this.nestedCall(maxDepth); + } + + function callChain1() public pure { + callChain2(); + } + + function callChain2() internal pure { + callChain3(); + } + + function callChain3() internal pure { + revert("Failed at chain level 3"); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/SimpleRevert.sol b/crates/forge/tests/fixtures/backtraces/SimpleRevert.sol new file mode 100644 index 0000000000000..02e46459dae79 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/SimpleRevert.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title SimpleRevert - Basic revert testing contract +contract SimpleRevert { + function doRevert(string memory reason) public pure { + revert(reason); + } + + function doRequire(uint256 value) public pure { + require(value > 0, "Value must be greater than zero"); + } + + function doAssert() public pure { + assert(false); + } + + error CustomError(uint256 code, address sender); + + function doCustomError() public view { + revert CustomError(42, msg.sender); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/StaticCall.sol b/crates/forge/tests/fixtures/backtraces/StaticCall.sol new file mode 100644 index 0000000000000..7129ab6fcb3bd --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/StaticCall.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title StaticCall - Testing static call traces +contract StaticTarget { + function viewFail() public pure { + revert("Static call failed"); + } + + function compute(uint256 value) public pure returns (uint256) { + require(value > 0, "Value must be positive"); + return value * 2; + } +} + +contract StaticCaller { + address public target; + + constructor(address _target) { + target = _target; + } + + function staticCallFail() public view { + (bool success,) = target.staticcall(abi.encodeWithSignature("viewFail()")); + require(success, "Static call reverted"); + } + + function staticCompute(uint256 value) public view returns (uint256) { + (bool success, bytes memory data) = target.staticcall(abi.encodeWithSignature("compute(uint256)", value)); + require(success, "Static compute failed"); + return abi.decode(data, (uint256)); + } +} diff --git a/crates/forge/tests/fixtures/backtraces/libraries/ExternalMathLib.sol b/crates/forge/tests/fixtures/backtraces/libraries/ExternalMathLib.sol new file mode 100644 index 0000000000000..609ba41f86db0 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/libraries/ExternalMathLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ExternalMathLib - A library with external functions that needs separate deployment +library ExternalMathLib { + error DivisionByZero(); + error Overflow(); + error Underflow(); + + /// @notice External division function + function div(uint256 a, uint256 b) external pure returns (uint256) { + if (b == 0) revert DivisionByZero(); + return a / b; + } + + /// @notice External multiplication with overflow check + function mul(uint256 a, uint256 b) external pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + if (c / a != b) revert Overflow(); + return c; + } + + /// @notice External subtraction with underflow check + function sub(uint256 a, uint256 b) external pure returns (uint256) { + if (b > a) revert Underflow(); + return a - b; + } + + /// @notice External function with require statement + function requirePositive(uint256 value) external pure returns (uint256) { + require(value > 0, "ExternalMathLib: value must be positive"); + return value * 2; + } +} diff --git a/crates/forge/tests/fixtures/backtraces/libraries/InternalMathLib.sol b/crates/forge/tests/fixtures/backtraces/libraries/InternalMathLib.sol new file mode 100644 index 0000000000000..e4337df046701 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/libraries/InternalMathLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title InternalMathLib - A library with internal functions that gets inlined +library InternalMathLib { + error DivisionByZero(); + error Overflow(); + error Underflow(); + + /// @notice Internal division function + function div(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) revert DivisionByZero(); + return a / b; + } + + /// @notice Internal multiplication with overflow check + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + if (c / a != b) revert Overflow(); + return c; + } + + /// @notice Internal subtraction with underflow check + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + if (b > a) revert Underflow(); + return a - b; + } + + /// @notice Internal function with require statement + function requirePositive(uint256 value) internal pure returns (uint256) { + require(value > 0, "InternalMathLib: value must be positive"); + return value * 2; + } +} diff --git a/crates/forge/tests/fixtures/backtraces/libraries/MultipleLibraries.sol b/crates/forge/tests/fixtures/backtraces/libraries/MultipleLibraries.sol new file mode 100644 index 0000000000000..9a9a3c3d77c66 --- /dev/null +++ b/crates/forge/tests/fixtures/backtraces/libraries/MultipleLibraries.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title FirstMathLib - First library in the file +library FirstMathLib { + error FirstLibError(); + + function divide(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + revert FirstLibError(); + } + return a / b; + } + + function multiply(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } +} + +/// @title SecondMathLib - Second library in the same file +library SecondMathLib { + error SecondLibError(); + + function subtract(uint256 a, uint256 b) internal pure returns (uint256) { + if (b > a) { + revert SecondLibError(); + } + return a - b; + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } +} + +/// @title ThirdMathLib - Third library in the same file +library ThirdMathLib { + error ThirdLibError(); + + function modulo(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + revert ThirdLibError(); + } + return a % b; + } + + function power(uint256 base, uint256 exp) internal pure returns (uint256) { + uint256 result = 1; + for (uint256 i = 0; i < exp; i++) { + result *= base; + } + return result; + } +} diff --git a/crates/forge/tests/fixtures/colored_traces.svg b/crates/forge/tests/fixtures/colored_traces.svg index e4181676bcc86..ade333cc8a503 100644 --- a/crates/forge/tests/fixtures/colored_traces.svg +++ b/crates/forge/tests/fixtures/colored_traces.svg @@ -1,9 +1,9 @@ - + , + /// The applicability of the suggested fix. + applicability: Applicability, + /// The style of the suggested fix. + style: SuggestionStyle, + }, +} + +// An emittable diagnostic suggestion. +// +// Depending on its [`SuggestionKind`] will be emitted as a simple note (examples), or a fix +// suggestion. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Suggestion { + /// An optional description displayed above the code block. + desc: Option<&'static str>, + /// The actual suggestion. + content: String, + /// The suggestion type and its specific data. + kind: SuggestionKind, +} + +impl Suggestion { + /// Creates a new [`SuggestionKind::Example`] suggestion. + pub const fn example(content: String) -> Self { + Self { desc: None, content, kind: SuggestionKind::Example } + } + + /// Creates a new [`SuggestionKind::Fix`] suggestion. + /// + /// When possible, will attempt to inline the suggestion. + pub const fn fix(content: String, applicability: Applicability) -> Self { + Self { + desc: None, + content, + kind: SuggestionKind::Fix { + span: None, + applicability, + style: SuggestionStyle::ShowCode, + }, + } + } + + /// Sets the description for the suggestion. + pub const fn with_desc(mut self, desc: &'static str) -> Self { + self.desc = Some(desc); + self + } + + /// Sets the span for a [`SuggestionKind::Fix`] suggestion. + pub const fn with_span(mut self, span: Span) -> Self { + if let SuggestionKind::Fix { span: ref mut s, .. } = self.kind { + *s = Some(span); + } + self + } + + /// Sets the style for a [`SuggestionKind::Fix`] suggestion. + pub const fn with_style(mut self, style: SuggestionStyle) -> Self { + if let SuggestionKind::Fix { style: ref mut s, .. } = self.kind { + *s = style; + } + self + } + + fn to_note(&self) -> Option> { + if let SuggestionKind::Fix { .. } = &self.kind { + return None; + }; + + let mut output = if let Some(desc) = self.desc { + vec![(DiagMsg::from(desc), Style::NoStyle), (DiagMsg::from("\n\n"), Style::NoStyle)] + } else { + vec![(DiagMsg::from(" \n"), Style::NoStyle)] + }; + + output.extend( + self.content.lines().map(|line| (DiagMsg::from(format!("{line}\n")), Style::NoStyle)), + ); + output.push((DiagMsg::from("\n"), Style::NoStyle)); + Some(output) + } +} + +/// Creates a hyperlink of the input url. +fn hyperlink(url: &'static str) -> String { + format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\") +} diff --git a/crates/lint/src/linter/project.rs b/crates/lint/src/linter/project.rs new file mode 100644 index 0000000000000..38fc1ad1ba59f --- /dev/null +++ b/crates/lint/src/linter/project.rs @@ -0,0 +1,92 @@ +use super::{Lint, LintContext, LinterConfig}; +use foundry_common::comments::inline_config::InlineConfig; +use foundry_config::lint::LintSpecificConfig; +use solar::{ + ast, + interface::{Session, Span, diagnostics::DiagMsg, source_map::SourceFile}, +}; +use std::{path::PathBuf, sync::Arc}; + +/// A single source unit visible to a project-wide lint pass, pre-loaded with its inline config so +/// emits respect `// forge-lint: disable-*` markers without rebuilding it per emit. +pub struct ProjectSource<'ast> { + pub path: PathBuf, + pub file: Arc, + pub ast: &'ast ast::SourceUnit<'ast>, + pub inline_config: InlineConfig>, +} + +/// Trait for lints that need to inspect every input source at once (e.g. cross-file checks). +/// +/// `check_project` runs once after all per-file [`super::EarlyLintPass`] / +/// [`super::LateLintPass`] passes have completed. +pub trait ProjectLintPass<'ast>: Send + Sync { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]); +} + +/// Helper passed to [`ProjectLintPass::check_project`] for emitting diagnostics against a specific +/// source. +pub struct ProjectLintEmitter<'s, 'c> { + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, +} + +impl<'s, 'c> ProjectLintEmitter<'s, 'c> { + pub const fn new( + sess: &'s Session, + with_description: bool, + with_json_emitter: bool, + lint_specific: &'c LintSpecificConfig, + active_lints: Vec<&'static str>, + ) -> Self { + Self { sess, with_description, with_json_emitter, lint_specific, active_lints } + } + + /// Returns `true` if the given lint id is enabled for this run. Project passes that perform + /// expensive analysis should guard their work behind this check. + pub fn is_lint_enabled(&self, id: &'static str) -> bool { + self.active_lints.contains(&id) + } + + /// Emits a diagnostic with the lint's default description as the message. + pub fn emit<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + ) where + 'c: 'a, + { + self.build_ctx(source).emit(lint, span); + } + + /// Emits a diagnostic with a caller-provided message. + pub fn emit_with_msg<'a, 'ast, L: Lint>( + &'a self, + source: &'a ProjectSource<'ast>, + lint: &'static L, + span: Span, + msg: impl Into, + ) where + 'c: 'a, + { + self.build_ctx(source).emit_with_msg(lint, span, msg); + } + + fn build_ctx<'a, 'ast>(&'a self, source: &'a ProjectSource<'ast>) -> LintContext<'s, 'a> + where + 'c: 'a, + { + LintContext::new( + self.sess, + self.with_description, + self.with_json_emitter, + LinterConfig { inline: &source.inline_config, lint_specific: self.lint_specific }, + self.active_lints.clone(), + Some(source.file.clone()), + ) + } +} diff --git a/crates/lint/src/sol/codesize/mod.rs b/crates/lint/src/sol/codesize/mod.rs new file mode 100644 index 0000000000000..730b341fbc35a --- /dev/null +++ b/crates/lint/src/sol/codesize/mod.rs @@ -0,0 +1,6 @@ +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; + +mod unwrapped_modifier_logic; +use unwrapped_modifier_logic::UNWRAPPED_MODIFIER_LOGIC; + +register_lints!((UnwrappedModifierLogic, late, (UNWRAPPED_MODIFIER_LOGIC))); diff --git a/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs b/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs new file mode 100644 index 0000000000000..65d195e47ada5 --- /dev/null +++ b/crates/lint/src/sol/codesize/unwrapped_modifier_logic.rs @@ -0,0 +1,181 @@ +use super::UnwrappedModifierLogic; +use crate::{ + linter::{LateLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast, + sema::hir::{self, Res}, +}; + +declare_forge_lint!( + UNWRAPPED_MODIFIER_LOGIC, + Severity::CodeSize, + "unwrapped-modifier-logic", + "wrap modifier logic to reduce code size" +); + +impl<'hir> LateLintPass<'hir> for UnwrappedModifierLogic { + fn check_function( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + func: &'hir hir::Function<'hir>, + ) { + // Only check modifiers with a body and a name + let body = match (func.kind, &func.body, func.name) { + (ast::FunctionKind::Modifier, Some(body), Some(_)) => body, + _ => return, + }; + + // Split statements into before and after the placeholder `_`. + let stmts = body.stmts[..].as_ref(); + let (before, after) = stmts + .iter() + .position(|s| matches!(s.kind, hir::StmtKind::Placeholder)) + .map_or((stmts, &[][..]), |idx| (&stmts[..idx], &stmts[idx + 1..])); + + // Generate a fix suggestion if the modifier logic should be wrapped. + if let Some(suggestion) = self.get_snippet(ctx, hir, func, before, after) { + ctx.emit_with_suggestion( + &UNWRAPPED_MODIFIER_LOGIC, + func.span.to(func.body_span), + suggestion, + ); + } + } +} + +impl UnwrappedModifierLogic { + /// Returns `true` if an expr is not a built-in ('require' or 'assert') call or a lib function. + fn is_valid_expr(&self, hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Call(func_expr, _, _) = &expr.kind { + if let hir::ExprKind::Ident(resolutions) = &func_expr.kind { + return !resolutions.iter().any(|r| matches!(r, Res::Builtin(_))); + } + + if let hir::ExprKind::Member(base, _) = &func_expr.kind + && let hir::ExprKind::Ident(resolutions) = &base.kind + { + return resolutions.iter().any(|r| { + matches!(r, Res::Item(hir::ItemId::Contract(id)) if hir.contract(*id).kind == ast::ContractKind::Library) + }); + } + } + + false + } + + /// Checks if a block of statements is complex and should be wrapped in a helper function. + /// + /// This always is 'false' the modifier contains assembly. We assume that if devs know how to + /// use assembly, they will also know how to reduce the codesize of their contracts and they + /// have a good reason to use it on their modifiers. + /// + /// This is 'true' if the block contains: + /// 1. Any statement that is not a placeholder or a valid expression. + /// 2. More than one simple call expression. + fn stmts_require_wrapping(&self, hir: &hir::Hir<'_>, stmts: &[hir::Stmt<'_>]) -> bool { + let (mut res, mut has_valid_stmt) = (false, false); + for stmt in stmts { + match &stmt.kind { + hir::StmtKind::Placeholder => {} + hir::StmtKind::Expr(expr) => { + if !self.is_valid_expr(hir, expr) || has_valid_stmt { + res = true; + } + has_valid_stmt = true; + } + // HIR doesn't support assembly yet: + // + hir::StmtKind::Err(_) => return false, + _ => res = true, + } + } + + res + } + + fn get_snippet<'a>( + &self, + ctx: &LintContext, + hir: &hir::Hir<'_>, + func: &hir::Function<'_>, + before: &'a [hir::Stmt<'a>], + after: &'a [hir::Stmt<'a>], + ) -> Option { + let wrap_before = !before.is_empty() && self.stmts_require_wrapping(hir, before); + let wrap_after = !after.is_empty() && self.stmts_require_wrapping(hir, after); + + if !(wrap_before || wrap_after) { + return None; + } + + let binding = func.name.unwrap(); + let modifier_name = binding.name.as_str(); + let mut param_list = vec![]; + let mut param_decls = vec![]; + + for var_id in func.parameters { + let var = hir.variable(*var_id); + let ty = ctx + .span_to_snippet(var.ty.span) + .unwrap_or_else(|| "/* unknown type */".to_string()); + + // solidity functions should always have named parameters + if let Some(ident) = var.name { + param_list.push(ident.to_string()); + param_decls.push(format!("{ty} {}", ident.to_string())); + } + } + + let param_list = param_list.join(", "); + let param_decls = param_decls.join(", "); + + let body_indent = " ".repeat(ctx.get_span_indentation( + before.first().or(after.first()).map(|stmt| stmt.span).unwrap_or(func.span), + )); + let body = match (wrap_before, wrap_after) { + (true, true) => format!( + "{body_indent}_{modifier_name}Before({param_list});\n{body_indent}_;\n{body_indent}_{modifier_name}After({param_list});" + ), + (true, false) => { + format!("{body_indent}_{modifier_name}({param_list});\n{body_indent}_;") + } + (false, true) => { + format!("{body_indent}_;\n{body_indent}_{modifier_name}({param_list});") + } + _ => unreachable!(), + }; + + let mod_indent = " ".repeat(ctx.get_span_indentation(func.span)); + let mut replacement = + format!("modifier {modifier_name}({param_decls}) {{\n{body}\n{mod_indent}}}"); + + let build_func = |stmts: &[hir::Stmt<'_>], suffix: &str| { + let body_stmts = stmts + .iter() + .filter_map(|s| ctx.span_to_snippet(s.span)) + .map(|code| format!("\n{body_indent}{code}")) + .collect::(); + format!( + "\n\n{mod_indent}function _{modifier_name}{suffix}({param_decls}) internal {{{body_stmts}\n{mod_indent}}}" + ) + }; + + if wrap_before { + replacement.push_str(&build_func(before, if wrap_after { "Before" } else { "" })); + } + if wrap_after { + replacement.push_str(&build_func(after, if wrap_before { "After" } else { "" })); + } + + Some( + Suggestion::fix( + replacement, + ast::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("wrap modifier logic to reduce code size"), + ) + } +} diff --git a/crates/lint/src/sol/gas/custom_errors.rs b/crates/lint/src/sol/gas/custom_errors.rs new file mode 100644 index 0000000000000..b1b7a68310885 --- /dev/null +++ b/crates/lint/src/sol/gas/custom_errors.rs @@ -0,0 +1,53 @@ +use super::CustomErrors; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{CallArgsKind, Expr, ExprKind}; + +declare_forge_lint!( + CUSTOM_ERRORS, + Severity::Gas, + "custom-errors", + "prefer using custom errors on revert and require calls" +); + +impl<'ast> EarlyLintPass<'ast> for CustomErrors { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(callee, args) = &expr.kind + && ((is_require_call(callee) && should_lint_require(args)) + || (is_revert_call(callee) && should_lint_revert(args))) + { + ctx.emit(&CUSTOM_ERRORS, expr.span); + } + } +} + +/// Checks if an expression is a call to the `require` builtin function. +fn is_require_call(callee: &Expr<'_>) -> bool { + matches!(&callee.kind, ExprKind::Ident(ident) if ident.as_str() == "require") +} + +/// Checks if an expression is a call to the `revert` builtin function. +fn is_revert_call(callee: &Expr<'_>) -> bool { + matches!(&callee.kind, ExprKind::Ident(ident) if ident.as_str() == "revert") +} + +/// Checks if a revert call should be linted: `revert()` or `revert("message")`. +fn should_lint_revert(args: &solar::ast::CallArgs<'_>) -> bool { + matches!(&args.kind, CallArgsKind::Unnamed(arg_exprs) if { + arg_exprs.is_empty() || arg_exprs.first().is_some_and(|e| is_string_literal(e)) + }) +} + +/// Checks if a require call should be linted: has string literal as second argument. +fn should_lint_require(args: &solar::ast::CallArgs<'_>) -> bool { + matches!(&args.kind, CallArgsKind::Unnamed(arg_exprs) if { + arg_exprs.get(1).is_some_and(|e| is_string_literal(e)) + }) +} + +/// Checks if an expression is a string literal. +const fn is_string_literal(expr: &Expr<'_>) -> bool { + matches!(&expr.kind, ExprKind::Lit(lit, _) if matches!(lit.kind, solar::ast::LitKind::Str(..))) +} diff --git a/crates/lint/src/sol/gas/immutable.rs b/crates/lint/src/sol/gas/immutable.rs new file mode 100644 index 0000000000000..5baba86996841 --- /dev/null +++ b/crates/lint/src/sol/gas/immutable.rs @@ -0,0 +1,406 @@ +use super::CouldBeImmutable; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{self, UnOpKind}, + interface::{kw, sym}, + sema::hir::{self, ExprKind, Res, StmtKind, TypeKind}, +}; +use std::collections::HashSet; + +declare_forge_lint!( + COULD_BE_IMMUTABLE, + Severity::Gas, + "could-be-immutable", + "state variable could be declared immutable" +); + +impl<'hir> LateLintPass<'hir> for CouldBeImmutable { + fn check_nested_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract_id: hir::ContractId, + ) { + let contract = hir.contract(contract_id); + if contract.kind == ast::ContractKind::Interface { + return; + } + if !is_most_derived_contract(hir, contract_id) { + return; + } + + let candidates: Vec<_> = contract + .linearized_bases + .iter() + .flat_map(|&contract_id| hir.contract(contract_id).variables()) + .filter(|&id| is_immutable_candidate_type(hir.variable(id))) + .collect(); + + if candidates.is_empty() { + return; + } + let candidate_set: HashSet<_> = candidates.iter().copied().collect(); + + if contract_contains_unlowered_stmt(hir, contract) { + return; + } + + let mut constructor_writes = HashSet::new(); + let mut runtime_writes = HashSet::new(); + + for &var_id in &candidates { + let var = hir.variable(var_id); + if var.initializer.is_some_and(|expr| !is_compile_time_constant(hir, expr)) { + constructor_writes.insert(var_id); + } + } + + for &contract_id in contract.linearized_bases { + for function_id in hir.contract(contract_id).all_functions() { + let function = hir.function(function_id); + if function.is_constructor() { + collect_modifier_writes( + hir, + function, + &candidate_set, + &mut constructor_writes, + &mut runtime_writes, + &mut HashSet::new(), + ); + + if let Some(body) = function.body { + collect_state_writes(hir, body, &candidate_set, &mut constructor_writes); + } + } else { + // Immutable variables can only be assigned inline or directly in constructor + // bodies, so writes hidden behind internal helpers are not valid candidates. + let mut modifier_argument_writes = HashSet::new(); + collect_modifier_writes( + hir, + function, + &candidate_set, + &mut modifier_argument_writes, + &mut runtime_writes, + &mut HashSet::new(), + ); + runtime_writes.extend(modifier_argument_writes); + + if let Some(body) = function.body { + collect_state_writes(hir, body, &candidate_set, &mut runtime_writes); + } + } + } + } + + for &var_id in &candidates { + if constructor_writes.contains(&var_id) && !runtime_writes.contains(&var_id) { + let var = hir.variable(var_id); + ctx.emit(&COULD_BE_IMMUTABLE, var.name.map_or(var.span, |name| name.span)); + } + } + } +} + +fn is_most_derived_contract(hir: &hir::Hir<'_>, contract_id: hir::ContractId) -> bool { + !hir.contracts() + .any(|contract| contract.linearized_bases.iter().skip(1).any(|&id| id == contract_id)) +} + +fn collect_modifier_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + function: &'hir hir::Function<'hir>, + candidates: &HashSet, + argument_writes: &mut HashSet, + body_writes: &mut HashSet, + visited_modifiers: &mut HashSet, +) { + for modifier in function.modifiers { + for expr in modifier.args.exprs() { + collect_expr_writes(expr, candidates, argument_writes); + } + + let Some(modifier_id) = modifier.id.as_function() else { continue }; + if !visited_modifiers.insert(modifier_id) { + continue; + } + + let modifier = hir.function(modifier_id); + let mut nested_argument_writes = HashSet::new(); + collect_modifier_writes( + hir, + modifier, + candidates, + &mut nested_argument_writes, + body_writes, + visited_modifiers, + ); + body_writes.extend(nested_argument_writes); + if let Some(body) = modifier.body { + collect_state_writes(hir, body, candidates, body_writes); + } + } +} + +fn is_immutable_candidate_type(var: &hir::Variable<'_>) -> bool { + var.is_state_variable() + && var.mutability.is_none() + && match var.ty.kind { + TypeKind::Elementary(ty) => ty.is_value_type(), + TypeKind::Custom(hir::ItemId::Contract(_)) => true, + _ => false, + } +} + +fn contract_contains_unlowered_stmt<'hir>( + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, +) -> bool { + contract.linearized_bases.iter().any(|&contract_id| { + hir.contract(contract_id).all_functions().any(|function_id| { + hir.function(function_id).body.is_some_and(|body| block_contains_unlowered_stmt(body)) + }) + }) +} + +fn block_contains_unlowered_stmt(block: hir::Block<'_>) -> bool { + block.stmts.iter().any(stmt_contains_unlowered_stmt) +} + +fn stmt_contains_unlowered_stmt(stmt: &hir::Stmt<'_>) -> bool { + match &stmt.kind { + StmtKind::Err(_) => true, + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => { + block_contains_unlowered_stmt(*block) + } + StmtKind::If(_, then_stmt, else_stmt) => { + stmt_contains_unlowered_stmt(then_stmt) + || else_stmt.is_some_and(stmt_contains_unlowered_stmt) + } + StmtKind::Try(stmt_try) => { + stmt_try.clauses.iter().any(|clause| block_contains_unlowered_stmt(clause.block)) + } + StmtKind::DeclSingle(_) + | StmtKind::DeclMulti(_, _) + | StmtKind::Emit(_) + | StmtKind::Revert(_) + | StmtKind::Return(_) + | StmtKind::Break + | StmtKind::Continue + | StmtKind::Expr(_) + | StmtKind::Placeholder => false, + } +} + +fn collect_state_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + block: hir::Block<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + for stmt in block.stmts { + collect_stmt_writes(hir, stmt, candidates, writes); + } +} + +fn collect_stmt_writes<'hir>( + hir: &'hir hir::Hir<'hir>, + stmt: &'hir hir::Stmt<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &stmt.kind { + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => { + collect_state_writes(hir, *block, candidates, writes); + } + StmtKind::If(condition, then_stmt, else_stmt) => { + collect_expr_writes(condition, candidates, writes); + collect_stmt_writes(hir, then_stmt, candidates, writes); + if let Some(else_stmt) = else_stmt { + collect_stmt_writes(hir, else_stmt, candidates, writes); + } + } + StmtKind::Try(stmt_try) => { + collect_expr_writes(&stmt_try.expr, candidates, writes); + for clause in stmt_try.clauses { + collect_state_writes(hir, clause.block, candidates, writes); + } + } + StmtKind::DeclSingle(var_id) => { + if let Some(initializer) = hir.variable(*var_id).initializer { + collect_expr_writes(initializer, candidates, writes); + } + } + StmtKind::DeclMulti(_, expr) + | StmtKind::Emit(expr) + | StmtKind::Revert(expr) + | StmtKind::Return(Some(expr)) + | StmtKind::Expr(expr) => collect_expr_writes(expr, candidates, writes), + StmtKind::Return(None) + | StmtKind::Break + | StmtKind::Continue + | StmtKind::Placeholder + | StmtKind::Err(_) => {} + } +} + +fn collect_expr_writes<'hir>( + expr: &'hir hir::Expr<'hir>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &expr.kind { + ExprKind::Assign(lhs, _, rhs) => { + collect_lvalue_writes(lhs, candidates, writes); + collect_expr_writes(lhs, candidates, writes); + collect_expr_writes(rhs, candidates, writes); + } + ExprKind::Delete(inner) => { + collect_lvalue_writes(inner, candidates, writes); + collect_expr_writes(inner, candidates, writes); + } + ExprKind::Unary(op, inner) => { + if op.kind.has_side_effects() { + collect_lvalue_writes(inner, candidates, writes); + } + collect_expr_writes(inner, candidates, writes); + } + ExprKind::Array(exprs) => { + for expr in *exprs { + collect_expr_writes(expr, candidates, writes); + } + } + ExprKind::Binary(lhs, _, rhs) => { + collect_expr_writes(lhs, candidates, writes); + collect_expr_writes(rhs, candidates, writes); + } + ExprKind::Call(callee, args, named_args) => { + collect_expr_writes(callee, candidates, writes); + for expr in args.exprs() { + collect_expr_writes(expr, candidates, writes); + } + if let Some(named_args) = named_args { + for arg in *named_args { + collect_expr_writes(&arg.value, candidates, writes); + } + } + } + ExprKind::Index(base, index) => { + collect_expr_writes(base, candidates, writes); + if let Some(index) = index { + collect_expr_writes(index, candidates, writes); + } + } + ExprKind::Slice(base, start, end) => { + collect_expr_writes(base, candidates, writes); + if let Some(start) = start { + collect_expr_writes(start, candidates, writes); + } + if let Some(end) = end { + collect_expr_writes(end, candidates, writes); + } + } + ExprKind::Member(base, _) | ExprKind::Payable(base) => { + collect_expr_writes(base, candidates, writes); + } + ExprKind::Ternary(condition, then_expr, else_expr) => { + collect_expr_writes(condition, candidates, writes); + collect_expr_writes(then_expr, candidates, writes); + collect_expr_writes(else_expr, candidates, writes); + } + ExprKind::Tuple(exprs) => { + for expr in exprs.iter().flatten() { + collect_expr_writes(expr, candidates, writes); + } + } + ExprKind::Ident(_) + | ExprKind::Lit(_) + | ExprKind::New(_) + | ExprKind::TypeCall(_) + | ExprKind::Type(_) + | ExprKind::Err(_) => {} + } +} + +fn collect_lvalue_writes( + expr: &hir::Expr<'_>, + candidates: &HashSet, + writes: &mut HashSet, +) { + match &expr.peel_parens().kind { + ExprKind::Ident([Res::Item(hir::ItemId::Variable(id)), ..]) if candidates.contains(id) => { + writes.insert(*id); + } + ExprKind::Tuple(exprs) => { + for expr in exprs.iter().flatten() { + collect_lvalue_writes(expr, candidates, writes); + } + } + ExprKind::Index(base, _) + | ExprKind::Slice(base, _, _) + | ExprKind::Member(base, _) + | ExprKind::Payable(base) => collect_lvalue_writes(base, candidates, writes), + _ => {} + } +} + +fn is_compile_time_constant(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + ExprKind::Lit(_) | ExprKind::Type(_) | ExprKind::TypeCall(_) => true, + ExprKind::Ident(resolutions) => resolutions.iter().all(|res| match res { + Res::Item(hir::ItemId::Variable(var_id)) => hir.variable(*var_id).is_constant(), + _ => false, + }), + ExprKind::Unary(op, inner) => { + !matches!( + op.kind, + UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec + ) && is_compile_time_constant(hir, inner) + } + ExprKind::Binary(lhs, _, rhs) => { + is_compile_time_constant(hir, lhs) && is_compile_time_constant(hir, rhs) + } + ExprKind::Call(callee, args, named_args) => { + is_allowed_constant_call(callee) + && args.exprs().all(|expr| is_compile_time_constant(hir, expr)) + && named_args.is_none_or(|args| { + args.iter().all(|arg| is_compile_time_constant(hir, &arg.value)) + }) + } + ExprKind::Ternary(condition, then_expr, else_expr) => { + is_compile_time_constant(hir, condition) + && is_compile_time_constant(hir, then_expr) + && is_compile_time_constant(hir, else_expr) + } + ExprKind::Tuple(exprs) => { + exprs.iter().flatten().all(|expr| is_compile_time_constant(hir, expr)) + } + ExprKind::Array(_) + | ExprKind::Assign(_, _, _) + | ExprKind::Delete(_) + | ExprKind::Index(_, _) + | ExprKind::Slice(_, _, _) + | ExprKind::Member(_, _) + | ExprKind::New(_) + | ExprKind::Payable(_) + | ExprKind::Err(_) => false, + } +} + +fn is_allowed_constant_call(callee: &hir::Expr<'_>) -> bool { + match &callee.kind { + ExprKind::Type(_) => true, + ExprKind::Ident([Res::Builtin(builtin), ..]) => { + let name = builtin.name(); + name == kw::Keccak256 + || name == kw::Addmod + || name == kw::Mulmod + || name == sym::sha256 + || name == sym::ripemd160 + || name == sym::ecrecover + } + _ => false, + } +} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index 7316f4c4239b7..cb942510bbb49 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -1,27 +1,103 @@ use super::AsmKeccak256; use crate::{ - declare_forge_lint, - linter::EarlyLintPass, + linter::{LateLintPass, LintContext}, sol::{Severity, SolLint}, }; -use solar_ast::{Expr, ExprKind}; -use solar_interface::kw; +use solar::{ + ast::{self as ast, Span}, + interface::kw, + sema::hir::{self}, +}; declare_forge_lint!( ASM_KECCAK256, Severity::Gas, "asm-keccak256", - "hash using inline assembly to save gas" + "use of inefficient hashing mechanism; consider using inline assembly" ); -impl<'ast> EarlyLintPass<'ast> for AsmKeccak256 { - fn check_expr(&mut self, ctx: &crate::linter::LintContext<'_>, expr: &'ast Expr<'ast>) { - if let ExprKind::Call(expr, _) = &expr.kind { - if let ExprKind::Ident(ident) = &expr.kind { - if ident.name == kw::Keccak256 { - ctx.emit(&ASM_KECCAK256, expr.span); +impl<'hir> LateLintPass<'hir> for AsmKeccak256 { + fn check_stmt( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + stmt: &'hir hir::Stmt<'hir>, + ) { + let check_expr_and_emit_lint = + |expr: &'hir hir::Expr<'hir>, assign: Option, is_return: bool| { + if let Some(hash_arg) = extract_keccak256_arg(expr) { + self.emit_lint( + ctx, + hir, + stmt.span, + expr, + hash_arg, + AsmContext { _assign: assign, _is_return: is_return }, + ); + } + }; + + match stmt.kind { + hir::StmtKind::DeclSingle(var_id) => { + let var = hir.variable(var_id); + if let Some(init) = var.initializer { + // Constants should be optimized by the compiler, so no gas savings apply. + if !matches!(var.mutability, Some(hir::VarMut::Constant)) { + check_expr_and_emit_lint(init, var.name, false); + } } } + // Expressions that don't (directly) assign to a variable + hir::StmtKind::Expr(expr) + | hir::StmtKind::Emit(expr) + | hir::StmtKind::Revert(expr) + | hir::StmtKind::DeclMulti(_, expr) + | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false), + hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true), + _ => (), } } } + +impl AsmKeccak256 { + /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls. + fn emit_lint( + &self, + ctx: &LintContext, + _hir: &hir::Hir<'_>, + _stmt_span: Span, + call: &hir::Expr<'_>, + _hash: &hir::Expr<'_>, + _asm_ctx: AsmContext, + ) { + ctx.emit(&ASM_KECCAK256, call.span); + } +} + +/// If the expression is a call to `keccak256` with one argument, returns that argument. +fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { + let hir::ExprKind::Call( + callee, + hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, + .., + ) = &expr.kind + else { + return None; + }; + + let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind { + matches!(builtin.name(), kw::Keccak256) + } else { + return None; + }; + + if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } +} + +// -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- + +#[derive(Debug, Clone, Copy)] +struct AsmContext { + _assign: Option, + _is_return: bool, +} diff --git a/crates/lint/src/sol/gas/mod.rs b/crates/lint/src/sol/gas/mod.rs index 1949cc9df63f3..858542b904d1d 100644 --- a/crates/lint/src/sol/gas/mod.rs +++ b/crates/lint/src/sol/gas/mod.rs @@ -1,9 +1,20 @@ -use crate::{ - register_lints, - sol::{EarlyLintPass, SolLint}, -}; +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; +mod custom_errors; +mod immutable; mod keccak; +mod unused_state_variables; +mod var_read_using_this; +use custom_errors::CUSTOM_ERRORS; +use immutable::COULD_BE_IMMUTABLE; use keccak::ASM_KECCAK256; +use unused_state_variables::UNUSED_STATE_VARIABLES; +use var_read_using_this::VAR_READ_USING_THIS; -register_lints!((AsmKeccak256, (ASM_KECCAK256))); +register_lints!( + (AsmKeccak256, late, (ASM_KECCAK256)), + (CustomErrors, early, (CUSTOM_ERRORS)), + (CouldBeImmutable, late, (COULD_BE_IMMUTABLE)), + (UnusedStateVariables, late, (UNUSED_STATE_VARIABLES)), + (VarReadUsingThis, late, (VAR_READ_USING_THIS)), +); diff --git a/crates/lint/src/sol/gas/unused_state_variables.rs b/crates/lint/src/sol/gas/unused_state_variables.rs new file mode 100644 index 0000000000000..78d32c196b20c --- /dev/null +++ b/crates/lint/src/sol/gas/unused_state_variables.rs @@ -0,0 +1,90 @@ +use super::UnusedStateVariables; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::ContractKind, + interface::data_structures::Never, + sema::hir::{self, Visit as _}, +}; +use std::{collections::HashSet, ops::ControlFlow}; + +declare_forge_lint!( + UNUSED_STATE_VARIABLES, + Severity::Gas, + "unused-state-variables", + "state variable is never used" +); + +impl<'hir> LateLintPass<'hir> for UnusedStateVariables { + fn check_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, + ) { + // Skip interfaces, they cannot have mutable state variables. + if contract.kind == ContractKind::Interface { + return; + } + + // Collect state variable IDs, skipping constants and immutables + // (those are handled by the compiler and don't occupy storage slots). + let state_vars: Vec = contract + .variables() + .filter(|&var_id| { + let var = hir.variable(var_id); + !var.is_constant() && !var.is_immutable() + }) + .collect(); + + if state_vars.is_empty() { + return; + } + + // Walk the full contract — functions (including modifier call args, parameters, returns, + // and bodies) and state variable initializers — to collect every variable referenced + // anywhere in this contract. + let mut collector = UsedVarCollector { hir, used: HashSet::new() }; + for func_id in contract.all_functions() { + let _ = collector.visit_nested_function(func_id); + } + // State variables can reference other state variables in their initializers. + for var_id in contract.variables() { + let _ = collector.visit_nested_var(var_id); + } + + // Report any state variable that was never referenced. + for var_id in state_vars { + if !collector.used.contains(&var_id) { + let var = hir.variable(var_id); + ctx.emit(&UNUSED_STATE_VARIABLES, var.span); + } + } + } +} + +struct UsedVarCollector<'hir> { + hir: &'hir hir::Hir<'hir>, + used: HashSet, +} + +impl<'hir> hir::Visit<'hir> for UsedVarCollector<'hir> { + type BreakValue = Never; + + fn hir(&self) -> &'hir hir::Hir<'hir> { + self.hir + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow { + if let hir::ExprKind::Ident(resolutions) = &expr.kind { + for res in *resolutions { + if let hir::Res::Item(hir::ItemId::Variable(var_id)) = res { + self.used.insert(*var_id); + } + } + } + self.walk_expr(expr) + } +} diff --git a/crates/lint/src/sol/gas/var_read_using_this.rs b/crates/lint/src/sol/gas/var_read_using_this.rs new file mode 100644 index 0000000000000..5270df4e075d0 --- /dev/null +++ b/crates/lint/src/sol/gas/var_read_using_this.rs @@ -0,0 +1,336 @@ +use super::VarReadUsingThis; +use crate::{ + linter::{LateLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{ContractKind, StateMutability}, + interface::{Symbol, diagnostics::Applicability, sym}, + sema::hir::{self, ExprKind, Res, StmtKind}, +}; +use std::collections::HashMap; + +declare_forge_lint!( + VAR_READ_USING_THIS, + Severity::Gas, + "var-read-using-this", + "reading a state variable via `this` causes an unnecessary STATICCALL; access it directly" +); + +impl<'hir> LateLintPass<'hir> for VarReadUsingThis { + fn check_nested_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract_id: hir::ContractId, + ) { + let contract = hir.contract(contract_id); + + // `this` only exists inside contracts (concrete or abstract). + // Libraries have no `this`; interfaces have no function bodies. + if !matches!(contract.kind, ContractKind::Contract | ContractKind::AbstractContract) { + return; + } + + // Collect every externally-callable function reachable on `this.X(...)`, + // grouped by name. Includes overloads (same name, different parameter types) + // as well as inherited overrides; `match_this_call` resolves them by arity + // and conservatively skips mixed-mutability overload sets. + let mut callable: HashMap>> = HashMap::new(); + for &cid in contract.linearized_bases { + for fid in hir.contract(cid).functions() { + let func = hir.function(fid); + let Some(name) = func.name else { continue }; + if !func.is_part_of_external_interface() { + continue; + } + callable.entry(name.name).or_default().push(func); + } + } + + if callable.is_empty() { + return; + } + + // Walk state variable initializers (these run in the synthesized constructor). + for var_id in contract.variables() { + let var = hir.variable(var_id); + if let Some(init) = var.initializer { + walk_expr(ctx, init, &callable); + } + } + + // Walk only function/modifier bodies *defined in this contract*; inherited + // bodies are walked when their defining contract is visited. + for fid in contract.all_functions() { + let func = hir.function(fid); + + // Modifier invocations on the function (also covers base-class constructor + // calls, which solar stores in the same field for constructors). + for modifier in func.modifiers { + for arg in modifier.args.exprs() { + walk_expr(ctx, arg, &callable); + } + } + + if let Some(body) = func.body { + walk_block(ctx, hir, body, &callable); + } + } + } +} + +fn walk_block<'hir>( + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + block: hir::Block<'hir>, + callable: &HashMap>>, +) { + for stmt in block.stmts { + walk_stmt(ctx, hir, stmt, callable); + } +} + +fn walk_stmt<'hir>( + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + stmt: &'hir hir::Stmt<'hir>, + callable: &HashMap>>, +) { + match &stmt.kind { + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => { + walk_block(ctx, hir, *block, callable); + } + StmtKind::If(cond, then_stmt, else_stmt) => { + walk_expr(ctx, cond, callable); + walk_stmt(ctx, hir, then_stmt, callable); + if let Some(else_stmt) = else_stmt { + walk_stmt(ctx, hir, else_stmt, callable); + } + } + StmtKind::Try(stmt_try) => { + // `try` requires an external call by Solidity's rules, so the outer + // `this.X(...)` cannot be replaced with a direct read. Skip flagging the + // top-level call but still recurse into its arguments so nested + // `this.X(...)` reads are caught. + let try_expr = &stmt_try.expr; + if let ExprKind::Call(callee, args, named_args) = &try_expr.kind { + walk_expr(ctx, callee, callable); + for e in args.exprs() { + walk_expr(ctx, e, callable); + } + if let Some(nargs) = named_args { + for arg in *nargs { + walk_expr(ctx, &arg.value, callable); + } + } + } else { + walk_expr(ctx, try_expr, callable); + } + for clause in stmt_try.clauses { + walk_block(ctx, hir, clause.block, callable); + } + } + StmtKind::DeclSingle(var_id) => { + if let Some(init) = hir.variable(*var_id).initializer { + walk_expr(ctx, init, callable); + } + } + StmtKind::DeclMulti(_, expr) + | StmtKind::Emit(expr) + | StmtKind::Revert(expr) + | StmtKind::Return(Some(expr)) + | StmtKind::Expr(expr) => walk_expr(ctx, expr, callable), + StmtKind::Return(None) + | StmtKind::Break + | StmtKind::Continue + | StmtKind::Placeholder + | StmtKind::Err(_) => {} + } +} + +fn walk_expr<'hir>( + ctx: &LintContext, + expr: &'hir hir::Expr<'hir>, + callable: &HashMap>>, +) { + // Check the outer expression first so we report the entire `this.X(args)` call, + // then keep walking children to also find nested matches like `this.foo(this.bar)`. + if let Some(matched) = match_this_call(expr, callable) { + emit(ctx, expr, &matched); + } + + match &expr.kind { + ExprKind::Array(exprs) => { + for e in *exprs { + walk_expr(ctx, e, callable); + } + } + ExprKind::Assign(lhs, _, rhs) | ExprKind::Binary(lhs, _, rhs) => { + walk_expr(ctx, lhs, callable); + walk_expr(ctx, rhs, callable); + } + ExprKind::Call(callee, args, named_args) => { + walk_expr(ctx, callee, callable); + for e in args.exprs() { + walk_expr(ctx, e, callable); + } + if let Some(nargs) = named_args { + for arg in *nargs { + walk_expr(ctx, &arg.value, callable); + } + } + } + ExprKind::Delete(inner) | ExprKind::Payable(inner) | ExprKind::Unary(_, inner) => { + walk_expr(ctx, inner, callable); + } + ExprKind::Index(base, index) => { + walk_expr(ctx, base, callable); + if let Some(i) = index { + walk_expr(ctx, i, callable); + } + } + ExprKind::Slice(base, start, end) => { + walk_expr(ctx, base, callable); + if let Some(s) = start { + walk_expr(ctx, s, callable); + } + if let Some(e) = end { + walk_expr(ctx, e, callable); + } + } + ExprKind::Member(base, _) => walk_expr(ctx, base, callable), + ExprKind::Ternary(cond, then_e, else_e) => { + walk_expr(ctx, cond, callable); + walk_expr(ctx, then_e, callable); + walk_expr(ctx, else_e, callable); + } + ExprKind::Tuple(exprs) => { + for e in exprs.iter().flatten() { + walk_expr(ctx, e, callable); + } + } + ExprKind::Ident(_) + | ExprKind::Lit(_) + | ExprKind::New(_) + | ExprKind::TypeCall(_) + | ExprKind::Type(_) + | ExprKind::Err(_) => {} + } +} + +#[derive(Clone, Copy)] +struct MatchedCall<'hir> { + func: &'hir hir::Function<'hir>, + args: hir::CallArgs<'hir>, + /// Whether the call expression carries options like `{gas: ...}`. + has_call_options: bool, + /// The name on the right-hand side of the `this.` member access. + member_name: Symbol, +} + +/// Returns `Some(...)` if `expr` is a `this.(args)` call where `` resolves +/// (via overload resolution by arity) to a `view`/`pure` external-interface function on +/// the current contract. +fn match_this_call<'hir>( + expr: &'hir hir::Expr<'hir>, + callable: &HashMap>>, +) -> Option> { + let ExprKind::Call(callee, args, named_args) = &expr.kind else { return None }; + + // Allow `(this.foo)(args)` by peeling parens around the callee. + let ExprKind::Member(base, member) = &callee.peel_parens().kind else { return None }; + + // Allow `(this).foo(args)` by peeling parens around the base. + let ExprKind::Ident(resolutions) = &base.peel_parens().kind else { return None }; + let is_this = resolutions.iter().any(|r| matches!(r, Res::Builtin(b) if b.name() == sym::this)); + if !is_this { + return None; + } + + let candidates = callable.get(&member.name)?; + let arity = args.len(); + + // Solar's HIR `Member(base, ident)` is name-based and does not carry the resolved + // function id, so we approximate overload resolution by arity. To avoid false + // positives when same-arity overloads mix mutability (e.g. `f(uint256) view` vs + // `f(address)` mutating), require ALL same-arity overloads to be `view`/`pure`. + let mut found: Option<&'hir hir::Function<'hir>> = None; + for f in candidates.iter().copied() { + if f.parameters.len() != arity { + continue; + } + if !matches!(f.state_mutability, StateMutability::View | StateMutability::Pure) { + return None; + } + if found.is_none() { + found = Some(f); + } + } + let func = found?; + + Some(MatchedCall { + func, + args: *args, + has_call_options: named_args.is_some(), + member_name: member.name, + }) +} + +fn emit(ctx: &LintContext, expr: &hir::Expr<'_>, matched: &MatchedCall<'_>) { + // When the call carries options like `{gas: ...}`, the developer is intentionally + // reaching for the external-call machinery; flag the gas waste but do not auto-fix. + if matched.has_call_options { + ctx.emit(&VAR_READ_USING_THIS, expr.span); + return; + } + + if let Some(suggestion) = build_suggestion(ctx, matched) { + ctx.emit_with_suggestion(&VAR_READ_USING_THIS, expr.span, suggestion); + } else { + ctx.emit(&VAR_READ_USING_THIS, expr.span); + } +} + +fn build_suggestion(ctx: &LintContext, matched: &MatchedCall<'_>) -> Option { + let name = matched.member_name.as_str(); + + if matched.func.is_getter() { + // Skip suggestions for struct-typed getters (multi-return) — the synthesized + // getter destructures struct fields, so a direct rewrite is not equivalent. + if matched.func.returns.len() != 1 { + return Some( + Suggestion::example(format!("read the state variable directly: `{name}`")) + .with_desc("read the state variable directly instead of via `this.`"), + ); + } + + if matched.args.is_empty() { + // Simple state variable getter: `this.foo()` -> `foo` + return Some( + Suggestion::fix(name.to_string(), Applicability::MachineApplicable) + .with_desc("consider reading the state variable directly"), + ); + } + + // Mapping/array getter: rebuild as `name[arg1][arg2]...` from arg snippets. + let mut indexed = String::from(name); + for arg in matched.args.exprs() { + let snippet = ctx.span_to_snippet(arg.span)?; + indexed.push('['); + indexed.push_str(snippet.trim()); + indexed.push(']'); + } + return Some( + Suggestion::fix(indexed, Applicability::MaybeIncorrect) + .with_desc("consider accessing storage directly"), + ); + } + + // Ordinary `view` / `pure` function: cannot auto-fix (visibility may be `external`, + // requiring a refactor to extract an internal helper). Show a generic example. + Some( + Suggestion::example(format!("call directly without `this.`: `{name}(...)`")) + .with_desc("avoid the STATICCALL by invoking the function directly"), + ) +} diff --git a/crates/lint/src/sol/high/incorrect_shift.rs b/crates/lint/src/sol/high/incorrect_shift.rs index 8d2326c5cc9f9..8e7fcbf868109 100644 --- a/crates/lint/src/sol/high/incorrect_shift.rs +++ b/crates/lint/src/sol/high/incorrect_shift.rs @@ -1,10 +1,9 @@ use super::IncorrectShift; use crate::{ - declare_forge_lint, linter::{EarlyLintPass, LintContext}, sol::{Severity, SolLint}, }; -use solar_ast::{BinOp, BinOpKind, Expr, ExprKind}; +use solar::ast::{BinOp, BinOpKind, Expr, ExprKind}; declare_forge_lint!( INCORRECT_SHIFT, @@ -14,16 +13,15 @@ declare_forge_lint!( ); impl<'ast> EarlyLintPass<'ast> for IncorrectShift { - fn check_expr(&mut self, ctx: &LintContext<'_>, expr: &'ast Expr<'ast>) { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { if let ExprKind::Binary( left_expr, BinOp { kind: BinOpKind::Shl | BinOpKind::Shr, .. }, right_expr, ) = &expr.kind + && contains_incorrect_shift(left_expr, right_expr) { - if contains_incorrect_shift(left_expr, right_expr) { - ctx.emit(&INCORRECT_SHIFT, expr.span); - } + ctx.emit(&INCORRECT_SHIFT, expr.span); } } } @@ -31,7 +29,7 @@ impl<'ast> EarlyLintPass<'ast> for IncorrectShift { // TODO: come up with a better heuristic. Treat initial impl as a PoC. // Checks if the left operand is a literal and the right operand is not, indicating a potential // reversed shift operation. -fn contains_incorrect_shift<'ast>( +const fn contains_incorrect_shift<'ast>( left_expr: &'ast Expr<'ast>, right_expr: &'ast Expr<'ast>, ) -> bool { diff --git a/crates/lint/src/sol/high/mod.rs b/crates/lint/src/sol/high/mod.rs index 720f172420e66..09658ac232461 100644 --- a/crates/lint/src/sol/high/mod.rs +++ b/crates/lint/src/sol/high/mod.rs @@ -1,9 +1,16 @@ -use crate::{ - register_lints, - sol::{EarlyLintPass, SolLint}, -}; +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod incorrect_shift; +mod rtlo; +mod unchecked_calls; + use incorrect_shift::INCORRECT_SHIFT; +use rtlo::RTLO; +use unchecked_calls::{ERC20_UNCHECKED_TRANSFER, UNCHECKED_CALL}; -register_lints!((IncorrectShift, (INCORRECT_SHIFT))); +register_lints!( + (IncorrectShift, early, (INCORRECT_SHIFT)), + (UncheckedCall, early, (UNCHECKED_CALL)), + (UncheckedTransferERC20, late, (ERC20_UNCHECKED_TRANSFER)), + (Rtlo, early, (RTLO)) +); diff --git a/crates/lint/src/sol/high/rtlo.rs b/crates/lint/src/sol/high/rtlo.rs new file mode 100644 index 0000000000000..c121ac758da32 --- /dev/null +++ b/crates/lint/src/sol/high/rtlo.rs @@ -0,0 +1,58 @@ +use super::Rtlo; +use crate::{ + linter::{EarlyLintPass, Lint, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + RTLO, + Severity::High, + "rtlo", + "unicode bidirectional override character can hide malicious code" +); + +impl<'ast> EarlyLintPass<'ast> for Rtlo { + fn check_full_source_unit( + &mut self, + ctx: &LintContext<'ast, '_>, + _unit: &'ast ast::SourceUnit<'ast>, + ) { + if !ctx.is_lint_enabled(RTLO.id()) { + return; + } + + // Scan the raw source so bidi chars in comments are also caught. + let Some(file) = ctx.source_file() else { return }; + + for (offset, ch) in file.src.char_indices() { + let Some(name) = bidi_char_name(ch) else { continue }; + + let lo = file.start_pos + BytePos::from_usize(offset); + let hi = lo + BytePos::from_usize(ch.len_utf8()); + let span = Span::new(lo, hi); + + ctx.emit_with_msg(&RTLO, span, format!("U+{:04X} ({name}) detected", ch as u32)); + } + } +} + +const fn bidi_char_name(ch: char) -> Option<&'static str> { + Some(match ch { + '\u{200E}' => "Left-to-Right Mark", + '\u{200F}' => "Right-to-Left Mark", + '\u{202A}' => "Left-to-Right Embedding", + '\u{202B}' => "Right-to-Left Embedding", + '\u{202C}' => "Pop Directional Formatting", + '\u{202D}' => "Left-to-Right Override", + '\u{202E}' => "Right-to-Left Override", + '\u{2066}' => "Left-to-Right Isolate", + '\u{2067}' => "Right-to-Left Isolate", + '\u{2068}' => "First Strong Isolate", + '\u{2069}' => "Pop Directional Isolate", + _ => return None, + }) +} diff --git a/crates/lint/src/sol/high/unchecked_calls.rs b/crates/lint/src/sol/high/unchecked_calls.rs new file mode 100644 index 0000000000000..900a3793496a9 --- /dev/null +++ b/crates/lint/src/sol/high/unchecked_calls.rs @@ -0,0 +1,199 @@ +use super::{UncheckedCall, UncheckedTransferERC20}; +use crate::{ + linter::{EarlyLintPass, LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Expr, ExprKind, ItemFunction, Stmt, StmtKind, visit::Visit}, + interface::kw, + sema::hir::{self}, +}; +use std::ops::ControlFlow; + +declare_forge_lint!( + UNCHECKED_CALL, + Severity::High, + "unchecked-call", + "Low-level calls should check the success return value" +); + +declare_forge_lint!( + ERC20_UNCHECKED_TRANSFER, + Severity::High, + "erc20-unchecked-transfer", + "ERC20 'transfer' and 'transferFrom' calls should check the return value" +); + +// -- ERC20 UNCKECKED TRANSFERS ------------------------------------------------------------------- + +/// Checks that calls to functions with the same signature as the ERC20 transfer methods, and which +/// return a boolean are not ignored. +/// +/// WARN: can issue false positives, as it doesn't check that the contract being called sticks to +/// the full ERC20 specification. +impl<'hir> LateLintPass<'hir> for UncheckedTransferERC20 { + fn check_stmt( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + stmt: &'hir hir::Stmt<'hir>, + ) { + // Only expression statements can contain unchecked transfers. + if let hir::StmtKind::Expr(expr) = &stmt.kind + && is_erc20_transfer_call(hir, expr) + { + ctx.emit(&ERC20_UNCHECKED_TRANSFER, expr.span); + } + } +} + +/// Checks if an expression is an ERC20 `transfer` or `transferFrom` call. +/// * `function transfer(address to, uint256 amount) external returns bool;` +/// * `function transferFrom(address from, address to, uint256 amount) external returns bool;` +/// +/// Validates the method name, the params (count + types), and the returns (count + types). +fn is_erc20_transfer_call(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool { + let is_type = |var_id: hir::VariableId, type_str: &str| { + matches!( + &hir.variable(var_id).ty.kind, + hir::TypeKind::Elementary(ty) if ty.to_abi_str() == type_str + ) + }; + + // Ensure the expression is a call to a contract member function. + let hir::ExprKind::Call( + hir::Expr { kind: hir::ExprKind::Member(contract_expr, func_ident), .. }, + hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. }, + .., + ) = &expr.kind + else { + return false; + }; + + // Determine the expected ERC20 signature from the call + let (expected_params, expected_returns): (&[&str], &[&str]) = match func_ident.as_str() { + "transferFrom" if args.len() == 3 => (&["address", "address", "uint256"], &["bool"]), + "transfer" if args.len() == 2 => (&["address", "uint256"], &["bool"]), + _ => return false, + }; + + let Some(cid) = (match &contract_expr.kind { + // Call to pre-instantiated contract variable + hir::ExprKind::Ident([hir::Res::Item(hir::ItemId::Variable(id)), ..]) => { + if let hir::TypeKind::Custom(hir::ItemId::Contract(cid)) = hir.variable(*id).ty.kind { + Some(cid) + } else { + None + } + } + // Call to address wrapped by the contract interface + hir::ExprKind::Call( + hir::Expr { + kind: hir::ExprKind::Ident([hir::Res::Item(hir::ItemId::Contract(cid))]), + .. + }, + .., + ) => Some(*cid), + _ => None, + }) else { + return false; + }; + + // Try to find a function in the contract that matches the expected signature. + hir.contract_item_ids(cid).any(|item| { + let Some(fid) = item.as_function() else { return false }; + let func = hir.function(fid); + func.name.is_some_and(|name| name.as_str() == func_ident.as_str()) + && func.kind.is_function() + && func.mutates_state() + && func.parameters.len() == expected_params.len() + && func.returns.len() == expected_returns.len() + && func.parameters.iter().zip(expected_params).all(|(id, &ty)| is_type(*id, ty)) + && func.returns.iter().zip(expected_returns).all(|(id, &ty)| is_type(*id, ty)) + }) +} + +// -- UNCKECKED LOW-LEVEL CALLS ------------------------------------------------------------------- + +impl<'ast> EarlyLintPass<'ast> for UncheckedCall { + fn check_item_function(&mut self, ctx: &LintContext, func: &'ast ItemFunction<'ast>) { + if let Some(body) = &func.body { + let mut checker = UncheckedCallChecker { ctx }; + let _ = checker.visit_block(body); + } + } +} + +/// Visitor that detects unchecked low-level calls within function bodies. +/// +/// Similar to unchecked transfers, unchecked calls appear as standalone expression +/// statements. When the success value is checked (in require, if, etc.), the call +/// is part of a larger expression and won't be flagged. +struct UncheckedCallChecker<'a, 's> { + ctx: &'a LintContext<'s, 'a>, +} + +impl<'ast> Visit<'ast> for UncheckedCallChecker<'_, '_> { + type BreakValue = (); + + fn visit_stmt(&mut self, stmt: &'ast Stmt<'ast>) -> ControlFlow { + match &stmt.kind { + // Check standalone expression statements: `target.call(data);` + StmtKind::Expr(expr) => { + if is_low_level_call(expr) { + self.ctx.emit(&UNCHECKED_CALL, expr.span); + } else if let ExprKind::Assign(lhs, _, rhs) = &expr.kind { + // Check assignments to existing vars: `(, existingVar) = target.call(data);` + if is_low_level_call(rhs) && is_unchecked_tuple_assignment(lhs) { + self.ctx.emit(&UNCHECKED_CALL, expr.span); + } + } + } + // Check multi-variable declarations: `(bool success, ) = target.call(data);` + StmtKind::DeclMulti(vars, expr) + if is_low_level_call(expr) && vars.first().is_none_or(|v| v.is_none()) => + { + self.ctx.emit(&UNCHECKED_CALL, stmt.span); + } + _ => {} + } + self.walk_stmt(stmt) + } +} + +/// Checks if an expression is a low-level call that should be checked. +/// +/// Detects patterns like: +/// - `target.call(...)` +/// - `target.delegatecall(...)` +/// - `target.staticcall(...)` +/// - `target.call{value: x}(...)` +const fn is_low_level_call(expr: &Expr<'_>) -> bool { + if let ExprKind::Call(call_expr, _args) = &expr.kind { + // Check the callee expression + let callee = match &call_expr.kind { + // Handle call options like {value: x} + ExprKind::CallOptions(inner_expr, _) => inner_expr, + // Direct call without options + _ => call_expr, + }; + + if let ExprKind::Member(_, member) = &callee.kind { + // Check for low-level call methods + return matches!(member.name, kw::Call | kw::Delegatecall | kw::Staticcall); + } + } + false +} + +/// Checks if a tuple assignment doesn't properly check the success value. +/// +/// Returns true if the first variable (success) is None: `(, bytes memory data) = +/// target.call(...)` +fn is_unchecked_tuple_assignment(expr: &Expr<'_>) -> bool { + if let ExprKind::Tuple(elements) = &expr.kind { + elements.first().is_none_or(|e| e.is_none()) + } else { + false + } +} diff --git a/crates/lint/src/sol/info/boolean_cst.rs b/crates/lint/src/sol/info/boolean_cst.rs new file mode 100644 index 0000000000000..50a7075338d3d --- /dev/null +++ b/crates/lint/src/sol/info/boolean_cst.rs @@ -0,0 +1,116 @@ +use super::BooleanCst; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind, Stmt, StmtKind, VariableDefinition}, + interface::SpannedOption, +}; + +declare_forge_lint!(BOOLEAN_CST, Severity::Med, "boolean-cst", "misuse of a boolean constant"); + +impl<'ast> EarlyLintPass<'ast> for BooleanCst { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + } + StmtKind::While(cond, _) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: true }); + } + StmtKind::For { cond: Some(cond), .. } => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + } + StmtKind::DeclMulti(_, expr) => check_allowed_bare_expr(ctx, expr), + StmtKind::Expr(expr) | StmtKind::Return(Some(expr)) => { + check_allowed_bare_expr(ctx, expr); + } + _ => {} + } + } + + fn check_variable_definition( + &mut self, + ctx: &LintContext, + var: &'ast VariableDefinition<'ast>, + ) { + if let Some(initializer) = &var.initializer { + check_allowed_bare_expr(ctx, initializer); + } + } +} + +#[derive(Clone, Copy)] +enum ExprContext { + Condition { allow_bare_true: bool }, + General, + AllowedBare, +} + +fn check_allowed_bare_expr(ctx: &LintContext, expr: &Expr<'_>) { + let context = + if bool_literal(expr).is_some() { ExprContext::AllowedBare } else { ExprContext::General }; + check_expr(ctx, expr, context); +} + +fn check_expr(ctx: &LintContext, expr: &Expr<'_>, context: ExprContext) { + if let Some(value) = bool_literal(expr) { + match context { + ExprContext::AllowedBare => {} + ExprContext::Condition { allow_bare_true: true } if value => {} + ExprContext::Condition { .. } | ExprContext::General => { + ctx.emit(&BOOLEAN_CST, expr.span); + } + } + return; + } + + match &expr.kind { + ExprKind::Assign(_, _, rhs) => check_allowed_bare_expr(ctx, rhs), + ExprKind::Binary(left, op, right) => check_binary_expr(ctx, left, *op, right), + ExprKind::Call(_, args) => { + for arg in args.exprs() { + check_allowed_bare_expr(ctx, arg); + } + } + ExprKind::Delete(expr) | ExprKind::Unary(_, expr) => { + check_expr(ctx, expr, ExprContext::General); + } + ExprKind::Ternary(cond, true_expr, false_expr) => { + check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false }); + check_expr(ctx, true_expr, ExprContext::General); + check_expr(ctx, false_expr, ExprContext::General); + } + ExprKind::Tuple(exprs) => { + for opt_expr in exprs.iter() { + if let SpannedOption::Some(expr) = opt_expr.as_ref() { + check_expr(ctx, expr, ExprContext::General); + } + } + } + _ => {} + } +} + +fn check_binary_expr(ctx: &LintContext, left: &Expr<'_>, op: BinOp, right: &Expr<'_>) { + if matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne) + && (bool_literal(left).is_some() || bool_literal(right).is_some()) + { + return; + } + + check_expr(ctx, left, ExprContext::General); + check_expr(ctx, right, ExprContext::General); +} + +fn bool_literal(expr: &Expr<'_>) -> Option { + let expr = expr.peel_parens(); + if let ExprKind::Lit(lit, _) = &expr.kind + && let LitKind::Bool(value) = lit.kind + { + Some(value) + } else { + None + } +} diff --git a/crates/lint/src/sol/info/boolean_equal.rs b/crates/lint/src/sol/info/boolean_equal.rs new file mode 100644 index 0000000000000..89cd7ec136f75 --- /dev/null +++ b/crates/lint/src/sol/info/boolean_equal.rs @@ -0,0 +1,108 @@ +use super::BooleanEqual; +use crate::{ + linter::{EarlyLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind}, + interface::diagnostics::Applicability, +}; + +declare_forge_lint!( + BOOLEAN_EQUAL, + Severity::Info, + "boolean-equal", + "boolean comparisons to constants should be simplified" +); + +impl<'ast> EarlyLintPass<'ast> for BooleanEqual { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Binary( + left, + op @ BinOp { kind: BinOpKind::Eq | BinOpKind::Ne, .. }, + right, + ) = &expr.kind + { + match bool_comparison_suggestion(ctx, left, op.kind, right) { + BoolComparison::WithSuggestion(simplified) => { + ctx.emit_with_suggestion( + &BOOLEAN_EQUAL, + expr.span, + Suggestion::fix(simplified, Applicability::MachineApplicable) + .with_desc("consider simplifying to"), + ); + } + BoolComparison::WithoutSuggestion => ctx.emit(&BOOLEAN_EQUAL, expr.span), + BoolComparison::None => {} + } + } + } +} + +enum BoolComparison { + WithSuggestion(String), + WithoutSuggestion, + None, +} + +fn bool_comparison_suggestion( + ctx: &LintContext, + left: &Expr<'_>, + op: BinOpKind, + right: &Expr<'_>, +) -> BoolComparison { + let left_bool = bool_literal(left); + let right_bool = bool_literal(right); + + match (left_bool, right_bool) { + (Some(value), None) => simplify_expr(ctx, right, op, value), + (None, Some(value)) => simplify_expr(ctx, left, op, value), + (Some(_), Some(_)) => BoolComparison::WithoutSuggestion, + (None, None) => BoolComparison::None, + } +} + +fn bool_literal(expr: &Expr<'_>) -> Option { + let expr = expr.peel_parens(); + if let ExprKind::Lit(lit, _) = &expr.kind + && let LitKind::Bool(value) = lit.kind + { + Some(value) + } else { + None + } +} + +fn simplify_expr( + ctx: &LintContext, + expr: &Expr<'_>, + op: BinOpKind, + constant: bool, +) -> BoolComparison { + let Some(snippet) = ctx.span_to_snippet(expr.span) else { + return BoolComparison::WithoutSuggestion; + }; + + let simplified = match (op, constant) { + (BinOpKind::Eq, true) | (BinOpKind::Ne, false) => snippet, + (BinOpKind::Eq, false) | (BinOpKind::Ne, true) if can_negate_without_parens(expr) => { + format!("!{snippet}") + } + (BinOpKind::Eq, false) | (BinOpKind::Ne, true) => format!("!({snippet})"), + _ => return BoolComparison::None, + }; + + BoolComparison::WithSuggestion(simplified) +} + +fn can_negate_without_parens(expr: &Expr<'_>) -> bool { + matches!( + expr.peel_parens().kind, + ExprKind::Call(..) + | ExprKind::CallOptions(..) + | ExprKind::Ident(_) + | ExprKind::Index(..) + | ExprKind::Lit(..) + | ExprKind::Member(..) + ) +} diff --git a/crates/lint/src/sol/info/imports.rs b/crates/lint/src/sol/info/imports.rs new file mode 100644 index 0000000000000..618b50bfe05f9 --- /dev/null +++ b/crates/lint/src/sol/info/imports.rs @@ -0,0 +1,171 @@ +use solar::{ + ast::{self as ast, SourceUnit, Span, Symbol, visit::Visit}, + data_structures::map::FxIndexSet, + interface::SourceMap, +}; +use std::ops::ControlFlow; + +use super::Imports; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; + +declare_forge_lint!( + UNUSED_IMPORT, + Severity::Info, + "unused-import", + "unused imports should be removed" +); + +declare_forge_lint!( + UNALIASED_PLAIN_IMPORT, + Severity::Info, + "unaliased-plain-import", + "use named imports '{A, B}' or alias 'import \"..\" as X'" +); + +impl<'ast> EarlyLintPass<'ast> for Imports { + fn check_import_directive( + &mut self, + ctx: &LintContext, + import: &'ast ast::ImportDirective<'ast>, + ) { + // Non-aliased plain imports like `import "File.sol";`. + if let ast::ImportItems::Plain(_) = &import.items + && import.source_alias().is_none() + { + ctx.emit(&UNALIASED_PLAIN_IMPORT, import.path.span); + } + } + + fn check_full_source_unit(&mut self, ctx: &LintContext<'ast, '_>, ast: &'ast SourceUnit<'ast>) { + // Despite disabled lints are filtered inside `ctx.emit()`, we explicitly check + // upfront to avoid the expensive full source unit traversal when unnecessary. + if ctx.is_lint_enabled(UNUSED_IMPORT.id) { + let mut checker = UnusedChecker::new(ctx.session().source_map()); + let _ = checker.visit_source_unit(ast); + checker.check_unused_imports(ast, ctx); + checker.clear(); + } + } +} + +/// Visitor that collects all used symbols in a source unit. +struct UnusedChecker<'ast> { + used_symbols: FxIndexSet, + source_map: &'ast SourceMap, +} + +impl<'ast> UnusedChecker<'ast> { + fn new(source_map: &'ast SourceMap) -> Self { + Self { source_map, used_symbols: Default::default() } + } + + fn clear(&mut self) { + self.used_symbols.clear(); + } + + /// Mark a symbol as used in a source. + fn mark_symbol_used(&mut self, symbol: Symbol) { + self.used_symbols.insert(symbol); + } + + /// Check for unused imports and emit warnings. + fn check_unused_imports(&self, ast: &SourceUnit<'_>, ctx: &LintContext) { + for item in ast.items.iter() { + let span = item.span; + let ast::ItemKind::Import(import) = &item.kind else { continue }; + #[allow(clippy::collapsible_match)] + match &import.items { + ast::ImportItems::Plain(_) | ast::ImportItems::Glob(_) => { + if let Some(alias) = import.source_alias() + && !self.used_symbols.contains(&alias.name) + { + self.unused_import(ctx, span); + } + } + ast::ImportItems::Aliases(symbols) => { + for &(orig, alias) in symbols.iter() { + let name = alias.unwrap_or(orig); + if !self.used_symbols.contains(&name.name) { + self.unused_import(ctx, orig.span.to(name.span)); + } + } + } + } + } + } + + fn unused_import(&self, ctx: &LintContext, span: Span) { + ctx.emit(&UNUSED_IMPORT, span); + } +} + +impl<'ast> Visit<'ast> for UnusedChecker<'ast> { + type BreakValue = solar::data_structures::Never; + + fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow { + if let ast::ItemKind::Import(_) = &item.kind { + return ControlFlow::Continue(()); + } + + self.walk_item(item) + } + + fn visit_using_directive( + &mut self, + using: &'ast ast::UsingDirective<'ast>, + ) -> ControlFlow { + match &using.list { + ast::UsingList::Single(path) => { + self.mark_symbol_used(path.first().name); + } + ast::UsingList::Multiple(items) => { + for (path, _) in items.iter() { + self.mark_symbol_used(path.first().name); + } + } + } + + self.walk_using_directive(using) + } + + fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow { + if let ast::ExprKind::Ident(id) = expr.kind { + self.mark_symbol_used(id.name); + } + + self.walk_expr(expr) + } + + fn visit_path(&mut self, path: &'ast ast::PathSlice) -> ControlFlow { + for id in path.segments() { + self.mark_symbol_used(id.name); + } + + self.walk_path(path) + } + + fn visit_ty(&mut self, ty: &'ast ast::Type<'ast>) -> ControlFlow { + if let ast::TypeKind::Custom(path) = &ty.kind { + self.mark_symbol_used(path.first().name); + } + + self.walk_ty(ty) + } + + fn visit_doc_comment( + &mut self, + cmnt: &'ast solar::ast::DocComment, + ) -> ControlFlow { + if let Ok(snip) = self.source_map.span_to_snippet(cmnt.span) { + for line in snip.lines() { + if let Some((_, relevant)) = line.split_once("@inheritdoc") { + self.mark_symbol_used(Symbol::intern(relevant.trim())); + } + } + } + ControlFlow::Continue(()) + } +} diff --git a/crates/lint/src/sol/info/inline_assembly.rs b/crates/lint/src/sol/info/inline_assembly.rs new file mode 100644 index 0000000000000..1111129dada34 --- /dev/null +++ b/crates/lint/src/sol/info/inline_assembly.rs @@ -0,0 +1,71 @@ +use super::InlineAssembly; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Stmt, StmtKind}, + interface::{BytePos, Span}, +}; + +declare_forge_lint!( + INLINE_ASSEMBLY, + Severity::Info, + "inline-assembly", + "usage of inline assembly; assembly bypasses Solidity safety features and should be reviewed" +); + +const ASSEMBLY_KW_LEN: u32 = 8; +const NATSPEC_MEMORY_SAFE_MARKER: &str = "@solidity memory-safe-assembly"; + +impl<'ast> EarlyLintPass<'ast> for InlineAssembly { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + let StmtKind::Assembly(asm) = &stmt.kind else { return }; + + let kw_span = assembly_keyword_span(stmt.span); + + let memory_safe = asm.flags.iter().any(|f| f.value.as_str() == "memory-safe") + || has_memory_safe_natspec(ctx, stmt.span.lo()); + + let msg = if memory_safe { + "inline assembly (declared memory-safe); review business logic and side effects" + } else { + "inline assembly used; review for memory safety and side effects" + }; + + ctx.emit_with_msg(&INLINE_ASSEMBLY, kw_span, msg); + } +} + +/// Narrows a span to the leading `assembly` keyword to keep diagnostics readable. +fn assembly_keyword_span(span: Span) -> Span { + span.with_hi(span.lo() + BytePos(ASSEMBLY_KW_LEN)) +} + +/// Returns `true` when the lines immediately preceding `stmt_lo` form a `///` NatSpec block +/// containing `@solidity memory-safe-assembly`. +fn has_memory_safe_natspec(ctx: &LintContext, stmt_lo: BytePos) -> bool { + let Some(source_file) = ctx.source_file() else { return false }; + let src = source_file.src.as_str(); + let start_pos = source_file.start_pos.to_u32(); + let lo_abs = stmt_lo.to_u32(); + if lo_abs < start_pos { + return false; + } + let offset = (lo_abs - start_pos) as usize; + if offset > src.len() { + return false; + } + + for line in src[..offset].lines().rev() { + let trimmed = line.trim_start(); + if trimmed.is_empty() { + continue; + } + let Some(rest) = trimmed.strip_prefix("///") else { return false }; + if rest.trim_start().starts_with(NATSPEC_MEMORY_SAFE_MARKER) { + return true; + } + } + false +} diff --git a/crates/lint/src/sol/info/interface_naming.rs b/crates/lint/src/sol/info/interface_naming.rs new file mode 100644 index 0000000000000..ba4bf13c48a81 --- /dev/null +++ b/crates/lint/src/sol/info/interface_naming.rs @@ -0,0 +1,62 @@ +use crate::{ + linter::{EarlyLintPass, Lint, LintContext}, + sol::{Severity, SolLint, info::InterfaceFileNaming}, +}; + +use solar::ast; + +declare_forge_lint!( + INTERFACE_FILE_NAMING, + Severity::Info, + "interface-file-naming", + "interface file names should be prefixed with 'I'" +); + +declare_forge_lint!( + INTERFACE_NAMING, + Severity::Info, + "interface-naming", + "interface names should be prefixed with 'I'" +); + +impl<'ast> EarlyLintPass<'ast> for InterfaceFileNaming { + fn check_full_source_unit( + &mut self, + ctx: &LintContext<'ast, '_>, + unit: &'ast ast::SourceUnit<'ast>, + ) { + if !ctx.is_lint_enabled(INTERFACE_FILE_NAMING.id()) { + return; + } + + if let Some(file_name) = file_name(ctx, unit) + && !file_name.starts_with('I') + && unit.items.iter().all(|item| match &item.kind { + ast::ItemKind::Contract(c) => c.kind == ast::ContractKind::Interface, + _ => true, + }) + && let Some(c) = unit.items.iter().find_map(|item| match &item.kind { + ast::ItemKind::Contract(c) => Some(c), + _ => None, + }) + { + ctx.emit(&INTERFACE_FILE_NAMING, c.name.span); + } + } + + fn check_item_contract(&mut self, ctx: &LintContext, contract: &'ast ast::ItemContract<'ast>) { + if ctx.is_lint_enabled(INTERFACE_NAMING.id()) + && contract.kind == ast::ContractKind::Interface + && !contract.name.as_str().starts_with('I') + { + ctx.emit(&INTERFACE_NAMING, contract.name.span); + } + } +} + +fn file_name(ctx: &LintContext, unit: &ast::SourceUnit) -> Option { + let first_item_span = unit.items.first()?.span; + let file = ctx.session().source_map().lookup_source_file(first_item_span.lo()); + let file_name = file.name.as_real()?.file_name()?.to_str()?; + Some(file_name.to_string()) +} diff --git a/crates/lint/src/sol/info/mixed_case.rs b/crates/lint/src/sol/info/mixed_case.rs index 5e839e9f313cf..669772188c2a3 100644 --- a/crates/lint/src/sol/info/mixed_case.rs +++ b/crates/lint/src/sol/info/mixed_case.rs @@ -1,10 +1,9 @@ use super::{MixedCaseFunction, MixedCaseVariable}; use crate::{ - declare_forge_lint, - linter::{EarlyLintPass, LintContext}, - sol::{Severity, SolLint}, + linter::{EarlyLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint, info::screaming_snake_case::check_screaming_snake_case}, }; -use solar_ast::{ItemFunction, VariableDefinition}; +use solar::ast::{FunctionHeader, ItemFunction, VariableDefinition, Visibility}; declare_forge_lint!( MIXED_CASE_FUNCTION, @@ -14,11 +13,24 @@ declare_forge_lint!( ); impl<'ast> EarlyLintPass<'ast> for MixedCaseFunction { - fn check_item_function(&mut self, ctx: &LintContext<'_>, func: &'ast ItemFunction<'ast>) { - if let Some(name) = func.header.name { - if !is_mixed_case(name.as_str(), true) { - ctx.emit(&MIXED_CASE_FUNCTION, name.span); - } + fn check_item_function(&mut self, ctx: &LintContext, func: &'ast ItemFunction<'ast>) { + if let Some(name) = func.header.name + && let Some(expected) = check_mixed_case( + name.as_str(), + true, + &ctx.config.lint_specific.mixed_case_exceptions, + ) + && !is_constant_getter(&func.header) + { + ctx.emit_with_suggestion( + &MIXED_CASE_FUNCTION, + name.span, + Suggestion::fix( + expected, + solar::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("consider using"), + ); } } } @@ -33,34 +45,94 @@ declare_forge_lint!( impl<'ast> EarlyLintPass<'ast> for MixedCaseVariable { fn check_variable_definition( &mut self, - ctx: &LintContext<'_>, + ctx: &LintContext, var: &'ast VariableDefinition<'ast>, ) { - if var.mutability.is_none() { - if let Some(name) = var.name { - if !is_mixed_case(name.as_str(), false) { - ctx.emit(&MIXED_CASE_VARIABLE, name.span); - } - } + if var.mutability.is_none() + && let Some(name) = var.name + && let Some(expected) = check_mixed_case( + name.as_str(), + false, + &ctx.config.lint_specific.mixed_case_exceptions, + ) + { + ctx.emit_with_suggestion( + &MIXED_CASE_VARIABLE, + name.span, + Suggestion::fix( + expected, + solar::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("consider using"), + ); } } } -/// Check if a string is mixedCase +/// If the string `s` is not mixedCase, returns a `Some(String)` with the +/// suggested conversion. Otherwise, returns `None`. /// -/// To avoid false positives like `fn increment()` or `uint256 counter`, -/// lowercase strings are treated as mixedCase. -pub fn is_mixed_case(s: &str, is_fn: bool) -> bool { +/// To avoid false positives: +/// - lowercase strings like `fn increment()` or `uint256 counter`, are treated as mixedCase. +/// - test functions starting with `test`, `invariant_` or `statefulFuzz` are ignored. +/// - user-defined patterns like `ERC20` are allowed. +fn check_mixed_case(s: &str, is_fn: bool, allowed_patterns: &[String]) -> Option { if s.len() <= 1 { - return true; + return None; + } + + // Exception for test, invariant, and stateful fuzzing functions. + if is_fn + && (s.starts_with("test") || s.starts_with("invariant_") || s.starts_with("statefulFuzz")) + { + return None; } - // Remove leading/trailing underscores like `heck` does - if s.trim_matches('_') == format!("{}", heck::AsLowerCamelCase(s)).as_str() { - return true + // Exception for user-defined infix patterns. + for pattern in allowed_patterns { + if let Some(pos) = s.find(pattern.as_str()) { + let (pre, post) = s.split_at(pos); + let post = &post[pattern.len()..]; + + // Check if the part before the pattern is valid lowerCamelCase. + let is_pre_valid = pre == heck::AsLowerCamelCase(pre).to_string(); + + // Check if the part after is valid UpperCamelCase (allowing leading numbers). + let post_trimmed = post.trim_start_matches(|c: char| c.is_numeric()); + let is_post_valid = post_trimmed == heck::AsUpperCamelCase(post_trimmed).to_string(); + + if is_pre_valid && is_post_valid { + return None; + } + } } - // Ignore `fn test*`, `fn invariant_*`, and `fn statefulFuzz*` patterns, as they usually contain - // (allowed) underscores. - is_fn && (s.starts_with("test") || s.starts_with("invariant_") || s.starts_with("statefulFuzz")) + // Generate the expected mixedCase version. + let suggestion = format!( + "{prefix}{name}{suffix}", + prefix = if s.starts_with('_') { "_" } else { "" }, + name = heck::AsLowerCamelCase(s), + suffix = if s.ends_with('_') { "_" } else { "" } + ); + + // If the original string already matches the suggestion, it's valid. + if s == suggestion { None } else { Some(suggestion) } +} + +/// Checks if a function getter is a valid constant getter with a heuristic: +/// * name is `SCREAMING_SNAKE_CASE` +/// * external view visibility and mutability. +/// * zero parameters. +/// * exactly one return value. +/// * return value is an elementary or a custom type +fn is_constant_getter(header: &FunctionHeader<'_>) -> bool { + header.visibility().is_some_and(|v| matches!(v, Visibility::External)) + && header.state_mutability().is_view() + && header.parameters.is_empty() + && header.returns().len() == 1 + && header + .returns() + .first() + .is_some_and(|ret| ret.ty.kind.is_elementary() || ret.ty.kind.is_custom()) + && check_screaming_snake_case(header.name.unwrap().as_str()).is_none() } diff --git a/crates/lint/src/sol/info/mod.rs b/crates/lint/src/sol/info/mod.rs index 6c414864f9688..913c5d2ea9da3 100644 --- a/crates/lint/src/sol/info/mod.rs +++ b/crates/lint/src/sol/info/mod.rs @@ -1,20 +1,57 @@ -use crate::{ - register_lints, - sol::{EarlyLintPass, SolLint}, -}; +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod mixed_case; use mixed_case::{MIXED_CASE_FUNCTION, MIXED_CASE_VARIABLE}; +mod boolean_cst; +use boolean_cst::BOOLEAN_CST; + +mod boolean_equal; +use boolean_equal::BOOLEAN_EQUAL; + mod pascal_case; use pascal_case::PASCAL_CASE_STRUCT; mod screaming_snake_case; use screaming_snake_case::{SCREAMING_SNAKE_CASE_CONSTANT, SCREAMING_SNAKE_CASE_IMMUTABLE}; +mod imports; +use imports::{UNALIASED_PLAIN_IMPORT, UNUSED_IMPORT}; + +mod named_struct_fields; +use named_struct_fields::NAMED_STRUCT_FIELDS; + +mod unsafe_cheatcodes; +use unsafe_cheatcodes::UNSAFE_CHEATCODE_USAGE; + +mod multi_contract_file; +use multi_contract_file::MULTI_CONTRACT_FILE; + +mod interface_naming; +use interface_naming::{INTERFACE_FILE_NAMING, INTERFACE_NAMING}; + +mod too_many_digits; +use too_many_digits::TOO_MANY_DIGITS; + +mod pragma_directive; +use pragma_directive::PRAGMA_INCONSISTENT; + +mod inline_assembly; +use inline_assembly::INLINE_ASSEMBLY; + register_lints!( - (PascalCaseStruct, (PASCAL_CASE_STRUCT)), - (MixedCaseVariable, (MIXED_CASE_VARIABLE)), - (MixedCaseFunction, (MIXED_CASE_FUNCTION)), - (ScreamingSnakeCase, (SCREAMING_SNAKE_CASE_CONSTANT, SCREAMING_SNAKE_CASE_IMMUTABLE)) + (BooleanCst, early, (BOOLEAN_CST)), + (BooleanEqual, early, (BOOLEAN_EQUAL)), + (PascalCaseStruct, early, (PASCAL_CASE_STRUCT)), + (MixedCaseVariable, early, (MIXED_CASE_VARIABLE)), + (MixedCaseFunction, early, (MIXED_CASE_FUNCTION)), + (ScreamingSnakeCase, early, (SCREAMING_SNAKE_CASE_CONSTANT, SCREAMING_SNAKE_CASE_IMMUTABLE)), + (Imports, early, (UNALIASED_PLAIN_IMPORT, UNUSED_IMPORT)), + (NamedStructFields, late, (NAMED_STRUCT_FIELDS)), + (UnsafeCheatcodes, early, (UNSAFE_CHEATCODE_USAGE)), + (MultiContractFile, early, (MULTI_CONTRACT_FILE)), + (InterfaceFileNaming, early, (INTERFACE_FILE_NAMING, INTERFACE_NAMING)), + (TooManyDigits, early, (TOO_MANY_DIGITS)), + (PragmaDirective, project, (PRAGMA_INCONSISTENT)), + (InlineAssembly, early, (INLINE_ASSEMBLY)), ); diff --git a/crates/lint/src/sol/info/multi_contract_file.rs b/crates/lint/src/sol/info/multi_contract_file.rs new file mode 100644 index 0000000000000..bab0845a71aab --- /dev/null +++ b/crates/lint/src/sol/info/multi_contract_file.rs @@ -0,0 +1,44 @@ +use crate::{ + linter::{EarlyLintPass, Lint, LintContext}, + sol::{Severity, SolLint, info::MultiContractFile}, +}; + +use solar::ast; + +declare_forge_lint!( + MULTI_CONTRACT_FILE, + Severity::Info, + "multi-contract-file", + "prefer having only one contract, interface or library per file" +); + +impl<'ast> EarlyLintPass<'ast> for MultiContractFile { + fn check_full_source_unit( + &mut self, + ctx: &LintContext<'ast, '_>, + unit: &'ast ast::SourceUnit<'ast>, + ) { + if !ctx.is_lint_enabled(MULTI_CONTRACT_FILE.id()) { + return; + } + + // Collect spans of all contract-like items, skipping those that are exempted + let relevant_spans: Vec<_> = unit + .items + .iter() + .filter_map(|item| match &item.kind { + ast::ItemKind::Contract(c) => { + (!ctx.config.lint_specific.is_exempted(&c.kind)).then_some(c.name.span) + } + _ => None, + }) + .collect(); + + // Flag all if there's more than one + if relevant_spans.len() > 1 { + for span in relevant_spans { + ctx.emit(&MULTI_CONTRACT_FILE, span); + } + } + } +} diff --git a/crates/lint/src/sol/info/named_struct_fields.rs b/crates/lint/src/sol/info/named_struct_fields.rs new file mode 100644 index 0000000000000..f3454f0dba3dc --- /dev/null +++ b/crates/lint/src/sol/info/named_struct_fields.rs @@ -0,0 +1,74 @@ +use solar::sema::hir::{CallArgs, CallArgsKind, Expr, ExprKind, ItemId, Res}; + +use crate::{ + linter::{LateLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint, info::NamedStructFields}, +}; + +declare_forge_lint!( + NAMED_STRUCT_FIELDS, + Severity::Info, + "named-struct-fields", + "prefer initializing structs with named fields" +); + +impl<'hir> LateLintPass<'hir> for NamedStructFields { + fn check_expr( + &mut self, + ctx: &LintContext, + hir: &'hir solar::sema::hir::Hir<'hir>, + expr: &'hir solar::sema::hir::Expr<'hir>, + ) { + let ExprKind::Call( + Expr { kind: ExprKind::Ident([Res::Item(ItemId::Struct(struct_id))]), span, .. }, + CallArgs { kind: CallArgsKind::Unnamed(args), .. }, + _, + ) = &expr.kind + else { + return; + }; + + let strukt = hir.strukt(*struct_id); + let fields = &strukt.fields; + + // Basic sanity conditions for a consistent auto-fix + if fields.len() != args.len() || fields.is_empty() { + // Emit without suggestion + ctx.emit(&NAMED_STRUCT_FIELDS, expr.span); + return; + } + + // Get struct name snippet and emit without suggestion if we can't get it + let Some(struct_name_snippet) = ctx.span_to_snippet(*span) else { + // Emit without suggestion if we can't get the struct name snippet + ctx.emit(&NAMED_STRUCT_FIELDS, expr.span); + return; + }; + + // Collect field names and corresponding argument source snippets + let mut field_assignments = Vec::new(); + for (field_id, arg) in fields.iter().zip(args.iter()) { + let field = hir.variable(*field_id); + + let Some((arg_snippet, field_name)) = + ctx.span_to_snippet(arg.span).zip(field.name.map(|n| n.to_string())) + else { + // Emit without suggestion if we can't get argument snippet + ctx.emit(&NAMED_STRUCT_FIELDS, expr.span); + return; + }; + + field_assignments.push(format!("{field_name}: {arg_snippet}")); + } + + ctx.emit_with_suggestion( + &NAMED_STRUCT_FIELDS, + expr.span, + Suggestion::fix( + format!("{}({{ {} }})", struct_name_snippet, field_assignments.join(", ")), + solar::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("consider using named fields"), + ); + } +} diff --git a/crates/lint/src/sol/info/pascal_case.rs b/crates/lint/src/sol/info/pascal_case.rs index 60cafbc8365a3..8e7bd22fe4bc1 100644 --- a/crates/lint/src/sol/info/pascal_case.rs +++ b/crates/lint/src/sol/info/pascal_case.rs @@ -1,10 +1,9 @@ use super::PascalCaseStruct; use crate::{ - declare_forge_lint, - linter::{EarlyLintPass, LintContext}, + linter::{EarlyLintPass, LintContext, Suggestion}, sol::{Severity, SolLint}, }; -use solar_ast::ItemStruct; +use solar::ast::ItemStruct; declare_forge_lint!( PASCAL_CASE_STRUCT, @@ -14,19 +13,29 @@ declare_forge_lint!( ); impl<'ast> EarlyLintPass<'ast> for PascalCaseStruct { - fn check_item_struct(&mut self, ctx: &LintContext<'_>, strukt: &'ast ItemStruct<'ast>) { + fn check_item_struct(&mut self, ctx: &LintContext, strukt: &'ast ItemStruct<'ast>) { let name = strukt.name.as_str(); - if name.len() > 1 && !is_pascal_case(name) { - ctx.emit(&PASCAL_CASE_STRUCT, strukt.name.span); + if let Some(expected) = check_pascal_case(name) { + ctx.emit_with_suggestion( + &PASCAL_CASE_STRUCT, + strukt.name.span, + Suggestion::fix( + expected, + solar::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("consider using"), + ); } } } -/// Check if a string is PascalCase -pub fn is_pascal_case(s: &str) -> bool { +/// If the string `s` is not PascalCase, returns a `Some(String)` with the +/// suggested conversion. Otherwise, returns `None`. +pub fn check_pascal_case(s: &str) -> Option { if s.len() <= 1 { - return true; + return None; } - s == format!("{}", heck::AsPascalCase(s)).as_str() + let expected = heck::AsPascalCase(s).to_string(); + if s == expected.as_str() { None } else { Some(expected) } } diff --git a/crates/lint/src/sol/info/pragma_directive.rs b/crates/lint/src/sol/info/pragma_directive.rs new file mode 100644 index 0000000000000..b66b6bcff6ade --- /dev/null +++ b/crates/lint/src/sol/info/pragma_directive.rs @@ -0,0 +1,71 @@ +use crate::{ + linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource}, + sol::{Severity, SolLint, info::PragmaDirective}, +}; +use solar::{ast, interface::Span}; + +declare_forge_lint!( + PRAGMA_INCONSISTENT, + Severity::Info, + "pragma-inconsistent", + "inconsistent Solidity pragma version requirements across the project" +); + +impl<'ast> ProjectLintPass<'ast> for PragmaDirective { + fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) { + if !ctx.is_lint_enabled(PRAGMA_INCONSISTENT.id()) { + return; + } + + // Collect every `pragma solidity` directive across input sources, with its rendered + // version-requirement string for grouping. Stores source index to avoid lifetime + // invariance issues with `&ProjectSource<'ast>`. + let mut entries: Vec<(usize, Span, String)> = Vec::new(); + for (idx, source) in sources.iter().enumerate() { + for (span, req) in solidity_pragmas(source.ast) { + entries.push((idx, span, req.to_string())); + } + } + + // Stable order for snapshots and JSON output. + entries.sort_by(|a, b| { + sources[a.0].path.cmp(&sources[b.0].path).then(a.1.lo().cmp(&b.1.lo())) + }); + + // Build the distinct list once and bail if all sources agree. + let mut distinct: Vec<&str> = entries.iter().map(|(_, _, s)| s.as_str()).collect(); + distinct.sort_unstable(); + distinct.dedup(); + if distinct.len() < 2 { + return; + } + + for (idx, span, req_str) in &entries { + let others = distinct + .iter() + .filter(|v| **v != req_str.as_str()) + .copied() + .collect::>() + .join(", "); + let msg = format!( + "'pragma solidity {req_str};' conflicts with other version requirements in the project: {others}" + ); + ctx.emit_with_msg(&sources[*idx], &PRAGMA_INCONSISTENT, *span, msg); + } + } +} + +/// Yields every top-level `pragma solidity ...;` directive in `unit`. +fn solidity_pragmas<'ast>( + unit: &'ast ast::SourceUnit<'ast>, +) -> impl Iterator)> + 'ast { + unit.items.iter().filter_map(|item| match &item.kind { + ast::ItemKind::Pragma(p) => match &p.tokens { + ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => { + Some((item.span, req)) + } + _ => None, + }, + _ => None, + }) +} diff --git a/crates/lint/src/sol/info/screaming_snake_case.rs b/crates/lint/src/sol/info/screaming_snake_case.rs index ccc978029b8a4..ff3d5982b2fb3 100644 --- a/crates/lint/src/sol/info/screaming_snake_case.rs +++ b/crates/lint/src/sol/info/screaming_snake_case.rs @@ -1,10 +1,9 @@ use super::ScreamingSnakeCase; use crate::{ - declare_forge_lint, - linter::{EarlyLintPass, LintContext}, + linter::{EarlyLintPass, LintContext, Suggestion}, sol::{Severity, SolLint}, }; -use solar_ast::{VarMut, VariableDefinition}; +use solar::ast::{VarMut, VariableDefinition}; declare_forge_lint!( SCREAMING_SNAKE_CASE_CONSTANT, @@ -23,29 +22,43 @@ declare_forge_lint!( impl<'ast> EarlyLintPass<'ast> for ScreamingSnakeCase { fn check_variable_definition( &mut self, - ctx: &LintContext<'_>, + ctx: &LintContext, var: &'ast VariableDefinition<'ast>, ) { - if let (Some(name), Some(mutability)) = (var.name, var.mutability) { - let name_str = name.as_str(); - if name_str.len() < 2 || is_screaming_snake_case(name_str) { - return; - } + if let (Some(name), Some(mutability)) = (var.name, var.mutability) + && let Some(expected) = check_screaming_snake_case(name.as_str()) + { + let suggestion = Suggestion::fix( + expected, + solar::interface::diagnostics::Applicability::MachineApplicable, + ) + .with_desc("consider using"); match mutability { - VarMut::Constant => ctx.emit(&SCREAMING_SNAKE_CASE_CONSTANT, name.span), - VarMut::Immutable => ctx.emit(&SCREAMING_SNAKE_CASE_IMMUTABLE, name.span), + VarMut::Constant => { + ctx.emit_with_suggestion(&SCREAMING_SNAKE_CASE_CONSTANT, name.span, suggestion) + } + VarMut::Immutable => { + ctx.emit_with_suggestion(&SCREAMING_SNAKE_CASE_IMMUTABLE, name.span, suggestion) + } } } } } -/// Check if a string is SCREAMING_SNAKE_CASE. Numbers don't need to be preceded by an underscore. -pub fn is_screaming_snake_case(s: &str) -> bool { +/// If the string `s` is not SCREAMING_SNAKE_CASE, returns a `Some(String)` with the suggested +/// conversion. Otherwise, returns `None`. +pub fn check_screaming_snake_case(s: &str) -> Option { if s.len() <= 1 { - return true; + return None; } - // Remove leading/trailing underscores like `heck` does - s.trim_matches('_') == format!("{}", heck::AsShoutySnakeCase(s)).as_str() + // Handle leading/trailing underscores like `heck` does + let expected = format!( + "{prefix}{name}{suffix}", + prefix = if s.starts_with('_') { "_" } else { "" }, + name = heck::AsShoutySnakeCase(s), + suffix = if s.ends_with('_') { "_" } else { "" } + ); + if s == expected { None } else { Some(expected) } } diff --git a/crates/lint/src/sol/info/too_many_digits.rs b/crates/lint/src/sol/info/too_many_digits.rs new file mode 100644 index 0000000000000..3ba9e8abba2de --- /dev/null +++ b/crates/lint/src/sol/info/too_many_digits.rs @@ -0,0 +1,50 @@ +use super::TooManyDigits; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{Expr, ExprKind, LitKind}; + +declare_forge_lint!( + TOO_MANY_DIGITS, + Severity::Info, + "too-many-digits", + "numeric literal with many digits is error-prone; \ + use scientific notation, sub-denominations, or underscore separators" +); + +impl<'ast> EarlyLintPass<'ast> for TooManyDigits { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + let ExprKind::Lit(lit, sub_denom) = &expr.kind else { return }; + + // Only plain integer literals. `LitKind::Address` (40-hex-digit address) is a + // distinct variant and is therefore skipped automatically. + if !matches!(lit.kind, LitKind::Number(_)) { + return; + } + + // Skip literals with a sub-denomination, e.g. `1000000 gwei`, `5 minutes`. + if sub_denom.is_some() { + return; + } + + let s = lit.symbol.as_str(); + + // Skip hex literals — long zero runs in hex are usually intentional (masks, + // selectors, bit patterns) and there is no scientific-notation alternative. + if s.starts_with("0x") || s.starts_with("0X") { + return; + } + + // Skip if the user already used scientific notation (`1e18`). + if s.contains('e') || s.contains('E') { + return; + } + + // 5+ consecutive zeros in the literal as written. Underscores are + // preserved, so `1_000_000` passes while `1_000000` is flagged. + if s.contains("00000") { + ctx.emit(&TOO_MANY_DIGITS, lit.span); + } + } +} diff --git a/crates/lint/src/sol/info/unsafe_cheatcodes.rs b/crates/lint/src/sol/info/unsafe_cheatcodes.rs new file mode 100644 index 0000000000000..f5f6efcb75a53 --- /dev/null +++ b/crates/lint/src/sol/info/unsafe_cheatcodes.rs @@ -0,0 +1,36 @@ +use super::UnsafeCheatcodes; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{Expr, ExprKind}; + +declare_forge_lint!( + UNSAFE_CHEATCODE_USAGE, + Severity::Info, + "unsafe-cheatcode", + "usage of unsafe cheatcodes that can perform dangerous operations" +); + +const UNSAFE_CHEATCODES: [&str; 9] = [ + "ffi", + "readFile", + "readLine", + "writeFile", + "writeLine", + "removeFile", + "closeFile", + "setEnv", + "deriveKey", +]; + +impl<'ast> EarlyLintPass<'ast> for UnsafeCheatcodes { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(lhs, _args) = &expr.kind + && let ExprKind::Member(_lhs, member) = &lhs.kind + && UNSAFE_CHEATCODES.iter().any(|&c| c == member.as_str()) + { + ctx.emit(&UNSAFE_CHEATCODE_USAGE, member.span); + } + } +} diff --git a/crates/lint/src/sol/low/block_timestamp.rs b/crates/lint/src/sol/low/block_timestamp.rs new file mode 100644 index 0000000000000..b4ba3c7712217 --- /dev/null +++ b/crates/lint/src/sol/low/block_timestamp.rs @@ -0,0 +1,70 @@ +use super::BlockTimestamp; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::ast::{BinOp, BinOpKind, Expr, ExprKind}; + +declare_forge_lint!( + BLOCK_TIMESTAMP, + Severity::Low, + "block-timestamp", + "usage of `block.timestamp` in a comparison may be manipulated by validators" +); + +impl<'ast> EarlyLintPass<'ast> for BlockTimestamp { + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Binary(lhs, BinOp { kind, .. }, rhs) = &expr.kind + && is_cmp(*kind) + && (contains_block_timestamp(lhs) || contains_block_timestamp(rhs)) + { + ctx.emit(&BLOCK_TIMESTAMP, expr.span); + } + } +} + +const fn is_cmp(kind: BinOpKind) -> bool { + matches!( + kind, + BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Eq + | BinOpKind::Ne + ) +} + +/// Returns `true` if `expr` is `block.timestamp`. +fn is_block_timestamp(expr: &Expr<'_>) -> bool { + matches!( + &expr.kind, + ExprKind::Member(base, member) + if member.as_str() == "timestamp" + && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "block") + ) +} + +/// Recursively checks if an expression tree contains `block.timestamp`. +fn contains_block_timestamp(expr: &Expr<'_>) -> bool { + if is_block_timestamp(expr) { + return true; + } + match &expr.kind { + ExprKind::Unary(_, inner) => contains_block_timestamp(inner), + ExprKind::Binary(lhs, _, rhs) => { + contains_block_timestamp(lhs) || contains_block_timestamp(rhs) + } + ExprKind::Tuple(elems) => elems.iter().any(|e| { + if let solar::interface::SpannedOption::Some(inner) = e.as_ref() { + contains_block_timestamp(inner) + } else { + false + } + }), + ExprKind::Call(callee, args) => { + contains_block_timestamp(callee) || args.exprs().any(|e| contains_block_timestamp(e)) + } + _ => false, + } +} diff --git a/crates/lint/src/sol/low/missing_zero_check.rs b/crates/lint/src/sol/low/missing_zero_check.rs new file mode 100644 index 0000000000000..def234e9d1582 --- /dev/null +++ b/crates/lint/src/sol/low/missing_zero_check.rs @@ -0,0 +1,443 @@ +use super::MissingZeroCheck; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast, + interface::{data_structures::Never, kw, sym}, + sema::hir::{self, ElementaryType, ExprKind, ItemId, Res, StmtKind, TypeKind, Visit}, +}; +use std::{ + collections::{HashMap, HashSet}, + ops::ControlFlow, +}; + +declare_forge_lint!( + MISSING_ZERO_CHECK, + Severity::Low, + "missing-zero-check", + "address parameter is used in a state write or value transfer without a zero-address check" +); + +impl<'hir> LateLintPass<'hir> for MissingZeroCheck { + fn check_function( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + func: &'hir hir::Function<'hir>, + ) { + if !is_entry_point(func) { + return; + } + + let params: HashSet = + func.parameters.iter().copied().filter(|id| is_address(hir, *id)).collect(); + + if params.is_empty() { + return; + } + + let Some(body) = func.body else { return }; + + let mut a = Analyzer::new(hir, ¶ms); + + for m in func.modifiers { + collect_modifier_guards(hir, m, ¶ms, &mut a.guarded); + } + + for stmt in body.stmts { + let _ = a.visit_stmt(stmt); + } + + for &p in ¶ms { + if a.sinks.contains(&p) { + ctx.emit(&MISSING_ZERO_CHECK, hir.variable(p).span); + } + } + } +} + +/// Externally callable, state-mutating functions and constructors. +fn is_entry_point(func: &hir::Function<'_>) -> bool { + if matches!(func.state_mutability, ast::StateMutability::Pure | ast::StateMutability::View) { + return false; + } + if func.is_constructor() { + return true; + } + func.kind.is_function() + && matches!(func.visibility, ast::Visibility::Public | ast::Visibility::External) +} + +fn is_address(hir: &hir::Hir<'_>, id: hir::VariableId) -> bool { + matches!(hir.variable(id).ty.kind, TypeKind::Elementary(ElementaryType::Address(_))) +} + +/// Tracks address-parameter taint, sinks reached, and guards observed in a function body. +struct Analyzer<'hir> { + hir: &'hir hir::Hir<'hir>, + /// Variables transitively derived from candidate parameters, mapped to their sources. + /// Each parameter is initially mapped to itself. + taint: HashMap>, + /// Source parameters that reached a sink. + sinks: HashSet, + /// Source parameters read inside an `if`/`require`/`assert` predicate. + guarded: HashSet, + guard_depth: u32, + sink_depth: u32, +} + +impl<'hir> Analyzer<'hir> { + fn new(hir: &'hir hir::Hir<'hir>, params: &HashSet) -> Self { + let mut taint = HashMap::with_capacity(params.len()); + for &p in params { + taint.insert(p, HashSet::from([p])); + } + Self { + hir, + taint, + sinks: HashSet::new(), + guarded: HashSet::new(), + guard_depth: 0, + sink_depth: 0, + } + } + + fn taint_sources(&self, expr: &hir::Expr<'_>) -> HashSet { + let mut out = HashSet::new(); + collect_taint_sources(&self.taint, expr, &mut out); + out + } +} + +fn collect_taint_sources( + taint: &HashMap>, + expr: &hir::Expr<'_>, + out: &mut HashSet, +) { + match &expr.kind { + ExprKind::Ident(reses) => { + for res in *reses { + if let Res::Item(ItemId::Variable(vid)) = res + && let Some(srcs) = taint.get(vid) + { + out.extend(srcs.iter().copied()); + } + } + } + ExprKind::Assign(_, _, rhs) => collect_taint_sources(taint, rhs, out), + ExprKind::Binary(lhs, _, rhs) => { + collect_taint_sources(taint, lhs, out); + collect_taint_sources(taint, rhs, out); + } + ExprKind::Unary(_, e) + | ExprKind::Delete(e) + | ExprKind::Member(e, _) + | ExprKind::Payable(e) => collect_taint_sources(taint, e, out), + ExprKind::Ternary(_, t, f) => { + collect_taint_sources(taint, t, out); + collect_taint_sources(taint, f, out); + } + ExprKind::Tuple(elems) => { + for e in elems.iter().copied().flatten() { + collect_taint_sources(taint, e, out); + } + } + ExprKind::Array(elems) => { + for e in *elems { + collect_taint_sources(taint, e, out); + } + } + ExprKind::Index(base, idx) => { + collect_taint_sources(taint, base, out); + if let Some(i) = idx { + collect_taint_sources(taint, i, out); + } + } + // Covers type casts (`address(x)`), method calls, and ordinary calls; conservative. + ExprKind::Call(callee, args, _) => { + collect_taint_sources(taint, callee, out); + for a in args.exprs() { + collect_taint_sources(taint, a, out); + } + } + _ => {} + } +} + +/// Returns the underlying local `VariableId` if `lhs` is a direct identifier reference to a +/// non-state variable. +fn lhs_local_var(hir: &hir::Hir<'_>, lhs: &hir::Expr<'_>) -> Option { + if let ExprKind::Ident(reses) = &lhs.kind { + for res in *reses { + if let Res::Item(ItemId::Variable(vid)) = res + && !hir.variable(*vid).kind.is_state() + { + return Some(*vid); + } + } + } + None +} + +impl<'hir> Visit<'hir> for Analyzer<'hir> { + type BreakValue = Never; + + fn hir(&self) -> &'hir hir::Hir<'hir> { + self.hir + } + + fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) -> ControlFlow { + match stmt.kind { + StmtKind::If(cond, then, else_) => { + self.guard_depth += 1; + let _ = self.visit_expr(cond); + self.guard_depth -= 1; + + let baseline = self.guarded.clone(); + let _ = self.visit_stmt(then); + let then_added: HashSet = + self.guarded.difference(&baseline).copied().collect(); + let then_exits = branch_always_exits(then); + + let (else_added, else_exits) = if let Some(e) = else_ { + self.guarded = baseline.clone(); + let _ = self.visit_stmt(e); + let added: HashSet = + self.guarded.difference(&baseline).copied().collect(); + (added, branch_always_exits(e)) + } else { + (HashSet::new(), false) + }; + + self.guarded = baseline; + let to_add: HashSet = match (then_exits, else_exits) { + (true, true) => then_added.union(&else_added).copied().collect(), + (true, false) => else_added, + (false, true) => then_added, + (false, false) => then_added.intersection(&else_added).copied().collect(), + }; + self.guarded.extend(to_add); + + return ControlFlow::Continue(()); + } + // Loop bodies may execute zero times, so guards inside must not persist. + StmtKind::Loop(block, _) => { + let baseline = self.guarded.clone(); + for s in block.stmts { + let _ = self.visit_stmt(s); + } + self.guarded = baseline; + return ControlFlow::Continue(()); + } + // Each try/catch clause is taken on a single path; discard clause-local guards. + StmtKind::Try(t) => { + let _ = self.visit_expr(&t.expr); + for clause in t.clauses { + let baseline = self.guarded.clone(); + for s in clause.block.stmts { + let _ = self.visit_stmt(s); + } + self.guarded = baseline; + } + return ControlFlow::Continue(()); + } + // Propagate taint through address-typed local declarations only; this avoids + // marking unrelated values (e.g. `bool ok = a.send(1)`) as derived from `a`. + StmtKind::DeclSingle(var_id) => { + let v = self.hir.variable(var_id); + if let Some(init) = v.initializer + && is_address(self.hir, var_id) + { + let srcs = self.taint_sources(init); + if !srcs.is_empty() { + self.taint.entry(var_id).or_default().extend(srcs); + } + } + } + _ => {} + } + self.walk_stmt(stmt) + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow { + match &expr.kind { + // `require(cond, ..)` / `assert(cond)`: only the first arg is a guard predicate. + ExprKind::Call(callee, args, _) if is_require_or_assert(callee) => { + let mut iter = args.exprs(); + if let Some(cond) = iter.next() { + self.guard_depth += 1; + let _ = self.visit_expr(cond); + self.guard_depth -= 1; + } + for rest in iter { + let _ = self.visit_expr(rest); + } + return ControlFlow::Continue(()); + } + + // `.call/.delegatecall/.transfer/.send(..)`: receiver is the sink. + ExprKind::Call(callee, args, _) => { + if let Some(receiver) = address_call_receiver(callee) { + self.sink_depth += 1; + let _ = self.visit_expr(receiver); + self.sink_depth -= 1; + let _ = self.visit_call_args(args); + return ControlFlow::Continue(()); + } + } + + ExprKind::Assign(lhs, _, rhs) => { + // Sink: assignment to an address state variable. + if is_address_state_var_lhs(self.hir, lhs) { + let _ = self.visit_expr(lhs); + self.sink_depth += 1; + let _ = self.visit_expr(rhs); + self.sink_depth -= 1; + return ControlFlow::Continue(()); + } + // Taint propagation: assignment to an address local. + if let Some(local) = lhs_local_var(self.hir, lhs) + && is_address(self.hir, local) + { + let srcs = self.taint_sources(rhs); + if !srcs.is_empty() { + self.taint.entry(local).or_default().extend(srcs); + } + } + } + + // Identifier reads contribute to whichever contexts are currently active. + ExprKind::Ident(reses) => { + for res in *reses { + if let Res::Item(ItemId::Variable(vid)) = res + && let Some(srcs) = self.taint.get(vid) + { + if self.guard_depth > 0 { + self.guarded.extend(srcs.iter().copied()); + } + if self.sink_depth > 0 { + for &src in srcs { + if !self.guarded.contains(&src) { + self.sinks.insert(src); + } + } + } + } + } + } + + _ => {} + } + self.walk_expr(expr) + } +} + +fn is_require_or_assert(callee: &hir::Expr<'_>) -> bool { + if let ExprKind::Ident(reses) = &callee.kind { + return reses.iter().any(|r| { + if let Res::Builtin(b) = r { + let n = b.name(); + n == sym::require || n == sym::assert + } else { + false + } + }); + } + false +} + +/// If `callee` is `.{call,delegatecall,transfer,send}` (with or without +/// call options), returns the `` expression. +fn address_call_receiver<'hir>(callee: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> { + // `addr.call{value: x}(..)` lowers as `Call(Member(receiver, "call"), ..)` — peel an + // outer call layer so the inner Member is reachable. + let inner = match &callee.kind { + ExprKind::Call(inner, ..) => inner, + _ => callee, + }; + let target = if matches!(inner.kind, ExprKind::Member(..)) { inner } else { callee }; + if let ExprKind::Member(receiver, name) = &target.kind { + let n = name.name; + if n == kw::Call || n == kw::Delegatecall || n == sym::transfer || n == sym::send { + return Some(receiver); + } + } + None +} + +fn branch_always_exits(stmt: &hir::Stmt<'_>) -> bool { + match &stmt.kind { + StmtKind::Return(_) | StmtKind::Revert(_) => true, + StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => { + block.stmts.last().is_some_and(branch_always_exits) + } + StmtKind::If(_, t, Some(e)) => branch_always_exits(t) && branch_always_exits(e), + _ => false, + } +} + +fn is_address_state_var_lhs(hir: &hir::Hir<'_>, lhs: &hir::Expr<'_>) -> bool { + if let ExprKind::Ident(reses) = &lhs.kind { + for res in *reses { + if let Res::Item(ItemId::Variable(vid)) = res { + let v = hir.variable(*vid); + if v.kind.is_state() + && matches!(v.ty.kind, TypeKind::Elementary(ElementaryType::Address(_))) + { + return true; + } + } + } + } + false +} + +/// Maps each direct-ident modifier argument back to its caller-side parameter, runs the same guard +/// analysis on the modifier body, and records any caller params whose mapped modifier parameter is +/// guarded. +fn collect_modifier_guards( + hir: &hir::Hir<'_>, + invocation: &hir::Modifier<'_>, + caller_params: &HashSet, + guarded: &mut HashSet, +) { + let ItemId::Function(fid) = invocation.id else { return }; + let modifier = hir.function(fid); + if !matches!(modifier.kind, hir::FunctionKind::Modifier) { + return; + } + + let mod_params = modifier.parameters; + let mut mapping: HashSet = HashSet::new(); + let mut caller_for_modparam: HashMap = HashMap::new(); + for (i, arg_expr) in invocation.args.exprs().enumerate() { + if let ExprKind::Ident(reses) = &arg_expr.kind { + for res in *reses { + if let Res::Item(ItemId::Variable(vid)) = res + && caller_params.contains(vid) + && let Some(&mp) = mod_params.get(i) + { + caller_for_modparam.insert(mp, *vid); + mapping.insert(mp); + } + } + } + } + if mapping.is_empty() { + return; + } + + let Some(body) = modifier.body else { return }; + let mut a = Analyzer::new(hir, &mapping); + for stmt in body.stmts { + let _ = a.visit_stmt(stmt); + } + + for mp in a.guarded { + if let Some(caller_vid) = caller_for_modparam.get(&mp) { + guarded.insert(*caller_vid); + } + } +} diff --git a/crates/lint/src/sol/low/mod.rs b/crates/lint/src/sol/low/mod.rs new file mode 100644 index 0000000000000..bd51ee187485e --- /dev/null +++ b/crates/lint/src/sol/low/mod.rs @@ -0,0 +1,12 @@ +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; + +mod block_timestamp; +use block_timestamp::BLOCK_TIMESTAMP; + +mod missing_zero_check; +use missing_zero_check::MISSING_ZERO_CHECK; + +register_lints!( + (BlockTimestamp, early, (BLOCK_TIMESTAMP)), + (MissingZeroCheck, late, (MISSING_ZERO_CHECK)), +); diff --git a/crates/lint/src/sol/macros.rs b/crates/lint/src/sol/macros.rs index 357f742aa297c..8540ab8b95b8f 100644 --- a/crates/lint/src/sol/macros.rs +++ b/crates/lint/src/sol/macros.rs @@ -3,15 +3,17 @@ /// # Parameters /// /// Each lint requires the following input fields: -/// - `$id`: Identitifier of the generated `SolLint` constant. +/// - `$id`: Identifier of the generated `SolLint` constant. /// - `$severity`: The `Severity` of the lint (e.g. `High`, `Med`, `Low`, `Info`, `Gas`). /// - `$str_id`: A unique identifier used to reference a specific lint during configuration. /// - `$desc`: A short description of the lint. /// /// # Note -/// Each lint must have a `help` section in the foundry book. This help field is auto-generated by -/// the macro. Because of that, to ensure that new lint rules have their corresponding docs in the -/// book, the existence of the lint rule's help section is validated with a unit test. +/// Each lint must have a corresponding markdown documentation file at +/// `crates/lint/docs/.md`. The `help` URL is auto-generated by the macro and points to +/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/`). To +/// ensure that new lint rules have their corresponding docs, the existence of every registered +/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`). #[macro_export] macro_rules! declare_forge_lint { ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { @@ -20,52 +22,138 @@ macro_rules! declare_forge_lint { id: $str_id, severity: $severity, description: $desc, - help: concat!("https://book.getfoundry.sh/reference/forge/forge-lint#", $str_id), + help: concat!("https://getfoundry.sh/forge/linting/", $str_id), }; }; - - ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => { - $crate::declare_forge_lint!($id, $severity, $str_id, $desc, ""); - }; } -/// Registers Solidity linter passes with their corresponding `SolLint`. +/// Registers Solidity linter passes that can have both early and late variants. /// /// # Parameters /// -/// - `$pass_id`: Identitifier of the generated struct that will implement the pass trait. -/// - (`$lint`): tuple with `SolLint` constants that should be evaluated on every input that pass. +/// Each pass is declared with: +/// - `$pass_id`: Identifier of the generated struct that will implement the pass trait(s). +/// - `$pass_type`: Either `early`, `late`, or `both` to indicate which traits to implement. +/// - `$lints`: A parenthesized, comma-separated list of `SolLint` constants. /// /// # Outputs /// -/// - Structs for each linting pass (which should manually implement `EarlyLintPass`) +/// - Structs for each linting pass +/// - Helper methods to create early and late passes with required lifetimes /// - `const REGISTERED_LINTS` containing all registered lint objects -/// - `const LINT_PASSES` mapping each lint to its corresponding pass #[macro_export] macro_rules! register_lints { - ( $( ($pass_id:ident, ($($lint:expr),+ $(,)?)) ),* $(,)? ) => { - // Declare the structs that will implement the pass trait + // 1. Internal rule for declaring structs and their associated lints. + ( @declare_structs $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?)) ),* $(,)? ) => { $( #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] pub struct $pass_id; impl $pass_id { - pub fn as_lint_pass<'a>() -> Box> { - Box::new(Self::default()) - } + /// Static slice of lints associated with this pass. + const LINTS: &'static [SolLint] = &[$($lint),*]; + + register_lints!(@early_impl $pass_id, $pass_type); + register_lints!(@late_impl $pass_id, $pass_type); + register_lints!(@project_impl $pass_id, $pass_type); } )* + }; - // Expose array constants - pub const REGISTERED_LINTS: &[SolLint] = &[$( $($lint,) + )*]; - pub const LINT_PASSES: &[(SolLint, fn() -> Box>)] = &[ - $( $( ($lint, || Box::new($pass_id::default())), )+ )* + // 2. Internal rule for declaring the const array of ALL lints. + ( @declare_consts $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?)) ),* $(,)? ) => { + pub const REGISTERED_LINTS: &[SolLint] = &[ + $( + $($lint,)* + )* ]; + }; + + // 3. Internal rule for declaring the helper functions. + ( @declare_funcs $( ($pass_id:ident, $pass_type:ident, $lints:tt) ),* $(,)? ) => { + pub fn create_early_lint_passes<'ast>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@early_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() + } + + pub fn create_late_lint_passes<'hir>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@late_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() + } - // Helper function to create lint passes with the required lifetime - pub fn create_lint_passes<'a>() -> Vec<(Box>, SolLint)> - { - vec![ $( $(($pass_id::as_lint_pass(), $lint), )+ )* ] + pub fn create_project_lint_passes<'ast>() -> Vec<(Box>, &'static [SolLint])> { + [ + $( + register_lints!(@project_create $pass_id, $pass_type), + )* + ] + .into_iter() + .flatten() + .collect() } }; + + // --- HELPERS ------------------------------------------------------------ + (@early_impl $_pass_id:ident, late) => {}; + (@early_impl $_pass_id:ident, project) => {}; + (@early_impl $pass_id:ident, $other:ident) => { + pub fn as_early_lint_pass<'a>() -> Box> { + Box::new(Self::default()) + } + }; + + (@late_impl $_pass_id:ident, early) => {}; + (@late_impl $_pass_id:ident, project) => {}; + (@late_impl $pass_id:ident, $other:ident) => { + pub fn as_late_lint_pass<'hir>() -> Box> { + Box::new(Self::default()) + } + }; + + (@project_impl $_pass_id:ident, early) => {}; + (@project_impl $_pass_id:ident, late) => {}; + (@project_impl $_pass_id:ident, both) => {}; + (@project_impl $pass_id:ident, $other:ident) => { + pub fn as_project_lint_pass<'ast>() -> Box> { + Box::new(Self::default()) + } + }; + + (@early_create $_pass_id:ident, late) => { None }; + (@early_create $_pass_id:ident, project) => { None }; + (@early_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_early_lint_pass(), $pass_id::LINTS)) + }; + + (@late_create $_pass_id:ident, early) => { None }; + (@late_create $_pass_id:ident, project) => { None }; + (@late_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_late_lint_pass(), $pass_id::LINTS)) + }; + + (@project_create $_pass_id:ident, early) => { None }; + (@project_create $_pass_id:ident, late) => { None }; + (@project_create $_pass_id:ident, both) => { None }; + (@project_create $pass_id:ident, $_other:ident) => { + Some(($pass_id::as_project_lint_pass(), $pass_id::LINTS)) + }; + + // --- ENTRY POINT --------------------------------------------------------- + ( $($tokens:tt)* ) => { + register_lints! { @declare_structs $($tokens)* } + register_lints! { @declare_consts $($tokens)* } + register_lints! { @declare_funcs $($tokens)* } + }; } diff --git a/crates/lint/src/sol/med/div_mul.rs b/crates/lint/src/sol/med/div_mul.rs index b154971e55434..a9be610d6e4c3 100644 --- a/crates/lint/src/sol/med/div_mul.rs +++ b/crates/lint/src/sol/med/div_mul.rs @@ -1,10 +1,12 @@ use super::DivideBeforeMultiply; use crate::{ - declare_forge_lint, linter::{EarlyLintPass, LintContext}, sol::{Severity, SolLint}, }; -use solar_ast::{BinOp, BinOpKind, Expr, ExprKind}; +use solar::{ + ast::{BinOp, BinOpKind, Expr, ExprKind}, + interface::SpannedOption, +}; declare_forge_lint!( DIVIDE_BEFORE_MULTIPLY, @@ -14,11 +16,11 @@ declare_forge_lint!( ); impl<'ast> EarlyLintPass<'ast> for DivideBeforeMultiply { - fn check_expr(&mut self, ctx: &LintContext<'_>, expr: &'ast Expr<'ast>) { - if let ExprKind::Binary(left_expr, BinOp { kind: BinOpKind::Mul, .. }, _) = &expr.kind { - if contains_division(left_expr) { - ctx.emit(&DIVIDE_BEFORE_MULTIPLY, expr.span); - } + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Binary(left_expr, BinOp { kind: BinOpKind::Mul, .. }, _) = &expr.kind + && contains_division(left_expr) + { + ctx.emit(&DIVIDE_BEFORE_MULTIPLY, expr.span); } } } @@ -27,7 +29,7 @@ fn contains_division<'ast>(expr: &'ast Expr<'ast>) -> bool { match &expr.kind { ExprKind::Binary(_, BinOp { kind: BinOpKind::Div, .. }, _) => true, ExprKind::Tuple(inner_exprs) => inner_exprs.iter().any(|opt_expr| { - if let Some(inner_expr) = opt_expr { + if let SpannedOption::Some(inner_expr) = opt_expr.as_ref() { contains_division(inner_expr) } else { false diff --git a/crates/lint/src/sol/med/incorrect_erc20_interface.rs b/crates/lint/src/sol/med/incorrect_erc20_interface.rs new file mode 100644 index 0000000000000..b2f81e4503200 --- /dev/null +++ b/crates/lint/src/sol/med/incorrect_erc20_interface.rs @@ -0,0 +1,112 @@ +use super::IncorrectERC20Interface; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::sema::hir; + +declare_forge_lint!( + INCORRECT_ERC20_INTERFACE, + Severity::Med, + "incorrect-erc20-interface", + "incorrect ERC20 function interface" +); + +impl<'hir> LateLintPass<'hir> for IncorrectERC20Interface { + fn check_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, + ) { + // Check if the contract is a possible ERC20 by name or inheritance. + let is_erc20 = contract.linearized_bases.iter().any(|base_id| { + let name = hir.contract(*base_id).name.as_str(); + name == "ERC20" || name == "IERC20" + }); + + if !is_erc20 { + return; + } + + // If this contract implements a function from ERC721, we can assume it is an ERC721 token. + // These tokens offer functions which are similar to ERC20, but are not compatible. + let is_erc721 = contract.linearized_bases.iter().any(|base_id| { + let name = hir.contract(*base_id).name.as_str(); + name == "ERC721" || name == "IERC721" + }); + + if is_erc721 { + return; + } + + // Check each function in the contract for incorrect ERC20 signatures. + for item_id in contract.items { + let Some(fid) = item_id.as_function() else { continue }; + let func = hir.function(fid); + + if !func.kind.is_function() { + continue; + } + + let Some(name) = func.name else { continue }; + + if has_incorrect_erc20_signature(hir, name.as_str(), func.parameters, func.returns) { + ctx.emit(&INCORRECT_ERC20_INTERFACE, func.span); + } + } + } +} + +/// Checks if a function signature does not match the expected ERC20 specification. +/// +/// Returns `true` if the function name and parameter types match an ERC20 function but the return +/// types are incorrect. +fn has_incorrect_erc20_signature( + hir: &hir::Hir<'_>, + name: &str, + parameters: &[hir::VariableId], + returns: &[hir::VariableId], +) -> bool { + let is_type = |var_id: hir::VariableId, type_str: &str| { + matches!( + &hir.variable(var_id).ty.kind, + hir::TypeKind::Elementary(ty) if ty.to_abi_str() == type_str + ) + }; + + let params_match = |params: &[hir::VariableId], expected: &[&str]| -> bool { + params.len() == expected.len() + && params.iter().zip(expected).all(|(&id, &ty)| is_type(id, ty)) + }; + + let returns_match = |rets: &[hir::VariableId], expected: &[&str]| -> bool { + rets.len() == expected.len() && rets.iter().zip(expected).all(|(&id, &ty)| is_type(id, ty)) + }; + + match name { + // function transfer(address,uint256) external returns (bool) + "transfer" if params_match(parameters, &["address", "uint256"]) => { + !returns_match(returns, &["bool"]) + } + // function transferFrom(address,address,uint256) external returns (bool) + "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => { + !returns_match(returns, &["bool"]) + } + // function approve(address,uint256) external returns (bool) + "approve" if params_match(parameters, &["address", "uint256"]) => { + !returns_match(returns, &["bool"]) + } + // function allowance(address,address) external view returns (uint256) + "allowance" if params_match(parameters, &["address", "address"]) => { + !returns_match(returns, &["uint256"]) + } + // function balanceOf(address) external view returns (uint256) + "balanceOf" if params_match(parameters, &["address"]) => { + !returns_match(returns, &["uint256"]) + } + // function totalSupply() external view returns (uint256) + "totalSupply" if params_match(parameters, &[]) => !returns_match(returns, &["uint256"]), + _ => false, + } +} diff --git a/crates/lint/src/sol/med/incorrect_erc721_interface.rs b/crates/lint/src/sol/med/incorrect_erc721_interface.rs new file mode 100644 index 0000000000000..c19f4dc9f653f --- /dev/null +++ b/crates/lint/src/sol/med/incorrect_erc721_interface.rs @@ -0,0 +1,121 @@ +use super::IncorrectERC721Interface; +use crate::{ + linter::{LateLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::sema::hir; + +declare_forge_lint!( + INCORRECT_ERC721_INTERFACE, + Severity::Med, + "incorrect-erc721-interface", + "incorrect ERC721 function interface" +); + +impl<'hir> LateLintPass<'hir> for IncorrectERC721Interface { + fn check_contract( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + contract: &'hir hir::Contract<'hir>, + ) { + // Check if the contract is a possible ERC721 by name or inheritance. + let is_erc721 = contract.linearized_bases.iter().any(|base_id| { + let name = hir.contract(*base_id).name.as_str(); + name == "ERC721" || name == "IERC721" + }); + + if !is_erc721 { + return; + } + + // Check each function in the contract for incorrect ERC721 signatures. + for item_id in contract.items { + let Some(fid) = item_id.as_function() else { continue }; + let func = hir.function(fid); + + if !func.kind.is_function() { + continue; + } + + let Some(name) = func.name else { continue }; + + if has_incorrect_erc721_signature(hir, name.as_str(), func.parameters, func.returns) { + ctx.emit(&INCORRECT_ERC721_INTERFACE, func.span); + } + } + } +} + +/// Checks if a function signature does not match the expected ERC721 (or ERC165) specification. +/// +/// Returns `true` if the function name and parameter types match an ERC721 function but the return +/// types are incorrect. +fn has_incorrect_erc721_signature( + hir: &hir::Hir<'_>, + name: &str, + parameters: &[hir::VariableId], + returns: &[hir::VariableId], +) -> bool { + let is_type = |var_id: hir::VariableId, type_str: &str| { + matches!( + &hir.variable(var_id).ty.kind, + hir::TypeKind::Elementary(ty) if ty.to_abi_str() == type_str + ) + }; + + let params_match = |params: &[hir::VariableId], expected: &[&str]| -> bool { + params.len() == expected.len() + && params.iter().zip(expected).all(|(&id, &ty)| is_type(id, ty)) + }; + + let returns_match = |rets: &[hir::VariableId], expected: &[&str]| -> bool { + rets.len() == expected.len() && rets.iter().zip(expected).all(|(&id, &ty)| is_type(id, ty)) + }; + + match name { + // function balanceOf(address) external view returns (uint256) + "balanceOf" if params_match(parameters, &["address"]) => { + !returns_match(returns, &["uint256"]) + } + // function ownerOf(uint256) external view returns (address) + "ownerOf" if params_match(parameters, &["uint256"]) => { + !returns_match(returns, &["address"]) + } + // function safeTransferFrom(address,address,uint256,bytes) external + "safeTransferFrom" + if params_match(parameters, &["address", "address", "uint256", "bytes"]) => + { + !returns_match(returns, &[]) + } + // function safeTransferFrom(address,address,uint256) external + "safeTransferFrom" if params_match(parameters, &["address", "address", "uint256"]) => { + !returns_match(returns, &[]) + } + // function transferFrom(address,address,uint256) external + "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => { + !returns_match(returns, &[]) + } + // function approve(address,uint256) external + "approve" if params_match(parameters, &["address", "uint256"]) => { + !returns_match(returns, &[]) + } + // function setApprovalForAll(address,bool) external + "setApprovalForAll" if params_match(parameters, &["address", "bool"]) => { + !returns_match(returns, &[]) + } + // function getApproved(uint256) external view returns (address) + "getApproved" if params_match(parameters, &["uint256"]) => { + !returns_match(returns, &["address"]) + } + // function isApprovedForAll(address,address) external view returns (bool) + "isApprovedForAll" if params_match(parameters, &["address", "address"]) => { + !returns_match(returns, &["bool"]) + } + // ERC165: function supportsInterface(bytes4) external view returns (bool) + "supportsInterface" if params_match(parameters, &["bytes4"]) => { + !returns_match(returns, &["bool"]) + } + _ => false, + } +} diff --git a/crates/lint/src/sol/med/mod.rs b/crates/lint/src/sol/med/mod.rs index d315ff5405e51..2673ba23d3252 100644 --- a/crates/lint/src/sol/med/mod.rs +++ b/crates/lint/src/sol/med/mod.rs @@ -1,9 +1,24 @@ -use crate::{ - register_lints, - sol::{EarlyLintPass, SolLint}, -}; +use crate::sol::{EarlyLintPass, LateLintPass, SolLint}; mod div_mul; use div_mul::DIVIDE_BEFORE_MULTIPLY; -register_lints!((DivideBeforeMultiply, (DIVIDE_BEFORE_MULTIPLY))); +mod incorrect_erc20_interface; +use incorrect_erc20_interface::INCORRECT_ERC20_INTERFACE; + +mod incorrect_erc721_interface; +use incorrect_erc721_interface::INCORRECT_ERC721_INTERFACE; + +mod tx_origin; +use tx_origin::TX_ORIGIN; + +mod unsafe_typecast; +use unsafe_typecast::UNSAFE_TYPECAST; + +register_lints!( + (DivideBeforeMultiply, early, (DIVIDE_BEFORE_MULTIPLY)), + (IncorrectERC20Interface, late, (INCORRECT_ERC20_INTERFACE)), + (IncorrectERC721Interface, late, (INCORRECT_ERC721_INTERFACE)), + (TxOrigin, early, (TX_ORIGIN)), + (UnsafeTypecast, late, (UNSAFE_TYPECAST)) +); diff --git a/crates/lint/src/sol/med/tx_origin.rs b/crates/lint/src/sol/med/tx_origin.rs new file mode 100644 index 0000000000000..00ff5f939ebfb --- /dev/null +++ b/crates/lint/src/sol/med/tx_origin.rs @@ -0,0 +1,101 @@ +use super::TxOrigin; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{Expr, ExprKind, IndexKind, Stmt, StmtKind}, + interface::SpannedOption, +}; + +declare_forge_lint!( + TX_ORIGIN, + Severity::Med, + "tx-origin", + "`tx.origin` should not be used for authorization" +); + +impl<'ast> EarlyLintPass<'ast> for TxOrigin { + fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) { + match &stmt.kind { + StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::While(cond, _) => { + emit_if_contains_tx_origin(ctx, cond); + } + StmtKind::For { cond: Some(cond), .. } => { + emit_if_contains_tx_origin(ctx, cond); + } + _ => {} + } + } + + fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) { + if let ExprKind::Call(callee, args) = &expr.kind + && is_require_or_assert_call(callee) + && let Some(cond) = args.exprs().next() + { + emit_if_contains_tx_origin(ctx, cond); + } + } +} + +fn emit_if_contains_tx_origin(ctx: &LintContext, expr: &Expr<'_>) { + if contains_tx_origin(expr) { + ctx.emit(&TX_ORIGIN, expr.span); + } +} + +fn contains_tx_origin(expr: &Expr<'_>) -> bool { + if is_tx_origin(expr) { + return true; + } + match &expr.kind { + ExprKind::Unary(_, inner) => contains_tx_origin(inner), + ExprKind::Binary(lhs, _, rhs) => contains_tx_origin(lhs) || contains_tx_origin(rhs), + ExprKind::Index(base, index) => { + contains_tx_origin(base) + || match index { + IndexKind::Index(Some(index)) => contains_tx_origin(index), + IndexKind::Range(start, end) => { + start.as_ref().is_some_and(|start| contains_tx_origin(start)) + || end.as_ref().is_some_and(|end| contains_tx_origin(end)) + } + _ => false, + } + } + ExprKind::Tuple(elems) => elems.iter().any(|elem| { + if let SpannedOption::Some(inner) = elem.as_ref() { + contains_tx_origin(inner) + } else { + false + } + }), + ExprKind::Call(callee, args) => { + contains_tx_origin(callee) || args.exprs().any(contains_tx_origin) + } + ExprKind::Ternary(cond, then_expr, else_expr) => { + contains_tx_origin(cond) + || contains_tx_origin(then_expr) + || contains_tx_origin(else_expr) + } + _ => false, + } +} + +fn is_tx_origin(expr: &Expr<'_>) -> bool { + matches!( + &expr.kind, + ExprKind::Member(base, member) + if member.as_str() == "origin" + && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "tx") + ) +} + +fn is_require_or_assert_call(callee: &Expr<'_>) -> bool { + matches!( + &callee.kind, + ExprKind::Ident(ident) if matches!(ident.as_str(), "require" | "assert") + ) +} diff --git a/crates/lint/src/sol/med/unsafe_typecast.rs b/crates/lint/src/sol/med/unsafe_typecast.rs new file mode 100644 index 0000000000000..860e87b38a2f2 --- /dev/null +++ b/crates/lint/src/sol/med/unsafe_typecast.rs @@ -0,0 +1,172 @@ +use super::UnsafeTypecast; +use crate::{ + linter::{LateLintPass, LintContext, Suggestion}, + sol::{Severity, SolLint}, +}; +use solar::{ + ast::{LitKind, StrKind}, + sema::hir::{self, ElementaryType, ExprKind, ItemId, Res, TypeKind}, +}; + +declare_forge_lint!( + UNSAFE_TYPECAST, + Severity::Med, + "unsafe-typecast", + "typecasts that can truncate values should be checked" +); + +impl<'hir> LateLintPass<'hir> for UnsafeTypecast { + fn check_expr( + &mut self, + ctx: &LintContext, + hir: &'hir hir::Hir<'hir>, + expr: &'hir hir::Expr<'hir>, + ) { + // Check for type cast expressions: Type(value) + if let ExprKind::Call(call, args, _) = &expr.kind + && let ExprKind::Type(hir::Type { kind: TypeKind::Elementary(ty), .. }) = &call.kind + && args.len() == 1 + && let Some(call_arg) = args.exprs().next() + && is_unsafe_typecast_hir(hir, call_arg, ty) + { + ctx.emit_with_suggestion( + &UNSAFE_TYPECAST, + expr.span, + Suggestion::example( + format!( + "// casting to '{abi_ty}' is safe because [explain why]\n// forge-lint: disable-next-line(unsafe-typecast)", + abi_ty = ty.to_abi_str() + )).with_desc("consider disabling this lint if you're certain the cast is safe")); + } + } +} + +/// Determines if a typecast is potentially unsafe (could lose data or precision). +fn is_unsafe_typecast_hir( + hir: &hir::Hir<'_>, + source_expr: &hir::Expr<'_>, + target_type: &hir::ElementaryType, +) -> bool { + let mut source_types = Vec::::new(); + infer_source_types(Some(&mut source_types), hir, source_expr); + + if source_types.is_empty() { + return false; + }; + + source_types.iter().any(|source_ty| is_unsafe_elementary_typecast(source_ty, target_type)) +} + +/// Infers the elementary source type(s) of an expression. +/// +/// This function traverses an expression tree to find the original "source" types. +/// For cast chains, it returns the ultimate source type, not intermediate cast results. +/// For binary operations, it collects types from both sides into the `output` vector. +/// +/// # Returns +/// An `Option` containing the inferred type of the expression if it can be +/// resolved to a single source (like variables, literals, or unary expressions). +/// Returns `None` for expressions complex expressions (like binary operations). +fn infer_source_types( + mut output: Option<&mut Vec>, + hir: &hir::Hir<'_>, + expr: &hir::Expr<'_>, +) -> Option { + let mut track = |ty: ElementaryType| -> Option { + if let Some(output) = output.as_mut() { + output.push(ty); + } + Some(ty) + }; + + match &expr.kind { + // A type cast call: `Type(val)` + ExprKind::Call(call_expr, args, ..) => { + // Check if the called expression is a type, which indicates a cast. + if let ExprKind::Type(hir::Type { kind: TypeKind::Elementary(..), .. }) = + &call_expr.kind + && let Some(inner) = args.exprs().next() + { + // Recurse to find the original (inner-most) source type. + return infer_source_types(output, hir, inner); + } + None + } + + // Identifiers (variables) + ExprKind::Ident(resolutions) => { + if let Some(Res::Item(ItemId::Variable(var_id))) = resolutions.first() { + let variable = hir.variable(*var_id); + if let TypeKind::Elementary(elem_type) = &variable.ty.kind { + return track(*elem_type); + } + } + None + } + + // Handle literal values + ExprKind::Lit(hir::Lit { kind, .. }) => match kind { + LitKind::Str(StrKind::Hex, ..) => track(ElementaryType::Bytes), + LitKind::Str(..) => track(ElementaryType::String), + LitKind::Address(_) => track(ElementaryType::Address(false)), + LitKind::Bool(_) => track(ElementaryType::Bool), + // Unnecessary to check numbers as assigning literal values that cannot fit into a type + // throws a compiler error. Reference: + _ => None, + }, + + // Unary operations: Recurse to find the source type of the inner expression. + ExprKind::Unary(_, inner_expr) => infer_source_types(output, hir, inner_expr), + + // Binary operations + ExprKind::Binary(lhs, _, rhs) => { + if let Some(mut output) = output { + // Recurse on both sides to find and collect all source types. + infer_source_types(Some(&mut output), hir, lhs); + infer_source_types(Some(&mut output), hir, rhs); + } + None + } + + // Complex expressions are not evaluated + _ => None, + } +} + +/// Checks if a type cast from source_type to target_type is unsafe. +const fn is_unsafe_elementary_typecast( + source_type: &ElementaryType, + target_type: &ElementaryType, +) -> bool { + match (source_type, target_type) { + // Numeric downcasts (smaller target size) + (ElementaryType::UInt(source_size), ElementaryType::UInt(target_size)) + | (ElementaryType::Int(source_size), ElementaryType::Int(target_size)) => { + source_size.bits() > target_size.bits() + } + + // Signed to unsigned conversion (potential loss of sign) + (ElementaryType::Int(_), ElementaryType::UInt(_)) => true, + + // Unsigned to signed conversion with same or smaller size + (ElementaryType::UInt(source_size), ElementaryType::Int(target_size)) => { + source_size.bits() >= target_size.bits() + } + + // Fixed bytes to smaller fixed bytes + (ElementaryType::FixedBytes(source_size), ElementaryType::FixedBytes(target_size)) => { + source_size.bytes() > target_size.bytes() + } + + // Dynamic bytes to fixed bytes (potential truncation) + (ElementaryType::Bytes | ElementaryType::String, ElementaryType::FixedBytes(_)) => true, + + // Address to smaller uint (truncation) - address is 160 bits + (ElementaryType::Address(_), ElementaryType::UInt(target_size)) => target_size.bits() < 160, + + // Address to int (sign issues) + (ElementaryType::Address(_), ElementaryType::Int(_)) => true, + + _ => false, + } +} diff --git a/crates/lint/src/sol/mod.rs b/crates/lint/src/sol/mod.rs index 463777d5b194a..7ae073f2ea20b 100644 --- a/crates/lint/src/sol/mod.rs +++ b/crates/lint/src/sol/mod.rs @@ -1,44 +1,87 @@ -use crate::linter::{EarlyLintPass, EarlyLintVisitor, Lint, LintContext, Linter}; -use foundry_compilers::solc::SolcLanguage; -use foundry_config::lint::Severity; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use solar_ast::{visit::Visit, Arena}; -use solar_interface::{ - diagnostics::{self, DiagCtxt, JsonEmitter}, - Session, SourceMap, +use crate::linter::{ + EarlyLintPass, EarlyLintVisitor, LateLintPass, LateLintVisitor, Lint, LintContext, Linter, + LinterConfig, ProjectLintEmitter, ProjectLintPass, ProjectSource, +}; +use foundry_common::{ + comments::{ + Comments, + inline_config::{InlineConfig, InlineConfigItem}, + }, + errors::convert_solar_errors, + sh_warn, +}; +use foundry_compilers::{ProjectPathsConfig, solc::SolcLanguage}; +use foundry_config::{ + DenyLevel, + lint::{LintSpecificConfig, Severity}, +}; +use rayon::prelude::*; +use solar::{ + ast::{self as ast, visit::Visit as _}, + interface::{ + Session, + diagnostics::{self, HumanEmitter, JsonEmitter}, + source_map::SourceFile, + }, + sema::{ + Compiler, Gcx, + hir::{self, Visit as _}, + }, }; use std::{ path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, }; use thiserror::Error; +#[macro_use] pub mod macros; +pub mod codesize; pub mod gas; pub mod high; pub mod info; +pub mod low; pub mod med; +static ALL_REGISTERED_LINTS: LazyLock> = LazyLock::new(|| { + let mut lints = Vec::new(); + lints.extend_from_slice(high::REGISTERED_LINTS); + lints.extend_from_slice(med::REGISTERED_LINTS); + lints.extend_from_slice(low::REGISTERED_LINTS); + lints.extend_from_slice(info::REGISTERED_LINTS); + lints.extend_from_slice(gas::REGISTERED_LINTS); + lints.extend_from_slice(codesize::REGISTERED_LINTS); + lints.into_iter().map(|lint| lint.id()).collect() +}); + +static DEFAULT_LINT_SPECIFIC_CONFIG: LazyLock = + LazyLock::new(LintSpecificConfig::default); + /// Linter implementation to analyze Solidity source code responsible for identifying /// vulnerabilities gas optimizations, and best practices. -#[derive(Debug, Clone, Default)] -pub struct SolidityLinter { +#[derive(Debug)] +pub struct SolidityLinter<'a> { + path_config: ProjectPathsConfig, severity: Option>, lints_included: Option>, lints_excluded: Option>, with_description: bool, with_json_emitter: bool, + // lint-specific configuration + lint_specific: &'a LintSpecificConfig, } -impl SolidityLinter { - pub fn new() -> Self { +impl<'a> SolidityLinter<'a> { + pub fn new(path_config: ProjectPathsConfig) -> Self { Self { + path_config, + with_description: true, severity: None, lints_included: None, lints_excluded: None, - with_description: true, with_json_emitter: false, + lint_specific: &DEFAULT_LINT_SPECIFIC_CONFIG, } } @@ -57,98 +100,346 @@ impl SolidityLinter { self } - pub fn with_description(mut self, with: bool) -> Self { + pub const fn with_description(mut self, with: bool) -> Self { self.with_description = with; self } - pub fn with_json_emitter(mut self, with: bool) -> Self { + pub const fn with_json_emitter(mut self, with: bool) -> Self { self.with_json_emitter = with; self } - fn process_file(&self, sess: &Session, file: &Path) { - let arena = Arena::new(); - - let _ = sess.enter(|| -> Result<(), diagnostics::ErrorGuaranteed> { - // Declare all available passes and lints - let mut passes_and_lints = Vec::new(); - passes_and_lints.extend(gas::create_lint_passes()); - passes_and_lints.extend(high::create_lint_passes()); - passes_and_lints.extend(med::create_lint_passes()); - passes_and_lints.extend(info::create_lint_passes()); - - // Filter based on linter config - let mut passes: Vec>> = passes_and_lints - .into_iter() - .filter_map(|(pass, lint)| { - let matches_severity = match self.severity { - Some(ref target) => target.contains(&lint.severity()), - None => true, - }; - let matches_lints_inc = match self.lints_included { - Some(ref target) => target.contains(&lint), - None => true, - }; - let matches_lints_exc = match self.lints_excluded { - Some(ref target) => target.contains(&lint), - None => false, - }; - - if matches_severity && matches_lints_inc && !matches_lints_exc { - Some(pass) - } else { - None - } - }) - .collect(); + pub const fn with_lint_specific(mut self, lint_specific: &'a LintSpecificConfig) -> Self { + self.lint_specific = lint_specific; + self + } - // Initialize the parser and get the AST - let mut parser = solar_parse::Parser::from_file(sess, &arena, file)?; - let ast = parser.parse_file().map_err(|e| e.emit())?; + const fn config(&'a self, inline: &'a InlineConfig>) -> LinterConfig<'a> { + LinterConfig { inline, lint_specific: self.lint_specific } + } - // Initialize and run the visitor - let ctx = LintContext::new(sess, self.with_description); - let mut visitor = EarlyLintVisitor { ctx: &ctx, passes: &mut passes }; - _ = visitor.visit_source_unit(&ast); + fn include_lint(&self, lint: SolLint) -> bool { + self.severity.as_ref().is_none_or(|sev| sev.contains(&lint.severity())) + && self.lints_included.as_ref().is_none_or(|incl| incl.contains(&lint)) + && !self.lints_excluded.as_ref().is_some_and(|excl| excl.contains(&lint)) + } - Ok(()) - }); + fn process_source_ast<'gcx>( + &self, + sess: &'gcx Session, + ast: &'gcx ast::SourceUnit<'gcx>, + path: &Path, + inline_config: &InlineConfig>, + source_file: Option>, + ) -> Result<(), diagnostics::ErrorGuaranteed> { + // Declare all available passes and lints + let mut passes_and_lints = Vec::new(); + passes_and_lints.extend(high::create_early_lint_passes()); + passes_and_lints.extend(med::create_early_lint_passes()); + passes_and_lints.extend(low::create_early_lint_passes()); + passes_and_lints.extend(info::create_early_lint_passes()); + + // Do not apply 'gas' and 'codesize' severity rules on tests and scripts + if !self.path_config.is_test_or_script(path) { + passes_and_lints.extend(gas::create_early_lint_passes()); + passes_and_lints.extend(codesize::create_early_lint_passes()); + } + + // Filter passes based on linter config + let (mut passes, lints): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included_ids: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + + if !included_ids.is_empty() { + passes.push(pass); + ids.extend(included_ids); + } + + (passes, ids) + }); + + // Initialize and run the early lint visitor + let ctx = LintContext::new( + sess, + self.with_description, + self.with_json_emitter, + self.config(inline_config), + lints, + source_file, + ); + let mut early_visitor = EarlyLintVisitor::new(&ctx, &mut passes); + _ = early_visitor.visit_source_unit(ast); + early_visitor.post_source_unit(ast); + + Ok(()) + } + + /// Runs all enabled project-wide lint passes against the given input sources. + fn process_project<'gcx>(&self, gcx: Gcx<'gcx>, input: &[PathBuf]) { + // Gather enabled project passes from every severity bucket. + let mut passes_and_lints: Vec<(Box>, &'static [SolLint])> = + Vec::new(); + passes_and_lints.extend(high::create_project_lint_passes()); + passes_and_lints.extend(med::create_project_lint_passes()); + passes_and_lints.extend(low::create_project_lint_passes()); + passes_and_lints.extend(info::create_project_lint_passes()); + passes_and_lints.extend(gas::create_project_lint_passes()); + passes_and_lints.extend(codesize::create_project_lint_passes()); + + let (mut passes, lint_ids): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + if !included.is_empty() { + passes.push(pass); + ids.extend(included); + } + (passes, ids) + }); + + if passes.is_empty() { + return; + } + + // Pre-load every input source with its inline config, in input order. + let sources: Vec> = input + .iter() + .filter_map(|path| { + let path = self.path_config.root.join(path); + let (_, source) = gcx.get_ast_source(&path)?; + let ast = source.ast.as_ref()?; + let comments = + Comments::new(&source.file, gcx.sess.source_map(), false, false, None); + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + Some(ProjectSource { path, file: source.file.clone(), ast, inline_config }) + }) + .collect(); + + let emitter = ProjectLintEmitter::new( + gcx.sess, + self.with_description, + self.with_json_emitter, + self.lint_specific, + lint_ids, + ); + for pass in &mut passes { + pass.check_project(&emitter, &sources); + } + } + + fn process_source_hir<'gcx>( + &self, + gcx: Gcx<'gcx>, + source_id: hir::SourceId, + path: &Path, + inline_config: &InlineConfig>, + source_file: Option>, + ) -> Result<(), diagnostics::ErrorGuaranteed> { + // Declare all available passes and lints + let mut passes_and_lints = Vec::new(); + passes_and_lints.extend(high::create_late_lint_passes()); + passes_and_lints.extend(med::create_late_lint_passes()); + passes_and_lints.extend(low::create_late_lint_passes()); + passes_and_lints.extend(info::create_late_lint_passes()); + + // Do not apply 'gas' and 'codesize' severity rules on tests and scripts + if !self.path_config.is_test_or_script(path) { + passes_and_lints.extend(gas::create_late_lint_passes()); + passes_and_lints.extend(codesize::create_late_lint_passes()); + } + + // Filter passes based on config + let (mut passes, lints): (Vec>>, Vec<_>) = passes_and_lints + .into_iter() + .fold((Vec::new(), Vec::new()), |(mut passes, mut ids), (pass, lints)| { + let included_ids: Vec<_> = lints + .iter() + .filter_map(|lint| self.include_lint(*lint).then_some(lint.id)) + .collect(); + + if !included_ids.is_empty() { + passes.push(pass); + ids.extend(included_ids); + } + + (passes, ids) + }); + + // Run late lint visitor + let ctx = LintContext::new( + gcx.sess, + self.with_description, + self.with_json_emitter, + self.config(inline_config), + lints, + source_file, + ); + let mut late_visitor = LateLintVisitor::new(&ctx, &mut passes, &gcx.hir); + + // Visit this specific source + let _ = late_visitor.visit_nested_source(source_id); + + Ok(()) } } -impl Linter for SolidityLinter { +impl<'a> Linter for SolidityLinter<'a> { type Language = SolcLanguage; type Lint = SolLint; - fn lint(&self, input: &[PathBuf]) { - let mut builder = Session::builder(); + fn lint( + &self, + input: &[PathBuf], + deny: DenyLevel, + compiler: &mut Compiler, + ) -> eyre::Result<()> { + convert_solar_errors(compiler.dcx())?; - // Build session based on the linter config - if self.with_json_emitter { - let map = Arc::::default(); - let json_emitter = JsonEmitter::new(Box::new(std::io::stderr()), map.clone()) - .rustc_like(true) - .ui_testing(false); + // Cache diagnostic count before linting to isolate from the build phase. + let warn_count_before = compiler.dcx().warn_count(); + let note_count_before = compiler.dcx().note_count(); - builder = builder.dcx(DiagCtxt::new(Box::new(json_emitter))).source_map(map); + let ui_testing = std::env::var_os("FOUNDRY_LINT_UI_TESTING").is_some(); + + let sm = compiler.sess().clone_source_map(); + let prev_emitter = compiler.dcx().set_emitter(if self.with_json_emitter { + let writer = Box::new(std::io::BufWriter::new(std::io::stderr())); + let json_emitter = JsonEmitter::new(writer, sm).rustc_like(true).ui_testing(ui_testing); + Box::new(json_emitter) } else { - builder = builder.with_stderr_emitter(); - }; + Box::new(HumanEmitter::stderr(Default::default()).source_map(Some(sm))) + }); + let sess = compiler.sess_mut(); + sess.dcx.set_flags_mut(|f| f.track_diagnostics = false); + if ui_testing { + sess.opts.unstable.ui_testing = true; + sess.reconfigure(); + } + + compiler.enter_mut(|compiler| -> eyre::Result<()> { + if compiler.gcx().stage() < Some(solar::config::CompilerStage::Lowering) { + let _ = compiler.lower_asts(); + } - // Create a single session for all files - let mut sess = builder.build(); - sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); + let gcx = compiler.gcx(); - // Process the files in parallel - sess.enter_parallel(|| { - input.into_par_iter().for_each(|file| { - self.process_file(&sess, file); + input.par_iter().for_each(|path| { + let path = &self.path_config.root.join(path); + let Some((_, ast_source)) = gcx.get_ast_source(path) else { + // issue a warning rather than panicking, in case that some (but not all) of the + // input files have old solidity versions which are not supported by solar. + _ = sh_warn!("AST source not found for {}", path.display()); + return; + }; + let Some(ast) = &ast_source.ast else { + panic!("AST missing for {}", path.display()); + }; + + // Parse inline config. + let file = &ast_source.file; + let comments = Comments::new(file, gcx.sess.source_map(), false, false, None); + let inline_config = parse_inline_config(gcx.sess, &comments, ast); + + // Early lints. + let _ = self.process_source_ast( + gcx.sess, + ast, + path, + &inline_config, + Some(file.clone()), + ); + + // Late lints. + let Some((hir_source_id, _)) = gcx.get_hir_source(path) else { + panic!("HIR source not found for {}", path.display()); + }; + let _ = self.process_source_hir( + gcx, + hir_source_id, + path, + &inline_config, + Some(file.clone()), + ); }); - }); + + // Project-wide lints, run once after all per-file passes. + self.process_project(gcx, input); + + convert_solar_errors(compiler.dcx()) + })?; + + let sess = compiler.sess_mut(); + sess.dcx.set_emitter(prev_emitter); + if ui_testing { + sess.opts.unstable.ui_testing = false; + sess.reconfigure(); + } + + let lint_warn_count = compiler.dcx().warn_count().saturating_sub(warn_count_before); + let lint_note_count = compiler.dcx().note_count().saturating_sub(note_count_before); + + const MSG: &str = "aborting due to "; + match (deny, lint_warn_count, lint_note_count) { + // Deny warnings. + (DenyLevel::Warnings, w, n) if w > 0 => { + if n > 0 { + Err(eyre::eyre!("{MSG}{w} linter warning(s); {n} note(s) were also emitted\n")) + } else { + Err(eyre::eyre!("{MSG}{w} linter warning(s)\n")) + } + } + + // Deny any diagnostic. + (DenyLevel::Notes, w, n) if w > 0 || n > 0 => match (w, n) { + (w, n) if w > 0 && n > 0 => { + Err(eyre::eyre!("{MSG}{w} linter warning(s) and {n} note(s)\n")) + } + (w, 0) => Err(eyre::eyre!("{MSG}{w} linter warning(s)\n")), + (0, n) => Err(eyre::eyre!("{MSG}{n} linter note(s)\n")), + _ => unreachable!(), + }, + + // Otherwise, succeed. + _ => Ok(()), + } } } +fn parse_inline_config<'ast>( + sess: &Session, + comments: &Comments, + ast: &'ast ast::SourceUnit<'ast>, +) -> InlineConfig> { + let items = comments.iter().filter_map(|comment| { + let mut item = comment.lines.first()?.as_str(); + if let Some(prefix) = comment.prefix() { + item = item.strip_prefix(prefix).unwrap_or(item); + } + if let Some(suffix) = comment.suffix() { + item = item.strip_suffix(suffix).unwrap_or(item); + } + let item = item.trim_start().strip_prefix("forge-lint:")?.trim(); + let span = comment.span; + match InlineConfigItem::parse(item, &ALL_REGISTERED_LINTS) { + Ok(item) => Some((span, item)), + Err(e) => { + sess.dcx.warn(e.to_string()).span(span).emit(); + None + } + } + }); + + InlineConfig::from_ast(items, ast, sess.source_map()) +} + #[derive(Error, Debug)] pub enum SolLintError { #[error("Unknown lint ID: {0}")] @@ -194,6 +485,12 @@ impl<'a> TryFrom<&'a str> for SolLint { } } + for &lint in low::REGISTERED_LINTS { + if lint.id() == value { + return Ok(lint); + } + } + for &lint in info::REGISTERED_LINTS { if lint.id() == value { return Ok(lint); @@ -206,6 +503,84 @@ impl<'a> TryFrom<&'a str> for SolLint { } } + for &lint in codesize::REGISTERED_LINTS { + if lint.id() == value { + return Ok(lint); + } + } + Err(SolLintError::InvalidId(value.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Every registered lint must have a markdown documentation file at + /// `crates/lint/docs/.md`. This test enforces that contract so that the `help` URL + /// generated by `declare_forge_lint!` always resolves to real documentation. + /// + /// When this test fails, add a new file at `crates/lint/docs/.md` describing the + /// lint. See [`crates/lint/docs/_template.md`](../../docs/_template.md) for the expected + /// structure. + #[test] + fn registered_lints_have_docs() { + let docs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs"); + assert!(docs_dir.is_dir(), "missing docs directory at {}", docs_dir.display()); + + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + let mut missing: Vec<&'static str> = Vec::new(); + let mut empty: Vec<&'static str> = Vec::new(); + for lint in &all_lints { + let path = docs_dir.join(format!("{}.md", lint.id())); + match std::fs::read_to_string(&path) { + Ok(content) => { + // Basic sanity: file should be non-trivial and reference the lint id. + if content.trim().is_empty() || !content.contains(lint.id()) { + empty.push(lint.id()); + } + } + Err(_) => missing.push(lint.id()), + } + } + + assert!( + missing.is_empty(), + "the following registered lints are missing a docs file at \ + `crates/lint/docs/.md`: {missing:?}\n\ + See `crates/lint/docs/_template.md` for the expected structure." + ); + assert!( + empty.is_empty(), + "the following lint docs files are empty or do not reference the lint id: {empty:?}" + ); + } + + /// The auto-generated `help` URL must point at the canonical Foundry docs site so that the + /// link printed in diagnostics resolves correctly. + #[test] + fn registered_lints_have_canonical_help_url() { + let all_lints: Vec<&'static SolLint> = high::REGISTERED_LINTS + .iter() + .chain(med::REGISTERED_LINTS) + .chain(low::REGISTERED_LINTS) + .chain(info::REGISTERED_LINTS) + .chain(gas::REGISTERED_LINTS) + .chain(codesize::REGISTERED_LINTS) + .collect(); + + for lint in all_lints { + let expected = format!("https://getfoundry.sh/forge/linting/{}", lint.id()); + assert_eq!(lint.help(), expected, "lint `{}` has a non-canonical help URL", lint.id()); + } + } +} diff --git a/crates/lint/testdata/.gitignore b/crates/lint/testdata/.gitignore new file mode 100644 index 0000000000000..d8a1d071d339d --- /dev/null +++ b/crates/lint/testdata/.gitignore @@ -0,0 +1,2 @@ +cache/ +out/ diff --git a/crates/lint/testdata/BlockTimestamp.sol b/crates/lint/testdata/BlockTimestamp.sol new file mode 100644 index 0000000000000..f5382f7a2988b --- /dev/null +++ b/crates/lint/testdata/BlockTimestamp.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract BlockTimestamp { + uint256 public deadline; + + // SHOULD FAIL: + + function directComparison() public view returns (bool) { + return block.timestamp > deadline; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function comparisonEq() public view returns (bool) { + return block.timestamp == 0; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function comparisonNe() public view returns (bool) { + return block.timestamp != 0; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function comparisonLe() public view returns (bool) { + return block.timestamp <= deadline; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function comparisonGe() public view returns (bool) { + return block.timestamp >= deadline; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function comparisonLt() public view returns (bool) { + return block.timestamp < deadline; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function timestampOnRight() public view returns (bool) { + return deadline > block.timestamp; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function timestampInArithmetic() public view returns (bool) { + return block.timestamp + 1 > deadline; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function timestampInComplexExpr() public view returns (bool) { + return (block.timestamp / 3600) == 0; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function inRequire() public view { + require(block.timestamp > deadline); //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + function inIfCondition() public view returns (uint256) { + if (block.timestamp > deadline) { //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + return 1; + } + return 0; + } + + function timestampInCallArg() public view returns (bool) { + return foo(block.timestamp) > 0; //~WARN: usage of `block.timestamp` in a comparison may be manipulated by validators + } + + // SHOULD PASS: + + function assignOnly() public view returns (uint256) { + uint256 t = block.timestamp; + return t; + } + + function emitOnly() public { + emit Timestamp(block.timestamp); + } + + function blockNumber() public view returns (bool) { + return block.number > 100; + } + + function arithmetic() public view returns (uint256) { + return block.timestamp + 100; + } + + function foo(uint256 x) internal pure returns (uint256) { + return x; + } + + event Timestamp(uint256 ts); +} diff --git a/crates/lint/testdata/BlockTimestamp.stderr b/crates/lint/testdata/BlockTimestamp.stderr new file mode 100644 index 0000000000000..62ab588ae7340 --- /dev/null +++ b/crates/lint/testdata/BlockTimestamp.stderr @@ -0,0 +1,96 @@ +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp > deadline; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp == 0; + │ ━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp != 0; + │ ━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp <= deadline; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp >= deadline; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp < deadline; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return deadline > block.timestamp; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return block.timestamp + 1 > deadline; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return (block.timestamp / 3600) == 0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ require(block.timestamp > deadline); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ if (block.timestamp > deadline) { + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + +warning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators + ╭▸ ROOT/testdata/BlockTimestamp.sol:LL:CC + │ +LL │ return foo(block.timestamp) > 0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/block-timestamp + diff --git a/crates/lint/testdata/BooleanCst.sol b/crates/lint/testdata/BooleanCst.sol new file mode 100644 index 0000000000000..2b7cdc32f6702 --- /dev/null +++ b/crates/lint/testdata/BooleanCst.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract BooleanCst { + function check(bool flag) public pure returns (bool) { + if (false) {} //~WARN: misuse of a boolean constant + if (flag || true) {} //~WARN: misuse of a boolean constant + if (flag ? true : false) {} + //~^WARN: misuse of a boolean constant + //~|WARN: misuse of a boolean constant + while (true) { + break; + } + + bool assigned = true; + return assigned && false; //~WARN: misuse of a boolean constant + } + + function allowedBareConstants(bool flag) public pure returns (bool) { + takesBool(true); + return true; + } + + function takesBool(bool value) internal pure {} +} diff --git a/crates/lint/testdata/BooleanCst.stderr b/crates/lint/testdata/BooleanCst.stderr new file mode 100644 index 0000000000000..75fdb0b57cea7 --- /dev/null +++ b/crates/lint/testdata/BooleanCst.stderr @@ -0,0 +1,40 @@ +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (false) {} + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag || true) {} + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag ? true : false) {} + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ if (flag ? true : false) {} + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + +warning[boolean-cst]: misuse of a boolean constant + ╭▸ ROOT/testdata/BooleanCst.sol:LL:CC + │ +LL │ return assigned && false; + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-cst + diff --git a/crates/lint/testdata/BooleanEqual.sol b/crates/lint/testdata/BooleanEqual.sol new file mode 100644 index 0000000000000..30171e9c797ac --- /dev/null +++ b/crates/lint/testdata/BooleanEqual.sol @@ -0,0 +1,24 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract BooleanEqual { + function check(bool enabled, bool paused, bool ready, bool done) public pure { + if (enabled == true) {} //~NOTE: boolean comparisons to constants should be simplified + if (paused == false) {} //~NOTE: boolean comparisons to constants should be simplified + if (true != ready) {} //~NOTE: boolean comparisons to constants should be simplified + while (done != false) { //~NOTE: boolean comparisons to constants should be simplified + break; + } + for (; enabled == true && paused != false;) { + //~^NOTE: boolean comparisons to constants should be simplified + //~|NOTE: boolean comparisons to constants should be simplified + break; + } + } + + function returnedComparison(bool enabled) public pure returns (bool) { + return enabled == true; //~NOTE: boolean comparisons to constants should be simplified + } +} diff --git a/crates/lint/testdata/BooleanEqual.stderr b/crates/lint/testdata/BooleanEqual.stderr new file mode 100644 index 0000000000000..590a85b806fcf --- /dev/null +++ b/crates/lint/testdata/BooleanEqual.stderr @@ -0,0 +1,56 @@ +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (enabled == true) {} + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (paused == false) {} + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `!paused` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ if (true != ready) {} + │ ━━━━━━━━━━━━━ help: consider simplifying to: `!ready` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ while (done != false) { + │ ━━━━━━━━━━━━━ help: consider simplifying to: `done` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ for (; enabled == true && paused != false;) { + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ for (; enabled == true && paused != false;) { + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `paused` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + +note[boolean-equal]: boolean comparisons to constants should be simplified + ╭▸ ROOT/testdata/BooleanEqual.sol:LL:CC + │ +LL │ return enabled == true; + │ ━━━━━━━━━━━━━━━ help: consider simplifying to: `enabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/boolean-equal + diff --git a/crates/lint/testdata/CouldBeImmutable.sol b/crates/lint/testdata/CouldBeImmutable.sol new file mode 100644 index 0000000000000..cd7e9aea13d01 --- /dev/null +++ b/crates/lint/testdata/CouldBeImmutable.sol @@ -0,0 +1,85 @@ +//@compile-flags: --only-lint could-be-immutable + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +contract CouldBeImmutable { + uint256 public constant MAX = 10; + uint256 public immutable ALREADY_IMMUTABLE; + + address public owner; + address public deployer = msg.sender; + uint256 private configured; + bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); + CouldBeImmutable private peer; + + uint256 private mutableValue; + uint256 private assignedInInternal; + uint256 private compileTimeConstant = 1 + 2; + string private dynamicValue; + + constructor(uint256 configured_, CouldBeImmutable peer_, string memory value) { + ALREADY_IMMUTABLE = configured_; + owner = msg.sender; + configured = configured_; + peer = peer_; + mutableValue = 1; + setInternal(1); + dynamicValue = value; + } + + function setMutableValue(uint256 newValue) public { + mutableValue = newValue; + } + + function setInternal(uint256 newValue) internal { + assignedInInternal = newValue; + } +} + +contract BaseImmutableCandidate { + uint256 internal inheritedBase; +} + +contract DerivedImmutableCandidate is BaseImmutableCandidate { + constructor(uint256 value) { + inheritedBase = value; + } +} + +contract BaseConstructorImmutableCandidate { + uint256 internal baseConfigured; + + constructor(uint256 value) { + baseConfigured = value; + } +} + +contract DerivedConstructorImmutableCandidate is BaseConstructorImmutableCandidate { + constructor(uint256 value) BaseConstructorImmutableCandidate(value) {} +} + +contract ModifierBodyWrite { + uint256 private fromModifier; + + modifier initializesState() { + fromModifier = 1; + _; + } + + constructor() initializesState() {} +} + +contract AssemblyWrite { + uint256 private fromAssembly; + + constructor() { + fromAssembly = 1; + } + + function mutate() public { + assembly { + sstore(0, 2) + } + } +} diff --git a/crates/lint/testdata/CouldBeImmutable.stderr b/crates/lint/testdata/CouldBeImmutable.stderr new file mode 100644 index 0000000000000..170682baf89d3 --- /dev/null +++ b/crates/lint/testdata/CouldBeImmutable.stderr @@ -0,0 +1,56 @@ +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ address public owner; + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ address public deployer = msg.sender; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 private configured; + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ bytes32 internal salt = keccak256(abi.encodePacked(block.timestamp)); + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ CouldBeImmutable private peer; + │ ━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 internal inheritedBase; + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/CouldBeImmutable.sol:LL:CC + │ +LL │ uint256 internal baseConfigured; + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + diff --git a/crates/lint/testdata/CustomErrors.sol b/crates/lint/testdata/CustomErrors.sol new file mode 100644 index 0000000000000..aea8e00b58826 --- /dev/null +++ b/crates/lint/testdata/CustomErrors.sol @@ -0,0 +1,42 @@ +//@compile-flags: --severity gas + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +error CustomError(); +error CustomErrorWithArg(uint256 value); +error CustomErrorWithNamedArgs(uint256 x, string message); + +contract CustomErrors { + // Require examples + function requireWithString(uint256 a, uint256 b) public pure { + require(a > 0, "Value must be greater than zero"); //~NOTE: prefer using custom errors on revert and require calls + require(a >= 0 && a <= 100 || b == 50, "Complex condition should be linted"); //~NOTE: prefer using custom errors on revert and require calls + } + + // Revert examples + function revertWithString() public pure { + revert("Something went wrong"); //~NOTE: prefer using custom errors on revert and require calls + revert(""); //~NOTE: prefer using custom errors on revert and require calls + revert(); //~NOTE: prefer using custom errors on revert and require calls + } + + // Custom error examples + function customErrors(uint256 value) public pure { + require(value > 0, CustomError()); + require(value < 100, CustomErrorWithArg(value)); + require(value > 0); + revert CustomError(); + revert CustomErrorWithArg(value); + revert CustomErrorWithNamedArgs({x: value, message: "test"}); + } + + // Test inline disable + function testDisableShouldNotLint() public pure { + // forge-lint: disable-next-line(custom-errors) + require(true, "This should not lint"); + // forge-lint: disable-next-line(custom-errors) + revert("This should not lint"); + } +} + diff --git a/crates/lint/testdata/CustomErrors.stderr b/crates/lint/testdata/CustomErrors.stderr new file mode 100644 index 0000000000000..286a649aee269 --- /dev/null +++ b/crates/lint/testdata/CustomErrors.stderr @@ -0,0 +1,40 @@ +note[custom-errors]: prefer using custom errors on revert and require calls + ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC + │ +LL │ require(a > 0, "Value must be greater than zero"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/custom-errors + +note[custom-errors]: prefer using custom errors on revert and require calls + ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC + │ +LL │ … require(a >= 0 && a <= 100 || b == 50, "Complex condition should be linted"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/custom-errors + +note[custom-errors]: prefer using custom errors on revert and require calls + ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC + │ +LL │ revert("Something went wrong"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/custom-errors + +note[custom-errors]: prefer using custom errors on revert and require calls + ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC + │ +LL │ revert(""); + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/custom-errors + +note[custom-errors]: prefer using custom errors on revert and require calls + ╭▸ ROOT/testdata/CustomErrors.sol:LL:CC + │ +LL │ revert(); + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/custom-errors + diff --git a/crates/lint/testdata/DivideBeforeMultiply.sol b/crates/lint/testdata/DivideBeforeMultiply.sol index 694558bfca363..4910d43641a8d 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.sol +++ b/crates/lint/testdata/DivideBeforeMultiply.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + contract DivideBeforeMultiply { function arithmetic() public { (1 / 2) * 3; //~WARN: multiplication should occur before division to avoid loss of precision diff --git a/crates/lint/testdata/DivideBeforeMultiply.stderr b/crates/lint/testdata/DivideBeforeMultiply.stderr index 1c98f4b13d194..95022f65db874 100644 --- a/crates/lint/testdata/DivideBeforeMultiply.stderr +++ b/crates/lint/testdata/DivideBeforeMultiply.stderr @@ -1,48 +1,48 @@ warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -3 | (1 / 2) * 3; - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ (1 / 2) * 3; + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -5 | ((1 / 2) * 3) * 4; - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ ((1 / 2) * 3) * 4; + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -6 | ((1 * 2) / 3) * 4; - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ ((1 * 2) / 3) * 4; + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -7 | (1 / 2 / 3) * 4; - | --------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ (1 / 2 / 3) * 4; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -8 | (1 / (2 + 3)) * 4; - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ (1 / (2 + 3)) * 4; + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision - --> ROOT/testdata/DivideBeforeMultiply.sol:LL:CC - | -15 | 1 / ((2 / 3) * 3); - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + ╭▸ ROOT/testdata/DivideBeforeMultiply.sol:LL:CC + │ +LL │ 1 / ((2 / 3) * 3); + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply diff --git a/crates/lint/testdata/Imports.sol b/crates/lint/testdata/Imports.sol new file mode 100644 index 0000000000000..85e906b0a2774 --- /dev/null +++ b/crates/lint/testdata/Imports.sol @@ -0,0 +1,80 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import { + symbol0 as mySymbol, + symbol1 as myOtherSymbol, + symbol2 as notUsed, //~NOTE: unused imports should be removed + symbol3, + symbol4, + symbol5, + docSymbol, + docSymbol2, + docSymbolWrongTag, //~NOTE: unused imports should be removed + eventSymbol, + BaseContract, + symbolNotUsed, //~NOTE: unused imports should be removed + IContract, + IContractNotUsed //~NOTE: unused imports should be removed +} from "./auxiliary/ImportsFile.sol"; + +// forge-lint: disable-next-item +import { + symbolNotUsed2 +} from "./auxiliary/ImportsFile.sol"; + +// in this case, disabling the following line doesn't do anything +// forge-lint: disable-next-line +import { + symbolNotUsed3 //~NOTE: unused imports should be removed +} from "./auxiliary/ImportsFile.sol"; + +import { + CONSTANT_0, + CONSTANT_1 //~NOTE: unused imports should be removed +} from "./auxiliary/ImportsConstants.sol"; + +import { + MyType, + MyOtherType, + YetAnotherType //~NOTE: unused imports should be removed +} from "./auxiliary/ImportsTypes.sol"; + +import "./auxiliary/ImportsSomeFile.sol"; //~NOTE: use named imports '{A, B}' or alias 'import ".." as X' +import "./auxiliary/ImportsAnotherFile.sol"; //~NOTE: use named imports '{A, B}' or alias 'import ".." as X' + +import "./auxiliary/ImportsSomeFile2.sol" as SomeFile2; +import "./auxiliary/ImportsAnotherFile2.sol" as AnotherFile2; //~NOTE: unused imports should be removed + +import * as Utils from "./auxiliary/ImportsUtils.sol"; +import * as OtherUtils from "./auxiliary/ImportsUtils2.sol"; //~NOTE: unused imports should be removed + + +abstract contract UnusedImport is IContract, BaseContract { + using mySymbol for address; + + uint256 constant MY_CONSTANT = CONSTANT_0; + + struct FooBar { + symbol3 foo; + myOtherSymbol bar; + } + + /// @dev docSymbolWrongTag + SomeFile.Baz public myStruct; + SomeFile2.Baz public myStruct2; + symbol4 public myVar; + + function foo(uint256 a, symbol5 b) external override(BaseContract) returns (uint256) { + uint256 c = Utils.calculate(a, symbol5.unwrap(b)); + emit eventSymbol.foo(c); + return c; + } + + function convert(address addr) public pure returns (MyOtherType) { + MyType a = MyType.wrap(123); + return MyOtherType.wrap(MyType.unwrap(a)); + } +} diff --git a/crates/lint/testdata/Imports.stderr b/crates/lint/testdata/Imports.stderr new file mode 100644 index 0000000000000..1031f4f6f8ca0 --- /dev/null +++ b/crates/lint/testdata/Imports.stderr @@ -0,0 +1,104 @@ +note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." as X' + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ import "./auxiliary/ImportsSomeFile.sol"; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import + +note[unaliased-plain-import]: use named imports '{A, B}' or alias 'import ".." as X' + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ import "./auxiliary/ImportsAnotherFile.sol"; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unaliased-plain-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ symbol2 as notUsed, + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ docSymbol, + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ docSymbol2, + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ docSymbolWrongTag, + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ symbolNotUsed, + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ IContractNotUsed + │ ━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ symbolNotUsed3 + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ CONSTANT_1 + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ YetAnotherType + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ import "./auxiliary/ImportsAnotherFile2.sol" as AnotherFile2; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + +note[unused-import]: unused imports should be removed + ╭▸ ROOT/testdata/Imports.sol:LL:CC + │ +LL │ import * as OtherUtils from "./auxiliary/ImportsUtils2.sol"; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-import + diff --git a/crates/lint/testdata/IncorrectERC20Interface.sol b/crates/lint/testdata/IncorrectERC20Interface.sol new file mode 100644 index 0000000000000..bfc571ec0a474 --- /dev/null +++ b/crates/lint/testdata/IncorrectERC20Interface.sol @@ -0,0 +1,44 @@ +//@compile-flags: --severity high med low info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface IERC20 {} + +// SHOULD FAIL: Interface named ERC20 with incorrect function signatures +interface ERC20 { + function transfer(address to, uint256 value) external returns (uint256); //~WARN: incorrect ERC20 function interface + function approve(address spender, uint256 value) external returns (uint256); //~WARN: incorrect ERC20 function interface +} + +// SHOULD FAIL: Interface inheriting from IERC20 with incorrect function signatures +interface IERC20Incorrect is IERC20 { + function transfer(address to, uint256 value) external returns (uint256); //~WARN: incorrect ERC20 function interface + function transferFrom(address from, address to, uint256 value) external returns (uint256); //~WARN: incorrect ERC20 function interface + function approve(address spender, uint256 value) external returns (uint256); //~WARN: incorrect ERC20 function interface + function allowance(address owner, address spender) external view returns (bool); //~WARN: incorrect ERC20 function interface + function balanceOf(address account) external view returns (bool); //~WARN: incorrect ERC20 function interface + function totalSupply() external view returns (bool); //~WARN: incorrect ERC20 function interface +} + +// SHOULD PASS: Correct ERC20 interface inheriting from IERC20 +interface IERC20Correct is IERC20 { + function transfer(address to, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function totalSupply() external view returns (uint256); +} + +// SHOULD PASS: Interface named IERC20 with correct function signatures +interface IERC20NamedCorrect { + function transfer(address to, uint256 value) external returns (bool); + function balanceOf(address account) external view returns (uint256); +} + +// SHOULD PASS: Contract that is NOT named ERC20 and does not inherit from one +interface INotERC20 { + function transfer(address to, uint256 value) external returns (uint256); + function balanceOf(address account) external view returns (bool); +} diff --git a/crates/lint/testdata/IncorrectERC20Interface.stderr b/crates/lint/testdata/IncorrectERC20Interface.stderr new file mode 100644 index 0000000000000..33e2f1ca27d22 --- /dev/null +++ b/crates/lint/testdata/IncorrectERC20Interface.stderr @@ -0,0 +1,120 @@ +note[interface-naming]: interface names should be prefixed with 'I' + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface ERC20 { + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/interface-naming + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface IERC20 {} + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface ERC20 { + │ ━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface IERC20Incorrect is IERC20 { + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface IERC20Correct is IERC20 { + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface IERC20NamedCorrect { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ interface INotERC20 { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function transfer(address to, uint256 value) external returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function approve(address spender, uint256 value) external returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function transfer(address to, uint256 value) external returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function transferFrom(address from, address to, uint256 value) external returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function approve(address spender, uint256 value) external returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function allowance(address owner, address spender) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function balanceOf(address account) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + +warning[incorrect-erc20-interface]: incorrect ERC20 function interface + ╭▸ ROOT/testdata/IncorrectERC20Interface.sol:LL:CC + │ +LL │ function totalSupply() external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc20-interface + diff --git a/crates/lint/testdata/IncorrectERC721Interface.sol b/crates/lint/testdata/IncorrectERC721Interface.sol new file mode 100644 index 0000000000000..431b1d5efdc96 --- /dev/null +++ b/crates/lint/testdata/IncorrectERC721Interface.sol @@ -0,0 +1,52 @@ +//@compile-flags: --severity high med low info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface IERC721 {} + +// SHOULD FAIL: Interface named ERC721 with incorrect function signatures +interface ERC721 { + function balanceOf(address owner) external view returns (bool); //~WARN: incorrect ERC721 function interface + function ownerOf(uint256 tokenId) external view returns (bool); //~WARN: incorrect ERC721 function interface +} + +// SHOULD FAIL: Interface inheriting from IERC721 with incorrect function signatures +interface IERC721Incorrect is IERC721 { + function balanceOf(address owner) external view returns (bool); //~WARN: incorrect ERC721 function interface + function ownerOf(uint256 tokenId) external view returns (bool); //~WARN: incorrect ERC721 function interface + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external returns (bool); //~WARN: incorrect ERC721 function interface + function safeTransferFrom(address from, address to, uint256 tokenId) external returns (bool); //~WARN: incorrect ERC721 function interface + function transferFrom(address from, address to, uint256 tokenId) external returns (bool); //~WARN: incorrect ERC721 function interface + function approve(address to, uint256 tokenId) external returns (bool); //~WARN: incorrect ERC721 function interface + function setApprovalForAll(address operator, bool approved) external returns (bool); //~WARN: incorrect ERC721 function interface + function getApproved(uint256 tokenId) external view returns (bool); //~WARN: incorrect ERC721 function interface + function isApprovedForAll(address owner, address operator) external view returns (address); //~WARN: incorrect ERC721 function interface + function supportsInterface(bytes4 interfaceId) external view returns (uint256); //~WARN: incorrect ERC721 function interface +} + +// SHOULD PASS: Correct ERC721 interface inheriting from IERC721 +interface IERC721Correct is IERC721 { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function transferFrom(address from, address to, uint256 tokenId) external; + function approve(address to, uint256 tokenId) external; + function setApprovalForAll(address operator, bool approved) external; + function getApproved(uint256 tokenId) external view returns (address); + function isApprovedForAll(address owner, address operator) external view returns (bool); + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// SHOULD PASS: Interface named IERC721 with correct function signatures +interface IERC721NamedCorrect { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); +} + +// SHOULD PASS: Contract that is NOT named ERC721 and does not inherit from one +interface INotERC721 { + function balanceOf(address owner) external view returns (bool); + function ownerOf(uint256 tokenId) external view returns (bool); +} diff --git a/crates/lint/testdata/IncorrectERC721Interface.stderr b/crates/lint/testdata/IncorrectERC721Interface.stderr new file mode 100644 index 0000000000000..2e68084c1cec1 --- /dev/null +++ b/crates/lint/testdata/IncorrectERC721Interface.stderr @@ -0,0 +1,152 @@ +note[interface-naming]: interface names should be prefixed with 'I' + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface ERC721 { + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/interface-naming + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface IERC721 {} + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface ERC721 { + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface IERC721Incorrect is IERC721 { + │ ━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface IERC721Correct is IERC721 { + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface IERC721NamedCorrect { + │ ━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ interface INotERC721 { + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function balanceOf(address owner) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function ownerOf(uint256 tokenId) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function balanceOf(address owner) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function ownerOf(uint256 tokenId) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function safeTransferFrom(address from, address to, uint256 tokenId) external returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function transferFrom(address from, address to, uint256 tokenId) external returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function approve(address to, uint256 tokenId) external returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function setApprovalForAll(address operator, bool approved) external returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function getApproved(uint256 tokenId) external view returns (bool); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function isApprovedForAll(address owner, address operator) external view returns (address); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + +warning[incorrect-erc721-interface]: incorrect ERC721 function interface + ╭▸ ROOT/testdata/IncorrectERC721Interface.sol:LL:CC + │ +LL │ function supportsInterface(bytes4 interfaceId) external view returns (uint256); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-erc721-interface + diff --git a/crates/lint/testdata/IncorrectShift.sol b/crates/lint/testdata/IncorrectShift.sol index 9377354851c42..9ca3297e101ce 100644 --- a/crates/lint/testdata/IncorrectShift.sol +++ b/crates/lint/testdata/IncorrectShift.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.18; contract IncorrectShift { uint256 stateValue = 100; diff --git a/crates/lint/testdata/IncorrectShift.stderr b/crates/lint/testdata/IncorrectShift.stderr index 46276262da4bd..dfff32db897bb 100644 --- a/crates/lint/testdata/IncorrectShift.stderr +++ b/crates/lint/testdata/IncorrectShift.stderr @@ -1,40 +1,40 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect - --> ROOT/testdata/IncorrectShift.sol:LL:CC - | -21 | result = 2 << stateValue; - | --------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC + │ +LL │ result = 2 << stateValue; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect - --> ROOT/testdata/IncorrectShift.sol:LL:CC - | -22 | result = 8 >> localValue; - | --------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC + │ +LL │ result = 8 >> localValue; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect - --> ROOT/testdata/IncorrectShift.sol:LL:CC - | -23 | result = 16 << (stateValue + 1); - | ---------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC + │ +LL │ result = 16 << (stateValue + 1); + │ ━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect - --> ROOT/testdata/IncorrectShift.sol:LL:CC - | -24 | result = 32 >> getAmount(); - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC + │ +LL │ result = 32 >> getAmount(); + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift warning[incorrect-shift]: the order of args in a shift operation is incorrect - --> ROOT/testdata/IncorrectShift.sol:LL:CC - | -25 | ... result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); - | ------------------------------------------------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + ╭▸ ROOT/testdata/IncorrectShift.sol:LL:CC + │ +LL │ … result = 1 << (localValue > 10 ? localShiftAmount : stateShiftAmount); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/incorrect-shift diff --git a/crates/lint/testdata/InlineAssembly.sol b/crates/lint/testdata/InlineAssembly.sol new file mode 100644 index 0000000000000..05917ea22784c --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.sol @@ -0,0 +1,110 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract InlineAssembly { + function bare() public view returns (uint256 id) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + id := chainid() + } + } + + function withMemorySafe() public view returns (uint256 size) { + assembly ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + size := extcodesize(address()) + } + } + + function withDialectAndMemorySafe() public view returns (uint256 ptr) { + assembly "evmasm" ("memory-safe") { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + ptr := mload(0x40) + } + } + + function withNatspecMemorySafe() public view returns (uint256 v) { + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := chainid() + } + } + + function withNatspecMemorySafeAndOtherDocs() public view returns (uint256 v) { + /// @notice does a thing + /// @solidity memory-safe-assembly + assembly { //~NOTE: inline assembly (declared memory-safe); review business logic and side effects + v := gas() + } + } + + function plainCommentDoesNotCount() public view returns (uint256 v) { + // solidity memory-safe-assembly + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := chainid() + } + } + + function nestedInControlFlow(bool flag) public view returns (uint256 v) { + if (flag) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := gas() + } + } + + for (uint256 i = 0; i < 1; ++i) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInUnchecked(uint256 x) public pure returns (uint256 v) { + unchecked { + v = x + 1; + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := add(v, 1) + } + } + } + + function nestedInTryCatch() public returns (uint256 v) { + try this.bare() returns (uint256) { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 1 + } + } catch { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + v := 2 + } + } + } + + function suppressed() public view returns (uint256 id) { + // forge-lint: disable-next-line(inline-assembly) + assembly { + id := chainid() + } + } + + modifier guarded() { + assembly { //~NOTE: inline assembly used; review for memory safety and side effects + if iszero(caller()) { revert(0, 0) } + } + _; + } + + function suppressedRegion() public view returns (uint256 a, uint256 b) { + // forge-lint: disable-start(inline-assembly) + assembly { + a := chainid() + } + assembly ("memory-safe") { + b := gas() + } + // forge-lint: disable-end(inline-assembly) + } + + function noAssembly() public pure returns (uint256) { + return 42; + } +} diff --git a/crates/lint/testdata/InlineAssembly.stderr b/crates/lint/testdata/InlineAssembly.stderr new file mode 100644 index 0000000000000..12f8bcbacd14e --- /dev/null +++ b/crates/lint/testdata/InlineAssembly.stderr @@ -0,0 +1,96 @@ +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly "evmasm" ("memory-safe") { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly (declared memory-safe); review business logic and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + +note[inline-assembly]: inline assembly used; review for memory safety and side effects + ╭▸ ROOT/testdata/InlineAssembly.sol:LL:CC + │ +LL │ assembly { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/inline-assembly + diff --git a/crates/lint/testdata/Keccak256.sol b/crates/lint/testdata/Keccak256.sol index 8d3a3044bc8cd..2457aed96d601 100644 --- a/crates/lint/testdata/Keccak256.sol +++ b/crates/lint/testdata/Keccak256.sol @@ -1,14 +1,58 @@ +//@compile-flags: --severity gas info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + contract AsmKeccak256 { - constructor(uint256 a, uint256 b) { - keccak256(abi.encodePacked(a, b)); //~NOTE: hash using inline assembly to save gas + // constants are optimized by the compiler + bytes32 constant HASH = keccak256("hello"); + bytes32 constant OTHER_HASH = keccak256(hex"1234"); + + constructor(uint256 a, uint256 b, address c) { + // forge-lint: disable-next-line(asm-keccak256) + keccak256(abi.encodePacked(a, b)); + + keccak256(abi.encodePacked(a, b)); // forge-lint: disable-line(asm-keccak256) + + // lints fire before the disabled block + address c = address(1); + bytes32 hash = keccak256(abi.encodePacked(a, b, bytes32(bytes20(c)))); //~NOTE: inefficient hashing mechanism + uint256 MixedCase_Variable = 1; //~NOTE: mutable variables should use mixedCase + + // forge-lint: disable-start(asm-keccak256) ------------------------------------- + keccak256(abi.encodePacked(a, b)); // disabled | + // | + // non-disabled lints still fire | + uint256 Another_MixedCase = 2; //~NOTE: mutable variables should use mixedCase + // | + // forge-lint: disable-start(asm-keccak256) ------- | + keccak256(abi.encodePacked(a, b)); // disabled | | + // | | + // forge-lint: disable-end(asm-keccak256) --------- | + // forge-lint: disable-end(asm-keccak256) --------------------------------------- + + // lints still fire after the disabled block + bytes32 afterDisabledBlock = keccak256(abi.encode(a, b, c)); //~NOTE: inefficient hashing mechanism + uint256 YetAnother_MixedCase = 3; //~NOTE: mutable variables should use mixedCase } - function solidityHash(uint256 a, uint256 b) public view { - keccak256(abi.encodePacked(a, b)); //~NOTE: hash using inline assembly to save gas + // forge-lint: disable-next-item(asm-keccak256) + function solidityHashDisabled(uint256 a, uint256 b) public pure returns (bytes32) { + bytes32 hash = keccak256(abi.encodePacked(a)); + return keccak256(abi.encodePacked(a, b)); } - function assemblyHash(uint256 a, uint256 b) public view { + function solidityHash(bytes calldata z, uint256 a, uint256 b, address c) public pure returns (bytes32) { + bytes32 loadsFromCalldata = keccak256(z); //~NOTE: inefficient hashing mechanism + bytes memory y = z; + bytes32 loadsFromMemory = keccak256(y); //~NOTE: inefficient hashing mechanism + bytes32 lintWithoutFix = keccak256(abi.encodePacked(a, b, c)); //~NOTE: inefficient hashing mechanism + return keccak256(abi.encode(a, b, c)); //~NOTE: inefficient hashing mechanism + } + + function assemblyHash(uint256 a, uint256 b) public pure returns (bytes32) { //optimized + // forge-lint: disable-next-line(inline-assembly) assembly { mstore(0x00, a) mstore(0x20, b) @@ -16,3 +60,30 @@ contract AsmKeccak256 { } } } + +// forge-lint: disable-next-item(asm-keccak256) +contract OtherAsmKeccak256 { + uint256 Enabled_MixedCase_Variable; //~NOTE: mutable variables should use mixedCase + + function contratDisabledHash(uint256 a, uint256 b) public pure returns (bytes32) { + return keccak256(abi.encode(a, b)); + } + + function contratDisabledHash2(uint256 a, uint256 b) public pure returns (bytes32) { + return keccak256(abi.encodePacked(a, b)); + } +} + +contract YetAnotherAsmKeccak256 { + function nonDisabledHash(uint256 x, uint256 y) public pure returns (bytes32) { + bytes32 doesNotUseScratchSpace = keccak256(abi.encode(x, y, x, y, x, y)); //~NOTE: inefficient hashing mechanism + bytes32 doesUseScratchSpace = keccak256(abi.encode(x)); //~NOTE: inefficient hashing mechanism + return keccak256(abi.encode(doesUseScratchSpace, doesNotUseScratchSpace)); //~NOTE: inefficient hashing mechanism + } + + // forge-lint: disable-next-item(asm-keccak256) + function functionDisabledHash(uint256 a, uint256 b) public pure returns (bytes32) { + uint256 Enabled_MixedCase_Variable = 1; //~NOTE: mutable variables should use mixedCase + return keccak256(abi.encodePacked(a, b)); + } +} diff --git a/crates/lint/testdata/Keccak256.stderr b/crates/lint/testdata/Keccak256.stderr index 8011afa89a112..a81e429e389a1 100644 --- a/crates/lint/testdata/Keccak256.stderr +++ b/crates/lint/testdata/Keccak256.stderr @@ -1,16 +1,144 @@ -note[asm-keccak256]: hash using inline assembly to save gas - --> ROOT/testdata/Keccak256.sol:LL:CC - | -3 | keccak256(abi.encodePacked(a, b)); - | --------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 - -note[asm-keccak256]: hash using inline assembly to save gas - --> ROOT/testdata/Keccak256.sol:LL:CC - | -7 | keccak256(abi.encodePacked(a, b)); - | --------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#asm-keccak256 +note[mixed-case-variable]: mutable variables should use mixedCase + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 MixedCase_Variable = 1; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `mixedCaseVariable` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + +note[mixed-case-variable]: mutable variables should use mixedCase + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 Another_MixedCase = 2; + │ ━━━━━━━━━━━━━━━━━ help: consider using: `anotherMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + +note[mixed-case-variable]: mutable variables should use mixedCase + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 YetAnother_MixedCase = 3; + │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `yetAnotherMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + +note[mixed-case-variable]: mutable variables should use mixedCase + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 Enabled_MixedCase_Variable; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + +note[mixed-case-variable]: mutable variables should use mixedCase + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 Enabled_MixedCase_Variable = 1; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `enabledMixedCaseVariable` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ contract AsmKeccak256 { + │ ━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ contract OtherAsmKeccak256 { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ contract YetAnotherAsmKeccak256 { + │ ━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 hash = keccak256(abi.encodePacked(a, b, bytes32(bytes20(c)))); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 afterDisabledBlock = keccak256(abi.encode(a, b, c)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 loadsFromCalldata = keccak256(z); + │ ━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 loadsFromMemory = keccak256(y); + │ ━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 lintWithoutFix = keccak256(abi.encodePacked(a, b, c)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ return keccak256(abi.encode(a, b, c)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ uint256 Enabled_MixedCase_Variable; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 doesNotUseScratchSpace = keccak256(abi.encode(x, y, x, y, x, y)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ bytes32 doesUseScratchSpace = keccak256(abi.encode(x)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 + +note[asm-keccak256]: use of inefficient hashing mechanism; consider using inline assembly + ╭▸ ROOT/testdata/Keccak256.sol:LL:CC + │ +LL │ return keccak256(abi.encode(doesUseScratchSpace, doesNotUseScratchSpace)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/asm-keccak256 diff --git a/crates/lint/testdata/MissingZeroCheck.sol b/crates/lint/testdata/MissingZeroCheck.sol new file mode 100644 index 0000000000000..c1af9bb2f06ae --- /dev/null +++ b/crates/lint/testdata/MissingZeroCheck.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface IExternal { + function ping() external; +} + +contract MissingZeroCheck { + address public owner; + address payable public recipient; + uint256 public n; + + modifier nonZero(address a) { + require(a != address(0), "zero"); + _; + } + + modifier doesNothing(address a) { + _; + } + + // SHOULD FAIL: + + function setOwner(address newOwner) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = newOwner; + } + + constructor(address initialOwner) { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = initialOwner; + } + + function pay(address payable to) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + to.transfer(1); + } + + function lowLevel(address payable to, bytes calldata data) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + (bool ok,) = to.call(data); + require(ok); + } + + function withUselessModifier(address a) external doesNothing(a) { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = a; + } + + function setOwnerViaAlias(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + address tmp = a; + owner = tmp; + } + + function setOwnerViaReassign(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + address tmp; + tmp = a; + owner = tmp; + } + + function setOwnerViaCast(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = address(uint160(a)); + } + + function payViaAlias(address payable a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + address payable tmp = a; + tmp.transfer(1); + } + + // Only `b` should be flagged; `a` is guarded. + function mixedParams(address a, address b) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + require(a != address(0)); + owner = a; + recipient = payable(b); + } + + // Same param feeds two sinks: should produce a single diagnostic. + function bothSinks(address payable a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + recipient = a; + a.transfer(1); + } + + function ternaryAlias(address a, bool flag) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + address tmp = flag ? a : address(0); + owner = tmp; + } + + function payableWrap(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + payable(a).transfer(1); + } + + // Modifier called with an expression, not a direct ident: we cannot prove the guard + // applies, so we should still flag. + function modifierWithExpr(address a) external nonZero(addrIdentity(a)) { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = a; + } + + function delegateCallSink(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + (bool ok,) = a.delegatecall(""); + require(ok); + } + + function sendSinkStmt(address payable a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + a.send(1); + } + + function sendSinkDecl(address payable a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + bool ok = a.send(1); + require(ok); + } + + function multiHopTaint(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + address x = a; + address y = x; + owner = y; + } + + function guardAfterSink(address a) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + owner = a; + require(a != address(0)); + } + + function guardOnOneBranch(address a, bool flag) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + if (flag) { + require(a != address(0)); + } + owner = a; + } + + function guardInForLoop(address a, uint256 n) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + for (uint256 i = 0; i < n; i++) { + require(a != address(0)); + } + owner = a; + } + + function guardInWhileLoop(address a, bool flag) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + while (flag) { + require(a != address(0)); + flag = false; + } + owner = a; + } + + function guardInTryClause(address a, address payable target) external { //~WARN: address parameter is used in a state write or value transfer without a zero-address check + try IExternal(target).ping() { + require(a != address(0)); + } catch { + require(a != address(0)); + } + owner = a; + } + + // SHOULD PASS: + + function setOwnerGuarded(address newOwner) external { + require(newOwner != address(0), "zero"); + owner = newOwner; + } + + function setOwnerIfGuarded(address newOwner) external { + if (newOwner == address(0)) revert(); + owner = newOwner; + } + + function setOwnerAssertGuarded(address newOwner) external { + assert(newOwner != address(0)); + owner = newOwner; + } + + function setOwnerWithModifier(address newOwner) external nonZero(newOwner) { + owner = newOwner; + } + + function setN(uint256 v) external { + n = v; + } + + function viewer(address a) external view returns (address) { + return a; + } + + function pureFn(address a) external pure returns (address) { + return a; + } + + function internalHelper(address a) internal { + owner = a; + } + + function callsHelper(address a) external { + require(a != address(0)); + internalHelper(a); + } + + function setOwnerViaAliasGuarded(address a) external { + require(a != address(0)); + address tmp = a; + owner = tmp; + } + + function privateHelper2(address a) private { + owner = a; + } + + event Deposit(address indexed from); + error ZeroAddress(); + + function emitOnly(address a) external { + emit Deposit(a); + } + + function guardViaCustomRevert(address a) external { + if (a == address(0)) revert ZeroAddress(); + owner = a; + } + + function noSinkJustPassthrough(address a) external returns (address) { + return a; + } + + function addrIdentity(address x) internal pure returns (address) { + return x; + } + + function staticCallOnly(address a) external { + (bool ok,) = a.staticcall(""); + require(ok); + } + + // Symmetric guard on both branches: universally checked. + function guardOnBothBranches(address a, bool flag) external { + if (flag) { + require(a != address(0)); + } else { + require(a != address(0)); + } + owner = a; + } + + // Inner zero-check guards `a` for the rest of the enclosing branch via early revert. + function nestedGuardWithRevert(address a, bool flag) external { + if (flag) { + if (a == address(0)) revert(); + owner = a; + } + } +} diff --git a/crates/lint/testdata/MissingZeroCheck.stderr b/crates/lint/testdata/MissingZeroCheck.stderr new file mode 100644 index 0000000000000..81a9179e79c94 --- /dev/null +++ b/crates/lint/testdata/MissingZeroCheck.stderr @@ -0,0 +1,184 @@ +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function setOwner(address newOwner) external { + │ ━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ constructor(address initialOwner) { + │ ━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function pay(address payable to) external { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function lowLevel(address payable to, bytes calldata data) external { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function withUselessModifier(address a) external doesNothing(a) { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function setOwnerViaAlias(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function setOwnerViaReassign(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function setOwnerViaCast(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function payViaAlias(address payable a) external { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function mixedParams(address a, address b) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function bothSinks(address payable a) external { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function ternaryAlias(address a, bool flag) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function payableWrap(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function modifierWithExpr(address a) external nonZero(addrIdentity(a)) { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function delegateCallSink(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function sendSinkStmt(address payable a) external { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function sendSinkDecl(address payable a) external { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function multiHopTaint(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function guardAfterSink(address a) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function guardOnOneBranch(address a, bool flag) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function guardInForLoop(address a, uint256 n) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function guardInWhileLoop(address a, bool flag) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + +warning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check + ╭▸ ROOT/testdata/MissingZeroCheck.sol:LL:CC + │ +LL │ function guardInTryClause(address a, address payable target) external { + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check + diff --git a/crates/lint/testdata/MixedCase.sol b/crates/lint/testdata/MixedCase.sol index 75126cf8998e6..c120a57856f0b 100644 --- a/crates/lint/testdata/MixedCase.sol +++ b/crates/lint/testdata/MixedCase.sol @@ -1,5 +1,11 @@ +//@compile-flags: --severity info + // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.18; + +interface IERC20 { + function decimals() external view returns(uint8); +} contract MixedCaseTest { uint256 variableMixedCase; @@ -59,4 +65,31 @@ contract MixedCaseTest { function statefulFuzz_mixedcase_disabled() public {} function statefulFuzzMixedCaseDisabled() public {} function statefulFuzzmixedcasedisabled() public {} + + // ERC is, by default, an allowed infix + function rescueERC6909(address token, address to, uint256 tokenId, uint256 amount) public {} + function ERC20DoSomething() public {} + function ERC20_DoSomething() public {} // invalid because of the underscore + //~^NOTE: function names should use mixedCase + + // Common abbreviations are allowed by default: ID, URL, URI, API, JSON, XML, HTML, HTTP, HTTPS + uint256 marketID; + uint256 userID; + uint256 optionID; + uint256 apiURL; + uint256 baseURL; + function parseJSON() public {} + function fetchAPIData() public {} + function processHTML() public {} + function sendHTTPRequest() public {} + function handleHTTPSConnection() public {} + function getXMLData() public {} + + // SCREAMING_SNAKE_CASE is allowed for functions that are most likely constant getters + function MAX_NUMBER() external view returns (uint256) {} + function CUSTOM_TYPE_RETURN() external view returns (IERC20) {} + function HAS_PARAMS(address addr) external view returns (uint256) {} //~NOTE: function names should use mixedCase + function HAS_NO_RETURN() external view {} //~NOTE: function names should use mixedCase + function HAS_MORE_THAN_ONE_RETURN() external view returns (uint256, uint256) {} //~NOTE: function names should use mixedCase + function NOT_ELEMENTARY_RETURN() external view returns (uint256[] memory) {} //~NOTE: function names should use mixedCase } diff --git a/crates/lint/testdata/MixedCase.stderr b/crates/lint/testdata/MixedCase.stderr index 0976dced8716c..2db30559ba5a6 100644 --- a/crates/lint/testdata/MixedCase.stderr +++ b/crates/lint/testdata/MixedCase.stderr @@ -1,96 +1,152 @@ note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -9 | uint256 Variablemixedcase; - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 Variablemixedcase; + │ ━━━━━━━━━━━━━━━━━ help: consider using: `variablemixedcase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -10 | uint256 VARIABLE_MIXED_CASE; - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 VARIABLE_MIXED_CASE; + │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -11 | uint256 VariableMixedCase; - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 VariableMixedCase; + │ ━━━━━━━━━━━━━━━━━ help: consider using: `variableMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -17 | uint256 testVAL; - | ------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 testVAL; + │ ━━━━━━━ help: consider using: `testVal` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -18 | uint256 TestVal; - | ------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 TestVal; + │ ━━━━━━━ help: consider using: `testVal` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-variable]: mutable variables should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -19 | uint256 TESTVAL; - | ------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ uint256 TESTVAL; + │ ━━━━━━━ help: consider using: `testval` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -26 | function Functionmixedcase() public {} - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function Functionmixedcase() public {} + │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionmixedcase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -27 | function FUNCTION_MIXED_CASE() public {} - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function FUNCTION_MIXED_CASE() public {} + │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -28 | function FunctionMixedCase() public {} - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function FunctionMixedCase() public {} + │ ━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -29 | function function_mixed_case() public {} - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function function_mixed_case() public {} + │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `functionMixedCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -53 | function invariantBalance_MixedCase_Enabled() public {} - | ---------------------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function invariantBalance_MixedCase_Enabled() public {} + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantBalanceMixedCaseEnabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function note[mixed-case-function]: function names should use mixedCase - --> ROOT/testdata/MixedCase.sol:LL:CC - | -54 | function invariantbalance_mixedcase_enabled() public {} - | ---------------------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-function + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function invariantbalance_mixedcase_enabled() public {} + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `invariantbalanceMixedcaseEnabled` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[mixed-case-function]: function names should use mixedCase + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function ERC20_DoSomething() public {} // invalid because of the underscore + │ ━━━━━━━━━━━━━━━━━ help: consider using: `erc20DoSomething` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[mixed-case-function]: function names should use mixedCase + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function HAS_PARAMS(address addr) external view returns (uint256) {} + │ ━━━━━━━━━━ help: consider using: `hasParams` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[mixed-case-function]: function names should use mixedCase + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function HAS_NO_RETURN() external view {} + │ ━━━━━━━━━━━━━ help: consider using: `hasNoReturn` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[mixed-case-function]: function names should use mixedCase + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function HAS_MORE_THAN_ONE_RETURN() external view returns (uint256, uint256) {} + │ ━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `hasMoreThanOneReturn` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[mixed-case-function]: function names should use mixedCase + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ function NOT_ELEMENTARY_RETURN() external view returns (uint256[] memory) {} + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `notElementaryReturn` + │ + ╰ help: https://getfoundry.sh/forge/linting/mixed-case-function + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ interface IERC20 { + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MixedCase.sol:LL:CC + │ +LL │ contract MixedCaseTest { + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file diff --git a/crates/lint/testdata/MultiContractFile.sol b/crates/lint/testdata/MultiContractFile.sol new file mode 100644 index 0000000000000..a316d0a8c2175 --- /dev/null +++ b/crates/lint/testdata/MultiContractFile.sol @@ -0,0 +1,14 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract A {} + +contract B {} //~NOTE: prefer having only one contract, interface or library per file + +contract C {} + +interface I {} + +library L {} diff --git a/crates/lint/testdata/MultiContractFile.stderr b/crates/lint/testdata/MultiContractFile.stderr new file mode 100644 index 0000000000000..e25f3d72ad01a --- /dev/null +++ b/crates/lint/testdata/MultiContractFile.stderr @@ -0,0 +1,40 @@ +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC + │ +LL │ contract A {} + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC + │ +LL │ contract B {} + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC + │ +LL │ contract C {} + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC + │ +LL │ interface I {} + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile.sol:LL:CC + │ +LL │ library L {} + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + diff --git a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.sol b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.sol new file mode 100644 index 0000000000000..619abe517659a --- /dev/null +++ b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.sol @@ -0,0 +1,14 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Interface counts as a contract-like item. +interface I1 {} + +// Library is also a contract-like item and it should be counted. +library L1 {} //~NOTE: prefer having only one contract, interface or library per file + +// Third contract-like item. +contract C1 {} + diff --git a/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr new file mode 100644 index 0000000000000..1912f16863712 --- /dev/null +++ b/crates/lint/testdata/MultiContractFile_InterfaceLibrary.stderr @@ -0,0 +1,24 @@ +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC + │ +LL │ interface I1 {} + │ ━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC + │ +LL │ library L1 {} + │ ━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/MultiContractFile_InterfaceLibrary.sol:LL:CC + │ +LL │ contract C1 {} + │ ━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + diff --git a/crates/lint/testdata/NamedStructFields.sol b/crates/lint/testdata/NamedStructFields.sol new file mode 100644 index 0000000000000..6c217b09b86bd --- /dev/null +++ b/crates/lint/testdata/NamedStructFields.sol @@ -0,0 +1,24 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract NamedStructFields { + struct Person { + string name; + uint256 age; + address wallet; + } + + function namedArgs() public { + Person memory person = Person({ + name: "Alice", + age: 25, + wallet: address(0) + }); + } + + function positionalArgs() public { + Person memory person = Person("Alice", 25, address(0)); //~NOTE: prefer initializing structs with named fields + } +} diff --git a/crates/lint/testdata/NamedStructFields.stderr b/crates/lint/testdata/NamedStructFields.stderr new file mode 100644 index 0000000000000..cfb35637176bd --- /dev/null +++ b/crates/lint/testdata/NamedStructFields.stderr @@ -0,0 +1,8 @@ +note[named-struct-fields]: prefer initializing structs with named fields + ╭▸ ROOT/testdata/NamedStructFields.sol:LL:CC + │ +LL │ Person memory person = Person("Alice", 25, address(0)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ help: consider using named fields: `Person({ name: "Alice", age: 25, wallet: address(0) })` + │ + ╰ help: https://getfoundry.sh/forge/linting/named-struct-fields + diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol new file mode 100644 index 0000000000000..bfc993baab794 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 +pragma solidity 0.8.18; //~NOTE: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr new file mode 100644 index 0000000000000..c2c967dee792f --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretAboveExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: 0.8.18 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.18;' conflicts with other version requirements in the project: ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentCaretAboveExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.18; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol new file mode 100644 index 0000000000000..75bc17988accc --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr new file mode 100644 index 0000000000000..f60361718ba9b --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretMatchesExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretMatchesExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol new file mode 100644 index 0000000000000..37b06040c33a6 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; //~NOTE: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 +pragma solidity ~0.8.20; //~NOTE: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr new file mode 100644 index 0000000000000..6c46f2478208d --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentCaretVsTilde.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity ^0.8.20;' conflicts with other version requirements in the project: ~0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ^0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.20;' conflicts with other version requirements in the project: ^0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentCaretVsTilde.sol:LL:CC + │ +LL │ pragma solidity ~0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.sol b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol new file mode 100644 index 0000000000000..f85a477cc8744 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20 || 0.8.21; //~NOTE: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr new file mode 100644 index 0000000000000..acf6bd7c2d6e0 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentOrVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity 0.8.20 || 0.8.21;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20 || 0.8.21; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: 0.8.20 || 0.8.21 + ╭▸ ROOT/testdata/PragmaInconsistentOrVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol new file mode 100644 index 0000000000000..d8fcb7a0eb4b1 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.sol @@ -0,0 +1,7 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; //~NOTE: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 +pragma solidity 0.8.20; //~NOTE: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr new file mode 100644 index 0000000000000..5ac221b924c9a --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentRangeVsExact.stderr @@ -0,0 +1,16 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0 <0.9.0;' conflicts with other version requirements in the project: 0.8.20 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0 <0.9.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity 0.8.20;' conflicts with other version requirements in the project: >=0.8.0 <0.9.0 + ╭▸ ROOT/testdata/PragmaInconsistentRangeVsExact.sol:LL:CC + │ +LL │ pragma solidity 0.8.20; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol new file mode 100644 index 0000000000000..fe208e15efb63 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.sol @@ -0,0 +1,8 @@ +//@compile-flags: --only-lint pragma-inconsistent + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; //~NOTE: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 +pragma solidity ^0.8.0; //~NOTE: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 +pragma solidity ~0.8.0; //~NOTE: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + +contract Main {} diff --git a/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr new file mode 100644 index 0000000000000..e1e5ad7333fb2 --- /dev/null +++ b/crates/lint/testdata/PragmaInconsistentThreeDistinct.stderr @@ -0,0 +1,24 @@ +note[pragma-inconsistent]: 'pragma solidity >=0.8.0;' conflicts with other version requirements in the project: ^0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity >=0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ^0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ~0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ^0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + +note[pragma-inconsistent]: 'pragma solidity ~0.8.0;' conflicts with other version requirements in the project: >=0.8.0, ^0.8.0 + ╭▸ ROOT/testdata/PragmaInconsistentThreeDistinct.sol:LL:CC + │ +LL │ pragma solidity ~0.8.0; + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/pragma-inconsistent + diff --git a/crates/lint/testdata/Rtlo.sol b/crates/lint/testdata/Rtlo.sol new file mode 100644 index 0000000000000..82f7e68a41548 --- /dev/null +++ b/crates/lint/testdata/Rtlo.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Tests for the `rtlo` lint, which detects "Trojan Source" bidirectional +// formatting characters (CVE-2021-42574). These have no legitimate use in +// Solidity source and can be used to hide malicious code. +// +// Note: solc itself rejects unbalanced directional override markers (error +// 8936), so each test uses a balanced opening/closing pair. Our lint flags +// each occurrence individually regardless of balance. + +contract Rtlo { + // SHOULD FAIL: every codepoint in the Trojan-Source set is flagged. + // Each line below contains two bidi characters (an opener and its closer) + // and produces two diagnostics. + + string public lre = unicode"‪_‬"; + //~^WARN: U+202A (Left-to-Right Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public rle = unicode"‫_‬"; + //~^WARN: U+202B (Right-to-Left Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public pdf = unicode"‪‬"; + //~^WARN: U+202A (Left-to-Right Embedding) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public lro = unicode"‭_‬"; + //~^WARN: U+202D (Left-to-Right Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public rlo = unicode"‮_‬"; + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + string public lri = unicode"⁦_⁩"; + //~^WARN: U+2066 (Left-to-Right Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public rli = unicode"⁧_⁩"; + //~^WARN: U+2067 (Right-to-Left Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public fsi = unicode"⁨_⁩"; + //~^WARN: U+2068 (First Strong Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + string public pdi = unicode"⁦⁩"; + //~^WARN: U+2066 (Left-to-Right Isolate) detected + //~|WARN: U+2069 (Pop Directional Isolate) detected + + // SHOULD FAIL: bidi controls inside a block comment are also detected. + /* hidden‮ /* text ‬ */ uint256 inBlockComment; + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + // SHOULD FAIL: bidi controls inside a line comment are also detected. The + // expectation markers must come on separate lines because the ui-test + // parser only treats the first comment on a line as a marker. + // sneaky‮ payload ‬ trailing + //~^WARN: U+202E (Right-to-Left Override) detected + //~|WARN: U+202C (Pop Directional Formatting) detected + + // SHOULD PASS: inline-config disable suppresses the diagnostic. + // forge-lint: disable-next-line(rtlo) + string public suppressedLine = unicode"‮_‬"; + + // forge-lint: disable-start(rtlo) + string public suppressedA = unicode"‮_‬"; + string public suppressedB = unicode"⁦_⁩"; + // forge-lint: disable-end(rtlo) + + // SHOULD PASS: plain ASCII source, no bidi controls. + string public clean = "no bidi here"; + + // SHOULD FAIL: LRM/RLM marks (U+200E/U+200F) are also flagged. + string public marks = unicode"left‎right‏end"; + //~^WARN: U+200E (Left-to-Right Mark) detected + //~|WARN: U+200F (Right-to-Left Mark) detected +} diff --git a/crates/lint/testdata/Rtlo.stderr b/crates/lint/testdata/Rtlo.stderr new file mode 100644 index 0000000000000..93f5bb191532f --- /dev/null +++ b/crates/lint/testdata/Rtlo.stderr @@ -0,0 +1,192 @@ +warning[rtlo]: U+202A (Left-to-Right Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lre = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lre = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202B (Right-to-Left Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rle = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rle = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202A (Left-to-Right Embedding) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdf = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdf = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202D (Left-to-Right Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lro = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lro = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rlo = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rlo = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2066 (Left-to-Right Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lri = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public lri = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2067 (Right-to-Left Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rli = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public rli = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2068 (First Strong Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public fsi = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public fsi = unicode"�_�"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2066 (Left-to-Right Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdi = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+2069 (Pop Directional Isolate) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public pdi = unicode"��"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ /* hidden� /* text � */ uint256 inBlockComment; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ /* hidden� /* text � */ uint256 inBlockComment; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ // sneaky� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ // sneaky� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+200E (Left-to-Right Mark) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public marks = unicode"left‎right‏end"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+200F (Right-to-Left Mark) detected + ╭▸ ROOT/testdata/Rtlo.sol:LL:CC + │ +LL │ string public marks = unicode"left‎right‏end"; + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + diff --git a/crates/lint/testdata/RtloCommentsOnly.sol b/crates/lint/testdata/RtloCommentsOnly.sol new file mode 100644 index 0000000000000..82a1fbd7fc58e --- /dev/null +++ b/crates/lint/testdata/RtloCommentsOnly.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Bidi chars that only appear in comments (outside any item) must still be +// reported. + +// hidden‮ payload ‬ trailing +//~^WARN: U+202E (Right-to-Left Override) detected +//~|WARN: U+202C (Pop Directional Formatting) detected + +/* block‮ comment ‬ end */ +//~^WARN: U+202E (Right-to-Left Override) detected +//~|WARN: U+202C (Pop Directional Formatting) detected + +contract RtloCommentsOnly {} diff --git a/crates/lint/testdata/RtloCommentsOnly.stderr b/crates/lint/testdata/RtloCommentsOnly.stderr new file mode 100644 index 0000000000000..5a7ec9ee6e69d --- /dev/null +++ b/crates/lint/testdata/RtloCommentsOnly.stderr @@ -0,0 +1,32 @@ +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ // hidden� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ // hidden� payload � trailing + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202E (Right-to-Left Override) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ /* block� comment � end */ + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + +warning[rtlo]: U+202C (Pop Directional Formatting) detected + ╭▸ ROOT/testdata/RtloCommentsOnly.sol:LL:CC + │ +LL │ /* block� comment � end */ + │ ━ + │ + ╰ help: https://getfoundry.sh/forge/linting/rtlo + diff --git a/crates/lint/testdata/ScreamingSnakeCase.sol b/crates/lint/testdata/ScreamingSnakeCase.sol index ccfe596d9e9dd..3cac5f46d16eb 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.sol +++ b/crates/lint/testdata/ScreamingSnakeCase.sol @@ -1,5 +1,7 @@ +//@compile-flags: --severity info + // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.18; contract ScreamingSnakeCaseTest { uint256 constant _SCREAMING_SNAKE_CASE = 0; diff --git a/crates/lint/testdata/ScreamingSnakeCase.stderr b/crates/lint/testdata/ScreamingSnakeCase.stderr index b511f09d254f3..36305bb268d9a 100644 --- a/crates/lint/testdata/ScreamingSnakeCase.stderr +++ b/crates/lint/testdata/ScreamingSnakeCase.stderr @@ -1,64 +1,64 @@ note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -9 | uint256 constant screamingSnakeCase = 0; - | ------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 constant screamingSnakeCase = 0; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -10 | uint256 constant screaming_snake_case = 0; - | -------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 constant screaming_snake_case = 0; + │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -11 | uint256 constant ScreamingSnakeCase = 0; - | ------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 constant ScreamingSnakeCase = 0; + │ ━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-const]: constants should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -12 | uint256 constant SCREAMING_snake_case = 0; - | -------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-const + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 constant SCREAMING_snake_case = 0; + │ ━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-const note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -19 | uint256 immutable screamingSnakeCase0 = 0; - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 immutable screamingSnakeCase0 = 0; + │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -20 | uint256 immutable screaming_snake_case0 = 0; - | --------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 immutable screaming_snake_case0 = 0; + │ ━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -21 | uint256 immutable ScreamingSnakeCase0 = 0; - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 immutable ScreamingSnakeCase0 = 0; + │ ━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE0` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable note[screaming-snake-case-immutable]: immutables should use SCREAMING_SNAKE_CASE - --> ROOT/testdata/ScreamingSnakeCase.sol:LL:CC - | -22 | uint256 immutable SCREAMING_snake_case_0 = 0; - | ---------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#screaming-snake-case-immutable + ╭▸ ROOT/testdata/ScreamingSnakeCase.sol:LL:CC + │ +LL │ uint256 immutable SCREAMING_snake_case_0 = 0; + │ ━━━━━━━━━━━━━━━━━━━━━━ help: consider using: `SCREAMING_SNAKE_CASE_0` + │ + ╰ help: https://getfoundry.sh/forge/linting/screaming-snake-case-immutable diff --git a/crates/lint/testdata/SoloInterfaces.sol b/crates/lint/testdata/SoloInterfaces.sol new file mode 100644 index 0000000000000..f9cc2ee8aa235 --- /dev/null +++ b/crates/lint/testdata/SoloInterfaces.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface SoloInterfaces { + function foo() external; +} + +interface I2 { + function foo() external; +} + +interface I3 { + function foo() external; +} + diff --git a/crates/lint/testdata/StructPascalCase.sol b/crates/lint/testdata/StructPascalCase.sol index 0ec638efe8211..ae1725e50d04e 100644 --- a/crates/lint/testdata/StructPascalCase.sol +++ b/crates/lint/testdata/StructPascalCase.sol @@ -1,5 +1,7 @@ +//@compile-flags: --severity info + // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.18; contract StructPascalCaseTest { struct PascalCase { diff --git a/crates/lint/testdata/StructPascalCase.stderr b/crates/lint/testdata/StructPascalCase.stderr index 7858c18cbf33d..255c1c4d5d74b 100644 --- a/crates/lint/testdata/StructPascalCase.stderr +++ b/crates/lint/testdata/StructPascalCase.stderr @@ -1,48 +1,48 @@ note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -13 | struct _PascalCase { - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct _PascalCase { + │ ━━━━━━━━━━━ help: consider using: `PascalCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -17 | struct pascalCase { - | ---------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct pascalCase { + │ ━━━━━━━━━━ help: consider using: `PascalCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -21 | struct pascalcase { - | ---------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct pascalcase { + │ ━━━━━━━━━━ help: consider using: `Pascalcase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -25 | struct pascal_case { - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct pascal_case { + │ ━━━━━━━━━━━ help: consider using: `PascalCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -29 | struct PASCAL_CASE { - | ----------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct PASCAL_CASE { + │ ━━━━━━━━━━━ help: consider using: `PascalCase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct note[pascal-case-struct]: structs should use PascalCase - --> ROOT/testdata/StructPascalCase.sol:LL:CC - | -33 | struct PASCALCASE { - | ---------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#pascal-case-struct + ╭▸ ROOT/testdata/StructPascalCase.sol:LL:CC + │ +LL │ struct PASCALCASE { + │ ━━━━━━━━━━ help: consider using: `Pascalcase` + │ + ╰ help: https://getfoundry.sh/forge/linting/pascal-case-struct diff --git a/crates/lint/testdata/TooManyDigits.sol b/crates/lint/testdata/TooManyDigits.sol new file mode 100644 index 0000000000000..a56ad67fe379e --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.sol @@ -0,0 +1,73 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TooManyDigits { + // SHOULD FAIL: plain decimal integer literals with 5+ consecutive zeros. + + uint256 stateA = 1000000000000000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + uint256 stateB = 100000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + function asReturn() public pure returns (uint256) { + return 10000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asComparison(uint256 x) public pure returns (bool) { + return x == 1000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArg(address to) public { + _send(to, 50000000000); //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + function asArraySize() public pure { + uint256[100000] memory _arr; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + } + + // Zero-run in the middle (not just trailing). + uint256 middleZeros = 123000007; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscores that don't actually break up the zero run. + uint256 badGrouping = 1_000000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // Underscore right after a single digit, leaving a 5-zero group. + uint256 badGrouping2 = 1_00000; //~NOTE: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + + // SHOULD PASS: + + // Boundary: 4 consecutive zeros (one short of the threshold). + uint256 fourZeros = 10000; + + // Uppercase scientific notation. + uint256 sciUpper = 1E18; + + // Scientific notation. + uint256 sci = 1e18; + + // Underscore-separated digit groups. + uint256 grouped = 1_000_000_000_000_000_000; + + // Sub-denominations. + uint256 oneEther = 1 ether; + uint256 oneGwei = 1 gwei; + uint256 fiveMin = 5 minutes; + + // Address literal (distinct AST kind, not flagged). + address adr = 0x1234567890123456789012345678901234567890; + + // Hex literal — intentional zero patterns (mask / padded value). + bytes32 mask = 0x0000000000000000000000000000000000000000000000000000000000000001; + uint256 hexNum = 0x100000; + + // Small literals (< 5 consecutive zeros). + uint256 small1 = 100; + uint256 small2 = 9999; + uint256 small3 = 1234; + uint256 spread = 101010; + + // Boolean literal. + bool flag = true; + + function _send(address, uint256) internal pure {} +} diff --git a/crates/lint/testdata/TooManyDigits.stderr b/crates/lint/testdata/TooManyDigits.stderr new file mode 100644 index 0000000000000..7e21a530776c2 --- /dev/null +++ b/crates/lint/testdata/TooManyDigits.stderr @@ -0,0 +1,72 @@ +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateA = 1000000000000000000; + │ ━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 stateB = 100000; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return 10000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … return x == 1000000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … _send(to, 50000000000); + │ ━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ … uint256[100000] memory _arr; + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 middleZeros = 123000007; + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping = 1_000000; + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + +note[too-many-digits]: numeric literal with many digits is error-prone; use scientific notation, sub-denominations, or underscore separators + ╭▸ ROOT/testdata/TooManyDigits.sol:LL:CC + │ +LL │ uint256 badGrouping2 = 1_00000; + │ ━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/too-many-digits + diff --git a/crates/lint/testdata/TxOrigin.sol b/crates/lint/testdata/TxOrigin.sol new file mode 100644 index 0000000000000..9728a7e528e5b --- /dev/null +++ b/crates/lint/testdata/TxOrigin.sol @@ -0,0 +1,65 @@ +//@compile-flags: --only-lint tx-origin +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract TxOrigin { + address public owner; + mapping(address => bool) public allowed; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(tx.origin == owner, "not owner"); //~WARN: `tx.origin` should not be used for authorization + _; + } + + function guardedByIf() external view { + if (tx.origin != owner) { //~WARN: `tx.origin` should not be used for authorization + revert("not owner"); + } + } + + function guardedByPredicate() external view { + assert(isOwner(tx.origin)); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByWhile() external view { + while (tx.origin == owner) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByFor() external view { + for (; tx.origin == owner;) { //~WARN: `tx.origin` should not be used for authorization + break; + } + } + + function guardedByDoWhile() external view { + do { + } while (tx.origin == owner); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByMapping() external view { + require(allowed[tx.origin], "not allowed"); //~WARN: `tx.origin` should not be used for authorization + require(allowed[tx.origin] == true, "not allowed"); //~WARN: `tx.origin` should not be used for authorization + } + + function guardedByTernary() external view { + require(tx.origin == owner ? true : false, "not owner"); //~WARN: `tx.origin` should not be used for authorization + } + + function readForLogging() external view returns (address) { + return tx.origin; + } + + function explicitSenderCheck() external view { + require(msg.sender == owner, "not owner"); + } + + function isOwner(address account) internal view returns (bool) { + return account == owner; + } +} diff --git a/crates/lint/testdata/TxOrigin.stderr b/crates/lint/testdata/TxOrigin.stderr new file mode 100644 index 0000000000000..7c2e70225b76d --- /dev/null +++ b/crates/lint/testdata/TxOrigin.stderr @@ -0,0 +1,72 @@ +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner, "not owner"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ if (tx.origin != owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ assert(isOwner(tx.origin)); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ while (tx.origin == owner) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ for (; tx.origin == owner;) { + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ } while (tx.origin == owner); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin], "not allowed"); + │ ━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(allowed[tx.origin] == true, "not allowed"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + +warning[tx-origin]: `tx.origin` should not be used for authorization + ╭▸ ROOT/testdata/TxOrigin.sol:LL:CC + │ +LL │ require(tx.origin == owner ? true : false, "not owner"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/tx-origin + diff --git a/crates/lint/testdata/UncheckedCall.sol b/crates/lint/testdata/UncheckedCall.sol new file mode 100644 index 0000000000000..da9e9cf3a0fe4 --- /dev/null +++ b/crates/lint/testdata/UncheckedCall.sol @@ -0,0 +1,91 @@ +//@compile-flags: --severity high + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract UncheckedCall { + event CallResult(bool, bytes); + + // SHOULD PASS: Properly checked low-level calls + function checkedCallWithTuple(address target, bytes memory data) public { + (bool success, bytes memory result) = target.call(data); + require(success, "Call failed"); + emit CallResult(success, result); + } + + function checkedCallWithIfStatement(address target, bytes memory data) public { + (bool success, ) = target.call(data); + if (!success) { + revert("Call failed"); + } + } + + function checkedDelegateCall(address target, bytes memory data) public returns (bool) { + (bool success, ) = target.delegatecall(data); + return success; + } + + function checkedStaticCall(address target, bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory result) = target.staticcall(data); + require(success, "Static call failed"); + return result; + } + + function checkedCallInRequire(address target) public { + (bool success, ) = target.call(""); + require(success, "Call must succeed"); + } + + function checkedCallWithAssert(address target) public { + (bool success, ) = target.call(""); + assert(success); + } + + // Edge case: pre-existing variable assignment + bool success; + function checkWithExistingVar(address target) public { + (success, ) = target.call(""); + // (success, existingData) = target.call(""); + } + + // Edge case: send and transfer are not low-level calls (they automatically revert on failure) + function sendEther(address payable target) public { + target.transfer(1 ether); // Should not trigger + bool sent = target.send(1 ether); // Should not trigger + require(sent, "Send failed"); + } + + + // SHOULD FAIL: Unchecked low-level calls + function uncheckedCall(address target, bytes memory data) public { + target.call(data); //~WARN: Low-level calls should check the success return value + } + + function uncheckedCallWithValue(address payable target, uint256 value) public { + target.call{value: value}(""); //~WARN: Low-level calls should check the success return value + } + + function uncheckedDelegateCall(address target, bytes memory data) public { + target.delegatecall(data); //~WARN: Low-level calls should check the success return value + } + + function uncheckedStaticCall(address target, bytes memory data) public { + target.staticcall(data); //~WARN: Low-level calls should check the success return value + } + + function multipleUncheckedCalls(address target1, address target2) public { + target1.call(""); //~WARN: Low-level calls should check the success return value + target2.delegatecall(""); //~WARN: Low-level calls should check the success return value + } + + function ignoredReturnWithPartialTuple(address target) public { + (, bytes memory data) = target.call(""); //~WARN: Low-level calls should check the success return value + // Only capturing data, not checking success + } + + bytes existingData; + function ignoredReturnExistingVar(address target) public { + (, existingData) = target.call(""); //~WARN: Low-level calls should check the success return value + } + +} diff --git a/crates/lint/testdata/UncheckedCall.stderr b/crates/lint/testdata/UncheckedCall.stderr new file mode 100644 index 0000000000000..8a8a9fa9b5e17 --- /dev/null +++ b/crates/lint/testdata/UncheckedCall.stderr @@ -0,0 +1,64 @@ +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target.call(data); + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target.call{value: value}(""); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target.delegatecall(data); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target.staticcall(data); + │ ━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target1.call(""); + │ ━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ target2.delegatecall(""); + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ (, bytes memory data) = target.call(""); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + +warning[unchecked-call]: Low-level calls should check the success return value + ╭▸ ROOT/testdata/UncheckedCall.sol:LL:CC + │ +LL │ (, existingData) = target.call(""); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unchecked-call + diff --git a/crates/lint/testdata/UncheckedTransferERC20.sol b/crates/lint/testdata/UncheckedTransferERC20.sol new file mode 100644 index 0000000000000..ee600aa956903 --- /dev/null +++ b/crates/lint/testdata/UncheckedTransferERC20.sol @@ -0,0 +1,127 @@ +//@compile-flags: --severity high info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); +} + +interface IERC20Wrapper { + function transfer(address to, uint256 amount) external; + function transferFrom(address from, address to, uint256 amount) external; +} + +contract UncheckedTransfer { + IERC20 public token; + IERC20Wrapper public tokenWrapper; + mapping(address => uint256) public balances; + + constructor(address _token) { + token = IERC20(_token); + tokenWrapper = IERC20Wrapper(_token); + } + + // SHOULD FAIL: Unchecked transfer calls + function uncheckedTransfer(address to, uint256 amount) public { + IERC20(address(token)).transfer(to, amount); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + token.transfer(to, amount); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + } + + function uncheckedTransferFrom(address from, address to, uint256 amount) public { + IERC20(address(token)).transferFrom(from, to, amount); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + token.transferFrom(from, to, amount); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + } + + function uncheckedInLoop(address[] memory recipients, uint256[] memory amounts) public { + for (uint256 i = 0; i < recipients.length; i++) { + IERC20(address(token)).transfer(recipients[i], amounts[i]); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + token.transfer(recipients[i], amounts[i]); //~WARN: ERC20 'transfer' and 'transferFrom' calls should check the return value + } + } + + // SHOULD PASS: Function with same params but NO boolean return + function proxyCheckedTransfer(address to, uint256 amount) public { + IERC20Wrapper(address(token)).transfer(to, amount); + tokenWrapper.transfer(to, amount); + } + + function proxyCheckedTransferFrom(address from, address to, uint256 amount) public { + IERC20Wrapper(address(token)).transferFrom(from, to, amount); + tokenWrapper.transferFrom(from, to, amount); + } + + // SHOULD PASS: Properly checked transfer calls + function checkedTransferWithRequire(address to, uint256 amount) public { + require(token.transfer(to, amount), "Transfer failed"); + } + + function checkedTransferWithVariable(address to, uint256 amount) public { + bool success = token.transfer(to, amount); + require(success, "Transfer failed"); + } + + function checkedTransferFromWithIf(address from, address to, uint256 amount) public { + bool success = token.transferFrom(from, to, amount); + if (!success) { + revert("TransferFrom failed"); + } + } + + function checkedTransferWithAssert(address to, uint256 amount) public { + assert(token.transfer(to, amount)); + } + + function checkedTransferInReturn(address to, uint256 amount) public returns (bool) { + return token.transfer(to, amount); + } + + function checkedTransferInExpression(address to, uint256 amount) public { + if (token.transfer(to, amount)) { + balances[to] += amount; + } else { + revert("Transfer failed"); + } + } + + function checkedTransferInRequireWithLogic(address to, uint256 amount) public { + require(amount > 0 && token.transfer(to, amount), "Invalid amount or transfer failed"); + } + + function uncheckedApprove(address spender, uint256 amount) public { + token.approve(spender, amount); + } +} + +library Currency { + function transfer(address currency, address to, uint256 amount) internal { + // transfer and check output internally + } + function transferFrom(address currency, address from, address to, uint256 amount) internal { + // transfer and check output internally + } +} + +contract UncheckedTransferUsingCurrencyLib { + using Currency for address; + + address public token; + mapping(address => uint256) public balances; + + constructor(address _token) { + token = _token; + } + + // SHOULD PASS: Function with same params but NO boolean return + function currencyTransfer(address to, uint256 amount) public { + token.transfer(to, amount); + token.transfer(to, amount); + } + + function currencyTransferFrom(address from, address to, uint256 amount) public { + token.transferFrom(from, to, amount); + token.transferFrom(from, to, amount); + } +} diff --git a/crates/lint/testdata/UncheckedTransferERC20.stderr b/crates/lint/testdata/UncheckedTransferERC20.stderr new file mode 100644 index 0000000000000..2c2caa69e7215 --- /dev/null +++ b/crates/lint/testdata/UncheckedTransferERC20.stderr @@ -0,0 +1,88 @@ +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ interface IERC20 { + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ interface IERC20Wrapper { + │ ━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ contract UncheckedTransfer { + │ ━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ library Currency { + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ contract UncheckedTransferUsingCurrencyLib { + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ IERC20(address(token)).transfer(to, amount); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ token.transfer(to, amount); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ … IERC20(address(token)).transferFrom(from, to, amount); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ token.transferFrom(from, to, amount); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ … IERC20(address(token)).transfer(recipients[i], amounts[i]); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + +warning[erc20-unchecked-transfer]: ERC20 'transfer' and 'transferFrom' calls should check the return value + ╭▸ ROOT/testdata/UncheckedTransferERC20.sol:LL:CC + │ +LL │ token.transfer(recipients[i], amounts[i]); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/erc20-unchecked-transfer + diff --git a/crates/lint/testdata/UnsafeCheatcodes.sol b/crates/lint/testdata/UnsafeCheatcodes.sol new file mode 100644 index 0000000000000..ecec9ecfeedf4 --- /dev/null +++ b/crates/lint/testdata/UnsafeCheatcodes.sol @@ -0,0 +1,66 @@ +//@compile-flags: --severity info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {Test} from "./auxiliary/Test.sol"; + +contract UnsafeCheatcodes is Test { + function testSafeCheatcodes() public { + vm.prank(address(0x1)); + vm.deal(address(0x1), 1 ether); + vm.warp(block.timestamp + 1); + vm.roll(block.number + 1); + vm.assume(true); + vm.expectRevert(); + } + + function testDirectFfi() public { + string[] memory inputs = new string[](1); + vm.ffi(inputs); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectReadFile() public { + vm.readFile("test.txt"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectReadLine() public { + vm.readLine("test.txt"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectWriteFile() public { + vm.writeFile("test.txt", "data"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectWriteLine() public { + vm.writeLine("test.txt", "data"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectRemoveFile() public { + vm.removeFile("test.txt"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectCloseFile() public { + vm.closeFile("test.txt"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectSetEnv() public { + vm.setEnv("KEY", "value"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testDirectDeriveKey() public { + vm.deriveKey("mnemonic", 0); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testAssignmentFfi() public { + string[] memory inputs = new string[](1); + bytes memory result = vm.ffi(inputs); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + + function testMultipleUnsafe() public { + vm.ffi(new string[](1)); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + vm.setEnv("KEY", "value"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + vm.readFile("test.txt"); //~NOTE: usage of unsafe cheatcodes that can perform dangerous operations + } + +} diff --git a/crates/lint/testdata/UnsafeCheatcodes.stderr b/crates/lint/testdata/UnsafeCheatcodes.stderr new file mode 100644 index 0000000000000..5b8b429942e80 --- /dev/null +++ b/crates/lint/testdata/UnsafeCheatcodes.stderr @@ -0,0 +1,104 @@ +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.readFile("test.txt"); + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.readLine("test.txt"); + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.writeFile("test.txt", "data"); + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.writeLine("test.txt", "data"); + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.removeFile("test.txt"); + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.closeFile("test.txt"); + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.setEnv("KEY", "value"); + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.deriveKey("mnemonic", 0); + │ ━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ bytes memory result = vm.ffi(inputs); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.ffi(new string[](1)); + │ ━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.setEnv("KEY", "value"); + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + +note[unsafe-cheatcode]: usage of unsafe cheatcodes that can perform dangerous operations + ╭▸ ROOT/testdata/UnsafeCheatcodes.sol:LL:CC + │ +LL │ vm.readFile("test.txt"); + │ ━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-cheatcode + diff --git a/crates/lint/testdata/UnsafeTypecast.sol b/crates/lint/testdata/UnsafeTypecast.sol new file mode 100644 index 0000000000000..5f54167bf6f3c --- /dev/null +++ b/crates/lint/testdata/UnsafeTypecast.sol @@ -0,0 +1,460 @@ +//@compile-flags: --severity high med low info + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// forge-lint: disable-start(mixed-case-variable) +contract UnsafeTypecast { + // Unsigned upcasts are always safe. + function upcastSafeUint() public pure { + uint8 a = type(uint8).max; + uint16 b = uint16(a); + uint24 c = uint24(b); + uint32 d = uint32(c); + uint40 e = uint40(d); + uint48 f = uint48(e); + uint56 g = uint56(f); + uint64 h = uint64(g); + uint72 i = uint72(h); + uint80 j = uint80(i); + uint88 k = uint88(j); + uint96 l = uint96(k); + uint104 m = uint104(l); + uint112 n = uint112(m); + uint120 o = uint120(n); + uint128 p = uint128(o); + uint136 q = uint136(p); + uint144 r = uint144(q); + uint152 s = uint152(r); + uint160 t = uint160(s); + uint168 u = uint168(t); + uint176 v = uint176(u); + uint184 w = uint184(v); + uint192 x = uint192(w); + uint200 y = uint200(x); + uint208 z = uint208(y); + uint216 A = uint216(z); + uint224 B = uint224(A); + uint232 C = uint232(B); + uint240 D = uint240(C); + uint248 E = uint248(D); + uint256 F = uint256(E); + } + + // Signed upcasts are safe. + function upcastSafeInt() public pure { + int8 a = type(int8).max; + int16 b = int16(a); + int24 c = int24(b); + int32 d = int32(c); + int40 e = int40(d); + int48 f = int48(e); + int56 g = int56(f); + int64 h = int64(g); + int72 i = int72(h); + int80 j = int80(i); + int88 k = int88(j); + int96 l = int96(k); + int104 m = int104(l); + int112 n = int112(m); + int120 o = int120(n); + int128 p = int128(o); + int136 q = int136(p); + int144 r = int144(q); + int152 s = int152(r); + int160 t = int160(s); + int168 u = int168(t); + int176 v = int176(u); + int184 w = int184(v); + int192 x = int192(w); + int200 y = int200(x); + int208 z = int208(y); + int216 A = int216(z); + int224 B = int224(A); + int232 C = int232(B); + int240 D = int240(C); + int248 E = int248(D); + int256 F = int256(E); + } + + function upcastSafeBytes() public pure { + bytes1 a = 0xFF; + bytes2 b = bytes2(a); + bytes3 c = bytes3(b); + bytes4 d = bytes4(c); + bytes5 e = bytes5(d); + bytes6 f = bytes6(e); + bytes7 g = bytes7(f); + bytes8 h = bytes8(g); + bytes9 i = bytes9(h); + bytes10 j = bytes10(i); + bytes11 k = bytes11(j); + bytes12 l = bytes12(k); + bytes13 m = bytes13(l); + bytes14 n = bytes14(m); + bytes15 o = bytes15(n); + bytes16 p = bytes16(o); + bytes17 q = bytes17(p); + bytes18 r = bytes18(q); + bytes19 s = bytes19(r); + bytes20 t = bytes20(s); + bytes21 u = bytes21(t); + bytes22 v = bytes22(u); + bytes23 w = bytes23(v); + bytes24 x = bytes24(w); + bytes25 y = bytes25(x); + bytes26 z = bytes26(y); + bytes27 A = bytes27(z); + bytes28 B = bytes28(A); + bytes29 C = bytes29(B); + bytes30 D = bytes30(C); + bytes31 E = bytes31(D); + bytes32 F = bytes32(E); + } + + function safeSizeUint() public pure { + uint256(type(uint256).max); + uint248(type(uint248).max); + uint240(type(uint240).max); + uint232(type(uint232).max); + uint224(type(uint224).max); + uint216(type(uint216).max); + uint208(type(uint208).max); + uint200(type(uint200).max); + uint192(type(uint192).max); + uint184(type(uint184).max); + uint176(type(uint176).max); + uint168(type(uint168).max); + uint160(type(uint160).max); + uint152(type(uint152).max); + uint144(type(uint144).max); + uint136(type(uint136).max); + uint128(type(uint128).max); + uint120(type(uint120).max); + uint112(type(uint112).max); + uint104(type(uint104).max); + uint96(type(uint96).max); + uint88(type(uint88).max); + uint80(type(uint80).max); + uint72(type(uint72).max); + uint64(type(uint64).max); + uint56(type(uint56).max); + uint48(type(uint48).max); + uint40(type(uint40).max); + uint32(type(uint32).max); + uint24(type(uint24).max); + uint16(type(uint16).max); + uint8(type(uint8).max); + } + + function safeSizeInt() public pure { + int256(type(int256).max); + int248(type(int248).max); + int240(type(int240).max); + int232(type(int232).max); + int224(type(int224).max); + int216(type(int216).max); + int208(type(int208).max); + int200(type(int200).max); + int192(type(int192).max); + int184(type(int184).max); + int176(type(int176).max); + int168(type(int168).max); + int160(type(int160).max); + int152(type(int152).max); + int144(type(int144).max); + int136(type(int136).max); + int128(type(int128).max); + int120(type(int120).max); + int112(type(int112).max); + int104(type(int104).max); + int96(type(int96).max); + int88(type(int88).max); + int80(type(int80).max); + int72(type(int72).max); + int64(type(int64).max); + int56(type(int56).max); + int48(type(int48).max); + int40(type(int40).max); + int32(type(int32).max); + int24(type(int24).max); + int16(type(int16).max); + int8(type(int8).max); + } + + function sameSizeAddressSafe() public pure { + address a = 0x1234567890123456789012345678901234567890; + uint160 b = uint160(a); + bytes20 c = bytes20(a); + address d = address(a); + // The following tests, `downcastUnsafeUint` and `downcastUnsafeBytes`, verify that other downcasts + // would also throw. Additionally, the compiler prevents direct casting of addresses to smaller types. + } + + function downcastUnsafeUint() public pure { + uint256 a = type(uint256).max; + uint248 b = uint248(a); //~WARN: typecasts that can truncate values should be checked + uint240 c = uint240(b); //~WARN: typecasts that can truncate values should be checked + uint232 d = uint232(c); //~WARN: typecasts that can truncate values should be checked + uint224 e = uint224(d); //~WARN: typecasts that can truncate values should be checked + uint216 f = uint216(e); //~WARN: typecasts that can truncate values should be checked + uint208 g = uint208(f); //~WARN: typecasts that can truncate values should be checked + uint200 h = uint200(g); //~WARN: typecasts that can truncate values should be checked + uint192 i = uint192(h); //~WARN: typecasts that can truncate values should be checked + uint184 j = uint184(i); //~WARN: typecasts that can truncate values should be checked + uint176 k = uint176(j); //~WARN: typecasts that can truncate values should be checked + uint168 l = uint168(k); //~WARN: typecasts that can truncate values should be checked + uint160 m = uint160(l); //~WARN: typecasts that can truncate values should be checked + uint152 n = uint152(m); //~WARN: typecasts that can truncate values should be checked + uint144 o = uint144(n); //~WARN: typecasts that can truncate values should be checked + uint136 p = uint136(o); //~WARN: typecasts that can truncate values should be checked + uint128 q = uint128(p); //~WARN: typecasts that can truncate values should be checked + uint120 r = uint120(q); //~WARN: typecasts that can truncate values should be checked + uint112 s = uint112(r); //~WARN: typecasts that can truncate values should be checked + uint104 t = uint104(s); //~WARN: typecasts that can truncate values should be checked + uint96 u = uint96(t); //~WARN: typecasts that can truncate values should be checked + uint88 v = uint88(u); //~WARN: typecasts that can truncate values should be checked + uint80 w = uint80(v); //~WARN: typecasts that can truncate values should be checked + uint72 x = uint72(w); //~WARN: typecasts that can truncate values should be checked + uint64 y = uint64(x); //~WARN: typecasts that can truncate values should be checked + uint56 z = uint56(y); //~WARN: typecasts that can truncate values should be checked + uint48 A = uint48(z); //~WARN: typecasts that can truncate values should be checked + uint40 B = uint40(A); //~WARN: typecasts that can truncate values should be checked + uint32 C = uint32(B); //~WARN: typecasts that can truncate values should be checked + uint24 D = uint24(C); //~WARN: typecasts that can truncate values should be checked + uint16 E = uint16(D); //~WARN: typecasts that can truncate values should be checked + uint8 F = uint8(E); //~WARN: typecasts that can truncate values should be checked + } + + function downcastUnsafeInt() public pure { + int256 a = type(int256).max; + int248 b = int248(a); //~WARN: typecasts that can truncate values should be checked + int240 c = int240(b); //~WARN: typecasts that can truncate values should be checked + int232 d = int232(c); //~WARN: typecasts that can truncate values should be checked + int224 e = int224(d); //~WARN: typecasts that can truncate values should be checked + int216 f = int216(e); //~WARN: typecasts that can truncate values should be checked + int208 g = int208(f); //~WARN: typecasts that can truncate values should be checked + int200 h = int200(g); //~WARN: typecasts that can truncate values should be checked + int192 i = int192(h); //~WARN: typecasts that can truncate values should be checked + int184 j = int184(i); //~WARN: typecasts that can truncate values should be checked + int176 k = int176(j); //~WARN: typecasts that can truncate values should be checked + int168 l = int168(k); //~WARN: typecasts that can truncate values should be checked + int160 m = int160(l); //~WARN: typecasts that can truncate values should be checked + int152 n = int152(m); //~WARN: typecasts that can truncate values should be checked + int144 o = int144(n); //~WARN: typecasts that can truncate values should be checked + int136 p = int136(o); //~WARN: typecasts that can truncate values should be checked + int128 q = int128(p); //~WARN: typecasts that can truncate values should be checked + int120 r = int120(q); //~WARN: typecasts that can truncate values should be checked + int112 s = int112(r); //~WARN: typecasts that can truncate values should be checked + int104 t = int104(s); //~WARN: typecasts that can truncate values should be checked + int96 u = int96(t); //~WARN: typecasts that can truncate values should be checked + int88 v = int88(u); //~WARN: typecasts that can truncate values should be checked + int80 w = int80(v); //~WARN: typecasts that can truncate values should be checked + int72 x = int72(w); //~WARN: typecasts that can truncate values should be checked + int64 y = int64(x); //~WARN: typecasts that can truncate values should be checked + int56 z = int56(y); //~WARN: typecasts that can truncate values should be checked + int48 A = int48(z); //~WARN: typecasts that can truncate values should be checked + int40 B = int40(A); //~WARN: typecasts that can truncate values should be checked + int32 C = int32(B); //~WARN: typecasts that can truncate values should be checked + int24 D = int24(C); //~WARN: typecasts that can truncate values should be checked + int16 E = int16(D); //~WARN: typecasts that can truncate values should be checked + int8 F = int8(E); //~WARN: typecasts that can truncate values should be checked + } + + function downcastUnsafeBytes() public pure { + bytes32 a = bytes32(type(uint256).max); + bytes31 b = bytes31(a); //~WARN: typecasts that can truncate values should be checked + bytes30 c = bytes30(b); //~WARN: typecasts that can truncate values should be checked + bytes29 d = bytes29(c); //~WARN: typecasts that can truncate values should be checked + bytes28 e = bytes28(d); //~WARN: typecasts that can truncate values should be checked + bytes27 f = bytes27(e); //~WARN: typecasts that can truncate values should be checked + bytes26 g = bytes26(f); //~WARN: typecasts that can truncate values should be checked + bytes25 h = bytes25(g); //~WARN: typecasts that can truncate values should be checked + bytes24 i = bytes24(h); //~WARN: typecasts that can truncate values should be checked + bytes23 j = bytes23(i); //~WARN: typecasts that can truncate values should be checked + bytes22 k = bytes22(j); //~WARN: typecasts that can truncate values should be checked + bytes21 l = bytes21(k); //~WARN: typecasts that can truncate values should be checked + bytes20 m = bytes20(l); //~WARN: typecasts that can truncate values should be checked + bytes19 n = bytes19(m); //~WARN: typecasts that can truncate values should be checked + bytes18 o = bytes18(n); //~WARN: typecasts that can truncate values should be checked + bytes17 p = bytes17(o); //~WARN: typecasts that can truncate values should be checked + bytes16 q = bytes16(p); //~WARN: typecasts that can truncate values should be checked + bytes15 r = bytes15(q); //~WARN: typecasts that can truncate values should be checked + bytes14 s = bytes14(r); //~WARN: typecasts that can truncate values should be checked + bytes13 t = bytes13(s); //~WARN: typecasts that can truncate values should be checked + bytes12 u = bytes12(t); //~WARN: typecasts that can truncate values should be checked + bytes11 v = bytes11(u); //~WARN: typecasts that can truncate values should be checked + bytes10 w = bytes10(v); //~WARN: typecasts that can truncate values should be checked + bytes9 x = bytes9(w); //~WARN: typecasts that can truncate values should be checked + bytes8 y = bytes8(x); //~WARN: typecasts that can truncate values should be checked + bytes7 z = bytes7(y); //~WARN: typecasts that can truncate values should be checked + bytes6 A = bytes6(z); //~WARN: typecasts that can truncate values should be checked + bytes5 B = bytes5(A); //~WARN: typecasts that can truncate values should be checked + bytes4 C = bytes4(B); //~WARN: typecasts that can truncate values should be checked + bytes3 D = bytes3(C); //~WARN: typecasts that can truncate values should be checked + bytes2 E = bytes2(D); //~WARN: typecasts that can truncate values should be checked + bytes1 F = bytes1(E); //~WARN: typecasts that can truncate values should be checked + } + + function unsignedSignedUnsafe() public pure { + uint256 a = type(uint256).max; + int256 b = int256(a); //~WARN: typecasts that can truncate values should be checked + uint248 c = type(uint248).max; + int248 d = int248(c); //~WARN: typecasts that can truncate values should be checked + uint240 e = type(uint240).max; + int240 f = int240(e); //~WARN: typecasts that can truncate values should be checked + uint232 g = type(uint232).max; + int232 h = int232(g); //~WARN: typecasts that can truncate values should be checked + uint224 i = type(uint224).max; + int224 j = int224(i); //~WARN: typecasts that can truncate values should be checked + uint216 k = type(uint216).max; + int216 l = int216(k); //~WARN: typecasts that can truncate values should be checked + uint208 m = type(uint208).max; + int208 n = int208(m); //~WARN: typecasts that can truncate values should be checked + uint200 o = type(uint200).max; + int200 p = int200(o); //~WARN: typecasts that can truncate values should be checked + uint192 q = type(uint192).max; + int192 r = int192(q); //~WARN: typecasts that can truncate values should be checked + uint184 s = type(uint184).max; + int184 t = int184(s); //~WARN: typecasts that can truncate values should be checked + uint176 u = type(uint176).max; + int176 v = int176(u); //~WARN: typecasts that can truncate values should be checked + uint168 w = type(uint168).max; + int168 x = int168(w); //~WARN: typecasts that can truncate values should be checked + uint160 y = type(uint160).max; + int160 z = int160(y); //~WARN: typecasts that can truncate values should be checked + uint152 A = type(uint152).max; + int152 B = int152(A); //~WARN: typecasts that can truncate values should be checked + uint144 C = type(uint144).max; + int144 D = int144(C); //~WARN: typecasts that can truncate values should be checked + uint136 E = type(uint136).max; + int136 F = int136(E); //~WARN: typecasts that can truncate values should be checked + uint128 G = type(uint128).max; + int128 H = int128(G); //~WARN: typecasts that can truncate values should be checked + uint120 I = type(uint120).max; + int120 J = int120(I); //~WARN: typecasts that can truncate values should be checked + uint112 K = type(uint112).max; + int112 L = int112(K); //~WARN: typecasts that can truncate values should be checked + uint104 M = type(uint104).max; + int104 N = int104(M); //~WARN: typecasts that can truncate values should be checked + uint96 O = type(uint96).max; + int96 P = int96(O); //~WARN: typecasts that can truncate values should be checked + uint88 Q = type(uint88).max; + int88 R = int88(Q); //~WARN: typecasts that can truncate values should be checked + uint80 S = type(uint80).max; + int80 T = int80(S); //~WARN: typecasts that can truncate values should be checked + uint72 U = type(uint72).max; + int72 V = int72(U); //~WARN: typecasts that can truncate values should be checked + uint64 W = type(uint64).max; + int64 X = int64(W); //~WARN: typecasts that can truncate values should be checked + uint56 Y = type(uint56).max; + int56 Z = int56(Y); //~WARN: typecasts that can truncate values should be checked + uint48 AA = type(uint48).max; + int48 BB = int48(AA); //~WARN: typecasts that can truncate values should be checked + uint40 CC = type(uint40).max; + int40 DD = int40(CC); //~WARN: typecasts that can truncate values should be checked + uint32 EE = type(uint32).max; + int32 FF = int32(EE); //~WARN: typecasts that can truncate values should be checked + uint24 GG = type(uint24).max; + int24 HH = int24(GG); //~WARN: typecasts that can truncate values should be checked + uint16 II = type(uint16).max; + int16 JJ = int16(II); //~WARN: typecasts that can truncate values should be checked + uint8 KK = type(uint8).max; + int8 LL = int8(KK); //~WARN: typecasts that can truncate values should be checked + } + + function signedUnsignedUnsafe() public pure { + int256 a = -1; + uint256 b = uint256(a); //~WARN: typecasts that can truncate values should be checked + int248 c = -1; + uint248 d = uint248(c); //~WARN: typecasts that can truncate values should be checked + int240 e = -1; + uint240 f = uint240(e); //~WARN: typecasts that can truncate values should be checked + int232 g = -1; + uint232 h = uint232(g); //~WARN: typecasts that can truncate values should be checked + int224 i = -1; + uint224 j = uint224(i); //~WARN: typecasts that can truncate values should be checked + int216 k = -1; + uint216 l = uint216(k); //~WARN: typecasts that can truncate values should be checked + int208 m = -1; + uint208 n = uint208(m); //~WARN: typecasts that can truncate values should be checked + int200 o = -1; + uint200 p = uint200(o); //~WARN: typecasts that can truncate values should be checked + int192 q = -1; + uint192 r = uint192(q); //~WARN: typecasts that can truncate values should be checked + int184 s = -1; + uint184 t = uint184(s); //~WARN: typecasts that can truncate values should be checked + int176 u = -1; + uint176 v = uint176(u); //~WARN: typecasts that can truncate values should be checked + int168 w = -1; + uint168 x = uint168(w); //~WARN: typecasts that can truncate values should be checked + int160 y = -1; + uint160 z = uint160(y); //~WARN: typecasts that can truncate values should be checked + int152 A = -1; + uint152 B = uint152(A); //~WARN: typecasts that can truncate values should be checked + int144 C = -1; + uint144 D = uint144(C); //~WARN: typecasts that can truncate values should be checked + int136 E = -1; + uint136 F = uint136(E); //~WARN: typecasts that can truncate values should be checked + int128 G = -1; + uint128 H = uint128(G); //~WARN: typecasts that can truncate values should be checked + int120 I = -1; + uint120 J = uint120(I); //~WARN: typecasts that can truncate values should be checked + int112 K = -1; + uint112 L = uint112(K); //~WARN: typecasts that can truncate values should be checked + int104 M = -1; + uint104 N = uint104(M); //~WARN: typecasts that can truncate values should be checked + int96 O = -1; + uint96 P = uint96(O); //~WARN: typecasts that can truncate values should be checked + int88 Q = -1; + uint88 R = uint88(Q); //~WARN: typecasts that can truncate values should be checked + int80 S = -1; + uint80 T = uint80(S); //~WARN: typecasts that can truncate values should be checked + int72 U = -1; + uint72 V = uint72(U); //~WARN: typecasts that can truncate values should be checked + int64 W = -1; + uint64 X = uint64(W); //~WARN: typecasts that can truncate values should be checked + int56 Y = -1; + uint56 Z = uint56(Y); //~WARN: typecasts that can truncate values should be checked + int48 AA = -1; + uint48 BB = uint48(AA); //~WARN: typecasts that can truncate values should be checked + int40 CC = -1; + uint40 DD = uint40(CC); //~WARN: typecasts that can truncate values should be checked + int32 EE = -1; + uint32 FF = uint32(EE); //~WARN: typecasts that can truncate values should be checked + int24 GG = -1; + uint24 HH = uint24(GG); //~WARN: typecasts that can truncate values should be checked + int16 II = -1; + uint16 JJ = uint16(II); //~WARN: typecasts that can truncate values should be checked + int8 KK = -1; + uint8 LL = uint8(KK); //~WARN: typecasts that can truncate values should be checked + } + + function downcastDynamicUnsafe() public pure { + bytes memory data = "hello world"; + bytes32 dataSlice = bytes32(data); //~WARN: typecasts that can truncate values should be checked + string memory str = "hello world"; + bytes32 strSlice = bytes32(bytes(str)); //~WARN: typecasts that can truncate values should be checked + } +} + +contract Repros { + function longDynamicBytesDoNotPanic() public pure { + bytes memory stringToBytes = bytes("Initializable: contract is already initialized"); + } + + function nestedCastsAreEvaluatedAtAllDepths(uint64 a, int128 b) internal pure returns (uint64) { + uint64 aAloneIsSafe = uint64(uint128(int128(uint128(a)))); + + uint128 aPlusB = uint128(int128(uint128(a)) + b); + //~^WARN: typecasts that can truncate values should be checked + + uint64 unsafe = uint64(aPlusB); + //~^WARN: typecasts that can truncate values should be checked + + return uint64(uint128(int128(uint128(a)) + b)); + //~^WARN: typecasts that can truncate values should be checked + //~|WARN: typecasts that can truncate values should be checked + } +} +// forge-lint: disable-end(mixed-case-variable) diff --git a/crates/lint/testdata/UnsafeTypecast.stderr b/crates/lint/testdata/UnsafeTypecast.stderr new file mode 100644 index 0000000000000..d909b90973e00 --- /dev/null +++ b/crates/lint/testdata/UnsafeTypecast.stderr @@ -0,0 +1,2298 @@ +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ contract UnsafeTypecast { + │ ━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +note[multi-contract-file]: prefer having only one contract, interface or library per file + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ contract Repros { + │ ━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/multi-contract-file + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint248 b = uint248(a); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint248' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint240 c = uint240(b); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint240' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint232 d = uint232(c); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint232' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint224 e = uint224(d); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint224' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint216 f = uint216(e); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint216' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint208 g = uint208(f); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint208' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint200 h = uint200(g); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint200' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint192 i = uint192(h); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint192' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint184 j = uint184(i); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint184' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint176 k = uint176(j); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint176' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint168 l = uint168(k); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint168' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint160 m = uint160(l); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint160' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint152 n = uint152(m); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint152' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint144 o = uint144(n); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint144' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint136 p = uint136(o); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint136' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint128 q = uint128(p); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint120 r = uint120(q); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint120' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint112 s = uint112(r); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint112' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint104 t = uint104(s); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint104' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint96 u = uint96(t); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint96' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint88 v = uint88(u); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint88' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint80 w = uint80(v); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint80' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint72 x = uint72(w); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint72' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint64 y = uint64(x); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint56 z = uint56(y); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint56' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint48 A = uint48(z); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint48' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint40 B = uint40(A); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint40' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint32 C = uint32(B); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint24 D = uint24(C); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint24' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint16 E = uint16(D); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint16' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint8 F = uint8(E); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint8' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int248 b = int248(a); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int248' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int240 c = int240(b); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int240' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int232 d = int232(c); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int232' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int224 e = int224(d); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int224' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int216 f = int216(e); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int216' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int208 g = int208(f); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int208' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int200 h = int200(g); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int200' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int192 i = int192(h); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int192' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int184 j = int184(i); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int184' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int176 k = int176(j); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int176' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int168 l = int168(k); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int168' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int160 m = int160(l); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int160' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int152 n = int152(m); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int152' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int144 o = int144(n); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int144' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int136 p = int136(o); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int136' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int128 q = int128(p); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int120 r = int120(q); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int120' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int112 s = int112(r); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int112' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int104 t = int104(s); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int104' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int96 u = int96(t); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int96' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int88 v = int88(u); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int88' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int80 w = int80(v); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int80' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int72 x = int72(w); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int72' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int64 y = int64(x); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int56 z = int56(y); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int56' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int48 A = int48(z); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int48' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int40 B = int40(A); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int40' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int32 C = int32(B); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int24 D = int24(C); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int24' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int16 E = int16(D); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int16' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int8 F = int8(E); + │ ━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int8' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes31 b = bytes31(a); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes31' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes30 c = bytes30(b); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes30' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes29 d = bytes29(c); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes29' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes28 e = bytes28(d); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes28' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes27 f = bytes27(e); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes27' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes26 g = bytes26(f); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes26' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes25 h = bytes25(g); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes25' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes24 i = bytes24(h); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes24' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes23 j = bytes23(i); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes23' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes22 k = bytes22(j); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes22' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes21 l = bytes21(k); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes21' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes20 m = bytes20(l); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes20' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes19 n = bytes19(m); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes19' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes18 o = bytes18(n); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes18' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes17 p = bytes17(o); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes17' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes16 q = bytes16(p); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes16' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes15 r = bytes15(q); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes15' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes14 s = bytes14(r); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes14' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes13 t = bytes13(s); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes13' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes12 u = bytes12(t); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes12' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes11 v = bytes11(u); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes11' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes10 w = bytes10(v); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes10' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes9 x = bytes9(w); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes9' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes8 y = bytes8(x); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes8' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes7 z = bytes7(y); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes7' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes6 A = bytes6(z); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes6' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes5 B = bytes5(A); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes5' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes4 C = bytes4(B); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes4' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes3 D = bytes3(C); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes3' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes2 E = bytes2(D); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes2' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes1 F = bytes1(E); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes1' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int256 b = int256(a); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int256' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int248 d = int248(c); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int248' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int240 f = int240(e); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int240' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int232 h = int232(g); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int232' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int224 j = int224(i); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int224' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int216 l = int216(k); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int216' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int208 n = int208(m); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int208' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int200 p = int200(o); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int200' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int192 r = int192(q); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int192' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int184 t = int184(s); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int184' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int176 v = int176(u); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int176' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int168 x = int168(w); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int168' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int160 z = int160(y); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int160' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int152 B = int152(A); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int152' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int144 D = int144(C); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int144' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int136 F = int136(E); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int136' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int128 H = int128(G); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int120 J = int120(I); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int120' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int112 L = int112(K); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int112' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int104 N = int104(M); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int104' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int96 P = int96(O); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int96' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int88 R = int88(Q); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int88' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int80 T = int80(S); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int80' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int72 V = int72(U); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int72' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int64 X = int64(W); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int56 Z = int56(Y); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int56' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int48 BB = int48(AA); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int48' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int40 DD = int40(CC); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int40' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int32 FF = int32(EE); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int24 HH = int24(GG); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int24' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int16 JJ = int16(II); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int16' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ int8 LL = int8(KK); + │ ━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'int8' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint256 b = uint256(a); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint256' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint248 d = uint248(c); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint248' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint240 f = uint240(e); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint240' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint232 h = uint232(g); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint232' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint224 j = uint224(i); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint224' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint216 l = uint216(k); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint216' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint208 n = uint208(m); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint208' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint200 p = uint200(o); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint200' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint192 r = uint192(q); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint192' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint184 t = uint184(s); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint184' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint176 v = uint176(u); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint176' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint168 x = uint168(w); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint168' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint160 z = uint160(y); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint160' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint152 B = uint152(A); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint152' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint144 D = uint144(C); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint144' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint136 F = uint136(E); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint136' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint128 H = uint128(G); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint120 J = uint120(I); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint120' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint112 L = uint112(K); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint112' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint104 N = uint104(M); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint104' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint96 P = uint96(O); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint96' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint88 R = uint88(Q); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint88' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint80 T = uint80(S); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint80' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint72 V = uint72(U); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint72' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint64 X = uint64(W); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint56 Z = uint56(Y); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint56' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint48 BB = uint48(AA); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint48' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint40 DD = uint40(CC); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint40' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint32 FF = uint32(EE); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint24 HH = uint24(GG); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint24' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint16 JJ = uint16(II); + │ ━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint16' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint8 LL = uint8(KK); + │ ━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint8' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes32 dataSlice = bytes32(data); + │ ━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ bytes32 strSlice = bytes32(bytes(str)); + │ ━━━━━━━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'bytes32' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint128 aPlusB = uint128(int128(uint128(a)) + b); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ uint64 unsafe = uint64(aPlusB); + │ ━━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ return uint64(uint128(int128(uint128(a)) + b)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint64' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + +warning[unsafe-typecast]: typecasts that can truncate values should be checked + ╭▸ ROOT/testdata/UnsafeTypecast.sol:LL:CC + │ +LL │ return uint64(uint128(int128(uint128(a)) + b)); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: consider disabling this lint if you're certain the cast is safe + │ + │ // casting to 'uint128' is safe because [explain why] + │ // forge-lint: disable-next-line(unsafe-typecast) + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast + diff --git a/crates/lint/testdata/UnusedStateVariables.sol b/crates/lint/testdata/UnusedStateVariables.sol new file mode 100644 index 0000000000000..f68650d2a8d01 --- /dev/null +++ b/crates/lint/testdata/UnusedStateVariables.sol @@ -0,0 +1,52 @@ +//@compile-flags: --severity gas + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract UnusedVars { + uint256 unused; //~NOTE: state variable is never used + uint256 usedInRead; + uint256 usedInWrite; + address usedInBoth; + uint256 constant CONST = 1; // skip constant + uint256 immutable IMMUT; // skip immutable + + constructor() { + usedInBoth = msg.sender; + } + + function read() external view returns (uint256) { + return usedInRead; + } + + function write(uint256 v) external { + usedInWrite = v; + } + + function both() external view returns (address) { + return usedInBoth; + } +} + +// State variables used only as modifier call arguments must not be flagged. +contract UsedInModifierArg { + uint256 limit; + uint256 unused; //~NOTE: state variable is never used + + modifier limitedBy(uint256 max) { + require(msg.value <= max); + _; + } + + function foo() external payable limitedBy(limit) {} +} + +contract MultiUnused { + uint256 firstUnused; //~NOTE: state variable is never used + uint256 secondUnused; //~NOTE: state variable is never used + uint256 usedVar; + + function use() external view returns (uint256) { + return usedVar; + } +} diff --git a/crates/lint/testdata/UnusedStateVariables.stderr b/crates/lint/testdata/UnusedStateVariables.stderr new file mode 100644 index 0000000000000..92a0082bb8293 --- /dev/null +++ b/crates/lint/testdata/UnusedStateVariables.stderr @@ -0,0 +1,40 @@ +note[could-be-immutable]: state variable could be declared immutable + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ address usedInBoth; + │ ━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/could-be-immutable + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 unused; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 unused; + │ ━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 firstUnused; + │ ━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + +note[unused-state-variables]: state variable is never used + ╭▸ ROOT/testdata/UnusedStateVariables.sol:LL:CC + │ +LL │ uint256 secondUnused; + │ ━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/unused-state-variables + diff --git a/crates/lint/testdata/UnwrappedModifierLogic.sol b/crates/lint/testdata/UnwrappedModifierLogic.sol new file mode 100644 index 0000000000000..d7d36db6f69e0 --- /dev/null +++ b/crates/lint/testdata/UnwrappedModifierLogic.sol @@ -0,0 +1,177 @@ +//@compile-flags: --severity code-size + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +library Lib { + function onlyOwner(address sender) internal {} +} + +contract C { + function onlyOwner(address sender) public {} +} + +/** + * @title UnwrappedModifierLogicTest + * @notice Test cases for the unwrapped-modifier-logic lint + * @dev This lint helps optimize gas by preventing modifier code duplication. + * Solidity inlines modifier code at each usage point instead of using jumps, + * so any logic in modifiers gets duplicated, increasing deployment costs. + */ +contract UnwrappedModifierLogicTest { + // Helpers + + C immutable c; + + event DidSomething(address who); + mapping(address => bool) isOwner; + mapping(address => mapping(bytes32 => bool)) hasRole; + + /// ----------------------------------------------------------------------- + /// Exceptions (assembly block) + /// ----------------------------------------------------------------------- + + modifier freeTempMemory() { + uint256 m; + assembly ("memory-safe") { + m := mload(0x40) + } + _; + assembly ("memory-safe") { + mstore(0x40, m) + } + } + + modifier assemblyBlock(address sender) { + assembly { + let x := sender + } + _; + } + + /// ----------------------------------------------------------------------- + /// Good patterns (only 1 valid statement before or after placeholder) + /// ----------------------------------------------------------------------- + + function checkPublic(address sender) public {} + function checkPrivate(address sender) private {} + function checkInternal(address sender) internal {} + + modifier onlyOwnerLibrary() { + Lib.onlyOwner(msg.sender); + _; + } + + modifier onlyOwnerPublic() { + checkPublic(msg.sender); + _; + } + + modifier onlyOwnerPrivate() { + checkPrivate(msg.sender); + _; + } + + modifier onlyOwnerInternal() { + checkInternal(msg.sender); + _; + } + + modifier onlyOwnerBeforeAfter() { + checkPublic(msg.sender); + _; + checkPrivate(msg.sender); + } + + /// ----------------------------------------------------------------------- + /// Bad patterns (multiple valid statements before or after placeholder) + /// ----------------------------------------------------------------------- + + // Bad because there are multiple valid function calls before the placeholder + modifier multipleBeforePlaceholder() { //~NOTE: wrap modifier logic to reduce code size + checkPublic(msg.sender); // These should become _multipleBeforePlaceholder() + checkPrivate(msg.sender); + checkInternal(msg.sender); + _; + } + + // Bad because there are multiple valid function calls after the placeholder + modifier multipleAfterPlaceholder() { //~NOTE: wrap modifier logic to reduce code size + _; + checkPublic(msg.sender); // These should become _multipleAfterPlaceholder() + checkPrivate(msg.sender); + checkInternal(msg.sender); + } + + // Bad because there are multiple valid statements both before and after + modifier multipleBeforeAfterPlaceholder(address sender) { //~NOTE: wrap modifier logic to reduce code size + checkPublic(sender); // These should become _multipleBeforeAfterPlaceholderBefore(sender) + checkPrivate(sender); + _; + checkInternal(sender); // These should become _multipleBeforeAfterPlaceholderAfter(sender) + checkPublic(sender); + } + + /// ----------------------------------------------------------------------- + /// Bad patterns (uses built-in control flow) + /// ----------------------------------------------------------------------- + + // Bad because `require` built-in is used. + modifier onlyOwner() { //~NOTE: wrap modifier logic to reduce code size + require(isOwner[msg.sender], "Not owner"); // _onlyOwner(); + _; + } + + // Bad because `if/revert` is used. + modifier onlyRole(bytes32 role) { //~NOTE: wrap modifier logic to reduce code size + if(!hasRole[msg.sender][role]) revert("Not authorized"); // _onlyRole(role); + _; + } + + // Bad because `assert` built-in is used. + modifier onlyRoleOrOpenRole(bytes32 role) { //~NOTE: wrap modifier logic to reduce code size + assert(hasRole[msg.sender][role] || hasRole[address(0)][role]); // _onlyRoleOrOpenRole(role); + _; + } + + // Bad because `assert` built-in is used (ensures we can parse multiple params). + modifier onlyRoleOrAdmin(bytes32 role, address admin) { //~NOTE: wrap modifier logic to reduce code size + assert(hasRole[msg.sender][role] || msg.sender == admin); // _onlyRoleOrAdmin(role, admin); + _; + } + + /// ----------------------------------------------------------------------- + /// Bad patterns (other invalid expressions and statements) + /// ----------------------------------------------------------------------- + + // Only call expressions are allowed (public/private/internal functions). + modifier assign(address sender) { //~NOTE: wrap modifier logic to reduce code size + bool _isOwner = true; + isOwner[sender] = _isOwner; + _; + } + + // Only call expressions are allowed (public/private/internal functions). + modifier uncheckedBlock(address sender) { //~NOTE: wrap modifier logic to reduce code size + unchecked { + sender; + } + _; + } + + // Only call expressions are allowed (public/private/internal functions). + modifier emitEvent(address sender) { //~NOTE: wrap modifier logic to reduce code size + emit DidSomething(sender); + _; + } + + /// ----------------------------------------------------------------------- + /// Bad patterns (contract calls) + /// ----------------------------------------------------------------------- + + // Bad because there's an external call. + modifier onlyOwnerContract(address sender) { //~NOTE: wrap modifier logic to reduce code size + c.onlyOwner(sender); + _; + } +} diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr new file mode 100644 index 0000000000000..dc5514c2d5e98 --- /dev/null +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -0,0 +1,265 @@ +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier multipleBeforePlaceholder() { +LL │ ┃ checkPublic(msg.sender); // These should become _multipleBeforePlaceholder() +LL │ ┃ checkPrivate(msg.sender); +LL │ ┃ checkInternal(msg.sender); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier multipleBeforePlaceholder() { +LL + _multipleBeforePlaceholder(); +LL + _; +LL + } +LL + +LL + function _multipleBeforePlaceholder() internal { +LL + checkPublic(msg.sender); +LL + checkPrivate(msg.sender); +LL + checkInternal(msg.sender); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier multipleAfterPlaceholder() { +LL │ ┃ _; +LL │ ┃ checkPublic(msg.sender); // These should become _multipleAfterPlaceholder() +LL │ ┃ checkPrivate(msg.sender); +LL │ ┃ checkInternal(msg.sender); +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier multipleAfterPlaceholder() { +LL + _; +LL + _multipleAfterPlaceholder(); +LL + } +LL + +LL + function _multipleAfterPlaceholder() internal { +LL + checkPublic(msg.sender); +LL + checkPrivate(msg.sender); +LL + checkInternal(msg.sender); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier multipleBeforeAfterPlaceholder(address sender) { +LL │ ┃ checkPublic(sender); // These should become _multipleBeforeAfterPlaceholderBefore(sender) +LL │ ┃ checkPrivate(sender); +LL │ ┃ _; +LL │ ┃ checkInternal(sender); // These should become _multipleBeforeAfterPlaceholderAfter(sender) +LL │ ┃ checkPublic(sender); +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier multipleBeforeAfterPlaceholder(address sender) { +LL + _multipleBeforeAfterPlaceholderBefore(sender); +LL + _; +LL + _multipleBeforeAfterPlaceholderAfter(sender); +LL + } +LL + +LL + function _multipleBeforeAfterPlaceholderBefore(address sender) internal { +LL + checkPublic(sender); +LL + checkPrivate(sender); +LL + } +LL + +LL + function _multipleBeforeAfterPlaceholderAfter(address sender) internal { +LL + checkInternal(sender); +LL + checkPublic(sender); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier onlyOwner() { +LL │ ┃ require(isOwner[msg.sender], "Not owner"); // _onlyOwner(); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier onlyOwner() { +LL + _onlyOwner(); +LL + _; +LL + } +LL + +LL + function _onlyOwner() internal { +LL + require(isOwner[msg.sender], "Not owner"); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier onlyRole(bytes32 role) { +LL │ ┃ if(!hasRole[msg.sender][role]) revert("Not authorized"); // _onlyRole(role); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier onlyRole(bytes32 role) { +LL + _onlyRole(role); +LL + _; +LL + } +LL + +LL + function _onlyRole(bytes32 role) internal { +LL + if(!hasRole[msg.sender][role]) revert("Not authorized"); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier onlyRoleOrOpenRole(bytes32 role) { +LL │ ┃ assert(hasRole[msg.sender][role] || hasRole[address(0)][role]); // _onlyRoleOrOpenRole(role); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier onlyRoleOrOpenRole(bytes32 role) { +LL + _onlyRoleOrOpenRole(role); +LL + _; +LL + } +LL + +LL + function _onlyRoleOrOpenRole(bytes32 role) internal { +LL + assert(hasRole[msg.sender][role] || hasRole[address(0)][role]); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier onlyRoleOrAdmin(bytes32 role, address admin) { +LL │ ┃ assert(hasRole[msg.sender][role] || msg.sender == admin); // _onlyRoleOrAdmin(role, admin); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier onlyRoleOrAdmin(bytes32 role, address admin) { +LL + _onlyRoleOrAdmin(role, admin); +LL + _; +LL + } +LL + +LL + function _onlyRoleOrAdmin(bytes32 role, address admin) internal { +LL + assert(hasRole[msg.sender][role] || msg.sender == admin); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier assign(address sender) { +LL │ ┃ bool _isOwner = true; +LL │ ┃ isOwner[sender] = _isOwner; +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier assign(address sender) { +LL + _assign(sender); +LL + _; +LL + } +LL + +LL + function _assign(address sender) internal { +LL + bool _isOwner = true; +LL + isOwner[sender] = _isOwner; +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier uncheckedBlock(address sender) { +LL │ ┃ unchecked { +LL │ ┃ sender; + ‡ ┃ +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier uncheckedBlock(address sender) { +LL + _uncheckedBlock(sender); +LL + _; +LL + } +LL + +LL + function _uncheckedBlock(address sender) internal { +LL + unchecked { +LL + sender; +LL + } +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier emitEvent(address sender) { +LL │ ┃ emit DidSomething(sender); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier emitEvent(address sender) { +LL + _emitEvent(sender); +LL + _; +LL + } +LL + +LL + function _emitEvent(address sender) internal { +LL + emit DidSomething(sender); +LL + } + ╰╴ + +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size + ╭▸ ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC + │ +LL │ ┏ modifier onlyOwnerContract(address sender) { +LL │ ┃ c.onlyOwner(sender); +LL │ ┃ _; +LL │ ┃ } + │ ┗━━━━━┛ + │ + ╰ help: https://getfoundry.sh/forge/linting/unwrapped-modifier-logic +help: wrap modifier logic to reduce code size + ╭╴ +LL ± modifier onlyOwnerContract(address sender) { +LL + _onlyOwnerContract(sender); +LL + _; +LL + } +LL + +LL + function _onlyOwnerContract(address sender) internal { +LL + c.onlyOwner(sender); +LL + } + ╰╴ + diff --git a/crates/lint/testdata/VarReadUsingThis.sol b/crates/lint/testdata/VarReadUsingThis.sol new file mode 100644 index 0000000000000..d34aa88700c8d --- /dev/null +++ b/crates/lint/testdata/VarReadUsingThis.sol @@ -0,0 +1,286 @@ +//@compile-flags: --only-lint var-read-using-this + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library Lib { + function foo() internal pure returns (uint256) { + return 1; + } +} + +interface IThing { + function value() external view returns (uint256); +} + +contract Other { + uint256 public value; +} + +contract Base { + uint256 public baseVar; + + function basePublicView() public view returns (uint256) { + return baseVar; + } +} + +contract VarReadUsingThis is Base { + uint256 public counter; + mapping(uint256 => address) public owners; + uint256[] public items; + mapping(uint256 => mapping(address => uint256)) public balances; + uint256 internal internalVar; + uint256 private privateVar; + + // State-variable initializer is walked too (runs in the synthesized constructor). + uint256 public initFromThis = uint256(uint160(address(this))) + this.counter(); //~NOTE: reading a state variable via `this` + + event Counted(uint256); + error BadCount(uint256); + + struct Info { + uint256 a; + uint256 b; + } + Info public info; + + Other public other; + + modifier withArg(uint256) { + _; + } + + constructor() { + // Reading `this` in the constructor reverts at runtime, but the lint still applies. + counter = this.counter(); //~NOTE: reading a state variable via `this` + } + + receive() external payable { + items.push(this.counter()); //~NOTE: reading a state variable via `this` + } + + // SHOULD FAIL: + + function simpleGetter() external view returns (uint256) { + return this.counter(); //~NOTE: reading a state variable via `this` + } + + function mappingGetter(uint256 k) external view returns (address) { + return this.owners(k); //~NOTE: reading a state variable via `this` + } + + function arrayGetter(uint256 i) external view returns (uint256) { + return this.items(i); //~NOTE: reading a state variable via `this` + } + + function nestedMappingGetter(uint256 a, address b) external view returns (uint256) { + return this.balances(a, b); //~NOTE: reading a state variable via `this` + } + + // Edge case: struct getter has multiple returns; emitted without an auto-fix. + function structGetter() external view returns (uint256, uint256) { + return this.info(); //~NOTE: reading a state variable via `this` + } + + function publicViewLocal() public view returns (uint256) { + return counter; + } + + function callPublicView() external view returns (uint256) { + return this.publicViewLocal(); //~NOTE: reading a state variable via `this` + } + + // Improvement over Slither: external view/pure called via `this` is also flagged. + function externalViewLocal() external view returns (uint256) { + return counter; + } + + function callExternalView() external view returns (uint256) { + return this.externalViewLocal(); //~NOTE: reading a state variable via `this` + } + + function inheritedStateVar() external view returns (uint256) { + return this.baseVar(); //~NOTE: reading a state variable via `this` + } + + function inheritedView() external view returns (uint256) { + return this.basePublicView(); //~NOTE: reading a state variable via `this` + } + + function parenAroundThis() external view returns (uint256) { + return (this).counter(); //~NOTE: reading a state variable via `this` + } + + function parenAroundCallee() external view returns (uint256) { + return (this.counter)(); //~NOTE: reading a state variable via `this` + } + + // Edge case: call options like `{gas: ...}` are flagged but no auto-fix is offered. + function withCallOptions() external view returns (uint256) { + return this.publicViewLocal{gas: 10000}(); //~NOTE: reading a state variable via `this` + } + + modifier checkCounter() { + require(this.counter() > 0, "zero"); //~NOTE: reading a state variable via `this` + _; + } + + function gated() external checkCounter {} + + // Modifier-invocation arguments are walked (this would be missed by a body-only walk). + function gatedWithArg() external view withArg(this.counter()) returns (uint256) { //~NOTE: reading a state variable via `this` + return 0; + } + + function takesUint(uint256 x) public pure returns (uint256) { + return x; + } + + // Both inner and outer `this.X(...)` calls are flagged. + function nestedCalls() external view returns (uint256) { + return this.publicViewLocal() + this.counter(); //~NOTE: reading a state variable via `this` + //~^NOTE: reading a state variable via `this` + } + + function nestedAsArg() external view returns (uint256) { + return this.takesUint(this.counter()); //~NOTE: reading a state variable via `this` + //~^NOTE: reading a state variable via `this` + } + + function inEmit() external { + emit Counted(this.counter()); //~NOTE: reading a state variable via `this` + } + + function inRevert() external view { + revert BadCount(this.counter()); //~NOTE: reading a state variable via `this` + } + + function inIfCondition() external view returns (uint256) { + if (this.counter() > 0) { //~NOTE: reading a state variable via `this` + return 1; + } + return 0; + } + + function inTernary(bool b) external view returns (uint256) { + return b ? this.counter() : this.publicViewLocal(); //~NOTE: reading a state variable via `this` + //~^NOTE: reading a state variable via `this` + } + + function inLoop(uint256 n) external view returns (uint256) { + uint256 sum; + for (uint256 i = 0; i < n; ++i) { + sum += this.counter(); //~NOTE: reading a state variable via `this` + } + return sum; + } + + function inUnchecked() external view returns (uint256) { + unchecked { + return this.counter() + 1; //~NOTE: reading a state variable via `this` + } + } + + // Inner `this.X(...)` inside a `try` argument is still flagged. + function tryWithNestedRead() external returns (uint256) { + try this.externalViewLocal() returns (uint256 v) { + return v + this.counter(); //~NOTE: reading a state variable via `this` + } catch { + return 0; + } + } + + // SHOULD PASS: + + function directAccess() external view returns (uint256) { + return counter; + } + + // Edge case: function reference (no call) must not be flagged. + function functionReference() external view returns (function() external view returns (uint256)) { + return this.publicViewLocal; + } + + function callOnOther() external view returns (uint256) { + return other.value(); + } + + function publicMutating() public { + counter += 1; + } + + function callMutating() external { + this.publicMutating(); + } + + function internalView() internal view returns (uint256) { + return counter; + } + + function callInternalView() external view returns (uint256) { + return internalView(); + } + + // Edge case: `super` is a delegatecall mechanism, not `this`. + function viaSuper() external view returns (uint256) { + return super.basePublicView(); + } + + function callLib() external pure returns (uint256) { + return Lib.foo(); + } + + // Edge case: `try this.X()` requires an external call by Solidity rules, + // so the outer call is intentional and must not be flagged. + function tryExternalView() external returns (uint256) { + try this.externalViewLocal() returns (uint256 v) { + return v; + } catch { + return 0; + } + } + + // Edge case: explicit interface cast through `address(this)` is not followed. + function viaAddressThisCast() external view returns (uint256) { + return Other(address(this)).value(); + } + + // Edge case: `this.balance` is a builtin address member, not a function call. + function thisBalance() external view returns (uint256) { + return address(this).balance; + } + + // Edge case: `this.foo.selector` is a function-pointer member access, not a call. + function selectorAccess() external view returns (bytes4) { + return this.publicViewLocal.selector; + } + + // Edge case: inline `disable-next-line` must suppress the diagnostic. + function suppressed() external view returns (uint256) { + // forge-lint: disable-next-line(var-read-using-this) + return this.counter(); + } + + // Edge case: same-arity overloads with mixed mutability — solar's HIR doesn't + // carry the resolved overload, so we conservatively skip flagging to avoid a + // false positive on the mutating overload below. + function ambiguous(uint256 x) public view returns (uint256) { + return x; + } + function ambiguous(address) public { + counter += 1; + } + function callAmbiguous() external view returns (uint256) { + return this.ambiguous(0); + } +} + +// Abstract contracts have `this`, so the lint still applies. +abstract contract AbstractCase { + uint256 public abstractVar; + + function readAbstract() external view returns (uint256) { + return this.abstractVar(); //~NOTE: reading a state variable via `this` + } +} diff --git a/crates/lint/testdata/VarReadUsingThis.stderr b/crates/lint/testdata/VarReadUsingThis.stderr new file mode 100644 index 0000000000000..39c91369dfc2b --- /dev/null +++ b/crates/lint/testdata/VarReadUsingThis.stderr @@ -0,0 +1,275 @@ +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ uint256 public initFromThis = uint256(uint160(address(this))) + this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ counter = this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ items.push(this.counter()); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.owners(k); + │ ━━━━━━━━━━━━━━ help: consider accessing storage directly: `owners[k]` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.items(i); + │ ━━━━━━━━━━━━━ help: consider accessing storage directly: `items[i]` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.balances(a, b); + │ ━━━━━━━━━━━━━━━━━━━ help: consider accessing storage directly: `balances[a][b]` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.info(); + │ ━━━━━━━━━━━ + │ + ├ note: read the state variable directly instead of via `this.` + │ + │ read the state variable directly: `info` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.publicViewLocal(); + │ ━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `publicViewLocal(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.externalViewLocal(); + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `externalViewLocal(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.baseVar(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `baseVar` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.basePublicView(); + │ ━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `basePublicView(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return (this).counter(); + │ ━━━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return (this.counter)(); + │ ━━━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.publicViewLocal{gas: 10000}(); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ require(this.counter() > 0, "zero"); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ function gatedWithArg() external view withArg(this.counter()) returns (uint256) { + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.publicViewLocal() + this.counter(); + │ ━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `publicViewLocal(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.publicViewLocal() + this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.takesUint(this.counter()); + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `takesUint(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.takesUint(this.counter()); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ emit Counted(this.counter()); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ revert BadCount(this.counter()); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ if (this.counter() > 0) { + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return b ? this.counter() : this.publicViewLocal(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return b ? this.counter() : this.publicViewLocal(); + │ ━━━━━━━━━━━━━━━━━━━━━━ + │ + ├ note: avoid the STATICCALL by invoking the function directly + │ + │ call directly without `this.`: `publicViewLocal(...)` + │ + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ sum += this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.counter() + 1; + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return v + this.counter(); + │ ━━━━━━━━━━━━━━ help: consider reading the state variable directly: `counter` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + +note[var-read-using-this]: reading a state variable via `this` causes an unnecessary STATICCALL; access it directly + ╭▸ ROOT/testdata/VarReadUsingThis.sol:LL:CC + │ +LL │ return this.abstractVar(); + │ ━━━━━━━━━━━━━━━━━━ help: consider reading the state variable directly: `abstractVar` + │ + ╰ help: https://getfoundry.sh/forge/linting/var-read-using-this + diff --git a/crates/lint/testdata/auxiliary/ImportsAnotherFile.sol b/crates/lint/testdata/auxiliary/ImportsAnotherFile.sol new file mode 100644 index 0000000000000..78fdf3ba92f83 --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsAnotherFile.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract AnotherFile { + // This contract is not used +} diff --git a/crates/lint/testdata/auxiliary/ImportsAnotherFile2.sol b/crates/lint/testdata/auxiliary/ImportsAnotherFile2.sol new file mode 100644 index 0000000000000..8730cee98a1ad --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsAnotherFile2.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract AnotherDummy { + // This file is not used +} diff --git a/crates/lint/testdata/auxiliary/ImportsConstants.sol b/crates/lint/testdata/auxiliary/ImportsConstants.sol new file mode 100644 index 0000000000000..0159ab5db0ccb --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsConstants.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +uint256 constant CONSTANT_0 = 42; +uint256 constant CONSTANT_1 = 99; diff --git a/crates/lint/testdata/auxiliary/ImportsFile.sol b/crates/lint/testdata/auxiliary/ImportsFile.sol new file mode 100644 index 0000000000000..d838257fb4fe5 --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsFile.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "./ImportsTypes.sol"; + +library symbol0 { + function isUsed(address) internal pure returns (bool) { + return true; + } +} + +type symbol1 is uint128; +type symbol3 is bytes32; +type symbol4 is uint256; +type symbol5 is uint256; +type symbol2 is bool; +type symbolNotUsed is address; +type symbolNotUsed2 is address; +type symbolNotUsed3 is address; + +abstract contract BaseContract { + function foo(uint256 a, symbol5 b) external virtual returns (uint256); +} +interface IContract { + function foo(uint256 a, uint248 b) external returns (uint256); + function convert(address addr) external pure returns (MyOtherType); +} + +interface IContractNotUsed { + function doSomething() external; +} + +interface docSymbol {} +interface docSymbol2 {} +interface docSymbolWrongTag {} + +interface eventSymbol { + event foo(uint256 bar); +} diff --git a/crates/lint/testdata/auxiliary/ImportsSomeFile.sol b/crates/lint/testdata/auxiliary/ImportsSomeFile.sol new file mode 100644 index 0000000000000..4cab7e99475e3 --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsSomeFile.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +library SomeFile { + struct Baz { + uint256 amount; + address owner; + } +} diff --git a/crates/lint/testdata/auxiliary/ImportsSomeFile2.sol b/crates/lint/testdata/auxiliary/ImportsSomeFile2.sol new file mode 100644 index 0000000000000..d4ac534e8ff19 --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsSomeFile2.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +struct Baz { + address sender; + uint256 value; +} diff --git a/crates/lint/testdata/auxiliary/ImportsTypes.sol b/crates/lint/testdata/auxiliary/ImportsTypes.sol new file mode 100644 index 0000000000000..54eab60cf444e --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsTypes.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +type MyType is uint256; +type MyOtherType is uint256; +type YetAnotherType is bool; diff --git a/crates/lint/testdata/auxiliary/ImportsUtils.sol b/crates/lint/testdata/auxiliary/ImportsUtils.sol new file mode 100644 index 0000000000000..975b71939236c --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsUtils.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +function process(bool flag) pure returns (uint256) { + return flag ? 1 : 0; +} + +function calculate(uint256 a, uint256 b) pure returns (uint256) { + return a + b; +} diff --git a/crates/lint/testdata/auxiliary/ImportsUtils2.sol b/crates/lint/testdata/auxiliary/ImportsUtils2.sol new file mode 100644 index 0000000000000..975b71939236c --- /dev/null +++ b/crates/lint/testdata/auxiliary/ImportsUtils2.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +function process(bool flag) pure returns (uint256) { + return flag ? 1 : 0; +} + +function calculate(uint256 a, uint256 b) pure returns (uint256) { + return a + b; +} diff --git a/crates/lint/testdata/auxiliary/Test.sol b/crates/lint/testdata/auxiliary/Test.sol new file mode 100644 index 0000000000000..640926eb35a15 --- /dev/null +++ b/crates/lint/testdata/auxiliary/Test.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +contract Test { + Vm vm; +} + +interface Vm { + // Unsafe cheatcodes + function ffi(string[] calldata) external returns (bytes memory); + function readFile(string calldata) external returns (string memory); + function readLine(string calldata) external returns (string memory); + function writeFile(string calldata, string calldata) external; + function writeLine(string calldata, string calldata) external; + function removeFile(string calldata) external; + function closeFile(string calldata) external; + function setEnv(string calldata, string calldata) external; + function deriveKey(string calldata, uint32) external returns (uint256); + + // Safe cheatcodes + function prank(address) external; + function deal(address, uint256) external; + function warp(uint256) external; + function roll(uint256) external; + function assume(bool) external; + function expectRevert() external; +} diff --git a/crates/macros/src/cheatcodes.rs b/crates/macros/src/cheatcodes.rs index 76eb32291a3e3..363c03c73c9e4 100644 --- a/crates/macros/src/cheatcodes.rs +++ b/crates/macros/src/cheatcodes.rs @@ -2,6 +2,14 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{Attribute, Data, DataStruct, DeriveInput, Error, Result}; +// TODO: `proc_macro_error2` only emits warnings when feature "nightly" is enabled, which we can't +// practically enable. +macro_rules! emit_warning { + ($($t:tt)*) => { + proc_macro_error2::emit_error! { $($t)* } + }; +} + pub fn derive_cheatcode(input: &DeriveInput) -> Result { let name = &input.ident; let name_s = name.to_string(); @@ -9,9 +17,9 @@ pub fn derive_cheatcode(input: &DeriveInput) -> Result { Data::Struct(s) if name_s.ends_with("Call") => derive_call(name, s, &input.attrs), Data::Struct(_) if name_s.ends_with("Return") => Ok(TokenStream::new()), Data::Struct(s) => derive_struct(name, s, &input.attrs), - Data::Enum(e) if name_s.ends_with("Calls") => derive_calls_enum(name, e), - Data::Enum(e) if name_s.ends_with("Errors") => derive_errors_events_enum(name, e, false), - Data::Enum(e) if name_s.ends_with("Events") => derive_errors_events_enum(name, e, true), + Data::Enum(e) if name_s.ends_with("Calls") => derive_calls_enum(e), + Data::Enum(e) if name_s.ends_with("Errors") => derive_errors_events_enum(e, false), + Data::Enum(e) if name_s.ends_with("Events") => derive_errors_events_enum(e, true), Data::Enum(e) => derive_enum(name, e, &input.attrs), Data::Union(_) => Err(Error::new(name.span(), "unions are not supported")), } @@ -99,21 +107,21 @@ fn derive_call(name: &Ident, data: &DataStruct, attrs: &[Attribute]) -> Result Result { - if input.variants.iter().any(|v| v.fields.len() != 1) { - return Err(syn::Error::new(name.span(), "expected all variants to have a single field")) +fn sorted_variant_types(input: &syn::DataEnum) -> Result> { + if let Some(v) = input.variants.iter().find(|v| v.fields.len() != 1) { + return Err(syn::Error::new(v.ident.span(), "expected variant to have a single field")); } - // keep original order for matching + let mut variants: Vec<_> = input.variants.iter().collect(); + variants.sort_by_key(|v| &v.ident); + Ok(variants.into_iter().map(|v| &v.fields.iter().next().unwrap().ty).collect()) +} + +/// Generates the `CHEATCODES` constant and implements `CheatcodeImpl` dispatch for an enum. +fn derive_calls_enum(input: &syn::DataEnum) -> Result { + let variant_tys = sorted_variant_types(input)?; let variant_names = input.variants.iter().map(|v| &v.ident); - let mut variants = input.variants.iter().collect::>(); - variants.sort_by(|a, b| a.ident.cmp(&b.ident)); - let variant_tys = variants.iter().map(|v| { - assert_eq!(v.fields.len(), 1); - &v.fields.iter().next().unwrap().ty - }); Ok(quote! { /// All the cheatcodes in [this contract](self). pub const CHEATCODES: &'static [&'static Cheatcode<'static>] = &[#(<#variant_tys as CheatcodeDef>::CHEATCODE,)*]; @@ -129,14 +137,8 @@ fn derive_calls_enum(name: &Ident, input: &syn::DataEnum) -> Result }) } -fn derive_errors_events_enum( - name: &Ident, - input: &syn::DataEnum, - events: bool, -) -> Result { - if input.variants.iter().any(|v| v.fields.len() != 1) { - return Err(syn::Error::new(name.span(), "expected all variants to have a single field")) - } +fn derive_errors_events_enum(input: &syn::DataEnum, events: bool) -> Result { + let variant_tys = sorted_variant_types(input)?; let (ident, ty_assoc_name, ty, doc) = if events { ("VM_EVENTS", "EVENT", "Event", "events") @@ -148,12 +150,6 @@ fn derive_errors_events_enum( let ty = Ident::new(ty, Span::call_site()); let doc = format!("All the {doc} in [this contract](self)."); - let mut variants = input.variants.iter().collect::>(); - variants.sort_by(|a, b| a.ident.cmp(&b.ident)); - let variant_tys = variants.iter().map(|v| { - assert_eq!(v.fields.len(), 1); - &v.fields.iter().next().unwrap().ty - }); Ok(quote! { #[doc = #doc] pub const #ident: &'static [&'static #ty<'static>] = &[#(#variant_tys::#ty_assoc_name,)*]; @@ -210,9 +206,7 @@ fn derive_struct( let ty = &def[..ty_end]; let ty_start = ty.rfind(';').or_else(|| ty.find('{')).expect("bad struct def") + 1; let ty = ty[ty_start..].trim(); - if ty.is_empty() { - panic!("bad struct def: {def:?}") - } + assert!(!ty.is_empty(), "bad struct def: {def:?}"); let doc = get_docstring(&f.attrs); let doc = doc.trim(); @@ -269,7 +263,7 @@ enum StructKind { } impl StructKind { - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Struct => "struct", Self::Error => "error", @@ -325,14 +319,14 @@ fn get_docstring(attrs: &[Attribute]) -> String { let mut doc = String::new(); for attr in attrs { if !attr.path().is_ident("doc") { - continue + continue; } let syn::Meta::NameValue(syn::MetaNameValue { value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }), .. }) = &attr.meta else { - continue + continue; }; let value = s.value(); @@ -406,7 +400,7 @@ fn func_docstring(doc: &str) -> (&str, &str, &str, &str) { /// Returns `(visibility, mutability)` from a given Solidity function declaration. fn parse_function_attrs(f: &str, span: Span) -> Result<(&str, &str)> { let Some(ext_start) = f.find("external") else { - return Err(Error::new(span, "functions must have `external` visibility")) + return Err(Error::new(span, "functions must have `external` visibility")); }; let visibility = "External"; diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index b8cdbc54db340..245ba6e5b78e2 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -1,13 +1,13 @@ use proc_macro2::{Delimiter, Group, Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{ - punctuated::Punctuated, Data, DataEnum, DataStruct, DeriveInput, Fields, Member, Token, Type, + Data, DataEnum, DataStruct, DeriveInput, Fields, Member, Token, Type, punctuated::Punctuated, }; pub fn console_fmt(input: &DeriveInput) -> TokenStream { let name = &input.ident; let tokens = match &input.data { - Data::Struct(s) => derive_struct(s), + Data::Struct(s) => derive_struct(s, name), Data::Enum(e) => derive_enum(e), Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; @@ -18,8 +18,8 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { } } -fn derive_struct(s: &DataStruct) -> TokenStream { - let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); +fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream { + let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new())); quote! { fn fmt(&self, _spec: FormatSpec) -> String { #imp @@ -27,7 +27,7 @@ fn derive_struct(s: &DataStruct) -> TokenStream { } } -fn impl_struct(s: &DataStruct) -> Option { +fn impl_struct(s: &DataStruct, name: &Ident) -> Option { if s.fields.is_empty() { return None; } @@ -36,21 +36,54 @@ fn impl_struct(s: &DataStruct) -> Option { return None; } + let members = s.fields.members().collect::>(); let fields = s.fields.iter().collect::>(); + + // Detect table call structs: name must start with "table" (from the ABI function name) and + // all fields must be Vec types (Solidity arrays). Both conditions together prevent + // accidental table rendering for unrelated structs that happen to have Vec fields. + let is_table = name.to_string().starts_with("table") + && !fields.is_empty() + && fields.iter().all(|f| match &f.ty { + Type::Path(path) => path.path.segments.last().is_some_and(|seg| seg.ident == "Vec"), + _ => false, + }); + if is_table { + let member_ref = |m: &Member| match m { + Member::Named(ident) => quote!(&self.#ident), + Member::Unnamed(idx) => quote!(&self.#idx), + }; + let imp = if members.len() == 1 { + let vals = member_ref(&members[0]); + quote! { + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(None, &values) + } + } else { + let keys = member_ref(&members[0]); + let vals = member_ref(&members[1]); + quote! { + let keys: ::std::vec::Vec<&dyn ConsoleFmt> = + (#keys).iter().map(|v| v as &dyn ConsoleFmt).collect(); + let values: ::std::vec::Vec<&dyn ConsoleFmt> = + (#vals).iter().map(|v| v as &dyn ConsoleFmt).collect(); + console_table_format(Some(&keys), &values) + } + }; + return Some(imp); + } + let first_ty = match &fields.first().unwrap().ty { Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), _ => String::new(), }; - let members = s.fields.members().collect::>(); let args: Punctuated = members .into_iter() .map(|member| match member { Member::Named(ident) => quote!(&self.#ident), - // For Tuple structs generated by the sol!. - // These are generated only in case of a single unnamed field, hence it is safe to - // hardcode the index to `.0`. - Member::Unnamed(_) => quote!(&self.0), + Member::Unnamed(idx) => quote!(&self.#idx), }) .collect(); @@ -84,9 +117,7 @@ fn derive_enum(e: &DataEnum) -> TokenStream { let fields: Punctuated = fields .enumerate() - .map(|(i, field)| { - field.ident.as_ref().cloned().unwrap_or_else(|| format_ident!("__var_{i}")) - }) + .map(|(i, field)| field.ident.clone().unwrap_or_else(|| format_ident!("__var_{i}"))) .collect(); if fields.len() != 1 { diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 375434f82d246..4d8b922437b13 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -3,13 +3,13 @@ //! Internal Foundry proc-macros. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate proc_macro_error2; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput, Error}; +use syn::{DeriveInput, Error, parse_macro_input}; mod cheatcodes; mod console_fmt; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml new file mode 100644 index 0000000000000..15363a6a40bb0 --- /dev/null +++ b/crates/primitives/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "foundry-primitives" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-consensus.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-provider.workspace = true +alloy-rlp.workspace = true +alloy-rpc-types = { workspace = true, features = ["trace"] } +alloy-rpc-types-eth.workspace = true +alloy-serde.workspace = true +alloy-signer.workspace = true +alloy-evm.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde", "alloy-compat"], optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +alloy-op-evm = { workspace = true, optional = true } +op-revm = { workspace = true, optional = true } +revm.workspace = true +serde_json.workspace = true +serde = { version = "1.0", features = ["derive"] } +derive_more.workspace = true +tempo-primitives.workspace = true +tempo-alloy.workspace = true +tempo-revm.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:alloy-op-evm", + "dep:op-revm", +] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs new file mode 100644 index 0000000000000..c96dbbcf5b783 --- /dev/null +++ b/crates/primitives/src/lib.rs @@ -0,0 +1,6 @@ +//! Foundry primitives +mod network; +mod transaction; + +pub use network::*; +pub use transaction::*; diff --git a/crates/primitives/src/network/mod.rs b/crates/primitives/src/network/mod.rs new file mode 100644 index 0000000000000..0b840185b0383 --- /dev/null +++ b/crates/primitives/src/network/mod.rs @@ -0,0 +1,64 @@ +use alloy_network::Network; + +#[cfg(feature = "optimism")] +mod optimism; +mod receipt; + +use alloy_provider::fillers::{ + BlobGasFiller, ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, +}; +#[cfg(feature = "optimism")] +pub use optimism::FoundryTransactionResponse; +pub use receipt::*; + +/// Default JSON-RPC transaction response when the `optimism` feature is disabled. +#[cfg(not(feature = "optimism"))] +pub type FoundryTransactionResponse = alloy_rpc_types_eth::Transaction; + +/// Foundry network type. +/// +/// This network type supports Foundry-specific transaction types, including +/// op-stack deposit transactions, alongside standard Ethereum transaction types. +/// +/// Note: This is a basic implementation ("for now") that provides the core Network +/// trait definitions. Full Foundry-specific RPC types will be implemented in future work. +/// Currently, this uses Ethereum's Network configuration as a compatibility layer. +#[derive(Debug, Clone, Copy)] +pub struct FoundryNetwork { + _private: (), +} + +// Use Ethereum's Network trait implementation as the basis. +// This provides compatibility with the alloy-network ecosystem while we build +// out Foundry-specific RPC types. +impl Network for FoundryNetwork { + type TxType = crate::FoundryTxType; + + type TxEnvelope = crate::FoundryTxEnvelope; + + type UnsignedTx = crate::FoundryTypedTx; + + type ReceiptEnvelope = crate::FoundryReceiptEnvelope; + + type Header = alloy_consensus::Header; + + type TransactionRequest = crate::FoundryTransactionRequest; + + type TransactionResponse = FoundryTransactionResponse; + + type ReceiptResponse = crate::FoundryTxReceipt; + + type HeaderResponse = alloy_rpc_types_eth::Header; + + type BlockResponse = + alloy_rpc_types_eth::Block; +} + +impl RecommendedFillers for FoundryNetwork { + type RecommendedFillers = + JoinFill>>; + + fn recommended_fillers() -> Self::RecommendedFillers { + Default::default() + } +} diff --git a/crates/primitives/src/network/optimism.rs b/crates/primitives/src/network/optimism.rs new file mode 100644 index 0000000000000..aff30a755663f --- /dev/null +++ b/crates/primitives/src/network/optimism.rs @@ -0,0 +1,47 @@ +//! OP-stack-specific helpers and type aliases used by [`super::FoundryNetwork`] and +//! [`super::FoundryTxReceipt`]. + +use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_primitives::U64; +use alloy_rpc_types::Log; +use alloy_serde::OtherFields; +use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom}; + +use crate::FoundryReceiptEnvelope; + +/// JSON-RPC transaction response type used by [`super::FoundryNetwork`]. +pub type FoundryTransactionResponse = op_alloy_rpc_types::Transaction; + +/// Build a [`FoundryReceiptEnvelope::Deposit`] from a `ReceiptWithBloom` plus the OP +/// deposit-specific fields decoded from the [`OtherFields`] of an `AnyTransactionReceipt`. +pub(super) fn build_deposit_receipt_envelope( + receipt_with_bloom: ReceiptWithBloom>, + other: &OtherFields, +) -> FoundryReceiptEnvelope { + // These fields may not be present in all receipts, so missing/invalid values are None. + let deposit_nonce = other + .get_deserialized::("depositNonce") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + let deposit_receipt_version = other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok() + .flatten() + .map(|v| v.to::()); + + FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: Receipt { + status: alloy_consensus::Eip658Value::Eip658(receipt_with_bloom.status()), + cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(), + logs: receipt_with_bloom.receipt.logs, + }, + deposit_nonce, + deposit_receipt_version, + }, + logs_bloom: receipt_with_bloom.logs_bloom, + }) +} diff --git a/crates/primitives/src/network/receipt.rs b/crates/primitives/src/network/receipt.rs new file mode 100644 index 0000000000000..6b01f9eaa9ee9 --- /dev/null +++ b/crates/primitives/src/network/receipt.rs @@ -0,0 +1,173 @@ +use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt, ReceiptResponse}; +use alloy_primitives::{Address, B256, BlockHash, TxHash}; +use alloy_rpc_types::{ConversionError, Log, TransactionReceipt}; +use alloy_serde::WithOtherFields; +use derive_more::AsRef; +use serde::{Deserialize, Serialize}; +use tempo_primitives::TEMPO_TX_TYPE_ID; + +#[cfg(feature = "optimism")] +use super::optimism::build_deposit_receipt_envelope; +use crate::FoundryReceiptEnvelope; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AsRef)] +pub struct FoundryTxReceipt(pub WithOtherFields>>); + +impl FoundryTxReceipt { + pub fn new(inner: TransactionReceipt>) -> Self { + Self(WithOtherFields::new(inner)) + } + + /// Creates a new receipt with a timestamp in the other fields. + /// This avoids extra block lookups when timestamp is needed later. + pub fn with_timestamp( + inner: TransactionReceipt>, + timestamp: u64, + ) -> Self { + let mut receipt = WithOtherFields::new(inner); + receipt + .other + .insert("blockTimestamp".to_string(), serde_json::to_value(timestamp).unwrap()); + Self(receipt) + } + + /// Adds a `feePayer` field to the receipt. + pub fn with_fee_payer(mut self, fee_payer: Address) -> Self { + self.0.other.insert("feePayer".to_string(), serde_json::to_value(fee_payer).unwrap()); + self + } + + /// Get block timestamp from other fields if present. + pub fn block_timestamp(&self) -> Option { + self.0.other.get_deserialized::("blockTimestamp").transpose().ok().flatten() + } +} + +impl ReceiptResponse for FoundryTxReceipt { + fn contract_address(&self) -> Option
{ + self.0.contract_address + } + + fn status(&self) -> bool { + self.0.inner.status() + } + + fn block_hash(&self) -> Option { + self.0.block_hash + } + + fn block_number(&self) -> Option { + self.0.block_number + } + + fn transaction_hash(&self) -> TxHash { + self.0.transaction_hash + } + + fn transaction_index(&self) -> Option { + self.0.transaction_index() + } + + fn gas_used(&self) -> u64 { + self.0.gas_used() + } + + fn effective_gas_price(&self) -> u128 { + self.0.effective_gas_price() + } + + fn blob_gas_used(&self) -> Option { + self.0.blob_gas_used() + } + + fn blob_gas_price(&self) -> Option { + self.0.blob_gas_price() + } + + fn from(&self) -> Address { + self.0.from() + } + + fn to(&self) -> Option
{ + self.0.to() + } + + fn cumulative_gas_used(&self) -> u64 { + self.0.cumulative_gas_used() + } + + fn state_root(&self) -> Option { + self.0.state_root() + } +} + +impl TryFrom for FoundryTxReceipt { + type Error = ConversionError; + + fn try_from(receipt: AnyTransactionReceipt) -> Result { + let WithOtherFields { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type }, + }, + other, + } = receipt.0; + + Ok(Self(WithOtherFields { + inner: TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + inner: match r#type { + 0x00 => FoundryReceiptEnvelope::Legacy(receipt_with_bloom), + 0x01 => FoundryReceiptEnvelope::Eip2930(receipt_with_bloom), + 0x02 => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom), + 0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom), + 0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom), + TEMPO_TX_TYPE_ID => FoundryReceiptEnvelope::Tempo(receipt_with_bloom), + #[cfg(feature = "optimism")] + 0x7E => build_deposit_receipt_envelope(receipt_with_bloom, &other), + _ => { + let tx_type = r#type; + return Err(ConversionError::Custom(format!( + "Unknown transaction receipt type: 0x{tx_type:02X}" + ))); + } + }, + }, + other, + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // + #[test] + fn test_receipt_convert() { + let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#; + let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap(); + let _converted = FoundryTxReceipt::try_from(receipt).unwrap(); + } +} diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs new file mode 100644 index 0000000000000..0a009a1931f06 --- /dev/null +++ b/crates/primitives/src/transaction/envelope.rs @@ -0,0 +1,619 @@ +#[cfg(feature = "optimism")] +use alloy_consensus::{Sealed, Transaction as _}; +use alloy_consensus::{ + Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType, Typed2718, + crypto::RecoveryError, + transaction::{ + SignerRecoverable, TxEip7702, TxHashRef, + eip4844::{TxEip4844Variant, TxEip4844WithSidecar}, + }, +}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse}; +use alloy_primitives::{Address, B256, Bytes, TxHash}; +use alloy_rpc_types::ConversionError; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec}; +use revm::context::TxEnv; +use tempo_primitives::{AASigned, TempoTransaction}; +use tempo_revm::TempoTxEnv; + +// +/// Container type for signed, typed transactions. +// NOTE(onbjerg): Boxing `Tempo(AASigned)` breaks `TransactionEnvelope` derive macro trait bounds. +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, TransactionEnvelope)] +#[envelope( + tx_type_name = FoundryTxType, + typed = FoundryTypedTx, +)] +pub enum FoundryTxEnvelope { + /// Legacy transaction type + #[envelope(ty = 0)] + Legacy(Signed), + /// [EIP-2930] transaction. + /// + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[envelope(ty = 1)] + Eip2930(Signed), + /// [EIP-1559] transaction. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[envelope(ty = 2)] + Eip1559(Signed), + /// [EIP-4844] transaction. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + #[envelope(ty = 3)] + Eip4844(Signed), + /// [EIP-7702] transaction. + /// + /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 + #[envelope(ty = 4)] + Eip7702(Signed), + /// OP stack deposit transaction. + /// + /// See . + #[cfg(feature = "optimism")] + #[envelope(ty = 126)] + Deposit(Sealed), + /// OP stack post-execution synthetic transaction. + #[cfg(feature = "optimism")] + #[envelope(ty = 0x7D)] + PostExec(Sealed), + /// Tempo transaction type. + /// + /// See . + #[envelope(ty = 0x76, typed = TempoTransaction)] + Tempo(AASigned), +} + +impl FoundryTxEnvelope { + /// Converts the transaction into an Ethereum [`TxEnvelope`]. + /// + /// Returns an error if the transaction is not part of the standard Ethereum transaction types. + pub fn try_into_eth(self) -> Result { + match self { + Self::Legacy(tx) => Ok(TxEnvelope::Legacy(tx)), + Self::Eip2930(tx) => Ok(TxEnvelope::Eip2930(tx)), + Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)), + Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)), + Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)), + #[cfg(feature = "optimism")] + Self::Deposit(_) => Err(self), + #[cfg(feature = "optimism")] + Self::PostExec(_) => Err(self), + Self::Tempo(_) => Err(self), + } + } + + pub const fn sidecar(&self) -> Option<&TxEip4844WithSidecar> { + match self { + Self::Eip4844(signed_variant) => match signed_variant.tx() { + TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar), + _ => None, + }, + _ => None, + } + } + + /// Returns the hash of the transaction. + /// + /// # Note + /// + /// If this transaction has the Impersonated signature then this returns a modified unique + /// hash. This allows us to treat impersonated transactions as unique. + pub fn hash(&self) -> B256 { + match self { + Self::Legacy(t) => *t.hash(), + Self::Eip2930(t) => *t.hash(), + Self::Eip1559(t) => *t.hash(), + Self::Eip4844(t) => *t.hash(), + Self::Eip7702(t) => *t.hash(), + #[cfg(feature = "optimism")] + Self::Deposit(t) => t.tx_hash(), + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.tx_hash(), + Self::Tempo(t) => *t.hash(), + } + } + + /// Returns `true` if this is a Tempo transaction. + pub const fn is_tempo(&self) -> bool { + matches!(self, Self::Tempo(_)) + } + + /// Recovers the Ethereum address which was used to sign the transaction. + pub fn recover(&self) -> Result { + Ok(match self { + Self::Legacy(tx) => tx.recover_signer()?, + Self::Eip2930(tx) => tx.recover_signer()?, + Self::Eip1559(tx) => tx.recover_signer()?, + Self::Eip4844(tx) => tx.recover_signer()?, + Self::Eip7702(tx) => tx.recover_signer()?, + #[cfg(feature = "optimism")] + Self::Deposit(tx) => tx.from, + #[cfg(feature = "optimism")] + Self::PostExec(tx) => tx.inner().signer_address(), + Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?, + }) + } +} + +impl TxHashRef for FoundryTxEnvelope { + fn tx_hash(&self) -> &TxHash { + match self { + Self::Legacy(t) => t.hash(), + Self::Eip2930(t) => t.hash(), + Self::Eip1559(t) => t.hash(), + Self::Eip4844(t) => t.hash(), + Self::Eip7702(t) => t.hash(), + #[cfg(feature = "optimism")] + Self::Deposit(t) => t.hash_ref(), + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.hash_ref(), + Self::Tempo(t) => t.hash(), + } + } +} + +impl SignerRecoverable for FoundryTxEnvelope { + fn recover_signer(&self) -> Result { + self.recover() + } + + fn recover_signer_unchecked(&self) -> Result { + self.recover() + } +} + +impl TryFrom for TxEnvelope { + type Error = FoundryTxEnvelope; + + fn try_from(envelope: FoundryTxEnvelope) -> Result { + envelope.try_into_eth() + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: TxEnvelope) -> Self { + match tx { + TxEnvelope::Legacy(tx) => Self::Legacy(tx), + TxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + TxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + TxEnvelope::Eip4844(tx) => Self::Eip4844(tx), + TxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + } + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: tempo_primitives::TempoTxEnvelope) -> Self { + match tx { + tempo_primitives::TempoTxEnvelope::Legacy(tx) => Self::Legacy(tx), + tempo_primitives::TempoTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + tempo_primitives::TempoTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + tempo_primitives::TempoTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + tempo_primitives::TempoTxEnvelope::AA(tx) => Self::Tempo(tx), + } + } +} + +impl TryFrom for FoundryTxEnvelope { + type Error = ConversionError; + + fn try_from(value: AnyRpcTransaction) -> Result { + let transaction = value.into_inner(); + let from = transaction.from(); + match transaction.into_inner() { + AnyTxEnvelope::Ethereum(tx) => match tx { + TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + TxEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)), + TxEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)), + TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)), + TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)), + }, + AnyTxEnvelope::Unknown(tx) => { + #[cfg(feature = "optimism")] + { + let mut tx = tx; + let _ = from; + // Try to convert to deposit transaction + if tx.ty() == DEPOSIT_TX_TYPE_ID { + tx.inner + .fields + .insert("from".to_string(), serde_json::to_value(from).unwrap()); + let deposit_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize deposit tx: {e}" + )) + })?; + + return Ok(Self::Deposit(Sealed::new(deposit_tx))); + } + + if tx.ty() == POST_EXEC_TX_TYPE_ID { + let post_exec_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize post-exec tx: {e}" + )) + })?; + + return Ok(Self::PostExec(Sealed::new(post_exec_tx))); + } + + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) + } + #[cfg(not(feature = "optimism"))] + { + let _ = from; + let tx_type = tx.ty(); + Err(ConversionError::Custom(format!( + "Unknown transaction type: 0x{tx_type:02X}" + ))) + } + } + } + } +} + +impl FromRecoveredTx for TxEnv { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + FoundryTxEnvelope::Eip2930(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::Deposit(sealed_tx) => { + let tx = sealed_tx.inner(); + Self { + tx_type: tx.ty(), + caller, + gas_limit: tx.gas_limit, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + ..Default::default() + } + } + #[cfg(feature = "optimism")] + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + Self { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Ethereum context"), + } + } +} + +impl FromTxWithEncoded for TxEnv { + fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { + Self::from_recovered_tx(tx, sender) + } +} + +impl FromRecoveredTx for TempoTxEnv { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + Self::from(TxEnv::from_recovered_tx(signed_tx, caller)) + } + #[cfg(feature = "optimism")] + FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::PostExec(_) => unreachable!("Post-exec tx in Tempo context"), + FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller), + } + } +} + +impl FromTxWithEncoded for TempoTxEnv { + fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self { + Self::from_recovered_tx(tx, sender) + } +} + +impl std::fmt::Display for FoundryTxType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Legacy => write!(f, "legacy"), + Self::Eip2930 => write!(f, "eip2930"), + Self::Eip1559 => write!(f, "eip1559"), + Self::Eip4844 => write!(f, "eip4844"), + Self::Eip7702 => write!(f, "eip7702"), + #[cfg(feature = "optimism")] + Self::Deposit => write!(f, "deposit"), + #[cfg(feature = "optimism")] + Self::PostExec => write!(f, "post-exec"), + Self::Tempo => write!(f, "tempo"), + } + } +} + +impl From for FoundryTxType { + fn from(tx: TxType) -> Self { + match tx { + TxType::Legacy => Self::Legacy, + TxType::Eip2930 => Self::Eip2930, + TxType::Eip1559 => Self::Eip1559, + TxType::Eip4844 => Self::Eip4844, + TxType::Eip7702 => Self::Eip7702, + } + } +} + +impl From for FoundryTypedTx { + fn from(envelope: FoundryTxEnvelope) -> Self { + match envelope { + FoundryTxEnvelope::Legacy(signed_tx) => Self::Legacy(signed_tx.strip_signature()), + FoundryTxEnvelope::Eip2930(signed_tx) => Self::Eip2930(signed_tx.strip_signature()), + FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()), + FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()), + FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()), + #[cfg(feature = "optimism")] + FoundryTxEnvelope::PostExec(sealed_tx) => Self::PostExec(sealed_tx.into_inner()), + FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()), + } + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use alloy_primitives::{TxKind, U256, b256, hex}; + use alloy_rlp::Decodable; + use alloy_signer::Signature; + + use super::*; + + #[test] + fn test_decode_call() { + let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; + let decoded = FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap(); + + let tx = TxLegacy { + nonce: 2u64, + gas_price: 1000000000u128, + gas_limit: 100000, + to: TxKind::Call(Address::from_slice( + &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], + )), + value: U256::from(1000000000000000u64), + input: Bytes::default(), + chain_id: Some(4), + }; + + let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); + + let tx = FoundryTxEnvelope::Legacy(Signed::new_unchecked( + tx, + signature, + b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"), + )); + + assert_eq!(tx, decoded); + } + + #[test] + fn test_decode_create_goerli() { + // test that an example create tx from goerli decodes properly + let tx_bytes = + hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471") + .unwrap(); + let _decoded = FoundryTxEnvelope::decode(&mut &tx_bytes[..]).unwrap(); + } + + #[test] + fn can_recover_sender() { + // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f + let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap(); + + let Ok(FoundryTxEnvelope::Eip1559(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else { + panic!("decoding FoundryTxEnvelope failed"); + }; + + assert_eq!( + tx.hash(), + &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f" + .parse::() + .unwrap() + ); + assert_eq!( + tx.recover_signer().unwrap(), + "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::
().unwrap() + ); + } + + // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + #[test] + fn test_decode_live_4844_tx() { + use alloy_primitives::{address, b256}; + + // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); + let res = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + assert!(res.is_type(3)); + + let tx = match res { + FoundryTxEnvelope::Eip4844(tx) => tx, + _ => unreachable!(), + }; + + assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064")); + + assert_eq!( + tx.tx().tx().blob_versioned_hashes, + vec![ + b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"), + b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), + b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), + b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), + b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") + ] + ); + + let from = tx.recover_signer().unwrap(); + assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); + } + + #[test] + fn can_recover_sender_not_normalized() { + let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); + + let Ok(FoundryTxEnvelope::Legacy(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else { + panic!("decoding FoundryTxEnvelope failed"); + }; + + assert_eq!(tx.tx().input, Bytes::from(b"")); + assert_eq!(tx.tx().gas_price, 1); + assert_eq!(tx.tx().gas_limit, 21000); + assert_eq!(tx.tx().nonce, 0); + if let TxKind::Call(to) = tx.tx().to { + assert_eq!( + to, + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::
().unwrap() + ); + } else { + panic!("expected a call transaction"); + } + assert_eq!(tx.tx().value, U256::from(0x0au64)); + assert_eq!( + tx.recover_signer().unwrap(), + "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::
().unwrap() + ); + } + + #[test] + fn deser_to_type_tx() { + let tx = r#" + { + "type": "0x2", + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let _typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + } + + #[test] + fn test_from_recovered_tx_legacy() { + let tx = r#" + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gas": "0x5208", + "gasPrice": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x1", + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "v": "0x1b", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + let sender = typed_tx.recover().unwrap(); + + // Test TxEnv conversion via FromRecoveredTx trait + let tx_env = TxEnv::from_recovered_tx(&typed_tx, sender); + assert_eq!(tx_env.caller, sender); + assert_eq!(tx_env.gas_limit, 0x5208); + assert_eq!(tx_env.gas_price, 1); + } + + // Test vector from Tempo testnet: + // https://explorer.testnet.tempo.xyz/tx/0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd + #[test] + fn test_decode_encode_tempo_tx() { + use alloy_primitives::address; + use tempo_primitives::TEMPO_TX_TYPE_ID; + + let tx_hash: TxHash = "0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd" + .parse::() + .unwrap(); + + // Raw transaction from Tempo testnet via eth_getRawTransactionByHash + let raw_tx = hex::decode( + "76f9025e82a5bd808502cb4178008302d178f8fcf85c9420c000000000000000000000000000000000000080b844095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680f89c94dec000000000000000000000000000000000000080b884f8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330c0808080809420c000000000000000000000000000000000000180c0b90133027b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657dcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85eaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e", + ) + .unwrap(); + + let tempo_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + // Verify it's a Tempo transaction (type 0x76) + assert!(tempo_tx.is_type(TEMPO_TX_TYPE_ID)); + + let FoundryTxEnvelope::Tempo(ref aa_signed) = tempo_tx else { + panic!("Expected Tempo transaction"); + }; + + // Verify the chain ID + assert_eq!(aa_signed.tx().chain_id, 42429); + + // Verify the fee token + assert_eq!( + aa_signed.tx().fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")) + ); + + // Verify gas limit + assert_eq!(aa_signed.tx().gas_limit, 184696); + + // Verify we have 2 calls + assert_eq!(aa_signed.tx().calls.len(), 2); + + // Verify the hash + assert_eq!(tx_hash, tempo_tx.hash()); + + // Verify round-trip encoding + let mut encoded = Vec::new(); + tempo_tx.encode_2718(&mut encoded); + assert_eq!(raw_tx, encoded); + + // Verify sender recovery (WebAuthn signature) + let sender = tempo_tx.recover().unwrap(); + assert_eq!(sender, address!("0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F")); + } +} diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs new file mode 100644 index 0000000000000..18f39c437bfbc --- /dev/null +++ b/crates/primitives/src/transaction/mod.rs @@ -0,0 +1,11 @@ +mod envelope; +#[cfg(feature = "optimism")] +mod optimism; +mod receipt; +mod request; + +pub use envelope::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; +#[cfg(feature = "optimism")] +pub use optimism::get_deposit_tx_parts; +pub use receipt::FoundryReceiptEnvelope; +pub use request::FoundryTransactionRequest; diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs new file mode 100644 index 0000000000000..09a93bedb48f9 --- /dev/null +++ b/crates/primitives/src/transaction/optimism.rs @@ -0,0 +1,300 @@ +//! OP-stack-specific impls for [`FoundryTxEnvelope`] and [`FoundryTransactionRequest`]. + +use alloy_consensus::{Sealed, Transaction as _, Typed2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_op_evm::OpTx; +use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_serde::OtherFields; +use op_alloy_consensus::{ + OpDepositReceipt, OpDepositReceiptWithBloom, OpTransaction as OpTransactionTrait, OpTxEnvelope, + TxDeposit, TxPostExec, +}; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +use revm::context::TxEnv; + +use super::{FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope}; + +impl OpTransactionTrait for FoundryTxEnvelope { + fn is_deposit(&self) -> bool { + matches!(self, Self::Deposit(_)) + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + Self::Deposit(tx) => Some(tx), + _ => None, + } + } + + fn as_post_exec(&self) -> Option<&Sealed> { + if let Self::PostExec(tx) = self { Some(tx) } else { None } + } +} + +impl From for FoundryTxEnvelope { + fn from(tx: OpTxEnvelope) -> Self { + match tx { + OpTxEnvelope::Legacy(tx) => Self::Legacy(tx), + OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx), + OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx), + OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx), + OpTxEnvelope::Deposit(tx) => Self::Deposit(tx), + OpTxEnvelope::PostExec(tx) => Self::PostExec(tx), + } + } +} + +impl FromRecoveredTx for OpTransaction { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: None, deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: None, deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self { + Self(OpTransaction::::from_recovered_tx(tx, caller)) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + Self(OpTransaction::::from_encoded_tx(tx, caller, encoded)) + } +} + +impl FromTxWithEncoded for OpTransaction { + fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + FoundryTxEnvelope::Legacy(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip2930(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip1559(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip4844(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Eip7702(signed_tx) => { + let base = TxEnv::from_recovered_tx(signed_tx, caller); + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Deposit(sealed_tx) => { + let deposit_tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: deposit_tx.ty(), + caller, + gas_limit: deposit_tx.gas_limit, + kind: deposit_tx.to, + value: deposit_tx.value, + data: deposit_tx.input.clone(), + ..Default::default() + }; + let deposit = DepositTransactionParts { + source_hash: deposit_tx.source_hash, + mint: Some(deposit_tx.mint), + is_system_transaction: deposit_tx.is_system_transaction, + }; + Self { base, enveloped_tx: Some(encoded), deposit } + } + FoundryTxEnvelope::PostExec(sealed_tx) => { + let tx = sealed_tx.inner(); + let base = TxEnv { + tx_type: tx.ty(), + caller, + kind: tx.kind(), + data: tx.input.clone(), + ..Default::default() + }; + Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } + } + FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"), + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: op_alloy_rpc_types::Transaction) -> Self { + tx.inner.into_inner().into() + } +} + +/// Converts `OtherFields` to `DepositTransactionParts`, produces error with missing fields. +pub fn get_deposit_tx_parts( + other: &OtherFields, +) -> Result> { + let mut missing = Vec::new(); + let source_hash = + other.get_deserialized::("sourceHash").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("sourceHash"); + Default::default() + }, + ); + let mint = other + .get_deserialized::("mint") + .transpose() + .unwrap_or_else(|_| { + missing.push("mint"); + Default::default() + }) + .map(|value| value.saturating_to::()); + let is_system_transaction = + other.get_deserialized::("isSystemTx").transpose().ok().flatten().unwrap_or_else( + || { + missing.push("isSystemTx"); + Default::default() + }, + ); + if missing.is_empty() { + Ok(DepositTransactionParts { source_hash, mint, is_system_transaction }) + } else { + Err(missing) + } +} + +/// OP-stack-specific accessors on [`FoundryReceiptEnvelope`]. +impl FoundryReceiptEnvelope { + /// Return the receipt's deposit_nonce if it is a deposit receipt. + pub fn deposit_nonce(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_nonce) + } + + /// Return the receipt's deposit version if it is a deposit receipt. + pub fn deposit_receipt_version(&self) -> Option { + self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version) + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom> { + match self { + Self::Deposit(t) => Some(t), + _ => None, + } + } + + /// Returns the deposit receipt if it is a deposit receipt. + pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> { + match self { + Self::Deposit(t) => Some(&t.receipt), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use alloy_network::eip2718::Encodable2718; + use alloy_primitives::TxHash; + use alloy_rlp::Decodable; + + use super::*; + + #[test] + fn test_from_recovered_tx_legacy_op() { + use alloy_consensus::transaction::SignerRecoverable; + + let tx = r#" + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gas": "0x5208", + "gasPrice": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x1", + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "v": "0x1b", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + }"#; + + let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap(); + let sender = typed_tx.recover_signer().unwrap(); + + // Test OpTransaction conversion via FromRecoveredTx trait + let op_tx = OpTransaction::::from_recovered_tx(&typed_tx, sender); + assert_eq!(op_tx.base.caller, sender); + assert_eq!(op_tx.base.gas_limit, 0x5208); + } + + #[test] + fn test_decode_encode_deposit_tx() { + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); + + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + let mut encoded = Vec::new(); + dep_tx.encode_2718(&mut encoded); + + assert_eq!(raw_tx, encoded); + + assert_eq!(tx_hash, dep_tx.hash()); + } +} diff --git a/crates/primitives/src/transaction/receipt.rs b/crates/primitives/src/transaction/receipt.rs new file mode 100644 index 0000000000000..78bbcd1efa2fb --- /dev/null +++ b/crates/primitives/src/transaction/receipt.rs @@ -0,0 +1,690 @@ +use alloy_consensus::{ + Eip658Value, Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt, Typed2718, +}; +use alloy_network::eip2718::{ + Decodable2718, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, + Eip2718Error, Encodable2718, LEGACY_TX_TYPE_ID, +}; +use alloy_primitives::{Bloom, Log, TxHash, logs_bloom}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header, bytes}; +use alloy_rpc_types::{BlockNumHash, trace::otterscan::OtsReceipt}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{ + DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, POST_EXEC_TX_TYPE_ID, +}; +use serde::{Deserialize, Serialize}; +use tempo_primitives::TEMPO_TX_TYPE_ID; + +use crate::FoundryTxType; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum FoundryReceiptEnvelope { + #[serde(rename = "0x0", alias = "0x00")] + Legacy(ReceiptWithBloom>), + #[serde(rename = "0x1", alias = "0x01")] + Eip2930(ReceiptWithBloom>), + #[serde(rename = "0x2", alias = "0x02")] + Eip1559(ReceiptWithBloom>), + #[serde(rename = "0x3", alias = "0x03")] + Eip4844(ReceiptWithBloom>), + #[serde(rename = "0x4", alias = "0x04")] + Eip7702(ReceiptWithBloom>), + #[cfg(feature = "optimism")] + #[serde(rename = "0x7D", alias = "0x7d")] + PostExec(ReceiptWithBloom>), + #[cfg(feature = "optimism")] + #[serde(rename = "0x7E", alias = "0x7e")] + Deposit(OpDepositReceiptWithBloom), + #[serde(rename = "0x76")] + Tempo(ReceiptWithBloom>), +} + +impl FoundryReceiptEnvelope { + /// Creates a new [`FoundryReceiptEnvelope`] from the given parts. + pub fn from_parts( + status: bool, + cumulative_gas_used: u64, + logs: impl IntoIterator, + tx_type: FoundryTxType, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] deposit_nonce: Option, + #[cfg_attr(not(feature = "optimism"), allow(unused_variables))] + deposit_receipt_version: Option, + ) -> Self { + let logs = logs.into_iter().collect::>(); + let logs_bloom = logs_bloom(logs.iter().map(|l| &l.inner)); + let inner_receipt = + Receipt { status: Eip658Value::Eip658(status), cumulative_gas_used, logs }; + match tx_type { + FoundryTxType::Legacy => { + Self::Legacy(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + FoundryTxType::Eip2930 => { + Self::Eip2930(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + FoundryTxType::Eip1559 => { + Self::Eip1559(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + FoundryTxType::Eip4844 => { + Self::Eip4844(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + FoundryTxType::Eip7702 => { + Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + #[cfg(feature = "optimism")] + FoundryTxType::PostExec => { + Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + #[cfg(feature = "optimism")] + FoundryTxType::Deposit => { + let inner = OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: inner_receipt, + deposit_nonce, + deposit_receipt_version, + }, + logs_bloom, + }; + Self::Deposit(inner) + } + FoundryTxType::Tempo => { + Self::Tempo(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + } + } +} + +impl FoundryReceiptEnvelope { + pub fn convert_logs_rpc( + self, + block_numhash: BlockNumHash, + block_timestamp: u64, + transaction_hash: TxHash, + transaction_index: u64, + next_log_index: usize, + ) -> FoundryReceiptEnvelope { + let logs = self + .logs() + .iter() + .enumerate() + .map(|(index, log)| alloy_rpc_types::Log { + inner: log.clone(), + block_hash: Some(block_numhash.hash), + block_number: Some(block_numhash.number), + block_timestamp: Some(block_timestamp), + transaction_hash: Some(transaction_hash), + transaction_index: Some(transaction_index), + log_index: Some((next_log_index + index) as u64), + removed: false, + }) + .collect::>(); + #[cfg(feature = "optimism")] + let (deposit_nonce, deposit_receipt_version) = + (self.deposit_nonce(), self.deposit_receipt_version()); + #[cfg(not(feature = "optimism"))] + let (deposit_nonce, deposit_receipt_version) = (None, None); + FoundryReceiptEnvelope::::from_parts( + self.status(), + self.cumulative_gas_used(), + logs, + self.tx_type(), + deposit_nonce, + deposit_receipt_version, + ) + } +} + +impl FoundryReceiptEnvelope { + /// Return the [`FoundryTxType`] of the inner receipt. + pub const fn tx_type(&self) -> FoundryTxType { + match self { + Self::Legacy(_) => FoundryTxType::Legacy, + Self::Eip2930(_) => FoundryTxType::Eip2930, + Self::Eip1559(_) => FoundryTxType::Eip1559, + Self::Eip4844(_) => FoundryTxType::Eip4844, + Self::Eip7702(_) => FoundryTxType::Eip7702, + #[cfg(feature = "optimism")] + Self::PostExec(_) => FoundryTxType::PostExec, + #[cfg(feature = "optimism")] + Self::Deposit(_) => FoundryTxType::Deposit, + Self::Tempo(_) => FoundryTxType::Tempo, + } + } + + /// Returns the success status of the receipt's transaction. + pub const fn status(&self) -> bool { + self.as_receipt().status.coerce_status() + } + + /// Returns the cumulative gas used at this receipt. + pub const fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().cumulative_gas_used + } + + /// Converts the receipt's log type by applying a function to each log. + /// + /// Returns the receipt with the new log type. + pub fn map_logs(self, f: impl FnMut(T) -> U) -> FoundryReceiptEnvelope { + match self { + Self::Legacy(r) => FoundryReceiptEnvelope::Legacy(r.map_logs(f)), + Self::Eip2930(r) => FoundryReceiptEnvelope::Eip2930(r.map_logs(f)), + Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)), + Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)), + Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)), + #[cfg(feature = "optimism")] + Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)), + #[cfg(feature = "optimism")] + Self::Deposit(r) => FoundryReceiptEnvelope::Deposit( + r.map_receipt(|r: OpDepositReceipt| r.map_logs(f)), + ), + Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)), + } + } + + /// Return the receipt logs. + pub fn logs(&self) -> &[T] { + &self.as_receipt().logs + } + + /// Consumes the type and returns the logs. + pub fn into_logs(self) -> Vec { + self.into_receipt().logs + } + + /// Return the receipt's bloom. + pub const fn logs_bloom(&self) -> &Bloom { + match self { + Self::Legacy(t) => &t.logs_bloom, + Self::Eip2930(t) => &t.logs_bloom, + Self::Eip1559(t) => &t.logs_bloom, + Self::Eip4844(t) => &t.logs_bloom, + Self::Eip7702(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] + Self::PostExec(t) => &t.logs_bloom, + #[cfg(feature = "optimism")] + Self::Deposit(t) => &t.logs_bloom, + Self::Tempo(t) => &t.logs_bloom, + } + } + + /// Consumes the type and returns the underlying [`Receipt`]. + pub fn into_receipt(self) -> Receipt { + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip4844(t) + | Self::Eip7702(t) + | Self::Tempo(t) => t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => t.receipt, + #[cfg(feature = "optimism")] + Self::Deposit(t) => t.receipt.into_inner(), + } + } + + /// Return the inner receipt. + pub const fn as_receipt(&self) -> &Receipt { + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip4844(t) + | Self::Eip7702(t) + | Self::Tempo(t) => &t.receipt, + #[cfg(feature = "optimism")] + Self::PostExec(t) => &t.receipt, + #[cfg(feature = "optimism")] + Self::Deposit(t) => &t.receipt.inner, + } + } +} + +impl TxReceipt for FoundryReceiptEnvelope +where + T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync, +{ + type Log = T; + + fn status_or_post_state(&self) -> Eip658Value { + self.as_receipt().status + } + + fn status(&self) -> bool { + self.status() + } + + /// Return the receipt's bloom. + fn bloom(&self) -> Bloom { + *self.logs_bloom() + } + + fn bloom_cheap(&self) -> Option { + Some(self.bloom()) + } + + /// Returns the cumulative gas used at this receipt. + fn cumulative_gas_used(&self) -> u64 { + self.cumulative_gas_used() + } + + /// Return the receipt logs. + fn logs(&self) -> &[T] { + self.logs() + } +} + +impl Encodable for FoundryReceiptEnvelope { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Legacy(r) => r.encode(out), + receipt => { + let payload_len = match receipt { + Self::Eip2930(r) => r.length() + 1, + Self::Eip1559(r) => r.length() + 1, + Self::Eip4844(r) => r.length() + 1, + Self::Eip7702(r) => r.length() + 1, + #[cfg(feature = "optimism")] + Self::PostExec(r) => r.length() + 1, + #[cfg(feature = "optimism")] + Self::Deposit(r) => r.length() + 1, + Self::Tempo(r) => r.length() + 1, + _ => unreachable!("receipt already matched"), + }; + + match receipt { + Self::Eip2930(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + EIP2930_TX_TYPE_ID.encode(out); + r.encode(out); + } + Self::Eip1559(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + EIP1559_TX_TYPE_ID.encode(out); + r.encode(out); + } + Self::Eip4844(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + EIP4844_TX_TYPE_ID.encode(out); + r.encode(out); + } + Self::Eip7702(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + EIP7702_TX_TYPE_ID.encode(out); + r.encode(out); + } + #[cfg(feature = "optimism")] + Self::PostExec(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + POST_EXEC_TX_TYPE_ID.encode(out); + r.encode(out); + } + #[cfg(feature = "optimism")] + Self::Deposit(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + DEPOSIT_TX_TYPE_ID.encode(out); + r.encode(out); + } + Self::Tempo(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + TEMPO_TX_TYPE_ID.encode(out); + r.encode(out); + } + _ => unreachable!("receipt already matched"), + } + } + } + } +} + +impl Decodable for FoundryReceiptEnvelope { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + use bytes::Buf; + use std::cmp::Ordering; + + // a receipt is either encoded as a string (non legacy) or a list (legacy). + // We should not consume the buffer if we are decoding a legacy receipt, so let's + // check if the first byte is between 0x80 and 0xbf. + let rlp_type = *buf + .first() + .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?; + + match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) { + Ordering::Less => { + // strip out the string header + let _header = Header::decode(buf)?; + let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom( + "typed receipt cannot be decoded from an empty slice", + ))?; + if receipt_type == EIP2930_TX_TYPE_ID { + buf.advance(1); + ::decode(buf) + .map(FoundryReceiptEnvelope::Eip2930) + } else if receipt_type == EIP1559_TX_TYPE_ID { + buf.advance(1); + ::decode(buf) + .map(FoundryReceiptEnvelope::Eip1559) + } else if receipt_type == EIP4844_TX_TYPE_ID { + buf.advance(1); + ::decode(buf) + .map(FoundryReceiptEnvelope::Eip4844) + } else if receipt_type == EIP7702_TX_TYPE_ID { + buf.advance(1); + ::decode(buf) + .map(FoundryReceiptEnvelope::Eip7702) + } else if receipt_type == TEMPO_TX_TYPE_ID { + buf.advance(1); + ::decode(buf).map(FoundryReceiptEnvelope::Tempo) + } else { + #[cfg(feature = "optimism")] + { + if receipt_type == POST_EXEC_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::PostExec); + } + if receipt_type == DEPOSIT_TX_TYPE_ID { + buf.advance(1); + return ::decode(buf) + .map(FoundryReceiptEnvelope::Deposit); + } + } + Err(alloy_rlp::Error::Custom("invalid receipt type")) + } + } + Ordering::Equal => { + Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding")) + } + Ordering::Greater => { + ::decode(buf).map(FoundryReceiptEnvelope::Legacy) + } + } + } +} + +impl Typed2718 for FoundryReceiptEnvelope { + fn ty(&self) -> u8 { + match self { + Self::Legacy(_) => LEGACY_TX_TYPE_ID, + Self::Eip2930(_) => EIP2930_TX_TYPE_ID, + Self::Eip1559(_) => EIP1559_TX_TYPE_ID, + Self::Eip4844(_) => EIP4844_TX_TYPE_ID, + Self::Eip7702(_) => EIP7702_TX_TYPE_ID, + #[cfg(feature = "optimism")] + Self::PostExec(_) => POST_EXEC_TX_TYPE_ID, + #[cfg(feature = "optimism")] + Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, + Self::Tempo(_) => TEMPO_TX_TYPE_ID, + } + } +} + +impl Encodable2718 for FoundryReceiptEnvelope { + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(r) => r.length(), + Self::Eip2930(r) => 1 + r.length(), + Self::Eip1559(r) => 1 + r.length(), + Self::Eip4844(r) => 1 + r.length(), + Self::Eip7702(r) => 1 + r.length(), + #[cfg(feature = "optimism")] + Self::PostExec(r) => 1 + r.length(), + #[cfg(feature = "optimism")] + Self::Deposit(r) => 1 + r.length(), + Self::Tempo(r) => 1 + r.length(), + } + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + if let Some(ty) = self.type_flag() { + out.put_u8(ty); + } + match self { + Self::Legacy(r) + | Self::Eip2930(r) + | Self::Eip1559(r) + | Self::Eip4844(r) + | Self::Eip7702(r) + | Self::Tempo(r) => r.encode(out), + #[cfg(feature = "optimism")] + Self::PostExec(r) => r.encode(out), + #[cfg(feature = "optimism")] + Self::Deposit(r) => r.encode(out), + } + } +} + +impl Decodable2718 for FoundryReceiptEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { + #[cfg(feature = "optimism")] + { + if ty == DEPOSIT_TX_TYPE_ID { + return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?)); + } + if ty == POST_EXEC_TX_TYPE_ID { + return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?)); + } + } + if ty == TEMPO_TX_TYPE_ID { + return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?)); + } + match ReceiptEnvelope::typed_decode(ty, buf)? { + ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)), + ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)), + ReceiptEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)), + ReceiptEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)), + _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Result { + match ReceiptEnvelope::fallback_decode(buf)? { + ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))), + } + } +} + +impl From> for OtsReceipt { + fn from(receipt: FoundryReceiptEnvelope) -> Self { + Self { + status: receipt.status(), + cumulative_gas_used: receipt.cumulative_gas_used(), + logs: Some(receipt.logs().to_vec()), + logs_bloom: Some(receipt.logs_bloom().to_owned()), + r#type: receipt.tx_type() as u8, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Address, B256, Bytes, LogData, hex}; + use std::str::FromStr; + + #[test] + fn encode_legacy_receipt() { + let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let mut data = vec![]; + let receipt = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], + }, + logs_bloom: [0; 256].into(), + }); + + receipt.encode(&mut data); + + // check that the rlp length equals the length of the expected rlp + assert_eq!(receipt.length(), expected.len()); + assert_eq!(data, expected); + } + + #[test] + fn decode_legacy_receipt() { + let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let expected = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], + }, + logs_bloom: [0; 256].into(), + }); + + let receipt = FoundryReceiptEnvelope::decode(&mut &data[..]).unwrap(); + + assert_eq!(receipt, expected); + } + + #[test] + fn encode_tempo_receipt() { + use alloy_network::eip2718::Encodable2718; + use tempo_primitives::TEMPO_TX_TYPE_ID; + + let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom { + receipt: Receipt { + status: true.into(), + cumulative_gas_used: 157716, + logs: vec![Log { + address: Address::from_str("20c0000000000000000000000000000000000000").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000dec0000000000000000000000000000000000000", + ) + .unwrap(), + ], + Bytes::from_str( + "0000000000000000000000000000000000000000000000000000000000989680", + ) + .unwrap(), + ), + }], + }, + logs_bloom: [0; 256].into(), + }); + + assert_eq!(receipt.tx_type(), FoundryTxType::Tempo); + assert_eq!(receipt.ty(), TEMPO_TX_TYPE_ID); + assert!(receipt.status()); + assert_eq!(receipt.cumulative_gas_used(), 157716); + assert_eq!(receipt.logs().len(), 1); + + // Encode and decode round-trip + let mut encoded = Vec::new(); + receipt.encode_2718(&mut encoded); + + // First byte should be the Tempo type ID + assert_eq!(encoded[0], TEMPO_TX_TYPE_ID); + + // Decode it back + let decoded = FoundryReceiptEnvelope::decode(&mut &encoded[..]).unwrap(); + assert_eq!(receipt, decoded); + } + + #[test] + fn decode_tempo_receipt() { + use alloy_network::eip2718::Encodable2718; + use tempo_primitives::TEMPO_TX_TYPE_ID; + + let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom { + receipt: Receipt { status: true.into(), cumulative_gas_used: 21000, logs: vec![] }, + logs_bloom: [0; 256].into(), + }); + + // Encode and decode via 2718 + let mut encoded = Vec::new(); + receipt.encode_2718(&mut encoded); + assert_eq!(encoded[0], TEMPO_TX_TYPE_ID); + + use alloy_network::eip2718::Decodable2718; + let decoded = FoundryReceiptEnvelope::decode_2718(&mut &encoded[..]).unwrap(); + assert_eq!(receipt, decoded); + } + + #[test] + fn tempo_receipt_from_parts() { + let receipt = FoundryReceiptEnvelope::::from_parts( + true, + 100000, + vec![], + FoundryTxType::Tempo, + None, + None, + ); + + assert_eq!(receipt.tx_type(), FoundryTxType::Tempo); + assert!(receipt.status()); + assert_eq!(receipt.cumulative_gas_used(), 100000); + assert!(receipt.logs().is_empty()); + #[cfg(feature = "optimism")] + { + assert!(receipt.deposit_nonce().is_none()); + assert!(receipt.deposit_receipt_version().is_none()); + } + } + + #[test] + fn tempo_receipt_map_logs() { + let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom { + receipt: Receipt { + status: true.into(), + cumulative_gas_used: 21000, + logs: vec![Log { + address: Address::from_str("20c0000000000000000000000000000000000000").unwrap(), + data: LogData::new_unchecked(vec![], Bytes::default()), + }], + }, + logs_bloom: [0; 256].into(), + }); + + // Map logs to a different type (just clone in this case) + let mapped = receipt.map_logs(|log| log); + assert_eq!(mapped.logs().len(), 1); + assert_eq!(mapped.tx_type(), FoundryTxType::Tempo); + } +} diff --git a/crates/primitives/src/transaction/request.rs b/crates/primitives/src/transaction/request.rs new file mode 100644 index 0000000000000..8ae31efbd5cb1 --- /dev/null +++ b/crates/primitives/src/transaction/request.rs @@ -0,0 +1,711 @@ +use alloy_consensus::{BlobTransactionSidecarVariant, EthereumTypedTransaction}; +use alloy_network::{ + BuildResult, NetworkTransactionBuilder, NetworkWallet, TransactionBuilder, + TransactionBuilder4844, TransactionBuilderError, +}; +use alloy_primitives::{Address, ChainId, TxKind, U256}; +use alloy_rpc_types::{AccessList, TransactionInputKind, TransactionRequest}; +use alloy_serde::{OtherFields, WithOtherFields}; +#[cfg(feature = "optimism")] +use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit}; +#[cfg(feature = "optimism")] +use op_revm::transaction::deposit::DepositTransactionParts; +use serde::{Deserialize, Serialize}; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::{TEMPO_TX_TYPE_ID, TempoTxType}; + +#[cfg(feature = "optimism")] +use super::optimism::get_deposit_tx_parts; +use super::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx}; +use crate::FoundryNetwork; + +/// Foundry transaction request builder. +/// +/// This is a union of different transaction request types, instantiated from a +/// [`WithOtherFields`]. The specific variant is determined by the transaction +/// type field and/or the presence of certain fields: +/// - **Ethereum**: Default variant when no special fields are present +/// - **Op**: When `sourceHash`, `mint`, and `isSystemTx` fields are present, or transaction type is +/// `DEPOSIT_TX_TYPE_ID` +/// - **Tempo**: When `feeToken` or `nonceKey` fields are present, or transaction type is +/// `TEMPO_TX_TYPE_ID` +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FoundryTransactionRequest { + Ethereum(TransactionRequest), + #[cfg(feature = "optimism")] + Op(WithOtherFields), + Tempo(Box), +} + +impl FoundryTransactionRequest { + /// Create a new [`FoundryTransactionRequest`] from given + /// [`WithOtherFields`]. + #[inline] + pub fn new(inner: WithOtherFields) -> Self { + inner.into() + } + + /// Consume the [`FoundryTransactionRequest`] and return the inner transaction request. + pub fn into_inner(self) -> TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] + Self::Op(tx) => tx.inner, + Self::Tempo(tx) => tx.inner, + } + } + + /// Get the deposit transaction parts from the request, calling [`get_deposit_tx_parts`] helper + /// with OtherFields. + /// + /// # Returns + /// - Ok(deposit_tx_parts) if all necessary keys are present to build a deposit transaction. + /// - Err(missing) if some keys are missing to build a deposit transaction. + #[cfg(feature = "optimism")] + pub fn get_deposit_tx_parts(&self) -> Result> { + match self { + Self::Op(tx) => get_deposit_tx_parts(&tx.other), + // Not a deposit transaction request, so missing at least sourceHash, mint, and + // isSystemTx + _ => Err(vec!["sourceHash", "mint", "isSystemTx"]), + } + } + + /// Returns the minimal transaction type this request can be converted into based on the fields + /// that are set. See [`TransactionRequest::preferred_type`]. + pub fn preferred_type(&self) -> FoundryTxType { + match self { + Self::Ethereum(tx) => tx.preferred_type().into(), + #[cfg(feature = "optimism")] + Self::Op(tx) if tx.inner.transaction_type == Some(POST_EXEC_TX_TYPE_ID) => { + FoundryTxType::PostExec + } + #[cfg(feature = "optimism")] + Self::Op(_) => FoundryTxType::Deposit, + Self::Tempo(_) => FoundryTxType::Tempo, + } + } + + /// Check if all necessary keys are present to build a 4844 transaction, + /// returning a list of keys that are missing. + /// + /// **NOTE:** Inner [`TransactionRequest::complete_4844`] method but "sidecar" key is filtered + /// from error. + pub fn complete_4844(&self) -> Result<(), Vec<&'static str>> { + match self.as_ref().complete_4844() { + Ok(()) => Ok(()), + Err(missing) => { + let filtered: Vec<_> = + missing.into_iter().filter(|&key| key != "sidecar").collect(); + if filtered.is_empty() { Ok(()) } else { Err(filtered) } + } + } + } + + /// Check if all necessary keys are present to build a Deposit transaction, returning a list of + /// keys that are missing. + #[cfg(feature = "optimism")] + pub fn complete_deposit(&self) -> Result<(), Vec<&'static str>> { + self.get_deposit_tx_parts().map(|_| ()) + } + + /// Check if all necessary keys are present to build a Tempo transaction, returning a list of + /// keys that are missing. + pub fn complete_tempo(&self) -> Result<(), Vec<&'static str>> { + match self { + Self::Tempo(tx) => tx.complete_type(TempoTxType::AA).map(|_| ()), + // Not a Tempo transaction request, so missing at least feeToken and nonceKey + _ => Err(vec!["feeToken", "nonceKey"]), + } + } + + /// Check if all necessary keys are present to build a transaction. + /// + /// # Returns + /// + /// - Ok(type) if all necessary keys are present to build the preferred type. + /// - Err((type, missing)) if some keys are missing to build the preferred type. + pub fn missing_keys(&self) -> Result)> { + let pref = self.preferred_type(); + if let Err(missing) = match pref { + FoundryTxType::Legacy => self.as_ref().complete_legacy(), + FoundryTxType::Eip2930 => self.as_ref().complete_2930(), + FoundryTxType::Eip1559 => self.as_ref().complete_1559(), + FoundryTxType::Eip4844 => self.complete_4844(), + FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] + FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] + FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), + FoundryTxType::Tempo => self.complete_tempo(), + } { + Err((pref, missing)) + } else { + Ok(pref) + } + } + + /// Build a typed transaction from this request. + /// + /// Converts the request into a `FoundryTypedTx`, handling all Ethereum and OP-stack transaction + /// types. + pub fn build_typed_tx(self) -> Result { + #[cfg(feature = "optimism")] + if let Ok(deposit_tx_parts) = self.get_deposit_tx_parts() { + // Build deposit transaction + return Ok(FoundryTypedTx::Deposit(TxDeposit { + from: self.from().unwrap_or_default(), + source_hash: deposit_tx_parts.source_hash, + to: self.kind().unwrap_or_default(), + mint: deposit_tx_parts.mint.unwrap_or_default(), + value: self.value().unwrap_or_default(), + gas_limit: self.gas_limit().unwrap_or_default(), + is_system_transaction: deposit_tx_parts.is_system_transaction, + input: self.input().cloned().unwrap_or_default(), + })); + } + if self.complete_tempo().is_ok() + && let Self::Tempo(tx_req) = self + { + // Build Tempo transaction + Ok(FoundryTypedTx::Tempo( + tx_req.build_aa().map_err(|e| Self::Tempo(Box::new(e.into_value())))?, + )) + } else if self.as_ref().has_eip4844_fields() && self.blob_sidecar().is_none() { + // if request has eip4844 fields but no blob sidecar (neither eip4844 nor eip7594 + // format), try to build to eip4844 without sidecar + self.into_inner() + .build_4844_without_sidecar() + .map_err(|e| Self::Ethereum(e.into_value())) + .map(|tx| FoundryTypedTx::Eip4844(tx.into())) + } else { + // Use the inner transaction request to build EthereumTypedTransaction + let typed_tx = self.into_inner().build_typed_tx().map_err(Self::Ethereum)?; + // Convert EthereumTypedTransaction to FoundryTypedTx + Ok(match typed_tx { + EthereumTypedTransaction::Legacy(tx) => FoundryTypedTx::Legacy(tx), + EthereumTypedTransaction::Eip2930(tx) => FoundryTypedTx::Eip2930(tx), + EthereumTypedTransaction::Eip1559(tx) => FoundryTypedTx::Eip1559(tx), + EthereumTypedTransaction::Eip4844(tx) => FoundryTypedTx::Eip4844(tx), + EthereumTypedTransaction::Eip7702(tx) => FoundryTypedTx::Eip7702(tx), + }) + } + } +} + +impl Default for FoundryTransactionRequest { + fn default() -> Self { + Self::Ethereum(TransactionRequest::default()) + } +} + +impl Serialize for FoundryTransactionRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Ethereum(tx) => tx.serialize(serializer), + #[cfg(feature = "optimism")] + Self::Op(tx) => tx.serialize(serializer), + Self::Tempo(tx) => tx.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for FoundryTransactionRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + WithOtherFields::::deserialize(deserializer).map(Into::::into) + } +} + +impl AsRef for FoundryTransactionRequest { + fn as_ref(&self) -> &TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] + Self::Op(tx) => tx, + Self::Tempo(tx) => tx.as_ref(), + } + } +} + +impl AsMut for FoundryTransactionRequest { + fn as_mut(&mut self) -> &mut TransactionRequest { + match self { + Self::Ethereum(tx) => tx, + #[cfg(feature = "optimism")] + Self::Op(tx) => tx, + Self::Tempo(tx) => tx.as_mut(), + } + } +} + +impl From> for FoundryTransactionRequest { + fn from(tx: WithOtherFields) -> Self { + if tx.transaction_type == Some(TEMPO_TX_TYPE_ID) + || tx.other.contains_key("feeToken") + || tx.other.contains_key("nonceKey") + { + let mut tempo_tx_req: TempoTransactionRequest = tx.inner.into(); + if let Some(fee_token) = + tx.other.get_deserialized::
("feeToken").transpose().ok().flatten() + { + tempo_tx_req.fee_token = Some(fee_token); + } + if let Some(nonce_key) = + tx.other.get_deserialized::("nonceKey").transpose().ok().flatten() + { + tempo_tx_req.set_nonce_key(nonce_key); + } + return Self::Tempo(Box::new(tempo_tx_req)); + } + #[cfg(feature = "optimism")] + if tx.transaction_type == Some(DEPOSIT_TX_TYPE_ID) + || tx.transaction_type == Some(POST_EXEC_TX_TYPE_ID) + || get_deposit_tx_parts(&tx.other).is_ok() + { + return Self::Op(tx); + } + Self::Ethereum(tx.into_inner()) + } +} + +impl From for FoundryTransactionRequest { + fn from(tx: FoundryTypedTx) -> Self { + match tx { + FoundryTypedTx::Legacy(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip2930(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip1559(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip4844(tx) => Self::Ethereum(Into::::into(tx)), + FoundryTypedTx::Eip7702(tx) => Self::Ethereum(Into::::into(tx)), + #[cfg(feature = "optimism")] + FoundryTypedTx::Deposit(tx) => { + let other = OtherFields::from_iter([ + ("sourceHash", tx.source_hash.to_string().into()), + ("mint", tx.mint.to_string().into()), + ("isSystemTx", tx.is_system_transaction.to_string().into()), + ]); + WithOtherFields { inner: Into::::into(tx), other }.into() + } + #[cfg(feature = "optimism")] + FoundryTypedTx::PostExec(tx) => WithOtherFields { + inner: Into::::into(tx), + other: OtherFields::default(), + } + .into(), + FoundryTypedTx::Tempo(tx) => { + let mut other = OtherFields::default(); + if let Some(fee_token) = tx.fee_token { + other.insert("feeToken".to_string(), serde_json::to_value(fee_token).unwrap()); + } + other.insert("nonceKey".to_string(), serde_json::to_value(tx.nonce_key).unwrap()); + let first_call = tx.calls.first(); + let mut inner = TransactionRequest::default() + .with_chain_id(tx.chain_id) + .with_nonce(tx.nonce) + .with_gas_limit(tx.gas_limit) + .with_max_fee_per_gas(tx.max_fee_per_gas) + .with_max_priority_fee_per_gas(tx.max_priority_fee_per_gas) + .with_kind(first_call.map(|c| c.to).unwrap_or_default()) + .with_value(first_call.map(|c| c.value).unwrap_or_default()) + .with_input(first_call.map(|c| c.input.clone()).unwrap_or_default()) + .with_access_list(tx.access_list); + inner.transaction_type = Some(TEMPO_TX_TYPE_ID); + WithOtherFields { inner, other }.into() + } + } + } +} + +impl From for FoundryTransactionRequest { + fn from(tx: FoundryTxEnvelope) -> Self { + FoundryTypedTx::from(tx).into() + } +} + +#[cfg(not(feature = "optimism"))] +impl From> for FoundryTransactionRequest { + fn from(tx: alloy_rpc_types_eth::Transaction) -> Self { + tx.inner.into_inner().into() + } +} + +// TransactionBuilder trait implementation for FoundryNetwork +impl TransactionBuilder for FoundryTransactionRequest { + fn chain_id(&self) -> Option { + self.as_ref().chain_id + } + + fn set_chain_id(&mut self, chain_id: ChainId) { + self.as_mut().chain_id = Some(chain_id); + } + + fn nonce(&self) -> Option { + self.as_ref().nonce + } + + fn set_nonce(&mut self, nonce: u64) { + self.as_mut().nonce = Some(nonce); + } + + fn take_nonce(&mut self) -> Option { + self.as_mut().nonce.take() + } + + fn input(&self) -> Option<&alloy_primitives::Bytes> { + self.as_ref().input.input() + } + + fn set_input>(&mut self, input: T) { + self.as_mut().input.input = Some(input.into()); + } + + fn set_input_kind>( + &mut self, + input: T, + kind: TransactionInputKind, + ) { + let inner = self.as_mut(); + match kind { + TransactionInputKind::Input => inner.input.input = Some(input.into()), + TransactionInputKind::Data => inner.input.data = Some(input.into()), + TransactionInputKind::Both => { + let bytes = input.into(); + inner.input.input = Some(bytes.clone()); + inner.input.data = Some(bytes); + } + } + } + + fn from(&self) -> Option
{ + self.as_ref().from + } + + fn set_from(&mut self, from: Address) { + self.as_mut().from = Some(from); + } + + fn kind(&self) -> Option { + self.as_ref().to + } + + fn clear_kind(&mut self) { + self.as_mut().to = None; + } + + fn set_kind(&mut self, kind: TxKind) { + self.as_mut().to = Some(kind); + } + + fn value(&self) -> Option { + self.as_ref().value + } + + fn set_value(&mut self, value: U256) { + self.as_mut().value = Some(value); + } + + fn gas_price(&self) -> Option { + self.as_ref().gas_price + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.as_mut().gas_price = Some(gas_price); + } + + fn max_fee_per_gas(&self) -> Option { + self.as_ref().max_fee_per_gas + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) { + self.as_mut().max_fee_per_gas = Some(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.as_ref().max_priority_fee_per_gas + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) { + self.as_mut().max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + } + + fn gas_limit(&self) -> Option { + self.as_ref().gas + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.as_mut().gas = Some(gas_limit); + } + + fn access_list(&self) -> Option<&AccessList> { + self.as_ref().access_list.as_ref() + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.as_mut().access_list = Some(access_list); + } +} + +impl NetworkTransactionBuilder for FoundryTransactionRequest { + fn complete_type(&self, ty: FoundryTxType) -> Result<(), Vec<&'static str>> { + match ty { + FoundryTxType::Legacy => self.as_ref().complete_legacy(), + FoundryTxType::Eip2930 => self.as_ref().complete_2930(), + FoundryTxType::Eip1559 => self.as_ref().complete_1559(), + FoundryTxType::Eip4844 => self.as_ref().complete_4844(), + FoundryTxType::Eip7702 => self.as_ref().complete_7702(), + #[cfg(feature = "optimism")] + FoundryTxType::Deposit => self.complete_deposit(), + #[cfg(feature = "optimism")] + FoundryTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), + FoundryTxType::Tempo => self.complete_tempo(), + } + } + + fn can_submit(&self) -> bool { + self.from().is_some() + } + + fn can_build(&self) -> bool { + if self.as_ref().can_build() || self.complete_tempo().is_ok() { + return true; + } + #[cfg(feature = "optimism")] + if self.complete_deposit().is_ok() { + return true; + } + false + } + + fn output_tx_type(&self) -> FoundryTxType { + self.preferred_type() + } + + fn output_tx_type_checked(&self) -> Option { + let pref = self.preferred_type(); + match pref { + FoundryTxType::Legacy => self.as_ref().complete_legacy().ok(), + FoundryTxType::Eip2930 => self.as_ref().complete_2930().ok(), + FoundryTxType::Eip1559 => self.as_ref().complete_1559().ok(), + FoundryTxType::Eip4844 => self.as_ref().complete_4844().ok(), + FoundryTxType::Eip7702 => self.as_ref().complete_7702().ok(), + #[cfg(feature = "optimism")] + FoundryTxType::Deposit => self.complete_deposit().ok(), + #[cfg(feature = "optimism")] + FoundryTxType::PostExec => self.complete_type(pref).ok(), + FoundryTxType::Tempo => self.complete_tempo().ok(), + }?; + Some(pref) + } + + /// Prepares [`FoundryTransactionRequest`] by trimming conflicting fields, and filling with + /// default values the mandatory fields. + fn prep_for_submission(&mut self) { + let preferred_type = self.preferred_type(); + let inner = self.as_mut(); + inner.transaction_type = Some(preferred_type as u8); + inner.gas.is_none().then(|| inner.set_gas_limit(Default::default())); + let is_deposit = { + #[cfg(feature = "optimism")] + { + preferred_type == FoundryTxType::Deposit + } + #[cfg(not(feature = "optimism"))] + { + false + } + }; + if !is_deposit && preferred_type != FoundryTxType::Tempo { + inner.trim_conflicting_keys(); + inner.populate_blob_hashes(); + } + if !is_deposit { + inner.nonce.is_none().then(|| inner.set_nonce(Default::default())); + } + if matches!(preferred_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { + inner.gas_price.is_none().then(|| inner.set_gas_price(Default::default())); + } + if preferred_type == FoundryTxType::Eip2930 { + inner.access_list.is_none().then(|| inner.set_access_list(Default::default())); + } + if matches!( + preferred_type, + FoundryTxType::Eip1559 + | FoundryTxType::Eip4844 + | FoundryTxType::Eip7702 + | FoundryTxType::Tempo + ) { + inner + .max_priority_fee_per_gas + .is_none() + .then(|| inner.set_max_priority_fee_per_gas(Default::default())); + inner.max_fee_per_gas.is_none().then(|| inner.set_max_fee_per_gas(Default::default())); + } + if preferred_type == FoundryTxType::Eip4844 { + inner + .as_ref() + .max_fee_per_blob_gas() + .is_none() + .then(|| inner.as_mut().set_max_fee_per_blob_gas(Default::default())); + } + } + + fn build_unsigned(self) -> BuildResult { + if let Err((tx_type, missing)) = self.missing_keys() { + return Err(TransactionBuilderError::InvalidTransactionRequest(tx_type, missing) + .into_unbuilt(self)); + } + Ok(self.build_typed_tx().expect("checked by missing_keys")) + } + + async fn build>( + self, + wallet: &W, + ) -> Result> { + Ok(wallet.sign_request(self).await?) + } +} + +impl TransactionBuilder4844 for FoundryTransactionRequest { + fn max_fee_per_blob_gas(&self) -> Option { + self.as_ref().max_fee_per_blob_gas() + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.as_mut().set_max_fee_per_blob_gas(max_fee_per_blob_gas); + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> { + self.as_ref().blob_sidecar() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) { + self.as_mut().set_blob_sidecar(sidecar); + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::B256; + + use super::*; + + fn default_tx_req() -> TransactionRequest { + TransactionRequest::default() + .with_to(Address::random()) + .with_nonce(1) + .with_value(U256::from(1000000)) + .with_gas_limit(1000000) + .with_max_fee_per_gas(1000000) + .with_max_priority_fee_per_gas(1000000) + } + + #[test] + fn test_routing_ethereum_default() { + let tx = default_tx_req(); + let req: FoundryTransactionRequest = WithOtherFields::new(tx).into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_routing_tempo_by_fee_token() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("feeToken".to_string(), serde_json::to_value(Address::random()).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Tempo(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Tempo(_)))); + } + + #[test] + #[cfg(feature = "optimism")] + fn test_routing_op_by_deposit_fields() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + other.insert("isSystemTx".to_string(), serde_json::to_value(false).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Op(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Deposit(_)))); + } + + #[test] + fn test_op_incomplete_routes_to_ethereum() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + // Only provide 2 of 3 required Op fields + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_ethereum_with_unrelated_other_fields() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("anotherField".to_string(), serde_json::to_value(123).unwrap()); + + let req: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + assert!(matches!(req, FoundryTransactionRequest::Ethereum(_))); + assert!(matches!(req.build_unsigned(), Ok(FoundryTypedTx::Eip1559(_)))); + } + + #[test] + fn test_serialization_ethereum() { + let tx = default_tx_req(); + let original: FoundryTransactionRequest = WithOtherFields::new(tx).into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Ethereum(_))); + } + + #[test] + #[cfg(feature = "optimism")] + fn test_serialization_op() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("sourceHash".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + other.insert("mint".to_string(), serde_json::to_value(U256::from(1000)).unwrap()); + other.insert("isSystemTx".to_string(), serde_json::to_value(false).unwrap()); + + let original: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Op(_))); + } + + #[test] + fn test_serialization_tempo() { + let tx = default_tx_req(); + let mut other = OtherFields::default(); + other.insert("feeToken".to_string(), serde_json::to_value(Address::ZERO).unwrap()); + other.insert("nonceKey".to_string(), serde_json::to_value(U256::from(42)).unwrap()); + + let original: FoundryTransactionRequest = WithOtherFields { inner: tx, other }.into(); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: FoundryTransactionRequest = serde_json::from_str(&serialized).unwrap(); + + assert!(matches!(deserialized, FoundryTransactionRequest::Tempo(_))); + } +} diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index 68a03e15bf94b..ce94945fb27cf 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] foundry-config.workspace = true foundry-common.workspace = true -foundry-compilers = { workspace = true, features = ["full"] } +foundry-compilers.workspace = true serde.workspace = true eyre.workspace = true @@ -25,5 +25,9 @@ walkdir.workspace = true revm-inspectors.workspace = true -alloy-primitives.workspace = true alloy-network.workspace = true +alloy-primitives.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/script-sequence/src/lib.rs b/crates/script-sequence/src/lib.rs index 929f44a724b8c..7f253d7437ef2 100644 --- a/crates/script-sequence/src/lib.rs +++ b/crates/script-sequence/src/lib.rs @@ -1,4 +1,5 @@ //! Script Sequence and related types. +#![cfg_attr(not(test), warn(unused_crate_dependencies))] #[macro_use] extern crate foundry_common; diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs index abed5f69dd211..4be6b42e4a35d 100644 --- a/crates/script-sequence/src/reader.rs +++ b/crates/script-sequence/src/reader.rs @@ -1,8 +1,9 @@ use crate::{ScriptSequence, TransactionWithMetadata}; -use alloy_network::AnyTransactionReceipt; -use eyre::{bail, Result}; +use alloy_network::{Network, ReceiptResponse}; +use eyre::{Result, bail}; use foundry_common::fs; use revm_inspectors::tracing::types::CallKind; +use serde::Deserialize; use std::path::{Component, Path, PathBuf}; /// This type reads broadcast files in the @@ -25,8 +26,8 @@ pub struct BroadcastReader { impl BroadcastReader { /// Create a new `BroadcastReader` instance. pub fn new(contract_name: String, chain_id: u64, broadcast_path: &Path) -> Result { - if !broadcast_path.exists() && !broadcast_path.is_dir() { - bail!("broadcast dir does not exist"); + if !broadcast_path.is_dir() { + bail!("broadcast dir does not exist or is not a directory"); } Ok(Self { @@ -43,16 +44,25 @@ impl BroadcastReader { self } + fn matches_filters(&self, tx: &TransactionWithMetadata) -> bool { + let name_filter = tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); + let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.call_kind); + name_filter && type_filter + } + /// Read all broadcast files in the broadcast directory. /// /// Example structure: /// /// project-root/broadcast/{script_name}.s.sol/{chain_id}/*.json /// project-root/broadcast/multi/{multichain_script_name}.s.sol-{timestamp}/deploy.json - pub fn read(&self) -> eyre::Result> { + pub fn read(&self) -> eyre::Result>> + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { // 1. Recursively read all .json files in the broadcast directory let mut broadcasts = vec![]; - for entry in walkdir::WalkDir::new(&self.broadcast_path).into_iter() { + for entry in walkdir::WalkDir::new(&self.broadcast_path) { let entry = entry?; let path = entry.path(); @@ -70,7 +80,8 @@ impl BroadcastReader { let multichain_deployments = broadcast .get("deployments") .and_then(|deployments| { - serde_json::from_value::>(deployments.clone()).ok() + serde_json::from_value::>>(deployments.clone()) + .ok() }) .unwrap_or_default(); @@ -78,7 +89,7 @@ impl BroadcastReader { continue; } - let broadcast = fs::read_json_file::(path)?; + let broadcast = fs::read_json_file::>(path)?; broadcasts.push(broadcast); } } @@ -91,7 +102,10 @@ impl BroadcastReader { /// Attempts read the latest broadcast file in the broadcast directory. /// /// This may be the `run-latest.json` file or the broadcast file with the latest timestamp. - pub fn read_latest(&self) -> eyre::Result { + pub fn read_latest(&self) -> eyre::Result> + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let broadcasts = self.read()?; // Find the broadcast with the latest timestamp @@ -104,7 +118,10 @@ impl BroadcastReader { } /// Applies the filters and sorts the broadcasts by descending timestamp. - pub fn filter_and_sort(&self, broadcasts: Vec) -> Vec { + pub fn filter_and_sort( + &self, + broadcasts: Vec>, + ) -> Vec> { // Apply the filters let mut seqs = broadcasts .into_iter() @@ -113,19 +130,12 @@ impl BroadcastReader { return false; } - broadcast.transactions.iter().any(move |tx| { - let name_filter = - tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); - - let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); - - name_filter && type_filter - }) + broadcast.transactions.iter().any(|tx| self.matches_filters(tx)) }) .collect::>(); // Sort by descending timestamp - seqs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + seqs.sort_by_key(|s| std::cmp::Reverse(s.timestamp)); seqs } @@ -139,38 +149,25 @@ impl BroadcastReader { /// Transactions that don't have a corresponding receipt are ignored. /// /// Sorts the transactions by descending block number. - pub fn into_tx_receipts( + pub fn into_tx_receipts( &self, - broadcast: ScriptSequence, - ) -> Vec<(TransactionWithMetadata, AnyTransactionReceipt)> { - let transactions = broadcast.transactions.clone(); + broadcast: ScriptSequence, + ) -> Vec<(TransactionWithMetadata, N::ReceiptResponse)> { + let ScriptSequence { transactions, receipts, .. } = broadcast; - let txs = transactions + let mut targets: Vec<_> = transactions .into_iter() - .filter(|tx| { - let name_filter = - tx.contract_name.as_ref().is_some_and(|cn| *cn == self.contract_name); - - let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode); - - name_filter && type_filter + .filter(|tx| self.matches_filters(tx)) + .filter_map(|tx| { + let receipt = receipts + .iter() + .find(|r| tx.hash.is_some_and(|hash| hash == r.transaction_hash()))?; + Some((tx, receipt.clone())) }) - .collect::>(); - - let mut targets = Vec::new(); - for tx in txs.into_iter() { - let maybe_receipt = broadcast - .receipts - .iter() - .find(|receipt| tx.hash.is_some_and(|hash| hash == receipt.transaction_hash)); - - if let Some(receipt) = maybe_receipt { - targets.push((tx, receipt.clone())); - } - } + .collect(); // Sort by descending block number - targets.sort_by(|a, b| b.1.block_number.cmp(&a.1.block_number)); + targets.sort_by_key(|t| std::cmp::Reverse(t.1.block_number())); targets } diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 4b2b434e11793..fb0c01eb4fd1e 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -1,14 +1,13 @@ use crate::transaction::TransactionWithMetadata; -use alloy_network::AnyTransactionReceipt; -use alloy_primitives::{hex, map::HashMap, TxHash}; +use alloy_network::{Network, ReceiptResponse}; +use alloy_primitives::{TxHash, hex, map::HashMap}; use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{fs, shell, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, fs, shell}; use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; use std::{ collections::VecDeque, - io::{BufWriter, Write}, path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -21,12 +20,28 @@ pub struct NestedValue { pub value: String, } +/// Sensitive values from the transactions in a script sequence +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveTransactionMetadata { + pub rpc: String, +} + +/// Sensitive info from the script sequence which is saved into the cache folder +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveScriptSequence { + pub transactions: VecDeque, +} + /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct ScriptSequence { - pub transactions: VecDeque, - pub receipts: Vec, +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" +))] +pub struct ScriptSequence { + pub transactions: VecDeque>, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] @@ -34,25 +49,29 @@ pub struct ScriptSequence { /// None if sequence should not be saved to disk (e.g. part of a multi-chain sequence) pub paths: Option<(PathBuf, PathBuf)>, pub returns: HashMap, - pub timestamp: u64, + pub timestamp: u128, pub chain: u64, pub commit: Option, } -/// Sensitive values from the transactions in a script sequence -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveTransactionMetadata { - pub rpc: String, -} - -/// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveScriptSequence { - pub transactions: VecDeque, +impl Default for ScriptSequence { + fn default() -> Self { + Self { + transactions: Default::default(), + receipts: Default::default(), + libraries: Default::default(), + pending: Default::default(), + paths: Default::default(), + returns: Default::default(), + timestamp: Default::default(), + chain: Default::default(), + commit: Default::default(), + } + } } -impl From for SensitiveScriptSequence { - fn from(sequence: ScriptSequence) -> Self { +impl From<&ScriptSequence> for SensitiveScriptSequence { + fn from(sequence: &ScriptSequence) -> Self { Self { transactions: sequence .transactions @@ -63,7 +82,7 @@ impl From for SensitiveScriptSequence { } } -impl ScriptSequence { +impl ScriptSequence { /// Loads The sequence for the corresponding json file pub fn load( config: &Config, @@ -71,7 +90,10 @@ impl ScriptSequence { target: &ArtifactId, chain_id: u64, dry_run: bool, - ) -> Result { + ) -> Result + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = fs::read_json_file(&path) @@ -92,38 +114,37 @@ impl ScriptSequence { /// Saves the transactions as file if it's a standalone deployment. /// `save_ts` should be set to true for checkpoint updates, which might happen many times and /// could result in us saving many identical files. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> + where + N::TxEnvelope: Serialize, + { self.sort_receipts(); if self.transactions.is_empty() { - return Ok(()) + return Ok(()); } - let Some((path, sensitive_path)) = self.paths.clone() else { return Ok(()) }; - - self.timestamp = now().as_secs(); + self.timestamp = now().as_millis(); let ts_name = format!("run-{}.json", self.timestamp); - let sensitive_script_sequence: SensitiveScriptSequence = self.clone().into(); + let sensitive_script_sequence = SensitiveScriptSequence::from(&*self); + + let Some((path, sensitive_path)) = self.paths.as_ref() else { return Ok(()) }; // broadcast folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; + fs::write_pretty_json_file(path, &self)?; if save_ts { //../run-[timestamp].json - fs::copy(&path, path.with_file_name(&ts_name))?; + fs::copy(path, path.with_file_name(&ts_name))?; } // cache folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; - writer.flush()?; + fs::write_pretty_json_file(sensitive_path, &sensitive_script_sequence)?; if save_ts { //../run-[timestamp].json - fs::copy(&sensitive_path, sensitive_path.with_file_name(&ts_name))?; + fs::copy(sensitive_path, sensitive_path.with_file_name(&ts_name))?; } if !silent { @@ -145,13 +166,13 @@ impl ScriptSequence { Ok(()) } - pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { + pub fn add_receipt(&mut self, receipt: N::ReceiptResponse) { self.receipts.push(receipt); } /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { - self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); + self.receipts.sort_by_key(|r| (r.block_number(), r.transaction_index())); } pub fn add_pending(&mut self, index: usize, tx_hash: TxHash) { @@ -166,8 +187,8 @@ impl ScriptSequence { } /// Gets paths in the formats - /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and - /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. + /// `./broadcast/[contract_filename]/[chain_id]/[sig]-latest.json` and + /// `./cache/[contract_filename]/[chain_id]/[sig]-latest.json`. pub fn get_paths( config: &Config, sig: &str, @@ -175,8 +196,8 @@ impl ScriptSequence { chain_id: u64, dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = config.broadcast.to_path_buf(); - let mut cache = config.cache_path.to_path_buf(); + let mut broadcast = config.broadcast.clone(); + let mut cache = config.cache_path.clone(); let mut common = PathBuf::new(); let target_fname = target.source.file_name().wrap_err("No filename.")?; @@ -194,9 +215,10 @@ impl ScriptSequence { // TODO: ideally we want the name of the function here if sig is calldata let filename = sig_to_file_name(sig); + let filename_with_ext = format!("{filename}-latest.json"); - broadcast.push(format!("{filename}-latest.json")); - cache.push(format!("{filename}-latest.json")); + broadcast.push(&filename_with_ext); + cache.push(&filename_with_ext); Ok((broadcast, cache)) } @@ -207,7 +229,7 @@ impl ScriptSequence { } /// Returns the list of the transactions without the metadata. - pub fn transactions(&self) -> impl Iterator { + pub fn transactions(&self) -> impl Iterator> { self.transactions.iter().map(|tx| tx.tx()) } @@ -225,15 +247,18 @@ impl ScriptSequence { pub fn sig_to_file_name(sig: &str) -> String { if let Some((name, _)) = sig.split_once('(') { // strip until call argument parenthesis - return name.to_string() + return name.to_string(); } // assume calldata if `sig` is hex - if let Ok(calldata) = hex::decode(sig) { - // in which case we return the function signature - return hex::encode(&calldata[..SELECTOR_LEN]) + if let Ok(calldata) = hex::decode(sig.strip_prefix("0x").unwrap_or(sig)) { + // in which case we return the function selector if available + if let Some(selector) = calldata.get(..SELECTOR_LEN) { + return hex::encode(selector); + } + // fallback to original string if calldata is too short to contain selector + return sig.to_string(); } - // return sig as is sig.to_string() } @@ -255,5 +280,20 @@ mod tests { .as_str(), "522bb704" ); + // valid calldata with 0x prefix + assert_eq!( + sig_to_file_name( + "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266" + ) + .as_str(), + "522bb704" + ); + // short calldata: should not panic and should return input as-is + assert_eq!(sig_to_file_name("0x1234").as_str(), "0x1234"); + assert_eq!(sig_to_file_name("123").as_str(), "123"); + // invalid hex: should return input as-is + assert_eq!(sig_to_file_name("0xnotahex").as_str(), "0xnotahex"); + // non-hex non-signature: should return input as-is + assert_eq!(sig_to_file_name("not_a_sig_or_hex").as_str(), "not_a_sig_or_hex"); } } diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs index 7f72a4d30980c..da4448228e117 100644 --- a/crates/script-sequence/src/transaction.rs +++ b/crates/script-sequence/src/transaction.rs @@ -1,4 +1,5 @@ -use alloy_primitives::{Address, Bytes, B256}; +use alloy_network::Network; +use alloy_primitives::{Address, B256, Bytes}; use foundry_common::TransactionMaybeSigned; use revm_inspectors::tracing::types::CallKind; use serde::{Deserialize, Serialize}; @@ -7,17 +8,24 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct AdditionalContract { #[serde(rename = "transactionType")] - pub opcode: CallKind, + pub call_kind: CallKind, + pub contract_name: Option, pub address: Address, pub init_code: Bytes, } #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionWithMetadata { +#[serde( + rename_all = "camelCase", + bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" + ) +)] +pub struct TransactionWithMetadata { pub hash: Option, #[serde(rename = "transactionType")] - pub opcode: CallKind, + pub call_kind: CallKind, #[serde(default = "default_string")] pub contract_name: Option, #[serde(default = "default_address")] @@ -28,29 +36,31 @@ pub struct TransactionWithMetadata { pub arguments: Option>, #[serde(skip)] pub rpc: String, - pub transaction: TransactionMaybeSigned, + pub transaction: TransactionMaybeSigned, + #[serde(default)] pub additional_contracts: Vec, + #[serde(default)] pub is_fixed_gas_limit: bool, } -fn default_string() -> Option { +const fn default_string() -> Option { Some(String::new()) } -fn default_address() -> Option
{ +const fn default_address() -> Option
{ Some(Address::ZERO) } -fn default_vec_of_strings() -> Option> { +const fn default_vec_of_strings() -> Option> { Some(vec![]) } -impl TransactionWithMetadata { - pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { Self { transaction, hash: Default::default(), - opcode: Default::default(), + call_kind: Default::default(), contract_name: Default::default(), contract_address: Default::default(), function: Default::default(), @@ -61,15 +71,15 @@ impl TransactionWithMetadata { } } - pub fn tx(&self) -> &TransactionMaybeSigned { + pub const fn tx(&self) -> &TransactionMaybeSigned { &self.transaction } - pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { + pub const fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { &mut self.transaction } pub fn is_create2(&self) -> bool { - self.opcode == CallKind::Create2 + self.call_kind == CallKind::Create2 } } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index b258da28d02f4..d243814c4b148 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -19,18 +19,19 @@ foundry-cli.workspace = true foundry-config.workspace = true foundry-common.workspace = true foundry-evm.workspace = true +foundry-evm-networks.workspace = true +alloy-evm.workspace = true foundry-debugger.workspace = true foundry-cheatcodes.workspace = true -foundry-wallets.workspace = true +foundry-wallets = { workspace = true, features = ["browser", "tempo"] } foundry-linking.workspace = true -foundry-block-explorers.workspace = true forge-script-sequence.workspace = true serde.workspace = true eyre.workspace = true serde_json.workspace = true dunce.workspace = true -foundry-compilers = { workspace = true, features = ["full"] } +foundry-compilers.workspace = true tracing.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } semver.workspace = true @@ -43,11 +44,10 @@ yansi.workspace = true revm-inspectors.workspace = true alloy-rpc-types.workspace = true alloy-json-abi.workspace = true -dialoguer = { version = "0.11", default-features = false } +dialoguer.workspace = true indicatif.workspace = true alloy-signer.workspace = true -alloy-serde.workspace = true alloy-network.workspace = true alloy-provider.workspace = true alloy-chains.workspace = true @@ -55,6 +55,23 @@ alloy-dyn-abi.workspace = true alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true +thiserror.workspace = true +tempo-alloy.workspace = true + +tempo-alloy.workspace = true +tempo-primitives.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-common/optimism", + "foundry-cheatcodes/optimism", + "foundry-cli/optimism", + "forge-script-sequence/optimism", + "forge-verify/optimism", +] diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index d40d03de2a0cc..1900c7f749794 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,45 +1,61 @@ +use std::{cmp::Ordering, num::NonZeroU64, sync::Arc, time::Duration}; + use crate::{ - build::LinkedBuildData, progress::ScriptProgress, sequence::ScriptSequenceKind, - verify::BroadcastedState, ScriptArgs, ScriptConfig, + ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, + sequence::ScriptSequenceKind, verify::BroadcastedState, +}; +use alloy_chains::{Chain, NamedChain}; +use alloy_consensus::{SignableTransaction, Signed}; +use alloy_eips::{BlockId, eip2718::Encodable2718}; +use alloy_network::{ + EthereumWallet, Network, NetworkTransactionBuilder, ReceiptResponse, TransactionBuilder, }; -use alloy_chains::Chain; -use alloy_consensus::TxEnvelope; -use alloy_eips::{eip2718::Encodable2718, BlockId}; -use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; use alloy_primitives::{ + Address, TxHash, TxKind, U256, map::{AddressHashMap, AddressHashSet}, utils::format_units, - Address, TxHash, }; -use alloy_provider::{utils::Eip1559Estimation, Provider}; +use alloy_provider::{Provider, RootProvider, utils::Eip1559Estimation}; use alloy_rpc_types::TransactionRequest; -use alloy_serde::WithOtherFields; -use eyre::{bail, Context, Result}; +use alloy_signer::Signature; +use eyre::{Context, Result, bail}; use forge_verify::provider::VerificationProviderType; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; use foundry_common::{ - provider::{get_http_provider, try_get_http_provider, RetryProvider}, - shell, TransactionMaybeSigned, + FoundryTransactionBuilder, TransactionMaybeSigned, + provider::{ProviderBuilder, try_get_http_provider}, + shell, + tempo::TempoSponsor, }; use foundry_config::Config; -use futures::{future::join_all, StreamExt}; +use foundry_evm::core::evm::{FoundryEvmNetwork, TempoEvmNetwork}; +use foundry_wallets::{ + TempoAccessKeyConfig, WalletSigner, + tempo::{TempoLookup, lookup_signer}, + wallet_browser::signer::BrowserSigner, +}; +use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; use itertools::Itertools; -use std::{cmp::Ordering, sync::Arc}; +use tempo_alloy::{TempoNetwork, rpc::TempoTransactionRequest}; +use tempo_primitives::transaction::Call; -pub async fn estimate_gas>( - tx: &mut WithOtherFields, +pub async fn estimate_gas>( + tx: &mut N::TransactionRequest, provider: &P, estimate_multiplier: u64, -) -> Result<()> { +) -> Result<()> +where + N::TransactionRequest: FoundryTransactionBuilder, +{ // if already set, some RPC endpoints might simply return the gas value that is already // set in the request and omit the estimate altogether, so we remove it here - tx.gas = None; + tx.reset_gas_limit(); tx.set_gas_limit( - provider.estimate_gas(tx.clone()).await.wrap_err("Failed to estimate gas for tx")? * - estimate_multiplier / - 100, + provider.estimate_gas(tx.clone()).await.wrap_err("Failed to estimate gas for tx")? + * estimate_multiplier + / 100, ); Ok(()) } @@ -56,95 +72,210 @@ pub async fn next_nonce( Ok(provider.get_transaction_count(caller).block_id(block_id).await?) } -pub async fn send_transaction( - provider: Arc, - mut kind: SendTransactionKind<'_>, - sequential_broadcast: bool, - is_fixed_gas_limit: bool, - estimate_via_rpc: bool, - estimate_multiplier: u64, -) -> Result { - if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { - if sequential_broadcast { - let from = tx.from.expect("no sender"); - - let tx_nonce = tx.nonce.expect("no nonce"); - for attempt in 0..5 { - let nonce = provider.get_transaction_count(from).await?; - match nonce.cmp(&tx_nonce) { - Ordering::Greater => { - bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") - } - Ordering::Less => { - if attempt == 4 { - bail!("After 5 attempts, provider nonce ({nonce}) is still behind expected nonce ({tx_nonce}).") +/// Represents how to send a single transaction. +#[derive(Clone)] +pub enum SendTransactionKind<'a, N: Network> { + Unlocked(N::TransactionRequest), + Raw(N::TransactionRequest, &'a EthereumWallet), + Browser(N::TransactionRequest, &'a BrowserSigner), + Signed(N::TxEnvelope), + AccessKey(N::TransactionRequest, &'a WalletSigner, &'a TempoAccessKeyConfig), +} + +impl<'a, N: Network> SendTransactionKind<'a, N> +where + N::TxEnvelope: From>, + N::UnsignedTx: SignableTransaction, + N::TransactionRequest: FoundryTransactionBuilder, +{ + /// Prepares the transaction for broadcasting by synchronizing nonce and estimating gas. + /// + /// This method performs two key operations: + /// 1. Nonce synchronization: Waits for the provider's nonce to catch up to the expected + /// transaction nonce when doing sequential broadcast + /// 2. Gas estimation: Re-estimates gas right before broadcasting for chains that require it + pub async fn prepare( + &mut self, + provider: &RootProvider, + sequential_broadcast: bool, + is_fixed_gas_limit: bool, + estimate_via_rpc: bool, + estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, + ) -> Result<()> { + let access_key_authorization = match self { + Self::AccessKey(_, _, access_key) => Some(( + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.clone(), + )), + _ => None, + }; + + if let Self::Raw(tx, _) + | Self::Unlocked(tx) + | Self::Browser(tx, _) + | Self::AccessKey(tx, _, _) = self + { + if sequential_broadcast { + let from = tx.from().expect("no sender"); + + let tx_nonce = tx.nonce().expect("no nonce"); + for attempt in 0..5 { + let nonce = provider.get_transaction_count(from).await?; + match nonce.cmp(&tx_nonce) { + Ordering::Greater => { + bail!( + "EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider." + ) + } + Ordering::Less => { + if attempt == 4 { + bail!( + "After 5 attempts, provider nonce ({nonce}) is still behind expected nonce ({tx_nonce})." + ) + } + warn!( + "Expected nonce ({tx_nonce}) is ahead of provider nonce ({nonce}). Retrying in 1 second..." + ); + tokio::time::sleep(std::time::Duration::from_millis(1000)).await; + } + Ordering::Equal => { + // Nonces are equal, we can proceed. + break; } - warn!("Expected nonce ({tx_nonce}) is ahead of provider nonce ({nonce}). Retrying in 1 second..."); - tokio::time::sleep(std::time::Duration::from_millis(1000)).await; - } - Ordering::Equal => { - // Nonces are equal, we can proceed - break; } } } - } - // Chains which use `eth_estimateGas` are being sent sequentially and require their - // gas to be re-estimated right before broadcasting. - if !is_fixed_gas_limit && estimate_via_rpc { - estimate_gas(tx, &provider, estimate_multiplier).await?; + if let Some((wallet_address, key_address, key_authorization)) = + access_key_authorization.as_ref() + { + tx.prepare_access_key_authorization( + provider, + *wallet_address, + *key_address, + key_authorization.as_ref(), + ) + .await?; + } + + // Chains which use `eth_estimateGas` are being sent sequentially and require their + // gas to be re-estimated right before broadcasting. + if !is_fixed_gas_limit && estimate_via_rpc { + estimate_gas(tx, provider, estimate_multiplier).await?; + } + + if let Some(sponsor) = tempo_sponsor { + let from = tx.from().expect("no sender"); + sponsor.attach_and_print::(tx, from).await?; + } } + + Ok(()) } - let pending = match kind { - SendTransactionKind::Unlocked(tx) => { - debug!("sending transaction from unlocked account {:?}", tx); + /// Sends the transaction to the network. + /// + /// Depending on the transaction kind, this will either: + /// - Submit via `eth_sendTransaction` for unlocked accounts + /// - Sign and submit via `eth_sendRawTransaction` for raw transactions + /// - Submit pre-signed transaction via `eth_sendRawTransaction` + pub async fn send(self, provider: Arc>) -> Result { + match self { + Self::Unlocked(tx) => { + debug!("sending transaction from unlocked account {:?}", tx); - // Submit the transaction - provider.send_transaction(tx).await? - } - SendTransactionKind::Raw(tx, signer) => { - debug!("sending transaction: {:?}", tx); - let signed = tx.build(signer).await?; + // Submit the transaction + let pending = provider.send_transaction(tx).await?; + Ok(*pending.tx_hash()) + } + Self::Raw(tx, signer) => { + debug!("sending transaction: {:?}", tx); + let signed = tx.build(signer).await?; - // Submit the raw transaction - provider.send_raw_transaction(signed.encoded_2718().as_ref()).await? - } - SendTransactionKind::Signed(tx) => { - debug!("sending transaction: {:?}", tx); - provider.send_raw_transaction(tx.encoded_2718().as_ref()).await? + // Submit the raw transaction + let pending = provider.send_raw_transaction(signed.encoded_2718().as_ref()).await?; + Ok(*pending.tx_hash()) + } + Self::Signed(tx) => { + debug!("sending transaction: {:?}", tx); + let pending = provider.send_raw_transaction(tx.encoded_2718().as_ref()).await?; + Ok(*pending.tx_hash()) + } + Self::Browser(tx, signer) => { + debug!("sending transaction: {:?}", tx); + + // Sign and send the transaction via the browser wallet + Ok(signer.send_transaction_via_browser(tx).await?) + } + Self::AccessKey(tx, signer, access_key) => { + debug!("sending transaction via tempo access key: {:?}", tx); + + let raw_tx = tx + .sign_with_access_key( + provider.as_ref(), + signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + + let pending = provider.send_raw_transaction(&raw_tx).await?; + Ok(*pending.tx_hash()) + } } - }; + } - Ok(*pending.tx_hash()) -} + /// Prepares and sends the transaction in one operation. + /// + /// This is a convenience method that combines [`prepare`](Self::prepare) and + /// [`send`](Self::send) into a single call. + pub async fn prepare_and_send( + mut self, + provider: Arc>, + sequential_broadcast: bool, + is_fixed_gas_limit: bool, + estimate_via_rpc: bool, + estimate_multiplier: u64, + tempo_sponsor: Option<&TempoSponsor>, + ) -> Result { + self.prepare( + &provider, + sequential_broadcast, + is_fixed_gas_limit, + estimate_via_rpc, + estimate_multiplier, + tempo_sponsor, + ) + .await?; -/// How to send a single transaction -#[derive(Clone)] -pub enum SendTransactionKind<'a> { - Unlocked(WithOtherFields), - Raw(WithOtherFields, &'a EthereumWallet), - Signed(TxEnvelope), + self.send(provider).await + } } /// Represents how to send _all_ transactions -pub enum SendTransactionsKind { +pub enum SendTransactionsKind { /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. Unlocked(AddressHashSet), - /// Send a signed transaction via `eth_sendRawTransaction` - Raw(AddressHashMap), + /// Send a signed transaction via `eth_sendRawTransaction`, or via browser + Raw { + eth_wallets: AddressHashMap, + browser: Option>, + access_keys: AddressHashMap<(WalletSigner, TempoAccessKeyConfig)>, + }, } -impl SendTransactionsKind { +impl SendTransactionsKind { /// Returns the [`SendTransactionKind`] for the given address /// /// Returns an error if no matching signer is found or the address is not unlocked pub fn for_sender( &self, addr: &Address, - tx: WithOtherFields, - ) -> Result> { + tx: N::TransactionRequest, + ) -> Result> { match self { Self::Unlocked(unlocked) => { if !unlocked.contains(addr) { @@ -152,9 +283,15 @@ impl SendTransactionsKind { } Ok(SendTransactionKind::Unlocked(tx)) } - Self::Raw(wallets) => { - if let Some(wallet) = wallets.get(addr) { + Self::Raw { eth_wallets, browser, access_keys } => { + if let Some((signer, config)) = access_keys.get(addr) { + Ok(SendTransactionKind::AccessKey(tx, signer, config)) + } else if let Some(wallet) = eth_wallets.get(addr) { Ok(SendTransactionKind::Raw(tx, wallet)) + } else if let Some(b) = browser + && b.address() == *addr + { + Ok(SendTransactionKind::Browser(tx, b)) } else { bail!("No matching signer for {:?} found", addr) } @@ -166,15 +303,16 @@ impl SendTransactionsKind { /// State after we have bundled all /// [`TransactionWithMetadata`](forge_script_sequence::TransactionWithMetadata) objects into a /// single [`ScriptSequenceKind`] object containing one or more script sequences. -pub struct BundledState { +pub struct BundledState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BundledState { +impl BundledState { pub async fn wait_for_pending(mut self) -> Result { let progress = ScriptProgress::default(); let progress_ref = &progress; @@ -185,7 +323,7 @@ impl BundledState { .enumerate() .map(|(sequence_idx, sequence)| async move { let rpc_url = sequence.rpc_url(); - let provider = Arc::new(get_http_provider(rpc_url)); + let provider = Arc::new(ProviderBuilder::new(rpc_url).build()?); progress_ref .wait_for_pending( sequence_idx, @@ -209,7 +347,7 @@ impl BundledState { } /// Broadcasts transactions from all sequences. - pub async fn broadcast(mut self) -> Result { + pub async fn broadcast(mut self) -> Result> { let required_addresses = self .sequence .sequences() @@ -231,12 +369,33 @@ impl BundledState { let send_kind = if self.args.unlocked { SendTransactionsKind::Unlocked(required_addresses.clone()) } else { - let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let signers: Vec
= self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("{e}"))? + .into_iter() + .chain(self.browser_wallet.as_ref().map(|b| b.address())) + .collect(); + + // For addresses without an explicit signer, try Tempo keys.toml fallback. + let mut access_keys: AddressHashMap<(WalletSigner, TempoAccessKeyConfig)> = + AddressHashMap::default(); + let mut direct_signers: AddressHashMap = AddressHashMap::default(); let mut missing_addresses = Vec::new(); for addr in &required_addresses { - if !signers.contains_key(addr) { - missing_addresses.push(addr); + if !signers.contains(addr) { + match lookup_signer(*addr) { + Ok(TempoLookup::Direct(signer)) => { + direct_signers.insert(*addr, signer); + } + Ok(TempoLookup::Keychain(signer, config)) => { + access_keys.insert(*addr, (signer, *config)); + } + _ => { + missing_addresses.push(addr); + } + } } } @@ -244,24 +403,47 @@ impl BundledState { eyre::bail!( "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", missing_addresses, - signers.keys().collect::>() + signers ); } - let signers = signers - .into_iter() - .map(|(addr, signer)| (addr, EthereumWallet::new(signer))) - .collect(); + let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let mut eth_wallets: AddressHashMap = + signers.into_iter().map(|(addr, signer)| (addr, signer.into())).collect(); + for (addr, signer) in direct_signers { + eth_wallets.insert(addr, signer.into()); + } - SendTransactionsKind::Raw(signers) + SendTransactionsKind::Raw { eth_wallets, browser: self.browser_wallet, access_keys } }; + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?.map(Arc::new); + if tempo_sponsor.is_some() && self.script_config.tempo.sponsor_sig.is_some() { + let remaining = self + .sequence + .sequences() + .iter() + .map(|sequence| { + sequence + .transactions() + .skip(sequence.receipts.len()) + .filter(|tx| tx.is_unsigned()) + .count() + }) + .sum::(); + if remaining > 1 { + eyre::bail!( + "--tempo.sponsor-sig can only sponsor one remaining script transaction; use --tempo.sponsor-signer for multi-transaction scripts" + ); + } + } + let progress = ScriptProgress::default(); for i in 0..self.sequence.sequences().len() { let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); - let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?); + let provider = Arc::new(ProviderBuilder::new(sequence.rpc_url()).build()?); let already_broadcasted = sequence.receipts.len(); let seq_progress = progress.get_sequence_progress(i, sequence); @@ -276,16 +458,40 @@ impl BundledState { ) { (true, Some(gas_price), _) => (Some(gas_price.to()), None), (true, None, _) => (Some(provider.get_gas_price().await?), None), - (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => ( - None, - Some(Eip1559Estimation { - max_fee_per_gas: max_fee_per_gas.to(), - max_priority_fee_per_gas: max_priority_fee_per_gas.to(), - }), - ), + (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { + let max_fee: u128 = max_fee_per_gas.to(); + let max_priority: u128 = max_priority_fee_per_gas.to(); + if max_priority > max_fee { + eyre::bail!( + "--priority-gas-price ({max_priority}) cannot be higher than --with-gas-price ({max_fee})" + ); + } + ( + None, + Some(Eip1559Estimation { + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: max_priority, + }), + ) + } (false, _, _) => { let mut fees = provider.estimate_eip1559_fees().await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + // When using --browser, the browser wallet may override the + // priority fee with its own estimate (from + // eth_maxPriorityFeePerGas) without adjusting maxFeePerGas, + // leading to maxPriorityFeePerGas > maxFeePerGas. + // This is common on OP Stack chains (e.g. Base) where + // eth_feeHistory returns empty reward arrays, causing the + // estimator to fall back to a 1 wei priority fee. + if matches!(&send_kind, SendTransactionsKind::Raw { browser: Some(_), .. }) + && let Ok(suggested_tip) = provider.get_max_priority_fee_per_gas().await + && suggested_tip > fees.max_priority_fee_per_gas + { + fees.max_fee_per_gas += suggested_tip - fees.max_priority_fee_per_gas; + fees.max_priority_fee_per_gas = suggested_tip; + } + if let Some(gas_price) = self.args.with_gas_price { fees.max_fee_per_gas = gas_price.to(); } @@ -309,16 +515,21 @@ impl BundledState { let kind = match tx_with_metadata.tx().clone() { TransactionMaybeSigned::Signed { tx, .. } => { + if tempo_sponsor.is_some() { + eyre::bail!( + "cannot attach Tempo sponsor signature to an already signed script transaction" + ); + } SendTransactionKind::Signed(tx) } TransactionMaybeSigned::Unsigned(mut tx) => { - let from = tx.from.expect("No sender for onchain transaction!"); + let from = tx.from().expect("No sender for onchain transaction!"); tx.set_chain_id(sequence.chain); // Set TxKind::Create explicitly to satisfy `check_reqd_fields` in // alloy - if tx.to.is_none() { + if tx.kind().is_none() { tx.set_create(); } @@ -332,6 +543,8 @@ impl BundledState { tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } + self.script_config.tempo.apply::(&mut tx, None); + send_kind.for_sender(&from, tx)? } }; @@ -340,48 +553,116 @@ impl BundledState { }) .collect::>>()?; - let estimate_via_rpc = - has_different_gas_calc(sequence.chain) || self.args.skip_simulation; + let estimate_via_rpc = has_different_gas_calc(sequence.chain) + || self.script_config.evm_opts.networks.is_tempo() + || self.args.skip_simulation; // We only wait for a transaction receipt before sending the next transaction, if // there is more than one signer. There would be no way of assuring // their order otherwise. // Or if the chain does not support batched transactions (eg. Arbitrum). // Or if we need to invoke eth_estimateGas before sending transactions. - let sequential_broadcast = estimate_via_rpc || - self.args.slow || - required_addresses.len() != 1 || - !has_batch_support(sequence.chain); + let sequential_broadcast = estimate_via_rpc + || self.args.slow + || required_addresses.len() != 1 + || !has_batch_support(sequence.chain); - // We send transactions and wait for receipts in batches. - let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size }; + // We send transactions and wait for receipts in batches of 100, since some networks + // cannot handle more than that. + let batch_size = if sequential_broadcast { 1 } else { 100 }; let mut index = already_broadcasted; for (batch_number, batch) in transactions.chunks(batch_size).enumerate() { - let mut pending_transactions = vec![]; - seq_progress.inner.write().set_status(&format!( "Sending transactions [{} - {}]", batch_number * batch_size, batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1 )); - for (kind, is_fixed_gas_limit) in batch { - let fut = send_transaction( - provider.clone(), - kind.clone(), - sequential_broadcast, - *is_fixed_gas_limit, - estimate_via_rpc, - self.args.gas_estimate_multiplier, - ); - pending_transactions.push(fut); - } - if !pending_transactions.is_empty() { - let mut buffer = futures::stream::iter(pending_transactions).buffered(7); + if !batch.is_empty() { + let pending_transactions = + batch.iter().map(|(kind, is_fixed_gas_limit)| { + let provider = provider.clone(); + let tempo_sponsor = tempo_sponsor.clone(); + async move { + let res = kind + .clone() + .prepare_and_send( + provider, + sequential_broadcast, + *is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), + ) + .await; + (res, kind, *is_fixed_gas_limit, 0, None) + } + .boxed() + }); + + let mut buffer = pending_transactions.collect::>(); + + 'send: while let Some(( + res, + kind, + is_fixed_gas_limit, + attempt, + original_res, + )) = buffer.next().await + { + if res.is_err() + && self.script_config.tempo.sponsor_sig.is_some() + && attempt == 0 + { + debug!( + "not retrying transaction because --tempo.sponsor-sig is a static signature" + ); + } else if res.is_err() && attempt <= 3 { + // Try to resubmit the transaction + let provider = provider.clone(); + let progress = seq_progress.inner.clone(); + let tempo_sponsor = tempo_sponsor.clone(); + buffer.push(Box::pin(async move { + debug!(err=?res, ?attempt, "retrying transaction "); + let attempt = attempt + 1; + progress.write().set_status(&format!( + "retrying transaction {res:?} (attempt {attempt})" + )); + tokio::time::sleep(Duration::from_millis(1000 * attempt)).await; + let r = kind + .clone() + .prepare_and_send( + provider, + sequential_broadcast, + is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + tempo_sponsor.as_deref(), + ) + .await; + ( + r, + kind, + is_fixed_gas_limit, + attempt, + original_res.or(Some(res)), + ) + })); - while let Some(tx_hash) = buffer.next().await { - let tx_hash = tx_hash.wrap_err("Failed to send transaction")?; + continue 'send; + } + + // Preserve the original error if any + let tx_hash = res.wrap_err_with(|| { + if let Some(original_res) = original_res { + format!( + "Failed to send transaction after {attempt} attempts {original_res:?}" + ) + } else { + "Failed to send transaction".to_string() + } + })?; sequence.add_pending(index, tx_hash); // Checkpoint save @@ -413,17 +694,24 @@ impl BundledState { let (total_gas, total_gas_price, total_paid) = sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| { - let gas_used = receipt.gas_used; - let gas_price = receipt.effective_gas_price as u64; + let gas_used = receipt.gas_used(); + let gas_price = receipt.effective_gas_price() as u64; (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price) }); let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); - let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u64, 9) - .unwrap_or_else(|_| "N/A".to_string()); + let avg_gas_price = total_gas_price + .checked_div(sequence.receipts.len() as u64) + .and_then(|avg| format_units(avg, 9).ok()) + .unwrap_or_else(|| "N/A".to_string()); + let token_symbol = NamedChain::try_from(sequence.chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); seq_progress.inner.write().set_status(&format!( - "Total Paid: {} ETH ({} gas * avg {} gwei)\n", + "Total Paid: {} {} ({} gas * avg {} gwei)\n", paid.trim_end_matches('0'), + token_symbol, total_gas, avg_gas_price.trim_end_matches('0').trim_end_matches('.') )); @@ -445,8 +733,9 @@ impl BundledState { pub fn verify_preflight_check(&self) -> Result<()> { for sequence in self.sequence.sequences() { - if self.args.verifier.verifier == VerificationProviderType::Etherscan && - self.script_config + if self.args.verifier.verifier == VerificationProviderType::Etherscan + && self + .script_config .config .get_etherscan_api_key(Some(sequence.chain.into())) .is_none() @@ -458,3 +747,290 @@ impl BundledState { Ok(()) } } + +impl BundledState { + /// Broadcasts all transactions as a single Tempo batch transaction (type 0x76). + /// + /// This method collects all individual transactions from the script and combines them + /// into a single batch transaction for atomic execution on Tempo. + pub async fn broadcast_batch(mut self) -> Result> { + // Batch mode only supports single chain for now + if self.sequence.sequences().len() != 1 { + bail!( + "--batch mode only supports single-chain scripts. \ + Use --multi without --batch for multi-chain." + ); + } + + let sequence = self.sequence.sequences_mut().get_mut(0).unwrap(); + let provider = Arc::new(ProviderBuilder::::new(sequence.rpc_url()).build()?); + let tempo_sponsor = self.script_config.tempo.sponsor_config().await?; + + // Collect sender addresses - batch mode requires single sender + let senders: AddressHashSet = sequence + .transactions() + .filter(|tx| tx.is_unsigned()) + .filter_map(|tx| tx.from()) + .collect(); + + if senders.len() != 1 { + bail!( + "--batch mode requires all transactions to have the same sender. \ + Found {} unique senders: {:?}", + senders.len(), + senders + ); + } + + let sender = *senders.iter().next().unwrap(); + + if sender == Config::DEFAULT_SENDER { + bail!( + "You seem to be using Foundry's default sender. Be sure to set your own --sender." + ); + } + + // Get wallet for signing + enum BatchSigner { + Unlocked, + Wallet(EthereumWallet), + TempoKeychain(Box, Box), + } + + let batch_signer = if self.args.unlocked { + BatchSigner::Unlocked + } else { + let mut signers = self.script_wallets.into_multi_wallet().into_signers()?; + if let Some(signer) = signers.remove(&sender) { + BatchSigner::Wallet(EthereumWallet::new(signer)) + } else { + // Try Tempo keys.toml fallback + match lookup_signer(sender)? { + TempoLookup::Direct(signer) => BatchSigner::Wallet(EthereumWallet::new(signer)), + TempoLookup::Keychain(signer, config) => { + BatchSigner::TempoKeychain(Box::new(signer), config) + } + TempoLookup::NotFound => { + bail!("No wallet found for sender {}", sender); + } + } + } + }; + + // Collect all transactions into Call structs + // Tempo batch transactions support CREATE only as the first call + let mut calls: Vec = Vec::new(); + let mut has_create = false; + for (idx, tx) in sequence.transactions().enumerate() { + let to = match tx.to() { + Some(addr) => TxKind::Call(addr), + None => { + if idx > 0 { + bail!( + "Contract creation must be the first transaction in --batch mode. \ + Found CREATE at position {}. Reorder your script or deploy separately.", + idx + 1 + ); + } + if has_create { + bail!("Only one contract creation is allowed per --batch transaction."); + } + has_create = true; + TxKind::Create + } + }; + let value = tx.value().unwrap_or(U256::ZERO); + let input = tx.input().cloned().unwrap_or_default(); + + calls.push(Call { to, value, input }); + } + + if calls.is_empty() { + sh_println!("No transactions to broadcast in batch mode.")?; + return Ok(BroadcastedState { + args: self.args, + script_config: self.script_config, + build_data: self.build_data, + sequence: self.sequence, + }); + } + + sh_println!( + "\n## Broadcasting batch transaction with {} call(s) to chain {}...", + calls.len(), + sequence.chain + )?; + + // Build the batch transaction request + let nonce = provider.get_transaction_count(sender).await?; + let chain_id = sequence.chain; + + // Get gas prices - batch transactions are Tempo-only, always use EIP-1559 style fees + let fees = provider.estimate_eip1559_fees().await?; + let max_fee_per_gas = + self.args.with_gas_price.map(|p| p.to()).unwrap_or(fees.max_fee_per_gas); + let max_priority_fee_per_gas = + self.args.priority_gas_price.map(|p| p.to()).unwrap_or(fees.max_priority_fee_per_gas); + + let mut batch_tx = TempoTransactionRequest { + inner: TransactionRequest { + from: Some(sender), + to: None, + value: None, + input: Default::default(), + nonce: Some(nonce), + chain_id: Some(chain_id), + max_fee_per_gas: Some(max_fee_per_gas), + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), + ..Default::default() + }, + fee_token: self.script_config.tempo.fee_token, + calls: calls.clone(), + nonce_key: self.script_config.tempo.expiring_nonce.then_some(U256::MAX), + valid_before: self.script_config.tempo.valid_before.and_then(NonZeroU64::new), + ..Default::default() + }; + self.script_config.tempo.apply::(&mut batch_tx, None); + + if let BatchSigner::TempoKeychain(_, ak) = &batch_signer { + batch_tx.key_id = Some(ak.key_address); + batch_tx + .prepare_access_key_authorization( + provider.as_ref(), + ak.wallet_address, + ak.key_address, + ak.key_authorization.as_ref(), + ) + .await?; + } + + // Estimate gas for the batch transaction + estimate_gas(&mut batch_tx, provider.as_ref(), self.args.gas_estimate_multiplier).await?; + + sh_println!("Estimated gas: {}", batch_tx.inner.gas.unwrap_or(0))?; + + if let Some(sponsor) = &tempo_sponsor { + sponsor.attach_and_print::(&mut batch_tx, sender).await?; + } + + // Sign and send + let tx_hash = match batch_signer { + BatchSigner::Wallet(wallet) => { + let provider_with_wallet = + alloy_provider::ProviderBuilder::<_, _, TempoNetwork>::default() + .wallet(wallet) + .connect_provider(provider.as_ref()); + + let pending = provider_with_wallet.send_transaction(batch_tx).await?; + *pending.tx_hash() + } + BatchSigner::TempoKeychain(signer, access_key) => { + let raw_tx = batch_tx + .sign_with_access_key( + provider.as_ref(), + &*signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; + + let pending = provider.send_raw_transaction(&raw_tx).await?; + *pending.tx_hash() + } + BatchSigner::Unlocked => { + let pending = provider.send_transaction(batch_tx).await?; + *pending.tx_hash() + } + }; + + sh_println!("Batch transaction sent: {:#x}", tx_hash)?; + + // Wait for receipt + let timeout = self.script_config.config.transaction_timeout; + let receipt = tokio::time::timeout(Duration::from_secs(timeout), async { + loop { + if let Some(receipt) = provider.get_transaction_receipt(tx_hash).await? { + return Ok::<_, eyre::Error>(receipt); + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + }) + .await + .map_err(|_| eyre::eyre!("Timeout waiting for batch transaction receipt"))??; + + let success = receipt.status(); + if success { + sh_println!( + "Batch transaction confirmed in block {}", + receipt.block_number.unwrap_or(0) + )?; + } else { + bail!("Batch transaction failed (reverted)"); + } + + // For CREATE transactions, compute the deployed contract address + let created_address = if has_create { + let deployed_addr = sender.create(nonce); + sh_println!("Contract deployed at: {:#x}", deployed_addr)?; + Some(deployed_addr) + } else { + None + }; + + // Add receipt to sequence for each original transaction. + // In batch mode, all calls share the same receipt. Set contract_address + // only for index 0 if CREATE, clear for the rest to prevent the verifier + // from attempting to verify the same address multiple times. + for idx in 0..calls.len() { + let mut tx_receipt = receipt.clone(); + if idx == 0 && has_create { + tx_receipt.contract_address = created_address; + } else { + tx_receipt.contract_address = None; + } + sequence.receipts.push(tx_receipt); + } + + // Mark all transactions as pending with the batch tx hash + for i in 0..sequence.transactions.len() { + sequence.add_pending(i, tx_hash); + } + + let chain = sequence.chain; + let _ = sequence; + + self.sequence.save(true, false)?; + + let total_gas = receipt.gas_used(); + let gas_price = receipt.effective_gas_price() as u64; + let total_paid = total_gas * gas_price; + let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); + let gas_price_gwei = format_units(gas_price, 9).unwrap_or_else(|_| "N/A".to_string()); + + let token_symbol = NamedChain::try_from(chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); + sh_println!( + "\nTotal Paid: {} {} ({} gas * {} gwei)", + paid.trim_end_matches('0'), + token_symbol, + total_gas, + gas_price_gwei.trim_end_matches('0').trim_end_matches('.') + )?; + + if !shell::is_json() { + sh_println!("\n\n==========================")?; + sh_println!("\nBATCH EXECUTION COMPLETE & SUCCESSFUL.")?; + sh_println!("All {} calls executed atomically in a single transaction.", calls.len())?; + } + + Ok(BroadcastedState { + args: self.args, + script_config: self.script_config, + build_data: self.build_data, + sequence: self.sequence, + }) + } +} diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 885e7ca5879be..f1462bb868f58 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -1,24 +1,26 @@ use crate::{ - broadcast::BundledState, execute::LinkedState, multi_sequence::MultiChainSequence, - sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig, + ScriptArgs, ScriptConfig, broadcast::BundledState, execute::LinkedState, + multi_sequence::MultiChainSequence, sequence::ScriptSequenceKind, }; -use alloy_primitives::{Bytes, B256}; +use alloy_network::AnyNetwork; +use alloy_primitives::{B256, Bytes}; use alloy_provider::Provider; use eyre::{OptionExt, Result}; use forge_script_sequence::ScriptSequence; use foundry_cheatcodes::Wallets; use foundry_common::{ - compile::ProjectCompiler, provider::try_get_http_provider, ContractData, ContractsByArtifact, + ContractData, ContractsByArtifact, compile::ProjectCompiler, provider::ProviderBuilder, }; use foundry_compilers::{ + ArtifactId, ProjectCompileOutput, artifacts::{BytecodeObject, Libraries}, - compilers::{multi::MultiCompilerLanguage, Language}, + compilers::{Language, multi::MultiCompilerLanguage}, info::ContractInfo, utils::source_files_iter, - ArtifactId, ProjectCompileOutput, }; -use foundry_evm::traces::debug::ContractSources; +use foundry_evm::{core::evm::FoundryEvmNetwork, traces::debug::ContractSources}; use foundry_linking::Linker; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use std::{path::PathBuf, str::FromStr, sync::Arc}; /// Container for the compiled contracts. @@ -39,10 +41,13 @@ impl BuildData { /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to /// default linking with sender nonce and address. - pub async fn link(self, script_config: &ScriptConfig) -> Result { + pub async fn link( + self, + script_config: &ScriptConfig, + ) -> Result { let create2_deployer = script_config.evm_opts.create2_deployer; let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { - let provider = try_get_http_provider(fork_url)?; + let provider = ProviderBuilder::::new(fork_url).build()?; let deployer_code = provider.get_code_at(create2_deployer).await?; !deployer_code.is_empty() @@ -102,7 +107,7 @@ pub enum ScriptPredeployLibraries { } impl ScriptPredeployLibraries { - pub fn libraries_count(&self) -> usize { + pub const fn libraries_count(&self) -> usize { match self { Self::Default(libs) => libs.len(), Self::Create2(libs, _) => libs.len(), @@ -152,17 +157,18 @@ impl LinkedBuildData { } /// First state basically containing only inputs of the user. -pub struct PreprocessedState { +pub struct PreprocessedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, } -impl PreprocessedState { +impl PreprocessedState { /// Parses user input and compiles the contracts depending on script target. /// After compilation, finds exact [ArtifactId] of the target contract. - pub fn compile(self) -> Result { - let Self { args, script_config, script_wallets } = self; + pub fn compile(self) -> Result> { + let Self { args, script_config, script_wallets, browser_wallet } = self; let project = script_config.config.project()?; let mut target_name = args.target_contract.clone(); @@ -182,25 +188,24 @@ impl PreprocessedState { } }; - #[expect(clippy::redundant_clone)] let sources_to_compile = source_files_iter( project.paths.sources.as_path(), MultiCompilerLanguage::FILE_EXTENSIONS, ) - .chain([target_path.to_path_buf()]); + .chain([target_path.clone()]); let output = ProjectCompiler::new().files(sources_to_compile).compile(&project)?; let mut target_id: Option = None; - // Find target artfifact id by name and path in compilation artifacts. + // Find target artifact id by name and path in compilation artifacts. for (id, contract) in output.artifact_ids().filter(|(id, _)| id.source == target_path) { if let Some(name) = &target_name { if id.name != *name { continue; } - } else if contract.abi.as_ref().is_none_or(|abi| abi.is_empty()) || - contract.bytecode.as_ref().is_none_or(|b| match &b.object { + } else if contract.abi.as_ref().is_none_or(|abi| abi.is_empty()) + || contract.bytecode.as_ref().is_none_or(|b| match &b.object { BytecodeObject::Bytecode(b) => b.is_empty(), BytecodeObject::Unlinked(_) => false, }) @@ -217,7 +222,9 @@ impl PreprocessedState { let target_name = target.name.split('.').next().unwrap(); let id_name = id.name.split('.').next().unwrap(); if target_name != id_name { - eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") + eyre::bail!( + "Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`" + ) } } target_id = Some(id); @@ -229,36 +236,38 @@ impl PreprocessedState { args, script_config, script_wallets, + browser_wallet, build_data: BuildData { output, target, project_root: project.root().to_path_buf() }, }) } } /// State after we have determined and compiled target contract to be executed. -pub struct CompiledState { +pub struct CompiledState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: BuildData, } -impl CompiledState { +impl CompiledState { /// Uses provided sender address to compute library addresses and link contracts with them. - pub async fn link(self) -> Result { - let Self { args, script_config, script_wallets, build_data } = self; + pub async fn link(self) -> Result> { + let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let build_data = build_data.link(&script_config).await?; - Ok(LinkedState { args, script_config, script_wallets, build_data }) + Ok(LinkedState { args, script_config, script_wallets, browser_wallet, build_data }) } /// Tries loading the resumed state from the cache files, skipping simulation stage. - pub async fn resume(self) -> Result { + pub async fn resume(self) -> Result> { let chain = if self.args.multi { None } else { let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; - let provider = Arc::new(try_get_http_provider(fork_url)?); + let provider = Arc::new(ProviderBuilder::::new(&fork_url).build()?); Some(provider.get_chain_id().await?) }; @@ -282,35 +291,49 @@ impl CompiledState { } }; - let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { - let mut froms = sequence.sequences().iter().flat_map(|s| { - s.transactions - .iter() - .skip(s.receipts.len()) - .map(|t| t.transaction.from().expect("from is missing in script artifact")) - }); - - let available_signers = self - .script_wallets - .signers() - .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; - - if !froms.all(|from| available_signers.contains(&from)) { - // IF we are missing required signers, execute script as we might need to collect - // private keys from the execution. - let executed = self.link().await?.prepare_execution().await?.execute().await?; + let (args, build_data, script_wallets, browser_wallet, script_config) = + if self.args.unlocked { ( - executed.args, - executed.build_data.build_data, - executed.script_wallets, - executed.script_config, + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, ) } else { - (self.args, self.build_data, self.script_wallets, self.script_config) - } - } else { - (self.args, self.build_data, self.script_wallets, self.script_config) - }; + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from().expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if froms.all(|from| available_signers.contains(&from)) { + ( + self.args, + self.build_data, + self.script_wallets, + self.browser_wallet, + self.script_config, + ) + } else { + // IF we are missing required signers, execute script as we might need to + // collect private keys from the execution. + let executed = self.link().await?.prepare_execution().await?.execute().await?; + ( + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.browser_wallet, + executed.script_config, + ) + } + }; // Collect libraries from sequence and link contracts with them. let libraries = match sequence { @@ -325,12 +348,17 @@ impl CompiledState { args, script_config, script_wallets, + browser_wallet, build_data: linked_build_data, sequence, }) } - fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { + fn try_load_sequence( + &self, + chain: Option, + dry_run: bool, + ) -> Result> { if let Some(chain) = chain { let sequence = ScriptSequence::load( &self.script_config.config, diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index ff9ed1ed6aab5..627bcfffd3802 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -1,36 +1,39 @@ -use super::{runner::ScriptRunner, JsonResult, NestedValue, ScriptResult}; +use super::{JsonResult, NestedValue, ScriptResult, runner::ScriptRunner}; use crate::{ + ScriptArgs, ScriptConfig, build::{CompiledState, LinkedBuildData}, simulate::PreSimulationState, - ScriptArgs, ScriptConfig, }; use alloy_dyn_abi::FunctionExt; use alloy_json_abi::{Function, InternalType, JsonAbi}; +use alloy_network::{AnyNetwork, Network, TransactionBuilder}; use alloy_primitives::{ - map::{HashMap, HashSet}, Address, Bytes, + map::{HashMap, HashSet}, }; use alloy_provider::Provider; -use alloy_rpc_types::TransactionInput; +use alloy_rpc_types::TransactionInputKind; use eyre::{OptionExt, Result}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; use foundry_common::{ - fmt::{format_token, format_token_raw}, - provider::get_http_provider, ContractsByArtifact, + fmt::{format_token, format_token_raw}, + provider::ProviderBuilder, }; use foundry_config::NamedChain; use foundry_debugger::Debugger; use foundry_evm::{ + core::evm::FoundryEvmNetwork, decode::decode_console_logs, inspectors::cheatcodes::BroadcastableTransactions, traces::{ - decode_trace_arena, + CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, decode_trace_arena, identifier::{SignaturesIdentifier, TraceIdentifiers}, - render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + render_trace_arena, }, }; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::join_all; use itertools::Itertools; use std::path::Path; @@ -38,10 +41,11 @@ use yansi::Paint; /// State after linking, contains the linked build data along with library addresses and optional /// array of libraries that need to be predeployed. -pub struct LinkedState { +pub struct LinkedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, } @@ -58,11 +62,11 @@ pub struct ExecutionData { pub abi: JsonAbi, } -impl LinkedState { +impl LinkedState { /// Given linked and compiled artifacts, prepares data we need for execution. /// This includes the function to call and the calldata to pass to it. - pub async fn prepare_execution(self) -> Result { - let Self { args, script_config, script_wallets, build_data } = self; + pub async fn prepare_execution(self) -> Result> { + let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let target_contract = build_data.get_target_contract()?; @@ -76,6 +80,7 @@ impl LinkedState { args, script_config, script_wallets, + browser_wallet, execution_data: ExecutionData { func, calldata, @@ -89,18 +94,19 @@ impl LinkedState { /// Same as [LinkedState], but also contains [ExecutionData]. #[derive(Debug)] -pub struct PreExecutionState { +pub struct PreExecutionState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, } -impl PreExecutionState { +impl PreExecutionState { /// Executes the script and returns the state after execution. /// Might require executing script twice in cases when we determine sender from execution. - pub async fn execute(mut self) -> Result { + pub async fn execute(mut self) -> Result> { let mut runner = self .script_config .get_runner_with_cheatcodes( @@ -122,6 +128,7 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data.build_data, }; @@ -132,6 +139,7 @@ impl PreExecutionState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: result, @@ -139,7 +147,10 @@ impl PreExecutionState { } /// Executes the script using the provided runner and returns the [ScriptResult]. - pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result { + pub async fn execute_with_runner( + &self, + runner: &mut ScriptRunner, + ) -> Result> { let (address, mut setup_result) = runner.setup( &self.build_data.predeploy_libraries, self.execution_data.bytecode.clone(), @@ -157,6 +168,7 @@ impl PreExecutionState { setup_result.traces.extend(script_result.traces); setup_result.labeled_addresses.extend(script_result.labeled_addresses); setup_result.returned = script_result.returned; + setup_result.exit_reason = script_result.exit_reason; setup_result.breakpoints = script_result.breakpoints; match (&mut setup_result.transactions, script_result.transactions) { @@ -179,21 +191,23 @@ impl PreExecutionState { /// them instead. fn maybe_new_sender( &self, - transactions: Option<&BroadcastableTransactions>, + transactions: Option<&BroadcastableTransactions>, ) -> Result> { let mut new_sender = None; if let Some(txs) = transactions { // If the user passed a `--sender` don't check anything. - if self.build_data.predeploy_libraries.libraries_count() > 0 && - self.args.evm.sender.is_none() + if self.build_data.predeploy_libraries.libraries_count() > 0 + && self.args.evm.sender.is_none() { for tx in txs { if tx.transaction.to().is_none() { let sender = tx.transaction.from().expect("no sender"); if let Some(ns) = new_sender { if sender != ns { - sh_warn!("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; + sh_warn!( + "You have more than one deployer who could predeploy libraries. Using `--sender` instead." + )?; return Ok(None); } } else if sender != self.script_config.evm_opts.sender { @@ -217,10 +231,9 @@ pub struct RpcData { impl RpcData { /// Iterates over script transactions and collects RPC urls. - fn from_transactions(txs: &BroadcastableTransactions) -> Self { + fn from_transactions(txs: &BroadcastableTransactions) -> Self { let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - let total_rpcs = - txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); + let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::>(); Self { total_rpcs, missing_rpc } } @@ -234,7 +247,7 @@ impl RpcData { /// Checks if all RPCs support EIP-3855. Prints a warning if not. async fn check_shanghai_support(&self) -> Result<()> { let chain_ids = self.total_rpcs.iter().map(|rpc| async move { - let provider = get_http_provider(rpc); + let provider = ProviderBuilder::::new(rpc).build().ok()?; let id = provider.get_chain_id().await.ok()?; NamedChain::try_from(id).ok() }); @@ -269,30 +282,33 @@ pub struct ExecutionArtifacts { } /// State after the script has been executed. -pub struct ExecutedState { +pub struct ExecutedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, } -impl ExecutedState { +impl ExecutedState { /// Collects the data we need for simulation and various post-execution tasks. - pub async fn prepare_simulation(self) -> Result { + pub async fn prepare_simulation(self) -> Result> { let returns = self.get_returns()?; let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; - let mut txs = self.execution_result.transactions.clone().unwrap_or_default(); + let mut txs: BroadcastableTransactions = + self.execution_result.transactions.clone().unwrap_or_default(); // Ensure that unsigned transactions have both `data` and `input` populated to avoid // issues with eth_estimateGas and eth_sendTransaction requests. for tx in &mut txs { - if let Some(req) = tx.transaction.as_unsigned_mut() { - req.input = - TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input()); + if let Some(req) = tx.transaction.as_unsigned_mut() + && let Some(input) = req.input().cloned() + { + *req = req.clone().with_input_kind(input, TransactionInputKind::Both); } } let rpc_data = RpcData::from_transactions(&txs); @@ -311,6 +327,7 @@ impl ExecutedState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_data: self.execution_data, execution_result: self.execution_result, @@ -323,6 +340,8 @@ impl ExecutedState { &self, known_contracts: &ContractsByArtifact, ) -> Result { + let chain_id = self.script_config.evm_opts.get_remote_chain_id().await; + let mut decoder = CallTraceDecoderBuilder::new() .with_labels(self.execution_result.labeled_addresses.clone()) .with_verbosity(self.script_config.evm_opts.verbosity) @@ -330,12 +349,13 @@ impl ExecutedState { .with_signature_identifier(SignaturesIdentifier::from_config( &self.script_config.config, )?) + .with_label_disabled(self.args.disable_labels) + .with_chain_id(chain_id.map(|c| c.id())) .build(); - let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan( - &self.script_config.config, - self.script_config.evm_opts.get_remote_chain_id().await, - )?; + let mut identifier = TraceIdentifiers::new() + .with_local(known_contracts) + .with_external(&self.script_config.config, chain_id)?; for (_, trace) in &self.execution_result.traces { decoder.identify(trace, &mut identifier); @@ -359,10 +379,10 @@ impl ExecutedState { ty: "unknown".to_string(), }); - let label = if !output.name.is_empty() { - output.name.to_string() - } else { + let label = if output.name.is_empty() { index.to_string() + } else { + output.name.clone() }; returns.insert( @@ -383,7 +403,7 @@ impl ExecutedState { } } -impl PreSimulationState { +impl PreSimulationState { pub async fn show_json(&self) -> Result<()> { let mut result = self.execution_result.clone(); @@ -403,7 +423,11 @@ impl PreSimulationState { if !self.execution_result.success { return Err(eyre::eyre!( "script failed: {}", - &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + &self + .execution_artifacts + .decoder + .revert_decoder + .decode(&result.returned[..], result.exit_reason) )); } @@ -457,10 +481,10 @@ impl PreSimulationState { ty: "unknown".to_string(), }); - let label = if !output.name.is_empty() { - output.name.to_string() - } else { + let label = if output.name.is_empty() { index.to_string() + } else { + output.name.clone() }; sh_println!( "{label}: {internal_type} {value}", @@ -486,7 +510,11 @@ impl PreSimulationState { if !result.success { return Err(eyre::eyre!( "script failed: {}", - &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + &self + .execution_artifacts + .decoder + .revert_decoder + .decode(&result.returned[..], result.exit_reason) )); } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index ac5954924fa47..22150c8b868ea 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -2,8 +2,9 @@ //! //! Smart contract scripting. +#![recursion_limit = "256"] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -11,12 +12,12 @@ extern crate foundry_common; #[macro_use] extern crate tracing; -use crate::runner::ScriptRunner; +use crate::{broadcast::BundledState, runner::ScriptRunner}; use alloy_json_abi::{Function, JsonAbi}; +use alloy_network::Network; use alloy_primitives::{ - hex, + Address, Bytes, Log, U256, hex, map::{AddressHashMap, HashMap}, - Address, Bytes, Log, TxKind, U256, }; use alloy_signer::Signer; use broadcast::next_nonce; @@ -26,35 +27,42 @@ use dialoguer::Confirm; use eyre::{ContextCompat, Result}; use forge_script_sequence::{AdditionalContract, NestedValue}; use forge_verify::{RetryArgs, VerifierArgs}; -use foundry_block_explorers::EtherscanApiVersion; use foundry_cli::{ - opts::{BuildOpts, GlobalArgs}, + opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts}, utils::LoadConfig, }; use foundry_common::{ + CONTRACT_MAX_SIZE, ContractsByArtifact, SELECTOR_LEN, abi::{encode_function_args, get_func}, - evm::{Breakpoints, EvmArgs}, - shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN, + shell, }; use foundry_compilers::ArtifactId; use foundry_config::{ - figment, + Config, figment, figment::{ - value::{Dict, Map}, Metadata, Profile, Provider, + value::{Dict, Map}, }, - Config, }; +#[cfg(feature = "optimism")] +use foundry_evm::core::evm::OpEvmNetwork; use foundry_evm::{ backend::Backend, + core::{ + Breakpoints, FoundryTransaction, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor}, + tempo::PATH_USD_ADDRESS, + }, executors::ExecutorBuilder, inspectors::{ - cheatcodes::{BroadcastableTransactions, Wallets}, CheatsConfig, + cheatcodes::{BroadcastableTransactions, Wallets}, }, opts::EvmOpts, + revm::interpreter::InstructionResult, traces::{TraceMode, Traces}, }; +use foundry_evm_networks::NetworkConfigs; use foundry_wallets::MultiWalletOpts; use serde::Serialize; use std::path::PathBuf; @@ -97,7 +105,7 @@ pub struct ScriptArgs { pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[arg(long, short, default_value = "run()")] + #[arg(long, short, default_value = "run")] pub sig: String, /// Max priority fee per gas for EIP1559 transactions. @@ -119,12 +127,25 @@ pub struct ScriptArgs { #[arg(long)] pub broadcast: bool, - /// Batch size of transactions. + /// Batch all broadcast transactions into a single Tempo batch transaction. + /// + /// When enabled, all vm.broadcast() calls are collected and sent as a single + /// atomic type 0x76 transaction instead of individual transactions. + /// This provides atomicity (all-or-nothing execution) and gas savings. + #[arg(long)] + pub batch: bool, + + /// Number of calls per Tempo batch transaction. /// - /// This is ignored and set to 1 if batching is not available or `--slow` is enabled. - #[arg(long, default_value = "100")] + /// When `--batch` is enabled, splits the collected calls into multiple batch + /// transactions of at most this many calls each. + #[arg(long, requires = "batch", default_value = "100")] pub batch_size: usize, + /// Tempo transaction options. + #[command(flatten)] + pub tempo: TempoOpts, + /// Skips on-chain simulation. #[arg(long)] pub skip_simulation: bool, @@ -133,10 +154,10 @@ pub struct ScriptArgs { #[arg(long, short, default_value = "130")] pub gas_estimate_multiplier: u64, - /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender + /// Send via `eth_sendTransaction` using the `--sender` argument as sender. #[arg( long, - conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], + conflicts_with_all = &["private_key", "private_keys", "ledger", "trezor", "aws", "browser"], )] pub unlocked: bool, @@ -179,16 +200,20 @@ pub struct ScriptArgs { #[arg(long)] pub non_interactive: bool, + /// Disables the contract size limit during script execution. + #[arg(long)] + pub disable_code_size_limit: bool, + + /// Disables the labels in the traces. + #[arg(long)] + pub disable_labels: bool, + /// The Etherscan (or equivalent) API key #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] pub etherscan_api_key: Option, - /// The Etherscan API version. - #[arg(long, env = "ETHERSCAN_API_VERSION", value_name = "VERSION")] - pub etherscan_api_version: Option, - /// Verifies all the contracts found in the receipts of a script, if any. - #[arg(long)] + #[arg(long, requires = "broadcast")] pub verify: bool, /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions, either @@ -224,32 +249,107 @@ pub struct ScriptArgs { } impl ScriptArgs { - pub async fn preprocess(self) -> Result { - let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); - + /// Loads config, resolves evm_opts (including network inference from fork), and returns them. + async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> { let (config, mut evm_opts) = self.load_config_and_evm_opts()?; + if self.tempo.is_tempo() { + // If fee token or expiry is set directly select tempo + evm_opts.networks = NetworkConfigs::with_tempo(); + } else { + // Auto-detect network from fork chain ID when not explicitly configured. + evm_opts.infer_network_from_fork().await; + } + + Ok((config, evm_opts)) + } + + async fn preprocess( + self, + config: Config, + mut evm_opts: EvmOpts, + ) -> Result> { + let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); + let browser_wallet = self.wallets.browser_signer::().await?; + if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; + } else if self.evm.sender.is_none() { + // If no sender was explicitly set via --sender, auto-detect it from available signers: + // use the sole signer's address if there's exactly one, or fall back to the browser + // wallet address if present. + if let Ok(signers) = script_wallets.signers() + && signers.len() == 1 + { + evm_opts.sender = signers[0]; + } else if let Some(signer) = browser_wallet.as_ref().map(|b| b.address()) { + evm_opts.sender = signer + } } - let script_config = ScriptConfig::new(config, evm_opts).await?; + let mut tempo = self.tempo.clone(); + tempo.resolve_expires(); - Ok(PreprocessedState { args: self, script_config, script_wallets }) + if evm_opts.networks.is_tempo() && tempo.fee_token.is_none() { + tempo.fee_token = Some(PATH_USD_ADDRESS); + } + + let script_config = ScriptConfig::new(config, evm_opts, self.batch, tempo).await?; + Ok(PreprocessedState { args: self, script_config, script_wallets, browser_wallet }) } /// Executes the script + #[allow(clippy::large_stack_frames)] pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let state = self.preprocess().await?; + let (config, evm_opts) = self.resolved_evm_opts().await?; + + let is_tempo = evm_opts.networks.is_tempo(); + + if self.batch && !is_tempo { + eyre::bail!("--batch mode is only supported on Tempo networks"); + } + + if is_tempo { + let batch = self.batch; + let bundled = match self.prepare_bundled::(config, evm_opts).await? { + Some(bundled) => bundled, + None => return Ok(()), + }; + let bundled = bundled.wait_for_pending().await?; + let broadcasted = + if batch { bundled.broadcast_batch().await? } else { bundled.broadcast().await? }; + if broadcasted.args.verify { + broadcasted.verify().await?; + } + return Ok(()); + } + + #[cfg(feature = "optimism")] + if evm_opts.networks.is_optimism() { + return self.run_generic_script::(config, evm_opts).await; + } + + self.run_generic_script::(config, evm_opts).await + } + + /// Prepares the bundled state (compile, simulate, bundle) and returns it + /// for broadcasting, or returns `None` if there's nothing to broadcast + /// (e.g., debug mode, no transactions, missing RPCs). + #[allow(clippy::large_stack_frames)] + async fn prepare_bundled( + self, + config: Config, + evm_opts: EvmOpts, + ) -> Result>> { + let state = self.preprocess::(config, evm_opts).await?; let create2_deployer = state.script_config.evm_opts.create2_deployer; let compiled = state.compile()?; // Move from `CompiledState` to `BundledState` either by resuming or executing and // simulating script. - let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast) - { + let bundled = if compiled.args.resume { compiled.resume().await? } else { // Drive state machine to point at which we have everything needed for simulation. @@ -265,8 +365,8 @@ impl ScriptArgs { if pre_simulation.args.debug { return match pre_simulation.args.dump.clone() { - Some(path) => pre_simulation.dump_debugger(&path), - None => pre_simulation.run_debugger(), + Some(path) => pre_simulation.dump_debugger(&path).map(|_| None), + None => pre_simulation.run_debugger().map(|_| None), }; } @@ -288,7 +388,7 @@ impl ScriptArgs { sh_warn!("No transactions to broadcast.")?; } - return Ok(()); + return Ok(None); } // Check if there are any missing RPCs and exit early to avoid hard error. @@ -297,7 +397,7 @@ impl ScriptArgs { sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; } - return Ok(()); + return Ok(None); } pre_simulation.args.check_contract_sizes( @@ -317,9 +417,11 @@ impl ScriptArgs { bundled.sequence.show_transactions()?; } - sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + sh_println!( + "\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more." + )?; } - return Ok(()); + return Ok(None); } // Exit early if something is wrong with verification options. @@ -327,6 +429,19 @@ impl ScriptArgs { bundled.verify_preflight_check()?; } + Ok(Some(bundled)) + } + + async fn run_generic_script( + self, + config: Config, + evm_opts: EvmOpts, + ) -> Result<()> { + let bundled = match self.prepare_bundled::(config, evm_opts).await? { + Some(bundled) => bundled, + None => return Ok(()), + }; + // Wait for pending txes and broadcast others. let broadcasted = bundled.wait_for_pending().await?.broadcast().await?; @@ -337,9 +452,13 @@ impl ScriptArgs { Ok(()) } - /// In case the user has loaded *only* one private-key, we can assume that he's using it as the - /// `--sender` + /// In case the user has loaded *only* one private-key or a single remote signer (e.g., + /// Turnkey), we can assume that they're using it as the `--sender`. fn maybe_load_private_key(&self) -> Result> { + if let Some(turnkey_address) = self.wallets.turnkey_address() { + return Ok(Some(turnkey_address)); + } + let maybe_sender = self .wallets .private_keys()? @@ -395,12 +514,17 @@ impl ScriptArgs { /// /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns /// the user. - fn check_contract_sizes( + fn check_contract_sizes( &self, - result: &ScriptResult, + result: &ScriptResult, known_contracts: &ContractsByArtifact, create2_deployer: Address, ) -> Result<()> { + // If disable-code-size-limit flag is enabled then skip the size check + if self.disable_code_size_limit { + return Ok(()); + } + // (name, &init, &deployed)[] let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; @@ -423,7 +547,6 @@ impl ScriptArgs { bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); unknown_c += 1; } - continue; } let mut prompt_user = false; @@ -443,15 +566,13 @@ impl ScriptArgs { let mut offset = 0; // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(TxKind::Call(to)) = to { + if let Some(to) = to { if to == create2_deployer { // Size of the salt prefix. offset = 32; } else { continue; } - } else if let Some(TxKind::Create) = to { - // Pass } // Find artifact with a deployment code same as the data. @@ -470,9 +591,9 @@ impl ScriptArgs { } // Only prompt if we're broadcasting and we've not disabled interactivity. - if prompt_user && - !self.non_interactive && - !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? + if prompt_user + && !self.non_interactive + && !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? { eyre::bail!("User canceled the script."); } @@ -480,9 +601,9 @@ impl ScriptArgs { Ok(()) } - /// We only broadcast transactions if --broadcast or --resume was passed. - fn should_broadcast(&self) -> bool { - self.broadcast || self.resume + /// We only broadcast transactions if --broadcast, --resume, or --verify was passed. + const fn should_broadcast(&self) -> bool { + self.broadcast || self.resume || self.verify } } @@ -493,26 +614,27 @@ impl Provider for ScriptArgs { fn data(&self) -> Result, figment::Error> { let mut dict = Dict::default(); - if let Some(ref etherscan_api_key) = + + if let Some(etherscan_api_key) = self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) { dict.insert( "etherscan_api_key".to_string(), - figment::value::Value::from(etherscan_api_key.to_string()), + figment::value::Value::from(etherscan_api_key.clone()), ); } - if let Some(api_version) = &self.etherscan_api_version { - dict.insert("etherscan_api_version".to_string(), api_version.to_string().into()); - } + if let Some(timeout) = self.timeout { dict.insert("transaction_timeout".to_string(), timeout.into()); } + Ok(Map::from([(Config::selected_profile(), dict)])) } } -#[derive(Default, Serialize, Clone)] -pub struct ScriptResult { +#[derive(Serialize, Clone)] +#[serde(bound = "")] +pub struct ScriptResult { pub success: bool, #[serde(rename = "raw_logs")] pub logs: Vec, @@ -520,24 +642,51 @@ pub struct ScriptResult { pub gas_used: u64, pub labeled_addresses: AddressHashMap, #[serde(skip)] - pub transactions: Option, + pub transactions: Option>, pub returned: Bytes, + #[serde(skip)] + pub exit_reason: Option, pub address: Option
, #[serde(skip)] pub breakpoints: Breakpoints, } -impl ScriptResult { - pub fn get_created_contracts(&self) -> Vec { +impl Default for ScriptResult { + fn default() -> Self { + Self { + success: Default::default(), + logs: Default::default(), + traces: Default::default(), + gas_used: Default::default(), + labeled_addresses: Default::default(), + transactions: Default::default(), + returned: Default::default(), + exit_reason: Default::default(), + address: Default::default(), + breakpoints: Default::default(), + } + } +} + +impl ScriptResult { + pub fn get_created_contracts( + &self, + known_contracts: &ContractsByArtifact, + ) -> Vec { self.traces .iter() .flat_map(|(_, traces)| { traces.nodes().iter().filter_map(|node| { if node.trace.kind.is_any_create() { + let init_code = node.trace.data.clone(); + let contract_name = known_contracts + .find_by_creation_code(init_code.as_ref()) + .map(|artifact| artifact.0.name.clone()); return Some(AdditionalContract { - opcode: node.trace.kind, + call_kind: node.trace.kind, address: node.trace.address, - init_code: node.trace.data.clone(), + contract_name, + init_code, }); } None @@ -548,24 +697,34 @@ impl ScriptResult { } #[derive(Serialize)] -struct JsonResult<'a> { +#[serde(bound = "")] +struct JsonResult<'a, N: Network> { logs: Vec, returns: &'a HashMap, #[serde(flatten)] - result: &'a ScriptResult, + result: &'a ScriptResult, } #[derive(Clone, Debug)] -pub struct ScriptConfig { +pub struct ScriptConfig { pub config: Config, pub evm_opts: EvmOpts, pub sender_nonce: u64, /// Maps a rpc url to a backend - pub backends: HashMap, + pub backends: HashMap>, + /// Whether to batch all broadcast transactions into a single Tempo batch transaction. + pub batch: bool, + /// Tempo transaction options applied to broadcast transactions. + pub tempo: TempoOpts, } -impl ScriptConfig { - pub async fn new(config: Config, evm_opts: EvmOpts) -> Result { +impl ScriptConfig { + pub async fn new( + config: Config, + evm_opts: EvmOpts, + batch: bool, + tempo: TempoOpts, + ) -> Result { let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? } else { @@ -573,7 +732,7 @@ impl ScriptConfig { 1 }; - Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() }) + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default(), batch, tempo }) } pub async fn update_sender(&mut self, sender: Address) -> Result<()> { @@ -587,7 +746,7 @@ impl ScriptConfig { Ok(()) } - async fn get_runner(&mut self) -> Result { + async fn get_runner(&mut self) -> Result> { self._get_runner(None, false).await } @@ -597,7 +756,7 @@ impl ScriptConfig { script_wallets: Wallets, debug: bool, target: ArtifactId, - ) -> Result { + ) -> Result> { self._get_runner(Some((known_contracts, script_wallets, target)), debug).await } @@ -605,15 +764,16 @@ impl ScriptConfig { &mut self, cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>, debug: bool, - ) -> Result { + ) -> Result> { trace!("preparing script runner"); - let env = self.evm_opts.evm_env().await?; + let (evm_env, mut tx_env, fork_block) = self.evm_opts.env::<_, _, TxEnvFor>().await?; let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { match self.backends.get(fork_url) { Some(db) => db.clone(), None => { - let fork = self.evm_opts.get_fork(&self.config, env.clone()); + let fork = + self.evm_opts.get_fork(&self.config, evm_env.cfg_env.chain_id, fork_block); let backend = Backend::spawn(fork)?; self.backends.insert(fork_url.clone(), backend.clone()); backend @@ -627,11 +787,12 @@ impl ScriptConfig { }; // We need to enable tracing to decode contract names: local or external. - let mut builder = ExecutorBuilder::new() + let mut builder = ExecutorBuilder::default() .inspectors(|stack| { stack + .logs(self.config.live_logs) .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) - .odyssey(self.evm_opts.odyssey) + .networks(self.evm_opts.networks) .create2_deployer(self.evm_opts.create2_deployer) }) .spec_id(self.config.evm_spec_id()) @@ -647,6 +808,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(target), + self.tempo.fee_token, ) .into(), ) @@ -655,13 +817,19 @@ impl ScriptConfig { }); } - Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone())) + // Propagate fee token to the transaction environment so that internal EVM calls + // (e.g. script deployment, setUp) use the correct fee token for Tempo networks. + tx_env.set_fee_token(self.tempo.fee_token); + + Ok(ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())) } } #[cfg(test)] mod tests { use super::*; + use alloy_network::Ethereum; + use alloy_primitives::address; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -673,6 +841,50 @@ mod tests { assert_eq!(args.sig, sig); } + #[test] + fn can_parse_shared_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.fee-token", + "1", + "--tempo.expires", + "10", + ]); + + assert_eq!( + args.tempo.fee_token, + Some(address!("0x20C0000000000000000000000000000000000001")) + ); + assert_eq!(args.tempo.expires, Some(10)); + } + + #[test] + fn can_parse_sponsor_tempo_opts() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--tempo.sponsor", + "0x1111111111111111111111111111111111111111", + "--tempo.sponsor-signer", + "env://TEMPO_SPONSOR_PK", + ]); + + assert_eq!( + args.tempo.sponsor, + Some(address!("0x1111111111111111111111111111111111111111")) + ); + assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK")); + } + + #[test] + fn can_parse_full_tempo_opts() { + let args = + ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]); + + assert_eq!(args.tempo.nonce_key, Some(U256::from(1))); + } + #[test] fn can_parse_unlocked() { let args = ScriptArgs::parse_from([ @@ -692,7 +904,7 @@ mod tests { "0x4e59b44847b379578588920ca78fbf26c0b4956c", "--unlocked", "--private-key", - key.to_string().as_str(), + &key.to_string(), ]); assert!(args.is_err()); } @@ -709,6 +921,18 @@ mod tests { assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); } + #[test] + fn can_disable_code_size_limit() { + let args = + ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]); + assert!(args.disable_code_size_limit); + + let result = ScriptResult::::default(); + let contracts = ContractsByArtifact::default(); + let create = Address::ZERO; + assert!(args.check_contract_sizes(&result, &contracts, create).is_ok()); + } + #[test] fn can_parse_verifier_url() { let args = ScriptArgs::parse_from([ @@ -753,10 +977,10 @@ mod tests { let config = r#" [profile.default] - etherscan_api_key = "mumbai" + etherscan_api_key = "amoy" [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/" } + amoy = { key = "https://etherscan-amoy.com/" } "#; let toml_file = root.join(Config::FILE_NAME); @@ -765,14 +989,14 @@ mod tests { "foundry-cli", "Contract.sol", "--etherscan-api-key", - "mumbai", + "amoy", "--root", root.as_os_str().to_str().unwrap(), ]); let config = args.load_config().unwrap(); - let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); - assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); + let amoy = config.get_etherscan_api_key(Some(NamedChain::PolygonAmoy.into())); + assert_eq!(amoy, Some("https://etherscan-amoy.com/".to_string())); } #[test] @@ -784,7 +1008,7 @@ mod tests { [profile.default] [rpc_endpoints] - polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" + polygonAmoy = "https://polygon-amoy.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" "#; let toml_file = root.join(Config::FILE_NAME); @@ -793,7 +1017,7 @@ mod tests { "foundry-cli", "DeployV1", "--rpc-url", - "polygonMumbai", + "polygonAmoy", "--root", root.as_os_str().to_str().unwrap(), ]); @@ -802,12 +1026,14 @@ mod tests { assert!(err.downcast::().is_ok()); - std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); + unsafe { + std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); + } let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string())); + assert_eq!(config.eth_rpc_url, Some("polygonAmoy".to_string())); assert_eq!( evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string()) ); } @@ -820,10 +1046,10 @@ mod tests { [profile.default] [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" + amoy = "https://polygon-amoy.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" [etherscan] - mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + amoy = { key = "${_ETHERSCAN_API_KEY}", chain = 80002, url = "https://amoy.polygonscan.com/" } "#; let toml_file = root.join(Config::FILE_NAME); @@ -832,9 +1058,9 @@ mod tests { "foundry-cli", "DeployV1", "--rpc-url", - "mumbai", + "amoy", "--etherscan-api-key", - "mumbai", + "amoy", "--root", root.as_os_str().to_str().unwrap(), ]); @@ -842,18 +1068,22 @@ mod tests { assert!(err.downcast::().is_ok()); - std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_POLYSCAN_API_KEY", "polygonkey"); + unsafe { + std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); + } + unsafe { + std::env::set_var("_ETHERSCAN_API_KEY", "etherscan_api_key"); + } let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("mumbai".to_string())); + assert_eq!(config.eth_rpc_url, Some("amoy".to_string())); assert_eq!( evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string()) ); - let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); - assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(Some(80002u64.into())); + assert_eq!(etherscan, Some("etherscan_api_key".to_string())); let etherscan = config.get_etherscan_api_key(None); - assert_eq!(etherscan, Some("polygonkey".to_string())); + assert_eq!(etherscan, Some("etherscan_api_key".to_string())); } #[test] @@ -865,10 +1095,10 @@ mod tests { [profile.default] [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" + amoy = "https://polygon-amoy.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" [etherscan] - mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" } + amoy = { key = "${_SOLE_ETHERSCAN_API_KEY}" } "#; let toml_file = root.join(Config::FILE_NAME); @@ -877,7 +1107,7 @@ mod tests { "foundry-cli", "DeployV1", "--rpc-url", - "mumbai", + "amoy", "--root", root.as_os_str().to_str().unwrap(), ]); @@ -885,17 +1115,21 @@ mod tests { assert!(err.downcast::().is_ok()); - std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey"); + unsafe { + std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); + } + unsafe { + std::env::set_var("_SOLE_ETHERSCAN_API_KEY", "etherscan_api_key"); + } let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); assert_eq!( evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string()) ); - let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); - assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(Some(80002u64.into())); + assert_eq!(etherscan, Some("etherscan_api_key".to_string())); let etherscan = config.get_etherscan_api_key(None); - assert_eq!(etherscan, Some("polygonkey".to_string())); + assert_eq!(etherscan, Some("etherscan_api_key".to_string())); } // @@ -918,4 +1152,19 @@ mod tests { ]); assert!(args.with_gas_price.unwrap().is_zero()); } + + #[test] + fn test_priority_gas_price_cannot_exceed_gas_price() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "--broadcast", + "--with-gas-price", + "100", + "--priority-gas-price", + "200", + "Script", + ]); + // priority (200) > max_fee (100) — broadcast should reject this at runtime + assert!(args.priority_gas_price.unwrap() > args.with_gas_price.unwrap()); + } } diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs index e0fd4d1bc7e68..14c92a5046f61 100644 --- a/crates/script/src/multi_sequence.rs +++ b/crates/script/src/multi_sequence.rs @@ -1,25 +1,27 @@ +use alloy_network::Network; use eyre::{ContextCompat, Result, WrapErr}; use forge_script_sequence::{ - now, sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR, + DRY_RUN_DIR, ScriptSequence, SensitiveScriptSequence, now, sig_to_file_name, }; use foundry_common::{fs, shell}; use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; -use std::{ - io::{BufWriter, Write}, - path::PathBuf, -}; +use std::path::PathBuf; /// Holds the sequences of multiple chain deployments. #[derive(Clone, Default, Serialize, Deserialize)] -pub struct MultiChainSequence { - pub deployments: Vec, +#[serde(bound( + serialize = "N::TransactionRequest: Serialize, N::TxEnvelope: Serialize", + deserialize = "N::TransactionRequest: for<'de2> Deserialize<'de2>, N::TxEnvelope: for<'de2> Deserialize<'de2>" +))] +pub struct MultiChainSequence { + pub deployments: Vec>, #[serde(skip)] pub path: PathBuf, #[serde(skip)] pub sensitive_path: PathBuf, - pub timestamp: u64, + pub timestamp: u128, } /// Sensitive values from script sequences. @@ -29,16 +31,16 @@ pub struct SensitiveMultiChainSequence { } impl SensitiveMultiChainSequence { - fn from_multi_sequence(sequence: MultiChainSequence) -> Self { + fn from_multi_sequence(sequence: &MultiChainSequence) -> Self { Self { - deployments: sequence.deployments.into_iter().map(|sequence| sequence.into()).collect(), + deployments: sequence.deployments.iter().map(SensitiveScriptSequence::from).collect(), } } } -impl MultiChainSequence { +impl MultiChainSequence { pub fn new( - deployments: Vec, + deployments: Vec>, sig: &str, target: &ArtifactId, config: &Config, @@ -46,7 +48,7 @@ impl MultiChainSequence { ) -> Result { let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; - Ok(Self { deployments, path, sensitive_path, timestamp: now().as_secs() }) + Ok(Self { deployments, path, sensitive_path, timestamp: now().as_millis() }) } /// Gets paths in the formats @@ -58,8 +60,8 @@ impl MultiChainSequence { target: &ArtifactId, dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = config.broadcast.to_path_buf(); - let mut cache = config.cache_path.to_path_buf(); + let mut broadcast = config.broadcast.clone(); + let mut cache = config.cache_path.clone(); let mut common = PathBuf::new(); common.push("multi"); @@ -91,7 +93,10 @@ impl MultiChainSequence { } /// Loads the sequences for the multi chain deployment. - pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result { + pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result + where + N::TxEnvelope: for<'d> Deserialize<'d>, + { let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; let mut sequence: Self = foundry_compilers::utils::read_json_file(&path) .wrap_err("Multi-chain deployment not found.")?; @@ -110,18 +115,19 @@ impl MultiChainSequence { } /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> + where + N::TxEnvelope: Serialize, + { self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); - self.timestamp = now().as_secs(); + self.timestamp = now().as_millis(); - let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(self.clone()); + let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(&*self); // broadcast writes //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; + fs::write_pretty_json_file(&self.path, self)?; if save_ts { //../Contract-[timestamp]/run.json @@ -133,9 +139,7 @@ impl MultiChainSequence { // cache writes //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; - writer.flush()?; + fs::write_pretty_json_file(&self.sensitive_path, &sensitive_sequence)?; if save_ts { //../Contract-[timestamp]/run.json diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs index a0a5ba5609030..303d5125c8521 100644 --- a/crates/script/src/progress.rs +++ b/crates/script/src/progress.rs @@ -1,13 +1,15 @@ -use crate::receipts::{check_tx_status, format_receipt, TxStatus}; +use crate::receipts::{PendingReceiptError, TxStatus, check_tx_status, format_receipt}; use alloy_chains::Chain; +use alloy_network::{Network, ReceiptResponse}; use alloy_primitives::{ - map::{B256HashMap, HashMap}, B256, + map::{B256HashMap, HashMap}, }; +use alloy_provider::RootProvider; use eyre::Result; use forge_script_sequence::ScriptSequence; use foundry_cli::utils::init_progress; -use foundry_common::{provider::RetryProvider, shell}; +use foundry_common::shell; use futures::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use parking_lot::RwLock; @@ -30,7 +32,11 @@ pub struct SequenceProgressState { } impl SequenceProgressState { - pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + pub fn new( + sequence_idx: usize, + sequence: &ScriptSequence, + multi: MultiProgress, + ) -> Self { let mut state = if shell::is_quiet() || shell::is_json() { let top_spinner = ProgressBar::hidden(); let txs = ProgressBar::hidden(); @@ -134,14 +140,18 @@ impl SequenceProgressState { } } -/// Clonable wrapper around [SequenceProgressState]. +/// Cloneable wrapper around [SequenceProgressState]. #[derive(Debug, Clone)] pub struct SequenceProgress { pub inner: Arc>, } impl SequenceProgress { - pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + pub fn new( + sequence_idx: usize, + sequence: &ScriptSequence, + multi: MultiProgress, + ) -> Self { Self { inner: Arc::new(RwLock::new(SequenceProgressState::new(sequence_idx, sequence, multi))), } @@ -158,10 +168,10 @@ pub struct ScriptProgress { impl ScriptProgress { /// Returns a [SequenceProgress] instance for the given sequence index. If it doesn't exist, /// creates one. - pub fn get_sequence_progress( + pub fn get_sequence_progress( &self, sequence_idx: usize, - sequence: &ScriptSequence, + sequence: &ScriptSequence, ) -> SequenceProgress { if let Some(progress) = self.state.read().get(&sequence_idx) { return progress.clone(); @@ -171,8 +181,8 @@ impl ScriptProgress { progress } - /// Traverses a set of pendings and either finds receipts, or clears them from - /// the deployment sequence. + /// Traverses a set of pending transactions and either finds receipts, or clears + /// them from the deployment sequence. /// /// For each `tx_hash`, we check if it has confirmed. If it has /// confirmed, we push the receipt (if successful) or push an error (if @@ -181,11 +191,11 @@ impl ScriptProgress { /// has not confirmed, and cannot be found in the mempool, we remove it from /// the `deploy_sequence.pending` vector so that it will be rebroadcast in /// later steps. - pub async fn wait_for_pending( + pub async fn wait_for_pending( &self, sequence_idx: usize, - deployment_sequence: &mut ScriptSequence, - provider: &RetryProvider, + deployment_sequence: &mut ScriptSequence, + provider: &RootProvider, timeout: u64, ) -> Result<()> { if deployment_sequence.pending.is_empty() { @@ -207,28 +217,49 @@ impl ScriptProgress { let mut tasks = futures::stream::iter(futs).buffer_unordered(10); let mut errors: Vec = vec![]; + let mut discarded_transactions = false; while let Some((tx_hash, result)) = tasks.next().await { match result { Err(err) => { - errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}")); - - seq_progress.inner.write().finish_tx_spinner(tx_hash); + // Check if this is a retry error for pending receipts + if err.downcast_ref::().is_some() { + // We've already retried several times with sleep, but the receipt is still + // pending + discarded_transactions = true; + deployment_sequence.remove_pending(tx_hash); + seq_progress + .inner + .write() + .finish_tx_spinner_with_msg(tx_hash, &err.to_string())?; + } else { + errors.push(format!( + "Failure on receiving a receipt for {tx_hash:?}:\n{err}" + )); + seq_progress.inner.write().finish_tx_spinner(tx_hash); + } } Ok(TxStatus::Dropped) => { // We want to remove it from pending so it will be re-broadcast. deployment_sequence.remove_pending(tx_hash); - errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}")); + discarded_transactions = true; - seq_progress.inner.write().finish_tx_spinner(tx_hash); + let msg = format!( + "Transaction {tx_hash:?} dropped from the mempool. It will be retried when using --resume." + ); + seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; } Ok(TxStatus::Success(receipt)) => { trace!(tx_hash=?tx_hash, "received tx receipt"); - let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + let msg = format_receipt( + deployment_sequence.chain.into(), + &receipt, + Some(deployment_sequence), + ); seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; - deployment_sequence.remove_pending(receipt.transaction_hash); + deployment_sequence.remove_pending(receipt.transaction_hash()); deployment_sequence.add_receipt(receipt); } Ok(TxStatus::Revert(receipt)) => { @@ -236,12 +267,16 @@ impl ScriptProgress { // if this is not removed from pending, then the script becomes // un-resumable. Is this desirable on reverts? warn!(tx_hash=?tx_hash, "Transaction Failure"); - deployment_sequence.remove_pending(receipt.transaction_hash); + deployment_sequence.remove_pending(receipt.transaction_hash()); - let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + let msg = format_receipt( + deployment_sequence.chain.into(), + &receipt, + Some(deployment_sequence), + ); seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; - errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); + errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash())); } } } @@ -249,11 +284,20 @@ impl ScriptProgress { // print any errors if !errors.is_empty() { let mut error_msg = errors.join("\n"); - if !deployment_sequence.pending.is_empty() { - error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting - the transactions." + + // Add information about using --resume if necessary + if !deployment_sequence.pending.is_empty() || discarded_transactions { + error_msg += r#" + +Add `--resume` to your command to try and continue broadcasting the transactions. This will attempt to resend transactions that were discarded by the RPC."#; } + eyre::bail!(error_msg); + } else if discarded_transactions { + // If we have discarded transactions but no errors, still inform the user + sh_warn!( + "Some transactions were discarded by the RPC node. Use `--resume` to retry these transactions." + )?; } Ok(()) diff --git a/crates/script/src/providers.rs b/crates/script/src/providers.rs index 6fb3ca81d82fa..7380b2b892580 100644 --- a/crates/script/src/providers.rs +++ b/crates/script/src/providers.rs @@ -1,23 +1,29 @@ -use alloy_primitives::map::{hash_map::Entry, HashMap}; -use alloy_provider::{utils::Eip1559Estimation, Provider}; +use alloy_network::Network; +use alloy_primitives::map::{HashMap, hash_map::Entry}; +use alloy_provider::{Provider, RootProvider, utils::Eip1559Estimation}; use eyre::{Result, WrapErr}; -use foundry_common::provider::{get_http_provider, RetryProvider}; +use foundry_common::provider::ProviderBuilder; use foundry_config::Chain; use std::{ops::Deref, sync::Arc}; /// Contains a map of RPC urls to single instances of [`ProviderInfo`]. -#[derive(Default)] -pub struct ProvidersManager { - pub inner: HashMap, +pub struct ProvidersManager { + pub inner: HashMap>, } -impl ProvidersManager { +impl Default for ProvidersManager { + fn default() -> Self { + Self { inner: Default::default() } + } +} + +impl ProvidersManager { /// Get or initialize the RPC provider. pub async fn get_or_init_provider( &mut self, rpc: &str, is_legacy: bool, - ) -> Result<&ProviderInfo> { + ) -> Result<&ProviderInfo> { Ok(match self.inner.entry(rpc.to_string()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { @@ -28,8 +34,8 @@ impl ProvidersManager { } } -impl Deref for ProvidersManager { - type Target = HashMap; +impl Deref for ProvidersManager { + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.inner @@ -38,8 +44,8 @@ impl Deref for ProvidersManager { /// Holds related metadata to each provider RPC. #[derive(Debug)] -pub struct ProviderInfo { - pub provider: Arc, +pub struct ProviderInfo { + pub provider: Arc>, pub chain: u64, pub gas_price: GasPrice, } @@ -51,9 +57,9 @@ pub enum GasPrice { EIP1559(Result), } -impl ProviderInfo { +impl ProviderInfo { pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { - let provider = Arc::new(get_http_provider(rpc)); + let provider = Arc::new(ProviderBuilder::new(rpc).build()?); let chain = provider.get_chain_id().await?; if let Some(chain) = Chain::from(chain).named() { diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index 605cdf9ddb0de..c066a026e0a0f 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,35 +1,43 @@ -use alloy_chains::Chain; -use alloy_network::AnyTransactionReceipt; -use alloy_primitives::{utils::format_units, TxHash, U256}; -use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; -use eyre::{eyre, Result}; -use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell}; +use alloy_chains::{Chain, NamedChain}; +use alloy_network::{Network, ReceiptResponse}; +use alloy_primitives::{TxHash, U256, utils::format_units}; +use alloy_provider::{ + PendingTransactionBuilder, PendingTransactionError, Provider, RootProvider, WatchTxError, +}; +use eyre::{Result, eyre}; +use forge_script_sequence::ScriptSequence; +use foundry_common::{retry, retry::RetryError, shell}; use std::time::Duration; +/// Marker error type for pending receipts +#[derive(Debug, thiserror::Error)] +#[error( + "Received a pending receipt for {tx_hash}, but transaction is still known to the node, retrying" +)] +pub struct PendingReceiptError { + pub tx_hash: TxHash, +} + /// Convenience enum for internal signalling of transaction status -pub enum TxStatus { +pub enum TxStatus { Dropped, - Success(AnyTransactionReceipt), - Revert(AnyTransactionReceipt), + Success(R), + Revert(R), } -impl From for TxStatus { - fn from(receipt: AnyTransactionReceipt) -> Self { - if !receipt.inner.inner.inner.receipt.status.coerce_status() { - Self::Revert(receipt) - } else { - Self::Success(receipt) - } +impl From for TxStatus { + fn from(receipt: R) -> Self { + if receipt.status() { Self::Success(receipt) } else { Self::Revert(receipt) } } } /// Checks the status of a txhash by first polling for a receipt, then for /// mempool inclusion. Returns the tx hash, and a status -pub async fn check_tx_status( - provider: &RetryProvider, +pub async fn check_tx_status( + provider: &RootProvider, hash: TxHash, timeout: u64, -) -> (TxHash, Result) { +) -> (TxHash, Result, eyre::Report>) { let result = retry::Retry::new_no_delay(3) .run_async_until_break(|| async { match PendingTransactionBuilder::new(provider.clone(), hash) @@ -37,7 +45,30 @@ pub async fn check_tx_status( .get_receipt() .await { - Ok(receipt) => Ok(receipt.into()), + Ok(receipt) => { + // Check if the receipt is pending (missing block information) + let is_pending = receipt.block_number().is_none() + || receipt.block_hash().is_none() + || receipt.transaction_index().is_none(); + + if !is_pending { + return Ok(receipt.into()); + } + + // Receipt is pending, try to sleep and retry a few times + match provider.get_transaction_by_hash(hash).await { + Ok(_) => { + // Sleep for a short time to allow the transaction to be mined + tokio::time::sleep(Duration::from_millis(500)).await; + // Transaction is still known to the node, retry + Err(RetryError::Retry(PendingReceiptError { tx_hash: hash }.into())) + } + Err(_) => { + // Transaction is not known to the node, mark it as dropped + Ok(TxStatus::Dropped) + } + } + } Err(e) => match provider.get_transaction_by_hash(hash).await { Ok(_) => match e { PendingTransactionError::TxWatcher(WatchTxError::Timeout) => { @@ -57,37 +88,70 @@ pub async fn check_tx_status( } /// Prints parts of the receipt to stdout -pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { - let gas_used = receipt.gas_used; - let gas_price = receipt.effective_gas_price; - let block_number = receipt.block_number.unwrap_or_default(); - let success = receipt.inner.inner.inner.receipt.status.coerce_status(); +pub fn format_receipt( + chain: Chain, + receipt: &N::ReceiptResponse, + sequence: Option<&ScriptSequence>, +) -> String { + let gas_used = receipt.gas_used(); + let gas_price = receipt.effective_gas_price(); + let block_number = receipt.block_number().unwrap_or_default(); + let success = receipt.status(); + + let (contract_name, function) = sequence + .and_then(|seq| { + seq.transactions + .iter() + .find(|tx| tx.hash == Some(receipt.transaction_hash())) + .map(|tx| (tx.contract_name.clone(), tx.function.clone())) + }) + .unwrap_or((None, None)); if shell::is_json() { - let _ = sh_println!( - "{}", - serde_json::json!({ - "chain": chain, - "status": if success { - "success" - } else { - "failed" - }, - "tx_hash": receipt.transaction_hash, - "contract_address": receipt.contract_address.map(|addr| addr.to_string()), - "block_number": block_number, - "gas_used": gas_used, - "gas_price": gas_price, - }) - ); + let mut json = serde_json::json!({ + "chain": chain, + "status": if success { + "success" + } else { + "failed" + }, + "tx_hash": receipt.transaction_hash(), + "contract_address": receipt.contract_address().map(|addr| addr.to_string()), + "block_number": block_number, + "gas_used": gas_used, + "gas_price": gas_price, + }); + + if let Some(name) = &contract_name + && !name.is_empty() + { + json["contract_name"] = serde_json::Value::String(name.clone()); + } + if let Some(func) = &function + && !func.is_empty() + { + json["function"] = serde_json::Value::String(func.clone()); + } + + let _ = sh_println!("{}", json); String::new() } else { + let contract_info = match &contract_name { + Some(name) if !name.is_empty() => format!("\nContract: {name}"), + _ => String::new(), + }; + + let function_info = match &function { + Some(func) if !func.is_empty() => format!("\nFunction: {func}"), + _ => String::new(), + }; + format!( - "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n", + "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_info}{function_info}{contract_address}\nBlock: {block_number}\n{gas}\n\n", status = if success { "✅ [Success]" } else { "❌ [Failed]" }, - tx_hash = receipt.transaction_hash, - contract_address = if let Some(addr) = &receipt.contract_address { + tx_hash = receipt.transaction_hash(), + contract_address = if let Some(addr) = receipt.contract_address() { format!("\nContract Address: {}", addr.to_checksum(None)) } else { String::new() @@ -99,12 +163,112 @@ pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { .unwrap_or_else(|_| "N/A".into()); let gas_price = format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + let token_symbol = NamedChain::try_from(chain) + .unwrap_or_default() + .native_currency_symbol() + .unwrap_or("ETH"); format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", + "Paid: {} {} ({gas_used} gas * {} gwei)", paid.trim_end_matches('0'), + token_symbol, gas_price.trim_end_matches('0').trim_end_matches('.') ) }, ) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_network::Ethereum; + use alloy_primitives::B256; + use alloy_rpc_types::TransactionReceipt; + use std::collections::VecDeque; + + fn mock_receipt(tx_hash: B256, success: bool) -> TransactionReceipt { + serde_json::from_value(serde_json::json!({ + "type": "0x02", "status": if success { "0x1" } else { "0x0" }, + "cumulativeGasUsed": "0x5208", "logs": [], "transactionHash": tx_hash, + "logsBloom": format!("0x{}", "0".repeat(512)), + "transactionIndex": "0x0", "blockHash": B256::ZERO, "blockNumber": "0x3039", + "gasUsed": "0x5208", "effectiveGasPrice": "0x4a817c800", + "from": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000000", "contractAddress": null + })) + .unwrap() + } + + fn mock_sequence( + tx_hash: B256, + contract: Option<&str>, + func: Option<&str>, + ) -> ScriptSequence { + let tx = serde_json::from_value(serde_json::json!({ + "hash": tx_hash, "transactionType": "CALL", + "contractName": contract, "contractAddress": null, "function": func, + "arguments": null, "additionalContracts": [], "isFixedGasLimit": false, + "transaction": { + "type": "0x02", "chainId": "0x1", "nonce": "0x0", "gas": "0x5208", + "maxFeePerGas": "0x4a817c800", "maxPriorityFeePerGas": "0x3b9aca00", + "to": "0x0000000000000000000000000000000000000000", + "value": "0x0", "input": "0x", "accessList": [] + }, + })) + .unwrap(); + ScriptSequence { transactions: VecDeque::from([tx]), chain: 1, ..Default::default() } + } + + #[test] + fn format_receipt_displays_contract_and_function() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some("MyContract"), Some("init(address)")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), Some(&seq)); + + assert!(out.contains("Contract: MyContract")); + assert!(out.contains("Function: init(address)")); + assert!(out.contains("✅ [Success]")); + } + + #[test] + fn format_receipt_without_sequence_omits_metadata() { + let hash = B256::repeat_byte(0x42); + let out = format_receipt::(Chain::mainnet(), &mock_receipt(hash, true), None); + + assert!(!out.contains("Contract:")); + assert!(!out.contains("Function:")); + } + + #[test] + fn format_receipt_skips_empty_contract_name() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some(""), Some("transfer(address)")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, true), Some(&seq)); + + assert!(!out.contains("Contract:")); + assert!(out.contains("Function: transfer(address)")); + } + + #[test] + fn format_receipt_handles_missing_tx_in_sequence() { + let seq = mock_sequence(B256::repeat_byte(0x99), Some("Other"), Some("other()")); + let out = format_receipt( + Chain::mainnet(), + &mock_receipt(B256::repeat_byte(0x42), true), + Some(&seq), + ); + + assert!(!out.contains("Contract:")); + assert!(!out.contains("Function:")); + } + + #[test] + fn format_receipt_shows_contract_on_failure() { + let hash = B256::repeat_byte(0x42); + let seq = mock_sequence(hash, Some("FailContract"), Some("fail()")); + let out = format_receipt(Chain::mainnet(), &mock_receipt(hash, false), Some(&seq)); + + assert!(out.contains("❌ [Failed]")); + assert!(out.contains("Contract: FailContract")); + } +} diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index b568cbce28632..b085f8eaf4545 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -1,29 +1,35 @@ use super::{ScriptConfig, ScriptResult}; use crate::build::ScriptPredeployLibraries; use alloy_eips::eip7702::SignedAuthorization; -use alloy_primitives::{Address, Bytes, TxKind, U256}; -use alloy_rpc_types::TransactionRequest; +use alloy_evm::revm::context::Transaction; +use alloy_network::TransactionBuilder; +use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; +use foundry_common::TransactionMaybeSigned; use foundry_config::Config; use foundry_evm::{ constants::CALLER, + core::{ + FoundryTransaction, + evm::{FoundryEvmNetwork, TransactionRequestFor}, + }, executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, opts::EvmOpts, - revm::interpreter::{return_ok, InstructionResult}, + revm::interpreter::{InstructionResult, return_ok}, traces::{TraceKind, Traces}, }; use std::collections::VecDeque; /// Drives script execution #[derive(Debug)] -pub struct ScriptRunner { - pub executor: Executor, +pub struct ScriptRunner { + pub executor: Executor, pub evm_opts: EvmOpts, } -impl ScriptRunner { - pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self { +impl ScriptRunner { + pub const fn new(executor: Executor, evm_opts: EvmOpts) -> Self { Self { executor, evm_opts } } @@ -33,9 +39,9 @@ impl ScriptRunner { libraries: &ScriptPredeployLibraries, code: Bytes, setup: bool, - script_config: &ScriptConfig, + script_config: &ScriptConfig, is_broadcast: bool, - ) -> Result<(Address, ScriptResult)> { + ) -> Result<(Address, ScriptResult)> { trace!(target: "script", "executing setUP()"); if !is_broadcast { @@ -44,7 +50,9 @@ impl ScriptRunner { self.executor.set_balance(self.evm_opts.sender, U256::MAX)?; } - if script_config.evm_opts.fork_url.is_none() { + if script_config.evm_opts.fork_url.is_none() + && !script_config.evm_opts.networks.is_tempo() + { self.executor.deploy_create2_deployer()?; } } @@ -71,15 +79,16 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } + let mut tx_req = TransactionRequestFor::::default() + .with_from(self.evm_opts.sender) + .with_input(code.clone()) + .with_nonce(sender_nonce + library_transactions.len() as u64); + + script_config.tempo.apply::(&mut tx_req, None); + library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionRequest { - from: Some(self.evm_opts.sender), - input: code.clone().into(), - nonce: Some(sender_nonce + library_transactions.len() as u64), - ..Default::default() - } - .into(), + transaction: TransactionMaybeSigned::new(tx_req), }) }), ScriptPredeployLibraries::Create2(libraries, salt) => { @@ -105,16 +114,17 @@ impl ScriptRunner { traces.push((TraceKind::Deployment, deploy_traces)); } + let mut tx_req = TransactionRequestFor::::default() + .with_from(self.evm_opts.sender) + .with_input(calldata) + .with_nonce(sender_nonce + library_transactions.len() as u64) + .with_to(create2_deployer); + + script_config.tempo.apply::(&mut tx_req, None); + library_transactions.push_back(BroadcastableTransaction { rpc: self.evm_opts.fork_url.clone(), - transaction: TransactionRequest { - from: Some(self.evm_opts.sender), - input: calldata.into(), - nonce: Some(sender_nonce + library_transactions.len() as u64), - to: Some(TxKind::Call(create2_deployer)), - ..Default::default() - } - .into(), + transaction: TransactionMaybeSigned::new(tx_req), }); } @@ -165,10 +175,7 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions) = if !setup { - self.executor.backend_mut().set_test_contract(address); - (true, 0, Default::default(), Some(library_transactions)) - } else { + let (success, gas_used, labeled_addresses, transactions) = if setup { match self.executor.setup(Some(self.evm_opts.sender), address, None) { Ok(RawCallResult { reverted, @@ -209,6 +216,9 @@ impl ScriptRunner { } Err(e) => return Err(e.into()), } + } else { + self.executor.backend_mut().set_test_contract(address); + (true, 0, Default::default(), Some(library_transactions)) }; Ok(( @@ -228,7 +238,11 @@ impl ScriptRunner { } /// Executes the method that will collect all broadcastable transactions. - pub fn script(&mut self, address: Address, calldata: Bytes) -> Result { + pub fn script( + &mut self, + address: Address, + calldata: Bytes, + ) -> Result> { self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false) } @@ -240,7 +254,7 @@ impl ScriptRunner { calldata: Option, value: Option, authorization_list: Option>, - ) -> Result { + ) -> Result> { if let Some(to) = to { self.call( from, @@ -250,14 +264,14 @@ impl ScriptRunner { authorization_list, true, ) - } else if to.is_none() { + } else { let res = self.executor.deploy( from, calldata.expect("No data for create transaction"), value.unwrap_or(U256::ZERO), None, ); - let (address, RawCallResult { gas_used, logs, traces, .. }) = match res { + let (address, RawCallResult { gas_used, logs, traces, exit_reason, .. }) = match res { Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { let ExecutionErr { raw, reason } = *err; @@ -276,11 +290,10 @@ impl ScriptRunner { traces: traces .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), + exit_reason, address: Some(address), ..Default::default() }) - } else { - eyre::bail!("ENS not supported."); } } @@ -298,14 +311,14 @@ impl ScriptRunner { value: U256, authorization_list: Option>, commit: bool, - ) -> Result { - let mut res = if let Some(authorization_list) = authorization_list { + ) -> Result> { + let mut res = if let Some(authorization_list) = &authorization_list { self.executor.call_raw_with_authorization( from, to, calldata.clone(), value, - authorization_list, + authorization_list.clone(), )? } else { self.executor.call_raw(from, to, calldata.clone(), value)? @@ -319,10 +332,22 @@ impl ScriptRunner { // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921 if commit { gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?; - res = self.executor.transact_raw(from, to, calldata, value)?; + res = if let Some(authorization_list) = authorization_list { + self.executor.transact_raw_with_authorization( + from, + to, + calldata, + value, + authorization_list, + )? + } else { + self.executor.transact_raw(from, to, calldata, value)? + } } - let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res; + let RawCallResult { + result, reverted, logs, traces, labels, transactions, exit_reason, .. + } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -339,6 +364,7 @@ impl ScriptRunner { .unwrap_or_default(), labeled_addresses: labels, transactions, + exit_reason, address: None, breakpoints, }) @@ -352,28 +378,30 @@ impl ScriptRunner { /// it might be problematic when using `ffi`. fn search_optimal_gas_usage( &mut self, - res: &RawCallResult, + res: &RawCallResult, from: Address, to: Address, calldata: &Bytes, value: U256, ) -> Result { let mut gas_used = res.gas_used; - if matches!(res.exit_reason, return_ok!()) { + if matches!(res.exit_reason, Some(return_ok!())) { // Store the current gas limit and reset it later. - let init_gas_limit = self.executor.env().tx.gas_limit; + let init_gas_limit = self.executor.tx_env().gas_limit(); let mut highest_gas_limit = gas_used * 3; let mut lowest_gas_limit = gas_used; let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env_mut().tx.gas_limit = mid_gas_limit; + self.executor.tx_env_mut().set_gas_limit(mid_gas_limit); let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?; match res.exit_reason { - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFunds => { + Some( + InstructionResult::Revert + | InstructionResult::OutOfGas + | InstructionResult::OutOfFunds, + ) => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -381,9 +409,9 @@ impl ScriptRunner { // if last two successful estimations only vary by 10%, we consider this to // sufficiently accurate const ACCURACY: u64 = 10; - if (last_highest_gas_limit - highest_gas_limit) * ACCURACY / - last_highest_gas_limit < - 1 + if (last_highest_gas_limit - highest_gas_limit) * ACCURACY + / last_highest_gas_limit + < 1 { // update the gas gas_used = highest_gas_limit; @@ -394,7 +422,7 @@ impl ScriptRunner { } } // Reset gas limit in the executor. - self.executor.env_mut().tx.gas_limit = init_gas_limit; + self.executor.tx_env_mut().set_gas_limit(init_gas_limit); } Ok(gas_used) } diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index adb205a87ed83..7b9f21ae71f1e 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,26 +1,35 @@ use crate::multi_sequence::MultiChainSequence; +use alloy_network::Network; use eyre::Result; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cli::utils::Git; -use foundry_common::fmt::UIfmt; +use foundry_common::{FoundryTransactionBuilder, fmt::UIfmt}; use foundry_compilers::ArtifactId; use foundry_config::Config; +use serde::{Deserialize, Serialize}; use std::{ fmt::{Error, Write}, path::Path, }; /// Format transaction details for display -fn format_transaction(index: usize, tx: &TransactionWithMetadata) -> Result { +fn format_transaction( + index: usize, + tx: &TransactionWithMetadata, +) -> Result +where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, +{ let mut output = String::new(); writeln!(output, "### Transaction {index} ###")?; writeln!(output, "{}", tx.tx().pretty())?; // Show contract name and address if available - if !tx.opcode.is_any_create() { - if let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) { - writeln!(output, "contract: {name}({addr})")?; - } + if !tx.call_kind.is_any_create() + && let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) + { + writeln!(output, "contract: {name}({addr})")?; } // Show decoded function if available @@ -45,12 +54,20 @@ pub fn get_commit_hash(root: &Path) -> Option { Git::new(root).commit_hash(true, "HEAD").ok() } -pub enum ScriptSequenceKind { - Single(ScriptSequence), - Multi(MultiChainSequence), +pub enum ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ + Single(ScriptSequence), + Multi(MultiChainSequence), } -impl ScriptSequenceKind { +impl ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { match self { Self::Single(sequence) => sequence.save(silent, save_ts), @@ -58,14 +75,14 @@ impl ScriptSequenceKind { } } - pub fn sequences(&self) -> &[ScriptSequence] { + pub fn sequences(&self) -> &[ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_ref(sequence), Self::Multi(sequence) => &sequence.deployments, } } - pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { match self { Self::Single(sequence) => std::slice::from_mut(sequence), Self::Multi(sequence) => &mut sequence.deployments, @@ -80,19 +97,28 @@ impl ScriptSequenceKind { ) -> Result<()> { match self { Self::Single(sequence) => { - sequence.paths = - Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); + sequence.paths = Some(ScriptSequence::::get_paths( + config, + sig, + target, + sequence.chain, + false, + )?); } Self::Multi(sequence) => { (sequence.path, sequence.sensitive_path) = - MultiChainSequence::get_paths(config, sig, target, false)?; + MultiChainSequence::::get_paths(config, sig, target, false)?; } }; Ok(()) } - pub fn show_transactions(&self) -> Result<()> { + pub fn show_transactions(&self) -> Result<()> + where + N::TxEnvelope: UIfmt, + N::TransactionRequest: FoundryTransactionBuilder, + { for sequence in self.sequences() { if !sequence.transactions.is_empty() { sh_println!("\nChain {}\n", sequence.chain)?; @@ -107,7 +133,11 @@ impl ScriptSequenceKind { } } -impl Drop for ScriptSequenceKind { +impl Drop for ScriptSequenceKind +where + N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, + N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, +{ fn drop(&mut self) { if let Err(err) = self.save(false, true) { error!(?err, "could not save deployment sequence"); diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 372cb4b250381..4ee9c3fb53cb6 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -3,26 +3,32 @@ use super::{ sequence::ScriptSequenceKind, transaction::ScriptTransactionBuilder, }; use crate::{ - broadcast::{estimate_gas, BundledState}, + ScriptArgs, ScriptConfig, ScriptResult, + broadcast::{BundledState, estimate_gas}, build::LinkedBuildData, execute::{ExecutionArtifacts, ExecutionData}, sequence::get_commit_hash, - ScriptArgs, ScriptConfig, ScriptResult, }; use alloy_chains::NamedChain; +use alloy_evm::revm::context::Block; use alloy_network::TransactionBuilder; -use alloy_primitives::{map::HashMap, utils::format_units, Address, Bytes, TxKind}; +use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; -use foundry_common::{shell, ContractData}; -use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; +use foundry_common::{ContractData, shell}; +use foundry_evm::{ + core::{FoundryBlock, evm::FoundryEvmNetwork}, + traces::{decode_trace_arena, render_trace_arena}, +}; +use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ collections::{BTreeMap, VecDeque}, + mem, sync::Arc, }; @@ -31,23 +37,24 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState { +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result { + pub async fn fill_metadata(self) -> Result> { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -59,12 +66,12 @@ impl PreSimulationState { .map(|tx| { let rpc = tx.rpc.expect("missing broadcastable tx rpc url"); let sender = tx.transaction.from().expect("all transactions should have a sender"); - let nonce = tx.transaction.nonce().expect("all transactions should have a sender"); + let nonce = tx.transaction.nonce().expect("all transactions should have a nonce"); let to = tx.transaction.to(); let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if let Some(TxKind::Call(_)) = to { + if let Some(alloy_primitives::TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -88,6 +95,7 @@ impl PreSimulationState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, execution_artifacts: self.execution_artifacts, transactions, @@ -100,8 +108,8 @@ impl PreSimulationState { /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque, - ) -> Result> { + transactions: VecDeque>, + ) -> Result>> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -121,13 +129,13 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; + let to = if let Some(alloy_primitives::TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() .expect("transaction doesn't have a `from` address at execution time"), to, - tx.input().map(Bytes::copy_from_slice), + tx.input().cloned(), tx.value(), tx.authorization_list(), ) @@ -139,7 +147,8 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.env_mut().evm_env.block_env.number += 1; + let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); + runner.executor.evm_env_mut().block_env.set_number(block_number); } let is_noop_tx = if let Some(to) = to { @@ -149,7 +158,11 @@ impl PreSimulationState { }; let transaction = ScriptTransactionBuilder::from(transaction) - .with_execution_result(&result, self.args.gas_estimate_multiplier) + .with_execution_result( + &result, + self.args.gas_estimate_multiplier, + &self.build_data, + ) .build(); eyre::Ok((Some(transaction), is_noop_tx, result.traces)) @@ -181,9 +194,9 @@ impl PreSimulationState { )?; // Only prompt if we're broadcasting and we've not disabled interactivity. - if self.args.should_broadcast() && - !self.args.non_interactive && - !Confirm::new() + if self.args.should_broadcast() + && !self.args.non_interactive + && !Confirm::new() .with_prompt("Do you wish to continue?".to_string()) .interact()? { @@ -222,12 +235,12 @@ impl PreSimulationState { } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result> { + async fn build_runners(&self) -> Result)>> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { let n = rpcs.len(); - let s = if n != 1 { "s" } else { "" }; + let s = if n == 1 { "" } else { "s" }; sh_println!("\n## Setting up {n} EVM{s}.")?; } @@ -235,7 +248,7 @@ impl PreSimulationState { let mut script_config = self.script_config.clone(); script_config.evm_opts.fork_url = Some(rpc.clone()); let runner = script_config.get_runner().await?; - Ok((rpc.clone(), runner)) + Ok((rpc, runner)) }); try_join_all(futs).await } @@ -244,22 +257,23 @@ impl PreSimulationState { /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque, + pub transactions: VecDeque>, } -impl FilledTransactionsState { +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(self) -> Result { + pub async fn bundle(mut self) -> Result> { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -270,15 +284,15 @@ impl FilledTransactionsState { // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::default(); + let mut manager = ProvidersManager::::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a // [`ScriptSequence`] from all the collected transactions up to this point. - let mut txes_iter = self.transactions.clone().into_iter().peekable(); + let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable(); while let Some(mut tx) = txes_iter.next() { - let tx_rpc = tx.rpc.to_owned(); + let tx_rpc = tx.rpc.clone(); let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; if let Some(tx) = tx.tx_mut().as_unsigned_mut() { @@ -293,7 +307,7 @@ impl FilledTransactionsState { // only estimate gas for unsigned transactions if let Some(tx) = tx.as_unsigned_mut() { trace!("estimating with different gas calculation"); - let gas = tx.gas.expect("gas is set by simulation."); + let gas = tx.gas_limit().expect("gas is set by simulation."); // We are trying to show the user an estimation of the total gas usage. // @@ -328,10 +342,10 @@ impl FilledTransactionsState { new_sequence.push_back(tx); // We only create a [`ScriptSequence`] object when we collect all the rpc related // transactions. - if let Some(next_tx) = txes_iter.peek() { - if next_tx.rpc == tx_rpc { - continue; - } + if let Some(next_tx) = txes_iter.peek() + && next_tx.rpc == tx_rpc + { + continue; } let sequence = @@ -370,15 +384,7 @@ impl FilledTransactionsState { .unwrap_or_else(|_| "[Could not calculate]".to_string()); let estimated_amount = estimated_amount_raw.trim_end_matches('0'); - if !shell::is_json() { - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; - sh_println!("\n==========================")?; - } else { + if shell::is_json() { sh_println!( "{}", serde_json::json!({ @@ -389,6 +395,14 @@ impl FilledTransactionsState { "token_symbol": token_symbol, }) )?; + } else { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?; + sh_println!("\n==========================")?; } } } @@ -409,6 +423,7 @@ impl FilledTransactionsState { args: self.args, script_config: self.script_config, script_wallets: self.script_wallets, + browser_wallet: self.browser_wallet, build_data: self.build_data, sequence, }) @@ -419,14 +434,14 @@ impl FilledTransactionsState { &self, multi: bool, chain: u64, - transactions: VecDeque, - ) -> Result { + transactions: VecDeque>, + ) -> Result> { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::get_paths( + Some(ScriptSequence::::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, @@ -454,7 +469,7 @@ impl FilledTransactionsState { receipts: vec![], pending: vec![], paths, - timestamp: now().as_secs(), + timestamp: now().as_millis(), libraries, chain, commit, diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index f72ce9697b13f..2a2bbf3e47bf0 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -1,21 +1,23 @@ use super::ScriptResult; +use crate::build::LinkedBuildData; use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{hex, Address, TxKind, B256}; +use alloy_network::{Network, TransactionBuilder}; +use alloy_primitives::{Address, B256, hex}; use eyre::Result; use forge_script_sequence::TransactionWithMetadata; -use foundry_common::{fmt::format_token_raw, ContractData, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_common::{ContractData, SELECTOR_LEN, TransactionMaybeSigned, fmt::format_token_raw}; use foundry_evm::traces::CallTraceDecoder; use itertools::Itertools; use revm_inspectors::tracing::types::CallKind; use std::collections::BTreeMap; #[derive(Debug)] -pub struct ScriptTransactionBuilder { - transaction: TransactionWithMetadata, +pub struct ScriptTransactionBuilder { + transaction: TransactionWithMetadata, } -impl ScriptTransactionBuilder { - pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { +impl ScriptTransactionBuilder { + pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { let mut transaction = TransactionWithMetadata::from_tx_request(transaction); transaction.rpc = rpc; // If tx.gas is already set that means it was specified in script @@ -31,7 +33,7 @@ impl ScriptTransactionBuilder { decoder: &CallTraceDecoder, create2_deployer: Address, ) -> Result<()> { - if let Some(TxKind::Call(to)) = self.transaction.transaction.to() { + if let Some(to) = self.transaction.transaction.to() { if to == create2_deployer { if let Some(input) = self.transaction.transaction.input() { let (salt, init_code) = input.split_at(32); @@ -43,7 +45,7 @@ impl ScriptTransactionBuilder { )?; } } else { - self.transaction.opcode = CallKind::Call; + self.transaction.call_kind = CallKind::Call; self.transaction.contract_address = Some(to); let Some(data) = self.transaction.transaction.input() else { return Ok(()) }; @@ -95,9 +97,9 @@ impl ScriptTransactionBuilder { contracts: &BTreeMap, ) -> Result<()> { if is_create2 { - self.transaction.opcode = CallKind::Create2; + self.transaction.call_kind = CallKind::Create2; } else { - self.transaction.opcode = CallKind::Create; + self.transaction.call_kind = CallKind::Create; } let info = contracts.get(&address); @@ -111,7 +113,7 @@ impl ScriptTransactionBuilder { // `create2` transactions are prefixed by a 32 byte salt. let creation_code = if is_create2 { if data.len() < 32 { - return Ok(()) + return Ok(()); } &data[32..] } else { @@ -144,10 +146,12 @@ impl ScriptTransactionBuilder { /// Populates additional data from the transaction execution result. pub fn with_execution_result( mut self, - result: &ScriptResult, + result: &ScriptResult, gas_estimate_multiplier: u64, + linked_build_data: &LinkedBuildData, ) -> Self { - let mut created_contracts = result.get_created_contracts(); + let mut created_contracts = + result.get_created_contracts(&linked_build_data.known_contracts); // Add the additional contracts created in this transaction, so we can verify them later. created_contracts.retain(|contract| { @@ -157,23 +161,23 @@ impl ScriptTransactionBuilder { self.transaction.additional_contracts = created_contracts; - if !self.transaction.is_fixed_gas_limit { - if let Some(unsigned) = self.transaction.transaction.as_unsigned_mut() { - // We inflate the gas used by the user specified percentage - unsigned.gas = Some(result.gas_used * gas_estimate_multiplier / 100); - } + if !self.transaction.is_fixed_gas_limit + && let Some(unsigned) = self.transaction.transaction.as_unsigned_mut() + { + // We inflate the gas used by the user specified percentage + unsigned.set_gas_limit(result.gas_used * gas_estimate_multiplier / 100); } self } - pub fn build(self) -> TransactionWithMetadata { + pub fn build(self) -> TransactionWithMetadata { self.transaction } } -impl From for ScriptTransactionBuilder { - fn from(transaction: TransactionWithMetadata) -> Self { +impl From> for ScriptTransactionBuilder { + fn from(transaction: TransactionWithMetadata) -> Self { Self { transaction } } } diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index c6a6bd14dca35..fe1e9345fa23c 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -1,29 +1,31 @@ use crate::{ - build::LinkedBuildData, - sequence::{get_commit_hash, ScriptSequenceKind}, ScriptArgs, ScriptConfig, + build::LinkedBuildData, + sequence::{ScriptSequenceKind, get_commit_hash}, }; -use alloy_primitives::{hex, Address}; -use eyre::{eyre, Result}; +use alloy_network::{Network, ReceiptResponse}; +use alloy_primitives::{Address, hex}; +use eyre::{Result, eyre}; use forge_script_sequence::{AdditionalContract, ScriptSequence}; -use forge_verify::{provider::VerificationProviderType, RetryArgs, VerifierArgs, VerifyArgs}; +use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs, provider::VerificationProviderType}; use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts}; -use foundry_common::ContractsByArtifact; -use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo, Project}; +use foundry_common::{ContractsByArtifact, FoundryReceiptResponse}; +use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo}; use foundry_config::{Chain, Config}; +use foundry_evm::core::evm::FoundryEvmNetwork; use semver::Version; /// State after we have broadcasted the script. /// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all /// broadcasted transactions. -pub struct BroadcastedState { +pub struct BroadcastedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BroadcastedState { +impl BroadcastedState { pub async fn verify(self) -> Result<()> { let Self { args, script_config, build_data, mut sequence, .. } = self; @@ -36,7 +38,7 @@ impl BroadcastedState { ); for sequence in sequence.sequences_mut() { - verify_contracts(sequence, &script_config.config, verify.clone()).await?; + verify_contracts::(sequence, &script_config.config, verify.clone()).await?; } Ok(()) @@ -76,7 +78,7 @@ impl VerifyBundle { cache_path: Some(project.paths.cache.clone()), lib_paths: project.paths.libraries.clone(), hardhat: config.profile == Config::HARDHAT_PROFILE, - config_path: if config_path.exists() { Some(config_path) } else { None }, + config_path: config_path.exists().then_some(config_path), }; let via_ir = config.via_ir; @@ -119,6 +121,7 @@ impl VerifyBundle { if artifact.source.extension().is_some_and(|e| e.to_str() == Some("vy")) { warn!("Skipping verification of Vyper contract: {}", artifact.name); + return None; } // Strip artifact profile from contract name when creating contract info. @@ -126,12 +129,12 @@ impl VerifyBundle { path: Some(artifact.source.to_string_lossy().to_string()), name: artifact .name - .strip_suffix(&format!(".{}", &artifact.profile)) + .strip_suffix(&format!(".{}", artifact.profile)) .unwrap_or_else(|| &artifact.name) .to_string(), }; - // We strip the build metadadata information, since it can lead to + // We strip the build metadata information, since it can lead to // etherscan not identifying it correctly. eg: // `v0.8.10+commit.fc410830.Linux.gcc` != `v0.8.10+commit.fc410830` let version = Version::new( @@ -146,6 +149,8 @@ impl VerifyBundle { compiler_version: Some(version.to_string()), constructor_args: Some(hex::encode(constructor_args)), constructor_args_path: None, + no_auto_detect: false, + use_solc: None, num_of_optimizations: self.num_of_optimizations, etherscan: self.etherscan.clone(), rpc: Default::default(), @@ -161,10 +166,12 @@ impl VerifyBundle { evm_version: Some(evm_version), show_standard_json_input: false, guess_constructor_args: false, - compilation_profile: Some(artifact.profile.to_string()), + compilation_profile: Some(artifact.profile.clone()), + language: None, + creation_transaction_hash: None, }; - return Some(verify) + return Some(verify); } } None @@ -173,8 +180,8 @@ impl VerifyBundle { /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. -async fn verify_contracts( - sequence: &mut ScriptSequence, +async fn verify_contracts( + sequence: &mut ScriptSequence, config: &Config, mut verify: VerifyBundle, ) -> Result<()> { @@ -194,15 +201,17 @@ async fn verify_contracts( for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) { // create2 hash offset - let mut offset = 0; - - if tx.is_create2() { - receipt.contract_address = tx.contract_address; - offset = 32; - } + let offset = if tx.is_create2() + && let Some(contract_address) = tx.contract_address + { + receipt.set_contract_address(contract_address); + 32 + } else { + 0 + }; // Verify contract created directly from the transaction - if let (Some(address), Some(data)) = (receipt.contract_address, tx.tx().input()) { + if let (Some(address), Some(data)) = (receipt.contract_address(), tx.tx().input()) { match verify.get_verify_args( address, offset, @@ -249,7 +258,9 @@ async fn verify_contracts( } if num_of_successful_verifications < num_verifications { - return Err(eyre!("Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!")) + return Err(eyre!( + "Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!" + )); } sh_println!("All ({num_verifications}) contracts were verified!")?; @@ -258,14 +269,16 @@ async fn verify_contracts( Ok(()) } -fn check_unverified( - sequence: &ScriptSequence, +fn check_unverified( + sequence: &ScriptSequence, unverifiable_contracts: Vec
, verify: VerifyBundle, ) { if !unverifiable_contracts.is_empty() { let _ = sh_warn!( - "We haven't found any matching bytecode for the following contracts: {:?}.\n\nThis may occur when resuming a verification, but the underlying source code or compiler version has changed.", + "We haven't found any matching bytecode for the following contracts: {:?}.\n\n\ + This may occur when resuming a verification, but the underlying source code or compiler version has changed.\n\ + Run `forge clean` to make sure builds are in sync with project files, then try again. Alternatively, use `forge verify-contract` to verify contracts that are already deployed.", unverifiable_contracts ); diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index b983dd6a762b8..d3ad56a96cdd2 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -23,8 +23,11 @@ proc-macro2.workspace = true quote.workspace = true syn.workspace = true prettyplease.workspace = true -serde_json.workspace = true eyre.workspace = true heck.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/sol-macro-gen/src/lib.rs b/crates/sol-macro-gen/src/lib.rs index 9984c6cce2b21..2cc229621155e 100644 --- a/crates/sol-macro-gen/src/lib.rs +++ b/crates/sol-macro-gen/src/lib.rs @@ -1,4 +1,5 @@ //! This crate contains the logic for Rust bindings generating from Solidity contracts +#![cfg_attr(not(test), warn(unused_crate_dependencies))] pub mod sol_macro_gen; diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 1e40d51169695..5921f020e2337 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -15,7 +15,6 @@ use eyre::{Context, OptionExt, Result}; use foundry_common::fs; use proc_macro2::{Span, TokenStream}; use std::{ - env::temp_dir, fmt::Write, path::{Path, PathBuf}, str::FromStr, @@ -30,7 +29,7 @@ pub struct SolMacroGen { } impl SolMacroGen { - pub fn new(path: PathBuf, name: String) -> Self { + pub const fn new(path: PathBuf, name: String) -> Self { Self { path, name, expansion: None } } @@ -38,6 +37,7 @@ impl SolMacroGen { let path = self.path.to_string_lossy().into_owned(); let name = proc_macro2::Ident::new(&self.name, Span::call_site()); let tokens = quote::quote! { + #[sol(ignore_unlinked)] #name, #path }; @@ -49,18 +49,17 @@ impl SolMacroGen { } pub struct MultiSolMacroGen { - pub artifacts_path: PathBuf, pub instances: Vec, } impl MultiSolMacroGen { - pub fn new(artifacts_path: &Path, instances: Vec) -> Self { - Self { artifacts_path: artifacts_path.to_path_buf(), instances } + pub const fn new(instances: Vec) -> Self { + Self { instances } } pub fn populate_expansion(&mut self, bindings_path: &Path) -> Result<()> { for instance in &mut self.instances { - let path = bindings_path.join(format!("{}.rs", instance.name.to_lowercase())); + let path = bindings_path.join(format!("{}.rs", instance.name.to_snake_case())); let expansion = fs::read_to_string(path).wrap_err("Failed to read file")?; let tokens = TokenStream::from_str(&expansion) @@ -85,38 +84,7 @@ impl MultiSolMacroGen { } fn generate_binding(instance: &mut SolMacroGen, all_derives: bool) -> Result<()> { - // TODO: in `get_sol_input` we currently can't handle unlinked bytecode: - let input = match instance.get_sol_input() { - Ok(input) => input.normalize_json()?, - Err(error) => { - // TODO(mattsse): remove after - if error.to_string().contains("expected bytecode, found unlinked bytecode") { - // we attempt to do a little hack here until we have this properly supported by - // removing the bytecode objects from the json file and using a tmpfile (very - // hacky) - let content = std::fs::read_to_string(&instance.path)?; - let mut value = serde_json::from_str::(&content)?; - let obj = value.as_object_mut().expect("valid abi"); - - // clear unlinked bytecode - obj.remove("bytecode"); - obj.remove("deployedBytecode"); - - let tmpdir = temp_dir(); - let mut tmp_file = tmpdir.join(instance.path.file_name().unwrap()); - std::fs::write(&tmp_file, serde_json::to_string(&value)?)?; - - // try again - std::mem::swap(&mut tmp_file, &mut instance.path); - let input = instance.get_sol_input()?.normalize_json()?; - std::mem::swap(&mut tmp_file, &mut instance.path); - input.normalize_json()? - } else { - return Err(error) - } - } - }; - + let input = instance.get_sol_input()?.normalize_json()?; let SolInput { attrs: _, path: _, kind } = input; let tokens = match kind { @@ -157,7 +125,7 @@ impl MultiSolMacroGen { self.generate_bindings(all_derives)?; let src = bindings_path.join("src"); - let _ = fs::create_dir_all(&src); + fs::create_dir_all(&src)?; // Write Cargo.toml let cargo_toml_path = bindings_path.join("Cargo.toml"); @@ -196,7 +164,7 @@ edition = "2021" let mut lib_contents = String::new(); write!( &mut lib_contents, - r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + r#"#![allow(unused_imports, unused_attributes, clippy::all, rustdoc::all)] //! This module contains the sol! generated bindings for solidity contracts. //! This is autogenerated code. //! Do not manually edit these files. @@ -257,19 +225,25 @@ edition = "2021" ) -> Result<()> { self.generate_bindings(all_derives)?; - let _ = fs::create_dir_all(bindings_path); + fs::create_dir_all(bindings_path)?; - let mut mod_contents = r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + let mut mod_contents = + r#"#![allow(unused_imports, unused_attributes, clippy::all, rustdoc::all)] //! This module contains the sol! generated bindings for solidity contracts. //! This is autogenerated code. //! Do not manually edit these files. //! These files may be overwritten by the codegen system at any time. "# - .to_string(); + .to_string(); for instance in &self.instances { let name = instance.name.to_snake_case(); - if !single_file { + if single_file { + // Single File + let mut contents = String::new(); + write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; + write!(mod_contents, "{contents}")?; + } else { // Module write_mod_name(&mut mod_contents, &name)?; let mut contents = String::new(); @@ -280,11 +254,6 @@ edition = "2021" let contents = prettyplease::unparse(&file); fs::write(bindings_path.join(format!("{name}.rs")), contents) .wrap_err("Failed to write file")?; - } else { - // Single File - let mut contents = String::new(); - write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; - write!(mod_contents, "{contents}")?; } } @@ -314,14 +283,14 @@ edition = "2021" alloy_version: Option, alloy_rev: Option, ) -> Result<()> { - if check_cargo_toml { + if check_cargo_toml && !is_mod { self.check_cargo_toml(name, version, crate_path, alloy_version, alloy_rev)?; } let mut super_contents = String::new(); write!( &mut super_contents, - r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + r#"#![allow(unused_imports, unused_attributes, clippy::all, rustdoc::all)] //! This module contains the sol! generated bindings for solidity contracts. //! This is autogenerated code. //! Do not manually edit these files. @@ -355,11 +324,7 @@ edition = "2021" } fn check_file_contents(&self, file_path: &Path, expected_contents: &str) -> Result<()> { - eyre::ensure!( - file_path.is_file() && file_path.exists(), - "{} is not a file", - file_path.display() - ); + eyre::ensure!(file_path.is_file(), "{} is not a file", file_path.display()); let file_contents = &fs::read_to_string(file_path).wrap_err("Failed to read file")?; // Format both @@ -395,9 +360,9 @@ edition = "2021" let name_check = format!("name = \"{name}\""); let version_check = format!("version = \"{version}\""); let alloy_dep_check = Self::get_alloy_dep(alloy_version, alloy_rev); - let toml_consistent = cargo_toml_contents.contains(&name_check) && - cargo_toml_contents.contains(&version_check) && - cargo_toml_contents.contains(&alloy_dep_check); + let toml_consistent = cargo_toml_contents.contains(&name_check) + && cargo_toml_contents.contains(&version_check) + && cargo_toml_contents.contains(&alloy_dep_check); eyre::ensure!( toml_consistent, r#"The contents of Cargo.toml do not match the expected output of the latest `sol!` version. @@ -420,13 +385,13 @@ edition = "2021" r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{alloy_rev}", features = ["sol-types", "contract"] }}"#, ) } else { - r#"alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types", "contract"] }"#.to_string() + r#"alloy = { version = "1.0", features = ["sol-types", "contract"] }"#.to_string() } } } fn write_mod_name(contents: &mut String, name: &str) -> Result<()> { - if syn::parse_str::(&format!("pub mod {name};")).is_ok() { + if syn::parse_str::(name).is_ok() { write!(contents, "pub mod {name};")?; } else { write!(contents, "pub mod r#{name};")?; diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 98bfbbc097048..8dc652bcb32bd 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -15,10 +15,12 @@ repository.workspace = true workspace = true [dependencies] +foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util"] } foundry-config.workspace = true +alloy-chains.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true @@ -30,14 +32,19 @@ serde_json.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } rand.workspace = true -snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } +snapbox.workspace = true tempfile.workspace = true -ui_test = "0.29.2" +ui_test = "0.30.4" +reqwest.workspace = true +svm.workspace = true -## Pinned dependencies. See /Cargo.toml. +# Pinned dependencies. See /Cargo.toml. +[target.'cfg(any())'.dependencies] idna_adapter.workspace = true -zip-extract.workspace = true [dev-dependencies] tokio.workspace = true -foundry-block-explorers.workspace = true + +[features] +default = ["optimism"] +optimism = ["foundry-common/optimism"] diff --git a/crates/test-utils/src/etherscan.rs b/crates/test-utils/src/etherscan.rs new file mode 100644 index 0000000000000..7ba33e0bbfbf5 --- /dev/null +++ b/crates/test-utils/src/etherscan.rs @@ -0,0 +1,32 @@ +//! Etherscan utilities for tests. + +use alloy_chains::Chain; +use alloy_primitives::Address; +use eyre::Result; +use foundry_block_explorers::Client; +use foundry_common::{compile::etherscan_project, flatten}; +use std::str::FromStr; + +/// Fetches the source code of a verified contract from Etherscan, flattens it, and returns it. +/// +/// This provides the same functionality as `cast source --flatten` but using the library directly, +/// avoiding the need to shell out to the `cast` binary. +pub async fn fetch_etherscan_source_flattened( + address: &str, + etherscan_api_key: &str, + chain: Chain, +) -> Result { + let client = Client::builder().chain(chain)?.with_api_key(etherscan_api_key).build()?; + + let address = Address::from_str(address)?; + let metadata = client.contract_source_code(address).await?; + let Some(metadata) = metadata.items.first() else { + eyre::bail!("Empty contract source code for {address}") + }; + + let tmp = tempfile::tempdir()?; + let project = etherscan_project(metadata, tmp.path())?; + let target_path = project.find_contract_path(&metadata.contract_name)?; + + flatten(project, &target_path) +} diff --git a/crates/test-utils/src/ext.rs b/crates/test-utils/src/ext.rs new file mode 100644 index 0000000000000..ba4f8421353b3 --- /dev/null +++ b/crates/test-utils/src/ext.rs @@ -0,0 +1,183 @@ +use crate::prj::{TestCommand, TestProject, clone_remote, setup_forge}; +use foundry_compilers::PathStyle; +use std::process::Command; + +/// External test builder +#[derive(Clone, Debug)] +#[must_use = "ExtTester does nothing unless you `run` it"] +pub struct ExtTester { + pub org: &'static str, + pub name: &'static str, + pub rev: &'static str, + pub style: PathStyle, + pub fork_block: Option, + pub args: Vec, + pub envs: Vec<(String, String)>, + pub install_commands: Vec>, + pub verbosity: String, +} + +impl ExtTester { + /// Creates a new external test builder. + pub fn new(org: &'static str, name: &'static str, rev: &'static str) -> Self { + Self { + org, + name, + rev, + style: PathStyle::Dapptools, + fork_block: None, + args: vec![], + envs: vec![], + install_commands: vec![], + verbosity: "-vvv".to_string(), + } + } + + /// Sets the path style. + pub const fn style(mut self, style: PathStyle) -> Self { + self.style = style; + self + } + + /// Sets the fork block. + pub const fn fork_block(mut self, fork_block: u64) -> Self { + self.fork_block = Some(fork_block); + self + } + + /// Adds an argument to the forge command. + pub fn arg(mut self, arg: impl Into) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to the forge command. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.args.extend(args.into_iter().map(Into::into)); + self + } + + /// Sets the verbosity + pub fn verbosity(mut self, verbosity: usize) -> Self { + self.verbosity = format!("-{}", "v".repeat(verbosity)); + self + } + + /// Adds an environment variable to the forge command. + pub fn env(mut self, key: impl Into, value: impl Into) -> Self { + self.envs.push((key.into(), value.into())); + self + } + + /// Adds multiple environment variables to the forge command. + pub fn envs(mut self, envs: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.envs.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } + + /// Adds a command to run after the project is cloned. + /// + /// Note that the command is run in the project's root directory, and it won't fail the test if + /// it fails. + pub fn install_command(mut self, command: &[&str]) -> Self { + self.install_commands.push(command.iter().map(|s| s.to_string()).collect()); + self + } + + pub fn setup_forge_prj(&self, recursive: bool) -> (TestProject, TestCommand) { + let (prj, mut test_cmd) = setup_forge(self.name, self.style.clone()); + + // Export vyper and forge in test command - workaround for snekmate venom tests. + if let Some(vyper) = &prj.inner.project().compiler.vyper { + let vyper_dir = vyper.path.parent().expect("vyper path should have a parent"); + let forge_bin = prj.forge_path(); + let forge_dir = forge_bin.parent().expect("forge path should have a parent"); + + let existing_path = std::env::var_os("PATH").unwrap_or_default(); + let mut new_paths = vec![vyper_dir.to_path_buf(), forge_dir.to_path_buf()]; + new_paths.extend(std::env::split_paths(&existing_path)); + + let joined_path = std::env::join_paths(new_paths).expect("failed to join PATH"); + test_cmd.env("PATH", joined_path); + } + + // Wipe the default structure. + prj.wipe(); + + // Clone the external repository. + let repo_url = format!("https://github.com/{}/{}.git", self.org, self.name); + let root = prj.root().to_str().unwrap(); + clone_remote(&repo_url, root, recursive); + + // Checkout the revision. + if self.rev.is_empty() { + let mut git = Command::new("git"); + git.current_dir(root).args(["log", "-n", "1"]); + test_debug!("$ {git:?}"); + let output = git.output().unwrap(); + assert!(output.status.success(), "git log failed: {output:?}"); + let stdout = String::from_utf8(output.stdout).unwrap(); + let commit = stdout.lines().next().unwrap().split_whitespace().nth(1).unwrap(); + panic!("pin to latest commit: {commit}"); + } else { + let mut git = Command::new("git"); + git.current_dir(root).args(["checkout", self.rev]); + test_debug!("$ {git:?}"); + let status = git.status().unwrap(); + assert!(status.success(), "git checkout failed: {status}") + } + + (prj, test_cmd) + } + + pub fn run_install_commands(&self, root: &str) { + for install_command in &self.install_commands { + let mut install_cmd = Command::new(&install_command[0]); + install_cmd.args(&install_command[1..]).current_dir(root); + test_debug!("cd {root}; {install_cmd:?}"); + match install_cmd.status() { + Ok(s) => { + test_debug!("\n\n{install_cmd:?}: {s}"); + if s.success() { + break; + } + } + Err(e) => { + eprintln!("\n\n{install_cmd:?}: {e}"); + } + } + } + } + + /// Runs the test. + pub fn run(&self) { + let (prj, mut test_cmd) = self.setup_forge_prj(true); + + // Run installation command. + self.run_install_commands(prj.root().to_str().unwrap()); + + // Run the tests. + test_cmd.arg("test"); + test_cmd.args(&self.args); + test_cmd.args(["--fuzz-runs=32", "--ffi", &self.verbosity]); + + test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); + if let Some(fork_block) = self.fork_block { + test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_url()); + test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); + } + test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15"); + test_cmd.env("FOUNDRY_ALLOW_INTERNAL_EXPECT_REVERT", "true"); + + test_cmd.assert_success(); + } +} diff --git a/crates/test-utils/src/fd_lock.rs b/crates/test-utils/src/fd_lock.rs index 1c5a479cd5efd..a131648f325a6 100644 --- a/crates/test-utils/src/fd_lock.rs +++ b/crates/test-utils/src/fd_lock.rs @@ -19,3 +19,9 @@ pub fn new_lock(lock_path: impl AsRef) -> RwLock { } new_lock(lock_path.as_ref()) } + +pub(crate) const LOCK_TOKEN: &[u8] = b"1"; + +pub(crate) fn lock_exists(lock_path: &Path) -> bool { + std::fs::read(lock_path).is_ok_and(|b| b == LOCK_TOKEN) +} diff --git a/crates/test-utils/src/filter.rs b/crates/test-utils/src/filter.rs index 1ba905d27d8c9..b7813bde14376 100644 --- a/crates/test-utils/src/filter.rs +++ b/crates/test-utils/src/filter.rs @@ -73,20 +73,20 @@ impl Filter { } impl TestFilter for Filter { - fn matches_test(&self, test_name: &str) -> bool { - if let Some(exclude) = &self.exclude_tests { - if exclude.is_match(test_name) { - return false; - } + fn matches_test(&self, test_signature: &str) -> bool { + if let Some(exclude) = &self.exclude_tests + && exclude.is_match(test_signature) + { + return false; } - self.test_regex.is_match(test_name) + self.test_regex.is_match(test_signature) } fn matches_contract(&self, contract_name: &str) -> bool { - if let Some(exclude) = &self.exclude_contracts { - if exclude.is_match(contract_name) { - return false; - } + if let Some(exclude) = &self.exclude_contracts + && exclude.is_match(contract_name) + { + return false; } self.contract_regex.is_match(contract_name) @@ -97,10 +97,10 @@ impl TestFilter for Filter { return false; }; - if let Some(exclude) = &self.exclude_paths { - if exclude.is_match(path) { - return false; - } + if let Some(exclude) = &self.exclude_paths + && exclude.is_match(path) + { + return false; } self.path_regex.is_match(path) } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 49c2ccf13bad0..64c68d69a5415 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -3,20 +3,18 @@ //! Internal Foundry testing utilities. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] // Shouldn't use sh_* macros here, as they don't get captured by the test runner. #![allow(clippy::disallowed_macros)] #[macro_use] extern crate tracing; -// See /Cargo.toml. -use idna_adapter as _; -use zip_extract as _; - // Macros useful for testing. +#[macro_use] mod macros; +pub mod etherscan; pub mod rpc; pub mod fd_lock; @@ -24,9 +22,14 @@ pub mod fd_lock; mod filter; pub use filter::Filter; +mod ext; +pub use ext::ExtTester; + +mod prj; +pub use prj::{TestCommand, TestProject}; + // Utilities for making it easier to handle tests. pub mod util; -pub use util::{TestCommand, TestProject}; mod script; pub use script::{ScriptOutcome, ScriptTester}; @@ -40,7 +43,37 @@ pub use snapbox::{self, assert_data_eq, file, str}; /// Initializes tracing for tests. pub fn init_tracing() { - let _ = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); + use std::sync::Once; + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + if std::env::var_os("RUST_BACKTRACE").is_none() { + unsafe { std::env::set_var("RUST_BACKTRACE", "1") }; + } + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(env_filter()) + .with_test_writer() + .try_init(); + let _ = ui_test::color_eyre::install(); + }); +} + +fn env_filter() -> tracing_subscriber::EnvFilter { + const DEFAULT_DIRECTIVES: &[&str] = &include!("../../cli/src/utils/default_directives.txt"); + let mut filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive("foundry_test_utils=debug".parse().unwrap()) + .from_env_lossy(); + for &directive in DEFAULT_DIRECTIVES { + filter = filter.add_directive(directive.parse().unwrap()); + } + filter +} + +pub fn test_debug(args: std::fmt::Arguments<'_>) { + init_tracing(); + debug!("{args}"); +} + +pub fn test_trace(args: std::fmt::Arguments<'_>) { + init_tracing(); + trace!("{args}"); } diff --git a/crates/test-utils/src/macros.rs b/crates/test-utils/src/macros.rs index 42843c3c91c10..49011a76a411b 100644 --- a/crates/test-utils/src/macros.rs +++ b/crates/test-utils/src/macros.rs @@ -4,33 +4,6 @@ /// closure to configure and execute the test. The `TestProject` provides utility functions to setup /// the project's workspace. The `TestCommand` is a wrapper around the actual `forge` executable /// that this then executed with the configured command arguments. -/// -/// # Example -/// -/// run `forge init` -/// -/// ```no_run -/// use foundry_test_utils::*; -/// forgetest!(my_test, |prj, cmd| { -/// // adds `init` to forge's command arguments -/// cmd.arg("init"); -/// // executes forge and panics if the command failed or output is empty -/// cmd.assert_success().stdout_eq(str![[r#""#]]); -/// }); -/// ``` -/// -/// Configure a hardhat project layout by adding a `PathStyle::HardHat` argument -/// -/// ```no_run -/// use foundry_test_utils::*; -/// use foundry_test_utils::foundry_compilers::PathStyle; -/// forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| { -/// prj.assert_create_dirs_exists(); -/// prj.assert_style_paths_exist(PathStyle::HardHat); -/// cmd.arg("clean"); -/// cmd.assert_empty_stdout(); -/// prj.assert_cleaned(); -/// }); #[macro_export] macro_rules! forgetest { ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { @@ -58,7 +31,8 @@ macro_rules! forgetest_async { $(#[$attr])* async fn $test() { let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); - $e + $e; + return (); // Works around weird method resolution in `$e` due to `#[tokio::test]`. } }; } @@ -83,12 +57,13 @@ macro_rules! casttest { $(#[$attr])* async fn $test() { let (mut $prj, mut $cmd) = $crate::util::setup_cast(stringify!($test), $style); - $e + $e; + return (); // Works around weird method resolution in `$e` due to `#[tokio::test]`. } }; } -/// Same as `forgetest` but returns an already initialized project workspace (`forge init`) +/// Same as `forgetest` but returns an already initialized project workspace (`forge init --empty`). #[macro_export] macro_rules! forgetest_init { ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { @@ -122,3 +97,17 @@ macro_rules! forgesoldeer { } }; } + +#[macro_export] +macro_rules! test_debug { + ($($args:tt)*) => { + $crate::test_debug(format_args!($($args)*)) + } +} + +#[macro_export] +macro_rules! test_trace { + ($($args:tt)*) => { + $crate::test_trace(format_args!($($args)*)) + } +} diff --git a/crates/test-utils/src/prj.rs b/crates/test-utils/src/prj.rs new file mode 100644 index 0000000000000..96fa065317bec --- /dev/null +++ b/crates/test-utils/src/prj.rs @@ -0,0 +1,880 @@ +use crate::{init_tracing, rpc::rpc_endpoints}; +use eyre::{Result, WrapErr}; +use foundry_compilers::{ + ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, artifacts::Contract, + cache::CompilerCache, compilers::multi::MultiCompiler, project_util::TempProject, + solc::SolcSettings, +}; +use foundry_config::Config; +use parking_lot::Mutex; +use regex::Regex; +use snapbox::{Data, IntoData, assert_data_eq, cmd::OutputAssert}; +use std::{ + env, + ffi::OsStr, + fs::{self, File}, + io::{BufWriter, Write}, + path::{Path, PathBuf}, + process::{Command, Output, Stdio}, + sync::{ + Arc, LazyLock, + atomic::{AtomicUsize, Ordering}, + }, +}; + +use crate::util::{SOLC_VERSION, copy_dir_filtered, pretty_err}; + +static CURRENT_DIR_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Global test identifier. +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +/// Clones a remote repository into the specified directory. Panics if the command fails. +pub fn clone_remote(repo_url: &str, target_dir: &str, recursive: bool) { + let mut cmd = Command::new("git"); + cmd.args(["clone"]); + if recursive { + cmd.args(["--recursive", "--shallow-submodules"]); + } else { + cmd.args(["--depth=1", "--no-checkout", "--filter=blob:none", "--no-recurse-submodules"]); + } + cmd.args([repo_url, target_dir]); + test_debug!("{cmd:?}"); + let status = cmd.status().unwrap(); + assert!(status.success(), "git clone failed: {status}") +} + +/// Setup an empty test project and return a command pointing to the forge +/// executable whose CWD is set to the project's root. +/// +/// The name given will be used to create the directory. Generally, it should +/// correspond to the test name. +#[track_caller] +pub fn setup_forge(name: &str, style: PathStyle) -> (TestProject, TestCommand) { + setup_forge_project(TestProject::new(name, style)) +} + +pub fn setup_forge_project(test: TestProject) -> (TestProject, TestCommand) { + let cmd = test.forge_command(); + (test, cmd) +} + +/// How to initialize a remote git project +#[derive(Clone, Debug)] +pub struct RemoteProject { + id: String, + run_build: bool, + run_commands: Vec>, + path_style: PathStyle, +} + +impl RemoteProject { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + run_build: true, + run_commands: vec![], + path_style: PathStyle::Dapptools, + } + } + + /// Whether to run `forge build` + pub const fn set_build(mut self, run_build: bool) -> Self { + self.run_build = run_build; + self + } + + /// Configures the project's pathstyle + pub const fn path_style(mut self, path_style: PathStyle) -> Self { + self.path_style = path_style; + self + } + + /// Add another command to run after cloning + pub fn cmd(mut self, cmd: impl IntoIterator>) -> Self { + self.run_commands.push(cmd.into_iter().map(Into::into).collect()); + self + } +} + +impl> From for RemoteProject { + fn from(id: T) -> Self { + Self::new(id) + } +} + +/// Setups a new local forge project by cloning and initializing the `RemoteProject` +/// +/// This will +/// 1. clone the prj, like "transmissions1/solmate" +/// 2. run `forge build`, if configured +/// 3. run additional commands +/// +/// # Panics +/// +/// If anything goes wrong during, checkout, build, or other commands are unsuccessful +pub fn setup_forge_remote(prj: impl Into) -> (TestProject, TestCommand) { + try_setup_forge_remote(prj).unwrap() +} + +/// Same as `setup_forge_remote` but not panicking +pub fn try_setup_forge_remote( + config: impl Into, +) -> Result<(TestProject, TestCommand)> { + let config = config.into(); + let mut tmp = TempProject::checkout(&config.id).wrap_err("failed to checkout project")?; + tmp.project_mut().paths = config.path_style.paths(tmp.root())?; + + let prj = TestProject::with_project(tmp); + if config.run_build { + let mut cmd = prj.forge_command(); + cmd.arg("build").assert_success(); + } + for addon in config.run_commands { + debug_assert!(!addon.is_empty()); + let mut cmd = Command::new(&addon[0]); + if addon.len() > 1 { + cmd.args(&addon[1..]); + } + let status = cmd + .current_dir(prj.root()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .wrap_err_with(|| format!("Failed to execute {addon:?}"))?; + eyre::ensure!(status.success(), "Failed to execute command {:?}", addon); + } + + let cmd = prj.forge_command(); + Ok((prj, cmd)) +} + +pub fn setup_cast(name: &str, style: PathStyle) -> (TestProject, TestCommand) { + setup_cast_project(TestProject::new(name, style)) +} + +pub fn setup_cast_project(test: TestProject) -> (TestProject, TestCommand) { + let cmd = test.cast_command(); + (test, cmd) +} + +/// `TestProject` represents a temporary project to run tests against. +/// +/// Test projects are created from a global atomic counter to avoid duplicates. +#[derive(Clone, Debug)] +pub struct TestProject< + T: ArtifactOutput + Default = ConfigurableArtifacts, +> { + /// The directory in which this test executable is running. + exe_root: PathBuf, + /// The project in which the test should run. + pub(crate) inner: Arc>, +} + +impl TestProject { + /// Create a new test project with the given name. The name + /// does not need to be distinct for each invocation, but should correspond + /// to a logical grouping of tests. + pub fn new(name: &str, style: PathStyle) -> Self { + let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); + let project = pretty_err(name, TempProject::with_style(&format!("{name}-{id}"), style)); + Self::with_project(project) + } + + pub fn with_project(project: TempProject) -> Self { + init_tracing(); + let this = env::current_exe().unwrap(); + let exe_root = canonicalize(this.parent().expect("executable's directory")); + Self { exe_root, inner: Arc::new(project) } + } + + /// Returns the root path of the project's workspace. + pub fn root(&self) -> &Path { + self.inner.root() + } + + /// Returns the paths config. + pub fn paths(&self) -> &ProjectPathsConfig { + self.inner.paths() + } + + /// Returns the path to the project's `foundry.toml` file. + pub fn config(&self) -> PathBuf { + self.root().join(Config::FILE_NAME) + } + + /// Returns the path to the project's cache file. + pub fn cache(&self) -> &PathBuf { + &self.paths().cache + } + + /// Returns the path to the project's artifacts directory. + pub fn artifacts(&self) -> &PathBuf { + &self.paths().artifacts + } + + /// Removes the project's cache and artifacts directory. + pub fn clear(&self) { + self.clear_cache(); + self.clear_artifacts(); + } + + /// Removes this project's cache file. + pub fn clear_cache(&self) { + let _ = fs::remove_file(self.cache()); + } + + /// Removes this project's artifacts directory. + pub fn clear_artifacts(&self) { + let _ = fs::remove_dir_all(self.artifacts()); + } + + /// Removes the entire cache directory (including fuzz, invariant, and test-failures caches). + pub fn clear_cache_dir(&self) { + let _ = fs::remove_dir_all(self.root().join("cache")); + } + + /// Updates the project's config with the given function. + pub fn update_config(&self, f: impl FnOnce(&mut Config)) { + self._update_config(Box::new(f)); + } + + fn _update_config(&self, f: Box) { + let mut config = self + .config() + .exists() + .then_some(()) + .and_then(|()| Config::load_with_root(self.root()).ok()) + .unwrap_or_default(); + config.remappings.clear(); + f(&mut config); + self.write_config(config); + } + + /// Writes the given config as toml to `foundry.toml`. + #[doc(hidden)] // Prefer `update_config`. + pub fn write_config(&self, config: Config) { + let file = self.config(); + pretty_err(&file, fs::write(&file, config.to_string_pretty().unwrap())); + } + + /// Writes [`rpc_endpoints`] to the project's config. + pub fn add_rpc_endpoints(&self) { + self.update_config(|config| { + config.rpc_endpoints = rpc_endpoints(); + }); + } + + /// Adds a source file to the project. + pub fn add_source(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_source(name, Self::add_source_prelude(contents)).unwrap() + } + + /// Adds a source file to the project. Prefer using `add_source` instead. + pub fn add_raw_source(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_source(name, contents).unwrap() + } + + /// Adds a script file to the project. + pub fn add_script(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_script(name, Self::add_source_prelude(contents)).unwrap() + } + + /// Adds a script file to the project. Prefer using `add_script` instead. + pub fn add_raw_script(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_script(name, contents).unwrap() + } + + /// Adds a test file to the project. + pub fn add_test(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_test(name, Self::add_source_prelude(contents)).unwrap() + } + + /// Adds a test file to the project. Prefer using `add_test` instead. + pub fn add_raw_test(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_test(name, contents).unwrap() + } + + /// Adds a library file to the project. + pub fn add_lib(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_lib(name, Self::add_source_prelude(contents)).unwrap() + } + + /// Adds a library file to the project. Prefer using `add_lib` instead. + pub fn add_raw_lib(&self, name: &str, contents: &str) -> PathBuf { + self.inner.add_lib(name, contents).unwrap() + } + + fn add_source_prelude(s: &str) -> String { + let mut s = s.to_string(); + if !s.contains("pragma solidity") { + s = format!("pragma solidity ={SOLC_VERSION};\n{s}"); + } + if !s.contains("// SPDX") { + s = format!("// SPDX-License-Identifier: MIT OR Apache-2.0\n{s}"); + } + s + } + + /// Asserts that the `/foundry.toml` file exists. + #[track_caller] + pub fn assert_config_exists(&self) { + assert!(self.config().exists()); + } + + /// Asserts that the `/cache/sol-files-cache.json` file exists. + #[track_caller] + pub fn assert_cache_exists(&self) { + assert!(self.cache().exists()); + } + + /// Asserts that the `/out` file exists. + #[track_caller] + pub fn assert_artifacts_dir_exists(&self) { + assert!(self.paths().artifacts.exists()); + } + + /// Creates all project dirs and ensure they were created + #[track_caller] + pub fn assert_create_dirs_exists(&self) { + self.paths().create_all().unwrap_or_else(|_| panic!("Failed to create project paths")); + CompilerCache::::default() + .write(&self.paths().cache) + .expect("Failed to create cache"); + self.assert_all_paths_exist(); + } + + /// Ensures that the given layout exists + #[track_caller] + pub fn assert_style_paths_exist(&self, style: PathStyle) { + let paths = style.paths(&self.paths().root).unwrap(); + config_paths_exist(&paths, self.inner.project().cached); + } + + /// Copies the project's root directory to the given target, excluding build artifacts. + #[track_caller] + pub fn copy_to(&self, target: impl AsRef) { + let target = target.as_ref(); + pretty_err(target, fs::create_dir_all(target)); + pretty_err(target, copy_dir_filtered(self.root(), target)); + } + + /// Creates a file with contents `contents` in the test project's directory. The + /// file will be deleted when the project is dropped. + pub fn create_file(&self, path: impl AsRef, contents: &str) -> PathBuf { + let path = path.as_ref(); + assert!(path.is_relative(), "create_file(): file path is absolute"); + let path = self.root().join(path); + if let Some(parent) = path.parent() { + pretty_err(parent, std::fs::create_dir_all(parent)); + } + let file = pretty_err(&path, File::create(&path)); + let mut writer = BufWriter::new(file); + pretty_err(&path, writer.write_all(contents.as_bytes())); + path + } + + /// Adds DSTest as a source under "test.sol" + pub fn insert_ds_test(&self) -> PathBuf { + self.add_source("test.sol", include_str!("../../../testdata/utils/DSTest.sol")) + } + + /// Adds custom test utils under the "test/utils" directory. + pub fn insert_utils(&self) { + self.add_test("utils/DSTest.sol", include_str!("../../../testdata/utils/DSTest.sol")); + self.add_test("utils/Test.sol", include_str!("../../../testdata/utils/Test.sol")); + self.add_test("utils/Vm.sol", include_str!("../../../testdata/utils/Vm.sol")); + self.add_test("utils/console.sol", include_str!("../../../testdata/utils/console.sol")); + } + + /// Adds `console.sol` as a source under "console.sol" + pub fn insert_console(&self) -> PathBuf { + let s = include_str!("../../../testdata/utils/console.sol"); + self.add_source("console.sol", s) + } + + /// Adds `Vm.sol` as a source under "Vm.sol" + pub fn insert_vm(&self) -> PathBuf { + let s = include_str!("../../../testdata/utils/Vm.sol"); + self.add_source("Vm.sol", s) + } + + /// Asserts all project paths exist. These are: + /// - sources + /// - artifacts + /// - libs + /// - cache + pub fn assert_all_paths_exist(&self) { + let paths = self.paths(); + config_paths_exist(paths, self.inner.project().cached); + } + + /// Asserts that the artifacts dir and cache don't exist + pub fn assert_cleaned(&self) { + let paths = self.paths(); + assert!(!paths.cache.exists()); + assert!(!paths.artifacts.exists()); + } + + /// Creates a new command that is set to use the forge executable for this project + #[track_caller] + pub fn forge_command(&self) -> TestCommand { + let cmd = self.forge_bin(); + let _lock = CURRENT_DIR_LOCK.lock(); + TestCommand { + project: self.clone(), + cmd, + current_dir_lock: None, + saved_cwd: pretty_err("", std::env::current_dir()), + stdin: None, + redact_output: true, + } + } + + /// Creates a new command that is set to use the cast executable for this project + pub fn cast_command(&self) -> TestCommand { + let mut cmd = self.cast_bin(); + cmd.current_dir(self.inner.root()); + let _lock = CURRENT_DIR_LOCK.lock(); + TestCommand { + project: self.clone(), + cmd, + current_dir_lock: None, + saved_cwd: pretty_err("", std::env::current_dir()), + stdin: None, + redact_output: true, + } + } + + /// Returns the path to the forge executable. + pub fn forge_bin(&self) -> Command { + let mut cmd = Command::new(self.forge_path()); + cmd.current_dir(self.inner.root()); + // Disable color output for comparisons; can be overridden with `--color always`. + cmd.env("NO_COLOR", "1"); + cmd + } + + pub(crate) fn forge_path(&self) -> PathBuf { + canonicalize(self.exe_root.join(format!("../forge{}", env::consts::EXE_SUFFIX))) + } + + /// Returns the path to the cast executable. + pub fn cast_bin(&self) -> Command { + let cast = canonicalize(self.exe_root.join(format!("../cast{}", env::consts::EXE_SUFFIX))); + let mut cmd = Command::new(cast); + // disable color output for comparisons + cmd.env("NO_COLOR", "1"); + cmd + } + + /// Returns the `Config` as spit out by `forge config` + pub fn config_from_output(&self, args: I) -> Config + where + I: IntoIterator, + A: AsRef, + { + let mut cmd = self.forge_bin(); + cmd.arg("config").arg("--root").arg(self.root()).args(args).arg("--json"); + let output = cmd.output().unwrap(); + let c = lossy_string(&output.stdout); + let config: Config = serde_json::from_str(c.as_ref()).unwrap(); + config.sanitized() + } + + /// Removes all files and dirs inside the project's root dir + pub fn wipe(&self) { + pretty_err(self.root(), fs::remove_dir_all(self.root())); + pretty_err(self.root(), fs::create_dir_all(self.root())); + } + + /// Removes all contract files from `src`, `test`, `script` + pub fn wipe_contracts(&self) { + fn rm_create(path: &Path) { + pretty_err(path, fs::remove_dir_all(path)); + pretty_err(path, fs::create_dir(path)); + } + rm_create(&self.paths().sources); + rm_create(&self.paths().tests); + rm_create(&self.paths().scripts); + } + + /// Initializes the default contracts (Counter.sol, Counter.t.sol, Counter.s.sol). + /// + /// This is useful for tests that need the default contracts created by `forge init`. + /// Most tests should not need this method, as the default behavior is to create an empty + /// project. + pub fn initialize_default_contracts(&self) { + self.add_raw_source( + "Counter.sol", + include_str!("../../forge/assets/solidity/CounterTemplate.sol"), + ); + self.add_raw_test( + "Counter.t.sol", + include_str!("../../forge/assets/solidity/CounterTemplate.t.sol"), + ); + self.add_raw_script( + "Counter.s.sol", + include_str!("../../forge/assets/solidity/CounterTemplate.s.sol"), + ); + } +} + +fn config_paths_exist(paths: &ProjectPathsConfig, cached: bool) { + if cached { + assert!(paths.cache.exists()); + } + assert!(paths.sources.exists()); + assert!(paths.artifacts.exists()); + paths.libraries.iter().for_each(|lib| assert!(lib.exists())); +} + +/// A simple wrapper around a Command with some conveniences. +pub struct TestCommand { + saved_cwd: PathBuf, + /// The project used to launch this command. + project: TestProject, + /// The actual command we use to control the process. + cmd: Command, + // initial: Command, + current_dir_lock: Option>, + stdin: Option>, + /// If true, command output is redacted. + redact_output: bool, +} + +impl TestCommand { + /// Returns a mutable reference to the underlying command. + pub const fn cmd(&mut self) -> &mut Command { + &mut self.cmd + } + + /// Replaces the underlying command. + pub fn set_cmd(&mut self, cmd: Command) -> &mut Self { + self.cmd = cmd; + self + } + + /// Resets the command to the default `forge` command. + pub fn forge_fuse(&mut self) -> &mut Self { + self.set_cmd(self.project.forge_bin()) + } + + /// Resets the command to the default `cast` command. + pub fn cast_fuse(&mut self) -> &mut Self { + self.set_cmd(self.project.cast_bin()) + } + + /// Sets the current working directory. + pub fn set_current_dir(&mut self, p: impl AsRef) { + drop(self.current_dir_lock.take()); + let lock = CURRENT_DIR_LOCK.lock(); + self.current_dir_lock = Some(lock); + let p = p.as_ref(); + pretty_err(p, std::env::set_current_dir(p)); + } + + /// Add an argument to pass to the command. + pub fn arg>(&mut self, arg: A) -> &mut Self { + self.cmd.arg(arg); + self + } + + /// Add any number of arguments to the command. + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + A: AsRef, + { + self.cmd.args(args); + self + } + + /// Set the stdin bytes for the next command. + pub fn stdin(&mut self, stdin: impl Into>) -> &mut Self { + self.stdin = Some(stdin.into()); + self + } + + /// Convenience function to add `--root project.root()` argument + pub fn root_arg(&mut self) -> &mut Self { + let root = self.project.root().to_path_buf(); + self.arg("--root").arg(root) + } + + /// Set the environment variable `k` to value `v` for the command. + pub fn env(&mut self, k: impl AsRef, v: impl AsRef) { + self.cmd.env(k, v); + } + + /// Set the environment variable `k` to value `v` for the command. + pub fn envs(&mut self, envs: I) + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + self.cmd.envs(envs); + } + + /// Unsets the environment variable `k` for the command. + pub fn unset_env(&mut self, k: impl AsRef) { + self.cmd.env_remove(k); + } + + /// Set the working directory for this command. + /// + /// Note that this does not need to be called normally, since the creation + /// of this TestCommand causes its working directory to be set to the + /// test's directory automatically. + pub fn current_dir>(&mut self, dir: P) -> &mut Self { + self.cmd.current_dir(dir); + self + } + + /// Returns the `Config` as spit out by `forge config` + #[track_caller] + pub fn config(&mut self) -> Config { + self.cmd.args(["config", "--json"]); + let output = self.assert().success().get_output().stdout_lossy(); + self.forge_fuse(); + serde_json::from_str(output.as_ref()).unwrap() + } + + /// Runs `git init` inside the project's dir + #[track_caller] + pub fn git_init(&self) { + let mut cmd = Command::new("git"); + cmd.arg("init").current_dir(self.project.root()); + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); + } + + /// Runs `git submodule status` inside the project's dir + #[track_caller] + pub fn git_submodule_status(&self) -> Output { + let mut cmd = Command::new("git"); + cmd.arg("submodule").arg("status").current_dir(self.project.root()); + cmd.output().unwrap() + } + + /// Runs `git add .` inside the project's dir + #[track_caller] + pub fn git_add(&self) { + let mut cmd = Command::new("git"); + cmd.current_dir(self.project.root()); + cmd.arg("add").arg("."); + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); + } + + /// Runs `git commit .` inside the project's dir + #[track_caller] + pub fn git_commit(&self, msg: &str) { + let mut cmd = Command::new("git"); + cmd.current_dir(self.project.root()); + cmd.arg("commit").arg("-m").arg(msg); + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); + } + + /// Runs the command, returning a [`snapbox`] object to assert the command output. + #[track_caller] + pub fn assert_with(&mut self, f: &[RegexRedaction]) -> OutputAssert { + let assert = OutputAssert::new(self.execute()); + if self.redact_output { + let mut redactions = test_redactions(); + insert_redactions(f, &mut redactions); + return assert.with_assert( + snapbox::Assert::new() + .action_env(snapbox::assert::DEFAULT_ACTION_ENV) + .redact_with(redactions), + ); + } + assert + } + + /// Runs the command, returning a [`snapbox`] object to assert the command output. + #[track_caller] + pub fn assert(&mut self) -> OutputAssert { + self.assert_with(&[]) + } + + /// Runs the command and asserts that it resulted in success. + #[track_caller] + pub fn assert_success(&mut self) -> OutputAssert { + self.assert().success() + } + + /// Runs the command and asserts that it resulted in success, with expected JSON data. + #[track_caller] + pub fn assert_json_stdout(&mut self, expected: impl IntoData) { + let expected = expected.is(snapbox::data::DataFormat::Json).unordered(); + let stdout = self.assert_success().get_output().stdout.clone(); + let actual = stdout.into_data().is(snapbox::data::DataFormat::Json).unordered(); + assert_data_eq!(actual, expected); + } + + /// Runs the command and asserts that it resulted in the expected outcome and JSON data. + #[track_caller] + pub fn assert_json_stderr(&mut self, success: bool, expected: impl IntoData) { + let expected = expected.is(snapbox::data::DataFormat::Json).unordered(); + let stderr = if success { self.assert_success() } else { self.assert_failure() } + .get_output() + .stderr + .clone(); + let actual = stderr.into_data().is(snapbox::data::DataFormat::Json).unordered(); + assert_data_eq!(actual, expected); + } + + /// Runs the command and asserts that it **succeeded** nothing was printed to stdout. + #[track_caller] + pub fn assert_empty_stdout(&mut self) { + self.assert_success().stdout_eq(Data::new()); + } + + /// Runs the command and asserts that it failed. + #[track_caller] + pub fn assert_failure(&mut self) -> OutputAssert { + self.assert().failure() + } + + /// Runs the command and asserts that the exit code is `expected`. + #[track_caller] + pub fn assert_code(&mut self, expected: i32) -> OutputAssert { + self.assert().code(expected) + } + + /// Runs the command and asserts that it **failed** nothing was printed to stderr. + #[track_caller] + pub fn assert_empty_stderr(&mut self) { + self.assert_failure().stderr_eq(Data::new()); + } + + /// Runs the command with a temporary file argument and asserts that the contents of the file + /// match the given data. + #[track_caller] + pub fn assert_file(&mut self, data: impl IntoData) { + self.assert_file_with(|this, path| _ = this.arg(path).assert_success(), data); + } + + /// Creates a temporary file, passes it to `f`, then asserts that the contents of the file match + /// the given data. + #[track_caller] + pub fn assert_file_with(&mut self, f: impl FnOnce(&mut Self, &Path), data: impl IntoData) { + let file = tempfile::NamedTempFile::new().expect("couldn't create temporary file"); + f(self, file.path()); + assert_data_eq!(Data::read_from(file.path(), None), data); + } + + /// Does not apply [`snapbox`] redactions to the command output. + pub const fn with_no_redact(&mut self) -> &mut Self { + self.redact_output = false; + self + } + + /// Executes command, applies stdin function and returns output + #[track_caller] + pub fn execute(&mut self) -> Output { + self.try_execute().unwrap() + } + + #[track_caller] + pub fn try_execute(&mut self) -> std::io::Result { + test_debug!("executing {:?}", self.cmd); + let mut child = + self.cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped()).spawn()?; + if let Some(bytes) = self.stdin.take() { + child.stdin.take().unwrap().write_all(&bytes)?; + } + let output = child.wait_with_output()?; + test_debug!("exited with {}", output.status); + test_trace!("\n--- stdout ---\n{}\n--- /stdout ---", output.stdout_lossy()); + test_trace!("\n--- stderr ---\n{}\n--- /stderr ---", output.stderr_lossy()); + Ok(output) + } +} + +impl Drop for TestCommand { + fn drop(&mut self) { + let _lock = self.current_dir_lock.take().unwrap_or_else(|| CURRENT_DIR_LOCK.lock()); + if self.saved_cwd.exists() { + let _ = std::env::set_current_dir(&self.saved_cwd); + } + } +} + +fn test_redactions() -> snapbox::Redactions { + static REDACTIONS: LazyLock = LazyLock::new(|| { + make_redactions(&[ + ("[SOLC_VERSION]", r"Solc( version)? \d+.\d+.\d+"), + ("[ELAPSED]", r"(finished )?in \d+(\.\d+)?\w?s( \(.*?s CPU time\))?"), + ("[GAS]", r"[Gg]as( used)?: \d+"), + ("[GAS_COST]", r"[Gg]as cost\s*\(\d+\)"), + ("[GAS_LIMIT]", r"[Gg]as limit\s*\(\d+\)"), + ("[AVG_GAS]", r"μ: \d+, ~: \d+"), + ("[FILE]", r"(-->|╭▸).*\.sol"), + ("[FILE]", r"Location(.|\n)*\.rs(.|\n)*Backtrace"), + ("[COMPILING_FILES]", r"Compiling \d+ files?"), + ("[TX_HASH]", r"Transaction hash: 0x[0-9A-Fa-f]{64}"), + ("[ADDRESS]", r"Address: +0x[0-9A-Fa-f]{40}"), + ("[PUBLIC_KEY]", r"Public key: +0x[0-9A-Fa-f]{128}"), + ("[PRIVATE_KEY]", r"Private key: +0x[0-9A-Fa-f]{64}"), + ("[UPDATING_DEPENDENCIES]", r"Updating dependencies in .*"), + ("[SAVED_TRANSACTIONS]", r"Transactions saved to: .*\.json"), + ("[SAVED_SENSITIVE_VALUES]", r"Sensitive values saved to: .*\.json"), + ("[ESTIMATED_GAS_PRICE]", r"Estimated gas price:\s*(\d+(\.\d+)?)\s*gwei"), + ("[ESTIMATED_TOTAL_GAS_USED]", r"Estimated total gas used for script: \d+"), + ( + "[ESTIMATED_AMOUNT_REQUIRED]", + r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", + ), + ("[SEED]", r"Fuzz seed: 0x[0-9A-Fa-f]+"), + ]) + }); + REDACTIONS.clone() +} + +/// A tuple of a placeholder and a regex replacement string. +pub type RegexRedaction = (&'static str, &'static str); + +/// Creates a [`snapbox`] redactions object from a list of regex redactions. +fn make_redactions(redactions: &[RegexRedaction]) -> snapbox::Redactions { + let mut r = snapbox::Redactions::new(); + insert_redactions(redactions, &mut r); + r +} + +fn insert_redactions(redactions: &[RegexRedaction], r: &mut snapbox::Redactions) { + for &(placeholder, re) in redactions { + r.insert(placeholder, Regex::new(re).expect(re)).expect(re); + } +} + +/// Extension trait for [`Output`]. +pub trait OutputExt { + /// Returns the stdout as lossy string + fn stdout_lossy(&self) -> String; + + /// Returns the stderr as lossy string + fn stderr_lossy(&self) -> String; +} + +impl OutputExt for Output { + fn stdout_lossy(&self) -> String { + lossy_string(&self.stdout) + } + + fn stderr_lossy(&self) -> String { + lossy_string(&self.stderr) + } +} + +pub fn lossy_string(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).replace("\r\n", "\n") +} + +fn canonicalize(path: impl AsRef) -> PathBuf { + foundry_common::fs::canonicalize_path(path.as_ref()) + .unwrap_or_else(|_| path.as_ref().to_path_buf()) +} diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 8547cc7006b96..ca787105418b3 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -1,49 +1,87 @@ //! RPC API keys utilities. use foundry_config::{ - NamedChain, - NamedChain::{Arbitrum, Base, BinanceSmartChainTestnet, Mainnet, Optimism, Polygon, Sepolia}, + NamedChain::{ + self, Arbitrum, Base, BinanceSmartChainTestnet, Celo, Mainnet, Optimism, Polygon, Sepolia, + }, + RpcEndpointUrl, RpcEndpoints, }; use rand::seq::SliceRandom; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - LazyLock, +use std::{ + env, + sync::{ + LazyLock, + atomic::{AtomicUsize, Ordering}, + }, }; -fn shuffled(mut vec: Vec) -> Vec { - vec.shuffle(&mut rand::rng()); - vec +macro_rules! shuffled_list { + ($name:ident, $e:expr $(,)?) => { + static $name: LazyLock> = + LazyLock::new(|| ShuffledList::new($e)); + }; } -// List of public archive reth nodes to use -static RETH_ARCHIVE_HOSTS: LazyLock> = LazyLock::new(|| { - shuffled(vec![ - // - "reth-ethereum.ithaca.xyz", - ]) -}); +struct ShuffledList { + list: Vec, + index: AtomicUsize, +} + +impl ShuffledList { + fn new(mut list: Vec) -> Self { + assert!(!list.is_empty()); + list.shuffle(&mut rand::rng()); + Self { list, index: AtomicUsize::new(0) } + } + + fn next(&self) -> &T { + let index = self.index.fetch_add(1, Ordering::Relaxed); + &self.list[index % self.list.len()] + } +} -// List of public reth nodes to use (archive and non archive) -static RETH_HOSTS: LazyLock> = LazyLock::new(|| { - shuffled(vec![ +shuffled_list!( + HTTP_ARCHIVE_DOMAINS, + vec![ // - "reth-ethereum.ithaca.xyz", - "reth-ethereum-full.ithaca.xyz", - ]) -}); + "ethereum.reth.rs/rpc", + ], +); +shuffled_list!( + HTTP_DOMAINS, + vec![ + // + "ethereum.reth.rs/rpc", + ], +); +shuffled_list!( + WS_ARCHIVE_DOMAINS, + vec![ + // + "ethereum.reth.rs/ws", + ], +); +shuffled_list!( + WS_DOMAINS, + vec![ + // + "ethereum.reth.rs/ws", + ], +); // List of general purpose DRPC keys to rotate through -static DRPC_KEYS: LazyLock> = LazyLock::new(|| { - shuffled(vec![ - // +shuffled_list!( + DRPC_KEYS, + vec![ "Agc9NK9-6UzYh-vQDDM80Tv0A5UnBkUR8I3qssvAG40d", "AjUPUPonSEInt2CZ_7A-ai3hMyxxBlsR8I4EssvAG40d", - ]) -}); + ], +); // List of etherscan keys. -static ETHERSCAN_KEYS: LazyLock> = LazyLock::new(|| { - shuffled(vec![ +shuffled_list!( + ETHERSCAN_KEYS, + vec![ "MCAUM7WPE9XP5UQMZPCKIBUJHPM1C24FP6", "JW6RWCG2C5QF8TANH4KC7AYIF1CX7RB5D1", "ZSMDY6BI2H55MBE3G9CUUQT4XYUDBB6ZSK", @@ -53,19 +91,27 @@ static ETHERSCAN_KEYS: LazyLock> = LazyLock::new(|| { "C7I2G4JTA5EPYS42Z8IZFEIMQNI5GXIJEV", "A15KZUMZXXCK1P25Y1VP1WGIVBBHIZDS74", "3IA6ASNQXN8WKN7PNFX7T72S9YG56X9FPG", - "ZUB97R31KSYX7NYVW6224Q6EYY6U56H591", + ], +); + +/// the RPC endpoints used during tests +pub fn rpc_endpoints() -> RpcEndpoints { + RpcEndpoints::new([ + ("mainnet", RpcEndpointUrl::Url(next_http_archive_rpc_url())), + ("mainnet2", RpcEndpointUrl::Url(next_http_archive_rpc_url())), + ("sepolia", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Sepolia))), + ("optimism", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Optimism))), + ("base", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Base))), + ("arbitrum", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Arbitrum))), + ("polygon", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Polygon))), + ("bsc", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::BinanceSmartChain))), + ("avaxTestnet", RpcEndpointUrl::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), + ("moonbeam", RpcEndpointUrl::Url("https://moonbeam-rpc.publicnode.com".into())), + ("polkadotTestnet", RpcEndpointUrl::Url("https://eth-rpc-testnet.polkadot.io".into())), + ("kusama", RpcEndpointUrl::Url("https://eth-rpc-kusama.polkadot.io".into())), + ("polkadot", RpcEndpointUrl::Url("https://eth-rpc.polkadot.io".into())), + ("rpcEnvAlias", RpcEndpointUrl::Env("${RPC_ENV_ALIAS}".into())), ]) -}); - -/// Returns the next index to use. -fn next_idx() -> usize { - static NEXT_INDEX: AtomicUsize = AtomicUsize::new(0); - NEXT_INDEX.fetch_add(1, Ordering::SeqCst) -} - -/// Returns the next item in the list to use. -fn next(list: &[T]) -> &T { - &list[next_idx() % list.len()] } /// Returns the next _mainnet_ rpc URL in inline @@ -92,57 +138,41 @@ pub fn next_ws_endpoint(chain: NamedChain) -> String { next_url(true, chain) } -/// Returns a websocket URL that has access to archive state +/// Returns an HTTP URL that has access to archive state pub fn next_http_archive_rpc_url() -> String { next_archive_url(false) } -/// Returns an HTTP URL that has access to archive state +/// Returns a websocket URL that has access to archive state pub fn next_ws_archive_rpc_url() -> String { next_archive_url(true) } /// Returns a URL that has access to archive state. fn next_archive_url(is_ws: bool) -> String { - let urls = archive_urls(is_ws); - let url = next(urls); - eprintln!("--- next_archive_url(is_ws={is_ws}) = {url} ---"); - url.clone() -} - -fn archive_urls(is_ws: bool) -> &'static [String] { - static WS: LazyLock> = LazyLock::new(|| get(true)); - static HTTP: LazyLock> = LazyLock::new(|| get(false)); - - fn get(is_ws: bool) -> Vec { - let mut urls = vec![]; - - for &host in RETH_ARCHIVE_HOSTS.iter() { - if is_ws { - urls.push(format!("wss://{host}/ws")); - } else { - urls.push(format!("https://{host}/rpc")); - } - } - - urls - } - - if is_ws { - &WS - } else { - &HTTP - } + let domain = if is_ws { &WS_ARCHIVE_DOMAINS } else { &HTTP_ARCHIVE_DOMAINS }.next(); + let url = if is_ws { format!("wss://{domain}") } else { format!("https://{domain}") }; + test_debug!("next_archive_url(is_ws={is_ws}) = {}", debug_url(&url)); + url } /// Returns the next etherscan api key. pub fn next_etherscan_api_key() -> String { - let key = next(ÐERSCAN_KEYS).to_string(); - eprintln!("--- next_etherscan_api_key() = {key} ---"); + let mut key = env::var("ETHERSCAN_KEY").unwrap_or_default(); + if key.is_empty() { + key = ETHERSCAN_KEYS.next().to_string(); + } + test_debug!("next_etherscan_api_key() = {}...", &key[..6]); key } fn next_url(is_ws: bool, chain: NamedChain) -> String { + let url = next_url_inner(is_ws, chain); + test_debug!("next_url(is_ws={is_ws}, chain={chain:?}) = {}", debug_url(&url)); + url +} + +fn next_url_inner(is_ws: bool, chain: NamedChain) -> String { if matches!(chain, Base) { return "https://mainnet.base.org".to_string(); } @@ -155,33 +185,52 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String { return "https://bsc-testnet-rpc.publicnode.com".to_string(); } - let domain = if matches!(chain, Mainnet) { - // For Mainnet pick one of Reth nodes. - let idx = next_idx() % RETH_HOSTS.len(); - let host = RETH_HOSTS[idx]; - if is_ws { - format!("{host}/ws") - } else { - format!("{host}/rpc") + if matches!(chain, Celo) { + return "https://celo.drpc.org".to_string(); + } + + if matches!(chain, Sepolia) { + let rpc_url = env::var("ETH_SEPOLIA_RPC").unwrap_or_default(); + if !rpc_url.is_empty() { + return rpc_url; } + } + + if matches!(chain, Arbitrum) { + let rpc_url = env::var("ARBITRUM_RPC").unwrap_or_default(); + if !rpc_url.is_empty() { + return rpc_url; + } + } + + let reth_works = true; + let domain = if reth_works && matches!(chain, Mainnet) { + *(if is_ws { &WS_DOMAINS } else { &HTTP_DOMAINS }).next() } else { // DRPC for other networks used in tests. - let idx = next_idx() % DRPC_KEYS.len(); - let key = DRPC_KEYS[idx]; - + let key = DRPC_KEYS.next(); let network = match chain { - Arbitrum => "arbitrum", + Mainnet => "ethereum", Polygon => "polygon", + Arbitrum => "arbitrum", Sepolia => "sepolia", _ => "", }; - format!("lb.drpc.org/ogrpc?network={network}&dkey={key}") + &format!("lb.drpc.org/ogrpc?network={network}&dkey={key}") }; - let url = if is_ws { format!("wss://{domain}") } else { format!("https://{domain}") }; + if is_ws { format!("wss://{domain}") } else { format!("https://{domain}") } +} - eprintln!("--- next_url(is_ws={is_ws}, chain={chain:?}) = {url} ---"); - url +/// Basic redaction for debugging RPC URLs. +fn debug_url(url: &str) -> impl std::fmt::Display + '_ { + let url = reqwest::Url::parse(url).unwrap(); + format!( + "{scheme}://{host}{path}", + scheme = url.scheme(), + host = url.host_str().unwrap(), + path = url.path().get(..8).unwrap_or(url.path()), + ) } #[cfg(test)] @@ -189,7 +238,6 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String { mod tests { use super::*; use alloy_primitives::address; - use foundry_block_explorers::EtherscanApiVersion; use foundry_config::Chain; #[tokio::test] @@ -198,7 +246,7 @@ mod tests { let address = address!("0xdAC17F958D2ee523a2206206994597C13D831ec7"); let mut first_abi = None; let mut failed = Vec::new(); - for (i, &key) in ETHERSCAN_KEYS.iter().enumerate() { + for (i, &key) in ETHERSCAN_KEYS.list.iter().enumerate() { println!("trying key {i} ({key})"); let client = foundry_block_explorers::Client::builder() @@ -229,36 +277,6 @@ mod tests { first_abi = Some(abi); } } - if !failed.is_empty() { - panic!("failed keys: {failed:#?}"); - } - } - - #[tokio::test] - #[ignore = "run manually"] - async fn test_etherscan_keys_compatibility() { - let address = address!("0x111111125421cA6dc452d289314280a0f8842A65"); - let ehterscan_key = "JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46"; - let client = foundry_block_explorers::Client::builder() - .with_api_key(ehterscan_key) - .chain(Chain::optimism_mainnet()) - .unwrap() - .build() - .unwrap(); - if client.contract_abi(address).await.is_ok() { - panic!("v1 Optimism key should not work with v2 version") - } - - let client = foundry_block_explorers::Client::builder() - .with_api_key(ehterscan_key) - .with_api_version(EtherscanApiVersion::V1) - .chain(Chain::optimism_mainnet()) - .unwrap() - .build() - .unwrap(); - match client.contract_abi(address).await { - Ok(_) => {} - Err(_) => panic!("v1 Optimism key should work with v1 version"), - }; + assert!(failed.is_empty(), "failed keys: {failed:#?}") } } diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index a872d1ecca92a..f0f8ef94ed978 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -1,8 +1,8 @@ -use crate::{init_tracing, util::lossy_string, TestCommand}; -use alloy_primitives::{address, Address}; +use crate::{TestCommand, init_tracing, util::lossy_string}; +use alloy_primitives::{Address, address}; use alloy_provider::Provider; use eyre::Result; -use foundry_common::provider::{get_http_provider, RetryProvider}; +use foundry_common::provider::{RetryProvider, get_http_provider}; use std::{ collections::BTreeMap, fs, @@ -21,17 +21,7 @@ fn init_script_cmd( cmd.forge_fuse(); cmd.set_current_dir(project_root); - cmd.args([ - "script", - "-R", - "ds-test/=lib/", - "-R", - "cheats/=cheats/", - target_contract, - "--root", - project_root.to_str().unwrap(), - "-vvvvv", - ]); + cmd.args(["script", target_contract, "--root", project_root.to_str().unwrap(), "-vvvvv"]); if let Some(rpc_url) = endpoint { cmd.args(["--fork-url", rpc_url]); @@ -125,11 +115,28 @@ impl ScriptTester { } /// Initialises the test contracts by copying them into the workspace - fn copy_testdata(current_dir: &Path) -> Result<()> { + fn copy_testdata(root: &Path) -> Result<()> { let testdata = Self::testdata_path(); - fs::create_dir_all(current_dir.join("cheats"))?; - fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("cheats/Vm.sol"))?; - fs::copy(testdata.join("lib/ds-test/src/test.sol"), current_dir.join("lib/test.sol"))?; + let from_dir = testdata.join("utils"); + let to_dir = root.join("utils"); + fs::create_dir_all(&to_dir)?; + for entry in fs::read_dir(&from_dir)? { + let file = &entry?.path(); + let name = file.file_name().unwrap(); + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Optionally verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(from_dir.canonicalize().unwrap_or_else(|_| from_dir.clone())) { + continue; + } + } + fs::copy(file, to_dir.join(name))?; + } Ok(()) } @@ -163,7 +170,7 @@ impl ScriptTester { /// Adds given address as sender pub fn sender(&mut self, addr: Address) -> &mut Self { - self.args(&["--sender", addr.to_string().as_str()]) + self.args(&["--sender", &addr.to_string()]) } pub fn add_sig(&mut self, contract_name: &str, sig: &str) -> &mut Self { @@ -171,7 +178,7 @@ impl ScriptTester { } pub fn add_create2_deployer(&mut self, create2_deployer: Address) -> &mut Self { - self.args(&["--create2-deployer", create2_deployer.to_string().as_str()]) + self.args(&["--create2-deployer", &create2_deployer.to_string()]) } /// Adds the `--unlocked` flag @@ -229,12 +236,11 @@ impl ScriptTester { trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { - panic!( - "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", - expected.as_str() - ); - } + assert!( + !(!stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str())), + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); self } @@ -282,35 +288,43 @@ pub enum ScriptOutcome { } impl ScriptOutcome { - pub fn as_str(&self) -> &'static str { + pub const fn as_str(&self) -> &'static str { match self { Self::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", Self::OkSimulation => "SIMULATION COMPLETE. To broadcast these", Self::OkBroadcast => "ONCHAIN EXECUTION COMPLETE & SUCCESSFUL", - Self::WarnSpecifyDeployer => "Warning: You have more than one deployer who could predeploy libraries. Using `--sender` instead.", - Self::MissingSender => "You seem to be using Foundry's default sender. Be sure to set your own --sender", + Self::WarnSpecifyDeployer => { + "Warning: You have more than one deployer who could predeploy libraries. Using `--sender` instead." + } + Self::MissingSender => { + "You seem to be using Foundry's default sender. Be sure to set your own --sender" + } Self::MissingWallet => "No associated wallet", - Self::StaticCallNotAllowed => "staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead", + Self::StaticCallNotAllowed => { + "staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead" + } Self::ScriptFailed => "script failed: ", - Self::UnsupportedLibraries => "Multi chain deployment does not support library linking at the moment.", + Self::UnsupportedLibraries => { + "Multi chain deployment does not support library linking at the moment." + } Self::ErrorSelectForkOnBroadcast => "cannot select forks during a broadcast", Self::OkRun => "Script ran successfully", } } - pub fn is_err(&self) -> bool { + pub const fn is_err(&self) -> bool { match self { - Self::OkNoEndpoint | - Self::OkSimulation | - Self::OkBroadcast | - Self::WarnSpecifyDeployer | - Self::OkRun => false, - Self::MissingSender | - Self::MissingWallet | - Self::StaticCallNotAllowed | - Self::UnsupportedLibraries | - Self::ErrorSelectForkOnBroadcast | - Self::ScriptFailed => true, + Self::OkNoEndpoint + | Self::OkSimulation + | Self::OkBroadcast + | Self::WarnSpecifyDeployer + | Self::OkRun => false, + Self::MissingSender + | Self::MissingWallet + | Self::StaticCallNotAllowed + | Self::UnsupportedLibraries + | Self::ErrorSelectForkOnBroadcast + | Self::ScriptFailed => true, } } } diff --git a/crates/test-utils/src/ui_runner.rs b/crates/test-utils/src/ui_runner.rs index 9f6b87a8055ce..c23d4b5ae08c9 100644 --- a/crates/test-utils/src/ui_runner.rs +++ b/crates/test-utils/src/ui_runner.rs @@ -1,7 +1,10 @@ use std::path::Path; -use ui_test::spanned::Spanned; +use ui_test::{ + spanned::Spanned, + status_emitter::{Gha, StatusEmitter}, +}; -/// Test runner based on `ui_test`. Adapted from `https://github.com/paradigmxyz/solar/tools/tester`. +/// Test runner based on `ui_test`. Adapted from `https://github.com/paradigmxyz/solar/blob/main/tools/tester/src/lib.rs`. pub fn run_tests<'a>(cmd: &str, cmd_path: &'a Path, testdata: &'a Path) -> eyre::Result<()> { ui_test::color_eyre::install()?; @@ -24,11 +27,8 @@ pub fn run_tests<'a>(cmd: &str, cmd_path: &'a Path, testdata: &'a Path) -> eyre: let config = config(cmd, cmd_path, &args, testdata); - let text_emitter = match args.format { - ui_test::Format::Terse => ui_test::status_emitter::Text::quiet(), - ui_test::Format::Pretty => ui_test::status_emitter::Text::verbose(), - }; - let gha_emitter = ui_test::status_emitter::Gha:: { name: "Foundry Lint UI".to_string() }; + let text_emitter: Box = args.format.into(); + let gha_emitter = Gha { name: "Foundry Lint UI".to_string(), group: true }; let status_emitter = (text_emitter, gha_emitter); // run tests on all .sol files @@ -63,16 +63,16 @@ fn config<'a>( program: ui_test::CommandBuilder { program: cmd_path.into(), args: { - let args = vec![cmd, "--json"]; + let args = vec![cmd, "--json", "--root", testdata.to_str().expect("invalid root")]; args.into_iter().map(Into::into).collect() }, out_dir_flag: None, input_file_flag: None, - envs: vec![], + envs: vec![("FOUNDRY_LINT_UI_TESTING".into(), Some("1".into()))], cfg_flag: None, }, output_conflict_handling: ui_test::error_on_output_conflict, - bless_command: Some(format!("cargo nextest run {} -- --bless", module_path!())), + bless_command: Some("cargo test -p forge --test ui -- --bless".into()), out_dir: root.join("target").join("ui"), comment_start: "//", diagnostic_extractor: ui_test::diagnostics::rustc::rustc_diagnostics_extractor, @@ -116,7 +116,7 @@ fn config<'a>( } let stdout_filters: &[(&str, &str)] = - &[(&env!("CARGO_PKG_VERSION").replace(".", r"\."), "VERSION")]; + &[(&env!("CARGO_PKG_VERSION").replace('.', r"\."), "VERSION")]; for &(pattern, replacement) in stdout_filters { config.stdout_filter(pattern, replacement); } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 3179503f28f90..fa065425b5281 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,35 +1,39 @@ -use crate::init_tracing; -use eyre::{Result, WrapErr}; -use foundry_compilers::{ - artifacts::Contract, - cache::CompilerCache, - compilers::multi::MultiCompiler, - error::Result as SolcResult, - project_util::{copy_dir, TempProject}, - solc::SolcSettings, - ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, -}; +use foundry_compilers::{Project, ProjectCompileOutput, Vyper, utils::RuntimeOrHandle}; use foundry_config::Config; -use parking_lot::Mutex; -use regex::Regex; -use snapbox::{assert_data_eq, cmd::OutputAssert, Data, IntoData}; use std::{ env, - ffi::OsStr, fs::{self, File}, - io::{BufWriter, IsTerminal, Read, Seek, Write}, + io::{self, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, - process::{ChildStdin, Command, Output, Stdio}, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, LazyLock, - }, + process::Command, + sync::LazyLock, }; -static CURRENT_DIR_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); +/// Base directory under which all test utility filesystem operations are constrained. +/// Using a fixed directory under the system temp dir avoids trusting the current +/// working directory (which may be user-controlled) as a security boundary. +static TEST_UTIL_BASE: LazyLock = LazyLock::new(|| { + // Resolve the system temp directory to an absolute, canonical path where possible. + // If canonicalization fails for any reason, fall back to the raw temp_dir value. + let tmp = env::temp_dir(); + let mut base = tmp + .canonicalize() + .unwrap_or(tmp); + base.push("foundry_test_utils"); + // Ignore errors here; they will surface when the path is actually used. + let _ = fs::create_dir_all(&base); + base +}); + +/// Directories to skip when copying project directories. +/// These are build artifacts and runtime-generated files that should not be copied to temp +/// workspaces. +const SKIP_DIRS: &[&str] = &["out", "cache", "broadcast"]; + +pub use crate::{ext::*, prj::*}; /// The commit of forge-std to use. -const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); +pub const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); /// Stores whether `stdout` is a tty / terminal. pub static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); @@ -44,175 +48,14 @@ static TEMPLATE_PATH: LazyLock = static TEMPLATE_LOCK: LazyLock = LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template.lock")); -/// Global test identifier. -static NEXT_ID: AtomicUsize = AtomicUsize::new(0); - /// The default Solc version used when compiling tests. -pub const SOLC_VERSION: &str = "0.8.27"; +pub const SOLC_VERSION: &str = "0.8.35"; /// Another Solc version used when compiling tests. /// /// Necessary to avoid downloading multiple versions. pub const OTHER_SOLC_VERSION: &str = "0.8.26"; -/// External test builder -#[derive(Clone, Debug)] -#[must_use = "ExtTester does nothing unless you `run` it"] -pub struct ExtTester { - pub org: &'static str, - pub name: &'static str, - pub rev: &'static str, - pub style: PathStyle, - pub fork_block: Option, - pub args: Vec, - pub envs: Vec<(String, String)>, - pub install_commands: Vec>, -} - -impl ExtTester { - /// Creates a new external test builder. - pub fn new(org: &'static str, name: &'static str, rev: &'static str) -> Self { - Self { - org, - name, - rev, - style: PathStyle::Dapptools, - fork_block: None, - args: vec![], - envs: vec![], - install_commands: vec![], - } - } - - /// Sets the path style. - pub fn style(mut self, style: PathStyle) -> Self { - self.style = style; - self - } - - /// Sets the fork block. - pub fn fork_block(mut self, fork_block: u64) -> Self { - self.fork_block = Some(fork_block); - self - } - - /// Adds an argument to the forge command. - pub fn arg(mut self, arg: impl Into) -> Self { - self.args.push(arg.into()); - self - } - - /// Adds multiple arguments to the forge command. - pub fn args(mut self, args: I) -> Self - where - I: IntoIterator, - A: Into, - { - self.args.extend(args.into_iter().map(Into::into)); - self - } - - /// Adds an environment variable to the forge command. - pub fn env(mut self, key: impl Into, value: impl Into) -> Self { - self.envs.push((key.into(), value.into())); - self - } - - /// Adds multiple environment variables to the forge command. - pub fn envs(mut self, envs: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self.envs.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } - - /// Adds a command to run after the project is cloned. - /// - /// Note that the command is run in the project's root directory, and it won't fail the test if - /// it fails. - pub fn install_command(mut self, command: &[&str]) -> Self { - self.install_commands.push(command.iter().map(|s| s.to_string()).collect()); - self - } - - /// Runs the test. - pub fn run(&self) { - // Skip fork tests if the RPC url is not set. - if self.fork_block.is_some() && std::env::var_os("ETH_RPC_URL").is_none() { - eprintln!("ETH_RPC_URL is not set; skipping"); - return; - } - - let (prj, mut test_cmd) = setup_forge(self.name, self.style.clone()); - - // Wipe the default structure. - prj.wipe(); - - // Clone the external repository. - let repo_url = format!("https://github.com/{}/{}.git", self.org, self.name); - let root = prj.root().to_str().unwrap(); - clone_remote(&repo_url, root); - - // Checkout the revision. - if self.rev.is_empty() { - let mut git = Command::new("git"); - git.current_dir(root).args(["log", "-n", "1"]); - println!("$ {git:?}"); - let output = git.output().unwrap(); - if !output.status.success() { - panic!("git log failed: {output:?}"); - } - let stdout = String::from_utf8(output.stdout).unwrap(); - let commit = stdout.lines().next().unwrap().split_whitespace().nth(1).unwrap(); - panic!("pin to latest commit: {commit}"); - } else { - let mut git = Command::new("git"); - git.current_dir(root).args(["checkout", self.rev]); - println!("$ {git:?}"); - let status = git.status().unwrap(); - if !status.success() { - panic!("git checkout failed: {status}"); - } - } - - // Run installation command. - for install_command in &self.install_commands { - let mut install_cmd = Command::new(&install_command[0]); - install_cmd.args(&install_command[1..]).current_dir(root); - println!("cd {root}; {install_cmd:?}"); - match install_cmd.status() { - Ok(s) => { - println!("\n\n{install_cmd:?}: {s}"); - if s.success() { - break; - } - } - Err(e) => { - eprintln!("\n\n{install_cmd:?}: {e}"); - } - } - } - - // Run the tests. - test_cmd.arg("test"); - test_cmd.args(&self.args); - test_cmd.args(["--fuzz-runs=32", "--ffi", "-vvv"]); - - test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); - if let Some(fork_block) = self.fork_block { - test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_url()); - test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); - } - test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15"); - test_cmd.env("FOUNDRY_ALLOW_INTERNAL_EXPECT_REVERT", "true"); - - test_cmd.assert_success(); - } -} - /// Initializes a project with `forge init` at the given path from a template directory. /// /// This should be called after an empty project is created like in @@ -228,17 +71,16 @@ impl ExtTester { /// test can initialize the template at a time. /// /// This sets the project's solc version to the [`SOLC_VERSION`]. -#[expect(clippy::disallowed_macros)] pub fn initialize(target: &Path) { - println!("initializing {}", target.display()); + test_debug!("initializing {}", target.display()); let tpath = TEMPLATE_PATH.as_path(); pretty_err(tpath, fs::create_dir_all(tpath)); // Initialize the global template if necessary. let mut lock = crate::fd_lock::new_lock(TEMPLATE_LOCK.as_path()); - let mut _read = Some(lock.read().unwrap()); - if fs::read(&*TEMPLATE_LOCK).unwrap() != b"1" { + let mut _read = lock.read().unwrap(); + if !crate::fd_lock::lock_exists(TEMPLATE_LOCK.as_path()) { // We are the first to acquire the lock: // - initialize a new empty temp project; // - run `forge init`; @@ -248,19 +90,17 @@ pub fn initialize(target: &Path) { // but `TempProject` does not currently allow this: https://github.com/foundry-rs/compilers/issues/22 // Release the read lock and acquire a write lock, initializing the lock file. - _read = None; - + drop(_read); let mut write = lock.write().unwrap(); - let mut data = String::new(); - write.read_to_string(&mut data).unwrap(); - - if data != "1" { + let mut data = Vec::new(); + write.read_to_end(&mut data).unwrap(); + if data != crate::fd_lock::LOCK_TOKEN { // Initialize and build. let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); - println!("- initializing template dir in {}", prj.root().display()); + test_debug!("- initializing template dir in {}", prj.root().display()); - cmd.args(["init", "--force"]).assert_success(); + cmd.args(["init", "--force", "--empty"]).assert_success(); prj.write_config(Config { solc: Some(foundry_config::SolcReq::Version(SOLC_VERSION.parse().unwrap())), ..Default::default() @@ -280,477 +120,116 @@ pub fn initialize(target: &Path) { // Remove the existing template, if any. let _ = fs::remove_dir_all(tpath); - // Copy the template to the global template path. - pretty_err(tpath, copy_dir(prj.root(), tpath)); + // Copy the template to the global template path, excluding build artifacts. + pretty_err(tpath, copy_dir_filtered(prj.root(), tpath)); // Update lockfile to mark that template is initialized. write.set_len(0).unwrap(); write.seek(std::io::SeekFrom::Start(0)).unwrap(); - write.write_all(b"1").unwrap(); + write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap(); } // Release the write lock and acquire a new read lock. drop(write); - _read = Some(lock.read().unwrap()); + _read = lock.read().unwrap(); } - println!("- copying template dir from {}", tpath.display()); + test_debug!("- copying template dir from {}", tpath.display()); pretty_err(target, fs::create_dir_all(target)); - pretty_err(target, copy_dir(tpath, target)); + pretty_err(target, copy_dir_filtered(tpath, target)); } -/// Clones a remote repository into the specified directory. Panics if the command fails. -pub fn clone_remote(repo_url: &str, target_dir: &str) { - let mut cmd = Command::new("git"); - cmd.args(["clone", "--no-tags", "--recursive", "--shallow-submodules"]); - cmd.args([repo_url, target_dir]); - println!("{cmd:?}"); - let status = cmd.status().unwrap(); - if !status.success() { - panic!("git clone failed: {status}"); - } - println!(); -} - -/// Setup an empty test project and return a command pointing to the forge -/// executable whose CWD is set to the project's root. -/// -/// The name given will be used to create the directory. Generally, it should -/// correspond to the test name. -#[track_caller] -pub fn setup_forge(name: &str, style: PathStyle) -> (TestProject, TestCommand) { - setup_forge_project(TestProject::new(name, style)) -} - -pub fn setup_forge_project(test: TestProject) -> (TestProject, TestCommand) { - let cmd = test.forge_command(); - (test, cmd) -} - -/// How to initialize a remote git project -#[derive(Clone, Debug)] -pub struct RemoteProject { - id: String, - run_build: bool, - run_commands: Vec>, - path_style: PathStyle, -} - -impl RemoteProject { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - run_build: true, - run_commands: vec![], - path_style: PathStyle::Dapptools, - } - } +/// Compile the project with a lock for the cache. +pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { + let lock_file_path = project.sources_path().join(".lock"); + // We need to use a file lock because `cargo-nextest` runs tests in different processes. + // This is similar to `initialize`, see its comments for more details. + let mut lock = crate::fd_lock::new_lock(&lock_file_path); + let read = lock.read().unwrap(); + let out; - /// Whether to run `forge build` - pub fn set_build(mut self, run_build: bool) -> Self { - self.run_build = run_build; - self + let mut write = None; + if !project.cache_path().exists() || !crate::fd_lock::lock_exists(&lock_file_path) { + drop(read); + write = Some(lock.write().unwrap()); + test_debug!("cache miss for {}", lock_file_path.display()); + } else { + test_debug!("cache hit for {}", lock_file_path.display()); } - /// Configures the project's pathstyle - pub fn path_style(mut self, path_style: PathStyle) -> Self { - self.path_style = path_style; - self + if project.compiler.vyper.is_none() { + project.compiler.vyper = Some(get_vyper()); } - /// Add another command to run after cloning - pub fn cmd(mut self, cmd: impl IntoIterator>) -> Self { - self.run_commands.push(cmd.into_iter().map(Into::into).collect()); - self - } -} + test_debug!("compiling {}", lock_file_path.display()); + out = project.compile().unwrap(); + test_debug!("compiled {}", lock_file_path.display()); -impl> From for RemoteProject { - fn from(id: T) -> Self { - Self::new(id) + if out.has_compiler_errors() { + panic!("Compiled with errors:\n{out}"); } -} - -/// Setups a new local forge project by cloning and initializing the `RemoteProject` -/// -/// This will -/// 1. clone the prj, like "transmissions1/solmate" -/// 2. run `forge build`, if configured -/// 3. run additional commands -/// -/// # Panics -/// -/// If anything goes wrong during, checkout, build, or other commands are unsuccessful -pub fn setup_forge_remote(prj: impl Into) -> (TestProject, TestCommand) { - try_setup_forge_remote(prj).unwrap() -} - -/// Same as `setup_forge_remote` but not panicking -pub fn try_setup_forge_remote( - config: impl Into, -) -> Result<(TestProject, TestCommand)> { - let config = config.into(); - let mut tmp = TempProject::checkout(&config.id).wrap_err("failed to checkout project")?; - tmp.project_mut().paths = config.path_style.paths(tmp.root())?; - let prj = TestProject::with_project(tmp); - if config.run_build { - let mut cmd = prj.forge_command(); - cmd.arg("build").assert_success(); - } - for addon in config.run_commands { - debug_assert!(!addon.is_empty()); - let mut cmd = Command::new(&addon[0]); - if addon.len() > 1 { - cmd.args(&addon[1..]); - } - let status = cmd - .current_dir(prj.root()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .wrap_err_with(|| format!("Failed to execute {addon:?}"))?; - eyre::ensure!(status.success(), "Failed to execute command {:?}", addon); + if let Some(write) = &mut write { + write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap(); } - let cmd = prj.forge_command(); - Ok((prj, cmd)) -} - -pub fn setup_cast(name: &str, style: PathStyle) -> (TestProject, TestCommand) { - setup_cast_project(TestProject::new(name, style)) -} - -pub fn setup_cast_project(test: TestProject) -> (TestProject, TestCommand) { - let cmd = test.cast_command(); - (test, cmd) -} - -/// `TestProject` represents a temporary project to run tests against. -/// -/// Test projects are created from a global atomic counter to avoid duplicates. -#[derive(Clone, Debug)] -pub struct TestProject< - T: ArtifactOutput + Default = ConfigurableArtifacts, -> { - /// The directory in which this test executable is running. - exe_root: PathBuf, - /// The project in which the test should run. - inner: Arc>, + out } -impl TestProject { - /// Create a new test project with the given name. The name - /// does not need to be distinct for each invocation, but should correspond - /// to a logical grouping of tests. - pub fn new(name: &str, style: PathStyle) -> Self { - let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); - let project = pretty_err(name, TempProject::with_style(&format!("{name}-{id}"), style)); - Self::with_project(project) - } - - pub fn with_project(project: TempProject) -> Self { - init_tracing(); - let this = env::current_exe().unwrap(); - let exe_root = this.parent().expect("executable's directory").to_path_buf(); - Self { exe_root, inner: Arc::new(project) } - } - - /// Returns the root path of the project's workspace. - pub fn root(&self) -> &Path { - self.inner.root() - } - - /// Returns the paths config. - pub fn paths(&self) -> &ProjectPathsConfig { - self.inner.paths() - } +/// Installs Vyper if it's not already present. +pub fn get_vyper() -> Vyper { + static VYPER: LazyLock = LazyLock::new(|| std::env::temp_dir().join("vyper")); - /// Returns the path to the project's `foundry.toml` file. - pub fn config(&self) -> PathBuf { - self.root().join(Config::FILE_NAME) + if let Ok(vyper) = Vyper::new("vyper") { + return vyper; } - - /// Returns the path to the project's cache file. - pub fn cache(&self) -> &PathBuf { - &self.paths().cache - } - - /// Returns the path to the project's artifacts directory. - pub fn artifacts(&self) -> &PathBuf { - &self.paths().artifacts + if let Ok(vyper) = Vyper::new(&*VYPER) { + return vyper; } + return RuntimeOrHandle::new().block_on(install()); - /// Removes the project's cache and artifacts directory. - pub fn clear(&self) { - self.clear_cache(); - self.clear_artifacts(); - } - - /// Removes this project's cache file. - pub fn clear_cache(&self) { - let _ = fs::remove_file(self.cache()); - } - - /// Removes this project's artifacts directory. - pub fn clear_artifacts(&self) { - let _ = fs::remove_dir_all(self.artifacts()); - } - - /// Updates the project's config with the given function. - pub fn update_config(&self, f: impl FnOnce(&mut Config)) { - self._update_config(Box::new(f)); - } - - fn _update_config(&self, f: Box) { - let mut config = self - .config() - .exists() - .then_some(()) - .and_then(|()| Config::load_with_root(self.root()).ok()) - .unwrap_or_default(); - config.remappings.clear(); - f(&mut config); - self.write_config(config); - } - - /// Writes the given config as toml to `foundry.toml`. - #[doc(hidden)] // Prefer `update_config`. - pub fn write_config(&self, config: Config) { - let file = self.config(); - pretty_err(&file, fs::write(&file, config.to_string_pretty().unwrap())); - } - - /// Adds a source file to the project. - pub fn add_source(&self, name: &str, contents: &str) -> SolcResult { - self.inner.add_source(name, Self::add_source_prelude(contents)) - } - - /// Adds a source file to the project. Prefer using `add_source` instead. - pub fn add_raw_source(&self, name: &str, contents: &str) -> SolcResult { - self.inner.add_source(name, contents) - } - - /// Adds a script file to the project. - pub fn add_script(&self, name: &str, contents: &str) -> SolcResult { - self.inner.add_script(name, Self::add_source_prelude(contents)) - } - - /// Adds a test file to the project. - pub fn add_test(&self, name: &str, contents: &str) -> SolcResult { - self.inner.add_test(name, Self::add_source_prelude(contents)) - } - - /// Adds a library file to the project. - pub fn add_lib(&self, name: &str, contents: &str) -> SolcResult { - self.inner.add_lib(name, Self::add_source_prelude(contents)) - } - - fn add_source_prelude(s: &str) -> String { - let mut s = s.to_string(); - if !s.contains("pragma solidity") { - s = format!("pragma solidity ={SOLC_VERSION};\n{s}"); - } - if !s.contains("// SPDX") { - s = format!("// SPDX-License-Identifier: MIT OR Apache-2.0\n{s}"); - } - s - } - - /// Asserts that the `/foundry.toml` file exists. - #[track_caller] - pub fn assert_config_exists(&self) { - assert!(self.config().exists()); - } + async fn install() -> Vyper { + #[cfg(target_family = "unix")] + use std::{fs::Permissions, os::unix::fs::PermissionsExt}; - /// Asserts that the `/cache/sol-files-cache.json` file exists. - #[track_caller] - pub fn assert_cache_exists(&self) { - assert!(self.cache().exists()); - } - - /// Asserts that the `/out` file exists. - #[track_caller] - pub fn assert_artifacts_dir_exists(&self) { - assert!(self.paths().artifacts.exists()); - } - - /// Creates all project dirs and ensure they were created - #[track_caller] - pub fn assert_create_dirs_exists(&self) { - self.paths().create_all().unwrap_or_else(|_| panic!("Failed to create project paths")); - CompilerCache::::default() - .write(&self.paths().cache) - .expect("Failed to create cache"); - self.assert_all_paths_exist(); - } - - /// Ensures that the given layout exists - #[track_caller] - pub fn assert_style_paths_exist(&self, style: PathStyle) { - let paths = style.paths(&self.paths().root).unwrap(); - config_paths_exist(&paths, self.inner.project().cached); - } - - /// Copies the project's root directory to the given target - #[track_caller] - pub fn copy_to(&self, target: impl AsRef) { - let target = target.as_ref(); - pretty_err(target, fs::create_dir_all(target)); - pretty_err(target, copy_dir(self.root(), target)); - } - - /// Creates a file with contents `contents` in the test project's directory. The - /// file will be deleted when the project is dropped. - pub fn create_file(&self, path: impl AsRef, contents: &str) -> PathBuf { - let path = path.as_ref(); - if !path.is_relative() { - panic!("create_file(): file path is absolute"); - } - let path = self.root().join(path); - if let Some(parent) = path.parent() { - pretty_err(parent, std::fs::create_dir_all(parent)); - } - let file = pretty_err(&path, File::create(&path)); - let mut writer = BufWriter::new(file); - pretty_err(&path, writer.write_all(contents.as_bytes())); - path - } - - /// Adds DSTest as a source under "test.sol" - pub fn insert_ds_test(&self) -> PathBuf { - let s = include_str!("../../../testdata/lib/ds-test/src/test.sol"); - self.add_source("test.sol", s).unwrap() - } - - /// Adds `console.sol` as a source under "console.sol" - pub fn insert_console(&self) -> PathBuf { - let s = include_str!("../../../testdata/default/logs/console.sol"); - self.add_source("console.sol", s).unwrap() - } - - /// Adds `Vm.sol` as a source under "Vm.sol" - pub fn insert_vm(&self) -> PathBuf { - let s = include_str!("../../../testdata/cheats/Vm.sol"); - self.add_source("Vm.sol", s).unwrap() - } - - /// Asserts all project paths exist. These are: - /// - sources - /// - artifacts - /// - libs - /// - cache - pub fn assert_all_paths_exist(&self) { - let paths = self.paths(); - config_paths_exist(paths, self.inner.project().cached); - } - - /// Asserts that the artifacts dir and cache don't exist - pub fn assert_cleaned(&self) { - let paths = self.paths(); - assert!(!paths.cache.exists()); - assert!(!paths.artifacts.exists()); - } - - /// Creates a new command that is set to use the forge executable for this project - #[track_caller] - pub fn forge_command(&self) -> TestCommand { - let cmd = self.forge_bin(); - let _lock = CURRENT_DIR_LOCK.lock(); - TestCommand { - project: self.clone(), - cmd, - current_dir_lock: None, - saved_cwd: pretty_err("", std::env::current_dir()), - stdin_fun: None, - redact_output: true, + let path = VYPER.as_path(); + let mut file = File::create(path).unwrap(); + if let Err(e) = file.try_lock() { + if let fs::TryLockError::WouldBlock = e { + file.lock().unwrap(); + assert!(path.exists()); + return Vyper::new(path).unwrap(); + } + file.lock().unwrap(); } - } - /// Creates a new command that is set to use the cast executable for this project - pub fn cast_command(&self) -> TestCommand { - let mut cmd = self.cast_bin(); - cmd.current_dir(self.inner.root()); - let _lock = CURRENT_DIR_LOCK.lock(); - TestCommand { - project: self.clone(), - cmd, - current_dir_lock: None, - saved_cwd: pretty_err("", std::env::current_dir()), - stdin_fun: None, - redact_output: true, - } - } + let suffix = match svm::platform() { + svm::Platform::MacOsAarch64 => "darwin", + svm::Platform::LinuxAmd64 => "linux", + svm::Platform::WindowsAmd64 => "windows.exe", + platform => panic!( + "unsupported platform {platform:?} for installing vyper, \ + install it manually and add it to $PATH" + ), + }; + let url = format!( + "https://github.com/vyperlang/vyper/releases/download/v0.4.3/vyper.0.4.3+commit.bff19ea2.{suffix}" + ); - /// Returns the path to the forge executable. - pub fn forge_bin(&self) -> Command { - let forge = self.exe_root.join(format!("../forge{}", env::consts::EXE_SUFFIX)); - let forge = forge.canonicalize().unwrap_or_else(|_| forge.clone()); - let mut cmd = Command::new(forge); - cmd.current_dir(self.inner.root()); - // Disable color output for comparisons; can be overridden with `--color always`. - cmd.env("NO_COLOR", "1"); - cmd - } + test_debug!("downloading vyper from {url}"); + let res = reqwest::Client::builder().build().unwrap().get(url).send().await.unwrap(); - /// Returns the path to the cast executable. - pub fn cast_bin(&self) -> Command { - let cast = self.exe_root.join(format!("../cast{}", env::consts::EXE_SUFFIX)); - let cast = cast.canonicalize().unwrap_or_else(|_| cast.clone()); - let mut cmd = Command::new(cast); - // disable color output for comparisons - cmd.env("NO_COLOR", "1"); - cmd - } + assert!(res.status().is_success()); - /// Returns the `Config` as spit out by `forge config` - pub fn config_from_output(&self, args: I) -> Config - where - I: IntoIterator, - A: AsRef, - { - let mut cmd = self.forge_bin(); - cmd.arg("config").arg("--root").arg(self.root()).args(args).arg("--json"); - let output = cmd.output().unwrap(); - let c = lossy_string(&output.stdout); - let config: Config = serde_json::from_str(c.as_ref()).unwrap(); - config.sanitized() - } + let bytes = res.bytes().await.unwrap(); - /// Removes all files and dirs inside the project's root dir - pub fn wipe(&self) { - pretty_err(self.root(), fs::remove_dir_all(self.root())); - pretty_err(self.root(), fs::create_dir_all(self.root())); - } - - /// Removes all contract files from `src`, `test`, `script` - pub fn wipe_contracts(&self) { - fn rm_create(path: &Path) { - pretty_err(path, fs::remove_dir_all(path)); - pretty_err(path, fs::create_dir(path)); - } - rm_create(&self.paths().sources); - rm_create(&self.paths().tests); - rm_create(&self.paths().scripts); - } -} + file.write_all(&bytes).unwrap(); -impl Drop for TestCommand { - fn drop(&mut self) { - let _lock = self.current_dir_lock.take().unwrap_or_else(|| CURRENT_DIR_LOCK.lock()); - if self.saved_cwd.exists() { - let _ = std::env::set_current_dir(&self.saved_cwd); - } - } -} + #[cfg(target_family = "unix")] + file.set_permissions(Permissions::from_mode(0o755)).unwrap(); -fn config_paths_exist(paths: &ProjectPathsConfig, cached: bool) { - if cached { - assert!(paths.cache.exists()); + Vyper::new(path).unwrap() } - assert!(paths.sources.exists()); - assert!(paths.artifacts.exists()); - paths.libraries.iter().for_each(|lib| assert!(lib.exists())); } #[track_caller] @@ -766,286 +245,91 @@ pub fn read_string(path: impl AsRef) -> String { pretty_err(path, std::fs::read_to_string(path)) } -/// A simple wrapper around a Command with some conveniences. -pub struct TestCommand { - saved_cwd: PathBuf, - /// The project used to launch this command. - project: TestProject, - /// The actual command we use to control the process. - cmd: Command, - // initial: Command, - current_dir_lock: Option>, - stdin_fun: Option>, - /// If true, command output is redacted. - redact_output: bool, +/// Copies the directory at `src` to `dst`, skipping build artifact directories. +/// +/// This is similar to `foundry_compilers::project_util::copy_dir`, but skips directories +/// like `out/`, `cache/`, and `broadcast/` which are build artifacts that should not be +/// copied to temporary test workspaces. +pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + fs::create_dir_all(&dst)?; + copy_dir_filtered_inner(&src, &dst, true) } -impl TestCommand { - /// Returns a mutable reference to the underlying command. - pub fn cmd(&mut self) -> &mut Command { - &mut self.cmd - } - - /// Replaces the underlying command. - pub fn set_cmd(&mut self, cmd: Command) -> &mut Self { - self.cmd = cmd; - self - } - - /// Resets the command to the default `forge` command. - pub fn forge_fuse(&mut self) -> &mut Self { - self.set_cmd(self.project.forge_bin()) - } - - /// Resets the command to the default `cast` command. - pub fn cast_fuse(&mut self) -> &mut Self { - self.set_cmd(self.project.cast_bin()) - } - - /// Sets the current working directory. - pub fn set_current_dir(&mut self, p: impl AsRef) { - drop(self.current_dir_lock.take()); - let lock = CURRENT_DIR_LOCK.lock(); - self.current_dir_lock = Some(lock); - let p = p.as_ref(); - pretty_err(p, std::env::set_current_dir(p)); - } - - /// Add an argument to pass to the command. - pub fn arg>(&mut self, arg: A) -> &mut Self { - self.cmd.arg(arg); - self - } - - /// Add any number of arguments to the command. - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - A: AsRef, - { - self.cmd.args(args); - self - } - - pub fn stdin(&mut self, fun: impl FnOnce(ChildStdin) + 'static) -> &mut Self { - self.stdin_fun = Some(Box::new(fun)); - self - } - - /// Convenience function to add `--root project.root()` argument - pub fn root_arg(&mut self) -> &mut Self { - let root = self.project.root().to_path_buf(); - self.arg("--root").arg(root) - } - - /// Set the environment variable `k` to value `v` for the command. - pub fn env(&mut self, k: impl AsRef, v: impl AsRef) { - self.cmd.env(k, v); - } - - /// Set the environment variable `k` to value `v` for the command. - pub fn envs(&mut self, envs: I) - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - self.cmd.envs(envs); - } - - /// Unsets the environment variable `k` for the command. - pub fn unset_env(&mut self, k: impl AsRef) { - self.cmd.env_remove(k); - } - - /// Set the working directory for this command. - /// - /// Note that this does not need to be called normally, since the creation - /// of this TestCommand causes its working directory to be set to the - /// test's directory automatically. - pub fn current_dir>(&mut self, dir: P) -> &mut Self { - self.cmd.current_dir(dir); - self - } - - /// Returns the `Config` as spit out by `forge config` - #[track_caller] - pub fn config(&mut self) -> Config { - self.cmd.args(["config", "--json"]); - let output = self.assert().success().get_output().stdout_lossy(); - self.forge_fuse(); - serde_json::from_str(output.as_ref()).unwrap() - } - - /// Runs `git init` inside the project's dir - #[track_caller] - pub fn git_init(&self) { - let mut cmd = Command::new("git"); - cmd.arg("init").current_dir(self.project.root()); - let output = OutputAssert::new(cmd.output().unwrap()); - output.success(); - } - - /// Runs `git add .` inside the project's dir - #[track_caller] - pub fn git_add(&self) { - let mut cmd = Command::new("git"); - cmd.current_dir(self.project.root()); - cmd.arg("add").arg("."); - let output = OutputAssert::new(cmd.output().unwrap()); - output.success(); - } - - /// Runs `git commit .` inside the project's dir - #[track_caller] - pub fn git_commit(&self, msg: &str) { - let mut cmd = Command::new("git"); - cmd.current_dir(self.project.root()); - cmd.arg("commit").arg("-m").arg(msg); - let output = OutputAssert::new(cmd.output().unwrap()); - output.success(); - } - - /// Runs the command, returning a [`snapbox`] object to assert the command output. - #[track_caller] - pub fn assert(&mut self) -> OutputAssert { - let assert = OutputAssert::new(self.execute()); - if self.redact_output { - return assert.with_assert(test_assert()); +/// Resolve a path against a safe base directory and ensure it does not escape that base. +/// +/// This guards against using uncontrolled paths that could traverse outside the intended +/// workspace (for example, via `..` components or absolute paths). +fn resolve_and_validate_under_base(path: &Path) -> io::Result { + // Use a fixed base directory for test utilities instead of the current working + // directory, which may be influenced by the environment. + let base = TEST_UTIL_BASE.clone(); + + // If `path` is absolute, interpret it relative to the base by stripping the + // root and joining the remaining components. This avoids treating arbitrary + // absolute paths as trustworthy. + let joined = if path.is_absolute() { + let relative_components = path.components().filter_map(|c| { + use std::path::Component; + match c { + Component::Normal(p) => Some(PathBuf::from(p)), + // Skip root and current-dir components; preserve parent-dir so that + // canonicalization below can detect and resolve them safely. + Component::RootDir | Component::CurDir => None, + Component::ParentDir => Some(PathBuf::from("..")), + Component::Prefix(_) => None, + } + }); + let mut rel = PathBuf::new(); + for c in relative_components { + rel.push(c); } - assert - } - - /// Runs the command and asserts that it resulted in success. - #[track_caller] - pub fn assert_success(&mut self) -> OutputAssert { - self.assert().success() - } - - /// Runs the command and asserts that it resulted in success, with expected JSON data. - #[track_caller] - pub fn assert_json_stdout(&mut self, expected: impl IntoData) { - let expected = expected.is(snapbox::data::DataFormat::Json).unordered(); - let stdout = self.assert_success().get_output().stdout.clone(); - let actual = stdout.into_data().is(snapbox::data::DataFormat::Json).unordered(); - assert_data_eq!(actual, expected); - } + base.join(rel) + } else { + base.join(path) + }; - /// Runs the command and asserts that it **succeeded** nothing was printed to stdout. - #[track_caller] - pub fn assert_empty_stdout(&mut self) { - self.assert_success().stdout_eq(Data::new()); + let canonical = joined.canonicalize()?; + if !canonical.starts_with(&base) { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "path escapes allowed base directory", + )); } - /// Runs the command and asserts that it failed. - #[track_caller] - pub fn assert_failure(&mut self) -> OutputAssert { - self.assert().failure() - } - - /// Runs the command and asserts that the exit code is `expected`. - #[track_caller] - pub fn assert_code(&mut self, expected: i32) -> OutputAssert { - self.assert().code(expected) - } - - /// Runs the command and asserts that it **failed** nothing was printed to stderr. - #[track_caller] - pub fn assert_empty_stderr(&mut self) { - self.assert_failure().stderr_eq(Data::new()); - } - - /// Runs the command with a temporary file argument and asserts that the contents of the file - /// match the given data. - #[track_caller] - pub fn assert_file(&mut self, data: impl IntoData) { - self.assert_file_with(|this, path| _ = this.arg(path).assert_success(), data); - } - - /// Creates a temporary file, passes it to `f`, then asserts that the contents of the file match - /// the given data. - #[track_caller] - pub fn assert_file_with(&mut self, f: impl FnOnce(&mut Self, &Path), data: impl IntoData) { - let file = tempfile::NamedTempFile::new().expect("couldn't create temporary file"); - f(self, file.path()); - assert_data_eq!(Data::read_from(file.path(), None), data); - } - - /// Does not apply [`snapbox`] redactions to the command output. - pub fn with_no_redact(&mut self) -> &mut Self { - self.redact_output = false; - self - } - - /// Executes command, applies stdin function and returns output - #[track_caller] - pub fn execute(&mut self) -> Output { - self.try_execute().unwrap() - } - - #[track_caller] - pub fn try_execute(&mut self) -> std::io::Result { - println!("executing {:?}", self.cmd); - let mut child = - self.cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped()).spawn()?; - if let Some(fun) = self.stdin_fun.take() { - fun(child.stdin.take().unwrap()); - } - child.wait_with_output() - } -} - -fn test_assert() -> snapbox::Assert { - snapbox::Assert::new() - .action_env(snapbox::assert::DEFAULT_ACTION_ENV) - .redact_with(test_redactions()) + Ok(canonical) } -fn test_redactions() -> snapbox::Redactions { - static REDACTIONS: LazyLock = LazyLock::new(|| { - let mut r = snapbox::Redactions::new(); - let redactions = [ - ("[SOLC_VERSION]", r"Solc( version)? \d+.\d+.\d+"), - ("[ELAPSED]", r"(finished )?in \d+(\.\d+)?\w?s( \(.*?s CPU time\))?"), - ("[GAS]", r"[Gg]as( used)?: \d+"), - ("[GAS_COST]", r"[Gg]as cost\s*\(\d+\)"), - ("[GAS_LIMIT]", r"[Gg]as limit\s*\(\d+\)"), - ("[AVG_GAS]", r"μ: \d+, ~: \d+"), - ("[FILE]", r"-->.*\.sol"), - ("[FILE]", r"Location(.|\n)*\.rs(.|\n)*Backtrace"), - ("[COMPILING_FILES]", r"Compiling \d+ files?"), - ("[TX_HASH]", r"Transaction hash: 0x[0-9A-Fa-f]{64}"), - ("[ADDRESS]", r"Address: 0x[0-9A-Fa-f]{40}"), - ("[UPDATING_DEPENDENCIES]", r"Updating dependencies in .*"), - ("[SAVED_TRANSACTIONS]", r"Transactions saved to: .*\.json"), - ("[SAVED_SENSITIVE_VALUES]", r"Sensitive values saved to: .*\.json"), - ("[ESTIMATED_GAS_PRICE]", r"Estimated gas price:\s*(\d+(\.\d+)?)\s*gwei"), - ("[ESTIMATED_TOTAL_GAS_USED]", r"Estimated total gas used for script: \d+"), - ( - "[ESTIMATED_AMOUNT_REQUIRED]", - r"Estimated amount required:\s*(\d+(\.\d+)?)\s*[A-Z]{3}", - ), - ]; - for (placeholder, re) in redactions { - r.insert(placeholder, Regex::new(re).expect(re)).expect(re); +fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { + // Ensure that each recursion step operates on paths that are constrained to the + // configured base directory. This guarantees that any `src_path` passed to + // filesystem operations cannot escape the allowed workspace even if the initial + // input was influenced by the user. + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + for entry in fs::read_dir(&src)? { + let entry = entry?; + let ty = entry.file_type()?; + let name = entry.file_name(); + let src_path = src.join(&name); + let dst_path = dst.join(&name); + + if ty.is_dir() { + // Skip build artifact directories at the root level + if is_root + && let Some(name_str) = name.to_str() + && SKIP_DIRS.contains(&name_str) + { + continue; + } + fs::create_dir_all(&dst_path)?; + copy_dir_filtered_inner(&src_path, &dst_path, false)?; + } else { + fs::copy(&src_path, &dst_path)?; } - r - }); - REDACTIONS.clone() -} - -/// Extension trait for [`Output`]. -pub trait OutputExt { - /// Returns the stdout as lossy string - fn stdout_lossy(&self) -> String; -} - -impl OutputExt for Output { - fn stdout_lossy(&self) -> String { - lossy_string(&self.stdout) } -} - -pub fn lossy_string(bytes: &[u8]) -> String { - String::from_utf8_lossy(bytes).replace("\r\n", "\n") + Ok(()) } diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 1d3afe7923ced..e3372a2494f34 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -18,19 +18,21 @@ foundry-config.workspace = true foundry-cli.workspace = true foundry-common.workspace = true foundry-evm.workspace = true -foundry-evm-core.workspace = true +foundry-evm-networks.workspace = true serde_json.workspace = true +alloy-consensus.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true -alloy-rpc-types.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } alloy-dyn-abi.workspace = true serde.workspace = true eyre.workspace = true -alloy-provider.workspace = true +alloy-provider = { workspace = true, features = ["trace-api"] } tracing.workspace = true -foundry-compilers = { workspace = true, features = ["full"] } +foundry-compilers.workspace = true foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } revm.workspace = true +alloy-evm = { workspace = true, features = ["rpc"] } clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } reqwest = { workspace = true, features = ["json"] } @@ -40,8 +42,18 @@ semver.workspace = true regex = { workspace = true, default-features = false } yansi.workspace = true itertools.workspace = true +url.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros"] } foundry-test-utils.workspace = true tempfile.workspace = true + +[features] +default = ["optimism"] +optimism = [ + "foundry-common/optimism", + "foundry-evm/optimism", + "foundry-evm-networks/optimism", + "foundry-cli/optimism", +] diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index ee2e96188dab3..2bae8dde48089 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -2,29 +2,36 @@ use crate::{ etherscan::EtherscanVerificationProvider, utils::{ - check_and_encode_args, check_explorer_args, configure_env_block, maybe_predeploy_contract, - BytecodeType, JsonResult, + BytecodeType, JsonResult, check_and_encode_args, check_explorer_args, configure_env_block, + maybe_predeploy_contract, }, verify::VerifierArgs, }; -use alloy_primitives::{hex, Address, Bytes, TxKind, U256}; +use alloy_consensus::BlockHeader; +use alloy_evm::{FromRecoveredTx, rpc::TryIntoTxEnv}; +use alloy_primitives::{Address, Bytes, TxKind, U256, hex}; use alloy_provider::{ - network::{AnyTxEnvelope, TransactionBuilder}, Provider, + ext::TraceApi, + network::{ + AnyTxEnvelope, TransactionBuilder, TransactionResponse, primitives::BlockTransactions, + }, +}; +use alloy_rpc_types::{ + BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest, TransactionTrait, + trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}, }; -use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest}; use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result}; use foundry_cli::{ opts::EtherscanOpts, - utils::{self, read_constructor_args_file, LoadConfig}, + utils::{self, LoadConfig, read_constructor_args_file}, }; -use foundry_common::shell; +use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender, shell}; use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo}; -use foundry_config::{figment, impl_figment_convert, Config}; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, utils::configure_tx_req_env}; -use foundry_evm_core::AsEnvMut; -use revm::state::AccountInfo; +use foundry_config::{Config, figment, impl_figment_convert}; +use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, executors::EvmError}; +use revm::{context::TxEnv, state::AccountInfo}; use std::path::PathBuf; impl_figment_convert!(VerifyBytecodeArgs); @@ -106,15 +113,11 @@ impl figment::Provider for VerifyBytecodeArgs { dict.insert("etherscan_api_key".into(), api_key.as_str().into()); } - if let Some(api_version) = &self.verifier.verifier_api_version { - dict.insert("etherscan_api_version".into(), api_version.to_string().into()); - } - if let Some(block) = &self.block { dict.insert("block".into(), figment::value::Value::serialize(block)?); } if let Some(rpc_url) = &self.rpc_url { - dict.insert("eth_rpc_url".into(), rpc_url.to_string().into()); + dict.insert("eth_rpc_url".into(), rpc_url.clone().into()); } Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) @@ -172,7 +175,7 @@ impl VerifyBytecodeArgs { let source_code = etherscan.contract_source_code(self.address).await?; // Check if the contract name matches. - let name = source_code.items.first().map(|item| item.contract_name.to_owned()); + let name = source_code.items.first().map(|item| item.contract_name.clone()); if name.as_ref() != Some(&self.contract.name) { eyre::bail!("Contract name mismatch"); } @@ -181,14 +184,7 @@ impl VerifyBytecodeArgs { let etherscan_metadata = source_code.items.first().unwrap(); // Obtain local artifact - let artifact = if let Ok(local_bytecode) = - crate::utils::build_using_cache(&self, etherscan_metadata, &config) - { - trace!("using cache"); - local_bytecode - } else { - crate::utils::build_project(&self, &config)? - }; + let artifact = crate::utils::build_project(&self, &config)?; // Get local bytecode (creation code) let local_bytecode = artifact @@ -198,15 +194,15 @@ impl VerifyBytecodeArgs { .ok_or_eyre("Unlinked bytecode is not supported for verification")?; // Get and encode user provided constructor args - let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() { + let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() { // Read from file Some(read_constructor_args_file(path)?) } else { - self.constructor_args.to_owned() + self.constructor_args.clone() } .map(|args| check_and_encode_args(&artifact, args)) .transpose()? - .or(self.encoded_constructor_args.to_owned().map(hex::decode).transpose()?); + .or(self.encoded_constructor_args.clone().map(hex::decode).transpose()?); let mut constructor_args = if let Some(provided) = provided_constructor_args { provided.into() @@ -235,7 +231,7 @@ impl VerifyBytecodeArgs { // Deploy at genesis let gen_blk_num = 0_u64; let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; - let (mut env, mut executor) = crate::utils::get_tracing_executor( + let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor( &mut fork_config, gen_blk_num, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), @@ -243,10 +239,10 @@ impl VerifyBytecodeArgs { ) .await?; - env.evm_env.block_env.number = 0; + evm_env.block_env.number = U256::ZERO; let genesis_block = provider.get_block(gen_blk_num.into()).full().await?; - // Setup genesis tx and env. + // Setup genesis tx_env and evm_evm. let deployer = Address::with_last_byte(0x1); let mut gen_tx_req = TransactionRequest::default() .with_from(deployer) @@ -254,14 +250,14 @@ impl VerifyBytecodeArgs { .into_create(); if let Some(ref block) = genesis_block { - configure_env_block(&mut env.as_env_mut(), block); - gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128); - gen_tx_req.gas = Some(block.header.gas_limit); - gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128); + configure_env_block(&mut evm_env, block, config.networks); + gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas().map(|g| g as u128); + gen_tx_req.gas = Some(block.header.gas_limit()); + gen_tx_req.gas_price = block.header.base_fee_per_gas().map(|g| g as u128); } - configure_tx_req_env(&mut env.as_env_mut(), &gen_tx_req, None) - .wrap_err("Failed to configure tx request env")?; + let kind = gen_tx_req.kind(); + let tx_env = gen_tx_req.try_into_tx_env(&evm_env)?; // Seed deployer account with funds let account_info = AccountInfo { @@ -273,9 +269,10 @@ impl VerifyBytecodeArgs { let fork_address = crate::utils::deploy_contract( &mut executor, - &env, + &evm_env, + &tx_env, config.evm_spec_id(), - gen_tx_req.to, + kind, )?; // Compare runtime bytecode @@ -322,6 +319,7 @@ impl VerifyBytecodeArgs { .ok_or_else(|| { eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) })?; + let tx_hash = transaction.tx_hash(); let receipt = provider .get_transaction_receipt(creation_data.transaction_hash) .await @@ -335,29 +333,48 @@ impl VerifyBytecodeArgs { ); }; + let creation_block = transaction.block_number; let mut transaction: TransactionRequest = match transaction.inner.inner.inner() { AnyTxEnvelope::Ethereum(tx) => tx.clone().into(), AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"), }; // Extract creation code from creation tx input. - let maybe_creation_code = - if receipt.to.is_none() && receipt.contract_address == Some(self.address) { - match &transaction.input.input { - Some(input) => &input[..], - None => unreachable!("creation tx input is None"), - } - } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) { - match &transaction.input.input { - Some(input) => &input[32..], - None => unreachable!("creation tx input is None"), - } - } else { - eyre::bail!( + let maybe_creation_code = if receipt.to.is_none() + && receipt.contract_address == Some(self.address) + { + match &transaction.input.input { + Some(input) => &input[..], + None => unreachable!("creation tx input is None"), + } + } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) { + match &transaction.input.input { + Some(input) => &input[32..], + None => unreachable!("creation tx input is None"), + } + } else { + // Try to get creation bytecode from tx trace. + let traces = provider + .trace_transaction(creation_data.transaction_hash) + .await + .unwrap_or_default(); + + let creation_bytecode = + traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) { + ( + Some(TraceOutput::Create(CreateOutput { address, .. })), + Action::Create(CreateAction { init, .. }), + ) if *address == self.address => Some(init.clone()), + _ => None, + }); + + &creation_bytecode.ok_or_else(|| { + eyre::eyre!( "Could not extract the creation code for contract at address {}", self.address - ); - }; + ) + })? + }; // In some cases, Etherscan will return incorrect constructor arguments. If this // happens, try extracting arguments ourselves. @@ -422,13 +439,7 @@ impl VerifyBytecodeArgs { Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, Some(_) => eyre::bail!("Invalid block number"), None => { - let provider = utils::get_provider(&config)?; - provider - .get_transaction_by_hash(creation_data.transaction_hash) - .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| { - eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) - })? - .block_number.ok_or_else(|| { + creation_block.ok_or_else(|| { eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag") })? } @@ -436,14 +447,14 @@ impl VerifyBytecodeArgs { // Fork the chain at `simulation_block`. let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; - let (mut env, mut executor) = crate::utils::get_tracing_executor( + let (mut evm_env, mut tx_env, mut executor) = crate::utils::get_tracing_executor( &mut fork_config, simulation_block - 1, // env.fork_block_number etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), evm_opts, ) .await?; - env.evm_env.block_env.number = simulation_block; + evm_env.block_env.number = U256::from(simulation_block); let block = provider.get_block(simulation_block.into()).full().await?; // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same @@ -459,7 +470,56 @@ impl VerifyBytecodeArgs { transaction.set_nonce(prev_block_nonce); if let Some(ref block) = block { - configure_env_block(&mut env.as_env_mut(), block) + configure_env_block(&mut evm_env, block, config.networks); + + let BlockTransactions::Full(ref txs) = block.transactions else { + return Err(eyre::eyre!("Could not get block txs")); + }; + + // Replay txes in block until the contract creation one. + for tx in txs { + trace!("replay tx::: {}", tx.tx_hash()); + if is_known_system_sender(tx.from()) + || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + { + continue; + } + if tx.tx_hash() == tx_hash { + break; + } + + if let Some(tx_envelope) = tx.as_envelope() { + tx_env = TxEnv::from_recovered_tx(tx_envelope, tx.from()); + } + + if let TxKind::Call(_) = tx.inner.kind() { + executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( + || { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + evm_env.block_env.number + ) + }, + )?; + } else if let Err(error) = + executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) + { + match error { + // Reverted transactions should be skipped + EvmError::Execution(_) => (), + error => { + return Err(error).wrap_err_with(|| { + format!( + "Failed to deploy transaction: {:?} in block {}", + tx.tx_hash(), + evm_env.block_env.number + ) + }); + } + } + } + } } // Replace the `input` with local creation code in the creation tx. @@ -476,15 +536,15 @@ impl VerifyBytecodeArgs { transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec)); } - // configure_req__env(&mut env, &transaction.inner); - configure_tx_req_env(&mut env.as_env_mut(), &transaction, None) - .wrap_err("Failed to configure tx request env")?; + let kind = transaction.kind(); + tx_env = transaction.try_into_tx_env(&evm_env)?; let fork_address = crate::utils::deploy_contract( &mut executor, - &env, + &evm_env, + &tx_env, config.evm_spec_id(), - transaction.to, + kind, )?; // State committed using deploy_with_env, now get the runtime bytecode from the db. diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index a0b3defd7f90e..81bd48bda4b88 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,16 +1,17 @@ use super::{EtherscanSourceProvider, VerifyArgs}; use crate::provider::VerificationContext; -use eyre::{Context, Result}; +use eyre::Result; use foundry_block_explorers::verify::CodeFormat; +use foundry_common::flatten; use foundry_compilers::{ + AggregatedCompilerOutput, artifacts::{BytecodeHash, Source, Sources}, buildinfo::RawBuildInfo, compilers::{ - solc::{SolcCompiler, SolcLanguage, SolcVersionedInput}, Compiler, CompilerInput, + solc::{SolcCompiler, SolcLanguage, SolcVersionedInput}, }, solc::Solc, - AggregatedCompilerOutput, }; use semver::{BuildMetadata, Version}; use std::path::Path; @@ -32,18 +33,15 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { bch, ); - let source = context - .project - .paths - .clone() - .with_language::() - .flatten(&context.target_path) - .wrap_err("Failed to flatten contract")?; - + let flattened_source = flatten(context.project.clone(), &context.target_path)?; if !args.force { // solc dry run of flattened code - self.check_flattened(source.clone(), &context.compiler_version, &context.target_path) - .map_err(|err| { + self.check_flattened( + flattened_source.clone(), + &context.compiler_version, + &context.target_path, + ) + .map_err(|err| { eyre::eyre!( "Failed to compile the flattened code locally: `{}`\ To skip this solc dry, have a look at the `--force` flag of this command.", @@ -52,7 +50,7 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { })?; } - Ok((source, context.target_name.clone(), CodeFormat::SingleFile)) + Ok((flattened_source, context.target_name.clone(), CodeFormat::SingleFile)) } } @@ -91,7 +89,7 @@ impl EtherscanFlattenedSource { if out.errors.iter().any(|e| e.is_error()) { let mut o = AggregatedCompilerOutput::::default(); o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out); - let diags = o.diagnostics(&[], &[], Default::default()); + let diags = o.diagnostics(&[], &[], Default::default(), Default::default()); eyre::bail!( "\ @@ -113,9 +111,9 @@ Diagnostics: {diags}", /// sanitized variant of the specific version so that it can be installed. This is merely /// intended to ensure the flattened code can be compiled without errors. fn strip_build_meta(version: Version) -> Version { - if version.build != BuildMetadata::EMPTY { - Version::new(version.major, version.minor, version.patch) - } else { + if version.build == BuildMetadata::EMPTY { version + } else { + Version::new(version.major, version.minor, version.patch) } } diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index fd0e08e355a14..7f0afd972c5bf 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -1,30 +1,30 @@ use crate::{ + VerifierArgs, provider::{VerificationContext, VerificationProvider}, retry::RETRY_CHECK_ON_VERIFY, - verify::{VerifyArgs, VerifyCheckArgs}, - VerifierArgs, + utils::ensure_solc_build_metadata, + verify::{ContractLanguage, VerifyArgs, VerifyCheckArgs}, }; use alloy_json_abi::Function; use alloy_primitives::hex; use alloy_provider::Provider; use alloy_rpc_types::TransactionTrait; -use eyre::{eyre, Context, OptionExt, Result}; +use eyre::{Context, OptionExt, Result, eyre}; use foundry_block_explorers::{ + Client, errors::EtherscanError, - utils::lookup_compiler_version, verify::{CodeFormat, VerifyContract}, - Client, EtherscanApiVersion, }; use foundry_cli::{ opts::EtherscanOpts, - utils::{get_provider, read_constructor_args_file, LoadConfig}, + utils::{LoadConfig, get_provider, read_constructor_args_file}, }; use foundry_common::{abi::encode_function_args, retry::RetryError}; -use foundry_compilers::{artifacts::BytecodeObject, Artifact}; +use foundry_compilers::{Artifact, artifacts::BytecodeObject}; use foundry_config::Config; use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use regex::Regex; -use semver::{BuildMetadata, Version}; +use semver::BuildMetadata; use std::{fmt::Debug, sync::LazyLock}; mod flatten; @@ -63,8 +63,8 @@ impl VerificationProvider for EtherscanVerificationProvider { async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { let (etherscan, verify_args) = self.prepare_verify_request(&args, &context).await?; - if !args.skip_is_verified_check && - self.is_contract_verified(ðerscan, &verify_args).await? + if !args.skip_is_verified_check + && self.is_contract_verified(ðerscan, &verify_args).await? { sh_println!( "\nContract [{}] {:?} is already verified. Skipping verification.", @@ -72,7 +72,7 @@ impl VerificationProvider for EtherscanVerificationProvider { verify_args.address.to_checksum(None) )?; - return Ok(()) + return Ok(()); } trace!(?verify_args, "submitting verification request"); @@ -92,7 +92,6 @@ impl VerificationProvider for EtherscanVerificationProvider { .wrap_err_with(|| { // valid json let args = serde_json::to_string(&verify_args).unwrap(); - error!(?args, "Failed to submit verification"); format!("Failed to submit contract verification, payload:\n{args}") })?; @@ -103,23 +102,24 @@ impl VerificationProvider for EtherscanVerificationProvider { // specific for blockscout response || resp.result == "Smart-contract already verified." { - return Ok(None) + return Ok(None); } - if resp.result.starts_with("Unable to locate ContractCode at") || - resp.result.starts_with("The address is not a smart contract") + if resp.result.starts_with("Unable to locate ContractCode at") + || resp.result.starts_with("The address is not a smart contract") + || resp.result.starts_with("Address is not a smart-contract") { warn!("{}", resp.result); - return Err(eyre!("Could not detect the deployment.")) + return Err(eyre!("Could not detect deployment: {}", resp.result)); } - warn!("Failed verify submission: {:?}", resp); sh_err!( "Encountered an error verifying this contract:\nResponse: `{}`\nDetails: `{}`", resp.message, resp.result )?; + warn!("Failed verify submission: {:?}", resp); std::process::exit(1); } @@ -142,7 +142,7 @@ impl VerificationProvider for EtherscanVerificationProvider { retry: RETRY_CHECK_ON_VERIFY, verifier: args.verifier, }; - return self.check(check_args).await + return self.check(check_args).await; } } else { sh_println!("Contract source code already verified")?; @@ -172,21 +172,27 @@ impl VerificationProvider for EtherscanVerificationProvider { resp.result ); - if resp.result == "Pending in queue" { - return Err(RetryError::Retry(eyre!("Verification is still pending..."))) + if resp.result == "Pending in queue" + || resp.result.starts_with("Error: contract does not exist") + { + return Err(RetryError::Retry(eyre!("Verification is still pending..."))); } if resp.result == "Unable to verify" { - return Err(RetryError::Retry(eyre!("Unable to verify."))) + return Err(RetryError::Retry(eyre!("Unable to verify."))); } if resp.result == "Already Verified" { let _ = sh_println!("Contract source code already verified"); - return Ok(()) + return Ok(()); } if resp.status == "0" { - return Err(RetryError::Break(eyre!("Contract failed to verify."))) + return Err(RetryError::Break(eyre!( + "Contract verification failed:\nStatus: `{}`\nResult: `{}`", + resp.status, + resp.result + ))); } if resp.result == "Pass - Verified" { @@ -232,10 +238,12 @@ impl EtherscanVerificationProvider { let check = etherscan.contract_abi(verify_contract.address).await; if let Err(err) = check { - match err { - EtherscanError::ContractCodeNotVerified(_) => return Ok(false), - error => return Err(error.into()), - } + return match err { + EtherscanError::ContractCodeNotVerified(_) => Ok(false), + error => Err(error).wrap_err_with(|| { + format!("Failed to obtain contract ABI for {}", verify_contract.address) + }), + }; } Ok(true) @@ -255,29 +263,12 @@ impl EtherscanVerificationProvider { // Verifier is etherscan if explicitly set or if no verifier set (default sourcify) but // API key passed. - let is_etherscan = verifier_type.is_etherscan() || - (verifier_type.is_sourcify() && etherscan_key.is_some()); + let is_etherscan = verifier_type.is_etherscan() + || (verifier_type.is_sourcify() && etherscan_key.is_some()); let etherscan_config = config.get_etherscan_config_with_chain(Some(chain))?; - let api_version = verifier_args.verifier_api_version.unwrap_or_else(|| { - if is_etherscan { - etherscan_config.as_ref().map(|c| c.api_version).unwrap_or_default() - } else { - EtherscanApiVersion::V1 - } - }); - - let etherscan_api_url = verifier_url - .or_else(|| { - if api_version == EtherscanApiVersion::V2 { - None - } else { - etherscan_config.as_ref().map(|c| c.api_url.as_str()) - } - }) - .map(str::to_owned); - - let api_url = etherscan_api_url.as_deref(); + let api_url = + verifier_url.or_else(|| etherscan_config.as_ref().map(|c| c.api_url.as_str())); let base_url = etherscan_config .as_ref() .and_then(|c| c.browser_url.as_deref()) @@ -285,18 +276,18 @@ impl EtherscanVerificationProvider { let etherscan_key = etherscan_key.or_else(|| etherscan_config.as_ref().map(|c| c.key.clone())); - let mut builder = Client::builder().with_api_version(api_version); + let mut builder = Client::builder(); builder = if let Some(api_url) = api_url { // we don't want any trailing slashes because this can cause cloudflare issues: let api_url = api_url.trim_end_matches('/'); - let base_url = if !is_etherscan { - // If verifier is not Etherscan then set base url as api url without /api suffix. - api_url.strip_prefix("/api").unwrap_or(api_url) - } else { + let base_url = if is_etherscan { base_url.unwrap_or(api_url) + } else { + // If verifier is not Etherscan then set base url as api url without /api suffix. + api_url.strip_suffix("/api").unwrap_or(api_url) }; - builder.with_chain_id(chain).with_api_url(api_url)?.with_url(base_url)? + builder.with_api_url(api_url)?.with_url(base_url)? } else { builder.chain(chain)? }; @@ -319,14 +310,20 @@ impl EtherscanVerificationProvider { let (source, contract_name, code_format) = self.source_provider(args).source(args, context)?; + let lang = args.detect_language(context); + let mut compiler_version = context.compiler_version.clone(); compiler_version.build = match RE_BUILD_COMMIT.captures(compiler_version.build.as_str()) { Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, _ => BuildMetadata::EMPTY, }; - let compiler_version = - format!("v{}", ensure_solc_build_metadata(context.compiler_version.clone()).await?); + let compiler_version = if matches!(lang, ContractLanguage::Vyper) { + format!("vyper:{}", compiler_version.to_string().split('+').next().unwrap_or("0.0.0")) + } else { + format!("v{}", ensure_solc_build_metadata(context.compiler_version.clone()).await?) + }; + let constructor_args = self.constructor_args(args, context).await?; let mut verify_args = VerifyContract::new(args.address, contract_name, source, compiler_version) @@ -353,6 +350,15 @@ impl EtherscanVerificationProvider { }; } + if code_format == CodeFormat::VyperJson { + verify_args = + if args.num_of_optimizations.is_some() || context.config.optimizer == Some(true) { + verify_args.optimized().runs(1) + } else { + verify_args.not_optimized().runs(0) + } + } + Ok(verify_args) } @@ -377,13 +383,13 @@ impl EtherscanVerificationProvider { }; let encoded_args = encode_function_args( &func, - read_constructor_args_file(constructor_args_path.to_path_buf())?, + read_constructor_args_file(constructor_args_path.clone())?, )?; let encoded_args = hex::encode(encoded_args); - return Ok(Some(encoded_args[8..].into())) + return Ok(Some(encoded_args[8..].into())); } if args.guess_constructor_args { - return Ok(Some(self.guess_constructor_args(args, context).await?)) + return Ok(Some(self.guess_constructor_args(args, context).await?)); } Ok(args.constructor_args.clone()) @@ -412,11 +418,13 @@ impl EtherscanVerificationProvider { .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; let maybe_creation_code = if receipt.contract_address == Some(args.address) { - transaction.inner.inner.input() + transaction.input() } else if transaction.to() == Some(DEFAULT_CREATE2_DEPLOYER) { - &transaction.inner.inner.input()[32..] + &transaction.input()[32..] } else { - eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") + eyre::bail!( + "Fetching of constructor arguments is not supported for contracts created by contracts" + ) }; let output = context.project.compile_file(&context.target_path)?; @@ -445,24 +453,6 @@ impl EtherscanVerificationProvider { } } -/// Given any solc [Version] return a [Version] with build metadata -/// -/// # Example -/// -/// ```ignore -/// use semver::{BuildMetadata, Version}; -/// let version = Version::new(1, 2, 3); -/// let version = ensure_solc_build_metadata(version).await?; -/// assert_ne!(version.build, BuildMetadata::EMPTY); -/// ``` -async fn ensure_solc_build_metadata(version: Version) -> Result { - if version.build != BuildMetadata::EMPTY { - Ok(version) - } else { - Ok(lookup_compiler_version(&version).await?) - } -} - #[cfg(test)] mod tests { use super::*; @@ -481,7 +471,7 @@ mod tests { [profile.default] [etherscan] - mumbai = { key = "dummykey", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + amoy = { key = "dummykey", chain = 80002, url = "https://amoy.polygonscan.com/" } "#; let toml_file = root.join(Config::FILE_NAME); @@ -492,7 +482,7 @@ mod tests { "0xd8509bee9c9bf012282ad33aba0d87241baf5064", "src/Counter.sol:Counter", "--chain", - "mumbai", + "amoy", "--root", root.as_os_str().to_str().unwrap(), ]); @@ -501,7 +491,8 @@ mod tests { let etherscan = EtherscanVerificationProvider::default(); let client = etherscan.client(&args.etherscan, &args.verifier, &config).unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://api-testnet.polygonscan.com/"); + // Custom URL from foundry.toml should be used + assert_eq!(client.etherscan_api_url().as_str(), "https://amoy.polygonscan.com/"); assert!(format!("{client:?}").contains("dummykey")); @@ -510,7 +501,7 @@ mod tests { "0xd8509bee9c9bf012282ad33aba0d87241baf5064", "src/Counter.sol:Counter", "--chain", - "mumbai", + "amoy", "--verifier-url", "https://verifier-url.com/", "--root", @@ -534,7 +525,7 @@ mod tests { [profile.default] [etherscan] - mumbai = { key = "dummykey", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + amoy = { key = "dummykey", chain = 80002, url = "https://amoy.polygonscan.com/" } "#; let toml_file = root.join(Config::FILE_NAME); @@ -547,7 +538,7 @@ mod tests { "--verifier", "etherscan", "--chain", - "mumbai", + "amoy", "--root", root.as_os_str().to_str().unwrap(), ]); @@ -558,7 +549,8 @@ mod tests { let client = etherscan.client(&args.etherscan, &args.verifier, &config).unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://api.etherscan.io/v2/api"); + // Custom URL from foundry.toml should be used + assert_eq!(client.etherscan_api_url().as_str(), "https://amoy.polygonscan.com/"); assert!(format!("{client:?}").contains("dummykey")); let args: VerifyArgs = VerifyArgs::parse_from([ @@ -568,7 +560,7 @@ mod tests { "--verifier", "etherscan", "--chain", - "mumbai", + "amoy", "--verifier-url", "https://verifier-url.com/", "--root", @@ -582,7 +574,6 @@ mod tests { let etherscan = EtherscanVerificationProvider::default(); let client = etherscan.client(&args.etherscan, &args.verifier, &config).unwrap(); assert_eq!(client.etherscan_api_url().as_str(), "https://verifier-url.com/"); - assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); assert!(format!("{client:?}").contains("dummykey")); } @@ -624,8 +615,8 @@ mod tests { } forgetest_async!(respects_path_for_duplicate, |prj, cmd| { - prj.add_source("Counter1", "contract Counter {}").unwrap(); - prj.add_source("Counter2", "contract Counter {}").unwrap(); + prj.add_source("Counter1", "contract Counter {}"); + prj.add_source("Counter2", "contract Counter {}"); cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index e2fb5d2a47c03..848d5808fd77b 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -1,43 +1,33 @@ use super::{EtherscanSourceProvider, VerifyArgs}; -use crate::provider::VerificationContext; +use crate::{provider::VerificationContext, verify::ContractLanguage}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; -use foundry_compilers::{artifacts::StandardJsonCompilerInput, solc::SolcLanguage}; #[derive(Debug)] pub struct EtherscanStandardJsonSource; impl EtherscanSourceProvider for EtherscanStandardJsonSource { fn source( &self, - _args: &VerifyArgs, + args: &VerifyArgs, context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let mut input: StandardJsonCompilerInput = context - .project - .standard_json_input(&context.target_path) - .wrap_err("Failed to get standard json input")? - .normalize_evm_version(&context.compiler_version); - - let mut settings = context.compiler_settings.solc.settings.clone(); - settings.libraries.libs = input - .settings - .libraries - .libs - .into_iter() - .map(|(f, libs)| { - (f.strip_prefix(context.project.root()).unwrap_or(&f).to_path_buf(), libs) - }) - .collect(); - - settings.remappings = input.settings.remappings; - - // remove all incompatible settings - settings.sanitize(&context.compiler_version, SolcLanguage::Solidity); - - input.settings = settings; - - let source = - serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; + let lang = args.detect_language(context); + + let code_format = match lang { + ContractLanguage::Solidity => CodeFormat::StandardJsonInput, + ContractLanguage::Vyper => CodeFormat::VyperJson, + }; + + let source = match lang { + ContractLanguage::Solidity => { + let input = context.get_solc_standard_json_input()?; + serde_json::to_string(&input).wrap_err("Failed to parse standard json input")? + } + ContractLanguage::Vyper => { + let input = context.get_vyper_standard_json_input()?; + serde_json::to_string(&input).wrap_err("Failed to parse vyper json input")? + } + }; trace!(target: "forge::verify", standard_json=source, "determined standard json input"); @@ -50,6 +40,6 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { .display(), context.target_name.clone() ); - Ok((source, name, CodeFormat::StandardJsonInput)) + Ok((source, name, code_format)) } } diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs index a46fdba901550..8e78c1bffde5f 100644 --- a/crates/verify/src/lib.rs +++ b/crates/verify/src/lib.rs @@ -1,7 +1,7 @@ //! Smart contract verification. #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; @@ -19,7 +19,7 @@ pub use bytecode::VerifyBytecodeArgs; pub mod retry; pub use retry::RetryArgs; -mod sourcify; +pub mod sourcify; pub mod verify; pub use verify::{VerifierArgs, VerifyArgs, VerifyCheckArgs}; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 74d2094c41b83..773430c4bf785 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -5,18 +5,24 @@ use crate::{ }; use alloy_json_abi::JsonAbi; use async_trait::async_trait; -use eyre::{OptionExt, Result}; +use eyre::{Context, OptionExt, Result}; use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ - artifacts::{output_selection::OutputSelection, Metadata, Source}, - compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler}, + Project, + artifacts::{ + Source, StandardJsonCompilerInput, output_selection::OutputSelection, vyper::VyperInput, + }, + compilers::solc::SolcCompiler, multi::MultiCompilerSettings, - solc::Solc, - Graph, Project, + solc::{Solc, SolcLanguage}, }; -use foundry_config::Config; +use foundry_config::{Chain, Config, EtherscanConfigError}; use semver::Version; -use std::{fmt, path::PathBuf, str::FromStr}; +use std::{ + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; /// Container with data required for contract verification. #[derive(Debug, Clone)] @@ -46,30 +52,43 @@ impl VerificationContext { Ok(Self { config, project, target_name, target_path, compiler_version, compiler_settings }) } - /// Compiles target contract requesting only ABI and returns it. - pub fn get_target_abi(&self) -> Result { - let mut project = self.project.clone(); - project.update_output_selection(|selection| { - *selection = OutputSelection::common_output_selection(["abi".to_string()]) - }); + pub fn get_solc_standard_json_input(&self) -> Result { + let mut input: StandardJsonCompilerInput = self + .project + .standard_json_input(&self.target_path) + .wrap_err("Failed to get standard json input")? + .normalize_evm_version(&self.compiler_version); - let output = ProjectCompiler::new() - .quiet(true) - .files([self.target_path.clone()]) - .compile(&project)?; + let mut settings = self.compiler_settings.solc.settings.clone(); + settings.libraries.libs = input + .settings + .libraries + .libs + .into_iter() + .map(|(f, libs)| { + (f.strip_prefix(self.project.root()).unwrap_or(&f).to_path_buf(), libs) + }) + .collect(); - let artifact = output - .find(&self.target_path, &self.target_name) - .ok_or_eyre("failed to find target artifact when compiling for abi")?; + settings.remappings = input.settings.remappings; + settings.sanitize(&self.compiler_version, SolcLanguage::Solidity); + input.settings = settings; - artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") + Ok(input) + } + + /// Creates Vyper standard JSON input for verification. + pub fn get_vyper_standard_json_input(&self) -> Result { + let path = Path::new(&self.target_path); + let sources = Source::read_all_from(path, &["vy", "vyi"])?; + Ok(VyperInput::new(sources, self.compiler_settings.vyper.clone(), &self.compiler_version)) } - /// Compiles target file requesting only metadata and returns it. - pub fn get_target_metadata(&self) -> Result { + /// Compiles target contract requesting only ABI and returns it. + pub fn get_target_abi(&self) -> Result { let mut project = self.project.clone(); project.update_output_selection(|selection| { - *selection = OutputSelection::common_output_selection(["metadata".to_string()]); + *selection = OutputSelection::common_output_selection(["abi".to_string()]); }); let output = ProjectCompiler::new() @@ -79,19 +98,9 @@ impl VerificationContext { let artifact = output .find(&self.target_path, &self.target_name) - .ok_or_eyre("failed to find target artifact when compiling for metadata")?; - - artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI") - } - - /// Returns [Vec] containing imports of the target file. - pub fn get_target_imports(&self) -> Result> { - let mut sources = self.project.paths.read_input_files()?; - sources.insert(self.target_path.clone(), Source::read(&self.target_path)?); - let graph = - Graph::::resolve_sources(&self.project.paths, sources)?; + .ok_or_eyre("failed to find target artifact when compiling for abi")?; - Ok(graph.imports(&self.target_path).into_iter().map(Into::into).collect()) + artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") } } @@ -169,19 +178,34 @@ pub enum VerificationProviderType { impl VerificationProviderType { /// Returns the corresponding `VerificationProvider` for the key - pub fn client(&self, key: Option<&str>) -> Result> { + pub fn client( + &self, + key: Option<&str>, + chain: Option, + has_url: bool, + ) -> Result> { let has_key = key.as_ref().is_some_and(|k| !k.is_empty()); // 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify. if !has_key && self.is_sourcify() { sh_println!( - "Attempting to verify on Sourcify. Pass the --etherscan-api-key to verify on Etherscan, \ + "Attempting to verify on Sourcify. Pass the --etherscan-api-key to verify on Etherscan, \ or use the --verifier flag to verify on another provider." - )?; + )?; return Ok(Box::::default()); } - // 2. If `--verifier etherscan` is explicitly set, enforce the API key requirement. + // 2. If `--verifier etherscan` is explicitly set, check if chain is supported and + // enforce the API key requirement. if self.is_etherscan() { + if let Some(chain) = chain + && chain.etherscan_urls().is_none() + && !has_url + { + eyre::bail!(EtherscanConfigError::UnknownChain( + "when using Etherscan verifier".to_string(), + chain + )) + } if !has_key { eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier") } @@ -189,25 +213,58 @@ impl VerificationProviderType { } // 3. If `--verifier blockscout | oklink | custom` is explicitly set, use the chosen - // verifier. + // verifier and make sure an URL was specified. if matches!(self, Self::Blockscout | Self::Oklink | Self::Custom) { + if !has_url { + eyre::bail!("No verifier URL specified for verifier {}", self); + } return Ok(Box::::default()); } // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan. if has_key { + sh_eprintln!( + "ETHERSCAN_API_KEY is set, defaulting to Etherscan verifier. \ + Unset it or pass `--verifier sourcify` (or another provider) to override." + )?; return Ok(Box::::default()); } // 5. If no valid provider is specified, bail. - eyre::bail!("No valid verification provider specified. Pass the --verifier flag to specify a provider or set the ETHERSCAN_API_KEY environment variable to use Etherscan as a verifier.") + eyre::bail!( + "No valid verification provider specified. Pass the --verifier flag to specify a provider or set the ETHERSCAN_API_KEY environment variable to use Etherscan as a verifier." + ) } - pub fn is_sourcify(&self) -> bool { + pub const fn is_sourcify(&self) -> bool { matches!(self, Self::Sourcify) } - pub fn is_etherscan(&self) -> bool { + pub const fn is_etherscan(&self) -> bool { matches!(self, Self::Etherscan) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn etherscan_allows_unknown_chain_with_verifier_url() { + let chain = Chain::from(3658348u64); + let res = VerificationProviderType::Etherscan.client(Some("key"), Some(chain), true); + assert!(res.is_ok()); + } + + #[test] + fn etherscan_rejects_unknown_chain_without_verifier_url() { + let chain = Chain::from(3658348u64); + let res = VerificationProviderType::Etherscan.client(Some("key"), Some(chain), false); + match res { + Ok(_) => panic!("expected unknown-chain error"), + Err(err) => { + assert!(err.to_string().contains("No known Etherscan API URL")); + } + } + } +} diff --git a/crates/verify/src/retry.rs b/crates/verify/src/retry.rs index a01b1c94522aa..7df2826ed8714 100644 --- a/crates/verify/src/retry.rs +++ b/crates/verify/src/retry.rs @@ -1,4 +1,4 @@ -use clap::{builder::RangedU64ValueParser, Parser}; +use clap::{Parser, builder::RangedU64ValueParser}; use foundry_common::retry::Retry; use std::time::Duration; @@ -37,7 +37,7 @@ impl Default for RetryArgs { impl RetryArgs { /// Converts the arguments into a `Retry` instance. - pub fn into_retry(self) -> Retry { + pub const fn into_retry(self) -> Retry { Retry::new(self.retries, Duration::from_secs(self.delay as u64)) } } diff --git a/crates/verify/src/sourcify.rs b/crates/verify/src/sourcify.rs index a57335d39398b..0054dbc8f6ced 100644 --- a/crates/verify/src/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -1,15 +1,17 @@ use crate::{ provider::{VerificationContext, VerificationProvider}, - verify::{VerifyArgs, VerifyCheckArgs}, + retry::RETRY_CHECK_ON_VERIFY, + utils::ensure_solc_build_metadata, + verify::{ContractLanguage, VerifyArgs, VerifyCheckArgs}, }; -use alloy_primitives::map::HashMap; +use alloy_primitives::Address; use async_trait::async_trait; -use eyre::Result; -use foundry_common::fs; +use eyre::{Context, Result, eyre}; +use foundry_common::retry::RetryError; use futures::FutureExt; -use reqwest::Url; +use reqwest::StatusCode; use serde::{Deserialize, Serialize}; -use std::str::FromStr; +use url::Url; pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; @@ -25,16 +27,29 @@ impl VerificationProvider for SourcifyVerificationProvider { args: VerifyArgs, context: VerificationContext, ) -> Result<()> { - let _ = self.prepare_request(&args, &context)?; + let _ = self.prepare_verify_request(&args, &context).await?; Ok(()) } async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { - let body = self.prepare_request(&args, &context)?; + let body = self.prepare_verify_request(&args, &context).await?; + let chain_id = args.etherscan.chain.unwrap_or_default().id(); + + if !args.skip_is_verified_check && self.is_contract_verified(&args).await? { + sh_println!( + "\nContract [{}] {:?} is already verified. Skipping verification.", + context.target_name, + args.address.to_string() + )?; + + return Ok(()); + } trace!("submitting verification request {:?}", body); let client = reqwest::Client::new(); + let url = + Self::get_verify_url(args.verifier.verifier_url.as_deref(), chain_id, args.address); let resp = args .retry @@ -47,155 +62,319 @@ impl VerificationProvider for SourcifyVerificationProvider { args.address.to_string() )?; let response = client - .post(args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL)) + .post(&url) .header("Content-Type", "application/json") .body(serde_json::to_string(&body)?) .send() .await?; let status = response.status(); - if !status.is_success() { - let error: serde_json::Value = response.json().await?; - eyre::bail!( - "Sourcify verification request for address ({}) \ - failed with status code {status}\n\ - Details: {error:#}", - args.address, - ); + match status { + StatusCode::CONFLICT => { + sh_println!("Contract source code already fully verified")?; + Ok(None) + } + StatusCode::ACCEPTED => { + let text = response.text().await?; + let verify_response: SourcifyVerificationResponse = + serde_json::from_str(&text) + .wrap_err("Failed to parse Sourcify verification response")?; + Ok(Some(verify_response)) + } + _ => { + let error: serde_json::Value = response.json().await?; + eyre::bail!( + "Sourcify verification request for address ({}) \ + failed with status code {status}\n\ + Details: {error:#}", + args.address, + ); + } } - - let text = response.text().await?; - Ok(Some(serde_json::from_str::(&text)?)) } .boxed() }) .await?; - self.process_sourcify_response(resp.map(|r| r.result)) + if let Some(resp) = resp { + let job_url = Self::get_job_status_url( + args.verifier.verifier_url.as_deref(), + resp.verification_id.clone(), + ); + sh_println!( + "Submitted contract for verification:\n\tVerification Job ID: `{}`\n\tURL: {}", + resp.verification_id, + job_url + )?; + + if args.watch { + let check_args = VerifyCheckArgs { + id: resp.verification_id, + etherscan: args.etherscan, + retry: RETRY_CHECK_ON_VERIFY, + verifier: args.verifier, + }; + return self.check(check_args).await; + } + } + + Ok(()) } async fn check(&self, args: VerifyCheckArgs) -> Result<()> { - let resp = args - .retry + let url = Self::get_job_status_url(args.verifier.verifier_url.as_deref(), args.id.clone()); + + args.retry .into_retry() - .run_async(|| { - async { - let url = Url::from_str( - args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL), - )?; - let query = format!( - "check-by-addresses?addresses={}&chainIds={}", - args.id, - args.etherscan.chain.unwrap_or_default().id(), + .run_async_until_break(|| async { + let response = reqwest::get(&url) + .await + .wrap_err("Failed to request verification status") + .map_err(RetryError::Retry)?; + + if response.status() == StatusCode::NOT_FOUND { + return Err(RetryError::Break(eyre!( + "No verification job found for ID {}", + args.id + ))); + } + + if !response.status().is_success() { + return Err(RetryError::Retry(eyre!( + "Failed to request verification status with status code {}", + response.status() + ))); + } + + let job_response: SourcifyJobResponse = response + .json() + .await + .wrap_err("Failed to parse job response") + .map_err(RetryError::Retry)?; + + if !job_response.is_job_completed { + return Err(RetryError::Retry(eyre!("Verification is still pending..."))); + } + + if let Some(error) = job_response.error { + if error.custom_code == "already_verified" { + let _ = sh_println!("Contract source code already verified"); + return Ok(()); + } + + return Err(RetryError::Break(eyre!( + "Verification job failed:\nError Code: `{}`\nMessage: `{}`", + error.custom_code, + error.message + ))); + } + + if let Some(contract_status) = job_response.contract.match_status { + let _ = sh_println!( + "Contract successfully verified:\nStatus: `{}`", + contract_status, ); - let url = url.join(&query)?; - let response = reqwest::get(url).await?; - if !response.status().is_success() { - eyre::bail!( - "Failed to request verification status with status code {}", - response.status() - ); - }; - - Ok(Some(response.json::>().await?)) } - .boxed() + Ok(()) }) - .await?; - - self.process_sourcify_response(resp) + .await + .wrap_err("Checking verification result failed") } } impl SourcifyVerificationProvider { + fn get_base_url(verifier_url: Option<&str>) -> Url { + // note(onbjerg): a little ugly but makes this infallible as we guarantee `SOURCIFY_URL` to + // be well formatted + Url::parse(verifier_url.unwrap_or(SOURCIFY_URL)) + .unwrap_or_else(|_| Url::parse(SOURCIFY_URL).unwrap()) + } + + fn get_verify_url( + verifier_url: Option<&str>, + chain_id: u64, + contract_address: Address, + ) -> String { + let base_url = Self::get_base_url(verifier_url); + format!("{base_url}v2/verify/{chain_id}/{contract_address}") + } + + fn get_job_status_url(verifier_url: Option<&str>, job_id: String) -> String { + let base_url = Self::get_base_url(verifier_url); + format!("{base_url}v2/verify/{job_id}") + } + + fn get_lookup_url( + verifier_url: Option<&str>, + chain_id: u64, + contract_address: Address, + ) -> String { + let base_url = Self::get_base_url(verifier_url); + format!("{base_url}v2/contract/{chain_id}/{contract_address}") + } + /// Configures the API request to the sourcify API using the given [`VerifyArgs`]. - fn prepare_request( + async fn prepare_verify_request( &self, args: &VerifyArgs, context: &VerificationContext, ) -> Result { - let metadata = context.get_target_metadata()?; - let imports = context.get_target_imports()?; + let lang = args.detect_language(context); + let contract_identifier = format!( + "{}:{}", + context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display(), + context.target_name + ); + let creation_transaction_hash = args.creation_transaction_hash.map(|h| h.to_string()); + + match lang { + ContractLanguage::Solidity => { + let input = context.get_solc_standard_json_input()?; - let mut files = HashMap::with_capacity_and_hasher(2 + imports.len(), Default::default()); + let std_json_input = serde_json::to_value(&input) + .wrap_err("Failed to serialize standard json input")?; + let compiler_version = + ensure_solc_build_metadata(context.compiler_version.clone()).await?.to_string(); - let metadata = serde_json::to_string_pretty(&metadata)?; - files.insert("metadata.json".to_string(), metadata); + Ok(SourcifyVerifyRequest { + std_json_input, + compiler_version, + contract_identifier, + creation_transaction_hash, + }) + } + ContractLanguage::Vyper => { + let input = context.get_vyper_standard_json_input()?; + let std_json_input = serde_json::to_value(&input) + .wrap_err("Failed to serialize vyper json input")?; - let contract_path = context.target_path.clone(); - let filename = contract_path.file_name().unwrap().to_string_lossy().to_string(); - files.insert(filename, fs::read_to_string(&contract_path)?); + let compiler_version = context.compiler_version.to_string(); - for import in imports { - let import_entry = format!("{}", import.display()); - files.insert(import_entry, fs::read_to_string(&import)?); + Ok(SourcifyVerifyRequest { + std_json_input, + compiler_version, + contract_identifier, + creation_transaction_hash, + }) + } } + } - let req = SourcifyVerifyRequest { - address: args.address.to_string(), - chain: args.etherscan.chain.unwrap_or_default().id().to_string(), - files, - chosen_contract: None, - }; + async fn is_contract_verified(&self, args: &VerifyArgs) -> Result { + let chain_id = args.etherscan.chain.unwrap_or_default().id(); + let url = + Self::get_lookup_url(args.verifier.verifier_url.as_deref(), chain_id, args.address); - Ok(req) - } + match reqwest::get(&url).await { + Ok(response) => { + if response.status().is_success() { + let contract_response: SourcifyContractResponse = + response.json().await.wrap_err("Failed to parse contract response")?; - fn process_sourcify_response( - &self, - response: Option>, - ) -> Result<()> { - let Some([response, ..]) = response.as_deref() else { return Ok(()) }; - match response.status.as_str() { - "perfect" => { - if let Some(ts) = &response.storage_timestamp { - sh_println!("Contract source code already verified. Storage Timestamp: {ts}")?; + let creation_exact = contract_response + .creation_match + .as_ref() + .map(|s| s == "exact_match") + .unwrap_or(false); + + let runtime_exact = contract_response + .runtime_match + .as_ref() + .map(|s| s == "exact_match") + .unwrap_or(false); + + Ok(creation_exact && runtime_exact) } else { - sh_println!("Contract successfully verified")?; + Ok(false) } } - "partial" => { - sh_println!("The recompiled contract partially matches the deployed version")?; - } - "false" => sh_println!("Contract source code is not verified")?, - s => eyre::bail!("Unknown status from sourcify. Status: {s:?}"), + Err(error) => Err(error).wrap_err_with(|| { + format!("Failed to query verification status for {}", args.address) + }), } - Ok(()) } } #[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct SourcifyVerifyRequest { - address: String, - chain: String, - files: HashMap, - #[serde(rename = "chosenContract", skip_serializing_if = "Option::is_none")] - chosen_contract: Option, + std_json_input: serde_json::Value, + compiler_version: String, + contract_identifier: String, + #[serde(skip_serializing_if = "Option::is_none")] + creation_transaction_hash: Option, } #[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SourcifyVerificationResponse { - result: Vec, + verification_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SourcifyJobResponse { + is_job_completed: bool, + contract: SourcifyContractResponse, + error: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SourcifyContractResponse { + #[serde(rename = "match")] + match_status: Option, + creation_match: Option, + runtime_match: Option, } #[derive(Debug, Deserialize)] -pub struct SourcifyResponseElement { - status: String, - #[serde(rename = "storageTimestamp")] - storage_timestamp: Option, +#[serde(rename_all = "camelCase")] +pub struct SourcifyErrorResponse { + custom_code: String, + message: String, } #[cfg(test)] mod tests { use super::*; + use clap::Parser; + use foundry_test_utils::forgetest_async; - #[test] - fn test_check_addresses_url() { - let url = Url::from_str("https://server-verify.hashscan.io").unwrap(); - let url = url.join("check-by-addresses?addresses=0x1234&chainIds=1").unwrap(); - assert_eq!( - url.as_str(), - "https://server-verify.hashscan.io/check-by-addresses?addresses=0x1234&chainIds=1" - ); - } + forgetest_async!(creates_correct_verify_request_body, |prj, _cmd| { + prj.add_source("Counter", "contract Counter {}"); + + let args = VerifyArgs::parse_from([ + "foundry-cli", + "0xd8509bee9c9bf012282ad33aba0d87241baf5064", + "src/Counter.sol:Counter", + "--compiler-version", + "0.8.19", + "--root", + &prj.root().to_string_lossy(), + ]); + + let context = args.resolve_context().await.unwrap(); + let provider = SourcifyVerificationProvider::default(); + let request = provider.prepare_verify_request(&args, &context).await.unwrap(); + + assert_eq!(request.compiler_version, "0.8.19+commit.7dd6d404"); + assert_eq!(request.contract_identifier, "src/Counter.sol:Counter"); + assert!(request.creation_transaction_hash.is_none()); + + assert!(request.std_json_input.is_object()); + let json_obj = request.std_json_input.as_object().unwrap(); + assert!(json_obj.contains_key("sources")); + assert!(json_obj.contains_key("settings")); + + let sources = json_obj.get("sources").unwrap().as_object().unwrap(); + assert!(sources.contains_key("src/Counter.sol")); + let counter_source = sources.get("src/Counter.sol").unwrap().as_object().unwrap(); + let content = counter_source.get("content").unwrap().as_str().unwrap(); + assert!(content.contains("contract Counter {}")); + }); } diff --git a/crates/verify/src/types.rs b/crates/verify/src/types.rs index 20d86499642d7..25269d9d47f22 100644 --- a/crates/verify/src/types.rs +++ b/crates/verify/src/types.rs @@ -25,15 +25,6 @@ impl FromStr for VerificationType { } } -impl From for String { - fn from(v: VerificationType) -> Self { - match v { - VerificationType::Full => "full".to_string(), - VerificationType::Partial => "partial".to_string(), - } - } -} - impl fmt::Display for VerificationType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 03eef1ff789ae..624d6b76980f3 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -1,27 +1,34 @@ use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; use alloy_dyn_abi::DynSolValue; +use alloy_evm::EvmEnv; use alloy_primitives::{Address, Bytes, TxKind}; -use alloy_provider::{network::AnyRpcBlock, Provider}; +use alloy_provider::{ + Provider, + network::{AnyNetwork, AnyRpcBlock}, +}; use alloy_rpc_types::BlockId; use clap::ValueEnum; use eyre::{OptionExt, Result}; use foundry_block_explorers::{ contract::{ContractCreationData, ContractMetadata, Metadata}, errors::EtherscanError, + utils::lookup_compiler_version, }; -use foundry_common::{ - abi::encode_args, compile::ProjectCompiler, ignore_metadata_hash, provider::RetryProvider, - shell, -}; +use foundry_common::{abi::encode_args, compile::ProjectCompiler, ignore_metadata_hash, shell}; use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; use foundry_evm::{ - constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts, - traces::TraceMode, Env, EnvMut, + constants::DEFAULT_CREATE2_DEPLOYER, + core::{decode::RevertDecoder, evm::EthEvmNetwork}, + executors::TracingExecutor, + opts::EvmOpts, + traces::TraceMode, + utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; +use foundry_evm_networks::NetworkConfigs; use reqwest::Url; -use revm::{bytecode::Bytecode, database::Database, primitives::hardfork::SpecId}; -use semver::Version; +use revm::{bytecode::Bytecode, context::TxEnv, database::Database, primitives::hardfork::SpecId}; +use semver::{BuildMetadata, Version}; use serde::{Deserialize, Serialize}; use yansi::Paint; @@ -36,12 +43,12 @@ pub enum BytecodeType { impl BytecodeType { /// Check if the bytecode type is creation - pub fn is_creation(&self) -> bool { + pub const fn is_creation(&self) -> bool { matches!(self, Self::Creation) } /// Check if the bytecode type is runtime - pub fn is_runtime(&self) -> bool { + pub const fn is_runtime(&self) -> bool { matches!(self, Self::Runtime) } } @@ -92,52 +99,6 @@ pub fn build_project( Ok(artifact.into_contract_bytecode()) } -pub fn build_using_cache( - args: &VerifyBytecodeArgs, - etherscan_settings: &Metadata, - config: &Config, -) -> Result { - let project = config.project()?; - let cache = project.read_cache_file()?; - let cached_artifacts = cache.read_artifacts::()?; - - for (key, value) in cached_artifacts { - let name = args.contract.name.to_owned() + ".sol"; - let version = etherscan_settings.compiler_version.to_owned(); - // Ignores vyper - if version.starts_with("vyper:") { - eyre::bail!("Vyper contracts are not supported") - } - // Parse etherscan version string - let version = version.split('+').next().unwrap_or("").trim_start_matches('v').to_string(); - - // Check if `out/directory` name matches the contract name - if key.ends_with(name.as_str()) { - let name = name.replace(".sol", ".json"); - for artifact in value.into_values().flatten() { - // Check if ABI file matches the name - if !artifact.file.ends_with(&name) { - continue; - } - - // Check if Solidity version matches - if let Ok(version) = Version::parse(&version) { - if !(artifact.version.major == version.major && - artifact.version.minor == version.minor && - artifact.version.patch == version.patch) - { - continue; - } - } - - return Ok(artifact.artifact) - } - } - } - - eyre::bail!("couldn't find cached artifact for contract {}", args.contract.name) -} - pub fn print_result( res: Option, bytecode_type: BytecodeType, @@ -146,15 +107,15 @@ pub fn print_result( config: &Config, ) { if let Some(res) = res { - if !shell::is_json() { + if shell::is_json() { + let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; + json_results.push(json_res); + } else { let _ = sh_println!( "{} with status {}", format!("{bytecode_type:?} code matched").green().bold(), res.green().bold() ); - } else { - let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; - json_results.push(json_res); } } else if !shell::is_json() { let _ = sh_err!( @@ -185,7 +146,7 @@ fn is_partial_match( // 1. Check length of constructor args if constructor_args.is_empty() || is_runtime { // Assume metadata is at the end of the bytecode - return try_extract_and_compare_bytecode(local_bytecode, bytecode) + return try_extract_and_compare_bytecode(local_bytecode, bytecode); } // If not runtime, extract constructor args from the end of the bytecode @@ -224,12 +185,12 @@ fn find_mismatch_in_settings( ); mismatches.push(str); } - if local_settings.optimizer_runs.is_some_and(|runs| etherscan_settings.runs != runs as u64) || - (local_settings.optimizer_runs.is_none() && etherscan_settings.runs > 0) + if local_settings.optimizer_runs.is_some_and(|runs| etherscan_settings.runs != runs as u64) + || (local_settings.optimizer_runs.is_none() && etherscan_settings.runs > 0) { let str = format!( "Optimizer runs mismatch: local={}, onchain={}", - local_settings.optimizer_runs.unwrap(), + local_settings.optimizer_runs.map_or("unknown".to_string(), |runs| runs.to_string()), etherscan_settings.runs ); mismatches.push(str); @@ -290,13 +251,14 @@ pub fn check_args_len( artifact: &CompactContractBytecode, args: &Bytes, ) -> Result<(), eyre::ErrReport> { - if let Some(constructor) = artifact.abi.as_ref().and_then(|abi| abi.constructor()) { - if !constructor.inputs.is_empty() && args.is_empty() { - eyre::bail!( - "Contract expects {} constructor argument(s), but none were provided", - constructor.inputs.len() - ); - } + if let Some(constructor) = artifact.abi.as_ref().and_then(|abi| abi.constructor()) + && !constructor.inputs.is_empty() + && args.is_empty() + { + eyre::bail!( + "Contract expects {} constructor argument(s), but none were provided", + constructor.inputs.len() + ); } Ok(()) } @@ -306,73 +268,90 @@ pub async fn get_tracing_executor( fork_blk_num: u64, evm_version: EvmVersion, evm_opts: EvmOpts, -) -> Result<(Env, TracingExecutor)> { +) -> Result<(EvmEnv, TxEnv, TracingExecutor)> { fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; let create2_deployer = evm_opts.create2_deployer; - let (env, fork, _chain, is_odyssey) = - TracingExecutor::get_fork_material(fork_config, evm_opts).await?; + let (evm_env, tx_env, fork, _chain, networks) = + TracingExecutor::::get_fork_material(fork_config, evm_opts).await?; - let executor = TracingExecutor::new( - env.clone(), + let executor = TracingExecutor::::new( + (evm_env.clone(), tx_env.clone()), fork, Some(fork_config.evm_version), TraceMode::Call, - is_odyssey, + networks, create2_deployer, + None, )?; - Ok((env, executor)) + Ok((evm_env, tx_env, executor)) } -pub fn configure_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { - env.block.timestamp = block.header.timestamp; - env.block.beneficiary = block.header.beneficiary; - env.block.difficulty = block.header.difficulty; - env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; +pub fn configure_env_block(evm_env: &mut EvmEnv, block: &AnyRpcBlock, config: NetworkConfigs) { + let number = evm_env.block_env.number; + evm_env.block_env = block_env_from_header(&block.header); + evm_env.block_env.number = number; + apply_chain_and_block_specific_env_changes::(evm_env, block, config); } pub fn deploy_contract( - executor: &mut TracingExecutor, - env: &Env, + executor: &mut TracingExecutor, + evm_env: &EvmEnv, + tx_env: &TxEnv, spec_id: SpecId, to: Option, ) -> Result { - let env = Env::new_with_spec_id( - env.evm_env.cfg_env.clone(), - env.evm_env.block_env.clone(), - env.tx.clone(), - spec_id, - ); + let mut evm_env = evm_env.clone(); + evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id); if to.is_some_and(|to| to.is_call()) { let TxKind::Call(to) = to.unwrap() else { unreachable!() }; if to != DEFAULT_CREATE2_DEPLOYER { - eyre::bail!("Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx."); + eyre::bail!( + "Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx." + ); } - let result = executor.transact_with_env(env)?; + let result = executor.transact_with_env(evm_env, tx_env.clone())?; trace!(transact_result = ?result.exit_reason); + + if result.reverted { + let decoded_reason = if result.result.is_empty() { + String::new() + } else { + format!(": {}", RevertDecoder::default().decode(&result.result, result.exit_reason)) + }; + eyre::bail!( + "Failed to deploy contract via CREATE2 on fork at block{decoded_reason}.\n\ + This typically happens when your local bytecode differs from what was actually deployed.\n\ + Common causes:\n\ + - Your contract source is not at the same commit used during deployment\n\ + - Cached build artifacts are stale (try `forge clean && forge build`)\n\ + - Compiler settings (optimizer, evm_version, via_ir) don't match the deployment" + ); + } + if result.result.len() != 20 { eyre::bail!( - "Failed to deploy contract on fork at block: call result is not exactly 20 bytes" + "Failed to deploy contract via CREATE2 on fork at block: deployer returned {} bytes instead of 20.\n\ + This may indicate a bytecode mismatch - ensure your source code matches the deployed contract.", + result.result.len() ); } Ok(Address::from_slice(&result.result)) } else { - let deploy_result = executor.deploy_with_env(env, None)?; + let deploy_result = executor.deploy_with_env(evm_env, tx_env.clone(), None)?; trace!(deploy_result = ?deploy_result.raw.exit_reason); Ok(deploy_result.address) } } pub async fn get_runtime_codes( - executor: &mut TracingExecutor, - provider: &RetryProvider, + executor: &mut TracingExecutor, + provider: &impl Provider, address: Address, fork_address: Address, block: Option, @@ -394,11 +373,8 @@ pub async fn get_runtime_codes( ) })?; - let onchain_runtime_code = if let Some(block) = block { - provider.get_code_at(address).block_id(BlockId::number(block)).await? - } else { - provider.get_code_at(address).await? - }; + let block_id = block.map_or_else(BlockId::latest, BlockId::number); + let onchain_runtime_code = provider.get_code_at(address).block_id(block_id).await?; Ok((fork_runtime_code, onchain_runtime_code)) } @@ -406,11 +382,57 @@ pub async fn get_runtime_codes( /// Returns `true` if the URL only consists of host. /// /// This is used to check user input url for missing /api path -#[inline] pub fn is_host_only(url: &Url) -> bool { matches!(url.path(), "/" | "") } +/// Wraps a failed verification error with guidance when `--verifier-url` looks misconfigured for +/// the Etherscan provider. Returns `err` untouched when no hint applies. +/// +/// The hint only fires when the Etherscan verifier is active: it requires an API endpoint +/// (typically `/api`). Sourcify, Blockscout, etc. accept host-only URLs, so we leave their +/// errors alone. +pub fn wrap_verifier_url_error( + err: eyre::Error, + verifier_url: Option<&str>, + using_etherscan: bool, +) -> eyre::Error { + let Some(verifier_url) = verifier_url else { return err }; + let url = match Url::parse(verifier_url) { + Ok(url) => url, + Err(url_err) => { + return err.wrap_err(format!("Invalid URL {verifier_url} provided: {url_err}")); + } + }; + if is_host_only(&url) && using_etherscan { + return err.wrap_err(format!( + "Verifier `etherscan` requires an API endpoint, but `--verifier-url` is host-only: `{verifier_url}`.\n\ + Fixes (pick one):\n\ + - Append the API path, e.g. `--verifier-url {verifier_url}/api`\n\ + - Switch verifier, e.g. `--verifier sourcify` (works with host-only URLs)" + )); + } + err +} + +/// Given any solc [Version] return a [Version] with build metadata +/// +/// # Example +/// +/// ```ignore +/// use semver::{BuildMetadata, Version}; +/// let version = Version::new(1, 2, 3); +/// let version = ensure_solc_build_metadata(version).await?; +/// assert_ne!(version.build, BuildMetadata::EMPTY); +/// ``` +pub async fn ensure_solc_build_metadata(version: Version) -> Result { + if version.build == BuildMetadata::EMPTY { + Ok(lookup_compiler_version(&version).await?) + } else { + Ok(version) + } +} + #[cfg(test)] mod tests { use super::*; @@ -421,4 +443,38 @@ mod tests { assert!(is_host_only(&Url::parse("https://blockscout.net/").unwrap())); assert!(is_host_only(&Url::parse("https://blockscout.net").unwrap())); } + + #[test] + fn wrap_verifier_url_error_passes_through_when_no_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, None, true); + assert_eq!(wrapped.to_string(), "upstream failure"); + } + + #[test] + fn wrap_verifier_url_error_adds_hint_for_host_only_etherscan_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("https://contracts.tempo.xyz"), true); + let msg = format!("{wrapped:#}"); + assert!(msg.contains("host-only"), "message: {msg}"); + assert!(msg.contains("--verifier-url https://contracts.tempo.xyz/api"), "message: {msg}"); + assert!(msg.contains("--verifier sourcify"), "message: {msg}"); + } + + /// Sourcify and other non-etherscan verifiers accept host-only URLs; we must not emit the + /// hint for them, otherwise we would mislead the user into editing a correct URL. + #[test] + fn wrap_verifier_url_error_does_not_hint_for_non_etherscan_provider() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("https://contracts.tempo.xyz"), false); + assert_eq!(wrapped.to_string(), "upstream failure"); + } + + #[test] + fn wrap_verifier_url_error_reports_invalid_url() { + let err = eyre::eyre!("upstream failure"); + let wrapped = wrap_verifier_url_error(err, Some("not a url"), true); + let msg = format!("{wrapped:#}"); + assert!(msg.contains("Invalid URL"), "message: {msg}"); + } } diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 804813c7a96e7..27f7975d283c2 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -1,28 +1,39 @@ //! The `forge verify-bytecode` command. use crate::{ + RetryArgs, etherscan::EtherscanVerificationProvider, provider::{VerificationContext, VerificationProvider, VerificationProviderType}, - utils::is_host_only, - RetryArgs, + utils::wrap_verifier_url_error, }; -use alloy_primitives::{map::HashSet, Address}; +use alloy_primitives::{Address, TxHash, map::HashSet}; use alloy_provider::Provider; -use clap::{Parser, ValueHint}; +use clap::{Parser, ValueEnum, ValueHint}; use eyre::Result; -use foundry_block_explorers::EtherscanApiVersion; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{self, LoadConfig}, }; -use foundry_common::{compile::ProjectCompiler, ContractsByArtifact}; +use foundry_common::{ContractsByArtifact, compile::ProjectCompiler}; use foundry_compilers::{artifacts::EvmVersion, compilers::solc::Solc, info::ContractInfo}; -use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config, SolcReq}; +use foundry_config::{ + Chain, Config, SolcReq, figment, impl_figment_convert, impl_figment_convert_cast, +}; use itertools::Itertools; -use reqwest::Url; use semver::BuildMetadata; use std::path::PathBuf; +/// The programming language used for smart contract development. +/// +/// This enum represents the supported contract languages for verification. +#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] +pub enum ContractLanguage { + /// Solidity programming language + Solidity, + /// Vyper programming language + Vyper, +} + /// Verification provider arguments #[derive(Clone, Debug, Parser)] pub struct VerifierArgs { @@ -37,10 +48,6 @@ pub struct VerifierArgs { /// The verifier URL, if using a custom provider. #[arg(long, help_heading = "Verifier options", env = "VERIFIER_URL")] pub verifier_url: Option, - - /// The verifier API version, if using a custom provider. - #[arg(long, help_heading = "Verifier options", env = "VERIFIER_API_VERSION")] - pub verifier_api_version: Option, } impl Default for VerifierArgs { @@ -49,7 +56,6 @@ impl Default for VerifierArgs { verifier: VerificationProviderType::Sourcify, verifier_api_key: None, verifier_url: None, - verifier_api_version: None, } } } @@ -63,7 +69,7 @@ pub struct VerifyArgs { /// The contract identifier in the form `:`. pub contract: Option, - /// The ABI-encoded constructor arguments. + /// The ABI-encoded constructor arguments. Only for Etherscan. #[arg( long, conflicts_with = "constructor_args_path", @@ -80,6 +86,10 @@ pub struct VerifyArgs { #[arg(long)] pub guess_constructor_args: bool, + /// The hash of the transaction which created the contract. Optional for Sourcify. + #[arg(long)] + pub creation_transaction_hash: Option, + /// The `solc` version to use to build the smart contract. #[arg(long, value_name = "VERSION")] pub compiler_version: Option, @@ -136,6 +146,16 @@ pub struct VerifyArgs { #[arg(long)] pub evm_version: Option, + /// Do not auto-detect the `solc` version. + #[arg(long, help_heading = "Compiler options")] + pub no_auto_detect: bool, + + /// Specify the solc version, or a path to a local solc, to build with. + /// + /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. + #[arg(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] + pub use_solc: Option, + #[command(flatten)] pub etherscan: EtherscanOpts, @@ -147,6 +167,12 @@ pub struct VerifyArgs { #[command(flatten)] pub verifier: VerifierArgs, + + /// The contract language (`solidity` or `vyper`). + /// + /// Defaults to `solidity` if none provided. + #[arg(long, value_enum)] + pub language: Option, } impl_figment_convert!(VerifyArgs); @@ -179,12 +205,17 @@ impl figment::Provider for VerifyArgs { dict.insert("via_ir".to_string(), figment::value::Value::serialize(self.via_ir)?); } - if let Some(api_key) = &self.verifier.verifier_api_key { - dict.insert("etherscan_api_key".into(), api_key.as_str().into()); + if self.no_auto_detect { + dict.insert("auto_detect_solc".to_string(), figment::value::Value::serialize(false)?); } - if let Some(api_version) = &self.verifier.verifier_api_version { - dict.insert("etherscan_api_version".into(), api_version.to_string().into()); + if let Some(ref solc) = self.use_solc { + let solc = solc.trim_start_matches("solc:"); + dict.insert("solc".to_string(), figment::value::Value::serialize(solc)?); + } + + if let Some(api_key) = &self.verifier.verifier_api_key { + dict.insert("etherscan_api_key".into(), api_key.as_str().into()); } Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) @@ -218,12 +249,20 @@ impl VerifyArgs { self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + // For chains with Sourcify-compatible APIs, use the chain's URL from etherscan_urls + if self.verifier.verifier.is_sourcify() + && self.verifier.verifier_url.is_none() + && let Some(url) = sourcify_api_url(chain) + { + self.verifier.verifier_url = Some(url); + } + if self.show_standard_json_input { let args = EtherscanVerificationProvider::default() .create_verify_request(&self, &context) .await?; sh_println!("{}", args.source)?; - return Ok(()) + return Ok(()); } let verifier_url = self.verifier.verifier_url.clone(); @@ -237,36 +276,42 @@ impl VerifyArgs { if let Some(optimizations) = &self.num_of_optimizations { sh_println!("Optimizations: {optimizations}")? } - if let Some(args) = &self.constructor_args { - if !args.is_empty() { - sh_println!("Constructor args: {args}")? - } + if let Some(args) = &self.constructor_args + && !args.is_empty() + { + sh_println!("Constructor args: {args}")? } - self.verifier.verifier.client(self.etherscan.key().as_deref())?.verify(self, context).await.map_err(|err| { - if let Some(verifier_url) = verifier_url { - match Url::parse(&verifier_url) { - Ok(url) => { - if is_host_only(&url) { - return err.wrap_err(format!( - "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" - )) - } - } - Err(url_err) => { - return err.wrap_err(format!( - "Invalid URL {verifier_url} provided: {url_err}" - )) - } - } - } - - err - }) + // `client()` picks Etherscan when `--verifier etherscan` is passed, or when + // `ETHERSCAN_API_KEY` is set and no other provider was explicitly chosen. This mirrors + // that selection closely enough to decide whether the host-only URL hint applies. + let etherscan_key = self.etherscan.key(); + let using_etherscan = self.verifier.verifier.is_etherscan() + || (etherscan_key.as_deref().is_some_and(|k| !k.is_empty()) + && !matches!( + self.verifier.verifier, + VerificationProviderType::Blockscout + | VerificationProviderType::Oklink + | VerificationProviderType::Custom + )); + self.verifier + .verifier + .client( + etherscan_key.as_deref(), + self.etherscan.chain, + self.verifier.verifier_url.is_some(), + )? + .verify(self, context) + .await + .map_err(|err| wrap_verifier_url_error(err, verifier_url.as_deref(), using_etherscan)) } /// Returns the configured verification provider pub fn verification_provider(&self) -> Result> { - self.verifier.verifier.client(self.etherscan.key().as_deref()) + self.verifier.verifier.client( + self.etherscan.key().as_deref(), + self.etherscan.chain, + self.verifier.verifier_url.is_some(), + ) } /// Resolves [VerificationContext] object either from entered contract name or by trying to @@ -303,18 +348,26 @@ impl VerifyArgs { .unwrap_or_default(); if unique_versions.is_empty() { - eyre::bail!("No matching artifact found for {}", contract.name); + eyre::bail!( + "No matching artifact found for {}. This could be due to:\n\ + - Compiler version mismatch - the contract was compiled with a different Solidity version than what's being used for verification", + contract.name + ); } else if unique_versions.len() > 1 { warn!( "Ambiguous compiler versions found in cache: {}", unique_versions.iter().join(", ") ); - eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") + eyre::bail!( + "Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag." + ) } unique_versions.into_iter().next().unwrap().to_owned() } else { - eyre::bail!("If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml") + eyre::bail!( + "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" + ) }; let settings = if let Some(profile) = &self.compilation_profile { @@ -351,19 +404,27 @@ impl VerifyArgs { .unwrap_or_default(); if profiles.is_empty() { - eyre::bail!("No matching artifact found for {}", contract.name); + eyre::bail!( + "No matching artifact found for {} with compiler version {}. This could be due to:\n\ + - Compiler version mismatch - the contract was compiled with a different Solidity version", + contract.name, + version + ); } else if profiles.len() > 1 { - eyre::bail!("Ambiguous compilation profiles found in cache: {}, please specify the profile through `--compilation-profile` flag", profiles.iter().join(", ")) + eyre::bail!( + "Ambiguous compilation profiles found in cache: {}, please specify the profile through `--compilation-profile` flag", + profiles.iter().join(", ") + ) } let profile = profiles.into_iter().next().unwrap().to_owned(); - let settings = cache.profiles.get(&profile).expect("must be present"); - - settings + cache.profiles.get(&profile).expect("must be present") } else if project.additional_settings.is_empty() { &project.settings } else { - eyre::bail!("If cache is disabled, compilation profile must be provided with `--compiler-version` option or set in foundry.toml") + eyre::bail!( + "If cache is disabled, compilation profile must be provided with `--compilation-profile` option or set in foundry.toml" + ) }; VerificationContext::new( @@ -408,6 +469,16 @@ impl VerifyArgs { ) } } + + /// Detects the language for verification from source file extension, if none provided. + pub fn detect_language(&self, ctx: &VerificationContext) -> ContractLanguage { + self.language.unwrap_or_else(|| { + match ctx.target_path.extension().and_then(|e| e.to_str()) { + Some("vy") => ContractLanguage::Vyper, + _ => ContractLanguage::Solidity, + } + }) + } } /// Check verification status arguments @@ -417,7 +488,7 @@ pub struct VerifyCheckArgs { /// /// For Etherscan - Submission GUID. /// - /// For Sourcify - Contract Address. + /// For Sourcify - Verification Job ID. pub id: String, #[command(flatten)] @@ -439,7 +510,15 @@ impl VerifyCheckArgs { "Checking verification status on {}", self.etherscan.chain.unwrap_or_default() )?; - self.verifier.verifier.client(self.etherscan.key().as_deref())?.check(self).await + self.verifier + .verifier + .client( + self.etherscan.key().as_deref(), + self.etherscan.chain, + self.verifier.verifier_url.is_some(), + )? + .check(self) + .await } } @@ -456,14 +535,25 @@ impl figment::Provider for VerifyCheckArgs { dict.insert("etherscan_api_key".into(), api_key.as_str().into()); } - if let Some(api_version) = &self.etherscan.api_version { - dict.insert("etherscan_api_version".into(), api_version.to_string().into()); - } - Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) } } +/// Returns the Sourcify-compatible API URL for chains that have one registered in `etherscan_urls`. +/// +/// Some chains register their Sourcify-compatible verification API under `etherscan_urls` in +/// alloy-chains. This function returns the properly formatted URL for such chains. +fn sourcify_api_url(chain: Chain) -> Option { + if chain.is_custom_sourcify() { + chain.etherscan_urls().map(|(api_url, _)| { + let api_url = api_url.trim_end_matches('/'); + format!("{api_url}/") + }) + } else { + None + } +} + #[cfg(test)] mod tests { use super::*; @@ -478,4 +568,18 @@ mod tests { ]); assert!(args.via_ir); } + + #[test] + fn can_parse_new_compiler_flags() { + let args: VerifyArgs = VerifyArgs::parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000000", + "src/Domains.sol:Domains", + "--no-auto-detect", + "--use", + "0.8.23", + ]); + assert!(args.no_auto_detect); + assert_eq!(args.use_solc.as_deref(), Some("0.8.23")); + } } diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml deleted file mode 100644 index fb5aa4132fd3d..0000000000000 --- a/crates/wallets/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "foundry-wallets" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -foundry-config.workspace = true - -alloy-primitives.workspace = true -alloy-signer = { workspace = true, features = ["eip712"] } -alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } -alloy-signer-ledger = { workspace = true, features = ["eip712"] } -alloy-signer-trezor.workspace = true -alloy-network.workspace = true -alloy-consensus.workspace = true -alloy-sol-types.workspace = true -alloy-dyn-abi.workspace = true - -# aws-kms -alloy-signer-aws = { workspace = true, features = ["eip712"], optional = true } -aws-config = { workspace = true, default-features = true, optional = true } -aws-sdk-kms = { workspace = true, default-features = false, optional = true } - -# gcp-kms -alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } -gcloud-sdk = { version = "0.27.1", features = [ - "google-cloud-kms-v1", - "google-longrunning", -], optional = true } - -async-trait.workspace = true -clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -derive_builder = "0.20" -eyre.workspace = true -rpassword = "7" -serde.workspace = true -thiserror.workspace = true -tracing.workspace = true -eth-keystore = "0.5.0" - -[dev-dependencies] -tokio = { workspace = true, features = ["macros"] } - -[features] -aws-kms = ["dep:alloy-signer-aws", "dep:aws-config", "dep:aws-sdk-kms"] -gcp-kms = ["dep:alloy-signer-gcp", "dep:gcloud-sdk"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs deleted file mode 100644 index 9deb037b71fb8..0000000000000 --- a/crates/wallets/src/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use alloy_primitives::hex::FromHexError; -use alloy_signer::k256::ecdsa; -use alloy_signer_ledger::LedgerError; -use alloy_signer_local::LocalSignerError; -use alloy_signer_trezor::TrezorError; - -#[cfg(feature = "aws-kms")] -use alloy_signer_aws::AwsSignerError; - -#[cfg(feature = "gcp-kms")] -use alloy_signer_gcp::GcpSignerError; - -#[derive(Debug, thiserror::Error)] -pub enum PrivateKeyError { - #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] - InvalidHex(#[from] FromHexError), - #[error("Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?")] - ExistsAsEnvVar(String), -} - -#[derive(Debug, thiserror::Error)] -pub enum WalletSignerError { - #[error(transparent)] - Local(#[from] LocalSignerError), - #[error("Failed to decrypt keystore: incorrect password")] - IncorrectKeystorePassword, - #[error(transparent)] - Ledger(#[from] LedgerError), - #[error(transparent)] - Trezor(#[from] TrezorError), - #[error(transparent)] - #[cfg(feature = "aws-kms")] - Aws(#[from] AwsSignerError), - #[error(transparent)] - #[cfg(feature = "gcp-kms")] - Gcp(#[from] GcpSignerError), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - InvalidHex(#[from] FromHexError), - #[error(transparent)] - Ecdsa(#[from] ecdsa::Error), - #[error("foundry was not built with support for {0} signer")] - UnsupportedSigner(&'static str), -} - -impl WalletSignerError { - pub fn aws_unsupported() -> Self { - Self::UnsupportedSigner("AWS KMS") - } - - pub fn gcp_unsupported() -> Self { - Self::UnsupportedSigner("Google Cloud KMS") - } -} diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs deleted file mode 100644 index e3be0971e47a9..0000000000000 --- a/crates/wallets/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! # foundry-wallets -//! -//! Utilities for working with multiple signers. - -#![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -#[macro_use] -extern crate tracing; - -pub mod error; -pub mod multi_wallet; -pub mod raw_wallet; -pub mod utils; -pub mod wallet; -pub mod wallet_signer; - -pub use multi_wallet::MultiWalletOpts; -pub use raw_wallet::RawWalletOpts; -pub use wallet::WalletOpts; -pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs deleted file mode 100644 index 39ba6fefa1480..0000000000000 --- a/crates/wallets/src/multi_wallet.rs +++ /dev/null @@ -1,521 +0,0 @@ -use crate::{ - utils, - wallet_signer::{PendingSigner, WalletSigner}, -}; -use alloy_primitives::{map::AddressHashMap, Address}; -use alloy_signer::Signer; -use clap::Parser; -use derive_builder::Builder; -use eyre::Result; -use foundry_config::Config; -use serde::Serialize; -use std::path::PathBuf; - -/// Container for multiple wallets. -#[derive(Debug, Default)] -pub struct MultiWallet { - /// Vector of wallets that require an action to be unlocked. - /// Those are lazily unlocked on the first access of the signers. - pending_signers: Vec, - /// Contains unlocked signers. - signers: AddressHashMap, -} - -impl MultiWallet { - pub fn new(pending_signers: Vec, signers: Vec) -> Self { - let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); - Self { pending_signers, signers } - } - - fn maybe_unlock_pending(&mut self) -> Result<()> { - for pending in self.pending_signers.drain(..) { - let signer = pending.unlock()?; - self.signers.insert(signer.address(), signer); - } - Ok(()) - } - - pub fn signers(&mut self) -> Result<&AddressHashMap> { - self.maybe_unlock_pending()?; - Ok(&self.signers) - } - - pub fn into_signers(mut self) -> Result> { - self.maybe_unlock_pending()?; - Ok(self.signers) - } - - pub fn add_signer(&mut self, signer: WalletSigner) { - self.signers.insert(signer.address(), signer); - } -} - -/// A macro that initializes multiple wallets -/// -/// Should be used with a [`MultiWallet`] instance -macro_rules! create_hw_wallets { - ($self:ident, $create_signer:expr, $signers:ident) => { - let mut $signers = vec![]; - - if let Some(hd_paths) = &$self.hd_paths { - for path in hd_paths { - let hw = $create_signer(Some(path), 0).await?; - $signers.push(hw); - } - } - - if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { - for index in mnemonic_indexes { - let hw = $create_signer(None, *index).await?; - $signers.push(hw); - } - } - - if $signers.is_empty() { - let hw = $create_signer(None, 0).await?; - $signers.push(hw); - } - }; -} - -/// The wallet options can either be: -/// 1. Ledger -/// 2. Trezor -/// 3. Mnemonics (via file path) -/// 4. Keystores (via file path) -/// 5. Private Keys (cleartext in CLI) -/// 6. Private Keys (interactively via secure prompt) -/// 7. AWS KMS -#[derive(Builder, Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct MultiWalletOpts { - /// The sender accounts. - #[arg( - long, - short = 'a', - help_heading = "Wallet options - raw", - value_name = "ADDRESSES", - env = "ETH_FROM", - num_args(0..), - )] - #[builder(default = "None")] - pub froms: Option>, - - /// Open an interactive prompt to enter your private key. - /// - /// Takes a value for the number of keys to enter. - #[arg( - long, - short, - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "NUM" - )] - pub interactives: u32, - - /// Use the provided private keys. - #[arg(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] - #[builder(default = "None")] - pub private_keys: Option>, - - /// Use the provided private key. - #[arg( - long, - help_heading = "Wallet options - raw", - conflicts_with = "private_keys", - value_name = "RAW_PRIVATE_KEY" - )] - #[builder(default = "None")] - pub private_key: Option, - - /// Use the mnemonic phrases of mnemonic files at the specified paths. - #[arg(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] - #[builder(default = "None")] - pub mnemonics: Option>, - - /// Use a BIP39 passphrases for the mnemonic. - #[arg(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] - #[builder(default = "None")] - pub mnemonic_passphrases: Option>, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[arg( - long = "mnemonic-derivation-paths", - alias = "hd-paths", - help_heading = "Wallet options - raw", - value_name = "PATH" - )] - #[builder(default = "None")] - pub hd_paths: Option>, - - /// Use the private key from the given mnemonic index. - /// - /// Can be used with --mnemonics, --ledger, --aws and --trezor. - #[arg( - long, - conflicts_with = "hd_paths", - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "INDEXES" - )] - pub mnemonic_indexes: Option>, - - /// Use the keystore by its filename in the given folder. - #[arg( - long = "keystore", - visible_alias = "keystores", - help_heading = "Wallet options - keystore", - value_name = "PATHS", - env = "ETH_KEYSTORE" - )] - #[builder(default = "None")] - pub keystore_paths: Option>, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename. - #[arg( - long = "account", - visible_alias = "accounts", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAMES", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_paths" - )] - #[builder(default = "None")] - pub keystore_account_names: Option>, - - /// The keystore password. - /// - /// Used with --keystore. - #[arg( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PASSWORDS" - )] - #[builder(default = "None")] - pub keystore_passwords: Option>, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[arg( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PATHS", - env = "ETH_PASSWORD" - )] - #[builder(default = "None")] - pub keystore_password_files: Option>, - - /// Use a Ledger hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] - pub aws: bool, - - /// Use Google Cloud Key Management Service. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] - pub gcp: bool, -} - -impl MultiWalletOpts { - /// Returns [MultiWallet] container configured with provided options. - pub async fn get_multi_wallet(&self) -> Result { - let mut pending = Vec::new(); - let mut signers: Vec = Vec::new(); - - if let Some(ledgers) = self.ledgers().await? { - signers.extend(ledgers); - } - if let Some(trezors) = self.trezors().await? { - signers.extend(trezors); - } - if let Some(aws_signers) = self.aws_signers().await? { - signers.extend(aws_signers); - } - if let Some(gcp_signer) = self.gcp_signers().await? { - signers.extend(gcp_signer); - } - if let Some((pending_keystores, unlocked)) = self.keystores()? { - pending.extend(pending_keystores); - signers.extend(unlocked); - } - if let Some(pks) = self.private_keys()? { - signers.extend(pks); - } - if let Some(mnemonics) = self.mnemonics()? { - signers.extend(mnemonics); - } - if self.interactives > 0 { - pending.extend(std::iter::repeat_n( - PendingSigner::Interactive, - self.interactives as usize, - )); - } - - Ok(MultiWallet::new(pending, signers)) - } - - pub fn private_keys(&self) -> Result>> { - let mut pks = vec![]; - if let Some(private_key) = &self.private_key { - pks.push(private_key); - } - if let Some(private_keys) = &self.private_keys { - for pk in private_keys { - pks.push(pk); - } - } - if !pks.is_empty() { - let wallets = pks - .into_iter() - .map(|pk| utils::create_private_key_signer(pk)) - .collect::>>()?; - Ok(Some(wallets)) - } else { - Ok(None) - } - } - - fn keystore_paths(&self) -> Result>> { - if let Some(keystore_paths) = &self.keystore_paths { - return Ok(Some(keystore_paths.iter().map(PathBuf::from).collect())); - } - if let Some(keystore_account_names) = &self.keystore_account_names { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - return Ok(Some( - keystore_account_names - .iter() - .map(|keystore_name| default_keystore_dir.join(keystore_name)) - .collect(), - )); - } - Ok(None) - } - - /// Returns all wallets read from the provided keystores arguments - /// - /// Returns `Ok(None)` if no keystore provided. - pub fn keystores(&self) -> Result, Vec)>> { - if let Some(keystore_paths) = self.keystore_paths()? { - let mut pending = Vec::new(); - let mut signers = Vec::new(); - - let mut passwords_iter = - self.keystore_passwords.clone().unwrap_or_default().into_iter(); - - let mut password_files_iter = - self.keystore_password_files.clone().unwrap_or_default().into_iter(); - - for path in &keystore_paths { - let (maybe_signer, maybe_pending) = utils::create_keystore_signer( - path, - passwords_iter.next().as_deref(), - password_files_iter.next().as_deref(), - )?; - if let Some(pending_signer) = maybe_pending { - pending.push(pending_signer); - } else if let Some(signer) = maybe_signer { - signers.push(signer); - } - } - return Ok(Some((pending, signers))); - } - Ok(None) - } - - pub fn mnemonics(&self) -> Result>> { - if let Some(ref mnemonics) = self.mnemonics { - let mut wallets = vec![]; - - let mut hd_paths_iter = self.hd_paths.clone().unwrap_or_default().into_iter(); - - let mut passphrases_iter = - self.mnemonic_passphrases.clone().unwrap_or_default().into_iter(); - - let mut indexes_iter = self.mnemonic_indexes.clone().unwrap_or_default().into_iter(); - - for mnemonic in mnemonics { - let wallet = utils::create_mnemonic_signer( - mnemonic, - passphrases_iter.next().as_deref(), - hd_paths_iter.next().as_deref(), - indexes_iter.next().unwrap_or(0), - )?; - wallets.push(wallet); - } - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn ledgers(&self) -> Result>> { - if self.ledger { - let mut args = self.clone(); - - if let Some(paths) = &args.hd_paths { - if paths.len() > 1 { - eyre::bail!("Ledger only supports one signer."); - } - args.mnemonic_indexes = None; - } - - create_hw_wallets!(args, utils::create_ledger_signer, wallets); - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn trezors(&self) -> Result>> { - if self.trezor { - create_hw_wallets!(self, utils::create_trezor_signer, wallets); - return Ok(Some(wallets)); - } - Ok(None) - } - - pub async fn aws_signers(&self) -> Result>> { - #[cfg(feature = "aws-kms")] - if self.aws { - let mut wallets = vec![]; - let aws_keys = std::env::var("AWS_KMS_KEY_IDS") - .or(std::env::var("AWS_KMS_KEY_ID"))? - .split(',') - .map(|k| k.to_string()) - .collect::>(); - - for key in aws_keys { - let aws_signer = WalletSigner::from_aws(key).await?; - wallets.push(aws_signer) - } - - return Ok(Some(wallets)); - } - - Ok(None) - } - - /// Returns a list of GCP signers if the GCP flag is set. - /// - /// The GCP signers are created from the following environment variables: - /// - GCP_PROJECT_ID: The GCP project ID. e.g. `my-project-123456`. - /// - GCP_LOCATION: The GCP location. e.g. `us-central1`. - /// - GCP_KEY_RING: The GCP key ring name. e.g. `my-key-ring`. - /// - GCP_KEY_NAME: The GCP key name. e.g. `my-key`. - /// - GCP_KEY_VERSION: The GCP key version. e.g. `1`. - /// - /// For more information on GCP KMS, see the [official documentation](https://cloud.google.com/kms/docs). - pub async fn gcp_signers(&self) -> Result>> { - #[cfg(feature = "gcp-kms")] - if self.gcp { - let mut wallets = vec![]; - - let project_id = std::env::var("GCP_PROJECT_ID")?; - let location = std::env::var("GCP_LOCATION")?; - let key_ring = std::env::var("GCP_KEY_RING")?; - let key_names = std::env::var("GCP_KEY_NAME")?; - let key_version = std::env::var("GCP_KEY_VERSION")?; - - let gcp_signer = WalletSigner::from_gcp( - project_id, - location, - key_ring, - key_names, - key_version.parse()?, - ) - .await?; - wallets.push(gcp_signer); - - return Ok(Some(wallets)); - } - - Ok(None) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::address; - use std::path::Path; - - #[test] - fn parse_keystore_args() { - let args: MultiWalletOpts = - MultiWalletOpts::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); - assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); - - std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); - let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); - assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); - - std::env::remove_var("ETH_KEYSTORE"); - } - - #[test] - fn parse_keystore_password_file() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - - let keystore_password_file = keystore.join("password-ec554").into_os_string(); - - let args: MultiWalletOpts = MultiWalletOpts::parse_from([ - "foundry-cli", - "--keystores", - keystore_file.to_str().unwrap(), - "--password-file", - keystore_password_file.to_str().unwrap(), - ]); - assert_eq!( - args.keystore_password_files, - Some(vec![keystore_password_file.to_str().unwrap().to_string()]) - ); - - let (_, unlocked) = args.keystores().unwrap().unwrap(); - assert_eq!(unlocked.len(), 1); - assert_eq!(unlocked[0].address(), address!("0xec554aeafe75601aaab43bd4621a22284db566c2")); - } - - // https://github.com/foundry-rs/foundry/issues/5179 - #[test] - fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { - let wallet_options = vec![ - ("ledger", "--mnemonic-indexes", 1), - ("trezor", "--mnemonic-indexes", 2), - ("aws", "--mnemonic-indexes", 10), - ]; - - for test_case in wallet_options { - let args: MultiWalletOpts = MultiWalletOpts::parse_from([ - "foundry-cli", - &format!("--{}", test_case.0), - test_case.1, - &test_case.2.to_string(), - ]); - - match test_case.0 { - "ledger" => assert!(args.ledger), - "trezor" => assert!(args.trezor), - "aws" => assert!(args.aws), - _ => panic!("Should have matched one of the previous wallet options"), - } - - assert_eq!( - args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], - test_case.2 - ) - } - } -} diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs deleted file mode 100644 index 3a5169cad9774..0000000000000 --- a/crates/wallets/src/raw_wallet.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::{utils, PendingSigner, WalletSigner}; -use clap::Parser; -use eyre::Result; -use serde::Serialize; - -/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. -/// The raw wallet options can either be: -/// 1. Private Key (cleartext in CLI) -/// 2. Private Key (interactively via secure prompt) -/// 3. Mnemonic (via file path) -#[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options - raw", about = None, long_about = None)] -pub struct RawWalletOpts { - /// Open an interactive prompt to enter your private key. - #[arg(long, short)] - pub interactive: bool, - - /// Use the provided private key. - #[arg(long, value_name = "RAW_PRIVATE_KEY")] - pub private_key: Option, - - /// Use the mnemonic phrase of mnemonic file at the specified path. - #[arg(long, alias = "mnemonic-path")] - pub mnemonic: Option, - - /// Use a BIP39 passphrase for the mnemonic. - #[arg(long, value_name = "PASSPHRASE")] - pub mnemonic_passphrase: Option, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[arg(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] - pub hd_path: Option, - - /// Use the private key from the given mnemonic index. - /// - /// Used with --mnemonic-path. - #[arg(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] - pub mnemonic_index: u32, -} - -impl RawWalletOpts { - /// Returns signer configured by provided parameters. - pub fn signer(&self) -> Result> { - if self.interactive { - return Ok(Some(PendingSigner::Interactive.unlock()?)); - } - if let Some(private_key) = &self.private_key { - return Ok(Some(utils::create_private_key_signer(private_key)?)); - } - if let Some(mnemonic) = &self.mnemonic { - return Ok(Some(utils::create_mnemonic_signer( - mnemonic, - self.mnemonic_passphrase.as_deref(), - self.hd_path.as_deref(), - self.mnemonic_index, - )?)); - } - Ok(None) - } -} diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs new file mode 100644 index 0000000000000..a86b568fdea2b --- /dev/null +++ b/crates/wallets/src/tempo.rs @@ -0,0 +1,196 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use alloy_signer::Signer; +use eyre::Result; +use std::path::PathBuf; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::transaction::{ + KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, +}; + +use crate::{WalletSigner, utils}; + +/// Wallet type: how this wallet was created. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// A single entry from Tempo's `keys.toml`. +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct KeyEntry { + #[serde(default)] + wallet_type: WalletType, + #[serde(default)] + wallet_address: Address, + #[serde(default)] + chain_id: u64, + #[serde(default)] + key_type: KeyType, + #[serde(default)] + key_address: Option
, + #[serde(default)] + key: Option, + #[serde(default)] + key_authorization: Option, + #[serde(default)] + expiry: Option, + #[serde(default)] + limits: Vec, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(serde::Deserialize)] +struct StoredTokenLimit { + #[allow(dead_code)] + currency: Address, + #[allow(dead_code)] + limit: String, +} + +/// The top-level structure of `~/.tempo/wallet/keys.toml`. +#[derive(serde::Deserialize)] +struct KeysFile { + #[serde(default)] + keys: Vec, +} + +/// Configuration for a Tempo access key (keychain mode). +/// +/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer +/// is an access key that signs on behalf of the root wallet. This struct carries the metadata +/// needed to construct the correct transaction. +#[derive(Debug, Clone)] +pub struct TempoAccessKeyConfig { + /// The root wallet address (the `from` address for transactions). + pub wallet_address: Address, + /// The access key's address (derived from the private key that actually signs). + pub key_address: Address, + /// Decoded key authorization for on-chain provisioning. + /// + /// When present, callers should check whether the key is already provisioned on-chain + /// (via the AccountKeychain precompile) before including this in a transaction. + pub key_authorization: Option, +} + +/// Result of looking up an address in Tempo's key store. +pub enum TempoLookup { + /// A direct (EOA) signer was found — `wallet_address == key_address`. + Direct(WalletSigner), + /// A keychain (access key) signer was found — `wallet_address != key_address`. + Keychain(WalletSigner, Box), + /// No matching entry was found. + NotFound, +} + +/// Returns the path to Tempo's keys file. +/// +/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. +fn keys_path() -> Option { + let base = std::env::var_os("TEMPO_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; + Some(base.join("wallet").join("keys.toml")) +} + +/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. +fn decode_key_authorization(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str)?; + let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; + Ok(auth) +} + +/// Looks up a signer for the given address in Tempo's `keys.toml`. +/// +/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, +/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, +/// or [`TempoLookup::NotFound`] if no entry matches. +pub fn lookup_signer(from: Address) -> Result { + let path = match keys_path() { + Some(p) if p.is_file() => p, + _ => return Ok(TempoLookup::NotFound), + }; + + let contents = std::fs::read_to_string(&path)?; + let file: KeysFile = toml::from_str(&contents)?; + + for entry in &file.keys { + if entry.wallet_address != from { + continue; + } + + let Some(key) = &entry.key else { + continue; + }; + + // Direct mode: wallet_address == key_address (or key_address is absent). + let is_direct = + entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let signer = utils::create_private_key_signer(key)?; + + if is_direct { + return Ok(TempoLookup::Direct(signer)); + } + + // Keychain mode: the key is an access key signing on behalf of wallet_address. + let key_authorization = + entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; + + let config = TempoAccessKeyConfig { + wallet_address: entry.wallet_address, + // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address + key_address: entry.key_address.unwrap(), + key_authorization, + }; + return Ok(TempoLookup::Keychain(signer, Box::new(config))); + } + + Ok(TempoLookup::NotFound) +} + +/// Signs a Tempo transaction request using an access key (keychain V2 mode). +/// +/// Bypasses the standard `EthereumWallet` signing path and instead: +/// 1. Builds the `TempoTransaction` from the request +/// 2. Computes the V2 keychain signing hash +/// 3. Signs with the access key +/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format +pub async fn sign_with_access_key( + tx_request: impl Into, + signer: &impl Signer, + wallet_address: Address, +) -> Result> { + let tx_request: TempoTransactionRequest = tx_request.into(); + let tempo_tx = tx_request + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + + Ok(buf) +} diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs deleted file mode 100644 index ab1871d6b90b0..0000000000000 --- a/crates/wallets/src/utils.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; -use alloy_primitives::{hex::FromHex, B256}; -use alloy_signer_ledger::HDPath as LedgerHDPath; -use alloy_signer_local::PrivateKeySigner; -use alloy_signer_trezor::HDPath as TrezorHDPath; -use eyre::{Context, Result}; -use foundry_config::Config; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -fn ensure_pk_not_env(pk: &str) -> Result<()> { - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into()); - } - Ok(()) -} - -/// Validates and sanitizes user inputs, returning configured [WalletSigner]. -pub fn create_private_key_signer(private_key_str: &str) -> Result { - let Ok(private_key) = B256::from_hex(private_key_str) else { - ensure_pk_not_env(private_key_str)?; - eyre::bail!("Failed to decode private key") - }; - match PrivateKeySigner::from_bytes(&private_key) { - Ok(pk) => Ok(WalletSigner::Local(pk)), - Err(err) => { - ensure_pk_not_env(private_key_str)?; - eyre::bail!("Failed to create wallet from private key: {err}") - } - } -} - -/// Creates [WalletSigner] instance from given mnemonic parameters. -/// -/// Mnemonic can be either a file path or a mnemonic phrase. -pub fn create_mnemonic_signer( - mnemonic: &str, - passphrase: Option<&str>, - hd_path: Option<&str>, - index: u32, -) -> Result { - let mnemonic = if Path::new(mnemonic).is_file() { - fs::read_to_string(mnemonic)?.replace('\n', "") - } else { - mnemonic.to_owned() - }; - - Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) -} - -/// Creates [WalletSigner] instance from given Ledger parameters. -pub async fn create_ledger_signer( - hd_path: Option<&str>, - mnemonic_index: u32, -) -> Result { - let derivation = if let Some(hd_path) = hd_path { - LedgerHDPath::Other(hd_path.to_owned()) - } else { - LedgerHDPath::LedgerLive(mnemonic_index as usize) - }; - - WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| { - "\ -Could not connect to Ledger device. -Make sure it's connected and unlocked, with no other desktop wallet apps open." - }) -} - -/// Creates [WalletSigner] instance from given Trezor parameters. -pub async fn create_trezor_signer( - hd_path: Option<&str>, - mnemonic_index: u32, -) -> Result { - let derivation = if let Some(hd_path) = hd_path { - TrezorHDPath::Other(hd_path.to_owned()) - } else { - TrezorHDPath::TrezorLive(mnemonic_index as usize) - }; - - WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| { - "\ -Could not connect to Trezor device. -Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." - }) -} - -pub fn maybe_get_keystore_path( - maybe_path: Option<&str>, - maybe_name: Option<&str>, -) -> Result> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - Ok(maybe_path - .map(PathBuf::from) - .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) -} - -/// Creates keystore signer from given parameters. -/// -/// If correct password or password file is provided, the keystore is decrypted and a [WalletSigner] -/// is returned. -/// -/// Otherwise, a [PendingSigner] is returned, which can be used to unlock the keystore later, -/// prompting user for password. -pub fn create_keystore_signer( - path: &PathBuf, - maybe_password: Option<&str>, - maybe_password_file: Option<&str>, -) -> Result<(Option, Option)> { - if !path.exists() { - eyre::bail!("Keystore file `{path:?}` does not exist") - } - - if path.is_dir() { - eyre::bail!( - "Keystore path `{path:?}` is a directory. Please specify the keystore file directly." - ) - } - - let password = match (maybe_password, maybe_password_file) { - (Some(password), _) => Ok(Some(password.to_string())), - (_, Some(password_file)) => { - let password_file = Path::new(password_file); - if !password_file.is_file() { - Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) - } else { - Ok(Some( - fs::read_to_string(password_file) - .wrap_err_with(|| { - format!("Failed to read keystore password file at {password_file:?}") - })? - .trim_end() - .to_string(), - )) - } - } - (None, None) => Ok(None), - }?; - - if let Some(password) = password { - let wallet = PrivateKeySigner::decrypt_keystore(path, password) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; - Ok((Some(WalletSigner::Local(wallet)), None)) - } else { - Ok((None, Some(PendingSigner::Keystore(path.clone())))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_private_key_signer() { - let pk = B256::random(); - let pk_str = pk.to_string(); - assert!(create_private_key_signer(&pk_str).is_ok()); - // skip 0x - assert!(create_private_key_signer(&pk_str[2..]).is_ok()); - } -} diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs deleted file mode 100644 index 8e3a4dafb332a..0000000000000 --- a/crates/wallets/src/wallet.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::{raw_wallet::RawWalletOpts, utils, wallet_signer::WalletSigner}; -use alloy_primitives::Address; -use clap::Parser; -use eyre::Result; -use serde::Serialize; - -/// The wallet options can either be: -/// 1. Raw (via private key / mnemonic file, see `RawWallet`) -/// 2. Ledger -/// 3. Trezor -/// 4. Keystore (via file path) -/// 5. AWS KMS -/// 6. Google Cloud KMS -#[derive(Clone, Debug, Default, Serialize, Parser)] -#[command(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct WalletOpts { - /// The sender account. - #[arg( - long, - short, - value_name = "ADDRESS", - help_heading = "Wallet options - raw", - env = "ETH_FROM" - )] - pub from: Option
, - - #[command(flatten)] - pub raw: RawWalletOpts, - - /// Use the keystore in the given folder or file. - #[arg( - long = "keystore", - help_heading = "Wallet options - keystore", - value_name = "PATH", - env = "ETH_KEYSTORE" - )] - pub keystore_path: Option, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[arg( - long = "account", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAME", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_path" - )] - pub keystore_account_name: Option, - - /// The keystore password. - /// - /// Used with --keystore. - #[arg( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD" - )] - pub keystore_password: Option, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[arg( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD_FILE", - env = "ETH_PASSWORD" - )] - pub keystore_password_file: Option, - - /// Use a Ledger hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[arg(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] - pub aws: bool, - - /// Use Google Cloud Key Management Service. - #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] - pub gcp: bool, -} - -impl WalletOpts { - pub async fn signer(&self) -> Result { - trace!("start finding signer"); - - let signer = if self.ledger { - utils::create_ledger_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) - .await? - } else if self.trezor { - utils::create_trezor_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) - .await? - } else if self.aws { - let key_id = std::env::var("AWS_KMS_KEY_ID")?; - WalletSigner::from_aws(key_id).await? - } else if self.gcp { - let project_id = std::env::var("GCP_PROJECT_ID")?; - let location = std::env::var("GCP_LOCATION")?; - let keyring = std::env::var("GCP_KEYRING")?; - let key_name = std::env::var("GCP_KEY_NAME")?; - let key_version = std::env::var("GCP_KEY_VERSION")?.parse()?; - WalletSigner::from_gcp(project_id, location, keyring, key_name, key_version).await? - } else if let Some(raw_wallet) = self.raw.signer()? { - raw_wallet - } else if let Some(path) = utils::maybe_get_keystore_path( - self.keystore_path.as_deref(), - self.keystore_account_name.as_deref(), - )? { - let (maybe_signer, maybe_pending) = utils::create_keystore_signer( - &path, - self.keystore_password.as_deref(), - self.keystore_password_file.as_deref(), - )?; - if let Some(pending) = maybe_pending { - pending.unlock()? - } else if let Some(signer) = maybe_signer { - signer - } else { - unreachable!() - } - } else { - eyre::bail!( - "\ -Error accessing local wallet. Did you set a private key, mnemonic or keystore? -Run the command with --help flag for more information or use the corresponding CLI -flag to set your key via: ---private-key, --mnemonic-path, --aws, --gcp, --interactive, --trezor or --ledger. -Alternatively, when using the `cast send` or `cast mktx` commands with a local node -or RPC that has unlocked accounts, the --unlocked or --ethsign flags can be used, -respectively. The sender address can be specified by setting the `ETH_FROM` environment -variable to the desired unlocked account address, or by providing the address directly -using the --from flag." - ) - }; - - Ok(signer) - } -} - -impl From for WalletOpts { - fn from(options: RawWalletOpts) -> Self { - Self { raw: options, ..Default::default() } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_signer::Signer; - use std::{path::Path, str::FromStr}; - - #[tokio::test] - async fn find_keystore() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - let password_file = keystore.join("password-ec554"); - let wallet: WalletOpts = WalletOpts::parse_from([ - "foundry-cli", - "--from", - "560d246fcddc9ea98a8b032c9a2f474efb493c28", - "--keystore", - keystore_file.to_str().unwrap(), - "--password-file", - password_file.to_str().unwrap(), - ]); - let signer = wallet.signer().await.unwrap(); - assert_eq!( - signer.address(), - Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() - ); - } - - #[tokio::test] - async fn illformed_private_key_generates_user_friendly_error() { - let wallet = WalletOpts { - raw: RawWalletOpts { - interactive: false, - private_key: Some("123".to_string()), - mnemonic: None, - mnemonic_passphrase: None, - hd_path: None, - mnemonic_index: 0, - }, - from: None, - keystore_path: None, - keystore_account_name: None, - keystore_password: None, - keystore_password_file: None, - ledger: false, - trezor: false, - aws: false, - gcp: false, - }; - match wallet.signer().await { - Ok(_) => { - panic!("illformed private key shouldn't decode") - } - Err(x) => { - assert!( - x.to_string().contains("Failed to decode private key"), - "Error message is not user-friendly" - ); - } - } - } -} diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs deleted file mode 100644 index a5d5b70843e5f..0000000000000 --- a/crates/wallets/src/wallet_signer.rs +++ /dev/null @@ -1,284 +0,0 @@ -use crate::error::WalletSignerError; -use alloy_consensus::SignableTransaction; -use alloy_dyn_abi::TypedData; -use alloy_network::TxSigner; -use alloy_primitives::{hex, Address, ChainId, Signature, B256}; -use alloy_signer::Signer; -use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; -use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}; -use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; -use alloy_sol_types::{Eip712Domain, SolStruct}; -use async_trait::async_trait; -use std::path::PathBuf; - -#[cfg(feature = "aws-kms")] -use {alloy_signer_aws::AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient}; - -#[cfg(feature = "gcp-kms")] -use { - alloy_signer_gcp::{GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier}, - gcloud_sdk::{ - google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, - GoogleApi, - }, -}; - -pub type Result = std::result::Result; - -/// Wrapper enum around different signers. -#[derive(Debug)] -pub enum WalletSigner { - /// Wrapper around local wallet. e.g. private key, mnemonic - Local(PrivateKeySigner), - /// Wrapper around Ledger signer. - Ledger(LedgerSigner), - /// Wrapper around Trezor signer. - Trezor(TrezorSigner), - /// Wrapper around AWS KMS signer. - #[cfg(feature = "aws-kms")] - Aws(AwsSigner), - /// Wrapper around Google Cloud KMS signer. - #[cfg(feature = "gcp-kms")] - Gcp(GcpSigner), -} - -impl WalletSigner { - pub async fn from_ledger_path(path: LedgerHDPath) -> Result { - let ledger = LedgerSigner::new(path, None).await?; - Ok(Self::Ledger(ledger)) - } - - pub async fn from_trezor_path(path: TrezorHDPath) -> Result { - let trezor = TrezorSigner::new(path, None).await?; - Ok(Self::Trezor(trezor)) - } - - pub async fn from_aws(key_id: String) -> Result { - #[cfg(feature = "aws-kms")] - { - let config = aws_config::load_defaults(BehaviorVersion::latest()).await; - let client = AwsClient::new(&config); - - Ok(Self::Aws(AwsSigner::new(client, key_id, None).await?)) - } - - #[cfg(not(feature = "aws-kms"))] - { - let _ = key_id; - Err(WalletSignerError::aws_unsupported()) - } - } - - pub async fn from_gcp( - project_id: String, - location: String, - keyring: String, - key_name: String, - key_version: u64, - ) -> Result { - #[cfg(feature = "gcp-kms")] - { - let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring); - let client = match GoogleApi::from_function( - KeyManagementServiceClient::new, - "https://cloudkms.googleapis.com", - None, - ) - .await - { - Ok(c) => c, - Err(e) => return Err(WalletSignerError::from(GcpSignerError::GoogleKmsError(e))), - }; - - let specifier = KeySpecifier::new(keyring, &key_name, key_version); - - Ok(Self::Gcp(GcpSigner::new(client, specifier, None).await?)) - } - - #[cfg(not(feature = "gcp-kms"))] - { - let _ = project_id; - let _ = location; - let _ = keyring; - let _ = key_name; - let _ = key_version; - Err(WalletSignerError::gcp_unsupported()) - } - } - - pub fn from_private_key(private_key: &B256) -> Result { - Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?)) - } - - /// Returns a list of addresses available to use with current signer - /// - /// - for Ledger and Trezor signers the number of addresses to retrieve is specified as argument - /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy - /// derivation paths - /// - for Local and AWS signers the result contains a single address - pub async fn available_senders(&self, max: usize) -> Result> { - let mut senders = Vec::new(); - match self { - Self::Local(local) => { - senders.push(local.address()); - } - Self::Ledger(ledger) => { - for i in 0..max { - if let Ok(address) = - ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await - { - senders.push(address); - } - } - for i in 0..max { - if let Ok(address) = - ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await - { - senders.push(address); - } - } - } - Self::Trezor(trezor) => { - for i in 0..max { - if let Ok(address) = - trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await - { - senders.push(address); - } - } - } - #[cfg(feature = "aws-kms")] - Self::Aws(aws) => { - senders.push(alloy_signer::Signer::address(aws)); - } - #[cfg(feature = "gcp-kms")] - Self::Gcp(gcp) => { - senders.push(alloy_signer::Signer::address(gcp)); - } - } - Ok(senders) - } - - pub fn from_mnemonic( - mnemonic: &str, - passphrase: Option<&str>, - derivation_path: Option<&str>, - index: u32, - ) -> Result { - let mut builder = MnemonicBuilder::::default().phrase(mnemonic); - - if let Some(passphrase) = passphrase { - builder = builder.password(passphrase) - } - - builder = if let Some(hd_path) = derivation_path { - builder.derivation_path(hd_path)? - } else { - builder.index(index)? - }; - - Ok(Self::Local(builder.build()?)) - } -} - -macro_rules! delegate { - ($s:ident, $inner:ident => $e:expr) => { - match $s { - Self::Local($inner) => $e, - Self::Ledger($inner) => $e, - Self::Trezor($inner) => $e, - #[cfg(feature = "aws-kms")] - Self::Aws($inner) => $e, - #[cfg(feature = "gcp-kms")] - Self::Gcp($inner) => $e, - } - }; -} - -#[async_trait] -impl Signer for WalletSigner { - /// Signs the given hash. - async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_hash(hash)).await - } - - async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_message(message)).await - } - - fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) - } - - fn chain_id(&self) -> Option { - delegate!(self, inner => inner.chain_id()) - } - - fn set_chain_id(&mut self, chain_id: Option) { - delegate!(self, inner => inner.set_chain_id(chain_id)) - } - - async fn sign_typed_data( - &self, - payload: &T, - domain: &Eip712Domain, - ) -> alloy_signer::Result - where - Self: Sized, - { - delegate!(self, inner => inner.sign_typed_data(payload, domain)).await - } - - async fn sign_dynamic_typed_data( - &self, - payload: &TypedData, - ) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await - } -} - -#[async_trait] -impl TxSigner for WalletSigner { - fn address(&self) -> Address { - delegate!(self, inner => alloy_signer::Signer::address(inner)) - } - - async fn sign_transaction( - &self, - tx: &mut dyn SignableTransaction, - ) -> alloy_signer::Result { - delegate!(self, inner => inner.sign_transaction(tx)).await - } -} - -/// Signers that require user action to be obtained. -#[derive(Debug, Clone)] -pub enum PendingSigner { - Keystore(PathBuf), - Interactive, -} - -impl PendingSigner { - pub fn unlock(self) -> Result { - match self { - Self::Keystore(path) => { - let password = rpassword::prompt_password("Enter keystore password:")?; - match PrivateKeySigner::decrypt_keystore(path, password) { - Ok(signer) => Ok(WalletSigner::Local(signer)), - Err(e) => match e { - // Catch the `MacMismatch` error, which indicates an incorrect password and - // return a more user-friendly `IncorrectKeystorePassword`. - alloy_signer_local::LocalSignerError::EthKeystoreError( - eth_keystore::KeystoreError::MacMismatch, - ) => Err(WalletSignerError::IncorrectKeystorePassword), - _ => Err(WalletSignerError::Local(e)), - }, - } - } - Self::Interactive => { - let private_key = rpassword::prompt_password("Enter private key:")?; - Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?) - } - } - } -} diff --git a/deny.toml b/deny.toml index be6615d08685f..0f891df4bfbfe 100644 --- a/deny.toml +++ b/deny.toml @@ -7,10 +7,10 @@ yanked = "warn" ignore = [ # https://rustsec.org/advisories/RUSTSEC-2024-0436 paste! is unmaintained "RUSTSEC-2024-0436", - # https://rustsec.org/advisories/RUSTSEC-2024-0437 protobuf! Crash due to uncontrolled recursion in protobuf crate. - "RUSTSEC-2024-0437", - # https://rustsec.org/advisories/RUSTSEC-2025-0021 gitoxide uses SHA-1 hash implementations without any collision detection, leaving it vulnerable to hash collision attacks. - "RUSTSEC-2025-0021", + # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained + "RUSTSEC-2025-0141", + # https://rustsec.org/advisories/RUSTSEC-2026-0097 rand is unsound with a custom logger + "RUSTSEC-2026-0097", ] # This section is considered when running `cargo deny check bans`. @@ -65,13 +65,14 @@ exceptions = [ # CC0 is a permissive license but somewhat unclear status for source code # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal - { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, { allow = ["CC0-1.0"], name = "dunce" }, { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, + # Rendered mdBook HTML source code includes attribution as required by CC-BY-4.0 + { allow = ["CC-BY-4.0", "MIT"], name = "font-awesome-as-a-crate" }, ] -#copyleft = "deny" +# copyleft = "deny" # See note in unicode-ident's readme! [[licenses.clarify]] @@ -98,7 +99,20 @@ unknown-registry = "warn" unknown-git = "deny" allow-git = [ "https://github.com/alloy-rs/alloy", + "https://github.com/alloy-rs/evm", + "https://github.com/foundry-rs/compilers", "https://github.com/foundry-rs/foundry-fork-db", + "https://github.com/foundry-rs/foundry-core", + "https://github.com/foundry-rs/optimism", "https://github.com/paradigmxyz/revm-inspectors", + "https://github.com/paradigmxyz/solar", "https://github.com/bluealloy/revm", + # Only for tests. + "https://github.com/rust-cli/rexpect", + # Tempo + "https://github.com/tempoxyz/tempo", + "https://github.com/tempoxyz/mpp-rs", + # Transitive dependency of Tempo + "https://github.com/paradigmxyz/reth", + "https://github.com/stevencartavia/reth", ] diff --git a/doc/doc-filelist.js b/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/doc/doc-script.js b/doc/doc-script.js new file mode 100644 index 0000000000000..7fa122605e7cb --- /dev/null +++ b/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +var sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/doc/doc-style.css b/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/docs/dev/README.md b/docs/dev/README.md index cde2a01c31d49..86b08d1ddcaf2 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -51,6 +51,7 @@ $ make pr - [Cheatcodes](./cheatcodes.md) - [Debugging](./debugging.md) - [Scripting](./scripting.md) +- [Custom Network Features](./networks.md) _Note: This is incomplete and possibly outdated_ @@ -78,6 +79,10 @@ We use [cargo-nextest][nextest] as the test runner. If `make test` passes locally, that's a good sign that CI will be green as well. +## Release Features + +Nightly/stable release builds derive their enabled functionality from the shared `RUST_FEATURES` environment variable in `.github/workflows/release.yml` and `.github/workflows/docker-publish.yml`. Keep that list aligned with the default `FEATURES` value in the root `Makefile` so published artifacts expose the same CLI surface area (wallet backends, allocators, tracers, etc.) as local builds. + [foundry-book]: https://book.getfoundry.sh [cargo-workspace]: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html [nextest]: https://nexte.st/ diff --git a/docs/dev/cheatcodes.md b/docs/dev/cheatcodes.md index 0815ca66bef50..036d85b8db9c8 100644 --- a/docs/dev/cheatcodes.md +++ b/docs/dev/cheatcodes.md @@ -123,16 +123,18 @@ These are all the attributes that can be specified on cheatcode functions: - `#[cheatcode(status = )]`: The current status of the cheatcode. E.g. whether it is stable or experimental, etc. Defaults to `Stable`. - `#[cheatcode(safety = )]`: Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an unexpected way. Defaults to the group's safety if unspecified. If the group is ambiguous, then it must be specified manually. -Multiple attributes can be specified by separating them with commas, e.g. `#[cheatcode(group = "evm", status = "unstable")]`. +Multiple attributes can be specified by separating them with commas, e.g. `#[cheatcode(group = Evm, status = Experimental)]`. ### `Cheatcode` trait This trait defines the interface that all cheatcode implementations must implement. -There are two methods that can be implemented: +There are three methods that can be implemented: -- `apply`: implemented when the cheatcode is pure and does not need to access EVM data -- `apply_stateful`: implemented when the cheatcode needs to access EVM data -- `apply_full`: implemented when the cheatcode needs to access EVM data and the EVM executor itself, for example to recursively call back into the EVM to execute an arbitrary transaction +| Method | Purpose | When to use | +|--------|---------|-------------| +| `apply` | Pure cheatcodes that don't need EVM data access | For simple state manipulations | +| `apply_stateful` | Cheatcodes that need EVM data access | For operations requiring current EVM state | +| `apply_full` | Cheatcodes that need EVM executor access | For operations requiring recursive EVM calls | Only one of these methods can be implemented. @@ -155,7 +157,7 @@ update of the files. 2. Implement the cheatcode in [`cheatcodes`] in its category's respective module. Follow the existing implementations as a guide. 3. If a struct, enum, error, or event was added to `Vm`, update [`spec::Cheatcodes::new`] 4. Update the JSON interface by running `cargo cheats` twice. This is expected to fail the first time that this is run after adding a new cheatcode; see [JSON interface](#json-interface) -5. Write an integration test for the cheatcode in [`testdata/cheats/`] +5. Write an integration test for the cheatcode in [`testdata/default/cheats/`] [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html [`cheatcodes/spec/src/vm.rs`]: ../../crates/cheatcodes/spec/src/vm.rs diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md index f72b8f4bfbae3..70ae97e56e62d 100644 --- a/docs/dev/debugging.md +++ b/docs/dev/debugging.md @@ -6,7 +6,7 @@ This is a working document intended to outline some commands contributors can us All crates use [tracing](https://docs.rs/tracing/latest/tracing/) for logging. A console formatter is installed in each binary (`cast`, `forge`, `anvil`). -By setting `RUST_LOG=` you can get a lot more info out of Forge and Cast. For example, running Forge with `RUST_LOG=forge` will emit all logs of the `cli` crate, same for Cast. +By setting `RUST_LOG=` you can get a lot more info out of Forge and Cast. For example, running Forge with `RUST_LOG=forge` will emit all logs from the `forge` package, same for Cast with `RUST_LOG=cast`. The most basic valid filter is a log level, of which these are valid: @@ -16,6 +16,6 @@ The most basic valid filter is a log level, of which these are valid: - `debug` - `trace` -Filters are explained in detail in the [`env_logger` crate docs](https://docs.rs/env_logger). +Filters are explained in detail in the [`tracing-subscriber` crate docs](https://docs.rs/tracing-subscriber). You can also use the `dbg!` macro from Rust's standard library. diff --git a/docs/dev/lintrules.md b/docs/dev/lintrules.md index 450934ab6f477..969d7effe142f 100644 --- a/docs/dev/lintrules.md +++ b/docs/dev/lintrules.md @@ -5,60 +5,131 @@ It helps enforce best practices and improve code quality within Foundry projects ## Architecture -The `forge-lint` system operates by analyzing Solidity source code: +The `forge-lint` system operates by analyzing Solidity source code through a dual-pass system: -1. **Parsing**: Solidity source files are parsed into an Abstract Syntax Tree (AST) using `solar-parse`. This AST represents the structure of the code. -2. **AST Traversal**: The generated AST is then traversed using a Visitor pattern. The `EarlyLintVisitor` is responsible for walking through the AST nodes. -3. **Applying Lint Passes**: As the visitor encounters different AST nodes (like functions, expressions, variable definitions), it invokes registered "lint passes" (`EarlyLintPass` implementations). Each pass is designed to check for a specific code pattern. -4. **Emitting Diagnostics**: If a lint pass identifies a violation of its rule, it uses the `LintContext` to emit a diagnostic (either `warning` or `note`) that pinpoints the issue in the source code. +1. **Parsing**: Solidity source files are parsed into an Abstract Syntax Tree (AST) using `solar`. This AST represents the syntactic structure of the code. +2. **HIR Generation**: The AST is then lowered into a High-level Intermediate Representation (HIR) that includes type information and semantic analysis. +3. **Early Lint Passes**: The `EarlyLintVisitor` traverses the AST, invoking registered "early lint passes" (`EarlyLintPass` implementations) for syntax-level checks. +4. **Late Lint Passes**: The `LateLintVisitor` traverses the HIR, invoking registered "late lint passes" (`LateLintPass` implementations) for semantic analysis. +5. **Emitting Diagnostics**: If a lint pass identifies a violation, it uses the `LintContext` to emit a diagnostic (either `warning` or `note`) that pinpoints the issue. Lints can also provide code fix suggestions through the `Suggestion` API, which integrates with solar's diagnostic system to support different applicability levels. ### Key Components -* **`Linter` Trait**: Defines a generic interface for linters. `SolidityLinter` is the concrete implementation tailored for Solidity. -* **`Lint` Trait & `SolLint` Struct**: - * `Lint`: A trait that defines the essential properties of a lint rule, such as its unique ID, severity, description, and an optional help message/URL. - * `SolLint`: A struct implementing the `Lint` trait, used to hold the metadata for each specific Solidity lint rule. -* **`EarlyLintPass<'ast>` Trait**: Lints that operate directly on AST nodes implement this trait. It contains methods (like `check_expr`, `check_item_function`, etc.) called by the visitor. -* **`LintContext<'s>`**: Provides contextual information to lint passes during execution, such as access to the session for emitting diagnostics. -* **`EarlyLintVisitor<'a, 's, 'ast>`**: The core visitor that traverses the AST and dispatches checks to the registered `EarlyLintPass` instances. +- **`Linter` Trait**: Defines a generic interface for linters. `SolidityLinter` is the concrete implementation tailored for Solidity. +- **`Lint` Trait & `SolLint` Struct**: + - `Lint`: A trait that defines the essential properties of a lint rule, such as its unique ID, severity, description, and an optional help message/URL. + - `SolLint`: A struct implementing the `Lint` trait, used to hold the metadata for each specific Solidity lint rule. +- **`EarlyLintPass<'ast>` Trait**: Lints that operate directly on AST nodes implement this trait. It contains methods (like `check_expr`, `check_item_function`, etc.) called by the AST visitor. +- **`LateLintPass<'hir>` Trait**: Lints that require type information and semantic analysis implement this trait. It contains methods (like `check_contract`, `check_function`, etc.) called by the HIR visitor. +- **`LintContext<'s>`**: Provides contextual information to lint passes during execution, such as access to the session for emitting diagnostics and methods for emitting suggestions. +- **`EarlyLintVisitor<'a, 's, 'ast>`**: The visitor that traverses the AST and dispatches checks to the registered `EarlyLintPass` instances. +- **`LateLintVisitor<'a, 's, 'hir>`**: The visitor that traverses the HIR and dispatches checks to the registered `LateLintPass` instances. +- **`Suggestion` Struct**: Represents code fix suggestions with different kinds (fix or example) and applicability levels, integrated with solar's diagnostic system. ## Developing a new lint rule +We recommend you start by writing out some Solidity code that you want to trigger a lint in [`crates/lint/testdata`](https://github.com/foundry-rs/foundry/tree/master/crates/lint/testdata). Name the file after your lint rule. + +Next, choose whether you want an [early or late lint pass](#choosing-between-early-and-late-passes). If your lint is early, you can use use [Solar](https://github.com/paradigmxyz/solar) to dump the AST and find the patterns you need to match on in your lint code using `solar -Zdump=ast crates/lint/testdata/`. If your lint is late, you can use `solar -Zdump=hir crates/lint/testdata/`. + 1. Specify an issue that is being addressed in the PR description. 2. In your PR: - * Create a static `SolLint` instance using the `declare_forge_lint!` to define its metadata. - ```rust - declare_forge_lint!( - MIXED_CASE_FUNCTION, // The Rust identifier for this SolLint static - Severity::Info, // The default severity of the lint - "mixed-case-function", // A unique string ID for configuration/CLI - "function names should use mixedCase", // A brief description - "https://docs.soliditylang.org/en/latest/style-guide.html#function-names" // Optional help link - ); - ``` - - * Register the pass struct and the lint using `register_lints!` in the `mod.rs` of its corresponding severity category. This macro generates the necessary boilerplate to make them discoverable by the linter, and creates helper functions to instantiate them. - ```rust - register_lints!( - (PascalCaseStruct, (PASCAL_CASE_STRUCT)), - (MixedCaseVariable, (MIXED_CASE_VARIABLE)), - (MixedCaseFunction, (MIXED_CASE_FUNCTION)) - ); - // The structs `PascalCaseStruct`, `MixedCaseVariable`, etc., would have to manually implement `EarlyLintPass`. - ``` - - * Implement the `EarlyLintPass` trait logic for the pass struct. Do it in a new file within the relevant severity module (e.g., `src/sol/med/my_new_lint.rs`). + +- Create a static `SolLint` instance using the `declare_forge_lint!` to define its metadata. + ```rust + declare_forge_lint!( + MIXED_CASE_FUNCTION, // The Rust identifier for this SolLint static + Severity::Info, // The default severity of the lint + "mixed-case-function", // A unique string ID for configuration/CLI + "function names should use mixedCase" // A brief description + ); + // Note: The macro automatically generates a help link to the Foundry book + ``` + +- Register the pass struct and the lint using `register_lints!` in the `mod.rs` of its corresponding severity category. Specify the pass type (`early`, `late`, or both). Note that a single pass can handle multiple lints: + ```rust + register_lints!( + (PascalCaseStruct, early, (PASCAL_CASE_STRUCT)), + (MixedCaseVariable, early, (MIXED_CASE_VARIABLE)), + (MixedCaseFunction, early, (MIXED_CASE_FUNCTION)), + (ScreamingSnakeCase, early, (SCREAMING_SNAKE_CASE_CONSTANT, SCREAMING_SNAKE_CASE_IMMUTABLE)), + (AsmKeccak256, late, (ASM_KECCAK256)) + ); + // The macro automatically generates the pass structs and helper functions + ``` + +- Implement the appropriate trait logic (`EarlyLintPass` or `LateLintPass`) for your lint. Do it in a new file within the relevant severity module (e.g., `src/sol/med/my_new_lint.rs`). + +- Add a markdown documentation file for the lint at `crates/lint/docs/.md`. The file is referenced by the lint's `help` URL (`https://getfoundry.sh/forge/linting/`) and is consumed by the [Foundry book](https://github.com/foundry-rs/book) to render the lint reference page. Use [`crates/lint/docs/_template.md`](../../crates/lint/docs/_template.md) as a starting point. The presence of this file is enforced by the `registered_lints_have_docs` unit test in `crates/lint/src/sol/mod.rs`. + +### Choosing Between Early and Late Passes + +- **Use `EarlyLintPass`** for: + - Syntax-level checks (naming conventions, formatting) + - Simple pattern matching that doesn't require type information + - Lints that can be determined from the AST alone + +- **Use `LateLintPass`** for: + - Semantic analysis requiring type information + - Cross-reference checks between different parts of the code + - Complex patterns that need to understand the actual behavior + - Avoiding false positives through type-aware analysis + +### Providing Code Fix Suggestions + +Lints can provide actionable code fix suggestions using the `emit_with_suggestion` method. The `Suggestion` API integrates with solar's diagnostic system and supports different applicability levels: + +```rust +use solar::interface::diagnostics::Applicability; + +// Example: Suggesting a machine-applicable fix +cx.emit_with_suggestion( + lint, + node.span, + Suggestion::fix( + corrected_name, + Applicability::MachineApplicable, + ) + .with_desc("consider using") +); + +// Example: Suggesting a fix with a specific span +cx.emit_with_suggestion( + lint, + node.span, + Suggestion::fix( + optimized_code, + Applicability::MaybeIncorrect, + ) + .with_desc("use inline assembly for gas optimization") + .with_span(replacement_span) +); + +// Example: Providing an example (non-applicable suggestion) +cx.emit_with_suggestion( + lint, + node.span, + Suggestion::example("some example") +); +``` + +**Applicability Levels:** +- `MachineApplicable`: The suggestion can be applied automatically with high confidence +- `MaybeIncorrect`: The suggestion might not be correct in all cases and should be reviewed +- `HasPlaceholders`: The suggestion contains placeholders that need to be filled in +- `Unspecified`: No applicability specified 3. Add comprehensive tests in `lint/testdata/`: - * Create `MyNewLint.sol` with various examples (triggering and non-triggering cases, edge cases). - * Generate the corresponding blessed file with the expected output. + - Create `MyNewLint.sol` with various examples (triggering and non-triggering cases, edge cases). + - If your test requires imports, add those files under `lint/testdata/auxiliary/` so that the ui runner doesn't lint them. + - Generate the corresponding blessed file with the expected output. ### Testing a lint rule Tests are located in the `lint/testdata/` directory. A test for a lint rule involves: - - A Solidity source file with various code snippets, some of which are expected to trigger the lint. Expected diagnostics must be indicated with either `//~WARN: description` or `//~NOTE: description` on the relevant line. - - corresponding `.stderr` (blessed) file which contains the exact diagnostic output the linter is expected to produce for that source file. +- A Solidity source file with various code snippets, some of which are expected to trigger the lint. Expected diagnostics must be indicated with either `//~WARN: description` or `//~NOTE: description` on the relevant line. +- corresponding `.stderr` (blessed) file which contains the exact diagnostic output the linter is expected to produce for that source file. The testing framework runs the linter on the `.sol` file and compares its standard error output against the content of the `.stderr` file to ensure correctness. @@ -74,5 +145,5 @@ The testing framework runs the linter on the `.sol` file and compares its standa - If you need to generate / bless (re-generate) the output files: ```sh // using the default cargo cmd for running tests - cargo test -p forge --test ui -- --bless + cargo bless-lints ``` diff --git a/docs/dev/networks.md b/docs/dev/networks.md new file mode 100644 index 0000000000000..a496a9842e310 --- /dev/null +++ b/docs/dev/networks.md @@ -0,0 +1,6 @@ +# Custom Network Features + +Foundry's `anvil`, `forge` and `cast` tools can be customized with specific network features (currently with custom +precompiles, with planned support for custom transaction types). + +For supporting custom features of a network, please check out documentation and examples within [`evm-networks`](../../crates/evm/networks) crate. diff --git a/docs/dev/scripting.md b/docs/dev/scripting.md index cdface73a28e5..92b65a03ab561 100644 --- a/docs/dev/scripting.md +++ b/docs/dev/scripting.md @@ -44,7 +44,6 @@ graph TD; MultiChainSequence::new-->ScriptArgs::multi_chain_deployment ScriptSequence::new-->ScriptArgs::single_deployment ScriptArgs::single_deployment-->ScriptArgs::send_transactions - ``` ### Notes @@ -83,7 +82,6 @@ ScriptRunner::script--"run()"-->Executor::call; end end Executor::call-. BroadcastableTransactions .->ScriptArgs::handle_broadcastable_transactions; - ``` ## Nonce Management diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000000000..bf3ea43b41a11 --- /dev/null +++ b/dprint.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://raw.githubusercontent.com/dprint/dprint/refs/heads/main/website/src/assets/schemas/v0.json", + "indentWidth": 2, + "useTabs": false, + "excludes": [ + "!dprint.json", + "!npm/**/*.{json,md,toml}", + "!npm/**/*.{js,cjs,mjs,d.ts,d.cts,d.mts,ts,tsx,jsx}", + "**/_", + "**/abi", + "**/build", + "**/target", + "**/test/**", + "**/*.min.*", + "**/dist/**", + "testdata/**", + "**/tests/**", + "node_modules", + "changelog.json", + "**/test-data/**", + "**/cheatcodes/**", + "**/node_modules/**", + ".github/scripts/**", + ".github/ISSUE_TEMPLATE/**" + ], + "plugins": [ + "https://plugins.dprint.dev/toml-0.7.0.wasm", + "https://plugins.dprint.dev/json-0.21.0.wasm", + "https://plugins.dprint.dev/markdown-0.20.0.wasm", + "https://plugins.dprint.dev/dockerfile-0.3.3.wasm", + "https://plugins.dprint.dev/typescript-0.95.11.wasm", + "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm" + ], + "markdown": { + "lineWidth": 100, + "textWrap": "maintain" + }, + "toml": { + "lineWidth": 100 + }, + "json": { + "lineWidth": 1, + "indentWidth": 2, + "useTabs": false, + "trailingCommas": "never", + "preferSingleLine": false, + "array.preferSingleLine": false + }, + "typescript": { + "useTabs": false, + "semiColons": "asi", + "quoteProps": "asNeeded", + "useBraces": "preferNone", + "trailingCommas": "never", + "quoteStyle": "alwaysSingle", + "arrowFunction.useParentheses": "preferNone", + "exportDeclaration.sortTypeOnlyExports": "none", + "importDeclaration.sortTypeOnlyImports": "none" + } +} diff --git a/flake.lock b/flake.lock index 45ed6bb432ffb..343a79c0cda3d 100644 --- a/flake.lock +++ b/flake.lock @@ -1,30 +1,33 @@ { "nodes": { - "flake-utils": { + "fenix": { "inputs": { - "systems": "systems" + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "lastModified": 1777708550, + "narHash": "sha256-Qif3UXT0l5OQq8H9pRWt4/ia4gF48MWK2oHKL8uVx8U=", + "owner": "nix-community", + "repo": "fenix", + "rev": "74c1591efaff494756b8d35ebe357c6c2bbdca96", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "nix-community", + "repo": "fenix", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1719468428, - "narHash": "sha256-vN5xJAZ4UGREEglh3lfbbkIj+MPEYMuqewMn4atZFaQ=", + "lastModified": 1777641297, + "narHash": "sha256-WNGcmeOZ8Tr9dq6ztCspYbzWFswr2mPebM9LpsfGxPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1e3deb3d8a86a870d925760db1a5adecc64d329d", + "rev": "c6d65881c5624c9cae5ea6cedef24699b0c0a4c0", "type": "github" }, "original": { @@ -36,80 +39,24 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay", - "solc": "solc" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1719714047, - "narHash": "sha256-MeNPopLLv63EZj5L43j4TZkmW4wj1ouoc/h/E20sl/U=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "cb216719ce89a43dfb3d1b86a9575e89f4b727a4", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "solc": { - "inputs": { - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ], - "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" - }, - "locked": { - "lastModified": 1717442267, - "narHash": "sha256-6TnQvA6Q/xC3r1M+wGC5gnDc/5XfOPjC8X6LlGDWDNc=", - "owner": "hellwolf", - "repo": "solc.nix", - "rev": "2ac2862f224aa0d67cbc6b3246392489f8a50596", - "type": "github" - }, - "original": { - "owner": "hellwolf", - "repo": "solc.nix", - "type": "github" + "fenix": "fenix", + "nixpkgs": "nixpkgs" } }, - "solc-macos-amd64-list-json": { + "rust-analyzer-src": { "flake": false, "locked": { - "narHash": "sha256-Prwz95BgMHcWd72VwVbcH17LsV9f24K2QMcUiWUQZzI=", - "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/f743ca7/macosx-amd64/list.json" - }, - "original": { - "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/f743ca7/macosx-amd64/list.json" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1777639980, + "narHash": "sha256-6d7Hdurvbjc5uwJuc0YiK7rZBGj6Gs3uzfBFcTs+xCc=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "64cdaeb06f69b6b769a492edd88b022ae88e8ca2", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", "type": "github" } } diff --git a/flake.nix b/flake.nix index ff783b4956e4e..6ecc79752a079 100644 --- a/flake.nix +++ b/flake.nix @@ -1,52 +1,60 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs = { - nixpkgs.follows = "nixpkgs"; - }; - }; - solc = { - url = "github:hellwolf/solc.nix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; - }; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; }; }; - outputs = { self, nixpkgs, rust-overlay, flake-utils, solc }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { - inherit system; - overlays = [ rust-overlay.overlays.default solc.overlay ]; - }; - lib = pkgs.lib; - toolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ "rustfmt" "clippy" "rust-src" ]; - }; - in - { - devShells.default = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ - pkg-config - solc_0_8_23 - (solc.mkDefault pkgs solc_0_8_23) - toolchain - ]; - buildInputs = lib.optionals pkgs.stdenv.isDarwin [ - pkgs.darwin.apple_sdk.frameworks.AppKit + outputs = { self, nixpkgs, fenix }: + let eachSystem = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; + in { + devShells = eachSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ fenix.overlays.default ]; + }; + + lib = pkgs.lib; + toolchain = fenix.packages.${system}.stable.withComponents [ + "rustc" + "cargo" + "rust-std" + "clippy-preview" + "rust-analyzer-preview" + "rust-src" ]; - packages = with pkgs; [ - rust-analyzer-unwrapped + nightlyToolchain = fenix.packages.${system}.latest.withComponents [ + "rustfmt-preview" ]; + in + { + default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + pkg-config + toolchain + nightlyToolchain + + # test dependencies + solc + vyper + dprint + nodejs + ]; - # Environment variables - RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; - LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; - }; - }); + packages = with pkgs; [ rust-analyzer-unwrapped ]; + + # Remove the hardening added by nix to fix jmalloc compilation error. + # More info: https://github.com/tikv/jemallocator/issues/108 + hardeningDisable = [ "fortify" ]; + + # Environment variables + RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; + CFLAGS = "-DJEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE"; + }; + }); + }; } diff --git a/foundryup/README.md b/foundryup/README.md index 5504063abfe37..2cf61f8725227 100644 --- a/foundryup/README.md +++ b/foundryup/README.md @@ -12,18 +12,30 @@ curl -L https://foundry.paradigm.xyz | bash ## Usage -To install the **nightly** version: +To install the **latest** version: ```sh foundryup ``` -To **install** a specific **version** (in this case the `nightly` version): +Or alternatively: + +```sh +foundryup -i latest +``` + +To install the latest **nightly** version: ```sh foundryup --install nightly ``` +To install a specific version (e.g. `v1.7.0`): + +```sh +foundryup --install v1.7.0 +``` + To **list** all **versions** installed: ```sh @@ -68,7 +80,7 @@ foundryup -C 94bfdb2 To install a local directory or repository (e.g. one located at `~/git/foundry`, assuming you're in the home directory) -##### Note: --branch, --repo, and --version flags are ignored during local installations. +#### Note: --branch, --repo, and --version flags are ignored during local installations. ```sh foundryup --path ./git/foundry @@ -79,3 +91,19 @@ foundryup --path ./git/foundry **Tip**: All flags have a single character shorthand equivalent! You can use `-i` instead of `--install`, etc. --- + +## Uninstalling + +Foundry contains everything in a `.foundry` directory, usually located in `/home//.foundry/` on Linux, `/Users//.foundry/` on MacOS and `C:\Users\\.foundry` on Windows where `` is your username. + +To uninstall Foundry remove the `.foundry` directory. + +#### Warning ⚠️: .foundry directory can contain keystores. Make sure to backup any keystores you want to keep. + +Remove Foundry from PATH: + +- Optionally Foundry can be removed from editing shell configuration file (`.bashrc`, `.zshrc`, etc.). To do so remove the line that adds Foundry to PATH: + +```sh +export PATH="$PATH:/home/user/.foundry/bin" +``` diff --git a/foundryup/foundryup b/foundryup/foundryup index 9379ca5183729..5fb086a75f8c1 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -2,21 +2,29 @@ set -eo pipefail # NOTE: if you make modifications to this script, please increment the version number. -# Major / minor: incremented for each stable release of Foundry. -# Patch: incremented for each change between stable releases. -FOUNDRYUP_INSTALLER_VERSION="1.1.0" +# WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. +FOUNDRYUP_INSTALLER_VERSION="1.8.3" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} FOUNDRY_VERSIONS_DIR="$FOUNDRY_DIR/versions" FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" -FOUNDRY_BIN_URL="https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup" +FOUNDRY_BIN_URL="https://raw.githubusercontent.com/foundry-rs/foundry/HEAD/foundryup/foundryup" FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" - FOUNDRYUP_JOBS="" +FOUNDRYUP_IGNORE_VERIFICATION=false + +# Retry/backoff settings used for `fetch` (GitHub API calls). +# Recovers from transient HTTP 403/429/5xx responses returned by +# api.github.com under heavy load or per-IP rate limiting. +FOUNDRYUP_MAX_RETRIES=5 +FOUNDRYUP_RETRY_DELAY=2 +FOUNDRYUP_RETRY_MAX_TIME=60 BINS=(forge cast anvil chisel) +HASH_NAMES=() +HASH_VALUES=() export RUSTFLAGS="${RUSTFLAGS:--C target-cpu=native}" @@ -39,6 +47,8 @@ main() { -P|--pr) shift; FOUNDRYUP_PR=$1;; -C|--commit) shift; FOUNDRYUP_COMMIT=$1;; -j|--jobs) shift; FOUNDRYUP_JOBS=$1;; + -n|--network) shift; FOUNDRYUP_NETWORK=$1;; + -f|--force) FOUNDRYUP_IGNORE_VERIFICATION=true;; --arch) shift; FOUNDRYUP_ARCH=$1;; --platform) shift; FOUNDRYUP_PLATFORM=$1;; -h|--help) @@ -61,6 +71,9 @@ main() { # Print the banner after successfully parsing args banner + # Check if the foundryup installer is up to date, warn the user if not + check_installer_up_to_date + if [ -n "$FOUNDRYUP_PR" ]; then if [ -z "$FOUNDRYUP_BRANCH" ]; then FOUNDRYUP_BRANCH="refs/pull/$FOUNDRYUP_PR/head" @@ -96,76 +109,139 @@ main() { exit 0 fi + if [[ -n "$FOUNDRYUP_NETWORK" ]]; then + warn "--network is deprecated and ignored; installing the regular Foundry release" + fi + FOUNDRYUP_REPO=${FOUNDRYUP_REPO:-foundry-rs/foundry} # Install by downloading binaries if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then - FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-stable} - FOUNDRYUP_TAG=$FOUNDRYUP_VERSION - - # Normalize versions (handle channels, versions without v prefix - if [[ "$FOUNDRYUP_VERSION" =~ ^nightly ]]; then - FOUNDRYUP_VERSION="nightly" - elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then - # Add v prefix - FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}" - FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}" - fi + FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-latest} + resolve_version_and_tag say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})" - uname_s=$(uname -s) - PLATFORM=$(tolower "${FOUNDRYUP_PLATFORM:-$uname_s}") - EXT="tar.gz" - case $PLATFORM in - linux|alpine) ;; - darwin|mac*) - PLATFORM="darwin" - ;; - mingw*|win*) - EXT="zip" - PLATFORM="win32" - ;; - *) - err "unsupported platform: $PLATFORM" - ;; - esac - - uname_m=$(uname -m) - ARCHITECTURE=$(tolower "${FOUNDRYUP_ARCH:-$uname_m}") - if [ "${ARCHITECTURE}" = "x86_64" ]; then - # Redirect stderr to /dev/null to avoid printing errors if non Rosetta. - if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then - ARCHITECTURE="arm64" # Rosetta. - else - ARCHITECTURE="amd64" # Intel. - fi - elif [ "${ARCHITECTURE}" = "arm64" ] ||[ "${ARCHITECTURE}" = "aarch64" ] ; then - ARCHITECTURE="arm64" # Arm. - else - ARCHITECTURE="amd64" # Amd. - fi + # Detect platform and architecture. + detect_platform_arch # Compute the URL of the release tarball in the Foundry repository. RELEASE_URL="https://github.com/${FOUNDRYUP_REPO}/releases/download/${FOUNDRYUP_TAG}/" + ATTESTATION_URL="${RELEASE_URL}foundry_${FOUNDRYUP_VERSION}_${PLATFORM}_${ARCHITECTURE}.attestation.txt" BIN_ARCHIVE_URL="${RELEASE_URL}foundry_${FOUNDRYUP_VERSION}_${PLATFORM}_${ARCHITECTURE}.$EXT" MAN_TARBALL_URL="${RELEASE_URL}foundry_man_${FOUNDRYUP_VERSION}.tar.gz" ensure mkdir -p "$FOUNDRY_VERSIONS_DIR" + + # If `--force` is set, skip the SHA verification. + if [ "$FOUNDRYUP_IGNORE_VERIFICATION" = false ]; then + # Check if the version is already installed by downloading the attestation file. + say "checking if forge, cast, anvil, and chisel for $FOUNDRYUP_TAG version are already installed" + + # Create a temporary directory to store the attestation link and artifact. + tmp_dir="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" + tmp="$tmp_dir/attestation.txt" + ensure download "$ATTESTATION_URL" "$tmp" + + # Read the first line of the attestation file to get the artifact link. + # The first line should contain the link to the attestation artifact. + attestation_artifact_link="$(head -n1 "$tmp" | tr -d '\r')" + attestation_missing=false + + # If the attestation artifact link is empty or the file contains 'Not Found', + # we consider the attestation missing and skip the SHA verification. + if [ -z "$attestation_artifact_link" ] || grep -q 'Not Found' "$tmp"; then + attestation_missing=true + fi + + # Clean up the temporary attestation file. + rm -f "$tmp" + + if $attestation_missing; then + say "no attestation found for this release, skipping SHA verification" + else + say "found attestation for $FOUNDRYUP_TAG version, downloading attestation artifact, checking..." + + # Download the attestation artifact JSON file. + tmp="$tmp_dir/foundry-attestation.sigstore.json" + ensure download "${attestation_artifact_link}/download" "$tmp" + + # Extract the payload from the JSON file. + payload_b64=$(awk '/"payload":/ {gsub(/[",]/, "", $2); print $2; exit}' "$tmp") + payload_json=$(printf '%s' "$payload_b64" | base64 -d 2>/dev/null || printf '%s' "$payload_b64" | base64 -D) + + + # Extract the names and hashes from the payload JSON. + # The payload is expected to be a JSON array of objects with "name" and "sha256" fields. + while read -r name_line && read -r sha_line; do + name=$(echo "$name_line" | sed -nE 's/.*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p') + sha=$(echo "$sha_line" | sed -nE 's/.*"sha256"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p') + if [ -n "$name" ] && [ -n "$sha" ]; then + HASH_NAMES+=("$name") + HASH_VALUES+=("$sha") + fi + done < <(echo "$payload_json" | tr '{}' '\n' | grep -E '"name"|sha256') + + # Clean up the temporary attestation artifact. + # The hashes are now stored in the HASHES associative array. + rm -f "$tmp" + + # Check if the binaries are already installed and match the expected hashes. + # If they do, skip the download. + version_dir="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" + all_match=true + for bin in "${BINS[@]}"; do + expected="" + for i in "${!HASH_NAMES[@]}"; do + if [ "${HASH_NAMES[$i]}" = "$bin" ] || [ "${HASH_NAMES[$i]}" = "$bin.exe" ]; then + expected="${HASH_VALUES[$i]}" + break + fi + done + + path="$version_dir/$bin" + + if [ -z "$expected" ] || [ ! -x "$path" ]; then + all_match=false + break + fi + + actual=$(compute_sha256 "$path") + if [ "$actual" != "$expected" ]; then + all_match=false + break + fi + done + + if $all_match; then + say "version $FOUNDRYUP_TAG already installed and verified, activating..." + FOUNDRYUP_VERSION=$FOUNDRYUP_TAG + use + say "done!" + exit 0 + fi + fi + + # If we reach here, we need to download the binaries. + say "binaries not found or do not match expected hashes, downloading new binaries" + fi + # Download and extract the binaries archive say "downloading forge, cast, anvil, and chisel for $FOUNDRYUP_TAG version" if [ "$PLATFORM" = "win32" ]; then - tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.zip" + tmp="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" + tmp="$tmp/foundry.zip" ensure download "$BIN_ARCHIVE_URL" "$tmp" ensure unzip "$tmp" -d "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" rm -f "$tmp" else - tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.tar.gz" + tmp="$(mktemp -d 2>/dev/null)" || err "failed to create temp dir" + tmp="$tmp/foundry.tar.gz" ensure download "$BIN_ARCHIVE_URL" "$tmp" # Make sure it's a valid tar archive. - ensure tar tf $tmp 1> /dev/null + ensure tar tf "$tmp" 1> /dev/null ensure mkdir -p "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" - ensure tar -C "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" -xvf $tmp + ensure tar -C "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" -xvf "$tmp" rm -f "$tmp" fi @@ -173,17 +249,69 @@ main() { if check_cmd tar; then say "downloading manpages" mkdir -p "$FOUNDRY_MAN_DIR" - download "$MAN_TARBALL_URL" | tar -xzC "$FOUNDRY_MAN_DIR" + if ! download "$MAN_TARBALL_URL" | tar -xzC "$FOUNDRY_MAN_DIR"; then + warn "skipping manpage download: unavailable or invalid archive" + fi else say 'skipping manpage download: missing "tar"' fi + if [ "$FOUNDRYUP_IGNORE_VERIFICATION" = true ]; then + say "skipped SHA verification for downloaded binaries due to --force flag" + else + # Verify the downloaded binaries against the attestation file. + # If the attestation file was not found or is empty, we skip the verification. + if $attestation_missing; then + say "no attestation found for these binaries, skipping SHA verification for downloaded binaries" + else + say "verifying downloaded binaries against the attestation file" + + failed=false + for bin in "${BINS[@]}"; do + expected="" + for i in "${!HASH_NAMES[@]}"; do + if [ "${HASH_NAMES[$i]}" = "$bin" ] || [ "${HASH_NAMES[$i]}" = "$bin.exe" ]; then + expected="${HASH_VALUES[$i]}" + break + fi + done + + path="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG/$bin" + + if [ -z "$expected" ]; then + say "no expected hash for $bin" + failed=true + continue + fi + + if [ ! -x "$path" ]; then + say "binary $bin not found at $path" + failed=true + continue + fi + + actual=$(compute_sha256 "$path") + if [ "$actual" != "$expected" ]; then + say "$bin hash verification failed:" + say " expected: $expected" + say " actual: $actual" + failed=true + else + say "$bin verified ✓" + fi + done + + if $failed; then + err "one or more binaries failed post-installation verification" + fi + fi + fi + # Use newly installed version. FOUNDRYUP_VERSION=$FOUNDRYUP_TAG use say "done!" - # Install by cloning the repo with the provided branch/tag else need_cmd cargo @@ -253,7 +381,7 @@ The installer for Foundry. Update or revert to a specific Foundry version with ease. -By default, the latest stable version is installed from built binaries. +By default, the latest version is installed from built binaries. USAGE: foundryup @@ -262,7 +390,7 @@ OPTIONS: -h, --help Print help information -v, --version Print the version of foundryup -U, --update Update foundryup to the latest version - -i, --install Install a specific version from built binaries + -i, --install Install a specific version (latest, nightly, nightly-, or v1.2.3) -l, --list List versions installed from built binaries -u, --use Use a specific installed version from built binaries -b, --branch Build and install a specific branch @@ -271,6 +399,8 @@ OPTIONS: -r, --repo Build and install from a remote GitHub repo (uses default branch if no other options are set) -p, --path Build and install a local repository -j, --jobs Number of CPUs to use for building Foundry (default: all CPUs) + -n, --network Deprecated and ignored; installs the regular Foundry release + -f, --force Skip SHA verification for downloaded binaries (INSECURE - use with caution) --arch Install a specific architecture (supports amd64 and arm64) --platform Install a specific platform (supports win32, linux, darwin and alpine) EOF @@ -284,15 +414,36 @@ version() { update() { say "updating foundryup..." - # Download to a temporary file first + current_version="$FOUNDRYUP_INSTALLER_VERSION" + + # Download the new version. tmp_file="$(mktemp)" ensure download "$FOUNDRY_BIN_URL" "$tmp_file" - # Replace the current foundryup with the downloaded file + # Extract new version from downloaded file. + new_version=$(grep -Eo 'FOUNDRYUP_INSTALLER_VERSION="[0-9]+\.[0-9]+\.[0-9]+"' "$tmp_file" | cut -d'"' -f2) + + # If the new version could not be determined, exit gracefully. + # This prevents from upgrading to an empty or invalid version. + if [ -z "$new_version" ]; then + warn "could not determine new foundryup version. Exiting." + rm -f "$tmp_file" + exit 0 + fi + + # If the new version is not greater than the current version, skip the update. + # This is to prevent downgrades or unnecessary updates. + if ! version_gt "$new_version" "$current_version"; then + say "foundryup is already up to date (installed: $current_version, remote: $new_version)." + rm -f "$tmp_file" + exit 0 + fi + + # Overwrite existing foundryup ensure mv "$tmp_file" "$FOUNDRY_BIN_PATH" ensure chmod +x "$FOUNDRY_BIN_PATH" - say "successfully updated foundryup" + say "successfully updated foundryup: $current_version → $new_version" exit 0 } @@ -317,6 +468,18 @@ list() { use() { [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" + + # If the requested version is a channel (`latest`, `stable`, `nightly`) or a bare semver + # version (e.g. `1.7.0`, `1.6.0-rc1`), resolve it to the immutable tag directory created by + # `--install` (channels hit the GitHub API; semver versions get a `v` prefix). + # Falls back to the literal value for locally-built versions (branches, PRs, commits, custom names). + case "$FOUNDRYUP_VERSION" in + latest|stable|nightly|[0-9]*.[0-9]*.[0-9]*) + resolve_version_and_tag + FOUNDRYUP_VERSION="$FOUNDRYUP_TAG" + ;; + esac + FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" if [ -d "$FOUNDRY_VERSION_DIR" ]; then @@ -365,6 +528,15 @@ tolower() { echo "$1" | awk '{print tolower($0)}' } +compute_sha256() { + if check_cmd sha256sum; then + # Use sed to strip leading backslash (sha256sum on Windows/Git Bash outputs \ prefix for binary files) + sha256sum "$1" | cut -d' ' -f1 | sed 's/^\\//' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + need_cmd() { if ! check_cmd "$1"; then err "need '$1' (command not found)" @@ -375,6 +547,99 @@ check_cmd() { command -v "$1" &>/dev/null } +detect_platform_arch() { + uname_s=$(uname -s) + PLATFORM=$(tolower "${FOUNDRYUP_PLATFORM:-$uname_s}") + EXT="tar.gz" + case $PLATFORM in + linux|alpine) ;; + darwin|mac*) + PLATFORM="darwin" + ;; + mingw*|win*) + EXT="zip" + PLATFORM="win32" + ;; + *) + err "unsupported platform: $PLATFORM" + ;; + esac + + uname_m=$(uname -m) + ARCHITECTURE=$(tolower "${FOUNDRYUP_ARCH:-$uname_m}") + if [ "${ARCHITECTURE}" = "x86_64" ]; then + # Redirect stderr to /dev/null to avoid printing errors if non Rosetta. + if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then + ARCHITECTURE="arm64" # Rosetta. + else + ARCHITECTURE="amd64" # Intel. + fi + elif [ "${ARCHITECTURE}" = "arm64" ] ||[ "${ARCHITECTURE}" = "aarch64" ] ; then + ARCHITECTURE="arm64" # Arm. + else + ARCHITECTURE="amd64" # Amd. + fi +} + +check_installer_up_to_date() { + say "checking if foundryup is up to date..." + + remote_version=$(fetch "$FOUNDRY_BIN_URL" | grep -Eo 'FOUNDRYUP_INSTALLER_VERSION="[0-9]+\.[0-9]+\.[0-9]+"' | cut -d'"' -f2 || true) + + if [ -z "$remote_version" ]; then + warn "Could not determine remote foundryup version. Skipping version check." + return 0 + fi + + if version_gt "$remote_version" "$FOUNDRYUP_INSTALLER_VERSION"; then + printf ' +Your installation of foundryup is out of date. + +Installed: %s → Latest: %s + +To update, run: + + foundryup --update + +Updating is highly recommended as it gives you access to the latest features and bug fixes. + +' "$FOUNDRYUP_INSTALLER_VERSION" "$remote_version" >&2 + else + say "foundryup is up to date." + fi +} + +# Compares two version strings in the format "major.minor.patch". +# Returns 0 if $1 is greater than $2, 1 if $1 is less than $2, and 1 if they are equal. +# +# Assumes that the version strings are well-formed and contain three numeric components separated by dots. +# +# Example: version_gt "1.2.3" "1.2.4" +# returns 1 (1.2.3 < 1.2.4) +# version_gt "1.2.3" "1.2.3" +# returns 1 (1.2.3 == 1.2.3) +# version_gt "1.2.4" "1.2.3" +# returns 0 (1.2.4 > 1.2.3) +version_gt() { + [ "$1" = "$2" ] && return 1 + + IFS=. read -r major1 minor1 patch1 <> "$PROFILE" && echo "fish_add_path -a $FOUNDRY_BIN_DIR" >> "$PROFILE" + echo >> "$PROFILE" && echo "fish_add_path -a \"$FOUNDRY_BIN_DIR\"" >> "$PROFILE" else echo >> "$PROFILE" && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> "$PROFILE" fi diff --git a/npm/.env.example b/npm/.env.example new file mode 100644 index 0000000000000..4fe68567dfcbd --- /dev/null +++ b/npm/.env.example @@ -0,0 +1,14 @@ +NODE_ENV="development" + +NPM_TOKEN="" + +PLATFORM_NAME="" +ARCH="" + +# for testing purposes +NPM_EMAIL="" +NPM_PASSWORD="" +NPM_REGISTRY_URL="" +NPM_USER="foundry-rs" +ALLOW_NO_INTEGRITY=false +ALLOW_INSECURE_REGISTRY=false diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 0000000000000..a353c2e8d0127 --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1,41 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +forge/*/bin/forge +anvil/*/bin/anvil +cast/*/bin/cast +chisel/*/bin/chisel +@foundry-rs/*/bin/ +_ diff --git a/npm/@foundry-rs/anvil/README.md b/npm/@foundry-rs/anvil/README.md new file mode 100644 index 0000000000000..f8d84dbff0408 --- /dev/null +++ b/npm/@foundry-rs/anvil/README.md @@ -0,0 +1,52 @@ +# [Anvil](https://getfoundry.sh/anvil) + +Anvil is a fast local Ethereum development node. +The anvil binary can be used both within and outside of a Foundry project. + +## Usage + +### One-off commands + +Example + +```sh +npx --yes @foundry-rs/anvil@nightly +``` + +More generally + +```sh +npx --yes @foundry-rs/anvil@ [args...] +``` + +### Install then use + +locally to your project + +```sh +npm add @foundry-rs/anvil@nightly +npx anvil [args...] +``` + +globally + +```sh +npm add --global @foundry-rs/anvil@nightly +anvil [args...] +``` + +--- + +Also works with `deno`, `bun`, and `pnpm`: + +```sh +deno run --quiet --allow-all npm:@foundry-rs/anvil@nightly [args...] +``` + +```sh +bun x @foundry-rs/anvil@nightly [args...] +``` + +```sh +pnpm dlx --silent @foundry-rs/anvil@nightly [args...] +``` diff --git a/npm/@foundry-rs/anvil/package.json b/npm/@foundry-rs/anvil/package.json new file mode 100644 index 0000000000000..82482334266ca --- /dev/null +++ b/npm/@foundry-rs/anvil/package.json @@ -0,0 +1,42 @@ +{ + "name": "@foundry-rs/anvil", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/anvil", + "description": "Anvil is a fast local Ethereum development node", + "bin": { + "anvil": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "TARGET_TOOL=anvil node ./dist/postinstall.mjs" + }, + "optionalDependencies": { + "@foundry-rs/anvil-darwin-arm64": "0.0.0", + "@foundry-rs/anvil-darwin-amd64": "0.0.0", + "@foundry-rs/anvil-linux-arm64": "0.0.0", + "@foundry-rs/anvil-linux-amd64": "0.0.0", + "@foundry-rs/anvil-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "keywords": [ + "foundry", + "testing", + "ethereum", + "solidity", + "blockchain", + "smart-contracts" + ], + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/cast/README.md b/npm/@foundry-rs/cast/README.md new file mode 100644 index 0000000000000..86390b09a08cf --- /dev/null +++ b/npm/@foundry-rs/cast/README.md @@ -0,0 +1,53 @@ +# [Cast](https://getfoundry.sh/cast) + +Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. +You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line! +The cast binary can be used both within and outside of a Foundry project. + +## Usage + +### One-off commands + +Example + +```sh +npx --yes @foundry-rs/cast@nightly block-number +``` + +More generally + +```sh +npx --yes @foundry-rs/cast@ [args...] +``` + +### Install then use + +locally to your project + +```sh +npm add @foundry-rs/cast@nightly +npx cast [args...] +``` + +globally + +```sh +npm add --global @foundry-rs/cast@nightly +cast [args...] +``` + +--- + +Also works with `deno`, `bun`, and `pnpm`: + +```sh +deno run --quiet --allow-all npm:@foundry-rs/cast@nightly [args...] +``` + +```sh +bun x @foundry-rs/cast@nightly [args...] +``` + +```sh +pnpm dlx --silent @foundry-rs/cast@nightly [args...] +``` diff --git a/npm/@foundry-rs/cast/package.json b/npm/@foundry-rs/cast/package.json new file mode 100644 index 0000000000000..e8ab6096fe237 --- /dev/null +++ b/npm/@foundry-rs/cast/package.json @@ -0,0 +1,42 @@ +{ + "name": "@foundry-rs/cast", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/cast", + "description": "Swiss Army knife for interacting with Ethereum applications from the command line", + "bin": { + "cast": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "TARGET_TOOL=cast node ./dist/postinstall.mjs" + }, + "optionalDependencies": { + "@foundry-rs/cast-darwin-arm64": "0.0.0", + "@foundry-rs/cast-darwin-amd64": "0.0.0", + "@foundry-rs/cast-linux-arm64": "0.0.0", + "@foundry-rs/cast-linux-amd64": "0.0.0", + "@foundry-rs/cast-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "keywords": [ + "foundry", + "testing", + "ethereum", + "solidity", + "blockchain", + "smart-contracts" + ], + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/chisel/README.md b/npm/@foundry-rs/chisel/README.md new file mode 100644 index 0000000000000..1164c58474163 --- /dev/null +++ b/npm/@foundry-rs/chisel/README.md @@ -0,0 +1,52 @@ +# [Chisel](https://getfoundry.sh/chisel) + +Chisel is a fast, utilitarian, and verbose Solidity REPL. +The chisel binary can be used both within and outside of a Foundry project. + +## Usage + +### One-off commands + +Example + +```sh +npx --yes @foundry-rs/chisel@nightly +``` + +More generally + +```sh +npx --yes @foundry-rs/chisel@ [args...] +``` + +### Install then use + +locally to your project + +```sh +npm add @foundry-rs/chisel@nightly +npx chisel [args...] +``` + +globally + +```sh +npm add --global @foundry-rs/chisel@nightly +chisel [args...] +``` + +--- + +Also works with `deno`, `bun`, and `pnpm`: + +```sh +deno run --quiet --allow-all npm:@foundry-rs/chisel@nightly [args...] +``` + +```sh +bun x @foundry-rs/chisel@nightly [args...] +``` + +```sh +pnpm dlx --silent @foundry-rs/chisel@nightly [args...] +``` diff --git a/npm/@foundry-rs/chisel/package.json b/npm/@foundry-rs/chisel/package.json new file mode 100644 index 0000000000000..2a8690af251e0 --- /dev/null +++ b/npm/@foundry-rs/chisel/package.json @@ -0,0 +1,42 @@ +{ + "name": "@foundry-rs/chisel", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/chisel", + "description": "Chisel is a fast, utilitarian, and verbose Solidity REPL", + "bin": { + "chisel": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "TARGET_TOOL=chisel node ./dist/postinstall.mjs" + }, + "optionalDependencies": { + "@foundry-rs/chisel-darwin-arm64": "0.0.0", + "@foundry-rs/chisel-darwin-amd64": "0.0.0", + "@foundry-rs/chisel-linux-arm64": "0.0.0", + "@foundry-rs/chisel-linux-amd64": "0.0.0", + "@foundry-rs/chisel-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "keywords": [ + "foundry", + "testing", + "ethereum", + "solidity", + "blockchain", + "smart-contracts" + ], + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/forge/README.md b/npm/@foundry-rs/forge/README.md new file mode 100644 index 0000000000000..ae45dee6af3e6 --- /dev/null +++ b/npm/@foundry-rs/forge/README.md @@ -0,0 +1,52 @@ +# [Forge](https://getfoundry.sh/forge) + +Forge is a command-line tool that ships with Foundry. Forge tests, builds, and deploys your smart contracts. +The forge binary can be used both within and outside of a Foundry project. + +## Usage + +### One-off commands + +Example + +```sh +npx --yes @foundry-rs/forge@nightly init +``` + +More generally + +```sh +npx --yes @foundry-rs/forge@ [args...] +``` + +### Install then use + +locally to your project + +```sh +npm add @foundry-rs/forge@nightly +npx forge [args...] +``` + +globally + +```sh +npm add --global @foundry-rs/forge@nightly +forge [args...] +``` + +--- + +Also works with `deno`, `bun`, and `pnpm`: + +```sh +deno run --quiet --allow-all npm:@foundry-rs/forge@nightly [args...] +``` + +```sh +bun x @foundry-rs/forge@nightly [args...] +``` + +```sh +pnpm dlx --silent @foundry-rs/forge@nightly [args...] +``` diff --git a/npm/@foundry-rs/forge/package.json b/npm/@foundry-rs/forge/package.json new file mode 100644 index 0000000000000..bf33cfb06c5b0 --- /dev/null +++ b/npm/@foundry-rs/forge/package.json @@ -0,0 +1,42 @@ +{ + "name": "@foundry-rs/forge", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/forge", + "description": "Fast and flexible Ethereum testing framework", + "bin": { + "forge": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "TARGET_TOOL=forge node ./dist/postinstall.mjs" + }, + "optionalDependencies": { + "@foundry-rs/forge-darwin-arm64": "0.0.0", + "@foundry-rs/forge-darwin-amd64": "0.0.0", + "@foundry-rs/forge-linux-arm64": "0.0.0", + "@foundry-rs/forge-linux-amd64": "0.0.0", + "@foundry-rs/forge-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "keywords": [ + "foundry", + "testing", + "ethereum", + "solidity", + "blockchain", + "smart-contracts" + ], + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000000000..bc19a408fd411 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,31 @@ +# `@foundry-rs` npm Packages + +This directory contains npm publisher and installer scripts for `forge`, `cast`, `anvil`, and `chisel` and per-arch packages for each: + +- [`@foundry-rs/forge`](https://npm.im/@foundry-rs/forge): + - `@foundry-rs/forge-darwin-arm64` + - `@foundry-rs/forge-darwin-amd64` + - `@foundry-rs/forge-linux-arm64` + - `@foundry-rs/forge-linux-amd64` + - `@foundry-rs/forge-win32-amd64` + +- [`@foundry-rs/cast`](https://npm.im/@foundry-rs/cast): + - `@foundry-rs/cast-darwin-arm64` + - `@foundry-rs/cast-darwin-amd64` + - `@foundry-rs/cast-linux-arm64` + - `@foundry-rs/cast-linux-amd64` + - `@foundry-rs/cast-win32-amd64` + +- [`@foundry-rs/anvil`](https://npm.im/@foundry-rs/anvil): + - `@foundry-rs/anvil-darwin-arm64` + - `@foundry-rs/anvil-darwin-amd64` + - `@foundry-rs/anvil-linux-arm64` + - `@foundry-rs/anvil-linux-amd64` + - `@foundry-rs/anvil-win32-amd64` + +- [`@foundry-rs/chisel`](https://npm.im/@foundry-rs/chisel): + - `@foundry-rs/chisel-darwin-arm64` + - `@foundry-rs/chisel-darwin-amd64` + - `@foundry-rs/chisel-linux-arm64` + - `@foundry-rs/chisel-linux-amd64` + - `@foundry-rs/chisel-win32-amd64` diff --git a/npm/bun.lock b/npm/bun.lock new file mode 100644 index 0000000000000..d9e8fed5ce479 --- /dev/null +++ b/npm/bun.lock @@ -0,0 +1,57 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "dependencies": { + "@types/bun": "^1.3.1", + "@types/node": "^25.5.2", + "bun": "^1.3.1", + "typescript": "^6.0.2", + }, + }, + }, + "packages": { + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7Rap1BHNWqgnexc4wLjjdZeVRQKtk534iGuJ7qZ42i/q1B+cxJZ6zSnrFsYmo+zreH7dUyUXL3AHuXGrl2772Q=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-wpqmgT/8w+tEr5YMGt1u1sEAMRHhyA2SKZddC6GCPasHxSqkCWOPQvYIHIApnTsoSsxhxP0x6Cpe93+4c7hq/w=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-mJo715WvwEHmJ6khNymWyxi0QrFzU94wolsUmxolViNHrk+2ugzIkVIJhTnxf7pHnarxxHwyJ/kgatuV//QILQ=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-ACn038SZL8del+sFnqCjf+haGB02//j2Ez491IMmPTvbv4a/D0iiNz9xiIB3ICbQd3EwQzi+Ut/om3Ba/KoHbQ=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gKU3Wv3BTG5VMjqMMnRwqU6tipCveE9oyYNt62efy6cQK3Vo1DOBwY2SmjbFw+yzj+Um20YoFOLGxghfQET4Ng=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cAUeM3I5CIYlu5Ur52eCOGg9yfqibQd4lzt9G1/rA0ajqcnCBaTuekhUDZETJJf5H9QV+Gm46CqQg2DpdJzJsw=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-7+2aCrL81mtltZQbKdiPB58UL+Gr3DAIuPyUAKm0Ib/KG/Z8t7nD/eSMRY/q6b+NsAjYnVPiPwqSjC3edpMmmQ=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-8AgEAHyuJ5Jm9MUo1L53K1SRYu0bNGqV0E0L5rB5DjkteO4GXrnWGBT8qsuwuy7WMuCMY3bj64/pFjlRkZuiXw=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-tP0WWcAqrMayvkggOHBGBoyyoK+QHAqgRUyj1F6x5/udiqc9vCXmIt1tlydxYV/NvyvUAmJ7MWT0af44Xm2kJw=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xdUjOZRq6PwPbbz4/F2QEMLBZwintGp7AS50cWxgkHnyp7Omz5eJfV6/vWtN4qwZIyR3V3DT/2oXsY1+7p3rtg=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dcA+Kj7hGFrY3G8NWyYf3Lj3/GMViknpttWUf5pI6p6RphltZaoDu0lY5Lr71PkMdRZTwL2NnZopa/x/NWCdKA=="], + + "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + + "bun": ["bun@1.3.1", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.1", "@oven/bun-darwin-x64": "1.3.1", "@oven/bun-darwin-x64-baseline": "1.3.1", "@oven/bun-linux-aarch64": "1.3.1", "@oven/bun-linux-aarch64-musl": "1.3.1", "@oven/bun-linux-x64": "1.3.1", "@oven/bun-linux-x64-baseline": "1.3.1", "@oven/bun-linux-x64-musl": "1.3.1", "@oven/bun-linux-x64-musl-baseline": "1.3.1", "@oven/bun-windows-x64": "1.3.1", "@oven/bun-windows-x64-baseline": "1.3.1" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-enqkEb0RhNOgDzHQwv7uvnIhX3uSzmKzz779dL7kdH8SauyTdQvCz4O1UT2rU0UldQp2K9OlrJNdyDHayPEIvw=="], + + "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "bun-types/@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/npm/bunfig.toml b/npm/bunfig.toml new file mode 100644 index 0000000000000..efabe9ba8189b --- /dev/null +++ b/npm/bunfig.toml @@ -0,0 +1,12 @@ +telemetry = false + +run.bun = true + +[install] +auto = "auto" +minimumReleaseAge = 86400 + +[install.scopes] +"@foundry-rs" = { url = "$NPM_REGISTRY_URL", username = "$NPM_USERNAME" } + +logLevel = "debug" diff --git a/npm/env.d.ts b/npm/env.d.ts new file mode 100644 index 0000000000000..828acd8e5a17b --- /dev/null +++ b/npm/env.d.ts @@ -0,0 +1,46 @@ +interface ImportMetaEnv { + readonly CI: string + readonly NPM_TOKEN: string + readonly BUN_AUTH_TOKEN: string + + readonly NODE_ENV: 'development' | 'production' + + readonly PROVENANCE: 'true' | 'false' + + TARGET_TOOL: 'forge' | 'cast' | 'anvil' | 'chisel' + + // release.yml#jobs:release:strategy:matrix:include:-|target + readonly TARGET: + | 'x86_64-unknown-linux-gnu' + | 'x86_64-unknown-linux-musl' + | 'aarch64-unknown-linux-gnu' + | 'aarch64-unknown-linux-musl' + | 'x86_64-apple-darwin' + | 'aarch64-apple-darwin' + | 'x86_64-pc-windows-msvc' + // + readonly ARCH: 'amd64' | 'arm64' | 'aarch64' + readonly IS_NIGHTLY: 'true' | 'false' + // `${(env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name}` + readonly VERSION_NAME: string + // release.yml#jobs:release:strategy:matrix:include:-|platform + readonly PLATFORM_NAME: 'linux' | 'darwin' | 'win32' + // `debug` / `release` / `maxperf` / `dist` + readonly PROFILE: 'debug' | 'release' | 'maxperf' | 'dist' + + // Used for local testing/development only + readonly REGISTRY_URL: string + readonly ALLOW_NO_INTEGRITY: 'true' | 'false' +} + +declare namespace NodeJS { + interface ProcessEnv extends ImportMetaEnv {} +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +declare namespace Bun { + interface Env extends ImportMetaEnv {} +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000000000..bac357038a1ea --- /dev/null +++ b/npm/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "type": "module", + "imports": { + "#*": "./src/*", + "#scripts/*": "./scripts/*", + "#package.json": "./package.json" + }, + "dependencies": { + "@types/bun": "^1.3.1", + "@types/node": "^25.5.2", + "bun": "^1.3.1", + "typescript": "^6.0.2" + }, + "license": "MIT OR Apache-2.0", + "$schema": "https://json.schemastore.org/package.json" +} diff --git a/npm/scripts/check.sh b/npm/scripts/check.sh new file mode 100755 index 0000000000000..82b77d5ba824b --- /dev/null +++ b/npm/scripts/check.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash + +set -eou pipefail + +# Ensure we're in the npm directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NPM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$NPM_DIR" || exit 1 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +tools=(cast anvil forge chisel) + +# Detect current platform +if [[ "$(uname)" == "Darwin" ]]; then + if [[ "$(uname -m)" == "arm64" ]]; then + TARGET="aarch64-apple-darwin" + PLATFORM="darwin" + ARCH="arm64" + else + TARGET="x86_64-apple-darwin" + PLATFORM="darwin" + ARCH="amd64" + fi +elif [[ "$(uname)" == "Linux" ]]; then + if [[ "$(uname -m)" == "aarch64" ]]; then + TARGET="aarch64-unknown-linux-gnu" + PLATFORM="linux" + ARCH="arm64" + else + TARGET="x86_64-unknown-linux-gnu" + PLATFORM="linux" + ARCH="amd64" + fi +else + echo -e "${RED}Unsupported platform: $(uname)${NC}" + exit 1 +fi + +# Check if we're in CI and can use artifacts +USE_ARTIFACTS=false +if [[ -n "${CI:-}" ]] && [[ -n "${ARTIFACT_DIR:-}" ]] && [[ -n "${RELEASE_VERSION:-}" ]]; then + USE_ARTIFACTS=true + echo -e "${BLUE}=== Foundry npm Package Check (CI Mode) ===${NC}" + echo -e "${BLUE}Using artifacts from: ${ARTIFACT_DIR}${NC}" + echo -e "${BLUE}Release version: ${RELEASE_VERSION}${NC}" +else + echo -e "${BLUE}=== Foundry npm Package Check (Local Mode) ===${NC}" + echo -e "${BLUE}Platform: ${PLATFORM}${NC}" + echo -e "${BLUE}Architecture: ${ARCH}${NC}" + echo -e "${BLUE}Target: ${TARGET}${NC}" +fi +echo "" + +# Determine total step count and current step +TOTAL_STEPS=$([[ "$USE_ARTIFACTS" == "true" ]] && echo "5" || echo "6") + +# Step 1: Build binaries or stage from artifacts +if [[ "$USE_ARTIFACTS" == "true" ]]; then + echo -e "${YELLOW}[1/${TOTAL_STEPS}] Staging packages from artifacts…${NC}" + for tool in "${tools[@]}"; do + echo -e " Staging ${tool} from artifacts…" + bun ./scripts/stage-from-artifact.mjs \ + --tool "$tool" \ + --platform "$PLATFORM" \ + --arch "$ARCH" \ + --release-version "$RELEASE_VERSION" \ + --artifact-dir "$ARTIFACT_DIR" || { + echo -e "${RED}Failed to stage ${tool} from artifacts${NC}" + exit 1 + } + done + echo -e "${GREEN}✓ All packages staged from artifacts${NC}" +else + echo -e "${YELLOW}[1/${TOTAL_STEPS}] Building tools…${NC}" + for tool in "${tools[@]}"; do + echo -e " Building ${tool}…" + cargo build \ + --package "$tool" \ + --target "$TARGET" \ + --release || { + echo -e "${RED}Failed to build ${tool}${NC}" + exit 1 + } + done + echo -e "${GREEN}✓ All tools built successfully${NC}" +fi +echo "" + +# Step 2: Stage platform-specific packages (if not using artifacts) +if [[ "$USE_ARTIFACTS" == "false" ]]; then + echo -e "${YELLOW}[2/${TOTAL_STEPS}] Staging platform-specific packages…${NC}" + for tool in "${tools[@]}"; do + echo -e " Staging ${tool}…" + BIN_PATH="../target/${TARGET}/release/${tool}" + if [[ "$PLATFORM" == "win32" ]]; then + BIN_PATH="../target/${TARGET}/release/${tool}.exe" + fi + + if [[ ! -f "$BIN_PATH" ]]; then + echo -e "${RED}Binary not found: ${BIN_PATH}${NC}" + exit 1 + fi + + PLATFORM_NAME="$PLATFORM" ARCH="$ARCH" bun ./scripts/prepublish.mjs \ + --tool "$tool" --bin-path "$BIN_PATH" || { + echo -e "${RED}Failed to stage ${tool}${NC}" + exit 1 + } + done + echo -e "${GREEN}✓ All platform-specific packages staged${NC}" + echo "" +fi + +# Step 2/3: Verify platform-specific packages +STEP_NUM=$([[ "$USE_ARTIFACTS" == "true" ]] && echo "2" || echo "3") +echo -e "${YELLOW}[${STEP_NUM}/${TOTAL_STEPS}] Verifying platform-specific packages…${NC}" +for tool in "${tools[@]}"; do + PACKAGE_DIR="@foundry-rs/${tool}-${PLATFORM}-${ARCH}" + BIN_NAME="${tool}" + if [[ "$PLATFORM" == "win32" ]]; then + BIN_NAME="${tool}.exe" + fi + + PACKAGE_JSON="${PACKAGE_DIR}/package.json" + BIN_FILE="${PACKAGE_DIR}/bin/${BIN_NAME}" + + if [[ ! -f "$PACKAGE_JSON" ]]; then + echo -e "${RED}Missing package.json: ${PACKAGE_JSON}${NC}" + exit 1 + fi + + if [[ ! -f "$BIN_FILE" ]]; then + echo -e "${RED}Missing binary: ${BIN_FILE}${NC}" + exit 1 + fi + + if [[ "$PLATFORM" != "win32" ]] && [[ ! -x "$BIN_FILE" ]]; then + echo -e "${RED}Binary not executable: ${BIN_FILE}${NC}" + exit 1 + fi + + # Validate package.json structure + if ! bun -e " + const pkg = JSON.parse(await Bun.file('${PACKAGE_JSON}').text()); + if (!pkg.name || !pkg.bin || !pkg.bin['${tool}']) { + console.error('Invalid package.json structure'); + process.exit(1); + } + "; then + echo -e "${RED}Invalid package.json: ${PACKAGE_JSON}${NC}" + exit 1 + fi + + echo -e " ✓ ${tool}-${PLATFORM}-${ARCH}" +done +echo -e "${GREEN}✓ All platform-specific packages verified${NC}" +echo "" + +# Step 3/4: Prepare meta packages +STEP_NUM=$([[ "$USE_ARTIFACTS" == "true" ]] && echo "3" || echo "4") +echo -e "${YELLOW}[${STEP_NUM}/${TOTAL_STEPS}] Preparing meta packages…${NC}" +# Use RELEASE_VERSION if available, otherwise a dummy version for testing +TEST_VERSION="${RELEASE_VERSION:-0.0.0-test}" +for tool in "${tools[@]}"; do + echo -e " Preparing ${tool} meta package…" + RELEASE_VERSION="$TEST_VERSION" bun ./scripts/publish-meta.mjs --tool "$tool" --release-version "$TEST_VERSION" || { + echo -e "${RED}Failed to prepare ${tool} meta package${NC}" + exit 1 + } +done +echo -e "${GREEN}✓ All meta packages prepared${NC}" +echo "" + +# Step 4/5: Verify meta packages +STEP_NUM=$([[ "$USE_ARTIFACTS" == "true" ]] && echo "4" || echo "5") +echo -e "${YELLOW}[${STEP_NUM}/${TOTAL_STEPS}] Verifying meta packages…${NC}" +for tool in "${tools[@]}"; do + META_DIR="@foundry-rs/${tool}" + PACKAGE_JSON="${META_DIR}/package.json" + BIN_MJS="${META_DIR}/bin.mjs" + CONST_MJS="${META_DIR}/const.mjs" + POSTINSTALL_MJS="${META_DIR}/postinstall.mjs" + + if [[ ! -f "$PACKAGE_JSON" ]]; then + echo -e "${RED}Missing package.json: ${PACKAGE_JSON}${NC}" + exit 1 + fi + + if [[ ! -f "$BIN_MJS" ]]; then + echo -e "${RED}Missing bin.mjs: ${BIN_MJS}${NC}" + exit 1 + fi + + if [[ ! -f "$CONST_MJS" ]]; then + echo -e "${RED}Missing const.mjs: ${CONST_MJS}${NC}" + exit 1 + fi + + if [[ ! -f "$POSTINSTALL_MJS" ]]; then + echo -e "${RED}Missing postinstall.mjs: ${POSTINSTALL_MJS}${NC}" + exit 1 + fi + + # Verify import map points to const.mjs + if ! bun -e " + const pkg = JSON.parse(await Bun.file('${PACKAGE_JSON}').text()); + if (pkg.imports?.['#const.mjs'] !== './const.mjs') { + console.error('Invalid import map: #const.mjs should point to ./const.mjs'); + console.error('Found:', pkg.imports?.['#const.mjs']); + process.exit(1); + } + if (pkg.bin?.['${tool}'] !== './bin.mjs') { + console.error('Invalid bin entry'); + process.exit(1); + } + "; then + echo -e "${RED}Invalid package.json structure for ${tool}${NC}" + exit 1 + fi + + # Verify bin.mjs can import from const.mjs + CONST_ABS_PATH="$(cd "$META_DIR" && pwd)/const.mjs" + if ! bun -e " + try { + // Try to import const.mjs to verify it exports correctly + const constModule = await import('${CONST_ABS_PATH}'); + const required = ['BINARY_NAME', 'colors', 'KNOWN_TOOLS', 'PLATFORM_SPECIFIC_PACKAGE_NAME', 'resolveTargetTool']; + for (const name of required) { + if (!(name in constModule)) { + console.error(\`Missing export: \${name}\`); + process.exit(1); + } + } + } catch (error) { + console.error('Failed to import const.mjs:', error.message); + process.exit(1); + } + "; then + echo -e "${RED}Failed to validate const.mjs exports for ${tool}${NC}" + exit 1 + fi + + echo -e " ✓ ${tool}" +done +echo -e "${GREEN}✓ All meta packages verified${NC}" +echo "" + +# Step 5/6: Test package packing +STEP_NUM=$([[ "$USE_ARTIFACTS" == "true" ]] && echo "5" || echo "6") +echo -e "${YELLOW}[${STEP_NUM}/${TOTAL_STEPS}] Testing package packing…${NC}" +for tool in "${tools[@]}"; do + META_DIR="@foundry-rs/${tool}" + echo -e " Packing ${tool}…" + + # Test that we can pack the meta package + ORIG_DIR=$(pwd) + cd "$META_DIR" || exit 1 + + # Try to pack and capture output + PACK_OUTPUT=$(bun pm pack 2>&1) + PACK_EXIT=$? + + # Clean up any generated .tgz files + rm -f -- *.tgz + + cd "$ORIG_DIR" || exit 1 + + if [[ $PACK_EXIT -ne 0 ]]; then + echo -e "${RED}Failed to pack ${tool}${NC}" + echo "$PACK_OUTPUT" + exit 1 + fi + + echo -e " ✓ ${tool}" +done +echo -e "${GREEN}✓ All packages can be packed${NC}" +echo "" + +echo -e "${GREEN}=== All checks passed! ===${NC}" +echo -e "${BLUE}Ready to publish${NC}" \ No newline at end of file diff --git a/npm/scripts/prepublish.mjs b/npm/scripts/prepublish.mjs new file mode 100644 index 0000000000000..40b3ae0e5bce4 --- /dev/null +++ b/npm/scripts/prepublish.mjs @@ -0,0 +1,263 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' + +import { colors, KNOWN_TOOLS, resolveTargetTool } from '#const.mjs' +import { generateBinaryPackageJson } from '../src/generate-package-json.mjs' + +/** + * @typedef {import('#const.mjs').Arch} Arch + * @typedef {import('#const.mjs').Platform} Platform + * @typedef {import('#const.mjs').Profile} Profile + * @typedef {import('#const.mjs').Tool} Tool + */ + +/** + * @typedef {{ + * tool: Tool + * platform: Platform + * arch: Arch + * binaryPath: string + * }} ResolvedInputs + */ + +/** + * @typedef {{ + * tool: Tool + * platform: Platform + * arch: Arch + * profile: Profile + * cliCandidates: Array + * }} ResolveBinaryPathOptions + */ + +/** + * @typedef {{ + * tool: Tool + * platform: Platform + * arch: Arch + * profile: Profile + * }} FallbackSearchOptions + */ + +const PLATFORM_MAP = /** @type {const} */ (/** @type {Record} */ ({ + linux: 'linux', + darwin: 'darwin', + win32: 'win32' +})) + +const TARGET_MAP = /** @type {const} */ (/** @type {Record<`${Arch}-${Platform}`, string>} */ ({ + 'amd64-linux': 'x86_64-unknown-linux-gnu', + 'arm64-linux': 'aarch64-unknown-linux-gnu', + 'amd64-darwin': 'x86_64-apple-darwin', + 'arm64-darwin': 'aarch64-apple-darwin', + 'amd64-win32': 'x86_64-pc-windows-msvc' +})) + +const PRESERVE = new Set(['package.json', 'README.md']) +const GENERIC_BIN_ENV_KEYS = [ + 'BIN_PATH', + 'bin_path', + 'BIN', + 'BINARY_PATH', + 'binary_path', + 'TARGET_BIN_PATH', + 'target_bin_path', + 'TARGET_BINARY_PATH', + 'target_binary_path' +] + +const TOOL_ENV_KEYS = /** @type {Record} */ ({ + forge: ['forge_bin_path', 'FORGE_BIN_PATH'], + cast: ['cast_bin_path', 'CAST_BIN_PATH'], + anvil: ['anvil_bin_path', 'ANVIL_BIN_PATH'], + chisel: ['chisel_bin_path', 'CHISEL_BIN_PATH'] +}) + +main().catch(error => { + console.error(colors.red, error) + process.exit(1) +}) + +/** + * Orchestrates package preparation for the current platform/tool pair. + * @returns {Promise} + */ +async function main() { + const { tool, platform, arch, binaryPath } = resolveInputs() + const packagePath = NodePath.join(process.cwd(), '@foundry-rs', `${tool}-${platform}-${arch}`) + + await NodeFS.promises.mkdir(packagePath, { recursive: true, mode: 0o755 }) + console.info(colors.green, `Ensured package directory at ${packagePath}`, colors.reset) + + await generateBinaryPackageJson({ tool, platform, arch, packagePath }) + await cleanPackageDirectory(packagePath) + await copyBinary({ source: binaryPath, tool, packagePath, platform }) + + console.info(colors.green, 'Binary copy completed successfully!', colors.reset) +} + +/** + * Collects CLI/env inputs, normalises them, and resolves the binary path. + * @returns {ResolvedInputs} + */ +function resolveInputs() { + const parsed = NodeUtil.parseArgs({ + args: Bun.argv.slice(2), + allowPositionals: true, + options: { + tool: { type: 'string' }, + binary: { type: 'string' }, + bin: { type: 'string' }, + 'bin-path': { type: 'string' }, + 'binary-path': { type: 'string' } + }, + strict: true + }) + + const platformEnv = Bun.env.PLATFORM_NAME || '' + const archEnv = Bun.env.ARCH || '' + + const platform = PLATFORM_MAP[platformEnv] + const arch = archEnv === 'aarch64' ? 'arm64' : archEnv + + if (!platform || (arch !== 'amd64' && arch !== 'arm64')) + throw new Error(`Invalid platform or architecture: platform=${platformEnv}, arch=${archEnv}`) + + const tool = resolveTool([ + parsed.values.tool, + parsed.positionals[0], + Bun.env.TARGET_TOOL, + Bun.env.TOOL, + Bun.env.BINARY_TOOL + ]) + + const profile = Bun.env.NODE_ENV === 'production' ? 'release' : Bun.env.PROFILE || 'release' + const binaryPath = resolveBinaryPath({ + tool, + platform, + arch, + profile, + cliCandidates: [ + parsed.values.binary, + parsed.values.bin, + parsed.values['bin-path'], + parsed.values['binary-path'], + parsed.positionals[1] + ] + }) + + return { tool, platform, arch, binaryPath } +} + +/** + * Picks the first candidate that resolves to a known tool. + * @param {Array} candidates + * @returns {Tool} + */ +function resolveTool(candidates) { + for (const candidate of candidates) { + if (typeof candidate !== 'string' || candidate.trim() === '') continue + try { + return resolveTargetTool(candidate) + } catch { + // try the next candidate + } + } + + throw new Error(`Tool not specified. Provide --tool=<${KNOWN_TOOLS.join('|')}> or set TARGET_TOOL.`) +} + +/** + * Resolves the filesystem path to the tool binary, honouring CLI/env overrides. + * @param {ResolveBinaryPathOptions} options + * @returns {string} + */ +function resolveBinaryPath({ tool, platform, arch, profile, cliCandidates }) { + const envCandidates = [ + ...GENERIC_BIN_ENV_KEYS, + ...(TOOL_ENV_KEYS[tool] ?? []) + ].map(readEnv) + + for (const candidate of [...cliCandidates, ...envCandidates]) { + if (typeof candidate === 'string' && candidate.trim()) + return NodePath.resolve(candidate.trim()) + } + + return findBinaryFallback({ tool, platform, arch, profile }) +} + +/** + * Searches the local Cargo build artefacts for the requested binary as a fallback. + * @param {FallbackSearchOptions} options + * @returns {string} + */ +function findBinaryFallback({ tool, platform, arch, profile }) { + const targetDir = TARGET_MAP[`${arch}-${platform}`] + const binaryName = platform === 'win32' ? `${tool}.exe` : tool + const searchOrder = [] + + if (targetDir) + searchOrder.push(NodePath.join(process.cwd(), '..', 'target', targetDir, profile, binaryName)) + + searchOrder.push( + NodePath.join(process.cwd(), '..', 'target', profile, binaryName), + NodePath.join(process.cwd(), '..', 'target', 'release', binaryName) + ) + + for (const candidate of searchOrder) + if (candidate && NodeFS.existsSync(candidate)) return candidate + + throw new Error(`Source binary for ${tool} not found. Looked in: ${searchOrder.join(', ')}`) +} + +/** + * Removes previously staged files while preserving metadata such as README/package.json. + * @param {string} packagePath + * @returns {Promise} + */ +async function cleanPackageDirectory(packagePath) { + const items = await NodeFS.promises.readdir(packagePath).catch(() => []) + + for (const item of items) { + if (PRESERVE.has(item)) continue + NodeFS.rmSync(NodePath.join(packagePath, item), { recursive: true, force: true }) + } + + console.info(colors.green, 'Cleaned up package directory', colors.reset) +} + +/** + * Copies the tool binary into the package directory. + * @param {{ source: string; tool: Tool; packagePath: string; platform: Platform }} parameters + * @returns {Promise} + */ +async function copyBinary({ source, tool, packagePath, platform }) { + if (!(await Bun.file(source).exists())) + throw new Error(`Source binary not found at ${source}`) + + const binaryName = platform === 'win32' ? `${tool}.exe` : tool + const targetDir = NodePath.join(packagePath, 'bin') + + NodeFS.mkdirSync(targetDir, { recursive: true }) + + const targetPath = NodePath.join(targetDir, binaryName) + console.info(colors.green, `Copying ${source} to ${targetPath}`, colors.reset) + + await Bun.write(targetPath, Bun.file(source)) + + if (platform !== 'win32') + NodeFS.chmodSync(targetPath, 0o755) +} + +/** + * Reads an environment variable, falling back across case variants. + * @param {string | undefined} key + * @returns {string | undefined} + */ +function readEnv(key) { + if (!key) return undefined + return Bun.env[key] ?? Bun.env[key.toUpperCase()] ?? Bun.env[key.toLowerCase()] +} diff --git a/npm/scripts/publish-meta.mjs b/npm/scripts/publish-meta.mjs new file mode 100755 index 0000000000000..3bacbf3ea0e42 --- /dev/null +++ b/npm/scripts/publish-meta.mjs @@ -0,0 +1,140 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs/promises' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' + +import { colors, KNOWN_TOOLS } from '#const.mjs' + +/** + * @typedef {import('#const.mjs').Tool} Tool + */ + +main().catch(error => { + console.error(colors.red, error) + console.error(colors.reset) + process.exit(1) +}) + +/** + * Publish each meta package (`@foundry-rs/`) to npm. + * @returns {Promise} + */ +async function main() { + const { tools, releaseVersion } = resolveArgs() + + for (const tool of tools) { + await prepareMetaPackage(tool) + const packageDir = NodePath.join('@foundry-rs', tool) + console.info(colors.green, `Publishing meta package ${packageDir}`, colors.reset) + + const result = await Bun + .$`bun ./scripts/publish.mjs ${packageDir}` + .cwd(NodePath.resolve(import.meta.dir, '..')) + .env({ + ...process.env, + TARGET_TOOL: tool, + TOOL: tool, + RELEASE_VERSION: releaseVersion, + VERSION_NAME: releaseVersion + }) + .nothrow() + + if (result.exitCode !== 0) { + const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8') + const stdout = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8') + throw new Error(stderr || stdout || `Failed to publish ${packageDir}`) + } + } +} + +/** + * Resolve CLI arguments and defaults. + * @returns {{ tools: Tool[]; releaseVersion: string }} + */ +function resolveArgs() { + const { values, positionals } = NodeUtil.parseArgs({ + args: Bun.argv.slice(2), + allowPositionals: true, + options: { + tool: { type: 'string', multiple: true }, + tools: { type: 'string', multiple: true }, + 'release-version': { type: 'string' }, + release: { type: 'string' } + }, + strict: true + }) + + const releaseCandidate = values['release-version'] + || values.release + || process.env.RELEASE_VERSION + || process.env.VERSION_NAME + || '' + + const releaseVersion = releaseCandidate.trim() + if (!releaseVersion) + throw new Error('Missing required RELEASE_VERSION') + + const explicitTools = [...(values.tool ?? []), ...(values.tools ?? []), ...positionals] + + const selected = explicitTools.length ? explicitTools : KNOWN_TOOLS + const tools = /** @type {Tool[]} */ (selected.map(candidate => { + const trimmed = candidate.trim() + if (!trimmed) return undefined + const maybeTool = /** @type {Tool} */ (trimmed) + if (!KNOWN_TOOLS.includes(maybeTool)) + throw new Error(`Unsupported tool: ${candidate}`) + return maybeTool + }).filter(Boolean)) + + return { tools, releaseVersion } +} + +/** + * Ensure the meta package directory contains the runtime files. + * @param {Tool} tool + * @returns {Promise} + */ +async function prepareMetaPackage(tool) { + const npmDir = NodePath.resolve(import.meta.dir, '..') + const sourceDir = NodePath.join(npmDir, 'src') + const metaDir = NodePath.join(npmDir, '@foundry-rs', tool) + await NodeFS.rm(NodePath.join(metaDir, 'dist'), { recursive: true, force: true }) + + const postinstallPath = NodePath.join(metaDir, 'postinstall.mjs') + const buildResult = await Bun + .$`bun build ${NodePath.join(sourceDir, 'install.mjs')} --format esm --outfile ${postinstallPath} --target node` + .cwd(npmDir) + .nothrow() + + if (buildResult.exitCode !== 0) { + const stderr = typeof buildResult.stderr === 'string' + ? buildResult.stderr + : buildResult.stderr?.toString('utf8') + const stdout = typeof buildResult.stdout === 'string' + ? buildResult.stdout + : buildResult.stdout?.toString('utf8') + throw new Error(stderr || stdout || 'Failed to build postinstall script') + } + + const binSource = await NodeFS.readFile(NodePath.join(sourceDir, 'bin.mjs'), 'utf8') + await NodeFS.writeFile(NodePath.join(metaDir, 'bin.mjs'), binSource) + + const constSource = await NodeFS.readFile(NodePath.join(sourceDir, 'const.mjs'), 'utf8') + await NodeFS.writeFile(NodePath.join(metaDir, 'const.mjs'), constSource) + + const packageJsonPath = NodePath.join(metaDir, 'package.json') + const pkg = JSON.parse(await NodeFS.readFile(packageJsonPath, 'utf8')) + pkg.imports = { ...(pkg.imports || {}), '#const.mjs': './const.mjs' } + pkg.scripts = { ...(pkg.scripts || {}), postinstall: `TARGET_TOOL=${tool} node ./postinstall.mjs` } + + const files = new Set([...(pkg.files ?? [])]) + files.delete('dist') + files.delete('bin') + files.add('bin.mjs') + files.add('const.mjs') + files.add('postinstall.mjs') + pkg.files = Array.from(files) + + await NodeFS.writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n') +} diff --git a/npm/scripts/publish.mjs b/npm/scripts/publish.mjs new file mode 100644 index 0000000000000..deee29e46c92f --- /dev/null +++ b/npm/scripts/publish.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs' +import * as NodePath from 'node:path' + +import { colors } from '#const.mjs' + +const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' + +main().catch(error => { + console.error(error) + process.exit(1) +}) + +async function main() { + + const inputPath = Bun.argv[2] + if (!inputPath) throw new Error('Package path is required') + const packagePath = NodePath.resolve(inputPath) + console.info(colors.green, 'Package path:', packagePath) + + const publishVersion = getPublishVersion() + console.info(colors.green, 'Publish version:', publishVersion) + if (!publishVersion) throw new Error('Publish version is required') + + if (await isMetaPackage(packagePath)) + await updateOptionalDependencies(packagePath, publishVersion) + + await setPackageVersion(packagePath, publishVersion) + const packedFile = await packPackage(packagePath) + await publishPackage(packagePath, packedFile, publishVersion) +} + +/** + * @returns {string} + */ +function getPublishVersion() { + const maybeVersion = (Bun.env.VERSION_NAME || '').replace(/^v/, '') + if (maybeVersion && Bun.semver.satisfies(maybeVersion, maybeVersion)) return maybeVersion + + const bump = (Bun.env.BUMP_VERSION || '').replace(/^v/, '') + if (bump && (!!bump && Bun.semver.satisfies(bump, bump))) return bump + + const releaseVersion = (Bun.env.RELEASE_VERSION || '').replace(/^v/, '') + const isNightly = releaseVersion.toLowerCase() === 'nightly' || Bun.env.IS_NIGHTLY === 'true' + + const cargoToml = NodeFS.readFileSync( + NodePath.join(import.meta.dirname, '..', '..', 'Cargo.toml'), + 'utf-8' + ) + + const versionMatch = cargoToml.match(/^version\s*=\s*"([^"]+)"/m) + if (!versionMatch) throw new Error('Version not found in Cargo.toml') + + const [, base] = versionMatch + if (!base) throw new Error('Version not found in Cargo.toml') + if (!isNightly) return base + + const date = new Date() + const y = date.getUTCFullYear() + const m = String(date.getUTCMonth() + 1).padStart(2, '0') + const d = String(date.getUTCDate()).padStart(2, '0') + const yyyymmdd = `${y}${m}${d}` + const sha = (Bun.env.GITHUB_SHA || '').slice(0, 7) + const suffix = sha ? `nightly.${yyyymmdd}.${sha}` : `nightly.${yyyymmdd}` + return `${base}-${suffix}` +} + +/** + * @param {string} packagePath + * @param {string} version + * @returns {Promise} + */ +async function updateOptionalDependencies(packagePath, version) { + const packageJsonPath = NodePath.join(packagePath, 'package.json') + const packageJson = JSON.parse(NodeFS.readFileSync(packageJsonPath, 'utf-8')) + + if (packageJson.optionalDependencies) { + Object.keys(packageJson.optionalDependencies).forEach(key => { + packageJson.optionalDependencies[key] = version + }) + + await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2)) + } +} + +/** + * @param {string} packagePath + * @returns {Promise} + */ +async function isMetaPackage(packagePath) { + try { + const packageJsonPath = NodePath.join(packagePath, 'package.json') + const packageJson = JSON.parse(NodeFS.readFileSync(packageJsonPath, 'utf-8')) + return ['@foundry-rs/forge', '@foundry-rs/cast', '@foundry-rs/anvil', '@foundry-rs/chisel'].includes( + packageJson?.name + ) + } catch { + return false + } +} + +/** + * @param {string} packagePath + * @param {string} version + * @returns {Promise} + */ +async function setPackageVersion(packagePath, version) { + console.info(colors.green, 'Setting package version:', version) + const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` + .cwd(packagePath) + .quiet() + .nothrow() + + if (result.exitCode !== 0) + throw new Error(`Failed to set version: ${result.stderr}`) +} + +/** + * @param {string} packagePath + * @returns {Promise} + */ +async function packPackage(packagePath) { + let packedFile = '' + + for await (const line of Bun.$`bun pm pack`.cwd(packagePath).lines()) + if (line.endsWith('.tgz')) packedFile = line + + if (!packedFile) throw new Error('Failed to pack package') + return packedFile +} + +/** + * @param {string} packagePath + * @param {string} packedFile + * @param {string} version + * @returns {Promise} + */ +async function publishPackage(packagePath, packedFile, version) { + const tag = /-nightly(\.|$)/.test(version) ? 'nightly' : 'latest' + const result = await Bun + .$`npm publish ./${packedFile} --access=public --registry=${REGISTRY_URL} --tag=${tag} --provenance=${ + Bun.env.PROVENANCE || 'true' + }` + .cwd(packagePath) + .nothrow() + + if (result.exitCode !== 0) + throw new Error(`Publish failed: ${result.stderr}`) +} diff --git a/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs new file mode 100755 index 0000000000000..1d39fdc82e84f --- /dev/null +++ b/npm/scripts/stage-from-artifact.mjs @@ -0,0 +1,214 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs/promises' +import * as NodeOS from 'node:os' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' + +import { colors } from '#const.mjs' + +/** + * @typedef {import('#const.mjs').Tool} Tool + * @typedef {import('#const.mjs').Platform} Platform + * @typedef {import('#const.mjs').Arch} Arch + */ + +const RELEASE_ARTIFACT_PREFIX = 'foundry' + +main().catch(error => { + console.error(colors.red, error) + console.error(colors.reset) + process.exit(1) +}) + +/** + * Entry point: locate the platform-specific artifact, extract the binary, + * and delegate to prepublish to stage it into the package directory. + * @returns {Promise} + */ +async function main() { + const { tool, platform, arch, releaseVersion, artifactDir } = resolveArgs() + + const artifactPrefix = NodePath.join( + artifactDir, + `${RELEASE_ARTIFACT_PREFIX}_${releaseVersion}_${platform}_${arch}` + ) + + const archivePath = await chooseArchive(artifactPrefix) + const extractionDir = await extractArchive(archivePath) + + try { + const binaryPath = await resolveExtractedBinary({ tool, platform, extractionDir }) + await stagePackage({ tool, platform, arch, binaryPath }) + } finally { + await NodeFS.rm(extractionDir, { recursive: true, force: true }) + } +} + +/** + * Parse CLI arguments/environment. + * @returns {{ tool: Tool; platform: Platform; arch: Arch; releaseVersion: string; artifactDir: string }} + */ +function resolveArgs() { + const { values } = NodeUtil.parseArgs({ + args: Bun.argv.slice(2), + options: { + tool: { type: 'string' }, + platform: { type: 'string' }, + arch: { type: 'string' }, + release: { type: 'string' }, + 'release-version': { type: 'string' }, + artifacts: { type: 'string' }, + 'artifact-dir': { type: 'string' } + }, + strict: true + }) + + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( + values.release || values['release-version'] || process.env.RELEASE_VERSION, + 'release version' + ) + const artifactDir = requireValue( + values.artifacts || values['artifact-dir'] || process.env.ARTIFACT_DIR, + 'artifact directory' + ) + + return /** @type {{ tool: Tool; platform: Platform; arch: Arch; releaseVersion: string; artifactDir: string }} */ ({ + tool: /** @type {Tool} */ (tool), + platform: /** @type {Platform} */ (platform), + arch: /** @type {Arch} */ (arch), + releaseVersion, + artifactDir: NodePath.resolve(artifactDir) + }) +} + +/** + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireValue(value, name) { + if (typeof value === 'string' && value.trim()) return value.trim() + throw new Error(`Missing required ${name}`) +} + +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + +/** + * Determine which archive variant exists for the given artifact prefix. + * @param {string} prefix + * @returns {Promise} + */ +async function chooseArchive(prefix) { + const tarPath = `${prefix}.tar.gz` + const zipPath = `${prefix}.zip` + + if (await pathExists(tarPath)) return tarPath + if (await pathExists(zipPath)) return zipPath + + throw new Error(`No release artifact found for prefix ${prefix}`) +} + +/** + * @param {string} filePath + * @returns {Promise} + */ +async function pathExists(filePath) { + try { + await NodeFS.access(filePath) + return true + } catch { + return false + } +} + +/** + * Extract the archive into a temporary directory. + * @param {string} archivePath + * @returns {Promise} + */ +async function extractArchive(archivePath) { + const tempDir = await NodeFS.mkdtemp(NodePath.join(NodeOS.tmpdir(), 'foundry-npm-')) + const command = archivePath.endsWith('.zip') + ? Bun.$`unzip -o ${archivePath} -d ${tempDir}` + : Bun.$`tar -xzf ${archivePath} -C ${tempDir}` + + const result = await command + .env(process.env) + .nothrow() + + if (result.exitCode !== 0) { + const stderr = typeof result.stderr === 'string' + ? result.stderr + : result.stderr?.toString('utf8') + const stdout = typeof result.stdout === 'string' + ? result.stdout + : result.stdout?.toString('utf8') + throw new Error(`Failed to extract ${archivePath}: ${stderr || stdout || 'unknown error'}`) + } + + return tempDir +} + +/** + * Locate the expected binary within the extraction directory. + * @param {{ tool: Tool; platform: Platform; extractionDir: string }} options + * @returns {Promise} + */ +async function resolveExtractedBinary({ tool, platform, extractionDir }) { + const binaryName = platform === 'win32' ? `${tool}.exe` : tool + const candidate = NodePath.join(extractionDir, binaryName) + + if (await pathExists(candidate)) return candidate + + throw new Error(`Binary ${binaryName} not found in ${extractionDir}`) +} + +/** + * Delegate to prepublish to stage the extracted binary into the package dir. + * @param {{ tool: Tool; platform: Platform; arch: Arch; binaryPath: string }} options + * @returns {Promise} + */ +async function stagePackage({ tool, platform, arch, binaryPath }) { + console.info(colors.green, `Staging ${tool} (${platform}/${arch}) from ${binaryPath}`, colors.reset) + + const subprocess = Bun + .$`bun ./scripts/prepublish.mjs --tool ${tool} --binary ${binaryPath}` + .cwd(NodePath.resolve(import.meta.dir, '..')) + .env({ + ...process.env, + TARGET_TOOL: tool, + TOOL: tool, + PLATFORM_NAME: platform, + ARCH: arch + }) + + const result = await subprocess.nothrow() + if (result.exitCode !== 0) { + const stderr = typeof result.stderr === 'string' + ? result.stderr + : result.stderr?.toString('utf8') + throw new Error(stderr || `Failed to stage package for ${tool}`) + } +} diff --git a/npm/src/bin.mjs b/npm/src/bin.mjs new file mode 100644 index 0000000000000..2413fea428301 --- /dev/null +++ b/npm/src/bin.mjs @@ -0,0 +1,152 @@ +#!/usr/bin/env node + +import { BINARY_NAME, colors, KNOWN_TOOLS, PLATFORM_SPECIFIC_PACKAGE_NAME, resolveTargetTool } from '#const.mjs' +import * as NodeChildProcess from 'node:child_process' +import * as NodeFS from 'node:fs' +import * as NodeModule from 'node:module' +import * as NodePath from 'node:path' +import { fileURLToPath } from 'node:url' + +/** + * @typedef {import('#const.mjs').Tool} Tool + */ + +const require = NodeModule.createRequire(import.meta.url) +const __dirname = NodePath.dirname(fileURLToPath(import.meta.url)) + +const targetTool = resolveTool() +process.env.TARGET_TOOL ??= targetTool + +const binaryName = BINARY_NAME(targetTool) +const platformPackage = PLATFORM_SPECIFIC_PACKAGE_NAME(targetTool) + +if (!platformPackage) { + console.error(colors.red, 'Platform not supported!') + console.error(colors.reset) + console.error(colors.yellow, `Platform: ${process.platform}, Architecture: ${process.arch}`) + console.error(colors.reset) + process.exit(1) +} + +const child = NodeChildProcess.spawn( + selectBinaryPath(), + process.argv.slice(2), + { stdio: 'inherit' } +) + +/** + * @type {Record<'SIGINT' | 'SIGTERM', () => void>} + */ +const signalHandlers = { + SIGINT: () => forwardSignal('SIGINT'), + SIGTERM: () => forwardSignal('SIGTERM') +} + +for (const [signal, handler] of Object.entries(signalHandlers)) + process.on(signal, handler) + +/** + * Determines which tool wrapper is executing. + * @returns {Tool} + */ +function resolveTool() { + const candidates = [ + process.env.TARGET_TOOL, + toolFromPackageName(process.env.npm_package_name), + toolFromLocalPackage(), + toolFromPath() + ] + + for (const candidate of candidates) { + if (!candidate) continue + try { + return resolveTargetTool(candidate) + } catch { + // try next + } + } + + throw new Error('TARGET_TOOL must be set to one of: ' + KNOWN_TOOLS.join(', ')) +} + +/** + * Attempts to read the tool name from the nearest package.json. + * @returns {Tool | undefined} + */ +function toolFromLocalPackage() { + try { + const packageJsonPath = NodePath.join(__dirname, 'package.json') + if (!NodeFS.existsSync(packageJsonPath)) return undefined + const pkg = require(packageJsonPath) + return toolFromPackageName(pkg?.name) + } catch { + return undefined + } +} + +/** + * Extracts the tool name from an @foundry-rs scoped package identifier. + * @param {unknown} name + * @returns {Tool | undefined} + */ +function toolFromPackageName(name) { + if (typeof name !== 'string') return undefined + const match = name.match(/^@foundry-rs\/(forge|cast|anvil|chisel)(?:$|-)/) + return match ? /** @type {Tool} */ (match[1]) : undefined +} + +/** + * Walks up the directory tree to infer the tool name from the folder structure. + * @returns {Tool | undefined} + */ +function toolFromPath() { + const segments = NodePath.resolve(__dirname).split(NodePath.sep) + for (let i = segments.length - 1; i >= 0; i--) { + const candidate = segments[i] + if (isTool(candidate)) return candidate + } + return undefined +} + +/** + * Type guard verifying a candidate string is a known tool. + * @param {string | undefined} candidate + * @returns {candidate is Tool} + */ +function isTool(candidate) { + if (typeof candidate !== 'string') return false + return KNOWN_TOOLS.includes(/** @type {Tool} */ (candidate)) +} + +/** + * Determines the executable file path for the current platform. + * @returns {string} + */ +function selectBinaryPath() { + try { + const candidate = require.resolve(`${platformPackage}/bin/${binaryName}`) + if (NodeFS.existsSync(candidate)) return candidate + } catch { + // fall through to dist/ binary + } + + return NodePath.join(__dirname, '..', 'dist', binaryName) +} + +/** + * Forwards a received signal to the child process, then re-emits it locally to + * preserve Node.js default exit semantics. + * @param {'SIGINT' | 'SIGTERM'} signal + */ +function forwardSignal(signal) { + try { + if (!child.killed) + child.kill(signal) + } catch (error) { + if (!error || (typeof error === 'object' && 'code' in error && error.code !== 'ESRCH')) + throw error + } + + process.off(signal, signalHandlers[signal]) + process.kill(process.pid, signal) +} diff --git a/npm/src/const.mjs b/npm/src/const.mjs new file mode 100644 index 0000000000000..e606759888acb --- /dev/null +++ b/npm/src/const.mjs @@ -0,0 +1,113 @@ +import * as NodePath from 'node:path' +import { URL } from 'node:url' + +/** + * @typedef {'amd64' | 'arm64'} Arch + * @typedef {'linux' | 'darwin' | 'win32'} Platform + * @typedef {'forge' | 'cast' | 'anvil' | 'chisel'} Tool + * @typedef {'debug' | 'release' | 'maxperf' | 'dist'} Profile + */ + +/** @type {readonly Tool[]} */ +export const KNOWN_TOOLS = Object.freeze(['forge', 'cast', 'anvil', 'chisel']) + +const TOOL_SET = new Set(KNOWN_TOOLS) + +/** + * @param {string | undefined} [raw] + * @returns {Tool} + * + * could be process.argv[2] + */ +export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[2]) { + const value = typeof raw === 'string' ? raw.trim() : '' + if (!value) + throw new Error('TARGET_TOOL must be set to one of: ' + KNOWN_TOOLS.join(', ')) + if (value !== NodePath.basename(value) || value.includes('..') || value.includes('/') || value.includes('\\')) + throw new Error('TARGET_TOOL contains invalid path segments') + // @ts-expect-error _ + if (!TOOL_SET.has(value)) + throw new Error(`TARGET_TOOL "${value}" is not supported. Expected: ${KNOWN_TOOLS.join(', ')}`) + return /** @type {Tool} */ (value) +} + +export function getRegistryUrl() { + // Prefer npm's configured registry (works with Verdaccio and custom registries) + // Fallback to REGISTRY_URL for tests/dev, then npmjs + const raw = + process.env.npm_config_registry + || process.env.REGISTRY_URL + || 'https://registry.npmjs.org' + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') +} + +/** + * @param {Tool} tool + * @returns {Record>} + */ +export const BINARY_DISTRIBUTION_PACKAGES = tool => ({ + darwin: { + x64: `@foundry-rs/${tool}-darwin-amd64`, + arm64: `@foundry-rs/${tool}-darwin-arm64` + }, + linux: { + x64: `@foundry-rs/${tool}-linux-amd64`, + arm64: `@foundry-rs/${tool}-linux-arm64` + }, + win32: { + x64: `@foundry-rs/${tool}-win32-amd64` + } +}) + +/** + * @param {Tool} tool + * @returns {string} + */ +export const BINARY_NAME = tool => process.platform === 'win32' ? `${tool}.exe` : tool + +/** + * @param {Tool} tool + * @returns {string | undefined} + */ +export const PLATFORM_SPECIFIC_PACKAGE_NAME = tool => { + // @ts-ignore + const platformPackages = BINARY_DISTRIBUTION_PACKAGES(tool)[process.platform] + if (!platformPackages) return undefined + return platformPackages?.[process.arch] +} + +export const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + reset: '\x1b[0m' +} diff --git a/npm/src/generate-package-json.mjs b/npm/src/generate-package-json.mjs new file mode 100644 index 0000000000000..851b8e1c9279a --- /dev/null +++ b/npm/src/generate-package-json.mjs @@ -0,0 +1,108 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs/promises' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' + +import { colors } from '#const.mjs' + +/** + * @typedef {import('#const.mjs').Tool} Tool + * @typedef {import('#const.mjs').Arch} Arch + * @typedef {import('#const.mjs').Platform} Platform + * @typedef {{ + * tool: Tool + * platform: Platform + * arch: Arch + * packagePath: string + * }} GenerateOptions + */ + +const TOOL_META = /** @type {const} */ (/** @type {Record} */ ({ + forge: { + homepage: 'https://getfoundry.sh/forge', + description: 'Fast and flexible Ethereum testing framework' + }, + cast: { + homepage: 'https://getfoundry.sh/cast', + description: 'Swiss Army knife for interacting with Ethereum applications from the command line' + }, + anvil: { + homepage: 'https://getfoundry.sh/anvil', + description: 'Anvil is a fast local Ethereum development node' + }, + chisel: { + homepage: 'https://getfoundry.sh/chisel', + description: 'Chisel is a fast, utilitarian, and verbose Solidity REPL' + } +})) + +/** + * @param {GenerateOptions} options + * @returns {Promise} + */ +export async function generateBinaryPackageJson({ + tool, + platform, + arch, + packagePath +}) { + const packageJsonPath = NodePath.join(packagePath, 'package.json') + + const cpu = arch === 'amd64' ? 'x64' : 'arm64' + const isWindows = platform === 'win32' + const binName = isWindows ? `${tool}.exe` : tool + const humanPlatform = platform === 'darwin' ? 'macOS' : platform === 'win32' ? 'Windows' : 'Linux' + const { homepage, description } = TOOL_META[tool] + + const pkg = { + name: `@foundry-rs/${tool}-${platform}-${arch}`, + version: '0.0.0', + type: 'module', + homepage, + description: `${description} (${humanPlatform} ${cpu})`, + bin: { [tool]: `./bin/${binName}` }, + os: [platform], + cpu: [cpu], + files: ['bin'], + engines: { node: '>=20' }, + license: 'MIT OR Apache-2.0', + repository: { + directory: 'npm', + url: 'https://github.com/foundry-rs/foundry' + }, + keywords: ['foundry', 'testing', 'ethereum', 'solidity', 'blockchain', 'smart-contracts'], + publishConfig: { provenance: true } + } + + await Bun.write(packageJsonPath, JSON.stringify(pkg, null, 2)) + console.info(colors.green, `Wrote ${NodePath.relative(process.cwd(), packageJsonPath)}`, colors.reset) +} + +// CLI entrypoint so CI can call directly if desired +if (import.meta.main) { + const { values } = NodeUtil.parseArgs({ + args: Bun.argv, + options: { + tool: { type: 'string', default: Bun.env.TARGET_TOOL }, + platform: { type: 'string' }, + arch: { type: 'string' }, + out: { type: 'string' } + }, + strict: true + }) + + const tool = /** @type {Tool} */ (String(values.tool || Bun.env.TARGET_TOOL)) + const platform = /** @type {Platform} */ (values.platform || Bun.env.PLATFORM_NAME) + const arch = /** @type {Arch} */ (values.arch || Bun.env.ARCH) + const out = /** @type {string} */ (values.out || '') + + if (!platform || !arch) + throw new Error('platform and arch are required (flags or env PLATFORM_NAME/ARCH)') + if (!out) + throw new Error('out is required (path to per-arch package directory)') + + // Ensure the directory exists + await NodeFS.mkdir(out, { recursive: true }) + await generateBinaryPackageJson({ tool, platform, arch, packagePath: out }) +} diff --git a/npm/src/install.mjs b/npm/src/install.mjs new file mode 100644 index 0000000000000..231e251aa2bf4 --- /dev/null +++ b/npm/src/install.mjs @@ -0,0 +1,363 @@ +#!/usr/bin/env node + +import * as NodeCrypto from 'node:crypto' +import * as NodeFS from 'node:fs' +import * as NodeHttp from 'node:http' +import * as NodeHttps from 'node:https' +import * as NodeModule from 'node:module' +import * as NodePath from 'node:path' +import { fileURLToPath } from 'node:url' +import * as NodeZlib from 'node:zlib' + +import { BINARY_NAME, colors, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME, resolveTargetTool } from './const.mjs' + +const __dirname = NodePath.dirname(fileURLToPath(import.meta.url)) +const targetTool = resolveTargetTool() +const binaryName = BINARY_NAME(targetTool) +const fallbackBinaryPath = NodePath.join(__dirname, binaryName) +const platformSpecificPackageName = PLATFORM_SPECIFIC_PACKAGE_NAME(targetTool) + +const expectedTarEntryPath = `package/bin/${binaryName}` + +if (NodePath.relative(__dirname, fallbackBinaryPath).startsWith('..')) + throw new Error('Resolved binary path escapes package directory') + +if (!platformSpecificPackageName) throw new Error('Platform not supported!') + +const require = NodeModule.createRequire(import.meta.url) + +/** + * Enforce HTTPS except for localhost, unless explicitly allowed + * @param {string} urlString + * @param {string} purpose + * @returns {void} + */ +function ensureSecureUrl(urlString, purpose) { + try { + const url = new URL(urlString) + if (url.protocol === 'http:') { + const allowInsecure = process.env.ALLOW_INSECURE_REGISTRY === 'true' + if ( + // Accept typical localhost variants by default + !['localhost', '127.0.0.1', '::1'].includes(url.hostname) + && !allowInsecure + ) { + throw new Error( + `Refusing to use insecure HTTP for ${purpose}: ${urlString}. ` + + `Set ALLOW_INSECURE_REGISTRY=true to override (not recommended).` + ) + } + } + } catch { + // If parsing fails, the request will fail so no need to do anything here + } +} + +const MAX_REDIRECTS = 10 +const REQUEST_TIMEOUT = 30_000 // 30s +const MAX_METADATA_BYTES = 5 * 1024 * 1024 // 5 MiB +const MAX_TARBALL_BYTES = 200 * 1024 * 1024 // 200 MiB +const MAX_BINARY_BYTES = 500 * 1024 * 1024 // 500 MiB + +/** + * @param {string} url + * @param {{ + * parentSignal?: AbortSignal + * redirectDepth?: number + * visited?: Set + * maxBytes?: number + * collect?: boolean + * onChunk?: (chunk: Buffer, response: import('node:http').IncomingMessage) => void + * } | undefined} options + * @returns {Promise} + */ +export function makeRequest(url, options = {}) { + const { + parentSignal, + redirectDepth = 0, + visited = new Set(), + maxBytes, + collect = true, + onChunk + } = options + + if (redirectDepth > MAX_REDIRECTS) throw new Error('Maximum redirect depth exceeded') + + if (visited.has(url)) throw new Error('Circular redirect detected') + visited.add(url) + + ensureSecureUrl(url, 'HTTP request') + + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT) + const signal = parentSignal + ? AbortSignal.any([parentSignal, controller.signal]) + : controller.signal + + return new Promise((resolve, reject) => { + const client = url.startsWith('https:') ? NodeHttps : NodeHttp + const request = client.get(url, { signal }, response => { + /** + * @param {Error | null} error + * @param {Buffer | undefined} value + */ + const finish = (error, value = undefined) => { + clearTimeout(timer) + if (error) reject(error) + else resolve(value) + } + + if ( + response?.statusCode + && response.statusCode >= 200 + && response.statusCode < 300 + ) { + let totalBytes = 0 + /** @type {Array | undefined} */ + const chunks = collect ? [] : undefined + + response.on('data', chunk => { + totalBytes += chunk.length + if (maxBytes && totalBytes > maxBytes) { + response.destroy(new Error('Response exceeded maximum allowed size')) + return + } + + if (chunks) chunks.push(chunk) + + if (onChunk) { + try { + onChunk(chunk, response) + } catch (error) { + response.destroy( + error instanceof Error ? error : new Error(`Error occurred: ${error}`) + ) + } + } + }) + + response.on('end', () => { + finish(null, chunks ? Buffer.concat(chunks) : undefined) + }) + + response.on('error', finish) + } else if ( + response?.statusCode + && response.statusCode >= 300 + && response.statusCode < 400 + && response.headers.location + ) { + clearTimeout(timer) + const nextUrl = new URL(response.headers.location, url).href + + return makeRequest(nextUrl, { + parentSignal: signal, + redirectDepth: redirectDepth + 1, + visited, + maxBytes, + collect, + onChunk + }).then(resolve, reject) + } else { + finish( + new Error( + `Package registry responded with status code ${ + response?.statusCode ?? '(none)' + } when downloading the package.` + ) + ) + } + }) + + request.on('error', error => { + clearTimeout(timer) + reject(error) + }) + }) +} + +/** + * Tar archives are organized in 512 byte blocks. + * Blocks can either be header blocks or data blocks. + * Header blocks contain file names of the archive in the first 100 bytes, terminated by a null byte. + * The size of a file is contained in bytes 124-135 of a header block and in octal format. + * The following blocks will be data blocks containing the file. + * @param {Buffer} tarballBuffer + * @param {string} filepath + * @returns {Buffer} + */ +function extractFileFromTarball(tarballBuffer, filepath) { + let offset = 0 + while (offset < tarballBuffer.length) { + const header = tarballBuffer.subarray(offset, offset + 512) + offset += 512 + + const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '') + const fileSize = Number.parseInt( + header.toString('utf-8', 124, 136).replace(/\0.*/g, ''), + 8 + ) + + if (fileName === filepath) { + if (!Number.isFinite(fileSize) || Number.isNaN(fileSize) || fileSize < 0) + throw new Error(`Invalid size for ${filepath} in tarball`) + if (fileSize > MAX_BINARY_BYTES) + throw new Error(`Binary size for ${filepath} exceeds maximum allowed threshold`) + return tarballBuffer.subarray(offset, offset + fileSize) + } + + // Clamp offset to the upper multiple of 512 + offset = (offset + fileSize + 511) & ~511 + } + throw new Error(`File ${filepath} not found in tarball`) +} + +async function downloadBinaryFromRegistry() { + if (!platformSpecificPackageName) + throw new Error('Platform-specific package name is not defined') + + const registryUrl = getRegistryUrl().replace(/\/$/, '') + ensureSecureUrl(registryUrl, 'registry URL') + + // Scoped package names should be percent-encoded + const encodedName = platformSpecificPackageName.startsWith('@') + ? encodeURIComponent(platformSpecificPackageName) + : platformSpecificPackageName + + // Determine which version to fetch: prefer the version pinned in optionalDependencies + /** @type {string | undefined} */ + let desiredVersion + try { + const pkgJsonPath = NodePath.join(__dirname, '..', 'package.json') + const pkgJson = JSON.parse(NodeFS.readFileSync(pkgJsonPath, 'utf8')) + desiredVersion = pkgJson?.optionalDependencies[platformSpecificPackageName] + || pkgJson?.version + } catch {} + + // Fetch metadata for the platform-specific package + const metaUrl = `${registryUrl}/${encodedName}` + const metaBuffer = await makeRequest(metaUrl, { maxBytes: MAX_METADATA_BYTES }) + if (!metaBuffer) + throw new Error('Failed to download package metadata') + const metadata = JSON.parse(metaBuffer.toString('utf8')) + + const version = desiredVersion || metadata?.['dist-tags']?.latest + const versionMeta = metadata?.versions?.[version] + const dist = versionMeta?.dist + if (!dist?.tarball) { + throw new Error( + `Could not find tarball for ${platformSpecificPackageName}@${version} from ${metaUrl}` + ) + } + + // Guard tarball URL scheme + ensureSecureUrl(dist.tarball, 'tarball URL') + + console.info( + colors.green, + 'Downloading binary from:\n', + dist.tarball, + '\n', + colors.reset + ) + + /** + * Download the tarball of the right binary distribution package + * Verify integrity: prefer SRI integrity (sha512/sha256/sha1), + * fallback to legacy dist.shasum (sha1 hex). Fail if neither unless explicitly allowed. + */ + const integrity = typeof dist.integrity === 'string' ? dist.integrity : '' + const sriMatch = integrity.match(/^([a-z0-9]+)-([A-Za-z0-9+/=]+)$/i) + const allowedSRIAlgorithms = new Set(['sha512', 'sha256', 'sha1']) + const sriAlgo = sriMatch && allowedSRIAlgorithms.has(sriMatch[1].toLowerCase()) + ? sriMatch[1].toLowerCase() + : undefined + const expectedSri = sriAlgo ? sriMatch?.[2] : undefined + const sriHasher = sriAlgo ? NodeCrypto.createHash(sriAlgo) : undefined + + const expectedSha1Hex = typeof dist.shasum === 'string' && dist.shasum.length === 40 + ? dist.shasum.toLowerCase() + : undefined + const sha1Hasher = expectedSha1Hex ? NodeCrypto.createHash('sha1') : undefined + + const tarballDownloadBuffer = await makeRequest(dist.tarball, { + maxBytes: MAX_TARBALL_BYTES, + onChunk: chunk => { + sriHasher?.update(chunk) + sha1Hasher?.update(chunk) + } + }) + + if (!tarballDownloadBuffer) + throw new Error('Failed to download tarball contents') + + let verified = false + + if (sriHasher && expectedSri) { + const actual = sriHasher.digest('base64') + if (expectedSri !== actual) { + throw new Error( + `Downloaded tarball failed integrity check (${sriAlgo} mismatch)` + ) + } + verified = true + } + + if (!verified && sha1Hasher && expectedSha1Hex) { + const actualSha1Hex = sha1Hasher.digest('hex') + if (expectedSha1Hex !== actualSha1Hex) { + throw new Error( + 'Downloaded tarball failed integrity check (sha1 shasum mismatch)' + ) + } + verified = true + } + + if (!verified) { + const allowNoIntegrity = process.env.ALLOW_NO_INTEGRITY === 'true' + || process.env.ALLOW_UNVERIFIED_TARBALL === 'true' + if (!allowNoIntegrity) { + throw new Error( + 'No integrity metadata found for downloaded tarball. ' + + 'Set ALLOW_NO_INTEGRITY=true to bypass (not recommended).' + ) + } + console.warn( + colors.yellow, + 'Warning: proceeding without integrity verification (explicitly allowed).', + colors.reset + ) + } + + // Unpack and write binary + const tarballBuffer = NodeZlib.gunzipSync(tarballDownloadBuffer) + + NodeFS.writeFileSync( + fallbackBinaryPath, + extractFileFromTarball(tarballBuffer, expectedTarEntryPath), + { mode: 0o755 } // Make binary file executable + ) +} + +function isPlatformSpecificPackageInstalled() { + try { + // Resolving will fail if the optionalDependency was not installed + require.resolve(`${platformSpecificPackageName}/bin/${binaryName}`) + return true + } catch { + return false + } +} + +// Skip downloading the binary if it was already installed via optionalDependencies +if (!isPlatformSpecificPackageInstalled()) { + console.log('Platform specific package not found. Will manually download binary.') + downloadBinaryFromRegistry().catch(error => { + console.error(colors.red, 'Failed to download binary:', error, colors.reset) + process.exitCode = 1 + }) +} else { + console.log( + 'Platform specific package already installed. Skipping manual download.' + ) +} diff --git a/npm/tsconfig.json b/npm/tsconfig.json new file mode 100644 index 0000000000000..a7a0093d7ee56 --- /dev/null +++ b/npm/tsconfig.json @@ -0,0 +1,50 @@ +{ + "schema": "https://json.schemastore.org/tsconfig.json", + "compilerOptions": { + "strict": true, + "noEmit": true, + "allowJs": true, + "checkJs": true, + "lib": [ + "ESNext" + ], + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true, + "alwaysStrict": true, + "esModuleInterop": true, + "isolatedModules": true, + "strictNullChecks": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "moduleResolution": "Bundler", + "useDefineForClassFields": true, + "noUncheckedIndexedAccess": true, + "resolvePackageJsonImports": true, + "resolvePackageJsonExports": true, + "useUnknownInCatchVariables": true, + "allowImportingTsExtensions": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "types": [ + "bun", + "node" + ] + }, + "exclude": [ + "dist", + "**/_/**", + "node_modules" + ], + "include": [ + "./src/**/*", + "./src/*.mjs", + "./scripts/**/*", + "./scripts/*.mjs" + ], + "files": [ + "env.d.ts", + "package.json" + ] +} diff --git a/rustfmt.toml b/rustfmt.toml index 68c3c93033d4f..3063df707a64e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,11 +1,12 @@ reorder_imports = true -imports_granularity = "Crate" +use_field_init_shorthand = true use_small_heuristics = "Max" + +# Nightly +max_width = 100 comment_width = 100 +imports_granularity = "Crate" wrap_comments = true -binop_separator = "Back" -trailing_comma = "Vertical" -trailing_semicolon = false -use_field_init_shorthand = true format_code_in_doc_comments = true doc_comment_code_block_width = 100 +format_macro_matchers = true diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +} diff --git a/testdata/default/cheats/AccessList.t.sol b/testdata/default/cheats/AccessList.t.sol index 4615ab588adba..f59e8cdb439c3 100644 --- a/testdata/default/cheats/AccessList.t.sol +++ b/testdata/default/cheats/AccessList.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AccessListIsolatedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +/// forge-config: default.isolate = true +contract AccessListIsolatedTest is Test { function test_access_list() public { Write anotherWrite = new Write(); Write write = new Write(); diff --git a/testdata/default/cheats/Addr.t.sol b/testdata/default/cheats/Addr.t.sol index b0b3fefbdba79..82b8cf7d277f0 100644 --- a/testdata/default/cheats/Addr.t.sol +++ b/testdata/default/cheats/Addr.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AddrTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract AddrTest is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevertIfPkZero() public { vm.expectRevert("vm.addr: private key cannot be 0"); diff --git a/testdata/default/cheats/ArbitraryStorage.t.sol b/testdata/default/cheats/ArbitraryStorage.t.sol index 1b5585b681b3d..407d14448a996 100644 --- a/testdata/default/cheats/ArbitraryStorage.t.sol +++ b/testdata/default/cheats/ArbitraryStorage.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public a; @@ -27,9 +26,8 @@ contract Counter { } } -contract CounterArbitraryStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract CounterArbitraryStorageWithSeedTest is Test { function test_fresh_storage() public { uint256 index = 55; Counter counter = new Counter(); @@ -77,9 +75,8 @@ contract AContract { bytes32[] public d; } -contract AContractArbitraryStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract AContractArbitraryStorageWithSeedTest is Test { function test_arbitrary_storage_with_seed() public { AContract target = new AContract(); vm.setArbitraryStorage(address(target)); @@ -96,9 +93,8 @@ contract SymbolicStore { constructor() {} } -contract SymbolicStorageWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.fuzz.seed = "100" +contract SymbolicStorageWithSeedTest is Test { function test_SymbolicStorage() public { uint256 slot = vm.randomUint(0, 100); address addr = 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8; @@ -125,8 +121,8 @@ contract SymbolicStorageWithSeedTest is DSTest { } // -contract ArbitraryStorageOverwriteWithSeedTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); +/// forge-config: default.fuzz.seed = "100" +contract ArbitraryStorageOverwriteWithSeedTest is Test { uint256 _value; function testArbitraryStorageFalse(uint256 value) public { diff --git a/testdata/default/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol index d6765967c5faa..d29a7dba3e604 100644 --- a/testdata/default/cheats/Assert.t.sol +++ b/testdata/default/cheats/Assert.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract AssertionsTest is DSTest { +contract AssertionsTest is Test { string constant errorMessage = "User provided message"; uint256 constant maxDecimals = 77; - Vm constant vm = Vm(HEVM_ADDRESS); - function _abs(int256 a) internal pure returns (uint256) { // Required or it will fail when `a = type(int256).min` if (a == type(int256).min) { @@ -804,22 +801,21 @@ contract AssertionsTest is DSTest { } function testAssertApproxEqRel() public { - vm._expectCheatcodeRevert(bytes("assertion failed: overflow in delta calculation")); - vm.assertApproxEqRel(type(int256).min, type(int256).max, 0); - vm._expectCheatcodeRevert( bytes(string.concat(errorMessage, ": 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)")) ); vm.assertApproxEqRel(int256(1), int256(0), 0, errorMessage); - vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": overflow in delta calculation"))); - vm.assertApproxEqRel(uint256(0), type(uint256).max, 0, errorMessage); - vm._expectCheatcodeRevert( bytes("assertion failed: 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)") ); vm.assertApproxEqRel(uint256(1), uint256(0), uint256(0)); + vm._expectCheatcodeRevert(bytes("assertion failed: overflow in delta calculation")); + vm.assertApproxEqRel(type(uint256).max, 1, 1); + + vm.assertApproxEqRel(type(int256).min, type(int256).max, 2e18); + vm.assertApproxEqRel(uint256(0), type(uint256).max, 1e18); vm.assertApproxEqRel(uint256(0), uint256(0), uint256(0)); } } diff --git a/testdata/default/cheats/Assume.t.sol b/testdata/default/cheats/Assume.t.sol index 14ed341c9970e..91de77f246e3b 100644 --- a/testdata/default/cheats/Assume.t.sol +++ b/testdata/default/cheats/Assume.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract AssumeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract AssumeTest is Test { function testAssume(uint8 x) public { vm.assume(x < 2 ** 7); assertTrue(x < 2 ** 7, "did not discard inputs"); diff --git a/testdata/default/cheats/AssumeNoRevert.t.sol b/testdata/default/cheats/AssumeNoRevert.t.sol index ea6d2d9747bdd..f6a017efec212 100644 --- a/testdata/default/cheats/AssumeNoRevert.t.sol +++ b/testdata/default/cheats/AssumeNoRevert.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import {DSTest as Test} from "ds-test/test.sol"; -import {Vm} from "cheats/Vm.sol"; +import "utils/Test.sol"; contract ReverterB { /// @notice has same error selectors as contract below to test the `reverter` param @@ -62,7 +61,6 @@ contract Reverter { contract ReverterTest is Test { Reverter reverter; - Vm _vm = Vm(HEVM_ADDRESS); function setUp() public { reverter = new Reverter(); @@ -70,7 +68,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector function testAssumeSelector(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, @@ -82,7 +80,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector and data function testAssumeWithDataSingle(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 2), partialMatch: false, @@ -94,7 +92,7 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector with any extra data (ie providing selector allows for arbitrary extra data) function testAssumeWithDataPartial(uint256 x) public view { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), partialMatch: true, @@ -106,14 +104,14 @@ contract ReverterTest is Test { /// @dev Test that `assumeNoRevert` assumptions are not cleared after a cheatcode call function testAssumeNotClearedAfterCheatcodeCall(uint256 x) public { - _vm.assumeNoRevert( + vm.assumeNoRevert( Vm.PotentialRevert({ revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(0) }) ); - _vm.warp(block.timestamp + 1000); + vm.warp(block.timestamp + 1000); reverter.revertIf2(x); } @@ -130,7 +128,7 @@ contract ReverterTest is Test { partialMatch: false, reverter: address(reverter) }); - _vm.assumeNoRevert(revertData); + vm.assumeNoRevert(revertData); reverter.twoPossibleReverts(x); } @@ -147,7 +145,7 @@ contract ReverterTest is Test { partialMatch: false, reverter: address(reverter) }); - _vm.assumeNoRevert(revertData); + vm.assumeNoRevert(revertData); reverter.twoPossibleReverts(x); } } diff --git a/testdata/default/cheats/AttachBlob.t.sol b/testdata/default/cheats/AttachBlob.t.sol index db17c1fae5f4c..1be49ceef2e5c 100644 --- a/testdata/default/cheats/AttachBlob.t.sol +++ b/testdata/default/cheats/AttachBlob.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public counter; @@ -12,8 +11,7 @@ contract Counter { } } -contract AttachBlobTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract AttachBlobTest is Test { uint256 bobPk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; address bob = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; diff --git a/testdata/default/cheats/AttachDelegation.t.sol b/testdata/default/cheats/AttachDelegation.t.sol index 91535b85718f0..63e00462d85ba 100644 --- a/testdata/default/cheats/AttachDelegation.t.sol +++ b/testdata/default/cheats/AttachDelegation.t.sol @@ -1,13 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract AttachDelegationTest is DSTest { +contract AttachDelegationTest is Test { event ExecutedBy(uint256 id); - Vm constant vm = Vm(HEVM_ADDRESS); uint256 alice_pk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; address payable alice = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); uint256 bob_pk = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; @@ -62,7 +60,7 @@ contract AttachDelegationTest is DSTest { vm._expectCheatcodeRevert("vm.attachDelegation: invalid nonce"); vm.attachDelegation(signedDelegation); - signedDelegation = vm.signDelegation(address(implementation), alice_pk, 1); + signedDelegation = vm.signDelegation(address(implementation), alice_pk, 0); vm.attachDelegation(signedDelegation); } @@ -75,9 +73,7 @@ contract AttachDelegationTest is DSTest { calls[0] = SimpleDelegateContract.Call({to: address(token), data: abi.encodeCall(ERC20.mint, (50, bob)), value: 0}); calls[1] = SimpleDelegateContract.Call({ - to: address(token), - data: abi.encodeCall(ERC20.mint, (50, address(this))), - value: 0 + to: address(token), data: abi.encodeCall(ERC20.mint, (50, address(this))), value: 0 }); SimpleDelegateContract(alice).execute(calls); @@ -95,9 +91,7 @@ contract AttachDelegationTest is DSTest { calls[0] = SimpleDelegateContract.Call({to: address(token), data: abi.encodeCall(ERC20.mint, (50, bob)), value: 0}); calls[1] = SimpleDelegateContract.Call({ - to: address(token), - data: abi.encodeCall(ERC20.mint, (50, address(this))), - value: 0 + to: address(token), data: abi.encodeCall(ERC20.mint, (50, address(this))), value: 0 }); SimpleDelegateContract(alice).execute(calls); @@ -192,7 +186,28 @@ contract AttachDelegationTest is DSTest { vm._expectCheatcodeRevert("vm.signAndAttachDelegation: invalid nonce"); vm.signAndAttachDelegation(address(implementation), alice_pk, 11); + vm.signAndAttachDelegation(address(implementation), alice_pk, 0); + } + + function testMultipleDelegationsOnTransaction() public { + vm.signAndAttachDelegation(address(implementation), alice_pk); + vm.signAndAttachDelegation(address(implementation2), bob_pk); + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](2); + calls[0] = SimpleDelegateContract.Call({ + to: address(token), data: abi.encodeCall(ERC20.mint, (50, address(this))), value: 0 + }); + calls[1] = + SimpleDelegateContract.Call({to: address(token), data: abi.encodeCall(ERC20.mint, (50, alice)), value: 0}); + vm.broadcast(bob_pk); + SimpleDelegateContract(alice).execute(calls); + + assertEq(token.balanceOf(address(this)), 50); + assertEq(token.balanceOf(alice), 50); + + vm._expectCheatcodeRevert("vm.signAndAttachDelegation: invalid nonce"); vm.signAndAttachDelegation(address(implementation), alice_pk, 1); + vm.signAndAttachDelegation(address(implementation), alice_pk, 0); + vm.signAndAttachDelegation(address(implementation2), bob_pk, 2); } } diff --git a/testdata/default/cheats/Bank.t.sol b/testdata/default/cheats/Bank.t.sol index 166fbb16ac8ea..bcda62aa6d676 100644 --- a/testdata/default/cheats/Bank.t.sol +++ b/testdata/default/cheats/Bank.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract CoinbaseTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract CoinbaseTest is Test { function testCoinbase() public { vm.coinbase(0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8); assertEq(block.coinbase, 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8, "coinbase failed"); diff --git a/testdata/default/cheats/Base64.t.sol b/testdata/default/cheats/Base64.t.sol index fad7bbf4f297c..a80aa78c39dce 100644 --- a/testdata/default/cheats/Base64.t.sol +++ b/testdata/default/cheats/Base64.t.sol @@ -1,13 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; - -contract Base64Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract Base64Test is Test { function test_toBase64() public { bytes memory input = hex"00112233445566778899aabbccddeeff"; string memory expected = "ABEiM0RVZneImaq7zN3u/w=="; @@ -15,10 +11,24 @@ contract Base64Test is DSTest { assertEq(actual, expected); } + function test_toBase64_string() public { + string memory input = "Hello, World!"; + string memory expected = "SGVsbG8sIFdvcmxkIQ=="; + string memory actual = vm.toBase64(input); + assertEq(actual, expected); + } + function test_toBase64URL() public { bytes memory input = hex"00112233445566778899aabbccddeeff"; string memory expected = "ABEiM0RVZneImaq7zN3u_w=="; string memory actual = vm.toBase64URL(input); assertEq(actual, expected); } + + function test_toBase64URL_string() public { + string memory input = "Hello, World!"; + string memory expected = "SGVsbG8sIFdvcmxkIQ=="; + string memory actual = vm.toBase64URL(input); + assertEq(actual, expected); + } } diff --git a/testdata/default/cheats/BlobBaseFee.t.sol b/testdata/default/cheats/BlobBaseFee.t.sol index 54fbc8f7f0616..6ce979d046eda 100644 --- a/testdata/default/cheats/BlobBaseFee.t.sol +++ b/testdata/default/cheats/BlobBaseFee.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BlobBaseFeeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BlobBaseFeeTest is Test { function test_blob_base_fee() public { vm.blobBaseFee(6969); assertEq(vm.getBlobBaseFee(), 6969); diff --git a/testdata/default/cheats/Blobhashes.t.sol b/testdata/default/cheats/Blobhashes.t.sol index 4a589b45a38ff..6cf672c540d29 100644 --- a/testdata/default/cheats/Blobhashes.t.sol +++ b/testdata/default/cheats/Blobhashes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.25; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BlobhashesTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BlobhashesTest is Test { function testSetAndGetBlobhashes() public { bytes32[] memory blobhashes = new bytes32[](2); blobhashes[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000001); diff --git a/testdata/default/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol index 9916ca0efc799..494478fa4711a 100644 --- a/testdata/default/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import {Test as ForgeTest} from "utils/Test.sol"; library F { function t2() public pure returns (uint256) { @@ -10,7 +9,7 @@ library F { } } -contract Test is DSTest { +contract Test is ForgeTest { uint256 public changed = 0; function t(uint256 a) public returns (uint256) { @@ -22,7 +21,7 @@ contract Test is DSTest { return b; } - function inc() public returns (uint256) { + function inc() public { changed += 1; } @@ -33,9 +32,7 @@ contract Test is DSTest { } } -contract BroadcastTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTest is ForgeTest { // 1st anvil account address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // 2nd anvil account @@ -160,7 +157,7 @@ contract BroadcastTest is DSTest { // } } -contract NoLink is DSTest { +contract NoLink is ForgeTest { function t(uint256 a) public returns (uint256) { uint256 b = 0; for (uint256 i; i < a; i++) { @@ -179,9 +176,7 @@ interface INoLink { function t(uint256 a) external returns (uint256); } -contract BroadcastTestNoLinking is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestNoLinking is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -254,9 +249,7 @@ contract BroadcastTestNoLinking is DSTest { } } -contract BroadcastMix is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastMix is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -312,9 +305,7 @@ contract BroadcastMix is DSTest { } } -contract BroadcastTestSetup is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestSetup is ForgeTest { function setUp() public { // It predeployed a library first assert(vm.getNonce(msg.sender) == 1); @@ -338,9 +329,7 @@ contract BroadcastTestSetup is DSTest { } } -contract BroadcastTestLog is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastTestLog is ForgeTest { function run() public { uint256[] memory arr = new uint256[](2); arr[0] = 3; @@ -361,14 +350,12 @@ contract BroadcastTestLog is DSTest { } } -contract TestInitialBalance is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract TestInitialBalance is ForgeTest { function runCustomSender() public { // Make sure we're testing a different caller than the default one. assert(msg.sender != address(0x00a329c0648769A73afAc7F9381E08FB43dBEA72)); - // NodeConfig::test() sets the balance of the address used in this test to 100 ether. + // NodeConfig::test() sets the balance of the address used in this ForgeTest to 100 ether. assert(msg.sender.balance == 100 ether); vm.broadcast(); @@ -386,9 +373,7 @@ contract TestInitialBalance is DSTest { } } -contract MultiChainBroadcastNoLink is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MultiChainBroadcastNoLink is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -431,9 +416,7 @@ contract MultiChainBroadcastNoLink is DSTest { } } -contract MultiChainBroadcastLink is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MultiChainBroadcastLink is ForgeTest { // ganache-cli -d 1st address public ACCOUNT_A = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; @@ -454,9 +437,7 @@ contract MultiChainBroadcastLink is DSTest { } } -contract BroadcastEmptySetUp is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract BroadcastEmptySetUp is ForgeTest { function setUp() public {} function run() public { @@ -495,9 +476,7 @@ contract ContractB { } } -contract CheckOverrides is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract CheckOverrides is ForgeTest { function run() external { // `script_caller` can be set by `--private-key ...` or `--sender ...` // Otherwise it will take the default value of 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 @@ -546,9 +525,7 @@ contract Parent { } } -contract ScriptAdditionalContracts is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ScriptAdditionalContracts is ForgeTest { function run() external { vm.startBroadcast(); new Parent(); @@ -562,13 +539,12 @@ contract SignatureTester { owner = msg.sender; } - function verifySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + function verifySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s) public view { require(ecrecover(digest, v, r, s) == owner, "Invalid signature"); } } -contract ScriptSign is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ScriptSign is ForgeTest { bytes32 digest = keccak256("something"); function run() external { diff --git a/testdata/default/cheats/BroadcastRawTransaction.t.sol b/testdata/default/cheats/BroadcastRawTransaction.t.sol index 36682bc893359..9cf6c4950e37a 100644 --- a/testdata/default/cheats/BroadcastRawTransaction.t.sol +++ b/testdata/default/cheats/BroadcastRawTransaction.t.sol @@ -1,19 +1,16 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract BroadcastRawTransactionTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract BroadcastRawTransactionTest is Test { function test_revert_not_a_tx() public { vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: unexpected string"); vm.broadcastRawTransaction(hex"0102"); } function test_revert_missing_signature() public { - vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: input too short"); + vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: Unexpected type flag"); vm.broadcastRawTransaction(hex"dd806483030d40940993863c19b0defb183ca2b502db7d1b331ded757b80"); } @@ -277,9 +274,7 @@ contract MyERC20 { } } -contract ScriptBroadcastRawTransactionBroadcast is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ScriptBroadcastRawTransactionBroadcast is Test { function runSignedTxBroadcast() public { uint256 pk_to = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; vm.startBroadcast(pk_to); diff --git a/testdata/default/cheats/ChainId.t.sol b/testdata/default/cheats/ChainId.t.sol index ef0108e7e208e..bc2c69b798942 100644 --- a/testdata/default/cheats/ChainId.t.sol +++ b/testdata/default/cheats/ChainId.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ChainIdTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ChainIdTest is Test { function testChainId() public { uint256 newChainId = 99; vm.chainId(newChainId); diff --git a/testdata/default/cheats/CloneAccount.t.sol b/testdata/default/cheats/CloneAccount.t.sol index d584c747cb9b9..95eccd843ee09 100644 --- a/testdata/default/cheats/CloneAccount.t.sol +++ b/testdata/default/cheats/CloneAccount.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Source { uint256 public a; @@ -20,9 +19,7 @@ contract Source { } } -contract CloneAccountTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +contract CloneAccountTest is Test { address clone = address(777); function setUp() public { diff --git a/testdata/default/cheats/Cool.t.sol b/testdata/default/cheats/Cool.t.sol index d0750bebfa18e..71e19e4f8fd88 100644 --- a/testdata/default/cheats/Cool.t.sol +++ b/testdata/default/cheats/Cool.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "lib/ds-test/src/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract CoolTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract CoolTest is Test { uint256 public slot0 = 1; function testCool_SLOAD_normal() public { diff --git a/testdata/default/cheats/CopyStorage.t.sol b/testdata/default/cheats/CopyStorage.t.sol index e9195949e49c9..881a0c0aacf57 100644 --- a/testdata/default/cheats/CopyStorage.t.sol +++ b/testdata/default/cheats/CopyStorage.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public a; @@ -18,10 +17,10 @@ contract Counter { } } -contract CounterWithSeedTest is DSTest { +/// forge-config: default.fuzz.seed = "100" +contract CounterWithSeedTest is Test { Counter public counter; Counter public counter1; - Vm vm = Vm(HEVM_ADDRESS); function test_copy_storage() public { counter = new Counter(); @@ -67,11 +66,10 @@ contract CopyStorageContract { uint256 public x; } -contract CopyStorageTest is DSTest { +contract CopyStorageTest is Test { CopyStorageContract csc_1; CopyStorageContract csc_2; CopyStorageContract csc_3; - Vm vm = Vm(HEVM_ADDRESS); function _storeUInt256(address contractAddress, uint256 slot, uint256 value) internal { vm.store(contractAddress, bytes32(slot), bytes32(value)); diff --git a/testdata/default/cheats/CurrentFilePath.t.sol b/testdata/default/cheats/CurrentFilePath.t.sol new file mode 100644 index 0000000000000..6628c5f455fc2 --- /dev/null +++ b/testdata/default/cheats/CurrentFilePath.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract CurrentFilePathTest is Test { + function testCurrentFilePath() public { + string memory filePath = vm.currentFilePath(); + // The path should be relative to the project root and point to this test file. + assertEq(normalizePath(filePath), "default/cheats/CurrentFilePath.t.sol"); + } + + function testCurrentFilePathIsNotEmpty() public { + string memory filePath = vm.currentFilePath(); + assertTrue(bytes(filePath).length > 0, "currentFilePath() should not return an empty string"); + } + + function normalizePath(string memory path) internal pure returns (string memory) { + return vm.replace(path, "\\", "/"); + } +} diff --git a/testdata/default/cheats/Deal.t.sol b/testdata/default/cheats/Deal.t.sol index a46d9e7140e3e..4fcb37249aa67 100644 --- a/testdata/default/cheats/Deal.t.sol +++ b/testdata/default/cheats/Deal.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DealTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract DealTest is Test { function testDeal(uint256 amount) public { address target = address(10); assertEq(target.balance, 0, "initial balance incorrect"); diff --git a/testdata/default/cheats/DeployCode.t.sol b/testdata/default/cheats/DeployCode.t.sol index 3978cfc335f5f..9499a9ff7f6fc 100644 --- a/testdata/default/cheats/DeployCode.t.sol +++ b/testdata/default/cheats/DeployCode.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} @@ -36,9 +35,7 @@ contract TestPayableContractWithArgs { } } -contract DeployCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DeployCodeTest is Test { address public constant overrideAddress = 0x0000000000000000000000000000000000000064; event Payload(address sender, address target, bytes data); diff --git a/testdata/default/cheats/Derive.t.sol b/testdata/default/cheats/Derive.t.sol index c27456c6ec487..5ed5bd709c5e3 100644 --- a/testdata/default/cheats/Derive.t.sol +++ b/testdata/default/cheats/Derive.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DeriveTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract DeriveTest is Test { function testDerive() public { string memory mnemonic = "test test test test test test test test test test test junk"; diff --git a/testdata/default/cheats/Ed25519.t.sol b/testdata/default/cheats/Ed25519.t.sol new file mode 100644 index 0000000000000..a7cdeae4ce3fc --- /dev/null +++ b/testdata/default/cheats/Ed25519.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract Ed25519Test is Test { + function testCreateEd25519Key() public { + bytes32 salt = bytes32(uint256(1)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + assertTrue(publicKey != bytes32(0), "public key should not be zero"); + assertEq(privateKey, salt, "private key should equal salt"); + } + + function testCreateEd25519KeyDeterministic() public { + bytes32 salt = bytes32(uint256(42)); + (bytes32 pub1, bytes32 priv1) = vm.createEd25519Key(salt); + (bytes32 pub2, bytes32 priv2) = vm.createEd25519Key(salt); + assertEq(pub1, pub2, "same salt should produce same public key"); + assertEq(priv1, priv2, "same salt should produce same private key"); + } + + function testCreateEd25519KeyDifferentSalts() public { + bytes32 salt1 = bytes32(uint256(1)); + bytes32 salt2 = bytes32(uint256(2)); + (bytes32 pub1,) = vm.createEd25519Key(salt1); + (bytes32 pub2,) = vm.createEd25519Key(salt2); + assertTrue(pub1 != pub2, "different salts should produce different public keys"); + } + + function testPublicKeyEd25519() public { + bytes32 salt = bytes32(uint256(123)); + (bytes32 expectedPub, bytes32 privateKey) = vm.createEd25519Key(salt); + bytes32 derivedPub = vm.publicKeyEd25519(privateKey); + assertEq(derivedPub, expectedPub, "derived public key should match created one"); + } + + function testSignAndVerifyEd25519() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "test.namespace"; + bytes memory message = "hello world"; + + bytes memory signature = vm.signEd25519(namespace, message, privateKey); + assertEq(signature.length, 64, "signature should be 64 bytes"); + + bool valid = vm.verifyEd25519(signature, namespace, message, publicKey); + assertTrue(valid, "signature should be valid"); + } + + function testVerifyEd25519WrongMessage() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "ns"; + bytes memory signature = vm.signEd25519(namespace, "correct message", privateKey); + + bool valid = vm.verifyEd25519(signature, namespace, "wrong message", publicKey); + vm.assertFalse(valid, "signature should not verify with wrong message"); + } + + function testVerifyEd25519NamespaceSeparation() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory message = "message"; + bytes memory signature = vm.signEd25519("namespace.a", message, privateKey); + + bool valid = vm.verifyEd25519(signature, "namespace.b", message, publicKey); + vm.assertFalse(valid, "signature should not verify with different namespace"); + + valid = vm.verifyEd25519(signature, "namespace.a", message, publicKey); + assertTrue(valid, "signature should verify with correct namespace"); + } + + function testVerifyEd25519InvalidSignature() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey,) = vm.createEd25519Key(salt); + + bytes memory invalidSig = new bytes(64); + bool valid = vm.verifyEd25519(invalidSig, "ns", "msg", publicKey); + vm.assertFalse(valid, "zero signature should not verify"); + } + + function testVerifyEd25519WrongSignatureLength() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (bytes32 publicKey,) = vm.createEd25519Key(salt); + + bytes memory shortSig = new bytes(32); + bool valid = vm.verifyEd25519(shortSig, "ns", "msg", publicKey); + vm.assertFalse(valid, "short signature should not verify"); + } + + function testSignEd25519Deterministic() public { + bytes32 salt = bytes32(uint256(0xdeadbeef)); + (, bytes32 privateKey) = vm.createEd25519Key(salt); + + bytes memory namespace = "ns"; + bytes memory message = "msg"; + + bytes memory sig1 = vm.signEd25519(namespace, message, privateKey); + bytes memory sig2 = vm.signEd25519(namespace, message, privateKey); + assertEq(sig1, sig2, "same inputs should produce same signature"); + } +} diff --git a/testdata/default/cheats/EnsNamehash.t.sol b/testdata/default/cheats/EnsNamehash.t.sol index 965d505006060..94af39a8eb784 100644 --- a/testdata/default/cheats/EnsNamehash.t.sol +++ b/testdata/default/cheats/EnsNamehash.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EnsNamehashTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EnsNamehashTest is Test { function testEnsNamehash() public { assertEq(vm.ensNamehash(""), 0x0000000000000000000000000000000000000000000000000000000000000000); assertEq(vm.ensNamehash("eth"), 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae); diff --git a/testdata/default/cheats/Env.t.sol b/testdata/default/cheats/Env.t.sol index 7edb35dff13ff..641167f3dfb14 100644 --- a/testdata/default/cheats/Env.t.sol +++ b/testdata/default/cheats/Env.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EnvTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EnvTest is Test { function testSetEnv() public { string memory key = "_foundryCheatcodeSetEnvTestKey"; string memory val = "_foundryCheatcodeSetEnvTestVal"; diff --git a/testdata/default/cheats/Etch.t.sol b/testdata/default/cheats/Etch.t.sol index 5e93dfb81213b..6bd4c28914052 100644 --- a/testdata/default/cheats/Etch.t.sol +++ b/testdata/default/cheats/Etch.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract EtchTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract EtchTest is Test { function testEtch() public { address target = address(7070707); bytes memory code = hex"1010"; diff --git a/testdata/default/cheats/ExecuteTransaction.t.sol b/testdata/default/cheats/ExecuteTransaction.t.sol new file mode 100644 index 0000000000000..b86a3b96c42ff --- /dev/null +++ b/testdata/default/cheats/ExecuteTransaction.t.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract ExecuteTransactionTest is Test { + function test_revert_not_a_tx() public { + vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: unexpected string"); + vm.executeTransaction(hex"0102"); + } + + function test_execute_legacy_transfer() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 17; + + vm.deal(address(from), balance); + assertEq(address(from).balance, balance); + assertEq(address(to).balance, 0); + + /* + Legacy signed transaction (type 0): + { from: 0x5316812db67073c4d4af8bb3000c5b86c2877e94, to: 0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6, gas: 200000, gasPrice: 100, value: 17, nonce: 0, chainId: 1 } + */ + vm.executeTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + // Gas price is set to 0 in isolated execution, so no gas cost deducted. + assertEq(address(from).balance, balance - amountSent); + assertEq(address(to).balance, amountSent); + } + + function test_execute_eip1559_transfer() public { + vm.chainId(1); + + address from = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 42; + + vm.deal(from, balance); + assertEq(from.balance, balance); + assertEq(to.balance, 0); + + /* + EIP-1559 signed transaction (type 2): + { from: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, to: 0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6, gas: 21000, maxFeePerGas: 100, maxPriorityFeePerGas: 10, value: 42, nonce: 0, chainId: 1 } + */ + vm.executeTransaction( + hex"02f86201800a64825208946fd0a0cff9a87adf51695b40b4fa267855a8f4c62a80c080a03447a5bb5068bea134c052824759b5dd973aefcf745d0d67a6e2ee6543571f2ca05f3ee9f04a4d3cbc883f5a8b68cb6149fbc47083bb7f4abf644df780f2f11638" + ); + + // Gas price is set to 0 in isolated execution, so no gas cost deducted. + assertEq(from.balance, balance - amountSent); + assertEq(to.balance, amountSent); + } + + function test_execute_erc20_transfer() public { + vm.fee(1); + vm.chainId(1); + + address alice = 0x7ED31830602f9F7419307235c0610Fb262AA0375; + address bob = 0x70CF146aB98ffD5dE24e75dd7423F16181Da8E13; + address charlie = 0xae0900Cf97f8C233c64F7089cEC7d5457215BB8d; + + bytes memory code = + hex"608060405234801561001057600080fd5b50600436106100625760003560e01c8063095ea7b31461006757806323b872dd1461008f57806370a08231146100a257806394bf804d146100d9578063a9059cbb146100ee578063dd62ed3e14610101575b600080fd5b61007a61007536600461051d565b61013a565b60405190151581526020015b60405180910390f35b61007a61009d366004610547565b610152565b6100cb6100b0366004610583565b6001600160a01b031660009081526020819052604090205490565b604051908152602001610086565b6100ec6100e73660046105a5565b610176565b005b61007a6100fc36600461051d565b610184565b6100cb61010f3660046105d1565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600033610148818585610192565b5060019392505050565b600033610160858285610286565b61016b858585610318565b506001949350505050565b6101808183610489565b5050565b600033610148818585610318565b6001600160a01b0383166101f95760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084015b60405180910390fd5b6001600160a01b03821661025a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016101f0565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461031257818110156103055760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016101f0565b6103128484848403610192565b50505050565b6001600160a01b03831661037c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016101f0565b6001600160a01b0382166103de5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016101f0565b6001600160a01b038316600090815260208190526040902054818110156104565760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016101f0565b6001600160a01b039384166000908152602081905260408082209284900390925592909316825291902080549091019055565b6001600160a01b0382166104df5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016101f0565b6001600160a01b03909116600090815260208190526040902080549091019055565b80356001600160a01b038116811461051857600080fd5b919050565b6000806040838503121561053057600080fd5b61053983610501565b946020939093013593505050565b60008060006060848603121561055c57600080fd5b61056584610501565b925061057360208501610501565b9150604084013590509250925092565b60006020828403121561059557600080fd5b61059e82610501565b9392505050565b600080604083850312156105b857600080fd5b823591506105c860208401610501565b90509250929050565b600080604083850312156105e457600080fd5b6105ed83610501565b91506105c86020840161050156fea2646970667358221220e1fee5cd1c5bbf066a9ce9228e1baf7e7fcb77b5050506c7d614aaf8608b42e364736f6c63430008110033"; + + MyERC20 token = MyERC20(address(uint160(uint256(keccak256(abi.encodePacked("mytoken")))))); + vm.etch(address(token), code); + + token.mint(100, alice); + + assertEq(token.balanceOf(alice), 100); + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 0); + + vm.deal(alice, 10 ether); + + /* + Signed transaction: + { + from: '0x7ED31830602f9F7419307235c0610Fb262AA0375', + to: '0x5bF11839F61EF5ccEEaf1F4153e44df5D02825f7', + value: 0, + data: '0x095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e130000000000000000000000000000000000000000000000000000000000000032', + nonce: 0, + gasPrice: 100, + gasLimit: 200000, + chainId: 1 + } + */ + // alice approves bob for 50 tokens + vm.executeTransaction( + hex"f8a5806483030d40945bf11839f61ef5cceeaf1f4153e44df5d02825f780b844095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e13000000000000000000000000000000000000000000000000000000000000003225a0e25b9ef561d9a413b21755cc0e4bb6e80f2a88a8a52305690956130d612074dfa07bfd418bc2ad3c3f435fa531cdcdc64887f64ed3fb0d347d6b0086e320ad4eb1" + ); + + assertEq(token.allowance(alice, bob), 50); + + // Use the allowance via a normal prank call. + vm.deal(bob, 1 ether); + vm.prank(bob); + token.transferFrom(alice, charlie, 20); + + assertEq(token.balanceOf(alice), 80); + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 20); + } + + // Verify state isolation: operations after executeTransaction should work correctly. + function test_execute_then_interact() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + address random = address(uint160(uint256(keccak256(abi.encodePacked("random"))))); + + uint256 balance = 1 ether; + + vm.deal(address(from), balance); + + vm.executeTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + assertEq(address(to).balance, 17); + + // Interact with the state after executeTransaction. + uint256 value = 5; + vm.prank(to); + (bool success,) = random.call{value: value}(""); + require(success); + assertEq(address(to).balance, 17 - value); + assertEq(address(random).balance, value); + } +} + +contract MyERC20 { + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + function mint(uint256 amount, address to) public { + _mint(to, amount); + } + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) public returns (bool) { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public returns (bool) { + address owner = msg.sender; + _approve(owner, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function _transfer(address from, address to, uint256 amount) internal { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + _balances[to] += amount; + } + } + + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + unchecked { + _balances[account] += amount; + } + } + + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + _allowances[owner][spender] = amount; + } + + function _spendAllowance(address owner, address spender, uint256 amount) internal { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } +} diff --git a/testdata/default/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol index 01a95b4277e01..9aff6458ceff3 100644 --- a/testdata/default/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Contract { function numberA() public pure returns (uint256) { @@ -56,13 +55,12 @@ contract SimpleCall { contract ProxyWithDelegateCall { function delegateCall(SimpleCall simpleCall) public { - address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); + (bool success,) = address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); + require(success, "delegatecall failed"); } } -contract ExpectCallTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallTest is Test { function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(a, b); @@ -206,9 +204,7 @@ contract ExpectCallTest is DSTest { } } -contract ExpectCallCountTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallCountTest is Test { function testExpectCallCountWithData() public { Contract target = new Contract(); vm.expectCall(address(target), abi.encodeWithSelector(Contract.add.selector, 1, 2), 3); @@ -332,9 +328,7 @@ contract ExpectCallCountTest is DSTest { } } -contract ExpectCallMixedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectCallMixedTest is Test { function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(1, 2); diff --git a/testdata/default/cheats/ExpectCreate.t.sol b/testdata/default/cheats/ExpectCreate.t.sol index a922d01b92bac..f416c645b3424 100644 --- a/testdata/default/cheats/ExpectCreate.t.sol +++ b/testdata/default/cheats/ExpectCreate.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Contract { function add(uint256 a, uint256 b) public pure returns (uint256) { @@ -20,8 +19,7 @@ contract ContractDeployer { } } -contract ExpectCreateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectCreateTest is Test { bytes bytecode = vm.getDeployedCode("cheats/ExpectCreate.t.sol:Contract"); function testExpectCreate() public { diff --git a/testdata/default/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol index b6b09a8a8db70..219bc9649eb2b 100644 --- a/testdata/default/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Emitter { uint256 public thing; @@ -109,14 +108,14 @@ contract Emitter { /// Emulates `Emitter` in #760 contract LowLevelCaller { function f() external { - address(this).call(abi.encodeWithSignature("g()")); + (bool success,) = address(this).call(abi.encodeWithSignature("g()")); + require(success, "call failed"); } function g() public {} } -contract ExpectEmitTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectEmitTest is Test { Emitter emitter; event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); @@ -408,7 +407,7 @@ contract ExpectEmitTest is DSTest { // vm.expectEmit(true, true, true, true); // emitter.doesNothing(); // emit Something(1, 2, 3, 4); - + // // // This should fail since `SomethingElse` in the test // // and in the `Emitter` contract have differing // // amounts of indexed topics. @@ -416,8 +415,7 @@ contract ExpectEmitTest is DSTest { // } } -contract ExpectEmitCountTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ExpectEmitCountTest is Test { Emitter emitter; event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 5ae262a4fd455..ae0c8ed844f5d 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Reverter { error CustomError(); @@ -71,9 +70,7 @@ contract Dummy { } } -contract ExpectRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertTest is Test { function shouldRevert() internal { revert(); } @@ -96,12 +93,6 @@ contract ExpectRevertTest is DSTest { reverter.revertWithMessage("revert: A"); } - function testShouldFailIfExpectRevertWrongString() public { - Reverter reverter = new Reverter(); - vm.expectRevert("my not so cool error", 0); - reverter.revertWithMessage("my cool error"); - } - function testExpectRevertConstructor() public { vm.expectRevert("constructor revert"); new ConstructorReverter("constructor revert"); @@ -269,9 +260,7 @@ contract DContract { } } -contract ExpectRevertWithReverterTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertWithReverterTest is Test { error CContractError(string reason); AContract aContract; @@ -316,11 +305,94 @@ contract ExpectRevertWithReverterTest is DSTest { vm.expectRevert(address(cContract)); aContract.createDContractThroughCContract(); } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // reverts directly, the reverter address argument must be enforced (it used to + // be silently ignored). The matched reverter is the would-be-deployed address. + function testExpectRevertsWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new DContract(); + + expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(abi.encodePacked("Reverted by DContract"), expected); + new DContract(); + } + + // + // Regression: when the next operation is a top-level CREATE whose constructor + // synchronously creates another contract that reverts (i.e. innermost frame is + // a CREATE), the matched reverter is the outer would-be-deployed address (the + // contract whose deployment failed). + function testExpectRevertsWithReverterNestedCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(expected); + new NestedDContractCreator(); + } + + // + // Regression: `expectPartialRevert(bytes4, address)` overload must enforce + // the reverter address argument when matching a top-level CREATE revert. + function testExpectPartialRevertWithReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + // `Reverted by DContract` triggers Solidity's `Error(string)` selector. + vm.expectPartialRevert(bytes4(keccak256("Error(string)")), expected); + new DContract(); + } + + // + // Regression: `expectRevert(bytes4, address)` (exact 4-byte selector + reverter) + // overload must enforce the reverter address argument for a top-level CREATE. + function testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate() public { + address expected = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectRevert(DCustomErrorContract.CustomError.selector, expected); + new DCustomErrorContract(); + } + + // + // Regression: `expectRevert(address, uint64)` count-bearing overload must + // exercise the `count > 1` branch in `create_end`. Use CREATE2 with the same + // salt so both deploys would resolve to the same would-be address (each + // constructor reverts so no contract is ever actually placed there). + function testExpectRevertsWithReverterCountTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0x42)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected, 2); + new DContract{salt: salt}(); + new DContract{salt: salt}(); + } + + // + // Regression: CREATE2 deploys must also enforce the reverter address argument. + function testExpectRevertsWithReverterTopLevelCreate2() public { + bytes32 salt = bytes32(uint256(0xC0FFEE)); + address expected = vm.computeCreate2Address(salt, keccak256(type(DContract).creationCode), address(this)); + vm.expectRevert(expected); + new DContract{salt: salt}(); + } +} + +// Used by `testExpectRevertsWithReverterNestedCreate`: a contract whose constructor +// directly creates another contract that reverts. +contract NestedDContractCreator { + constructor() { + new DContract(); + } } -contract ExpectRevertCount is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +// Used by `testExpectRevertWithBytes4SelectorAndReverterTopLevelCreate`: constructor +// reverts with a parameter-less custom error so the full revert data is exactly the +// 4-byte selector. +contract DCustomErrorContract { + error CustomError(); + + constructor() { + revert CustomError(); + } +} +contract ExpectRevertCount is Test { function testRevertCountAny() public { uint64 count = 3; Reverter reverter = new Reverter(); @@ -355,13 +427,6 @@ contract ExpectRevertCount is DSTest { reverter.doNotRevert(); } - function testNoRevertSpecificButDiffRevert() public { - uint64 count = 0; - Reverter reverter = new Reverter(); - vm.expectRevert("revert", count); - reverter.revertWithMessage("revert2"); - } - function testRevertCountWithConstructor() public { uint64 count = 1; vm.expectRevert("constructor revert", count); @@ -408,9 +473,7 @@ contract ExpectRevertCount is DSTest { } } -contract ExpectRevertCountWithReverter is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ExpectRevertCountWithReverter is Test { function testRevertCountWithReverter() public { uint64 count = 2; Reverter reverter = new Reverter(); @@ -426,14 +489,6 @@ contract ExpectRevertCountWithReverter is DSTest { reverter.doNotRevert(); } - function testNoRevertWithWrongReverter() public { - uint64 count = 0; - Reverter reverter = new Reverter(); - Reverter reverter2 = new Reverter(); - vm.expectRevert(address(reverter), count); - reverter2.revertWithMessage("revert"); // revert from wrong reverter - } - function testReverterCountWithData() public { uint64 count = 2; Reverter reverter = new Reverter(); @@ -441,14 +496,35 @@ contract ExpectRevertCountWithReverter is DSTest { reverter.revertWithMessage("revert"); reverter.revertWithMessage("revert"); } +} - function testNoReverterCountWithData() public { - uint64 count = 0; - Reverter reverter = new Reverter(); - vm.expectRevert("revert", address(reverter), count); - reverter.doNotRevert(); +contract ExpectRevertPrecompileTest is Test { + /// Test that vm.expectRevert works when the next external call targets a + /// precompile address directly. Precompile calls don't create an interpreter + /// frame (no `initialize_interp`), so depth tracking must account for them. + function testExpectRevertDirectPrecompileCall() public { + // BLAKE2F precompile (0x09) expects exactly 213 bytes of input. + // Calling it with invalid input reverts. + vm.expectRevert(); + address(0x09).call(hex"00"); + } +} - vm.expectRevert("revert", address(reverter), count); - reverter.revertWithMessage("revert2"); +contract ExpectRevertWithErrorTest is Test { + /// Ref: + function test_f() external { + bytes memory v = abi.encodeWithSignature("Error(string)", ""); + vm.expectRevert(v); + this.f(v); + + bytes memory v1 = abi.encodeWithSignature("Error(string)", unicode"🙀"); + vm.expectRevert(v1); + this.f(v1); + } + + function f(bytes memory v) external pure { + assembly { + revert(add(v, 32), mload(v)) + } } } diff --git a/testdata/default/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol index 120627c0004e9..d96fa6f9b6af7 100644 --- a/testdata/default/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FeeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract FeeTest is Test { function testFee() public { vm.fee(10); assertEq(block.basefee, 10, "fee failed"); diff --git a/testdata/default/cheats/Ffi.t.sol b/testdata/default/cheats/Ffi.t.sol index 23ac54e6ace12..6c84bdf893c29 100644 --- a/testdata/default/cheats/Ffi.t.sol +++ b/testdata/default/cheats/Ffi.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FfiTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract FfiTest is Test { function testFfi() public { string[] memory inputs = new string[](3); inputs[0] = "bash"; diff --git a/testdata/default/cheats/Fork.t.sol b/testdata/default/cheats/Fork.t.sol index 2f2e627de131a..281a27a0b868c 100644 --- a/testdata/default/cheats/Fork.t.sol +++ b/testdata/default/cheats/Fork.t.sol @@ -1,19 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); } -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint256 constant mainblock = 14_608_400; - Vm constant vm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); uint256 forkA; diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index d0703ce7fa6ce..d83c0480b7e72 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "../logs/console.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct MyStruct { uint256 value; @@ -13,7 +11,7 @@ contract MyContract { uint256 forkId; bytes32 blockHash; - constructor(uint256 _forkId) public { + constructor(uint256 _forkId) { forkId = _forkId; blockHash = blockhash(block.number - 1); } @@ -27,9 +25,7 @@ contract MyContract { } } -contract ForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ForkTest is Test { uint256 mainnetFork; uint256 optimismFork; @@ -98,14 +94,14 @@ contract ForkTest is DSTest { // test that we can "roll" blocks until a transaction function testCanRollForkUntilTransaction() public { // block to run transactions from - uint256 block = 16261704; + uint256 blockNumber = 16261704; // fork until previous block - uint256 fork = vm.createSelectFork("mainnet", block - 1); + uint256 fork = vm.createSelectFork("mainnet", blockNumber - 1); // block transactions in order: https://beaconcha.in/block/16261704#transactions // run transactions from current block until tx - bytes32 tx = 0x67cbad73764049e228495a3f90144aab4a37cb4b5fd697dffc234aa5ed811ace; + bytes32 transaction = 0x67cbad73764049e228495a3f90144aab4a37cb4b5fd697dffc234aa5ed811ace; // account that sends ether in 2 transaction before tx address account = 0xAe45a8240147E6179ec7c9f92c5A18F9a97B3fCA; @@ -119,7 +115,7 @@ contract ForkTest is DSTest { uint256 newBalance = account.balance - transferAmount; // execute transactions in block until tx - vm.rollFork(tx); + vm.rollFork(transaction); // balance must be less than newBalance due to gas spent assert(account.balance < newBalance); @@ -150,13 +146,13 @@ contract ForkTest is DSTest { assertEq(dummy.val(), expectedValue); } - // checks diagnostic + /// forge-config: default.allow_internal_expect_revert = true function testNonExistingContractRevert() public { vm.selectFork(mainnetFork); DummyContract dummy = new DummyContract(); // this will succeed since `dummy` is deployed on the currently active fork - string memory msg = dummy.hello(); + string memory message = dummy.hello(); address dummyAddress = address(dummy); @@ -164,7 +160,8 @@ contract ForkTest is DSTest { assertEq(dummyAddress, address(dummy)); // this will revert since `dummy` does not exists on the currently active fork - string memory msg2 = dummy.hello(); + vm.expectRevert(); + dummy.noop(); } struct EthGetLogsJsonParseable { @@ -235,16 +232,141 @@ contract ForkTest is DSTest { assertGt(decodedResult, 20_000_000); } + struct Withdrawal { + address addr; + bytes amount; + bytes index; + bytes validatorIndex; + } + + struct BlockResult { + bytes baseFeePerGas; + bytes blobGasUsed; + bytes difficulty; + bytes excessBlobGas; + bytes extraData; + bytes gasLimit; + bytes gasUsed; + bytes32 hash; + bytes logsBloom; + address miner; + bytes32 mixHash; + bytes nonce; + bytes number; + bytes32 parentBeaconBlockRoot; + bytes32 parentHash; + bytes32 receiptsRoot; + bytes32 sha3Uncles; + bytes size; + bytes32 stateRoot; + bytes timestamp; + bytes32[] transactions; + bytes32 transactionsRoot; + bytes32[] uncles; + Withdrawal[] withdrawals; + bytes32 withdrawalsRoot; + } + + function testRpcBlockByNumberFullReturndata() public { + bytes memory data = vm.rpc("sepolia", "eth_getBlockByNumber", '["0x588b24", false]'); + BlockResult memory blockResult = abi.decode(data, (BlockResult)); + // Verify block hash + assertEq( + blockResult.hash, + bytes32(hex"50b08560cfeef4a4005333a78bef1190f3d8708a074c549e0e5d834c6d7eab3f"), + "hash mismatch" + ); + // Verify parent hash + assertEq( + blockResult.parentHash, + bytes32(hex"ee012f100cea384420e993e4eab8c3cf0ed35a49f75769eb8a37c9e0c93ea235"), + "parentHash mismatch" + ); + // Verify block number (0x588b24) + assertEq(blockResult.number, hex"588b24", "number mismatch"); + // Verify nested struct arrays + assertEq(blockResult.withdrawals.length, 16, "withdrawals length mismatch"); + assertEq( + blockResult.withdrawals[0].addr, 0x25c4a76E7d118705e7Ea2e9b7d8C59930d8aCD3b, "withdrawal address mismatch" + ); + // Verify transaction hashes array + assertEq(blockResult.transactions.length, 133, "transactions length mismatch"); + // Verify uncles array (should be empty for this block) + assertEq(blockResult.uncles.length, 0, "uncles should be empty"); + } + + function testRpcClientVersion() public { + bytes memory data = vm.rpc("sepolia", "web3_clientVersion", "[]"); + string memory clientVersion = abi.decode(data, (string)); + assertGt(bytes(clientVersion).length, 0, "clientVersion should not be empty"); + } + + function testRpcNetListening() public { + bytes memory data = vm.rpc("sepolia", "net_listening", "[]"); + bool listening = abi.decode(data, (bool)); + assertTrue(listening, "net_listening should return true"); + } + + // Verify abi.decode works for eth_chainId (simple hex scalar to uint). + function testRpcChainId() public { + bytes memory data = vm.rpc("sepolia", "eth_chainId", "[]"); + // Sepolia chain ID is 11155111 (0xaa36a7) + assertEq(data, hex"aa36a7", "chain ID mismatch"); + } + + // Verify null response handling (eth_getBlockByNumber for a non-existent future block). + function testRpcNullResponse() public { + bytes memory data = vm.rpc("sepolia", "eth_getBlockByNumber", '["0xffffffffffffff", false]'); + // Null responses are encoded as zero bytes32 + assertEq(data.length, 32, "null should encode as bytes32"); + } + + // Struct matching a legacy (type 0) transaction fields sorted alphabetically. + struct LegacyTransactionResult { + bytes32 blockHash; + bytes blockNumber; + bytes blockTimestamp; + bytes chainId; + address from; + bytes gas; + bytes gasPrice; + bytes32 hash; + bytes input; + bytes nonce; + bytes32 r; + bytes32 s; + address to; + bytes transactionIndex; + bytes type_; + bytes v; + bytes value; + } + + // Verify struct decoding for transaction objects (original issue #7858). + // Hardcode the DRPC URL to avoid provider-specific non-standard fields + // (e.g. `blockTimestamp` from PublicNode) that shift ABI decoding offsets. // function testRpcTransactionByHash() public { - string memory param = string.concat('["0xe1a0fba63292976050b2fbf4379a1901691355ed138784b4e0d1854b4cf9193e"]'); - vm.rpc("sepolia", "eth_getTransactionByHash", param); + bytes memory data = vm.rpc( + "https://sepolia.drpc.org", + "eth_getTransactionByHash", + '["0xe1a0fba63292976050b2fbf4379a1901691355ed138784b4e0d1854b4cf9193e"]' + ); + LegacyTransactionResult memory txn = abi.decode(data, (LegacyTransactionResult)); + assertEq( + txn.hash, bytes32(hex"e1a0fba63292976050b2fbf4379a1901691355ed138784b4e0d1854b4cf9193e"), "tx hash mismatch" + ); + assertEq(txn.from, 0x8Be6209bC9BD1a8e6e015ADe090F6BE7BE6f032A, "tx from mismatch"); + assertEq(txn.to, 0xF04fd9a66DE511BC389D3b830C1F850a4A4A8c61, "tx to mismatch"); + assertEq(txn.blockNumber, hex"588b24", "tx blockNumber mismatch"); } } contract DummyContract { uint256 public val; + function noop() external pure {} + function hello() external view returns (string memory) { return "hello"; } diff --git a/testdata/default/cheats/Fs.t.sol b/testdata/default/cheats/Fs.t.sol index b4882525944cf..80b89d7d69931 100644 --- a/testdata/default/cheats/Fs.t.sol +++ b/testdata/default/cheats/Fs.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract FsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract FsTest is Test { bytes constant FOUNDRY_TOML_ACCESS_ERR = "access to foundry.toml is not allowed"; bytes constant FOUNDRY_READ_ERR = "the path /etc/hosts is not allowed to be accessed for read operations"; bytes constant FOUNDRY_READ_DIR_ERR = "the path /etc is not allowed to be accessed for read operations"; @@ -42,7 +40,7 @@ contract FsTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm.writeFile(path, data); @@ -58,7 +56,7 @@ contract FsTest is DSTest { function testCopyFile() public { string memory from = "fixtures/File/read.txt"; - string memory to = "fixtures/File/copy.txt"; + string memory to = "fixtures/File/ignored/copy.txt"; uint64 copied = vm.copyFile(from, to); assertEq(vm.fsMetadata(to).length, uint256(copied)); assertEq(vm.readFile(from), vm.readFile(to)); @@ -66,7 +64,7 @@ contract FsTest is DSTest { } function testWriteLine() public { - string memory path = "fixtures/File/write_line.txt"; + string memory path = "fixtures/File/ignored/write_line.txt"; string memory line1 = "first line"; vm.writeLine(path, line1); @@ -91,7 +89,7 @@ contract FsTest is DSTest { } function testRemoveFile() public { - string memory path = "fixtures/File/remove_file.txt"; + string memory path = "fixtures/File/ignored/remove_file.txt"; string memory data = "hello writable world"; vm.writeFile(path, data); diff --git a/testdata/default/cheats/GasMetering.t.sol b/testdata/default/cheats/GasMetering.t.sol index 3cb105d236f02..fbde54741c624 100644 --- a/testdata/default/cheats/GasMetering.t.sol +++ b/testdata/default/cheats/GasMetering.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract B { function a() public returns (uint256) { @@ -10,9 +9,7 @@ contract B { } } -contract GasMeteringTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GasMeteringTest is Test { function testGasMetering() public { uint256 gas_start = gasleft(); @@ -40,6 +37,7 @@ contract GasMeteringTest is DSTest { function testGasMeteringExternal() public { B b = new B(); + uint256 gas_start = gasleft(); b.a(); diff --git a/testdata/default/cheats/GetArtifactPath.t.sol b/testdata/default/cheats/GetArtifactPath.t.sol index 4b0df4ba6e6ec..7a2e1335d0c04 100644 --- a/testdata/default/cheats/GetArtifactPath.t.sol +++ b/testdata/default/cheats/GetArtifactPath.t.sol @@ -1,20 +1,16 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract DummyForGetArtifactPath {} -contract GetArtifactPathTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetArtifactPathTest is Test { function testGetArtifactPathByCode() public { - DummyForGetArtifactPath dummy = new DummyForGetArtifactPath(); bytes memory dummyCreationCode = type(DummyForGetArtifactPath).creationCode; string memory path = vm.getArtifactPathByCode(dummyCreationCode); - assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + assertTrue(vm.contains(path, "/out/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); } function testGetArtifactPathByDeployedCode() public { @@ -22,6 +18,6 @@ contract GetArtifactPathTest is DSTest { bytes memory dummyRuntimeCode = address(dummy).code; string memory path = vm.getArtifactPathByDeployedCode(dummyRuntimeCode); - assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + assertTrue(vm.contains(path, "/out/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); } } diff --git a/testdata/default/cheats/GetBlockTimestamp.t.sol b/testdata/default/cheats/GetBlockTimestamp.t.sol index 816bc0d1ef89e..65bda02052764 100644 --- a/testdata/default/cheats/GetBlockTimestamp.t.sol +++ b/testdata/default/cheats/GetBlockTimestamp.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetBlockTimestampTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetBlockTimestampTest is Test { function testGetTimestamp() public { uint256 timestamp = vm.getBlockTimestamp(); assertEq(timestamp, 1, "timestamp should be 1"); diff --git a/testdata/default/cheats/GetChain.t.sol b/testdata/default/cheats/GetChain.t.sol index 856a6b8a75712..ca6e1a5c284cf 100644 --- a/testdata/default/cheats/GetChain.t.sol +++ b/testdata/default/cheats/GetChain.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetChainTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetChainTest is Test { function testGetMainnet() public { // Test mainnet Vm.Chain memory mainnet = vm.getChain("mainnet"); @@ -56,7 +53,7 @@ contract GetChainTest is DSTest { Vm.Chain memory mainnet = vm.getChain(1); assertEq(mainnet.name, "mainnet"); assertEq(mainnet.chainId, 1); - assertEq(mainnet.chainAlias, "1"); + assertEq(mainnet.chainAlias, "mainnet"); } function testGetSepoliaById() public { @@ -64,7 +61,7 @@ contract GetChainTest is DSTest { Vm.Chain memory sepolia = vm.getChain(11155111); assertEq(sepolia.name, "sepolia"); assertEq(sepolia.chainId, 11155111); - assertEq(sepolia.chainAlias, "11155111"); + assertEq(sepolia.chainAlias, "sepolia"); } function testGetOptimismById() public { @@ -72,7 +69,16 @@ contract GetChainTest is DSTest { Vm.Chain memory optimism = vm.getChain(10); assertEq(optimism.name, "optimism"); assertEq(optimism.chainId, 10); - assertEq(optimism.chainAlias, "10"); + assertEq(optimism.chainAlias, "optimism"); + } + + function testGetBerachainById() public { + // Test Berachain using chain ID + Vm.Chain memory bera = vm.getChain(80094); + assertEq(bera.name, "berachain"); + assertEq(bera.chainId, 80094); + // No rpc url configured, chain alias is the chain id. + assertEq(bera.chainAlias, "80094"); } function testGetArbitrumById() public { @@ -80,7 +86,7 @@ contract GetChainTest is DSTest { Vm.Chain memory arbitrum = vm.getChain(42161); assertEq(arbitrum.name, "arbitrum"); assertEq(arbitrum.chainId, 42161); - assertEq(arbitrum.chainAlias, "42161"); + assertEq(arbitrum.chainAlias, "arbitrum"); } function testInvalidChainId() public { diff --git a/testdata/default/cheats/GetCode.t.sol b/testdata/default/cheats/GetCode.t.sol index 6020e4f1fd5bb..514ac66b9d0b3 100644 --- a/testdata/default/cheats/GetCode.t.sol +++ b/testdata/default/cheats/GetCode.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} contract TestContractGetCode {} -contract GetCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest is Test { function testGetCode() public { bytes memory fullPath = vm.getCode("fixtures/GetCode/WorkingContract.json"); //bytes memory fileOnly = vm.getCode("WorkingContract.sol"); @@ -73,6 +70,7 @@ contract GetCodeTest is DSTest { /// forge-config: default.allow_internal_expect_revert = true function testRevertIfGetUnlinked() public { + vm.skip(true, "artifacts are always linked now"); vm.expectRevert("vm.getCode: no matching artifact found"); vm.getCode("UnlinkedContract.sol"); } diff --git a/testdata/default/cheats/GetDeployedCode.t.sol b/testdata/default/cheats/GetDeployedCode.t.sol index 295d2ae8f3f38..a33a13d47805c 100644 --- a/testdata/default/cheats/GetDeployedCode.t.sol +++ b/testdata/default/cheats/GetDeployedCode.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract TestContract {} -contract GetDeployedCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetDeployedCodeTest is Test { address public constant overrideAddress = 0x0000000000000000000000000000000000000064; event Payload(address sender, address target, bytes data); diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index ac30bb8491874..f01b7cdd7d213 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetFoundryVersionTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetFoundryVersionTest is Test { function testGetFoundryVersion() public view { // (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug) string memory fullVersionString = vm.getFoundryVersion(); @@ -87,4 +84,55 @@ contract GetFoundryVersionTest is DSTest { // Should return true for past versions assertTrue(vm.foundryVersionAtLeast("0.2.0")); } + + /// Returns the `MAJOR.MINOR.PATCH` prefix of `vm.getFoundryVersion()`, + /// stripping any pre-release suffix (`-nightly`, `-dev`, …) and the + /// `+..` build metadata. + function _semverPrefix() internal view returns (string memory) { + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + string[] memory dashSplit = vm.split(plusSplit[0], "-"); + return dashSplit[0]; + } + + function testGetFoundryVersionMajorMinorPatchIsParseable() public view { + // The MAJOR.MINOR.PATCH prefix must always be three numeric components, + // regardless of build kind (tagged release / nightly / dev). + string[] memory parts = vm.split(_semverPrefix(), "."); + require(parts.length == 3, "Invalid semver prefix: expected MAJOR.MINOR.PATCH"); + // Each component must parse as a uint (this reverts on garbage). + vm.parseUint(parts[0]); + vm.parseUint(parts[1]); + vm.parseUint(parts[2]); + } + + function testGetFoundryVersionBuildProfile() public view { + // The build profile must be present and non-empty (e.g. "debug", "release", "dist", …). + string[] memory plusSplit = vm.split(vm.getFoundryVersion(), "+"); + string[] memory metadataComponents = vm.split(plusSplit[1], "."); + require(bytes(metadataComponents[2]).length > 0, "Build profile is empty"); + } + + function testFoundryVersionCmpAndAtLeastAreConsistent() public { + // `foundryVersionAtLeast(v)` must equal `foundryVersionCmp(v) >= 0` for any input. + string[3] memory probes = ["0.0.1", _semverPrefix(), "99.0.0"]; + for (uint256 i = 0; i < probes.length; i++) { + assertEq(vm.foundryVersionAtLeast(probes[i]), vm.foundryVersionCmp(probes[i]) >= 0); + } + } + + function testFoundryVersionCmpRejectsPreRelease() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0-nightly"); + } + + function testFoundryVersionCmpRejectsBuildMetadata() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("1.0.0+abc1234567.1700000000.release"); + } + + function testFoundryVersionCmpRejectsInvalidVersion() public { + vm._expectCheatcodeRevert(); + vm.foundryVersionCmp("not-a-version"); + } } diff --git a/testdata/default/cheats/GetLabel.t.sol b/testdata/default/cheats/GetLabel.t.sol index c5a5638d36752..b5ccc64533862 100644 --- a/testdata/default/cheats/GetLabel.t.sol +++ b/testdata/default/cheats/GetLabel.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetLabelTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetLabelTest is Test { function testGetLabel() public { // Label an address. vm.label(address(1), "Sir Address the 1st"); diff --git a/testdata/default/cheats/GetNonce.t.sol b/testdata/default/cheats/GetNonce.t.sol index d4043a59992a8..a786fd496ff94 100644 --- a/testdata/default/cheats/GetNonce.t.sol +++ b/testdata/default/cheats/GetNonce.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo {} -contract GetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetNonceTest is Test { function testGetNonce() public { uint64 nonce1 = vm.getNonce(address(this)); new Foo(); diff --git a/testdata/default/cheats/GetRawBlockHeader.t.sol b/testdata/default/cheats/GetRawBlockHeader.t.sol new file mode 100644 index 0000000000000..54600149863a0 --- /dev/null +++ b/testdata/default/cheats/GetRawBlockHeader.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract GetRawBlockHeaderTest is Test { + function testGetRawBlockHeaderWithFork() public { + vm.createSelectFork("mainnet"); + assertEq( + keccak256(vm.getRawBlockHeader(22985278)), + // `cast keccak256 $(cast block 22985278 --raw)` + 0x492419d85d2817f50577807a287742fbdcaae00ce89f2ea885e419ee4493b00f + ); + } +} diff --git a/testdata/default/cheats/GetStorageSlots.t.sol b/testdata/default/cheats/GetStorageSlots.t.sol new file mode 100644 index 0000000000000..f61638ac51ea1 --- /dev/null +++ b/testdata/default/cheats/GetStorageSlots.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract StorageContract { + // Simple variables - 1 slot each + uint256 public value; // Slot 0 + address public owner; // Slot 1 + + // Fixed array - 3 consecutive slots + uint256[3] public numbers; // Slots 2, 3, 4 + + // Bytes variables + bytes public shortBytes; // Slot 5 (less than 32 bytes) + mapping(address => uint256) public balances; // Slot 6 Inserted in between to make sure we can still properly identify the bytes + bytes public longBytes; // Slot 7 (32+ bytes, will use multiple slots) + + // String variables + string public shortString; // Slot 8 (less than 32 bytes) + string public longString; // Slot 9 (32+ bytes, will use multiple slots) + + function setShortBytes(bytes memory _data) public { + shortBytes = _data; + } + + function setLongBytes(bytes memory _data) public { + longBytes = _data; + } + + function setShortString(string memory _str) public { + shortString = _str; + } + + function setLongString(string memory _str) public { + longString = _str; + } + + function setNumbers(uint256 a, uint256 b, uint256 c) public { + numbers[0] = a; + numbers[1] = b; + numbers[2] = c; + } +} + +contract GetStorageSlotsTest is Test { + StorageContract storageContract; + + function setUp() public { + storageContract = new StorageContract(); + } + + function testGetStorageSlots() public { + // Test 1: Simple variable + uint256[] memory slots = vm.getStorageSlots(address(storageContract), "value"); + assertEq(slots.length, 1); + assertEq(slots[0], 0); + + // Test 2: Fixed array (should return 3 consecutive slots) + slots = vm.getStorageSlots(address(storageContract), "numbers"); + assertEq(slots.length, 3); + assertEq(slots[0], 2); + assertEq(slots[1], 3); + assertEq(slots[2], 4); + + // Test 3: Short bytes (less than 32 bytes) + storageContract.setShortBytes(hex"deadbeef"); + slots = vm.getStorageSlots(address(storageContract), "shortBytes"); + assertEq(slots.length, 1); + assertEq(slots[0], 5); + + // Test 4: Long bytes (100 bytes = 4 slots needed) + bytes memory longData = new bytes(100); + for (uint256 i = 0; i < 100; i++) { + longData[i] = bytes1(uint8(i)); + } + storageContract.setLongBytes(longData); + + slots = vm.getStorageSlots(address(storageContract), "longBytes"); + // Should return 5 slots: 1 base slot + 4 data slots + assertEq(slots.length, 5); + assertEq(slots[0], 7); // Base slot + + // Data slots start at keccak256(base_slot) + uint256 dataStart = uint256(keccak256(abi.encode(uint256(7)))); + assertEq(slots[1], dataStart); + assertEq(slots[2], dataStart + 1); + assertEq(slots[3], dataStart + 2); + assertEq(slots[4], dataStart + 3); + } +} diff --git a/testdata/default/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol index ff1b62c6ee8a2..44bba4025e82d 100644 --- a/testdata/default/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; library JsonStructs { address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); @@ -66,7 +64,7 @@ library JsonStructs { } } -contract ParseJsonTest is DSTest { +contract ParseJsonTest is Test { using JsonStructs for *; struct FlatJson { @@ -88,7 +86,6 @@ contract ParseJsonTest is DSTest { string name; } - Vm constant vm = Vm(HEVM_ADDRESS); string json; function setUp() public { @@ -328,9 +325,7 @@ contract ParseJsonTest is DSTest { } } -contract WriteJsonTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract WriteJsonTest is Test { string json1; string json2; @@ -491,4 +486,43 @@ contract WriteJsonTest is DSTest { address decodedAddress = abi.decode(data, (address)); assertEq(decodedAddress, ex); } + + function test_writeJson_createKeys() public { + string memory path = "fixtures/Json/write_test.json"; + string memory json = vm.readFile(path); + + bool exists = vm.keyExistsJson(json, ".parent"); + assertTrue(!exists); + exists = vm.keyExistsJson(json, ".parent.child"); + assertTrue(!exists); + exists = vm.keyExistsJson(json, ".parent.child.value"); + assertTrue(!exists); + + // Write to nested path, creating intermediate keys + vm.writeJson(vm.toString(uint256(42)), path, ".parent.child.value"); + + // Verify the value was written and intermediate keys were created + json = vm.readFile(path); + uint256 value = abi.decode(vm.parseJson(json, ".parent.child.value"), (uint256)); + assertEq(value, 42); + + // Clean up the test file by removing the parent key we added + vm.removeFile(path); + vm.writeJson("{\"a\": 123, \"b\": \"0x000000000000000000000000000000000000bEEF\"}", path); + } + + function test_writeJson_createFile() public { + string memory path = "fixtures/Json/write_test_nonexistent.json"; + + // Write to a file that does not exist using the 3-arg overload + vm.writeJson(vm.toString(uint256(99)), path, ".x.y"); + + // Verify the file was created with the correct content + string memory json = vm.readFile(path); + uint256 value = abi.decode(vm.parseJson(json, ".x.y"), (uint256)); + assertEq(value, 99); + + // Clean up + vm.removeFile(path); + } } diff --git a/testdata/default/cheats/Label.t.sol b/testdata/default/cheats/Label.t.sol index 4ff5d3860bed0..7c1ebd02d2443 100644 --- a/testdata/default/cheats/Label.t.sol +++ b/testdata/default/cheats/Label.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract LabelTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract LabelTest is Test { function testLabel() public { vm.label(address(1), "Sir Address the 1st"); } diff --git a/testdata/default/cheats/Load.t.sol b/testdata/default/cheats/Load.t.sol index 06f4b5bd52718..dcdd145c7c8f7 100644 --- a/testdata/default/cheats/Load.t.sol +++ b/testdata/default/cheats/Load.t.sol @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Storage { uint256 slot0 = 10; } -contract LoadTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract LoadTest is Test { uint256 slot0 = 20; Storage store; diff --git a/testdata/default/cheats/Mapping.t.sol b/testdata/default/cheats/Mapping.t.sol index 82477150ae9ca..7b0eb11ccb32d 100644 --- a/testdata/default/cheats/Mapping.t.sol +++ b/testdata/default/cheats/Mapping.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract RecordMapping { int256 length; @@ -18,9 +17,7 @@ contract RecordMapping { } } -contract RecordMappingTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract RecordMappingTest is Test { function testRecordMapping() public { RecordMapping target = new RecordMapping(); diff --git a/testdata/default/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol index b18673e93e081..953ca07e85e03 100644 --- a/testdata/default/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract MemSafetyTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract MemSafetyTest is Test { //////////////////////////////////////////////////////////////// // MSTORE // //////////////////////////////////////////////////////////////// @@ -440,7 +437,7 @@ contract MemSafetyTest is DSTest { vm.stopExpectSafeMemory(); assembly { - // write ouside allowed range, this should be fine + // write outside allowed range, this should be fine mstore(add(initPtr, 0x20), 0x01) } } diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index f11fd20984571..d8019ab4f6ee8 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Mock { uint256 state = 0; @@ -56,9 +55,7 @@ contract NestedMockDelegateCall { } } -contract MockCallTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MockCallTest is Test { function testMockGetters() public { Mock target = new Mock(); @@ -161,6 +158,35 @@ contract MockCallTest is DSTest { assertEq(mock.pay{value: 50}(1), 100); } + function testMockCallWithValueTransfersBalance() public { + Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + assertEq(address(mock).balance, 0); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + } + + function testMockCallWithValueTransfersPrankedSenderBalance() public { + Mock mock = new Mock(); + address sender = address(0xBEEF); + uint256 value = 10; + vm.deal(address(this), 0); + vm.deal(sender, value); + + vm.mockCall(address(mock), value, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + vm.prank(sender); + assertEq(mock.pay{value: value}(1), 10); + assertEq(address(mock).balance, value); + assertEq(address(this).balance, 0); + assertEq(sender.balance, 0); + } + function testMockCallWithValueCalldataPrecedence() public { Mock mock = new Mock(); @@ -183,9 +209,7 @@ contract MockCallTest is DSTest { } } -contract MockCallRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract MockCallRevertTest is Test { error TestError(bytes msg); bytes constant ERROR_MESSAGE = "ERROR_MESSAGE"; @@ -284,17 +308,25 @@ contract MockCallRevertTest is DSTest { function testMockCallRevertWithValue() public { Mock mock = new Mock(); + uint256 value = 10; + vm.deal(address(this), value); - vm.mockCallRevert(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); + vm.mockCallRevert(address(mock), value, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - try mock.pay{value: 10}(1) { + uint256 initSenderBalance = address(this).balance; + uint256 initTargetBalance = address(mock).balance; + + try mock.pay{value: value}(1) { revert(); } catch (bytes memory err) { require(keccak256(err) == keccak256(ERROR_MESSAGE)); } + + assertEq(address(this).balance, initSenderBalance); + assertEq(address(mock).balance, initTargetBalance); } function testMockCallResetsMockCallRevert() public { diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol index 2bd4d8bd9ea2e..777543f28e361 100644 --- a/testdata/default/cheats/MockCalls.t.sol +++ b/testdata/default/cheats/MockCalls.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract MockCallsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract MockCallsTest is Test { function testMockCallsLastShouldPersist() public { address mockUser = vm.addr(vm.randomUint()); address mockErc20 = vm.addr(vm.randomUint()); @@ -31,13 +28,17 @@ contract MockCallsTest is DSTest { mocks[0] = abi.encode(2 ether); mocks[1] = abi.encode(1 ether); mocks[2] = abi.encode(6.423 ether); + vm.deal(address(this), 3 ether); vm.mockCalls(mockErc20, 1 ether, data, mocks); (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret1, (uint256)), 2 ether); + assertEq(mockErc20.balance, 1 ether); (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret2, (uint256)), 1 ether); + assertEq(mockErc20.balance, 2 ether); (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + assertEq(mockErc20.balance, 3 ether); } function testMockCalls() public { diff --git a/testdata/default/cheats/MockFunction.t.sol b/testdata/default/cheats/MockFunction.t.sol index 6d670024b2053..6cd93253c3953 100644 --- a/testdata/default/cheats/MockFunction.t.sol +++ b/testdata/default/cheats/MockFunction.t.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract MockFunctionContract { +interface IMockFunctionContract { + function a() external view returns (uint256); + function mocked_function() external; + function mocked_args_function(uint256 x) external; +} + +contract MockFunctionContract is IMockFunctionContract { uint256 public a; function mocked_function() public { @@ -16,7 +21,7 @@ contract MockFunctionContract { } } -contract ModelMockFunctionContract { +contract ModelMockFunctionContract is IMockFunctionContract { uint256 public a; function mocked_function() public { @@ -28,14 +33,53 @@ contract ModelMockFunctionContract { } } -contract MockFunctionTest is DSTest { +contract Proxy { + address immutable impl; + + constructor(address impl_) { + impl = impl_; + } + + fallback() external { + _delegate(impl); + } + + // code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/239795bea728c8dca4deb6c66856dd58a6991112/contracts/proxy/Proxy.sol#L22-L45 + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0x00, 0x00, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0x00, calldatasize(), 0x00, 0x00) + + // Copy the returned data. + returndatacopy(0x00, 0x00, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0x00, returndatasize()) + } + default { + return(0x00, returndatasize()) + } + } + } +} + +contract MockFunctionTest is Test { MockFunctionContract my_contract; ModelMockFunctionContract model_contract; - Vm vm = Vm(HEVM_ADDRESS); + IMockFunctionContract my_proxy; function setUp() public { my_contract = new MockFunctionContract(); model_contract = new ModelMockFunctionContract(); + my_proxy = IMockFunctionContract(address(new Proxy(address(my_contract)))); } function test_mock_function() public { @@ -71,4 +115,132 @@ contract MockFunctionTest is DSTest { my_contract.mocked_args_function(789); assertEq(my_contract.a(), 123 + 789); } + + function test_mock_function_via_proxy() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 123, "mocked function should be called via proxy"); + + // reset mock + vm.mockFunction( + address(my_proxy), address(my_proxy), abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 321, "after reset, original function should be called"); + } + + function test_mock_function_via_proxy_concrete_args() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 100) + ); + my_proxy.mocked_args_function(100); + assertEq(my_proxy.a(), 123 + 100, "mocked args function should be called via proxy"); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "original args function should be called for different args"); + + // reset mock + vm.mockFunction( + address(my_proxy), + address(my_proxy), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 100) + ); + my_proxy.mocked_args_function(100); + assertEq(my_proxy.a(), 321 + 100, "after reset, original args function should be called"); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "original args function should be called for different args"); + } + + function test_mock_function_via_proxy_all_args() public { + vm.mockFunction( + address(my_proxy), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 123 + 300, "mocked args function should be called via proxy"); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 123 + 400, "mocked args function should be called via proxy"); + + // reset mock + vm.mockFunction( + address(my_proxy), + address(my_proxy), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "after reset, original args function should be called"); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 321 + 400, "after reset, original args function should be called"); + } + + function test_mock_function_via_impl() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 123, "mocked function should be called via impl address"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_proxy.mocked_function(); + assertEq(my_proxy.a(), 321, "after reset, original function should be called"); + } + + function test_mock_function_via_impl_concrete_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 200) + ); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 123 + 200, "mocked args function should be called via impl address"); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "original args function should be called for different args"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 200) + ); + my_proxy.mocked_args_function(200); + assertEq(my_proxy.a(), 321 + 200, "after reset, original args function should be called"); + my_proxy.mocked_args_function(300); + assertEq(my_proxy.a(), 321 + 300, "original args function should be called for different args"); + } + + function test_mock_function_via_impl_all_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 123 + 400, "mocked args function should be called via impl address"); + my_proxy.mocked_args_function(500); + assertEq(my_proxy.a(), 123 + 500, "mocked args function should be called via impl address"); + + // reset mock + vm.mockFunction( + address(my_contract), + address(my_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_proxy.mocked_args_function(400); + assertEq(my_proxy.a(), 321 + 400, "after reset, original args function should be called"); + my_proxy.mocked_args_function(500); + assertEq(my_proxy.a(), 321 + 500, "after reset, original args function should be called"); + } } diff --git a/testdata/default/cheats/Nonce.t.sol b/testdata/default/cheats/Nonce.t.sol index 312c2b4d7edcc..7b7f7553e7854 100644 --- a/testdata/default/cheats/Nonce.t.sol +++ b/testdata/default/cheats/Nonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public count; @@ -12,9 +11,8 @@ contract Counter { } } -contract NonceIsolatedTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.isolate = true +contract NonceIsolatedTest is Test { function testIncrementNonce() public { address bob = address(14); vm.startPrank(bob); diff --git a/testdata/default/cheats/Parse.t.sol b/testdata/default/cheats/Parse.t.sol index 65e7561d104de..2736aa8ade7b6 100644 --- a/testdata/default/cheats/Parse.t.sol +++ b/testdata/default/cheats/Parse.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ParseTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ParseTest is Test { function testParseBytes() public { bytes memory testBytes = hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D"; diff --git a/testdata/default/cheats/Prank.t.sol b/testdata/default/cheats/Prank.t.sol index 151f0d8306776..33287f8bbaaed 100644 --- a/testdata/default/cheats/Prank.t.sol +++ b/testdata/default/cheats/Prank.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Victim { function assertCallerAndOrigin( @@ -107,9 +106,7 @@ contract ProxyTest { address public sender; } -contract PrankTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract PrankTest is Test { function testPrankDelegateCallPrank2() public { ProxyTest proxy = new ProxyTest(); ImplementationTest impl = new ImplementationTest(); @@ -201,7 +198,8 @@ contract PrankTest is DSTest { vm.expectRevert("vm.prank: cannot `prank` delegate call from an EOA"); vm.prank(alice, true); // Should fail when EOA pranked with delegatecall. - address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", alice)); + (bool success,) = address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", alice)); + require(success, "delegate call failed"); } function testPrankSender(address sender) public { @@ -551,9 +549,7 @@ contract PrankTest is DSTest { } } -contract Issue9990 is DSTest { - Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); - +contract Issue9990 is Test { function testDelegatePrank() external { A a = new A(); vm.etch(address(0x11111), hex"11"); @@ -600,9 +596,7 @@ contract Counter { } } -contract Issue10528 is DSTest { - Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); - +contract Issue10528 is Test { function testStartPrankOnContractCreation() external { vm.startPrank(address(0x22222)); Counter counter = new Counter(); diff --git a/testdata/default/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol index aab8e326c43ce..ff4d8b48b8c8d 100644 --- a/testdata/default/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract PrevrandaoTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract PrevrandaoTest is Test { function testPrevrandao() public { assertEq(block.prevrandao, 0); vm.prevrandao(uint256(10)); diff --git a/testdata/default/cheats/ProjectRoot.t.sol b/testdata/default/cheats/ProjectRoot.t.sol index cff3d83751d66..8835d2192cbfc 100644 --- a/testdata/default/cheats/ProjectRoot.t.sol +++ b/testdata/default/cheats/ProjectRoot.t.sol @@ -1,15 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ProjectRootTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ProjectRootTest is Test { bytes public manifestDirBytes; function testProjectRoot() public { - manifestDirBytes = bytes(vm.envString("CARGO_MANIFEST_DIR")); + // .../crates/forge + string memory manifestDir = vm.envOr("CARGO_MANIFEST_DIR", string("")); + if (bytes(manifestDir).length == 0) { + vm.skip(true, "CARGO_MANIFEST_DIR environment variable is not set"); + } + manifestDirBytes = bytes(manifestDir); + for (uint256 i = 0; i < 7; i++) { manifestDirBytes.pop(); } @@ -20,6 +24,10 @@ contract ProjectRootTest is DSTest { } bytes memory expectedRootDir = abi.encodePacked(manifestDirBytes, "ata"); - assertEq(vm.projectRoot(), string(expectedRootDir)); + assertEq(normalizePath(vm.projectRoot()), normalizePath(string(expectedRootDir))); + } + + function normalizePath(string memory path) internal pure returns (string memory) { + return vm.replace(path, "\\", "/"); } } diff --git a/testdata/default/cheats/Prompt.t.sol b/testdata/default/cheats/Prompt.t.sol index 2e623a28ef22c..5505629da15ba 100644 --- a/testdata/default/cheats/Prompt.t.sol +++ b/testdata/default/cheats/Prompt.t.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; - -contract PromptTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +// All `prompt` functions should revert in CI and testing environments either +// with a timeout or because no terminal is available. +contract PromptTest is Test { function testPrompt_revertNotATerminal() public { - // should revert in CI and testing environments either with timout or because no terminal is available + checkTty(); + vm._expectCheatcodeRevert(); vm.prompt("test"); @@ -21,12 +20,22 @@ contract PromptTest is DSTest { } function testPrompt_Address() public { + checkTty(); + vm._expectCheatcodeRevert(); address test = vm.promptAddress("test"); } function testPrompt_Uint() public { + checkTty(); + vm._expectCheatcodeRevert(); uint256 test = vm.promptUint("test"); } + + function checkTty() internal { + if (!vm.envOr("CI", false)) { + vm.skip(true, "min timeout is 1s, don't test it"); + } + } } diff --git a/testdata/default/cheats/RandomAddress.t.sol b/testdata/default/cheats/RandomAddress.t.sol index 61510ed4eaaac..043884ed0a014 100644 --- a/testdata/default/cheats/RandomAddress.t.sol +++ b/testdata/default/cheats/RandomAddress.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomAddress is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomAddress is Test { function testRandomAddress() public { vm.randomAddress(); } diff --git a/testdata/default/cheats/RandomBytes.t.sol b/testdata/default/cheats/RandomBytes.t.sol index dbc03a6ccfb8f..4cdc08a61604c 100644 --- a/testdata/default/cheats/RandomBytes.t.sol +++ b/testdata/default/cheats/RandomBytes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomBytes is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomBytes is Test { function testRandomBytes4() public { vm.randomBytes4(); } diff --git a/testdata/default/cheats/RandomCheatcodes.t.sol b/testdata/default/cheats/RandomCheatcodes.t.sol index c42b4310012f1..bd1968000eab7 100644 --- a/testdata/default/cheats/RandomCheatcodes.t.sol +++ b/testdata/default/cheats/RandomCheatcodes.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomCheatcodesTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomCheatcodesTest is Test { int128 constant min = -170141183460469231731687303715884105728; int128 constant max = 170141183460469231731687303715884105727; @@ -57,9 +54,7 @@ contract RandomCheatcodesTest is DSTest { } } -contract RandomBytesTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - +contract RandomBytesTest is Test { bytes1 local_byte; bytes local_bytes; diff --git a/testdata/default/cheats/RandomUint.t.sol b/testdata/default/cheats/RandomUint.t.sol index c0021030d0d1e..8e0b402386133 100644 --- a/testdata/default/cheats/RandomUint.t.sol +++ b/testdata/default/cheats/RandomUint.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RandomUint is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RandomUint is Test { function testRandomUint() public { vm.randomUint(); } diff --git a/testdata/default/cheats/ReadCallers.t.sol b/testdata/default/cheats/ReadCallers.t.sol index dbd198a2d93e8..7bef8c502a091 100644 --- a/testdata/default/cheats/ReadCallers.t.sol +++ b/testdata/default/cheats/ReadCallers.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Target { function consumeNewCaller() external {} } -contract ReadCallersTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract ReadCallersTest is Test { function testReadCallersWithNoActivePrankOrBroadcast() public { address expectedSender = msg.sender; address expectedTxOrigin = tx.origin; diff --git a/testdata/default/cheats/Record.t.sol b/testdata/default/cheats/Record.t.sol index c3029d5f54205..6525092803303 100644 --- a/testdata/default/cheats/Record.t.sol +++ b/testdata/default/cheats/Record.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract RecordAccess { function record() public returns (NestedRecordAccess) { @@ -25,9 +24,7 @@ contract NestedRecordAccess { } } -contract RecordTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract RecordTest is Test { function testRecordAccess() public { RecordAccess target = new RecordAccess(); diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index 63100f51f1284..566058da99cf6 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; /// @notice Helper contract with a construction that makes a call to itself then /// optionally reverts if zero-length data is passed @@ -124,13 +122,13 @@ contract NestedRunner { /// Helper contract that uses all three EXT* opcodes on a given address contract ExtChecker { - function checkExts(address a) external { + function checkExts(address a) external returns (bytes memory out) { assembly { - let x := extcodesize(a) - let y := extcodehash(a) - extcodecopy(a, x, y, 0) - // sstore to check that storage accesses are correctly stored in a new access with a "resume" context - sstore(0, balance(a)) + mstore(out, mul(0x20, 4)) + mstore(add(out, 0x20), extcodesize(a)) + mstore(add(out, 0x40), extcodehash(a)) + extcodecopy(a, 0, 0x60, 0x20) + mstore(add(out, 0x80), balance(a)) } } } @@ -203,8 +201,7 @@ contract Proxy { } /// @notice Test that the cheatcode correctly records account accesses -contract RecordAccountAccessesTest is DSTest { - Vm constant cheats = Vm(HEVM_ADDRESS); +contract RecordAccountAccessesTest is Test { NestedRunner runner; NestedStorer nestedStorer; Create2or create2or; @@ -225,9 +222,10 @@ contract RecordAccountAccessesTest is DSTest { StorageAccessor one = test1; Proxy proxy = new Proxy(address(one)); - cheats.startStateDiffRecording(); - address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + vm.startStateDiffRecording(); + (bool success,) = address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); + require(success, "call failed"); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 2, "incorrect length"); @@ -255,24 +253,24 @@ contract RecordAccountAccessesTest is DSTest { function testStorageAccesses() public { StorageAccessor one = test1; StorageAccessor two = test2; - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); one.read(bytes32(uint256(1234))); one.write(bytes32(uint256(1235)), bytes32(uint256(5678))); two.write(bytes32(uint256(5678)), bytes32(uint256(123469))); two.write(bytes32(uint256(5678)), bytes32(uint256(1234))); - string memory diffs = cheats.getStateDiff(); + string memory diffs = vm.getStateDiff(); assertEq( - "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n", + "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n", diffs ); - string memory diffsJson = cheats.getStateDiffJson(); + string memory diffsJson = vm.getStateDiffJson(); assertEq( - "{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}", + '{"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9":{"label":null,"contract":"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x00000000000000000000000000000000000000000000000000000000000004d3":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000000000000000000000000000162e"}}},"0xc7183455a4c133ae270771860664b6b7ec320bb1":{"label":null,"contract":"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x000000000000000000000000000000000000000000000000000000000000162e":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x00000000000000000000000000000000000000000000000000000000000004d2"}}}}', diffsJson ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 4, "incorrect length"); assertEq(called[0].storageAccesses.length, 1, "incorrect storage length"); @@ -334,7 +332,7 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that basic account accesses are correctly recorded function testRecordAccountAccesses() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); (bool succ,) = address(1234).call(""); (succ,) = address(5678).call{value: 1 ether}(""); @@ -343,14 +341,19 @@ contract RecordAccountAccessesTest is DSTest { // contract calls to self in constructor SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); - string memory callerAddress = cheats.toString(address(caller)); + string memory callerAddress = vm.toString(address(caller)); string memory expectedStateDiff = "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n"; expectedStateDiff = string.concat(expectedStateDiff, callerAddress); - expectedStateDiff = string.concat(expectedStateDiff, "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n\n"); - assertEq(expectedStateDiff, cheats.getStateDiff()); + expectedStateDiff = + string.concat(expectedStateDiff, "\ncontract: default/cheats/RecordAccountAccesses.t.sol:SelfCaller"); + expectedStateDiff = string.concat( + expectedStateDiff, + "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n- nonce diff: 0 \xE2\x86\x92 1\n\n" + ); + assertEq(expectedStateDiff, vm.getStateDiff()); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 6); assertEq( called[0], @@ -362,6 +365,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "", @@ -381,6 +386,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 1 ether, data: "", @@ -399,6 +406,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "hello world", @@ -417,6 +426,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 1 ether, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "", @@ -435,6 +446,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 0, newBalance: 2 ether, + oldNonce: 0, + newNonce: 1, deployedCode: address(caller).code, value: 2 ether, data: abi.encodePacked(type(SelfCaller).creationCode, abi.encode("hello2 world2")), @@ -453,6 +466,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 2 ether, newBalance: 2 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0.2 ether, data: "", @@ -467,17 +482,17 @@ contract RecordAccountAccessesTest is DSTest { /// reverts function testRevertingCall() public { uint256 initBalance = address(this).balance; - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); try this.revertingCall{value: 1 ether}(address(1234), "") {} catch {} assertEq( "0x00000000000000000000000000000000000004d2\n- balance diff: 0 \xE2\x86\x92 100000000000000000\n\n", - cheats.getStateDiff() + vm.getStateDiff() ); assertEq( - "{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"stateDiff\":{}}}", - cheats.getStateDiffJson() + '{"0x00000000000000000000000000000000000004d2":{"label":null,"contract":null,"balanceDiff":{"previousValue":"0x0","newValue":"0x16345785d8a0000"},"nonceDiff":null,"stateDiff":{}}}', + vm.getStateDiffJson() ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 2); assertEq( called[0], @@ -489,6 +504,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: initBalance, newBalance: initBalance, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 1 ether, data: abi.encodeCall(this.revertingCall, (address(1234), "")), @@ -507,6 +524,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0.1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0.1 ether, data: "", @@ -519,14 +538,14 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that nested account accesses are correctly recorded function testNested() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); runNested(false, false); } /// @notice Test that nested account accesses are correctly recorded when /// the first call reverts function testNested_Revert() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); runNested(true, false); } @@ -534,7 +553,7 @@ contract RecordAccountAccessesTest is DSTest { /// @param shouldRevert Whether the first call should revert function runNested(bool shouldRevert, bool expectFirstCall) public { try runner.run{value: 1 ether}(shouldRevert) {} catch {} - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 7 + toUint(expectFirstCall), "incorrect length"); uint64 startingIndex = uint64(toUint(expectFirstCall)); @@ -548,6 +567,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: false, value: 0, @@ -581,6 +602,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: shouldRevert ? 0 : 0.9 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 1 ether, @@ -614,6 +637,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.1 ether, @@ -647,6 +672,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.01 ether, @@ -680,6 +707,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0.01 ether, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.001 ether, @@ -713,6 +742,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.09 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.1 ether, @@ -746,6 +777,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.01 ether, @@ -779,6 +812,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0.01 ether, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.001 ether, @@ -792,18 +827,18 @@ contract RecordAccountAccessesTest is DSTest { } function testNestedStorage() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); nestedStorer.run(); - cheats.label(address(nestedStorer), "NestedStorer"); + vm.label(address(nestedStorer), "NestedStorer"); assertEq( - "0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n", - cheats.getStateDiff() + "0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\ncontract: default/cheats/RecordAccountAccesses.t.sol:NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n", + vm.getStateDiff() ); assertEq( - "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}", - cheats.getStateDiffJson() + '{"0x2e234dae75c793f67a35089c9d99245e1c58470b":{"label":"NestedStorer","contract":"default/cheats/RecordAccountAccesses.t.sol:NestedStorer","balanceDiff":null,"nonceDiff":null,"stateDiff":{"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"},"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}}}}', + vm.getStateDiffJson() ); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(called[0].storageAccesses.length, 2, "incorrect run storage length"); @@ -828,6 +863,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -873,6 +910,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -906,6 +945,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Resume, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -920,14 +961,14 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that constructor account and storage accesses are recorded, including reverts function testConstructorStorage() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); address storer = address(new ConstructorStorer(false)); try create2or.create2(bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) {} catch {} bytes memory creationCode = abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true)); address hypotheticalStorer = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create), "incorrect kind"); assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call), "incorrect kind"); @@ -952,6 +993,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: storer.code, initialized: true, value: 0, @@ -972,6 +1015,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -1004,6 +1049,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: address(hypotheticalStorer).code, initialized: true, value: 0, @@ -1034,12 +1081,12 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that constructor calls and calls made within a constructor /// are correctly recorded, even if it reverts function testCreateRevert() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); bytes memory creationCode = abi.encodePacked(type(SelfCaller).creationCode, abi.encode("")); try create2or.create2(bytes32(0), creationCode) {} catch {} address hypotheticalAddress = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect length"); assertEq( called[1], @@ -1050,6 +1097,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: address(hypotheticalAddress).code, initialized: true, value: 0, @@ -1068,6 +1117,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", initialized: true, value: 0, @@ -1087,7 +1138,7 @@ contract RecordAccountAccessesTest is DSTest { this.startRecordingFromLowerDepth(); address a = address(new SelfDestructor{value: 1 ether}(address(this))); address b = address(new SelfDestructor{value: 1 ether}(address(bytes20("doesn't exist yet")))); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 5, "incorrect length"); assertEq( called[1], @@ -1098,6 +1149,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: "", initialized: true, value: 1 ether, @@ -1116,6 +1169,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.SelfDestruct, oldBalance: startingBalance - 1 ether, newBalance: startingBalance, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 1 ether, @@ -1134,10 +1189,14 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: "", initialized: true, value: 1 ether, - data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(bytes20("doesn't exist yet")))), + data: abi.encodePacked( + type(SelfDestructor).creationCode, abi.encode(address(bytes20("doesn't exist yet"))) + ), reverted: false, storageAccesses: new Vm.StorageAccess[](0), depth: 3 @@ -1152,6 +1211,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.SelfDestruct, oldBalance: 0, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", initialized: false, value: 1 ether, @@ -1165,13 +1226,13 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Asserts interaction between broadcast and recording cheatcodes function testIssue6514() public { - cheats.startStateDiffRecording(); - cheats.startBroadcast(); + vm.startStateDiffRecording(); + vm.startBroadcast(); StorageAccessor a = new StorageAccessor(); - cheats.stopBroadcast(); - Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + vm.stopBroadcast(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(vm.stopAndReturnStateDiff()); assertEq(called.length, 1, "incorrect length"); assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create)); assertEq(called[0].account, address(a)); @@ -1179,22 +1240,17 @@ contract RecordAccountAccessesTest is DSTest { /// @notice Test that EXT* opcodes are recorded as account accesses function testExtOpcodes() public { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); extChecker.checkExts(address(1234)); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); - assertEq(called.length, 7, "incorrect length"); - // initial solidity extcodesize check for calling extChecker - assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + assertEq(called.length, 5, "incorrect length"); // call to extChecker - assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call)); + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Call)); // extChecker checks - assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodesize)); - assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodehash)); - assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); - assertEq(toUint(called[5].kind), toUint(Vm.AccountAccessKind.Balance)); - // resume of extChecker to hold SSTORE access - assertEq(toUint(called[6].kind), toUint(Vm.AccountAccessKind.Resume)); - assertEq(called[6].storageAccesses.length, 1, "incorrect length"); + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodehash)); + assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); + assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Balance)); } /** @@ -1224,7 +1280,7 @@ contract RecordAccountAccessesTest is DSTest { } function startRecordingFromLowerDepth() external { - cheats.startStateDiffRecording(); + vm.startStateDiffRecording(); assembly { pop(call(gas(), 1234, 0, 0, 0, 0, 0)) } @@ -1248,6 +1304,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Resume, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: expected.initialized, value: 0, @@ -1340,4 +1398,51 @@ contract RecordAccountAccessesTest is DSTest { function deriveCreate2Address(address deployer, bytes32 salt, bytes32 codeHash) internal pure returns (address) { return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, codeHash))))); } + + /// @notice Simple test for getStorageAccesses() cheatcode + function testGetStorageAccesses() public { + StorageAccessor accessor = test1; + + // Start recording to enable storage access tracking + vm.startStateDiffRecording(); + + // Perform a read operation + accessor.read(bytes32(uint256(789))); + + // Perform a write operation + accessor.write(bytes32(uint256(123)), bytes32(uint256(456))); + + // Perform another read operation after the write + accessor.read(bytes32(uint256(123))); + + // Get all storage accesses + Vm.StorageAccess[] memory accesses = vm.getStorageAccesses(); + + // Check we have 3 storage accesses (2 reads + 1 write) + assertEq(accesses.length, 3, "should have 3 storage accesses"); + + // Check the first read access + assertEq(accesses[0].account, address(accessor)); + assertEq(accesses[0].slot, bytes32(uint256(789))); + assertEq(accesses[0].isWrite, false); + assertEq(accesses[0].previousValue, bytes32(uint256(0))); + assertEq(accesses[0].newValue, bytes32(uint256(0))); + assertEq(accesses[0].reverted, false); + + // Check the write access + assertEq(accesses[1].account, address(accessor)); + assertEq(accesses[1].slot, bytes32(uint256(123))); + assertEq(accesses[1].isWrite, true); + assertEq(accesses[1].previousValue, bytes32(uint256(0))); + assertEq(accesses[1].newValue, bytes32(uint256(456))); + assertEq(accesses[1].reverted, false); + + // Check the second read access (reading the value we just wrote) + assertEq(accesses[2].account, address(accessor)); + assertEq(accesses[2].slot, bytes32(uint256(123))); + assertEq(accesses[2].isWrite, false); + assertEq(accesses[2].previousValue, bytes32(uint256(456))); + assertEq(accesses[2].newValue, bytes32(uint256(456))); + assertEq(accesses[2].reverted, false); + } } diff --git a/testdata/default/cheats/RecordDebugTrace.t.sol b/testdata/default/cheats/RecordDebugTrace.t.sol index ade2e7aafb7e1..b4f15e1aad5b6 100644 --- a/testdata/default/cheats/RecordDebugTrace.t.sol +++ b/testdata/default/cheats/RecordDebugTrace.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract MStoreAndMLoadCaller { uint256 public constant expectedValueInMemory = 999; @@ -64,23 +63,21 @@ contract OutOfGas { } } -contract RecordDebugTraceTest is DSTest { - Vm constant cheats = Vm(HEVM_ADDRESS); +contract RecordDebugTraceTest is Test { /** * The goal of this test is to ensure the debug steps provide the correct OPCODE with its stack * and memory input used. The test checke MSTORE and MLOAD and ensure it records the expected * stack and memory inputs. */ - function testDebugTraceCanRecordOpcodeWithStackAndMemoryData() public { MStoreAndMLoadCaller testContract = new MStoreAndMLoadCaller(); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); uint256 val = testContract.storeAndLoadValueFromMemory(); assertTrue(val == testContract.expectedValueInMemory()); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool mstoreCalled = false; bool mloadCalled = false; @@ -88,14 +85,16 @@ contract RecordDebugTraceTest is DSTest { for (uint256 i = 0; i < steps.length; i++) { Vm.DebugStep memory step = steps[i]; if ( - step.opcode == 0x52 /*MSTORE*/ && step.stack[0] == testContract.memPtr() // MSTORE offset + step.opcode == 0x52 /*MSTORE*/ + && step.stack[0] == testContract.memPtr() // MSTORE offset && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val ) { mstoreCalled = true; } if ( - step.opcode == 0x51 /*MLOAD*/ && step.stack[0] == testContract.memPtr() // MLOAD offset + step.opcode == 0x51 /*MLOAD*/ + && step.stack[0] == testContract.memPtr() // MLOAD offset && step.memoryInput.length == 32 // MLOAD should always load 32 bytes && uint256(bytes32(step.memoryInput)) == testContract.expectedValueInMemory() // MLOAD value ) { @@ -116,11 +115,11 @@ contract RecordDebugTraceTest is DSTest { SecondLayer second = new SecondLayer(); FirstLayer first = new FirstLayer(second); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); first.callSecondLayer(); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool goToDepthTwo = false; bool goToDepthThree = false; @@ -147,11 +146,11 @@ contract RecordDebugTraceTest is DSTest { function testDebugTraceCanRecordOutOfGas() public { OutOfGas testContract = new OutOfGas(); - cheats.startDebugTraceRecording(); + vm.startDebugTraceRecording(); testContract.triggerOOG(); - Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); bool isOOG = false; for (uint256 i = 0; i < steps.length; i++) { @@ -163,4 +162,19 @@ contract RecordDebugTraceTest is DSTest { } assertTrue(isOOG, "should OOG"); } + + /// forge-config: default.allow_internal_expect_revert = true + function testCannotRestartDebugTraceRecordingWhileActive() public { + MStoreAndMLoadCaller testContract = new MStoreAndMLoadCaller(); + + vm.startDebugTraceRecording(); + + vm.expectRevert("vm.startDebugTraceRecording: debug trace recording was already started"); + vm.startDebugTraceRecording(); + + testContract.storeAndLoadValueFromMemory(); + + Vm.DebugStep[] memory steps = vm.stopAndReturnDebugTraceRecording(); + assertGt(steps.length, 0, "first recording should remain active after failed restart"); + } } diff --git a/testdata/default/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol index 14ca8dde35e99..a21e7f0a06304 100644 --- a/testdata/default/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Emitter { event LogAnonymous(bytes data) anonymous; @@ -48,8 +47,7 @@ contract Emitterv2 { } } -contract RecordLogsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract RecordLogsTest is Test { Emitter emitter; bytes32 internal seedTestData = keccak256(abi.encodePacked("Some data")); @@ -190,6 +188,41 @@ contract RecordLogsTest is DSTest { assertEq(entries[2].emitter, emitter2.getEmitterAddr()); } + function testRecordedLogsJson() public { + bytes memory testData = "Event Data in String"; + + vm.recordLogs(); + emitter.emitEvent(1, 2, 3, testData); + string memory logsJson = vm.getRecordedLogsJson(); + + // Verify JSON structure and values + assertGt(bytes(logsJson).length, 0); + + // Verify emitter address + string memory emitterAddr = vm.parseJsonString(logsJson, "[0].emitter"); + assertEq(vm.parseAddress(emitterAddr), address(emitter)); + + // Verify topics - first topic is event signature + string[] memory topics = vm.parseJsonStringArray(logsJson, "[0].topics"); + assertEq(topics.length, 4); + assertEq(vm.parseBytes32(topics[0]), keccak256("LogTopic123(uint256,uint256,uint256,bytes)")); + assertEq(vm.parseBytes32(topics[1]), bytes32(uint256(1))); + assertEq(vm.parseBytes32(topics[2]), bytes32(uint256(2))); + assertEq(vm.parseBytes32(topics[3]), bytes32(uint256(3))); + + // Verify data is hex-encoded + string memory data = vm.parseJsonString(logsJson, "[0].data"); + assertGt(bytes(data).length, 2); // At least "0x" + } + + function testRecordedLogsJsonEmpty() public { + vm.recordLogs(); + string memory logsJson = vm.getRecordedLogsJson(); + + // Empty array + assertEq(logsJson, "[]"); + } + function testRecordsConsumednAsRead() public { Vm.Log[] memory entries; diff --git a/testdata/default/cheats/Remember.t.sol b/testdata/default/cheats/Remember.t.sol index b8dbe7e38007c..fdf00e3839f72 100644 --- a/testdata/default/cheats/Remember.t.sol +++ b/testdata/default/cheats/Remember.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RememberTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RememberTest is Test { function testRememberKey() public { string memory mnemonic = "test test test test test test test test test test test junk"; diff --git a/testdata/default/cheats/ResetNonce.t.sol b/testdata/default/cheats/ResetNonce.t.sol index d8c911587095c..53c8ec54b09c0 100644 --- a/testdata/default/cheats/ResetNonce.t.sol +++ b/testdata/default/cheats/ResetNonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract ResetNonce is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract ResetNonce is Test { Foo public fooContract; address barEOA; diff --git a/testdata/default/cheats/Rlp.t.sol b/testdata/default/cheats/Rlp.t.sol new file mode 100644 index 0000000000000..24f2ec833de7a --- /dev/null +++ b/testdata/default/cheats/Rlp.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract Rlp is Test { + function testToRlp() public { + bytes[] memory data = new bytes[](2); + data[0] = hex"01"; + data[1] = hex"02"; + + bytes memory rlp = vm.toRlp(data); + + // Assert the expected RLP encoding for [0x01, 0x02] + // 0xc2 = list with 2 bytes total length + // 0x01 = first byte + // 0x02 = second byte + assertEq(rlp, hex"c20102"); + } + + function testFromRlp() public { + // RLP encoded [0x01, 0x02] + bytes memory rlp = hex"c20102"; + + bytes[] memory decoded = vm.fromRlp(rlp); + assertEq(decoded.length, 2); + assertEq(decoded[0], hex"01"); + assertEq(decoded[1], hex"02"); + } + + function testRoundTrip() public { + bytes[] memory original = new bytes[](3); + original[0] = hex"deadbeef"; + original[1] = hex"cafebabe"; + original[2] = hex"01020304"; + + bytes memory rlp = vm.toRlp(original); + bytes[] memory decoded = vm.fromRlp(rlp); + + assertEq(decoded.length, original.length); + for (uint256 i = 0; i < original.length; i++) { + assertEq(decoded[i], original[i]); + } + } + + function testDecodeBlockHeader() public { + // cast block 23270177 --raw + bytes memory blockHeader = + hex"f9027da01581f4448b16694d5a728161cd65f8c80b88f5352a6f5bd2d2315b970582958da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794dadb0d80178819f2319190d340ce9a924f783711a010d2afa5dabcf2dbfe3aa82b758427938e07880bd6fef3c82c404d0dd7c3f0f3a0f81230c715a462c827898bf2e337982907a7af90e5be20f911785bda05dab93ca0740f11bc75cf25e40d78d892d2e03083eaa573e5b4c26913fcc1b833db854c94b9010085f734fb06ea8fe377abbcb2e27f9ac99751ba817dc327327db101fd76f964ed0b7ca161f148fc165b9e5b575dc7473f17f4b8ebbf4a7b02b3e1e642197f27b2af54680834449abaf833619ac7d18afb50b19d5f6944dca0dc952edfdd9837573783c339ee6a36353ce6e536eaaf29fcd569c426091d4e24568dc353347f98c74fb6f8c91d68d358467c437563f66566377fe6c3f9e8301dbeb5fc7e7adee7a85ef5f8fa905cedbaf26601e21ba91646cac4034601e51d889d49739ee6990943a6a41927660f68e1f50b9f9209ee29551a7dae478d88e0547eefc83334ea770bb6fbac620fc47479c2c59389622bf32f55e36a75e56a5fc47c38bf8ef211fc0e8084016313218402af50e883fc53b78468b5ea9b974275696c6465724e657420284e65746865726d696e6429a0580ca94e91c0e7aef26ffb0c86f6ae48ef40df6dd1629f203a1930e0ce0be9d188000000000000000084479c1e2aa00345740e1b79edb2fbb3a20220e1a497ea9bb82aaba7dc7a881f7f3cae8a8ea38080a06675ad2a40134499a753924a04b75898ae09efc6fba6b3d7a506203042cb7611a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + bytes[] memory decoded = vm.fromRlp(blockHeader); + + // Verify key fields against known values from block 23270177 + assertEq(decoded.length, 21); + assertEq(decoded[0], hex"1581f4448b16694d5a728161cd65f8c80b88f5352a6f5bd2d2315b970582958d", "parentHash"); + assertEq(decoded[1], hex"1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "uncleHash"); + assertEq(decoded[2], hex"dadb0d80178819f2319190d340ce9a924f783711", "coinbase"); + assertEq(decoded[3], hex"10d2afa5dabcf2dbfe3aa82b758427938e07880bd6fef3c82c404d0dd7c3f0f3", "stateRoot"); + assertEq(decoded[4], hex"f81230c715a462c827898bf2e337982907a7af90e5be20f911785bda05dab93c", "transactionsRoot"); + assertEq(decoded[5], hex"740f11bc75cf25e40d78d892d2e03083eaa573e5b4c26913fcc1b833db854c94", "receiptsRoot"); + + { + // Verify logsBloom (256 bytes) + bytes memory logsBloom = decoded[6]; + assertEq(logsBloom.length, 256, "logsBloom length"); + _checkLogsBloom(logsBloom, 0, 0x85f734fb06ea8fe377abbcb2e27f9ac99751ba817dc327327db101fd76f964ed); + _checkLogsBloom(logsBloom, 1, 0x0b7ca161f148fc165b9e5b575dc7473f17f4b8ebbf4a7b02b3e1e642197f27b2); + _checkLogsBloom(logsBloom, 2, 0xaf54680834449abaf833619ac7d18afb50b19d5f6944dca0dc952edfdd983757); + _checkLogsBloom(logsBloom, 3, 0x3783c339ee6a36353ce6e536eaaf29fcd569c426091d4e24568dc353347f98c7); + _checkLogsBloom(logsBloom, 4, 0x4fb6f8c91d68d358467c437563f66566377fe6c3f9e8301dbeb5fc7e7adee7a8); + _checkLogsBloom(logsBloom, 5, 0x5ef5f8fa905cedbaf26601e21ba91646cac4034601e51d889d49739ee6990943); + _checkLogsBloom(logsBloom, 6, 0xa6a41927660f68e1f50b9f9209ee29551a7dae478d88e0547eefc83334ea770b); + _checkLogsBloom(logsBloom, 7, 0xb6fbac620fc47479c2c59389622bf32f55e36a75e56a5fc47c38bf8ef211fc0e); + } + + assertEq(decoded[7], hex"", "difficulty"); + assertEq(decoded[8], hex"01631321", "number"); + assertEq(decoded[9], hex"02af50e8", "gasLimit"); + assertEq(decoded[10], hex"fc53b7", "gasUsed"); + assertEq(decoded[11], hex"68b5ea9b", "timestamp"); + assertEq(decoded[12], hex"4275696c6465724e657420284e65746865726d696e6429", "extraData"); + assertEq(decoded[13], hex"580ca94e91c0e7aef26ffb0c86f6ae48ef40df6dd1629f203a1930e0ce0be9d1", "mixHash"); + assertEq(decoded[14], hex"0000000000000000", "nonce"); + assertEq(decoded[15], hex"479c1e2a", "baseFee"); + assertEq(decoded[16], hex"0345740e1b79edb2fbb3a20220e1a497ea9bb82aaba7dc7a881f7f3cae8a8ea3", "withdrawalsHash"); + assertEq(decoded[17], hex"", "blobGasUsed"); + assertEq(decoded[18], hex"", "excessBlobGas"); + assertEq(decoded[19], hex"6675ad2a40134499a753924a04b75898ae09efc6fba6b3d7a506203042cb7611", "parentBeaconRoot"); + assertEq(decoded[20], hex"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "requestsHash"); + } + + function _checkLogsBloom(bytes memory data, uint256 n, uint256 expected) internal { + uint256 offset = (n + 1) * 32; + bytes32 result; + assembly { + result := mload(add(data, offset)) + } + + assertEq(result, bytes32(expected), string.concat("logsBloom[", vm.toString(n), "]")); + } +} diff --git a/testdata/default/cheats/Roll.t.sol b/testdata/default/cheats/Roll.t.sol index 0f26e3a431d7a..38e94b2bea267 100644 --- a/testdata/default/cheats/Roll.t.sol +++ b/testdata/default/cheats/Roll.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RollTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RollTest is Test { function testRoll() public { vm.roll(10); assertEq(block.number, 10, "roll failed"); diff --git a/testdata/default/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol index 86f4d33b1d41c..97ee9ad3a9293 100644 --- a/testdata/default/cheats/RpcUrls.t.sol +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RpcUrlTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RpcUrlTest is Test { // returns the correct url function testCanGetRpcUrl() public { string memory url = vm.rpcUrl("mainnet"); - assertTrue(bytes(url).length >= 36); + assertTrue(bytes(url).length != 0); } // returns an error if env alias does not exist diff --git a/testdata/default/cheats/Seed.t.sol b/testdata/default/cheats/Seed.t.sol new file mode 100644 index 0000000000000..0909c38a26f72 --- /dev/null +++ b/testdata/default/cheats/Seed.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract SeedTest is Test { + function testSeedAffectsRandom() public { + // Use a known seed + uint256 seed = 123456789; + vm.setSeed(seed); + + // Call a foundry cheatcode to get a random value (this depends on the integration) + uint256 rand1 = uint256(vm.randomUint()); + + // Reset the seed and verify the result is the same + vm.setSeed(seed); + uint256 rand2 = uint256(vm.randomUint()); + + uint256 rand3 = uint256(vm.randomUint()); + // If the seed is the same, the random value must be equal + assertEq(rand1, rand2); + assertTrue(rand1 != rand3); + } + + function testSeedChangesRandom() public { + // Use one seed + vm.setSeed(1); + uint256 randA = uint256(vm.randomUint()); + + // Use a different seed + vm.setSeed(2); + uint256 randB = uint256(vm.randomUint()); + + // Values must be different + assertTrue(randA != randB, "Random value must be different if seed is different"); + } + + function testSeedAffectsShuffle() public { + // Use a known seed + uint256 seed = 123456789; + vm.setSeed(seed); + + // Create two identical arrays + uint256[] memory array1 = new uint256[](5); + uint256[] memory array2 = new uint256[](5); + for (uint256 i = 0; i < 5; i++) { + array1[i] = i; + array2[i] = i; + } + + // Shuffle both arrays with the same seed + array1 = vm.shuffle(array1); + vm.setSeed(seed); // Reset the seed to get the same shuffle pattern + array2 = vm.shuffle(array2); + + // Compare elements - they should be identical after shuffle + for (uint256 i = 0; i < array1.length; i++) { + assertEq(array1[i], array2[i], "Arrays should be identical with same seed"); + } + } + + function testDifferentSeedsProduceDifferentShuffles() public { + // Create the initial array + uint256[] memory array1 = new uint256[](5); + uint256[] memory array2 = new uint256[](5); + for (uint256 i = 0; i < 5; i++) { + array1[i] = i; + array2[i] = i; + } + + // Use first seed + vm.setSeed(1); + array1 = vm.shuffle(array1); + + // Use second seed + vm.setSeed(2); + array2 = vm.shuffle(array2); + + // Arrays should be different (we'll check at least one difference exists) + bool foundDifference = false; + for (uint256 i = 0; i < array1.length; i++) { + if (array1[i] != array2[i]) { + foundDifference = true; + break; + } + } + assertTrue(foundDifference, "Arrays should be different with different seeds"); + } +} diff --git a/testdata/default/cheats/SetBlockhash.t.sol b/testdata/default/cheats/SetBlockhash.t.sol index 1274620df41a6..2897976d11e5e 100644 --- a/testdata/default/cheats/SetBlockhash.t.sol +++ b/testdata/default/cheats/SetBlockhash.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SetBlockhash is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SetBlockhash is Test { function testSetBlockhash() public { bytes32 blockHash = 0x1234567890123456789012345678901234567890123456789012345678901234; vm.setBlockhash(block.number - 1, blockHash); diff --git a/testdata/default/cheats/SetNonce.t.sol b/testdata/default/cheats/SetNonce.t.sol index e0fda6aaec688..19e8a366b92ca 100644 --- a/testdata/default/cheats/SetNonce.t.sol +++ b/testdata/default/cheats/SetNonce.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract SetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract SetNonceTest is Test { Foo public foo; function setUp() public { diff --git a/testdata/default/cheats/SetNonceUnsafe.t.sol b/testdata/default/cheats/SetNonceUnsafe.t.sol index 0caf2b4ce7421..9f799f0829acc 100644 --- a/testdata/default/cheats/SetNonceUnsafe.t.sol +++ b/testdata/default/cheats/SetNonceUnsafe.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo { function f() external view returns (uint256) { @@ -10,8 +9,7 @@ contract Foo { } } -contract SetNonceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract SetNonceTest is Test { Foo public foo; function setUp() public { diff --git a/testdata/default/cheats/Setup.t.sol b/testdata/default/cheats/Setup.t.sol index 4d6e5954b5fe1..9ce1682ab7a3c 100644 --- a/testdata/default/cheats/Setup.t.sol +++ b/testdata/default/cheats/Setup.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Victim { function assertSender(address sender) external { @@ -10,8 +9,7 @@ contract Victim { } } -contract VmSetupTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract VmSetupTest is Test { Victim victim; function setUp() public { diff --git a/testdata/default/cheats/Shuffle.t.sol b/testdata/default/cheats/Shuffle.t.sol new file mode 100644 index 0000000000000..5701fa7ab72c2 --- /dev/null +++ b/testdata/default/cheats/Shuffle.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract ShuffleTest is Test { + function testDeterministicShuffle() public { + // Use a known seed + uint256 seed = 123456789; + vm.setSeed(seed); + + // Create two identical arrays + uint256[] memory array1 = new uint256[](5); + uint256[] memory array2 = new uint256[](5); + for (uint256 i = 0; i < 5; i++) { + array1[i] = i; + array2[i] = i; + } + + // Shuffle both arrays with the same seed + array1 = vm.shuffle(array1); + vm.setSeed(seed); // Reset the seed to get the same shuffle pattern + array2 = vm.shuffle(array2); + + // Compare elements - they should be identical after shuffle + for (uint256 i = 0; i < array1.length; i++) { + assertEq(array1[i], array2[i], "Arrays should be identical with same seed"); + } + } + + function testDifferentSeedsProduceDifferentShuffles() public { + // Create the initial array + uint256[] memory array1 = new uint256[](5); + uint256[] memory array2 = new uint256[](5); + for (uint256 i = 0; i < 5; i++) { + array1[i] = i; + array2[i] = i; + } + + // Use first seed + vm.setSeed(1); + array1 = vm.shuffle(array1); + + // Use second seed + vm.setSeed(2); + array2 = vm.shuffle(array2); + + // Arrays should be different (we'll check at least one difference exists) + bool foundDifference = false; + for (uint256 i = 0; i < array1.length; i++) { + if (array1[i] != array2[i]) { + foundDifference = true; + break; + } + } + assertTrue(foundDifference, "Arrays should be different with different seeds"); + } +} diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol index 937ebc00a9222..ed0a9aa2e93de 100644 --- a/testdata/default/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SignTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SignTest is Test { function testSignDigest(uint248 pk, bytes32 digest) public { vm.assume(pk != 0); @@ -44,4 +41,60 @@ contract SignTest is DSTest { function testSignCompactMessage(uint248 pk, bytes memory message) public { testSignCompactDigest(pk, keccak256(message)); } + + /// secp256k1 subgroup order n + function _secp256k1Order() internal pure returns (uint256) { + return 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; + } + + function testsignWithNonceUnsafeDigestDifferentNonces(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + uint256 n1 = 123; + uint256 n2 = 456; + vm.assume(n1 != 0 && n2 != 0 && n1 != n2); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.signWithNonceUnsafe(pk, digest, n1); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.signWithNonceUnsafe(pk, digest, n2); + assertTrue(r1 != r2 || s1 != s2, "signatures should differ for different nonces"); + address expected = vm.addr(pk); + assertEq(ecrecover(digest, v1, r1, s1), expected, "recover for nonce n1 failed"); + assertEq(ecrecover(digest, v2, r2, s2), expected, "recover for nonce n2 failed"); + } + + function testsignWithNonceUnsafeDigestSameNonceDeterministic(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + uint256 n = 777; + vm.assume(n != 0); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.signWithNonceUnsafe(pk, digest, n); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.signWithNonceUnsafe(pk, digest, n); + assertEq(v1, v2, "v should match"); + assertEq(r1, r2, "r should match"); + assertEq(s1, s2, "s should match"); + address expected = vm.addr(pk); + assertEq(ecrecover(digest, v1, r1, s1), expected, "recover failed"); + } + + function testsignWithNonceUnsafeInvalidNoncesRevert() public { + uint256 pk = 1; + bytes32 digest = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + (bool ok, bytes memory data) = + HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonceUnsafe.selector, pk, digest, 0)); + assertTrue(!ok, "expected revert on nonce=0"); + assertEq(_revertString(data), "vm.signWithNonceUnsafe: nonce cannot be 0"); + uint256 n = _secp256k1Order(); + (ok, data) = HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonceUnsafe.selector, pk, digest, n)); + assertTrue(!ok, "expected revert on nonce >= n"); + assertEq(_revertString(data), "vm.signWithNonceUnsafe: invalid nonce scalar"); + } + + /// Decode revert payload + /// by stripping the 4-byte selector and ABI-decoding the tail as `string`. + function _revertString(bytes memory data) internal pure returns (string memory) { + if (data.length < 4) return ""; + // copy data[4:] into a new bytes + bytes memory tail = new bytes(data.length - 4); + for (uint256 i = 0; i < tail.length; i++) { + tail[i] = data[i + 4]; + } + return abi.decode(tail, (string)); + } } diff --git a/testdata/default/cheats/SignP256.t.sol b/testdata/default/cheats/SignP256.t.sol index b92588ce9f823..d8d07acbf67b7 100644 --- a/testdata/default/cheats/SignP256.t.sol +++ b/testdata/default/cheats/SignP256.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SignTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SignTest is Test { function testSignP256() public { bytes32 pk = hex"A8568B74282DCC66FF70F10B4CE5CC7B391282F5381BBB4F4D8DD96974B16E6B"; bytes32 digest = hex"54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad"; diff --git a/testdata/default/cheats/Skip.t.sol b/testdata/default/cheats/Skip.t.sol index d7e75fa0f51af..0eff55b95db30 100644 --- a/testdata/default/cheats/Skip.t.sol +++ b/testdata/default/cheats/Skip.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SkipTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SkipTest is Test { function testSkip() public { vm.skip(true); revert("Should not reach this revert"); diff --git a/testdata/default/cheats/Sleep.t.sol b/testdata/default/cheats/Sleep.t.sol index 7af548e742573..b3268d41d3672 100644 --- a/testdata/default/cheats/Sleep.t.sol +++ b/testdata/default/cheats/Sleep.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SleepTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SleepTest is Test { function testSleep() public { uint256 milliseconds = 1234; diff --git a/testdata/default/cheats/Sort.t.sol b/testdata/default/cheats/Sort.t.sol index 2f557d54c2bbc..91991b572a081 100644 --- a/testdata/default/cheats/Sort.t.sol +++ b/testdata/default/cheats/Sort.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract SortTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract SortTest is Test { function testSortCheatcode() public { uint256[] memory numbers = new uint256[](3); numbers[0] = 3; diff --git a/testdata/default/cheats/StateDiffBytesString.t.sol b/testdata/default/cheats/StateDiffBytesString.t.sol new file mode 100644 index 0000000000000..a61e3ee37f60d --- /dev/null +++ b/testdata/default/cheats/StateDiffBytesString.t.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract BytesStringStorage { + // Short string (less than 32 bytes) + string public shortString; // Slot 0 + + // Long string (32 bytes or more) + string public longString; // Slot 1 + + // Short bytes + bytes public shortBytes; // Slot 2 + + // Long bytes + bytes public longBytes; // Slot 3 + + // Fixed size bytes + bytes32 public fixedBytes; // Slot 4 + + // Mapping with string values + mapping(address => string) public userNames; // Slot 5 + + function setShortString(string memory _value) public { + shortString = _value; + } + + function setLongString(string memory _value) public { + longString = _value; + } + + function setShortBytes(bytes memory _value) public { + shortBytes = _value; + } + + function setLongBytes(bytes memory _value) public { + longBytes = _value; + } + + function setFixedBytes(bytes32 _value) public { + fixedBytes = _value; + } + + function setUserName(address user, string memory name) public { + userNames[user] = name; + } +} + +contract StateDiffBytesStringTest is Test { + BytesStringStorage bytesStringStorage; + + function setUp() public { + bytesStringStorage = new BytesStringStorage(); + } + + function testLongStringStorage() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set a long string (32 bytes or more) + string memory longStr = + "This is a very long string that exceeds 32 bytes and will be stored differently in Solidity storage"; + bytesStringStorage.setLongString(longStr); + + // Get the state diff as string + string memory stateDiff = vm.getStateDiff(); + emit log_string("State diff for long string:"); + emit log_string(stateDiff); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + emit log_string("State diff JSON for long string:"); + emit log_string(stateDiffJson); + + // Verify the JSON contains expected fields + assertTrue(vm.contains(stateDiffJson, '"label":"longString"')); + assertTrue(vm.contains(stateDiffJson, '"type":"string"')); + + // For long strings, we should see multiple slots being accessed + // The main slot (slot 1) contains the length + // The data slots start at keccak256(1) + + // Stop recording + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + assertTrue(accesses.length > 0); + + // Verify storage accesses + uint256 writeCount = 0; + for (uint256 i = 0; i < accesses.length; i++) { + if (accesses[i].account == address(bytesStringStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + if (accesses[i].storageAccesses[j].isWrite) { + writeCount++; + } + } + } + } + // Long string should write to multiple slots (main slot + data slots) + assertTrue(writeCount >= 2); + } + + function testShortBytesStorage() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set short bytes (less than 32 bytes) + bytes memory shortData = hex"deadbeef"; + bytesStringStorage.setShortBytes(shortData); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + emit log_string("State diff JSON for short bytes:"); + emit log_string(stateDiffJson); + + // Verify the JSON contains expected fields + assertTrue(vm.contains(stateDiffJson, '"label":"shortBytes"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes"')); + assertTrue(vm.contains(stateDiffJson, '"decoded":')); + + // Check the decoded bytes value + assertTrue(vm.contains(stateDiffJson, '"newValue":"0xdeadbeef"')); + + // Stop recording + vm.stopAndReturnStateDiff(); + } + + function testLongBytesStorage() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set long bytes (32 bytes or more) + bytes memory longData = new bytes(100); + for (uint256 i = 0; i < 100; i++) { + longData[i] = bytes1(uint8(i)); + } + bytesStringStorage.setLongBytes(longData); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + emit log_string("State diff JSON for long bytes:"); + emit log_string(stateDiffJson); + + // Verify the JSON contains expected fields + assertTrue(vm.contains(stateDiffJson, '"label":"longBytes"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes"')); + + // Stop recording + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + + // Verify multiple slots were written (main slot + data slots) + uint256 writeCount = 0; + for (uint256 i = 0; i < accesses.length; i++) { + if (accesses[i].account == address(bytesStringStorage)) { + writeCount += accesses[i].storageAccesses.length; + } + } + assertTrue(writeCount >= 2); + } + + function testFixedBytesStorage() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set fixed bytes32 + bytes32 fixedData = keccak256("test data"); + bytesStringStorage.setFixedBytes(fixedData); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + emit log_string("State diff JSON for fixed bytes:"); + emit log_string(stateDiffJson); + + // Verify the JSON contains expected fields + assertTrue(vm.contains(stateDiffJson, '"label":"fixedBytes"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes32"')); + assertTrue(vm.contains(stateDiffJson, '"decoded":')); + + // Stop recording + vm.stopAndReturnStateDiff(); + } + + function testMultipleBytesStringChanges() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Make multiple changes + bytesStringStorage.setShortString("Short"); + bytesStringStorage.setLongString("This is a long string that will use multiple storage slots for data"); + bytesStringStorage.setShortBytes(hex"1234"); + bytesStringStorage.setFixedBytes(bytes32(uint256(0xdeadbeef))); + + // Get the state diff as string + string memory stateDiff = vm.getStateDiff(); + emit log_string("State diff for multiple changes:"); + emit log_string(stateDiff); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + emit log_string("State diff JSON for multiple changes:"); + emit log_string(stateDiffJson); + + // Verify all fields are properly labeled + assertTrue(vm.contains(stateDiffJson, '"label":"shortString"')); + assertTrue(vm.contains(stateDiffJson, '"label":"longString"')); + assertTrue(vm.contains(stateDiffJson, '"label":"shortBytes"')); + assertTrue(vm.contains(stateDiffJson, '"label":"fixedBytes"')); + + // Verify types are correct + assertTrue(vm.contains(stateDiffJson, '"type":"string"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes32"')); + + // Stop recording + vm.stopAndReturnStateDiff(); + } +} diff --git a/testdata/default/cheats/StateDiffMappings.t.sol b/testdata/default/cheats/StateDiffMappings.t.sol new file mode 100644 index 0000000000000..45b53e0dfc5f0 --- /dev/null +++ b/testdata/default/cheats/StateDiffMappings.t.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract MappingStorage { + // Simple mappings only + mapping(address => uint256) public balances; // Slot 0 + mapping(uint256 => address) public owners; // Slot 1 + mapping(bytes32 => bool) public flags; // Slot 2 + // Nested mapping + mapping(address => mapping(address => uint256)) public allowances; // Slot 3 + + function setBalance(address account, uint256 amount) public { + balances[account] = amount; + } + + function setOwner(uint256 tokenId, address owner) public { + owners[tokenId] = owner; + } + + function setFlag(bytes32 key, bool value) public { + flags[key] = value; + } + + function setAllowance(address owner, address spender, uint256 amount) public { + allowances[owner][spender] = amount; + } +} + +contract StateDiffMappingsTest is Test { + MappingStorage public mappingStorage; + + function setUp() public { + mappingStorage = new MappingStorage(); + } + + function testSimpleMappingStateDiff() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Modify a simple mapping + address testAccount = address(0x1234); + mappingStorage.setBalance(testAccount, 1000 ether); + + // Test the text format output + string memory stateDiffText = vm.getStateDiff(); + emit log_string("State diff text format:"); + emit log_string(stateDiffText); + + // Verify text format contains the mapping label + assertTrue(vm.contains(stateDiffText, "balances[0x0000000000000000000000000000000000001234]")); + + // Verify text format contains the value type + assertTrue(vm.contains(stateDiffText, "uint256")); + + // Verify text format contains decoded values (shown with arrow) + assertTrue(vm.contains(stateDiffText, ": 0")); + assertTrue(vm.contains(stateDiffText, "1000000000000000000000")); + + // Test JSON format output + string memory json = vm.getStateDiffJson(); + emit log_string("State diff JSON (simple mapping):"); + emit log_string(json); + + // The JSON should contain the decoded mapping slot with proper label + assertTrue(vm.contains(json, '"label":"balances[0x0000000000000000000000000000000000001234]"')); + + // Check the type is correctly identified + assertTrue(vm.contains(json, '"type":"mapping(address => uint256)"')); + + // Check decoded values + assertTrue(vm.contains(json, '"decoded":{"previousValue":"0","newValue":"1000000000000000000000"}')); + + // Check that the key field is present for simple mapping + assertTrue(vm.contains(json, '"key":"0x0000000000000000000000000000000000001234"')); + + // Stop recording and verify we have account accesses + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + assertTrue(accesses.length > 0); + + // The AccountAccess structure contains information about storage changes + // but the label and decoded values are only available in the string/JSON outputs + // We've already verified those above + } + + function testMappingWithDifferentKeyTypes() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Test uint256 key + mappingStorage.setOwner(12345, address(0x7777)); + + // Test bytes32 key + bytes32 flagKey = keccak256("test_flag"); + mappingStorage.setFlag(flagKey, true); + + // Test text format output first + string memory stateDiffText = vm.getStateDiff(); + emit log_string("State diff text format (different key types):"); + emit log_string(stateDiffText); + + // Verify text format contains decoded values for uint256 key + assertTrue(vm.contains(stateDiffText, "owners[12345]")); + assertTrue(vm.contains(stateDiffText, "address): 0x0000000000000000000000000000000000000000")); + assertTrue(vm.contains(stateDiffText, "0x0000000000000000000000000000000000007777")); + + // Verify text format contains decoded values for bytes32 key + assertTrue(vm.contains(stateDiffText, "bool): false")); + assertTrue(vm.contains(stateDiffText, "true")); + + // Get state diff JSON + string memory json = vm.getStateDiffJson(); + + // Debug: log the JSON for inspection + emit log_string("State diff JSON (different key types):"); + emit log_string(json); + + // Check uint256 key mapping + assertTrue(vm.contains(json, '"label":"owners[12345]"')); + assertTrue(vm.contains(json, '"type":"mapping(uint256 => address)"')); + assertTrue( + vm.contains( + json, + '"decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x0000000000000000000000000000000000007777"}' + ) + ); + + // Check bytes32 key mapping - the key will be shown as hex + assertTrue(vm.contains(json, '"label":"flags[')); + assertTrue(vm.contains(json, '"type":"mapping(bytes32 => bool)"')); + assertTrue(vm.contains(json, '"decoded":{"previousValue":"false","newValue":"true"}')); + + // Check that the key field is present for uint256 key mapping + assertTrue(vm.contains(json, '"key":"12345"')); + + // Stop recording + vm.stopAndReturnStateDiff(); + } + + function testNestedMappingStateDiff() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Test case 1: owner1 -> spender1 + address owner1 = address(0x1111); + address spender1 = address(0x2222); + mappingStorage.setAllowance(owner1, spender1, 500 ether); + + // Test case 2: same owner (owner1) -> different spender (spender2) + address spender2 = address(0x3333); + mappingStorage.setAllowance(owner1, spender2, 750 ether); + + // Test case 3: different owner (owner2) -> different spender (spender3) + address owner2 = address(0x4444); + address spender3 = address(0x5555); + mappingStorage.setAllowance(owner2, spender3, 1000 ether); + + // Test text format output + string memory stateDiffText = vm.getStateDiff(); + emit log_string("State diff text format (nested mappings):"); + emit log_string(stateDiffText); + + // Verify text format contains nested mapping labels + assertTrue( + vm.contains( + stateDiffText, + "allowances[0x0000000000000000000000000000000000001111][0x0000000000000000000000000000000000002222]" + ) + ); + assertTrue( + vm.contains( + stateDiffText, + "allowances[0x0000000000000000000000000000000000001111][0x0000000000000000000000000000000000003333]" + ) + ); + // The text format shows the value type (uint256) not the full mapping type + assertTrue(vm.contains(stateDiffText, "uint256): 0")); + + // Verify text format contains decoded values for nested mappings + assertTrue(vm.contains(stateDiffText, "500000000000000000000")); + assertTrue(vm.contains(stateDiffText, "750000000000000000000")); + assertTrue(vm.contains(stateDiffText, "1000000000000000000000")); + + // Test JSON format output + string memory json = vm.getStateDiffJson(); + emit log_string("State diff JSON (nested mapping - multiple entries):"); + emit log_string(json); + + // Check that all three nested mapping entries are correctly decoded + + // Entry 1: owner1 -> spender1 + assertTrue( + vm.contains( + json, + '"label":"allowances[0x0000000000000000000000000000000000001111][0x0000000000000000000000000000000000002222]"' + ) + ); + assertTrue(vm.contains(json, '"newValue":"500000000000000000000"')); + + // Entry 2: owner1 -> spender2 (same owner, different spender) + assertTrue( + vm.contains( + json, + '"label":"allowances[0x0000000000000000000000000000000000001111][0x0000000000000000000000000000000000003333]"' + ) + ); + assertTrue(vm.contains(json, '"newValue":"750000000000000000000"')); + + // Entry 3: owner2 -> spender3 (different owner) + assertTrue( + vm.contains( + json, + '"label":"allowances[0x0000000000000000000000000000000000004444][0x0000000000000000000000000000000000005555]"' + ) + ); + assertTrue(vm.contains(json, '"newValue":"1000000000000000000000"')); + + // Check the type is correctly identified for all entries + assertTrue(vm.contains(json, '"type":"mapping(address => mapping(address => uint256))"')); + + // Check that the keys field is present for nested mappings + assertTrue( + vm.contains( + json, + '"keys":["0x0000000000000000000000000000000000001111","0x0000000000000000000000000000000000002222"]' + ) + ); + + assertTrue( + vm.contains( + json, + '"keys":["0x0000000000000000000000000000000000001111","0x0000000000000000000000000000000000003333"]' + ) + ); + + assertTrue( + vm.contains( + json, + '"keys":["0x0000000000000000000000000000000000004444","0x0000000000000000000000000000000000005555"]' + ) + ); + + // Stop recording + vm.stopAndReturnStateDiff(); + } +} diff --git a/testdata/default/cheats/StateDiffStorageLayout.t.sol b/testdata/default/cheats/StateDiffStorageLayout.t.sol new file mode 100644 index 0000000000000..298a8eb8bf236 --- /dev/null +++ b/testdata/default/cheats/StateDiffStorageLayout.t.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +contract SimpleStorage { + uint256 public value; // Slot 0 + address public owner; // Slot 1 + uint256[3] public values; // Slots 2, 3, 4 + + constructor() { + owner = msg.sender; + } + + function setValue(uint256 _value) public { + value = _value; + } + + function setOwner(address _owner) public { + owner = _owner; + } + + function setValues(uint256 a, uint256 b, uint256 c) public { + values[0] = a; + values[1] = b; + values[2] = c; + } +} + +contract VariousArrays { + // Different array types to test + uint256[3] public numbers; // Slots 0, 1, 2 + address[2] public addresses; // Slots 3, 4 + bool[5] public flags; // Slot 5 (packed) + bytes32[2] public hashes; // Slots 6, 7 + + function setNumbers(uint256 a, uint256 b, uint256 c) public { + numbers[0] = a; + numbers[1] = b; + numbers[2] = c; + } + + function setAddresses(address a, address b) public { + addresses[0] = a; + addresses[1] = b; + } + + function setFlags(bool a, bool b, bool c, bool d, bool e) public { + flags[0] = a; + flags[1] = b; + flags[2] = c; + flags[3] = d; + flags[4] = e; + } + + function setHashes(bytes32 a, bytes32 b) public { + hashes[0] = a; + hashes[1] = b; + } +} + +contract TwoDArrayStorage { + // 2D array: 2 arrays of 3 uint256 elements each + // Total slots: 6 (slots 0-5) + // [0][0] at slot 0, [0][1] at slot 1, [0][2] at slot 2 + // [1][0] at slot 3, [1][1] at slot 4, [1][2] at slot 5 + uint256[3][2] public matrix; + + // Another 2D array starting at slot 6 + // 3 arrays of 2 addresses each + // Total slots: 6 (slots 6-11) + address[2][3] public addresses2D; + + // Mixed size 2D array starting at slot 12 + // 4 arrays of 2 bytes32 each + // Total slots: 8 (slots 12-19) + bytes32[2][4] public data2D; + + function setMatrix(uint256[3] memory row0, uint256[3] memory row1) public { + matrix[0] = row0; + matrix[1] = row1; + } + + function setMatrixElement(uint256 i, uint256 j, uint256 value) public { + matrix[i][j] = value; + } + + function setAddresses2D(address[2] memory row0, address[2] memory row1, address[2] memory row2) public { + addresses2D[0] = row0; + addresses2D[1] = row1; + addresses2D[2] = row2; + } + + function setData2D(uint256 i, uint256 j, bytes32 value) public { + data2D[i][j] = value; + } +} + +contract StateDiffStorageLayoutTest is Test { + SimpleStorage simpleStorage; + VariousArrays variousArrays; + TwoDArrayStorage twoDArrayStorage; + + function setUp() public { + simpleStorage = new SimpleStorage(); + variousArrays = new VariousArrays(); + twoDArrayStorage = new TwoDArrayStorage(); + } + + function testSimpleStorageLayout() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Modify storage slots with known positions + simpleStorage.setValue(42); // Modifies slot 0 (value) + simpleStorage.setOwner(address(this)); // Modifies slot 1 (owner) + simpleStorage.setValues(100, 200, 300); // Modifies slots 2, 3, 4 (values array) + + // Get the state diff as string + string memory stateDiff = vm.getStateDiff(); + + // Get the state diff as JSON and verify it contains the expected structure + string memory stateDiffJson = vm.getStateDiffJson(); + + // The JSON should contain storage layout info for all slots + // We check the JSON contains expected substrings for the labels and types + assertTrue(vm.contains(stateDiffJson, '"label":"value"')); + assertTrue(vm.contains(stateDiffJson, '"label":"owner"')); + assertTrue(vm.contains(stateDiffJson, '"label":"values[0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"values[1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"values[2]"')); + + assertTrue(vm.contains(stateDiffJson, '"type":"uint256"')); + assertTrue(vm.contains(stateDiffJson, '"type":"address"')); + assertTrue(vm.contains(stateDiffJson, '"type":"uint256[3]"')); + + // Check for decoded values + assertTrue(vm.contains(stateDiffJson, '"decoded":')); + + // Check specific decoded values within the decoded object + // The value 42 should be decoded in the first slot + assertTrue(vm.contains(stateDiffJson, '"decoded":{"previousValue":"0","newValue":"42"}')); + + // Check that array values are decoded properly (they will have separate decoded objects) + assertTrue(vm.contains(stateDiffJson, '"newValue":"100"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"200"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"300"')); + + // Stop recording and verify we get the expected account accesses + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + assertTrue(accesses.length >= 3); + + // Verify storage accesses for SimpleStorage + bool foundValueSlot = false; + bool foundOwnerSlot = false; + bool foundValuesSlot0 = false; + bool foundValuesSlot1 = false; + bool foundValuesSlot2 = false; + + for (uint256 i = 0; i < accesses.length; i++) { + if (accesses[i].account == address(simpleStorage)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + bytes32 slot = accesses[i].storageAccesses[j].slot; + if (slot == bytes32(uint256(0))) foundValueSlot = true; + if (slot == bytes32(uint256(1))) foundOwnerSlot = true; + if (slot == bytes32(uint256(2))) foundValuesSlot0 = true; + if (slot == bytes32(uint256(3))) foundValuesSlot1 = true; + if (slot == bytes32(uint256(4))) foundValuesSlot2 = true; + } + } + } + + assertTrue(foundValueSlot); + assertTrue(foundOwnerSlot); + assertTrue(foundValuesSlot0); + assertTrue(foundValuesSlot1); + assertTrue(foundValuesSlot2); + } + + function testVariousArrayTypes() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Modify different array types + variousArrays.setNumbers(100, 200, 300); + variousArrays.setAddresses(address(0x1), address(0x2)); + variousArrays.setFlags(true, false, true, false, true); + variousArrays.setHashes(keccak256("test1"), keccak256("test2")); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + + // Verify all array types are properly labeled with indices + assertTrue(vm.contains(stateDiffJson, '"label":"numbers[0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"numbers[1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"numbers[2]"')); + + assertTrue(vm.contains(stateDiffJson, '"label":"addresses[0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"addresses[1]"')); + + assertTrue(vm.contains(stateDiffJson, '"label":"flags[0]"')); + + assertTrue(vm.contains(stateDiffJson, '"label":"hashes[0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"hashes[1]"')); + + // Verify types are correctly identified + assertTrue(vm.contains(stateDiffJson, '"type":"uint256[3]"')); + assertTrue(vm.contains(stateDiffJson, '"type":"address[2]"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bool[5]"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes32[2]"')); + + // Check decoded values + assertTrue(vm.contains(stateDiffJson, '"decoded":')); + // Check addresses are decoded as raw hex strings + assertTrue(vm.contains(stateDiffJson, '"newValue":"0x0000000000000000000000000000000000000001"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"0x0000000000000000000000000000000000000002"')); + + // Stop recording and verify account accesses + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + assertTrue(accesses.length > 0); + } + + function testStateDiffJsonFormat() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Make a simple change to verify JSON format + simpleStorage.setValue(123); + + // Get the JSON and verify it's properly formatted + string memory stateDiffJson = vm.getStateDiffJson(); + + // Check JSON structure contains expected fields + assertTrue(vm.contains(stateDiffJson, '"previousValue":')); + assertTrue(vm.contains(stateDiffJson, '"newValue":')); + assertTrue(vm.contains(stateDiffJson, '"label":')); + assertTrue(vm.contains(stateDiffJson, '"type":')); + assertTrue(vm.contains(stateDiffJson, '"offset":')); + assertTrue(vm.contains(stateDiffJson, '"slot":')); + + vm.stopAndReturnStateDiff(); + } + + function test2DArrayStorageLayout() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set matrix values + // matrix[0][0] = 100, matrix[0][1] = 101, matrix[0][2] = 102 + // matrix[1][0] = 200, matrix[1][1] = 201, matrix[1][2] = 202 + uint256[3] memory row0 = [uint256(100), 101, 102]; + uint256[3] memory row1 = [uint256(200), 201, 202]; + twoDArrayStorage.setMatrix(row0, row1); + + // Get the state diff and check labels + string memory stateDiffJson = vm.getStateDiffJson(); + + // Verify the labels for 2D array elements + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[0][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[0][1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[0][2]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[1][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[1][1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"matrix[1][2]"')); + + // Check that we have the right type + assertTrue(vm.contains(stateDiffJson, '"type":"uint256[3][2]"')); + + // Check decoded values for 2D arrays + assertTrue(vm.contains(stateDiffJson, '"decoded":')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"100"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"101"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"102"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"200"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"201"')); + assertTrue(vm.contains(stateDiffJson, '"newValue":"202"')); + + vm.stopAndReturnStateDiff(); + } + + function testMixed2DArrays() public { + vm.startStateDiffRecording(); + + // Test address 2D array + address[2] memory addrRow0 = [address(0x1), address(0x2)]; + address[2] memory addrRow1 = [address(0x3), address(0x4)]; + address[2] memory addrRow2 = [address(0x5), address(0x6)]; + twoDArrayStorage.setAddresses2D(addrRow0, addrRow1, addrRow2); + + // Test bytes32 2D array + twoDArrayStorage.setData2D(0, 0, keccak256("data00")); + twoDArrayStorage.setData2D(0, 1, keccak256("data01")); + twoDArrayStorage.setData2D(1, 0, keccak256("data10")); + twoDArrayStorage.setData2D(1, 1, keccak256("data11")); + + string memory stateDiffJson = vm.getStateDiffJson(); + + // Check for proper types + assertTrue(vm.contains(stateDiffJson, '"type":"address[2][3]"')); + assertTrue(vm.contains(stateDiffJson, '"type":"bytes32[2][4]"')); + + // Verify address 2D array labels + assertTrue(vm.contains(stateDiffJson, '"label":"addresses2D[0][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"addresses2D[0][1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"addresses2D[1][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"addresses2D[2][1]"')); + + // Verify data 2D array labels + assertTrue(vm.contains(stateDiffJson, '"label":"data2D[0][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"data2D[0][1]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"data2D[1][0]"')); + assertTrue(vm.contains(stateDiffJson, '"label":"data2D[1][1]"')); + + vm.stopAndReturnStateDiff(); + } + + function testStateDiffDecodedValues() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Make changes to create state diffs with decoded values + simpleStorage.setValue(42); // uint256 value + simpleStorage.setOwner(address(0xBEEF)); // address value + simpleStorage.setValues(100, 200, 300); // array values + + // Get the state diff as string (not JSON) + string memory stateDiff = vm.getStateDiff(); + + // Test that decoded values are shown in the string format + // The output uses Unicode arrow → + // For uint256 values, should show decoded value "42" + assertTrue(vm.contains(stateDiff, unicode"0 → 42")); + + // For addresses, should show decoded address format + assertTrue(vm.contains(stateDiff, "0x000000000000000000000000000000000000bEEF")); + + // For array elements, should show decoded values + assertTrue(vm.contains(stateDiff, unicode"0 → 100")); + assertTrue(vm.contains(stateDiff, unicode"0 → 200")); + assertTrue(vm.contains(stateDiff, unicode"0 → 300")); + + vm.stopAndReturnStateDiff(); + } +} diff --git a/testdata/default/cheats/StateDiffStructTest.t.sol b/testdata/default/cheats/StateDiffStructTest.t.sol new file mode 100644 index 0000000000000..54e5403411dd4 --- /dev/null +++ b/testdata/default/cheats/StateDiffStructTest.t.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "utils/Test.sol"; + +contract DiffTest { + // slot 0 + struct TestStruct { + uint128 a; + uint128 b; + } + + // Multi-slot struct (spans 3 slots) + struct MultiSlotStruct { + uint256 value1; // slot 1 + address addr; // slot 2 (takes 20 bytes, but uses full slot) + uint256 value2; // slot 3 + } + + // Nested struct with MultiSlotStruct as inner + struct NestedStruct { + MultiSlotStruct inner; // slots 4-6 (spans 3 slots) + uint256 value; // slot 7 + address owner; // slot 8 + } + + TestStruct internal testStruct; + MultiSlotStruct internal multiSlotStruct; + NestedStruct internal nestedStruct; + + function setStruct(uint128 a, uint128 b) public { + testStruct.a = a; + testStruct.b = b; + } + + function setMultiSlotStruct(uint256 v1, address a, uint256 v2) public { + multiSlotStruct.value1 = v1; + multiSlotStruct.addr = a; + multiSlotStruct.value2 = v2; + } + + function setNestedStruct(uint256 v1, address a, uint256 v2, uint256 v, address o) public { + nestedStruct.inner.value1 = v1; + nestedStruct.inner.addr = a; + nestedStruct.inner.value2 = v2; + nestedStruct.value = v; + nestedStruct.owner = o; + } +} + +contract StateDiffStructTest is Test { + DiffTest internal test; + + function setUp() public { + test = new DiffTest(); + } + + function testStruct() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set struct values: a=1, b=2 + test.setStruct(1, 2); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + + // Debug: log the JSON for inspection + emit log_string("State diff JSON (testdata):"); + emit log_string(stateDiffJson); + + // Check that the struct is properly labeled + assertTrue(vm.contains(stateDiffJson, '"label":"testStruct"')); + + // Check that the type is correctly identified as a struct + assertTrue(vm.contains(stateDiffJson, '"type":"struct DiffTest.TestStruct"')); + + // Check for members field - structs have members with individual decoded values + assertTrue(vm.contains(stateDiffJson, '"members":')); + + // Check that member 'a' is properly decoded + assertTrue(vm.contains(stateDiffJson, '"label":"a"')); + assertTrue(vm.contains(stateDiffJson, '"type":"uint128"')); + + // Check that member 'b' is properly decoded + assertTrue(vm.contains(stateDiffJson, '"label":"b"')); + + // The members should have decoded values + // Check specific decoded values for each member in the members array + // Member 'a' at offset 0 should have previous value 0 and new value 1 + assertTrue( + vm.contains( + stateDiffJson, + '{"label":"a","type":"uint128","offset":0,"slot":"0","decoded":{"previousValue":"0","newValue":"1"}}' + ) + ); + + // Member 'b' at offset 16 should have previous value 0 and new value 2 + assertTrue( + vm.contains( + stateDiffJson, + '{"label":"b","type":"uint128","offset":16,"slot":"0","decoded":{"previousValue":"0","newValue":"2"}}' + ) + ); + + // Verify the raw storage values are correct + // The storage layout packs uint128 a at offset 0 and uint128 b at offset 16 + // So the value 0x0000000000000000000200000000000000000000000000000001 represents: + // - First 16 bytes (a): 0x0000000000000000000000000000000001 = 1 + // - Last 16 bytes (b): 0x0000000000000000000000000000000002 = 2 + assertTrue(vm.contains(stateDiffJson, '"0x0000000000000000000000000000000200000000000000000000000000000001"')); + + // Stop recording and verify we get the expected account accesses + Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + assertTrue(accesses.length > 0); + + // Find the storage access for our struct + bool foundStructAccess = false; + for (uint256 i = 0; i < accesses.length; i++) { + if (accesses[i].account == address(test)) { + for (uint256 j = 0; j < accesses[i].storageAccesses.length; j++) { + Vm.StorageAccess memory access = accesses[i].storageAccesses[j]; + if (access.slot == bytes32(uint256(0)) && access.isWrite) { + foundStructAccess = true; + // Verify the storage values + assertEq(access.previousValue, bytes32(uint256(0))); + assertEq( + access.newValue, bytes32(uint256(0x0000000000000000000200000000000000000000000000000001)) + ); + } + } + } + } + + assertTrue(foundStructAccess); + } + + function testMultiSlotStruct() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set multi-slot struct values + test.setMultiSlotStruct( + 123456789, // value1 + address(0xdEaDbEeF), // addr + 987654321 // value2 + ); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + + // Debug: log the JSON for inspection + emit log_string("State diff JSON:"); + emit log_string(stateDiffJson); + + // Check that the struct's first member is properly labeled + assertTrue(vm.contains(stateDiffJson, '"label":"multiSlotStruct.value1"')); + + // For multi-slot structs, the base slot now shows the first member's type + // The struct type itself is not shown since we decode the first member directly + + // Multi-slot structs don't have members field in the base slot + // Instead, each member appears as a separate slot entry with dotted labels + + // Check that each member slot is properly labeled + // Note: slot 1 now shows multiSlotStruct.value1 since it's the first member + assertTrue(vm.contains(stateDiffJson, '"label":"multiSlotStruct.value1"')); + assertTrue(vm.contains(stateDiffJson, '"label":"multiSlotStruct.addr"')); + assertTrue(vm.contains(stateDiffJson, '"label":"multiSlotStruct.value2"')); + + // Check member types + assertTrue(vm.contains(stateDiffJson, '"type":"uint256"')); + assertTrue(vm.contains(stateDiffJson, '"type":"address"')); + + // Check that value1 is properly decoded from slot 1 + assertTrue(vm.contains(stateDiffJson, '"decoded":{"previousValue":"0","newValue":"123456789"}')); + + // Also verify the raw hex value + assertTrue(vm.contains(stateDiffJson, "0x00000000000000000000000000000000000000000000000000000000075bcd15")); + + // Slot 2 should have the address decoded + assertTrue( + vm.contains( + stateDiffJson, + '"decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x00000000000000000000000000000000DeaDBeef"}' + ) + ); + + // Slot 3 should have value2 decoded + assertTrue(vm.contains(stateDiffJson, '"decoded":{"previousValue":"0","newValue":"987654321"}')); + + // Stop recording + vm.stopAndReturnStateDiff(); + } + + function testNestedStruct() public { + // Start recording state diffs + vm.startStateDiffRecording(); + + // Set nested struct values - now with MultiSlotStruct as inner + test.setNestedStruct( + 111111111, // inner.value1 + address(0xCAFE), // inner.addr + 222222222, // inner.value2 + 333333333, // value + address(0xBEEF) // owner + ); + + // Get the state diff as JSON + string memory stateDiffJson = vm.getStateDiffJson(); + + // Debug: log the JSON for inspection + emit log_string("State diff JSON (testdata):"); + emit log_string(stateDiffJson); + + assertTrue( + vm.contains( + stateDiffJson, + '"label":"nestedStruct.inner.value1","type":"uint256","offset":0,"slot":"4","decoded":{"previousValue":"0","newValue":"111111111"}' + ) + ); + + assertTrue( + vm.contains( + stateDiffJson, + '"label":"nestedStruct.inner.addr","type":"address","offset":0,"slot":"5","decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000cafE"}' + ) + ); + + assertTrue( + vm.contains( + stateDiffJson, + '"label":"nestedStruct.inner.value2","type":"uint256","offset":0,"slot":"6","decoded":{"previousValue":"0","newValue":"222222222"}' + ) + ); + + assertTrue(vm.contains(stateDiffJson, "0x00000000000000000000000000000000000000000000000000000000069f6bc7")); + + assertTrue( + vm.contains( + stateDiffJson, + '"label":"nestedStruct.value","type":"uint256","offset":0,"slot":"7","decoded":{"previousValue":"0","newValue":"333333333"}' + ) + ); + + assertTrue( + vm.contains( + stateDiffJson, + '"label":"nestedStruct.owner","type":"address","offset":0,"slot":"8","decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000bEEF"}' + ) + ); + + // Stop recording + vm.stopAndReturnStateDiff(); + } +} diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 8751a04094129..4a72639ce8c8d 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct Storage { uint256 slot0; uint256 slot1; } -contract StateSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract StateSnapshotTest is Test { Storage store; function setUp() public { @@ -105,9 +102,7 @@ contract StateSnapshotTest is DSTest { } // TODO: remove this test suite once `snapshot*` has been deprecated in favor of `snapshotState*`. -contract DeprecatedStateSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DeprecatedStateSnapshotTest is Test { Storage store; function setUp() public { diff --git a/testdata/default/cheats/StorageSlotState.t.sol b/testdata/default/cheats/StorageSlotState.t.sol index 7c2971f22a14f..6c80900038558 100644 --- a/testdata/default/cheats/StorageSlotState.t.sol +++ b/testdata/default/cheats/StorageSlotState.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract StorageSlotStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract StorageSlotStateTest is Test { function test_gas_two_reads() public { Read read = new Read(); read.number(); diff --git a/testdata/default/cheats/Store.t.sol b/testdata/default/cheats/Store.t.sol index 9a1ce6101c1b0..e096dc3f50341 100644 --- a/testdata/default/cheats/Store.t.sol +++ b/testdata/default/cheats/Store.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Storage { uint256 public slot0 = 10; uint256 public slot1 = 20; } -contract StoreTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract StoreTest is Test { Storage store; function setUp() public { diff --git a/testdata/default/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol index 256d65302a445..fbe8718d99cc6 100644 --- a/testdata/default/cheats/StringUtils.t.sol +++ b/testdata/default/cheats/StringUtils.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract StringManipulationTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract StringManipulationTest is Test { function testToLowercase() public { string memory original = "Hello World"; string memory lowercased = vm.toLowercase(original); diff --git a/testdata/default/cheats/ToString.t.sol b/testdata/default/cheats/ToString.t.sol index f19110e3e8655..3fda38c4e5f1b 100644 --- a/testdata/default/cheats/ToString.t.sol +++ b/testdata/default/cheats/ToString.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ToStringTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ToStringTest is Test { function testAddressToString() public { address testAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; string memory stringAddress = vm.toString(testAddress); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol index 5f0ef5b43c09e..613ec7c4046fb 100644 --- a/testdata/default/cheats/Toml.t.sol +++ b/testdata/default/cheats/Toml.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; library TomlStructs { address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); @@ -58,7 +56,7 @@ library TomlStructs { } } -contract ParseTomlTest is DSTest { +contract ParseTomlTest is Test { using TomlStructs for *; struct FlatToml { @@ -80,7 +78,6 @@ contract ParseTomlTest is DSTest { string name; } - Vm constant vm = Vm(HEVM_ADDRESS); string toml; function setUp() public { @@ -329,11 +326,27 @@ contract ParseTomlTest is DSTest { assertEq(keccak256(abi.encode(members)), keccak256(abi.encode(data.members))); } -} -contract WriteTomlTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); + function test_floatNaN() public { + bytes memory data = vm.parseToml(toml, ".nanFloat"); + string memory decodedData = abi.decode(data, (string)); + assertEq("NaN", decodedData); + } + + function test_floatInf() public { + bytes memory data = vm.parseToml(toml, ".infFloat"); + string memory decodedData = abi.decode(data, (string)); + assertEq("inf", decodedData); + } + + function test_floatNegInf() public { + bytes memory data = vm.parseToml(toml, ".neginfFloat"); + string memory decodedData = abi.decode(data, (string)); + assertEq("-inf", decodedData); + } +} +contract WriteTomlTest is Test { string json1; string json2; @@ -421,4 +434,28 @@ contract WriteTomlTest is DSTest { address decodedAddress = abi.decode(data, (address)); assertEq(decodedAddress, ex); } + + function test_writeToml_createKeys() public { + string memory path = "fixtures/Toml/write_test.toml"; + string memory toml = vm.readFile(path); + + bool exists = vm.keyExistsToml(toml, ".parent"); + assertTrue(!exists); + exists = vm.keyExistsToml(toml, ".parent.child"); + assertTrue(!exists); + exists = vm.keyExistsToml(toml, ".parent.child.value"); + assertTrue(!exists); + + // Write to nested path, creating intermediate keys + vm.writeToml(vm.toString(uint256(42)), path, ".parent.child.value"); + + // Verify the value was written and intermediate keys were created + toml = vm.readFile(path); + uint256 value = abi.decode(vm.parseToml(toml, ".parent.child.value"), (uint256)); + assertEq(value, 42); + + // Clean up the test file by removing the parent key we added + vm.removeFile(path); + vm.writeToml("{\"a\": 123, \"b\": \"0x000000000000000000000000000000000000bEEF\"}", path); + } } diff --git a/testdata/default/cheats/Travel.t.sol b/testdata/default/cheats/Travel.t.sol index b46d2e7ad7041..4937b679dc413 100644 --- a/testdata/default/cheats/Travel.t.sol +++ b/testdata/default/cheats/Travel.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ChainIdTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ChainIdTest is Test { function testChainId() public { vm.chainId(10); assertEq(block.chainid, 10, "chainId switch failed"); diff --git a/testdata/default/cheats/TryFfi.sol b/testdata/default/cheats/TryFfi.sol index 58d93a48b4fea..916377b6cc590 100644 --- a/testdata/default/cheats/TryFfi.sol +++ b/testdata/default/cheats/TryFfi.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract TryFfiTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract TryFfiTest is Test { function testTryFfi() public { string[] memory inputs = new string[](3); inputs[0] = "bash"; diff --git a/testdata/default/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol index 29d86699f64d1..6d11cdb3da02f 100644 --- a/testdata/default/cheats/UnixTime.t.sol +++ b/testdata/default/cheats/UnixTime.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract UnixTimeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract UnixTimeTest is Test { // This is really wide because CI sucks. uint256 constant errMargin = 1000; diff --git a/testdata/default/cheats/Wallet.t.sol b/testdata/default/cheats/Wallet.t.sol index d061b55ae45c5..e25ed4a9d071e 100644 --- a/testdata/default/cheats/Wallet.t.sol +++ b/testdata/default/cheats/Wallet.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Foo {} -contract WalletTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract WalletTest is Test { uint256 internal constant Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 private constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; diff --git a/testdata/default/cheats/Warp.t.sol b/testdata/default/cheats/Warp.t.sol index 7ba53f6e5eda4..f3512944d6797 100644 --- a/testdata/default/cheats/Warp.t.sol +++ b/testdata/default/cheats/Warp.t.sol @@ -1,26 +1,23 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract WarpTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract WarpTest is Test { function testWarp() public { vm.warp(10); - assertEq(block.timestamp, 10, "warp failed"); + assertEq(vm.getBlockTimestamp(), 10, "warp failed"); } function testWarpFuzzed(uint32 jump) public { - uint256 pre = block.timestamp; - vm.warp(block.timestamp + jump); - assertEq(block.timestamp, pre + jump, "warp failed"); + uint256 pre = vm.getBlockTimestamp(); + vm.warp(vm.getBlockTimestamp() + jump); + assertEq(vm.getBlockTimestamp(), pre + jump, "warp failed"); } function testWarp2() public { - assertEq(block.timestamp, 1); + assertEq(vm.getBlockTimestamp(), 1); vm.warp(100); - assertEq(block.timestamp, 100); + assertEq(vm.getBlockTimestamp(), 100); } } diff --git a/testdata/default/cheats/dumpState.t.sol b/testdata/default/cheats/dumpState.t.sol index 8a8675ca5eace..887de131e5888 100644 --- a/testdata/default/cheats/dumpState.t.sol +++ b/testdata/default/cheats/dumpState.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleContract { constructor() { @@ -12,9 +11,7 @@ contract SimpleContract { } } -contract DumpStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract DumpStateTest is Test { function testDumpStateCheatAccount() public { // Path to temporary file that is deleted after the test string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_cheat.json"); diff --git a/testdata/default/cheats/getBlockNumber.t.sol b/testdata/default/cheats/getBlockNumber.t.sol index 18e2a163f3b86..491a726781b99 100644 --- a/testdata/default/cheats/getBlockNumber.t.sol +++ b/testdata/default/cheats/getBlockNumber.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GetBlockNumberTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GetBlockNumberTest is Test { function testGetBlockNumber() public { uint256 height = vm.getBlockNumber(); assertEq(height, uint256(block.number), "height should be equal to block.number"); diff --git a/testdata/default/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol index 94ce6804c1260..fa660e75fb775 100644 --- a/testdata/default/cheats/loadAllocs.t.sol +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract LoadAllocsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract LoadAllocsTest is Test { address constant ALLOCD = address(0x420); address constant ALLOCD_B = address(0x421); diff --git a/testdata/default/core/BadSigAfterInvariant.t.sol b/testdata/default/core/BadSigAfterInvariant.t.sol index 7b485e24f4a04..1e2ce2f9a9448 100644 --- a/testdata/default/core/BadSigAfterInvariant.t.sol +++ b/testdata/default/core/BadSigAfterInvariant.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract BadSigAfterInvariant is DSTest { +contract BadSigAfterInvariant is Test { function afterinvariant() public {} function testShouldPassWithWarning() public { diff --git a/testdata/default/core/ContractEnvironment.t.sol b/testdata/default/core/ContractEnvironment.t.sol index 452fa88022557..ddb03b41a2556 100644 --- a/testdata/default/core/ContractEnvironment.t.sol +++ b/testdata/default/core/ContractEnvironment.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract ContractEnvironmentTest is DSTest { +contract ContractEnvironmentTest is Test { function chainId() internal view returns (uint256 id) { assembly { id := chainid() diff --git a/testdata/default/core/FailingTestAfterFailedSetup.t.sol b/testdata/default/core/FailingTestAfterFailedSetup.t.sol deleted file mode 100644 index c56f4ba5de605..0000000000000 --- a/testdata/default/core/FailingTestAfterFailedSetup.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract FailingTestAfterFailedSetupTest is DSTest { - function setUp() public { - assertTrue(false); - } - - function testAssertSuccess() public { - assertTrue(true); - } - - function testAssertFailure() public { - assertTrue(false); - } -} diff --git a/testdata/default/core/LegacyAssertions.t.sol b/testdata/default/core/LegacyAssertions.t.sol deleted file mode 100644 index c35a63417efc3..0000000000000 --- a/testdata/default/core/LegacyAssertions.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract NoAssertionsRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testMultipleAssertFailures() public { - vm.assertEq(uint256(1), uint256(2)); - vm.assertLt(uint256(5), uint256(4)); - } -} - -contract LegacyAssertionsTest { - bool public failed; - - function testFlagNotSetSuccess() public {} - - function testFlagSetFailure() public { - failed = true; - } -} diff --git a/testdata/default/core/PaymentFailure.t.sol b/testdata/default/core/PaymentFailure.t.sol deleted file mode 100644 index 52c42fd376052..0000000000000 --- a/testdata/default/core/PaymentFailure.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Payable { - function pay() public payable {} -} - -contract PaymentFailureTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testCantPay() public { - Payable target = new Payable(); - vm.prank(address(1)); - target.pay{value: 1}(); - } -} diff --git a/testdata/default/core/Reverting.t.sol b/testdata/default/core/Reverting.t.sol index 73877cab0b542..699b2bf1123d3 100644 --- a/testdata/default/core/Reverting.t.sol +++ b/testdata/default/core/Reverting.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RevertingTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract RevertingTest is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevert() public { vm.expectRevert("should revert here"); diff --git a/testdata/default/core/SetupConsistency.t.sol b/testdata/default/core/SetupConsistency.t.sol index 08d766f0f9242..bf5a2e31935c1 100644 --- a/testdata/default/core/SetupConsistency.t.sol +++ b/testdata/default/core/SetupConsistency.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract SetupConsistencyCheck is DSTest { +contract SetupConsistencyCheck is Test { uint256 two; uint256 four; uint256 result; diff --git a/testdata/default/fork/DssExecLib.sol b/testdata/default/fork/DssExecLib.sol index 41becd090e658..5632d029d94fe 100644 --- a/testdata/default/fork/DssExecLib.sol +++ b/testdata/default/fork/DssExecLib.sol @@ -1356,8 +1356,8 @@ library DssExecLib { uint256 _end, uint256 _duration ) public returns (address) { - address lerp = - LerpFactoryLike(lerpFab()).newIlkLerp(_name, _target, _ilk, _what, _startTime, _start, _end, _duration); + address lerp = LerpFactoryLike(lerpFab()) + .newIlkLerp(_name, _target, _ilk, _what, _startTime, _start, _end, _duration); Authorizable(_target).rely(lerp); LerpLike(lerp).tick(); return lerp; diff --git a/testdata/default/fork/ForkSame_1.t.sol b/testdata/default/fork/ForkSame_1.t.sol index 949c7ea9ec17d..f8c0234f5baa6 100644 --- a/testdata/default/fork/ForkSame_1.t.sol +++ b/testdata/default/fork/ForkSame_1.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - Vm constant vm = Vm(HEVM_ADDRESS); uint256 forkA; // this will create two _different_ forks during setup diff --git a/testdata/default/fork/ForkSame_2.t.sol b/testdata/default/fork/ForkSame_2.t.sol index 949c7ea9ec17d..f8c0234f5baa6 100644 --- a/testdata/default/fork/ForkSame_2.t.sol +++ b/testdata/default/fork/ForkSame_2.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - Vm constant vm = Vm(HEVM_ADDRESS); uint256 forkA; // this will create two _different_ forks during setup diff --git a/testdata/default/fork/LaunchFork.t.sol b/testdata/default/fork/LaunchFork.t.sol index 710ac97f51d97..8784b84b8e7e1 100644 --- a/testdata/default/fork/LaunchFork.t.sol +++ b/testdata/default/fork/LaunchFork.t.sol @@ -1,14 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.6.12; -import "ds-test/test.sol"; +import "utils/Test.sol"; import "./DssExecLib.sol"; -interface Vm { - function store(address account, bytes32 slot, bytes32 value) external; - function activeFork() external returns (uint256); -} - interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); @@ -23,14 +18,13 @@ contract DummyContract { } } -contract ForkTest is DSTest { +abstract contract ForkTest is Test { address constant DAI_TOKEN_ADDR = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // checks that we can retrieve the fork we launched with function testActiveFork() public { - Vm cheatvm = Vm(HEVM_ADDRESS); - uint256 activeFork = cheatvm.activeFork(); + uint256 activeFork = vm.activeFork(); // launch fork has id `0` assertEq(activeFork, 0); } @@ -52,13 +46,12 @@ contract ForkTest is DSTest { } function testCheatcode() public { - Vm cheatvm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); bytes32 value = bytes32(uint256(1)); // "0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff" is the slot storing the balance of zero address for the weth contract // `cast index address uint 0x0000000000000000000000000000000000000000 3` bytes32 zero_address_balance_slot = 0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff; - cheatvm.store(WETH_TOKEN_ADDR, zero_address_balance_slot, value); + vm.store(WETH_TOKEN_ADDR, zero_address_balance_slot, value); assertEq( WETH.balanceOf(0x0000000000000000000000000000000000000000), 1, @@ -77,3 +70,9 @@ contract ForkTest is DSTest { assertEq(WETH.balanceOf(address(this)) - current, 1000, "WETH balance is not equal to deposited amount."); } } + +contract ForkTestHttp is ForkTest { + function setUp() public { + vm.createSelectFork("mainnet"); + } +} diff --git a/testdata/default/fs/Disabled.t.sol b/testdata/default/fs/Disabled.t.sol index 36f05c211fcad..19fb755c4f425 100644 --- a/testdata/default/fs/Disabled.t.sol +++ b/testdata/default/fs/Disabled.t.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract DisabledTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +// No permissions: all FS operations should revert. +/// forge-config: default.fs_permissions = [] +contract DisabledAccessTest is Test { function testReadFile() public { string memory path = "fixtures/File/read.txt"; vm._expectCheatcodeRevert(); vm.readFile(path); + + vm._expectCheatcodeRevert(); + vm.readFileBinary(path); } function testReadLine() public { @@ -20,21 +23,27 @@ contract DisabledTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); vm.writeFile(path, data); + + vm._expectCheatcodeRevert(); + vm.writeFileBinary(path, bytes(data)); } function testWriteLine() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); vm.writeLine(path, data); } function testRemoveFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; + vm._expectCheatcodeRevert(); vm.removeFile(path); } diff --git a/testdata/default/fs/Default.t.sol b/testdata/default/fs/ReadOnly.sol similarity index 68% rename from testdata/default/fs/Default.t.sol rename to testdata/default/fs/ReadOnly.sol index e1524963f6c3e..a738ae20e61c4 100644 --- a/testdata/default/fs/Default.t.sol +++ b/testdata/default/fs/ReadOnly.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract DefaultAccessTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +// Default permissions: only read FS operations should succeed. +/// forge-config: default.fs_permissions = [{ access = "read", path = "./fixtures"}] +contract ReadOnlyAccessTest is Test { function testReadFile() public { string memory path = "fixtures/File/read.txt"; vm.readFile(path); @@ -20,7 +20,7 @@ contract DefaultAccessTest is DSTest { } function testWriteFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm._expectCheatcodeRevert(); @@ -31,7 +31,7 @@ contract DefaultAccessTest is DSTest { } function testWriteLine() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; string memory data = "hello writable world"; vm._expectCheatcodeRevert(); @@ -39,7 +39,7 @@ contract DefaultAccessTest is DSTest { } function testRemoveFile() public { - string memory path = "fixtures/File/write_file.txt"; + string memory path = "fixtures/File/ignored/write_file.txt"; vm._expectCheatcodeRevert(); vm.removeFile(path); diff --git a/testdata/default/fuzz/Fuzz.t.sol b/testdata/default/fuzz/Fuzz.t.sol deleted file mode 100644 index b1cf54716be93..0000000000000 --- a/testdata/default/fuzz/Fuzz.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FuzzTest is DSTest { - constructor() { - emit log("constructor"); - } - - Vm constant vm = Vm(HEVM_ADDRESS); - - function setUp() public { - emit log("setUp"); - } - - function testShouldFailFuzz(uint8 x) public { - emit log("testFailFuzz"); - require(x > 128, "should revert"); - } - - function testSuccessfulFuzz(uint128 a, uint128 b) public { - emit log("testSuccessfulFuzz"); - assertEq(uint256(a) + uint256(b), uint256(a) + uint256(b)); - } - - function testToStringFuzz(bytes32 data) public { - vm.toString(data); - } -} diff --git a/testdata/default/fuzz/FuzzCollection.t.sol b/testdata/default/fuzz/FuzzCollection.t.sol deleted file mode 100644 index 0c98ddc66b6b2..0000000000000 --- a/testdata/default/fuzz/FuzzCollection.t.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract SampleContract { - uint256 public counter; - uint256 public counterX2; - address public owner = address(0xBEEF); - bool public found_needle; - - event Incremented(uint256 counter); - - modifier onlyOwner() { - require(msg.sender == owner, "ONLY_OWNER"); - _; - } - - function compare(uint256 val) public { - if (val == 0x4446) { - found_needle = true; - } - } - - function incrementBy(uint256 numToIncrement) public onlyOwner { - counter += numToIncrement; - counterX2 += numToIncrement * 2; - - emit Incremented(counter); - } - - function breakTheInvariant(uint256 x) public { - if (x == 0x5556) { - counterX2 = 0; - } - } -} - -interface Vm { - function startPrank(address) external; - function expectRevert(bytes calldata msg) external; -} - -contract SampleContractTest is DSTest { - Vm hevm = Vm(HEVM_ADDRESS); - - event Incremented(uint256 counter); - - SampleContract public sample; - - function setUp() public { - sample = new SampleContract(); - } - - function testIncrement(address caller) public { - hevm.startPrank(address(caller)); - - hevm.expectRevert("ONLY_OWNER"); - sample.incrementBy(1); - } - - function testNeedle(uint256 needle) public { - sample.compare(needle); - require(!sample.found_needle(), "needle found."); - } - - function invariantCounter() public { - require(sample.counter() * 2 == sample.counterX2(), "broken counter."); - } -} diff --git a/testdata/default/fuzz/FuzzFailurePersist.t.sol b/testdata/default/fuzz/FuzzFailurePersist.t.sol deleted file mode 100644 index 3787060411e5d..0000000000000 --- a/testdata/default/fuzz/FuzzFailurePersist.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct TestTuple { - address user; - uint256 amount; -} - -contract FuzzFailurePersistTest is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - - function test_persist_fuzzed_failure( - uint256 x, - int256 y, - address addr, - bool cond, - string calldata test, - TestTuple calldata tuple, - address[] calldata addresses - ) public { - // dummy assume to trigger runs - vm.assume(x > 1 && x < 1111111111111111111111111111); - vm.assume(y > 1 && y < 1111111111111111111111111111); - require(false); - } -} diff --git a/testdata/default/fuzz/FuzzInt.t.sol b/testdata/default/fuzz/FuzzInt.t.sol deleted file mode 100644 index a47ff2953331f..0000000000000 --- a/testdata/default/fuzz/FuzzInt.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 -// random values (instead edge cases) are generated if no fixtures defined -contract FuzzNumbersTest is DSTest { - function testPositive(int256) public { - assertTrue(true); - } - - function testNegativeHalf(int256 val) public { - assertTrue(val < 2 ** 128 - 1); - } - - function testNegative0(int256 val) public { - assertTrue(val == 0); - } - - function testNegative1(int256 val) public { - assertTrue(val == -1); - } - - function testNegative2(int128 val) public { - assertTrue(val == 1); - } - - function testNegativeMax0(int256 val) public { - assertTrue(val == type(int256).max); - } - - function testNegativeMax1(int256 val) public { - assertTrue(val == type(int256).max - 2); - } - - function testNegativeMin0(int256 val) public { - assertTrue(val == type(int256).min); - } - - function testNegativeMin1(int256 val) public { - assertTrue(val == type(int256).min + 2); - } - - function testEquality(int256 x, int256 y) public { - int256 xy; - - unchecked { - xy = x * y; - } - - if ((x != 0 && xy / x != y)) { - return; - } - - assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); - } -} diff --git a/testdata/default/fuzz/FuzzPositive.t.sol b/testdata/default/fuzz/FuzzPositive.t.sol deleted file mode 100644 index 7d3639dfe5ec2..0000000000000 --- a/testdata/default/fuzz/FuzzPositive.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract FuzzPositive is DSTest { - function testSuccessChecker(uint256 val) public { - assertTrue(true); - } - - function testSuccessChecker2(int256 val) public { - assert(val == val); - } - - function testSuccessChecker3(uint32 val) public { - assert(val + 0 == val); - } -} diff --git a/testdata/default/fuzz/FuzzUint.t.sol b/testdata/default/fuzz/FuzzUint.t.sol deleted file mode 100644 index c0cbf6466c31b..0000000000000 --- a/testdata/default/fuzz/FuzzUint.t.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 -// random values (instead edge cases) are generated if no fixtures defined -contract FuzzNumbersTest is DSTest { - function testPositive(uint256) public { - assertTrue(true); - } - - function testNegativeHalf(uint256 val) public { - assertTrue(val < 2 ** 128 - 1); - } - - function testNegative0(uint256 val) public { - assertTrue(val == 0); - } - - function testNegative2(uint256 val) public { - assertTrue(val == 2); - } - - function testNegative2Max(uint256 val) public { - assertTrue(val == type(uint256).max - 2); - } - - function testNegativeMax(uint256 val) public { - assertTrue(val == type(uint256).max); - } - - function testEquality(uint256 x, uint256 y) public { - uint256 xy; - - unchecked { - xy = x * y; - } - - if ((x != 0 && xy / x != y)) { - return; - } - - assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol deleted file mode 100644 index 3030b43e077cc..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract AfterInvariantHandler { - uint256 public count; - - function inc() external { - count += 1; - } -} - -contract InvariantAfterInvariantTest is DSTest { - AfterInvariantHandler handler; - - function setUp() public { - handler = new AfterInvariantHandler(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = handler.inc.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function afterInvariant() public { - require(handler.count() < 10, "afterInvariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 11 - function invariant_after_invariant_failure() public view { - require(handler.count() < 20, "invariant after invariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 11 - function invariant_failure() public view { - require(handler.count() < 9, "invariant failure"); - } - - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 5 - function invariant_success() public view { - require(handler.count() < 11, "invariant should not fail"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol deleted file mode 100644 index 9808a870f7228..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function doSomething(uint256 param) public { - vm.assume(param == 0); - } -} - -contract InvariantAssume is DSTest { - Handler handler; - - function setUp() public { - handler = new Handler(); - } - - function invariant_dummy() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol deleted file mode 100644 index 3d4c51eac41e5..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -// https://github.com/foundry-rs/foundry/issues/5868 -contract Owned { - address public owner; - address private ownerCandidate; - - constructor() { - owner = msg.sender; - } - - modifier onlyOwner() { - require(msg.sender == owner); - _; - } - - modifier onlyOwnerCandidate() { - require(msg.sender == ownerCandidate); - _; - } - - function transferOwnership(address candidate) external onlyOwner { - ownerCandidate = candidate; - } - - function acceptOwnership() external onlyOwnerCandidate { - owner = ownerCandidate; - } -} - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - Owned owned; - - constructor(Owned _owned) { - owned = _owned; - } - - function transferOwnership(address sender, address candidate) external { - vm.assume(sender != address(0)); - vm.prank(sender); - owned.transferOwnership(candidate); - } - - function acceptOwnership(address sender) external { - vm.assume(sender != address(0)); - vm.prank(sender); - owned.acceptOwnership(); - } -} - -contract InvariantCalldataDictionary is DSTest { - address owner; - Owned owned; - Handler handler; - address[] actors; - - function setUp() public { - owner = address(this); - owned = new Owned(); - handler = new Handler(owned); - actors.push(owner); - actors.push(address(777)); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = handler.transferOwnership.selector; - selectors[1] = handler.acceptOwnership.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function fixtureSender() external returns (address[] memory) { - return actors; - } - - function fixtureCandidate() external returns (address[] memory) { - return actors; - } - - function invariant_owner_never_changes() public { - assertEq(owned.owner(), owner); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol deleted file mode 100644 index 737cf5ba9dd05..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ContractWithCustomError { - error InvariantCustomError(uint256, string); - - function revertWithInvariantCustomError() external { - revert InvariantCustomError(111, "custom"); - } -} - -contract Handler is DSTest { - ContractWithCustomError target; - - constructor() { - target = new ContractWithCustomError(); - } - - function revertTarget() external { - target.revertWithInvariantCustomError(); - } -} - -contract InvariantCustomError is DSTest { - Handler handler; - - function setUp() external { - handler = new Handler(); - } - - function invariant_decode_error() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol deleted file mode 100644 index 8fe0bed2c6e7c..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract InvariantSenders { - function checkSender() external { - require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); - require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); - require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); - } -} - -contract InvariantExcludedSendersTest is DSTest { - InvariantSenders target; - - function setUp() public { - target = new InvariantSenders(); - } - - function invariant_check_sender() public view {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol deleted file mode 100644 index b3f1e17cb2497..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; - -contract Target { - bool ownerFound; - bool amountFound; - bool magicFound; - bool keyFound; - bool backupFound; - bool extraStringFound; - - function fuzzWithFixtures( - address owner_, - uint256 _amount, - int32 magic, - bytes32 key, - bytes memory backup, - string memory extra - ) external { - if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { - ownerFound = true; - } - if (_amount == 1122334455) amountFound = true; - if (magic == -777) magicFound = true; - if (key == "abcd1234") keyFound = true; - if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; - if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { - extraStringFound = true; - } - } - - function isCompromised() public view returns (bool) { - return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; - } -} - -/// Try to compromise target contract by finding all accepted values using fixtures. -contract InvariantFixtures is DSTest { - Target target; - address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; - uint256[] public fixture_amount = [1, 2, 1122334455]; - - function setUp() public { - target = new Target(); - } - - function fixtureMagic() external returns (int32[2] memory) { - int32[2] memory magic; - magic[0] = -777; - magic[1] = 777; - return magic; - } - - function fixtureKey() external pure returns (bytes32[] memory) { - bytes32[] memory keyFixture = new bytes32[](1); - keyFixture[0] = "abcd1234"; - return keyFixture; - } - - function fixtureBackup() external pure returns (bytes[] memory) { - bytes[] memory backupFixture = new bytes[](1); - backupFixture[0] = "qwerty1234"; - return backupFixture; - } - - function fixtureExtra() external pure returns (string[] memory) { - string[] memory extraFixture = new string[](1); - extraFixture[0] = "112233aabbccdd"; - return extraFixture; - } - - function invariant_target_not_compromised() public { - assertEq(target.isCompromised(), false); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol deleted file mode 100644 index 5ff50e782ac55..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.0; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Handler is DSTest { - function doSomething() public { - require(false, "failed on revert"); - } -} - -contract InvariantHandlerFailure is DSTest { - bytes4[] internal selectors; - - Handler handler; - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = handler.doSomething.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function setUp() public { - handler = new Handler(); - } - - function statefulFuzz_BrokenInvariant() public {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol deleted file mode 100644 index f8330a33cd6ec..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -/*////////////////////////////////////////////////////////////// - Here we test that the fuzz engine can include a contract created during the fuzz - in its fuzz dictionary and eventually break the invariant. - Specifically, can Judas, a created contract from Jesus, break Jesus contract - by revealing his identity. -/*/ -///////////////////////////////////////////////////////////// - -contract Jesus { - address fren; - bool public identity_revealed; - - function create_fren() public { - fren = address(new Judas()); - } - - function kiss() public { - require(msg.sender == fren); - identity_revealed = true; - } -} - -contract Judas { - Jesus jesus; - - constructor() { - jesus = Jesus(msg.sender); - } - - function betray() public { - jesus.kiss(); - } -} - -contract InvariantInnerContract is DSTest { - Jesus jesus; - - function setUp() public { - jesus = new Jesus(); - } - - function invariantHideJesus() public { - require(jesus.identity_revealed() == false, "jesus betrayed"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol deleted file mode 100644 index bd70dd3aeafba..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -// https://github.com/foundry-rs/foundry/issues/7219 - -contract Handler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function thisFunctionReverts() external { - if (block.number < 10) {} else { - revert(); - } - } - - function advanceTime(uint256 blocks) external { - blocks = blocks % 10; - vm.roll(block.number + blocks); - vm.warp(block.timestamp + blocks * 12); - } -} - -contract InvariantPreserveState is DSTest { - Handler handler; - - function setUp() public { - handler = new Handler(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = handler.thisFunctionReverts.selector; - selectors[1] = handler.advanceTime.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } - - function invariant_preserve_state() public { - assertTrue(true); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol deleted file mode 100644 index 74a01f1805de6..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Malicious { - function world() public { - // add code so contract is accounted as valid sender - // see https://github.com/foundry-rs/foundry/issues/4245 - payable(msg.sender).call(""); - } -} - -contract Vulnerable { - bool public open_door = false; - bool public stolen = false; - Malicious mal; - - constructor(address _mal) { - mal = Malicious(_mal); - } - - function hello() public { - open_door = true; - mal.world(); - open_door = false; - } - - function backdoor() public { - require(open_door, ""); - stolen = true; - } -} - -contract InvariantReentrancy is DSTest { - Vulnerable vuln; - Malicious mal; - - function setUp() public { - mal = new Malicious(); - vuln = new Vulnerable(address(mal)); - } - - // do not include `mal` in identified contracts - // see https://github.com/foundry-rs/foundry/issues/4245 - function targetContracts() public view returns (address[] memory) { - address[] memory targets = new address[](1); - targets[0] = address(vuln); - return targets; - } - - function invariantNotStolen() public { - require(vuln.stolen() == false, "stolen"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol deleted file mode 100644 index d15619b635f18..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -interface IERC20 { - function totalSupply() external view returns (uint256 supply); -} - -contract RollForkHandler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - uint256 public totalSupply; - - function work() external { - vm.rollFork(block.number + 1); - totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); - } -} - -contract InvariantRollForkBlockTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - RollForkHandler forkHandler; - - function setUp() public { - vm.createSelectFork("mainnet", 19812632); - forkHandler = new RollForkHandler(); - } - - /// forge-config: default.invariant.runs = 2 - /// forge-config: default.invariant.depth = 4 - function invariant_fork_handler_block() public { - require(block.number < 19812634, "too many blocks mined"); - } -} - -contract InvariantRollForkStateTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - RollForkHandler forkHandler; - - function setUp() public { - vm.createSelectFork("mainnet", 19812632); - forkHandler = new RollForkHandler(); - } - - /// forge-config: default.invariant.runs = 1 - function invariant_fork_handler_state() public { - require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol deleted file mode 100644 index 40824a2602e77..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract FindFromReturnValue { - bool public found = false; - - function seed() public returns (int256) { - int256 mystery = 13337; - return (1337 + mystery); - } - - function find(int256 i) public { - int256 mystery = 13337; - if (i == 1337 + mystery) { - found = true; - } - } -} - -contract FindFromReturnValueTest is DSTest { - FindFromReturnValue target; - - function setUp() public { - target = new FindFromReturnValue(); - } - - /// forge-config: default.invariant.runs = 50 - /// forge-config: default.invariant.depth = 300 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_value_not_found() public view { - require(!target.found(), "value from return found"); - } -} - -contract FindFromLogValue { - event FindFromLog(int256 indexed mystery, bytes32 rand); - - bool public found = false; - - function seed() public { - int256 mystery = 13337; - emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); - } - - function find(int256 i) public { - int256 mystery = 13337; - if (i == 1337 + mystery) { - found = true; - } - } -} - -contract FindFromLogValueTest is DSTest { - FindFromLogValue target; - - function setUp() public { - target = new FindFromLogValue(); - } - - /// forge-config: default.invariant.runs = 50 - /// forge-config: default.invariant.depth = 300 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_value_not_found() public view { - require(!target.found(), "value from logs found"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol b/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol deleted file mode 100644 index 993d806f81b38..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract SequenceNoReverts { - uint256 public count; - - function work(uint256 x) public { - require(x % 2 != 0); - count++; - } -} - -contract SequenceNoRevertsTest is DSTest { - SequenceNoReverts target; - - function setUp() public { - target = new SequenceNoReverts(); - } - - function invariant_no_reverts() public view { - require(target.count() < 10, "condition met"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol deleted file mode 100644 index 5699d9c455e89..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShrinkBigSequence { - uint256 cond; - - function work(uint256 x) public { - if (x % 2 != 0 && x < 9000) { - cond++; - } - } - - function checkCond() public view { - require(cond < 77, "condition met"); - } -} - -contract ShrinkBigSequenceTest is DSTest { - ShrinkBigSequence target; - - function setUp() public { - target = new ShrinkBigSequence(); - } - - function invariant_shrink_big_sequence() public view { - target.checkCond(); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol deleted file mode 100644 index d971367b69988..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShrinkFailOnRevert { - uint256 cond; - - function work(uint256 x) public { - if (x % 2 != 0 && x < 9000) { - cond++; - } - require(cond < 10, "condition met"); - } -} - -contract ShrinkFailOnRevertTest is DSTest { - ShrinkFailOnRevert target; - - function setUp() public { - target = new ShrinkFailOnRevert(); - } - - function invariant_shrink_fail_on_revert() public view {} -} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol deleted file mode 100644 index fa4a6e945804e..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -contract Counter { - uint256 public number; - - function increment() public { - number++; - } - - function decrement() public { - number--; - } -} - -contract InvariantShrinkWithAssert is DSTest { - Counter public counter; - - function setUp() public { - counter = new Counter(); - } - - function invariant_with_assert() public { - assertTrue(counter.number() < 2, "wrong counter"); - } - - function invariant_with_require() public { - require(counter.number() < 2, "wrong counter"); - } -} diff --git a/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol deleted file mode 100644 index bb62f34c6a965..0000000000000 --- a/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract InvariantBreaker { - bool public flag0 = true; - bool public flag1 = true; - - function set0(int256 val) public returns (bool) { - if (val % 100 == 0) { - flag0 = false; - } - return flag0; - } - - function set1(int256 val) public returns (bool) { - if (val % 10 == 0 && !flag0) { - flag1 = false; - } - return flag1; - } -} - -contract InvariantTest is DSTest { - InvariantBreaker inv; - - function setUp() public { - inv = new InvariantBreaker(); - } - - function invariant_neverFalse() public { - require(inv.flag1(), "false"); - } - - function statefulFuzz_neverFalseWithInvariantAlias() public { - require(inv.flag1(), "false"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol deleted file mode 100644 index e2e850e316d1e..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract ExcludeContracts is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - new Hello(); - } - - function excludeContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(hello); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol deleted file mode 100644 index e2251f42c8126..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Hello { - bool public world = false; - - function change() public { - world = true; - } - - function real_change() public { - world = false; - } -} - -contract ExcludeSelectors is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function excludeSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hello.change.selector; - targets[0] = FuzzSelector(address(hello), selectors); - return targets; - } - - function invariantFalseWorld() public { - require(hello.world() == false, "true world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol deleted file mode 100644 index dda07074d18c7..0000000000000 --- a/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - address seed_address = address(0xdeadbeef); - bool public world = true; - - function changeBeef() public { - require(msg.sender == address(0xdeadbeef)); - world = false; - } - - // address(0) should be automatically excluded - function change0() public { - require(msg.sender == address(0)); - world = false; - } -} - -contract ExcludeSenders is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function excludeSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - // Tests clashing. Exclusion takes priority. - function targetSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol deleted file mode 100644 index 759810611e95b..0000000000000 --- a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -interface Vm { - function etch(address target, bytes calldata newRuntimeBytecode) external; -} - -// https://github.com/foundry-rs/foundry/issues/5625 -// https://github.com/foundry-rs/foundry/issues/6166 -// `Target.wrongSelector` is not called when handler added as `targetContract` -// `Target.wrongSelector` is called (and test fails) when no `targetContract` set -contract Target { - uint256 count; - - function wrongSelector() external { - revert("wrong target selector called"); - } - - function goodSelector() external { - count++; - } -} - -contract Handler is DSTest { - function increment() public { - Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); - } -} - -contract ExplicitTargetContract is DSTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - Handler handler; - - function setUp() public { - Target target = new Target(); - bytes memory targetCode = address(target).code; - vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); - - handler = new Handler(); - } - - function targetContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(handler); - return addrs; - } - - function invariant_explicit_target() public {} -} - -contract DynamicTargetContract is DSTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - Handler handler; - - function setUp() public { - Target target = new Target(); - bytes memory targetCode = address(target).code; - vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); - - handler = new Handler(); - } - - function invariant_dynamic_targets() public {} -} diff --git a/testdata/default/fuzz/invariant/target/TargetContracts.t.sol b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol deleted file mode 100644 index d24c7eb5282a4..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetContracts.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract TargetContracts is DSTest { - Hello hello1; - Hello hello2; - - function setUp() public { - hello1 = new Hello(); - hello2 = new Hello(); - } - - function targetContracts() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(hello1); - return addrs; - } - - function invariantTrueWorld() public { - require(hello2.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol deleted file mode 100644 index 30b4a05e3eaf9..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzInterface { - address target; - string[] artifacts; -} - -contract Hello { - bool public world; - - function changeWorld() external { - world = true; - } -} - -interface IHello { - function world() external view returns (bool); - function changeWorld() external; -} - -contract HelloProxy { - address internal immutable _implementation; - - constructor(address implementation_) { - _implementation = implementation_; - } - - function _delegate(address implementation) internal { - assembly { - calldatacopy(0, 0, calldatasize()) - - let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } - - fallback() external payable { - _delegate(_implementation); - } -} - -contract TargetWorldInterfaces is DSTest { - IHello proxy; - - function setUp() public { - Hello hello = new Hello(); - proxy = IHello(address(new HelloProxy(address(hello)))); - } - - function targetInterfaces() public returns (FuzzInterface[] memory) { - FuzzInterface[] memory targets = new FuzzInterface[](1); - - string[] memory artifacts = new string[](1); - artifacts[0] = "IHello"; - - targets[0] = FuzzInterface(address(proxy), artifacts); - - return targets; - } - - function invariantTrueWorld() public { - require(proxy.world() == false, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol deleted file mode 100644 index c74ac7fa18114..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzSelector { - address addr; - bytes4[] selectors; -} - -contract Hello { - bool public world = true; - - function change() public { - world = true; - } - - function real_change() public { - world = false; - } -} - -contract TargetSelectors is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hello.change.selector; - targets[0] = FuzzSelector(address(hello), selectors); - return targets; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/target/TargetSenders.t.sol b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol deleted file mode 100644 index 6fa4c9a6387d5..0000000000000 --- a/testdata/default/fuzz/invariant/target/TargetSenders.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Hello { - bool public world = true; - - function change() public { - require(msg.sender == address(0xdeadbeef)); - world = false; - } -} - -contract TargetSenders is DSTest { - Hello hello; - - function setUp() public { - hello = new Hello(); - } - - function targetSenders() public returns (address[] memory) { - address[] memory addrs = new address[](1); - addrs[0] = address(0xdeadbeef); - return addrs; - } - - function invariantTrueWorld() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol deleted file mode 100644 index 86ca6d5439b7a..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -// Will get automatically excluded. Otherwise it would throw error. -contract NoMutFunctions { - function no_change() public pure {} -} - -contract Excluded { - bool public world = true; - - function change() public { - world = false; - } -} - -contract Hello { - bool public world = true; - - function change() public { - world = false; - } -} - -contract ExcludeArtifacts is DSTest { - Excluded excluded; - - function setUp() public { - excluded = new Excluded(); - new Hello(); - new NoMutFunctions(); - } - - function excludeArtifacts() public returns (string[] memory) { - string[] memory abis = new string[](1); - abis[0] = "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; - return abis; - } - - function invariantShouldPass() public { - require(excluded.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol deleted file mode 100644 index 440f6183f415c..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzArtifactSelector { - string artifact; - bytes4[] selectors; -} - -contract Hi { - bool public world = true; - - function no_change() public { - world = true; - } - - function changee() public { - world = false; - } -} - -contract TargetArtifactSelectors is DSTest { - Hi hello; - - function setUp() public { - hello = new Hi(); - } - - function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { - FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = Hi.no_change.selector; - targets[0] = - FuzzArtifactSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); - return targets; - } - - function invariantShouldPass() public { - require(hello.world() == true, "false world"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol deleted file mode 100644 index 162d9cc2e1106..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -struct FuzzArtifactSelector { - string artifact; - bytes4[] selectors; -} - -contract Parent { - bool public should_be_true = true; - address public child; - - function change() public { - child = msg.sender; - should_be_true = false; - } - - function create() public { - new Child(); - } -} - -contract Child { - Parent parent; - bool public changed = false; - - constructor() { - parent = Parent(msg.sender); - } - - function change_parent() public { - parent.change(); - } - - function tracked_change_parent() public { - parent.change(); - } -} - -contract TargetArtifactSelectors2 is DSTest { - Parent parent; - - function setUp() public { - parent = new Parent(); - } - - function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { - FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); - bytes4[] memory selectors_child = new bytes4[](1); - - selectors_child[0] = Child.change_parent.selector; - targets[0] = FuzzArtifactSelector( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child - ); - - bytes4[] memory selectors_parent = new bytes4[](1); - selectors_parent[0] = Parent.create.selector; - targets[1] = FuzzArtifactSelector( - "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent - ); - return targets; - } - - function invariantShouldFail() public { - if (!parent.should_be_true()) { - require(!Child(address(parent.child())).changed(), "should have not happened"); - } - require(parent.should_be_true() == true, "it's false"); - } -} diff --git a/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol deleted file mode 100644 index 28fa146059f92..0000000000000 --- a/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; - -contract Targeted { - bool public world = true; - - function change() public { - world = false; - } -} - -contract Hello { - bool public world = true; - - function no_change() public {} -} - -contract TargetArtifacts is DSTest { - Targeted target1; - Targeted target2; - Hello hello; - - function setUp() public { - target1 = new Targeted(); - target2 = new Targeted(); - hello = new Hello(); - } - - function targetArtifacts() public returns (string[] memory) { - string[] memory abis = new string[](1); - abis[0] = "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; - return abis; - } - - function invariantShouldPass() public { - require(target2.world() == true || target1.world() == true || hello.world() == true, "false world"); - } - - function invariantShouldFail() public { - require(target2.world() == true || target1.world() == true, "false world"); - } -} diff --git a/testdata/default/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol index 73d2de2fc4ed1..24c059e935be7 100644 --- a/testdata/default/inline/FuzzInlineConf.t.sol +++ b/testdata/default/inline/FuzzInlineConf.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; -contract FuzzInlineConf is DSTest { +contract FuzzInlineConf is Test { /** * forge-config: default.fuzz.runs = 1024 * forge-config: default.fuzz.max-test-rejects = 500 @@ -14,7 +14,7 @@ contract FuzzInlineConf is DSTest { } /// forge-config: default.fuzz.runs = 10 -contract FuzzInlineConf2 is DSTest { +contract FuzzInlineConf2 is Test { /// forge-config: default.fuzz.runs = 1 function testInlineConfFuzz1(uint8 x) public { require(true, "this is not going to revert"); diff --git a/testdata/default/inline/InvariantInlineConf.t.sol b/testdata/default/inline/InvariantInlineConf.t.sol index 5ac81755e998d..6699008a343ac 100644 --- a/testdata/default/inline/InvariantInlineConf.t.sol +++ b/testdata/default/inline/InvariantInlineConf.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract InvariantBreaker { bool public flag0 = true; @@ -22,7 +22,7 @@ contract InvariantBreaker { } } -contract InvariantInlineConf is DSTest { +contract InvariantInlineConf is Test { InvariantBreaker inv; function setUp() public { @@ -38,7 +38,7 @@ contract InvariantInlineConf is DSTest { } } -contract InvariantInlineConf2 is DSTest { +contract InvariantInlineConf2 is Test { InvariantBreaker inv; function setUp() public { diff --git a/testdata/default/linking/duplicate/Duplicate.t.sol b/testdata/default/linking/duplicate/Duplicate.t.sol index d1d0f32789238..2ad1d9f7fbe5b 100644 --- a/testdata/default/linking/duplicate/Duplicate.t.sol +++ b/testdata/default/linking/duplicate/Duplicate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract has many dependencies, some of which appear to the linker // more than once. @@ -93,7 +93,7 @@ contract LibraryConsumer { } } -contract DuplicateLibraryLinkingTest is DSTest { +contract DuplicateLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/linking/nested/Nested.t.sol b/testdata/default/linking/nested/Nested.t.sol index 136cb36479cbf..709bf0143857d 100644 --- a/testdata/default/linking/nested/Nested.t.sol +++ b/testdata/default/linking/nested/Nested.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract with a library that depends on a library @@ -27,7 +27,7 @@ contract LibraryConsumer { } } -contract NestedLibraryLinkingTest is DSTest { +contract NestedLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/linking/samefile_union/Libs.sol b/testdata/default/linking/samefile_union/Libs.sol new file mode 100644 index 0000000000000..1e93f56c66e6a --- /dev/null +++ b/testdata/default/linking/samefile_union/Libs.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +library LInit { + function f() external view returns (uint256) { + return block.number; + } +} + +library LRun { + function g() external view returns (uint256) { + return block.timestamp; + } +} diff --git a/testdata/default/linking/samefile_union/SameFileUnion.t.sol b/testdata/default/linking/samefile_union/SameFileUnion.t.sol new file mode 100644 index 0000000000000..013f0e0b0c501 --- /dev/null +++ b/testdata/default/linking/samefile_union/SameFileUnion.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "./Libs.sol"; + +contract UsesBoth { + uint256 public x; + + constructor() { + // used only in creation bytecode + x = LInit.f(); + } + + function y() external view returns (uint256) { + // used only in deployed bytecode + return LRun.g(); + } +} diff --git a/testdata/default/linking/simple/Simple.t.sol b/testdata/default/linking/simple/Simple.t.sol index 85be791fd2489..ded3efb1694c8 100644 --- a/testdata/default/linking/simple/Simple.t.sol +++ b/testdata/default/linking/simple/Simple.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // Linking scenario: contract with one library @@ -17,7 +17,7 @@ contract LibraryConsumer { } } -contract SimpleLibraryLinkingTest is DSTest { +contract SimpleLibraryLinkingTest is Test { LibraryConsumer consumer; function setUp() public { diff --git a/testdata/default/logs/DebugLogs.t.sol b/testdata/default/logs/DebugLogs.t.sol deleted file mode 100644 index b560fd2bfb9ca..0000000000000 --- a/testdata/default/logs/DebugLogs.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract DebugLogsTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - constructor() { - emit log_uint(0); - } - - function setUp() public { - emit log_uint(1); - } - - function test1() public { - emit log_uint(2); - } - - function test2() public { - emit log_uint(3); - } - - function testRevertIfWithRevert() public { - Fails fails = new Fails(); - emit log_uint(4); - vm.expectRevert(); - fails.failure(); - } - - /// forge-config: default.allow_internal_expect_revert = true - function testRevertIfWithRequire() public { - emit log_uint(5); - vm.expectRevert(); - require(false); - } - - function testLog() public { - emit log("Error: Assertion Failed"); - } - - function testLogs() public { - emit logs(bytes("abcd")); - } - - function testLogAddress() public { - emit log_address(address(1)); - } - - function testLogBytes32() public { - emit log_bytes32(bytes32("abcd")); - } - - function testLogInt() public { - emit log_int(int256(-31337)); - } - - function testLogBytes() public { - emit log_bytes(bytes("abcd")); - } - - function testLogString() public { - emit log_string("here"); - } - - function testLogNamedAddress() public { - emit log_named_address("address", address(1)); - } - - function testLogNamedBytes32() public { - emit log_named_bytes32("abcd", bytes32("abcd")); - } - - function testLogNamedDecimalInt() public { - emit log_named_decimal_int("amount", int256(-31337), uint256(18)); - } - - function testLogNamedDecimalUint() public { - emit log_named_decimal_uint("amount", uint256(1 ether), uint256(18)); - } - - function testLogNamedInt() public { - emit log_named_int("amount", int256(-31337)); - } - - function testLogNamedUint() public { - emit log_named_uint("amount", uint256(1 ether)); - } - - function testLogNamedBytes() public { - emit log_named_bytes("abcd", bytes("abcd")); - } - - function testLogNamedString() public { - emit log_named_string("key", "val"); - } -} - -contract Fails is DSTest { - function failure() public { - emit log_uint(100); - revert(); - } -} diff --git a/testdata/default/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol deleted file mode 100644 index a6226bbd665fe..0000000000000 --- a/testdata/default/logs/HardhatLogs.t.sol +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "./console.sol"; - -contract HardhatLogsTest { - constructor() { - console.log("constructor"); - } - - string testStr; - int256 testInt; - uint256 testUint; - bool testBool; - address testAddr; - bytes testBytes; - - function setUp() public { - testStr = "test"; - testInt = -31337; - testUint = 1; - testBool = false; - testAddr = 0x0000000000000000000000000000000000000001; - testBytes = "a"; - } - - function testInts() public view { - console.log(uint256(0)); - console.log(uint256(1)); - console.log(uint256(2)); - console.log(uint256(3)); - } - - function testStrings() public view { - console.log("testStrings"); - } - - function testMisc() public view { - console.log("testMisc", address(1)); - console.log("testMisc", uint256(42)); - } - - function testConsoleLog() public view { - console.log(testStr); - } - - function testLogInt() public view { - console.logInt(testInt); - } - - function testLogUint() public view { - console.logUint(testUint); - } - - function testLogString() public view { - console.logString(testStr); - } - - function testLogBool() public view { - console.logBool(testBool); - } - - function testLogAddress() public view { - console.logAddress(testAddr); - } - - function testLogBytes() public view { - console.logBytes(testBytes); - } - - function testLogBytes1() public view { - console.logBytes1(bytes1(testBytes)); - } - - function testLogBytes2() public view { - console.logBytes2(bytes2(testBytes)); - } - - function testLogBytes3() public view { - console.logBytes3(bytes3(testBytes)); - } - - function testLogBytes4() public view { - console.logBytes4(bytes4(testBytes)); - } - - function testLogBytes5() public view { - console.logBytes5(bytes5(testBytes)); - } - - function testLogBytes6() public view { - console.logBytes6(bytes6(testBytes)); - } - - function testLogBytes7() public view { - console.logBytes7(bytes7(testBytes)); - } - - function testLogBytes8() public view { - console.logBytes8(bytes8(testBytes)); - } - - function testLogBytes9() public view { - console.logBytes9(bytes9(testBytes)); - } - - function testLogBytes10() public view { - console.logBytes10(bytes10(testBytes)); - } - - function testLogBytes11() public view { - console.logBytes11(bytes11(testBytes)); - } - - function testLogBytes12() public view { - console.logBytes12(bytes12(testBytes)); - } - - function testLogBytes13() public view { - console.logBytes13(bytes13(testBytes)); - } - - function testLogBytes14() public view { - console.logBytes14(bytes14(testBytes)); - } - - function testLogBytes15() public view { - console.logBytes15(bytes15(testBytes)); - } - - function testLogBytes16() public view { - console.logBytes16(bytes16(testBytes)); - } - - function testLogBytes17() public view { - console.logBytes17(bytes17(testBytes)); - } - - function testLogBytes18() public view { - console.logBytes18(bytes18(testBytes)); - } - - function testLogBytes19() public view { - console.logBytes19(bytes19(testBytes)); - } - - function testLogBytes20() public view { - console.logBytes20(bytes20(testBytes)); - } - - function testLogBytes21() public view { - console.logBytes21(bytes21(testBytes)); - } - - function testLogBytes22() public view { - console.logBytes22(bytes22(testBytes)); - } - - function testLogBytes23() public view { - console.logBytes23(bytes23(testBytes)); - } - - function testLogBytes24() public view { - console.logBytes24(bytes24(testBytes)); - } - - function testLogBytes25() public view { - console.logBytes25(bytes25(testBytes)); - } - - function testLogBytes26() public view { - console.logBytes26(bytes26(testBytes)); - } - - function testLogBytes27() public view { - console.logBytes27(bytes27(testBytes)); - } - - function testLogBytes28() public view { - console.logBytes28(bytes28(testBytes)); - } - - function testLogBytes29() public view { - console.logBytes29(bytes29(testBytes)); - } - - function testLogBytes30() public view { - console.logBytes30(bytes30(testBytes)); - } - - function testLogBytes31() public view { - console.logBytes31(bytes31(testBytes)); - } - - function testLogBytes32() public view { - console.logBytes32(bytes32(testBytes)); - } - - function testConsoleLogUint() public view { - console.log(testUint); - } - - function testConsoleLogString() public view { - console.log(testStr); - } - - function testConsoleLogBool() public view { - console.log(testBool); - } - - function testConsoleLogAddress() public view { - console.log(testAddr); - } - - function testConsoleLogFormatString() public view { - console.log("formatted log str=%s", testStr); - } - - function testConsoleLogFormatUint() public view { - console.log("formatted log uint=%s", testUint); - } - - function testConsoleLogFormatAddress() public view { - console.log("formatted log addr=%s", testAddr); - } - - function testConsoleLogFormatMulti() public view { - console.log("formatted log str=%s uint=%d", testStr, testUint); - } - - function testConsoleLogFormatEscape() public view { - console.log("formatted log %% %s", testStr); - } - - function testConsoleLogFormatSpill() public view { - console.log("formatted log %s", testStr, testUint); - } -} diff --git a/testdata/default/repros/Issue10302.t.sol b/testdata/default/repros/Issue10302.t.sol index c47332cb2872d..c5516804ccd1c 100644 --- a/testdata/default/repros/Issue10302.t.sol +++ b/testdata/default/repros/Issue10302.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract A { function foo() public pure returns (bool) { @@ -10,9 +9,7 @@ contract A { } } -contract Issue10302Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10302Test is Test { function testDelegateFails() external { vm.createSelectFork("sepolia"); A a = new A(); diff --git a/testdata/default/repros/Issue10477.t.sol b/testdata/default/repros/Issue10477.t.sol index 86d36fc741ef4..29bfcb37ced29 100644 --- a/testdata/default/repros/Issue10477.t.sol +++ b/testdata/default/repros/Issue10477.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleDelegate { function call(address target, bytes memory data) external returns (bool callResult, bytes memory callData) { @@ -18,9 +17,7 @@ contract Counter { } } -contract Issue10477Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10477Test is Test { address payable ALICE_ADDRESS = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); uint256 constant ALICE_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; diff --git a/testdata/default/repros/Issue10527.t.sol b/testdata/default/repros/Issue10527.t.sol index f9149267dbde3..ba92317672f3c 100644 --- a/testdata/default/repros/Issue10527.t.sol +++ b/testdata/default/repros/Issue10527.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract A { event Event1(); @@ -17,8 +16,9 @@ contract A { } } -contract Issue10527Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue10527Test is Test { + event Event1(); + event Event2(); A a; @@ -28,14 +28,14 @@ contract Issue10527Test is DSTest { function test_foo_Event1() public { vm.expectEmit(address(a)); - emit A.Event1(); + emit Event1(); a.foo(); } function test_foo_Event2() public { vm.expectEmit({emitter: address(a), count: 0}); - emit A.Event2(); + emit Event2(); a.foo(); } diff --git a/testdata/default/repros/Issue10552.t.sol b/testdata/default/repros/Issue10552.t.sol index e77168f150f4d..d0f6a7713a9b7 100644 --- a/testdata/default/repros/Issue10552.t.sol +++ b/testdata/default/repros/Issue10552.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -21,9 +20,7 @@ contract Counter { } } -contract Issue10552Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10552Test is Test { Counter public counter; uint256 mainnetId; uint256 opId; diff --git a/testdata/default/repros/Issue10586.t.sol b/testdata/default/repros/Issue10586.t.sol index 3d7eefbf3ac03..24c355abef7ee 100644 --- a/testdata/default/repros/Issue10586.t.sol +++ b/testdata/default/repros/Issue10586.t.sol @@ -1,20 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Target is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract Target is Test { function setChainId() public { vm.chainId(123); } } -contract Issue10586Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue10586Test is Test { Target public target; function setUp() public { diff --git a/testdata/default/repros/Issue10957.t.sol b/testdata/default/repros/Issue10957.t.sol new file mode 100644 index 0000000000000..a8efd20f5cda6 --- /dev/null +++ b/testdata/default/repros/Issue10957.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +// https://github.com/foundry-rs/foundry/issues/10957 +contract Issue10957Test is Test { + function testCreateSelectForkBlockNumber() public { + // Transaction hash from mainnet + bytes32 txHash = 0x2e175897d19307c664815129720c8ac3581da6cb92e4cce923996dd59fbb6ffc; + + // Expected block number for this transaction + uint256 expectedBlockNumber = 22875105; + + // Create fork at the transaction + uint256 forkId = vm.createSelectFork("mainnet", txHash); + + // Get the current block number + uint256 currentBlock = vm.getBlockNumber(); + + // The fork should be at the transaction's block, not one block behind + assertEq(currentBlock, expectedBlockNumber, "Fork should be at the transaction's block number"); + } +} diff --git a/testdata/default/repros/Issue11353.t.sol b/testdata/default/repros/Issue11353.t.sol new file mode 100644 index 0000000000000..cb89de17a0ada --- /dev/null +++ b/testdata/default/repros/Issue11353.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.24; + +import "utils/Test.sol"; + +contract Blobhash { + function getIndices(uint256[] calldata blobIndices) public view returns (bytes32[] memory) { + bytes32[] memory blobHashes = new bytes32[](blobIndices.length); + for (uint256 i = 0; i < blobIndices.length; i++) { + uint256 blobIndex = blobIndices[i]; + bytes32 blobHash = blobhash(blobIndex); + require(blobHash != 0, "blob not found"); + blobHashes[i] = blobHash; + } + return blobHashes; + } +} + +// https://github.com/foundry-rs/foundry/issues/11353 +contract Issue11353Test is Test { + Blobhash public blobhashContract; + + function setUp() public { + blobhashContract = new Blobhash(); + } + + function test_blobhashes() public { + uint256[] memory blobIndices = new uint256[](1); + blobIndices[0] = 0; + + bytes32[] memory blobHashes = new bytes32[](1); + blobHashes[0] = keccak256(abi.encode(0)); + vm.blobhashes(blobHashes); + + vm.assertEq(blobhashContract.getIndices(blobIndices), blobHashes); + } +} diff --git a/testdata/default/repros/Issue11616.t.sol b/testdata/default/repros/Issue11616.t.sol new file mode 100644 index 0000000000000..cf2cbb1900faa --- /dev/null +++ b/testdata/default/repros/Issue11616.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.24; + +import "utils/Test.sol"; + +contract Emit { + event A(); + event B(); + + function emitB() public { + emit B(); + } +} + +contract Issue11616Test is Test { + Emit public e; + + function setUp() public { + e = new Emit(); + } + + function test_emitNotOk() public { + vm.expectEmit({count: 0}); + emit Emit.A(); + vm.expectEmit(); + emit Emit.B(); + e.emitB(); + } +} diff --git a/testdata/default/repros/Issue12075.t.sol b/testdata/default/repros/Issue12075.t.sol new file mode 100644 index 0000000000000..3efb38a6d7076 --- /dev/null +++ b/testdata/default/repros/Issue12075.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +// https://github.com/foundry-rs/foundry/issues/12075 +contract Issue12075Test is Test { + address payable internal ALICE = payable(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + address payable internal BOB = payable(address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8)); + + Target internal target; + + function setUp() public virtual { + target = new Target(); + + vm.deal({account: ALICE, newBalance: 100 ether}); + vm.startPrank(ALICE); + } + + function test_NativeTransfer() public { + BOB.transfer(1 ether); + assertEq(BOB.balance, 1 ether); + } + + function test_PayableFunction() public { + target.hit{value: 1 wei}(); + } +} + +contract Target { + function hit() public payable {} +} diff --git a/testdata/default/repros/Issue14212.t.sol b/testdata/default/repros/Issue14212.t.sol new file mode 100644 index 0000000000000..0135e072bb72f --- /dev/null +++ b/testdata/default/repros/Issue14212.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "utils/Test.sol"; + +// https://github.com/foundry-rs/foundry/issues/14212 +// EthEvmNetwork uses Ethereum as its Network type, which cannot deserialize +// OP Stack deposit transactions (type 0x7e). These tests verify that the fork +// backend can handle blocks and transactions containing deposit txs. + +contract Issue14212Test is Test { + // Base block 30434326 contains a deposit tx at index 0: + // tx: 0x6fc82bcdcdeba0385c3910cd8e92074e51aaa9f21528dbc4c242f560a2f27bab + // type: 0x7e (deposit) + // from: 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001 + // to: 0x4200000000000000000000000000000000000015 + // + // A regular tx in the same block: + // tx: 0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2 + + /// vm.transact on an OP deposit tx should not revert with a deserialization error. + /// This exercises the fork backend's get_transaction codepath. + function test_transactDepositTxOnBase() public { + // Fork Base at the block before the deposit tx + vm.createSelectFork("base", 30434325); + + // Transact the deposit tx from the next block. + // This calls fork.backend().get_transaction() which uses FEN::Network + // to deserialize the response. With Network = Ethereum, this fails: + // "deserialization error: data did not match any variant of untagged enum BlockTransactions" + vm.transact(0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2); + } + + /// vm.rollFork to a tx hash in a block containing deposit txs should work. + /// This exercises the fork backend's get_full_block codepath. + function test_rollForkToTxOnBase() public { + vm.createSelectFork("base", 30434325); + + // Roll to a regular tx in block 30434326 which also contains deposit txs. + // This calls get_full_block internally which must deserialize the entire + // block including the deposit tx. + bytes32 txHash = 0xe2f4bffbcc88dd94cabf9b15e2318df0afc2ec895012274d0ecec3d27d6da3e2; + vm.rollFork(txHash); + } + + /// vm.transact on an OP deposit tx on Optimism mainnet. + function test_transactDepositTxOnOptimism() public { + // Optimism block 127867197 contains deposit tx at index 0: + // tx: 0x56b04a2e66cba482270c6e68244a8faaa59a1e1878a04086a12514be2d6e14f9 + vm.createSelectFork("optimism", 127867196); + + // Transact a regular tx from the block containing deposit txs. + // The regular tx at index 1: + vm.transact(0xa003e419e2d7502269eb5eda56947b580120e00abfd5b5460d08f8af44a0c24f); + } +} diff --git a/testdata/default/repros/Issue2623.t.sol b/testdata/default/repros/Issue2623.t.sol index 31252cae36c3b..486db1963b190 100644 --- a/testdata/default/repros/Issue2623.t.sol +++ b/testdata/default/repros/Issue2623.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2623 -contract Issue2623Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2623Test is Test { function testRollFork() public { uint256 fork = vm.createFork("mainnet", 10); vm.selectFork(fork); diff --git a/testdata/default/repros/Issue2629.t.sol b/testdata/default/repros/Issue2629.t.sol index d46868903a60e..b7e1d9ebdb208 100644 --- a/testdata/default/repros/Issue2629.t.sol +++ b/testdata/default/repros/Issue2629.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2629 -contract Issue2629Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2629Test is Test { function testSelectFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; diff --git a/testdata/default/repros/Issue2723.t.sol b/testdata/default/repros/Issue2723.t.sol index b7678df450cb8..58e9ba43f64d8 100644 --- a/testdata/default/repros/Issue2723.t.sol +++ b/testdata/default/repros/Issue2723.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2723 -contract Issue2723Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue2723Test is Test { function testRollFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; diff --git a/testdata/default/repros/Issue2851.t.sol b/testdata/default/repros/Issue2851.t.sol deleted file mode 100644 index f90a5f7c5dc7d..0000000000000 --- a/testdata/default/repros/Issue2851.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.1; - -import "ds-test/test.sol"; - -contract Backdoor { - uint256 public number = 1; - - function backdoor(uint256 newNumber) public payable { - uint256 x = newNumber - 1; - if (x == 6912213124124531) { - number = 0; - } - } -} - -// https://github.com/foundry-rs/foundry/issues/2851 -contract Issue2851Test is DSTest { - Backdoor back; - - function setUp() public { - back = new Backdoor(); - } - - function invariantNotZero() public { - assertEq(back.number(), 1); - } -} diff --git a/testdata/default/repros/Issue2898.t.sol b/testdata/default/repros/Issue2898.t.sol index a16adf5a350ff..564ef4fa2ef78 100644 --- a/testdata/default/repros/Issue2898.t.sol +++ b/testdata/default/repros/Issue2898.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2898 -contract Issue2898Test is DSTest { +contract Issue2898Test is Test { address private constant BRIDGE = address(10); address private constant BENEFICIARY = address(11); - Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { vm.deal(BRIDGE, 100); diff --git a/testdata/default/repros/Issue2956.t.sol b/testdata/default/repros/Issue2956.t.sol index c57b46cc1f4bc..3295df41dbf36 100644 --- a/testdata/default/repros/Issue2956.t.sol +++ b/testdata/default/repros/Issue2956.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2956 -contract Issue2956Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue2956Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol index fbcd1ab8c3c4a..52a3c52d06a84 100644 --- a/testdata/default/repros/Issue2984.t.sol +++ b/testdata/default/repros/Issue2984.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/2984 -contract Issue2984Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue2984Test is Test { uint256 fork; uint256 snapshot; diff --git a/testdata/default/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol deleted file mode 100644 index 90ac8c3b08afd..0000000000000 --- a/testdata/default/repros/Issue3055.t.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3055 -contract Issue3055Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function test_snapshot() external { - uint256 snapshotId = vm.snapshotState(); - assertEq(uint256(0), uint256(1)); - vm.revertToState(snapshotId); - } - - function test_snapshot2() public { - uint256 snapshotId = vm.snapshotState(); - assertTrue(false); - vm.revertToState(snapshotId); - assertTrue(true); - } - - function test_snapshot3(uint256) public { - vm.expectRevert(); - // Call exposed_snapshot3() using this to perform an external call, - // so we can properly test for reverts. - this.exposed_snapshot3(); - } - - function exposed_snapshot3() public { - uint256 snapshotId = vm.snapshotState(); - assertTrue(false); - vm.revertToState(snapshotId); - } -} diff --git a/testdata/default/repros/Issue3077.t.sol b/testdata/default/repros/Issue3077.t.sol index 3b5e4257a3ad4..ec68bc3f4bf63 100644 --- a/testdata/default/repros/Issue3077.t.sol +++ b/testdata/default/repros/Issue3077.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3077 -abstract contract ZeroState is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +abstract contract ZeroState is Test { // deployer and users address public deployer = vm.addr(1); Token aaveToken; diff --git a/testdata/default/repros/Issue3110.t.sol b/testdata/default/repros/Issue3110.t.sol index 9f1da8d032ec3..4c4f3d257d5a5 100644 --- a/testdata/default/repros/Issue3110.t.sol +++ b/testdata/default/repros/Issue3110.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3110 -abstract contract ZeroState is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +abstract contract ZeroState is Test { // deployer and users address public deployer = vm.addr(1); Token aaveToken; diff --git a/testdata/default/repros/Issue3119.t.sol b/testdata/default/repros/Issue3119.t.sol index 6c0ceb429d6d6..99de3f7aab7fd 100644 --- a/testdata/default/repros/Issue3119.t.sol +++ b/testdata/default/repros/Issue3119.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3119 -contract Issue3119Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3119Test is Test { address public owner = vm.addr(1); address public alice = vm.addr(2); diff --git a/testdata/default/repros/Issue3189.t.sol b/testdata/default/repros/Issue3189.t.sol deleted file mode 100644 index 0bcf5ddce8d76..0000000000000 --- a/testdata/default/repros/Issue3189.t.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3189 -contract MyContract { - function foo(uint256 arg) public returns (uint256) { - return arg + 2; - } -} - -contract MyContractUser is DSTest { - MyContract immutable myContract; - - constructor() { - myContract = new MyContract(); - } - - function foo(uint256 arg) public returns (uint256 ret) { - ret = myContract.foo(arg); - assertEq(ret, arg + 1, "Invariant failed"); - } -} - -contract Issue3189Test is DSTest { - function testFoo() public { - MyContractUser user = new MyContractUser(); - uint256 fooRet = user.foo(123); - } -} diff --git a/testdata/default/repros/Issue3190.t.sol b/testdata/default/repros/Issue3190.t.sol index ede3e50e2e3ec..30a6bb623b8a1 100644 --- a/testdata/default/repros/Issue3190.t.sol +++ b/testdata/default/repros/Issue3190.t.sol @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3190 -contract Issue3190Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3190Test is Test { function setUp() public { vm.chainId(99); assertEq(99, block.chainid); diff --git a/testdata/default/repros/Issue3192.t.sol b/testdata/default/repros/Issue3192.t.sol index 9c5be8d89f3d6..6fa3e8dfd442c 100644 --- a/testdata/default/repros/Issue3192.t.sol +++ b/testdata/default/repros/Issue3192.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3192 -contract Issue3192Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3192Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3220.t.sol b/testdata/default/repros/Issue3220.t.sol index 5235e44c79c60..366c079619d84 100644 --- a/testdata/default/repros/Issue3220.t.sol +++ b/testdata/default/repros/Issue3220.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3220 -contract Issue3220Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3220Test is Test { IssueRepro repro; uint256 fork1; diff --git a/testdata/default/repros/Issue3221.t.sol b/testdata/default/repros/Issue3221.t.sol index 81398c41fc290..9526987ddd87b 100644 --- a/testdata/default/repros/Issue3221.t.sol +++ b/testdata/default/repros/Issue3221.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3221 -contract Issue3221Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3221Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3223.t.sol b/testdata/default/repros/Issue3223.t.sol index 6c21b7b3d60b1..ede3042ba732f 100644 --- a/testdata/default/repros/Issue3223.t.sol +++ b/testdata/default/repros/Issue3223.t.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3223 -contract Issue3223Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +/// forge-config: default.sender = "0xF0959944122fb1ed4CfaBA645eA06EED30427BAA" +contract Issue3223Test is Test { uint256 fork1; uint256 fork2; diff --git a/testdata/default/repros/Issue3347.t.sol b/testdata/default/repros/Issue3347.t.sol deleted file mode 100644 index e48c1305db426..0000000000000 --- a/testdata/default/repros/Issue3347.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3347 -contract Issue3347Test is DSTest { - event log2(uint256, uint256); - - function test() public { - emit log2(1, 2); - } -} diff --git a/testdata/default/repros/Issue3437.t.sol b/testdata/default/repros/Issue3437.t.sol deleted file mode 100644 index 69f56ca8283dd..0000000000000 --- a/testdata/default/repros/Issue3437.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3437 -contract Issue3347Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function internalRevert() internal { - revert(); - } - - function testFailExample() public { - vm.expectRevert(); - internalRevert(); - } -} diff --git a/testdata/default/repros/Issue3596.t.sol b/testdata/default/repros/Issue3596.t.sol deleted file mode 100644 index b0c6785874375..0000000000000 --- a/testdata/default/repros/Issue3596.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3596 -contract Issue3596Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testDealTransfer() public { - address addr = vm.addr(1337); - vm.startPrank(addr); - vm.deal(addr, 20000001 ether); - payable(address(this)).transfer(20000000 ether); - - Nested nested = new Nested(); - nested.doStuff(); - vm.stopPrank(); - } -} - -contract Nested { - function doStuff() public { - doRevert(); - } - - function doRevert() public { - revert("This fails"); - } -} diff --git a/testdata/default/repros/Issue3653.t.sol b/testdata/default/repros/Issue3653.t.sol index 26eb38e4a29f1..0657e9908d306 100644 --- a/testdata/default/repros/Issue3653.t.sol +++ b/testdata/default/repros/Issue3653.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3653 -contract Issue3653Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3653Test is Test { uint256 fork; Token token; diff --git a/testdata/default/repros/Issue3661.t.sol b/testdata/default/repros/Issue3661.t.sol index 76b55a222ca00..8491857074e07 100644 --- a/testdata/default/repros/Issue3661.t.sol +++ b/testdata/default/repros/Issue3661.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3661 -contract Issue3661Test is DSTest { +contract Issue3661Test is Test { address sender; function setUp() public { diff --git a/testdata/default/repros/Issue3674.t.sol b/testdata/default/repros/Issue3674.t.sol index de5a960059f52..39d8d9c24f582 100644 --- a/testdata/default/repros/Issue3674.t.sol +++ b/testdata/default/repros/Issue3674.t.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3674 -contract Issue3674Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.sender = "0xF0959944122fb1ed4CfaBA645eA06EED30427BAA" +contract Issue3674Test is Test { function testNonceCreateSelect() public { vm.createSelectFork("sepolia"); vm.createSelectFork("avaxTestnet"); - assert(vm.getNonce(msg.sender) > 0x17); + assertTrue(vm.getNonce(msg.sender) > 0x17); } } diff --git a/testdata/default/repros/Issue3685.t.sol b/testdata/default/repros/Issue3685.t.sol index f1da5bf6997bd..e7ff00264e52c 100644 --- a/testdata/default/repros/Issue3685.t.sol +++ b/testdata/default/repros/Issue3685.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3685 -contract Issue3685Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue3685Test is Test { Actor a; Actor b; diff --git a/testdata/default/repros/Issue3703.t.sol b/testdata/default/repros/Issue3703.t.sol index 48651be24c669..3ac50765f1a93 100644 --- a/testdata/default/repros/Issue3703.t.sol +++ b/testdata/default/repros/Issue3703.t.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3703 -contract Issue3703Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3703Test is Test { function setUp() public { + vm.skip(true, "flaky polygon RPCs"); + uint256 fork = vm.createSelectFork("polygon", bytes32(0xbed0c8c1b9ff8bf0452979d170c52893bb8954f18a904aa5bcbd0f709be050b9)); } diff --git a/testdata/default/repros/Issue3708.t.sol b/testdata/default/repros/Issue3708.t.sol index 53a7c461f873f..c78304a1cbf02 100644 --- a/testdata/default/repros/Issue3708.t.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -1,14 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3708 -contract Issue3708Test is DSTest { +contract Issue3708Test is Test { // https://optimistic.etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { uint256 forkId = vm.createSelectFork("optimism"); diff --git a/testdata/default/repros/Issue3723.t.sol b/testdata/default/repros/Issue3723.t.sol deleted file mode 100644 index 9ea3fe733c944..0000000000000 --- a/testdata/default/repros/Issue3723.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/3723 -contract Issue3723Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testFailExample() public { - vm.expectRevert(); - revert(); - - vm.expectRevert(); - emit log_string("Do not revert"); - } -} diff --git a/testdata/default/repros/Issue3753.t.sol b/testdata/default/repros/Issue3753.t.sol index 2c927c823dbb6..1fe6040f2a44a 100644 --- a/testdata/default/repros/Issue3753.t.sol +++ b/testdata/default/repros/Issue3753.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/3753 -contract Issue3753Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue3753Test is Test { function test_repro() public { bool res; assembly { diff --git a/testdata/default/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol index 37f62bc61fabe..39d3f77bc28a4 100644 --- a/testdata/default/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/DSTest.sol"; +import "utils/Vm.sol"; contract Config { address public test = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; diff --git a/testdata/default/repros/Issue4232.t.sol b/testdata/default/repros/Issue4232.t.sol index 0ac6a77c77076..44f038bfbf28e 100644 --- a/testdata/default/repros/Issue4232.t.sol +++ b/testdata/default/repros/Issue4232.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4232 -contract Issue4232Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4232Test is Test { function testFork() public { // Smoke test, worked previously as well vm.createSelectFork("sepolia", 7215400); diff --git a/testdata/default/repros/Issue4402.t.sol b/testdata/default/repros/Issue4402.t.sol index 830b2926ef269..19072638e74da 100644 --- a/testdata/default/repros/Issue4402.t.sol +++ b/testdata/default/repros/Issue4402.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4402 -contract Issue4402Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4402Test is Test { function testReadNonEmptyArray() public { string memory path = "fixtures/Json/Issue4402.json"; string memory json = vm.readFile(path); diff --git a/testdata/default/repros/Issue4586.t.sol b/testdata/default/repros/Issue4586.t.sol index c904af1e46abb..57f1dd3052d04 100644 --- a/testdata/default/repros/Issue4586.t.sol +++ b/testdata/default/repros/Issue4586.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4586 -contract Issue4586Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4586Test is Test { uint256 constant initialBlock = 16730733; InvariantHandler handler; @@ -31,8 +28,7 @@ contract Issue4586Test is DSTest { } contract InvariantHandler { - address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); - Vm constant vm = Vm(HEVM_ADDRESS); + Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); uint256 public calledRollFork; diff --git a/testdata/default/repros/Issue4630.t.sol b/testdata/default/repros/Issue4630.t.sol index 01eb626505cd1..cfc0ebde43089 100644 --- a/testdata/default/repros/Issue4630.t.sol +++ b/testdata/default/repros/Issue4630.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4630 -contract Issue4630Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4630Test is Test { function testExistingValue() public { string memory path = "fixtures/Json/Issue4630.json"; string memory json = vm.readFile(path); diff --git a/testdata/default/repros/Issue4640.t.sol b/testdata/default/repros/Issue4640.t.sol index 1e7d887a9b57d..5c445d22b3516 100644 --- a/testdata/default/repros/Issue4640.t.sol +++ b/testdata/default/repros/Issue4640.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/4640 -contract Issue4640Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue4640Test is Test { function testArbitrumBlockNumber() public { - // - vm.createSelectFork("arbitrum", 75219831); + // + vm.createSelectFork("arbitrum", 394276729); // L1 block number - assertEq(block.number, 16939475); + assertEq(block.number, 23675778); } } diff --git a/testdata/default/repros/Issue4832.t.sol b/testdata/default/repros/Issue4832.t.sol deleted file mode 100644 index 192d805c1bc36..0000000000000 --- a/testdata/default/repros/Issue4832.t.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/4832 -contract Issue4832Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testFailExample() public { - assertEq(uint256(1), 2); - - vm.expectRevert(); - revert(); - } -} diff --git a/testdata/default/repros/Issue5038.t.sol b/testdata/default/repros/Issue5038.t.sol index 51a90bca10d4c..2139639cc56ac 100644 --- a/testdata/default/repros/Issue5038.t.sol +++ b/testdata/default/repros/Issue5038.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; struct Value { uint256 value; } // https://github.com/foundry-rs/foundry/issues/5038 -contract Issue5038Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5038Test is Test { function testParseMaxUint64() public { string memory json = '{"value": 18446744073709551615}'; bytes memory parsed = vm.parseJson(json); diff --git a/testdata/default/repros/Issue5529.t.sol b/testdata/default/repros/Issue5529.t.sol index 14ec7cfdbce60..2f7afbbc2d524 100644 --- a/testdata/default/repros/Issue5529.t.sol +++ b/testdata/default/repros/Issue5529.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -16,20 +15,21 @@ contract Counter { } } -contract Issue5529Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.always_use_create_2_factory = true +contract Issue5529Test is Test { Counter public counter; address public constant default_create2_factory = 0x4e59b44847b379578588920cA78FbF26c0B4956C; function testCreate2FactoryUsedInTests() public { - address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); - address b = address(new Counter{salt: 0}()); - require(a == b, "create2 address mismatch"); + run(); } function testCreate2FactoryUsedWhenPranking() public { vm.startPrank(address(1234)); + run(); + } + + function run() private { address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); address b = address(new Counter{salt: 0}()); require(a == b, "create2 address mismatch"); diff --git a/testdata/default/repros/Issue5739.t.sol b/testdata/default/repros/Issue5739.t.sol index 6f3494b7e6e7d..bbde1b73e7c20 100644 --- a/testdata/default/repros/Issue5739.t.sol +++ b/testdata/default/repros/Issue5739.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IERC20 { function totalSupply() external view returns (uint256 supply); } // https://github.com/foundry-rs/foundry/issues/5739 -contract Issue5739Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue5739Test is Test { IERC20 dai; function setUp() public { diff --git a/testdata/default/repros/Issue5808.t.sol b/testdata/default/repros/Issue5808.t.sol index 40efe65a9ce69..fc76fe1e4a3cf 100644 --- a/testdata/default/repros/Issue5808.t.sol +++ b/testdata/default/repros/Issue5808.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5808 -contract Issue5808Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5808Test is Test { function testReadInt() public { string memory str1 = '["ffffffff","00000010"]'; vm._expectCheatcodeRevert(); diff --git a/testdata/default/repros/Issue5929.t.sol b/testdata/default/repros/Issue5929.t.sol index ced9d6d9b4a39..6dac42653e3ed 100644 --- a/testdata/default/repros/Issue5929.t.sol +++ b/testdata/default/repros/Issue5929.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5929 -contract Issue5929Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5929Test is Test { function test_transact_not_working() public { vm.createSelectFork("mainnet", 21134547); // https://etherscan.io/tx/0x96a129768ec66fd7d65114bf182f4e173bf0b73a44219adaf71f01381a3d0143 diff --git a/testdata/default/repros/Issue5935.t.sol b/testdata/default/repros/Issue5935.t.sol index 8ef724412ce31..220681cab55fb 100644 --- a/testdata/default/repros/Issue5935.t.sol +++ b/testdata/default/repros/Issue5935.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract SimpleStorage { uint256 public value; @@ -12,9 +11,7 @@ contract SimpleStorage { } } -contract Issue5935Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5935Test is Test { function testFork() public { uint256 forkId1 = vm.createFork("mainnet", 18234083); uint256 forkId2 = vm.createFork("mainnet", 18234083); diff --git a/testdata/default/repros/Issue5948.t.sol b/testdata/default/repros/Issue5948.t.sol index ae6ee9d50d8ba..b7a5080721baf 100644 --- a/testdata/default/repros/Issue5948.t.sol +++ b/testdata/default/repros/Issue5948.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/5948 -contract Issue5948Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue5948Test is Test { /// forge-config: default.fuzz.runs = 2 function testSleepFuzzed(uint256 _milliseconds) public { // Limit sleep time to 2 seconds to decrease test time diff --git a/testdata/default/repros/Issue6006.t.sol b/testdata/default/repros/Issue6006.t.sol index 54f0d11376d79..30dcbd00a74a2 100644 --- a/testdata/default/repros/Issue6006.t.sol +++ b/testdata/default/repros/Issue6006.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6006 -contract Issue6066Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6066Test is Test { function test_parse_11e20_sci() public { string memory json = '{"value": 1.1e20}'; bytes memory parsed = vm.parseJson(json); diff --git a/testdata/default/repros/Issue6032.t.sol b/testdata/default/repros/Issue6032.t.sol index 2fa05222d5a54..149097975ad30 100644 --- a/testdata/default/repros/Issue6032.t.sol +++ b/testdata/default/repros/Issue6032.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6032 -contract Issue6032Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6032Test is Test { function testEtchFork() public { // Deploy initial contract Counter counter = new Counter(); diff --git a/testdata/default/repros/Issue6070.t.sol b/testdata/default/repros/Issue6070.t.sol index ebf3c7ab15c7b..5188a877e8d90 100644 --- a/testdata/default/repros/Issue6070.t.sol +++ b/testdata/default/repros/Issue6070.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6070 -contract Issue6066Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6066Test is Test { function testNonPrefixed() public { vm.setEnv("__FOUNDRY_ISSUE_6066", "abcd"); vm._expectCheatcodeRevert( diff --git a/testdata/default/repros/Issue6115.t.sol b/testdata/default/repros/Issue6115.t.sol index ae65a7dae8647..878b584a31afb 100644 --- a/testdata/default/repros/Issue6115.t.sol +++ b/testdata/default/repros/Issue6115.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -16,7 +16,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/6115 -contract Issue6115Test is DSTest { +contract Issue6115Test is Test { Counter public counter; function setUp() public { diff --git a/testdata/default/repros/Issue6170.t.sol b/testdata/default/repros/Issue6170.t.sol deleted file mode 100644 index 78511f4a2dc91..0000000000000 --- a/testdata/default/repros/Issue6170.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract Emitter { - event Values(uint256 indexed a, uint256 indexed b); - - function plsEmit(uint256 a, uint256 b) external { - emit Values(a, b); - } -} - -// https://github.com/foundry-rs/foundry/issues/6170 -contract Issue6170Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - event Values(uint256 indexed a, uint256 b); - - Emitter e = new Emitter(); - - function test() public { - vm.expectEmit(true, true, false, true); - emit Values(69, 420); - e.plsEmit(69, 420); - } -} diff --git a/testdata/default/repros/Issue6180.t.sol b/testdata/default/repros/Issue6180.t.sol index 3d08ccbebac5d..5d6b82988e139 100644 --- a/testdata/default/repros/Issue6180.t.sol +++ b/testdata/default/repros/Issue6180.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6180 -contract Issue6180Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6180Test is Test { function test_timebug() external { uint256 start = block.timestamp; uint256 count = 4; diff --git a/testdata/default/repros/Issue6293.t.sol b/testdata/default/repros/Issue6293.t.sol index 6d57d13850950..5f9484aeeee57 100644 --- a/testdata/default/repros/Issue6293.t.sol +++ b/testdata/default/repros/Issue6293.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6293 -contract Issue6293Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6293Test is Test { constructor() { require(address(this).balance > 0); - payable(address(1)).call{value: 1}(""); + (bool success,) = payable(address(1)).call{value: 1}(""); + require(success, "call failed"); } function test() public { diff --git a/testdata/default/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol deleted file mode 100644 index bbc3a4a98d412..0000000000000 --- a/testdata/default/repros/Issue6355.t.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/6355 -contract Issue6355Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - uint256 snapshotId; - Target targ; - - function setUp() public { - snapshotId = vm.snapshotState(); - targ = new Target(); - } - - // this non-deterministically fails sometimes and passes sometimes - function test_shouldPass() public { - assertEq(2, targ.num()); - } - - // always fails - function test_shouldFailWithRevertToState() public { - assertEq(3, targ.num()); - vm.revertToState(snapshotId); - } - - // always fails - function test_shouldFail() public { - assertEq(3, targ.num()); - } -} - -contract Target { - function num() public pure returns (uint256) { - return 2; - } -} diff --git a/testdata/default/repros/Issue6437.t.sol b/testdata/default/repros/Issue6437.t.sol index 4cf27be7b233e..e2fceaa660f2a 100644 --- a/testdata/default/repros/Issue6437.t.sol +++ b/testdata/default/repros/Issue6437.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6437 -contract Issue6437Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6437Test is Test { function test0() public { string memory json = "[]"; address[] memory arr = vm.parseJsonAddressArray(json, "$"); diff --git a/testdata/default/repros/Issue6501.t.sol b/testdata/default/repros/Issue6501.t.sol deleted file mode 100644 index 5d631cbe3e0a8..0000000000000 --- a/testdata/default/repros/Issue6501.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "../logs/console.sol"; - -// https://github.com/foundry-rs/foundry/issues/6501 -contract Issue6501Test is DSTest { - function test_hhLogs() public { - console.log("a"); - console.log(uint256(1)); - console.log("b", uint256(2)); - } -} diff --git a/testdata/default/repros/Issue6538.t.sol b/testdata/default/repros/Issue6538.t.sol index 34c4e2253a68b..c870c668e6816 100644 --- a/testdata/default/repros/Issue6538.t.sol +++ b/testdata/default/repros/Issue6538.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6538 -contract Issue6538Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6538Test is Test { function test_transact() public { bytes32 lastHash = 0x4b70ca8c5a0990b43df3064372d424d46efa41dfaab961754b86c5afb2df4f61; vm.createSelectFork("mainnet", lastHash); diff --git a/testdata/default/repros/Issue6554.t.sol b/testdata/default/repros/Issue6554.t.sol index 7a5fe7879c655..a6b086653a96a 100644 --- a/testdata/default/repros/Issue6554.t.sol +++ b/testdata/default/repros/Issue6554.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6554 -contract Issue6554Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6554Test is Test { function testPermissions() public { - vm.writeFile("./out/default/Issue6554.t.sol/cachedFile.txt", "cached data"); - string memory content = vm.readFile("./out/default/Issue6554.t.sol/cachedFile.txt"); + vm.writeFile("./out/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/Issue6554.t.sol/cachedFile.txt"); assertEq(content, "cached data"); } } diff --git a/testdata/default/repros/Issue6616.t.sol b/testdata/default/repros/Issue6616.t.sol index 262721d86d118..587c4703e3d6c 100644 --- a/testdata/default/repros/Issue6616.t.sol +++ b/testdata/default/repros/Issue6616.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6616 -contract Issue6616Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6616Test is Test { function testCreateForkRollLatestBlock() public { vm.createSelectFork("mainnet"); uint256 startBlock = block.number; diff --git a/testdata/default/repros/Issue6634.t.sol b/testdata/default/repros/Issue6634.t.sol index aa94922dd1ccb..5426b3f2404ba 100644 --- a/testdata/default/repros/Issue6634.t.sol +++ b/testdata/default/repros/Issue6634.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../logs/console.sol"; +import "utils/Test.sol"; contract Box { uint256 public number; @@ -14,9 +12,8 @@ contract Box { } // https://github.com/foundry-rs/foundry/issues/6634 -contract Issue6634Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +/// forge-config: default.always_use_create_2_factory = true +contract Issue6634Test is Test { function test_Create2FactoryCallRecordedInStandardTest() public { address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; diff --git a/testdata/default/repros/Issue6643.t.sol b/testdata/default/repros/Issue6643.t.sol index 5c7e1c483a03a..0f8b45fa2864d 100644 --- a/testdata/default/repros/Issue6643.t.sol +++ b/testdata/default/repros/Issue6643.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { event TestEvent(uint256 n); @@ -24,8 +23,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/6643 -contract Issue6643Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue6643Test is Test { Counter public counter; event TestEvent(uint256 n); diff --git a/testdata/default/repros/Issue6759.t.sol b/testdata/default/repros/Issue6759.t.sol index ffdcb88935a92..436d7922fe3d2 100644 --- a/testdata/default/repros/Issue6759.t.sol +++ b/testdata/default/repros/Issue6759.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6759 -contract Issue6759Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue6759Test is Test { function testCreateMulti() public { uint256 fork1 = vm.createFork("mainnet", 10); uint256 fork2 = vm.createFork("mainnet", 10); diff --git a/testdata/default/repros/Issue6966.t.sol b/testdata/default/repros/Issue6966.t.sol index 7e35a869ed0fb..9032a51dd63f7 100644 --- a/testdata/default/repros/Issue6966.t.sol +++ b/testdata/default/repros/Issue6966.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/6966 // See also https://github.com/RustCrypto/elliptic-curves/issues/988#issuecomment-1817681013 -contract Issue6966Test is DSTest { +contract Issue6966Test is Test { function testEcrecover() public { bytes32 h = 0x0000000000000000000000000000000000000000000000000000000000000000; uint8 v = 27; diff --git a/testdata/default/repros/Issue7238.t.sol b/testdata/default/repros/Issue7238.t.sol index a2227fabed8bc..989b8d5799fb6 100644 --- a/testdata/default/repros/Issue7238.t.sol +++ b/testdata/default/repros/Issue7238.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Reverter { function doNotRevert() public {} @@ -13,9 +12,7 @@ contract Reverter { } // https://github.com/foundry-rs/foundry/issues/7238 -contract Issue7238Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7238Test is Test { function testExpectRevertString() public { Reverter reverter = new Reverter(); vm.expectRevert("revert"); @@ -33,10 +30,10 @@ contract Issue7238Test is DSTest { /// forge-config: default.allow_internal_expect_revert = true function testShouldFailEarlyRevert() public { vm.expectRevert(); - rever(); + revert_(); } - function rever() internal { + function revert_() internal { revert(); } } diff --git a/testdata/default/repros/Issue7457.t.sol b/testdata/default/repros/Issue7457.t.sol index 13cd033afac9a..c6413ec8cbe4b 100644 --- a/testdata/default/repros/Issue7457.t.sol +++ b/testdata/default/repros/Issue7457.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface ITarget { event AnonymousEventEmpty() anonymous; @@ -46,9 +45,7 @@ contract Target is ITarget { } // https://github.com/foundry-rs/foundry/issues/7457 -contract Issue7457Test is DSTest, ITarget { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7457Test is Test, ITarget { Target public target; function setUp() external { diff --git a/testdata/default/repros/Issue7481.t.sol b/testdata/default/repros/Issue7481.t.sol index c8116b8095aeb..d182f7fd333ac 100644 --- a/testdata/default/repros/Issue7481.t.sol +++ b/testdata/default/repros/Issue7481.t.sol @@ -1,21 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/7481 // This test ensures that we don't panic -contract Issue7481Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue7481Test is Test { /// forge-config: default.allow_internal_expect_revert = true function testRevertTransact() public { vm.expectRevert("vm.createSelectFork: invalid rpc url: mainnet"); vm.createSelectFork("mainnet", 19514903); // Transfer some funds to sender of tx being transacted to ensure that it appears in journaled state - payable(address(0x5C60cD7a3D50877Bfebd484750FBeb245D936dAD)).call{value: 1}(""); + (bool success,) = payable(address(0x5C60cD7a3D50877Bfebd484750FBeb245D936dAD)).call{value: 1}(""); + console.log(success); vm.transact(0xccfd66fc409a633a99b5b75b0e9a2040fcf562d03d9bee3fefc1a5c0eb49c999); // Revert the current call to ensure that revm can revert state journal diff --git a/testdata/default/repros/Issue8004.t.sol b/testdata/default/repros/Issue8004.t.sol index 278aa12125ff3..b7dd82af36cec 100644 --- a/testdata/default/repros/Issue8004.t.sol +++ b/testdata/default/repros/Issue8004.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; -contract NonPersistentHelper is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract NonPersistentHelper is Test { uint256 public curState; function createSelectFork() external { @@ -42,8 +40,7 @@ contract NonPersistentHelper is DSTest { } // https://github.com/foundry-rs/foundry/issues/8004 -contract Issue8004CreateSelectForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8004CreateSelectForkTest is Test { NonPersistentHelper helper; function setUp() public { @@ -72,8 +69,7 @@ contract Issue8004CreateSelectForkTest is DSTest { } } -contract Issue8004RollForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8004RollForkTest is Test { NonPersistentHelper helper; uint256 forkId; diff --git a/testdata/default/repros/Issue8006.t.sol b/testdata/default/repros/Issue8006.t.sol index efe339d9fef2b..706703eecf8ba 100644 --- a/testdata/default/repros/Issue8006.t.sol +++ b/testdata/default/repros/Issue8006.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IERC20 { function totalSupply() external view returns (uint256 supply); @@ -15,8 +14,7 @@ contract Mock { } // https://github.com/foundry-rs/foundry/issues/8006 -contract Issue8006Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +contract Issue8006Test is Test { IERC20 dai; bytes32 transaction = 0xb23f389b26eb6f95c08e275ec2c360ab3990169492ff0d3e7b7233a3f81d299f; diff --git a/testdata/default/repros/Issue8168.t.sol b/testdata/default/repros/Issue8168.t.sol index 9a072ce4bbf8f..f7871b4b76f71 100644 --- a/testdata/default/repros/Issue8168.t.sol +++ b/testdata/default/repros/Issue8168.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8168 -contract Issue8168Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8168Test is Test { function testForkWarpRollPreserved() public { uint256 fork1 = vm.createFork("mainnet"); uint256 fork2 = vm.createFork("mainnet"); diff --git a/testdata/default/repros/Issue8277.t.sol b/testdata/default/repros/Issue8277.t.sol index 48a089575b402..8dca9cd7ea832 100644 --- a/testdata/default/repros/Issue8277.t.sol +++ b/testdata/default/repros/Issue8277.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8277 -contract Issue8277Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8277Test is Test { struct MyJson { string s; } diff --git a/testdata/default/repros/Issue8287.t.sol b/testdata/default/repros/Issue8287.t.sol index d1e372bda91d8..e1a67f3538e46 100644 --- a/testdata/default/repros/Issue8287.t.sol +++ b/testdata/default/repros/Issue8287.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8287 -contract Issue8287Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8287Test is Test { function testRpcBalance() public { uint256 f2 = vm.createSelectFork("mainnet", 10); bytes memory data = vm.rpc("eth_getBalance", "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"); diff --git a/testdata/default/repros/Issue8383.t.sol b/testdata/default/repros/Issue8383.t.sol deleted file mode 100644 index 339f5b518a30c..0000000000000 --- a/testdata/default/repros/Issue8383.t.sol +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/8383 -contract Issue8383Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - address internal _verifier; - - mapping(bytes32 => bool) internal _vectorTested; - mapping(bytes32 => bool) internal _vectorResult; - - function setUp() public { - _verifier = address(new P256Verifier()); - } - - function _verifyViaVerifier(bytes32 hash, uint256 r, uint256 s, uint256 x, uint256 y) internal returns (bool) { - return _verifyViaVerifier(hash, bytes32(r), bytes32(s), bytes32(x), bytes32(y)); - } - - function _verifyViaVerifier(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) internal returns (bool) { - bytes memory payload = abi.encode(hash, r, s, x, y); - if (uint256(y) & 0xff == 0) { - bytes memory truncatedPayload = abi.encodePacked(hash, r, s, x, bytes31(y)); - _verifierCall(truncatedPayload); - } - if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) { - uint256 r = uint256(keccak256(abi.encode(payload, "2"))); - payload = abi.encodePacked(payload, new bytes(r & 0xff)); - } - bytes32 payloadHash = keccak256(payload); - if (_vectorTested[payloadHash]) return _vectorResult[payloadHash]; - _vectorTested[payloadHash] = true; - return (_vectorResult[payloadHash] = _verifierCall(payload)); - } - - function _verifierCall(bytes memory payload) internal returns (bool) { - (bool success, bytes memory result) = _verifier.call(payload); - return abi.decode(result, (bool)); - } - - function testP256VerifyOutOfBounds() public { - vm.pauseGasMetering(); - uint256 p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; - _verifyViaVerifier(bytes32(0), 1, 1, 1, 1); - _verifyViaVerifier(bytes32(0), 1, 1, 0, 1); - _verifyViaVerifier(bytes32(0), 1, 1, 1, 0); - _verifyViaVerifier(bytes32(0), 1, 1, 1, p); - _verifyViaVerifier(bytes32(0), 1, 1, p, 1); - _verifyViaVerifier(bytes32(0), 1, 1, p - 1, 1); - vm.resumeGasMetering(); - } -} - -contract P256Verifier { - uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; - uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; - uint256 private constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; // `A = P - 3`. - uint256 private constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; - uint256 private constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; - - fallback() external payable { - assembly { - // For this implementation, we will use the memory without caring about - // the free memory pointer or zero pointer. - // The slots `0x00`, `0x20`, `0x40`, `0x60`, will not be accessed for the `Points[16]` array, - // and can be used for storing other variables. - - mstore(0x40, P) // Set `0x40` to `P`. - - function jAdd(x1, y1, z1, x2, y2, z2) -> x3, y3, z3 { - if iszero(z1) { - x3 := x2 - y3 := y2 - z3 := z2 - leave - } - if iszero(z2) { - x3 := x1 - y3 := y1 - z3 := z1 - leave - } - let p := mload(0x40) - let zz1 := mulmod(z1, z1, p) - let zz2 := mulmod(z2, z2, p) - let u1 := mulmod(x1, zz2, p) - let u2 := mulmod(x2, zz1, p) - let s1 := mulmod(y1, mulmod(zz2, z2, p), p) - let s2 := mulmod(y2, mulmod(zz1, z1, p), p) - let h := addmod(u2, sub(p, u1), p) - let hh := mulmod(h, h, p) - let hhh := mulmod(h, hh, p) - let r := addmod(s2, sub(p, s1), p) - x3 := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) - y3 := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, x3), p), p), sub(p, mulmod(s1, hhh, p)), p) - z3 := mulmod(h, mulmod(z1, z2, p), p) - } - - function setJPoint(i, x, y, z) { - // We will multiply by `0x80` (i.e. `shl(7, i)`) instead - // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. - // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. - i := shl(7, i) - mstore(i, x) - mstore(add(i, returndatasize()), y) - mstore(add(i, 0x40), z) - } - - function setJPointDouble(i, j) { - j := shl(7, j) - let x := mload(j) - let y := mload(add(j, returndatasize())) - let z := mload(add(j, 0x40)) - let p := mload(0x40) - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) - let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) - let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - let z2 := mulmod(2, mulmod(y, z, p), p) - setJPoint(i, x2, y2, z2) - } - - function setJPointAdd(i, j, k) { - j := shl(7, j) - k := shl(7, k) - let x, y, z := - jAdd( - mload(j), - mload(add(j, returndatasize())), - mload(add(j, 0x40)), - mload(k), - mload(add(k, returndatasize())), - mload(add(k, 0x40)) - ) - setJPoint(i, x, y, z) - } - - let r := calldataload(0x20) - let n := N - - { - let s := calldataload(0x40) - if lt(shr(1, n), s) { s := sub(n, s) } - - // Perform `modExp(s, N - 2, N)`. - // After which, we can abuse `returndatasize()` to get `0x20`. - mstore(0x800, 0x20) - mstore(0x820, 0x20) - mstore(0x840, 0x20) - mstore(0x860, s) - mstore(0x880, sub(n, 2)) - mstore(0x8a0, n) - - let p := mload(0x40) - mstore(0x20, xor(3, p)) // Set `0x20` to `A`. - let Qx := calldataload(0x60) - let Qy := calldataload(0x80) - - if iszero( - and( // The arguments of `and` are evaluated last to first. - and( - and(gt(calldatasize(), 0x9f), and(lt(iszero(r), lt(r, n)), lt(iszero(s), lt(s, n)))), - eq( - mulmod(Qy, Qy, p), - addmod(mulmod(addmod(mulmod(Qx, Qx, p), mload(returndatasize()), p), Qx, p), B, p) - ) - ), - and( - // We need to check that the `returndatasize` is indeed 32, - // so that we can return false if the chain does not have the modexp precompile. - eq(returndatasize(), 0x20), - staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), 0x20) - ) - ) - ) { - // POC Note: - // Changing this to `return(0x80, 0x20)` fixes it. - // Alternatively, adding `if mload(0x8c0) { invalid() }` just before the return also fixes it. - return(0x8c0, 0x20) - } - - setJPoint(0x01, Qx, Qy, 1) - setJPoint(0x04, GX, GY, 1) - setJPointDouble(0x02, 0x01) - setJPointDouble(0x08, 0x04) - setJPointAdd(0x03, 0x01, 0x02) - setJPointAdd(0x05, 0x01, 0x04) - setJPointAdd(0x06, 0x02, 0x04) - setJPointAdd(0x07, 0x03, 0x04) - setJPointAdd(0x09, 0x01, 0x08) - setJPointAdd(0x0a, 0x02, 0x08) - setJPointAdd(0x0b, 0x03, 0x08) - setJPointAdd(0x0c, 0x04, 0x08) - setJPointAdd(0x0d, 0x01, 0x0c) - setJPointAdd(0x0e, 0x02, 0x0c) - setJPointAdd(0x0f, 0x03, 0x0c) - } - - let i := 0 - let u1 := mulmod(calldataload(0x00), mload(0x00), n) - let u2 := mulmod(r, mload(0x00), n) - let y := 0 - let z := 0 - let x := 0 - let p := mload(0x40) - for {} 1 {} { - if z { - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) - let m := - addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) - let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - let z2 := mulmod(2, mulmod(y, z, p), p) - yy := mulmod(y2, y2, p) - zz := mulmod(z2, z2, p) - s := mulmod(4, mulmod(x2, yy, p), p) - m := - addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) - x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - z := mulmod(2, mulmod(y2, z2, p), p) - y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - } - for { let o := or(and(shr(245, shl(i, u1)), 0x600), and(shr(247, shl(i, u2)), 0x180)) } o {} { - let z2 := mload(add(o, 0x40)) - if iszero(z2) { break } - if iszero(z) { - x := mload(o) - y := mload(add(o, returndatasize())) - z := z2 - break - } - let zz1 := mulmod(z, z, p) - let zz2 := mulmod(z2, z2, p) - let u1_ := mulmod(x, zz2, p) - let s1 := mulmod(y, mulmod(zz2, z2, p), p) - let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) - let hh := mulmod(h, h, p) - let hhh := mulmod(h, hh, p) - let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) - x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) - y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) - z := mulmod(h, mulmod(z, z2, p), p) - break - } - // Just unroll twice. Fully unrolling will only save around 1% to 2% gas, but make the - // bytecode very bloated, which may incur more runtime costs after Verkle. - // See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip - // It's very unlikely that Verkle will come before the P256 precompile. But who knows? - if z { - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) - let m := - addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) - let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - let z2 := mulmod(2, mulmod(y, z, p), p) - yy := mulmod(y2, y2, p) - zz := mulmod(z2, z2, p) - s := mulmod(4, mulmod(x2, yy, p), p) - m := - addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) - x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - z := mulmod(2, mulmod(y2, z2, p), p) - y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - } - for { let o := or(and(shr(243, shl(i, u1)), 0x600), and(shr(245, shl(i, u2)), 0x180)) } o {} { - let z2 := mload(add(o, 0x40)) - if iszero(z2) { break } - if iszero(z) { - x := mload(o) - y := mload(add(o, returndatasize())) - z := z2 - break - } - let zz1 := mulmod(z, z, p) - let zz2 := mulmod(z2, z2, p) - let u1_ := mulmod(x, zz2, p) - let s1 := mulmod(y, mulmod(zz2, z2, p), p) - let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) - let hh := mulmod(h, h, p) - let hhh := mulmod(h, hh, p) - let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) - x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) - y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) - z := mulmod(h, mulmod(z, z2, p), p) - break - } - i := add(i, 4) - if eq(i, 256) { break } - } - - if iszero(z) { - mstore(returndatasize(), iszero(r)) - return(returndatasize(), 0x20) - } - - // Perform `modExp(z, P - 2, P)`. - // `0x800`, `0x820, `0x840` are still set to `0x20`. - mstore(0x860, z) - mstore(0x880, sub(p, 2)) - mstore(0x8a0, p) - - mstore( - returndatasize(), - and( // The arguments of `and` are evaluated last to first. - eq(mod(mulmod(x, mulmod(mload(returndatasize()), mload(returndatasize()), p), p), n), r), - staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), returndatasize()) - ) - ) - return(returndatasize(), returndatasize()) - } - } -} diff --git a/testdata/default/repros/Issue8566.t.sol b/testdata/default/repros/Issue8566.t.sol index f300d096f7a22..2dad2fc1f8371 100644 --- a/testdata/default/repros/Issue8566.t.sol +++ b/testdata/default/repros/Issue8566.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; // https://github.com/foundry-rs/foundry/issues/8566 -contract Issue8566Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue8566Test is Test { function testParseJsonUint() public { string memory json = "{ \"1284\": { \"addRewardInfo\": { \"amount\": 74258.225772486694040708e18, \"rewardPerSec\": 0.03069536448928848133e20 } } }"; diff --git a/testdata/default/repros/Issue8639.t.sol b/testdata/default/repros/Issue8639.t.sol index 6f0a7b526336f..96b2fa3621522 100644 --- a/testdata/default/repros/Issue8639.t.sol +++ b/testdata/default/repros/Issue8639.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; library ExternalLibrary { function doWork(uint256 a) public returns (uint256) { @@ -20,7 +20,7 @@ contract Counter { } // https://github.com/foundry-rs/foundry/issues/8639 -contract Issue8639Test is DSTest { +contract Issue8639Test is Test { Counter counter; function setUp() public { @@ -28,15 +28,15 @@ contract Issue8639Test is DSTest { } /// forge-config: default.fuzz.runs = 1000 - /// forge-config: default.fuzz.seed = '100' + /// forge-config: default.fuzz.seed = "100" function test_external_library_address(address test) public { require(test != address(ExternalLibrary)); } } -contract Issue8639AnotherTest is DSTest { +contract Issue8639AnotherTest is Test { /// forge-config: default.fuzz.runs = 1000 - /// forge-config: default.fuzz.seed = '100' + /// forge-config: default.fuzz.seed = "100" function test_another_external_library_address(address test) public { require(test != address(ExternalLibrary)); } diff --git a/testdata/default/repros/Issue8971.t.sol b/testdata/default/repros/Issue8971.t.sol index 37861b483ec5d..4d235be214679 100644 --- a/testdata/default/repros/Issue8971.t.sol +++ b/testdata/default/repros/Issue8971.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Counter { uint256 public number; @@ -31,7 +30,8 @@ contract Handler { } } -contract Invariant is DSTest { +/// forge-config: default.isolate = true +contract Invariant is Test { Handler h; function setUp() public { diff --git a/testdata/default/repros/Issue9643.t.sol b/testdata/default/repros/Issue9643.t.sol index 7a985138dc335..fead6ebcbb0c4 100644 --- a/testdata/default/repros/Issue9643.t.sol +++ b/testdata/default/repros/Issue9643.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Mock { uint256 private counter; @@ -33,16 +32,14 @@ contract DelegateProxy { } } -contract Issue9643Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract Issue9643Test is Test { function test_storage_json_diff() public { vm.startStateDiffRecording(); Mock proxied = Mock(address(new DelegateProxy(address(new Mock())))); proxied.setCounter(42); string memory rawDiff = vm.getStateDiffJson(); assertEq( - "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}}}", + '{"0x2e234dae75c793f67a35089c9d99245e1c58470b":{"label":null,"contract":"default/repros/Issue9643.t.sol:DelegateProxy","balanceDiff":null,"nonceDiff":{"previousValue":0,"newValue":1},"stateDiff":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000000000000000000000000000002a","label":"implementation","type":"address","offset":0,"slot":"0","decoded":{"previousValue":"0x0000000000000000000000000000000000000000","newValue":"0x000000000000000000000000000000000000002A"}}}},"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f":{"label":null,"contract":"default/repros/Issue9643.t.sol:Mock","balanceDiff":null,"nonceDiff":{"previousValue":0,"newValue":1},"stateDiff":{}}}', rawDiff ); } diff --git a/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json deleted file mode 100644 index 1a8bccb392489..0000000000000 --- a/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json +++ /dev/null @@ -1 +0,0 @@ -{"transactions":[{"hash":null,"type":"CREATE","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","gas":"0x54653","value":"0x0","data":"0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033","nonce":"0x0","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xef15","value":"0x0","data":"0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000","nonce":"0x1","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xdcde","value":"0x0","data":"0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b","nonce":"0x2","accessList":[]}}],"receipts":[],"libraries":[],"pending":[],"path":"broadcast/deploy.sol/31337/run-latest.json","returns":{},"timestamp":1658913881} \ No newline at end of file diff --git a/testdata/default/script/deploy.sol b/testdata/default/script/deploy.sol deleted file mode 100644 index 7570c706a5cec..0000000000000 --- a/testdata/default/script/deploy.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.18; - -import {DSTest} from "lib/ds-test/src/test.sol"; -import {Vm} from "cheats/Vm.sol"; - -contract Greeter { - string name; - uint256 age; - - event Greet(string greet); - - function greeting(string memory _name) public returns (string memory) { - name = _name; - string memory greet = string(abi.encodePacked("Hello ", _name)); - emit Greet(greet); - return greet; - } - - function setAge(uint256 _age) external { - age = _age; - } -} - -contract Deploy is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - Greeter greeter; - string greeting; - - function run() external { - vm.startBroadcast(); - greeter = new Greeter(); - greeting = greeter.greeting("john"); - greeter.setAge(123); - vm.stopBroadcast(); - } -} diff --git a/testdata/default/spec/ShanghaiCompat.t.sol b/testdata/default/spec/ShanghaiCompat.t.sol index fd7213b3d0702..7ff6405d16ccf 100644 --- a/testdata/default/spec/ShanghaiCompat.t.sol +++ b/testdata/default/spec/ShanghaiCompat.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShanghaiCompat is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ShanghaiCompat is Test { function testPush0() public { address target = address(uint160(uint256(0xc4f3))); diff --git a/testdata/default/trace/ConflictingSignatures.t.sol b/testdata/default/trace/ConflictingSignatures.t.sol deleted file mode 100644 index c8b7066c7a2f1..0000000000000 --- a/testdata/default/trace/ConflictingSignatures.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ReturnsNothing { - function func() public pure {} -} - -contract ReturnsString { - function func() public pure returns (string memory) { - return "string"; - } -} - -contract ReturnsUint { - function func() public pure returns (uint256) { - return 1; - } -} - -contract ConflictingSignaturesTest is DSTest { - ReturnsNothing retsNothing; - ReturnsString retsString; - ReturnsUint retsUint; - - function setUp() public { - retsNothing = new ReturnsNothing(); - retsString = new ReturnsString(); - retsUint = new ReturnsUint(); - } - - /// Tests that traces are decoded properly when multiple - /// functions have the same 4byte signature, but different - /// return values. - function testTraceWithConflictingSignatures() public { - retsNothing.func(); - retsString.func(); - retsUint.func(); - } -} diff --git a/testdata/default/trace/Trace.t.sol b/testdata/default/trace/Trace.t.sol deleted file mode 100644 index 19af6dd7c9fe7..0000000000000 --- a/testdata/default/trace/Trace.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -pragma solidity ^0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract RecursiveCall { - TraceTest factory; - - event Depth(uint256 depth); - event ChildDepth(uint256 childDepth); - event CreatedChild(uint256 childDepth); - - constructor(address _factory) { - factory = TraceTest(_factory); - } - - function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { - if (depth == neededDepth) { - this.negativeNum(); - return neededDepth; - } - - uint256 childDepth = this.recurseCall(neededDepth, depth + 1); - emit ChildDepth(childDepth); - - this.someCall(); - emit Depth(depth); - - return depth; - } - - function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { - if (depth == neededDepth) { - return neededDepth; - } - - RecursiveCall child = factory.create(); - emit CreatedChild(depth + 1); - - uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); - emit ChildDepth(childDepth); - emit Depth(depth); - - return depth; - } - - function someCall() public pure {} - - function negativeNum() public pure returns (int256) { - return -1000000000; - } -} - -contract TraceTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - uint256 nodeId = 0; - RecursiveCall first; - - function setUp() public { - first = this.create(); - } - - function create() public returns (RecursiveCall) { - RecursiveCall node = new RecursiveCall(address(this)); - vm.label(address(node), string(abi.encodePacked("Node ", uintToString(nodeId++)))); - - return node; - } - - function testRecurseCall() public { - first.recurseCall(8, 0); - } - - function testRecurseCreate() public { - first.recurseCreate(8, 0); - } -} - -function uintToString(uint256 value) pure returns (string memory) { - // Taken from OpenZeppelin - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); -} diff --git a/testdata/default/vyper/CounterTest.vy b/testdata/default/vyper/CounterTest.vy index b6cc517d25dd6..960ec5cbaa830 100644 --- a/testdata/default/vyper/CounterTest.vy +++ b/testdata/default/vyper/CounterTest.vy @@ -1,4 +1,4 @@ -from . import ICounter +from src import ICounter interface Vm: def deployCode(artifact_name: String[1024], args: Bytes[1024] = b"") -> address: nonpayable @@ -8,7 +8,7 @@ counter: ICounter @external def setUp(): - self.counter = ICounter(extcall vm.deployCode("vyper/Counter.vy")) + self.counter = ICounter(extcall vm.deployCode("src/Counter.vy")) @external def test_increment(): diff --git a/testdata/fixtures/File/ignored/.gitignore b/testdata/fixtures/File/ignored/.gitignore new file mode 100644 index 0000000000000..9c558e357c416 --- /dev/null +++ b/testdata/fixtures/File/ignored/.gitignore @@ -0,0 +1 @@ +. diff --git a/testdata/fixtures/SolidityGeneration/Fastlane.json b/testdata/fixtures/SolidityGeneration/Fastlane.json deleted file mode 100644 index 9afbb8ab70a95..0000000000000 --- a/testdata/fixtures/SolidityGeneration/Fastlane.json +++ /dev/null @@ -1,1219 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "AuctionEnded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "AuctionStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "starter", - "type": "address" - } - ], - "name": "AuctionStarterSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint16", - "name": "batch_size", - "type": "uint16" - } - ], - "name": "AutopayBatchSizeSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "bidder", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "auction_number", - "type": "uint256" - } - ], - "name": "BidAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "BidTokenSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "FastLaneFeeSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - } - ], - "name": "MinimumAutoshipThresholdSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "MinimumBidIncrementSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "OpportunityAddressDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "OpportunityAddressEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "ops", - "type": "address" - } - ], - "name": "OpsSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "PausedStateSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - } - ], - "name": "ResolverMaxGasPriceSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "ValidatorAddressDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "ValidatorAddressEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "minAutoshipAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "validatorPayableAddress", - "type": "address" - } - ], - "name": "ValidatorPreferencesSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "destination", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "ValidatorWithdrawnBalance", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "WithdrawStuckERC20", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "WithdrawStuckNativeToken", - "type": "event" - }, - { - "inputs": [], - "name": "MAX_AUCTION_VALUE", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auctionStarter", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auction_live", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auction_number", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "autopay_batch_size", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bid_increment", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bid_token", - "outputs": [ - { - "internalType": "contract ERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "checker", - "outputs": [ - { - "internalType": "bool", - "name": "canExec", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "execPayload", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "disableOpportunityAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - } - ], - "name": "disableValidatorAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "enableOpportunityAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - } - ], - "name": "enableValidatorAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_minAutoshipAmount", - "type": "uint128" - }, - { - "internalType": "address", - "name": "_validatorPayableAddress", - "type": "address" - } - ], - "name": "enableValidatorAddressWithPreferences", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "endAuction", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "fast_lane_fee", - "outputs": [ - { - "internalType": "uint24", - "name": "", - "type": "uint24" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - }, - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findFinalizedAuctionWinnerAtAuction", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findLastFinalizedAuctionWinner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findLiveAuctionTopBid", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getActivePrivilegesAuctionNumber", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "batch_size", - "type": "uint16" - }, - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - } - ], - "name": "getAutopayJobs", - "outputs": [ - { - "internalType": "bool", - "name": "hasJobs", - "type": "bool" - }, - { - "internalType": "address[]", - "name": "autopayRecipients", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getCheckpoint", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "pendingBalanceAtlastBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "outstandingBalance", - "type": "uint256" - }, - { - "internalType": "uint128", - "name": "lastWithdrawnAuction", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "lastBidReceivedAuction", - "type": "uint128" - } - ], - "internalType": "struct ValidatorBalanceCheckpoint", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getPreferences", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "minAutoshipAmount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "validatorPayableAddress", - "type": "address" - } - ], - "internalType": "struct ValidatorPreferences", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getStatus", - "outputs": [ - { - "components": [ - { - "internalType": "uint128", - "name": "activeAtAuction", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "inactiveAtAuction", - "type": "uint128" - }, - { - "internalType": "enum statusType", - "name": "kind", - "type": "uint8" - } - ], - "internalType": "struct Status", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - } - ], - "name": "getValidatorsActiveAtAuction", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_initial_bid_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_ops", - "type": "address" - }, - { - "internalType": "address", - "name": "_starter", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "max_gas_price", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minAutoShipThreshold", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ops", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "outstandingFLBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "autopayRecipients", - "type": "address[]" - } - ], - "name": "processAutopayJobs", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "outstandingValidatorWithBalance", - "type": "address" - } - ], - "name": "redeemOutstandingBalance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "size", - "type": "uint16" - } - ], - "name": "setAutopayBatchSize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_bid_token_address", - "type": "address" - } - ], - "name": "setBidToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint24", - "name": "_fastLaneFee", - "type": "uint24" - } - ], - "name": "setFastlaneFee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_minAmount", - "type": "uint128" - } - ], - "name": "setMinimumAutoShipThreshold", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_bid_increment", - "type": "uint256" - } - ], - "name": "setMinimumBidIncrement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "setOffchainCheckerDisabledState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_ops", - "type": "address" - } - ], - "name": "setOps", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "setPausedState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_maxgas", - "type": "uint128" - } - ], - "name": "setResolverMaxGasPrice", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_starter", - "type": "address" - } - ], - "name": "setStarter", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_minAutoshipAmount", - "type": "uint128" - }, - { - "internalType": "address", - "name": "_validatorPayableAddress", - "type": "address" - } - ], - "name": "setValidatorPreferences", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "startAuction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "searcherContractAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "searcherPayableAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "bidAmount", - "type": "uint256" - } - ], - "internalType": "struct Bid", - "name": "bid", - "type": "tuple" - } - ], - "name": "submitBid", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - } - ], - "name": "withdrawStuckERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawStuckNativeToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/testdata/fixtures/SolidityGeneration/GaugeController.json b/testdata/fixtures/SolidityGeneration/GaugeController.json deleted file mode 100644 index 8abacee7477e4..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GaugeController.json +++ /dev/null @@ -1,471 +0,0 @@ -[ - { - "name": "CommitOwnership", - "inputs": [{ "name": "admin", "type": "address", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "name": "ApplyOwnership", - "inputs": [{ "name": "admin", "type": "address", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "name": "AddType", - "inputs": [ - { "name": "name", "type": "string", "indexed": false }, - { "name": "type_id", "type": "int128", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "NewTypeWeight", - "inputs": [ - { "name": "type_id", "type": "int128", "indexed": false }, - { "name": "time", "type": "uint256", "indexed": false }, - { "name": "weight", "type": "uint256", "indexed": false }, - { "name": "total_weight", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "NewGaugeWeight", - "inputs": [ - { "name": "gauge_address", "type": "address", "indexed": false }, - { "name": "time", "type": "uint256", "indexed": false }, - { "name": "weight", "type": "uint256", "indexed": false }, - { "name": "total_weight", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "VoteForGauge", - "inputs": [ - { "name": "time", "type": "uint256", "indexed": false }, - { "name": "user", "type": "address", "indexed": false }, - { "name": "gauge_addr", "type": "address", "indexed": false }, - { "name": "weight", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "NewGauge", - "inputs": [ - { "name": "addr", "type": "address", "indexed": false }, - { "name": "gauge_type", "type": "int128", "indexed": false }, - { "name": "weight", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "stateMutability": "nonpayable", - "type": "constructor", - "inputs": [ - { "name": "_token", "type": "address" }, - { "name": "_voting_escrow", "type": "address" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "commit_transfer_ownership", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [], - "gas": 39445 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "apply_transfer_ownership", - "inputs": [], - "outputs": [], - "gas": 41536 - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_corrected_info", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [ - { - "name": "", - "type": "tuple", - "components": [ - { "name": "bias", "type": "uint256" }, - { "name": "slope", "type": "uint256" }, - { "name": "lock_end", "type": "uint256" }, - { "name": "fxs_amount", "type": "uint256" } - ] - } - ], - "gas": 8550 - }, - { - "stateMutability": "view", - "type": "function", - "name": "gauge_types", - "inputs": [{ "name": "_addr", "type": "address" }], - "outputs": [{ "name": "", "type": "int128" }], - "gas": 3105 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "add_gauge", - "inputs": [ - { "name": "addr", "type": "address" }, - { "name": "gauge_type", "type": "int128" }, - { "name": "weight", "type": "uint256" } - ], - "outputs": [], - "gas": 9355936493 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "checkpoint", - "inputs": [], - "outputs": [], - "gas": 9265868764 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "checkpoint_gauge", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [], - "gas": 9320254139 - }, - { - "stateMutability": "view", - "type": "function", - "name": "gauge_relative_weight", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 11293 - }, - { - "stateMutability": "view", - "type": "function", - "name": "gauge_relative_weight", - "inputs": [ - { "name": "addr", "type": "address" }, - { "name": "time", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 11293 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "gauge_relative_weight_write", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 9320264858 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "gauge_relative_weight_write", - "inputs": [ - { "name": "addr", "type": "address" }, - { "name": "time", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 9320264858 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "add_type", - "inputs": [ - { "name": "_name", "type": "string" }, - { "name": "weight", "type": "uint256" } - ], - "outputs": [], - "gas": 9355872406 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "change_type_weight", - "inputs": [ - { "name": "type_id", "type": "int128" }, - { "name": "weight", "type": "uint256" } - ], - "outputs": [], - "gas": 9355717527 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "change_gauge_weight", - "inputs": [ - { "name": "addr", "type": "address" }, - { "name": "weight", "type": "uint256" } - ], - "outputs": [], - "gas": 9410175915 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "vote_for_gauge_weights", - "inputs": [ - { "name": "_gauge_addr", "type": "address" }, - { "name": "_user_weight", "type": "uint256" } - ], - "outputs": [], - "gas": 9375126614 - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_gauge_weight", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 5443 - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_type_weight", - "inputs": [{ "name": "type_id", "type": "int128" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 5413 - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_total_weight", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 5122 - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_weights_sum_per_type", - "inputs": [{ "name": "type_id", "type": "int128" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 5473 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "change_global_emission_rate", - "inputs": [{ "name": "new_rate", "type": "uint256" }], - "outputs": [], - "gas": 37964 - }, - { - "stateMutability": "view", - "type": "function", - "name": "admin", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3000 - }, - { - "stateMutability": "view", - "type": "function", - "name": "future_admin", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3030 - }, - { - "stateMutability": "view", - "type": "function", - "name": "token", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3060 - }, - { - "stateMutability": "view", - "type": "function", - "name": "voting_escrow", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3090 - }, - { - "stateMutability": "view", - "type": "function", - "name": "n_gauge_types", - "inputs": [], - "outputs": [{ "name": "", "type": "int128" }], - "gas": 3120 - }, - { - "stateMutability": "view", - "type": "function", - "name": "n_gauges", - "inputs": [], - "outputs": [{ "name": "", "type": "int128" }], - "gas": 3150 - }, - { - "stateMutability": "view", - "type": "function", - "name": "gauge_type_names", - "inputs": [{ "name": "arg0", "type": "int128" }], - "outputs": [{ "name": "", "type": "string" }], - "gas": 13756 - }, - { - "stateMutability": "view", - "type": "function", - "name": "gauges", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3255 - }, - { - "stateMutability": "view", - "type": "function", - "name": "vote_user_slopes", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "outputs": [ - { - "name": "", - "type": "tuple", - "components": [ - { "name": "slope", "type": "uint256" }, - { "name": "power", "type": "uint256" }, - { "name": "end", "type": "uint256" } - ] - } - ], - "gas": 8045 - }, - { - "stateMutability": "view", - "type": "function", - "name": "vote_user_power", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3536 - }, - { - "stateMutability": "view", - "type": "function", - "name": "last_user_vote", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3832 - }, - { - "stateMutability": "view", - "type": "function", - "name": "points_weight", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "uint256" } - ], - "outputs": [ - { - "name": "", - "type": "tuple", - "components": [ - { "name": "bias", "type": "uint256" }, - { "name": "slope", "type": "uint256" } - ] - } - ], - "gas": 5868 - }, - { - "stateMutability": "view", - "type": "function", - "name": "time_weight", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3626 - }, - { - "stateMutability": "view", - "type": "function", - "name": "points_sum", - "inputs": [ - { "name": "arg0", "type": "int128" }, - { "name": "arg1", "type": "uint256" } - ], - "outputs": [ - { - "name": "", - "type": "tuple", - "components": [ - { "name": "bias", "type": "uint256" }, - { "name": "slope", "type": "uint256" } - ] - } - ], - "gas": 5938 - }, - { - "stateMutability": "view", - "type": "function", - "name": "time_sum", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3465 - }, - { - "stateMutability": "view", - "type": "function", - "name": "points_total", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3565 - }, - { - "stateMutability": "view", - "type": "function", - "name": "time_total", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3480 - }, - { - "stateMutability": "view", - "type": "function", - "name": "points_type_weight", - "inputs": [ - { "name": "arg0", "type": "int128" }, - { "name": "arg1", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3901 - }, - { - "stateMutability": "view", - "type": "function", - "name": "time_type_weight", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3585 - }, - { - "stateMutability": "view", - "type": "function", - "name": "global_emission_rate", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3570 - } -] diff --git a/testdata/fixtures/SolidityGeneration/GeneratedFastLane.sol b/testdata/fixtures/SolidityGeneration/GeneratedFastLane.sol deleted file mode 100644 index 9c48395be8d59..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GeneratedFastLane.sol +++ /dev/null @@ -1,130 +0,0 @@ -interface test { - event AuctionEnded(uint128 indexed auction_number); - event AuctionStarted(uint128 indexed auction_number); - event AuctionStarterSet(address indexed starter); - event AutopayBatchSizeSet(uint16 batch_size); - event BidAdded( - address bidder, - address indexed validator, - address indexed opportunity, - uint256 amount, - uint256 indexed auction_number - ); - event BidTokenSet(address indexed token); - event FastLaneFeeSet(uint256 amount); - event MinimumAutoshipThresholdSet(uint128 amount); - event MinimumBidIncrementSet(uint256 amount); - event OpportunityAddressDisabled(address indexed opportunity, uint128 indexed auction_number); - event OpportunityAddressEnabled(address indexed opportunity, uint128 indexed auction_number); - event OpsSet(address ops); - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event PausedStateSet(bool state); - event ResolverMaxGasPriceSet(uint128 amount); - event ValidatorAddressDisabled(address indexed validator, uint128 indexed auction_number); - event ValidatorAddressEnabled(address indexed validator, uint128 indexed auction_number); - event ValidatorPreferencesSet( - address indexed validator, uint256 minAutoshipAmount, address validatorPayableAddress - ); - event ValidatorWithdrawnBalance( - address indexed validator, - uint128 indexed auction_number, - uint256 amount, - address destination, - address indexed caller - ); - event WithdrawStuckERC20(address indexed receiver, address indexed token, uint256 amount); - event WithdrawStuckNativeToken(address indexed receiver, uint256 amount); - - struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; - } - - struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - uint8 kind; - } - - struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; - } - - struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; - } - - function MAX_AUCTION_VALUE() external view returns (uint128); - function auctionStarter() external view returns (address); - function auction_live() external view returns (bool); - function auction_number() external view returns (uint128); - function autopay_batch_size() external view returns (uint16); - function bid_increment() external view returns (uint256); - function bid_token() external view returns (address); - function checker() external view returns (bool canExec, bytes memory execPayload); - function disableOpportunityAddress(address opportunityAddress) external; - function disableValidatorAddress(address _validatorAddress) external; - function enableOpportunityAddress(address opportunityAddress) external; - function enableValidatorAddress(address _validatorAddress) external; - function enableValidatorAddressWithPreferences( - address _validatorAddress, - uint128 _minAutoshipAmount, - address _validatorPayableAddress - ) external; - function endAuction() external returns (bool); - function fast_lane_fee() external view returns (uint24); - function findFinalizedAuctionWinnerAtAuction( - uint128 auction_index, - address validatorAddress, - address opportunityAddress - ) external view returns (bool, address, uint128); - function findLastFinalizedAuctionWinner(address validatorAddress, address opportunityAddress) - external - view - returns (bool, address, uint128); - function findLiveAuctionTopBid(address validatorAddress, address opportunityAddress) - external - view - returns (uint256, uint128); - function getActivePrivilegesAuctionNumber() external view returns (uint128); - function getAutopayJobs(uint16 batch_size, uint128 auction_index) - external - view - returns (bool hasJobs, address[] memory autopayRecipients); - function getCheckpoint(address who) external view returns (ValidatorBalanceCheckpoint memory); - function getPreferences(address who) external view returns (ValidatorPreferences memory); - function getStatus(address who) external view returns (Status memory); - function getValidatorsActiveAtAuction(uint128 auction_index) external view returns (address[] memory); - function init(address _initial_bid_token, address _ops, address _starter) external; - function max_gas_price() external view returns (uint128); - function minAutoShipThreshold() external view returns (uint128); - function ops() external view returns (address); - function outstandingFLBalance() external view returns (uint256); - function owner() external view returns (address); - function processAutopayJobs(address[] memory autopayRecipients) external; - function redeemOutstandingBalance(address outstandingValidatorWithBalance) external; - function renounceOwnership() external; - function setAutopayBatchSize(uint16 size) external; - function setBidToken(address _bid_token_address) external; - function setFastlaneFee(uint24 _fastLaneFee) external; - function setMinimumAutoShipThreshold(uint128 _minAmount) external; - function setMinimumBidIncrement(uint256 _bid_increment) external; - function setOffchainCheckerDisabledState(bool state) external; - function setOps(address _ops) external; - function setPausedState(bool state) external; - function setResolverMaxGasPrice(uint128 _maxgas) external; - function setStarter(address _starter) external; - function setValidatorPreferences(uint128 _minAutoshipAmount, address _validatorPayableAddress) external; - function startAuction() external; - function submitBid(Bid memory bid) external; - function transferOwnership(address newOwner) external; - function withdrawStuckERC20(address _tokenAddress) external; - function withdrawStuckNativeToken(uint256 amount) external; -} diff --git a/testdata/fixtures/SolidityGeneration/GeneratedGaugeController.sol b/testdata/fixtures/SolidityGeneration/GeneratedGaugeController.sol deleted file mode 100644 index 26f0b4f82dc7e..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GeneratedGaugeController.sol +++ /dev/null @@ -1,50 +0,0 @@ -interface test { - event AddType(string name, int128 type_id); - event ApplyOwnership(address admin); - event CommitOwnership(address admin); - event NewGauge(address addr, int128 gauge_type, uint256 weight); - event NewGaugeWeight(address gauge_address, uint256 time, uint256 weight, uint256 total_weight); - event NewTypeWeight(int128 type_id, uint256 time, uint256 weight, uint256 total_weight); - event VoteForGauge(uint256 time, address user, address gauge_addr, uint256 weight); - - function add_gauge(address addr, int128 gauge_type, uint256 weight) external; - function add_type(string memory _name, uint256 weight) external; - function admin() external view returns (address); - function apply_transfer_ownership() external; - function change_gauge_weight(address addr, uint256 weight) external; - function change_global_emission_rate(uint256 new_rate) external; - function change_type_weight(int128 type_id, uint256 weight) external; - function checkpoint() external; - function checkpoint_gauge(address addr) external; - function commit_transfer_ownership(address addr) external; - function future_admin() external view returns (address); - function gauge_relative_weight(address addr) external view returns (uint256); - function gauge_relative_weight(address addr, uint256 time) external view returns (uint256); - function gauge_relative_weight_write(address addr) external returns (uint256); - function gauge_relative_weight_write(address addr, uint256 time) external returns (uint256); - function gauge_type_names(int128 arg0) external view returns (string memory); - function gauge_types(address _addr) external view returns (int128); - function gauges(uint256 arg0) external view returns (address); - function get_corrected_info(address addr) external view returns (uint256, uint256, uint256, uint256); - function get_gauge_weight(address addr) external view returns (uint256); - function get_total_weight() external view returns (uint256); - function get_type_weight(int128 type_id) external view returns (uint256); - function get_weights_sum_per_type(int128 type_id) external view returns (uint256); - function global_emission_rate() external view returns (uint256); - function last_user_vote(address arg0, address arg1) external view returns (uint256); - function n_gauge_types() external view returns (int128); - function n_gauges() external view returns (int128); - function points_sum(int128 arg0, uint256 arg1) external view returns (uint256, uint256); - function points_total(uint256 arg0) external view returns (uint256); - function points_type_weight(int128 arg0, uint256 arg1) external view returns (uint256); - function points_weight(address arg0, uint256 arg1) external view returns (uint256, uint256); - function time_sum(uint256 arg0) external view returns (uint256); - function time_total() external view returns (uint256); - function time_type_weight(uint256 arg0) external view returns (uint256); - function time_weight(address arg0) external view returns (uint256); - function token() external view returns (address); - function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external; - function vote_user_power(address arg0) external view returns (uint256); - function vote_user_slopes(address arg0, address arg1) external view returns (uint256, uint256, uint256); - function voting_escrow() external view returns (address); -} diff --git a/testdata/fixtures/SolidityGeneration/GeneratedLiquidityGaugeV4.sol b/testdata/fixtures/SolidityGeneration/GeneratedLiquidityGaugeV4.sol deleted file mode 100644 index 10716115176d4..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GeneratedLiquidityGaugeV4.sol +++ /dev/null @@ -1,79 +0,0 @@ -interface test { - event ApplyOwnership(address admin); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - event CommitOwnership(address admin); - event Deposit(address indexed provider, uint256 value); - event RewardDataUpdate(address indexed _token, uint256 _amount); - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event UpdateLiquidityLimit( - address user, uint256 original_balance, uint256 original_supply, uint256 working_balance, uint256 working_supply - ); - event Withdraw(address indexed provider, uint256 value); - - function SDT() external view returns (address); - function accept_transfer_ownership() external; - function add_reward(address _reward_token, address _distributor) external; - function admin() external view returns (address); - function allowance(address arg0, address arg1) external view returns (uint256); - function approve(address _spender, uint256 _value) external returns (bool); - function balanceOf(address arg0) external view returns (uint256); - function claim_rewards() external; - function claim_rewards(address _addr) external; - function claim_rewards(address _addr, address _receiver) external; - function claim_rewards_for(address _addr, address _receiver) external; - function claimable_reward(address _user, address _reward_token) external view returns (uint256); - function claimed_reward(address _addr, address _token) external view returns (uint256); - function claimer() external view returns (address); - function commit_transfer_ownership(address addr) external; - function decimal_staking_token() external view returns (uint256); - function decimals() external view returns (uint256); - function decreaseAllowance(address _spender, uint256 _subtracted_value) external returns (bool); - function deposit(uint256 _value) external; - function deposit(uint256 _value, address _addr) external; - function deposit(uint256 _value, address _addr, bool _claim_rewards) external; - function deposit_reward_token(address _reward_token, uint256 _amount) external; - function future_admin() external view returns (address); - function increaseAllowance(address _spender, uint256 _added_value) external returns (bool); - function initialize( - address _staking_token, - address _admin, - address _SDT, - address _voting_escrow, - address _veBoost_proxy, - address _distributor - ) external; - function initialized() external view returns (bool); - function integrate_checkpoint_of(address arg0) external view returns (uint256); - function kick(address addr) external; - function name() external view returns (string memory); - function reward_count() external view returns (uint256); - function reward_data(address arg0) - external - view - returns ( - address token, - address distributor, - uint256 period_finish, - uint256 rate, - uint256 last_update, - uint256 integral - ); - function reward_integral_for(address arg0, address arg1) external view returns (uint256); - function reward_tokens(uint256 arg0) external view returns (address); - function rewards_receiver(address arg0) external view returns (address); - function set_claimer(address _claimer) external; - function set_reward_distributor(address _reward_token, address _distributor) external; - function set_rewards_receiver(address _receiver) external; - function staking_token() external view returns (address); - function symbol() external view returns (string memory); - function totalSupply() external view returns (uint256); - function transfer(address _to, uint256 _value) external returns (bool); - function transferFrom(address _from, address _to, uint256 _value) external returns (bool); - function user_checkpoint(address addr) external returns (bool); - function veBoost_proxy() external view returns (address); - function voting_escrow() external view returns (address); - function withdraw(uint256 _value) external; - function withdraw(uint256 _value, bool _claim_rewards) external; - function working_balances(address arg0) external view returns (uint256); - function working_supply() external view returns (uint256); -} diff --git a/testdata/fixtures/SolidityGeneration/GeneratedNamedInterface.sol b/testdata/fixtures/SolidityGeneration/GeneratedNamedInterface.sol deleted file mode 100644 index bc1f6d713c171..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GeneratedNamedInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -interface test { - event Bar(address indexed x); - event Foo(address x); - - function guess(uint8 n, address x) external payable; - function isComplete() external view returns (bool example, string memory); -} diff --git a/testdata/fixtures/SolidityGeneration/GeneratedUnnamedInterface.sol b/testdata/fixtures/SolidityGeneration/GeneratedUnnamedInterface.sol deleted file mode 100644 index cb547ef3d3f1b..0000000000000 --- a/testdata/fixtures/SolidityGeneration/GeneratedUnnamedInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -interface Interface { - event Bar(address indexed x); - event Foo(address x); - - function guess(uint8 n, address x) external payable; - function isComplete() external view returns (bool example, string memory); -} diff --git a/testdata/fixtures/SolidityGeneration/InterfaceABI.json b/testdata/fixtures/SolidityGeneration/InterfaceABI.json deleted file mode 100644 index 410dfbfe7a43f..0000000000000 --- a/testdata/fixtures/SolidityGeneration/InterfaceABI.json +++ /dev/null @@ -1,64 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "x", - "type": "address" - } - ], - "name": "Foo", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "x", - "type": "address" - } - ], - "name": "Bar", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "n", - "type": "uint8" - }, - { - "internalType": "address", - "name": "x", - "type": "address" - } - ], - "name": "guess", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "isComplete", - "outputs": [ - { - "internalType": "bool", - "name": "example", - "type": "bool" - }, - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/testdata/fixtures/SolidityGeneration/LiquidityGaugeV4.json b/testdata/fixtures/SolidityGeneration/LiquidityGaugeV4.json deleted file mode 100644 index 8107dcf05aae9..0000000000000 --- a/testdata/fixtures/SolidityGeneration/LiquidityGaugeV4.json +++ /dev/null @@ -1,530 +0,0 @@ -[ - { - "name": "Deposit", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Withdraw", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "UpdateLiquidityLimit", - "inputs": [ - { "name": "user", "type": "address", "indexed": false }, - { "name": "original_balance", "type": "uint256", "indexed": false }, - { "name": "original_supply", "type": "uint256", "indexed": false }, - { "name": "working_balance", "type": "uint256", "indexed": false }, - { "name": "working_supply", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "CommitOwnership", - "inputs": [{ "name": "admin", "type": "address", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "name": "ApplyOwnership", - "inputs": [{ "name": "admin", "type": "address", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "name": "Transfer", - "inputs": [ - { "name": "_from", "type": "address", "indexed": true }, - { "name": "_to", "type": "address", "indexed": true }, - { "name": "_value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Approval", - "inputs": [ - { "name": "_owner", "type": "address", "indexed": true }, - { "name": "_spender", "type": "address", "indexed": true }, - { "name": "_value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RewardDataUpdate", - "inputs": [ - { "name": "_token", "type": "address", "indexed": true }, - { "name": "_amount", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "stateMutability": "nonpayable", - "type": "constructor", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "initialize", - "inputs": [ - { "name": "_staking_token", "type": "address" }, - { "name": "_admin", "type": "address" }, - { "name": "_SDT", "type": "address" }, - { "name": "_voting_escrow", "type": "address" }, - { "name": "_veBoost_proxy", "type": "address" }, - { "name": "_distributor", "type": "address" } - ], - "outputs": [], - "gas": 548461 - }, - { - "stateMutability": "view", - "type": "function", - "name": "decimals", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 2418 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "user_checkpoint", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 3493693 - }, - { - "stateMutability": "view", - "type": "function", - "name": "claimed_reward", - "inputs": [ - { "name": "_addr", "type": "address" }, - { "name": "_token", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 2976 - }, - { - "stateMutability": "view", - "type": "function", - "name": "claimable_reward", - "inputs": [ - { "name": "_user", "type": "address" }, - { "name": "_reward_token", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 26704 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "set_rewards_receiver", - "inputs": [{ "name": "_receiver", "type": "address" }], - "outputs": [], - "gas": 35613 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "claim_rewards", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "claim_rewards", - "inputs": [{ "name": "_addr", "type": "address" }], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "claim_rewards", - "inputs": [ - { "name": "_addr", "type": "address" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "claim_rewards_for", - "inputs": [ - { "name": "_addr", "type": "address" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [], - "gas": 3464244 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "kick", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [], - "gas": 3514487 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "deposit", - "inputs": [{ "name": "_value", "type": "uint256" }], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "deposit", - "inputs": [ - { "name": "_value", "type": "uint256" }, - { "name": "_addr", "type": "address" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "deposit", - "inputs": [ - { "name": "_value", "type": "uint256" }, - { "name": "_addr", "type": "address" }, - { "name": "_claim_rewards", "type": "bool" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "withdraw", - "inputs": [{ "name": "_value", "type": "uint256" }], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "withdraw", - "inputs": [ - { "name": "_value", "type": "uint256" }, - { "name": "_claim_rewards", "type": "bool" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "transfer", - "inputs": [ - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 14116366 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "transferFrom", - "inputs": [ - { "name": "_from", "type": "address" }, - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 14154316 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "approve", - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 39421 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "increaseAllowance", - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_added_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 41965 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "decreaseAllowance", - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_subtracted_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 41989 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "add_reward", - "inputs": [ - { "name": "_reward_token", "type": "address" }, - { "name": "_distributor", "type": "address" } - ], - "outputs": [], - "gas": 113003 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "set_reward_distributor", - "inputs": [ - { "name": "_reward_token", "type": "address" }, - { "name": "_distributor", "type": "address" } - ], - "outputs": [], - "gas": 40753 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "set_claimer", - "inputs": [{ "name": "_claimer", "type": "address" }], - "outputs": [], - "gas": 38182 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "deposit_reward_token", - "inputs": [ - { "name": "_reward_token", "type": "address" }, - { "name": "_amount", "type": "uint256" } - ], - "outputs": [], - "gas": 3585430 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "commit_transfer_ownership", - "inputs": [{ "name": "addr", "type": "address" }], - "outputs": [], - "gas": 40142 - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "accept_transfer_ownership", - "inputs": [], - "outputs": [], - "gas": 39990 - }, - { - "stateMutability": "view", - "type": "function", - "name": "SDT", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3048 - }, - { - "stateMutability": "view", - "type": "function", - "name": "voting_escrow", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3078 - }, - { - "stateMutability": "view", - "type": "function", - "name": "veBoost_proxy", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3108 - }, - { - "stateMutability": "view", - "type": "function", - "name": "staking_token", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3138 - }, - { - "stateMutability": "view", - "type": "function", - "name": "decimal_staking_token", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3168 - }, - { - "stateMutability": "view", - "type": "function", - "name": "balanceOf", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3413 - }, - { - "stateMutability": "view", - "type": "function", - "name": "totalSupply", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3228 - }, - { - "stateMutability": "view", - "type": "function", - "name": "allowance", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3688 - }, - { - "stateMutability": "view", - "type": "function", - "name": "name", - "inputs": [], - "outputs": [{ "name": "", "type": "string" }], - "gas": 13518 - }, - { - "stateMutability": "view", - "type": "function", - "name": "symbol", - "inputs": [], - "outputs": [{ "name": "", "type": "string" }], - "gas": 11271 - }, - { - "stateMutability": "view", - "type": "function", - "name": "working_balances", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3563 - }, - { - "stateMutability": "view", - "type": "function", - "name": "working_supply", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3378 - }, - { - "stateMutability": "view", - "type": "function", - "name": "integrate_checkpoint_of", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3623 - }, - { - "stateMutability": "view", - "type": "function", - "name": "reward_count", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3438 - }, - { - "stateMutability": "view", - "type": "function", - "name": "reward_tokens", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3513 - }, - { - "stateMutability": "view", - "type": "function", - "name": "reward_data", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [ - { "name": "token", "type": "address" }, - { "name": "distributor", "type": "address" }, - { "name": "period_finish", "type": "uint256" }, - { "name": "rate", "type": "uint256" }, - { "name": "last_update", "type": "uint256" }, - { "name": "integral", "type": "uint256" } - ], - "gas": 14943 - }, - { - "stateMutability": "view", - "type": "function", - "name": "rewards_receiver", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3743 - }, - { - "stateMutability": "view", - "type": "function", - "name": "reward_integral_for", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }], - "gas": 3988 - }, - { - "stateMutability": "view", - "type": "function", - "name": "admin", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3588 - }, - { - "stateMutability": "view", - "type": "function", - "name": "future_admin", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3618 - }, - { - "stateMutability": "view", - "type": "function", - "name": "claimer", - "inputs": [], - "outputs": [{ "name": "", "type": "address" }], - "gas": 3648 - }, - { - "stateMutability": "view", - "type": "function", - "name": "initialized", - "inputs": [], - "outputs": [{ "name": "", "type": "bool" }], - "gas": 3678 - } -] diff --git a/testdata/fixtures/SolidityGeneration/WithStructs.json b/testdata/fixtures/SolidityGeneration/WithStructs.json deleted file mode 100644 index cbea1b413558a..0000000000000 --- a/testdata/fixtures/SolidityGeneration/WithStructs.json +++ /dev/null @@ -1,1293 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "AuctionEnded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "AuctionStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "starter", - "type": "address" - } - ], - "name": "AuctionStarterSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint16", - "name": "batch_size", - "type": "uint16" - } - ], - "name": "AutopayBatchSizeSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "bidder", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "auction_number", - "type": "uint256" - } - ], - "name": "BidAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "BidTokenSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "FastLaneFeeSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - } - ], - "name": "MinimumAutoshipThresholdSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "MinimumBidIncrementSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "OpportunityAddressDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "opportunity", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "OpportunityAddressEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "ops", - "type": "address" - } - ], - "name": "OpsSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "PausedStateSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - } - ], - "name": "ResolverMaxGasPriceSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "ValidatorAddressDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - } - ], - "name": "ValidatorAddressEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "minAutoshipAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "validatorPayableAddress", - "type": "address" - } - ], - "name": "ValidatorPreferencesSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint128", - "name": "auction_number", - "type": "uint128" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "destination", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "ValidatorWithdrawnBalance", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "WithdrawStuckERC20", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "WithdrawStuckNativeToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "implem", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "internalType": "struct NFToken", - "name": "token", - "type": "tuple" - } - ], - "name": "NFTokenAdded", - "type": "event" - }, - { - "inputs": [], - "name": "MAX_AUCTION_VALUE", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auctionStarter", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auction_live", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "auction_number", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "autopay_batch_size", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bid_increment", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bid_token", - "outputs": [ - { - "internalType": "contract ERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "checker", - "outputs": [ - { - "internalType": "bool", - "name": "canExec", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "execPayload", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "disableOpportunityAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - } - ], - "name": "disableValidatorAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "enableOpportunityAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - } - ], - "name": "enableValidatorAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorAddress", - "type": "address" - }, - { - "internalType": "uint128", - "name": "_minAutoshipAmount", - "type": "uint128" - }, - { - "internalType": "address", - "name": "_validatorPayableAddress", - "type": "address" - } - ], - "name": "enableValidatorAddressWithPreferences", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "endAuction", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "fast_lane_fee", - "outputs": [ - { - "internalType": "uint24", - "name": "", - "type": "uint24" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - }, - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findFinalizedAuctionWinnerAtAuction", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findLastFinalizedAuctionWinner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - } - ], - "name": "findLiveAuctionTopBid", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getActivePrivilegesAuctionNumber", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "batch_size", - "type": "uint16" - }, - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - } - ], - "name": "getAutopayJobs", - "outputs": [ - { - "internalType": "bool", - "name": "hasJobs", - "type": "bool" - }, - { - "internalType": "address[]", - "name": "autopayRecipients", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getCheckpoint", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "pendingBalanceAtlastBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "outstandingBalance", - "type": "uint256" - }, - { - "internalType": "uint128", - "name": "lastWithdrawnAuction", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "lastBidReceivedAuction", - "type": "uint128" - } - ], - "internalType": "struct ValidatorBalanceCheckpoint", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getPreferences", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "minAutoshipAmount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "validatorPayableAddress", - "type": "address" - } - ], - "internalType": "struct ValidatorPreferences", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "getStatus", - "outputs": [ - { - "components": [ - { - "internalType": "uint128", - "name": "activeAtAuction", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "inactiveAtAuction", - "type": "uint128" - }, - { - "internalType": "enum statusType", - "name": "kind", - "type": "uint8" - } - ], - "internalType": "struct Status", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "auction_index", - "type": "uint128" - } - ], - "name": "getValidatorsActiveAtAuction", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_initial_bid_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_ops", - "type": "address" - }, - { - "internalType": "address", - "name": "_starter", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "max_gas_price", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minAutoShipThreshold", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ops", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "outstandingFLBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "autopayRecipients", - "type": "address[]" - } - ], - "name": "processAutopayJobs", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "outstandingValidatorWithBalance", - "type": "address" - } - ], - "name": "redeemOutstandingBalance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "size", - "type": "uint16" - } - ], - "name": "setAutopayBatchSize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_bid_token_address", - "type": "address" - } - ], - "name": "setBidToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint24", - "name": "_fastLaneFee", - "type": "uint24" - } - ], - "name": "setFastlaneFee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_minAmount", - "type": "uint128" - } - ], - "name": "setMinimumAutoShipThreshold", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_bid_increment", - "type": "uint256" - } - ], - "name": "setMinimumBidIncrement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "setOffchainCheckerDisabledState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_ops", - "type": "address" - } - ], - "name": "setOps", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "state", - "type": "bool" - } - ], - "name": "setPausedState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_maxgas", - "type": "uint128" - } - ], - "name": "setResolverMaxGasPrice", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_starter", - "type": "address" - } - ], - "name": "setStarter", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "_minAutoshipAmount", - "type": "uint128" - }, - { - "internalType": "address", - "name": "_validatorPayableAddress", - "type": "address" - } - ], - "name": "setValidatorPreferences", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "startAuction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "validatorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "opportunityAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "searcherContractAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "searcherPayableAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "bidAmount", - "type": "uint256" - } - ], - "internalType": "struct Bid", - "name": "bid", - "type": "tuple" - } - ], - "name": "submitBid", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - } - ], - "name": "withdrawStuckERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawStuckNativeToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "implem", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "internalType": "struct NFToken", - "name": "nft", - "type": "tuple" - } - ], - "name": "storeNFToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "viewNFToken", - "outputs": [ - { - "components": [ - { - "internalType": "address", - "name": "implem", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "internalType": "struct NFToken", - "name": "nft", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/testdata/fixtures/SolidityGeneration/WithStructs.sol b/testdata/fixtures/SolidityGeneration/WithStructs.sol deleted file mode 100644 index bf21c5f9e8225..0000000000000 --- a/testdata/fixtures/SolidityGeneration/WithStructs.sol +++ /dev/null @@ -1,138 +0,0 @@ -interface test { - event AuctionEnded(uint128 indexed auction_number); - event AuctionStarted(uint128 indexed auction_number); - event AuctionStarterSet(address indexed starter); - event AutopayBatchSizeSet(uint16 batch_size); - event BidAdded( - address bidder, - address indexed validator, - address indexed opportunity, - uint256 amount, - uint256 indexed auction_number - ); - event BidTokenSet(address indexed token); - event FastLaneFeeSet(uint256 amount); - event MinimumAutoshipThresholdSet(uint128 amount); - event MinimumBidIncrementSet(uint256 amount); - event NFTokenAdded(NFToken token); - event OpportunityAddressDisabled(address indexed opportunity, uint128 indexed auction_number); - event OpportunityAddressEnabled(address indexed opportunity, uint128 indexed auction_number); - event OpsSet(address ops); - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event PausedStateSet(bool state); - event ResolverMaxGasPriceSet(uint128 amount); - event ValidatorAddressDisabled(address indexed validator, uint128 indexed auction_number); - event ValidatorAddressEnabled(address indexed validator, uint128 indexed auction_number); - event ValidatorPreferencesSet( - address indexed validator, uint256 minAutoshipAmount, address validatorPayableAddress - ); - event ValidatorWithdrawnBalance( - address indexed validator, - uint128 indexed auction_number, - uint256 amount, - address destination, - address indexed caller - ); - event WithdrawStuckERC20(address indexed receiver, address indexed token, uint256 amount); - event WithdrawStuckNativeToken(address indexed receiver, uint256 amount); - - struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; - } - - struct NFToken { - address implem; - uint256 id; - } - - struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - uint8 kind; - } - - struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; - } - - struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; - } - - function MAX_AUCTION_VALUE() external view returns (uint128); - function auctionStarter() external view returns (address); - function auction_live() external view returns (bool); - function auction_number() external view returns (uint128); - function autopay_batch_size() external view returns (uint16); - function bid_increment() external view returns (uint256); - function bid_token() external view returns (address); - function checker() external view returns (bool canExec, bytes memory execPayload); - function disableOpportunityAddress(address opportunityAddress) external; - function disableValidatorAddress(address _validatorAddress) external; - function enableOpportunityAddress(address opportunityAddress) external; - function enableValidatorAddress(address _validatorAddress) external; - function enableValidatorAddressWithPreferences( - address _validatorAddress, - uint128 _minAutoshipAmount, - address _validatorPayableAddress - ) external; - function endAuction() external returns (bool); - function fast_lane_fee() external view returns (uint24); - function findFinalizedAuctionWinnerAtAuction( - uint128 auction_index, - address validatorAddress, - address opportunityAddress - ) external view returns (bool, address, uint128); - function findLastFinalizedAuctionWinner(address validatorAddress, address opportunityAddress) - external - view - returns (bool, address, uint128); - function findLiveAuctionTopBid(address validatorAddress, address opportunityAddress) - external - view - returns (uint256, uint128); - function getActivePrivilegesAuctionNumber() external view returns (uint128); - function getAutopayJobs(uint16 batch_size, uint128 auction_index) - external - view - returns (bool hasJobs, address[] memory autopayRecipients); - function getCheckpoint(address who) external view returns (ValidatorBalanceCheckpoint memory); - function getPreferences(address who) external view returns (ValidatorPreferences memory); - function getStatus(address who) external view returns (Status memory); - function getValidatorsActiveAtAuction(uint128 auction_index) external view returns (address[] memory); - function init(address _initial_bid_token, address _ops, address _starter) external; - function max_gas_price() external view returns (uint128); - function minAutoShipThreshold() external view returns (uint128); - function ops() external view returns (address); - function outstandingFLBalance() external view returns (uint256); - function owner() external view returns (address); - function processAutopayJobs(address[] memory autopayRecipients) external; - function redeemOutstandingBalance(address outstandingValidatorWithBalance) external; - function renounceOwnership() external; - function setAutopayBatchSize(uint16 size) external; - function setBidToken(address _bid_token_address) external; - function setFastlaneFee(uint24 _fastLaneFee) external; - function setMinimumAutoShipThreshold(uint128 _minAmount) external; - function setMinimumBidIncrement(uint256 _bid_increment) external; - function setOffchainCheckerDisabledState(bool state) external; - function setOps(address _ops) external; - function setPausedState(bool state) external; - function setResolverMaxGasPrice(uint128 _maxgas) external; - function setStarter(address _starter) external; - function setValidatorPreferences(uint128 _minAutoshipAmount, address _validatorPayableAddress) external; - function startAuction() external; - function storeNFToken(NFToken memory nft) external; - function submitBid(Bid memory bid) external; - function transferOwnership(address newOwner) external; - function viewNFToken() external returns (NFToken memory nft); - function withdrawStuckERC20(address _tokenAddress) external; - function withdrawStuckNativeToken(uint256 amount) external; -} diff --git a/testdata/fixtures/Toml/test.toml b/testdata/fixtures/Toml/test.toml index 806dc2224de46..13c7501dbae1a 100644 --- a/testdata/fixtures/Toml/test.toml +++ b/testdata/fixtures/Toml/test.toml @@ -1,3 +1,7 @@ +nanFloat = nan +infFloat = +inf +neginfFloat = -inf + basicString = "hai" nullString = "null" multilineString = """ @@ -48,3 +52,5 @@ id = 1 [[advancedTomlPath]] id = 2 + + diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev index 893b927f82be2..977c31eec5512 100644 --- a/testdata/forge-std-rev +++ b/testdata/forge-std-rev @@ -1 +1 @@ -77041d2ce690e692d6e03cc812b57d1ddaa4d505 \ No newline at end of file +620536fa5277db4e3fd46772d5cbc1ea0696fb43 \ No newline at end of file diff --git a/testdata/foundry.toml b/testdata/foundry.toml index e9189bb008a32..6307668074860 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -1,55 +1,66 @@ [profile.default] -solc = "0.8.18" -block_base_fee_per_gas = 0 -block_coinbase = "0x0000000000000000000000000000000000000000" -block_difficulty = 0 -block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000" -block_number = 0 -block_timestamp = 0 -bytecode_hash = "ipfs" -cache = true -cache_path = "cache" -evm_version = "paris" -extra_output = [] -extra_output_files = [] -ffi = false -force = false -invariant_fail_on_revert = false -invariant_call_override = false -invariant_shrink_run_limit = 5000 -gas_limit = 9223372036854775807 -gas_price = 0 -gas_reports = ["*"] -ignored_error_codes = [1878] -deny_warnings = false -initial_balance = "0xffffffffffffffffffffffff" -libraries = [] -libs = ["lib"] -names = false -no_storage_caching = false -no_rpc_rate_limit = false -offline = false +src = "src" +test = "./" optimizer = true optimizer_runs = 200 -out = "out" -remappings = ["ds-test/=lib/ds-test/src/"] -sender = "0x00a329c0648769a73afac7f9381e08fb43dbea72" -sizes = false -sparse_mode = false -src = "./" -test = "./" -tx_origin = "0x00a329c0648769a73afac7f9381e08fb43dbea72" -verbosity = 3 via_ir = false -fs_permissions = [{ access = "read-write", path = "./" }] +ignored_error_codes = [ + 1878, # SPDX license identifier not provided + 2018, # Function state mutability can be restricted + 2072, # Unused local variable + 2424, # Natspec memory-safe-assembly special comment deprecated + 2519, # This declaration shadows an existing declaration + 3860, # Contract init code size exceeds limit + 5159, # Selfdestruct has been deprecated + 5574, # Contract code size exceeds limit + 5667, # Unused function parameter + 9207, # 'transfer' is deprecated +] +extra_output = ["storageLayout"] + +additional_compiler_profiles = [ + # paris + { name = "paris", evm_version = "paris" }, +] +compilation_restrictions = [ + # paris + { paths = "paris/**", evm_version = "paris" }, +] + +ffi = true +fs_permissions = [ + { access = "read-write", path = "out/" }, + { access = "read-write", path = "fixtures/" }, +] +verbosity = 3 +prompt_timeout = 0 [profile.default.rpc_storage_caching] chains = "all" endpoints = "all" -[fuzz] -runs = 256 -max_test_rejects = 65536 +[profile.default.invariant] +depth = 15 [fmt] ignore = ["cheats/Vm.sol"] + +[lint] +lint_on_build = false + +# These are set in .env, which is populated when running through `cargo (nex)test`. +[rpc_endpoints] +mainnet = "${RPC_MAINNET}" +mainnet2 = "${RPC_MAINNET2}" +sepolia = "${RPC_SEPOLIA}" +optimism = "${RPC_OPTIMISM}" +base = "${RPC_BASE}" +arbitrum = "${RPC_ARBITRUM}" +polygon = "${RPC_POLYGON}" +bsc = "${RPC_BSC}" +avaxTestnet = "https://api.avax-test.network/ext/bc/C/rpc" +moonbeam = "https://moonbeam-rpc.publicnode.com" +polkadotTestnet = "https://eth-rpc-testnet.polkadot.io" +kusama = "https://eth-rpc-kusama.polkadot.io" +polkadot = "https://eth-rpc.polkadot.io" +rpcEnvAlias = "${RPC_ENV_ALIAS}" diff --git a/testdata/multi-version/Counter.sol b/testdata/multi-version/Counter.sol index 4f0c350335f8e..b2d9774d4b9a8 100644 --- a/testdata/multi-version/Counter.sol +++ b/testdata/multi-version/Counter.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.4; + contract Counter { uint256 public number; diff --git a/testdata/multi-version/cheats/GetCode.t.sol b/testdata/multi-version/cheats/GetCode.t.sol index 72dae24e676af..ecb78876cb053 100644 --- a/testdata/multi-version/cheats/GetCode.t.sol +++ b/testdata/multi-version/cheats/GetCode.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; import "../Counter.sol"; -contract GetCodeTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest is Test { function testGetCodeMultiVersion() public { assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); require( diff --git a/testdata/multi-version/cheats/GetCode17.t.sol b/testdata/multi-version/cheats/GetCode17.t.sol index f8bf4bb2aee28..3cdd42ef3c78a 100644 --- a/testdata/multi-version/cheats/GetCode17.t.sol +++ b/testdata/multi-version/cheats/GetCode17.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity =0.8.17; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; import "../Counter.sol"; // Same as GetCode.t.sol but for 0.8.17 version -contract GetCodeTest17 is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GetCodeTest17 is Test { function testGetCodeMultiVersion() public { assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); require( diff --git a/testdata/paris/cheats/Fork.t.sol b/testdata/paris/cheats/Fork.t.sol index 2f2e627de131a..281a27a0b868c 100644 --- a/testdata/paris/cheats/Fork.t.sol +++ b/testdata/paris/cheats/Fork.t.sol @@ -1,19 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; interface IWETH { function deposit() external payable; function balanceOf(address) external view returns (uint256); } -contract ForkTest is DSTest { +contract ForkTest is Test { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; uint256 constant mainblock = 14_608_400; - Vm constant vm = Vm(HEVM_ADDRESS); IWETH WETH = IWETH(WETH_TOKEN_ADDR); uint256 forkA; diff --git a/testdata/paris/cheats/GasSnapshots.t.sol b/testdata/paris/cheats/GasSnapshots.t.sol index 98abfa3e48efe..a3f1c9921a4b5 100644 --- a/testdata/paris/cheats/GasSnapshots.t.sol +++ b/testdata/paris/cheats/GasSnapshots.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract GasSnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract GasSnapshotTest is Test { uint256 public slot0; Flare public flare; @@ -94,7 +91,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionDefaultGroupStop() public { vm.startSnapshotGas("testSnapshotGasSection"); - flare.run(256); + flare.run(8); // vm.stopSnapshotGas() will use the last snapshot name. uint256 gasUsed = vm.stopSnapshotGas(); @@ -105,7 +102,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionCustomGroupStop() public { vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); - flare.run(256); + flare.run(8); // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. uint256 gasUsed = vm.stopSnapshotGas(); @@ -116,7 +113,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionName() public { vm.startSnapshotGas("testSnapshotGasSectionName"); - flare.run(256); + flare.run(8); uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); assertGt(gasUsed, 0); @@ -126,7 +123,7 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionGroupName() public { vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); - flare.run(256); + flare.run(8); uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); @@ -149,9 +146,7 @@ contract GasSnapshotTest is DSTest { } } -contract GasComparisonTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract GasComparisonTest is Test { uint256 public slot0; uint256 public slot1; @@ -162,7 +157,7 @@ contract GasComparisonTest is DSTest { vm.startSnapshotGas("ComparisonGroup", "testGasComparisonEmptyA"); uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); uint256 b = _snapEnd(); vm.snapshotValue("ComparisonGroup", "testGasComparisonEmptyB", b); @@ -176,7 +171,7 @@ contract GasComparisonTest is DSTest { slot0 = 1; uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); slot1 = 1; uint256 b = _snapEnd(); @@ -194,7 +189,7 @@ contract GasComparisonTest is DSTest { slot0 = 2; uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); slot0 = 3; uint256 b = _snapEnd(); @@ -213,7 +208,7 @@ contract GasComparisonTest is DSTest { target.update(2); uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); target.update(3); uint256 b = _snapEnd(); @@ -228,7 +223,7 @@ contract GasComparisonTest is DSTest { new TargetC(); uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); new TargetC(); uint256 b = _snapEnd(); @@ -247,7 +242,7 @@ contract GasComparisonTest is DSTest { target.update(2); uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); target.update(3); uint256 b = _snapEnd(); @@ -263,12 +258,12 @@ contract GasComparisonTest is DSTest { // Start a cheatcode snapshot. vm.startSnapshotGas("ComparisonGroup", "testGasComparisonFlareA"); - flare.run(256); + flare.run(8); uint256 a = vm.stopSnapshotGas(); - // Start a comparitive Solidity snapshot. + // Start a comparative Solidity snapshot. _snapStart(); - flare.run(256); + flare.run(8); uint256 b = _snapEnd(); vm.snapshotValue("ComparisonGroup", "testGasComparisonFlareB", b); diff --git a/testdata/paris/cheats/LastCallGas.t.sol b/testdata/paris/cheats/LastCallGas.t.sol index 23f6df224963f..8c2c20ee8c41e 100644 --- a/testdata/paris/cheats/LastCallGas.t.sol +++ b/testdata/paris/cheats/LastCallGas.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; +import "utils/Test.sol"; contract Target { uint256 public slot0; @@ -28,8 +27,7 @@ contract Target { fallback() external {} } -abstract contract LastCallGasFixture is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +abstract contract LastCallGasFixture is Test { Target public target; struct Gas { @@ -67,6 +65,7 @@ abstract contract LastCallGasFixture is DSTest { } } +/// forge-config: default.isolate = true contract LastCallGasIsolatedTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); diff --git a/testdata/paris/core/BeforeTest.t.sol b/testdata/paris/core/BeforeTest.t.sol index 2b14bcad1d2ef..10acfb53ee88f 100644 --- a/testdata/paris/core/BeforeTest.t.sol +++ b/testdata/paris/core/BeforeTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; +import "utils/Test.sol"; contract SelfDestructor { function kill() external { @@ -10,7 +10,7 @@ contract SelfDestructor { } // https://github.com/foundry-rs/foundry/issues/1543 -contract BeforeTestSelfDestructTest is DSTest { +contract BeforeTestSelfDestructTest is Test { SelfDestructor killer; uint256 a; uint256 b; @@ -46,16 +46,18 @@ contract BeforeTestSelfDestructTest is DSTest { function kill_contract() external { uint256 killer_size = getSize(address(killer)); - require(killer_size == 106); + assertEq(killer_size, 106); killer.kill(); + assertEq(killer_size, 106); } - function testKill() public view { + /// forge-config: default.evm_version = "paris" + function testKill() public { uint256 killer_size = getSize(address(killer)); - require(killer_size == 0); + assertEq(killer_size, 0); } - function getSize(address c) public view returns (uint32) { + function getSize(address c) internal view returns (uint32) { uint32 size; assembly { size := extcodesize(c) @@ -64,12 +66,12 @@ contract BeforeTestSelfDestructTest is DSTest { } function testA() public { - require(a <= 3); + assertLe(a, 3); a += 1; } - function testSimpleA() public view { - require(a == 0); + function testSimpleA() public { + assertEq(a, 0); } function setB() public { @@ -77,7 +79,7 @@ contract BeforeTestSelfDestructTest is DSTest { } function testB() public { - require(b == 100); + assertEq(b, 100); } function setBWithValue(uint256 value) public { diff --git a/testdata/paris/fork/Transact.t.sol b/testdata/paris/fork/Transact.t.sol index 92d595f98c516..6f7ca7f03a282 100644 --- a/testdata/paris/fork/Transact.t.sol +++ b/testdata/paris/fork/Transact.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; -import "../../default/logs/console.sol"; +import "utils/Test.sol"; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); @@ -11,9 +9,7 @@ interface IERC20 { function balanceOf(address account) external view returns (uint256); } -contract TransactOnForkTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - +contract TransactOnForkTest is Test { IERC20 constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); event Transfer(address indexed from, address indexed to, uint256 value); @@ -23,7 +19,7 @@ contract TransactOnForkTest is DSTest { uint256 fork = vm.createFork("mainnet", 17134913); vm.selectFork(fork); // a random transfer transaction in the next block: https://etherscan.io/tx/0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771 - bytes32 tx = 0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771; + bytes32 transaction = 0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771; address sender = address(0x9B315A70FEe05a70A9F2c832E93a7095FEb32Bfe); address recipient = address(0xDB358B93157Df9b3B1eE9Ea5CDB7D0aE9a1D8110); @@ -37,7 +33,7 @@ contract TransactOnForkTest is DSTest { uint256 expectedSenderBalance = sender.balance - transferAmount; // execute the transaction - vm.transact(tx); + vm.transact(transaction); // recipient received transfer assertEq(recipient.balance, expectedRecipientBalance); @@ -52,7 +48,7 @@ contract TransactOnForkTest is DSTest { vm.selectFork(fork); // a random ERC20 USDT transfer transaction in the next block: https://etherscan.io/tx/0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c - bytes32 tx = 0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c; + bytes32 transaction = 0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c; address sender = address(0x2e09BB78B3D64d98Da44D1C776fa77dcd133ED54); address recipient = address(0x23a6B9711B711b1d404F2AA740bde350c67a6F06); @@ -83,7 +79,7 @@ contract TransactOnForkTest is DSTest { vm.recordLogs(); // execute the transaction - vm.transact(tx); + vm.transact(transaction); // extract recorded logs Vm.Log[] memory logs = vm.getRecordedLogs(); diff --git a/testdata/paris/spec/ShanghaiCompat.t.sol b/testdata/paris/spec/ShanghaiCompat.t.sol index fd7213b3d0702..7ff6405d16ccf 100644 --- a/testdata/paris/spec/ShanghaiCompat.t.sol +++ b/testdata/paris/spec/ShanghaiCompat.t.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -contract ShanghaiCompat is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); +import "utils/Test.sol"; +contract ShanghaiCompat is Test { function testPush0() public { address target = address(uint160(uint256(0xc4f3))); diff --git a/testdata/default/vyper/Counter.vy b/testdata/src/Counter.vy similarity index 100% rename from testdata/default/vyper/Counter.vy rename to testdata/src/Counter.vy diff --git a/testdata/default/vyper/ICounter.vyi b/testdata/src/ICounter.vyi similarity index 100% rename from testdata/default/vyper/ICounter.vyi rename to testdata/src/ICounter.vyi diff --git a/testdata/lib/ds-test/src/test.sol b/testdata/utils/DSTest.sol similarity index 100% rename from testdata/lib/ds-test/src/test.sol rename to testdata/utils/DSTest.sol diff --git a/testdata/utils/Test.sol b/testdata/utils/Test.sol new file mode 100644 index 0000000000000..6c7930bc3088e --- /dev/null +++ b/testdata/utils/Test.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./DSTest.sol"; +import "./Vm.sol"; +import "./console.sol"; + +contract Test is DSTest { + Vm public constant vm = Vm(HEVM_ADDRESS); +} diff --git a/testdata/cheats/Vm.sol b/testdata/utils/Vm.sol similarity index 88% rename from testdata/cheats/Vm.sol rename to testdata/utils/Vm.sol index 6d054abbfc6ec..e488a1820453e 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/utils/Vm.sol @@ -19,7 +19,7 @@ interface Vm { struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } struct ChainInfo { uint256 forkId; uint256 chainId; } struct Chain { string name; uint256 chainId; string chainAlias; string rpcUrl; } - struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } + struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; uint64 oldNonce; uint64 newNonce; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } struct DebugStep { uint256[] stack; bytes memoryInput; uint8 opcode; uint64 depth; bool isOutOfGas; address contractAddr; } @@ -31,126 +31,126 @@ interface Vm { function _expectCheatcodeRevert(bytes4 revertData) external; function _expectCheatcodeRevert(bytes calldata revertData) external; function accessList(AccessListItem[] calldata access) external; - function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); + function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); function activeFork() external view returns (uint256 forkId); function addr(uint256 privateKey) external pure returns (address keyAddr); function allowCheatcodes(address account) external; function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; - function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; - function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; - function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata err) external pure; function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; - function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata err) external pure; function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure; - function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure; - function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata err) external pure; function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; - function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata err) external pure; function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; - function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata err) external pure; function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertEq(bool left, bool right) external pure; - function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(bool left, bool right, string calldata err) external pure; function assertEq(string calldata left, string calldata right) external pure; - function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right, string calldata err) external pure; function assertEq(bytes calldata left, bytes calldata right) external pure; - function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata err) external pure; function assertEq(bool[] calldata left, bool[] calldata right) external pure; - function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; - function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; function assertEq(int256[] calldata left, int256[] calldata right) external pure; - function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; function assertEq(uint256 left, uint256 right) external pure; function assertEq(address[] calldata left, address[] calldata right) external pure; - function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata err) external pure; function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; - function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; function assertEq(string[] calldata left, string[] calldata right) external pure; - function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata err) external pure; function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; - function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; - function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; + function assertEq(uint256 left, uint256 right, string calldata err) external pure; function assertEq(int256 left, int256 right) external pure; - function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right, string calldata err) external pure; function assertEq(address left, address right) external pure; - function assertEq(address left, address right, string calldata error) external pure; + function assertEq(address left, address right, string calldata err) external pure; function assertEq(bytes32 left, bytes32 right) external pure; - function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata err) external pure; function assertFalse(bool condition) external pure; - function assertFalse(bool condition, string calldata error) external pure; + function assertFalse(bool condition, string calldata err) external pure; function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertGe(uint256 left, uint256 right) external pure; - function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(uint256 left, uint256 right, string calldata err) external pure; function assertGe(int256 left, int256 right) external pure; - function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right, string calldata err) external pure; function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertGt(uint256 left, uint256 right) external pure; - function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(uint256 left, uint256 right, string calldata err) external pure; function assertGt(int256 left, int256 right) external pure; - function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right, string calldata err) external pure; function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertLe(uint256 left, uint256 right) external pure; - function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(uint256 left, uint256 right, string calldata err) external pure; function assertLe(int256 left, int256 right) external pure; - function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right, string calldata err) external pure; function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertLt(uint256 left, uint256 right) external pure; - function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(uint256 left, uint256 right, string calldata err) external pure; function assertLt(int256 left, int256 right) external pure; - function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right, string calldata err) external pure; function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; - function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata err) external pure; function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; - function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata err) external pure; function assertNotEq(bool left, bool right) external pure; - function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(bool left, bool right, string calldata err) external pure; function assertNotEq(string calldata left, string calldata right) external pure; - function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata err) external pure; function assertNotEq(bytes calldata left, bytes calldata right) external pure; - function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata err) external pure; function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; - function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata err) external pure; function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; - function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata err) external pure; function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; - function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata err) external pure; function assertNotEq(uint256 left, uint256 right) external pure; function assertNotEq(address[] calldata left, address[] calldata right) external pure; - function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata err) external pure; function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; - function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata err) external pure; function assertNotEq(string[] calldata left, string[] calldata right) external pure; - function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata err) external pure; function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; - function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; - function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata err) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata err) external pure; function assertNotEq(int256 left, int256 right) external pure; - function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right, string calldata err) external pure; function assertNotEq(address left, address right) external pure; - function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(address left, address right, string calldata err) external pure; function assertNotEq(bytes32 left, bytes32 right) external pure; - function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata err) external pure; function assertTrue(bool condition) external pure; - function assertTrue(bool condition, string calldata error) external pure; + function assertTrue(bool condition, string calldata err) external pure; function assume(bool condition) external pure; function assumeNoRevert() external pure; function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; @@ -160,6 +160,8 @@ interface Vm { function attachDelegation(SignedDelegation calldata signedDelegation, bool crossChain) external; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; + function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256); + function bound(int256 current, int256 min, int256 max) external view returns (int256); function breakpoint(string calldata char) external pure; function breakpoint(string calldata char, bool value) external pure; function broadcastRawTransaction(bytes calldata data) external; @@ -174,12 +176,13 @@ interface Vm { function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); - function contains(string calldata subject, string calldata search) external returns (bool result); + function contains(string calldata subject, string calldata search) external pure returns (bool result); function cool(address target) external; function coolSlot(address target, bytes32 slot) external; function copyFile(string calldata from, string calldata to) external returns (uint64 copied); function copyStorage(address from, address to) external; function createDir(string calldata path, bool recursive) external; + function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey); function createFork(string calldata urlOrAlias) external returns (uint256 forkId); function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); @@ -189,6 +192,7 @@ interface Vm { function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); function createWallet(uint256 privateKey) external returns (Wallet memory wallet); function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); + function currentFilePath() external view returns (string memory path); function deal(address account, uint256 newBalance) external; function deleteSnapshot(uint256 snapshotId) external returns (bool success); function deleteSnapshots() external; @@ -208,6 +212,11 @@ interface Vm { function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey); function difficulty(uint256 newDifficulty) external; function dumpState(string calldata pathToStateJson) external; + function eip712HashStruct(string calldata typeNameOrDefinition, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + function eip712HashStruct(string calldata bindingsPath, string calldata typeName, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + function eip712HashType(string calldata typeNameOrDefinition) external pure returns (bytes32 typeHash); + function eip712HashType(string calldata bindingsPath, string calldata typeName) external pure returns (bytes32 typeHash); + function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest); function ensNamehash(string calldata name) external pure returns (bytes32); function envAddress(string calldata name) external view returns (address value); function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); @@ -239,7 +248,8 @@ interface Vm { function envUint(string calldata name) external view returns (uint256 value); function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); function etch(address target, bytes calldata newRuntimeBytecode) external; - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external view returns (EthGetLogs[] memory logs); + function executeTransaction(bytes calldata rawTx) external returns (bytes memory); function exists(string calldata path) external view returns (bool result); function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; @@ -283,6 +293,7 @@ interface Vm { function ffi(string[] calldata commandInput) external returns (bytes memory result); function foundryVersionAtLeast(string calldata version) external view returns (bool); function foundryVersionCmp(string calldata version) external view returns (int256); + function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data); function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); function getArtifactPathByCode(bytes calldata code) external view returns (string memory path); function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path); @@ -293,6 +304,7 @@ interface Vm { function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getChainId() external view returns (uint256 blockChainId); function getChain(string calldata chainAlias) external view returns (Chain memory chain); function getChain(uint256 chainId) external view returns (Chain memory chain); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); @@ -300,17 +312,22 @@ interface Vm { function getDeployment(string calldata contractName) external view returns (address deployedAddress); function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getEvmVersion() external pure returns (string memory evm); function getFoundryVersion() external view returns (string memory version); function getLabel(address account) external view returns (string memory currentLabel); - function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent); - function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); - function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); + function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external view returns (bool found, bytes32 key, bytes32 parent); + function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length); + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value); function getNonce(address account) external view returns (uint64 nonce); - function getNonce(Wallet calldata wallet) external returns (uint64 nonce); - function getRecordedLogs() external returns (Log[] memory logs); + function getNonce(Wallet calldata wallet) external view returns (uint64 nonce); + function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader); + function getRecordedLogs() external view returns (Log[] memory logs); + function getRecordedLogsJson() external view returns (string memory logsJson); function getStateDiff() external view returns (string memory diff); function getStateDiffJson() external view returns (string memory diff); - function getWallets() external returns (address[] memory wallets); + function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses); + function getStorageSlots(address target, string calldata variableName) external view returns (uint256[] memory slots); + function getWallets() external view returns (address[] memory wallets); function indexOf(string calldata input, string calldata key) external pure returns (uint256); function interceptInitcode() external; function isContext(ForgeContext context) external view returns (bool result); @@ -400,18 +417,19 @@ interface Vm { function promptSecret(string calldata promptText) external returns (string memory input); function promptSecretUint(string calldata promptText) external returns (uint256); function promptUint(string calldata promptText) external returns (uint256); + function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey); function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); - function randomAddress() external returns (address); + function randomAddress() external view returns (address); function randomBool() external view returns (bool); function randomBytes(uint256 len) external view returns (bytes memory); function randomBytes4() external view returns (bytes4); function randomBytes8() external view returns (bytes8); function randomInt() external view returns (int256); function randomInt(uint256 bits) external view returns (int256); - function randomUint() external returns (uint256); - function randomUint(uint256 min, uint256 max) external returns (uint256); + function randomUint() external view returns (uint256); + function randomUint(uint256 min, uint256 max) external view returns (uint256); function randomUint(uint256 bits) external view returns (uint256); - function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin); function readDir(string calldata path) external view returns (DirEntry[] memory entries); function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries); @@ -429,6 +447,7 @@ interface Vm { function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); function resetGasMetering() external; function resetNonce(address account) external; + function resolveEnv(string calldata input) external returns (string memory); function resumeGasMetering() external; function resumeTracing() external view; function revertTo(uint256 snapshotId) external returns (bool success); @@ -470,21 +489,25 @@ interface Vm { function setArbitraryStorage(address target, bool overwrite) external; function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; function setEnv(string calldata name, string calldata value) external; + function setEvmVersion(string calldata evm) external; function setNonce(address account, uint64 newNonce) external; function setNonceUnsafe(address account, uint64 newNonce) external; + function setSeed(uint256 seed) external; function shuffle(uint256[] calldata array) external returns (uint256[] memory); function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); function signAndAttachDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); - function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); function signDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); + function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature); function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); - function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); @@ -527,6 +550,7 @@ interface Vm { function toBase64(bytes calldata data) external pure returns (string memory); function toBase64(string calldata data) external pure returns (string memory); function toLowercase(string calldata input) external pure returns (string memory output); + function toRlp(bytes[] calldata data) external pure returns (bytes memory); function toString(address value) external pure returns (string memory stringifiedValue); function toString(bytes calldata value) external pure returns (string memory stringifiedValue); function toString(bytes32 value) external pure returns (string memory stringifiedValue); @@ -540,6 +564,7 @@ interface Vm { function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); function txGasPrice(uint256 newGasPrice) external; function unixTime() external view returns (uint256 milliseconds); + function verifyEd25519(bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey) external pure returns (bool valid); function warmSlot(address target, bytes32 slot) external; function warp(uint256 newTimestamp) external; function writeFile(string calldata path, string calldata data) external; diff --git a/testdata/default/logs/console.sol b/testdata/utils/console.sol similarity index 100% rename from testdata/default/logs/console.sol rename to testdata/utils/console.sol diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000000000..136333b3b57e3 --- /dev/null +++ b/typos.toml @@ -0,0 +1,25 @@ +[files] +extend-exclude = [ + ".git", + "target", + "testdata", + "Cargo.toml", + "Cargo.lock", + "*.json", + "*.js", + "*.css", + "*.html", + "**/tests/**", + "**/test/**", + "**/*_test.*", + "**/*_tests.*", +] + +[default.extend-words] +ser = "ser" +ratatui = "ratatui" +Caf = "Caf" +froms = "froms" +strat = "strat" +ba = "ba" +consts = "consts" From 8511082e7f43833f4f47b5151c1e43b3bff0d220 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 10 May 2026 17:14:11 +0700 Subject: [PATCH 390/391] Update benches/src/lib.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- benches/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 8faaa8c87ccff..f64c8476c1f9b 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -148,7 +148,7 @@ impl BenchmarkProject { Err(_) => continue, // Skip if unable to canonicalize }; // Ensure canonicalized path stays strictly within root_path (TempProject root) - if !canon.starts_with(root_path.canonicalize().unwrap_or_else(|_| root_path.clone())) { + if !canon.starts_with(&root_path) { sh_eprintln!("⚠️ Skipping suspicious path during cleanup: {:?}", canon); continue; } From 8ea81cfe54870eb7b7bddd0ad6521a40c8f7ded7 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 10 May 2026 17:16:20 +0700 Subject: [PATCH 391/391] chore(ci): increase nightly bench result retention days (#14681) (#551) Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> --- .github/workflows/benchmarks-nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks-nightly.yml b/.github/workflows/benchmarks-nightly.yml index 4c24c79980691..1b8272419681c 100644 --- a/.github/workflows/benchmarks-nightly.yml +++ b/.github/workflows/benchmarks-nightly.yml @@ -235,7 +235,7 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: bench-results - retention-days: 7 + retention-days: 45 path: | benches/stable-*.json benches/nightly-*.json